summaryrefslogtreecommitdiffstats
path: root/vcl
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /vcl
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl')
-rw-r--r--vcl/AllLangMoTarget_vcl.mk11
-rw-r--r--vcl/CppunitTest_vcl_animation.mk45
-rw-r--r--vcl/CppunitTest_vcl_apitests.mk64
-rw-r--r--vcl/CppunitTest_vcl_app_test.mk44
-rw-r--r--vcl/CppunitTest_vcl_backend_test.mk51
-rw-r--r--vcl/CppunitTest_vcl_bitmap_render_test.mk48
-rw-r--r--vcl/CppunitTest_vcl_bitmap_test.mk55
-rw-r--r--vcl/CppunitTest_vcl_bitmapprocessor_test.mk55
-rw-r--r--vcl/CppunitTest_vcl_blocklistparser_test.mk45
-rw-r--r--vcl/CppunitTest_vcl_cjk.mk69
-rw-r--r--vcl/CppunitTest_vcl_dialogs_test.mk68
-rw-r--r--vcl/CppunitTest_vcl_drawmode.mk47
-rw-r--r--vcl/CppunitTest_vcl_errorhandler.mk49
-rw-r--r--vcl/CppunitTest_vcl_filter_igif.mk46
-rw-r--r--vcl/CppunitTest_vcl_filter_ipdf.mk56
-rw-r--r--vcl/CppunitTest_vcl_filters_test.mk73
-rw-r--r--vcl/CppunitTest_vcl_font.mk64
-rw-r--r--vcl/CppunitTest_vcl_fontcharmap.mk49
-rw-r--r--vcl/CppunitTest_vcl_fontfeature.mk51
-rw-r--r--vcl/CppunitTest_vcl_fontmetric.mk55
-rw-r--r--vcl/CppunitTest_vcl_gen.mk44
-rw-r--r--vcl/CppunitTest_vcl_gradient.mk50
-rw-r--r--vcl/CppunitTest_vcl_graphic_test.mk56
-rw-r--r--vcl/CppunitTest_vcl_gtk3_a11y.mk63
-rw-r--r--vcl/CppunitTest_vcl_jpeg_read_write_test.mk51
-rw-r--r--vcl/CppunitTest_vcl_lifecycle.mk60
-rw-r--r--vcl/CppunitTest_vcl_mnemonic.mk49
-rw-r--r--vcl/CppunitTest_vcl_outdev.mk50
-rw-r--r--vcl/CppunitTest_vcl_pdfexport.mk52
-rw-r--r--vcl/CppunitTest_vcl_pdfexport2.mk52
-rw-r--r--vcl/CppunitTest_vcl_pdfium_library_test.mk46
-rw-r--r--vcl/CppunitTest_vcl_png_test.mk54
-rw-r--r--vcl/CppunitTest_vcl_skia.mk51
-rw-r--r--vcl/CppunitTest_vcl_svm_test.mk57
-rw-r--r--vcl/CppunitTest_vcl_text.mk66
-rw-r--r--vcl/CppunitTest_vcl_timer.mk48
-rw-r--r--vcl/CppunitTest_vcl_type_serializer_test.mk47
-rw-r--r--vcl/CppunitTest_vcl_widget_definition_reader_test.mk52
-rw-r--r--vcl/CustomTarget_gtk3_kde5_moc.mk24
-rw-r--r--vcl/CustomTarget_kf5_moc.mk23
-rw-r--r--vcl/CustomTarget_kf6_moc.mk29
-rw-r--r--vcl/CustomTarget_nativecalc.mk18
-rw-r--r--vcl/CustomTarget_nativecore.mk18
-rw-r--r--vcl/CustomTarget_nativedraw.mk18
-rw-r--r--vcl/CustomTarget_nativemath.mk18
-rw-r--r--vcl/CustomTarget_nativewriter.mk18
-rw-r--r--vcl/CustomTarget_qt5_moc.mk32
-rw-r--r--vcl/CustomTarget_qt6_moc.mk38
-rw-r--r--vcl/Executable_602fuzzer.mk46
-rw-r--r--vcl/Executable_bmpfuzzer.mk45
-rw-r--r--vcl/Executable_cgmfuzzer.mk46
-rw-r--r--vcl/Executable_dbffuzzer.mk48
-rw-r--r--vcl/Executable_diffuzzer.mk46
-rw-r--r--vcl/Executable_docxfuzzer.mk50
-rw-r--r--vcl/Executable_dxffuzzer.mk45
-rw-r--r--vcl/Executable_epsfuzzer.mk45
-rw-r--r--vcl/Executable_fftester.mk37
-rw-r--r--vcl/Executable_fodpfuzzer.mk48
-rw-r--r--vcl/Executable_fodsfuzzer.mk48
-rw-r--r--vcl/Executable_fodt2pdffuzzer.mk50
-rw-r--r--vcl/Executable_fodtfuzzer.mk50
-rw-r--r--vcl/Executable_giffuzzer.mk45
-rw-r--r--vcl/Executable_htmlfuzzer.mk50
-rw-r--r--vcl/Executable_hwpfuzzer.mk46
-rw-r--r--vcl/Executable_icontest.mk37
-rw-r--r--vcl/Executable_jpgfuzzer.mk45
-rw-r--r--vcl/Executable_listfonts.mk36
-rw-r--r--vcl/Executable_listglyphs.mk41
-rw-r--r--vcl/Executable_lo_kde5filepicker.mk89
-rw-r--r--vcl/Executable_lwpfuzzer.mk46
-rw-r--r--vcl/Executable_metfuzzer.mk45
-rw-r--r--vcl/Executable_minvcl.mk38
-rw-r--r--vcl/Executable_mmlfuzzer.mk48
-rw-r--r--vcl/Executable_mtfdemo.mk43
-rw-r--r--vcl/Executable_mtpfuzzer.mk46
-rw-r--r--vcl/Executable_olefuzzer.mk45
-rw-r--r--vcl/Executable_pcdfuzzer.mk45
-rw-r--r--vcl/Executable_pctfuzzer.mk45
-rw-r--r--vcl/Executable_pcxfuzzer.mk45
-rw-r--r--vcl/Executable_pngfuzzer.mk45
-rw-r--r--vcl/Executable_ppmfuzzer.mk45
-rw-r--r--vcl/Executable_pptfuzzer.mk46
-rw-r--r--vcl/Executable_pptxfuzzer.mk48
-rw-r--r--vcl/Executable_psdfuzzer.mk45
-rw-r--r--vcl/Executable_qpwfuzzer.mk46
-rw-r--r--vcl/Executable_rasfuzzer.mk45
-rw-r--r--vcl/Executable_rtffuzzer.mk46
-rw-r--r--vcl/Executable_scrtffuzzer.mk46
-rw-r--r--vcl/Executable_sftfuzzer.mk45
-rw-r--r--vcl/Executable_slkfuzzer.mk46
-rw-r--r--vcl/Executable_svdemo.mk38
-rw-r--r--vcl/Executable_svgfuzzer.mk45
-rw-r--r--vcl/Executable_svmfuzzer.mk45
-rw-r--r--vcl/Executable_svpclient.mk44
-rw-r--r--vcl/Executable_svptest.mk36
-rw-r--r--vcl/Executable_tgafuzzer.mk45
-rw-r--r--vcl/Executable_tiffuzzer.mk45
-rw-r--r--vcl/Executable_vcldemo.mk46
-rw-r--r--vcl/Executable_visualbackendtest.mk40
-rw-r--r--vcl/Executable_webpfuzzer.mk45
-rw-r--r--vcl/Executable_wksfuzzer.mk48
-rw-r--r--vcl/Executable_wmffuzzer.mk45
-rw-r--r--vcl/Executable_ww2fuzzer.mk46
-rw-r--r--vcl/Executable_ww6fuzzer.mk46
-rw-r--r--vcl/Executable_ww8fuzzer.mk46
-rw-r--r--vcl/Executable_xbmfuzzer.mk45
-rw-r--r--vcl/Executable_xlsfuzzer.mk48
-rw-r--r--vcl/Executable_xlsxfuzzer.mk48
-rw-r--r--vcl/Executable_xpmfuzzer.mk45
-rw-r--r--vcl/Executable_zipfuzzer.mk45
-rw-r--r--vcl/IwyuFilter_vcl.yaml118
-rw-r--r--vcl/Library_desktop_detector.mk73
-rw-r--r--vcl/Library_vcl.mk745
-rw-r--r--vcl/Library_vclplug_gen.mk154
-rw-r--r--vcl/Library_vclplug_gtk3.mk126
-rw-r--r--vcl/Library_vclplug_gtk3_kde5.mk131
-rw-r--r--vcl/Library_vclplug_gtk4.mk115
-rw-r--r--vcl/Library_vclplug_kf5.mk85
-rw-r--r--vcl/Library_vclplug_kf6.mk86
-rw-r--r--vcl/Library_vclplug_osx.mk165
-rw-r--r--vcl/Library_vclplug_qt5.mk130
-rw-r--r--vcl/Library_vclplug_qt6.mk129
-rw-r--r--vcl/Library_vclplug_win.mk143
-rw-r--r--vcl/Makefile14
-rw-r--r--vcl/Module_vcl.mk294
-rw-r--r--vcl/Package_fontunxppds.mk25
-rw-r--r--vcl/Package_fontunxpsprint.mk25
-rw-r--r--vcl/Package_opengl_denylist.mk16
-rw-r--r--vcl/Package_osxres.mk18
-rw-r--r--vcl/Package_skia_denylist.mk16
-rw-r--r--vcl/Package_theme_definitions.mk57
-rw-r--r--vcl/Package_tipoftheday.mk27
-rw-r--r--vcl/Package_toolbarmode.mk24
-rw-r--r--vcl/README.GDIMetaFile.md182
-rw-r--r--vcl/README.lifecycle.md357
-rw-r--r--vcl/README.md264
-rw-r--r--vcl/README.scheduler.md448
-rw-r--r--vcl/README.vars.md74
-rw-r--r--vcl/StaticLibrary_fuzzer_calc.mk25
-rw-r--r--vcl/StaticLibrary_fuzzer_core.mk25
-rw-r--r--vcl/StaticLibrary_fuzzer_draw.mk25
-rw-r--r--vcl/StaticLibrary_fuzzer_math.mk25
-rw-r--r--vcl/StaticLibrary_fuzzer_writer.mk25
-rw-r--r--vcl/StaticLibrary_vclmain.mk42
-rw-r--r--vcl/UIConfig_vcl.mk39
-rw-r--r--vcl/WinResTarget_vcl.mk98
-rw-r--r--vcl/android/androidinst.cxx197
-rw-r--r--vcl/backendtest/GraphicsRenderTests.cxx2595
-rw-r--r--vcl/backendtest/VisualBackendTest.cxx886
-rw-r--r--vcl/backendtest/outputdevice/bitmap.cxx180
-rw-r--r--vcl/backendtest/outputdevice/clip.cxx82
-rw-r--r--vcl/backendtest/outputdevice/common.cxx1668
-rw-r--r--vcl/backendtest/outputdevice/gradient.cxx131
-rw-r--r--vcl/backendtest/outputdevice/line.cxx278
-rw-r--r--vcl/backendtest/outputdevice/outputdevice.cxx112
-rw-r--r--vcl/backendtest/outputdevice/pixel.cxx82
-rw-r--r--vcl/backendtest/outputdevice/polygon.cxx257
-rw-r--r--vcl/backendtest/outputdevice/polyline.cxx245
-rw-r--r--vcl/backendtest/outputdevice/polyline_b2d.cxx192
-rw-r--r--vcl/backendtest/outputdevice/polypolygon.cxx212
-rw-r--r--vcl/backendtest/outputdevice/polypolygon_b2d.cxx199
-rw-r--r--vcl/backendtest/outputdevice/rectangle.cxx142
-rw-r--r--vcl/backendtest/outputdevice/text.cxx34
-rw-r--r--vcl/commonfuzzer.mk192
-rw-r--r--vcl/headless/BitmapHelper.cxx226
-rw-r--r--vcl/headless/CairoCommon.cxx2208
-rw-r--r--vcl/headless/SvpGraphicsBackend.cxx287
-rw-r--r--vcl/headless/headlessinst.cxx55
-rw-r--r--vcl/headless/svpbmp.cxx272
-rw-r--r--vcl/headless/svpdummies.cxx58
-rw-r--r--vcl/headless/svpframe.cxx502
-rw-r--r--vcl/headless/svpgdi.cxx98
-rw-r--r--vcl/headless/svpinst.cxx553
-rw-r--r--vcl/headless/svpprn.cxx268
-rw-r--r--vcl/headless/svptext.cxx80
-rw-r--r--vcl/headless/svpvd.cxx157
-rw-r--r--vcl/inc/BitmapSymmetryCheck.hxx31
-rw-r--r--vcl/inc/ContextVBox.hxx38
-rw-r--r--vcl/inc/ControlCacheKey.hxx94
-rw-r--r--vcl/inc/DropdownBox.hxx51
-rw-r--r--vcl/inc/FileDefinitionWidgetDraw.hxx82
-rw-r--r--vcl/inc/IPrioritable.hxx54
-rw-r--r--vcl/inc/IconThemeScanner.hxx86
-rw-r--r--vcl/inc/IconThemeSelector.hxx98
-rw-r--r--vcl/inc/ImplLayoutArgs.hxx77
-rw-r--r--vcl/inc/ImplLayoutRuns.hxx48
-rw-r--r--vcl/inc/ImplOutDevData.hxx51
-rw-r--r--vcl/inc/NotebookbarPopup.hxx54
-rw-r--r--vcl/inc/OptionalBox.hxx39
-rw-r--r--vcl/inc/PriorityHBox.hxx60
-rw-r--r--vcl/inc/PriorityMergedHBox.hxx48
-rw-r--r--vcl/inc/ResampleKernel.hxx116
-rw-r--r--vcl/inc/SalGradient.hxx37
-rw-r--r--vcl/inc/TextLayoutCache.hxx87
-rw-r--r--vcl/inc/WidgetDrawInterface.hxx126
-rw-r--r--vcl/inc/WidgetThemeLibraryTypes.hxx235
-rw-r--r--vcl/inc/accel.hxx93
-rw-r--r--vcl/inc/accmgr.hxx53
-rw-r--r--vcl/inc/android/androidinst.hxx50
-rw-r--r--vcl/inc/animate/AnimationRenderer.hxx93
-rw-r--r--vcl/inc/bitmap/BitmapColorizeFilter.hxx30
-rw-r--r--vcl/inc/bitmap/BitmapDisabledImageFilter.hxx26
-rw-r--r--vcl/inc/bitmap/BitmapFastScaleFilter.hxx36
-rw-r--r--vcl/inc/bitmap/BitmapInterpolateScaleFilter.hxx35
-rw-r--r--vcl/inc/bitmap/BitmapLightenFilter.hxx24
-rw-r--r--vcl/inc/bitmap/BitmapMaskToAlphaFilter.hxx21
-rw-r--r--vcl/inc/bitmap/BitmapScaleConvolutionFilter.hxx78
-rw-r--r--vcl/inc/bitmap/BitmapScaleSuperFilter.hxx40
-rw-r--r--vcl/inc/bitmap/Octree.hxx83
-rw-r--r--vcl/inc/bitmap/ScanlineTools.hxx236
-rw-r--r--vcl/inc/bitmap/bmpfast.hxx51
-rw-r--r--vcl/inc/bitmap/impoctree.hxx110
-rw-r--r--vcl/inc/bitmaps.hlst229
-rw-r--r--vcl/inc/brdwin.hxx290
-rw-r--r--vcl/inc/bubblewindow.hxx56
-rw-r--r--vcl/inc/calendar.hxx228
-rw-r--r--vcl/inc/canvasbitmap.hxx123
-rw-r--r--vcl/inc/configsettings.hxx66
-rw-r--r--vcl/inc/cursor_hotspots.hxx171
-rw-r--r--vcl/inc/dbggui.hxx29
-rw-r--r--vcl/inc/debugevent.hxx35
-rw-r--r--vcl/inc/displayconnectiondispatch.hxx63
-rw-r--r--vcl/inc/dndeventdispatcher.hxx112
-rw-r--r--vcl/inc/dndhelper.hxx41
-rw-r--r--vcl/inc/dndlistenercontainer.hxx135
-rw-r--r--vcl/inc/drawmode.hxx50
-rw-r--r--vcl/inc/driverblocklist.hxx179
-rw-r--r--vcl/inc/factory.hxx58
-rw-r--r--vcl/inc/filter/BmpReader.hxx26
-rw-r--r--vcl/inc/filter/BmpWriter.hxx29
-rw-r--r--vcl/inc/filter/DxfReader.hxx26
-rw-r--r--vcl/inc/filter/EpsReader.hxx26
-rw-r--r--vcl/inc/filter/EpsWriter.hxx28
-rw-r--r--vcl/inc/filter/GifWriter.hxx28
-rw-r--r--vcl/inc/filter/MetReader.hxx26
-rw-r--r--vcl/inc/filter/PbmReader.hxx26
-rw-r--r--vcl/inc/filter/PcdReader.hxx28
-rw-r--r--vcl/inc/filter/PcxReader.hxx26
-rw-r--r--vcl/inc/filter/PictReader.hxx33
-rw-r--r--vcl/inc/filter/PsdReader.hxx26
-rw-r--r--vcl/inc/filter/RasReader.hxx26
-rw-r--r--vcl/inc/filter/TgaReader.hxx26
-rw-r--r--vcl/inc/filter/TiffReader.hxx26
-rw-r--r--vcl/inc/filter/TiffWriter.hxx28
-rw-r--r--vcl/inc/filter/WebpReader.hxx28
-rw-r--r--vcl/inc/filter/WebpWriter.hxx29
-rw-r--r--vcl/inc/filter/XpmReader.hxx32
-rw-r--r--vcl/inc/fltcall.hxx37
-rw-r--r--vcl/inc/font/DirectFontSubstitution.hxx68
-rw-r--r--vcl/inc/font/EmphasisMark.hxx45
-rw-r--r--vcl/inc/font/FeatureCollector.hxx49
-rw-r--r--vcl/inc/font/FontMetricData.hxx155
-rw-r--r--vcl/inc/font/FontSelectPattern.hxx86
-rw-r--r--vcl/inc/font/LogicalFontInstance.hxx167
-rw-r--r--vcl/inc/font/OpenTypeFeatureDefinitionList.hxx38
-rw-r--r--vcl/inc/font/OpenTypeFeatureStrings.hrc104
-rw-r--r--vcl/inc/font/PhysicalFontCollection.hxx103
-rw-r--r--vcl/inc/font/PhysicalFontFace.hxx213
-rw-r--r--vcl/inc/font/PhysicalFontFaceCollection.hxx48
-rw-r--r--vcl/inc/font/PhysicalFontFamily.hxx111
-rw-r--r--vcl/inc/font/fontsubstitution.hxx68
-rw-r--r--vcl/inc/fontattributes.hxx90
-rw-r--r--vcl/inc/fontsubset.hxx90
-rw-r--r--vcl/inc/glyphid.hxx26
-rw-r--r--vcl/inc/graphic/DetectorTools.hxx65
-rw-r--r--vcl/inc/graphic/GraphicFormatDetector.hxx208
-rw-r--r--vcl/inc/graphic/GraphicID.hxx46
-rw-r--r--vcl/inc/graphic/GraphicReader.hxx37
-rw-r--r--vcl/inc/graphic/Manager.hxx81
-rw-r--r--vcl/inc/graphic/UnoBinaryDataContainer.hxx39
-rw-r--r--vcl/inc/graphic/UnoGraphic.hxx89
-rw-r--r--vcl/inc/graphic/UnoGraphicDescriptor.hxx119
-rw-r--r--vcl/inc/graphic/VectorGraphicLoader.hxx23
-rw-r--r--vcl/inc/headless/BitmapHelper.hxx75
-rw-r--r--vcl/inc/headless/CairoCommon.hxx269
-rw-r--r--vcl/inc/headless/SvpGraphicsBackend.hxx139
-rw-r--r--vcl/inc/headless/svpbmp.hxx75
-rw-r--r--vcl/inc/headless/svpdata.hxx25
-rw-r--r--vcl/inc/headless/svpdummies.hxx64
-rw-r--r--vcl/inc/headless/svpframe.hxx145
-rw-r--r--vcl/inc/headless/svpgdi.hxx110
-rw-r--r--vcl/inc/headless/svpinst.hxx192
-rw-r--r--vcl/inc/headless/svpprn.hxx39
-rw-r--r--vcl/inc/headless/svpvd.hxx66
-rw-r--r--vcl/inc/helpwin.hxx86
-rw-r--r--vcl/inc/hyperlabel.hxx71
-rw-r--r--vcl/inc/iconview.hxx69
-rw-r--r--vcl/inc/image.h76
-rw-r--r--vcl/inc/imagerepository.hxx58
-rw-r--r--vcl/inc/impdel.hxx81
-rw-r--r--vcl/inc/impfont.hxx148
-rw-r--r--vcl/inc/impfontcache.hxx95
-rw-r--r--vcl/inc/impfontcharmap.hxx56
-rw-r--r--vcl/inc/impglyphitem.hxx166
-rw-r--r--vcl/inc/impgraph.hxx226
-rw-r--r--vcl/inc/implimagetree.hxx169
-rw-r--r--vcl/inc/ios/iosinst.hxx67
-rw-r--r--vcl/inc/jobdata.hxx81
-rw-r--r--vcl/inc/jobset.h111
-rw-r--r--vcl/inc/jsdialog/enabled.hxx22
-rw-r--r--vcl/inc/jsdialog/jsdialogbuilder.hxx894
-rw-r--r--vcl/inc/langboost.hxx18
-rw-r--r--vcl/inc/listbox.hxx599
-rw-r--r--vcl/inc/managedmenubutton.hxx31
-rw-r--r--vcl/inc/menubarvalue.hxx48
-rw-r--r--vcl/inc/menutogglebutton.hxx34
-rw-r--r--vcl/inc/messagedialog.hxx59
-rw-r--r--vcl/inc/opengl/win/WinDeviceInfo.hxx96
-rw-r--r--vcl/inc/opengl/zone.hxx33
-rw-r--r--vcl/inc/osx/a11yfactory.h40
-rw-r--r--vcl/inc/osx/a11yfocustracker.hxx97
-rw-r--r--vcl/inc/osx/a11ylistener.hxx49
-rw-r--r--vcl/inc/osx/a11ywrapper.h121
-rw-r--r--vcl/inc/osx/keyboardfocuslistener.hxx35
-rw-r--r--vcl/inc/osx/osxvcltypes.h30
-rw-r--r--vcl/inc/osx/printview.h63
-rw-r--r--vcl/inc/osx/runinmain.hxx175
-rw-r--r--vcl/inc/osx/saldata.hxx109
-rw-r--r--vcl/inc/osx/salframe.h233
-rw-r--r--vcl/inc/osx/salframeview.h278
-rw-r--r--vcl/inc/osx/salinst.h168
-rw-r--r--vcl/inc/osx/salmenu.h112
-rw-r--r--vcl/inc/osx/salnativewidgets.h69
-rw-r--r--vcl/inc/osx/salnsmenu.h53
-rw-r--r--vcl/inc/osx/salnstimer.h35
-rw-r--r--vcl/inc/osx/salobj.h71
-rw-r--r--vcl/inc/osx/salprn.h156
-rw-r--r--vcl/inc/osx/salsys.h44
-rw-r--r--vcl/inc/osx/saltimer.h75
-rw-r--r--vcl/inc/osx/svsys.h29
-rw-r--r--vcl/inc/osx/vclnsapp.h68
-rw-r--r--vcl/inc/pch/precompiled_vcl.cxx12
-rw-r--r--vcl/inc/pch/precompiled_vcl.hxx406
-rw-r--r--vcl/inc/pdf/BitmapID.hxx40
-rw-r--r--vcl/inc/pdf/ExternalPDFStreams.hxx73
-rw-r--r--vcl/inc/pdf/Matrix3.hxx52
-rw-r--r--vcl/inc/pdf/PdfConfig.hxx18
-rw-r--r--vcl/inc/pdf/ResourceDict.hxx38
-rw-r--r--vcl/inc/pdf/XmpMetadata.hxx57
-rw-r--r--vcl/inc/pdf/objectcopier.hxx64
-rw-r--r--vcl/inc/pdf/pdfbuildin_fonts.hxx79
-rw-r--r--vcl/inc/pdf/pdfcompat.hxx40
-rw-r--r--vcl/inc/pdf/pdfwriter_impl.hxx1358
-rw-r--r--vcl/inc/ppdparser.hxx269
-rw-r--r--vcl/inc/print.h73
-rw-r--r--vcl/inc/print.hrc124
-rw-r--r--vcl/inc/printaccessoryview.hrc38
-rw-r--r--vcl/inc/printdlg.hxx275
-rw-r--r--vcl/inc/printerinfomanager.hxx168
-rw-r--r--vcl/inc/qt5/QtAccessibleEventListener.hxx38
-rw-r--r--vcl/inc/qt5/QtAccessibleRegistry.hxx41
-rw-r--r--vcl/inc/qt5/QtAccessibleWidget.hxx188
-rw-r--r--vcl/inc/qt5/QtBitmap.hxx61
-rw-r--r--vcl/inc/qt5/QtClipboard.hxx97
-rw-r--r--vcl/inc/qt5/QtData.hxx49
-rw-r--r--vcl/inc/qt5/QtDragAndDrop.hxx115
-rw-r--r--vcl/inc/qt5/QtFilePicker.hxx186
-rw-r--r--vcl/inc/qt5/QtFont.hxx40
-rw-r--r--vcl/inc/qt5/QtFontFace.hxx68
-rw-r--r--vcl/inc/qt5/QtFrame.hxx237
-rw-r--r--vcl/inc/qt5/QtGraphics.hxx240
-rw-r--r--vcl/inc/qt5/QtGraphicsBase.hxx29
-rw-r--r--vcl/inc/qt5/QtGraphics_Controls.hxx100
-rw-r--r--vcl/inc/qt5/QtInstance.hxx192
-rw-r--r--vcl/inc/qt5/QtMainWindow.hxx40
-rw-r--r--vcl/inc/qt5/QtMenu.hxx138
-rw-r--r--vcl/inc/qt5/QtObject.hxx86
-rw-r--r--vcl/inc/qt5/QtOpenGLContext.hxx50
-rw-r--r--vcl/inc/qt5/QtPainter.hxx68
-rw-r--r--vcl/inc/qt5/QtPrinter.hxx32
-rw-r--r--vcl/inc/qt5/QtSvpGraphics.hxx54
-rw-r--r--vcl/inc/qt5/QtSvpSurface.hxx44
-rw-r--r--vcl/inc/qt5/QtSystem.hxx24
-rw-r--r--vcl/inc/qt5/QtTimer.hxx49
-rw-r--r--vcl/inc/qt5/QtTools.hxx188
-rw-r--r--vcl/inc/qt5/QtTransferable.hxx128
-rw-r--r--vcl/inc/qt5/QtVirtualDevice.hxx56
-rw-r--r--vcl/inc/qt5/QtWidget.hxx107
-rw-r--r--vcl/inc/qt5/QtX11Support.hxx22
-rw-r--r--vcl/inc/qt5/QtXAccessible.hxx39
-rw-r--r--vcl/inc/qt6/QtAccessibleEventListener.hxx12
-rw-r--r--vcl/inc/qt6/QtAccessibleRegistry.hxx12
-rw-r--r--vcl/inc/qt6/QtAccessibleWidget.hxx12
-rw-r--r--vcl/inc/qt6/QtBitmap.hxx12
-rw-r--r--vcl/inc/qt6/QtClipboard.hxx12
-rw-r--r--vcl/inc/qt6/QtData.hxx12
-rw-r--r--vcl/inc/qt6/QtDragAndDrop.hxx12
-rw-r--r--vcl/inc/qt6/QtFilePicker.hxx12
-rw-r--r--vcl/inc/qt6/QtFont.hxx12
-rw-r--r--vcl/inc/qt6/QtFontFace.hxx12
-rw-r--r--vcl/inc/qt6/QtFrame.hxx12
-rw-r--r--vcl/inc/qt6/QtGraphics.hxx12
-rw-r--r--vcl/inc/qt6/QtGraphicsBase.hxx12
-rw-r--r--vcl/inc/qt6/QtGraphics_Controls.hxx12
-rw-r--r--vcl/inc/qt6/QtInstance.hxx12
-rw-r--r--vcl/inc/qt6/QtMainWindow.hxx12
-rw-r--r--vcl/inc/qt6/QtMenu.hxx12
-rw-r--r--vcl/inc/qt6/QtObject.hxx12
-rw-r--r--vcl/inc/qt6/QtOpenGLContext.hxx12
-rw-r--r--vcl/inc/qt6/QtPainter.hxx12
-rw-r--r--vcl/inc/qt6/QtPrinter.hxx12
-rw-r--r--vcl/inc/qt6/QtSvpGraphics.hxx12
-rw-r--r--vcl/inc/qt6/QtSvpSurface.hxx12
-rw-r--r--vcl/inc/qt6/QtSystem.hxx12
-rw-r--r--vcl/inc/qt6/QtTimer.hxx12
-rw-r--r--vcl/inc/qt6/QtTools.hxx12
-rw-r--r--vcl/inc/qt6/QtTransferable.hxx12
-rw-r--r--vcl/inc/qt6/QtVirtualDevice.hxx12
-rw-r--r--vcl/inc/qt6/QtWidget.hxx12
-rw-r--r--vcl/inc/qt6/QtX11Support.hxx12
-rw-r--r--vcl/inc/qt6/QtXAccessible.hxx12
-rw-r--r--vcl/inc/quartz/CGHelpers.hxx105
-rw-r--r--vcl/inc/quartz/CoreTextFont.hxx74
-rw-r--r--vcl/inc/quartz/CoreTextFontFace.hxx69
-rw-r--r--vcl/inc/quartz/SystemFontList.hxx70
-rw-r--r--vcl/inc/quartz/cgutils.h38
-rw-r--r--vcl/inc/quartz/common.h41
-rw-r--r--vcl/inc/quartz/salbmp.h105
-rw-r--r--vcl/inc/quartz/salgdi.h480
-rw-r--r--vcl/inc/quartz/salgdicommon.hxx111
-rw-r--r--vcl/inc/quartz/salvd.h75
-rw-r--r--vcl/inc/quartz/utils.h53
-rw-r--r--vcl/inc/regband.hxx131
-rw-r--r--vcl/inc/regionband.hxx83
-rw-r--r--vcl/inc/salbmp.hxx199
-rw-r--r--vcl/inc/salframe.hxx325
-rw-r--r--vcl/inc/salgdi.hxx891
-rw-r--r--vcl/inc/salgdiimpl.hxx225
-rw-r--r--vcl/inc/salgeom.hxx90
-rw-r--r--vcl/inc/salinst.hxx232
-rw-r--r--vcl/inc/sallayout.hxx167
-rw-r--r--vcl/inc/salmenu.hxx101
-rw-r--r--vcl/inc/salobj.hxx79
-rw-r--r--vcl/inc/salprn.hxx125
-rw-r--r--vcl/inc/salptype.hxx53
-rw-r--r--vcl/inc/salsession.hxx113
-rw-r--r--vcl/inc/salsys.hxx83
-rw-r--r--vcl/inc/saltimer.hxx103
-rw-r--r--vcl/inc/salusereventlist.hxx128
-rw-r--r--vcl/inc/salvd.hxx58
-rw-r--r--vcl/inc/salvtables.hxx2298
-rw-r--r--vcl/inc/salwtype.hxx287
-rw-r--r--vcl/inc/scanlinewriter.hxx91
-rw-r--r--vcl/inc/schedulerimpl.hxx68
-rw-r--r--vcl/inc/scrptrun.h153
-rw-r--r--vcl/inc/scrwnd.hxx82
-rw-r--r--vcl/inc/sft.hxx725
-rw-r--r--vcl/inc/skia/gdiimpl.hxx441
-rw-r--r--vcl/inc/skia/osx/bitmap.hxx26
-rw-r--r--vcl/inc/skia/osx/gdiimpl.hxx57
-rw-r--r--vcl/inc/skia/quartz/cgutils.h40
-rw-r--r--vcl/inc/skia/salbmp.hxx231
-rw-r--r--vcl/inc/skia/utils.hxx341
-rw-r--r--vcl/inc/skia/win/font.hxx40
-rw-r--r--vcl/inc/skia/win/gdiimpl.hxx91
-rw-r--r--vcl/inc/skia/x11/gdiimpl.hxx45
-rw-r--r--vcl/inc/skia/x11/salvd.hxx48
-rw-r--r--vcl/inc/skia/x11/textrender.hxx39
-rw-r--r--vcl/inc/skia/zone.hxx33
-rw-r--r--vcl/inc/slider.hxx101
-rw-r--r--vcl/inc/spin.hxx43
-rw-r--r--vcl/inc/strhelper.hxx54
-rw-r--r--vcl/inc/strings.hrc132
-rw-r--r--vcl/inc/strings.hxx17
-rw-r--r--vcl/inc/svdata.hxx483
-rw-r--r--vcl/inc/svimpbox.hxx388
-rw-r--r--vcl/inc/svsys.h39
-rw-r--r--vcl/inc/test/GraphicsRenderTests.hxx24
-rw-r--r--vcl/inc/test/outputdevice.hxx316
-rw-r--r--vcl/inc/textlayout.hxx143
-rw-r--r--vcl/inc/textlineinfo.hxx74
-rw-r--r--vcl/inc/textrender.hxx51
-rw-r--r--vcl/inc/toolbarvalue.hxx52
-rw-r--r--vcl/inc/toolbox.h160
-rw-r--r--vcl/inc/treeglue.hxx171
-rw-r--r--vcl/inc/uiobject-internal.hxx34
-rw-r--r--vcl/inc/units.hrc65
-rw-r--r--vcl/inc/unx/cairotextrender.hxx45
-rw-r--r--vcl/inc/unx/cpdmgr.hxx122
-rw-r--r--vcl/inc/unx/cupsmgr.hxx89
-rw-r--r--vcl/inc/unx/desktops.hxx39
-rw-r--r--vcl/inc/unx/fc_fontoptions.hxx40
-rw-r--r--vcl/inc/unx/fontmanager.hxx204
-rw-r--r--vcl/inc/unx/freetype_glyphcache.hxx121
-rw-r--r--vcl/inc/unx/freetypetextrender.hxx55
-rw-r--r--vcl/inc/unx/gendata.hxx118
-rw-r--r--vcl/inc/unx/gendisp.hxx52
-rw-r--r--vcl/inc/unx/geninst.h86
-rw-r--r--vcl/inc/unx/genprn.h79
-rw-r--r--vcl/inc/unx/genpspgraphics.h97
-rw-r--r--vcl/inc/unx/gensys.h47
-rw-r--r--vcl/inc/unx/glyphcache.hxx152
-rw-r--r--vcl/inc/unx/gstsink.hxx27
-rw-r--r--vcl/inc/unx/gtk/atkbridge.hxx25
-rw-r--r--vcl/inc/unx/gtk/gloactiongroup.h75
-rw-r--r--vcl/inc/unx/gtk/glomenu.h134
-rw-r--r--vcl/inc/unx/gtk/gtkbackend.hxx30
-rw-r--r--vcl/inc/unx/gtk/gtkdata.hxx367
-rw-r--r--vcl/inc/unx/gtk/gtkframe.hxx694
-rw-r--r--vcl/inc/unx/gtk/gtkgdi.hxx245
-rw-r--r--vcl/inc/unx/gtk/gtkinst.hxx349
-rw-r--r--vcl/inc/unx/gtk/gtkobject.hxx120
-rw-r--r--vcl/inc/unx/gtk/gtksalmenu.hxx158
-rw-r--r--vcl/inc/unx/gtk/gtksys.hxx47
-rw-r--r--vcl/inc/unx/gtk/hudawareness.h29
-rw-r--r--vcl/inc/unx/helper.hxx50
-rw-r--r--vcl/inc/unx/i18n_cb.hxx89
-rw-r--r--vcl/inc/unx/i18n_ic.hxx77
-rw-r--r--vcl/inc/unx/i18n_im.hxx49
-rw-r--r--vcl/inc/unx/i18n_keysym.hxx64
-rw-r--r--vcl/inc/unx/i18n_xkb.hxx62
-rw-r--r--vcl/inc/unx/saldata.hxx71
-rw-r--r--vcl/inc/unx/saldisp.hxx381
-rw-r--r--vcl/inc/unx/salframe.h266
-rw-r--r--vcl/inc/unx/salgdi.h148
-rw-r--r--vcl/inc/unx/salinst.h86
-rw-r--r--vcl/inc/unx/salobj.h87
-rw-r--r--vcl/inc/unx/saltimer.h37
-rw-r--r--vcl/inc/unx/saltype.h25
-rw-r--r--vcl/inc/unx/salunx.h29
-rw-r--r--vcl/inc/unx/salunxtime.h73
-rw-r--r--vcl/inc/unx/salvd.h82
-rw-r--r--vcl/inc/unx/sessioninhibitor.hxx77
-rw-r--r--vcl/inc/unx/sm.hxx77
-rw-r--r--vcl/inc/unx/svsys.h26
-rw-r--r--vcl/inc/unx/wmadaptor.hxx292
-rw-r--r--vcl/inc/unx/x11/x11gdiimpl.h23
-rw-r--r--vcl/inc/unx/x11/x11sys.hxx42
-rw-r--r--vcl/inc/unx/x11/xlimits.hxx16
-rw-r--r--vcl/inc/unx/x11_cursors/ase_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/ase_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/asn_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/asn_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/asne_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/asne_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/asns_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/asns_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/asnswe_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/asnswe_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/asnw_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/asnw_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/ass_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/ass_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/asse_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/asse_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/assw_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/assw_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/asw_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/asw_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/aswe_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/aswe_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/chain_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/chain_mask.h32
-rw-r--r--vcl/inc/unx/x11_cursors/chainnot_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/chainnot_mask.h32
-rw-r--r--vcl/inc/unx/x11_cursors/chart_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/chart_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/copydata_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/copydata_mask.h36
-rw-r--r--vcl/inc/unx/x11_cursors/copydlnk_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/copydlnk_mask.h36
-rw-r--r--vcl/inc/unx/x11_cursors/copyfile_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/copyfile_mask.h36
-rw-r--r--vcl/inc/unx/x11_cursors/copyfiles_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/copyfiles_mask.h36
-rw-r--r--vcl/inc/unx/x11_cursors/copyflnk_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/copyflnk_mask.h36
-rw-r--r--vcl/inc/unx/x11_cursors/crook_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/crook_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/crop_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/crop_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/detective_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/detective_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/drawarc_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/drawarc_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/drawbezier_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/drawbezier_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/drawcaption_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/drawcaption_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/drawcirclecut_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/drawcirclecut_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/drawconnect_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/drawconnect_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/drawellipse_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/drawellipse_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/drawfreehand_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/drawfreehand_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/drawline_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/drawline_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/drawpie_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/drawpie_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/drawpolygon_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/drawpolygon_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/drawrect_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/drawrect_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/drawtext_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/drawtext_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/fatcross_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/fatcross_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/fill_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/fill_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/hshear_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/hshear_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/invert50.h40
-rw-r--r--vcl/inc/unx/x11_cursors/linkdata_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/linkdata_mask.h36
-rw-r--r--vcl/inc/unx/x11_cursors/linkfile_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/linkfile_mask.h36
-rw-r--r--vcl/inc/unx/x11_cursors/magnify_curs.h34
-rw-r--r--vcl/inc/unx/x11_cursors/magnify_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/mirror_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/mirror_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/movebezierweight_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/movebezierweight_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/movedata_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/movedata_mask.h36
-rw-r--r--vcl/inc/unx/x11_cursors/movedlnk_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/movedlnk_mask.h36
-rw-r--r--vcl/inc/unx/x11_cursors/movefile_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/movefile_mask.h36
-rw-r--r--vcl/inc/unx/x11_cursors/movefiles_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/movefiles_mask.h36
-rw-r--r--vcl/inc/unx/x11_cursors/moveflnk_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/moveflnk_mask.h36
-rw-r--r--vcl/inc/unx/x11_cursors/movepoint_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/movepoint_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/nodrop_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/nodrop_mask.h36
-rw-r--r--vcl/inc/unx/x11_cursors/null_curs.h24
-rw-r--r--vcl/inc/unx/x11_cursors/null_mask.h22
-rw-r--r--vcl/inc/unx/x11_cursors/pivotcol_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/pivotcol_mask.h36
-rw-r--r--vcl/inc/unx/x11_cursors/pivotdel_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/pivotdel_mask.h36
-rw-r--r--vcl/inc/unx/x11_cursors/pivotfld_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/pivotfld_mask.h36
-rw-r--r--vcl/inc/unx/x11_cursors/pivotrow_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/pivotrow_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/rotate_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/rotate_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/salcursors.h153
-rw-r--r--vcl/inc/unx/x11_cursors/tblsele_curs.h29
-rw-r--r--vcl/inc/unx/x11_cursors/tblsele_mask.h27
-rw-r--r--vcl/inc/unx/x11_cursors/tblsels_curs.h29
-rw-r--r--vcl/inc/unx/x11_cursors/tblsels_mask.h27
-rw-r--r--vcl/inc/unx/x11_cursors/tblselse_curs.h29
-rw-r--r--vcl/inc/unx/x11_cursors/tblselse_mask.h27
-rw-r--r--vcl/inc/unx/x11_cursors/tblselsw_curs.h29
-rw-r--r--vcl/inc/unx/x11_cursors/tblselsw_mask.h27
-rw-r--r--vcl/inc/unx/x11_cursors/tblselw_curs.h29
-rw-r--r--vcl/inc/unx/x11_cursors/tblselw_mask.h27
-rw-r--r--vcl/inc/unx/x11_cursors/vertcurs_curs.h29
-rw-r--r--vcl/inc/unx/x11_cursors/vertcurs_mask.h29
-rw-r--r--vcl/inc/unx/x11_cursors/vshear_curs.h36
-rw-r--r--vcl/inc/unx/x11_cursors/vshear_mask.h34
-rw-r--r--vcl/inc/unx/x11_cursors/wshide_curs.h29
-rw-r--r--vcl/inc/unx/x11_cursors/wshide_mask.h29
-rw-r--r--vcl/inc/unx/x11_cursors/wsshow_curs.h29
-rw-r--r--vcl/inc/unx/x11_cursors/wsshow_mask.h29
-rw-r--r--vcl/inc/vcleventlisteners.hxx39
-rw-r--r--vcl/inc/vclpluginapi.h76
-rw-r--r--vcl/inc/vclstatuslistener.hxx92
-rw-r--r--vcl/inc/verticaltabctrl.hxx88
-rw-r--r--vcl/inc/watchdog.hxx27
-rw-r--r--vcl/inc/widgetdraw/WidgetDefinition.hxx293
-rw-r--r--vcl/inc/widgetdraw/WidgetDefinitionReader.hxx42
-rw-r--r--vcl/inc/win/DWriteTextRenderer.hxx89
-rw-r--r--vcl/inc/win/dnd_source.hxx124
-rw-r--r--vcl/inc/win/dnd_target.hxx177
-rw-r--r--vcl/inc/win/salbmp.h95
-rw-r--r--vcl/inc/win/saldata.hxx295
-rw-r--r--vcl/inc/win/salframe.h162
-rw-r--r--vcl/inc/win/salgdi.h382
-rw-r--r--vcl/inc/win/salids.hrc94
-rw-r--r--vcl/inc/win/salinst.h88
-rw-r--r--vcl/inc/win/salmenu.h68
-rw-r--r--vcl/inc/win/salobj.h52
-rw-r--r--vcl/inc/win/salprn.h115
-rw-r--r--vcl/inc/win/salsys.h67
-rw-r--r--vcl/inc/win/saltimer.h83
-rw-r--r--vcl/inc/win/salvd.h66
-rw-r--r--vcl/inc/win/scoped_gdi.hxx72
-rw-r--r--vcl/inc/win/svsys.h30
-rw-r--r--vcl/inc/win/wincomp.hxx181
-rw-r--r--vcl/inc/win/wingdiimpl.hxx48
-rw-r--r--vcl/inc/win/winlayout.hxx102
-rw-r--r--vcl/inc/window.h440
-rw-r--r--vcl/inc/windowdev.hxx82
-rw-r--r--vcl/inc/wizdlg.hxx284
-rw-r--r--vcl/ios/DataFlavorMapping.cxx576
-rw-r--r--vcl/ios/DataFlavorMapping.hxx124
-rw-r--r--vcl/ios/HtmlFmtFlt.cxx172
-rw-r--r--vcl/ios/HtmlFmtFlt.hxx38
-rw-r--r--vcl/ios/clipboard.cxx182
-rw-r--r--vcl/ios/clipboard.hxx107
-rw-r--r--vcl/ios/dummies.cxx117
-rw-r--r--vcl/ios/iOSTransferable.cxx184
-rw-r--r--vcl/ios/iOSTransferable.hxx69
-rw-r--r--vcl/ios/iosinst.cxx165
-rw-r--r--vcl/ios/salios.cxx585
-rw-r--r--vcl/jsdialog/enabled.cxx395
-rw-r--r--vcl/jsdialog/executor.cxx677
-rw-r--r--vcl/jsdialog/jsdialogbuilder.cxx2370
-rw-r--r--vcl/null/printerinfomanager.cxx109
-rw-r--r--vcl/osx/DataFlavorMapping.cxx788
-rw-r--r--vcl/osx/DataFlavorMapping.hxx126
-rw-r--r--vcl/osx/DragActionConversion.cxx84
-rw-r--r--vcl/osx/DragActionConversion.hxx40
-rw-r--r--vcl/osx/DragSource.cxx338
-rw-r--r--vcl/osx/DragSource.hxx125
-rw-r--r--vcl/osx/DragSourceContext.cxx55
-rw-r--r--vcl/osx/DragSourceContext.hxx50
-rw-r--r--vcl/osx/DropTarget.cxx543
-rw-r--r--vcl/osx/DropTarget.hxx154
-rw-r--r--vcl/osx/HtmlFmtFlt.cxx165
-rw-r--r--vcl/osx/HtmlFmtFlt.hxx38
-rw-r--r--vcl/osx/OSXTransferable.cxx198
-rw-r--r--vcl/osx/OSXTransferable.hxx71
-rw-r--r--vcl/osx/PictToBmpFlt.cxx72
-rw-r--r--vcl/osx/PictToBmpFlt.hxx34
-rw-r--r--vcl/osx/README.a11y7
-rw-r--r--vcl/osx/a11yactionwrapper.h33
-rw-r--r--vcl/osx/a11yactionwrapper.mm96
-rw-r--r--vcl/osx/a11ycomponentwrapper.h36
-rw-r--r--vcl/osx/a11ycomponentwrapper.mm102
-rw-r--r--vcl/osx/a11yfactory.mm193
-rw-r--r--vcl/osx/a11yfocuslistener.cxx82
-rw-r--r--vcl/osx/a11yfocuslistener.hxx44
-rw-r--r--vcl/osx/a11yfocustracker.cxx269
-rw-r--r--vcl/osx/a11ylistener.cxx145
-rw-r--r--vcl/osx/a11yrolehelper.h32
-rw-r--r--vcl/osx/a11yrolehelper.mm296
-rw-r--r--vcl/osx/a11yselectionwrapper.h34
-rw-r--r--vcl/osx/a11yselectionwrapper.mm100
-rw-r--r--vcl/osx/a11ytablewrapper.h36
-rw-r--r--vcl/osx/a11ytablewrapper.mm212
-rw-r--r--vcl/osx/a11ytextattributeswrapper.h30
-rw-r--r--vcl/osx/a11ytextattributeswrapper.mm354
-rw-r--r--vcl/osx/a11ytextwrapper.h55
-rw-r--r--vcl/osx/a11ytextwrapper.mm296
-rw-r--r--vcl/osx/a11yutil.h31
-rw-r--r--vcl/osx/a11yutil.mm47
-rw-r--r--vcl/osx/a11yvaluewrapper.h37
-rw-r--r--vcl/osx/a11yvaluewrapper.mm87
-rw-r--r--vcl/osx/a11ywrapper.mm1616
-rw-r--r--vcl/osx/a11ywrapperbutton.h32
-rw-r--r--vcl/osx/a11ywrapperbutton.mm58
-rw-r--r--vcl/osx/a11ywrappercheckbox.h32
-rw-r--r--vcl/osx/a11ywrappercheckbox.mm65
-rw-r--r--vcl/osx/a11ywrappercombobox.h42
-rw-r--r--vcl/osx/a11ywrappercombobox.mm165
-rw-r--r--vcl/osx/a11ywrappergroup.h31
-rw-r--r--vcl/osx/a11ywrappergroup.mm53
-rw-r--r--vcl/osx/a11ywrapperlist.h30
-rw-r--r--vcl/osx/a11ywrapperlist.mm44
-rw-r--r--vcl/osx/a11ywrapperradiobutton.h32
-rw-r--r--vcl/osx/a11ywrapperradiobutton.mm64
-rw-r--r--vcl/osx/a11ywrapperradiogroup.h30
-rw-r--r--vcl/osx/a11ywrapperradiogroup.mm44
-rw-r--r--vcl/osx/a11ywrapperrow.h31
-rw-r--r--vcl/osx/a11ywrapperrow.mm54
-rw-r--r--vcl/osx/a11ywrapperscrollarea.h32
-rw-r--r--vcl/osx/a11ywrapperscrollarea.mm81
-rw-r--r--vcl/osx/a11ywrapperscrollbar.h30
-rw-r--r--vcl/osx/a11ywrapperscrollbar.mm47
-rw-r--r--vcl/osx/a11ywrappersplitter.h30
-rw-r--r--vcl/osx/a11ywrappersplitter.mm44
-rw-r--r--vcl/osx/a11ywrapperstatictext.h31
-rw-r--r--vcl/osx/a11ywrapperstatictext.mm52
-rw-r--r--vcl/osx/a11ywrappertabgroup.h30
-rw-r--r--vcl/osx/a11ywrappertabgroup.mm46
-rw-r--r--vcl/osx/a11ywrappertextarea.h30
-rw-r--r--vcl/osx/a11ywrappertextarea.mm44
-rw-r--r--vcl/osx/a11ywrappertoolbar.h30
-rw-r--r--vcl/osx/a11ywrappertoolbar.mm46
-rw-r--r--vcl/osx/clipboard.cxx353
-rw-r--r--vcl/osx/clipboard.hxx149
-rw-r--r--vcl/osx/cuidraw.hxx45
-rw-r--r--vcl/osx/documentfocuslistener.cxx230
-rw-r--r--vcl/osx/documentfocuslistener.hxx97
-rw-r--r--vcl/osx/printaccessoryview.mm1264
-rw-r--r--vcl/osx/printview.mm80
-rw-r--r--vcl/osx/res/MainMenu.nib/classes.nib4
-rw-r--r--vcl/osx/res/MainMenu.nib/info.nib21
-rw-r--r--vcl/osx/res/MainMenu.nib/keyedobjects.nibbin0 -> 3615 bytes
-rw-r--r--vcl/osx/saldata.cxx281
-rw-r--r--vcl/osx/salframe.cxx2027
-rw-r--r--vcl/osx/salframeview.mm2595
-rw-r--r--vcl/osx/salgdiutils.cxx362
-rw-r--r--vcl/osx/salinst.cxx1093
-rw-r--r--vcl/osx/salmacos.cxx529
-rw-r--r--vcl/osx/salmenu.cxx888
-rw-r--r--vcl/osx/salnativewidgets.cxx1366
-rw-r--r--vcl/osx/salnsmenu.mm261
-rw-r--r--vcl/osx/salnstimer.mm40
-rw-r--r--vcl/osx/salobj.cxx444
-rw-r--r--vcl/osx/salprn.cxx677
-rw-r--r--vcl/osx/salsys.cxx118
-rw-r--r--vcl/osx/saltimer.cxx206
-rw-r--r--vcl/osx/service_entry.cxx62
-rw-r--r--vcl/osx/vclnsapp.mm481
-rw-r--r--vcl/qa/afl-eventtesting/README.eventtesting24
-rw-r--r--vcl/qa/afl-eventtesting/eventtesting.impressbin0 -> 196 bytes
-rw-r--r--vcl/qa/afl-eventtesting/eventtesting.impress.crash-1bin0 -> 235 bytes
-rw-r--r--vcl/qa/afl-eventtesting/eventtesting.impress.crash-2bin0 -> 110 bytes
-rw-r--r--vcl/qa/afl-eventtesting/eventtesting.impress.crash-3bin0 -> 196 bytes
-rw-r--r--vcl/qa/afl-eventtesting/eventtesting.impress.crash-4bin0 -> 208 bytes
-rw-r--r--vcl/qa/afl-eventtesting/eventtesting.impress.crash-5bin0 -> 184 bytes
-rw-r--r--vcl/qa/afl-eventtesting/eventtesting.writerbin0 -> 28 bytes
-rw-r--r--vcl/qa/api/XGraphicTest.cxx245
-rw-r--r--vcl/qa/api/data/TestGraphic.pngbin0 -> 81 bytes
-rw-r--r--vcl/qa/cppunit/BackendTest.cxx1644
-rw-r--r--vcl/qa/cppunit/BinaryDataContainerTest.cxx67
-rw-r--r--vcl/qa/cppunit/BitmapExTest.cxx251
-rw-r--r--vcl/qa/cppunit/BitmapFilterTest.cxx283
-rw-r--r--vcl/qa/cppunit/BitmapProcessorTest.cxx88
-rw-r--r--vcl/qa/cppunit/BitmapScaleTest.cxx312
-rw-r--r--vcl/qa/cppunit/BitmapTest.cxx722
-rw-r--r--vcl/qa/cppunit/BmpFilterTest.cxx220
-rw-r--r--vcl/qa/cppunit/FontFeatureTest.cxx441
-rw-r--r--vcl/qa/cppunit/GraphicDescriptorTest.cxx200
-rw-r--r--vcl/qa/cppunit/GraphicFormatDetectorTest.cxx513
-rw-r--r--vcl/qa/cppunit/GraphicNativeMetadataTest.cxx105
-rw-r--r--vcl/qa/cppunit/GraphicTest.cxx1364
-rw-r--r--vcl/qa/cppunit/PDFDocumentTest.cxx605
-rw-r--r--vcl/qa/cppunit/PDFiumLibraryTest.cxx454
-rw-r--r--vcl/qa/cppunit/ScanlineToolsTest.cxx227
-rw-r--r--vcl/qa/cppunit/TypeSerializerTest.cxx394
-rw-r--r--vcl/qa/cppunit/VectorGraphicSearchTest.cxx288
-rw-r--r--vcl/qa/cppunit/XpmFilterTest.cxx76
-rw-r--r--vcl/qa/cppunit/a11y/atspi2/atspi2.cxx503
-rw-r--r--vcl/qa/cppunit/a11y/atspi2/atspi2.hxx45
-rw-r--r--vcl/qa/cppunit/a11y/atspi2/atspi2testbase.hxx97
-rw-r--r--vcl/qa/cppunit/a11y/atspi2/atspi2text.cxx1017
-rw-r--r--vcl/qa/cppunit/a11y/atspi2/atspiwrapper.cxx22
-rw-r--r--vcl/qa/cppunit/a11y/atspi2/atspiwrapper.hxx784
-rw-r--r--vcl/qa/cppunit/a11y/atspi2/testdocuments/ecclectic.fodt258
-rw-r--r--vcl/qa/cppunit/animation.cxx69
-rw-r--r--vcl/qa/cppunit/animationrenderer.cxx106
-rw-r--r--vcl/qa/cppunit/app/test_IconThemeInfo.cxx111
-rw-r--r--vcl/qa/cppunit/app/test_IconThemeScanner.cxx82
-rw-r--r--vcl/qa/cppunit/app/test_IconThemeSelector.cxx183
-rw-r--r--vcl/qa/cppunit/bitmapcolor.cxx263
-rw-r--r--vcl/qa/cppunit/bitmaprender/BitmapRenderTest.cxx286
-rw-r--r--vcl/qa/cppunit/bitmaprender/data/ImageRGBA.pngbin0 -> 106 bytes
-rw-r--r--vcl/qa/cppunit/bitmaprender/data/tdf104141.gifbin0 -> 12205 bytes
-rw-r--r--vcl/qa/cppunit/bitmaprender/data/tdf113918.pngbin0 -> 20043 bytes
-rw-r--r--vcl/qa/cppunit/bitmaprender/data/tdf116888.gifbin0 -> 1875 bytes
-rw-r--r--vcl/qa/cppunit/blocklistparsertest.cxx174
-rw-r--r--vcl/qa/cppunit/builder/demo.ui1883
-rw-r--r--vcl/qa/cppunit/canvasbitmaptest.cxx778
-rw-r--r--vcl/qa/cppunit/cjktext.cxx248
-rw-r--r--vcl/qa/cppunit/complextext.cxx614
-rw-r--r--vcl/qa/cppunit/data/123_Numbers.gifbin0 -> 1515 bytes
-rw-r--r--vcl/qa/cppunit/data/BMP_8bit_RLE.bmpbin0 -> 1194 bytes
-rw-r--r--vcl/qa/cppunit/data/BMP_A8R8G8B8.bmpbin0 -> 202 bytes
-rw-r--r--vcl/qa/cppunit/data/BMP_Paint_1bit.bmpbin0 -> 78 bytes
-rw-r--r--vcl/qa/cppunit/data/BMP_Paint_24bit.bmpbin0 -> 102 bytes
-rw-r--r--vcl/qa/cppunit/data/BMP_Paint_4bit.bmpbin0 -> 134 bytes
-rw-r--r--vcl/qa/cppunit/data/BMP_Paint_8bit.bmpbin0 -> 1094 bytes
-rw-r--r--vcl/qa/cppunit/data/BMP_R5G6B5.bmpbin0 -> 338 bytes
-rw-r--r--vcl/qa/cppunit/data/BMP_R8G8B8.bmpbin0 -> 442 bytes
-rw-r--r--vcl/qa/cppunit/data/BMP_RLE.bmpbin0 -> 1188 bytes
-rw-r--r--vcl/qa/cppunit/data/BMP_RLE_V2.bmpbin0 -> 810 bytes
-rw-r--r--vcl/qa/cppunit/data/BMP_RLE_V3.bmpbin0 -> 1104 bytes
-rw-r--r--vcl/qa/cppunit/data/DocumentWithNull.pdfbin0 -> 1080 bytes
-rw-r--r--vcl/qa/cppunit/data/Exif1.jpgbin0 -> 759 bytes
-rw-r--r--vcl/qa/cppunit/data/Exif1_090CW.jpgbin0 -> 771 bytes
-rw-r--r--vcl/qa/cppunit/data/Exif1_180.jpgbin0 -> 771 bytes
-rw-r--r--vcl/qa/cppunit/data/Exif1_270CW.jpgbin0 -> 771 bytes
-rw-r--r--vcl/qa/cppunit/data/Pangram.pdfbin0 -> 16880 bytes
-rw-r--r--vcl/qa/cppunit/data/PangramAcrobatAnnotations.pdfbin0 -> 21709 bytes
-rw-r--r--vcl/qa/cppunit/data/PangramWithAnnotations.pdfbin0 -> 17785 bytes
-rw-r--r--vcl/qa/cppunit/data/PangramWithMultipleTypeOfAnnotations.pdfbin0 -> 23143 bytes
-rw-r--r--vcl/qa/cppunit/data/SimpleExample.svg4
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.apngbin0 -> 239 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.bmpbin0 -> 442 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.emfbin0 -> 696 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.emzbin0 -> 397 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.eps82
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.gifbin0 -> 50 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.jpgbin0 -> 992 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.metbin0 -> 629 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.pcxbin0 -> 284 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.pdfbin0 -> 962 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.pngbin0 -> 94 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.psdbin0 -> 1338 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.svg4
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.svgzbin0 -> 298 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.tgabin0 -> 148 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.tifbin0 -> 603 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.webpbin0 -> 52 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.wmfbin0 -> 290 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.wmzbin0 -> 196 bytes
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.xbm5
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.xpm15
-rw-r--r--vcl/qa/cppunit/data/XPM_1.xpm15
-rw-r--r--vcl/qa/cppunit/data/XPM_4.xpm23
-rw-r--r--vcl/qa/cppunit/data/XPM_8.xpm263
-rw-r--r--vcl/qa/cppunit/data/basic.pdf71
-rw-r--r--vcl/qa/cppunit/data/basicSource.pdf60
-rw-r--r--vcl/qa/cppunit/data/form-fields.pdf95
-rw-r--r--vcl/qa/cppunit/data/graphic-descriptor-mapmode.bmpbin0 -> 4922 bytes
-rw-r--r--vcl/qa/cppunit/data/inch-size.emfbin0 -> 28322 bytes
-rw-r--r--vcl/qa/cppunit/data/roundtrip.wmfbin0 -> 6475 bytes
-rw-r--r--vcl/qa/cppunit/data/tdf107718.otfbin0 -> 5280 bytes
-rw-r--r--vcl/qa/cppunit/data/tdf107718.otf.readme11
-rw-r--r--vcl/qa/cppunit/data/tdf149545.svg32
-rw-r--r--vcl/qa/cppunit/data/tdf153440.ttfbin0 -> 1804 bytes
-rw-r--r--vcl/qa/cppunit/data/tdf153440.ttf.readme12
-rw-r--r--vcl/qa/cppunit/data/tdf156016.svg33
-rw-r--r--vcl/qa/cppunit/data/tdf73523.bmpbin0 -> 70 bytes
-rw-r--r--vcl/qa/cppunit/data/testBasicMorphology.pngbin0 -> 226 bytes
-rw-r--r--vcl/qa/cppunit/data/testBasicMorphologyDilated1.pngbin0 -> 219 bytes
-rw-r--r--vcl/qa/cppunit/data/testBasicMorphologyDilated1Eroded1.pngbin0 -> 224 bytes
-rw-r--r--vcl/qa/cppunit/data/testBasicMorphologyDilated2.pngbin0 -> 174 bytes
-rw-r--r--vcl/qa/cppunit/data/testBasicMorphologyDilated2Eroded1.pngbin0 -> 178 bytes
-rw-r--r--vcl/qa/cppunit/data/testColorChange-red-linear-gradient.pngbin0 -> 9341 bytes
-rw-r--r--vcl/qa/cppunit/data/to-wmf.emfbin0 -> 1057 bytes
-rw-r--r--vcl/qa/cppunit/data/wmf-embedded-emfplus.wmfbin0 -> 10610 bytes
-rw-r--r--vcl/qa/cppunit/dndtest.cxx311
-rw-r--r--vcl/qa/cppunit/drawmode.cxx364
-rw-r--r--vcl/qa/cppunit/errorhandler.cxx66
-rw-r--r--vcl/qa/cppunit/filter/igif/data/logic-lazy-read.gifbin0 -> 1258 bytes
-rw-r--r--vcl/qa/cppunit/filter/igif/igif.cxx47
-rw-r--r--vcl/qa/cppunit/filter/ipdf/data/add-visible-signature-last-page.pdf111
-rw-r--r--vcl/qa/cppunit/filter/ipdf/data/array-mixed-numbers-and-elements.pdf55
-rw-r--r--vcl/qa/cppunit/filter/ipdf/data/comment-end.pdf69
-rw-r--r--vcl/qa/cppunit/filter/ipdf/data/dict-array-dict.pdf55
-rw-r--r--vcl/qa/cppunit/filter/ipdf/data/real-numbers.pdf55
-rw-r--r--vcl/qa/cppunit/filter/ipdf/ipdf.cxx218
-rw-r--r--vcl/qa/cppunit/font.cxx219
-rw-r--r--vcl/qa/cppunit/fontcharmap.cxx48
-rw-r--r--vcl/qa/cppunit/fontmetric.cxx138
-rw-r--r--vcl/qa/cppunit/fontmocks.hxx64
-rw-r--r--vcl/qa/cppunit/gen/data/tdf121120.pngbin0 -> 3196 bytes
-rw-r--r--vcl/qa/cppunit/gen/gen.cxx91
-rw-r--r--vcl/qa/cppunit/gradient.cxx256
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/README16
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2004-0691-1.bmpbin0 -> 313 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2006-0006-1.bmpbin0 -> 43218 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2007-2244-1.bmpbin0 -> 21424 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2007-3741-1.bmpbin0 -> 8585 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2007-3741-2.bmpbin0 -> 8620 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2008-1097-1.bmpbin0 -> 93078 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2008-5870-1.bmp1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2016-10504-1.bmpbin0 -> 2616 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/fail/EDB-24743-1.bmpbin0 -> 2222 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/fail/EDB-24743-4.bmpbin0 -> 73470 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/fail/afl-sample-bad-rle-1.bmpbin0 -> 1038 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/fail/nodict-compress.bmpbin0 -> 110 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/indeterminate/.gitignore1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/pass/CVE-2014-1947-1.bmpbin0 -> 5000 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/pass/EDB-22680-1.bmpbin0 -> 18862 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/bmp/pass/crash-1.bmpbin0 -> 632 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/dxf/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/dxf/fail/CVE-2010-1681-1.dxfbin0 -> 4063 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/dxf/fail/hang-1.dxf1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/dxf/indeterminate/.gitignore1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/dxf/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/dxf/pass/bigangle-1.dxf143
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/dxf/pass/loop-1.dxf17320
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/dxf/pass/loop-2.dxf13974
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/dxf/pass/pyramid.dxf25008
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/fail/CVE-2004-0209-1.emfbin0 -> 576 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/fail/CVE-2008-1083-1.emfbin0 -> 3524 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/fail/CVE-2009-1217-1.emfbin0 -> 1075 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/fail/crash-2.emfbin0 -> 3848 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/fail/crash-3.emfbin0 -> 456 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/fail/fdo71307-2.emfbin0 -> 24229 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/fail/hang-1.emfbin0 -> 2225 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/fail/hang-2.emfbin0 -> 7057 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/fail/slow-moveclip-1.emfbin0 -> 8700 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/indeterminate/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2008-1087-1.emfbin0 -> 3380 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2008-2245-1.emfbin0 -> 3524 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0168-1.emfbin0 -> 1040240 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0168-2.emfbin0 -> 3872 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0169-1.emfbin0 -> 3792 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0169-2.emfbin0 -> 3792 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0169-3.emfbin0 -> 3792 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0170-1.emfbin0 -> 3792 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-3301-1.emfbin0 -> 42756 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-3303-1.emfbin0 -> 42756 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-3304-1.emfbin0 -> 25160 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/pass/crash-1.emfbin0 -> 7068 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/pass/crash-2.emfbin0 -> 133136 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/emf/pass/fdo38580-3.emfbin0 -> 7068 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/eps/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/eps/fail/CVE-2009-4195-1.epsbin0 -> 45336 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/eps/fail/short-1.epsbin0 -> 29 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/eps/indeterminate/.gitignore1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/eps/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/eps/pass/CVE-2013-4979-1.epsbin0 -> 5708521 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/eps/pass/fdo13433-4.eps667
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/fail/CVE-2007-3958-1.gifbin0 -> 328 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/fail/CVE-2008-5937-1.gif1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/fail/EBD-36334-1.gif2
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/fail/EBD-36335-1.gifbin0 -> 1190 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/fail/EDB-23279-1.gif1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/fail/too-small-1.gifbin0 -> 3080 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/indeterminate/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/pass/CVE-2007-6715-1.gifbin0 -> 47778 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/pass/CVE-2008-3013-1.gifbin0 -> 2382 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/pass/CVE-2011-2131-1.gifbin0 -> 10998 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/pass/CVE-2012-0282-1.gifbin0 -> 2876 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/pass/EDB-19333-1.gif1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/pass/afl-sample-short-read-1.gifbin0 -> 275 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/pass/afl-sample-short-read-2.gifbin0 -> 35 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/pass/crash-1.gifbin0 -> 111 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/pass/crash-2.gifbin0 -> 257 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/gif/pass/sf_3e0068c9b19bb548826bed0599f65745-15940-minimized.gifbin0 -> 47778 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-1.jpgbin0 -> 31214 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-2.jpgbin0 -> 31129 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-3.jpgbin0 -> 31214 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-4.jpgbin0 -> 31214 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-5.jpgbin0 -> 31214 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-5314-1.jpgbin0 -> 12000000 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/fail/crash-1.jpgbin0 -> 443 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/indeterminate/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-1.jpgbin0 -> 4098 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-2.jpgbin0 -> 1674 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-3.jpgbin0 -> 2634 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-4.jpgbin0 -> 4098 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-5.jpgbin0 -> 8903 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2017-9614-1.jpgbin0 -> 504 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/pass/EDB-24743-2.jpgbin0 -> 8559 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/pass/EDB-24743-3.jpgbin0 -> 24253 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/jpg/pass/fatalerror-1.jpgbin0 -> 276 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/met/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/met/fail/afl-divide-zero-1.metbin0 -> 629 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/met/fail/afl-msan-1.metbin0 -> 27 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/met/fail/crash-1.metbin0 -> 681 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/met/fail/hang-1.metbin0 -> 600 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/met/fail/hang-2.metbin0 -> 289 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/met/fail/hang-3.metbin0 -> 608 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/met/indeterminate/.gitignore1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/met/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/met/pass/sample.metbin0 -> 629 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pbm/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pbm/fail/crash-1.pbm6
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pbm/fail/hang-1.pbmbin0 -> 266 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pbm/indeterminate/.gitignore1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pbm/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pbm/pass/rhbz160429-1.pbmbin0 -> 456 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pcd/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pcd/indeterminate/.gitignore1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pcd/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pcd/pass/blank-square.pcdbin0 -> 788480 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pcx/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pcx/fail/CVE-2007-3741-1.pcxbin0 -> 41066 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pcx/fail/CVE-2007-3741-2.pcxbin0 -> 41136 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pcx/fail/CVE-2007-3741-3.pcxbin0 -> 41107 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pcx/fail/CVE-2008-1097-1.pcxbin0 -> 91531 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pcx/fail/hang-1.pcxbin0 -> 897 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pcx/indeterminate/.gitignore1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pcx/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pcx/pass/rhbz469075-1.pcxbin0 -> 58596 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pict/clipping-problem.pctbin0 -> 32768 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pict/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pict/fail/CVE-2008-1097-1.pctbin0 -> 22898 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pict/fail/CVE-2012-0277-1.pctbin0 -> 30649 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pict/fail/CVE-2013-2577-1.pctbin0 -> 3725256 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pict/fail/EDB-19332-1.pctbin0 -> 95604 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pict/fail/exception-1.pctbin0 -> 11086 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pict/fail/hang-1.pctbin0 -> 97206 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pict/indeterminate/.gitignore2
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pict/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pict/pass/ooo25876-2.pctbin0 -> 11168 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/pict/pass/tdf92789.pctbin0 -> 62988 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2004-0597-1.png3
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2005-0633-1.pngbin0 -> 346 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2006-7210-1.pngbin0 -> 2495 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2007-2365-1.pngbin0 -> 18470 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2009-1511-1.png1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2016-0951-2.png1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2016-0952-1.png1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2016-0952-2.pngbin0 -> 383 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/fail/EDB-34720-1.pngbin0 -> 5000 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/fail/ofz32026.pngbin0 -> 41 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/indeterminate/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/pass/CVE-2016-0951-1.png1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/pass/afl-sample-IDAT.pngbin0 -> 260 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/pass/afl-sample-Z_NEED_DICT.pngbin0 -> 260 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/pass/black.pngbin0 -> 175 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/png/pass/invalid-chunk.pngbin0 -> 5312 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/ppm/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/ppm/fail/CVE-2008-1097-1.ppmbin0 -> 92583 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/ppm/indeterminate/.gitignore1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/ppm/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/ppm/pass/fdo19811-2.ppmbin0 -> 90735 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/psd/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/psd/fail/CVE-2007-3741-1.psdbin0 -> 896870 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/psd/indeterminate/.gitignore1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/psd/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/psd/pass/blank-square.psdbin0 -> 23846 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/psd/pass/hang-1.psdbin0 -> 67086 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/psd/pass/rhbz899670-1.psdbin0 -> 2147990 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/psd/tdf142629.psdbin0 -> 53812 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/ras/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/ras/fail/CVE-2007-2356-1.rasbin0 -> 4000 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/ras/fail/CVE-2008-1097-1.rasbin0 -> 92752 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/ras/fail/crash-1.rasbin0 -> 913 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/ras/fail/hang-1.rasbin0 -> 40 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/ras/indeterminate/.gitignore1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/ras/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/ras/pass/marbles.rasbin0 -> 4262290 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/svm/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-1.svmbin0 -> 152 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-2.svmbin0 -> 110 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-3.svmbin0 -> 142 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-4.svmbin0 -> 532 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-5.svmbin0 -> 162 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-6.svmbin0 -> 79 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/svm/fail/ofz7165-1.svmbin0 -> 816777 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/svm/indeterminate/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/svm/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/svm/pass/leak-1.svmbin0 -> 856 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/svm/pass/ofz-timeout-1.svmbin0 -> 3027 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/svm/pass/ofz32885-1.svmbin0 -> 49 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/svm/pass/ofz45269-1.svmbin0 -> 198 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/svm/pass/ofz45583-1.svmbin0 -> 2238 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tga/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tga/fail/CVE-2012-3755-1.tgabin0 -> 1440044 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tga/indeterminate/.gitignore1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tga/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tga/pass/fdo14924-5.tgabin0 -> 2005 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tga/pass/fdo14924-6.tgabin0 -> 3171 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/blue16.tifbin0 -> 873 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/blue8.tifbin0 -> 576 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/BID-51132-1.tiffbin0 -> 115150 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2006-3459-1.tiffbin0 -> 2180 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2007-2217-1.tiffbin0 -> 16202 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2009-2285-1.tiffbin0 -> 214342 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2010-2482-1.tiffbin0 -> 5052 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2013-3906-1.tiffbin0 -> 19098 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2013-5575-1.tiffbin0 -> 125824 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-10688-1.tiffbin0 -> 5356 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-18013-1.tiffbin0 -> 286 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-9147-1.tiffbin0 -> 226 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-9936-1.tiffbin0 -> 2492 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-9937-1.tiffbin0 -> 722 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2022-1210-1.tiffbin0 -> 4490 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/EBD-22681-1.tiffbin0 -> 5052 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/EDB-24743-5.tiffbin0 -> 67100 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-1.tiffbin0 -> 257 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-2.tiffbin0 -> 260 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-3.tiffbin0 -> 260 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-4.tiffbin0 -> 260 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-5.tiffbin0 -> 252 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-6.tiffbin0 -> 260 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-7.tiffbin0 -> 179 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-1.tiffbin0 -> 205 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-10.tiffbin0 -> 5254 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-2.tiffbin0 -> 111 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-3.tiffbin0 -> 17 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-4.tiffbin0 -> 43 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-5.tiffbin0 -> 281 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-6.tiffbin0 -> 259 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-7.tiffbin0 -> 504 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-8.tiffbin0 -> 272 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-9.tiffbin0 -> 188 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/fail/ofz47589.tiffbin0 -> 82 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/green16.tifbin0 -> 875 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/green8.tifbin0 -> 577 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/indeterminate/.gitignore2
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2005-1544-1.tiffbin0 -> 1340 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2006-2656-1.tiffbin0 -> 563386 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2012-0276-1.tiffbin0 -> 115150 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2012-0276-2.tiffbin0 -> 72236 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2012-2027-1.tiffbin0 -> 115150 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/pass/multi-page-1.tiffbin0 -> 1202 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/pass/tdf149417.tiffbin0 -> 74205 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/red16.tifbin0 -> 842 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/red8.tifbin0 -> 542 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/tdf115863.tifbin0 -> 40962 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/tdf126460.tifbin0 -> 9054 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/tdf138818.tifbin0 -> 46428 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/tdf149418.tifbin0 -> 50938 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/tiff/tdf74331.tifbin0 -> 80146 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossless.webpbin0 -> 744 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossy.webpbin0 -> 826 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/webp/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/webp/indeterminate/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossless.webpbin0 -> 762 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossy.webpbin0 -> 782 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/webp/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2005-2123-1.wmf-0.009-676bin0 -> 684 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2005-2124-1.wmfbin0 -> 218 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2005-4560-1.wmfbin0 -> 12178 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2006-0143-1.wmf1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2006-0143-2.wmfbin0 -> 68 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2007-1238-1.wmfbin0 -> 382 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2007-1245-1.wmfbin0 -> 382 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/RC4-hang-1.wmfbin0 -> 675 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/RC4-hang-2.wmfbin0 -> 375 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/RC4-hang-3.wmf5
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/RC4-hang-4.wmf3
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/bitcount-1.wmfbin0 -> 1916 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/exttextout-2.wmfbin0 -> 54196 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/facename-1.wmfbin0 -> 4197 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/ofz35149-1.wmfbin0 -> 75 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/ofz35150-1.wmfbin0 -> 74 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/ofz5942-1.wmfbin0 -> 16064 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/fail/seek-1.wmfbin0 -> 5082 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/indeterminate/.gitignore1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/pass/CVE-2005-2123-1.wmfbin0 -> 684 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/pass/CVE-2006-4071-1.wmf1
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/pass/CVE-2007-1090-1.wmfbin0 -> 238 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/pass/CVE-2015-0848-1.wmfbin0 -> 4192 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/pass/exttextout-1.wmfbin0 -> 2142 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/wmf/pass/noheader.wmfbin0 -> 13801 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/xbm/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/xbm/fail/crash-1.xbm12
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/xbm/indeterminate/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/xbm/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/xbm/pass/grafix4.xbm2011
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/xpm/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/xpm/fail/gentoo22729-1.xpmbin0 -> 645514 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/xpm/indeterminate/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/xpm/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/xpm/pass/tdf111925-1.xpm306
-rw-r--r--vcl/qa/cppunit/graphicfilter/filters-dxf-test.cxx61
-rw-r--r--vcl/qa/cppunit/graphicfilter/filters-eps-test.cxx61
-rw-r--r--vcl/qa/cppunit/graphicfilter/filters-met-test.cxx59
-rw-r--r--vcl/qa/cppunit/graphicfilter/filters-pcd-test.cxx61
-rw-r--r--vcl/qa/cppunit/graphicfilter/filters-pcx-test.cxx61
-rw-r--r--vcl/qa/cppunit/graphicfilter/filters-pict-test.cxx92
-rw-r--r--vcl/qa/cppunit/graphicfilter/filters-ppm-test.cxx64
-rw-r--r--vcl/qa/cppunit/graphicfilter/filters-psd-test.cxx84
-rw-r--r--vcl/qa/cppunit/graphicfilter/filters-ras-test.cxx61
-rw-r--r--vcl/qa/cppunit/graphicfilter/filters-test.cxx188
-rw-r--r--vcl/qa/cppunit/graphicfilter/filters-tga-test.cxx60
-rw-r--r--vcl/qa/cppunit/graphicfilter/filters-tiff-test.cxx317
-rw-r--r--vcl/qa/cppunit/graphicfilter/filters-webp-test.cxx203
-rw-r--r--vcl/qa/cppunit/jpeg/JpegReaderTest.cxx202
-rw-r--r--vcl/qa/cppunit/jpeg/JpegWriterTest.cxx117
-rw-r--r--vcl/qa/cppunit/jpeg/data/8BitGrayscale.jpgbin0 -> 1384 bytes
-rw-r--r--vcl/qa/cppunit/jpeg/data/8BitNonGrayscale.gifbin0 -> 1875 bytes
-rw-r--r--vcl/qa/cppunit/jpeg/data/JPEGTestCMYK.jpegbin0 -> 405 bytes
-rw-r--r--vcl/qa/cppunit/jpeg/data/JPEGTestGray.jpegbin0 -> 196 bytes
-rw-r--r--vcl/qa/cppunit/jpeg/data/JPEGTestRGB.jpegbin0 -> 619 bytes
-rw-r--r--vcl/qa/cppunit/jpeg/data/tdf138950.jpegbin0 -> 60662 bytes
-rw-r--r--vcl/qa/cppunit/lifecycle.cxx314
-rw-r--r--vcl/qa/cppunit/logicalfontinstance.cxx65
-rw-r--r--vcl/qa/cppunit/mnemonic.cxx131
-rw-r--r--vcl/qa/cppunit/outdev.cxx2081
-rw-r--r--vcl/qa/cppunit/pdfexport/data/6m-wide.odgbin0 -> 9146 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/BrownFoxLazyDog.odtbin0 -> 12595 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/ComplexContentDictionary.pdfbin0 -> 29050 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/Description PDF Export test .odtbin0 -> 22599 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/LO_Lbl_Lbody_bug_report.fodt125
-rw-r--r--vcl/qa/cppunit/pdfexport/data/LinkWithFly.fodt137
-rw-r--r--vcl/qa/cppunit/pdfexport/data/PDFWithImages.pdfbin0 -> 4836 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/PDF_export_with_formcontrol.fodt174
-rw-r--r--vcl/qa/cppunit/pdfexport/data/SimpleMultiPagePDF.pdfbin0 -> 17693 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/SimpleTOC.fodt323
-rw-r--r--vcl/qa/cppunit/pdfexport/data/TableTH_test_LibreOfficeWriter7.3.3_HeaderRow-HeadersInTopRow.fodt560
-rw-r--r--vcl/qa/cppunit/pdfexport/data/WG7100-Preface.odtbin0 -> 25317 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/alternativeText.fodp489
-rw-r--r--vcl/qa/cppunit/pdfexport/data/bitmap-scaledown.odtbin0 -> 13423 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/content-control-rtl.docxbin0 -> 5178 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/flowframe_null_ptr_deref.sample654
-rw-r--r--vcl/qa/cppunit/pdfexport/data/forcepoint71.keybin0 -> 114022 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/forcepoint80-1.rtf1
-rw-r--r--vcl/qa/cppunit/pdfexport/data/form-font-name.odtbin0 -> 8548 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/formcontrol.fodt192
-rw-r--r--vcl/qa/cppunit/pdfexport/data/grouped-shape.fodt213
-rw-r--r--vcl/qa/cppunit/pdfexport/data/image-hyperlink-alttext.fodt195
-rw-r--r--vcl/qa/cppunit/pdfexport/data/image-shape.fodt141
-rw-r--r--vcl/qa/cppunit/pdfexport/data/justified-arabic-kashida.odtbin0 -> 12875 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/link-wrong-page-partial.odgbin0 -> 11175 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/link-wrong-page.odpbin0 -> 12293 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/master.odmbin0 -> 22304 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/nestedsection.fodt132
-rw-r--r--vcl/qa/cppunit/pdfexport/data/pdf-image-annots.odgbin0 -> 29714 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/pdf-image-hyperlink.odgbin0 -> 28633 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/pdf-image-resource-inline-xobject-ref.pdfbin0 -> 1372 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/pdf-image-rotate-180.pdfbin0 -> 1319 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/rectangles.pdf54
-rw-r--r--vcl/qa/cppunit/pdfexport/data/reduce-image.fodt29
-rw-r--r--vcl/qa/cppunit/pdfexport/data/reduce-small-image.fodt21
-rw-r--r--vcl/qa/cppunit/pdfexport/data/ref-to-kids.pdfbin0 -> 2959 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/softhyphen_pdf.odtbin0 -> 9071 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/spanlist.fodt207
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf103492.odtbin0 -> 11218 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf105093.odpbin0 -> 211126 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf105461.odpbin0 -> 10320 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf105954.odtbin0 -> 74147 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf105972.fodt175
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf106059.odtbin0 -> 13585 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf106206.odtbin0 -> 74641 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf106693.odtbin0 -> 13585 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf106702.odtbin0 -> 42158 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf106972-pdf17.odtbin0 -> 21561 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf106972.odtbin0 -> 21376 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf107013.odtbin0 -> 19823 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf107018.odtbin0 -> 15787 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf107089.odtbin0 -> 20171 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf107868.odtbin0 -> 10567 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf108963.odpbin0 -> 10369 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf109143.odtbin0 -> 68155 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf113143.odpbin0 -> 100637 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf114256.odsbin0 -> 7716 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf115117-1.odtbin0 -> 8566 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf115117-2.odtbin0 -> 8629 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf115262.odsbin0 -> 27638 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf115967.odtbin0 -> 14621 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf118244_radioButtonGroup.odtbin0 -> 12847 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf121615.odtbin0 -> 10188 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf121962.odtbin0 -> 14974 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf124272.odtbin0 -> 10292 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf127217.odtbin0 -> 9243 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf128445.odpbin0 -> 41261 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf128630.odpbin0 -> 17558 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf129085.docxbin0 -> 44600 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf135192-1.fodp239
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf135346.odsbin0 -> 10422 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf139065.odtbin0 -> 13172 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf139736-1.odtbin0 -> 33041 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf141171.odtbin0 -> 9512 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf144222.odsbin0 -> 9410 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf145274.docxbin0 -> 3788 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf145873.pptxbin0 -> 48006 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf147027.odsbin0 -> 16310 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf147164.odpbin0 -> 17732 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf148442.odtbin0 -> 10562 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf148706.odtbin0 -> 9969 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf150846.txt1
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf150931.odsbin0 -> 8605 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf152231.fodt208
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf154549.odtbin0 -> 84740 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf154982.odtbin0 -> 18250 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf155161.odtbin0 -> 123319 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf155190.odtbin0 -> 9628 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf156685.docxbin0 -> 23687 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf157679.pptxbin0 -> 15255 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf157816.fodt175
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf48707-1.fodt290
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf48707-2.fodt335
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf66597-1.odtbin0 -> 9286 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf66597-2.odtbin0 -> 8265 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf66597-3.odtbin0 -> 8251 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf84283.docbin0 -> 36352 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf99680-2.odtbin0 -> 215797 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf99680.odtbin0 -> 30257 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/toc-link.fodt45
-rw-r--r--vcl/qa/cppunit/pdfexport/data/transparentshape.fodp439
-rw-r--r--vcl/qa/cppunit/pdfexport/data/vid.odtbin0 -> 134265 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/wide_page1.fodt40
-rw-r--r--vcl/qa/cppunit/pdfexport/pdfexport.cxx2774
-rw-r--r--vcl/qa/cppunit/pdfexport/pdfexport2.cxx4858
-rw-r--r--vcl/qa/cppunit/physicalfontcollection.cxx551
-rw-r--r--vcl/qa/cppunit/physicalfontface.cxx301
-rw-r--r--vcl/qa/cppunit/physicalfontfacecollection.cxx53
-rw-r--r--vcl/qa/cppunit/physicalfontfamily.cxx142
-rw-r--r--vcl/qa/cppunit/png/PngFilterTest.cxx2084
-rw-r--r--vcl/qa/cppunit/png/data/alpha-rect-8bit-RGBA.pngbin0 -> 158 bytes
-rw-r--r--vcl/qa/cppunit/png/data/apng_simple.apngbin0 -> 239 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basi0g01.pngbin0 -> 217 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basi0g02.pngbin0 -> 154 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basi0g04.pngbin0 -> 247 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basi0g08.pngbin0 -> 254 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basi0g16.pngbin0 -> 299 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basi2c08.pngbin0 -> 315 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basi2c16.pngbin0 -> 595 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basi3p01.pngbin0 -> 132 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basi3p02.pngbin0 -> 193 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basi3p04.pngbin0 -> 327 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basi3p08.pngbin0 -> 1527 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basi4a08.pngbin0 -> 214 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basi4a16.pngbin0 -> 2855 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basi6a08.pngbin0 -> 361 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basi6a16.pngbin0 -> 4180 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basn0g01.pngbin0 -> 164 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basn0g02.pngbin0 -> 104 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basn0g04.pngbin0 -> 145 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basn0g08.pngbin0 -> 138 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basn0g16.pngbin0 -> 167 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basn2c08.pngbin0 -> 145 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basn2c16.pngbin0 -> 302 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basn3p01.pngbin0 -> 112 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basn3p02.pngbin0 -> 146 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basn3p04.pngbin0 -> 216 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basn3p08.pngbin0 -> 1286 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basn4a08.pngbin0 -> 126 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basn4a16.pngbin0 -> 2206 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basn6a08.pngbin0 -> 184 bytes
-rw-r--r--vcl/qa/cppunit/png/data/basn6a16.pngbin0 -> 3435 bytes
-rw-r--r--vcl/qa/cppunit/png/data/bgai4a08.pngbin0 -> 214 bytes
-rw-r--r--vcl/qa/cppunit/png/data/bgai4a16.pngbin0 -> 2855 bytes
-rw-r--r--vcl/qa/cppunit/png/data/bgan6a08.pngbin0 -> 184 bytes
-rw-r--r--vcl/qa/cppunit/png/data/bgan6a16.pngbin0 -> 3435 bytes
-rw-r--r--vcl/qa/cppunit/png/data/bgbn4a08.pngbin0 -> 140 bytes
-rw-r--r--vcl/qa/cppunit/png/data/bggn4a16.pngbin0 -> 2220 bytes
-rw-r--r--vcl/qa/cppunit/png/data/bgwn6a08.pngbin0 -> 202 bytes
-rw-r--r--vcl/qa/cppunit/png/data/bgyn6a16.pngbin0 -> 3453 bytes
-rw-r--r--vcl/qa/cppunit/png/data/ccwn2c08.pngbin0 -> 1514 bytes
-rw-r--r--vcl/qa/cppunit/png/data/ccwn3p08.pngbin0 -> 1554 bytes
-rw-r--r--vcl/qa/cppunit/png/data/cdfn2c08.pngbin0 -> 404 bytes
-rw-r--r--vcl/qa/cppunit/png/data/cdhn2c08.pngbin0 -> 344 bytes
-rw-r--r--vcl/qa/cppunit/png/data/cdsn2c08.pngbin0 -> 232 bytes
-rw-r--r--vcl/qa/cppunit/png/data/cdun2c08.pngbin0 -> 724 bytes
-rw-r--r--vcl/qa/cppunit/png/data/ch1n3p04.pngbin0 -> 258 bytes
-rw-r--r--vcl/qa/cppunit/png/data/ch2n3p08.pngbin0 -> 1810 bytes
-rw-r--r--vcl/qa/cppunit/png/data/cm0n0g04.pngbin0 -> 292 bytes
-rw-r--r--vcl/qa/cppunit/png/data/cm7n0g04.pngbin0 -> 292 bytes
-rw-r--r--vcl/qa/cppunit/png/data/cm9n0g04.pngbin0 -> 292 bytes
-rw-r--r--vcl/qa/cppunit/png/data/color-rect-4bit-pal.pngbin0 -> 104 bytes
-rw-r--r--vcl/qa/cppunit/png/data/color-rect-8bit-RGB-interlaced.pngbin0 -> 92 bytes
-rw-r--r--vcl/qa/cppunit/png/data/color-rect-8bit-RGB.pngbin0 -> 90 bytes
-rw-r--r--vcl/qa/cppunit/png/data/cs3n2c16.pngbin0 -> 214 bytes
-rw-r--r--vcl/qa/cppunit/png/data/cs3n3p08.pngbin0 -> 259 bytes
-rw-r--r--vcl/qa/cppunit/png/data/cs5n2c08.pngbin0 -> 186 bytes
-rw-r--r--vcl/qa/cppunit/png/data/cs5n3p08.pngbin0 -> 271 bytes
-rw-r--r--vcl/qa/cppunit/png/data/cs8n2c08.pngbin0 -> 149 bytes
-rw-r--r--vcl/qa/cppunit/png/data/cs8n3p08.pngbin0 -> 256 bytes
-rw-r--r--vcl/qa/cppunit/png/data/ct0n0g04.pngbin0 -> 273 bytes
-rw-r--r--vcl/qa/cppunit/png/data/ct1n0g04.pngbin0 -> 792 bytes
-rw-r--r--vcl/qa/cppunit/png/data/cten0g04.pngbin0 -> 742 bytes
-rw-r--r--vcl/qa/cppunit/png/data/ctfn0g04.pngbin0 -> 716 bytes
-rw-r--r--vcl/qa/cppunit/png/data/ctgn0g04.pngbin0 -> 1182 bytes
-rw-r--r--vcl/qa/cppunit/png/data/cthn0g04.pngbin0 -> 1269 bytes
-rw-r--r--vcl/qa/cppunit/png/data/ctjn0g04.pngbin0 -> 941 bytes
-rw-r--r--vcl/qa/cppunit/png/data/ctzn0g04.pngbin0 -> 753 bytes
-rw-r--r--vcl/qa/cppunit/png/data/dummy.gifbin0 -> 101 bytes
-rw-r--r--vcl/qa/cppunit/png/data/exif2c08.pngbin0 -> 1788 bytes
-rw-r--r--vcl/qa/cppunit/png/data/f00n0g08.pngbin0 -> 319 bytes
-rw-r--r--vcl/qa/cppunit/png/data/f00n2c08.pngbin0 -> 2475 bytes
-rw-r--r--vcl/qa/cppunit/png/data/f01n0g08.pngbin0 -> 321 bytes
-rw-r--r--vcl/qa/cppunit/png/data/f01n2c08.pngbin0 -> 1180 bytes
-rw-r--r--vcl/qa/cppunit/png/data/f02n0g08.pngbin0 -> 355 bytes
-rw-r--r--vcl/qa/cppunit/png/data/f02n2c08.pngbin0 -> 1729 bytes
-rw-r--r--vcl/qa/cppunit/png/data/f03n0g08.pngbin0 -> 389 bytes
-rw-r--r--vcl/qa/cppunit/png/data/f03n2c08.pngbin0 -> 1291 bytes
-rw-r--r--vcl/qa/cppunit/png/data/f04n0g08.pngbin0 -> 269 bytes
-rw-r--r--vcl/qa/cppunit/png/data/f04n2c08.pngbin0 -> 985 bytes
-rw-r--r--vcl/qa/cppunit/png/data/f99n0g04.pngbin0 -> 426 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g03n0g16.pngbin0 -> 345 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g03n2c08.pngbin0 -> 370 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g03n3p04.pngbin0 -> 214 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g04n0g16.pngbin0 -> 363 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g04n2c08.pngbin0 -> 377 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g04n3p04.pngbin0 -> 219 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g05n0g16.pngbin0 -> 339 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g05n2c08.pngbin0 -> 350 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g05n3p04.pngbin0 -> 206 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g07n0g16.pngbin0 -> 321 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g07n2c08.pngbin0 -> 340 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g07n3p04.pngbin0 -> 207 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g10n0g16.pngbin0 -> 262 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g10n2c08.pngbin0 -> 285 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g10n3p04.pngbin0 -> 214 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g25n0g16.pngbin0 -> 383 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g25n2c08.pngbin0 -> 405 bytes
-rw-r--r--vcl/qa/cppunit/png/data/g25n3p04.pngbin0 -> 215 bytes
-rw-r--r--vcl/qa/cppunit/png/data/ms-gif.pngbin0 -> 15901 bytes
-rw-r--r--vcl/qa/cppunit/png/data/oi1n0g16.pngbin0 -> 167 bytes
-rw-r--r--vcl/qa/cppunit/png/data/oi1n2c16.pngbin0 -> 302 bytes
-rw-r--r--vcl/qa/cppunit/png/data/oi2n0g16.pngbin0 -> 179 bytes
-rw-r--r--vcl/qa/cppunit/png/data/oi2n2c16.pngbin0 -> 314 bytes
-rw-r--r--vcl/qa/cppunit/png/data/oi4n0g16.pngbin0 -> 203 bytes
-rw-r--r--vcl/qa/cppunit/png/data/oi4n2c16.pngbin0 -> 338 bytes
-rw-r--r--vcl/qa/cppunit/png/data/oi9n0g16.pngbin0 -> 1283 bytes
-rw-r--r--vcl/qa/cppunit/png/data/oi9n2c16.pngbin0 -> 3038 bytes
-rw-r--r--vcl/qa/cppunit/png/data/pp0n2c16.pngbin0 -> 962 bytes
-rw-r--r--vcl/qa/cppunit/png/data/pp0n6a08.pngbin0 -> 818 bytes
-rw-r--r--vcl/qa/cppunit/png/data/ps1n0g08.pngbin0 -> 1456 bytes
-rw-r--r--vcl/qa/cppunit/png/data/ps1n2c16.pngbin0 -> 1620 bytes
-rw-r--r--vcl/qa/cppunit/png/data/ps2n0g08.pngbin0 -> 2320 bytes
-rw-r--r--vcl/qa/cppunit/png/data/ps2n2c16.pngbin0 -> 2484 bytes
-rw-r--r--vcl/qa/cppunit/png/data/rect-1bit-pal.pngbin0 -> 89 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s01i3p01.pngbin0 -> 113 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s01n3p01.pngbin0 -> 113 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s02i3p01.pngbin0 -> 114 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s02n3p01.pngbin0 -> 115 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s03i3p01.pngbin0 -> 118 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s03n3p01.pngbin0 -> 120 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s04i3p01.pngbin0 -> 126 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s04n3p01.pngbin0 -> 121 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s05i3p02.pngbin0 -> 134 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s05n3p02.pngbin0 -> 129 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s06i3p02.pngbin0 -> 143 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s06n3p02.pngbin0 -> 131 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s07i3p02.pngbin0 -> 149 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s07n3p02.pngbin0 -> 138 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s08i3p02.pngbin0 -> 149 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s08n3p02.pngbin0 -> 139 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s09i3p02.pngbin0 -> 147 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s09n3p02.pngbin0 -> 143 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s32i3p04.pngbin0 -> 355 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s32n3p04.pngbin0 -> 263 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s33i3p04.pngbin0 -> 385 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s33n3p04.pngbin0 -> 329 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s34i3p04.pngbin0 -> 349 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s34n3p04.pngbin0 -> 248 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s35i3p04.pngbin0 -> 399 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s35n3p04.pngbin0 -> 338 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s36i3p04.pngbin0 -> 356 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s36n3p04.pngbin0 -> 258 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s37i3p04.pngbin0 -> 393 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s37n3p04.pngbin0 -> 336 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s38i3p04.pngbin0 -> 357 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s38n3p04.pngbin0 -> 245 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s39i3p04.pngbin0 -> 420 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s39n3p04.pngbin0 -> 352 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s40i3p04.pngbin0 -> 357 bytes
-rw-r--r--vcl/qa/cppunit/png/data/s40n3p04.pngbin0 -> 256 bytes
-rw-r--r--vcl/qa/cppunit/png/data/tbbn0g04.pngbin0 -> 429 bytes
-rw-r--r--vcl/qa/cppunit/png/data/tbbn2c16.pngbin0 -> 2041 bytes
-rw-r--r--vcl/qa/cppunit/png/data/tbbn3p08.pngbin0 -> 1499 bytes
-rw-r--r--vcl/qa/cppunit/png/data/tbgn2c16.pngbin0 -> 2041 bytes
-rw-r--r--vcl/qa/cppunit/png/data/tbgn3p08.pngbin0 -> 1499 bytes
-rw-r--r--vcl/qa/cppunit/png/data/tbrn2c08.pngbin0 -> 1633 bytes
-rw-r--r--vcl/qa/cppunit/png/data/tbwn0g16.pngbin0 -> 1313 bytes
-rw-r--r--vcl/qa/cppunit/png/data/tbwn3p08.pngbin0 -> 1496 bytes
-rw-r--r--vcl/qa/cppunit/png/data/tbyn3p08.pngbin0 -> 1499 bytes
-rw-r--r--vcl/qa/cppunit/png/data/tm3n3p02.pngbin0 -> 116 bytes
-rw-r--r--vcl/qa/cppunit/png/data/tp1n3p08.pngbin0 -> 1483 bytes
-rw-r--r--vcl/qa/cppunit/png/data/xc1n0g08.pngbin0 -> 138 bytes
-rw-r--r--vcl/qa/cppunit/png/data/xc9n2c08.pngbin0 -> 145 bytes
-rw-r--r--vcl/qa/cppunit/png/data/xcrn0g04.pngbin0 -> 145 bytes
-rw-r--r--vcl/qa/cppunit/png/data/xcsn0g01.pngbin0 -> 164 bytes
-rw-r--r--vcl/qa/cppunit/png/data/xd0n2c08.pngbin0 -> 145 bytes
-rw-r--r--vcl/qa/cppunit/png/data/xd3n2c08.pngbin0 -> 145 bytes
-rw-r--r--vcl/qa/cppunit/png/data/xd9n2c08.pngbin0 -> 145 bytes
-rw-r--r--vcl/qa/cppunit/png/data/xdtn0g01.pngbin0 -> 61 bytes
-rw-r--r--vcl/qa/cppunit/png/data/xhdn0g08.pngbin0 -> 138 bytes
-rw-r--r--vcl/qa/cppunit/png/data/xlfn0g04.pngbin0 -> 145 bytes
-rw-r--r--vcl/qa/cppunit/png/data/xs1n0g01.pngbin0 -> 164 bytes
-rw-r--r--vcl/qa/cppunit/png/data/xs2n0g01.pngbin0 -> 164 bytes
-rw-r--r--vcl/qa/cppunit/png/data/xs4n0g01.pngbin0 -> 164 bytes
-rw-r--r--vcl/qa/cppunit/png/data/xs7n0g01.pngbin0 -> 164 bytes
-rw-r--r--vcl/qa/cppunit/png/data/z00n2c08.pngbin0 -> 3172 bytes
-rw-r--r--vcl/qa/cppunit/png/data/z03n2c08.pngbin0 -> 232 bytes
-rw-r--r--vcl/qa/cppunit/png/data/z06n2c08.pngbin0 -> 224 bytes
-rw-r--r--vcl/qa/cppunit/png/data/z09n2c08.pngbin0 -> 224 bytes
-rw-r--r--vcl/qa/cppunit/skia/skia.cxx592
-rw-r--r--vcl/qa/cppunit/svm/data/arc.svmbin0 -> 279 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/bitmapexs.svmbin0 -> 3773 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/bitmaps.svmbin0 -> 599 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/chord.svmbin0 -> 279 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/clipregion.svmbin0 -> 431 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/comment.svmbin0 -> 357 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/ellipse.svmbin0 -> 263 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/eps.svmbin0 -> 542 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/fillcolor.svmbin0 -> 244 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/floattransparent.svmbin0 -> 520 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/font.svmbin0 -> 320 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/gradient.svmbin0 -> 15497 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/gradientex.svmbin0 -> 2946 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/hatch.svmbin0 -> 271 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/intersectrectclipregion.svmbin0 -> 254 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/intersectregionclipregion.svmbin0 -> 378 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/layoutmode.svmbin0 -> 237 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/line.svmbin0 -> 325 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/linecolor.svmbin0 -> 244 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/mapmode.svmbin0 -> 336 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/masks.svmbin0 -> 603 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/moveclipregion.svmbin0 -> 287 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/overlinecolor.svmbin0 -> 244 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/pie.svmbin0 -> 279 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/pixel.svmbin0 -> 253 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/point.svmbin0 -> 229 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/polygon.svmbin0 -> 336 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/polyline.svmbin0 -> 400 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/polypolygon.svmbin0 -> 332 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/pushpop.svmbin0 -> 456 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/rasterop.svmbin0 -> 223 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/rect.svmbin0 -> 263 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/refpoint.svmbin0 -> 247 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/roundrect.svmbin0 -> 271 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/strecthtext.svmbin0 -> 259 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/text.svmbin0 -> 249 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/textalign.svmbin0 -> 223 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/textarray.svmbin0 -> 275 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/textcolor.svmbin0 -> 225 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/textfillecolor.svmbin0 -> 226 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/textlanguage.svmbin0 -> 233 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/textline.svmbin0 -> 245 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/textlinecolor.svmbin0 -> 226 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/textrectangle.svmbin0 -> 261 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/transparent.svmbin0 -> 251 bytes
-rw-r--r--vcl/qa/cppunit/svm/data/wallpaper.svmbin0 -> 323 bytes
-rw-r--r--vcl/qa/cppunit/svm/svmtest.cxx2352
-rw-r--r--vcl/qa/cppunit/test_blocklist_evaluate.xml46
-rw-r--r--vcl/qa/cppunit/test_blocklist_parse.xml63
-rw-r--r--vcl/qa/cppunit/test_blocklist_vulkan.xml30
-rw-r--r--vcl/qa/cppunit/text.cxx836
-rw-r--r--vcl/qa/cppunit/timer.cxx566
-rw-r--r--vcl/qa/cppunit/widgetdraw/WidgetDefinitionReaderTest.cxx138
-rw-r--r--vcl/qa/cppunit/widgetdraw/data/definition1.xml81
-rw-r--r--vcl/qa/cppunit/widgetdraw/data/definitionSettings1.xml5
-rw-r--r--vcl/qa/cppunit/widgetdraw/data/definitionSettings2.xml6
-rw-r--r--vcl/qa/cppunit/widgetdraw/data/definitionSettings3.xml13
-rw-r--r--vcl/qa/unit/data/vcl-dialogs-test.txt45
-rw-r--r--vcl/qa/unit/vcl-dialogs-test.cxx58
-rw-r--r--vcl/qt5/QtAccessibleEventListener.cxx439
-rw-r--r--vcl/qt5/QtAccessibleRegistry.cxx45
-rw-r--r--vcl/qt5/QtAccessibleWidget.cxx1848
-rw-r--r--vcl/qt5/QtBitmap.cxx186
-rw-r--r--vcl/qt5/QtClipboard.cxx259
-rw-r--r--vcl/qt5/QtData.cxx227
-rw-r--r--vcl/qt5/QtDragAndDrop.cxx249
-rw-r--r--vcl/qt5/QtFilePicker.cxx983
-rw-r--r--vcl/qt5/QtFont.cxx190
-rw-r--r--vcl/qt5/QtFontFace.cxx217
-rw-r--r--vcl/qt5/QtFrame.cxx1520
-rw-r--r--vcl/qt5/QtGraphics.cxx106
-rw-r--r--vcl/qt5/QtGraphics_Controls.cxx1168
-rw-r--r--vcl/qt5/QtGraphics_GDI.cxx710
-rw-r--r--vcl/qt5/QtGraphics_Text.cxx235
-rw-r--r--vcl/qt5/QtInstance.cxx771
-rw-r--r--vcl/qt5/QtInstance_Print.cxx139
-rw-r--r--vcl/qt5/QtMainWindow.cxx46
-rw-r--r--vcl/qt5/QtMenu.cxx920
-rw-r--r--vcl/qt5/QtObject.cxx155
-rw-r--r--vcl/qt5/QtOpenGLContext.cxx154
-rw-r--r--vcl/qt5/QtPainter.cxx58
-rw-r--r--vcl/qt5/QtPrinter.cxx27
-rw-r--r--vcl/qt5/QtSvpGraphics.cxx114
-rw-r--r--vcl/qt5/QtSvpSurface.cxx91
-rw-r--r--vcl/qt5/QtSvpVirtualDevice.hxx36
-rw-r--r--vcl/qt5/QtSystem.cxx30
-rw-r--r--vcl/qt5/QtTimer.cxx62
-rw-r--r--vcl/qt5/QtTools.cxx123
-rw-r--r--vcl/qt5/QtTransferable.cxx361
-rw-r--r--vcl/qt5/QtVirtualDevice.cxx85
-rw-r--r--vcl/qt5/QtWidget.cxx1024
-rw-r--r--vcl/qt5/QtX11Support.cxx48
-rw-r--r--vcl/qt5/QtXAccessible.cxx30
-rw-r--r--vcl/qt6/QtAccessibleEventListener.cxx12
-rw-r--r--vcl/qt6/QtAccessibleRegistry.cxx12
-rw-r--r--vcl/qt6/QtAccessibleWidget.cxx12
-rw-r--r--vcl/qt6/QtBitmap.cxx12
-rw-r--r--vcl/qt6/QtClipboard.cxx12
-rw-r--r--vcl/qt6/QtData.cxx12
-rw-r--r--vcl/qt6/QtDragAndDrop.cxx12
-rw-r--r--vcl/qt6/QtFilePicker.cxx12
-rw-r--r--vcl/qt6/QtFont.cxx12
-rw-r--r--vcl/qt6/QtFontFace.cxx12
-rw-r--r--vcl/qt6/QtFrame.cxx12
-rw-r--r--vcl/qt6/QtGraphics.cxx12
-rw-r--r--vcl/qt6/QtGraphics_Controls.cxx12
-rw-r--r--vcl/qt6/QtGraphics_GDI.cxx12
-rw-r--r--vcl/qt6/QtGraphics_Text.cxx12
-rw-r--r--vcl/qt6/QtInstance.cxx12
-rw-r--r--vcl/qt6/QtInstance_Print.cxx12
-rw-r--r--vcl/qt6/QtMainWindow.cxx12
-rw-r--r--vcl/qt6/QtMenu.cxx14
-rw-r--r--vcl/qt6/QtObject.cxx12
-rw-r--r--vcl/qt6/QtOpenGLContext.cxx12
-rw-r--r--vcl/qt6/QtPainter.cxx12
-rw-r--r--vcl/qt6/QtPrinter.cxx12
-rw-r--r--vcl/qt6/QtSvpGraphics.cxx12
-rw-r--r--vcl/qt6/QtSvpSurface.cxx12
-rw-r--r--vcl/qt6/QtSvpVirtualDevice.hxx12
-rw-r--r--vcl/qt6/QtSystem.cxx12
-rw-r--r--vcl/qt6/QtTimer.cxx12
-rw-r--r--vcl/qt6/QtTools.cxx12
-rw-r--r--vcl/qt6/QtTransferable.cxx12
-rw-r--r--vcl/qt6/QtVirtualDevice.cxx12
-rw-r--r--vcl/qt6/QtWidget.cxx12
-rw-r--r--vcl/qt6/QtX11Support.cxx12
-rw-r--r--vcl/qt6/QtXAccessible.cxx12
-rw-r--r--vcl/quartz/AquaGraphicsBackend.cxx1346
-rw-r--r--vcl/quartz/CoreTextFont.cxx235
-rw-r--r--vcl/quartz/CoreTextFontFace.cxx98
-rw-r--r--vcl/quartz/SystemFontList.cxx301
-rw-r--r--vcl/quartz/cgutils.mm131
-rw-r--r--vcl/quartz/salbmp.cxx677
-rw-r--r--vcl/quartz/salgdi.cxx495
-rw-r--r--vcl/quartz/salgdicommon.cxx234
-rw-r--r--vcl/quartz/salvd.cxx177
-rw-r--r--vcl/quartz/utils.cxx242
-rw-r--r--vcl/skia/README87
-rw-r--r--vcl/skia/SkiaHelper.cxx914
-rw-r--r--vcl/skia/gdiimpl.cxx2227
-rw-r--r--vcl/skia/osx/bitmap.cxx102
-rw-r--r--vcl/skia/osx/gdiimpl.cxx384
-rw-r--r--vcl/skia/quartz/salbmp.mm107
-rw-r--r--vcl/skia/salbmp.cxx1500
-rw-r--r--vcl/skia/skia_denylist_vulkan.xml73
-rw-r--r--vcl/skia/win/gdiimpl.cxx428
-rw-r--r--vcl/skia/x11/gdiimpl.cxx175
-rw-r--r--vcl/skia/x11/salvd.cxx82
-rw-r--r--vcl/skia/x11/textrender.cxx118
-rw-r--r--vcl/skia/zone.cxx87
-rw-r--r--vcl/source/accessibility/AccessibleTextAttributeHelper.cxx396
-rw-r--r--vcl/source/animate/Animation.cxx706
-rw-r--r--vcl/source/animate/AnimationFrame.cxx58
-rw-r--r--vcl/source/animate/AnimationRenderer.cxx326
-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.cxx203
-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.cxx686
-rw-r--r--vcl/source/app/htmltransferable.cxx78
-rw-r--r--vcl/source/app/i18nhelp.cxx156
-rw-r--r--vcl/source/app/idle.cxx66
-rw-r--r--vcl/source/app/salplug.cxx478
-rw-r--r--vcl/source/app/salusereventlist.cxx164
-rw-r--r--vcl/source/app/salvtables.cxx7618
-rw-r--r--vcl/source/app/scheduler.cxx667
-rw-r--r--vcl/source/app/session.cxx416
-rw-r--r--vcl/source/app/settings.cxx3378
-rw-r--r--vcl/source/app/sound.cxx38
-rw-r--r--vcl/source/app/stdtext.cxx124
-rw-r--r--vcl/source/app/svapp.cxx1786
-rw-r--r--vcl/source/app/svdata.cxx534
-rw-r--r--vcl/source/app/svmain.cxx714
-rw-r--r--vcl/source/app/timer.cxx103
-rw-r--r--vcl/source/app/unohelp.cxx213
-rw-r--r--vcl/source/app/unohelp2.cxx113
-rw-r--r--vcl/source/app/vclevent.cxx94
-rw-r--r--vcl/source/app/watchdog.cxx168
-rw-r--r--vcl/source/app/weldutils.cxx662
-rw-r--r--vcl/source/app/winscheduler.cxx46
-rw-r--r--vcl/source/bitmap/BitmapAlphaClampFilter.cxx44
-rw-r--r--vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx370
-rw-r--r--vcl/source/bitmap/BitmapColorQuantizationFilter.cxx200
-rw-r--r--vcl/source/bitmap/BitmapColorizeFilter.cxx92
-rw-r--r--vcl/source/bitmap/BitmapConvolutionMatrixFilter.cxx199
-rw-r--r--vcl/source/bitmap/BitmapDisabledImageFilter.cxx60
-rw-r--r--vcl/source/bitmap/BitmapDuoToneFilter.cxx59
-rw-r--r--vcl/source/bitmap/BitmapEmbossGreyFilter.cxx136
-rw-r--r--vcl/source/bitmap/BitmapEx.cxx1523
-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.cxx91
-rw-r--r--vcl/source/bitmap/BitmapInterpolateScaleFilter.cxx245
-rw-r--r--vcl/source/bitmap/BitmapLightenFilter.cxx67
-rw-r--r--vcl/source/bitmap/BitmapMaskToAlphaFilter.cxx58
-rw-r--r--vcl/source/bitmap/BitmapMedianFilter.cxx204
-rw-r--r--vcl/source/bitmap/BitmapMonochromeFilter.cxx87
-rw-r--r--vcl/source/bitmap/BitmapMosaicFilter.cxx183
-rw-r--r--vcl/source/bitmap/BitmapPopArtFilter.cxx96
-rw-r--r--vcl/source/bitmap/BitmapReadAccess.cxx534
-rw-r--r--vcl/source/bitmap/BitmapScaleConvolutionFilter.cxx381
-rw-r--r--vcl/source/bitmap/BitmapScaleSuperFilter.cxx1046
-rw-r--r--vcl/source/bitmap/BitmapSeparableUnsharpenFilter.cxx75
-rw-r--r--vcl/source/bitmap/BitmapSepiaFilter.cxx98
-rw-r--r--vcl/source/bitmap/BitmapShadowFilter.cxx47
-rw-r--r--vcl/source/bitmap/BitmapSimpleColorQuantizationFilter.cxx88
-rw-r--r--vcl/source/bitmap/BitmapSmoothenFilter.cxx32
-rw-r--r--vcl/source/bitmap/BitmapSobelGreyFilter.cxx154
-rw-r--r--vcl/source/bitmap/BitmapSolarizeFilter.cxx70
-rw-r--r--vcl/source/bitmap/BitmapSymmetryCheck.cxx83
-rw-r--r--vcl/source/bitmap/BitmapTools.cxx1302
-rw-r--r--vcl/source/bitmap/BitmapWriteAccess.cxx399
-rw-r--r--vcl/source/bitmap/Octree.cxx280
-rw-r--r--vcl/source/bitmap/alpha.cxx209
-rw-r--r--vcl/source/bitmap/bitmap.cxx1681
-rw-r--r--vcl/source/bitmap/bitmapfilter.cxx58
-rw-r--r--vcl/source/bitmap/bitmappaint.cxx1226
-rw-r--r--vcl/source/bitmap/bitmappalette.cxx242
-rw-r--r--vcl/source/bitmap/bmpfast.cxx823
-rw-r--r--vcl/source/bitmap/dibtools.cxx1769
-rw-r--r--vcl/source/bitmap/floyd.hxx177
-rw-r--r--vcl/source/bitmap/impvect.cxx996
-rw-r--r--vcl/source/bitmap/impvect.hxx32
-rw-r--r--vcl/source/bitmap/salbmp.cxx335
-rw-r--r--vcl/source/cnttype/mcnttfactory.cxx67
-rw-r--r--vcl/source/cnttype/mcnttfactory.hxx49
-rw-r--r--vcl/source/cnttype/mcnttype.cxx90
-rw-r--r--vcl/source/cnttype/mcnttype.hxx58
-rw-r--r--vcl/source/components/dtranscomp.cxx467
-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.cxx208
-rw-r--r--vcl/source/control/NotebookbarPopup.cxx161
-rw-r--r--vcl/source/control/PriorityHBox.cxx197
-rw-r--r--vcl/source/control/PriorityMergedHBox.cxx199
-rw-r--r--vcl/source/control/WeldedTabbedNotebookbar.cxx23
-rw-r--r--vcl/source/control/button.cxx3842
-rw-r--r--vcl/source/control/calendar.cxx1747
-rw-r--r--vcl/source/control/combobox.cxx1608
-rw-r--r--vcl/source/control/ctrl.cxx513
-rw-r--r--vcl/source/control/edit.cxx2937
-rw-r--r--vcl/source/control/field.cxx1882
-rw-r--r--vcl/source/control/field2.cxx3188
-rw-r--r--vcl/source/control/fixed.cxx995
-rw-r--r--vcl/source/control/fixedhyper.cxx186
-rw-r--r--vcl/source/control/fmtfield.cxx1366
-rw-r--r--vcl/source/control/hyperlabel.cxx176
-rw-r--r--vcl/source/control/imgctrl.cxx184
-rw-r--r--vcl/source/control/imivctl.hxx510
-rw-r--r--vcl/source/control/imivctl1.cxx2927
-rw-r--r--vcl/source/control/imivctl2.cxx715
-rw-r--r--vcl/source/control/imp_listbox.cxx3029
-rw-r--r--vcl/source/control/ivctrl.cxx679
-rw-r--r--vcl/source/control/listbox.cxx1457
-rw-r--r--vcl/source/control/longcurr.cxx429
-rw-r--r--vcl/source/control/managedmenubutton.cxx100
-rw-r--r--vcl/source/control/menubtn.cxx309
-rw-r--r--vcl/source/control/notebookbar.cxx375
-rw-r--r--vcl/source/control/prgsbar.cxx239
-rw-r--r--vcl/source/control/quickselectionengine.cxx158
-rw-r--r--vcl/source/control/roadmap.cxx845
-rw-r--r--vcl/source/control/roadmapwizard.cxx837
-rw-r--r--vcl/source/control/scrbar.cxx1473
-rw-r--r--vcl/source/control/slider.cxx906
-rw-r--r--vcl/source/control/spinbtn.cxx468
-rw-r--r--vcl/source/control/spinfld.cxx1028
-rw-r--r--vcl/source/control/tabctrl.cxx2402
-rw-r--r--vcl/source/control/throbber.cxx234
-rw-r--r--vcl/source/control/thumbpos.hxx21
-rw-r--r--vcl/source/control/wizardmachine.cxx1436
-rw-r--r--vcl/source/control/wizimpldata.hxx108
-rw-r--r--vcl/source/edit/textdat2.hxx281
-rw-r--r--vcl/source/edit/textdata.cxx345
-rw-r--r--vcl/source/edit/textdoc.cxx536
-rw-r--r--vcl/source/edit/textdoc.hxx126
-rw-r--r--vcl/source/edit/texteng.cxx2894
-rw-r--r--vcl/source/edit/textund2.hxx105
-rw-r--r--vcl/source/edit/textundo.cxx331
-rw-r--r--vcl/source/edit/textundo.hxx77
-rw-r--r--vcl/source/edit/textview.cxx2238
-rw-r--r--vcl/source/edit/txtattr.cxx93
-rw-r--r--vcl/source/edit/vclmedit.cxx1509
-rw-r--r--vcl/source/edit/xtextedt.cxx227
-rw-r--r--vcl/source/filter/FilterConfigCache.cxx489
-rw-r--r--vcl/source/filter/FilterConfigCache.hxx97
-rw-r--r--vcl/source/filter/FilterConfigItem.cxx404
-rw-r--r--vcl/source/filter/GraphicFormatDetector.cxx1428
-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.cxx551
-rw-r--r--vcl/source/filter/egif/giflzwc.cxx225
-rw-r--r--vcl/source/filter/egif/giflzwc.hxx52
-rw-r--r--vcl/source/filter/eps/eps.cxx2667
-rw-r--r--vcl/source/filter/etiff/etiff.cxx585
-rw-r--r--vcl/source/filter/graphicfilter.cxx1961
-rw-r--r--vcl/source/filter/graphicfilter2.cxx510
-rw-r--r--vcl/source/filter/idxf/dxf2mtf.cxx902
-rw-r--r--vcl/source/filter/idxf/dxf2mtf.hxx114
-rw-r--r--vcl/source/filter/idxf/dxfblkrd.cxx125
-rw-r--r--vcl/source/filter/idxf/dxfblkrd.hxx79
-rw-r--r--vcl/source/filter/idxf/dxfentrd.cxx848
-rw-r--r--vcl/source/filter/idxf/dxfentrd.hxx535
-rw-r--r--vcl/source/filter/idxf/dxfgrprd.cxx213
-rw-r--r--vcl/source/filter/idxf/dxfgrprd.hxx111
-rw-r--r--vcl/source/filter/idxf/dxfreprd.cxx480
-rw-r--r--vcl/source/filter/idxf/dxfreprd.hxx125
-rw-r--r--vcl/source/filter/idxf/dxftblrd.cxx381
-rw-r--r--vcl/source/filter/idxf/dxftblrd.hxx171
-rw-r--r--vcl/source/filter/idxf/dxfvec.cxx232
-rw-r--r--vcl/source/filter/idxf/dxfvec.hxx214
-rw-r--r--vcl/source/filter/idxf/idxf.cxx43
-rw-r--r--vcl/source/filter/ieps/ieps.cxx817
-rw-r--r--vcl/source/filter/igif/decode.cxx214
-rw-r--r--vcl/source/filter/igif/decode.hxx63
-rw-r--r--vcl/source/filter/igif/gifread.cxx1022
-rw-r--r--vcl/source/filter/igif/gifread.hxx27
-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.cxx113
-rw-r--r--vcl/source/filter/ipdf/pdfdocument.cxx3384
-rw-r--r--vcl/source/filter/ipdf/pdfread.cxx395
-rw-r--r--vcl/source/filter/ipict/ipict.cxx2040
-rw-r--r--vcl/source/filter/ipict/shape.cxx259
-rw-r--r--vcl/source/filter/ipict/shape.hxx54
-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.cxx359
-rw-r--r--vcl/source/filter/ixbm/xbmread.cxx396
-rw-r--r--vcl/source/filter/ixbm/xbmread.hxx26
-rw-r--r--vcl/source/filter/ixpm/rgbtable.hxx693
-rw-r--r--vcl/source/filter/ixpm/xpmread.cxx695
-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.hxx71
-rw-r--r--vcl/source/filter/jpeg/JpegTransform.cxx42
-rw-r--r--vcl/source/filter/jpeg/JpegTransform.hxx37
-rw-r--r--vcl/source/filter/jpeg/JpegWriter.cxx265
-rw-r--r--vcl/source/filter/jpeg/JpegWriter.hxx54
-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.h64
-rw-r--r--vcl/source/filter/jpeg/jpeg.hxx35
-rw-r--r--vcl/source/filter/jpeg/jpegc.cxx551
-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.cxx915
-rw-r--r--vcl/source/filter/png/PngImageWriter.cxx511
-rw-r--r--vcl/source/filter/png/png.hxx32
-rw-r--r--vcl/source/filter/svm/SvmConverter.cxx1289
-rw-r--r--vcl/source/filter/svm/SvmConverter.hxx89
-rw-r--r--vcl/source/filter/svm/SvmReader.cxx1480
-rw-r--r--vcl/source/filter/svm/SvmWriter.cxx1447
-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.cxx1510
-rw-r--r--vcl/source/filter/wmf/emfwr.hxx111
-rw-r--r--vcl/source/filter/wmf/wmf.cxx132
-rw-r--r--vcl/source/filter/wmf/wmfexternal.cxx62
-rw-r--r--vcl/source/filter/wmf/wmfwr.cxx1901
-rw-r--r--vcl/source/filter/wmf/wmfwr.hxx202
-rw-r--r--vcl/source/font/DirectFontSubstitution.cxx71
-rw-r--r--vcl/source/font/EmphasisMark.cxx174
-rw-r--r--vcl/source/font/Feature.cxx162
-rw-r--r--vcl/source/font/FeatureCollector.cxx206
-rw-r--r--vcl/source/font/FeatureParser.cxx67
-rw-r--r--vcl/source/font/FontSelectPattern.cxx156
-rw-r--r--vcl/source/font/LogicalFontInstance.cxx348
-rw-r--r--vcl/source/font/OpenTypeFeatureDefinitionList.cxx159
-rw-r--r--vcl/source/font/PhysicalFontCollection.cxx1279
-rw-r--r--vcl/source/font/PhysicalFontFace.cxx510
-rw-r--r--vcl/source/font/PhysicalFontFamily.cxx269
-rw-r--r--vcl/source/font/font.cxx1185
-rw-r--r--vcl/source/font/fontattributes.cxx61
-rw-r--r--vcl/source/font/fontcache.cxx283
-rw-r--r--vcl/source/font/fontcharmap.cxx251
-rw-r--r--vcl/source/font/fontmetric.cxx563
-rw-r--r--vcl/source/fontsubset/cff.cxx2625
-rw-r--r--vcl/source/fontsubset/fontsubset.cxx94
-rw-r--r--vcl/source/fontsubset/sft.cxx1847
-rw-r--r--vcl/source/fontsubset/ttcr.cxx1136
-rw-r--r--vcl/source/fontsubset/ttcr.hxx288
-rw-r--r--vcl/source/fontsubset/xlat.cxx144
-rw-r--r--vcl/source/fontsubset/xlat.hxx37
-rw-r--r--vcl/source/gdi/CommonSalLayout.cxx850
-rw-r--r--vcl/source/gdi/FileDefinitionWidgetDraw.cxx1130
-rw-r--r--vcl/source/gdi/TypeSerializer.cxx483
-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.cxx363
-rw-r--r--vcl/source/gdi/extoutdevdata.cxx27
-rw-r--r--vcl/source/gdi/formpdfexport.cxx824
-rw-r--r--vcl/source/gdi/gdimetafiletools.cxx1086
-rw-r--r--vcl/source/gdi/gdimtf.cxx2357
-rw-r--r--vcl/source/gdi/gfxlink.cxx173
-rw-r--r--vcl/source/gdi/gradient.cxx675
-rw-r--r--vcl/source/gdi/graph.cxx560
-rw-r--r--vcl/source/gdi/graphictools.cxx289
-rw-r--r--vcl/source/gdi/hatch.cxx114
-rw-r--r--vcl/source/gdi/impglyphitem.cxx555
-rw-r--r--vcl/source/gdi/impgraph.cxx1775
-rw-r--r--vcl/source/gdi/jobset.cxx421
-rw-r--r--vcl/source/gdi/lineinfo.cxx293
-rw-r--r--vcl/source/gdi/mapmod.cxx191
-rw-r--r--vcl/source/gdi/metaact.cxx2208
-rw-r--r--vcl/source/gdi/mtfxmldump.cxx1568
-rw-r--r--vcl/source/gdi/oldprintadaptor.cxx109
-rw-r--r--vcl/source/gdi/pdfbuildin_fonts.cxx760
-rw-r--r--vcl/source/gdi/pdfextoutdevdata.cxx919
-rw-r--r--vcl/source/gdi/pdfobjectcopier.cxx346
-rw-r--r--vcl/source/gdi/pdfwriter.cxx485
-rw-r--r--vcl/source/gdi/pdfwriter_impl.cxx11934
-rw-r--r--vcl/source/gdi/pdfwriter_impl2.cxx2047
-rw-r--r--vcl/source/gdi/print.cxx1676
-rw-r--r--vcl/source/gdi/print2.cxx70
-rw-r--r--vcl/source/gdi/print3.cxx2156
-rw-r--r--vcl/source/gdi/regband.cxx886
-rw-r--r--vcl/source/gdi/region.cxx1793
-rw-r--r--vcl/source/gdi/regionband.cxx1365
-rw-r--r--vcl/source/gdi/salgdiimpl.cxx24
-rw-r--r--vcl/source/gdi/salgdilayout.cxx940
-rw-r--r--vcl/source/gdi/sallayout.cxx1182
-rw-r--r--vcl/source/gdi/salmisc.cxx433
-rw-r--r--vcl/source/gdi/scrptrun.cxx259
-rw-r--r--vcl/source/gdi/vectorgraphicdata.cxx357
-rw-r--r--vcl/source/gdi/virdev.cxx521
-rw-r--r--vcl/source/gdi/wall.cxx271
-rw-r--r--vcl/source/graphic/BinaryDataContainer.cxx189
-rw-r--r--vcl/source/graphic/BinaryDataContainerTools.cxx28
-rw-r--r--vcl/source/graphic/GraphicID.cxx101
-rw-r--r--vcl/source/graphic/GraphicLoader.cxx44
-rw-r--r--vcl/source/graphic/GraphicObject.cxx935
-rw-r--r--vcl/source/graphic/GraphicObject2.cxx503
-rw-r--r--vcl/source/graphic/GraphicReader.cxx29
-rw-r--r--vcl/source/graphic/Manager.cxx317
-rw-r--r--vcl/source/graphic/UnoBinaryDataContainer.cxx22
-rw-r--r--vcl/source/graphic/UnoGraphic.cxx261
-rw-r--r--vcl/source/graphic/UnoGraphicDescriptor.cxx421
-rw-r--r--vcl/source/graphic/UnoGraphicMapper.cxx87
-rw-r--r--vcl/source/graphic/UnoGraphicObject.cxx101
-rw-r--r--vcl/source/graphic/UnoGraphicProvider.cxx837
-rw-r--r--vcl/source/graphic/VectorGraphicLoader.cxx26
-rw-r--r--vcl/source/graphic/VectorGraphicSearch.cxx307
-rw-r--r--vcl/source/helper/canvasbitmap.cxx1346
-rw-r--r--vcl/source/helper/canvastools.cxx627
-rw-r--r--vcl/source/helper/commandinfoprovider.cxx477
-rw-r--r--vcl/source/helper/displayconnectiondispatch.cxx111
-rw-r--r--vcl/source/helper/driverblocklist.cxx769
-rw-r--r--vcl/source/helper/evntpost.cxx57
-rw-r--r--vcl/source/helper/idletask.cxx51
-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.cxx186
-rw-r--r--vcl/source/image/ImplImageTree.cxx716
-rw-r--r--vcl/source/opengl/OpenGLContext.cxx496
-rw-r--r--vcl/source/opengl/OpenGLHelper.cxx911
-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.cxx667
-rw-r--r--vcl/source/opengl/x11/context.cxx517
-rw-r--r--vcl/source/outdev/background.cxx77
-rw-r--r--vcl/source/outdev/bitmap.cxx993
-rw-r--r--vcl/source/outdev/bitmapex.cxx680
-rw-r--r--vcl/source/outdev/clipping.cxx224
-rw-r--r--vcl/source/outdev/curvedshapes.cxx212
-rw-r--r--vcl/source/outdev/eps.cxx84
-rw-r--r--vcl/source/outdev/fill.cxx99
-rw-r--r--vcl/source/outdev/font.cxx1293
-rw-r--r--vcl/source/outdev/gradient.cxx624
-rw-r--r--vcl/source/outdev/hatch.cxx439
-rw-r--r--vcl/source/outdev/line.cxx370
-rw-r--r--vcl/source/outdev/map.cxx1868
-rw-r--r--vcl/source/outdev/mask.cxx162
-rw-r--r--vcl/source/outdev/nativecontrols.cxx326
-rw-r--r--vcl/source/outdev/outdev.cxx822
-rw-r--r--vcl/source/outdev/pixel.cxx118
-rw-r--r--vcl/source/outdev/polygon.cxx510
-rw-r--r--vcl/source/outdev/polyline.cxx404
-rw-r--r--vcl/source/outdev/rect.cxx438
-rw-r--r--vcl/source/outdev/stack.cxx205
-rw-r--r--vcl/source/outdev/text.cxx2106
-rw-r--r--vcl/source/outdev/textline.cxx1126
-rw-r--r--vcl/source/outdev/transparent.cxx1935
-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.cxx122
-rw-r--r--vcl/source/pdf/PDFiumLibrary.cxx1531
-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.cxx367
-rw-r--r--vcl/source/printer/Options.cxx108
-rw-r--r--vcl/source/printer/QueueInfo.cxx35
-rw-r--r--vcl/source/rendercontext/drawmode.cxx283
-rw-r--r--vcl/source/salmain/salmain.cxx35
-rw-r--r--vcl/source/text/ImplLayoutArgs.cxx363
-rw-r--r--vcl/source/text/ImplLayoutRuns.cxx172
-rw-r--r--vcl/source/text/TextLayoutCache.cxx75
-rw-r--r--vcl/source/text/mnemonic.cxx53
-rw-r--r--vcl/source/text/textlayout.cxx746
-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.cxx1301
-rw-r--r--vcl/source/treelist/iconview.cxx330
-rw-r--r--vcl/source/treelist/iconviewimpl.cxx738
-rw-r--r--vcl/source/treelist/iconviewimpl.hxx91
-rw-r--r--vcl/source/treelist/imap.cxx988
-rw-r--r--vcl/source/treelist/imap2.cxx528
-rw-r--r--vcl/source/treelist/imap3.cxx84
-rw-r--r--vcl/source/treelist/inetimg.cxx136
-rw-r--r--vcl/source/treelist/svimpbox.cxx3160
-rw-r--r--vcl/source/treelist/svlbitm.cxx551
-rw-r--r--vcl/source/treelist/svtabbx.cxx1139
-rw-r--r--vcl/source/treelist/transfer.cxx2243
-rw-r--r--vcl/source/treelist/transfer2.cxx528
-rw-r--r--vcl/source/treelist/treelist.cxx1509
-rw-r--r--vcl/source/treelist/treelistbox.cxx3589
-rw-r--r--vcl/source/treelist/treelistentry.cxx234
-rw-r--r--vcl/source/treelist/uiobject.cxx220
-rw-r--r--vcl/source/treelist/viewdataentry.cxx87
-rw-r--r--vcl/source/uitest/logger.cxx624
-rw-r--r--vcl/source/uitest/uiobject.cxx1864
-rw-r--r--vcl/source/uitest/uitest.cxx80
-rw-r--r--vcl/source/uitest/uno/uiobject_uno.cxx209
-rw-r--r--vcl/source/uitest/uno/uiobject_uno.hxx53
-rw-r--r--vcl/source/uitest/uno/uitest_uno.cxx114
-rw-r--r--vcl/source/window/DocWindow.cxx39
-rw-r--r--vcl/source/window/EnumContext.cxx220
-rw-r--r--vcl/source/window/NotebookBarAddonsMerger.cxx183
-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.cxx594
-rw-r--r--vcl/source/window/accmgr.cxx228
-rw-r--r--vcl/source/window/brdwin.cxx2003
-rw-r--r--vcl/source/window/bubblewindow.cxx591
-rw-r--r--vcl/source/window/bufferdevice.cxx45
-rw-r--r--vcl/source/window/bufferdevice.hxx35
-rw-r--r--vcl/source/window/builder.cxx4399
-rw-r--r--vcl/source/window/clipping.cxx709
-rw-r--r--vcl/source/window/commandevent.cxx214
-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.cxx1020
-rw-r--r--vcl/source/window/dialog.cxx1736
-rw-r--r--vcl/source/window/dlgctrl.cxx1168
-rw-r--r--vcl/source/window/dlgctrl.hxx34
-rw-r--r--vcl/source/window/dndeventdispatcher.cxx412
-rw-r--r--vcl/source/window/dndlistenercontainer.cxx455
-rw-r--r--vcl/source/window/dockingarea.cxx257
-rw-r--r--vcl/source/window/dockmgr.cxx1083
-rw-r--r--vcl/source/window/dockwin.cxx1148
-rw-r--r--vcl/source/window/errinf.cxx222
-rw-r--r--vcl/source/window/event.cxx673
-rw-r--r--vcl/source/window/floatwin.cxx974
-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.cxx3128
-rw-r--r--vcl/source/window/legacyaccessibility.cxx241
-rw-r--r--vcl/source/window/menu.cxx3143
-rw-r--r--vcl/source/window/menubarwindow.cxx1220
-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.cxx316
-rw-r--r--vcl/source/window/menuitemlist.hxx151
-rw-r--r--vcl/source/window/menuwindow.cxx118
-rw-r--r--vcl/source/window/menuwindow.hxx56
-rw-r--r--vcl/source/window/mnemonic.cxx351
-rw-r--r--vcl/source/window/mouse.cxx744
-rw-r--r--vcl/source/window/paint.cxx1808
-rw-r--r--vcl/source/window/printdlg.cxx2254
-rw-r--r--vcl/source/window/scrwnd.cxx379
-rw-r--r--vcl/source/window/seleng.cxx422
-rw-r--r--vcl/source/window/settings.cxx255
-rw-r--r--vcl/source/window/split.cxx698
-rw-r--r--vcl/source/window/splitwin.cxx2717
-rw-r--r--vcl/source/window/stacking.cxx1152
-rw-r--r--vcl/source/window/status.cxx1494
-rw-r--r--vcl/source/window/syschild.cxx200
-rw-r--r--vcl/source/window/syswin.cxx1171
-rw-r--r--vcl/source/window/tabdlg.cxx184
-rw-r--r--vcl/source/window/tabpage.cxx309
-rw-r--r--vcl/source/window/taskpanelist.cxx287
-rw-r--r--vcl/source/window/toolbox.cxx4810
-rw-r--r--vcl/source/window/toolbox2.cxx1757
-rw-r--r--vcl/source/window/window.cxx3979
-rw-r--r--vcl/source/window/window2.cxx2059
-rw-r--r--vcl/source/window/window3.cxx220
-rw-r--r--vcl/source/window/winproc.cxx2948
-rw-r--r--vcl/source/window/wrkwin.cxx271
-rw-r--r--vcl/uiconfig/theme_definitions/ios/arrow-down.svg5
-rw-r--r--vcl/uiconfig/theme_definitions/ios/arrow-up.svg5
-rw-r--r--vcl/uiconfig/theme_definitions/ios/combobox-button-disabled.svg4
-rw-r--r--vcl/uiconfig/theme_definitions/ios/combobox-button.svg4
-rw-r--r--vcl/uiconfig/theme_definitions/ios/combobox-disabled.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/combobox.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/common-rect-disabled.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/common-rect-focus-slim.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/common-rect-focus.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/common-rect.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/definition.xml530
-rw-r--r--vcl/uiconfig/theme_definitions/ios/pushbutton-default.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/pushbutton-disabled.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/pushbutton-rollover.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/scrollbar-horizontal.svg4
-rw-r--r--vcl/uiconfig/theme_definitions/ios/scrollbar-vertical.svg4
-rw-r--r--vcl/uiconfig/theme_definitions/ios/slider-button-disabled.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/slider-button.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/spinbox-left-disabled.svg4
-rw-r--r--vcl/uiconfig/theme_definitions/ios/spinbox-left-pressed.svg5
-rw-r--r--vcl/uiconfig/theme_definitions/ios/spinbox-left.svg4
-rw-r--r--vcl/uiconfig/theme_definitions/ios/spinbox-right-disabled.svg5
-rw-r--r--vcl/uiconfig/theme_definitions/ios/spinbox-right-pressed.svg6
-rw-r--r--vcl/uiconfig/theme_definitions/ios/spinbox-right.svg5
-rw-r--r--vcl/uiconfig/theme_definitions/ios/switch-off-disabled.svg14
-rw-r--r--vcl/uiconfig/theme_definitions/ios/switch-off-pressed.svg15
-rw-r--r--vcl/uiconfig/theme_definitions/ios/switch-off.svg15
-rw-r--r--vcl/uiconfig/theme_definitions/ios/switch-on-disabled.svg15
-rw-r--r--vcl/uiconfig/theme_definitions/ios/switch-on-pressed.svg11
-rw-r--r--vcl/uiconfig/theme_definitions/ios/switch-on.svg15
-rw-r--r--vcl/uiconfig/theme_definitions/ios/tabitem-first-selected.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/tabitem-first.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/tabitem-last-selected.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/tabitem-last.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/tabitem-middle-selected.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/tabitem-middle.svg3
-rw-r--r--vcl/uiconfig/theme_definitions/ios/tick-off-disabled.svg4
-rw-r--r--vcl/uiconfig/theme_definitions/ios/tick-off-pressed.svg11
-rw-r--r--vcl/uiconfig/theme_definitions/ios/tick-off.svg13
-rw-r--r--vcl/uiconfig/theme_definitions/ios/tick-on-disabled.svg11
-rw-r--r--vcl/uiconfig/theme_definitions/ios/tick-on-pressed.svg11
-rw-r--r--vcl/uiconfig/theme_definitions/ios/tick-on.svg13
-rw-r--r--vcl/uiconfig/ui/aboutbox.ui137
-rw-r--r--vcl/uiconfig/ui/combobox.ui126
-rw-r--r--vcl/uiconfig/ui/cupspassworddialog.ui185
-rw-r--r--vcl/uiconfig/ui/dockingwindow.ui23
-rw-r--r--vcl/uiconfig/ui/editmenu.ui76
-rw-r--r--vcl/uiconfig/ui/errornocontentdialog.ui35
-rw-r--r--vcl/uiconfig/ui/errornoprinterdialog.ui38
-rw-r--r--vcl/uiconfig/ui/interimdockparent.ui29
-rw-r--r--vcl/uiconfig/ui/interimtearableparent.ui27
-rw-r--r--vcl/uiconfig/ui/menutogglebutton3.ui40
-rw-r--r--vcl/uiconfig/ui/menutogglebutton4.ui37
-rw-r--r--vcl/uiconfig/ui/openlockedquerybox.ui266
-rw-r--r--vcl/uiconfig/ui/printdialog.ui1481
-rw-r--r--vcl/uiconfig/ui/printerdevicepage.ui235
-rw-r--r--vcl/uiconfig/ui/printerpaperpage.ui131
-rw-r--r--vcl/uiconfig/ui/printerpropertiesdialog.ui178
-rw-r--r--vcl/uiconfig/ui/printprogressdialog.ui88
-rw-r--r--vcl/uiconfig/ui/querydialog.ui113
-rw-r--r--vcl/uiconfig/ui/screenshotparent.ui54
-rw-r--r--vcl/uiconfig/ui/wizard.ui17
-rw-r--r--vcl/unx/generic/app/gendata.cxx69
-rw-r--r--vcl/unx/generic/app/gendisp.cxx69
-rw-r--r--vcl/unx/generic/app/geninst.cxx99
-rw-r--r--vcl/unx/generic/app/gensys.cxx116
-rw-r--r--vcl/unx/generic/app/i18n_cb.cxx494
-rw-r--r--vcl/unx/generic/app/i18n_ic.cxx604
-rw-r--r--vcl/unx/generic/app/i18n_im.cxx410
-rw-r--r--vcl/unx/generic/app/i18n_keysym.cxx358
-rw-r--r--vcl/unx/generic/app/i18n_xkb.cxx107
-rw-r--r--vcl/unx/generic/app/keysymnames.cxx509
-rw-r--r--vcl/unx/generic/app/randrwrapper.cxx181
-rw-r--r--vcl/unx/generic/app/saldata.cxx775
-rw-r--r--vcl/unx/generic/app/saldisp.cxx2489
-rw-r--r--vcl/unx/generic/app/salinst.cxx253
-rw-r--r--vcl/unx/generic/app/saltimer.cxx68
-rw-r--r--vcl/unx/generic/app/sm.cxx857
-rw-r--r--vcl/unx/generic/app/wmadaptor.cxx2196
-rw-r--r--vcl/unx/generic/desktopdetect/desktopdetector.cxx264
-rw-r--r--vcl/unx/generic/dtrans/X11_clipboard.cxx231
-rw-r--r--vcl/unx/generic/dtrans/X11_clipboard.hxx111
-rw-r--r--vcl/unx/generic/dtrans/X11_dndcontext.cxx118
-rw-r--r--vcl/unx/generic/dtrans/X11_dndcontext.hxx80
-rw-r--r--vcl/unx/generic/dtrans/X11_droptarget.cxx177
-rw-r--r--vcl/unx/generic/dtrans/X11_selection.cxx4157
-rw-r--r--vcl/unx/generic/dtrans/X11_selection.hxx496
-rw-r--r--vcl/unx/generic/dtrans/X11_service.cxx88
-rw-r--r--vcl/unx/generic/dtrans/X11_transferable.cxx101
-rw-r--r--vcl/unx/generic/dtrans/X11_transferable.hxx50
-rw-r--r--vcl/unx/generic/dtrans/bmp.cxx786
-rw-r--r--vcl/unx/generic/dtrans/bmp.hxx75
-rw-r--r--vcl/unx/generic/dtrans/config.cxx123
-rw-r--r--vcl/unx/generic/dtrans/copydata_curs.h36
-rw-r--r--vcl/unx/generic/dtrans/copydata_mask.h36
-rw-r--r--vcl/unx/generic/dtrans/linkdata_curs.h36
-rw-r--r--vcl/unx/generic/dtrans/linkdata_mask.h36
-rw-r--r--vcl/unx/generic/dtrans/movedata_curs.h36
-rw-r--r--vcl/unx/generic/dtrans/movedata_mask.h36
-rw-r--r--vcl/unx/generic/dtrans/nodrop_curs.h36
-rw-r--r--vcl/unx/generic/dtrans/nodrop_mask.h36
-rw-r--r--vcl/unx/generic/fontmanager/fontconfig.cxx1310
-rw-r--r--vcl/unx/generic/fontmanager/fontmanager.cxx736
-rw-r--r--vcl/unx/generic/fontmanager/fontsubst.cxx223
-rw-r--r--vcl/unx/generic/fontmanager/helper.cxx261
-rw-r--r--vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.cxx243
-rw-r--r--vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.hxx188
-rw-r--r--vcl/unx/generic/gdi/cairo_xlib_cairo.cxx277
-rw-r--r--vcl/unx/generic/gdi/cairo_xlib_cairo.hxx93
-rw-r--r--vcl/unx/generic/gdi/cairotextrender.cxx526
-rw-r--r--vcl/unx/generic/gdi/font.cxx84
-rw-r--r--vcl/unx/generic/gdi/freetypetextrender.cxx185
-rw-r--r--vcl/unx/generic/gdi/salgdi.cxx298
-rw-r--r--vcl/unx/generic/gdi/salvd.cxx241
-rw-r--r--vcl/unx/generic/glyphs/freetype_glyphcache.cxx824
-rw-r--r--vcl/unx/generic/glyphs/glyphcache.cxx86
-rw-r--r--vcl/unx/generic/print/genprnpsp.cxx1097
-rw-r--r--vcl/unx/generic/print/genpspgraphics.cxx195
-rw-r--r--vcl/unx/generic/print/prtsetup.cxx465
-rw-r--r--vcl/unx/generic/print/prtsetup.hxx135
-rw-r--r--vcl/unx/generic/print/psheader.ps363
-rw-r--r--vcl/unx/generic/printer/configuration/README5
-rw-r--r--vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS582
-rw-r--r--vcl/unx/generic/printer/configuration/psprint.conf94
-rw-r--r--vcl/unx/generic/printer/cpdmgr.cxx756
-rw-r--r--vcl/unx/generic/printer/cupsmgr.cxx992
-rw-r--r--vcl/unx/generic/printer/jobdata.cxx238
-rw-r--r--vcl/unx/generic/printer/ppdparser.cxx1951
-rw-r--r--vcl/unx/generic/printer/printerinfomanager.cxx868
-rw-r--r--vcl/unx/generic/window/salframe.cxx3871
-rw-r--r--vcl/unx/generic/window/salobj.cxx506
-rw-r--r--vcl/unx/generic/window/sessioninhibitor.cxx370
-rw-r--r--vcl/unx/gtk3/a11y/TODO49
-rw-r--r--vcl/unx/gtk3/a11y/atkaction.cxx269
-rw-r--r--vcl/unx/gtk3/a11y/atkbridge.cxx32
-rw-r--r--vcl/unx/gtk3/a11y/atkcomponent.cxx431
-rw-r--r--vcl/unx/gtk3/a11y/atkeditabletext.cxx193
-rw-r--r--vcl/unx/gtk3/a11y/atkfactory.cxx186
-rw-r--r--vcl/unx/gtk3/a11y/atkfactory.hxx30
-rw-r--r--vcl/unx/gtk3/a11y/atkhypertext.cxx277
-rw-r--r--vcl/unx/gtk3/a11y/atkimage.cxx135
-rw-r--r--vcl/unx/gtk3/a11y/atklistener.cxx783
-rw-r--r--vcl/unx/gtk3/a11y/atklistener.hxx70
-rw-r--r--vcl/unx/gtk3/a11y/atkregistry.cxx66
-rw-r--r--vcl/unx/gtk3/a11y/atkregistry.hxx34
-rw-r--r--vcl/unx/gtk3/a11y/atkselection.cxx206
-rw-r--r--vcl/unx/gtk3/a11y/atktable.cxx647
-rw-r--r--vcl/unx/gtk3/a11y/atktablecell.cxx269
-rw-r--r--vcl/unx/gtk3/a11y/atktext.cxx881
-rw-r--r--vcl/unx/gtk3/a11y/atktextattributes.cxx1388
-rw-r--r--vcl/unx/gtk3/a11y/atktextattributes.hxx45
-rw-r--r--vcl/unx/gtk3/a11y/atkutil.cxx626
-rw-r--r--vcl/unx/gtk3/a11y/atkutil.hxx26
-rw-r--r--vcl/unx/gtk3/a11y/atkvalue.cxx154
-rw-r--r--vcl/unx/gtk3/a11y/atkwrapper.cxx1103
-rw-r--r--vcl/unx/gtk3/a11y/atkwrapper.hxx131
-rw-r--r--vcl/unx/gtk3/customcellrenderer.cxx316
-rw-r--r--vcl/unx/gtk3/customcellrenderer.hxx49
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx2091
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx239
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx205
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFolderPicker.hxx66
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkPicker.cxx295
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkPicker.hxx144
-rw-r--r--vcl/unx/gtk3/fpicker/eventnotification.hxx41
-rw-r--r--vcl/unx/gtk3/fpicker/resourceprovider.cxx80
-rw-r--r--vcl/unx/gtk3/gloactiongroup.cxx405
-rw-r--r--vcl/unx/gtk3/glomenu.cxx694
-rw-r--r--vcl/unx/gtk3/gtkcairo.cxx129
-rw-r--r--vcl/unx/gtk3/gtkcairo.hxx46
-rw-r--r--vcl/unx/gtk3/gtkdata.cxx984
-rw-r--r--vcl/unx/gtk3/gtkframe.cxx6416
-rw-r--r--vcl/unx/gtk3/gtkinst.cxx24855
-rw-r--r--vcl/unx/gtk3/gtkobject.cxx611
-rw-r--r--vcl/unx/gtk3/gtksalmenu.cxx1646
-rw-r--r--vcl/unx/gtk3/gtksys.cxx330
-rw-r--r--vcl/unx/gtk3/hudawareness.cxx110
-rw-r--r--vcl/unx/gtk3/salnativewidgets-gtk.cxx3082
-rw-r--r--vcl/unx/gtk3_kde5/FPServiceInfo.hxx28
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkaction.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkbridge.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkcomponent.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkeditabletext.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkfactory.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkhypertext.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkimage.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atklistener.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkregistry.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkselection.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktable.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktablecell.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktext.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktextattributes.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkutil.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkvalue.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwrapper.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/filepicker_ipc_commands.hxx173
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_cairo.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_customcellrenderer.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.cxx455
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.hxx131
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.cxx276
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.hxx135
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker.cxx85
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker.hxx59
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_gloactiongroup.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_glomenu.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_gtkdata.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_gtkframe.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_gtkinst.cxx57
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_gtkobject.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_gtksalmenu.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_gtksys.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_hudawareness.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_salnativewidgets-gtk.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/kde5_filepicker.cxx286
-rw-r--r--vcl/unx/gtk3_kde5/kde5_filepicker.hxx110
-rw-r--r--vcl/unx/gtk3_kde5/kde5_filepicker_ipc.cxx374
-rw-r--r--vcl/unx/gtk3_kde5/kde5_filepicker_ipc.hxx51
-rw-r--r--vcl/unx/gtk3_kde5/kde5_lo_filepicker_main.cxx53
-rw-r--r--vcl/unx/gtk4/a11y.cxx851
-rw-r--r--vcl/unx/gtk4/a11y.hxx24
-rw-r--r--vcl/unx/gtk4/convert3to4.cxx1603
-rw-r--r--vcl/unx/gtk4/convert3to4.hxx16
-rw-r--r--vcl/unx/gtk4/customcellrenderer.cxx12
-rw-r--r--vcl/unx/gtk4/fpicker/SalGtkFilePicker.cxx12
-rw-r--r--vcl/unx/gtk4/fpicker/SalGtkFilePicker.hxx12
-rw-r--r--vcl/unx/gtk4/fpicker/SalGtkFolderPicker.cxx12
-rw-r--r--vcl/unx/gtk4/fpicker/SalGtkFolderPicker.hxx12
-rw-r--r--vcl/unx/gtk4/fpicker/SalGtkPicker.cxx12
-rw-r--r--vcl/unx/gtk4/fpicker/SalGtkPicker.hxx12
-rw-r--r--vcl/unx/gtk4/fpicker/eventnotification.hxx12
-rw-r--r--vcl/unx/gtk4/fpicker/resourceprovider.cxx12
-rw-r--r--vcl/unx/gtk4/gloactiongroup.cxx12
-rw-r--r--vcl/unx/gtk4/glomenu.cxx12
-rw-r--r--vcl/unx/gtk4/gtkcairo.cxx12
-rw-r--r--vcl/unx/gtk4/gtkcairo.hxx12
-rw-r--r--vcl/unx/gtk4/gtkdata.cxx12
-rw-r--r--vcl/unx/gtk4/gtkframe.cxx14
-rw-r--r--vcl/unx/gtk4/gtkinst.cxx21
-rw-r--r--vcl/unx/gtk4/gtkobject.cxx12
-rw-r--r--vcl/unx/gtk4/gtksalmenu.cxx12
-rw-r--r--vcl/unx/gtk4/gtksys.cxx12
-rw-r--r--vcl/unx/gtk4/hudawareness.cxx12
-rw-r--r--vcl/unx/gtk4/notifyinglayout.cxx88
-rw-r--r--vcl/unx/gtk4/notifyinglayout.hxx36
-rw-r--r--vcl/unx/gtk4/salnativewidgets-gtk.cxx12
-rw-r--r--vcl/unx/gtk4/surfacecellrenderer.cxx241
-rw-r--r--vcl/unx/gtk4/surfacecellrenderer.hxx42
-rw-r--r--vcl/unx/gtk4/surfacepaintable.cxx100
-rw-r--r--vcl/unx/gtk4/surfacepaintable.hxx32
-rw-r--r--vcl/unx/gtk4/transferableprovider.cxx118
-rw-r--r--vcl/unx/gtk4/transferableprovider.hxx51
-rw-r--r--vcl/unx/kf5/KFFilePicker.cxx179
-rw-r--r--vcl/unx/kf5/KFFilePicker.hxx63
-rw-r--r--vcl/unx/kf5/KFSalInstance.cxx116
-rw-r--r--vcl/unx/kf5/KFSalInstance.hxx37
-rw-r--r--vcl/unx/kf6/KFFilePicker.cxx12
-rw-r--r--vcl/unx/kf6/KFFilePicker.hxx12
-rw-r--r--vcl/unx/kf6/KFSalInstance.cxx12
-rw-r--r--vcl/unx/kf6/KFSalInstance.hxx12
-rw-r--r--vcl/unx/x11/x11sys.cxx98
-rw-r--r--vcl/unx/x11/xlimits.cxx29
-rw-r--r--vcl/vcl.common.component89
-rw-r--r--vcl/vcl.common.component.android9
-rw-r--r--vcl/vcl.common.component.headless8
-rw-r--r--vcl/vcl.common.component.ios9
-rw-r--r--vcl/vcl.common.component.macosx12
-rw-r--r--vcl/vcl.common.component.unx12
-rw-r--r--vcl/vcl.common.component.windows9
-rw-r--r--vcl/vclplug_win.component46
-rw-r--r--vcl/win/app/fileregistration.cxx182
-rw-r--r--vcl/win/app/saldata.cxx78
-rw-r--r--vcl/win/app/salinfo.cxx177
-rw-r--r--vcl/win/app/salinst.cxx1008
-rw-r--r--vcl/win/app/salshl.cxx112
-rw-r--r--vcl/win/app/saltimer.cxx201
-rw-r--r--vcl/win/dtrans/APNDataObject.cxx320
-rw-r--r--vcl/win/dtrans/APNDataObject.hxx74
-rw-r--r--vcl/win/dtrans/DOTransferable.cxx577
-rw-r--r--vcl/win/dtrans/DOTransferable.hxx98
-rw-r--r--vcl/win/dtrans/DTransHelper.cxx205
-rw-r--r--vcl/win/dtrans/DTransHelper.hxx167
-rw-r--r--vcl/win/dtrans/DataFmtTransl.cxx252
-rw-r--r--vcl/win/dtrans/DataFmtTransl.hxx64
-rw-r--r--vcl/win/dtrans/DtObjFactory.cxx34
-rw-r--r--vcl/win/dtrans/DtObjFactory.hxx33
-rw-r--r--vcl/win/dtrans/Fetc.cxx166
-rw-r--r--vcl/win/dtrans/Fetc.hxx76
-rw-r--r--vcl/win/dtrans/FetcList.cxx340
-rw-r--r--vcl/win/dtrans/FetcList.hxx139
-rw-r--r--vcl/win/dtrans/FmtFilter.cxx435
-rw-r--r--vcl/win/dtrans/FmtFilter.hxx84
-rw-r--r--vcl/win/dtrans/ImplHelper.cxx352
-rw-r--r--vcl/win/dtrans/ImplHelper.hxx74
-rw-r--r--vcl/win/dtrans/MimeAttrib.hxx31
-rw-r--r--vcl/win/dtrans/MtaOleClipb.cxx748
-rw-r--r--vcl/win/dtrans/MtaOleClipb.hxx110
-rw-r--r--vcl/win/dtrans/TxtCnvtHlp.cxx125
-rw-r--r--vcl/win/dtrans/TxtCnvtHlp.hxx41
-rw-r--r--vcl/win/dtrans/WinClip.hxx26
-rw-r--r--vcl/win/dtrans/WinClipboard.cxx383
-rw-r--r--vcl/win/dtrans/WinClipboard.hxx112
-rw-r--r--vcl/win/dtrans/XNotifyingDataObject.cxx149
-rw-r--r--vcl/win/dtrans/XNotifyingDataObject.hxx84
-rw-r--r--vcl/win/dtrans/XTDataObject.cxx751
-rw-r--r--vcl/win/dtrans/XTDataObject.hxx134
-rw-r--r--vcl/win/dtrans/clipboardmanager.cxx198
-rw-r--r--vcl/win/dtrans/clipboardmanager.hxx89
-rw-r--r--vcl/win/dtrans/ftransl.cxx550
-rw-r--r--vcl/win/dtrans/ftransl.hxx59
-rw-r--r--vcl/win/dtrans/generic_clipboard.cxx136
-rw-r--r--vcl/win/dtrans/generic_clipboard.hxx101
-rw-r--r--vcl/win/dtrans/globals.cxx130
-rw-r--r--vcl/win/dtrans/globals.hxx70
-rw-r--r--vcl/win/dtrans/idroptarget.cxx96
-rw-r--r--vcl/win/dtrans/idroptarget.hxx65
-rw-r--r--vcl/win/dtrans/source.cxx373
-rw-r--r--vcl/win/dtrans/sourcecontext.cxx134
-rw-r--r--vcl/win/dtrans/sourcecontext.hxx66
-rw-r--r--vcl/win/dtrans/target.cxx646
-rw-r--r--vcl/win/dtrans/targetdragcontext.cxx39
-rw-r--r--vcl/win/dtrans/targetdragcontext.hxx50
-rw-r--r--vcl/win/dtrans/targetdropcontext.cxx50
-rw-r--r--vcl/win/dtrans/targetdropcontext.hxx52
-rw-r--r--vcl/win/gdi/DWriteTextRenderer.cxx341
-rw-r--r--vcl/win/gdi/dw-extra.h141
-rw-r--r--vcl/win/gdi/gdiimpl.cxx2731
-rw-r--r--vcl/win/gdi/gdiimpl.hxx253
-rw-r--r--vcl/win/gdi/salbmp.cxx910
-rw-r--r--vcl/win/gdi/salfont.cxx1377
-rw-r--r--vcl/win/gdi/salgdi.cxx1139
-rw-r--r--vcl/win/gdi/salgdi2.cxx240
-rw-r--r--vcl/win/gdi/salgdi_gdiplus.cxx104
-rw-r--r--vcl/win/gdi/salnativewidgets-luna.cxx1634
-rw-r--r--vcl/win/gdi/salprn.cxx1604
-rw-r--r--vcl/win/gdi/salvd.cxx223
-rw-r--r--vcl/win/gdi/winlayout.cxx235
-rw-r--r--vcl/win/src/50.bmpbin0 -> 94 bytes
-rw-r--r--vcl/win/src/50.pngbin0 -> 125 bytes
-rw-r--r--vcl/win/src/ase.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/asn.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/asne.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/asns.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/asnswe.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/asnw.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/ass.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/asse.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/assw.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/asw.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/aswe.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/chain.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/chainnot.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/chart.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/copydata.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/copydlnk.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/copyf.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/copyf2.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/copyflnk.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/crook.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/crop.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/darc.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/dbezier.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/dcapt.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/dcirccut.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/dconnect.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/dellipse.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/detectiv.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/dfree.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/dline.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/dpie.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/dpolygon.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/drect.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/dtext.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/fatcross.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/fill.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/hshear.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/linkdata.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/linkf.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/magnify.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/mirror.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/movebw.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/movedata.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/movedlnk.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/movef.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/movef2.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/moveflnk.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/movept.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/nullptr.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/pivotcol.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/pivotdel.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/pivotfld.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/pivotrow.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/rotate.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/salsrc.rc90
-rw-r--r--vcl/win/src/sd.icobin0 -> 3310 bytes
-rw-r--r--vcl/win/src/tblsele.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/tblsels.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/tblselse.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/tblselsw.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/tblselw.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/vshear.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/vtext.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/wshide.curbin0 -> 326 bytes
-rw-r--r--vcl/win/src/wsshow.curbin0 -> 326 bytes
-rw-r--r--vcl/win/window/keynames.cxx225
-rw-r--r--vcl/win/window/salframe.cxx6099
-rw-r--r--vcl/win/window/salmenu.cxx329
-rw-r--r--vcl/win/window/salobj.cxx679
-rw-r--r--vcl/workben/602fuzzer.cxx57
-rw-r--r--vcl/workben/602fuzzer.options2
-rw-r--r--vcl/workben/bmpfuzzer.cxx56
-rw-r--r--vcl/workben/bmpfuzzer.options3
-rw-r--r--vcl/workben/cgmfuzzer.cxx175
-rw-r--r--vcl/workben/cgmfuzzer.options2
-rw-r--r--vcl/workben/cnttype/makefile.mk44
-rw-r--r--vcl/workben/cnttype/testcnttype.cxx186
-rw-r--r--vcl/workben/commonfuzzer.hxx153
-rw-r--r--vcl/workben/dbffuzzer.cxx52
-rw-r--r--vcl/workben/dbffuzzer.options2
-rw-r--r--vcl/workben/diffuzzer.cxx89
-rw-r--r--vcl/workben/diffuzzer.options2
-rw-r--r--vcl/workben/docxfuzzer.cxx34
-rw-r--r--vcl/workben/docxfuzzer.options2
-rw-r--r--vcl/workben/dtrans/makefile.mk44
-rw-r--r--vcl/workben/dtrans/test_dtrans.cxx414
-rw-r--r--vcl/workben/dxffuzzer.cxx70
-rw-r--r--vcl/workben/dxffuzzer.options2
-rw-r--r--vcl/workben/epsfuzzer.cxx70
-rw-r--r--vcl/workben/epsfuzzer.options2
-rw-r--r--vcl/workben/fftester.cxx575
-rw-r--r--vcl/workben/fodpfuzzer.cxx64
-rw-r--r--vcl/workben/fodpfuzzer.options3
-rw-r--r--vcl/workben/fodsfuzzer.cxx34
-rw-r--r--vcl/workben/fodsfuzzer.options3
-rw-r--r--vcl/workben/fodt2pdffuzzer.cxx90
-rw-r--r--vcl/workben/fodt2pdffuzzer.options3
-rw-r--r--vcl/workben/fodtfuzzer.cxx34
-rw-r--r--vcl/workben/fodtfuzzer.options3
-rw-r--r--vcl/workben/giffuzzer.cxx56
-rw-r--r--vcl/workben/giffuzzer.options3
-rw-r--r--vcl/workben/htmlfuzzer.cxx31
-rw-r--r--vcl/workben/htmlfuzzer.options3
-rw-r--r--vcl/workben/hwpfuzzer.cxx57
-rw-r--r--vcl/workben/hwpfuzzer.options2
-rw-r--r--vcl/workben/icontest.cxx215
-rw-r--r--vcl/workben/jpgfuzzer.cxx56
-rw-r--r--vcl/workben/jpgfuzzer.options3
-rw-r--r--vcl/workben/listfonts.cxx544
-rw-r--r--vcl/workben/listglyphs.cxx231
-rw-r--r--vcl/workben/lwpfuzzer.cxx57
-rw-r--r--vcl/workben/lwpfuzzer.options2
-rw-r--r--vcl/workben/metfuzzer.cxx70
-rw-r--r--vcl/workben/metfuzzer.options2
-rw-r--r--vcl/workben/minvcl.cxx76
-rw-r--r--vcl/workben/mmlfuzzer.cxx29
-rw-r--r--vcl/workben/mmlfuzzer.options3
-rw-r--r--vcl/workben/mtfdemo.cxx246
-rw-r--r--vcl/workben/mtpfuzzer.cxx42
-rw-r--r--vcl/workben/mtpfuzzer.options2
-rw-r--r--vcl/workben/olefuzzer.cxx65
-rw-r--r--vcl/workben/olefuzzer.options2
-rw-r--r--vcl/workben/pasteboard.mm133
-rw-r--r--vcl/workben/pcdfuzzer.cxx57
-rw-r--r--vcl/workben/pcdfuzzer.options2
-rw-r--r--vcl/workben/pctfuzzer.cxx57
-rw-r--r--vcl/workben/pctfuzzer.options2
-rw-r--r--vcl/workben/pcxfuzzer.cxx57
-rw-r--r--vcl/workben/pcxfuzzer.options2
-rw-r--r--vcl/workben/pngfuzzer.cxx56
-rw-r--r--vcl/workben/pngfuzzer.options3
-rw-r--r--vcl/workben/ppmfuzzer.cxx57
-rw-r--r--vcl/workben/ppmfuzzer.options2
-rw-r--r--vcl/workben/pptfuzzer.cxx153
-rw-r--r--vcl/workben/pptfuzzer.options2
-rw-r--r--vcl/workben/pptxfuzzer.cxx43
-rw-r--r--vcl/workben/pptxfuzzer.options2
-rw-r--r--vcl/workben/psdfuzzer.cxx57
-rw-r--r--vcl/workben/psdfuzzer.options3
-rw-r--r--vcl/workben/qpwfuzzer.cxx91
-rw-r--r--vcl/workben/qpwfuzzer.options2
-rw-r--r--vcl/workben/rasfuzzer.cxx57
-rw-r--r--vcl/workben/rasfuzzer.options2
-rw-r--r--vcl/workben/rtffuzzer.cxx90
-rw-r--r--vcl/workben/rtffuzzer.options3
-rw-r--r--vcl/workben/scrtffuzzer.cxx86
-rw-r--r--vcl/workben/scrtffuzzer.options2
-rw-r--r--vcl/workben/sftfuzzer.cxx43
-rw-r--r--vcl/workben/sftfuzzer.options3
-rw-r--r--vcl/workben/slkfuzzer.cxx91
-rw-r--r--vcl/workben/slkfuzzer.options2
-rw-r--r--vcl/workben/svdem.cxx105
-rw-r--r--vcl/workben/svgfuzzer.cxx48
-rw-r--r--vcl/workben/svgfuzzer.options3
-rw-r--r--vcl/workben/svmfuzzer.cxx83
-rw-r--r--vcl/workben/svmfuzzer.options2
-rw-r--r--vcl/workben/svpclient.cxx284
-rw-r--r--vcl/workben/svptest.cxx326
-rw-r--r--vcl/workben/tgafuzzer.cxx57
-rw-r--r--vcl/workben/tgafuzzer.options2
-rw-r--r--vcl/workben/tiffuzzer.cxx63
-rw-r--r--vcl/workben/tiffuzzer.options3
-rw-r--r--vcl/workben/vcldemo.cxx2270
-rw-r--r--vcl/workben/webpfuzzer.cxx48
-rw-r--r--vcl/workben/webpfuzzer.options3
-rw-r--r--vcl/workben/win/dnd/atlwindow.cxx238
-rw-r--r--vcl/workben/win/dnd/atlwindow.hxx87
-rw-r--r--vcl/workben/win/dnd/dndTest.cxx177
-rw-r--r--vcl/workben/win/dnd/makefile.mk70
-rw-r--r--vcl/workben/win/dnd/sourcelistener.cxx59
-rw-r--r--vcl/workben/win/dnd/sourcelistener.hxx56
-rw-r--r--vcl/workben/win/dnd/targetlistener.cxx78
-rw-r--r--vcl/workben/win/dnd/targetlistener.hxx64
-rw-r--r--vcl/workben/win/dnd/transferable.cxx105
-rw-r--r--vcl/workben/win/dnd/transferable.hxx88
-rw-r--r--vcl/workben/win/dtrans/XTDo.cxx358
-rw-r--r--vcl/workben/win/dtrans/XTDo.hxx111
-rw-r--r--vcl/workben/win/dtrans/makefile.mk81
-rw-r--r--vcl/workben/win/dtrans/test_wincb.cxx287
-rw-r--r--vcl/workben/win/dtrans/testmarshal.cxx212
-rw-r--r--vcl/workben/wksfuzzer.cxx52
-rw-r--r--vcl/workben/wksfuzzer.options2
-rw-r--r--vcl/workben/wmffuzzer.cxx68
-rw-r--r--vcl/workben/wmffuzzer.options2
-rw-r--r--vcl/workben/ww2fuzzer.cxx130
-rw-r--r--vcl/workben/ww2fuzzer.options2
-rw-r--r--vcl/workben/ww6fuzzer.cxx132
-rw-r--r--vcl/workben/ww6fuzzer.options2
-rw-r--r--vcl/workben/ww8fuzzer.cxx132
-rw-r--r--vcl/workben/ww8fuzzer.options2
-rw-r--r--vcl/workben/xbmfuzzer.cxx56
-rw-r--r--vcl/workben/xbmfuzzer.options2
-rw-r--r--vcl/workben/xlsfuzzer.cxx52
-rw-r--r--vcl/workben/xlsfuzzer.options2
-rw-r--r--vcl/workben/xlsxfuzzer.cxx31
-rw-r--r--vcl/workben/xlsxfuzzer.options2
-rw-r--r--vcl/workben/xpmfuzzer.cxx56
-rw-r--r--vcl/workben/xpmfuzzer.options2
-rw-r--r--vcl/workben/zipfuzzer.cxx48
-rw-r--r--vcl/workben/zipfuzzer.options3
2711 files changed, 664075 insertions, 0 deletions
diff --git a/vcl/AllLangMoTarget_vcl.mk b/vcl/AllLangMoTarget_vcl.mk
new file mode 100644
index 0000000000..784cbe5cff
--- /dev/null
+++ b/vcl/AllLangMoTarget_vcl.mk
@@ -0,0 +1,11 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$(eval $(call gb_AllLangMoTarget_AllLangMoTarget,vcl))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_animation.mk b/vcl/CppunitTest_vcl_animation.mk
new file mode 100644
index 0000000000..e64d908fa8
--- /dev/null
+++ b/vcl/CppunitTest_vcl_animation.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_animation))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_animation,\
+ -I$(SRCDIR)/vcl/inc \
+ $$(INCLUDE) \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_animation, \
+ vcl/qa/cppunit/animationrenderer \
+ vcl/qa/cppunit/animation \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_animation, \
+ test \
+ tl \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_animation, \
+ boost_headers \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_animation))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_animation))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_animation))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_animation,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_animation))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_apitests.mk b/vcl/CppunitTest_vcl_apitests.mk
new file mode 100644
index 0000000000..83c618b061
--- /dev/null
+++ b/vcl/CppunitTest_vcl_apitests.mk
@@ -0,0 +1,64 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_apitests))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_apitests,\
+ $$(INCLUDE) \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_apitests, \
+ vcl/qa/api/XGraphicTest \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_apitests,boost_headers))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_apitests, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_api,vcl_apitests,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_apitests))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_apitests))
+$(eval $(call gb_CppunitTest_use_configuration,vcl_apitests))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_apitests,\
+ comphelper/util/comphelp \
+ configmgr/source/configmgr \
+ filter/source/config/cache/filterconfig1 \
+ filter/source/storagefilterdetect/storagefd \
+ i18npool/util/i18npool \
+ package/source/xstor/xstor \
+ package/util/package2 \
+ sfx2/util/sfx \
+ sot/util/sot \
+ svl/source/fsstor/fsstorage \
+ svtools/util/svt \
+ ucb/source/core/ucb1 \
+ ucb/source/ucp/file/ucpfile1 \
+ ucb/source/ucp/tdoc/ucptdoc1 \
+ unotools/util/utl \
+ uui/util/uui \
+ vcl/vcl.common \
+))
+
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_app_test.mk b/vcl/CppunitTest_vcl_app_test.mk
new file mode 100644
index 0000000000..3749a7f29c
--- /dev/null
+++ b/vcl/CppunitTest_vcl_app_test.mk
@@ -0,0 +1,44 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_app_test))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_app_test, \
+ vcl/qa/cppunit/app/test_IconThemeInfo \
+ vcl/qa/cppunit/app/test_IconThemeScanner \
+ vcl/qa/cppunit/app/test_IconThemeSelector \
+))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_app_test,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_app_test, \
+ sal \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_app_test, \
+ boost_headers \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_app_test))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_app_test))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_app_test))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_app_test,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_app_test))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_backend_test.mk b/vcl/CppunitTest_vcl_backend_test.mk
new file mode 100644
index 0000000000..53338c3490
--- /dev/null
+++ b/vcl/CppunitTest_vcl_backend_test.mk
@@ -0,0 +1,51 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_backend_test))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_backend_test, \
+ vcl/qa/cppunit/BackendTest \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_backend_test, \
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_backend_test))
+$(eval $(call gb_CppunitTest_use_ure,vcl_backend_test))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_backend_test))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_backend_test,\
+ boost_headers\
+ harfbuzz \
+))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_backend_test,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_backend_test,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_backend_test))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_bitmap_render_test.mk b/vcl/CppunitTest_vcl_bitmap_render_test.mk
new file mode 100644
index 0000000000..3af9c9fafd
--- /dev/null
+++ b/vcl/CppunitTest_vcl_bitmap_render_test.mk
@@ -0,0 +1,48 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_bitmap_render_test))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_bitmap_render_test, \
+ vcl/qa/cppunit/bitmaprender/BitmapRenderTest \
+))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_bitmap_render_test,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_bitmap_render_test, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_bitmap_render_test))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_bitmap_render_test))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_bitmap_render_test))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_bitmap_render_test,boost_headers))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_bitmap_render_test,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_bitmap_render_test))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_bitmap_test.mk b/vcl/CppunitTest_vcl_bitmap_test.mk
new file mode 100644
index 0000000000..d9cec0023f
--- /dev/null
+++ b/vcl/CppunitTest_vcl_bitmap_test.mk
@@ -0,0 +1,55 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_bitmap_test))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_bitmap_test, \
+ vcl/qa/cppunit/BitmapTest \
+ vcl/qa/cppunit/BitmapExTest \
+ vcl/qa/cppunit/bitmapcolor \
+ vcl/qa/cppunit/ScanlineToolsTest \
+ vcl/qa/cppunit/BitmapScaleTest \
+ vcl/qa/cppunit/BitmapFilterTest \
+ vcl/qa/cppunit/BmpFilterTest \
+ vcl/qa/cppunit/XpmFilterTest \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_bitmap_test,\
+ boost_headers \
+))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_bitmap_test,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_bitmap_test, \
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+ utl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_bitmap_test))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_bitmap_test))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_bitmap_test))
+
+$(eval $(call gb_CppunitTest_use_rdb,vcl_bitmap_test,services))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_bitmap_test))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_bitmapprocessor_test.mk b/vcl/CppunitTest_vcl_bitmapprocessor_test.mk
new file mode 100644
index 0000000000..6bdffa9d41
--- /dev/null
+++ b/vcl/CppunitTest_vcl_bitmapprocessor_test.mk
@@ -0,0 +1,55 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_bitmapprocessor_test))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_bitmapprocessor_test, \
+ vcl/qa/cppunit/BitmapProcessorTest \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_bitmapprocessor_test,\
+ boost_headers \
+))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_bitmapprocessor_test,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_bitmapprocessor_test, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+ utl \
+))
+
+$(eval $(call gb_CppunitTest_use_api,vcl_bitmapprocessor_test,\
+ udkapi \
+ offapi \
+))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_bitmapprocessor_test))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_bitmapprocessor_test))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_bitmapprocessor_test,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+ unotools/util/utl \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_bitmapprocessor_test))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_blocklistparser_test.mk b/vcl/CppunitTest_vcl_blocklistparser_test.mk
new file mode 100644
index 0000000000..b585382b44
--- /dev/null
+++ b/vcl/CppunitTest_vcl_blocklistparser_test.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_blocklistparser_test))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_blocklistparser_test,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_blocklistparser_test, \
+ vcl/qa/cppunit/blocklistparsertest \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_blocklistparser_test,\
+ boost_headers \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_blocklistparser_test, \
+ sal \
+ test \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_blocklistparser_test))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_blocklistparser_test))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_blocklistparser_test))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_blocklistparser_test,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_blocklistparser_test))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_cjk.mk b/vcl/CppunitTest_vcl_cjk.mk
new file mode 100644
index 0000000000..ea2d70a35c
--- /dev/null
+++ b/vcl/CppunitTest_vcl_cjk.mk
@@ -0,0 +1,69 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_cjk))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_cjk,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_cjk, \
+ vcl/qa/cppunit/cjktext \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_cjk,\
+ boost_headers \
+ harfbuzz \
+))
+
+ifeq ($(SYSTEM_ICU),TRUE)
+$(eval $(call gb_CppunitTest_use_externals,vcl_cjk,\
+ icuuc \
+))
+else
+$(eval $(call gb_CppunitTest_use_externals,vcl_cjk,\
+ icu_headers \
+))
+endif
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_cjk, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ i18nlangtag \
+ sal \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_cjk))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_cjk))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_cjk))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_cjk,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_cjk))
+
+$(eval $(call gb_CppunitTest_use_more_fonts,vcl_cjk))
+
+# we don't have any bundled cjk fonts, so allow use of
+# system fonts for the cjk tests, tests have to survive
+# unavailable fonts
+$(eval $(call gb_CppunitTest_set_non_application_font_use,vcl_cjk,allow))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_dialogs_test.mk b/vcl/CppunitTest_vcl_dialogs_test.mk
new file mode 100644
index 0000000000..a602b42707
--- /dev/null
+++ b/vcl/CppunitTest_vcl_dialogs_test.mk
@@ -0,0 +1,68 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#*************************************************************************
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+#*************************************************************************
+
+$(eval $(call gb_CppunitTest_CppunitScreenShot,vcl_dialogs_test))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_dialogs_test, \
+ vcl/qa/unit/vcl-dialogs-test \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_dialogs_test))
+
+$(eval $(call gb_CppunitTest_set_include,desktop_dialogs_test,\
+ -I$(SRCDIR)/vcl/inc \
+ $$(INCLUDE) \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_dialogs_test, \
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ drawinglayer \
+ editeng \
+ i18nlangtag \
+ i18nutil \
+ msfilter \
+ oox \
+ sal \
+ salhelper \
+ sax \
+ sfx \
+ sot \
+ svl \
+ svt \
+ test \
+ tl \
+ tk \
+ ucbhelper \
+ unotest \
+ utl \
+ vcl \
+ xo \
+))
+
+$(eval $(call gb_CppunitTest_use_external,vcl_dialogs_test,boost_headers))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_dialogs_test))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_dialogs_test))
+$(eval $(call gb_CppunitTest_use_vcl_non_headless_with_windows,vcl_dialogs_test))
+
+$(eval $(call gb_CppunitTest_use_rdb,vcl_dialogs_test,services))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_dialogs_test))
+
+$(eval $(call gb_CppunitTest_use_uiconfigs,vcl_dialogs_test,\
+ vcl \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_drawmode.mk b/vcl/CppunitTest_vcl_drawmode.mk
new file mode 100644
index 0000000000..2a107c214a
--- /dev/null
+++ b/vcl/CppunitTest_vcl_drawmode.mk
@@ -0,0 +1,47 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_drawmode))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_drawmode,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_drawmode, \
+ vcl/qa/cppunit/drawmode \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_drawmode, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ tk \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_drawmode))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_drawmode))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_drawmode))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_drawmode))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_drawmode,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_errorhandler.mk b/vcl/CppunitTest_vcl_errorhandler.mk
new file mode 100644
index 0000000000..00857fff01
--- /dev/null
+++ b/vcl/CppunitTest_vcl_errorhandler.mk
@@ -0,0 +1,49 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_errorhandler))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_errorhandler,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_errorhandler, \
+ vcl/qa/cppunit/errorhandler \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_errorhandler,boost_headers))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_errorhandler, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ tk \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_errorhandler))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_errorhandler))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_errorhandler))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_errorhandler,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_errorhandler))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_filter_igif.mk b/vcl/CppunitTest_vcl_filter_igif.mk
new file mode 100644
index 0000000000..c6b6759899
--- /dev/null
+++ b/vcl/CppunitTest_vcl_filter_igif.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#*************************************************************************
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+#*************************************************************************
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_filter_igif))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_filter_igif,\
+ boost_headers \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_filter_igif, \
+ vcl/qa/cppunit/filter/igif/igif \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_filter_igif, \
+ comphelper \
+ cppu \
+ sal \
+ test \
+ tl \
+ unotest \
+ utl \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_filter_igif))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_filter_igif))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_filter_igif))
+
+$(eval $(call gb_CppunitTest_use_rdb,vcl_filter_igif,services))
+
+$(eval $(call gb_CppunitTest_use_custom_headers,vcl_filter_igif,\
+ officecfg/registry \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_filter_igif))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_filter_ipdf.mk b/vcl/CppunitTest_vcl_filter_ipdf.mk
new file mode 100644
index 0000000000..d5daba87e3
--- /dev/null
+++ b/vcl/CppunitTest_vcl_filter_ipdf.mk
@@ -0,0 +1,56 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#*************************************************************************
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+#*************************************************************************
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_filter_ipdf))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_filter_ipdf,\
+ boost_headers \
+ pdfium \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_filter_ipdf, \
+ vcl/qa/cppunit/filter/ipdf/ipdf \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_filter_ipdf, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ sfx \
+ subsequenttest \
+ svx \
+ test \
+ tl \
+ unotest \
+ utl \
+ tl \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_filter_ipdf))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_filter_ipdf))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_filter_ipdf))
+
+$(eval $(call gb_CppunitTest_use_rdb,vcl_filter_ipdf,services))
+
+$(eval $(call gb_CppunitTest_use_custom_headers,vcl_filter_ipdf,\
+ officecfg/registry \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_filter_ipdf))
+
+ifeq ($(ENABLE_POPPLER),TRUE)
+$(eval $(call gb_CppunitTest_use_executable,vcl_filter_ipdf,xpdfimport))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_filters_test.mk b/vcl/CppunitTest_vcl_filters_test.mk
new file mode 100644
index 0000000000..f9f8e40cca
--- /dev/null
+++ b/vcl/CppunitTest_vcl_filters_test.mk
@@ -0,0 +1,73 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_filters_test))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_filters_test, \
+ vcl/qa/cppunit/graphicfilter/filters-dxf-test \
+ vcl/qa/cppunit/graphicfilter/filters-eps-test \
+ vcl/qa/cppunit/graphicfilter/filters-met-test \
+ vcl/qa/cppunit/graphicfilter/filters-pcd-test \
+ vcl/qa/cppunit/graphicfilter/filters-pcx-test \
+ vcl/qa/cppunit/graphicfilter/filters-pict-test \
+ vcl/qa/cppunit/graphicfilter/filters-ppm-test \
+ vcl/qa/cppunit/graphicfilter/filters-psd-test \
+ vcl/qa/cppunit/graphicfilter/filters-ras-test \
+ vcl/qa/cppunit/graphicfilter/filters-test \
+ vcl/qa/cppunit/graphicfilter/filters-tiff-test \
+ vcl/qa/cppunit/graphicfilter/filters-tga-test \
+ vcl/qa/cppunit/graphicfilter/filters-webp-test \
+))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_filters_test,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+ifeq ($(DISABLE_CVE_TESTS),TRUE)
+$(eval $(call gb_CppunitTest_add_defs,vcl_filters_test,\
+ -DDISABLE_CVE_TESTS \
+))
+endif
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_filters_test,\
+ boost_headers \
+ libxml2 \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_filters_test, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+ emfio \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_filters_test))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_filters_test))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_filters_test))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_filters_test,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+ ucb/source/ucp/file/ucpfile1 \
+ uui/util/uui \
+ emfio/emfio \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_filters_test))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_font.mk b/vcl/CppunitTest_vcl_font.mk
new file mode 100644
index 0000000000..813ab1c9c0
--- /dev/null
+++ b/vcl/CppunitTest_vcl_font.mk
@@ -0,0 +1,64 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_font))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_font,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_font, \
+ vcl/qa/cppunit/font \
+ vcl/qa/cppunit/physicalfontface \
+ vcl/qa/cppunit/physicalfontfacecollection \
+ vcl/qa/cppunit/physicalfontfamily \
+ vcl/qa/cppunit/physicalfontcollection \
+ vcl/qa/cppunit/logicalfontinstance \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_font,\
+ boost_headers \
+ harfbuzz \
+ graphite \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_font, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ salhelper \
+ svt \
+ test \
+ tl \
+ tk \
+ unotest \
+ utl \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_font))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_font))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_font))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_font,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_font))
+
+$(eval $(call gb_CppunitTest_use_more_fonts,vcl_font))
+
+$(eval $(call gb_CppunitTest_set_non_application_font_use,vcl_font,abort))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_fontcharmap.mk b/vcl/CppunitTest_vcl_fontcharmap.mk
new file mode 100644
index 0000000000..0c596bf8d2
--- /dev/null
+++ b/vcl/CppunitTest_vcl_fontcharmap.mk
@@ -0,0 +1,49 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_fontcharmap))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_fontcharmap,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_fontcharmap, \
+ vcl/qa/cppunit/fontcharmap \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_fontcharmap,boost_headers))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_fontcharmap, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ tk \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_fontcharmap))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_fontcharmap))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_fontcharmap))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_fontcharmap,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_fontcharmap))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_fontfeature.mk b/vcl/CppunitTest_vcl_fontfeature.mk
new file mode 100644
index 0000000000..07a1b319e2
--- /dev/null
+++ b/vcl/CppunitTest_vcl_fontfeature.mk
@@ -0,0 +1,51 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_fontfeature))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_fontfeature,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_fontfeature, \
+ vcl/qa/cppunit/FontFeatureTest \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_fontfeature,boost_headers))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_fontfeature, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ tk \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_fontfeature))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_fontfeature))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_fontfeature))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_fontfeature,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_fontfeature))
+
+$(eval $(call gb_CppunitTest_use_more_fonts,vcl_fontfeature))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_fontmetric.mk b/vcl/CppunitTest_vcl_fontmetric.mk
new file mode 100644
index 0000000000..5496a75c14
--- /dev/null
+++ b/vcl/CppunitTest_vcl_fontmetric.mk
@@ -0,0 +1,55 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_fontmetric))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_fontmetric,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_fontmetric, \
+ vcl/qa/cppunit/fontmetric \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_fontmetric,boost_headers))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_fontmetric, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ tk \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_fontmetric))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_fontmetric))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_fontmetric))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_fontmetric,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_fontmetric))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_fontmetric,\
+ harfbuzz \
+))
+
+$(eval $(call gb_CppunitTest_use_more_fonts,vcl_fontmetric))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_gen.mk b/vcl/CppunitTest_vcl_gen.mk
new file mode 100644
index 0000000000..c65141255b
--- /dev/null
+++ b/vcl/CppunitTest_vcl_gen.mk
@@ -0,0 +1,44 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#*************************************************************************
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+#*************************************************************************
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_gen))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_gen, \
+ vcl/qa/cppunit/gen/gen \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_gen, \
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ sfx \
+ subsequenttest \
+ test \
+ tl \
+ unotest \
+ utl \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_external,vcl_gen,boost_headers))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_gen))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_gen))
+$(eval $(call gb_CppunitTest_use_vcl_non_headless,vcl_gen))
+
+$(eval $(call gb_CppunitTest_use_rdb,vcl_gen,services))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_gen))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_gradient.mk b/vcl/CppunitTest_vcl_gradient.mk
new file mode 100644
index 0000000000..64c7e1e2b9
--- /dev/null
+++ b/vcl/CppunitTest_vcl_gradient.mk
@@ -0,0 +1,50 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_gradient))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_gradient,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+ -I$(SRCDIR)/vcl/source/window \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_gradient, \
+ vcl/qa/cppunit/gradient \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_gradient,boost_headers))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_gradient, \
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_gradient))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_gradient))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_gradient))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_gradient,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_gradient))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_graphic_test.mk b/vcl/CppunitTest_vcl_graphic_test.mk
new file mode 100644
index 0000000000..6089512f5b
--- /dev/null
+++ b/vcl/CppunitTest_vcl_graphic_test.mk
@@ -0,0 +1,56 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_graphic_test))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_graphic_test, \
+ vcl/qa/cppunit/GraphicTest \
+ vcl/qa/cppunit/GraphicDescriptorTest \
+ vcl/qa/cppunit/GraphicFormatDetectorTest \
+ vcl/qa/cppunit/GraphicNativeMetadataTest \
+ $(if $(filter PDFIUM,$(BUILD_TYPE)),vcl/qa/cppunit/VectorGraphicSearchTest) \
+ vcl/qa/cppunit/BinaryDataContainerTest \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_graphic_test, \
+ boost_headers \
+ $(if $(filter PDFIUM,$(BUILD_TYPE)),pdfium) \
+))
+ifeq ($(TLS),NSS)
+$(eval $(call gb_CppunitTest_use_externals,vcl_graphic_test,\
+ plc4 \
+ nss3 \
+))
+endif
+
+$(eval $(call gb_CppunitTest_set_include,vcl_graphic_test,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_graphic_test, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+ utl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_graphic_test))
+$(eval $(call gb_CppunitTest_use_ure,vcl_graphic_test))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_graphic_test))
+$(eval $(call gb_CppunitTest_use_rdb,vcl_graphic_test,services))
+$(eval $(call gb_CppunitTest_use_configuration,vcl_graphic_test))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_gtk3_a11y.mk b/vcl/CppunitTest_vcl_gtk3_a11y.mk
new file mode 100644
index 0000000000..476f803344
--- /dev/null
+++ b/vcl/CppunitTest_vcl_gtk3_a11y.mk
@@ -0,0 +1,63 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+# hack plugging into the CppunitTest machinery yet using a xvfb-wrapper GTK3 run
+$(call gb_CppunitTest_get_target,vcl_gtk3_a11y) : gb_TEST_ENV_VARS += SAL_USE_VCLPLUGIN=gtk3
+# force running with the X11 Gdk backend also when running on Wayland
+$(call gb_CppunitTest_get_target,vcl_gtk3_a11y) : gb_TEST_ENV_VARS += GDK_BACKEND=x11
+ifeq (,$(VCL_GTK3_TESTS_NO_XVFB))
+$(call gb_CppunitTest_get_target,vcl_gtk3_a11y) : \
+ ICECREAM_RUN += $(XVFB_RUN) --auto-servernum $(DBUS_LAUNCH) --exit-with-session
+endif
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_gtk3_a11y))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_gtk3_a11y, \
+ vcl/qa/cppunit/a11y/atspi2/atspiwrapper \
+ vcl/qa/cppunit/a11y/atspi2/atspi2 \
+ vcl/qa/cppunit/a11y/atspi2/atspi2text \
+))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_gtk3_a11y,\
+ $$(INCLUDE) \
+ $$(ATSPI2_CFLAGS) \
+))
+
+$(eval $(call gb_CppunitTest_add_libs,vcl_gtk3_a11y,\
+ $$(ATSPI2_LIBS) \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_gtk3_a11y, \
+ sal \
+ cppu \
+ subsequenttest \
+ test \
+ i18nlangtag \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_gtk3_a11y,\
+ boost_headers \
+))
+
+$(eval $(call gb_CppunitTest_use_api,vcl_gtk3_a11y,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_gtk3_a11y))
+$(eval $(call gb_CppunitTest_use_rdb,vcl_gtk3_a11y,services))
+$(eval $(call gb_CppunitTest_use_ure,vcl_gtk3_a11y))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_gtk3_a11y))
+
+$(eval $(call gb_CppunitTest_use_instdir_configuration,vcl_gtk3_a11y))
+$(eval $(call gb_CppunitTest_use_common_configuration,vcl_gtk3_a11y))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_jpeg_read_write_test.mk b/vcl/CppunitTest_vcl_jpeg_read_write_test.mk
new file mode 100644
index 0000000000..88385872b3
--- /dev/null
+++ b/vcl/CppunitTest_vcl_jpeg_read_write_test.mk
@@ -0,0 +1,51 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_jpeg_read_write_test))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_jpeg_read_write_test, \
+ vcl/qa/cppunit/jpeg/JpegReaderTest \
+ vcl/qa/cppunit/jpeg/JpegWriterTest \
+))
+
+$(eval $(call gb_CppunitTest_use_external,vcl_jpeg_read_write_test,boost_headers))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_jpeg_read_write_test,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_jpeg_read_write_test, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_jpeg_read_write_test))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_jpeg_read_write_test))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_jpeg_read_write_test))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_jpeg_read_write_test,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+ ucb/source/ucp/file/ucpfile1 \
+ uui/util/uui \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_jpeg_read_write_test))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_lifecycle.mk b/vcl/CppunitTest_vcl_lifecycle.mk
new file mode 100644
index 0000000000..4e88950138
--- /dev/null
+++ b/vcl/CppunitTest_vcl_lifecycle.mk
@@ -0,0 +1,60 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_lifecycle))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_lifecycle,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_lifecycle, \
+ vcl/qa/cppunit/lifecycle \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_lifecycle,boost_headers))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_lifecycle, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tk \
+ tl \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_add_defs,vcl_lifecycle,\
+ -DVCL_INTERNALS \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_lifecycle))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_lifecycle))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_lifecycle))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_lifecycle,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+ ucb/source/ucp/file/ucpfile1 \
+ framework/util/fwk \
+ sfx2/util/sfx \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_lifecycle))
+
+$(eval $(call gb_CppunitTest_use_uiconfigs,vcl_lifecycle, \
+ vcl \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_mnemonic.mk b/vcl/CppunitTest_vcl_mnemonic.mk
new file mode 100644
index 0000000000..15dc99e8f0
--- /dev/null
+++ b/vcl/CppunitTest_vcl_mnemonic.mk
@@ -0,0 +1,49 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_mnemonic))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_mnemonic,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_mnemonic, \
+ vcl/qa/cppunit/mnemonic \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_mnemonic,boost_headers))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_mnemonic, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ tk \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_mnemonic))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_mnemonic))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_mnemonic))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_mnemonic,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_mnemonic))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_outdev.mk b/vcl/CppunitTest_vcl_outdev.mk
new file mode 100644
index 0000000000..65fd6b5fa1
--- /dev/null
+++ b/vcl/CppunitTest_vcl_outdev.mk
@@ -0,0 +1,50 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_outdev))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_outdev,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+ -I$(SRCDIR)/vcl/source/window \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_outdev, \
+ vcl/qa/cppunit/outdev \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_outdev,boost_headers))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_outdev, \
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_outdev))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_outdev))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_outdev))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_outdev,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_outdev))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_pdfexport.mk b/vcl/CppunitTest_vcl_pdfexport.mk
new file mode 100644
index 0000000000..33716f5dad
--- /dev/null
+++ b/vcl/CppunitTest_vcl_pdfexport.mk
@@ -0,0 +1,52 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_pdfexport))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_pdfexport, \
+ vcl/qa/cppunit/pdfexport/pdfexport \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_pdfexport))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_pdfexport, \
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ subsequenttest \
+ test \
+ unotest \
+ utl \
+ tl \
+ vcl \
+ xmlsecurity \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_pdfexport, \
+ boost_headers \
+ $(if $(filter PDFIUM,$(BUILD_TYPE)),pdfium) \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_pdfexport))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_pdfexport))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_pdfexport))
+
+$(eval $(call gb_CppunitTest_use_rdb,vcl_pdfexport,services))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_pdfexport))
+
+# assert if font/glyph fallback occurs
+$(eval $(call gb_CppunitTest_set_non_application_font_use,vcl_pdfexport,abort))
+
+$(eval $(call gb_CppunitTest_use_more_fonts,vcl_pdfexport))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_pdfexport2.mk b/vcl/CppunitTest_vcl_pdfexport2.mk
new file mode 100644
index 0000000000..5574f515a7
--- /dev/null
+++ b/vcl/CppunitTest_vcl_pdfexport2.mk
@@ -0,0 +1,52 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_pdfexport2))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_pdfexport2, \
+ vcl/qa/cppunit/pdfexport/pdfexport2 \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_pdfexport2))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_pdfexport2, \
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ subsequenttest \
+ test \
+ unotest \
+ utl \
+ tl \
+ vcl \
+ xmlsecurity \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_pdfexport2, \
+ boost_headers \
+ $(if $(filter PDFIUM,$(BUILD_TYPE)),pdfium) \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_pdfexport2))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_pdfexport2))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_pdfexport2))
+
+$(eval $(call gb_CppunitTest_use_rdb,vcl_pdfexport2,services))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_pdfexport2))
+
+# assert if font/glyph fallback occurs
+$(eval $(call gb_CppunitTest_set_non_application_font_use,vcl_pdfexport2,abort))
+
+$(eval $(call gb_CppunitTest_use_more_fonts,vcl_pdfexport2))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_pdfium_library_test.mk b/vcl/CppunitTest_vcl_pdfium_library_test.mk
new file mode 100644
index 0000000000..37acb11255
--- /dev/null
+++ b/vcl/CppunitTest_vcl_pdfium_library_test.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_pdfium_library_test))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_pdfium_library_test, \
+ vcl/qa/cppunit/PDFiumLibraryTest \
+ vcl/qa/cppunit/PDFDocumentTest \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_pdfium_library_test))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_pdfium_library_test, \
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ test \
+ unotest \
+ utl \
+ tl \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_pdfium_library_test, \
+ boost_headers \
+ $(if $(filter PDFIUM,$(BUILD_TYPE)),pdfium) \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_pdfium_library_test))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_pdfium_library_test))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_pdfium_library_test))
+
+$(eval $(call gb_CppunitTest_use_rdb,vcl_pdfium_library_test,services))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_pdfium_library_test))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_png_test.mk b/vcl/CppunitTest_vcl_png_test.mk
new file mode 100644
index 0000000000..fc513eaecf
--- /dev/null
+++ b/vcl/CppunitTest_vcl_png_test.mk
@@ -0,0 +1,54 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_png_test))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_png_test, \
+ vcl/qa/cppunit/png/PngFilterTest \
+))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_png_test,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_use_external,vcl_png_test,boost_headers))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_png_test, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+ utl \
+ $(gb_UWINAPI) \
+))
+
+$(eval $(call gb_CppunitTest_use_api,vcl_png_test,\
+ udkapi \
+ offapi \
+))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_png_test))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_png_test))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_png_test,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+ unotools/util/utl \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_png_test))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_skia.mk b/vcl/CppunitTest_vcl_skia.mk
new file mode 100644
index 0000000000..094bba75fa
--- /dev/null
+++ b/vcl/CppunitTest_vcl_skia.mk
@@ -0,0 +1,51 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#*************************************************************************
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+#*************************************************************************
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_skia))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_skia, \
+ vcl/qa/cppunit/skia/skia \
+))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_skia,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_skia, \
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ sfx \
+ subsequenttest \
+ test \
+ tl \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_skia, \
+ boost_headers \
+ $(if $(filter SKIA,$(BUILD_TYPE)),skia) \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_skia))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_skia))
+$(eval $(call gb_CppunitTest_use_vcl_non_headless,vcl_skia))
+
+$(eval $(call gb_CppunitTest_use_rdb,vcl_skia,services))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_skia))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_svm_test.mk b/vcl/CppunitTest_vcl_svm_test.mk
new file mode 100644
index 0000000000..f689b6384b
--- /dev/null
+++ b/vcl/CppunitTest_vcl_svm_test.mk
@@ -0,0 +1,57 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_svm_test))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_svm_test, \
+ vcl/qa/cppunit/svm/svmtest \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_svm_test,\
+ boost_headers \
+ libxml2 \
+))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_svm_test,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_svm_test, \
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ salhelper \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+ utl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_svm_test))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_svm_test))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_svm_test))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_svm_test,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+ unotools/util/utl \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_svm_test))
+
+$(eval $(call gb_CppunitTest_use_more_fonts,vcl_svm_test))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_text.mk b/vcl/CppunitTest_vcl_text.mk
new file mode 100644
index 0000000000..4ffa3b1990
--- /dev/null
+++ b/vcl/CppunitTest_vcl_text.mk
@@ -0,0 +1,66 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_text))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_text,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_text, \
+ vcl/qa/cppunit/canvasbitmaptest \
+ vcl/qa/cppunit/complextext \
+ vcl/qa/cppunit/text \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_text,\
+ boost_headers \
+ harfbuzz \
+))
+
+ifeq ($(SYSTEM_ICU),TRUE)
+$(eval $(call gb_CppunitTest_use_externals,vcl_text,\
+ icuuc \
+))
+else
+$(eval $(call gb_CppunitTest_use_externals,vcl_text,\
+ icu_headers \
+))
+endif
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_text, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ i18nlangtag \
+ sal \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_text))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_text))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_text))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_text,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_text))
+
+$(eval $(call gb_CppunitTest_use_more_fonts,vcl_text))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_timer.mk b/vcl/CppunitTest_vcl_timer.mk
new file mode 100644
index 0000000000..a89e4e070e
--- /dev/null
+++ b/vcl/CppunitTest_vcl_timer.mk
@@ -0,0 +1,48 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_timer))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_timer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_timer, \
+ vcl/qa/cppunit/timer \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_timer,boost_headers))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_timer, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ salhelper \
+ test \
+ tl \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_timer))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_timer))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_timer))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_timer,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_timer))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_type_serializer_test.mk b/vcl/CppunitTest_vcl_type_serializer_test.mk
new file mode 100644
index 0000000000..ac668da41b
--- /dev/null
+++ b/vcl/CppunitTest_vcl_type_serializer_test.mk
@@ -0,0 +1,47 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_type_serializer_test))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_type_serializer_test, \
+ vcl/qa/cppunit/TypeSerializerTest \
+))
+
+$(eval $(call gb_CppunitTest_use_external,vcl_type_serializer_test,boost_headers))
+ifeq ($(TLS),NSS)
+$(eval $(call gb_CppunitTest_use_externals,vcl_type_serializer_test,\
+ plc4 \
+ nss3 \
+))
+endif
+
+$(eval $(call gb_CppunitTest_set_include,vcl_type_serializer_test,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_type_serializer_test, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_type_serializer_test))
+$(eval $(call gb_CppunitTest_use_rdb,vcl_type_serializer_test,services))
+$(eval $(call gb_CppunitTest_use_ure,vcl_type_serializer_test))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_type_serializer_test))
+$(eval $(call gb_CppunitTest_use_configuration,vcl_type_serializer_test))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CppunitTest_vcl_widget_definition_reader_test.mk b/vcl/CppunitTest_vcl_widget_definition_reader_test.mk
new file mode 100644
index 0000000000..a7d77312cd
--- /dev/null
+++ b/vcl/CppunitTest_vcl_widget_definition_reader_test.mk
@@ -0,0 +1,52 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,vcl_widget_definition_reader_test))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,vcl_widget_definition_reader_test, \
+ vcl/qa/cppunit/widgetdraw/WidgetDefinitionReaderTest \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,vcl_widget_definition_reader_test,\
+ boost_headers \
+))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_widget_definition_reader_test,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,vcl_widget_definition_reader_test, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ svt \
+ test \
+ tl \
+ unotest \
+ vcl \
+ utl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,vcl_widget_definition_reader_test))
+
+$(eval $(call gb_CppunitTest_use_ure,vcl_widget_definition_reader_test))
+$(eval $(call gb_CppunitTest_use_vcl,vcl_widget_definition_reader_test))
+
+$(eval $(call gb_CppunitTest_use_components,vcl_widget_definition_reader_test,\
+ configmgr/source/configmgr \
+ i18npool/util/i18npool \
+ ucb/source/core/ucb1 \
+ unotools/util/utl \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,vcl_widget_definition_reader_test))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CustomTarget_gtk3_kde5_moc.mk b/vcl/CustomTarget_gtk3_kde5_moc.mk
new file mode 100644
index 0000000000..06f28de31c
--- /dev/null
+++ b/vcl/CustomTarget_gtk3_kde5_moc.mk
@@ -0,0 +1,24 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CustomTarget_CustomTarget,vcl/unx/gtk3_kde5))
+
+$(call gb_CustomTarget_get_target,vcl/unx/gtk3_kde5) : \
+ $(call gb_CustomTarget_get_workdir,vcl/unx/gtk3_kde5)/kde5_filepicker.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/unx/gtk3_kde5)/kde5_filepicker_ipc.moc \
+
+$(call gb_CustomTarget_get_workdir,vcl/unx/gtk3_kde5)/%.moc : \
+ $(SRCDIR)/vcl/unx/gtk3_kde5/%.hxx \
+ | $(call gb_CustomTarget_get_workdir,vcl/unx/gtk3_kde5)/.dir
+ $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),MOC,1)
+ $(call gb_Trace_StartRange,$(subst $(WORKDIR)/,,$@),MOC)
+ $(MOC5) $< -o $@
+ $(call gb_Trace_EndRange,$(subst $(WORKDIR)/,,$@),MOC)
+
+# vim: set noet sw=4:
diff --git a/vcl/CustomTarget_kf5_moc.mk b/vcl/CustomTarget_kf5_moc.mk
new file mode 100644
index 0000000000..27e783fad1
--- /dev/null
+++ b/vcl/CustomTarget_kf5_moc.mk
@@ -0,0 +1,23 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CustomTarget_CustomTarget,vcl/unx/kf5))
+
+$(call gb_CustomTarget_get_target,vcl/unx/kf5) : \
+ $(call gb_CustomTarget_get_workdir,vcl/unx/kf5)/KFFilePicker.moc
+
+$(call gb_CustomTarget_get_workdir,vcl/unx/kf5)/%.moc : \
+ $(SRCDIR)/vcl/unx/kf5/%.hxx \
+ | $(call gb_CustomTarget_get_workdir,vcl/unx/kf5)/.dir
+ $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),MOC,1)
+ $(call gb_Trace_StartRange,$(subst $(WORKDIR)/,,$@),MOC)
+ $(MOC5) $< -o $@
+ $(call gb_Trace_EndRange,$(subst $(WORKDIR)/,,$@),MOC)
+
+# vim: set noet sw=4:
diff --git a/vcl/CustomTarget_kf6_moc.mk b/vcl/CustomTarget_kf6_moc.mk
new file mode 100644
index 0000000000..ba0f514cd8
--- /dev/null
+++ b/vcl/CustomTarget_kf6_moc.mk
@@ -0,0 +1,29 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CustomTarget_CustomTarget,vcl/unx/kf6))
+
+$(call gb_CustomTarget_get_target,vcl/unx/kf6) : \
+ $(call gb_CustomTarget_get_workdir,vcl/unx/kf6)/KFFilePicker.moc
+
+# For now, the headers in vcl/unx/kf6 just '#include' the ones
+# in 'vcl/unx/kf5'.
+# Since moc does not process classes from the included headers,
+# it needs to be run on the headers in the kf5 dir.
+# That will have to be adapted in case the kf6 VCL plugin
+# uses "own" headers
+$(call gb_CustomTarget_get_workdir,vcl/unx/kf6)/%.moc : \
+ $(SRCDIR)/vcl/unx/kf5/%.hxx \
+ | $(call gb_CustomTarget_get_workdir,vcl/unx/kf6)/.dir
+ $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),MOC,1)
+ $(call gb_Trace_StartRange,$(subst $(WORKDIR)/,,$@),MOC)
+ $(MOC6) $< -o $@
+ $(call gb_Trace_EndRange,$(subst $(WORKDIR)/,,$@),MOC)
+
+# vim: set noet sw=4:
diff --git a/vcl/CustomTarget_nativecalc.mk b/vcl/CustomTarget_nativecalc.mk
new file mode 100644
index 0000000000..1c6804082a
--- /dev/null
+++ b/vcl/CustomTarget_nativecalc.mk
@@ -0,0 +1,18 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CustomTarget_CustomTarget,vcl/workben))
+
+fuzzer_PYTHONCOMMAND := $(call gb_ExternalExecutable_get_command,python)
+
+fuzzer_Native_cxx=$(call gb_CustomTarget_get_workdir,vcl/workben)/native-calc.cxx
+
+$(fuzzer_Native_cxx): $(SRCDIR)/solenv/bin/native-code.py | $(call gb_CustomTarget_get_workdir,vcl/workben)/.dir
+ $(call gb_Helper_abbreviate_dirs, $(fuzzer_PYTHONCOMMAND) $(SRCDIR)/solenv/bin/native-code.py -g core -g calc) > $@
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CustomTarget_nativecore.mk b/vcl/CustomTarget_nativecore.mk
new file mode 100644
index 0000000000..964e7af1a8
--- /dev/null
+++ b/vcl/CustomTarget_nativecore.mk
@@ -0,0 +1,18 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CustomTarget_CustomTarget,vcl/workben))
+
+fuzzer_PYTHONCOMMAND := $(call gb_ExternalExecutable_get_command,python)
+
+fuzzer_Native_cxx=$(call gb_CustomTarget_get_workdir,vcl/workben)/native-core.cxx
+
+$(fuzzer_Native_cxx): $(SRCDIR)/solenv/bin/native-code.py | $(call gb_CustomTarget_get_workdir,vcl/workben)/.dir
+ $(call gb_Helper_abbreviate_dirs, $(fuzzer_PYTHONCOMMAND) $(SRCDIR)/solenv/bin/native-code.py -g core) > $@
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CustomTarget_nativedraw.mk b/vcl/CustomTarget_nativedraw.mk
new file mode 100644
index 0000000000..513bf564b9
--- /dev/null
+++ b/vcl/CustomTarget_nativedraw.mk
@@ -0,0 +1,18 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CustomTarget_CustomTarget,vcl/workben))
+
+fuzzer_PYTHONCOMMAND := $(call gb_ExternalExecutable_get_command,python)
+
+fuzzer_Native_cxx=$(call gb_CustomTarget_get_workdir,vcl/workben)/native-draw.cxx
+
+$(fuzzer_Native_cxx): $(SRCDIR)/solenv/bin/native-code.py | $(call gb_CustomTarget_get_workdir,vcl/workben)/.dir
+ $(call gb_Helper_abbreviate_dirs, $(fuzzer_PYTHONCOMMAND) $(SRCDIR)/solenv/bin/native-code.py -g core -g draw) > $@
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CustomTarget_nativemath.mk b/vcl/CustomTarget_nativemath.mk
new file mode 100644
index 0000000000..7465378c18
--- /dev/null
+++ b/vcl/CustomTarget_nativemath.mk
@@ -0,0 +1,18 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CustomTarget_CustomTarget,vcl/workben))
+
+fuzzer_PYTHONCOMMAND := $(call gb_ExternalExecutable_get_command,python)
+
+fuzzer_Native_cxx=$(call gb_CustomTarget_get_workdir,vcl/workben)/native-math.cxx
+
+$(fuzzer_Native_cxx): $(SRCDIR)/solenv/bin/native-code.py | $(call gb_CustomTarget_get_workdir,vcl/workben)/.dir
+ $(call gb_Helper_abbreviate_dirs, $(fuzzer_PYTHONCOMMAND) $(SRCDIR)/solenv/bin/native-code.py -g core -g math) > $@
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CustomTarget_nativewriter.mk b/vcl/CustomTarget_nativewriter.mk
new file mode 100644
index 0000000000..c396f0f5df
--- /dev/null
+++ b/vcl/CustomTarget_nativewriter.mk
@@ -0,0 +1,18 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CustomTarget_CustomTarget,vcl/workben))
+
+fuzzer_PYTHONCOMMAND := $(call gb_ExternalExecutable_get_command,python)
+
+fuzzer_Native_cxx=$(call gb_CustomTarget_get_workdir,vcl/workben)/native-writer.cxx
+
+$(fuzzer_Native_cxx): $(SRCDIR)/solenv/bin/native-code.py | $(call gb_CustomTarget_get_workdir,vcl/workben)/.dir
+ $(call gb_Helper_abbreviate_dirs, $(fuzzer_PYTHONCOMMAND) $(SRCDIR)/solenv/bin/native-code.py -g core -g writer) > $@
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/CustomTarget_qt5_moc.mk b/vcl/CustomTarget_qt5_moc.mk
new file mode 100644
index 0000000000..5fb4f482e7
--- /dev/null
+++ b/vcl/CustomTarget_qt5_moc.mk
@@ -0,0 +1,32 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CustomTarget_CustomTarget,vcl/qt5))
+
+$(call gb_CustomTarget_get_target,vcl/qt5) : \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/QtClipboard.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/QtFilePicker.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/QtFrame.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/QtInstance.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/QtMainWindow.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/QtMenu.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/QtObject.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/QtTimer.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/QtWidget.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt5)/QtXAccessible.moc \
+
+$(call gb_CustomTarget_get_workdir,vcl/qt5)/%.moc : \
+ $(SRCDIR)/vcl/inc/qt5/%.hxx \
+ | $(call gb_CustomTarget_get_workdir,vcl/qt5)/.dir
+ $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),MOC,1)
+ $(call gb_Trace_StartRange,$(subst $(WORKDIR)/,,$@),MOC)
+ $(MOC5) $< -o $@
+ $(call gb_Trace_EndRange,$(subst $(WORKDIR)/,,$@),MOC)
+
+# vim: set noet sw=4:
diff --git a/vcl/CustomTarget_qt6_moc.mk b/vcl/CustomTarget_qt6_moc.mk
new file mode 100644
index 0000000000..77aab94716
--- /dev/null
+++ b/vcl/CustomTarget_qt6_moc.mk
@@ -0,0 +1,38 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CustomTarget_CustomTarget,vcl/qt6))
+
+$(call gb_CustomTarget_get_target,vcl/qt6) : \
+ $(call gb_CustomTarget_get_workdir,vcl/qt6)/QtClipboard.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt6)/QtFilePicker.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt6)/QtFrame.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt6)/QtInstance.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt6)/QtMainWindow.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt6)/QtMenu.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt6)/QtObject.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt6)/QtTimer.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt6)/QtWidget.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/qt6)/QtXAccessible.moc \
+
+# For now, the headers in vcl/inc/qt6 just '#include' the ones
+# in 'vcl/inc/qt5'.
+# Since moc does not process classes from the included headers,
+# it needs to be run on the headers in the qt5 dir.
+# That will have to be adapted in case the qt6 VCL plugin
+# uses "own" headers
+$(call gb_CustomTarget_get_workdir,vcl/qt6)/%.moc : \
+ $(SRCDIR)/vcl/inc/qt5/%.hxx \
+ | $(call gb_CustomTarget_get_workdir,vcl/qt6)/.dir
+ $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),MOC,1)
+ $(call gb_Trace_StartRange,$(subst $(WORKDIR)/,,$@),MOC)
+ $(MOC6) $< -o $@
+ $(call gb_Trace_EndRange,$(subst $(WORKDIR)/,,$@),MOC)
+
+# vim: set noet sw=4:
diff --git a/vcl/Executable_602fuzzer.mk b/vcl/Executable_602fuzzer.mk
new file mode 100644
index 0000000000..633e536a41
--- /dev/null
+++ b/vcl/Executable_602fuzzer.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,602fuzzer))
+
+$(eval $(call gb_Executable_use_api,602fuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,602fuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,602fuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,602fuzzer,\
+ t602filter \
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,602fuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,602fuzzer,\
+ vcl/workben/602fuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,602fuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_bmpfuzzer.mk b/vcl/Executable_bmpfuzzer.mk
new file mode 100644
index 0000000000..64e132ad54
--- /dev/null
+++ b/vcl/Executable_bmpfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,bmpfuzzer))
+
+$(eval $(call gb_Executable_use_api,bmpfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,bmpfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,bmpfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,bmpfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,bmpfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,bmpfuzzer,\
+ vcl/workben/bmpfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,bmpfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_cgmfuzzer.mk b/vcl/Executable_cgmfuzzer.mk
new file mode 100644
index 0000000000..074f18fa4b
--- /dev/null
+++ b/vcl/Executable_cgmfuzzer.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,cgmfuzzer))
+
+$(eval $(call gb_Executable_use_api,cgmfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,cgmfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,cgmfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,cgmfuzzer,\
+ $(fuzzer_draw_libraries) \
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,cgmfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,cgmfuzzer,\
+ vcl/workben/cgmfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,cgmfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_dbffuzzer.mk b/vcl/Executable_dbffuzzer.mk
new file mode 100644
index 0000000000..64de9072cd
--- /dev/null
+++ b/vcl/Executable_dbffuzzer.mk
@@ -0,0 +1,48 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,dbffuzzer))
+
+$(eval $(call gb_Executable_use_api,dbffuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,dbffuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,dbffuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,dbffuzzer,\
+ $(fuzzer_calc_libraries) \
+ $(fuzzer_core_libraries) \
+ pdffilter \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,dbffuzzer,\
+ $(fuzzer_statics) \
+ fuzzer_calc \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,dbffuzzer,\
+ vcl/workben/dbffuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,dbffuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_diffuzzer.mk b/vcl/Executable_diffuzzer.mk
new file mode 100644
index 0000000000..349a416960
--- /dev/null
+++ b/vcl/Executable_diffuzzer.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,diffuzzer))
+
+$(eval $(call gb_Executable_use_api,diffuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,diffuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,diffuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,diffuzzer,\
+ $(fuzzer_calc_libraries) \
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,diffuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,diffuzzer,\
+ vcl/workben/diffuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,diffuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_docxfuzzer.mk b/vcl/Executable_docxfuzzer.mk
new file mode 100644
index 0000000000..57bc902071
--- /dev/null
+++ b/vcl/Executable_docxfuzzer.mk
@@ -0,0 +1,50 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,docxfuzzer))
+
+$(eval $(call gb_Executable_use_api,docxfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,docxfuzzer,\
+ $(fuzzer_externals) \
+ epubgen \
+ revenge \
+))
+
+$(eval $(call gb_Executable_set_include,docxfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,docxfuzzer,\
+ $(fuzzer_writer_libraries) \
+ $(fuzzer_core_libraries) \
+ pdffilter \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,docxfuzzer,\
+ $(fuzzer_statics) \
+ fuzzer_writer \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,docxfuzzer,\
+ vcl/workben/docxfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,docxfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_dxffuzzer.mk b/vcl/Executable_dxffuzzer.mk
new file mode 100644
index 0000000000..efbbe5fff9
--- /dev/null
+++ b/vcl/Executable_dxffuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,dxffuzzer))
+
+$(eval $(call gb_Executable_use_api,dxffuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,dxffuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,dxffuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,dxffuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,dxffuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,dxffuzzer,\
+ vcl/workben/dxffuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,dxffuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_epsfuzzer.mk b/vcl/Executable_epsfuzzer.mk
new file mode 100644
index 0000000000..c900dd2579
--- /dev/null
+++ b/vcl/Executable_epsfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,epsfuzzer))
+
+$(eval $(call gb_Executable_use_api,epsfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,epsfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,epsfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,epsfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,epsfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,epsfuzzer,\
+ vcl/workben/epsfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,epsfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_fftester.mk b/vcl/Executable_fftester.mk
new file mode 100644
index 0000000000..0c3ff92be3
--- /dev/null
+++ b/vcl/Executable_fftester.mk
@@ -0,0 +1,37 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Executable_Executable,fftester))
+
+$(eval $(call gb_Executable_use_api,fftester,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_set_include,fftester,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,fftester,\
+ tl \
+ sal \
+ utl \
+ vcl \
+ cppu \
+ cppuhelper \
+ comphelper \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,fftester,\
+ vcl/workben/fftester \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_fodpfuzzer.mk b/vcl/Executable_fodpfuzzer.mk
new file mode 100644
index 0000000000..aa84e71e6a
--- /dev/null
+++ b/vcl/Executable_fodpfuzzer.mk
@@ -0,0 +1,48 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,fodpfuzzer))
+
+$(eval $(call gb_Executable_use_api,fodpfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,fodpfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,fodpfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,fodpfuzzer,\
+ $(fuzzer_draw_libraries) \
+ $(fuzzer_core_libraries) \
+ pdffilter \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,fodpfuzzer,\
+ $(fuzzer_statics) \
+ fuzzer_draw \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,fodpfuzzer,\
+ vcl/workben/fodpfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,fodpfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_fodsfuzzer.mk b/vcl/Executable_fodsfuzzer.mk
new file mode 100644
index 0000000000..b4aa966b54
--- /dev/null
+++ b/vcl/Executable_fodsfuzzer.mk
@@ -0,0 +1,48 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,fodsfuzzer))
+
+$(eval $(call gb_Executable_use_api,fodsfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,fodsfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,fodsfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,fodsfuzzer,\
+ $(fuzzer_calc_libraries) \
+ $(fuzzer_core_libraries) \
+ pdffilter \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,fodsfuzzer,\
+ $(fuzzer_statics) \
+ fuzzer_calc \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,fodsfuzzer,\
+ vcl/workben/fodsfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,fodsfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_fodt2pdffuzzer.mk b/vcl/Executable_fodt2pdffuzzer.mk
new file mode 100644
index 0000000000..d4ba9f6665
--- /dev/null
+++ b/vcl/Executable_fodt2pdffuzzer.mk
@@ -0,0 +1,50 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,fodt2pdffuzzer))
+
+$(eval $(call gb_Executable_use_api,fodt2pdffuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,fodt2pdffuzzer,\
+ $(fuzzer_externals) \
+ epubgen \
+ revenge \
+))
+
+$(eval $(call gb_Executable_set_include,fodt2pdffuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,fodt2pdffuzzer,\
+ $(fuzzer_writer_libraries) \
+ $(fuzzer_core_libraries) \
+ pdffilter \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,fodt2pdffuzzer,\
+ $(fuzzer_statics) \
+ fuzzer_writer \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,fodt2pdffuzzer,\
+ vcl/workben/fodt2pdffuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,fodt2pdffuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_fodtfuzzer.mk b/vcl/Executable_fodtfuzzer.mk
new file mode 100644
index 0000000000..f07a002f8f
--- /dev/null
+++ b/vcl/Executable_fodtfuzzer.mk
@@ -0,0 +1,50 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,fodtfuzzer))
+
+$(eval $(call gb_Executable_use_api,fodtfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,fodtfuzzer,\
+ $(fuzzer_externals) \
+ epubgen \
+ revenge \
+))
+
+$(eval $(call gb_Executable_set_include,fodtfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,fodtfuzzer,\
+ $(fuzzer_writer_libraries) \
+ $(fuzzer_core_libraries) \
+ pdffilter \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,fodtfuzzer,\
+ $(fuzzer_statics) \
+ fuzzer_writer \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,fodtfuzzer,\
+ vcl/workben/fodtfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,fodtfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_giffuzzer.mk b/vcl/Executable_giffuzzer.mk
new file mode 100644
index 0000000000..89757046df
--- /dev/null
+++ b/vcl/Executable_giffuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,giffuzzer))
+
+$(eval $(call gb_Executable_use_api,giffuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,giffuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,giffuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,giffuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,giffuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,giffuzzer,\
+ vcl/workben/giffuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,giffuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_htmlfuzzer.mk b/vcl/Executable_htmlfuzzer.mk
new file mode 100644
index 0000000000..fc42be8584
--- /dev/null
+++ b/vcl/Executable_htmlfuzzer.mk
@@ -0,0 +1,50 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,htmlfuzzer))
+
+$(eval $(call gb_Executable_use_api,htmlfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,htmlfuzzer,\
+ $(fuzzer_externals) \
+ epubgen \
+ revenge \
+))
+
+$(eval $(call gb_Executable_set_include,htmlfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,htmlfuzzer,\
+ $(fuzzer_writer_libraries) \
+ $(fuzzer_core_libraries) \
+ pdffilter \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,htmlfuzzer,\
+ $(fuzzer_statics) \
+ fuzzer_writer \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,htmlfuzzer,\
+ vcl/workben/htmlfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,htmlfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_hwpfuzzer.mk b/vcl/Executable_hwpfuzzer.mk
new file mode 100644
index 0000000000..1157ba0f0b
--- /dev/null
+++ b/vcl/Executable_hwpfuzzer.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,hwpfuzzer))
+
+$(eval $(call gb_Executable_use_api,hwpfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,hwpfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,hwpfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,hwpfuzzer,\
+ hwp \
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,hwpfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,hwpfuzzer,\
+ vcl/workben/hwpfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,hwpfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_icontest.mk b/vcl/Executable_icontest.mk
new file mode 100644
index 0000000000..dc71ce3443
--- /dev/null
+++ b/vcl/Executable_icontest.mk
@@ -0,0 +1,37 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Executable_Executable,icontest))
+
+$(eval $(call gb_Executable_add_defs,icontest,\
+ -DVCL_INTERNALS \
+))
+
+$(eval $(call gb_Executable_use_api,icontest,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_libraries,icontest,\
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ tl \
+ ucbhelper \
+ vcl \
+))
+
+$(eval $(call gb_Executable_use_vclmain,icontest))
+
+$(eval $(call gb_Executable_add_exception_objects,icontest,\
+ vcl/workben/icontest \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_jpgfuzzer.mk b/vcl/Executable_jpgfuzzer.mk
new file mode 100644
index 0000000000..748b6dff93
--- /dev/null
+++ b/vcl/Executable_jpgfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,jpgfuzzer))
+
+$(eval $(call gb_Executable_use_api,jpgfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,jpgfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,jpgfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,jpgfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,jpgfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,jpgfuzzer,\
+ vcl/workben/jpgfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,jpgfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_listfonts.mk b/vcl/Executable_listfonts.mk
new file mode 100644
index 0000000000..94ed8bd11e
--- /dev/null
+++ b/vcl/Executable_listfonts.mk
@@ -0,0 +1,36 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Executable_Executable,listfonts))
+
+$(eval $(call gb_Executable_use_api,listfonts,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_set_include,listfonts,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,listfonts,\
+ tl \
+ sal \
+ vcl \
+ cppu \
+ cppuhelper \
+ comphelper \
+ i18nlangtag \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,listfonts,\
+ vcl/workben/listfonts \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_listglyphs.mk b/vcl/Executable_listglyphs.mk
new file mode 100644
index 0000000000..acd1fd9b00
--- /dev/null
+++ b/vcl/Executable_listglyphs.mk
@@ -0,0 +1,41 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Executable_Executable,listglyphs))
+
+$(eval $(call gb_Executable_use_api,listglyphs,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_set_include,listglyphs,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_externals,listglyphs,\
+ harfbuzz \
+ graphite \
+))
+
+$(eval $(call gb_Executable_use_libraries,listglyphs,\
+ tl \
+ sal \
+ vcl \
+ cppu \
+ cppuhelper \
+ comphelper \
+ i18nlangtag \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,listglyphs,\
+ vcl/workben/listglyphs \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_lo_kde5filepicker.mk b/vcl/Executable_lo_kde5filepicker.mk
new file mode 100644
index 0000000000..9244a8c18b
--- /dev/null
+++ b/vcl/Executable_lo_kde5filepicker.mk
@@ -0,0 +1,89 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Executable_Executable,lo_kde5filepicker))
+
+# FIXME: how to find the moc files automatically?!
+$(eval $(call gb_Executable_set_include,lo_kde5filepicker,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+ -I$(WORKDIR)/CustomTarget/vcl/unx/gtk3_kde5 \
+))
+
+$(eval $(call gb_Executable_add_cxxflags,lo_kde5filepicker,\
+ $$(INCLUDE) \
+ $$(BOOST_CXXFLAGS) \
+))
+
+$(eval $(call gb_Executable_use_custom_headers,lo_kde5filepicker,\
+ officecfg/registry \
+))
+
+$(eval $(call gb_Executable_use_sdk_api,lo_kde5filepicker))
+
+$(eval $(call gb_Executable_add_libs,lo_kde5filepicker,\
+ -lX11 \
+ -lXext \
+ -lSM \
+ -lICE \
+))
+
+$(eval $(call gb_Executable_use_libraries,lo_kde5filepicker,\
+ vclplug_gen \
+ vcl \
+ tl \
+ utl \
+ sot \
+ ucbhelper \
+ basegfx \
+ comphelper \
+ cppuhelper \
+ i18nlangtag \
+ i18nutil \
+ $(if $(ENABLE_JAVA), \
+ jvmaccess) \
+ cppu \
+ sal \
+))
+
+$(eval $(call gb_Executable_use_externals,lo_kde5filepicker,\
+ qt5 \
+ kf5 \
+ dbus \
+))
+
+$(eval $(call gb_Executable_add_libs,lo_kde5filepicker,\
+ $(BOOST_PROCESS_LIB) \
+ $(BOOST_FILESYSTEM_LIB) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,lo_kde5filepicker,\
+ vcl/unx/gtk3_kde5/kde5_lo_filepicker_main \
+ vcl/unx/gtk3_kde5/kde5_filepicker \
+ vcl/unx/gtk3_kde5/kde5_filepicker_ipc \
+))
+
+ifeq ($(OS),LINUX)
+$(eval $(call gb_Executable_add_libs,lo_kde5filepicker,\
+ -lm \
+ -ldl \
+))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_lwpfuzzer.mk b/vcl/Executable_lwpfuzzer.mk
new file mode 100644
index 0000000000..c1d68df433
--- /dev/null
+++ b/vcl/Executable_lwpfuzzer.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,lwpfuzzer))
+
+$(eval $(call gb_Executable_use_api,lwpfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,lwpfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,lwpfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,lwpfuzzer,\
+ lwpft \
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,lwpfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,lwpfuzzer,\
+ vcl/workben/lwpfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,lwpfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_metfuzzer.mk b/vcl/Executable_metfuzzer.mk
new file mode 100644
index 0000000000..442a20e863
--- /dev/null
+++ b/vcl/Executable_metfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,metfuzzer))
+
+$(eval $(call gb_Executable_use_api,metfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,metfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,metfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,metfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,metfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,metfuzzer,\
+ vcl/workben/metfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,metfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_minvcl.mk b/vcl/Executable_minvcl.mk
new file mode 100644
index 0000000000..f3b603e8c6
--- /dev/null
+++ b/vcl/Executable_minvcl.mk
@@ -0,0 +1,38 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Executable_Executable,minvcl))
+
+$(eval $(call gb_Executable_use_api,minvcl,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_set_include,minvcl,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,minvcl,\
+ tl \
+ sal \
+ vcl \
+ cppu \
+ cppuhelper \
+ comphelper \
+ i18nlangtag \
+ fwk \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,minvcl,\
+ vcl/workben/minvcl \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_mmlfuzzer.mk b/vcl/Executable_mmlfuzzer.mk
new file mode 100644
index 0000000000..e2b2b8596a
--- /dev/null
+++ b/vcl/Executable_mmlfuzzer.mk
@@ -0,0 +1,48 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,mmlfuzzer))
+
+$(eval $(call gb_Executable_use_api,mmlfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,mmlfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,mmlfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,mmlfuzzer,\
+ $(fuzzer_math_libraries) \
+ $(fuzzer_core_libraries) \
+ pdffilter \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,mmlfuzzer,\
+ $(fuzzer_statics) \
+ fuzzer_math \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,mmlfuzzer,\
+ vcl/workben/mmlfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,mmlfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_mtfdemo.mk b/vcl/Executable_mtfdemo.mk
new file mode 100644
index 0000000000..31e017f08d
--- /dev/null
+++ b/vcl/Executable_mtfdemo.mk
@@ -0,0 +1,43 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Executable_Executable,mtfdemo))
+
+$(eval $(call gb_Executable_use_api,mtfdemo,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_set_include,mtfdemo,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,mtfdemo,\
+ basegfx \
+ tl \
+ sal \
+ cppu \
+ cppuhelper \
+ comphelper \
+ fwk \
+ drawinglayer \
+ emfio \
+ i18nlangtag \
+ vcl \
+))
+
+$(eval $(call gb_Executable_use_vclmain,mtfdemo))
+
+$(eval $(call gb_Executable_add_exception_objects,mtfdemo,\
+ vcl/workben/mtfdemo \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_mtpfuzzer.mk b/vcl/Executable_mtpfuzzer.mk
new file mode 100644
index 0000000000..c16857fcdc
--- /dev/null
+++ b/vcl/Executable_mtpfuzzer.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,mtpfuzzer))
+
+$(eval $(call gb_Executable_use_api,mtpfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,mtpfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,mtpfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,mtpfuzzer,\
+ $(fuzzer_math_libraries) \
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,mtpfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,mtpfuzzer,\
+ vcl/workben/mtpfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,mtpfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_olefuzzer.mk b/vcl/Executable_olefuzzer.mk
new file mode 100644
index 0000000000..f19604550f
--- /dev/null
+++ b/vcl/Executable_olefuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,olefuzzer))
+
+$(eval $(call gb_Executable_use_api,olefuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,olefuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,olefuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,olefuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,olefuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,olefuzzer,\
+ vcl/workben/olefuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,olefuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_pcdfuzzer.mk b/vcl/Executable_pcdfuzzer.mk
new file mode 100644
index 0000000000..a53db76326
--- /dev/null
+++ b/vcl/Executable_pcdfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,pcdfuzzer))
+
+$(eval $(call gb_Executable_use_api,pcdfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,pcdfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,pcdfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,pcdfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,pcdfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,pcdfuzzer,\
+ vcl/workben/pcdfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,pcdfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_pctfuzzer.mk b/vcl/Executable_pctfuzzer.mk
new file mode 100644
index 0000000000..66b62d8e19
--- /dev/null
+++ b/vcl/Executable_pctfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,pctfuzzer))
+
+$(eval $(call gb_Executable_use_api,pctfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,pctfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,pctfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,pctfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,pctfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,pctfuzzer,\
+ vcl/workben/pctfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,pctfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_pcxfuzzer.mk b/vcl/Executable_pcxfuzzer.mk
new file mode 100644
index 0000000000..868321c726
--- /dev/null
+++ b/vcl/Executable_pcxfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,pcxfuzzer))
+
+$(eval $(call gb_Executable_use_api,pcxfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,pcxfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,pcxfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,pcxfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,pcxfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,pcxfuzzer,\
+ vcl/workben/pcxfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,pcxfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_pngfuzzer.mk b/vcl/Executable_pngfuzzer.mk
new file mode 100644
index 0000000000..682f02102a
--- /dev/null
+++ b/vcl/Executable_pngfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,pngfuzzer))
+
+$(eval $(call gb_Executable_use_api,pngfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,pngfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,pngfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,pngfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,pngfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,pngfuzzer,\
+ vcl/workben/pngfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,pngfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_ppmfuzzer.mk b/vcl/Executable_ppmfuzzer.mk
new file mode 100644
index 0000000000..a6d1850d60
--- /dev/null
+++ b/vcl/Executable_ppmfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,ppmfuzzer))
+
+$(eval $(call gb_Executable_use_api,ppmfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,ppmfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,ppmfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,ppmfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,ppmfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,ppmfuzzer,\
+ vcl/workben/ppmfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,ppmfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_pptfuzzer.mk b/vcl/Executable_pptfuzzer.mk
new file mode 100644
index 0000000000..bdb4c39f8a
--- /dev/null
+++ b/vcl/Executable_pptfuzzer.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,pptfuzzer))
+
+$(eval $(call gb_Executable_use_api,pptfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,pptfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,pptfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,pptfuzzer,\
+ $(fuzzer_draw_libraries) \
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,pptfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,pptfuzzer,\
+ vcl/workben/pptfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,pptfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_pptxfuzzer.mk b/vcl/Executable_pptxfuzzer.mk
new file mode 100644
index 0000000000..83aa65d800
--- /dev/null
+++ b/vcl/Executable_pptxfuzzer.mk
@@ -0,0 +1,48 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,pptxfuzzer))
+
+$(eval $(call gb_Executable_use_api,pptxfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,pptxfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,pptxfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,pptxfuzzer,\
+ $(fuzzer_draw_libraries) \
+ $(fuzzer_core_libraries) \
+ pdffilter \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,pptxfuzzer,\
+ $(fuzzer_statics) \
+ fuzzer_draw \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,pptxfuzzer,\
+ vcl/workben/pptxfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,pptxfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_psdfuzzer.mk b/vcl/Executable_psdfuzzer.mk
new file mode 100644
index 0000000000..995b0410e9
--- /dev/null
+++ b/vcl/Executable_psdfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,psdfuzzer))
+
+$(eval $(call gb_Executable_use_api,psdfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,psdfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,psdfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,psdfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,psdfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,psdfuzzer,\
+ vcl/workben/psdfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,psdfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_qpwfuzzer.mk b/vcl/Executable_qpwfuzzer.mk
new file mode 100644
index 0000000000..d5de32b867
--- /dev/null
+++ b/vcl/Executable_qpwfuzzer.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,qpwfuzzer))
+
+$(eval $(call gb_Executable_use_api,qpwfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,qpwfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,qpwfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,qpwfuzzer,\
+ $(fuzzer_calc_libraries) \
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,qpwfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,qpwfuzzer,\
+ vcl/workben/qpwfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,qpwfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_rasfuzzer.mk b/vcl/Executable_rasfuzzer.mk
new file mode 100644
index 0000000000..a0b8fd21c7
--- /dev/null
+++ b/vcl/Executable_rasfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,rasfuzzer))
+
+$(eval $(call gb_Executable_use_api,rasfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,rasfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,rasfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,rasfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,rasfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,rasfuzzer,\
+ vcl/workben/rasfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,rasfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_rtffuzzer.mk b/vcl/Executable_rtffuzzer.mk
new file mode 100644
index 0000000000..c5c05a8eb6
--- /dev/null
+++ b/vcl/Executable_rtffuzzer.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,rtffuzzer))
+
+$(eval $(call gb_Executable_use_api,rtffuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,rtffuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,rtffuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,rtffuzzer,\
+ $(fuzzer_writer_libraries) \
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,rtffuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,rtffuzzer,\
+ vcl/workben/rtffuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,rtffuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_scrtffuzzer.mk b/vcl/Executable_scrtffuzzer.mk
new file mode 100644
index 0000000000..984644c204
--- /dev/null
+++ b/vcl/Executable_scrtffuzzer.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,scrtffuzzer))
+
+$(eval $(call gb_Executable_use_api,scrtffuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,scrtffuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,scrtffuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,scrtffuzzer,\
+ $(fuzzer_calc_libraries) \
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,scrtffuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,scrtffuzzer,\
+ vcl/workben/scrtffuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,scrtffuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_sftfuzzer.mk b/vcl/Executable_sftfuzzer.mk
new file mode 100644
index 0000000000..41781d024e
--- /dev/null
+++ b/vcl/Executable_sftfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,sftfuzzer))
+
+$(eval $(call gb_Executable_use_api,sftfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,sftfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,sftfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,sftfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,sftfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,sftfuzzer,\
+ vcl/workben/sftfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,sftfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_slkfuzzer.mk b/vcl/Executable_slkfuzzer.mk
new file mode 100644
index 0000000000..75d44fec34
--- /dev/null
+++ b/vcl/Executable_slkfuzzer.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,slkfuzzer))
+
+$(eval $(call gb_Executable_use_api,slkfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,slkfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,slkfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,slkfuzzer,\
+ $(fuzzer_calc_libraries) \
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,slkfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,slkfuzzer,\
+ vcl/workben/slkfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,slkfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_svdemo.mk b/vcl/Executable_svdemo.mk
new file mode 100644
index 0000000000..f6d8b9df3a
--- /dev/null
+++ b/vcl/Executable_svdemo.mk
@@ -0,0 +1,38 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Executable_Executable,svdemo))
+
+$(eval $(call gb_Executable_use_api,svdemo,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_set_include,svdemo,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,svdemo,\
+ tl \
+ sal \
+ vcl \
+ cppu \
+ cppuhelper \
+ comphelper \
+ i18nlangtag \
+ fwk \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,svdemo,\
+ vcl/workben/svdem \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_svgfuzzer.mk b/vcl/Executable_svgfuzzer.mk
new file mode 100644
index 0000000000..584a0f8265
--- /dev/null
+++ b/vcl/Executable_svgfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,svgfuzzer))
+
+$(eval $(call gb_Executable_use_api,svgfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,svgfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,svgfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,svgfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,svgfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,svgfuzzer,\
+ vcl/workben/svgfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,svgfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_svmfuzzer.mk b/vcl/Executable_svmfuzzer.mk
new file mode 100644
index 0000000000..7c54dbd1b0
--- /dev/null
+++ b/vcl/Executable_svmfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,svmfuzzer))
+
+$(eval $(call gb_Executable_use_api,svmfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,svmfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,svmfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,svmfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,svmfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,svmfuzzer,\
+ vcl/workben/svmfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,svmfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_svpclient.mk b/vcl/Executable_svpclient.mk
new file mode 100644
index 0000000000..eb20033fe7
--- /dev/null
+++ b/vcl/Executable_svpclient.mk
@@ -0,0 +1,44 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Executable_Executable,svpclient))
+
+$(eval $(call gb_Executable_use_api,svpclient,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_set_include,svpclient,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_add_defs,svpclient,\
+ -DVCL_INTERNALS \
+))
+
+$(eval $(call gb_Executable_use_libraries,svpclient,\
+ tl \
+ sal \
+ vcl \
+ cppu \
+ cppuhelper \
+ comphelper \
+))
+
+ifeq ($(OS),HAIKU)
+$(eval $(call gb_Executable_add_libs,svpclient,-lnetwork))
+endif
+
+$(eval $(call gb_Executable_add_exception_objects,svpclient,\
+ vcl/workben/svpclient \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_svptest.mk b/vcl/Executable_svptest.mk
new file mode 100644
index 0000000000..8651a27ac7
--- /dev/null
+++ b/vcl/Executable_svptest.mk
@@ -0,0 +1,36 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Executable_Executable,svptest))
+
+$(eval $(call gb_Executable_use_api,svptest,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_set_include,svptest,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,svptest,\
+ tl \
+ sal \
+ vcl \
+ cppu \
+ cppuhelper \
+ comphelper \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,svptest,\
+ vcl/workben/svptest \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_tgafuzzer.mk b/vcl/Executable_tgafuzzer.mk
new file mode 100644
index 0000000000..f42142b185
--- /dev/null
+++ b/vcl/Executable_tgafuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,tgafuzzer))
+
+$(eval $(call gb_Executable_use_api,tgafuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,tgafuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,tgafuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,tgafuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,tgafuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,tgafuzzer,\
+ vcl/workben/tgafuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,tgafuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_tiffuzzer.mk b/vcl/Executable_tiffuzzer.mk
new file mode 100644
index 0000000000..fdba17a02d
--- /dev/null
+++ b/vcl/Executable_tiffuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,tiffuzzer))
+
+$(eval $(call gb_Executable_use_api,tiffuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,tiffuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,tiffuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,tiffuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,tiffuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,tiffuzzer,\
+ vcl/workben/tiffuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,tiffuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_vcldemo.mk b/vcl/Executable_vcldemo.mk
new file mode 100644
index 0000000000..f09f9fffeb
--- /dev/null
+++ b/vcl/Executable_vcldemo.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Executable_Executable,vcldemo))
+
+$(eval $(call gb_Executable_use_api,vcldemo,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_add_defs,vcldemo,\
+ -DVCL_INTERNALS \
+))
+
+$(eval $(call gb_Executable_set_include,vcldemo,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,vcldemo,\
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ tl \
+ sal \
+ salhelper \
+ fwk \
+ i18nlangtag \
+ vcl \
+))
+
+$(eval $(call gb_Executable_use_vclmain,vcldemo))
+
+$(eval $(call gb_Executable_add_exception_objects,vcldemo,\
+ vcl/workben/vcldemo \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_visualbackendtest.mk b/vcl/Executable_visualbackendtest.mk
new file mode 100644
index 0000000000..567399464e
--- /dev/null
+++ b/vcl/Executable_visualbackendtest.mk
@@ -0,0 +1,40 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Executable_Executable,visualbackendtest))
+
+$(eval $(call gb_Executable_use_api,visualbackendtest,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_set_include,visualbackendtest,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,visualbackendtest,\
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ tl \
+ sal \
+ salhelper \
+ vcl \
+))
+
+$(eval $(call gb_Executable_use_vclmain,visualbackendtest))
+
+$(eval $(call gb_Executable_add_exception_objects,visualbackendtest,\
+ vcl/backendtest/VisualBackendTest \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_webpfuzzer.mk b/vcl/Executable_webpfuzzer.mk
new file mode 100644
index 0000000000..3851fbe52c
--- /dev/null
+++ b/vcl/Executable_webpfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,webpfuzzer))
+
+$(eval $(call gb_Executable_use_api,webpfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,webpfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,webpfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,webpfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,webpfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,webpfuzzer,\
+ vcl/workben/webpfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,webpfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_wksfuzzer.mk b/vcl/Executable_wksfuzzer.mk
new file mode 100644
index 0000000000..0728f5f14d
--- /dev/null
+++ b/vcl/Executable_wksfuzzer.mk
@@ -0,0 +1,48 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,wksfuzzer))
+
+$(eval $(call gb_Executable_use_api,wksfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,wksfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,wksfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,wksfuzzer,\
+ $(fuzzer_calc_libraries) \
+ $(fuzzer_core_libraries) \
+ pdffilter \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,wksfuzzer,\
+ $(fuzzer_statics) \
+ fuzzer_calc \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,wksfuzzer,\
+ vcl/workben/wksfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,wksfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_wmffuzzer.mk b/vcl/Executable_wmffuzzer.mk
new file mode 100644
index 0000000000..5da2cdfaee
--- /dev/null
+++ b/vcl/Executable_wmffuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,wmffuzzer))
+
+$(eval $(call gb_Executable_use_api,wmffuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,wmffuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,wmffuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,wmffuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,wmffuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,wmffuzzer,\
+ vcl/workben/wmffuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,wmffuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_ww2fuzzer.mk b/vcl/Executable_ww2fuzzer.mk
new file mode 100644
index 0000000000..8bb3ec0528
--- /dev/null
+++ b/vcl/Executable_ww2fuzzer.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,ww2fuzzer))
+
+$(eval $(call gb_Executable_use_api,ww2fuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,ww2fuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,ww2fuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,ww2fuzzer,\
+ $(fuzzer_writer_libraries) \
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,ww2fuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,ww2fuzzer,\
+ vcl/workben/ww2fuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,ww2fuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_ww6fuzzer.mk b/vcl/Executable_ww6fuzzer.mk
new file mode 100644
index 0000000000..527e633460
--- /dev/null
+++ b/vcl/Executable_ww6fuzzer.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,ww6fuzzer))
+
+$(eval $(call gb_Executable_use_api,ww6fuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,ww6fuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,ww6fuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,ww6fuzzer,\
+ $(fuzzer_writer_libraries) \
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,ww6fuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,ww6fuzzer,\
+ vcl/workben/ww6fuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,ww6fuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_ww8fuzzer.mk b/vcl/Executable_ww8fuzzer.mk
new file mode 100644
index 0000000000..b604f6be45
--- /dev/null
+++ b/vcl/Executable_ww8fuzzer.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,ww8fuzzer))
+
+$(eval $(call gb_Executable_use_api,ww8fuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,ww8fuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,ww8fuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,ww8fuzzer,\
+ $(fuzzer_writer_libraries) \
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,ww8fuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,ww8fuzzer,\
+ vcl/workben/ww8fuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,ww8fuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_xbmfuzzer.mk b/vcl/Executable_xbmfuzzer.mk
new file mode 100644
index 0000000000..484250c9f8
--- /dev/null
+++ b/vcl/Executable_xbmfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,xbmfuzzer))
+
+$(eval $(call gb_Executable_use_api,xbmfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,xbmfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,xbmfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,xbmfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,xbmfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,xbmfuzzer,\
+ vcl/workben/xbmfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,xbmfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_xlsfuzzer.mk b/vcl/Executable_xlsfuzzer.mk
new file mode 100644
index 0000000000..496bac2475
--- /dev/null
+++ b/vcl/Executable_xlsfuzzer.mk
@@ -0,0 +1,48 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,xlsfuzzer))
+
+$(eval $(call gb_Executable_use_api,xlsfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,xlsfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,xlsfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,xlsfuzzer,\
+ $(fuzzer_calc_libraries) \
+ $(fuzzer_core_libraries) \
+ pdffilter \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,xlsfuzzer,\
+ $(fuzzer_statics) \
+ fuzzer_calc \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,xlsfuzzer,\
+ vcl/workben/xlsfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,xlsfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_xlsxfuzzer.mk b/vcl/Executable_xlsxfuzzer.mk
new file mode 100644
index 0000000000..60bbe467dd
--- /dev/null
+++ b/vcl/Executable_xlsxfuzzer.mk
@@ -0,0 +1,48 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,xlsxfuzzer))
+
+$(eval $(call gb_Executable_use_api,xlsxfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,xlsxfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,xlsxfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,xlsxfuzzer,\
+ $(fuzzer_calc_libraries) \
+ $(fuzzer_core_libraries) \
+ pdffilter \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,xlsxfuzzer,\
+ $(fuzzer_statics) \
+ fuzzer_calc \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,xlsxfuzzer,\
+ vcl/workben/xlsxfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,xlsxfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_xpmfuzzer.mk b/vcl/Executable_xpmfuzzer.mk
new file mode 100644
index 0000000000..402c53bb6e
--- /dev/null
+++ b/vcl/Executable_xpmfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,xpmfuzzer))
+
+$(eval $(call gb_Executable_use_api,xpmfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,xpmfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,xpmfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,xpmfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,xpmfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,xpmfuzzer,\
+ vcl/workben/xpmfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,xpmfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Executable_zipfuzzer.mk b/vcl/Executable_zipfuzzer.mk
new file mode 100644
index 0000000000..8b8b519db7
--- /dev/null
+++ b/vcl/Executable_zipfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,zipfuzzer))
+
+$(eval $(call gb_Executable_use_api,zipfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,zipfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,zipfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,zipfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,zipfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,zipfuzzer,\
+ vcl/workben/zipfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,zipfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/IwyuFilter_vcl.yaml b/vcl/IwyuFilter_vcl.yaml
new file mode 100644
index 0000000000..aba0e21f13
--- /dev/null
+++ b/vcl/IwyuFilter_vcl.yaml
@@ -0,0 +1,118 @@
+---
+assumeFilename: vcl/source/app/svapp.cxx
+excludelist:
+ vcl/inc/salusereventlist.hxx:
+ # Don't propose hxx -> h change in URE libs
+ - osl/thread.hxx
+ vcl/inc/headless/svpinst.hxx:
+ # Don't propose hxx -> h change in URE libs
+ - osl/thread.hxx
+ vcl/inc/unx/gendata.hxx:
+ # Don't propose hxx -> h change in URE libs
+ - osl/socket.hxx
+ vcl/inc/unx/saldisp.hxx:
+ # Don't replace with generated header
+ - epoxy/glx.h
+ vcl/inc/unx/svsys.h:
+ # Empty header, but keep intact for nice system abstraction
+ - X11/Xlib.h
+ - X11/Xutil.h
+ - X11/XKBlib.h
+ vcl/inc/svsys.h:
+ # Needed for nice system abstraction
+ - unx/svsys.h
+ vcl/qa/cppunit/outdev.cxx:
+ # Needed for direct member access
+ - basegfx/matrix/b2dhommatrix.hxx
+ vcl/source/app/salplug.cxx:
+ # Needed on WIN32
+ - salframe.hxx
+ vcl/source/app/svdata.cxx:
+ # Needed on WIN32
+ - com/sun/star/accessibility/MSAAService.hpp
+ - salframe.hxx
+ vcl/source/app/svmain.cxx:
+ # Needed on WIN32
+ - desktop/exithelper.h
+ vcl/source/components/factory.cxx:
+ # Actually these are used
+ - com/sun/star/lang/XMultiServiceFactory.hpp
+ - com/sun/star/lang/XSingleServiceFactory.hpp
+ vcl/source/filter/FilterConfigItem.cxx:
+ # Needed for direct member access
+ - com/sun/star/task/XStatusIndicator.hpp
+ vcl/source/filter/ipdf/pdfdocument.cxx:
+ - comphelper/scopeguard.hxx
+ # Actually these are used
+ - com/sun/star/security/XCertificate.hpp
+ - vector
+ vcl/source/filter/jpeg/JpegWriter.hxx:
+ # Needed for direct member access
+ - vcl/BitmapReadAccess.hxx
+ vcl/source/filter/wmf/wmfexternal.cxx:
+ # Actually these are used
+ - com/sun/star/beans/PropertyValue.hpp
+ vcl/source/fontsubset/sft.cxx:
+ # Needed on WIN32 / MAC / IOS
+ - xlat.hxx
+ vcl/source/gdi/configsettings.cxx:
+ # Needed for OSL_DEBUG_LEVEL > 2
+ - sal/log.hxx
+ vcl/source/gdi/salgdilayout.cxx:
+ # Needed on WIN32
+ - desktop/exithelper.h
+ vcl/source/helper/commandinfoprovider.cxx:
+ # Actually these are used
+ - com/sun/star/frame/XFrame.hpp
+ vcl/source/image/ImageTree.cxx:
+ # Actually these are used
+ - com/sun/star/container/XNameAccess.hpp
+ - com/sun/star/uno/Reference.hxx
+ vcl/source/treelist/headbar.cxx:
+ # Actually these are used
+ - com/sun/star/accessibility/XAccessible.hpp
+ vcl/source/window/dialog.cxx:
+ # comphelper::ScopeGuard is actually used
+ - comphelper/scopeguard.hxx
+ vcl/source/window/event.cxx:
+ # comphelper::ScopeGuard is actually used
+ - comphelper/scopeguard.hxx
+ vcl/unx/generic/app/saldisp.cxx:
+ # needed for transitive cursor includes
+ - unx/x11_cursors/salcursors.h
+ vcl/unx/generic/gdi/font.cxx:
+ # Complete type needed for implicit dtor
+ - vcl/fontcharmap.hxx
+ vcl/unx/generic/glyphs/freetype_glyphcache.cxx:
+ # Needed for FreeType header macros
+ - ft2build.h
+ vcl/unx/generic/print/genpspgraphics.cxx:
+ # Complete type needed for implicit dtor
+ - vcl/fontcharmap.hxx
+ vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.cxx:
+ # Actually these are used
+ - QUrl
+ - KFileWidget
+ vcl/unx/gtk3_kde5/kde5_filepicker.cxx:
+ # Actually these are used
+ - KWindowSystem
+ - KFileWidget
+ - QtCore/QDebug
+ - QtCore/QUrl
+ - QtWidgets/QCheckBox
+ - QtWidgets/QFileDialog
+ - QtWidgets/QGridLayout
+ - QtWidgets/QWidget
+ - QtWidgets/QApplication
+ vcl/unx/gtk3_kde5/kde5_lo_filepicker_main.cxx:
+ # Actually these are used
+ - QApplication
+ - QCommandLineParser
+ vcl/unx/gtk3_kde5/kde5_filepicker_ipc.cxx:
+ # Actually these are used
+ - QUrl
+ - QApplication
+ - QDebug
+ vcl/qa/cppunit/filter/ipdf/ipdf.cxx:
+ - prewin.h
+ - postwin.h
diff --git a/vcl/Library_desktop_detector.mk b/vcl/Library_desktop_detector.mk
new file mode 100644
index 0000000000..2a2858fc4e
--- /dev/null
+++ b/vcl/Library_desktop_detector.mk
@@ -0,0 +1,73 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Library_Library,desktop_detector))
+
+$(eval $(call gb_Library_set_plugin_for,desktop_detector,vcl))
+
+$(eval $(call gb_Library_set_include,desktop_detector,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Library_add_defs,desktop_detector,\
+ -DDESKTOP_DETECTOR_IMPLEMENTATION \
+))
+
+$(eval $(call gb_Library_use_sdk_api,desktop_detector))
+
+$(eval $(call gb_Library_use_libraries,desktop_detector,\
+ tl \
+ utl \
+ sot \
+ ucbhelper \
+ basegfx \
+ comphelper \
+ cppuhelper \
+ i18nlangtag \
+ i18nutil \
+ $(if $(ENABLE_JAVA), \
+ jvmaccess) \
+ cppu \
+ sal \
+))
+
+$(eval $(call gb_Library_use_externals,desktop_detector,\
+ boost_headers \
+ icuuc \
+))
+
+$(eval $(call gb_Library_add_libs,desktop_detector,\
+ -lX11 \
+ -lXext \
+ -lSM \
+ -lICE \
+))
+
+$(eval $(call gb_Library_add_exception_objects,desktop_detector,\
+ vcl/unx/generic/desktopdetect/desktopdetector \
+))
+
+ifeq ($(OS), $(filter LINUX %BSD SOLARIS, $(OS)))
+$(eval $(call gb_Library_add_libs,desktop_detector,\
+ -lm $(UNIX_DLAPI_LIBS) \
+))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
new file mode 100644
index 0000000000..fb9687dc47
--- /dev/null
+++ b/vcl/Library_vcl.mk
@@ -0,0 +1,745 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Library_Library,vcl))
+
+$(eval $(call gb_Library_set_componentfile,vcl,vcl/vcl.common,services))
+
+ifeq ($(OS),MACOSX)
+$(eval $(call gb_Library_add_componentimpl,vcl,macosx))
+else ifeq ($(OS),WNT)
+$(eval $(call gb_Library_add_componentimpl,vcl,windows))
+else ifeq ($(OS),ANDROID)
+$(eval $(call gb_Library_add_componentimpl,vcl,android))
+else ifeq ($(OS),iOS)
+$(eval $(call gb_Library_add_componentimpl,vcl,ios))
+else ifeq ($(DISABLE_GUI),TRUE)
+$(eval $(call gb_Library_add_componentimpl,vcl,headless))
+else
+$(eval $(call gb_Library_add_componentimpl,vcl,unx))
+endif
+
+$(eval $(call gb_Library_set_precompiled_header,vcl,vcl/inc/pch/precompiled_vcl))
+
+$(eval $(call gb_Library_set_include,vcl,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Library_add_defs,vcl,\
+ -DVCL_DLLIMPLEMENTATION \
+ -DDLLIMPLEMENTATION_UITEST \
+ -DCUI_DLL_NAME=\"$(call gb_Library_get_runtime_filename,$(call gb_Library__get_name,cui))\" \
+ -DDESKTOP_DETECTOR_DLL_NAME=\"$(call gb_Library_get_runtime_filename,$(call gb_Library__get_name,desktop_detector))\" \
+ -DTK_DLL_NAME=\"$(call gb_Library_get_runtime_filename,$(call gb_Library__get_name,tk))\" \
+ $(if $(SYSTEM_LIBFIXMATH),-DSYSTEM_LIBFIXMATH) \
+))
+
+$(eval $(call gb_Library_use_sdk_api,vcl))
+
+$(eval $(call gb_Library_use_custom_headers,vcl,\
+ officecfg/registry \
+))
+
+$(eval $(call gb_Library_use_libraries,vcl,\
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ $(call gb_Helper_optional,BREAKPAD,crashreport) \
+ drawinglayercore \
+ i18nlangtag \
+ i18nutil \
+ $(if $(ENABLE_JAVA),jvmaccess) \
+ $(if $(filter OPENCL,$(BUILD_TYPE)),opencl) \
+ sal \
+ salhelper \
+ sot \
+ svl \
+ tl \
+ ucbhelper \
+ utl \
+ xmlreader \
+))
+
+$(eval $(call gb_Library_use_externals,vcl,\
+ boost_headers \
+ expat \
+ frozen \
+ gio \
+ graphite \
+ harfbuzz \
+ icu_headers \
+ icuuc \
+ lcms2 \
+ libeot \
+ libjpeg \
+ libpng \
+ libtiff \
+ libwebp \
+))
+
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/source/rendercontext/drawmode \
+ vcl/skia/SkiaHelper \
+ vcl/source/accessibility/AccessibleTextAttributeHelper \
+ vcl/source/animate/Animation \
+ vcl/source/animate/AnimationFrame \
+ vcl/source/animate/AnimationRenderer \
+ vcl/source/cnttype/mcnttfactory \
+ vcl/source/cnttype/mcnttype \
+ vcl/source/printer/Options \
+ vcl/source/printer/QueueInfo \
+ vcl/source/window/bubblewindow \
+ vcl/source/window/errinf \
+ vcl/source/window/settings \
+ vcl/source/window/paint \
+ vcl/source/window/abstdlg \
+ vcl/source/window/accel \
+ vcl/source/window/accmgr \
+ vcl/source/window/brdwin \
+ vcl/source/window/bufferdevice \
+ vcl/source/window/accessibility \
+ vcl/source/window/legacyaccessibility \
+ vcl/source/window/clipping \
+ vcl/source/window/stacking \
+ vcl/source/window/debug \
+ vcl/source/window/globalization \
+ vcl/source/window/builder \
+ vcl/source/window/commandevent \
+ vcl/source/window/cursor \
+ vcl/source/window/debugevent \
+ vcl/source/window/decoview \
+ vcl/source/window/dialog \
+ vcl/source/window/dlgctrl \
+ vcl/source/window/dndeventdispatcher \
+ vcl/source/window/dndlistenercontainer \
+ vcl/source/window/dockingarea \
+ vcl/source/window/dockmgr \
+ vcl/source/window/dockwin \
+ vcl/source/window/DocWindow \
+ vcl/source/window/event \
+ vcl/source/window/floatwin \
+ $(if $(ENABLE_WASM_STRIP_SPLASH),,vcl/source/window/introwin) \
+ vcl/source/window/keycod \
+ vcl/source/window/keyevent \
+ vcl/source/window/layout \
+ vcl/source/window/menu \
+ vcl/source/window/menubarwindow \
+ vcl/source/window/menufloatingwindow \
+ vcl/source/window/menuitemlist \
+ vcl/source/window/menuwindow \
+ vcl/source/window/mnemonic \
+ vcl/source/window/mouse \
+ vcl/source/window/NotebookBarAddonsMerger \
+ vcl/source/window/OptionalBox \
+ vcl/source/window/printdlg \
+ vcl/source/window/scrwnd \
+ vcl/source/window/seleng \
+ vcl/source/window/split \
+ vcl/source/window/splitwin \
+ vcl/source/window/status \
+ vcl/source/window/syschild \
+ vcl/source/window/syswin \
+ vcl/source/window/tabdlg \
+ vcl/source/window/tabpage \
+ vcl/source/window/taskpanelist \
+ vcl/source/window/toolbox2 \
+ vcl/source/window/toolbox \
+ vcl/source/window/window2 \
+ vcl/source/window/window3 \
+ vcl/source/window/window \
+ vcl/source/window/winproc \
+ vcl/source/window/wrkwin \
+ vcl/source/window/EnumContext \
+ vcl/source/control/button \
+ vcl/source/control/calendar \
+ vcl/source/control/combobox \
+ vcl/source/control/ctrl \
+ vcl/source/control/ContextVBox \
+ vcl/source/control/DropdownBox \
+ vcl/source/control/edit \
+ vcl/source/control/field2 \
+ vcl/source/control/field \
+ vcl/source/control/fixed \
+ vcl/source/control/fixedhyper \
+ vcl/source/control/hyperlabel \
+ vcl/source/control/fmtfield \
+ vcl/source/control/InterimItemWindow \
+ vcl/source/control/imgctrl \
+ vcl/source/control/imivctl1 \
+ vcl/source/control/imivctl2 \
+ vcl/source/control/ivctrl \
+ vcl/source/control/longcurr \
+ vcl/source/control/imp_listbox \
+ vcl/source/control/listbox \
+ vcl/source/control/managedmenubutton \
+ vcl/source/control/menubtn \
+ vcl/source/control/NotebookbarPopup \
+ vcl/source/control/PriorityHBox \
+ vcl/source/control/PriorityMergedHBox \
+ vcl/source/control/notebookbar \
+ vcl/source/control/WeldedTabbedNotebookbar \
+ vcl/source/control/quickselectionengine \
+ vcl/source/control/prgsbar \
+ vcl/source/control/roadmap \
+ vcl/source/control/roadmapwizard \
+ vcl/source/control/scrbar \
+ vcl/source/control/slider \
+ vcl/source/control/spinbtn \
+ vcl/source/control/spinfld \
+ vcl/source/control/tabctrl \
+ vcl/source/control/throbber \
+ vcl/source/control/wizardmachine \
+ vcl/source/edit/vclmedit \
+ vcl/source/edit/textdata \
+ vcl/source/edit/textdoc \
+ vcl/source/edit/texteng \
+ vcl/source/edit/textundo \
+ vcl/source/edit/textview \
+ vcl/source/edit/txtattr \
+ vcl/source/edit/xtextedt \
+ vcl/source/toolkit/group \
+ vcl/source/toolkit/morebtn \
+ vcl/source/outdev/background \
+ vcl/source/outdev/eps \
+ vcl/source/outdev/outdev \
+ vcl/source/outdev/stack \
+ vcl/source/outdev/clipping \
+ vcl/source/outdev/fill \
+ vcl/source/outdev/polygon \
+ vcl/source/outdev/transparent \
+ vcl/source/outdev/mask \
+ vcl/source/outdev/bitmap \
+ vcl/source/outdev/bitmapex \
+ vcl/source/outdev/font \
+ vcl/source/outdev/text \
+ vcl/source/outdev/textline \
+ vcl/source/outdev/pixel \
+ vcl/source/outdev/rect \
+ vcl/source/outdev/line \
+ vcl/source/outdev/polyline \
+ vcl/source/outdev/hatch \
+ vcl/source/outdev/gradient \
+ vcl/source/outdev/curvedshapes \
+ vcl/source/outdev/wallpaper \
+ vcl/source/outdev/vclreferencebase \
+ vcl/source/outdev/nativecontrols \
+ vcl/source/outdev/map \
+ vcl/source/text/ImplLayoutArgs \
+ vcl/source/text/TextLayoutCache \
+ vcl/source/text/textlayout \
+ vcl/source/treelist/headbar \
+ vcl/source/treelist/iconview \
+ vcl/source/treelist/iconviewimpl \
+ vcl/source/treelist/imap \
+ vcl/source/treelist/imap2 \
+ vcl/source/treelist/imap3 \
+ vcl/source/treelist/inetimg \
+ vcl/source/treelist/svtabbx \
+ vcl/source/treelist/transfer \
+ vcl/source/treelist/transfer2 \
+ vcl/source/treelist/viewdataentry \
+ vcl/source/treelist/treelist \
+ vcl/source/treelist/treelistbox \
+ vcl/source/treelist/treelistentry \
+ vcl/source/treelist/svimpbox \
+ vcl/source/treelist/svlbitm \
+ vcl/source/treelist/uiobject \
+ vcl/source/text/ImplLayoutRuns \
+ vcl/source/text/mnemonic \
+ vcl/source/gdi/formpdfexport \
+ vcl/source/gdi/configsettings \
+ vcl/source/gdi/cvtgrf \
+ vcl/source/gdi/embeddedfontshelper \
+ vcl/source/gdi/FileDefinitionWidgetDraw \
+ vcl/source/gdi/WidgetDefinitionReader \
+ vcl/source/gdi/WidgetDefinition \
+ vcl/source/gdi/extoutdevdata \
+ vcl/source/gdi/gdimtf \
+ vcl/source/gdi/mtfxmldump \
+ vcl/source/gdi/gdimetafiletools \
+ vcl/source/gdi/gfxlink \
+ vcl/source/gdi/gradient \
+ vcl/source/gdi/graph \
+ vcl/source/gdi/graphictools \
+ vcl/source/gdi/hatch \
+ vcl/source/gdi/impglyphitem \
+ vcl/source/gdi/impgraph \
+ vcl/source/gdi/jobset \
+ vcl/source/gdi/lineinfo \
+ vcl/source/gdi/mapmod \
+ vcl/source/gdi/metaact \
+ vcl/source/gdi/oldprintadaptor \
+ vcl/source/gdi/pdfbuildin_fonts \
+ vcl/source/gdi/pdfextoutdevdata \
+ vcl/source/gdi/pdfwriter \
+ vcl/source/gdi/pdfwriter_impl2 \
+ vcl/source/gdi/pdfwriter_impl \
+ vcl/source/gdi/pdfobjectcopier \
+ vcl/source/gdi/print2 \
+ vcl/source/gdi/print3 \
+ vcl/source/gdi/print \
+ vcl/source/gdi/regband \
+ vcl/source/gdi/region \
+ vcl/source/gdi/regionband \
+ vcl/source/gdi/salgdilayout \
+ vcl/source/gdi/salgdiimpl \
+ vcl/source/gdi/sallayout \
+ vcl/source/gdi/salmisc \
+ vcl/source/gdi/vectorgraphicdata \
+ vcl/source/gdi/virdev \
+ vcl/source/gdi/wall \
+ vcl/source/gdi/scrptrun \
+ vcl/source/gdi/CommonSalLayout \
+ vcl/source/gdi/TypeSerializer \
+ vcl/source/pdf/PdfConfig \
+ vcl/source/pdf/ResourceDict \
+ vcl/source/pdf/Matrix3 \
+ vcl/source/pdf/XmpMetadata \
+ vcl/source/pdf/ExternalPDFStreams \
+ vcl/source/graphic/BinaryDataContainer \
+ vcl/source/graphic/BinaryDataContainerTools \
+ vcl/source/graphic/GraphicID \
+ vcl/source/graphic/GraphicLoader \
+ vcl/source/graphic/GraphicObject \
+ vcl/source/graphic/GraphicObject2 \
+ vcl/source/graphic/GraphicReader \
+ vcl/source/graphic/Manager \
+ vcl/source/graphic/UnoBinaryDataContainer \
+ vcl/source/graphic/UnoGraphic \
+ vcl/source/graphic/UnoGraphicMapper \
+ vcl/source/graphic/UnoGraphicDescriptor \
+ vcl/source/graphic/UnoGraphicObject \
+ vcl/source/graphic/UnoGraphicProvider \
+ vcl/source/graphic/VectorGraphicSearch \
+ vcl/source/graphic/VectorGraphicLoader \
+ vcl/source/bitmap/impvect \
+ vcl/source/bitmap/bitmap \
+ vcl/source/bitmap/bitmappalette \
+ vcl/source/bitmap/BitmapEx \
+ vcl/source/bitmap/BitmapInfoAccess \
+ vcl/source/bitmap/BitmapReadAccess \
+ vcl/source/bitmap/BitmapWriteAccess \
+ vcl/source/bitmap/alpha \
+ vcl/source/bitmap/dibtools \
+ vcl/source/bitmap/bmpfast \
+ vcl/source/bitmap/bitmapfilter \
+ vcl/source/bitmap/bitmappaint \
+ vcl/source/bitmap/BitmapShadowFilter \
+ vcl/source/bitmap/BitmapAlphaClampFilter \
+ vcl/source/bitmap/BitmapBasicMorphologyFilter \
+ vcl/source/bitmap/BitmapMaskToAlphaFilter \
+ vcl/source/bitmap/BitmapMonochromeFilter \
+ vcl/source/bitmap/BitmapSmoothenFilter \
+ vcl/source/bitmap/BitmapLightenFilter \
+ vcl/source/bitmap/BitmapDisabledImageFilter \
+ vcl/source/bitmap/BitmapColorizeFilter \
+ vcl/source/bitmap/BitmapGaussianSeparableBlurFilter \
+ vcl/source/bitmap/BitmapSobelGreyFilter \
+ vcl/source/bitmap/BitmapSolarizeFilter \
+ vcl/source/bitmap/BitmapSepiaFilter \
+ vcl/source/bitmap/BitmapMosaicFilter \
+ vcl/source/bitmap/BitmapEmbossGreyFilter \
+ vcl/source/bitmap/BitmapPopArtFilter \
+ vcl/source/bitmap/BitmapDuoToneFilter \
+ vcl/source/bitmap/BitmapConvolutionMatrixFilter \
+ vcl/source/bitmap/BitmapMedianFilter \
+ vcl/source/bitmap/BitmapInterpolateScaleFilter \
+ vcl/source/bitmap/BitmapSeparableUnsharpenFilter \
+ vcl/source/bitmap/BitmapFastScaleFilter \
+ vcl/source/bitmap/BitmapScaleSuperFilter \
+ vcl/source/bitmap/BitmapScaleConvolutionFilter \
+ vcl/source/bitmap/BitmapSymmetryCheck \
+ vcl/source/bitmap/BitmapColorQuantizationFilter \
+ vcl/source/bitmap/BitmapSimpleColorQuantizationFilter \
+ vcl/source/bitmap/BitmapTools \
+ vcl/source/bitmap/Octree \
+ vcl/source/bitmap/salbmp \
+ vcl/source/image/Image \
+ vcl/source/image/ImageTree \
+ vcl/source/image/ImageRepository \
+ vcl/source/image/ImplImage \
+ vcl/source/image/ImplImageTree \
+ vcl/source/bitmap/BitmapFilterStackBlur \
+ vcl/source/helper/canvasbitmap \
+ vcl/source/helper/canvastools \
+ vcl/source/helper/commandinfoprovider \
+ vcl/source/helper/displayconnectiondispatch \
+ vcl/source/helper/driverblocklist \
+ vcl/source/helper/evntpost \
+ vcl/source/helper/idletask \
+ vcl/source/helper/lazydelete \
+ vcl/source/helper/strhelper \
+ vcl/source/helper/svtaccessiblefactory \
+ vcl/source/helper/threadex \
+ vcl/source/app/brand \
+ vcl/source/app/customweld \
+ vcl/source/app/dbggui \
+ vcl/source/app/dndhelp \
+ vcl/source/app/help \
+ vcl/source/app/i18nhelp \
+ vcl/source/app/idle \
+ vcl/source/app/salplug \
+ vcl/source/app/salusereventlist \
+ vcl/source/app/salvtables \
+ vcl/source/app/scheduler \
+ vcl/source/app/session \
+ vcl/source/app/settings \
+ vcl/source/app/IconThemeInfo \
+ vcl/source/app/IconThemeScanner \
+ vcl/source/app/IconThemeSelector \
+ vcl/source/app/ITiledRenderable \
+ vcl/source/app/sound \
+ vcl/source/app/stdtext \
+ vcl/source/app/svapp \
+ vcl/source/app/svdata \
+ vcl/source/app/svmain \
+ vcl/source/app/timer \
+ vcl/source/app/unohelp2 \
+ vcl/source/app/htmltransferable \
+ vcl/source/app/unohelp \
+ vcl/source/app/vclevent \
+ vcl/source/app/watchdog \
+ vcl/source/app/weldutils \
+ vcl/source/app/winscheduler \
+ vcl/source/components/dtranscomp \
+ vcl/source/components/factory \
+ vcl/source/components/fontident \
+ vcl/source/filter/bmp/BmpReader \
+ vcl/source/filter/bmp/BmpWriter \
+ vcl/source/filter/egif/egif \
+ vcl/source/filter/egif/giflzwc \
+ vcl/source/filter/eps/eps \
+ vcl/source/filter/etiff/etiff \
+ vcl/source/filter/FilterConfigCache \
+ vcl/source/filter/FilterConfigItem \
+ vcl/source/filter/graphicfilter \
+ vcl/source/filter/graphicfilter2 \
+ vcl/source/filter/GraphicNativeTransform \
+ vcl/source/filter/GraphicNativeMetadata \
+ vcl/source/filter/GraphicFormatDetector \
+ vcl/source/filter/idxf/dxf2mtf \
+ vcl/source/filter/idxf/dxfblkrd \
+ vcl/source/filter/idxf/dxfentrd \
+ vcl/source/filter/idxf/dxfgrprd \
+ vcl/source/filter/idxf/dxfreprd \
+ vcl/source/filter/idxf/dxftblrd \
+ vcl/source/filter/idxf/dxfvec \
+ vcl/source/filter/idxf/idxf \
+ vcl/source/filter/ieps/ieps \
+ vcl/source/filter/igif/decode \
+ vcl/source/filter/igif/gifread \
+ vcl/source/filter/imet/ios2met \
+ vcl/source/filter/ipbm/ipbm \
+ vcl/source/filter/ipcd/ipcd \
+ vcl/source/filter/ipcx/ipcx \
+ vcl/source/filter/ipict/ipict \
+ vcl/source/filter/ipsd/ipsd \
+ vcl/source/filter/ipict/shape \
+ vcl/source/filter/ipdf/pdfcompat \
+ vcl/source/filter/ipdf/pdfread \
+ vcl/source/filter/ipdf/pdfdocument \
+ vcl/source/filter/iras/iras \
+ vcl/source/filter/itga/itga \
+ vcl/source/filter/itiff/itiff \
+ vcl/source/filter/ixbm/xbmread \
+ vcl/source/filter/ixpm/xpmread \
+ vcl/source/filter/jpeg/Exif \
+ vcl/source/filter/jpeg/jpeg \
+ vcl/source/filter/jpeg/jpegc \
+ vcl/source/filter/jpeg/JpegReader \
+ vcl/source/filter/jpeg/JpegWriter \
+ vcl/source/filter/jpeg/JpegTransform \
+ vcl/source/filter/svm/SvmConverter \
+ vcl/source/filter/svm/SvmReader \
+ vcl/source/filter/svm/SvmWriter \
+ vcl/source/filter/wmf/emfwr \
+ vcl/source/filter/wmf/wmf \
+ vcl/source/filter/wmf/wmfexternal \
+ vcl/source/filter/wmf/wmfwr \
+ vcl/source/filter/png/PngImageReader \
+ vcl/source/filter/png/PngImageWriter \
+ vcl/source/filter/webp/reader \
+ vcl/source/filter/webp/writer \
+ vcl/source/font/DirectFontSubstitution \
+ vcl/source/font/EmphasisMark \
+ vcl/source/font/Feature \
+ vcl/source/font/FeatureCollector \
+ vcl/source/font/FeatureParser \
+ vcl/source/font/FontSelectPattern \
+ vcl/source/font/LogicalFontInstance \
+ vcl/source/font/OpenTypeFeatureDefinitionList \
+ vcl/source/font/PhysicalFontCollection \
+ vcl/source/font/PhysicalFontFace \
+ vcl/source/font/PhysicalFontFamily \
+ vcl/source/font/fontattributes \
+ vcl/source/font/fontcache \
+ vcl/source/font/fontcharmap \
+ vcl/source/font/fontmetric \
+ vcl/source/font/font \
+ vcl/source/fontsubset/cff \
+ vcl/source/fontsubset/fontsubset \
+ vcl/source/fontsubset/sft \
+ vcl/source/fontsubset/ttcr \
+ vcl/source/fontsubset/xlat \
+ vcl/source/pdf/PDFiumTools \
+ vcl/source/uitest/logger \
+ vcl/source/uitest/uiobject \
+ vcl/source/uitest/uitest \
+ vcl/source/uitest/uno/uiobject_uno \
+ vcl/source/uitest/uno/uitest_uno \
+ vcl/backendtest/outputdevice/bitmap \
+ vcl/backendtest/outputdevice/clip \
+ vcl/backendtest/outputdevice/common \
+ vcl/backendtest/outputdevice/gradient \
+ vcl/backendtest/outputdevice/line \
+ vcl/backendtest/outputdevice/outputdevice \
+ vcl/backendtest/outputdevice/pixel \
+ vcl/backendtest/outputdevice/polygon \
+ vcl/backendtest/outputdevice/polypolygon \
+ vcl/backendtest/outputdevice/polypolygon_b2d \
+ vcl/backendtest/outputdevice/polyline \
+ vcl/backendtest/outputdevice/polyline_b2d \
+ vcl/backendtest/outputdevice/text \
+ vcl/backendtest/outputdevice/rectangle \
+ vcl/backendtest/GraphicsRenderTests \
+ vcl/jsdialog/enabled \
+ vcl/jsdialog/jsdialogbuilder \
+ vcl/jsdialog/executor \
+))
+
+$(eval $(call gb_Library_add_cobjects,vcl,\
+ vcl/source/filter/jpeg/transupp \
+))
+
+vcl_headless_code= \
+ vcl/headless/svpframe \
+ $(if $(filter-out iOS,$(OS)), \
+ vcl/headless/svpbmp \
+ vcl/headless/svpgdi \
+ vcl/headless/SvpGraphicsBackend \
+ vcl/headless/CairoCommon \
+ vcl/headless/BitmapHelper \
+ ) \
+ vcl/headless/svpdummies \
+ vcl/headless/svpinst \
+ vcl/headless/svpvd \
+ vcl/unx/generic/app/gendisp \
+ vcl/unx/generic/app/geninst \
+ vcl/unx/generic/app/gensys \
+
+vcl_headless_freetype_code=\
+ vcl/headless/svpprn \
+ vcl/headless/svptext \
+ vcl/unx/generic/app/gendata \
+ vcl/unx/generic/gdi/cairotextrender \
+ vcl/unx/generic/gdi/freetypetextrender \
+ vcl/unx/generic/glyphs/freetype_glyphcache \
+ vcl/unx/generic/glyphs/glyphcache \
+ vcl/unx/generic/fontmanager/fontsubst \
+ vcl/unx/generic/fontmanager/fontconfig \
+ vcl/unx/generic/fontmanager/fontmanager \
+ vcl/unx/generic/fontmanager/helper \
+ vcl/unx/generic/print/genpspgraphics \
+ vcl/unx/generic/print/genprnpsp \
+ vcl/unx/generic/print/prtsetup \
+ vcl/unx/generic/printer/jobdata \
+ vcl/unx/generic/printer/ppdparser \
+
+ifeq ($(SYSTEM_LIBFIXMATH),TRUE)
+$(eval $(call gb_Library_add_libs,vcl,\
+ -llibfixmath \
+))
+endif
+
+ifeq ($(USING_X11),TRUE)
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/unx/generic/window/sessioninhibitor \
+ vcl/unx/generic/printer/cpdmgr \
+))
+
+$(eval $(call gb_Library_use_externals,vcl,\
+ dbus \
+ valgrind \
+))
+
+$(eval $(call gb_Library_add_libs,vcl,\
+ -lX11 \
+ -lXext \
+))
+endif # USING_X11
+
+
+ifeq ($(DISABLE_GUI),TRUE)
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/headless/headlessinst \
+))
+
+else # !DISABLE_GUI
+
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/source/opengl/OpenGLContext \
+ vcl/source/opengl/OpenGLHelper \
+ $(if $(filter SKIA,$(BUILD_TYPE)), \
+ vcl/skia/salbmp \
+ vcl/skia/zone \
+ vcl/skia/gdiimpl \
+ ) \
+))
+
+$(eval $(call gb_Library_use_externals,vcl,\
+ epoxy \
+ $(if $(filter SKIA,$(BUILD_TYPE)),skia) \
+))
+endif # !DISABLE_GUI
+
+
+#
+# * plugin loader: used on all platforms except iOS and Android
+# * select headless code and corresponding libraries
+#
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ $(if $(USE_HEADLESS_CODE), \
+ $(if $(ENABLE_CUPS), \
+ vcl/unx/generic/printer/cupsmgr \
+ vcl/unx/generic/printer/printerinfomanager \
+ , \
+ vcl/null/printerinfomanager \
+ ) \
+ $(vcl_headless_code) \
+ $(vcl_headless_freetype_code) \
+ ) \
+ vcl/source/pdf/$(if $(filter PDFIUM,$(BUILD_TYPE)),,Dummy)PDFiumLibrary \
+))
+
+# fontconfig depends on expat for static builds
+$(eval $(call gb_Library_use_externals,vcl,\
+ $(if $(USE_HEADLESS_CODE), \
+ cairo \
+ $(if $(ENABLE_CUPS),cups) \
+ fontconfig \
+ freetype \
+ ) \
+ $(if $(filter PDFIUM,$(BUILD_TYPE)),pdfium) \
+))
+
+$(eval $(call gb_Library_add_libs,vcl,\
+ $(if $(filter LINUX %BSD SOLARIS,$(OS)), \
+ -lm \
+ $(if $(DISABLE_DYNLOADING),,$(UNIX_DLAPI_LIBS)) \
+ ) \
+))
+
+
+#
+# OS specific stuff not handled yet
+#
+
+ifeq ($(OS),HAIKU)
+$(eval $(call gb_Library_add_libs,vcl,\
+ -lbe \
+))
+endif
+
+
+ifeq ($(OS),ANDROID)
+$(eval $(call gb_Library_add_libs,vcl,\
+ -llog \
+ -landroid \
+ -llo-bootstrap \
+))
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/android/androidinst \
+))
+endif
+
+
+ifeq ($(OS),iOS)
+$(eval $(call gb_Library_add_cxxflags,vcl,\
+ $(gb_OBJCXXFLAGS) \
+))
+$(eval $(call gb_Library_add_objcxxobjects,vcl,\
+ vcl/quartz/cgutils \
+))
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/ios/iosinst \
+ vcl/ios/dummies \
+ vcl/ios/clipboard \
+ vcl/ios/salios \
+ vcl/ios/iOSTransferable \
+ vcl/ios/DataFlavorMapping \
+ vcl/ios/HtmlFmtFlt \
+ vcl/quartz/CoreTextFont \
+ vcl/quartz/CoreTextFontFace \
+ vcl/quartz/SystemFontList \
+ vcl/quartz/salbmp \
+ vcl/quartz/salgdi \
+ vcl/quartz/salgdicommon \
+ vcl/quartz/salvd \
+ vcl/quartz/utils \
+ vcl/quartz/AquaGraphicsBackend \
+ $(vcl_headless_code) \
+ vcl/unx/generic/app/gendata \
+))
+$(eval $(call gb_Library_use_system_darwin_frameworks,vcl,\
+ UIKit \
+ CoreFoundation \
+))
+endif
+
+
+ifeq ($(OS),MACOSX)
+$(eval $(call gb_Library_add_objcxxobjects,vcl,\
+ vcl/quartz/cgutils \
+ $(if $(filter SKIA,$(BUILD_TYPE)), \
+ vcl/skia/quartz/salbmp \
+ ) \
+))
+$(eval $(call gb_Library_use_system_darwin_frameworks,vcl,\
+ Cocoa \
+ CoreFoundation \
+ Metal \
+))
+endif
+
+
+ifeq ($(OS),WNT)
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+ vcl/source/opengl/win/WinDeviceInfo \
+ vcl/win/app/fileregistration \
+))
+
+$(eval $(call gb_Library_use_system_win32_libs,vcl,\
+ ole32 \
+ setupapi \
+ version \
+))
+
+$(eval $(call gb_Library_add_nativeres,vcl,vcl/salsrc))
+
+# HACK: dependency on icon themes so running unit tests don't
+# prevent delivering these by having open file handles on WNT
+$(eval $(call gb_Library_use_packages,vcl, \
+ vcl_opengl_denylist \
+ $(if $(filter host,$(gb_Side)),postprocess_images) \
+))
+endif # WNT
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Library_vclplug_gen.mk b/vcl/Library_vclplug_gen.mk
new file mode 100644
index 0000000000..2d314017bb
--- /dev/null
+++ b/vcl/Library_vclplug_gen.mk
@@ -0,0 +1,154 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Library_Library,vclplug_gen))
+
+$(eval $(call gb_Library_set_plugin_for,vclplug_gen,vcl))
+
+$(eval $(call gb_Library_set_include,vclplug_gen,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Library_use_custom_headers,vclplug_gen,\
+ officecfg/registry \
+))
+
+$(eval $(call gb_Library_use_sdk_api,vclplug_gen))
+
+$(eval $(call gb_Library_use_common_precompiled_header,vclplug_gen))
+
+$(eval $(call gb_Library_use_libraries,vclplug_gen,\
+ tl \
+ utl \
+ sot \
+ ucbhelper \
+ basegfx \
+ comphelper \
+ cppuhelper \
+ i18nlangtag \
+ i18nutil \
+ $(if $(ENABLE_JAVA), \
+ jvmaccess) \
+ cppu \
+ sal \
+))
+
+$(eval $(call gb_Library_use_externals,vclplug_gen,\
+ boost_headers \
+ cairo \
+ graphite \
+ epoxy \
+ expat \
+ harfbuzz \
+ icu_headers \
+ icuuc \
+ valgrind \
+ Xrender \
+ $(if $(filter SKIA,$(BUILD_TYPE)), \
+ skia \
+ fontconfig \
+ ) \
+))
+
+$(eval $(call gb_Library_add_libs,vclplug_gen,\
+ -lX11 \
+ -lXext \
+ -lSM \
+ -lICE \
+))
+
+$(eval $(call gb_Library_add_exception_objects,vclplug_gen,\
+ vcl/unx/generic/app/i18n_cb \
+ vcl/unx/generic/app/i18n_ic \
+ vcl/unx/generic/app/i18n_im \
+ vcl/unx/generic/app/i18n_keysym \
+ vcl/unx/generic/app/i18n_xkb \
+ vcl/unx/generic/app/keysymnames \
+ vcl/unx/generic/app/randrwrapper \
+ vcl/unx/generic/app/saldata \
+ vcl/unx/generic/app/saldisp \
+ vcl/unx/generic/app/salinst \
+ vcl/unx/generic/app/saltimer \
+ vcl/unx/generic/app/sm \
+ vcl/unx/generic/app/wmadaptor \
+ vcl/unx/generic/dtrans/bmp \
+ vcl/unx/generic/dtrans/config \
+ vcl/unx/generic/dtrans/X11_clipboard \
+ vcl/unx/generic/dtrans/X11_dndcontext \
+ vcl/unx/generic/dtrans/X11_droptarget \
+ vcl/unx/generic/dtrans/X11_selection \
+ vcl/unx/generic/dtrans/X11_service \
+ vcl/unx/generic/dtrans/X11_transferable \
+ vcl/unx/generic/gdi/cairo_xlib_cairo \
+ vcl/unx/generic/gdi/X11CairoSalGraphicsImpl \
+ vcl/unx/generic/gdi/font \
+ vcl/unx/generic/gdi/salgdi \
+ vcl/unx/generic/gdi/salvd \
+ vcl/unx/generic/window/salframe \
+ vcl/unx/generic/window/salobj \
+ vcl/unx/x11/x11sys \
+ vcl/unx/x11/xlimits \
+ vcl/source/opengl/x11/context \
+ $(if $(filter SKIA,$(BUILD_TYPE)), \
+ vcl/skia/x11/gdiimpl \
+ vcl/skia/x11/salvd \
+ vcl/skia/x11/textrender \
+ ) \
+))
+
+# ultimately we want to split the x11 dependencies out
+# into their own library I think.
+
+$(eval $(call gb_Library_add_defs,vclplug_gen,\
+ -DVCLPLUG_GEN_IMPLEMENTATION \
+ -DVCL_INTERNALS \
+))
+
+## handle RandR
+ifneq ($(ENABLE_RANDR),)
+$(eval $(call gb_Library_use_externals,vclplug_gen,\
+ Xrandr \
+))
+$(eval $(call gb_Library_add_defs,vclplug_gen,\
+ -DUSE_RANDR \
+))
+endif
+
+## handle Xinerama
+ifneq ($(USING_X11),)
+ifeq ($(XINERAMA_LINK),dynamic)
+$(eval $(call gb_Library_add_libs,vclplug_gen,\
+ -lXinerama \
+))
+else
+$(eval $(call gb_Library_add_libs,vclplug_gen,\
+ -Wl$(COMMA)-Bstatic -lXinerama -Wl$(COMMA)-Bdynamic \
+))
+endif
+endif # USING_X11
+
+ifeq ($(OS),LINUX)
+$(eval $(call gb_Library_add_libs,vclplug_gen,\
+ -lm \
+ -ldl \
+))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Library_vclplug_gtk3.mk b/vcl/Library_vclplug_gtk3.mk
new file mode 100644
index 0000000000..0809edd770
--- /dev/null
+++ b/vcl/Library_vclplug_gtk3.mk
@@ -0,0 +1,126 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Library_Library,vclplug_gtk3))
+
+$(eval $(call gb_Library_set_plugin_for,vclplug_gtk3,vcl))
+
+# Silence deprecation warnings wholesale as long as vcl/unx/gtk3/*.cxx just
+# forward to vcl/unx/gtk/*.cxx:
+$(eval $(call gb_Library_add_cxxflags,vclplug_gtk3, \
+ -Wno-deprecated-declarations \
+))
+
+$(eval $(call gb_Library_set_include,vclplug_gtk3,\
+ $$(INCLUDE) \
+ $$(GTK3_CFLAGS) \
+ $$(GSTREAMER_1_0_CFLAGS) \
+ -I$(SRCDIR)/vcl/inc \
+ -I$(SRCDIR)/vcl/unx \
+ -I$(SRCDIR)/vcl/unx/gtk3 \
+))
+
+$(eval $(call gb_Library_add_defs,vclplug_gtk3,\
+ -DVCLPLUG_GTK_IMPLEMENTATION \
+ -DVCL_INTERNALS \
+))
+
+$(eval $(call gb_Library_use_custom_headers,vclplug_gtk3,\
+ officecfg/registry \
+))
+
+$(eval $(call gb_Library_use_sdk_api,vclplug_gtk3))
+
+$(eval $(call gb_Library_add_libs,vclplug_gtk3,\
+ $(GTK3_LIBS) \
+ -lX11 \
+ -lXext \
+ -lSM \
+ -lICE \
+))
+
+$(eval $(call gb_Library_use_libraries,vclplug_gtk3,\
+ svl \
+ tl \
+ utl \
+ sot \
+ ucbhelper \
+ basegfx \
+ comphelper \
+ cppuhelper \
+ i18nlangtag \
+ i18nutil \
+ $(if $(ENABLE_JAVA), \
+ jvmaccess) \
+ cppu \
+ sal \
+))
+
+$(eval $(call gb_Library_use_externals,vclplug_gtk3,\
+ boost_headers \
+ epoxy \
+ dbus \
+ graphite \
+ harfbuzz \
+))
+
+$(eval $(call gb_Library_add_exception_objects,vclplug_gtk3,\
+ vcl/unx/gtk3/a11y/atkaction \
+ vcl/unx/gtk3/a11y/atkbridge \
+ vcl/unx/gtk3/a11y/atkcomponent \
+ vcl/unx/gtk3/a11y/atkeditabletext \
+ vcl/unx/gtk3/a11y/atkfactory \
+ vcl/unx/gtk3/a11y/atkhypertext \
+ vcl/unx/gtk3/a11y/atkimage \
+ vcl/unx/gtk3/a11y/atklistener \
+ vcl/unx/gtk3/a11y/atkregistry \
+ vcl/unx/gtk3/a11y/atkselection \
+ vcl/unx/gtk3/a11y/atktable \
+ vcl/unx/gtk3/a11y/atktablecell \
+ vcl/unx/gtk3/a11y/atktextattributes \
+ vcl/unx/gtk3/a11y/atktext \
+ vcl/unx/gtk3/a11y/atkutil \
+ vcl/unx/gtk3/a11y/atkvalue \
+ vcl/unx/gtk3/a11y/atkwrapper \
+ vcl/unx/gtk3/fpicker/resourceprovider \
+ vcl/unx/gtk3/fpicker/SalGtkFilePicker \
+ vcl/unx/gtk3/fpicker/SalGtkFolderPicker \
+ vcl/unx/gtk3/fpicker/SalGtkPicker \
+ vcl/unx/gtk3/customcellrenderer \
+ vcl/unx/gtk3/gtkdata \
+ vcl/unx/gtk3/gtkinst \
+ vcl/unx/gtk3/gtksys \
+ vcl/unx/gtk3/gtkcairo \
+ vcl/unx/gtk3/salnativewidgets-gtk \
+ vcl/unx/gtk3/gtkframe \
+ vcl/unx/gtk3/gtkobject \
+ vcl/unx/gtk3/gtksalmenu \
+ vcl/unx/gtk3/glomenu \
+ vcl/unx/gtk3/gloactiongroup \
+ vcl/unx/gtk3/hudawareness \
+))
+
+ifeq ($(OS),LINUX)
+$(eval $(call gb_Library_add_libs,vclplug_gtk3,\
+ -lm \
+ -ldl \
+))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Library_vclplug_gtk3_kde5.mk b/vcl/Library_vclplug_gtk3_kde5.mk
new file mode 100644
index 0000000000..853ec7aafa
--- /dev/null
+++ b/vcl/Library_vclplug_gtk3_kde5.mk
@@ -0,0 +1,131 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Library_Library,vclplug_gtk3_kde5))
+
+$(eval $(call gb_Library_set_plugin_for,vclplug_gtk3_kde5,vcl))
+
+# Silence deprecation warnings wholesale as long as vcl/unx/gtk3/*.cxx just
+# forward to vcl/unx/gtk/*.cxx:
+$(eval $(call gb_Library_add_cxxflags,vclplug_gtk3_kde5, \
+ -Wno-deprecated-declarations \
+))
+
+$(eval $(call gb_Library_set_include,vclplug_gtk3_kde5,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+ -I$(SRCDIR)/vcl/unx \
+ -I$(SRCDIR)/vcl/unx/gtk3 \
+))
+
+$(eval $(call gb_Library_add_cxxflags,vclplug_gtk3_kde5,\
+ $$(INCLUDE) \
+ $$(GTK3_CFLAGS) \
+ $$(GSTREAMER_1_0_CFLAGS) \
+))
+
+$(eval $(call gb_Library_add_defs,vclplug_gtk3_kde5,\
+ -DVCLPLUG_GTK_IMPLEMENTATION -DVCLPLUG_GTK3_KDE5_IMPLEMENTATION \
+ -DVCL_INTERNALS \
+))
+
+$(eval $(call gb_Library_use_custom_headers,vclplug_gtk3_kde5,\
+ officecfg/registry \
+))
+
+$(eval $(call gb_Library_use_sdk_api,vclplug_gtk3_kde5))
+
+$(eval $(call gb_Library_add_libs,vclplug_gtk3_kde5,\
+ $(GTK3_LIBS) \
+ -lX11 \
+ -lXext \
+ -lSM \
+ -lICE \
+))
+
+$(eval $(call gb_Library_use_libraries,vclplug_gtk3_kde5,\
+ svl \
+ tl \
+ utl \
+ sot \
+ ucbhelper \
+ basegfx \
+ comphelper \
+ cppuhelper \
+ i18nlangtag \
+ i18nutil \
+ $(if $(ENABLE_JAVA), \
+ jvmaccess) \
+ cppu \
+ sal \
+))
+
+$(eval $(call gb_Library_use_externals,vclplug_gtk3_kde5,\
+ boost_headers \
+ boost_filesystem \
+ epoxy \
+ dbus \
+ graphite \
+ harfbuzz \
+ kf5 \
+))
+
+$(eval $(call gb_Library_add_exception_objects,vclplug_gtk3_kde5,\
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkaction \
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkbridge \
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkcomponent \
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkeditabletext \
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkfactory \
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkhypertext \
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkimage \
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atklistener \
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkregistry \
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkselection \
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktable \
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktablecell \
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktextattributes \
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktext \
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkutil \
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkvalue \
+ vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwrapper \
+ vcl/unx/gtk3_kde5/gtk3_kde5_customcellrenderer \
+ vcl/unx/gtk3_kde5/gtk3_kde5_gtkdata \
+ vcl/unx/gtk3_kde5/gtk3_kde5_gtkinst \
+ vcl/unx/gtk3_kde5/gtk3_kde5_gtksys \
+ vcl/unx/gtk3_kde5/gtk3_kde5_filepicker \
+ vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc \
+ vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker \
+ vcl/unx/gtk3_kde5/gtk3_kde5_cairo \
+ vcl/unx/gtk3_kde5/gtk3_kde5_salnativewidgets-gtk \
+ vcl/unx/gtk3_kde5/gtk3_kde5_gtkframe \
+ vcl/unx/gtk3_kde5/gtk3_kde5_gtkobject \
+ vcl/unx/gtk3_kde5/gtk3_kde5_gtksalmenu \
+ vcl/unx/gtk3_kde5/gtk3_kde5_glomenu \
+ vcl/unx/gtk3_kde5/gtk3_kde5_gloactiongroup \
+ vcl/unx/gtk3_kde5/gtk3_kde5_hudawareness \
+))
+
+ifeq ($(OS),LINUX)
+$(eval $(call gb_Library_add_libs,vclplug_gtk3_kde5,\
+ -lm \
+ -ldl \
+))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Library_vclplug_gtk4.mk b/vcl/Library_vclplug_gtk4.mk
new file mode 100644
index 0000000000..bb9d82ad57
--- /dev/null
+++ b/vcl/Library_vclplug_gtk4.mk
@@ -0,0 +1,115 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Library_Library,vclplug_gtk4))
+
+$(eval $(call gb_Library_set_plugin_for,vclplug_gtk4,vcl))
+
+# Silence deprecation warnings wholesale as long as vcl/unx/gtk4/*.cxx just
+# forward to vcl/unx/gtk/*.cxx:
+$(eval $(call gb_Library_add_cxxflags,vclplug_gtk4, \
+ -Wno-deprecated-declarations \
+))
+
+$(eval $(call gb_Library_set_include,vclplug_gtk4,\
+ $$(INCLUDE) \
+ $$(GTK4_CFLAGS) \
+ $$(GSTREAMER_1_0_CFLAGS) \
+ -I$(SRCDIR)/vcl/inc \
+ -I$(SRCDIR)/vcl/unx \
+ -I$(SRCDIR)/vcl/unx/gtk4 \
+))
+
+$(eval $(call gb_Library_add_defs,vclplug_gtk4,\
+ -DVCLPLUG_GTK_IMPLEMENTATION \
+ -DVCL_INTERNALS \
+))
+
+$(eval $(call gb_Library_use_custom_headers,vclplug_gtk4,\
+ officecfg/registry \
+))
+
+$(eval $(call gb_Library_use_sdk_api,vclplug_gtk4))
+
+$(eval $(call gb_Library_add_libs,vclplug_gtk4,\
+ $(GTK4_LIBS) \
+ -lX11 \
+ -lXext \
+ -lSM \
+ -lICE \
+))
+
+$(eval $(call gb_Library_use_libraries,vclplug_gtk4,\
+ svl \
+ tl \
+ utl \
+ sot \
+ ucbhelper \
+ basegfx \
+ comphelper \
+ cppuhelper \
+ i18nlangtag \
+ i18nutil \
+ $(if $(ENABLE_JAVA), \
+ jvmaccess) \
+ cppu \
+ sal \
+))
+
+$(eval $(call gb_Library_use_externals,vclplug_gtk4,\
+ boost_headers \
+ epoxy \
+ dbus \
+ graphite \
+ harfbuzz \
+))
+
+$(eval $(call gb_Library_add_exception_objects,vclplug_gtk4,\
+ vcl/unx/gtk4/fpicker/resourceprovider \
+ vcl/unx/gtk4/fpicker/SalGtkFilePicker \
+ vcl/unx/gtk4/fpicker/SalGtkFolderPicker \
+ vcl/unx/gtk4/fpicker/SalGtkPicker \
+ vcl/unx/gtk4/a11y \
+ vcl/unx/gtk4/convert3to4 \
+ vcl/unx/gtk4/customcellrenderer \
+ vcl/unx/gtk4/gtkdata \
+ vcl/unx/gtk4/gtkinst \
+ vcl/unx/gtk4/gtksys \
+ vcl/unx/gtk4/gtkcairo \
+ vcl/unx/gtk4/salnativewidgets-gtk \
+ vcl/unx/gtk4/gtkframe \
+ vcl/unx/gtk4/gtkobject \
+ vcl/unx/gtk4/gtksalmenu \
+ vcl/unx/gtk4/glomenu \
+ vcl/unx/gtk4/gloactiongroup \
+ vcl/unx/gtk4/hudawareness \
+ vcl/unx/gtk4/notifyinglayout \
+ vcl/unx/gtk4/surfacecellrenderer \
+ vcl/unx/gtk4/surfacepaintable \
+ vcl/unx/gtk4/transferableprovider \
+))
+
+ifeq ($(OS),LINUX)
+$(eval $(call gb_Library_add_libs,vclplug_gtk4,\
+ -lm \
+ -ldl \
+))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Library_vclplug_kf5.mk b/vcl/Library_vclplug_kf5.mk
new file mode 100644
index 0000000000..4e28be29a3
--- /dev/null
+++ b/vcl/Library_vclplug_kf5.mk
@@ -0,0 +1,85 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Library_Library,vclplug_kf5))
+
+$(eval $(call gb_Library_set_plugin_for,vclplug_kf5,vcl))
+
+$(eval $(call gb_Library_use_custom_headers,vclplug_kf5,vcl/unx/kf5))
+
+$(eval $(call gb_Library_set_include,vclplug_kf5,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+ -I$(SRCDIR)/vcl/inc/qt5 \
+))
+
+$(eval $(call gb_Library_add_defs,vclplug_kf5,\
+ -DVCLPLUG_KF_IMPLEMENTATION \
+))
+
+$(eval $(call gb_Library_use_sdk_api,vclplug_kf5))
+
+$(eval $(call gb_Library_use_libraries,vclplug_kf5,\
+ vclplug_qt5 \
+ tl \
+ utl \
+ sot \
+ ucbhelper \
+ basegfx \
+ comphelper \
+ cppuhelper \
+ i18nlangtag \
+ i18nutil \
+ $(if $(ENABLE_JAVA), \
+ jvmaccess) \
+ cppu \
+ sal \
+))
+
+$(eval $(call gb_Library_use_externals,vclplug_kf5,\
+ boost_headers \
+ cairo \
+ graphite \
+ harfbuzz \
+ icuuc \
+ kf5 \
+ epoxy \
+))
+
+$(eval $(call gb_Library_add_exception_objects,vclplug_kf5,\
+ vcl/unx/kf5/KFFilePicker \
+ vcl/unx/kf5/KFSalInstance \
+))
+
+ifeq ($(OS),LINUX)
+$(eval $(call gb_Library_add_libs,vclplug_kf5,\
+ -lm \
+ -ldl \
+))
+endif
+
+# Workaround for clang+icecream (clang's -frewrite-includes
+# doesn't handle Qt5's QT_HAS_INCLUDE that Qt5 uses for <chrono>).
+ifeq ($(COM_IS_CLANG),TRUE)
+$(eval $(call gb_Library_add_cxxflags,vclplug_kf5, \
+ -include chrono \
+))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Library_vclplug_kf6.mk b/vcl/Library_vclplug_kf6.mk
new file mode 100644
index 0000000000..1e7ece0703
--- /dev/null
+++ b/vcl/Library_vclplug_kf6.mk
@@ -0,0 +1,86 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Library_Library,vclplug_kf6))
+
+$(eval $(call gb_Library_set_plugin_for,vclplug_kf6,vcl))
+
+$(eval $(call gb_Library_use_custom_headers,vclplug_kf6,vcl/unx/kf6))
+
+$(eval $(call gb_Library_set_include,vclplug_kf6,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+ -I$(SRCDIR)/vcl/inc/qt5 \
+))
+
+$(eval $(call gb_Library_add_defs,vclplug_kf6,\
+ -DVCLPLUG_KF_IMPLEMENTATION \
+))
+
+$(eval $(call gb_Library_use_sdk_api,vclplug_kf6))
+
+$(eval $(call gb_Library_use_libraries,vclplug_kf6,\
+ vclplug_qt6 \
+ tl \
+ utl \
+ sot \
+ ucbhelper \
+ basegfx \
+ comphelper \
+ cppuhelper \
+ i18nlangtag \
+ i18nutil \
+ $(if $(ENABLE_JAVA), \
+ jvmaccess) \
+ cppu \
+ sal \
+))
+
+$(eval $(call gb_Library_use_externals,vclplug_kf6,\
+ boost_headers \
+ cairo \
+ graphite \
+ harfbuzz \
+ icuuc \
+ kf6 \
+ epoxy \
+))
+
+$(eval $(call gb_Library_add_exception_objects,vclplug_kf6,\
+ vcl/unx/kf6/KFFilePicker \
+ vcl/unx/kf6/KFSalInstance \
+))
+
+ifeq ($(OS),LINUX)
+$(eval $(call gb_Library_add_libs,vclplug_kf6,\
+ -lm \
+ -ldl \
+))
+endif
+
+# Workaround for clang+icecream (clang's -frewrite-includes
+# doesn't handle Qt5's QT_HAS_INCLUDE that Qt5 uses for <chrono>),
+# and probably the same is true for Qt6.
+ifeq ($(COM_IS_CLANG),TRUE)
+$(eval $(call gb_Library_add_cxxflags,vclplug_kf6, \
+ -include chrono \
+))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Library_vclplug_osx.mk b/vcl/Library_vclplug_osx.mk
new file mode 100644
index 0000000000..0545af8dd3
--- /dev/null
+++ b/vcl/Library_vclplug_osx.mk
@@ -0,0 +1,165 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Library_Library,vclplug_osx))
+
+$(eval $(call gb_Library_set_plugin_for,vclplug_osx,vcl))
+
+$(eval $(call gb_Library_set_include,vclplug_osx,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Library_use_sdk_api,vclplug_osx))
+
+$(eval $(call gb_Library_use_custom_headers,vclplug_osx,\
+ officecfg/registry \
+))
+
+# TODO: arguably the private CoreUI framework should never be used, no matter whether building
+# a sandboxed version or a "regular" desktop version
+$(eval $(call gb_Library_add_libs,vclplug_osx,\
+ -framework IOKit \
+ $(if $(ENABLE_MACOSX_SANDBOX),,\
+ -F/System/Library/PrivateFrameworks \
+ -framework CoreUI \
+ ) \
+ -lobjc \
+))
+
+$(eval $(call gb_Library_add_cxxflags,vclplug_osx,\
+ $(gb_OBJCXXFLAGS) \
+))
+
+$(eval $(call gb_Library_use_libraries,vclplug_osx,\
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ i18nlangtag \
+ i18nutil \
+ sal \
+ salhelper \
+ tl \
+))
+
+$(eval $(call gb_Library_use_externals,vclplug_osx,\
+ boost_headers \
+ epoxy \
+ harfbuzz \
+ $(if $(filter SKIA,$(BUILD_TYPE)), \
+ skia \
+ ) \
+))
+
+$(eval $(call gb_Library_add_defs,vclplug_osx,\
+ -DMACOSX_BUNDLE_IDENTIFIER=\"$(MACOSX_BUNDLE_IDENTIFIER)\" \
+ -DVCL_INTERNALS \
+))
+
+$(eval $(call gb_Library_add_objcxxobjects,vclplug_osx,\
+ vcl/osx/a11yactionwrapper \
+ vcl/osx/a11ycomponentwrapper \
+ vcl/osx/a11yfactory \
+ vcl/osx/a11yrolehelper \
+ vcl/osx/a11yselectionwrapper \
+ vcl/osx/a11ytablewrapper \
+ vcl/osx/a11ytextattributeswrapper \
+ vcl/osx/a11ytextwrapper \
+ vcl/osx/a11yutil \
+ vcl/osx/a11yvaluewrapper \
+ vcl/osx/a11ywrapper \
+ vcl/osx/a11ywrapperbutton \
+ vcl/osx/a11ywrappercheckbox \
+ vcl/osx/a11ywrappercombobox \
+ vcl/osx/a11ywrappergroup \
+ vcl/osx/a11ywrapperlist \
+ vcl/osx/a11ywrapperradiobutton \
+ vcl/osx/a11ywrapperradiogroup \
+ vcl/osx/a11ywrapperrow \
+ vcl/osx/a11ywrapperscrollarea \
+ vcl/osx/a11ywrapperscrollbar \
+ vcl/osx/a11ywrappersplitter \
+ vcl/osx/a11ywrapperstatictext \
+ vcl/osx/a11ywrappertabgroup \
+ vcl/osx/a11ywrappertextarea \
+ vcl/osx/a11ywrappertoolbar \
+ vcl/osx/printaccessoryview \
+ vcl/osx/printview \
+ vcl/osx/salframeview \
+ vcl/osx/salnsmenu \
+ vcl/osx/salnstimer \
+ vcl/osx/vclnsapp \
+))
+
+$(eval $(call gb_Library_add_exception_objects,vclplug_osx,\
+ vcl/osx/DataFlavorMapping \
+ vcl/osx/DragActionConversion \
+ vcl/osx/DragSource \
+ vcl/osx/DragSourceContext \
+ vcl/osx/DropTarget \
+ vcl/osx/HtmlFmtFlt \
+ vcl/osx/OSXTransferable \
+ vcl/osx/PictToBmpFlt \
+ vcl/osx/a11yfocuslistener \
+ vcl/osx/a11yfocustracker \
+ vcl/osx/a11ylistener \
+ vcl/osx/clipboard \
+ vcl/osx/documentfocuslistener \
+ vcl/osx/saldata \
+ vcl/osx/salframe \
+ vcl/osx/salgdiutils \
+ vcl/osx/salinst \
+ vcl/osx/salmacos \
+ vcl/osx/salmenu \
+ vcl/osx/salnativewidgets \
+ vcl/osx/salobj \
+ vcl/osx/salprn \
+ vcl/osx/salsys \
+ vcl/osx/saltimer \
+ vcl/osx/service_entry \
+ vcl/quartz/CoreTextFont \
+ vcl/quartz/CoreTextFontFace \
+ vcl/quartz/SystemFontList \
+ vcl/quartz/salbmp \
+ vcl/quartz/salgdi \
+ vcl/quartz/salgdicommon \
+ vcl/quartz/salvd \
+ vcl/quartz/utils \
+ vcl/quartz/AquaGraphicsBackend \
+ $(if $(filter SKIA,$(BUILD_TYPE)), \
+ vcl/skia/osx/bitmap \
+ vcl/skia/osx/gdiimpl \
+ ) \
+))
+
+$(eval $(call gb_Library_use_system_darwin_frameworks,vclplug_osx,\
+ ApplicationServices \
+ Cocoa \
+ Carbon \
+ CoreFoundation \
+))
+
+ifneq ($(ENABLE_MACOSX_SANDBOX),TRUE)
+$(eval $(call gb_Library_use_libraries,vclplug_osx,\
+ AppleRemote \
+))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Library_vclplug_qt5.mk b/vcl/Library_vclplug_qt5.mk
new file mode 100644
index 0000000000..2a72693e0e
--- /dev/null
+++ b/vcl/Library_vclplug_qt5.mk
@@ -0,0 +1,130 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Library_Library,vclplug_qt5))
+
+$(eval $(call gb_Library_set_plugin_for,vclplug_qt5,vcl))
+
+$(eval $(call gb_Library_use_custom_headers,vclplug_qt5,vcl/qt5))
+
+$(eval $(call gb_Library_set_include,vclplug_qt5,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+ -I$(SRCDIR)/vcl/inc/qt5 \
+ $(GSTREAMER_1_0_CFLAGS) \
+))
+
+$(eval $(call gb_Library_add_defs,vclplug_qt5,\
+ -DVCLPLUG_QT_IMPLEMENTATION \
+ -DVCL_INTERNALS \
+))
+
+$(eval $(call gb_Library_use_sdk_api,vclplug_qt5))
+
+$(eval $(call gb_Library_use_libraries,vclplug_qt5,\
+ tl \
+ utl \
+ sot \
+ ucbhelper \
+ basegfx \
+ comphelper \
+ cppuhelper \
+ i18nlangtag \
+ i18nutil \
+ $(if $(ENABLE_JAVA), \
+ jvmaccess) \
+ cppu \
+ sal \
+ salhelper \
+))
+
+$(eval $(call gb_Library_use_externals,vclplug_qt5,\
+ boost_headers \
+ cairo \
+ epoxy \
+ graphite \
+ harfbuzz \
+ icu_headers \
+ icuuc \
+ qt5 \
+))
+
+ifneq ($(QT5_HAVE_GOBJECT),)
+$(eval $(call gb_Library_add_cxxflags,vclplug_qt5,\
+ $(QT5_GOBJECT_CFLAGS) \
+))
+$(eval $(call gb_Library_add_libs,vclplug_qt5,\
+ $(QT5_GOBJECT_LIBS) \
+))
+endif
+
+$(eval $(call gb_Library_add_exception_objects,vclplug_qt5,\
+ vcl/qt5/QtAccessibleEventListener \
+ vcl/qt5/QtAccessibleRegistry \
+ vcl/qt5/QtAccessibleWidget \
+ vcl/qt5/QtBitmap \
+ vcl/qt5/QtClipboard \
+ vcl/qt5/QtData \
+ vcl/qt5/QtDragAndDrop \
+ vcl/qt5/QtFilePicker \
+ vcl/qt5/QtFont \
+ vcl/qt5/QtFontFace \
+ vcl/qt5/QtFrame \
+ vcl/qt5/QtGraphics \
+ vcl/qt5/QtGraphics_Controls \
+ vcl/qt5/QtGraphics_GDI \
+ vcl/qt5/QtGraphics_Text \
+ vcl/qt5/QtInstance \
+ vcl/qt5/QtInstance_Print \
+ vcl/qt5/QtMainWindow \
+ vcl/qt5/QtMenu \
+ vcl/qt5/QtObject \
+ vcl/qt5/QtOpenGLContext \
+ vcl/qt5/QtPainter \
+ vcl/qt5/QtPrinter \
+ vcl/qt5/QtSvpGraphics \
+ vcl/qt5/QtSvpSurface \
+ vcl/qt5/QtSystem \
+ vcl/qt5/QtTimer \
+ vcl/qt5/QtTools \
+ vcl/qt5/QtTransferable \
+ vcl/qt5/QtVirtualDevice \
+ vcl/qt5/QtWidget \
+ vcl/qt5/QtXAccessible \
+ $(if $(USING_X11), \
+ vcl/qt5/QtX11Support \
+ ) \
+))
+
+ifeq ($(OS),LINUX)
+$(eval $(call gb_Library_add_libs,vclplug_qt5,\
+ -lm \
+ -ldl \
+))
+endif
+
+# Workaround for clang+icecream (clang's -frewrite-includes
+# doesn't handle Qt5's QT_HAS_INCLUDE that Qt5 uses for <chrono>).
+ifeq ($(COM_IS_CLANG),TRUE)
+$(eval $(call gb_Library_add_cxxflags,vclplug_qt5, \
+ -include chrono \
+))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Library_vclplug_qt6.mk b/vcl/Library_vclplug_qt6.mk
new file mode 100644
index 0000000000..a6b14da23f
--- /dev/null
+++ b/vcl/Library_vclplug_qt6.mk
@@ -0,0 +1,129 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Library_Library,vclplug_qt6))
+
+$(eval $(call gb_Library_set_plugin_for,vclplug_qt6,vcl))
+
+$(eval $(call gb_Library_use_custom_headers,vclplug_qt6,vcl/qt6))
+
+$(eval $(call gb_Library_set_include,vclplug_qt6,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+ -I$(SRCDIR)/vcl/inc/qt6 \
+))
+
+$(eval $(call gb_Library_add_defs,vclplug_qt6,\
+ -DVCLPLUG_QT_IMPLEMENTATION \
+ -DVCL_INTERNALS \
+))
+
+$(eval $(call gb_Library_use_sdk_api,vclplug_qt6))
+
+$(eval $(call gb_Library_use_libraries,vclplug_qt6,\
+ tl \
+ utl \
+ sot \
+ ucbhelper \
+ basegfx \
+ comphelper \
+ cppuhelper \
+ i18nlangtag \
+ i18nutil \
+ $(if $(ENABLE_JAVA), \
+ jvmaccess) \
+ cppu \
+ sal \
+ salhelper \
+))
+
+$(eval $(call gb_Library_use_externals,vclplug_qt6,\
+ boost_headers \
+ cairo \
+ epoxy \
+ graphite \
+ harfbuzz \
+ icu_headers \
+ icuuc \
+ qt6 \
+))
+
+ifneq ($(QT6_HAVE_GOBJECT),)
+$(eval $(call gb_Library_add_cxxflags,vclplug_qt6,\
+ $(QT6_GOBJECT_CFLAGS) \
+))
+$(eval $(call gb_Library_add_libs,vclplug_qt6,\
+ $(QT6_GOBJECT_LIBS) \
+))
+endif
+
+$(eval $(call gb_Library_add_exception_objects,vclplug_qt6,\
+ vcl/qt6/QtAccessibleEventListener \
+ vcl/qt6/QtAccessibleRegistry \
+ vcl/qt6/QtAccessibleWidget \
+ vcl/qt6/QtBitmap \
+ vcl/qt6/QtClipboard \
+ vcl/qt6/QtData \
+ vcl/qt6/QtDragAndDrop \
+ vcl/qt6/QtFilePicker \
+ vcl/qt6/QtFont \
+ vcl/qt6/QtFontFace \
+ vcl/qt6/QtFrame \
+ vcl/qt6/QtGraphics \
+ vcl/qt6/QtGraphics_Controls \
+ vcl/qt6/QtGraphics_GDI \
+ vcl/qt6/QtGraphics_Text \
+ vcl/qt6/QtInstance \
+ vcl/qt6/QtInstance_Print \
+ vcl/qt6/QtMainWindow \
+ vcl/qt6/QtMenu \
+ vcl/qt6/QtObject \
+ vcl/qt6/QtOpenGLContext \
+ vcl/qt6/QtPainter \
+ vcl/qt6/QtPrinter \
+ vcl/qt6/QtSvpGraphics \
+ vcl/qt6/QtSvpSurface \
+ vcl/qt6/QtSystem \
+ vcl/qt6/QtTimer \
+ vcl/qt6/QtTools \
+ vcl/qt6/QtTransferable \
+ vcl/qt6/QtVirtualDevice \
+ vcl/qt6/QtWidget \
+ vcl/qt6/QtXAccessible \
+ $(if $(USING_X11), \
+ vcl/qt6/QtX11Support \
+ ) \
+))
+
+ifeq ($(OS),LINUX)
+$(eval $(call gb_Library_add_libs,vclplug_qt6,\
+ -lm \
+ -ldl \
+))
+endif
+
+# Workaround for clang+icecream (clang's -frewrite-includes
+# doesn't handle Qt6's QT_HAS_INCLUDE that Qt6 uses for <chrono>).
+ifeq ($(COM_IS_CLANG),TRUE)
+$(eval $(call gb_Library_add_cxxflags,vclplug_qt6, \
+ -include chrono \
+))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Library_vclplug_win.mk b/vcl/Library_vclplug_win.mk
new file mode 100644
index 0000000000..d4656302d4
--- /dev/null
+++ b/vcl/Library_vclplug_win.mk
@@ -0,0 +1,143 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Library_Library,vclplug_win))
+
+$(eval $(call gb_Library_set_plugin_for,vclplug_win,vcl))
+
+$(eval $(call gb_Library_set_componentfile,vclplug_win,vcl/vclplug_win,services))
+
+$(eval $(call gb_Library_set_include,vclplug_win,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+ -I$(SRCDIR)/vcl/inc/win \
+))
+
+$(eval $(call gb_Library_add_defs,vclplug_win,\
+ -DVCLPLUG_WIN_IMPLEMENTATION \
+ -DVCL_INTERNALS \
+))
+
+$(eval $(call gb_Library_use_custom_headers,vclplug_win,\
+ officecfg/registry \
+))
+
+$(eval $(call gb_Library_use_sdk_api,vclplug_win))
+
+$(eval $(call gb_Library_use_common_precompiled_header,vclplugin_win))
+
+$(eval $(call gb_Library_use_libraries,vclplug_win,\
+ $(call gb_Helper_optional,BREAKPAD, \
+ crashreport) \
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ i18nlangtag \
+ i18nutil \
+ sal \
+ salhelper \
+ tl \
+ utl \
+))
+
+$(eval $(call gb_Library_use_externals,vclplug_win,\
+ boost_headers \
+ epoxy \
+ harfbuzz \
+ $(if $(filter SKIA,$(BUILD_TYPE)),skia) \
+))
+
+$(eval $(call gb_Library_add_exception_objects,vclplug_win,\
+ vcl/source/opengl/win/context \
+ vcl/win/app/saldata \
+ vcl/win/app/salinfo \
+ vcl/win/app/salinst \
+ vcl/win/app/salshl \
+ vcl/win/app/saltimer \
+ vcl/win/dtrans/APNDataObject \
+ vcl/win/dtrans/clipboardmanager \
+ vcl/win/dtrans/DataFmtTransl \
+ vcl/win/dtrans/DOTransferable \
+ vcl/win/dtrans/DtObjFactory \
+ vcl/win/dtrans/DTransHelper \
+ vcl/win/dtrans/Fetc \
+ vcl/win/dtrans/FetcList \
+ vcl/win/dtrans/FmtFilter \
+ vcl/win/dtrans/ftransl \
+ vcl/win/dtrans/generic_clipboard \
+ vcl/win/dtrans/globals \
+ vcl/win/dtrans/idroptarget \
+ vcl/win/dtrans/ImplHelper \
+ vcl/win/dtrans/MtaOleClipb \
+ vcl/win/dtrans/source \
+ vcl/win/dtrans/sourcecontext \
+ vcl/win/dtrans/target \
+ vcl/win/dtrans/targetdragcontext \
+ vcl/win/dtrans/targetdropcontext \
+ vcl/win/dtrans/TxtCnvtHlp \
+ vcl/win/dtrans/WinClipboard \
+ vcl/win/dtrans/XNotifyingDataObject \
+ vcl/win/dtrans/XTDataObject \
+ vcl/win/gdi/gdiimpl \
+ vcl/win/gdi/salbmp \
+ vcl/win/gdi/salfont \
+ vcl/win/gdi/salgdi \
+ vcl/win/gdi/salgdi2 \
+ vcl/win/gdi/salgdi_gdiplus \
+ vcl/win/gdi/salnativewidgets-luna \
+ vcl/win/gdi/salprn \
+ vcl/win/gdi/salvd \
+ vcl/win/gdi/winlayout \
+ vcl/win/gdi/DWriteTextRenderer \
+ vcl/win/window/keynames \
+ vcl/win/window/salframe \
+ vcl/win/window/salmenu \
+ vcl/win/window/salobj \
+ $(if $(filter SKIA,$(BUILD_TYPE)), \
+ vcl/skia/win/gdiimpl ) \
+))
+
+$(eval $(call gb_Library_use_system_win32_libs,vclplug_win,\
+ advapi32 \
+ d2d1 \
+ dwmapi \
+ dwrite \
+ gdi32 \
+ gdiplus \
+ imm32 \
+ ole32 \
+ oleaut32 \
+ shell32 \
+ shlwapi \
+ uuid \
+ uxtheme \
+ version \
+ winspool \
+))
+
+$(eval $(call gb_Library_add_nativeres,vclplug_win,vcl/salsrc))
+
+# HACK: dependency on icon themes so running unit tests don't
+# prevent delivering these by having open file handles on WNT
+ifeq ($(gb_Side),host)
+$(eval $(call gb_Library_use_package,vclplug_win,postprocess_images))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Makefile b/vcl/Makefile
new file mode 100644
index 0000000000..0997e62848
--- /dev/null
+++ b/vcl/Makefile
@@ -0,0 +1,14 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST))))
+
+include $(module_directory)/../solenv/gbuild/partial_build.mk
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Module_vcl.mk b/vcl/Module_vcl.mk
new file mode 100644
index 0000000000..19ab27106a
--- /dev/null
+++ b/vcl/Module_vcl.mk
@@ -0,0 +1,294 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Module_Module,vcl))
+
+ifneq ($(ENABLE_WASM_STRIP_PINGUSER),TRUE)
+$(eval $(call gb_Module_add_targets,vcl,\
+ Package_tipoftheday \
+))
+endif
+
+$(eval $(call gb_Module_add_targets,vcl,\
+ Library_vcl \
+ Package_theme_definitions \
+ Package_toolbarmode \
+ UIConfig_vcl \
+ $(if $(filter WNT,$(OS)), \
+ Package_opengl_denylist ) \
+ $(if $(filter SKIA,$(BUILD_TYPE)), \
+ Package_skia_denylist ) \
+ $(if $(filter DESKTOP FUZZERS,$(BUILD_TYPE)), \
+ StaticLibrary_vclmain \
+ $(if $(or $(DISABLE_GUI),$(DISABLE_DYNLOADING)), \
+ $(if $(filter EMSCRIPTEN,$(OS)), \
+ $(if $(ENABLE_QT5),Executable_vcldemo) \
+ ) \
+ , \
+ $(if $(filter LINUX MACOSX SOLARIS WNT %BSD,$(OS)), \
+ Executable_vcldemo \
+ Executable_svdemo \
+ Executable_minvcl \
+ Executable_svptest \
+ Executable_icontest \
+ Executable_visualbackendtest \
+ Executable_mtfdemo \
+ ) \
+ )) \
+))
+
+ifeq ($(CROSS_COMPILING)$(DISABLE_DYNLOADING),)
+
+$(eval $(call gb_Module_add_targets,vcl,\
+ $(if $(filter-out ANDROID iOS WNT,$(OS)), \
+ Executable_fftester \
+ Executable_listfonts \
+ Executable_listglyphs \
+ Executable_svpclient) \
+))
+
+endif
+
+$(eval $(call gb_Module_add_l10n_targets,vcl,\
+ AllLangMoTarget_vcl \
+))
+
+ifeq ($(USING_X11),TRUE)
+$(eval $(call gb_Module_add_targets,vcl,\
+ $(if $(ENABLE_GEN),Library_vclplug_gen) \
+ Library_desktop_detector \
+ Package_fontunxppds \
+ Package_fontunxpsprint \
+))
+endif
+
+ifneq ($(ENABLE_GTK3),)
+$(eval $(call gb_Module_add_targets,vcl,\
+ Library_vclplug_gtk3 \
+))
+
+ifneq ($(ENABLE_ATSPI_TESTS),)
+$(eval $(call gb_Module_add_check_targets,vcl,\
+ CppunitTest_vcl_gtk3_a11y \
+))
+endif
+endif
+
+ifneq ($(ENABLE_GTK4),)
+$(eval $(call gb_Module_add_targets,vcl,\
+ Library_vclplug_gtk4 \
+))
+endif
+
+ifneq ($(ENABLE_KF5),)
+$(eval $(call gb_Module_add_targets,vcl,\
+ CustomTarget_kf5_moc \
+ Library_vclplug_kf5 \
+))
+endif
+
+ifneq ($(ENABLE_KF6),)
+$(eval $(call gb_Module_add_targets,vcl,\
+ CustomTarget_kf6_moc \
+ Library_vclplug_kf6 \
+))
+endif
+
+
+ifneq ($(ENABLE_QT5),)
+$(eval $(call gb_Module_add_targets,vcl,\
+ CustomTarget_qt5_moc \
+ Library_vclplug_qt5 \
+))
+endif
+
+ifneq ($(ENABLE_QT6),)
+$(eval $(call gb_Module_add_targets,vcl,\
+ CustomTarget_qt6_moc \
+ Library_vclplug_qt6 \
+))
+endif
+
+ifneq ($(ENABLE_GTK3_KDE5),)
+$(eval $(call gb_Module_add_targets,vcl,\
+ CustomTarget_gtk3_kde5_moc \
+ Library_vclplug_gtk3_kde5 \
+ Executable_lo_kde5filepicker \
+))
+endif
+
+ifeq ($(OS),MACOSX)
+$(eval $(call gb_Module_add_targets,vcl,\
+ Package_osxres \
+ Library_vclplug_osx \
+))
+endif
+
+ifeq ($(OS),WNT)
+$(eval $(call gb_Module_add_targets,vcl,\
+ WinResTarget_vcl \
+ Library_vclplug_win \
+))
+endif
+
+ifneq (,$(filter FUZZERS,$(BUILD_TYPE)))
+$(eval $(call gb_Module_add_targets,vcl,\
+ CustomTarget_nativecore \
+ CustomTarget_nativecalc \
+ CustomTarget_nativedraw \
+ CustomTarget_nativewriter \
+ CustomTarget_nativemath \
+ StaticLibrary_fuzzer_core \
+ StaticLibrary_fuzzer_calc \
+ StaticLibrary_fuzzer_draw \
+ StaticLibrary_fuzzer_writer \
+ StaticLibrary_fuzzer_math \
+ Executable_wmffuzzer \
+ Executable_jpgfuzzer \
+ Executable_giffuzzer \
+ Executable_xbmfuzzer \
+ Executable_xpmfuzzer \
+ Executable_pngfuzzer \
+ Executable_bmpfuzzer \
+ Executable_svmfuzzer \
+ Executable_pcdfuzzer \
+ Executable_dxffuzzer \
+ Executable_metfuzzer \
+ Executable_ppmfuzzer \
+ Executable_psdfuzzer \
+ Executable_epsfuzzer \
+ Executable_pctfuzzer \
+ Executable_pcxfuzzer \
+ Executable_rasfuzzer \
+ Executable_tgafuzzer \
+ Executable_tiffuzzer \
+ Executable_hwpfuzzer \
+ Executable_602fuzzer \
+ Executable_lwpfuzzer \
+ Executable_olefuzzer \
+ Executable_pptfuzzer \
+ Executable_rtffuzzer \
+ Executable_cgmfuzzer \
+ Executable_ww2fuzzer \
+ Executable_ww6fuzzer \
+ Executable_ww8fuzzer \
+ Executable_qpwfuzzer \
+ Executable_slkfuzzer \
+ Executable_fodtfuzzer \
+ Executable_fodt2pdffuzzer \
+ Executable_fodsfuzzer \
+ Executable_fodpfuzzer \
+ Executable_xlsfuzzer \
+ Executable_scrtffuzzer \
+ Executable_wksfuzzer \
+ Executable_diffuzzer \
+ Executable_docxfuzzer \
+ Executable_xlsxfuzzer \
+ Executable_pptxfuzzer \
+ Executable_mmlfuzzer \
+ Executable_mtpfuzzer \
+ Executable_htmlfuzzer \
+ Executable_sftfuzzer \
+ Executable_dbffuzzer \
+ Executable_webpfuzzer \
+ Executable_zipfuzzer \
+ Executable_svgfuzzer \
+))
+endif
+
+$(eval $(call gb_Module_add_check_targets,vcl,\
+ CppunitTest_vcl_drawmode \
+ CppunitTest_vcl_lifecycle \
+ CppunitTest_vcl_bitmap_test \
+ CppunitTest_vcl_bitmapprocessor_test \
+ CppunitTest_vcl_cjk \
+ CppunitTest_vcl_graphic_test \
+ CppunitTest_vcl_fontcharmap \
+ CppunitTest_vcl_font \
+ CppunitTest_vcl_fontfeature \
+ CppunitTest_vcl_fontmetric \
+ CppunitTest_vcl_text \
+ CppunitTest_vcl_filters_test \
+ CppunitTest_vcl_mnemonic \
+ CppunitTest_vcl_outdev \
+ CppunitTest_vcl_gradient \
+ CppunitTest_vcl_app_test \
+ CppunitTest_vcl_jpeg_read_write_test \
+ CppunitTest_vcl_svm_test \
+ CppunitTest_vcl_errorhandler \
+ CppunitTest_vcl_bitmap_render_test \
+ CppunitTest_vcl_apitests \
+ CppunitTest_vcl_png_test \
+ CppunitTest_vcl_widget_definition_reader_test \
+ CppunitTest_vcl_backend_test \
+ CppunitTest_vcl_blocklistparser_test \
+ CppunitTest_vcl_type_serializer_test \
+ CppunitTest_vcl_animation \
+ $(call gb_Helper_optional, PDFIUM, \
+ CppunitTest_vcl_pdfium_library_test) \
+ $(if $(filter SKIA,$(BUILD_TYPE)), \
+ CppunitTest_vcl_skia) \
+ CppunitTest_vcl_filter_igif \
+))
+
+ifeq ($(USING_X11),TRUE)
+$(eval $(call gb_Module_add_check_targets,vcl,\
+ CppunitTest_vcl_timer \
+))
+endif
+
+ifeq ($(DISABLE_GUI),TRUE)
+$(eval $(call gb_Module_add_check_targets,vcl,\
+ CppunitTest_vcl_timer \
+))
+endif
+
+# Is any configuration missing?
+ifeq ($(OS),WNT)
+$(eval $(call gb_Module_add_check_targets,vcl,\
+ CppunitTest_vcl_timer \
+))
+endif
+
+ifeq ($(OS),MACOSX)
+$(eval $(call gb_Module_add_check_targets,vcl,\
+ CppunitTest_vcl_timer \
+))
+endif
+
+# screenshots
+$(eval $(call gb_Module_add_screenshot_targets,vcl,\
+ CppunitTest_vcl_dialogs_test \
+))
+
+ifneq ($(DISPLAY),)
+$(eval $(call gb_Module_add_slowcheck_targets,vcl,\
+ CppunitTest_vcl_gen \
+))
+endif
+
+ifneq (,$(filter PDFIUM,$(BUILD_TYPE)))
+$(eval $(call gb_Module_add_slowcheck_targets,vcl,\
+ CppunitTest_vcl_pdfexport \
+ CppunitTest_vcl_pdfexport2 \
+ CppunitTest_vcl_filter_ipdf \
+))
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Package_fontunxppds.mk b/vcl/Package_fontunxppds.mk
new file mode 100644
index 0000000000..0902bcb428
--- /dev/null
+++ b/vcl/Package_fontunxppds.mk
@@ -0,0 +1,25 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Package_Package,vcl_fontunxppds,$(SRCDIR)/vcl/unx/generic/printer/configuration/ppds))
+
+$(eval $(call gb_Package_add_files,vcl_fontunxppds,$(LIBO_SHARE_FOLDER)/psprint/driver,\
+ SGENPRT.PS \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Package_fontunxpsprint.mk b/vcl/Package_fontunxpsprint.mk
new file mode 100644
index 0000000000..1ab2a560b7
--- /dev/null
+++ b/vcl/Package_fontunxpsprint.mk
@@ -0,0 +1,25 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Package_Package,vcl_fontunxpsprint,$(SRCDIR)/vcl/unx/generic/printer/configuration))
+
+$(eval $(call gb_Package_add_files,vcl_fontunxpsprint,$(LIBO_SHARE_FOLDER)/psprint,\
+ psprint.conf \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Package_opengl_denylist.mk b/vcl/Package_opengl_denylist.mk
new file mode 100644
index 0000000000..bf947dd47b
--- /dev/null
+++ b/vcl/Package_opengl_denylist.mk
@@ -0,0 +1,16 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Package_Package,vcl_opengl_denylist,$(SRCDIR)/vcl/source/opengl))
+
+$(eval $(call gb_Package_add_files,vcl_opengl_denylist,$(LIBO_SHARE_FOLDER)/opengl,\
+ opengl_denylist_windows.xml \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Package_osxres.mk b/vcl/Package_osxres.mk
new file mode 100644
index 0000000000..dfbc35a970
--- /dev/null
+++ b/vcl/Package_osxres.mk
@@ -0,0 +1,18 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Package_Package,vcl_osxres,$(SRCDIR)/vcl/osx/res))
+
+$(eval $(call gb_Package_add_files_with_dir,vcl_osxres,Resources,\
+ MainMenu.nib/classes.nib \
+ MainMenu.nib/info.nib \
+ MainMenu.nib/keyedobjects.nib \
+))
+
+# vim:set noet sw=4 ts=4:
diff --git a/vcl/Package_skia_denylist.mk b/vcl/Package_skia_denylist.mk
new file mode 100644
index 0000000000..70f6ac7016
--- /dev/null
+++ b/vcl/Package_skia_denylist.mk
@@ -0,0 +1,16 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Package_Package,vcl_skia_denylist,$(SRCDIR)/vcl/skia))
+
+$(eval $(call gb_Package_add_files,vcl_skia_denylist,$(LIBO_SHARE_FOLDER)/skia,\
+ skia_denylist_vulkan.xml \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Package_theme_definitions.mk b/vcl/Package_theme_definitions.mk
new file mode 100644
index 0000000000..395a90b19b
--- /dev/null
+++ b/vcl/Package_theme_definitions.mk
@@ -0,0 +1,57 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Package_Package,vcl_theme_definitions,$(SRCDIR)/vcl/uiconfig/theme_definitions))
+
+$(eval $(call gb_Package_add_files_with_dir,vcl_theme_definitions,$(LIBO_SHARE_FOLDER)/theme_definitions,\
+ ios/definition.xml \
+ ios/switch-off.svg \
+ ios/switch-off-disabled.svg \
+ ios/switch-off-pressed.svg \
+ ios/switch-on.svg \
+ ios/switch-on-pressed.svg \
+ ios/switch-on-disabled.svg \
+ ios/tick-off.svg \
+ ios/tick-off-disabled.svg \
+ ios/tick-off-pressed.svg \
+ ios/tick-on.svg \
+ ios/tick-on-pressed.svg \
+ ios/tick-on-disabled.svg \
+ ios/spinbox-left.svg \
+ ios/spinbox-left-pressed.svg \
+ ios/spinbox-left-disabled.svg \
+ ios/spinbox-right.svg \
+ ios/spinbox-right-pressed.svg \
+ ios/spinbox-right-disabled.svg \
+ ios/common-rect.svg \
+ ios/common-rect-disabled.svg \
+ ios/common-rect-focus.svg \
+ ios/common-rect-focus-slim.svg \
+ ios/pushbutton-default.svg \
+ ios/pushbutton-rollover.svg \
+ ios/pushbutton-disabled.svg \
+ ios/tabitem-first.svg \
+ ios/tabitem-middle.svg \
+ ios/tabitem-last.svg \
+ ios/tabitem-first-selected.svg \
+ ios/tabitem-middle-selected.svg \
+ ios/tabitem-last-selected.svg \
+ ios/scrollbar-horizontal.svg \
+ ios/scrollbar-vertical.svg \
+ ios/combobox.svg \
+ ios/combobox-disabled.svg \
+ ios/combobox-button.svg \
+ ios/combobox-button-disabled.svg \
+ ios/arrow-up.svg \
+ ios/arrow-down.svg \
+ ios/slider-button.svg \
+ ios/slider-button-disabled.svg \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Package_tipoftheday.mk b/vcl/Package_tipoftheday.mk
new file mode 100644
index 0000000000..6b67d5e44a
--- /dev/null
+++ b/vcl/Package_tipoftheday.mk
@@ -0,0 +1,27 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Package_Package,tipoftheday_images,$(SRCDIR)/extras/source/tipoftheday))
+
+$(eval $(call gb_Package_add_files_with_dir,tipoftheday_images,$(LIBO_SHARE_FOLDER)/tipoftheday,\
+ toolbarmode.png \
+ marchingants.gif \
+ printnote.png \
+ formdocuments.png \
+ masterdocument.png \
+ statusbar.png \
+ expand_formula_bar.png \
+ fraction.png \
+ hybrid_pdf.png \
+ icon_sets.png \
+ remove_hyperlink.png \
+ sum_sheets.png \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Package_toolbarmode.mk b/vcl/Package_toolbarmode.mk
new file mode 100644
index 0000000000..9421001cdd
--- /dev/null
+++ b/vcl/Package_toolbarmode.mk
@@ -0,0 +1,24 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Package_Package,toolbarmode_images,$(SRCDIR)/extras/source/toolbarmode))
+
+$(eval $(call gb_Package_add_files_with_dir,toolbarmode_images,$(LIBO_SHARE_FOLDER)/toolbarmode,\
+ default.png \
+ single.png \
+ sidebar.png \
+ notebookbar.png \
+ notebookbar_compact.png \
+ notebookbar_groupedbar_compact.png \
+ notebookbar_groupedbar_full.png \
+ notebookbar_single.png \
+ notebookbar_groups.png \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/README.GDIMetaFile.md b/vcl/README.GDIMetaFile.md
new file mode 100644
index 0000000000..386a91e2ad
--- /dev/null
+++ b/vcl/README.GDIMetaFile.md
@@ -0,0 +1,182 @@
+# GDIMetaFile class
+
+The `GDIMetaFile` class reads, writes, manipulates and replays metafiles via the
+`VCL` module.
+
+A typical use case is to initialize a new `GDIMetaFile`, open the actual stored
+metafile and read it in via `GDIMetaFile::Read( aIStream )`. This reads in the
+metafile into the `GDIMetafile` object - it can read in an old-style `VCLMTF`
+metafile (back in the days that Microsoft didn't document the metafile format
+this was used), as well as EMF+ files - and adds them to a list (vector) of
+`MetaActions`. You can also populate your own `GDIMetaFile` via `AddAction()`,
+`RemoveAction()`, `ReplaceAction()`, etc.
+
+Once the `GDIMetafile` object is read to be used, you can "play" the metafile,
+"pause" it, "wind forward" or "rewind" the metafile. The metafile can be moved,
+scaled, rotated and clipped, as well have the colours adjusted or replaced, or
+even made monochrome.
+
+The GDIMetafile can be used to get an `OutputDevice`'s metafile via the
+`Linker()` and `Record()` functions.
+
+## Using GDIMetafile
+
+First, create a new `GDIMetafile`, this can be done via the default constructor.
+It can then be constructed manually, or you can use `Record()` on an
+`OutputDevice` to populate the `GDIMetaFile`, or of course you can read it from
+file with `Read()`. From here you can then elect to manipulate the metafile, or
+play it back to another `GDIMetafile` or to an `OutputDevice` via `Play()`. To
+store the file, use `Write()`.
+
+### CONSTRUCTORS AND DESTRUCTORS
+
+- `GDIMetaFile`
+- `GDIMetaFile( cosnt GDIMetaFile& rMtf )` - copy constructor
+- `~GDIMetaFile`
+
+### OPERATORS
+
+- `operator =`
+- `operator ==`
+- `operator !=`
+
+### RECORDING AND PLAYBACK FUNCTIONS
+
+- `Play(GDIMetaFile&, size_t)` - play back metafile into another
+ metafile up to position
+- `Play(OutputDevice*, size_t)` - play back metafile into
+ `OutputDevice` up to position
+- `Play(OutputDevice*, Point, Size, size_t)` - play back metafile into
+ `OutputDevice` at a particular
+ location on the `OutputDevice`, up
+ to the position in the metafile
+- `Pause` - pauses or continues the playback
+- `IsPause`
+- `Stop` - stop playback fully
+- `WindStart` - windback to start of the metafile
+- `windPrev` - windback one record
+- `GetActionSize` - get the number of records in the
+ metafile
+
+
+### METAFILE RECORD FUNCTIONS
+
+- `FirstAction` - get the first metafile record
+- `NextAction` - get the next metafile record from
+ the current position
+- `GetAction(size_t)` - get the metafile record at
+ location in file
+- `GetCurAction` - get the current metafile record
+- `AddAction(MetaAction*)` - appends a metafile record
+- `AddAction(MetaAction*, size_t)` - adds a metafile record to a
+ particular location in the file
+- `RemoveAction` - removes record at file location
+- `Clear` - first stops if recording, then
+ removes all metafile records
+- `push_back` - pushes back, basically a thin
+ wrapper to the metafile record list
+
+
+### READ AND WRITING
+
+- `Read`
+- `Write`
+- `GetChecksum`
+- `GetSizeBytes`
+
+
+### DISPLACEMENT FUNCTIONS
+
+- `Move( long nX, long nX)`
+- `Move( long nX, long nX, long nDPIX, long nDPIY )` - Move method getting
+ specifics how to handle
+ MapMode( MapUnit::MapPixel )
+
+
+### TRANSFORMATION FUNCTIONS
+
+- `Scale( double fScaleX, double fScaleY )`
+- `Scale( const Fraction& rScaleX, const Fraction& rScaleY )`
+- `Mirror`
+- `Rotate( long nAngle10 )`
+- `Clip( const Rectangle& )`
+
+
+### COLOR ADJUSTMENT FUNCTIONS
+
+- `Adjust` - change luminance, contrast,
+ gamma and RGB via a percentage
+- `Convert` - colour conversion
+- `ReplaceColors`
+- `GetMonochromeMtf`
+
+## Related classes
+
+`MetaAction`: a base class used by all records. It implements a command-like
+pattern, and also acts as a prototype for other actions.
+
+### CONSTRUCTORS AND DESTRUCTORS
+
+- `MetaAction()` - default constructor, sets mnRefCount to 1 and
+ mnType, in this case MetaActionType::NONE
+- `MetaAction(sal_uInt16 nType)` - virtual constructor, sets mnType to nType, and
+ mnRefCount to 1
+- `~MetaAction`
+
+### COMMAND FUNCTION
+
+- `Execute(OutputDevice*)` - execute the functionality of the record to the
+ OutputDevice. Part of command pattern.
+### FACTORY FUNCTION
+
+- `Clone()` - prototype clone function
+
+### MANIPULATION FUNCTIONS
+
+- `Move(long nHorzMove, long nVerMove)`
+- `Scale(double fScaleX, double fScaleY)`
+
+
+### READ AND WRITE FUNCTIONS
+
+- `Read`
+- `Write`
+- `ReadMetaAction` - a static function, only used to determine which
+ MetaAction to call on to read the record, which
+ means that this is the function that must be used.
+
+### INTROSPECTIVE FUNCTIONS
+
+- `GetType`
+
+### A note about MetaCommentAction
+
+So this class is the most interesting - a comment record is what is used to
+extended metafiles, to make what we call an "Enhanced Metafile". This basically
+gets the `OutputDevice`'s connect metafile and adds the record via this when it
+runs `Execute()`. It doesn't actually do anything else, unlike other
+`MetaAction`s which invoke functions from `OutputDevice`. And if there is no
+connect metafile in `OutputDevice`, then it just does nothing at all in Execute.
+Everything else works as normal (Read, Write, etc).
+
+## Basic pseudocode
+
+The following illustrates an exceptionally basic and incomplete implementation
+of how to use `GDIMetafile`. An example can be found at
+`vcl/workben/mtfdemo.cxx`
+
+```
+DemoWin::Paint()
+{
+ // assume that VCL has been initialized and a new application created
+
+ Window* pWin = new WorkWindow();
+ GDIMetaFile* pMtf = new GDIMetaFile();
+
+ SvFileStream aFileStream("example.emf", STEAM_READ);
+
+ ReadWindowMetafile(aFileStream, pMtf);
+ pMtf->Play(pWin);
+
+}
+```
diff --git a/vcl/README.lifecycle.md b/vcl/README.lifecycle.md
new file mode 100644
index 0000000000..8b0e5de2d0
--- /dev/null
+++ b/vcl/README.lifecycle.md
@@ -0,0 +1,357 @@
+# Understanding Transitional VCL Lifecycle
+
+## How it used to look
+
+All VCL classes were explicitly lifecycle managed; so you would do:
+
+```
+Dialog aDialog(...); // old - on stack allocation
+aDialog.Execute(...);
+```
+
+or:
+
+```
+Dialog *pDialog = new Dialog(...); // old - manual heap allocation
+pDialog->Execute(...);
+delete pDialog;
+```
+
+or:
+
+```
+std::shared_ptr<Dialog> xDialog(new pDialog()); // old
+xDialog->Execute(...);
+// depending who shared the ptr this would be freed sometime
+```
+
+In several cases this lead to rather unpleasant code, when
+various `shared_ptr` wrappers were used, the lifecycle was far less than
+obvious. Where controls were wrapped by other ref-counted classes -
+such as UNO interfaces, which were also used by native Window
+pointers, the lifecycle became extremely opaque. In addition VCL had
+significant issues with re-enterancy and event emission - adding
+various means such as DogTags to try to detect destruction of a window
+between calls:
+
+```
+ImplDelData aDogTag( this ); // 'orrible old code
+Show( true, ShowFlags::NoActivate );
+if( !aDogTag.IsDead() ) // did 'this' go invalid yet ?
+ Update();
+```
+
+Unfortunately use of such protection is/was ad-hoc, and far
+from uniform, despite the prevalence of such potential problems.
+
+When a lifecycle problem was hit, typically it would take the
+form of accessing memory that had been freed, and contained garbage due
+to lingering pointers to freed objects.
+
+
+## Where we are now
+
+To fix this situation we now have a `VclPtr` - which is a smart
+reference-counting pointer (`include/vcl/vclptr.hxx`) which is
+designed to look and behave -very- much like a normal pointer
+to reduce code-thrash. `VclPtr` is used to wrap all `OutputDevice`
+derived classes thus:
+
+```
+VclPtr<Dialog> pDialog( new Dialog( ... ), SAL_NO_ACQUIRE );
+...
+pDialog.disposeAndClear();
+```
+
+However - while the `VclPtr` reference count controls the
+lifecycle of the Dialog object, it is necessary to be able to
+break reference count cycles. These are extremely common in
+widget hierarchies as each widget holds (smart) pointers to
+its parents and also its children.
+
+Thus - all previous `delete` calls are replaced with `dispose`
+method calls:
+
+## What is dispose ?
+
+Dispose is defined to be a method that releases all references
+that an object holds - thus allowing their underlying
+resources to be released. However - in this specific case it
+also releases all backing graphical resources. In practical
+terms, all destructor functionality has been moved into
+`dispose` methods, in order to provide a minimal initial
+behavioral change.
+
+As such a `VclPtr` can have three states:
+
+```
+VclPtr<PushButton> pButton;
+...
+assert (pButton == nullptr || !pButton); // null
+assert (pButton && !pButton->isDisposed()); // alive
+assert (pButton && pButton->isDisposed()); // disposed
+```
+
+## `ScopedVclPtr` - making disposes easier
+
+While replacing existing code with new, it can be a bit
+tiresome to have to manually add `disposeAndClear()`
+calls to `VclPtr<>` instances.
+
+Luckily it is easy to avoid that with a `ScopedVclPtr` which
+does this for you when it goes out of scope.
+
+## One extra gotcha - an initial reference-count of 1
+
+In the normal world of love and sanity, eg. creating UNO
+objects, the objects start with a ref-count of zero. Thus
+the first reference is always taken after construction by
+the surrounding smart pointer.
+
+Unfortunately, the existing VCL code is somewhat tortured,
+and does a lot of reference and de-reference action on the
+class -during- construction. This forces us to construct with
+a reference of 1 - and to hand that into the initial smart
+pointer with a `SAL_NO_ACQUIRE`.
+
+To make this easier, we have `Instance` template wrappers
+that make this apparently easier, by constructing the
+pointer for you.
+
+### How does my familiar code change ?
+
+Lets tweak the exemplary code above to fit the new model:
+
+```
+- Dialog aDialog(... dialog params ... );
+- aDialog.Execute(...);
++ ScopedVclPtrInstance<Dialog> pDialog(... dialog params ... );
++ pDialog->Execute(...); // VclPtr behaves much like a pointer
+```
+
+or:
+
+```
+- Dialog *pDialog = new Dialog(... dialog params ...);
++ VclPtrInstance<Dialog> pDialog(... dialog params ...);
+ pDialog->Execute(...);
+- delete pDialog;
++ pDialog.disposeAndClear(); // done manually - replaces a delete
+```
+
+or:
+
+```
+- std::shared_ptr<Dialog> xDialog(new Dialog(...));
++ ScopedVclPtrInstance<Dialog> xDialog(...);
+ xDialog->Execute(...);
++ // depending how shared_ptr was shared perhaps
++ // someone else gets a VclPtr to xDialog
+```
+
+or:
+
+```
+- VirtualDevice aDev;
++ ScopedVclPtrInstance<VirtualDevice> pDev;
+```
+
+Other things that are changed are these:
+
+```
+- pButton = new PushButton(NULL);
++ pButton = VclPtr<PushButton>::Create(nullptr);
+...
+- vcl::Window *pWindow = new PushButton(NULL);
++ VclPtr<vcl::Window> pWindow;
++ pWindow.reset(VclPtr<PushButton>::Create(nullptr));
+```
+
+### Why are these `disposeOnce` calls in destructors?
+
+This is an interim measure while we are migrating, such that
+it is possible to delete an object conventionally and ensure
+that its dispose method gets called. In the 'end' we would
+instead assert that a Window has been disposed in its
+destructor, and elide these calls.
+
+As the object's vtable is altered as we go down the
+destruction process, and we want to call the correct dispose
+methods we need this `disposeOnce();` call for the interim in
+every destructor. This is enforced by a clang plugin.
+
+The plus side of disposeOnce is that the mechanics behind it
+ensure that a `dispose()` method is only called a single time,
+simplifying their implementation.
+
+
+### Who owns & disposes what?
+
+Window sub-classes tend to create their widgets in one of two
+ways and often both.
+
+1. Derive from `VclBuilderContainer`. The `VclBuilder` then owns
+ many of the sub-windows, which are fetched by a `get`
+ method into local variables often in constructors eg.
+
+```
+VclPtr<PushButton> mpButton; // in the class
+, get(mpButton, "buttonName") // in the constructor
+mpButton.clear(); // in dispose.
+```
+
+We only clear, not `disposeAndClear()` in our dispose method
+for this case, since the `VclBuilder` / Container truly owns
+this Window, and needs to dispose its hierarchy in the
+right order - first children then parents.
+
+2. Explicitly allocated Windows. These are often created and
+ managed by custom widgets:
+
+```
+VclPtr<ComplexWidget> mpComplex; // in the class
+, mpComplex( VclPtr<ComplexWidget>::Create( this ) ) // constructor
+mpComplex.disposeAndClear(); // in dispose
+```
+
+ie. an owner has to dispose things they explicitly allocate.
+
+In order to ensure that the VclBuilderConstructor
+sub-classes have their Windows disposed at the correct time
+there is a `disposeBuilder();` method - that should be added
+-only- to the class immediately deriving from
+`VclBuilderContainer`'s dispose.
+
+### What remains to be done?
+
+* Expand the `VclPtr` pattern to many other less
+ than safe VCL types.
+
+* create factory functions for `VclPtr<>` types and privatize
+ their constructors.
+
+* Pass `const VclPtr<> &` instead of pointers everywhere
+ + add `explicit` keywords to VclPtr constructors to
+ accelerate compilation etc.
+
+* Cleanup common existing methods such that they continue to
+ work post-dispose.
+
+* Dispose functions should be audited to:
+ + not leave dangling pointsr
+ + shrink them - some work should incrementally
+ migrate back to destructors.
+
+* `VclBuilder`
+ + ideally should keep a reference to pointers assigned
+ in `get()` calls - to avoid needing explicit `clear`
+ code in destructors.
+
+## FAQ / debugging hints
+
+### Compile with dbgutil
+
+This is by far the best way to turn on debugging and
+assertions that help you find problems. In particular
+there are a few that are really helpful:
+
+```
+vcl/source/window/window.cxx (Window::dispose)
+"Window ( N4sfx27sidebar20SidebarDockingWindowE (Properties))
+ ^^^ class name window title ^^^
+with live children destroyed: N4sfx27sidebar6TabBarE ()
+N4sfx27sidebar4DeckE () 10FixedImage ()"
+```
+
+You can de-mangle these names if you can't read them thus:
+
+```
+$ c++filt -t N4sfx27sidebar20SidebarDockingWindowE
+sfx2::sidebar::SidebarDockingWindow
+```
+
+In the above case - it is clear that the children have not been
+disposed before their parents. As an aside, having a dispose chain
+separate from destructors allows us to emit real type names for
+parents here.
+
+To fix this, we will need to get the dispose ordering right,
+occasionally in the conversion we re-ordered destruction, or
+omitted a `disposeAndClear()` in a `::dispose()` method.
+
+- If you see this, check the order of `disposeAndClear()` in
+ the `sfx2::Sidebar::SidebarDockingWindow::dispose()` method
+
+- also worth `git grep`ing for `new sfx::sidebar::TabBar` to
+ see where those children were added.
+
+### Check what it used to do
+
+While a ton of effort has been put into ensuring that the new
+lifecycle code is the functional equivalent of the old code,
+the code was created by humans. If you identify an area where
+something asserts or crashes here are a few helpful heuristics:
+
+* Read the `git log -u -- path/to/file.cxx`
+
+### Is the order of destruction different?
+
+In the past many things were destructed (in reverse order of
+declaration in the class) without explicit code. Some of these
+may be important to do explicitly at the end of the destructor.
+
+eg. having a `Idle` or `Timer` as a member, may now need an
+ explicit `.Stop()` and/or protection from running on a
+ disposed Window in its callback.
+
+### Is it `clear` not `disposeAndClear`?
+
+sometimes we get this wrong. If the code previously used to
+use `delete pFoo;` it should now read `pFoo->disposeAndClear();`.
+Conversely if it didn't delete it, it should be `clear()` it
+is by far the best to leave disposing to the `VclBuilder` where
+possible.
+
+In simple cases, if we allocate the widget with `VclPtrInstance`
+or `VclPtr<Foo>::Create` - then we need to `disposeAndClear` it too.
+
+### Event / focus / notification ordering
+
+In the old world, a large amount of work was done in the
+`~Window` destructor that is now done in `Window::dispose`.
+
+Since those Windows were in the process of being destroyed
+themselves, their vtables were adjusted to only invoke Window
+methods. In the new world, sub-classed methods such as
+`PreNotify`, `GetFocus`, `LoseFocus` and others are invoked all down
+the inheritance chain from children to parent, during dispose.
+
+The easiest way to fix these is to just ensure that these
+cleanup methods, especially LoseFocus, continue to work even
+on disposed Window sub-class instances.
+
+### It crashes with some invalid memory...
+
+Assuming that the invalid memory is a Window sub-class itself,
+then almost certainly there is some cockup in the
+reference-counting; eg. if you hit an `OutputDevice::release`
+assert on `mnRefCount` - then almost certainly you have a
+Window that has already been destroyed. This can easily
+happen via this sort of pattern:
+
+```
+Dialog *pDlg = VclPtr<Dialog>(nullptr /* parent */);
+// by here the pDlg quite probably points to free'd memory...
+```
+
+It is necessary in these cases to ensure that the `*pDlg` is
+a `VclPtr<Dialog>` instead.
+
+### It crashes with some invalid memory #2...
+
+Often a `::dispose` method will free some `pImpl` member, but
+not `NULL` it; and (cf. above) we can now get various `virtual`
+methods called post-dispose; so:
+
+a) `delete pImpl; pImpl = NULL; // in the destructor`
+b) `if (pImpl && ...) // in the subsequently called method`
diff --git a/vcl/README.md b/vcl/README.md
new file mode 100644
index 0000000000..c6e436aa48
--- /dev/null
+++ b/vcl/README.md
@@ -0,0 +1,264 @@
+# Visual Class Library (VCL)
+
+Visual Class Library (VCL) is responsible for the widgets (windowing, buttons, controls,
+file-pickers etc.), operating system abstraction, including basic rendering (e.g. the output device).
+
+It should not be confused with Borland's Visual Component Library, which is entirely unrelated.
+
+VCL provides a graphical toolkit similar to gtk+, Qt, SWING etc.
+
++ source/
+ + the main cross-platform chunk of source
+
++ inc/
+ + cross-platform abstraction headers
+
++ headless/
+ + a backend renderer that draws to bitmaps
+
++ android/
+ + Android backend
+
++ osx/
+ + macOS backend
+
++ ios/
+ + iOS backend
+
++ quartz/
+ + code common to macOS and iOS
+
++ win/
+ + Windows backend
+
++ qt5/
+ + Qt5
+
++ qt6/
+ + Qt6
+
++ unx/
+ + X11 backend and its sub-platforms
+ + gtk3/
+ + GTK3 support
+ + gtk4/
+ + GTK4 support (experimental)
+ + kf5/
+ + KF5 support (based on qt5 VCL plugin mentioned above)
+ + kf6
+ + KF6 support (based on qt6 VCL plugin mentioned above)
+ + gtk3_kde5/
+ + GTK3 support with KDE5 file pickers (alternative to native kf5 one)
+ + generic/
+ + raw X11 support
+
+ + dtrans/
+ + "data transfer" - clipboard handling
+ + http://stackoverflow.com/questions/3261379/getting-html-source-or-rich-text-from-the-x-clipboard
+ for tips how to show the current content of the
+ clipboard
+
+
++ How the platform abstraction works
+
+ + InitVCL calls 'CreateSalInstance'
+ + this is implemented by the compiled platform backends
+ + the SalInstance vtable is the primary outward facing gateway
+ API for platform backends
+ + It is a factory for:
+ SalFrames, SalVirtualDevices, SalPrinters,
+ Timers, the SolarMutex, Drag&Drop and other
+ objects, as well as the primary event loop wrapper.
+
+Note: references to "SV" in the code mean StarView, which was a
+portable C++ class library for GUIs, with very old roots, that was
+developed by StarDivision. Nowadays it is not used by anything except
+LibreOffice (and OpenOffice).
+
+"svp" stands for "StarView Plugin".
+
+## SalData implementations
+
+Each backend must provide an implementation of the SalData class. There is no
+defined interface, so feel free to implement whatever suits your platform.
+
+If your platform does font handling based on Freetype and Fontconfig, it's
+highly recommended to use GenericUnixSalData as the base class; there isn't
+really much *nix stuff in it.
+
+Currently Windows, iOS and MacOSX have independent SalData implementations.
+
+## COM Threading
+
+The way COM is used in LO generally:
+- vcl puts main thread into Single-threaded Apartment (STA)
+- oslWorkerWrapperFunction() puts every thread spawned via `oslCreateThread()`
+ into MTA (free-threaded)
+
+## GDIMetafile
+
+GDIMetafile is a vector drawing representation that corresponds directly
+to the SVM (StarView Metafile) format; it is extremely important as
+an intermediate format in all sorts of drawing and printing operations.
+
+There is a class `MetafileXmlDump` in `include/vcl/mtfxmldump.hxx` that
+can store a GDIMetafile as XML, which makes debugging much easier
+since you can just use "diff" to see changes.
+
+## EMF+
+
+emf+ is vector file format used by MSO and is successor of wmf and
+emf formats. see
+http://msdn.microsoft.com/en-us/library/cc230724.aspx for
+documentation. note that we didn't have this documentation from
+start, so part of the code predates to the time when we had guessed
+some parts and can be enhanced today. there also still many thing not
+complete
+
+emf+ is handled a bit differently compared to original emf/wmf files,
+because GDIMetafile is missing features we need (mostly related to
+transparency, argb colors, etc.)
+
+emf/wmf is translated to GDIMetafile in import filter
+`vcl/source/filter/wmf` and so special handling ends here
+
+emf+ is encapsulated into GDIMetafile inside comment records and
+parsed/rendered later, when it reaches cppcanvas. It is parsed and
+rendered in cppcanvas/source/mtfrenderer. also note that there are
+emf+-only and emf+-dual files. dual files contains both types of
+records (emf and emf+) for rendering the images. these can used also
+in applications which don't know emf+. in that case we must ignore
+emf records and use emf+ for rendering. for more details see
+the documentation.
+
+## Parsing
+
+ wmf/emf filter --> GDI metafile with emf+ in comments --> cppcanvas metafile renderer
+
+lately the GDIMetafile rendering path changed which also influenced
+emf+ rendering. now many things happen in drawing layer, where
+GDIMetafile is translated into drawing layer primitives. for
+metafiles with emf+ we let the mtfrenderer render them into bitmap
+(with transparency) and use this bitmap in drawinlayer. cleaner
+solution for current state would be to extend the drawing layer for
+missing features and move parsing into drawing layer (might be quite
+a lot of work). intermediary enhancement would be to know better the
+needed size/resolution of the bitmap, before we render emf+ into
+bitmap in drawing layer. Thorsten is working on the same problem with
+svg rendering, so hopefully his approach could be extended for emf+ as
+well. the places in drawing layer where we use canvas mtfrenderer to
+render into bitmaps can be found when you grep for GetUseCanvas. also
+look at vcl/source/gdi/gdimetafile.cxx where you can look for
+UseCanvas again. moving the parsing into drawinglayer might also have
+nice side effect for emf+-dual metafiles. in case the emf+ records
+are broken, it would be easier to use the duplicit emf
+rendering. fortunately we didn't run into such a broken emf+ file
+yet. but there were already few cases where we first though that the
+problem might be because of broken emf+ part. so far it always turned
+out to be another problem.
+
+## Rendering
+
+ before
+
+ vcl --> cppcanvas metafile renderer --> vcl
+
+ now
+
+ drawing layer --> vcl --> cppcanvas metafile renderer --> vcl --> drawing layer
+
+another interesting part is actual rendering into canvas bitmap and
+using that bitmap later in code using vcl API.
+
+EMF+ implementation has some extensive logging, best if you do a dbgutil
+build, and then
+
+ export SAL_LOG=+INFO.cppcanvas.emf+INFO.vcl.emf
+
+before running LibreOffice; it will give you lots of useful hints.
+
+You can also fallback to EMF (from EMF+) rendering via
+
+ export EMF_PLUS_DISABLE=1
+
+
+## Printing/PDF Export
+
+Printing from Writer works like this:
+
+1) individual pages print by passing an appropriate OutputDevice to XRenderable
+2) in drawinglayer, a VclMetafileProcessor2D is used to record everything on
+ the page (because the OutputDevice has been set up to record a GDIMetaFile)
+3) the pages' GDIMetaFiles are converted to PDF by the vcl::PDFWriter
+ in `vcl/source/gdi/pdfwriter*`
+
+Creating the ODF thumbnail for the first page works as above except step 3 is:
+
+3) the GDIMetaFile is replayed to create the thumbnail
+
+On-screen display differs in step 1 and 2:
+
+1) the VCL Window gets invalidated somehow and paints itself
+2) in drawinglayer, a `VclPixelProcessor2D` is used to display the content
+
+
+### Debugging PDF export
+
+Debugging the PDF export becomes much easier when
+compression is disabled (so the PDF file is directly readable) and
+the MARK function puts comments into the PDF file about which method
+generated the following PDF content.
+
+The compression can be disabled even using an env. var:
+
+ export VCL_DEBUG_DISABLE_PDFCOMPRESSION=1
+
+To de-compress the contents of a PDF file written by a release build or
+other programs, use the "pdfunzip" tool:
+
+ bin/run pdfunzip input.pdf output.pdf
+
+### SolarMutexGuard
+
+The solar mutex is the "big kernel lock" of LibreOffice, a global one. It's a
+recursive mutex, so it's allowed to take the lock on the same thread multiple
+times, and only the last unlock will actually release the mutex.
+
+UNO methods on components can be called from multiple threads, while the
+majority of the codebase is not prepared for multi-threading. One way to get
+around this mismatch is to create a SolarMutexGuard instance at the start of
+each & every UNO method implementation, but only when it is necessary:
+
+- Only acquire the SolarMutex if you actually need it (e.g., not in functions
+ that return static information).
+
+- Only around the code that actually needs it (i.e., never call out with it
+ locked).
+
+This way you ensure that code (not prepared for multithreading) is still
+executed only on a single thread.
+
+In case you expect that your caller takes the solar mutex, then you can use
+the `DBG_TESTSOLARMUTEX()` macro to assert that in dbgutil builds.
+
+Event listeners are a special (but frequent) case of the "never call out with
+a mutex (`SolarMutex` or other) locked" fundamental rule:
+
+- UNO methods can be called from multiple threads, so most implementations
+ take the solar mutex as their first action when necessary.
+
+- This can be problematic if later calling out (an event handler is called),
+ where the called function may be an UNO method implementation as well and
+ may be invoked on a different thread.
+
+- So we try to not own the solar mutex, whenever we call out (invoke event
+ listeners).
+
+In short, never hold any mutex unless necessary, especially not when calling
+out.
+
+## Read More
+* [Environment variables in VCL](README.vars.md)
+* [GDIMetaFile class](README.GDIMetaFile.md)
+* [Understanding transitional VCL lifecycle](README.lifecycle.md)
+* [VCL scheduler](README.scheduler.md)
diff --git a/vcl/README.scheduler.md b/vcl/README.scheduler.md
new file mode 100644
index 0000000000..b213448762
--- /dev/null
+++ b/vcl/README.scheduler.md
@@ -0,0 +1,448 @@
+# VCL Scheduler
+
+## Introduction
+
+The VCL scheduler handles LOs primary event queue. It is simple by design,
+currently just a single-linked list, processed in list-order by priority
+using round-robin for reoccurring tasks.
+
+The scheduler has the following behaviour:
+
+B.1. Tasks are scheduled just priority based
+
+B.2. Implicitly cooperative AKA non-preemptive
+
+B.3. It's not "fair" in any way (a consequence of B.2)
+
+B.4. Tasks are handled round-robin (per priority)
+
+B.5. Higher priorities have lower values
+
+B.6. A small set of priorities instead of an flexible value AKA int
+
+There are some consequences due to this design.
+
+C.1. Higher priority tasks starve lower priority tasks
+ As long as a higher task is available, lower tasks are never run!
+ See Anti-pattern.
+
+C.2. Tasks should be split into sensible blocks
+ If this can't really be done, process pending tasks by calling
+ `Application::Reschedule()`. Or use a thread.
+
+C.3. This is not an OS scheduler
+ There is no real way to "fix" B.2. and B.3.
+ If you need to do a preemptive task, use a thread!
+ Otherwise make your task suspendable.
+
+
+## Driving the scheduler AKA the system timer
+
+ 1. There is just one system timer, which drives LO event loop
+ 2. The timer has to run in the main window thread
+ 3. The scheduler is run with the Solar mutex acquired
+ 4. The system timer is a single-shot timer
+ 5. The scheduler system event / message has a low system priority.
+ All system events should have a higher priority.
+
+Every time a task is started, the scheduler timer is adjusted. When the timer
+fires, it posts an event to the system message queue. If the next most
+important task is an Idle (AKA instant, 0ms timeout), the event is pushed to
+the back of the queue, so we don't starve system messages, otherwise to the
+front.
+
+Every time the scheduler is invoked it searches for the next task to process,
+restarts the timer with the timeout for the next event and then invokes the
+task. After invoking the task and if the task is still active, it is pushed
+to the end of the queue and the timeout is eventually adjusted.
+
+
+## Locking
+
+The locking is quite primitive: all interaction with internal Scheduler
+structures are locked. This includes the `ImplSchedulerContext` and the
+`Task::mpSchedulerData`, which is actually a part of the scheduler.
+Before invoking the task, we have to release the lock, so others can
+Start new Tasks.
+
+The Scheduler just processes its own Tasks in the main thread and needs
+the `SolarMutex` for it and for `DeInit` (tested by `DBG_TESTSOLARMUTEX`). All
+the other interaction just take the scheduler mutex or don't need locking
+at all.
+
+There is a "workaround" for static Task objects, which would crash LO on
+destruction, because `Task::~Task` would try to de-register itself in the
+Scheduler, while the `SchedulerLock` would be long gone. OTOH this makes
+Task handling less error-prone, than doing "special" manual cleanup.
+There is also a "= TODOs and ideas =" to get rid if static Tasks.
+
+Actually the scheduler mutex should never be locked when calling into
+non-scheduler code, so it was converted to a non-recursive
+`std::mutex`.
+
+
+## Idle processing
+
+Confusingly, there are 2 concepts that are called 'idle':
+
+* Instant (zero timeout) tasks, represented e.g. by the Idle class. This is
+a misnomer, as these tasks are processed after returning to the main loop.
+This is not necessarily when LO is idle, in fact such tasks may be invoked
+while there is input in the OS event queue pending.
+(TODO: This case should be fixed by renaming.)
+
+* Low priority tasks, represented by priorities `TaskPriority::HIGH_IDLE` and
+lower. In addition to being invoked only when there is no task with a higher
+priority, pending input in the OS event queue also takes precedence.
+
+
+## Lifecycle / thread-safety of Scheduler-based objects
+
+A scheduler object it thread-safe in the way, that it can be associated to
+any thread and any thread is free to call any functions on it. The owner must
+guarantee that the `Invoke()` function can be called, while the Scheduler object
+exists / is not disposed.
+
+
+## Anti-pattern: Dependencies via (fine grained) priorities
+
+"Idle 1" should run before "Idle 2", therefore give "Idle 1" a higher priority
+then "Idle 2". This just works correct for low frequency idles, but otherwise
+always breaks!
+
+If you have some longer work - even if it can be split by into schedulable,
+smaller blocks - you normally don't want to schedule it with a non-default
+priority, as it starves all lower priority tasks. Even if a block was processed
+in "Idle 1", it is scheduled with the same (higher) priority again. Changing
+the "Idle" to a "Timer" also won't work, as this breaks the dependency.
+
+What is needed is task based dependency handling, so if "Task 1" is done, it
+has to start "Task 2" and if "Task 1" is started again, it has to stop
+"Task 2". This currently has to be done by the implementor, but this feature
+can be added to the scheduler reasonably.
+
+
+## Implementation details
+
+### General: event priority for `DoYield`
+
+There are three types of events, with different priority:
+
+1. LO user events
+2. System events
+3. LO Scheduler event
+
+They should be processed according to the following code:
+
+```
+bool ImplYield(bool bWait, bool bAllCurrent)
+{
+ DBG_TESTSOLARMUTEX();
+ assert(IsMainThread());
+
+ bool bWasEvent = ProcessUserEvents( bAllCurrent );
+ if ( !bAllCurrent && bWasEvent )
+ return true;
+
+ SolarMutexReleaser();
+ bWasEvent = ProcessSystemEvents( bAllCurrent, &bWasSchedulerEvent ) || bWasEvent;
+ if ( !bWasSchedulerEvent && IsSchedulerEvent() )
+ {
+ ProcessSchedulerEvent()
+ bWasEvent = true;
+ }
+ if ( !bWasEvent && bWait )
+ {
+ WaitForSystemEvents();
+ bWasEvent = true;
+ }
+ return bWasEvent;
+}
+```
+
+### General: main thread deferral
+
+In almost all VCL backends, we run main thread deferrals by disabling the
+`SolarMutex` using a boolean. In the case of the redirect, this makes
+`tryToAcquire` and `doAcquire` return `true` or `1`, while a release is ignored.
+Also the `IsCurrentThread()` mutex check function will act accordingly, so all
+the `DBG_TESTSOLARMUTEX` won't fail.
+
+Since we just disable the locks when we start running the deferred code in the
+main thread, we won't let the main thread run into stuff, where it would
+normally wait for the SolarMutex.
+
+Eventually this will move into the `SolarMutex`. KDE / Qt also does main
+thread redirects using `Qt::BlockingQueuedConnection`.
+
+### General: processing all current events for `DoYield`
+
+This is easily implemented for all non-priority queue based implementations.
+Windows and macOS both have a timestamp attached to their events / messages,
+so simply get the current time and just process anything < timestamp.
+For the **KDE** backend this is already the default behaviour - single event
+processing isn't even supported. The headless backend accomplishes this by
+just processing a copy of the list of current events.
+
+Problematic in this regard is the **Gtk+** backend. `g_main_context_iteration`
+dispatches "only those highest priority event sources". There is no real way
+to tell, when these became ready. I've added a workaround idea to the TODO
+list. FWIW: **Qt** runs just a single timer source in the glib main context,
+basically the same we're doing with the LO scheduler as a system event.
+
+The gen **X11** backend has some levels of redirection, but needs quite some work
+to get this fixed.
+
+### General: non-main thread yield
+
+Yielding from a non-main thread must not wait in the main thread, as this
+may block the main thread until some events happen.
+
+Currently we wait on an extra conditional, which is cleared by the main event
+loop.
+
+### General: invalidation of elapsed timer event messages
+
+Since the system timer to run the scheduler is single-shot, there should never
+be more than one elapsed timer event in system event queue. When stopping or
+restarting the timer, we eventually have to remove the now invalid event from
+the queue.
+
+But for the Windows and macOS backends this may fail as they have delayed
+posting of events, so a consecutive remove after a post will actually yield no
+remove. On Windows we even get unwanted processing of events outside of the
+main event loop, which may call the Scheduler, as timer management is handled
+in critical scheduler code.
+
+To prevent these problems, we don't even try to remove these events, but
+invalidate them by versioning the timer events. Timer events with invalid
+versions are processed but simply don't run the scheduler.
+
+### General: track time of long running tasks
+
+There is `TaskStopwatch` class. It'll track the time and report a timeout either
+when the tasks time slice is finished or some system event did occur.
+
+Eventually it will be merged into the main scheduler, so each invoked task can
+easily track it's runtime and eventually this can be used to "blame" / find
+other long running tasks, so interactivity can be improved.
+
+There were some questions coming up when implementing it:
+
+#### Why does the scheduler not detect that we only have idle tasks pending, and skip the instant timeout?
+
+You never know how long a task will run. Currently the scheduler simply asks
+each task when it'll be ready to run, until two runnable tasks are found.
+Normally this is very quick, as LO has a lot of one-shot instant tasks / Idles
+and just a very few long term pending Timers.
+
+Especially UNO calls add a lot of Idles to the task list, which just need to
+be processed in order.
+
+#### Why not use things like Linux timer wheels?
+
+LO has relatively few timers and a lot one-shot Idles. 99% of time the search
+for the next task is quick, because there are just ~5 long term timers per
+document (cache invalidation, cursor blinking etc.).
+
+This might become a problem, if you have a lot of open documents, so the long
+term timer list increases AKA for highly loaded LOOL instances.
+
+But the Linux timer wheel mainly relies on the facts that the OS timers are
+expected to not expire, as they are use to catch "error" timeouts, which rarely
+happen, so this definitely not matches LO's usage.
+
+#### Not really usable to find misbehaving tasks
+
+The TaskStopwatch class is just a little time keeper + detecting of input
+events. This is not about misbehaving Tasks, but long running tasks, which
+have to yield to the Scheduler, so other Tasks and System events can be
+processed.
+
+There is the TODO to merge the functionality into the Scheduler itself, at
+which point we can think about profiling individual Tasks to improve
+interactivity.
+
+### macOS implementation details
+
+Generally the Scheduler is handled as expected, except on resize, which is
+handled with different runloop-modes in macOS. In case of a resize, the normal
+`runloop` is suspended in `sendEvent`, so we can't call the scheduler via posted
+main loop-events. Instead the scheduler uses the timer again.
+
+Like the Windows backend, all Cocoa / GUI handling also has to be run in
+the main thread. We're emulating Windows out-of-order PeekMessage processing,
+via a `YieldWakeupEvent` and two conditionals. When in a `RUNINMAIN` call, all
+the `DBG_TESTSOLARMUTEX` calls are disabled, as we can't release the
+`SolarMutex`, but we can prevent running any other `SolarMutex` based code.
+Those wakeup events must be ignored to prevent busy-locks. For more info read
+the "General: main thread deferral" section.
+
+We can neither rely on macOS `dispatch_sync` code block execution nor the
+message handling, as both can't be prioritized or filtered and the first
+does also not allow nested execution and is just processed in sequence.
+
+There is also a workaround for a problem for pushing tasks to an empty queue,
+as `[NSApp postEvent: ... atStart: NO]` doesn't append the event, if the
+message queue is empty.
+
+An additional problem is the filtering of events on Window close. This drops
+posted timer events, when a Window is closed resulting in a busy DoYield loop,
+so we have to re-post the event, after closing a window.
+
+### Windows implementation details
+
+Posted or sent event messages often trigger processing of WndProc in
+`PeekMessage`, `GetMessage` or `DispatchMessage`, independently from the message
+to fetch, remove or dispatch ("During this call, the system delivers pending,
+nonqueued messages..."). Additionally messages have an inherited priority
+based on the function used to generate them. Even if `WM_TIMER` messages should
+have the lowest priority, a manually posted `WM_TIMER` is processed with the
+priority of a PostMessage message.
+
+So we're giving up on processing all our Scheduler events as a message in the
+system message loop. Instead we just indicate a 0ms timer message by setting
+the `m_bDirectTimeout` in the timer object. This timer is always processed, if
+the system message wasn't already our timer. As a result we can also skip the
+polling. All this is one more reason to drop the single message processing
+in favour of always processing all pending (system) events.
+
+There is another special case, we have to handle: window updates during move
+and resize of windows. These system actions run in their own nested message
+loop. So we have to completely switch to timers, even for 0 ms. But these
+posted events prevent any event processing, while we're busy. The only viable
+solution seems to be to switch to `WM_TIMER` based timers, as these generate
+messages with the lowest system priority (but they don't allow 0 ms timeouts).
+So processing slows down during resize and move, but we gain working painting,
+even when busy.
+
+An additional workaround is implemented for the delayed queuing of posted
+messages, where `PeekMessage` in `WinSalTimer::Stop()` won't be able remove the
+just posted timer callback message. See "General: invalidation of elapsed
+timer event messages" for the details.
+
+To run the required GUI code in the main thread without unlocking the
+`SolarMutex`, we "disable" it. For more infos read the "General: main thread
+deferral" section.
+
+### KDE implementation details
+
+This implementation also works as intended. But there is a different Yield
+handling, because Qts `QAbstractEventDispatcher::processEvents` will always
+process all pending events.
+
+
+## TODOs and ideas
+
+### Task dependencies AKA children
+
+Every task can have a list of children / a child.
+
+ * When a task is stopped, the children are started.
+ * When a task is started, the children are stopped.
+
+This should be easy to implement.
+
+### Per priority time-sorted queues
+
+This would result in O(1) scheduler. It was used in the Linux kernel for some
+time (search Ingo Molnar's O(1) scheduler). This can be a scheduling
+optimization, which would prevent walking longer event list. But probably the
+management overhead would be too large, as we have many one-shot events.
+
+To find the next task the scheduler just walks the (constant) list of priority
+queues and schedules the first ready event of any queue.
+
+The downside of this approach: Insert / Start / Reschedule(for "auto" tasks)
+now need O(log(n)) to find the position in the queue of the priority.
+
+### Always process all (higher priority) pending events
+
+Currently `Application::Reschedule()` processes a single event or "all" events,
+with "all" defined as "100 events" in most backends. This already is ignored
+by the KDE backend, as Qt defines its `QAbstractEventDispatcher::processEvents`
+processing all pending events (there are ways to skip event classes, but no
+easy way to process just a single event).
+
+Since the Scheduler is always handled by the system message queue, there is
+really no more reasoning to stop after 100 events to prevent LO Scheduler
+starvation.
+
+### Drop static inherited or composed Task objects
+
+The sequence of destruction of static objects is not defined. So a static Task
+can not be guaranteed to happen before the Scheduler. When dynamic unloading
+is involved, this becomes an even worse problem. This way we could drop the
+mbStatic workaround from the Task class.
+
+### Run the LO application in its own thread
+
+This would probably get rid of most of the macOS and Windows implementation
+details / workarounds, but is quite probably a large amount of work.
+
+Instead of LO running in the main process / thread, we run it in a 2nd thread
+and defer al GUI calls to the main thread. This way it'll hopefully not block
+and can process system events.
+
+That's just a theory - it definitely needs more analysis before even attending
+an implementation.
+
+### Re-evaluate the macOS `ImplNSAppPostEvent`
+
+Probably a solution comparable to the Windows backends delayed PostMessage
+workaround using a validation timestamp is better then the current peek,
+remove, re-postEvent, which has to run in the main thread.
+
+Originally I didn't evaluate, if the event is actually lost or just delayed.
+
+### Drop `nMaxEvents` from Gtk+ based backends
+
+```
+gint last_priority = G_MAXINT;
+bool bWasEvent = false;
+do {
+ gint max_priority;
+ g_main_context_acquire( NULL );
+ bool bHasPending = g_main_context_prepare( NULL, &max_priority );
+ g_main_context_release( NULL );
+ if ( bHasPending )
+ {
+ if ( last_priority > max_priority )
+ {
+ bHasPending = g_main_context_iteration( NULL, bWait );
+ bWasEvent = bWasEvent || bHasPending;
+ }
+ else
+ bHasPending = false;
+ }
+}
+while ( bHasPending )
+```
+
+The idea is to use `g_main_context_prepare` and keep the `max_priority` as an
+indicator. We cannot prevent running newer lower events, but we can prevent
+running new higher events, which should be sufficient for most stuff.
+
+This also touches user event processing, which currently runs as a high
+priority idle in the event loop.
+
+### Drop nMaxEvents from gen (X11) backend
+
+A few layers of indirection make this code hard to follow. The `SalXLib::Yield`
+and `SalX11Display::Yield` architecture makes it impossible to process just the
+current events. This really needs a refactoring and rearchitecture step, which
+will also affect the Gtk+ and KDE backend for the user event handling.
+
+### Merge TaskStopwatch functionality into the Scheduler
+
+This way it can be easier used to profile Tasks, eventually to improve LO's
+interactivity.
+
+## See Also
+
+- [Solar Mutex](https://wiki.openoffice.org/wiki/Terms/Solar_Mutex)
+- [LibreOffice Main Loop](https://wiki.documentfoundation.org/Development/LHM_LiMux/Main_Loop)
+- [AOO Advanced Threading-Architecture (proposal)](https://wiki.openoffice.org/wiki/Architecture/Proposal/Advanced_Threading-Architecture)
+- [Revise OOo Multi-Threading Efforts](https://wiki.openoffice.org/wiki/Effort/Revise_OOo_Multi-Threading)
+- [Multi-Threading Analysis](https://wiki.openoffice.org/wiki/Analysis/Multi-Threading)
+- [AOO Wiki - Category:Multi-Threading](https://wiki.openoffice.org/wiki/Category:Multi-Threading)
diff --git a/vcl/README.vars.md b/vcl/README.vars.md
new file mode 100644
index 0000000000..92931b51f1
--- /dev/null
+++ b/vcl/README.vars.md
@@ -0,0 +1,74 @@
+# Environment variables in VCL
+
+## General
+
+These are the general environment variables used in the VCL:
+
+* `SAL_USE_VCLPLUGIN` - use a VCL plugin
+* `SAL_RTL_ENABLED` - Enable RTL UI
+* `SAL_NO_NWF` - disable native widgets
+* `SAL_FORCEDPI` - force a specific DPI (gtk3 & qt5/kf5 plugins only)
+* `SAL_FORCE_HC` - force high-contrast mode
+* `SAL_USE_SYSTEM_LOOP` - calls std::abort on nested event loop calls. Currently just for Qt with many crashes. WIP.
+
+* `SAL_NO_FONT_LOOKUP` - disable font search and fallback and always use a hard-coded font name (for some unit tests)
+* `SAL_NON_APPLICATION_FONT_USE` - control use of non-bundled fonts, values are `deny` or `abort`;
+ for now only works on platforms using fontconfig (i.e., on Linux, but neither on macOS nor on
+ Windows); also see gb_CppunitTest_set_non_application_font_use for using it in unit tests
+* `SAL_ALLOW_DEFAULT_HINTING` - use default font hinting for the platform. Enables medium/full hinting
+ style which is otherwise reverted to (s)light.
+
+* `LO_COLLECT_UIINFO` - enable the uitesting logging, value is expected to be a relative file name that
+will be used to write the log under `instdir/uitest/`.
+
+* `VCL_DOUBLEBUFFERING_AVOID_PAINT` - don't paint the buffer, useful to see where we do direct painting
+* `VCL_DOUBLEBUFFERING_FORCE_ENABLE` - enable double buffered painting
+* `VCL_DOUBLEBUFFERING_ENABLE` - enable a safe subset of double buffered painting (currently in Writer, not in any other applications)
+
+* `VCL_DEBUG_DISABLE_PDFCOMPRESSION` - disable compression in the PDF writer
+
+* `SAL_DISABLE_WATCHDOG` - don't start the thread that watches for broken GL/Vulkan/OpenCL drivers
+
+* `SAL_NO_MOUSEGRABS` - for debugging - stop blocking UI if a breakpoint is hit
+
+## Gtk+
+
+* `VCL_GTK3_PAINTDEBUG` - in debug builds, if set to `1` then holding down `shift+0` forces a redraw event, `shift+1` repaints everything, and
+`shift+2` dumps cairo frames to pngs as `/tmp/frame<n>.png`
+* `GDK_SCALE=2` - for HiDPI scaling (just supports integers)
+
+## Bitmap
+
+* `VCL_NO_THREAD_SCALE` - disable threaded bitmap scale
+* `VCL_NO_THREAD_IMPORT` - disable threaded bitmap import
+* `EMF_PLUS_DISABLE` - use EMF rendering and ignore EMF+ specifics
+
+## OpenGL
+
+* `SAL_DISABLEGL` - disable OpenGL use
+* `SAL_GL_NO_SWAP` - disable buffer swapping if set (should show nothing)
+* `SAL_GL_SLEEP_ON_SWAP` - sleep for half a second on each swap-buffers.
+
+## Skia
+
+* `SAL_DISABLESKIA=1` - force disabled Skia
+* `SAL_ENABLESKIA=1` - enable Skia, unless denylisted (and if the VCL backend supports Skia)
+* `SAL_FORCESKIA=1` - force using Skia, even if denylisted
+* `SAL_SKIA=raster|vulkan|metal` - select Skia's drawing method, by default Vulkan or Metal are used if available
+* `SAL_DISABLE_SKIA_CACHE=1` - disable caching of complex images
+* `SAL_SKIA_KEEP_BITMAP_BUFFER=1` - `SkiaSalBitmap` will keep its bitmap buffer even after storing in `SkImage`
+
+## OpenGL,Skia
+
+* `SAL_WITHOUT_WIDGET_CACHE` - disable LRU caching of native widget textures
+
+## Qt
+
+* `QT_SCALE_FACTOR=2` - for HiDPI testing (also supports float)
+* `SAL_VCL_QT5_NO_FONTCONFIG` - ignore fontconfig provided font substitutions
+* `SAL_VCL_QT5_NO_NATIVE` - disable `QStyle`'d controls
+* `SAL_VCL_QT_USE_QFONT` - use `QFont` for text layout and rendering (default is to use cairo)
+
+## Mac
+
+* `SAL_FORCE_HIDPI_SCALING` - set to 2 to fake HiDPI drawing (useful for unittests, windows may draw only top-left 1/4 of the content scaled)
diff --git a/vcl/StaticLibrary_fuzzer_calc.mk b/vcl/StaticLibrary_fuzzer_calc.mk
new file mode 100644
index 0000000000..5bb543f566
--- /dev/null
+++ b/vcl/StaticLibrary_fuzzer_calc.mk
@@ -0,0 +1,25 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$(eval $(call gb_StaticLibrary_StaticLibrary,fuzzer_calc))
+
+$(eval $(call gb_StaticLibrary_set_include,fuzzer_calc,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_StaticLibrary_use_api,fuzzer_calc,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_StaticLibrary_add_generated_exception_objects,fuzzer_calc,\
+ CustomTarget/vcl/workben/native-calc \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/StaticLibrary_fuzzer_core.mk b/vcl/StaticLibrary_fuzzer_core.mk
new file mode 100644
index 0000000000..6fa58b10b4
--- /dev/null
+++ b/vcl/StaticLibrary_fuzzer_core.mk
@@ -0,0 +1,25 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$(eval $(call gb_StaticLibrary_StaticLibrary,fuzzer_core))
+
+$(eval $(call gb_StaticLibrary_set_include,fuzzer_core,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_StaticLibrary_use_api,fuzzer_core,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_StaticLibrary_add_generated_exception_objects,fuzzer_core,\
+ CustomTarget/vcl/workben/native-core \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/StaticLibrary_fuzzer_draw.mk b/vcl/StaticLibrary_fuzzer_draw.mk
new file mode 100644
index 0000000000..69c2cd5ab6
--- /dev/null
+++ b/vcl/StaticLibrary_fuzzer_draw.mk
@@ -0,0 +1,25 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$(eval $(call gb_StaticLibrary_StaticLibrary,fuzzer_draw))
+
+$(eval $(call gb_StaticLibrary_set_include,fuzzer_draw,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_StaticLibrary_use_api,fuzzer_draw,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_StaticLibrary_add_generated_exception_objects,fuzzer_draw,\
+ CustomTarget/vcl/workben/native-draw \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/StaticLibrary_fuzzer_math.mk b/vcl/StaticLibrary_fuzzer_math.mk
new file mode 100644
index 0000000000..ba5d03889a
--- /dev/null
+++ b/vcl/StaticLibrary_fuzzer_math.mk
@@ -0,0 +1,25 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$(eval $(call gb_StaticLibrary_StaticLibrary,fuzzer_math))
+
+$(eval $(call gb_StaticLibrary_set_include,fuzzer_math,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_StaticLibrary_use_api,fuzzer_math,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_StaticLibrary_add_generated_exception_objects,fuzzer_math,\
+ CustomTarget/vcl/workben/native-math \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/StaticLibrary_fuzzer_writer.mk b/vcl/StaticLibrary_fuzzer_writer.mk
new file mode 100644
index 0000000000..91765353fa
--- /dev/null
+++ b/vcl/StaticLibrary_fuzzer_writer.mk
@@ -0,0 +1,25 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$(eval $(call gb_StaticLibrary_StaticLibrary,fuzzer_writer))
+
+$(eval $(call gb_StaticLibrary_set_include,fuzzer_writer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_StaticLibrary_use_api,fuzzer_writer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_StaticLibrary_add_generated_exception_objects,fuzzer_writer,\
+ CustomTarget/vcl/workben/native-writer \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/StaticLibrary_vclmain.mk b/vcl/StaticLibrary_vclmain.mk
new file mode 100644
index 0000000000..cc283cc0d0
--- /dev/null
+++ b/vcl/StaticLibrary_vclmain.mk
@@ -0,0 +1,42 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_StaticLibrary_StaticLibrary,vclmain))
+
+ifeq ($(OS),iOS)
+$(eval $(call gb_StaticLibrary_add_cxxflags,vclmain,\
+ $(gb_OBJCXXFLAGS) \
+))
+endif
+
+$(eval $(call gb_StaticLibrary_set_include,vclmain,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_StaticLibrary_use_api,vclmain,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_StaticLibrary_add_exception_objects,vclmain,\
+ vcl/source/salmain/salmain \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/UIConfig_vcl.mk b/vcl/UIConfig_vcl.mk
new file mode 100644
index 0000000000..10a823cc87
--- /dev/null
+++ b/vcl/UIConfig_vcl.mk
@@ -0,0 +1,39 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_UIConfig_UIConfig,vcl))
+
+$(eval $(call gb_UIConfig_add_uifiles,vcl,\
+ vcl/uiconfig/ui/aboutbox \
+ vcl/uiconfig/ui/combobox \
+ vcl/uiconfig/ui/cupspassworddialog \
+ vcl/uiconfig/ui/dockingwindow \
+ vcl/uiconfig/ui/editmenu \
+ vcl/uiconfig/ui/errornocontentdialog \
+ vcl/uiconfig/ui/errornoprinterdialog \
+ vcl/uiconfig/ui/interimdockparent \
+ vcl/uiconfig/ui/interimtearableparent \
+ vcl/uiconfig/ui/menutogglebutton3 \
+ vcl/uiconfig/ui/menutogglebutton4 \
+ vcl/uiconfig/ui/printdialog \
+ vcl/uiconfig/ui/printerdevicepage \
+ vcl/uiconfig/ui/printerpaperpage \
+ vcl/uiconfig/ui/printerpropertiesdialog \
+ vcl/uiconfig/ui/printprogressdialog \
+ vcl/uiconfig/ui/querydialog \
+ vcl/uiconfig/ui/screenshotparent \
+ vcl/uiconfig/ui/wizard \
+ vcl/uiconfig/ui/openlockedquerybox \
+))
+
+$(eval $(call gb_UIConfig_add_a11yerrors_uifiles,vcl,\
+ vcl/qa/cppunit/builder/demo \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/WinResTarget_vcl.mk b/vcl/WinResTarget_vcl.mk
new file mode 100644
index 0000000000..c32f5e3538
--- /dev/null
+++ b/vcl/WinResTarget_vcl.mk
@@ -0,0 +1,98 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_WinResTarget_WinResTarget,vcl/salsrc))
+
+$(eval $(call gb_WinResTarget_set_include,vcl/salsrc,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_WinResTarget_set_rcfile,vcl/salsrc,\
+ vcl/win/src/salsrc \
+))
+$(eval $(call gb_WinResTarget_add_dependencies,vcl/salsrc,\
+ vcl/win/src/dtext.cur \
+ vcl/win/src/50.bmp \
+ vcl/win/src/copydata.cur \
+ vcl/win/src/dpie.cur \
+ vcl/win/src/movedata.cur \
+ vcl/win/src/rotate.cur \
+ vcl/win/src/50.png \
+ vcl/win/src/copydlnk.cur \
+ vcl/win/src/dpolygon.cur \
+ vcl/win/src/movedlnk.cur \
+ vcl/win/src/salsrc.rc \
+ vcl/win/src/copyf.cur \
+ vcl/win/src/drect.cur \
+ vcl/win/src/movef.cur \
+ vcl/win/src/ase.cur \
+ vcl/win/src/copyf2.cur \
+ vcl/win/src/dtext.cur \
+ vcl/win/src/movef2.cur \
+ vcl/win/src/tblsele.cur \
+ vcl/win/src/asn.cur \
+ vcl/win/src/copyflnk.cur \
+ vcl/win/src/fill.cur \
+ vcl/win/src/moveflnk.cur \
+ vcl/win/src/tblsels.cur \
+ vcl/win/src/asne.cur \
+ vcl/win/src/crook.cur \
+ vcl/win/src/movept.cur \
+ vcl/win/src/tblselse.cur \
+ vcl/win/src/asns.cur \
+ vcl/win/src/crop.cur \
+ vcl/win/src/tblselsw.cur \
+ vcl/win/src/asnswe.cur \
+ vcl/win/src/hshear.cur \
+ vcl/win/src/tblselw.cur \
+ vcl/win/src/asnw.cur \
+ vcl/win/src/darc.cur \
+ vcl/win/src/nullptr.cur \
+ vcl/win/src/ass.cur \
+ vcl/win/src/dbezier.cur \
+ vcl/win/src/asse.cur \
+ vcl/win/src/dcapt.cur \
+ vcl/win/src/vshear.cur \
+ vcl/win/src/assw.cur \
+ vcl/win/src/dcirccut.cur \
+ vcl/win/src/linkdata.cur \
+ vcl/win/src/pivotcol.cur \
+ vcl/win/src/asw.cur \
+ vcl/win/src/dconnect.cur \
+ vcl/win/src/linkf.cur \
+ vcl/win/src/pivotdel.cur \
+ vcl/win/src/aswe.cur \
+ vcl/win/src/dellipse.cur \
+ vcl/win/src/magnify.cur \
+ vcl/win/src/pivotfld.cur \
+ vcl/win/src/chain.cur \
+ vcl/win/src/detectiv.cur \
+ vcl/win/src/mirror.cur \
+ vcl/win/src/pivotrow.cur \
+ vcl/win/src/vtext.cur \
+ vcl/win/src/chainnot.cur \
+ vcl/win/src/dfree.cur \
+ vcl/win/src/chart.cur \
+ vcl/win/src/dline.cur \
+ vcl/win/src/movebw.cur \
+ vcl/win/src/fatcross.cur \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/android/androidinst.cxx b/vcl/android/androidinst.cxx
new file mode 100644
index 0000000000..7c3132d7dd
--- /dev/null
+++ b/vcl/android/androidinst.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/.
+ */
+
+#include <jni.h>
+
+#include <android/looper.h>
+#include <android/bitmap.h>
+
+#include <android/androidinst.hxx>
+#include <headless/svpdummies.hxx>
+#include <headless/svpdata.hxx>
+#include <osl/detail/android-bootstrap.h>
+#include <rtl/strbuf.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <memory>
+#include <unistd.h>
+
+// Horrible hack
+static int viewWidth = 1, viewHeight = 1;
+
+void AndroidSalInstance::GetWorkArea(AbsoluteScreenPixelRectangle& rRect)
+{
+ rRect = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( 0, 0 ),
+ AbsoluteScreenPixelSize( viewWidth, viewHeight ) );
+}
+
+AndroidSalInstance *AndroidSalInstance::getInstance()
+{
+ if (!ImplGetSVData())
+ return NULL;
+ return static_cast<AndroidSalInstance *>(GetSalInstance());
+}
+
+AndroidSalInstance::AndroidSalInstance( std::unique_ptr<SalYieldMutex> pMutex )
+ : SvpSalInstance( std::move(pMutex) )
+{
+ // FIXME: remove when uniPoll & runLoop is the only Android entry point.
+ int res = (lo_get_javavm())->AttachCurrentThread(&m_pJNIEnv, NULL);
+ LOGI("AttachCurrentThread res=%d env=%p", res, m_pJNIEnv);
+}
+
+// This is never called on Android until app exit.
+AndroidSalInstance::~AndroidSalInstance()
+{
+ int res = (lo_get_javavm())->DetachCurrentThread();
+ LOGI("DetachCurrentThread res=%d", res);
+ LOGI("destroyed Android Sal Instance");
+}
+
+bool AndroidSalInstance::AnyInput( VclInputFlags nType )
+{
+ if( nType & VclInputFlags::TIMER )
+ return CheckTimeout( false );
+
+ // Unfortunately there is no way to check for a specific type of
+ // input being queued. That information is too hidden, sigh.
+ return SvpSalInstance::s_pDefaultInstance->HasUserEvents();
+}
+
+void AndroidSalInstance::updateMainThread()
+{
+ int res = (lo_get_javavm())->AttachCurrentThread(&m_pJNIEnv, NULL);
+ LOGI("updateMainThread AttachCurrentThread res=%d env=%p", res, m_pJNIEnv);
+ SvpSalInstance::updateMainThread();
+}
+
+void AndroidSalInstance::releaseMainThread()
+{
+ int res = (lo_get_javavm())->DetachCurrentThread();
+ LOGI("releaseMainThread DetachCurrentThread res=%d", res);
+
+ SvpSalInstance::releaseMainThread();
+}
+
+class AndroidSalSystem : public SvpSalSystem {
+public:
+ AndroidSalSystem() : SvpSalSystem() {}
+ virtual ~AndroidSalSystem() {}
+ virtual int ShowNativeDialog( const OUString& rTitle,
+ const OUString& rMessage,
+ const std::vector< OUString >& rButtons );
+};
+
+SalSystem *AndroidSalInstance::CreateSalSystem()
+{
+ return new AndroidSalSystem();
+}
+
+class AndroidSalFrame : public SvpSalFrame
+{
+public:
+ AndroidSalFrame( AndroidSalInstance *pInstance,
+ SalFrame *pParent,
+ SalFrameStyleFlags nSalFrameStyle )
+ : SvpSalFrame(pInstance, pParent, nSalFrameStyle)
+ {
+ if (pParent == NULL && viewWidth > 1 && viewHeight > 1)
+ SetPosSize(0, 0, viewWidth, viewHeight, SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT);
+ }
+
+ virtual void GetWorkArea(AbsoluteScreenPixelRectangle& rRect)
+ {
+ AndroidSalInstance::getInstance()->GetWorkArea( rRect );
+ }
+
+ virtual void UpdateSettings( AllSettings &rSettings )
+ {
+ // Clobber the UI fonts
+#if 0
+ psp::FastPrintFontInfo aInfo;
+ aInfo.m_aFamilyName = "Roboto";
+ aInfo.m_eItalic = ITALIC_NORMAL;
+ aInfo.m_eWeight = WEIGHT_NORMAL;
+ aInfo.m_eWidth = WIDTH_NORMAL;
+ psp::PrintFontManager::get().matchFont( aInfo, rSettings.GetUILocale() );
+#endif
+
+ // FIXME: is 14 point enough ?
+ vcl::Font aFont( OUString( "Roboto" ), Size( 0, 14 ) );
+
+ StyleSettings aStyleSet = rSettings.GetStyleSettings();
+ aStyleSet.SetAppFont( aFont );
+ aStyleSet.SetHelpFont( aFont );
+ aStyleSet.SetMenuFont( aFont );
+ aStyleSet.SetToolFont( aFont );
+ aStyleSet.SetLabelFont( aFont );
+ aStyleSet.SetRadioCheckFont( aFont );
+ aStyleSet.SetPushButtonFont( aFont );
+ aStyleSet.SetFieldFont( aFont );
+ aStyleSet.SetIconFont( aFont );
+ aStyleSet.SetTabFont( aFont );
+ aStyleSet.SetGroupFont( aFont );
+
+ rSettings.SetStyleSettings( aStyleSet );
+ }
+};
+
+SalFrame *AndroidSalInstance::CreateChildFrame( SystemParentData* /*pParent*/, SalFrameStyleFlags nStyle )
+{
+ return new AndroidSalFrame( this, NULL, nStyle );
+}
+
+SalFrame *AndroidSalInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
+{
+ return new AndroidSalFrame( this, pParent, nStyle );
+}
+
+// This is our main entry point:
+extern "C" SalInstance *create_SalInstance()
+{
+ LOGI("Android: create_SalInstance!");
+ AndroidSalInstance* pInstance = new AndroidSalInstance( std::make_unique<SvpSalYieldMutex>() );
+ new SvpSalData();
+ return pInstance;
+}
+
+int AndroidSalSystem::ShowNativeDialog( const OUString& rTitle,
+ const OUString& rMessage,
+ const std::vector< OUString >& rButtons )
+{
+ (void)rButtons;
+ LOGI("LibreOffice native dialog '%s': '%s'",
+ OUStringToOString(rTitle, RTL_TEXTENCODING_ASCII_US).getStr(),
+ OUStringToOString(rMessage, RTL_TEXTENCODING_ASCII_US).getStr());
+ LOGI("Dialog '%s': '%s'",
+ OUStringToOString(rTitle, RTL_TEXTENCODING_ASCII_US).getStr(),
+ OUStringToOString(rMessage, RTL_TEXTENCODING_ASCII_US).getStr());
+
+ if (AndroidSalInstance::getInstance() != NULL)
+ {
+ // Does Android have a native dialog ? if not,. we have to do this ...
+
+ // Of course it has. android.app.AlertDialog seems like a good
+ // choice, it even has one, two or three buttons. Naturally,
+ // it intended to be used from Java, so some verbose JNI
+ // horror would be needed to use it directly here. Probably we
+ // want some easier to use magic wrapper, hmm.
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Warning, VclButtonsType::Ok,
+ rMessage));
+ xBox->set_title(rTitle);
+ xBox->run();
+ }
+ else
+ LOGE("VCL not initialized");
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/backendtest/GraphicsRenderTests.cxx b/vcl/backendtest/GraphicsRenderTests.cxx
new file mode 100644
index 0000000000..962c60f94e
--- /dev/null
+++ b/vcl/backendtest/GraphicsRenderTests.cxx
@@ -0,0 +1,2595 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/outputdevice.hxx>
+#include <svl/svlresid.hxx>
+#include <svl/svl.hrc>
+#include <unotools/bootstrap.hxx>
+#include <vcl/test/GraphicsRenderTests.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <tools/stream.hxx>
+
+#include <svdata.hxx>
+#include <salinst.hxx>
+#include <strings.hrc>
+#include <test/GraphicsRenderTests.hxx>
+
+#include <unordered_map>
+
+#define SHOULD_ASSERT \
+ (aOutDevTest.getRenderBackendName() != "qt5" && aOutDevTest.getRenderBackendName() != "qt5svp" \
+ && aOutDevTest.getRenderBackendName() != "gtk3svp" \
+ && aOutDevTest.getRenderBackendName() != "aqua" \
+ && aOutDevTest.getRenderBackendName() != "gen" \
+ && aOutDevTest.getRenderBackendName() != "genpsp" \
+ && aOutDevTest.getRenderBackendName() != "win")
+
+namespace vcl::test
+{
+static OUString activeGraphicsRenderTestName;
+void setActiveGraphicsRenderTest(const OUString& name) { activeGraphicsRenderTestName = name; }
+const OUString& activeGraphicsRenderTest() { return activeGraphicsRenderTestName; }
+} // namespace vcl::test
+
+OUString VclTestResult::getStatus(bool bLocalize)
+{ // tdf#145919 localize for UI but not in the log file
+ if (bLocalize)
+ {
+ if (m_aTestStatus == "PASSED")
+ {
+ return SvlResId(GRTSTR_PASSED);
+ }
+ else if (m_aTestStatus == "QUIRKY")
+ {
+ return SvlResId(GRTSTR_QUIRKY);
+ }
+ else if (m_aTestStatus == "FAILED")
+ {
+ return SvlResId(GRTSTR_FAILED);
+ }
+ else
+ {
+ return SvlResId(GRTSTR_SKIPPED);
+ }
+ }
+ else
+ return m_aTestStatus;
+}
+
+namespace
+{
+void exportBitmapExToImage(OUString const& rImageName, const BitmapEx& rBitmapEx)
+{
+ BitmapEx aBitmapEx(rBitmapEx);
+ aBitmapEx.Scale(Size(500, 500), BmpScaleFlag::Fast);
+ SvFileStream aStream(rImageName, StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter::GetGraphicFilter().compressAsPNG(aBitmapEx, aStream);
+}
+bool is32bppSupported() { return ImplGetSVData()->mpDefInst->supportsBitmap32(); }
+
+// Some tests need special handling in drawing code (for example, not smoothscaling
+// when handling HiDPI bitmaps). Temporarily set up the test name to get such special
+// handling as well.
+class GraphicsTestZone
+{
+public:
+ GraphicsTestZone(std::u16string_view name)
+ {
+ vcl::test::setActiveGraphicsRenderTest(OUString::Concat("GraphicsRenderTest__") + name);
+ }
+ ~GraphicsTestZone() { vcl::test::setActiveGraphicsRenderTest(""); }
+};
+}
+
+OUString GraphicsRenderTests::returnTestStatus(vcl::test::TestResult const result)
+{
+ switch (result)
+ {
+ case vcl::test::TestResult::Passed:
+ return "PASSED";
+ case vcl::test::TestResult::PassedWithQuirks:
+ return "QUIRKY";
+ case vcl::test::TestResult::Failed:
+ return "FAILED";
+ }
+ return "SKIPPED";
+}
+
+void GraphicsRenderTests::testDrawRectWithRectangle()
+{
+ OUString aTestName = "testDrawRectWithRectangle";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ m_aCurGraphicsBackend = aOutDevTest.getRenderBackendName();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectWithPixel()
+{
+ OUString aTestName = "testDrawRectWithPixel";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPixel aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectWithLine()
+{
+ OUString aTestName = "testDrawRectWithLine";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectWithPolygon()
+{
+ OUString aTestName = "testDrawRectWithPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectWithPolyLine()
+{
+ OUString aTestName = "testDrawRectWithPolyLine";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectWithPolyLineB2D()
+{
+ OUString aTestName = "testDrawRectWithPolyLineB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectWithPolyPolygon()
+{
+ OUString aTestName = "testDrawRectWithPolyPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectWithPolyPolygonB2D()
+{
+ OUString aTestName = "testDrawRectWithPolyPolygonB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyPolygonB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectAAWithRectangle()
+{
+ OUString aTestName = "testDrawRectAAWithRectangle";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(true);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangleAA(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectAAWithPixel()
+{
+ OUString aTestName = "testDrawRectAAWithPixel";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPixel aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(true);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangleAA(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectAAWithLine()
+{
+ OUString aTestName = "testDrawRectAAWithLine";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(true);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangleAA(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectAAWithPolygon()
+{
+ OUString aTestName = "testDrawRectAAWithPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(true);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangleAA(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectAAWithPolyLine()
+{
+ OUString aTestName = "testDrawRectAAWithPolyLine";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(true);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangleAA(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectAAWithPolyLineB2D()
+{
+ OUString aTestName = "testDrawRectAAWithPolyLineB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(true);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangleAA(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectAAWithPolyPolygon()
+{
+ OUString aTestName = "testDrawRectAAWithPolyPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(true);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangleAA(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectAAWithPolyPolygonB2D()
+{
+ OUString aTestName = "testDrawRectAAWithPolyPolygonB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyPolygonB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(true);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangleAA(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawFilledRectWithRectangle()
+{
+ OUString aTestName = "testDrawFilledRectWithRectangle";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledRectangle(false);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, false);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+ aTestName += "WithAA";
+ aBitmap = aOutDevTest.setupFilledRectangle(true);
+ eResult = vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, true);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawFilledRectWithPolygon()
+{
+ OUString aTestName = "testDrawFilledRectWithPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledRectangle(false);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, false);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+ aTestName += "WithAA";
+ aBitmap = aOutDevTest.setupFilledRectangle(true);
+ eResult = vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, true);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawFilledRectWithPolyPolygon()
+{
+ OUString aTestName = "testDrawFilledRectWithPolyPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledRectangle(false);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, false);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+ aTestName += "WithAA";
+ aBitmap = aOutDevTest.setupFilledRectangle(true);
+ eResult = vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, true);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawFilledRectWithPolyPolygon2D()
+{
+ OUString aTestName = "testDrawFilledRectWithPolyPolygon2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyPolygonB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledRectangle(false);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, false);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+ aTestName += "WithAA";
+ aBitmap = aOutDevTest.setupFilledRectangle(true);
+ eResult = vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, true);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawDiamondWithPolygon()
+{
+ OUString aTestName = "testDrawDiamondWithPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDiamond();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkDiamond(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawDiamondWithLine()
+{
+ OUString aTestName = "testDrawDiamondWithLine";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDiamond();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkDiamond(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawDiamondWithPolyline()
+{
+ OUString aTestName = "testDrawDiamondWithPolyline";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDiamond();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkDiamond(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawDiamondWithPolylineB2D()
+{
+ OUString aTestName = "testDrawDiamondWithPolylineB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDiamond();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkDiamond(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawInvertWithRectangle()
+{
+ OUString aTestName = "testDrawInvertWithRectangle";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupInvert_NONE();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestCommon::checkInvertRectangle(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawInvertN50WithRectangle()
+{
+ OUString aTestName = "testDrawInvertN50WithRectangle";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupInvert_N50();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestCommon::checkInvertN50Rectangle(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawInvertTrackFrameWithRectangle()
+{
+ OUString aTestName = "testDrawInvertTrackFrameWithRectangle";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupInvert_TrackFrame();
+ if (!(SHOULD_ASSERT && aOutDevTest.getRenderBackendName() != "svp"))
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestCommon::checkInvertTrackFrameRectangle(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawDropShapeWithPolyline()
+{
+ OUString aTestName = "testDrawDropShapeWithPolyline";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDropShape();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkDropShape(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawDropShapeAAWithPolyline()
+{
+ OUString aTestName = "testDrawDropShapeAAWithPolyline";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupAADropShape();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestCommon::checkDropShape(aBitmap, true);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawBezierWithPolylineB2D()
+{
+ OUString aTestName = "testDrawBezierWithPolylineB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupBezier();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkBezier(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawBezierAAWithPolylineB2D()
+{
+ OUString aTestName = "testDrawBezierAAWithPolylineB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupAABezier();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkBezier(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawDropShapeWithPolygon()
+{
+ OUString aTestName = "testDrawDropShapeWithPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDropShape();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkDropShape(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawDropShapeAAWithPolygon()
+{
+ OUString aTestName = "testDrawDropShapeAAWithPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupAADropShape();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestCommon::checkDropShape(aBitmap, true);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawBitmap24bpp()
+{
+ OUString aTestName = "testDrawBitmap24bpp";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawBitmap(vcl::PixelFormat::N24_BPP);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawTransformedBitmap24bpp()
+{
+ OUString aTestName = "testDrawTransformedBitmap24bpp";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawTransformedBitmap(vcl::PixelFormat::N24_BPP);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testComplexDrawTransformedBitmap24bpp()
+{
+ OUString aTestName = "testComplexDrawTransformedBitmap24bpp";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupComplexDrawTransformedBitmap(vcl::PixelFormat::N24_BPP);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestBitmap::checkComplexTransformedBitmap(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawBitmapExWithAlpha24bpp()
+{
+ OUString aTestName = "testDrawBitmapExWithAlpha24bpp";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawBitmapExWithAlpha(vcl::PixelFormat::N24_BPP);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestBitmap::checkBitmapExWithAlpha(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawMask24bpp()
+{
+ OUString aTestName = "testDrawMask24bpp";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawMask(vcl::PixelFormat::N24_BPP);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestBitmap::checkMask(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawBlend24bpp()
+{
+ OUString aTestName = "testDrawBlend24bpp";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ BitmapEx aBitmapEx = aOutDevTest.setupDrawBlend(vcl::PixelFormat::N24_BPP);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestBitmap::checkBlend(aBitmapEx);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmapEx.GetBitmap() : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawXor()
+{
+ OUString aTestName = "testDrawXor";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestAnotherOutDev aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupXOR();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestAnotherOutDev::checkXOR(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testClipRectangle()
+{
+ OUString aTestName = "testClipRectangle";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestClip aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupClipRectangle();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestClip::checkClip(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testClipPolygon()
+{
+ OUString aTestName = "testClipPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestClip aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupClipPolygon();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestClip::checkClip(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testClipPolyPolygon()
+{
+ OUString aTestName = "testClipPolyPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestClip aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupClipPolyPolygon();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestClip::checkClip(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testClipB2DPolyPolygon()
+{
+ OUString aTestName = "testClipB2DPolyPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestClip aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupClipB2DPolyPolygon();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestClip::checkClip(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawOutDev()
+{
+ OUString aTestName = "testDrawOutDev";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestAnotherOutDev aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawOutDev();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestAnotherOutDev::checkDrawOutDev(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawOutDevScaledClipped()
+{
+ OUString aTestName = "testDrawOutDevScaledClipped";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestAnotherOutDev aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawOutDevScaledClipped();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestAnotherOutDev::checkDrawOutDevScaledClipped(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawOutDevSelf()
+{
+ OUString aTestName = "testDrawOutDevSelf";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestAnotherOutDev aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawOutDevSelf();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestAnotherOutDev::checkDrawOutDevSelf(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDashedLine()
+{
+ OUString aTestName = "testDashedLine";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDashedLine();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestLine::checkDashedLine(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testLinearGradient()
+{
+ OUString aTestName = "testLinearGradient";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLinearGradient();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestGradient::checkLinearGradient(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testLinearGradientAngled()
+{
+ OUString aTestName = "testLinearGradientAngled";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLinearGradientAngled();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestGradient::checkLinearGradientAngled(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testLinearGradientBorder()
+{
+ OUString aTestName = "testLinearGradientBorder";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLinearGradientBorder();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestGradient::checkLinearGradientBorder(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testLinearGradientIntensity()
+{
+ OUString aTestName = "testLinearGradientIntensity";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLinearGradientIntensity();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestGradient::checkLinearGradientIntensity(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testLinearGradientSteps()
+{
+ OUString aTestName = "testLinearGradientSteps";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLinearGradientSteps();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestGradient::checkLinearGradientSteps(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testAxialGradient()
+{
+ OUString aTestName = "testAxialGradient";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupAxialGradient();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestGradient::checkAxialGradient(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testRadialGradient()
+{
+ OUString aTestName = "testRadialGradient";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRadialGradient();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestGradient::checkRadialGradient(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testRadialGradientOfs()
+{
+ OUString aTestName = "testRadialGradientOfs";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRadialGradientOfs();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestGradient::checkRadialGradientOfs(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+}
+
+void GraphicsRenderTests::testLineJoinBevel()
+{
+ OUString aTestName = "testLineJoinBevel";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineJoinBevel();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestLine::checkLineJoinBevel(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testLineJoinRound()
+{
+ OUString aTestName = "testLineJoinRound";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineJoinRound();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestLine::checkLineJoinRound(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testLineJoinMiter()
+{
+ OUString aTestName = "testLineJoinMiter";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineJoinMiter();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestLine::checkLineJoinMiter(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testLineJoinNone()
+{
+ OUString aTestName = "testLineJoinNone";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineJoinNone();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestLine::checkLineJoinNone(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testLineCapRound()
+{
+ OUString aTestName = "testLineCapRound";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineCapRound();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestLine::checkLineCapRound(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testLineCapSquare()
+{
+ OUString aTestName = "testLineCapSquare";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineCapSquare();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestLine::checkLineCapSquare(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testLineCapButt()
+{
+ OUString aTestName = "testLineCapButt";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineCapButt();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestLine::checkLineCapButt(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testHalfEllipseWithPolyLine()
+{
+ OUString aTestName = "testHalfEllipseWithPolyLine";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupHalfEllipse();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestLine::checkHalfEllipse(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testHalfEllipseAAWithPolyLine()
+{
+ OUString aTestName = "testHalfEllipseAAWithPolyLine";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupHalfEllipse(true);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestLine::checkHalfEllipse(aBitmap, true);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testHalfEllipseWithPolyLineB2D()
+{
+ OUString aTestName = "testHalfEllipseWithPolyLineB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupHalfEllipse();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestLine::checkHalfEllipse(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testHalfEllipseAAWithPolyLineB2D()
+{
+ OUString aTestName = "testHalfEllipseAAWithPolyLineB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupHalfEllipse(true);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestLine::checkHalfEllipse(aBitmap, true);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testHalfEllipseWithPolygon()
+{
+ OUString aTestName = "testHalfEllipseWithPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupHalfEllipse();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestLine::checkHalfEllipse(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testClosedBezierWithPolyline()
+{
+ OUString aTestName = "testClosedBezierWithPolyline";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupClosedBezier();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkClosedBezier(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testClosedBezierWithPolygon()
+{
+ OUString aTestName = "testClosedBezierWithPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupClosedBezier();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkClosedBezier(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testHalfEllipseAAWithPolygon()
+{
+ OUString aTestName = "testHalfEllipseAAWithPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupHalfEllipse(true);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestLine::checkHalfEllipse(aBitmap, true);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testFilledAsymmetricalDropShape()
+{
+ OUString aTestName = "testFilledAsymmetricalDropShape";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledAsymmetricalDropShape();
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestLine::checkFilledAsymmetricalDropShape(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testTextDrawing()
+{
+ OUString aTestName = "testTextDrawing";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestText aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupTextBitmap();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestLine::checkTextLocation(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testEvenOddRuleInIntersectingRectsWithPolyPolygon()
+{
+ OUString aTestName = "testEvenOddRuleInIntersectingRectsWithPolyPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupIntersectingRectangles();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestLine::checkEvenOddRuleInIntersectingRecs(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testEvenOddRuleInIntersectingRectsWithPolyPolygonB2D()
+{
+ OUString aTestName = "testEvenOddRuleInIntersectingRectsWithPolyPolygonB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyPolygonB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupIntersectingRectangles();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestLine::checkEvenOddRuleInIntersectingRecs(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawBitmap8bppGreyScale()
+{
+ OUString aTestName = "testDrawBitmap8bppGreyScale";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawBitmap(vcl::PixelFormat::N8_BPP, true);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap8bppGreyScale(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawTransformedBitmap8bppGreyScale()
+{
+ OUString aTestName = "testDrawTransformedBitmap8bppGreyScale";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawTransformedBitmap(vcl::PixelFormat::N8_BPP, true);
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap8bppGreyScale(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawBitmap32bpp()
+{
+ OUString aTestName = "testDrawBitmap32bpp";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawBitmap(vcl::PixelFormat::N32_BPP);
+ if (!SHOULD_ASSERT || !is32bppSupported())
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawTransformedBitmap32bpp()
+{
+ OUString aTestName = "testDrawTransformedBitmap32bpp";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawTransformedBitmap(vcl::PixelFormat::N32_BPP);
+ if (!SHOULD_ASSERT || !is32bppSupported())
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawBitmapExWithAlpha32bpp()
+{
+ OUString aTestName = "testDrawBitmapExWithAlpha32bpp";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawBitmapExWithAlpha(vcl::PixelFormat::N32_BPP);
+ if (!SHOULD_ASSERT || !is32bppSupported())
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestBitmap::checkBitmapExWithAlpha(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawMask32bpp()
+{
+ OUString aTestName = "testDrawMask32bpp";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawMask(vcl::PixelFormat::N32_BPP);
+ if (!SHOULD_ASSERT || !is32bppSupported())
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestBitmap::checkMask(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawBlend32bpp()
+{
+ OUString aTestName = "testDrawBlend32bpp";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ BitmapEx aBitmapEx = aOutDevTest.setupDrawBlend(vcl::PixelFormat::N32_BPP);
+ if (!SHOULD_ASSERT || !is32bppSupported())
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestBitmap::checkBlend(aBitmapEx);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmapEx.GetBitmap() : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectangleOnSize1028WithRect()
+{
+ OUString aTestName = "testDrawRectangleOnSize1028WithRect";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangleOnSize1028();
+ m_aCurGraphicsBackend = aOutDevTest.getRenderBackendName();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectangleOnSize4096WithRect()
+{
+ OUString aTestName = "testDrawRectangleOnSize4096WithRect";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangleOnSize4096();
+ m_aCurGraphicsBackend = aOutDevTest.getRenderBackendName();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectangleOnSize1028WithPixel()
+{
+ OUString aTestName = "testDrawRectangleOnSize1028WithPixel";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPixel aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangleOnSize1028();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectangleOnSize4096WithPixel()
+{
+ OUString aTestName = "testDrawRectangleOnSize4096WithPixel";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPixel aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangleOnSize4096();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectangleOnSize1028WithLine()
+{
+ OUString aTestName = "testDrawRectangleOnSize1028WithLine";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangleOnSize1028();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectangleOnSize4096WithLine()
+{
+ OUString aTestName = "testDrawRectangleOnSize4096WithLine";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangleOnSize4096();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectangleOnSize1028WithPolyLine()
+{
+ OUString aTestName = "testDrawRectangleOnSize1028WithPolyLine";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangleOnSize1028();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectangleOnSize4096WithPolyLine()
+{
+ OUString aTestName = "testDrawRectangleOnSize4096WithPolyLine";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangleOnSize4096();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectangleOnSize1028WithPolygon()
+{
+ OUString aTestName = "testDrawRectangleOnSize1028WithPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangleOnSize1028();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectangleOnSize4096WithPolygon()
+{
+ OUString aTestName = "testDrawRectangleOnSize4096WithPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangleOnSize4096();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectangleOnSize1028WithPolyLineB2D()
+{
+ OUString aTestName = "testDrawRectangleOnSize1028WithPolyLineB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangleOnSize1028();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectangleOnSize4096WithPolyLineB2D()
+{
+ OUString aTestName = "testDrawRectangleOnSize4096WithPolyLineB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangleOnSize4096();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectangleOnSize1028WithPolyPolygon()
+{
+ OUString aTestName = "testDrawRectangleOnSize1028WithPolyPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangleOnSize1028();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectangleOnSize4096WithPolyPolygon()
+{
+ OUString aTestName = "testDrawRectangleOnSize4096WithPolyPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangleOnSize4096();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectangleOnSize1028WithPolyPolygonB2D()
+{
+ OUString aTestName = "testDrawRectangleOnSize1028WithPolyPolygonB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyPolygonB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangleOnSize1028();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawRectangleOnSize4096WithPolygonPolygonB2D()
+{
+ OUString aTestName = "testDrawRectangleOnSize4096WithPolygonPolygonB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyPolygonB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangleOnSize4096();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawOpenPolygonWithPolyLine()
+{
+ OUString aTestName = "testDrawOpenPolygonWithPolyLine";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupOpenPolygon();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkOpenPolygon(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawOpenPolygonWithPolyLineB2D()
+{
+ OUString aTestName = "testDrawOpenPolygonWithPolyLineB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupOpenPolygon();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkOpenPolygon(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawOpenPolygonWithPolygon()
+{
+ OUString aTestName = "testDrawOpenPolygonWithPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupOpenPolygon();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkOpenPolygon(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawOpenPolygonWithPolyPolygon()
+{
+ OUString aTestName = "testDrawOpenPolygonWithPolyPolygon";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupOpenPolygon();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkOpenPolygon(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawOpenPolygonWithPolyPolygonB2D()
+{
+ OUString aTestName = "testDrawOpenPolygonWithPolyPolygonB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupOpenPolygon();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkOpenPolygon(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawOpenBezierWithPolyLine()
+{
+ OUString aTestName = "testDrawOpenBezierWithPolyLine";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupOpenBezier();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkOpenBezier(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::testDrawOpenBezierWithPolyLineB2D()
+{
+ OUString aTestName = "testDrawOpenBezierWithPolyLineB2D";
+ GraphicsTestZone zone(aTestName);
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupOpenBezier();
+ if (!SHOULD_ASSERT)
+ {
+ appendTestResult(aTestName, "SKIPPED");
+ return;
+ }
+ vcl::test::TestResult eResult = vcl::test::OutputDeviceTestCommon::checkOpenBezier(aBitmap);
+ appendTestResult(aTestName, returnTestStatus(eResult),
+ (m_aStoreResultantBitmap ? aBitmap : Bitmap()));
+ if (m_aStoreResultantBitmap)
+ {
+ BitmapEx aBitmapEx(aBitmap);
+ exportBitmapExToImage(m_aUserInstallPath + aTestName + ".png", aBitmapEx);
+ }
+}
+
+void GraphicsRenderTests::runALLTests()
+{
+ testDrawRectWithRectangle();
+ testDrawRectWithPixel();
+ testDrawRectWithLine();
+ testDrawRectWithPolygon();
+ testDrawRectWithPolyLine();
+ testDrawRectWithPolyLineB2D();
+ testDrawRectWithPolyPolygon();
+ testDrawRectWithPolyPolygonB2D();
+ testDrawRectAAWithRectangle();
+ testDrawRectAAWithPixel();
+ testDrawRectAAWithLine();
+ testDrawRectAAWithPolygon();
+ testDrawRectAAWithPolyLine();
+ testDrawRectAAWithPolyLineB2D();
+ testDrawRectAAWithPolyPolygon();
+ testDrawRectAAWithPolyPolygonB2D();
+ testDrawFilledRectWithRectangle();
+ testDrawFilledRectWithPolygon();
+ testDrawFilledRectWithPolyPolygon();
+ testDrawFilledRectWithPolyPolygon2D();
+ testDrawDiamondWithPolygon();
+ testDrawDiamondWithLine();
+ testDrawDiamondWithPolyline();
+ testDrawDiamondWithPolylineB2D();
+ testDrawInvertWithRectangle();
+ testDrawInvertN50WithRectangle();
+ testDrawInvertTrackFrameWithRectangle();
+ testDrawBezierWithPolylineB2D();
+ testDrawBezierAAWithPolylineB2D();
+ testDrawBitmap24bpp();
+ testDrawTransformedBitmap24bpp();
+ testComplexDrawTransformedBitmap24bpp();
+ testDrawBitmapExWithAlpha24bpp();
+ testDrawMask24bpp();
+ testDrawBlend24bpp();
+ testDrawXor();
+ testClipRectangle();
+ testClipPolygon();
+ testClipPolyPolygon();
+ testClipB2DPolyPolygon();
+ testDrawOutDev();
+ testDrawOutDevScaledClipped();
+ testDrawOutDevSelf();
+ testDashedLine();
+ testLinearGradient();
+ testLinearGradientAngled();
+ testLinearGradientBorder();
+ testLinearGradientIntensity();
+ testLinearGradientSteps();
+ testAxialGradient();
+ testRadialGradient();
+ testRadialGradientOfs();
+ testLineJoinBevel();
+ testLineJoinRound();
+ testLineJoinMiter();
+ testLineJoinNone();
+ testLineCapRound();
+ testLineCapSquare();
+ testLineCapButt();
+ testDrawDropShapeWithPolyline();
+ testDrawDropShapeAAWithPolyline();
+ testDrawDropShapeWithPolygon();
+ testDrawDropShapeAAWithPolygon();
+ testHalfEllipseWithPolyLine();
+ testHalfEllipseAAWithPolyLine();
+ testHalfEllipseWithPolyLineB2D();
+ testHalfEllipseAAWithPolyLineB2D();
+ testHalfEllipseWithPolygon();
+ testHalfEllipseAAWithPolygon();
+ testClosedBezierWithPolyline();
+ testClosedBezierWithPolygon();
+ testFilledAsymmetricalDropShape();
+ testTextDrawing();
+ testEvenOddRuleInIntersectingRectsWithPolyPolygon();
+ testEvenOddRuleInIntersectingRectsWithPolyPolygonB2D();
+ testDrawBitmap8bppGreyScale();
+ testDrawTransformedBitmap8bppGreyScale();
+ testDrawBitmap32bpp();
+ testDrawTransformedBitmap32bpp();
+ testDrawBitmapExWithAlpha32bpp();
+ testDrawMask32bpp();
+ testDrawBlend32bpp();
+ testDrawRectangleOnSize1028WithRect();
+ testDrawRectangleOnSize4096WithRect();
+ testDrawRectangleOnSize1028WithPixel();
+ testDrawRectangleOnSize4096WithPixel();
+ testDrawRectangleOnSize1028WithLine();
+ testDrawRectangleOnSize4096WithLine();
+ testDrawRectangleOnSize1028WithPolyLine();
+ testDrawRectangleOnSize4096WithPolyLine();
+ testDrawRectangleOnSize1028WithPolygon();
+ testDrawRectangleOnSize4096WithPolygon();
+ testDrawRectangleOnSize1028WithPolyLineB2D();
+ testDrawRectangleOnSize4096WithPolyLineB2D();
+ testDrawRectangleOnSize1028WithPolyPolygon();
+ testDrawRectangleOnSize4096WithPolyPolygon();
+ testDrawRectangleOnSize1028WithPolyPolygonB2D();
+ testDrawRectangleOnSize4096WithPolygonPolygonB2D();
+ testDrawOpenPolygonWithPolyLine();
+ testDrawOpenPolygonWithPolyLineB2D();
+ testDrawOpenPolygonWithPolygon();
+ testDrawOpenPolygonWithPolyPolygon();
+ testDrawOpenPolygonWithPolyPolygonB2D();
+ testDrawOpenBezierWithPolyLine();
+ testDrawOpenBezierWithPolyLineB2D();
+}
+
+void GraphicsRenderTests::appendTestResult(OUString aTestName, OUString aTestStatus,
+ Bitmap aTestBitmap)
+{
+ m_aTestResult.push_back(VclTestResult(aTestName, aTestStatus, aTestBitmap));
+}
+
+std::vector<VclTestResult>& GraphicsRenderTests::getTestResults() { return m_aTestResult; }
+
+OUString GraphicsRenderTests::getResultString(bool bLocalize)
+{
+ std::vector<int> testResults(4);
+ for (VclTestResult& test : m_aTestResult)
+ {
+ if (test.getStatus() == "PASSED")
+ {
+ testResults[0]++;
+ }
+ else if (test.getStatus() == "QUIRKY")
+ {
+ testResults[1]++;
+ }
+ else if (test.getStatus() == "FAILED")
+ {
+ testResults[2]++;
+ }
+ else
+ {
+ testResults[3]++;
+ }
+ }
+ // tdf#145919 localize for UI but not in the log file
+ OUString resultString;
+ if (bLocalize)
+ {
+ resultString
+ = VclResId(STR_GBU).replaceFirst("%1", m_aCurGraphicsBackend) + "\n"
+ + VclResId(STR_PASSED).replaceFirst("%1", OUString::number(testResults[0])) + "\n"
+ + VclResId(STR_QUIRKY).replaceFirst("%1", OUString::number(testResults[1])) + "\n"
+ + VclResId(STR_FAILED).replaceFirst("%1", OUString::number(testResults[2])) + "\n"
+ + VclResId(STR_SKIPPED).replaceFirst("%1", OUString::number(testResults[3])) + "\n";
+ }
+ else
+ {
+ resultString = "Graphics Backend used: " + m_aCurGraphicsBackend
+ + "\nPassed Tests: " + OUString::number(testResults[0])
+ + "\nQuirky Tests: " + OUString::number(testResults[1])
+ + "\nFailed Tests: " + OUString::number(testResults[2])
+ + "\nSkipped Tests: " + OUString::number(testResults[3]) + "\n";
+ }
+ return resultString;
+}
+
+void GraphicsRenderTests::run(bool storeResultBitmap)
+{
+ m_aStoreResultantBitmap = storeResultBitmap;
+ ::utl::Bootstrap::locateUserInstallation(m_aUserInstallPath);
+ if (storeResultBitmap)
+ {
+ m_aUserInstallPath += "/user/GraphicTestResults/";
+ }
+ else
+ {
+ m_aUserInstallPath += "/user/";
+ }
+ runALLTests();
+ //Storing the test's results in the main user installation directory.
+ SvFileStream logFile(m_aUserInstallPath + "GraphicsRenderTests.log",
+ StreamMode::WRITE | StreamMode::TRUNC);
+ std::unordered_map<OUString, std::vector<OUString>> aTests;
+ for (VclTestResult& tests : m_aTestResult)
+ {
+ aTests[tests.getStatus()].push_back(tests.getTestName());
+ }
+ OUString writeResult = getResultString() + "\n---Name of the tests that failed---\n";
+ if (static_cast<int>(aTests["FAILED"].size()) > 0)
+ {
+ for (const class OUString& tests : aTests["FAILED"])
+ {
+ writeResult += tests + "\n";
+ }
+ }
+ else
+ {
+ writeResult += "No test has been failed.\n";
+ }
+ writeResult += "\n---Name of the tests that were Quirky---\n";
+ if (static_cast<int>(aTests["QUIRKY"].size()) > 0)
+ {
+ for (const class OUString& tests : aTests["QUIRKY"])
+ {
+ writeResult += tests + "\n";
+ }
+ }
+ else
+ {
+ writeResult += "No test was Quirky.\n";
+ }
+ writeResult += "\n---Name of the tests that were Skipped---\n";
+ if (static_cast<int>(aTests["SKIPPED"].size()) > 0)
+ {
+ for (const class OUString& tests : aTests["SKIPPED"])
+ {
+ writeResult += tests + "\n";
+ }
+ }
+ else
+ {
+ writeResult += "No test was Skipped.";
+ }
+ logFile.WriteOString(OUStringToOString(writeResult, RTL_TEXTENCODING_UTF8));
+}
diff --git a/vcl/backendtest/VisualBackendTest.cxx b/vcl/backendtest/VisualBackendTest.cxx
new file mode 100644
index 0000000000..cbcf881bc7
--- /dev/null
+++ b/vcl/backendtest/VisualBackendTest.cxx
@@ -0,0 +1,886 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <math.h>
+#include <sal/log.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/bootstrap.hxx>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/event.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/vclmain.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/virdev.hxx>
+
+#include <basegfx/numeric/ftools.hxx>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <chrono>
+#include <iostream>
+
+#include <test/outputdevice.hxx>
+
+using namespace css;
+
+static void drawBitmapCentered(tools::Rectangle const& rRect, const Bitmap& aBitmap,
+ vcl::RenderContext& rRenderContext)
+{
+ tools::Long nWidth = rRect.GetWidth();
+ tools::Long nHeight = rRect.GetHeight();
+
+ Size aBitmapSize(aBitmap.GetSizePixel());
+
+ Point aPoint(rRect.TopLeft());
+
+ aPoint.AdjustX((nWidth - aBitmapSize.Width()) / 2 );
+ aPoint.AdjustY((nHeight - aBitmapSize.Height()) / 2 );
+
+ rRenderContext.DrawBitmap(aPoint, aBitmap);
+}
+
+static void drawBitmapScaledAndCentered(tools::Rectangle const & rRect, Bitmap aBitmap, vcl::RenderContext& rRenderContext, BmpScaleFlag aFlag = BmpScaleFlag::Fast)
+{
+ tools::Long nWidth = rRect.GetWidth();
+ tools::Long nHeight = rRect.GetHeight();
+
+ Size aBitmapSize(aBitmap.GetSizePixel());
+
+ double fWidthHeight = std::min(nWidth, nHeight);
+ double fScale = fWidthHeight / aBitmapSize.Width();
+ aBitmap.Scale(fScale, fScale, aFlag);
+
+ drawBitmapCentered(rRect, aBitmap, rRenderContext);
+}
+
+static void drawBackgroundRect(tools::Rectangle const & rRect, Color aColor, vcl::RenderContext& rRenderContext)
+{
+ rRenderContext.Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR);
+ rRenderContext.SetFillColor(aColor);
+ rRenderContext.SetLineColor(aColor);
+ rRenderContext.DrawRect(rRect);
+ rRenderContext.Pop();
+}
+
+static void assertAndSetBackground(vcl::test::TestResult eResult, tools::Rectangle const & rRect, vcl::RenderContext& rRenderContext)
+{
+ if (eResult == vcl::test::TestResult::Passed)
+ drawBackgroundRect(rRect, COL_GREEN, rRenderContext);
+ else if (eResult == vcl::test::TestResult::PassedWithQuirks)
+ drawBackgroundRect(rRect, COL_YELLOW, rRenderContext);
+ else if (eResult == vcl::test::TestResult::Failed)
+ drawBackgroundRect(rRect, COL_RED, rRenderContext);
+}
+
+namespace {
+
+class VisualBackendTestWindow : public WorkWindow
+{
+private:
+ Timer maUpdateTimer;
+ std::vector<std::chrono::high_resolution_clock::time_point> mTimePoints;
+ static constexpr unsigned char gnNumberOfTests = 12;
+ unsigned char mnTest;
+ bool mbAnimate;
+ ScopedVclPtr<VirtualDevice> mpVDev;
+
+public:
+ VisualBackendTestWindow()
+ : WorkWindow(nullptr, WB_APP | WB_STDWORK)
+ , maUpdateTimer("VisualBackendTestWindow maUpdateTimer")
+ , mnTest(10 * gnNumberOfTests)
+ , mbAnimate(mnTest % gnNumberOfTests == gnNumberOfTests - 1)
+ , mpVDev(VclPtr<VirtualDevice>::Create())
+ {
+ maUpdateTimer.SetInvokeHandler(LINK(this, VisualBackendTestWindow, updateHdl));
+ maUpdateTimer.SetPriority(TaskPriority::DEFAULT_IDLE);
+ if (mbAnimate)
+ {
+ maUpdateTimer.SetTimeout(1000.0);
+ maUpdateTimer.Start();
+ }
+ }
+
+ virtual ~VisualBackendTestWindow() override
+ {
+ disposeOnce();
+ }
+
+ DECL_LINK(updateHdl, Timer*, void);
+
+ virtual void KeyInput(const KeyEvent& rKEvt) override
+ {
+ sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
+
+ if (nCode == KEY_BACKSPACE)
+ mnTest--;
+ else if(nCode == KEY_SPACE)
+ mnTest++;
+
+ if (nCode != KEY_BACKSPACE && nCode != KEY_SPACE)
+ return;
+
+ if (mnTest % gnNumberOfTests == gnNumberOfTests - 1)
+ {
+ mbAnimate = true;
+ maUpdateTimer.Start();
+ }
+ else
+ {
+ mbAnimate = false;
+ Invalidate();
+ }
+ }
+
+ static std::vector<tools::Rectangle> setupRegions(int nPartitionsX, int nPartitionsY, int nWidth, int nHeight)
+ {
+ std::vector<tools::Rectangle> aRegions;
+
+ for (int y = 0; y < nPartitionsY; y++)
+ {
+ for (int x = 0; x < nPartitionsX; x++)
+ {
+ tools::Long x1 = x * (nWidth / nPartitionsX);
+ tools::Long y1 = y * (nHeight / nPartitionsY);
+ tools::Long x2 = (x+1) * (nWidth / nPartitionsX);
+ tools::Long y2 = (y+1) * (nHeight / nPartitionsY);
+
+ aRegions.emplace_back(x1 + 1, y1 + 1, x2 - 6, y2 - 2);
+ }
+ }
+ return aRegions;
+ }
+
+ static void testRectangles(vcl::RenderContext& rRenderContext, int nWidth, int nHeight, bool AA)
+ {
+ tools::Rectangle aRectangle;
+ size_t index = 0;
+
+ std::vector<tools::Rectangle> aRegions = setupRegions(4, 2, nWidth, nHeight);
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(AA);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap, AA), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPixel aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(AA);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap, AA), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(AA);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap, AA), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(AA);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap, AA), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(AA);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap, AA), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(AA);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap, AA), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(AA);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap, AA), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolyPolygonB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(AA);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap, AA), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ }
+
+ static void testFilledRectangles(vcl::RenderContext& rRenderContext, int nWidth, int nHeight)
+ {
+ tools::Rectangle aRectangle;
+ size_t index = 0;
+
+ std::vector<tools::Rectangle> aRegions = setupRegions(4, 2, nWidth, nHeight);
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledRectangle(false);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, false), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledRectangle(false);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, false), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledRectangle(false);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, false), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolyPolygonB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledRectangle(false);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, false), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledRectangle(true);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, true), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledRectangle(true);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, true), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledRectangle(true);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, true), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolyPolygonB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledRectangle(true);
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, true), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ }
+
+ static void testDiamondsAndBezier(vcl::RenderContext& rRenderContext, int nWidth, int nHeight)
+ {
+ tools::Rectangle aRectangle;
+ size_t index = 0;
+
+ std::vector<tools::Rectangle> aRegions = setupRegions(3, 2, nWidth, nHeight);
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDiamond();
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkDiamond(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDiamond();
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkDiamond(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDiamond();
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkDiamond(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDiamond();
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkDiamond(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupOpenBezier();
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkOpenBezier(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupAABezier();
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkBezier(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ }
+
+ static void testLines(vcl::RenderContext& rRenderContext, int nWidth, int nHeight)
+ {
+ tools::Rectangle aRectangle;
+ size_t index = 0;
+
+ std::vector<tools::Rectangle> aRegions = setupRegions(3, 2, nWidth, nHeight);
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLines();
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkLines(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLines();
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkLines(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLines();
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkLines(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupAALines();
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkAALines(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupAALines();
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkAALines(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupAALines();
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkAALines(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ }
+
+ static void testLineTypes(vcl::RenderContext& rRenderContext, int nWidth, int nHeight)
+ {
+ tools::Rectangle aRectangle;
+ size_t index = 0;
+
+ std::vector<tools::Rectangle> aRegions = setupRegions(4, 2, nWidth, nHeight);
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineJoinBevel();
+ assertAndSetBackground(vcl::test::OutputDeviceTestLine::checkLineJoinBevel(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineJoinRound();
+ assertAndSetBackground(vcl::test::OutputDeviceTestLine::checkLineJoinRound(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineJoinMiter();
+ assertAndSetBackground(vcl::test::OutputDeviceTestLine::checkLineJoinMiter(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineJoinNone();
+ assertAndSetBackground(vcl::test::OutputDeviceTestLine::checkLineJoinNone(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineCapRound();
+ assertAndSetBackground(vcl::test::OutputDeviceTestLine::checkLineCapRound(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineCapSquare();
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkLineCapSquare(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineCapButt();
+ assertAndSetBackground(vcl::test::OutputDeviceTestCommon::checkLineCapButt(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ }
+
+ static void testBitmaps(vcl::RenderContext& rRenderContext, int nWidth, int nHeight)
+ {
+ tools::Rectangle aRectangle;
+ size_t index = 0;
+
+ std::vector<tools::Rectangle> aRegions = setupRegions(3, 2, nWidth, nHeight);
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawBitmap(vcl::PixelFormat::N24_BPP);
+ assertAndSetBackground(vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawTransformedBitmap(vcl::PixelFormat::N24_BPP);
+ assertAndSetBackground(vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupComplexDrawTransformedBitmap(vcl::PixelFormat::N24_BPP);
+ assertAndSetBackground(vcl::test::OutputDeviceTestBitmap::checkComplexTransformedBitmap(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawBitmapExWithAlpha(vcl::PixelFormat::N24_BPP);
+ assertAndSetBackground(vcl::test::OutputDeviceTestBitmap::checkBitmapExWithAlpha(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawMask(vcl::PixelFormat::N24_BPP);
+ assertAndSetBackground(vcl::test::OutputDeviceTestBitmap::checkMask(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ BitmapEx aBitmap = aOutDevTest.setupDrawBlend(vcl::PixelFormat::N24_BPP);
+ assertAndSetBackground(vcl::test::OutputDeviceTestBitmap::checkBlend(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap.GetBitmap(), rRenderContext);
+ }
+ }
+
+ static void testInvert(vcl::RenderContext& rRenderContext, int nWidth, int nHeight)
+ {
+ tools::Rectangle aRectangle;
+ size_t index = 0;
+
+ std::vector<tools::Rectangle> aRegions = setupRegions(2, 2, nWidth, nHeight);
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupInvert_NONE();
+ assertAndSetBackground(vcl::test::OutputDeviceTestRect::checkInvertRectangle(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupInvert_N50();
+ assertAndSetBackground(vcl::test::OutputDeviceTestRect::checkInvertN50Rectangle(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupInvert_TrackFrame();
+ assertAndSetBackground(vcl::test::OutputDeviceTestRect::checkInvertTrackFrameRectangle(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestAnotherOutDev aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupXOR();
+ assertAndSetBackground(vcl::test::OutputDeviceTestAnotherOutDev::checkXOR(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ }
+
+ static void testClip(vcl::RenderContext& rRenderContext, int nWidth, int nHeight)
+ {
+ tools::Rectangle aRectangle;
+ size_t index = 0;
+
+ std::vector<tools::Rectangle> aRegions = setupRegions(2, 2, nWidth, nHeight);
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestClip aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupClipRectangle();
+ assertAndSetBackground(vcl::test::OutputDeviceTestClip::checkClip(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestClip aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupClipPolygon();
+ assertAndSetBackground(vcl::test::OutputDeviceTestClip::checkClip(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestClip aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupClipPolyPolygon();
+ assertAndSetBackground(vcl::test::OutputDeviceTestClip::checkClip(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestClip aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupClipB2DPolyPolygon();
+ assertAndSetBackground(vcl::test::OutputDeviceTestClip::checkClip(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ }
+
+ static void testGradients(vcl::RenderContext& rRenderContext, int nWidth, int nHeight)
+ {
+ tools::Rectangle aRectangle;
+ size_t index = 0;
+
+ std::vector<tools::Rectangle> aRegions = setupRegions(4, 2, nWidth, nHeight);
+
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLinearGradient();
+ assertAndSetBackground(vcl::test::OutputDeviceTestGradient::checkLinearGradient(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLinearGradientAngled();
+ assertAndSetBackground(vcl::test::OutputDeviceTestGradient::checkLinearGradientAngled(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLinearGradientBorder();
+ assertAndSetBackground(vcl::test::OutputDeviceTestGradient::checkLinearGradientBorder(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLinearGradientIntensity();
+ assertAndSetBackground(vcl::test::OutputDeviceTestGradient::checkLinearGradientIntensity(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLinearGradientSteps();
+ assertAndSetBackground(vcl::test::OutputDeviceTestGradient::checkLinearGradientSteps(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupAxialGradient();
+ assertAndSetBackground(vcl::test::OutputDeviceTestGradient::checkAxialGradient(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRadialGradient();
+ assertAndSetBackground(vcl::test::OutputDeviceTestGradient::checkRadialGradient(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRadialGradientOfs();
+ assertAndSetBackground(vcl::test::OutputDeviceTestGradient::checkRadialGradientOfs(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ }
+
+ virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/) override
+ {
+ if (mnTest % gnNumberOfTests == gnNumberOfTests - 1)
+ {
+ rRenderContext.SetBackground(Wallpaper(COL_GREEN));
+
+ static size_t nTimeIndex = 0;
+ static const size_t constSamplesFPS = 120;
+ double fps = 0.0;
+
+ if (mTimePoints.size() < constSamplesFPS)
+ {
+ mTimePoints.push_back(std::chrono::high_resolution_clock::now());
+ nTimeIndex++;
+ }
+ else
+ {
+ size_t current = nTimeIndex % constSamplesFPS;
+ mTimePoints[current] = std::chrono::high_resolution_clock::now();
+ size_t last = (nTimeIndex + 1) % constSamplesFPS;
+ auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(mTimePoints[current] - mTimePoints[last]).count();
+ fps = constSamplesFPS * 1000.0 / ms;
+ nTimeIndex++;
+ }
+
+ double fTime = 0.5 + std::sin(nTimeIndex / 100.0) / 2.0;
+
+ Size aSizePixel = GetSizePixel();
+
+ mpVDev->SetAntialiasing(AntialiasingFlags::Enable | AntialiasingFlags::PixelSnapHairline);
+ mpVDev->SetOutputSizePixel(aSizePixel);
+ mpVDev->SetBackground(Wallpaper(COL_LIGHTGRAY));
+ mpVDev->Erase();
+ mpVDev->SetFillColor(COL_LIGHTRED);
+ mpVDev->SetLineColor(COL_LIGHTBLUE);
+
+ basegfx::B2DPolyPolygon polyPolygon;
+
+ for (int b=10; b<14; b++)
+ {
+ basegfx::B2DPolygon polygon;
+ for (double a=0.0; a<360.0; a+=0.5)
+ {
+ double x = std::sin(basegfx::deg2rad(a)) * (b+1) * 20;
+ double y = std::cos(basegfx::deg2rad(a)) * (b+1) * 20;
+ polygon.append(basegfx::B2DPoint(x + 200 + 500 * fTime, y + 200 + 500 * fTime));
+ }
+ polygon.setClosed(true);
+ polyPolygon.append(polygon);
+ }
+
+ mpVDev->DrawPolyPolygon(polyPolygon);
+
+ tools::Rectangle aGradientRect(Point(200, 200), Size(200 + fTime * 300, 200 + fTime * 300));
+ mpVDev->DrawGradient(aGradientRect, Gradient(css::awt::GradientStyle_LINEAR, COL_YELLOW, COL_BLUE));
+
+ rRenderContext.DrawOutDev(Point(), mpVDev->GetOutputSizePixel(),
+ Point(), mpVDev->GetOutputSizePixel(),
+ *mpVDev);
+ rRenderContext.SetTextColor(COL_LIGHTRED);
+ rRenderContext.DrawText(Point(10, 10), "FPS: " + OUString::number(int(fps)));
+ return;
+ }
+
+ rRenderContext.SetBackground(Wallpaper(COL_GREEN));
+
+ Size aSize = GetOutputSizePixel();
+
+ tools::Long nWidth = aSize.Width();
+ tools::Long nHeight = aSize.Height();
+
+ if (mnTest % gnNumberOfTests == 0)
+ {
+ testRectangles(rRenderContext, nWidth, nHeight, false);
+ }
+ else if (mnTest % gnNumberOfTests == 1)
+ {
+ testRectangles(rRenderContext, nWidth, nHeight, true);
+ }
+ else if (mnTest % gnNumberOfTests == 2)
+ {
+ testFilledRectangles(rRenderContext, nWidth, nHeight);
+ }
+ else if (mnTest % gnNumberOfTests == 3)
+ {
+ testDiamondsAndBezier(rRenderContext, nWidth, nHeight);
+ }
+ else if (mnTest % gnNumberOfTests == 4)
+ {
+ testLines(rRenderContext, nWidth, nHeight);
+ }
+ else if (mnTest % gnNumberOfTests == 5)
+ {
+ testLineTypes(rRenderContext, nWidth, nHeight);
+ }
+ else if (mnTest % gnNumberOfTests == 6)
+ {
+ testBitmaps(rRenderContext, nWidth, nHeight);
+ }
+ else if (mnTest % gnNumberOfTests == 7)
+ {
+ testInvert(rRenderContext, nWidth, nHeight);
+ }
+ else if (mnTest % gnNumberOfTests == 8)
+ {
+ testClip(rRenderContext, nWidth, nHeight);
+ }
+ else if (mnTest % gnNumberOfTests == 9)
+ {
+ testGradients(rRenderContext, nWidth, nHeight);
+ }
+ else if (mnTest % gnNumberOfTests == 10)
+ {
+ std::vector<tools::Rectangle> aRegions = setupRegions(2, 2, nWidth, nHeight);
+ size_t index = 0;
+
+ tools::Rectangle aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestAnotherOutDev aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawOutDev();
+ assertAndSetBackground(vcl::test::OutputDeviceTestAnotherOutDev::checkDrawOutDev(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestAnotherOutDev aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawOutDevScaledClipped();
+ assertAndSetBackground(vcl::test::OutputDeviceTestAnotherOutDev::checkDrawOutDevScaledClipped(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestAnotherOutDev aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawOutDevSelf();
+ assertAndSetBackground(vcl::test::OutputDeviceTestAnotherOutDev::checkDrawOutDevSelf(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ aRectangle = aRegions[index++];
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDashedLine();
+ assertAndSetBackground(vcl::test::OutputDeviceTestLine::checkDashedLine(aBitmap), aRectangle, rRenderContext);
+ drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
+ }
+ }
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(VisualBackendTestWindow, updateHdl, Timer *, void)
+{
+ if (mbAnimate)
+ {
+ maUpdateTimer.SetTimeout(1.0);
+ maUpdateTimer.Start();
+ Invalidate();
+ }
+}
+
+namespace {
+
+class VisualBackendTestApp : public Application
+{
+
+public:
+ VisualBackendTestApp()
+ {}
+
+ virtual int Main() override
+ {
+ try
+ {
+ ScopedVclPtrInstance<VisualBackendTestWindow> aMainWindow;
+
+ aMainWindow->SetText("VCL Test");
+ aMainWindow->Show();
+
+ Application::Execute();
+ }
+ catch (const css::uno::Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("vcl.app", "Fatal");
+ return 1;
+ }
+ catch (const std::exception& rException)
+ {
+ SAL_WARN("vcl.app", "Fatal exception: " << rException.what());
+ return 1;
+ }
+ return 0;
+ }
+
+protected:
+ void Init() override
+ {
+ try
+ {
+ uno::Reference<uno::XComponentContext> xComponentContext = ::cppu::defaultBootstrap_InitialComponentContext();
+ uno::Reference<lang::XMultiServiceFactory> xMSF(xComponentContext->getServiceManager(), uno::UNO_QUERY);
+
+ if (!xMSF.is())
+ Application::Abort("Bootstrap failure - no service manager");
+
+ comphelper::setProcessServiceFactory(xMSF);
+ }
+ catch (const uno::Exception &e)
+ {
+ Application::Abort("Bootstrap exception " + e.Message);
+ }
+ }
+
+ void DeInit() override
+ {
+ comphelper::setProcessServiceFactory(nullptr);
+ }
+};
+
+}
+
+void vclmain::createApplication()
+{
+ static VisualBackendTestApp aApplication;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/backendtest/outputdevice/bitmap.cxx b/vcl/backendtest/outputdevice/bitmap.cxx
new file mode 100644
index 0000000000..517be3968e
--- /dev/null
+++ b/vcl/backendtest/outputdevice/bitmap.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 <test/outputdevice.hxx>
+#include <vcl/bitmapex.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+namespace vcl::test {
+
+Bitmap OutputDeviceTestBitmap::setupDrawTransformedBitmap(vcl::PixelFormat aBitmapFormat,bool isBitmapGreyScale)
+{
+ Size aBitmapSize(9, 9);
+ Bitmap aBitmap(aBitmapSize, aBitmapFormat);
+ {
+ BitmapScopedWriteAccess aWriteAccess(aBitmap);
+ aWriteAccess->Erase(constFillColor);
+ aWriteAccess->SetLineColor(COL_YELLOW);
+ aWriteAccess->DrawRect(tools::Rectangle(0, 0, 8, 8));
+ aWriteAccess->DrawRect(tools::Rectangle(2, 2, 6, 6));
+ }
+
+ if (isBitmapGreyScale)
+ aBitmap.Convert(BmpConversion::N8BitGreys);
+
+ initialSetup(13, 13, constBackgroundColor);
+
+ basegfx::B2DHomMatrix aTransform;
+ aTransform.scale(aBitmapSize.Width(), aBitmapSize.Height());
+ aTransform.translate((maVDRectangle.GetWidth() / 2.0) - (aBitmapSize.Width() / 2.0),
+ (maVDRectangle.GetHeight() / 2.0) - (aBitmapSize.Height() / 2.0));
+
+ mpVirtualDevice->DrawTransformedBitmapEx(aTransform, BitmapEx(aBitmap));
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+
+Bitmap OutputDeviceTestBitmap::setupComplexDrawTransformedBitmap(vcl::PixelFormat aBitmapFormat,bool isBitmapGreyScale)
+{
+ Size aBitmapSize(6, 6);
+ Bitmap aBitmap(aBitmapSize, aBitmapFormat);
+ aBitmap.Erase(constFillColor);
+
+ if (isBitmapGreyScale)
+ aBitmap.Convert(BmpConversion::N8BitGreys);
+
+ initialSetup(17, 14, constBackgroundColor);
+
+ basegfx::B2DHomMatrix aTransform;
+ aTransform.shearX(0.25);
+ aTransform.scale(aBitmapSize.Width() * 2, aBitmapSize.Height() * 2);
+ aTransform.translate(1, 1);
+
+ mpVirtualDevice->DrawTransformedBitmapEx(aTransform, BitmapEx(aBitmap));
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+
+Bitmap OutputDeviceTestBitmap::setupDrawBitmap(vcl::PixelFormat aBitmapFormat,bool isBitmapGreyScale)
+{
+ Size aBitmapSize(9, 9);
+ Bitmap aBitmap(aBitmapSize, aBitmapFormat);
+ {
+ BitmapScopedWriteAccess aWriteAccess(aBitmap);
+ aWriteAccess->Erase(constFillColor);
+ aWriteAccess->SetLineColor(COL_YELLOW);
+ aWriteAccess->DrawRect(tools::Rectangle(0, 0, 8, 8));
+ aWriteAccess->DrawRect(tools::Rectangle(2, 2, 6, 6));
+ }
+
+ if (isBitmapGreyScale)
+ aBitmap.Convert(BmpConversion::N8BitGreys);
+
+ initialSetup(13, 13, constBackgroundColor);
+
+ Point aPoint((maVDRectangle.GetWidth() / 2.0) - (aBitmapSize.Width() / 2.0),
+ (maVDRectangle.GetHeight() / 2.0) - (aBitmapSize.Height() / 2.0));
+
+ mpVirtualDevice->DrawBitmapEx(aPoint, BitmapEx(aBitmap));
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestBitmap::setupDrawBitmapExWithAlpha(vcl::PixelFormat aBitmapFormat)
+{
+ Size aBitmapSize(9, 9);
+ Bitmap aBitmap(aBitmapSize, aBitmapFormat);
+ {
+ BitmapScopedWriteAccess aWriteAccess(aBitmap);
+ aWriteAccess->Erase(COL_WHITE);
+ aWriteAccess->SetLineColor(Color(0xFF, 0xFF, 0x00));
+ aWriteAccess->DrawRect(tools::Rectangle(0, 0, 8, 8));
+ aWriteAccess->DrawRect(tools::Rectangle(3, 3, 5, 5));
+ }
+
+ AlphaMask aAlpha(aBitmapSize);
+ {
+ BitmapScopedWriteAccess aWriteAccess(aAlpha);
+ aWriteAccess->Erase(COL_ALPHA_TRANSPARENT);
+ aWriteAccess->SetLineColor(Color(0xBB, 0xBB, 0xBB));
+ aWriteAccess->DrawRect(tools::Rectangle(0, 0, 8, 8));
+ aWriteAccess->DrawRect(tools::Rectangle(3, 3, 5, 5));
+ }
+
+ initialSetup(13, 13, constBackgroundColor);
+
+ Point aPoint(alignToCenter(maVDRectangle, tools::Rectangle(Point(), aBitmapSize)).TopLeft());
+
+ mpVirtualDevice->DrawBitmapEx(aPoint, BitmapEx(aBitmap, aAlpha));
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestBitmap::setupDrawMask(vcl::PixelFormat aBitmapFormat)
+{
+ Size aBitmapSize(9, 9);
+ Bitmap aBitmap(aBitmapSize, aBitmapFormat);
+ {
+ BitmapScopedWriteAccess aWriteAccess(aBitmap);
+ aWriteAccess->Erase(COL_WHITE);
+ aWriteAccess->SetLineColor(COL_BLACK);
+ aWriteAccess->DrawRect(tools::Rectangle(0, 0, 8, 8));
+ aWriteAccess->DrawRect(tools::Rectangle(3, 3, 5, 5));
+ }
+
+ initialSetup(13, 13, constBackgroundColor);
+
+ mpVirtualDevice->DrawMask(Point(2, 2), aBitmap, constLineColor);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+BitmapEx OutputDeviceTestBitmap::setupDrawBlend(vcl::PixelFormat aBitmapFormat)
+{
+ Size aBitmapSize(9, 9);
+ Bitmap aBitmap(aBitmapSize, aBitmapFormat);
+ {
+ BitmapScopedWriteAccess aWriteAccess(aBitmap);
+ aWriteAccess->Erase(COL_WHITE);
+ aWriteAccess->SetLineColor(Color(0xFF, 0xFF, 0x00));
+ aWriteAccess->DrawRect(tools::Rectangle(0, 0, 8, 8));
+ aWriteAccess->DrawRect(tools::Rectangle(3, 3, 5, 5));
+ }
+
+ AlphaMask aAlpha(aBitmapSize);
+ {
+ BitmapScopedWriteAccess aWriteAccess(aAlpha);
+ aWriteAccess->Erase(COL_ALPHA_TRANSPARENT);
+ aWriteAccess->SetLineColor(Color(0xBB, 0xBB, 0xBB));
+ aWriteAccess->DrawRect(tools::Rectangle(0, 0, 8, 8));
+ aWriteAccess->DrawRect(tools::Rectangle(3, 3, 5, 5));
+ }
+
+ initialSetup(13, 13, COL_TRANSPARENT, false, true);
+ mpVirtualDevice->SetFillColor(constBackgroundColor);
+ mpVirtualDevice->SetLineColor(constBackgroundColor);
+ // Leave the outer part of the device transparent, the inner part set to the background color.
+ // This will test blending of VirtualDevice's "alpha" device (outer yellow rectangle
+ // will be blended with transparent background, inner with the grey one).
+ mpVirtualDevice->DrawRect( tools::Rectangle( Point( 3, 3 ), Size( 7, 7 )));
+
+ Point aPoint(alignToCenter(maVDRectangle, tools::Rectangle(Point(), aBitmapSize)).TopLeft());
+
+ mpVirtualDevice->DrawBitmapEx(aPoint, BitmapEx(aBitmap, aAlpha));
+
+ return mpVirtualDevice->GetBitmapEx(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+} // end namespace vcl::test
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/backendtest/outputdevice/clip.cxx b/vcl/backendtest/outputdevice/clip.cxx
new file mode 100644
index 0000000000..495b43b6f1
--- /dev/null
+++ b/vcl/backendtest/outputdevice/clip.cxx
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/outputdevice.hxx>
+
+namespace vcl::test
+{
+Bitmap OutputDeviceTestClip::setupClipRectangle()
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ tools::Rectangle rectangle = maVDRectangle;
+ rectangle.shrink(2);
+ mpVirtualDevice->SetClipRegion(vcl::Region(rectangle));
+ mpVirtualDevice->SetBackground(constFillColor);
+ mpVirtualDevice->Erase(maVDRectangle);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestClip::setupClipPolygon()
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ tools::Rectangle rectangle = maVDRectangle;
+ rectangle.shrink(2);
+ mpVirtualDevice->SetClipRegion(vcl::Region(tools::Polygon(rectangle)));
+ mpVirtualDevice->SetBackground(constFillColor);
+ mpVirtualDevice->Erase(maVDRectangle);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestClip::setupClipPolyPolygon()
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ tools::Rectangle rectangle = maVDRectangle;
+ rectangle.shrink(2);
+ mpVirtualDevice->SetClipRegion(vcl::Region(tools::PolyPolygon(rectangle)));
+ mpVirtualDevice->SetBackground(constFillColor);
+ mpVirtualDevice->Erase(maVDRectangle);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestClip::setupClipB2DPolyPolygon()
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ tools::Rectangle rectangle = maVDRectangle;
+ rectangle.shrink(2);
+ mpVirtualDevice->SetClipRegion(vcl::Region(basegfx::B2DPolyPolygon(basegfx::B2DPolygon{
+ basegfx::B2DPoint(rectangle.Left(), rectangle.Top()),
+ basegfx::B2DPoint(rectangle.Left(), rectangle.Bottom()),
+ basegfx::B2DPoint(rectangle.Right(), rectangle.Bottom()),
+ basegfx::B2DPoint(rectangle.Right(), rectangle.Top()),
+ })));
+ mpVirtualDevice->SetBackground(constFillColor);
+ mpVirtualDevice->Erase(maVDRectangle);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+TestResult OutputDeviceTestClip::checkClip(Bitmap& aBitmap)
+{
+ std::vector<Color> aExpected{ constBackgroundColor, constBackgroundColor, constFillColor,
+ constFillColor, constFillColor, constFillColor,
+ constFillColor };
+ return checkRectangles(aBitmap, aExpected);
+}
+
+} // end namespace vcl::test
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/backendtest/outputdevice/common.cxx b/vcl/backendtest/outputdevice/common.cxx
new file mode 100644
index 0000000000..bd8b905f64
--- /dev/null
+++ b/vcl/backendtest/outputdevice/common.cxx
@@ -0,0 +1,1668 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/outputdevice.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <salgdi.hxx>
+
+#include <map>
+
+namespace vcl::test {
+
+namespace
+{
+
+
+int deltaColor(BitmapColor aColor1, BitmapColor aColor2)
+{
+ int deltaR = std::abs(aColor1.GetRed() - aColor2.GetRed());
+ int deltaG = std::abs(aColor1.GetGreen() - aColor2.GetGreen());
+ int deltaB = std::abs(aColor1.GetBlue() - aColor2.GetBlue());
+
+ return std::max(std::max(deltaR, deltaG), deltaB);
+}
+
+void checkValue(BitmapScopedWriteAccess& pAccess, int x, int y, Color aExpected,
+ int& nNumberOfQuirks, int& nNumberOfErrors, bool bQuirkMode, int nColorDeltaThresh = 0)
+{
+ const bool bColorize = false;
+ Color aColor = pAccess->GetPixel(y, x);
+ int nColorDelta = deltaColor(aColor, aExpected);
+
+ if (nColorDelta <= nColorDeltaThresh)
+ {
+ if (bColorize)
+ pAccess->SetPixel(y, x, COL_LIGHTGREEN);
+ }
+ else if (bQuirkMode)
+ {
+ nNumberOfQuirks++;
+ if (bColorize)
+ pAccess->SetPixel(y, x, COL_YELLOW);
+ }
+ else
+ {
+ nNumberOfErrors++;
+ if (bColorize)
+ pAccess->SetPixel(y, x, COL_LIGHTRED);
+ }
+}
+
+void checkValue(BitmapScopedWriteAccess& pAccess, const Point& point, Color aExpected,
+ int& nNumberOfQuirks, int& nNumberOfErrors, bool bQuirkMode, int nColorDeltaThresh = 0)
+{
+ checkValue(pAccess, point.getX(), point.getY(), aExpected, nNumberOfQuirks, nNumberOfErrors, bQuirkMode, nColorDeltaThresh);
+}
+
+void checkValue(BitmapScopedWriteAccess& pAccess, int x, int y, Color aExpected,
+ int& nNumberOfQuirks, int& nNumberOfErrors, int nColorDeltaThresh, int nColorDeltaThreshQuirk = 0)
+{
+ const bool bColorize = false;
+ Color aColor = pAccess->GetPixel(y, x);
+ int nColorDelta = deltaColor(aColor, aExpected);
+ nColorDeltaThreshQuirk = std::max( nColorDeltaThresh, nColorDeltaThreshQuirk);
+
+ if (nColorDelta <= nColorDeltaThresh)
+ {
+ if (bColorize)
+ pAccess->SetPixel(y, x, COL_LIGHTGREEN);
+ }
+ else if (nColorDelta <= nColorDeltaThreshQuirk)
+ {
+ nNumberOfQuirks++;
+ if (bColorize)
+ pAccess->SetPixel(y, x, COL_YELLOW);
+ }
+ else
+ {
+ nNumberOfErrors++;
+ if (bColorize)
+ pAccess->SetPixel(y, x, COL_LIGHTRED);
+ }
+}
+
+char returnDominantColor(Color aColor)
+{
+ int aRed = aColor.GetRed();
+ int aGreen = aColor.GetGreen();
+ int aBlue = aColor.GetBlue();
+ if (aRed > aGreen && aRed > aBlue)
+ return 'R';
+
+ if (aGreen > aRed && aGreen > aBlue)
+ return 'G';
+
+ if(aBlue > aRed && aBlue > aGreen)
+ return 'B';
+
+ return 'X'; //No Dominant Color.
+}
+
+void checkValueAA(BitmapScopedWriteAccess& pAccess, int x, int y, Color aExpected,
+ int& nNumberOfQuirks, int& nNumberOfErrors, int nColorDeltaThresh = 64)
+{
+ const bool bColorize = false;
+ Color aColor = pAccess->GetPixel(y, x);
+ bool aColorResult = returnDominantColor(aExpected) == returnDominantColor(aColor);
+ int nColorDelta = deltaColor(aColor, aExpected);
+
+ if (nColorDelta <= nColorDeltaThresh && aColorResult)
+ {
+ if (bColorize)
+ pAccess->SetPixel(y, x, COL_LIGHTGREEN);
+ }
+ else if (aColorResult)
+ {
+ nNumberOfQuirks++;
+ if (bColorize)
+ pAccess->SetPixel(y, x, COL_YELLOW);
+ }
+ else
+ {
+ nNumberOfErrors++;
+ if (bColorize)
+ pAccess->SetPixel(y, x, COL_LIGHTRED);
+ }
+}
+
+// Return all colors in the rectangle and their count.
+std::map<Color, int> collectColors(Bitmap& bitmap, const tools::Rectangle& rectangle)
+{
+ std::map<Color, int> colors;
+ BitmapScopedWriteAccess pAccess(bitmap);
+ for (tools::Long y = rectangle.Top(); y < rectangle.Bottom(); ++y)
+ for (tools::Long x = rectangle.Left(); x < rectangle.Right(); ++x)
+ ++colors[pAccess->GetPixel(y, x)]; // operator[] initializes to 0 (default ctor) if creating
+ return colors;
+}
+
+bool checkConvexHullProperty(Bitmap& bitmap, Color constLineColor, int nWidthOffset,
+ int nHeightOffset)
+{
+ BitmapScopedWriteAccess pAccess(bitmap);
+ tools::Long thresholdWidth = pAccess->Width() - nWidthOffset;
+ tools::Long thresholdHeight = pAccess->Height() - nHeightOffset;
+ for (tools::Long y = 0; y < pAccess->Height(); ++y)
+ {
+ for (tools::Long x = 0; x < pAccess->Width(); ++x)
+ {
+ /*
+ If the shape exceeds the threshold limit of height or width or both,
+ this would indicate that the bezier curve is not within its convex polygon and
+ hence is faulty.
+ */
+ if (pAccess->GetPixel(y, x) == constLineColor
+ && (thresholdHeight < y || thresholdWidth < x))
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+TestResult checkRect(Bitmap& rBitmap, int aLayerNumber, Color aExpectedColor)
+{
+ BitmapScopedWriteAccess pAccess(rBitmap);
+ tools::Long nHeight = pAccess->Height();
+ tools::Long nWidth = pAccess->Width();
+
+ tools::Long firstX = 0 + aLayerNumber;
+ tools::Long firstY = 0 + aLayerNumber;
+
+ tools::Long lastX = nWidth - aLayerNumber - 1;
+ tools::Long lastY = nHeight - aLayerNumber - 1;
+
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ // check corner quirks
+ checkValue(pAccess, firstX, firstY, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true);
+ checkValue(pAccess, lastX, firstY, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true);
+ checkValue(pAccess, firstX, lastY, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true);
+ checkValue(pAccess, lastX, lastY, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true);
+
+ for (tools::Long y = firstY + 1; y <= lastY - 1; y++)
+ {
+ checkValue(pAccess, firstX, y, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(pAccess, lastX, y, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, false);
+ }
+ for (tools::Long x = firstX + 1; x <= lastX - 1; x++)
+ {
+ checkValue(pAccess, x, firstY, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(pAccess, x, lastY, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, false);
+ }
+ if (nNumberOfQuirks > 0)
+ aResult = TestResult::PassedWithQuirks;
+ if (nNumberOfErrors > 0)
+ aResult = TestResult::Failed;
+ return aResult;
+}
+
+TestResult checkHorizontalVerticalDiagonalLines(Bitmap& rBitmap, Color aExpectedColor, int nColorThresh)
+{
+ BitmapScopedWriteAccess pAccess(rBitmap);
+ tools::Long nWidth = pAccess->Width();
+ tools::Long nHeight = pAccess->Height();
+
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ // check horizontal line
+ {
+ tools::Long startX = 4;
+ tools::Long endX = nWidth - 2;
+
+ tools::Long y = 1;
+
+ checkValue(pAccess, startX, y, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true, nColorThresh);
+ checkValue(pAccess, endX, y, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true, nColorThresh);
+
+ for (tools::Long x = startX + 1; x <= endX - 1; x++)
+ {
+ checkValue(pAccess, x, y, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, false, nColorThresh);
+ }
+ }
+
+ // check vertical line
+ {
+ tools::Long startY = 4;
+ tools::Long endY = nHeight - 2;
+
+ tools::Long x = 1;
+
+ checkValue(pAccess, x, startY, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true, nColorThresh);
+ checkValue(pAccess, x, endY, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true, nColorThresh);
+
+ for (tools::Long y = startY + 1; y <= endY - 1; y++)
+ {
+ checkValue(pAccess, x, y, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, false, nColorThresh);
+ }
+ }
+
+ // check diagonal line
+ {
+ tools::Long startX = 1;
+ tools::Long endX = nWidth - 2;
+
+ tools::Long startY = 1;
+ tools::Long endY = nHeight - 2;
+
+ tools::Long x = startX;
+ tools::Long y = startY;
+
+ checkValue(pAccess, startX, startY, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true, nColorThresh);
+ checkValue(pAccess, endX, endY, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true, nColorThresh);
+
+ x++; y++;
+
+ while(y <= endY - 1 && x <= endX - 1)
+ {
+ checkValue(pAccess, x, y, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, false, nColorThresh);
+ x++; y++;
+ }
+ }
+
+ if (nNumberOfQuirks > 0)
+ aResult = TestResult::PassedWithQuirks;
+ if (nNumberOfErrors > 0)
+ aResult = TestResult::Failed;
+ return aResult;
+}
+
+TestResult checkDiamondLine(Bitmap& rBitmap, int aLayerNumber, Color aExpectedColor)
+{
+ BitmapScopedWriteAccess pAccess(rBitmap);
+ tools::Long nHeight = pAccess->Height();
+ tools::Long nWidth = pAccess->Width();
+
+ tools::Long midX = nWidth / 2;
+ tools::Long midY = nHeight / 2;
+
+ tools::Long firstX = aLayerNumber;
+ tools::Long lastX = nWidth - aLayerNumber - 1;
+
+ tools::Long firstY = aLayerNumber;
+ tools::Long lastY = nHeight - aLayerNumber - 1;
+
+ tools::Long offsetFromMid = 0;
+
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ checkValue(pAccess, firstX, midY, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true);
+ checkValue(pAccess, lastX, midY, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true);
+ checkValue(pAccess, midX, firstY, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true);
+ checkValue(pAccess, midX, lastY, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, true);
+
+ offsetFromMid = 1;
+ for (tools::Long x = firstX + 1; x <= midX - 1; x++)
+ {
+ checkValue(pAccess, x, midY - offsetFromMid, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(pAccess, x, midY + offsetFromMid, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, false);
+
+ offsetFromMid++;
+ }
+
+ offsetFromMid = midY - aLayerNumber - 1;
+
+ for (tools::Long x = midX + 1; x <= lastX - 1; x++)
+ {
+ checkValue(pAccess, x, midY - offsetFromMid, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(pAccess, x, midY + offsetFromMid, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, false);
+
+ offsetFromMid--;
+ }
+
+ if (nNumberOfQuirks > 0)
+ aResult = TestResult::PassedWithQuirks;
+ if (nNumberOfErrors > 0)
+ aResult = TestResult::Failed;
+ return aResult;
+}
+
+} // end anonymous namespace
+
+const Color OutputDeviceTestCommon::constBackgroundColor(COL_LIGHTGRAY);
+const Color OutputDeviceTestCommon::constLineColor(COL_LIGHTBLUE);
+const Color OutputDeviceTestCommon::constFillColor(COL_BLUE);
+
+OutputDeviceTestCommon::OutputDeviceTestCommon()
+{}
+
+OUString OutputDeviceTestCommon::getRenderBackendName() const
+{
+ if (mpVirtualDevice && mpVirtualDevice->GetGraphics())
+ {
+ SalGraphics const * pGraphics = mpVirtualDevice->GetGraphics();
+ return pGraphics->getRenderBackendName();
+ }
+ return OUString();
+}
+
+void OutputDeviceTestCommon::initialSetup(tools::Long nWidth, tools::Long nHeight, Color aColor, bool bEnableAA, bool bAlphaVirtualDevice)
+{
+ if (bAlphaVirtualDevice)
+ mpVirtualDevice = VclPtr<VirtualDevice>::Create(DeviceFormat::WITH_ALPHA);
+ else
+ mpVirtualDevice = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+
+ maVDRectangle = tools::Rectangle(Point(), Size (nWidth, nHeight));
+ mpVirtualDevice->SetOutputSizePixel(maVDRectangle.GetSize());
+ if (bEnableAA)
+ mpVirtualDevice->SetAntialiasing(AntialiasingFlags::Enable | AntialiasingFlags::PixelSnapHairline);
+ else
+ mpVirtualDevice->SetAntialiasing(AntialiasingFlags::NONE);
+ mpVirtualDevice->SetBackground(Wallpaper(aColor));
+ mpVirtualDevice->Erase();
+}
+
+TestResult OutputDeviceTestCommon::checkLines(Bitmap& rBitmap)
+{
+ return checkHorizontalVerticalDiagonalLines(rBitmap, constLineColor, 0);
+}
+
+TestResult OutputDeviceTestCommon::checkAALines(Bitmap& rBitmap)
+{
+ return checkHorizontalVerticalDiagonalLines(rBitmap, constLineColor, 30); // 30 color values threshold delta
+}
+
+static void checkResult(TestResult eResult, TestResult & eTotal)
+{
+ if (eTotal == TestResult::Failed)
+ return;
+
+ if (eResult == TestResult::Failed)
+ eTotal = TestResult::Failed;
+
+ if (eResult == TestResult::PassedWithQuirks)
+ eTotal = TestResult::PassedWithQuirks;
+}
+
+TestResult OutputDeviceTestCommon::checkInvertRectangle(Bitmap& aBitmap)
+{
+ TestResult aReturnValue = TestResult::Passed;
+ TestResult eResult;
+
+ std::vector<Color> aExpected{ COL_WHITE, COL_WHITE };
+ eResult = checkRectangles(aBitmap, aExpected);
+ checkResult(eResult, aReturnValue);
+
+ eResult = checkFilled(aBitmap, tools::Rectangle(Point(2, 2), Size(8, 8)), COL_LIGHTCYAN);
+ checkResult(eResult, aReturnValue);
+
+ eResult = checkFilled(aBitmap, tools::Rectangle(Point(10, 2), Size(8, 8)), COL_LIGHTMAGENTA);
+ checkResult(eResult, aReturnValue);
+
+ eResult = checkFilled(aBitmap, tools::Rectangle(Point(2, 10), Size(8, 8)), COL_YELLOW);
+ checkResult(eResult, aReturnValue);
+
+ eResult = checkFilled(aBitmap, tools::Rectangle(Point(10, 10), Size(8, 8)), COL_BLACK);
+ checkResult(eResult, aReturnValue);
+
+ return aReturnValue;
+}
+
+TestResult OutputDeviceTestCommon::checkChecker(Bitmap& rBitmap, sal_Int32 nStartX, sal_Int32 nEndX, sal_Int32 nStartY, sal_Int32 nEndY, std::vector<Color> const & rExpected)
+{
+ TestResult aReturnValue = TestResult::Passed;
+
+ int choice = 0;
+ for (sal_Int32 y = nStartY; y <= nEndY; ++y)
+ {
+ for (sal_Int32 x = nStartX; x <= nEndX; ++x)
+ {
+ TestResult eResult = checkFilled(rBitmap, tools::Rectangle(Point(x, y), Size(1, 1)), rExpected[choice % 2]);
+ checkResult(eResult, aReturnValue);
+ choice++;
+ }
+ choice++;
+ }
+ return aReturnValue;
+}
+
+TestResult OutputDeviceTestCommon::checkInvertN50Rectangle(Bitmap& aBitmap)
+{
+ TestResult aReturnValue = TestResult::Passed;
+ TestResult eResult;
+
+ std::vector<Color> aExpected{ COL_WHITE, COL_WHITE };
+ eResult = checkRectangles(aBitmap, aExpected);
+ checkResult(eResult, aReturnValue);
+
+ eResult = checkChecker(aBitmap, 2, 9, 2, 9, { COL_LIGHTCYAN, COL_LIGHTRED });
+ checkResult(eResult, aReturnValue);
+ eResult = checkChecker(aBitmap, 2, 9, 10, 17, { COL_YELLOW, COL_LIGHTBLUE });
+ checkResult(eResult, aReturnValue);
+ eResult = checkChecker(aBitmap, 10, 17, 2, 9, { COL_LIGHTMAGENTA, COL_LIGHTGREEN });
+ checkResult(eResult, aReturnValue);
+ eResult = checkChecker(aBitmap, 10, 17, 10, 17, { COL_BLACK, COL_WHITE });
+ checkResult(eResult, aReturnValue);
+
+ return aReturnValue;
+}
+
+TestResult OutputDeviceTestCommon::checkInvertTrackFrameRectangle(Bitmap& aBitmap)
+{
+ std::vector<Color> aExpected
+ {
+ COL_WHITE, COL_WHITE
+ };
+ return checkRectangles(aBitmap, aExpected);
+}
+
+TestResult OutputDeviceTestCommon::checkRectangle(Bitmap& aBitmap)
+{
+ std::vector<Color> aExpected
+ {
+ constBackgroundColor, constBackgroundColor, constLineColor,
+ constBackgroundColor, constBackgroundColor, constLineColor, constBackgroundColor
+ };
+ return checkRectangles(aBitmap, aExpected);
+}
+
+TestResult OutputDeviceTestCommon::checkRectangles(Bitmap& rBitmap, bool aEnableAA)
+{
+ BitmapScopedWriteAccess pAccess(rBitmap);
+
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ std::vector<Color> aExpected = { constBackgroundColor, constLineColor, constLineColor };
+
+ for (size_t aLayerNumber = 0; aLayerNumber < aExpected.size(); aLayerNumber++)
+ {
+ tools::Long startX = aLayerNumber, endX = pAccess->Width() / 2 - aLayerNumber + 1;
+ tools::Long startY = aLayerNumber, endY = pAccess->Height() - aLayerNumber - 1;
+
+ for (tools::Long ptX = startX; ptX <= endX; ++ptX)
+ {
+ if (aEnableAA)
+ {
+ checkValueAA(pAccess, ptX, startY + (aLayerNumber == 2 ? 2 : 0),
+ aExpected[aLayerNumber], nNumberOfQuirks, nNumberOfErrors);
+ checkValueAA(pAccess, ptX, endY - (aLayerNumber == 2 ? 2 : 0),
+ aExpected[aLayerNumber], nNumberOfQuirks, nNumberOfErrors);
+ }
+ else
+ {
+ checkValue(pAccess, ptX, startY + (aLayerNumber == 2 ? 2 : 0),
+ aExpected[aLayerNumber], nNumberOfQuirks, nNumberOfErrors, true);
+ checkValue(pAccess, ptX, endY - (aLayerNumber == 2 ? 2 : 0),
+ aExpected[aLayerNumber], nNumberOfQuirks, nNumberOfErrors, true);
+ }
+ }
+ for (tools::Long ptY = startY + (aLayerNumber == 2 ? 2 : 0);
+ ptY <= endY - (aLayerNumber == 2 ? 2 : 0); ++ptY)
+ {
+ if (aEnableAA)
+ {
+ checkValueAA(pAccess, startX, ptY, aExpected[aLayerNumber], nNumberOfQuirks,
+ nNumberOfErrors);
+ checkValueAA(pAccess, endX, ptY, aExpected[aLayerNumber], nNumberOfQuirks,
+ nNumberOfErrors);
+ }
+ else
+ {
+ checkValue(pAccess, startX, ptY, aExpected[aLayerNumber], nNumberOfQuirks,
+ nNumberOfErrors, true);
+ checkValue(pAccess, endX, ptY, aExpected[aLayerNumber], nNumberOfQuirks,
+ nNumberOfErrors, true);
+ }
+ }
+ }
+ if (nNumberOfQuirks > 0)
+ aResult = TestResult::PassedWithQuirks;
+ if (nNumberOfErrors > 0)
+ aResult = TestResult::Failed;
+ return aResult;
+}
+
+TestResult OutputDeviceTestCommon::checkRectangleAA(Bitmap& aBitmap)
+{
+ return checkRectangles(aBitmap, true);
+}
+
+TestResult OutputDeviceTestCommon::checkFilledRectangle(Bitmap& aBitmap, bool useLineColor)
+{
+ std::vector<Color> aExpected{ constBackgroundColor,
+ useLineColor ? constLineColor : constFillColor, constFillColor,
+ constFillColor, constFillColor };
+
+ BitmapScopedWriteAccess pAccess(aBitmap);
+
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ for (size_t aLayerNumber = 0; aLayerNumber < aExpected.size(); aLayerNumber++)
+ {
+ tools::Long startX = aLayerNumber, endX = pAccess->Width() / 2 - aLayerNumber + 1;
+ tools::Long startY = aLayerNumber, endY = pAccess->Height() - aLayerNumber - 1;
+
+ for (tools::Long ptX = startX; ptX <= endX; ++ptX)
+ {
+ checkValue(pAccess, ptX, startY, aExpected[aLayerNumber], nNumberOfQuirks, nNumberOfErrors,
+ true);
+ checkValue(pAccess, ptX, endY, aExpected[aLayerNumber], nNumberOfQuirks, nNumberOfErrors, true);
+ }
+ for (tools::Long ptY = startY; ptY <= endY; ++ptY)
+ {
+ checkValue(pAccess, startX, ptY, aExpected[aLayerNumber], nNumberOfQuirks, nNumberOfErrors,
+ true);
+ checkValue(pAccess, endX, ptY, aExpected[aLayerNumber], nNumberOfQuirks, nNumberOfErrors, true);
+ }
+ }
+ if (nNumberOfQuirks > 0)
+ aResult = TestResult::PassedWithQuirks;
+ if (nNumberOfErrors > 0)
+ aResult = TestResult::Failed;
+ return aResult;
+}
+
+TestResult OutputDeviceTestCommon::checkFilled(Bitmap& rBitmap, tools::Rectangle aRectangle, Color aExpectedColor)
+{
+ BitmapScopedWriteAccess pAccess(rBitmap);
+
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ for (tools::Long y = aRectangle.Top(); y < aRectangle.Top() + aRectangle.GetHeight(); y++)
+ {
+ for (tools::Long x = aRectangle.Left(); x < aRectangle.Left() + aRectangle.GetWidth(); x++)
+ {
+ checkValue(pAccess, x, y, aExpectedColor, nNumberOfQuirks, nNumberOfErrors, false);
+ }
+ }
+
+ if (nNumberOfQuirks > 0)
+ aResult = TestResult::PassedWithQuirks;
+
+ if (nNumberOfErrors > 0)
+ aResult = TestResult::Failed;
+
+ return aResult;
+}
+
+TestResult OutputDeviceTestCommon::checkRectangles(Bitmap& aBitmap, std::vector<Color>& aExpectedColors)
+{
+ TestResult aReturnValue = TestResult::Passed;
+ for (size_t i = 0; i < aExpectedColors.size(); i++)
+ {
+ TestResult eResult = checkRect(aBitmap, i, aExpectedColors[i]);
+
+ if (eResult == TestResult::Failed)
+ aReturnValue = TestResult::Failed;
+ if (eResult == TestResult::PassedWithQuirks && aReturnValue != TestResult::Failed)
+ aReturnValue = TestResult::PassedWithQuirks;
+ }
+ return aReturnValue;
+}
+
+TestResult OutputDeviceTestCommon::checkRectangle(Bitmap& rBitmap, int aLayerNumber, Color aExpectedColor)
+{
+ return checkRect(rBitmap, aLayerNumber, aExpectedColor);
+}
+
+tools::Rectangle OutputDeviceTestCommon::alignToCenter(tools::Rectangle aRect1, tools::Rectangle aRect2)
+{
+ Point aPoint((aRect1.GetWidth() / 2.0) - (aRect2.GetWidth() / 2.0),
+ (aRect1.GetHeight() / 2.0) - (aRect2.GetHeight() / 2.0));
+
+ return tools::Rectangle(aPoint, aRect2.GetSize());
+}
+
+TestResult OutputDeviceTestCommon::checkDiamond(Bitmap& rBitmap)
+{
+ return checkDiamondLine(rBitmap, 1, constLineColor);
+}
+
+void OutputDeviceTestCommon::createDiamondPoints(tools::Rectangle rRect, int nOffset,
+ Point& rPoint1, Point& rPoint2,
+ Point& rPoint3, Point& rPoint4)
+{
+ tools::Long midPointX = rRect.Left() + (rRect.Right() - rRect.Left()) / 2.0;
+ tools::Long midPointY = rRect.Top() + (rRect.Bottom() - rRect.Top()) / 2.0;
+
+ rPoint1 = Point(midPointX , midPointY - nOffset);
+ rPoint2 = Point(midPointX + nOffset, midPointY );
+ rPoint3 = Point(midPointX , midPointY + nOffset);
+ rPoint4 = Point(midPointX - nOffset, midPointY );
+}
+
+tools::Polygon OutputDeviceTestCommon::createDropShapePolygon()
+{
+ tools::Polygon aPolygon(15);
+
+ aPolygon.SetPoint(Point(10, 2), 0);
+ aPolygon.SetFlags(0, PolyFlags::Normal);
+ aPolygon.SetPoint(Point(14, 2), 1);
+ aPolygon.SetFlags(1, PolyFlags::Control);
+ aPolygon.SetPoint(Point(18, 6), 2);
+ aPolygon.SetFlags(2, PolyFlags::Control);
+ aPolygon.SetPoint(Point(18, 10), 3);
+
+ aPolygon.SetFlags(3, PolyFlags::Normal);
+ aPolygon.SetPoint(Point(18, 10), 4);
+ aPolygon.SetFlags(4, PolyFlags::Normal);
+ aPolygon.SetPoint(Point(18, 14), 5);
+ aPolygon.SetFlags(5, PolyFlags::Control);
+ aPolygon.SetPoint(Point(14, 18), 6);
+ aPolygon.SetFlags(6, PolyFlags::Control);
+ aPolygon.SetPoint(Point(10, 18), 7);
+ aPolygon.SetFlags(7, PolyFlags::Normal);
+
+ aPolygon.SetPoint(Point(10, 18), 8);
+ aPolygon.SetFlags(8, PolyFlags::Normal);
+ aPolygon.SetPoint(Point(6, 18), 9);
+ aPolygon.SetFlags(9, PolyFlags::Control);
+ aPolygon.SetPoint(Point(2, 14), 10);
+ aPolygon.SetFlags(10, PolyFlags::Control);
+ aPolygon.SetPoint(Point(2, 10), 11);
+ aPolygon.SetFlags(11, PolyFlags::Normal);
+
+ aPolygon.SetPoint(Point(2, 10), 12);
+ aPolygon.SetFlags(12, PolyFlags::Normal);
+ aPolygon.SetPoint(Point(2, 2), 13);
+ aPolygon.SetFlags(13, PolyFlags::Normal);
+ aPolygon.SetPoint(Point(10, 2), 14);
+ aPolygon.SetFlags(14, PolyFlags::Normal);
+
+ aPolygon.Optimize(PolyOptimizeFlags::CLOSE);
+
+ return aPolygon;
+}
+
+basegfx::B2DPolygon OutputDeviceTestCommon::createHalfEllipsePolygon()
+{
+ basegfx::B2DPolygon aPolygon;
+
+ aPolygon.append({ 9.0, 1.0 });
+ aPolygon.append({ 17.0, 10.0 });
+ aPolygon.append({ 1.0, 10.0 });
+ aPolygon.setClosed(true);
+
+ aPolygon.setControlPoints(0, { 1.5, 1.5 }, { 16.5, 1.5 });
+
+ return aPolygon;
+}
+
+tools::Polygon OutputDeviceTestCommon::createClosedBezierLoop(const tools::Rectangle& rRect)
+{
+ tools::Long minX = rRect.Left();
+ tools::Long maxX = rRect.Right() - 2;
+ tools::Long minY = rRect.Top();
+ tools::Long maxY = rRect.Bottom() - 2;
+
+ tools::Polygon aPolygon(4);
+
+ aPolygon.SetPoint(Point((maxX / 2.0), maxY), 0);
+ aPolygon.SetFlags(0, PolyFlags::Normal);
+ aPolygon.SetPoint(Point(maxX, minY), 1);
+ aPolygon.SetFlags(1, PolyFlags::Control);
+ aPolygon.SetPoint(Point(minX, minY), 2);
+ aPolygon.SetFlags(2, PolyFlags::Control);
+ aPolygon.SetPoint(Point((maxX / 2.0), maxY), 3);
+ aPolygon.SetFlags(3, PolyFlags::Normal);
+
+ aPolygon.Optimize(PolyOptimizeFlags::CLOSE);
+
+ return aPolygon;
+}
+
+basegfx::B2DPolygon OutputDeviceTestCommon::createOpenPolygon(const tools::Rectangle& rRect, int nOffset)
+{
+ int nMidOffset = rRect.GetWidth() / 2;
+ basegfx::B2DPolygon aPolygon{
+ basegfx::B2DPoint(rRect.Left() + nOffset - (nOffset + 1) / 2, rRect.Top() + nOffset - 1),
+ basegfx::B2DPoint(rRect.Left() + nOffset - (nOffset + 1) / 2, rRect.Bottom() - nOffset + 1),
+ basegfx::B2DPoint(rRect.Right() - nMidOffset - nOffset / 3, rRect.Bottom() - nOffset + 1),
+ basegfx::B2DPoint(rRect.Right() - nMidOffset - nOffset / 3, rRect.Top() + nOffset - 1),
+ };
+ aPolygon.setClosed(false);
+ return aPolygon;
+}
+
+basegfx::B2DPolygon OutputDeviceTestCommon::createOpenBezier()
+{
+ basegfx::B2DPolygon aPolygon;
+
+ aPolygon.append({ 5.0, 2.0 });
+ aPolygon.append({ 3.0, 14.0 });
+ aPolygon.setClosed(false);
+
+ aPolygon.setControlPoints(0, { 15.0, 2.0 }, { 15.0, 15.0 });
+
+ return aPolygon;
+}
+
+TestResult OutputDeviceTestCommon::checkDropShape(Bitmap& rBitmap, bool aEnableAA)
+{
+ BitmapScopedWriteAccess pAccess(rBitmap);
+
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ std::map<std::pair<int, int>, bool> SetPixels
+ = { { { 2, 2 }, true }, { { 3, 2 }, true }, { { 4, 2 }, true }, { { 5, 2 }, true },
+ { { 6, 2 }, true }, { { 7, 2 }, true }, { { 8, 2 }, true }, { { 9, 2 }, true },
+ { { 10, 2 }, true }, { { 11, 2 }, true }, { { 12, 2 }, true }, { { 2, 3 }, true },
+ { { 13, 3 }, true }, { { 14, 3 }, true }, { { 2, 4 }, true }, { { 15, 4 }, true },
+ { { 2, 5 }, true }, { { 16, 5 }, true }, { { 2, 6 }, true }, { { 17, 6 }, true },
+ { { 2, 7 }, true }, { { 17, 7 }, true }, { { 2, 8 }, true }, { { 18, 8 }, true },
+ { { 2, 9 }, true }, { { 18, 9 }, true }, { { 2, 10 }, true }, { { 18, 10 }, true },
+ { { 2, 11 }, true }, { { 18, 11 }, true }, { { 2, 12 }, true }, { { 18, 12 }, true },
+ { { 3, 13 }, true }, { { 17, 13 }, true }, { { 3, 14 }, true }, { { 17, 14 }, true },
+ { { 4, 15 }, true }, { { 16, 15 }, true }, { { 5, 16 }, true }, { { 15, 16 }, true },
+ { { 6, 17 }, true }, { { 7, 17 }, true }, { { 13, 17 }, true }, { { 14, 17 }, true },
+ { { 8, 18 }, true }, { { 9, 18 }, true }, { { 10, 18 }, true }, { { 11, 18 }, true },
+ { { 12, 18 }, true } };
+
+ for (tools::Long x = 0; x < pAccess->Width(); x++)
+ {
+ for (tools::Long y = 0; y < pAccess->Height(); y++)
+ {
+ if (SetPixels[{ x, y }])
+ {
+ if (aEnableAA)
+ {
+ // coverity[swapped_arguments : FALSE] - this is in the correct order
+ checkValueAA(pAccess, y, x, constLineColor, nNumberOfQuirks, nNumberOfErrors);
+ }
+ else
+ checkValue(pAccess, y, x, constLineColor, nNumberOfQuirks, nNumberOfErrors,
+ true);
+ }
+ else
+ {
+ if (!aEnableAA)
+ checkValue(pAccess, y, x, constBackgroundColor, nNumberOfQuirks, nNumberOfErrors,
+ true);
+ }
+ }
+ }
+
+ if (nNumberOfQuirks > 0)
+ aResult = TestResult::PassedWithQuirks;
+ if (nNumberOfErrors > 0)
+ aResult = TestResult::Failed;
+ return aResult;
+}
+
+void OutputDeviceTestCommon::createHorizontalVerticalDiagonalLinePoints(tools::Rectangle rRect,
+ Point& rHorizontalLinePoint1, Point& rHorizontalLinePoint2,
+ Point& rVerticalLinePoint1, Point& rVerticalLinePoint2,
+ Point& rDiagonalLinePoint1, Point& rDiagonalLinePoint2)
+{
+ rHorizontalLinePoint1 = Point(4, 1);
+ rHorizontalLinePoint2 = Point(rRect.Right() - 1, 1);
+
+ rVerticalLinePoint1 = Point(1, 4);
+ rVerticalLinePoint2 = Point(1,rRect.Bottom() - 1);
+
+ rDiagonalLinePoint1 = Point(1, 1);
+ rDiagonalLinePoint2 = Point(rRect.Right() - 1, rRect.Bottom() - 1);
+}
+
+TestResult OutputDeviceTestCommon::checkBezier(Bitmap& rBitmap)
+{
+ std::vector<Color> aExpected
+ {
+ constBackgroundColor, constBackgroundColor
+ };
+ // Check the bezier doesn't go over to the margins first
+ // TODO extend the check with more exact assert
+ return checkRectangles(rBitmap, aExpected);
+}
+
+TestResult OutputDeviceTestCommon::checkHalfEllipse(Bitmap& rBitmap, bool aEnableAA)
+{
+ BitmapScopedWriteAccess pAccess(rBitmap);
+
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ std::map<std::pair<tools::Long, tools::Long>, bool> SetPixels = {
+ { { 8, 1 }, true }, { { 9, 1 }, true }, { { 10, 1 }, true }, { { 6, 2 }, true },
+ { { 7, 2 }, true }, { { 10, 2 }, true }, { { 4, 3 }, true }, { { 5, 3 }, true },
+ { { 10, 3 }, true }, { { 3, 4 }, true }, { { 10, 4 }, true }, { { 2, 5 }, true },
+ { { 10, 5 }, true }, { { 2, 6 }, true }, { { 10, 6 }, true }, { { 1, 7 }, true },
+ { { 10, 7 }, true }, { { 1, 8 }, true }, { { 10, 8 }, true }, { { 1, 9 }, true },
+ { { 10, 9 }, true }, { { 1, 10 }, true }, { { 10, 10 }, true }, { { 1, 11 }, true },
+ { { 10, 11 }, true }, { { 2, 12 }, true }, { { 10, 12 }, true }, { { 2, 13 }, true },
+ { { 10, 13 }, true }, { { 3, 14 }, true }, { { 10, 14 }, true }, { { 4, 15 }, true },
+ { { 5, 15 }, true }, { { 10, 15 }, true }, { { 6, 16 }, true }, { { 7, 16 }, true },
+ { { 10, 16 }, true }, { { 8, 17 }, true }, { { 9, 17 }, true }, { { 10, 17 }, true }
+ };
+
+ for (tools::Long x = 0; x < pAccess->Width(); x++)
+ {
+ for (tools::Long y = 0; y < pAccess->Height(); ++y)
+ {
+ // coverity[swapped_arguments : FALSE] - this is in the correct order
+ if (SetPixels[{ y, x }])
+ {
+ if (aEnableAA)
+ checkValueAA(pAccess, x, y, constLineColor, nNumberOfQuirks, nNumberOfErrors);
+ else
+ checkValue(pAccess, x, y, constLineColor, nNumberOfQuirks, nNumberOfErrors,
+ true);
+ }
+ else
+ {
+ if (!aEnableAA)
+ checkValue(pAccess, x, y, constBackgroundColor, nNumberOfQuirks,
+ nNumberOfErrors, true);
+ }
+ }
+ }
+
+ if (nNumberOfQuirks > 0)
+ aResult = TestResult::PassedWithQuirks;
+ if (nNumberOfErrors > 0)
+ aResult = TestResult::Failed;
+ return aResult;
+}
+
+TestResult OutputDeviceTestCommon::checkClosedBezier(Bitmap& rBitmap)
+{
+ BitmapScopedWriteAccess pAccess(rBitmap);
+
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ std::map<std::pair<tools::Long, tools::Long>, bool> SetPixels
+ = { { { 3, 8 }, true }, { { 3, 9 }, true }, { { 3, 10 }, true }, { { 4, 7 }, true },
+ { { 4, 8 }, true }, { { 4, 9 }, true }, { { 4, 10 }, true }, { { 4, 11 }, true },
+ { { 5, 7 }, true }, { { 5, 11 }, true }, { { 6, 6 }, true }, { { 6, 12 }, true },
+ { { 7, 6 }, true }, { { 7, 12 }, true }, { { 8, 7 }, true }, { { 8, 11 }, true },
+ { { 9, 7 }, true }, { { 9, 11 }, true }, { { 10, 7 }, true }, { { 10, 11 }, true },
+ { { 11, 8 }, true }, { { 11, 9 }, true }, { { 11, 10 }, true }, { { 12, 8 }, true },
+ { { 12, 9 }, true }, { { 12, 10 }, true }, { { 13, 9 }, true } };
+
+ for (tools::Long x = 0; x < pAccess->Width(); x++)
+ {
+ for (tools::Long y = 0; y < pAccess->Height(); ++y)
+ {
+ // coverity[swapped_arguments : FALSE] - this is in the correct order
+ if (SetPixels[{ y, x }])
+ {
+ checkValue(pAccess, x, y, constLineColor, nNumberOfQuirks, nNumberOfErrors, true);
+ }
+ else
+ {
+ checkValue(pAccess, x, y, constBackgroundColor, nNumberOfQuirks, nNumberOfErrors,
+ true);
+ }
+ }
+ }
+
+ if (nNumberOfQuirks > 0)
+ aResult = TestResult::PassedWithQuirks;
+ if (nNumberOfErrors > 0 || !checkConvexHullProperty(rBitmap, constLineColor, 2, 2))
+ aResult = TestResult::Failed;
+ return aResult;
+}
+
+TestResult OutputDeviceTestCommon::checkOpenBezier(Bitmap& rBitmap)
+{
+ BitmapScopedWriteAccess pAccess(rBitmap);
+
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ std::map<std::pair<int, int>, bool> SetPixels
+ = { { { 14, 3 }, true }, { { 14, 4 }, true }, { { 14, 5 }, true }, { { 3, 6 }, true },
+ { { 4, 6 }, true }, { { 14, 6 }, true }, { { 4, 7 }, true }, { { 5, 7 }, true },
+ { { 13, 7 }, true }, { { 6, 8 }, true }, { { 7, 8 }, true }, { { 12, 8 }, true },
+ { { 13, 8 }, true }, { { 8, 9 }, true }, { { 9, 9 }, true }, { { 10, 9 }, true },
+ { { 11, 9 }, true }, { { 12, 9 }, true } };
+
+ for (tools::Long x = 0; x < pAccess->Width(); x++)
+ {
+ for (tools::Long y = 0; y < pAccess->Height(); ++y)
+ {
+ // coverity[swapped_arguments : FALSE] - this is in the correct order
+ if (SetPixels[{ y, x }])
+ {
+ checkValue(pAccess, x, y, constLineColor, nNumberOfQuirks, nNumberOfErrors, true);
+ }
+ else
+ {
+ checkValue(pAccess, x, y, constBackgroundColor, nNumberOfQuirks, nNumberOfErrors,
+ true);
+ }
+ }
+ }
+ if (nNumberOfQuirks > 0)
+ aResult = TestResult::PassedWithQuirks;
+ if (nNumberOfErrors > 0 || !checkConvexHullProperty(rBitmap, constLineColor, 2, 5))
+ aResult = TestResult::Failed;
+ return aResult;
+}
+
+TestResult OutputDeviceTestCommon::checkFilledAsymmetricalDropShape(Bitmap& rBitmap)
+{
+ BitmapScopedWriteAccess pAccess(rBitmap);
+
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ std::map<std::pair<tools::Long, tools::Long>, bool> SetPixels
+ = { { { 2, 2 }, true }, { { 3, 2 }, true }, { { 4, 2 }, true }, { { 5, 2 }, true },
+ { { 6, 2 }, true }, { { 7, 2 }, true }, { { 8, 2 }, true }, { { 9, 2 }, true },
+ { { 10, 2 }, true }, { { 11, 2 }, true }, { { 2, 3 }, true }, { { 3, 3 }, true },
+ { { 4, 3 }, true }, { { 5, 3 }, true }, { { 6, 3 }, true }, { { 7, 3 }, true },
+ { { 8, 3 }, true }, { { 9, 3 }, true }, { { 10, 3 }, true }, { { 11, 3 }, true },
+ { { 12, 3 }, true }, { { 13, 3 }, true }, { { 2, 4 }, true }, { { 3, 4 }, true },
+ { { 4, 4 }, true }, { { 5, 4 }, true }, { { 6, 4 }, true }, { { 7, 4 }, true },
+ { { 8, 4 }, true }, { { 9, 4 }, true }, { { 10, 4 }, true }, { { 11, 4 }, true },
+ { { 12, 4 }, true }, { { 13, 4 }, true }, { { 14, 4 }, true }, { { 15, 4 }, true },
+ { { 2, 5 }, true }, { { 3, 5 }, true }, { { 4, 5 }, true }, { { 5, 5 }, true },
+ { { 6, 5 }, true }, { { 7, 5 }, true }, { { 8, 5 }, true }, { { 9, 5 }, true },
+ { { 10, 5 }, true }, { { 11, 5 }, true }, { { 12, 5 }, true }, { { 13, 5 }, true },
+ { { 14, 5 }, true }, { { 15, 5 }, true }, { { 2, 6 }, true }, { { 3, 6 }, true },
+ { { 4, 6 }, true }, { { 5, 6 }, true }, { { 6, 6 }, true }, { { 7, 6 }, true },
+ { { 8, 6 }, true }, { { 9, 6 }, true }, { { 10, 6 }, true }, { { 11, 6 }, true },
+ { { 12, 6 }, true }, { { 13, 6 }, true }, { { 14, 6 }, true }, { { 15, 6 }, true },
+ { { 16, 6 }, true }, { { 2, 7 }, true }, { { 3, 7 }, true }, { { 4, 7 }, true },
+ { { 5, 7 }, true }, { { 6, 7 }, true }, { { 7, 7 }, true }, { { 8, 7 }, true },
+ { { 9, 7 }, true }, { { 10, 7 }, true }, { { 11, 7 }, true }, { { 12, 7 }, true },
+ { { 13, 7 }, true }, { { 14, 7 }, true }, { { 15, 7 }, true }, { { 16, 7 }, true },
+ { { 2, 8 }, true }, { { 3, 8 }, true }, { { 4, 8 }, true }, { { 5, 8 }, true },
+ { { 6, 8 }, true }, { { 7, 8 }, true }, { { 8, 8 }, true }, { { 9, 8 }, true },
+ { { 10, 8 }, true }, { { 11, 8 }, true }, { { 12, 8 }, true }, { { 13, 8 }, true },
+ { { 14, 8 }, true }, { { 15, 8 }, true }, { { 16, 8 }, true }, { { 17, 8 }, true },
+ { { 2, 9 }, true }, { { 3, 9 }, true }, { { 4, 9 }, true }, { { 5, 9 }, true },
+ { { 6, 9 }, true }, { { 7, 9 }, true }, { { 8, 9 }, true }, { { 9, 9 }, true },
+ { { 10, 9 }, true }, { { 11, 9 }, true }, { { 12, 9 }, true }, { { 13, 9 }, true },
+ { { 14, 9 }, true }, { { 15, 9 }, true }, { { 16, 9 }, true }, { { 17, 9 }, true },
+ { { 2, 10 }, true }, { { 3, 10 }, true }, { { 4, 10 }, true }, { { 5, 10 }, true },
+ { { 6, 10 }, true }, { { 7, 10 }, true }, { { 8, 10 }, true }, { { 9, 10 }, true },
+ { { 10, 10 }, true }, { { 11, 10 }, true }, { { 12, 10 }, true }, { { 13, 10 }, true },
+ { { 14, 10 }, true }, { { 15, 10 }, true }, { { 16, 10 }, true }, { { 17, 10 }, true },
+ { { 2, 11 }, true }, { { 3, 11 }, true }, { { 4, 11 }, true }, { { 5, 11 }, true },
+ { { 6, 11 }, true }, { { 7, 11 }, true }, { { 8, 11 }, true }, { { 9, 11 }, true },
+ { { 10, 11 }, true }, { { 11, 11 }, true }, { { 12, 11 }, true }, { { 13, 11 }, true },
+ { { 14, 11 }, true }, { { 15, 11 }, true }, { { 16, 11 }, true }, { { 17, 11 }, true },
+ { { 3, 12 }, true }, { { 4, 12 }, true }, { { 5, 12 }, true }, { { 6, 12 }, true },
+ { { 7, 12 }, true }, { { 8, 12 }, true }, { { 9, 12 }, true }, { { 10, 12 }, true },
+ { { 11, 12 }, true }, { { 12, 12 }, true }, { { 13, 12 }, true }, { { 14, 12 }, true },
+ { { 15, 12 }, true }, { { 16, 12 }, true }, { { 3, 13 }, true }, { { 4, 13 }, true },
+ { { 5, 13 }, true }, { { 6, 13 }, true }, { { 7, 13 }, true }, { { 8, 13 }, true },
+ { { 9, 13 }, true }, { { 10, 13 }, true }, { { 11, 13 }, true }, { { 12, 13 }, true },
+ { { 13, 13 }, true }, { { 14, 13 }, true }, { { 15, 13 }, true }, { { 16, 13 }, true },
+ { { 4, 14 }, true }, { { 5, 14 }, true }, { { 6, 14 }, true }, { { 7, 14 }, true },
+ { { 8, 14 }, true }, { { 9, 14 }, true }, { { 10, 14 }, true }, { { 11, 14 }, true },
+ { { 12, 14 }, true }, { { 13, 14 }, true }, { { 14, 14 }, true }, { { 15, 14 }, true },
+ { { 5, 15 }, true }, { { 6, 15 }, true }, { { 7, 15 }, true }, { { 8, 15 }, true },
+ { { 9, 15 }, true }, { { 10, 15 }, true }, { { 11, 15 }, true }, { { 12, 15 }, true },
+ { { 13, 15 }, true }, { { 14, 15 }, true }, { { 15, 15 }, true }, { { 6, 16 }, true },
+ { { 7, 16 }, true }, { { 8, 16 }, true }, { { 9, 16 }, true }, { { 10, 16 }, true },
+ { { 11, 16 }, true }, { { 12, 16 }, true }, { { 13, 16 }, true }, { { 8, 17 }, true },
+ { { 9, 17 }, true }, { { 10, 17 }, true }, { { 11, 17 }, true } };
+
+ for (tools::Long x = 0; x < pAccess->Width(); x++)
+ {
+ for (tools::Long y = 0; y < pAccess->Height(); ++y)
+ {
+ if (SetPixels[{ x, y }])
+ {
+ checkValue(pAccess, y, x, constFillColor, nNumberOfQuirks, nNumberOfErrors, true);
+ }
+ else
+ {
+ checkValue(pAccess, y, x, constBackgroundColor, nNumberOfQuirks, nNumberOfErrors, true);
+ }
+ }
+ }
+
+ if (nNumberOfQuirks > 0)
+ aResult = TestResult::PassedWithQuirks;
+ if (nNumberOfErrors > 0)
+ aResult = TestResult::Failed;
+ return aResult;
+}
+
+TestResult OutputDeviceTestCommon::checkTextLocation(Bitmap& rBitmap)
+{
+ BitmapScopedWriteAccess pAccess(rBitmap);
+
+ TestResult aResult = TestResult::Passed;
+
+ //The limit to which error would be tolerated.
+ tools::Long textThreshold = 3;
+ tools::Long textWidth = 3, textHeight = 8;
+ tools::Long deviationX = 0, deviationY = 0;
+ tools::Long verticalStart = 0, verticalEnd = 0;
+ tools::Long horizontalStart = 0, horizontalEnd = 0;
+ tools::Long midX = pAccess->Width() / 2.0;
+ tools::Long midY = pAccess->Height() / 2.0;
+ bool insideFlag = false;
+
+ //Traversing horizontally
+ for (tools::Long x = 0, y = pAccess->Height() / 2.0; x < pAccess->Width(); ++x)
+ {
+ if (pAccess->GetPixel(y, x) != constBackgroundColor)
+ {
+ if (!insideFlag)
+ {
+ horizontalStart = x;
+ insideFlag = true;
+ }
+ else
+ {
+ horizontalEnd = x;
+ }
+ }
+ }
+
+ deviationX = abs(midX - horizontalStart);
+ midY -= midY / 2.0;
+ midY += 1;
+
+ insideFlag = false;
+ //Traversing vertically
+ for (tools::Long x = 0, y = pAccess->Height() / 2.0; x < pAccess->Height(); ++x)
+ {
+ if (pAccess->GetPixel(x, y) != constBackgroundColor)
+ {
+ if (!insideFlag)
+ {
+ verticalStart = x;
+ insideFlag = true;
+ }
+ else
+ {
+ verticalEnd = x;
+ }
+ }
+ }
+
+ deviationY = abs(midY - verticalStart);
+
+ if (deviationX != 0 || deviationY != 0 || abs(horizontalStart - horizontalEnd) + 1 != textWidth
+ || abs(verticalStart - verticalEnd) + 1 != textHeight)
+ {
+ aResult = TestResult::PassedWithQuirks;
+ }
+
+ if (deviationX > textThreshold || deviationY > textThreshold
+ || abs((abs(horizontalStart - horizontalEnd) + 1) - textWidth) > textThreshold
+ || abs((abs(verticalStart - verticalEnd) + 1) - textHeight) > textThreshold)
+ {
+ aResult = TestResult::Failed;
+ }
+
+ return aResult;
+}
+
+TestResult OutputDeviceTestCommon::checkIntersectingRecs(Bitmap& rBitmap, int aLayerNumber,
+ Color aExpected)
+{
+ BitmapScopedWriteAccess pAccess(rBitmap);
+
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ for (int x = 4; x <= 19; ++x)
+ {
+ checkValue(pAccess, x, aLayerNumber, aExpected, nNumberOfQuirks, nNumberOfErrors, true);
+ }
+
+ if (nNumberOfQuirks > 0)
+ aResult = TestResult::PassedWithQuirks;
+ if (nNumberOfErrors > 0)
+ aResult = TestResult::Failed;
+ return aResult;
+}
+
+TestResult OutputDeviceTestCommon::checkEvenOddRuleInIntersectingRecs(Bitmap& rBitmap)
+{
+ /*
+ The even-odd rule would be tested via the below pattern as layers both of the
+ constFillColor & constBackgroundColor appears in an even-odd fashion.
+ */
+ std::vector<Color> aExpectedColors
+ = { constBackgroundColor, constBackgroundColor, constLineColor, constFillColor,
+ constFillColor, constLineColor, constBackgroundColor, constBackgroundColor,
+ constLineColor, constFillColor, constFillColor, constLineColor,
+ constBackgroundColor, constBackgroundColor, constLineColor, constFillColor,
+ constFillColor, constLineColor, constBackgroundColor, constBackgroundColor,
+ constLineColor, constFillColor, constLineColor };
+
+ TestResult aReturnValue = TestResult::Passed;
+ for (size_t i = 0; i < aExpectedColors.size(); i++)
+ {
+ TestResult eResult = checkIntersectingRecs(rBitmap, i, aExpectedColors[i]);
+
+ if (eResult == TestResult::Failed)
+ aReturnValue = TestResult::Failed;
+ if (eResult == TestResult::PassedWithQuirks && aReturnValue != TestResult::Failed)
+ aReturnValue = TestResult::PassedWithQuirks;
+ }
+ return aReturnValue;
+}
+
+TestResult OutputDeviceTestCommon::checkOpenPolygon(Bitmap& rBitmap, bool aEnableAA)
+{
+ std::vector<Color> aExpected = { constBackgroundColor, constLineColor, constLineColor };
+
+ BitmapScopedWriteAccess pAccess(rBitmap);
+
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ for (size_t aLayerNumber = 0; aLayerNumber < aExpected.size(); aLayerNumber++)
+ {
+ tools::Long startX = aLayerNumber + 1, endX = pAccess->Width() / 2 - aLayerNumber;
+ tools::Long startY = aLayerNumber + 2, endY = pAccess->Height() - aLayerNumber - 3;
+
+ for (tools::Long ptX = startX; ptX <= endX; ++ptX)
+ {
+ if (aEnableAA)
+ {
+ checkValueAA(pAccess, ptX, endY - (aLayerNumber == 2 ? 2 : 0),
+ aExpected[aLayerNumber], nNumberOfQuirks, nNumberOfErrors);
+ }
+ else
+ {
+ checkValue(pAccess, ptX, endY - (aLayerNumber == 2 ? 2 : 0),
+ aExpected[aLayerNumber], nNumberOfQuirks, nNumberOfErrors, true);
+ }
+ }
+ for (tools::Long ptY = startY + (aLayerNumber == 2 ? 2 : 0);
+ ptY <= endY - (aLayerNumber == 2 ? 2 : 0); ++ptY)
+ {
+ if (aEnableAA)
+ {
+ checkValueAA(pAccess, startX, ptY, aExpected[aLayerNumber], nNumberOfQuirks,
+ nNumberOfErrors);
+ checkValueAA(pAccess, endX, ptY, aExpected[aLayerNumber], nNumberOfQuirks,
+ nNumberOfErrors);
+ }
+ else
+ {
+ checkValue(pAccess, startX, ptY, aExpected[aLayerNumber], nNumberOfQuirks,
+ nNumberOfErrors, true);
+ checkValue(pAccess, endX, ptY, aExpected[aLayerNumber], nNumberOfQuirks,
+ nNumberOfErrors, true);
+ }
+ }
+ }
+
+ if (nNumberOfQuirks > 0)
+ aResult = TestResult::PassedWithQuirks;
+ if (nNumberOfErrors > 0)
+ aResult = TestResult::Failed;
+ return aResult;
+}
+
+// Check 'count' pixels from (x,y) in (addX,addY) direction, the color values must not decrease.
+static bool checkGradient(BitmapScopedWriteAccess& pAccess, int x, int y, int count, int addX, int addY)
+{
+ const bool bColorize = false;
+ Color maxColor = COL_BLACK;
+ for( int i = 0; i < count; ++i )
+ {
+ Color color = pAccess->GetPixel(y, x);
+ if( color.GetRed() < maxColor.GetRed() || color.GetGreen() < maxColor.GetGreen() || color.GetBlue() < maxColor.GetBlue())
+ {
+ if (bColorize)
+ pAccess->SetPixel(y, x, COL_RED);
+ return false;
+ }
+ maxColor = color;
+ if (bColorize)
+ pAccess->SetPixel(y, x, COL_LIGHTGREEN);
+ x += addX;
+ y += addY;
+ }
+ return true;
+}
+
+TestResult OutputDeviceTestCommon::checkLinearGradient(Bitmap& bitmap)
+{
+ BitmapScopedWriteAccess pAccess(bitmap);
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ // The lowest line is missing in the default VCL implementation => quirk.
+ checkValue(pAccess, 1, 10, COL_WHITE, nNumberOfQuirks, nNumberOfErrors, true, 255 / 10);
+ checkValue(pAccess, 10, 10, COL_BLACK, nNumberOfQuirks, nNumberOfErrors, true, 255 / 10);
+ for(int y = 1; y < 10; ++y)
+ {
+ checkValue(pAccess, 1, y, COL_WHITE, nNumberOfQuirks, nNumberOfErrors, 255 / 10);
+ checkValue(pAccess, 10, y, COL_BLACK, nNumberOfQuirks, nNumberOfErrors, 255 / 10);
+ }
+ for(int y = 1; y < 10; ++y)
+ if( !checkGradient( pAccess, 10, y, 10, -1, 0 ))
+ return TestResult::Failed;
+ if (nNumberOfQuirks > 0)
+ checkResult(TestResult::PassedWithQuirks, aResult);
+ if (nNumberOfErrors > 0)
+ checkResult(TestResult::Failed, aResult);
+ return aResult;
+}
+
+TestResult OutputDeviceTestCommon::checkLinearGradientAngled(Bitmap& bitmap)
+{
+ BitmapScopedWriteAccess pAccess(bitmap);
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ // The top-left pixel is not white but gray in the default VCL implementation => quirk.
+ checkValue(pAccess, 1, 1, COL_WHITE, nNumberOfQuirks, nNumberOfErrors, 50);
+ checkValue(pAccess, 10, 10, COL_BLACK, nNumberOfQuirks, nNumberOfErrors, 0, 255 / 10); // Bottom-right.
+ // Main diagonal.
+ if( !checkGradient( pAccess, 10, 10, 10, -1, -1 ))
+ return TestResult::Failed;
+ if (nNumberOfQuirks > 0)
+ checkResult(TestResult::PassedWithQuirks, aResult);
+ if (nNumberOfErrors > 0)
+ checkResult(TestResult::Failed, aResult);
+ return TestResult::Passed;
+}
+
+TestResult OutputDeviceTestCommon::checkLinearGradientBorder(Bitmap& bitmap)
+{
+ TestResult aResult = TestResult::Passed;
+ // Top half is border.
+ checkResult(checkFilled(bitmap, tools::Rectangle(Point(1, 1), Size(10, 5)), COL_WHITE), aResult);
+ BitmapScopedWriteAccess pAccess(bitmap);
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+ for(int x = 1; x <= 10; ++x)
+ {
+ checkValue(pAccess, x, 10, COL_BLACK, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 5);
+ if( !checkGradient( pAccess, x, 10, 5, 0, -1 ))
+ return TestResult::Failed;
+ }
+ if (nNumberOfQuirks > 0)
+ checkResult(TestResult::PassedWithQuirks, aResult);
+ if (nNumberOfErrors > 0)
+ checkResult(TestResult::Failed, aResult);
+ return aResult;
+}
+
+TestResult OutputDeviceTestCommon::checkLinearGradientIntensity(Bitmap& bitmap)
+{
+ BitmapScopedWriteAccess pAccess(bitmap);
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ for(int x = 1; x <= 10; ++x)
+ {
+ // The gradient starts at half intensity, i.e. white's 255's are halved.
+ checkValue(pAccess, x, 1, Color(128,128,128), nNumberOfQuirks, nNumberOfErrors, false, 10);
+ checkValue(pAccess, x, 10, COL_BLACK, nNumberOfQuirks, nNumberOfErrors, 255 / 10);
+ if( !checkGradient( pAccess, x, 10, 10, 0, -1 ))
+ return TestResult::Failed;
+ }
+ if (nNumberOfQuirks > 0)
+ checkResult(TestResult::PassedWithQuirks, aResult);
+ if (nNumberOfErrors > 0)
+ checkResult(TestResult::Failed, aResult);
+ return aResult;
+}
+
+TestResult OutputDeviceTestCommon::checkLinearGradientSteps(Bitmap& bitmap)
+{
+ // Reuse the basic linear gradient check.
+ TestResult aResult = checkLinearGradient(bitmap);
+ // Only 4 steps in the gradient, there should be only 4 colors.
+ if( collectColors( bitmap, tools::Rectangle( Point( 1, 1 ), Size( 10, 10 ))).size() != 4 )
+ return TestResult::Failed;
+ return aResult;
+}
+
+TestResult OutputDeviceTestCommon::checkAxialGradient(Bitmap& bitmap)
+{
+ BitmapScopedWriteAccess pAccess(bitmap);
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ for(int y = 1; y <= 11; ++y)
+ {
+ // Middle horizontal line is white, gradients to the sides.
+ checkValue(pAccess, 6, y, COL_WHITE, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 5);
+ checkValue(pAccess, 1, y, COL_BLACK, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 5);
+ checkValue(pAccess, 11, y, COL_BLACK, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 5);
+ if( !checkGradient( pAccess, 1, y, 6, 1, 0 ))
+ return TestResult::Failed;
+ if( !checkGradient( pAccess, 11, y, 6, -1, 0 ))
+ return TestResult::Failed;
+ }
+ if (nNumberOfQuirks > 0)
+ checkResult(TestResult::PassedWithQuirks, aResult);
+ if (nNumberOfErrors > 0)
+ checkResult(TestResult::Failed, aResult);
+ return aResult;
+}
+
+TestResult OutputDeviceTestCommon::checkRadialGradient(Bitmap& bitmap)
+{
+ BitmapScopedWriteAccess pAccess(bitmap);
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+ // The default VCL implementation is off-center in the direction to the top-left.
+ // This means not all corners will be pure white => quirks.
+ checkValue(pAccess, 1, 1, COL_WHITE, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 2);
+ checkValue(pAccess, 1, 10, COL_WHITE, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 5);
+ checkValue(pAccess, 10, 1, COL_WHITE, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 5);
+ checkValue(pAccess, 10, 10, COL_WHITE, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 5);
+ // And not all centers will be pure black => quirks.
+ checkValue(pAccess, 5, 5, COL_BLACK, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 5);
+ checkValue(pAccess, 5, 6, COL_BLACK, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 3);
+ checkValue(pAccess, 6, 5, COL_BLACK, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 3);
+ checkValue(pAccess, 6, 6, COL_BLACK, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 2);
+ // Check diagonals, from the offset center.
+ if(!checkGradient(pAccess, 5, 5, 5, -1, -1))
+ return TestResult::Failed;
+ if(!checkGradient(pAccess, 5, 5, 6, 1, 1))
+ return TestResult::Failed;
+ if(!checkGradient(pAccess, 5, 5, 5, 1, -1))
+ return TestResult::Failed;
+ if(!checkGradient(pAccess, 5, 5, 5, -1, 1))
+ return TestResult::Failed;
+ if (nNumberOfQuirks > 0)
+ checkResult(TestResult::PassedWithQuirks, aResult);
+ if (nNumberOfErrors > 0)
+ checkResult(TestResult::Failed, aResult);
+ return aResult;
+}
+
+TestResult OutputDeviceTestCommon::checkRadialGradientOfs(Bitmap& bitmap)
+{
+ BitmapScopedWriteAccess pAccess(bitmap);
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+ checkValue(pAccess, 1, 1, COL_WHITE, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 5);
+ checkValue(pAccess, 10, 1, COL_WHITE, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 5);
+ checkValue(pAccess, 1, 10, COL_WHITE, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 5);
+ checkValue(pAccess, 10, 10, COL_BLACK, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 5);
+ // Check gradients from the center (=bottom-right corner).
+ if(!checkGradient(pAccess, 10, 10, 10, -1, -1))
+ return TestResult::Failed;
+ if(!checkGradient(pAccess, 10, 10, 10, -1, 0))
+ return TestResult::Failed;
+ if(!checkGradient(pAccess, 10, 10, 10, 0, -1))
+ return TestResult::Failed;
+ if (nNumberOfQuirks > 0)
+ checkResult(TestResult::PassedWithQuirks, aResult);
+ if (nNumberOfErrors > 0)
+ checkResult(TestResult::Failed, aResult);
+ return aResult;
+}
+
+constexpr int CAPSHRINK = 25;
+constexpr int CAPWIDTH = 20;
+TestResult OutputDeviceTestCommon::checkLineCap(Bitmap& rBitmap, css::drawing::LineCap lineCap)
+{
+ BitmapScopedWriteAccess access(rBitmap);
+ tools::Rectangle rectangle( Point( 0, 0 ), Size( 101, 101 ));
+ rectangle.shrink(CAPSHRINK);
+ rectangle = tools::Rectangle( Point(rectangle.LeftCenter().getX(), rectangle.LeftCenter().getY() - CAPWIDTH / 2),
+ Point(rectangle.RightCenter().getX(), rectangle.RightCenter().getY() + CAPWIDTH / 2));
+ rectangle.shrink(1);
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ // the line itself
+ checkValue(access, rectangle.TopLeft(), constLineColor, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(access, rectangle.TopRight(), constLineColor, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(access, rectangle.BottomLeft(), constLineColor, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(access, rectangle.BottomRight(), constLineColor, nNumberOfQuirks, nNumberOfErrors, false);
+
+ // the cap in the middle
+ Color color = ( lineCap == css::drawing::LineCap_BUTT ) ? constBackgroundColor : constLineColor;
+ checkValue(access, rectangle.LeftCenter() - Point(CAPWIDTH/2, 0), color, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(access, rectangle.RightCenter() + Point(CAPWIDTH/2, 0), color, nNumberOfQuirks, nNumberOfErrors, false);
+
+ // the cap corners
+ color = ( lineCap == css::drawing::LineCap_SQUARE ) ? constLineColor : constBackgroundColor;
+ checkValue(access, rectangle.TopLeft() - Point(CAPWIDTH/2, 0), color, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(access, rectangle.TopRight() + Point(CAPWIDTH/2, 0), color, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(access, rectangle.BottomLeft() - Point(CAPWIDTH/2, 0), color, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(access, rectangle.BottomRight() + Point(CAPWIDTH/2, 0), color, nNumberOfQuirks, nNumberOfErrors, false);
+
+ if (nNumberOfQuirks > 0)
+ checkResult(TestResult::PassedWithQuirks, aResult);
+ if (nNumberOfErrors > 0)
+ checkResult(TestResult::Failed, aResult);
+ return aResult;
+}
+
+TestResult OutputDeviceTestCommon::checkLineJoin(Bitmap& rBitmap, basegfx::B2DLineJoin lineJoin)
+{
+ BitmapScopedWriteAccess access(rBitmap);
+ tools::Rectangle rectangle( Point( 0, 0 ), Size( 101, 101 ));
+ rectangle.shrink(CAPSHRINK);
+ tools::Rectangle rectangle1( Point(rectangle.TopLeft().getX(), rectangle.TopLeft().getY() - CAPWIDTH / 2),
+ Point(rectangle.TopRight().getX(), rectangle.TopRight().getY() + CAPWIDTH / 2));
+ tools::Rectangle rectangle2( Point(rectangle.TopRight().getX() - CAPWIDTH / 2, rectangle.TopRight().getY()),
+ Point(rectangle.BottomRight().getX() + CAPWIDTH / 2, rectangle.BottomRight().getY()));
+ rectangle1.shrink(1);
+ rectangle2.shrink(1);
+ TestResult aResult = TestResult::Passed;
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+
+ // the lines themselves
+ checkValue(access, rectangle1.TopLeft(), constLineColor, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(access, rectangle1.TopRight(), constLineColor, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(access, rectangle1.BottomLeft(), constLineColor, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(access, rectangle1.BottomRight(), constLineColor, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(access, rectangle2.TopLeft(), constLineColor, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(access, rectangle2.TopRight(), constLineColor, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(access, rectangle2.BottomLeft(), constLineColor, nNumberOfQuirks, nNumberOfErrors, false);
+ checkValue(access, rectangle2.BottomRight(), constLineColor, nNumberOfQuirks, nNumberOfErrors, false);
+
+ // Only miter has the corner point.
+ Color color = ( lineJoin == basegfx::B2DLineJoin::Miter ) ? constLineColor : constBackgroundColor;
+ checkValue(access, rectangle2.Right(), rectangle1.Top(), color, nNumberOfQuirks, nNumberOfErrors, false);
+
+ // Round reaches a bit past the diagonal.
+ Point midDiagonal = (Point( rectangle2.Right(), rectangle1.Top()) + rectangle.TopRight()) / 2;
+ if( lineJoin == basegfx::B2DLineJoin::Round)
+ color = constLineColor;
+ checkValue(access, midDiagonal + Point( 2, -2 ), color, nNumberOfQuirks, nNumberOfErrors, false);
+ // Bevel is the diagonal.
+ if( lineJoin == basegfx::B2DLineJoin::Bevel)
+ color = constLineColor;
+ checkValue(access, midDiagonal + Point( -1, 1 ), color, nNumberOfQuirks, nNumberOfErrors, false);
+ // Everything except None has at least some line join.
+ checkValue(access, rectangle.TopRight() + Point( 1, -1 ), color, nNumberOfQuirks, nNumberOfErrors, false);
+
+ if (nNumberOfQuirks > 0)
+ checkResult(TestResult::PassedWithQuirks, aResult);
+ if (nNumberOfErrors > 0)
+ checkResult(TestResult::Failed, aResult);
+ return aResult;
+}
+
+TestResult OutputDeviceTestAnotherOutDev::checkDrawOutDev(Bitmap& rBitmap)
+{
+ std::vector<Color> aExpected
+ {
+ constBackgroundColor, constBackgroundColor,
+ constFillColor, constFillColor, constFillColor, constFillColor, constFillColor
+ };
+ return checkRectangles(rBitmap, aExpected);
+}
+
+TestResult OutputDeviceTestAnotherOutDev::checkDrawOutDevScaledClipped(Bitmap& rBitmap)
+{
+ TestResult aReturnValue = TestResult::Passed;
+ TestResult eResult;
+
+ eResult = checkRect(rBitmap, 0, constBackgroundColor); // outer line
+ checkResult(eResult, aReturnValue);
+ eResult = checkRect(rBitmap, 1, constBackgroundColor); // next outer line
+ checkResult(eResult, aReturnValue);
+ eResult = checkFilled(rBitmap, tools::Rectangle(Point(2, 2), Size(4, 8)), constBackgroundColor);
+ checkResult(eResult, aReturnValue);
+ eResult = checkFilled(rBitmap, tools::Rectangle(Point(6, 2), Size(4, 8)), constFillColor);
+ checkResult(eResult, aReturnValue);
+
+ return aReturnValue;
+}
+
+TestResult OutputDeviceTestAnotherOutDev::checkDrawOutDevSelf(Bitmap& rBitmap)
+{
+ TestResult aReturnValue = TestResult::Passed;
+ TestResult eResult;
+
+ eResult = checkRect(rBitmap, 0, constBackgroundColor); // outer line
+ checkResult(eResult, aReturnValue);
+ eResult = checkFilled(rBitmap, tools::Rectangle(Point(1, 1), Size(4, 4)), constBackgroundColor);
+ checkResult(eResult, aReturnValue);
+ eResult = checkFilled(rBitmap, tools::Rectangle(Point(8, 8), Size(4, 4)), constBackgroundColor);
+ checkResult(eResult, aReturnValue);
+
+ eResult = checkFilled(rBitmap, tools::Rectangle(Point(11, 1), Size(1, 1)), COL_YELLOW);
+ checkResult(eResult, aReturnValue);
+ eResult = checkFilled(rBitmap, tools::Rectangle(Point(7, 5), Size(1, 1)), COL_YELLOW);
+ checkResult(eResult, aReturnValue);
+ eResult = checkFilled(rBitmap, tools::Rectangle(Point(1, 11), Size(1, 1)), COL_YELLOW);
+ checkResult(eResult, aReturnValue);
+
+ eResult = checkFilled(rBitmap, tools::Rectangle(Point(1, 5), Size(6, 6)), constFillColor);
+ checkResult(eResult, aReturnValue);
+ eResult = checkFilled(rBitmap, tools::Rectangle(Point(2, 6), Size(6, 6)), constFillColor);
+ checkResult(eResult, aReturnValue);
+ eResult = checkFilled(rBitmap, tools::Rectangle(Point(5, 1), Size(6, 4)), constFillColor);
+ checkResult(eResult, aReturnValue);
+ eResult = checkFilled(rBitmap, tools::Rectangle(Point(8, 2), Size(4, 6)), constFillColor);
+ checkResult(eResult, aReturnValue);
+
+ return aReturnValue;
+}
+
+TestResult OutputDeviceTestAnotherOutDev::checkXOR(Bitmap& rBitmap)
+{
+ Color xorColor( constBackgroundColor.GetRed() ^ constFillColor.GetRed(),
+ constBackgroundColor.GetGreen() ^ constFillColor.GetGreen(),
+ constBackgroundColor.GetBlue() ^ constFillColor.GetBlue());
+ std::vector<Color> aExpected
+ {
+ constBackgroundColor, xorColor,
+ constBackgroundColor, constBackgroundColor,
+ constFillColor, constFillColor,
+ constFillColor
+ };
+ return checkRectangles(rBitmap, aExpected);
+}
+
+
+TestResult OutputDeviceTestBitmap::checkTransformedBitmap(Bitmap& rBitmap)
+{
+ std::vector<Color> aExpected
+ {
+ constBackgroundColor, constBackgroundColor,
+ COL_YELLOW, constFillColor, COL_YELLOW, constFillColor, constFillColor
+ };
+ return checkRectangles(rBitmap, aExpected);
+}
+
+TestResult OutputDeviceTestBitmap::checkComplexTransformedBitmap(Bitmap& rBitmap)
+{
+ TestResult aReturnValue = TestResult::Passed;
+ TestResult eResult;
+ eResult = checkRectangle(rBitmap, 0, constBackgroundColor); // outer line not affected
+ checkResult(eResult, aReturnValue);
+ // empty "corners" should not be affected
+ eResult = checkFilled(rBitmap, tools::Rectangle(Point(1, 11), Size(2, 2)), constBackgroundColor);
+ checkResult(eResult, aReturnValue);
+ eResult = checkFilled(rBitmap, tools::Rectangle(Point(14, 1), Size(2, 2)), constBackgroundColor);
+ // check the middle
+ eResult = checkFilled(rBitmap, tools::Rectangle(Point(4, 3), Size(9, 8)), constFillColor);
+ checkResult(eResult, aReturnValue);
+ checkResult(eResult, aReturnValue);
+ int nNumberOfQuirks = 0;
+ int nNumberOfErrors = 0;
+ BitmapScopedWriteAccess pAccess(rBitmap);
+ // starting and ending corner, headless draws with AA, so be lenient
+ checkValue(pAccess, 1, 1, constFillColor, nNumberOfQuirks, nNumberOfErrors, 0, 192);
+ checkValue(pAccess, 2, 2, constFillColor, nNumberOfQuirks, nNumberOfErrors, 0, 16);
+ checkValue(pAccess, 14, 11, constFillColor, nNumberOfQuirks, nNumberOfErrors, 0, 16);
+ checkValue(pAccess, 15, 12, constFillColor, nNumberOfQuirks, nNumberOfErrors, 0, 192);
+ if (nNumberOfQuirks > 0)
+ checkResult(TestResult::PassedWithQuirks, aReturnValue);
+ if (nNumberOfErrors > 0)
+ checkResult(TestResult::Failed, aReturnValue);
+ return aReturnValue;
+}
+
+TestResult OutputDeviceTestBitmap::checkTransformedBitmap8bppGreyScale(Bitmap& rBitmap)
+{
+ std::vector<Color> aExpected
+ {
+ Color(0xC0,0xC0,0xC0), Color(0xC0,0xC0,0xC0),
+ Color(0xE2,0xE2,0xE2), Color(0xE,0xE,0xE), Color(0xE2,0xE2,0xE2), Color(0xE,0xE,0xE), Color(0xE,0xE,0xE)
+ };
+ return checkRectangles(rBitmap, aExpected);
+}
+
+TestResult OutputDeviceTestBitmap::checkBitmapExWithAlpha(Bitmap& rBitmap)
+{
+ const Color aBlendedColor(0xEE, 0xEE, 0x33);
+
+ std::vector<Color> aExpected
+ {
+ constBackgroundColor, constBackgroundColor,
+ aBlendedColor, constBackgroundColor, constBackgroundColor,
+ aBlendedColor, constBackgroundColor
+ };
+ return checkRectangles(rBitmap, aExpected);
+}
+
+TestResult OutputDeviceTestBitmap::checkMask(Bitmap& rBitmap)
+{
+ return checkRectangle(rBitmap);
+}
+
+TestResult OutputDeviceTestBitmap::checkBlend(const BitmapEx& rBitmapEx)
+{
+ const Color aBlendedColor(0xEE, 0xEE, 0x33);
+
+ std::vector<Color> aExpected
+ {
+ COL_WHITE, COL_WHITE, COL_YELLOW, constBackgroundColor,
+ constBackgroundColor, aBlendedColor, constBackgroundColor
+ };
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+ return checkRectangles(aBitmap, aExpected);
+}
+
+
+} // end namespace vcl::test
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/backendtest/outputdevice/gradient.cxx b/vcl/backendtest/outputdevice/gradient.cxx
new file mode 100644
index 0000000000..2596670b68
--- /dev/null
+++ b/vcl/backendtest/outputdevice/gradient.cxx
@@ -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/.
+ *
+ */
+
+#include <test/outputdevice.hxx>
+#include <vcl/gradient.hxx>
+
+namespace vcl::test
+{
+Bitmap OutputDeviceTestGradient::setupLinearGradient()
+{
+ initialSetup(12, 12, constBackgroundColor);
+
+ Gradient aGradient(css::awt::GradientStyle_LINEAR, Color(0xFF, 0xFF, 0xFF),
+ Color(0x00, 0x00, 0x00));
+ aGradient.SetAngle(900_deg10);
+ tools::Rectangle aDrawRect(maVDRectangle.Left() + 1, maVDRectangle.Top() + 1,
+ maVDRectangle.Right() - 1, maVDRectangle.Bottom() - 1);
+ mpVirtualDevice->DrawGradient(aDrawRect, aGradient);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestGradient::setupLinearGradientAngled()
+{
+ initialSetup(12, 12, constBackgroundColor);
+
+ Gradient aGradient(css::awt::GradientStyle_LINEAR, Color(0xFF, 0xFF, 0xFF),
+ Color(0x00, 0x00, 0x00));
+ aGradient.SetAngle(450_deg10);
+ tools::Rectangle aDrawRect(maVDRectangle.Left() + 1, maVDRectangle.Top() + 1,
+ maVDRectangle.Right() - 1, maVDRectangle.Bottom() - 1);
+ mpVirtualDevice->DrawGradient(aDrawRect, aGradient);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestGradient::setupLinearGradientBorder()
+{
+ initialSetup(12, 12, constBackgroundColor);
+
+ Gradient aGradient(css::awt::GradientStyle_LINEAR, Color(0xFF, 0xFF, 0xFF),
+ Color(0x00, 0x00, 0x00));
+ aGradient.SetBorder(50);
+ tools::Rectangle aDrawRect(maVDRectangle.Left() + 1, maVDRectangle.Top() + 1,
+ maVDRectangle.Right() - 1, maVDRectangle.Bottom() - 1);
+ mpVirtualDevice->DrawGradient(aDrawRect, aGradient);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestGradient::setupLinearGradientIntensity()
+{
+ initialSetup(12, 12, constBackgroundColor);
+
+ Gradient aGradient(css::awt::GradientStyle_LINEAR, Color(0xFF, 0xFF, 0xFF),
+ Color(0x00, 0x00, 0x00));
+ aGradient.SetStartIntensity(50);
+ tools::Rectangle aDrawRect(maVDRectangle.Left() + 1, maVDRectangle.Top() + 1,
+ maVDRectangle.Right() - 1, maVDRectangle.Bottom() - 1);
+ mpVirtualDevice->DrawGradient(aDrawRect, aGradient);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestGradient::setupLinearGradientSteps()
+{
+ initialSetup(12, 12, constBackgroundColor);
+
+ Gradient aGradient(css::awt::GradientStyle_LINEAR, Color(0xFF, 0xFF, 0xFF),
+ Color(0x00, 0x00, 0x00));
+ aGradient.SetAngle(900_deg10);
+ aGradient.SetSteps(4);
+ tools::Rectangle aDrawRect(maVDRectangle.Left() + 1, maVDRectangle.Top() + 1,
+ maVDRectangle.Right() - 1, maVDRectangle.Bottom() - 1);
+ mpVirtualDevice->DrawGradient(aDrawRect, aGradient);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestGradient::setupAxialGradient()
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ Gradient aGradient(css::awt::GradientStyle_AXIAL, Color(0xFF, 0xFF, 0xFF),
+ Color(0x00, 0x00, 0x00));
+ aGradient.SetAngle(900_deg10);
+ tools::Rectangle aDrawRect(maVDRectangle.Left() + 1, maVDRectangle.Top() + 1,
+ maVDRectangle.Right() - 1, maVDRectangle.Bottom() - 1);
+ mpVirtualDevice->DrawGradient(aDrawRect, aGradient);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestGradient::setupRadialGradient()
+{
+ initialSetup(12, 12, constBackgroundColor);
+
+ Gradient aGradient(css::awt::GradientStyle_RADIAL, Color(0xFF, 0xFF, 0xFF),
+ Color(0x00, 0x00, 0x00));
+ tools::Rectangle aDrawRect(maVDRectangle.Left() + 1, maVDRectangle.Top() + 1,
+ maVDRectangle.Right() - 1, maVDRectangle.Bottom() - 1);
+ mpVirtualDevice->DrawGradient(aDrawRect, aGradient);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestGradient::setupRadialGradientOfs()
+{
+ initialSetup(12, 12, constBackgroundColor);
+
+ Gradient aGradient(css::awt::GradientStyle_RADIAL, Color(0xFF, 0xFF, 0xFF),
+ Color(0x00, 0x00, 0x00));
+ aGradient.SetOfsX(100); // Move center to the bottom-right corner.
+ aGradient.SetOfsY(100);
+ tools::Rectangle aDrawRect(maVDRectangle.Left() + 1, maVDRectangle.Top() + 1,
+ maVDRectangle.Right() - 1, maVDRectangle.Bottom() - 1);
+ mpVirtualDevice->DrawGradient(aDrawRect, aGradient);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+} // end namespace vcl::test
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/backendtest/outputdevice/line.cxx b/vcl/backendtest/outputdevice/line.cxx
new file mode 100644
index 0000000000..5cb6db10a9
--- /dev/null
+++ b/vcl/backendtest/outputdevice/line.cxx
@@ -0,0 +1,278 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/outputdevice.hxx>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+
+#include <list>
+
+namespace vcl::test {
+
+namespace
+{
+
+void drawLineOffset(OutputDevice& rDevice, tools::Rectangle const & rRect, int nOffset)
+{
+
+ int nMidOffset = rRect.GetWidth()/2;
+ Point aLeftTop (rRect.Left() + nOffset - (nOffset+1)/2, rRect.Top() + nOffset - 1);
+ Point aRightTop (rRect.Right() - nMidOffset - nOffset/3, rRect.Top() + nOffset - 1);
+ Point aLeftBottom (rRect.Left() + nOffset - (nOffset+1)/2, rRect.Bottom() - nOffset + 1);
+ Point aRightBottom (rRect.Right() - nMidOffset - nOffset/3, rRect.Bottom() - nOffset + 1);
+
+ rDevice.DrawLine(aLeftTop, aRightTop);
+ rDevice.DrawLine(aRightTop, aRightBottom);
+ rDevice.DrawLine(aRightBottom, aLeftBottom);
+ rDevice.DrawLine(aLeftBottom, aLeftTop);
+}
+
+} // end anonymous namespace
+
+Bitmap OutputDeviceTestLine::setupRectangle(bool bEnableAA)
+{
+ initialSetup(13, 13, constBackgroundColor, bEnableAA);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawLineOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawLineOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestLine::setupRectangleOnSize1028()
+{
+ initialSetup(1028, 1028, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawLineOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawLineOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestLine::setupRectangleOnSize4096()
+{
+ initialSetup(4096, 4096, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawLineOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawLineOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestLine::setupDiamond()
+{
+ initialSetup(11, 11, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ Point aPoint1, aPoint2, aPoint3, aPoint4;
+ OutputDeviceTestCommon::createDiamondPoints(maVDRectangle, 4, aPoint1, aPoint2, aPoint3, aPoint4);
+
+ mpVirtualDevice->DrawLine(aPoint1, aPoint2);
+ mpVirtualDevice->DrawLine(aPoint2, aPoint3);
+ mpVirtualDevice->DrawLine(aPoint3, aPoint4);
+ mpVirtualDevice->DrawLine(aPoint4, aPoint1);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestLine::setupLines()
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ Point aHorizontalLinePoint1, aHorizontalLinePoint2;
+ Point aVerticalLinePoint1, aVerticalLinePoint2;
+ Point aDiagonalLinePoint1, aDiagonalLinePoint2;
+
+ OutputDeviceTestCommon::createHorizontalVerticalDiagonalLinePoints(
+ maVDRectangle, aHorizontalLinePoint1, aHorizontalLinePoint2,
+ aVerticalLinePoint1, aVerticalLinePoint2,
+ aDiagonalLinePoint1, aDiagonalLinePoint2);
+
+ mpVirtualDevice->DrawLine(aHorizontalLinePoint1, aHorizontalLinePoint2);
+ mpVirtualDevice->DrawLine(aVerticalLinePoint1, aVerticalLinePoint2);
+ mpVirtualDevice->DrawLine(aDiagonalLinePoint1, aDiagonalLinePoint2);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestLine::setupAALines()
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ mpVirtualDevice->SetAntialiasing(AntialiasingFlags::Enable);
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ Point aHorizontalLinePoint1, aHorizontalLinePoint2;
+ Point aVerticalLinePoint1, aVerticalLinePoint2;
+ Point aDiagonalLinePoint1, aDiagonalLinePoint2;
+
+ OutputDeviceTestCommon::createHorizontalVerticalDiagonalLinePoints(
+ maVDRectangle, aHorizontalLinePoint1, aHorizontalLinePoint2,
+ aVerticalLinePoint1, aVerticalLinePoint2,
+ aDiagonalLinePoint1, aDiagonalLinePoint2);
+
+ mpVirtualDevice->DrawLine(aHorizontalLinePoint1, aHorizontalLinePoint2);
+ mpVirtualDevice->DrawLine(aVerticalLinePoint1, aVerticalLinePoint2);
+ mpVirtualDevice->DrawLine(aDiagonalLinePoint1, aDiagonalLinePoint2);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestLine::setupDashedLine()
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ tools::Rectangle rectangle = maVDRectangle;
+ rectangle.shrink(2);
+
+ std::vector<double> stroke({ 2.0, 1.0 });
+ mpVirtualDevice->DrawPolyLineDirect( basegfx::B2DHomMatrix(),
+ basegfx::B2DPolygon{
+ basegfx::B2DPoint(rectangle.Left(), rectangle.Top()),
+ basegfx::B2DPoint(rectangle.Left(), rectangle.Bottom()),
+ basegfx::B2DPoint(rectangle.Right(), rectangle.Bottom()),
+ basegfx::B2DPoint(rectangle.Right(), rectangle.Top()),
+ basegfx::B2DPoint(rectangle.Left(), rectangle.Top())},
+ 1, 0, &stroke, basegfx::B2DLineJoin::NONE );
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+TestResult OutputDeviceTestLine::checkDashedLine(Bitmap& rBitmap)
+{
+ TestResult returnValue = TestResult::Passed;
+ for (int i = 0; i < 7; i++)
+ {
+ TestResult eResult = TestResult::Passed;
+ if( i == 2 )
+ {
+ // Build a sequence of pixels for the drawn rectangle border,
+ // check that they alternate appropriately (there should be
+ // normally 2 line, 1 background).
+ std::list< bool > dash; // true - line color, false - background
+ const int width = rBitmap.GetSizePixel().Width();
+ const int height = rBitmap.GetSizePixel().Height();
+ BitmapReadAccess access(rBitmap);
+ for( int x = 2; x < width - 2; ++x )
+ dash.push_back( access.GetPixel( 2, x ) == constLineColor );
+ for( int y = 3; y < height - 3; ++y )
+ dash.push_back( access.GetPixel( y, width - 3 ) == constLineColor );
+ for( int x = width - 3; x >= 2; --x )
+ dash.push_back( access.GetPixel( height - 3, x ) == constLineColor );
+ for( int y = height - 4; y >= 3; --y )
+ dash.push_back( access.GetPixel( y, 2 ) == constLineColor );
+ for( int x = 2; x < width - 2; ++x ) // repeat, to check also the corner
+ dash.push_back( access.GetPixel( 2, x ) == constLineColor );
+ bool last = false;
+ int lastCount = 0;
+ while( !dash.empty())
+ {
+ if( dash.front() == last )
+ {
+ ++lastCount;
+ if( lastCount > ( last ? 4 : 3 ))
+ eResult = TestResult::Failed;
+ else if( lastCount > ( last ? 3 : 2 ) && eResult != TestResult::Failed)
+ eResult = TestResult::PassedWithQuirks;
+ }
+ else
+ {
+ last = dash.front();
+ lastCount = 1;
+ }
+ dash.pop_front();
+ }
+ }
+ else
+ {
+ eResult = OutputDeviceTestCommon::checkRectangle(rBitmap, i, constBackgroundColor);
+ }
+
+ if (eResult == TestResult::Failed)
+ returnValue = TestResult::Failed;
+ if (eResult == TestResult::PassedWithQuirks && returnValue != TestResult::Failed)
+ returnValue = TestResult::PassedWithQuirks;
+ }
+ return returnValue;
+}
+
+constexpr int CAPSHRINK = 25;
+constexpr int CAPWIDTH = 20;
+Bitmap OutputDeviceTestLine::setupLineCap( css::drawing::LineCap lineCap )
+{
+ initialSetup(101, 101, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ tools::Rectangle rectangle = maVDRectangle;
+ rectangle.shrink(CAPSHRINK);
+
+ const basegfx::B2DPolygon poly{
+ basegfx::B2DPoint(rectangle.LeftCenter().getX(), rectangle.LeftCenter().getY()),
+ basegfx::B2DPoint(rectangle.RightCenter().getX(), rectangle.RightCenter().getY())};
+
+ mpVirtualDevice->DrawPolyLineDirect( basegfx::B2DHomMatrix(),poly,
+ CAPWIDTH, 0, nullptr, basegfx::B2DLineJoin::NONE, lineCap );
+
+ mpVirtualDevice->SetLineColor(constFillColor);
+ mpVirtualDevice->DrawPolyLineDirect( basegfx::B2DHomMatrix(), poly,
+ 0, 0, nullptr, basegfx::B2DLineJoin::NONE );
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestLine::setupLineJoin( basegfx::B2DLineJoin lineJoin )
+{
+ initialSetup(101, 101, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ tools::Rectangle rectangle = maVDRectangle;
+ rectangle.shrink(CAPSHRINK);
+
+ const basegfx::B2DPolygon poly{
+ basegfx::B2DPoint(rectangle.TopLeft().getX(), rectangle.TopLeft().getY()),
+ basegfx::B2DPoint(rectangle.TopRight().getX(), rectangle.TopRight().getY()),
+ basegfx::B2DPoint(rectangle.BottomRight().getX(), rectangle.BottomRight().getY())};
+
+ mpVirtualDevice->DrawPolyLineDirect( basegfx::B2DHomMatrix(), poly,
+ CAPWIDTH, 0, nullptr, lineJoin );
+
+ mpVirtualDevice->SetLineColor(constFillColor);
+ mpVirtualDevice->DrawPolyLineDirect( basegfx::B2DHomMatrix(), poly,
+ 0, 0, nullptr, lineJoin );
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+} // end namespace vcl::test
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/backendtest/outputdevice/outputdevice.cxx b/vcl/backendtest/outputdevice/outputdevice.cxx
new file mode 100644
index 0000000000..ca58e006fd
--- /dev/null
+++ b/vcl/backendtest/outputdevice/outputdevice.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 <test/outputdevice.hxx>
+
+namespace vcl::test {
+
+Bitmap OutputDeviceTestAnotherOutDev::setupDrawOutDev()
+{
+ ScopedVclPtrInstance<VirtualDevice> pSourceDev;
+ Size aSourceSize(9, 9);
+ pSourceDev->SetOutputSizePixel(aSourceSize);
+ pSourceDev->SetBackground(Wallpaper(constFillColor));
+ pSourceDev->Erase();
+
+ initialSetup(13, 13, constBackgroundColor);
+
+ mpVirtualDevice->DrawOutDev(Point(2, 2), aSourceSize, Point(), aSourceSize, *pSourceDev);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+
+Bitmap OutputDeviceTestAnotherOutDev::setupDrawOutDevScaledClipped()
+{
+ ScopedVclPtrInstance<VirtualDevice> pSourceDev;
+ Size aSourceSize(18, 18);
+ pSourceDev->SetOutputSizePixel(aSourceSize);
+ pSourceDev->SetBackground(Wallpaper(constFillColor));
+ pSourceDev->Erase();
+
+ initialSetup(13, 13, constBackgroundColor);
+
+ tools::Rectangle rectangle = maVDRectangle;
+ rectangle.SetLeft(rectangle.GetWidth() / 2);
+ mpVirtualDevice->SetClipRegion(vcl::Region(rectangle));
+
+ mpVirtualDevice->DrawOutDev(Point(2, 2), aSourceSize / 2, Point(), aSourceSize, *pSourceDev);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestAnotherOutDev::setupDrawOutDevSelf()
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor();
+ mpVirtualDevice->SetFillColor(constFillColor);
+
+ tools::Rectangle aDrawRectangle(maVDRectangle);
+ aDrawRectangle.shrink(3);
+ aDrawRectangle.Move( 2, -2 );
+ mpVirtualDevice->DrawRect(aDrawRectangle);
+ mpVirtualDevice->SetLineColor(COL_YELLOW);
+ mpVirtualDevice->DrawPixel(aDrawRectangle.TopLeft() + Point(aDrawRectangle.GetWidth() - 1, 0));
+ mpVirtualDevice->DrawPixel(aDrawRectangle.TopLeft() + Point(0,aDrawRectangle.GetHeight() - 1));
+
+ // Intentionally overlap a bit.
+ mpVirtualDevice->DrawOutDev(Point(1, 5), aDrawRectangle.GetSize(),
+ Point(5,1), aDrawRectangle.GetSize(), *mpVirtualDevice);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestAnotherOutDev::setupXOR()
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ tools::Rectangle aDrawRectangle(maVDRectangle);
+ aDrawRectangle.shrink(2);
+
+ tools::Rectangle aScissorRectangle(maVDRectangle);
+ aScissorRectangle.shrink(4);
+
+ mpVirtualDevice->SetRasterOp(RasterOp::Xor);
+ mpVirtualDevice->SetFillColor(constFillColor);
+ mpVirtualDevice->DrawRect(aDrawRectangle);
+
+ mpVirtualDevice->SetRasterOp(RasterOp::N0);
+ mpVirtualDevice->SetFillColor(COL_BLACK);
+ mpVirtualDevice->DrawRect(aScissorRectangle);
+
+ mpVirtualDevice->SetRasterOp(RasterOp::Xor);
+ mpVirtualDevice->SetFillColor(constFillColor);
+ mpVirtualDevice->DrawRect(aDrawRectangle);
+
+ mpVirtualDevice->SetRasterOp(RasterOp::Xor);
+ mpVirtualDevice->SetLineColor(constFillColor);
+ mpVirtualDevice->SetFillColor();
+ // Rectangle drawn twice is a no-op.
+ aDrawRectangle = maVDRectangle;
+ mpVirtualDevice->DrawRect(aDrawRectangle);
+ mpVirtualDevice->DrawRect(aDrawRectangle);
+ // Rectangle drawn three times is like drawing once.
+ aDrawRectangle.shrink(1);
+ mpVirtualDevice->DrawRect(aDrawRectangle);
+ mpVirtualDevice->DrawRect(aDrawRectangle);
+ mpVirtualDevice->DrawRect(aDrawRectangle);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+} // end namespace vcl::test
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/backendtest/outputdevice/pixel.cxx b/vcl/backendtest/outputdevice/pixel.cxx
new file mode 100644
index 0000000000..3892160ef7
--- /dev/null
+++ b/vcl/backendtest/outputdevice/pixel.cxx
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/outputdevice.hxx>
+
+namespace vcl::test
+{
+namespace
+{
+void drawPixelOffset(OutputDevice& rDevice, tools::Rectangle const& rRect, int nOffset)
+{
+ int nMidOffset = rRect.GetWidth() / 2;
+ for (tools::Long x = 0 + nOffset / 2; x < (rRect.GetWidth() - nMidOffset); ++x)
+ {
+ tools::Long y1 = nOffset - 1;
+ tools::Long y2 = rRect.GetHeight() - nOffset;
+
+ rDevice.DrawPixel(Point(x, y1));
+ rDevice.DrawPixel(Point(x, y2));
+ }
+
+ for (tools::Long y = 0 + nOffset; y < (rRect.GetHeight() - nOffset); ++y)
+ {
+ tools::Long x1 = nOffset / 2;
+ tools::Long x2 = rRect.GetWidth() - nMidOffset - nOffset / std::max((nOffset - 3), 2);
+
+ rDevice.DrawPixel(Point(x1, y));
+ rDevice.DrawPixel(Point(x2, y));
+ }
+}
+
+} // end anonymous namespace
+
+Bitmap OutputDeviceTestPixel::setupRectangle(bool bEnableAA)
+{
+ initialSetup(13, 13, constBackgroundColor, bEnableAA);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawPixelOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawPixelOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPixel::setupRectangleOnSize1028()
+{
+ initialSetup(1028, 1028, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawPixelOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawPixelOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPixel::setupRectangleOnSize4096()
+{
+ initialSetup(4096, 4096, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawPixelOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawPixelOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+} // end namespace vcl::test
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/backendtest/outputdevice/polygon.cxx b/vcl/backendtest/outputdevice/polygon.cxx
new file mode 100644
index 0000000000..051c0d893c
--- /dev/null
+++ b/vcl/backendtest/outputdevice/polygon.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/.
+ *
+ */
+
+#include <test/outputdevice.hxx>
+
+namespace vcl::test
+{
+namespace
+{
+void drawPolygonOffset(OutputDevice& rDevice, tools::Rectangle const& rRect, int nOffset,
+ int nFix = 0)
+{
+ // Note: According to https://lists.freedesktop.org/archives/libreoffice/2019-November/083709.html
+ // filling polygons always skips the right-most and bottom-most pixels, in order to avoid
+ // overlaps when drawing adjacent polygons. Specifying nFix = 1 allows to visually compensate
+ // for this by making the polygon explicitly larger.
+ tools::Polygon aPolygon(4);
+ int nMidOffset = rRect.GetWidth() / 2;
+ aPolygon.SetPoint(Point(rRect.Left() + nOffset - (nOffset+1)/2, rRect.Top() + nOffset - 1), 0);
+ aPolygon.SetPoint(Point(rRect.Right() - nMidOffset + nFix - nOffset/3, rRect.Top() + nOffset - 1), 1);
+ aPolygon.SetPoint(Point(rRect.Right() - nMidOffset + nFix - nOffset/3, rRect.Bottom() - nOffset + nFix + 1), 2);
+ aPolygon.SetPoint(Point(rRect.Left() + nOffset - (nOffset+1)/2, rRect.Bottom() - nOffset + nFix + 1), 3);
+ aPolygon.Optimize(PolyOptimizeFlags::CLOSE);
+
+ rDevice.DrawPolygon(aPolygon);
+}
+
+} // end anonymous namespace
+
+Bitmap OutputDeviceTestPolygon::setupRectangle(bool bEnableAA)
+{
+ initialSetup(13, 13, constBackgroundColor, bEnableAA);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawPolygonOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawPolygonOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolygon::setupFilledRectangle(bool useLineColor)
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ if (useLineColor)
+ mpVirtualDevice->SetLineColor(constLineColor);
+ else
+ mpVirtualDevice->SetLineColor();
+ mpVirtualDevice->SetFillColor(constFillColor);
+ drawPolygonOffset(*mpVirtualDevice, maVDRectangle, 2, useLineColor ? 0 : 1);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolygon::setupDiamond()
+{
+ initialSetup(11, 11, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ Point aPoint1, aPoint2, aPoint3, aPoint4;
+ OutputDeviceTestCommon::createDiamondPoints(maVDRectangle, 4, aPoint1, aPoint2, aPoint3,
+ aPoint4);
+
+ tools::Polygon aPolygon(4);
+
+ aPolygon.SetPoint(aPoint1, 0);
+ aPolygon.SetPoint(aPoint2, 1);
+ aPolygon.SetPoint(aPoint3, 2);
+ aPolygon.SetPoint(aPoint4, 3);
+ aPolygon.Optimize(PolyOptimizeFlags::CLOSE);
+
+ mpVirtualDevice->DrawPolygon(aPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolygon::setupLines()
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ Point aHorizontalLinePoint1, aHorizontalLinePoint2;
+ Point aVerticalLinePoint1, aVerticalLinePoint2;
+ Point aDiagonalLinePoint1, aDiagonalLinePoint2;
+
+ OutputDeviceTestCommon::createHorizontalVerticalDiagonalLinePoints(
+ maVDRectangle, aHorizontalLinePoint1, aHorizontalLinePoint2, aVerticalLinePoint1,
+ aVerticalLinePoint2, aDiagonalLinePoint1, aDiagonalLinePoint2);
+
+ tools::Polygon aHorizontalPolygon(2);
+ aHorizontalPolygon.SetPoint(aHorizontalLinePoint1, 0);
+ aHorizontalPolygon.SetPoint(aHorizontalLinePoint2, 1);
+ mpVirtualDevice->DrawPolygon(aHorizontalPolygon);
+
+ tools::Polygon aVerticalPolygon(2);
+ aVerticalPolygon.SetPoint(aVerticalLinePoint1, 0);
+ aVerticalPolygon.SetPoint(aVerticalLinePoint2, 1);
+ mpVirtualDevice->DrawPolygon(aVerticalPolygon);
+
+ tools::Polygon aDiagonalPolygon(2);
+ aDiagonalPolygon.SetPoint(aDiagonalLinePoint1, 0);
+ aDiagonalPolygon.SetPoint(aDiagonalLinePoint2, 1);
+ mpVirtualDevice->DrawPolygon(aDiagonalPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolygon::setupAALines()
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ mpVirtualDevice->SetAntialiasing(AntialiasingFlags::Enable);
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ Point aHorizontalLinePoint1, aHorizontalLinePoint2;
+ Point aVerticalLinePoint1, aVerticalLinePoint2;
+ Point aDiagonalLinePoint1, aDiagonalLinePoint2;
+
+ OutputDeviceTestCommon::createHorizontalVerticalDiagonalLinePoints(
+ maVDRectangle, aHorizontalLinePoint1, aHorizontalLinePoint2, aVerticalLinePoint1,
+ aVerticalLinePoint2, aDiagonalLinePoint1, aDiagonalLinePoint2);
+
+ tools::Polygon aHorizontalPolygon(2);
+ aHorizontalPolygon.SetPoint(aHorizontalLinePoint1, 0);
+ aHorizontalPolygon.SetPoint(aHorizontalLinePoint2, 1);
+ mpVirtualDevice->DrawPolygon(aHorizontalPolygon);
+
+ tools::Polygon aVerticalPolygon(2);
+ aVerticalPolygon.SetPoint(aVerticalLinePoint1, 0);
+ aVerticalPolygon.SetPoint(aVerticalLinePoint2, 1);
+ mpVirtualDevice->DrawPolygon(aVerticalPolygon);
+
+ tools::Polygon aDiagonalPolygon(2);
+ aDiagonalPolygon.SetPoint(aDiagonalLinePoint1, 0);
+ aDiagonalPolygon.SetPoint(aDiagonalLinePoint2, 1);
+ mpVirtualDevice->DrawPolygon(aDiagonalPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolygon::setupDropShape()
+{
+ initialSetup(21, 21, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ mpVirtualDevice->DrawPolygon(OutputDeviceTestCommon::createDropShapePolygon());
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolygon::setupAADropShape()
+{
+ initialSetup(21, 21, constBackgroundColor, true);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ mpVirtualDevice->DrawPolygon(OutputDeviceTestCommon::createDropShapePolygon());
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolygon::setupHalfEllipse(bool aEnableAA)
+{
+ initialSetup(19, 21, constBackgroundColor, aEnableAA);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ mpVirtualDevice->DrawPolyLine(
+ tools::Polygon(OutputDeviceTestCommon::createHalfEllipsePolygon()));
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolygon::setupClosedBezier()
+{
+ initialSetup(21, 16, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ mpVirtualDevice->DrawPolyLine(OutputDeviceTestCommon::createClosedBezierLoop(maVDRectangle));
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolygon::setupFilledAsymmetricalDropShape()
+{
+ initialSetup(21, 21, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor();
+ mpVirtualDevice->SetFillColor(constFillColor);
+
+ mpVirtualDevice->DrawPolygon(OutputDeviceTestCommon::createDropShapePolygon());
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolygon::setupRectangleOnSize1028()
+{
+ initialSetup(1028, 1028, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawPolygonOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawPolygonOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolygon::setupRectangleOnSize4096()
+{
+ initialSetup(4096, 4096, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawPolygonOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawPolygonOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolygon::setupOpenPolygon()
+{
+ initialSetup(21, 21, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ mpVirtualDevice->DrawPolygon(tools::Polygon(OutputDeviceTestCommon::createOpenPolygon(maVDRectangle)));
+ mpVirtualDevice->DrawPolygon(tools::Polygon(OutputDeviceTestCommon::createOpenPolygon(maVDRectangle, 7)));
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+} // end namespace vcl::test
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/backendtest/outputdevice/polyline.cxx b/vcl/backendtest/outputdevice/polyline.cxx
new file mode 100644
index 0000000000..d187d4addd
--- /dev/null
+++ b/vcl/backendtest/outputdevice/polyline.cxx
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/outputdevice.hxx>
+
+#include <cmath>
+#include <vector>
+
+namespace vcl::test {
+
+namespace
+{
+
+void drawPolyLineOffset(OutputDevice& rDevice, tools::Rectangle const & rRect, int nOffset)
+{
+ tools::Polygon aPolygon(4);
+ int nMidOffset = rRect.GetWidth() / 2;
+ aPolygon.SetPoint(Point(rRect.Left() + nOffset - (nOffset+1)/2, rRect.Top() + nOffset - 1), 0);
+ aPolygon.SetPoint(Point(rRect.Right() - nMidOffset - nOffset/3, rRect.Top() + nOffset - 1), 1);
+ aPolygon.SetPoint(Point(rRect.Right() - nMidOffset - nOffset/3, rRect.Bottom() - nOffset + 1), 2);
+ aPolygon.SetPoint(Point(rRect.Left() + nOffset - (nOffset+1)/2, rRect.Bottom() - nOffset + 1), 3);
+ aPolygon.Optimize(PolyOptimizeFlags::CLOSE);
+
+ rDevice.DrawPolygon(aPolygon);
+}
+
+} // end anonymous namespace
+
+Bitmap OutputDeviceTestPolyLine::setupRectangle(bool bEnableAA)
+{
+ initialSetup(13, 13, constBackgroundColor, bEnableAA);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawPolyLineOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawPolyLineOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLine::setupDiamond()
+{
+ initialSetup(11, 11, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ Point aPoint1, aPoint2, aPoint3, aPoint4;
+ OutputDeviceTestCommon::createDiamondPoints(maVDRectangle, 4, aPoint1, aPoint2, aPoint3, aPoint4);
+
+ tools::Polygon aPolygon(4);
+
+ aPolygon.SetPoint(aPoint1, 0);
+ aPolygon.SetPoint(aPoint2, 1);
+ aPolygon.SetPoint(aPoint3, 2);
+ aPolygon.SetPoint(aPoint4, 3);
+ aPolygon.Optimize(PolyOptimizeFlags::CLOSE);
+
+ mpVirtualDevice->DrawPolyLine(aPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLine::setupLines()
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ Point aHorizontalLinePoint1, aHorizontalLinePoint2;
+ Point aVerticalLinePoint1, aVerticalLinePoint2;
+ Point aDiagonalLinePoint1, aDiagonalLinePoint2;
+
+ OutputDeviceTestCommon::createHorizontalVerticalDiagonalLinePoints(
+ maVDRectangle, aHorizontalLinePoint1, aHorizontalLinePoint2,
+ aVerticalLinePoint1, aVerticalLinePoint2,
+ aDiagonalLinePoint1, aDiagonalLinePoint2);
+
+ tools::Polygon aHorizontalPolygon(2);
+ aHorizontalPolygon.SetPoint(aHorizontalLinePoint1, 0);
+ aHorizontalPolygon.SetPoint(aHorizontalLinePoint2, 1);
+ mpVirtualDevice->DrawPolyLine(aHorizontalPolygon);
+
+ tools::Polygon aVerticalPolygon(2);
+ aVerticalPolygon.SetPoint(aVerticalLinePoint1, 0);
+ aVerticalPolygon.SetPoint(aVerticalLinePoint2, 1);
+ mpVirtualDevice->DrawPolyLine(aVerticalPolygon);
+
+ tools::Polygon aDiagonalPolygon(2);
+ aDiagonalPolygon.SetPoint(aDiagonalLinePoint1, 0);
+ aDiagonalPolygon.SetPoint(aDiagonalLinePoint2, 1);
+ mpVirtualDevice->DrawPolyLine(aDiagonalPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLine::setupAALines()
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ mpVirtualDevice->SetAntialiasing(AntialiasingFlags::Enable);
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ Point aHorizontalLinePoint1, aHorizontalLinePoint2;
+ Point aVerticalLinePoint1, aVerticalLinePoint2;
+ Point aDiagonalLinePoint1, aDiagonalLinePoint2;
+
+ OutputDeviceTestCommon::createHorizontalVerticalDiagonalLinePoints(
+ maVDRectangle, aHorizontalLinePoint1, aHorizontalLinePoint2,
+ aVerticalLinePoint1, aVerticalLinePoint2,
+ aDiagonalLinePoint1, aDiagonalLinePoint2);
+
+ tools::Polygon aHorizontalPolygon(2);
+ aHorizontalPolygon.SetPoint(aHorizontalLinePoint1, 0);
+ aHorizontalPolygon.SetPoint(aHorizontalLinePoint2, 1);
+ mpVirtualDevice->DrawPolyLine(aHorizontalPolygon);
+
+ tools::Polygon aVerticalPolygon(2);
+ aVerticalPolygon.SetPoint(aVerticalLinePoint1, 0);
+ aVerticalPolygon.SetPoint(aVerticalLinePoint2, 1);
+ mpVirtualDevice->DrawPolyLine(aVerticalPolygon);
+
+ tools::Polygon aDiagonalPolygon(2);
+ aDiagonalPolygon.SetPoint(aDiagonalLinePoint1, 0);
+ aDiagonalPolygon.SetPoint(aDiagonalLinePoint2, 1);
+ mpVirtualDevice->DrawPolyLine(aDiagonalPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLine::setupDropShape()
+{
+ initialSetup(21, 21, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ mpVirtualDevice->DrawPolyLine(OutputDeviceTestCommon::createDropShapePolygon());
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLine::setupAADropShape()
+{
+ initialSetup(21, 21, constBackgroundColor,true);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ mpVirtualDevice->DrawPolyLine(OutputDeviceTestCommon::createDropShapePolygon());
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLine::setupHalfEllipse(bool aEnableAA)
+{
+ initialSetup(19, 21, constBackgroundColor, aEnableAA);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ mpVirtualDevice->DrawPolyLine(
+ tools::Polygon(OutputDeviceTestCommon::createHalfEllipsePolygon()));
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLine::setupClosedBezier()
+{
+ initialSetup(21, 16, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ mpVirtualDevice->DrawPolyLine(OutputDeviceTestCommon::createClosedBezierLoop(maVDRectangle));
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLine::setupRectangleOnSize1028()
+{
+ initialSetup(1028, 1028, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawPolyLineOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawPolyLineOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLine::setupRectangleOnSize4096()
+{
+ initialSetup(4096, 4096, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawPolyLineOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawPolyLineOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLine::setupOpenPolygon()
+{
+ initialSetup(21, 21, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ mpVirtualDevice->DrawPolyLine(
+ tools::Polygon(OutputDeviceTestCommon::createOpenPolygon(maVDRectangle)));
+ mpVirtualDevice->DrawPolyLine(
+ tools::Polygon(OutputDeviceTestCommon::createOpenPolygon(maVDRectangle, 7)));
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLine::setupOpenBezier()
+{
+ initialSetup(21, 21, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ mpVirtualDevice->DrawPolyLine(tools::Polygon(OutputDeviceTestCommon::createOpenBezier()));
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+} // end namespace vcl::test
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/backendtest/outputdevice/polyline_b2d.cxx b/vcl/backendtest/outputdevice/polyline_b2d.cxx
new file mode 100644
index 0000000000..1fa603d1ea
--- /dev/null
+++ b/vcl/backendtest/outputdevice/polyline_b2d.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/.
+ *
+ */
+
+#include <test/outputdevice.hxx>
+#include <vcl/bitmapex.hxx>
+
+namespace vcl::test
+{
+namespace
+{
+void drawPolyLineOffset(OutputDevice& rDevice, tools::Rectangle const& rRect, int nOffset)
+{
+ int nMidOffset = rRect.GetWidth() / 2;
+ basegfx::B2DPolygon aPolygon{
+ basegfx::B2DPoint(rRect.Left() + nOffset - (nOffset + 1) / 2, rRect.Top() + nOffset - 1),
+ basegfx::B2DPoint(rRect.Right() - nMidOffset - nOffset / 3, rRect.Top() + nOffset - 1),
+ basegfx::B2DPoint(rRect.Right() - nMidOffset - nOffset / 3, rRect.Bottom() - nOffset + 1),
+ basegfx::B2DPoint(rRect.Left() + nOffset - (nOffset + 1) / 2, rRect.Bottom() - nOffset + 1),
+ };
+ aPolygon.setClosed(true);
+
+ rDevice.DrawPolyLine(aPolygon, 0.0); // draw hairline
+}
+
+void addDiamondPoints(tools::Rectangle rRect, int nOffset, basegfx::B2DPolygon& rPolygon)
+{
+ double midPointX = rRect.Left() + (rRect.Right() - rRect.Left()) / 2.0;
+ double midPointY = rRect.Top() + (rRect.Bottom() - rRect.Top()) / 2.0;
+
+ rPolygon.append({ midPointX, midPointY - nOffset });
+ rPolygon.append({ midPointX + nOffset, midPointY });
+ rPolygon.append({ midPointX, midPointY + nOffset });
+ rPolygon.append({ midPointX - nOffset, midPointY });
+}
+
+} // end anonymous namespace
+
+Bitmap OutputDeviceTestPolyLineB2D::setupRectangle(bool bEnableAA)
+{
+ initialSetup(13, 13, constBackgroundColor, bEnableAA);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawPolyLineOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawPolyLineOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmapEx(maVDRectangle.TopLeft(), maVDRectangle.GetSize())
+ .GetBitmap();
+}
+
+Bitmap OutputDeviceTestPolyLineB2D::setupDiamond()
+{
+ initialSetup(11, 11, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ basegfx::B2DPolygon aPolygon;
+ addDiamondPoints(maVDRectangle, 4, aPolygon);
+ aPolygon.setClosed(true);
+
+ mpVirtualDevice->DrawPolyLine(aPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLineB2D::setupBezier()
+{
+ initialSetup(21, 21, constBackgroundColor, false);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ basegfx::B2DPolygon aPolygon;
+ addDiamondPoints(maVDRectangle, 8, aPolygon);
+ aPolygon.setClosed(true);
+
+ double minX = maVDRectangle.Left() + 4;
+ double maxX = maVDRectangle.Right() - 4;
+ double minY = maVDRectangle.Top() + 4;
+ double maxY = maVDRectangle.Bottom() - 4;
+
+ aPolygon.setControlPoints(0, { minX, minY }, { maxX, minY });
+ aPolygon.setControlPoints(1, { maxX, minY }, { maxX, maxY });
+ aPolygon.setControlPoints(2, { maxX, maxY }, { minX, maxY });
+ aPolygon.setControlPoints(3, { minX, maxY }, { minX, minY });
+
+ mpVirtualDevice->DrawPolyLine(aPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLineB2D::setupAABezier()
+{
+ initialSetup(21, 21, constBackgroundColor, true);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ basegfx::B2DPolygon aPolygon;
+ addDiamondPoints(maVDRectangle, 8, aPolygon);
+ aPolygon.setClosed(true);
+
+ double minX = maVDRectangle.Left() + 4;
+ double maxX = maVDRectangle.Right() - 4;
+ double minY = maVDRectangle.Top() + 4;
+ double maxY = maVDRectangle.Bottom() - 4;
+
+ aPolygon.setControlPoints(0, { minX, minY }, { maxX, minY });
+ aPolygon.setControlPoints(1, { maxX, minY }, { maxX, maxY });
+ aPolygon.setControlPoints(2, { maxX, maxY }, { minX, maxY });
+ aPolygon.setControlPoints(3, { minX, maxY }, { minX, minY });
+
+ mpVirtualDevice->DrawPolyLine(aPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLineB2D::setupHalfEllipse(bool aEnableAA)
+{
+ initialSetup(19, 21, constBackgroundColor, aEnableAA);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ mpVirtualDevice->DrawPolyLine(OutputDeviceTestCommon::createHalfEllipsePolygon());
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLineB2D::setupRectangleOnSize1028()
+{
+ initialSetup(1028, 1028, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawPolyLineOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawPolyLineOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLineB2D::setupRectangleOnSize4096()
+{
+ initialSetup(4096, 4096, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawPolyLineOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawPolyLineOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLineB2D::setupOpenPolygon()
+{
+ initialSetup(21, 21, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ mpVirtualDevice->DrawPolyLine(OutputDeviceTestCommon::createOpenPolygon(maVDRectangle));
+ mpVirtualDevice->DrawPolyLine(OutputDeviceTestCommon::createOpenPolygon(maVDRectangle, 7));
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyLineB2D::setupOpenBezier()
+{
+ initialSetup(21, 21, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ mpVirtualDevice->DrawPolyLine(OutputDeviceTestCommon::createOpenBezier());
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+} // end namespace vcl::test
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/backendtest/outputdevice/polypolygon.cxx b/vcl/backendtest/outputdevice/polypolygon.cxx
new file mode 100644
index 0000000000..e1abb2f901
--- /dev/null
+++ b/vcl/backendtest/outputdevice/polypolygon.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/.
+ *
+ */
+
+#include <test/outputdevice.hxx>
+
+namespace vcl::test
+{
+namespace
+{
+tools::Polygon createPolygonOffset(tools::Rectangle const& rRect, int nOffset, int nFix = 0)
+{
+ // Note: According to https://lists.freedesktop.org/archives/libreoffice/2019-November/083709.html
+ // filling polygons always skips the right-most and bottom-most pixels, in order to avoid
+ // overlaps when drawing adjacent polygons. Specifying nFix = 1 allows to visually compensate
+ // for this by making the polygon explicitly larger.
+ tools::Polygon aPolygon(4);
+ int nMidOffset = rRect.GetWidth() / 2;
+ aPolygon.SetPoint(Point(rRect.Left() + nOffset - (nOffset + 1) / 2, rRect.Top() + nOffset - 1),
+ 0);
+ aPolygon.SetPoint(
+ Point(rRect.Right() - nMidOffset + nFix - nOffset / 3, rRect.Top() + nOffset - 1), 1);
+ aPolygon.SetPoint(
+ Point(rRect.Right() - nMidOffset + nFix - nOffset / 3, rRect.Bottom() - nOffset + nFix + 1),
+ 2);
+ aPolygon.SetPoint(
+ Point(rRect.Left() + nOffset - (nOffset + 1) / 2, rRect.Bottom() - nOffset + nFix + 1), 3);
+ aPolygon.Optimize(PolyOptimizeFlags::CLOSE);
+ return aPolygon;
+}
+
+} // end anonymous namespace
+
+Bitmap OutputDeviceTestPolyPolygon::setupRectangle(bool bEnableAA)
+{
+ initialSetup(13, 13, constBackgroundColor, bEnableAA);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ tools::PolyPolygon aPolyPolygon(2);
+ aPolyPolygon.Insert(createPolygonOffset(maVDRectangle, 2));
+ aPolyPolygon.Insert(createPolygonOffset(maVDRectangle, 5));
+
+ mpVirtualDevice->DrawPolyPolygon(aPolyPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyPolygon::setupFilledRectangle(bool useLineColor)
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ if (useLineColor)
+ mpVirtualDevice->SetLineColor(constLineColor);
+ else
+ mpVirtualDevice->SetLineColor();
+ mpVirtualDevice->SetFillColor(constFillColor);
+
+ tools::PolyPolygon aPolyPolygon(1);
+ aPolyPolygon.Insert(createPolygonOffset(maVDRectangle, 2, useLineColor ? 0 : 1));
+
+ mpVirtualDevice->DrawPolyPolygon(aPolyPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyPolygon::setupIntersectingRectangles()
+{
+ initialSetup(24, 24, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor(constFillColor);
+
+ tools::PolyPolygon aPolyPolygon(4);
+
+ int nOffset = 2, nFix = 1;
+ tools::Polygon aPolygon1(4), aPolygon2(4), aPolygon3(4), aPolygon4(4);
+
+ /*
+ The intersection between different rectangles has been
+ achieved by stacking them on top of each other and decreasing and
+ increasing the top and bottom offset accordingly to the rectangle
+ keeping the left and the right offset intact which in turn coalesced
+ them to each other helping in achieving multiple intersecting rectangles.
+ The desired color fill pattern is then achieved by setting the fill
+ color which in turn would fill the shape with the provided color
+ in accordance to the even-odd filling rule.
+ */
+
+ //Rect - 1
+ aPolygon1.SetPoint(
+ Point(maVDRectangle.Left() + nOffset + nFix, maVDRectangle.Top() + (nOffset - 1) + nFix),
+ 0);
+ aPolygon1.SetPoint(Point(maVDRectangle.Right() - (nOffset + 2) + nFix,
+ maVDRectangle.Top() + (nOffset - 1) + nFix),
+ 1);
+ aPolygon1.SetPoint(Point(maVDRectangle.Right() - (nOffset + 2) + nFix,
+ maVDRectangle.Bottom() - (nOffset + 8) + nFix),
+ 2);
+ aPolygon1.SetPoint(
+ Point(maVDRectangle.Left() + nOffset + nFix, maVDRectangle.Bottom() - (nOffset + 8) + nFix),
+ 3);
+ aPolyPolygon.Insert(aPolygon1);
+
+ //Rect - 2
+ aPolygon2.SetPoint(
+ Point(maVDRectangle.Left() + nOffset + nFix, maVDRectangle.Top() + (nOffset + 2) + nFix),
+ 0);
+ aPolygon2.SetPoint(Point(maVDRectangle.Right() - (nOffset + 2) + nFix,
+ maVDRectangle.Top() + (nOffset + 2) + nFix),
+ 1);
+ aPolygon2.SetPoint(Point(maVDRectangle.Right() - (nOffset + 2) + nFix,
+ maVDRectangle.Bottom() - (nOffset + 5) + nFix),
+ 2);
+ aPolygon2.SetPoint(
+ Point(maVDRectangle.Left() + nOffset + nFix, maVDRectangle.Bottom() - (nOffset + 5) + nFix),
+ 3);
+ aPolyPolygon.Insert(aPolygon2);
+
+ //Rect - 3
+ aPolygon3.SetPoint(
+ Point(maVDRectangle.Left() + nOffset + nFix, maVDRectangle.Top() + (nOffset + 5) + nFix),
+ 0);
+ aPolygon3.SetPoint(Point(maVDRectangle.Right() - (nOffset + 2) + nFix,
+ maVDRectangle.Top() + (nOffset + 5) + nFix),
+ 1);
+ aPolygon3.SetPoint(Point(maVDRectangle.Right() - (nOffset + 2) + nFix,
+ maVDRectangle.Bottom() - (nOffset + 2) + nFix),
+ 2);
+ aPolygon3.SetPoint(
+ Point(maVDRectangle.Left() + nOffset + nFix, maVDRectangle.Bottom() - (nOffset + 2) + nFix),
+ 3);
+ aPolyPolygon.Insert(aPolygon3);
+
+ //Rect - 4
+ aPolygon4.SetPoint(
+ Point(maVDRectangle.Left() + nOffset + nFix, maVDRectangle.Top() + (nOffset + 8) + nFix),
+ 0);
+ aPolygon4.SetPoint(Point(maVDRectangle.Right() - (nOffset + 2) + nFix,
+ maVDRectangle.Top() + (nOffset + 8) + nFix),
+ 1);
+ aPolygon4.SetPoint(Point(maVDRectangle.Right() - (nOffset + 2) + nFix,
+ maVDRectangle.Bottom() - nOffset + nFix),
+ 2);
+ aPolygon4.SetPoint(
+ Point(maVDRectangle.Left() + nOffset + nFix, maVDRectangle.Bottom() - nOffset + nFix), 3);
+ aPolyPolygon.Insert(aPolygon4);
+
+ mpVirtualDevice->DrawPolyPolygon(aPolyPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyPolygon::setupRectangleOnSize1028()
+{
+ initialSetup(1028, 1028, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ tools::PolyPolygon aPolyPolygon(2);
+ aPolyPolygon.Insert(createPolygonOffset(maVDRectangle, 2));
+ aPolyPolygon.Insert(createPolygonOffset(maVDRectangle, 5));
+
+ mpVirtualDevice->DrawPolyPolygon(aPolyPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyPolygon::setupRectangleOnSize4096()
+{
+ initialSetup(4096, 4096, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ tools::PolyPolygon aPolyPolygon(2);
+ aPolyPolygon.Insert(createPolygonOffset(maVDRectangle, 2));
+ aPolyPolygon.Insert(createPolygonOffset(maVDRectangle, 5));
+
+ mpVirtualDevice->DrawPolyPolygon(aPolyPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyPolygon::setupOpenPolygon()
+{
+ initialSetup(21, 21, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ tools::PolyPolygon aPolyPolygon;
+ aPolyPolygon.Insert(tools::Polygon(OutputDeviceTestCommon::createOpenPolygon(maVDRectangle)));
+ aPolyPolygon.Insert(
+ tools::Polygon(OutputDeviceTestCommon::createOpenPolygon(maVDRectangle, 7)));
+
+ mpVirtualDevice->DrawPolyPolygon(aPolyPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+} // end namespace vcl::test
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/backendtest/outputdevice/polypolygon_b2d.cxx b/vcl/backendtest/outputdevice/polypolygon_b2d.cxx
new file mode 100644
index 0000000000..59391abe19
--- /dev/null
+++ b/vcl/backendtest/outputdevice/polypolygon_b2d.cxx
@@ -0,0 +1,199 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/outputdevice.hxx>
+
+namespace vcl::test
+{
+namespace
+{
+basegfx::B2DPolygon createPolygonOffset(tools::Rectangle const& rRect, int nOffset, int nFix = 0)
+{
+ // Note: According to https://lists.freedesktop.org/archives/libreoffice/2019-November/083709.html
+ // filling polygons always skips the right-most and bottom-most pixels, in order to avoid
+ // overlaps when drawing adjacent polygons. Specifying nFix = 1 allows to visually compensate
+ // for this by making the polygon explicitly larger.
+ int nMidOffset = rRect.GetWidth() / 2;
+ basegfx::B2DPolygon aPolygon{
+ basegfx::B2DPoint(rRect.Left() + nOffset - (nOffset + 1) / 2, rRect.Top() + nOffset - 1),
+ basegfx::B2DPoint(rRect.Right() - nMidOffset - nOffset / 3 + nFix,
+ rRect.Top() + nOffset - 1),
+ basegfx::B2DPoint(rRect.Right() - nMidOffset - nOffset / 3 + nFix,
+ rRect.Bottom() - nOffset + 1 + nFix),
+ basegfx::B2DPoint(rRect.Left() + nOffset - (nOffset + 1) / 2,
+ rRect.Bottom() - nOffset + 1 + nFix),
+ };
+ aPolygon.setClosed(true);
+ return aPolygon;
+}
+
+} // end anonymous namespace
+
+Bitmap OutputDeviceTestPolyPolygonB2D::setupRectangle(bool bEnableAA)
+{
+ initialSetup(13, 13, constBackgroundColor, bEnableAA);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ basegfx::B2DPolyPolygon aPolyPolygon;
+ aPolyPolygon.append(createPolygonOffset(maVDRectangle, 2));
+ aPolyPolygon.append(createPolygonOffset(maVDRectangle, 5));
+
+ mpVirtualDevice->DrawPolyPolygon(aPolyPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyPolygonB2D::setupFilledRectangle(bool useLineColor)
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ if (useLineColor)
+ mpVirtualDevice->SetLineColor(constLineColor);
+ else
+ mpVirtualDevice->SetLineColor();
+ mpVirtualDevice->SetFillColor(constFillColor);
+
+ basegfx::B2DPolyPolygon aPolyPolygon(
+ createPolygonOffset(maVDRectangle, 2, useLineColor ? 0 : 1));
+
+ mpVirtualDevice->DrawPolyPolygon(aPolyPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyPolygonB2D::setupIntersectingRectangles()
+{
+ initialSetup(24, 24, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor(constFillColor);
+
+ basegfx::B2DPolyPolygon aPolyPolygon;
+
+ int nOffset = 2, nFix = 1;
+ basegfx::B2DPolygon aPolygon1, aPolygon2, aPolygon3, aPolygon4;
+
+ /*
+ The intersection between different rectangles has been
+ achieved by stacking them on top of each other and decreasing and
+ increasing the top and bottom offset accordingly to the rectangle
+ keeping the left and the right offset intact which in turn coalesced
+ them to each other helping in achieving multiple intersecting rectangles.
+ The desired color fill pattern is then achieved by setting the fill
+ color which in turn would fill the shape with the provided color
+ in accordance to the even-odd filling rule.
+ */
+
+ //Rect - 1
+ aPolygon1.append(basegfx::B2DPoint(maVDRectangle.Left() + nOffset + nFix,
+ maVDRectangle.Top() + (nOffset - 1) + nFix));
+ aPolygon1.append(basegfx::B2DPoint(maVDRectangle.Right() - (nOffset + 2) + nFix,
+ maVDRectangle.Top() + (nOffset - 1) + nFix));
+ aPolygon1.append(basegfx::B2DPoint(maVDRectangle.Right() - (nOffset + 2) + nFix,
+ maVDRectangle.Bottom() - (nOffset + 8) + nFix));
+ aPolygon1.append(basegfx::B2DPoint(maVDRectangle.Left() + nOffset + nFix,
+ maVDRectangle.Bottom() - (nOffset + 8) + nFix));
+ aPolygon1.setClosed(true);
+ aPolyPolygon.append(aPolygon1);
+
+ //Rect - 2
+ aPolygon2.append(basegfx::B2DPoint(maVDRectangle.Left() + nOffset + nFix,
+ maVDRectangle.Top() + (nOffset + 2) + nFix));
+ aPolygon2.append(basegfx::B2DPoint(maVDRectangle.Right() - (nOffset + 2) + nFix,
+ maVDRectangle.Top() + (nOffset + 2) + nFix));
+ aPolygon2.append(basegfx::B2DPoint(maVDRectangle.Right() - (nOffset + 2) + nFix,
+ maVDRectangle.Bottom() - (nOffset + 5) + nFix));
+ aPolygon2.append(basegfx::B2DPoint(maVDRectangle.Left() + nOffset + nFix,
+ maVDRectangle.Bottom() - (nOffset + 5) + nFix));
+ aPolygon2.setClosed(true);
+ aPolyPolygon.append(aPolygon2);
+
+ //Rect - 3
+ aPolygon3.append(basegfx::B2DPoint(maVDRectangle.Left() + nOffset + nFix,
+ maVDRectangle.Top() + (nOffset + 5) + nFix));
+ aPolygon3.append(basegfx::B2DPoint(maVDRectangle.Right() - (nOffset + 2) + nFix,
+ maVDRectangle.Top() + (nOffset + 5) + nFix));
+ aPolygon3.append(basegfx::B2DPoint(maVDRectangle.Right() - (nOffset + 2) + nFix,
+ maVDRectangle.Bottom() - (nOffset + 2) + nFix));
+ aPolygon3.append(basegfx::B2DPoint(maVDRectangle.Left() + nOffset + nFix,
+ maVDRectangle.Bottom() - (nOffset + 2) + nFix));
+ aPolygon3.setClosed(true);
+ aPolyPolygon.append(aPolygon3);
+
+ //Rect - 4
+ aPolygon4.append(basegfx::B2DPoint(maVDRectangle.Left() + nOffset + nFix,
+ maVDRectangle.Top() + (nOffset + 8) + nFix));
+ aPolygon4.append(basegfx::B2DPoint(maVDRectangle.Right() - (nOffset + 2) + nFix,
+ maVDRectangle.Top() + (nOffset + 8) + nFix));
+ aPolygon4.append(basegfx::B2DPoint(maVDRectangle.Right() - (nOffset + 2) + nFix,
+ maVDRectangle.Bottom() - nOffset + nFix));
+ aPolygon4.append(basegfx::B2DPoint(maVDRectangle.Left() + nOffset + nFix,
+ maVDRectangle.Bottom() - nOffset + nFix));
+ aPolygon4.setClosed(true);
+ aPolyPolygon.append(aPolygon4);
+
+ mpVirtualDevice->DrawPolyPolygon(aPolyPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyPolygonB2D::setupRectangleOnSize1028()
+{
+ initialSetup(1028, 1028, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ basegfx::B2DPolyPolygon aPolyPolygon;
+ aPolyPolygon.append(createPolygonOffset(maVDRectangle, 2));
+ aPolyPolygon.append(createPolygonOffset(maVDRectangle, 5));
+
+ mpVirtualDevice->DrawPolyPolygon(aPolyPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyPolygonB2D::setupRectangleOnSize4096()
+{
+ initialSetup(4096, 4096, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ basegfx::B2DPolyPolygon aPolyPolygon;
+ aPolyPolygon.append(createPolygonOffset(maVDRectangle, 2));
+ aPolyPolygon.append(createPolygonOffset(maVDRectangle, 5));
+
+ mpVirtualDevice->DrawPolyPolygon(aPolyPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestPolyPolygonB2D::setupOpenPolygon()
+{
+ initialSetup(21, 21, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ basegfx::B2DPolyPolygon aPolyPolygon;
+ aPolyPolygon.append(OutputDeviceTestCommon::createOpenPolygon(maVDRectangle));
+ aPolyPolygon.append(OutputDeviceTestCommon::createOpenPolygon(maVDRectangle, 7));
+
+ mpVirtualDevice->DrawPolyPolygon(aPolyPolygon);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+} // end namespace vcl::test
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/backendtest/outputdevice/rectangle.cxx b/vcl/backendtest/outputdevice/rectangle.cxx
new file mode 100644
index 0000000000..f35285f282
--- /dev/null
+++ b/vcl/backendtest/outputdevice/rectangle.cxx
@@ -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/.
+ *
+ */
+
+#include <test/outputdevice.hxx>
+
+namespace vcl::test
+{
+namespace
+{
+void drawRectOffset(OutputDevice& rDevice, tools::Rectangle const& rRect, int nOffset)
+{
+ int nMidOffset = rRect.Left() + (rRect.Right() - rRect.Left()) / 2;
+ rDevice.DrawRect(
+ tools::Rectangle(rRect.Left() + nOffset - (nOffset + 1) / 2, rRect.Top() + nOffset - 1,
+ rRect.Right() - nMidOffset - nOffset / 3, rRect.Bottom() - nOffset + 1));
+}
+
+void drawInvertOffset(OutputDevice& rDevice, tools::Rectangle const& rRect, int nOffset,
+ InvertFlags eFlags)
+{
+ tools::Rectangle aRectangle(rRect.Left() + nOffset, rRect.Top() + nOffset,
+ rRect.Right() - nOffset, rRect.Bottom() - nOffset);
+ rDevice.Invert(aRectangle, eFlags);
+}
+
+} // end anonymous namespace
+
+Bitmap OutputDeviceTestRect::setupFilledRectangle(bool useLineColor)
+{
+ initialSetup(13, 13, constBackgroundColor);
+
+ if (useLineColor)
+ mpVirtualDevice->SetLineColor(constLineColor);
+ else
+ mpVirtualDevice->SetLineColor();
+ mpVirtualDevice->SetFillColor(constFillColor);
+
+ drawRectOffset(*mpVirtualDevice, maVDRectangle, 2);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestRect::setupRectangle(bool bEnableAA)
+{
+ initialSetup(13, 13, constBackgroundColor, bEnableAA);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawRectOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawRectOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestRect::setupInvert_NONE()
+{
+ initialSetup(20, 20, COL_WHITE);
+
+ mpVirtualDevice->SetLineColor();
+ mpVirtualDevice->SetFillColor(COL_LIGHTRED);
+ mpVirtualDevice->DrawRect(tools::Rectangle(Point(2, 2), Size(8, 8)));
+ mpVirtualDevice->SetFillColor(COL_LIGHTGREEN);
+ mpVirtualDevice->DrawRect(tools::Rectangle(Point(10, 2), Size(8, 8)));
+ mpVirtualDevice->SetFillColor(COL_LIGHTBLUE);
+ mpVirtualDevice->DrawRect(tools::Rectangle(Point(2, 10), Size(8, 8)));
+
+ drawInvertOffset(*mpVirtualDevice, maVDRectangle, 2, InvertFlags::NONE);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestRect::setupInvert_N50()
+{
+ initialSetup(20, 20, COL_WHITE);
+
+ mpVirtualDevice->SetLineColor();
+ mpVirtualDevice->SetFillColor(COL_LIGHTRED);
+ mpVirtualDevice->DrawRect(tools::Rectangle(Point(2, 2), Size(8, 8)));
+ mpVirtualDevice->SetFillColor(COL_LIGHTGREEN);
+ mpVirtualDevice->DrawRect(tools::Rectangle(Point(10, 2), Size(8, 8)));
+ mpVirtualDevice->SetFillColor(COL_LIGHTBLUE);
+ mpVirtualDevice->DrawRect(tools::Rectangle(Point(2, 10), Size(8, 8)));
+
+ drawInvertOffset(*mpVirtualDevice, maVDRectangle, 2, InvertFlags::N50);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestRect::setupInvert_TrackFrame()
+{
+ initialSetup(20, 20, COL_WHITE);
+
+ mpVirtualDevice->SetLineColor();
+ mpVirtualDevice->SetFillColor(COL_LIGHTRED);
+ mpVirtualDevice->DrawRect(tools::Rectangle(Point(2, 2), Size(8, 8)));
+ mpVirtualDevice->SetFillColor(COL_LIGHTGREEN);
+ mpVirtualDevice->DrawRect(tools::Rectangle(Point(10, 2), Size(8, 8)));
+ mpVirtualDevice->SetFillColor(COL_LIGHTBLUE);
+ mpVirtualDevice->DrawRect(tools::Rectangle(Point(2, 10), Size(8, 8)));
+
+ drawInvertOffset(*mpVirtualDevice, maVDRectangle, 2, InvertFlags::TrackFrame);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestRect::setupRectangleOnSize1028()
+{
+ initialSetup(1028, 1028, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawRectOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawRectOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+Bitmap OutputDeviceTestRect::setupRectangleOnSize4096()
+{
+ initialSetup(4096, 4096, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ drawRectOffset(*mpVirtualDevice, maVDRectangle, 2);
+ drawRectOffset(*mpVirtualDevice, maVDRectangle, 5);
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+
+} // end namespace vcl::test
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/backendtest/outputdevice/text.cxx b/vcl/backendtest/outputdevice/text.cxx
new file mode 100644
index 0000000000..5e53d900ba
--- /dev/null
+++ b/vcl/backendtest/outputdevice/text.cxx
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: offset; indent-tabs-mode: nil; c-basic-offset: offset -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/outputdevice.hxx>
+
+namespace vcl::test
+{
+Bitmap OutputDeviceTestText::setupTextBitmap()
+{
+ initialSetup(21, 21, constBackgroundColor);
+
+ mpVirtualDevice->SetLineColor(constLineColor);
+ mpVirtualDevice->SetFillColor();
+
+ tools::Long midX = (maVDRectangle.Right() - maVDRectangle.Left()) / 2.0;
+ tools::Long midY = (maVDRectangle.Bottom() - maVDRectangle.Top()) / 2.0;
+
+ vcl::Font Font("DejaVu Sans", "Book", Size(0, 10));
+
+ mpVirtualDevice->Erase();
+ mpVirtualDevice->SetFont(Font);
+ mpVirtualDevice->SetTextColor(COL_LIGHTRED);
+ mpVirtualDevice->DrawText(Point(midX, midY - midY / 2), "I");
+
+ return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
+}
+} \ No newline at end of file
diff --git a/vcl/commonfuzzer.mk b/vcl/commonfuzzer.mk
new file mode 100644
index 0000000000..ef3e1d7541
--- /dev/null
+++ b/vcl/commonfuzzer.mk
@@ -0,0 +1,192 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+fuzzer_externals = \
+ boost_headers \
+ orcus \
+ orcus-parser \
+ boost_locale \
+ boost_filesystem \
+ boost_system \
+ boost_iostreams \
+ curl \
+ dtoa \
+ harfbuzz \
+ graphite \
+ cairo \
+ fontconfig \
+ freetype \
+ gpgmepp \
+ icui18n \
+ icuuc \
+ icudata \
+ lcms2 \
+ librdf \
+ libexttextcat \
+ liblangtag \
+ libxslt \
+ libxml2 \
+ libjpeg \
+ libpng \
+ libtiff \
+ libwebp \
+ openssl \
+ expat \
+ mythes \
+ hyphen \
+ hunspell \
+ zlib \
+ zxcvbn-c \
+
+fuzzer_statics = \
+ findsofficepath \
+ ulingu \
+
+fuzzer_core_libraries = \
+ avmedia \
+ basegfx \
+ $(call gb_Helper_optional,SCRIPTING, \
+ basctl \
+ basprov \
+ ) \
+ bib \
+ canvastools \
+ configmgr \
+ cppcanvas \
+ ctl \
+ dba \
+ dbase \
+ dbtools \
+ deployment \
+ deploymentmisc \
+ docmodel \
+ drawinglayer \
+ drawinglayercore \
+ editeng \
+ emfio \
+ file \
+ filterconfig \
+ fsstorage \
+ fwk \
+ i18npool \
+ i18nutil \
+ io \
+ lng \
+ localebe1 \
+ msfilter \
+ package2 \
+ sax \
+ sb \
+ sdbc2 \
+ $(call gb_Helper_optional,SCRIPTING, \
+ scriptframe) \
+ spell \
+ sfx \
+ sofficeapp \
+ sot \
+ svl \
+ svt \
+ svx \
+ svxcore \
+ emboleobj \
+ svgfilter \
+ svgio \
+ animcore \
+ tk \
+ tl \
+ ucb1 \
+ ucbhelper \
+ ucpexpand1 \
+ ucpfile1 \
+ ucppkg1 \
+ unoxml \
+ utl \
+ uui \
+ vcl \
+ xmlscript \
+ xo \
+ xstor \
+ cui \
+ chartcontroller \
+ chartcore \
+ sm \
+ oox \
+ proxyfac \
+ reflection \
+ odfflatxml \
+ invocadapt \
+ bootstrap \
+ introspection \
+ stocservices \
+ lnth \
+ hyphen \
+ i18nsearch \
+ embobj \
+ evtatt \
+ unordf \
+ ucphier1 \
+ ucptdoc1 \
+ srtrs1 \
+ storagefd \
+ mtfrenderer \
+ canvasfactory \
+ vclcanvas \
+ xof \
+ xmlfa \
+ xmlfd \
+ cppu \
+ cppuhelper \
+ comphelper \
+ i18nlangtag \
+ xmlreader \
+ unoidl \
+ reg \
+ store \
+ gcc3_uno \
+ salhelper \
+ sal \
+ index_data \
+ localedata_en \
+ localedata_others \
+
+fuzzer_calc_libraries = \
+ analysis \
+ date \
+ pricing \
+ scfilt \
+ scd \
+ $(call gb_Helper_optional,SCRIPTING, \
+ vbaevents \
+ vbahelper \
+ vbaobj \
+ ) \
+ sc \
+ for \
+ forui \
+ guesslang \
+
+fuzzer_writer_libraries = \
+ msword \
+ sw \
+ swd \
+ writerfilter \
+ wpftwriter \
+ textfd \
+ guesslang \
+
+fuzzer_draw_libraries = \
+ sd \
+ sdd \
+ icg \
+ guesslang \
+
+fuzzer_math_libraries = \
+ sm \
+ guesslang \
diff --git a/vcl/headless/BitmapHelper.cxx b/vcl/headless/BitmapHelper.cxx
new file mode 100644
index 0000000000..2cdf502fc9
--- /dev/null
+++ b/vcl/headless/BitmapHelper.cxx
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <headless/BitmapHelper.hxx>
+#include <svdata.hxx>
+#include <utility>
+
+BitmapHelper::BitmapHelper(const SalBitmap& rSourceBitmap, const bool bForceARGB32)
+#ifdef HAVE_CAIRO_FORMAT_RGB24_888
+ : m_bForceARGB32(bForceARGB32)
+#endif
+{
+ const SvpSalBitmap& rSrcBmp = static_cast<const SvpSalBitmap&>(rSourceBitmap);
+#ifdef HAVE_CAIRO_FORMAT_RGB24_888
+ if ((rSrcBmp.GetBitCount() != 32 && rSrcBmp.GetBitCount() != 24) || bForceARGB32)
+#else
+ (void)bForceARGB32;
+ if (rSrcBmp.GetBitCount() != 32)
+#endif
+ {
+ //big stupid copy here
+ const BitmapBuffer* pSrc = rSrcBmp.GetBuffer();
+ const SalTwoRect aTwoRect
+ = { 0, 0, pSrc->mnWidth, pSrc->mnHeight, 0, 0, pSrc->mnWidth, pSrc->mnHeight };
+ std::optional<BitmapBuffer> pTmp
+ = (pSrc->mnFormat == SVP_24BIT_FORMAT
+ ? FastConvert24BitRgbTo32BitCairo(pSrc)
+ : StretchAndConvert(*pSrc, aTwoRect, SVP_CAIRO_FORMAT));
+ aTmpBmp.Create(std::move(pTmp));
+
+ assert(aTmpBmp.GetBitCount() == 32);
+ implSetSurface(CairoCommon::createCairoSurface(aTmpBmp.GetBuffer()));
+ }
+ else
+ {
+ implSetSurface(CairoCommon::createCairoSurface(rSrcBmp.GetBuffer()));
+ }
+}
+
+void BitmapHelper::mark_dirty() { cairo_surface_mark_dirty(implGetSurface()); }
+
+unsigned char* BitmapHelper::getBits(sal_Int32& rStride)
+{
+ cairo_surface_flush(implGetSurface());
+
+ unsigned char* mask_data = cairo_image_surface_get_data(implGetSurface());
+
+ const cairo_format_t nFormat = cairo_image_surface_get_format(implGetSurface());
+#ifdef HAVE_CAIRO_FORMAT_RGB24_888
+ if (!m_bForceARGB32)
+ assert(nFormat == CAIRO_FORMAT_RGB24_888 && "Expected RGB24_888 image");
+ else
+#endif
+ {
+ assert(nFormat == CAIRO_FORMAT_ARGB32
+ && "need to implement CAIRO_FORMAT_A1 after all here");
+ }
+
+ rStride
+ = cairo_format_stride_for_width(nFormat, cairo_image_surface_get_width(implGetSurface()));
+
+ return mask_data;
+}
+
+MaskHelper::MaskHelper(const SalBitmap& rAlphaBitmap)
+{
+ const SvpSalBitmap& rMask = static_cast<const SvpSalBitmap&>(rAlphaBitmap);
+ const BitmapBuffer* pMaskBuf = rMask.GetBuffer();
+ assert(rAlphaBitmap.GetBitCount() == 8 && "we only support 8-bit masks now");
+
+ implSetSurface(cairo_image_surface_create_for_data(pMaskBuf->mpBits, CAIRO_FORMAT_A8,
+ pMaskBuf->mnWidth, pMaskBuf->mnHeight,
+ pMaskBuf->mnScanlineSize));
+}
+
+namespace
+{
+// check for env var that decides for using downscale pattern
+const char* pDisableDownScale(getenv("SAL_DISABLE_CAIRO_DOWNSCALE"));
+bool bDisableDownScale(nullptr != pDisableDownScale);
+
+sal_Int64 estimateUsageInBytesForSurfaceHelper(const SurfaceHelper* pHelper)
+{
+ sal_Int64 nRetval(0);
+
+ if (nullptr != pHelper)
+ {
+ cairo_surface_t* pSurface(pHelper->getSurface());
+
+ if (pSurface)
+ {
+ const tools::Long nStride(cairo_image_surface_get_stride(pSurface));
+ const tools::Long nHeight(cairo_image_surface_get_height(pSurface));
+
+ nRetval = nStride * nHeight;
+
+ // if we do downscale, size will grow by 1/4 + 1/16 + 1/32 + ...,
+ // rough estimation just multiplies by 1.25, should be good enough
+ // for estimation of buffer survival time
+ if (!bDisableDownScale)
+ {
+ nRetval = (nRetval * 5) / 4;
+ }
+ }
+ }
+
+ return nRetval;
+}
+
+} // end anonymous namespace
+
+SystemDependentData_BitmapHelper::SystemDependentData_BitmapHelper(
+ std::shared_ptr<BitmapHelper> xBitmapHelper)
+ : basegfx::SystemDependentData(Application::GetSystemDependentDataManager())
+ , maBitmapHelper(std::move(xBitmapHelper))
+{
+}
+
+sal_Int64 SystemDependentData_BitmapHelper::estimateUsageInBytes() const
+{
+ return estimateUsageInBytesForSurfaceHelper(maBitmapHelper.get());
+}
+
+SystemDependentData_MaskHelper::SystemDependentData_MaskHelper(
+ std::shared_ptr<MaskHelper> xMaskHelper)
+ : basegfx::SystemDependentData(Application::GetSystemDependentDataManager())
+ , maMaskHelper(std::move(xMaskHelper))
+{
+}
+
+sal_Int64 SystemDependentData_MaskHelper::estimateUsageInBytes() const
+{
+ return estimateUsageInBytesForSurfaceHelper(maMaskHelper.get());
+}
+
+namespace
+{
+// MM02 decide to use buffers or not
+const char* pDisableMM02Goodies(getenv("SAL_DISABLE_MM02_GOODIES"));
+bool bUseBuffer(nullptr == pDisableMM02Goodies);
+const tools::Long nMinimalSquareSizeToBuffer(64 * 64);
+}
+
+void tryToUseSourceBuffer(const SalBitmap& rSourceBitmap, std::shared_ptr<BitmapHelper>& rSurface)
+{
+ // MM02 try to access buffered BitmapHelper
+ std::shared_ptr<SystemDependentData_BitmapHelper> pSystemDependentData_BitmapHelper;
+ const bool bBufferSource(bUseBuffer
+ && rSourceBitmap.GetSize().Width() * rSourceBitmap.GetSize().Height()
+ > nMinimalSquareSizeToBuffer);
+
+ if (bBufferSource)
+ {
+ pSystemDependentData_BitmapHelper
+ = rSourceBitmap.getSystemDependentData<SystemDependentData_BitmapHelper>();
+
+ if (pSystemDependentData_BitmapHelper)
+ {
+ // reuse buffered data
+ rSurface = pSystemDependentData_BitmapHelper->getBitmapHelper();
+ }
+ }
+
+ if (rSurface)
+ return;
+
+ // create data on-demand
+ rSurface = std::make_shared<BitmapHelper>(rSourceBitmap);
+
+ if (bBufferSource)
+ {
+ // add to buffering mechanism to potentially reuse next time
+ rSourceBitmap.addOrReplaceSystemDependentData<SystemDependentData_BitmapHelper>(rSurface);
+ }
+}
+
+void tryToUseMaskBuffer(const SalBitmap& rMaskBitmap, std::shared_ptr<MaskHelper>& rMask)
+{
+ // MM02 try to access buffered MaskHelper
+ std::shared_ptr<SystemDependentData_MaskHelper> pSystemDependentData_MaskHelper;
+ const bool bBufferMask(bUseBuffer
+ && rMaskBitmap.GetSize().Width() * rMaskBitmap.GetSize().Height()
+ > nMinimalSquareSizeToBuffer);
+
+ if (bBufferMask)
+ {
+ pSystemDependentData_MaskHelper
+ = rMaskBitmap.getSystemDependentData<SystemDependentData_MaskHelper>();
+
+ if (pSystemDependentData_MaskHelper)
+ {
+ // reuse buffered data
+ rMask = pSystemDependentData_MaskHelper->getMaskHelper();
+ }
+ }
+
+ if (rMask)
+ return;
+
+ // create data on-demand
+ rMask = std::make_shared<MaskHelper>(rMaskBitmap);
+
+ if (bBufferMask)
+ {
+ // add to buffering mechanism to potentially reuse next time
+ rMaskBitmap.addOrReplaceSystemDependentData<SystemDependentData_MaskHelper>(rMask);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/CairoCommon.cxx b/vcl/headless/CairoCommon.cxx
new file mode 100644
index 0000000000..2af8e0d892
--- /dev/null
+++ b/vcl/headless/CairoCommon.cxx
@@ -0,0 +1,2208 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <headless/BitmapHelper.hxx>
+#include <headless/CairoCommon.hxx>
+#include <dlfcn.h>
+#include <vcl/BitmapTools.hxx>
+#include <SalGradient.hxx>
+#include <svdata.hxx>
+#include <tools/helpers.hxx>
+#include <basegfx/utils/canvastools.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/range/b2irange.hxx>
+#include <unotools/configmgr.hxx>
+#include <sal/log.hxx>
+#include <osl/module.h>
+
+#if CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 12, 0)
+#error "require at least cairo 1.12.0"
+#endif
+
+void dl_cairo_surface_set_device_scale(cairo_surface_t* surface, double x_scale, double y_scale)
+{
+#if !HAVE_DLAPI
+ cairo_surface_set_device_scale(surface, x_scale, y_scale);
+#else
+ static auto func = reinterpret_cast<void (*)(cairo_surface_t*, double, double)>(
+ osl_getAsciiFunctionSymbol(nullptr, "cairo_surface_set_device_scale"));
+ if (func)
+ func(surface, x_scale, y_scale);
+#endif
+}
+
+void dl_cairo_surface_get_device_scale(cairo_surface_t* surface, double* x_scale, double* y_scale)
+{
+#if !HAVE_DLAPI
+ cairo_surface_get_device_scale(surface, x_scale, y_scale);
+#else
+ static auto func = reinterpret_cast<void (*)(cairo_surface_t*, double*, double*)>(
+ osl_getAsciiFunctionSymbol(nullptr, "cairo_surface_get_device_scale"));
+ if (func)
+ func(surface, x_scale, y_scale);
+ else
+ {
+ if (x_scale)
+ *x_scale = 1.0;
+ if (y_scale)
+ *y_scale = 1.0;
+ }
+#endif
+}
+
+basegfx::B2DRange getFillDamage(cairo_t* cr)
+{
+ double x1, y1, x2, y2;
+
+ // this is faster than cairo_fill_extents, at the cost of some overdraw
+ cairo_path_extents(cr, &x1, &y1, &x2, &y2);
+
+ // support B2DRange::isEmpty()
+ if (0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
+ {
+ return basegfx::B2DRange(x1, y1, x2, y2);
+ }
+
+ return basegfx::B2DRange();
+}
+
+basegfx::B2DRange getClipBox(cairo_t* cr)
+{
+ double x1, y1, x2, y2;
+
+ cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
+
+ // support B2DRange::isEmpty()
+ if (0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
+ {
+ return basegfx::B2DRange(x1, y1, x2, y2);
+ }
+
+ return basegfx::B2DRange();
+}
+
+basegfx::B2DRange getClippedFillDamage(cairo_t* cr)
+{
+ basegfx::B2DRange aDamageRect(getFillDamage(cr));
+ aDamageRect.intersect(getClipBox(cr));
+ return aDamageRect;
+}
+
+basegfx::B2DRange getStrokeDamage(cairo_t* cr)
+{
+ double x1, y1, x2, y2;
+
+ // less accurate, but much faster
+ cairo_path_extents(cr, &x1, &y1, &x2, &y2);
+
+ // support B2DRange::isEmpty()
+ if (0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
+ {
+ return basegfx::B2DRange(x1, y1, x2, y2);
+ }
+
+ return basegfx::B2DRange();
+}
+
+basegfx::B2DRange getClippedStrokeDamage(cairo_t* cr)
+{
+ basegfx::B2DRange aDamageRect(getStrokeDamage(cr));
+ aDamageRect.intersect(getClipBox(cr));
+ return aDamageRect;
+}
+
+// Remove bClosePath: Checked that the already used mechanism for Win using
+// Gdiplus already relies on rPolygon.isClosed(), so should be safe to replace
+// this.
+// For PixelSnap we need the ObjectToDevice transformation here now. This is a
+// special case relative to the also executed LineDraw-Offset of (0.5, 0.5) in
+// DeviceCoordinates: The LineDraw-Offset is applied *after* the snap, so we
+// need the ObjectToDevice transformation *without* that offset here to do the
+// same. The LineDraw-Offset will be applied by the callers using a linear
+// transformation for Cairo now
+// For support of PixelSnapHairline we also need the ObjectToDevice transformation
+// and a method (same as in gdiimpl.cxx for Win and Gdiplus). This is needed e.g.
+// for Chart-content visualization. CAUTION: It's not the same as PixelSnap (!)
+// tdf#129845 add reply value to allow counting a point/byte/size measurement to
+// be included
+size_t AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon,
+ const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap,
+ bool bPixelSnapHairline)
+{
+ // short circuit if there is nothing to do
+ const sal_uInt32 nPointCount(rPolygon.count());
+ size_t nSizeMeasure(0);
+
+ if (0 == nPointCount)
+ {
+ return nSizeMeasure;
+ }
+
+ const bool bHasCurves(rPolygon.areControlPointsUsed());
+ const bool bClosePath(rPolygon.isClosed());
+ const bool bObjectToDeviceUsed(!rObjectToDevice.isIdentity());
+ basegfx::B2DHomMatrix aObjectToDeviceInv;
+ basegfx::B2DPoint aLast;
+ PixelSnapper aSnapper;
+
+ for (sal_uInt32 nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++)
+ {
+ int nClosedIdx = nPointIdx;
+ if (nPointIdx >= nPointCount)
+ {
+ // prepare to close last curve segment if needed
+ if (bClosePath && (nPointIdx == nPointCount))
+ {
+ nClosedIdx = 0;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ basegfx::B2DPoint aPoint(rPolygon.getB2DPoint(nClosedIdx));
+
+ if (bPixelSnap)
+ {
+ // snap device coordinates to full pixels
+ if (bObjectToDeviceUsed)
+ {
+ // go to DeviceCoordinates
+ aPoint *= rObjectToDevice;
+ }
+
+ // snap by rounding
+ aPoint.setX(basegfx::fround(aPoint.getX()));
+ aPoint.setY(basegfx::fround(aPoint.getY()));
+
+ if (bObjectToDeviceUsed)
+ {
+ if (aObjectToDeviceInv.isIdentity())
+ {
+ aObjectToDeviceInv = rObjectToDevice;
+ aObjectToDeviceInv.invert();
+ }
+
+ // go back to ObjectCoordinates
+ aPoint *= aObjectToDeviceInv;
+ }
+ }
+
+ if (bPixelSnapHairline)
+ {
+ // snap horizontal and vertical lines (mainly used in Chart for
+ // 'nicer' AAing)
+ aPoint = aSnapper.snap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nClosedIdx);
+ }
+
+ if (!nPointIdx)
+ {
+ // first point => just move there
+ cairo_move_to(cr, aPoint.getX(), aPoint.getY());
+ aLast = aPoint;
+ continue;
+ }
+
+ bool bPendingCurve(false);
+
+ if (bHasCurves)
+ {
+ bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx);
+ bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx);
+ }
+
+ if (!bPendingCurve) // line segment
+ {
+ cairo_line_to(cr, aPoint.getX(), aPoint.getY());
+ nSizeMeasure++;
+ }
+ else // cubic bezier segment
+ {
+ basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint(nPrevIdx);
+ basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint(nClosedIdx);
+
+ // tdf#99165 if the control points are 'empty', create the mathematical
+ // correct replacement ones to avoid problems with the graphical sub-system
+ // tdf#101026 The 1st attempt to create a mathematically correct replacement control
+ // vector was wrong. Best alternative is one as close as possible which means short.
+ if (aCP1.equal(aLast))
+ {
+ aCP1 = aLast + ((aCP2 - aLast) * 0.0005);
+ }
+
+ if (aCP2.equal(aPoint))
+ {
+ aCP2 = aPoint + ((aCP1 - aPoint) * 0.0005);
+ }
+
+ cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), aPoint.getX(),
+ aPoint.getY());
+ // take some bigger measure for curve segments - too expensive to subdivide
+ // here and that precision not needed, but four (2 points, 2 control-points)
+ // would be a too low weight
+ nSizeMeasure += 10;
+ }
+
+ aLast = aPoint;
+ }
+
+ if (bClosePath)
+ {
+ cairo_close_path(cr);
+ }
+
+ return nSizeMeasure;
+}
+
+basegfx::B2DPoint PixelSnapper::snap(const basegfx::B2DPolygon& rPolygon,
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ basegfx::B2DHomMatrix& rObjectToDeviceInv, sal_uInt32 nIndex)
+{
+ const sal_uInt32 nCount(rPolygon.count());
+
+ // get the data
+ if (nIndex == 0)
+ {
+ // if it's the first time, we need to calculate everything
+ maPrevPoint = rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount);
+ maCurrPoint = rObjectToDevice * rPolygon.getB2DPoint(nIndex);
+ maPrevTuple = basegfx::fround(maPrevPoint);
+ maCurrTuple = basegfx::fround(maCurrPoint);
+ }
+ else
+ {
+ // but for all other times, we can re-use the previous iteration computations
+ maPrevPoint = maCurrPoint;
+ maPrevTuple = maCurrTuple;
+ maCurrPoint = maNextPoint;
+ maCurrTuple = maNextTuple;
+ }
+ maNextPoint = rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount);
+ maNextTuple = basegfx::fround(maNextPoint);
+
+ // get the states
+ const bool bPrevVertical(maPrevTuple.getX() == maCurrTuple.getX());
+ const bool bNextVertical(maNextTuple.getX() == maCurrTuple.getX());
+ const bool bPrevHorizontal(maPrevTuple.getY() == maCurrTuple.getY());
+ const bool bNextHorizontal(maNextTuple.getY() == maCurrTuple.getY());
+ const bool bSnapX(bPrevVertical || bNextVertical);
+ const bool bSnapY(bPrevHorizontal || bNextHorizontal);
+
+ if (bSnapX || bSnapY)
+ {
+ basegfx::B2DPoint aSnappedPoint(bSnapX ? maCurrTuple.getX() : maCurrPoint.getX(),
+ bSnapY ? maCurrTuple.getY() : maCurrPoint.getY());
+
+ if (rObjectToDeviceInv.isIdentity())
+ {
+ rObjectToDeviceInv = rObjectToDevice;
+ rObjectToDeviceInv.invert();
+ }
+
+ aSnappedPoint *= rObjectToDeviceInv;
+
+ return aSnappedPoint;
+ }
+
+ return rPolygon.getB2DPoint(nIndex);
+}
+
+SystemDependentData_CairoPath::SystemDependentData_CairoPath(size_t nSizeMeasure, cairo_t* cr,
+ bool bNoJoin, bool bAntiAlias,
+ const std::vector<double>* pStroke)
+ : basegfx::SystemDependentData(Application::GetSystemDependentDataManager())
+ , mpCairoPath(nullptr)
+ , mbNoJoin(bNoJoin)
+ , mbAntiAlias(bAntiAlias)
+{
+ static const bool bFuzzing = utl::ConfigManager::IsFuzzing();
+
+ // tdf#129845 only create a copy of the path when nSizeMeasure is
+ // bigger than some decent threshold
+ if (!bFuzzing && nSizeMeasure > 50)
+ {
+ mpCairoPath = cairo_copy_path(cr);
+
+ if (nullptr != pStroke)
+ {
+ maStroke = *pStroke;
+ }
+ }
+}
+
+SystemDependentData_CairoPath::~SystemDependentData_CairoPath()
+{
+ if (nullptr != mpCairoPath)
+ {
+ cairo_path_destroy(mpCairoPath);
+ mpCairoPath = nullptr;
+ }
+}
+
+sal_Int64 SystemDependentData_CairoPath::estimateUsageInBytes() const
+{
+ // tdf#129845 by using the default return value of zero when no path
+ // was created, SystemDependentData::calculateCombinedHoldCyclesInSeconds
+ // will do the right thing and not buffer this entry at all
+ sal_Int64 nRetval(0);
+
+ if (nullptr != mpCairoPath)
+ {
+ // per node
+ // - num_data incarnations of
+ // - sizeof(cairo_path_data_t) which is a union of defines and point data
+ // thus may 2 x sizeof(double)
+ nRetval = mpCairoPath->num_data * sizeof(cairo_path_data_t);
+ }
+
+ return nRetval;
+}
+
+void add_polygon_path(cairo_t* cr, const basegfx::B2DPolyPolygon& rPolyPolygon,
+ const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap)
+{
+ // try to access buffered data
+ std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath(
+ rPolyPolygon.getSystemDependentData<SystemDependentData_CairoPath>());
+
+ if (pSystemDependentData_CairoPath)
+ {
+ // re-use data
+ cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath());
+ }
+ else
+ {
+ // create data
+ size_t nSizeMeasure(0);
+
+ for (const auto& rPoly : rPolyPolygon)
+ {
+ // PixelOffset used: Was dependent of 'm_aLineColor != SALCOLOR_NONE'
+ // Adapt setupPolyPolygon-users to set a linear transformation to achieve PixelOffset
+ nSizeMeasure += AddPolygonToPath(cr, rPoly, rObjectToDevice, bPixelSnap, false);
+ }
+
+ // copy and add to buffering mechanism
+ // for decisions how/what to buffer, see Note in WinSalGraphicsImpl::drawPolyPolygon
+ pSystemDependentData_CairoPath
+ = rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>(
+ nSizeMeasure, cr, false, false, nullptr);
+ }
+}
+
+cairo_user_data_key_t* CairoCommon::getDamageKey()
+{
+ static cairo_user_data_key_t aDamageKey;
+ return &aDamageKey;
+}
+
+sal_uInt16 CairoCommon::GetBitCount() const
+{
+ if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_ALPHA)
+ return 1;
+ return 32;
+}
+
+cairo_t* CairoCommon::getCairoContext(bool bXorModeAllowed, bool bAntiAlias) const
+{
+ cairo_t* cr;
+ if (m_ePaintMode == PaintMode::Xor && bXorModeAllowed)
+ cr = createTmpCompatibleCairoContext();
+ else
+ cr = cairo_create(m_pSurface);
+ cairo_set_line_width(cr, 1);
+ cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
+ cairo_set_antialias(cr, bAntiAlias ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
+ cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
+
+ // ensure no linear transformation and no PathInfo in local cairo_path_t
+ cairo_identity_matrix(cr);
+ cairo_new_path(cr);
+
+ return cr;
+}
+
+void CairoCommon::releaseCairoContext(cairo_t* cr, bool bXorModeAllowed,
+ const basegfx::B2DRange& rExtents) const
+{
+ const bool bXoring = (m_ePaintMode == PaintMode::Xor && bXorModeAllowed);
+
+ if (rExtents.isEmpty())
+ {
+ //nothing changed, return early
+ if (bXoring)
+ {
+ cairo_surface_t* surface = cairo_get_target(cr);
+ cairo_surface_destroy(surface);
+ }
+ cairo_destroy(cr);
+ return;
+ }
+
+ basegfx::B2IRange aIntExtents(basegfx::unotools::b2ISurroundingRangeFromB2DRange(rExtents));
+ sal_Int32 nExtentsLeft(aIntExtents.getMinX()), nExtentsTop(aIntExtents.getMinY());
+ sal_Int32 nExtentsRight(aIntExtents.getMaxX()), nExtentsBottom(aIntExtents.getMaxY());
+ sal_Int32 nWidth = m_aFrameSize.getX();
+ sal_Int32 nHeight = m_aFrameSize.getY();
+ nExtentsLeft = std::max<sal_Int32>(nExtentsLeft, 0);
+ nExtentsTop = std::max<sal_Int32>(nExtentsTop, 0);
+ nExtentsRight = std::min<sal_Int32>(nExtentsRight, nWidth);
+ nExtentsBottom = std::min<sal_Int32>(nExtentsBottom, nHeight);
+
+ cairo_surface_t* surface = cairo_get_target(cr);
+ cairo_surface_flush(surface);
+
+ //For the most part we avoid the use of XOR these days, but there
+ //are some edge cases where legacy stuff still supports it, so
+ //emulate it (slowly) here.
+ if (bXoring)
+ doXorOnRelease(nExtentsLeft, nExtentsTop, nExtentsRight, nExtentsBottom, surface, nWidth);
+
+ cairo_destroy(cr); // unref
+
+ DamageHandler* pDamage
+ = static_cast<DamageHandler*>(cairo_surface_get_user_data(m_pSurface, getDamageKey()));
+
+ if (pDamage)
+ {
+ pDamage->damaged(pDamage->handle, nExtentsLeft, nExtentsTop, nExtentsRight - nExtentsLeft,
+ nExtentsBottom - nExtentsTop);
+ }
+}
+
+void CairoCommon::doXorOnRelease(sal_Int32 nExtentsLeft, sal_Int32 nExtentsTop,
+ sal_Int32 nExtentsRight, sal_Int32 nExtentsBottom,
+ cairo_surface_t* const surface, sal_Int32 nWidth) const
+{
+ //For the most part we avoid the use of XOR these days, but there
+ //are some edge cases where legacy stuff still supports it, so
+ //emulate it (slowly) here.
+ cairo_surface_t* target_surface = m_pSurface;
+ if (cairo_surface_get_type(target_surface) != CAIRO_SURFACE_TYPE_IMAGE)
+ {
+ //in the unlikely case we can't use m_pSurface directly, copy contents
+ //to another temp image surface
+ if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_COLOR_ALPHA)
+ target_surface = cairo_surface_map_to_image(target_surface, nullptr);
+ else
+ {
+ // for gen, which is CAIRO_FORMAT_RGB24/CAIRO_CONTENT_COLOR I'm getting
+ // visual corruption in vcldemo with cairo_surface_map_to_image
+ cairo_t* copycr = createTmpCompatibleCairoContext();
+ cairo_rectangle(copycr, nExtentsLeft, nExtentsTop, nExtentsRight - nExtentsLeft,
+ nExtentsBottom - nExtentsTop);
+ cairo_set_source_surface(copycr, m_pSurface, 0, 0);
+ cairo_fill(copycr);
+ target_surface = cairo_get_target(copycr);
+ cairo_destroy(copycr);
+ }
+ }
+
+ cairo_surface_flush(target_surface);
+ unsigned char* target_surface_data = cairo_image_surface_get_data(target_surface);
+ unsigned char* xor_surface_data = cairo_image_surface_get_data(surface);
+
+ cairo_format_t nFormat = cairo_image_surface_get_format(target_surface);
+ assert(nFormat == CAIRO_FORMAT_ARGB32 && "need to implement CAIRO_FORMAT_A1 after all here");
+ sal_Int32 nStride = cairo_format_stride_for_width(nFormat, nWidth * m_fScale);
+ sal_Int32 nUnscaledExtentsLeft = nExtentsLeft * m_fScale;
+ sal_Int32 nUnscaledExtentsRight = nExtentsRight * m_fScale;
+ sal_Int32 nUnscaledExtentsTop = nExtentsTop * m_fScale;
+ sal_Int32 nUnscaledExtentsBottom = nExtentsBottom * m_fScale;
+
+ // Handle headless size forced to (1,1) by SvpSalFrame::GetSurfaceFrameSize().
+ int target_surface_width = cairo_image_surface_get_width(target_surface);
+ if (nUnscaledExtentsLeft > target_surface_width)
+ nUnscaledExtentsLeft = target_surface_width;
+ if (nUnscaledExtentsRight > target_surface_width)
+ nUnscaledExtentsRight = target_surface_width;
+ int target_surface_height = cairo_image_surface_get_height(target_surface);
+ if (nUnscaledExtentsTop > target_surface_height)
+ nUnscaledExtentsTop = target_surface_height;
+ if (nUnscaledExtentsBottom > target_surface_height)
+ nUnscaledExtentsBottom = target_surface_height;
+
+#if !ENABLE_WASM_STRIP_PREMULTIPLY
+ vcl::bitmap::lookup_table const& unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
+ vcl::bitmap::lookup_table const& premultiply_table = vcl::bitmap::get_premultiply_table();
+#endif
+ for (sal_Int32 y = nUnscaledExtentsTop; y < nUnscaledExtentsBottom; ++y)
+ {
+ unsigned char* true_row = target_surface_data + (nStride * y);
+ unsigned char* xor_row = xor_surface_data + (nStride * y);
+ unsigned char* true_data = true_row + (nUnscaledExtentsLeft * 4);
+ unsigned char* xor_data = xor_row + (nUnscaledExtentsLeft * 4);
+ for (sal_Int32 x = nUnscaledExtentsLeft; x < nUnscaledExtentsRight; ++x)
+ {
+ sal_uInt8 a = true_data[SVP_CAIRO_ALPHA];
+ sal_uInt8 xor_a = xor_data[SVP_CAIRO_ALPHA];
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ sal_uInt8 b = vcl::bitmap::unpremultiply(a, true_data[SVP_CAIRO_BLUE])
+ ^ vcl::bitmap::unpremultiply(xor_a, xor_data[SVP_CAIRO_BLUE]);
+ sal_uInt8 g = vcl::bitmap::unpremultiply(a, true_data[SVP_CAIRO_GREEN])
+ ^ vcl::bitmap::unpremultiply(xor_a, xor_data[SVP_CAIRO_GREEN]);
+ sal_uInt8 r = vcl::bitmap::unpremultiply(a, true_data[SVP_CAIRO_RED])
+ ^ vcl::bitmap::unpremultiply(xor_a, xor_data[SVP_CAIRO_RED]);
+ true_data[SVP_CAIRO_BLUE] = vcl::bitmap::premultiply(a, b);
+ true_data[SVP_CAIRO_GREEN] = vcl::bitmap::premultiply(a, g);
+ true_data[SVP_CAIRO_RED] = vcl::bitmap::premultiply(a, r);
+#else
+ sal_uInt8 b = unpremultiply_table[a][true_data[SVP_CAIRO_BLUE]]
+ ^ unpremultiply_table[xor_a][xor_data[SVP_CAIRO_BLUE]];
+ sal_uInt8 g = unpremultiply_table[a][true_data[SVP_CAIRO_GREEN]]
+ ^ unpremultiply_table[xor_a][xor_data[SVP_CAIRO_GREEN]];
+ sal_uInt8 r = unpremultiply_table[a][true_data[SVP_CAIRO_RED]]
+ ^ unpremultiply_table[xor_a][xor_data[SVP_CAIRO_RED]];
+ true_data[SVP_CAIRO_BLUE] = premultiply_table[a][b];
+ true_data[SVP_CAIRO_GREEN] = premultiply_table[a][g];
+ true_data[SVP_CAIRO_RED] = premultiply_table[a][r];
+#endif
+ true_data += 4;
+ xor_data += 4;
+ }
+ }
+ cairo_surface_mark_dirty(target_surface);
+
+ if (target_surface != m_pSurface)
+ {
+ if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_COLOR_ALPHA)
+ cairo_surface_unmap_image(m_pSurface, target_surface);
+ else
+ {
+ cairo_t* copycr = cairo_create(m_pSurface);
+ //copy contents back from image surface
+ cairo_rectangle(copycr, nExtentsLeft, nExtentsTop, nExtentsRight - nExtentsLeft,
+ nExtentsBottom - nExtentsTop);
+ cairo_set_source_surface(copycr, target_surface, 0, 0);
+ cairo_fill(copycr);
+ cairo_destroy(copycr);
+ cairo_surface_destroy(target_surface);
+ }
+ }
+
+ cairo_surface_destroy(surface);
+}
+
+cairo_t* CairoCommon::createTmpCompatibleCairoContext() const
+{
+ cairo_surface_t* target = cairo_surface_create_similar_image(m_pSurface, CAIRO_FORMAT_ARGB32,
+ m_aFrameSize.getX() * m_fScale,
+ m_aFrameSize.getY() * m_fScale);
+
+ dl_cairo_surface_set_device_scale(target, m_fScale, m_fScale);
+
+ return cairo_create(target);
+}
+
+void CairoCommon::applyColor(cairo_t* cr, Color aColor, double fTransparency)
+{
+ if (cairo_surface_get_content(cairo_get_target(cr)) != CAIRO_CONTENT_ALPHA)
+ {
+ cairo_set_source_rgba(cr, aColor.GetRed() / 255.0, aColor.GetGreen() / 255.0,
+ aColor.GetBlue() / 255.0, 1.0 - fTransparency);
+ }
+ else
+ {
+ double fSet = aColor == COL_BLACK ? 1.0 : 0.0;
+ cairo_set_source_rgba(cr, 1, 1, 1, fSet);
+ cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+ }
+}
+
+void CairoCommon::clipRegion(cairo_t* cr, const vcl::Region& rClipRegion)
+{
+ RectangleVector aRectangles;
+ if (!rClipRegion.IsEmpty())
+ {
+ rClipRegion.GetRegionRectangles(aRectangles);
+ }
+ if (!aRectangles.empty())
+ {
+ bool bEmpty = true;
+ for (auto const& rectangle : aRectangles)
+ {
+ if (rectangle.GetWidth() <= 0 || rectangle.GetHeight() <= 0)
+ {
+ SAL_WARN("vcl.gdi", "bad clip rect of: " << rectangle);
+ continue;
+ }
+ cairo_rectangle(cr, rectangle.Left(), rectangle.Top(), rectangle.GetWidth(),
+ rectangle.GetHeight());
+ bEmpty = false;
+ }
+ if (!bEmpty)
+ cairo_clip(cr);
+ }
+}
+
+void CairoCommon::clipRegion(cairo_t* cr) { CairoCommon::clipRegion(cr, m_aClipRegion); }
+
+void CairoCommon::SetXORMode(bool bSet, bool /*bInvertOnly*/)
+{
+ m_ePaintMode = bSet ? PaintMode::Xor : PaintMode::Over;
+}
+
+void CairoCommon::SetROPLineColor(SalROPColor nROPColor)
+{
+ switch (nROPColor)
+ {
+ case SalROPColor::N0:
+ m_oLineColor = Color(0, 0, 0);
+ break;
+ case SalROPColor::N1:
+ m_oLineColor = Color(0xff, 0xff, 0xff);
+ break;
+ case SalROPColor::Invert:
+ m_oLineColor = Color(0xff, 0xff, 0xff);
+ break;
+ }
+}
+
+void CairoCommon::SetROPFillColor(SalROPColor nROPColor)
+{
+ switch (nROPColor)
+ {
+ case SalROPColor::N0:
+ m_oFillColor = Color(0, 0, 0);
+ break;
+ case SalROPColor::N1:
+ m_oFillColor = Color(0xff, 0xff, 0xff);
+ break;
+ case SalROPColor::Invert:
+ m_oFillColor = Color(0xff, 0xff, 0xff);
+ break;
+ }
+}
+
+void CairoCommon::drawPixel(const std::optional<Color>& rLineColor, tools::Long nX, tools::Long nY,
+ bool bAntiAlias)
+{
+ if (!rLineColor)
+ return;
+
+ cairo_t* cr = getCairoContext(true, bAntiAlias);
+ clipRegion(cr);
+
+ cairo_rectangle(cr, nX, nY, 1, 1);
+ CairoCommon::applyColor(cr, *rLineColor, 0.0);
+ cairo_fill(cr);
+
+ basegfx::B2DRange extents = getClippedFillDamage(cr);
+ releaseCairoContext(cr, true, extents);
+}
+
+Color CairoCommon::getPixel(cairo_surface_t* pSurface, tools::Long nX, tools::Long nY)
+{
+ cairo_surface_t* target
+ = cairo_surface_create_similar_image(pSurface, CAIRO_FORMAT_ARGB32, 1, 1);
+
+ cairo_t* cr = cairo_create(target);
+
+ cairo_rectangle(cr, 0, 0, 1, 1);
+ cairo_set_source_surface(cr, pSurface, -nX, -nY);
+ cairo_paint(cr);
+ cairo_destroy(cr);
+
+ cairo_surface_flush(target);
+#if !ENABLE_WASM_STRIP_PREMULTIPLY
+ vcl::bitmap::lookup_table const& unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
+#endif
+ unsigned char* data = cairo_image_surface_get_data(target);
+ sal_uInt8 a = data[SVP_CAIRO_ALPHA];
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ sal_uInt8 b = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_BLUE]);
+ sal_uInt8 g = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_GREEN]);
+ sal_uInt8 r = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_RED]);
+#else
+ sal_uInt8 b = unpremultiply_table[a][data[SVP_CAIRO_BLUE]];
+ sal_uInt8 g = unpremultiply_table[a][data[SVP_CAIRO_GREEN]];
+ sal_uInt8 r = unpremultiply_table[a][data[SVP_CAIRO_RED]];
+#endif
+ Color aColor(ColorAlpha, a, r, g, b);
+ cairo_surface_destroy(target);
+
+ return aColor;
+}
+
+void CairoCommon::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2,
+ bool bAntiAlias)
+{
+ cairo_t* cr = getCairoContext(false, bAntiAlias);
+ clipRegion(cr);
+
+ basegfx::B2DPolygon aPoly;
+
+ // PixelOffset used: To not mix with possible PixelSnap, cannot do
+ // directly on coordinates as tried before - despite being already 'snapped'
+ // due to being integer. If it would be directly added here, it would be
+ // 'snapped' again when !getAntiAlias(), losing the (0.5, 0.5) offset
+ aPoly.append(basegfx::B2DPoint(nX1, nY1));
+ aPoly.append(basegfx::B2DPoint(nX2, nY2));
+
+ // PixelOffset used: Set PixelOffset as linear transformation
+ cairo_matrix_t aMatrix;
+ cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
+ cairo_set_matrix(cr, &aMatrix);
+
+ AddPolygonToPath(cr, aPoly, basegfx::B2DHomMatrix(), !bAntiAlias, false);
+
+ CairoCommon::applyColor(cr, *m_oLineColor);
+
+ basegfx::B2DRange extents = getClippedStrokeDamage(cr);
+ extents.transform(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5));
+
+ cairo_stroke(cr);
+
+ releaseCairoContext(cr, false, extents);
+}
+
+// true if we have a fill color and the line color is the same or non-existent
+static bool onlyFillRect(const std::optional<Color>& rFillColor,
+ const std::optional<Color>& rLineColor)
+{
+ if (!rFillColor)
+ return false;
+ if (!rLineColor)
+ return true;
+ return *rFillColor == *rLineColor;
+}
+
+void CairoCommon::drawRect(double nX, double nY, double nWidth, double nHeight, bool bAntiAlias)
+{
+ // fast path for the common case of simply creating a solid block of color
+ if (onlyFillRect(m_oFillColor, m_oLineColor))
+ {
+ double fTransparency = 0;
+ // don't bother trying to draw stuff which is effectively invisible
+ if (nWidth < 0.1 || nHeight < 0.1)
+ return;
+
+ cairo_t* cr = getCairoContext(true, bAntiAlias);
+ clipRegion(cr);
+
+ bool bPixelSnap = !bAntiAlias;
+ if (bPixelSnap)
+ {
+ // snap by rounding
+ nX = basegfx::fround(nX);
+ nY = basegfx::fround(nY);
+ nWidth = basegfx::fround(nWidth);
+ nHeight = basegfx::fround(nHeight);
+ }
+ cairo_rectangle(cr, nX, nY, nWidth, nHeight);
+
+ CairoCommon::applyColor(cr, *m_oFillColor, fTransparency);
+ // Get FillDamage
+ basegfx::B2DRange extents = getClippedFillDamage(cr);
+
+ cairo_fill(cr);
+
+ releaseCairoContext(cr, true, extents);
+
+ return;
+ }
+ // because of the -1 hack we have to do fill and draw separately
+ std::optional<Color> aOrigFillColor = m_oFillColor;
+ std::optional<Color> aOrigLineColor = m_oLineColor;
+ m_oFillColor = std::nullopt;
+ m_oLineColor = std::nullopt;
+
+ if (aOrigFillColor)
+ {
+ basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(
+ basegfx::B2DRectangle(nX, nY, nX + nWidth, nY + nHeight));
+
+ m_oFillColor = aOrigFillColor;
+ drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aRect), 0.0, bAntiAlias);
+ m_oFillColor = std::nullopt;
+ }
+
+ if (aOrigLineColor)
+ {
+ // need -1 hack to exclude the bottom and right edges to act like wingdi "Rectangle"
+ // function which is what was probably the ultimate origin of this behavior
+ basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(
+ basegfx::B2DRectangle(nX, nY, nX + nWidth - 1, nY + nHeight - 1));
+
+ m_oLineColor = aOrigLineColor;
+ drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aRect), 0.0, bAntiAlias);
+ m_oLineColor = std::nullopt;
+ }
+
+ m_oFillColor = aOrigFillColor;
+ m_oLineColor = aOrigLineColor;
+}
+
+void CairoCommon::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry, bool bAntiAlias)
+{
+ basegfx::B2DPolygon aPoly;
+ aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
+
+ drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aPoly), 0.0, bAntiAlias);
+}
+
+void CairoCommon::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPointCounts,
+ const Point** pPtAry, bool bAntiAlias)
+{
+ basegfx::B2DPolyPolygon aPolyPoly;
+ for (sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
+ {
+ sal_uInt32 nPoints = pPointCounts[nPolygon];
+ if (nPoints)
+ {
+ const Point* pPoints = pPtAry[nPolygon];
+ basegfx::B2DPolygon aPoly;
+ aPoly.append(basegfx::B2DPoint(pPoints->getX(), pPoints->getY()), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPoly.setB2DPoint(i, basegfx::B2DPoint(pPoints[i].getX(), pPoints[i].getY()));
+
+ aPolyPoly.append(aPoly);
+ }
+ }
+
+ drawPolyPolygon(basegfx::B2DHomMatrix(), aPolyPoly, 0.0, bAntiAlias);
+}
+
+void CairoCommon::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon, double fTransparency,
+ bool bAntiAlias)
+{
+ const bool bHasFill(m_oFillColor.has_value());
+ const bool bHasLine(m_oLineColor.has_value());
+
+ if (0 == rPolyPolygon.count() || !(bHasFill || bHasLine) || fTransparency < 0.0
+ || fTransparency >= 1.0)
+ {
+ return;
+ }
+
+ if (!bHasLine)
+ {
+ // don't bother trying to draw stuff which is effectively invisible, speeds up
+ // drawing some complex drawings. This optimisation is not valid when we do
+ // the pixel offset thing (i.e. bHasLine)
+ basegfx::B2DRange aPolygonRange = rPolyPolygon.getB2DRange();
+ aPolygonRange.transform(rObjectToDevice);
+ if (aPolygonRange.getWidth() < 0.1 || aPolygonRange.getHeight() < 0.1)
+ return;
+ }
+
+ cairo_t* cr = getCairoContext(true, bAntiAlias);
+ if (cairo_status(cr) != CAIRO_STATUS_SUCCESS)
+ {
+ SAL_WARN("vcl.gdi",
+ "cannot render to surface: " << cairo_status_to_string(cairo_status(cr)));
+ releaseCairoContext(cr, true, basegfx::B2DRange());
+ return;
+ }
+ clipRegion(cr);
+
+ // Set full (Object-to-Device) transformation - if used
+ if (!rObjectToDevice.isIdentity())
+ {
+ cairo_matrix_t aMatrix;
+
+ cairo_matrix_init(&aMatrix, rObjectToDevice.get(0, 0), rObjectToDevice.get(1, 0),
+ rObjectToDevice.get(0, 1), rObjectToDevice.get(1, 1),
+ rObjectToDevice.get(0, 2), rObjectToDevice.get(1, 2));
+ cairo_set_matrix(cr, &aMatrix);
+ }
+
+ // To make releaseCairoContext work, use empty extents
+ basegfx::B2DRange extents;
+
+ if (bHasFill)
+ {
+ add_polygon_path(cr, rPolyPolygon, rObjectToDevice, !bAntiAlias);
+
+ CairoCommon::applyColor(cr, *m_oFillColor, fTransparency);
+ // Get FillDamage (will be extended for LineDamage below)
+ extents = getClippedFillDamage(cr);
+
+ cairo_fill(cr);
+ }
+
+ if (bHasLine)
+ {
+ // PixelOffset used: Set PixelOffset as linear transformation
+ cairo_matrix_t aMatrix;
+ cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
+ cairo_set_matrix(cr, &aMatrix);
+
+ add_polygon_path(cr, rPolyPolygon, rObjectToDevice, !bAntiAlias);
+
+ CairoCommon::applyColor(cr, *m_oLineColor, fTransparency);
+
+ // expand with possible StrokeDamage
+ basegfx::B2DRange stroke_extents = getClippedStrokeDamage(cr);
+ stroke_extents.transform(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5));
+ extents.expand(stroke_extents);
+
+ cairo_stroke(cr);
+ }
+
+ // if transformation has been applied, transform also extents (ranges)
+ // of damage so they can be correctly redrawn
+ extents.transform(rObjectToDevice);
+ releaseCairoContext(cr, true, extents);
+}
+
+void CairoCommon::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry, bool bAntiAlias)
+{
+ basegfx::B2DPolygon aPoly;
+ aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
+ aPoly.setClosed(false);
+
+ drawPolyLine(basegfx::B2DHomMatrix(), aPoly, 0.0, 1.0, nullptr, basegfx::B2DLineJoin::Miter,
+ css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0) /*default*/, false, bAntiAlias);
+}
+
+bool CairoCommon::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& rPolyLine, double fTransparency,
+ double fLineWidth, const std::vector<double>* pStroke,
+ basegfx::B2DLineJoin eLineJoin, css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle, bool bPixelSnapHairline, bool bAntiAlias)
+{
+ // short circuit if there is nothing to do
+ if (0 == rPolyLine.count() || fTransparency < 0.0 || fTransparency >= 1.0)
+ {
+ return true;
+ }
+
+ static const bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ if (bFuzzing)
+ {
+ const basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyLine));
+ if (aRange.getMaxX() - aRange.getMinX() > 0x10000000
+ || aRange.getMaxY() - aRange.getMinY() > 0x10000000)
+ {
+ SAL_WARN("vcl.gdi", "drawPolyLine, skipping suspicious range of: "
+ << aRange << " for fuzzing performance");
+ return true;
+ }
+ }
+
+ cairo_t* cr = getCairoContext(false, bAntiAlias);
+ clipRegion(cr);
+
+ // need to check/handle LineWidth when ObjectToDevice transformation is used
+ const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity());
+
+ // tdf#124848 calculate-back logical LineWidth for a hairline
+ // since this implementation hands over the transformation to
+ // the graphic sub-system
+ if (fLineWidth == 0)
+ {
+ fLineWidth = 1.0;
+
+ if (!bObjectToDeviceIsIdentity)
+ {
+ basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice);
+ aObjectToDeviceInv.invert();
+ fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(fLineWidth, 0)).getLength();
+ }
+ }
+
+ // PixelOffset used: Need to reflect in linear transformation
+ cairo_matrix_t aMatrix;
+ basegfx::B2DHomMatrix aDamageMatrix(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5));
+
+ if (bObjectToDeviceIsIdentity)
+ {
+ // Set PixelOffset as requested
+ cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
+ }
+ else
+ {
+ // Prepare ObjectToDevice transformation. Take PixelOffset for Lines into
+ // account: Multiply from left to act in DeviceCoordinates
+ aDamageMatrix = aDamageMatrix * rObjectToDevice;
+ cairo_matrix_init(&aMatrix, aDamageMatrix.get(0, 0), aDamageMatrix.get(1, 0),
+ aDamageMatrix.get(0, 1), aDamageMatrix.get(1, 1), aDamageMatrix.get(0, 2),
+ aDamageMatrix.get(1, 2));
+ }
+
+ // set linear transformation
+ cairo_set_matrix(cr, &aMatrix);
+
+ // setup line attributes
+ cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
+ switch (eLineJoin)
+ {
+ case basegfx::B2DLineJoin::Bevel:
+ eCairoLineJoin = CAIRO_LINE_JOIN_BEVEL;
+ break;
+ case basegfx::B2DLineJoin::Round:
+ eCairoLineJoin = CAIRO_LINE_JOIN_ROUND;
+ break;
+ case basegfx::B2DLineJoin::NONE:
+ case basegfx::B2DLineJoin::Miter:
+ eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
+ break;
+ }
+
+ // convert miter minimum angle to miter limit
+ double fMiterLimit = 1.0 / sin(std::max(fMiterMinimumAngle, 0.01 * M_PI) / 2.0);
+
+ // setup cap attribute
+ cairo_line_cap_t eCairoLineCap(CAIRO_LINE_CAP_BUTT);
+
+ switch (eLineCap)
+ {
+ default: // css::drawing::LineCap_BUTT:
+ {
+ eCairoLineCap = CAIRO_LINE_CAP_BUTT;
+ break;
+ }
+ case css::drawing::LineCap_ROUND:
+ {
+ eCairoLineCap = CAIRO_LINE_CAP_ROUND;
+ break;
+ }
+ case css::drawing::LineCap_SQUARE:
+ {
+ eCairoLineCap = CAIRO_LINE_CAP_SQUARE;
+ break;
+ }
+ }
+
+ cairo_set_source_rgba(cr, m_oLineColor->GetRed() / 255.0, m_oLineColor->GetGreen() / 255.0,
+ m_oLineColor->GetBlue() / 255.0, 1.0 - fTransparency);
+
+ cairo_set_line_join(cr, eCairoLineJoin);
+ cairo_set_line_cap(cr, eCairoLineCap);
+
+ constexpr int MaxNormalLineWidth = 64;
+ if (fLineWidth > MaxNormalLineWidth)
+ {
+ const double fLineWidthPixel
+ = bObjectToDeviceIsIdentity
+ ? fLineWidth
+ : (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
+ if (fLineWidthPixel > MaxNormalLineWidth)
+ {
+ SAL_WARN("vcl.gdi", "drawPolyLine, suspicious input line width of: "
+ << fLineWidth << ", will be " << fLineWidthPixel
+ << " pixels thick");
+ if (bFuzzing)
+ {
+ basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice);
+ aObjectToDeviceInv.invert();
+ fLineWidth
+ = (aObjectToDeviceInv * basegfx::B2DVector(MaxNormalLineWidth, 0)).getLength();
+ fLineWidth = std::min(fLineWidth, 2048.0);
+ }
+ }
+ }
+ cairo_set_line_width(cr, fLineWidth);
+ cairo_set_miter_limit(cr, fMiterLimit);
+
+ // try to access buffered data
+ std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath(
+ rPolyLine.getSystemDependentData<SystemDependentData_CairoPath>());
+
+ // MM01 need to do line dashing as fallback stuff here now
+ const double fDotDashLength(
+ nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0);
+ const bool bStrokeUsed(0.0 != fDotDashLength);
+ assert(!bStrokeUsed || (bStrokeUsed && pStroke));
+
+ // MM01 decide if to stroke directly
+ static const bool bDoDirectCairoStroke(true);
+
+ // MM01 activate to stroke directly
+ if (bDoDirectCairoStroke && bStrokeUsed)
+ {
+ cairo_set_dash(cr, pStroke->data(), pStroke->size(), 0.0);
+ }
+
+ if (!bDoDirectCairoStroke && pSystemDependentData_CairoPath)
+ {
+ // MM01 - check on stroke change. Used against not used, or if both used,
+ // equal or different?
+ const bool bStrokeWasUsed(!pSystemDependentData_CairoPath->getStroke().empty());
+
+ if (bStrokeWasUsed != bStrokeUsed
+ || (bStrokeUsed && *pStroke != pSystemDependentData_CairoPath->getStroke()))
+ {
+ // data invalid, forget
+ pSystemDependentData_CairoPath.reset();
+ }
+ }
+
+ // check for basegfx::B2DLineJoin::NONE to react accordingly
+ const bool bNoJoin(
+ (basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(fLineWidth, 0.0)));
+
+ if (pSystemDependentData_CairoPath)
+ {
+ // check data validity
+ if (nullptr == pSystemDependentData_CairoPath->getCairoPath()
+ || pSystemDependentData_CairoPath->getNoJoin() != bNoJoin
+ || pSystemDependentData_CairoPath->getAntiAlias() != bAntiAlias
+ || bPixelSnapHairline /*tdf#124700*/)
+ {
+ // data invalid, forget
+ pSystemDependentData_CairoPath.reset();
+ }
+ }
+
+ if (pSystemDependentData_CairoPath)
+ {
+ // re-use data
+ cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath());
+ }
+ else
+ {
+ // create data
+ size_t nSizeMeasure(0);
+
+ // MM01 need to do line dashing as fallback stuff here now
+ basegfx::B2DPolyPolygon aPolyPolygonLine;
+
+ if (!bDoDirectCairoStroke && bStrokeUsed)
+ {
+ // apply LineStyle
+ basegfx::utils::applyLineDashing(rPolyLine, // source
+ *pStroke, // pattern
+ &aPolyPolygonLine, // target for lines
+ nullptr, // target for gaps
+ fDotDashLength); // full length if available
+ }
+ else
+ {
+ // no line dashing or direct stroke, just copy
+ aPolyPolygonLine.append(rPolyLine);
+ }
+
+ // MM01 checked/verified for Cairo
+ for (sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
+ {
+ const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
+
+ if (!bNoJoin)
+ {
+ // PixelOffset now reflected in linear transformation used
+ nSizeMeasure
+ += AddPolygonToPath(cr, aPolyLine,
+ rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset
+ !bAntiAlias, bPixelSnapHairline);
+ }
+ else
+ {
+ const sal_uInt32 nPointCount(aPolyLine.count());
+ const sal_uInt32 nEdgeCount(aPolyLine.isClosed() ? nPointCount : nPointCount - 1);
+ basegfx::B2DPolygon aEdge;
+
+ aEdge.append(aPolyLine.getB2DPoint(0));
+ aEdge.append(basegfx::B2DPoint(0.0, 0.0));
+
+ for (sal_uInt32 i(0); i < nEdgeCount; i++)
+ {
+ const sal_uInt32 nNextIndex((i + 1) % nPointCount);
+ aEdge.setB2DPoint(1, aPolyLine.getB2DPoint(nNextIndex));
+ aEdge.setNextControlPoint(0, aPolyLine.getNextControlPoint(i));
+ aEdge.setPrevControlPoint(1, aPolyLine.getPrevControlPoint(nNextIndex));
+
+ // PixelOffset now reflected in linear transformation used
+ nSizeMeasure += AddPolygonToPath(
+ cr, aEdge,
+ rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset
+ !bAntiAlias, bPixelSnapHairline);
+
+ // prepare next step
+ aEdge.setB2DPoint(0, aEdge.getB2DPoint(1));
+ }
+ }
+ }
+
+ // copy and add to buffering mechanism
+ if (!bPixelSnapHairline /*tdf#124700*/)
+ {
+ pSystemDependentData_CairoPath
+ = rPolyLine.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>(
+ nSizeMeasure, cr, bNoJoin, bAntiAlias, pStroke);
+ }
+ }
+
+ // extract extents
+ basegfx::B2DRange extents = getClippedStrokeDamage(cr);
+ // transform also extents (ranges) of damage so they can be correctly redrawn
+ extents.transform(aDamageMatrix);
+
+ // draw and consume
+ cairo_stroke(cr);
+
+ releaseCairoContext(cr, false, extents);
+
+ return true;
+}
+
+bool CairoCommon::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, sal_uInt8 nTransparency, bool bAntiAlias)
+{
+ const bool bHasFill(m_oFillColor.has_value());
+ const bool bHasLine(m_oLineColor.has_value());
+
+ if (!bHasFill && !bHasLine)
+ return true;
+
+ cairo_t* cr = getCairoContext(false, bAntiAlias);
+ clipRegion(cr);
+
+ const double fTransparency = nTransparency * (1.0 / 100);
+
+ // To make releaseCairoContext work, use empty extents
+ basegfx::B2DRange extents;
+
+ if (bHasFill)
+ {
+ cairo_rectangle(cr, nX, nY, nWidth, nHeight);
+
+ applyColor(cr, *m_oFillColor, fTransparency);
+
+ // set FillDamage
+ extents = getClippedFillDamage(cr);
+
+ cairo_fill(cr);
+ }
+
+ if (bHasLine)
+ {
+ // PixelOffset used: Set PixelOffset as linear transformation
+ // Note: Was missing here - probably not by purpose (?)
+ cairo_matrix_t aMatrix;
+ cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
+ cairo_set_matrix(cr, &aMatrix);
+
+ cairo_rectangle(cr, nX, nY, nWidth, nHeight);
+
+ applyColor(cr, *m_oLineColor, fTransparency);
+
+ // expand with possible StrokeDamage
+ basegfx::B2DRange stroke_extents = getClippedStrokeDamage(cr);
+ stroke_extents.transform(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5));
+ extents.expand(stroke_extents);
+
+ cairo_stroke(cr);
+ }
+
+ releaseCairoContext(cr, false, extents);
+
+ return true;
+}
+
+bool CairoCommon::drawGradient(const tools::PolyPolygon& rPolyPolygon, const Gradient& rGradient,
+ bool bAntiAlias)
+{
+ if (rGradient.GetStyle() != css::awt::GradientStyle_LINEAR
+ && rGradient.GetStyle() != css::awt::GradientStyle_RADIAL)
+ return false; // unsupported
+ if (rGradient.GetSteps() != 0)
+ return false; // We can't tell cairo how many colors to use in the gradient.
+
+ cairo_t* cr = getCairoContext(true, bAntiAlias);
+ clipRegion(cr);
+
+ tools::Rectangle aInputRect(rPolyPolygon.GetBoundRect());
+ if (rPolyPolygon.IsRect())
+ {
+ // Rect->Polygon conversion loses the right and bottom edge, fix that.
+ aInputRect.AdjustRight(1);
+ aInputRect.AdjustBottom(1);
+ basegfx::B2DHomMatrix rObjectToDevice;
+ AddPolygonToPath(cr, tools::Polygon(aInputRect).getB2DPolygon(), rObjectToDevice,
+ !bAntiAlias, false);
+ }
+ else
+ {
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPolygon.getB2DPolyPolygon());
+ for (auto const& rPolygon : std::as_const(aB2DPolyPolygon))
+ {
+ basegfx::B2DHomMatrix rObjectToDevice;
+ AddPolygonToPath(cr, rPolygon, rObjectToDevice, !bAntiAlias, false);
+ }
+ }
+
+ Gradient aGradient(rGradient);
+
+ tools::Rectangle aBoundRect;
+ Point aCenter;
+
+ aGradient.SetAngle(aGradient.GetAngle() + 2700_deg10);
+ aGradient.GetBoundRect(aInputRect, aBoundRect, aCenter);
+ Color aStartColor = aGradient.GetStartColor();
+ Color aEndColor = aGradient.GetEndColor();
+
+ cairo_pattern_t* pattern;
+ if (rGradient.GetStyle() == css::awt::GradientStyle_LINEAR)
+ {
+ tools::Polygon aPoly(aBoundRect);
+ aPoly.Rotate(aCenter, aGradient.GetAngle() % 3600_deg10);
+ pattern
+ = cairo_pattern_create_linear(aPoly[0].X(), aPoly[0].Y(), aPoly[1].X(), aPoly[1].Y());
+ }
+ else
+ {
+ double radius = std::max(aBoundRect.GetWidth() / 2.0, aBoundRect.GetHeight() / 2.0);
+ // Move the center a bit to the top-left (the default VCL algorithm is a bit off-center that way,
+ // cairo is the opposite way).
+ pattern = cairo_pattern_create_radial(aCenter.X() - 0.5, aCenter.Y() - 0.5, 0,
+ aCenter.X() - 0.5, aCenter.Y() - 0.5, radius);
+ std::swap(aStartColor, aEndColor);
+ }
+
+ cairo_pattern_add_color_stop_rgba(
+ pattern, aGradient.GetBorder() / 100.0,
+ aStartColor.GetRed() * aGradient.GetStartIntensity() / 25500.0,
+ aStartColor.GetGreen() * aGradient.GetStartIntensity() / 25500.0,
+ aStartColor.GetBlue() * aGradient.GetStartIntensity() / 25500.0, 1.0);
+
+ cairo_pattern_add_color_stop_rgba(
+ pattern, 1.0, aEndColor.GetRed() * aGradient.GetEndIntensity() / 25500.0,
+ aEndColor.GetGreen() * aGradient.GetEndIntensity() / 25500.0,
+ aEndColor.GetBlue() * aGradient.GetEndIntensity() / 25500.0, 1.0);
+
+ cairo_set_source(cr, pattern);
+ cairo_pattern_destroy(pattern);
+
+ basegfx::B2DRange extents = getClippedFillDamage(cr);
+ cairo_fill_preserve(cr);
+
+ releaseCairoContext(cr, true, extents);
+
+ return true;
+}
+
+bool CairoCommon::implDrawGradient(basegfx::B2DPolyPolygon const& rPolyPolygon,
+ SalGradient const& rGradient, bool bAntiAlias)
+{
+ cairo_t* cr = getCairoContext(true, bAntiAlias);
+
+ basegfx::B2DHomMatrix rObjectToDevice;
+
+ for (auto const& rPolygon : rPolyPolygon)
+ AddPolygonToPath(cr, rPolygon, rObjectToDevice, !bAntiAlias, false);
+
+ cairo_pattern_t* pattern
+ = cairo_pattern_create_linear(rGradient.maPoint1.getX(), rGradient.maPoint1.getY(),
+ rGradient.maPoint2.getX(), rGradient.maPoint2.getY());
+
+ for (SalGradientStop const& rStop : rGradient.maStops)
+ {
+ double r = rStop.maColor.GetRed() / 255.0;
+ double g = rStop.maColor.GetGreen() / 255.0;
+ double b = rStop.maColor.GetBlue() / 255.0;
+ double a = rStop.maColor.GetAlpha() / 255.0;
+ double offset = rStop.mfOffset;
+
+ cairo_pattern_add_color_stop_rgba(pattern, offset, r, g, b, a);
+ }
+ cairo_set_source(cr, pattern);
+ cairo_pattern_destroy(pattern);
+
+ basegfx::B2DRange extents = getClippedFillDamage(cr);
+
+ cairo_fill_preserve(cr);
+
+ releaseCairoContext(cr, true, extents);
+
+ return true;
+}
+
+namespace
+{
+basegfx::B2DRange renderWithOperator(cairo_t* cr, const SalTwoRect& rTR, cairo_surface_t* source,
+ cairo_operator_t eOperator = CAIRO_OPERATOR_SOURCE)
+{
+ cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
+
+ basegfx::B2DRange extents = getClippedFillDamage(cr);
+
+ cairo_clip(cr);
+
+ cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
+ if (rTR.mnSrcWidth != 0 && rTR.mnSrcHeight != 0)
+ {
+ double fXScale = static_cast<double>(rTR.mnDestWidth) / rTR.mnSrcWidth;
+ double fYScale = static_cast<double>(rTR.mnDestHeight) / rTR.mnSrcHeight;
+ cairo_scale(cr, fXScale, fYScale);
+ }
+
+ cairo_save(cr);
+ cairo_set_source_surface(cr, source, -rTR.mnSrcX, -rTR.mnSrcY);
+
+ if (cairo_status(cr) == CAIRO_STATUS_SUCCESS)
+ {
+ //tdf#133716 borders of upscaled images should not be blurred
+ cairo_pattern_t* sourcepattern = cairo_get_source(cr);
+ cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_PAD);
+ }
+
+ cairo_set_operator(cr, eOperator);
+ cairo_paint(cr);
+ cairo_restore(cr);
+
+ return extents;
+}
+
+} // end anonymous ns
+
+basegfx::B2DRange CairoCommon::renderSource(cairo_t* cr, const SalTwoRect& rTR,
+ cairo_surface_t* source)
+{
+ return renderWithOperator(cr, rTR, source, CAIRO_OPERATOR_SOURCE);
+}
+
+void CairoCommon::copyWithOperator(const SalTwoRect& rTR, cairo_surface_t* source,
+ cairo_operator_t eOp, bool bAntiAlias)
+{
+ cairo_t* cr = getCairoContext(false, bAntiAlias);
+ clipRegion(cr);
+
+ basegfx::B2DRange extents = renderWithOperator(cr, rTR, source, eOp);
+
+ releaseCairoContext(cr, false, extents);
+}
+
+void CairoCommon::copySource(const SalTwoRect& rTR, cairo_surface_t* source, bool bAntiAlias)
+{
+ copyWithOperator(rTR, source, CAIRO_OPERATOR_SOURCE, bAntiAlias);
+}
+
+void CairoCommon::copyBitsCairo(const SalTwoRect& rTR, cairo_surface_t* pSourceSurface,
+ bool bAntiAlias)
+{
+ SalTwoRect aTR(rTR);
+
+ cairo_surface_t* pCopy = nullptr;
+
+ if (pSourceSurface == getSurface())
+ {
+ //self copy is a problem, so dup source in that case
+ pCopy
+ = cairo_surface_create_similar(pSourceSurface, cairo_surface_get_content(getSurface()),
+ aTR.mnSrcWidth * m_fScale, aTR.mnSrcHeight * m_fScale);
+ dl_cairo_surface_set_device_scale(pCopy, m_fScale, m_fScale);
+ cairo_t* cr = cairo_create(pCopy);
+ cairo_set_source_surface(cr, pSourceSurface, -aTR.mnSrcX, -aTR.mnSrcY);
+ cairo_rectangle(cr, 0, 0, aTR.mnSrcWidth, aTR.mnSrcHeight);
+ cairo_fill(cr);
+ cairo_destroy(cr);
+
+ pSourceSurface = pCopy;
+
+ aTR.mnSrcX = 0;
+ aTR.mnSrcY = 0;
+ }
+
+ copySource(aTR, pSourceSurface, bAntiAlias);
+
+ if (pCopy)
+ cairo_surface_destroy(pCopy);
+}
+
+namespace
+{
+cairo_pattern_t* create_stipple()
+{
+ static unsigned char data[16] = { 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
+ 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF };
+ cairo_surface_t* surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_A8, 4, 4, 4);
+ cairo_pattern_t* pattern = cairo_pattern_create_for_surface(surface);
+ cairo_surface_destroy(surface);
+ cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
+ cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST);
+ return pattern;
+}
+} // end anonymous ns
+
+void CairoCommon::invert(const basegfx::B2DPolygon& rPoly, SalInvert nFlags, bool bAntiAlias)
+{
+ cairo_t* cr = getCairoContext(false, bAntiAlias);
+ clipRegion(cr);
+
+ // To make releaseCairoContext work, use empty extents
+ basegfx::B2DRange extents;
+
+ AddPolygonToPath(cr, rPoly, basegfx::B2DHomMatrix(), !bAntiAlias, false);
+
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+
+ cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE);
+
+ if (nFlags & SalInvert::TrackFrame)
+ {
+ cairo_set_line_width(cr, 2.0);
+ const double dashLengths[2] = { 4.0, 4.0 };
+ cairo_set_dash(cr, dashLengths, 2, 0);
+
+ extents = getClippedStrokeDamage(cr);
+ //see tdf#106577 under wayland, some pixel droppings seen, maybe we're
+ //out by one somewhere, or cairo_stroke_extents is confused by
+ //dashes/line width
+ if (!extents.isEmpty())
+ {
+ extents.grow(1);
+ }
+
+ cairo_stroke(cr);
+ }
+ else
+ {
+ extents = getClippedFillDamage(cr);
+
+ cairo_clip(cr);
+
+ if (nFlags & SalInvert::N50)
+ {
+ cairo_pattern_t* pattern = create_stipple();
+ cairo_surface_t* surface = cairo_surface_create_similar(
+ m_pSurface, cairo_surface_get_content(m_pSurface), extents.getWidth() * m_fScale,
+ extents.getHeight() * m_fScale);
+
+ dl_cairo_surface_set_device_scale(surface, m_fScale, m_fScale);
+ cairo_t* stipple_cr = cairo_create(surface);
+ cairo_set_source_rgb(stipple_cr, 1.0, 1.0, 1.0);
+ cairo_mask(stipple_cr, pattern);
+ cairo_pattern_destroy(pattern);
+ cairo_destroy(stipple_cr);
+ cairo_mask_surface(cr, surface, extents.getMinX(), extents.getMinY());
+ cairo_surface_destroy(surface);
+ }
+ else
+ {
+ cairo_paint(cr);
+ }
+ }
+
+ releaseCairoContext(cr, false, extents);
+}
+
+void CairoCommon::invert(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ SalInvert nFlags, bool bAntiAlias)
+{
+ basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(
+ basegfx::B2DRectangle(nX, nY, nX + nWidth, nY + nHeight));
+
+ invert(aRect, nFlags, bAntiAlias);
+}
+
+void CairoCommon::invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags, bool bAntiAlias)
+{
+ basegfx::B2DPolygon aPoly;
+ aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
+ aPoly.setClosed(true);
+
+ invert(aPoly, nFlags, bAntiAlias);
+}
+
+void CairoCommon::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ bool bAntiAlias)
+{
+ // MM02 try to access buffered BitmapHelper
+ std::shared_ptr<BitmapHelper> aSurface;
+ tryToUseSourceBuffer(rSalBitmap, aSurface);
+ cairo_surface_t* source = aSurface->getSurface(rPosAry.mnDestWidth, rPosAry.mnDestHeight);
+
+ if (!source)
+ {
+ SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
+ return;
+ }
+
+#if 0 // LO code is not yet bitmap32-ready.
+ // if m_bSupportsBitmap32 becomes true for Svp revisit this
+ copyWithOperator(rPosAry, source, CAIRO_OPERATOR_OVER, bAntiAlias);
+#else
+ copyWithOperator(rPosAry, source, CAIRO_OPERATOR_SOURCE, bAntiAlias);
+#endif
+}
+
+bool CairoCommon::drawAlphaBitmap(const SalTwoRect& rTR, const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap, bool bAntiAlias)
+{
+ if (rAlphaBitmap.GetBitCount() != 8 && rAlphaBitmap.GetBitCount() != 1)
+ {
+ SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap alpha depth case: "
+ << rAlphaBitmap.GetBitCount());
+ return false;
+ }
+
+ if (!rTR.mnSrcWidth || !rTR.mnSrcHeight)
+ {
+ SAL_WARN("vcl.gdi", "not possible to stretch nothing");
+ return true;
+ }
+
+ // MM02 try to access buffered BitmapHelper
+ std::shared_ptr<BitmapHelper> aSurface;
+ tryToUseSourceBuffer(rSourceBitmap, aSurface);
+ cairo_surface_t* source = aSurface->getSurface(rTR.mnDestWidth, rTR.mnDestHeight);
+
+ if (!source)
+ {
+ SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
+ return false;
+ }
+
+ // MM02 try to access buffered MaskHelper
+ std::shared_ptr<MaskHelper> aMask;
+ tryToUseMaskBuffer(rAlphaBitmap, aMask);
+ cairo_surface_t* mask = aMask->getSurface(rTR.mnDestWidth, rTR.mnDestHeight);
+
+ if (!mask)
+ {
+ SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
+ return false;
+ }
+
+ cairo_t* cr = getCairoContext(false, bAntiAlias);
+ if (cairo_status(cr) != CAIRO_STATUS_SUCCESS)
+ {
+ SAL_WARN("vcl.gdi",
+ "cannot render to surface: " << cairo_status_to_string(cairo_status(cr)));
+ releaseCairoContext(cr, false, basegfx::B2DRange());
+ return true;
+ }
+
+ clipRegion(cr);
+
+ cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
+
+ basegfx::B2DRange extents = getClippedFillDamage(cr);
+
+ cairo_clip(cr);
+
+ cairo_pattern_t* maskpattern = cairo_pattern_create_for_surface(mask);
+ cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
+ double fXScale = static_cast<double>(rTR.mnDestWidth) / rTR.mnSrcWidth;
+ double fYScale = static_cast<double>(rTR.mnDestHeight) / rTR.mnSrcHeight;
+ cairo_scale(cr, fXScale, fYScale);
+ cairo_set_source_surface(cr, source, -rTR.mnSrcX, -rTR.mnSrcY);
+
+ cairo_pattern_t* sourcepattern = cairo_get_source(cr);
+
+ //tdf#133716 borders of upscaled images should not be blurred
+ //tdf#114117 when stretching a single or multi pixel width/height source to fit an area
+ //the image will be extended into that size.
+ cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_PAD);
+ cairo_pattern_set_extend(maskpattern, CAIRO_EXTEND_PAD);
+
+ //this block is just "cairo_mask_surface", but we have to make it explicit
+ //because of the cairo_pattern_set_filter etc we may want applied
+ cairo_matrix_t matrix;
+ cairo_matrix_init_translate(&matrix, rTR.mnSrcX, rTR.mnSrcY);
+ cairo_pattern_set_matrix(maskpattern, &matrix);
+ cairo_mask(cr, maskpattern);
+
+ cairo_pattern_destroy(maskpattern);
+
+ releaseCairoContext(cr, false, extents);
+
+ return true;
+}
+
+bool CairoCommon::drawTransformedBitmap(const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY, const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap, double fAlpha,
+ bool bAntiAlias)
+{
+ if (pAlphaBitmap && pAlphaBitmap->GetBitCount() != 8 && pAlphaBitmap->GetBitCount() != 1)
+ {
+ SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap alpha depth case: "
+ << pAlphaBitmap->GetBitCount());
+ return false;
+ }
+
+ if (fAlpha != 1.0)
+ return false;
+
+ // MM02 try to access buffered BitmapHelper
+ std::shared_ptr<BitmapHelper> aSurface;
+ tryToUseSourceBuffer(rSourceBitmap, aSurface);
+ const tools::Long nDestWidth(basegfx::fround(basegfx::B2DVector(rX - rNull).getLength()));
+ const tools::Long nDestHeight(basegfx::fround(basegfx::B2DVector(rY - rNull).getLength()));
+ cairo_surface_t* source(aSurface->getSurface(nDestWidth, nDestHeight));
+
+ if (!source)
+ {
+ SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case");
+ return false;
+ }
+
+ // MM02 try to access buffered MaskHelper
+ std::shared_ptr<MaskHelper> aMask;
+ if (nullptr != pAlphaBitmap)
+ {
+ tryToUseMaskBuffer(*pAlphaBitmap, aMask);
+ }
+
+ // access cairo_surface_t from MaskHelper
+ cairo_surface_t* mask(nullptr);
+ if (aMask)
+ {
+ mask = aMask->getSurface(nDestWidth, nDestHeight);
+ }
+
+ if (nullptr != pAlphaBitmap && nullptr == mask)
+ {
+ SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case");
+ return false;
+ }
+
+ const Size aSize = rSourceBitmap.GetSize();
+ cairo_t* cr = getCairoContext(false, bAntiAlias);
+ clipRegion(cr);
+
+ // setup the image transformation
+ // using the rNull,rX,rY points as destinations for the (0,0),(0,Width),(Height,0) source points
+ const basegfx::B2DVector aXRel = rX - rNull;
+ const basegfx::B2DVector aYRel = rY - rNull;
+ cairo_matrix_t matrix;
+ cairo_matrix_init(&matrix, aXRel.getX() / aSize.Width(), aXRel.getY() / aSize.Width(),
+ aYRel.getX() / aSize.Height(), aYRel.getY() / aSize.Height(), rNull.getX(),
+ rNull.getY());
+
+ cairo_transform(cr, &matrix);
+
+ cairo_rectangle(cr, 0, 0, aSize.Width(), aSize.Height());
+ basegfx::B2DRange extents = getClippedFillDamage(cr);
+ cairo_clip(cr);
+
+ cairo_set_source_surface(cr, source, 0, 0);
+ if (mask)
+ cairo_mask_surface(cr, mask, 0, 0);
+ else
+ cairo_paint(cr);
+
+ releaseCairoContext(cr, false, extents);
+
+ return true;
+}
+
+void CairoCommon::drawMask(const SalTwoRect& rTR, const SalBitmap& rSalBitmap, Color nMaskColor,
+ bool bAntiAlias)
+{
+ /** creates an image from the given rectangle, replacing all black pixels
+ * with nMaskColor and make all other full transparent */
+ // MM02 here decided *against* using buffered BitmapHelper
+ // because the data gets somehow 'unmuliplied'. This may also be
+ // done just once, but I am not sure if this is safe to do.
+ // So for now dispense re-using data here.
+ BitmapHelper aSurface(rSalBitmap, true); // The mask is argb32
+ if (!aSurface.getSurface())
+ {
+ SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawMask case");
+ return;
+ }
+ sal_Int32 nStride;
+ unsigned char* mask_data = aSurface.getBits(nStride);
+#if !ENABLE_WASM_STRIP_PREMULTIPLY
+ vcl::bitmap::lookup_table const& unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
+#endif
+ for (tools::Long y = rTR.mnSrcY; y < rTR.mnSrcY + rTR.mnSrcHeight; ++y)
+ {
+ unsigned char* row = mask_data + (nStride * y);
+ unsigned char* data = row + (rTR.mnSrcX * 4);
+ for (tools::Long x = rTR.mnSrcX; x < rTR.mnSrcX + rTR.mnSrcWidth; ++x)
+ {
+ sal_uInt8 a = data[SVP_CAIRO_ALPHA];
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ sal_uInt8 b = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_BLUE]);
+ sal_uInt8 g = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_GREEN]);
+ sal_uInt8 r = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_RED]);
+#else
+ sal_uInt8 b = unpremultiply_table[a][data[SVP_CAIRO_BLUE]];
+ sal_uInt8 g = unpremultiply_table[a][data[SVP_CAIRO_GREEN]];
+ sal_uInt8 r = unpremultiply_table[a][data[SVP_CAIRO_RED]];
+#endif
+ if (r == 0 && g == 0 && b == 0)
+ {
+ data[0] = nMaskColor.GetBlue();
+ data[1] = nMaskColor.GetGreen();
+ data[2] = nMaskColor.GetRed();
+ data[3] = 0xff;
+ }
+ else
+ {
+ data[0] = 0;
+ data[1] = 0;
+ data[2] = 0;
+ data[3] = 0;
+ }
+ data += 4;
+ }
+ }
+ aSurface.mark_dirty();
+
+ cairo_t* cr = getCairoContext(false, bAntiAlias);
+ clipRegion(cr);
+
+ cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
+
+ basegfx::B2DRange extents = getClippedFillDamage(cr);
+
+ cairo_clip(cr);
+
+ cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
+ double fXScale = static_cast<double>(rTR.mnDestWidth) / rTR.mnSrcWidth;
+ double fYScale = static_cast<double>(rTR.mnDestHeight) / rTR.mnSrcHeight;
+ cairo_scale(cr, fXScale, fYScale);
+ cairo_set_source_surface(cr, aSurface.getSurface(), -rTR.mnSrcX, -rTR.mnSrcY);
+
+ if (cairo_status(cr) == CAIRO_STATUS_SUCCESS)
+ {
+ //tdf#133716 borders of upscaled images should not be blurred
+ cairo_pattern_t* sourcepattern = cairo_get_source(cr);
+ cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_PAD);
+ }
+
+ cairo_paint(cr);
+
+ releaseCairoContext(cr, false, extents);
+}
+
+std::shared_ptr<SalBitmap> CairoCommon::getBitmap(tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight)
+{
+ std::shared_ptr<SvpSalBitmap> pBitmap = std::make_shared<SvpSalBitmap>();
+ BitmapPalette aPal;
+ assert(GetBitCount() != 1 && "not supported anymore");
+ vcl::PixelFormat ePixelFormat = vcl::PixelFormat::N32_BPP;
+
+ if (!pBitmap->ImplCreate(Size(nWidth, nHeight), ePixelFormat, aPal, false))
+ {
+ SAL_WARN("vcl.gdi", "SvpSalGraphics::getBitmap, cannot create bitmap");
+ return nullptr;
+ }
+
+ cairo_surface_t* target = CairoCommon::createCairoSurface(pBitmap->GetBuffer());
+ if (!target)
+ {
+ SAL_WARN("vcl.gdi", "SvpSalGraphics::getBitmap, cannot create cairo surface");
+ return nullptr;
+ }
+ cairo_t* cr = cairo_create(target);
+
+ SalTwoRect aTR(nX, nY, nWidth, nHeight, 0, 0, nWidth, nHeight);
+ CairoCommon::renderSource(cr, aTR, m_pSurface);
+
+ cairo_destroy(cr);
+ cairo_surface_destroy(target);
+
+ return pBitmap;
+}
+
+cairo_format_t getCairoFormat(const BitmapBuffer& rBuffer)
+{
+ cairo_format_t nFormat;
+#ifdef HAVE_CAIRO_FORMAT_RGB24_888
+ assert(rBuffer.mnBitCount == 32 || rBuffer.mnBitCount == 24 || rBuffer.mnBitCount == 1);
+#else
+ assert(rBuffer.mnBitCount == 32 || rBuffer.mnBitCount == 1);
+#endif
+
+ if (rBuffer.mnBitCount == 32)
+ nFormat = CAIRO_FORMAT_ARGB32;
+#ifdef HAVE_CAIRO_FORMAT_RGB24_888
+ else if (rBuffer.mnBitCount == 24)
+ nFormat = CAIRO_FORMAT_RGB24_888;
+#endif
+ else
+ nFormat = CAIRO_FORMAT_A1;
+ return nFormat;
+}
+
+namespace
+{
+bool isCairoCompatible(const BitmapBuffer* pBuffer)
+{
+ if (!pBuffer)
+ return false;
+
+ // We use Cairo that supports 24-bit RGB.
+#ifdef HAVE_CAIRO_FORMAT_RGB24_888
+ if (pBuffer->mnBitCount != 32 && pBuffer->mnBitCount != 24 && pBuffer->mnBitCount != 1)
+#else
+ if (pBuffer->mnBitCount != 32 && pBuffer->mnBitCount != 1)
+#endif
+ return false;
+
+ cairo_format_t nFormat = getCairoFormat(*pBuffer);
+ return (cairo_format_stride_for_width(nFormat, pBuffer->mnWidth) == pBuffer->mnScanlineSize);
+}
+}
+
+cairo_surface_t* CairoCommon::createCairoSurface(const BitmapBuffer* pBuffer)
+{
+ if (!isCairoCompatible(pBuffer))
+ return nullptr;
+
+ cairo_format_t nFormat = getCairoFormat(*pBuffer);
+ cairo_surface_t* target = cairo_image_surface_create_for_data(
+ pBuffer->mpBits, nFormat, pBuffer->mnWidth, pBuffer->mnHeight, pBuffer->mnScanlineSize);
+ if (cairo_surface_status(target) != CAIRO_STATUS_SUCCESS)
+ {
+ cairo_surface_destroy(target);
+ return nullptr;
+ }
+ return target;
+}
+
+bool CairoCommon::hasFastDrawTransformedBitmap() { return false; }
+
+bool CairoCommon::supportsOperation(OutDevSupportType eType)
+{
+ switch (eType)
+ {
+ case OutDevSupportType::TransparentRect:
+ case OutDevSupportType::TransparentText:
+ return true;
+ }
+ return false;
+}
+
+std::optional<BitmapBuffer> FastConvert24BitRgbTo32BitCairo(const BitmapBuffer* pSrc)
+{
+ if (pSrc == nullptr)
+ return std::nullopt;
+
+ assert(pSrc->mnFormat == SVP_24BIT_FORMAT);
+ const tools::Long nWidth = pSrc->mnWidth;
+ const tools::Long nHeight = pSrc->mnHeight;
+ std::optional<BitmapBuffer> pDst(std::in_place);
+ pDst->mnFormat = (ScanlineFormat::N32BitTcArgb | ScanlineFormat::TopDown);
+ pDst->mnWidth = nWidth;
+ pDst->mnHeight = nHeight;
+ pDst->mnBitCount = 32;
+ pDst->maColorMask = pSrc->maColorMask;
+ pDst->maPalette = pSrc->maPalette;
+
+ tools::Long nScanlineBase;
+ const bool bFail = o3tl::checked_multiply<tools::Long>(pDst->mnBitCount, nWidth, nScanlineBase);
+ if (bFail)
+ {
+ SAL_WARN("vcl.gdi", "checked multiply failed");
+ pDst->mpBits = nullptr;
+ return std::nullopt;
+ }
+
+ pDst->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase);
+ if (pDst->mnScanlineSize < nScanlineBase / 8)
+ {
+ SAL_WARN("vcl.gdi", "scanline calculation wraparound");
+ pDst->mpBits = nullptr;
+ return std::nullopt;
+ }
+
+ try
+ {
+ pDst->mpBits = new sal_uInt8[pDst->mnScanlineSize * nHeight];
+ }
+ catch (const std::bad_alloc&)
+ {
+ // memory exception, clean up
+ pDst->mpBits = nullptr;
+ return std::nullopt;
+ }
+
+ for (tools::Long y = 0; y < nHeight; ++y)
+ {
+ sal_uInt8* pS = pSrc->mpBits + y * pSrc->mnScanlineSize;
+ sal_uInt8* pD = pDst->mpBits + y * pDst->mnScanlineSize;
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+#if ENABLE_CAIRO_RGBA
+ static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown)
+ == ScanlineFormat::N32BitTcRgba,
+ "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra");
+ static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown)
+ == ScanlineFormat::N24BitTcRgb,
+ "Expected SVP_24BIT_FORMAT set to N24BitTcRgb");
+ pD[0] = pS[0];
+ pD[1] = pS[1];
+ pD[2] = pS[2];
+ pD[3] = 0xff; // Alpha
+#elif defined OSL_BIGENDIAN
+ static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown)
+ == ScanlineFormat::N32BitTcArgb,
+ "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra");
+ static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown)
+ == ScanlineFormat::N24BitTcRgb,
+ "Expected SVP_24BIT_FORMAT set to N24BitTcRgb");
+ pD[0] = 0xff; // Alpha
+ pD[1] = pS[0];
+ pD[2] = pS[1];
+ pD[3] = pS[2];
+#else
+ static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown)
+ == ScanlineFormat::N32BitTcBgra,
+ "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra");
+ static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown)
+ == ScanlineFormat::N24BitTcBgr,
+ "Expected SVP_24BIT_FORMAT set to N24BitTcBgr");
+ pD[0] = pS[0];
+ pD[1] = pS[1];
+ pD[2] = pS[2];
+ pD[3] = 0xff; // Alpha
+#endif
+
+ pS += 3;
+ pD += 4;
+ }
+ }
+
+ return pDst;
+}
+
+namespace
+{
+// check for env var that decides for using downscale pattern
+const char* pDisableDownScale(getenv("SAL_DISABLE_CAIRO_DOWNSCALE"));
+bool bDisableDownScale(nullptr != pDisableDownScale);
+}
+
+cairo_surface_t* SurfaceHelper::implCreateOrReuseDownscale(unsigned long nTargetWidth,
+ unsigned long nTargetHeight)
+{
+ const unsigned long nSourceWidth(cairo_image_surface_get_width(pSurface));
+ const unsigned long nSourceHeight(cairo_image_surface_get_height(pSurface));
+
+ // zoomed in, need to stretch at paint, no pre-scale useful
+ if (nTargetWidth >= nSourceWidth || nTargetHeight >= nSourceHeight)
+ {
+ return pSurface;
+ }
+
+ // calculate downscale factor
+ unsigned long nWFactor(1);
+ unsigned long nW((nSourceWidth + 1) / 2);
+ unsigned long nHFactor(1);
+ unsigned long nH((nSourceHeight + 1) / 2);
+
+ while (nW > nTargetWidth && nW > 1)
+ {
+ nW = (nW + 1) / 2;
+ nWFactor *= 2;
+ }
+
+ while (nH > nTargetHeight && nH > 1)
+ {
+ nH = (nH + 1) / 2;
+ nHFactor *= 2;
+ }
+
+ if (1 == nWFactor && 1 == nHFactor)
+ {
+ // original size *is* best binary size, use it
+ return pSurface;
+ }
+
+ // go up one scale again - look for no change
+ nW = (1 == nWFactor) ? nTargetWidth : nW * 2;
+ nH = (1 == nHFactor) ? nTargetHeight : nH * 2;
+
+ // check if we have a downscaled version of required size
+ // bail out if the multiplication for the key would overflow
+ if (nW >= SAL_MAX_UINT32 || nH >= SAL_MAX_UINT32)
+ return pSurface;
+ const sal_uInt64 key((nW * static_cast<sal_uInt64>(SAL_MAX_UINT32)) + nH);
+ auto isHit(maDownscaled.find(key));
+
+ if (isHit != maDownscaled.end())
+ {
+ return isHit->second;
+ }
+
+ // create new surface in the targeted size
+ cairo_surface_t* pSurfaceTarget
+ = cairo_surface_create_similar(pSurface, cairo_surface_get_content(pSurface), nW, nH);
+
+ // made a version to scale self first that worked well, but would've
+ // been hard to support CAIRO_FORMAT_A1 including bit shifting, so
+ // I decided to go with cairo itself - use CAIRO_FILTER_FAST or
+ // CAIRO_FILTER_GOOD though. Please modify as needed for
+ // performance/quality
+ cairo_t* cr = cairo_create(pSurfaceTarget);
+ const double fScaleX(static_cast<double>(nW) / static_cast<double>(nSourceWidth));
+ const double fScaleY(static_cast<double>(nH) / static_cast<double>(nSourceHeight));
+ cairo_scale(cr, fScaleX, fScaleY);
+ cairo_set_source_surface(cr, pSurface, 0.0, 0.0);
+ cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_GOOD);
+ cairo_paint(cr);
+ cairo_destroy(cr);
+
+ // need to set device_scale for downscale surfaces to get
+ // them handled correctly
+ cairo_surface_set_device_scale(pSurfaceTarget, fScaleX, fScaleY);
+
+ // add entry to cached entries
+ maDownscaled[key] = pSurfaceTarget;
+
+ return pSurfaceTarget;
+}
+
+bool SurfaceHelper::isTrivial() const
+{
+ constexpr unsigned long nMinimalSquareSizeToBuffer(64 * 64);
+ const unsigned long nSourceWidth(cairo_image_surface_get_width(pSurface));
+ const unsigned long nSourceHeight(cairo_image_surface_get_height(pSurface));
+
+ return nSourceWidth * nSourceHeight < nMinimalSquareSizeToBuffer;
+}
+
+SurfaceHelper::SurfaceHelper()
+ : pSurface(nullptr)
+{
+}
+
+SurfaceHelper::~SurfaceHelper()
+{
+ cairo_surface_destroy(pSurface);
+ for (auto& candidate : maDownscaled)
+ {
+ cairo_surface_destroy(candidate.second);
+ }
+}
+
+cairo_surface_t* SurfaceHelper::getSurface(unsigned long nTargetWidth,
+ unsigned long nTargetHeight) const
+{
+ if (bDisableDownScale || 0 == nTargetWidth || 0 == nTargetHeight || !pSurface || isTrivial())
+ {
+ // caller asks for original or disabled or trivial (smaller then a minimal square size)
+ // also excludes zero cases for width/height after this point if need to prescale
+ return pSurface;
+ }
+
+ return const_cast<SurfaceHelper*>(this)->implCreateOrReuseDownscale(nTargetWidth,
+ nTargetHeight);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/SvpGraphicsBackend.cxx b/vcl/headless/SvpGraphicsBackend.cxx
new file mode 100644
index 0000000000..e8d582b98f
--- /dev/null
+++ b/vcl/headless/SvpGraphicsBackend.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 <headless/SvpGraphicsBackend.hxx>
+
+#include <sal/log.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <headless/BitmapHelper.hxx>
+
+SvpGraphicsBackend::SvpGraphicsBackend(CairoCommon& rCairoCommon)
+ : m_rCairoCommon(rCairoCommon)
+{
+}
+
+void SvpGraphicsBackend::Init() {}
+
+void SvpGraphicsBackend::freeResources() {}
+
+void SvpGraphicsBackend::setClipRegion(const vcl::Region& i_rClip)
+{
+ m_rCairoCommon.m_aClipRegion = i_rClip;
+}
+
+void SvpGraphicsBackend::ResetClipRegion() { m_rCairoCommon.m_aClipRegion.SetNull(); }
+
+sal_uInt16 SvpGraphicsBackend::GetBitCount() const { return m_rCairoCommon.GetBitCount(); }
+
+tools::Long SvpGraphicsBackend::GetGraphicsWidth() const
+{
+ return m_rCairoCommon.m_pSurface ? m_rCairoCommon.m_aFrameSize.getX() : 0;
+}
+
+void SvpGraphicsBackend::SetLineColor() { m_rCairoCommon.m_oLineColor = std::nullopt; }
+
+void SvpGraphicsBackend::SetLineColor(Color nColor) { m_rCairoCommon.m_oLineColor = nColor; }
+
+void SvpGraphicsBackend::SetFillColor() { m_rCairoCommon.m_oFillColor = std::nullopt; }
+
+void SvpGraphicsBackend::SetFillColor(Color nColor) { m_rCairoCommon.m_oFillColor = nColor; }
+
+void SvpGraphicsBackend::SetXORMode(bool bSet, bool bInvertOnly)
+{
+ m_rCairoCommon.SetXORMode(bSet, bInvertOnly);
+}
+
+void SvpGraphicsBackend::SetROPLineColor(SalROPColor nROPColor)
+{
+ m_rCairoCommon.SetROPLineColor(nROPColor);
+}
+
+void SvpGraphicsBackend::SetROPFillColor(SalROPColor nROPColor)
+{
+ m_rCairoCommon.SetROPFillColor(nROPColor);
+}
+
+void SvpGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY)
+{
+ m_rCairoCommon.drawPixel(m_rCairoCommon.m_oLineColor, nX, nY, getAntiAlias());
+}
+
+void SvpGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY, Color aColor)
+{
+ m_rCairoCommon.drawPixel(aColor, nX, nY, getAntiAlias());
+}
+
+void SvpGraphicsBackend::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2,
+ tools::Long nY2)
+{
+ m_rCairoCommon.drawLine(nX1, nY1, nX2, nY2, getAntiAlias());
+}
+
+void SvpGraphicsBackend::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight)
+{
+ m_rCairoCommon.drawRect(nX, nY, nWidth, nHeight, getAntiAlias());
+}
+
+void SvpGraphicsBackend::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry)
+{
+ m_rCairoCommon.drawPolyLine(nPoints, pPtAry, getAntiAlias());
+}
+
+void SvpGraphicsBackend::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry)
+{
+ m_rCairoCommon.drawPolygon(nPoints, pPtAry, getAntiAlias());
+}
+
+void SvpGraphicsBackend::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPointCounts,
+ const Point** pPtAry)
+{
+ m_rCairoCommon.drawPolyPolygon(nPoly, pPointCounts, pPtAry, getAntiAlias());
+}
+
+void SvpGraphicsBackend::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+ m_rCairoCommon.drawPolyPolygon(rObjectToDevice, rPolyPolygon, fTransparency, getAntiAlias());
+}
+
+bool SvpGraphicsBackend::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& rPolyLine, double fTransparency,
+ double fLineWidth, const std::vector<double>* pStroke,
+ basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
+ bool bPixelSnapHairline)
+{
+ return m_rCairoCommon.drawPolyLine(rObjectToDevice, rPolyLine, fTransparency, fLineWidth,
+ pStroke, eLineJoin, eLineCap, fMiterMinimumAngle,
+ bPixelSnapHairline, getAntiAlias());
+}
+
+bool SvpGraphicsBackend::drawPolyLineBezier(sal_uInt32, const Point*, const PolyFlags*)
+{
+ SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolyLineBezier case");
+ return false;
+}
+
+bool SvpGraphicsBackend::drawPolygonBezier(sal_uInt32, const Point*, const PolyFlags*)
+{
+ SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolygonBezier case");
+ return false;
+}
+
+bool SvpGraphicsBackend::drawPolyPolygonBezier(sal_uInt32, const sal_uInt32*, const Point* const*,
+ const PolyFlags* const*)
+{
+ SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolyPolygonBezier case");
+ return false;
+}
+
+void SvpGraphicsBackend::copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX,
+ tools::Long nSrcY, tools::Long nSrcWidth, tools::Long nSrcHeight,
+ bool /*bWindowInvalidate*/)
+{
+ SalTwoRect aTR(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
+
+ cairo_surface_t* source = m_rCairoCommon.m_pSurface;
+ m_rCairoCommon.copyBitsCairo(aTR, source, getAntiAlias());
+}
+
+void SvpGraphicsBackend::copyBits(const SalTwoRect& rTR, SalGraphics* pSrcGraphics)
+{
+ cairo_surface_t* source = nullptr;
+
+ if (pSrcGraphics)
+ {
+ SvpGraphicsBackend* pSrc = static_cast<SvpGraphicsBackend*>(pSrcGraphics->GetImpl());
+ source = pSrc->m_rCairoCommon.m_pSurface;
+ }
+ else
+ {
+ source = m_rCairoCommon.m_pSurface;
+ }
+
+ m_rCairoCommon.copyBitsCairo(rTR, source, getAntiAlias());
+}
+
+void SvpGraphicsBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
+{
+ m_rCairoCommon.drawBitmap(rPosAry, rSalBitmap, getAntiAlias());
+}
+
+void SvpGraphicsBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransparentBitmap)
+{
+ drawAlphaBitmap(rPosAry, rSalBitmap, rTransparentBitmap);
+}
+
+void SvpGraphicsBackend::drawMask(const SalTwoRect& rTR, const SalBitmap& rSalBitmap,
+ Color nMaskColor)
+{
+ m_rCairoCommon.drawMask(rTR, rSalBitmap, nMaskColor, getAntiAlias());
+}
+
+std::shared_ptr<SalBitmap> SvpGraphicsBackend::getBitmap(tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight)
+{
+ return m_rCairoCommon.getBitmap(nX, nY, nWidth, nHeight);
+}
+
+void SvpGraphicsBackend::drawBitmapBuffer(const SalTwoRect& rTR, const BitmapBuffer* pBuffer,
+ cairo_operator_t eOp)
+{
+ cairo_surface_t* source = CairoCommon::createCairoSurface(pBuffer);
+ m_rCairoCommon.copyWithOperator(rTR, source, eOp, getAntiAlias());
+ cairo_surface_destroy(source);
+}
+
+Color SvpGraphicsBackend::getPixel(tools::Long nX, tools::Long nY)
+{
+ return CairoCommon::getPixel(m_rCairoCommon.m_pSurface, nX, nY);
+}
+
+void SvpGraphicsBackend::invert(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, SalInvert nFlags)
+{
+ m_rCairoCommon.invert(nX, nY, nWidth, nHeight, nFlags, getAntiAlias());
+}
+
+void SvpGraphicsBackend::invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags)
+{
+ m_rCairoCommon.invert(nPoints, pPtAry, nFlags, getAntiAlias());
+}
+
+bool SvpGraphicsBackend::drawEPS(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/,
+ tools::Long /*nHeight*/, void* /*pPtr*/, sal_uInt32 /*nSize*/)
+{
+ return false;
+}
+
+bool SvpGraphicsBackend::blendBitmap(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rBitmap*/)
+{
+ return false;
+}
+
+bool SvpGraphicsBackend::blendAlphaBitmap(const SalTwoRect& /*rPosAry*/,
+ const SalBitmap& /*rSrcBitmap*/,
+ const SalBitmap& /*rMaskBitmap*/,
+ const SalBitmap& /*rAlphaBitmap*/)
+{
+ return false;
+}
+
+bool SvpGraphicsBackend::drawAlphaBitmap(const SalTwoRect& rTR, const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap)
+{
+ return m_rCairoCommon.drawAlphaBitmap(rTR, rSourceBitmap, rAlphaBitmap, getAntiAlias());
+}
+
+bool SvpGraphicsBackend::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap, double fAlpha)
+{
+ return m_rCairoCommon.drawTransformedBitmap(rNull, rX, rY, rSourceBitmap, pAlphaBitmap, fAlpha,
+ getAntiAlias());
+}
+
+bool SvpGraphicsBackend::hasFastDrawTransformedBitmap() const
+{
+ return CairoCommon::hasFastDrawTransformedBitmap();
+}
+
+bool SvpGraphicsBackend::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, sal_uInt8 nTransparency)
+{
+ return m_rCairoCommon.drawAlphaRect(nX, nY, nWidth, nHeight, nTransparency, getAntiAlias());
+}
+
+bool SvpGraphicsBackend::drawGradient(const tools::PolyPolygon& rPolyPolygon,
+ const Gradient& rGradient)
+{
+ return m_rCairoCommon.drawGradient(rPolyPolygon, rGradient, getAntiAlias());
+}
+
+bool SvpGraphicsBackend::implDrawGradient(basegfx::B2DPolyPolygon const& rPolyPolygon,
+ SalGradient const& rGradient)
+{
+ return m_rCairoCommon.implDrawGradient(rPolyPolygon, rGradient, getAntiAlias());
+}
+
+bool SvpGraphicsBackend::supportsOperation(OutDevSupportType eType) const
+{
+ return CairoCommon::supportsOperation(eType);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/headlessinst.cxx b/vcl/headless/headlessinst.cxx
new file mode 100644
index 0000000000..abe3e1cf92
--- /dev/null
+++ b/vcl/headless/headlessinst.cxx
@@ -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/.
+ */
+#include <headless/svpinst.hxx>
+#include <headless/svpdummies.hxx>
+#include <headless/svpdata.hxx>
+#include <unistd.h>
+
+class HeadlessSalInstance : public SvpSalInstance
+{
+public:
+ explicit HeadlessSalInstance(std::unique_ptr<SalYieldMutex> pMutex);
+
+ virtual SalSystem* CreateSalSystem() override;
+};
+
+HeadlessSalInstance::HeadlessSalInstance(std::unique_ptr<SalYieldMutex> pMutex)
+ : SvpSalInstance(std::move(pMutex))
+{
+}
+
+class HeadlessSalSystem : public SvpSalSystem {
+public:
+ HeadlessSalSystem() : SvpSalSystem() {}
+ virtual int ShowNativeDialog( const OUString& rTitle,
+ const OUString& rMessage,
+ const std::vector< OUString >& rButtons ) override
+ {
+ (void)rButtons;
+ SAL_INFO("vcl.headless",
+ "LibreOffice - dialog '"
+ << rTitle << "': '"
+ << rMessage << "'");
+ return 0;
+ }
+};
+
+SalSystem *HeadlessSalInstance::CreateSalSystem()
+{
+ return new HeadlessSalSystem();
+}
+
+extern "C" SalInstance *create_SalInstance()
+{
+ HeadlessSalInstance* pInstance = new HeadlessSalInstance(std::make_unique<SvpSalYieldMutex>());
+ new SvpSalData();
+ return pInstance;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/svpbmp.cxx b/vcl/headless/svpbmp.cxx
new file mode 100644
index 0000000000..178ea129c6
--- /dev/null
+++ b/vcl/headless/svpbmp.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/config.h>
+#include <sal/log.hxx>
+
+#include <cstring>
+
+#include <headless/svpbmp.hxx>
+#include <headless/svpgdi.hxx>
+#include <headless/svpinst.hxx>
+
+#include <basegfx/vector/b2ivector.hxx>
+#include <basegfx/range/b2ibox.hxx>
+#include <o3tl/safeint.hxx>
+#include <tools/helpers.hxx>
+#include <vcl/bitmap.hxx>
+
+using namespace basegfx;
+
+SvpSalBitmap::SvpSalBitmap()
+{
+}
+
+SvpSalBitmap::~SvpSalBitmap()
+{
+ Destroy();
+}
+
+static std::optional<BitmapBuffer> ImplCreateDIB(
+ const Size& rSize,
+ vcl::PixelFormat ePixelFormat,
+ const BitmapPalette& rPal,
+ bool bClear)
+{
+ if (!rSize.Width() || !rSize.Height())
+ return std::nullopt;
+
+ std::optional<BitmapBuffer> pDIB(std::in_place);
+
+ switch (ePixelFormat)
+ {
+ case vcl::PixelFormat::N8_BPP:
+ pDIB->mnFormat = ScanlineFormat::N8BitPal;
+ break;
+ case vcl::PixelFormat::N24_BPP:
+ pDIB->mnFormat = SVP_24BIT_FORMAT;
+ break;
+ case vcl::PixelFormat::N32_BPP:
+ pDIB->mnFormat = SVP_CAIRO_FORMAT;
+ break;
+ case vcl::PixelFormat::INVALID:
+ assert(false);
+ pDIB->mnFormat = SVP_CAIRO_FORMAT;
+ break;
+ }
+
+ sal_uInt16 nColors = 0;
+ if (ePixelFormat <= vcl::PixelFormat::N8_BPP)
+ nColors = vcl::numberOfColors(ePixelFormat);
+
+ pDIB->mnFormat |= ScanlineFormat::TopDown;
+ pDIB->mnWidth = rSize.Width();
+ pDIB->mnHeight = rSize.Height();
+ tools::Long nScanlineBase;
+ bool bFail = o3tl::checked_multiply<tools::Long>(pDIB->mnWidth, vcl::pixelFormatBitCount(ePixelFormat), nScanlineBase);
+ if (bFail)
+ {
+ SAL_WARN("vcl.gdi", "checked multiply failed");
+ return std::nullopt;
+ }
+ pDIB->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase);
+ if (pDIB->mnScanlineSize < nScanlineBase/8)
+ {
+ SAL_WARN("vcl.gdi", "scanline calculation wraparound");
+ return std::nullopt;
+ }
+ pDIB->mnBitCount = vcl::pixelFormatBitCount(ePixelFormat);
+
+ if (nColors)
+ {
+ pDIB->maPalette = rPal;
+ pDIB->maPalette.SetEntryCount( nColors );
+ }
+
+ size_t size;
+ bFail = o3tl::checked_multiply<size_t>(pDIB->mnHeight, pDIB->mnScanlineSize, size);
+ SAL_WARN_IF(bFail, "vcl.gdi", "checked multiply failed");
+ if (bFail || size > SAL_MAX_INT32/2)
+ {
+ return std::nullopt;
+ }
+
+ try
+ {
+ pDIB->mpBits = new sal_uInt8[size];
+#ifdef __SANITIZE_ADDRESS__
+ if (!pDIB->mpBits)
+ { // can only happen with ASAN allocator_may_return_null=1
+ pDIB.reset();
+ }
+ else
+#endif
+ if (bClear)
+ {
+ std::memset(pDIB->mpBits, 0, size);
+ }
+ }
+ catch (const std::bad_alloc&)
+ {
+ pDIB.reset();
+ }
+
+ return pDIB;
+}
+
+void SvpSalBitmap::Create(const std::optional<BitmapBuffer>& pBuf)
+{
+ Destroy();
+ moDIB = pBuf;
+}
+
+bool SvpSalBitmap::ImplCreate(const Size& rSize, vcl::PixelFormat ePixelFormat,
+ const BitmapPalette& rPal, bool bClear)
+{
+ Destroy();
+ moDIB = ImplCreateDIB(rSize, ePixelFormat, rPal, bClear);
+ return moDIB.has_value();
+}
+
+bool SvpSalBitmap::Create(const Size& rSize, vcl::PixelFormat ePixelFormat, const BitmapPalette& rPal)
+{
+ return ImplCreate(rSize, ePixelFormat, rPal, true);
+}
+
+bool SvpSalBitmap::Create(const SalBitmap& rBmp)
+{
+ Destroy();
+
+ const SvpSalBitmap& rSalBmp = static_cast<const SvpSalBitmap&>(rBmp);
+
+ if (rSalBmp.moDIB)
+ {
+ // TODO: reference counting...
+ moDIB.emplace( *rSalBmp.moDIB );
+
+ const size_t size = moDIB->mnScanlineSize * moDIB->mnHeight;
+ if (size > SAL_MAX_INT32/2)
+ {
+ moDIB.reset();
+ return false;
+ }
+
+ // TODO: get rid of this when BitmapBuffer gets copy constructor
+ try
+ {
+ moDIB->mpBits = new sal_uInt8[size];
+ std::memcpy(moDIB->mpBits, rSalBmp.moDIB->mpBits, size);
+ }
+ catch (const std::bad_alloc&)
+ {
+ moDIB.reset();
+ }
+ }
+
+ return !rSalBmp.moDIB.has_value() || moDIB.has_value();
+}
+
+bool SvpSalBitmap::Create( const SalBitmap& /*rSalBmp*/,
+ SalGraphics* /*pGraphics*/ )
+{
+ return false;
+}
+
+bool SvpSalBitmap::Create(const SalBitmap& /*rSalBmp*/,
+ vcl::PixelFormat /*eNewPixelFormat*/)
+{
+ return false;
+}
+
+bool SvpSalBitmap::Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& /*xBitmapCanvas*/, Size& /*rSize*/, bool /*bMask*/ )
+{
+ return false;
+}
+
+void SvpSalBitmap::Destroy()
+{
+ if (moDIB.has_value())
+ {
+ delete[] moDIB->mpBits;
+ moDIB.reset();
+ }
+}
+
+Size SvpSalBitmap::GetSize() const
+{
+ Size aSize;
+
+ if (moDIB.has_value())
+ {
+ aSize.setWidth( moDIB->mnWidth );
+ aSize.setHeight( moDIB->mnHeight );
+ }
+
+ return aSize;
+}
+
+sal_uInt16 SvpSalBitmap::GetBitCount() const
+{
+ sal_uInt16 nBitCount;
+
+ if (moDIB.has_value())
+ nBitCount = moDIB->mnBitCount;
+ else
+ nBitCount = 0;
+
+ return nBitCount;
+}
+
+BitmapBuffer* SvpSalBitmap::AcquireBuffer(BitmapAccessMode)
+{
+ return moDIB ? &*moDIB : nullptr;
+}
+
+void SvpSalBitmap::ReleaseBuffer(BitmapBuffer*, BitmapAccessMode nMode)
+{
+ if( nMode == BitmapAccessMode::Write )
+ InvalidateChecksum();
+}
+
+bool SvpSalBitmap::GetSystemData( BitmapSystemData& )
+{
+ return false;
+}
+
+bool SvpSalBitmap::ScalingSupported() const
+{
+ return false;
+}
+
+bool SvpSalBitmap::Scale( const double& /*rScaleX*/, const double& /*rScaleY*/, BmpScaleFlag /*nScaleFlag*/ )
+{
+ return false;
+}
+
+bool SvpSalBitmap::Replace( const ::Color& /*rSearchColor*/, const ::Color& /*rReplaceColor*/, sal_uInt8 /*nTol*/ )
+{
+ return false;
+}
+
+const basegfx::SystemDependentDataHolder* SvpSalBitmap::accessSystemDependentDataHolder() const
+{
+ return this;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/svpdummies.cxx b/vcl/headless/svpdummies.cxx
new file mode 100644
index 0000000000..944d494976
--- /dev/null
+++ b/vcl/headless/svpdummies.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 <rtl/ustrbuf.hxx>
+#include <headless/svpdummies.hxx>
+#include <headless/svpinst.hxx>
+
+SvpSalObject::~SvpSalObject()
+{
+}
+
+void SvpSalObject::ResetClipRegion() {}
+void SvpSalObject::BeginSetClipRegion( sal_uInt32 ) {}
+void SvpSalObject::UnionClipRegion( tools::Long, tools::Long, tools::Long, tools::Long ) {}
+void SvpSalObject::EndSetClipRegion() {}
+void SvpSalObject::SetPosSize( tools::Long, tools::Long, tools::Long, tools::Long ) {}
+void SvpSalObject::Show( bool ) {}
+const SystemEnvData* SvpSalObject::GetSystemData() const { return &m_aSystemChildData; }
+
+// SalSystem
+SvpSalSystem::~SvpSalSystem() {}
+
+unsigned int SvpSalSystem::GetDisplayScreenCount()
+{
+ return 1;
+}
+
+AbsoluteScreenPixelRectangle SvpSalSystem::GetDisplayScreenPosSizePixel( unsigned int nScreen )
+{
+ AbsoluteScreenPixelRectangle aRect;
+ if( nScreen == 0 )
+ aRect = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint(0,0), AbsoluteScreenPixelSize(VIRTUAL_DESKTOP_WIDTH,VIRTUAL_DESKTOP_HEIGHT) );
+ return aRect;
+}
+
+int SvpSalSystem::ShowNativeDialog( const OUString&, const OUString&,
+ const std::vector< OUString >& )
+{
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/svpframe.cxx b/vcl/headless/svpframe.cxx
new file mode 100644
index 0000000000..e0971f85b9
--- /dev/null
+++ b/vcl/headless/svpframe.cxx
@@ -0,0 +1,502 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <o3tl/safeint.hxx>
+#include <vcl/syswin.hxx>
+#include <sal/log.hxx>
+
+#include <headless/svpframe.hxx>
+#include <headless/svpinst.hxx>
+#ifndef IOS
+#include <headless/svpgdi.hxx>
+#endif
+#include <salsys.hxx>
+
+#include <basegfx/vector/b2ivector.hxx>
+
+#ifndef IOS
+#include <cairo.h>
+#endif
+
+SvpSalFrame* SvpSalFrame::s_pFocusFrame = nullptr;
+
+#ifdef IOS
+#define SvpSalGraphics AquaSalGraphics
+#endif
+
+SvpSalFrame::SvpSalFrame( SvpSalInstance* pInstance,
+ SalFrame* pParent,
+ SalFrameStyleFlags nSalFrameStyle ) :
+ m_pInstance( pInstance ),
+ m_pParent( static_cast<SvpSalFrame*>(pParent) ),
+ m_nStyle( nSalFrameStyle ),
+ m_bVisible( false ),
+#ifndef IOS
+ m_pSurface( nullptr ),
+#endif
+ m_nMinWidth( 0 ),
+ m_nMinHeight( 0 ),
+ m_nMaxWidth( 0 ),
+ m_nMaxHeight( 0 )
+{
+#ifdef IOS
+ // Nothing
+#elif defined ANDROID
+ // Nothing
+#else
+ m_aSystemChildData.pSalFrame = this;
+#endif
+
+ if( m_pParent )
+ m_pParent->m_aChildren.push_back( this );
+
+ if( m_pInstance )
+ m_pInstance->registerFrame( this );
+
+ SetPosSize( 0, 0, 800, 600, SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
+}
+
+SvpSalFrame::~SvpSalFrame()
+{
+ if( m_pInstance )
+ m_pInstance->deregisterFrame( this );
+
+ std::vector<SvpSalFrame*> Children = m_aChildren;
+ for( auto& rChild : Children )
+ rChild->SetParent( m_pParent );
+ if( m_pParent )
+ std::erase(m_pParent->m_aChildren, this);
+
+ if( s_pFocusFrame == this )
+ {
+ s_pFocusFrame = nullptr;
+ // call directly here, else an event for a destroyed frame would be dispatched
+ CallCallback( SalEvent::LoseFocus, nullptr );
+ // if the handler has not set a new focus frame
+ // pass focus to another frame, preferably a document style window
+ if( s_pFocusFrame == nullptr )
+ {
+ for (auto pSalFrame : m_pInstance->getFrames() )
+ {
+ SvpSalFrame* pFrame = static_cast<SvpSalFrame*>( pSalFrame );
+ if( pFrame->m_bVisible &&
+ pFrame->m_pParent == nullptr &&
+ (pFrame->m_nStyle & (SalFrameStyleFlags::MOVEABLE |
+ SalFrameStyleFlags::SIZEABLE |
+ SalFrameStyleFlags::CLOSEABLE) )
+ )
+ {
+ pFrame->GetFocus();
+ break;
+ }
+ }
+ }
+ }
+#ifndef IOS
+ if (m_pSurface)
+ cairo_surface_destroy(m_pSurface);
+#endif
+}
+
+void SvpSalFrame::GetFocus()
+{
+ if (m_nStyle == SalFrameStyleFlags::NONE)
+ return;
+ if( s_pFocusFrame == this )
+ return;
+ // FIXME: return if !m_bVisible
+ // That's IMHO why CppunitTest_sd_tiledrendering crashes non-headless
+
+ if( (m_nStyle & (SalFrameStyleFlags::OWNERDRAWDECORATION | SalFrameStyleFlags::FLOAT)) == SalFrameStyleFlags::NONE )
+ {
+ if( s_pFocusFrame )
+ s_pFocusFrame->LoseFocus();
+ s_pFocusFrame = this;
+ m_pInstance->PostEvent( this, nullptr, SalEvent::GetFocus );
+ }
+}
+
+void SvpSalFrame::LoseFocus()
+{
+ if( s_pFocusFrame == this )
+ {
+ m_pInstance->PostEvent( this, nullptr, SalEvent::LoseFocus );
+ s_pFocusFrame = nullptr;
+ }
+}
+
+basegfx::B2IVector SvpSalFrame::GetSurfaceFrameSize() const
+{
+ basegfx::B2IVector aFrameSize( maGeometry.width(), maGeometry.height() );
+ if( aFrameSize.getX() == 0 )
+ aFrameSize.setX( 1 );
+ if( aFrameSize.getY() == 0 )
+ aFrameSize.setY( 1 );
+ // Creating backing surfaces for invisible windows costs a big chunk of RAM.
+ if (Application::IsHeadlessModeEnabled())
+ aFrameSize = basegfx::B2IVector( 1, 1 );
+ return aFrameSize;
+}
+
+SalGraphics* SvpSalFrame::AcquireGraphics()
+{
+ SvpSalGraphics* pGraphics = new SvpSalGraphics();
+#ifndef IOS
+ pGraphics->setSurface(m_pSurface, GetSurfaceFrameSize());
+#endif
+ m_aGraphics.push_back( pGraphics );
+ return pGraphics;
+}
+
+void SvpSalFrame::ReleaseGraphics( SalGraphics* pGraphics )
+{
+ SvpSalGraphics* pSvpGraphics = dynamic_cast<SvpSalGraphics*>(pGraphics);
+ std::erase(m_aGraphics, pSvpGraphics);
+ delete pSvpGraphics;
+}
+
+bool SvpSalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
+{
+ m_pInstance->PostEvent( this, pData.release(), SalEvent::UserEvent );
+ return true;
+}
+
+void SvpSalFrame::PostPaint() const
+{
+ if( m_bVisible )
+ {
+ SalPaintEvent aPEvt(0, 0, maGeometry.width(), maGeometry.height());
+ aPEvt.mbImmediateUpdate = false;
+ CallCallback( SalEvent::Paint, &aPEvt );
+ }
+}
+
+void SvpSalFrame::SetTitle(const OUString& sTitle)
+{
+ m_sTitle = sTitle;
+}
+
+void SvpSalFrame::SetIcon( sal_uInt16 )
+{
+}
+
+void SvpSalFrame::SetMenu( SalMenu* )
+{
+}
+
+void SvpSalFrame::SetExtendedFrameStyle( SalExtStyle )
+{
+}
+
+void SvpSalFrame::Show( bool bVisible, bool bNoActivate )
+{
+ if (m_nStyle == SalFrameStyleFlags::NONE)
+ return;
+ if (bVisible == m_bVisible)
+ {
+ if (m_bVisible && !bNoActivate)
+ GetFocus();
+ return;
+ }
+
+ if (bVisible)
+ {
+ m_bVisible = true;
+ m_pInstance->PostEvent( this, nullptr, SalEvent::Resize );
+ if( ! bNoActivate )
+ GetFocus();
+ }
+ else
+ {
+ m_bVisible = false;
+ LoseFocus();
+ }
+}
+
+void SvpSalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight )
+{
+ m_nMinWidth = nWidth;
+ m_nMinHeight = nHeight;
+}
+
+void SvpSalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight )
+{
+ m_nMaxWidth = nWidth;
+ m_nMaxHeight = nHeight;
+}
+
+void SvpSalFrame::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags )
+{
+ if( (nFlags & SAL_FRAME_POSSIZE_X) != 0 )
+ maGeometry.setX(nX);
+ if( (nFlags & SAL_FRAME_POSSIZE_Y) != 0 )
+ maGeometry.setY(nY);
+ if( (nFlags & SAL_FRAME_POSSIZE_WIDTH) != 0 )
+ {
+ maGeometry.setWidth(nWidth);
+ if (m_nMaxWidth > 0 && maGeometry.width() > m_nMaxWidth)
+ maGeometry.setWidth(m_nMaxWidth);
+ if (m_nMinWidth > 0 && maGeometry.width() < m_nMinWidth)
+ maGeometry.setWidth(m_nMinWidth);
+ }
+ if( (nFlags & SAL_FRAME_POSSIZE_HEIGHT) != 0 )
+ {
+ maGeometry.setHeight(nHeight);
+ if (m_nMaxHeight > 0 && maGeometry.height() > m_nMaxHeight)
+ maGeometry.setHeight(m_nMaxHeight);
+ if (m_nMinHeight > 0 && maGeometry.height() < m_nMinHeight)
+ maGeometry.setHeight(m_nMinHeight);
+ }
+#ifndef IOS
+ basegfx::B2IVector aFrameSize = GetSurfaceFrameSize();
+ if (!m_pSurface || cairo_image_surface_get_width(m_pSurface) != aFrameSize.getX() ||
+ cairo_image_surface_get_height(m_pSurface) != aFrameSize.getY() )
+ {
+ if (m_pSurface)
+ cairo_surface_destroy(m_pSurface);
+
+ m_pSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ aFrameSize.getX(),
+ aFrameSize.getY());
+
+ // update device in existing graphics
+ for (auto const& graphic : m_aGraphics)
+ {
+ graphic->setSurface(m_pSurface, aFrameSize);
+ }
+ }
+ if( m_bVisible )
+ m_pInstance->PostEvent( this, nullptr, SalEvent::Resize );
+#endif
+}
+
+void SvpSalFrame::GetClientSize( tools::Long& rWidth, tools::Long& rHeight )
+{
+ rWidth = maGeometry.width();
+ rHeight = maGeometry.height();
+}
+
+void SvpSalFrame::GetWorkArea( AbsoluteScreenPixelRectangle& rRect )
+{
+ rRect = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( 0, 0 ),
+ AbsoluteScreenPixelSize( VIRTUAL_DESKTOP_WIDTH, VIRTUAL_DESKTOP_HEIGHT ) );
+}
+
+SalFrame* SvpSalFrame::GetParent() const
+{
+ return m_pParent;
+}
+
+void SvpSalFrame::SetWindowState(const vcl::WindowData *pState)
+{
+ if (pState == nullptr)
+ return;
+
+ // Request for position or size change
+ if (!(pState->mask() & vcl::WindowDataMask::PosSize))
+ return;
+
+ tools::Long nX = maGeometry.x();
+ tools::Long nY = maGeometry.y();
+ tools::Long nWidth = maGeometry.width();
+ tools::Long nHeight = maGeometry.height();
+
+ // change requested properties
+ if (pState->mask() & vcl::WindowDataMask::X)
+ nX = pState->x();
+ if (pState->mask() & vcl::WindowDataMask::Y)
+ nY = pState->y();
+ if (pState->mask() & vcl::WindowDataMask::Width)
+ nWidth = pState->width();
+ if (pState->mask() & vcl::WindowDataMask::Height)
+ nHeight = pState->height();
+
+ SetPosSize( nX, nY, nWidth, nHeight,
+ SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y |
+ SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
+}
+
+bool SvpSalFrame::GetWindowState(vcl::WindowData* pState)
+{
+ pState->setPosSize(maGeometry.posSize());
+ pState->setState(vcl::WindowState::Normal);
+ pState->setMask(vcl::WindowDataMask::PosSizeState);
+ return true;
+}
+
+void SvpSalFrame::ShowFullScreen( bool, sal_Int32 )
+{
+ SetPosSize( 0, 0, VIRTUAL_DESKTOP_WIDTH, VIRTUAL_DESKTOP_HEIGHT,
+ SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
+}
+
+void SvpSalFrame::StartPresentation( bool )
+{
+}
+
+void SvpSalFrame::SetAlwaysOnTop( bool )
+{
+}
+
+void SvpSalFrame::ToTop(SalFrameToTop nFlags)
+{
+ if (m_nStyle == SalFrameStyleFlags::NONE)
+ return;
+ if (nFlags & SalFrameToTop::RestoreWhenMin)
+ Show(true, false);
+ else
+ GetFocus();
+}
+
+void SvpSalFrame::SetPointer( PointerStyle )
+{
+}
+
+void SvpSalFrame::CaptureMouse( bool )
+{
+}
+
+void SvpSalFrame::SetPointerPos( tools::Long, tools::Long )
+{
+}
+
+void SvpSalFrame::Flush()
+{
+}
+
+void SvpSalFrame::SetInputContext( SalInputContext* )
+{
+}
+
+void SvpSalFrame::EndExtTextInput( EndExtTextInputFlags )
+{
+}
+
+OUString SvpSalFrame::GetKeyName( sal_uInt16 )
+{
+ return OUString();
+}
+
+bool SvpSalFrame::MapUnicodeToKeyCode( sal_Unicode, LanguageType, vcl::KeyCode& )
+{
+ return false;
+}
+
+LanguageType SvpSalFrame::GetInputLanguage()
+{
+ return LANGUAGE_DONTKNOW;
+}
+
+void SvpSalFrame::UpdateSettings( AllSettings& rSettings )
+{
+ StyleSettings aStyleSettings = rSettings.GetStyleSettings();
+
+ Color aBackgroundColor( 0xef, 0xef, 0xef );
+ aStyleSettings.BatchSetBackgrounds( aBackgroundColor, false );
+ aStyleSettings.SetMenuColor( aBackgroundColor );
+ aStyleSettings.SetMenuBarColor( aBackgroundColor );
+
+ if (comphelper::LibreOfficeKit::isActive()) // TODO: remove this.
+ {
+ vcl::Font aStdFont( FAMILY_SWISS, Size( 0, 14 ) );
+ aStdFont.SetCharSet( osl_getThreadTextEncoding() );
+ aStdFont.SetWeight( WEIGHT_NORMAL );
+ aStdFont.SetFamilyName( "Liberation Sans" );
+ aStyleSettings.BatchSetFonts( aStdFont, aStdFont );
+
+ aStdFont.SetFontSize(Size(0, 12));
+ aStyleSettings.SetMenuFont(aStdFont);
+
+ SvpSalGraphics* pGraphics = m_aGraphics.empty() ? nullptr : m_aGraphics.back();
+ bool bFreeGraphics = false;
+ if (!pGraphics)
+ {
+ pGraphics = dynamic_cast<SvpSalGraphics*>(AcquireGraphics());
+ if (!pGraphics)
+ {
+ SAL_WARN("vcl.gtk3", "Could not get graphics - unable to update settings");
+ return;
+ }
+ bFreeGraphics = true;
+ }
+ rSettings.SetStyleSettings(aStyleSettings);
+#ifndef IOS // For now...
+ pGraphics->UpdateSettings(rSettings);
+#endif
+ if (bFreeGraphics)
+ ReleaseGraphics(pGraphics);
+ }
+ else
+ rSettings.SetStyleSettings(aStyleSettings);
+}
+
+void SvpSalFrame::Beep()
+{
+}
+
+const SystemEnvData* SvpSalFrame::GetSystemData() const
+{
+ return &m_aSystemChildData;
+}
+
+SalFrame::SalPointerState SvpSalFrame::GetPointerState()
+{
+ SalPointerState aState;
+ aState.mnState = 0;
+ return aState;
+}
+
+KeyIndicatorState SvpSalFrame::GetIndicatorState()
+{
+ return KeyIndicatorState::NONE;
+}
+
+void SvpSalFrame::SimulateKeyPress( sal_uInt16 /*nKeyCode*/ )
+{
+}
+
+void SvpSalFrame::SetParent( SalFrame* pNewParent )
+{
+ if( m_pParent )
+ std::erase(m_pParent->m_aChildren, this);
+ m_pParent = static_cast<SvpSalFrame*>(pNewParent);
+}
+
+void SvpSalFrame::SetPluginParent( SystemParentData* )
+{
+}
+
+void SvpSalFrame::ResetClipRegion()
+{
+}
+
+void SvpSalFrame::BeginSetClipRegion( sal_uInt32 )
+{
+}
+
+void SvpSalFrame::UnionClipRegion( tools::Long, tools::Long, tools::Long, tools::Long )
+{
+}
+
+void SvpSalFrame::EndSetClipRegion()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/svpgdi.cxx b/vcl/headless/svpgdi.cxx
new file mode 100644
index 0000000000..c9edd7e9a1
--- /dev/null
+++ b/vcl/headless/svpgdi.cxx
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <memory>
+#include <numeric>
+
+#include <headless/svpgdi.hxx>
+#include <comphelper/lok.hxx>
+
+SvpSalGraphics::SvpSalGraphics()
+ : m_aTextRenderImpl(m_aCairoCommon)
+ , m_pBackend(new SvpGraphicsBackend(m_aCairoCommon))
+{
+ bool bLOKActive = comphelper::LibreOfficeKit::isActive();
+ initWidgetDrawBackends(bLOKActive);
+}
+
+SvpSalGraphics::~SvpSalGraphics()
+{
+ ReleaseFonts();
+}
+
+void SvpSalGraphics::setSurface(cairo_surface_t* pSurface, const basegfx::B2IVector& rSize)
+{
+ m_aCairoCommon.m_pSurface = pSurface;
+ m_aCairoCommon.m_aFrameSize = rSize;
+ dl_cairo_surface_get_device_scale(pSurface, &m_aCairoCommon.m_fScale, nullptr);
+ GetImpl()->ResetClipRegion();
+}
+
+void SvpSalGraphics::GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY )
+{
+ rDPIX = rDPIY = 96;
+}
+
+bool SvpSalGraphics::ShouldDownscaleIconsAtSurface(double* pScaleOut) const
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ return SalGraphics::ShouldDownscaleIconsAtSurface(pScaleOut);
+ if (pScaleOut)
+ *pScaleOut = m_aCairoCommon.m_fScale;
+ return true;
+}
+
+#if ENABLE_CAIRO_CANVAS
+bool SvpSalGraphics::SupportsCairo() const
+{
+ return false;
+}
+
+cairo::SurfaceSharedPtr SvpSalGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& /*rSurface*/) const
+{
+ return cairo::SurfaceSharedPtr();
+}
+
+cairo::SurfaceSharedPtr SvpSalGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int /*x*/, int /*y*/, int /*width*/, int /*height*/) const
+{
+ return cairo::SurfaceSharedPtr();
+}
+
+cairo::SurfaceSharedPtr SvpSalGraphics::CreateBitmapSurface(const OutputDevice& /*rRefDevice*/, const BitmapSystemData& /*rData*/, const Size& /*rSize*/) const
+{
+ return cairo::SurfaceSharedPtr();
+}
+
+css::uno::Any SvpSalGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& /*rSurface*/, const basegfx::B2ISize& /*rSize*/) const
+{
+ return css::uno::Any();
+}
+
+#endif // ENABLE_CAIRO_CANVAS
+
+SystemGraphicsData SvpSalGraphics::GetGraphicsData() const
+{
+ SystemGraphicsData aGraphicsData;
+ aGraphicsData.pSurface = m_aCairoCommon.m_pSurface;
+ return aGraphicsData;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/svpinst.cxx b/vcl/headless/svpinst.cxx
new file mode 100644
index 0000000000..19eef89976
--- /dev/null
+++ b/vcl/headless/svpinst.cxx
@@ -0,0 +1,553 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/config.h>
+
+#include <mutex>
+
+#include <pthread.h>
+#include <sys/time.h>
+#include <poll.h>
+
+#include <sal/types.h>
+#include <sal/log.hxx>
+
+#include <vcl/virdev.hxx>
+#include <vcl/inputtypes.hxx>
+#include <vcl/lok.hxx>
+
+#include <headless/svpinst.hxx>
+#include <headless/svpframe.hxx>
+#include <headless/svpdummies.hxx>
+#include <headless/svpvd.hxx>
+#ifdef IOS
+# include <quartz/salbmp.h>
+# include <quartz/salgdi.h>
+# include <quartz/salvd.h>
+#else
+# include <cairo.h>
+# include <headless/svpgdi.hxx>
+#endif
+#include <headless/svpbmp.hxx>
+
+#include <salframe.hxx>
+#include <svdata.hxx>
+// FIXME: remove when we re-work the svp mainloop
+#include <unx/salunxtime.h>
+#include <comphelper/lok.hxx>
+#include <tools/debug.hxx>
+
+SvpSalInstance* SvpSalInstance::s_pDefaultInstance = nullptr;
+
+#ifndef NDEBUG
+static bool g_CheckedMutex = false;
+
+#define DBG_TESTSVPYIELDMUTEX() \
+do { \
+ if (!g_CheckedMutex) \
+ { \
+ assert(dynamic_cast<SvpSalYieldMutex*>(GetYieldMutex()) != nullptr \
+ && "This SvpSalInstance function requires use of SvpSalYieldMutex"); \
+ g_CheckedMutex = true; \
+ } \
+} while(false)
+
+#else // NDEBUG
+#define DBG_TESTSVPYIELDMUTEX() ((void)0)
+#endif
+
+#if !defined(ANDROID) && !defined(IOS) && !defined(EMSCRIPTEN)
+
+static void atfork_child()
+{
+ if (SvpSalInstance::s_pDefaultInstance != nullptr)
+ {
+ SvpSalInstance::s_pDefaultInstance->CloseWakeupPipe();
+ }
+}
+
+#endif
+
+SvpSalInstance::SvpSalInstance( std::unique_ptr<SalYieldMutex> pMutex )
+ : SalGenericInstance( std::move(pMutex) )
+{
+ m_aTimeout.tv_sec = 0;
+ m_aTimeout.tv_usec = 0;
+ m_nTimeoutMS = 0;
+
+ m_MainThread = osl::Thread::getCurrentIdentifier();
+ if( s_pDefaultInstance == nullptr )
+ s_pDefaultInstance = this;
+#if !defined(ANDROID) && !defined(IOS) && !defined(EMSCRIPTEN)
+ pthread_atfork(nullptr, nullptr, atfork_child);
+#endif
+}
+
+SvpSalInstance::~SvpSalInstance()
+{
+ if( s_pDefaultInstance == this )
+ s_pDefaultInstance = nullptr;
+ CloseWakeupPipe();
+}
+
+void SvpSalInstance::CloseWakeupPipe()
+{
+ SvpSalYieldMutex *const pMutex(dynamic_cast<SvpSalYieldMutex*>(GetYieldMutex()));
+ if (!pMutex)
+ return;
+ while (!pMutex->m_FeedbackPipe.empty())
+ pMutex->m_FeedbackPipe.pop();
+}
+
+void SvpSalInstance::TriggerUserEventProcessing()
+{
+ Wakeup();
+}
+
+void SvpSalInstance::Wakeup(SvpRequest const request)
+{
+ DBG_TESTSVPYIELDMUTEX();
+
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->mpWakeCallback && pSVData->mpPollClosure)
+ pSVData->mpWakeCallback(pSVData->mpPollClosure);
+
+ SvpSalYieldMutex *const pMutex(static_cast<SvpSalYieldMutex*>(GetYieldMutex()));
+ std::scoped_lock<std::mutex> g(pMutex->m_WakeUpMainMutex);
+ if (request != SvpRequest::NONE)
+ pMutex->m_Request = request;
+ pMutex->m_wakeUpMain = true;
+ pMutex->m_WakeUpMainCond.notify_one();
+}
+
+bool SvpSalInstance::CheckTimeout( bool bExecuteTimers )
+{
+ bool bRet = false;
+ if( m_aTimeout.tv_sec ) // timer is started
+ {
+ timeval aTimeOfDay;
+ gettimeofday( &aTimeOfDay, nullptr );
+ if( aTimeOfDay >= m_aTimeout )
+ {
+ bRet = true;
+ if( bExecuteTimers )
+ {
+ // timed out, update timeout
+ m_aTimeout = aTimeOfDay;
+ m_aTimeout += m_nTimeoutMS;
+
+ osl::Guard< comphelper::SolarMutex > aGuard( GetYieldMutex() );
+
+ // notify
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->maSchedCtx.mpSalTimer )
+ pSVData->maSchedCtx.mpSalTimer->CallCallback();
+ }
+ }
+ }
+ return bRet;
+}
+
+SalFrame* SvpSalInstance::CreateChildFrame( SystemParentData* /*pParent*/, SalFrameStyleFlags nStyle )
+{
+ return new SvpSalFrame( this, nullptr, nStyle );
+}
+
+SalFrame* SvpSalInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
+{
+ return new SvpSalFrame( this, pParent, nStyle );
+}
+
+void SvpSalInstance::DestroyFrame( SalFrame* pFrame )
+{
+ delete pFrame;
+}
+
+SalObject* SvpSalInstance::CreateObject( SalFrame*, SystemWindowData*, bool )
+{
+ return new SvpSalObject;
+}
+
+void SvpSalInstance::DestroyObject( SalObject* pObject )
+{
+ delete pObject;
+}
+
+#ifndef IOS
+
+std::unique_ptr<SalVirtualDevice> SvpSalInstance::CreateVirtualDevice(SalGraphics& rGraphics,
+ tools::Long &nDX, tools::Long &nDY,
+ DeviceFormat /*eFormat*/,
+ const SystemGraphicsData* pGd)
+{
+ SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(&rGraphics);
+ assert(pSvpSalGraphics);
+#ifndef ANDROID
+ // tdf#127529 normally pPreExistingTarget is null and we are a true virtualdevice drawing to a backing buffer.
+ // Occasionally, for canvas/slideshow, pPreExistingTarget is pre-provided as a hack to use the vcl drawing
+ // apis to render onto a preexisting cairo surface. The necessity for that precedes the use of cairo in vcl proper
+ cairo_surface_t* pPreExistingTarget = pGd ? static_cast<cairo_surface_t*>(pGd->pSurface) : nullptr;
+#else
+ //ANDROID case
+ (void)pGd;
+ cairo_surface_t* pPreExistingTarget = nullptr;
+#endif
+ std::unique_ptr<SalVirtualDevice> xNew(new SvpSalVirtualDevice(pSvpSalGraphics->getSurface(), pPreExistingTarget));
+ if (!xNew->SetSize(nDX, nDY))
+ xNew.reset();
+ return xNew;
+}
+
+cairo_surface_t* get_underlying_cairo_surface(const VirtualDevice& rDevice)
+{
+ return static_cast<SvpSalVirtualDevice*>(rDevice.mpVirDev.get())->GetSurface();
+}
+
+const cairo_font_options_t* SvpSalInstance::GetCairoFontOptions()
+{
+ static cairo_font_options_t *gOptions = nullptr;
+ if (!gOptions)
+ {
+ gOptions = cairo_font_options_create();
+ cairo_font_options_set_antialias(gOptions, CAIRO_ANTIALIAS_GRAY);
+ }
+ return gOptions;
+}
+
+#else // IOS
+
+const cairo_font_options_t* SvpSalInstance::GetCairoFontOptions()
+{
+ return nullptr;
+}
+
+#endif
+
+SalTimer* SvpSalInstance::CreateSalTimer()
+{
+ return new SvpSalTimer( this );
+}
+
+SalSystem* SvpSalInstance::CreateSalSystem()
+{
+ return new SvpSalSystem();
+}
+
+std::shared_ptr<SalBitmap> SvpSalInstance::CreateSalBitmap()
+{
+#ifdef IOS
+ return std::make_shared<QuartzSalBitmap>();
+#else
+ return std::make_shared<SvpSalBitmap>();
+#endif
+}
+
+void SvpSalInstance::ProcessEvent( SalUserEvent aEvent )
+{
+ DBG_TESTSVPYIELDMUTEX();
+
+ aEvent.m_pFrame->CallCallback( aEvent.m_nEvent, aEvent.m_pData );
+ if( aEvent.m_nEvent == SalEvent::Resize )
+ {
+ // this would be a good time to post a paint
+ const SvpSalFrame* pSvpFrame = static_cast<const SvpSalFrame*>( aEvent.m_pFrame);
+ pSvpFrame->PostPaint();
+ }
+
+ SvpSalYieldMutex *const pMutex(static_cast<SvpSalYieldMutex*>(GetYieldMutex()));
+ pMutex->m_NonMainWaitingYieldCond.set();
+}
+
+SvpSalYieldMutex::SvpSalYieldMutex()
+{
+}
+
+SvpSalYieldMutex::~SvpSalYieldMutex()
+{
+}
+
+void SvpSalYieldMutex::doAcquire(sal_uInt32 const nLockCount)
+{
+ auto *const pInst = static_cast<SvpSalInstance*>(GetSalInstance());
+ if (pInst && pInst->IsMainThread())
+ {
+ if (m_bNoYieldLock)
+ return;
+
+ do
+ {
+ SvpRequest request = SvpRequest::NONE;
+ {
+ std::unique_lock<std::mutex> g(m_WakeUpMainMutex);
+ if (m_aMutex.tryToAcquire()) {
+ // if there's a request, the other thread holds m_aMutex
+ assert(m_Request == SvpRequest::NONE);
+ m_wakeUpMain = false;
+ break;
+ }
+ m_WakeUpMainCond.wait(g, [this]() { return m_wakeUpMain; });
+ m_wakeUpMain = false;
+ std::swap(m_Request, request);
+ }
+ if (request != SvpRequest::NONE)
+ {
+ // nested Yield on behalf of another thread
+ assert(!m_bNoYieldLock);
+ m_bNoYieldLock = true;
+ bool const bEvents = pInst->DoYield(false, request == SvpRequest::MainThreadDispatchAllEvents);
+ m_bNoYieldLock = false;
+ {
+ std::lock_guard lock(m_FeedbackMutex);
+ m_FeedbackPipe.push(bEvents);
+ }
+ m_FeedbackCV.notify_all();
+ }
+ }
+ while (true);
+ }
+ else
+ {
+ m_aMutex.acquire();
+ }
+ ++m_nCount;
+ SalYieldMutex::doAcquire(nLockCount - 1);
+}
+
+sal_uInt32 SvpSalYieldMutex::doRelease(bool const bUnlockAll)
+{
+ auto *const pInst = static_cast<SvpSalInstance*>(GetSalInstance());
+ if (pInst && pInst->IsMainThread())
+ {
+ if (m_bNoYieldLock)
+ return 1;
+ else
+ return SalYieldMutex::doRelease(bUnlockAll);
+ }
+ sal_uInt32 nCount;
+ {
+ // read m_nCount before doRelease
+ bool const isReleased(bUnlockAll || m_nCount == 1);
+ nCount = comphelper::SolarMutex::doRelease( bUnlockAll );
+
+ if (isReleased)
+ {
+ if (vcl::lok::isUnipoll())
+ {
+ if (pInst)
+ pInst->Wakeup();
+ }
+ else
+ {
+ std::scoped_lock<std::mutex> g(m_WakeUpMainMutex);
+ m_wakeUpMain = true;
+ m_WakeUpMainCond.notify_one();
+ }
+ }
+ }
+ return nCount;
+}
+
+bool SvpSalYieldMutex::IsCurrentThread() const
+{
+ if (GetSalInstance()->IsMainThread() && m_bNoYieldLock)
+ return true;
+ else
+ return SalYieldMutex::IsCurrentThread();
+}
+
+bool SvpSalInstance::IsMainThread() const
+{
+ return osl::Thread::getCurrentIdentifier() == m_MainThread;
+}
+
+void SvpSalInstance::updateMainThread()
+{
+ if (!IsMainThread())
+ {
+ m_MainThread = osl::Thread::getCurrentIdentifier();
+ ImplGetSVData()->mnMainThreadId = osl::Thread::getCurrentIdentifier();
+ }
+}
+
+bool SvpSalInstance::ImplYield(bool bWait, bool bHandleAllCurrentEvents)
+{
+ DBG_TESTSVPYIELDMUTEX();
+ DBG_TESTSOLARMUTEX();
+ assert(IsMainThread());
+
+ bool bWasEvent = DispatchUserEvents(bHandleAllCurrentEvents);
+ if (!bHandleAllCurrentEvents && bWasEvent)
+ return true;
+
+ bWasEvent = CheckTimeout() || bWasEvent;
+ const bool bMustSleep = bWait && !bWasEvent;
+
+ // This is wrong and must be removed!
+ // We always want to drop the SolarMutex on yield; that is the whole point of yield.
+ if (!bMustSleep)
+ return bWasEvent;
+
+ sal_Int64 nTimeoutMicroS = 0;
+ if (bMustSleep)
+ {
+ if (m_aTimeout.tv_sec) // Timer is started.
+ {
+ timeval Timeout;
+ // determine remaining timeout.
+ gettimeofday (&Timeout, nullptr);
+ if (m_aTimeout > Timeout)
+ nTimeoutMicroS = ((m_aTimeout.tv_sec - Timeout.tv_sec) * 1000 * 1000 +
+ (m_aTimeout.tv_usec - Timeout.tv_usec));
+ }
+ else
+ nTimeoutMicroS = -1; // wait until something happens
+ }
+
+ SolarMutexReleaser aReleaser;
+
+ if (vcl::lok::isUnipoll())
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->mpPollClosure)
+ {
+ int nPollResult = pSVData->mpPollCallback(pSVData->mpPollClosure, nTimeoutMicroS);
+ if (nPollResult < 0)
+ pSVData->maAppData.mbAppQuit = true;
+ bWasEvent = bWasEvent || (nPollResult != 0);
+ }
+ }
+ else if (bMustSleep)
+ {
+ SvpSalYieldMutex *const pMutex(static_cast<SvpSalYieldMutex*>(GetYieldMutex()));
+ std::unique_lock<std::mutex> g(pMutex->m_WakeUpMainMutex);
+ // wait for doRelease() or Wakeup() to set the condition
+ if (nTimeoutMicroS == -1)
+ {
+ pMutex->m_WakeUpMainCond.wait(g,
+ [pMutex]() { return pMutex->m_wakeUpMain; });
+ }
+ else
+ {
+ int nTimeoutMS = nTimeoutMicroS / 1000;
+ if (nTimeoutMicroS % 1000)
+ nTimeoutMS += 1;
+ pMutex->m_WakeUpMainCond.wait_for(g,
+ std::chrono::milliseconds(nTimeoutMS),
+ [pMutex]() { return pMutex->m_wakeUpMain; });
+ }
+ // here no need to check m_Request because Acquire will do it
+ }
+
+ return bWasEvent;
+}
+
+bool SvpSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
+{
+ DBG_TESTSVPYIELDMUTEX();
+ DBG_TESTSOLARMUTEX();
+
+ bool bWasEvent(false);
+ SvpSalYieldMutex *const pMutex(static_cast<SvpSalYieldMutex*>(GetYieldMutex()));
+
+ if (IsMainThread())
+ {
+ bWasEvent = ImplYield(bWait, bHandleAllCurrentEvents);
+ if (bWasEvent)
+ pMutex->m_NonMainWaitingYieldCond.set(); // wake up other threads
+ }
+ else
+ {
+ // TODO: use a SolarMutexReleaser here and drop the m_bNoYieldLock usage
+ Wakeup(bHandleAllCurrentEvents
+ ? SvpRequest::MainThreadDispatchAllEvents
+ : SvpRequest::MainThreadDispatchOneEvent);
+
+ // blocking read (for synchronisation)
+ {
+ std::unique_lock lock(pMutex->m_FeedbackMutex);
+ pMutex->m_FeedbackCV.wait(lock, [pMutex] { return !pMutex->m_FeedbackPipe.empty(); });
+ bWasEvent = pMutex->m_FeedbackPipe.front();
+ pMutex->m_FeedbackPipe.pop();
+ }
+ if (!bWasEvent && bWait)
+ {
+ // block & release YieldMutex until the main thread does something
+ pMutex->m_NonMainWaitingYieldCond.reset();
+ SolarMutexReleaser aReleaser;
+ pMutex->m_NonMainWaitingYieldCond.wait();
+ }
+ }
+
+ return bWasEvent;
+}
+
+bool SvpSalInstance::AnyInput( VclInputFlags nType )
+{
+ if( nType & VclInputFlags::TIMER )
+ return CheckTimeout( false );
+ return false;
+}
+
+OUString SvpSalInstance::GetConnectionIdentifier()
+{
+ return OUString();
+}
+
+void SvpSalInstance::StopTimer()
+{
+ m_aTimeout.tv_sec = 0;
+ m_aTimeout.tv_usec = 0;
+ m_nTimeoutMS = 0;
+}
+
+void SvpSalInstance::StartTimer( sal_uInt64 nMS )
+{
+ timeval aPrevTimeout (m_aTimeout);
+ gettimeofday (&m_aTimeout, nullptr);
+
+ m_nTimeoutMS = nMS;
+ m_aTimeout += m_nTimeoutMS;
+
+ if ((aPrevTimeout > m_aTimeout) || (aPrevTimeout.tv_sec == 0))
+ {
+ // Wakeup from previous timeout (or stopped timer).
+ Wakeup();
+ }
+}
+
+void SvpSalInstance::AddToRecentDocumentList(const OUString&, const OUString&, const OUString&)
+{
+}
+
+SvpSalTimer::~SvpSalTimer()
+{
+}
+
+void SvpSalTimer::Stop()
+{
+ m_pInstance->StopTimer();
+}
+
+void SvpSalTimer::Start( sal_uInt64 nMS )
+{
+ m_pInstance->StartTimer( nMS );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/svpprn.cxx b/vcl/headless/svpprn.cxx
new file mode 100644
index 0000000000..6c40a0fd8d
--- /dev/null
+++ b/vcl/headless/svpprn.cxx
@@ -0,0 +1,268 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/svapp.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/QueueInfo.hxx>
+#include <printerinfomanager.hxx>
+
+#include <jobset.h>
+#include <print.h>
+#include <salptype.hxx>
+#include <svdata.hxx>
+
+#include <unx/genpspgraphics.h>
+
+#include <headless/svpprn.hxx>
+#include <headless/svpinst.hxx>
+
+using namespace psp;
+
+/*
+ * static helpers
+ */
+
+static OUString getPdfDir( const PrinterInfo& rInfo )
+{
+ OUString aDir;
+ sal_Int32 nIndex = 0;
+ while( nIndex != -1 )
+ {
+ OUString aToken( rInfo.m_aFeatures.getToken( 0, ',', nIndex ) );
+ if( aToken.startsWith( "pdf=" ) )
+ {
+ sal_Int32 nPos = 0;
+ aDir = aToken.getToken( 1, '=', nPos );
+ if( aDir.isEmpty() )
+ if (auto const env = getenv( "HOME" )) {
+ aDir = OStringToOUString(
+ std::string_view( env ), osl_getThreadTextEncoding() );
+ }
+ break;
+ }
+ }
+ return aDir;
+}
+
+static int PtTo10Mu( int nPoints ) { return static_cast<int>((static_cast<double>(nPoints)*35.27777778)+0.5); }
+
+static void copyJobDataToJobSetup( ImplJobSetup* pJobSetup, JobData& rData )
+{
+ pJobSetup->SetOrientation( rData.m_eOrientation == orientation::Landscape ? Orientation::Landscape : Orientation::Portrait );
+
+ // copy page size
+ OUString aPaper;
+ int width, height;
+
+ rData.m_aContext.getPageSize( aPaper, width, height );
+ pJobSetup->SetPaperFormat( PaperInfo::fromPSName(OUStringToOString( aPaper, RTL_TEXTENCODING_ISO_8859_1 )) );
+ pJobSetup->SetPaperWidth( 0 );
+ pJobSetup->SetPaperHeight( 0 );
+ if( pJobSetup->GetPaperFormat() == PAPER_USER )
+ {
+ // transform to 100dth mm
+ width = PtTo10Mu( width );
+ height = PtTo10Mu( height );
+
+ if( rData.m_eOrientation == psp::orientation::Portrait )
+ {
+ pJobSetup->SetPaperWidth( width );
+ pJobSetup->SetPaperHeight( height );
+ }
+ else
+ {
+ pJobSetup->SetPaperWidth( height );
+ pJobSetup->SetPaperHeight( width );
+ }
+ }
+
+ // copy input slot
+ const PPDKey* pKey = nullptr;
+ const PPDValue* pValue = nullptr;
+
+ pJobSetup->SetPaperBin( 0xffff );
+ if( rData.m_pParser )
+ pKey = rData.m_pParser->getKey( "InputSlot" );
+ if( pKey )
+ pValue = rData.m_aContext.getValue( pKey );
+ if( pKey && pValue )
+ {
+ int nPaperBin;
+ for( nPaperBin = 0;
+ pValue != pKey->getValue( nPaperBin ) &&
+ nPaperBin < pKey->countValues();
+ nPaperBin++ );
+ pJobSetup->SetPaperBin(
+ (nPaperBin == pKey->countValues()
+ || pValue == pKey->getDefaultValue())
+ ? 0xffff : nPaperBin);
+ }
+
+ // copy duplex
+ pKey = nullptr;
+ pValue = nullptr;
+
+ pJobSetup->SetDuplexMode( DuplexMode::Unknown );
+ if( rData.m_pParser )
+ pKey = rData.m_pParser->getKey( "Duplex" );
+ if( pKey )
+ pValue = rData.m_aContext.getValue( pKey );
+ if( pKey && pValue )
+ {
+ if( pValue->m_aOption.equalsIgnoreAsciiCase( "None" ) ||
+ pValue->m_aOption.startsWithIgnoreAsciiCase( "Simplex" )
+ )
+ {
+ pJobSetup->SetDuplexMode( DuplexMode::Off );
+ }
+ else if( pValue->m_aOption.equalsIgnoreAsciiCase( "DuplexNoTumble" ) )
+ {
+ pJobSetup->SetDuplexMode( DuplexMode::LongEdge );
+ }
+ else if( pValue->m_aOption.equalsIgnoreAsciiCase( "DuplexTumble" ) )
+ {
+ pJobSetup->SetDuplexMode( DuplexMode::ShortEdge );
+ }
+ }
+
+ // copy the whole context
+
+ sal_uInt32 nBytes;
+ std::unique_ptr<sal_uInt8[]> pBuffer;
+ if( rData.getStreamBuffer( pBuffer, nBytes ) )
+ pJobSetup->SetDriverData( std::move(pBuffer), nBytes );
+ else
+ pJobSetup->SetDriverData( nullptr, 0 );
+}
+
+// SalInstance
+
+SalInfoPrinter* SvpSalInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pJobSetup )
+{
+ // create and initialize SalInfoPrinter
+ SvpSalInfoPrinter* pPrinter = new SvpSalInfoPrinter;
+
+ if( pJobSetup )
+ {
+ PrinterInfoManager& rManager( PrinterInfoManager::get() );
+ PrinterInfo aInfo( rManager.getPrinterInfo( pQueueInfo->maPrinterName ) );
+ pPrinter->m_aJobData = aInfo;
+
+ if( pJobSetup->GetDriverData() )
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(),
+ pJobSetup->GetDriverDataLen(), aInfo );
+
+ pJobSetup->SetSystem( JOBSETUP_SYSTEM_UNIX );
+ pJobSetup->SetPrinterName( pQueueInfo->maPrinterName );
+ pJobSetup->SetDriver( aInfo.m_aDriverName );
+ copyJobDataToJobSetup( pJobSetup, aInfo );
+ }
+
+ return pPrinter;
+}
+
+void SvpSalInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter )
+{
+ delete pPrinter;
+}
+
+std::unique_ptr<SalPrinter> SvpSalInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
+{
+ // create and initialize SalPrinter
+ SvpSalPrinter* pPrinter = new SvpSalPrinter( pInfoPrinter );
+ pPrinter->m_aJobData = static_cast<SvpSalInfoPrinter*>(pInfoPrinter)->m_aJobData;
+
+ return std::unique_ptr<SalPrinter>(pPrinter);
+}
+
+void SvpSalInstance::GetPrinterQueueInfo( ImplPrnQueueList* pList )
+{
+ PrinterInfoManager& rManager( PrinterInfoManager::get() );
+ static const char* pNoSyncDetection = getenv( "SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION" );
+ if( ! pNoSyncDetection || ! *pNoSyncDetection )
+ {
+ // #i62663# synchronize possible asynchronouse printer detection now
+ rManager.checkPrintersChanged( true );
+ }
+ ::std::vector< OUString > aPrinters;
+ rManager.listPrinters( aPrinters );
+
+ for (auto const& printer : aPrinters)
+ {
+ const PrinterInfo& rInfo( rManager.getPrinterInfo(printer) );
+ // create new entry
+ std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo);
+ pInfo->maPrinterName = printer;
+ pInfo->maDriver = rInfo.m_aDriverName;
+ pInfo->maLocation = rInfo.m_aLocation;
+ pInfo->maComment = rInfo.m_aComment;
+
+ sal_Int32 nIndex = 0;
+ while( nIndex != -1 )
+ {
+ OUString aToken( rInfo.m_aFeatures.getToken( 0, ',', nIndex ) );
+ if( aToken.startsWith( "pdf=" ) )
+ {
+ pInfo->maLocation = getPdfDir( rInfo );
+ break;
+ }
+ }
+
+ pList->Add( std::move(pInfo) );
+ }
+}
+
+void SvpSalInstance::GetPrinterQueueState( SalPrinterQueueInfo* )
+{
+}
+
+OUString SvpSalInstance::GetDefaultPrinter()
+{
+ PrinterInfoManager& rManager( PrinterInfoManager::get() );
+ return rManager.getDefaultPrinter();
+}
+
+void SvpSalInstance::PostPrintersChanged()
+{
+ SvpSalInstance *pInst = SvpSalInstance::s_pDefaultInstance;
+ for (auto pSalFrame : pInst->getFrames() )
+ pInst->PostEvent( pSalFrame, nullptr, SalEvent::PrinterChanged );
+}
+
+std::unique_ptr<GenPspGraphics> SvpSalInstance::CreatePrintGraphics()
+{
+ return std::make_unique<GenPspGraphics>();
+}
+
+bool SvpSalInfoPrinter::Setup( weld::Window*, ImplJobSetup* )
+{
+ return false;
+}
+
+SvpSalPrinter::SvpSalPrinter( SalInfoPrinter* pInfoPrinter )
+ : PspSalPrinter( pInfoPrinter )
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/svptext.cxx b/vcl/headless/svptext.cxx
new file mode 100644
index 0000000000..297523b8b3
--- /dev/null
+++ b/vcl/headless/svptext.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 <sal/config.h>
+
+#include <sal/types.h>
+#include <vcl/fontcharmap.hxx>
+#include <basegfx/range/b2ibox.hxx>
+#include <headless/svpgdi.hxx>
+#include <font/FontMetricData.hxx>
+#include <sallayout.hxx>
+
+void SvpSalGraphics::SetFont(LogicalFontInstance* pIFSD, int nFallbackLevel)
+{
+ m_aTextRenderImpl.SetFont(pIFSD, nFallbackLevel);
+}
+
+void SvpSalGraphics::GetFontMetric( FontMetricDataRef& xFontMetric, int nFallbackLevel )
+{
+ m_aTextRenderImpl.GetFontMetric(xFontMetric, nFallbackLevel);
+}
+
+FontCharMapRef SvpSalGraphics::GetFontCharMap() const
+{
+ return m_aTextRenderImpl.GetFontCharMap();
+}
+
+bool SvpSalGraphics::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ return m_aTextRenderImpl.GetFontCapabilities(rFontCapabilities);
+}
+
+void SvpSalGraphics::GetDevFontList( vcl::font::PhysicalFontCollection* pFontCollection )
+{
+ m_aTextRenderImpl.GetDevFontList(pFontCollection);
+}
+
+void SvpSalGraphics::ClearDevFontCache()
+{
+ m_aTextRenderImpl.ClearDevFontCache();
+}
+
+bool SvpSalGraphics::AddTempDevFont( vcl::font::PhysicalFontCollection* pFontCollection,
+ const OUString& rFileURL, const OUString& rFontName)
+{
+ return m_aTextRenderImpl.AddTempDevFont(pFontCollection, rFileURL, rFontName);
+}
+
+std::unique_ptr<GenericSalLayout> SvpSalGraphics::GetTextLayout(int nFallbackLevel)
+{
+ return m_aTextRenderImpl.GetTextLayout(nFallbackLevel);
+}
+
+void SvpSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+ m_aTextRenderImpl.DrawTextLayout(rLayout, *this);
+}
+
+void SvpSalGraphics::SetTextColor( Color nColor )
+{
+ m_aTextRenderImpl.SetTextColor(nColor);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/svpvd.cxx b/vcl/headless/svpvd.cxx
new file mode 100644
index 0000000000..c65951e029
--- /dev/null
+++ b/vcl/headless/svpvd.cxx
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 IOS
+
+#include <headless/svpbmp.hxx>
+#include <headless/svpinst.hxx>
+#include <headless/svpvd.hxx>
+#include <headless/svpgdi.hxx>
+
+#include <basegfx/vector/b2ivector.hxx>
+#include <comphelper/lok.hxx>
+
+#include <cairo.h>
+
+using namespace basegfx;
+
+SvpSalVirtualDevice::SvpSalVirtualDevice(cairo_surface_t* pRefSurface, cairo_surface_t* pPreExistingTarget)
+ : m_pRefSurface(pRefSurface)
+ , m_pSurface(pPreExistingTarget)
+ , m_bOwnsSurface(!pPreExistingTarget)
+{
+ cairo_surface_reference(m_pRefSurface);
+}
+
+SvpSalVirtualDevice::~SvpSalVirtualDevice()
+{
+ if (m_bOwnsSurface)
+ cairo_surface_destroy(m_pSurface);
+ cairo_surface_destroy(m_pRefSurface);
+}
+
+SvpSalGraphics* SvpSalVirtualDevice::AddGraphics(SvpSalGraphics* pGraphics)
+{
+ pGraphics->setSurface(m_pSurface, m_aFrameSize);
+ m_aGraphics.push_back(pGraphics);
+ return pGraphics;
+}
+
+SalGraphics* SvpSalVirtualDevice::AcquireGraphics()
+{
+ return AddGraphics(new SvpSalGraphics());
+}
+
+void SvpSalVirtualDevice::ReleaseGraphics( SalGraphics* pGraphics )
+{
+ std::erase(m_aGraphics, dynamic_cast<SvpSalGraphics*>(pGraphics));
+ delete pGraphics;
+}
+
+bool SvpSalVirtualDevice::SetSize( tools::Long nNewDX, tools::Long nNewDY )
+{
+ return SetSizeUsingBuffer(nNewDX, nNewDY, nullptr);
+}
+
+bool SvpSalVirtualDevice::CreateSurface(tools::Long nNewDX, tools::Long nNewDY, sal_uInt8 *const pBuffer)
+{
+ if (m_pSurface)
+ {
+ cairo_surface_destroy(m_pSurface);
+ }
+
+ if (pBuffer)
+ {
+ // The buffer should only be set by VirtualDevice::SetOutputSizePixelScaleOffsetAndLOKBuffer()
+ // when used to draw a tile for LOK. It cannot be used for something else, because otherwise
+ // this would need a way to detect whether this is a tiled paint that needs LOK handling
+ // or whether it's that something else that just might happen to be called with LOK active.
+ assert(comphelper::LibreOfficeKit::isActive());
+ // Force scaling of the painting
+ double fScale = comphelper::LibreOfficeKit::getDPIScale();
+
+ m_pSurface = cairo_image_surface_create_for_data(pBuffer, CAIRO_FORMAT_ARGB32,
+ nNewDX, nNewDY, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, nNewDX));
+ dl_cairo_surface_set_device_scale(m_pSurface, fScale, fScale);
+ }
+ else if(nNewDX <= 32 && nNewDY <= 32)
+ {
+ double fXScale, fYScale;
+ dl_cairo_surface_get_device_scale(m_pRefSurface, &fXScale, &fYScale);
+ nNewDX *= fXScale;
+ nNewDY *= fYScale;
+
+ // Force image-based surface if small. Small VirtualDevice instances are often used for small
+ // temporary bitmaps that will eventually have GetBitmap() called on them, which would cause
+ // X Server roundtrip with Xlib-based surface, which may be way more costly than doing the drawing
+ // in software (which should be fairly cheap for small surfaces anyway).
+ m_pSurface = cairo_surface_create_similar_image(m_pRefSurface, CAIRO_FORMAT_ARGB32, nNewDX, nNewDY);
+ dl_cairo_surface_set_device_scale(m_pSurface, fXScale, fYScale);
+ }
+ else
+ {
+ m_pSurface = cairo_surface_create_similar(m_pRefSurface, CAIRO_CONTENT_COLOR_ALPHA, nNewDX, nNewDY);
+ // Device scale is inherited in this case.
+ }
+
+ SAL_WARN_IF(cairo_surface_status(m_pSurface) != CAIRO_STATUS_SUCCESS, "vcl", "surface of size " << nNewDX << " by " << nNewDY << " creation failed with status of: " << cairo_status_to_string(cairo_surface_status(m_pSurface)));
+ return cairo_surface_status(m_pSurface) == CAIRO_STATUS_SUCCESS;
+}
+
+bool SvpSalVirtualDevice::SetSizeUsingBuffer( tools::Long nNewDX, tools::Long nNewDY,
+ sal_uInt8 *const pBuffer)
+{
+ bool bSuccess = true;
+
+ if (nNewDX == 0)
+ nNewDX = 1;
+ if (nNewDY == 0)
+ nNewDY = 1;
+
+ if (!m_pSurface || m_aFrameSize.getX() != nNewDX ||
+ m_aFrameSize.getY() != nNewDY)
+ {
+ m_aFrameSize = basegfx::B2IVector(nNewDX, nNewDY);
+
+ if (m_bOwnsSurface)
+ bSuccess = CreateSurface(nNewDX, nNewDY, pBuffer);
+
+ assert(m_pSurface);
+
+ // update device in existing graphics
+ for (auto const& graphic : m_aGraphics)
+ graphic->setSurface(m_pSurface, m_aFrameSize);
+ }
+
+ return bSuccess;
+}
+
+tools::Long SvpSalVirtualDevice::GetWidth() const
+{
+ return m_pSurface ? m_aFrameSize.getX() : 0;
+}
+
+tools::Long SvpSalVirtualDevice::GetHeight() const
+{
+ return m_pSurface ? m_aFrameSize.getY() : 0;
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/BitmapSymmetryCheck.hxx b/vcl/inc/BitmapSymmetryCheck.hxx
new file mode 100644
index 0000000000..917b8b6d13
--- /dev/null
+++ b/vcl/inc/BitmapSymmetryCheck.hxx
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_INC_BITMAPSYMMETRYCHECK_HXX
+#define INCLUDED_VCL_INC_BITMAPSYMMETRYCHECK_HXX
+
+#include <vcl/bitmap.hxx>
+
+class BitmapReadAccess;
+
+class VCL_DLLPUBLIC BitmapSymmetryCheck final
+{
+public:
+ BitmapSymmetryCheck();
+
+ static bool check(Bitmap& rBitmap);
+
+private:
+ static bool checkImpl(BitmapReadAccess const* pReadAccess);
+};
+
+#endif // INCLUDED_VCL_INC_BITMAPSYMMETRYCHECK_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/ContextVBox.hxx b/vcl/inc/ContextVBox.hxx
new file mode 100644
index 0000000000..0f88e4f232
--- /dev/null
+++ b/vcl/inc/ContextVBox.hxx
@@ -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 <sal/config.h>
+
+#include <vcl/NotebookbarContextControl.hxx>
+#include <vcl/layout.hxx>
+
+/*
+ * ContextVBox is a VclVBox which shows own children depending on current context.
+ * This control can be used in the notebookbar .ui files
+ */
+
+class ContextVBox final : public VclVBox, public NotebookbarContextControl
+{
+public:
+ explicit ContextVBox(vcl::Window* pParent);
+ virtual ~ContextVBox() override;
+ void SetContext(vcl::EnumContext::Context eContext) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/ControlCacheKey.hxx b/vcl/inc/ControlCacheKey.hxx
new file mode 100644
index 0000000000..e422004ca5
--- /dev/null
+++ b/vcl/inc/ControlCacheKey.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_CONTROLCACHEKEY_HXX
+#define INCLUDED_VCL_INC_CONTROLCACHEKEY_HXX
+
+#include <tools/gen.hxx>
+#include <vcl/salnativewidgets.hxx>
+#include <o3tl/hash_combine.hxx>
+
+class ControlCacheKey
+{
+public:
+ ControlType mnType;
+ ControlPart mnPart;
+ ControlState mnState;
+ Size maSize;
+
+ ControlCacheKey(ControlType nType, ControlPart nPart, ControlState nState, const Size& rSize)
+ : mnType(nType)
+ , mnPart(nPart)
+ , mnState(nState)
+ , maSize(rSize)
+ {
+ }
+
+ bool operator==(ControlCacheKey const& aOther) const
+ {
+ return mnType == aOther.mnType && mnPart == aOther.mnPart && mnState == aOther.mnState
+ && maSize.Width() == aOther.maSize.Width()
+ && maSize.Height() == aOther.maSize.Height();
+ }
+
+ bool canCacheControl() const
+ {
+ switch (mnType)
+ {
+ case ControlType::Checkbox:
+ case ControlType::Radiobutton:
+ case ControlType::ListNode:
+ case ControlType::Slider:
+ case ControlType::LevelBar:
+ case ControlType::Progress:
+ // FIXME: these guys have complex state hidden in ImplControlValue
+ // structs which affects rendering, needs to be a and needs to be
+ // part of the key to our cache.
+ case ControlType::Spinbox:
+ case ControlType::SpinButtons:
+ case ControlType::TabItem:
+ return false;
+
+ case ControlType::Menubar:
+ if (mnPart == ControlPart::Entire)
+ return false;
+ break;
+
+ default:
+ break;
+ }
+ return true;
+ }
+};
+
+struct ControlCacheHashFunction
+{
+ std::size_t operator()(ControlCacheKey const& aCache) const
+ {
+ std::size_t seed = 0;
+ o3tl::hash_combine(seed, aCache.mnType);
+ o3tl::hash_combine(seed, aCache.mnPart);
+ o3tl::hash_combine(seed, aCache.mnState);
+ o3tl::hash_combine(seed, aCache.maSize.Width());
+ o3tl::hash_combine(seed, aCache.maSize.Height());
+ return seed;
+ }
+};
+
+#endif // INCLUDED_VCL_INC_CONTROLCACHEKEY_HXX
diff --git a/vcl/inc/DropdownBox.hxx b/vcl/inc/DropdownBox.hxx
new file mode 100644
index 0000000000..a6530babca
--- /dev/null
+++ b/vcl/inc/DropdownBox.hxx
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SFX2_NOTEBOOKBAR_DROPDOWNBOX_HXX
+#define INCLUDED_SFX2_NOTEBOOKBAR_DROPDOWNBOX_HXX
+
+#include <vcl/layout.hxx>
+#include "IPrioritable.hxx"
+#include "NotebookbarPopup.hxx"
+
+class Button;
+
+class DropdownBox final : public VclHBox, public vcl::IPrioritable
+{
+private:
+ bool m_bInFullView;
+ VclPtr<PushButton> m_pButton;
+ VclPtr<NotebookbarPopup> m_pPopup;
+
+public:
+ explicit DropdownBox(vcl::Window* pParent);
+ virtual ~DropdownBox() override;
+ virtual void dispose() override;
+
+ void HideContent() override;
+ void ShowContent() override;
+ bool IsHidden() override;
+
+private:
+ DECL_LINK(PBClickHdl, Button*, void);
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/FileDefinitionWidgetDraw.hxx b/vcl/inc/FileDefinitionWidgetDraw.hxx
new file mode 100644
index 0000000000..881316c2f4
--- /dev/null
+++ b/vcl/inc/FileDefinitionWidgetDraw.hxx
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_INC_FILEDEFINITIONWIDGETDRAW_HXX
+#define INCLUDED_VCL_INC_FILEDEFINITIONWIDGETDRAW_HXX
+
+#include "widgetdraw/WidgetDefinition.hxx"
+#include "salgdi.hxx"
+#include "WidgetDrawInterface.hxx"
+
+namespace vcl
+{
+class FileDefinitionWidgetDraw final : public vcl::WidgetDrawInterface
+{
+private:
+ SalGraphics& m_rGraphics;
+ bool m_bIsActive;
+
+ std::shared_ptr<WidgetDefinition> m_pWidgetDefinition;
+
+ bool resolveDefinition(ControlType eType, ControlPart ePart, ControlState eState,
+ const ImplControlValue& rValue, tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight);
+
+public:
+ FileDefinitionWidgetDraw(SalGraphics& rGraphics);
+
+ bool isActive() const { return m_bIsActive; }
+
+ bool isNativeControlSupported(ControlType eType, ControlPart ePart) override;
+
+ bool hitTestNativeControl(ControlType eType, ControlPart ePart,
+ const tools::Rectangle& rBoundingControlRegion, const Point& aPos,
+ bool& rIsInside) override;
+
+ bool drawNativeControl(ControlType eType, ControlPart ePart,
+ const tools::Rectangle& rBoundingControlRegion, ControlState eState,
+ const ImplControlValue& aValue, const OUString& aCaptions,
+ const Color& rBackgroundColor) override;
+
+ bool getNativeControlRegion(ControlType eType, ControlPart ePart,
+ const tools::Rectangle& rBoundingControlRegion, ControlState eState,
+ const ImplControlValue& aValue, const OUString& aCaption,
+ tools::Rectangle& rNativeBoundingRegion,
+ tools::Rectangle& rNativeContentRegion) override;
+
+ bool updateSettings(AllSettings& rSettings) override;
+
+ static void drawPolyPolygon(SalGraphics& rGraphics,
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& i_rPolyPolygon,
+ double i_fTransparency);
+
+ static void 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);
+
+ static void drawBitmap(SalGraphics& rGraphics, const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap);
+
+ static void drawBitmap(SalGraphics& rGraphics, const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap, const SalBitmap& rTransparentBitmap);
+
+ static void implDrawGradient(SalGraphics& rGraphics,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ const SalGradient& rGradient);
+};
+
+} // end vcl namespace
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/IPrioritable.hxx b/vcl/inc/IPrioritable.hxx
new file mode 100644
index 0000000000..559fbc982f
--- /dev/null
+++ b/vcl/inc/IPrioritable.hxx
@@ -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/.
+ */
+
+#ifndef INCLUDED_VCL_IPRIORITABLE_HXX
+#define INCLUDED_VCL_IPRIORITABLE_HXX
+
+#include <vcl/dllapi.h>
+
+#define VCL_PRIORITY_DEFAULT -1
+
+namespace vcl
+{
+
+class VCL_DLLPUBLIC SAL_LOPLUGIN_ANNOTATE("crosscast") IPrioritable
+{
+protected:
+ IPrioritable() : m_nPriority(VCL_PRIORITY_DEFAULT)
+ {
+ }
+
+public:
+ virtual ~IPrioritable()
+ {
+ }
+
+ int GetPriority() const
+ {
+ return m_nPriority;
+ }
+
+ void SetPriority(int nPriority)
+ {
+ m_nPriority = nPriority;
+ }
+
+ virtual void HideContent() = 0;
+ virtual void ShowContent() = 0;
+ virtual bool IsHidden() = 0;
+
+private:
+ int m_nPriority;
+};
+
+} // namespace vcl
+
+#endif // INCLUDED_VCL_IPRIORITABLE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/IconThemeScanner.hxx b/vcl/inc/IconThemeScanner.hxx
new file mode 100644
index 0000000000..80f2405479
--- /dev/null
+++ b/vcl/inc/IconThemeScanner.hxx
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_ICONTHEMESCANNER_HXX
+#define INCLUDED_VCL_ICONTHEMESCANNER_HXX
+
+#include <vcl/dllapi.h>
+
+#include <rtl/ustring.hxx>
+#include <vcl/IconThemeInfo.hxx>
+
+#include <memory>
+#include <vector>
+
+// forward declaration of unit test class. Required for friend relationship.
+class IconThemeScannerTest;
+
+namespace vcl
+{
+/** This class scans a folder for icon themes and provides the results.
+ */
+class VCL_DLLPUBLIC IconThemeScanner
+{
+public:
+ /** Factory method to create the object.
+ * Provide a path to search for IconThemes.
+ */
+ static std::shared_ptr<IconThemeScanner> Create(std::u16string_view path);
+
+ /** This method will return the standard path where icon themes are located.
+ */
+ static OUString GetStandardIconThemePath();
+
+ const std::vector<IconThemeInfo>& GetFoundIconThemes() const { return mFoundIconThemes; }
+
+ /** Get the IconThemeInfo for a theme.
+ * If the theme id is not among the found themes, a std::runtime_error will be thrown.
+ * Use IconThemeIsInstalled() to check whether it is available.
+ */
+ const IconThemeInfo& GetIconThemeInfo(const OUString& themeId);
+
+ /** Checks whether the theme with the provided name has been found in the
+ * scanned directory.
+ */
+ bool IconThemeIsInstalled(const OUString& themeId) const;
+
+private:
+ IconThemeScanner();
+
+ /** Scan a directory for icon themes.
+ *
+ * @return
+ * There are several cases when this method will fail:
+ * - The directory does not exist
+ * - There are no files which match the pattern images_xxx.zip
+ */
+ void ScanDirectoryForIconThemes(std::u16string_view path);
+
+ /** Adds the provided icon theme by path.
+ */
+ bool AddIconThemeByPath(const OUString& path);
+
+ /** Scans the provided directory for icon themes.
+ * The returned strings will contain the URLs to the icon themes.
+ */
+ static std::vector<OUString> ReadIconThemesFromPath(const OUString& dir);
+
+ /** Check whether a single file is valid */
+ static bool FileIsValidIconTheme(const OUString&);
+
+ std::vector<IconThemeInfo> mFoundIconThemes;
+
+ friend class ::IconThemeScannerTest;
+};
+
+} // end namespace vcl
+
+#endif // INCLUDED_VCL_ICONTHEMESCANNER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/IconThemeSelector.hxx b/vcl/inc/IconThemeSelector.hxx
new file mode 100644
index 0000000000..65bfdf0063
--- /dev/null
+++ b/vcl/inc/IconThemeSelector.hxx
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_ICONTHEMESELECTOR_HXX
+#define INCLUDED_VCL_ICONTHEMESELECTOR_HXX
+
+#include <rtl/ustring.hxx>
+
+#include <vcl/dllapi.h>
+
+#include <vector>
+
+// forward declaration of unit test class. Required for friend relationship.
+class IconThemeSelectorTest;
+
+namespace vcl {
+class IconThemeInfo;
+
+/** This class helps to choose an icon theme from a list of installed themes.
+ *
+ * The following factors influence the selection:
+ * -# When high contrast mode is enabled, the high contrast icon theme is selected (if it is installed).
+ * -# When a preferred theme has been set (e.g., in the gnome desktop settings), that theme is selected.
+ */
+class VCL_DLLPUBLIC IconThemeSelector {
+public:
+ IconThemeSelector();
+
+ /** Select an icon theme from the list of installed themes.
+ *
+ * If high contrast mode has been enabled, the highcontrast theme will be selected (if it is available).
+ *
+ * @pre
+ * @p installedThemes must not be empty
+ */
+ OUString
+ SelectIconTheme(
+ const std::vector<IconThemeInfo>& installedThemes,
+ const OUString& theme
+ ) const;
+
+ /** Select the standard icon theme for a desktop environment from a list of installed themes.
+ *
+ * If a preferred theme has been set, this one will take precedence.
+ *
+ * The same logic as in SelectIconTheme() will apply.
+ *
+ * @pre
+ * @p installedThemes must not be empty
+ */
+ OUString
+ SelectIconThemeForDesktopEnvironment(
+ const std::vector<IconThemeInfo>& installedThemes,
+ const OUString& desktopEnvironment) const;
+
+ void
+ SetUseHighContrastTheme(bool);
+
+ /** Returns true if the PreferredIconTheme was changed */
+ bool
+ SetPreferredIconTheme(const OUString&, bool bDarkIconTheme);
+
+ bool
+ operator==(const vcl::IconThemeSelector&) const;
+
+ bool
+ operator!=(const vcl::IconThemeSelector&) const;
+
+private:
+ /** Return the first element of the themes, or the fallback if the vector is empty */
+ static OUString
+ ReturnFallback(const std::vector<IconThemeInfo>& installedThemes);
+
+ /** The name of the icon themes which are used as fallbacks */
+ static constexpr OUString FALLBACK_LIGHT_ICON_THEME_ID = u"colibre"_ustr;
+ static constexpr OUString FALLBACK_DARK_ICON_THEME_ID = u"colibre_dark"_ustr;
+
+ static OUString
+ GetIconThemeForDesktopEnvironment(const OUString& desktopEnvironment, bool bPreferDarkIconTheme);
+
+ OUString mPreferredIconTheme;
+ bool mUseHighContrastTheme;
+ bool mPreferDarkIconTheme;
+
+ friend class ::IconThemeSelectorTest;
+};
+
+} /* namespace vcl */
+
+#endif // INCLUDED_VCL_ICONTHEMESELECTOR_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/ImplLayoutArgs.hxx b/vcl/inc/ImplLayoutArgs.hxx
new file mode 100644
index 0000000000..faf170ca72
--- /dev/null
+++ b/vcl/inc/ImplLayoutArgs.hxx
@@ -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 <i18nlangtag/languagetag.hxx>
+#include <vcl/outdev.hxx>
+
+#include "impglyphitem.hxx"
+#include "ImplLayoutRuns.hxx"
+
+namespace vcl::text
+{
+class VCL_DLLPUBLIC ImplLayoutArgs
+{
+public:
+ // string related inputs
+ LanguageTag maLanguageTag;
+ SalLayoutFlags mnFlags;
+ const OUString& mrStr;
+ int mnMinCharPos;
+ int mnEndCharPos;
+
+ // performance hack
+ vcl::text::TextLayoutCache const* m_pTextLayoutCache;
+
+ // positioning related inputs
+ const double* mpDXArray; // in floating point pixel units
+ const sal_Bool* mpKashidaArray;
+ double mnLayoutWidth; // in pixel units
+ Degree10 mnOrientation; // in 0-3600 system
+
+ // data for bidi and glyph+script fallback
+ ImplLayoutRuns maRuns;
+ ImplLayoutRuns maFallbackRuns;
+
+ ImplLayoutArgs(OUString const& rStr, int nMinCharPos, int nEndCharPos, SalLayoutFlags nFlags,
+ LanguageTag aLanguageTag, vcl::text::TextLayoutCache const* pLayoutCache);
+
+ void SetLayoutWidth(double nWidth);
+ void SetDXArray(const double* pDXArray);
+ void SetKashidaArray(const sal_Bool* pKashidaArray);
+ void SetOrientation(Degree10 nOrientation);
+
+ void ResetPos();
+ bool GetNextPos(int* nCharPos, bool* bRTL);
+ bool GetNextRun(int* nMinRunPos, int* nEndRunPos, bool* bRTL);
+ void AddFallbackRun(int nMinRunPos, int nEndRunPos, bool bRTL);
+ bool HasDXArray() const { return mpDXArray; }
+
+ // methods used by BiDi and glyph fallback
+ bool HasFallbackRun() const;
+ bool PrepareFallback(const SalLayoutGlyphsImpl* pGlyphsImpl);
+
+private:
+ void AddRun(int nMinCharPos, int nEndCharPos, bool bRTL);
+};
+}
+
+// For nice SAL_INFO logging of ImplLayoutArgs values
+std::ostream& operator<<(std::ostream& s, vcl::text::ImplLayoutArgs const& rArgs);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/ImplLayoutRuns.hxx b/vcl/inc/ImplLayoutRuns.hxx
new file mode 100644
index 0000000000..dec9ca59dc
--- /dev/null
+++ b/vcl/inc/ImplLayoutRuns.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <vcl/dllapi.h>
+#include <boost/container/small_vector.hpp>
+
+// used for managing runs e.g. for BiDi, glyph and script fallback
+class VCL_DLLPUBLIC ImplLayoutRuns
+{
+private:
+ int mnRunIndex;
+ boost::container::small_vector<int, 8> maRuns;
+
+public:
+ ImplLayoutRuns() { mnRunIndex = 0; }
+
+ void Clear() { maRuns.clear(); }
+ void AddPos(int nCharPos, bool bRTL);
+ void AddRun(int nMinRunPos, int nEndRunPos, bool bRTL);
+
+ bool IsEmpty() const { return maRuns.empty(); }
+ void ResetPos() { mnRunIndex = 0; }
+ void NextRun() { mnRunIndex += 2; }
+ bool GetRun(int* nMinRunPos, int* nEndRunPos, bool* bRTL) const;
+ bool GetNextPos(int* nCharPos, bool* bRTL);
+ bool PosIsInRun(int nCharPos) const;
+ bool PosIsInAnyRun(int nCharPos) const;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/ImplOutDevData.hxx b/vcl/inc/ImplOutDevData.hxx
new file mode 100644
index 0000000000..f56caba1c3
--- /dev/null
+++ b/vcl/inc/ImplOutDevData.hxx
@@ -0,0 +1,51 @@
+/* -*- 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
+
+#include <tools/gen.hxx>
+
+#include <vcl/virdev.hxx>
+#include <vcl/vclptr.hxx>
+
+class VirtualDevice;
+
+namespace vcl
+{
+struct ControlLayoutData;
+}
+
+// #i75163#
+namespace basegfx
+{
+class B2DHomMatrix;
+}
+
+struct ImplOutDevData
+{
+ VclPtr<VirtualDevice> mpRotateDev;
+ vcl::ControlLayoutData* mpRecordLayout;
+ tools::Rectangle maRecordRect;
+
+ // #i75163#
+ basegfx::B2DHomMatrix* mpViewTransform;
+ basegfx::B2DHomMatrix* mpInverseViewTransform;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/NotebookbarPopup.hxx b/vcl/inc/NotebookbarPopup.hxx
new file mode 100644
index 0000000000..8c3bcfee1c
--- /dev/null
+++ b/vcl/inc/NotebookbarPopup.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <vcl/layout.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+
+/*
+ * Popup - shows hidden content, controls are moved to this popup
+ * and after close moved to the original parent
+ */
+
+class NotebookbarPopup final : public FloatingWindow
+{
+private:
+ VclPtr<VclHBox> m_pBox;
+ ScopedVclPtr<VclHBox> m_pParent;
+
+public:
+ explicit NotebookbarPopup(const VclPtr<VclHBox>& pParent);
+
+ virtual ~NotebookbarPopup() override;
+
+ VclHBox* getBox();
+
+ virtual void PopupModeEnd() override;
+
+ void hideSeparators(bool bHide);
+
+ void dispose() override;
+
+ void ApplyBackground(vcl::Window* pWindow);
+
+ void RemoveBackground(vcl::Window* pWindow);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/OptionalBox.hxx b/vcl/inc/OptionalBox.hxx
new file mode 100644
index 0000000000..05ab1cbf0c
--- /dev/null
+++ b/vcl/inc/OptionalBox.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 .
+ */
+
+#pragma once
+
+#include <vcl/layout.hxx>
+#include "IPrioritable.hxx"
+
+class OptionalBox final : public VclHBox, public vcl::IPrioritable
+{
+private:
+ bool m_bInFullView;
+
+public:
+ explicit OptionalBox(vcl::Window* pParent);
+ virtual ~OptionalBox() override;
+
+ void HideContent() override;
+ void ShowContent() override;
+ bool IsHidden() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/PriorityHBox.hxx b/vcl/inc/PriorityHBox.hxx
new file mode 100644
index 0000000000..8c5529ec88
--- /dev/null
+++ b/vcl/inc/PriorityHBox.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/layout.hxx>
+#include "IPrioritable.hxx"
+
+#include <vector>
+
+/*
+ * PriorityHBox is a VclHBox which hides its own children if there is no sufficient space.
+ * Hiding order can be modified using child's priorities. If a control have default
+ * priority assigned (VCL_PRIORITY_DEFAULT), it is always shown.
+ */
+
+class PriorityHBox : public VclHBox
+{
+protected:
+ bool m_bInitialized;
+
+ std::vector<vcl::IPrioritable*> m_aSortedChildren;
+
+ virtual int GetHiddenCount() const;
+
+ virtual void GetChildrenWithPriorities();
+
+public:
+ explicit PriorityHBox(vcl::Window* pParent);
+
+ virtual ~PriorityHBox() override;
+
+ void Initialize();
+
+ void SetSizeFromParent();
+
+ virtual Size calculateRequisition() const override;
+
+ virtual void Resize() override;
+
+ virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/PriorityMergedHBox.hxx b/vcl/inc/PriorityMergedHBox.hxx
new file mode 100644
index 0000000000..ed2f105f20
--- /dev/null
+++ b/vcl/inc/PriorityMergedHBox.hxx
@@ -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/toolkit/button.hxx>
+#include "NotebookbarPopup.hxx"
+#include "PriorityHBox.hxx"
+
+class PriorityMergedHBox final : public PriorityHBox
+{
+private:
+ VclPtr<PushButton> m_pButton;
+ VclPtr<NotebookbarPopup> m_pPopup;
+
+ DECL_LINK(PBClickHdl, Button*, void);
+
+public:
+ explicit PriorityMergedHBox(vcl::Window* pParent);
+
+ virtual ~PriorityMergedHBox() override { disposeOnce(); }
+
+ virtual void Resize() override;
+
+ virtual void dispose() override;
+
+ int GetHiddenCount() const override;
+
+ Size calculateRequisition() const override;
+
+ void GetChildrenWithPriorities() override{};
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/ResampleKernel.hxx b/vcl/inc/ResampleKernel.hxx
new file mode 100644
index 0000000000..ca54213f54
--- /dev/null
+++ b/vcl/inc/ResampleKernel.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_RESAMPLEKERNEL_HXX
+#define INCLUDED_VCL_RESAMPLEKERNEL_HXX
+
+#include <boost/math/special_functions/sinc.hpp>
+
+namespace vcl {
+
+// Resample kernels
+
+class Kernel
+{
+public:
+ Kernel() {}
+ virtual ~Kernel() {}
+
+ virtual double GetWidth() const = 0;
+ virtual double Calculate( double x ) const = 0;
+};
+
+class Lanczos3Kernel final : public Kernel
+{
+public:
+ Lanczos3Kernel() : Kernel () {}
+
+ virtual double GetWidth() const override { return 3.0; }
+ virtual double Calculate (double x) const override
+ {
+ return (-3.0 <= x && x < 3.0) ? SincFilter(x) * SincFilter( x / 3.0 ) : 0.0;
+ }
+
+ static double SincFilter(double x)
+ {
+ if (x == 0.0)
+ {
+ return 1.0;
+ }
+ x = x * M_PI;
+ return boost::math::sinc_pi(x, SincPolicy());
+ }
+
+private:
+ typedef boost::math::policies::policy<
+ boost::math::policies::promote_double<false> > SincPolicy;
+};
+
+class BicubicKernel final : public Kernel
+{
+public:
+ BicubicKernel() : Kernel () {}
+
+private:
+ virtual double GetWidth() const override { return 2.0; }
+ virtual double Calculate (double x) const override
+ {
+ if (x < 0.0)
+ {
+ x = -x;
+ }
+
+ if (x <= 1.0)
+ {
+ return (1.5 * x - 2.5) * x * x + 1.0;
+ }
+ else if (x < 2.0)
+ {
+ return ((-0.5 * x + 2.5) * x - 4) * x + 2;
+ }
+ return 0.0;
+ }
+};
+
+class BilinearKernel final : public Kernel
+{
+public:
+ BilinearKernel() : Kernel () {}
+
+private:
+ virtual double GetWidth() const override { return 1.0; }
+ virtual double Calculate (double x) const override
+ {
+ if (x < 0.0)
+ {
+ x = -x;
+ }
+ if (x < 1.0)
+ {
+ return 1.0-x;
+ }
+ return 0.0;
+ }
+};
+
+} // namespace vcl
+
+#endif // INCLUDED_VCL_RESAMPLEKERNEL_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/SalGradient.hxx b/vcl/inc/SalGradient.hxx
new file mode 100644
index 0000000000..c183d75506
--- /dev/null
+++ b/vcl/inc/SalGradient.hxx
@@ -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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_SALGRADIENT_HXX
+#define INCLUDED_VCL_INC_SALGRADIENT_HXX
+
+#include <basegfx/point/b2dpoint.hxx>
+#include <tools/color.hxx>
+
+struct SalGradientStop
+{
+ Color maColor;
+ float mfOffset;
+
+ SalGradientStop(Color const& rColor, float fOffset)
+ : maColor(rColor)
+ , mfOffset(fOffset)
+ {
+ }
+};
+
+struct SalGradient
+{
+ basegfx::B2DPoint maPoint1;
+ basegfx::B2DPoint maPoint2;
+ std::vector<SalGradientStop> maStops;
+};
+
+#endif // INCLUDED_VCL_INC_SALGRADIENT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/TextLayoutCache.hxx b/vcl/inc/TextLayoutCache.hxx
new file mode 100644
index 0000000000..304c6d51eb
--- /dev/null
+++ b/vcl/inc/TextLayoutCache.hxx
@@ -0,0 +1,87 @@
+/* -*- 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
+
+#include <sal/types.h>
+#include <rtl/ustring.hxx>
+#include <o3tl/hash_combine.hxx>
+
+#include <vcl/dllapi.h>
+
+#include <unicode/uscript.h>
+
+#include <vector>
+
+namespace vcl::text
+{
+struct Run
+{
+ int32_t nStart;
+ int32_t nEnd;
+ UScriptCode nCode;
+ Run(int32_t nStart_, int32_t nEnd_, UScriptCode nCode_)
+ : nStart(nStart_)
+ , nEnd(nEnd_)
+ , nCode(nCode_)
+ {
+ }
+};
+
+class VCL_DLLPUBLIC TextLayoutCache
+{
+public:
+ std::vector<vcl::text::Run> runs;
+ TextLayoutCache(sal_Unicode const* pStr, sal_Int32 const nEnd);
+ // Creates a cached instance.
+ static std::shared_ptr<const vcl::text::TextLayoutCache> Create(OUString const&);
+};
+
+struct FirstCharsStringHash
+{
+ size_t operator()(const OUString& str) const
+ {
+ // Strings passed to GenericSalLayout::CreateTextLayoutCache() may be very long,
+ // and computing an entire hash could almost negate the gain of hashing. Hash just first
+ // characters, that should be good enough.
+ size_t hash
+ = rtl_ustr_hashCode_WithLength(str.getStr(), std::min<size_t>(100, str.getLength()));
+ o3tl::hash_combine(hash, str.getLength());
+ return hash;
+ }
+};
+
+struct FastStringCompareEqual
+{
+ bool operator()(const OUString& str1, const OUString& str2) const
+ {
+ // Strings passed to GenericSalLayout::CreateTextLayoutCache() may be very long,
+ // and OUString operator == compares backwards and using hard-written code, while
+ // memcmp() compares much faster.
+ if (str1.getLength() != str2.getLength())
+ return false;
+ if (str1.getStr() == str2.getStr())
+ return true;
+ return memcmp(str1.getStr(), str2.getStr(), str1.getLength() * sizeof(str1.getStr()[0]))
+ == 0;
+ }
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/WidgetDrawInterface.hxx b/vcl/inc/WidgetDrawInterface.hxx
new file mode 100644
index 0000000000..78d5d76254
--- /dev/null
+++ b/vcl/inc/WidgetDrawInterface.hxx
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_INC_WIDGETDRAWINTERFACE_HXX
+#define INCLUDED_VCL_INC_WIDGETDRAWINTERFACE_HXX
+
+#include <vcl/dllapi.h>
+#include <vcl/salnativewidgets.hxx>
+#include <vcl/settings.hxx>
+
+namespace vcl
+{
+class VCL_PLUGIN_PUBLIC WidgetDrawInterface
+{
+public:
+ virtual ~WidgetDrawInterface() COVERITY_NOEXCEPT_FALSE {}
+
+ /**
+ * Query the platform layer for native control support.
+ *
+ * @param [in] eType The widget type.
+ * @param [in] ePart The part of the widget.
+ * @return true if the platform supports native drawing of the widget type defined by part.
+ */
+ virtual inline bool isNativeControlSupported(ControlType eType, ControlPart ePart);
+
+ /**
+ * Query if a position is inside the native widget part.
+ *
+ * Mainly used for scrollbars.
+ *
+ * @param [in] eType The widget type.
+ * @param [in] ePart The part of the widget.
+ * @param [in] rBoundingControlRegion The bounding Rectangle of
+ the complete control in VCL frame coordinates.
+ * @param [in] aPos The position to check the hit.
+ * @param [out] rIsInside true, if \a aPos was inside the native widget.
+ * @return true, if the query was successful.
+ */
+ virtual inline bool hitTestNativeControl(ControlType eType, ControlPart ePart,
+ const tools::Rectangle& rBoundingControlRegion,
+ const Point& aPos, bool& rIsInside);
+
+ /**
+ * Draw the requested control.
+ *
+ * @param [in] eType The widget type.
+ * @param [in] ePart The part of the widget.
+ * @param [in] rBoundingControlRegion The bounding rectangle of
+ * the complete control in VCL frame coordinates.
+ * @param [in] eState The general state of the control (enabled, focused, etc.).
+ * @param [in] aValue Addition control specific information.
+ * @param [in] aCaption A caption or title string (like button text etc.).
+ * @param [in] rBackgroundColor Background color for the control (may be COL_AUTO)
+ * @return true, if the control could be drawn.
+ */
+ virtual inline bool drawNativeControl(ControlType eType, ControlPart ePart,
+ const tools::Rectangle& rBoundingControlRegion,
+ ControlState eState, const ImplControlValue& aValue,
+ const OUString& aCaptions, const Color& rBackgroundColor);
+
+ /**
+ * Get the native control regions for the control part.
+ *
+ * If the return value is true, \a rNativeBoundingRegion contains
+ * the true bounding region covered by the control including any
+ * adornment, while \a rNativeContentRegion contains the area
+ * within the control that can be safely drawn into without drawing over
+ * the borders of the control.
+ *
+ * @param [in] eType Type of the widget.
+ * @param [in] ePart Specification of the widget's part if it consists of more than one.
+ * @param [in] rBoundingControlRegion The bounding region of the control in VCL frame coordinates.
+ * @param [in] eState The general state of the control (enabled, focused, etc.).
+ * @param [in] aValue Addition control specific information.
+ * @param [in] aCaption A caption or title string (like button text etc.).
+ * @param [out] rNativeBoundingRegion The region covered by the control including any adornment.
+ * @param [out] rNativeContentRegion The region within the control that can be safely drawn into.
+ * @return true, if the regions are filled.
+ */
+ virtual inline bool getNativeControlRegion(ControlType eType, ControlPart ePart,
+ const tools::Rectangle& rBoundingControlRegion,
+ ControlState eState, const ImplControlValue& aValue,
+ const OUString& aCaption,
+ tools::Rectangle& rNativeBoundingRegion,
+ tools::Rectangle& rNativeContentRegion);
+
+ virtual inline bool updateSettings(AllSettings& rSettings);
+};
+
+bool WidgetDrawInterface::isNativeControlSupported(ControlType, ControlPart) { return false; }
+
+bool WidgetDrawInterface::hitTestNativeControl(ControlType, ControlPart, const tools::Rectangle&,
+ const Point&, bool&)
+{
+ return false;
+}
+
+bool WidgetDrawInterface::drawNativeControl(ControlType, ControlPart, const tools::Rectangle&,
+ ControlState, const ImplControlValue&, const OUString&,
+ const Color& /*rBackgroundColor*/)
+{
+ return false;
+}
+
+bool WidgetDrawInterface::getNativeControlRegion(ControlType, ControlPart, const tools::Rectangle&,
+ ControlState, const ImplControlValue&,
+ const OUString&, tools::Rectangle&,
+ tools::Rectangle&)
+{
+ return false;
+}
+
+bool WidgetDrawInterface::updateSettings(AllSettings&) { return false; }
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/WidgetThemeLibraryTypes.hxx b/vcl/inc/WidgetThemeLibraryTypes.hxx
new file mode 100644
index 0000000000..b3270bf23e
--- /dev/null
+++ b/vcl/inc/WidgetThemeLibraryTypes.hxx
@@ -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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_WIDGETTHEMETYPES_HXX
+#define INCLUDED_VCL_INC_WIDGETTHEMETYPES_HXX
+
+#include <o3tl/typed_flags_set.hxx>
+
+/**
+ * These types are all based on the supported variants
+ * vcl/salnativewidgets.hxx and must be kept in-sync.
+ **/
+
+/* Control Types:
+ *
+ * Specify the overall, whole control
+ * type (as opposed to parts of the
+ * control if it were composite).
+ */
+
+enum class ControlType {
+// for use in general purpose ImplControlValue
+ Generic = 0,
+// Normal PushButton/Command Button
+ Pushbutton = 1,
+// Normal single radio button
+ Radiobutton = 2,
+// Normal single checkbox
+ Checkbox = 10,
+// Combobox, i.e. a ListBox
+// that allows data entry by user
+ Combobox = 20,
+// Control that allows text entry
+ Editbox = 30,
+// Control that allows text entry, but without the usual border
+// Has to be handled separately, because this one cannot handle
+// ControlPart::HasBackgroundTexture, which is drawn in the edit box'es
+// border window.
+ EditboxNoBorder = 31,
+// Control that allows text entry
+// ( some systems distinguish between single and multi line edit boxes )
+ MultilineEditbox = 32,
+// Control that pops up a menu,
+// but does NOT allow data entry
+ Listbox = 35,
+// An edit field together with two little
+// buttons on the side (aka spin field)
+ Spinbox = 40,
+// Two standalone spin buttons
+// without an edit field
+ SpinButtons = 45,
+// A single tab
+ TabItem = 50,
+// The border around a tab area,
+// but without the tabs themselves.
+// May have a gap at the top for
+// the active tab
+ TabPane = 55,
+// The background to the tab area
+ TabHeader = 56,
+// Background of a Tab Pane
+ TabBody = 57,
+// Normal scrollbar, including
+// all parts like slider, buttons
+ Scrollbar = 60,
+ Slider = 65,
+// A separator line
+ Fixedline = 80,
+// A toolbar control with buttons and a grip
+ Toolbar = 100,
+// The menubar
+ Menubar = 120,
+// popup menu
+ MenuPopup = 121,
+ Progress = 131,
+// Progress bar for the intro window
+// (aka splash screen), in case some
+// wants native progress bar in the
+// application but not for the splash
+// screen (used in desktop/)
+ IntroProgress = 132,
+// tool tips
+ Tooltip = 140,
+// to draw the implemented theme
+ WindowBackground = 150,
+//to draw border of frames natively
+ Frame = 160,
+// for nodes in listviews
+// used in svtools/source/contnr/svtreebx.cxx
+ ListNode = 170,
+// nets between elements of listviews
+// with nodes
+ ListNet = 171,
+// for list headers
+ ListHeader = 172,
+};
+
+
+/* Control Parts:
+ *
+ * Uniquely identify a part of a control,
+ * for example the slider of a scroll bar.
+ */
+
+enum class ControlPart
+{
+ NONE = 0,
+ Entire = 1,
+ ListboxWindow = 5, // the static listbox window containing the list
+ Button = 100,
+ ButtonUp = 101,
+ ButtonDown = 102, // Also for ComboBoxes/ListBoxes
+ ButtonLeft = 103,
+ ButtonRight = 104,
+ AllButtons = 105,
+ SeparatorHorz = 106,
+ SeparatorVert = 107,
+ TrackHorzLeft = 200,
+ TrackVertUpper = 201,
+ TrackHorzRight = 202,
+ TrackVertLower = 203,
+ TrackHorzArea = 204,
+ TrackVertArea = 205,
+ Arrow = 220,
+ ThumbHorz = 210, // Also used as toolbar grip
+ ThumbVert = 211, // Also used as toolbar grip
+ MenuItem = 250,
+ MenuItemCheckMark = 251,
+ MenuItemRadioMark = 252,
+ Separator = 253,
+ SubmenuArrow = 254,
+
+/* #i77549#
+ HACK: for scrollbars in case of thumb rect, page up and page down rect we
+ abuse the HitTestNativeScrollbar interface. All theming engines but aqua
+ are actually able to draw the thumb according to our internal representation.
+ However aqua 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.
+*/
+
+/** The edit field part of a control, e.g. of the combo box.
+
+ Currently used just for combo boxes and just for GetNativeControlRegion().
+ It is valid only if GetNativeControlRegion() supports ControlPart::ButtonDown as
+ well.
+*/
+ SubEdit = 300,
+
+// For controls that require the entire background
+// to be drawn first, and then other pieces over top.
+// (GTK+ scrollbars for example). Control region passed
+// in to draw this part is expected to be the entire
+// area of the control.
+// A control may respond to one or both.
+ DrawBackgroundHorz = 1000,
+ DrawBackgroundVert = 1001,
+
+// GTK+ also draws tabs right->left since there is a
+// hardcoded 2 pixel overlap between adjacent tabs
+ TabsDrawRtl = 3000,
+
+// For themes that do not want to have the focus
+// rectangle part drawn by VCL but take care of the
+// whole inner control part by themselves
+// eg, listboxes or comboboxes or spinbuttons
+ HasBackgroundTexture = 4000,
+
+// For scrollbars that have 3 buttons (most KDE themes)
+ HasThreeButtons = 5000,
+
+ BackgroundWindow = 6000,
+ BackgroundDialog = 6001,
+
+//to draw natively the border of frames
+ Border = 7000,
+
+//to draw natively the focus rects
+ Focus = 8000
+};
+
+/* Control State:
+ *
+ * Specify how a particular part of the control
+ * is to be drawn. Constants are bitwise OR-ed
+ * together to compose a final drawing state.
+ * A _disabled_ state is assumed by the drawing
+ * functions until an ENABLED or HIDDEN is passed
+ * in the ControlState.
+ */
+enum class ControlState {
+ NONE = 0,
+ ENABLED = 0x0001,
+ FOCUSED = 0x0002,
+ PRESSED = 0x0004,
+ ROLLOVER = 0x0008,
+ DEFAULT = 0x0020,
+ SELECTED = 0x0040,
+ DOUBLEBUFFERING = 0x4000, ///< Set when the control is painted using double-buffering via VirtualDevice.
+ CACHING_ALLOWED = 0x8000, ///< Set when the control is completely visible (i.e. not clipped).
+};
+
+template<> struct o3tl::typed_flags<ControlState>: o3tl::is_typed_flags<ControlState, 0xC06F> {};
+
+/* ButtonValue:
+ *
+ * Identifies the tri-state value options
+ * that buttons allow
+ */
+
+enum class ButtonValue {
+ DontKnow,
+ On,
+ Off,
+ Mixed
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/accel.hxx b/vcl/inc/accel.hxx
new file mode 100644
index 0000000000..c7140db221
--- /dev/null
+++ b/vcl/inc/accel.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <tools/solar.h>
+#include <tools/link.hxx>
+#include <vcl/keycod.hxx>
+#include <memory>
+#include <map>
+#include <vector>
+
+class CommandEvent;
+
+class Accelerator;
+
+class ImplAccelEntry
+{
+public:
+ Accelerator* mpAccel;
+ Accelerator* mpAutoAccel;
+ vcl::KeyCode maKeyCode;
+ sal_uInt16 mnId;
+ bool mbEnabled;
+};
+
+typedef ::std::vector< std::unique_ptr<ImplAccelEntry> > ImplAccelList;
+
+class Accelerator
+{
+ friend class ImplAccelManager;
+
+private:
+ typedef ::std::map< sal_uLong, ImplAccelEntry* > ImplAccelMap;
+ ImplAccelMap maKeyMap; // for keycodes, generated with a code
+ ImplAccelList maIdList; // Id-List
+ Link<Accelerator&,void> maActivateHdl;
+ Link<Accelerator&,void> maSelectHdl;
+
+ // Will be set by AcceleratorManager
+ sal_uInt16 mnCurId;
+ bool* mpDel;
+
+ void ImplInit();
+ void ImplCopyData( const Accelerator& rAccelData );
+ void ImplDeleteData();
+ void ImplInsertAccel(sal_uInt16 nItemId, const vcl::KeyCode& rKeyCode,
+ bool bEnable, Accelerator* pAutoAccel);
+
+ ImplAccelEntry* ImplGetAccelData( const vcl::KeyCode& rKeyCode ) const;
+
+public:
+ Accelerator();
+ Accelerator( const Accelerator& rAccel );
+ ~Accelerator();
+
+ void Activate();
+ void Select();
+
+ void InsertItem( sal_uInt16 nItemId, const vcl::KeyCode& rKeyCode );
+
+ sal_uInt16 GetCurItemId() const { return mnCurId; }
+
+ sal_uInt16 GetItemCount() const;
+ sal_uInt16 GetItemId( sal_uInt16 nPos ) const;
+
+ Accelerator* GetAccel( sal_uInt16 nItemId ) const;
+
+ void SetActivateHdl( const Link<Accelerator&,void>& rLink ) { maActivateHdl = rLink; }
+ void SetSelectHdl( const Link<Accelerator&,void>& rLink ) { maSelectHdl = rLink; }
+
+ Accelerator& operator=( const Accelerator& rAccel );
+};
+
+bool ImplGetKeyCode( KeyFuncType eFunc, sal_uInt16& rCode1, sal_uInt16& rCode2, sal_uInt16& rCode3, sal_uInt16& rCode4 );
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/accmgr.hxx b/vcl/inc/accmgr.hxx
new file mode 100644
index 0000000000..55e028f69a
--- /dev/null
+++ b/vcl/inc/accmgr.hxx
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_ACCMGR_HXX
+#define INCLUDED_VCL_INC_ACCMGR_HXX
+
+#include <vector>
+#include <optional>
+
+#include <vcl/keycod.hxx>
+
+class Accelerator;
+
+class ImplAccelManager
+{
+private:
+ std::optional<std::vector< Accelerator* >> mxAccelList;
+ std::optional<std::vector< Accelerator* >> mxSequenceList;
+
+public:
+ ImplAccelManager()
+ {
+ }
+ ~ImplAccelManager();
+
+ bool InsertAccel( Accelerator* pAccel );
+ void RemoveAccel( Accelerator const * pAccel );
+
+ void EndSequence();
+ void FlushAccel() { EndSequence(); }
+
+ bool IsAccelKey( const vcl::KeyCode& rKeyCode );
+};
+
+#endif // INCLUDED_VCL_INC_ACCMGR_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/android/androidinst.hxx b/vcl/inc/android/androidinst.hxx
new file mode 100644
index 0000000000..58ebdbb253
--- /dev/null
+++ b/vcl/inc/android/androidinst.hxx
@@ -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/.
+ */
+
+#pragma once
+
+#include <jni.h>
+#include <android/input.h>
+#include <android/log.h>
+#include <android/native_window.h>
+#include <headless/svpinst.hxx>
+#include <headless/svpframe.hxx>
+
+#define LOGTAG "LibreOffice/androidinst"
+#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOGTAG, __VA_ARGS__))
+#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOGTAG, __VA_ARGS__))
+
+class AndroidSalFrame;
+class AndroidSalInstance : public SvpSalInstance
+{
+ // This JNIEnv is valid only in the thread where this
+ // AndroidSalInstance object is created, which is the "LO" thread
+ // in which soffice_main() runs
+ JNIEnv* m_pJNIEnv;
+
+public:
+ AndroidSalInstance(std::unique_ptr<SalYieldMutex> pMutex);
+ virtual ~AndroidSalInstance();
+ static AndroidSalInstance* getInstance();
+
+ virtual SalSystem* CreateSalSystem();
+
+ // frame management
+ void GetWorkArea(AbsoluteScreenPixelRectangle& rRect);
+ SalFrame* CreateFrame(SalFrame* pParent, SalFrameStyleFlags nStyle);
+ SalFrame* CreateChildFrame(SystemParentData* pParent, SalFrameStyleFlags nStyle);
+
+ // mainloop pieces
+ virtual bool AnyInput(VclInputFlags nType);
+
+ virtual void updateMainThread();
+ virtual void releaseMainThread();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/animate/AnimationRenderer.hxx b/vcl/inc/animate/AnimationRenderer.hxx
new file mode 100644
index 0000000000..bc86e65e3f
--- /dev/null
+++ b/vcl/inc/animate/AnimationRenderer.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <vcl/dllapi.h>
+#include <vcl/animate/Animation.hxx>
+#include <vcl/vclptr.hxx>
+
+class Animation;
+class OutputDevice;
+class VirtualDevice;
+struct AnimationFrame;
+
+struct AnimationData
+{
+ Point maOriginStartPt;
+ Size maStartSize;
+ VclPtr<OutputDevice> mpRenderContext;
+ AnimationRenderer* mpRendererData;
+ tools::Long mnRendererId;
+ bool mbIsPaused;
+
+ AnimationData();
+};
+
+
+class VCL_DLLPUBLIC AnimationRenderer
+{
+private:
+ Animation* mpParent;
+ VclPtr<OutputDevice> mpRenderContext;
+ tools::Long mnRendererId;
+ Point maOriginPt;
+ Point maDispPt;
+ Point maRestPt;
+ Size maLogicalSize;
+ Size maSizePx;
+ Size maDispSz;
+ Size maRestSz;
+ vcl::Region maClip;
+ VclPtr<VirtualDevice> mpBackground;
+ VclPtr<VirtualDevice> mpRestore;
+ sal_uLong mnActIndex;
+ Disposal meLastDisposal;
+ bool mbIsPaused;
+ bool mbIsMarked;
+ bool mbIsMirroredHorizontally;
+ bool mbIsMirroredVertically;
+
+public:
+ AnimationRenderer( Animation* pParent, OutputDevice* pOut,
+ const Point& rPt, const Size& rSz, sal_uLong nRendererId,
+ OutputDevice* pFirstFrameOutDev = nullptr );
+ AnimationRenderer(AnimationRenderer&&) = delete;
+ ~AnimationRenderer();
+
+ bool matches(const OutputDevice* pOut, tools::Long nRendererId) const;
+ void drawToIndex( sal_uLong nIndex );
+ void draw( sal_uLong nIndex, VirtualDevice* pVDev=nullptr );
+ void repaint();
+ AnimationData* createAnimationData() const;
+
+ void getPosSize( const AnimationFrame& rAnm, Point& rPosPix, Size& rSizePix );
+
+ const Point& getOriginPosition() const { return maOriginPt; }
+
+ const Size& getOutSizePix() const { return maSizePx; }
+
+ void pause( bool bIsPaused ) { mbIsPaused = bIsPaused; }
+ bool isPaused() const { return mbIsPaused; }
+
+ void setMarked( bool bIsMarked ) { mbIsMarked = bIsMarked; }
+ bool isMarked() const { return mbIsMarked; }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/bitmap/BitmapColorizeFilter.hxx b/vcl/inc/bitmap/BitmapColorizeFilter.hxx
new file mode 100644
index 0000000000..ae511b322d
--- /dev/null
+++ b/vcl/inc/bitmap/BitmapColorizeFilter.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/.
+ *
+ */
+
+#pragma once
+
+#include <tools/color.hxx>
+#include <vcl/BitmapFilter.hxx>
+
+class BitmapColorizeFilter final : public BitmapFilter
+{
+public:
+ BitmapColorizeFilter(Color aColor)
+ : maColor(aColor)
+ {
+ }
+
+ virtual BitmapEx execute(BitmapEx const& rBitmapEx) const override;
+
+private:
+ Color maColor;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/bitmap/BitmapDisabledImageFilter.hxx b/vcl/inc/bitmap/BitmapDisabledImageFilter.hxx
new file mode 100644
index 0000000000..e1d9d21a9d
--- /dev/null
+++ b/vcl/inc/bitmap/BitmapDisabledImageFilter.hxx
@@ -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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_BITMAP_BITMAPDISABLEDIMAGEFILTER_HXX
+#define INCLUDED_VCL_INC_BITMAP_BITMAPDISABLEDIMAGEFILTER_HXX
+
+#include <vcl/BitmapFilter.hxx>
+
+class VCL_DLLPUBLIC BitmapDisabledImageFilter final : public BitmapFilter
+{
+public:
+ BitmapDisabledImageFilter() {}
+
+ virtual BitmapEx execute(BitmapEx const& rBitmapEx) const override;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/bitmap/BitmapFastScaleFilter.hxx b/vcl/inc/bitmap/BitmapFastScaleFilter.hxx
new file mode 100644
index 0000000000..bea516c9e0
--- /dev/null
+++ b/vcl/inc/bitmap/BitmapFastScaleFilter.hxx
@@ -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/.
+ *
+ */
+
+#ifndef VCL_INC_BITMAP_BITMAPFASTSCALEFILTER_HXX
+#define VCL_INC_BITMAP_BITMAPFASTSCALEFILTER_HXX
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapFilter.hxx>
+
+class BitmapFastScaleFilter final : public BitmapFilter
+{
+public:
+ explicit BitmapFastScaleFilter(double fScaleX, double fScaleY)
+ : mfScaleX(fScaleX)
+ , mfScaleY(fScaleY)
+ {
+ }
+
+ virtual BitmapEx execute(BitmapEx const& rBitmapEx) const override;
+
+private:
+ double mfScaleX;
+ double mfScaleY;
+ Size maSize;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/bitmap/BitmapInterpolateScaleFilter.hxx b/vcl/inc/bitmap/BitmapInterpolateScaleFilter.hxx
new file mode 100644
index 0000000000..7d17c97d16
--- /dev/null
+++ b/vcl/inc/bitmap/BitmapInterpolateScaleFilter.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/.
+ *
+ */
+
+#ifndef VCL_INC_BITMAP_BITMAPINTERPOLATESCALEFILTER_HXX
+#define VCL_INC_BITMAP_BITMAPINTERPOLATESCALEFILTER_HXX
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapFilter.hxx>
+
+class BitmapInterpolateScaleFilter final : public BitmapFilter
+{
+public:
+ explicit BitmapInterpolateScaleFilter(double fScaleX, double fScaleY)
+ : mfScaleX(fScaleX)
+ , mfScaleY(fScaleY)
+ {
+ }
+
+ virtual BitmapEx execute(BitmapEx const& rBitmapEx) const override;
+
+private:
+ double mfScaleX;
+ double mfScaleY;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/bitmap/BitmapLightenFilter.hxx b/vcl/inc/bitmap/BitmapLightenFilter.hxx
new file mode 100644
index 0000000000..98f2493285
--- /dev/null
+++ b/vcl/inc/bitmap/BitmapLightenFilter.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_INC_BITMAP_BITMAPLIGHTENFILTER_HXX
+#define INCLUDED_VCL_INC_BITMAP_BITMAPLIGHTENFILTER_HXX
+
+#include <vcl/BitmapFilter.hxx>
+
+class BitmapLightenFilter final : public BitmapFilter
+{
+public:
+ virtual BitmapEx execute(BitmapEx const& rBitmapEx) const override;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/bitmap/BitmapMaskToAlphaFilter.hxx b/vcl/inc/bitmap/BitmapMaskToAlphaFilter.hxx
new file mode 100644
index 0000000000..355d066846
--- /dev/null
+++ b/vcl/inc/bitmap/BitmapMaskToAlphaFilter.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 <vcl/BitmapFilter.hxx>
+
+class BitmapMaskToAlphaFilter final : public BitmapFilter
+{
+public:
+ virtual BitmapEx execute(BitmapEx const& rBitmapEx) const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/bitmap/BitmapScaleConvolutionFilter.hxx b/vcl/inc/bitmap/BitmapScaleConvolutionFilter.hxx
new file mode 100644
index 0000000000..1c9bb8e330
--- /dev/null
+++ b/vcl/inc/bitmap/BitmapScaleConvolutionFilter.hxx
@@ -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 .
+ */
+
+#ifndef VCL_INC_BITMAP_BITMAPSCALECONVOLUTIONFILTER_HXX
+#define VCL_INC_BITMAP_BITMAPSCALECONVOLUTIONFILTER_HXX
+
+#include <vcl/BitmapFilter.hxx>
+
+#include <ResampleKernel.hxx>
+
+namespace vcl
+{
+class BitmapScaleConvolutionFilter : public BitmapFilter
+{
+protected:
+ BitmapScaleConvolutionFilter(const double& rScaleX, const double& rScaleY,
+ std::unique_ptr<Kernel> pKernel)
+ : mxKernel(std::move(pKernel))
+ , mrScaleX(rScaleX)
+ , mrScaleY(rScaleY)
+ {
+ }
+
+ virtual BitmapEx execute(BitmapEx const& rBitmap) const override;
+
+private:
+ std::unique_ptr<Kernel> mxKernel;
+ double mrScaleX;
+ double mrScaleY;
+};
+
+class VCL_DLLPUBLIC BitmapScaleBilinearFilter final : public BitmapScaleConvolutionFilter
+{
+public:
+ BitmapScaleBilinearFilter(const double& rScaleX, const double& rScaleY)
+ : BitmapScaleConvolutionFilter(rScaleX, rScaleY, std::make_unique<BilinearKernel>())
+ {
+ }
+};
+
+class VCL_DLLPUBLIC BitmapScaleBicubicFilter final : public BitmapScaleConvolutionFilter
+{
+public:
+ BitmapScaleBicubicFilter(const double& rScaleX, const double& rScaleY)
+ : BitmapScaleConvolutionFilter(rScaleX, rScaleY, std::make_unique<BicubicKernel>())
+ {
+ }
+};
+
+class VCL_DLLPUBLIC BitmapScaleLanczos3Filter final : public BitmapScaleConvolutionFilter
+{
+public:
+ BitmapScaleLanczos3Filter(const double& rScaleX, const double& rScaleY)
+ : BitmapScaleConvolutionFilter(rScaleX, rScaleY, std::make_unique<Lanczos3Kernel>())
+ {
+ }
+};
+}
+
+#endif // VCL_INC_BITMAPSCALECONVOLUTIONFILTER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/bitmap/BitmapScaleSuperFilter.hxx b/vcl/inc/bitmap/BitmapScaleSuperFilter.hxx
new file mode 100644
index 0000000000..46c21d8d78
--- /dev/null
+++ b/vcl/inc/bitmap/BitmapScaleSuperFilter.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_INC_BITMAP_BITMAPSCALESUPER_HXX
+#define INCLUDED_VCL_INC_BITMAP_BITMAPSCALESUPER_HXX
+
+#include <vcl/BitmapFilter.hxx>
+
+class BitmapScaleSuperFilter final : public BitmapFilter
+{
+public:
+ BitmapScaleSuperFilter(const double& rScaleX, const double& rScaleY);
+ virtual ~BitmapScaleSuperFilter() override;
+
+ virtual BitmapEx execute(BitmapEx const& rBitmap) const override;
+
+private:
+ double mrScaleX;
+ double mrScaleY;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/bitmap/Octree.hxx b/vcl/inc/bitmap/Octree.hxx
new file mode 100644
index 0000000000..3c9b5eb270
--- /dev/null
+++ b/vcl/inc/bitmap/Octree.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_VCL_INC_OCTREE_HXX
+#define INCLUDED_VCL_INC_OCTREE_HXX
+
+#include <vcl/dllapi.h>
+#include <vcl/BitmapColor.hxx>
+#include <vcl/BitmapPalette.hxx>
+#include <tools/solar.h>
+
+struct OctreeNode
+{
+ sal_uLong nCount = 0;
+ sal_uLong nRed = 0;
+ sal_uLong nGreen = 0;
+ sal_uLong nBlue = 0;
+ std::unique_ptr<OctreeNode> pChild[8];
+ OctreeNode* pNext = nullptr;
+ sal_uInt16 nPalIndex = 0;
+ bool bLeaf = false;
+};
+
+class BitmapReadAccess;
+
+class VCL_PLUGIN_PUBLIC Octree
+{
+private:
+ void CreatePalette(OctreeNode* pNode);
+ void GetPalIndex(const OctreeNode* pNode, BitmapColor const& color);
+
+ SAL_DLLPRIVATE void add(std::unique_ptr<OctreeNode>& rpNode, BitmapColor const& color);
+ SAL_DLLPRIVATE void reduce();
+
+ BitmapPalette maPalette;
+ sal_uLong mnLeafCount;
+ sal_uLong mnLevel;
+ std::unique_ptr<OctreeNode> pTree;
+ std::vector<OctreeNode*> mpReduce;
+ sal_uInt16 mnPalIndex;
+
+public:
+ Octree(const BitmapReadAccess& rReadAcc, sal_uLong nColors);
+ ~Octree();
+
+ const BitmapPalette& GetPalette();
+ sal_uInt16 GetBestPaletteIndex(const BitmapColor& rColor);
+};
+
+class InverseColorMap
+{
+private:
+ std::vector<sal_uInt8> mpBuffer;
+ std::vector<sal_uInt8> mpMap;
+
+ void ImplCreateBuffers();
+
+public:
+ explicit InverseColorMap(const BitmapPalette& rPal);
+ ~InverseColorMap();
+
+ sal_uInt16 GetBestPaletteIndex(const BitmapColor& rColor);
+};
+
+#endif // INCLUDED_VCL_INC_OCTREE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/bitmap/ScanlineTools.hxx b/vcl/inc/bitmap/ScanlineTools.hxx
new file mode 100644
index 0000000000..99ce5dc33a
--- /dev/null
+++ b/vcl/inc/bitmap/ScanlineTools.hxx
@@ -0,0 +1,236 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_INC_BITMAP_SCANLINETOOLS_HXX
+#define INCLUDED_VCL_INC_BITMAP_SCANLINETOOLS_HXX
+
+#include <tools/color.hxx>
+#include <vcl/BitmapPalette.hxx>
+
+namespace vcl::bitmap
+{
+class ScanlineTransformer
+{
+public:
+ virtual void startLine(sal_uInt8* pLine) = 0;
+ virtual void skipPixel(sal_uInt32 nPixel) = 0;
+ virtual Color readPixel() = 0;
+ virtual void writePixel(Color nColor) = 0;
+
+ virtual ~ScanlineTransformer() = default;
+};
+
+class ScanlineTransformer_ARGB final : public ScanlineTransformer
+{
+private:
+ sal_uInt8* pData;
+
+public:
+ virtual void startLine(sal_uInt8* pLine) override { pData = pLine; }
+
+ virtual void skipPixel(sal_uInt32 nPixel) override { pData += nPixel << 2; }
+
+ virtual Color readPixel() override
+ {
+ const Color aColor(ColorAlpha, pData[4], pData[1], pData[2], pData[3]);
+ pData += 4;
+ return aColor;
+ }
+
+ virtual void writePixel(Color nColor) override
+ {
+ *pData++ = nColor.GetAlpha();
+ *pData++ = nColor.GetRed();
+ *pData++ = nColor.GetGreen();
+ *pData++ = nColor.GetBlue();
+ }
+};
+
+class ScanlineTransformer_BGR final : public ScanlineTransformer
+{
+private:
+ sal_uInt8* pData;
+
+public:
+ virtual void startLine(sal_uInt8* pLine) override { pData = pLine; }
+
+ virtual void skipPixel(sal_uInt32 nPixel) override { pData += (nPixel << 1) + nPixel; }
+
+ virtual Color readPixel() override
+ {
+ const Color aColor(pData[2], pData[1], pData[0]);
+ pData += 3;
+ return aColor;
+ }
+
+ virtual void writePixel(Color nColor) override
+ {
+ *pData++ = nColor.GetBlue();
+ *pData++ = nColor.GetGreen();
+ *pData++ = nColor.GetRed();
+ }
+};
+
+class ScanlineTransformer_8BitPalette final : public ScanlineTransformer
+{
+private:
+ sal_uInt8* pData;
+ const BitmapPalette& mrPalette;
+
+public:
+ explicit ScanlineTransformer_8BitPalette(const BitmapPalette& rPalette)
+ : pData(nullptr)
+ , mrPalette(rPalette)
+ {
+ }
+
+ virtual void startLine(sal_uInt8* pLine) override { pData = pLine; }
+
+ virtual void skipPixel(sal_uInt32 nPixel) override { pData += nPixel; }
+
+ virtual Color readPixel() override
+ {
+ const sal_uInt8 nIndex(*pData++);
+ if (nIndex < mrPalette.GetEntryCount())
+ return mrPalette[nIndex];
+ else
+ return COL_BLACK;
+ }
+
+ virtual void writePixel(Color nColor) override
+ {
+ *pData++ = static_cast<sal_uInt8>(mrPalette.GetBestIndex(nColor));
+ }
+};
+
+class ScanlineTransformer_4BitPalette final : public ScanlineTransformer
+{
+private:
+ sal_uInt8* pData;
+ const BitmapPalette& mrPalette;
+ sal_uInt32 mnX;
+ sal_uInt32 mnShift;
+
+public:
+ explicit ScanlineTransformer_4BitPalette(const BitmapPalette& rPalette)
+ : pData(nullptr)
+ , mrPalette(rPalette)
+ , mnX(0)
+ , mnShift(0)
+ {
+ }
+
+ virtual void skipPixel(sal_uInt32 nPixel) override
+ {
+ mnX += nPixel;
+ if (nPixel & 1) // is nPixel an odd number
+ mnShift ^= 4;
+ }
+
+ virtual void startLine(sal_uInt8* pLine) override
+ {
+ pData = pLine;
+ mnX = 0;
+ mnShift = 4;
+ }
+
+ virtual Color readPixel() override
+ {
+ const sal_uInt32 nDataIndex = mnX / 2;
+ const sal_uInt8 nIndex((pData[nDataIndex] >> mnShift) & 0x0f);
+ mnX++;
+ mnShift ^= 4;
+
+ if (nIndex < mrPalette.GetEntryCount())
+ return mrPalette[nIndex];
+ else
+ return COL_BLACK;
+ }
+
+ virtual void writePixel(Color nColor) override
+ {
+ const sal_uInt32 nDataIndex = mnX / 2;
+ const sal_uInt8 nColorIndex = mrPalette.GetBestIndex(nColor);
+ pData[nDataIndex] |= (nColorIndex & 0x0f) << mnShift;
+ mnX++;
+ mnShift ^= 4;
+ }
+};
+
+class ScanlineTransformer_1BitPalette final : public ScanlineTransformer
+{
+private:
+ sal_uInt8* pData;
+ const BitmapPalette& mrPalette;
+ sal_uInt32 mnX;
+
+public:
+ explicit ScanlineTransformer_1BitPalette(const BitmapPalette& rPalette)
+ : pData(nullptr)
+ , mrPalette(rPalette)
+ , mnX(0)
+ {
+ }
+
+ virtual void skipPixel(sal_uInt32 nPixel) override { mnX += nPixel; }
+
+ virtual void startLine(sal_uInt8* pLine) override
+ {
+ pData = pLine;
+ mnX = 0;
+ }
+
+ virtual Color readPixel() override
+ {
+ const sal_uInt8 nIndex((pData[mnX >> 3] >> (7 - (mnX & 7))) & 1);
+ mnX++;
+
+ if (nIndex < mrPalette.GetEntryCount())
+ return mrPalette[nIndex];
+ else
+ return COL_BLACK;
+ }
+
+ virtual void writePixel(Color nColor) override
+ {
+ if (mrPalette.GetBestIndex(nColor) & 1)
+ pData[mnX >> 3] |= 1 << (7 - (mnX & 7));
+ else
+ pData[mnX >> 3] &= ~(1 << (7 - (mnX & 7)));
+ mnX++;
+ }
+};
+
+std::unique_ptr<ScanlineTransformer> getScanlineTransformer(sal_uInt16 nBits,
+ const BitmapPalette& rPalette)
+{
+ switch (nBits)
+ {
+ case 1:
+ return std::make_unique<ScanlineTransformer_1BitPalette>(rPalette);
+ case 4:
+ return std::make_unique<ScanlineTransformer_4BitPalette>(rPalette);
+ case 8:
+ return std::make_unique<ScanlineTransformer_8BitPalette>(rPalette);
+ case 24:
+ return std::make_unique<ScanlineTransformer_BGR>();
+ case 32:
+ return std::make_unique<ScanlineTransformer_ARGB>();
+ default:
+ assert(false);
+ break;
+ }
+ return nullptr;
+}
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/bitmap/bmpfast.hxx b/vcl/inc/bitmap/bmpfast.hxx
new file mode 100644
index 0000000000..3234f7a840
--- /dev/null
+++ b/vcl/inc/bitmap/bmpfast.hxx
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_BMPFAST_HXX
+#define INCLUDED_VCL_INC_BMPFAST_HXX
+
+#include <vcl/dllapi.h>
+#include <vcl/Scanline.hxx>
+#include <tools/long.hxx>
+
+class BitmapWriteAccess;
+class BitmapReadAccess;
+struct BitmapBuffer;
+class BitmapColor;
+struct SalTwoRect;
+
+// the bmpfast functions have signatures with good compatibility to
+// their canonic counterparts, which employ the GetPixel/SetPixel methods
+
+VCL_DLLPUBLIC bool ImplFastBitmapConversion( BitmapBuffer& rDst, const BitmapBuffer& rSrc,
+ const SalTwoRect& rTwoRect );
+
+bool ImplFastCopyScanline( tools::Long nY, BitmapBuffer& rDst, const BitmapBuffer& rSrc);
+bool ImplFastCopyScanline( tools::Long nY, BitmapBuffer& rDst, ConstScanline aSrcScanline,
+ ScanlineFormat nSrcScanlineFormat, sal_uInt32 nSrcScanlineSize);
+
+bool ImplFastBitmapBlending( BitmapWriteAccess const & rDst,
+ const BitmapReadAccess& rSrc, const BitmapReadAccess& rMask,
+ const SalTwoRect& rTwoRect );
+
+bool ImplFastEraseBitmap( BitmapBuffer&, const BitmapColor& );
+
+#endif // INCLUDED_VCL_INC_BMPFAST_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/bitmap/impoctree.hxx b/vcl/inc/bitmap/impoctree.hxx
new file mode 100644
index 0000000000..7581b91088
--- /dev/null
+++ b/vcl/inc/bitmap/impoctree.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_IMPOCTREE_HXX
+#define INCLUDED_VCL_INC_IMPOCTREE_HXX
+
+#include <vcl/BitmapColor.hxx>
+
+class ImpErrorQuad
+{
+ sal_Int16 nRed;
+ sal_Int16 nGreen;
+ sal_Int16 nBlue;
+
+public:
+ ImpErrorQuad()
+ : nRed(0)
+ , nGreen(0)
+ , nBlue(0)
+ {
+ }
+
+ ImpErrorQuad(const BitmapColor& rColor)
+ : nRed(rColor.GetRed() << 5)
+ , nGreen(rColor.GetGreen() << 5)
+ , nBlue(rColor.GetBlue() << 5)
+ {
+ }
+
+ inline void operator=(const BitmapColor& rColor);
+ inline ImpErrorQuad& operator-=(const BitmapColor& rColor);
+
+ inline void ImplAddColorError1(const ImpErrorQuad& rErrQuad);
+ inline void ImplAddColorError3(const ImpErrorQuad& rErrQuad);
+ inline void ImplAddColorError5(const ImpErrorQuad& rErrQuad);
+ inline void ImplAddColorError7(const ImpErrorQuad& rErrQuad);
+
+ inline BitmapColor ImplGetColor() const;
+};
+
+inline void ImpErrorQuad::operator=(const BitmapColor& rColor)
+{
+ nRed = rColor.GetRed() << 5;
+ nGreen = rColor.GetGreen() << 5;
+ nBlue = rColor.GetBlue() << 5;
+}
+
+inline ImpErrorQuad& ImpErrorQuad::operator-=(const BitmapColor& rColor)
+{
+ nRed -= rColor.GetRed() << 5;
+ nGreen -= rColor.GetGreen() << 5;
+ nBlue -= rColor.GetBlue() << 5;
+
+ return *this;
+}
+
+inline void ImpErrorQuad::ImplAddColorError1(const ImpErrorQuad& rErrQuad)
+{
+ nRed += rErrQuad.nRed >> 4;
+ nGreen += rErrQuad.nGreen >> 4;
+ nBlue += rErrQuad.nBlue >> 4;
+}
+
+inline void ImpErrorQuad::ImplAddColorError3(const ImpErrorQuad& rErrQuad)
+{
+ nRed += rErrQuad.nRed * 3L >> 4;
+ nGreen += rErrQuad.nGreen * 3L >> 4;
+ nBlue += rErrQuad.nBlue * 3L >> 4;
+}
+
+inline void ImpErrorQuad::ImplAddColorError5(const ImpErrorQuad& rErrQuad)
+{
+ nRed += rErrQuad.nRed * 5L >> 4;
+ nGreen += rErrQuad.nGreen * 5L >> 4;
+ nBlue += rErrQuad.nBlue * 5L >> 4;
+}
+
+inline void ImpErrorQuad::ImplAddColorError7(const ImpErrorQuad& rErrQuad)
+{
+ nRed += rErrQuad.nRed * 7L >> 4;
+ nGreen += rErrQuad.nGreen * 7L >> 4;
+ nBlue += rErrQuad.nBlue * 7L >> 4;
+}
+
+inline BitmapColor ImpErrorQuad::ImplGetColor() const
+{
+ return BitmapColor(std::clamp(nRed, sal_Int16(0), sal_Int16(8160)) >> 5,
+ std::clamp(nGreen, sal_Int16(0), sal_Int16(8160)) >> 5,
+ std::clamp(nBlue, sal_Int16(0), sal_Int16(8160)) >> 5);
+}
+
+#endif // INCLUDED_VCL_INC_IMPOCTREE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/bitmaps.hlst b/vcl/inc/bitmaps.hlst
new file mode 100644
index 0000000000..f4ccaaa46a
--- /dev/null
+++ b/vcl/inc/bitmaps.hlst
@@ -0,0 +1,229 @@
+/* -*- 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/.
+ */
+#pragma once
+
+#include <rtl/ustring.hxx>
+
+inline constexpr OUString SV_RESID_BITMAP_CHECK1 = u"vcl/res/check1.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECK2 = u"vcl/res/check2.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECK3 = u"vcl/res/check3.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECK4 = u"vcl/res/check4.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECK5 = u"vcl/res/check5.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECK6 = u"vcl/res/check6.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECK7 = u"vcl/res/check7.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECK8 = u"vcl/res/check8.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECK9 = u"vcl/res/check9.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECKMONO1 = u"vcl/res/checkmono1.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECKMONO2 = u"vcl/res/checkmono2.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECKMONO3 = u"vcl/res/checkmono3.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECKMONO4 = u"vcl/res/checkmono4.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECKMONO5 = u"vcl/res/checkmono5.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECKMONO6 = u"vcl/res/checkmono6.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECKMONO7 = u"vcl/res/checkmono7.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECKMONO8 = u"vcl/res/checkmono8.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CHECKMONO9 = u"vcl/res/checkmono9.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_RADIO1 = u"vcl/res/radio1.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_RADIO2 = u"vcl/res/radio2.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_RADIO3 = u"vcl/res/radio3.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_RADIO4 = u"vcl/res/radio4.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_RADIO5 = u"vcl/res/radio5.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_RADIO6 = u"vcl/res/radio6.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_RADIOMONO1 = u"vcl/res/radiomono1.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_RADIOMONO2 = u"vcl/res/radiomono2.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_RADIOMONO3 = u"vcl/res/radiomono3.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_RADIOMONO4 = u"vcl/res/radiomono4.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_RADIOMONO5 = u"vcl/res/radiomono5.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_RADIOMONO6 = u"vcl/res/radiomono6.png"_ustr;
+
+inline constexpr OUString SV_RESID_BITMAP_ERRORBOX = u"vcl/res/errorbox.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_QUERYBOX = u"vcl/res/querybox.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_WARNINGBOX = u"vcl/res/warningbox.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_INFOBOX = u"vcl/res/infobox.png"_ustr;
+
+inline constexpr OUString SV_RESID_BITMAP_SCROLLMSK = u"vcl/res/scrmsk.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_WHEELVH = u"vcl/res/wheelvh.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_WHEELV = u"vcl/res/wheelv.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_WHEELH = u"vcl/res/wheelh.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_SCROLLVH = u"vcl/res/scrollvh.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_SCROLLV = u"vcl/res/scrollv.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_SCROLLH = u"vcl/res/scrollh.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_CLOSEDOC = u"vcl/res/closedoc.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_REFRESH = u"res/reload.png"_ustr;
+inline constexpr OUString SV_RESID_BITMAP_NOTEBOOKBAR = u"res/notebookbar.png"_ustr;
+
+inline constexpr OUString SV_DISCLOSURE_PLUS = u"res/plus.png"_ustr;
+inline constexpr OUString SV_DISCLOSURE_MINUS = u"res/minus.png"_ustr;
+
+inline constexpr OUString SV_PRINT_COLLATE_BMP = u"vcl/res/collate.png"_ustr;
+inline constexpr OUString SV_PRINT_NOCOLLATE_BMP = u"vcl/res/ncollate.png"_ustr;
+
+inline constexpr OUString MAINAPP_48_8 = u"res/mainapp_48_8.png"_ustr;
+inline constexpr OUString ODT_48_8 = u"res/odt_48_8.png"_ustr;
+inline constexpr OUString OTT_48_8 = u"res/ott_48_8.png"_ustr;
+inline constexpr OUString ODS_48_8 = u"res/ods_48_8.png"_ustr;
+inline constexpr OUString OTS_48_8 = u"res/ots_48_8.png"_ustr;
+inline constexpr OUString ODG_48_8 = u"res/odg_48_8.png"_ustr;
+inline constexpr OUString ODP_48_8 = u"res/odp_48_8.png"_ustr;
+inline constexpr OUString ODM_48_8 = u"res/odm_48_8.png"_ustr;
+inline constexpr OUString ODB_48_8 = u"res/odb_48_8.png"_ustr;
+inline constexpr OUString ODF_48_8 = u"res/odf_48_8.png"_ustr;
+
+inline constexpr OUString MAINAPP_32_8 = u"res/mainapp_32_8.png"_ustr;
+inline constexpr OUString ODT_32_8 = u"res/odt_32_8.png"_ustr;
+inline constexpr OUString OTT_32_8 = u"res/ott_32_8.png"_ustr;
+inline constexpr OUString ODS_32_8 = u"res/ods_32_8.png"_ustr;
+inline constexpr OUString OTS_32_8 = u"res/ots_32_8.png"_ustr;
+inline constexpr OUString ODG_32_8 = u"res/odg_32_8.png"_ustr;
+inline constexpr OUString ODP_32_8 = u"res/odp_32_8.png"_ustr;
+inline constexpr OUString ODM_32_8 = u"res/odm_32_8.png"_ustr;
+inline constexpr OUString ODB_32_8 = u"res/odb_32_8.png"_ustr;
+inline constexpr OUString ODF_32_8 = u"res/odf_32_8.png"_ustr;
+
+inline constexpr OUString MAINAPP_16_8 = u"res/mainapp_16_8.png"_ustr;
+inline constexpr OUString ODT_16_8 = u"res/odt_16_8.png"_ustr;
+inline constexpr OUString OTT_16_8 = u"res/ott_16_8.png"_ustr;
+inline constexpr OUString ODS_16_8 = u"res/ods_16_8.png"_ustr;
+inline constexpr OUString OTS_16_8 = u"res/ots_16_8.png"_ustr;
+inline constexpr OUString ODG_16_8 = u"res/odg_16_8.png"_ustr;
+inline constexpr OUString ODP_16_8 = u"res/odp_16_8.png"_ustr;
+inline constexpr OUString ODM_16_8 = u"res/odm_16_8.png"_ustr;
+inline constexpr OUString ODB_16_8 = u"res/odb_16_8.png"_ustr;
+inline constexpr OUString ODF_16_8 = u"res/odf_16_8.png"_ustr;
+
+//start, Throbber::getDefaultImageURLs constructs these dynamically (not unused)
+#define SPINNER_16_01 "vcl/res/spinner-16-01.png"
+#define SPINNER_16_02 "vcl/res/spinner-16-02.png"
+#define SPINNER_16_03 "vcl/res/spinner-16-03.png"
+#define SPINNER_16_04 "vcl/res/spinner-16-04.png"
+#define SPINNER_16_05 "vcl/res/spinner-16-05.png"
+#define SPINNER_16_06 "vcl/res/spinner-16-06.png"
+
+#define SPINNER_32_01 "vcl/res/spinner-32-01.png"
+#define SPINNER_32_02 "vcl/res/spinner-32-02.png"
+#define SPINNER_32_03 "vcl/res/spinner-32-03.png"
+#define SPINNER_32_04 "vcl/res/spinner-32-04.png"
+#define SPINNER_32_05 "vcl/res/spinner-32-05.png"
+#define SPINNER_32_06 "vcl/res/spinner-32-06.png"
+#define SPINNER_32_07 "vcl/res/spinner-32-07.png"
+#define SPINNER_32_08 "vcl/res/spinner-32-08.png"
+#define SPINNER_32_09 "vcl/res/spinner-32-09.png"
+#define SPINNER_32_10 "vcl/res/spinner-32-10.png"
+#define SPINNER_32_11 "vcl/res/spinner-32-11.png"
+#define SPINNER_32_12 "vcl/res/spinner-32-12.png"
+
+#define SPINNER_64_01 "vcl/res/spinner-64-01.png"
+#define SPINNER_64_02 "vcl/res/spinner-64-02.png"
+#define SPINNER_64_03 "vcl/res/spinner-64-03.png"
+#define SPINNER_64_04 "vcl/res/spinner-64-04.png"
+#define SPINNER_64_05 "vcl/res/spinner-64-05.png"
+#define SPINNER_64_06 "vcl/res/spinner-64-06.png"
+#define SPINNER_64_07 "vcl/res/spinner-64-07.png"
+#define SPINNER_64_08 "vcl/res/spinner-64-08.png"
+#define SPINNER_64_09 "vcl/res/spinner-64-09.png"
+#define SPINNER_64_10 "vcl/res/spinner-64-10.png"
+#define SPINNER_64_11 "vcl/res/spinner-64-11.png"
+#define SPINNER_64_12 "vcl/res/spinner-64-12.png"
+//end, Throbber::getDefaultImageURLs
+
+inline constexpr OUString IMG_WARN = u"dbaccess/res/exwarning.png"_ustr;
+inline constexpr OUString IMG_ERROR = u"dbaccess/res/exerror.png"_ustr;
+inline constexpr OUString IMG_INFO = u"dbaccess/res/exinfo.png"_ustr;
+inline constexpr OUString IMG_ADD = u"extensions/res/scanner/plus.png"_ustr;
+inline constexpr OUString IMG_REMOVE = u"extensions/res/scanner/minus.png"_ustr;
+inline constexpr OUString IMG_COPY = u"cmd/sc_copy.png"_ustr;
+inline constexpr OUString IMG_PASTE = u"cmd/sc_paste.png"_ustr;
+inline constexpr OUString IMG_MENU = u"sfx2/res/menu.png"_ustr;
+inline constexpr OUString IMG_CALENDAR = u"sc/res/date.png"_ustr;
+inline constexpr OUString IMG_OPEN = u"cmd/sc_open.png"_ustr;
+
+inline constexpr OUString RID_BMP_TREENODE_COLLAPSED = u"res/plus.png"_ustr;
+inline constexpr OUString RID_BMP_TREENODE_EXPANDED = u"res/minus.png"_ustr;
+
+inline constexpr OUString RID_CURSOR_AUTOSCROLL_E = u"vcl/res/autoscroll_e.png"_ustr;
+inline constexpr OUString RID_CURSOR_AUTOSCROLL_N = u"vcl/res/autoscroll_n.png"_ustr;
+inline constexpr OUString RID_CURSOR_AUTOSCROLL_NE = u"vcl/res/autoscroll_ne.png"_ustr;
+inline constexpr OUString RID_CURSOR_AUTOSCROLL_NS = u"vcl/res/autoscroll_ns.png"_ustr;
+inline constexpr OUString RID_CURSOR_AUTOSCROLL_NSWE = u"vcl/res/autoscroll_nswe.png"_ustr;
+inline constexpr OUString RID_CURSOR_AUTOSCROLL_NW = u"vcl/res/autoscroll_nw.png"_ustr;
+inline constexpr OUString RID_CURSOR_AUTOSCROLL_S = u"vcl/res/autoscroll_s.png"_ustr;
+inline constexpr OUString RID_CURSOR_AUTOSCROLL_SE = u"vcl/res/autoscroll_se.png"_ustr;
+inline constexpr OUString RID_CURSOR_AUTOSCROLL_SW = u"vcl/res/autoscroll_sw.png"_ustr;
+inline constexpr OUString RID_CURSOR_AUTOSCROLL_W = u"vcl/res/autoscroll_w.png"_ustr;
+inline constexpr OUString RID_CURSOR_AUTOSCROLL_WE = u"vcl/res/autoscroll_we.png"_ustr;
+inline constexpr OUString RID_CURSOR_CHAIN = u"vcl/res/chain.png"_ustr;
+inline constexpr OUString RID_CURSOR_CHAIN_NOT_ALLOWED = u"vcl/res/chain_not_allowed.png"_ustr;
+inline constexpr OUString RID_CURSOR_CHART = u"vcl/res/chart.png"_ustr;
+inline constexpr OUString RID_CURSOR_COPY_DATA = u"vcl/res/copy_data.png"_ustr;
+inline constexpr OUString RID_CURSOR_COPY_DATA_LINK = u"vcl/res/copy_data_link.png"_ustr;
+inline constexpr OUString RID_CURSOR_COPY_FILE = u"vcl/res/copy_file.png"_ustr;
+inline constexpr OUString RID_CURSOR_COPY_FILES = u"vcl/res/copy_files.png"_ustr;
+inline constexpr OUString RID_CURSOR_COPY_FILE_LINK = u"vcl/res/copy_file_link.png"_ustr;
+inline constexpr OUString RID_CURSOR_CROOK = u"vcl/res/crook.png"_ustr;
+inline constexpr OUString RID_CURSOR_CROP = u"vcl/res/crop.png"_ustr;
+inline constexpr OUString RID_CURSOR_DRAW_ARC = u"vcl/res/draw_arc.png"_ustr;
+inline constexpr OUString RID_CURSOR_DRAW_BEZIER = u"vcl/res/draw_bezier.png"_ustr;
+inline constexpr OUString RID_CURSOR_DRAW_CAPTION = u"vcl/res/draw_caption.png"_ustr;
+inline constexpr OUString RID_CURSOR_DRAW_CIRCLE_CUT = u"vcl/res/draw_circle_cut.png"_ustr;
+inline constexpr OUString RID_CURSOR_DRAW_CONNECT = u"vcl/res/draw_connect.png"_ustr;
+inline constexpr OUString RID_CURSOR_DRAW_ELLIPSE = u"vcl/res/draw_ellipse.png"_ustr;
+inline constexpr OUString RID_CURSOR_DETECTIVE = u"vcl/res/detective.png"_ustr;
+inline constexpr OUString RID_CURSOR_DRAW_FREEHAND = u"vcl/res/draw_freehand.png"_ustr;
+inline constexpr OUString RID_CURSOR_DRAW_LINE = u"vcl/res/draw_line.png"_ustr;
+inline constexpr OUString RID_CURSOR_DRAW_PIE = u"vcl/res/draw_pie.png"_ustr;
+inline constexpr OUString RID_CURSOR_DRAW_POLYGON = u"vcl/res/draw_polygon.png"_ustr;
+inline constexpr OUString RID_CURSOR_DRAW_RECT = u"vcl/res/draw_rect.png"_ustr;
+inline constexpr OUString RID_CURSOR_DRAW_TEXT = u"vcl/res/draw_text.png"_ustr;
+inline constexpr OUString RID_CURSOR_FILL = u"vcl/res/fill.png"_ustr;
+#define RID_CURSOR_HELP "vcl/res/help.png"
+inline constexpr OUString RID_CURSOR_H_SHEAR = u"vcl/res/h_shear.png"_ustr;
+inline constexpr OUString RID_CURSOR_LINK_DATA = u"vcl/res/link_data.png"_ustr;
+inline constexpr OUString RID_CURSOR_LINK_FILE = u"vcl/res/link_file.png"_ustr;
+inline constexpr OUString RID_CURSOR_MAGNIFY = u"vcl/res/magnify.png"_ustr;
+inline constexpr OUString RID_CURSOR_MIRROR = u"vcl/res/mirror.png"_ustr;
+inline constexpr OUString RID_CURSOR_MOVE_BEZIER_WEIGHT = u"vcl/res/move_bezier_weight.png"_ustr;
+inline constexpr OUString RID_CURSOR_MOVE_DATA = u"vcl/res/move_data.png"_ustr;
+inline constexpr OUString RID_CURSOR_MOVE_DATA_LINK = u"vcl/res/move_data_link.png"_ustr;
+inline constexpr OUString RID_CURSOR_MOVE_FILE = u"vcl/res/move_file.png"_ustr;
+inline constexpr OUString RID_CURSOR_MOVE_FILES = u"vcl/res/move_files.png"_ustr;
+inline constexpr OUString RID_CURSOR_MOVE_FILE_LINK = u"vcl/res/move_file_link.png"_ustr;
+inline constexpr OUString RID_CURSOR_MOVE_POINT = u"vcl/res/move_point.png"_ustr;
+inline constexpr OUString RID_CURSOR_NOT_ALLOWED = u"vcl/res/not_allowed.png"_ustr;
+inline constexpr OUString RID_CURSOR_NULL = u"vcl/res/null.png"_ustr;
+#define RID_CURSOR_PEN "vcl/res/pen.png"
+inline constexpr OUString RID_CURSOR_PIVOT_COLUMN = u"vcl/res/pivot_column.png"_ustr;
+inline constexpr OUString RID_CURSOR_PIVOT_DELETE = u"vcl/res/pivot_delete.png"_ustr;
+inline constexpr OUString RID_CURSOR_PIVOT_FIELD = u"vcl/res/pivot_field.png"_ustr;
+inline constexpr OUString RID_CURSOR_PIVOT_ROW = u"vcl/res/pivot_row.png"_ustr;
+inline constexpr OUString RID_CURSOR_ROTATE = u"vcl/res/rotate.png"_ustr;
+inline constexpr OUString RID_CURSOR_TAB_SELECT_E = u"vcl/res/tab_select_e.png"_ustr;
+inline constexpr OUString RID_CURSOR_TAB_SELECT_S = u"vcl/res/tab_select_s.png"_ustr;
+inline constexpr OUString RID_CURSOR_TAB_SELECT_SE = u"vcl/res/tab_select_se.png"_ustr;
+inline constexpr OUString RID_CURSOR_TAB_SELECT_SW = u"vcl/res/tab_select_sw.png"_ustr;
+inline constexpr OUString RID_CURSOR_TAB_SELECT_W = u"vcl/res/tab_select_w.png"_ustr;
+inline constexpr OUString RID_CURSOR_V_SHEAR = u"vcl/res/v_shear.png"_ustr;
+inline constexpr OUString RID_CURSOR_TEXT_VERTICAL = u"vcl/res/text_vertical.png"_ustr;
+inline constexpr OUString RID_CURSOR_HIDE_WHITESPACE = u"vcl/res/hide_whitespace.png"_ustr;
+inline constexpr OUString RID_CURSOR_SHOW_WHITESPACE = u"vcl/res/show_whitespace.png"_ustr;
+#define RID_CURSOR_WAIT "vcl/res/wait.png"
+#define RID_CURSOR_NWSIZE "vcl/res/nwsize.png"
+#define RID_CURSOR_NESIZE "vcl/res/nesize.png"
+#define RID_CURSOR_SWSIZE "vcl/res/swsize.png"
+#define RID_CURSOR_SESIZE "vcl/res/sesize.png"
+#define RID_CURSOR_WINDOW_NWSIZE "vcl/res/window_nwsize.png"
+#define RID_CURSOR_WINDOW_NESIZE "vcl/res/window_nesize.png"
+#define RID_CURSOR_WINDOW_SWSIZE "vcl/res/window_swsize.png"
+#define RID_CURSOR_WINDOW_SESIZE "vcl/res/window_sesize.png"
+inline constexpr OUString RID_CURSOR_FATCROSS = u"vcl/res/fatcross.png"_ustr;
+
+inline constexpr OUString CHEVRON = u"sfx2/res/chevron.png"_ustr;
+
+inline constexpr OUString RID_UPDATE_AVAILABLE_16 = u"extensions/res/update/ui/onlineupdate_16.png"_ustr;
+inline constexpr OUString RID_UPDATE_AVAILABLE_26 = u"extensions/res/update/ui/onlineupdate_26.png"_ustr;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/brdwin.hxx b/vcl/inc/brdwin.hxx
new file mode 100644
index 0000000000..f9c8a8edb8
--- /dev/null
+++ b/vcl/inc/brdwin.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_BRDWIN_HXX
+#define INCLUDED_VCL_INC_BRDWIN_HXX
+
+#include <vcl/notebookbar/notebookbar.hxx>
+#include <vcl/window.hxx>
+#include <o3tl/typed_flags_set.hxx>
+#include <vcl/notebookbar/NotebookBarAddonsMerger.hxx>
+
+#include <com/sun/star/frame/XFrame.hpp>
+
+class ImplBorderWindowView;
+enum class DrawButtonFlags;
+
+enum class BorderWindowStyle {
+ NONE = 0x0000,
+ Overlap = 0x0001,
+ Float = 0x0004,
+ Frame = 0x0008,
+ App = 0x0010
+};
+namespace o3tl {
+ template<> struct typed_flags<BorderWindowStyle> : is_typed_flags<BorderWindowStyle, 0x001d> {};
+};
+
+enum class BorderWindowHitTest {
+ NONE = 0x0000,
+ Title = 0x0001,
+ Left = 0x0002,
+ Menu = 0x0004,
+ Top = 0x0008,
+ Right = 0x0010,
+ Bottom = 0x0020,
+ TopLeft = 0x0040,
+ TopRight = 0x0080,
+ BottomLeft = 0x0100,
+ BottomRight = 0x0200,
+ Close = 0x0400,
+ Dock = 0x0800,
+ Hide = 0x1000,
+ Help = 0x2000,
+};
+namespace o3tl {
+ template<> struct typed_flags<BorderWindowHitTest> : is_typed_flags<BorderWindowHitTest, 0x3fff> {};
+};
+
+enum class BorderWindowTitleType {
+ Normal = 0x0001,
+ Small = 0x0002,
+ Tearoff = 0x0004,
+ Popup = 0x0008,
+ NONE = 0x0010
+};
+namespace o3tl {
+ template<> struct typed_flags<BorderWindowTitleType> : is_typed_flags<BorderWindowTitleType, 0x001f> {};
+};
+
+class ImplBorderWindow final : public vcl::Window
+{
+ friend class vcl::Window;
+ friend class vcl::WindowOutputDevice;
+ friend class ImplBorderWindowView;
+ friend class ImplSmallBorderWindowView;
+ friend class ImplStdBorderWindowView;
+
+private:
+ std::unique_ptr<ImplBorderWindowView> mpBorderView;
+ VclPtr<vcl::Window> mpMenuBarWindow;
+ VclPtr<NotebookBar> mpNotebookBar;
+ tools::Long mnMinWidth;
+ tools::Long mnMinHeight;
+ tools::Long mnMaxWidth;
+ tools::Long mnMaxHeight;
+ tools::Long mnOrgMenuHeight;
+ BorderWindowTitleType mnTitleType;
+ WindowBorderStyle mnBorderStyle;
+ bool mbFloatWindow;
+ bool mbSmallOutBorder;
+ bool mbFrameBorder;
+ bool mbMenuHide;
+ bool mbDockBtn;
+ bool mbHideBtn;
+ bool mbMenuBtn;
+ bool mbDisplayActive;
+
+ using Window::ImplInit;
+ void ImplInit( vcl::Window* pParent,
+ WinBits nStyle, BorderWindowStyle nTypeStyle,
+ SystemParentData* pParentData );
+
+ ImplBorderWindow (const ImplBorderWindow &) = delete;
+ ImplBorderWindow& operator= (const ImplBorderWindow &) = delete;
+
+public:
+ ImplBorderWindow( vcl::Window* pParent,
+ SystemParentData* pParentData,
+ WinBits nStyle,
+ BorderWindowStyle nTypeStyle );
+ ImplBorderWindow( vcl::Window* pParent, WinBits nStyle,
+ BorderWindowStyle nTypeStyle );
+ virtual ~ImplBorderWindow() override;
+ virtual void dispose() override;
+
+ virtual void MouseMove( const MouseEvent& rMEvt ) override;
+ virtual void MouseButtonDown( const MouseEvent& rMEvt ) override;
+ virtual void Tracking( const TrackingEvent& rTEvt ) override;
+ virtual void Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) override;
+ virtual void Activate() override;
+ virtual void Deactivate() override;
+ virtual void Resize() override;
+ virtual void RequestHelp( const HelpEvent& rHEvt ) override;
+ virtual void StateChanged( StateChangedType nType ) override;
+ virtual void DataChanged( const DataChangedEvent& rDCEvt ) override;
+ virtual void queue_resize(StateChangedType eReason = StateChangedType::Layout) override;
+
+ void InitView();
+ void UpdateView( bool bNewView, const Size& rNewOutSize );
+ void InvalidateBorder();
+
+ using Window::Draw;
+ void Draw( OutputDevice* pDev, const Point& rPos );
+
+ void SetDisplayActive( bool bActive );
+ void SetTitleType( BorderWindowTitleType nTitleType, const Size& rSize );
+ void SetBorderStyle( WindowBorderStyle nStyle );
+ WindowBorderStyle GetBorderStyle() const { return mnBorderStyle; }
+ void SetCloseButton();
+ void SetDockButton( bool bDockButton );
+ void SetHideButton( bool bHideButton );
+ void SetMenuButton( bool bMenuButton );
+
+ void UpdateMenuHeight();
+ void SetMenuBarWindow( vcl::Window* pWindow );
+ void SetMenuBarMode( bool bHide );
+
+ void SetNotebookBar(const OUString& rUIXMLDescription,
+ const css::uno::Reference<css::frame::XFrame>& rFrame,
+ const NotebookBarAddonsItem &aNotebookBarAddonsItem);
+ void CloseNotebookBar();
+ const VclPtr<NotebookBar>& GetNotebookBar() const { return mpNotebookBar; }
+
+ void SetMinOutputSize( tools::Long nWidth, tools::Long nHeight )
+ { mnMinWidth = nWidth; mnMinHeight = nHeight; }
+ void SetMaxOutputSize( tools::Long nWidth, tools::Long nHeight )
+ { mnMaxWidth = nWidth; mnMaxHeight = nHeight; }
+
+ void GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder,
+ sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const;
+ tools::Long CalcTitleWidth() const;
+
+ tools::Rectangle GetMenuRect() const;
+
+ virtual Size GetOptimalSize() const override;
+};
+
+struct ImplBorderFrameData
+{
+ VclPtr<ImplBorderWindow> mpBorderWindow;
+ VclPtr<OutputDevice> mpOutDev;
+ tools::Rectangle maTitleRect;
+ tools::Rectangle maCloseRect;
+ tools::Rectangle maDockRect;
+ tools::Rectangle maMenuRect;
+ tools::Rectangle maHideRect;
+ tools::Rectangle maHelpRect;
+ Point maMouseOff;
+ tools::Long mnWidth;
+ tools::Long mnHeight;
+ tools::Long mnTrackX;
+ tools::Long mnTrackY;
+ tools::Long mnTrackWidth;
+ tools::Long mnTrackHeight;
+ sal_Int32 mnLeftBorder;
+ sal_Int32 mnTopBorder;
+ sal_Int32 mnRightBorder;
+ sal_Int32 mnBottomBorder;
+ tools::Long mnNoTitleTop;
+ tools::Long mnBorderSize;
+ tools::Long mnTitleHeight;
+ BorderWindowHitTest mnHitTest;
+ DrawButtonFlags mnCloseState;
+ DrawButtonFlags mnDockState;
+ DrawButtonFlags mnMenuState;
+ DrawButtonFlags mnHideState;
+ DrawButtonFlags mnHelpState;
+ BorderWindowTitleType mnTitleType;
+ bool mbDragFull;
+ bool mbTitleClipped;
+};
+
+class ImplBorderWindowView
+{
+public:
+ virtual ~ImplBorderWindowView();
+
+ virtual bool MouseMove( const MouseEvent& rMEvt );
+ virtual bool MouseButtonDown( const MouseEvent& rMEvt );
+ virtual bool Tracking( const TrackingEvent& rTEvt );
+ virtual OUString RequestHelp( const Point& rPos, tools::Rectangle& rHelpRect );
+
+ virtual void Init( OutputDevice* pDev, tools::Long nWidth, tools::Long nHeight ) = 0;
+ virtual void GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder,
+ sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const = 0;
+ virtual tools::Long CalcTitleWidth() const = 0;
+ virtual void DrawWindow(vcl::RenderContext& rRenderContext, const Point* pOffset = nullptr) = 0;
+ virtual tools::Rectangle GetMenuRect() const;
+
+ static void ImplInitTitle( ImplBorderFrameData* pData );
+ static BorderWindowHitTest ImplHitTest( ImplBorderFrameData const * pData, const Point& rPos );
+ static void ImplMouseMove( ImplBorderFrameData* pData, const MouseEvent& rMEvt );
+ static OUString ImplRequestHelp( ImplBorderFrameData const * pData, const Point& rPos, tools::Rectangle& rHelpRect );
+ static tools::Long ImplCalcTitleWidth( const ImplBorderFrameData* pData );
+};
+
+class ImplNoBorderWindowView final : public ImplBorderWindowView
+{
+public:
+ ImplNoBorderWindowView();
+
+ virtual void Init( OutputDevice* pDev, tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder,
+ sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const override;
+ virtual tools::Long CalcTitleWidth() const override;
+ virtual void DrawWindow(vcl::RenderContext& rRenderContext, const Point* pOffset = nullptr) override;
+};
+
+class ImplSmallBorderWindowView final : public ImplBorderWindowView
+{
+ VclPtr<ImplBorderWindow> mpBorderWindow;
+ VclPtr<OutputDevice> mpOutDev;
+ tools::Long mnWidth;
+ tools::Long mnHeight;
+ sal_Int32 mnLeftBorder;
+ sal_Int32 mnTopBorder;
+ sal_Int32 mnRightBorder;
+ sal_Int32 mnBottomBorder;
+ bool mbNWFBorder;
+
+public:
+ ImplSmallBorderWindowView( ImplBorderWindow* pBorderWindow );
+
+ virtual void Init( OutputDevice* pOutDev, tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder,
+ sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const override;
+ virtual tools::Long CalcTitleWidth() const override;
+ virtual void DrawWindow(vcl::RenderContext& rRenderContext, const Point* pOffset = nullptr) override;
+};
+
+class ImplStdBorderWindowView final : public ImplBorderWindowView
+{
+ ImplBorderFrameData maFrameData;
+
+public:
+ ImplStdBorderWindowView( ImplBorderWindow* pBorderWindow );
+ virtual ~ImplStdBorderWindowView() override;
+
+ virtual bool MouseMove( const MouseEvent& rMEvt ) override;
+ virtual bool MouseButtonDown( const MouseEvent& rMEvt ) override;
+ virtual bool Tracking( const TrackingEvent& rTEvt ) override;
+ virtual OUString RequestHelp( const Point& rPos, tools::Rectangle& rHelpRect ) override;
+ virtual tools::Rectangle GetMenuRect() const override;
+
+ virtual void Init( OutputDevice* pDev, tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder,
+ sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const override;
+ virtual tools::Long CalcTitleWidth() const override;
+ virtual void DrawWindow(vcl::RenderContext& rRenderContext, const Point* pOffset = nullptr) override;
+};
+
+#endif // INCLUDED_VCL_INC_BRDWIN_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/bubblewindow.hxx b/vcl/inc/bubblewindow.hxx
new file mode 100644
index 0000000000..12254d2dbb
--- /dev/null
+++ b/vcl/inc/bubblewindow.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 <vcl/toolkit/floatwin.hxx>
+#include <vcl/image.hxx>
+#include <vcl/menu.hxx>
+
+class BubbleWindow final : public FloatingWindow
+{
+ Point maTipPos;
+ vcl::Region maBounds;
+ tools::Polygon maRectPoly;
+ tools::Polygon maTriPoly;
+ OUString maBubbleTitle;
+ OUString maBubbleText;
+ Image maBubbleImage;
+ Size maMaxTextSize;
+ tools::Rectangle maTitleRect;
+ tools::Rectangle maTextRect;
+ tools::Long mnTipOffset;
+
+private:
+ void RecalcTextRects();
+
+public:
+ BubbleWindow( vcl::Window* pParent, OUString aTitle,
+ OUString aText, Image aImage );
+
+ virtual void MouseButtonDown( const MouseEvent& rMEvt ) override;
+ virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override;
+ void Resize() override;
+ void Show( bool bVisible = true );
+ void SetTipPosPixel( const Point& rTipPos ) { maTipPos = rTipPos; }
+ void SetTitleAndText( const OUString& rTitle, const OUString& rText,
+ const Image& rImage );
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/calendar.hxx b/vcl/inc/calendar.hxx
new file mode 100644
index 0000000000..b727039cee
--- /dev/null
+++ b/vcl/inc/calendar.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_CALENDAR_HXX
+#define INCLUDED_VCL_CALENDAR_HXX
+
+#include <unotools/calendarwrapper.hxx>
+
+#include <vcl/ctrl.hxx>
+#include <memory>
+#include <set>
+
+class MouseEvent;
+class TrackingEvent;
+class KeyEvent;
+class HelpEvent;
+class DataChangedEvent;
+
+/*************************************************************************
+
+Description
+============
+
+class Calendar
+
+This class allows for the selection of a date. The displayed date range is
+the one specified by the Date class. We display as many months as we have
+space in the control. The user can switch between months using a ContextMenu
+(clicking on the month's name) or via two ScrollButtons in-between the months.
+
+--------------------------------------------------------------------------
+
+WinBits
+
+WB_BORDER We draw a border around the window.
+WB_TABSTOP Keyboard control is possible. We get the focus, when
+ the user clicks in the Control.
+
+--------------------------------------------------------------------------
+
+We set and get the selected date by SetCurDate()/GetCurDate().
+If the user selects a date Select() is called. If the user double clicks
+DoubleClick() is called.
+
+--------------------------------------------------------------------------
+
+CalcWindowSizePixel() calculates the window size in pixel that is needed
+to display a certain number of months.
+
+--------------------------------------------------------------------------
+
+SetSaturdayColor() and SetSundayColor() set a special color for Saturdays
+and Sundays.
+AddDateInfo() marks special days. With that we can set e.g. public holidays
+to another color or encircle them (for e.g. appointments).
+If we do not supply a year in the date, the day is used in EVERY year.
+
+AddDateInfo() can also add text for every date, which is displayed if the
+BalloonHelp is enabled.
+In order to not have to supply all years with the relevant data, we call
+the RequestDateInfo() handler if a new year is displayed. We can then query
+the year in the handler with GetRequestYear().
+
+--------------------------------------------------------------------------
+
+In order to display a ContextMenu for a date, we need to override the
+Command handler. GetDate() can infer the date from the mouse's position.
+If we use the keyboard, the current date should be use.
+
+If a ContextMenu is displayed, the baseclass' handler must not be called.
+
+--------------------------------------------------------------------------
+
+SetNoSelection() deselects everything.
+SetCurDate() does not select the current date, but only defines the focus
+rectangle.
+GetSelectDateCount()/GetSelectDate() query the selected range.
+IsDateSelected() queries for the status of a date.
+
+The SelectionChanging() handler is being called while a user selects a
+date. In it, we can change the selected range. E.g. if we want to limit
+or extend the selected range. The selected range is realised via SelectDate()
+and SelectDateRange() and queried with GetSelectDateCount()/GetSelectDate().
+
+IsSelectLeft() returns the direction of the selection:
+sal_True is a selection to the left or up
+sal_False is a selection to the right or down
+
+--------------------------------------------------------------------------
+
+If the DateRange area changes and we want to take over the selection, we
+should only do this is if IsScrollDateRangeChanged() returns sal_True.
+This method returns sal_True if the area change was triggered by using the
+ScrollButtons and sal_False if it was triggered by Resize(), other method
+calls or by ending a selection.
+
+*************************************************************************/
+
+typedef std::set<sal_Int32> IntDateSet;
+
+class Calendar final : public Control
+{
+ std::unique_ptr<IntDateSet> mpSelectTable;
+ std::unique_ptr<IntDateSet> mpOldSelectTable;
+ OUString maDayTexts[31];
+ OUString maDayText;
+ OUString maWeekText;
+ CalendarWrapper maCalendarWrapper;
+ tools::Rectangle maPrevRect;
+ tools::Rectangle maNextRect;
+ OUString maDayOfWeekText;
+ sal_Int32 mnDayOfWeekAry[8];
+ Date maOldFormatFirstDate;
+ Date maOldFormatLastDate;
+ Date maFirstDate;
+ Date maOldFirstDate;
+ Date maCurDate;
+ Date maOldCurDate;
+ Color maSelColor;
+ Color maOtherColor;
+ sal_Int32 mnDayCount;
+ tools::Long mnDaysOffX;
+ tools::Long mnWeekDayOffY;
+ tools::Long mnDaysOffY;
+ tools::Long mnMonthHeight;
+ tools::Long mnMonthWidth;
+ tools::Long mnMonthPerLine;
+ tools::Long mnLines;
+ tools::Long mnDayWidth;
+ tools::Long mnDayHeight;
+ WinBits mnWinStyle;
+ sal_Int16 mnFirstYear;
+ sal_Int16 mnLastYear;
+ bool mbCalc:1,
+ mbFormat:1,
+ mbDrag:1,
+ mbMenuDown:1,
+ mbSpinDown:1,
+ mbPrevIn:1,
+ mbNextIn:1;
+ Link<Calendar*,void> maSelectHdl;
+ Link<Calendar*,void> maActivateHdl;
+
+ using Control::ImplInitSettings;
+ using Window::ImplInit;
+ void ImplInit( WinBits nWinStyle );
+ void ImplInitSettings();
+
+ virtual void ApplySettings(vcl::RenderContext& rRenderContext) override;
+
+ void ImplFormat();
+ sal_uInt16 ImplDoHitTest( const Point& rPos, Date& rDate ) const;
+ void ImplDrawSpin(vcl::RenderContext& rRenderContext);
+ void ImplDrawDate(vcl::RenderContext& rRenderContext, tools::Long nX, tools::Long nY,
+ sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear,
+ bool bOther, sal_Int32 nToday);
+ void ImplDraw(vcl::RenderContext& rRenderContext);
+ void ImplUpdateDate( const Date& rDate );
+ void ImplUpdateSelection( IntDateSet* pOld );
+ void ImplMouseSelect( const Date& rDate, sal_uInt16 nHitTest );
+ void ImplUpdate( bool bCalcNew = false );
+ void ImplScrollCalendar( bool bPrev );
+ void ImplShowMenu( const Point& rPos, const Date& rDate );
+ void ImplTracking( const Point& rPos, bool bRepeat );
+ void ImplEndTracking( bool bCancel );
+ DayOfWeek ImplGetWeekStart() const;
+
+ virtual Size GetOptimalSize() const override;
+public:
+ Calendar( vcl::Window* pParent, WinBits nWinStyle );
+ virtual ~Calendar() override;
+ virtual void dispose() override;
+
+ virtual void MouseButtonDown( const MouseEvent& rMEvt ) override;
+ virtual void Tracking( const TrackingEvent& rMEvt ) override;
+ virtual void KeyInput( const KeyEvent& rKEvt ) override;
+ virtual void Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) override;
+ virtual void Resize() override;
+ virtual void GetFocus() override;
+ virtual void LoseFocus() override;
+ virtual void RequestHelp( const HelpEvent& rHEvt ) override;
+ virtual void Command( const CommandEvent& rCEvt ) override;
+ virtual void StateChanged( StateChangedType nStateChange ) override;
+ virtual void DataChanged( const DataChangedEvent& rDCEvt ) override;
+
+ void Select();
+
+ Date GetFirstSelectedDate() const;
+
+ void SetCurDate( const Date& rNewDate );
+ void SetFirstDate( const Date& rNewFirstDate );
+ const Date& GetFirstDate() const { return maFirstDate; }
+ Date GetLastDate() const { return GetFirstDate() + mnDayCount; }
+ Date GetFirstMonth() const;
+ Date GetLastMonth() const;
+ sal_uInt16 GetMonthCount() const;
+ bool GetDate( const Point& rPos, Date& rDate ) const;
+ tools::Rectangle GetDateRect( const Date& rDate ) const;
+
+ void EndSelection();
+
+ Size CalcWindowSizePixel() const;
+
+ void SetSelectHdl( const Link<Calendar*,void>& rLink ) { maSelectHdl = rLink; }
+ void SetActivateHdl( const Link<Calendar*,void>& rLink ) { maActivateHdl = rLink; }
+
+ virtual void DumpAsPropertyTree(tools::JsonWriter&) override;
+};
+
+#endif // INCLUDED_VCL_CALENDAR_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/canvasbitmap.hxx b/vcl/inc/canvasbitmap.hxx
new file mode 100644
index 0000000000..9460dcec96
--- /dev/null
+++ b/vcl/inc/canvasbitmap.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_CANVASBITMAP_HXX
+#define INCLUDED_VCL_INC_CANVASBITMAP_HXX
+
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/rendering/XIntegerReadOnlyBitmap.hpp>
+#include <com/sun/star/rendering/XIntegerBitmapColorSpace.hpp>
+#include <com/sun/star/rendering/XBitmapPalette.hpp>
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+
+namespace vcl::unotools
+{
+ class VCL_DLLPUBLIC VclCanvasBitmap final :
+ public cppu::WeakImplHelper< css::rendering::XIntegerReadOnlyBitmap,
+ css::rendering::XBitmapPalette,
+ css::rendering::XIntegerBitmapColorSpace >
+ {
+ private:
+ BitmapEx m_aBmpEx;
+ ::Bitmap m_aBitmap;
+ ::Bitmap m_aAlpha;
+ BitmapScopedInfoAccess m_pBmpAcc;
+ BitmapScopedInfoAccess m_pAlphaAcc;
+ std::optional<BitmapScopedReadAccess> m_pBmpReadAcc;
+ std::optional<BitmapScopedReadAccess> m_pAlphaReadAcc;
+ css::uno::Sequence<sal_Int8> m_aComponentTags;
+ css::uno::Sequence<sal_Int32> m_aComponentBitCounts;
+ css::rendering::IntegerBitmapLayout m_aLayout;
+ sal_Int32 m_nBitsPerInputPixel;
+ sal_Int32 m_nBitsPerOutputPixel;
+ sal_Int32 m_nRedIndex;
+ sal_Int32 m_nGreenIndex;
+ sal_Int32 m_nBlueIndex;
+ sal_Int32 m_nAlphaIndex;
+ sal_Int32 m_nIndexIndex;
+ bool m_bPalette;
+
+ SAL_DLLPRIVATE void setComponentInfo( sal_uInt32 redShift, sal_uInt32 greenShift, sal_uInt32 blueShift );
+ BitmapScopedReadAccess& getBitmapReadAccess();
+ BitmapScopedReadAccess& getAlphaReadAccess();
+
+ virtual ~VclCanvasBitmap() override;
+
+ public:
+ // XBitmap
+ virtual css::geometry::IntegerSize2D SAL_CALL getSize() override;
+ virtual sal_Bool SAL_CALL hasAlpha( ) override;
+ virtual css::uno::Reference< css::rendering::XBitmap > SAL_CALL getScaledBitmap( const css::geometry::RealSize2D& newSize, sal_Bool beFast ) override;
+
+ // XIntegerReadOnlyBitmap
+ virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL getData( css::rendering::IntegerBitmapLayout& bitmapLayout, const css::geometry::IntegerRectangle2D& rect ) override;
+ virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL getPixel( css::rendering::IntegerBitmapLayout& bitmapLayout, const css::geometry::IntegerPoint2D& pos ) override;
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::rendering::XBitmapPalette > getPalette( );
+ virtual css::rendering::IntegerBitmapLayout SAL_CALL getMemoryLayout( ) override;
+
+ // XBitmapPalette
+ virtual sal_Int32 SAL_CALL getNumberOfEntries() override;
+ virtual sal_Bool SAL_CALL getIndex( css::uno::Sequence< double >& entry, ::sal_Int32 nIndex ) override;
+ virtual sal_Bool SAL_CALL setIndex( const css::uno::Sequence< double >& color, sal_Bool transparency, ::sal_Int32 nIndex ) override;
+ virtual css::uno::Reference< css::rendering::XColorSpace > SAL_CALL getColorSpace( ) override;
+
+ // XIntegerBitmapColorSpace
+ virtual ::sal_Int8 SAL_CALL getType( ) override;
+ virtual css::uno::Sequence< sal_Int8 > SAL_CALL getComponentTags( ) override;
+ virtual ::sal_Int8 SAL_CALL getRenderingIntent( ) override;
+ virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getProperties( ) override;
+ virtual css::uno::Sequence< double > SAL_CALL convertColorSpace( const css::uno::Sequence< double >& deviceColor, const css::uno::Reference< css::rendering::XColorSpace >& targetColorSpace ) override;
+ virtual css::uno::Sequence< css::rendering::RGBColor > SAL_CALL convertToRGB( const css::uno::Sequence< double >& deviceColor ) override;
+ virtual css::uno::Sequence< css::rendering::ARGBColor > SAL_CALL convertToARGB( const css::uno::Sequence< double >& deviceColor ) override;
+ virtual css::uno::Sequence< css::rendering::ARGBColor > SAL_CALL convertToPARGB( const css::uno::Sequence< double >& deviceColor ) override;
+ virtual css::uno::Sequence< double > SAL_CALL convertFromRGB( const css::uno::Sequence< css::rendering::RGBColor >& rgbColor ) override;
+ virtual css::uno::Sequence< double > SAL_CALL convertFromARGB( const css::uno::Sequence< css::rendering::ARGBColor >& rgbColor ) override;
+ virtual css::uno::Sequence< double > SAL_CALL convertFromPARGB( const css::uno::Sequence< css::rendering::ARGBColor >& rgbColor ) override;
+ virtual ::sal_Int32 SAL_CALL getBitsPerPixel( ) override;
+ virtual css::uno::Sequence< ::sal_Int32 > SAL_CALL getComponentBitCounts( ) override;
+ virtual ::sal_Int8 SAL_CALL getEndianness( ) override;
+ virtual css::uno::Sequence<double> SAL_CALL convertFromIntegerColorSpace( const css::uno::Sequence< ::sal_Int8 >& deviceColor, const css::uno::Reference< css::rendering::XColorSpace >& targetColorSpace ) override;
+ virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL convertToIntegerColorSpace( const css::uno::Sequence< ::sal_Int8 >& deviceColor, const css::uno::Reference< css::rendering::XIntegerBitmapColorSpace >& targetColorSpace ) override;
+ virtual css::uno::Sequence< css::rendering::RGBColor > SAL_CALL convertIntegerToRGB( const css::uno::Sequence< ::sal_Int8 >& deviceColor ) override;
+ virtual css::uno::Sequence< css::rendering::ARGBColor > SAL_CALL convertIntegerToARGB( const css::uno::Sequence< ::sal_Int8 >& deviceColor ) override;
+ virtual css::uno::Sequence< css::rendering::ARGBColor > SAL_CALL convertIntegerToPARGB( const css::uno::Sequence< ::sal_Int8 >& deviceColor ) override;
+ virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromRGB( const css::uno::Sequence< css::rendering::RGBColor >& rgbColor ) override;
+ virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromARGB( const css::uno::Sequence< css::rendering::ARGBColor >& rgbColor ) override;
+ virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromPARGB( const css::uno::Sequence< css::rendering::ARGBColor >& rgbColor ) override;
+
+ /** Create API wrapper for given BitmapEx
+
+ @param rBitmap
+ Bitmap to wrap. As usual, changes to the original bitmap
+ are not reflected in this object (copy on write).
+ */
+ explicit VclCanvasBitmap( const BitmapEx& rBitmap );
+
+ /// Retrieve contained bitmap. Call me with locked Solar mutex!
+ const BitmapEx& getBitmapEx() const { return m_aBmpEx; }
+ };
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/configsettings.hxx b/vcl/inc/configsettings.hxx
new file mode 100644
index 0000000000..ea6cf65740
--- /dev/null
+++ b/vcl/inc/configsettings.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_CONFIGSETTINGS_HXX
+#define INCLUDED_VCL_CONFIGSETTINGS_HXX
+
+#include <rtl/ustring.hxx>
+#include <unotools/configitem.hxx>
+#include <vcl/dllapi.h>
+
+#include <unordered_map>
+
+namespace com::sun::star::uno { template <typename > class Sequence; }
+
+namespace vcl
+{
+ typedef std::unordered_map< OUString, OUString > OUStrMap;
+ class SmallOUStrMap : public OUStrMap { public: SmallOUStrMap() : OUStrMap(1) {} };
+
+
+ //= SettingsConfigItem
+
+ class VCL_DLLPUBLIC SettingsConfigItem final : public ::utl::ConfigItem
+ {
+ private:
+ std::unordered_map< OUString, SmallOUStrMap > m_aSettings;
+
+ virtual void Notify( const css::uno::Sequence< OUString >& rPropertyNames ) override;
+
+ void getValues();
+ SettingsConfigItem();
+
+ virtual void ImplCommit() override;
+
+ public:
+ virtual ~SettingsConfigItem() override;
+
+ static SettingsConfigItem* get();
+
+ OUString getValue( const OUString& rGroup, const OUString& rKey ) const;
+ void setValue( const OUString& rGroup, const OUString& rKey, const OUString& rValue );
+
+ };
+
+
+} // namespace vcl
+
+
+#endif // INCLUDED_VCL_CONFIGSETTINGS_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/cursor_hotspots.hxx b/vcl/inc/cursor_hotspots.hxx
new file mode 100644
index 0000000000..7f356fa956
--- /dev/null
+++ b/vcl/inc/cursor_hotspots.hxx
@@ -0,0 +1,171 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_CURSOR_HOTSPOTS_HXX
+#define INCLUDED_VCL_INC_CURSOR_HOTSPOTS_HXX
+
+#define help_curs_x_hot 0
+#define help_curs_y_hot 0
+#define pen_curs_x_hot 3
+#define pen_curs_y_hot 27
+#define nodrop_curs_x_hot 9
+#define nodrop_curs_y_hot 9
+#define magnify_curs_x_hot 12
+#define magnify_curs_y_hot 13
+#define rotate_curs_x_hot 15
+#define rotate_curs_y_hot 15
+#define hshear_curs_x_hot 15
+#define hshear_curs_y_hot 15
+#define vshear_curs_x_hot 15
+#define vshear_curs_y_hot 15
+#define drawline_curs_x_hot 7
+#define drawline_curs_y_hot 7
+#define drawrect_curs_x_hot 7
+#define drawrect_curs_y_hot 7
+#define drawpolygon_curs_x_hot 7
+#define drawpolygon_curs_y_hot 7
+#define drawbezier_curs_x_hot 7
+#define drawbezier_curs_y_hot 7
+#define drawarc_curs_x_hot 7
+#define drawarc_curs_y_hot 7
+#define drawpie_curs_x_hot 7
+#define drawpie_curs_y_hot 7
+#define drawcirclecut_curs_x_hot 7
+#define drawcirclecut_curs_y_hot 7
+#define drawellipse_curs_x_hot 7
+#define drawellipse_curs_y_hot 7
+#define drawconnect_curs_x_hot 7
+#define drawconnect_curs_y_hot 7
+#define drawtext_curs_x_hot 8
+#define drawtext_curs_y_hot 8
+#define mirror_curs_x_hot 14
+#define mirror_curs_y_hot 12
+#define crook_curs_x_hot 15
+#define crook_curs_y_hot 14
+#define crop_curs_x_hot 9
+#define crop_curs_y_hot 9
+#define movepoint_curs_x_hot 0
+#define movepoint_curs_y_hot 0
+#define movebezierweight_curs_x_hot 0
+#define movebezierweight_curs_y_hot 0
+#define drawfreehand_curs_x_hot 8
+#define drawfreehand_curs_y_hot 8
+#define drawcaption_curs_x_hot 8
+#define drawcaption_curs_y_hot 8
+#define movedata_curs_x_hot 1
+#define movedata_curs_y_hot 1
+#define copydata_curs_x_hot 1
+#define copydata_curs_y_hot 1
+#define linkdata_curs_x_hot 1
+#define linkdata_curs_y_hot 1
+#define movedlnk_curs_x_hot 1
+#define movedlnk_curs_y_hot 1
+#define copydlnk_curs_x_hot 1
+#define copydlnk_curs_y_hot 1
+#define movefile_curs_x_hot 9
+#define movefile_curs_y_hot 9
+#define copyfile_curs_x_hot 9
+#define copyfile_curs_y_hot 9
+#define linkfile_curs_x_hot 9
+#define linkfile_curs_y_hot 9
+#define moveflnk_curs_x_hot 9
+#define moveflnk_curs_y_hot 9
+#define copyflnk_curs_x_hot 9
+#define copyflnk_curs_y_hot 9
+#define movefiles_curs_x_hot 8
+#define movefiles_curs_y_hot 9
+#define copyfiles_curs_x_hot 8
+#define copyfiles_curs_y_hot 9
+
+#define chart_curs_x_hot 15
+#define chart_curs_y_hot 16
+#define detective_curs_x_hot 12
+#define detective_curs_y_hot 13
+#define pivotcol_curs_x_hot 7
+#define pivotcol_curs_y_hot 5
+#define pivotfld_curs_x_hot 8
+#define pivotfld_curs_y_hot 7
+#define pivotrow_curs_x_hot 8
+#define pivotrow_curs_y_hot 7
+#define pivotdel_curs_x_hot 9
+#define pivotdel_curs_y_hot 8
+
+#define chain_curs_x_hot 0
+#define chain_curs_y_hot 2
+#define chainnot_curs_x_hot 2
+#define chainnot_curs_y_hot 2
+
+#define ase_curs_x_hot 19
+#define ase_curs_y_hot 16
+#define asn_curs_x_hot 16
+#define asn_curs_y_hot 12
+#define asne_curs_x_hot 21
+#define asne_curs_y_hot 10
+#define asns_curs_x_hot 15
+#define asns_curs_y_hot 15
+#define asnswe_curs_x_hot 15
+#define asnswe_curs_y_hot 15
+#define asnw_curs_x_hot 10
+#define asnw_curs_y_hot 10
+#define ass_curs_x_hot 15
+#define ass_curs_y_hot 19
+#define asse_curs_x_hot 21
+#define asse_curs_y_hot 21
+#define assw_curs_x_hot 10
+#define assw_curs_y_hot 21
+#define asw_curs_x_hot 12
+#define asw_curs_y_hot 15
+#define aswe_curs_x_hot 15
+#define aswe_curs_y_hot 15
+#define nullcurs_x_hot 2
+#define nullcurs_y_hot 2
+
+#define fill_curs_x_hot 10
+#define fill_curs_y_hot 22
+#define vertcurs_curs_x_hot 8
+#define vertcurs_curs_y_hot 8
+#define tblsele_curs_x_hot 14
+#define tblsele_curs_y_hot 8
+#define tblsels_curs_x_hot 7
+#define tblsels_curs_y_hot 14
+#define tblselse_curs_x_hot 14
+#define tblselse_curs_y_hot 14
+#define tblselw_curs_x_hot 1
+#define tblselw_curs_y_hot 8
+#define tblselsw_curs_x_hot 1
+#define tblselsw_curs_y_hot 14
+#define hidewhitespace_curs_x_hot 0
+#define hidewhitespace_curs_y_hot 10
+#define showwhitespace_curs_x_hot 0
+#define showwhitespace_curs_y_hot 10
+
+#define wait_curs_x_hot 10
+#define wait_curs_y_hot 10
+#define nwsize_curs_x_hot 10
+#define nwsize_curs_y_hot 10
+#define nesize_curs_x_hot 10
+#define nesize_curs_y_hot 10
+#define swsize_curs_x_hot 10
+#define swsize_curs_y_hot 10
+#define sesize_curs_x_hot 10
+#define sesize_curs_y_hot 10
+#define window_nwsize_curs_x_hot 10
+#define window_nwsize_curs_y_hot 10
+#define window_nesize_curs_x_hot 10
+#define window_nesize_curs_y_hot 10
+#define window_swsize_curs_x_hot 10
+#define window_swsize_curs_y_hot 10
+#define window_sesize_curs_x_hot 10
+#define window_sesize_curs_y_hot 10
+#define fatcross_curs_x_hot 15
+#define fatcross_curs_y_hot 15
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/dbggui.hxx b/vcl/inc/dbggui.hxx
new file mode 100644
index 0000000000..b3d93c5e16
--- /dev/null
+++ b/vcl/inc/dbggui.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 .
+ */
+
+#pragma once
+
+#ifndef NDEBUG
+
+void DbgGUIInitSolarMutexCheck();
+void DbgGUIDeInitSolarMutexCheck();
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/debugevent.hxx b/vcl/inc/debugevent.hxx
new file mode 100644
index 0000000000..89010face2
--- /dev/null
+++ b/vcl/inc/debugevent.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/.
+ */
+
+#ifndef INCLUDED_VCL_DEBUGEVENT_HXX
+#define INCLUDED_VCL_DEBUGEVENT_HXX
+
+#include <vcl/timer.hxx>
+#include <sal/types.h>
+
+namespace vcl { class Window; }
+
+class DebugEventInjector final : private Timer {
+ sal_uInt32 mnEventsLeft;
+ DebugEventInjector( sal_uInt32 nMaxEvents );
+
+ static vcl::Window *ChooseWindow();
+ static void InjectTextEvent();
+ static void InjectMenuEvent();
+ static void InjectEvent();
+ static void InjectKeyNavEdit();
+ virtual void Invoke() override;
+
+ public:
+ static DebugEventInjector *getCreate();
+};
+
+#endif // INCLUDED_VCL_DEBUGEVENT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/displayconnectiondispatch.hxx b/vcl/inc/displayconnectiondispatch.hxx
new file mode 100644
index 0000000000..a763ccd469
--- /dev/null
+++ b/vcl/inc/displayconnectiondispatch.hxx
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_DISPLAYCONNECTIONDISPATCH_HXX
+#define INCLUDED_VCL_INC_DISPLAYCONNECTIONDISPATCH_HXX
+
+#include <sal/config.h>
+#include <com/sun/star/awt/XDisplayConnection.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/uno/Reference.hxx>
+#include <mutex>
+#include <vector>
+
+namespace vcl {
+
+class DisplayConnectionDispatch final :
+ public cppu::WeakImplHelper< css::awt::XDisplayConnection >
+{
+ std::mutex m_aMutex;
+ ::std::vector< css::uno::Reference< css::awt::XEventHandler > >
+ m_aHandlers;
+ OUString m_ConnectionIdentifier;
+public:
+ DisplayConnectionDispatch();
+ ~DisplayConnectionDispatch() override;
+
+ void start();
+ void terminate();
+
+ bool dispatchEvent( void const * pData, int nBytes );
+
+ // XDisplayConnection
+ virtual void SAL_CALL addEventHandler( const css::uno::Any& window, const css::uno::Reference< css::awt::XEventHandler >& handler, sal_Int32 eventMask ) override;
+ virtual void SAL_CALL removeEventHandler( const css::uno::Any& window, const css::uno::Reference< css::awt::XEventHandler >& handler ) override;
+ virtual void SAL_CALL addErrorHandler( const css::uno::Reference< css::awt::XEventHandler >& handler ) override;
+ virtual void SAL_CALL removeErrorHandler( const css::uno::Reference< css::awt::XEventHandler >& handler ) override;
+ virtual css::uno::Any SAL_CALL getIdentifier() override;
+
+};
+
+}
+
+
+
+#endif // INCLUDED_VCL_INC_DISPLAYCONNECTIONDISPATCH_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/dndeventdispatcher.hxx b/vcl/inc/dndeventdispatcher.hxx
new file mode 100644
index 0000000000..0b3f585602
--- /dev/null
+++ b/vcl/inc/dndeventdispatcher.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_DNDEVENTDISPATCHER_HXX
+#define INCLUDED_VCL_INC_DNDEVENTDISPATCHER_HXX
+
+#include <com/sun/star/datatransfer/dnd/XDropTargetListener.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTargetDragContext.hpp>
+
+#include <com/sun/star/datatransfer/dnd/XDragGestureListener.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <vcl/window.hxx>
+#include <mutex>
+
+class DNDEventDispatcher final : public ::cppu::WeakImplHelper<
+ css::datatransfer::dnd::XDropTargetListener,
+ css::datatransfer::dnd::XDropTargetDragContext,
+ css::datatransfer::dnd::XDragGestureListener >
+{
+ VclPtr<vcl::Window> m_pTopWindow;
+
+ VclPtr<vcl::Window> m_pCurrentWindow;
+ void designate_currentwindow(vcl::Window *pWindow);
+ DECL_LINK(WindowEventListener, VclWindowEvent&, void);
+
+ std::recursive_mutex m_aMutex;
+ css::uno::Sequence< css::datatransfer::DataFlavor > m_aDataFlavorList;
+
+ vcl::Window* findTopLevelWindow(Point& location);
+ /*
+ * fire the events on the dnd listener container of the specified window
+ */
+
+ /// @throws css::uno::RuntimeException
+ static sal_Int32 fireDragEnterEvent( vcl::Window *pWindow, const css::uno::Reference< css::datatransfer::dnd::XDropTargetDragContext >& xContext,
+ const sal_Int8 nDropAction, const Point& rLocation, const sal_Int8 nSourceAction,
+ const css::uno::Sequence< css::datatransfer::DataFlavor >& aFlavorList );
+
+ /// @throws css::uno::RuntimeException
+ static sal_Int32 fireDragOverEvent( vcl::Window *pWindow, const css::uno::Reference< css::datatransfer::dnd::XDropTargetDragContext >& xContext,
+ const sal_Int8 nDropAction, const Point& rLocation, const sal_Int8 nSourceAction );
+
+ /// @throws css::uno::RuntimeException
+ static sal_Int32 fireDragExitEvent( vcl::Window *pWindow );
+
+ /// @throws css::uno::RuntimeException
+ static sal_Int32 fireDropActionChangedEvent( vcl::Window *pWindow, const css::uno::Reference< css::datatransfer::dnd::XDropTargetDragContext >& xContext,
+ const sal_Int8 nDropAction, const Point& rLocation, const sal_Int8 nSourceAction );
+
+ /// @throws css::uno::RuntimeException
+ static sal_Int32 fireDropEvent( vcl::Window *pWindow, const css::uno::Reference< css::datatransfer::dnd::XDropTargetDropContext >& xContext,
+ const sal_Int8 nDropAction, const Point& rLocation, const sal_Int8 nSourceAction,
+ const css::uno::Reference< css::datatransfer::XTransferable >& xTransferable );
+
+ /// @throws css::uno::RuntimeException
+ static sal_Int32 fireDragGestureEvent( vcl::Window *pWindow, const css::uno::Reference< css::datatransfer::dnd::XDragSource >& xSource,
+ const css::uno::Any& event, const Point& rOrigin, const sal_Int8 nDragAction );
+
+public:
+
+ DNDEventDispatcher( vcl::Window * pTopWindow );
+ virtual ~DNDEventDispatcher() override;
+
+ /*
+ * XDropTargetDragContext
+ */
+
+ virtual void SAL_CALL acceptDrag( sal_Int8 dropAction ) override;
+ virtual void SAL_CALL rejectDrag() override;
+
+ /*
+ * XDropTargetListener
+ */
+
+ virtual void SAL_CALL drop( const css::datatransfer::dnd::DropTargetDropEvent& dtde ) override;
+ virtual void SAL_CALL dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& dtdee ) override;
+ virtual void SAL_CALL dragExit( const css::datatransfer::dnd::DropTargetEvent& dte ) override;
+ virtual void SAL_CALL dragOver( const css::datatransfer::dnd::DropTargetDragEvent& dtde ) override;
+ virtual void SAL_CALL dropActionChanged( const css::datatransfer::dnd::DropTargetDragEvent& dtde ) override;
+
+ /*
+ * XDragGestureListener
+ */
+
+ virtual void SAL_CALL dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& dge ) override;
+
+ /*
+ * XEventListener
+ */
+
+ virtual void SAL_CALL disposing( const css::lang::EventObject& eo ) override;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/dndhelper.hxx b/vcl/inc/dndhelper.hxx
new file mode 100644
index 0000000000..f79382f4ee
--- /dev/null
+++ b/vcl/inc/dndhelper.hxx
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <sal/types.h>
+#include <vcl/dllapi.h>
+#include <com/sun/star/uno/Reference.hxx>
+
+namespace com::sun::star::lang
+{
+class XInitialization;
+}
+namespace com::sun::star::uno
+{
+class XInterface;
+}
+
+namespace vcl
+{
+// Ole and X11 refer to the UNO DnD interface names and their expected XInitialization arguments.
+
+enum class DragOrDrop
+{
+ Drag,
+ Drop
+};
+VCL_DLLPUBLIC css::uno::Reference<css::uno::XInterface>
+OleDnDHelper(const css::uno::Reference<css::lang::XInitialization>&, sal_IntPtr pWin, DragOrDrop);
+
+VCL_DLLPUBLIC css::uno::Reference<css::uno::XInterface>
+X11DnDHelper(const css::uno::Reference<css::lang::XInitialization>&, sal_IntPtr pWin);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/dndlistenercontainer.hxx b/vcl/inc/dndlistenercontainer.hxx
new file mode 100644
index 0000000000..bc4109d5e4
--- /dev/null
+++ b/vcl/inc/dndlistenercontainer.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_DNDLCON_HXX
+#define INCLUDED_VCL_INC_DNDLCON_HXX
+
+#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragSource.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTargetDragContext.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTargetDropContext.hpp>
+#include <comphelper/compbase.hxx>
+#include <comphelper/interfacecontainer4.hxx>
+
+class GenericDropTargetDropContext :
+ public ::cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDropContext>
+{
+public:
+ GenericDropTargetDropContext();
+
+ // XDropTargetDropContext
+ virtual void SAL_CALL acceptDrop( sal_Int8 dragOperation ) override;
+ virtual void SAL_CALL rejectDrop() override;
+ virtual void SAL_CALL dropComplete( sal_Bool success ) override;
+};
+
+class GenericDropTargetDragContext :
+ public ::cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDragContext>
+{
+public:
+ GenericDropTargetDragContext();
+
+ // XDropTargetDragContext
+ virtual void SAL_CALL acceptDrag( sal_Int8 dragOperation ) override;
+ virtual void SAL_CALL rejectDrag() override;
+};
+
+class DNDListenerContainer final :
+ public ::comphelper::WeakComponentImplHelper<
+ css::datatransfer::dnd::XDragGestureRecognizer,
+ css::datatransfer::dnd::XDropTargetDragContext,
+ css::datatransfer::dnd::XDropTargetDropContext,
+ css::datatransfer::dnd::XDropTarget >
+{
+ bool m_bActive;
+ sal_Int8 m_nDefaultActions;
+ comphelper::OInterfaceContainerHelper4<css::datatransfer::dnd::XDragGestureListener> maDragGestureListeners;
+ comphelper::OInterfaceContainerHelper4<css::datatransfer::dnd::XDropTargetListener> maDropTargetListeners;
+ css::uno::Reference< css::datatransfer::dnd::XDropTargetDragContext > m_xDropTargetDragContext;
+ css::uno::Reference< css::datatransfer::dnd::XDropTargetDropContext > m_xDropTargetDropContext;
+
+public:
+
+ DNDListenerContainer( sal_Int8 nDefaultActions );
+ virtual ~DNDListenerContainer() override;
+
+ sal_uInt32 fireDropEvent(
+ const css::uno::Reference< css::datatransfer::dnd::XDropTargetDropContext >& context,
+ sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions,
+ const css::uno::Reference< css::datatransfer::XTransferable >& transferable );
+
+ sal_uInt32 fireDragExitEvent();
+
+ sal_uInt32 fireDragOverEvent(
+ const css::uno::Reference< css::datatransfer::dnd::XDropTargetDragContext >& context,
+ sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions );
+
+ sal_uInt32 fireDragEnterEvent(
+ const css::uno::Reference< css::datatransfer::dnd::XDropTargetDragContext >& context,
+ sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions,
+ const css::uno::Sequence< css::datatransfer::DataFlavor >& dataFlavor );
+
+ sal_uInt32 fireDropActionChangedEvent(
+ const css::uno::Reference< css::datatransfer::dnd::XDropTargetDragContext >& context,
+ sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions );
+
+ sal_uInt32 fireDragGestureEvent(
+ sal_Int8 dragAction, sal_Int32 dragOriginX, sal_Int32 dragOriginY,
+ const css::uno::Reference< css::datatransfer::dnd::XDragSource >& dragSource,
+ const css::uno::Any& triggerEvent );
+
+ /*
+ * XDragGestureRecognizer
+ */
+
+ virtual void SAL_CALL addDragGestureListener( const css::uno::Reference< css::datatransfer::dnd::XDragGestureListener >& dgl ) override;
+ virtual void SAL_CALL removeDragGestureListener( const css::uno::Reference< css::datatransfer::dnd::XDragGestureListener >& dgl ) override;
+ virtual void SAL_CALL resetRecognizer( ) override;
+
+ /*
+ * XDropTargetDragContext
+ */
+
+ virtual void SAL_CALL acceptDrag( sal_Int8 dragOperation ) override;
+ virtual void SAL_CALL rejectDrag( ) override;
+
+ /*
+ * XDropTargetDropContext
+ */
+
+ virtual void SAL_CALL acceptDrop( sal_Int8 dropOperation ) override;
+ virtual void SAL_CALL rejectDrop( ) override;
+ virtual void SAL_CALL dropComplete( sal_Bool success ) override;
+
+ /*
+ * XDropTarget
+ */
+
+ virtual void SAL_CALL addDropTargetListener( const css::uno::Reference< css::datatransfer::dnd::XDropTargetListener >& dtl ) override;
+ virtual void SAL_CALL removeDropTargetListener( const css::uno::Reference< css::datatransfer::dnd::XDropTargetListener >& dtl ) 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;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/drawmode.hxx b/vcl/inc/drawmode.hxx
new file mode 100644
index 0000000000..4c1647e0e1
--- /dev/null
+++ b/vcl/inc/drawmode.hxx
@@ -0,0 +1,50 @@
+/* -*- 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
+
+#include <tools/color.hxx>
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/font.hxx>
+#include <vcl/rendercontext/DrawModeFlags.hxx>
+
+class StyleSettings;
+
+namespace vcl::drawmode
+{
+VCL_DLLPUBLIC Color GetLineColor(Color const& rColor, DrawModeFlags nDrawMode,
+ StyleSettings const& rStyleSettings);
+
+VCL_DLLPUBLIC Color GetFillColor(Color const& rColor, DrawModeFlags nDrawMode,
+ StyleSettings const& rStyleSettings);
+
+VCL_DLLPUBLIC Color GetHatchColor(Color const& rColor, DrawModeFlags nDrawMode,
+ StyleSettings const& rStyleSettings);
+
+VCL_DLLPUBLIC Color GetTextColor(Color const& rColor, DrawModeFlags nDrawMode,
+ StyleSettings const& rStyleSettings);
+
+VCL_DLLPUBLIC vcl::Font GetFont(vcl::Font const& rFont, DrawModeFlags nDrawMode,
+ StyleSettings const& rStyleSettings);
+
+VCL_DLLPUBLIC BitmapEx GetBitmapEx(BitmapEx const& rBitmapEx, DrawModeFlags nDrawMode);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/driverblocklist.hxx b/vcl/inc/driverblocklist.hxx
new file mode 100644
index 0000000000..f3fc81c664
--- /dev/null
+++ b/vcl/inc/driverblocklist.hxx
@@ -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/.
+ */
+
+#ifndef INCLUDED_VCL_DRIVERBLOCKLIST_HXX
+#define INCLUDED_VCL_DRIVERBLOCKLIST_HXX
+
+#include <vcl/dllapi.h>
+#include <xmlreader/xmlreader.hxx>
+
+#include <string_view>
+#include <vector>
+
+namespace DriverBlocklist
+{
+// Details of how to treat a version number.
+enum class VersionType
+{
+ OpenGL, // a.b.c.d, 1.98 > 1.978
+ Vulkan // a.b.c , 1.98 < 1.978
+};
+
+VCL_DLLPUBLIC bool IsDeviceBlocked(const OUString& blocklistURL, VersionType versionType,
+ std::u16string_view driverVersion, std::u16string_view vendorId,
+ const OUString& deviceId);
+
+#ifdef _WIN32
+VCL_DLLPUBLIC int32_t GetWindowsVersion();
+#endif
+
+enum DeviceVendor
+{
+ VendorAll,
+ VendorIntel,
+ VendorNVIDIA,
+ VendorAMD,
+ VendorMicrosoft,
+};
+const int DeviceVendorMax = VendorMicrosoft + 1;
+
+/// Returns vendor for the given vendor ID, or VendorAll if not known.
+VCL_DLLPUBLIC DeviceVendor GetVendorFromId(uint32_t id);
+
+VCL_DLLPUBLIC std::string_view GetVendorNameFromId(uint32_t id);
+
+// The rest should be private (only for the unittest).
+
+struct InvalidFileException
+{
+};
+
+enum OperatingSystem
+{
+ DRIVER_OS_UNKNOWN = 0,
+ DRIVER_OS_WINDOWS_FIRST,
+ DRIVER_OS_WINDOWS_7 = DRIVER_OS_WINDOWS_FIRST,
+ DRIVER_OS_WINDOWS_8,
+ DRIVER_OS_WINDOWS_8_1,
+ DRIVER_OS_WINDOWS_10,
+ DRIVER_OS_WINDOWS_LAST = DRIVER_OS_WINDOWS_10,
+ DRIVER_OS_WINDOWS_ALL,
+ DRIVER_OS_LINUX,
+ DRIVER_OS_OSX_FIRST,
+ DRIVER_OS_OSX_10_5 = DRIVER_OS_OSX_FIRST,
+ DRIVER_OS_OSX_10_6,
+ DRIVER_OS_OSX_10_7,
+ DRIVER_OS_OSX_10_8,
+ DRIVER_OS_OSX_LAST = DRIVER_OS_OSX_10_8,
+ DRIVER_OS_OSX_ALL,
+ DRIVER_OS_ANDROID,
+ DRIVER_OS_ALL
+};
+
+enum VersionComparisonOp
+{
+ DRIVER_LESS_THAN, // driver < version
+ DRIVER_LESS_THAN_OR_EQUAL, // driver <= version
+ DRIVER_GREATER_THAN, // driver > version
+ DRIVER_GREATER_THAN_OR_EQUAL, // driver >= version
+ DRIVER_EQUAL, // driver == version
+ DRIVER_NOT_EQUAL, // driver != version
+ DRIVER_BETWEEN_EXCLUSIVE, // driver > version && driver < versionMax
+ DRIVER_BETWEEN_INCLUSIVE, // driver >= version && driver <= versionMax
+ DRIVER_BETWEEN_INCLUSIVE_START, // driver >= version && driver < versionMax
+ DRIVER_COMPARISON_IGNORED
+};
+
+struct DriverInfo
+{
+ DriverInfo(OperatingSystem os, OUString vendor, VersionComparisonOp op, uint64_t driverVersion,
+ bool bAllowListed = false, const char* suggestedVersion = nullptr);
+
+ DriverInfo();
+
+ OperatingSystem meOperatingSystem;
+ OUString maAdapterVendor;
+ std::vector<OUString> maDevices;
+
+ bool mbAllowlisted;
+
+ VersionComparisonOp meComparisonOp;
+
+ /* versions are assumed to be A.B.C.D packed as 0xAAAABBBBCCCCDDDD */
+ uint64_t mnDriverVersion;
+ uint64_t mnDriverVersionMax;
+
+ OUString maSuggestedVersion;
+ OUString maMsg;
+};
+
+class VCL_DLLPUBLIC Parser
+{
+public:
+ Parser(OUString aURL, std::vector<DriverInfo>& rDriverList, VersionType versionType);
+ bool parse();
+
+private:
+ void handleEntry(DriverInfo& rDriver, xmlreader::XmlReader& rReader);
+ void handleList(xmlreader::XmlReader& rReader);
+ void handleContent(xmlreader::XmlReader& rReader);
+ static void handleDevices(DriverInfo& rDriver, xmlreader::XmlReader& rReader);
+ uint64_t getVersion(std::string_view rString);
+
+ enum class BlockType
+ {
+ ALLOWLIST,
+ DENYLIST,
+ UNKNOWN
+ };
+
+ BlockType meBlockType;
+ std::vector<DriverInfo>& mrDriverList;
+ OUString maURL;
+ const VersionType mVersionType;
+};
+
+OUString VCL_DLLPUBLIC GetVendorId(DeviceVendor id);
+
+bool VCL_DLLPUBLIC FindBlocklistedDeviceInList(std::vector<DriverInfo>& aDeviceInfos,
+ VersionType versionType,
+ std::u16string_view sDriverVersion,
+ std::u16string_view sAdapterVendorID,
+ OUString const& sAdapterDeviceID,
+ OperatingSystem system,
+ const OUString& blocklistURL = OUString());
+
+#define GFX_DRIVER_VERSION(a, b, c, d) \
+ ((uint64_t(a) << 48) | (uint64_t(b) << 32) | (uint64_t(c) << 16) | uint64_t(d))
+
+inline uint64_t OpenGLVersion(uint32_t a, uint32_t b, uint32_t c, uint32_t d)
+{
+ // We make sure every driver number is padded by 0s, this will allow us the
+ // easiest 'compare as if decimals' approach. See ParseDriverVersion for a
+ // more extensive explanation of this approach.
+ while (b > 0 && b < 1000)
+ {
+ b *= 10;
+ }
+ while (c > 0 && c < 1000)
+ {
+ c *= 10;
+ }
+ while (d > 0 && d < 1000)
+ {
+ d *= 10;
+ }
+ return GFX_DRIVER_VERSION(a, b, c, d);
+}
+
+} // namespace
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/factory.hxx b/vcl/inc/factory.hxx
new file mode 100644
index 0000000000..4a808d29c7
--- /dev/null
+++ b/vcl/inc/factory.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_FACTORY_HXX
+#define INCLUDED_VCL_INC_FACTORY_HXX
+
+#include <sal/config.h>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <rtl/ustring.hxx>
+
+namespace com::sun::star {
+ namespace lang {
+ class XMultiServiceFactory;
+ class XSingleServiceFactory;
+ }
+ namespace uno { class XInterface; }
+}
+
+namespace vcl {
+
+css::uno::Sequence<OUString> DragSource_getSupportedServiceNames();
+
+OUString DragSource_getImplementationName();
+
+css::uno::Reference<css::uno::XInterface> DragSource_createInstance(
+ css::uno::Reference<css::lang::XMultiServiceFactory > const &);
+
+css::uno::Sequence<OUString> DropTarget_getSupportedServiceNames();
+
+OUString DropTarget_getImplementationName();
+
+css::uno::Reference<css::uno::XInterface> DropTarget_createInstance(
+ css::uno::Reference<css::lang::XMultiServiceFactory > const &);
+
+}
+
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/BmpReader.hxx b/vcl/inc/filter/BmpReader.hxx
new file mode 100644
index 0000000000..4b6733eeb7
--- /dev/null
+++ b/vcl/inc/filter/BmpReader.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+
+VCL_DLLPUBLIC bool BmpReader(SvStream& rStream, Graphic& rGraphic);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/BmpWriter.hxx b/vcl/inc/filter/BmpWriter.hxx
new file mode 100644
index 0000000000..745aeb6b69
--- /dev/null
+++ b/vcl/inc/filter/BmpWriter.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 .
+ */
+
+#pragma once
+
+#include <tools/stream.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/FilterConfigItem.hxx>
+
+VCL_DLLPUBLIC bool BmpWriter(SvStream& rStream, const Graphic& rGraphic,
+ FilterConfigItem* pFilterConfigItem);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/DxfReader.hxx b/vcl/inc/filter/DxfReader.hxx
new file mode 100644
index 0000000000..f1e89bf4b1
--- /dev/null
+++ b/vcl/inc/filter/DxfReader.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+
+VCL_DLLPUBLIC bool ImportDxfGraphic(SvStream& rStream, Graphic& rGraphic);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/EpsReader.hxx b/vcl/inc/filter/EpsReader.hxx
new file mode 100644
index 0000000000..8cc945ee8e
--- /dev/null
+++ b/vcl/inc/filter/EpsReader.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+
+VCL_DLLPUBLIC bool ImportEpsGraphic(SvStream& rStream, Graphic& rGraphic);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/EpsWriter.hxx b/vcl/inc/filter/EpsWriter.hxx
new file mode 100644
index 0000000000..104b5ff6bf
--- /dev/null
+++ b/vcl/inc/filter/EpsWriter.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/FilterConfigItem.hxx>
+
+VCL_DLLPUBLIC bool ExportEpsGraphic(SvStream& rStream, const Graphic& rGraphic,
+ FilterConfigItem* pFilterConfigItem);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/GifWriter.hxx b/vcl/inc/filter/GifWriter.hxx
new file mode 100644
index 0000000000..1b67039f7e
--- /dev/null
+++ b/vcl/inc/filter/GifWriter.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/FilterConfigItem.hxx>
+
+VCL_DLLPUBLIC bool ExportGifGraphic(SvStream& rStream, const Graphic& rGraphic,
+ FilterConfigItem* pFilterConfigItem);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/MetReader.hxx b/vcl/inc/filter/MetReader.hxx
new file mode 100644
index 0000000000..6855cf7a3f
--- /dev/null
+++ b/vcl/inc/filter/MetReader.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+
+VCL_DLLPUBLIC bool ImportMetGraphic(SvStream& rStream, Graphic& rGraphic);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/PbmReader.hxx b/vcl/inc/filter/PbmReader.hxx
new file mode 100644
index 0000000000..5fe4d8a295
--- /dev/null
+++ b/vcl/inc/filter/PbmReader.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+
+VCL_DLLPUBLIC bool ImportPbmGraphic(SvStream& rStream, Graphic& rGraphic);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/PcdReader.hxx b/vcl/inc/filter/PcdReader.hxx
new file mode 100644
index 0000000000..216a14b890
--- /dev/null
+++ b/vcl/inc/filter/PcdReader.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/FilterConfigItem.hxx>
+
+VCL_DLLPUBLIC bool ImportPcdGraphic(SvStream& rStream, Graphic& rGraphic,
+ FilterConfigItem* pFilterConfigItem);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/PcxReader.hxx b/vcl/inc/filter/PcxReader.hxx
new file mode 100644
index 0000000000..73e182c628
--- /dev/null
+++ b/vcl/inc/filter/PcxReader.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+
+VCL_DLLPUBLIC bool ImportPcxGraphic(SvStream& rStream, Graphic& rGraphic);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/PictReader.hxx b/vcl/inc/filter/PictReader.hxx
new file mode 100644
index 0000000000..fa3fb09a00
--- /dev/null
+++ b/vcl/inc/filter/PictReader.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 <sal/config.h>
+
+class GDIMetaFile;
+class SvStream;
+
+VCL_DLLPUBLIC bool ImportPictGraphic(SvStream& rStream, Graphic& rGraphic);
+
+/// Function to access PictReader::ReadPict for unit testing.
+VCL_DLLPUBLIC void ReadPictFile(SvStream& rStreamPict, GDIMetaFile& rGDIMetaFile);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/PsdReader.hxx b/vcl/inc/filter/PsdReader.hxx
new file mode 100644
index 0000000000..a257f04d43
--- /dev/null
+++ b/vcl/inc/filter/PsdReader.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+
+VCL_DLLPUBLIC bool ImportPsdGraphic(SvStream& rStream, Graphic& rGraphic);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/RasReader.hxx b/vcl/inc/filter/RasReader.hxx
new file mode 100644
index 0000000000..ea658cb317
--- /dev/null
+++ b/vcl/inc/filter/RasReader.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+
+VCL_DLLPUBLIC bool ImportRasGraphic(SvStream& rStream, Graphic& rGraphic);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/TgaReader.hxx b/vcl/inc/filter/TgaReader.hxx
new file mode 100644
index 0000000000..34f7564871
--- /dev/null
+++ b/vcl/inc/filter/TgaReader.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+
+VCL_DLLPUBLIC bool ImportTgaGraphic(SvStream& rStream, Graphic& rGraphic);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/TiffReader.hxx b/vcl/inc/filter/TiffReader.hxx
new file mode 100644
index 0000000000..3c9922895a
--- /dev/null
+++ b/vcl/inc/filter/TiffReader.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+
+VCL_DLLPUBLIC bool ImportTiffGraphicImport(SvStream& rStream, Graphic& rGraphic);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/TiffWriter.hxx b/vcl/inc/filter/TiffWriter.hxx
new file mode 100644
index 0000000000..71bad2554f
--- /dev/null
+++ b/vcl/inc/filter/TiffWriter.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/FilterConfigItem.hxx>
+
+VCL_DLLPUBLIC bool ExportTiffGraphicImport(SvStream& rStream, const Graphic& rGraphic,
+ const FilterConfigItem* pFilterConfigItem);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/WebpReader.hxx b/vcl/inc/filter/WebpReader.hxx
new file mode 100644
index 0000000000..fd8cc4894a
--- /dev/null
+++ b/vcl/inc/filter/WebpReader.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+
+VCL_DLLPUBLIC bool ImportWebpGraphic(SvStream& rStream, Graphic& rGraphic);
+
+bool ReadWebpInfo(SvStream& rStream, Size& pixelSize, sal_uInt16& bitsPerPixel, bool& hasAlpha);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/WebpWriter.hxx b/vcl/inc/filter/WebpWriter.hxx
new file mode 100644
index 0000000000..d3b6431c63
--- /dev/null
+++ b/vcl/inc/filter/WebpWriter.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 .
+ */
+
+#pragma once
+
+#include <tools/stream.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/FilterConfigItem.hxx>
+
+VCL_DLLPUBLIC bool ExportWebpGraphic(SvStream& rStream, const Graphic& rGraphic,
+ FilterConfigItem* pFilterConfigItem);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/XpmReader.hxx b/vcl/inc/filter/XpmReader.hxx
new file mode 100644
index 0000000000..6928a10be8
--- /dev/null
+++ b/vcl/inc/filter/XpmReader.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_IXPM_XPMREAD_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_IXPM_XPMREAD_HXX
+
+#include <tools/stream.hxx>
+#include <vcl/dllapi.h>
+
+class Graphic;
+
+VCL_DLLPUBLIC bool ImportXPM(SvStream& rStream, Graphic& rGraphic);
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_IXPM_XPMREAD_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/fltcall.hxx b/vcl/inc/fltcall.hxx
new file mode 100644
index 0000000000..f10e72b070
--- /dev/null
+++ b/vcl/inc/fltcall.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_FLTCALL_HXX
+#define INCLUDED_VCL_FLTCALL_HXX
+
+class FilterConfigItem;
+class SvStream;
+class Graphic;
+
+typedef bool (*PFilterCall)(SvStream & rStream, Graphic & rGraphic,
+ FilterConfigItem* pConfigItem);
+ // Of this type are both export-filter and import-filter functions
+ // rFileName is the complete path to the file to be imported or exported
+ // pCallBack can be NULL. pCallerData is handed to the callback function
+ // pOptionsConfig can be NULL; if not, the group of the config is already set
+ // and may not be changed by this filter!
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/font/DirectFontSubstitution.hxx b/vcl/inc/font/DirectFontSubstitution.hxx
new file mode 100644
index 0000000000..150bc37281
--- /dev/null
+++ b/vcl/inc/font/DirectFontSubstitution.hxx
@@ -0,0 +1,68 @@
+/* -*- 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
+
+#include <sal/config.h>
+
+#include <rtl/ustring.hxx>
+#include <unotools/fontdefs.hxx>
+
+#include <vcl/rendercontext/AddFontSubstituteFlags.hxx>
+
+#include <font/fontsubstitution.hxx>
+
+#include <string_view>
+#include <vector>
+
+namespace vcl::font
+{
+struct FontSubstEntry
+{
+ FontSubstEntry(std::u16string_view rFontName, std::u16string_view rSubstFontName,
+ AddFontSubstituteFlags nSubstFlags)
+ : maSearchName(GetEnglishSearchFontName(rFontName))
+ , maSearchReplaceName(GetEnglishSearchFontName(rSubstFontName))
+ , mnFlags(nSubstFlags)
+ {
+ }
+
+ OUString maSearchName;
+ OUString maSearchReplaceName;
+ AddFontSubstituteFlags mnFlags;
+};
+
+/** DirectFontSubstitution is for Tools->Options->FontReplacement and PsPrinter substitutions
+ The class is just a simple port of the unmaintainable manual-linked-list based mechanism
+ */
+// TODO: get rid of this class when the Tools->Options->FontReplacement tabpage is gone for good
+class DirectFontSubstitution final : public FontSubstitution
+{
+private:
+ std::vector<FontSubstEntry> maFontSubstList;
+
+public:
+ void AddFontSubstitute(const OUString& rFontName, const OUString& rSubstName,
+ AddFontSubstituteFlags nFlags);
+ void RemoveFontsSubstitute();
+ bool FindFontSubstitute(OUString& rSubstName, std::u16string_view rFontName) const;
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/font/EmphasisMark.hxx b/vcl/inc/font/EmphasisMark.hxx
new file mode 100644
index 0000000000..5e902da262
--- /dev/null
+++ b/vcl/inc/font/EmphasisMark.hxx
@@ -0,0 +1,45 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <tools/fontenum.hxx>
+#include <tools/gen.hxx>
+#include <tools/long.hxx>
+#include <tools/poly.hxx>
+
+#include <vcl/dllapi.h>
+
+namespace vcl::font
+{
+class VCL_DLLPUBLIC EmphasisMark
+{
+public:
+ EmphasisMark(FontEmphasisMark eEmphasis, tools::Long nHeight, sal_Int32 nDPIY);
+
+ tools::PolyPolygon GetShape() const { return maPolyPoly; }
+ bool IsShapePolyLine() const { return mbIsPolyLine; }
+ tools::Rectangle GetRect1() const { return maRect1; }
+ tools::Rectangle GetRect2() const { return maRect2; }
+ tools::Long GetYOffset() const { return mnYOff; }
+ tools::Long GetWidth() const { return mnWidth; }
+
+private:
+ tools::PolyPolygon maPolyPoly;
+ bool mbIsPolyLine;
+ tools::Rectangle maRect1;
+ tools::Rectangle maRect2;
+ tools::Long mnYOff;
+ tools::Long mnWidth;
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/font/FeatureCollector.hxx b/vcl/inc/font/FeatureCollector.hxx
new file mode 100644
index 0000000000..1b71d02159
--- /dev/null
+++ b/vcl/inc/font/FeatureCollector.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/.
+ *
+ */
+
+#pragma once
+
+#include <vcl/font/Feature.hxx>
+#include <hb.h>
+#include <i18nlangtag/languagetag.hxx>
+
+#include <font/PhysicalFontFace.hxx>
+
+namespace vcl::font
+{
+class FeatureCollector
+{
+private:
+ const PhysicalFontFace* m_pFace;
+ hb_face_t* m_pHbFace;
+ std::vector<vcl::font::Feature>& m_rFontFeatures;
+ const LanguageTag& m_rLanguageTag;
+
+public:
+ FeatureCollector(const PhysicalFontFace* pFace, std::vector<vcl::font::Feature>& rFontFeatures,
+ const LanguageTag& rLanguageTag)
+ : m_pFace(pFace)
+ , m_pHbFace(pFace->GetHbFace())
+ , m_rFontFeatures(rFontFeatures)
+ , m_rLanguageTag(rLanguageTag)
+ {
+ }
+
+private:
+ void collectForTable(hb_tag_t aTableTag);
+ bool collectGraphite();
+
+public:
+ bool collect();
+};
+
+} // namespace vcl::font
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/font/FontMetricData.hxx b/vcl/inc/font/FontMetricData.hxx
new file mode 100644
index 0000000000..83eb77d54c
--- /dev/null
+++ b/vcl/inc/font/FontMetricData.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <vcl/dllapi.h>
+#include <tools/degree.hxx>
+#include <tools/long.hxx>
+#include <tools/ref.hxx>
+#include <fontattributes.hxx>
+
+class FontMetricData;
+typedef tools::SvRef<FontMetricData> FontMetricDataRef;
+
+class OutputDevice;
+namespace vcl::font
+{
+class FontSelectPattern;
+}
+class LogicalFontInstance;
+
+class VCL_DLLPUBLIC FontMetricData final : public FontAttributes, public SvRefBase
+{
+public:
+ explicit FontMetricData( const vcl::font::FontSelectPattern& );
+
+ // font instance attributes from the font request
+ tools::Long GetWidth() const { return mnWidth; }
+ Degree10 GetOrientation() const { return mnOrientation; }
+
+ void SetWidth(tools::Long nWidth) { mnWidth=nWidth; }
+ void SetOrientation(Degree10 nOrientation) { mnOrientation=nOrientation; }
+
+ // font metrics measured for the font instance
+ tools::Long GetAscent() const { return mnAscent; }
+ tools::Long GetDescent() const { return mnDescent; }
+ tools::Long GetInternalLeading() const { return mnIntLeading; }
+ tools::Long GetExternalLeading() const { return mnExtLeading; }
+ int GetSlant() const { return mnSlant; }
+ double GetMinKashida() const { return mnMinKashida; }
+ tools::Long GetHangingBaseline() const { return mnHangingBaseline; }
+
+ void SetSlant(int nSlant) { mnSlant=nSlant; }
+ void SetMinKashida(double nMinKashida ) { mnMinKashida=nMinKashida; }
+
+ // font attributes queried from the font instance
+ bool IsFullstopCentered() const { return mbFullstopCentered; }
+ tools::Long GetBulletOffset() const { return mnBulletOffset; }
+
+ void SetFullstopCenteredFlag(bool bFullstopCentered) { mbFullstopCentered = bFullstopCentered; }
+
+ // font metrics that are usually derived from the measurements
+ tools::Long GetUnderlineSize() const { return mnUnderlineSize; }
+ tools::Long GetUnderlineOffset() const { return mnUnderlineOffset; }
+ tools::Long GetBoldUnderlineSize() const { return mnBUnderlineSize; }
+ tools::Long GetBoldUnderlineOffset() const { return mnBUnderlineOffset; }
+ tools::Long GetDoubleUnderlineSize() const { return mnDUnderlineSize; }
+ tools::Long GetDoubleUnderlineOffset1() const { return mnDUnderlineOffset1; }
+ tools::Long GetDoubleUnderlineOffset2() const { return mnDUnderlineOffset2; }
+ tools::Long GetWavelineUnderlineSize() const { return mnWUnderlineSize; }
+ tools::Long GetWavelineUnderlineOffset() const { return mnWUnderlineOffset; }
+ tools::Long GetAboveUnderlineSize() const { return mnAboveUnderlineSize; }
+ tools::Long GetAboveUnderlineOffset() const { return mnAboveUnderlineOffset; }
+ tools::Long GetAboveBoldUnderlineSize() const { return mnAboveBUnderlineSize; }
+ tools::Long GetAboveBoldUnderlineOffset() const { return mnAboveBUnderlineOffset; }
+ tools::Long GetAboveDoubleUnderlineSize() const { return mnAboveDUnderlineSize; }
+ tools::Long GetAboveDoubleUnderlineOffset1() const { return mnAboveDUnderlineOffset1; }
+ tools::Long GetAboveDoubleUnderlineOffset2() const { return mnAboveDUnderlineOffset2; }
+ tools::Long GetAboveWavelineUnderlineSize() const { return mnAboveWUnderlineSize; }
+ tools::Long GetAboveWavelineUnderlineOffset() const { return mnAboveWUnderlineOffset; }
+ tools::Long GetStrikeoutSize() const { return mnStrikeoutSize; }
+ tools::Long GetStrikeoutOffset() const { return mnStrikeoutOffset; }
+ tools::Long GetBoldStrikeoutSize() const { return mnBStrikeoutSize; }
+ tools::Long GetBoldStrikeoutOffset() const { return mnBStrikeoutOffset; }
+ tools::Long GetDoubleStrikeoutSize() const { return mnDStrikeoutSize; }
+ tools::Long GetDoubleStrikeoutOffset1() const { return mnDStrikeoutOffset1; }
+ tools::Long GetDoubleStrikeoutOffset2() const { return mnDStrikeoutOffset2; }
+
+ void ImplInitTextLineSize( const OutputDevice* pDev );
+ void ImplInitAboveTextLineSize( const OutputDevice* pDev );
+ void ImplInitFlags( const OutputDevice* pDev );
+ void ImplCalcLineSpacing(LogicalFontInstance *pFontInstance);
+ void ImplInitBaselines(LogicalFontInstance *pFontInstance);
+
+private:
+ bool ShouldNotUseUnderlineMetrics() const;
+ bool ImplInitTextLineSizeHarfBuzz(LogicalFontInstance *pFontInstance);
+ bool ShouldUseWinMetrics(int, int, int, int, int, int) const;
+
+ // font instance attributes from the font request
+ tools::Long mnHeight; // Font size
+ tools::Long mnWidth; // Reference Width
+ Degree10 mnOrientation; // Rotation in 1/10 degrees
+
+ // font metrics measured for the font instance
+ tools::Long mnAscent; // Ascent
+ tools::Long mnDescent; // Descent
+ tools::Long mnIntLeading; // Internal Leading
+ tools::Long mnExtLeading; // External Leading
+ int mnSlant; // Slant (Italic/Oblique)
+ double mnMinKashida; // Minimal width of kashida (Arabic)
+ tools::Long mnHangingBaseline; // Offset of hanging baseline to Romn baseline
+
+ // font attributes queried from the font instance
+ bool mbFullstopCentered;
+ tools::Long mnBulletOffset; // Offset to position non-print character
+
+ // font metrics that are usually derived from the measurements
+ tools::Long mnUnderlineSize; // Lineheight of Underline
+ tools::Long mnUnderlineOffset; // Offset from Underline to Baseline
+ tools::Long mnBUnderlineSize; // Height of bold underline
+ tools::Long mnBUnderlineOffset; // Offset from bold underline to baseline
+ tools::Long mnDUnderlineSize; // Height of double underline
+ tools::Long mnDUnderlineOffset1; // Offset from double underline to baseline
+ tools::Long mnDUnderlineOffset2; // Offset from double underline to baseline
+ tools::Long mnWUnderlineSize; // Height of WaveLine underline
+ tools::Long mnWUnderlineOffset; // Offset from WaveLine underline to baseline, but centrered to WaveLine
+ tools::Long mnAboveUnderlineSize; // Height of single underline (for Vertical Right)
+ tools::Long mnAboveUnderlineOffset; // Offset from single underline to baseline (for Vertical Right)
+ tools::Long mnAboveBUnderlineSize; // Height of bold underline (for Vertical Right)
+ tools::Long mnAboveBUnderlineOffset; // Offset from bold underline to baseline (for Vertical Right)
+ tools::Long mnAboveDUnderlineSize; // Height of double underline (for Vertical Right)
+ tools::Long mnAboveDUnderlineOffset1; // Offset from double underline to baseline (for Vertical Right)
+ tools::Long mnAboveDUnderlineOffset2; // Offset from double underline to baseline (for Vertical Right)
+ tools::Long mnAboveWUnderlineSize; // Height of WaveLine-strike-out (for Vertical Right)
+ tools::Long mnAboveWUnderlineOffset; // Offset from WaveLine-strike-out to baseline, but centrered to the WaveLine (for Vertical Right)
+ tools::Long mnStrikeoutSize; // Height of single strike-out
+ tools::Long mnStrikeoutOffset; // Offset from single strike-out to baseline
+ tools::Long mnBStrikeoutSize; // Height of bold strike-out
+ tools::Long mnBStrikeoutOffset; // Offset of bold strike-out to baseline
+ tools::Long mnDStrikeoutSize; // Height of double strike-out
+ tools::Long mnDStrikeoutOffset1; // Offset of double strike-out to baseline
+ tools::Long mnDStrikeoutOffset2; // Offset of double strike-out to baseline
+
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/font/FontSelectPattern.hxx b/vcl/inc/font/FontSelectPattern.hxx
new file mode 100644
index 0000000000..8e21cf5696
--- /dev/null
+++ b/vcl/inc/font/FontSelectPattern.hxx
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <i18nlangtag/lang.h>
+#include <tools/degree.hxx>
+
+#include <vcl/vclenum.hxx>
+
+#include <fontattributes.hxx>
+
+#include <ostream>
+
+namespace vcl { class Font; }
+
+class LogicalFontInstance;
+class Size;
+
+namespace vcl::font
+{
+class PhysicalFontFace;
+
+class VCL_DLLPUBLIC FontSelectPattern : public FontAttributes
+{
+public:
+ FontSelectPattern(const vcl::Font&, OUString aSearchName,
+ const Size&, float fExactHeight, bool bNonAntialias = false);
+#ifdef _WIN32
+ FontSelectPattern( const PhysicalFontFace&, const Size&,
+ float fExactHeight, int nOrientation, bool bVertical );
+#endif
+
+ size_t hashCode() const;
+ bool operator==(const FontSelectPattern& rOther) const;
+ bool operator!=(const FontSelectPattern& rOther) const
+ {
+ return !(*this == rOther);
+ }
+
+ static const char FEAT_PREFIX;
+ static const char FEAT_SEPARATOR;
+
+public:
+ OUString maTargetName; // name of the font name token that is chosen
+ OUString maSearchName; // name of the font that matches best
+ int mnWidth; // width of font in pixel units
+ int mnHeight; // height of font in pixel units
+ float mfExactHeight; // requested height (in pixels with subpixel details)
+ Degree10 mnOrientation; // text orientation in 1/10 degree (0-3600)
+ LanguageType meLanguage; // text language
+ bool mbVertical; // vertical mode of requested font
+ bool mbNonAntialiased; // true if antialiasing is disabled
+
+ bool mbEmbolden; // Force emboldening
+ ItalicMatrix maItalicMatrix; // Force matrix for slant
+};
+}
+
+template< typename charT, typename traits >
+inline std::basic_ostream<charT, traits> & operator <<(
+ std::basic_ostream<charT, traits> & stream, const vcl::font::FontSelectPattern & rFSP)
+{
+ stream << (rFSP.maTargetName.isEmpty() ? "<default>" : rFSP.maTargetName)
+ << " (" << rFSP.maSearchName << ") w: " << rFSP.mnWidth << " h: "
+ << rFSP.mnHeight << " alias: " << rFSP.mbNonAntialiased;
+ return stream;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/font/LogicalFontInstance.hxx b/vcl/inc/font/LogicalFontInstance.hxx
new file mode 100644
index 0000000000..40d3c57c4e
--- /dev/null
+++ b/vcl/inc/font/LogicalFontInstance.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <o3tl/hash_combine.hxx>
+#include <rtl/ref.hxx>
+#include <salhelper/simplereferenceobject.hxx>
+#include <tools/gen.hxx>
+#include <tools/fontenum.hxx>
+#include <tools/degree.hxx>
+
+#include <font/FontSelectPattern.hxx>
+#include <font/FontMetricData.hxx>
+#include <glyphid.hxx>
+
+#include <optional>
+#include <unordered_map>
+
+#include <hb.h>
+
+class ConvertChar;
+class ImplFontCache;
+
+constexpr float ARTIFICIAL_ITALIC_MATRIX_XX = 1 << 16;
+constexpr float ARTIFICIAL_ITALIC_MATRIX_XY = (1 << 16) / 3.f;
+constexpr float ARTIFICIAL_ITALIC_SKEW = ARTIFICIAL_ITALIC_MATRIX_XY / ARTIFICIAL_ITALIC_MATRIX_XX;
+
+// extend std namespace to add custom hash needed for LogicalFontInstance
+
+namespace std
+{
+template <> struct hash<pair<sal_UCS4, FontWeight>>
+{
+ size_t operator()(const pair<sal_UCS4, FontWeight>& rData) const
+ {
+ std::size_t seed = 0;
+ o3tl::hash_combine(seed, rData.first);
+ o3tl::hash_combine(seed, rData.second);
+ return seed;
+ }
+};
+}
+
+// TODO: allow sharing of metrics for related fonts
+
+class VCL_PLUGIN_PUBLIC LogicalFontInstance : public salhelper::SimpleReferenceObject
+{
+ // just declaring the factory function doesn't work AKA
+ // friend LogicalFontInstance* PhysicalFontFace::CreateFontInstance(const FontSelectPattern&) const;
+ friend class vcl::font::PhysicalFontFace;
+ friend class ImplFontCache;
+
+public: // TODO: make data members private
+ virtual ~LogicalFontInstance() override;
+
+ FontMetricDataRef mxFontMetric; // Font attributes
+ const ConvertChar* mpConversion; // used e.g. for StarBats->StarSymbol
+
+ tools::Long mnLineHeight;
+ Degree10 mnOwnOrientation; // text angle if lower layers don't rotate text themselves
+ Degree10 mnOrientation; // text angle in 3600 system
+ bool mbInit; // true if maFontMetric member is valid
+
+ void AddFallbackForUnicode(sal_UCS4 cChar, FontWeight eWeight, const OUString& rFontName,
+ bool bEmbolden, const ItalicMatrix& rMatrix);
+ bool GetFallbackForUnicode(sal_UCS4 cInChar, FontWeight eInWeight, OUString* pOutFontName,
+ bool* pOutEmbolden, ItalicMatrix* pOutItalicMatrix) const;
+ void IgnoreFallbackForUnicode(sal_UCS4, FontWeight eWeight, std::u16string_view rFontName);
+
+ inline hb_font_t* GetHbFont();
+ bool IsGraphiteFont();
+ // NeedOffsetCorrection: Return if the font need offset correction in TTB direction.
+ // nYOffset is the original offset. It is used to check if the correction is necessary.
+ bool NeedOffsetCorrection(sal_Int32 nYOffset);
+ void SetAverageWidthFactor(double nFactor) { m_nAveWidthFactor = std::abs(nFactor); }
+ double GetAverageWidthFactor() const { return m_nAveWidthFactor; }
+ const vcl::font::FontSelectPattern& GetFontSelectPattern() const { return m_aFontSelData; }
+
+ const vcl::font::PhysicalFontFace* GetFontFace() const { return m_pFontFace.get(); }
+ vcl::font::PhysicalFontFace* GetFontFace() { return m_pFontFace.get(); }
+ const ImplFontCache* GetFontCache() const { return mpFontCache; }
+
+ bool GetGlyphBoundRect(sal_GlyphId, tools::Rectangle&, bool) const;
+ virtual bool GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const = 0;
+ basegfx::B2DPolyPolygon GetGlyphOutlineUntransformed(sal_GlyphId) const;
+
+ sal_GlyphId GetGlyphIndex(uint32_t, uint32_t = 0) const;
+
+ double GetGlyphWidth(sal_GlyphId, bool = false, bool = true) const;
+
+ double GetKashidaWidth() const;
+
+ void GetScale(double* nXScale, double* nYScale) const;
+
+ bool NeedsArtificialItalic() const;
+ bool NeedsArtificialBold() const;
+
+protected:
+ explicit LogicalFontInstance(const vcl::font::PhysicalFontFace&,
+ const vcl::font::FontSelectPattern&);
+
+ hb_font_t* InitHbFont();
+ virtual void ImplInitHbFont(hb_font_t*) {}
+
+private:
+ hb_font_t* GetHbFontUntransformed() const;
+
+ struct MapEntry
+ {
+ OUString sFontName;
+ bool bEmbolden;
+ ItalicMatrix aItalicMatrix;
+ };
+ // cache of Unicode characters and replacement font names and attributes
+ // TODO: a fallback map can be shared with many other ImplFontEntries
+ // TODO: at least the ones which just differ in orientation, stretching or height
+ typedef ::std::unordered_map<::std::pair<sal_UCS4, FontWeight>, MapEntry> UnicodeFallbackList;
+ UnicodeFallbackList maUnicodeFallbackList;
+ mutable ImplFontCache* mpFontCache;
+ const vcl::font::FontSelectPattern m_aFontSelData;
+ hb_font_t* m_pHbFont;
+ mutable hb_font_t* m_pHbFontUntransformed = nullptr;
+ double m_nAveWidthFactor;
+ rtl::Reference<vcl::font::PhysicalFontFace> m_pFontFace;
+ std::optional<bool> m_xbIsGraphiteFont;
+
+ enum class FontFamilyEnum
+ {
+ Unclassified,
+ DFKaiSB
+ };
+
+ // The value is initialized and used in NeedOffsetCorrection().
+ std::optional<FontFamilyEnum> m_xeFontFamilyEnum;
+
+ mutable hb_draw_funcs_t* m_pHbDrawFuncs = nullptr;
+ basegfx::B2DPolygon m_aDrawPolygon;
+};
+
+inline hb_font_t* LogicalFontInstance::GetHbFont()
+{
+ if (!m_pHbFont)
+ m_pHbFont = InitHbFont();
+ return m_pHbFont;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/font/OpenTypeFeatureDefinitionList.hxx b/vcl/inc/font/OpenTypeFeatureDefinitionList.hxx
new file mode 100644
index 0000000000..1ae634deab
--- /dev/null
+++ b/vcl/inc/font/OpenTypeFeatureDefinitionList.hxx
@@ -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/.
+ */
+
+#pragma once
+
+#include <vcl/dllapi.h>
+#include <vcl/font/Feature.hxx>
+#include <vector>
+#include <unordered_map>
+
+namespace vcl::font
+{
+class OpenTypeFeatureDefinitionListPrivate
+{
+private:
+ std::vector<FeatureDefinition> m_aFeatureDefinition;
+ std::unordered_map<sal_uInt32, size_t> m_aCodeToIndex;
+ std::vector<sal_uInt32> m_aRequiredFeatures;
+
+ void init();
+
+public:
+ OpenTypeFeatureDefinitionListPrivate();
+ FeatureDefinition getDefinition(vcl::font::Feature& rFeature);
+ bool isRequired(sal_uInt32 nFeatureCode);
+};
+
+VCL_DLLPUBLIC OpenTypeFeatureDefinitionListPrivate& OpenTypeFeatureDefinitionList();
+
+} // namespace vcl::font
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/font/OpenTypeFeatureStrings.hrc b/vcl/inc/font/OpenTypeFeatureStrings.hrc
new file mode 100644
index 0000000000..086d7d500c
--- /dev/null
+++ b/vcl/inc/font/OpenTypeFeatureStrings.hrc
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_FONT_OPENTYPEFEATRESTRINGS_HRC
+#define INCLUDED_VCL_INC_FONT_OPENTYPEFEATRESTRINGS_HRC
+
+#define NC_(Context, String) TranslateId(Context, u8##String)
+
+#define STR_FONT_FEATURE_ID_AALT NC_("STR_FONT_FEATURE_ID_AALT", "Access All Alternates")
+#define STR_FONT_FEATURE_ID_AFRC NC_("STR_FONT_FEATURE_ID_AFRC", "Alternative (Vertical) Fractions")
+#define STR_FONT_FEATURE_ID_ALIG NC_("STR_FONT_FEATURE_ID_ALIG", "Ancient Ligatures")
+#define STR_FONT_FEATURE_ID_C2PC NC_("STR_FONT_FEATURE_ID_C2PC", "Capitals to Petite Capitals")
+#define STR_FONT_FEATURE_ID_C2SC NC_("STR_FONT_FEATURE_ID_C2SC", "Capitals to Small Capitals")
+#define STR_FONT_FEATURE_ID_CALT NC_("STR_FONT_FEATURE_ID_CALT", "Contextual Alternates")
+#define STR_FONT_FEATURE_ID_CASE NC_("STR_FONT_FEATURE_ID_CASE", "Case-Sensitive Forms")
+#define STR_FONT_FEATURE_ID_CLIG NC_("STR_FONT_FEATURE_ID_CLIG", "Contextual Ligatures")
+#define STR_FONT_FEATURE_ID_CPCT NC_("STR_FONT_FEATURE_ID_CPCT", "Centered CJK Punctuation")
+#define STR_FONT_FEATURE_ID_CPSP NC_("STR_FONT_FEATURE_ID_CPSP", "Capital Spacing")
+#define STR_FONT_FEATURE_ID_CSWH NC_("STR_FONT_FEATURE_ID_CSWH", "Contextual Swash")
+#define STR_FONT_FEATURE_ID_CVXX NC_("STR_FONT_FEATURE_ID_CVXX", "Character Variant %1")
+#define STR_FONT_FEATURE_ID_DCAP NC_("STR_FONT_FEATURE_ID_DCAP", "Drop Caps")
+#define STR_FONT_FEATURE_ID_DLIG NC_("STR_FONT_FEATURE_ID_DLIG", "Discretionary Ligatures")
+#define STR_FONT_FEATURE_ID_DNOM NC_("STR_FONT_FEATURE_ID_DNOM", "Denominators")
+#define STR_FONT_FEATURE_ID_DPNG NC_("STR_FONT_FEATURE_ID_DPNG", "Diphthongs (Obsolete)")
+#define STR_FONT_FEATURE_ID_EXPT NC_("STR_FONT_FEATURE_ID_EXPT", "Expert Forms")
+#define STR_FONT_FEATURE_ID_FALT NC_("STR_FONT_FEATURE_ID_FALT", "Final Glyph on Line Alternates")
+#define STR_FONT_FEATURE_ID_FRAC NC_("STR_FONT_FEATURE_ID_FRAC", "Fraction style")
+#define STR_FONT_FEATURE_ID_FWID NC_("STR_FONT_FEATURE_ID_FWID", "Full Widths")
+#define STR_FONT_FEATURE_ID_HALT NC_("STR_FONT_FEATURE_ID_HALT", "Alternate Half Widths")
+#define STR_FONT_FEATURE_ID_HIST NC_("STR_FONT_FEATURE_ID_HIST", "Historical Forms")
+#define STR_FONT_FEATURE_ID_HKNA NC_("STR_FONT_FEATURE_ID_HKNA", "Horizontal Kana Alternates")
+#define STR_FONT_FEATURE_ID_HLIG NC_("STR_FONT_FEATURE_ID_HLIG", "Historical Ligatures")
+#define STR_FONT_FEATURE_ID_HNGL NC_("STR_FONT_FEATURE_ID_HNGL", "Hanja to Hangul (Obsolete)")
+#define STR_FONT_FEATURE_ID_HOJO NC_("STR_FONT_FEATURE_ID_HOJO", "Hojo Kanji Forms (JIS X 0212-1990 Kanji Forms)")
+#define STR_FONT_FEATURE_ID_HWID NC_("STR_FONT_FEATURE_ID_HWID", "Half Widths")
+#define STR_FONT_FEATURE_ID_ITAL NC_("STR_FONT_FEATURE_ID_ITAL", "Italics")
+#define STR_FONT_FEATURE_ID_JALT NC_("STR_FONT_FEATURE_ID_JALT", "Justification Alternates")
+#define STR_FONT_FEATURE_ID_JP04 NC_("STR_FONT_FEATURE_ID_JP04", "JIS2004 Forms")
+#define STR_FONT_FEATURE_ID_JP78 NC_("STR_FONT_FEATURE_ID_JP78", "JIS78 Forms")
+#define STR_FONT_FEATURE_ID_JP83 NC_("STR_FONT_FEATURE_ID_JP83", "JIS83 Forms")
+#define STR_FONT_FEATURE_ID_JP90 NC_("STR_FONT_FEATURE_ID_JP90", "JIS90 Forms")
+#define STR_FONT_FEATURE_ID_KERN NC_("STR_FONT_FEATURE_ID_KERN", "Horizontal Kerning")
+#define STR_FONT_FEATURE_ID_LFBD NC_("STR_FONT_FEATURE_ID_LFBD", "Left Bounds")
+#define STR_FONT_FEATURE_ID_LIGA NC_("STR_FONT_FEATURE_ID_LIGA", "Standard Ligatures")
+#define STR_FONT_FEATURE_ID_LNUM NC_("STR_FONT_FEATURE_ID_LNUM", "Lining Figures")
+#define STR_FONT_FEATURE_ID_MGRK NC_("STR_FONT_FEATURE_ID_MGRK", "Mathematical Greek")
+#define STR_FONT_FEATURE_ID_NALT NC_("STR_FONT_FEATURE_ID_NALT", "Alternate Annotation Forms")
+#define STR_FONT_FEATURE_ID_NLCK NC_("STR_FONT_FEATURE_ID_NLCK", "NLC Kanji Forms")
+#define STR_FONT_FEATURE_ID_NUMR NC_("STR_FONT_FEATURE_ID_NUMR", "Numerators")
+#define STR_FONT_FEATURE_ID_ONUM NC_("STR_FONT_FEATURE_ID_ONUM", "Oldstyle Figures")
+#define STR_FONT_FEATURE_ID_OPBD NC_("STR_FONT_FEATURE_ID_OPBD", "Optical Bounds")
+#define STR_FONT_FEATURE_ID_ORDN NC_("STR_FONT_FEATURE_ID_ORDN", "Ordinals")
+#define STR_FONT_FEATURE_ID_ORNM NC_("STR_FONT_FEATURE_ID_ORNM", "Ornaments")
+#define STR_FONT_FEATURE_ID_PALT NC_("STR_FONT_FEATURE_ID_PALT", "Proportional Alternate Metrics")
+#define STR_FONT_FEATURE_ID_PCAP NC_("STR_FONT_FEATURE_ID_PCAP", "Lowercase to Petite Capitals")
+#define STR_FONT_FEATURE_ID_PKNA NC_("STR_FONT_FEATURE_ID_PKNA", "Proportional Kana")
+#define STR_FONT_FEATURE_ID_PNUM NC_("STR_FONT_FEATURE_ID_PNUM", "Proportional Numbers")
+#define STR_FONT_FEATURE_ID_PWID NC_("STR_FONT_FEATURE_ID_PWID", "Proportional Widths")
+#define STR_FONT_FEATURE_ID_QWID NC_("STR_FONT_FEATURE_ID_QWID", "Quarter Widths")
+#define STR_FONT_FEATURE_ID_RTBD NC_("STR_FONT_FEATURE_ID_RTBD", "Right Bounds")
+#define STR_FONT_FEATURE_ID_RUBY NC_("STR_FONT_FEATURE_ID_RUBY", "Ruby Notation Forms")
+#define STR_FONT_FEATURE_ID_SALT NC_("STR_FONT_FEATURE_ID_SALT", "Stylistic Alternates")
+#define STR_FONT_FEATURE_ID_SINF NC_("STR_FONT_FEATURE_ID_SINF", "Scientific Inferiors")
+#define STR_FONT_FEATURE_ID_SMCP NC_("STR_FONT_FEATURE_ID_SMCP", "Lowercase to Small Capitals")
+#define STR_FONT_FEATURE_ID_SMPL NC_("STR_FONT_FEATURE_ID_SMPL", "Simplified Forms")
+#define STR_FONT_FEATURE_ID_SSXX NC_("STR_FONT_FEATURE_ID_SSXX", "Stylistic Set %1")
+#define STR_FONT_FEATURE_ID_SUBS NC_("STR_FONT_FEATURE_ID_SUBS", "Subscript")
+#define STR_FONT_FEATURE_ID_SUPS NC_("STR_FONT_FEATURE_ID_SUPS", "Superscript")
+#define STR_FONT_FEATURE_ID_SWSH NC_("STR_FONT_FEATURE_ID_SWSH", "Swash")
+#define STR_FONT_FEATURE_ID_TITL NC_("STR_FONT_FEATURE_ID_TITL", "Titling")
+#define STR_FONT_FEATURE_ID_TNAM NC_("STR_FONT_FEATURE_ID_TNAM", "Traditional Name Forms")
+#define STR_FONT_FEATURE_ID_TNUM NC_("STR_FONT_FEATURE_ID_TNUM", "Tabular Numbers")
+#define STR_FONT_FEATURE_ID_TRAD NC_("STR_FONT_FEATURE_ID_TRAD", "Traditional Forms")
+#define STR_FONT_FEATURE_ID_TWID NC_("STR_FONT_FEATURE_ID_TWID", "Third Widths")
+#define STR_FONT_FEATURE_ID_UNIC NC_("STR_FONT_FEATURE_ID_UNIC", "Unicase")
+#define STR_FONT_FEATURE_ID_VALT NC_("STR_FONT_FEATURE_ID_VALT", "Alternate Vertical Metrics")
+#define STR_FONT_FEATURE_ID_VHAL NC_("STR_FONT_FEATURE_ID_VHAL", "Alternate Vertical Half Metrics")
+#define STR_FONT_FEATURE_ID_VKNA NC_("STR_FONT_FEATURE_ID_VKNA", "Vertical Kana Alternates")
+#define STR_FONT_FEATURE_ID_VKRN NC_("STR_FONT_FEATURE_ID_VKRN", "Vertical Kerning")
+#define STR_FONT_FEATURE_ID_VPAL NC_("STR_FONT_FEATURE_ID_VPAL", "Proportional Alternate Vertical Metrics")
+#define STR_FONT_FEATURE_ID_VRT2 NC_("STR_FONT_FEATURE_ID_VRT2", "Vertical Alternates and Rotation")
+#define STR_FONT_FEATURE_ID_VRTR NC_("STR_FONT_FEATURE_ID_VRTR", "Vertical Alternates for Rotation")
+#define STR_FONT_FEATURE_ID_ZERO NC_("STR_FONT_FEATURE_ID_ZERO", "Slashed Zero")
+#define STR_FONT_FEATURE_PARAM_NONE NC_("STR_FONT_FEATURE_PARAM_NONE", "None")
+
+#endif // INCLUDED_VCL_INC_STRINGS_HRC
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/font/PhysicalFontCollection.hxx b/vcl/inc/font/PhysicalFontCollection.hxx
new file mode 100644
index 0000000000..d791f7b692
--- /dev/null
+++ b/vcl/inc/font/PhysicalFontCollection.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <vcl/dllapi.h>
+
+#include <font/LogicalFontInstance.hxx>
+
+#include "PhysicalFontFamily.hxx"
+
+#include <array>
+#include <string_view>
+
+#define MAX_GLYPHFALLBACK 16
+
+namespace vcl::font
+{
+class GlyphFallbackFontSubstitution;
+class PreMatchFontSubstitution;
+}
+
+// TODO: merge with ImplFontCache
+// TODO: rename to LogicalFontManager
+
+namespace vcl::font
+{
+
+class VCL_PLUGIN_PUBLIC PhysicalFontCollection final
+{
+public:
+ explicit PhysicalFontCollection();
+ ~PhysicalFontCollection();
+
+ // fill the list with device font faces
+ void Add( vcl::font::PhysicalFontFace* );
+ void Clear();
+ int Count() const { return maPhysicalFontFamilies.size(); }
+
+ // find the device font family
+ vcl::font::PhysicalFontFamily* FindFontFamily( std::u16string_view rFontName ) const;
+ vcl::font::PhysicalFontFamily* FindOrCreateFontFamily( const OUString &rFamilyName );
+ vcl::font::PhysicalFontFamily* FindFontFamily( vcl::font::FontSelectPattern& ) const;
+ vcl::font::PhysicalFontFamily* FindFontFamilyByTokenNames(std::u16string_view rTokenStr) const;
+ vcl::font::PhysicalFontFamily* FindFontFamilyByAttributes(ImplFontAttrs nSearchType, FontWeight, FontWidth,
+ FontItalic, std::u16string_view rSearchFamily) const;
+
+ // suggest fonts for glyph fallback
+ vcl::font::PhysicalFontFamily* GetGlyphFallbackFont( vcl::font::FontSelectPattern&,
+ LogicalFontInstance* pLogicalFont,
+ OUString& rMissingCodes, int nFallbackLevel ) const;
+
+ // prepare platform specific font substitutions
+ void SetPreMatchHook( vcl::font::PreMatchFontSubstitution* );
+ void SetFallbackHook( vcl::font::GlyphFallbackFontSubstitution* );
+
+ // misc utilities
+ std::shared_ptr<PhysicalFontCollection> Clone() const;
+ std::unique_ptr<vcl::font::PhysicalFontFaceCollection> GetFontFaceCollection() const;
+
+private:
+ mutable bool mbMatchData; // true if matching attributes are initialized
+
+ typedef std::unordered_map<OUString, std::unique_ptr<vcl::font::PhysicalFontFamily>> PhysicalFontFamilies;
+ PhysicalFontFamilies maPhysicalFontFamilies;
+
+ vcl::font::PreMatchFontSubstitution* mpPreMatchHook; // device specific prematch substitution
+ vcl::font::GlyphFallbackFontSubstitution* mpFallbackHook; // device specific glyph fallback substitution
+
+ mutable std::unique_ptr<std::array<vcl::font::PhysicalFontFamily*,MAX_GLYPHFALLBACK>> mpFallbackList;
+ mutable int mnFallbackCount;
+
+ void ImplInitMatchData() const;
+ void ImplInitGenericGlyphFallback() const;
+
+ vcl::font::PhysicalFontFamily* ImplFindFontFamilyBySearchName( const OUString& ) const;
+ vcl::font::PhysicalFontFamily* ImplFindFontFamilyBySubstFontAttr( const utl::FontNameAttr& ) const;
+
+ vcl::font::PhysicalFontFamily* ImplFindFontFamilyOfDefaultFont() const;
+
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/font/PhysicalFontFace.hxx b/vcl/inc/font/PhysicalFontFace.hxx
new file mode 100644
index 0000000000..1fce6a6cba
--- /dev/null
+++ b/vcl/inc/font/PhysicalFontFace.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <rtl/ref.hxx>
+#include <salhelper/simplereferenceobject.hxx>
+#include <tools/color.hxx>
+#include <vcl/dllapi.h>
+#include <vcl/fontcapabilities.hxx>
+#include <vcl/fontcharmap.hxx>
+
+#include <fontattributes.hxx>
+#include <fontsubset.hxx>
+
+#include <hb.h>
+#include <hb-ot.h>
+
+class LogicalFontInstance;
+struct FontMatchStatus;
+namespace vcl::font
+{
+class FontSelectPattern;
+}
+
+namespace vcl
+{
+class PhysicalFontFamily;
+}
+
+namespace vcl::font
+{
+class FontSelectPattern;
+
+struct FontMatchStatus
+{
+public:
+ int mnFaceMatch;
+ const OUString* mpTargetStyleName;
+};
+
+struct RawFontData
+{
+public:
+ RawFontData(hb_blob_t* pBlob = nullptr)
+ : mpBlob(pBlob ? pBlob : hb_blob_get_empty())
+ {
+ }
+
+ RawFontData(const RawFontData& rOther)
+ : mpBlob(hb_blob_reference(rOther.mpBlob))
+ {
+ }
+
+ ~RawFontData() { hb_blob_destroy(mpBlob); }
+
+ RawFontData& operator=(const RawFontData& rOther)
+ {
+ hb_blob_destroy(mpBlob);
+ mpBlob = hb_blob_reference(rOther.mpBlob);
+ return *this;
+ }
+
+ size_t size() const { return hb_blob_get_length(mpBlob); }
+ bool empty() const { return size() == 0; }
+ const uint8_t* data() const
+ {
+ return reinterpret_cast<const uint8_t*>(hb_blob_get_data(mpBlob, nullptr));
+ }
+
+private:
+ hb_blob_t* mpBlob;
+};
+
+struct ColorLayer
+{
+ sal_GlyphId nGlyphIndex;
+ uint32_t nColorIndex;
+};
+
+typedef std::vector<Color> ColorPalette;
+
+// https://learn.microsoft.com/en-us/typography/opentype/spec/name#name-ids
+typedef enum : hb_ot_name_id_t {
+ NAME_ID_COPYRIGHT = 0,
+ NAME_ID_FONT_FAMILY = 1,
+ NAME_ID_FONT_SUBFAMILY = 2,
+ NAME_ID_UNIQUE_ID = 3,
+ NAME_ID_FULL_NAME = 4,
+ NAME_ID_VERSION_STRING = 5,
+ NAME_ID_POSTSCRIPT_NAME = 6,
+ NAME_ID_TRADEMARK = 7,
+ NAME_ID_MANUFACTURER = 8,
+ NAME_ID_DESIGNER = 9,
+ NAME_ID_DESCRIPTION = 10,
+ NAME_ID_VENDOR_URL = 11,
+ NAME_ID_DESIGNER_URL = 12,
+ NAME_ID_LICENSE = 13,
+ NAME_ID_LICENSE_URL = 14,
+ //NAME_ID_RESERVED = 15,
+ NAME_ID_TYPOGRAPHIC_FAMILY = 16,
+ NAME_ID_TYPOGRAPHIC_SUBFAMILY = 17,
+ NAME_ID_MAC_FULL_NAME = 18,
+ NAME_ID_SAMPLE_TEXT = 19,
+ NAME_ID_CID_FINDFONT_NAME = 20,
+ NAME_ID_WWS_FAMILY = 21,
+ NAME_ID_WWS_SUBFAMILY = 22,
+ NAME_ID_LIGHT_BACKGROUND = 23,
+ NAME_ID_DARK_BACKGROUND = 24,
+ NAME_ID_VARIATIONS_PS_PREFIX = 25,
+} NameID;
+
+// TODO: no more direct access to members
+// TODO: get rid of height/width for scalable fonts
+// TODO: make cloning cheaper
+
+/**
+ * abstract base class for physical font faces
+ *
+ * It acts as a factory for its corresponding LogicalFontInstances and
+ * can be extended to cache device and font instance specific data.
+ */
+class VCL_PLUGIN_PUBLIC PhysicalFontFace : public FontAttributes,
+ public salhelper::SimpleReferenceObject
+{
+public:
+ ~PhysicalFontFace();
+
+ virtual rtl::Reference<LogicalFontInstance>
+ CreateFontInstance(const vcl::font::FontSelectPattern&) const = 0;
+
+ virtual sal_IntPtr GetFontId() const = 0;
+ virtual FontCharMapRef GetFontCharMap() const;
+ virtual bool GetFontCapabilities(vcl::FontCapabilities&) const;
+
+ RawFontData GetRawFontData(uint32_t) const;
+
+ bool IsBetterMatch(const vcl::font::FontSelectPattern&, FontMatchStatus&) const;
+ sal_Int32 CompareIgnoreSize(const PhysicalFontFace&) const;
+
+ // CreateFontSubset: a method to get a subset of glyphs of a font inside a
+ // new valid font file
+ // returns true if creation of subset was successful
+ // parameters: rOutBuffer: vector to write the subset to
+ // pGlyphIDs: the glyph ids to be extracted
+ // pEncoding: the character code corresponding to each glyph
+ // nGlyphs: the number of glyphs
+ // rInfo: additional outgoing information
+ // implementation note: encoding 0 with glyph id 0 should be added implicitly
+ // as "undefined character"
+ bool CreateFontSubset(std::vector<sal_uInt8>&, const sal_GlyphId*, const sal_uInt8*, const int,
+ FontSubsetInfo&) const;
+
+ bool IsColorFont() const { return HasColorLayers() || HasColorBitmaps(); }
+
+ bool HasColorLayers() const;
+ std::vector<ColorLayer> GetGlyphColorLayers(sal_GlyphId) const;
+
+ const std::vector<ColorPalette>& GetColorPalettes() const;
+
+ bool HasColorBitmaps() const;
+ RawFontData GetGlyphColorBitmap(sal_GlyphId, tools::Rectangle&) const;
+
+ OString GetGlyphName(sal_GlyphId, bool = false) const;
+
+ uint32_t UnitsPerEm() const { return hb_face_get_upem(GetHbFace()); }
+
+ OUString GetName(NameID, const LanguageTag&) const;
+ OUString GetName(NameID aNameID) const { return GetName(aNameID, LanguageTag(LANGUAGE_NONE)); }
+
+ virtual hb_face_t* GetHbFace() const;
+ virtual hb_blob_t* GetHbTable(hb_tag_t) const
+ {
+ assert(false);
+ return nullptr;
+ }
+
+ virtual const std::vector<hb_variation_t>& GetVariations(const LogicalFontInstance&) const;
+
+protected:
+ mutable hb_face_t* mpHbFace;
+ mutable hb_font_t* mpHbUnscaledFont;
+ mutable FontCharMapRef mxCharMap;
+ mutable std::optional<vcl::FontCapabilities> mxFontCapabilities;
+ mutable std::optional<std::vector<ColorPalette>> mxColorPalettes;
+ mutable std::optional<std::vector<hb_variation_t>> mxVariations;
+
+ explicit PhysicalFontFace(const FontAttributes&);
+
+ hb_font_t* GetHbUnscaledFont() const;
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/font/PhysicalFontFaceCollection.hxx b/vcl/inc/font/PhysicalFontFaceCollection.hxx
new file mode 100644
index 0000000000..8208af44c4
--- /dev/null
+++ b/vcl/inc/font/PhysicalFontFaceCollection.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include "PhysicalFontFace.hxx"
+
+#include <vector>
+
+/**
+ A PhysicalFontFaceCollection is created by a PhysicalFontCollection and
+ becomes invalid when original PhysicalFontCollection is modified.
+ */
+
+namespace vcl::font
+{
+class PhysicalFontFaceCollection
+{
+private:
+ std::vector<rtl::Reference<PhysicalFontFace>> maDevFontVector;
+
+public:
+ PhysicalFontFaceCollection() { maDevFontVector.reserve(1024); }
+ void Add(PhysicalFontFace* pFace) { maDevFontVector.push_back(pFace); }
+ PhysicalFontFace* Get(int nIndex) const { return maDevFontVector[nIndex].get(); }
+ int Count() const { return maDevFontVector.size(); }
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/font/PhysicalFontFamily.hxx b/vcl/inc/font/PhysicalFontFamily.hxx
new file mode 100644
index 0000000000..a944ed72fe
--- /dev/null
+++ b/vcl/inc/font/PhysicalFontFamily.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <vcl/dllapi.h>
+#include <vcl/outdev.hxx>
+
+#include <unotools/fontcfg.hxx>
+
+namespace vcl::font
+{
+// flags for mnTypeFaces member
+enum class FontTypeFaces
+{
+ NONE = 0x00,
+ Scalable = 0x01,
+ Symbol = 0x02,
+ NoneSymbol = 0x04,
+ Light = 0x08,
+ Bold = 0x10,
+ Normal = 0x20,
+ NoneItalic = 0x40,
+ Italic = 0x80
+};
+}
+namespace o3tl
+{
+template <>
+struct typed_flags<vcl::font::FontTypeFaces> : is_typed_flags<vcl::font::FontTypeFaces, 0xff>
+{
+};
+};
+
+namespace vcl::font
+{
+class FontSelectPattern;
+class PhysicalFontCollection;
+class PhysicalFontFace;
+class PhysicalFontFaceCollection;
+
+class VCL_PLUGIN_PUBLIC PhysicalFontFamily
+{
+public:
+ PhysicalFontFamily(OUString aSearchName);
+ ~PhysicalFontFamily();
+
+ // Avoid implicitly defined copy constructors/assignments for the DLLPUBLIC class (they may
+ // require forward-declared classes used internally to be defined in places using this)
+ PhysicalFontFamily(const PhysicalFontFamily&) = delete;
+ PhysicalFontFamily(PhysicalFontFamily&&) = delete;
+ PhysicalFontFamily& operator=(const PhysicalFontFamily&) = delete;
+ PhysicalFontFamily& operator=(PhysicalFontFamily&&) = delete;
+
+ const OUString& GetFamilyName() const { return maFamilyName; }
+ const OUString& GetSearchName() const { return maSearchName; }
+ int GetMinQuality() const { return mnMinQuality; }
+ FontTypeFaces GetTypeFaces() const { return mnTypeFaces; }
+
+ const OUString& GetMatchFamilyName() const { return maMatchFamilyName; }
+ ImplFontAttrs GetMatchType() const { return mnMatchType; }
+ FontWeight GetMatchWeight() const { return meMatchWeight; }
+ FontWidth GetMatchWidth() const { return meMatchWidth; }
+ void InitMatchData(const utl::FontSubstConfiguration&, const OUString& rSearchName);
+
+ void AddFontFace(PhysicalFontFace*);
+
+ PhysicalFontFace* FindBestFontFace(const vcl::font::FontSelectPattern& rFSD) const;
+
+ void UpdateDevFontList(PhysicalFontFaceCollection&) const;
+ void UpdateCloneFontList(PhysicalFontCollection&) const;
+
+ static void CalcType(ImplFontAttrs& rType, FontWeight& rWeight, FontWidth& rWidth,
+ FontFamily eFamily, const utl::FontNameAttr* pFontAttr);
+
+private:
+ std::vector<rtl::Reference<PhysicalFontFace>> maFontFaces;
+
+ OUString maFamilyName; // original font family name
+ OUString maSearchName; // normalized font family name
+ FontTypeFaces mnTypeFaces; // Typeface Flags
+ FontFamily meFamily;
+ FontPitch mePitch;
+ int mnMinQuality; // quality of the worst font face
+
+ ImplFontAttrs mnMatchType; // MATCH - Type
+ OUString maMatchFamilyName; // MATCH - FamilyName
+ FontWeight meMatchWeight; // MATCH - Weight
+ FontWidth meMatchWidth; // MATCH - Width
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/font/fontsubstitution.hxx b/vcl/inc/font/fontsubstitution.hxx
new file mode 100644
index 0000000000..f7befca4c3
--- /dev/null
+++ b/vcl/inc/font/fontsubstitution.hxx
@@ -0,0 +1,68 @@
+/* -*- 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 .
+ */
+
+// nowadays these substitutions are needed for backward compatibility and tight platform integration:
+// - substitutions from configuration entries (Tools->Options->FontReplacement and/or fontconfig)
+// - device specific substitutions (e.g. for PS printer builtin fonts)
+// - substitutions for missing fonts defined by configuration entries (generic and/or platform dependent fallbacks)
+// - substitutions for missing fonts defined by multi-token fontnames (e.g. fontname="SpecialFont;FallbackA;FallbackB")
+// - substitutions for incomplete fonts (implicit, generic, EUDC and/or platform dependent fallbacks)
+// - substitutions for missing symbol fonts by translating code points into other symbol fonts
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <rtl/ustring.hxx>
+
+#include <font/FontSelectPattern.hxx>
+
+namespace vcl::font
+{
+class FontSelectPattern;
+
+class FontSubstitution
+{
+ // TODO: there is more commonality between the different substitutions
+protected:
+ virtual ~FontSubstitution() {}
+};
+
+/// Abstracts the concept of finding the best font to support an incomplete font
+class GlyphFallbackFontSubstitution : public FontSubstitution
+{
+public:
+ virtual bool FindFontSubstitute(vcl::font::FontSelectPattern&,
+ LogicalFontInstance* pLogicalFont,
+ OUString& rMissingCodes) const = 0;
+};
+
+/** Abstracts the concept of a configured font substitution before the
+ availability of the originally selected font has been checked.
+ */
+class PreMatchFontSubstitution : public FontSubstitution
+{
+public:
+ virtual bool FindFontSubstitute(vcl::font::FontSelectPattern&) const = 0;
+};
+
+void ImplFontSubstitute(OUString& rFontName);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/fontattributes.hxx b/vcl/inc/fontattributes.hxx
new file mode 100644
index 0000000000..6d2983222b
--- /dev/null
+++ b/vcl/inc/fontattributes.hxx
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_FONTATTRIBUTES_HXX
+#define INCLUDED_VCL_INC_FONTATTRIBUTES_HXX
+
+#include <vcl/dllapi.h>
+#include <rtl/ustring.hxx>
+#include <tools/fontenum.hxx>
+
+
+/* The following class is extraordinarily similar to ImplFont. */
+
+class VCL_DLLPUBLIC FontAttributes
+{
+public:
+ explicit FontAttributes();
+
+ // device independent font functions
+ const OUString& GetFamilyName() const { return maFamilyName; }
+ FontFamily GetFamilyType() const { return meFamily; }
+ const OUString& GetStyleName() const { return maStyleName; }
+
+ FontWeight GetWeight() const { return meWeight; }
+ FontItalic GetItalic() const { return meItalic; }
+ FontPitch GetPitch() const { return mePitch; }
+ FontWidth GetWidthType() const { return meWidthType; }
+
+ bool IsMicrosoftSymbolEncoded() const { return mbMicrosoftSymbolEncoded; }
+
+ void SetFamilyName(const OUString& sFamilyName) { maFamilyName = sFamilyName; }
+ void SetStyleName( const OUString& sStyleName) { maStyleName = sStyleName; }
+ void SetFamilyType(const FontFamily eFontFamily) { meFamily = eFontFamily; }
+
+ void SetPitch(const FontPitch ePitch ) { mePitch = ePitch; }
+ void SetItalic(const FontItalic eItalic ) { meItalic = eItalic; }
+ void SetWeight(const FontWeight eWeight ) { meWeight = eWeight; }
+ void SetWidthType(const FontWidth eWidthType) { meWidthType = eWidthType; }
+
+ void SetMicrosoftSymbolEncoded(const bool );
+
+ bool CompareDeviceIndependentFontAttributes(const FontAttributes& rOther) const;
+
+ // Device dependent functions
+ int GetQuality() const { return mnQuality; }
+
+
+ void SetQuality( int nQuality ) { mnQuality = nQuality; }
+ void IncreaseQualityBy( int nQualityAmount ) { mnQuality += nQualityAmount; }
+
+private:
+ // device independent variables
+ OUString maFamilyName; // Font Family Name
+ OUString maStyleName; // Font Style Name
+ FontWeight meWeight; // Weight Type
+ FontFamily meFamily; // Family Type
+ FontPitch mePitch; // Pitch Type
+ FontWidth meWidthType; // Width Type
+ FontItalic meItalic; // Slant Type
+ bool mbMicrosoftSymbolEncoded; // Is font microsoft symbol encoded?
+
+ // device dependent variables
+ int mnQuality; // Quality (used when similar fonts compete)
+
+};
+
+inline void FontAttributes::SetMicrosoftSymbolEncoded(const bool bMicrosoftSymbolEncoded)
+{
+ mbMicrosoftSymbolEncoded = bMicrosoftSymbolEncoded;
+}
+
+#endif // INCLUDED_VCL_INC_FONTATTRIBUTES_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/fontsubset.hxx b/vcl/inc/fontsubset.hxx
new file mode 100644
index 0000000000..beec3f2fb5
--- /dev/null
+++ b/vcl/inc/fontsubset.hxx
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <tools/gen.hxx>
+#include <o3tl/typed_flags_set.hxx>
+
+#include <vcl/dllapi.h>
+
+#include "glyphid.hxx"
+
+namespace vcl { class TrueTypeFont; } ///< SFT's idea of a TTF font
+class SvStream;
+
+enum class FontType {
+ NO_FONT = 0,
+ SFNT_TTF = 1<<1, ///< SFNT container with TrueType glyphs
+ SFNT_CFF = 1<<2, ///< SFNT container with CFF-container
+ TYPE1_PFA = 1<<3, ///< PSType1 Postscript Font Ascii
+ TYPE1_PFB = 1<<4, ///< PSType1 Postscript Font Binary
+ CFF_FONT = 1<<5, ///< CFF-container with PSType2 glyphs
+ ANY_SFNT = SFNT_TTF | SFNT_CFF,
+ ANY_TYPE1 = TYPE1_PFA | TYPE1_PFB
+};
+namespace o3tl {
+ template<> struct typed_flags<FontType> : is_typed_flags<FontType, (1<<8)-1> {};
+}
+
+class VCL_DLLPUBLIC FontSubsetInfo final
+{
+public:
+ explicit FontSubsetInfo();
+ ~FontSubsetInfo();
+
+ void LoadFont( FontType eInFontType,
+ const unsigned char* pFontBytes, int nByteLength );
+
+ bool CreateFontSubset( FontType nOutFontTypeMask,
+ SvStream* pOutFile, const char* pOutFontName,
+ const sal_GlyphId* pGlyphIds, const sal_uInt8* pEncodedIds,
+ int nReqGlyphCount);
+
+public: // TODO: make subsetter results private and provide accessor methods instead
+ // subsetter-provided subset details needed by e.g. Postscript or PDF
+ OUString m_aPSName;
+ int m_nAscent; ///< all metrics in PS font units
+ int m_nDescent;
+ int m_nCapHeight;
+ tools::Rectangle m_aFontBBox;
+ FontType m_nFontType; ///< font-type of subset result
+ bool m_bFilled;
+
+private:
+ // input-font-specific details
+ unsigned const char* mpInFontBytes;
+ int mnInByteLength;
+ FontType meInFontType; ///< allowed mask of input font-types
+
+ // subset-request details
+ FontType mnReqFontTypeMask; ///< allowed subset-target font types
+ SvStream* mpOutFile;
+ const char* mpReqFontName;
+ const sal_GlyphId* mpReqGlyphIds;
+ const sal_uInt8* mpReqEncodedIds;
+ int mnReqGlyphCount;
+
+ bool CreateFontSubsetFromCff();
+};
+
+int VCL_DLLPUBLIC TestFontSubset(const void* data, sal_uInt32 size);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/glyphid.hxx b/vcl/inc/glyphid.hxx
new file mode 100644
index 0000000000..6bffe2722e
--- /dev/null
+++ b/vcl/inc/glyphid.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <cstdint>
+
+typedef uint32_t sal_GlyphId;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/graphic/DetectorTools.hxx b/vcl/inc/graphic/DetectorTools.hxx
new file mode 100644
index 0000000000..3a3bae012e
--- /dev/null
+++ b/vcl/inc/graphic/DetectorTools.hxx
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <rtl/string.hxx>
+
+#include <vector>
+
+namespace vcl
+{
+const char* matchArray(const char* pSource, sal_Int32 nSourceSize, const char* pSearch,
+ sal_Int32 nSearchSize)
+{
+ for (sal_Int32 increment = 0; increment <= (nSourceSize - nSearchSize); ++increment)
+ {
+ bool bMatch = true;
+ // search both arrays if they match
+ for (sal_Int32 index = 0; index < nSearchSize && bMatch; ++index)
+ {
+ if (pSource[index] != pSearch[index])
+ bMatch = false;
+ }
+ // match has been found
+ if (bMatch)
+ return pSource;
+ pSource++;
+ }
+ return nullptr;
+}
+
+const char* matchArrayWithString(const char* pSource, sal_Int32 nSourceSize, OString const& rString)
+{
+ return matchArray(pSource, nSourceSize, rString.getStr(), rString.getLength());
+}
+
+bool checkArrayForMatchingStrings(const char* pSource, sal_Int32 nSourceSize,
+ std::vector<OString> const& rStrings)
+{
+ if (rStrings.empty())
+ return false;
+ if (rStrings.size() < 2)
+ return matchArrayWithString(pSource, nSourceSize, rStrings[0]) != nullptr;
+
+ const char* pBegin = pSource;
+ const char* pCurrent = pSource;
+ for (OString const& rString : rStrings)
+ {
+ sal_Int32 nCurrentSize = nSourceSize - sal_Int32(pCurrent - pBegin);
+ pCurrent = matchArray(pCurrent, nCurrentSize, rString.getStr(), rString.getLength());
+ if (pCurrent == nullptr)
+ return false;
+ }
+ return true;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/graphic/GraphicFormatDetector.hxx b/vcl/inc/graphic/GraphicFormatDetector.hxx
new file mode 100644
index 0000000000..d6791e377f
--- /dev/null
+++ b/vcl/inc/graphic/GraphicFormatDetector.hxx
@@ -0,0 +1,208 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_GRAPHICFORMATDETECTOR_HXX
+#define INCLUDED_VCL_INC_GRAPHICFORMATDETECTOR_HXX
+
+#include <tools/stream.hxx>
+#include <vector>
+#include <vcl/graphic/GraphicMetadata.hxx>
+
+namespace vcl
+{
+static inline OUString 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::APNG:
+ pKeyName = "APNG";
+ 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::WMZ:
+ pKeyName = "WMZ";
+ break;
+ case GraphicFileFormat::EMZ:
+ pKeyName = "EMZ";
+ break;
+ case GraphicFileFormat::SVGZ:
+ pKeyName = "SVGZ";
+ break;
+ case GraphicFileFormat::WEBP:
+ pKeyName = "WEBP";
+ break;
+ case GraphicFileFormat::MOV:
+ pKeyName = "MOV";
+ break;
+ case GraphicFileFormat::PDF:
+ pKeyName = "PDF";
+ break;
+ default:
+ assert(false);
+ }
+
+ return OUString::createFromAscii(pKeyName);
+}
+/***
+ * This function is has two modes:
+ * - determine the file format when bTest = false
+ * returns true, success
+ * out rFormatExtension - on success: file format string
+ * - verify file format when bTest = true
+ * returns false, if file type can't be verified
+ * true, if the format is verified or the format is not known
+ */
+VCL_DLLPUBLIC bool peekGraphicFormat(SvStream& rStream, OUString& rFormatExtension, bool bTest);
+
+class VCL_DLLPUBLIC GraphicFormatDetector
+{
+public:
+ SvStream& mrStream;
+ OUString maExtension;
+
+ std::vector<sal_uInt8> maFirstBytes;
+ sal_uInt32 mnFirstLong;
+ sal_uInt32 mnSecondLong;
+
+ sal_uInt64 mnStreamPosition;
+ sal_uInt64 mnStreamLength;
+
+ GraphicFormatDetector(SvStream& rStream, OUString aFormatExtension, bool bExtendedInfo = false);
+
+ bool detect();
+
+ bool checkMET();
+ bool checkBMP();
+ bool checkWMF();
+ bool checkEMF();
+ bool checkPCX();
+ bool checkTIF();
+ bool checkGIF();
+ bool checkPNG();
+ bool checkAPNG();
+ bool checkJPG();
+ bool checkSVM();
+ bool checkPCD();
+ bool checkPSD();
+ bool checkEPS();
+ bool checkDXF();
+ bool checkPCT();
+ bool checkPBM();
+ bool checkPGM();
+ bool checkPPM();
+ bool checkRAS();
+ bool checkXPM();
+ bool checkXBM();
+ bool checkSVG();
+ bool checkTGA();
+ bool checkMOV();
+ bool checkPDF();
+ bool checkWEBP();
+ const GraphicMetadata& getMetadata();
+
+private:
+ /**
+ * @brief Checks whether mrStream needs to be uncompressed and returns a pointer to the
+ * to aUncompressedBuffer or a pointer to maFirstBytes if it doesn't need to be uncompressed
+ *
+ * @param aUncompressedBuffer the buffer to hold the uncompressed data
+ * @param nSize the amount of bytes to uncompress
+ * @param nRetSize the amount of bytes actually uncompressed
+ * @return sal_uInt8* a pointer to maFirstBytes or aUncompressed buffer
+ */
+ sal_uInt8* checkAndUncompressBuffer(sal_uInt8* aUncompressedBuffer, sal_uInt32 nSize,
+ sal_uInt64& nDecompressedSize);
+ bool mbExtendedInfo;
+ bool mbWasCompressed;
+ GraphicMetadata maMetadata;
+};
+}
+
+#endif // INCLUDED_VCL_INC_GRAPHICFORMATDETECTOR_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/graphic/GraphicID.hxx b/vcl/inc/graphic/GraphicID.hxx
new file mode 100644
index 0000000000..f2f156affe
--- /dev/null
+++ b/vcl/inc/graphic/GraphicID.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <rtl/string.hxx>
+#include <vcl/checksum.hxx>
+
+class ImpGraphic;
+
+class GraphicID
+{
+private:
+ sal_uInt32 mnID1;
+ sal_uInt32 mnID2;
+ sal_uInt32 mnID3;
+ BitmapChecksum mnID4;
+
+public:
+ GraphicID(ImpGraphic const& rGraphic);
+
+ bool operator==(const GraphicID& rID) const
+ {
+ return rID.mnID1 == mnID1 && rID.mnID2 == mnID2 && rID.mnID3 == mnID3 && rID.mnID4 == mnID4;
+ }
+
+ OString getIDString() const;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/graphic/GraphicReader.hxx b/vcl/inc/graphic/GraphicReader.hxx
new file mode 100644
index 0000000000..0faf5a7fe9
--- /dev/null
+++ b/vcl/inc/graphic/GraphicReader.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+
+class GraphicReader
+{
+public:
+ virtual ~GraphicReader();
+
+ const OUString& GetUpperFilterName() const { return maUpperName; }
+
+protected:
+ OUString maUpperName;
+
+ GraphicReader();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/graphic/Manager.hxx b/vcl/inc/graphic/Manager.hxx
new file mode 100644
index 0000000000..65e9214649
--- /dev/null
+++ b/vcl/inc/graphic/Manager.hxx
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_INC_GRAPHIC_MANAGER_HXX
+#define INCLUDED_VCL_INC_GRAPHIC_MANAGER_HXX
+
+#include <sal/types.h>
+#include <rtl/strbuf.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/animate/Animation.hxx>
+#include <vcl/vectorgraphicdata.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/GraphicExternalLink.hxx>
+#include <vcl/gfxlink.hxx>
+
+#include <memory>
+#include <mutex>
+#include <chrono>
+#include <o3tl/sorted_vector.hxx>
+
+class ImpGraphic;
+
+namespace vcl::graphic
+{
+class Manager final
+{
+private:
+ std::mutex maMutex; // instead of SolarMutex because graphics can live past vcl main
+ o3tl::sorted_vector<ImpGraphic*> m_pImpGraphicList;
+ std::chrono::seconds mnAllowedIdleTime;
+ bool mbSwapEnabled;
+ bool mbReducingGraphicMemory;
+ sal_Int64 mnMemoryLimit;
+ sal_Int64 mnUsedSize;
+ Timer maSwapOutTimer;
+
+ Manager();
+
+ void registerGraphic(const std::shared_ptr<ImpGraphic>& rImpGraphic);
+ void loopGraphicsAndSwapOut(std::unique_lock<std::mutex>& rGuard, bool bDropAll);
+
+ DECL_LINK(SwapOutTimerHandler, Timer*, void);
+
+ static sal_Int64 getGraphicSizeBytes(const ImpGraphic* pImpGraphic);
+ void reduceGraphicMemory(std::unique_lock<std::mutex>& rGuard, bool bDropAll = false);
+
+public:
+ static Manager& get();
+
+ void dropCache();
+ void dumpState(rtl::OStringBuffer& rState);
+
+ void swappedIn(const ImpGraphic* pImpGraphic, sal_Int64 nSizeBytes);
+ void swappedOut(const ImpGraphic* pImpGraphic, sal_Int64 nSizeBytes);
+
+ void changeExisting(const ImpGraphic* pImpGraphic, sal_Int64 nOldSize);
+ void unregisterGraphic(ImpGraphic* pImpGraphic);
+
+ std::shared_ptr<ImpGraphic> copy(std::shared_ptr<ImpGraphic> const& pImpGraphic);
+ std::shared_ptr<ImpGraphic> newInstance();
+ std::shared_ptr<ImpGraphic> newInstance(const BitmapEx& rBitmapEx);
+ std::shared_ptr<ImpGraphic> newInstance(std::shared_ptr<GfxLink> const& rLink,
+ sal_Int32 nPageIndex = 0);
+ std::shared_ptr<ImpGraphic>
+ newInstance(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr);
+ std::shared_ptr<ImpGraphic> newInstance(const Animation& rAnimation);
+ std::shared_ptr<ImpGraphic> newInstance(const GDIMetaFile& rMtf);
+ std::shared_ptr<ImpGraphic> newInstance(const GraphicExternalLink& rGraphicLink);
+};
+
+} // end namespace vcl::graphic
+
+#endif // INCLUDED_VCL_INC_GRAPHIC_MANAGER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/graphic/UnoBinaryDataContainer.hxx b/vcl/inc/graphic/UnoBinaryDataContainer.hxx
new file mode 100644
index 0000000000..f4a63ce60d
--- /dev/null
+++ b/vcl/inc/graphic/UnoBinaryDataContainer.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:
+ *
+ */
+
+#pragma once
+
+#include <cppuhelper/implbase.hxx>
+
+#include <com/sun/star/util/XBinaryDataContainer.hpp>
+
+#include <utility>
+#include <vcl/BinaryDataContainer.hxx>
+
+class UnoBinaryDataContainer final : public cppu::WeakImplHelper<css::util::XBinaryDataContainer>
+{
+private:
+ BinaryDataContainer maBinaryDataContainer;
+
+public:
+ UnoBinaryDataContainer(BinaryDataContainer aBinaryDataContainer)
+ : maBinaryDataContainer(std::move(aBinaryDataContainer))
+ {
+ }
+
+ BinaryDataContainer const& getBinaryDataContainer() const { return maBinaryDataContainer; }
+
+ // XBinaryDataContainer
+ css::uno::Sequence<sal_Int8> SAL_CALL getCopyAsByteSequence() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/graphic/UnoGraphic.hxx b/vcl/inc/graphic/UnoGraphic.hxx
new file mode 100644
index 0000000000..ce060c98f4
--- /dev/null
+++ b/vcl/inc/graphic/UnoGraphic.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_GRAPHIC_GRAPHIC_HXX
+#define INCLUDED_VCL_SOURCE_GRAPHIC_GRAPHIC_HXX
+
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/awt/XBitmap.hpp>
+#include <com/sun/star/graphic/XGraphicTransformer.hpp>
+
+#include <graphic/UnoGraphicDescriptor.hxx>
+
+#include <vcl/graph.hxx>
+
+namespace unographic {
+
+class Graphic final : public css::graphic::XGraphic,
+ public css::awt::XBitmap,
+ public css::graphic::XGraphicTransformer,
+ public ::unographic::GraphicDescriptor
+{
+public:
+ Graphic();
+ virtual ~Graphic() noexcept override;
+
+ using ::unographic::GraphicDescriptor::init;
+ void init(const ::Graphic& rGraphic);
+
+ const ::Graphic& GetGraphic() const { return maGraphic; }
+
+ // XInterface
+ virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override;
+ virtual void SAL_CALL acquire() noexcept override;
+ virtual void SAL_CALL release() noexcept override;
+private:
+ // 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;
+
+ // XGraphic
+ virtual ::sal_Int8 SAL_CALL getType( ) override;
+
+ // XBitmap
+ virtual css::awt::Size SAL_CALL getSize( ) override;
+ virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL getDIB( ) override;
+ virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL getMaskDIB( ) override;
+
+ // XGraphicTransformer
+ virtual css::uno::Reference< css::graphic::XGraphic > SAL_CALL colorChange(
+ const css::uno::Reference< css::graphic::XGraphic >& rGraphic,
+ sal_Int32 nColorFrom, sal_Int8 nTolerance, sal_Int32 nColorTo, sal_Int8 nAlphaTo ) override;
+
+ virtual css::uno::Reference< css::graphic::XGraphic > SAL_CALL applyDuotone(
+ const css::uno::Reference< css::graphic::XGraphic >& rGraphic,
+ sal_Int32 nColorOne, sal_Int32 nColorTwo ) override;
+
+ virtual css::uno::Reference< css::graphic::XGraphic > SAL_CALL applyBrightnessContrast(
+ const css::uno::Reference< css::graphic::XGraphic >& rxGraphic,
+ sal_Int32 nBrightness, sal_Int32 nContrast, sal_Bool mso ) override;
+
+ ::Graphic maGraphic;
+};
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/graphic/UnoGraphicDescriptor.hxx b/vcl/inc/graphic/UnoGraphicDescriptor.hxx
new file mode 100644
index 0000000000..2adc19dac0
--- /dev/null
+++ b/vcl/inc/graphic/UnoGraphicDescriptor.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_VCL_SOURCE_GRAPHIC_DESCRIPTOR_HXX
+#define INCLUDED_VCL_SOURCE_GRAPHIC_DESCRIPTOR_HXX
+
+#include <comphelper/propertysethelper.hxx>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+
+#include <comphelper/propertysetinfo.hxx>
+#include <vcl/graph.hxx>
+
+inline constexpr OUString MIMETYPE_BMP = u"image/x-MS-bmp"_ustr;
+inline constexpr OUString MIMETYPE_GIF = u"image/gif"_ustr;
+inline constexpr OUString MIMETYPE_JPG = u"image/jpeg"_ustr;
+inline constexpr OUString MIMETYPE_PCD = u"image/x-photo-cd"_ustr;
+inline constexpr OUString MIMETYPE_PCX = u"image/x-pcx"_ustr;
+inline constexpr OUString MIMETYPE_PNG = u"image/png"_ustr;
+inline constexpr OUString MIMETYPE_TIF = u"image/tiff"_ustr;
+inline constexpr OUString MIMETYPE_XBM = u"image/x-xbitmap"_ustr;
+inline constexpr OUString MIMETYPE_XPM = u"image/x-xpixmap"_ustr;
+inline constexpr OUString MIMETYPE_PBM = u"image/x-portable-bitmap"_ustr;
+inline constexpr OUString MIMETYPE_PGM = u"image/x-portable-graymap"_ustr;
+inline constexpr OUString MIMETYPE_PPM = u"image/x-portable-pixmap"_ustr;
+inline constexpr OUString MIMETYPE_RAS = u"image/x-cmu-raster"_ustr;
+inline constexpr OUString MIMETYPE_TGA = u"image/x-targa"_ustr;
+inline constexpr OUString MIMETYPE_PSD = u"image/vnd.adobe.photoshop"_ustr;
+inline constexpr OUString MIMETYPE_EPS = u"image/x-eps"_ustr;
+inline constexpr OUString MIMETYPE_DXF = u"image/vnd.dxf"_ustr;
+inline constexpr OUString MIMETYPE_MET = u"image/x-met"_ustr;
+inline constexpr OUString MIMETYPE_PCT = u"image/x-pict"_ustr;
+inline constexpr OUString MIMETYPE_SVM = u"image/x-svm"_ustr;
+inline constexpr OUString MIMETYPE_WMF = u"image/x-wmf"_ustr;
+inline constexpr OUString MIMETYPE_EMF = u"image/x-emf"_ustr;
+inline constexpr OUString MIMETYPE_SVG = u"image/svg+xml"_ustr;
+inline constexpr OUString MIMETYPE_PDF = u"application/pdf"_ustr;
+inline constexpr OUString MIMETYPE_WEBP = u"image/webp"_ustr;
+inline constexpr OUString MIMETYPE_VCLGRAPHIC = u"image/x-vclgraphic"_ustr;
+
+namespace comphelper { class PropertySetInfo; }
+namespace com::sun::star::io { class XInputStream; }
+
+class Graphic;
+
+namespace unographic {
+
+class GraphicDescriptor : public ::cppu::OWeakObject,
+ public css::lang::XServiceInfo,
+ public css::lang::XTypeProvider,
+ public ::comphelper::PropertySetHelper
+{
+public:
+
+ GraphicDescriptor();
+ virtual ~GraphicDescriptor() noexcept override;
+
+ void init( const ::Graphic& rGraphic );
+ void init( const OUString& rURL );
+ void init( const css::uno::Reference< css::io::XInputStream >& rxIStm, const OUString& rURL );
+
+ static rtl::Reference<::comphelper::PropertySetInfo> createPropertySetInfo();
+
+ // XInterface
+ virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override;
+ virtual void SAL_CALL acquire() noexcept override;
+ virtual void SAL_CALL release() noexcept override;
+
+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;
+
+ // PropertySetHelper
+ virtual void _setPropertyValues( const comphelper::PropertyMapEntry** ppEntries, const css::uno::Any* pValues ) override;
+ virtual void _getPropertyValues( const comphelper::PropertyMapEntry** ppEntries, css::uno::Any* pValue ) override;
+
+private:
+
+ const ::Graphic* mpGraphic;
+ GraphicType meType;
+ OUString maMimeType;
+ Size maSizePixel;
+ Size maSize100thMM;
+ sal_uInt16 mnBitsPerPixel;
+ bool mbTransparent;
+
+ GraphicDescriptor( const GraphicDescriptor& rDescriptor ) = delete;
+
+ GraphicDescriptor& operator=( const GraphicDescriptor& ) = delete;
+
+ void implCreate( SvStream& rIStm, const OUString* pPath );
+};
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/graphic/VectorGraphicLoader.hxx b/vcl/inc/graphic/VectorGraphicLoader.hxx
new file mode 100644
index 0000000000..55714fd2be
--- /dev/null
+++ b/vcl/inc/graphic/VectorGraphicLoader.hxx
@@ -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/.
+ *
+ */
+
+#pragma once
+
+#include <vcl/vectorgraphicdata.hxx>
+#include <vcl/BinaryDataContainer.hxx>
+#include <memory>
+
+namespace vcl
+{
+std::shared_ptr<VectorGraphicData> loadVectorGraphic(BinaryDataContainer const& rDataContainer,
+ VectorGraphicDataType eType);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/headless/BitmapHelper.hxx b/vcl/inc/headless/BitmapHelper.hxx
new file mode 100644
index 0000000000..2b65984647
--- /dev/null
+++ b/vcl/inc/headless/BitmapHelper.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <headless/CairoCommon.hxx>
+#include <headless/svpbmp.hxx>
+#include <basegfx/utils/systemdependentdata.hxx>
+
+class VCL_DLLPUBLIC BitmapHelper : public SurfaceHelper
+{
+private:
+#ifdef HAVE_CAIRO_FORMAT_RGB24_888
+ const bool m_bForceARGB32;
+#endif
+ SvpSalBitmap aTmpBmp;
+
+public:
+ explicit BitmapHelper(const SalBitmap& rSourceBitmap, const bool bForceARGB32 = false);
+ void mark_dirty();
+ unsigned char* getBits(sal_Int32& rStride);
+};
+
+class VCL_DLLPUBLIC MaskHelper : public SurfaceHelper
+{
+public:
+ explicit MaskHelper(const SalBitmap& rAlphaBitmap);
+};
+
+class SystemDependentData_BitmapHelper : public basegfx::SystemDependentData
+{
+private:
+ std::shared_ptr<BitmapHelper> maBitmapHelper;
+
+public:
+ SystemDependentData_BitmapHelper(std::shared_ptr<BitmapHelper> xBitmapHelper);
+
+ const std::shared_ptr<BitmapHelper>& getBitmapHelper() const { return maBitmapHelper; };
+ virtual sal_Int64 estimateUsageInBytes() const override;
+};
+
+class SystemDependentData_MaskHelper : public basegfx::SystemDependentData
+{
+private:
+ std::shared_ptr<MaskHelper> maMaskHelper;
+
+public:
+ SystemDependentData_MaskHelper(std::shared_ptr<MaskHelper> xMaskHelper);
+
+ const std::shared_ptr<MaskHelper>& getMaskHelper() const { return maMaskHelper; };
+ virtual sal_Int64 estimateUsageInBytes() const override;
+};
+
+VCL_DLLPUBLIC void tryToUseSourceBuffer(const SalBitmap& rSourceBitmap,
+ std::shared_ptr<BitmapHelper>& rSurface);
+VCL_DLLPUBLIC void tryToUseMaskBuffer(const SalBitmap& rMaskBitmap,
+ std::shared_ptr<MaskHelper>& rMask);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/headless/CairoCommon.hxx b/vcl/inc/headless/CairoCommon.hxx
new file mode 100644
index 0000000000..13e0398dd6
--- /dev/null
+++ b/vcl/inc/headless/CairoCommon.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <cairo.h>
+
+#include <vcl/dllapi.h>
+#include <vcl/region.hxx>
+#include <vcl/salgtype.hxx>
+#include <vcl/vclenum.hxx>
+#include <vcl/BitmapBuffer.hxx>
+
+#include <com/sun/star/drawing/LineCap.hpp>
+
+#include <basegfx/utils/systemdependentdata.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+
+#include <optional>
+#include <unordered_map>
+
+typedef struct _cairo cairo_t;
+typedef struct _cairo_surface cairo_surface_t;
+typedef struct _cairo_user_data_key cairo_user_data_key_t;
+
+class Gradient;
+class SalBitmap;
+struct SalGradient;
+
+VCL_DLLPUBLIC void dl_cairo_surface_set_device_scale(cairo_surface_t* surface, double x_scale,
+ double y_scale);
+VCL_DLLPUBLIC void dl_cairo_surface_get_device_scale(cairo_surface_t* surface, double* x_scale,
+ double* y_scale);
+
+VCL_DLLPUBLIC basegfx::B2DRange getFillDamage(cairo_t* cr);
+VCL_DLLPUBLIC basegfx::B2DRange getClipBox(cairo_t* cr);
+VCL_DLLPUBLIC basegfx::B2DRange getClippedFillDamage(cairo_t* cr);
+VCL_DLLPUBLIC basegfx::B2DRange getClippedStrokeDamage(cairo_t* cr);
+VCL_DLLPUBLIC basegfx::B2DRange getStrokeDamage(cairo_t* cr);
+
+class SystemDependentData_CairoPath final : public basegfx::SystemDependentData
+{
+private:
+ // the path data itself
+ cairo_path_t* mpCairoPath;
+
+ // all other values the path data is based on and
+ // need to be compared with to check for data validity
+ bool mbNoJoin;
+ bool mbAntiAlias;
+ std::vector<double> maStroke;
+
+public:
+ SystemDependentData_CairoPath(size_t nSizeMeasure, cairo_t* cr, bool bNoJoin, bool bAntiAlias,
+ const std::vector<double>* pStroke); // MM01
+ virtual ~SystemDependentData_CairoPath() override;
+
+ // read access
+ cairo_path_t* getCairoPath() { return mpCairoPath; }
+ bool getNoJoin() const { return mbNoJoin; }
+ bool getAntiAlias() const { return mbAntiAlias; }
+ const std::vector<double>& getStroke() const { return maStroke; }
+
+ virtual sal_Int64 estimateUsageInBytes() const override;
+};
+
+VCL_DLLPUBLIC size_t AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon,
+ const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap,
+ bool bPixelSnapHairline);
+
+class VCL_DLLPUBLIC PixelSnapper
+{
+public:
+ basegfx::B2DPoint snap(const basegfx::B2DPolygon& rPolygon,
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ basegfx::B2DHomMatrix& rObjectToDeviceInv, sal_uInt32 nIndex);
+
+private:
+ basegfx::B2DPoint maPrevPoint, maCurrPoint, maNextPoint;
+ basegfx::B2ITuple maPrevTuple, maCurrTuple, maNextTuple;
+};
+
+VCL_DLLPUBLIC void add_polygon_path(cairo_t* cr, const basegfx::B2DPolyPolygon& rPolyPolygon,
+ const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap);
+
+VCL_DLLPUBLIC cairo_format_t getCairoFormat(const BitmapBuffer& rBuffer);
+
+VCL_DLLPUBLIC std::optional<BitmapBuffer> FastConvert24BitRgbTo32BitCairo(const BitmapBuffer* pSrc);
+
+enum class PaintMode
+{
+ Over,
+ Xor
+};
+
+typedef void (*damageHandler)(void* handle, sal_Int32 nExtentsX, sal_Int32 nExtentsY,
+ sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight);
+
+struct VCL_DLLPUBLIC DamageHandler
+{
+ void* handle;
+ damageHandler damaged;
+};
+
+struct VCL_DLLPUBLIC CairoCommon
+{
+ cairo_surface_t* m_pSurface;
+ basegfx::B2IVector m_aFrameSize;
+ vcl::Region m_aClipRegion;
+ std::optional<Color> m_oLineColor;
+ std::optional<Color> m_oFillColor;
+ PaintMode m_ePaintMode;
+ double m_fScale;
+
+ CairoCommon()
+ : m_pSurface(nullptr)
+ , m_oLineColor(Color(0x00, 0x00, 0x00))
+ , m_oFillColor(Color(0xFF, 0xFF, 0XFF))
+ , m_ePaintMode(PaintMode::Over)
+ , m_fScale(1.0)
+ {
+ }
+
+ static cairo_user_data_key_t* getDamageKey();
+
+ cairo_surface_t* getSurface() const { return m_pSurface; }
+
+ sal_uInt16 GetBitCount() const;
+
+ cairo_t* getCairoContext(bool bXorModeAllowed, bool bAntiAlias) const;
+ void releaseCairoContext(cairo_t* cr, bool bXorModeAllowed,
+ const basegfx::B2DRange& rExtents) const;
+
+ cairo_t* createTmpCompatibleCairoContext() const;
+
+ static void applyColor(cairo_t* cr, Color rColor, double fTransparency = 0.0);
+ void clipRegion(cairo_t* cr);
+ static void clipRegion(cairo_t* cr, const vcl::Region& rClipRegion);
+
+ void SetXORMode(bool bSet, bool bInvertOnly);
+ void SetROPLineColor(SalROPColor nROPColor);
+ void SetROPFillColor(SalROPColor nROPColor);
+
+ void drawPixel(const std::optional<Color>& rLineColor, tools::Long nX, tools::Long nY,
+ bool bAntiAlias);
+
+ static Color getPixel(cairo_surface_t* pSurface, tools::Long nX, tools::Long nY);
+
+ void drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2,
+ bool bAntiAlias);
+
+ void drawRect(double nX, double nY, double nWidth, double nHeight, bool bAntiAlias);
+
+ void drawPolygon(sal_uInt32 nPoints, const Point* pPtAry, bool bAntiAlias);
+
+ void drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints, const Point** pPtAry,
+ bool bAntiAlias);
+
+ void drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon&, double fTransparency, bool bAntiAlias);
+
+ void drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry, bool bAntiAlias);
+
+ bool drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& rPolyLine, double fTransparency, double fLineWidth,
+ const std::vector<double>* pStroke, basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
+ bool bPixelSnapHairline, bool bAntiAlias);
+
+ bool drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ sal_uInt8 nTransparency, bool bAntiAlias);
+
+ bool drawGradient(const tools::PolyPolygon& rPolyPolygon, const Gradient& rGradient,
+ bool bAntiAlias);
+
+ bool implDrawGradient(basegfx::B2DPolyPolygon const& rPolyPolygon, SalGradient const& rGradient,
+ bool bAntiAlias);
+
+ void copyWithOperator(const SalTwoRect& rTR, cairo_surface_t* source, cairo_operator_t eOp,
+ bool bAntiAlias);
+
+ void copySource(const SalTwoRect& rTR, cairo_surface_t* source, bool bAntiAlias);
+
+ static basegfx::B2DRange renderSource(cairo_t* cr, const SalTwoRect& rTR,
+ cairo_surface_t* source);
+
+ void copyBitsCairo(const SalTwoRect& rTR, cairo_surface_t* pSourceSurface, bool bAntiAlias);
+
+ void invert(const basegfx::B2DPolygon& rPoly, SalInvert nFlags, bool bAntiAlias);
+
+ void invert(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ SalInvert nFlags, bool bAntiAlias);
+
+ void invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags, bool bAntiAlias);
+
+ void drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap, bool bAntiAlias);
+
+ bool drawAlphaBitmap(const SalTwoRect& rTR, const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap, bool bAntiAlias);
+
+ bool drawTransformedBitmap(const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY, const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap, double fAlpha, bool bAntiAlias);
+
+ void drawMask(const SalTwoRect& rTR, const SalBitmap& rSalBitmap, Color nMaskColor,
+ bool bAntiAlias);
+
+ std::shared_ptr<SalBitmap> getBitmap(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight);
+
+ static cairo_surface_t* createCairoSurface(const BitmapBuffer* pBuffer);
+
+ static bool supportsOperation(OutDevSupportType eType);
+ static bool hasFastDrawTransformedBitmap();
+
+private:
+ void doXorOnRelease(sal_Int32 nExtentsLeft, sal_Int32 nExtentsTop, sal_Int32 nExtentsRight,
+ sal_Int32 nExtentsBottom, cairo_surface_t* const surface,
+ sal_Int32 nWidth) const;
+};
+
+class VCL_DLLPUBLIC SurfaceHelper
+{
+private:
+ cairo_surface_t* pSurface;
+ std::unordered_map<sal_uInt64, cairo_surface_t*> maDownscaled;
+
+ SurfaceHelper(const SurfaceHelper&) = delete;
+ SurfaceHelper& operator=(const SurfaceHelper&) = delete;
+
+ cairo_surface_t* implCreateOrReuseDownscale(unsigned long nTargetWidth,
+ unsigned long nTargetHeight);
+
+protected:
+ cairo_surface_t* implGetSurface() const { return pSurface; }
+ void implSetSurface(cairo_surface_t* pNew) { pSurface = pNew; }
+
+ bool isTrivial() const;
+
+public:
+ explicit SurfaceHelper();
+ ~SurfaceHelper();
+
+ cairo_surface_t* getSurface(unsigned long nTargetWidth = 0,
+ unsigned long nTargetHeight = 0) const;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/headless/SvpGraphicsBackend.hxx b/vcl/inc/headless/SvpGraphicsBackend.hxx
new file mode 100644
index 0000000000..6a68899c8a
--- /dev/null
+++ b/vcl/inc/headless/SvpGraphicsBackend.hxx
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/dllapi.h>
+#include <tools/long.hxx>
+#include <tools/color.hxx>
+#include <salgdiimpl.hxx>
+#include <salgdi.hxx>
+
+#include <headless/CairoCommon.hxx>
+
+class VCL_DLLPUBLIC SvpGraphicsBackend final : public SalGraphicsImpl
+{
+ CairoCommon& m_rCairoCommon;
+
+public:
+ SvpGraphicsBackend(CairoCommon& rCairoCommon);
+
+ void Init() override;
+
+ void freeResources() override;
+
+ OUString getRenderBackendName() const override { return "svp"; }
+
+ void setClipRegion(vcl::Region const& rRegion) override;
+ void ResetClipRegion() override;
+
+ sal_uInt16 GetBitCount() const override;
+
+ tools::Long GetGraphicsWidth() const override;
+
+ void SetLineColor() override;
+ void SetLineColor(Color nColor) override;
+ void SetFillColor() override;
+ void SetFillColor(Color nColor) override;
+ void SetXORMode(bool bSet, bool bInvertOnly) override;
+ void SetROPLineColor(SalROPColor nROPColor) override;
+ void SetROPFillColor(SalROPColor nROPColor) override;
+
+ void drawPixel(tools::Long nX, tools::Long nY) override;
+ void drawPixel(tools::Long nX, tools::Long nY, Color nColor) override;
+
+ void drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2) override;
+ void drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight) override;
+ void drawPolyLine(sal_uInt32 nPoints, const Point* pPointArray) override;
+ void drawPolygon(sal_uInt32 nPoints, const Point* pPointArray) override;
+ void drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point** pPointArray) override;
+
+ void drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon&, double fTransparency) override;
+
+ bool drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice, const basegfx::B2DPolygon&,
+ double fTransparency, double fLineWidth, const std::vector<double>* pStroke,
+ basegfx::B2DLineJoin, css::drawing::LineCap, double fMiterMinimumAngle,
+ bool bPixelSnapHairline) override;
+
+ bool drawPolyLineBezier(sal_uInt32 nPoints, const Point* pPointArray,
+ const PolyFlags* pFlagArray) override;
+
+ bool drawPolygonBezier(sal_uInt32 nPoints, const Point* pPointArray,
+ const PolyFlags* pFlagArray) override;
+
+ bool drawPolyPolygonBezier(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point* const* pPointArray,
+ const PolyFlags* const* pFlagArray) override;
+
+ void copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight, bool bWindowInvalidate) override;
+
+ void copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics) override;
+
+ void drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap) override;
+
+ void drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ const SalBitmap& rMaskBitmap) override;
+
+ void drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ Color nMaskColor) override;
+
+ std::shared_ptr<SalBitmap> getBitmap(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight) override;
+
+ Color getPixel(tools::Long nX, tools::Long nY) override;
+
+ void invert(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ SalInvert nFlags) override;
+
+ void invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags) override;
+
+ bool drawEPS(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ void* pPtr, sal_uInt32 nSize) override;
+
+ bool blendBitmap(const SalTwoRect&, const SalBitmap& rBitmap) override;
+
+ bool blendAlphaBitmap(const SalTwoRect&, const SalBitmap& rSrcBitmap,
+ const SalBitmap& rMaskBitmap, const SalBitmap& rAlphaBitmap) override;
+
+ bool drawAlphaBitmap(const SalTwoRect&, const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap) override;
+
+ bool drawTransformedBitmap(const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY, const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap, double fAlpha) override;
+
+ bool hasFastDrawTransformedBitmap() const override;
+
+ bool drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ sal_uInt8 nTransparency) override;
+
+ bool drawGradient(const tools::PolyPolygon& rPolygon, const Gradient& rGradient) override;
+ bool implDrawGradient(basegfx::B2DPolyPolygon const& rPolyPolygon,
+ SalGradient const& rGradient) override;
+
+ bool supportsOperation(OutDevSupportType eType) const override;
+
+ void drawBitmapBuffer(const SalTwoRect& rPosAry, const BitmapBuffer* pBuffer,
+ cairo_operator_t eOp);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/headless/svpbmp.hxx b/vcl/inc/headless/svpbmp.hxx
new file mode 100644
index 0000000000..b7fdb230f9
--- /dev/null
+++ b/vcl/inc/headless/svpbmp.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_HEADLESS_SVPBMP_HXX
+#define INCLUDED_VCL_INC_HEADLESS_SVPBMP_HXX
+
+#include <sal/config.h>
+
+#include <salbmp.hxx>
+#include <basegfx/utils/systemdependentdata.hxx>
+#include <optional>
+
+class VCL_DLLPUBLIC SvpSalBitmap final : public SalBitmap, public basegfx::SystemDependentDataHolder // MM02
+{
+ std::optional<BitmapBuffer> moDIB;
+public:
+ SvpSalBitmap();
+ virtual ~SvpSalBitmap() override;
+
+ bool ImplCreate(const Size& rSize,
+ vcl::PixelFormat ePixelFormat,
+ const BitmapPalette& rPalette,
+ bool bClear);
+
+ // SalBitmap
+ virtual bool Create(const Size& rSize,
+ vcl::PixelFormat ePixelFormat,
+ const BitmapPalette& rPalette) override;
+ virtual bool Create( const SalBitmap& rSalBmp ) override;
+ virtual bool Create( const SalBitmap& rSalBmp,
+ SalGraphics* pGraphics ) override;
+ virtual bool Create(const SalBitmap& rSalBmp,
+ vcl::PixelFormat eNewPixelFormat) override;
+ virtual bool Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& rBitmapCanvas,
+ Size& rSize,
+ bool bMask = false ) override;
+ void Create(const std::optional<BitmapBuffer> & pBuf);
+ const BitmapBuffer* GetBuffer() const
+ {
+ return moDIB ? &*moDIB : nullptr;
+ }
+ virtual void Destroy() final override;
+ virtual Size GetSize() const override;
+ virtual sal_uInt16 GetBitCount() const override;
+
+ virtual BitmapBuffer* AcquireBuffer( BitmapAccessMode nMode ) override;
+ virtual void ReleaseBuffer( BitmapBuffer* pBuffer, BitmapAccessMode nMode ) override;
+ virtual bool GetSystemData( BitmapSystemData& rData ) override;
+
+ virtual bool ScalingSupported() const override;
+ virtual bool Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag ) override;
+ virtual bool Replace( const Color& rSearchColor, const Color& rReplaceColor, sal_uInt8 nTol ) override;
+
+ virtual const basegfx::SystemDependentDataHolder* accessSystemDependentDataHolder() const override;
+};
+
+#endif // INCLUDED_VCL_INC_HEADLESS_SVPBMP_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/headless/svpdata.hxx b/vcl/inc/headless/svpdata.hxx
new file mode 100644
index 0000000000..f995d7ef39
--- /dev/null
+++ b/vcl/inc/headless/svpdata.hxx
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <unx/gendata.hxx>
+
+class SvpSalData : public GenericUnixSalData
+{
+public:
+ explicit SvpSalData()
+ : GenericUnixSalData()
+ {
+ }
+ virtual void ErrorTrapPush() override {}
+ virtual bool ErrorTrapPop(bool /*bIgnoreError*/ = true) override { return false; }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/headless/svpdummies.hxx b/vcl/inc/headless/svpdummies.hxx
new file mode 100644
index 0000000000..92958f8d55
--- /dev/null
+++ b/vcl/inc/headless/svpdummies.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_INC_HEADLESS_SVPDUMMIES_HXX
+#define INCLUDED_VCL_INC_HEADLESS_SVPDUMMIES_HXX
+
+#include <vcl/sysdata.hxx>
+#include <unx/gensys.h>
+#include <salobj.hxx>
+
+class SalGraphics;
+
+class SvpSalObject final : public SalObject
+{
+ SystemEnvData m_aSystemChildData;
+
+public:
+ virtual ~SvpSalObject() override;
+
+ // override all pure virtual methods
+ virtual void ResetClipRegion() override;
+ virtual void BeginSetClipRegion( sal_uInt32 nRects ) override;
+ virtual void UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void EndSetClipRegion() override;
+
+ virtual void SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void Show( bool bVisible ) override;
+
+ virtual const SystemEnvData* GetSystemData() const override;
+};
+
+class SvpSalSystem : public SalGenericSystem
+{
+public:
+ SvpSalSystem() {}
+ virtual ~SvpSalSystem() override;
+ // get info about the display
+ virtual unsigned int GetDisplayScreenCount() override;
+ virtual AbsoluteScreenPixelRectangle GetDisplayScreenPosSizePixel( unsigned int nScreen ) override;
+
+ virtual int ShowNativeDialog( const OUString& rTitle,
+ const OUString& rMessage,
+ const std::vector< OUString >& rButtons ) override;
+};
+
+#endif // INCLUDED_VCL_INC_HEADLESS_SVPDUMMIES_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/headless/svpframe.hxx b/vcl/inc/headless/svpframe.hxx
new file mode 100644
index 0000000000..3789e44745
--- /dev/null
+++ b/vcl/inc/headless/svpframe.hxx
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_HEADLESS_SVPFRAME_HXX
+#define INCLUDED_VCL_INC_HEADLESS_SVPFRAME_HXX
+
+#include <vcl/sysdata.hxx>
+
+#include <salframe.hxx>
+
+#include <vector>
+
+#ifdef IOS
+#include <quartz/salgdi.h>
+#define SvpSalInstance AquaSalInstance
+#define SvpSalGraphics AquaSalGraphics
+#endif
+
+class SvpSalInstance;
+class SvpSalGraphics;
+
+class SvpSalFrame : public SalFrame
+{
+ SvpSalInstance* m_pInstance;
+ SvpSalFrame* m_pParent; // pointer to parent frame
+ std::vector< SvpSalFrame* > m_aChildren; // Vector of child frames
+ SalFrameStyleFlags m_nStyle;
+ bool m_bVisible;
+#ifndef IOS
+ cairo_surface_t* m_pSurface;
+#endif
+ tools::Long m_nMinWidth;
+ tools::Long m_nMinHeight;
+ tools::Long m_nMaxWidth;
+ tools::Long m_nMaxHeight;
+
+ SystemEnvData m_aSystemChildData;
+
+ std::vector< SvpSalGraphics* > m_aGraphics;
+
+ static SvpSalFrame* s_pFocusFrame;
+ OUString m_sTitle;
+
+public:
+ SvpSalFrame( SvpSalInstance* pInstance,
+ SalFrame* pParent,
+ SalFrameStyleFlags nSalFrameStyle );
+ virtual ~SvpSalFrame() override;
+
+ void GetFocus();
+ void LoseFocus();
+ void PostPaint() const;
+
+ const OUString& title() const { return m_sTitle; }
+ SalFrameStyleFlags style() const { return m_nStyle; }
+ bool isVisible() const { return m_bVisible; }
+ bool hasFocus() const { return s_pFocusFrame == this; }
+
+ // SalFrame
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics( SalGraphics* pGraphics ) override;
+
+ virtual bool PostEvent(std::unique_ptr<ImplSVEvent> pData) override;
+
+ virtual void SetTitle( const OUString& rTitle ) override;
+ virtual void SetIcon( sal_uInt16 nIcon ) override;
+ virtual void SetMenu( SalMenu* pMenu ) override;
+
+ virtual void SetExtendedFrameStyle( SalExtStyle nExtStyle ) override;
+ virtual void Show( bool bVisible, bool bNoActivate = false ) override;
+ virtual void SetMinClientSize( tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void SetMaxClientSize( tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags ) override;
+ virtual void GetClientSize( tools::Long& rWidth, tools::Long& rHeight ) override;
+ virtual void GetWorkArea( AbsoluteScreenPixelRectangle& rRect ) override;
+ virtual SalFrame* GetParent() const override;
+ virtual void SetWindowState(const vcl::WindowData*) override;
+ virtual bool GetWindowState(vcl::WindowData*) override;
+ virtual void ShowFullScreen( bool bFullScreen, sal_Int32 nDisplay ) override;
+ virtual void StartPresentation( bool bStart ) override;
+ virtual void SetAlwaysOnTop( bool bOnTop ) override;
+ virtual void ToTop( SalFrameToTop nFlags ) override;
+ virtual void SetPointer( PointerStyle ePointerStyle ) override;
+ virtual void CaptureMouse( bool bMouse ) override;
+ virtual void SetPointerPos( tools::Long nX, tools::Long nY ) override;
+ using SalFrame::Flush;
+ virtual void Flush() override;
+ virtual void SetInputContext( SalInputContext* pContext ) override;
+ virtual void EndExtTextInput( EndExtTextInputFlags nFlags ) override;
+ virtual OUString GetKeyName( sal_uInt16 nKeyCode ) override;
+ virtual bool MapUnicodeToKeyCode( sal_Unicode aUnicode, LanguageType aLangType, vcl::KeyCode& rKeyCode ) override;
+ virtual LanguageType GetInputLanguage() override;
+ virtual void UpdateSettings( AllSettings& rSettings ) override;
+ virtual void Beep() override;
+ virtual const SystemEnvData* GetSystemData() const override;
+ virtual SalPointerState GetPointerState() override;
+ virtual KeyIndicatorState GetIndicatorState() override;
+ virtual void SimulateKeyPress( sal_uInt16 nKeyCode ) override;
+ virtual void SetParent( SalFrame* pNewParent ) override;
+ virtual void SetPluginParent( SystemParentData* pNewParent ) override;
+ virtual void ResetClipRegion() override;
+ virtual void BeginSetClipRegion( sal_uInt32 nRects ) override;
+ virtual void UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void EndSetClipRegion() override;
+
+ /*TODO: functional implementation */
+ virtual void SetScreenNumber( unsigned int ) override {}
+ virtual void SetApplicationID(const OUString &) override {}
+
+private:
+ basegfx::B2IVector GetSurfaceFrameSize() const;
+};
+
+template <typename charT, typename traits>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& stream,
+ const SvpSalFrame& frame)
+{
+ stream << &frame << " (vis " << frame.isVisible() << " focus " << frame.hasFocus();
+ stream << " style " << std::hex << std::setfill('0') << std::setw(8) << static_cast<sal_uInt32>(frame.style());
+ OUString sTitle = frame.title();
+ if (!sTitle.isEmpty())
+ stream << " '" << sTitle << "'";
+ stream << ")";
+ return stream;
+}
+
+#endif // INCLUDED_VCL_INC_HEADLESS_SVPFRAME_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/headless/svpgdi.hxx b/vcl/inc/headless/svpgdi.hxx
new file mode 100644
index 0000000000..a68c1b974c
--- /dev/null
+++ b/vcl/inc/headless/svpgdi.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#ifdef IOS
+#error This file is not for iOS
+#endif
+
+#include <sal/config.h>
+
+#include <osl/endian.h>
+#include <vcl/sysdata.hxx>
+#include <config_cairo_canvas.h>
+
+#include <salgdi.hxx>
+#include <sallayout.hxx>
+#include <unx/cairotextrender.hxx>
+#include <font/FontMetricData.hxx>
+
+#include <headless/SvpGraphicsBackend.hxx>
+#include <headless/CairoCommon.hxx>
+
+struct BitmapBuffer;
+class FreetypeFont;
+
+class VCL_DLLPUBLIC SvpSalGraphics : public SalGraphicsAutoDelegateToImpl
+{
+ CairoCommon m_aCairoCommon;
+ CairoTextRender m_aTextRenderImpl;
+ std::unique_ptr<SvpGraphicsBackend> m_pBackend;
+
+public:
+ void setSurface(cairo_surface_t* pSurface, const basegfx::B2IVector& rSize);
+ cairo_surface_t* getSurface() const { return m_aCairoCommon.m_pSurface; }
+ static cairo_user_data_key_t* getDamageKey()
+ {
+ return CairoCommon::getDamageKey();
+ }
+
+protected:
+
+ cairo_t* createTmpCompatibleCairoContext() const;
+
+public:
+ SvpSalGraphics();
+ virtual ~SvpSalGraphics() override;
+
+ virtual SalGraphicsImpl* GetImpl() const override { return m_pBackend.get(); }
+ std::unique_ptr<SvpGraphicsBackend> const& getSvpBackend() { return m_pBackend; }
+
+ virtual void GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY ) override;
+
+ virtual void SetTextColor( Color nColor ) override;
+ virtual void SetFont(LogicalFontInstance*, int nFallbackLevel) override;
+ virtual void GetFontMetric( FontMetricDataRef&, int nFallbackLevel ) override;
+ virtual FontCharMapRef GetFontCharMap() const override;
+ virtual bool GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const override;
+ virtual void GetDevFontList( vcl::font::PhysicalFontCollection* ) override;
+ virtual void ClearDevFontCache() override;
+ virtual bool AddTempDevFont( vcl::font::PhysicalFontCollection*, const OUString& rFileURL, const OUString& rFontName ) override;
+ virtual std::unique_ptr<GenericSalLayout>
+ GetTextLayout(int nFallbackLevel) override;
+ virtual void DrawTextLayout( const GenericSalLayout& ) override;
+
+ virtual bool ShouldDownscaleIconsAtSurface(double* pScaleOut) const override;
+
+ virtual SystemGraphicsData GetGraphicsData() const override;
+
+#if ENABLE_CAIRO_CANVAS
+ virtual bool SupportsCairo() const override;
+ virtual cairo::SurfaceSharedPtr CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const override;
+ virtual cairo::SurfaceSharedPtr CreateSurface(const OutputDevice& rRefDevice, int x, int y, int width, int height) const override;
+ virtual cairo::SurfaceSharedPtr CreateBitmapSurface(const OutputDevice& rRefDevice, const BitmapSystemData& rData, const Size& rSize) const override;
+ virtual css::uno::Any GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& rSurface, const basegfx::B2ISize& rSize) const override;
+#endif // ENABLE_CAIRO_CANVAS
+
+ cairo_t* getCairoContext() const
+ {
+ return m_aCairoCommon.getCairoContext(/*bXorModeAllowed*/false, getAntiAlias());
+ }
+
+ void clipRegion(cairo_t* cr)
+ {
+ m_aCairoCommon.clipRegion(cr);
+ }
+
+ void copySource(const SalTwoRect& rTR, cairo_surface_t* source)
+ {
+ m_aCairoCommon.copySource(rTR, source, getAntiAlias());
+ }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/headless/svpinst.hxx b/vcl/inc/headless/svpinst.hxx
new file mode 100644
index 0000000000..efe32761f5
--- /dev/null
+++ b/vcl/inc/headless/svpinst.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_HEADLESS_SVPINST_HXX
+#define INCLUDED_VCL_INC_HEADLESS_SVPINST_HXX
+
+#include <osl/thread.hxx>
+#include <osl/conditn.hxx>
+#include <salinst.hxx>
+#include <saltimer.hxx>
+#include <salusereventlist.hxx>
+#include <unx/geninst.h>
+#include <unx/genprn.h>
+
+#include <condition_variable>
+#include <mutex>
+#include <queue>
+
+#include <sys/time.h>
+
+#ifdef IOS
+#define SvpSalInstance AquaSalInstance
+#endif
+
+class SvpSalInstance;
+class SvpSalTimer final : public SalTimer
+{
+ SvpSalInstance* m_pInstance;
+public:
+ SvpSalTimer( SvpSalInstance* pInstance ) : m_pInstance( pInstance ) {}
+ virtual ~SvpSalTimer() override;
+
+ // override all pure virtual methods
+ virtual void Start( sal_uInt64 nMS ) override;
+ virtual void Stop() override;
+};
+
+class SvpSalFrame;
+class GenPspGraphics;
+
+enum class SvpRequest
+{
+ NONE,
+ MainThreadDispatchOneEvent,
+ MainThreadDispatchAllEvents,
+};
+
+class SvpSalYieldMutex final : public SalYieldMutex
+{
+private:
+ // note: these members might as well live in SvpSalInstance, but there is
+ // at least one subclass of SvpSalInstance (GTK3) that doesn't use them.
+ friend class SvpSalInstance;
+ // members for communication from main thread to non-main thread
+ std::mutex m_FeedbackMutex;
+ std::queue<bool> m_FeedbackPipe;
+ std::condition_variable m_FeedbackCV;
+ osl::Condition m_NonMainWaitingYieldCond;
+ // members for communication from non-main thread to main thread
+ bool m_bNoYieldLock = false; // accessed only on main thread
+ std::mutex m_WakeUpMainMutex; // guard m_wakeUpMain & m_Request
+ std::condition_variable m_WakeUpMainCond;
+ bool m_wakeUpMain = false;
+ SvpRequest m_Request = SvpRequest::NONE;
+
+ virtual void doAcquire( sal_uInt32 nLockCount ) override;
+ virtual sal_uInt32 doRelease( bool bUnlockAll ) override;
+
+public:
+ SvpSalYieldMutex();
+ virtual ~SvpSalYieldMutex() override;
+
+ virtual bool IsCurrentThread() const override;
+};
+
+// NOTE: the functions IsMainThread, DoYield and Wakeup *require* the use of
+// SvpSalYieldMutex; if a subclass uses something else it must override these
+// (Wakeup is only called by SvpSalTimer and SvpSalFrame)
+class VCL_DLLPUBLIC SvpSalInstance : public SalGenericInstance, public SalUserEventList
+{
+ timeval m_aTimeout;
+ sal_uLong m_nTimeoutMS;
+ oslThreadIdentifier m_MainThread;
+
+ virtual void TriggerUserEventProcessing() override;
+ virtual void ProcessEvent( SalUserEvent aEvent ) override;
+ bool ImplYield(bool bWait, bool bHandleAllCurrentEvents);
+
+public:
+ static SvpSalInstance* s_pDefaultInstance;
+
+ SvpSalInstance( std::unique_ptr<SalYieldMutex> pMutex );
+ virtual ~SvpSalInstance() override;
+
+ void CloseWakeupPipe();
+ void Wakeup(SvpRequest request = SvpRequest::NONE);
+
+ void StartTimer( sal_uInt64 nMS );
+ void StopTimer();
+
+ inline void registerFrame( SalFrame* pFrame );
+ inline void deregisterFrame( SalFrame* pFrame );
+
+ bool CheckTimeout( bool bExecuteTimers = true );
+
+ // Frame
+ virtual SalFrame* CreateChildFrame( SystemParentData* pParent, SalFrameStyleFlags nStyle ) override;
+ virtual SalFrame* CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle ) override;
+ virtual void DestroyFrame( SalFrame* pFrame ) override;
+
+ // Object (System Child Window)
+ virtual SalObject* CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow ) override;
+ virtual void DestroyObject( SalObject* pObject ) override;
+
+ // VirtualDevice
+ // nDX and nDY in Pixel
+ // nBitCount: 0 == Default(=as window) / 1 == Mono
+ // pData allows for using a system dependent graphics or device context
+ virtual std::unique_ptr<SalVirtualDevice>
+ CreateVirtualDevice( SalGraphics& rGraphics,
+ tools::Long &nDX, tools::Long &nDY,
+ DeviceFormat eFormat, const SystemGraphicsData *pData = nullptr ) override;
+
+ // Printer
+ // pSetupData->mpDriverData can be 0
+ // pSetupData must be updated with the current
+ // JobSetup
+ virtual SalInfoPrinter* CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pSetupData ) override;
+ virtual void DestroyInfoPrinter( SalInfoPrinter* pPrinter ) override;
+ virtual std::unique_ptr<SalPrinter> CreatePrinter( SalInfoPrinter* pInfoPrinter ) override;
+
+ virtual void GetPrinterQueueInfo( ImplPrnQueueList* pList ) override;
+ virtual void GetPrinterQueueState( SalPrinterQueueInfo* pInfo ) override;
+ virtual OUString GetDefaultPrinter() override;
+ virtual void PostPrintersChanged() override;
+
+ // SalTimer
+ virtual SalTimer* CreateSalTimer() override;
+ // SalSystem
+ virtual SalSystem* CreateSalSystem() override;
+ // SalBitmap
+ virtual std::shared_ptr<SalBitmap> CreateSalBitmap() override;
+
+ // wait next event and dispatch
+ // must returned by UserEvent (SalFrame::PostEvent)
+ // and timer
+ virtual bool DoYield(bool bWait, bool bHandleAllCurrentEvents) override;
+ virtual bool AnyInput( VclInputFlags nType ) override;
+ virtual bool IsMainThread() const override;
+ virtual void updateMainThread() override;
+
+ virtual OUString GetConnectionIdentifier() override;
+
+ virtual void AddToRecentDocumentList(const OUString& rFileUrl, const OUString& rMimeType, const OUString& rDocumentService) override;
+
+ virtual std::unique_ptr<GenPspGraphics> CreatePrintGraphics() override;
+
+ virtual const cairo_font_options_t* GetCairoFontOptions() override;
+};
+
+inline void SvpSalInstance::registerFrame( SalFrame* pFrame )
+{
+ insertFrame( pFrame );
+}
+
+inline void SvpSalInstance::deregisterFrame( SalFrame* pFrame )
+{
+ eraseFrame( pFrame );
+}
+
+VCL_DLLPUBLIC cairo_surface_t* get_underlying_cairo_surface(const VirtualDevice& rDevice);
+
+#endif // INCLUDED_VCL_INC_HEADLESS_SVPINST_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/headless/svpprn.hxx b/vcl/inc/headless/svpprn.hxx
new file mode 100644
index 0000000000..e1dd1e15f9
--- /dev/null
+++ b/vcl/inc/headless/svpprn.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_INC_HEADLESS_SVPPRN_HXX
+#define INCLUDED_VCL_INC_HEADLESS_SVPPRN_HXX
+
+#include <unx/genprn.h>
+
+class SvpSalInfoPrinter final : public PspSalInfoPrinter
+{
+public:
+ virtual bool Setup(weld::Window* pFrame, ImplJobSetup* pSetupData) override;
+};
+
+class SvpSalPrinter final : public PspSalPrinter
+{
+public:
+ SvpSalPrinter(SalInfoPrinter* pInfoPrinter);
+};
+
+#endif // INCLUDED_VCL_INC_HEADLESS_SVPPRN_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/headless/svpvd.hxx b/vcl/inc/headless/svpvd.hxx
new file mode 100644
index 0000000000..f1666b689a
--- /dev/null
+++ b/vcl/inc/headless/svpvd.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_INC_HEADLESS_SVPVD_HXX
+#define INCLUDED_VCL_INC_HEADLESS_SVPVD_HXX
+
+#include <salvd.hxx>
+#include <basegfx/vector/b2ivector.hxx>
+
+#include <vector>
+
+class SvpSalGraphics;
+typedef struct _cairo_surface cairo_surface_t;
+
+class VCL_DLLPUBLIC SvpSalVirtualDevice : public SalVirtualDevice
+{
+ cairo_surface_t* m_pRefSurface;
+ cairo_surface_t* m_pSurface;
+ bool m_bOwnsSurface; // nearly always true, except for edge case of tdf#127529
+ basegfx::B2IVector m_aFrameSize;
+ std::vector< SvpSalGraphics* > m_aGraphics;
+
+ bool CreateSurface(tools::Long nNewDX, tools::Long nNewDY, sal_uInt8 *const pBuffer);
+
+protected:
+ SvpSalGraphics* AddGraphics(SvpSalGraphics* aGraphics);
+
+public:
+ SvpSalVirtualDevice(cairo_surface_t* pRefSurface, cairo_surface_t* pPreExistingTarget);
+ virtual ~SvpSalVirtualDevice() override;
+
+ // SalVirtualDevice
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics( SalGraphics* pGraphics ) override;
+
+ virtual bool SetSize( tools::Long nNewDX, tools::Long nNewDY ) override;
+ virtual bool SetSizeUsingBuffer( tools::Long nNewDX, tools::Long nNewDY,
+ sal_uInt8 * pBuffer
+ ) override;
+
+ cairo_surface_t* GetSurface() const { return m_pSurface; }
+
+ // SalGeometryProvider
+ virtual tools::Long GetWidth() const override;
+ virtual tools::Long GetHeight() const override;
+};
+
+#endif // INCLUDED_VCL_INC_HEADLESS_SVPVD_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/helpwin.hxx b/vcl/inc/helpwin.hxx
new file mode 100644
index 0000000000..5a9975cee8
--- /dev/null
+++ b/vcl/inc/helpwin.hxx
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_HELPWIN_HXX
+#define INCLUDED_VCL_INC_HELPWIN_HXX
+
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/timer.hxx>
+
+enum class QuickHelpFlags;
+struct ImplSVHelpData;
+
+/// A tooltip: adds tips to widgets in a floating / popup window.
+class HelpTextWindow final : public FloatingWindow
+{
+private:
+ tools::Rectangle maHelpArea; // If next Help for the same rectangle w/ same text, then keep window
+
+ tools::Rectangle maTextRect; // For wrapped text in QuickHelp
+
+ OUString maHelpText;
+
+ Timer maShowTimer;
+ Timer maHideTimer;
+
+ sal_uInt16 mnHelpWinStyle;
+ QuickHelpFlags mnStyle;
+
+private:
+ DECL_LINK( TimerHdl, Timer*, void );
+
+ virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) override;
+ virtual void RequestHelp( const HelpEvent& rHEvt ) override;
+ virtual void ApplySettings(vcl::RenderContext& rRenderContext) override;
+
+ virtual OUString GetText() const override;
+ void ImplShow();
+
+ virtual void dispose() override;
+public:
+ HelpTextWindow( vcl::Window* pParent, const OUString& rText, sal_uInt16 nHelpWinStyle, QuickHelpFlags nStyle );
+ virtual ~HelpTextWindow() override;
+
+ const OUString& GetHelpText() const { return maHelpText; }
+ void SetHelpText( const OUString& rHelpText );
+ sal_uInt16 GetWinStyle() const { return mnHelpWinStyle; }
+ QuickHelpFlags GetStyle() const { return mnStyle; }
+
+ // only remember:
+ void SetHelpArea( const tools::Rectangle& rRect ) { maHelpArea = rRect; }
+
+ void ShowHelp(bool bNoDelay);
+
+ Size CalcOutSize() const;
+ const tools::Rectangle& GetHelpArea() const { return maHelpArea; }
+
+ void ResetHideTimer();
+};
+
+void ImplShowHelpWindow( vcl::Window* pParent, sal_uInt16 nHelpWinStyle, QuickHelpFlags nStyle,
+ const OUString& rHelpText,
+ const Point& rScreenPos, const tools::Rectangle& rHelpArea );
+VCL_DLLPUBLIC void ImplDestroyHelpWindow( bool bUpdateHideTime );
+void ImplDestroyHelpWindow(ImplSVHelpData& rHelpData, bool bUpdateHideTime);
+void ImplSetHelpWindowPos( vcl::Window* pHelpWindow, sal_uInt16 nHelpWinStyle, QuickHelpFlags nStyle,
+ const Point& rPos, const tools::Rectangle& rHelpArea );
+
+#endif // INCLUDED_VCL_INC_HELPWIN_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/hyperlabel.hxx b/vcl/inc/hyperlabel.hxx
new file mode 100644
index 0000000000..6415742616
--- /dev/null
+++ b/vcl/inc/hyperlabel.hxx
@@ -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 .
+ */
+#ifndef INCLUDED_VCL_HYPERLABEL_HXX
+#define INCLUDED_VCL_HYPERLABEL_HXX
+
+#include <vcl/toolkit/fixed.hxx>
+
+namespace vcl
+{
+ class HyperLabel final : public FixedText
+ {
+ Link<HyperLabel*,void> maClickHdl;
+
+ virtual void MouseMove( const MouseEvent& rMEvt ) override;
+ virtual void MouseButtonDown( const MouseEvent& rMEvt ) override;
+ virtual void GetFocus() override;
+ virtual void LoseFocus() override;
+
+ void implInit();
+
+ using FixedText::CalcMinimumSize;
+
+ public:
+ HyperLabel( vcl::Window* _pParent, WinBits _nWinStyle );
+ virtual ~HyperLabel( ) override;
+
+ virtual void DataChanged( const DataChangedEvent& rDCEvt ) override;
+ virtual void ApplySettings(vcl::RenderContext& rRenderContext) override;
+
+ void SetID( sal_Int16 ID );
+ sal_Int16 GetID() const;
+
+ void SetIndex( sal_Int32 Index );
+ sal_Int32 GetIndex() const;
+
+ void SetLabel( const OUString& _rText );
+
+ void ToggleBackgroundColor( const Color& _rGBColor );
+ void SetInteractive( bool _bInteractive );
+
+ void SetClickHdl( const Link<HyperLabel*,void>& rLink ) { maClickHdl = rLink; }
+
+ Size const & CalcMinimumSize( tools::Long nMaxWidth );
+ private:
+ sal_Int16 ID;
+ sal_Int32 Index;
+ bool bInteractive;
+ Size m_aMinSize;
+ bool m_bHyperMode;
+ };
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/iconview.hxx b/vcl/inc/iconview.hxx
new file mode 100644
index 0000000000..54c2681a9e
--- /dev/null
+++ b/vcl/inc/iconview.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_SVTOOLS_ICONVIEW_HXX
+#define INCLUDED_SVTOOLS_ICONVIEW_HXX
+
+#include <tools/json_writer.hxx>
+#include <vcl/toolkit/treelistbox.hxx>
+
+class IconView final : public SvTreeListBox
+{
+public:
+ IconView(vcl::Window* pParent, WinBits nBits);
+
+ Size GetEntrySize(const SvTreeListEntry&) const;
+
+ virtual void Resize() override;
+
+ virtual tools::Rectangle GetFocusRect(const SvTreeListEntry*, tools::Long) override;
+
+ void PaintEntry(SvTreeListEntry&, tools::Long nX, tools::Long nY,
+ vcl::RenderContext& rRenderContext);
+
+ virtual css::uno::Reference<css::accessibility::XAccessible> CreateAccessible() override;
+
+ virtual OUString GetEntryAccessibleDescription(SvTreeListEntry* pEntry) const override;
+ void SetEntryAccessibleDescriptionHdl(const Link<SvTreeListEntry*, OUString>& rLink)
+ {
+ maEntryAccessibleDescriptionHdl = rLink;
+ }
+
+ virtual FactoryFunction GetUITestFactory() const override;
+ virtual void DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) override;
+
+ typedef std::tuple<tools::JsonWriter&, SvTreeListEntry*, std::string_view> json_prop_query;
+
+ void SetDumpElemToPropertyTreeHdl(const Link<const json_prop_query&, bool>& rLink)
+ {
+ maDumpElemToPropertyTreeHdl = rLink;
+ }
+
+protected:
+ virtual void CalcEntryHeight(SvTreeListEntry const* pEntry) override;
+
+private:
+ Link<SvTreeListEntry*, OUString> maEntryAccessibleDescriptionHdl;
+ Link<const json_prop_query&, bool> maDumpElemToPropertyTreeHdl;
+ void DumpEntryAndSiblings(tools::JsonWriter& rJsonWriter, SvTreeListEntry* pEntry);
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/image.h b/vcl/inc/image.h
new file mode 100644
index 0000000000..cb75b45b83
--- /dev/null
+++ b/vcl/inc/image.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_IMAGE_H
+#define INCLUDED_VCL_INC_IMAGE_H
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/gdimtf.hxx>
+
+class SalGraphics;
+
+class ImplImage
+{
+private:
+ BitmapChecksum maBitmapChecksum;
+ /// if non-empty: cached original size of maStockName else Size of maBitmap
+ Size maSizePixel;
+ /// If set - defines the bitmap via images.zip*
+ OUString maStockName;
+ /// rare case of dynamically created Image contents
+ std::unique_ptr<GDIMetaFile> mxMetaFile;
+
+ /// Original bitmap - or cache of a potentially scaled bitmap
+ BitmapEx maBitmapEx;
+ BitmapEx maDisabledBitmapEx;
+
+ bool loadStockAtScale(SalGraphics* pGraphics, BitmapEx &rBitmapEx);
+
+public:
+ ImplImage(const BitmapEx& rBitmapEx);
+ ImplImage(const GDIMetaFile& rMetaFile);
+ ImplImage(OUString aStockName);
+
+ bool isStock() const
+ {
+ return maStockName.getLength() > 0;
+ }
+
+ const OUString & getStock() const
+ {
+ return maStockName;
+ }
+
+ /// get size in co-ordinates not scaled for HiDPI
+ Size getSizePixel();
+ /// Legacy - the original bitmap
+ BitmapEx const & getBitmapEx(bool bDisabled = false);
+ /// Taking account of HiDPI scaling
+ BitmapEx const & getBitmapExForHiDPI(bool bDisabled, SalGraphics* pGraphics);
+
+ bool isEqual(const ImplImage &ref) const;
+ bool isSizeEmpty() const
+ {
+ return maSizePixel == Size();
+ }
+};
+
+#endif // INCLUDED_VCL_INC_IMAGE_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/imagerepository.hxx b/vcl/inc/imagerepository.hxx
new file mode 100644
index 0000000000..5e1b87595b
--- /dev/null
+++ b/vcl/inc/imagerepository.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 .
+ */
+
+#ifndef INCLUDED_VCL_IMAGEREPOSITORY_HXX
+#define INCLUDED_VCL_IMAGEREPOSITORY_HXX
+
+#include <rtl/ustring.hxx>
+
+class BitmapEx;
+
+
+namespace vcl
+{
+
+
+ //= ImageRepository
+
+ // provides access to the application's image repository (image.zip)
+ class ImageRepository
+ {
+ public:
+ /** loads an image from the application's image repository
+ @param _rName
+ the name of the image to load.
+ @param _out_rImage
+ will take the image upon successful return.
+ @return
+ whether or not the image could be loaded successfully.
+ */
+ static bool loadImage(
+ const OUString& _rName,
+ BitmapEx& _out_rImage
+ );
+ };
+
+
+} // namespace vcl
+
+
+#endif // INCLUDED_VCL_IMAGEREPOSITORY_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/impdel.hxx b/vcl/inc/impdel.hxx
new file mode 100644
index 0000000000..b387c34a09
--- /dev/null
+++ b/vcl/inc/impdel.hxx
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_IMPDEL_HXX
+#define INCLUDED_VCL_IMPDEL_HXX
+
+#include <algorithm>
+#include <vector>
+
+namespace vcl
+{
+
+class DeletionListener;
+
+class DeletionNotifier
+{
+ std::vector< DeletionListener* > m_aListeners;
+ protected:
+ DeletionNotifier() {}
+
+ ~DeletionNotifier()
+ { notifyDelete(); }
+
+ inline void notifyDelete();
+
+ public:
+ void addDel( DeletionListener* pListener )
+ { m_aListeners.push_back( pListener ); }
+
+ void removeDel( DeletionListener* pListener )
+ { std::erase(m_aListeners, pListener); }
+};
+
+class DeletionListener
+{
+ DeletionNotifier* m_pNotifier;
+ public:
+ DeletionListener( DeletionNotifier* pNotifier )
+ : m_pNotifier( pNotifier )
+ {
+ if( m_pNotifier )
+ m_pNotifier->addDel( this );
+ }
+ ~DeletionListener()
+ {
+ if( m_pNotifier )
+ m_pNotifier->removeDel( this );
+ }
+ void deleted() { m_pNotifier = nullptr; }
+ bool isDeleted() const { return (m_pNotifier == nullptr); }
+};
+
+inline void DeletionNotifier::notifyDelete()
+{
+ for( auto& rListener : m_aListeners )
+ rListener->deleted();
+
+ m_aListeners.clear();
+}
+
+} // namespace vcl
+
+#endif // INCLUDED_VCL_IMPDEL_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/impfont.hxx b/vcl/inc/impfont.hxx
new file mode 100644
index 0000000000..1e697b9ee3
--- /dev/null
+++ b/vcl/inc/impfont.hxx
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <rtl/ustring.hxx>
+#include <tools/color.hxx>
+#include <tools/fontenum.hxx>
+#include <tools/gen.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <vcl/fntstyle.hxx>
+#include <vcl/font.hxx>
+
+/* The following class is extraordinarily similar to FontAttributes. */
+
+class ImplFont
+{
+public:
+ explicit ImplFont();
+ explicit ImplFont( const ImplFont& );
+
+ // device independent font functions
+ const OUString& GetFamilyName() const { return maFamilyName; }
+ FontFamily GetFamilyType() { if(meFamily==FAMILY_DONTKNOW) AskConfig(); return meFamily; }
+ const OUString& GetStyleName() const { return maStyleName; }
+
+ FontWeight GetWeight() { if(meWeight==WEIGHT_DONTKNOW) AskConfig(); return meWeight; }
+ FontItalic GetItalic() { if(meItalic==ITALIC_DONTKNOW) AskConfig(); return meItalic; }
+ FontPitch GetPitch() { if(mePitch==PITCH_DONTKNOW) AskConfig(); return mePitch; }
+ FontWidth GetWidthType() { if(meWidthType==WIDTH_DONTKNOW) AskConfig(); return meWidthType; }
+ TextAlign GetAlignment() const { return meAlign; }
+ rtl_TextEncoding GetCharSet() const { return meCharSet; }
+ const Size& GetFontSize() const { return maAverageFontSize; }
+
+ void SetFamilyName( const OUString& sFamilyName ) { maFamilyName = sFamilyName; }
+ void SetStyleName( const OUString& sStyleName ) { maStyleName = sStyleName; }
+ void SetFamilyType( const FontFamily eFontFamily ) { meFamily = eFontFamily; }
+
+ void SetPitch( const FontPitch ePitch ) { mePitch = ePitch; }
+ void SetItalic( const FontItalic eItalic ) { meItalic = eItalic; }
+ void SetWeight( const FontWeight eWeight ) { meWeight = eWeight; }
+ void SetWidthType( const FontWidth eWidthType ) { meWidthType = eWidthType; }
+ void SetAlignment( const TextAlign eAlignment ) { meAlign = eAlignment; }
+ void SetCharSet( const rtl_TextEncoding eCharSet ) { meCharSet = eCharSet; }
+ void SetFontSize( const Size& rSize )
+ {
+ if(rSize.Height() != maAverageFontSize.Height())
+ {
+ // reset evtl. buffered calculated AverageFontSize, it depends
+ // on Font::Height
+ mnCalculatedAverageFontWidth = 0;
+ }
+ maAverageFontSize = rSize;
+ }
+
+ // straight properties, no getting them from AskConfig()
+ FontFamily GetFamilyTypeNoAsk() const { return meFamily; }
+ FontWeight GetWeightNoAsk() const { return meWeight; }
+ FontItalic GetItalicNoAsk() const { return meItalic; }
+ FontPitch GetPitchNoAsk() const { return mePitch; }
+ FontWidth GetWidthTypeNoAsk() const { return meWidthType; }
+
+ // device dependent functions
+ int GetQuality() const { return mnQuality; }
+
+ void SetQuality( int nQuality ) { mnQuality = nQuality; }
+ void IncreaseQualityBy( int nQualityAmount ) { mnQuality += nQualityAmount; }
+ void DecreaseQualityBy( int nQualityAmount ) { mnQuality -= nQualityAmount; }
+
+ tools::Long GetCalculatedAverageFontWidth() const { return mnCalculatedAverageFontWidth; }
+ void SetCalculatedAverageFontWidth(tools::Long nNew) { mnCalculatedAverageFontWidth = nNew; }
+
+ bool operator==( const ImplFont& ) const;
+ bool EqualIgnoreColor( const ImplFont& ) const;
+
+ size_t GetHashValue() const;
+ size_t GetHashValueIgnoreColor() const;
+
+private:
+ friend class vcl::Font;
+ friend SvStream& ReadImplFont( SvStream& rIStm, ImplFont&, tools::Long& );
+ friend SvStream& WriteImplFont( SvStream& rOStm, const ImplFont&, tools::Long );
+
+ void AskConfig();
+
+ // Device independent variables
+ OUString maFamilyName;
+ OUString maStyleName;
+ FontWeight meWeight;
+ FontFamily meFamily;
+ FontPitch mePitch;
+ FontWidth meWidthType;
+ FontItalic meItalic;
+ TextAlign meAlign;
+ FontLineStyle meUnderline;
+ FontLineStyle meOverline;
+ FontStrikeout meStrikeout;
+ FontRelief meRelief;
+ FontEmphasisMark meEmphasisMark;
+ FontKerning meKerning;
+ short mnSpacing;
+ Size maAverageFontSize;
+ rtl_TextEncoding meCharSet;
+
+ LanguageTag maLanguageTag;
+ LanguageTag maCJKLanguageTag;
+
+ // Flags - device independent
+ bool mbOutline:1,
+ mbConfigLookup:1, // config lookup should only be done once
+ mbShadow:1,
+ mbVertical:1,
+ mbTransparent:1; // compatibility, now on output device
+
+ // deprecated variables - device independent
+ Color maColor; // compatibility, now on output device
+ Color maFillColor; // compatibility, now on output device
+
+ // Device dependent variables
+ bool mbWordLine:1;
+
+ // TODO: metric data, should be migrated to ImplFontMetric
+ Degree10 mnOrientation;
+
+ int mnQuality;
+
+ tools::Long mnCalculatedAverageFontWidth;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/impfontcache.hxx b/vcl/inc/impfontcache.hxx
new file mode 100644
index 0000000000..5ea19b05d9
--- /dev/null
+++ b/vcl/inc/impfontcache.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <rtl/ref.hxx>
+#include <o3tl/lru_map.hxx>
+#include <o3tl/hash_combine.hxx>
+#include <tools/gen.hxx>
+
+#include "font/FontSelectPattern.hxx"
+#include "glyphid.hxx"
+
+class Size;
+namespace vcl { class Font; }
+namespace vcl::font { class PhysicalFontCollection; }
+
+// TODO: closely couple with PhysicalFontCollection
+
+struct GlyphBoundRectCacheKey
+{
+ const LogicalFontInstance* m_pFont;
+ const sal_GlyphId m_nId;
+
+ GlyphBoundRectCacheKey(const LogicalFontInstance* pFont, sal_GlyphId nID)
+ : m_pFont(pFont), m_nId(nID)
+ {}
+
+ bool operator==(GlyphBoundRectCacheKey const& aOther) const
+ { return m_pFont == aOther.m_pFont && m_nId == aOther.m_nId; }
+};
+
+struct GlyphBoundRectCacheHash
+{
+ std::size_t operator()(GlyphBoundRectCacheKey const& aCache) const
+ {
+ std::size_t seed = 0;
+ o3tl::hash_combine(seed, aCache.m_pFont);
+ o3tl::hash_combine(seed, aCache.m_nId);
+ return seed;
+ }
+};
+
+typedef o3tl::lru_map<GlyphBoundRectCacheKey, tools::Rectangle,
+ GlyphBoundRectCacheHash> GlyphBoundRectCache;
+
+class ImplFontCache
+{
+private:
+ // cache of recently used font instances
+ struct IFSD_Equal { bool operator()( const vcl::font::FontSelectPattern&, const vcl::font::FontSelectPattern& ) const; };
+ struct IFSD_Hash { size_t operator()( const vcl::font::FontSelectPattern& ) const; };
+ typedef o3tl::lru_map<vcl::font::FontSelectPattern, rtl::Reference<LogicalFontInstance>, IFSD_Hash, IFSD_Equal> FontInstanceList;
+
+ LogicalFontInstance* mpLastHitCacheEntry; ///< keeps the last hit cache entry
+ FontInstanceList maFontInstanceList;
+ GlyphBoundRectCache m_aBoundRectCache;
+
+ rtl::Reference<LogicalFontInstance> GetFontInstance(vcl::font::PhysicalFontCollection const*, vcl::font::FontSelectPattern&);
+
+public:
+ ImplFontCache();
+ ~ImplFontCache();
+
+ rtl::Reference<LogicalFontInstance> GetFontInstance(vcl::font::PhysicalFontCollection const *,
+ const vcl::Font&, const Size& rPixelSize, float fExactHeight, bool bNonAntialias = false);
+ rtl::Reference<LogicalFontInstance> GetGlyphFallbackFont( vcl::font::PhysicalFontCollection const *, vcl::font::FontSelectPattern&,
+ LogicalFontInstance* pLogicalFont,
+ int nFallbackLevel, OUString& rMissingCodes );
+
+ bool GetCachedGlyphBoundRect(const LogicalFontInstance *, sal_GlyphId, tools::Rectangle &);
+ void CacheGlyphBoundRect(const LogicalFontInstance *, sal_GlyphId, tools::Rectangle &);
+
+ void Invalidate();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/impfontcharmap.hxx b/vcl/inc/impfontcharmap.hxx
new file mode 100644
index 0000000000..59f9f3baa0
--- /dev/null
+++ b/vcl/inc/impfontcharmap.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_IMPFONTCHARMAP_HXX
+#define INCLUDED_VCL_INC_IMPFONTCHARMAP_HXX
+
+#include <tools/ref.hxx>
+#include <vcl/dllapi.h>
+#include <vector>
+
+class ImplFontCharMap;
+typedef tools::SvRef<ImplFontCharMap> ImplFontCharMapRef;
+
+class ImplFontCharMap final : public SvRefBase
+{
+public:
+ explicit ImplFontCharMap(bool bMicrosoftSymbolMap,
+ std::vector<sal_uInt32> aRangeCodes);
+ virtual ~ImplFontCharMap() override;
+
+private:
+ friend class FontCharMap;
+
+ ImplFontCharMap( const ImplFontCharMap& ) = delete;
+ void operator=( const ImplFontCharMap& ) = delete;
+
+ static ImplFontCharMapRef const & getDefaultMap(bool bMicrosoftSymbolMap = false);
+ bool isDefaultMap() const;
+
+private:
+ std::vector<sal_uInt32> maRangeCodes; // pairs of StartCode/(EndCode+1)
+ int mnCharCount; // covered codepoints
+ const bool m_bMicrosoftSymbolMap;
+};
+
+bool VCL_DLLPUBLIC HasMicrosoftSymbolCmap(const unsigned char* pRawData, int nRawLength);
+
+#endif // INCLUDED_VCL_INC_IMPFONTCHARMAP_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/impglyphitem.hxx b/vcl/inc/impglyphitem.hxx
new file mode 100644
index 0000000000..1fa8454e2e
--- /dev/null
+++ b/vcl/inc/impglyphitem.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_IMPGLYPHITEM_HXX
+#define INCLUDED_VCL_IMPGLYPHITEM_HXX
+
+#include <o3tl/typed_flags_set.hxx>
+#include <tools/gen.hxx>
+#include <vcl/dllapi.h>
+#include <vcl/rendercontext/SalLayoutFlags.hxx>
+#include <rtl/math.hxx>
+#include <vector>
+
+#include "font/LogicalFontInstance.hxx"
+#include "glyphid.hxx"
+
+enum class GlyphItemFlags : sal_uInt8
+{
+ NONE = 0,
+ IS_IN_CLUSTER = 0x01,
+ IS_RTL_GLYPH = 0x02,
+ IS_VERTICAL = 0x04,
+ IS_SPACING = 0x08,
+ IS_DROPPED = 0x10,
+ IS_CLUSTER_START = 0x20,
+ IS_UNSAFE_TO_BREAK = 0x40, // HB_GLYPH_FLAG_UNSAFE_TO_BREAK from harfbuzz
+ IS_SAFE_TO_INSERT_KASHIDA = 0x80 // HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL from harfbuzz
+};
+namespace o3tl
+{
+template <> struct typed_flags<GlyphItemFlags> : is_typed_flags<GlyphItemFlags, 0xff>
+{
+};
+};
+
+class VCL_DLLPUBLIC GlyphItem
+{
+ basegfx::B2DPoint m_aLinearPos; // absolute position of non rotated string
+ double m_nOrigWidth; // original glyph width
+ sal_Int32 m_nCharPos; // index in string
+ double m_nXOffset;
+ double m_nYOffset;
+ double m_nNewWidth; // width after adjustments
+ sal_GlyphId m_aGlyphId;
+ GlyphItemFlags m_nFlags;
+ sal_Int8 m_nCharCount; // number of characters making up this glyph
+
+public:
+ GlyphItem(int nCharPos, int nCharCount, sal_GlyphId aGlyphId,
+ const basegfx::B2DPoint& rLinearPos, GlyphItemFlags nFlags, double nOrigWidth,
+ double nXOffset, double nYOffset)
+ : m_aLinearPos(rLinearPos)
+ , m_nOrigWidth(nOrigWidth)
+ , m_nCharPos(nCharPos)
+ , m_nXOffset(nXOffset)
+ , m_nYOffset(nYOffset)
+ , m_nNewWidth(nOrigWidth)
+ , m_aGlyphId(aGlyphId)
+ , m_nFlags(nFlags)
+ , m_nCharCount(nCharCount)
+ {
+ }
+
+ bool IsInCluster() const { return bool(m_nFlags & GlyphItemFlags::IS_IN_CLUSTER); }
+ bool IsRTLGlyph() const { return bool(m_nFlags & GlyphItemFlags::IS_RTL_GLYPH); }
+ bool IsVertical() const { return bool(m_nFlags & GlyphItemFlags::IS_VERTICAL); }
+ bool IsSpacing() const { return bool(m_nFlags & GlyphItemFlags::IS_SPACING); }
+ bool IsDropped() const { return bool(m_nFlags & GlyphItemFlags::IS_DROPPED); }
+ bool IsClusterStart() const { return bool(m_nFlags & GlyphItemFlags::IS_CLUSTER_START); }
+ bool IsUnsafeToBreak() const { return bool(m_nFlags & GlyphItemFlags::IS_UNSAFE_TO_BREAK); }
+ bool IsSafeToInsertKashida() const
+ {
+ return bool(m_nFlags & GlyphItemFlags::IS_SAFE_TO_INSERT_KASHIDA);
+ }
+
+ inline bool GetGlyphBoundRect(const LogicalFontInstance*, tools::Rectangle&) const;
+ inline bool GetGlyphOutline(const LogicalFontInstance*, basegfx::B2DPolyPolygon&) const;
+ inline void dropGlyph();
+
+ sal_GlyphId glyphId() const { return m_aGlyphId; }
+ int charCount() const { return m_nCharCount; }
+ double origWidth() const { return m_nOrigWidth; }
+ int charPos() const { return m_nCharPos; }
+ double xOffset() const { return m_nXOffset; }
+ double yOffset() const { return m_nYOffset; }
+ double newWidth() const { return m_nNewWidth; }
+ const basegfx::B2DPoint& linearPos() const { return m_aLinearPos; }
+
+ void setNewWidth(double width) { m_nNewWidth = width; }
+ void addNewWidth(double width) { m_nNewWidth += width; }
+ void setLinearPos(const basegfx::B2DPoint& point) { m_aLinearPos = point; }
+ void setLinearPosX(double x) { m_aLinearPos.setX(x); }
+ void adjustLinearPosX(double diff) { m_aLinearPos.adjustX(diff); }
+ bool isLayoutEquivalent(const GlyphItem& other) const
+ {
+ return rtl::math::approxEqual(m_aLinearPos.getX(), other.m_aLinearPos.getX(), 8)
+ && rtl::math::approxEqual(m_aLinearPos.getY(), other.m_aLinearPos.getY(), 8)
+ && m_nOrigWidth == other.m_nOrigWidth && m_nCharPos == other.m_nCharPos
+ && m_nXOffset == other.m_nXOffset && m_nYOffset == other.m_nYOffset
+ && m_nNewWidth == other.m_nNewWidth && m_aGlyphId == other.m_aGlyphId
+ && m_nCharCount == other.m_nCharCount
+ && (m_nFlags & ~GlyphItemFlags::IS_UNSAFE_TO_BREAK)
+ == (other.m_nFlags & ~GlyphItemFlags::IS_UNSAFE_TO_BREAK);
+ }
+};
+
+bool GlyphItem::GetGlyphBoundRect(const LogicalFontInstance* pFontInstance,
+ tools::Rectangle& rRect) const
+{
+ return pFontInstance->GetGlyphBoundRect(m_aGlyphId, rRect, IsVertical());
+}
+
+bool GlyphItem::GetGlyphOutline(const LogicalFontInstance* pFontInstance,
+ basegfx::B2DPolyPolygon& rPoly) const
+{
+ return pFontInstance->GetGlyphOutline(m_aGlyphId, rPoly, IsVertical());
+}
+
+void GlyphItem::dropGlyph()
+{
+ m_nCharPos = -1;
+ m_nFlags |= GlyphItemFlags::IS_DROPPED;
+}
+
+class SalLayoutGlyphsImpl : public std::vector<GlyphItem>
+{
+public:
+ SalLayoutGlyphsImpl(LogicalFontInstance& rFontInstance)
+ : m_rFontInstance(&rFontInstance)
+ {
+ }
+ SalLayoutGlyphsImpl* clone() const;
+ SalLayoutGlyphsImpl* cloneCharRange(sal_Int32 index, sal_Int32 length) const;
+ const rtl::Reference<LogicalFontInstance>& GetFont() const { return m_rFontInstance; }
+ bool IsValid() const;
+ void SetFlags(SalLayoutFlags flags) { mnFlags = flags; }
+ SalLayoutFlags GetFlags() const { return mnFlags; }
+#ifdef DBG_UTIL
+ bool isLayoutEquivalent(const SalLayoutGlyphsImpl* other) const;
+#endif
+
+private:
+ bool isSafeToBreak(const_iterator pos, bool rtl) const;
+ rtl::Reference<LogicalFontInstance> m_rFontInstance;
+ SalLayoutFlags mnFlags = SalLayoutFlags::NONE;
+};
+
+#endif // INCLUDED_VCL_IMPGLYPHITEM_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/impgraph.hxx b/vcl/inc/impgraph.hxx
new file mode 100644
index 0000000000..2e2b00640a
--- /dev/null
+++ b/vcl/inc/impgraph.hxx
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/dllapi.h>
+#include <vcl/GraphicExternalLink.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/graph.hxx>
+#include "graphic/Manager.hxx"
+#include "graphic/GraphicID.hxx"
+#include <optional>
+
+struct ImpSwapInfo
+{
+ MapMode maPrefMapMode;
+ Size maPrefSize;
+ Size maSizePixel;
+
+ bool mbIsAnimated;
+ bool mbIsEPS;
+ bool mbIsTransparent;
+ bool mbIsAlpha;
+
+ sal_uInt32 mnAnimationLoopCount;
+ sal_Int32 mnPageIndex;
+};
+
+class OutputDevice;
+class GfxLink;
+class ImpSwapFile;
+class GraphicConversionParameters;
+class ImpGraphic;
+namespace rtl { class OStringBuffer; }
+
+enum class GraphicContentType : sal_Int32
+{
+ Bitmap,
+ Animation,
+ Vector
+};
+
+class VCL_DLLPUBLIC ImpGraphic final
+{
+ friend class Graphic;
+ friend class GraphicID;
+ friend class vcl::graphic::Manager;
+
+private:
+
+ GDIMetaFile maMetaFile;
+ BitmapEx maBitmapEx;
+ /// If maBitmapEx is empty, this preferred size will be set on it when it gets initialized.
+ Size maExPrefSize;
+ ImpSwapInfo maSwapInfo;
+ std::unique_ptr<Animation> mpAnimation;
+ std::shared_ptr<GraphicReader> mpContext;
+ std::shared_ptr<ImpSwapFile> mpSwapFile;
+ std::shared_ptr<GfxLink> mpGfxLink;
+ GraphicType meType;
+ mutable sal_uLong mnSizeBytes;
+ bool mbSwapOut;
+ bool mbDummyContext;
+ std::shared_ptr<VectorGraphicData> maVectorGraphicData;
+ // cache checksum computation
+ mutable BitmapChecksum mnChecksum = 0;
+
+ std::optional<GraphicID> mxGraphicID;
+ GraphicExternalLink maGraphicExternalLink;
+
+ std::chrono::high_resolution_clock::time_point maLastUsed;
+ bool mbPrepared;
+
+public:
+ ImpGraphic();
+ ImpGraphic( const ImpGraphic& rImpGraphic );
+ ImpGraphic( ImpGraphic&& rImpGraphic ) noexcept;
+ ImpGraphic( GraphicExternalLink aExternalLink);
+ ImpGraphic(std::shared_ptr<GfxLink> xGfxLink, sal_Int32 nPageIndex = 0);
+ ImpGraphic( const BitmapEx& rBmpEx );
+ ImpGraphic(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr);
+ ImpGraphic( const Animation& rAnimation );
+ ImpGraphic( const GDIMetaFile& rMtf );
+ ~ImpGraphic();
+
+ void setPrepared(bool bAnimated, const Size* pSizeHint);
+
+private:
+
+ ImpGraphic& operator=( const ImpGraphic& rImpGraphic );
+ ImpGraphic& operator=( ImpGraphic&& rImpGraphic );
+ bool operator==( const ImpGraphic& rImpGraphic ) const;
+ bool operator!=( const ImpGraphic& rImpGraphic ) const { return !( *this == rImpGraphic ); }
+
+ OUString const & getOriginURL() const
+ {
+ return maGraphicExternalLink.msURL;
+ }
+
+ void setOriginURL(OUString const & rOriginURL)
+ {
+ maGraphicExternalLink.msURL = rOriginURL;
+ }
+
+ OString getUniqueID()
+ {
+ if (!mxGraphicID)
+ mxGraphicID.emplace(*this);
+ return mxGraphicID->getIDString();
+ }
+
+ void createSwapInfo();
+ void restoreFromSwapInfo();
+
+ void clearGraphics();
+ void clear();
+
+ GraphicType getType() const { return meType;}
+ void setDefaultType();
+ bool isSupportedGraphic() const;
+
+ bool isTransparent() const;
+ bool isAlpha() const;
+ bool isAnimated() const;
+ bool isEPS() const;
+
+ bool isAvailable() const;
+ bool makeAvailable();
+
+ Bitmap getBitmap(const GraphicConversionParameters& rParameters) const;
+ BitmapEx getBitmapEx(const GraphicConversionParameters& rParameters) const;
+ /// Gives direct access to the contained BitmapEx.
+ const BitmapEx& getBitmapExRef() const;
+ Animation getAnimation() const;
+ const GDIMetaFile& getGDIMetaFile() const;
+
+ Size getSizePixel() const;
+
+ Size getPrefSize() const;
+ void setPrefSize( const Size& rPrefSize );
+
+ MapMode getPrefMapMode() const;
+ void setPrefMapMode( const MapMode& rPrefMapMode );
+
+ sal_uLong getSizeBytes() const;
+
+ void draw(OutputDevice& rOutDev, const Point& rDestPt) const;
+ void draw(OutputDevice& rOutDev, const Point& rDestPt,
+ const Size& rDestSize) const;
+
+ void startAnimation(OutputDevice& rOutDev,
+ const Point& rDestPt,
+ const Size& rDestSize,
+ tools::Long nRendererId,
+ OutputDevice* pFirstFrameOutDev);
+ void stopAnimation( const OutputDevice* pOutputDevice,
+ tools::Long nRendererId );
+
+ void setAnimationNotifyHdl( const Link<Animation*,void>& rLink );
+ Link<Animation*,void> getAnimationNotifyHdl() const;
+
+ sal_uInt32 getAnimationLoopCount() const;
+
+private:
+ // swapping methods
+ bool swapInFromStream(SvStream& rStream);
+ bool swapInGraphic(SvStream& rStream);
+
+ bool swapInContent(SvStream& rStream);
+ bool swapOutContent(SvStream& rStream);
+ bool swapOutGraphic(SvStream& rStream);
+ // end swapping
+
+ std::shared_ptr<GraphicReader>& getContext() { return mpContext;}
+ void setContext( const std::shared_ptr<GraphicReader>& pReader );
+ void setDummyContext( bool value ) { mbDummyContext = value; }
+ bool isDummyContext() const { return mbDummyContext; }
+ void setGfxLink( const std::shared_ptr<GfxLink>& );
+ const std::shared_ptr<GfxLink> & getSharedGfxLink() const;
+ GfxLink getGfxLink() const;
+ bool isGfxLink() const;
+
+ BitmapChecksum getChecksum() const;
+
+ const std::shared_ptr<VectorGraphicData>& getVectorGraphicData() const;
+
+ /// Gets the bitmap replacement for a vector graphic.
+ BitmapEx getVectorGraphicReplacement() const;
+
+ bool ensureAvailable () const;
+
+ sal_Int32 getPageNumber() const;
+
+ // Set the pref size, but don't force swap-in
+ void setValuesForPrefSize(const Size& rPrefSize);
+ // Set the pref map mode, but don't force swap-in
+ void setValuesForPrefMapMod(const MapMode& rPrefMapMode);
+
+public:
+ void resetChecksum() { mnChecksum = 0; }
+ bool swapIn();
+ bool swapOut();
+ bool isSwappedOut() const { return mbSwapOut; }
+ SvStream* getSwapFileStream() const;
+ // public only because of use in GraphicFilter
+ void updateFromLoadedGraphic(const ImpGraphic* graphic);
+ void dumpState(rtl::OStringBuffer &rState);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/implimagetree.hxx b/vcl/inc/implimagetree.hxx
new file mode 100644
index 0000000000..beecb233e1
--- /dev/null
+++ b/vcl/inc/implimagetree.hxx
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_IMPLIMAGETREE_HXX
+#define INCLUDED_VCL_INC_IMPLIMAGETREE_HXX
+
+#include <sal/config.h>
+
+#include <memory>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <rtl/ustring.hxx>
+#include <vcl/bitmapex.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <vcl/ImageTree.hxx>
+
+namespace com::sun::star::container {
+ class XNameAccess;
+}
+
+namespace com::sun::star::io {
+ class XInputStream;
+}
+
+struct ImageRequestParameters
+{
+ OUString msName;
+ OUString msStyle;
+ BitmapEx& mrBitmap;
+ bool mbLocalized;
+ ImageLoadFlags meFlags;
+ bool mbWriteImageToCache;
+ sal_Int32 mnScalePercentage;
+
+ ImageRequestParameters(OUString aName, OUString aStyle, BitmapEx& rBitmap, bool bLocalized,
+ ImageLoadFlags eFlags, sal_Int32 nScalePercentage)
+ : msName(std::move(aName))
+ , msStyle(std::move(aStyle))
+ , mrBitmap(rBitmap)
+ , mbLocalized(bLocalized)
+ , meFlags(eFlags)
+ , mbWriteImageToCache(false)
+ , mnScalePercentage(nScalePercentage)
+ {}
+
+ bool convertToDarkTheme();
+ sal_Int32 scalePercentage();
+};
+
+class ImplImageTree
+{
+public:
+ ImplImageTree();
+ ~ImplImageTree();
+
+ OUString getImageUrl(
+ OUString const & name, OUString const & style, OUString const & lang);
+
+ css::uno::Reference<css::io::XInputStream> getImageXInputStream(OUString const & rName,
+ OUString const & rStyle, OUString const & rLang);
+
+ std::shared_ptr<SvMemoryStream> getImageStream(
+ OUString const & rName, OUString const & rStyle, OUString const & rLang);
+
+ bool loadImage(
+ OUString const & name, OUString const & style,
+ BitmapEx & bitmap, bool localized,
+ const ImageLoadFlags eFlags,
+ sal_Int32 nScalePercentage = -1);
+
+ /** a crude form of life cycle control (called from DeInitVCL; otherwise,
+ * if the ImplImageTree singleton were destroyed during exit that would
+ * be too late for the destructors of the bitmaps in maIconCache)*/
+ void shutdown();
+
+ css::uno::Reference< css::container::XNameAccess > const & getNameAccess();
+
+private:
+ ImplImageTree(const ImplImageTree&) = delete;
+ ImplImageTree& operator=(const ImplImageTree&) = delete;
+
+ typedef std::unordered_map<OUString, std::pair<bool,BitmapEx>> IconCache;
+ typedef std::unordered_map<sal_Int32, IconCache> ScaledIconCache;
+ typedef std::unordered_map<OUString, OUString> IconLinkHash;
+
+ struct IconSet
+ {
+ OUString maURL;
+ css::uno::Reference<css::container::XNameAccess> maNameAccess;
+ ScaledIconCache maScaledIconCaches;
+ IconLinkHash maLinkHash;
+
+ IconSet()
+ {
+ maLinkHash.reserve(50);
+ }
+
+ IconSet(OUString aURL)
+ : maURL(std::move(aURL))
+ {
+ maLinkHash.reserve(50);
+ }
+ };
+
+ /// Remember all the (used) icon styles and individual icons in them.
+ /// Map between the theme name(s) and the content.
+ std::unordered_map<OUString, IconSet> maIconSets;
+
+ /// Style used for the current operations; switches switch several times during fallback search.
+ OUString maCurrentStyle;
+
+ IconSet& getCurrentIconSet()
+ {
+ return maIconSets[maCurrentStyle];
+ }
+
+ bool doLoadImage(ImageRequestParameters& rParameters);
+
+ std::vector<OUString> getPaths(OUString const & name, LanguageTag const & rLanguageTag);
+
+ bool checkPathAccess();
+
+ void setStyle(OUString const & rStyle);
+
+ void createStyle();
+
+ IconCache &getIconCache(const ImageRequestParameters& rParameters);
+
+ bool iconCacheLookup(ImageRequestParameters& rParameters);
+
+ bool findImage(std::vector<OUString> const & rPaths, ImageRequestParameters& rParameters);
+
+ void loadImageLinks();
+
+ void parseLinkFile(std::shared_ptr<SvStream> const & aStream);
+
+ /// Return name of a real .png according to links.txt.
+ OUString const & getRealImageName(OUString const & rName);
+
+
+ /** Return name of the fallback style for the provided one.
+
+ Must not be cyclic :-) The last theme in the chain returns an empty string.
+ */
+ static OUString fallbackStyle(std::u16string_view rStyle);
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/ios/iosinst.hxx b/vcl/inc/ios/iosinst.hxx
new file mode 100644
index 0000000000..63182bcd0c
--- /dev/null
+++ b/vcl/inc/ios/iosinst.hxx
@@ -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_INC_IOS_IOSINST_HXX
+#define INCLUDED_VCL_INC_IOS_IOSINST_HXX
+
+#include <premac.h>
+#include <CoreGraphics/CoreGraphics.h>
+#include <postmac.h>
+
+#include <tools/link.hxx>
+
+#include "headless/svpinst.hxx"
+#include "headless/svpframe.hxx"
+
+class IosSalFrame;
+class SystemFontList;
+
+class IosSalInstance : public SvpSalInstance
+{
+public:
+ IosSalInstance(std::unique_ptr<SalYieldMutex> pMutex);
+ virtual ~IosSalInstance();
+ static IosSalInstance* getInstance();
+
+ SalSystem* CreateSalSystem() override;
+
+ css::uno::Reference<css::uno::XInterface>
+ CreateClipboard(const css::uno::Sequence<css::uno::Any>& i_rArguments) override;
+
+ void GetWorkArea(AbsoluteScreenPixelRectangle& rRect);
+ SalFrame* CreateFrame(SalFrame* pParent, SalFrameStyleFlags nStyle) override;
+ SalFrame* CreateChildFrame(SystemParentData* pParent, SalFrameStyleFlags nStyle) override;
+};
+
+class SalData
+{
+public:
+ std::unique_ptr<SystemFontList> mpFontList;
+ CGColorSpaceRef mxRGBSpace;
+ CGColorSpaceRef mxGraySpace;
+
+ static void ensureThreadAutoreleasePool(){};
+
+ explicit SalData();
+ virtual ~SalData();
+};
+
+#endif // INCLUDED_VCL_INC_IOS_IOSINST_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/jobdata.hxx b/vcl/inc/jobdata.hxx
new file mode 100644
index 0000000000..46110057a8
--- /dev/null
+++ b/vcl/inc/jobdata.hxx
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_JOBDATA_HXX
+#define INCLUDED_VCL_JOBDATA_HXX
+
+#include "ppdparser.hxx"
+
+namespace psp {
+
+enum class orientation {
+ Portrait,
+ Landscape
+};
+
+struct VCL_DLLPUBLIC JobData
+{
+ int m_nCopies;
+ bool m_bCollate;
+ int m_nLeftMarginAdjust;
+ int m_nRightMarginAdjust;
+ int m_nTopMarginAdjust;
+ int m_nBottomMarginAdjust;
+ // user overrides for PPD
+ int m_nColorDepth;
+ int m_nColorDevice; // 0: no override, -1 grey scale, +1 color
+ orientation m_eOrientation;
+ OUString m_aPrinterName;
+ bool m_bPapersizeFromSetup;
+ const PPDParser* m_pParser;
+ PPDContext m_aContext;
+
+ JobData() :
+ m_nCopies( 1 ),
+ m_bCollate(false),
+ m_nLeftMarginAdjust( 0 ),
+ m_nRightMarginAdjust( 0 ),
+ m_nTopMarginAdjust( 0 ),
+ m_nBottomMarginAdjust( 0 ),
+ m_nColorDepth( 24 ),
+ m_nColorDevice( 0 ),
+ m_eOrientation( orientation::Portrait ),
+ m_bPapersizeFromSetup( false ),
+ m_pParser( nullptr ) {}
+
+ JobData& operator=(const psp::JobData& rRight);
+
+ JobData( const JobData& rData ) { *this = rData; }
+
+ void setCollate( bool bCollate );
+ void setPaper( int nWidth, int nHeight ); // dimensions in pt
+ void setPaperBin( int nPaperBin );
+
+ // creates a new buffer using new
+ // it is up to the user to delete it again
+ bool getStreamBuffer( std::unique_ptr<sal_uInt8[]>& pData, sal_uInt32& bytes );
+ static bool constructFromStreamBuffer( const void* pData, sal_uInt32 bytes, JobData& rJobData );
+};
+
+} // namespace
+
+
+#endif // PSPRINT_JOBDATA_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/jobset.h b/vcl/inc/jobset.h
new file mode 100644
index 0000000000..7c0d0b55a4
--- /dev/null
+++ b/vcl/inc/jobset.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_JOBSET_H
+#define INCLUDED_VCL_INC_JOBSET_H
+
+#include <rtl/ustring.hxx>
+#include <i18nutil/paper.hxx>
+#include <vcl/dllapi.h>
+#include <vcl/prntypes.hxx>
+#include <unordered_map>
+#include <memory>
+
+// see com.sun.star.portal.client.JobSetupSystem.idl:
+#define JOBSETUP_SYSTEM_WINDOWS 1
+#define JOBSETUP_SYSTEM_UNIX 3
+#define JOBSETUP_SYSTEM_MAC 4
+
+class VCL_DLLPUBLIC ImplJobSetup
+{
+private:
+ sal_uInt16 mnSystem; //< System - JOBSETUP_SYSTEM_xxxx
+ OUString maPrinterName; //< Printer-Name
+ OUString maDriver; //< Driver-Name
+ Orientation meOrientation; //< Orientation
+ DuplexMode meDuplexMode; //< Duplex
+ sal_uInt16 mnPaperBin; //< paper bin / in tray
+ Paper mePaperFormat; //< paper format
+ tools::Long mnPaperWidth; //< paper width (100th mm)
+ tools::Long mnPaperHeight; //< paper height (100th mm)
+ sal_uInt32 mnDriverDataLen; //< length of system specific data
+ std::unique_ptr<sal_uInt8[]> mpDriverData; //< system specific data (will be streamed a byte block)
+ bool mbPapersizeFromSetup;
+ // setup mode
+ PrinterSetupMode meSetupMode;
+ // TODO: orig paper size
+ std::unordered_map< OUString, OUString > maValueMap;
+
+public:
+ ImplJobSetup();
+ ImplJobSetup( const ImplJobSetup& rJobSetup );
+ ~ImplJobSetup();
+
+ bool operator==( const ImplJobSetup& rImplJobSetup ) const;
+
+ sal_uInt16 GetSystem() const { return mnSystem; }
+ void SetSystem(sal_uInt16 nSystem);
+
+ const OUString& GetPrinterName() const { return maPrinterName; }
+ void SetPrinterName(const OUString& rPrinterName);
+
+ const OUString& GetDriver() const { return maDriver; }
+ void SetDriver(const OUString& rDriver);
+
+ Orientation GetOrientation() const { return meOrientation; }
+ void SetOrientation(Orientation eOrientation);
+
+ DuplexMode GetDuplexMode() const { return meDuplexMode; }
+ void SetDuplexMode(DuplexMode eDuplexMode);
+
+ sal_uInt16 GetPaperBin() const { return mnPaperBin; }
+ void SetPaperBin(sal_uInt16 nPaperBin);
+
+ Paper GetPaperFormat() const { return mePaperFormat; }
+ void SetPaperFormat(Paper ePaperFormat);
+
+ tools::Long GetPaperWidth() const { return mnPaperWidth; }
+ void SetPaperWidth(tools::Long nWidth);
+
+ tools::Long GetPaperHeight() const { return mnPaperHeight; }
+ void SetPaperHeight(tools::Long nHeight);
+
+ sal_uInt32 GetDriverDataLen() const { return mnDriverDataLen; }
+ const sal_uInt8* GetDriverData() const { return mpDriverData.get(); }
+ void SetDriverData(std::unique_ptr<sal_uInt8[]> pDriverData, sal_uInt32 nDriverDataLen);
+
+ bool GetPapersizeFromSetup() const { return mbPapersizeFromSetup; }
+ void SetPapersizeFromSetup(bool bPapersizeFromSetup);
+
+ PrinterSetupMode GetPrinterSetupMode() const { return meSetupMode; }
+ void SetPrinterSetupMode(PrinterSetupMode eMode);
+
+ const std::unordered_map< OUString, OUString >& GetValueMap() const
+ { return maValueMap; }
+ void SetValueMap(const OUString& rKey, const OUString& rValue);
+};
+
+// If paper format is PAPER_USER, in the system-independent part it will
+// automatically be computed from paper width/height.
+// If paper width/height is 0, in the system-independent part it will
+// automatically be computed from paper format, if the latter is not PAPER_USER.
+
+#endif // INCLUDED_VCL_INC_JOBSET_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/jsdialog/enabled.hxx b/vcl/inc/jsdialog/enabled.hxx
new file mode 100644
index 0000000000..6354b70a8d
--- /dev/null
+++ b/vcl/inc/jsdialog/enabled.hxx
@@ -0,0 +1,22 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <string_view>
+
+namespace jsdialog
+{
+bool isBuilderEnabled(std::u16string_view rUIFile, bool bMobile);
+bool isBuilderEnabledForPopup(std::u16string_view rUIFile);
+bool isBuilderEnabledForSidebar(std::u16string_view rUIFile);
+bool isInterimBuilderEnabledForNotebookbar(std::u16string_view rUIFile);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/jsdialog/jsdialogbuilder.hxx b/vcl/inc/jsdialog/jsdialogbuilder.hxx
new file mode 100644
index 0000000000..6e611c4f96
--- /dev/null
+++ b/vcl/inc/jsdialog/jsdialogbuilder.hxx
@@ -0,0 +1,894 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <utility>
+#include <vcl/weld.hxx>
+#include <vcl/jsdialog/executor.hxx>
+#include <vcl/virdev.hxx>
+#include <salvtables.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/toolkit/fmtfield.hxx>
+
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <comphelper/compbase.hxx>
+
+#include <deque>
+#include <list>
+#include <mutex>
+
+#define ACTION_TYPE "action_type"
+#define PARENT_ID "parent_id"
+#define WINDOW_ID "id"
+#define CLOSE_ID "close_id"
+
+class ToolBox;
+class ComboBox;
+class VclMultiLineEdit;
+class SvTabListBox;
+class IconView;
+class VclScrolledWindow;
+
+namespace vcl
+{
+class ILibreOfficeKitNotifier;
+}
+
+typedef std::map<OUString, weld::Widget*> WidgetMap;
+
+namespace jsdialog
+{
+enum MessageType
+{
+ FullUpdate,
+ WidgetUpdate,
+ Close,
+ Action,
+ Popup,
+ PopupClose
+};
+}
+
+/// Class with the message description for storing in the queue
+class JSDialogMessageInfo
+{
+public:
+ jsdialog::MessageType m_eType;
+ VclPtr<vcl::Window> m_pWindow;
+ std::unique_ptr<jsdialog::ActionDataMap> m_pData;
+
+private:
+ void copy(const JSDialogMessageInfo& rInfo)
+ {
+ this->m_eType = rInfo.m_eType;
+ this->m_pWindow = rInfo.m_pWindow;
+ if (rInfo.m_pData)
+ {
+ std::unique_ptr<jsdialog::ActionDataMap> pData(
+ new jsdialog::ActionDataMap(*rInfo.m_pData));
+ this->m_pData = std::move(pData);
+ }
+ }
+
+public:
+ JSDialogMessageInfo(jsdialog::MessageType eType, VclPtr<vcl::Window> pWindow,
+ std::unique_ptr<jsdialog::ActionDataMap> pData)
+ : m_eType(eType)
+ , m_pWindow(std::move(pWindow))
+ , m_pData(std::move(pData))
+ {
+ }
+
+ JSDialogMessageInfo(const JSDialogMessageInfo& rInfo) { copy(rInfo); }
+
+ JSDialogMessageInfo& operator=(JSDialogMessageInfo aInfo)
+ {
+ if (this == &aInfo)
+ return *this;
+
+ copy(aInfo);
+ return *this;
+ }
+};
+
+class JSDialogNotifyIdle final : public Idle
+{
+ // used to send message
+ VclPtr<vcl::Window> m_aNotifierWindow;
+ // used to generate JSON
+ VclPtr<vcl::Window> m_aContentWindow;
+ OUString m_sTypeOfJSON;
+ OString m_LastNotificationMessage;
+ bool m_bForce;
+
+ std::deque<JSDialogMessageInfo> m_aMessageQueue;
+ std::mutex m_aQueueMutex;
+
+public:
+ JSDialogNotifyIdle(VclPtr<vcl::Window> aNotifierWindow, VclPtr<vcl::Window> aContentWindow,
+ const OUString& sTypeOfJSON);
+
+ void Invoke() override;
+
+ void clearQueue();
+ void forceUpdate();
+ void sendMessage(jsdialog::MessageType eType, VclPtr<vcl::Window> pWindow,
+ std::unique_ptr<jsdialog::ActionDataMap> pData = nullptr);
+
+private:
+ void send(tools::JsonWriter& aJsonWriter);
+ std::unique_ptr<tools::JsonWriter> generateFullUpdate() const;
+ std::unique_ptr<tools::JsonWriter> generateWidgetUpdate(VclPtr<vcl::Window> pWindow) const;
+ std::unique_ptr<tools::JsonWriter> generateCloseMessage() const;
+ std::unique_ptr<tools::JsonWriter>
+ generateActionMessage(VclPtr<vcl::Window> pWindow,
+ std::unique_ptr<jsdialog::ActionDataMap> pData) const;
+ std::unique_ptr<tools::JsonWriter>
+ generatePopupMessage(VclPtr<vcl::Window> pWindow, OUString sParentId, OUString sCloseId) const;
+ std::unique_ptr<tools::JsonWriter> generateClosePopupMessage(OUString sWindowId) const;
+};
+
+class JSDialogSender
+{
+ std::unique_ptr<JSDialogNotifyIdle> mpIdleNotify;
+
+protected:
+ bool m_bCanClose; // specifies if can send a close message
+
+public:
+ JSDialogSender()
+ : m_bCanClose(true)
+ {
+ }
+ JSDialogSender(VclPtr<vcl::Window> aNotifierWindow, VclPtr<vcl::Window> aContentWindow,
+ const OUString& sTypeOfJSON)
+ : m_bCanClose(true)
+ {
+ initializeSender(aNotifierWindow, aContentWindow, sTypeOfJSON);
+ }
+
+ virtual ~JSDialogSender() COVERITY_NOEXCEPT_FALSE;
+
+ virtual void sendFullUpdate(bool bForce = false);
+ void sendClose();
+ void sendUpdate(VclPtr<vcl::Window> pWindow, bool bForce = false);
+ virtual void sendAction(VclPtr<vcl::Window> pWindow,
+ std::unique_ptr<jsdialog::ActionDataMap> pData);
+ virtual void sendPopup(VclPtr<vcl::Window> pWindow, OUString sParentId, OUString sCloseId);
+ virtual void sendClosePopup(vcl::LOKWindowId nWindowId);
+ void flush() { mpIdleNotify->Invoke(); }
+
+protected:
+ void initializeSender(VclPtr<vcl::Window> aNotifierWindow, VclPtr<vcl::Window> aContentWindow,
+ const OUString& sTypeOfJSON)
+ {
+ mpIdleNotify.reset(new JSDialogNotifyIdle(aNotifierWindow, aContentWindow, sTypeOfJSON));
+ }
+};
+
+class JSDropTarget final
+ : public comphelper::WeakComponentImplHelper<
+ css::datatransfer::dnd::XDropTarget, css::lang::XInitialization, css::lang::XServiceInfo>
+{
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> m_aListeners;
+
+public:
+ JSDropTarget();
+
+ // XInitialization
+ virtual void SAL_CALL initialize(const css::uno::Sequence<css::uno::Any>& rArgs) override;
+
+ // XDropTarget
+ virtual void SAL_CALL addDropTargetListener(
+ const css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>&) override;
+ virtual void SAL_CALL removeDropTargetListener(
+ const css::uno::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;
+
+ sal_Bool SAL_CALL supportsService(OUString const& ServiceName) override;
+
+ css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+ void fire_drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde);
+
+ void fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde);
+};
+
+class JSInstanceBuilder final : public SalInstanceBuilder, public JSDialogSender
+{
+ sal_uInt64 m_nWindowId;
+ /// used in case of tab pages where dialog is not a direct top level
+ VclPtr<vcl::Window> m_aParentDialog;
+ VclPtr<vcl::Window> m_aContentWindow;
+ std::list<OUString> m_aRememberedWidgets;
+ OUString m_sTypeOfJSON;
+ bool m_bHasTopLevelDialog;
+ bool m_bIsNotebookbar;
+ /// used to detect when we have to send Full Update in container handler
+ bool m_bSentInitialUpdate;
+ /// is true for tabpages, prevents from closing parent window on destroy
+ bool m_bIsNestedBuilder;
+ /// When LOKNotifier is set by jsdialogs code we need to release it
+ VclPtr<vcl::Window> m_aWindowToRelease;
+
+ friend class JSMessageDialog; // static message boxes have to be registered outside
+ friend class JSDialog;
+ friend class JSAssistant;
+
+ friend VCL_DLLPUBLIC bool jsdialog::ExecuteAction(const OUString& nWindowId,
+ const OUString& rWidget, StringMap& rData);
+ friend VCL_DLLPUBLIC void jsdialog::SendFullUpdate(const OUString& nWindowId,
+ const OUString& rWidget);
+ friend VCL_DLLPUBLIC void jsdialog::SendAction(const OUString& nWindowId,
+ const OUString& rWidget,
+ std::unique_ptr<jsdialog::ActionDataMap> pData);
+
+ static std::map<OUString, WidgetMap>& GetLOKWeldWidgetsMap();
+ static void InsertWindowToMap(const OUString& nWindowId);
+ void RememberWidget(OUString id, weld::Widget* pWidget);
+ static void RememberWidget(const OUString& nWindowId, const OUString& id,
+ weld::Widget* pWidget);
+ static weld::Widget* FindWeldWidgetsMap(const OUString& nWindowId, const OUString& rWidget);
+
+ OUString getMapIdFromWindowId() const;
+
+public:
+ /// used for dialogs or popups
+ JSInstanceBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile,
+ bool bPopup = false);
+ /// used for sidebar panels
+ JSInstanceBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile,
+ sal_uInt64 nLOKWindowId);
+ /// used for notebookbar, optional nWindowId is used if getting parent id failed
+ JSInstanceBuilder(vcl::Window* pParent, const OUString& rUIRoot, const OUString& rUIFile,
+ const css::uno::Reference<css::frame::XFrame>& rFrame,
+ sal_uInt64 nWindowId = 0);
+ /// used for formulabar
+ JSInstanceBuilder(vcl::Window* pParent, const OUString& rUIRoot, const OUString& rUIFile,
+ sal_uInt64 nLOKWindowId);
+
+ static std::unique_ptr<JSInstanceBuilder>
+ CreateDialogBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile);
+ static std::unique_ptr<JSInstanceBuilder>
+ CreateNotebookbarBuilder(vcl::Window* pParent, const OUString& rUIRoot, const OUString& rUIFile,
+ const css::uno::Reference<css::frame::XFrame>& rFrame,
+ sal_uInt64 nWindowId = 0);
+ static std::unique_ptr<JSInstanceBuilder> CreateSidebarBuilder(weld::Widget* pParent,
+ const OUString& rUIRoot,
+ const OUString& rUIFile,
+ sal_uInt64 nLOKWindowId = 0);
+ static std::unique_ptr<JSInstanceBuilder>
+ CreatePopupBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile);
+ static std::unique_ptr<JSInstanceBuilder> CreateFormulabarBuilder(vcl::Window* pParent,
+ const OUString& rUIRoot,
+ const OUString& rUIFile,
+ sal_uInt64 nLOKWindowId);
+
+ virtual ~JSInstanceBuilder() override;
+ virtual std::unique_ptr<weld::MessageDialog> weld_message_dialog(const OUString& id) override;
+ virtual std::unique_ptr<weld::Dialog> weld_dialog(const OUString& id) override;
+ virtual std::unique_ptr<weld::Assistant> weld_assistant(const OUString& id) override;
+ virtual std::unique_ptr<weld::Container> weld_container(const OUString& id) override;
+ virtual std::unique_ptr<weld::Label> weld_label(const OUString& id) override;
+ virtual std::unique_ptr<weld::Button> weld_button(const OUString& id) override;
+ virtual std::unique_ptr<weld::LinkButton> weld_link_button(const OUString& id) override;
+ virtual std::unique_ptr<weld::ToggleButton> weld_toggle_button(const OUString& id) override;
+ virtual std::unique_ptr<weld::Entry> weld_entry(const OUString& id) override;
+ virtual std::unique_ptr<weld::ComboBox> weld_combo_box(const OUString& id) override;
+ virtual std::unique_ptr<weld::Notebook> weld_notebook(const OUString& id) override;
+ virtual std::unique_ptr<weld::SpinButton> weld_spin_button(const OUString& id) override;
+ virtual std::unique_ptr<weld::FormattedSpinButton>
+ weld_formatted_spin_button(const OUString& id) override;
+ virtual std::unique_ptr<weld::CheckButton> weld_check_button(const OUString& id) override;
+ virtual std::unique_ptr<weld::DrawingArea>
+ weld_drawing_area(const OUString& id, const a11yref& rA11yImpl = nullptr,
+ FactoryFunction pUITestFactoryFunction = nullptr,
+ void* pUserData = nullptr) override;
+ virtual std::unique_ptr<weld::Toolbar> weld_toolbar(const OUString& id) override;
+ virtual std::unique_ptr<weld::TextView> weld_text_view(const OUString& id) override;
+ virtual std::unique_ptr<weld::TreeView> weld_tree_view(const OUString& id) override;
+ virtual std::unique_ptr<weld::Expander> weld_expander(const OUString& id) override;
+ virtual std::unique_ptr<weld::IconView> weld_icon_view(const OUString& id) override;
+ virtual std::unique_ptr<weld::ScrolledWindow>
+ weld_scrolled_window(const OUString& id, bool bUserManagedScrolling = false) override;
+ virtual std::unique_ptr<weld::RadioButton> weld_radio_button(const OUString& id) override;
+ virtual std::unique_ptr<weld::Frame> weld_frame(const OUString& id) override;
+ virtual std::unique_ptr<weld::MenuButton> weld_menu_button(const OUString& id) override;
+ virtual std::unique_ptr<weld::Popover> weld_popover(const OUString& id) override;
+ virtual std::unique_ptr<weld::Box> weld_box(const OUString& id) override;
+ virtual std::unique_ptr<weld::Widget> weld_widget(const OUString& id) override;
+ virtual std::unique_ptr<weld::Image> weld_image(const OUString& id) override;
+ virtual std::unique_ptr<weld::Calendar> weld_calendar(const OUString& id) override;
+
+ static weld::MessageDialog*
+ CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType,
+ VclButtonsType eButtonType, const OUString& rPrimaryMessage,
+ const vcl::ILibreOfficeKitNotifier* pNotifier = nullptr);
+
+ static void AddChildWidget(const OUString& nWindowId, const OUString& id,
+ weld::Widget* pWidget);
+ static void RemoveWindowWidget(const OUString& nWindowId);
+
+ // we need to remember original popup window to close it properly (its handled by vcl)
+ static void RememberPopup(const OUString& nWindowId, VclPtr<vcl::Window> pWidget);
+ static void ForgetPopup(const OUString& nWindowId);
+ static vcl::Window* FindPopup(const OUString& nWindowId);
+
+private:
+ const OUString& GetTypeOfJSON() const;
+ VclPtr<vcl::Window>& GetContentWindow();
+ VclPtr<vcl::Window>& GetNotifierWindow();
+};
+
+class SAL_LOPLUGIN_ANNOTATE("crosscast") BaseJSWidget
+{
+public:
+ virtual ~BaseJSWidget() = default;
+
+ virtual void sendClose() = 0;
+
+ virtual void sendUpdate(bool bForce = false) = 0;
+
+ virtual void sendFullUpdate(bool bForce = false) = 0;
+
+ virtual void sendAction(std::unique_ptr<jsdialog::ActionDataMap> pData) = 0;
+
+ virtual void sendPopup(vcl::Window* pPopup, OUString sParentId, OUString sCloseId) = 0;
+
+ virtual void sendClosePopup(vcl::LOKWindowId nWindowId) = 0;
+};
+
+template <class BaseInstanceClass, class VclClass>
+class JSWidget : public BaseInstanceClass, public BaseJSWidget
+{
+protected:
+ rtl::Reference<JSDropTarget> m_xDropTarget;
+ bool m_bIsFreezed;
+
+ JSDialogSender* m_pSender;
+
+public:
+ JSWidget(JSDialogSender* pSender, VclClass* pObject, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : BaseInstanceClass(pObject, pBuilder, bTakeOwnership)
+ , m_bIsFreezed(false)
+ , m_pSender(pSender)
+ {
+ }
+
+ JSWidget(JSDialogSender* pSender, VclClass* pObject, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership, bool bUserManagedScrolling)
+ : BaseInstanceClass(pObject, pBuilder, bTakeOwnership, bUserManagedScrolling)
+ , m_bIsFreezed(false)
+ , m_pSender(pSender)
+ {
+ }
+
+ JSWidget(JSDialogSender* pSender, VclClass* pObject, SalInstanceBuilder* pBuilder,
+ const a11yref& rAlly, FactoryFunction pUITestFactoryFunction, void* pUserData,
+ bool bTakeOwnership)
+ : BaseInstanceClass(pObject, pBuilder, rAlly, pUITestFactoryFunction, pUserData,
+ bTakeOwnership)
+ , m_bIsFreezed(false)
+ , m_pSender(pSender)
+ {
+ }
+
+ virtual void show() override
+ {
+ bool bWasVisible = BaseInstanceClass::get_visible();
+ BaseInstanceClass::show();
+ if (!bWasVisible)
+ {
+ std::unique_ptr<jsdialog::ActionDataMap> pMap
+ = std::make_unique<jsdialog::ActionDataMap>();
+ (*pMap)[ACTION_TYPE ""_ostr] = "show";
+ sendAction(std::move(pMap));
+ }
+ }
+
+ virtual void hide() override
+ {
+ bool bWasVisible = BaseInstanceClass::get_visible();
+ BaseInstanceClass::hide();
+ if (bWasVisible)
+ {
+ std::unique_ptr<jsdialog::ActionDataMap> pMap
+ = std::make_unique<jsdialog::ActionDataMap>();
+ (*pMap)[ACTION_TYPE ""_ostr] = "hide";
+ sendAction(std::move(pMap));
+ }
+ }
+
+ using BaseInstanceClass::set_sensitive;
+ virtual void set_sensitive(bool sensitive) override
+ {
+ bool bIsSensitive = BaseInstanceClass::get_sensitive();
+ BaseInstanceClass::set_sensitive(sensitive);
+ if (bIsSensitive != sensitive)
+ sendUpdate();
+ }
+
+ virtual css::uno::Reference<css::datatransfer::dnd::XDropTarget> get_drop_target() override
+ {
+ if (!m_xDropTarget)
+ m_xDropTarget.set(new JSDropTarget);
+
+ return m_xDropTarget;
+ }
+
+ virtual void freeze() override
+ {
+ BaseInstanceClass::freeze();
+ m_bIsFreezed = true;
+ }
+
+ virtual void thaw() override
+ {
+ BaseInstanceClass::thaw();
+ m_bIsFreezed = false;
+ sendUpdate();
+ }
+
+ virtual void grab_focus() override
+ {
+ BaseInstanceClass::grab_focus();
+ std::unique_ptr<jsdialog::ActionDataMap> pMap = std::make_unique<jsdialog::ActionDataMap>();
+ (*pMap)[ACTION_TYPE ""_ostr] = "grab_focus";
+ sendAction(std::move(pMap));
+ }
+
+ virtual void sendClose() override
+ {
+ if (m_pSender)
+ m_pSender->sendClose();
+ }
+
+ virtual void sendUpdate(bool bForce = false) override
+ {
+ if (!m_bIsFreezed && m_pSender)
+ m_pSender->sendUpdate(BaseInstanceClass::m_xWidget, bForce);
+ }
+
+ virtual void sendFullUpdate(bool bForce = false) override
+ {
+ if ((!m_bIsFreezed || bForce) && m_pSender)
+ m_pSender->sendFullUpdate(bForce);
+ }
+
+ virtual void sendAction(std::unique_ptr<jsdialog::ActionDataMap> pData) override
+ {
+ if (!m_bIsFreezed && m_pSender && pData)
+ m_pSender->sendAction(BaseInstanceClass::m_xWidget, std::move(pData));
+ }
+
+ virtual void sendPopup(vcl::Window* pPopup, OUString sParentId, OUString sCloseId) override
+ {
+ if (!m_bIsFreezed && m_pSender)
+ m_pSender->sendPopup(pPopup, sParentId, sCloseId);
+ }
+
+ virtual void sendClosePopup(vcl::LOKWindowId nWindowId) override
+ {
+ if (!m_bIsFreezed && m_pSender)
+ m_pSender->sendClosePopup(nWindowId);
+ }
+
+ virtual void set_buildable_name(const OUString& rName) override
+ {
+ SalInstanceWidget::set_buildable_name(rName);
+ assert(false); // we remember old name in GetLOKWeldWidgetsMap()
+ // TODO: implement renaming or avoid it for LOK
+ }
+};
+
+class JSDialog final : public JSWidget<SalInstanceDialog, ::Dialog>
+{
+public:
+ JSDialog(JSDialogSender* pSender, ::Dialog* pDialog, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual void collapse(weld::Widget* pEdit, weld::Widget* pButton) override;
+ virtual void undo_collapse() override;
+ virtual void response(int response) override;
+ virtual weld::Button* weld_widget_for_response(int response) override;
+ virtual int run() override;
+ virtual bool runAsync(std::shared_ptr<weld::DialogController> aOwner,
+ const std::function<void(sal_Int32)>& rEndDialogFn) override;
+ virtual bool runAsync(std::shared_ptr<Dialog> const& rxSelf,
+ const std::function<void(sal_Int32)>& func) override;
+};
+
+class JSAssistant final : public JSWidget<SalInstanceAssistant, vcl::RoadmapWizard>
+{
+public:
+ JSAssistant(JSDialogSender* pSender, vcl::RoadmapWizard* pDialog, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual void set_current_page(int nPage) override;
+ virtual void set_current_page(const OUString& rIdent) override;
+ virtual void response(int response) override;
+ virtual weld::Button* weld_widget_for_response(int response) override;
+ virtual int run() override;
+ virtual bool runAsync(std::shared_ptr<weld::DialogController> aOwner,
+ const std::function<void(sal_Int32)>& rEndDialogFn) override;
+ virtual bool runAsync(std::shared_ptr<Dialog> const& rxSelf,
+ const std::function<void(sal_Int32)>& func) override;
+};
+
+class JSContainer final : public JSWidget<SalInstanceContainer, vcl::Window>
+{
+public:
+ JSContainer(JSDialogSender* pSender, vcl::Window* pContainer, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+};
+
+class JSScrolledWindow final : public JSWidget<SalInstanceScrolledWindow, ::VclScrolledWindow>
+{
+public:
+ JSScrolledWindow(JSDialogSender* pSender, ::VclScrolledWindow* pWindow,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership, bool bUserManagedScrolling);
+
+ virtual void vadjustment_configure(int value, int lower, int upper, int step_increment,
+ int page_increment, int page_size) override;
+ virtual void vadjustment_set_value(int value) override;
+ void vadjustment_set_value_no_notification(int value);
+ virtual void vadjustment_set_page_size(int size) override;
+ virtual void set_vpolicy(VclPolicyType eVPolicy) override;
+
+ virtual void hadjustment_configure(int value, int lower, int upper, int step_increment,
+ int page_increment, int page_size) override;
+ virtual void hadjustment_set_value(int value) override;
+ void hadjustment_set_value_no_notification(int value);
+ virtual void hadjustment_set_page_size(int size) override;
+ virtual void set_hpolicy(VclPolicyType eVPolicy) override;
+};
+
+class JSLabel final : public JSWidget<SalInstanceLabel, Control>
+{
+public:
+ JSLabel(JSDialogSender* pSender, Control* pLabel, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+ virtual void set_label(const OUString& rText) override;
+};
+
+class JSButton final : public JSWidget<SalInstanceButton, ::Button>
+{
+public:
+ JSButton(JSDialogSender* pSender, ::Button* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+};
+
+class JSLinkButton final : public JSWidget<SalInstanceLinkButton, ::FixedHyperlink>
+{
+public:
+ JSLinkButton(JSDialogSender* pSender, ::FixedHyperlink* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+};
+
+class JSToggleButton final : public JSWidget<SalInstanceToggleButton, ::PushButton>
+{
+public:
+ JSToggleButton(JSDialogSender* pSender, ::PushButton* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+};
+
+class JSEntry final : public JSWidget<SalInstanceEntry, ::Edit>
+{
+public:
+ JSEntry(JSDialogSender* pSender, ::Edit* pEntry, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+ virtual void set_text(const OUString& rText) override;
+ void set_text_without_notify(const OUString& rText);
+ virtual void replace_selection(const OUString& rText) override;
+};
+
+class JSListBox final : public JSWidget<SalInstanceComboBoxWithoutEdit, ::ListBox>
+{
+public:
+ JSListBox(JSDialogSender* pSender, ::ListBox* pListBox, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+ virtual void insert(int pos, const OUString& rStr, const OUString* pId,
+ const OUString* pIconName, VirtualDevice* pImageSurface) override;
+ virtual void remove(int pos) override;
+ virtual void set_active(int pos) override;
+};
+
+class JSComboBox final : public JSWidget<SalInstanceComboBoxWithEdit, ::ComboBox>
+{
+public:
+ JSComboBox(JSDialogSender* pSender, ::ComboBox* pComboBox, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+ virtual void insert(int pos, const OUString& rStr, const OUString* pId,
+ const OUString* pIconName, VirtualDevice* pImageSurface) override;
+ virtual void remove(int pos) override;
+ virtual void set_entry_text_without_notify(const OUString& rText);
+ virtual void set_entry_text(const OUString& rText) override;
+ virtual void set_active(int pos) override;
+ virtual void set_active_id(const OUString& rText) override;
+ virtual bool changed_by_direct_pick() const override;
+
+ void render_entry(int pos, int dpix, int dpiy);
+};
+
+class JSNotebook final : public JSWidget<SalInstanceNotebook, ::TabControl>
+{
+public:
+ JSNotebook(JSDialogSender* pSender, ::TabControl* pControl, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual void remove_page(const OUString& rIdent) override;
+ virtual void insert_page(const OUString& rIdent, const OUString& rLabel, int nPos) override;
+};
+
+class JSVerticalNotebook final : public JSWidget<SalInstanceVerticalNotebook, ::VerticalTabControl>
+{
+public:
+ JSVerticalNotebook(JSDialogSender* pSender, ::VerticalTabControl* pControl,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual void remove_page(const OUString& rIdent) override;
+ virtual void insert_page(const OUString& rIdent, const OUString& rLabel, int nPos) override;
+};
+
+class JSSpinButton final : public JSWidget<SalInstanceSpinButton, ::FormattedField>
+{
+public:
+ JSSpinButton(JSDialogSender* pSender, ::FormattedField* pSpin, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual void set_value(sal_Int64 value) override;
+};
+
+class JSFormattedSpinButton final
+ : public JSWidget<SalInstanceFormattedSpinButton, ::FormattedField>
+{
+public:
+ JSFormattedSpinButton(JSDialogSender* pSender, ::FormattedField* pSpin,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual void set_text(const OUString& rText) override;
+ void set_text_without_notify(const OUString& rText);
+};
+
+class JSMessageDialog final : public JSWidget<SalInstanceMessageDialog, ::MessageDialog>
+{
+ std::unique_ptr<JSDialogSender> m_pOwnedSender;
+ std::unique_ptr<JSButton> m_pOK;
+ std::unique_ptr<JSButton> m_pCancel;
+
+ // used for message dialogs created using static functions
+ OUString m_sWindowId;
+
+ DECL_LINK(OKHdl, weld::Button&, void);
+ DECL_LINK(CancelHdl, weld::Button&, void);
+
+ void RememberMessageDialog();
+
+public:
+ JSMessageDialog(JSDialogSender* pSender, ::MessageDialog* pDialog, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+ JSMessageDialog(::MessageDialog* pDialog, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+ virtual ~JSMessageDialog();
+
+ virtual void set_primary_text(const OUString& rText) override;
+
+ virtual void set_secondary_text(const OUString& rText) override;
+
+ virtual void response(int response) override;
+
+ virtual int run() override;
+ // TODO: move to dialog class so we will not send json when built but on run
+ bool runAsync(std::shared_ptr<weld::DialogController> aOwner,
+ const std::function<void(sal_Int32)>& rEndDialogFn) override;
+
+ bool runAsync(std::shared_ptr<Dialog> const& rxSelf,
+ const std::function<void(sal_Int32)>& rEndDialogFn) override;
+};
+
+class JSCheckButton final : public JSWidget<SalInstanceCheckButton, ::CheckBox>
+{
+public:
+ JSCheckButton(JSDialogSender* pSender, ::CheckBox* pCheckBox, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual void set_active(bool active) override;
+};
+
+class JSDrawingArea final : public JSWidget<SalInstanceDrawingArea, VclDrawingArea>
+{
+public:
+ JSDrawingArea(JSDialogSender* pSender, VclDrawingArea* pDrawingArea,
+ SalInstanceBuilder* pBuilder, const a11yref& rAlly,
+ FactoryFunction pUITestFactoryFunction, void* pUserData);
+
+ virtual void queue_draw() override;
+ virtual void queue_draw_area(int x, int y, int width, int height) override;
+};
+
+class JSToolbar final : public JSWidget<SalInstanceToolbar, ::ToolBox>
+{
+ std::map<sal_uInt16, weld::Widget*> m_pPopovers;
+
+public:
+ JSToolbar(JSDialogSender* pSender, ::ToolBox* pToolbox, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual void set_menu_item_active(const OUString& rIdent, bool bActive) override;
+ virtual void set_item_sensitive(const OUString& rIdent, bool bSensitive) override;
+ virtual void set_item_icon_name(const OUString& rIdent, const OUString& rIconName) override;
+};
+
+class JSTextView final : public JSWidget<SalInstanceTextView, ::VclMultiLineEdit>
+{
+public:
+ JSTextView(JSDialogSender* pSender, ::VclMultiLineEdit* pTextView, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+ virtual void set_text(const OUString& rText) override;
+ void set_text_without_notify(const OUString& rText);
+ virtual void replace_selection(const OUString& rText) override;
+};
+
+class JSTreeView final : public JSWidget<SalInstanceTreeView, ::SvTabListBox>
+{
+public:
+ JSTreeView(JSDialogSender* pSender, ::SvTabListBox* pTextView, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ using SalInstanceTreeView::set_toggle;
+ /// pos is used differently here, it defines how many steps of iterator we need to perform to take entry
+ virtual void set_toggle(int pos, TriState eState, int col = -1) override;
+ virtual void set_toggle(const weld::TreeIter& rIter, TriState bOn, int col = -1) override;
+
+ using SalInstanceTreeView::select;
+ /// pos is used differently here, it defines how many steps of iterator we need to perform to take entry
+ virtual void select(int pos) override;
+
+ virtual weld::TreeView* get_drag_source() const override;
+
+ using SalInstanceTreeView::insert;
+ virtual void insert(const weld::TreeIter* pParent, int pos, const OUString* pStr,
+ const OUString* pId, const OUString* pIconName,
+ VirtualDevice* pImageSurface, bool bChildrenOnDemand,
+ weld::TreeIter* pRet) override;
+
+ virtual void set_text(int row, const OUString& rText, int col = -1) override;
+ virtual void set_text(const weld::TreeIter& rIter, const OUString& rStr, int col = -1) override;
+
+ virtual void expand_row(const weld::TreeIter& rIter) override;
+ virtual void collapse_row(const weld::TreeIter& rIter) override;
+
+ virtual void set_cursor(const weld::TreeIter& rIter) override;
+ void set_cursor_without_notify(const weld::TreeIter& rIter);
+ virtual void set_cursor(int pos) override;
+
+ using SalInstanceTreeView::remove;
+ virtual void remove(int pos) override;
+ virtual void remove(const weld::TreeIter& rIter) override;
+
+ virtual void clear() override;
+
+ void drag_start();
+ void drag_end();
+};
+
+class JSExpander final : public JSWidget<SalInstanceExpander, ::VclExpander>
+{
+public:
+ JSExpander(JSDialogSender* pSender, ::VclExpander* pExpander, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual void set_expanded(bool bExpand) override;
+};
+
+class JSIconView final : public JSWidget<SalInstanceIconView, ::IconView>
+{
+public:
+ JSIconView(JSDialogSender* pSender, ::IconView* pIconView, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual void insert(int pos, const OUString* pStr, const OUString* pId,
+ const OUString* pIconName, weld::TreeIter* pRet) override;
+
+ virtual void insert(int pos, const OUString* pStr, const OUString* pId,
+ const VirtualDevice* pIcon, weld::TreeIter* pRet) override;
+
+ virtual void insert_separator(int pos, const OUString* pId) override;
+
+ virtual void clear() override;
+ virtual void select(int pos) override;
+ virtual void unselect(int pos) override;
+};
+
+class JSRadioButton final : public JSWidget<SalInstanceRadioButton, ::RadioButton>
+{
+public:
+ JSRadioButton(JSDialogSender* pSender, ::RadioButton* pRadioButton,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual void set_active(bool active) override;
+};
+
+class JSFrame : public JSWidget<SalInstanceFrame, ::VclFrame>
+{
+public:
+ JSFrame(JSDialogSender* pSender, ::VclFrame* pFrame, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+};
+
+class JSMenuButton : public JSWidget<SalInstanceMenuButton, ::MenuButton>
+{
+public:
+ JSMenuButton(JSDialogSender* pSender, ::MenuButton* pMenuButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual void set_label(const OUString& rText) override;
+ virtual void set_image(VirtualDevice* pDevice) override;
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override;
+ virtual void set_active(bool active) override;
+};
+
+class JSPopover : public JSWidget<SalInstancePopover, DockingWindow>
+{
+ vcl::LOKWindowId mnWindowId;
+
+public:
+ JSPopover(JSDialogSender* pSender, DockingWindow* pPopover, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual void popup_at_rect(weld::Widget* pParent, const tools::Rectangle& rRect,
+ weld::Placement ePlace = weld::Placement::Under) override;
+ virtual void popdown() override;
+
+ void set_window_id(vcl::LOKWindowId nWindowId) { mnWindowId = nWindowId; }
+};
+
+class JSBox : public JSWidget<SalInstanceBox, VclBox>
+{
+public:
+ JSBox(JSDialogSender* pSender, VclBox* pBox, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ void reorder_child(weld::Widget* pWidget, int nNewPosition) override;
+};
+
+class JSWidgetInstance : public JSWidget<SalInstanceWidget, vcl::Window>
+{
+public:
+ JSWidgetInstance(JSDialogSender* pSender, vcl::Window* pObject, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : JSWidget<SalInstanceWidget, vcl::Window>(pSender, pObject, pBuilder, bTakeOwnership)
+ {
+ }
+};
+
+class JSImage : public JSWidget<SalInstanceImage, FixedImage>
+{
+public:
+ JSImage(JSDialogSender* pSender, FixedImage* pImage, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+ virtual void set_image(VirtualDevice* pDevice) override;
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override;
+};
+
+class JSCalendar : public JSWidget<SalInstanceCalendar, ::Calendar>
+{
+public:
+ JSCalendar(JSDialogSender* pSender, ::Calendar* pCalendar, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/langboost.hxx b/vcl/inc/langboost.hxx
new file mode 100644
index 0000000000..77553de05c
--- /dev/null
+++ b/vcl/inc/langboost.hxx
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_INC_LANGBOOST_HXX
+#define INCLUDED_VCL_INC_LANGBOOST_HXX
+
+namespace vcl
+{
+const char* getLangBoost();
+}
+
+#endif
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/listbox.hxx b/vcl/inc/listbox.hxx
new file mode 100644
index 0000000000..8b37562971
--- /dev/null
+++ b/vcl/inc/listbox.hxx
@@ -0,0 +1,599 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_LISTBOX_HXX
+#define INCLUDED_VCL_INC_LISTBOX_HXX
+
+#include <sal/config.h>
+
+#include <o3tl/safeint.hxx>
+#include <utility>
+#include <vcl/glyphitem.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+#include <vcl/quickselectionengine.hxx>
+
+#include <set>
+#include <vector>
+#include <memory>
+
+class ScrollBar;
+class ScrollBarBox;
+
+#define HORZ_SCROLL 4
+#define IMG_TXT_DISTANCE 6
+
+enum LB_EVENT_TYPE
+{
+ LET_MBDOWN,
+ LET_TRACKING,
+ LET_KEYMOVE,
+ LET_KEYSPACE
+};
+
+struct ImplEntryType
+{
+ OUString maStr;
+ SalLayoutGlyphs maStrGlyphs;
+ Image maImage;
+ void* mpUserData;
+ bool mbIsSelected;
+ ListBoxEntryFlags mnFlags;
+ tools::Long mnHeight;
+
+ tools::Long getHeightWithMargin() const;
+
+ ImplEntryType( OUString aStr, Image aImage ) :
+ maStr(std::move( aStr )),
+ maImage(std::move( aImage )),
+ mnFlags( ListBoxEntryFlags::NONE ),
+ mnHeight( 0 )
+ {
+ mbIsSelected = false;
+ mpUserData = nullptr;
+ }
+
+ ImplEntryType( OUString aStr ) :
+ maStr(std::move( aStr )),
+ mnFlags( ListBoxEntryFlags::NONE ),
+ mnHeight( 0 )
+ {
+ mbIsSelected = false;
+ mpUserData = nullptr;
+ }
+
+ /// Computes maStr's text layout (glyphs), cached in maStrGlyphs.
+ SalLayoutGlyphs* GetTextGlyphs(const OutputDevice* pOutputDevice);
+};
+
+class ImplEntryList
+{
+private:
+ VclPtr<vcl::Window> mpWindow; ///< For getting the current locale when matching strings
+ sal_Int32 mnLastSelected;
+ sal_Int32 mnSelectionAnchor;
+ sal_Int32 mnImages;
+
+ sal_Int32 mnMRUCount;
+ sal_Int32 mnMaxMRUCount;
+
+ Link<sal_Int32,void> maSelectionChangedHdl;
+ bool mbCallSelectionChangedHdl;
+ std::vector<std::unique_ptr<ImplEntryType> > maEntries;
+
+ ImplEntryType* GetEntry( sal_Int32 nPos ) const
+ {
+ if (nPos < 0 || o3tl::make_unsigned(nPos) >= maEntries.size())
+ return nullptr;
+ return maEntries[nPos].get();
+ }
+
+public:
+ ImplEntryList( vcl::Window* pWindow );
+ ~ImplEntryList();
+
+ sal_Int32 InsertEntry( sal_Int32 nPos, ImplEntryType* pNewEntry, bool bSort );
+ void RemoveEntry( sal_Int32 nPos );
+ const ImplEntryType* GetEntryPtr( sal_Int32 nPos ) const { return GetEntry( nPos ); }
+ ImplEntryType* GetMutableEntryPtr( sal_Int32 nPos ) const { return GetEntry( nPos ); }
+ void Clear();
+ void dispose();
+
+ sal_Int32 FindMatchingEntry( const OUString& rStr, sal_Int32 nStart, bool bLazy ) const;
+ sal_Int32 FindEntry( std::u16string_view rStr, bool bSearchMRUArea = false ) const;
+
+ /// helper: add up heights up to index nEndIndex.
+ /// GetAddedHeight( 0 ) @return 0
+ /// GetAddedHeight( LISTBOX_ENTRY_NOTFOUND ) @return 0
+ /// GetAddedHeight( i, k ) with k > i is equivalent -GetAddedHeight( k, i )
+ tools::Long GetAddedHeight( sal_Int32 nEndIndex, sal_Int32 nBeginIndex ) const;
+ tools::Long GetEntryHeight( sal_Int32 nPos ) const;
+
+ sal_Int32 GetEntryCount() const { return static_cast<sal_Int32>(maEntries.size()); }
+ bool HasImages() const { return mnImages != 0; }
+
+ OUString GetEntryText( sal_Int32 nPos ) const;
+
+ bool HasEntryImage( sal_Int32 nPos ) const;
+ Image GetEntryImage( sal_Int32 nPos ) const;
+
+ void SetEntryData( sal_Int32 nPos, void* pNewData );
+ void* GetEntryData( sal_Int32 nPos ) const;
+
+ void SetEntryFlags( sal_Int32 nPos, ListBoxEntryFlags nFlags );
+
+ void SelectEntry( sal_Int32 nPos, bool bSelect );
+
+ sal_Int32 GetSelectedEntryCount() const;
+ OUString GetSelectedEntry( sal_Int32 nIndex ) const;
+ sal_Int32 GetSelectedEntryPos( sal_Int32 nIndex ) const;
+ bool IsEntryPosSelected( sal_Int32 nIndex ) const;
+
+ void SetLastSelected( sal_Int32 nPos ) { mnLastSelected = nPos; }
+ sal_Int32 GetLastSelected() const { return mnLastSelected; }
+
+ void SetSelectionAnchor( sal_Int32 nPos ) { mnSelectionAnchor = nPos; }
+ sal_Int32 GetSelectionAnchor() const { return mnSelectionAnchor; }
+
+ void SetSelectionChangedHdl( const Link<sal_Int32,void>& rLnk ) { maSelectionChangedHdl = rLnk; }
+ void SetCallSelectionChangedHdl( bool bCall ) { mbCallSelectionChangedHdl = bCall; }
+
+ void SetMRUCount( sal_Int32 n ) { mnMRUCount = n; }
+ sal_Int32 GetMRUCount() const { return mnMRUCount; }
+
+ void SetMaxMRUCount( sal_Int32 n ) { mnMaxMRUCount = n; }
+ sal_Int32 GetMaxMRUCount() const { return mnMaxMRUCount; }
+
+ /** An Entry is selectable if its mnFlags does not have the
+ ListBoxEntryFlags::DisableSelection flag set. */
+ bool IsEntrySelectable( sal_Int32 nPos ) const;
+
+ /** @return the first entry found from the given position nPos that is selectable
+ or LISTBOX_ENTRY_NOTFOUND if non is found. If the entry at nPos is not selectable,
+ it returns the first selectable entry after nPos if bForward is true and the
+ first selectable entry after nPos is bForward is false.
+ */
+ sal_Int32 FindFirstSelectable( sal_Int32 nPos, bool bForward = true ) const;
+};
+
+class ImplListBoxWindow final : public Control, public vcl::ISearchableStringList
+{
+private:
+ ImplEntryList maEntryList; ///< EntryList
+ tools::Rectangle maFocusRect;
+
+ Size maUserItemSize;
+
+ tools::Long mnMaxTxtHeight; ///< Maximum height of a text item
+ tools::Long mnMaxTxtWidth; ///< Maximum width of a text item
+ ///< Entry without Image
+ tools::Long mnMaxImgTxtWidth;///< Maximum width of a text item
+ ///< Entry AND Image
+ tools::Long mnMaxImgWidth; ///< Maximum width of an image item
+ tools::Long mnMaxImgHeight; ///< Maximum height of an image item
+ tools::Long mnMaxWidth; ///< Maximum width of an entry
+ tools::Long mnMaxHeight; ///< Maximum height of an entry
+
+ sal_Int32 mnCurrentPos; ///< Position (Focus)
+ sal_Int32 mnTrackingSaveSelection; ///< Selection before Tracking();
+
+ std::set< sal_Int32 > maSeparators; ///< Separator positions
+
+ sal_Int32 mnUserDrawEntry;
+
+ sal_Int32 mnTop; ///< output from line on
+ tools::Long mnLeft; ///< output from column on
+ tools::Long mnTextHeight; ///< text height
+
+ sal_uInt16 mnSelectModifier; ///< Modifiers
+
+ bool mbHasFocusRect : 1;
+ bool mbSort : 1; ///< ListBox sorted
+ bool mbTrack : 1; ///< Tracking
+ bool mbMulti : 1; ///< MultiListBox
+ bool mbSimpleMode : 1; ///< SimpleMode for MultiListBox
+ bool mbTravelSelect : 1; ///< TravelSelect
+ bool mbTrackingSelect : 1; ///< Selected at a MouseMove
+ bool mbSelectionChanged : 1; ///< Do not call Select() too often ...
+ bool mbMouseMoveSelect : 1; ///< Select at MouseMove
+ bool mbGrabFocus : 1; ///< Grab focus at MBDown
+ bool mbUserDrawEnabled : 1; ///< UserDraw possible
+ bool mbInUserDraw : 1; ///< In UserDraw
+ bool mbReadOnly : 1; ///< ReadOnly
+ bool mbCenter : 1; ///< center Text output
+ bool mbRight : 1; ///< right align Text output
+ bool mbEdgeBlending : 1;
+ /// Listbox is actually a dropdown (either combobox, or popup window treated as dropdown)
+ bool mbIsDropdown : 1;
+
+ Link<ImplListBoxWindow*,void> maScrollHdl;
+ Link<LinkParamNone*,void> maSelectHdl;
+ Link<LinkParamNone*,void> maCancelHdl;
+ Link<ImplListBoxWindow*,void> maDoubleClickHdl;
+ Link<UserDrawEvent*, void> maUserDrawHdl;
+ Link<LinkParamNone*,void> maMRUChangedHdl;
+ Link<sal_Int32,void> maFocusHdl;
+ Link<LinkParamNone*,void> maListItemSelectHdl;
+
+ vcl::QuickSelectionEngine maQuickSelectionEngine;
+
+ virtual void KeyInput( const KeyEvent& rKEvt ) override;
+ virtual void MouseButtonDown( const MouseEvent& rMEvt ) override;
+ virtual void MouseMove( const MouseEvent& rMEvt ) override;
+ virtual void Tracking( const TrackingEvent& rTEvt ) override;
+ virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override;
+ virtual void Resize() override;
+ virtual void GetFocus() override;
+ virtual void LoseFocus() override;
+
+ bool SelectEntries( sal_Int32 nSelect, LB_EVENT_TYPE eLET, bool bShift = false, bool bCtrl = false, bool bSelectPosChange = false );
+ void ImplPaint(vcl::RenderContext& rRenderContext, sal_Int32 nPos);
+ void ImplDoPaint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect);
+ void ImplCalcMetrics();
+ void ImplUpdateEntryMetrics( ImplEntryType& rEntry );
+ void ImplCallSelect();
+
+ void ImplShowFocusRect();
+ void ImplHideFocusRect();
+
+ virtual void StateChanged( StateChangedType nType ) override;
+ virtual void DataChanged( const DataChangedEvent& rDCEvt ) override;
+
+public:
+ virtual void FillLayoutData() const override;
+
+ ImplListBoxWindow( vcl::Window* pParent, WinBits nWinStyle );
+ virtual ~ImplListBoxWindow() override;
+ virtual void dispose() override;
+
+ const ImplEntryList& GetEntryList() const { return maEntryList; }
+ ImplEntryList& GetEntryList() { return maEntryList; }
+
+ sal_Int32 InsertEntry( sal_Int32 nPos, ImplEntryType* pNewEntry ); // sorts using mbSort
+ sal_Int32 InsertEntry( sal_Int32 nPos, ImplEntryType* pNewEntry, bool bSort ); // to insert ignoring mbSort, e.g. mru
+ void RemoveEntry( sal_Int32 nPos );
+ void Clear();
+ void ResetCurrentPos() { mnCurrentPos = LISTBOX_ENTRY_NOTFOUND; }
+ sal_Int32 GetCurrentPos() const { return mnCurrentPos; }
+ sal_uInt16 GetDisplayLineCount() const;
+ void SetEntryFlags( sal_Int32 nPos, ListBoxEntryFlags nFlags );
+
+ void DrawEntry(vcl::RenderContext& rRenderContext, sal_Int32 nPos, bool bDrawImage, bool bDrawText);
+
+ void SelectEntry( sal_Int32 nPos, bool bSelect );
+ void DeselectAll();
+ sal_Int32 GetEntryPosForPoint( const Point& rPoint ) const;
+ sal_Int32 GetLastVisibleEntry() const;
+
+ bool ProcessKeyInput( const KeyEvent& rKEvt );
+
+ void SetTopEntry( sal_Int32 nTop );
+ sal_Int32 GetTopEntry() const { return mnTop; }
+ /** ShowProminentEntry will set the entry corresponding to nEntryPos
+ either at top or in the middle depending on the chosen style*/
+ void ShowProminentEntry( sal_Int32 nEntryPos );
+ using Window::IsVisible;
+ bool IsVisible( sal_Int32 nEntry ) const;
+
+ tools::Long GetLeftIndent() const { return mnLeft; }
+ void SetLeftIndent( tools::Long n );
+ void ScrollHorz( tools::Long nDiff );
+
+ void AllowGrabFocus( bool b ) { mbGrabFocus = b; }
+ bool IsGrabFocusAllowed() const { return mbGrabFocus; }
+
+ /**
+ * Removes existing separators, and sets the position of the
+ * one and only separator.
+ */
+ void SetSeparatorPos( sal_Int32 n );
+ /**
+ * Gets the position of the separator which was added first.
+ * Returns LISTBOX_ENTRY_NOTFOUND if there is no separator.
+ */
+ sal_Int32 GetSeparatorPos() const;
+
+ /**
+ * Adds a new separator at the given position n.
+ */
+ void AddSeparator( sal_Int32 n ) { maSeparators.insert( n ); }
+ /**
+ * Checks if the given number n is an element of the separator positions set.
+ */
+ bool isSeparator( const sal_Int32 &n ) const;
+
+ void SetTravelSelect( bool bTravelSelect ) { mbTravelSelect = bTravelSelect; }
+ bool IsTravelSelect() const { return mbTravelSelect; }
+ bool IsTrackingSelect() const { return mbTrackingSelect; }
+
+ void SetUserItemSize( const Size& rSz );
+
+ void EnableUserDraw( bool bUserDraw ) { mbUserDrawEnabled = bUserDraw; }
+ bool IsUserDrawEnabled() const { return mbUserDrawEnabled; }
+
+ void EnableMultiSelection( bool bMulti ) { mbMulti = bMulti; }
+ bool IsMultiSelectionEnabled() const { return mbMulti; }
+
+ void SetMultiSelectionSimpleMode( bool bSimple ) { mbSimpleMode = bSimple; }
+
+ void EnableMouseMoveSelect( bool bMouseMoveSelect ) { mbMouseMoveSelect = bMouseMoveSelect; }
+ bool IsMouseMoveSelect() const { return mbMouseMoveSelect; }
+
+ Size CalcSize(sal_Int32 nMaxLines) const;
+ tools::Rectangle GetBoundingRectangle( sal_Int32 nItem ) const;
+
+ tools::Long GetEntryHeight() const { return mnMaxHeight; }
+ tools::Long GetEntryHeightWithMargin() const;
+ tools::Long GetMaxEntryWidth() const { return mnMaxWidth; }
+
+ void SetScrollHdl( const Link<ImplListBoxWindow*,void>& rLink ) { maScrollHdl = rLink; }
+ void SetSelectHdl( const Link<LinkParamNone*,void>& rLink ) { maSelectHdl = rLink; }
+ void SetCancelHdl( const Link<LinkParamNone*,void>& rLink ) { maCancelHdl = rLink; }
+ void SetDoubleClickHdl( const Link<ImplListBoxWindow*,void>& rLink ) { maDoubleClickHdl = rLink; }
+ void SetUserDrawHdl( const Link<UserDrawEvent*, void>& rLink ) { maUserDrawHdl = rLink; }
+ void SetMRUChangedHdl( const Link<LinkParamNone*,void>& rLink ) { maMRUChangedHdl = rLink; }
+ void SetFocusHdl( const Link<sal_Int32,void>& rLink ) { maFocusHdl = rLink ; }
+
+ void SetListItemSelectHdl( const Link<LinkParamNone*,void>& rLink ) { maListItemSelectHdl = rLink ; }
+ bool IsSelectionChanged() const { return mbSelectionChanged; }
+ sal_uInt16 GetSelectModifier() const { return mnSelectModifier; }
+
+ void EnableSort( bool b ) { mbSort = b; }
+
+ void SetReadOnly( bool bReadOnly ) { mbReadOnly = bReadOnly; }
+ bool IsReadOnly() const { return mbReadOnly; }
+
+ DrawTextFlags ImplGetTextStyle() const;
+
+ bool GetEdgeBlending() const { return mbEdgeBlending; }
+ void SetEdgeBlending(bool bNew) { mbEdgeBlending = bNew; }
+
+ using Control::ImplInitSettings;
+ virtual void ApplySettings(vcl::RenderContext& rRenderContext) override;
+
+private:
+ // ISearchableStringList
+ virtual vcl::StringEntryIdentifier CurrentEntry( OUString& _out_entryText ) const override;
+ virtual vcl::StringEntryIdentifier NextEntry( vcl::StringEntryIdentifier _currentEntry, OUString& _out_entryText ) const override;
+ virtual void SelectEntry( vcl::StringEntryIdentifier _entry ) override;
+};
+
+class ImplListBox final : public Control
+{
+private:
+ VclPtr<ImplListBoxWindow> maLBWindow;
+ VclPtr<ScrollBar> mpHScrollBar;
+ VclPtr<ScrollBar> mpVScrollBar;
+ VclPtr<ScrollBarBox> mpScrollBarBox;
+
+ bool mbVScroll : 1; // VScroll on or off
+ bool mbHScroll : 1; // HScroll on or off
+ bool mbAutoHScroll : 1; // AutoHScroll on or off
+ bool mbEdgeBlending : 1;
+
+ Link<ImplListBox*,void> maScrollHdl; // because it is needed by ImplListBoxWindow itself
+
+ virtual void GetFocus() override;
+ virtual void StateChanged( StateChangedType nType ) override;
+
+ virtual bool EventNotify( NotifyEvent& rNEvt ) override;
+
+ void ImplResizeControls();
+ void ImplCheckScrollBars();
+ void ImplInitScrollBars();
+
+ DECL_LINK( ScrollBarHdl, ScrollBar*, void );
+ DECL_LINK( LBWindowScrolled, ImplListBoxWindow*, void );
+ DECL_LINK( MRUChanged, LinkParamNone*, void );
+
+public:
+ ImplListBox( vcl::Window* pParent, WinBits nWinStyle );
+ virtual ~ImplListBox() override;
+ virtual void dispose() override;
+
+ const ImplEntryList& GetEntryList() const { return maLBWindow->GetEntryList(); }
+ ImplListBoxWindow* GetMainWindow() { return maLBWindow.get(); }
+
+ virtual void Resize() override;
+ virtual const Wallpaper& GetDisplayBackground() const override;
+
+ sal_Int32 InsertEntry( sal_Int32 nPos, const OUString& rStr );
+ sal_Int32 InsertEntry( sal_Int32 nPos, const OUString& rStr, const Image& rImage );
+ void RemoveEntry( sal_Int32 nPos );
+ void SetEntryData( sal_Int32 nPos, void* pNewData ) { maLBWindow->GetEntryList().SetEntryData( nPos, pNewData ); }
+ void Clear();
+
+ void SetEntryFlags( sal_Int32 nPos, ListBoxEntryFlags nFlags );
+
+ void SelectEntry( sal_Int32 nPos, bool bSelect );
+ void SetNoSelection();
+ void ResetCurrentPos() { maLBWindow->ResetCurrentPos(); }
+ sal_Int32 GetCurrentPos() const { return maLBWindow->GetCurrentPos(); }
+
+ bool ProcessKeyInput( const KeyEvent& rKEvt ) { return maLBWindow->ProcessKeyInput( rKEvt ); }
+ bool HandleWheelAsCursorTravel(const CommandEvent& rCEvt, Control& rControl);
+
+ /**
+ * Removes existing separators, and sets the position of the
+ * one and only separator.
+ */
+ void SetSeparatorPos( sal_Int32 n ) { maLBWindow->SetSeparatorPos( n ); }
+ /**
+ * Gets the position of the separator which was added first.
+ * Returns LISTBOX_ENTRY_NOTFOUND if there is no separator.
+ */
+ sal_Int32 GetSeparatorPos() const { return maLBWindow->GetSeparatorPos(); }
+
+ /**
+ * Adds a new separator at the given position n.
+ */
+ void AddSeparator( sal_Int32 n ) { maLBWindow->AddSeparator( n ); }
+
+ void SetTopEntry( sal_Int32 nTop ) { maLBWindow->SetTopEntry( nTop ); }
+ sal_Int32 GetTopEntry() const { return maLBWindow->GetTopEntry(); }
+ void ShowProminentEntry( sal_Int32 nPos ) { maLBWindow->ShowProminentEntry( nPos ); }
+ using Window::IsVisible;
+ bool IsVisible( sal_Int32 nEntry ) const { return maLBWindow->IsVisible( nEntry ); }
+
+ tools::Long GetLeftIndent() const { return maLBWindow->GetLeftIndent(); }
+ void SetLeftIndent( sal_uInt16 n ) { maLBWindow->SetLeftIndent( n ); }
+
+ void SetTravelSelect( bool bTravelSelect ) { maLBWindow->SetTravelSelect( bTravelSelect ); }
+ bool IsTravelSelect() const { return maLBWindow->IsTravelSelect(); }
+ bool IsTrackingSelect() const { return maLBWindow->IsTrackingSelect(); }
+
+ void EnableMultiSelection( bool bMulti ) { maLBWindow->EnableMultiSelection( bMulti ); }
+ bool IsMultiSelectionEnabled() const { return maLBWindow->IsMultiSelectionEnabled(); }
+
+ void SetMultiSelectionSimpleMode( bool bSimple ) { maLBWindow->SetMultiSelectionSimpleMode( bSimple ); }
+
+ void SetReadOnly( bool b ) { maLBWindow->SetReadOnly( b ); }
+ bool IsReadOnly() const { return maLBWindow->IsReadOnly(); }
+
+ Size CalcSize( sal_Int32 nMaxLines ) const { return maLBWindow->CalcSize( nMaxLines ); }
+ tools::Long GetEntryHeight() const { return maLBWindow->GetEntryHeight(); }
+ tools::Long GetEntryHeightWithMargin() const{ return maLBWindow->GetEntryHeightWithMargin(); }
+ tools::Long GetMaxEntryWidth() const { return maLBWindow->GetMaxEntryWidth(); }
+
+ void SetScrollHdl( const Link<ImplListBox*,void>& rLink ) { maScrollHdl = rLink; }
+ void SetSelectHdl( const Link<LinkParamNone*,void>& rLink ) { maLBWindow->SetSelectHdl( rLink ); }
+ void SetCancelHdl( const Link<LinkParamNone*,void>& rLink ) { maLBWindow->SetCancelHdl( rLink ); }
+ void SetDoubleClickHdl( const Link<ImplListBoxWindow*,void>& rLink ) { maLBWindow->SetDoubleClickHdl( rLink ); }
+ void SetUserDrawHdl( const Link<UserDrawEvent*, void>& rLink ) { maLBWindow->SetUserDrawHdl( rLink ); }
+ void SetFocusHdl( const Link<sal_Int32,void>& rLink ) { maLBWindow->SetFocusHdl( rLink ); }
+ void SetListItemSelectHdl( const Link<LinkParamNone*,void>& rLink ) { maLBWindow->SetListItemSelectHdl( rLink ); }
+ void SetSelectionChangedHdl( const Link<sal_Int32,void>& rLnk ) { maLBWindow->GetEntryList().SetSelectionChangedHdl( rLnk ); }
+ void SetCallSelectionChangedHdl( bool bCall ) { maLBWindow->GetEntryList().SetCallSelectionChangedHdl( bCall ); }
+ bool IsSelectionChanged() const { return maLBWindow->IsSelectionChanged(); }
+ sal_uInt16 GetSelectModifier() const { return maLBWindow->GetSelectModifier(); }
+ void SetHighlightColor(const Color& rColor);
+ void SetHighlightTextColor(const Color& rColor);
+
+ void SetMRUEntries( std::u16string_view rEntries, sal_Unicode cSep );
+ OUString GetMRUEntries( sal_Unicode cSep ) const;
+ void SetMaxMRUCount( sal_Int32 n ) { maLBWindow->GetEntryList().SetMaxMRUCount( n ); }
+ sal_Int32 GetMaxMRUCount() const { return maLBWindow->GetEntryList().GetMaxMRUCount(); }
+ sal_uInt16 GetDisplayLineCount() const
+ { return maLBWindow->GetDisplayLineCount(); }
+
+ bool GetEdgeBlending() const { return mbEdgeBlending; }
+ void SetEdgeBlending(bool bNew);
+};
+
+class ImplListBoxFloatingWindow final : public FloatingWindow
+{
+private:
+ VclPtr<ImplListBox> mpImplLB;
+ Size maPrefSz;
+ sal_uInt16 mnDDLineCount;
+ sal_Int32 mnPopupModeStartSaveSelection;
+ bool mbAutoWidth;
+
+ virtual bool PreNotify( NotifyEvent& rNEvt ) override;
+
+public:
+ ImplListBoxFloatingWindow( vcl::Window* pParent );
+ virtual ~ImplListBoxFloatingWindow() override;
+ virtual void dispose() override;
+ void SetImplListBox( ImplListBox* pLB ) { mpImplLB = pLB; }
+
+ void SetPrefSize( const Size& rSz ) { maPrefSz = rSz; }
+ const Size& GetPrefSize() const { return maPrefSz; }
+
+ void SetAutoWidth( bool b ) { mbAutoWidth = b; }
+
+ Size CalcFloatSize() const;
+ void StartFloat( bool bStartTracking );
+
+ virtual void setPosSizePixel( tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight, PosSizeFlags nFlags = PosSizeFlags::All ) override;
+
+ void SetDropDownLineCount( sal_uInt16 n ) { mnDDLineCount = n; }
+ sal_uInt16 GetDropDownLineCount() const { return mnDDLineCount; }
+
+ sal_Int32 GetPopupModeStartSaveSelection() const { return mnPopupModeStartSaveSelection; }
+
+ virtual void Resize() override;
+};
+
+class ImplWin final : public Control
+{
+private:
+
+ sal_Int32 mnItemPos; ///< because of UserDraw I have to know which item I draw
+ OUString maString;
+ Image maImage;
+
+ tools::Rectangle maFocusRect;
+
+ Link<void*,void> maMBDownHdl;
+
+ bool mbEdgeBlending : 1;
+
+ void ImplDraw(vcl::RenderContext& rRenderContext, bool bLayout = false);
+ virtual void FillLayoutData() const override;
+
+public:
+ ImplWin( vcl::Window* pParent, WinBits nWinStyle );
+
+ virtual void MouseButtonDown( const MouseEvent& rMEvt ) override;
+ virtual void Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) override;
+ virtual void Resize() override;
+ virtual void GetFocus() override;
+ virtual void LoseFocus() override;
+
+ sal_Int32 GetItemPos() const { return mnItemPos; }
+ void SetItemPos( sal_Int32 n ) { mnItemPos = n; }
+
+ void SetString( const OUString& rStr ) { maString = rStr; }
+
+ void SetImage( const Image& rImg ) { maImage = rImg; }
+
+ void SetMBDownHdl( const Link<void*,void>& rLink ) { maMBDownHdl = rLink; }
+
+ void DrawEntry(vcl::RenderContext& rRenderContext, bool bLayout);
+
+ bool GetEdgeBlending() const { return mbEdgeBlending; }
+ void SetEdgeBlending(bool bNew) { mbEdgeBlending = bNew; }
+
+ virtual void ShowFocus(const tools::Rectangle& rRect) override;
+
+ using Control::ImplInitSettings;
+ virtual void ApplySettings(vcl::RenderContext& rRenderContext) override;
+
+};
+
+class ImplBtn final : public PushButton
+{
+private:
+ Link<void*,void> maMBDownHdl;
+
+public:
+ ImplBtn( vcl::Window* pParent, WinBits nWinStyle );
+
+ virtual void MouseButtonDown( const MouseEvent& rMEvt ) override;
+ void SetMBDownHdl( const Link<void*,void>& rLink ) { maMBDownHdl = rLink; }
+};
+
+void ImplInitDropDownButton( PushButton* pButton );
+
+#endif // INCLUDED_VCL_INC_LISTBOX_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/managedmenubutton.hxx b/vcl/inc/managedmenubutton.hxx
new file mode 100644
index 0000000000..0f6492b6fd
--- /dev/null
+++ b/vcl/inc/managedmenubutton.hxx
@@ -0,0 +1,31 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <vcl/toolkit/menubtn.hxx>
+#include <com/sun/star/awt/XPopupMenu.hpp>
+#include <com/sun/star/frame/XPopupMenuController.hpp>
+
+class ManagedMenuButton final : public MenuButton
+{
+public:
+ ManagedMenuButton(vcl::Window* pParent, WinBits nStyle);
+ ~ManagedMenuButton() override;
+
+ void dispose() override;
+
+private:
+ void PrepareExecute() override;
+
+ css::uno::Reference<css::awt::XPopupMenu> m_xPopupMenu;
+ css::uno::Reference<css::frame::XPopupMenuController> m_xPopupController;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/menubarvalue.hxx b/vcl/inc/menubarvalue.hxx
new file mode 100644
index 0000000000..0b9f0dbbec
--- /dev/null
+++ b/vcl/inc/menubarvalue.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_MENUBARVALUE_HXX
+#define INCLUDED_VCL_INC_MENUBARVALUE_HXX
+
+#include <vcl/salnativewidgets.hxx>
+
+/* MenubarValue:
+ *
+ * Value container for menubars specifying height of adjacent docking area
+ */
+class MenubarValue final : public ImplControlValue
+{
+public:
+ MenubarValue()
+ : ImplControlValue(ControlType::Menubar, 0)
+ {
+ maTopDockingAreaHeight = 0;
+ }
+ virtual ~MenubarValue() override;
+ virtual MenubarValue* clone() const override;
+ MenubarValue(MenubarValue const&) = default;
+ MenubarValue(MenubarValue&&) = default;
+ MenubarValue& operator=(MenubarValue const&) = delete; // due to ImplControlValue
+ MenubarValue& operator=(MenubarValue&&) = delete; // due to ImplControlValue
+ int maTopDockingAreaHeight;
+};
+
+#endif // INCLUDED_VCL_INC_MENUBARVALUE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/menutogglebutton.hxx b/vcl/inc/menutogglebutton.hxx
new file mode 100644
index 0000000000..c1b5aef544
--- /dev/null
+++ b/vcl/inc/menutogglebutton.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/toolkit/menubtn.hxx>
+
+class MenuToggleButton final : public MenuButton
+{
+public:
+ explicit MenuToggleButton(vcl::Window* pParent, WinBits nStyle);
+ virtual ~MenuToggleButton() override;
+
+ void SetActive(bool bSel);
+ bool GetActive() const;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/messagedialog.hxx b/vcl/inc/messagedialog.hxx
new file mode 100644
index 0000000000..9c265c0570
--- /dev/null
+++ b/vcl/inc/messagedialog.hxx
@@ -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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_MESSAGEDIALOG_HXX
+#define INCLUDED_VCL_INC_MESSAGEDIALOG_HXX
+
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/toolkit/vclmedit.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/toolkit/fixed.hxx>
+
+class MessageDialog final : public Dialog
+{
+private:
+ VclButtonsType m_eButtonsType;
+ VclMessageType m_eMessageType;
+ VclPtr<VclBox> m_pOwnedContentArea;
+ VclPtr<VclButtonBox> m_pOwnedActionArea;
+ VclPtr<VclGrid> m_pGrid;
+ VclPtr<VclVBox> m_pMessageBox;
+ VclPtr<FixedImage> m_pImage;
+ VclPtr<VclMultiLineEdit> m_pPrimaryMessage;
+ VclPtr<VclMultiLineEdit> m_pSecondaryMessage;
+ OUString m_sPrimaryString;
+ OUString m_sSecondaryString;
+ void create_owned_areas();
+
+ static void SetMessagesWidths(vcl::Window const* pParent, VclMultiLineEdit* pPrimaryMessage,
+ VclMultiLineEdit* pSecondaryMessage);
+
+ friend class VclPtr<MessageDialog>;
+ MessageDialog(vcl::Window* pParent, WinBits nStyle);
+
+ virtual void StateChanged(StateChangedType nType) override;
+
+public:
+ MessageDialog(vcl::Window* pParent, OUString aMessage, VclMessageType eMessageType,
+ VclButtonsType eButtonsType);
+ virtual bool set_property(const OUString& rKey, const OUString& rValue) override;
+ OUString const& get_primary_text() const;
+ OUString const& get_secondary_text() const;
+ void set_primary_text(const OUString& rPrimaryString);
+ void set_secondary_text(const OUString& rSecondaryString);
+ virtual ~MessageDialog() override;
+ virtual void dispose() override;
+
+ void create_message_area();
+ VclContainer* get_message_area() const { return m_pMessageBox.get(); }
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/win/WinDeviceInfo.hxx b/vcl/inc/opengl/win/WinDeviceInfo.hxx
new file mode 100644
index 0000000000..2f9a52596b
--- /dev/null
+++ b/vcl/inc/opengl/win/WinDeviceInfo.hxx
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_OPENGL_WIN_WINDEVICEINFO_HXX
+#define INCLUDED_VCL_OPENGL_WIN_WINDEVICEINFO_HXX
+
+#include <vcl/dllapi.h>
+
+#include <rtl/ustring.hxx>
+
+class VCL_DLLPUBLIC WinOpenGLDeviceInfo
+{
+private:
+ OUString maDriverVersion;
+ OUString maDriverVersion2;
+
+ OUString maDriverDate;
+ OUString maDriverDate2;
+
+ OUString maDeviceID;
+ OUString maDeviceID2;
+
+ OUString maAdapterVendorID;
+ OUString maAdapterDeviceID;
+ OUString maAdapterSubsysID;
+
+ OUString maAdapterVendorID2;
+ OUString maAdapterDeviceID2;
+ OUString maAdapterSubsysID2;
+
+ OUString maDeviceKey;
+ OUString maDeviceKey2;
+
+ OUString maDeviceString;
+ OUString maDeviceString2;
+
+ bool mbHasDualGPU;
+ bool mbRDP;
+
+ void GetData();
+ bool FindBlocklistedDeviceInList();
+
+public:
+ WinOpenGLDeviceInfo();
+
+ bool isDeviceBlocked();
+
+ const OUString& GetDriverVersion() const
+ {
+ return maDriverVersion;
+ }
+
+ const OUString& GetDriverDate() const
+ {
+ return maDriverDate;
+ }
+
+ const OUString& GetDeviceID() const
+ {
+ return maDeviceID;
+ }
+
+ const OUString& GetAdapterVendorID() const
+ {
+ return maAdapterVendorID;
+ }
+
+ const OUString& GetAdapterDeviceID() const
+ {
+ return maAdapterDeviceID;
+ }
+
+ const OUString& GetAdapterSubsysID() const
+ {
+ return maAdapterSubsysID;
+ }
+ const OUString& GetDeviceKey() const
+ {
+ return maDeviceKey;
+ }
+
+ const OUString& GetDeviceString() const
+ {
+ return maDeviceString;
+ }
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/zone.hxx b/vcl/inc/opengl/zone.hxx
new file mode 100644
index 0000000000..f03e07ccfd
--- /dev/null
+++ b/vcl/inc/opengl/zone.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_ZONE_H
+#define INCLUDED_VCL_INC_OPENGL_ZONE_H
+
+#include <sal/config.h>
+#include <vcl/dllapi.h>
+#include <comphelper/crashzone.hxx>
+
+/**
+ * We want to be able to detect if a given crash came
+ * from the OpenGL code, so use this helper to track that.
+ */
+class VCL_DLLPUBLIC OpenGLZone : public CrashZone<OpenGLZone>
+{
+public:
+ static void hardDisable();
+ static void relaxWatchdogTimings();
+ static const CrashWatchdogTimingsValues& getCrashWatchdogTimingsValues();
+ static void checkDebug(int nUnchanged, const CrashWatchdogTimingsValues& aTimingValues);
+ static const char* name() { return "OpenGL"; }
+};
+
+#endif // INCLUDED_VCL_INC_OPENGL_ZONE_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/a11yfactory.h b/vcl/inc/osx/a11yfactory.h
new file mode 100644
index 0000000000..9ef837dfaa
--- /dev/null
+++ b/vcl/inc/osx/a11yfactory.h
@@ -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 .
+ */
+
+#pragma once
+
+#include "osxvcltypes.h"
+#include "a11ywrapper.h"
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+
+@interface AquaA11yFactory : NSObject
+{
+}
++(void)insertIntoWrapperRepository: (AquaA11yWrapper *) element forAccessibleContext: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext;
++(AquaA11yWrapper *)wrapperForAccessible: (css::uno::Reference < css::accessibility::XAccessible >) rxAccessible;
++(AquaA11yWrapper *)wrapperForAccessibleContext: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext;
++(AquaA11yWrapper *)wrapperForAccessibleContext: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext createIfNotExists:(BOOL) bCreate;
++(AquaA11yWrapper *)wrapperForAccessibleContext: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext createIfNotExists:(BOOL) bCreate asRadioGroup:(BOOL) asRadioGroup;
++(void)removeFromWrapperRepositoryFor: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext;
++(void)registerWrapper: (AquaA11yWrapper *) theWrapper;
++(void)revokeWrapper: (AquaA11yWrapper *) theWrapper;
+@end
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/a11yfocustracker.hxx b/vcl/inc/osx/a11yfocustracker.hxx
new file mode 100644
index 0000000000..aafa9944de
--- /dev/null
+++ b/vcl/inc/osx/a11yfocustracker.hxx
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <com/sun/star/accessibility/XAccessible.hpp>
+
+#include "keyboardfocuslistener.hxx"
+
+#include <tools/link.hxx>
+#include <vcl/vclevent.hxx>
+#include <set>
+
+namespace vcl { class Window; }
+class ToolBox;
+class DocumentFocusListener;
+
+
+class AquaA11yFocusTracker
+{
+
+public:
+ AquaA11yFocusTracker();
+
+ ~AquaA11yFocusTracker();
+
+ css::uno::Reference< css::accessibility::XAccessible > const & getFocusedObject() { return m_xFocusedObject; };
+
+ // sets the currently focus object and notifies the FocusEventListener (if any)
+ void setFocusedObject(const css::uno::Reference< css::accessibility::XAccessible >& xAccessible);
+
+ // may evolve to add/remove later
+ void setFocusListener(const rtl::Reference< KeyboardFocusListener >& aFocusListener) { m_aFocusListener = aFocusListener; };
+
+protected:
+
+ // received a WINDOW_GETFOCUS event for this window
+ void window_got_focus(vcl::Window *pWindow);
+
+ // received a TOOLBOX_HIGHLIGHT event for this window
+ void toolbox_highlight_on(vcl::Window *pWindow);
+
+ // received a TOOLBOX_HIGHLIGHTOFF event for this window
+ void toolbox_highlight_off(vcl::Window const *pWindow);
+
+ // received a TABPAGE_ACTIVATE event for this window
+ void tabpage_activated(vcl::Window *pWindow);
+
+ // received a MENU_HIGHLIGHT event for this window
+ void menu_highlighted(const ::VclMenuEvent *pEvent);
+
+ // toolbox items are widgets in gtk+ and Cocoa
+ void notify_toolbox_item_focus(ToolBox *pToolBox);
+
+ // toolbox item opened a floating window (e.g. color chooser)
+ void toolbox_open_floater(vcl::Window *pWindow);
+
+ // callback function for Application::addEventListener
+ static void WindowEventHandler(void * pThis, VclSimpleEvent&);
+
+private:
+ // the accessible object that has the keyboard focus (if any)
+ css::uno::Reference< css::accessibility::XAccessible > m_xFocusedObject;
+
+ // the listener for focus events
+ rtl::Reference< KeyboardFocusListener > m_aFocusListener;
+
+ // the list of Windows that need deeper (focus) investigation
+ std::set<VclPtr<vcl::Window>> m_aDocumentWindowList;
+
+ // the link object needed for Application::addEventListener
+ Link<VclSimpleEvent&,void> m_aWindowEventLink;
+
+ // the UNO XAccessibilityEventListener for Documents and other non VCL objects
+ const rtl::Reference< DocumentFocusListener > m_xDocumentFocusListener;
+};
+
+AquaA11yFocusTracker& TheAquaA11yFocusTracker();
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/a11ylistener.hxx b/vcl/inc/osx/a11ylistener.hxx
new file mode 100644
index 0000000000..7211e81d46
--- /dev/null
+++ b/vcl/inc/osx/a11ylistener.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 <com/sun/star/accessibility/XAccessibleEventListener.hpp>
+#include <cppuhelper/implbase.hxx>
+
+#include "a11yfocustracker.hxx"
+#include "osxvcltypes.h"
+#include <set>
+#include <com/sun/star/awt/Rectangle.hpp>
+
+class AquaA11yEventListener
+ : public ::cppu::WeakImplHelper<css::accessibility::XAccessibleEventListener>
+{
+public:
+ AquaA11yEventListener(id wrapperObject, sal_Int16 role);
+ virtual ~AquaA11yEventListener() override;
+
+ // XEventListener
+ virtual void SAL_CALL disposing(const css::lang::EventObject& Source) override;
+
+ // XAccessibleEventListener
+ virtual void SAL_CALL
+ notifyEvent(const css::accessibility::AccessibleEventObject& aEvent) override;
+
+private:
+ const id m_wrapperObject;
+ const sal_Int16 m_role;
+ css::awt::Rectangle m_oldBounds;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/a11ywrapper.h b/vcl/inc/osx/a11ywrapper.h
new file mode 100644
index 0000000000..1eb4039c57
--- /dev/null
+++ b/vcl/inc/osx/a11ywrapper.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 "osxvcltypes.h"
+#include <com/sun/star/accessibility/XAccessibleAction.hpp>
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/XAccessibleComponent.hpp>
+#include <com/sun/star/accessibility/XAccessibleExtendedComponent.hpp>
+#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
+#include <com/sun/star/accessibility/XAccessibleTable.hpp>
+#include <com/sun/star/accessibility/XAccessibleText.hpp>
+#include <com/sun/star/accessibility/XAccessibleTextAttributes.hpp>
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+#include <com/sun/star/accessibility/XAccessibleValue.hpp>
+#include <com/sun/star/accessibility/XAccessibleMultiLineText.hpp>
+#include <com/sun/star/accessibility/XAccessibleTextMarkup.hpp>
+
+// rAccessibleXYZ as a field in an Objective-C-Class would not call Con-/Destructor, so use a struct instead
+struct ReferenceWrapper
+{
+ css::uno::Reference < css::accessibility::XAccessibleAction > rAccessibleAction;
+ css::uno::Reference < css::accessibility::XAccessibleContext > rAccessibleContext;
+ css::uno::Reference < css::accessibility::XAccessibleComponent > rAccessibleComponent;
+ css::uno::Reference < css::accessibility::XAccessibleExtendedComponent > rAccessibleExtendedComponent;
+ css::uno::Reference < css::accessibility::XAccessibleSelection > rAccessibleSelection;
+ css::uno::Reference < css::accessibility::XAccessibleTable > rAccessibleTable;
+ css::uno::Reference < css::accessibility::XAccessibleText > rAccessibleText;
+ css::uno::Reference < css::accessibility::XAccessibleEditableText > rAccessibleEditableText;
+ css::uno::Reference < css::accessibility::XAccessibleValue > rAccessibleValue;
+ css::uno::Reference < css::accessibility::XAccessibleTextAttributes > rAccessibleTextAttributes;
+ css::uno::Reference < css::accessibility::XAccessibleMultiLineText > rAccessibleMultiLineText;
+ css::uno::Reference < css::accessibility::XAccessibleTextMarkup > rAccessibleTextMarkup;
+};
+
+@interface AquaA11yWrapper : NSAccessibilityElement
+ <NSAccessibilityElement,
+ NSAccessibilityGroup,
+ NSAccessibilityButton,
+ NSAccessibilitySwitch,
+ NSAccessibilityRadioButton,
+ NSAccessibilityCheckBox,
+ NSAccessibilityStaticText,
+ NSAccessibilityNavigableStaticText,
+ NSAccessibilityProgressIndicator,
+ NSAccessibilityStepper,
+ NSAccessibilitySlider,
+ NSAccessibilityImage>
+{
+ ReferenceWrapper maReferenceWrapper;
+ BOOL mActsAsRadioGroup;
+ BOOL mIsTableCell;
+}
+// NSAccessibility Protocol
+-(id)accessibilityAttributeValue:(NSString *)attribute;
+-(BOOL)accessibilityIsIgnored;
+-(NSArray *)accessibilityAttributeNames;
+-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute;
+-(NSArray *)accessibilityParameterizedAttributeNames;
+-(BOOL)accessibilitySetOverrideValue:(id)value forAttribute:(NSString *)attribute;
+-(void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute;
+-(id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter;
+-(id)accessibilityFocusedUIElement;
+-(NSString *)accessibilityActionDescription:(NSString *)action;
+-(void)accessibilityPerformAction:(NSString *)action;
+-(BOOL)performAction:(NSString *)action;
+-(NSArray *)accessibilityActionNames;
+-(id)accessibilityHitTest:(NSPoint)point;
+// Attribute values
+-(id)parentAttribute;
+-(id)valueAttribute;
+-(id)titleAttribute;
+-(id)helpAttribute;
+-(id)numberOfCharactersAttribute;
+-(id)selectedTextAttribute;
+-(id)selectedTextRangeAttribute;
+-(id)visibleCharacterRangeAttribute;
+-(id)childrenAttribute;
+-(id)orientationAttribute;
+-(id)windowAttribute;
+// Wrapper-specific
+-(void)setActsAsRadioGroup:(BOOL)actsAsRadioGroup;
+-(BOOL)actsAsRadioGroup;
+-(NSWindow*)windowForParent;
+-(id)init;
+-(id)initWithAccessibleContext: (css::uno::Reference < css::accessibility::XAccessibleContext >) anAccessibleContext;
+-(void) setDefaults: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext;
++(void)setPopupMenuOpen:(BOOL)popupMenuOpen;
+-(css::accessibility::XAccessibleAction *)accessibleAction;
+-(css::accessibility::XAccessibleContext *)accessibleContext;
+-(css::accessibility::XAccessibleComponent *)accessibleComponent;
+-(css::accessibility::XAccessibleExtendedComponent *)accessibleExtendedComponent;
+-(css::accessibility::XAccessibleSelection *)accessibleSelection;
+-(css::accessibility::XAccessibleTable *)accessibleTable;
+-(css::accessibility::XAccessibleText *)accessibleText;
+-(css::accessibility::XAccessibleEditableText *)accessibleEditableText;
+-(css::accessibility::XAccessibleValue *)accessibleValue;
+-(css::accessibility::XAccessibleTextAttributes *)accessibleTextAttributes;
+-(css::accessibility::XAccessibleMultiLineText *)accessibleMultiLineText;
+-(css::accessibility::XAccessibleTextMarkup *)accessibleTextMarkup;
+@end
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/keyboardfocuslistener.hxx b/vcl/inc/osx/keyboardfocuslistener.hxx
new file mode 100644
index 0000000000..f94afaaf4f
--- /dev/null
+++ b/vcl/inc/osx/keyboardfocuslistener.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <com/sun/star/accessibility/XAccessible.hpp>
+
+#include <rtl/ref.hxx>
+#include <salhelper/simplereferenceobject.hxx>
+
+class KeyboardFocusListener : public salhelper::SimpleReferenceObject
+{
+public:
+ virtual void
+ focusedObjectChanged(const css::uno::Reference<css::accessibility::XAccessible>& xAccessible)
+ = 0;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/osxvcltypes.h b/vcl/inc/osx/osxvcltypes.h
new file mode 100644
index 0000000000..bf3f4ec46f
--- /dev/null
+++ b/vcl/inc/osx/osxvcltypes.h
@@ -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_INC_OSX_OSXVCLTYPES_H
+#define INCLUDED_VCL_INC_OSX_OSXVCLTYPES_H
+
+#include <premac.h>
+#import <Cocoa/Cocoa.h>
+#import <AppKit/NSEvent.h>
+#include <postmac.h>
+
+#endif // INCLUDED_VCL_INC_OSX_OSXVCLTYPES_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/printview.h b/vcl/inc/osx/printview.h
new file mode 100644
index 0000000000..a2a6e5454e
--- /dev/null
+++ b/vcl/inc/osx/printview.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_OSX_PRINTVIEW_H
+#define INCLUDED_VCL_INC_OSX_PRINTVIEW_H
+
+#include <premac.h>
+#include <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+#include <vcl/print.hxx>
+
+class AquaSalInfoPrinter;
+
+struct PrintAccessoryViewState
+{
+ bool bNeedRestart;
+ sal_Int32 nLastPage;
+
+ PrintAccessoryViewState()
+ : bNeedRestart( false ), nLastPage( 0 ) {}
+};
+
+@interface AquaPrintView : NSView
+{
+ vcl::PrinterController* mpController;
+ AquaSalInfoPrinter* mpInfoPrinter;
+}
+-(id)initWithController: (vcl::PrinterController*)pController
+ withInfoPrinter: (AquaSalInfoPrinter*)pInfoPrinter;
+-(BOOL)knowsPageRange: (NSRangePointer)range;
+-(NSRect)rectForPage: (int)page;
+-(NSPoint)locationOfPrintRect: (NSRect)aRect;
+-(void)drawRect: (NSRect)rect;
+@end
+
+@interface AquaPrintAccessoryView : NSObject
+{
+}
++(NSObject*)setupPrinterPanel: (NSPrintOperation*)pOp
+ withController: (vcl::PrinterController*)pController
+ withState: (PrintAccessoryViewState*)pState;
+@end
+
+#endif // INCLUDED_VCL_INC_OSX_PRINTVIEW_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/runinmain.hxx b/vcl/inc/osx/runinmain.hxx
new file mode 100644
index 0000000000..e68bc4d350
--- /dev/null
+++ b/vcl/inc/osx/runinmain.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_VCL_INC_OSX_RUNINMAIN_HXX
+#define INCLUDED_VCL_INC_OSX_RUNINMAIN_HXX
+
+/**
+ * Runs a command in the main thread.
+ *
+ * These macros are always used like a recursive calls, so they work like
+ * a closure.
+ *
+ * Uses two conditionals for a two way communication.
+ * The data (code block + result) is protected by the SolarMutex.
+ *
+ * There are three main macros, which act as function initializers:
+ * - OSX_RUNINMAIN - for all functions without return values
+ * - OSX_RUNINMAIN_POINTER - for all functions returning a pointer
+ * - OSX_RUNINMAIN_UNION - for all other return types
+ *
+ * All types used via OSX_RUNINMAIN_UNION must implement a move constructor,
+ * so there is no memory leak!
+ */
+
+#include <unordered_map>
+
+#include <Block.h>
+
+#include <osl/thread.h>
+
+#include "saltimer.h"
+#include <salframe.hxx>
+#include <tools/debug.hxx>
+
+union RuninmainResult
+{
+ void* pointer;
+ bool boolean;
+ struct SalFrame::SalPointerState state;
+
+ RuninmainResult() {}
+};
+
+#define OSX_RUNINMAIN_MEMBERS \
+ std::mutex m_runInMainMutex; \
+ std::condition_variable m_aInMainCondition; \
+ std::condition_variable m_aResultCondition; \
+ bool m_wakeUpMain = false; \
+ bool m_resultReady = false; \
+ RuninmainBlock m_aCodeBlock; \
+ RuninmainResult m_aResult;
+
+#define OSX_RUNINMAIN( instance, command ) \
+ if ( !instance->IsMainThread() ) \
+ { \
+ DBG_TESTSOLARMUTEX(); \
+ SalYieldMutex *aMutex = static_cast<SalYieldMutex*>(instance->GetYieldMutex()); \
+ { \
+ std::scoped_lock<std::mutex> g(aMutex->m_runInMainMutex); \
+ assert( !aMutex->m_aCodeBlock ); \
+ aMutex->m_aCodeBlock = Block_copy(^{ \
+ command; \
+ }); \
+ aMutex->m_wakeUpMain = true; \
+ aMutex->m_aInMainCondition.notify_all(); \
+ } \
+ dispatch_async(dispatch_get_main_queue(),^{ \
+ ImplNSAppPostEvent( AquaSalInstance::YieldWakeupEvent, NO ); \
+ }); \
+ { \
+ std::unique_lock<std::mutex> g(aMutex->m_runInMainMutex); \
+ aMutex->m_aResultCondition.wait( \
+ g, [&aMutex]() { return aMutex->m_resultReady; }); \
+ aMutex->m_resultReady = false; \
+ } \
+ return; \
+ }
+
+#define OSX_RUNINMAIN_POINTER( instance, command, type ) \
+ if ( !instance->IsMainThread() ) \
+ { \
+ DBG_TESTSOLARMUTEX(); \
+ SalYieldMutex *aMutex = static_cast<SalYieldMutex*>(instance->GetYieldMutex()); \
+ { \
+ std::scoped_lock<std::mutex> g(aMutex->m_runInMainMutex); \
+ assert( !aMutex->m_aCodeBlock ); \
+ aMutex->m_aCodeBlock = Block_copy(^{ \
+ aMutex->m_aResult.pointer = static_cast<void*>( command ); \
+ }); \
+ aMutex->m_wakeUpMain = true; \
+ aMutex->m_aInMainCondition.notify_all(); \
+ } \
+ dispatch_async(dispatch_get_main_queue(),^{ \
+ ImplNSAppPostEvent( AquaSalInstance::YieldWakeupEvent, NO ); \
+ }); \
+ { \
+ std::unique_lock<std::mutex> g(aMutex->m_runInMainMutex); \
+ aMutex->m_aResultCondition.wait( \
+ g, [&aMutex]() { return aMutex->m_resultReady; }); \
+ aMutex->m_resultReady = false; \
+ } \
+ return static_cast<type>( aMutex->m_aResult.pointer ); \
+ }
+
+#define OSX_RUNINMAIN_UNION( instance, command, member ) \
+ if ( !instance->IsMainThread() ) \
+ { \
+ DBG_TESTSOLARMUTEX(); \
+ SalYieldMutex *aMutex = static_cast<SalYieldMutex*>(instance->GetYieldMutex()); \
+ { \
+ std::scoped_lock<std::mutex> g(aMutex->m_runInMainMutex); \
+ assert( !aMutex->m_aCodeBlock ); \
+ aMutex->m_aCodeBlock = Block_copy(^{ \
+ aMutex->m_aResult.member = command; \
+ }); \
+ aMutex->m_wakeUpMain = true; \
+ aMutex->m_aInMainCondition.notify_all(); \
+ } \
+ dispatch_async(dispatch_get_main_queue(),^{ \
+ ImplNSAppPostEvent( AquaSalInstance::YieldWakeupEvent, NO ); \
+ }); \
+ { \
+ std::unique_lock<std::mutex> g(aMutex->m_runInMainMutex); \
+ aMutex->m_aResultCondition.wait( \
+ g, [&aMutex]() { return aMutex->m_resultReady; }); \
+ aMutex->m_resultReady = false; \
+ } \
+ return std::move( aMutex->m_aResult.member ); \
+ }
+
+/**
+ * convenience macros used from SalInstance
+ */
+
+#define OSX_INST_RUNINMAIN( command ) \
+ OSX_RUNINMAIN( this, command )
+
+#define OSX_INST_RUNINMAIN_POINTER( command, type ) \
+ OSX_RUNINMAIN_POINTER( this, command, type )
+
+#define OSX_INST_RUNINMAIN_UNION( command, member ) \
+ OSX_RUNINMAIN_UNION( this, command, member )
+
+/**
+ * convenience macros using global SalData
+ */
+
+#define OSX_SALDATA_RUNINMAIN( command ) \
+ OSX_RUNINMAIN( GetSalData()->mpInstance, command )
+
+#define OSX_SALDATA_RUNINMAIN_POINTER( command, type ) \
+ OSX_RUNINMAIN_POINTER( GetSalData()->mpInstance, command, type )
+
+#define OSX_SALDATA_RUNINMAIN_UNION( command, member ) \
+ OSX_RUNINMAIN_UNION( GetSalData()->mpInstance, command, member )
+
+#endif // INCLUDED_VCL_INC_OSX_RUNINMAIN_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/saldata.hxx b/vcl/inc/osx/saldata.hxx
new file mode 100644
index 0000000000..ba4d6e8c85
--- /dev/null
+++ b/vcl/inc/osx/saldata.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_OSX_SALDATA_HXX
+#define INCLUDED_VCL_INC_OSX_SALDATA_HXX
+
+#include <config_features.h>
+
+#include <premac.h>
+#include <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+#include <com/sun/star/uno/Reference.hxx>
+
+#include <vcl/ptrstyle.hxx>
+
+#include <svdata.hxx>
+#include <salwtype.hxx>
+
+#include <functional>
+#include <list>
+#include <map>
+#include <memory>
+#include <unordered_set>
+#include <vector>
+#include <o3tl/enumarray.hxx>
+
+#include <cstdio>
+#include <cstdarg>
+
+#include <apple_remote/RemoteMainController.h>
+
+class AquaSalFrame;
+class AquaSalInstance;
+class SalObject;
+class SalFrame;
+class SalVirtualDevice;
+class SalPrinter;
+class SystemFontList;
+
+#define SAL_CLIPRECT_COUNT 16
+#define INVALID_CURSOR_PTR reinterpret_cast<NSCursor*>(0xdeadbeef)
+
+// Singleton, instantiated from Application::Application() in
+// vcl/source/app/svapp.cxx.
+
+class SalData
+{
+public:
+ SALTIMERPROC mpTimerProc; // timer callback proc
+ AquaSalInstance *mpInstance;
+ std::list<AquaSalFrame*> maPresentationFrames; // list of frames in presentation mode
+ SalObject *mpFirstObject; // pointer of first object window
+ SalVirtualDevice *mpFirstVD; // first VirDev
+ SalPrinter *mpFirstPrinter; // first printing printer
+ std::unique_ptr<SystemFontList> mpFontList;
+ NSStatusItem* mpStatusItem; // one status item that draws all our statuses
+ // at the moment this is only one add menu button
+ CGColorSpaceRef mxRGBSpace;
+ CGColorSpaceRef mxGraySpace;
+
+ o3tl::enumarray< PointerStyle, NSCursor* > maCursors;
+ std::vector< NSMenuItem* > maFallbackMenu;
+ std::map< NSEvent*, bool > maKeyEventAnswer;
+
+ static oslThreadKey s_aAutoReleaseKey;
+
+ bool mbIsScrollbarDoubleMax; // TODO: support DoubleMin and DoubleBoth too
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+ AppleRemoteMainController* mpAppleRemoteMainController;
+#endif
+ NSObject* mpDockIconClickHandler;
+ sal_Int32 mnDPIX; // #i100617# read DPI only once per office life
+ sal_Int32 mnDPIY; // #i100617# read DPI only once per office life
+
+ css::uno::Reference< css::uno::XInterface > mxClipboard;
+
+ SalData();
+ ~SalData();
+
+ NSCursor* getCursor( PointerStyle i_eStyle );
+
+ static void ensureThreadAutoreleasePool();
+
+ static NSStatusItem* getStatusItem();
+};
+
+bool ImplSalYieldMutexTryToAcquire();
+void ImplSalYieldMutexRelease();
+
+#endif // INCLUDED_VCL_INC_OSX_SALDATA_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/salframe.h b/vcl/inc/osx/salframe.h
new file mode 100644
index 0000000000..717e5f3101
--- /dev/null
+++ b/vcl/inc/osx/salframe.h
@@ -0,0 +1,233 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_OSX_SALFRAME_H
+#define INCLUDED_VCL_INC_OSX_SALFRAME_H
+
+#include <premac.h>
+#include <IOKit/pwr_mgt/IOPMLib.h>
+#include <postmac.h>
+
+#include <tools/long.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <osx/salinst.h>
+#include <osx/salmenu.h>
+#include <osx/saldata.hxx>
+#include <osx/osxvcltypes.h>
+
+#include <salframe.hxx>
+
+#include <vector>
+#include <utility>
+#include <stdexcept>
+
+class AquaSalGraphics;
+class AquaSalFrame;
+class AquaSalTimer;
+class AquaSalInstance;
+class AquaSalMenu;
+
+class AquaSalFrame : public SalFrame
+{
+public:
+ NSWindow* mpNSWindow; // Cocoa window
+ NSView* mpNSView; // Cocoa view (actually a custom view)
+ NSMenuItem* mpDockMenuEntry; // entry in the dynamic dock menu
+ NSRect maScreenRect; // for mirroring purposes
+ AquaSalGraphics* mpGraphics; // current frame graphics
+ AquaSalFrame* mpParent; // pointer to parent frame
+ SystemEnvData maSysData; // system data
+ int mnMinWidth; // min. client width in pixels
+ int mnMinHeight; // min. client height in pixels
+ int mnMaxWidth; // max. client width in pixels
+ int mnMaxHeight; // max. client height in pixels
+ NSRect maFullScreenRect; // old window size when in FullScreen
+ bool mbGraphics; // is Graphics used?
+ bool mbFullScreen; // is Window in FullScreen?
+ bool mbShown;
+ bool mbInitShow;
+ bool mbPositioned;
+ bool mbSized;
+ bool mbPresentation;
+
+ SalFrameStyleFlags mnStyle;
+ unsigned int mnStyleMask; // our style mask from NSWindow creation
+
+ sal_uInt64 mnLastEventTime;
+ unsigned int mnLastModifierFlags;
+ AquaSalMenu* mpMenu;
+
+ SalExtStyle mnExtStyle; // currently document frames are marked this way
+
+ PointerStyle mePointerStyle; // currently active pointer style
+
+ NSRect maTrackingRect;
+
+ CGMutablePathRef mrClippingPath; // used for "shaping"
+ std::vector< CGRect > maClippingRects;
+
+ tools::Rectangle maInvalidRect;
+
+ InputContextFlags mnICOptions;
+
+ // To prevent display sleep during presentation
+ IOPMAssertionID mnAssertionID;
+
+ NSRect maFrameRect;
+ NSRect maContentRect;
+
+ bool mbGeometryDidChange;
+
+ int mnBlinkCursorDelay;
+
+ // tdf#155266 force flush after scrolling
+ bool mbForceFlush;
+
+public:
+ /** Constructor
+
+ Creates a system window and connects this frame with it.
+
+ @throws std::runtime_error in case window creation fails
+ */
+ AquaSalFrame( SalFrame* pParent, SalFrameStyleFlags salFrameStyle );
+
+ virtual ~AquaSalFrame() override;
+
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics( SalGraphics* pGraphics ) override;
+ virtual bool PostEvent(std::unique_ptr<ImplSVEvent> pData) override;
+ virtual void SetTitle( const OUString& rTitle ) override;
+ virtual void SetIcon( sal_uInt16 nIcon ) override;
+ virtual void SetRepresentedURL( const OUString& ) override;
+ virtual void SetMenu( SalMenu* pSalMenu ) override;
+ virtual void Show( bool bVisible, bool bNoActivate = false ) override;
+ virtual void SetMinClientSize( tools::Long nWidth, tools::Long nHeight )
+ override;
+ virtual void SetMaxClientSize( tools::Long nWidth, tools::Long nHeight )
+ override;
+ virtual void SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags ) override;
+ virtual void GetClientSize( tools::Long& rWidth, tools::Long& rHeight ) override;
+ virtual void GetWorkArea( AbsoluteScreenPixelRectangle& rRect ) override;
+ virtual SalFrame* GetParent() const override;
+ virtual void SetWindowState(const vcl::WindowData*) override;
+ virtual bool GetWindowState(vcl::WindowData*) override;
+ virtual void ShowFullScreen( bool bFullScreen, sal_Int32 nDisplay ) override;
+ virtual void StartPresentation( bool bStart ) override;
+ virtual void SetAlwaysOnTop( bool bOnTop ) override;
+ virtual void ToTop( SalFrameToTop nFlags ) override;
+ virtual void SetPointer( PointerStyle ePointerStyle ) override;
+ virtual void CaptureMouse( bool bMouse ) override;
+ virtual void SetPointerPos( tools::Long nX, tools::Long nY ) override;
+ virtual void Flush( void ) override;
+ virtual void Flush( const tools::Rectangle& ) override;
+ virtual void SetInputContext( SalInputContext* pContext ) override;
+ virtual void EndExtTextInput( EndExtTextInputFlags nFlags ) override;
+ virtual OUString GetKeyName( sal_uInt16 nKeyCode ) override;
+ virtual bool MapUnicodeToKeyCode( sal_Unicode aUnicode, LanguageType aLangType, vcl::KeyCode& rKeyCode ) override;
+ virtual LanguageType GetInputLanguage() override;
+ virtual void UpdateSettings( AllSettings& rSettings ) override;
+ virtual void Beep() override;
+ virtual const SystemEnvData* GetSystemData() const override;
+ virtual SalPointerState GetPointerState() override;
+ virtual KeyIndicatorState GetIndicatorState() override;
+ virtual void SimulateKeyPress( sal_uInt16 nKeyCode ) override;
+ virtual void SetParent( SalFrame* pNewParent ) override;
+ virtual void SetPluginParent( SystemParentData* pNewParent ) override;
+ virtual void SetExtendedFrameStyle( SalExtStyle ) override;
+ virtual void SetScreenNumber(unsigned int) override;
+ virtual void SetApplicationID( const OUString &rApplicationID ) override;
+
+ // shaped system windows
+ // set clip region to none (-> rectangular windows, normal state)
+ virtual void ResetClipRegion() override;
+ // start setting the clipregion consisting of nRects rectangles
+ virtual void BeginSetClipRegion( sal_uInt32 nRects ) override;
+ // add a rectangle to the clip region
+ virtual void UnionClipRegion(
+ tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+ // done setting up the clipregion
+ virtual void EndSetClipRegion() override;
+ virtual void UpdateDarkMode() override;
+ virtual bool GetUseDarkMode() const override;
+ virtual bool GetUseReducedAnimation() const override;
+
+ void UpdateFrameGeometry();
+
+ // trigger painting of the window
+ void SendPaintEvent( const tools::Rectangle* pRect = nullptr );
+
+ static inline bool isAlive( const AquaSalFrame* pFrame );
+
+ static AquaSalFrame* GetCaptureFrame() { return s_pCaptureFrame; }
+
+ NSWindow* getNSWindow() const { return mpNSWindow; }
+ NSView* getNSView() const { return mpNSView; }
+ unsigned int getStyleMask() const { return mnStyleMask; }
+
+ void getResolution( sal_Int32& o_rDPIX, sal_Int32& o_rDPIY );
+
+ // actually the following methods do the same thing: flipping y coordinates
+ // but having two of them makes clearer what the coordinate system
+ // is supposed to be before and after
+ void VCLToCocoa( NSRect& io_rRect, bool bRelativeToScreen = true );
+ void CocoaToVCL( NSRect& io_rRect, bool bRelativeToScreen = true );
+
+ void VCLToCocoa( NSPoint& io_rPoint, bool bRelativeToScreen = true );
+ void CocoaToVCL( NSPoint& io_Point, bool bRelativeToScreen = true );
+
+ NSCursor* getCurrentCursor();
+
+ CGMutablePathRef getClipPath() const { return mrClippingPath; }
+
+ // called by VCL_NSApplication to indicate screen settings have changed
+ void screenParametersChanged();
+
+protected:
+ SalEvent PreparePosSize(
+ tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags);
+
+private: // methods
+ /** do things on initial show (like centering on parent or on screen)
+ */
+ void initShow();
+
+ void initWindowAndView();
+
+ void doShowFullScreen( bool bFullScreen, sal_Int32 nDisplay );
+
+ void doResetClipRegion();
+
+private: // data
+ static AquaSalFrame* s_pCaptureFrame;
+
+ AquaSalFrame( const AquaSalFrame& ) = delete;
+ AquaSalFrame& operator=(const AquaSalFrame&) = delete;
+};
+
+inline bool AquaSalFrame::isAlive( const AquaSalFrame* pFrame )
+{
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ return pInst && pInst->isFrameAlive( pFrame );
+}
+
+#endif // INCLUDED_VCL_INC_OSX_SALFRAME_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/salframeview.h b/vcl/inc/osx/salframeview.h
new file mode 100644
index 0000000000..2b1a3f9baa
--- /dev/null
+++ b/vcl/inc/osx/salframeview.h
@@ -0,0 +1,278 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_OSX_SALFRAMEVIEW_H
+#define INCLUDED_VCL_INC_OSX_SALFRAMEVIEW_H
+
+#include <osx/a11ywrapper.h>
+
+enum class SalEvent;
+
+@interface SalFrameWindow : NSWindow<NSWindowDelegate>
+{
+ AquaSalFrame* mpFrame;
+ id mDraggingDestinationHandler;
+ BOOL mbInWindowDidResize;
+ NSTimer* mpLiveResizeTimer;
+}
+-(id)initWithSalFrame: (AquaSalFrame*)pFrame;
+-(void)clearLiveResizeTimer;
+-(void)dealloc;
+-(BOOL)canBecomeKeyWindow;
+-(void)displayIfNeeded;
+-(void)windowDidBecomeKey: (NSNotification*)pNotification;
+-(void)windowDidResignKey: (NSNotification*)pNotification;
+-(void)windowDidChangeScreen: (NSNotification*)pNotification;
+-(void)windowDidMove: (NSNotification*)pNotification;
+-(void)windowDidResize: (NSNotification*)pNotification;
+-(void)windowDidMiniaturize: (NSNotification*)pNotification;
+-(void)windowDidDeminiaturize: (NSNotification*)pNotification;
+-(BOOL)windowShouldClose: (NSNotification*)pNotification;
+-(void)windowDidChangeBackingProperties:(NSNotification *)pNotification;
+-(void)windowWillStartLiveResize:(NSNotification *)pNotification;
+-(void)windowDidEndLiveResize:(NSNotification *)pNotification;
+//-(void)willEncodeRestorableState:(NSCoder*)pCoderState;
+//-(void)didDecodeRestorableState:(NSCoder*)pCoderState;
+//-(void)windowWillEnterVersionBrowser:(NSNotification*)pNotification;
+-(void)dockMenuItemTriggered: (id)sender;
+-(AquaSalFrame*)getSalFrame;
+-(BOOL)containsMouse;
+-(css::uno::Reference < css::accessibility::XAccessibleContext >)accessibleContext;
+
+/* NSDraggingDestination protocol methods
+ */
+-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender;
+-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender;
+-(void)draggingExited:(id <NSDraggingInfo>)sender;
+-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender;
+-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender;
+-(void)concludeDragOperation:(id <NSDraggingInfo>)sender;
+
+-(void)registerDraggingDestinationHandler:(id)theHandler;
+-(void)unregisterDraggingDestinationHandler:(id)theHandler;
+
+-(void)endExtTextInput;
+-(void)endExtTextInput:(EndExtTextInputFlags)nFlags;
+
+-(void)windowDidResizeWithTimer:(NSTimer *)pTimer;
+
+-(BOOL)isIgnoredWindow;
+
+// NSAccessibilityElement overrides
+-(id)accessibilityApplicationFocusedUIElement;
+-(id)accessibilityFocusedUIElement;
+-(BOOL)accessibilityIsIgnored;
+-(BOOL)isAccessibilityElement;
+
+@end
+
+@interface SalFrameView : NSView <NSTextInputClient>
+{
+ AquaSalFrame* mpFrame;
+ AquaA11yWrapper* mpChildWrapper;
+ BOOL mbNeedChildWrapper;
+
+ // for NSTextInput/NSTextInputClient
+ NSEvent* mpLastEvent;
+ BOOL mbNeedSpecialKeyHandle;
+ BOOL mbInKeyInput;
+ BOOL mbKeyHandled;
+ NSRange mMarkedRange;
+ NSRange mSelectedRange;
+ id mpMouseEventListener;
+ id mDraggingDestinationHandler;
+ NSEvent* mpLastSuperEvent;
+
+ // #i102807# used by magnify event handler
+ NSTimeInterval mfLastMagnifyTime;
+ float mfMagnifyDeltaSum;
+
+ BOOL mbInEndExtTextInput;
+ BOOL mbInCommitMarkedText;
+ NSAttributedString* mpLastMarkedText;
+ BOOL mbTextInputWantsNonRepeatKeyDown;
+}
++(void)unsetMouseFrame: (AquaSalFrame*)pFrame;
+-(id)initWithSalFrame: (AquaSalFrame*)pFrame;
+-(void)dealloc;
+-(AquaSalFrame*)getSalFrame;
+-(BOOL)acceptsFirstResponder;
+-(BOOL)acceptsFirstMouse: (NSEvent *)pEvent;
+-(BOOL)isOpaque;
+-(void)drawRect: (NSRect)aRect;
+-(void)mouseDown: (NSEvent*)pEvent;
+-(void)mouseDragged: (NSEvent*)pEvent;
+-(void)mouseUp: (NSEvent*)pEvent;
+-(void)mouseMoved: (NSEvent*)pEvent;
+-(void)mouseEntered: (NSEvent*)pEvent;
+-(void)mouseExited: (NSEvent*)pEvent;
+-(void)rightMouseDown: (NSEvent*)pEvent;
+-(void)rightMouseDragged: (NSEvent*)pEvent;
+-(void)rightMouseUp: (NSEvent*)pEvent;
+-(void)otherMouseDown: (NSEvent*)pEvent;
+-(void)otherMouseDragged: (NSEvent*)pEvent;
+-(void)otherMouseUp: (NSEvent*)pEvent;
+-(void)scrollWheel: (NSEvent*)pEvent;
+-(void)magnifyWithEvent: (NSEvent*)pEvent;
+-(void)rotateWithEvent: (NSEvent*)pEvent;
+-(void)swipeWithEvent: (NSEvent*)pEvent;
+-(void)keyDown: (NSEvent*)pEvent;
+-(void)flagsChanged: (NSEvent*)pEvent;
+-(void)sendMouseEventToFrame:(NSEvent*)pEvent button:(sal_uInt16)nButton eventtype:(SalEvent)nEvent;
+-(BOOL)sendKeyInputAndReleaseToFrame: (sal_uInt16)nKeyCode character: (sal_Unicode)aChar;
+-(BOOL)sendKeyInputAndReleaseToFrame: (sal_uInt16)nKeyCode character: (sal_Unicode)aChar modifiers: (unsigned int)nMod;
+-(BOOL)sendKeyToFrameDirect: (sal_uInt16)nKeyCode character: (sal_Unicode)aChar modifiers: (unsigned int)nMod;
+-(BOOL)sendSingleCharacter:(NSEvent*)pEvent;
+-(BOOL)handleKeyDownException:(NSEvent*)pEvent;
+-(void)clearLastEvent;
+-(void)clearLastMarkedText;
+/*
+ text action methods
+*/
+-(void)insertText:(id)aString replacementRange:(NSRange)replacementRange;
+-(void)insertTab: (id)aSender;
+-(void)insertBacktab: (id)aSender;
+-(void)moveLeft: (id)aSender;
+-(void)moveLeftAndModifySelection: (id)aSender;
+-(void)moveBackwardAndModifySelection: (id)aSender;
+-(void)moveRight: (id)aSender;
+-(void)moveRightAndModifySelection: (id)aSender;
+-(void)moveForwardAndModifySelection: (id)aSender;
+-(void)moveUp: (id)aSender;
+-(void)moveDown: (id)aSender;
+-(void)moveWordBackward: (id)aSender;
+-(void)moveWordBackwardAndModifySelection: (id)aSender;
+-(void)moveWordLeftAndModifySelection: (id)aSender;
+-(void)moveWordForward: (id)aSender;
+-(void)moveWordForwardAndModifySelection: (id)aSender;
+-(void)moveWordRightAndModifySelection: (id)aSender;
+-(void)moveToEndOfLine: (id)aSender;
+-(void)moveToRightEndOfLine: (id)aSender;
+-(void)moveToLeftEndOfLine: (id)aSender;
+-(void)moveToEndOfLineAndModifySelection: (id)aSender;
+-(void)moveToRightEndOfLineAndModifySelection: (id)aSender;
+-(void)moveToLeftEndOfLineAndModifySelection: (id)aSender;
+-(void)moveToBeginningOfLine: (id)aSender;
+-(void)moveToBeginningOfLineAndModifySelection: (id)aSender;
+-(void)moveToEndOfParagraph: (id)aSender;
+-(void)moveToEndOfParagraphAndModifySelection: (id)aSender;
+-(void)moveToBeginningOfParagraph: (id)aSender;
+-(void)moveToBeginningOfParagraphAndModifySelection: (id)aSender;
+-(void)moveParagraphForward: (id)aSender;
+-(void)moveParagraphForwardAndModifySelection: (id)aSender;
+-(void)moveParagraphBackward: (id)aSender;
+-(void)moveParagraphBackwardAndModifySelection: (id)aSender;
+-(void)moveToEndOfDocument: (id)aSender;
+-(void)scrollToEndOfDocument: (id)aSender;
+-(void)moveToEndOfDocumentAndModifySelection: (id)aSender;
+-(void)moveToBeginningOfDocument: (id)aSender;
+-(void)scrollToBeginningOfDocument: (id)aSender;
+-(void)moveToBeginningOfDocumentAndModifySelection: (id)aSender;
+-(void)insertNewline: (id)aSender;
+-(void)deleteBackward: (id)aSender;
+-(void)deleteForward: (id)aSender;
+-(void)cancelOperation: (id)aSender;
+-(void)deleteBackwardByDecomposingPreviousCharacter: (id)aSender;
+-(void)deleteWordBackward: (id)aSender;
+-(void)deleteWordForward: (id)aSender;
+-(void)deleteToBeginningOfLine: (id)aSender;
+-(void)deleteToEndOfLine: (id)aSender;
+-(void)deleteToBeginningOfParagraph: (id)aSender;
+-(void)deleteToEndOfParagraph: (id)aSender;
+-(void)insertLineBreak: (id)aSender;
+-(void)insertParagraphSeparator: (id)aSender;
+-(void)selectWord: (id)aSender;
+-(void)selectLine: (id)aSender;
+-(void)selectParagraph: (id)aSender;
+-(void)selectAll: (id)aSender;
+-(void)noop: (id)aSender;
+/* set the correct pointer for our view */
+-(void)resetCursorRects;
+-(css::accessibility::XAccessibleContext *)accessibleContext;
+-(id)parentAttribute;
+-(NSWindow*)windowForParent;
+/*
+ Event hook for D&D service.
+
+ A drag operation will be invoked on a NSView using
+ the method 'dragImage'. This method requires the
+ actual mouse event initiating this drag operation.
+ Mouse events can only be received by subclassing
+ NSView and overriding methods like 'mouseDown' etc.
+ hence we implement an event hook here so that the
+ D&D service can register as listener for mouse
+ messages and use the last 'mouseDown' or
+ 'mouseDragged' message to initiate the drag
+ operation.
+*/
+-(void)registerMouseEventListener: (id)theListener;
+-(void)unregisterMouseEventListener: (id)theListener;
+
+/* NSDraggingDestination protocol methods
+ */
+-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender;
+-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender;
+-(void)draggingExited:(id <NSDraggingInfo>)sender;
+-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender;
+-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender;
+-(void)concludeDragOperation:(id <NSDraggingInfo>)sender;
+
+-(void)registerDraggingDestinationHandler:(id)theHandler;
+-(void)unregisterDraggingDestinationHandler:(id)theHandler;
+
+-(void)endExtTextInput;
+-(void)endExtTextInput:(EndExtTextInputFlags)nFlags;
+-(void)deleteTextInputWantsNonRepeatKeyDown;
+
+-(void)insertRegisteredWrapperIntoWrapperRepository;
+-(void)registerWrapper;
+-(void)revokeWrapper;
+
+// NSAccessibilityElement overrides
+-(id)accessibilityAttributeValue:(NSString *)pAttribute;
+-(BOOL)accessibilityIsIgnored;
+-(NSArray *)accessibilityAttributeNames;
+-(BOOL)accessibilityIsAttributeSettable:(NSString *)pAttribute;
+-(NSArray *)accessibilityParameterizedAttributeNames;
+-(BOOL)accessibilitySetOverrideValue:(id)pValue forAttribute:(NSString *)pAttribute;
+-(void)accessibilitySetValue:(id)pValue forAttribute:(NSString *)pAttribute;
+-(id)accessibilityAttributeValue:(NSString *)pAttribute forParameter:(id)pParameter;
+-(id)accessibilityFocusedUIElement;
+-(NSString *)accessibilityActionDescription:(NSString *)pAction;
+-(void)accessibilityPerformAction:(NSString *)pAction;
+-(NSArray *)accessibilityActionNames;
+-(id)accessibilityHitTest:(NSPoint)aPoint;
+-(NSArray *)accessibilityVisibleChildren;
+-(NSArray *)accessibilitySelectedChildren;
+-(NSArray *)accessibilityChildren;
+-(NSArray <id<NSAccessibilityElement>> *)accessibilityChildrenInNavigationOrder;
+
+@end
+
+@interface SalFrameViewA11yWrapper : AquaA11yWrapper
+{
+ SalFrameView* mpParentView;
+}
+-(id)initWithParent:(SalFrameView*)pParentView accessibleContext:(::com::sun::star::uno::Reference<::com::sun::star::accessibility::XAccessibleContext>&)rxAccessibleContext;
+-(void)dealloc;
+@end
+
+#endif // INCLUDED_VCL_INC_OSX_SALFRAMEVIEW_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/salinst.h b/vcl/inc/osx/salinst.h
new file mode 100644
index 0000000000..8811fa3c9c
--- /dev/null
+++ b/vcl/inc/osx/salinst.h
@@ -0,0 +1,168 @@
+
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <condition_variable>
+#include <list>
+#include <mutex>
+
+#include <comphelper/solarmutex.hxx>
+#include <osl/conditn.hxx>
+#include <osl/thread.hxx>
+#include <tools/long.hxx>
+
+#ifdef MACOSX
+#include <osx/osxvcltypes.h>
+#endif
+#include <salinst.hxx>
+
+#include <osx/runinmain.hxx>
+
+#include <salusereventlist.hxx>
+
+class AquaSalFrame;
+class SalFrame;
+class SalObject;
+class ApplicationEvent;
+class Image;
+enum class SalEvent;
+
+typedef void(^RuninmainBlock)(void);
+
+class SalYieldMutex : public comphelper::SolarMutex
+{
+public:
+ OSX_RUNINMAIN_MEMBERS
+
+protected:
+ virtual void doAcquire( sal_uInt32 nLockCount ) override;
+ virtual sal_uInt32 doRelease( bool bUnlockAll ) override;
+
+public:
+ SalYieldMutex();
+ virtual ~SalYieldMutex() override;
+
+ virtual bool IsCurrentThread() const override;
+};
+
+class AquaSalInstance : public SalInstance, public SalUserEventList
+{
+ friend class AquaSalFrame;
+
+ bool RunInMainYield( bool bHandleAllCurrentEvents );
+
+ virtual void ProcessEvent( SalUserEvent aEvent ) override;
+
+public:
+ virtual void TriggerUserEventProcessing() override;
+
+ NSButtonCell* mpButtonCell;
+ NSButtonCell* mpCheckCell;
+ NSButtonCell* mpRadioCell;
+ NSTextFieldCell* mpTextFieldCell;
+ NSComboBoxCell* mpComboBoxCell;
+ NSPopUpButtonCell* mpPopUpButtonCell;
+ NSStepperCell* mpStepperCell;
+ NSButtonCell* mpListNodeCell;
+ OUString maDefaultPrinter;
+ oslThreadIdentifier maMainThread;
+ int mnActivePrintJobs;
+ osl::Mutex maUserEventListMutex;
+ osl::Condition maWaitingYieldCond;
+ bool mbNoYieldLock;
+ bool mbTimerProcessed;
+
+ static std::list<const ApplicationEvent*> aAppEventList;
+
+ AquaSalInstance();
+ virtual ~AquaSalInstance() override;
+
+ virtual void AfterAppInit() override;
+ virtual bool SVMainHook(int *) override;
+
+ virtual SalFrame* CreateChildFrame( SystemParentData* pParent, SalFrameStyleFlags nStyle ) override;
+ virtual SalFrame* CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle ) override;
+ virtual void DestroyFrame( SalFrame* pFrame ) override;
+ virtual SalObject* CreateObject( SalFrame* pParent, SystemWindowData* pWindowData,
+ bool bShow ) override;
+ virtual void DestroyObject( SalObject* pObject ) override;
+ virtual std::unique_ptr<SalVirtualDevice>
+ CreateVirtualDevice( SalGraphics& rGraphics,
+ tools::Long &nDX, tools::Long &nDY,
+ DeviceFormat eFormat,
+ const SystemGraphicsData *pData = nullptr ) override;
+ virtual SalInfoPrinter* CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pSetupData ) override;
+ virtual void DestroyInfoPrinter( SalInfoPrinter* pPrinter ) override;
+ virtual std::unique_ptr<SalPrinter> CreatePrinter( SalInfoPrinter* pInfoPrinter ) override;
+ virtual void GetPrinterQueueInfo( ImplPrnQueueList* pList ) override;
+ virtual void GetPrinterQueueState( SalPrinterQueueInfo* pInfo ) override;
+ virtual OUString GetDefaultPrinter() override;
+ virtual SalTimer* CreateSalTimer() override;
+ virtual SalSystem* CreateSalSystem() override;
+ virtual std::shared_ptr<SalBitmap> CreateSalBitmap() override;
+ virtual bool DoYield(bool bWait, bool bHandleAllCurrentEvents) override;
+ virtual bool AnyInput( VclInputFlags nType ) override;
+ virtual std::unique_ptr<SalMenu> CreateMenu( bool bMenuBar, Menu* pVCLMenu ) override;
+ virtual std::unique_ptr<SalMenuItem> CreateMenuItem( const SalItemParams & rItemData ) override;
+ virtual OpenGLContext* CreateOpenGLContext() override;
+ virtual OUString GetConnectionIdentifier() override;
+ virtual void AddToRecentDocumentList(const OUString& rFileUrl, const OUString& rMimeType,
+ const OUString& rDocumentService) override;
+
+ virtual OUString getOSVersion() override;
+
+ // dtrans implementation
+ virtual css::uno::Reference< css::uno::XInterface > CreateClipboard(
+ const css::uno::Sequence< css::uno::Any >& i_rArguments ) override;
+ virtual css::uno::Reference<css::uno::XInterface> ImplCreateDragSource(const SystemEnvData*) override;
+ virtual css::uno::Reference<css::uno::XInterface> ImplCreateDropTarget(const SystemEnvData*) override;
+
+ static void handleAppDefinedEvent( NSEvent* pEvent );
+
+ // check whether a particular string is passed on the command line
+ // this is needed to avoid duplicate open events through a) command line and b) NSApp's openFile
+ static bool isOnCommandLine( const OUString& );
+
+ void delayedSettingsChanged( bool bInvalidate );
+
+ // Is this the NSAppThread?
+ virtual bool IsMainThread() const override;
+
+ void startedPrintJob() { mnActivePrintJobs++; }
+ void endedPrintJob() { mnActivePrintJobs--; }
+
+ // event subtypes for NSEventTypeApplicationDefined events
+ static const short AppExecuteSVMain = 1;
+ static const short AppStartTimerEvent = 10;
+ static const short YieldWakeupEvent = 20;
+ static const short DispatchTimerEvent = 30;
+
+ static NSMenu* GetDynamicDockMenu();
+};
+
+CGImageRef CreateCGImage( const Image& );
+NSImage* CreateNSImage( const Image& );
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/salmenu.h b/vcl/inc/osx/salmenu.h
new file mode 100644
index 0000000000..597180cc1a
--- /dev/null
+++ b/vcl/inc/osx/salmenu.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_OSX_SALMENU_H
+#define INCLUDED_VCL_INC_OSX_SALMENU_H
+
+#include <premac.h>
+#include <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+#include <salmenu.hxx>
+
+#include <vector>
+
+class AquaSalFrame;
+class AquaSalMenuItem;
+
+class AquaSalMenu : public SalMenu
+{
+ std::vector< AquaSalMenuItem* > maItems;
+
+public: // for OOStatusView
+ struct MenuBarButtonEntry
+ {
+ SalMenuButtonItem maButton;
+ NSImage* mpNSImage; // cached image
+ NSString* mpToolTipString;
+
+ MenuBarButtonEntry() : mpNSImage( nil ), mpToolTipString( nil ) {}
+ MenuBarButtonEntry( const SalMenuButtonItem& i_rItem )
+ : maButton( i_rItem), mpNSImage( nil ), mpToolTipString( nil ) {}
+ };
+private:
+ std::vector< MenuBarButtonEntry > maButtons;
+
+ MenuBarButtonEntry* findButtonItem( sal_uInt16 i_nItemId );
+ static void statusLayout();
+public:
+ AquaSalMenu( bool bMenuBar );
+ virtual ~AquaSalMenu() override;
+
+ virtual bool VisibleMenuBar() override;
+
+ virtual void InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos ) override;
+ virtual void RemoveItem( unsigned nPos ) override;
+ virtual void SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned nPos ) override;
+ virtual void SetFrame( const SalFrame* pFrame ) override;
+ virtual void CheckItem( unsigned nPos, bool bCheck ) override;
+ virtual void EnableItem( unsigned nPos, bool bEnable ) override;
+ virtual void SetItemText( unsigned nPos, SalMenuItem* pSalMenuItem, const OUString& rText ) override;
+ virtual void SetItemImage( unsigned nPos, SalMenuItem* pSalMenuItem, const Image& rImage) override;
+ virtual void SetAccelerator( unsigned nPos, SalMenuItem* pSalMenuItem, const vcl::KeyCode& rKeyCode, const OUString& rKeyName ) override;
+ virtual void GetSystemMenuData( SystemMenuData* pData ) override;
+ virtual bool ShowNativePopupMenu(FloatingWindow * pWin, const tools::Rectangle& rRect, FloatWinPopupFlags nFlags) override;
+ virtual bool AddMenuBarButton( const SalMenuButtonItem& ) override;
+ virtual void RemoveMenuBarButton( sal_uInt16 nId ) override;
+ virtual tools::Rectangle GetMenuBarButtonRectPixel( sal_uInt16 i_nItemId, SalFrame* i_pReferenceFrame ) override;
+
+ int getItemIndexByPos( sal_uInt16 nPos ) const;
+ const AquaSalFrame* getFrame() const;
+
+ void setMainMenu();
+ static void unsetMainMenu();
+ static void setDefaultMenu();
+ static void enableMainMenu( bool bEnable );
+ static void addFallbackMenuItem( NSMenuItem* NewItem );
+ static void removeFallbackMenuItem( NSMenuItem* pOldItem );
+
+ const std::vector< MenuBarButtonEntry >& getButtons() const { return maButtons; }
+
+ bool mbMenuBar; // true - Menubar, false - Menu
+ NSMenu* mpMenu; // The Carbon reference to this menu
+ VclPtr<Menu> mpVCLMenu; // the corresponding vcl Menu object
+ const AquaSalFrame* mpFrame; // the frame to dispatch the menu events to
+ AquaSalMenu* mpParentSalMenu; // the parent menu that contains us (and perhaps has a frame)
+
+ static const AquaSalMenu* pCurrentMenuBar;
+
+};
+
+class AquaSalMenuItem : public SalMenuItem
+{
+public:
+ AquaSalMenuItem( const SalItemParams* );
+ virtual ~AquaSalMenuItem() override;
+
+ sal_uInt16 mnId; // Item ID
+ VclPtr<Menu> mpVCLMenu; // VCL Menu into which this MenuItem is inserted
+ AquaSalMenu* mpParentMenu; // The menu in which this menu item is inserted
+ AquaSalMenu* mpSubMenu; // Sub menu of this item (if defined)
+ NSMenuItem* mpMenuItem; // The NSMenuItem
+};
+
+#endif // INCLUDED_VCL_INC_OSX_SALMENU_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/salnativewidgets.h b/vcl/inc/osx/salnativewidgets.h
new file mode 100644
index 0000000000..86675e0623
--- /dev/null
+++ b/vcl/inc/osx/salnativewidgets.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_OSX_SALNATIVEWIDGETS_H
+#define INCLUDED_VCL_INC_OSX_SALNATIVEWIDGETS_H
+
+#define TAB_HEIGHT 20 // height of tab header in pixels
+#define TAB_TEXT_MARGIN 12 // left/right margin of text within tab headers
+
+#define FOCUS_RING_WIDTH 4 // width of focus ring in pixels
+
+#define MEDIUM_PROGRESS_INDICATOR_HEIGHT 10 // height of medium progress indicator in pixels
+#define LARGE_PROGRESS_INDICATOR_HEIGHT 16 // height of large progress indicator in pixels
+
+#define PUSH_BUTTON_NORMAL_HEIGHT 21 // height of normal push button without focus ring in pixels
+#define PUSH_BUTTON_SMALL_HEIGHT 15 // height of small push button without focus ring in pixels
+
+#define RADIO_BUTTON_SMALL_SIZE 14 // width/height of small radio button without focus ring in pixels
+#define RADIO_BUTTON_TEXT_SEPARATOR 3 // space between radio button and following text in pixels
+
+#define CHECKBOX_SMALL_SIZE 14 // width/height of checkbox without focus ring in pixels
+#define CHECKBOX_TEXT_SEPARATOR 3 // space between checkbox and following text in pixels
+
+#define SLIDER_WIDTH 19 // width of slider in pixels
+#define SLIDER_HEIGHT 18 // height of slider in pixels
+
+#define EDITBOX_HEIGHT 21 // height of editbox without focus ring in pixels
+#define EDITBOX_BORDER_WIDTH 1 // width of editbox border in pixels
+#define EDITBOX_INSET_MARGIN 1 // width of left/right as well as top/bottom editbox margin in pixels
+
+#define COMBOBOX_HEIGHT 20 // height of combobox without focus ring in pixels
+#define COMBOBOX_BUTTON_WIDTH 18 // width of combobox button without focus ring in pixels
+#define COMBOBOX_BORDER_WIDTH 1 // width of combobox border in pixels
+#define COMBOBOX_TEXT_MARGIN 1 // left/right margin of text in pixels
+
+#define LISTBOX_HEIGHT 20 // height of listbox without focus ring in pixels
+#define LISTBOX_BUTTON_WIDTH 18 // width of listbox button without focus ring in pixels
+#define LISTBOX_BORDER_WIDTH 1 // width of listbox border in pixels
+#define LISTBOX_TEXT_MARGIN 1 // left/right margin of text in pixels
+
+#define SPIN_BUTTON_WIDTH 13 // width of spin button without focus ring in pixels
+#define SPIN_UPPER_BUTTON_HEIGHT 11 // height of upper spin button without focus ring in pixels
+#define SPIN_LOWER_BUTTON_HEIGHT 11 // height of lower spin button without focus ring in pixels
+
+// FIXME: spinboxes are positioned one pixel shifted to the right by VCL. As positioning as well as size should be equal to
+// corresponding editboxes, comboboxes or listboxes, positioning of spinboxes should by equal too. Issue cannot be fixed within
+// native widget drawing code. As a workaround, an offset is considered for spinboxes to align spinboxes correctly.
+
+#define SPINBOX_OFFSET 1 // left offset for alignment with editboxes, comboboxes, and listboxes
+
+#endif // INCLUDED_VCL_INC_OSX_SALNATIVEWIDGETS_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/salnsmenu.h b/vcl/inc/osx/salnsmenu.h
new file mode 100644
index 0000000000..696abca2fc
--- /dev/null
+++ b/vcl/inc/osx/salnsmenu.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_OSX_SALNSMENU_H
+#define INCLUDED_VCL_INC_OSX_SALNSMENU_H
+
+class AquaSalMenu;
+class AquaSalMenuItem;
+
+@interface OOStatusItemView : NSView
+{
+}
+- (void)drawRect:(NSRect)aRect;
+- (void)layout;
+- (void)mouseUp:(NSEvent*)pEvent;
+@end
+
+@interface SalNSMenu : NSMenu
+{
+ AquaSalMenu* mpMenu;
+}
+- (id)initWithMenu:(AquaSalMenu*)pMenu;
+- (void)menuNeedsUpdate:(NSMenu*)pMenu;
+- (void)setSalMenu:(AquaSalMenu*)pMenu;
+@end
+
+@interface SalNSMenuItem : NSMenuItem
+{
+ AquaSalMenuItem* mpMenuItem;
+}
+- (id)initWithMenuItem:(AquaSalMenuItem*)pMenuItem;
+- (void)menuItemTriggered:(id)aSender;
+@end
+
+#endif // INCLUDED_VCL_INC_OSX_SALNSMENU_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/salnstimer.h b/vcl/inc/osx/salnstimer.h
new file mode 100644
index 0000000000..5c831cdf45
--- /dev/null
+++ b/vcl/inc/osx/salnstimer.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_OSX_SALNSTIMER_H
+#define INCLUDED_VCL_INC_OSX_SALNSTIMER_H
+
+#include <premac.h>
+#include <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+@interface TimerCallbackCaller : NSObject
+{
+}
+- (void)timerElapsed:(NSTimer*)pTimer;
+@end
+
+#endif // INCLUDED_VCL_INC_OSX_SALNSTIMER_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/salobj.h b/vcl/inc/osx/salobj.h
new file mode 100644
index 0000000000..4c2ac88be6
--- /dev/null
+++ b/vcl/inc/osx/salobj.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_OSX_SALOBJ_H
+#define INCLUDED_VCL_INC_OSX_SALOBJ_H
+
+#include <sal/config.h>
+
+#include <tools/long.hxx>
+#include <vcl/sysdata.hxx>
+#include <salobj.hxx>
+
+class AquaSalFrame;
+class AquaSalObject;
+
+
+struct SalObjectData
+{
+};
+
+class AquaSalObject : public SalObject
+{
+public:
+ AquaSalFrame* mpFrame; // parent frame
+ NSClipView* mpClipView;
+ SystemEnvData maSysData;
+
+ CGFloat mnClipX;
+ CGFloat mnClipY;
+ CGFloat mnClipWidth;
+ CGFloat mnClipHeight;
+ bool mbClip;
+
+ CGFloat mnX;
+ CGFloat mnY;
+ CGFloat mnWidth;
+ CGFloat mnHeight;
+
+ void setClippedPosSize();
+
+ AquaSalObject( AquaSalFrame* pFrame, SystemWindowData const * pWinData );
+ virtual ~AquaSalObject() override;
+
+ virtual void ResetClipRegion() override;
+ virtual void BeginSetClipRegion( sal_uInt32 nRects ) override;
+ virtual void UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void EndSetClipRegion() override;
+ virtual void SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void Show( bool bVisible ) override;
+ virtual const SystemEnvData* GetSystemData() const override;
+};
+
+#endif // INCLUDED_VCL_INC_OSX_SALOBJ_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/salprn.h b/vcl/inc/osx/salprn.h
new file mode 100644
index 0000000000..7bfd41787e
--- /dev/null
+++ b/vcl/inc/osx/salprn.h
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_OSX_SALPRN_H
+#define INCLUDED_VCL_INC_OSX_SALPRN_H
+
+#include <sal/config.h>
+
+#include <tools/long.hxx>
+
+#include <osx/osxvcltypes.h>
+
+#include <salprn.hxx>
+
+#include <memory>
+
+class AquaSalGraphics;
+
+class AquaSalInfoPrinter : public SalInfoPrinter
+{
+ /// Printer graphics
+ AquaSalGraphics* mpGraphics;
+ /// is Graphics used
+ bool mbGraphics;
+ /// job active ?
+ bool mbJob;
+
+ /// cocoa printer object
+ NSPrinter* mpPrinter;
+ /// cocoa print info object
+ NSPrintInfo* mpPrintInfo;
+
+ /// FIXME: get real printer context for infoprinter if possible
+ /// fake context for info printer
+ /// graphics context for Quartz 2D
+ CGContextRef mrContext;
+ /// memory for graphics bitmap context for querying metrics
+ std::unique_ptr<sal_uInt8[]> mpContextMemory;
+
+ // since changes to NSPrintInfo during a job are ignored
+ // we have to care for some settings ourselves
+ // currently we do this for orientation;
+ // really needed however is a solution for paper formats
+ Orientation mePageOrientation;
+
+ int mnStartPageOffsetX;
+ int mnStartPageOffsetY;
+ sal_Int32 mnCurPageRangeStart;
+ sal_Int32 mnCurPageRangeCount;
+
+ public:
+ AquaSalInfoPrinter( const SalPrinterQueueInfo& pInfo );
+ virtual ~AquaSalInfoPrinter() override;
+
+ void SetupPrinterGraphics( CGContextRef i_xContext ) const;
+
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics( SalGraphics* i_pGraphics ) override;
+ virtual bool Setup( weld::Window* i_pFrame, ImplJobSetup* i_pSetupData ) override;
+ virtual bool SetPrinterData( ImplJobSetup* pSetupData ) override;
+ virtual bool SetData( JobSetFlags i_nFlags, ImplJobSetup* i_pSetupData ) override;
+ virtual void GetPageInfo( const ImplJobSetup* i_pSetupData,
+ tools::Long& o_rOutWidth, tools::Long& o_rOutHeight,
+ Point& rPageOffset,
+ Size& rPaperSize ) override;
+ virtual sal_uInt32 GetCapabilities( const ImplJobSetup* i_pSetupData, PrinterCapType i_nType ) override;
+ virtual sal_uInt16 GetPaperBinCount( const ImplJobSetup* i_pSetupData ) override;
+ virtual OUString GetPaperBinName( const ImplJobSetup* i_pSetupData, sal_uInt16 i_nPaperBin ) override;
+ virtual void InitPaperFormats( const ImplJobSetup* i_pSetupData ) override;
+ virtual int GetLandscapeAngle( const ImplJobSetup* i_pSetupData ) override;
+
+ // the artificial separation between InfoPrinter and Printer
+ // is not really useful for us
+ // so let's make AquaSalPrinter just a forwarder to AquaSalInfoPrinter
+ // and concentrate the real work in one class
+ // implement pull model print system
+ bool StartJob( const OUString* i_pFileName,
+ const OUString& rJobName,
+ ImplJobSetup* i_pSetupData,
+ vcl::PrinterController& i_rController );
+ bool EndJob();
+ bool AbortJob();
+ SalGraphics* StartPage( ImplJobSetup* i_pSetupData, bool i_bNewJobData );
+ bool EndPage();
+
+ NSPrintInfo* getPrintInfo() const { return mpPrintInfo; }
+ void setStartPageOffset( int nOffsetX, int nOffsetY ) { mnStartPageOffsetX = nOffsetX; mnStartPageOffsetY = nOffsetY; }
+ sal_Int32 getCurPageRangeStart() const { return mnCurPageRangeStart; }
+ sal_Int32 getCurPageRangeCount() const { return mnCurPageRangeCount; }
+
+ // match width/height against known paper formats, possibly switching orientation
+ const PaperInfo* matchPaper(
+ tools::Long i_nWidth, tools::Long i_nHeight, Orientation& o_rOrientation ) const;
+ void setPaperSize( tools::Long i_nWidth, tools::Long i_nHeight, Orientation i_eSetOrientation );
+
+ private:
+ AquaSalInfoPrinter( const AquaSalInfoPrinter& ) = delete;
+ AquaSalInfoPrinter& operator=(const AquaSalInfoPrinter&) = delete;
+};
+
+
+class AquaSalPrinter : public SalPrinter
+{
+ AquaSalInfoPrinter* mpInfoPrinter; // pointer to the compatible InfoPrinter
+ public:
+ AquaSalPrinter( AquaSalInfoPrinter* i_pInfoPrinter );
+ virtual ~AquaSalPrinter() override;
+
+ virtual bool StartJob( const OUString* i_pFileName,
+ const OUString& i_rJobName,
+ const OUString& i_rAppName,
+ sal_uInt32 i_nCopies,
+ bool i_bCollate,
+ bool i_bDirect,
+ ImplJobSetup* i_pSetupData ) override;
+ // implement pull model print system
+ virtual bool StartJob( const OUString* i_pFileName,
+ const OUString& rJobName,
+ const OUString& i_rAppName,
+ ImplJobSetup* i_pSetupData,
+ vcl::PrinterController& i_rListener ) override;
+
+ virtual bool EndJob() override;
+ virtual SalGraphics* StartPage( ImplJobSetup* i_pSetupData, bool i_bNewJobData ) override;
+ virtual void EndPage() override;
+
+ private:
+ AquaSalPrinter( const AquaSalPrinter& ) = delete;
+ AquaSalPrinter& operator=(const AquaSalPrinter&) = delete;
+};
+
+const double fPtTo100thMM = 35.27777778;
+
+inline int PtTo10Mu( double nPoints ) { return static_cast<int>((nPoints*fPtTo100thMM)+0.5); }
+
+inline double TenMuToPt( double nUnits ) { return floor((nUnits/fPtTo100thMM)+0.5); }
+
+#endif // INCLUDED_VCL_INC_OSX_SALPRN_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/salsys.h b/vcl/inc/osx/salsys.h
new file mode 100644
index 0000000000..4b8b077088
--- /dev/null
+++ b/vcl/inc/osx/salsys.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_OSX_SALSYS_H
+#define INCLUDED_VCL_INC_OSX_SALSYS_H
+
+#include <salsys.hxx>
+
+#include <list>
+
+
+class VCL_DLLPUBLIC AquaSalSystem : public SalSystem
+{
+public:
+ AquaSalSystem() {}
+ virtual ~AquaSalSystem() override;
+
+ // get info about the display
+ virtual unsigned int GetDisplayScreenCount() override;
+ virtual AbsoluteScreenPixelRectangle GetDisplayScreenPosSizePixel( unsigned int nScreen ) override;
+
+ virtual int ShowNativeMessageBox( const OUString& rTitle,
+ const OUString& rMessage) override;
+};
+
+#endif // INCLUDED_VCL_INC_OSX_SALSYS_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/saltimer.h b/vcl/inc/osx/saltimer.h
new file mode 100644
index 0000000000..cdde3ec847
--- /dev/null
+++ b/vcl/inc/osx/saltimer.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_OSX_SALTIMER_H
+#define INCLUDED_VCL_INC_OSX_SALTIMER_H
+
+#include <premac.h>
+#include <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+#include <saltimer.hxx>
+
+/**
+ * if NO == bAtStart, then it has to be run in the main thread,
+ * e.g. via performSelectorOnMainThread!
+ **/
+void ImplNSAppPostEvent( short nEventId, BOOL bAtStart, int nUserData = 0 );
+
+class ReleasePoolHolder
+{
+ NSAutoreleasePool* mpPool;
+
+public:
+ ReleasePoolHolder() : mpPool( [[NSAutoreleasePool alloc] init] ) {}
+ ~ReleasePoolHolder() { [mpPool release]; }
+};
+
+class AquaSalTimer final : public SalTimer, protected VersionedEvent
+{
+ NSTimer *m_pRunningTimer;
+ bool m_bDirectTimeout; ///< timeout can be processed directly
+
+ void queueDispatchTimerEvent( bool bAtStart );
+ void callTimerCallback();
+
+public:
+ AquaSalTimer();
+ virtual ~AquaSalTimer() override;
+
+ void Start( sal_uInt64 nMS ) override;
+ void Stop() override;
+
+ void handleStartTimerEvent( NSEvent* pEvent );
+ bool handleDispatchTimerEvent( NSEvent* pEvent );
+ void handleTimerElapsed();
+ void handleWindowShouldClose();
+
+ bool IsTimerElapsed() const;
+ inline bool IsDirectTimeout() const;
+};
+
+inline bool AquaSalTimer::IsDirectTimeout() const
+{
+ return m_bDirectTimeout;
+}
+
+#endif // INCLUDED_VCL_INC_OSX_SALTIMER_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/svsys.h b/vcl/inc/osx/svsys.h
new file mode 100644
index 0000000000..9fcabe074f
--- /dev/null
+++ b/vcl/inc/osx/svsys.h
@@ -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_INC_OSX_SVSYS_H
+#define INCLUDED_VCL_INC_OSX_SVSYS_H
+
+#include <premac.h>
+#include <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+#endif // INCLUDED_VCL_INC_OSX_SVSYS_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/osx/vclnsapp.h b/vcl/inc/osx/vclnsapp.h
new file mode 100644
index 0000000000..e3329f554c
--- /dev/null
+++ b/vcl/inc/osx/vclnsapp.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <config_features.h>
+
+#include <premac.h>
+#include <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+class AquaSalFrame;
+
+@interface CocoaThreadEnabler : NSObject
+{
+}
+-(void)enableCocoaThreads:(id)param;
+@end
+
+// our very own application
+@interface VCL_NSApplication : NSApplication
+{
+}
+-(void)applicationDidFinishLaunching:(NSNotification*)pNotification;
+-(void)sendEvent:(NSEvent*)pEvent;
+-(void)sendSuperEvent:(NSEvent*)pEvent;
+-(NSMenu*)applicationDockMenu:(NSApplication *)sender;
+-(BOOL)application: (NSApplication*) app openFile: (NSString*)file;
+-(void)application: (NSApplication*) app openFiles: (NSArray*)files;
+-(BOOL)application: (NSApplication*) app printFile: (NSString*)file;
+-(NSApplicationPrintReply)application: (NSApplication *) app printFiles:(NSArray *)files withSettings: (NSDictionary *)printSettings showPrintPanels:(BOOL)bShowPrintPanels;
+-(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app;
+-(void)applicationWillTerminate: (NSNotification *) aNotification;
+-(void)observeValueForKeyPath: (NSString*) keyPath ofObject:(id)object
+ change: (NSDictionary<NSKeyValueChangeKey, id>*)change
+ context: (void*)context;
+-(void)systemColorsChanged: (NSNotification*) pNotification;
+-(void)screenParametersChanged: (NSNotification*) pNotification;
+-(void)scrollbarVariantChanged: (NSNotification*) pNotification;
+-(void)scrollbarSettingsChanged: (NSNotification*) pNotification;
+-(void)addFallbackMenuItem: (NSMenuItem*)pNewItem;
+-(void)removeFallbackMenuItem: (NSMenuItem*)pOldItem;
+-(void)addDockMenuItem: (NSMenuItem*)pNewItem;
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+-(void)applicationWillBecomeActive: (NSNotification *)pNotification;
+-(void)applicationWillResignActive: (NSNotification *)pNotification;
+#endif
+-(BOOL)applicationShouldHandleReopen: (NSApplication*)pApp hasVisibleWindows: (BOOL)bWinVisible;
+-(void)setDockIconClickHandler: (NSObject*)pHandler;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/pch/precompiled_vcl.cxx b/vcl/inc/pch/precompiled_vcl.cxx
new file mode 100644
index 0000000000..79285a8f96
--- /dev/null
+++ b/vcl/inc/pch/precompiled_vcl.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "precompiled_vcl.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/pch/precompiled_vcl.hxx b/vcl/inc/pch/precompiled_vcl.hxx
new file mode 100644
index 0000000000..fac11693cf
--- /dev/null
+++ b/vcl/inc/pch/precompiled_vcl.hxx
@@ -0,0 +1,406 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/*
+ This file has been autogenerated by update_pch.sh. It is possible to edit it
+ manually (such as when an include file has been moved/renamed/removed). All such
+ manual changes will be rewritten by the next run of update_pch.sh (which presumably
+ also fixes all possible problems, so it's usually better to use it).
+
+ Generated on 2023-07-19 09:24:26 using:
+ ./bin/update_pch vcl vcl --cutoff=6 --exclude:system --include:module --include:local
+
+ If after updating build fails, use the following command to locate conflicting headers:
+ ./bin/update_pch_bisect ./vcl/inc/pch/precompiled_vcl.hxx "make vcl.build" --find-conflicts
+*/
+
+#include <sal/config.h>
+#if PCH_LEVEL >= 1
+#include <algorithm>
+#include <array>
+#include <atomic>
+#include <cassert>
+#include <chrono>
+#include <cmath>
+#include <cstddef>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <deque>
+#include <float.h>
+#include <functional>
+#include <hb.h>
+#include <initializer_list>
+#include <iomanip>
+#include <iterator>
+#include <limits>
+#include <list>
+#include <map>
+#include <math.h>
+#include <memory>
+#include <mutex>
+#include <new>
+#include <numeric>
+#include <optional>
+#include <ostream>
+#include <set>
+#include <stddef.h>
+#include <string.h>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+#include <boost/container/small_vector.hpp>
+#include <boost/math/special_functions/sinc.hpp>
+#include <boost/multi_array.hpp>
+#include <boost/property_tree/json_parser.hpp>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/ptree_fwd.hpp>
+#include <boost/rational.hpp>
+#endif // PCH_LEVEL >= 1
+#if PCH_LEVEL >= 2
+#include <osl/conditn.hxx>
+#include <osl/diagnose.h>
+#include <osl/doublecheckedlocking.h>
+#include <osl/endian.h>
+#include <osl/file.h>
+#include <osl/file.hxx>
+#include <osl/getglobalmutex.hxx>
+#include <osl/interlck.h>
+#include <osl/module.hxx>
+#include <osl/mutex.h>
+#include <osl/mutex.hxx>
+#include <osl/nlsupport.h>
+#include <osl/security.h>
+#include <osl/signal.h>
+#include <osl/socket.h>
+#include <osl/socket.hxx>
+#include <osl/thread.h>
+#include <osl/time.h>
+#include <rtl/alloc.h>
+#include <rtl/bootstrap.hxx>
+#include <rtl/byteseq.hxx>
+#include <rtl/character.hxx>
+#include <rtl/cipher.h>
+#include <rtl/crc.h>
+#include <rtl/digest.h>
+#include <rtl/instance.hxx>
+#include <rtl/locale.h>
+#include <rtl/math.h>
+#include <rtl/math.hxx>
+#include <rtl/process.h>
+#include <rtl/ref.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/string.h>
+#include <rtl/string.hxx>
+#include <rtl/stringconcat.hxx>
+#include <rtl/stringutils.hxx>
+#include <rtl/tencinfo.h>
+#include <rtl/textcvt.h>
+#include <rtl/textenc.h>
+#include <rtl/unload.h>
+#include <rtl/uri.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ustring.h>
+#include <rtl/ustring.hxx>
+#include <rtl/uuid.h>
+#include <sal/backtrace.hxx>
+#include <sal/log.hxx>
+#include <sal/macros.h>
+#include <sal/saldllapi.h>
+#include <sal/types.h>
+#endif // PCH_LEVEL >= 2
+#if PCH_LEVEL >= 3
+#include <basegfx/basegfxdllapi.h>
+#include <basegfx/color/bcolor.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/point/b2ipoint.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/range/Range2D.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/range/basicrange.hxx>
+#include <basegfx/tuple/Size2D.hxx>
+#include <basegfx/tuple/Tuple2D.hxx>
+#include <basegfx/tuple/Tuple3D.hxx>
+#include <basegfx/tuple/b2dtuple.hxx>
+#include <basegfx/tuple/b2ituple.hxx>
+#include <basegfx/tuple/b3dtuple.hxx>
+#include <basegfx/utils/common.hxx>
+#include <basegfx/vector/b2dsize.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/vector/b2enums.hxx>
+#include <basegfx/vector/b2isize.hxx>
+#include <basegfx/vector/b2ivector.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/datatransfer/XTransferable2.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardOwner.hpp>
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/datatransfer/dnd/DropTargetDragEvent.hpp>
+#include <com/sun/star/datatransfer/dnd/DropTargetDropEvent.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragGestureListener.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragSourceListener.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTargetListener.hpp>
+#include <com/sun/star/embed/Aspects.hpp>
+#include <com/sun/star/frame/XTerminateListener.hpp>
+#include <com/sun/star/i18n/Calendar2.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/io/XSeekable.hpp>
+#include <com/sun/star/io/XStream.hpp>
+#include <com/sun/star/io/XTruncate.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/lang/EventObject.hpp>
+#include <com/sun/star/lang/Locale.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XTypeProvider.hpp>
+#include <com/sun/star/uno/Any.h>
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/RuntimeException.hpp>
+#include <com/sun/star/uno/Sequence.h>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/uno/Type.h>
+#include <com/sun/star/uno/Type.hxx>
+#include <com/sun/star/uno/TypeClass.hdl>
+#include <com/sun/star/uno/XAggregation.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/uno/XInterface.hpp>
+#include <com/sun/star/uno/XWeak.hpp>
+#include <com/sun/star/uno/genfunc.h>
+#include <com/sun/star/uno/genfunc.hxx>
+#include <com/sun/star/util/Date.hpp>
+#include <com/sun/star/util/DateTime.hpp>
+#include <comphelper/comphelperdllapi.h>
+#include <comphelper/diagnose_ex.hxx>
+#include <comphelper/fileformat.h>
+#include <comphelper/interfacecontainer4.hxx>
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/string.hxx>
+#include <comphelper/unoimplbase.hxx>
+#include <cppu/cppudllapi.h>
+#include <cppu/unotype.hxx>
+#include <cppuhelper/cppuhelperdllapi.h>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/queryinterface.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/weak.hxx>
+#include <cppuhelper/weakagg.hxx>
+#include <cppuhelper/weakref.hxx>
+#include <font/FontSelectPattern.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <font/PhysicalFontFaceCollection.hxx>
+#include <i18nlangtag/lang.h>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <i18nutil/i18nutildllapi.h>
+#include <o3tl/cow_wrapper.hxx>
+#include <o3tl/hash_combine.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/sorted_vector.hxx>
+#include <o3tl/string_view.hxx>
+#include <o3tl/strong_int.hxx>
+#include <o3tl/typed_flags_set.hxx>
+#include <o3tl/underlyingenumvalue.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <salhelper/linkhelper.hxx>
+#include <salhelper/salhelperdllapi.h>
+#include <salhelper/simplereferenceobject.hxx>
+#include <salhelper/thread.hxx>
+#include <sot/exchange.hxx>
+#include <sot/formats.hxx>
+#include <svl/hint.hxx>
+#include <svl/macitem.hxx>
+#include <svl/poolitem.hxx>
+#include <svl/svldllapi.h>
+#include <svl/typedwhich.hxx>
+#include <test/outputdevice.hxx>
+#include <tools/color.hxx>
+#include <tools/contnr.hxx>
+#include <tools/date.hxx>
+#include <tools/datetime.hxx>
+#include <tools/debug.hxx>
+#include <tools/degree.hxx>
+#include <tools/fldunit.hxx>
+#include <tools/fontenum.hxx>
+#include <tools/fract.hxx>
+#include <tools/gen.hxx>
+#include <tools/globname.hxx>
+#include <tools/helpers.hxx>
+#include <tools/json_writer.hxx>
+#include <tools/link.hxx>
+#include <tools/long.hxx>
+#include <tools/mapunit.hxx>
+#include <tools/poly.hxx>
+#include <tools/ref.hxx>
+#include <tools/solar.h>
+#include <tools/stream.hxx>
+#include <tools/time.hxx>
+#include <tools/toolsdllapi.h>
+#include <tools/urlobj.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/zcodec.hxx>
+#include <typelib/typeclass.h>
+#include <typelib/typedescription.h>
+#include <typelib/uik.h>
+#include <uno/any2.h>
+#include <uno/data.h>
+#include <uno/sequence2.h>
+#include <unotools/calendarwrapper.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/fontdefs.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/resmgr.hxx>
+#include <unotools/syslocale.hxx>
+#include <unotools/tempfile.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <unotools/unotoolsdllapi.h>
+#endif // PCH_LEVEL >= 3
+#if PCH_LEVEL >= 4
+#include <ImplOutDevData.hxx>
+#include <accel.hxx>
+#include <brdwin.hxx>
+#include <configsettings.hxx>
+#include <drawmode.hxx>
+#include <fontattributes.hxx>
+#include <glyphid.hxx>
+#include <impfontcache.hxx>
+#include <impglyphitem.hxx>
+#include <ppdparser.hxx>
+#include <salbmp.hxx>
+#include <salframe.hxx>
+#include <salgdi.hxx>
+#include <salgdiimpl.hxx>
+#include <sallayout.hxx>
+#include <salmenu.hxx>
+#include <salobj.hxx>
+#include <salptype.hxx>
+#include <salsession.hxx>
+#include <salsys.hxx>
+#include <saltimer.hxx>
+#include <salusereventlist.hxx>
+#include <salvd.hxx>
+#include <salvtables.hxx>
+#include <sft.hxx>
+#include <svdata.hxx>
+#include <vcl/AccessibleBrowseBoxObjType.hxx>
+#include <vcl/BinaryDataContainer.hxx>
+#include <vcl/BitmapColor.hxx>
+#include <vcl/BitmapFilter.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <vcl/QueueInfo.hxx>
+#include <vcl/TypeSerializer.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/checksum.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/commandinfoprovider.hxx>
+#include <vcl/ctrl.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/dllapi.h>
+#include <vcl/dockwin.hxx>
+#include <vcl/event.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+#include <vcl/filter/PngImageWriter.hxx>
+#include <vcl/filter/SvmReader.hxx>
+#include <vcl/filter/SvmWriter.hxx>
+#include <vcl/fntstyle.hxx>
+#include <vcl/font.hxx>
+#include <vcl/formatter.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/gfxlink.hxx>
+#include <vcl/glyphitem.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/help.hxx>
+#include <vcl/i18nhelp.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/image.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/lazydelete.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <vcl/quickselectionengine.hxx>
+#include <vcl/region.hxx>
+#include <vcl/salnativewidgets.hxx>
+#include <vcl/scheduler.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/tabpage.hxx>
+#include <vcl/taskpanelist.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/toolkit/combobox.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/toolkit/edit.hxx>
+#include <vcl/toolkit/fixed.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+#include <vcl/toolkit/scrbar.hxx>
+#include <vcl/toolkit/spinfld.hxx>
+#include <vcl/toolkit/svlbitm.hxx>
+#include <vcl/toolkit/treelist.hxx>
+#include <vcl/toolkit/treelistbox.hxx>
+#include <vcl/toolkit/treelistentries.hxx>
+#include <vcl/toolkit/treelistentry.hxx>
+#include <vcl/toolkit/unowrap.hxx>
+#include <vcl/toolkit/vclmedit.hxx>
+#include <vcl/toolkit/viewdataentry.hxx>
+#include <vcl/transfer.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/unohelp.hxx>
+#include <vcl/vclenum.hxx>
+#include <vcl/vclevent.hxx>
+#include <vcl/vcllayout.hxx>
+#include <vcl/vclptr.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/weldutils.hxx>
+#include <vcl/window.hxx>
+#include <vcl/wrkwin.hxx>
+#include <window.h>
+#endif // PCH_LEVEL >= 4
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/pdf/BitmapID.hxx b/vcl/inc/pdf/BitmapID.hxx
new file mode 100644
index 0000000000..34f99944dc
--- /dev/null
+++ b/vcl/inc/pdf/BitmapID.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/.
+ *
+ */
+
+#pragma once
+
+#include <vcl/checksum.hxx>
+#include <tools/gen.hxx>
+
+namespace vcl::pdf
+{
+struct BitmapID
+{
+ Size m_aPixelSize;
+ sal_Int32 m_nSize;
+ BitmapChecksum m_nChecksum;
+ BitmapChecksum m_nMaskChecksum;
+
+ BitmapID()
+ : m_nSize(0)
+ , m_nChecksum(0)
+ , m_nMaskChecksum(0)
+ {
+ }
+
+ bool operator==(const BitmapID& rComp) const
+ {
+ return (m_aPixelSize == rComp.m_aPixelSize && m_nSize == rComp.m_nSize
+ && m_nChecksum == rComp.m_nChecksum && m_nMaskChecksum == rComp.m_nMaskChecksum);
+ }
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/pdf/ExternalPDFStreams.hxx b/vcl/inc/pdf/ExternalPDFStreams.hxx
new file mode 100644
index 0000000000..b2936f01a8
--- /dev/null
+++ b/vcl/inc/pdf/ExternalPDFStreams.hxx
@@ -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/.
+ */
+
+#pragma once
+
+#include <sal/types.h>
+#include <sal/log.hxx>
+#include <vcl/dllapi.h>
+
+#include <map>
+#include <vector>
+#include <memory>
+
+#include <vcl/filter/pdfdocument.hxx>
+#include <vcl/BinaryDataContainer.hxx>
+
+namespace vcl
+{
+// A external PDF stream, which stores the PDF stream data as byte array.
+// This struct is also responsible to parsing the stream as a PDFDocument,
+// and store its instance for the life-cycle of the struct, so that it
+// reused to avoid unnecessary parsing.
+struct VCL_DLLPUBLIC ExternalPDFStream
+{
+ BinaryDataContainer maDataContainer;
+ std::shared_ptr<filter::PDFDocument> mpPDFDocument;
+ std::map<sal_Int32, sal_Int32> maCopiedResources;
+
+ std::map<sal_Int32, sal_Int32>& getCopiedResources() { return maCopiedResources; }
+
+ std::shared_ptr<filter::PDFDocument>& getPDFDocument()
+ {
+ if (!mpPDFDocument)
+ {
+ std::shared_ptr<SvStream> aPDFStream = maDataContainer.getAsStream();
+ auto pPDFDocument = std::make_shared<filter::PDFDocument>();
+ if (!pPDFDocument->ReadWithPossibleFixup(*aPDFStream))
+ {
+ SAL_WARN("vcl.pdfwriter",
+ "PDFWriterImpl::writeReferenceXObject: reading the PDF document failed");
+ }
+ else
+ {
+ mpPDFDocument = pPDFDocument;
+ }
+ }
+ return mpPDFDocument;
+ }
+};
+
+// Class to manage external PDF streams, for the de-duplication purpose.
+class ExternalPDFStreams
+{
+private:
+ std::map<std::vector<sal_uInt8>, sal_Int32> maStreamIndexMap;
+ std::vector<ExternalPDFStream> maStreamList;
+
+public:
+ ExternalPDFStreams() {}
+
+ sal_Int32 store(BinaryDataContainer const& rDataContainer);
+
+ ExternalPDFStream& get(sal_uInt32 nIndex);
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/pdf/Matrix3.hxx b/vcl/inc/pdf/Matrix3.hxx
new file mode 100644
index 0000000000..226235f526
--- /dev/null
+++ b/vcl/inc/pdf/Matrix3.hxx
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <basegfx/point/b2dpoint.hxx>
+#include <tools/gen.hxx>
+
+namespace vcl::pdf
+{
+// matrix helper class
+// TODO: use basegfx matrix class instead or derive from it
+
+/* for sparse matrices of the form (2D linear transformations)
+ * f[0] f[1] 0
+ * f[2] f[3] 0
+ * f[4] f[5] 1
+ */
+class Matrix3
+{
+ double f[6];
+
+ void set(const double* pn)
+ {
+ for (int i = 0; i < 6; i++)
+ f[i] = pn[i];
+ }
+
+public:
+ Matrix3();
+
+ void skew(double alpha, double beta);
+ void scale(double sx, double sy);
+ void rotate(double angle);
+ void translate(double tx, double ty);
+ void invert();
+
+ double get(size_t i) const { return f[i]; }
+
+ Point transform(const Point& rPoint) const;
+ basegfx::B2DPoint transform(const basegfx::B2DPoint& rPoint) const;
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/pdf/PdfConfig.hxx b/vcl/inc/pdf/PdfConfig.hxx
new file mode 100644
index 0000000000..235fd008ea
--- /dev/null
+++ b/vcl/inc/pdf/PdfConfig.hxx
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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
+
+namespace vcl::pdf
+{
+double getDefaultPdfResolutionDpi();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/pdf/ResourceDict.hxx b/vcl/inc/pdf/ResourceDict.hxx
new file mode 100644
index 0000000000..119d9314f4
--- /dev/null
+++ b/vcl/inc/pdf/ResourceDict.hxx
@@ -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/.
+ *
+ */
+
+#pragma once
+
+#include <rtl/strbuf.hxx>
+#include <map>
+
+namespace vcl::pdf
+{
+enum class ResourceKind
+{
+ XObject,
+ ExtGState,
+ Shading,
+ Pattern
+};
+
+struct ResourceDict
+{
+ // note: handle fonts globally for performance
+ std::map<OString, sal_Int32> m_aXObjects;
+ std::map<OString, sal_Int32> m_aExtGStates;
+ std::map<OString, sal_Int32> m_aShadings;
+ std::map<OString, sal_Int32> m_aPatterns;
+
+ void append(OStringBuffer& rBuffer, sal_Int32 nFontDictObject);
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/pdf/XmpMetadata.hxx b/vcl/inc/pdf/XmpMetadata.hxx
new file mode 100644
index 0000000000..33fce97a21
--- /dev/null
+++ b/vcl/inc/pdf/XmpMetadata.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/.
+ *
+ */
+
+#pragma once
+
+#include <rtl/string.hxx>
+#include <tools/stream.hxx>
+#include <memory>
+#include <vector>
+
+namespace vcl::pdf
+{
+class XmpMetadata
+{
+private:
+ bool mbWritten;
+ std::unique_ptr<SvMemoryStream> mpMemoryStream;
+
+public:
+ OString msTitle;
+ OString msAuthor;
+ OString msSubject;
+ OString msProducer;
+ OString msPDFVersion;
+ OString msKeywords;
+ std::vector<OString> maContributor;
+ OString msCoverage;
+ OString msIdentifier;
+ std::vector<OString> maPublisher;
+ std::vector<OString> maRelation;
+ OString msRights;
+ OString msSource;
+ OString msType;
+ OString m_sCreatorTool;
+ OString m_sCreateDate;
+
+ sal_Int32 mnPDF_A;
+ bool mbPDF_UA;
+
+public:
+ XmpMetadata();
+ sal_uInt64 getSize();
+ const void* getData();
+
+private:
+ void write();
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/pdf/objectcopier.hxx b/vcl/inc/pdf/objectcopier.hxx
new file mode 100644
index 0000000000..0168f69717
--- /dev/null
+++ b/vcl/inc/pdf/objectcopier.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/.
+ */
+
+#pragma once
+
+#include <map>
+#include <vector>
+
+#include <rtl/strbuf.hxx>
+#include <rtl/string.hxx>
+#include <sal/types.h>
+
+class SvMemoryStream;
+
+namespace vcl
+{
+class PDFObjectContainer;
+namespace filter
+{
+class PDFObjectElement;
+class PDFElement;
+}
+
+/// Copies objects from one PDF file into another one.
+class PDFObjectCopier
+{
+ PDFObjectContainer& m_rContainer;
+
+ void copyRecursively(OStringBuffer& rLine, filter::PDFElement& rInputElement,
+ SvMemoryStream& rDocBuffer,
+ std::map<sal_Int32, sal_Int32>& rCopiedResources);
+
+public:
+ PDFObjectCopier(PDFObjectContainer& rContainer);
+
+ /// Copies resources of a given kind from an external page to the output,
+ /// returning what has to be included in the new resource dictionary.
+ OString copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind,
+ std::map<sal_Int32, sal_Int32>& rCopiedResources);
+
+ /// Copies a single resource from an external document, returns the new
+ /// object ID in our document.
+ sal_Int32 copyExternalResource(SvMemoryStream& rDocBuffer, filter::PDFObjectElement& rObject,
+ std::map<sal_Int32, sal_Int32>& rCopiedResources);
+
+ /// Copies resources of pPage into rLine.
+ void copyPageResources(filter::PDFObjectElement* pPage, OStringBuffer& rLine);
+
+ void copyPageResources(filter::PDFObjectElement* pPage, OStringBuffer& rLine,
+ std::map<sal_Int32, sal_Int32>& rCopiedResources);
+
+ /// Copies page one or more page streams from rContentStreams into rStream.
+ static sal_Int32 copyPageStreams(std::vector<filter::PDFObjectElement*>& rContentStreams,
+ SvMemoryStream& rStream, bool& rCompressed);
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/pdf/pdfbuildin_fonts.hxx b/vcl/inc/pdf/pdfbuildin_fonts.hxx
new file mode 100644
index 0000000000..a0c2fc0628
--- /dev/null
+++ b/vcl/inc/pdf/pdfbuildin_fonts.hxx
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <font/PhysicalFontFace.hxx>
+#include <font/LogicalFontInstance.hxx>
+
+namespace vcl::pdf
+{
+struct BuildinFont
+{
+ const char* m_pName;
+ const char* m_pStyleName;
+ const char* m_pPSName;
+ int const m_nAscent;
+ int const m_nDescent;
+ FontFamily const m_eFamily;
+ rtl_TextEncoding const m_eCharSet;
+ FontPitch const m_ePitch;
+ FontWidth const m_eWidthType;
+ FontWeight const m_eWeight;
+ FontItalic const m_eItalic;
+ int const m_aWidths[256];
+ mutable FontCharMapRef m_xFontCharMap;
+
+ OString getNameObject() const;
+ const FontCharMapRef& GetFontCharMap() const;
+ FontAttributes GetFontAttributes() const;
+};
+
+class BuildinFontInstance final : public LogicalFontInstance
+{
+public:
+ BuildinFontInstance(const vcl::font::PhysicalFontFace&, const vcl::font::FontSelectPattern&);
+
+ bool GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rPoly, bool) const override;
+};
+
+class BuildinFontFace final : public vcl::font::PhysicalFontFace
+{
+ static const BuildinFont m_aBuildinFonts[14];
+ const BuildinFont& mrBuildin;
+
+ rtl::Reference<LogicalFontInstance>
+ CreateFontInstance(const vcl::font::FontSelectPattern& rFSD) const override;
+
+public:
+ explicit BuildinFontFace(int nId);
+
+ const BuildinFont& GetBuildinFont() const { return mrBuildin; }
+ sal_IntPtr GetFontId() const override { return reinterpret_cast<sal_IntPtr>(&mrBuildin); }
+ FontCharMapRef GetFontCharMap() const override { return mrBuildin.GetFontCharMap(); }
+ bool GetFontCapabilities(vcl::FontCapabilities&) const override { return false; }
+
+ static const BuildinFont& Get(int nId) { return m_aBuildinFonts[nId]; }
+};
+
+} // namespace vcl::pdf
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/pdf/pdfcompat.hxx b/vcl/inc/pdf/pdfcompat.hxx
new file mode 100644
index 0000000000..0664a400f9
--- /dev/null
+++ b/vcl/inc/pdf/pdfcompat.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/.
+ */
+
+#pragma once
+
+#include <o3tl/unit_conversion.hxx>
+#include <tools/stream.hxx>
+#include <vcl/BinaryDataContainer.hxx>
+
+namespace vcl::pdf
+{
+/// Convert to inch, then apply custom resolution.
+inline double pointToPixel(const double fPoint, const double fResolutionDPI)
+{
+ return o3tl::convert(fPoint, o3tl::Length::pt, o3tl::Length::in) * fResolutionDPI;
+}
+
+/// Decide if PDF data is old enough to be compatible.
+bool isCompatible(SvStream& rInStream, sal_uInt64 nPos, sal_uInt64 nSize);
+
+/// Converts to highest supported format version (currently 1.6).
+/// Usually used to deal with missing referenced objects in the
+/// source pdf stream.
+bool convertToHighestSupported(SvStream& rInStream, SvStream& rOutStream);
+
+/// 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);
+
+BinaryDataContainer createBinaryDataContainer(SvStream& rStream);
+
+} // end of vcl::filter::ipdf namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/pdf/pdfwriter_impl.hxx b/vcl/inc/pdf/pdfwriter_impl.hxx
new file mode 100644
index 0000000000..090f3e090c
--- /dev/null
+++ b/vcl/inc/pdf/pdfwriter_impl.hxx
@@ -0,0 +1,1358 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <map>
+#include <list>
+#include <unordered_map>
+#include <unordered_set>
+#include <memory>
+#include <string_view>
+#include <vector>
+#include <stack>
+#include <variant>
+
+#include <pdf/ResourceDict.hxx>
+#include <pdf/BitmapID.hxx>
+#include <pdf/Matrix3.hxx>
+
+#include <com/sun/star/lang/Locale.hpp>
+#include <com/sun/star/util/XURLTransformer.hpp>
+#include <osl/file.hxx>
+#include <rtl/cipher.h>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <tools/gen.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/graphictools.hxx>
+#include <vcl/hatch.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/pdfwriter.hxx>
+#include <vcl/wall.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/typed_flags_set.hxx>
+#include <o3tl/lru_map.hxx>
+#include <comphelper/hash.hxx>
+#include <tools/stream.hxx>
+#include <vcl/BinaryDataContainer.hxx>
+
+#include <vcl/filter/pdfobjectcontainer.hxx>
+#include <vcl/settings.hxx>
+#include <pdf/ExternalPDFStreams.hxx>
+#include <pdf/pdfbuildin_fonts.hxx>
+#include <salgdi.hxx>
+
+class FontSubsetInfo;
+class ZCodec;
+class EncHashTransporter;
+struct BitStreamState;
+namespace vcl::font { class PhysicalFontFace; }
+class SvStream;
+class SvMemoryStream;
+
+// the maximum password length
+constexpr sal_Int32 ENCRYPTED_PWD_SIZE = 32;
+constexpr sal_Int32 MD5_DIGEST_SIZE = 16;
+// security 128 bit
+constexpr sal_Int32 SECUR_128BIT_KEY = 16;
+// maximum length of MD5 digest input, in step 2 of algorithm 3.1
+// PDF spec ver. 1.4: see there for details
+constexpr sal_Int32 MAXIMUM_RC4_KEY_LENGTH = SECUR_128BIT_KEY + 3 + 2;
+
+namespace vcl::pdf
+{
+
+enum class GraphicsStateUpdateFlags {
+ Font = 0x0001,
+ MapMode = 0x0002,
+ LineColor = 0x0004,
+ FillColor = 0x0008,
+ ClipRegion = 0x0040,
+ LayoutMode = 0x0100,
+ TransparentPercent = 0x0200,
+ DigitLanguage = 0x0400,
+ All = 0x077f
+};
+
+} // end vcl::pdf
+
+namespace o3tl {
+ template<> struct typed_flags<vcl::pdf::GraphicsStateUpdateFlags> : is_typed_flags<vcl::pdf::GraphicsStateUpdateFlags, 0x077f> {};
+}
+
+namespace vcl
+{
+
+using namespace vcl::pdf;
+
+class PDFStreamIf;
+
+namespace filter
+{
+class PDFObjectElement;
+}
+
+namespace pdf
+{
+struct PDFPage
+{
+ VclPtr<PDFWriterImpl> m_pWriter;
+ double m_nPageWidth; // in inch/72
+ double m_nPageHeight; // in inch/72
+ /**
+ * A positive number that gives the size of default user space units, in multiples of points.
+ * Typically 1, larger if page size is > 508 cm.
+ */
+ sal_Int32 m_nUserUnit;
+ PDFWriter::Orientation m_eOrientation;
+ sal_Int32 m_nPageObject;
+ std::vector<sal_Int32> m_aStreamObjects;
+ sal_Int32 m_nStreamLengthObject;
+ sal_uInt64 m_nBeginStreamPos;
+ std::vector<sal_Int32> m_aAnnotations;
+ std::vector<sal_Int32> m_aMCIDParents;
+ PDFWriter::PageTransition m_eTransition;
+ sal_uInt32 m_nTransTime;
+
+ PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation );
+
+ void beginStream();
+ void endStream();
+ bool emit( sal_Int32 nParentPage );
+
+ // converts point from ref device coordinates to
+ // page coordinates and appends the point to the buffer
+ // if pOutPoint is set it will be updated to the emitted point
+ // (in PDF map mode, that is 10th of point)
+ void appendPoint( const Point& rPoint, OStringBuffer& rBuffer ) const;
+ // appends a B2DPoint without further transformation
+ void appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const;
+ // appends a rectangle
+ void appendRect( const tools::Rectangle& rRect, OStringBuffer& rBuffer ) const;
+ // converts a rectangle to 10th points page space
+ void convertRect( tools::Rectangle& rRect ) const;
+ // appends a polygon optionally closing it
+ void appendPolygon( const tools::Polygon& rPoly, OStringBuffer& rBuffer, bool bClose = true ) const;
+ // appends a polygon optionally closing it
+ void appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer ) const;
+ // appends a polypolygon optionally closing the subpaths
+ void appendPolyPolygon( const tools::PolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const;
+ // appends a polypolygon optionally closing the subpaths
+ void appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const;
+ // converts a length (either vertical or horizontal; this
+ // can be important if the source MapMode is not
+ // symmetrical) to page length and appends it to the buffer
+ // if pOutLength is set it will be updated to the emitted length
+ // (in PDF map mode, that is 10th of point)
+ void appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical = true, sal_Int32* pOutLength = nullptr ) const;
+ // the same for double values
+ void appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical = true, sal_Int32 nPrecision = 5 ) const;
+ // appends LineInfo
+ // returns false if too many dash array entry were created for
+ // the implementation limits of some PDF readers
+ bool appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const;
+ // appends a horizontal waveline with vertical offset (helper for drawWaveLine)
+ void appendWaveLine( sal_Int32 nLength, sal_Int32 nYOffset, sal_Int32 nDelta, OStringBuffer& rBuffer ) const;
+
+ void appendMatrix3(Matrix3 const & rMatrix, OStringBuffer& rBuffer);
+
+ double getHeight() const;
+};
+
+/// Contains information to emit a reference XObject.
+struct ReferenceXObjectEmit
+{
+ /// ID of the Form XObject, if any.
+ sal_Int32 m_nFormObject;
+ /// ID of the vector/embedded object, if m_nFormObject is used.
+ sal_Int32 m_nEmbeddedObject;
+ /// ID of the bitmap object, if m_nFormObject is used.
+ sal_Int32 m_nBitmapObject;
+ /// Size of the bitmap replacement, in pixels.
+ Size m_aPixelSize;
+ /// PDF data from the graphic object, if not writing a reference XObject.
+ sal_Int32 m_nExternalPDFDataIndex;
+ sal_Int32 m_nExternalPDFPageIndex;
+
+ ReferenceXObjectEmit()
+ : m_nFormObject(0)
+ , m_nEmbeddedObject(0)
+ , m_nBitmapObject(0)
+ , m_nExternalPDFDataIndex(-1)
+ , m_nExternalPDFPageIndex(-1)
+ {
+ }
+
+ /// Returns the ID one should use when referring to this bitmap.
+ sal_Int32 getObject() const;
+
+ bool hasExternalPDFData() const
+ {
+ return m_nExternalPDFDataIndex >= 0;
+ }
+};
+
+struct BitmapEmit
+{
+ BitmapID m_aID;
+ BitmapEx m_aBitmap;
+ sal_Int32 m_nObject;
+ ReferenceXObjectEmit m_aReferenceXObject;
+
+ BitmapEmit()
+ : m_nObject(0)
+ {
+ }
+};
+
+struct JPGEmit
+{
+ BitmapID m_aID;
+ std::unique_ptr<SvMemoryStream>
+ m_pStream;
+ AlphaMask m_aAlphaMask;
+ sal_Int32 m_nObject;
+ bool m_bTrueColor;
+ ReferenceXObjectEmit m_aReferenceXObject;
+
+ JPGEmit()
+ : m_nObject(0)
+ , m_bTrueColor(false)
+ {
+ }
+};
+
+struct GradientEmit
+{
+ Gradient m_aGradient;
+ Size m_aSize;
+ sal_Int32 m_nObject;
+};
+
+// for tilings (drawWallpaper, begin/endPattern)
+struct TilingEmit
+{
+ sal_Int32 m_nObject;
+ tools::Rectangle m_aRectangle;
+ Size m_aCellSize;
+ SvtGraphicFill::Transform m_aTransform;
+ ResourceDict m_aResources;
+ std::unique_ptr<SvMemoryStream> m_pTilingStream;
+
+ TilingEmit()
+ : m_nObject( 0 )
+ {}
+};
+
+// for transparency group XObjects
+struct TransparencyEmit
+{
+ sal_Int32 m_nObject;
+ sal_Int32 m_nExtGStateObject;
+ double m_fAlpha;
+ tools::Rectangle m_aBoundRect;
+ std::unique_ptr<SvMemoryStream> m_pContentStream;
+
+ TransparencyEmit()
+ : m_nObject( 0 ),
+ m_nExtGStateObject( -1 ),
+ m_fAlpha( 0.0 )
+ {}
+};
+
+// font subsets
+
+struct ColorLayer
+{
+ sal_Int32 m_nFontID;
+ sal_uInt8 m_nSubsetGlyphID;
+ uint32_t m_nColorIndex;
+};
+
+class GlyphEmit
+{
+ // performance: actually this should probably a vector;
+ std::vector<sal_Ucs> m_CodeUnits;
+ sal_uInt8 m_nSubsetGlyphID;
+ sal_Int32 m_nGlyphWidth;
+ std::vector<ColorLayer> m_aColorLayers;
+ font::RawFontData m_aColorBitmap;
+ tools::Rectangle m_aRect;
+ basegfx::B2DPolyPolygon m_aOutline;
+
+public:
+ GlyphEmit() : m_nSubsetGlyphID(0), m_nGlyphWidth(0)
+ {
+ }
+
+ void setGlyphId( sal_uInt8 i_nId ) { m_nSubsetGlyphID = i_nId; }
+ sal_uInt8 getGlyphId() const { return m_nSubsetGlyphID; }
+
+ void setGlyphWidth( sal_Int32 nWidth ) { m_nGlyphWidth = nWidth; }
+ sal_Int32 getGlyphWidth() const { return m_nGlyphWidth; }
+
+ void addColorLayer(ColorLayer aLayer) { m_aColorLayers.push_back(aLayer); }
+ const std::vector<ColorLayer>& getColorLayers() const { return m_aColorLayers; }
+
+ void setColorBitmap(font::RawFontData aData, tools::Rectangle aRect)
+ {
+ m_aColorBitmap = aData;
+ m_aRect = aRect;
+ }
+ const font::RawFontData& getColorBitmap(tools::Rectangle& rRect) const
+ {
+ rRect = m_aRect;
+ return m_aColorBitmap;
+ }
+
+ void setOutline(const basegfx::B2DPolyPolygon& rOutline) { m_aOutline = rOutline; }
+ const basegfx::B2DPolyPolygon& getOutline() const { return m_aOutline; }
+
+ void addCode( sal_Ucs i_cCode )
+ {
+ m_CodeUnits.push_back(i_cCode);
+ }
+ sal_Int32 countCodes() const { return m_CodeUnits.size(); }
+ const std::vector<sal_Ucs>& codes() const { return m_CodeUnits; }
+ sal_Ucs getCode( sal_Int32 i_nIndex ) const
+ {
+ sal_Ucs nRet = 0;
+ if (o3tl::make_unsigned(i_nIndex) < m_CodeUnits.size())
+ nRet = m_CodeUnits[i_nIndex];
+ return nRet;
+ }
+};
+
+struct FontEmit
+{
+ sal_Int32 m_nFontID;
+ std::map<sal_GlyphId, GlyphEmit> m_aMapping;
+
+ explicit FontEmit( sal_Int32 nID ) : m_nFontID( nID ) {}
+};
+
+struct Glyph
+{
+ sal_Int32 m_nFontID;
+ sal_uInt8 m_nSubsetGlyphID;
+};
+
+struct FontSubset
+{
+ std::vector< FontEmit > m_aSubsets;
+ std::map<sal_GlyphId, Glyph> m_aMapping;
+};
+
+struct EmbedFont
+{
+ sal_Int32 m_nNormalFontID;
+ LogicalFontInstance* m_pFontInstance;
+
+ EmbedFont()
+ : m_nNormalFontID(0)
+ , m_pFontInstance(nullptr) {}
+};
+
+struct PDFDest
+{
+ sal_Int32 m_nPage;
+ PDFWriter::DestAreaType m_eType;
+ tools::Rectangle m_aRect;
+};
+
+//--->i56629
+struct PDFNamedDest
+{
+ OUString m_aDestName;
+ sal_Int32 m_nPage;
+ PDFWriter::DestAreaType m_eType;
+ tools::Rectangle m_aRect;
+};
+
+struct PDFOutlineEntry
+{
+ sal_Int32 m_nObject;
+ sal_Int32 m_nParentObject;
+ sal_Int32 m_nNextObject;
+ sal_Int32 m_nPrevObject;
+ std::vector< sal_Int32 > m_aChildren;
+ OUString m_aTitle;
+ sal_Int32 m_nDestID;
+
+ PDFOutlineEntry()
+ : m_nObject( 0 ),
+ m_nParentObject( 0 ),
+ m_nNextObject( 0 ),
+ m_nPrevObject( 0 ),
+ m_nDestID( -1 )
+ {}
+};
+
+struct PDFAnnotation
+{
+ sal_Int32 m_nObject;
+ tools::Rectangle m_aRect;
+ sal_Int32 m_nPage;
+
+ PDFAnnotation()
+ : m_nObject( -1 ),
+ m_nPage( -1 )
+ {}
+};
+
+struct PDFLink : public PDFAnnotation
+{
+ sal_Int32 m_nDest; // set to -1 for URL, to a dest else
+ OUString m_aURL;
+ sal_Int32 m_nStructParent; // struct parent entry
+ OUString m_AltText;
+
+ PDFLink(OUString const& rAltText)
+ : m_nDest( -1 ),
+ m_nStructParent( -1 )
+ , m_AltText(rAltText)
+ {}
+};
+
+/// A PDF embedded file.
+struct PDFEmbeddedFile
+{
+ /// ID of the file.
+ sal_Int32 m_nObject;
+ OUString m_aSubType;
+ /// Contents of the file.
+ BinaryDataContainer m_aDataContainer;
+ std::unique_ptr<PDFOutputStream> m_pStream;
+
+ PDFEmbeddedFile()
+ : m_nObject(0)
+ {
+ }
+};
+
+struct PDFPopupAnnotation : public PDFAnnotation
+{
+ /// ID of the parent object.
+ sal_Int32 m_nParentObject;
+};
+
+struct PDFNoteEntry : public PDFAnnotation
+{
+ PDFNote m_aContents;
+
+ PDFPopupAnnotation m_aPopUpAnnotation;
+
+ PDFNoteEntry()
+ {}
+};
+
+/// A PDF Screen annotation.
+struct PDFScreen : public PDFAnnotation
+{
+ /// Linked video.
+ OUString m_aURL;
+ /// Embedded video.
+ OUString m_aTempFileURL;
+ /// ID of the EmbeddedFile object.
+ sal_Int32 m_nTempFileObject;
+ /// alternative text description
+ OUString m_AltText;
+ sal_Int32 m_nStructParent;
+ OUString m_MimeType;
+
+ PDFScreen(OUString const& rAltText, OUString const& rMimeType)
+ : m_nTempFileObject(0)
+ , m_AltText(rAltText)
+ , m_nStructParent(-1)
+ , m_MimeType(rMimeType)
+ {
+ }
+};
+
+struct PDFWidget : public PDFAnnotation
+{
+ PDFWriter::WidgetType m_eType;
+ OString m_aName;
+ OUString m_aDescription;
+ OUString m_aText;
+ DrawTextFlags m_nTextStyle;
+ OUString m_aValue;
+ OString m_aDAString;
+ OString m_aDRDict;
+ OString m_aMKDict;
+ OString m_aMKDictCAString; // i12626, added to be able to encrypt the /CA text string
+ // since the object number is not known at the moment
+ // of filling m_aMKDict, the string will be encrypted when emitted.
+ // the /CA string MUST BE the last added to m_aMKDict
+ // see code for details
+ sal_Int32 m_nFlags;
+ sal_Int32 m_nParent; // if not 0, parent's object number
+ std::vector<sal_Int32> m_aKids; // widget children, contains object numbers
+ std::vector<sal_Int32> m_aKidsIndex; // widget children, contains index to m_aWidgets
+ OUString m_aOnValue;
+ OUString m_aOffValue;
+ sal_Int32 m_nTabOrder; // lowest number gets first in tab order
+ sal_Int32 m_nRadioGroup;
+ sal_Int32 m_nMaxLen;
+ PDFWriter::FormatType m_nFormat;
+ OUString m_aCurrencySymbol;
+ sal_Int32 m_nDecimalAccuracy;
+ bool m_bPrependCurrencySymbol;
+ OUString m_aTimeFormat;
+ OUString m_aDateFormat;
+ bool m_bSubmit;
+ bool m_bSubmitGet;
+ sal_Int32 m_nDest;
+ std::vector<OUString> m_aListEntries;
+ std::vector<sal_Int32> m_aSelectedEntries;
+ typedef std::unordered_map<OString, SvMemoryStream*> PDFAppearanceStreams;
+ std::unordered_map<OString, PDFAppearanceStreams> m_aAppearances;
+ sal_Int32 m_nStructParent = -1;
+
+ PDFWidget()
+ : m_eType( PDFWriter::PushButton ),
+ m_nTextStyle( DrawTextFlags::NONE ),
+ m_nFlags( 0 ),
+ m_nParent( 0 ),
+ m_nTabOrder( 0 ),
+ m_nRadioGroup( -1 ),
+ m_nMaxLen( 0 ),
+ m_nFormat( PDFWriter::FormatType::Text ),
+ m_nDecimalAccuracy ( 0 ),
+ m_bPrependCurrencySymbol( false ),
+ m_bSubmit( false ),
+ m_bSubmitGet( false ),
+ m_nDest( -1 )
+ {}
+};
+
+struct PDFStructureAttribute
+{
+ PDFWriter::StructAttributeValue eValue;
+ sal_Int32 nValue;
+
+ PDFStructureAttribute()
+ : eValue( PDFWriter::Invalid ),
+ nValue( 0 )
+ {}
+
+ explicit PDFStructureAttribute( PDFWriter::StructAttributeValue eVal )
+ : eValue( eVal ),
+ nValue( 0 )
+ {}
+
+ explicit PDFStructureAttribute( sal_Int32 nVal )
+ : eValue( PDFWriter::Invalid ),
+ nValue( nVal )
+ {}
+};
+
+struct ObjReference { sal_Int32 const nObject; };
+struct ObjReferenceObj { sal_Int32 const nObject; };
+struct MCIDReference { sal_Int32 const nPageObj; sal_Int32 const nMCID; };
+typedef ::std::variant<ObjReference, ObjReferenceObj, MCIDReference> PDFStructureElementKid;
+
+struct PDFStructureElement
+{
+ sal_Int32 m_nObject;
+ ::std::optional<PDFWriter::StructElement> m_oType;
+ OString m_aAlias;
+ sal_Int32 m_nOwnElement; // index into structure vector
+ sal_Int32 m_nParentElement; // index into structure vector
+ sal_Int32 m_nFirstPageObject;
+ bool m_bOpenMCSeq;
+ std::vector< sal_Int32 > m_aChildren; // indexes into structure vector
+ std::list< PDFStructureElementKid > m_aKids;
+ std::map<PDFWriter::StructAttribute, PDFStructureAttribute >
+ m_aAttributes;
+ ::std::vector<sal_Int32> m_AnnotIds;
+ tools::Rectangle m_aBBox;
+ OUString m_aActualText;
+ OUString m_aAltText;
+ css::lang::Locale m_aLocale;
+
+ // m_aContents contains the element's marked content sequence
+ // as pairs of (page nr, MCID)
+
+ PDFStructureElement()
+ : m_nObject( 0 ),
+ m_nOwnElement( -1 ),
+ m_nParentElement( -1 ),
+ m_nFirstPageObject( 0 ),
+ m_bOpenMCSeq( false )
+ {
+ }
+
+};
+
+// helper structure for drawLayout and friends
+struct PDFGlyph
+{
+ basegfx::B2DPoint const m_aPos;
+ const GlyphItem* m_pGlyph;
+ const LogicalFontInstance* m_pFont;
+ sal_Int32 const m_nNativeWidth;
+ sal_Int32 const m_nMappedFontId;
+ sal_uInt8 const m_nMappedGlyphId;
+ int const m_nCharPos;
+
+ PDFGlyph( const basegfx::B2DPoint& rPos,
+ const GlyphItem* pGlyph,
+ const LogicalFontInstance* pFont,
+ sal_Int32 nNativeWidth,
+ sal_Int32 nFontId,
+ sal_uInt8 nMappedGlyphId,
+ int nCharPos )
+ : m_aPos( rPos ), m_pGlyph(pGlyph), m_pFont(pFont), m_nNativeWidth( nNativeWidth ),
+ m_nMappedFontId( nFontId ), m_nMappedGlyphId( nMappedGlyphId ),
+ m_nCharPos(nCharPos)
+ {}
+};
+
+struct StreamRedirect
+{
+ SvStream* m_pStream;
+ MapMode m_aMapMode;
+ tools::Rectangle m_aTargetRect;
+ ResourceDict m_aResourceDict;
+};
+
+// graphics state
+struct GraphicsState
+{
+ vcl::Font m_aFont;
+ MapMode m_aMapMode;
+ Color m_aLineColor;
+ Color m_aFillColor;
+ Color m_aTextLineColor;
+ Color m_aOverlineColor;
+ basegfx::B2DPolyPolygon m_aClipRegion;
+ bool m_bClipRegion;
+ vcl::text::ComplexTextLayoutFlags m_nLayoutMode;
+ LanguageType m_aDigitLanguage;
+ PushFlags m_nFlags;
+ GraphicsStateUpdateFlags m_nUpdateFlags;
+
+ GraphicsState() :
+ m_aLineColor( COL_TRANSPARENT ),
+ m_aFillColor( COL_TRANSPARENT ),
+ m_aTextLineColor( COL_TRANSPARENT ),
+ m_aOverlineColor( COL_TRANSPARENT ),
+ m_bClipRegion( false ),
+ m_nLayoutMode( vcl::text::ComplexTextLayoutFlags::Default ),
+ m_aDigitLanguage( 0 ),
+ m_nFlags( PushFlags::ALL ),
+ m_nUpdateFlags( GraphicsStateUpdateFlags::All )
+ {}
+};
+
+enum class Mode { DEFAULT, NOWRITE };
+
+struct PDFDocumentAttachedFile
+{
+ OUString maFilename;
+ OUString maMimeType;
+ OUString maDescription;
+ sal_Int32 mnEmbeddedFileObjectId;
+ sal_Int32 mnObjectId;
+};
+
+} // end pdf namespace
+
+class PDFWriterImpl final : public VirtualDevice, public PDFObjectContainer
+{
+ friend class PDFStreamIf;
+
+public:
+ friend struct vcl::pdf::PDFPage;
+
+ const char* getStructureTag( PDFWriter::StructElement );
+ static const char* getAttributeTag( PDFWriter::StructAttribute eAtr );
+ static const char* getAttributeValueTag( PDFWriter::StructAttributeValue eVal );
+
+ // returns true if compression was done
+ // else false
+ static bool compressStream( SvMemoryStream* );
+
+ static void convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut );
+
+private:
+ bool ImplNewFont() const override;
+ void ImplClearFontData(bool bNewFontLists) override;
+ void ImplRefreshFontData(bool bNewFontLists) override;
+ vcl::Region ClipToDeviceBounds(vcl::Region aRegion) const override;
+ void DrawHatchLine_DrawLine(const Point& rStartPoint, const Point& rEndPoint) override;
+
+ MapMode m_aMapMode; // PDFWriterImpl scaled units
+ StyleSettings m_aWidgetStyleSettings;
+ std::vector< PDFPage > m_aPages;
+ /* maps object numbers to file offsets (needed for xref) */
+ std::vector< sal_uInt64 > m_aObjects;
+ /* contains Bitmaps until they are written to the
+ * file stream as XObjects*/
+ std::list< BitmapEmit > m_aBitmaps;
+ /* contains JPG streams until written to file */
+ std::vector<JPGEmit> m_aJPGs;
+ /*--->i56629 contains all named destinations ever set during the PDF creation,
+ destination id is always the destination's position in this vector
+ */
+ std::vector<PDFNamedDest> m_aNamedDests;
+ /* contains all dests ever set during the PDF creation,
+ dest id is always the dest's position in this vector
+ */
+ std::vector<PDFDest> m_aDests;
+ /** contains destinations accessible via a public Id, instead of being linked to by an ordinary link
+ */
+ ::std::map< sal_Int32, sal_Int32 > m_aDestinationIdTranslation;
+ /* contains all links ever set during PDF creation,
+ link id is always the link's position in this vector
+ */
+ std::vector<PDFLink> m_aLinks;
+ /// Contains all screen annotations.
+ std::vector<PDFScreen> m_aScreens;
+ /// Contains embedded files.
+ std::vector<PDFEmbeddedFile> m_aEmbeddedFiles;
+
+ std::vector<PDFDocumentAttachedFile> m_aDocumentAttachedFiles;
+
+ /* makes correctly encoded for export to PDF URLS
+ */
+ css::uno::Reference< css::util::XURLTransformer > m_xTrans;
+ /* maps arbitrary link ids for structure attributes to real link ids
+ (for setLinkPropertyId)
+ */
+ std::map<sal_Int32, sal_Int32> m_aLinkPropertyMap;
+ /* contains all outline items,
+ object 0 is the outline root
+ */
+ std::vector<PDFOutlineEntry> m_aOutline;
+ /* contains all notes set during PDF creation
+ */
+ std::vector<PDFNoteEntry> m_aNotes;
+ /* the root of the structure tree
+ */
+ std::vector<PDFStructureElement> m_aStructure;
+ /* current object in the structure hierarchy
+ */
+ sal_Int32 m_nCurrentStructElement;
+ std::stack<sal_Int32> m_StructElementStack;
+ /* structure parent tree */
+ std::vector< OString > m_aStructParentTree;
+ /* emit structure marks currently (aka. NonStructElement or not)
+ */
+ bool m_bEmitStructure;
+ /* role map of struct tree root */
+ std::unordered_map< OString, OString >
+ m_aRoleMap;
+ /* structure elements (object ids) that should have ID */
+ std::unordered_set<sal_Int32> m_StructElemObjsWithID;
+
+ /* contains all widgets used in the PDF
+ */
+ std::vector<PDFWidget> m_aWidgets;
+ /* maps radio group id to index of radio group control in m_aWidgets */
+ std::map< sal_Int32, sal_Int32 > m_aRadioGroupWidgets;
+ /* unordered_map for field names, used to ensure unique field names */
+ std::unordered_map< OString, sal_Int32 > m_aFieldNameMap;
+
+ /* contains Bitmaps for gradient functions until they are written
+ * to the file stream */
+ std::list< GradientEmit > m_aGradients;
+ /* contains bitmap tiling patterns */
+ std::vector< TilingEmit > m_aTilings;
+ std::vector< TransparencyEmit > m_aTransparentObjects;
+ /* contains all font subsets in use */
+ std::map<const vcl::font::PhysicalFontFace*, FontSubset> m_aSubsets;
+ std::map<const vcl::font::PhysicalFontFace*, EmbedFont> m_aSystemFonts;
+ std::map<const vcl::font::PhysicalFontFace*, FontSubset> m_aType3Fonts;
+ sal_Int32 m_nNextFID;
+
+ /// Cache some most recent bitmaps we've exported, in case we encounter them again..
+ o3tl::lru_map<BitmapChecksum,
+ std::shared_ptr<SvMemoryStream>> m_aPDFBmpCache;
+
+ sal_Int32 m_nCurrentPage;
+
+ sal_Int32 m_nCatalogObject;
+ // object number of the main signature dictionary
+ sal_Int32 m_nSignatureObject;
+ sal_Int64 m_nSignatureContentOffset;
+ sal_Int64 m_nSignatureLastByteRangeNoOffset;
+ sal_Int32 m_nResourceDict;
+ ResourceDict m_aGlobalResourceDict;
+ sal_Int32 m_nFontDictObject;
+ std::map< sal_Int32, sal_Int32 > m_aBuildinFontToObjectMap;
+
+ PDFWriter::PDFWriterContext m_aContext;
+ osl::File m_aFile;
+ bool m_bOpen;
+
+ ExternalPDFStreams m_aExternalPDFStreams;
+
+ /* output redirection; e.g. to accumulate content streams for
+ XObjects
+ */
+ std::list< StreamRedirect > m_aOutputStreams;
+
+ std::list< GraphicsState > m_aGraphicsStack;
+ GraphicsState m_aCurrentPDFState;
+
+ std::unique_ptr<ZCodec> m_pCodec;
+ std::unique_ptr<SvMemoryStream> m_pMemStream;
+
+ std::set< PDFWriter::ErrorCode > m_aErrors;
+
+ ::comphelper::Hash m_DocDigest;
+
+ sal_uInt64 getCurrentFilePosition()
+ {
+ sal_uInt64 nPosition{};
+ if (osl::File::E_None != m_aFile.getPos(nPosition))
+ {
+ m_aFile.close();
+ m_bOpen = false;
+ }
+ return nPosition;
+ }
+/*
+variables for PDF security
+i12626
+*/
+/* used to cipher the stream data and for password management */
+ rtlCipher m_aCipher;
+ /* pad string used for password in Standard security handler */
+ static const sal_uInt8 s_nPadString[ENCRYPTED_PWD_SIZE];
+
+ /* the encryption key, formed with the user password according to algorithm 3.2, maximum length is 16 bytes + 3 + 2
+ for 128 bit security */
+ sal_Int32 m_nKeyLength; // key length, 16 or 5
+ sal_Int32 m_nRC4KeyLength; // key length, 16 or 10, to be input to the algorithm 3.1
+
+ /* set to true if the following stream must be encrypted, used inside writeBuffer() */
+ bool m_bEncryptThisStream;
+
+ /* the numerical value of the access permissions, according to PDF spec, must be signed */
+ sal_Int32 m_nAccessPermissions;
+ /* string to hold the PDF creation date */
+ OString m_aCreationDateString;
+ /* string to hold the PDF creation date, for PDF/A metadata */
+ OString m_aCreationMetaDateString;
+ /* the buffer where the data are encrypted, dynamically allocated */
+ std::vector<sal_uInt8> m_vEncryptionBuffer;
+
+ void addRoleMap(OString aAlias, PDFWriter::StructElement eType);
+
+ /* this function implements part of the PDF spec algorithm 3.1 in encryption, the rest (the actual encryption) is in PDFWriterImpl::writeBuffer */
+ void checkAndEnableStreamEncryption( sal_Int32 nObject ) override;
+
+ void disableStreamEncryption() override { m_bEncryptThisStream = false; };
+
+ /* */
+ void enableStringEncryption( sal_Int32 nObject );
+
+// test if the encryption is active, if yes than encrypt the unicode string and add to the OStringBuffer parameter
+ void appendUnicodeTextStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer );
+
+ void appendLiteralStringEncrypt( std::u16string_view rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer, rtl_TextEncoding nEnc = RTL_TEXTENCODING_ASCII_US );
+ void appendLiteralStringEncrypt( std::string_view rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer );
+
+ /* creates fonts and subsets that will be emitted later */
+ void registerGlyph(const sal_GlyphId, const vcl::font::PhysicalFontFace*, const LogicalFontInstance* pFont, const std::vector<sal_Ucs>&, sal_Int32, sal_uInt8&, sal_Int32&);
+ void registerSimpleGlyph(const sal_GlyphId, const vcl::font::PhysicalFontFace*, const std::vector<sal_Ucs>&, sal_Int32, sal_uInt8&, sal_Int32&);
+
+ /* emits a text object according to the passed layout */
+ /* TODO: remove rText as soon as SalLayout will change so that rText is not necessary anymore */
+ void drawVerticalGlyphs( const std::vector<PDFGlyph>& rGlyphs, OStringBuffer& rLine, const Point& rAlignOffset, const Matrix3& rRotScale, double fAngle, double fXScale, sal_Int32 nFontHeight );
+ void drawHorizontalGlyphs( const std::vector<PDFGlyph>& rGlyphs, OStringBuffer& rLine, const Point& rAlignOffset, bool bFirst, double fAngle, double fXScale, sal_Int32 nFontHeight, sal_Int32 nPixelFontHeight );
+ void drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines );
+ void drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines );
+ void drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines );
+
+ /* writes differences between graphics stack and current real PDF
+ * state to the file
+ */
+ void updateGraphicsState(Mode mode = Mode::DEFAULT);
+
+ /* writes a transparency group object */
+ void writeTransparentObject( TransparencyEmit& rObject );
+
+ /* writes an XObject of type image, may create
+ a second for the mask
+ */
+ bool writeBitmapObject( const BitmapEmit& rObject, bool bMask = false );
+
+ void writeJPG( const JPGEmit& rEmit );
+ /// Writes the form XObject proxy for the image.
+ void writeReferenceXObject(const ReferenceXObjectEmit& rEmit);
+
+ /* tries to find the bitmap by its id and returns its emit data if exists,
+ else creates a new emit data block */
+ const BitmapEmit& createBitmapEmit( const BitmapEx& rBitmapEx, const Graphic& rGraphic, std::list<BitmapEmit>& rBitmaps, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams );
+ const BitmapEmit& createBitmapEmit( const BitmapEx& rBitmapEx, const Graphic& rGraphic );
+
+ /* writes the Do operation inside the content stream */
+ void drawBitmap( const Point& rDestPt, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor );
+ /* write the function object for a Gradient */
+ bool writeGradientFunction( GradientEmit const & rObject );
+ /* creates a GradientEmit and returns its object number */
+ sal_Int32 createGradient( const Gradient& rGradient, const Size& rSize );
+
+ /* writes all tilings */
+ bool emitTilings();
+ /* writes all gradient patterns */
+ bool emitGradients();
+ /* writes a builtin font object and returns its objectid (or 0 in case of failure ) */
+ sal_Int32 emitBuildinFont( const pdf::BuildinFontFace*, sal_Int32 nObject );
+ /* writes a type1 system font object and returns its mapping from font ids to object ids (or 0 in case of failure ) */
+ std::map< sal_Int32, sal_Int32 > emitSystemFont(const vcl::font::PhysicalFontFace*, EmbedFont const &);
+ /* writes a type3 font object and appends it to the font id mapping, or returns false in case of failure */
+ bool emitType3Font(const vcl::font::PhysicalFontFace*, const FontSubset&, std::map<sal_Int32, sal_Int32>&);
+ /* writes a font descriptor and returns its object id (or 0) */
+ sal_Int32 emitFontDescriptor(const vcl::font::PhysicalFontFace*, FontSubsetInfo const &, sal_Int32 nSubsetID, sal_Int32 nStream);
+ /* writes a ToUnicode cmap, returns the corresponding stream object */
+ sal_Int32 createToUnicodeCMap( sal_uInt8 const * pEncoding, const std::vector<sal_Ucs>& CodeUnits, const sal_Int32* pCodeUnitsPerGlyph,
+ const sal_Int32* pEncToUnicodeIndex, uint32_t nGlyphs );
+
+ /* get resource dict object number */
+ sal_Int32 getResourceDictObj()
+ {
+ if( m_nResourceDict <= 0 )
+ m_nResourceDict = createObject();
+ return m_nResourceDict;
+ }
+ /* get the font dict object */
+ sal_Int32 getFontDictObject()
+ {
+ if( m_nFontDictObject <= 0 )
+ m_nFontDictObject = createObject();
+ return m_nFontDictObject;
+ }
+ /* push resource into current (redirected) resource dict */
+ static void pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams );
+ void pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject );
+
+ void appendBuildinFontsToDict( OStringBuffer& rDict ) const;
+ /* writes the font dictionary and emits all font objects
+ * returns object id of font directory (or 0 on error)
+ */
+ bool emitFonts();
+ /* writes the Resource dictionary;
+ * returns dict object id (or 0 on error)
+ */
+ sal_Int32 emitResources();
+ // appends a dest
+ bool appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer );
+ // write all links
+ bool emitLinkAnnotations();
+ // Write all screen annotations.
+ bool emitScreenAnnotations();
+
+ void emitTextAnnotationLine(OStringBuffer & aLine, PDFNoteEntry const & rNote);
+ static void emitPopupAnnotationLine(OStringBuffer & aLine, PDFPopupAnnotation const & rPopUp);
+ // write all notes
+ bool emitNoteAnnotations();
+
+ // write the appearance streams of a widget
+ bool emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict );
+ // clean up radio button "On" values
+ void ensureUniqueRadioOnValues();
+ // write all widgets
+ bool emitWidgetAnnotations();
+ // writes all annotation objects
+ bool emitAnnotations();
+ /// Writes embedded files.
+ bool emitEmbeddedFiles();
+ //write the named destination stuff
+ sal_Int32 emitNamedDestinations();//i56629
+ // writes outline dict and tree
+ sal_Int32 emitOutline();
+ template<typename T> void AppendAnnotKid(PDFStructureElement& i_rEle, T & rAnnot);
+ // puts the attribute objects of a structure element into the returned string,
+ // helper for emitStructure
+ OString emitStructureAttributes( PDFStructureElement& rEle );
+ //--->i94258
+ // the maximum array elements allowed for PDF array object
+ static const sal_uInt32 ncMaxPDFArraySize = 8191;
+ //check if internal dummy container are needed in the structure elements
+ void addInternalStructureContainer( PDFStructureElement& rEle );
+ //<---i94258
+ // writes document structure
+ sal_Int32 emitStructure( PDFStructureElement& rEle );
+ // writes structure parent tree
+ sal_Int32 emitStructParentTree( sal_Int32 nTreeObject );
+ // writes structure IDTree
+ sal_Int32 emitStructIDTree(sal_Int32 nTreeObject);
+ // writes page tree and catalog
+ bool emitCatalog();
+ // writes signature dictionary object
+ bool emitSignature();
+ // creates a PKCS7 object using the ByteRange and overwrite /Contents
+ // of the signature dictionary
+ bool finalizeSignature();
+ // writes xref and trailer
+ bool emitTrailer();
+ // emits info dict (if applicable)
+ sal_Int32 emitInfoDict( );
+
+ // acrobat reader 5 and 6 use the order of the annotations
+ // as their tab order; since PDF1.5 one can make the
+ // tab order explicit by using the structure tree
+ void sortWidgets();
+
+ // updates the count numbers of outline items
+ sal_Int32 updateOutlineItemCount( std::vector< sal_Int32 >& rCounts,
+ sal_Int32 nItemLevel,
+ sal_Int32 nCurrentItemId );
+ // default appearances for widgets
+ sal_Int32 findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rRadio );
+ Font replaceFont( const Font& rControlFont, const Font& rAppSetFont );
+ sal_Int32 getBestBuildinFont( const Font& rFont );
+ sal_Int32 getSystemFont( const Font& i_rFont );
+
+ // used for edit and listbox
+ Font drawFieldBorder( PDFWidget&, const PDFWriter::AnyWidget&, const StyleSettings& );
+
+ void createDefaultPushButtonAppearance( PDFWidget&, const PDFWriter::PushButtonWidget& rWidget );
+ void createDefaultCheckBoxAppearance( PDFWidget&, const PDFWriter::CheckBoxWidget& rWidget );
+ void createDefaultRadioButtonAppearance( PDFWidget&, const PDFWriter::RadioButtonWidget& rWidget );
+ void createDefaultEditAppearance( PDFWidget&, const PDFWriter::EditWidget& rWidget );
+ void createDefaultListBoxAppearance( PDFWidget&, const PDFWriter::ListBoxWidget& rWidget );
+
+ /* ensure proper escapement and uniqueness of field names */
+ void createWidgetFieldName( sal_Int32 i_nWidgetsIndex, const PDFWriter::AnyWidget& i_rInWidget );
+ /// See vcl::PDFObjectContainer::createObject().
+ sal_Int32 createObject() override;
+ /// See vcl::PDFObjectContainer::updateObject().
+ bool updateObject( sal_Int32 n ) override;
+
+ /// See vcl::PDFObjectContainer::writeBuffer().
+ bool writeBufferBytes( const void* pBuffer, sal_uInt64 nBytes ) override;
+ void beginCompression();
+ void endCompression();
+ void beginRedirect( SvStream* pStream, const tools::Rectangle& );
+ SvStream* endRedirect();
+
+ void endPage();
+
+ void beginStructureElementMCSeq();
+ enum class EndMode { Default, OnlyStruct };
+ void endStructureElementMCSeq(EndMode = EndMode::Default);
+ /** checks whether a non struct element lies in the ancestor hierarchy
+ of the current structure element
+
+ @returns
+ true if no NonStructElement was found in ancestor path and tagged
+ PDF output is enabled
+ false else
+ */
+ bool checkEmitStructure();
+
+ /* draws an emphasis mark */
+ void drawEmphasisMark( tools::Long nX, tools::Long nY, const tools::PolyPolygon& rPolyPoly, bool bPolyLine, const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 );
+
+ /* true if PDF/A-1a or PDF/A-1b is output */
+ bool m_bIsPDF_A1;
+ /* true if PDF/A-2a is output */
+ bool m_bIsPDF_A2;
+
+ /* PDF/UA support enabled */
+ bool m_bIsPDF_UA;
+
+ bool m_bIsPDF_A3;
+
+ PDFWriter& m_rOuterFace;
+
+ /*
+ i12626
+ methods for PDF security
+
+ pad a password according algorithm 3.2, step 1 */
+ static void padPassword( std::u16string_view i_rPassword, sal_uInt8* o_pPaddedPW );
+ /* algorithm 3.2: compute an encryption key */
+ static bool computeEncryptionKey( EncHashTransporter*,
+ vcl::PDFWriter::PDFEncryptionProperties& io_rProperties,
+ sal_Int32 i_nAccessPermissions
+ );
+ /* algorithm 3.3: computing the encryption dictionary'ss owner password value ( /O ) */
+ static bool computeODictionaryValue( const sal_uInt8* i_pPaddedOwnerPassword, const sal_uInt8* i_pPaddedUserPassword,
+ std::vector< sal_uInt8 >& io_rOValue,
+ sal_Int32 i_nKeyLength
+ );
+ /* algorithm 3.4 or 3.5: computing the encryption dictionary's user password value ( /U ) revision 2 or 3 of the standard security handler */
+ static bool computeUDictionaryValue( EncHashTransporter* i_pTransporter,
+ vcl::PDFWriter::PDFEncryptionProperties& io_rProperties,
+ sal_Int32 i_nKeyLength,
+ sal_Int32 i_nAccessPermissions
+ );
+
+ static void computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier,
+ const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
+ const OString& i_rCString1,
+ const css::util::DateTime& rCreationMetaDate,
+ OString& o_rCString2
+ );
+ static sal_Int32 computeAccessPermissions( const vcl::PDFWriter::PDFEncryptionProperties& i_rProperties,
+ sal_Int32& o_rKeyLength, sal_Int32& o_rRC4KeyLength );
+ void setupDocInfo();
+ bool prepareEncryption( const css::uno::Reference< css::beans::XMaterialHolder >& );
+
+ // helper for playMetafile
+ void implWriteGradient( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient,
+ VirtualDevice* pDummyVDev, const vcl::PDFWriter::PlayMetafileContext& );
+ void implWriteBitmapEx( const Point& rPoint, const Size& rSize, const BitmapEx& rBitmapEx, const Graphic& i_pGraphic,
+ VirtualDevice const * pDummyVDev, const vcl::PDFWriter::PlayMetafileContext& );
+
+ // helpers for CCITT 1bit bitmap stream
+ void putG4Bits( sal_uInt32 i_nLength, sal_uInt32 i_nCode, BitStreamState& io_rState );
+ void putG4Span( tools::Long i_nSpan, bool i_bWhitePixel, BitStreamState& io_rState );
+ void writeG4Stream( BitmapReadAccess const * i_pBitmap );
+
+ // color helper functions
+ void appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer );
+ void appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer );
+public:
+ PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext, const css::uno::Reference< css::beans::XMaterialHolder >&, PDFWriter& );
+ ~PDFWriterImpl() override;
+ void dispose() override;
+
+ static css::uno::Reference< css::beans::XMaterialHolder >
+ initEncryption( const OUString& i_rOwnerPassword,
+ const OUString& i_rUserPassword );
+
+ /* document structure */
+ void newPage( double nPageWidth , double nPageHeight, PDFWriter::Orientation eOrientation );
+ bool emit();
+ const std::set< PDFWriter::ErrorCode > & getErrors() const { return m_aErrors;}
+ void insertError( PDFWriter::ErrorCode eErr ) { m_aErrors.insert( eErr ); }
+ void playMetafile( const GDIMetaFile&, vcl::PDFExtOutDevData*, const vcl::PDFWriter::PlayMetafileContext&, VirtualDevice* pDummyDev = nullptr );
+
+ Size getCurPageSize() const
+ {
+ Size aSize;
+ if( m_nCurrentPage >= 0 && o3tl::make_unsigned(m_nCurrentPage) < m_aPages.size() )
+ aSize = Size( m_aPages[ m_nCurrentPage ].m_nPageWidth, m_aPages[ m_nCurrentPage ].m_nPageHeight );
+ return aSize;
+ }
+
+ void setDocumentLocale( const css::lang::Locale& rLoc )
+ { m_aContext.DocumentLocale = rLoc; }
+
+ /* graphics state */
+ void push( PushFlags nFlags );
+ void pop();
+
+ void setFont( const Font& rFont );
+
+ void setMapMode( const MapMode& rMapMode );
+
+ const MapMode& getMapMode() { return m_aGraphicsStack.front().m_aMapMode; }
+
+ void setLineColor( const Color& rColor )
+ {
+ m_aGraphicsStack.front().m_aLineColor = rColor.IsTransparent() ? COL_TRANSPARENT : rColor;
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::LineColor;
+ }
+
+ void setFillColor( const Color& rColor )
+ {
+ m_aGraphicsStack.front().m_aFillColor = rColor.IsTransparent() ? COL_TRANSPARENT : rColor;
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::FillColor;
+ }
+
+ void setTextLineColor()
+ {
+ m_aGraphicsStack.front().m_aTextLineColor = COL_TRANSPARENT;
+ }
+
+ void setTextLineColor( const Color& rColor )
+ {
+ m_aGraphicsStack.front().m_aTextLineColor = rColor;
+ }
+
+ void setOverlineColor()
+ {
+ m_aGraphicsStack.front().m_aOverlineColor = COL_TRANSPARENT;
+ }
+
+ void setOverlineColor( const Color& rColor )
+ {
+ m_aGraphicsStack.front().m_aOverlineColor = rColor;
+ }
+
+ void setTextFillColor( const Color& rColor )
+ {
+ m_aGraphicsStack.front().m_aFont.SetFillColor( rColor );
+ m_aGraphicsStack.front().m_aFont.SetTransparent( rColor.IsTransparent() );
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
+ }
+ void setTextFillColor()
+ {
+ m_aGraphicsStack.front().m_aFont.SetFillColor( COL_TRANSPARENT );
+ m_aGraphicsStack.front().m_aFont.SetTransparent( true );
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
+ }
+ void setTextColor( const Color& rColor )
+ {
+ m_aGraphicsStack.front().m_aFont.SetColor( rColor );
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
+ }
+
+ void clearClipRegion()
+ {
+ m_aGraphicsStack.front().m_aClipRegion.clear();
+ m_aGraphicsStack.front().m_bClipRegion = false;
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
+ }
+
+ void setClipRegion( const basegfx::B2DPolyPolygon& rRegion );
+
+ void moveClipRegion( sal_Int32 nX, sal_Int32 nY );
+
+ void intersectClipRegion( const tools::Rectangle& rRect );
+
+ void intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion );
+
+ void setLayoutMode( vcl::text::ComplexTextLayoutFlags nLayoutMode )
+ {
+ m_aGraphicsStack.front().m_nLayoutMode = nLayoutMode;
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::LayoutMode;
+ }
+
+ void setDigitLanguage( LanguageType eLang )
+ {
+ m_aGraphicsStack.front().m_aDigitLanguage = eLang;
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::DigitLanguage;
+ }
+
+ void setTextAlign( TextAlign eAlign )
+ {
+ m_aGraphicsStack.front().m_aFont.SetAlignment( eAlign );
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
+ }
+
+ /* actual drawing functions */
+ void drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines = true );
+ void drawTextArray( const Point& rPos, const OUString& rText, KernArraySpan pDXArray, std::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex, sal_Int32 nLen );
+ void drawStretchText( const Point& rPos, sal_Int32 nWidth, const OUString& rText,
+ sal_Int32 nIndex, sal_Int32 nLen );
+ void drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle );
+ void drawTextLine( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove );
+ void drawWaveTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove );
+ void drawStraightTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove );
+ void drawStrikeoutLine( OStringBuffer& aLine, tools::Long nWidth, FontStrikeout eStrikeout, Color aColor );
+ void drawStrikeoutChar( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout );
+
+ void drawLine( const Point& rStart, const Point& rStop );
+ void drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo );
+ void drawPolygon( const tools::Polygon& rPoly );
+ void drawPolyPolygon( const tools::PolyPolygon& rPolyPoly );
+ void drawPolyLine( const tools::Polygon& rPoly );
+ void drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo );
+ void drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo );
+
+ void drawPixel( const Point& rPt, const Color& rColor );
+
+ void drawRectangle( const tools::Rectangle& rRect );
+ void drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound );
+ void drawEllipse( const tools::Rectangle& rRect );
+ void drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWidthChord );
+
+ void drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic );
+ void drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEx& rBitmap );
+ void drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const AlphaMask& rAlphaMask, const Graphic& rGraphic );
+ /// Stores the original PDF data from rGraphic as an embedded file.
+ void createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject);
+
+ void drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient );
+ void drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch );
+ void drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall );
+ void drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent );
+ void beginTransparencyGroup();
+ void endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent );
+
+ void emitComment( const char* pComment );
+
+ //--->i56629 named destinations
+ sal_Int32 createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType );
+
+ //--->i59651
+ //emits output intent
+ sal_Int32 emitOutputIntent();
+
+ //emits the document metadata
+ sal_Int32 emitDocumentMetadata();
+
+ // links
+ sal_Int32 createLink(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText);
+ sal_Int32 createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType );
+ sal_Int32 registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType );
+ void setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId );
+ void setLinkURL( sal_Int32 nLinkId, const OUString& rURL );
+ void setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId );
+
+ // screens
+ sal_Int32 createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText, OUString const& rMimeType);
+ void setScreenURL(sal_Int32 nScreenId, const OUString& rURL);
+ void setScreenStream(sal_Int32 nScreenId, const OUString& rURL);
+
+ // outline
+ sal_Int32 createOutlineItem( sal_Int32 nParent, std::u16string_view rText, sal_Int32 nDestID );
+ void setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent );
+ void setOutlineItemText( sal_Int32 nItem, std::u16string_view rText );
+ void setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID );
+
+ // notes
+ void createNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr );
+ // structure elements
+ sal_Int32 ensureStructureElement();
+ void initStructureElement(sal_Int32 id, PDFWriter::StructElement eType, std::u16string_view rAlias);
+ void beginStructureElement(sal_Int32 id);
+ void endStructureElement();
+ bool setCurrentStructureElement( sal_Int32 nElement );
+ bool setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal );
+ bool setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue );
+ void setStructureBoundingBox( const tools::Rectangle& rRect );
+ void setStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds);
+ void setActualText( const OUString& rText );
+ void setAlternateText( const OUString& rText );
+
+ // transitional effects
+ void setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr );
+
+ // controls
+ sal_Int32 createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr = -1 );
+
+ // attached file
+ void addDocumentAttachedFile(OUString const& rFileName, OUString const& rMimeType, OUString const& rDescription, std::unique_ptr<PDFOutputStream> rStream);
+
+ sal_Int32 addEmbeddedFile(BinaryDataContainer const & rDataContainer);
+ sal_Int32 addEmbeddedFile(std::unique_ptr<PDFOutputStream> rStream, OUString const& rMimeType);
+
+ // helper: eventually begin marked content sequence and
+ // emit a comment in debug case
+ void MARK( const char* pString );
+};
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/ppdparser.hxx b/vcl/inc/ppdparser.hxx
new file mode 100644
index 0000000000..cbc1b94b4e
--- /dev/null
+++ b/vcl/inc/ppdparser.hxx
@@ -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 .
+ */
+#ifndef INCLUDED_VCL_PPDPARSER_HXX
+#define INCLUDED_VCL_PPDPARSER_HXX
+
+#include <sal/config.h>
+
+#include <cstddef>
+#include <memory>
+#include <string_view>
+#include <unordered_map>
+#include <vector>
+
+#include <rtl/string.hxx>
+#include <rtl/ustring.hxx>
+#include <tools/solar.h>
+#include <vcl/dllapi.h>
+
+#define PRINTER_PPDDIR "driver"
+
+namespace psp {
+
+enum class orientation;
+
+class PPDCache;
+class PPDTranslator;
+
+enum PPDValueType { eInvocation, eQuoted, eSymbol, eString, eNo };
+
+struct VCL_DLLPUBLIC PPDValue
+{
+ PPDValueType m_eType;
+ //CustomOption stuff for fdo#43049
+ //see http://www.cups.org/documentation.php/spec-ppd.html#OPTIONS
+ //for full specs, only the basics are implemented here
+ bool m_bCustomOption;
+ mutable bool m_bCustomOptionSetViaApp;
+ mutable OUString m_aCustomOption;
+ OUString m_aOption;
+ OUString m_aValue;
+};
+
+
+/*
+ * PPDKey - a container for the available options (=values) of a PPD keyword
+ */
+
+class PPDKey
+{
+ friend class PPDParser;
+ friend class CPDManager;
+
+ typedef std::unordered_map< OUString, PPDValue > hash_type;
+ typedef std::vector< PPDValue* > value_type;
+
+ OUString m_aKey;
+ hash_type m_aValues;
+ value_type m_aOrderedValues;
+ const PPDValue* m_pDefaultValue;
+ bool m_bQueryValue;
+ OUString m_aGroup;
+
+private:
+
+ bool m_bUIOption;
+ int m_nOrderDependency;
+
+ void eraseValue( const OUString& rOption );
+public:
+ PPDKey( OUString aKey );
+ ~PPDKey();
+
+ PPDValue* insertValue(const OUString& rOption, PPDValueType eType, bool bCustomOption = false);
+ int countValues() const
+ { return m_aValues.size(); }
+ // neither getValue will return the query option
+ const PPDValue* getValue( int n ) const;
+ const PPDValue* getValue( const OUString& rOption ) const;
+ const PPDValue* getValueCaseInsensitive( const OUString& rOption ) const;
+ const PPDValue* getDefaultValue() const { return m_pDefaultValue; }
+ const OUString& getGroup() const { return m_aGroup; }
+
+ const OUString& getKey() const { return m_aKey; }
+ bool isUIKey() const { return m_bUIOption; }
+ int getOrderDependency() const { return m_nOrderDependency; }
+};
+
+// define a hash for PPDKey
+struct PPDKeyhash
+{
+ size_t operator()( const PPDKey * pKey) const
+ { return reinterpret_cast<size_t>(pKey); }
+};
+
+/*
+ * PPDParser - parses a PPD file and contains all available keys from it
+ */
+
+class PPDParser
+{
+ friend class PPDContext;
+ friend class CUPSManager;
+ friend class CPDManager;
+ friend class PPDCache;
+
+ typedef std::unordered_map< OUString, std::unique_ptr<PPDKey> > hash_type;
+ typedef std::vector< PPDKey* > value_type;
+
+ void insertKey( std::unique_ptr<PPDKey> pKey );
+public:
+ struct PPDConstraint
+ {
+ const PPDKey* m_pKey1;
+ const PPDValue* m_pOption1;
+ const PPDKey* m_pKey2;
+ const PPDValue* m_pOption2;
+
+ PPDConstraint() : m_pKey1( nullptr ), m_pOption1( nullptr ), m_pKey2( nullptr ), m_pOption2( nullptr ) {}
+ };
+private:
+ hash_type m_aKeys;
+ value_type m_aOrderedKeys;
+ ::std::vector< PPDConstraint > m_aConstraints;
+
+ // the full path of the PPD file
+ OUString m_aFile;
+ // some basic attributes
+ rtl_TextEncoding m_aFileEncoding;
+
+
+ // shortcuts to important keys and their default values
+ // imageable area
+ const PPDKey* m_pImageableAreas;
+ // paper dimensions
+ const PPDValue* m_pDefaultPaperDimension;
+ const PPDKey* m_pPaperDimensions;
+ // paper trays
+ const PPDValue* m_pDefaultInputSlot;
+ // resolutions
+ const PPDValue* m_pDefaultResolution;
+
+ // translations
+ std::unique_ptr<PPDTranslator> m_pTranslator;
+
+ PPDParser( OUString aFile );
+ PPDParser(OUString aFile, const std::vector<PPDKey*>& keys);
+
+ void parseOrderDependency(const OString& rLine);
+ void parseOpenUI(const OString& rLine, std::string_view rPPDGroup);
+ void parseConstraint(const OString& rLine);
+ void parse( std::vector< OString >& rLines );
+
+ OUString handleTranslation(const OString& i_rString, bool i_bIsGlobalized);
+
+ static void scanPPDDir( const OUString& rDir );
+ static void initPPDFiles(PPDCache &rPPDCache);
+ static OUString getPPDFile( const OUString& rFile );
+
+ OUString matchPaperImpl(int nWidth, int nHeight, bool bDontSwap = false, psp::orientation* pOrientation = nullptr) const;
+
+public:
+ ~PPDParser();
+ static const PPDParser* getParser( const OUString& rFile );
+
+ const PPDKey* getKey( int n ) const;
+ const PPDKey* getKey( const OUString& rKey ) const;
+ int getKeys() const { return m_aKeys.size(); }
+ bool hasKey( const PPDKey* ) const;
+
+ const ::std::vector< PPDConstraint >& getConstraints() const { return m_aConstraints; }
+
+ OUString getDefaultPaperDimension() const;
+ void getDefaultPaperDimension( int& rWidth, int& rHeight ) const
+ { getPaperDimension( getDefaultPaperDimension(), rWidth, rHeight ); }
+ bool getPaperDimension( std::u16string_view rPaperName,
+ int& rWidth, int& rHeight ) const;
+ // width and height in pt
+ // returns false if paper not found
+
+ // match the best paper for width and height
+ OUString matchPaper( int nWidth, int nHeight, psp::orientation* pOrientation = nullptr ) const;
+
+ bool getMargins( std::u16string_view rPaperName,
+ int &rLeft, int& rRight,
+ int &rUpper, int& rLower ) const;
+ // values in pt
+ // returns true if paper found
+
+ // values int pt
+
+ OUString getDefaultInputSlot() const;
+
+ void getDefaultResolution( int& rXRes, int& rYRes ) const;
+ // values in dpi
+ static void getResolutionFromString( std::u16string_view, int&, int& );
+ // helper function
+
+ OUString translateKey( const OUString& i_rKey ) const;
+ OUString translateOption( std::u16string_view i_rKey,
+ const OUString& i_rOption ) const;
+};
+
+
+/*
+ * PPDContext - a class to manage user definable states based on the
+ * contents of a PPDParser.
+ */
+
+class PPDContext
+{
+ typedef std::unordered_map< const PPDKey*, const PPDValue*, PPDKeyhash > hash_type;
+ hash_type m_aCurrentValues;
+ const PPDParser* m_pParser;
+
+ // returns false: check failed, new value is constrained
+ // true: check succeeded, new value can be set
+ bool checkConstraints( const PPDKey*, const PPDValue*, bool bDoReset );
+ bool resetValue( const PPDKey*, bool bDefaultable = false );
+public:
+ PPDContext();
+ PPDContext( const PPDContext& rContext ) { operator=( rContext ); }
+ PPDContext& operator=( const PPDContext& rContext ) = default;
+ PPDContext& operator=( PPDContext&& rContext );
+
+ void setParser( const PPDParser* );
+ const PPDParser* getParser() const { return m_pParser; }
+
+ const PPDValue* getValue( const PPDKey* ) const;
+ const PPDValue* setValue( const PPDKey*, const PPDValue*, bool bDontCareForConstraints = false );
+
+ std::size_t countValuesModified() const { return m_aCurrentValues.size(); }
+ const PPDKey* getModifiedKey( std::size_t n ) const;
+
+ // public wrapper for the private method
+ bool checkConstraints( const PPDKey*, const PPDValue* );
+
+ // for printer setup
+ char* getStreamableBuffer( sal_uLong& rBytes ) const;
+ void rebuildFromStreamBuffer(const std::vector<char> &rBuffer);
+
+ // convenience
+ int getRenderResolution() const;
+
+ // width, height in points, paper will contain the name of the selected
+ // paper after the call
+ void getPageSize( OUString& rPaper, int& rWidth, int& rHeight ) const;
+};
+
+} // namespace
+
+#endif // INCLUDED_VCL_PPDPARSER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/print.h b/vcl/inc/print.h
new file mode 100644
index 0000000000..dc61c9cd96
--- /dev/null
+++ b/vcl/inc/print.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_PRINT_H
+#define INCLUDED_VCL_INC_PRINT_H
+
+#include <rtl/ustring.hxx>
+#include <vcl/dllapi.h>
+#include <vcl/QueueInfo.hxx>
+#include "salprn.hxx"
+
+#include <vector>
+#include <unordered_map>
+
+class JobSetup;
+
+namespace vcl
+{ class PrinterListener; }
+
+struct ImplPrnQueueData
+{
+ std::unique_ptr<QueueInfo> mpQueueInfo;
+ std::unique_ptr<SalPrinterQueueInfo> mpSalQueueInfo;
+
+// unlike other similar places, we need to ifdef this to keep old GCC baseline happy
+#ifdef _MSC_VER
+ ImplPrnQueueData() {}
+ ImplPrnQueueData(ImplPrnQueueData&&) = default;
+
+ ImplPrnQueueData& operator=( ImplPrnQueueData const & ) = delete; // MSVC2017 workaround
+ ImplPrnQueueData( ImplPrnQueueData const & ) = delete; // MSVC2017 workaround
+#endif
+};
+
+class VCL_PLUGIN_PUBLIC ImplPrnQueueList
+{
+public:
+ std::unordered_map< OUString, sal_Int32 > m_aNameToIndex;
+ std::vector< ImplPrnQueueData > m_aQueueInfos;
+ std::vector< OUString > m_aPrinterList;
+
+ ImplPrnQueueList() {}
+ ~ImplPrnQueueList();
+
+ ImplPrnQueueList& operator=( ImplPrnQueueList const & ) = delete; // MSVC2017 workaround
+ ImplPrnQueueList( ImplPrnQueueList const & ) = delete; // MSVC2017 workaround
+
+void Add( std::unique_ptr<SalPrinterQueueInfo> pData );
+ ImplPrnQueueData* Get( const OUString& rPrinter );
+};
+
+void ImplDeletePrnQueueList();
+void ImplUpdateJobSetupPaper( JobSetup& rJobSetup );
+
+#endif // INCLUDED_VCL_INC_PRINT_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/print.hrc b/vcl/inc/print.hrc
new file mode 100644
index 0000000000..661e881690
--- /dev/null
+++ b/vcl/inc/print.hrc
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_PRINT_HRC
+#define INCLUDED_VCL_INC_PRINT_HRC
+
+#include <unotools/resmgr.hxx>
+
+#define NC_(Context, String) TranslateId(Context, u8##String)
+
+const TranslateId RID_STR_PAPERNAMES[] =
+{
+ // To translators: This is the first entry of a sequence of paper size names
+
+ // This array must (probably) match exactly the enum Paper in <i18nutil/paper.hxx>
+
+ NC_("RID_STR_PAPERNAMES", "A0"),
+ NC_("RID_STR_PAPERNAMES", "A1"),
+ NC_("RID_STR_PAPERNAMES", "A2"),
+ NC_("RID_STR_PAPERNAMES", "A3"),
+ NC_("RID_STR_PAPERNAMES", "A4"),
+ NC_("RID_STR_PAPERNAMES", "A5"),
+ NC_("RID_STR_PAPERNAMES", "B4 (ISO)"),
+ NC_("RID_STR_PAPERNAMES", "B5 (ISO)"),
+ NC_("RID_STR_PAPERNAMES", "Letter"),
+ NC_("RID_STR_PAPERNAMES", "Legal"),
+ NC_("RID_STR_PAPERNAMES", "Tabloid"),
+ NC_("RID_STR_PAPERNAMES", "User Defined"),
+ NC_("RID_STR_PAPERNAMES", "B6 (ISO)"),
+ NC_("RID_STR_PAPERNAMES", "C4 Envelope"),
+ NC_("RID_STR_PAPERNAMES", "C5 Envelope"),
+ NC_("RID_STR_PAPERNAMES", "C6 Envelope"),
+ NC_("RID_STR_PAPERNAMES", "C6/5 Envelope"),
+ NC_("RID_STR_PAPERNAMES", "DL Envelope"),
+ NC_("RID_STR_PAPERNAMES", "Dia Slide"),
+ NC_("RID_STR_PAPERNAMES", "Screen 4:3"),
+ NC_("RID_STR_PAPERNAMES", "C"),
+ NC_("RID_STR_PAPERNAMES", "D"),
+ NC_("RID_STR_PAPERNAMES", "E"),
+ NC_("RID_STR_PAPERNAMES", "Executive"),
+ NC_("RID_STR_PAPERNAMES", "German Legal Fanfold"),
+ NC_("RID_STR_PAPERNAMES", "#8 (Monarch) Envelope"),
+ NC_("RID_STR_PAPERNAMES", "#6 3/4 (Personal) Envelope"),
+ NC_("RID_STR_PAPERNAMES", "#9 Envelope"),
+ NC_("RID_STR_PAPERNAMES", "#10 Envelope"),
+ NC_("RID_STR_PAPERNAMES", "#11 Envelope"),
+ NC_("RID_STR_PAPERNAMES", "#12 Envelope"),
+ NC_("RID_STR_PAPERNAMES", "16 Kai (16k)"),
+ NC_("RID_STR_PAPERNAMES", "32 Kai"),
+ NC_("RID_STR_PAPERNAMES", "Big 32 Kai"),
+ NC_("RID_STR_PAPERNAMES", "B4 (JIS)"),
+ NC_("RID_STR_PAPERNAMES", "B5 (JIS)"),
+ NC_("RID_STR_PAPERNAMES", "B6 (JIS)"),
+ NC_("RID_STR_PAPERNAMES", "Ledger"),
+ NC_("RID_STR_PAPERNAMES", "Statement"),
+ NC_("RID_STR_PAPERNAMES", "Quarto"),
+ NC_("RID_STR_PAPERNAMES", "10x14"),
+ NC_("RID_STR_PAPERNAMES", "#14 Envelope"),
+ NC_("RID_STR_PAPERNAMES", "C3 Envelope"),
+ NC_("RID_STR_PAPERNAMES", "Italian Envelope"),
+ NC_("RID_STR_PAPERNAMES", "U.S. Standard Fanfold"),
+ NC_("RID_STR_PAPERNAMES", "German Standard Fanfold"),
+ NC_("RID_STR_PAPERNAMES", "Japanese Postcard"),
+ NC_("RID_STR_PAPERNAMES", "9x11"),
+ NC_("RID_STR_PAPERNAMES", "10x11"),
+ NC_("RID_STR_PAPERNAMES", "15x11"),
+ NC_("RID_STR_PAPERNAMES", "Invitation Envelope"),
+ NC_("RID_STR_PAPERNAMES", "SuperA"),
+ NC_("RID_STR_PAPERNAMES", "SuperB"),
+ NC_("RID_STR_PAPERNAMES", "Letter Plus"),
+ NC_("RID_STR_PAPERNAMES", "A4 Plus"),
+ NC_("RID_STR_PAPERNAMES", "Double Postcard"),
+ NC_("RID_STR_PAPERNAMES", "A6"),
+ NC_("RID_STR_PAPERNAMES", "12x11"),
+ NC_("RID_STR_PAPERNAMES", "A7"),
+ NC_("RID_STR_PAPERNAMES", "A8"),
+ NC_("RID_STR_PAPERNAMES", "A9"),
+ NC_("RID_STR_PAPERNAMES", "A10"),
+ NC_("RID_STR_PAPERNAMES", "B0 (ISO)"),
+ NC_("RID_STR_PAPERNAMES", "B1 (ISO)"),
+ NC_("RID_STR_PAPERNAMES", "B2 (ISO)"),
+ NC_("RID_STR_PAPERNAMES", "B3 (ISO)"),
+ NC_("RID_STR_PAPERNAMES", "B7 (ISO)"),
+ NC_("RID_STR_PAPERNAMES", "B8 (ISO)"),
+ NC_("RID_STR_PAPERNAMES", "B9 (ISO)"),
+ NC_("RID_STR_PAPERNAMES", "B10 (ISO)"),
+ NC_("RID_STR_PAPERNAMES", "C2 Envelope"),
+ NC_("RID_STR_PAPERNAMES", "C7 Envelope"),
+ NC_("RID_STR_PAPERNAMES", "C8 Envelope"),
+ NC_("RID_STR_PAPERNAMES", "Arch A"),
+ NC_("RID_STR_PAPERNAMES", "Arch B"),
+ NC_("RID_STR_PAPERNAMES", "Arch C"),
+ NC_("RID_STR_PAPERNAMES", "Arch D"),
+ NC_("RID_STR_PAPERNAMES", "Arch E"),
+ NC_("RID_STR_PAPERNAMES", "Screen 16:9"),
+ NC_("RID_STR_PAPERNAMES", "Screen 16:10"),
+ NC_("RID_STR_PAPERNAMES", "16k (195 x 270)"),
+ NC_("RID_STR_PAPERNAMES", "16k (197 x 273)"),
+ NC_("RID_STR_PAPERNAMES", "Widescreen"),
+ NC_("RID_STR_PAPERNAMES", "On-screen Show (4:3)"),
+ NC_("RID_STR_PAPERNAMES", "On-screen Show (16:9)"),
+ // To translators: This is the last entry of the sequence of paper size names
+ NC_("RID_STR_PAPERNAMES", "On-screen Show (16:10)")
+};
+
+#endif // INCLUDED_VCL_INC_PRINT_HRC
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/printaccessoryview.hrc b/vcl/inc/printaccessoryview.hrc
new file mode 100644
index 0000000000..9d94654b4c
--- /dev/null
+++ b/vcl/inc/printaccessoryview.hrc
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_PRINTACCESSORYVIEW_HRC
+#define INCLUDED_VCL_INC_PRINTACCESSORYVIEW_HRC
+
+#include <unotools/resmgr.hxx>
+
+#define NC_(Context, String) TranslateId(Context, u8##String)
+
+const TranslateId SV_PRINT_NATIVE_STRINGS[] =
+{
+ NC_("SV_PRINT_NATIVE_STRINGS", "Preview"),
+ NC_("SV_PRINT_NATIVE_STRINGS", "Page number"),
+ NC_("SV_PRINT_NATIVE_STRINGS", "Number of pages"),
+ NC_("SV_PRINT_NATIVE_STRINGS", "More"),
+ NC_("SV_PRINT_NATIVE_STRINGS", "Print selection only")
+};
+
+#endif // INCLUDED_VCL_INC_PRINTACCESSORYVIEW_HRC
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/printdlg.hxx b/vcl/inc/printdlg.hxx
new file mode 100644
index 0000000000..bf058b0797
--- /dev/null
+++ b/vcl/inc/printdlg.hxx
@@ -0,0 +1,275 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 VCL_INC_NEWPRINTDLG_HXX
+#define VCL_INC_NEWPRINTDLG_HXX
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/print.hxx>
+#include <vcl/customweld.hxx>
+#include <vcl/weld.hxx>
+#include <map>
+
+namespace vcl {
+ class PrintDialog;
+}
+
+namespace vcl
+{
+ class PrintDialog final : public weld::GenericDialogController
+ {
+ friend class MoreOptionsDialog;
+ public:
+
+ class PrintPreviewWindow final : public weld::CustomWidgetController
+ {
+ PrintDialog* mpDialog;
+ GDIMetaFile maMtf;
+ Size maOrigSize;
+ Size maPreviewSize;
+ sal_Int32 mnDPIX;
+ sal_Int32 mnDPIY;
+ BitmapEx maPreviewBitmap;
+ OUString maReplacementString;
+ bool mbGreyscale;
+
+ OUString maHorzText;
+ OUString maVertText;
+
+ void preparePreviewBitmap();
+
+ public:
+ PrintPreviewWindow(PrintDialog* pDialog);
+ virtual ~PrintPreviewWindow() override;
+
+ virtual void Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) override;
+ virtual bool Command( const CommandEvent& ) override;
+ virtual void Resize() override;
+
+ void setPreview( const GDIMetaFile&, const Size& i_rPaperSize,
+ std::u16string_view i_rPaperName,
+ const OUString& i_rNoPageString,
+ sal_Int32 i_nDPIX, sal_Int32 i_nDPIY,
+ bool i_bGreyscale
+ );
+ };
+
+ class ShowNupOrderWindow final : public weld::CustomWidgetController
+ {
+ NupOrderType mnOrderMode;
+ int mnRows;
+ int mnColumns;
+ public:
+ ShowNupOrderWindow();
+
+ virtual void SetDrawingArea(weld::DrawingArea* pDrawingArea) override;
+
+ virtual void Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& ) override;
+
+ void setValues( NupOrderType i_nOrderMode, int i_nColumns, int i_nRows )
+ {
+ mnOrderMode = i_nOrderMode;
+ mnRows = i_nRows;
+ mnColumns = i_nColumns;
+ Invalidate();
+ }
+ };
+
+ PrintDialog(weld::Window*, std::shared_ptr<PrinterController> );
+ virtual ~PrintDialog() override;
+
+ bool isPrintToFile() const;
+ bool isCollate() const;
+ bool isSingleJobs() const;
+ bool hasPreview() const;
+
+ void setPaperSizes();
+ void previewForward();
+ void previewBackward();
+ void previewFirst();
+ void previewLast();
+
+ private:
+
+ std::unique_ptr<weld::Builder> mxCustomOptionsUIBuilder;
+
+ std::shared_ptr<PrinterController> maPController;
+
+ std::unique_ptr<weld::Notebook> mxTabCtrl;
+ std::unique_ptr<weld::ScrolledWindow> mxScrolledWindow;
+ std::unique_ptr<weld::Frame> mxPageLayoutFrame;
+ std::unique_ptr<weld::ComboBox> mxPrinters;
+ std::unique_ptr<weld::Label> mxStatusTxt;
+ std::unique_ptr<weld::Button> mxSetupButton;
+
+ std::unique_ptr<weld::SpinButton> mxCopyCountField;
+ std::unique_ptr<weld::CheckButton> mxCollateBox;
+ std::unique_ptr<weld::Image> mxCollateImage;
+ std::unique_ptr<weld::Entry> mxPageRangeEdit;
+ std::unique_ptr<weld::RadioButton> mxPageRangesRadioButton;
+ std::unique_ptr<weld::ComboBox> mxPaperSidesBox;
+ std::unique_ptr<weld::CheckButton> mxSingleJobsBox;
+ std::unique_ptr<weld::CheckButton> mxReverseOrderBox;
+
+ std::unique_ptr<weld::Button> mxOKButton;
+ std::unique_ptr<weld::Button> mxCancelButton;
+
+ std::unique_ptr<weld::Button> mxBackwardBtn;
+ std::unique_ptr<weld::Button> mxForwardBtn;
+ std::unique_ptr<weld::Button> mxFirstBtn;
+ std::unique_ptr<weld::Button> mxLastBtn;
+
+ std::unique_ptr<weld::CheckButton> mxPreviewBox;
+ std::unique_ptr<weld::Label> mxNumPagesText;
+ std::unique_ptr<PrintPreviewWindow> mxPreview;
+ std::unique_ptr<weld::CustomWeld> mxPreviewWindow;
+ std::unique_ptr<weld::Entry> mxPageEdit;
+
+ std::unique_ptr<weld::RadioButton> mxPagesBtn;
+ std::unique_ptr<weld::RadioButton> mxBrochureBtn;
+ std::unique_ptr<weld::Label> mxPagesBoxTitleTxt;
+ std::unique_ptr<weld::ComboBox> mxNupPagesBox;
+
+ // controls for "Custom" page mode
+ std::unique_ptr<weld::Label> mxNupNumPagesTxt;
+ std::unique_ptr<weld::SpinButton> mxNupColEdt;
+ std::unique_ptr<weld::Label> mxNupTimesTxt;
+ std::unique_ptr<weld::SpinButton> mxNupRowsEdt;
+ std::unique_ptr<weld::Label> mxPageMarginTxt1;
+ std::unique_ptr<weld::MetricSpinButton> mxPageMarginEdt;
+ std::unique_ptr<weld::Label> mxPageMarginTxt2;
+ std::unique_ptr<weld::Label> mxSheetMarginTxt1;
+ std::unique_ptr<weld::MetricSpinButton> mxSheetMarginEdt;
+ std::unique_ptr<weld::Label> mxSheetMarginTxt2;
+ std::unique_ptr<weld::ComboBox> mxPaperSizeBox;
+ std::unique_ptr<weld::ComboBox> mxOrientationBox;
+
+ // page order ("left to right, then down")
+ std::unique_ptr<weld::Label> mxNupOrderTxt;
+ std::unique_ptr<weld::ComboBox> mxNupOrderBox;
+ std::unique_ptr<ShowNupOrderWindow> mxNupOrder;
+ std::unique_ptr<weld::CustomWeld> mxNupOrderWin;
+ /// border around each page
+ std::unique_ptr<weld::CheckButton> mxBorderCB;
+ std::unique_ptr<weld::Expander> mxRangeExpander;
+ std::unique_ptr<weld::Expander> mxLayoutExpander;
+ std::unique_ptr<weld::Widget> mxCustom;
+
+ OUString maPrintToFileText;
+ OUString maPrintText;
+ OUString maDefPrtText;
+
+ OUString maPageStr;
+ OUString maNoPageStr;
+ OUString maNoPreviewStr;
+ sal_Int32 mnCurPage;
+ sal_Int32 mnCachedPages;
+
+ bool mbCollateAlwaysOff;
+
+ std::vector<std::unique_ptr<weld::Widget>>
+ maExtraControls;
+
+ std::map<weld::Widget*, OUString>
+ maControlToPropertyMap;
+ std::map<OUString, std::vector<weld::Widget*>>
+ maPropertyToWindowMap;
+ std::map<weld::Widget*, sal_Int32>
+ maControlToNumValMap;
+
+ Size maNupPortraitSize;
+ Size maNupLandscapeSize;
+ /// internal, used for automatic Nup-Portrait/landscape
+ Size maFirstPageSize;
+
+ bool mbShowLayoutFrame;
+
+ Paper mePaper;
+
+ Idle maUpdatePreviewIdle;
+ DECL_LINK(updatePreviewIdle, Timer*, void);
+ Idle maUpdatePreviewNoCacheIdle;
+ DECL_LINK(updatePreviewNoCacheIdle, Timer*, void);
+
+ DECL_LINK( ClickHdl, weld::Button&, void );
+ DECL_LINK( SelectHdl, weld::ComboBox&, void );
+ DECL_LINK( ActivateHdl, weld::Entry&, bool );
+ DECL_LINK( FocusOutHdl, weld::Widget&, void );
+ DECL_LINK( SpinModifyHdl, weld::SpinButton&, void );
+ DECL_LINK( MetricSpinModifyHdl, weld::MetricSpinButton&, void );
+ DECL_LINK( ToggleHdl, weld::Toggleable&, void );
+
+ DECL_LINK( UIOption_CheckHdl, weld::Toggleable&, void );
+ DECL_LINK( UIOption_RadioHdl, weld::Toggleable&, void );
+ DECL_LINK( UIOption_SelectHdl, weld::ComboBox&, void );
+ DECL_LINK( UIOption_SpinModifyHdl, weld::SpinButton&, void );
+ DECL_LINK( UIOption_EntryModifyHdl, weld::Entry&, void );
+
+ css::beans::PropertyValue* getValueForWindow(weld::Widget*) const;
+
+ void preparePreview( bool i_bMayUseCache );
+ void setupPaperSidesBox();
+ void storeToSettings();
+ void readFromSettings();
+ void setPaperOrientation( Orientation eOrientation, bool fromUser );
+ void updateOrientationBox( bool bAutomatic = true );
+ bool hasOrientationChanged() const;
+ void setPreviewText();
+ void updatePrinterText();
+ void checkControlDependencies();
+ void checkOptionalControlDependencies();
+ void makeEnabled( weld::Widget* );
+ void updateWindowFromProperty( const OUString& );
+ void initFromMultiPageSetup( const vcl::PrinterController::MultiPageSetup& );
+ void showAdvancedControls( bool );
+ void updateNup( bool i_bMayUseCache = true );
+ void updateNupFromPages( bool i_bMayUseCache = true );
+ void enableNupControls( bool bEnable );
+ void setupOptionalUI();
+ Size const & getJobPageSize();
+
+ };
+
+ class PrintProgressDialog final : public weld::GenericDialogController
+ {
+ OUString maStr;
+ bool mbCanceled;
+ sal_Int32 mnCur;
+ sal_Int32 mnMax;
+
+ std::unique_ptr<weld::Label> mxText;
+ std::unique_ptr<weld::ProgressBar> mxProgress;
+ std::unique_ptr<weld::Button> mxButton;
+
+ DECL_LINK( ClickHdl, weld::Button&, void );
+
+ public:
+ PrintProgressDialog(weld::Window* i_pParent, int i_nMax);
+ virtual ~PrintProgressDialog() override;
+ bool isCanceled() const { return mbCanceled; }
+ void setProgress( int i_nCurrent );
+ void tick();
+ };
+}
+
+#endif // VCL_INC_NEWPRINTDLG_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/printerinfomanager.hxx b/vcl/inc/printerinfomanager.hxx
new file mode 100644
index 0000000000..67dd8e59b2
--- /dev/null
+++ b/vcl/inc/printerinfomanager.hxx
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_PRINTERINFOMANAGER_HXX
+#define INCLUDED_VCL_PRINTERINFOMANAGER_HXX
+
+#include <memory>
+#include <vector>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <vcl/dllapi.h>
+#include <vcl/prntypes.hxx>
+#include <osl/time.h>
+
+#include <cstdio>
+
+#include "jobdata.hxx"
+
+namespace psp
+{
+
+class SystemQueueInfo;
+
+struct PrinterInfo : JobData
+{
+ // basename of PPD
+ OUString m_aDriverName;
+ // can be the queue
+ OUString m_aLocation;
+ // a user defined comment
+ OUString m_aComment;
+ // a command line to pipe a PS-file to
+ OUString m_aCommand;
+ // a command line to pipe a PS-file to in case of direct print
+ OUString m_aQuickCommand;
+ // a list of special features separated by ',' not used by psprint
+ // but assigned from the outside (currently for "fax","pdf=","autoqueue","external_dialog")
+ OUString m_aFeatures;
+ // auth-info-required, potential [domain],[username],[password] to prompt for to authenticate printing
+ OUString m_aAuthInfoRequired;
+ PrinterSetupMode meSetupMode;
+
+ PrinterInfo()
+ : JobData()
+ , meSetupMode(PrinterSetupMode::SingleJob)
+ {}
+};
+
+class VCL_DLLPUBLIC PrinterInfoManager
+{
+public:
+ enum class Type { Default = 0, CUPS = 1, CPD = 2 };
+
+ struct SystemPrintQueue
+ {
+ OUString m_aQueue;
+ OUString m_aLocation;
+ OUString m_aComment;
+ };
+protected:
+ // needed for checkPrintersChanged: files (not necessarily existent)
+ // and their last known modification time
+ struct WatchFile
+ {
+ // the file in question
+ OUString m_aFilePath;
+ // the last know modification time or 0, if file did not exist
+ TimeValue m_aModified;
+ };
+
+ // internal data to describe a printer
+ struct Printer
+ {
+ // configuration file containing this printer
+ // empty means a freshly added printer that has to be saved yet
+ OUString m_aFile;
+ // details other config files that have this printer
+ // in case of removal all have to be removed
+ std::unordered_set< OUString > m_aAlternateFiles;
+ // the corresponding info and job data
+ PrinterInfo m_aInfo;
+ };
+
+ std::unordered_map< OUString, Printer > m_aPrinters;
+ PrinterInfo m_aGlobalDefaults;
+ std::vector< WatchFile > m_aWatchFiles;
+ OUString m_aDefaultPrinter;
+ OUString m_aSystemPrintCommand;
+
+ std::vector< SystemPrintQueue > m_aSystemPrintQueues;
+
+ std::unique_ptr<SystemQueueInfo>
+ m_pQueueInfo;
+
+ Type m_eType;
+ OUString m_aSystemDefaultPaper;
+
+ PrinterInfoManager( Type eType = Type::Default );
+
+ virtual void initialize();
+
+ // fill default paper if not configured in config file
+ // default paper is e.g. locale dependent
+ // if a paper is already set it will not be overwritten
+ void setDefaultPaper( PPDContext& rInfo ) const;
+
+public:
+
+ // there can only be one
+ static PrinterInfoManager& get();
+
+ // get PrinterInfoManager type
+ Type getType() const { return m_eType; }
+
+ // lists the names of all known printers
+ void listPrinters( std::vector< OUString >& rVector ) const;
+
+ // gets info about a named printer
+ const PrinterInfo& getPrinterInfo( const OUString& rPrinter ) const;
+
+ // gets the name of the default printer
+ const OUString& getDefaultPrinter() const { return m_aDefaultPrinter; }
+
+ virtual void setupJobContextData( JobData& rData );
+
+ // check if the printer configuration has changed
+ // if bwait is true, then this method waits for eventual asynchronous
+ // printer discovery to finish
+ virtual bool checkPrintersChanged( bool bWait );
+
+ // abstract print command
+ // returns a stdio FILE* that a postscript file may be written to
+ // this may either be a regular file or the result of popen()
+ virtual FILE* startSpool( const OUString& rPrinterName, bool bQuickCommand );
+ // close the FILE* returned by startSpool and does the actual spooling
+ // set bBanner to "false" will attempt to suppress banner printing
+ // set bBanner to "true" will rely on the system default
+ // returns true on success
+ virtual bool endSpool( const OUString& rPrinterName, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString &rFaxNumber );
+
+ // check whether a printer's feature string contains a subfeature
+ bool checkFeatureToken( const OUString& rPrinterName, std::string_view pToken ) const;
+
+ virtual ~PrinterInfoManager();
+};
+
+} // namespace
+
+#endif // INCLUDED_VCL_PRINTERINFOMANAGER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtAccessibleEventListener.hxx b/vcl/inc/qt5/QtAccessibleEventListener.hxx
new file mode 100644
index 0000000000..f6c7c4866e
--- /dev/null
+++ b/vcl/inc/qt5/QtAccessibleEventListener.hxx
@@ -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/.
+ */
+
+#pragma once
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+#include <com/sun/star/accessibility/XAccessibleEventListener.hpp>
+#include <com/sun/star/lang/EventObject.hpp>
+
+#include "QtAccessibleWidget.hxx"
+
+#include <cppuhelper/implbase.hxx>
+
+class QtAccessibleEventListener final
+ : public cppu::WeakImplHelper<css::accessibility::XAccessibleEventListener>
+{
+public:
+ explicit QtAccessibleEventListener(QtAccessibleWidget* pAccessibleWidget);
+
+ virtual void SAL_CALL
+ notifyEvent(const css::accessibility::AccessibleEventObject& aEvent) override;
+
+ virtual void SAL_CALL disposing(const css::lang::EventObject& Source) override;
+
+private:
+ QtAccessibleWidget* m_pAccessibleWidget;
+
+ static void HandleStateChangedEvent(QAccessibleInterface* pQAccessibleInterface,
+ const css::accessibility::AccessibleEventObject& rEvent);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtAccessibleRegistry.hxx b/vcl/inc/qt5/QtAccessibleRegistry.hxx
new file mode 100644
index 0000000000..87781752f7
--- /dev/null
+++ b/vcl/inc/qt5/QtAccessibleRegistry.hxx
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <map>
+
+#include <QtCore/QObject>
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+
+using namespace css::accessibility;
+
+/**
+ * Maintains a mapping between XAccessible objects and the
+ * associated QObjects. The corresponding QObject can be
+ * passed to the QAccessible::queryAccessibleInterface method in
+ * order to retrieve the QAccessibleInterface for the
+ * XAccessible object.
+ */
+class QtAccessibleRegistry
+{
+private:
+ static std::map<css::accessibility::XAccessible*, QObject*> m_aMapping;
+ QtAccessibleRegistry() = delete;
+
+public:
+ /** Returns the related QObject* for the XAccessible. Creates a new one if none exists yet. */
+ static QObject* getQObject(css::uno::Reference<XAccessible> xAcc);
+ static void insert(css::uno::Reference<XAccessible> xAcc, QObject* pQObject);
+ /** Removes the entry for the given XAccessible. */
+ static void remove(css::uno::Reference<XAccessible> xAcc);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtAccessibleWidget.hxx b/vcl/inc/qt5/QtAccessibleWidget.hxx
new file mode 100644
index 0000000000..8d71ecd0ea
--- /dev/null
+++ b/vcl/inc/qt5/QtAccessibleWidget.hxx
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vclpluginapi.h>
+
+#include <QtCore/QObject>
+#include <QtCore/QPair>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtCore/QVector>
+#include <QtGui/QAccessible>
+#include <QtGui/QAccessibleActionInterface>
+#include <QtGui/QAccessibleInterface>
+#include <QtGui/QAccessibleTableCellInterface>
+#include <QtGui/QAccessibleTableInterface>
+#include <QtGui/QAccessibleTextInterface>
+#include <QtGui/QAccessibleValueInterface>
+#include <QtGui/QColor>
+#include <QtGui/QWindow>
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+
+namespace com::sun::star::accessibility
+{
+class XAccessibleTable;
+}
+
+class QtFrame;
+class QtWidget;
+
+class QtAccessibleWidget final : public QAccessibleInterface,
+ public QAccessibleActionInterface,
+ public QAccessibleTextInterface,
+ public QAccessibleEditableTextInterface,
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+ public QAccessibleSelectionInterface,
+#endif
+ public QAccessibleTableCellInterface,
+ public QAccessibleTableInterface,
+ public QAccessibleValueInterface
+{
+public:
+ QtAccessibleWidget(const css::uno::Reference<css::accessibility::XAccessible> xAccessible,
+ QObject* pObject);
+
+ void invalidate();
+
+ // QAccessibleInterface
+ QWindow* window() const override;
+ int childCount() const override;
+ int indexOfChild(const QAccessibleInterface* child) const override;
+ QVector<QPair<QAccessibleInterface*, QAccessible::Relation>>
+ relations(QAccessible::Relation match = QAccessible::AllRelations) const override;
+ QAccessibleInterface* focusChild() const override;
+
+ QRect rect() const override;
+
+ QAccessibleInterface* parent() const override;
+ QAccessibleInterface* child(int index) const override;
+
+ QString text(QAccessible::Text t) const override;
+ QAccessible::Role role() const override;
+ QAccessible::State state() const override;
+
+ QColor foregroundColor() const override;
+ QColor backgroundColor() const override;
+
+ bool isValid() const override;
+ QObject* object() const override;
+ void setText(QAccessible::Text t, const QString& text) override;
+ QAccessibleInterface* childAt(int x, int y) const override;
+
+ void* interface_cast(QAccessible::InterfaceType t) override;
+
+ // QAccessibleActionInterface
+ QStringList actionNames() const override;
+ void doAction(const QString& actionName) override;
+ QStringList keyBindingsForAction(const QString& actionName) const override;
+
+ // QAccessibleTextInterface
+ void addSelection(int startOffset, int endOffset) override;
+ QString attributes(int offset, int* startOffset, int* endOffset) const override;
+ int characterCount() const override;
+ QRect characterRect(int offset) const override;
+ int cursorPosition() const override;
+ int offsetAtPoint(const QPoint& point) const override;
+ void removeSelection(int selectionIndex) override;
+ void scrollToSubstring(int startIndex, int endIndex) override;
+ void selection(int selectionIndex, int* startOffset, int* endOffset) const override;
+ int selectionCount() const override;
+ void setCursorPosition(int position) override;
+ void setSelection(int selectionIndex, int startOffset, int endOffset) override;
+ QString text(int startOffset, int endOffset) const override;
+ QString textAfterOffset(int offset, QAccessible::TextBoundaryType boundaryType,
+ int* startOffset, int* endOffset) const override;
+ QString textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType, int* startOffset,
+ int* endOffset) const override;
+ QString textBeforeOffset(int offset, QAccessible::TextBoundaryType boundaryType,
+ int* startOffset, int* endOffset) const override;
+
+ // QAccessibleEditableTextInterface
+ virtual void deleteText(int startOffset, int endOffset) override;
+ virtual void insertText(int offset, const QString& text) override;
+ virtual void replaceText(int startOffset, int endOffset, const QString& text) override;
+
+ // QAccessibleValueInterface
+ QVariant currentValue() const override;
+ QVariant maximumValue() const override;
+ QVariant minimumStepSize() const override;
+ QVariant minimumValue() const override;
+ void setCurrentValue(const QVariant& value) override;
+
+ // QAccessibleTableInterface
+ virtual QAccessibleInterface* caption() const override;
+ virtual QAccessibleInterface* cellAt(int row, int column) const override;
+ virtual int columnCount() const override;
+ virtual QString columnDescription(int column) const override;
+ virtual bool isColumnSelected(int column) const override;
+ virtual bool isRowSelected(int row) const override;
+ virtual void modelChange(QAccessibleTableModelChangeEvent* event) override;
+ virtual int rowCount() const override;
+ virtual QString rowDescription(int row) const override;
+ virtual bool selectColumn(int column) override;
+ virtual bool selectRow(int row) override;
+ virtual int selectedCellCount() const override;
+ virtual QList<QAccessibleInterface*> selectedCells() const override;
+ virtual int selectedColumnCount() const override;
+ virtual QList<int> selectedColumns() const override;
+ virtual int selectedRowCount() const override;
+ virtual QList<int> selectedRows() const override;
+ virtual QAccessibleInterface* summary() const override;
+ virtual bool unselectColumn(int column) override;
+ virtual bool unselectRow(int row) override;
+
+ // QAccessibleTableCellInterface
+ virtual QList<QAccessibleInterface*> columnHeaderCells() const override;
+ virtual int columnIndex() const override;
+ virtual bool isSelected() const override;
+ virtual int columnExtent() const override;
+ virtual QList<QAccessibleInterface*> rowHeaderCells() const override;
+ virtual int rowExtent() const override;
+ virtual int rowIndex() const override;
+ virtual QAccessibleInterface* table() const override;
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+ // QAccessibleSelectionInterface
+ virtual int selectedItemCount() const override;
+ virtual QList<QAccessibleInterface*> selectedItems() const override;
+ virtual QAccessibleInterface* selectedItem(int selectionIndex) const override;
+ virtual bool isSelected(QAccessibleInterface* item) const override;
+ virtual bool select(QAccessibleInterface* item) override;
+ virtual bool unselect(QAccessibleInterface* item) override;
+ virtual bool selectAll() override;
+ virtual bool clear() override;
+#else
+ // no override, but used in QAccessibleTableInterface methods
+ int selectedItemCount() const;
+ QList<QAccessibleInterface*> selectedItems() const;
+#endif
+
+ // Factory
+ static QAccessibleInterface* customFactory(const QString& classname, QObject* object);
+
+private:
+ css::uno::Reference<css::accessibility::XAccessible> m_xAccessible;
+ css::uno::Reference<css::accessibility::XAccessibleContext> getAccessibleContextImpl() const;
+ css::uno::Reference<css::accessibility::XAccessibleTable> getAccessibleTableForParent() const;
+
+ template <class Interface> bool accessibleProvidesInterface() const
+ {
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext
+ = getAccessibleContextImpl();
+ css::uno::Reference<Interface> xInterface(xContext, css::uno::UNO_QUERY);
+ return xInterface.is();
+ }
+
+ QObject* m_pObject;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtBitmap.hxx b/vcl/inc/qt5/QtBitmap.hxx
new file mode 100644
index 0000000000..a15deab294
--- /dev/null
+++ b/vcl/inc/qt5/QtBitmap.hxx
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <salbmp.hxx>
+
+#include <memory>
+
+class QImage;
+
+class QtBitmap final : public SalBitmap
+{
+ std::unique_ptr<QImage> m_pImage;
+ BitmapPalette m_aPalette;
+
+public:
+ QtBitmap();
+ QtBitmap(const QImage& rQImage);
+
+ const QImage* GetQImage() const { return m_pImage.get(); }
+
+ virtual bool Create(const Size& rSize, vcl::PixelFormat ePixelFormat,
+ const BitmapPalette& rPal) override;
+ virtual bool Create(const SalBitmap& rSalBmp) override;
+ virtual bool Create(const SalBitmap& rSalBmp, SalGraphics* pGraphics) override;
+ virtual bool Create(const SalBitmap& rSalBmp, vcl::PixelFormat eNewPixelFormat) override;
+ virtual bool Create(const css::uno::Reference<css::rendering::XBitmapCanvas>& rBitmapCanvas,
+ Size& rSize, bool bMask = false) override;
+ virtual void Destroy() final override;
+ virtual Size GetSize() const override;
+ virtual sal_uInt16 GetBitCount() const override;
+
+ virtual BitmapBuffer* AcquireBuffer(BitmapAccessMode nMode) override;
+ virtual void ReleaseBuffer(BitmapBuffer* pBuffer, BitmapAccessMode nMode) override;
+ virtual bool GetSystemData(BitmapSystemData& rData) override;
+
+ virtual bool ScalingSupported() const override;
+ virtual bool Scale(const double& rScaleX, const double& rScaleY,
+ BmpScaleFlag nScaleFlag) override;
+ virtual bool Replace(const Color& rSearchColor, const Color& rReplaceColor,
+ sal_uInt8 nTol) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtClipboard.hxx b/vcl/inc/qt5/QtClipboard.hxx
new file mode 100644
index 0000000000..f07414bfbc
--- /dev/null
+++ b/vcl/inc/qt5/QtClipboard.hxx
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
+#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardOwner.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
+#include <cppuhelper/compbase.hxx>
+
+#include <QtGui/QClipboard>
+
+/**
+ * This implementation has two main functions, which handle the clipboard content:
+ * the XClipboard::setContent function and the QClipboard::change signal handler.
+ *
+ * The first just sets the respective clipboard to the expected content from LO,
+ * the latter will handle any reported changes.
+ **/
+class QtClipboard final
+ : public QObject,
+ public cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard,
+ css::datatransfer::clipboard::XFlushableClipboard,
+ css::lang::XServiceInfo>
+{
+ Q_OBJECT
+
+ osl::Mutex m_aMutex;
+ const OUString m_aClipboardName;
+ const QClipboard::Mode m_aClipboardMode;
+ // has to be set, if LO changes the QClipboard itself, so it won't instantly lose
+ // ownership by it's self-triggered QClipboard::changed handler
+ bool m_bOwnClipboardChange;
+ // true, if LO really wants to give up clipboard ownership
+ bool m_bDoClear;
+
+ // if not empty, this holds the setContents provided XTransferable or a QtClipboardTransferable
+ css::uno::Reference<css::datatransfer::XTransferable> m_aContents;
+ // the owner of the current contents, which must be informed on content change
+ css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> m_aOwner;
+ std::vector<css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>> m_aListeners;
+
+ static bool isOwner(const QClipboard::Mode aMode);
+ static bool isSupported(const QClipboard::Mode aMode);
+
+ explicit QtClipboard(OUString aModeString, const QClipboard::Mode aMode);
+
+private Q_SLOTS:
+ void handleChanged(QClipboard::Mode mode);
+ void handleClearClipboard();
+
+signals:
+ void clearClipboard();
+
+public:
+ // factory function to construct only valid QtClipboard objects by name
+ static css::uno::Reference<css::uno::XInterface> create(const OUString& aModeString);
+
+ // 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;
+
+ // XClipboard
+ virtual css::uno::Reference<css::datatransfer::XTransferable> SAL_CALL getContents() override;
+ virtual void SAL_CALL setContents(
+ const css::uno::Reference<css::datatransfer::XTransferable>& xTrans,
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& xClipboardOwner)
+ override;
+ virtual OUString SAL_CALL getName() override;
+
+ // XClipboardEx
+ virtual sal_Int8 SAL_CALL getRenderingCapabilities() override;
+
+ // XFlushableClipboard
+ virtual void SAL_CALL flushClipboard() override;
+
+ // XClipboardNotifier
+ virtual void SAL_CALL addClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+ override;
+ virtual void SAL_CALL removeClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+ override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtData.hxx b/vcl/inc/qt5/QtData.hxx
new file mode 100644
index 0000000000..82cfecd57e
--- /dev/null
+++ b/vcl/inc/qt5/QtData.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 <unx/gendata.hxx>
+
+#include <o3tl/enumarray.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <memory>
+#include <vclpluginapi.h>
+
+class QCursor;
+
+class VCLPLUG_QT_PUBLIC QtData final : public GenericUnixSalData
+{
+ o3tl::enumarray<PointerStyle, std::unique_ptr<QCursor>> m_aCursors;
+
+public:
+ explicit QtData();
+ virtual ~QtData() override;
+
+ virtual void ErrorTrapPush() override;
+ virtual bool ErrorTrapPop(bool bIgnoreError = true) override;
+
+ QCursor& getCursor(PointerStyle ePointerStyle);
+
+ static bool noNativeControls();
+};
+
+inline QtData* GetQtData() { return static_cast<QtData*>(GetSalData()); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtDragAndDrop.hxx b/vcl/inc/qt5/QtDragAndDrop.hxx
new file mode 100644
index 0000000000..0ca1ebfb83
--- /dev/null
+++ b/vcl/inc/qt5/QtDragAndDrop.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/.
+ *
+ */
+
+#pragma once
+
+#include <com/sun/star/datatransfer/dnd/XDragSource.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <cppuhelper/compbase.hxx>
+
+class QtFrame;
+
+class QtDragSource final
+ : public cppu::WeakComponentImplHelper<css::datatransfer::dnd::XDragSource,
+ css::lang::XInitialization, css::lang::XServiceInfo>
+{
+ osl::Mutex m_aMutex;
+ QtFrame* m_pFrame;
+ css::uno::Reference<css::datatransfer::dnd::XDragSourceListener> m_xListener;
+
+public:
+ QtDragSource()
+ : WeakComponentImplHelper(m_aMutex)
+ , m_pFrame(nullptr)
+ {
+ }
+
+ virtual ~QtDragSource() override;
+
+ // XDragSource
+ virtual sal_Bool SAL_CALL isDragImageSupported() override;
+ virtual sal_Int32 SAL_CALL getDefaultCursor(sal_Int8 dragAction) override;
+ virtual void SAL_CALL startDrag(
+ const css::datatransfer::dnd::DragGestureEvent& trigger, sal_Int8 sourceActions,
+ sal_Int32 cursor, sal_Int32 image,
+ const css::uno::Reference<css::datatransfer::XTransferable>& transferable,
+ const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& listener) override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize(const css::uno::Sequence<css::uno::Any>& rArguments) override;
+ void deinitialize();
+
+ OUString SAL_CALL getImplementationName() override;
+
+ sal_Bool SAL_CALL supportsService(OUString const& ServiceName) override;
+
+ css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+ void fire_dragEnd(sal_Int8 nAction, bool bSuccessful);
+};
+
+class QtDropTarget final
+ : public cppu::WeakComponentImplHelper<css::datatransfer::dnd::XDropTarget,
+ css::datatransfer::dnd::XDropTargetDragContext,
+ css::datatransfer::dnd::XDropTargetDropContext,
+ css::lang::XInitialization, css::lang::XServiceInfo>
+{
+ osl::Mutex m_aMutex;
+ QtFrame* m_pFrame;
+ sal_Int8 m_nDropAction;
+ bool m_bActive;
+ sal_Int8 m_nDefaultActions;
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> m_aListeners;
+ bool m_bDropSuccessful;
+
+public:
+ QtDropTarget();
+ virtual ~QtDropTarget() override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize(const css::uno::Sequence<css::uno::Any>& rArgs) override;
+ void deinitialize();
+
+ // XDropTarget
+ virtual void SAL_CALL addDropTargetListener(
+ const css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>&) override;
+ virtual void SAL_CALL removeDropTargetListener(
+ const css::uno::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;
+
+ // XDropTargetDragContext
+ virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) override;
+ virtual void SAL_CALL rejectDrag() override;
+
+ // XDropTargetDropContext
+ virtual void SAL_CALL acceptDrop(sal_Int8 dropOperation) override;
+ virtual void SAL_CALL rejectDrop() override;
+ virtual void SAL_CALL dropComplete(sal_Bool success) override;
+
+ // XServiceInfo
+ OUString SAL_CALL getImplementationName() override;
+ sal_Bool SAL_CALL supportsService(OUString const& ServiceName) override;
+ css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+ void fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde);
+ void fire_dragExit(const css::datatransfer::dnd::DropTargetEvent& dte);
+ void fire_dragOver(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde);
+ void fire_drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde);
+
+ sal_Int8 proposedDropAction() const { return m_nDropAction; }
+ bool dropSuccessful() const { return m_bDropSuccessful; }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtFilePicker.hxx b/vcl/inc/qt5/QtFilePicker.hxx
new file mode 100644
index 0000000000..58824adbbd
--- /dev/null
+++ b/vcl/inc/qt5/QtFilePicker.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <vclpluginapi.h>
+
+#include <cppuhelper/compbase.hxx>
+
+#include <com/sun/star/frame/XTerminateListener.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/ui/dialogs/XAsynchronousExecutableDialog.hpp>
+#include <com/sun/star/ui/dialogs/XFilePicker3.hpp>
+#include <com/sun/star/ui/dialogs/XFilePickerControlAccess.hpp>
+#include <com/sun/star/ui/dialogs/XFolderPicker2.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <osl/conditn.hxx>
+#include <osl/mutex.hxx>
+#include <unotools/resmgr.hxx>
+
+#include <QtCore/QObject>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtCore/QHash>
+#include <QtWidgets/QFileDialog>
+
+#include <memory>
+
+class QComboBox;
+class QGridLayout;
+class QLabel;
+class QWidget;
+
+typedef ::cppu::WeakComponentImplHelper<
+ css::frame::XTerminateListener, css::lang::XInitialization, css::lang::XServiceInfo,
+ css::ui::dialogs::XFilePicker3, css::ui::dialogs::XFilePickerControlAccess,
+ css::ui::dialogs::XAsynchronousExecutableDialog, css::ui::dialogs::XFolderPicker2>
+ QtFilePicker_Base;
+
+class VCLPLUG_QT_PUBLIC QtFilePicker : public QObject, public QtFilePicker_Base
+{
+ Q_OBJECT
+
+private:
+ css::uno::Reference<css::uno::XComponentContext> m_context;
+
+ css::uno::Reference<css::ui::dialogs::XFilePickerListener> m_xListener;
+ css::uno::Reference<css::ui::dialogs::XDialogClosedListener> m_xClosedListener;
+
+ osl::Mutex m_aHelperMutex; ///< mutex used by the WeakComponentImplHelper
+
+ QStringList m_aNamedFilterList; ///< to keep the original sequence
+ QHash<QString, QString> m_aTitleToFilterMap;
+ // to retrieve the filename extension for a given filter
+ QHash<QString, QString> m_aNamedFilterToExtensionMap;
+ QString m_aCurrentFilter;
+
+ QGridLayout* m_pLayout; ///< layout for extra custom controls
+ QHash<sal_Int16, QWidget*> m_aCustomWidgetsMap; ///< map of SAL control ID's to widget
+
+ const bool m_bIsFolderPicker;
+
+ QWidget* m_pParentWidget;
+
+protected:
+ std::unique_ptr<QFileDialog> m_pFileDialog; ///< the file picker dialog
+ QWidget* m_pExtraControls; ///< widget to contain extra custom controls
+
+public:
+ // use non-native file dialog by default; there's no easy way to add custom widgets
+ // in a generic way in the native one
+ explicit QtFilePicker(css::uno::Reference<css::uno::XComponentContext> context,
+ QFileDialog::FileMode, bool bUseNative = false);
+ virtual ~QtFilePicker() override;
+
+ // XFilePickerNotifier
+ virtual void SAL_CALL addFilePickerListener(
+ const css::uno::Reference<css::ui::dialogs::XFilePickerListener>& xListener) override;
+ virtual void SAL_CALL removeFilePickerListener(
+ const css::uno::Reference<css::ui::dialogs::XFilePickerListener>& xListener) override;
+
+ // XFilterManager functions
+ virtual void SAL_CALL appendFilter(const OUString& rTitle, const OUString& rFilter) override;
+ virtual void SAL_CALL setCurrentFilter(const OUString& rTitle) override;
+ virtual OUString SAL_CALL getCurrentFilter() override;
+
+ // XFilterGroupManager functions
+ virtual void SAL_CALL
+ appendFilterGroup(const OUString& rGroupTitle,
+ const css::uno::Sequence<css::beans::StringPair>& rFilters) override;
+
+ // XCancellable
+ virtual void SAL_CALL cancel() override;
+
+ // XExecutableDialog functions
+ virtual void SAL_CALL setTitle(const OUString& rTitle) override;
+ virtual sal_Int16 SAL_CALL execute() override;
+
+ // XAsynchronousExecutableDialog functions
+ virtual void SAL_CALL setDialogTitle(const OUString&) override;
+ virtual void SAL_CALL
+ startExecuteModal(const css::uno::Reference<css::ui::dialogs::XDialogClosedListener>&) override;
+
+ // XFilePicker functions
+ virtual void SAL_CALL setMultiSelectionMode(sal_Bool bMode) override;
+ virtual void SAL_CALL setDefaultName(const OUString& rName) override;
+ virtual void SAL_CALL setDisplayDirectory(const OUString& rDirectory) override;
+ virtual OUString SAL_CALL getDisplayDirectory() override;
+ virtual css::uno::Sequence<OUString> SAL_CALL getFiles() override;
+
+ // XFilePickerControlAccess functions
+ virtual void SAL_CALL setValue(sal_Int16 nControlId, sal_Int16 nControlAction,
+ const css::uno::Any& rValue) override;
+ virtual css::uno::Any SAL_CALL getValue(sal_Int16 nControlId,
+ sal_Int16 nControlAction) override;
+ virtual void SAL_CALL enableControl(sal_Int16 nControlId, sal_Bool bEnable) override;
+ virtual void SAL_CALL setLabel(sal_Int16 nControlId, const OUString& rLabel) override;
+ virtual OUString SAL_CALL getLabel(sal_Int16 nControlId) override;
+
+ // XFilePicker2 functions
+ virtual css::uno::Sequence<OUString> SAL_CALL getSelectedFiles() override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize(const css::uno::Sequence<css::uno::Any>& rArguments) override;
+
+ // XEventListener
+ void SAL_CALL disposing(const css::lang::EventObject& rEvent) override;
+ using cppu::WeakComponentImplHelperBase::disposing;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override;
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+ // XFolderPicker functions
+ virtual OUString SAL_CALL getDirectory() override;
+ virtual void SAL_CALL setDescription(const OUString& rDescription) override;
+
+ // XTerminateListener
+ void SAL_CALL queryTermination(const css::lang::EventObject& aEvent) override;
+ void SAL_CALL notifyTermination(const css::lang::EventObject& aEvent) override;
+
+protected:
+ virtual void addCustomControl(sal_Int16 controlId);
+ void setCustomControlWidgetLayout(QGridLayout* pLayout) { m_pLayout = pLayout; }
+
+private:
+ QtFilePicker(const QtFilePicker&) = delete;
+ QtFilePicker& operator=(const QtFilePicker&) = delete;
+
+ static QString getResString(TranslateId pRedId);
+ static css::uno::Any handleGetListValue(const QComboBox* pWidget, sal_Int16 nControlAction);
+ static void handleSetListValue(QComboBox* pQComboBox, sal_Int16 nAction,
+ const css::uno::Any& rValue);
+
+ void prepareExecute();
+
+private Q_SLOTS:
+ // emit XFilePickerListener controlStateChanged event
+ void filterSelected(const QString&);
+ // emit XFilePickerListener fileSelectionChanged event
+ void currentChanged(const QString&);
+ // (un)set automatic file extension
+ virtual void updateAutomaticFileExtension();
+ void finished(int);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtFont.hxx b/vcl/inc/qt5/QtFont.hxx
new file mode 100644
index 0000000000..7d0b338c93
--- /dev/null
+++ b/vcl/inc/qt5/QtFont.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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <font/LogicalFontInstance.hxx>
+
+#include <QtGui/QFont>
+
+#include "QtFontFace.hxx"
+
+class QtFont final : public QFont, public LogicalFontInstance
+{
+ friend rtl::Reference<LogicalFontInstance>
+ QtFontFace::CreateFontInstance(const vcl::font::FontSelectPattern&) const;
+
+ bool GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const override;
+
+ explicit QtFont(const vcl::font::PhysicalFontFace&, const vcl::font::FontSelectPattern&);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtFontFace.hxx b/vcl/inc/qt5/QtFontFace.hxx
new file mode 100644
index 0000000000..06260468cb
--- /dev/null
+++ b/vcl/inc/qt5/QtFontFace.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <vclpluginapi.h>
+#include <font/PhysicalFontFace.hxx>
+
+#include <tools/ref.hxx>
+
+#include <QtCore/QString>
+#include <QtGui/QFont>
+
+class FontAttributes;
+namespace vcl::font
+{
+class FontSelectPattern;
+}
+
+class QtFontFace final : public vcl::font::PhysicalFontFace
+{
+public:
+ static QtFontFace* fromQFont(const QFont& rFont);
+ static QtFontFace* fromQFontDatabase(const QString& aFamily, const QString& aStyle);
+ static void fillAttributesFromQFont(const QFont& rFont, FontAttributes& rFA);
+
+ VCLPLUG_QT_PUBLIC static FontWeight toFontWeight(const int nWeight);
+ VCLPLUG_QT_PUBLIC static FontWidth toFontWidth(const int nStretch);
+ VCLPLUG_QT_PUBLIC static FontItalic toFontItalic(const QFont::Style eStyle);
+
+ sal_IntPtr GetFontId() const override;
+
+ QFont CreateFont() const;
+
+ rtl::Reference<LogicalFontInstance>
+ CreateFontInstance(const vcl::font::FontSelectPattern& rFSD) const override;
+
+ hb_blob_t* GetHbTable(hb_tag_t nTag) const override;
+
+private:
+ typedef enum { Font, FontDB } FontIdType;
+
+ QtFontFace(const QtFontFace&);
+ QtFontFace(const FontAttributes&, QString rFontID, const FontIdType);
+
+ const QString m_aFontId;
+ const FontIdType m_eFontIdType;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtFrame.hxx b/vcl/inc/qt5/QtFrame.hxx
new file mode 100644
index 0000000000..b80818687c
--- /dev/null
+++ b/vcl/inc/qt5/QtFrame.hxx
@@ -0,0 +1,237 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <config_vclplug.h>
+
+#include <salframe.hxx>
+#include <vclpluginapi.h>
+
+#include "QtTools.hxx"
+#include "QtWidget.hxx"
+
+#include <headless/svpgdi.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <QtCore/QObject>
+
+#if CHECK_ANY_QT_USING_X11
+#include <unx/sessioninhibitor.hxx>
+// any better way to get rid of the X11 / Qt type clashes?
+#undef Bool
+#undef CursorShape
+#undef Expose
+#undef KeyPress
+#undef KeyRelease
+#undef FocusIn
+#undef FocusOut
+#undef FontChange
+#undef None
+#undef Status
+#undef Unsorted
+#endif
+
+class QtDragSource;
+class QtDropTarget;
+class QtGraphics;
+class QtInstance;
+class QtMainWindow;
+class QtMenu;
+class QtSvpGraphics;
+
+class QDragMoveEvent;
+class QDropEvent;
+class QImage;
+class QMimeData;
+class QPaintDevice;
+class QScreen;
+class QWidget;
+
+class VCLPLUG_QT_PUBLIC QtFrame : public QObject, public SalFrame
+{
+ Q_OBJECT
+
+ friend class QtWidget;
+
+ QtWidget* m_pQWidget;
+ QtMainWindow* m_pTopLevel;
+
+ const bool m_bUseCairo;
+ std::unique_ptr<QImage> m_pQImage;
+ std::unique_ptr<QtGraphics> m_pQtGraphics;
+ UniqueCairoSurface m_pSurface;
+ std::unique_ptr<QtSvpGraphics> m_pSvpGraphics;
+ DamageHandler m_aDamageHandler;
+ QRegion m_aRegion;
+ bool m_bNullRegion;
+
+ bool m_bGraphicsInUse;
+ SalFrameStyleFlags m_nStyle;
+ QtFrame* m_pParent;
+ PointerStyle m_ePointerStyle;
+
+ SystemEnvData m_aSystemData;
+
+ QtDragSource* m_pDragSource;
+ QtDropTarget* m_pDropTarget;
+ bool m_bInDrag;
+
+ bool m_bDefaultSize;
+ bool m_bDefaultPos;
+ bool m_bFullScreen;
+ bool m_bFullScreenSpanAll;
+ sal_uInt32 m_nRestoreScreen;
+ QRect m_aRestoreGeometry;
+
+#if CHECK_ANY_QT_USING_X11
+ SessionManagerInhibitor m_SessionManagerInhibitor;
+ ModKeyFlags m_nKeyModifiers;
+#endif
+
+ LanguageType m_nInputLanguage;
+
+ OUString m_aTooltipText;
+ QRect m_aTooltipArea;
+
+ void SetDefaultPos();
+ Size CalcDefaultSize();
+ void SetDefaultSize();
+
+ bool isChild(bool bPlug = true, bool bSysChild = true) const
+ {
+ SalFrameStyleFlags nMask = SalFrameStyleFlags::NONE;
+ if (bPlug)
+ nMask |= SalFrameStyleFlags::PLUG;
+ if (bSysChild)
+ nMask |= SalFrameStyleFlags::SYSTEMCHILD;
+ return bool(m_nStyle & nMask);
+ }
+
+ bool isWindow() const;
+ QWindow* windowHandle() const;
+ QScreen* screen() const;
+ bool isMinimized() const;
+ bool isMaximized() const;
+ void SetWindowStateImpl(Qt::WindowStates eState);
+
+private Q_SLOTS:
+ void screenChanged(QScreen*);
+
+public:
+ QtFrame(QtFrame* pParent, SalFrameStyleFlags nSalFrameStyle, bool bUseCairo);
+ virtual ~QtFrame() override;
+
+ QWidget* GetQWidget() const { return m_pQWidget; }
+ QtMainWindow* GetTopLevelWindow() const { return m_pTopLevel; }
+ QWidget* asChild() const;
+ qreal devicePixelRatioF() const;
+ int menuBarOffset() const;
+
+ void Damage(sal_Int32 nExtentsX, sal_Int32 nExtentsY, sal_Int32 nExtentsWidth,
+ sal_Int32 nExtentsHeight) const;
+
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics(SalGraphics* pGraphics) override;
+
+ virtual bool PostEvent(std::unique_ptr<ImplSVEvent> pData) override;
+
+ virtual void SetTitle(const OUString& rTitle) override;
+ virtual void SetIcon(sal_uInt16 nIcon) override;
+ virtual void SetMenu(SalMenu* pMenu) override;
+
+ virtual void registerDragSource(QtDragSource* pDragSource);
+ virtual void deregisterDragSource(QtDragSource const* pDragSource);
+ virtual void registerDropTarget(QtDropTarget* pDropTarget);
+ virtual void deregisterDropTarget(QtDropTarget const* pDropTarget);
+
+ void handleDragLeave();
+ void handleDragMove(QDragMoveEvent* pEvent);
+ void handleDrop(QDropEvent* pEvent);
+
+ virtual void SetExtendedFrameStyle(SalExtStyle nExtStyle) override;
+ virtual void Show(bool bVisible, bool bNoActivate = false) override;
+ virtual void SetMinClientSize(tools::Long nWidth, tools::Long nHeight) override;
+ virtual void SetMaxClientSize(tools::Long nWidth, tools::Long nHeight) override;
+ virtual void SetPosSize(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ sal_uInt16 nFlags) override;
+ virtual void GetClientSize(tools::Long& rWidth, tools::Long& rHeight) override;
+ virtual void GetWorkArea(AbsoluteScreenPixelRectangle& rRect) override;
+ virtual SalFrame* GetParent() const override;
+ virtual void SetModal(bool bModal) override;
+ virtual bool GetModal() const override;
+ virtual void SetWindowState(const vcl::WindowData*) override;
+ virtual bool GetWindowState(vcl::WindowData*) override;
+ virtual void ShowFullScreen(bool bFullScreen, sal_Int32 nDisplay) override;
+ virtual void StartPresentation(bool bStart) override;
+ virtual void SetAlwaysOnTop(bool bOnTop) override;
+ virtual void ToTop(SalFrameToTop nFlags) override;
+ virtual void SetPointer(PointerStyle ePointerStyle) override;
+ virtual void CaptureMouse(bool bMouse) override;
+ virtual void SetPointerPos(tools::Long nX, tools::Long nY) override;
+ virtual bool ShowTooltip(const OUString& rText, const tools::Rectangle& rHelpArea) override;
+ using SalFrame::Flush;
+ virtual void Flush() override;
+ virtual void SetInputContext(SalInputContext* pContext) override;
+ virtual void EndExtTextInput(EndExtTextInputFlags nFlags) override;
+ virtual OUString GetKeyName(sal_uInt16 nKeyCode) override;
+ virtual bool MapUnicodeToKeyCode(sal_Unicode aUnicode, LanguageType aLangType,
+ vcl::KeyCode& rKeyCode) override;
+ virtual LanguageType GetInputLanguage() override;
+ virtual void UpdateSettings(AllSettings& rSettings) override;
+ virtual void Beep() override;
+ virtual const SystemEnvData* GetSystemData() const override { return &m_aSystemData; }
+ virtual SalPointerState GetPointerState() override;
+ virtual KeyIndicatorState GetIndicatorState() override;
+ virtual void SimulateKeyPress(sal_uInt16 nKeyCode) override;
+ virtual void SetParent(SalFrame* pNewParent) override;
+ virtual void SetPluginParent(SystemParentData* pNewParent) override;
+ virtual void ResetClipRegion() override;
+ virtual void BeginSetClipRegion(sal_uInt32 nRects) override;
+ virtual void UnionClipRegion(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight) override;
+ virtual void EndSetClipRegion() override;
+
+ virtual void SetScreenNumber(unsigned int) override;
+ virtual void SetApplicationID(const OUString&) override;
+ virtual void ResolveWindowHandle(SystemEnvData& rData) const override;
+ virtual bool GetUseDarkMode() const override;
+ virtual bool GetUseReducedAnimation() const override;
+
+ inline bool CallCallback(SalEvent nEvent, const void* pEvent) const;
+
+ void setInputLanguage(LanguageType);
+ inline bool isPopup() const;
+ static void FillSystemEnvData(SystemEnvData&, sal_IntPtr pWindow, QWidget* pWidget);
+};
+
+inline bool QtFrame::CallCallback(SalEvent nEvent, const void* pEvent) const
+{
+ SolarMutexGuard aGuard;
+ return SalFrame::CallCallback(nEvent, pEvent);
+}
+
+inline bool QtFrame::isPopup() const
+{
+ return ((m_nStyle & SalFrameStyleFlags::FLOAT)
+ && !(m_nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtGraphics.hxx b/vcl/inc/qt5/QtGraphics.hxx
new file mode 100644
index 0000000000..fce2b0d9b5
--- /dev/null
+++ b/vcl/inc/qt5/QtGraphics.hxx
@@ -0,0 +1,240 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <salgdi.hxx>
+
+#include <memory>
+#include <optional>
+
+#include <QtGui/QPainter>
+#include <QtGui/QPainterPath>
+#include <QtGui/QRegion>
+
+#include "QtGraphicsBase.hxx"
+
+namespace vcl::font
+{
+class PhysicalFontCollection;
+}
+class QImage;
+class QPushButton;
+class QtFont;
+class QtFontFace;
+class QtFrame;
+class QtPainter;
+
+class QtGraphicsBackend final : public SalGraphicsImpl, public QtGraphicsBase
+{
+ friend class QtPainter;
+
+ QtFrame* m_pFrame;
+ QImage* m_pQImage;
+ QRegion m_aClipRegion;
+ QPainterPath m_aClipPath;
+ std::optional<Color> m_oLineColor;
+ std::optional<Color> m_oFillColor;
+ QPainter::CompositionMode m_eCompositionMode;
+
+public:
+ QtGraphicsBackend(QtFrame* pFrame, QImage* pQImage);
+ ~QtGraphicsBackend() override;
+
+ void Init() override {}
+
+ QImage* getQImage() { return m_pQImage; }
+
+ void setQImage(QImage* pQImage) { m_pQImage = pQImage; }
+
+ void freeResources() override {}
+
+ OUString getRenderBackendName() const override { return "qt5"; }
+
+ void setClipRegion(vcl::Region const& rRegion) override;
+ void ResetClipRegion() override;
+
+ sal_uInt16 GetBitCount() const override;
+
+ tools::Long GetGraphicsWidth() const override;
+
+ void SetLineColor() override;
+ void SetLineColor(Color nColor) override;
+ void SetFillColor() override;
+ void SetFillColor(Color nColor) override;
+ void SetXORMode(bool bSet, bool bInvertOnly) override;
+ void SetROPLineColor(SalROPColor nROPColor) override;
+ void SetROPFillColor(SalROPColor nROPColor) override;
+
+ void drawPixel(tools::Long nX, tools::Long nY) override;
+ void drawPixel(tools::Long nX, tools::Long nY, Color nColor) override;
+
+ void drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2) override;
+ void drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight) override;
+ void drawPolyLine(sal_uInt32 nPoints, const Point* pPointArray) override;
+ void drawPolygon(sal_uInt32 nPoints, const Point* pPointArray) override;
+ void drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point** pPointArray) override;
+
+ void drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon&, double fTransparency) override;
+
+ bool drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice, const basegfx::B2DPolygon&,
+ double fTransparency, double fLineWidth, const std::vector<double>* pStroke,
+ basegfx::B2DLineJoin, css::drawing::LineCap, double fMiterMinimumAngle,
+ bool bPixelSnapHairline) override;
+
+ bool drawPolyLineBezier(sal_uInt32 nPoints, const Point* pPointArray,
+ const PolyFlags* pFlagArray) override;
+
+ bool drawPolygonBezier(sal_uInt32 nPoints, const Point* pPointArray,
+ const PolyFlags* pFlagArray) override;
+
+ bool drawPolyPolygonBezier(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point* const* pPointArray,
+ const PolyFlags* const* pFlagArray) override;
+
+ void copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight, bool bWindowInvalidate) override;
+
+ void copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics) override;
+
+ void drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap) override;
+
+ void drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ const SalBitmap& rMaskBitmap) override;
+
+ void drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ Color nMaskColor) override;
+
+ std::shared_ptr<SalBitmap> getBitmap(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight) override;
+
+ Color getPixel(tools::Long nX, tools::Long nY) override;
+
+ void invert(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ SalInvert nFlags) override;
+
+ void invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags) override;
+
+ bool drawEPS(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ void* pPtr, sal_uInt32 nSize) override;
+
+ bool blendBitmap(const SalTwoRect&, const SalBitmap& rBitmap) override;
+
+ bool blendAlphaBitmap(const SalTwoRect&, const SalBitmap& rSrcBitmap,
+ const SalBitmap& rMaskBitmap, const SalBitmap& rAlphaBitmap) override;
+
+ bool drawAlphaBitmap(const SalTwoRect&, const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap) override;
+
+ bool drawTransformedBitmap(const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY, const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap, double fAlpha) override;
+
+ bool hasFastDrawTransformedBitmap() const override;
+
+ bool drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ sal_uInt8 nTransparency) override;
+
+ bool drawGradient(const tools::PolyPolygon& rPolygon, const Gradient& rGradient) override;
+ bool implDrawGradient(basegfx::B2DPolyPolygon const& rPolyPolygon,
+ SalGradient const& rGradient) override;
+
+ bool supportsOperation(OutDevSupportType eType) const override;
+
+private:
+ void drawScaledImage(const SalTwoRect& rPosAry, const QImage& rImage);
+};
+
+class QtGraphics final : public SalGraphicsAutoDelegateToImpl, public QtGraphicsBase
+{
+ friend class QtBitmap;
+
+ std::unique_ptr<QtGraphicsBackend> m_pBackend;
+
+ QtFrame* m_pFrame;
+
+ rtl::Reference<QtFont> m_pTextStyle[MAX_FALLBACK];
+ Color m_aTextColor;
+
+ QtGraphics(QtFrame* pFrame, QImage* pQImage);
+
+ void drawScaledImage(const SalTwoRect& rPosAry, const QImage& rImage);
+
+ void handleDamage(const tools::Rectangle&) override;
+
+public:
+ QtGraphics(QtFrame* pFrame)
+ : QtGraphics(pFrame, nullptr)
+ {
+ }
+ QtGraphics(QImage* pQImage)
+ : QtGraphics(nullptr, pQImage)
+ {
+ }
+ virtual ~QtGraphics() override;
+
+ QImage* getQImage() { return m_pBackend->getQImage(); }
+
+ void ChangeQImage(QImage* pImage);
+
+ virtual SalGraphicsImpl* GetImpl() const override;
+ virtual SystemGraphicsData GetGraphicsData() const override;
+ virtual OUString getRenderBackendName() const override
+ {
+ return m_pBackend->getRenderBackendName();
+ }
+
+#if ENABLE_CAIRO_CANVAS
+ virtual bool SupportsCairo() const override;
+ virtual cairo::SurfaceSharedPtr
+ CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const override;
+ virtual cairo::SurfaceSharedPtr CreateSurface(const OutputDevice& rRefDevice, int x, int y,
+ int width, int height) const override;
+ virtual cairo::SurfaceSharedPtr CreateBitmapSurface(const OutputDevice& rRefDevice,
+ const BitmapSystemData& rData,
+ const Size& rSize) const override;
+ virtual css::uno::Any GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& rSurface,
+ const basegfx::B2ISize& rSize) const override;
+#endif // ENABLE_CAIRO_CANVAS
+
+ // GDI
+
+ virtual void GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY) override;
+
+ // Text rendering + font support
+
+ virtual void SetTextColor(Color nColor) override;
+ virtual void SetFont(LogicalFontInstance*, int nFallbackLevel) override;
+ virtual void GetFontMetric(FontMetricDataRef&, int nFallbackLevel) override;
+ virtual FontCharMapRef GetFontCharMap() const override;
+ virtual bool GetFontCapabilities(vcl::FontCapabilities& rFontCapabilities) const override;
+ virtual void GetDevFontList(vcl::font::PhysicalFontCollection*) override;
+ virtual void ClearDevFontCache() override;
+ virtual bool AddTempDevFont(vcl::font::PhysicalFontCollection*, const OUString& rFileURL,
+ const OUString& rFontName) override;
+
+ virtual std::unique_ptr<GenericSalLayout> GetTextLayout(int nFallbackLevel) override;
+ virtual void DrawTextLayout(const GenericSalLayout&) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtGraphicsBase.hxx b/vcl/inc/qt5/QtGraphicsBase.hxx
new file mode 100644
index 0000000000..73c39fb5ba
--- /dev/null
+++ b/vcl/inc/qt5/QtGraphicsBase.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/.
+ */
+
+#pragma once
+
+#include <QtWidgets/QApplication>
+
+class QtGraphicsBase
+{
+ qreal m_fDPR;
+
+public:
+ QtGraphicsBase()
+ : m_fDPR(qApp ? qApp->devicePixelRatio() : 1.0)
+ {
+ }
+
+ void setDevicePixelRatioF(qreal fDPR) { m_fDPR = fDPR; }
+
+ qreal devicePixelRatioF() const { return m_fDPR; }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtGraphics_Controls.hxx b/vcl/inc/qt5/QtGraphics_Controls.hxx
new file mode 100644
index 0000000000..dd1865e340
--- /dev/null
+++ b/vcl/inc/qt5/QtGraphics_Controls.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 .
+ */
+
+#pragma once
+
+#include <vclpluginapi.h>
+#include <WidgetDrawInterface.hxx>
+
+#include <memory>
+
+#include <QtGui/QImage>
+#include <QtGui/QPainter>
+#include <QtGui/QRegion>
+#include <QtWidgets/QPushButton>
+#include <QtWidgets/QStyle>
+#include <QtWidgets/QStyleOption>
+
+class QtGraphicsBase;
+
+class QtGraphics_Controls final : public vcl::WidgetDrawInterface
+{
+ std::unique_ptr<QImage> m_image;
+ QRect m_lastPopupRect;
+ QtGraphicsBase const& m_rGraphics;
+
+public:
+ QtGraphics_Controls(const QtGraphicsBase& rGraphics);
+
+ QImage* getImage() { return m_image.get(); }
+
+ bool isNativeControlSupported(ControlType nType, ControlPart nPart) override;
+ bool hitTestNativeControl(ControlType nType, ControlPart nPart,
+ const tools::Rectangle& rControlRegion, const Point& aPos,
+ bool& rIsInside) override;
+ bool drawNativeControl(ControlType nType, ControlPart nPart,
+ const tools::Rectangle& rControlRegion, ControlState nState,
+ const ImplControlValue& aValue, const OUString& aCaption,
+ const Color& rBackgroundColor) override;
+ bool getNativeControlRegion(ControlType nType, ControlPart nPart,
+ const tools::Rectangle& rControlRegion, ControlState nState,
+ const ImplControlValue& aValue, const OUString& aCaption,
+ tools::Rectangle& rNativeBoundingRegion,
+ tools::Rectangle& rNativeContentRegion) override;
+
+private:
+ static int pixelMetric(QStyle::PixelMetric metric, const QStyleOption* option = nullptr,
+ const QWidget* pWidget = nullptr);
+ static QSize sizeFromContents(QStyle::ContentsType type, const QStyleOption* option,
+ const QSize& contentsSize);
+ static QRect subControlRect(QStyle::ComplexControl control, const QStyleOptionComplex* option,
+ QStyle::SubControl subControl);
+ static QRect subElementRect(QStyle::SubElement element, const QStyleOption* option);
+
+ void draw(QStyle::ControlElement element, QStyleOption& rOption, QImage* image,
+ const Color& rBackgroundColor, QStyle::State const state = QStyle::State_None,
+ QRect rect = QRect());
+ void draw(QStyle::PrimitiveElement element, QStyleOption& rOption, QImage* image,
+ const Color& rBackgroundColor, QStyle::State const state = QStyle::State_None,
+ QRect rect = QRect());
+ void draw(QStyle::ComplexControl element, QStyleOptionComplex& rOption, QImage* image,
+ const Color& rBackgroundColor, QStyle::State const state = QStyle::State_None);
+ void drawFrame(QStyle::PrimitiveElement element, QImage* image, const Color& rBackGroundColor,
+ QStyle::State const& state, bool bClip = true,
+ QStyle::PixelMetric eLineMetric = QStyle::PM_DefaultFrameWidth);
+
+ static void fillQStyleOptionTab(const ImplControlValue& value, QStyleOptionTab& sot);
+ void fullQStyleOptionTabWidgetFrame(QStyleOptionTabWidgetFrame& option, bool bDownscale);
+
+ enum class Round
+ {
+ Floor,
+ Ceil,
+ };
+
+ int downscale(int value, Round eRound);
+ int upscale(int value, Round eRound);
+ QRect downscale(const QRect& rect);
+ QRect upscale(const QRect& rect);
+ QSize downscale(const QSize& size, Round eRound);
+ QSize upscale(const QSize& size, Round eRound);
+ QPoint upscale(const QPoint& point, Round eRound);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtInstance.hxx b/vcl/inc/qt5/QtInstance.hxx
new file mode 100644
index 0000000000..2073c8ac05
--- /dev/null
+++ b/vcl/inc/qt5/QtInstance.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <vclpluginapi.h>
+#include <unx/geninst.h>
+#include <salusereventlist.hxx>
+#include <vcl/timer.hxx>
+
+#include <osl/conditn.hxx>
+
+#include <QtCore/QObject>
+
+#include <cstdlib>
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include "QtFilePicker.hxx"
+
+class QtFrame;
+class QtTimer;
+
+class QApplication;
+class SalYieldMutex;
+class SalFrame;
+
+struct StdFreeCStr
+{
+ void operator()(char* arg) const noexcept { std::free(arg); }
+};
+using FreeableCStr = std::unique_ptr<char[], StdFreeCStr>;
+
+class VCLPLUG_QT_PUBLIC QtInstance : public QObject,
+ public SalGenericInstance,
+ public SalUserEventList
+{
+ Q_OBJECT
+
+ osl::Condition m_aWaitingYieldCond;
+ const bool m_bUseCairo;
+ QtTimer* m_pTimer;
+ bool m_bSleeping;
+ std::unordered_map<OUString, css::uno::Reference<css::uno::XInterface>> m_aClipboards;
+
+ std::unique_ptr<QApplication> m_pQApplication;
+ std::vector<FreeableCStr> m_pFakeArgvFreeable;
+ std::unique_ptr<char* []> m_pFakeArgv;
+ std::unique_ptr<int> m_pFakeArgc;
+
+ Timer m_aUpdateStyleTimer;
+ bool m_bUpdateFonts;
+
+ QtFrame* m_pActivePopup;
+
+ DECL_DLLPRIVATE_LINK(updateStyleHdl, Timer*, void);
+ void AfterAppInit() override;
+
+private Q_SLOTS:
+ bool ImplYield(bool bWait, bool bHandleAllCurrentEvents);
+ static void deleteObjectLater(QObject* pObject);
+ static void localeChanged();
+
+ void orientationChanged(Qt::ScreenOrientation);
+ void primaryScreenChanged(QScreen*);
+ void screenAdded(QScreen*);
+ void screenRemoved(QScreen*);
+ void virtualGeometryChanged(const QRect&);
+
+Q_SIGNALS:
+ bool ImplYieldSignal(bool bWait, bool bHandleAllCurrentEvents);
+ void deleteObjectLaterSignal(QObject* pObject);
+
+protected:
+ virtual rtl::Reference<QtFilePicker>
+ createPicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode);
+ bool useCairo() const { return m_bUseCairo; }
+ // encodes cairo usage and Qt platform name into the ToolkitName
+ OUString constructToolkitID(std::u16string_view sTKname);
+ void connectQScreenSignals(const QScreen*);
+ void notifyDisplayChanged();
+
+public:
+ explicit QtInstance(std::unique_ptr<QApplication>& pQApp);
+ virtual ~QtInstance() override;
+
+ // handle common SalInstance setup
+ static void AllocFakeCmdlineArgs(std::unique_ptr<char* []>& rFakeArgv,
+ std::unique_ptr<int>& rFakeArgc,
+ std::vector<FreeableCStr>& rFakeArgvFreeable);
+ void MoveFakeCmdlineArgs(std::unique_ptr<char* []>& rFakeArgv, std::unique_ptr<int>& rFakeArgc,
+ std::vector<FreeableCStr>& rFakeArgvFreeable);
+ static std::unique_ptr<QApplication> CreateQApplication(int& nArgc, char** pArgv);
+
+ void RunInMainThread(std::function<void()> func);
+
+ virtual SalFrame* CreateFrame(SalFrame* pParent, SalFrameStyleFlags nStyle) override;
+ virtual SalFrame* CreateChildFrame(SystemParentData* pParent,
+ SalFrameStyleFlags nStyle) override;
+ virtual void DestroyFrame(SalFrame* pFrame) override;
+
+ virtual SalObject* CreateObject(SalFrame* pParent, SystemWindowData* pWindowData,
+ bool bShow) override;
+ virtual void DestroyObject(SalObject* pObject) override;
+
+ virtual std::unique_ptr<SalVirtualDevice>
+ CreateVirtualDevice(SalGraphics& rGraphics, tools::Long& nDX, tools::Long& nDY,
+ DeviceFormat eFormat, const SystemGraphicsData* pData = nullptr) override;
+
+ virtual SalInfoPrinter* CreateInfoPrinter(SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pSetupData) override;
+ virtual void DestroyInfoPrinter(SalInfoPrinter* pPrinter) override;
+ virtual std::unique_ptr<SalPrinter> CreatePrinter(SalInfoPrinter* pInfoPrinter) override;
+ virtual void GetPrinterQueueInfo(ImplPrnQueueList* pList) override;
+ virtual void GetPrinterQueueState(SalPrinterQueueInfo* pInfo) override;
+ virtual OUString GetDefaultPrinter() override;
+ virtual void PostPrintersChanged() override;
+
+ virtual std::unique_ptr<SalMenu> CreateMenu(bool, Menu*) override;
+ virtual std::unique_ptr<SalMenuItem> CreateMenuItem(const SalItemParams&) override;
+
+ virtual SalTimer* CreateSalTimer() override;
+ virtual SalSystem* CreateSalSystem() override;
+ virtual std::shared_ptr<SalBitmap> CreateSalBitmap() override;
+
+ virtual bool DoYield(bool bWait, bool bHandleAllCurrentEvents) override;
+ virtual bool AnyInput(VclInputFlags nType) override;
+
+// so we fall back to the default abort, instead of duplicating it...
+#ifndef EMSCRIPTEN
+ virtual OpenGLContext* CreateOpenGLContext() override;
+#endif
+
+ virtual OUString GetConnectionIdentifier() override;
+
+ virtual void AddToRecentDocumentList(const OUString& rFileUrl, const OUString& rMimeType,
+ const OUString& rDocumentService) override;
+
+ virtual std::unique_ptr<GenPspGraphics> CreatePrintGraphics() override;
+
+ virtual bool IsMainThread() const override;
+
+ virtual void TriggerUserEventProcessing() override;
+ virtual void ProcessEvent(SalUserEvent aEvent) override;
+
+ bool hasNativeFileSelection() const override { return true; }
+ css::uno::Reference<css::ui::dialogs::XFilePicker2>
+ createFilePicker(const css::uno::Reference<css::uno::XComponentContext>&) override;
+ css::uno::Reference<css::ui::dialogs::XFolderPicker2>
+ createFolderPicker(const css::uno::Reference<css::uno::XComponentContext>&) override;
+
+ virtual css::uno::Reference<css::uno::XInterface>
+ CreateClipboard(const css::uno::Sequence<css::uno::Any>& i_rArguments) override;
+ virtual css::uno::Reference<css::uno::XInterface>
+ ImplCreateDragSource(const SystemEnvData*) override;
+ virtual css::uno::Reference<css::uno::XInterface>
+ ImplCreateDropTarget(const SystemEnvData*) override;
+
+ // whether to reduce animations; KFSalInstance overrides this to read Plasma settings
+ virtual bool GetUseReducedAnimation() { return false; }
+ void UpdateStyle(bool bFontsChanged);
+
+ void* CreateGStreamerSink(const SystemChildWindow*) override;
+
+ bool DoExecute(int& nExitCode) override;
+ void DoQuit() override;
+
+ QtFrame* activePopup() const { return m_pActivePopup; }
+ void setActivePopup(QtFrame*);
+};
+
+inline QtInstance* GetQtInstance() { return static_cast<QtInstance*>(GetSalInstance()); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtMainWindow.hxx b/vcl/inc/qt5/QtMainWindow.hxx
new file mode 100644
index 0000000000..29621310b7
--- /dev/null
+++ b/vcl/inc/qt5/QtMainWindow.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 .
+ */
+
+#pragma once
+
+#include <QtWidgets/QWidget>
+#include <QtWidgets/QMainWindow>
+
+#include "QtFrame.hxx"
+
+class QtMainWindow final : public QMainWindow
+{
+ Q_OBJECT
+
+ QtFrame& m_rFrame;
+
+ virtual void closeEvent(QCloseEvent* pEvent) override;
+ void moveEvent(QMoveEvent*) override;
+
+public:
+ QtMainWindow(QtFrame& rFrame, Qt::WindowFlags f = Qt::WindowFlags());
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtMenu.hxx b/vcl/inc/qt5/QtMenu.hxx
new file mode 100644
index 0000000000..587e1cfea8
--- /dev/null
+++ b/vcl/inc/qt5/QtMenu.hxx
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <salmenu.hxx>
+
+#include <QtCore/QObject>
+
+#include <memory>
+
+class MenuItemList;
+class QAbstractButton;
+class QAction;
+class QActionGroup;
+class QButtonGroup;
+class QMenu;
+class QMenuBar;
+class QPushButton;
+class QtMenuItem;
+class QtFrame;
+
+/*
+ * QtMenu can represent
+ * (1) the top-level menu of a menubar, in which case 'mbMenuBar' is true and
+ * 'mpQMenuBar' refers to the corresponding QMenuBar
+ * (2) another kind of menu (like a PopupMenu), in which case the corresponding QMenu
+ * object is instantiated and owned by this QtMenu (held in 'mpOwnedQMenu').
+ * (3) a "submenu" in an existing menu (like (1)), in which case the corresponding
+ * QMenu object is owned by the corresponding QtMenuItem.
+ *
+ * For (2) and (3), member 'mpQMenu' points to the corresponding QMenu object.
+ */
+class QtMenu : public QObject, public SalMenu
+{
+ Q_OBJECT
+private:
+ std::vector<QtMenuItem*> maItems;
+ VclPtr<Menu> mpVCLMenu;
+ QtMenu* mpParentSalMenu;
+ QtFrame* mpFrame;
+ bool mbMenuBar;
+ QMenuBar* mpQMenuBar;
+ // self-created QMenu that this QtMenu represents, if applicable (s. comment for class)
+ std::unique_ptr<QMenu> mpOwnedQMenu;
+ // pointer to QMenu owned by the corresponding QtMenuItem or self (-> mpOwnedQMenu)
+ QMenu* mpQMenu;
+ QButtonGroup* m_pButtonGroup;
+
+ // help ID of currently/last selected item
+ static OUString m_sCurrentHelpId;
+
+ void DoFullMenuUpdate(Menu* pMenuBar);
+ static void NativeItemText(OUString& rItemText);
+
+ void InsertMenuItem(QtMenuItem* pSalMenuItem, unsigned nPos);
+
+ void ReinitializeActionGroup(unsigned nPos);
+ void ResetAllActionGroups();
+ void UpdateActionGroupItem(const QtMenuItem* pSalMenuItem);
+ bool validateQMenuBar() const;
+ QPushButton* ImplAddMenuBarButton(const QIcon& rIcon, const QString& rToolTip, int nId);
+ void ImplRemoveMenuBarButton(int nId);
+ void connectHelpShortcut(QMenu* pMenu);
+ // set slots that handle signals relevant for help menu
+ void connectHelpSignalSlots(QMenu* pMenu, QtMenuItem* pSalMenuItem);
+
+public:
+ QtMenu(bool bMenuBar);
+
+ virtual bool VisibleMenuBar() override; // must return TRUE to actually DISPLAY native menu bars
+
+ virtual void InsertItem(SalMenuItem* pSalMenuItem, unsigned nPos) override;
+ virtual void RemoveItem(unsigned nPos) override;
+ virtual void SetSubMenu(SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned nPos) override;
+ virtual void SetFrame(const SalFrame* pFrame) override;
+ virtual void ShowMenuBar(bool bVisible) override;
+ virtual bool ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangle& rRect,
+ FloatWinPopupFlags nFlags) override;
+ QtMenu* GetTopLevel();
+ virtual void SetItemBits(unsigned nPos, MenuItemBits nBits) override;
+ virtual void CheckItem(unsigned nPos, bool bCheck) override;
+ virtual void EnableItem(unsigned nPos, bool bEnable) override;
+ virtual void ShowItem(unsigned nPos, bool bShow) override;
+ virtual void SetItemText(unsigned nPos, SalMenuItem* pSalMenuItem,
+ const OUString& rText) override;
+ virtual void SetItemImage(unsigned nPos, SalMenuItem* pSalMenuItem,
+ const Image& rImage) override;
+ virtual void SetAccelerator(unsigned nPos, SalMenuItem* pSalMenuItem,
+ const vcl::KeyCode& rKeyCode, const OUString& rKeyName) override;
+ virtual void GetSystemMenuData(SystemMenuData* pData) override;
+ virtual void ShowCloseButton(bool bShow) override;
+ virtual bool AddMenuBarButton(const SalMenuButtonItem&) override;
+ virtual void RemoveMenuBarButton(sal_uInt16 nId) override;
+ virtual tools::Rectangle GetMenuBarButtonRectPixel(sal_uInt16 nId, SalFrame*) override;
+ virtual int GetMenuBarHeight() const override;
+
+ void SetMenu(Menu* pMenu) { mpVCLMenu = pMenu; }
+ Menu* GetMenu() { return mpVCLMenu; }
+ unsigned GetItemCount() const { return maItems.size(); }
+ QtMenuItem* GetItemAtPos(unsigned nPos) { return maItems[nPos]; }
+
+private slots:
+ static void slotShowHelp();
+ static void slotMenuHovered(QtMenuItem* pItem);
+ static void slotMenuTriggered(QtMenuItem* pQItem);
+ static void slotMenuAboutToShow(QtMenuItem* pQItem);
+ static void slotMenuAboutToHide(QtMenuItem* pQItem);
+ void slotCloseDocument();
+ void slotMenuBarButtonClicked(QAbstractButton*);
+};
+
+class QtMenuItem : public SalMenuItem
+{
+public:
+ QtMenuItem(const SalItemParams*);
+
+ QAction* getAction() const;
+
+ QtMenu* mpParentMenu; // The menu into which this menu item is inserted
+ QtMenu* mpSubMenu; // Submenu of this item (if defined)
+ std::unique_ptr<QAction> mpAction; // action corresponding to this item
+ std::unique_ptr<QMenu> mpMenu; // menu corresponding to this item
+ std::shared_ptr<QActionGroup> mpActionGroup; // empty if it's a separator element
+ sal_uInt16 mnId; // Item ID
+ MenuItemType mnType; // Item type
+ bool mbVisible; // Item visibility.
+ bool mbEnabled; // Item active.
+ Image maImage; // Item image
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtObject.hxx b/vcl/inc/qt5/QtObject.hxx
new file mode 100644
index 0000000000..bc5a8e584b
--- /dev/null
+++ b/vcl/inc/qt5/QtObject.hxx
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <salobj.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <QtCore/QObject>
+#include <QtGui/QRegion>
+#include <QtWidgets/QWidget>
+
+class QtFrame;
+class QtObjectWidget;
+class QWindow;
+
+class QtObject final : public QObject, public SalObject
+{
+ Q_OBJECT
+
+ SystemEnvData m_aSystemData;
+ QtFrame* m_pParent;
+ QtObjectWidget* m_pQWidget;
+ QRegion m_pRegion;
+ bool m_bForwardKey;
+
+public:
+ QtObject(QtFrame* pParent, bool bShow);
+ ~QtObject() override;
+
+ QtFrame* frame() const { return m_pParent; }
+ inline QWidget* widget() const;
+ QWindow* windowHandle() const;
+ bool forwardKey() const { return m_bForwardKey; }
+
+ virtual void ResetClipRegion() override;
+ virtual void BeginSetClipRegion(sal_uInt32 nRects) override;
+ virtual void UnionClipRegion(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight) override;
+ virtual void EndSetClipRegion() override;
+
+ virtual void SetPosSize(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight) override;
+ virtual void Show(bool bVisible) override;
+
+ virtual void SetForwardKey(bool bEnable) override;
+
+ virtual const SystemEnvData* GetSystemData() const override { return &m_aSystemData; }
+
+ virtual void Reparent(SalFrame* pFrame) override;
+};
+
+class QtObjectWidget final : public QWidget
+{
+ QtObject& m_rParent;
+
+ void focusInEvent(QFocusEvent*) override;
+ void focusOutEvent(QFocusEvent*) override;
+ void mousePressEvent(QMouseEvent*) override;
+ void mouseReleaseEvent(QMouseEvent*) override;
+ void keyPressEvent(QKeyEvent*) override;
+ void keyReleaseEvent(QKeyEvent*) override;
+
+public:
+ explicit QtObjectWidget(QtObject& rParent);
+};
+
+QWidget* QtObject::widget() const { return m_pQWidget; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtOpenGLContext.hxx b/vcl/inc/qt5/QtOpenGLContext.hxx
new file mode 100644
index 0000000000..8036d50777
--- /dev/null
+++ b/vcl/inc/qt5/QtOpenGLContext.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <vcl/opengl/OpenGLContext.hxx>
+
+class QWindow;
+class QOpenGLContext;
+
+class QtOpenGLContext final : public OpenGLContext
+{
+public:
+ virtual void initWindow() override;
+
+private:
+ 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;
+
+ static bool g_bAnyCurrent;
+
+ GLWindow m_aGLWin;
+
+ QWindow* m_pWindow;
+ QOpenGLContext* m_pContext;
+};
diff --git a/vcl/inc/qt5/QtPainter.hxx b/vcl/inc/qt5/QtPainter.hxx
new file mode 100644
index 0000000000..9702a19bdb
--- /dev/null
+++ b/vcl/inc/qt5/QtPainter.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <QtCore/QRectF>
+#include <QtGui/QPainter>
+#include <QtWidgets/QWidget>
+
+#include "QtFrame.hxx"
+#include "QtGraphics.hxx"
+
+class QtPainter final : public QPainter
+{
+ QtGraphicsBackend& m_rGraphics;
+ QRegion m_aRegion;
+
+public:
+ QtPainter(QtGraphicsBackend& rGraphics, bool bPrepareBrush = false,
+ sal_uInt8 nTransparency = 255);
+ ~QtPainter()
+ {
+ if (m_rGraphics.m_pFrame && !m_aRegion.isEmpty())
+ m_rGraphics.m_pFrame->GetQWidget()->update(m_aRegion);
+ }
+
+ void update(int nx, int ny, int nw, int nh)
+ {
+ if (m_rGraphics.m_pFrame)
+ m_aRegion += scaledQRect({ nx, ny, nw, nh }, 1 / m_rGraphics.devicePixelRatioF());
+ }
+
+ void update(const QRect& rRect)
+ {
+ if (m_rGraphics.m_pFrame)
+ m_aRegion += scaledQRect(rRect, 1 / m_rGraphics.devicePixelRatioF());
+ }
+
+ void update(const QRectF& rRectF)
+ {
+ if (m_rGraphics.m_pFrame)
+ update(scaledQRect(rRectF.toAlignedRect(), 1 / m_rGraphics.devicePixelRatioF()));
+ }
+
+ void update()
+ {
+ if (m_rGraphics.m_pFrame)
+ m_aRegion += m_rGraphics.m_pFrame->GetQWidget()->rect();
+ }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtPrinter.hxx b/vcl/inc/qt5/QtPrinter.hxx
new file mode 100644
index 0000000000..5aacfd44ec
--- /dev/null
+++ b/vcl/inc/qt5/QtPrinter.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 <unx/genprn.h>
+
+class SalFrame;
+
+class QtPrinter final : public PspSalPrinter
+{
+public:
+ QtPrinter(SalInfoPrinter* pInfoPrinter);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtSvpGraphics.hxx b/vcl/inc/qt5/QtSvpGraphics.hxx
new file mode 100644
index 0000000000..da3786eee1
--- /dev/null
+++ b/vcl/inc/qt5/QtSvpGraphics.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <vclpluginapi.h>
+#include <headless/svpgdi.hxx>
+
+#include "QtGraphicsBase.hxx"
+
+class QtFrame;
+
+class VCLPLUG_QT_PUBLIC QtSvpGraphics final : public SvpSalGraphics, public QtGraphicsBase
+{
+ QtFrame* const m_pFrame;
+
+ void handleDamage(const tools::Rectangle&) override;
+
+public:
+ QtSvpGraphics(QtFrame* pFrame);
+ ~QtSvpGraphics() override;
+
+ void updateQWidget() const;
+
+#if ENABLE_CAIRO_CANVAS
+ bool SupportsCairo() const override;
+ cairo::SurfaceSharedPtr
+ CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const override;
+ cairo::SurfaceSharedPtr CreateSurface(const OutputDevice& rRefDevice, int x, int y, int width,
+ int height) const override;
+#endif // ENABLE_CAIRO_CANVAS
+
+ virtual void GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY) override;
+
+ virtual OUString getRenderBackendName() const override { return "qt5svp"; }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtSvpSurface.hxx b/vcl/inc/qt5/QtSvpSurface.hxx
new file mode 100644
index 0000000000..d86553643e
--- /dev/null
+++ b/vcl/inc/qt5/QtSvpSurface.hxx
@@ -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/.
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <vcl/cairo.hxx>
+
+class QtSvpGraphics;
+class OutputDevice;
+
+namespace cairo
+{
+class QtSvpSurface final : public Surface
+{
+ const QtSvpGraphics* m_pGraphics;
+ cairo_t* const m_pCairoContext;
+ CairoSurfaceSharedPtr m_pSurface;
+
+public:
+ /// takes over ownership of passed cairo_surface
+ explicit QtSvpSurface(CairoSurfaceSharedPtr pSurface);
+ /// create surface on subarea of given drawable
+ explicit QtSvpSurface(const QtSvpGraphics* pGraphics, int x, int y, int width, int height);
+ ~QtSvpSurface() override;
+
+ // Surface interface
+ CairoSharedPtr getCairo() const override;
+ CairoSurfaceSharedPtr getCairoSurface() const override { return m_pSurface; }
+ SurfaceSharedPtr getSimilar(int nContentType, int width, int height) const override;
+
+ VclPtr<VirtualDevice> createVirtualDevice() const override;
+ void flush() const override;
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtSystem.hxx b/vcl/inc/qt5/QtSystem.hxx
new file mode 100644
index 0000000000..760520f42a
--- /dev/null
+++ b/vcl/inc/qt5/QtSystem.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/.
+ */
+
+#pragma once
+
+#include <unx/gensys.h>
+
+class QtSystem final : public SalGenericSystem
+{
+public:
+ virtual unsigned int GetDisplayScreenCount() override;
+ virtual AbsoluteScreenPixelRectangle
+ GetDisplayScreenPosSizePixel(unsigned int nScreen) override;
+ virtual int ShowNativeDialog(const OUString& rTitle, const OUString& rMessage,
+ const std::vector<OUString>& rButtons) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtTimer.hxx b/vcl/inc/qt5/QtTimer.hxx
new file mode 100644
index 0000000000..204b1bfe43
--- /dev/null
+++ b/vcl/inc/qt5/QtTimer.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 <saltimer.hxx>
+#include <QtCore/QTimer>
+
+class QtTimer final : public QObject, public SalTimer
+{
+ Q_OBJECT
+
+ QTimer m_aTimer;
+
+private Q_SLOTS:
+ void timeoutActivated();
+ void startTimer(int);
+ void stopTimer();
+
+Q_SIGNALS:
+ void startTimerSignal(int);
+ void stopTimerSignal();
+
+public:
+ QtTimer();
+
+ int remainingTime() const { return m_aTimer.remainingTime(); }
+
+ virtual void Start(sal_uInt64 nMS) override;
+ virtual void Stop() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtTools.hxx b/vcl/inc/qt5/QtTools.hxx
new file mode 100644
index 0000000000..5a0032ccc3
--- /dev/null
+++ b/vcl/inc/qt5/QtTools.hxx
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <config_vclplug.h>
+
+#include <QtCore/QPoint>
+#include <QtCore/QRect>
+#include <QtCore/QSize>
+#include <QtCore/QString>
+#include <QtGui/QImage>
+
+#include <rtl/string.hxx>
+#include <rtl/ustring.hxx>
+#include <tools/color.hxx>
+#include <tools/gen.hxx>
+#include <vcl/bitmap/BitmapTypes.hxx>
+
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+
+#include <memory>
+
+class Image;
+class QImage;
+
+inline OUString toOUString(const QString& s)
+{
+ // QString stores UTF16, just like OUString
+ return OUString(reinterpret_cast<const sal_Unicode*>(s.data()), s.length());
+}
+
+inline QString toQString(const OUString& s)
+{
+ return QString::fromUtf16(s.getStr(), s.getLength());
+}
+
+inline QRect toQRect(const tools::Rectangle& rRect)
+{
+ return QRect(rRect.Left(), rRect.Top(), rRect.GetWidth(), rRect.GetHeight());
+}
+
+inline QRect toQRect(const AbsoluteScreenPixelRectangle& rRect, const qreal fScale)
+{
+ return QRect(floor(rRect.Left() * fScale), floor(rRect.Top() * fScale),
+ ceil(rRect.GetWidth() * fScale), ceil(rRect.GetHeight() * fScale));
+}
+
+inline QRect scaledQRect(const QRect& rRect, const qreal fScale)
+{
+ return QRect(floor(rRect.x() * fScale), floor(rRect.y() * fScale), ceil(rRect.width() * fScale),
+ ceil(rRect.height() * fScale));
+}
+
+inline tools::Rectangle toRectangle(const QRect& rRect)
+{
+ return tools::Rectangle(rRect.left(), rRect.top(), rRect.right(), rRect.bottom());
+}
+
+inline QSize toQSize(const Size& rSize) { return QSize(rSize.Width(), rSize.Height()); }
+
+inline Size toSize(const QSize& rSize) { return Size(rSize.width(), rSize.height()); }
+
+inline Point toPoint(const QPoint& rPoint) { return Point(rPoint.x(), rPoint.y()); }
+
+inline QColor toQColor(const Color& rColor)
+{
+ return QColor(rColor.GetRed(), rColor.GetGreen(), rColor.GetBlue(), rColor.GetAlpha());
+}
+
+Qt::DropActions toQtDropActions(sal_Int8 dragOperation);
+sal_Int8 toVclDropActions(Qt::DropActions dragOperation);
+sal_Int8 toVclDropAction(Qt::DropAction dragOperation);
+Qt::DropAction getPreferredDropAction(sal_Int8 dragOperation);
+
+inline QList<int> toQList(const css::uno::Sequence<sal_Int32>& aSequence)
+{
+ QList<int> aList;
+ for (sal_Int32 i : aSequence)
+ {
+ aList.append(i);
+ }
+ return aList;
+}
+
+constexpr QImage::Format Qt_DefaultFormat32 = QImage::Format_ARGB32;
+
+inline QImage::Format getBitFormat(vcl::PixelFormat ePixelFormat)
+{
+ switch (ePixelFormat)
+ {
+ case vcl::PixelFormat::N8_BPP:
+ return QImage::Format_Indexed8;
+ case vcl::PixelFormat::N24_BPP:
+ return QImage::Format_RGB888;
+ case vcl::PixelFormat::N32_BPP:
+ return Qt_DefaultFormat32;
+ default:
+ std::abort();
+ break;
+ }
+ return QImage::Format_Invalid;
+}
+
+inline sal_uInt16 getFormatBits(QImage::Format eFormat)
+{
+ switch (eFormat)
+ {
+ case QImage::Format_Mono:
+ return 1;
+ case QImage::Format_Indexed8:
+ return 8;
+ case QImage::Format_RGB888:
+ return 24;
+ case Qt_DefaultFormat32:
+ case QImage::Format_ARGB32_Premultiplied:
+ return 32;
+ default:
+ std::abort();
+ return 0;
+ }
+}
+
+typedef struct _cairo_surface cairo_surface_t;
+struct CairoDeleter
+{
+ void operator()(cairo_surface_t* pSurface) const;
+};
+
+typedef std::unique_ptr<cairo_surface_t, CairoDeleter> UniqueCairoSurface;
+
+sal_uInt16 GetKeyModCode(Qt::KeyboardModifiers eKeyModifiers);
+sal_uInt16 GetMouseModCode(Qt::MouseButtons eButtons);
+
+QImage toQImage(const Image& rImage);
+
+template <typename charT, typename traits>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& stream,
+ const QString& rString)
+{
+ return stream << toOUString(rString);
+}
+
+template <typename charT, typename traits>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& stream,
+ const QRect& rRect)
+{
+ return stream << toRectangle(rRect);
+}
+
+template <typename charT, typename traits>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& stream,
+ const QSize& rSize)
+{
+ return stream << toSize(rSize);
+}
+
+template <typename charT, typename traits>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& stream,
+ const QPoint& rPoint)
+{
+ return stream << toPoint(rPoint);
+}
+
+#define CHECK_QT5_USING_X11 (QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && QT5_USING_X11)
+
+#define CHECK_QT6_USING_X11 (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) && QT6_USING_X11)
+
+#define CHECK_ANY_QT_USING_X11 CHECK_QT5_USING_X11 || CHECK_QT6_USING_X11
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtTransferable.hxx b/vcl/inc/qt5/QtTransferable.hxx
new file mode 100644
index 0000000000..5f1533dd59
--- /dev/null
+++ b/vcl/inc/qt5/QtTransferable.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/.
+ *
+ */
+
+#pragma once
+
+#include <cppuhelper/compbase.hxx>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+
+#include <QtCore/QMimeData>
+#include <QtCore/QStringList>
+#include <QtGui/QClipboard>
+
+/**
+ * QtTransferable classes are used to read QMimeData via the XTransferable
+ * interface. All the functionality is already implemented in the QtTransferable.
+ *
+ * The specialisations map to the two users, which provide QMimeData: the Clipboard
+ * and the Drag'n'Drop functionality.
+ *
+ * LO itself seem to just accept "text/plain;charset=utf-16", so it relies on the
+ * backend to convert to this charset, but still offers "text/plain" itself.
+ *
+ * It's the "mirror" interface of the QtMimeData, which is defined below.
+ **/
+class QtTransferable : public cppu::WeakImplHelper<css::datatransfer::XTransferable>
+{
+ QtTransferable(const QtTransferable&) = delete;
+
+ const QMimeData* m_pMimeData;
+ osl::Mutex m_aMutex;
+ bool m_bProvideUTF16FromOtherEncoding;
+ css::uno::Sequence<css::datatransfer::DataFlavor> m_aMimeTypeSeq;
+
+public:
+ QtTransferable(const QMimeData* pMimeData);
+ const QMimeData* mimeData() const { return m_pMimeData; }
+
+ css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL getTransferDataFlavors() override;
+ sal_Bool SAL_CALL isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor) override;
+ css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override;
+};
+
+/**
+ * The QClipboard's QMimeData is volatile. As written in the QClipboard::mimeData
+ * documentation, "the pointer returned might become invalidated when the contents
+ * of the clipboard changes". Therefore it can just be accessed reliably inside
+ * the QClipboard's object thread, which is the QApplication's thread, so all of
+ * the access has to go through RunInMainThread().
+ *
+ * If we detect a QMimeData change, we simply drop reporting any content. In theory
+ * we can recover in the case where there hadn't been any calls of the XTransferable
+ * interface, but currently we don't. But we ensure to never report mixed content,
+ * so we'll just cease operation on QMimeData change.
+ **/
+class QtClipboardTransferable final : public QtTransferable
+{
+ // to detect in-flight QMimeData changes
+ const QClipboard::Mode m_aMode;
+
+ bool hasInFlightChanged() const;
+
+public:
+ explicit QtClipboardTransferable(const QClipboard::Mode aMode, const QMimeData* pMimeData);
+
+ // these are the same then QtTransferable, except they go through RunInMainThread
+ css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL getTransferDataFlavors() override;
+ sal_Bool SAL_CALL isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor) override;
+ css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override;
+};
+
+/**
+ * Convenience typedef for better code readability
+ *
+ * This just uses the QMimeData provided by the QWidgets D'n'D events.
+ **/
+typedef QtTransferable QtDnDTransferable;
+
+/**
+ * A lazy loading QMimeData for XTransferable reads
+ *
+ * This is an interface class to make a XTransferable read accessible as a
+ * QMimeData. The mime data is just stored inside the XTransferable, never
+ * in the QMimeData itself! It's objects are just used for QClipboard to read
+ * the XTransferable data.
+ *
+ * Like XTransferable itself, this class should be considered an immutable
+ * container for mime data. There is no need to ever set any of its data.
+ *
+ * LO will offer at least UTF-16, if there is a viable text representation.
+ * If LO misses to offer a UTF-8 or a locale encoded string, these objects
+ * will offer them themselves and convert from UTF-16 on demand.
+ *
+ * It's the "mirror" interface of the QtTransferable.
+ **/
+class QtMimeData final : public QMimeData
+{
+ friend class QtClipboardTransferable;
+
+ const css::uno::Reference<css::datatransfer::XTransferable> m_aContents;
+ mutable bool m_bHaveNoCharset; // = uses the locale charset
+ mutable bool m_bHaveUTF8;
+ mutable QStringList m_aMimeTypeList;
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ QVariant retrieveData(const QString& mimeType, QVariant::Type type) const override;
+#else
+ QVariant retrieveData(const QString& mimeType, QMetaType type) const override;
+#endif
+
+public:
+ explicit QtMimeData(const css::uno::Reference<css::datatransfer::XTransferable>& aContents);
+
+ bool hasFormat(const QString& mimeType) const override;
+ QStringList formats() const override;
+
+ bool deepCopy(QMimeData** const) const;
+
+ css::datatransfer::XTransferable* xTransferable() const { return m_aContents.get(); }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtVirtualDevice.hxx b/vcl/inc/qt5/QtVirtualDevice.hxx
new file mode 100644
index 0000000000..2481f63d06
--- /dev/null
+++ b/vcl/inc/qt5/QtVirtualDevice.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 <salvd.hxx>
+
+#include <memory>
+#include <vector>
+
+#include <QtCore/QSize>
+
+class QtGraphics;
+class QImage;
+enum class DeviceFormat;
+
+class QtVirtualDevice final : public SalVirtualDevice
+{
+ std::vector<QtGraphics*> m_aGraphics;
+ std::unique_ptr<QImage> m_pImage;
+ QSize m_aFrameSize;
+ double m_fScale;
+
+public:
+ QtVirtualDevice(double fScale);
+
+ // SalVirtualDevice
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics(SalGraphics* pGraphics) override;
+
+ virtual bool SetSize(tools::Long nNewDX, tools::Long nNewDY) override;
+ virtual bool SetSizeUsingBuffer(tools::Long nNewDX, tools::Long nNewDY,
+ sal_uInt8* pBuffer) override;
+
+ // SalGeometryProvider
+ virtual tools::Long GetWidth() const override;
+ virtual tools::Long GetHeight() const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtWidget.hxx b/vcl/inc/qt5/QtWidget.hxx
new file mode 100644
index 0000000000..ca0165401e
--- /dev/null
+++ b/vcl/inc/qt5/QtWidget.hxx
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <QtCore/QRect>
+#include <QtWidgets/QGestureEvent>
+#include <QtWidgets/QWidget>
+#include <rtl/ustring.hxx>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+
+class QInputEvent;
+class QtFrame;
+class QtObject;
+struct SalAbstractMouseEvent;
+
+class QtWidget : public QWidget
+{
+ Q_OBJECT
+
+ QtFrame& m_rFrame;
+ bool m_bNonEmptyIMPreeditSeen;
+ mutable bool m_bInInputMethodQueryCursorRectangle;
+ mutable QRect m_aImCursorRectangle;
+ int m_nDeltaX;
+ int m_nDeltaY;
+
+ static void commitText(QtFrame&, const QString& aText);
+ static void deleteReplacementText(QtFrame& rFrame, int nReplacementStart,
+ int nReplacementLength);
+ static bool handleGestureEvent(QtFrame& rFrame, QGestureEvent* pGestureEvent);
+ static bool handleKeyEvent(QtFrame&, const QWidget&, QKeyEvent*);
+ static void handleMouseEnterLeaveEvents(const QtFrame&, QEvent*);
+ static void fillSalAbstractMouseEvent(const QtFrame& rFrame, const QInputEvent* pQEvent,
+ const QPoint& rPos, Qt::MouseButtons eButtons, int nWidth,
+ SalAbstractMouseEvent& aSalEvent);
+
+ virtual bool event(QEvent*) override;
+
+ virtual void focusInEvent(QFocusEvent*) override;
+ virtual void focusOutEvent(QFocusEvent*) override;
+ // keyPressEvent(QKeyEvent*) is handled via event(QEvent*); see comment
+ virtual void keyReleaseEvent(QKeyEvent*) override;
+ virtual void mouseMoveEvent(QMouseEvent*) override;
+ virtual void mousePressEvent(QMouseEvent*) override;
+ virtual void mouseReleaseEvent(QMouseEvent*) override;
+ virtual void dragEnterEvent(QDragEnterEvent*) override;
+ virtual void dragLeaveEvent(QDragLeaveEvent*) override;
+ virtual void dragMoveEvent(QDragMoveEvent*) override;
+ virtual void dropEvent(QDropEvent*) override;
+ virtual void moveEvent(QMoveEvent*) override;
+ virtual void paintEvent(QPaintEvent*) override;
+ virtual void resizeEvent(QResizeEvent*) override;
+ virtual void showEvent(QShowEvent*) override;
+ virtual void hideEvent(QHideEvent*) override;
+ virtual void wheelEvent(QWheelEvent*) override;
+ virtual void closeEvent(QCloseEvent*) override;
+ virtual void changeEvent(QEvent*) override;
+ virtual void leaveEvent(QEvent*) override;
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ virtual void enterEvent(QEnterEvent*) override;
+#else
+ virtual void enterEvent(QEvent*) override;
+#endif
+
+ void inputMethodEvent(QInputMethodEvent*) override;
+ QVariant inputMethodQuery(Qt::InputMethodQuery) const override;
+ static void closePopup();
+
+public:
+ QtWidget(QtFrame& rFrame, Qt::WindowFlags f = Qt::WindowFlags());
+
+ QtFrame& frame() const { return m_rFrame; }
+ void endExtTextInput();
+ void fakeResize();
+
+ static bool handleEvent(QtFrame&, QWidget&, QEvent*);
+ // key events might be propagated further down => call base on false
+ static inline bool handleKeyReleaseEvent(QtFrame&, const QWidget&, QKeyEvent*);
+ // mouse events are always accepted
+ static void handleMouseButtonEvent(const QtFrame&, const QMouseEvent*);
+};
+
+bool QtWidget::handleKeyReleaseEvent(QtFrame& rFrame, const QWidget& rWidget, QKeyEvent* pEvent)
+{
+ return handleKeyEvent(rFrame, rWidget, pEvent);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtX11Support.hxx b/vcl/inc/qt5/QtX11Support.hxx
new file mode 100644
index 0000000000..17696a8952
--- /dev/null
+++ b/vcl/inc/qt5/QtX11Support.hxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <string_view>
+
+#include <xcb/xcb.h>
+
+class QtX11Support final
+{
+public:
+ static void setApplicationID(xcb_window_t nWinId, std::u16string_view rWMClass);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtXAccessible.hxx b/vcl/inc/qt5/QtXAccessible.hxx
new file mode 100644
index 0000000000..ddb7849b64
--- /dev/null
+++ b/vcl/inc/qt5/QtXAccessible.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/.
+ */
+
+#pragma once
+
+#include <vclpluginapi.h>
+
+#include <QtCore/QObject>
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+
+#include <vcl/window.hxx>
+
+class QtFrame;
+class QtWidget;
+
+// Wrapper class to hold a css::accessibility::XAccessible object
+// while being able to pass it as a QObject
+class QtXAccessible : public QObject
+{
+ Q_OBJECT
+
+public:
+ QtXAccessible(css::uno::Reference<css::accessibility::XAccessible> xAccessible);
+
+ /** Reference to the XAccessible.
+ * This is cleared once it has been passed to the QtAccessibleWidget,
+ * which then keeps an own reference and takes care of all required
+ * access to the XAccessible for the Qt a11y bridge. */
+ css::uno::Reference<css::accessibility::XAccessible> m_xAccessible;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtAccessibleEventListener.hxx b/vcl/inc/qt6/QtAccessibleEventListener.hxx
new file mode 100644
index 0000000000..7f67ea9fe9
--- /dev/null
+++ b/vcl/inc/qt6/QtAccessibleEventListener.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtAccessibleEventListener.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtAccessibleRegistry.hxx b/vcl/inc/qt6/QtAccessibleRegistry.hxx
new file mode 100644
index 0000000000..b29fbb3263
--- /dev/null
+++ b/vcl/inc/qt6/QtAccessibleRegistry.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtAccessibleRegistry.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtAccessibleWidget.hxx b/vcl/inc/qt6/QtAccessibleWidget.hxx
new file mode 100644
index 0000000000..937012308d
--- /dev/null
+++ b/vcl/inc/qt6/QtAccessibleWidget.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtAccessibleWidget.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtBitmap.hxx b/vcl/inc/qt6/QtBitmap.hxx
new file mode 100644
index 0000000000..78332058e3
--- /dev/null
+++ b/vcl/inc/qt6/QtBitmap.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtBitmap.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtClipboard.hxx b/vcl/inc/qt6/QtClipboard.hxx
new file mode 100644
index 0000000000..0f3b718b88
--- /dev/null
+++ b/vcl/inc/qt6/QtClipboard.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtClipboard.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtData.hxx b/vcl/inc/qt6/QtData.hxx
new file mode 100644
index 0000000000..827f3b6af6
--- /dev/null
+++ b/vcl/inc/qt6/QtData.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtData.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtDragAndDrop.hxx b/vcl/inc/qt6/QtDragAndDrop.hxx
new file mode 100644
index 0000000000..9f8056d9cf
--- /dev/null
+++ b/vcl/inc/qt6/QtDragAndDrop.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtDragAndDrop.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtFilePicker.hxx b/vcl/inc/qt6/QtFilePicker.hxx
new file mode 100644
index 0000000000..20a3f6d5ce
--- /dev/null
+++ b/vcl/inc/qt6/QtFilePicker.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtFilePicker.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtFont.hxx b/vcl/inc/qt6/QtFont.hxx
new file mode 100644
index 0000000000..a477a46bbe
--- /dev/null
+++ b/vcl/inc/qt6/QtFont.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtFont.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtFontFace.hxx b/vcl/inc/qt6/QtFontFace.hxx
new file mode 100644
index 0000000000..875fa08127
--- /dev/null
+++ b/vcl/inc/qt6/QtFontFace.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtFontFace.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtFrame.hxx b/vcl/inc/qt6/QtFrame.hxx
new file mode 100644
index 0000000000..6b6bae498c
--- /dev/null
+++ b/vcl/inc/qt6/QtFrame.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtFrame.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtGraphics.hxx b/vcl/inc/qt6/QtGraphics.hxx
new file mode 100644
index 0000000000..91e0808075
--- /dev/null
+++ b/vcl/inc/qt6/QtGraphics.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtGraphics.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtGraphicsBase.hxx b/vcl/inc/qt6/QtGraphicsBase.hxx
new file mode 100644
index 0000000000..31bcfc3242
--- /dev/null
+++ b/vcl/inc/qt6/QtGraphicsBase.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtGraphicsBase.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtGraphics_Controls.hxx b/vcl/inc/qt6/QtGraphics_Controls.hxx
new file mode 100644
index 0000000000..9601a2ace4
--- /dev/null
+++ b/vcl/inc/qt6/QtGraphics_Controls.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtGraphics_Controls.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtInstance.hxx b/vcl/inc/qt6/QtInstance.hxx
new file mode 100644
index 0000000000..65eec7db32
--- /dev/null
+++ b/vcl/inc/qt6/QtInstance.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtInstance.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtMainWindow.hxx b/vcl/inc/qt6/QtMainWindow.hxx
new file mode 100644
index 0000000000..bef22b2b61
--- /dev/null
+++ b/vcl/inc/qt6/QtMainWindow.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtMainWindow.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtMenu.hxx b/vcl/inc/qt6/QtMenu.hxx
new file mode 100644
index 0000000000..42df15e37b
--- /dev/null
+++ b/vcl/inc/qt6/QtMenu.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtMenu.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtObject.hxx b/vcl/inc/qt6/QtObject.hxx
new file mode 100644
index 0000000000..b4a7f51212
--- /dev/null
+++ b/vcl/inc/qt6/QtObject.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtObject.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtOpenGLContext.hxx b/vcl/inc/qt6/QtOpenGLContext.hxx
new file mode 100644
index 0000000000..1a82e2dba4
--- /dev/null
+++ b/vcl/inc/qt6/QtOpenGLContext.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtOpenGLContext.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtPainter.hxx b/vcl/inc/qt6/QtPainter.hxx
new file mode 100644
index 0000000000..791d633355
--- /dev/null
+++ b/vcl/inc/qt6/QtPainter.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtPainter.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtPrinter.hxx b/vcl/inc/qt6/QtPrinter.hxx
new file mode 100644
index 0000000000..f9caeb40fc
--- /dev/null
+++ b/vcl/inc/qt6/QtPrinter.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtPrinter.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtSvpGraphics.hxx b/vcl/inc/qt6/QtSvpGraphics.hxx
new file mode 100644
index 0000000000..ab002c09b9
--- /dev/null
+++ b/vcl/inc/qt6/QtSvpGraphics.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtSvpGraphics.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtSvpSurface.hxx b/vcl/inc/qt6/QtSvpSurface.hxx
new file mode 100644
index 0000000000..952bb4de55
--- /dev/null
+++ b/vcl/inc/qt6/QtSvpSurface.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtSvpSurface.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtSystem.hxx b/vcl/inc/qt6/QtSystem.hxx
new file mode 100644
index 0000000000..1a91f678a2
--- /dev/null
+++ b/vcl/inc/qt6/QtSystem.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtSystem.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtTimer.hxx b/vcl/inc/qt6/QtTimer.hxx
new file mode 100644
index 0000000000..9ef6563ebc
--- /dev/null
+++ b/vcl/inc/qt6/QtTimer.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtTimer.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtTools.hxx b/vcl/inc/qt6/QtTools.hxx
new file mode 100644
index 0000000000..e6078fb143
--- /dev/null
+++ b/vcl/inc/qt6/QtTools.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtTools.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtTransferable.hxx b/vcl/inc/qt6/QtTransferable.hxx
new file mode 100644
index 0000000000..f3cd0a9107
--- /dev/null
+++ b/vcl/inc/qt6/QtTransferable.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtTransferable.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtVirtualDevice.hxx b/vcl/inc/qt6/QtVirtualDevice.hxx
new file mode 100644
index 0000000000..3e57fb2b03
--- /dev/null
+++ b/vcl/inc/qt6/QtVirtualDevice.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtVirtualDevice.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtWidget.hxx b/vcl/inc/qt6/QtWidget.hxx
new file mode 100644
index 0000000000..486af7c19f
--- /dev/null
+++ b/vcl/inc/qt6/QtWidget.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtWidget.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtX11Support.hxx b/vcl/inc/qt6/QtX11Support.hxx
new file mode 100644
index 0000000000..df4ae0d1e0
--- /dev/null
+++ b/vcl/inc/qt6/QtX11Support.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtX11Support.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt6/QtXAccessible.hxx b/vcl/inc/qt6/QtXAccessible.hxx
new file mode 100644
index 0000000000..e591a77647
--- /dev/null
+++ b/vcl/inc/qt6/QtXAccessible.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtXAccessible.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/quartz/CGHelpers.hxx b/vcl/inc/quartz/CGHelpers.hxx
new file mode 100644
index 0000000000..a43b3e9751
--- /dev/null
+++ b/vcl/inc/quartz/CGHelpers.hxx
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_INC_QUARTZ_CGHELPER_HXX
+#define INCLUDED_VCL_INC_QUARTZ_CGHELPER_HXX
+
+#include <premac.h>
+#include <CoreGraphics/CoreGraphics.h>
+#include <postmac.h>
+
+#include <quartz/utils.h>
+
+class CGLayerHolder
+{
+private:
+ CGLayerRef mpLayer;
+
+ // Layer's scaling factor
+ float mfScale;
+
+public:
+ CGLayerHolder()
+ : mpLayer(nullptr)
+ , mfScale(1.0)
+ {
+ }
+
+ CGLayerHolder(CGLayerRef pLayer, float fScale = 1.0)
+ : mpLayer(pLayer)
+ , mfScale(fScale)
+ {
+ }
+
+ // Just the size of the layer in pixels
+ CGSize getSizePixels() const
+ {
+ CGSize aSize;
+ if (mpLayer)
+ {
+ aSize = CGLayerGetSize(mpLayer);
+ }
+ return aSize;
+ }
+
+ // Size in points is size in pixels divided by the scaling factor
+ CGSize getSizePoints() const
+ {
+ CGSize aSize;
+ if (mpLayer)
+ {
+ const CGSize aLayerSize = getSizePixels();
+ aSize.width = aLayerSize.width / mfScale;
+ aSize.height = aLayerSize.height / mfScale;
+ }
+ return aSize;
+ }
+
+ CGLayerRef get() const { return mpLayer; }
+
+ bool isSet() const { return mpLayer != nullptr; }
+
+ void set(CGLayerRef const& pLayer) { mpLayer = pLayer; }
+
+ float getScale() const { return mfScale; }
+
+ void setScale(float fScale) { mfScale = fScale; }
+};
+
+class CGContextHolder
+{
+private:
+ CGContextRef mpContext;
+
+public:
+ CGContextHolder()
+ : mpContext(nullptr)
+ {
+ }
+
+ CGContextHolder(CGContextRef pContext)
+ : mpContext(pContext)
+ {
+ }
+
+ CGContextRef get() const { return mpContext; }
+
+ bool isSet() const { return mpContext != nullptr; }
+
+ void set(CGContextRef const& pContext) { mpContext = pContext; }
+
+ void saveState() { CGContextSaveGState(mpContext); }
+
+ void restoreState() { CGContextRestoreGState(mpContext); }
+};
+
+#endif // INCLUDED_VCL_INC_QUARTZ_CGHELPER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/quartz/CoreTextFont.hxx b/vcl/inc/quartz/CoreTextFont.hxx
new file mode 100644
index 0000000000..90cf641677
--- /dev/null
+++ b/vcl/inc/quartz/CoreTextFont.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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <premac.h>
+#ifdef MACOSX
+#include <ApplicationServices/ApplicationServices.h>
+#include <osx/osxvcltypes.h>
+#include <osx/salframe.h>
+#else
+#include <CoreGraphics/CoreGraphics.h>
+#include <CoreText/CoreText.h>
+#endif
+#include <postmac.h>
+
+#ifdef IOS
+// iOS defines a different Point class so include salgeom.hxx after postmac.h
+// so that it will use the Point class in tools/gen.hxx
+#include "salgeom.hxx"
+#endif
+
+#include <font/LogicalFontInstance.hxx>
+#include <font/FontMetricData.hxx>
+#include <salgdi.hxx>
+
+#include <quartz/salgdicommon.hxx>
+
+#include <quartz/CGHelpers.hxx>
+#include <quartz/CoreTextFontFace.hxx>
+
+class CoreTextFont final : public LogicalFontInstance
+{
+ friend rtl::Reference<LogicalFontInstance>
+ CoreTextFontFace::CreateFontInstance(const vcl::font::FontSelectPattern&) const;
+
+public:
+ ~CoreTextFont() override;
+
+ void GetFontMetric(FontMetricDataRef const&);
+ bool GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const override;
+
+ CTFontRef GetCTFont() const { return mpCTFont; }
+
+ /// <1.0: font is squeezed, >1.0 font is stretched, else 1.0
+ float mfFontStretch;
+ /// text rotation in radian
+ float mfFontRotation;
+
+private:
+ explicit CoreTextFont(const CoreTextFontFace&, const vcl::font::FontSelectPattern&);
+
+ CTFontRef mpCTFont;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/quartz/CoreTextFontFace.hxx b/vcl/inc/quartz/CoreTextFontFace.hxx
new file mode 100644
index 0000000000..482b0b700d
--- /dev/null
+++ b/vcl/inc/quartz/CoreTextFontFace.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <vector>
+
+#include <premac.h>
+#ifdef MACOSX
+#include <ApplicationServices/ApplicationServices.h>
+#include <osx/osxvcltypes.h>
+#include <osx/salframe.h>
+#else
+#include <CoreGraphics/CoreGraphics.h>
+#include <CoreText/CoreText.h>
+#endif
+#include <postmac.h>
+
+#include <font/LogicalFontInstance.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <salgdi.hxx>
+
+#include <quartz/salgdicommon.hxx>
+#include <hb.h>
+
+class AquaSalFrame;
+class FontAttributes;
+
+// CoreText-specific physically available font face
+class CoreTextFontFace final : public vcl::font::PhysicalFontFace
+{
+public:
+ CoreTextFontFace(const FontAttributes&, CTFontDescriptorRef xRef);
+ ~CoreTextFontFace() override;
+
+ sal_IntPtr GetFontId() const override;
+
+ CTFontDescriptorRef GetFontDescriptorRef() const { return mxFontDescriptor; }
+
+ rtl::Reference<LogicalFontInstance>
+ CreateFontInstance(const vcl::font::FontSelectPattern&) const override;
+
+ hb_blob_t* GetHbTable(hb_tag_t nTag) const override;
+
+ const std::vector<hb_variation_t>& GetVariations(const LogicalFontInstance&) const override;
+
+private:
+ CTFontDescriptorRef mxFontDescriptor;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/quartz/SystemFontList.hxx b/vcl/inc/quartz/SystemFontList.hxx
new file mode 100644
index 0000000000..4f62380470
--- /dev/null
+++ b/vcl/inc/quartz/SystemFontList.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <premac.h>
+#ifdef MACOSX
+#include <ApplicationServices/ApplicationServices.h>
+#include <osx/osxvcltypes.h>
+#include <osx/salframe.h>
+#else
+#include <CoreGraphics/CoreGraphics.h>
+#include <CoreText/CoreText.h>
+#endif
+#include <postmac.h>
+
+#include <font/PhysicalFontFace.hxx>
+#ifdef IOS
+#include <font/PhysicalFontCollection.hxx>
+#endif
+
+#include <unordered_map>
+
+// TODO: move into cross-platform headers
+
+class CoreTextFontFace;
+
+class SystemFontList
+{
+public:
+ SystemFontList();
+ ~SystemFontList();
+
+ bool Init();
+ void AddFont(CoreTextFontFace*);
+
+ void AnnounceFonts(vcl::font::PhysicalFontCollection&) const;
+ CoreTextFontFace* GetFontDataFromId(sal_IntPtr nFontId) const;
+
+ CTFontCollectionRef fontCollection() { return mpCTFontCollection; }
+
+private:
+ CTFontCollectionRef mpCTFontCollection;
+ CFArrayRef mpCTFontArray;
+
+ std::unordered_map<sal_IntPtr, rtl::Reference<CoreTextFontFace>> maFontContainer;
+};
+
+FontAttributes DevFontFromCTFontDescriptor(CTFontDescriptorRef, bool*);
+std::unique_ptr<SystemFontList> GetCoretextFontList();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/quartz/cgutils.h b/vcl/inc/quartz/cgutils.h
new file mode 100644
index 0000000000..786b21458d
--- /dev/null
+++ b/vcl/inc/quartz/cgutils.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <vcl/dllapi.h>
+
+#include <premac.h>
+#include <CoreGraphics/CoreGraphics.h>
+#include <postmac.h>
+
+class SalBitmap;
+
+CGImageRef VCL_DLLPUBLIC CreateWithSalBitmapAndMask(const SalBitmap& rBitmap,
+ const SalBitmap& rMask, int nX, int nY,
+ int nWidth, int nHeight);
+
+#ifdef MACOSX
+bool VCL_DLLPUBLIC DefaultMTLDeviceIsSupported();
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/quartz/common.h b/vcl/inc/quartz/common.h
new file mode 100644
index 0000000000..851fa5d7a7
--- /dev/null
+++ b/vcl/inc/quartz/common.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_QUARTZ_COMMON_H
+#define INCLUDED_VCL_INC_QUARTZ_COMMON_H
+
+#include <iostream>
+
+#include <premac.h>
+#ifdef MACOSX
+#include <ApplicationServices/ApplicationServices.h>
+#else
+#include <CoreGraphics/CoreGraphics.h>
+#include <CoreText/CoreText.h>
+#endif
+#include <postmac.h>
+
+#include <sal/types.h>
+#include <vcl/salgtype.hxx>
+
+std::ostream &operator <<(std::ostream& s, CTFontRef pFont);
+
+#endif // INCLUDED_VCL_INC_QUARTZ_COMMON_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/quartz/salbmp.h b/vcl/inc/quartz/salbmp.h
new file mode 100644
index 0000000000..8f4e6a34ee
--- /dev/null
+++ b/vcl/inc/quartz/salbmp.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_QUARTZ_SALBMP_H
+#define INCLUDED_VCL_INC_QUARTZ_SALBMP_H
+
+#include <tools/gen.hxx>
+
+#include <vcl/BitmapBuffer.hxx>
+#include <vcl/BitmapPalette.hxx>
+
+#include <quartz/salgdi.h>
+
+#include <salinst.hxx>
+#include <salvd.hxx>
+#include <salbmp.hxx>
+
+#include <memory>
+
+
+struct BitmapBuffer;
+class BitmapPalette;
+
+class QuartzSalBitmap : public SalBitmap
+{
+public:
+ CGContextHolder maGraphicContext;
+ mutable CGImageRef mxCachedImage;
+ BitmapPalette maPalette;
+ std::shared_ptr<sal_uInt8> m_pUserBuffer;
+ std::shared_ptr<sal_uInt8> m_pContextBuffer;
+ sal_uInt16 mnBits;
+ int mnWidth;
+ int mnHeight;
+ sal_uInt32 mnBytesPerRow;
+
+public:
+ QuartzSalBitmap();
+ virtual ~QuartzSalBitmap() override;
+
+public:
+
+ // SalBitmap methods
+ bool Create( const Size& rSize, vcl::PixelFormat ePixelFormat, const BitmapPalette& rPal ) override;
+ bool Create( const SalBitmap& rSalBmp ) override;
+ bool Create( const SalBitmap& rSalBmp, SalGraphics* pGraphics ) override;
+ bool Create( const SalBitmap& rSalBmp, vcl::PixelFormat eNewPixelFormat) override;
+ virtual bool Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& rBitmapCanvas,
+ Size& rSize,
+ bool bMask = false ) override;
+
+ void Destroy() override;
+
+ Size GetSize() const override;
+ sal_uInt16 GetBitCount() const override;
+
+ BitmapBuffer *AcquireBuffer( BitmapAccessMode nMode ) override;
+ void ReleaseBuffer( BitmapBuffer* pBuffer, BitmapAccessMode nMode ) override;
+
+ bool GetSystemData( BitmapSystemData& rData ) override;
+
+ bool ScalingSupported() const override;
+ bool Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag ) override;
+ bool Replace( const Color& rSearchColor, const Color& rReplaceColor, sal_uInt8 nTol ) override;
+
+private:
+ // quartz helper
+ bool CreateContext();
+ void DestroyContext();
+ bool AllocateUserData();
+
+ void ConvertBitmapData( sal_uInt32 nWidth, sal_uInt32 nHeight,
+ sal_uInt16 nDestBits, sal_uInt32 nDestBytesPerRow, const BitmapPalette& rDestPalette, sal_uInt8* pDestData,
+ sal_uInt16 nSrcBits, sal_uInt32 nSrcBytesPerRow, const BitmapPalette& rSrcPalette, sal_uInt8* pSrcData );
+
+public:
+ bool Create(CGLayerHolder const & rLayerHolder, int nBitCount, int nX, int nY, int nWidth, int nHeight, bool bFlipped);
+
+public:
+ virtual CGImageRef CreateWithMask( const SalBitmap& rMask, int nX, int nY, int nWidth, int nHeight ) const override;
+ virtual CGImageRef CreateColorMask( int nX, int nY, int nWidth, int nHeight, Color nMaskColor ) const override;
+ virtual CGImageRef CreateCroppedImage( int nX, int nY, int nWidth, int nHeight ) const override;
+
+ void doDestroy();
+};
+
+#endif // INCLUDED_VCL_INC_QUARTZ_SALBMP_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/quartz/salgdi.h b/vcl/inc/quartz/salgdi.h
new file mode 100644
index 0000000000..c8befcc502
--- /dev/null
+++ b/vcl/inc/quartz/salgdi.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <vector>
+
+#include <tools/long.hxx>
+
+#include <premac.h>
+#ifdef MACOSX
+#include <ApplicationServices/ApplicationServices.h>
+#include <osx/osxvcltypes.h>
+#include <osx/salframe.h>
+#else
+#include <CoreGraphics/CoreGraphics.h>
+#include <CoreText/CoreText.h>
+#endif
+#include <postmac.h>
+
+#ifdef IOS
+// iOS defines a different Point class so include salgeom.hxx after postmac.h
+// so that it will use the Point class in tools/gen.hxx
+#include "salgeom.hxx"
+#endif
+
+#include <vcl/fontcapabilities.hxx>
+#include <vcl/metric.hxx>
+
+
+#include <font/LogicalFontInstance.hxx>
+#include <font/FontMetricData.hxx>
+#include <salgdi.hxx>
+
+#include <quartz/salgdicommon.hxx>
+
+#include <quartz/CGHelpers.hxx>
+
+class AquaSalFrame;
+class XorEmulation;
+class CoreTextFont;
+
+namespace sal::aqua
+{
+#ifdef MACOSX
+NSRect getTotalScreenBounds();
+void resetTotalScreenBounds();
+#endif
+float getWindowScaling();
+void resetWindowScaling();
+}
+
+struct AquaSharedAttributes
+{
+ /// path representing current clip region
+ CGMutablePathRef mxClipPath;
+
+ /// Drawing colors
+ /// pen color RGBA
+ RGBAColor maLineColor;
+
+ /// brush color RGBA
+ RGBAColor maFillColor;
+
+ // Graphics types
+#ifdef MACOSX
+ AquaSalFrame* mpFrame;
+ /// is this a window graphics
+ bool mbWindow;
+#else // IOS
+ // mirror AquaSalVirtualDevice::mbForeignContext for SvpSalGraphics objects related to such
+ bool mbForeignContext;
+#endif
+ /// is this a printer graphics
+ bool mbPrinter;
+ /// is this a virtual device graphics
+ bool mbVirDev;
+
+ CGLayerHolder maLayer; // Quartz graphics layer
+ CGContextHolder maContextHolder; // Quartz drawing context
+ CGContextHolder maBGContextHolder; // Quartz drawing context for CGLayer
+ CGContextHolder maCSContextHolder; // Quartz drawing context considering the color space
+ int mnWidth;
+ int mnHeight;
+ int mnXorMode; // 0: off 1: on 2: invert only
+ int mnBitmapDepth; // zero unless bitmap
+
+ Color maTextColor;
+ /// allows text to be rendered without antialiasing
+ bool mbNonAntialiasedText;
+
+ std::unique_ptr<XorEmulation> mpXorEmulation;
+
+ AquaSharedAttributes()
+ : mxClipPath(nullptr)
+ , maLineColor(COL_WHITE)
+ , maFillColor(COL_BLACK)
+#ifdef MACOSX
+ , mpFrame(nullptr)
+ , mbWindow(false)
+#else
+ , mbForeignContext(false)
+#endif
+ , mbPrinter(false)
+ , mbVirDev(false)
+ , mnWidth(0)
+ , mnHeight(0)
+ , mnXorMode(0)
+ , mnBitmapDepth(0)
+ , maTextColor( COL_BLACK )
+ , mbNonAntialiasedText( false )
+ {}
+
+ void unsetClipPath()
+ {
+ if (mxClipPath)
+ {
+ CGPathRelease(mxClipPath);
+ mxClipPath = nullptr;
+ }
+ }
+
+ void unsetState()
+ {
+ unsetClipPath();
+ }
+
+ bool checkContext();
+ void setState();
+
+ bool isPenVisible() const
+ {
+ return maLineColor.IsVisible();
+ }
+ bool isBrushVisible() const
+ {
+ return maFillColor.IsVisible();
+ }
+
+ void refreshRect(float lX, float lY, float lWidth, float lHeight)
+ {
+#ifdef MACOSX
+ if (!mbWindow) // view only on Window graphics
+ return;
+
+ if (mpFrame)
+ {
+ // update a little more around the designated rectangle
+ // this helps with antialiased rendering
+ // Rounding down x and width can accumulate a rounding error of up to 2
+ // The decrementing of x, the rounding error and the antialiasing border
+ // require that the width and the height need to be increased by four
+ const tools::Rectangle aVclRect(
+ Point(tools::Long(lX - 1), tools::Long(lY - 1)),
+ Size(tools::Long(lWidth + 4), tools::Long(lHeight + 4)));
+
+ mpFrame->maInvalidRect.Union(aVclRect);
+ }
+#else
+ (void) lX;
+ (void) lY;
+ (void) lWidth;
+ (void) lHeight;
+ return;
+#endif
+ }
+
+ // apply the XOR mask to the target context if active and dirty
+ void applyXorContext()
+ {
+ if (!mpXorEmulation)
+ return;
+ if (mpXorEmulation->UpdateTarget())
+ {
+ refreshRect(0, 0, mnWidth, mnHeight); // TODO: refresh minimal changerect
+ }
+ }
+
+ // differences between VCL, Quartz and kHiThemeOrientation coordinate systems
+ // make some graphics seem to be vertically-mirrored from a VCL perspective
+ bool isFlipped() const
+ {
+ #ifdef MACOSX
+ return mbWindow;
+ #else
+ return false;
+ #endif
+ }
+};
+
+class AquaGraphicsBackendBase
+{
+public:
+ virtual ~AquaGraphicsBackendBase() = 0;
+ AquaSharedAttributes& GetShared() { return mrShared; }
+ SalGraphicsImpl* GetImpl()
+ {
+ return mpImpl;
+ }
+ virtual void UpdateGeometryProvider(SalGeometryProvider*) {};
+ virtual bool drawNativeControl(ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle &rControlRegion,
+ ControlState nState,
+ const ImplControlValue &aValue) = 0;
+ virtual void drawTextLayout(const GenericSalLayout& layout) = 0;
+ virtual void Flush() {}
+ virtual void Flush( const tools::Rectangle& ) {}
+ virtual void WindowBackingPropertiesChanged() {};
+protected:
+ AquaGraphicsBackendBase(AquaSharedAttributes& rShared, SalGraphicsImpl * impl)
+ : mrShared( rShared ), mpImpl(impl)
+ {}
+ static bool performDrawNativeControl(ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle &rControlRegion,
+ ControlState nState,
+ const ImplControlValue &aValue,
+ CGContextRef context,
+ AquaSalFrame* mpFrame);
+ AquaSharedAttributes& mrShared;
+private:
+ SalGraphicsImpl* mpImpl;
+};
+
+inline AquaGraphicsBackendBase::~AquaGraphicsBackendBase() {}
+
+class AquaGraphicsBackend final : public SalGraphicsImpl, public AquaGraphicsBackendBase
+{
+private:
+ void drawPixelImpl( tools::Long nX, tools::Long nY, const RGBAColor& rColor); // helper to draw single pixels
+
+#ifdef MACOSX
+ void refreshRect(const NSRect& rRect)
+ {
+ mrShared.refreshRect(rRect.origin.x, rRect.origin.y, rRect.size.width, rRect.size.height);
+ }
+#else
+ void refreshRect(const CGRect& /*rRect*/)
+ {}
+#endif
+
+ void pattern50Fill();
+
+#ifdef MACOSX
+ void copyScaledArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight, AquaSharedAttributes* pSrcShared);
+#endif
+
+public:
+ AquaGraphicsBackend(AquaSharedAttributes & rShared);
+ ~AquaGraphicsBackend() override;
+
+ void Init() override;
+
+ void freeResources() override;
+
+ OUString getRenderBackendName() const override
+ {
+ return "aqua";
+ }
+
+ void setClipRegion(vcl::Region const& rRegion) override;
+ void ResetClipRegion() override;
+
+ sal_uInt16 GetBitCount() const override;
+
+ tools::Long GetGraphicsWidth() const override;
+
+ void SetLineColor() override;
+ void SetLineColor(Color nColor) override;
+ void SetFillColor() override;
+ void SetFillColor(Color nColor) override;
+ void SetXORMode(bool bSet, bool bInvertOnly) override;
+ void SetROPLineColor(SalROPColor nROPColor) override;
+ void SetROPFillColor(SalROPColor nROPColor) override;
+
+ void drawPixel(tools::Long nX, tools::Long nY) override;
+ void drawPixel(tools::Long nX, tools::Long nY, Color nColor) override;
+
+ void drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2) override;
+ void drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight) override;
+ void drawPolyLine(sal_uInt32 nPoints, const Point* pPointArray) override;
+ void drawPolygon(sal_uInt32 nPoints, const Point* pPointArray) override;
+ void drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point** pPointArray) override;
+
+ void drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon&, double fTransparency) override;
+
+ bool drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice, const basegfx::B2DPolygon&,
+ double fTransparency, double fLineWidth, const std::vector<double>* pStroke,
+ basegfx::B2DLineJoin, css::drawing::LineCap, double fMiterMinimumAngle,
+ bool bPixelSnapHairline) override;
+
+ bool drawPolyLineBezier(sal_uInt32 nPoints, const Point* pPointArray,
+ const PolyFlags* pFlagArray) override;
+
+ bool drawPolygonBezier(sal_uInt32 nPoints, const Point* pPointArray,
+ const PolyFlags* pFlagArray) override;
+
+ bool drawPolyPolygonBezier(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point* const* pPointArray,
+ const PolyFlags* const* pFlagArray) override;
+
+ void copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight, bool bWindowInvalidate) override;
+
+ void copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics) override;
+
+ void drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap) override;
+
+ void drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ const SalBitmap& rMaskBitmap) override;
+
+ void drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ Color nMaskColor) override;
+
+ std::shared_ptr<SalBitmap> getBitmap(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight) override;
+
+ Color getPixel(tools::Long nX, tools::Long nY) override;
+
+ void invert(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ SalInvert nFlags) override;
+
+ void invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags) override;
+
+ bool drawEPS(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ void* pPtr, sal_uInt32 nSize) override;
+
+ bool blendBitmap(const SalTwoRect&, const SalBitmap& rBitmap) override;
+
+ bool blendAlphaBitmap(const SalTwoRect&, const SalBitmap& rSrcBitmap,
+ const SalBitmap& rMaskBitmap, const SalBitmap& rAlphaBitmap) override;
+
+ bool drawAlphaBitmap(const SalTwoRect&, const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap) override;
+
+ bool drawTransformedBitmap(const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY, const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap, double fAlpha) override;
+
+ bool hasFastDrawTransformedBitmap() const override;
+
+ bool drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ sal_uInt8 nTransparency) override;
+
+ bool drawGradient(const tools::PolyPolygon& rPolygon, const Gradient& rGradient) override;
+ bool implDrawGradient(basegfx::B2DPolyPolygon const& rPolyPolygon,
+ SalGradient const& rGradient) override;
+
+ virtual bool drawNativeControl(ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle &rControlRegion,
+ ControlState nState,
+ const ImplControlValue &aValue) override;
+
+ virtual void drawTextLayout(const GenericSalLayout& layout) override;
+
+ bool supportsOperation(OutDevSupportType eType) const override;
+};
+
+class AquaSalGraphics : public SalGraphicsAutoDelegateToImpl
+{
+ AquaSharedAttributes maShared;
+ std::unique_ptr<AquaGraphicsBackendBase> mpBackend;
+
+ /// device resolution of this graphics
+ sal_Int32 mnRealDPIX;
+ sal_Int32 mnRealDPIY;
+
+ // Device Font settings
+ rtl::Reference<CoreTextFont> mpFont[MAX_FALLBACK];
+
+public:
+ AquaSalGraphics(bool bPrinter = false);
+ virtual ~AquaSalGraphics() override;
+
+ void SetVirDevGraphics(SalVirtualDevice* pVirDev,CGLayerHolder const &rLayer, CGContextRef, int nBitDepth = 0);
+#ifdef MACOSX
+ void initResolution( NSWindow* );
+ void copyResolution( AquaSalGraphics& );
+ void updateResolution();
+
+ void SetWindowGraphics( AquaSalFrame* pFrame );
+ bool IsWindowGraphics() const { return maShared.mbWindow; }
+ void SetPrinterGraphics(CGContextRef, sal_Int32 nRealDPIX, sal_Int32 nRealDPIY);
+ AquaSalFrame* getGraphicsFrame() const { return maShared.mpFrame; }
+ void setGraphicsFrame( AquaSalFrame* pFrame ) { maShared.mpFrame = pFrame; }
+#endif
+
+#ifdef MACOSX
+ void UpdateWindow( NSRect& ); // delivered in NSView coordinates
+ void RefreshRect(const NSRect& rRect)
+ {
+ maShared.refreshRect(rRect.origin.x, rRect.origin.y, rRect.size.width, rRect.size.height);
+ }
+#else
+ void RefreshRect( const CGRect& ) {}
+#endif
+
+ void Flush();
+ void Flush( const tools::Rectangle& );
+ void WindowBackingPropertiesChanged();
+
+ void UnsetState();
+ // InvalidateContext does an UnsetState and sets mrContext to 0
+ void InvalidateContext();
+
+ AquaGraphicsBackendBase* getAquaGraphicsBackend() const
+ {
+ return mpBackend.get();
+ }
+
+ virtual SalGraphicsImpl* GetImpl() const override;
+
+#ifdef MACOSX
+
+protected:
+
+ // native widget rendering methods that require mirroring
+
+ virtual bool isNativeControlSupported( ControlType nType, ControlPart nPart ) override;
+
+ virtual bool hitTestNativeControl( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion,
+ const Point& aPos, bool& rIsInside ) override;
+ virtual bool drawNativeControl( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion,
+ ControlState nState, const ImplControlValue& aValue,
+ const OUString& aCaption, const Color& rBackgroundColor ) override;
+ virtual bool getNativeControlRegion( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion, ControlState nState,
+ const ImplControlValue& aValue, const OUString& aCaption,
+ tools::Rectangle &rNativeBoundingRegion, tools::Rectangle &rNativeContentRegion ) override;
+#endif
+
+public:
+ // get device resolution
+ virtual void GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY ) override;
+ // set the text color to a specific color
+ virtual void SetTextColor( Color nColor ) override;
+ // set the font
+ virtual void SetFont( LogicalFontInstance*, int nFallbackLevel ) override;
+ // get the current font's metrics
+ virtual void GetFontMetric( FontMetricDataRef&, int nFallbackLevel ) override;
+ // get the repertoire of the current font
+ virtual FontCharMapRef GetFontCharMap() const override;
+ virtual bool GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const override;
+ // graphics must fill supplied font list
+ virtual void GetDevFontList( vcl::font::PhysicalFontCollection* ) override;
+ // graphics must drop any cached font info
+ virtual void ClearDevFontCache() override;
+ virtual bool AddTempDevFont( vcl::font::PhysicalFontCollection*, const OUString& rFileURL, const OUString& rFontName ) override;
+
+ virtual std::unique_ptr<GenericSalLayout>
+ GetTextLayout(int nFallbackLevel) override;
+ virtual void DrawTextLayout( const GenericSalLayout& ) override;
+
+ virtual SystemGraphicsData
+ GetGraphicsData() const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/quartz/salgdicommon.hxx b/vcl/inc/quartz/salgdicommon.hxx
new file mode 100644
index 0000000000..71c40acdfa
--- /dev/null
+++ b/vcl/inc/quartz/salgdicommon.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_QUARTZ_SALGDICOMMON_HXX
+#define INCLUDED_VCL_INC_QUARTZ_SALGDICOMMON_HXX
+
+#include <iostream>
+
+#include <premac.h>
+#ifdef IOS
+#include <CoreGraphics/CoreGraphics.h>
+#else
+#include <ApplicationServices/ApplicationServices.h>
+#endif
+#include <postmac.h>
+
+#include <tools/color.hxx>
+#include <vcl/salgtype.hxx>
+
+// abstracting quartz color instead of having to use a CGFloat[] array
+class RGBAColor
+{
+public:
+ RGBAColor( ::Color );
+ RGBAColor( float fRed, float fGreen, float fBlue, float fAlpha ); //NOTUSEDYET
+ const CGFloat* AsArray() const { return m_fRGBA; }
+ bool IsVisible() const { return m_fRGBA[3] > 0; }
+ void SetAlpha( float fAlpha ) { m_fRGBA[3] = fAlpha; }
+
+ CGFloat GetRed() const { return m_fRGBA[0]; }
+ CGFloat GetGreen() const { return m_fRGBA[1]; }
+ CGFloat GetBlue() const { return m_fRGBA[2]; }
+ CGFloat GetAlpha() const { return m_fRGBA[3]; }
+private:
+ CGFloat m_fRGBA[4]; // red, green, blue, alpha
+};
+
+inline RGBAColor::RGBAColor( ::Color nColor )
+{
+ m_fRGBA[0] = nColor.GetRed() * (1.0/255);
+ m_fRGBA[1] = nColor.GetGreen() * (1.0/255);
+ m_fRGBA[2] = nColor.GetBlue() * (1.0/255);
+ m_fRGBA[3] = 1.0; // opaque
+}
+
+inline RGBAColor::RGBAColor( float fRed, float fGreen, float fBlue, float fAlpha )
+{
+ m_fRGBA[0] = fRed;
+ m_fRGBA[1] = fGreen;
+ m_fRGBA[2] = fBlue;
+ m_fRGBA[3] = fAlpha;
+}
+
+inline std::ostream &operator <<(std::ostream& s, const RGBAColor &aColor)
+{
+#ifndef SAL_LOG_INFO
+ (void) aColor;
+#else
+ s << "{" << aColor.GetRed() << "," << aColor.GetGreen() << "," << aColor.GetBlue() << "," << aColor.GetAlpha() << "}";
+#endif
+ return s;
+}
+
+// XOR emulation suckage.
+// See http://www.openoffice.org/marketing/ooocon2008/programme/wednesday_1401.pdf
+// and https://bugs.freedesktop.org/show_bug.cgi?id=38844 .
+
+class XorEmulation
+{
+public:
+ XorEmulation();
+ ~XorEmulation();
+
+ void SetTarget( int nWidth, int nHeight, int nBitmapDepth, CGContextRef, CGLayerRef );
+ bool UpdateTarget();
+ void Enable() { m_bIsEnabled = true; }
+ void Disable() { m_bIsEnabled = false; }
+ bool IsEnabled() const { return m_bIsEnabled; }
+ CGContextRef GetTargetContext() const { return m_xTargetContext; }
+ CGContextRef GetMaskContext() const { return (m_bIsEnabled ? m_xMaskContext : nullptr); }
+
+private:
+ CGLayerRef m_xTargetLayer;
+ CGContextRef m_xTargetContext;
+ CGContextRef m_xMaskContext;
+ CGContextRef m_xTempContext;
+ sal_uLong* m_pMaskBuffer;
+ sal_uLong* m_pTempBuffer;
+ int m_nBufferLongs;
+ bool m_bIsEnabled;
+};
+
+#endif // INCLUDED_VCL_INC_QUARTZ_SALGDICOMMON_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/quartz/salvd.h b/vcl/inc/quartz/salvd.h
new file mode 100644
index 0000000000..939bd041ce
--- /dev/null
+++ b/vcl/inc/quartz/salvd.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_QUARTZ_SALVD_H
+#define INCLUDED_VCL_INC_QUARTZ_SALVD_H
+
+#include <premac.h>
+#ifdef MACOSX
+#include <ApplicationServices/ApplicationServices.h>
+#else
+#include <CoreGraphics/CoreGraphics.h>
+#endif
+#include <postmac.h>
+
+#include <tools/long.hxx>
+
+#include <quartz/salgdi.h>
+
+#include <salvd.hxx>
+
+class AquaSalGraphics;
+
+class AquaSalVirtualDevice : public SalVirtualDevice
+{
+private:
+ bool mbGraphicsUsed; // is Graphics used
+ bool mbForeignContext; // is mxContext from outside VCL
+ CGContextHolder maBitmapContext;
+ int mnBitmapDepth;
+ CGLayerHolder maLayer;
+ AquaSalGraphics* mpGraphics; // current VirDev graphics
+
+ tools::Long mnWidth;
+ tools::Long mnHeight;
+
+ void Destroy();
+
+public:
+ AquaSalVirtualDevice( AquaSalGraphics* pGraphic, tools::Long &nDX, tools::Long &nDY, DeviceFormat eFormat, const SystemGraphicsData *pData );
+ virtual ~AquaSalVirtualDevice() override;
+
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics( SalGraphics* pGraphics ) override;
+ virtual bool SetSize( tools::Long nNewDX, tools::Long nNewDY ) override;
+
+ tools::Long GetWidth() const override
+ {
+ return mnWidth;
+ }
+
+ tools::Long GetHeight() const override
+ {
+ return mnHeight;
+ }
+};
+
+#endif // INCLUDED_VCL_INC_QUARTZ_SALVD_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/quartz/utils.h b/vcl/inc/quartz/utils.h
new file mode 100644
index 0000000000..759e47f72f
--- /dev/null
+++ b/vcl/inc/quartz/utils.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_QUARTZ_UTILS_H
+#define INCLUDED_VCL_INC_QUARTZ_UTILS_H
+
+#include <iostream>
+
+#include <premac.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <Foundation/Foundation.h>
+#ifdef MACOSX
+#include <ApplicationServices/ApplicationServices.h>
+#else
+#include <CoreGraphics/CoreGraphics.h>
+#endif
+#include <postmac.h>
+
+#include <rtl/ustring.hxx>
+
+OUString GetOUString( CFStringRef );
+OUString GetOUString( const NSString* );
+CFStringRef CreateCFString( const OUString& );
+NSString* CreateNSString( const OUString& );
+OUString NSStringArrayToOUString(NSArray* array);
+OUString NSDictionaryKeysToOUString(NSDictionary* dict);
+
+std::ostream &operator <<(std::ostream& s, const CGRect &rRect);
+std::ostream &operator <<(std::ostream& s, const CGPoint &rPoint);
+std::ostream &operator <<(std::ostream& s, const CGSize &rSize);
+std::ostream &operator <<(std::ostream& s, CGColorRef pSize);
+std::ostream &operator <<(std::ostream& s, const CGAffineTransform &aXform);
+std::ostream &operator <<(std::ostream& s, CGColorSpaceRef cs);
+
+#endif // INCLUDED_VCL_INC_QUARTZ_UTILS_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/regband.hxx b/vcl/inc/regband.hxx
new file mode 100644
index 0000000000..33cedcd309
--- /dev/null
+++ b/vcl/inc/regband.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_REGBAND_HXX
+#define INCLUDED_VCL_INC_REGBAND_HXX
+
+#include <tools/long.hxx>
+
+/*
+
+class ImplRegionBand
+
+This class handles one y-band of the region. In this band may contain one
+or more separations in x-direction. The y-Band do not contain any
+separation after creation.
+
+The separations are modified with basic clipping functions like Union and
+Intersection - the Class will process the clipping for the actual band.
+
+*/
+
+// element for the list with x-separations
+struct ImplRegionBandSep
+{
+ ImplRegionBandSep* mpNextSep;
+ tools::Long mnXLeft;
+ tools::Long mnXRight;
+ bool mbRemoved;
+};
+
+enum class LineType { Ascending, Descending };
+
+// element for the list with x-separations
+struct ImplRegionBandPoint
+{
+ ImplRegionBandPoint* mpNextBandPoint;
+ tools::Long mnX;
+ tools::Long mnLineId;
+ bool mbEndPoint;
+ LineType meLineType;
+};
+
+class ImplRegionBand
+{
+public:
+ ImplRegionBand* mpNextBand; // pointer to the next element of the list
+ ImplRegionBand* mpPrevBand; // pointer to the previous element of the list (only used temporarily)
+ ImplRegionBandSep* mpFirstSep; // root of the list with x-separations
+ ImplRegionBandPoint* mpFirstBandPoint; // root of the list with lines
+ tools::Long mnYTop; // actual boundary of the band
+ tools::Long mnYBottom;
+
+ bool mbTouched : 1;
+
+ // create y-band with boundaries
+ ImplRegionBand( tools::Long nYTop, tools::Long nYBottom );
+ /** copy y-band with all data
+ @param theSourceBand
+ The new ImplRegionBand object will
+ be a copy of this band.
+ @param bIgnorePoints
+ When true (the default) the
+ band points pointed to by
+ mpFirstBandPoint are not copied.
+ When false they are copied.
+ You need the points when you are
+ planning to call ProcessPoints()
+ later on.
+ */
+ ImplRegionBand( const ImplRegionBand & theSourceBand,
+ const bool bIgnorePoints = true);
+ ~ImplRegionBand();
+
+ tools::Long GetXLeftBoundary() const;
+ tools::Long GetXRightBoundary() const;
+
+ // combine overlapping bands
+ void OptimizeBand();
+
+ // generate separations from lines and process
+ // union with existing separations
+ void ProcessPoints();
+ // insert point in the list for later processing
+ bool InsertPoint( tools::Long nX, tools::Long nLineID,
+ bool bEndPoint, LineType eLineType );
+
+ void Union( tools::Long nXLeft, tools::Long nXRight );
+ void Intersect( tools::Long nXLeft, tools::Long nXRight );
+ void Exclude( tools::Long nXLeft, tools::Long nXRight );
+ void XOr( tools::Long nXLeft, tools::Long nXRight );
+
+ void MoveX( tools::Long nHorzMove );
+ void ScaleX( double fHorzScale );
+
+ bool Contains( tools::Long nX );
+
+ bool IsEmpty() const { return ((!mpFirstSep) && (!mpFirstBandPoint)); }
+
+ bool operator==( const ImplRegionBand& rRegionBand ) const;
+
+ /** Split the called band at the given vertical coordinate. After the
+ split the called band will cover the upper part not including nY.
+ The new band will cover the lower part including nY.
+ @param nY
+ The band is split at this y coordinate. The new, lower band
+ will include this very value.
+ @return
+ Returns the new, lower band.
+ */
+ ImplRegionBand* SplitBand (const sal_Int32 nY);
+};
+
+#endif // INCLUDED_VCL_INC_REGBAND_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/regionband.hxx b/vcl/inc/regionband.hxx
new file mode 100644
index 0000000000..c25b5dd648
--- /dev/null
+++ b/vcl/inc/regionband.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_VCL_INC_REGIONBAND_HXX
+#define INCLUDED_VCL_INC_REGIONBAND_HXX
+
+#include <vcl/region.hxx>
+
+#include "regband.hxx"
+
+#ifdef DBG_UTIL
+const char* ImplDbgTestRegionBand(const void*);
+#endif
+
+class RegionBand
+{
+private:
+ friend const char* ImplDbgTestRegionBand(const void*);
+
+ ImplRegionBand* mpFirstBand; // root of the list with y-bands
+ ImplRegionBand* mpLastCheckedBand;
+
+ void implReset();
+ [[nodiscard]] bool CheckConsistency() const;
+
+public:
+ RegionBand();
+ RegionBand(const RegionBand&);
+ RegionBand& operator=(const RegionBand&);
+ RegionBand(const tools::Rectangle&);
+ ~RegionBand();
+
+ bool operator==(const RegionBand& rRegionBand) const;
+
+ [[nodiscard]] bool load(SvStream& rIStrm);
+ void save(SvStream& rIStrm) const;
+
+ bool isSingleRectangle() const;
+ ImplRegionBand* ImplGetFirstRegionBand() const { return mpFirstBand; }
+ void ImplAddMissingBands(const tools::Long nTop, const tools::Long nBottom);
+ void InsertBand(ImplRegionBand* pPreviousBand, ImplRegionBand* pBandToInsert);
+ void processPoints();
+ void CreateBandRange(tools::Long nYTop, tools::Long nYBottom);
+ void InsertLine(const Point& rStartPt, const Point& rEndPt, tools::Long nLineId);
+ void InsertPoint(const Point& rPoint, tools::Long nLineID, bool bEndPoint, LineType eLineType);
+ bool OptimizeBandList();
+ void Move(tools::Long nHorzMove, tools::Long nVertMove);
+ void Scale(double fScaleX, double fScaleY);
+ void InsertBands(tools::Long nTop, tools::Long nBottom);
+ static bool InsertSingleBand(ImplRegionBand* pBand, tools::Long nYBandPosition);
+ void Union(tools::Long nLeft, tools::Long nTop, tools::Long nRight, tools::Long nBottom);
+ void Intersect(tools::Long nLeft, tools::Long nTop, tools::Long nRight, tools::Long nBottom);
+ void Union(const RegionBand& rSource);
+ void Exclude(tools::Long nLeft, tools::Long nTop, tools::Long nRight, tools::Long nBottom);
+ void XOr(tools::Long nLeft, tools::Long nTop, tools::Long nRight, tools::Long nBottom);
+ void Intersect(const RegionBand& rSource);
+ bool Exclude(const RegionBand& rSource);
+ void XOr(const RegionBand& rSource);
+ tools::Rectangle GetBoundRect() const;
+ bool Contains(const Point& rPoint) const;
+ sal_uInt32 getRectangleCount()
+ const; // only users are Region::Intersect, Region::IsRectangle and PSWriter::ImplBmp
+ void GetRegionRectangles(RectangleVector& rTarget) const;
+};
+
+#endif // INCLUDED_VCL_INC_REGIONBAND_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/salbmp.hxx b/vcl/inc/salbmp.hxx
new file mode 100644
index 0000000000..3f9dafab15
--- /dev/null
+++ b/vcl/inc/salbmp.hxx
@@ -0,0 +1,199 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SALBMP_HXX
+#define INCLUDED_VCL_INC_SALBMP_HXX
+
+#include <tools/gen.hxx>
+#include <tools/solar.h>
+#include <vcl/checksum.hxx>
+#include <vcl/BitmapAccessMode.hxx>
+#include <vcl/BitmapBuffer.hxx>
+#include <vcl/bitmap/BitmapTypes.hxx>
+#include <com/sun/star/rendering/XBitmapCanvas.hpp>
+#include <basegfx/utils/systemdependentdata.hxx>
+
+#if defined MACOSX || defined IOS
+#include <premac.h>
+#include <CoreGraphics/CoreGraphics.h>
+#include <postmac.h>
+#endif
+
+struct BitmapBuffer;
+class Color;
+class SalGraphics;
+class BitmapPalette;
+struct BitmapSystemData;
+enum class BmpScaleFlag;
+
+extern const sal_uLong nVCLRLut[ 6 ];
+extern const sal_uLong nVCLGLut[ 6 ];
+extern const sal_uLong nVCLBLut[ 6 ];
+extern const sal_uLong nVCLDitherLut[ 256 ];
+extern const sal_uLong nVCLLut[ 256 ];
+
+class VCL_PLUGIN_PUBLIC SalBitmap
+{
+public:
+
+ SalBitmap()
+ : mnChecksum(0)
+ , mbChecksumValid(false)
+ {
+ }
+
+ virtual ~SalBitmap();
+
+ virtual bool Create( const Size& rSize,
+ vcl::PixelFormat ePixelFormat,
+ const BitmapPalette& rPal ) = 0;
+ virtual bool Create( const SalBitmap& rSalBmp ) = 0;
+ virtual bool Create( const SalBitmap& rSalBmp,
+ SalGraphics* pGraphics ) = 0;
+ virtual bool Create( const SalBitmap& rSalBmp,
+ vcl::PixelFormat eNewPixelFormat) = 0;
+ virtual bool Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& rBitmapCanvas,
+ Size& rSize,
+ bool bMask = false ) = 0;
+ virtual void Destroy() = 0;
+ virtual Size GetSize() const = 0;
+ virtual sal_uInt16 GetBitCount() const = 0;
+
+ virtual BitmapBuffer* AcquireBuffer( BitmapAccessMode nMode ) = 0;
+ virtual void ReleaseBuffer( BitmapBuffer* pBuffer, BitmapAccessMode nMode ) = 0;
+ virtual bool GetSystemData( BitmapSystemData& rData ) = 0;
+
+ virtual bool ScalingSupported() const = 0;
+ virtual bool Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag ) = 0;
+ void DropScaledCache();
+
+ virtual bool Replace( const Color& rSearchColor, const Color& rReplaceColor, sal_uInt8 nTol ) = 0;
+
+ virtual bool ConvertToGreyscale()
+ {
+ return false;
+ }
+ virtual bool InterpretAs8Bit()
+ {
+ return false;
+ }
+
+ virtual bool Erase( const Color& /*color*/ )
+ {
+ return false;
+ }
+ // Optimized case for AlphaMask::BlendWith().
+ virtual bool AlphaBlendWith( const SalBitmap& /*rSalBmp*/ )
+ {
+ return false;
+ }
+
+ virtual bool Invert()
+ {
+ return false;
+ }
+
+#if defined MACOSX || defined IOS
+ // Related: tdf#146842 Eliminate temporary copies of SkiaSalBitmap when
+ // printing
+ // Commit 9eb732a32023e74c44ac8c3b5af9f5424273bb6c fixed crashing when
+ // printing SkiaSalBitmaps to a non-Skia SalGraphics. However, the fix
+ // almost always makes two copies of the SkiaSalBitmap's bitmap data: the
+ // first copy is made in SkiaSalBitmap::AcquireBuffer() and then
+ // QuartzSalBitmap makes a copy of the first copy.
+ // By making QuartzSalBitmap's methods that return a CGImageRef virtual,
+ // a non-Skia SalGraphics can now create a CGImageRef directly from a
+ // SkiaSalBitmap's Skia bitmap data without copying to any intermediate
+ // buffers.
+ // Note: these methods are not pure virtual as the SvpSalBitmap class
+ // extends this class directly.
+ virtual CGImageRef CreateWithMask( const SalBitmap&, int, int, int, int ) const { return nullptr; }
+ virtual CGImageRef CreateColorMask( int, int, int, int, Color ) const { return nullptr; }
+ virtual CGImageRef CreateCroppedImage( int, int, int, int ) const { return nullptr; }
+#endif
+
+ BitmapChecksum GetChecksum() const
+ {
+ updateChecksum();
+ if (!mbChecksumValid)
+ return 0; // back-compat
+ return mnChecksum;
+ }
+
+ void InvalidateChecksum()
+ {
+ mbChecksumValid = false;
+ }
+
+protected:
+ void updateChecksum() const;
+ // helper function to convert data in 1,2,4 bpp formats to a 8/24/32bpp format
+ enum class BitConvert
+ {
+ A8,
+ RGBA,
+ BGRA,
+ LAST = BGRA
+ };
+ static std::unique_ptr< sal_uInt8[] > convertDataBitCount( const sal_uInt8* src,
+ int width, int height, int bitCount, int bytesPerRow, const BitmapPalette& palette,
+ BitConvert type );
+
+public:
+ // access to SystemDependentDataHolder, to support overload in derived class(es)
+ virtual const basegfx::SystemDependentDataHolder* accessSystemDependentDataHolder() const;
+
+ // exclusive management op's for SystemDependentData at SalBitmap
+ template<class T>
+ std::shared_ptr<T> getSystemDependentData() const
+ {
+ const basegfx::SystemDependentDataHolder* pDataHolder(accessSystemDependentDataHolder());
+ if(pDataHolder)
+ return std::static_pointer_cast<T>(pDataHolder->getSystemDependentData(typeid(T).hash_code()));
+ return std::shared_ptr<T>();
+ }
+
+ template<class T, class... Args>
+ std::shared_ptr<T> addOrReplaceSystemDependentData(Args&&... args) const
+ {
+ const basegfx::SystemDependentDataHolder* pDataHolder(accessSystemDependentDataHolder());
+ if(!pDataHolder)
+ return std::shared_ptr<T>();
+
+ std::shared_ptr<T> r = std::make_shared<T>(std::forward<Args>(args)...);
+
+ // tdf#129845 only add to buffer if a relevant buffer time is estimated
+ if(r->calculateCombinedHoldCyclesInSeconds() > 0)
+ {
+ basegfx::SystemDependentData_SharedPtr r2(r);
+ const_cast< basegfx::SystemDependentDataHolder* >(pDataHolder)->addOrReplaceSystemDependentData(r2);
+ }
+
+ return r;
+ }
+
+private:
+ BitmapChecksum mnChecksum;
+ bool mbChecksumValid;
+
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/salframe.hxx b/vcl/inc/salframe.hxx
new file mode 100644
index 0000000000..f25f8de927
--- /dev/null
+++ b/vcl/inc/salframe.hxx
@@ -0,0 +1,325 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SALFRAME_HXX
+#define INCLUDED_VCL_INC_SALFRAME_HXX
+
+#include "impdel.hxx"
+#include "salwtype.hxx"
+#include "salgeom.hxx"
+
+#include <vcl/help.hxx>
+#include <o3tl/typed_flags_set.hxx>
+
+#include <vcl/window.hxx>
+ // complete vcl::Window for SalFrame::CallCallback under -fsanitize=function
+
+class AllSettings;
+class SalGraphics;
+class SalBitmap;
+class SalMenu;
+
+namespace vcl { class WindowData; }
+struct SalInputContext;
+struct SystemEnvData;
+
+// SalFrame types
+enum class SalFrameToTop {
+ NONE = 0x00,
+ RestoreWhenMin = 0x01,
+ ForegroundTask = 0x02,
+ GrabFocus = 0x04,
+ GrabFocusOnly = 0x08
+};
+namespace o3tl {
+ template<> struct typed_flags<SalFrameToTop> : is_typed_flags<SalFrameToTop, 0x0f> {};
+};
+
+namespace vcl { class KeyCode; }
+
+namespace weld
+{
+ class Window;
+}
+
+enum class FloatWinPopupFlags;
+
+// SalFrame styles
+enum class SalFrameStyleFlags
+{
+ NONE = 0x00000000,
+ DEFAULT = 0x00000001,
+ MOVEABLE = 0x00000002,
+ SIZEABLE = 0x00000004,
+ CLOSEABLE = 0x00000008,
+ // no shadow effect on Windows XP
+ NOSHADOW = 0x00000010,
+ // indicate tooltip windows, so they can always be topmost
+ TOOLTIP = 0x00000020,
+ // windows without windowmanager decoration, this typically only applies to floating windows
+ OWNERDRAWDECORATION = 0x00000040,
+ // dialogs
+ DIALOG = 0x00000080,
+ // the window containing the intro bitmap, aka splashscreen
+ INTRO = 0x00000100,
+ // tdf#144624: don't set icon
+ NOICON = 0x01000000,
+ // system child window inside another SalFrame
+ SYSTEMCHILD = 0x08000000,
+ // plugged system child window
+ PLUG = 0x10000000,
+ // floating window
+ FLOAT = 0x20000000,
+ // toolwindows should be painted with a smaller decoration
+ TOOLWINDOW = 0x40000000,
+};
+
+namespace o3tl {
+ template<> struct typed_flags<SalFrameStyleFlags> : is_typed_flags<SalFrameStyleFlags, 0x798001ff> {};
+};
+
+// Extended frame style (sal equivalent to extended WinBits)
+typedef sal_uInt64 SalExtStyle;
+#define SAL_FRAME_EXT_STYLE_DOCUMENT SalExtStyle(0x00000001)
+#define SAL_FRAME_EXT_STYLE_DOCMODIFIED SalExtStyle(0x00000002)
+
+// Flags for SetPosSize
+#define SAL_FRAME_POSSIZE_X (sal_uInt16(0x0001))
+#define SAL_FRAME_POSSIZE_Y (sal_uInt16(0x0002))
+#define SAL_FRAME_POSSIZE_WIDTH (sal_uInt16(0x0004))
+#define SAL_FRAME_POSSIZE_HEIGHT (sal_uInt16(0x0008))
+
+struct SystemParentData;
+struct ImplSVEvent;
+
+/// A SalFrame is a system window (e.g. an X11 window).
+class VCL_PLUGIN_PUBLIC SalFrame
+ : public vcl::DeletionNotifier
+ , public SalGeometryProvider
+{
+private:
+ // the VCL window corresponding to this frame
+ VclPtr<vcl::Window> m_pWindow;
+ SALFRAMEPROC m_pProc;
+ Link<bool, void> m_aModalHierarchyHdl;
+protected:
+ mutable std::unique_ptr<weld::Window> m_xFrameWeld;
+public:
+ SalFrame();
+ virtual ~SalFrame() override;
+
+ SalFrameGeometry maGeometry; ///< absolute, unmirrored values
+
+ // SalGeometryProvider
+ virtual tools::Long GetWidth() const override { return maGeometry.width(); }
+ virtual tools::Long GetHeight() const override { return maGeometry.height(); }
+ virtual bool IsOffScreen() const override { return false; }
+
+ // SalGraphics or NULL, but two Graphics for all SalFrames
+ // must be returned
+ virtual SalGraphics* AcquireGraphics() = 0;
+ virtual void ReleaseGraphics( SalGraphics* pGraphics ) = 0;
+
+ // Event must be destroyed, when Frame is destroyed
+ // When Event is called, SalInstance::Yield() must be returned
+ virtual bool PostEvent(std::unique_ptr<ImplSVEvent> pData) = 0;
+
+ virtual void SetTitle( const OUString& rTitle ) = 0;
+ virtual void SetIcon( sal_uInt16 nIcon ) = 0;
+ virtual void SetRepresentedURL( const OUString& );
+ virtual void SetMenu( SalMenu *pSalMenu ) = 0;
+
+ virtual void SetExtendedFrameStyle( SalExtStyle nExtStyle ) = 0;
+
+ // Before the window is visible, a resize event
+ // must be sent with the correct size
+ virtual void Show( bool bVisible, bool bNoActivate = false ) = 0;
+
+ // Set ClientSize and Center the Window to the desktop
+ // and send/post a resize message
+ virtual void SetMinClientSize( tools::Long nWidth, tools::Long nHeight ) = 0;
+ virtual void SetMaxClientSize( tools::Long nWidth, tools::Long nHeight ) = 0;
+ virtual void SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags ) = 0;
+ static OUString DumpSetPosSize(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags);
+ virtual void GetClientSize( tools::Long& rWidth, tools::Long& rHeight ) = 0;
+ virtual void GetWorkArea( AbsoluteScreenPixelRectangle& rRect ) = 0;
+ virtual SalFrame* GetParent() const = 0;
+ // Note: x will be mirrored at parent if UI mirroring is active
+ SalFrameGeometry GetGeometry() const;
+ const SalFrameGeometry& GetUnmirroredGeometry() const { return maGeometry; }
+
+ virtual void SetWindowState(const vcl::WindowData*) = 0;
+ // return the absolute, unmirrored system frame state
+ // if this returns false the structure is uninitialised
+ [[nodiscard]]
+ virtual bool GetWindowState(vcl::WindowData*) = 0;
+ virtual void ShowFullScreen( bool bFullScreen, sal_Int32 nDisplay ) = 0;
+ virtual void PositionByToolkit( const tools::Rectangle&, FloatWinPopupFlags ) {};
+
+ // Enable/Disable ScreenSaver, SystemAgents, ...
+ virtual void StartPresentation( bool bStart ) = 0;
+ // Show Window over all other Windows
+ virtual void SetAlwaysOnTop( bool bOnTop ) = 0;
+
+ // Window to top and grab focus
+ virtual void ToTop( SalFrameToTop nFlags ) = 0;
+
+ // grab focus to the main widget, can be no-op if the vclplug only uses one widget
+ virtual void GrabFocus() {}
+
+ // this function can call with the same
+ // pointer style
+ virtual void SetPointer( PointerStyle ePointerStyle ) = 0;
+ virtual void CaptureMouse( bool bMouse ) = 0;
+ virtual void SetPointerPos( tools::Long nX, tools::Long nY ) = 0;
+
+ // flush output buffer
+ virtual void Flush() = 0;
+ virtual void Flush( const tools::Rectangle& );
+
+ virtual void SetInputContext( SalInputContext* pContext ) = 0;
+ virtual void EndExtTextInput( EndExtTextInputFlags nFlags ) = 0;
+
+ virtual OUString GetKeyName( sal_uInt16 nKeyCode ) = 0;
+
+ // returns in 'rKeyCode' the single keycode that translates to the given unicode when using a keyboard layout of language 'aLangType'
+ // returns false if no mapping exists or function not supported
+ // this is required for advanced menu support
+ virtual bool MapUnicodeToKeyCode( sal_Unicode aUnicode, LanguageType aLangType, vcl::KeyCode& rKeyCode ) = 0;
+
+ // returns the input language used for the last key stroke
+ // may be LANGUAGE_DONTKNOW if not supported by the OS
+ virtual LanguageType GetInputLanguage() = 0;
+
+ virtual void UpdateSettings( AllSettings& rSettings ) = 0;
+
+ virtual void Beep() = 0;
+
+ // returns system data (most prominent: window handle)
+ virtual const SystemEnvData*
+ GetSystemData() const = 0;
+
+ // tdf#139609 SystemEnvData::GetWindowHandle() calls this to on-demand fill the aWindow
+ // member of SystemEnvData for backends that want to defer doing that
+ virtual void ResolveWindowHandle(SystemEnvData& /*rData*/) const {};
+
+ // get current modifier, button mask and mouse position
+ struct SalPointerState
+ {
+ sal_Int32 mnState;
+ Point maPos; // in frame coordinates
+ };
+
+ virtual SalPointerState GetPointerState() = 0;
+
+ virtual KeyIndicatorState GetIndicatorState() = 0;
+
+ virtual void SimulateKeyPress( sal_uInt16 nKeyCode ) = 0;
+
+ // set new parent window
+ virtual void SetParent( SalFrame* pNewParent ) = 0;
+ // reparent window to act as a plugin; implementation
+ // may choose to use a new system window internally
+ // return false to indicate failure
+ virtual void SetPluginParent( SystemParentData* pNewParent ) = 0;
+
+ // move the frame to a new screen
+ virtual void SetScreenNumber( unsigned int nScreen ) = 0;
+
+ virtual void SetApplicationID( const OUString &rApplicationID) = 0;
+
+ // shaped system windows
+ // set clip region to none (-> rectangular windows, normal state)
+ virtual void ResetClipRegion() = 0;
+ // start setting the clipregion consisting of nRects rectangles
+ virtual void BeginSetClipRegion( sal_uInt32 nRects ) = 0;
+ // add a rectangle to the clip region
+ virtual void UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) = 0;
+ // done setting up the clipregion
+ virtual void EndSetClipRegion() = 0;
+
+ virtual void SetModal(bool /*bModal*/)
+ {
+ }
+
+ virtual bool GetModal() const
+ {
+ return false;
+ }
+
+ // return true to indicate tooltips are shown natively, false otherwise
+ virtual bool ShowTooltip(const OUString& /*rHelpText*/, const tools::Rectangle& /*rHelpArea*/)
+ {
+ return false;
+ }
+
+ // return !0 to indicate popovers are shown natively, 0 otherwise
+ virtual void* ShowPopover(const OUString& /*rHelpText*/, vcl::Window* /*pParent*/, const tools::Rectangle& /*rHelpArea*/, QuickHelpFlags /*nFlags*/)
+ {
+ return nullptr;
+ }
+
+ // return true to indicate popovers are shown natively, false otherwise
+ virtual bool UpdatePopover(void* /*nId*/, const OUString& /*rHelpText*/, vcl::Window* /*pParent*/, const tools::Rectangle& /*rHelpArea*/)
+ {
+ return false;
+ }
+
+ // return true to indicate popovers are shown natively, false otherwise
+ virtual bool HidePopover(void* /*nId*/)
+ {
+ return false;
+ }
+
+ virtual weld::Window* GetFrameWeld() const;
+
+ // Callbacks (independent part in vcl/source/window/winproc.cxx)
+ // for default message handling return 0
+ void SetCallback( vcl::Window* pWindow, SALFRAMEPROC pProc );
+
+ // returns the instance set
+ vcl::Window* GetWindow() const { return m_pWindow; }
+
+ void SetModalHierarchyHdl(const Link<bool, void>& rLink) { m_aModalHierarchyHdl = rLink; }
+ void NotifyModalHierarchy(bool bModal) { m_aModalHierarchyHdl.Call(bModal); }
+
+ virtual void UpdateDarkMode() {}
+ virtual bool GetUseDarkMode() const { return false; }
+ virtual bool GetUseReducedAnimation() const { return false; };
+
+ // Call the callback set; this sometimes necessary for implementation classes
+ // that should not know more than necessary about the SalFrame implementation
+ // (e.g. input methods, printer update handlers).
+ bool CallCallback( SalEvent nEvent, const void* pEvent ) const
+ { return m_pProc && m_pProc( m_pWindow, nEvent, pEvent ); }
+
+ // Helper method for input method handling: Calculate cursor index in (UTF-16) OUString,
+ // starting at nCursorIndex, moving number of characters (not UTF-16 codepoints) specified
+ // in nOffset, nChars.
+ static Selection CalcDeleteSurroundingSelection(const OUString& rSurroundingText,
+ sal_Int32 nCursorIndex, int nOffset, int nChars);
+};
+
+#ifdef _WIN32
+bool HasAtHook();
+#endif
+
+#endif // INCLUDED_VCL_INC_SALFRAME_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/salgdi.hxx b/vcl/inc/salgdi.hxx
new file mode 100644
index 0000000000..7e73e59daf
--- /dev/null
+++ b/vcl/inc/salgdi.hxx
@@ -0,0 +1,891 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <vcl/outdev.hxx>
+
+#include "font/FontMetricData.hxx"
+#include "salgdiimpl.hxx"
+#include "sallayout.hxx"
+#include "SalGradient.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include "WidgetDrawInterface.hxx"
+
+#include <config_cairo_canvas.h>
+
+#include <vector>
+
+class SalBitmap;
+class FontAttributes;
+namespace vcl::font {
+ class FontSelectPattern;
+ class PhysicalFontFace;
+ class PhysicalFontCollection;
+}
+class SalLayout;
+namespace tools { class Rectangle; }
+class OutputDevice;
+class FreetypeFont;
+struct SystemGraphicsData;
+
+namespace basegfx {
+ class B2DVector;
+ class B2DPolygon;
+ class B2DPolyPolygon;
+}
+
+namespace vcl
+{
+class AbstractTrueTypeFont;
+class FileDefinitionWidgetDraw;
+typedef struct TTGlobalFontInfo_ TTGlobalFontInfo;
+}
+
+typedef sal_Unicode sal_Ucs; // TODO: use sal_UCS4 instead of sal_Unicode
+
+// note: if you add any new methods to class SalGraphics using coordinates
+// make sure they have a corresponding protected pure virtual method
+// which has to be implemented by the platform dependent part.
+// Add a method that performs coordinate mirroring if required, (see
+// existing methods as sample) and then calls the equivalent pure method.
+
+// note: all positions are in pixel and relative to
+// the top/left-position of the virtual output area
+
+class VCL_PLUGIN_PUBLIC SalGraphics : protected vcl::WidgetDrawInterface
+{
+public:
+ SalGraphics();
+ ~SalGraphics() COVERITY_NOEXCEPT_FALSE override;
+
+ virtual SalGraphicsImpl* GetImpl() const = 0;
+
+ void setAntiAlias(bool bNew)
+ {
+ m_bAntiAlias = bNew;
+
+ // Temporary store in both
+ if (GetImpl())
+ GetImpl()->setAntiAlias(bNew);
+ }
+
+ bool getAntiAlias() const
+ {
+ return m_bAntiAlias;
+ }
+
+ // public SalGraphics methods, the interface to the independent vcl part
+
+ // get device resolution
+ virtual void GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY ) = 0;
+
+ // get the depth of the device
+ virtual sal_uInt16 GetBitCount() const = 0;
+
+ // get the width of the device
+ virtual tools::Long GetGraphicsWidth() const = 0;
+
+ // set the clip region to empty
+ virtual void ResetClipRegion() = 0;
+
+ // set the line color to transparent (= don't draw lines)
+
+ virtual void SetLineColor() = 0;
+
+ // set the line color to a specific color
+ virtual void SetLineColor( Color nColor ) = 0;
+
+ // set the fill color to transparent (= don't fill)
+ virtual void SetFillColor() = 0;
+
+ // set the fill color to a specific color, shapes will be
+ // filled accordingly
+ virtual void SetFillColor( Color nColor ) = 0;
+
+ // enable/disable XOR drawing
+ virtual void SetXORMode( bool bSet, bool bInvertOnly ) = 0;
+
+ // set line color for raster operations
+ virtual void SetROPLineColor( SalROPColor nROPColor ) = 0;
+
+ // set fill color for raster operations
+ virtual void SetROPFillColor( SalROPColor nROPColor ) = 0;
+
+ // set the text color to a specific color
+ virtual void SetTextColor( Color nColor ) = 0;
+
+ // set the font
+ virtual void SetFont(LogicalFontInstance*, int nFallbackLevel) = 0;
+
+ // release the fonts
+ void ReleaseFonts() { SetFont( nullptr, 0 ); }
+
+ // get the current font's metrics
+ virtual void GetFontMetric( FontMetricDataRef&, int nFallbackLevel ) = 0;
+
+ // get the repertoire of the current font
+ virtual FontCharMapRef GetFontCharMap() const = 0;
+
+ // get the layout capabilities of the current font
+ virtual bool GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const = 0;
+
+ // graphics must fill supplied font list
+ virtual void GetDevFontList( vcl::font::PhysicalFontCollection* ) = 0;
+
+ // graphics must drop any cached font info
+ virtual void ClearDevFontCache() = 0;
+
+ virtual bool AddTempDevFont(
+ vcl::font::PhysicalFontCollection*,
+ const OUString& rFileURL,
+ const OUString& rFontName ) = 0;
+
+ virtual std::unique_ptr<GenericSalLayout>
+ GetTextLayout(int nFallbackLevel) = 0;
+ virtual void DrawTextLayout( const GenericSalLayout& ) = 0;
+
+ virtual bool supportsOperation( OutDevSupportType ) const = 0;
+
+ // mirroring specifics
+ SalLayoutFlags GetLayout() const { return m_nLayout; }
+ void SetLayout( SalLayoutFlags aLayout ) { m_nLayout = aLayout;}
+
+ void mirror( tools::Long& nX, const OutputDevice& rOutDev ) const;
+ // only called mirror2 to avoid ambiguity
+ [[nodiscard]] tools::Long mirror2( tools::Long nX, const OutputDevice& rOutDev ) const;
+ void mirror( tools::Long& nX, tools::Long nWidth, const OutputDevice& rOutDev, bool bBack = false ) const;
+ bool mirror( sal_uInt32 nPoints, const Point *pPtAry, Point *pPtAry2, const OutputDevice& rOutDev ) const;
+ void mirror( tools::Rectangle& rRect, const OutputDevice&, bool bBack = false ) const;
+ void mirror( vcl::Region& rRgn, const OutputDevice& rOutDev ) const;
+ void mirror( ImplControlValue&, const OutputDevice& ) const;
+ basegfx::B2DPolyPolygon mirror( const basegfx::B2DPolyPolygon& i_rPoly, const OutputDevice& rOutDev ) const;
+ const basegfx::B2DHomMatrix& getMirror( const OutputDevice& rOutDev ) const;
+
+ // non virtual methods; these do possible coordinate mirroring and
+ // then delegate to protected virtual methods
+ void SetClipRegion( const vcl::Region&, const OutputDevice& rOutDev );
+
+ // draw --> LineColor and FillColor and RasterOp and ClipRegion
+ void DrawPixel( tools::Long nX, tools::Long nY, const OutputDevice& rOutDev );
+ void DrawPixel( tools::Long nX, tools::Long nY, Color nColor, const OutputDevice& rOutDev );
+
+ void DrawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2, const OutputDevice& rOutDev );
+
+ void DrawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, const OutputDevice& rOutDev );
+
+ void DrawPolyLine( sal_uInt32 nPoints, Point const * pPtAry, const OutputDevice& rOutDev );
+
+ void DrawPolygon( sal_uInt32 nPoints, const Point* pPtAry, const OutputDevice& rOutDev );
+
+ void DrawPolyPolygon(
+ sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const Point** pPtAry,
+ const OutputDevice& rOutDev );
+
+ void DrawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon &i_rPolyPolygon,
+ double i_fTransparency,
+ const OutputDevice& i_rOutDev);
+
+ bool DrawPolyLine(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& i_rPolygon,
+ double i_fTransparency,
+ double i_fLineWidth,
+ 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);
+
+ bool DrawPolyLineBezier(
+ sal_uInt32 nPoints,
+ const Point* pPtAry,
+ const PolyFlags* pFlgAry,
+ const OutputDevice& rOutDev );
+
+ bool DrawPolygonBezier(
+ sal_uInt32 nPoints,
+ const Point* pPtAry,
+ const PolyFlags* pFlgAry,
+ const OutputDevice& rOutDev );
+
+ bool DrawPolyPolygonBezier(
+ sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const Point* const* pPtAry,
+ const PolyFlags* const* pFlgAry,
+ const OutputDevice& rOutDev );
+
+ bool DrawGradient(
+ const tools::PolyPolygon& rPolyPoly,
+ const Gradient& rGradient,
+ const OutputDevice& rOutDev);
+
+ // CopyArea --> No RasterOp, but ClipRegion
+ void CopyArea(
+ tools::Long nDestX, tools::Long nDestY,
+ tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight,
+ const OutputDevice& rOutDev );
+
+ // CopyBits --> RasterOp and ClipRegion
+ // CopyBits() CopyBits on same Graphics
+ void CopyBits(
+ const SalTwoRect& rPosAry,
+ const OutputDevice& rOutDev);
+
+ // CopyBits --> RasterOp and ClipRegion
+ // CopyBits() CopyBits on different Graphics
+ void CopyBits(
+ const SalTwoRect& rPosAry,
+ SalGraphics& rSrcGraphics,
+ const OutputDevice& rOutDev,
+ const OutputDevice& rSrcOutDev );
+
+
+ void DrawBitmap(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const OutputDevice& rOutDev );
+
+ void DrawBitmap(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransparentBitmap,
+ const OutputDevice& rOutDev );
+
+ void DrawMask(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ Color nMaskColor,
+ const OutputDevice& rOutDev );
+
+ std::shared_ptr<SalBitmap> GetBitmap(
+ tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ const OutputDevice& rOutDev );
+
+ Color GetPixel(
+ tools::Long nX, tools::Long nY,
+ const OutputDevice& rOutDev );
+
+ // invert --> ClipRegion (only Windows)
+ void Invert(
+ tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ SalInvert nFlags,
+ const OutputDevice& rOutDev );
+
+ void Invert(
+ sal_uInt32 nPoints,
+ const Point* pPtAry,
+ SalInvert nFlags,
+ const OutputDevice& rOutDev );
+
+ bool DrawEPS(
+ tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ void* pPtr,
+ sal_uInt32 nSize,
+ const OutputDevice& rOutDev );
+
+ // native widget rendering functions
+
+ /**
+ * @see WidgetDrawInterface::isNativeControlSupported
+ */
+ inline bool IsNativeControlSupported(ControlType, ControlPart);
+
+ /**
+ * @see WidgetDrawInterface::hitTestNativeControl
+ */
+ bool HitTestNativeScrollbar(
+ ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ const Point& aPos,
+ bool& rIsInside,
+ const OutputDevice& rOutDev);
+
+ /**
+ * @see WidgetDrawInterface::drawNativeControl
+ */
+ bool DrawNativeControl(
+ ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ ControlState nState,
+ const ImplControlValue& aValue,
+ const OUString& aCaption,
+ const OutputDevice& rOutDev,
+ const Color& rBackgroundColor = COL_AUTO );
+
+ /**
+ * @see WidgetDrawInterface::getNativeControlRegion
+ */
+ bool GetNativeControlRegion(
+ ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ ControlState nState,
+ const ImplControlValue& aValue,
+ tools::Rectangle &rNativeBoundingRegion,
+ tools::Rectangle &rNativeContentRegion,
+ const OutputDevice& rOutDev );
+
+ /**
+ * @see WidgetDrawInterface::updateSettings
+ */
+ inline bool UpdateSettings(AllSettings&);
+
+ bool BlendBitmap(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const OutputDevice& rOutDev );
+
+ bool BlendAlphaBitmap(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalSrcBitmap,
+ const SalBitmap& rSalMaskBitmap,
+ const SalBitmap& rSalAlphaBitmap,
+ const OutputDevice& rOutDev );
+
+ bool DrawAlphaBitmap(
+ const SalTwoRect&,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap,
+ const OutputDevice& rOutDev );
+
+ bool DrawTransformedBitmap(
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap,
+ double fAlpha,
+ const OutputDevice& rOutDev );
+
+ bool HasFastDrawTransformedBitmap() const;
+
+ bool DrawAlphaRect(
+ tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ sal_uInt8 nTransparency,
+ const OutputDevice& rOutDev );
+
+ virtual OUString getRenderBackendName() const;
+
+ virtual SystemGraphicsData GetGraphicsData() const = 0;
+
+ // Backends like the svp/gtk ones use cairo and hidpi scale at the surface
+ // but bitmaps aren't hidpi, so if this returns true for the case that the
+ // surface is hidpi then pScaleOut contains the scaling factor. So we can
+ // create larger hires bitmaps which we know will be logically scaled down
+ // by this factor but physically just copied
+ virtual bool ShouldDownscaleIconsAtSurface(double* pScaleOut) const;
+
+
+#if ENABLE_CAIRO_CANVAS
+
+ /// Check whether cairo will work
+ virtual bool SupportsCairo() const = 0;
+ /// Create Surface from given cairo surface
+ virtual cairo::SurfaceSharedPtr CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const = 0;
+ /// Create surface with given dimensions
+ virtual cairo::SurfaceSharedPtr CreateSurface(const OutputDevice& rRefDevice, int x, int y, int width, int height) const = 0;
+ /// Create Surface for given bitmap data
+ virtual cairo::SurfaceSharedPtr CreateBitmapSurface(const OutputDevice& rRefDevice, const BitmapSystemData& rData, const Size& rSize) const = 0;
+ virtual css::uno::Any GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& rSurface, const basegfx::B2ISize& rSize) const = 0;
+
+#endif // ENABLE_CAIRO_CANVAS
+
+protected:
+
+ friend class vcl::FileDefinitionWidgetDraw;
+
+ virtual void setClipRegion( const vcl::Region& ) = 0;
+
+ // draw --> LineColor and FillColor and RasterOp and ClipRegion
+ virtual void drawPixel( tools::Long nX, tools::Long nY ) = 0;
+ virtual void drawPixel( tools::Long nX, tools::Long nY, Color nColor ) = 0;
+
+ virtual void drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 ) = 0;
+
+ virtual void drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) = 0;
+
+ virtual void drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry ) = 0;
+
+ virtual void drawPolygon( sal_uInt32 nPoints, const Point* pPtAry ) = 0;
+
+ virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, const Point** pPtAry ) = 0;
+
+ virtual void drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon&,
+ double fTransparency) = 0;
+
+ virtual bool drawPolyLine(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon&,
+ double fTransparency,
+ double fLineWidth,
+ const std::vector< double >* pStroke, // MM01
+ basegfx::B2DLineJoin,
+ css::drawing::LineCap,
+ double fMiterMinimumAngle,
+ bool bPixelSnapHairline) = 0;
+
+ virtual bool drawPolyLineBezier(
+ sal_uInt32 nPoints,
+ const Point* pPtAry,
+ const PolyFlags* pFlgAry ) = 0;
+
+ virtual bool drawPolygonBezier(
+ sal_uInt32 nPoints,
+ const Point* pPtAry,
+ const PolyFlags* pFlgAry ) = 0;
+
+ virtual bool drawPolyPolygonBezier(
+ sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const Point* const* pPtAry,
+ const PolyFlags* const* pFlgAry ) = 0;
+
+ virtual bool drawGradient(
+ const tools::PolyPolygon& rPolyPoly,
+ const Gradient& rGradient ) = 0;
+
+ virtual bool implDrawGradient(basegfx::B2DPolyPolygon const & /*rPolyPolygon*/,
+ SalGradient const & /*rGradient*/)
+ {
+ return false;
+ }
+
+ // CopyArea --> No RasterOp, but ClipRegion
+ virtual void copyArea(
+ tools::Long nDestX, tools::Long nDestY,
+ tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight,
+ bool bWindowInvalidate ) = 0;
+
+ // CopyBits and DrawBitmap --> RasterOp and ClipRegion
+ // CopyBits() --> pSrcGraphics == NULL, then CopyBits on same Graphics
+ virtual void copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) = 0;
+
+ virtual void drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap ) = 0;
+
+ virtual void drawBitmap(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rMaskBitmap ) = 0;
+
+ virtual void drawMask(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ Color nMaskColor ) = 0;
+
+ virtual std::shared_ptr<SalBitmap> getBitmap( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) = 0;
+
+ virtual Color getPixel( tools::Long nX, tools::Long nY ) = 0;
+
+ // invert --> ClipRegion (only Windows or VirDevs)
+ virtual void invert(
+ tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ SalInvert nFlags) = 0;
+
+ virtual void invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags ) = 0;
+
+ virtual bool drawEPS(
+ tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ void* pPtr,
+ sal_uInt32 nSize ) = 0;
+
+ /** Blend the bitmap with the current buffer */
+ virtual bool blendBitmap(
+ const SalTwoRect&,
+ const SalBitmap& rBitmap ) = 0;
+
+ /** Draw the bitmap by blending using the mask and alpha channel */
+ virtual bool blendAlphaBitmap(
+ const SalTwoRect&,
+ const SalBitmap& rSrcBitmap,
+ const SalBitmap& rMaskBitmap,
+ const SalBitmap& rAlphaBitmap ) = 0;
+
+ /** Render bitmap with alpha channel
+
+ @param rSourceBitmap
+ Source bitmap to blit
+
+ @param rAlphaBitmap
+ Alpha channel to use for blitting
+
+ @return true, if the operation succeeded, and false
+ otherwise. In this case, clients should try to emulate alpha
+ compositing themselves
+ */
+ virtual bool drawAlphaBitmap(
+ const SalTwoRect&,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap ) = 0;
+
+ /** draw transformed bitmap (maybe with alpha) where Null, X, Y define the coordinate system
+
+ @param fAlpha additional alpha (0 to 1) to apply while drawing
+ */
+ virtual bool drawTransformedBitmap(
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap,
+ double fAlpha) = 0;
+
+ /// Returns true if the drawTransformedBitmap() call is fast, and so it should
+ /// be used directly without trying to optimize some calls e.g. by calling drawBitmap()
+ /// instead (which is faster for most VCL backends). These optimizations are not
+ /// done unconditionally because they may be counter-productive for some fast VCL backends
+ /// (for example, some OutputDevice optimizations could try access the pixels, which
+ /// would make performance worse for GPU-backed backends).
+ /// See also tdf#138068.
+ virtual bool hasFastDrawTransformedBitmap() const = 0;
+
+ /** Render solid rectangle with given transparency
+ *
+ * @param nX Top left coordinate of rectangle
+ * @param nY Bottom right coordinate of rectangle
+ * @param nWidth Width of rectangle
+ * @param nHeight Height of rectangle
+ * @param nTransparency Transparency value (0-255) to use. 0 blits and opaque, 255 a
+ * fully transparent rectangle
+ * @returns true if successfully drawn, false if not able to draw rectangle
+ */
+ virtual bool drawAlphaRect(
+ tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ sal_uInt8 nTransparency ) = 0;
+
+private:
+ SalLayoutFlags m_nLayout; //< 0: mirroring off, 1: mirror x-axis
+
+ // for buffering the Mirror-Matrix, see ::getMirror
+ enum class MirrorMode
+ {
+ NONE,
+ Antiparallel,
+ AntiparallelBiDi,
+ BiDi
+ };
+ MirrorMode m_eLastMirrorMode;
+ tools::Long m_nLastMirrorTranslation;
+ basegfx::B2DHomMatrix m_aLastMirror;
+
+ MirrorMode GetMirrorMode(const OutputDevice& rOutDev) const;
+
+protected:
+ /// flags which hold the SetAntialiasing() value from OutputDevice
+ bool m_bAntiAlias : 1;
+
+ inline tools::Long GetDeviceWidth(const OutputDevice& rOutDev) const;
+
+ /**
+ * Handle damage done by drawing with a widget draw override
+ *
+ * If a m_pWidgetDraw is set and successfully draws using drawNativeControl,
+ * this function is called to handle the damage done to the graphics buffer.
+ *
+ * @param rDamagedRegion the region damaged by drawNativeControl.
+ **/
+ virtual inline void handleDamage(const tools::Rectangle& rDamagedRegion);
+
+ // native controls
+ bool initWidgetDrawBackends(bool bForce = false);
+
+ std::unique_ptr<vcl::WidgetDrawInterface> m_pWidgetDraw;
+ vcl::WidgetDrawInterface* forWidget() { return m_pWidgetDraw ? m_pWidgetDraw.get() : this; }
+};
+
+bool SalGraphics::IsNativeControlSupported(ControlType eType, ControlPart ePart)
+{
+ return forWidget()->isNativeControlSupported(eType, ePart);
+}
+
+bool SalGraphics::UpdateSettings(AllSettings& rSettings)
+{
+ return forWidget()->updateSettings(rSettings);
+}
+
+void SalGraphics::handleDamage(const tools::Rectangle&) {}
+
+
+class VCL_DLLPUBLIC SalGraphicsAutoDelegateToImpl : public SalGraphics
+{
+public:
+ sal_uInt16 GetBitCount() const override
+ {
+ return GetImpl()->GetBitCount();
+ }
+
+ tools::Long GetGraphicsWidth() const override
+ {
+ return GetImpl()->GetGraphicsWidth();
+ }
+
+ void ResetClipRegion() override
+ {
+ GetImpl()->ResetClipRegion();
+ }
+
+ void setClipRegion( const vcl::Region& i_rClip ) override
+ {
+ GetImpl()->setClipRegion(i_rClip);
+ }
+
+ void SetLineColor() override
+ {
+ GetImpl()->SetLineColor();
+ }
+
+ void SetLineColor( Color nColor ) override
+ {
+ GetImpl()->SetLineColor(nColor);
+ }
+
+ void SetFillColor() override
+ {
+ GetImpl()->SetFillColor();
+ }
+
+ void SetFillColor( Color nColor ) override
+ {
+ GetImpl()->SetFillColor (nColor);
+ }
+
+ void SetROPLineColor(SalROPColor aColor) override
+ {
+ GetImpl()->SetROPLineColor(aColor);
+ }
+
+ void SetROPFillColor( SalROPColor aColor) override
+ {
+ GetImpl()->SetROPFillColor(aColor);
+ }
+
+ void SetXORMode(bool bSet, bool bInvertOnly) override
+ {
+ GetImpl()->SetXORMode(bSet, bInvertOnly);
+ }
+
+ void drawPixel( tools::Long nX, tools::Long nY ) override
+ {
+ GetImpl()->drawPixel(nX, nY);
+ }
+
+ void drawPixel( tools::Long nX, tools::Long nY, Color nColor ) override
+ {
+ GetImpl()->drawPixel(nX, nY, nColor);
+ }
+
+ void drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 ) override
+ {
+ GetImpl()->drawLine(nX1, nY1, nX2, nY2);
+ }
+
+ void drawRect( tools::Long nX, tools::Long nY, tools::Long nDX, tools::Long nDY ) override
+ {
+ GetImpl()->drawRect(nX, nY, nDX, nDY);
+ }
+
+ void drawPolyLine( sal_uInt32 nPoints, const Point *pPtAry ) override
+ {
+ GetImpl()->drawPolyLine(nPoints, pPtAry);
+ }
+
+ void drawPolygon( sal_uInt32 nPoints, const Point* pPtAry ) override
+ {
+ GetImpl()->drawPolygon(nPoints, pPtAry);
+ }
+
+ void drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints, const Point** pPtAry) override
+ {
+ GetImpl()->drawPolyPolygon (nPoly, pPoints, pPtAry);
+ }
+
+ void drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency) override
+ {
+ GetImpl()->drawPolyPolygon(rObjectToDevice, rPolyPolygon, fTransparency);
+ }
+
+ bool drawPolyLine(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& rPolygon,
+ double fTransparency,
+ double fLineWidth,
+ const std::vector< double >* pStroke,
+ basegfx::B2DLineJoin eJoin,
+ css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle,
+ bool bPixelSnapHairline) override
+ {
+ return GetImpl()->drawPolyLine(rObjectToDevice, rPolygon, fTransparency, fLineWidth, pStroke, eJoin, eLineCap, fMiterMinimumAngle, bPixelSnapHairline);
+ }
+
+ bool drawPolyLineBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry ) override
+ {
+ return GetImpl()->drawPolyLineBezier(nPoints, pPtAry, pFlgAry);
+ }
+
+ bool drawPolygonBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry ) override
+ {
+ return GetImpl()->drawPolygonBezier(nPoints, pPtAry, pFlgAry);
+ }
+
+ bool drawPolyPolygonBezier( sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const Point* const* pPtAry,
+ const PolyFlags* const* pFlgAry) override
+ {
+ return GetImpl()->drawPolyPolygonBezier(nPoly, pPoints, pPtAry, pFlgAry);
+ }
+
+ void invert(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ SalInvert nFlags) override
+ {
+ GetImpl()->invert(nX, nY, nWidth, nHeight, nFlags);
+ }
+
+ void invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags) override
+ {
+ GetImpl()->invert(nPoints, pPtAry, nFlags);
+ }
+
+ bool drawEPS(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, void* pPtr, sal_uInt32 nSize) override
+ {
+ return GetImpl()->drawEPS(nX, nY, nWidth, nHeight, pPtr, nSize);
+ }
+
+ void copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics) override
+ {
+ GetImpl()->copyBits(rPosAry, pSrcGraphics);
+ }
+
+ void copyArea (tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX,
+ tools::Long nSrcY, tools::Long nSrcWidth, tools::Long nSrcHeight,
+ bool bWindowInvalidate) override
+ {
+ GetImpl()->copyArea(nDestX, nDestY, nSrcX, nSrcY, nSrcWidth, nSrcHeight, bWindowInvalidate);
+ }
+
+ void drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap) override
+ {
+ GetImpl()->drawBitmap(rPosAry, rSalBitmap);
+ }
+
+ void drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap, const SalBitmap& rMaskBitmap) override
+ {
+ GetImpl()->drawBitmap(rPosAry, rSalBitmap, rMaskBitmap);
+ }
+
+ void drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap, Color nMaskColor) override
+ {
+ GetImpl()->drawMask(rPosAry, rSalBitmap, nMaskColor);
+ }
+
+ std::shared_ptr<SalBitmap> getBitmap(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight) override
+ {
+ return GetImpl()->getBitmap(nX, nY, nWidth, nHeight);
+ }
+
+ Color getPixel(tools::Long nX, tools::Long nY) override
+ {
+ return GetImpl()->getPixel(nX, nY);
+ }
+
+ bool blendBitmap(const SalTwoRect& rPosAry, const SalBitmap& rBitmap) override
+ {
+ return GetImpl()->blendBitmap(rPosAry, rBitmap);
+ }
+
+ bool blendAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSourceBitmap,
+ const SalBitmap& rMaskBitmap, const SalBitmap& rAlphaBitmap) override
+ {
+ return GetImpl()->blendAlphaBitmap(rPosAry, rSourceBitmap, rMaskBitmap, rAlphaBitmap);
+ }
+
+ bool drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap) override
+ {
+ return GetImpl()->drawAlphaBitmap(rPosAry, rSourceBitmap, rAlphaBitmap);
+ }
+
+ bool drawTransformedBitmap(const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap, double fAlpha) override
+ {
+ return GetImpl()->drawTransformedBitmap(rNull, rX, rY, rSourceBitmap, pAlphaBitmap, fAlpha);
+ }
+
+ bool hasFastDrawTransformedBitmap() const override
+ {
+ return GetImpl()->hasFastDrawTransformedBitmap();
+ }
+
+ bool drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, sal_uInt8 nTransparency) override
+ {
+ return GetImpl()->drawAlphaRect(nX, nY, nWidth, nHeight, nTransparency);
+ }
+
+ bool drawGradient(const tools::PolyPolygon& rPolygon, const Gradient& rGradient) override
+ {
+ return GetImpl()->drawGradient(rPolygon, rGradient);
+ }
+
+ bool implDrawGradient(basegfx::B2DPolyPolygon const& rPolyPolygon,
+ SalGradient const& rGradient) override
+ {
+ return GetImpl()->implDrawGradient(rPolyPolygon, rGradient);
+ }
+
+ bool supportsOperation(OutDevSupportType eType) const override
+ {
+ return GetImpl()->supportsOperation(eType);
+ }
+
+ OUString getRenderBackendName() const override
+ {
+ return GetImpl()->getRenderBackendName();
+ }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/salgdiimpl.hxx b/vcl/inc/salgdiimpl.hxx
new file mode 100644
index 0000000000..fd3a13378b
--- /dev/null
+++ b/vcl/inc/salgdiimpl.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SALGDIIMPL_HXX
+#define INCLUDED_VCL_INC_SALGDIIMPL_HXX
+
+#include <vcl/dllapi.h>
+
+#include <tools/color.hxx>
+#include <tools/poly.hxx>
+
+#include <vcl/salgtype.hxx>
+#include <vcl/region.hxx>
+#include <vcl/vclenum.hxx>
+
+#include <com/sun/star/drawing/LineCap.hpp>
+
+class SalGraphics;
+class SalBitmap;
+class SalFrame;
+class Gradient;
+class SalVirtualDevice;
+struct SalGradient;
+
+/**
+Implementation class for SalGraphics.
+
+This class allows having an implementation of drawing calls that is separate from SalGraphics,
+and SalGraphics can forward all such calls to SalGraphicsImpl. For example X11SalGraphics
+may internally use either Cairo-based X11CairoSalGraphicsImpl or Skia-based SkiaSalGraphicsImpl,
+and the latter may be used also by other SalGraphics implementations. All the functions
+here should be implementations of the relevant SalGraphics functions.
+*/
+class VCL_PLUGIN_PUBLIC SalGraphicsImpl
+{
+ bool m_bAntiAlias;
+public:
+
+ void setAntiAlias(bool bNew)
+ {
+ m_bAntiAlias = bNew;
+ }
+
+ bool getAntiAlias() const
+ {
+ return m_bAntiAlias;
+ }
+
+ SalGraphicsImpl()
+ : m_bAntiAlias(false)
+ {}
+
+ virtual ~SalGraphicsImpl();
+
+ // All the functions are implementations of functions from the SalGraphics class,
+ // so see the SalGraphics class for documentation (both uppercase and lowercase
+ // function variants).
+
+ virtual void Init() = 0;
+
+ virtual void DeInit() {}
+
+ virtual void freeResources() = 0;
+
+ virtual OUString getRenderBackendName() const = 0;
+
+ virtual void setClipRegion( const vcl::Region& ) = 0;
+
+ virtual sal_uInt16 GetBitCount() const = 0;
+
+ virtual tools::Long GetGraphicsWidth() const = 0;
+
+ virtual void ResetClipRegion() = 0;
+
+ virtual void SetLineColor() = 0;
+
+ virtual void SetLineColor( Color nColor ) = 0;
+
+ virtual void SetFillColor() = 0;
+
+ virtual void SetFillColor( Color nColor ) = 0;
+
+ virtual void SetXORMode( bool bSet, bool bInvertOnly ) = 0;
+
+ virtual void SetROPLineColor( SalROPColor nROPColor ) = 0;
+
+ virtual void SetROPFillColor( SalROPColor nROPColor ) = 0;
+
+ virtual void drawPixel( tools::Long nX, tools::Long nY ) = 0;
+ virtual void drawPixel( tools::Long nX, tools::Long nY, Color nColor ) = 0;
+
+ virtual void drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 ) = 0;
+
+ virtual void drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) = 0;
+
+ virtual void drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry ) = 0;
+
+ virtual void drawPolygon( sal_uInt32 nPoints, const Point* pPtAry ) = 0;
+
+ virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, const Point** pPtAry ) = 0;
+
+ virtual void drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon&,
+ double fTransparency) = 0;
+
+ virtual bool drawPolyLine(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon&,
+ double fTransparency,
+ double fLineWidth,
+ const std::vector< double >* pStroke,
+ basegfx::B2DLineJoin,
+ css::drawing::LineCap,
+ double fMiterMinimumAngle,
+ bool bPixelSnapHairline) = 0;
+
+ virtual bool drawPolyLineBezier(
+ sal_uInt32 nPoints,
+ const Point* pPtAry,
+ const PolyFlags* pFlgAry ) = 0;
+
+ virtual bool drawPolygonBezier(
+ sal_uInt32 nPoints,
+ const Point* pPtAry,
+ const PolyFlags* pFlgAry ) = 0;
+
+ virtual bool drawPolyPolygonBezier(
+ sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const Point* const* pPtAry,
+ const PolyFlags* const* pFlgAry ) = 0;
+
+ virtual void copyArea(
+ tools::Long nDestX, tools::Long nDestY,
+ tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight,
+ bool bWindowInvalidate ) = 0;
+
+ virtual void copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) = 0;
+
+ virtual void drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap ) = 0;
+
+ virtual void drawBitmap(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rMaskBitmap ) = 0;
+
+ virtual void drawMask(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ Color nMaskColor ) = 0;
+
+ virtual std::shared_ptr<SalBitmap> getBitmap( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) = 0;
+
+ virtual Color getPixel( tools::Long nX, tools::Long nY ) = 0;
+
+ virtual void invert(
+ tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ SalInvert nFlags) = 0;
+
+ virtual void invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags ) = 0;
+
+ virtual bool drawEPS(
+ tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ void* pPtr,
+ sal_uInt32 nSize ) = 0;
+
+ virtual bool blendBitmap(
+ const SalTwoRect&,
+ const SalBitmap& rBitmap ) = 0;
+
+ virtual bool blendAlphaBitmap(
+ const SalTwoRect&,
+ const SalBitmap& rSrcBitmap,
+ const SalBitmap& rMaskBitmap,
+ const SalBitmap& rAlphaBitmap ) = 0;
+
+ virtual bool drawAlphaBitmap(
+ const SalTwoRect&,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap ) = 0;
+
+ virtual bool drawTransformedBitmap(
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap,
+ double fAlpha) = 0;
+
+ virtual bool hasFastDrawTransformedBitmap() const = 0;
+
+ virtual bool drawAlphaRect(
+ tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ sal_uInt8 nTransparency ) = 0;
+
+ virtual bool drawGradient(const tools::PolyPolygon& rPolygon, const Gradient& rGradient) = 0;
+ virtual bool implDrawGradient(basegfx::B2DPolyPolygon const & rPolyPolygon, SalGradient const & rGradient) = 0;
+
+ virtual bool supportsOperation(OutDevSupportType eType) const = 0;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/salgeom.hxx b/vcl/inc/salgeom.hxx
new file mode 100644
index 0000000000..418b1cf375
--- /dev/null
+++ b/vcl/inc/salgeom.hxx
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SALGEOM_HXX
+#define INCLUDED_VCL_INC_SALGEOM_HXX
+
+#include <iostream>
+
+#include <vcl/dllapi.h>
+#include <vcl/WindowPosSize.hxx>
+#include <tools/long.hxx>
+
+// There are some unused functions, which I would keep to ease understanding.
+class SalFrameGeometry : public vcl::WindowPosSize
+{
+ // non-drawable area / margins / frame / decorations around the client area
+ sal_uInt32 m_nLeftDecoration, m_nTopDecoration, m_nRightDecoration, m_nBottomDecoration;
+ unsigned int m_nDisplayScreenNumber;
+
+public:
+ SalFrameGeometry()
+ : m_nLeftDecoration(0)
+ , m_nTopDecoration(0)
+ , m_nRightDecoration(0)
+ , m_nBottomDecoration(0)
+ , m_nDisplayScreenNumber(0)
+ {
+ }
+
+ constexpr sal_uInt32 leftDecoration() const { return m_nLeftDecoration; }
+ void setLeftDecoration(sal_uInt32 nLeftDecoration) { m_nLeftDecoration = nLeftDecoration; }
+ constexpr sal_uInt32 topDecoration() const { return m_nTopDecoration; }
+ void setTopDecoration(sal_uInt32 nTopDecoration) { m_nTopDecoration = nTopDecoration; }
+ constexpr sal_uInt32 rightDecoration() const { return m_nRightDecoration; }
+ void setRightDecoration(sal_uInt32 nRightDecoration) { m_nRightDecoration = nRightDecoration; }
+ constexpr sal_uInt32 bottomDecoration() const { return m_nBottomDecoration; }
+ void setBottomDecoration(sal_uInt32 nBottomDecoration)
+ {
+ m_nBottomDecoration = nBottomDecoration;
+ }
+ void setDecorations(sal_uInt32 nLeft, sal_uInt32 nTop, sal_uInt32 nRight, sal_uInt32 nBottom)
+ {
+ m_nLeftDecoration = nLeft;
+ m_nTopDecoration = nTop;
+ m_nRightDecoration = nRight;
+ m_nBottomDecoration = nBottom;
+ }
+
+ unsigned int screen() const { return m_nDisplayScreenNumber; }
+ void setScreen(unsigned int nScreen) { m_nDisplayScreenNumber = nScreen; }
+};
+
+inline std::ostream& operator<<(std::ostream& s, const SalFrameGeometry& rGeom)
+{
+ s << *static_cast<const vcl::WindowPosSize*>(&rGeom) << ":{" << rGeom.leftDecoration() << ","
+ << rGeom.topDecoration() << "," << rGeom.rightDecoration() << "," << rGeom.bottomDecoration()
+ << "}s" << rGeom.screen();
+ return s;
+}
+
+/// Interface used to share logic on sizing between
+/// SalVirtualDevices and SalFrames
+class VCL_PLUGIN_PUBLIC SalGeometryProvider
+{
+public:
+ virtual ~SalGeometryProvider() {}
+ virtual tools::Long GetWidth() const = 0;
+ virtual tools::Long GetHeight() const = 0;
+ virtual bool IsOffScreen() const = 0;
+};
+
+#endif // INCLUDED_VCL_INC_SALGEOM_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/salinst.hxx b/vcl/inc/salinst.hxx
new file mode 100644
index 0000000000..124118f4e6
--- /dev/null
+++ b/vcl/inc/salinst.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SALINST_HXX
+#define INCLUDED_VCL_INC_SALINST_HXX
+
+#include <sal/types.h>
+#include <rtl/ref.hxx>
+#include <vcl/dllapi.h>
+#include <vcl/salgtype.hxx>
+#include <vcl/vclenum.hxx>
+
+#include "displayconnectiondispatch.hxx"
+
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/ui/dialogs/XFilePicker2.hpp>
+#include <com/sun/star/ui/dialogs/XFolderPicker2.hpp>
+#include <memory>
+
+namespace com::sun::star::awt {
+ class XWindow;
+}
+namespace comphelper { class SolarMutex; }
+namespace vcl { class Window; }
+namespace weld {
+ class Builder;
+ class MessageDialog;
+ class Widget;
+ class Window;
+}
+class SystemChildWindow;
+struct SystemParentData;
+struct SalPrinterQueueInfo;
+class ImplJobSetup;
+class OpenGLContext;
+class SalGraphics;
+class SalFrame;
+class SalObject;
+class SalMenu;
+class SalMenuItem;
+class SalVirtualDevice;
+class SalInfoPrinter;
+class SalPrinter;
+class SalTimer;
+class ImplPrnQueueList;
+class SalSystem;
+class SalBitmap;
+struct SalItemParams;
+class SalSession;
+struct SystemEnvData;
+struct SystemGraphicsData;
+struct SystemWindowData;
+class Menu;
+enum class VclInputFlags;
+enum class SalFrameStyleFlags;
+
+typedef struct _cairo_font_options cairo_font_options_t;
+
+class VCL_DLLPUBLIC SalInstance
+{
+private:
+ rtl::Reference< vcl::DisplayConnectionDispatch > m_pEventInst;
+ const std::unique_ptr<comphelper::SolarMutex> m_pYieldMutex;
+ css::uno::Reference<css::uno::XInterface> m_clipboard;
+
+protected:
+ bool m_bSupportsBitmap32 = false;
+ bool m_bSupportsOpenGL = false;
+
+public:
+ SalInstance(std::unique_ptr<comphelper::SolarMutex> pMutex);
+ virtual ~SalInstance();
+
+ bool supportsBitmap32() const { return m_bSupportsBitmap32; }
+ bool supportsOpenGL() const { return m_bSupportsOpenGL; }
+
+ //called directly after Application::Init
+ virtual void AfterAppInit() {}
+ virtual bool SVMainHook(int*) { return false; }
+
+ // Frame
+ // DisplayName for Unix ???
+ virtual SalFrame* CreateChildFrame( SystemParentData* pParent, SalFrameStyleFlags nStyle ) = 0;
+ virtual SalFrame* CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle ) = 0;
+ virtual void DestroyFrame( SalFrame* pFrame ) = 0;
+
+ // Object (System Child Window)
+ virtual SalObject* CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow ) = 0;
+ virtual void DestroyObject( SalObject* pObject ) = 0;
+
+ // VirtualDevice
+ // nDX and nDY in pixels
+ // nBitCount: 0 == default(=as window) / 1 == mono
+ // pData allows for using a system dependent graphics or device context,
+ // if a system context is passed in nDX and nDY are updated to reflect
+ // its size; otherwise these remain unchanged.
+ virtual std::unique_ptr<SalVirtualDevice>
+ CreateVirtualDevice( SalGraphics& rGraphics,
+ tools::Long &rDX, tools::Long &rDY,
+ DeviceFormat eFormat, const SystemGraphicsData *pData = nullptr ) = 0;
+
+ // Printer
+ // pSetupData->mpDriverData can be 0
+ // pSetupData must be updated with the current
+ // JobSetup
+ virtual SalInfoPrinter* CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pSetupData ) = 0;
+ virtual void DestroyInfoPrinter( SalInfoPrinter* pPrinter ) = 0;
+ virtual std::unique_ptr<SalPrinter> CreatePrinter( SalInfoPrinter* pInfoPrinter ) = 0;
+
+ virtual void GetPrinterQueueInfo( ImplPrnQueueList* pList ) = 0;
+ virtual void GetPrinterQueueState( SalPrinterQueueInfo* pInfo ) = 0;
+ virtual OUString GetDefaultPrinter() = 0;
+
+ // SalTimer
+ virtual SalTimer* CreateSalTimer() = 0;
+ // SalSystem
+ virtual SalSystem* CreateSalSystem() = 0;
+ // SalBitmap
+ virtual std::shared_ptr<SalBitmap> CreateSalBitmap() = 0;
+
+ // YieldMutex
+ comphelper::SolarMutex* GetYieldMutex();
+ sal_uInt32 ReleaseYieldMutexAll();
+ void AcquireYieldMutex(sal_uInt32 nCount = 1);
+
+ // return true, if the current thread is the main thread
+ virtual bool IsMainThread() const = 0;
+
+ /**
+ * Wait for the next event (if bWait) and dispatch it,
+ * includes posted events, and timers.
+ * If bHandleAllCurrentEvents - dispatch multiple posted
+ * user events. Returns true if events were processed.
+ */
+ virtual bool DoYield(bool bWait, bool bHandleAllCurrentEvents) = 0;
+ virtual bool AnyInput( VclInputFlags nType ) = 0;
+
+ // menus
+ virtual std::unique_ptr<SalMenu> CreateMenu( bool bMenuBar, Menu* pMenu );
+ virtual std::unique_ptr<SalMenuItem> CreateMenuItem( const SalItemParams& pItemData );
+
+ // may return NULL to disable session management, only used by X11 backend
+ virtual std::unique_ptr<SalSession> CreateSalSession();
+
+ // also needs to set m_bSupportsOpenGL = true in your SalInstance implementation!
+ virtual OpenGLContext* CreateOpenGLContext();
+
+ virtual std::unique_ptr<weld::Builder> CreateBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile);
+ virtual std::unique_ptr<weld::Builder> CreateInterimBuilder(vcl::Window* pParent, const OUString& rUIRoot, const OUString& rUIFile,
+ bool bAllowCycleFocusOut, sal_uInt64 nLOKWindowId = 0);
+ virtual weld::MessageDialog* CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType,
+ VclButtonsType eButtonType, const OUString& rPrimaryMessage);
+ virtual weld::Window* GetFrameWeld(const css::uno::Reference<css::awt::XWindow>& rWindow);
+
+ // methods for XDisplayConnection
+
+ void SetEventCallback( rtl::Reference< vcl::DisplayConnectionDispatch > const & pInstance )
+ { m_pEventInst = pInstance; }
+
+ bool CallEventCallback( void const * pEvent, int nBytes );
+
+ virtual OUString GetConnectionIdentifier() = 0;
+
+ // dtrans implementation
+ virtual css::uno::Reference< css::uno::XInterface > CreateClipboard( const css::uno::Sequence< css::uno::Any >& i_rArguments );
+ virtual css::uno::Reference<css::uno::XInterface> ImplCreateDragSource(const SystemEnvData*);
+ virtual css::uno::Reference<css::uno::XInterface> ImplCreateDropTarget(const SystemEnvData*);
+ css::uno::Reference<css::uno::XInterface> CreateDragSource(const SystemEnvData* = nullptr);
+ css::uno::Reference<css::uno::XInterface> CreateDropTarget(const SystemEnvData* = nullptr);
+ virtual void AddToRecentDocumentList(const OUString& rFileUrl, const OUString& rMimeType, const OUString& rDocumentService) = 0;
+
+ virtual bool hasNativeFileSelection() const { return false; }
+ // if you override this, make sure to override hasNativeFileSelection too.
+ virtual css::uno::Reference< css::ui::dialogs::XFilePicker2 > createFilePicker( const css::uno::Reference< css::uno::XComponentContext >& )
+ { return css::uno::Reference< css::ui::dialogs::XFilePicker2 >(); }
+ virtual css::uno::Reference< css::ui::dialogs::XFolderPicker2 > createFolderPicker( const css::uno::Reference< css::uno::XComponentContext >& )
+ { return css::uno::Reference< css::ui::dialogs::XFolderPicker2 >(); }
+
+ // callbacks for printer updates
+ virtual void updatePrinterUpdate() {}
+ virtual void jobEndedPrinterUpdate() {}
+
+ /// Set the app's (somewhat) magic/main-thread to this one.
+ virtual void updateMainThread() {}
+ /// Disconnect that - good for detaching from the JavaVM on Android.
+ virtual void releaseMainThread() {}
+
+ /// get information about underlying versions
+ virtual OUString getOSVersion() { return "-"; }
+
+ virtual const cairo_font_options_t* GetCairoFontOptions() { return nullptr; }
+
+ virtual void* CreateGStreamerSink(const SystemChildWindow*) { return nullptr; }
+
+ virtual void BeforeAbort(const OUString& /* rErrorText */, bool /* bDumpCore */) {}
+
+ // Note: we cannot make this a global variable, because it might be initialised BEFORE the putenv() call in cppunittester.
+ static bool IsRunningUnitTest() { return getenv("LO_TESTNAME") != nullptr; }
+
+ // both must be implemented, if the VCL plugin needs to run via system event loop
+ virtual bool DoExecute(int &nExitCode);
+ virtual void DoQuit();
+};
+
+// called from SVMain
+SalInstance* CreateSalInstance();
+void DestroySalInstance( SalInstance* pInst );
+
+void SalAbort( const OUString& rErrorText, bool bDumpCore );
+
+const OUString& SalGetDesktopEnvironment();
+
+#endif // INCLUDED_VCL_INC_SALINST_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/sallayout.hxx b/vcl/inc/sallayout.hxx
new file mode 100644
index 0000000000..bdbd451890
--- /dev/null
+++ b/vcl/inc/sallayout.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SALLAYOUT_HXX
+#define INCLUDED_VCL_INC_SALLAYOUT_HXX
+
+#include <sal/config.h>
+
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+
+#include <vcl/dllapi.h>
+#include <vcl/vclenum.hxx> // for typedef sal_UCS4
+#include <vcl/vcllayout.hxx>
+
+#include "ImplLayoutRuns.hxx"
+#include "impglyphitem.hxx"
+
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+
+#include <hb.h>
+
+#include <memory>
+#include <vector>
+
+#define MAX_FALLBACK 16
+
+class GenericSalLayout;
+class SalGraphics;
+enum class SalLayoutFlags;
+
+namespace vcl::font {
+ class PhysicalFontFace;
+}
+
+namespace vcl::text {
+ class TextLayoutCache;
+}
+
+class VCL_DLLPUBLIC MultiSalLayout final : public SalLayout
+{
+public:
+ void DrawText(SalGraphics&) const override;
+ sal_Int32 GetTextBreak(double nMaxWidth, double nCharExtra, int nFactor) const override;
+ double GetTextWidth() const final override;
+ double FillDXArray(std::vector<double>* pDXArray, const OUString& rStr) const override;
+ void GetCaretPositions(std::vector<double>& rCaretPositions, const OUString& rStr) const override;
+ bool GetNextGlyph(const GlyphItem** pGlyph, basegfx::B2DPoint& rPos, int& nStart,
+ const LogicalFontInstance** ppGlyphFont = nullptr) const override;
+ bool GetOutline(basegfx::B2DPolyPolygonVector&) const override;
+ bool IsKashidaPosValid(int nCharPos, int nNextCharPos) const override;
+ SalLayoutGlyphs GetGlyphs() const final override;
+
+ // used only by OutputDevice::ImplLayout, TODO: make friend
+ explicit MultiSalLayout( std::unique_ptr<SalLayout> pBaseLayout );
+ void AddFallback(std::unique_ptr<SalLayout> pFallbackLayout, ImplLayoutRuns const &);
+ // give up ownership of the initial pBaseLayout taken by the ctor
+ std::unique_ptr<SalLayout> ReleaseBaseLayout();
+ bool LayoutText(vcl::text::ImplLayoutArgs&, const SalLayoutGlyphsImpl*) override;
+ void AdjustLayout(vcl::text::ImplLayoutArgs&) override;
+ void InitFont() const override;
+
+ void SetIncomplete(bool bIncomplete);
+
+ void ImplAdjustMultiLayout(vcl::text::ImplLayoutArgs& rArgs,
+ vcl::text::ImplLayoutArgs& rMultiArgs,
+ const double* pMultiDXArray);
+
+ SAL_DLLPRIVATE ImplLayoutRuns* GetFallbackRuns() { return maFallbackRuns; }
+
+ virtual ~MultiSalLayout() override;
+
+private:
+ MultiSalLayout( const MultiSalLayout& ) = delete;
+ MultiSalLayout& operator=( const MultiSalLayout& ) = delete;
+
+ std::unique_ptr<GenericSalLayout> mpLayouts[ MAX_FALLBACK ];
+ ImplLayoutRuns maFallbackRuns[ MAX_FALLBACK ];
+ int mnLevel;
+ bool mbIncomplete;
+};
+
+class VCL_DLLPUBLIC GenericSalLayout : public SalLayout
+{
+ friend void MultiSalLayout::ImplAdjustMultiLayout(
+ vcl::text::ImplLayoutArgs& rArgs,
+ vcl::text::ImplLayoutArgs& rMultiArgs,
+ const double* pMultiDXArray);
+
+public:
+ GenericSalLayout(LogicalFontInstance&);
+ ~GenericSalLayout() override;
+
+ void AdjustLayout(vcl::text::ImplLayoutArgs&) final override;
+ bool LayoutText(vcl::text::ImplLayoutArgs&, const SalLayoutGlyphsImpl*) final override;
+ void DrawText(SalGraphics&) const final override;
+ SalLayoutGlyphs GetGlyphs() const final override;
+
+ bool IsKashidaPosValid(int nCharPos, int nNextCharPos) const final override;
+
+ // used by upper layers
+ double GetTextWidth() const final override;
+ double FillDXArray(std::vector<double>* pDXArray, const OUString& rStr) const final override;
+ sal_Int32 GetTextBreak(double nMaxWidth, double nCharExtra, int nFactor) const final override;
+ void GetCaretPositions(std::vector<double>& rCaretPositions, const OUString& rStr) const override;
+
+ // used by display layers
+ LogicalFontInstance& GetFont() const
+ { return *m_GlyphItems.GetFont(); }
+
+ bool GetNextGlyph(const GlyphItem** pGlyph, basegfx::B2DPoint& rPos, int& nStart,
+ const LogicalFontInstance** ppGlyphFont = nullptr) const override;
+
+ const SalLayoutGlyphsImpl& GlyphsImpl() const { return m_GlyphItems; }
+
+private:
+ // for glyph+font+script fallback
+ void MoveGlyph(int nStart, double nNewXPos);
+ void DropGlyph(int nStart);
+ void Simplify(bool bIsBase);
+
+ GenericSalLayout( const GenericSalLayout& ) = delete;
+ GenericSalLayout& operator=( const GenericSalLayout& ) = delete;
+
+ void ApplyDXArray(const double*, const sal_Bool*);
+ void Justify(double nNewWidth);
+ void ApplyAsianKerning(std::u16string_view rStr);
+
+ void GetCharWidths(std::vector<double>& rCharWidths,
+ const OUString& rStr) const;
+
+ void SetNeedFallback(vcl::text::ImplLayoutArgs&, sal_Int32, bool);
+
+ bool HasVerticalAlternate(sal_UCS4 aChar, sal_UCS4 aNextChar);
+
+ void ParseFeatures(std::u16string_view name);
+
+ css::uno::Reference<css::i18n::XBreakIterator> mxBreak;
+
+ SalLayoutGlyphsImpl m_GlyphItems;
+
+ OString msLanguage;
+ std::vector<hb_feature_t> maFeatures;
+
+ hb_set_t* mpVertGlyphs;
+ const bool mbFuzzing;
+};
+
+#endif // INCLUDED_VCL_INC_SALLAYOUT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/salmenu.hxx b/vcl/inc/salmenu.hxx
new file mode 100644
index 0000000000..975df9391e
--- /dev/null
+++ b/vcl/inc/salmenu.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SALMENU_HXX
+#define INCLUDED_VCL_INC_SALMENU_HXX
+
+#include <utility>
+#include <vcl/menu.hxx>
+#include <vcl/image.hxx>
+
+struct SystemMenuData;
+class FloatingWindow;
+class SalFrame;
+
+struct SalItemParams
+{
+ Image aImage; // Image
+ VclPtr<Menu> pMenu; // Pointer to Menu
+ OUString aText; // Menu-Text
+ MenuItemType eType; // MenuItem-Type
+ sal_uInt16 nId; // item Id
+ MenuItemBits nBits; // MenuItem-Bits
+};
+
+struct SalMenuButtonItem
+{
+ sal_uInt16 mnId;
+ Image maImage;
+ OUString maToolTipText;
+
+ SalMenuButtonItem() : mnId( 0 ) {}
+ SalMenuButtonItem( sal_uInt16 i_nId, Image aImg, OUString i_TTText )
+ : mnId( i_nId ), maImage(std::move( aImg )), maToolTipText(std::move( i_TTText )) {}
+};
+
+class VCL_PLUGIN_PUBLIC SalMenuItem
+{
+public:
+ virtual ~SalMenuItem();
+};
+
+class VCL_PLUGIN_PUBLIC SalMenu
+{
+public:
+ virtual ~SalMenu();
+
+ virtual bool VisibleMenuBar() = 0; // must return true to actually DISPLAY native menu bars
+ // otherwise only menu messages are processed (eg, OLE on Windows)
+ virtual void ShowMenuBar( bool ) {}
+ virtual void InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos ) = 0;
+ virtual void RemoveItem( unsigned nPos ) = 0;
+ virtual void SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned nPos ) = 0;
+ virtual void SetFrame( const SalFrame* pFrame ) = 0;
+ virtual void SetItemBits( unsigned /*nPos*/, MenuItemBits /*nBits*/ ) {}
+ virtual void CheckItem( unsigned nPos, bool bCheck ) = 0;
+ virtual void EnableItem( unsigned nPos, bool bEnable ) = 0;
+ virtual void SetItemText( unsigned nPos, SalMenuItem* pSalMenuItem, const OUString& rText )= 0;
+ virtual void SetItemImage( unsigned nPos, SalMenuItem* pSalMenuItem, const Image& rImage ) = 0;
+ virtual void SetAccelerator( unsigned nPos, SalMenuItem* pSalMenuItem, const vcl::KeyCode& rKeyCode, const OUString& rKeyName ) = 0;
+ virtual void GetSystemMenuData( SystemMenuData* pData ) = 0;
+ virtual bool ShowNativePopupMenu(FloatingWindow * pWin, const tools::Rectangle& rRect, FloatWinPopupFlags nFlags);
+ virtual void ShowCloseButton(bool bShow);
+ virtual bool AddMenuBarButton( const SalMenuButtonItem& ); // return false if not implemented or failure
+ virtual void RemoveMenuBarButton( sal_uInt16 nId );
+ virtual void Update() {}
+
+ virtual bool CanGetFocus() const { return false; }
+ virtual bool TakeFocus() { return false; }
+
+ // TODO: implement show/hide for the Win/Mac VCL native backends
+ virtual void ShowItem( unsigned nPos, bool bShow ) { EnableItem( nPos, bShow ); }
+
+ // return an empty rectangle if not implemented
+ // return Rectangle( Point( -1, -1 ), Size( 1, 1 ) ) if menu bar buttons implemented
+ // but rectangle cannot be determined
+ virtual tools::Rectangle GetMenuBarButtonRectPixel( sal_uInt16 i_nItemId, SalFrame* i_pReferenceFrame );
+
+ virtual int GetMenuBarHeight() const;
+
+ virtual void ApplyPersona();
+};
+
+#endif // INCLUDED_VCL_INC_SALMENU_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/salobj.hxx b/vcl/inc/salobj.hxx
new file mode 100644
index 0000000000..b5d9d64f82
--- /dev/null
+++ b/vcl/inc/salobj.hxx
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SALOBJ_HXX
+#define INCLUDED_VCL_INC_SALOBJ_HXX
+
+#include <vcl/dllapi.h>
+#include <vcl/syschild.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include "salwtype.hxx"
+
+struct SystemEnvData;
+
+typedef void (*SALOBJECTPROC)(SystemChildWindow* pInst, SalObjEvent nEvent);
+
+class VCL_PLUGIN_PUBLIC SalObject
+{
+ VclPtr<SystemChildWindow> m_pInst;
+ SALOBJECTPROC m_pCallback;
+ bool m_bMouseTransparent:1,
+ m_bEraseBackground:1;
+public:
+ SalObject() : m_pInst( nullptr ), m_pCallback( nullptr ), m_bMouseTransparent( false ), m_bEraseBackground( true ) {}
+ virtual ~SalObject();
+
+ virtual void ResetClipRegion() = 0;
+ virtual void BeginSetClipRegion( sal_uInt32 nRects ) = 0;
+ virtual void UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) = 0;
+ virtual void EndSetClipRegion() = 0;
+
+ virtual void SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) = 0;
+ virtual void Show( bool bVisible ) = 0;
+ virtual void Enable( bool /* nEnable */ ) {} // overridden by WinSalObject
+ virtual void GrabFocus() {}
+ virtual void Reparent(SalFrame* /*pFrame*/) {}
+
+ virtual void SetForwardKey( bool /* bEnable */ ) {}
+
+ virtual void SetLeaveEnterBackgrounds(const css::uno::Sequence<css::uno::Any>& /*rLeaveArgs*/, const css::uno::Sequence<css::uno::Any>& /*rEnterArgs*/) {}
+
+ virtual const SystemEnvData* GetSystemData() const = 0;
+
+ virtual Size GetOptimalSize() const { return Size(); }
+
+ void SetCallback( SystemChildWindow* pInst, SALOBJECTPROC pProc )
+ { m_pInst = pInst; m_pCallback = pProc; }
+ void CallCallback( SalObjEvent nEvent )
+ { if (m_pCallback) m_pCallback( m_pInst, nEvent ); }
+
+ void SetMouseTransparent( bool bMouseTransparent )
+ { m_bMouseTransparent = bMouseTransparent; }
+ bool IsMouseTransparent() const
+ { return m_bMouseTransparent; }
+
+ void EnableEraseBackground( bool bEnable )
+ { m_bEraseBackground = bEnable; }
+ bool IsEraseBackgroundEnabled() const
+ { return m_bEraseBackground; }
+};
+
+#endif // INCLUDED_VCL_INC_SALOBJ_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/salprn.hxx b/vcl/inc/salprn.hxx
new file mode 100644
index 0000000000..97a0fe13aa
--- /dev/null
+++ b/vcl/inc/salprn.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SALPRN_HXX
+#define INCLUDED_VCL_INC_SALPRN_HXX
+
+#include <i18nutil/paper.hxx>
+#include <rtl/ustring.hxx>
+#include <vcl/prntypes.hxx>
+#include <vcl/dllapi.h>
+#include <tools/gen.hxx>
+
+#include "salptype.hxx"
+
+#include <vector>
+#include <optional>
+
+class SalGraphics;
+class SalFrame;
+class ImplJobSetup;
+namespace vcl { class PrinterController; }
+namespace weld { class Window; }
+
+struct VCL_PLUGIN_PUBLIC SalPrinterQueueInfo
+{
+ OUString maPrinterName;
+ OUString maDriver;
+ OUString maLocation;
+ OUString maComment;
+ PrintQueueFlags mnStatus;
+ sal_uInt32 mnJobs;
+ std::optional<OUString> moPortName; // only used by Windows backend
+
+ SalPrinterQueueInfo();
+ ~SalPrinterQueueInfo();
+};
+
+class VCL_PLUGIN_PUBLIC SalInfoPrinter
+{
+public:
+ std::vector< PaperInfo > m_aPaperFormats; // all printer supported formats
+ bool m_bPapersInit; // set to true after InitPaperFormats
+
+ SalInfoPrinter() : m_bPapersInit( false ) {}
+ virtual ~SalInfoPrinter();
+
+ // SalGraphics or NULL, but two Graphics for all SalFrames
+ // must be returned
+ virtual SalGraphics* AcquireGraphics() = 0;
+ virtual void ReleaseGraphics( SalGraphics* pGraphics ) = 0;
+
+ virtual bool Setup(weld::Window* pFrame, ImplJobSetup* pSetupData) = 0;
+ // This function set the driver data and
+ // set the new indepen data in pSetupData
+ virtual bool SetPrinterData( ImplJobSetup* pSetupData ) = 0;
+ // This function merged the indepen driver data
+ // and set the new indepen data in pSetupData
+ // Only the data must changed, where the bit
+ // in nFlags is set
+ virtual bool SetData( JobSetFlags nFlags, ImplJobSetup* pSetupData ) = 0;
+
+ virtual void GetPageInfo( const ImplJobSetup* pSetupData,
+ tools::Long& rOutWidth, tools::Long& rOutHeight,
+ Point& rPageOffset,
+ Size& rPaperSize ) = 0;
+ virtual sal_uInt32 GetCapabilities( const ImplJobSetup* pSetupData, PrinterCapType nType ) = 0;
+ virtual sal_uInt16 GetPaperBinCount( const ImplJobSetup* pSetupData ) = 0;
+ virtual OUString GetPaperBinName( const ImplJobSetup* pSetupData, sal_uInt16 nPaperBin ) = 0;
+ // fills m_aPaperFormats and sets m_bPapersInit to true
+ virtual void InitPaperFormats( const ImplJobSetup* pSetupData ) = 0;
+ // returns angle that a landscape page will be turned counterclockwise wrt to portrait
+ virtual int GetLandscapeAngle( const ImplJobSetup* pSetupData ) = 0;
+};
+
+class VCL_PLUGIN_PUBLIC SalPrinter
+{
+ SalPrinter( const SalPrinter& ) = delete;
+ SalPrinter& operator=( const SalPrinter& ) = delete;
+
+public:
+ SalPrinter() {}
+ virtual ~SalPrinter();
+
+ virtual bool StartJob( const OUString* pFileName,
+ const OUString& rJobName,
+ const OUString& rAppName,
+ sal_uInt32 nCopies,
+ bool bCollate,
+ bool bDirect,
+ ImplJobSetup* pSetupData ) = 0;
+
+ // implement for pull model print systems only,
+ // default implementations (see salvtables.cxx) just returns false
+ virtual bool StartJob( const OUString* pFileName,
+ const OUString& rJobName,
+ const OUString& rAppName,
+ ImplJobSetup* pSetupData,
+ vcl::PrinterController& rController );
+
+ virtual bool EndJob() = 0;
+ virtual SalGraphics* StartPage( ImplJobSetup* pSetupData, bool bNewJobData ) = 0;
+ virtual void EndPage() = 0;
+ virtual SalPrinterError GetErrorCode() { return SalPrinterError::NONE; }
+
+};
+
+#endif // INCLUDED_VCL_INC_SALPRN_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/salptype.hxx b/vcl/inc/salptype.hxx
new file mode 100644
index 0000000000..32405aaf24
--- /dev/null
+++ b/vcl/inc/salptype.hxx
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SALPTYPE_HXX
+#define INCLUDED_VCL_INC_SALPTYPE_HXX
+
+#include <sal/config.h>
+
+#include <sal/types.h>
+#include <o3tl/typed_flags_set.hxx>
+
+enum class JobSetFlags : sal_uInt16 {
+ ORIENTATION = 1,
+ PAPERBIN = 2,
+ PAPERSIZE = 4,
+ DUPLEXMODE = 8,
+ ALL = ORIENTATION | PAPERBIN | PAPERSIZE | DUPLEXMODE
+};
+
+namespace o3tl {
+
+template<> struct typed_flags<JobSetFlags>: is_typed_flags<JobSetFlags, 0xF> {};
+
+}
+
+enum class SalPrinterError {
+ NONE = 0,
+ General = 1,
+ Abort = 2
+};
+
+class SalPrinter;
+typedef long (*SALPRNABORTPROC)( void* pInst, SalPrinter* pPrinter );
+
+#endif // INCLUDED_VCL_INC_SALPTYPE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/salsession.hxx b/vcl/inc/salsession.hxx
new file mode 100644
index 0000000000..fb9763bb68
--- /dev/null
+++ b/vcl/inc/salsession.hxx
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SALSESSION_HXX
+#define INCLUDED_VCL_INC_SALSESSION_HXX
+
+#include <vcl/dllapi.h>
+
+enum SalSessionEventType
+{
+ Interaction,
+ SaveRequest,
+ ShutdownCancel,
+ Quit
+};
+
+struct SalSessionEvent
+{
+ SalSessionEventType m_eType;
+
+ SalSessionEvent( SalSessionEventType eType )
+ : m_eType( eType )
+ {}
+};
+
+struct SalSessionInteractionEvent : public SalSessionEvent
+{
+ bool m_bInteractionGranted;
+
+ SalSessionInteractionEvent( bool bGranted )
+ : SalSessionEvent( Interaction ),
+ m_bInteractionGranted( bGranted )
+ {}
+};
+
+struct SalSessionSaveRequestEvent : public SalSessionEvent
+{
+ bool m_bShutdown;
+
+ SalSessionSaveRequestEvent( bool bShutdown )
+ : SalSessionEvent( SaveRequest ),
+ m_bShutdown( bShutdown )
+ {}
+};
+
+struct SalSessionShutdownCancelEvent : public SalSessionEvent
+{
+ SalSessionShutdownCancelEvent()
+ : SalSessionEvent( ShutdownCancel )
+ {}
+};
+
+struct SalSessionQuitEvent : public SalSessionEvent
+{
+ SalSessionQuitEvent()
+ : SalSessionEvent( Quit )
+ {}
+};
+
+typedef void(*SessionProc)(void *pData, SalSessionEvent *pEvent);
+
+class VCL_PLUGIN_PUBLIC SalSession
+{
+ SessionProc m_aProc;
+ void * m_pProcData;
+public:
+ SalSession()
+ : m_aProc(nullptr)
+ , m_pProcData(nullptr)
+ {
+ }
+ virtual ~SalSession();
+
+ void SetCallback( SessionProc aCallback, void * pCallbackData )
+ {
+ m_aProc = aCallback;
+ m_pProcData = pCallbackData;
+ }
+ void CallCallback( SalSessionEvent* pEvent )
+ {
+ if( m_aProc )
+ m_aProc( m_pProcData, pEvent );
+ }
+
+ // query the session manager for a user interaction slot
+ virtual void queryInteraction() = 0;
+ // signal the session manager that we're done with user interaction
+ virtual void interactionDone() = 0;
+ // signal that we're done saving
+ virtual void saveDone() = 0;
+ // try to cancel the shutdown in progress
+ virtual bool cancelShutdown() = 0;
+};
+
+#endif // INCLUDED_VCL_INC_SALSESSION_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/salsys.hxx b/vcl/inc/salsys.hxx
new file mode 100644
index 0000000000..d7cf10edb1
--- /dev/null
+++ b/vcl/inc/salsys.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_VCL_INC_SALSYS_HXX
+#define INCLUDED_VCL_INC_SALSYS_HXX
+
+#include <tools/gen.hxx>
+#include <vcl/dllapi.h>
+#include <rtl/ustring.hxx>
+
+// Button identifier for ShowNativeMessageBox
+const int SALSYSTEM_SHOWNATIVEMSGBOX_BTN_OK = 1;
+
+class VCL_PLUGIN_PUBLIC SalSystem
+{
+public:
+ virtual ~SalSystem();
+
+ // get info about the display
+
+ /* Gets the number of active screens attached to the display
+
+ @returns the number of active screens
+ */
+ virtual unsigned int GetDisplayScreenCount() = 0;
+ /* Queries the default screen number. The default screen is the
+ screen on which windows will appear if no special positioning
+ is made.
+
+ @returns the default screen number
+ */
+ virtual unsigned int GetDisplayBuiltInScreen() { return 0; }
+ /* Gets relative position and size of the screens attached to the display
+
+ @param nScreen
+ The screen number to be queried
+
+ @returns position: (0,0) in case of IsMultiscreen() == true
+ else position relative to whole display
+ size: size of the screen
+ */
+ virtual AbsoluteScreenPixelRectangle GetDisplayScreenPosSizePixel(unsigned int nScreen) = 0;
+
+ /* Shows a native message box with the specified title, message and button
+ combination.
+
+ @param rTitle
+ The title to be shown by the dialog box.
+
+ @param rMessage
+ The message to be shown by the dialog box.
+
+ @returns the identifier of the button that was pressed by the user.
+ See button identifier above. If the function fails the
+ return value is 0.
+ */
+ virtual int ShowNativeMessageBox(const OUString& rTitle, const OUString& rMessage) = 0;
+};
+
+VCL_DLLPUBLIC SalSystem* ImplGetSalSystem();
+
+#define VIRTUAL_DESKTOP_WIDTH 1024
+#define VIRTUAL_DESKTOP_HEIGHT 768
+
+#endif // INCLUDED_VCL_INC_SALSYS_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/saltimer.hxx b/vcl/inc/saltimer.hxx
new file mode 100644
index 0000000000..69545ac96b
--- /dev/null
+++ b/vcl/inc/saltimer.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SALTIMER_HXX
+#define INCLUDED_VCL_INC_SALTIMER_HXX
+
+#include <sal/config.h>
+#include <vcl/dllapi.h>
+#include "salwtype.hxx"
+
+/*
+ * note: there will be only a single instance of SalTimer
+ * SalTimer originally had only static methods, but
+ * this needed to be virtualized for the sal plugin migration
+ */
+
+class VCL_PLUGIN_PUBLIC SalTimer
+{
+ SALTIMERPROC m_pProc;
+
+public:
+ SalTimer() : m_pProc( nullptr ) {}
+ virtual ~SalTimer() COVERITY_NOEXCEPT_FALSE;
+
+ // AutoRepeat and Restart
+ virtual void Start( sal_uInt64 nMS ) = 0;
+ virtual void Stop() = 0;
+
+ // Callbacks (indepen in \sv\source\app\timer.cxx)
+ void SetCallback( SALTIMERPROC pProc )
+ {
+ m_pProc = pProc;
+ }
+
+ void CallCallback()
+ {
+ if( m_pProc )
+ m_pProc();
+ }
+};
+
+class VersionedEvent
+{
+ /**
+ * The "additional event data" members on macOS are integers, so we can't
+ * use an unsigned integer and rely on the defined unsigned overflow in
+ * InvalidateEvent().
+ */
+ sal_Int32 m_nEventVersion;
+ bool m_bIsValidVersion;
+
+public:
+ VersionedEvent() : m_nEventVersion( 0 ), m_bIsValidVersion( false ) {}
+
+ sal_Int32 GetNextEventVersion()
+ {
+ InvalidateEvent();
+ m_bIsValidVersion = true;
+ return m_nEventVersion;
+ }
+
+ void InvalidateEvent()
+ {
+ if ( m_bIsValidVersion )
+ {
+ if ( m_nEventVersion == SAL_MAX_INT32 )
+ m_nEventVersion = 0;
+ else
+ ++m_nEventVersion;
+ m_bIsValidVersion = false;
+ }
+ }
+
+ bool ExistsValidEvent() const
+ {
+ return m_bIsValidVersion;
+ }
+
+ bool IsValidEventVersion( const sal_Int32 nEventVersion ) const
+ {
+ return m_bIsValidVersion && nEventVersion == m_nEventVersion;
+ }
+};
+
+#endif // INCLUDED_VCL_INC_SALTIMER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/salusereventlist.hxx b/vcl/inc/salusereventlist.hxx
new file mode 100644
index 0000000000..864802c50a
--- /dev/null
+++ b/vcl/inc/salusereventlist.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SALUSEREVENTLIST_HXX
+#define INCLUDED_VCL_INC_SALUSEREVENTLIST_HXX
+
+#include <sal/config.h>
+#include <vcl/dllapi.h>
+#include <mutex>
+#include <osl/thread.hxx>
+
+#include <list>
+#include <o3tl/sorted_vector.hxx>
+
+class SalFrame;
+enum class SalEvent;
+
+typedef o3tl::sorted_vector< SalFrame* > SalFrameSet;
+
+class VCL_PLUGIN_PUBLIC SalUserEventList
+{
+public:
+ struct SalUserEvent
+ {
+ SalFrame* m_pFrame;
+ void* m_pData;
+ SalEvent m_nEvent;
+
+ SalUserEvent( SalFrame* pFrame, void* pData, SalEvent nEvent )
+ : m_pFrame( pFrame ),
+ m_pData( pData ),
+ m_nEvent( nEvent )
+ {}
+
+ bool operator==(const SalUserEvent &aEvent) const
+ {
+ return m_pFrame == aEvent.m_pFrame
+ && m_pData == aEvent.m_pData
+ && m_nEvent== aEvent.m_nEvent;
+ }
+ };
+
+protected:
+ mutable std::mutex m_aUserEventsMutex;
+ std::list< SalUserEvent > m_aUserEvents;
+ std::list< SalUserEvent > m_aProcessingUserEvents;
+ bool m_bAllUserEventProcessedSignaled;
+ SalFrameSet m_aFrames;
+ oslThreadIdentifier m_aProcessingThread;
+
+ virtual void ProcessEvent( SalUserEvent aEvent ) = 0;
+ virtual void TriggerUserEventProcessing() = 0;
+ virtual void TriggerAllUserEventsProcessed() {}
+
+ inline bool HasUserEvents_NoLock() const;
+public:
+ SalUserEventList();
+ virtual ~SalUserEventList() COVERITY_NOEXCEPT_FALSE;
+
+ inline const SalFrameSet& getFrames() const;
+ inline SalFrame* anyFrame() const;
+ void insertFrame( SalFrame* pFrame );
+ void eraseFrame( SalFrame* pFrame );
+ inline bool isFrameAlive( const SalFrame* pFrame ) const;
+
+ void PostEvent( SalFrame* pFrame, void* pData, SalEvent nEvent );
+ void RemoveEvent( SalFrame* pFrame, void* pData, SalEvent nEvent );
+ inline bool HasUserEvents() const;
+
+ bool DispatchUserEvents( bool bHandleAllCurrentEvents );
+};
+
+inline SalFrame* SalUserEventList::anyFrame() const
+{
+ if ( m_aFrames.empty() )
+ return nullptr;
+ return *m_aFrames.begin();
+}
+
+inline bool SalUserEventList::isFrameAlive( const SalFrame* pFrame ) const
+{
+ auto it = m_aFrames.find( const_cast<SalFrame*>( pFrame ) );
+ return it != m_aFrames.end();
+}
+
+inline bool SalUserEventList::HasUserEvents() const
+{
+ std::unique_lock aGuard( m_aUserEventsMutex );
+ return HasUserEvents_NoLock();
+}
+
+inline bool SalUserEventList::HasUserEvents_NoLock() const
+{
+ return !(m_aUserEvents.empty() && m_aProcessingUserEvents.empty());
+}
+
+inline void SalUserEventList::PostEvent( SalFrame* pFrame, void* pData, SalEvent nEvent )
+{
+ std::unique_lock aGuard( m_aUserEventsMutex );
+ m_aUserEvents.push_back( SalUserEvent( pFrame, pData, nEvent ) );
+ m_bAllUserEventProcessedSignaled = false;
+ TriggerUserEventProcessing();
+}
+
+inline const SalFrameSet& SalUserEventList::getFrames() const
+{
+ return m_aFrames;
+}
+
+#endif // INCLUDED_VCL_INC_SALUSEREVENTLIST_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/salvd.hxx b/vcl/inc/salvd.hxx
new file mode 100644
index 0000000000..d1035feaeb
--- /dev/null
+++ b/vcl/inc/salvd.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SALVD_HXX
+#define INCLUDED_VCL_INC_SALVD_HXX
+
+#include "salgeom.hxx"
+
+class SalGraphics;
+
+/// A non-visible drawable/buffer (e.g. an X11 Pixmap).
+class VCL_PLUGIN_PUBLIC SalVirtualDevice
+ : public SalGeometryProvider
+{
+public:
+ SalVirtualDevice() {}
+ virtual ~SalVirtualDevice() override;
+
+ // SalGeometryProvider
+ virtual bool IsOffScreen() const override { return true; }
+
+ // SalGraphics or NULL, but two Graphics for all SalVirtualDevices
+ // must be returned
+ virtual SalGraphics* AcquireGraphics() = 0;
+ virtual void ReleaseGraphics( SalGraphics* pGraphics ) = 0;
+
+ // Set new size, without saving the old contents
+ virtual bool SetSize( tools::Long nNewDX, tools::Long nNewDY ) = 0;
+
+ // Set new size using a buffer at the given address
+ virtual bool SetSizeUsingBuffer( tools::Long nNewDX, tools::Long nNewDY,
+ sal_uInt8 * /* pBuffer */)
+ {
+ // Only the headless virtual device has an implementation that uses
+ // pBuffer (and bTopDown).
+ return SetSize( nNewDX, nNewDY );
+ }
+};
+
+#endif // INCLUDED_VCL_INC_SALVD_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/salvtables.hxx b/vcl/inc/salvtables.hxx
new file mode 100644
index 0000000000..4074e097a4
--- /dev/null
+++ b/vcl/inc/salvtables.hxx
@@ -0,0 +1,2298 @@
+/* -*- 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/.
+ */
+#pragma once
+
+#include <vcl/builder.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/ctrl.hxx>
+#include <vcl/toolkit/edit.hxx>
+#include <vcl/formatter.hxx>
+#include <vcl/toolkit/spinfld.hxx>
+#include <vcl/toolkit/fixed.hxx>
+#include <vcl/toolkit/fixedhyper.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+#include <vcl/toolkit/menubtn.hxx>
+#include <vcl/toolkit/combobox.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/toolkit/svtabbx.hxx>
+#include <vcl/toolkit/svlbitm.hxx>
+#include <o3tl/sorted_vector.hxx>
+#include "calendar.hxx"
+#include "iconview.hxx"
+#include "messagedialog.hxx"
+#include "verticaltabctrl.hxx"
+
+namespace vcl
+{
+class RoadmapWizard;
+};
+
+class SalInstanceBuilder : public weld::Builder
+{
+protected:
+ std::unique_ptr<VclBuilder> m_xBuilder;
+ VclPtr<vcl::Window> m_aOwnedToplevel;
+
+public:
+ SalInstanceBuilder(vcl::Window* pParent, const OUString& rUIRoot, const OUString& rUIFile,
+ const css::uno::Reference<css::frame::XFrame>& rFrame
+ = css::uno::Reference<css::frame::XFrame>());
+
+ virtual std::unique_ptr<weld::MessageDialog> weld_message_dialog(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Dialog> weld_dialog(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Assistant> weld_assistant(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Window> create_screenshot_window() override;
+
+ virtual std::unique_ptr<weld::Widget> weld_widget(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Container> weld_container(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Box> weld_box(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Paned> weld_paned(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Frame> weld_frame(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::ScrolledWindow>
+ weld_scrolled_window(const OUString& id, bool bUserManagedScrolling = false) override;
+
+ virtual std::unique_ptr<weld::Notebook> weld_notebook(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Button> weld_button(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::MenuButton> weld_menu_button(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::MenuToggleButton>
+ weld_menu_toggle_button(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::LinkButton> weld_link_button(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::ToggleButton> weld_toggle_button(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::RadioButton> weld_radio_button(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::CheckButton> weld_check_button(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Scale> weld_scale(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::ProgressBar> weld_progress_bar(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::LevelBar> weld_level_bar(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Spinner> weld_spinner(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Image> weld_image(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Calendar> weld_calendar(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Entry> weld_entry(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::SpinButton> weld_spin_button(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::MetricSpinButton>
+ weld_metric_spin_button(const OUString& id, FieldUnit eUnit) override;
+
+ virtual std::unique_ptr<weld::FormattedSpinButton>
+ weld_formatted_spin_button(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::ComboBox> weld_combo_box(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::EntryTreeView>
+ weld_entry_tree_view(const OUString& containerid, const OUString& entryid,
+ const OUString& treeviewid) override;
+
+ virtual std::unique_ptr<weld::TreeView> weld_tree_view(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::IconView> weld_icon_view(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Label> weld_label(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::TextView> weld_text_view(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Expander> weld_expander(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::DrawingArea>
+ weld_drawing_area(const OUString& id, const a11yref& rA11yImpl = nullptr,
+ FactoryFunction pUITestFactoryFunction = nullptr,
+ void* pUserData = nullptr) override;
+
+ virtual std::unique_ptr<weld::Menu> weld_menu(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Popover> weld_popover(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Toolbar> weld_toolbar(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::Scrollbar> weld_scrollbar(const OUString& id) override;
+
+ virtual std::unique_ptr<weld::SizeGroup> create_size_group() override;
+
+ OUString get_current_page_help_id() const;
+
+ virtual ~SalInstanceBuilder() override;
+};
+
+class SAL_DLLPUBLIC_RTTI SalInstanceMenu final : public weld::Menu
+{
+private:
+ VclPtr<PopupMenu> m_xMenu;
+
+ bool m_bTakeOwnership;
+ sal_uInt16 m_nLastId;
+
+ DECL_DLLPRIVATE_LINK(SelectMenuHdl, ::Menu*, bool);
+
+public:
+ SalInstanceMenu(PopupMenu* pMenu, bool bTakeOwnership);
+ virtual OUString popup_at_rect(weld::Widget* pParent, const tools::Rectangle& rRect,
+ weld::Placement ePlace = weld::Placement::Under) override;
+ virtual void set_sensitive(const OUString& rIdent, bool bSensitive) override;
+ virtual bool get_sensitive(const OUString& rIdent) const override;
+ virtual void set_active(const OUString& rIdent, bool bActive) override;
+ virtual bool get_active(const OUString& rIdent) const override;
+ virtual void set_label(const OUString& rIdent, const OUString& rLabel) override;
+ virtual OUString get_label(const OUString& rIdent) const override;
+ virtual void set_visible(const OUString& rIdent, bool bShow) override;
+ virtual void clear() override;
+ virtual void insert(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, VirtualDevice* pImageSurface,
+ const css::uno::Reference<css::graphic::XGraphic>& rImage,
+ TriState eCheckRadioFalse) override;
+ virtual void insert_separator(int pos, const OUString& rId) override;
+ virtual void set_item_help_id(const OUString& rIdent, const OUString& rHelpId) override;
+ virtual void remove(const OUString& rId) override;
+ virtual OUString get_id(int pos) const override;
+ virtual int n_children() const override;
+ PopupMenu* getMenu() const;
+ virtual ~SalInstanceMenu() override;
+};
+
+class SalFlashAttention;
+
+class SalInstanceWidget : public virtual weld::Widget
+{
+protected:
+ VclPtr<vcl::Window> m_xWidget;
+ std::unique_ptr<SalFlashAttention> m_xFlashAttention;
+ SalInstanceBuilder* m_pBuilder;
+
+private:
+ DECL_LINK(EventListener, VclWindowEvent&, void);
+ DECL_LINK(KeyEventListener, VclWindowEvent&, bool);
+ DECL_LINK(MouseEventListener, VclWindowEvent&, void);
+ DECL_LINK(SettingsChangedHdl, VclWindowEvent&, void);
+ DECL_LINK(MnemonicActivateHdl, vcl::Window&, bool);
+
+ static void DoRecursivePaint(vcl::Window* pWindow, const Point& rPos, OutputDevice& rOutput);
+
+ const bool m_bTakeOwnership;
+ bool m_bEventListener;
+ bool m_bKeyEventListener;
+ bool m_bMouseEventListener;
+ int m_nBlockNotify;
+ int m_nFreezeCount;
+
+protected:
+ void ensure_event_listener();
+
+ // 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 ensure_key_listener();
+
+ // 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 ensure_mouse_listener();
+
+ bool IsFirstFreeze() const { return m_nFreezeCount == 0; }
+ bool IsLastThaw() const { return m_nFreezeCount == 1; }
+
+ virtual void HandleEventListener(VclWindowEvent& rEvent);
+ virtual bool HandleKeyEventListener(VclWindowEvent& rEvent);
+ virtual void HandleMouseEventListener(VclWindowEvent& rEvent);
+
+public:
+ SalInstanceWidget(vcl::Window* pWidget, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual void set_sensitive(bool sensitive) override;
+
+ virtual bool get_sensitive() const override;
+
+ virtual bool get_visible() const override;
+
+ virtual bool is_visible() const override;
+
+ virtual void set_can_focus(bool bCanFocus) override;
+
+ virtual void grab_focus() override;
+
+ virtual bool has_focus() const override;
+
+ virtual bool is_active() const override;
+
+ virtual bool has_child_focus() const override;
+
+ virtual void show() override;
+
+ virtual void hide() override;
+
+ virtual void set_size_request(int nWidth, int nHeight) override;
+
+ virtual Size get_size_request() const override;
+
+ virtual Size get_preferred_size() const override;
+
+ virtual float get_approximate_digit_width() const override;
+
+ virtual int get_text_height() const override;
+
+ virtual Size get_pixel_size(const OUString& rText) const override;
+
+ virtual vcl::Font get_font() override;
+
+ virtual OUString get_buildable_name() const override;
+
+ virtual void set_buildable_name(const OUString& rId) override;
+
+ virtual void set_help_id(const OUString& rId) override;
+
+ virtual OUString get_help_id() const override;
+
+ virtual void set_grid_left_attach(int nAttach) override;
+
+ virtual int get_grid_left_attach() const override;
+
+ virtual void set_grid_width(int nCols) override;
+
+ virtual void set_grid_top_attach(int nAttach) override;
+
+ virtual int get_grid_top_attach() const override;
+
+ virtual void set_hexpand(bool bExpand) override;
+
+ virtual bool get_hexpand() const override;
+
+ virtual void set_vexpand(bool bExpand) override;
+
+ virtual bool get_vexpand() const override;
+
+ virtual void set_margin_top(int nMargin) override;
+
+ virtual void set_margin_bottom(int nMargin) override;
+
+ virtual void set_margin_start(int nMargin) override;
+
+ virtual void set_margin_end(int nMargin) override;
+
+ virtual int get_margin_top() const override;
+
+ virtual int get_margin_bottom() const override;
+
+ virtual int get_margin_start() const override;
+
+ virtual int get_margin_end() const override;
+
+ virtual void set_accessible_name(const OUString& rName) override;
+
+ virtual void set_accessible_description(const OUString& rDescription) override;
+
+ virtual OUString get_accessible_name() const override;
+
+ virtual OUString get_accessible_description() const override;
+
+ virtual void set_accessible_relation_labeled_by(weld::Widget* pLabel) override;
+
+ virtual void set_tooltip_text(const OUString& rTip) override;
+
+ virtual OUString get_tooltip_text() const override;
+
+ virtual void set_cursor_data(void* pData) override;
+
+ virtual void connect_focus_in(const Link<Widget&, void>& rLink) override;
+
+ virtual void connect_mnemonic_activate(const Link<Widget&, bool>& rLink) override;
+
+ virtual void connect_focus_out(const Link<Widget&, void>& rLink) override;
+
+ virtual void connect_size_allocate(const Link<const Size&, void>& rLink) override;
+
+ virtual void connect_mouse_press(const Link<const MouseEvent&, bool>& rLink) override;
+
+ virtual void connect_mouse_move(const Link<const MouseEvent&, bool>& rLink) override;
+
+ virtual void connect_mouse_release(const Link<const MouseEvent&, bool>& rLink) override;
+
+ virtual void connect_key_press(const Link<const KeyEvent&, bool>& rLink) override;
+
+ virtual void connect_key_release(const Link<const KeyEvent&, bool>& rLink) override;
+
+ virtual void connect_style_updated(const Link<Widget&, void>& rLink) override;
+
+ virtual bool get_extents_relative_to(const Widget& rRelative, int& x, int& y, int& width,
+ int& height) const override;
+
+ virtual void grab_add() override;
+
+ virtual bool has_grab() const override;
+
+ virtual void grab_remove() override;
+
+ virtual bool get_direction() const override;
+
+ virtual void set_direction(bool bRTL) override;
+
+ virtual void freeze() override;
+
+ virtual void thaw() override;
+
+ virtual void set_busy_cursor(bool bBusy) override;
+
+ virtual std::unique_ptr<weld::Container> weld_parent() const override;
+
+ virtual ~SalInstanceWidget() override;
+
+ vcl::Window* getWidget() const;
+
+ void disable_notify_events();
+
+ bool notify_events_disabled() const;
+
+ void enable_notify_events();
+
+ virtual void queue_resize() override;
+
+ virtual void help_hierarchy_foreach(const std::function<bool(const OUString&)>& func) override;
+
+ virtual OUString strip_mnemonic(const OUString& rLabel) const override;
+
+ virtual VclPtr<VirtualDevice> create_virtual_device() const override;
+
+ virtual css::uno::Reference<css::datatransfer::dnd::XDropTarget> get_drop_target() override;
+ virtual css::uno::Reference<css::datatransfer::clipboard::XClipboard>
+ get_clipboard() const override;
+
+ virtual void connect_get_property_tree(const Link<tools::JsonWriter&, void>& rLink) override;
+
+ virtual void get_property_tree(tools::JsonWriter& rJsonWriter) override;
+
+ virtual void call_attention_to() override;
+
+ virtual void set_stack_background() override;
+
+ virtual void set_title_background() override;
+
+ virtual void set_toolbar_background() override;
+
+ virtual void set_highlight_background() override;
+
+ virtual void set_background(const Color& rColor) override;
+
+ virtual void draw(OutputDevice& rOutput, const Point& rPos, const Size& rSizePixel) override;
+
+ SystemWindow* getSystemWindow();
+};
+
+class SalInstanceLabel : public SalInstanceWidget, public virtual weld::Label
+{
+private:
+ // Control instead of FixedText so we can also use this for
+ // SelectableFixedText which is derived from Edit. We just typically need
+ // [G|S]etText which exists in their shared baseclass
+ VclPtr<Control> m_xLabel;
+
+public:
+ SalInstanceLabel(Control* pLabel, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual void set_label(const OUString& rText) override;
+
+ virtual OUString get_label() const override;
+
+ virtual void set_mnemonic_widget(Widget* pTarget) override;
+
+ virtual void set_label_type(weld::LabelType eType) override;
+
+ virtual void set_font(const vcl::Font& rFont) override;
+
+ virtual void set_font_color(const Color& rColor) override;
+};
+
+class SalInstanceContainer : public SalInstanceWidget, public virtual weld::Container
+{
+ VclPtr<vcl::Window> m_xContainer;
+
+public:
+ SalInstanceContainer(vcl::Window* pContainer, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+ virtual void HandleEventListener(VclWindowEvent& rEvent) override;
+ virtual void connect_container_focus_changed(const Link<Container&, void>& rLink) override;
+ virtual void child_grab_focus() override;
+ virtual void move(weld::Widget* pWidget, weld::Container* pNewParent) override;
+ virtual css::uno::Reference<css::awt::XWindow> CreateChildFrame() override;
+};
+
+class SalInstanceWindow : public SalInstanceContainer, public virtual weld::Window
+{
+private:
+ VclPtr<vcl::Window> m_xWindow;
+
+ DECL_LINK(HelpHdl, vcl::Window&, bool);
+
+ void override_child_help(vcl::Window* pParent);
+
+ void clear_child_help(vcl::Window* pParent);
+
+ void recursively_unset_default_buttons();
+
+ void implResetDefault(const vcl::Window* _pWindow);
+
+public:
+ SalInstanceWindow(vcl::Window* pWindow, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual void set_title(const OUString& rTitle) override;
+
+ virtual OUString get_title() const override;
+
+ void help();
+
+ virtual css::uno::Reference<css::awt::XWindow> GetXWindow() override;
+
+ virtual void resize_to_request() override;
+
+ virtual void set_modal(bool bModal) override;
+
+ virtual bool get_modal() const override;
+
+ virtual void window_move(int x, int y) override;
+
+ virtual Size get_size() const override;
+
+ virtual Point get_position() const override;
+
+ virtual AbsoluteScreenPixelRectangle get_monitor_workarea() const override;
+
+ virtual void set_centered_on_parent(bool /*bTrackGeometryRequests*/) override;
+
+ virtual bool get_resizable() const override;
+
+ virtual bool has_toplevel_focus() const override;
+
+ virtual void present() override;
+
+ virtual void change_default_widget(weld::Widget* pOld, weld::Widget* pNew) override;
+
+ virtual bool is_default_widget(const weld::Widget* pCandidate) const override;
+
+ virtual void set_window_state(const OUString& rStr) override;
+
+ virtual OUString get_window_state(vcl::WindowDataMask nMask) const override;
+
+ virtual SystemEnvData get_system_data() const override;
+
+ virtual weld::ScreenShotCollection collect_screenshot_data() override;
+
+ virtual VclPtr<VirtualDevice> screenshot() override;
+
+ virtual const vcl::ILibreOfficeKitNotifier* GetLOKNotifier() override;
+
+ virtual ~SalInstanceWindow() override;
+};
+
+class SalInstanceDialog : public SalInstanceWindow, public virtual weld::Dialog
+{
+protected:
+ VclPtr<::Dialog> m_xDialog;
+
+private:
+ // for calc ref dialog that shrink to range selection widgets and resize back
+ VclPtr<vcl::Window> m_xRefEdit;
+ std::vector<VclPtr<vcl::Window>> m_aHiddenWidgets; // vector of hidden Controls
+ tools::Long m_nOldEditWidthReq; // Original width request of the input field
+ sal_Int32 m_nOldBorderWidth; // border width for expanded dialog
+
+ DECL_LINK(PopupScreenShotMenuHdl, const CommandEvent&, bool);
+
+public:
+ SalInstanceDialog(::Dialog* pDialog, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual bool runAsync(std::shared_ptr<weld::DialogController> aOwner,
+ const std::function<void(sal_Int32)>& rEndDialogFn) override;
+
+ virtual bool runAsync(std::shared_ptr<Dialog> const& rxSelf,
+ const std::function<void(sal_Int32)>& rEndDialogFn) override;
+
+ virtual void collapse(weld::Widget* pEdit, weld::Widget* pButton) override;
+
+ virtual void undo_collapse() override;
+
+ virtual void
+ SetInstallLOKNotifierHdl(const Link<void*, vcl::ILibreOfficeKitNotifier*>& rLink) override;
+
+ virtual int run() override;
+
+ virtual void response(int nResponse) override;
+
+ virtual void add_button(const OUString& rText, int nResponse,
+ const OUString& rHelpId = {}) override;
+
+ virtual void set_modal(bool bModal) override;
+
+ virtual bool get_modal() const override;
+
+ virtual weld::Button* weld_widget_for_response(int nResponse) override;
+
+ virtual void set_default_response(int nResponse) override;
+
+ virtual weld::Container* weld_content_area() override;
+};
+
+class SalInstanceAssistant : public SalInstanceDialog, public virtual weld::Assistant
+{
+protected:
+ VclPtr<vcl::RoadmapWizard> m_xWizard;
+
+private:
+ 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::u16string_view rIdent) const;
+ int find_id(int nId) const;
+
+ DECL_LINK(OnRoadmapItemSelected, LinkParamNone*, void);
+ DECL_LINK(UpdateRoadmap_Hdl, Timer*, void);
+
+public:
+ SalInstanceAssistant(vcl::RoadmapWizard* pDialog, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+ virtual int get_current_page() const override;
+ virtual int get_n_pages() const override;
+ virtual OUString get_page_ident(int nPage) const override;
+ virtual OUString get_current_page_ident() const override;
+ virtual void set_current_page(int nPage) override;
+ virtual void set_current_page(const OUString& rIdent) override;
+ virtual void set_page_index(const OUString& rIdent, int nNewIndex) override;
+ virtual weld::Container* append_page(const OUString& rIdent) override;
+ virtual OUString get_page_title(const OUString& rIdent) const override;
+ virtual void set_page_title(const OUString& rIdent, const OUString& rTitle) override;
+ virtual void set_page_sensitive(const OUString& rIdent, bool bSensitive) override;
+ virtual void set_page_side_help_id(const OUString& rHelpId) override;
+ virtual void set_page_side_image(const OUString& rImage) override;
+ weld::Button* weld_widget_for_response(int nResponse) override;
+
+ virtual ~SalInstanceAssistant() override;
+};
+
+class WeldTextFilter final : public TextFilter
+{
+private:
+ Link<OUString&, bool>& m_rInsertTextHdl;
+
+public:
+ WeldTextFilter(Link<OUString&, bool>& rInsertTextHdl);
+
+ virtual OUString filter(const OUString& rText) override;
+};
+
+class SalInstanceEntry : public SalInstanceWidget, public virtual weld::Entry
+{
+private:
+ VclPtr<::Edit> m_xEntry;
+
+ DECL_LINK(ChangeHdl, Edit&, void);
+ DECL_LINK(CursorListener, VclWindowEvent&, void);
+ DECL_LINK(ActivateHdl, Edit&, bool);
+
+ WeldTextFilter m_aTextFilter;
+
+public:
+ SalInstanceEntry(::Edit* pEntry, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual void set_text(const OUString& rText) override;
+
+ virtual OUString get_text() const override;
+
+ virtual void set_width_chars(int nChars) override;
+
+ virtual int get_width_chars() const override;
+
+ virtual void set_max_length(int nChars) override;
+
+ virtual void select_region(int nStartPos, int nEndPos) override;
+
+ bool get_selection_bounds(int& rStartPos, int& rEndPos) override;
+
+ virtual void replace_selection(const OUString& rText) override;
+
+ virtual void set_position(int nCursorPos) override;
+
+ virtual int get_position() const override;
+
+ virtual void set_editable(bool bEditable) override;
+
+ virtual bool get_editable() const override;
+
+ virtual void set_overwrite_mode(bool bOn) override;
+
+ virtual bool get_overwrite_mode() const override;
+
+ virtual void set_message_type(weld::EntryMessageType eType) override;
+
+ virtual void set_font(const vcl::Font& rFont) override;
+
+ virtual void set_font_color(const Color& rColor) override;
+
+ virtual void connect_cursor_position(const Link<Entry&, void>& rLink) override;
+
+ virtual void set_placeholder_text(const OUString& rText) override;
+
+ Edit& getEntry();
+
+ void fire_signal_changed();
+
+ virtual void cut_clipboard() override;
+
+ virtual void copy_clipboard() override;
+
+ virtual void paste_clipboard() override;
+
+ virtual void set_alignment(TxtAlign eXAlign) override;
+
+ virtual ~SalInstanceEntry() override;
+};
+
+class SalInstanceSpinButton : public SalInstanceEntry, public virtual weld::SpinButton
+{
+ VclPtr<FormattedField> m_xButton;
+
+protected:
+ Formatter& m_rFormatter;
+
+private:
+ DECL_LINK(UpDownHdl, SpinField&, void);
+ DECL_LINK(LoseFocusHdl, Control&, void);
+ DECL_LINK(OutputHdl, LinkParamNone*, bool);
+ DECL_LINK(InputHdl, sal_Int64*, TriState);
+ DECL_LINK(ActivateHdl, Edit&, bool);
+
+ double toField(sal_Int64 nValue) const;
+
+ sal_Int64 fromField(double fValue) const;
+
+public:
+ SalInstanceSpinButton(FormattedField* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual sal_Int64 get_value() const override;
+
+ virtual void set_value(sal_Int64 value) override;
+
+ virtual void set_range(sal_Int64 min, sal_Int64 max) override;
+
+ virtual void get_range(sal_Int64& min, sal_Int64& max) const override;
+
+ virtual void set_increments(int step, int /*page*/) override;
+
+ virtual void get_increments(int& step, int& page) const override;
+
+ virtual void set_digits(unsigned int digits) override;
+
+ // SpinButton may be comprised of multiple subwidgets, consider the lot as
+ // one thing for focus
+ virtual bool has_focus() const override;
+
+ //off by default for direct SpinButtons, MetricSpinButton enables it
+ void SetUseThousandSep();
+
+ virtual unsigned int get_digits() const override;
+
+ virtual ~SalInstanceSpinButton() override;
+};
+
+//ComboBox and ListBox have similar apis, ComboBoxes in LibreOffice have an edit box and ListBoxes
+//don't. This distinction isn't there in Gtk. Use a template to sort this problem out.
+template <class vcl_type>
+class SalInstanceComboBox : public SalInstanceWidget, public virtual weld::ComboBox
+{
+protected:
+ // owner for ListBox/ComboBox UserData
+ std::vector<std::shared_ptr<OUString>> m_aUserData;
+ VclPtr<vcl_type> m_xComboBox;
+ ScopedVclPtr<MenuButton> m_xMenuButton;
+ OUString m_sMenuButtonRow;
+
+public:
+ SalInstanceComboBox(vcl_type* pComboBox, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceWidget(pComboBox, pBuilder, bTakeOwnership)
+ , m_xComboBox(pComboBox)
+ {
+ }
+
+ virtual int get_active() const override
+ {
+ const sal_Int32 nRet = m_xComboBox->GetSelectedEntryPos();
+ if (nRet == LISTBOX_ENTRY_NOTFOUND)
+ return -1;
+ return nRet;
+ }
+
+ const OUString* getEntryData(int index) const
+ {
+ return static_cast<const OUString*>(m_xComboBox->GetEntryData(index));
+ }
+
+ // ComboBoxes are comprised of multiple subwidgets, consider the lot as
+ // one thing for focus
+ virtual bool has_focus() const override
+ {
+ return m_xWidget->HasChildPathFocus()
+ || (m_xMenuButton && (m_xMenuButton->HasFocus() || m_xMenuButton->InPopupMode()));
+ }
+
+ virtual OUString get_active_id() const override
+ {
+ sal_Int32 nPos = m_xComboBox->GetSelectedEntryPos();
+ const OUString* pRet;
+ if (nPos != LISTBOX_ENTRY_NOTFOUND)
+ pRet = getEntryData(m_xComboBox->GetSelectedEntryPos());
+ else
+ pRet = nullptr;
+ if (!pRet)
+ return OUString();
+ return *pRet;
+ }
+
+ virtual void set_active_id(const OUString& rStr) override
+ {
+ for (int i = 0; i < get_count(); ++i)
+ {
+ const OUString* pId = getEntryData(i);
+ if (!pId)
+ continue;
+ if (*pId == rStr)
+ m_xComboBox->SelectEntryPos(i);
+ }
+ }
+
+ virtual void set_active(int pos) override
+ {
+ assert(m_xComboBox->IsUpdateMode()
+ && "don't set_active when frozen, set_active after thaw. Note selection doesn't "
+ "survive a "
+ "freeze");
+ if (pos == -1)
+ {
+ m_xComboBox->SetNoSelection();
+ return;
+ }
+ m_xComboBox->SelectEntryPos(pos);
+ }
+
+ virtual OUString get_text(int pos) const override { return m_xComboBox->GetEntry(pos); }
+
+ virtual OUString get_id(int pos) const override
+ {
+ const OUString* pRet = getEntryData(pos);
+ if (!pRet)
+ return OUString();
+ return *pRet;
+ }
+
+ virtual void set_id(int row, const OUString& rId) override
+ {
+ m_aUserData.emplace_back(std::make_unique<OUString>(rId));
+ m_xComboBox->SetEntryData(row, m_aUserData.back().get());
+ }
+
+ virtual void insert_vector(const std::vector<weld::ComboBoxEntry>& rItems,
+ bool bKeepExisting) override
+ {
+ freeze();
+ if (!bKeepExisting)
+ clear();
+ for (const auto& rItem : rItems)
+ {
+ insert(-1, rItem.sString, rItem.sId.isEmpty() ? nullptr : &rItem.sId,
+ rItem.sImage.isEmpty() ? nullptr : &rItem.sImage, nullptr);
+ }
+ thaw();
+ }
+
+ virtual int get_count() const override { return m_xComboBox->GetEntryCount(); }
+
+ virtual int find_text(const OUString& rStr) const override
+ {
+ const sal_Int32 nRet = m_xComboBox->GetEntryPos(rStr);
+ if (nRet == LISTBOX_ENTRY_NOTFOUND)
+ return -1;
+ return nRet;
+ }
+
+ virtual int find_id(const OUString& rStr) const override
+ {
+ for (int i = 0; i < get_count(); ++i)
+ {
+ const OUString* pId = getEntryData(i);
+ if (!pId)
+ continue;
+ if (*pId == rStr)
+ return i;
+ }
+ return -1;
+ }
+
+ virtual void clear() override
+ {
+ m_xComboBox->Clear();
+ m_aUserData.clear();
+ }
+
+ virtual void make_sorted() override
+ {
+ m_xComboBox->SetStyle(m_xComboBox->GetStyle() | WB_SORT);
+ }
+
+ virtual bool get_popup_shown() const override { return m_xComboBox->IsInDropDown(); }
+
+ virtual void connect_popup_toggled(const Link<ComboBox&, void>& rLink) override
+ {
+ weld::ComboBox::connect_popup_toggled(rLink);
+ ensure_event_listener();
+ }
+
+ void call_signal_custom_render(UserDrawEvent* pEvent)
+ {
+ vcl::RenderContext* pRenderContext = pEvent->GetRenderContext();
+ auto nPos = pEvent->GetItemId();
+ const tools::Rectangle& rRect = pEvent->GetRect();
+ const OUString sId = get_id(nPos);
+ signal_custom_render(*pRenderContext, rRect, pEvent->IsSelected(), sId);
+ m_xComboBox->DrawEntry(*pEvent); // draw separator
+
+ if (m_xMenuButton && m_xMenuButton->IsVisible() && m_sMenuButtonRow == sId)
+ {
+ vcl::Window* pEventWindow = m_xComboBox->GetMainWindow();
+ if (m_xMenuButton->GetParent() != pEventWindow)
+ m_xMenuButton->SetParent(pEventWindow);
+ int nButtonWidth = get_menu_button_width();
+ m_xMenuButton->SetSizePixel(Size(nButtonWidth, rRect.GetHeight()));
+ m_xMenuButton->SetPosPixel(Point(rRect.GetWidth() - nButtonWidth, rRect.Top()));
+ }
+ }
+
+ VclPtr<VirtualDevice> create_render_virtual_device() const override
+ {
+ auto xRet = VclPtr<VirtualDevice>::Create();
+ xRet->SetBackground(Application::GetSettings().GetStyleSettings().GetFieldColor());
+ return xRet;
+ }
+
+ virtual void set_item_menu(const OUString& rIdent, weld::Menu* pMenu) override
+ {
+ SalInstanceMenu* pInstanceMenu = dynamic_cast<SalInstanceMenu*>(pMenu);
+
+ PopupMenu* pPopup = pInstanceMenu ? pInstanceMenu->getMenu() : nullptr;
+
+ if (!m_xMenuButton)
+ m_xMenuButton
+ = VclPtr<MenuButton>::Create(m_xComboBox, WB_FLATBUTTON | WB_NOPOINTERFOCUS);
+
+ m_xMenuButton->SetPopupMenu(pPopup);
+ m_xMenuButton->Show(pPopup != nullptr);
+ m_sMenuButtonRow = rIdent;
+ }
+
+ int get_menu_button_width() const override
+ {
+ OutputDevice* pDefault = Application::GetDefaultDevice();
+ return 20 * (pDefault ? pDefault->GetDPIScaleFactor() : 1.0);
+ }
+
+ void CallHandleEventListener(VclWindowEvent& rEvent)
+ {
+ if (rEvent.GetId() == VclEventId::DropdownPreOpen
+ || rEvent.GetId() == VclEventId::DropdownClose)
+ {
+ signal_popup_toggled();
+ return;
+ }
+ SalInstanceWidget::HandleEventListener(rEvent);
+ }
+};
+
+class SalInstanceComboBoxWithoutEdit : public SalInstanceComboBox<ListBox>
+{
+private:
+ DECL_LINK(SelectHdl, ListBox&, void);
+
+public:
+ SalInstanceComboBoxWithoutEdit(ListBox* pListBox, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual OUString get_active_text() const override;
+
+ virtual void remove(int pos) override;
+
+ virtual void insert(int pos, const OUString& rStr, const OUString* pId,
+ const OUString* pIconName, VirtualDevice* pImageSurface) override;
+
+ virtual void insert_separator(int pos, const OUString& /*rId*/) override;
+
+ virtual bool has_entry() const override;
+
+ virtual bool changed_by_direct_pick() const override;
+
+ virtual void set_entry_message_type(weld::EntryMessageType /*eType*/) override;
+
+ virtual void set_entry_text(const OUString& /*rText*/) override;
+
+ virtual void select_entry_region(int /*nStartPos*/, int /*nEndPos*/) override;
+
+ virtual bool get_entry_selection_bounds(int& /*rStartPos*/, int& /*rEndPos*/) override;
+
+ virtual void set_entry_width_chars(int /*nChars*/) override;
+
+ virtual void set_entry_max_length(int /*nChars*/) override;
+
+ virtual void set_entry_completion(bool, bool bCaseSensitive = false) override;
+
+ virtual void set_entry_placeholder_text(const OUString&) override;
+
+ virtual void set_entry_editable(bool bEditable) override;
+
+ virtual void cut_entry_clipboard() override;
+
+ virtual void copy_entry_clipboard() override;
+
+ virtual void paste_entry_clipboard() override;
+
+ virtual void set_font(const vcl::Font& rFont) override;
+
+ virtual void set_entry_font(const vcl::Font&) override;
+
+ virtual vcl::Font get_entry_font() override;
+
+ virtual void set_custom_renderer(bool bOn) override;
+
+ virtual int get_max_mru_count() const override;
+
+ virtual void set_max_mru_count(int) override;
+
+ virtual OUString get_mru_entries() const override;
+
+ virtual void set_mru_entries(const OUString&) override;
+
+ virtual void HandleEventListener(VclWindowEvent& rEvent) override;
+
+ virtual ~SalInstanceComboBoxWithoutEdit() override;
+};
+
+class SalInstanceComboBoxWithEdit : public SalInstanceComboBox<ComboBox>
+{
+private:
+ DECL_LINK(ChangeHdl, Edit&, void);
+ DECL_LINK(EntryActivateHdl, Edit&, bool);
+ DECL_LINK(SelectHdl, ::ComboBox&, void);
+ DECL_LINK(UserDrawHdl, UserDrawEvent*, void);
+ WeldTextFilter m_aTextFilter;
+ bool m_bInSelect;
+
+public:
+ SalInstanceComboBoxWithEdit(::ComboBox* pComboBox, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual bool has_entry() const override;
+
+ virtual bool changed_by_direct_pick() const override;
+
+ virtual void set_entry_message_type(weld::EntryMessageType eType) override;
+
+ virtual OUString get_active_text() const override;
+
+ virtual void remove(int pos) override;
+
+ virtual void insert(int pos, const OUString& rStr, const OUString* pId,
+ const OUString* pIconName, VirtualDevice* pImageSurface) override;
+
+ virtual void insert_separator(int pos, const OUString& /*rId*/) override;
+
+ virtual void set_entry_text(const OUString& rText) override;
+
+ virtual void set_entry_width_chars(int nChars) override;
+
+ virtual void set_entry_max_length(int nChars) override;
+
+ virtual void set_entry_completion(bool bEnable, bool bCaseSensitive = false) override;
+
+ virtual void set_entry_placeholder_text(const OUString& rText) override;
+
+ virtual void set_entry_editable(bool bEditable) override;
+
+ virtual void cut_entry_clipboard() override;
+
+ virtual void copy_entry_clipboard() override;
+
+ virtual void paste_entry_clipboard() override;
+
+ virtual void select_entry_region(int nStartPos, int nEndPos) override;
+
+ virtual bool get_entry_selection_bounds(int& rStartPos, int& rEndPos) override;
+
+ virtual void set_font(const vcl::Font& rFont) override;
+
+ virtual void set_entry_font(const vcl::Font& rFont) override;
+
+ virtual vcl::Font get_entry_font() override;
+
+ virtual void set_custom_renderer(bool bOn) override;
+
+ virtual int get_max_mru_count() const override;
+
+ virtual void set_max_mru_count(int nCount) override;
+
+ virtual OUString get_mru_entries() const override;
+
+ virtual void set_mru_entries(const OUString& rEntries) override;
+
+ virtual void HandleEventListener(VclWindowEvent& rEvent) override;
+
+ virtual void call_attention_to() override;
+
+ virtual ~SalInstanceComboBoxWithEdit() override;
+};
+
+class SalInstanceButton : public SalInstanceWidget, public virtual weld::Button
+{
+private:
+ VclPtr<::Button> m_xButton;
+ Link<::Button*, void> const m_aOldClickHdl;
+
+ DECL_LINK(ClickHdl, ::Button*, void);
+
+public:
+ SalInstanceButton(::Button* pButton, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual void set_label(const OUString& rText) override;
+
+ virtual void set_image(VirtualDevice* pDevice) override;
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override;
+
+ virtual void set_from_icon_name(const OUString& rIconName) override;
+
+ virtual OUString get_label() const override;
+
+ virtual void set_font(const vcl::Font& rFont) override;
+
+ virtual void set_custom_button(VirtualDevice* pDevice) override;
+
+ virtual ~SalInstanceButton() override;
+};
+
+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)
+ {
+ m_xToggleButton->setToggleButton(true);
+ }
+
+ 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));
+ }
+};
+
+class SalInstanceNotebook : public SalInstanceWidget, public virtual weld::Notebook
+{
+private:
+ VclPtr<TabControl> m_xNotebook;
+ mutable std::vector<std::shared_ptr<SalInstanceContainer>> m_aPages;
+ std::map<OUString, std::pair<VclPtr<TabPage>, VclPtr<VclGrid>>> m_aAddedPages;
+
+ DECL_LINK(DeactivatePageHdl, TabControl*, bool);
+ DECL_LINK(ActivatePageHdl, TabControl*, void);
+
+public:
+ SalInstanceNotebook(TabControl* pNotebook, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual int get_current_page() const override;
+
+ virtual int get_page_index(const OUString& rIdent) const override;
+
+ virtual OUString get_page_ident(int nPage) const override;
+
+ virtual OUString get_current_page_ident() const override;
+
+ virtual weld::Container* get_page(const OUString& rIdent) const override;
+
+ virtual void set_current_page(int nPage) override;
+
+ virtual void set_current_page(const OUString& rIdent) override;
+
+ virtual void remove_page(const OUString& rIdent) override;
+
+ virtual void insert_page(const OUString& rIdent, const OUString& rLabel, int nPos) override;
+
+ virtual int get_n_pages() const override;
+
+ virtual OUString get_tab_label_text(const OUString& rIdent) const override;
+
+ virtual void set_tab_label_text(const OUString& rIdent, const OUString& rText) override;
+
+ virtual void set_show_tabs(bool bShow) override;
+
+ virtual ~SalInstanceNotebook() override;
+};
+
+class SalInstanceMessageDialog : public SalInstanceDialog, public virtual weld::MessageDialog
+{
+protected:
+ VclPtr<::MessageDialog> m_xMessageDialog;
+
+public:
+ SalInstanceMessageDialog(::MessageDialog* pDialog, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual void set_primary_text(const OUString& rText) override;
+
+ virtual OUString get_primary_text() const override;
+
+ virtual void set_secondary_text(const OUString& rText) override;
+
+ virtual OUString get_secondary_text() const override;
+
+ virtual weld::Container* weld_message_area() override;
+};
+
+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 void set_label_wrap(bool wrap) override;
+
+ virtual ~SalInstanceLinkButton() override { m_xButton->SetClickHdl(m_aOrigClickHdl); }
+};
+
+class SalInstanceCheckButton : public SalInstanceButton, public virtual weld::CheckButton
+{
+private:
+ VclPtr<CheckBox> m_xCheckButton;
+
+ DECL_LINK(ToggleHdl, CheckBox&, void);
+
+public:
+ SalInstanceCheckButton(CheckBox* pButton, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual void set_active(bool active) override;
+
+ virtual bool get_active() const override;
+
+ virtual void set_inconsistent(bool inconsistent) override;
+
+ virtual bool get_inconsistent() const override;
+
+ virtual void set_label(const OUString& rText) override { SalInstanceButton::set_label(rText); }
+
+ virtual OUString get_label() const override { return SalInstanceButton::get_label(); }
+
+ virtual void set_label_wrap(bool wrap) override;
+
+ virtual ~SalInstanceCheckButton() override;
+};
+
+class SalInstanceDrawingArea : public SalInstanceWidget, public virtual weld::DrawingArea
+{
+private:
+ VclPtr<VclDrawingArea> m_xDrawingArea;
+
+ typedef std::pair<vcl::RenderContext&, const tools::Rectangle&> target_and_area;
+ DECL_LINK(PaintHdl, target_and_area, void);
+ DECL_LINK(ResizeHdl, const Size&, void);
+ DECL_LINK(MousePressHdl, const MouseEvent&, bool);
+ DECL_LINK(MouseMoveHdl, const MouseEvent&, bool);
+ DECL_LINK(MouseReleaseHdl, const MouseEvent&, bool);
+ DECL_LINK(KeyPressHdl, const KeyEvent&, bool);
+ DECL_LINK(KeyReleaseHdl, const KeyEvent&, bool);
+ DECL_LINK(StyleUpdatedHdl, VclDrawingArea&, void);
+ DECL_LINK(CommandHdl, const CommandEvent&, bool);
+ DECL_LINK(QueryTooltipHdl, tools::Rectangle&, OUString);
+ DECL_LINK(GetSurroundingHdl, OUString&, int);
+ DECL_LINK(DeleteSurroundingHdl, const Selection&, bool);
+ DECL_LINK(StartDragHdl, VclDrawingArea*, bool);
+
+ // SalInstanceWidget has a generic listener for all these
+ // events, ignore the ones we have specializations for
+ // in VclDrawingArea
+ virtual void HandleEventListener(VclWindowEvent& rEvent) override;
+
+ virtual void HandleMouseEventListener(VclWindowEvent& rEvent) override;
+
+ virtual bool HandleKeyEventListener(VclWindowEvent& /*rEvent*/) override;
+
+public:
+ SalInstanceDrawingArea(VclDrawingArea* pDrawingArea, SalInstanceBuilder* pBuilder,
+ const a11yref& rAlly, FactoryFunction pUITestFactoryFunction,
+ void* pUserData, bool bTakeOwnership);
+
+ virtual void queue_draw() override;
+
+ virtual void queue_draw_area(int x, int y, int width, int height) override;
+
+ virtual void connect_size_allocate(const Link<const Size&, void>& rLink) override;
+
+ virtual void connect_key_press(const Link<const KeyEvent&, bool>& rLink) override;
+
+ virtual void connect_key_release(const Link<const KeyEvent&, bool>& rLink) override;
+
+ virtual void connect_style_updated(const Link<Widget&, void>& rLink) override;
+
+ virtual void set_cursor(PointerStyle ePointerStyle) override;
+
+ virtual Point get_pointer_position() const override;
+
+ virtual void set_input_context(const InputContext& rInputContext) override;
+
+ virtual void im_context_set_cursor_location(const tools::Rectangle& rCursorRect,
+ int nExtTextInputWidth) override;
+
+ virtual a11yref get_accessible_parent() override;
+
+ virtual a11yrelationset get_accessible_relation_set() override;
+
+ virtual AbsoluteScreenPixelPoint get_accessible_location_on_screen() override;
+
+ virtual void enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper,
+ sal_uInt8 eDNDConstants) override;
+
+ virtual ~SalInstanceDrawingArea() override;
+
+ virtual OutputDevice& get_ref_device() override;
+
+ virtual void click(const Point& rPos) override;
+
+ virtual void dblclick(const Point& rPos) override;
+
+ virtual void mouse_up(const Point& rPos) override;
+
+ virtual void mouse_down(const Point& rPos) override;
+
+ virtual void mouse_move(const Point& rPos) override;
+};
+
+class SalInstanceToolbar : public SalInstanceWidget, public virtual weld::Toolbar
+{
+protected:
+ VclPtr<ToolBox> m_xToolBox;
+ std::map<ToolBoxItemId, VclPtr<vcl::Window>> m_aFloats;
+ std::map<ToolBoxItemId, VclPtr<PopupMenu>> m_aMenus;
+
+ OUString m_sStartShowIdent;
+
+ DECL_LINK(ClickHdl, ToolBox*, void);
+ DECL_LINK(DropdownClick, ToolBox*, void);
+ DECL_LINK(MenuToggleListener, VclWindowEvent&, void);
+
+public:
+ SalInstanceToolbar(ToolBox* pToolBox, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual void set_item_sensitive(const OUString& rIdent, bool bSensitive) override;
+
+ virtual bool get_item_sensitive(const OUString& rIdent) const override;
+
+ virtual void set_item_visible(const OUString& rIdent, bool bVisible) override;
+
+ virtual void set_item_help_id(const OUString& rIdent, const OUString& rHelpId) override;
+
+ virtual bool get_item_visible(const OUString& rIdent) const override;
+
+ virtual void set_item_active(const OUString& rIdent, bool bActive) override;
+
+ virtual bool get_item_active(const OUString& rIdent) const override;
+
+ void set_menu_item_active(const OUString& rIdent, bool bActive) override;
+
+ bool get_menu_item_active(const OUString& rIdent) const override;
+
+ virtual void set_item_popover(const OUString& rIdent, weld::Widget* pPopover) override;
+
+ virtual void set_item_menu(const OUString& rIdent, weld::Menu* pMenu) override;
+
+ virtual void insert_item(int pos, const OUString& rId) override;
+
+ virtual void insert_separator(int pos, const OUString& /*rId*/) override;
+
+ virtual int get_n_items() const override;
+
+ virtual OUString get_item_ident(int nIndex) const override;
+
+ virtual void set_item_ident(int nIndex, const OUString& rIdent) override;
+
+ virtual void set_item_label(int nIndex, const OUString& rLabel) override;
+
+ virtual OUString get_item_label(const OUString& rIdent) const override;
+
+ virtual void set_item_label(const OUString& rIdent, const OUString& rLabel) override;
+
+ virtual void set_item_icon_name(const OUString& rIdent, const OUString& rIconName) override;
+
+ virtual void set_item_image_mirrored(const OUString& rIdent, bool bMirrored) override;
+
+ virtual void set_item_image(const OUString& rIdent,
+ const css::uno::Reference<css::graphic::XGraphic>& rIcon) override;
+
+ virtual void set_item_image(const OUString& rIdent, VirtualDevice* pDevice) override;
+
+ virtual void set_item_image(int nIndex,
+ const css::uno::Reference<css::graphic::XGraphic>& rIcon) override;
+
+ virtual void set_item_tooltip_text(int nIndex, const OUString& rTip) override;
+
+ virtual void set_item_tooltip_text(const OUString& rIdent, const OUString& rTip) override;
+
+ virtual OUString get_item_tooltip_text(const OUString& rIdent) const override;
+
+ virtual vcl::ImageType get_icon_size() const override;
+
+ virtual void set_icon_size(vcl::ImageType eType) override;
+
+ virtual sal_uInt16 get_modifier_state() const override;
+
+ virtual int get_drop_index(const Point& rPoint) const override;
+
+ virtual ~SalInstanceToolbar() override;
+};
+
+class SalInstanceTextView : public SalInstanceWidget, public virtual weld::TextView
+{
+private:
+ VclPtr<VclMultiLineEdit> m_xTextView;
+ Link<ScrollBar*, void> m_aOrigVScrollHdl;
+
+ DECL_LINK(ChangeHdl, Edit&, void);
+ DECL_LINK(VscrollHdl, ScrollBar*, void);
+ DECL_LINK(CursorListener, VclWindowEvent&, void);
+
+public:
+ SalInstanceTextView(VclMultiLineEdit* pTextView, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual void set_text(const OUString& rText) override;
+
+ virtual void replace_selection(const OUString& rText) override;
+
+ virtual OUString get_text() const override;
+
+ bool get_selection_bounds(int& rStartPos, int& rEndPos) override;
+
+ virtual void select_region(int nStartPos, int nEndPos) override;
+
+ virtual void set_editable(bool bEditable) override;
+ virtual bool get_editable() const override;
+ virtual void set_max_length(int nChars) override;
+
+ virtual void set_monospace(bool bMonospace) override;
+
+ virtual void set_font(const vcl::Font& rFont) override;
+
+ virtual void set_font_color(const Color& rColor) override;
+
+ virtual void connect_cursor_position(const Link<TextView&, void>& rLink) override;
+
+ virtual bool can_move_cursor_with_up() const override;
+
+ virtual bool can_move_cursor_with_down() const override;
+
+ virtual void cut_clipboard() override;
+
+ virtual void copy_clipboard() override;
+
+ virtual void paste_clipboard() override;
+
+ virtual void set_alignment(TxtAlign eXAlign) override;
+
+ virtual int vadjustment_get_value() const override;
+
+ virtual void vadjustment_set_value(int value) override;
+
+ virtual int vadjustment_get_upper() const override;
+
+ virtual int vadjustment_get_lower() const override;
+
+ virtual int vadjustment_get_page_size() const override;
+
+ virtual bool has_focus() const override;
+
+ virtual ~SalInstanceTextView() override;
+};
+
+struct SalInstanceTreeIter final : public weld::TreeIter
+{
+ SalInstanceTreeIter(const SalInstanceTreeIter* pOrig)
+ : iter(pOrig ? pOrig->iter : nullptr)
+ {
+ }
+ SalInstanceTreeIter(SvTreeListEntry* pIter)
+ : iter(pIter)
+ {
+ }
+ virtual bool equal(const TreeIter& rOther) const override
+ {
+ return iter == static_cast<const SalInstanceTreeIter&>(rOther).iter;
+ }
+ SvTreeListEntry* iter;
+};
+
+class SalInstanceTreeView : public SalInstanceWidget, public virtual weld::TreeView
+{
+protected:
+ // owner for UserData
+ std::vector<std::unique_ptr<OUString>> m_aUserData;
+ VclPtr<SvTabListBox> m_xTreeView;
+ SvLBoxButtonData m_aCheckButtonData;
+ SvLBoxButtonData m_aRadioButtonData;
+ // currently expanding parent that logically, but not currently physically,
+ // contain placeholders
+ o3tl::sorted_vector<SvTreeListEntry*> m_aExpandingPlaceHolderParents;
+ // which columns should be custom rendered
+ o3tl::sorted_vector<int> m_aCustomRenders;
+ bool m_bTogglesAsRadio;
+ int m_nSortColumn;
+
+ DECL_LINK(SelectHdl, SvTreeListBox*, void);
+ DECL_LINK(DeSelectHdl, SvTreeListBox*, void);
+ DECL_LINK(DoubleClickHdl, SvTreeListBox*, bool);
+ DECL_LINK(ExpandingHdl, SvTreeListBox*, bool);
+ DECL_LINK(EndDragHdl, HeaderBar*, void);
+ DECL_LINK(HeaderBarClickedHdl, HeaderBar*, void);
+ DECL_LINK(ToggleHdl, SvLBoxButtonData*, void);
+ DECL_LINK(ModelChangedHdl, SvTreeListBox*, void);
+ DECL_LINK(StartDragHdl, SvTreeListBox*, bool);
+ DECL_STATIC_LINK(SalInstanceTreeView, FinishDragHdl, SvTreeListBox*, void);
+ DECL_LINK(EditingEntryHdl, SvTreeListEntry*, bool);
+ typedef std::pair<SvTreeListEntry*, OUString> IterString;
+ DECL_LINK(EditedEntryHdl, IterString, bool);
+ DECL_LINK(VisibleRangeChangedHdl, SvTreeListBox*, void);
+ DECL_LINK(CompareHdl, const SvSortData&, sal_Int32);
+ DECL_LINK(PopupMenuHdl, const CommandEvent&, bool);
+ DECL_LINK(TooltipHdl, SvTreeListEntry*, OUString);
+ DECL_LINK(CustomRenderHdl, svtree_render_args, void);
+ DECL_LINK(CustomMeasureHdl, svtree_measure_args, Size);
+
+ bool ExpandRow(const SalInstanceTreeIter& rIter);
+
+ // 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 to_internal_model(int col) const;
+
+ int to_external_model(int col) const;
+
+ bool IsDummyEntry(SvTreeListEntry* pEntry) const;
+
+ SvTreeListEntry* GetPlaceHolderChild(SvTreeListEntry* pEntry) const;
+
+ static void set_font_color(SvTreeListEntry* pEntry, const Color& rColor);
+
+ void AddStringItem(SvTreeListEntry* pEntry, const OUString& rStr, int nCol);
+
+ void 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);
+
+ void update_checkbutton_column_width(SvTreeListEntry* pEntry);
+
+ void InvalidateModelEntry(SvTreeListEntry* pEntry);
+
+ void do_set_toggle(SvTreeListEntry* pEntry, TriState eState, int col);
+
+ static TriState do_get_toggle(SvTreeListEntry* pEntry, int col);
+ static bool do_get_sensitive(SvTreeListEntry* pEntry, int col);
+
+ TriState get_toggle(SvTreeListEntry* pEntry, int col) const;
+
+ void set_toggle(SvTreeListEntry* pEntry, TriState eState, int col);
+
+ bool get_text_emphasis(SvTreeListEntry* pEntry, int col) const;
+
+ void set_header_item_width(const std::vector<int>& rWidths);
+
+public:
+ SalInstanceTreeView(SvTabListBox* pTreeView, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual void connect_query_tooltip(const Link<const weld::TreeIter&, OUString>& rLink) override;
+
+ virtual void columns_autosize() override;
+
+ virtual void freeze() override;
+
+ virtual void thaw() override;
+
+ virtual void set_column_fixed_widths(const std::vector<int>& rWidths) override;
+
+ virtual void set_column_editables(const std::vector<bool>& rEditables) override;
+
+ virtual void set_centered_column(int nCol) override;
+
+ virtual int get_column_width(int nColumn) const override;
+
+ virtual OUString get_column_title(int nColumn) const override;
+
+ virtual void set_column_title(int nColumn, const OUString& rTitle) override;
+
+ virtual void set_column_custom_renderer(int nColumn, bool bEnable) override;
+
+ virtual void queue_draw() override;
+
+ virtual void show() override;
+
+ virtual void hide() override;
+
+ virtual void insert(const weld::TreeIter* pParent, int pos, const OUString* pStr,
+ const OUString* pId, const OUString* pIconName,
+ VirtualDevice* pImageSurface, bool bChildrenOnDemand,
+ weld::TreeIter* pRet) override;
+
+ virtual void insert_separator(int pos, const OUString& /*rId*/) override;
+
+ virtual void
+ bulk_insert_for_each(int nSourceCount,
+ const std::function<void(weld::TreeIter&, int nSourceIndex)>& func,
+ const weld::TreeIter* pParent = nullptr,
+ const std::vector<int>* pFixedWidths = nullptr) override;
+
+ virtual void set_font_color(int pos, const Color& rColor) override;
+
+ virtual void set_font_color(const weld::TreeIter& rIter, const Color& rColor) override;
+
+ virtual void remove(int pos) override;
+
+ virtual int find_text(const OUString& rText) const override;
+
+ virtual int find_id(const OUString& rId) const override;
+
+ virtual void swap(int pos1, int pos2) override;
+
+ virtual void clear() override;
+
+ virtual int n_children() const override;
+
+ virtual int iter_n_children(const weld::TreeIter& rIter) const override;
+
+ virtual void select(int pos) override;
+
+ virtual int get_cursor_index() const override;
+
+ virtual void set_cursor(int pos) override;
+
+ virtual void scroll_to_row(int pos) override;
+
+ virtual bool is_selected(int pos) const override;
+
+ virtual void unselect(int pos) override;
+
+ virtual std::vector<int> get_selected_rows() const override;
+
+ OUString get_text(SvTreeListEntry* pEntry, int col) const;
+
+ virtual OUString get_text(int pos, int col = -1) const override;
+
+ void set_text(SvTreeListEntry* pEntry, const OUString& rText, int col);
+
+ virtual void set_text(int pos, const OUString& rText, int col = -1) override;
+
+ using SalInstanceWidget::set_sensitive;
+ using SalInstanceWidget::get_sensitive;
+
+ void set_sensitive(SvTreeListEntry* pEntry, bool bSensitive, int col);
+ bool get_sensitive(SvTreeListEntry* pEntry, int col) const;
+
+ virtual void set_sensitive(int pos, bool bSensitive, int col = -1) override;
+
+ virtual void set_sensitive(const weld::TreeIter& rIter, bool bSensitive, int col = -1) override;
+
+ virtual bool get_sensitive(int pos, int col) const override;
+
+ virtual bool get_sensitive(const weld::TreeIter& rIter, int col) const override;
+
+ virtual TriState get_toggle(int pos, int col = -1) const override;
+
+ virtual TriState get_toggle(const weld::TreeIter& rIter, int col = -1) const override;
+
+ virtual void enable_toggle_buttons(weld::ColumnToggleType eType) override;
+
+ virtual void set_toggle(int pos, TriState eState, int col = -1) override;
+
+ virtual void set_toggle(const weld::TreeIter& rIter, TriState eState, int col = -1) override;
+
+ virtual void set_clicks_to_toggle(int nToggleBehavior) override;
+
+ virtual void set_extra_row_indent(const weld::TreeIter& rIter, int nIndentLevel) override;
+
+ void set_text_emphasis(SvTreeListEntry* pEntry, bool bOn, int col = -1);
+
+ virtual void set_text_emphasis(const weld::TreeIter& rIter, bool bOn, int col) override;
+
+ virtual void set_text_emphasis(int pos, bool bOn, int col) override;
+
+ virtual bool get_text_emphasis(const weld::TreeIter& rIter, int col) const override;
+
+ virtual bool get_text_emphasis(int pos, int col) const override;
+
+ void set_text_align(SvTreeListEntry* pEntry, double fAlign, int col);
+
+ virtual void set_text_align(const weld::TreeIter& rIter, double fAlign, int col) override;
+
+ virtual void set_text_align(int pos, double fAlign, int col) override;
+
+ virtual void connect_editing(const Link<const weld::TreeIter&, bool>& rStartLink,
+ const Link<const iter_string&, bool>& rEndLink) override;
+
+ virtual void start_editing(const weld::TreeIter& rIter) override;
+
+ virtual void end_editing() override;
+
+ void set_image(SvTreeListEntry* pEntry, const Image& rImage, int col);
+
+ virtual void set_image(int pos, const OUString& rImage, int col = -1) override;
+
+ virtual void set_image(int pos, const css::uno::Reference<css::graphic::XGraphic>& rImage,
+ int col = -1) override;
+
+ virtual void set_image(int pos, VirtualDevice& rImage, int col = -1) override;
+
+ virtual void set_image(const weld::TreeIter& rIter, const OUString& rImage,
+ int col = -1) override;
+
+ virtual void set_image(const weld::TreeIter& rIter,
+ const css::uno::Reference<css::graphic::XGraphic>& rImage,
+ int col = -1) override;
+
+ virtual void set_image(const weld::TreeIter& rIter, VirtualDevice& rImage,
+ int col = -1) override;
+
+ const OUString* getEntryData(int index) const;
+
+ virtual OUString get_id(int pos) const override;
+
+ void set_id(SvTreeListEntry* pEntry, const OUString& rId);
+
+ virtual void set_id(int pos, const OUString& rId) override;
+
+ virtual int get_selected_index() const override;
+
+ virtual OUString get_selected_text() const override;
+
+ virtual OUString get_selected_id() const override;
+
+ virtual std::unique_ptr<weld::TreeIter> make_iterator(const weld::TreeIter* pOrig
+ = nullptr) const override;
+
+ virtual void copy_iterator(const weld::TreeIter& rSource, weld::TreeIter& rDest) const override;
+
+ virtual bool get_selected(weld::TreeIter* pIter) const override;
+
+ virtual bool get_cursor(weld::TreeIter* pIter) const override;
+
+ virtual void set_cursor(const weld::TreeIter& rIter) override;
+
+ virtual bool get_iter_first(weld::TreeIter& rIter) const override;
+
+ bool get_iter_abs_pos(weld::TreeIter& rIter, int nPos) const;
+
+ virtual bool iter_next_sibling(weld::TreeIter& rIter) const override;
+
+ virtual bool iter_previous_sibling(weld::TreeIter& rIter) const override;
+
+ virtual bool iter_next(weld::TreeIter& rIter) const override;
+
+ virtual bool iter_previous(weld::TreeIter& rIter) const override;
+
+ virtual bool iter_children(weld::TreeIter& rIter) const override;
+
+ virtual bool iter_parent(weld::TreeIter& rIter) const override;
+
+ virtual void remove(const weld::TreeIter& rIter) override;
+
+ virtual void select(const weld::TreeIter& rIter) override;
+
+ virtual void scroll_to_row(const weld::TreeIter& rIter) override;
+
+ virtual void unselect(const weld::TreeIter& rIter) override;
+
+ virtual int get_iter_depth(const weld::TreeIter& rIter) const override;
+
+ virtual bool iter_has_child(const weld::TreeIter& rIter) const override;
+
+ virtual bool get_row_expanded(const weld::TreeIter& rIter) const override;
+
+ virtual bool get_children_on_demand(const weld::TreeIter& rIter) const override;
+
+ virtual void set_children_on_demand(const weld::TreeIter& rIter,
+ bool bChildrenOnDemand) override;
+
+ virtual void expand_row(const weld::TreeIter& rIter) override;
+
+ virtual void collapse_row(const weld::TreeIter& rIter) override;
+
+ virtual OUString get_text(const weld::TreeIter& rIter, int col = -1) const override;
+
+ virtual void set_text(const weld::TreeIter& rIter, const OUString& rText,
+ int col = -1) override;
+
+ virtual OUString get_id(const weld::TreeIter& rIter) const override;
+
+ virtual void set_id(const weld::TreeIter& rIter, const OUString& rId) override;
+
+ virtual void enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper,
+ sal_uInt8 eDNDConstants) override;
+
+ virtual void set_selection_mode(SelectionMode eMode) override;
+
+ virtual void all_foreach(const std::function<bool(weld::TreeIter&)>& func) override;
+
+ virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override;
+
+ virtual void visible_foreach(const std::function<bool(weld::TreeIter&)>& func) override;
+
+ virtual void connect_visible_range_changed(const Link<weld::TreeView&, void>& rLink) override;
+
+ virtual void remove_selection() override;
+
+ virtual bool is_selected(const weld::TreeIter& rIter) const override;
+
+ virtual int get_iter_index_in_parent(const weld::TreeIter& rIter) const override;
+
+ virtual int iter_compare(const weld::TreeIter& a, const weld::TreeIter& b) const override;
+
+ virtual void move_subtree(weld::TreeIter& rNode, const weld::TreeIter* pNewParent,
+ int nIndexInNewParent) override;
+
+ virtual int count_selected_rows() const override;
+
+ virtual int get_height_rows(int nRows) const override;
+
+ virtual void make_sorted() override;
+
+ virtual void set_sort_func(
+ const std::function<int(const weld::TreeIter&, const weld::TreeIter&)>& func) override;
+
+ virtual void make_unsorted() override;
+
+ virtual void set_sort_order(bool bAscending) override;
+
+ virtual bool get_sort_order() const override;
+
+ virtual void set_sort_indicator(TriState eState, int col) override;
+
+ virtual TriState get_sort_indicator(int col) const override;
+
+ virtual int get_sort_column() const override;
+
+ virtual void set_sort_column(int nColumn) override;
+
+ SvTabListBox& getTreeView();
+
+ virtual bool get_dest_row_at_pos(const Point& rPos, weld::TreeIter* pResult, bool bDnDMode,
+ bool bAutoScroll = true) override;
+
+ virtual void unset_drag_dest_row() override;
+
+ virtual tools::Rectangle get_row_area(const weld::TreeIter& rIter) const override;
+
+ virtual TreeView* get_drag_source() const override;
+
+ virtual int vadjustment_get_value() const override;
+
+ virtual void vadjustment_set_value(int nValue) override;
+
+ virtual void set_show_expanders(bool bShow) override;
+
+ virtual bool changed_by_hover() const override;
+
+ virtual ~SalInstanceTreeView() override;
+};
+
+class SalInstanceExpander : public SalInstanceWidget, public virtual weld::Expander
+{
+private:
+ VclPtr<VclExpander> m_xExpander;
+
+ DECL_LINK(ExpandedHdl, VclExpander&, void);
+
+public:
+ SalInstanceExpander(VclExpander* pExpander, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual void set_label(const OUString& rText) override;
+
+ virtual OUString get_label() const override;
+
+ virtual bool get_expanded() const override;
+
+ virtual void set_expanded(bool bExpand) override;
+
+ virtual bool has_focus() const override;
+
+ virtual void grab_focus() override;
+
+ virtual ~SalInstanceExpander() override;
+};
+
+class SalInstanceIconView : public SalInstanceWidget, public virtual weld::IconView
+{
+private:
+ // owner for UserData
+ std::vector<std::unique_ptr<OUString>> m_aUserData;
+ VclPtr<::IconView> m_xIconView;
+
+ DECL_LINK(SelectHdl, SvTreeListBox*, void);
+ DECL_LINK(DeSelectHdl, SvTreeListBox*, void);
+ DECL_LINK(DoubleClickHdl, SvTreeListBox*, bool);
+ DECL_LINK(CommandHdl, const CommandEvent&, bool);
+ DECL_LINK(TooltipHdl, SvTreeListEntry*, OUString);
+ DECL_LINK(EntryAccessibleDescriptionHdl, SvTreeListEntry*, OUString);
+ DECL_LINK(DumpElemToPropertyTreeHdl, const ::IconView::json_prop_query&, bool);
+
+public:
+ SalInstanceIconView(::IconView* pIconView, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual int get_item_width() const override;
+ virtual void set_item_width(int width) override;
+
+ virtual void freeze() override;
+
+ virtual void thaw() override;
+
+ virtual void insert(int pos, const OUString* pStr, const OUString* pId,
+ const OUString* pIconName, weld::TreeIter* pRet) override;
+
+ virtual void insert(int pos, const OUString* pStr, const OUString* pId,
+ const VirtualDevice* pIcon, weld::TreeIter* pRet) override;
+
+ virtual void insert_separator(int pos, const OUString* pId) override;
+
+ virtual void connect_query_tooltip(const Link<const weld::TreeIter&, OUString>& rLink) override;
+
+ virtual void
+ connect_get_property_tree_elem(const Link<const weld::json_prop_query&, bool>& rLink) override;
+
+ virtual OUString get_selected_id() const override;
+
+ virtual OUString get_selected_text() const override;
+
+ virtual int count_selected_items() const override;
+
+ virtual void select(int pos) override;
+
+ virtual void unselect(int pos) override;
+
+ virtual int n_children() const override;
+
+ virtual std::unique_ptr<weld::TreeIter> make_iterator(const weld::TreeIter* pOrig
+ = nullptr) const override;
+
+ virtual bool get_selected(weld::TreeIter* pIter) const override;
+
+ virtual bool get_cursor(weld::TreeIter* pIter) const override;
+
+ virtual void set_cursor(const weld::TreeIter& rIter) override;
+
+ virtual bool get_iter_first(weld::TreeIter& rIter) const override;
+
+ virtual void scroll_to_item(const weld::TreeIter& rIter) override;
+
+ virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override;
+
+ virtual OUString get_id(const weld::TreeIter& rIter) const override;
+
+ virtual OUString get_text(const weld::TreeIter& rIter) const override;
+
+ virtual void clear() override;
+
+ virtual ~SalInstanceIconView() override;
+};
+
+class SalInstanceRadioButton : public SalInstanceButton, public virtual weld::RadioButton
+{
+private:
+ VclPtr<::RadioButton> m_xRadioButton;
+
+ DECL_LINK(ToggleHdl, ::RadioButton&, void);
+
+public:
+ SalInstanceRadioButton(::RadioButton* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership);
+
+ virtual void set_active(bool active) override;
+
+ virtual bool get_active() const override;
+
+ virtual void set_image(VirtualDevice* pDevice) override;
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override;
+
+ virtual void set_from_icon_name(const OUString& rIconName) override;
+
+ virtual void set_inconsistent(bool /*inconsistent*/) override;
+
+ virtual bool get_inconsistent() const override;
+
+ virtual void set_label(const OUString& rText) override { SalInstanceButton::set_label(rText); }
+
+ virtual OUString get_label() const override { return SalInstanceButton::get_label(); }
+
+ virtual void set_label_wrap(bool wrap) override;
+
+ virtual ~SalInstanceRadioButton() override;
+};
+
+class SalInstanceFrame : public SalInstanceContainer, public virtual weld::Frame
+{
+private:
+ VclPtr<VclFrame> m_xFrame;
+
+public:
+ SalInstanceFrame(VclFrame* pFrame, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual void set_label(const OUString& rText) override;
+
+ virtual OUString get_label() const override;
+
+ virtual std::unique_ptr<weld::Label> weld_label_widget() const override;
+};
+
+class SalInstanceMenuButton : public SalInstanceButton, public virtual weld::MenuButton
+{
+protected:
+ VclPtr<::MenuButton> m_xMenuButton;
+ sal_uInt16 m_nLastId;
+
+ DECL_LINK(MenuSelectHdl, ::MenuButton*, void);
+ DECL_LINK(ActivateHdl, ::MenuButton*, void);
+
+public:
+ SalInstanceMenuButton(::MenuButton* pButton, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual void set_active(bool active) override;
+
+ virtual bool get_active() const override;
+
+ virtual void set_inconsistent(bool /*inconsistent*/) override;
+
+ virtual bool get_inconsistent() const override;
+
+ virtual void insert_item(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, VirtualDevice* pImageSurface,
+ TriState eCheckRadioFalse) override;
+
+ virtual void insert_separator(int pos, const OUString& rId) override;
+
+ virtual void set_item_sensitive(const OUString& rIdent, bool bSensitive) override;
+
+ virtual void remove_item(const OUString& rId) override;
+
+ virtual void clear() override;
+
+ virtual void set_item_active(const OUString& rIdent, bool bActive) override;
+
+ virtual void set_item_label(const OUString& rIdent, const OUString& rText) override;
+
+ virtual OUString get_item_label(const OUString& rIdent) const override;
+
+ virtual void set_item_visible(const OUString& rIdent, bool bShow) override;
+
+ virtual void set_popover(weld::Widget* pPopover) override;
+
+ virtual ~SalInstanceMenuButton() override;
+};
+
+class SalInstancePopover : public SalInstanceContainer, public virtual weld::Popover
+{
+private:
+ VclPtr<DockingWindow> m_xPopover;
+
+ DECL_LINK(PopupModeEndHdl, FloatingWindow*, void);
+
+ void ImplPopDown();
+
+public:
+ SalInstancePopover(DockingWindow* pPopover, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ ~SalInstancePopover();
+
+ virtual void popup_at_rect(weld::Widget* pParent, const tools::Rectangle& rRect,
+ weld::Placement ePlace = weld::Placement::Under) override;
+
+ virtual void popdown() override;
+
+ virtual void resize_to_request() override;
+};
+
+class SalInstanceBox : public SalInstanceContainer, public virtual weld::Box
+{
+private:
+ VclPtr<VclBox> m_xBox;
+
+public:
+ SalInstanceBox(VclBox* pContainer, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+ virtual void reorder_child(weld::Widget* pWidget, int nNewPosition) override;
+ virtual void sort_native_button_order() override;
+};
+
+class SalInstanceImage : public SalInstanceWidget, public virtual weld::Image
+{
+private:
+ VclPtr<FixedImage> m_xImage;
+
+public:
+ SalInstanceImage(FixedImage* pImage, SalInstanceBuilder* pBuilder, bool bTakeOwnership);
+
+ virtual void set_from_icon_name(const OUString& rIconName) override;
+
+ virtual void set_image(VirtualDevice* pDevice) override;
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override;
+};
+
+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);
+
+public:
+ SalInstanceScrolledWindow(VclScrolledWindow* pScrolledWindow, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership, bool bUserManagedScrolling);
+
+ virtual void hadjustment_configure(int value, int lower, int upper, int step_increment,
+ int page_increment, int page_size) override;
+ virtual int hadjustment_get_value() const override;
+ virtual void hadjustment_set_value(int value) override;
+ virtual int hadjustment_get_upper() const override;
+ virtual void hadjustment_set_upper(int upper) override;
+ virtual int hadjustment_get_page_size() const override;
+ virtual void hadjustment_set_page_size(int size) override;
+ virtual void hadjustment_set_page_increment(int size) override;
+ virtual void hadjustment_set_step_increment(int size) override;
+ virtual void set_hpolicy(VclPolicyType eHPolicy) override;
+ virtual VclPolicyType get_hpolicy() const override;
+
+ virtual void vadjustment_configure(int value, int lower, int upper, int step_increment,
+ int page_increment, int page_size) override;
+ virtual int vadjustment_get_value() const override;
+ virtual void vadjustment_set_value(int value) override;
+ virtual int vadjustment_get_upper() const override;
+ virtual void vadjustment_set_upper(int upper) override;
+ virtual int vadjustment_get_lower() const override;
+ virtual void vadjustment_set_lower(int lower) override;
+ virtual int vadjustment_get_page_size() const override;
+ virtual void vadjustment_set_page_size(int size) override;
+ virtual void vadjustment_set_page_increment(int size) override;
+ virtual void vadjustment_set_step_increment(int size) override;
+
+ virtual void set_vpolicy(VclPolicyType eVPolicy) override;
+ virtual VclPolicyType get_vpolicy() const override;
+ virtual int get_scroll_thickness() const override;
+ virtual void set_scroll_thickness(int nThickness) override;
+ virtual void customize_scrollbars(const Color& rBackgroundColor, const Color& rShadowColor,
+ const Color& rFaceColor) override;
+ virtual ~SalInstanceScrolledWindow() override;
+};
+
+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>());
+ }
+};
+
+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);
+
+ virtual void set_text(const OUString& rText) override;
+
+ virtual void connect_changed(const Link<weld::Entry&, void>& rLink) override;
+
+ virtual void connect_focus_out(const Link<weld::Widget&, void>& rLink) override;
+
+ virtual void SetFormatter(weld::EntryFormatter* pFormatter) override;
+
+ 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;
+
+ virtual ~SalInstanceFormattedSpinButton() override;
+};
+
+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);
+
+ virtual int get_current_page() const override;
+
+ virtual OUString get_page_ident(int nPage) const override;
+
+ virtual OUString get_current_page_ident() const override;
+
+ virtual int get_page_index(const OUString& rIdent) const override;
+
+ virtual weld::Container* get_page(const OUString& rIdent) const override;
+
+ virtual void set_current_page(int nPage) override;
+
+ virtual void set_current_page(const OUString& rIdent) override;
+
+ virtual void remove_page(const OUString& rIdent) override;
+
+ virtual void insert_page(const OUString& rIdent, const OUString& rLabel, int nPos) override;
+
+ virtual int get_n_pages() const override;
+
+ virtual void set_tab_label_text(const OUString& rIdent, const OUString& rText) override;
+
+ virtual OUString get_tab_label_text(const OUString& rIdent) const override;
+
+ virtual void set_show_tabs(bool /*bShow*/) override;
+
+ virtual ~SalInstanceVerticalNotebook() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/salwtype.hxx b/vcl/inc/salwtype.hxx
new file mode 100644
index 0000000000..d197f17fe4
--- /dev/null
+++ b/vcl/inc/salwtype.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SALWTYPE_HXX
+#define INCLUDED_VCL_INC_SALWTYPE_HXX
+
+#include <rtl/ref.hxx>
+#include <rtl/ustring.hxx>
+#include <tools/gen.hxx>
+#include <tools/solar.h>
+#include <tools/long.hxx>
+#include <vcl/GestureEventPan.hxx>
+#include <vcl/GestureEventZoom.hxx>
+#include <vcl/GestureEventRotate.hxx>
+
+class LogicalFontInstance;
+class SalGraphics;
+class SalFrame;
+class SalObject;
+namespace vcl
+{
+ class Window;
+ enum class WindowState;
+}
+enum class InputContextFlags;
+enum class WindowStateMask;
+enum class ExtTextInputAttr;
+enum class ModKeyFlags;
+
+enum class SalEvent {
+ NONE,
+ MouseMove,
+ MouseLeave,
+ MouseButtonDown,
+ MouseButtonUp,
+ KeyInput,
+ KeyUp,
+ KeyModChange,
+ Paint,
+ Resize,
+ GetFocus,
+ LoseFocus,
+ Close,
+ Shutdown,
+ SettingsChanged,
+ PrinterChanged,
+ DisplayChanged,
+ FontChanged,
+ WheelMouse,
+ UserEvent,
+ MouseActivate,
+ ExtTextInput,
+ EndExtTextInput,
+ ExtTextInputPos,
+ InputContextChange,
+ Move,
+ MoveResize,
+ ClosePopups,
+ ExternalKeyInput,
+ ExternalKeyUp,
+ MenuCommand,
+ MenuHighlight,
+ MenuActivate,
+ MenuDeactivate,
+ ExternalMouseMove,
+ ExternalMouseButtonDown,
+ ExternalMouseButtonUp,
+ InputLanguageChange,
+ ShowDialog,
+ MenuButtonCommand,
+ SurroundingTextRequest,
+ DeleteSurroundingTextRequest,
+ SurroundingTextSelectionChange,
+ StartReconversion,
+ QueryCharPosition,
+ GestureSwipe,
+ GestureLongPress,
+ ExternalGesture,
+ GesturePan,
+ GestureZoom,
+ GestureRotate,
+};
+
+struct SalAbstractMouseEvent
+{
+ sal_uInt64 mnTime; // Time in ms, when event is created
+ tools::Long mnX; // X-Position (Pixel, TopLeft-Output)
+ tools::Long mnY; // Y-Position (Pixel, TopLeft-Output)
+ sal_uInt16 mnCode; // SV-Modifiercode (KEY_SHIFT|KEY_MOD1|KEY_MOD2|MOUSE_LEFT|MOUSE_MIDDLE|MOUSE_RIGHT)
+
+protected:
+ SalAbstractMouseEvent() : mnTime(0), mnX(0), mnY(0), mnCode(0) {}
+};
+
+// MOUSELEAVE must send, when the pointer leave the client area and
+// the mouse is not captured
+// MOUSEMOVE, MOUSELEAVE, MOUSEBUTTONDOWN and MOUSEBUTTONUP
+// MAC: Ctrl+Button is MOUSE_RIGHT
+struct SalMouseEvent final : public SalAbstractMouseEvent
+{
+ sal_uInt16 mnButton; // 0-MouseMove/MouseLeave, MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE
+
+ SalMouseEvent() : mnButton(0) {}
+};
+
+// KEYINPUT and KEYUP
+struct SalKeyEvent
+{
+ sal_uInt16 mnCode; // SV-KeyCode (KEY_xxx | KEY_SHIFT | KEY_MOD1 | KEY_MOD2)
+ sal_uInt16 mnCharCode; // SV-CharCode
+ sal_uInt16 mnRepeat; // Repeat-Count (KeyInputs-1)
+};
+
+// MENUEVENT
+struct SalMenuEvent
+{
+ sal_uInt16 mnId; // Menu item ID
+ void* mpMenu; // pointer to VCL menu (class Menu)
+
+ SalMenuEvent() : mnId( 0 ), mpMenu( nullptr ) {}
+ SalMenuEvent( sal_uInt16 i_nId, void* i_pMenu )
+ : mnId( i_nId ), mpMenu( i_pMenu ) {}
+};
+
+// KEYMODCHANGE
+struct SalKeyModEvent
+{
+ bool mbDown; // Whether the change occurred on a key down event
+ sal_uInt16 mnCode; // SV-Modifiercode (KEY_SHIFT|KEY_MOD1|KEY_MOD2)
+ ModKeyFlags mnModKeyCode; // extended Modifier (MODKEY_LEFT,MODKEY_RIGHT,MODKEY_PRESS,MODKEY_RELEASE)
+};
+
+struct SalPaintEvent
+{
+ tools::Long mnBoundX; // BoundRect - X
+ tools::Long mnBoundY; // BoundRect - Y
+ tools::Long mnBoundWidth; // BoundRect - Width
+ tools::Long mnBoundHeight; // BoundRect - Height
+ bool mbImmediateUpdate; // set to true to force an immediate update
+
+ SalPaintEvent( tools::Long x, tools::Long y, tools::Long w, tools::Long h, bool bImmediate = false ) :
+ mnBoundX( x ), mnBoundY( y ),
+ mnBoundWidth( w ), mnBoundHeight( h ),
+ mbImmediateUpdate( bImmediate )
+ {}
+};
+
+#define SAL_WHEELMOUSE_EVENT_PAGESCROLL (sal_uLong(0xFFFFFFFF))
+struct SalWheelMouseEvent final : public SalAbstractMouseEvent
+{
+ tools::Long mnDelta; // Number of rotations
+ tools::Long mnNotchDelta; // Number of fixed rotations
+ double mnScrollLines; // Actual number of lines to scroll
+ bool mbHorz; // Horizontal
+ bool mbDeltaIsPixel; // delta value is a pixel value (on touch devices)
+
+ SalWheelMouseEvent()
+ : mnDelta(0), mnNotchDelta(0), mnScrollLines(0), mbHorz(false), mbDeltaIsPixel(false)
+ {}
+};
+
+struct SalExtTextInputEvent
+{
+ OUString maText; // Text
+ const ExtTextInputAttr* mpTextAttr; // Text-Attribute
+ sal_Int32 mnCursorPos; // Cursor-Position
+ sal_uInt8 mnCursorFlags; // EXTTEXTINPUT_CURSOR_xxx
+};
+
+struct SalExtTextInputPosEvent
+{
+ tools::Long mnX; // Cursor-X-Position to upper left corner of frame
+ tools::Long mnY; // Cursor-Y-Position to upper left corner of frame
+ tools::Long mnWidth; // Cursor-Width in Pixel
+ tools::Long mnHeight; // Cursor-Height in Pixel
+ tools::Long mnExtWidth; // Width of the PreEdit area
+ bool mbVertical; // true if in vertical mode
+ SalExtTextInputPosEvent()
+ : mnX(0)
+ , mnY(0)
+ , mnWidth(0)
+ , mnHeight(0)
+ , mnExtWidth(0)
+ , mbVertical(false)
+ {
+ }
+};
+
+struct SalInputContextChangeEvent
+{
+};
+
+struct SalSurroundingTextRequestEvent
+{
+ OUString maText; // Text
+ sal_uLong mnStart; // The beginning index of selected range
+ sal_uLong mnEnd; // The end index of selected range
+};
+
+struct SalSurroundingTextSelectionChangeEvent
+{
+ sal_uLong mnStart; // The beginning index of selected range
+ sal_uLong mnEnd; // The end index of selected range
+};
+
+struct SalQueryCharPositionEvent
+{
+ sal_uLong mnCharPos; // The index of character in a composition.
+ AbsoluteScreenPixelRectangle maCursorBound; // The cursor bounds corresponding to the character specified by mnCharPos - X
+ bool mbValid; // The data is valid or not.
+ bool mbVertical; // The text is vertical or not.
+};
+
+typedef bool (*SALFRAMEPROC)( vcl::Window* pInst, SalEvent nEvent, const void* pEvent );
+
+enum class SalObjEvent {
+ GetFocus = 1,
+ LoseFocus = 2,
+ ToTop = 3
+};
+
+struct SalInputContext
+{
+ rtl::Reference<LogicalFontInstance> mpFont;
+ InputContextFlags mnOptions;
+};
+
+struct SalGestureSwipeEvent
+{
+ double mnVelocityX;
+ double mnVelocityY;
+ tools::Long mnX;
+ tools::Long mnY;
+};
+
+struct SalGestureLongPressEvent
+{
+ tools::Long mnX;
+ tools::Long mnY;
+};
+
+struct SalGestureEvent
+{
+ GestureEventPanType meEventType;
+ PanningOrientation meOrientation;
+ double mfOffset;
+ tools::Long mnX;
+ tools::Long mnY;
+};
+
+struct SalGestureZoomEvent
+{
+ GestureEventZoomType meEventType = GestureEventZoomType::Begin;
+ tools::Long mnX = 0;
+ tools::Long mnY = 0;
+ double mfScaleDelta = 0;
+};
+
+struct SalGestureRotateEvent
+{
+ GestureEventRotateType meEventType = GestureEventRotateType::Begin;
+ tools::Long mnX = 0;
+ tools::Long mnY = 0;
+ double mfAngleDelta = 0;
+};
+
+typedef void (*SALTIMERPROC)();
+
+#endif // INCLUDED_VCL_INC_SALWTYPE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/scanlinewriter.hxx b/vcl/inc/scanlinewriter.hxx
new file mode 100644
index 0000000000..87a7827b7a
--- /dev/null
+++ b/vcl/inc/scanlinewriter.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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SCANLINEWRITER_HXX
+#define INCLUDED_VCL_INC_SCANLINEWRITER_HXX
+
+#include <tools/long.hxx>
+#include <vcl/BitmapPalette.hxx>
+
+namespace vcl
+{
+// Write color information for 1, 4 and 8 bit palette bitmap scanlines.
+class ScanlineWriter
+{
+ BitmapPalette& maPalette;
+ sal_uInt8 const mnColorsPerByte; // number of colors that are stored in one byte
+ sal_uInt8 const mnColorBitSize; // number of bits a color takes
+ sal_uInt8 const mnColorBitMask; // bit mask used to isolate the color
+ sal_uInt8* mpCurrentScanline;
+ tools::Long mnX;
+
+public:
+ ScanlineWriter(BitmapPalette& aPalette, sal_Int8 nColorsPerByte)
+ : maPalette(aPalette)
+ , mnColorsPerByte(nColorsPerByte)
+ , mnColorBitSize(
+ 8
+ / mnColorsPerByte) // bit size is number of bit in a byte divided by number of colors per byte (8 / 2 = 4 for 4-bit)
+ , mnColorBitMask((1 << mnColorBitSize) - 1) // calculate the bit mask from the bit size
+ , mpCurrentScanline(nullptr)
+ , mnX(0)
+ {
+ }
+
+ static std::unique_ptr<ScanlineWriter> Create(sal_uInt16 nBits, BitmapPalette& aPalette)
+ {
+ switch (nBits)
+ {
+ case 1:
+ return std::make_unique<ScanlineWriter>(aPalette, 8);
+ case 4:
+ return std::make_unique<ScanlineWriter>(aPalette, 2);
+ case 8:
+ return std::make_unique<ScanlineWriter>(aPalette, 1);
+ default:
+ abort();
+ }
+ }
+
+ void writeRGB(sal_uInt8 nR, sal_uInt8 nG, sal_uInt8 nB)
+ {
+ // calculate to which index we will write
+ tools::Long nScanlineIndex = mnX / mnColorsPerByte;
+
+ // calculate the number of shifts to get the color information to the right place
+ tools::Long nShift = (8 - mnColorBitSize) - ((mnX % mnColorsPerByte) * mnColorBitSize);
+
+ sal_uInt16 nColorIndex = maPalette.GetBestIndex(BitmapColor(nR, nG, nB));
+ mpCurrentScanline[nScanlineIndex] &= ~(mnColorBitMask << nShift); // clear
+ mpCurrentScanline[nScanlineIndex] |= (nColorIndex & mnColorBitMask) << nShift; // set
+ mnX++;
+ }
+
+ void nextLine(sal_uInt8* pScanline)
+ {
+ mnX = 0;
+ mpCurrentScanline = pScanline;
+ }
+};
+
+} // namespace vcl
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/schedulerimpl.hxx b/vcl/inc/schedulerimpl.hxx
new file mode 100644
index 0000000000..e7968c8f50
--- /dev/null
+++ b/vcl/inc/schedulerimpl.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SCHEDULERIMPL_HXX
+#define INCLUDED_VCL_INC_SCHEDULERIMPL_HXX
+
+#include <vcl/scheduler.hxx>
+#include <vcl/task.hxx>
+
+class Task;
+
+// Internal scheduler record holding intrusive linked list pieces
+struct ImplSchedulerData final
+{
+ ImplSchedulerData* mpNext; ///< Pointer to the next element in list
+ Task* mpTask; ///< Pointer to VCL Task instance
+ sal_uInt64 mnUpdateTime; ///< Last Update Time
+ TaskPriority mePriority; ///< Task priority
+ /**
+ * Is the Task currently processed / on the stack?
+ *
+ * Since the introduction of the scheduler stack, this became merely a
+ * debugging and assertion hint. No decisions are anymore made based on
+ * this, because invoked Tasks are removed from the scheduler lists and
+ * placed on the stack, so no code should actually ever find one, where
+ * mbInScheduler is true (I don't see a reason to walk the stack for
+ * normal Scheduler usage, that is).
+ *
+ * This was originally used to prevent invoking Tasks recursively.
+ **/
+ bool mbInScheduler; ///< Task currently processed?
+
+ const char *GetDebugName() const;
+};
+
+class SchedulerGuard final
+{
+public:
+ SchedulerGuard()
+ {
+ Scheduler::Lock();
+ }
+
+ ~SchedulerGuard()
+ {
+ Scheduler::Unlock();
+ }
+};
+
+#endif // INCLUDED_VCL_INC_SCHEDULERIMPL_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/scrptrun.h b/vcl/inc/scrptrun.h
new file mode 100644
index 0000000000..d2dee3e155
--- /dev/null
+++ b/vcl/inc/scrptrun.h
@@ -0,0 +1,153 @@
+/*
+ *******************************************************************************
+ *
+ * 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.h
+ *
+ * created on: 10/17/2001
+ * created by: Eric R. Mader
+ */
+
+#ifndef INCLUDED_VCL_INC_SCRPTRUN_H
+#define INCLUDED_VCL_INC_SCRPTRUN_H
+
+#include <sal/config.h>
+
+#include <vcl/dllapi.h>
+
+#include <unicode/uobject.h>
+#include <unicode/uscript.h>
+#include <vector>
+
+namespace vcl
+{
+struct ParenStackEntry
+{
+ int32_t pairIndex;
+ UScriptCode scriptCode;
+ ParenStackEntry()
+ : pairIndex(0)
+ , scriptCode(USCRIPT_INVALID_CODE)
+ {
+ }
+};
+
+class VCL_DLLPUBLIC ScriptRun final : public icu::UObject
+{
+public:
+ ScriptRun(const UChar chars[], int32_t length);
+
+ void reset();
+
+ void reset(int32_t start, int32_t count);
+
+ void reset(const UChar chars[], int32_t start, int32_t length);
+
+ int32_t getScriptStart() const;
+
+ int32_t getScriptEnd() const;
+
+ UScriptCode getScriptCode() const;
+
+ UBool next();
+
+ /**
+s * ICU "poor man's RTTI", returns a UClassID for the actual class.
+ *
+ * @stable ICU 2.2
+ */
+ virtual UClassID getDynamicClassID() const override { return getStaticClassID(); }
+
+ /**
+ * ICU "poor man's RTTI", returns a UClassID for this class.
+ *
+ * @stable ICU 2.2
+ */
+ static UClassID getStaticClassID()
+ {
+ return static_cast<UClassID>(const_cast<char*>(&fgClassID));
+ }
+
+private:
+ int32_t charStart;
+ int32_t charLimit;
+ const UChar* charArray;
+
+ int32_t scriptStart;
+ int32_t scriptEnd;
+ UScriptCode scriptCode;
+
+ std::vector<ParenStackEntry> parenStack;
+ int32_t parenSP;
+
+ /**
+ * The address of this static class variable serves as this class's ID
+ * for ICU "poor man's RTTI".
+ */
+ static const char fgClassID;
+};
+
+inline ScriptRun::ScriptRun(const UChar chars[], int32_t length)
+{
+ parenStack.reserve(128);
+ reset(chars, 0, length);
+}
+
+inline int32_t ScriptRun::getScriptStart() const { return scriptStart; }
+
+inline int32_t ScriptRun::getScriptEnd() const { return scriptEnd; }
+
+inline UScriptCode ScriptRun::getScriptCode() const { return scriptCode; }
+
+inline void ScriptRun::reset()
+{
+ scriptStart = charStart;
+ scriptEnd = charStart;
+ scriptCode = USCRIPT_INVALID_CODE;
+ parenSP = -1;
+ parenStack.clear();
+}
+
+inline void ScriptRun::reset(int32_t start, int32_t length)
+{
+ charStart = start;
+ charLimit = start + length;
+
+ reset();
+}
+
+inline void ScriptRun::reset(const UChar chars[], int32_t start, int32_t length)
+{
+ charArray = chars;
+
+ reset(start, length);
+}
+}
+
+#endif
diff --git a/vcl/inc/scrwnd.hxx b/vcl/inc/scrwnd.hxx
new file mode 100644
index 0000000000..f92ebe556c
--- /dev/null
+++ b/vcl/inc/scrwnd.hxx
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SCRWND_HXX
+#define INCLUDED_VCL_INC_SCRWND_HXX
+
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/image.hxx>
+
+class Timer;
+
+enum class WheelMode {
+ NONE = 0x0000,
+ VH = 0x0001,
+ V = 0x0002,
+ H = 0x0004,
+ ScrollVH = 0x0008,
+ ScrollV = 0x0010,
+ ScrollH = 0x0020
+};
+namespace o3tl {
+ template<> struct typed_flags<WheelMode> : is_typed_flags<WheelMode, 0x003f> {};
+}
+
+class ImplWheelWindow final : public FloatingWindow
+{
+private:
+
+ std::vector<Image> maImgList;
+ Point maLastMousePos;
+ Point maCenter;
+ std::unique_ptr<Timer> mpTimer;
+ sal_uInt64 mnRepaintTime;
+ sal_uInt64 mnTimeout;
+ WheelMode mnWheelMode;
+ sal_uLong mnMaxWidth;
+ sal_uLong mnActDist;
+ tools::Long mnActDeltaX;
+ tools::Long mnActDeltaY;
+ void ImplCreateImageList();
+ void ImplSetRegion(const Bitmap& rRegionBmp);
+ using Window::ImplGetMousePointer;
+ PointerStyle ImplGetMousePointer( tools::Long nDistX, tools::Long nDistY ) const;
+ void ImplDrawWheel(vcl::RenderContext& rRenderContext);
+ void ImplRecalcScrollValues();
+
+ DECL_LINK(ImplScrollHdl, Timer *, void);
+
+ virtual void Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) override;
+ virtual void MouseMove( const MouseEvent& rMEvt ) override;
+ virtual void MouseButtonUp( const MouseEvent& rMEvt ) override;
+
+public:
+
+ explicit ImplWheelWindow( vcl::Window* pParent );
+ virtual ~ImplWheelWindow() override;
+ virtual void dispose() override;
+
+ void ImplStop();
+ void ImplSetWheelMode( WheelMode nWheelMode );
+};
+
+#endif // INCLUDED_VCL_INC_SCRWND_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/sft.hxx b/vcl/inc/sft.hxx
new file mode 100644
index 0000000000..96553ba7d8
--- /dev/null
+++ b/vcl/inc/sft.hxx
@@ -0,0 +1,725 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 sft.hxx
+ * @brief Sun Font Tools
+ */
+
+/*
+ * Generated fonts contain an XUID entry in the form of:
+ *
+ * 103 0 T C1 N C2 C3
+ *
+ * 103 - Sun's Adobe assigned XUID number. Contact person: Alexander Gelfenbain <gelf@eng.sun.com>
+ *
+ * T - font type. 0: Type 3, 1: Type 42
+ * C1 - CRC-32 of the entire source TrueType font
+ * N - number of glyphs in the subset
+ * C2 - CRC-32 of the array of glyph IDs used to generate the subset
+ * C3 - CRC-32 of the array of encoding numbers used to generate the subset
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_SFT_HXX
+#define INCLUDED_VCL_INC_SFT_HXX
+
+#include <rtl/ustring.hxx>
+#include <vcl/dllapi.h>
+#include <vcl/fontcapabilities.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <i18nlangtag/lang.h>
+
+#include "fontsubset.hxx"
+#include "glyphid.hxx"
+
+#include <array>
+#include <memory>
+#include <vector>
+
+class SvStream;
+
+namespace vcl
+{
+
+/*@{*/
+ typedef sal_Int32 F16Dot16; /**< fixed: 16.16 */
+/*@}*/
+
+/** Return value of OpenTTFont() */
+ enum class SFErrCodes {
+ Ok, /**< no error */
+ BadFile, /**< file not found */
+ FileIo, /**< file I/O error */
+ Memory, /**< memory allocation error */
+ GlyphNum, /**< incorrect number of glyphs */
+ BadArg, /**< incorrect arguments */
+ TtFormat, /**< incorrect TrueType font format */
+ FontNo /**< incorrect logical font number of a TTC font */
+ };
+
+#ifndef FW_THIN /* WIN32 compilation would conflict */
+/** Value of the weight member of the TTGlobalFontInfo struct */
+ enum WeightClass {
+ FW_THIN = 100, /**< Thin */
+ FW_EXTRALIGHT = 200, /**< Extra-light (Ultra-light) */
+ FW_LIGHT = 300, /**< Light */
+ FW_NORMAL = 400, /**< Normal (Regular) */
+ FW_MEDIUM = 500, /**< Medium */
+ FW_SEMIBOLD = 600, /**< Semi-bold (Demi-bold) */
+ FW_BOLD = 700, /**< Bold */
+ FW_EXTRABOLD = 800, /**< Extra-bold (Ultra-bold) */
+ FW_BLACK = 900 /**< Black (Heavy) */
+ };
+#endif /* FW_THIN */
+
+/** Value of the width member of the TTGlobalFontInfo struct */
+ enum WidthClass {
+ FWIDTH_ULTRA_CONDENSED = 1, /**< 50% of normal */
+ FWIDTH_EXTRA_CONDENSED = 2, /**< 62.5% of normal */
+ FWIDTH_CONDENSED = 3, /**< 75% of normal */
+ FWIDTH_SEMI_CONDENSED = 4, /**< 87.5% of normal */
+ FWIDTH_NORMAL = 5, /**< Medium, 100% */
+ FWIDTH_SEMI_EXPANDED = 6, /**< 112.5% of normal */
+ FWIDTH_EXPANDED = 7, /**< 125% of normal */
+ FWIDTH_EXTRA_EXPANDED = 8, /**< 150% of normal */
+ FWIDTH_ULTRA_EXPANDED = 9 /**< 200% of normal */
+ };
+
+/** Composite glyph flags definition */
+ enum CompositeFlags {
+ ARG_1_AND_2_ARE_WORDS = 1,
+ ARGS_ARE_XY_VALUES = 1<<1,
+ ROUND_XY_TO_GRID = 1<<2,
+ WE_HAVE_A_SCALE = 1<<3,
+ MORE_COMPONENTS = 1<<5,
+ WE_HAVE_AN_X_AND_Y_SCALE = 1<<6,
+ WE_HAVE_A_TWO_BY_TWO = 1<<7,
+ WE_HAVE_INSTRUCTIONS = 1<<8,
+ USE_MY_METRICS = 1<<9,
+ OVERLAP_COMPOUND = 1<<10
+ };
+
+/** Structure used by GetTTSimpleCharMetrics() functions */
+ typedef struct {
+ sal_uInt16 adv; /**< advance width or height */
+ sal_Int16 sb; /**< left or top sidebearing */
+ } TTSimpleGlyphMetrics;
+
+/** Structure used by the TrueType Creator and GetRawGlyphData() */
+
+ typedef struct {
+ sal_uInt32 glyphID; /**< glyph ID */
+ sal_uInt16 nbytes; /**< number of bytes in glyph data */
+ std::unique_ptr<sal_uInt8[]> ptr; /**< pointer to glyph data */
+ sal_uInt16 aw; /**< advance width */
+ sal_Int16 lsb; /**< left sidebearing */
+ bool compflag; /**< false- if non-composite */
+ sal_uInt16 npoints; /**< number of points */
+ sal_uInt16 ncontours; /**< number of contours */
+ /* */
+ sal_uInt32 newID; /**< used internally by the TTCR */
+ } GlyphData;
+
+/** Structure used by the TrueType Creator and CreateTTFromTTGlyphs() */
+ struct NameRecord {
+ sal_uInt16 platformID; /**< Platform ID */
+ sal_uInt16 encodingID; /**< Platform-specific encoding ID */
+ LanguageType languageID; /**< Language ID */
+ sal_uInt16 nameID; /**< Name ID */
+ std::vector<sal_uInt8> sptr; /**< string data (not zero-terminated!) */
+ };
+
+/** Return value of GetTTGlobalFontInfo() */
+
+ typedef struct TTGlobalFontInfo_ {
+ OString family; /**< family name */
+ OUString ufamily; /**< family name UCS2 */
+ OString subfamily; /**< subfamily name */
+ OUString usubfamily; /**< subfamily name UCS2 */
+ OString psname; /**< PostScript name */
+ sal_uInt16 macStyle = 0; /**< macstyle bits from 'HEAD' table */
+ int weight = 0; /**< value of WeightClass or 0 if can't be determined */
+ int width = 0; /**< value of WidthClass or 0 if can't be determined */
+ int pitch = 0; /**< 0: proportional font, otherwise: monospaced */
+ int italicAngle = 0; /**< in counter-clockwise degrees * 65536 */
+ int xMin = 0; /**< global bounding box: xMin */
+ int yMin = 0; /**< global bounding box: yMin */
+ int xMax = 0; /**< global bounding box: xMax */
+ int yMax = 0; /**< global bounding box: yMax */
+ int ascender = 0; /**< typographic ascent. */
+ int descender = 0; /**< typographic descent. */
+ int linegap = 0; /**< typographic line gap.\ Negative values are treated as
+ zero in Win 3.1, System 6 and System 7. */
+ int typoAscender = 0; /**< OS/2 portable typographic ascender */
+ int typoDescender = 0; /**< OS/2 portable typographic descender */
+ int typoLineGap = 0; /**< OS/2 portable typographic line gap */
+ int winAscent = 0; /**< ascender metric for Windows */
+ int winDescent = 0; /**< descender metric for Windows */
+ bool microsoftSymbolEncoded = false; /**< true: MS symbol encoded */
+ sal_uInt8 panose[10] = {}; /**< PANOSE classification number */
+ sal_uInt32 typeFlags = 0; /**< type flags (copyright bits) */
+ sal_uInt16 fsSelection = 0; /**< OS/2 fsSelection */
+ } TTGlobalFontInfo;
+
+/** ControlPoint structure used by GetTTGlyphPoints() */
+ typedef struct {
+ sal_uInt32 flags; /**< 00000000 00000000 e0000000 bbbbbbbb */
+ /**< b - byte flags from the glyf array */
+ /**< e == 0 - regular point */
+ /**< e == 1 - end contour */
+ sal_Int16 x; /**< X coordinate in EmSquare units */
+ sal_Int16 y; /**< Y coordinate in EmSquare units */
+ } ControlPoint;
+
+
+/*
+ Some table OS/2 consts
+ quick history:
+ OpenType has been created from TrueType
+ - original TrueType had an OS/2 table with a length of 68 bytes
+ (cf https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6OS2.html)
+ - There have been 6 versions (from version 0 to 5)
+ (cf https://docs.microsoft.com/en-us/typography/opentype/otspec140/os2ver0)
+
+ For the record:
+ // From Initial TrueType version
+ TYPE NAME FROM BYTE
+ uint16 version 0
+ int16 xAvgCharWidth 2
+ uint16 usWeightClass 4
+ uint16 usWidthClass 6
+ uint16 fsType 8
+ int16 ySubscriptXSize 10
+ int16 ySubscriptYSize 12
+ int16 ySubscriptXOffset 14
+ int16 ySubscriptYOffset 16
+ int16 ySuperscriptXSize 18
+ int16 ySuperscriptYSize 20
+ int16 ySuperscriptXOffset 22
+ int16 ySuperscriptYOffset 24
+ int16 yStrikeoutSize 26
+ int16 yStrikeoutPosition 28
+ int16 sFamilyClass 30
+ uint8 panose[10] 32
+ uint32 ulUnicodeRange1 42
+ uint32 ulUnicodeRange2 46
+ uint32 ulUnicodeRange3 50
+ uint32 ulUnicodeRange4 54
+ Tag achVendID 58
+ uint16 fsSelection 62
+ uint16 usFirstCharIndex 64
+ uint16 usLastCharIndex 66
+
+ // From Version 0 of OpenType
+ int16 sTypoAscender 68
+ int16 sTypoDescender 70
+ int16 sTypoLineGap 72
+ uint16 usWinAscent 74
+ uint16 usWinDescent 76
+
+ => length for OpenType version 0 = 78 bytes
+
+ // From Version 1 of OpenType
+ uint32 ulCodePageRange1 78
+ uint32 ulCodePageRange2 82
+
+ => length for OpenType version 1 = 86 bytes
+
+ // From Version 2 of OpenType
+ // (idem for Versions 3 and 4)
+ int16 sxHeight 86
+ int16 sCapHeight 88
+ uint16 usDefaultChar 90
+ uint16 usBreakChar 92
+ uint16 usMaxContext 94
+
+ => length for OpenType version 2, 3 and 4 = 96 bytes
+
+ // From Version 5 of OpenType
+ uint16 usLowerOpticalPointSize 96
+ uint16 usUpperOpticalPointSize 98
+ END 100
+
+ => length for OS/2 table version 5 = 100 bytes
+
+*/
+constexpr int OS2_Legacy_length = 68;
+constexpr int OS2_V0_length = 78;
+constexpr int OS2_V1_length = 86;
+
+constexpr int OS2_usWeightClass_offset = 4;
+constexpr int OS2_usWidthClass_offset = 6;
+constexpr int OS2_fsType_offset = 8;
+constexpr int OS2_panose_offset = 32;
+constexpr int OS2_panoseNbBytes_offset = 10;
+constexpr int OS2_ulUnicodeRange1_offset = 42;
+constexpr int OS2_ulUnicodeRange2_offset = 46;
+constexpr int OS2_ulUnicodeRange3_offset = 50;
+constexpr int OS2_ulUnicodeRange4_offset = 54;
+constexpr int OS2_fsSelection_offset = 62;
+constexpr int OS2_typoAscender_offset = 68;
+constexpr int OS2_typoDescender_offset = 70;
+constexpr int OS2_typoLineGap_offset = 72;
+constexpr int OS2_winAscent_offset = 74;
+constexpr int OS2_winDescent_offset = 76;
+constexpr int OS2_ulCodePageRange1_offset = 78;
+constexpr int OS2_ulCodePageRange2_offset = 82;
+
+/*
+ Some table hhea consts
+ cf https://docs.microsoft.com/fr-fr/typography/opentype/spec/hhea
+ TYPE NAME FROM BYTE
+ uint16 majorVersion 0
+ uint16 minorVersion 2
+ FWORD ascender 4
+ FWORD descender 6
+ FWORD lineGap 8
+ UFWORD advanceWidthMax 10
+ FWORD minLeftSideBearing 12
+ FWORD minRightSideBearing 14
+ FWORD xMaxExtent 16
+ int16 caretSlopeRise 18
+ int16 caretSlopeRun 20
+ int16 caretOffset 22
+ int16 (reserved) 24
+ int16 (reserved) 26
+ int16 (reserved) 28
+ int16 (reserved) 30
+ int16 metricDataFormat 32
+ uint16 numberOfHMetrics 34
+ END 36
+
+ => length for hhea table = 36 bytes
+
+*/
+constexpr int HHEA_Length = 36;
+
+constexpr int HHEA_ascender_offset = 4;
+constexpr int HHEA_descender_offset = 6;
+constexpr int HHEA_lineGap_offset = 8;
+constexpr int HHEA_caretSlopeRise_offset = 18;
+constexpr int HHEA_caretSlopeRun_offset = 20;
+
+/*
+ Some table post consts
+ cf https://docs.microsoft.com/fr-fr/typography/opentype/spec/post
+ TYPE NAME FROM BYTE
+ Fixed version 0
+ Fixed italicAngle 4
+ FWord underlinePosition 8
+ FWord underlineThickness 10
+ uint32 isFixedPitch 12
+ ...
+
+*/
+constexpr int POST_italicAngle_offset = 4;
+constexpr int POST_underlinePosition_offset = 8;
+constexpr int POST_underlineThickness_offset = 10;
+constexpr int POST_isFixedPitch_offset = 12;
+
+/*
+ Some table head consts
+ cf https://docs.microsoft.com/fr-fr/typography/opentype/spec/head
+ TYPE NAME FROM BYTE
+ uit16 majorVersion 0
+ uit16 minorVersion 2
+ Fixed fontRevision 4
+ uint32 checkSumAdjustment 8
+ uint32 magicNumber 12 (= 0x5F0F3CF5)
+ uint16 flags 16
+ uint16 unitsPerEm 18
+ LONGDATETIME created 20
+ LONGDATETIME modified 28
+ int16 xMin 36
+ int16 yMin 38
+ int16 xMax 40
+ int16 yMax 42
+ uint16 macStyle 44
+ uint16 lowestRecPPEM 46
+ int16 fontDirectionHint 48
+ int16 indexToLocFormat 50
+ int16 glyphDataFormat 52
+
+ END 54
+
+ => length head table = 54 bytes
+*/
+constexpr int HEAD_Length = 54;
+
+constexpr int HEAD_majorVersion_offset = 0;
+constexpr int HEAD_fontRevision_offset = 4;
+constexpr int HEAD_magicNumber_offset = 12;
+constexpr int HEAD_flags_offset = 16;
+constexpr int HEAD_unitsPerEm_offset = 18;
+constexpr int HEAD_created_offset = 20;
+constexpr int HEAD_xMin_offset = 36;
+constexpr int HEAD_yMin_offset = 38;
+constexpr int HEAD_xMax_offset = 40;
+constexpr int HEAD_yMax_offset = 42;
+constexpr int HEAD_macStyle_offset = 44;
+constexpr int HEAD_lowestRecPPEM_offset = 46;
+constexpr int HEAD_fontDirectionHint_offset = 48;
+constexpr int HEAD_indexToLocFormat_offset = 50;
+constexpr int HEAD_glyphDataFormat_offset = 52;
+
+/*
+ Some table maxp consts
+ cf https://docs.microsoft.com/fr-fr/typography/opentype/spec/maxp
+ For 0.5 version
+ TYPE NAME FROM BYTE
+ Fixed version 0
+ uint16 numGlyphs 4
+
+ For 1.0 Version
+ Fixed version 0
+ uint16 numGlyphs 4
+ uint16 maxPoints 6
+ uint16 maxContours 8
+ uint16 maxCompositePoints 10
+ uint16 maxCompositeContours 12
+ ...
+
+*/
+constexpr int MAXP_Version1Length = 32;
+
+constexpr int MAXP_numGlyphs_offset = 4;
+constexpr int MAXP_maxPoints_offset = 6;
+constexpr int MAXP_maxContours_offset = 8;
+constexpr int MAXP_maxCompositePoints_offset = 10;
+constexpr int MAXP_maxCompositeContours_offset = 12;
+
+/*
+ Some table glyf consts
+ cf https://docs.microsoft.com/fr-fr/typography/opentype/spec/glyf
+ For 0.5 version
+ TYPE NAME FROM BYTE
+ int16 numberOfContours 0
+ int16 xMin 2
+ int16 yMin 4
+ int16 xMax 6
+ int16 yMax 8
+
+ END 10
+
+ => length glyf table = 10 bytes
+
+*/
+constexpr int GLYF_Length = 10;
+
+constexpr int GLYF_numberOfContours_offset = 0;
+constexpr int GLYF_xMin_offset = 2;
+constexpr int GLYF_yMin_offset = 4;
+constexpr int GLYF_xMax_offset = 6;
+constexpr int GLYF_yMax_offset = 8;
+
+constexpr sal_uInt32 T_true = 0x74727565; /* 'true' */
+constexpr sal_uInt32 T_ttcf = 0x74746366; /* 'ttcf' */
+constexpr sal_uInt32 T_otto = 0x4f54544f; /* 'OTTO' */
+
+// standard TrueType table tags
+constexpr sal_uInt32 T_maxp = 0x6D617870;
+constexpr sal_uInt32 T_glyf = 0x676C7966;
+constexpr sal_uInt32 T_head = 0x68656164;
+constexpr sal_uInt32 T_loca = 0x6C6F6361;
+constexpr sal_uInt32 T_name = 0x6E616D65;
+constexpr sal_uInt32 T_hhea = 0x68686561;
+constexpr sal_uInt32 T_hmtx = 0x686D7478;
+constexpr sal_uInt32 T_cmap = 0x636D6170;
+constexpr sal_uInt32 T_vhea = 0x76686561;
+constexpr sal_uInt32 T_vmtx = 0x766D7478;
+constexpr sal_uInt32 T_OS2 = 0x4F532F32;
+constexpr sal_uInt32 T_post = 0x706F7374;
+constexpr sal_uInt32 T_cvt = 0x63767420;
+constexpr sal_uInt32 T_prep = 0x70726570;
+constexpr sal_uInt32 T_fpgm = 0x6670676D;
+constexpr sal_uInt32 T_CFF = 0x43464620;
+
+class AbstractTrueTypeFont;
+class TrueTypeFont;
+
+/**
+ * @defgroup sft Sun Font Tools Exported Functions
+ */
+
+/**
+ * Get the number of fonts contained in a TrueType collection
+ * @param fname - file name
+ * @return number of fonts or zero, if file is not a TTC file.
+ * @ingroup sft
+ */
+ int CountTTCFonts(const char* fname);
+
+/**
+ * TrueTypeFont constructor.
+ * The font file has to be provided as a memory buffer and length
+ * @param pBuffer - memory buffer
+ * @param nLen - size of memory buffer
+ * @param facenum - logical font number within a TTC file. This value is ignored
+ * for TrueType fonts
+ * @param ttf - returns the opened TrueTypeFont
+ * @param xCharMap - optional parsed character map
+ * @return value of SFErrCodes enum
+ * @ingroup sft
+ */
+ SFErrCodes VCL_DLLPUBLIC OpenTTFontBuffer(const void* pBuffer, sal_uInt32 nLen, sal_uInt32 facenum,
+ TrueTypeFont** ttf, const FontCharMapRef xCharMap = nullptr);
+#if !defined(_WIN32)
+/**
+ * TrueTypeFont constructor.
+ * Reads the font file and allocates the memory for the structure.
+ * on WIN32 the font has to be provided as a memory buffer and length
+ * @param fname - name of TrueType font file
+ * @param facenum - logical font number within a TTC file. This value is ignored
+ * for TrueType fonts
+ * @param ttf - returns the opened TrueTypeFont
+ * @param xCharMap - optional parsed character map
+ * @return value of SFErrCodes enum
+ * @ingroup sft
+ */
+ SFErrCodes VCL_DLLPUBLIC OpenTTFontFile(const char *fname, sal_uInt32 facenum, TrueTypeFont** ttf,
+ const FontCharMapRef xCharMap = nullptr);
+#endif
+
+ bool VCL_DLLPUBLIC getTTCoverage(
+ std::optional<std::bitset<UnicodeCoverage::MAX_UC_ENUM>> & rUnicodeCoverage,
+ std::optional<std::bitset<CodePageCoverage::MAX_CP_ENUM>> & rCodePageCoverage,
+ const unsigned char* pTable, size_t nLength);
+
+/**
+ * TrueTypeFont destructor. Deallocates the memory.
+ * @ingroup sft
+ */
+ void VCL_DLLPUBLIC CloseTTFont(TrueTypeFont *);
+
+/**
+ * Extracts TrueType control points, and stores them in an allocated array pointed to
+ * by *pointArray. This function returns the number of extracted points.
+ *
+ * @param ttf pointer to the TrueTypeFont structure
+ * @param glyphID Glyph ID
+ * @param pointArray Return value - address of the pointer to the first element of the array
+ * of points allocated by the function
+ * @return Returns the number of points in *pointArray or -1 if glyphID is
+ * invalid.
+ * @ingroup sft
+ *
+ */
+ int GetTTGlyphPoints(AbstractTrueTypeFont *ttf, sal_uInt32 glyphID, std::vector<ControlPoint>& pointArray);
+
+/**
+ * Extracts raw glyph data from the 'glyf' table and returns it in an allocated
+ * GlyphData structure.
+ *
+ * @param ttf pointer to the TrueTypeFont structure
+ * @param glyphID Glyph ID
+ *
+ * @return pointer to an allocated GlyphData structure or NULL if
+ * glyphID is not present in the font
+ * @ingroup sft
+ *
+ */
+ std::unique_ptr<GlyphData> GetTTRawGlyphData(AbstractTrueTypeFont *ttf, sal_uInt32 glyphID);
+
+/**
+ * For a specified glyph adds all component glyphs IDs to the list and
+ * return their number. If the glyph is a single glyph it has one component
+ * glyph (which is added to the list) and the function returns 1.
+ * For a composite glyphs it returns the number of component glyphs
+ * and adds all of them to the list.
+ *
+ * @param ttf pointer to the TrueTypeFont structure
+ * @param glyphID Glyph ID
+ * @param glyphlist list of glyphs
+ *
+ * @return number of component glyphs
+ * @ingroup sft
+ *
+ */
+ int GetTTGlyphComponents(AbstractTrueTypeFont *ttf, sal_uInt32 glyphID, std::vector< sal_uInt32 >& glyphlist);
+
+/**
+ * Extracts all Name Records from the font and stores them in an allocated
+ * array of NameRecord structs
+ *
+ * @param ttf pointer to the TrueTypeFont struct
+ * @param nr reference to the vector of NameRecord structs
+ *
+ * @ingroup sft
+ */
+
+ void GetTTNameRecords(AbstractTrueTypeFont const *ttf, std::vector<NameRecord>& nr);
+
+/**
+ * Generates a new TrueType font and dumps it to <b>outf</b> file.
+ * This function substitutes glyph 0 for all glyphIDs that are not found in the font.
+ * @param ttf pointer to the TrueTypeFont structure
+ * @param fname file name for the output TrueType font file
+ * @param glyphArray pointer to an array of glyphs that are to be extracted from ttf. The first
+ * element of this array has to be glyph 0 (default glyph)
+ * @param encoding array of encoding values. encoding[i] specifies character code for
+ * the glyphID glyphArray[i]. Character code 0 usually points to a default
+ * glyph (glyphID 0)
+ * @param nGlyphs number of glyph IDs in glyphArray and encoding values in encoding
+ * @param flags or'ed TTCreationFlags
+ * @return return the value of SFErrCodes enum
+ * @see SFErrCodes
+ * @ingroup sft
+ *
+ */
+ VCL_DLLPUBLIC SFErrCodes CreateTTFromTTGlyphs(AbstractTrueTypeFont *ttf,
+ std::vector<sal_uInt8>& rOutBuffer,
+ sal_uInt16 const *glyphArray,
+ sal_uInt8 const *encoding,
+ int nGlyphs);
+
+ VCL_DLLPUBLIC bool CreateTTFfontSubset(AbstractTrueTypeFont& aTTF,
+ std::vector<sal_uInt8>& rOutBuffer,
+ const sal_GlyphId* pGlyphIds,
+ const sal_uInt8* pEncoding,
+ int nGlyphCount, FontSubsetInfo& rInfo);
+
+/**
+ * Returns global font information about the TrueType font.
+ * @see TTGlobalFontInfo
+ *
+ * @param ttf pointer to a TrueTypeFont structure
+ * @param info pointer to a TTGlobalFontInfo structure
+ * @ingroup sft
+ *
+ */
+ VCL_DLLPUBLIC void GetTTGlobalFontInfo(AbstractTrueTypeFont *ttf, TTGlobalFontInfo *info);
+
+/**
+ * Returns part of the head table info, normally collected by GetTTGlobalFontInfo.
+ *
+ * Just implemented separate, because this info not available via Qt API.
+ *
+ * @param ttf pointer to a AbstractTrueTypeFont structure
+ * @param xMin global glyph bounding box min X
+ * @param yMin global glyph bounding box min Y
+ * @param xMax global glyph bounding box max X
+ * @param yMax global glyph bounding box max Y
+ * @param macStyle encoded Mac style flags of the font
+ * @return true, if table data could be decoded
+ * @ingroup sft
+ */
+ VCL_DLLPUBLIC bool GetTTGlobalFontHeadInfo(const AbstractTrueTypeFont *ttf, int& xMin, int& yMin, int& xMax, int& yMax, sal_uInt16& macStyle);
+
+/*- private definitions */
+
+/* indexes into TrueTypeFont::tables[] and TrueTypeFont::tlens[] */
+constexpr int O_maxp = 0;
+constexpr int O_glyf = 1; /* 'glyf' */
+constexpr int O_head = 2; /* 'head' */
+constexpr int O_loca = 3; /* 'loca' */
+constexpr int O_name = 4; /* 'name' */
+constexpr int O_hhea = 5; /* 'hhea' */
+constexpr int O_hmtx = 6; /* 'hmtx' */
+constexpr int O_cmap = 7; /* 'cmap' */
+constexpr int O_vhea = 8; /* 'vhea' */
+constexpr int O_vmtx = 9; /* 'vmtx' */
+constexpr int O_OS2 = 10; /* 'OS/2' */
+constexpr int O_post = 11; /* 'post' */
+constexpr int O_cvt = 12; /* 'cvt_' - only used in TT->TT generation */
+constexpr int O_prep = 13; /* 'prep' - only used in TT->TT generation */
+constexpr int O_fpgm = 14; /* 'fpgm' - only used in TT->TT generation */
+constexpr int O_CFF = 15; /* 'CFF' */
+constexpr int NUM_TAGS = 16;
+
+class VCL_DLLPUBLIC AbstractTrueTypeFont
+{
+ std::string m_sFileName;
+ sal_uInt32 m_nGlyphs;
+ sal_uInt32 m_nHorzMetrics;
+ sal_uInt32 m_nVertMetrics; /* if not 0 => font has vertical metrics information */
+ sal_uInt32 m_nUnitsPerEm;
+ std::vector<sal_uInt32> m_aGlyphOffsets;
+ FontCharMapRef m_xCharMap;
+ bool m_bMicrosoftSymbolEncoded;
+
+protected:
+ SFErrCodes indexGlyphData();
+
+public:
+ AbstractTrueTypeFont(const char* fileName = nullptr, const FontCharMapRef xCharMap = nullptr);
+ virtual ~AbstractTrueTypeFont();
+
+ SFErrCodes initialize();
+
+ std::string const & fileName() const { return m_sFileName; }
+ sal_uInt32 glyphCount() const { return m_nGlyphs; }
+ sal_uInt32 glyphOffset(sal_uInt32 glyphID) const;
+ sal_uInt32 horzMetricCount() const { return m_nHorzMetrics; }
+ sal_uInt32 vertMetricCount() const { return m_nVertMetrics; }
+ sal_uInt32 unitsPerEm() const { return m_nUnitsPerEm; }
+ bool IsMicrosoftSymbolEncoded() const { return m_bMicrosoftSymbolEncoded; }
+
+ virtual bool hasTable(sal_uInt32 ord) const = 0;
+ virtual const sal_uInt8* table(sal_uInt32 ord, sal_uInt32& size) const = 0;
+
+ OString psname;
+ OString family;
+ OUString ufamily;
+ OString subfamily;
+ OUString usubfamily;
+};
+
+class TrueTypeFont final : public AbstractTrueTypeFont
+{
+ struct TTFontTable_
+ {
+ const sal_uInt8* pData = nullptr; /* pointer to a raw subtable in the SFNT file */
+ sal_uInt32 nSize = 0; /* table size */
+ };
+
+ std::array<struct TTFontTable_, NUM_TAGS> m_aTableList;
+
+public:
+ sal_Int32 fsize;
+ sal_uInt8 *ptr;
+ sal_uInt32 ntables;
+
+ TrueTypeFont(const char* pFileName = nullptr, const FontCharMapRef xCharMap = nullptr);
+ ~TrueTypeFont() override;
+
+ SFErrCodes open(sal_uInt32 facenum);
+
+ bool hasTable(sal_uInt32 ord) const override { return m_aTableList[ord].pData != nullptr; }
+ inline const sal_uInt8* table(sal_uInt32 ord, sal_uInt32& size) const override;
+};
+
+const sal_uInt8* TrueTypeFont::table(sal_uInt32 ord, sal_uInt32& size) const
+{
+ if (ord >= NUM_TAGS)
+ {
+ size = 0;
+ return nullptr;
+ }
+
+ auto& rTable = m_aTableList[ord];
+ size = rTable.nSize;
+ return rTable.pData;
+}
+
+} // namespace vcl
+
+#endif // INCLUDED_VCL_INC_SFT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/skia/gdiimpl.hxx b/vcl/inc/skia/gdiimpl.hxx
new file mode 100644
index 0000000000..b879872a8b
--- /dev/null
+++ b/vcl/inc/skia/gdiimpl.hxx
@@ -0,0 +1,441 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SKIA_GDIIMPL_HXX
+#define INCLUDED_VCL_SKIA_GDIIMPL_HXX
+
+#include <vcl/dllapi.h>
+
+#include <salgdiimpl.hxx>
+#include <salgeom.hxx>
+
+#include <skia/utils.hxx>
+
+#include <SkPaint.h>
+#include <SkBlendMode.h>
+#include <optional>
+
+class SkiaFlushIdle;
+class GenericSalLayout;
+class SkFont;
+class SkiaSalBitmap;
+
+class VCL_DLLPUBLIC SkiaSalGraphicsImpl : public SalGraphicsImpl
+{
+public:
+ SkiaSalGraphicsImpl(SalGraphics& pParent, SalGeometryProvider* pProvider);
+ virtual ~SkiaSalGraphicsImpl() override;
+
+ virtual void Init() override;
+
+ virtual void DeInit() override;
+
+ virtual OUString getRenderBackendName() const override { return "skia"; }
+
+ const vcl::Region& getClipRegion() const;
+ virtual void setClipRegion(const vcl::Region&) override;
+
+ //
+ // get the depth of the device
+ virtual sal_uInt16 GetBitCount() const override;
+
+ // get the width of the device
+ virtual tools::Long GetGraphicsWidth() const override;
+
+ // set the clip region to empty
+ virtual void ResetClipRegion() override;
+
+ // set the line color to transparent (= don't draw lines)
+
+ virtual void SetLineColor() override;
+
+ // set the line color to a specific color
+ virtual void SetLineColor(Color nColor) override;
+
+ // set the fill color to transparent (= don't fill)
+ virtual void SetFillColor() override;
+
+ // set the fill color to a specific color, shapes will be
+ // filled accordingly
+ virtual void SetFillColor(Color nColor) override;
+
+ // enable/disable XOR drawing
+ virtual void SetXORMode(bool bSet, bool bInvertOnly) override;
+
+ // set line color for raster operations
+ virtual void SetROPLineColor(SalROPColor nROPColor) override;
+
+ // set fill color for raster operations
+ virtual void SetROPFillColor(SalROPColor nROPColor) override;
+
+ // draw --> LineColor and FillColor and RasterOp and ClipRegion
+ virtual void drawPixel(tools::Long nX, tools::Long nY) override;
+ virtual void drawPixel(tools::Long nX, tools::Long nY, Color nColor) override;
+
+ virtual void drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2,
+ tools::Long nY2) override;
+
+ virtual void drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight) override;
+
+ virtual void drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry) override;
+
+ virtual void drawPolygon(sal_uInt32 nPoints, const Point* pPtAry) override;
+
+ virtual void drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point** pPtAry) override;
+
+ virtual void drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon&, double fTransparency) override;
+
+ virtual bool drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon&, double fTransparency, double fLineWidth,
+ const std::vector<double>* pStroke, basegfx::B2DLineJoin,
+ css::drawing::LineCap, double fMiterMinimumAngle,
+ bool bPixelSnapHairline) override;
+
+ virtual bool drawPolyLineBezier(sal_uInt32 nPoints, const Point* pPtAry,
+ const PolyFlags* pFlgAry) override;
+
+ virtual bool drawPolygonBezier(sal_uInt32 nPoints, const Point* pPtAry,
+ const PolyFlags* pFlgAry) override;
+
+ virtual bool drawPolyPolygonBezier(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point* const* pPtAry,
+ const PolyFlags* const* pFlgAry) override;
+
+ // CopyArea --> No RasterOp, but ClipRegion
+ virtual void copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX,
+ tools::Long nSrcY, tools::Long nSrcWidth, tools::Long nSrcHeight,
+ bool bWindowInvalidate) override;
+
+ virtual void copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics) override;
+
+ virtual bool blendBitmap(const SalTwoRect&, const SalBitmap& rBitmap) override;
+
+ virtual bool blendAlphaBitmap(const SalTwoRect&, const SalBitmap& rSrcBitmap,
+ const SalBitmap& rMaskBitmap,
+ const SalBitmap& rAlphaBitmap) override;
+
+ virtual void drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap) override;
+
+ virtual void drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ const SalBitmap& rMaskBitmap) override;
+
+ virtual void drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ Color nMaskColor) override;
+
+ virtual std::shared_ptr<SalBitmap> getBitmap(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight) override;
+
+ virtual Color getPixel(tools::Long nX, tools::Long nY) override;
+
+ // invert --> ClipRegion (only Windows or VirDevs)
+ virtual void invert(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ SalInvert nFlags) override;
+
+ virtual void invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags) override;
+
+ virtual bool drawEPS(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ void* pPtr, sal_uInt32 nSize) override;
+
+ /** Render bitmap with alpha channel
+
+ @param rSourceBitmap
+ Source bitmap to blit
+
+ @param rAlphaBitmap
+ Alpha channel to use for blitting
+
+ @return true, if the operation succeeded, and false
+ otherwise. In this case, clients should try to emulate alpha
+ compositing themselves
+ */
+ virtual bool drawAlphaBitmap(const SalTwoRect&, const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap) override;
+
+ /** draw transformed bitmap (maybe with alpha) where Null, X, Y define the coordinate system */
+ virtual bool drawTransformedBitmap(const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY, const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap, double fAlpha) override;
+
+ virtual bool hasFastDrawTransformedBitmap() const override;
+
+ /** Render solid rectangle with given transparency
+
+ @param nX Top left coordinate of rectangle
+
+ @param nY Bottom right coordinate of rectangle
+
+ @param nWidth Width of rectangle
+
+ @param nHeight Height of rectangle
+
+ @param nTransparency Transparency value (0-255) to use. 0 blits and opaque, 255 a
+ fully transparent rectangle
+
+ @returns true if successfully drawn, false if not able to draw rectangle
+ */
+ virtual bool drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, sal_uInt8 nTransparency) override;
+
+ virtual bool drawGradient(const tools::PolyPolygon& rPolygon,
+ const Gradient& rGradient) override;
+ virtual bool implDrawGradient(const basegfx::B2DPolyPolygon& rPolyPolygon,
+ const SalGradient& rGradient) override;
+
+ virtual bool supportsOperation(OutDevSupportType eType) const override;
+
+ // Dump contents to a file for debugging.
+ void dump(const char* file) const;
+
+ // Default blend mode for SkPaint is SkBlendMode::kSrcOver
+ void drawBitmap(const SalTwoRect& rPosAry, const SkiaSalBitmap& bitmap,
+ SkBlendMode blendMode = SkBlendMode::kSrcOver);
+
+ void drawImage(const SalTwoRect& rPosAry, const sk_sp<SkImage>& aImage, int srcScaling = 1,
+ SkBlendMode eBlendMode = SkBlendMode::kSrcOver);
+
+ void drawShader(const SalTwoRect& rPosAry, const sk_sp<SkShader>& shader,
+ SkBlendMode blendMode = SkBlendMode::kSrcOver);
+
+ void drawGenericLayout(const GenericSalLayout& layout, Color textColor, const SkFont& font,
+ const SkFont& verticalFont);
+
+protected:
+ // To be called before any drawing.
+ void preDraw();
+ // To be called after any drawing.
+ void postDraw();
+ // The canvas to draw to.
+ SkCanvas* getDrawCanvas() { return mSurface->getCanvas(); }
+ // Call before makeImageSnapshot(), ensures the content is up to date.
+ void flushDrawing();
+
+ virtual void createSurface();
+ // Call to ensure that mSurface is valid. If mSurface is going to be modified,
+ // use preDraw() instead of this.
+ void checkSurface();
+ void destroySurface();
+ // Reimplemented for X11.
+ virtual bool avoidRecreateByResize() const;
+ void createWindowSurface(bool forceRaster = false);
+ virtual void createWindowSurfaceInternal(bool forceRaster = false) = 0;
+ void createOffscreenSurface();
+ virtual void flushSurfaceToWindowContext();
+
+ void privateDrawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, double nTransparency, bool blockAA = false);
+ void privateCopyBits(const SalTwoRect& rPosAry, SkiaSalGraphicsImpl* src);
+
+ void setProvider(SalGeometryProvider* provider) { mProvider = provider; }
+
+ bool isOffscreen() const;
+ bool isGPU() const { return mIsGPU; }
+
+ void invert(basegfx::B2DPolygon const& rPoly, SalInvert eFlags);
+
+ // Called by SkiaFlushIdle.
+ void performFlush();
+ void scheduleFlush();
+ friend class SkiaFlushIdle;
+
+ // get the width of the device
+ int GetWidth() const { return mProvider ? mProvider->GetWidth() : 1; }
+ // get the height of the device
+ int GetHeight() const { return mProvider ? mProvider->GetHeight() : 1; }
+ // Get the global HiDPI scaling factor.
+ virtual int getWindowScaling() const;
+
+ void addUpdateRegion(const SkRect& rect)
+ {
+ // Make slightly larger, just in case (rounding, antialiasing,...).
+ SkIRect addedRect = rect.makeOutset(2, 2).round();
+ // Using SkIRect should be enough, SkRegion would be too slow with many operations
+ // and swapping to the screen is not _that_slow.
+ mDirtyRect.join(addedRect);
+ }
+ void setCanvasScalingAndClipping();
+ void resetCanvasScalingAndClipping();
+ static void setCanvasClipRegion(SkCanvas* canvas, const vcl::Region& region);
+ sk_sp<SkImage> mergeCacheBitmaps(const SkiaSalBitmap& bitmap, const SkiaSalBitmap* alphaBitmap,
+ const Size& targetSize);
+ using DirectImage = SkiaHelper::DirectImage;
+ static OString makeCachedImageKey(const SkiaSalBitmap& bitmap, const SkiaSalBitmap* alphaBitmap,
+ const Size& targetSize, DirectImage bitmapType,
+ DirectImage alphaBitmapType);
+
+ // Skia uses floating point coordinates, so when we use integer coordinates, sometimes
+ // rounding results in off-by-one errors (down), especially when drawing using GPU,
+ // see https://bugs.chromium.org/p/skia/issues/detail?id=9611 . Compensate for
+ // it by using centers of pixels. Using 0.5 may sometimes round up, so go with 0.495 .
+ static constexpr SkScalar toSkX(tools::Long x) { return x + 0.495; }
+ static constexpr SkScalar toSkY(tools::Long y) { return y + 0.495; }
+ // Value to add to be exactly in the middle of the pixel.
+ static constexpr SkScalar toSkXYFix = SkScalar(0.005);
+
+ // Perform any pending drawing such as delayed merging of polygons. Called by preDraw()
+ // and anything that means the next operation cannot be another one in a series (e.g.
+ // changing colors).
+ void checkPendingDrawing();
+ bool delayDrawPolyPolygon(const basegfx::B2DPolyPolygon& polygon, double transparency);
+ void performDrawPolyPolygon(const basegfx::B2DPolyPolygon& polygon, double transparency,
+ bool useAA);
+
+ BmpScaleFlag goodScalingQuality() const { return SkiaHelper::goodScalingQuality(isGPU()); }
+ SkSamplingOptions makeSamplingOptions(const SalTwoRect& rPosAry, int scalingFactor,
+ int srcScalingFactor = 1)
+ {
+ return SkiaHelper::makeSamplingOptions(rPosAry, scalingFactor, srcScalingFactor, isGPU());
+ }
+ SkSamplingOptions makeSamplingOptions(const SkMatrix& matrix, int scalingFactor)
+ {
+ return SkiaHelper::makeSamplingOptions(goodScalingQuality(), matrix, scalingFactor);
+ }
+
+ // Create SkPaint to use when drawing to the surface. It is not to be used
+ // when doing internal drawing such as when merging two bitmaps together.
+ // This may apply some default settings to the paint as necessary.
+ SkPaint makePaintInternal() const;
+ // Create SkPaint set up for drawing lines (using mLineColor etc.).
+ SkPaint makeLinePaint(double transparency = 0) const;
+ // Create SkPaint set up for filling (using mFillColor etc.).
+ SkPaint makeFillPaint(double transparency = 0) const;
+ // Create SkPaint set up for bitmap drawing.
+ SkPaint makeBitmapPaint() const;
+ // Create SkPaint set up for gradient drawing.
+ SkPaint makeGradientPaint() const;
+ // Create SkPaint set up for text drawing.
+ SkPaint makeTextPaint(std::optional<Color> color) const;
+ // Create SkPaint for unspecified pixel drawing. Avoid if possible.
+ SkPaint makePixelPaint(std::optional<Color> color) const;
+
+ template <typename charT, typename traits>
+ friend inline std::basic_ostream<charT, traits>&
+ operator<<(std::basic_ostream<charT, traits>& stream, const SkiaSalGraphicsImpl* graphics)
+ {
+ if (graphics == nullptr)
+ return stream << "(null)";
+ // O - offscreen, G - GPU-based, R - raster
+ stream << static_cast<const void*>(graphics) << " "
+ << Size(graphics->GetWidth(), graphics->GetHeight());
+ if (graphics->mScaling != 1)
+ stream << "*" << graphics->mScaling;
+ stream << (graphics->isGPU() ? "G" : "R") << (graphics->isOffscreen() ? "O" : "");
+ return stream;
+ }
+
+ void windowBackingPropertiesChanged();
+
+ SalGraphics& mParent;
+ /// Pointer to the SalFrame or SalVirtualDevice
+ SalGeometryProvider* mProvider;
+ // The Skia surface that is target of all the rendering.
+ sk_sp<SkSurface> mSurface;
+ // Note that mSurface may be a proxy surface and not the one from the window context.
+ std::unique_ptr<sk_app::WindowContext> mWindowContext;
+ bool mIsGPU; // whether the surface is GPU-backed
+ // Note that we generally use VCL coordinates, which is not mSurface coordinates if mScaling!=1.
+ SkIRect mDirtyRect; // The area that has been changed since the last performFlush().
+ vcl::Region mClipRegion;
+ std::optional<Color> moLineColor;
+ std::optional<Color> moFillColor;
+ enum class XorMode
+ {
+ None,
+ Invert,
+ Xor
+ };
+ XorMode mXorMode;
+ std::unique_ptr<SkiaFlushIdle> mFlush;
+ // Info about pending polygons to draw (we try to merge adjacent polygons into one).
+ struct LastPolyPolygonInfo
+ {
+ basegfx::B2DPolyPolygonVector polygons;
+ basegfx::B2DRange bounds;
+ double transparency;
+ };
+ LastPolyPolygonInfo mLastPolyPolygonInfo;
+ inline static int pendingOperationsToFlush = 0;
+ int mScaling; // The scale factor for HiDPI screens.
+ bool mInWindowBackingPropertiesChanged;
+};
+
+inline SkPaint SkiaSalGraphicsImpl::makePaintInternal() const
+{
+ SkPaint paint;
+ // Invert could be done using a blend mode like invert() does, but
+ // intentionally use SkBlender to make sure it's not overwritten
+ // by a blend mode set later (which would be probably a mistake),
+ // and so that the drawing color does not actually matter.
+ if (mXorMode == XorMode::Invert)
+ SkiaHelper::setBlenderInvert(&paint);
+ else if (mXorMode == XorMode::Xor)
+ SkiaHelper::setBlenderXor(&paint);
+ return paint;
+}
+
+inline SkPaint SkiaSalGraphicsImpl::makeLinePaint(double transparency) const
+{
+ assert(moLineColor.has_value());
+ SkPaint paint = makePaintInternal();
+ paint.setColor(transparency == 0
+ ? SkiaHelper::toSkColor(*moLineColor)
+ : SkiaHelper::toSkColorWithTransparency(*moLineColor, transparency));
+ paint.setStyle(SkPaint::kStroke_Style);
+ return paint;
+}
+
+inline SkPaint SkiaSalGraphicsImpl::makeFillPaint(double transparency) const
+{
+ assert(moFillColor.has_value());
+ SkPaint paint = makePaintInternal();
+ paint.setColor(transparency == 0
+ ? SkiaHelper::toSkColor(*moFillColor)
+ : SkiaHelper::toSkColorWithTransparency(*moFillColor, transparency));
+ if (moLineColor == moFillColor)
+ paint.setStyle(SkPaint::kStrokeAndFill_Style);
+ else
+ paint.setStyle(SkPaint::kFill_Style);
+ return paint;
+}
+
+inline SkPaint SkiaSalGraphicsImpl::makeBitmapPaint() const { return makePaintInternal(); }
+
+inline SkPaint SkiaSalGraphicsImpl::makeGradientPaint() const { return makePaintInternal(); }
+
+inline SkPaint SkiaSalGraphicsImpl::makeTextPaint(std::optional<Color> color) const
+{
+ assert(color.has_value());
+ SkPaint paint = makePaintInternal();
+ paint.setColor(SkiaHelper::toSkColor(*color));
+ return paint;
+}
+
+inline SkPaint SkiaSalGraphicsImpl::makePixelPaint(std::optional<Color> color) const
+{
+ assert(color.has_value());
+ SkPaint paint = makePaintInternal();
+ paint.setColor(SkiaHelper::toSkColor(*color));
+ return paint;
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/skia/osx/bitmap.hxx b/vcl/inc/skia/osx/bitmap.hxx
new file mode 100644
index 0000000000..0c0b1ee09b
--- /dev/null
+++ b/vcl/inc/skia/osx/bitmap.hxx
@@ -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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_SKIA_OSX_BITMAP_HXX
+#define INCLUDED_VCL_INC_SKIA_OSX_BITMAP_HXX
+
+#include <vcl/dllapi.h>
+
+#include <osx/osxvcltypes.h>
+
+class Image;
+
+namespace SkiaHelper
+{
+VCL_PLUGIN_PUBLIC CGImageRef createCGImage(const Image& rImage);
+};
+
+#endif // INCLUDED_VCL_INC_SKIA_OSX_BITMAP_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/skia/osx/gdiimpl.hxx b/vcl/inc/skia/osx/gdiimpl.hxx
new file mode 100644
index 0000000000..b97245e86e
--- /dev/null
+++ b/vcl/inc/skia/osx/gdiimpl.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_SKIA_OSX_GDIIMPL_HXX
+#define INCLUDED_VCL_INC_SKIA_OSX_GDIIMPL_HXX
+
+#include <vcl/dllapi.h>
+
+#include <quartz/salgdi.h>
+
+#include <skia/gdiimpl.hxx>
+#include <skia/utils.hxx>
+
+#include <SkFontMgr.h>
+
+class VCL_PLUGIN_PUBLIC AquaSkiaSalGraphicsImpl final : public SkiaSalGraphicsImpl,
+ public AquaGraphicsBackendBase
+{
+public:
+ AquaSkiaSalGraphicsImpl(AquaSalGraphics& rParent, AquaSharedAttributes& rShared);
+ virtual ~AquaSkiaSalGraphicsImpl() override;
+
+ virtual void freeResources() override;
+
+ virtual void UpdateGeometryProvider(SalGeometryProvider* provider) override
+ {
+ setProvider(provider);
+ }
+ static void prepareSkia();
+
+ virtual bool drawNativeControl(ControlType nType, ControlPart nPart,
+ const tools::Rectangle& rControlRegion, ControlState nState,
+ const ImplControlValue& aValue) override;
+
+ virtual void drawTextLayout(const GenericSalLayout& layout) override;
+
+ virtual void Flush() override;
+ virtual void Flush(const tools::Rectangle&) override;
+ virtual void WindowBackingPropertiesChanged() override;
+
+private:
+ virtual int getWindowScaling() const override;
+ virtual void createWindowSurfaceInternal(bool forceRaster = false) override;
+ virtual void flushSurfaceToWindowContext() override;
+ void flushSurfaceToScreenCG();
+ static inline sk_sp<SkFontMgr> fontManager;
+};
+
+#endif // INCLUDED_VCL_INC_SKIA_OSX_GDIIMPL_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/skia/quartz/cgutils.h b/vcl/inc/skia/quartz/cgutils.h
new file mode 100644
index 0000000000..cb43a31dcd
--- /dev/null
+++ b/vcl/inc/skia/quartz/cgutils.h
@@ -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 .
+ */
+
+#pragma once
+
+constexpr uint32_t SkiaToCGBitmapType(SkColorType color, SkAlphaType alpha)
+{
+ if (alpha == kPremul_SkAlphaType)
+ {
+ return color == kBGRA_8888_SkColorType
+ ? (uint32_t(kCGImageAlphaPremultipliedFirst)
+ | uint32_t(kCGBitmapByteOrder32Little))
+ : (uint32_t(kCGImageAlphaPremultipliedLast) | uint32_t(kCGBitmapByteOrder32Big));
+ }
+ else
+ {
+ assert(alpha == kOpaque_SkAlphaType);
+ return color == kBGRA_8888_SkColorType
+ ? (uint32_t(kCGImageAlphaNoneSkipFirst) | uint32_t(kCGBitmapByteOrder32Little))
+ : (uint32_t(kCGImageAlphaNoneSkipLast) | uint32_t(kCGBitmapByteOrder32Big));
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/skia/salbmp.hxx b/vcl/inc/skia/salbmp.hxx
new file mode 100644
index 0000000000..c42aa30f46
--- /dev/null
+++ b/vcl/inc/skia/salbmp.hxx
@@ -0,0 +1,231 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SKIA_SALBMP_H
+#define INCLUDED_VCL_INC_SKIA_SALBMP_H
+
+#include <salbmp.hxx>
+#include <vcl/bitmap.hxx>
+
+#include <boost/shared_ptr.hpp>
+
+#include <skia/utils.hxx>
+
+#include <SkImage.h>
+
+class VCL_PLUGIN_PUBLIC SkiaSalBitmap final : public SalBitmap
+{
+public:
+ SkiaSalBitmap();
+ SkiaSalBitmap(const sk_sp<SkImage>& image);
+ virtual ~SkiaSalBitmap() override;
+
+ // SalBitmap methods
+ virtual bool Create(const Size& rSize, vcl::PixelFormat ePixelFormat,
+ const BitmapPalette& rPal) override;
+ virtual bool Create(const SalBitmap& rSalBmp) override;
+ virtual bool Create(const SalBitmap& rSalBmp, SalGraphics* pGraphics) override;
+ virtual bool Create(const SalBitmap& rSalBmp, vcl::PixelFormat eNewPixelFormat) override;
+ virtual bool Create(const css::uno::Reference<css::rendering::XBitmapCanvas>& rBitmapCanvas,
+ Size& rSize, bool bMask = false) override;
+
+ virtual void Destroy() final override;
+
+ virtual Size GetSize() const override;
+ virtual sal_uInt16 GetBitCount() const override;
+
+ virtual BitmapBuffer* AcquireBuffer(BitmapAccessMode nMode) override;
+ virtual void ReleaseBuffer(BitmapBuffer* pBuffer, BitmapAccessMode nMode) override;
+
+ virtual bool GetSystemData(BitmapSystemData& rData) override;
+
+ virtual bool ScalingSupported() const override;
+ virtual bool Scale(const double& rScaleX, const double& rScaleY,
+ BmpScaleFlag nScaleFlag) override;
+ virtual bool Replace(const Color& rSearchColor, const Color& rReplaceColor,
+ sal_uInt8 nTol) override;
+ virtual bool InterpretAs8Bit() override;
+ virtual bool ConvertToGreyscale() override;
+ virtual bool Erase(const Color& color) override;
+ virtual bool AlphaBlendWith(const SalBitmap& rSalBmp) override;
+ virtual bool Invert() override;
+#if defined MACOSX || defined IOS
+ virtual CGImageRef CreateWithMask(const SalBitmap& rMask, int nX, int nY, int nWidth,
+ int nHeight) const override;
+ virtual CGImageRef CreateColorMask(int nX, int nY, int nWidth, int nHeight,
+ Color nMaskColor) const override;
+ virtual CGImageRef CreateCroppedImage(int nX, int nY, int nWidth, int nHeight) const override;
+#endif
+
+ const BitmapPalette& Palette() const { return mPalette; }
+
+ // True if GetSkShader() should be preferred to GetSkImage() (or the Alpha variants).
+ bool PreferSkShader() const;
+
+ // Direct image means direct access to the stored SkImage, without checking
+ // if its size is up to date. This should be used only in special cases with care.
+ using DirectImage = SkiaHelper::DirectImage;
+ // Returns the contents as SkImage (possibly GPU-backed).
+ const sk_sp<SkImage>& GetSkImage(DirectImage direct = DirectImage::No) const;
+ sk_sp<SkShader> GetSkShader(const SkSamplingOptions& samplingOptions,
+ DirectImage direct = DirectImage::No) const;
+ // Returns the contents as alpha SkImage (possibly GPU-backed)
+ const sk_sp<SkImage>& GetAlphaSkImage(DirectImage direct = DirectImage::No) const;
+ sk_sp<SkShader> GetAlphaSkShader(const SkSamplingOptions& samplingOptions,
+ DirectImage direct = DirectImage::No) const;
+
+ // Key for caching/hashing.
+ OString GetImageKey(DirectImage direct = DirectImage::No) const;
+ OString GetAlphaImageKey(DirectImage direct = DirectImage::No) const;
+
+ // Returns true if it is known that this bitmap can be ignored if it's to be used
+ // as an alpha bitmap. An optimization, not guaranteed to return true for all such cases.
+ bool IsFullyOpaqueAsAlpha() const;
+ // Alpha type best suitable for the content.
+ SkAlphaType alphaType() const;
+
+ // Tries to create direct GetAlphaSkImage() from direct GetSkImage().
+ void TryDirectConvertToAlphaNoScaling();
+
+ // Dump contents to a file for debugging.
+ void dump(const char* file) const;
+
+ // These are to be used only by unittests.
+ bool unittestHasBuffer() const { return mBuffer.get(); }
+ bool unittestHasImage() const { return mImage.get(); }
+ bool unittestHasAlphaImage() const { return mAlphaImage.get(); }
+ bool unittestHasEraseColor() const { return mEraseColorSet; }
+ bool unittestHasPendingScale() const { return mSize != mPixelsSize; }
+ const sal_uInt8* unittestGetBuffer() const { return mBuffer.get(); }
+ const SkImage* unittestGetImage() const { return mImage.get(); }
+ const SkImage* unittestGetAlphaImage() const { return mAlphaImage.get(); }
+ void unittestResetToImage() { ResetToSkImage(GetSkImage()); }
+
+private:
+ // This should be called whenever the contents have (possibly) changed.
+ // It may reset some cached data such as the checksum.
+ void DataChanged();
+ // Reset the state to pixel data (resets cached images allocated in GetSkImage()/GetAlphaSkImage()).
+ void ResetToBuffer();
+ // Sets the data only as SkImage (will be converted as needed).
+ void ResetToSkImage(sk_sp<SkImage> image);
+ // Resets all data (buffer and images).
+ void ResetAllData();
+ // Call to ensure mBuffer has data (will convert from mImage if necessary).
+ void EnsureBitmapData();
+ void EnsureBitmapData() const { return const_cast<SkiaSalBitmap*>(this)->EnsureBitmapData(); }
+ // Like EnsureBitmapData(), but will also make any shared data unique.
+ // Call before changing the data.
+ void EnsureBitmapUniqueData();
+ // Allocate mBuffer (with uninitialized contents).
+ void CreateBitmapData();
+ // Should be called whenever mPixelsSize or mBitCount is set/changed.
+ bool ComputeScanlineSize();
+ // Resets information about pending scaling. To be called when mBuffer is resized or created.
+ void ResetPendingScaling();
+ // Sets bitmap to be erased on demand.
+ void EraseInternal(const Color& color);
+ // Sets pixels to the erase color.
+ void PerformErase();
+ // Try to find out if the content is completely black. Used for optimizations,
+ // not guaranteed to always return true for such bitmaps.
+ bool IsAllBlack() const;
+ void ReleaseBuffer(BitmapBuffer* pBuffer, BitmapAccessMode nMode, bool dontChangeToErase);
+ SkBitmap GetAsSkBitmap() const;
+ bool ConserveMemory() const;
+ void verify() const
+#ifdef DBG_UTIL
+ ;
+#else
+ {
+ }
+#endif
+
+ template <typename charT, typename traits>
+ friend std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& stream,
+ const SkiaSalBitmap* bitmap)
+ {
+ if (bitmap == nullptr)
+ return stream << "(null)";
+ // p - has (non-trivial) palette
+ // I/i - has SkImage (on GPU/CPU),
+ // A/a - has alpha SkImage (on GPU/CPU)
+ // E - has erase color
+ // B - has pixel buffer
+ // (wxh) - has pending scaling (after each item)
+ stream << static_cast<const void*>(bitmap) << " " << bitmap->GetSize() << "x"
+ << bitmap->GetBitCount();
+ if (bitmap->GetBitCount() <= 8 && !bitmap->Palette().IsGreyPalette8Bit())
+ stream << "p";
+ stream << "/";
+ if (bitmap->mImage)
+ {
+ stream << (bitmap->mImage->isTextureBacked() ? "I" : "i");
+ if (SkiaHelper::imageSize(bitmap->mImage) != bitmap->mSize)
+ stream << "(" << SkiaHelper::imageSize(bitmap->mImage) << ")";
+ }
+ if (bitmap->mAlphaImage)
+ {
+ stream << (bitmap->mAlphaImage->isTextureBacked() ? "A" : "a");
+ if (SkiaHelper::imageSize(bitmap->mAlphaImage) != bitmap->mSize)
+ stream << "(" << SkiaHelper::imageSize(bitmap->mAlphaImage) << ")";
+ }
+ if (bitmap->mEraseColorSet)
+ stream << "E" << bitmap->mEraseColor;
+ if (bitmap->mBuffer)
+ {
+ stream << "B";
+ if (bitmap->mSize != bitmap->mPixelsSize)
+ stream << "(" << bitmap->mPixelsSize << ")";
+ }
+ return stream;
+ }
+
+ BitmapPalette mPalette;
+ int mBitCount = 0; // bpp
+ Size mSize;
+ // The contents of the bitmap may be stored in several different ways:
+ // As mBuffer buffer, which normally stores pixels in the given format.
+ // As SkImage, as cached GPU-backed data, but sometimes also a result of some operation.
+ // There is no "master" storage that the other would be derived from. The usual
+ // mode of operation is that mBuffer holds the data, mImage is created
+ // on demand as GPU-backed cached data by calling GetSkImage(), and the cached mImage
+ // is reset by ResetCachedImage(). But sometimes only mImage will be set and in that case
+ // mBuffer must be filled from it on demand if necessary by EnsureBitmapData().
+ boost::shared_ptr<sal_uInt8[]> mBuffer;
+ int mScanlineSize; // size of one row in mBuffer (based on mPixelsSize)
+ sk_sp<SkImage> mImage; // possibly GPU-backed
+ bool mImageImmutable = false;
+ sk_sp<SkImage> mAlphaImage; // cached contents as alpha image, possibly GPU-backed
+ // Actual scaling triggered by scale() is done on-demand. This is the size of the pixel
+ // data in mBuffer, if it differs from mSize, then there is a scaling operation pending.
+ Size mPixelsSize;
+ BmpScaleFlag mScaleQuality = BmpScaleFlag::BestQuality; // quality for on-demand scaling
+ // Erase() is delayed, just sets these two instead of filling the buffer.
+ bool mEraseColorSet = false;
+ Color mEraseColor;
+ int mReadAccessCount = 0; // number of read AcquireAccess() that have not been released
+#ifdef DBG_UTIL
+ int mWriteAccessCount = 0; // number of write AcquireAccess() that have not been released
+#endif
+};
+
+#endif // INCLUDED_VCL_INC_SKIA_SALBMP_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/skia/utils.hxx b/vcl/inc/skia/utils.hxx
new file mode 100644
index 0000000000..3c19192e1c
--- /dev/null
+++ b/vcl/inc/skia/utils.hxx
@@ -0,0 +1,341 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_SKIA_UTILS_H
+#define INCLUDED_VCL_INC_SKIA_UTILS_H
+
+#include <vcl/skia/SkiaHelper.hxx>
+
+#include <tools/gen.hxx>
+#include <driverblocklist.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/salgtype.hxx>
+
+#include <test/GraphicsRenderTests.hxx>
+
+#include <premac.h>
+#include <SkRegion.h>
+#include <SkSurface.h>
+#if defined __GNUC__ && !defined __clang__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wattributes"
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+#include <tools/sk_app/WindowContext.h>
+#if defined __GNUC__ && !defined __clang__
+#pragma GCC diagnostic pop
+#endif
+#include <postmac.h>
+
+#include <string_view>
+
+namespace SkiaHelper
+{
+// Get the one shared GrDirectContext instance.
+GrDirectContext* getSharedGrDirectContext();
+
+void disableRenderMethod(RenderMethod method);
+
+// Create SkSurface, GPU-backed if possible.
+VCL_DLLPUBLIC sk_sp<SkSurface> createSkSurface(int width, int height,
+ SkColorType type = kN32_SkColorType,
+ SkAlphaType alpha = kPremul_SkAlphaType);
+
+inline sk_sp<SkSurface> createSkSurface(const Size& size, SkColorType type = kN32_SkColorType,
+ SkAlphaType alpha = kPremul_SkAlphaType)
+{
+ return createSkSurface(size.Width(), size.Height(), type, alpha);
+}
+
+inline sk_sp<SkSurface> createSkSurface(int width, int height, SkAlphaType alpha)
+{
+ return createSkSurface(width, height, kN32_SkColorType, alpha);
+}
+
+inline sk_sp<SkSurface> createSkSurface(const Size& size, SkAlphaType alpha)
+{
+ return createSkSurface(size.Width(), size.Height(), kN32_SkColorType, alpha);
+}
+
+// Create SkImage, GPU-backed if possible.
+VCL_DLLPUBLIC sk_sp<SkImage> createSkImage(const SkBitmap& bitmap);
+
+// Call surface->makeImageSnapshot() and abort on failure.
+VCL_DLLPUBLIC sk_sp<SkImage> makeCheckedImageSnapshot(sk_sp<SkSurface> surface);
+VCL_DLLPUBLIC sk_sp<SkImage> makeCheckedImageSnapshot(sk_sp<SkSurface> surface,
+ const SkIRect& bounds);
+
+inline Size imageSize(const sk_sp<SkImage>& image) { return Size(image->width(), image->height()); }
+
+inline SkColor toSkColor(Color color)
+{
+ return SkColorSetARGB(color.GetAlpha(), color.GetRed(), color.GetGreen(), color.GetBlue());
+}
+
+inline SkColor toSkColorWithTransparency(Color aColor, double fTransparency)
+{
+ return SkColorSetA(toSkColor(aColor), 255 * (1.0 - fTransparency));
+}
+
+inline SkColor toSkColorWithIntensity(Color color, int intensity)
+{
+ return SkColorSetARGB(color.GetAlpha(), color.GetRed() * intensity / 100,
+ color.GetGreen() * intensity / 100, color.GetBlue() * intensity / 100);
+}
+
+inline Color fromSkColor(SkColor color)
+{
+ return Color(ColorAlpha, SkColorGetA(color), SkColorGetR(color), SkColorGetG(color),
+ SkColorGetB(color));
+}
+
+// Whether to use GetSkImage() that checks for delayed scaling or whether to access
+// the stored image directly without checks.
+enum DirectImage
+{
+ Yes,
+ No
+};
+
+// Sets SkBlender that will do an invert operation.
+void setBlenderInvert(SkPaint* paint);
+// Sets SkBlender that will do a xor operation.
+void setBlenderXor(SkPaint* paint);
+
+// Must be called in any VCL backend before any Skia functionality is used.
+// If not set, Skia will be disabled.
+VCL_DLLPUBLIC void
+ prepareSkia(std::unique_ptr<sk_app::WindowContext> (*createGpuWindowContext)(bool));
+
+// Shared cache of images.
+void addCachedImage(const OString& key, sk_sp<SkImage> image);
+sk_sp<SkImage> findCachedImage(const OString& key);
+void removeCachedImage(sk_sp<SkImage> image);
+tools::Long maxImageCacheSize();
+
+// Get checksum of the image content, only for raster images. Is cached,
+// but may still be somewhat expensive.
+uint32_t getSkImageChecksum(sk_sp<SkImage> image);
+
+// SkSurfaceProps to be used by all Skia surfaces.
+VCL_DLLPUBLIC const SkSurfaceProps* surfaceProps();
+// Set pixel geometry to be used by SkSurfaceProps.
+VCL_DLLPUBLIC void setPixelGeometry(SkPixelGeometry pixelGeometry);
+
+inline bool isUnitTestRunning(const char* name = nullptr)
+{
+ if (name == nullptr)
+ {
+ static const char* const testname = getenv("LO_TESTNAME");
+ if (testname != nullptr)
+ return true;
+ return !vcl::test::activeGraphicsRenderTest().isEmpty();
+ }
+ const char* const testname = getenv("LO_TESTNAME");
+ if (testname != nullptr && std::string_view(name) == testname)
+ return true;
+ return vcl::test::activeGraphicsRenderTest().equalsAscii(name);
+}
+
+// Scaling done on the GPU is fast, but bicubic done in raster mode can be slow
+// if done too much, and it generally shouldn't be needed for to-screen drawing.
+// In that case use only BmpScaleFlag::Default, which is bilinear+mipmap,
+// which should be good enough (and that's what the "super" bitmap scaling
+// algorithm done by VCL does as well).
+inline BmpScaleFlag goodScalingQuality(bool isGPU)
+{
+ return isGPU ? BmpScaleFlag::BestQuality : BmpScaleFlag::Default;
+}
+
+// Normal scaling algorithms have a poor quality when downscaling a lot.
+// https://bugs.chromium.org/p/skia/issues/detail?id=11810 suggests to use mipmaps
+// in such a case, which is annoying to do explicitly instead of Skia deciding which
+// algorithm would be the best, but now with Skia removing SkFilterQuality and requiring
+// explicitly being told what algorithm to use this appears to be the best we can do.
+// Anything scaled down at least this ratio will use linear+mipmaps.
+constexpr int downscaleRatioThreshold = 4;
+
+inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scalingType, SkMatrix matrix,
+ int scalingFactor)
+{
+ switch (scalingType)
+ {
+ case BmpScaleFlag::BestQuality:
+ if (scalingFactor != 1)
+ matrix.postScale(scalingFactor, scalingFactor);
+ if (matrix.getScaleX() <= 1.0 / downscaleRatioThreshold
+ || matrix.getScaleY() <= 1.0 / downscaleRatioThreshold)
+ return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
+ return SkSamplingOptions(SkCubicResampler::Mitchell());
+ case BmpScaleFlag::Default:
+ // Use SkMipmapMode::kNearest for better quality when downscaling. SkMipmapMode::kLinear
+ // would be even better, but it is not specially optimized in raster mode.
+ return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNearest);
+ case BmpScaleFlag::Fast:
+ case BmpScaleFlag::NearestNeighbor:
+ return SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone);
+ default:
+ assert(false);
+ return SkSamplingOptions();
+ }
+}
+
+inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scalingType, const Size& srcSize,
+ Size destSize, int scalingFactor)
+{
+ switch (scalingType)
+ {
+ case BmpScaleFlag::BestQuality:
+ if (scalingFactor != 1)
+ destSize *= scalingFactor;
+ if (srcSize.Width() / destSize.Width() >= downscaleRatioThreshold
+ || srcSize.Height() / destSize.Height() >= downscaleRatioThreshold)
+ return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
+ return SkSamplingOptions(SkCubicResampler::Mitchell());
+ case BmpScaleFlag::Default:
+ // As in the first overload, use kNearest.
+ return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNearest);
+ case BmpScaleFlag::Fast:
+ case BmpScaleFlag::NearestNeighbor:
+ return SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone);
+ default:
+ assert(false);
+ return SkSamplingOptions();
+ }
+}
+
+inline SkSamplingOptions makeSamplingOptions(const SalTwoRect& rPosAry, int scalingFactor,
+ int srcScalingFactor, bool isGPU)
+{
+ // If there will be scaling, make it smooth, but not in unittests, as those often
+ // require exact color values and would be confused by this.
+ if (isUnitTestRunning())
+ return SkSamplingOptions(); // none
+ Size srcSize(rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+ Size destSize(rPosAry.mnDestWidth, rPosAry.mnDestHeight);
+ if (scalingFactor != 1)
+ destSize *= scalingFactor;
+ if (srcScalingFactor != 1)
+ srcSize *= srcScalingFactor;
+ if (srcSize != destSize)
+ return makeSamplingOptions(goodScalingQuality(isGPU), srcSize, destSize, 1);
+ return SkSamplingOptions(); // none
+}
+
+inline SkRect scaleRect(const SkRect& rect, int scaling)
+{
+ return SkRect::MakeXYWH(rect.x() * scaling, rect.y() * scaling, rect.width() * scaling,
+ rect.height() * scaling);
+}
+
+inline SkIRect scaleRect(const SkIRect& rect, int scaling)
+{
+ return SkIRect::MakeXYWH(rect.x() * scaling, rect.y() * scaling, rect.width() * scaling,
+ rect.height() * scaling);
+}
+
+#ifdef DBG_UTIL
+void prefillSurface(const sk_sp<SkSurface>& surface);
+#endif
+
+VCL_DLLPUBLIC void dump(const SkBitmap& bitmap, const char* file);
+VCL_DLLPUBLIC void dump(const sk_sp<SkImage>& image, const char* file);
+VCL_DLLPUBLIC void dump(const sk_sp<SkSurface>& surface, const char* file);
+
+VCL_DLLPUBLIC extern uint32_t vendorId;
+
+inline DriverBlocklist::DeviceVendor getVendor()
+{
+ return DriverBlocklist::GetVendorFromId(vendorId);
+}
+
+} // namespace SkiaHelper
+
+// For unittests.
+namespace SkiaTests
+{
+VCL_DLLPUBLIC bool matrixNeedsHighQuality(const SkMatrix& matrix);
+}
+
+template <typename charT, typename traits>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& stream,
+ const SkRect& rectangle)
+{
+ if (rectangle.isEmpty())
+ return stream << "EMPTY";
+ else
+ return stream << rectangle.width() << 'x' << rectangle.height() << "@(" << rectangle.x()
+ << ',' << rectangle.y() << ")";
+}
+
+template <typename charT, typename traits>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& stream,
+ const SkIRect& rectangle)
+{
+ if (rectangle.isEmpty())
+ return stream << "EMPTY";
+ else
+ return stream << rectangle.width() << 'x' << rectangle.height() << "@(" << rectangle.x()
+ << ',' << rectangle.y() << ")";
+}
+
+template <typename charT, typename traits>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& stream,
+ const SkRegion& region)
+{
+ if (region.isEmpty())
+ return stream << "EMPTY";
+ stream << "(";
+ SkRegion::Iterator it(region);
+ for (int i = 0; !it.done(); it.next(), ++i)
+ stream << "[" << i << "] " << it.rect();
+ stream << ")";
+ return stream;
+}
+
+template <typename charT, typename traits>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& stream,
+ const SkMatrix& matrix)
+{
+ return stream << "[" << matrix[0] << " " << matrix[1] << " " << matrix[2] << "]"
+ << "[" << matrix[3] << " " << matrix[4] << " " << matrix[5] << "]"
+ << "[" << matrix[6] << " " << matrix[7] << " " << matrix[8] << "]";
+}
+
+template <typename charT, typename traits>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& stream,
+ const SkImage& image)
+{
+ // G - on GPU
+ return stream << static_cast<const void*>(&image) << " " << Size(image.width(), image.height())
+ << "/" << (SkColorTypeBytesPerPixel(image.imageInfo().colorType()) * 8)
+ << (image.isTextureBacked() ? "G" : "");
+}
+template <typename charT, typename traits>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& stream,
+ const sk_sp<SkImage>& image)
+{
+ if (image == nullptr)
+ return stream << "(null)";
+ return stream << *image;
+}
+
+#endif // INCLUDED_VCL_INC_SKIA_UTILS_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/skia/win/font.hxx b/vcl/inc/skia/win/font.hxx
new file mode 100644
index 0000000000..b63c5cba47
--- /dev/null
+++ b/vcl/inc/skia/win/font.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/.
+ *
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <win/winlayout.hxx>
+
+#include <SkTypeface.h>
+
+// This class only adds SkTypeface in order to allow its caching.
+class SkiaWinFontInstance : public WinFontInstance
+{
+ friend rtl::Reference<LogicalFontInstance>
+ WinFontFace::CreateFontInstance(const vcl::font::FontSelectPattern&) const;
+
+public:
+ sk_sp<SkTypeface> GetSkiaTypeface() const { return m_skiaTypeface; }
+ bool GetSkiaDWrite() const { return m_skiaDWrite; }
+ void SetSkiaTypeface(const sk_sp<SkTypeface>& typeface, bool dwrite)
+ {
+ m_skiaTypeface = typeface;
+ m_skiaDWrite = dwrite;
+ }
+
+private:
+ using WinFontInstance::WinFontInstance;
+ sk_sp<SkTypeface> m_skiaTypeface;
+ bool m_skiaDWrite;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/skia/win/gdiimpl.hxx b/vcl/inc/skia/win/gdiimpl.hxx
new file mode 100644
index 0000000000..c5b12d0881
--- /dev/null
+++ b/vcl/inc/skia/win/gdiimpl.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_SKIA_WIN_GDIIMPL_HXX
+#define INCLUDED_VCL_INC_SKIA_WIN_GDIIMPL_HXX
+
+#include <memory>
+#include <systools/win32/comtools.hxx>
+
+#include <vcl/dllapi.h>
+#include <skia/gdiimpl.hxx>
+#include <skia/utils.hxx>
+#include <win/salgdi.h>
+#include <win/wingdiimpl.hxx>
+#include <o3tl/lru_map.hxx>
+#include <ControlCacheKey.hxx>
+#include <svdata.hxx>
+
+#include <SkFont.h>
+#include <SkFontMgr.h>
+
+#include <dwrite_3.h>
+
+class SkTypeface;
+class ControlCacheKey;
+
+class SkiaCompatibleDC : public CompatibleDC
+{
+public:
+ SkiaCompatibleDC(SalGraphics& rGraphics, int x, int y, int width, int height);
+
+ sk_sp<SkImage> getAsImageDiff(const SkiaCompatibleDC& white) const;
+};
+
+class WinSkiaSalGraphicsImpl : public SkiaSalGraphicsImpl, public WinSalGraphicsImplBase
+{
+private:
+ WinSalGraphics& mWinParent;
+
+public:
+ WinSkiaSalGraphicsImpl(WinSalGraphics& rGraphics, SalGeometryProvider* mpProvider);
+
+ virtual bool UseRenderNativeControl() const override { return true; }
+ virtual bool TryRenderCachedNativeControl(ControlCacheKey const& rControlCacheKey, int nX,
+ int nY) override;
+ virtual bool RenderAndCacheNativeControl(CompatibleDC& rWhite, CompatibleDC& rBlack, int nX,
+ int nY, ControlCacheKey& aControlCacheKey) override;
+
+ virtual bool DrawTextLayout(const GenericSalLayout& layout) override;
+ virtual void ClearDevFontCache() override;
+ virtual void ClearNativeControlCache() override;
+
+ virtual void freeResources() override;
+ virtual void Flush() override;
+
+ static void prepareSkia();
+
+protected:
+ virtual void createWindowSurfaceInternal(bool forceRaster = false) override;
+ static sk_sp<SkTypeface> createDirectWriteTypeface(const WinFontInstance* pWinFont);
+ static void initFontInfo();
+ inline static sal::systools::COMReference<IDWriteFontSetBuilder> dwriteFontSetBuilder;
+ inline static sal::systools::COMReference<IDWriteFontCollection1> dwritePrivateCollection;
+ inline static sk_sp<SkFontMgr> dwriteFontMgr;
+ inline static bool dwriteDone = false;
+ static SkFont::Edging fontEdging;
+};
+
+typedef std::pair<ControlCacheKey, sk_sp<SkImage>> SkiaControlCachePair;
+typedef o3tl::lru_map<ControlCacheKey, sk_sp<SkImage>, ControlCacheHashFunction>
+ SkiaControlCacheType;
+
+class SkiaControlsCache
+{
+ SkiaControlCacheType cache;
+
+ SkiaControlsCache();
+
+public:
+ static SkiaControlCacheType& get();
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/skia/x11/gdiimpl.hxx b/vcl/inc/skia/x11/gdiimpl.hxx
new file mode 100644
index 0000000000..f7198671b4
--- /dev/null
+++ b/vcl/inc/skia/x11/gdiimpl.hxx
@@ -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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_SKIA_X11_GDIIMPL_HXX
+#define INCLUDED_VCL_INC_SKIA_X11_GDIIMPL_HXX
+
+#include <vcl/dllapi.h>
+
+#include <skia/gdiimpl.hxx>
+#include <unx/salgdi.h>
+#include <unx/x11/x11gdiimpl.h>
+
+class VCL_PLUGIN_PUBLIC X11SkiaSalGraphicsImpl final : public SkiaSalGraphicsImpl,
+ public X11GraphicsImpl
+{
+private:
+ X11SalGraphics& mX11Parent;
+
+public:
+ X11SkiaSalGraphicsImpl(X11SalGraphics& rParent);
+
+ virtual void Init() override;
+ virtual void freeResources() override;
+ virtual void Flush() override;
+
+ static void prepareSkia();
+
+private:
+ virtual void createWindowSurfaceInternal(bool forceRaster = false) override;
+ virtual bool avoidRecreateByResize() const override;
+ static std::unique_ptr<sk_app::WindowContext>
+ createWindowContext(Display* display, Drawable drawable, const XVisualInfo* visual, int width,
+ int height, SkiaHelper::RenderMethod renderMethod, bool temporary);
+ friend std::unique_ptr<sk_app::WindowContext> createVulkanWindowContext(bool);
+};
+
+#endif // INCLUDED_VCL_INC_SKIA_X11_GDIIMPL_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/skia/x11/salvd.hxx b/vcl/inc/skia/x11/salvd.hxx
new file mode 100644
index 0000000000..137a58ae67
--- /dev/null
+++ b/vcl/inc/skia/x11/salvd.hxx
@@ -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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_SKIA_X11_SALVD_H
+#define INCLUDED_VCL_INC_SKIA_X11_SALVD_H
+
+#include <salvd.hxx>
+#include <unx/saldisp.hxx>
+#include <unx/salgdi.h>
+
+class X11SkiaSalVirtualDevice final : public SalVirtualDevice
+{
+ SalDisplay* mpDisplay;
+ std::unique_ptr<X11SalGraphics> mpGraphics;
+ bool mbGraphics; // is Graphics used
+ SalX11Screen mnXScreen;
+ int mnWidth;
+ int mnHeight;
+
+public:
+ X11SkiaSalVirtualDevice(const SalGraphics& rGraphics, tools::Long nDX, tools::Long nDY,
+ const SystemGraphicsData* pData,
+ std::unique_ptr<X11SalGraphics> pNewGraphics);
+ virtual ~X11SkiaSalVirtualDevice() override;
+
+ // SalGeometryProvider
+ virtual tools::Long GetWidth() const override { return mnWidth; }
+ virtual tools::Long GetHeight() const override { return mnHeight; }
+
+ SalDisplay* GetDisplay() const { return mpDisplay; }
+ const SalX11Screen& GetXScreenNumber() const { return mnXScreen; }
+
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics(SalGraphics* pGraphics) override;
+
+ // Set new size, without saving the old contents
+ virtual bool SetSize(tools::Long nNewDX, tools::Long nNewDY) override;
+};
+
+#endif // INCLUDED_VCL_INC_SKIA_X11_SALVD_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/skia/x11/textrender.hxx b/vcl/inc/skia/x11/textrender.hxx
new file mode 100644
index 0000000000..623e229e8e
--- /dev/null
+++ b/vcl/inc/skia/x11/textrender.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_INC_SKIA_TEXTRENDER_HXX
+#define INCLUDED_VCL_INC_SKIA_TEXTRENDER_HXX
+
+#include <unx/freetypetextrender.hxx>
+
+#include <SkFontMgr.h>
+
+class VCL_DLLPUBLIC SkiaTextRender final : public FreeTypeTextRenderImpl
+{
+public:
+ virtual void DrawTextLayout(const GenericSalLayout&, const SalGraphics&) override;
+ virtual void ClearDevFontCache() override;
+
+private:
+ static inline sk_sp<SkFontMgr> fontManager;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/skia/zone.hxx b/vcl/inc/skia/zone.hxx
new file mode 100644
index 0000000000..6d503e7eb8
--- /dev/null
+++ b/vcl/inc/skia/zone.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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_SKIA_ZONE_H
+#define INCLUDED_VCL_INC_SKIA_ZONE_H
+
+#include <comphelper/crashzone.hxx>
+
+#include <vcl/dllapi.h>
+
+#include <comphelper/solarmutex.hxx>
+
+// Used around calls to Skia code to detect crashes in drivers.
+class VCL_DLLPUBLIC SkiaZone : public CrashZone<SkiaZone>
+{
+public:
+ SkiaZone() { assert(comphelper::SolarMutex::get()->IsCurrentThread()); }
+ static void hardDisable();
+ static void relaxWatchdogTimings();
+ static const CrashWatchdogTimingsValues& getCrashWatchdogTimingsValues();
+ static void checkDebug(int nUnchanged, const CrashWatchdogTimingsValues& aTimingValues);
+ static const char* name() { return "Skia"; }
+};
+
+#endif // INCLUDED_VCL_INC_SKIA_ZONE_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/slider.hxx b/vcl/inc/slider.hxx
new file mode 100644
index 0000000000..73a0dc0a20
--- /dev/null
+++ b/vcl/inc/slider.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_SLIDER_HXX
+#define INCLUDED_VCL_SLIDER_HXX
+
+#include <vcl/ctrl.hxx>
+
+class Slider final : public Control
+{
+private:
+ tools::Rectangle maChannel1Rect;
+ tools::Rectangle maChannel2Rect;
+ tools::Rectangle maThumbRect;
+ tools::Long mnStartPos;
+ tools::Long mnMouseOff;
+ tools::Long mnThumbPixOffset;
+ tools::Long mnThumbPixRange;
+ tools::Long mnThumbPixPos;
+ tools::Long mnThumbSize;
+ tools::Long mnChannelPixRange;
+ tools::Long mnChannelPixTop;
+ tools::Long mnChannelPixBottom;
+ tools::Long mnMinRange;
+ tools::Long mnMaxRange;
+ tools::Long mnThumbPos;
+ tools::Long mnLineSize;
+ tools::Long mnPageSize;
+ sal_uInt16 mnStateFlags;
+ ScrollType meScrollType;
+ bool mbCalcSize;
+
+ Link<Slider*,void> maSlideHdl;
+
+ using Control::ImplInitSettings;
+ using Window::ImplInit;
+ void ImplInit( vcl::Window* pParent, WinBits nStyle );
+ void ImplInitSettings();
+ void ImplUpdateRects( bool bUpdate = true );
+ tools::Long ImplCalcThumbPos( tools::Long nPixPos ) const;
+ tools::Long ImplCalcThumbPosPix( tools::Long nPos ) const;
+ void ImplCalc( bool bUpdate = true );
+ void ImplDraw(vcl::RenderContext& rRenderContext);
+ bool ImplIsPageUp( const Point& rPos ) const;
+ bool ImplIsPageDown( const Point& rPos ) const;
+ tools::Long ImplSlide( tools::Long nNewPos );
+ tools::Long ImplDoAction( );
+ void ImplDoMouseAction( const Point& rPos, bool bCallAction );
+ void ImplDoSlide( tools::Long nNewPos );
+ void ImplDoSlideAction( ScrollType eScrollType );
+
+public:
+ Slider( vcl::Window* pParent, WinBits nStyle);
+ virtual ~Slider() override;
+ virtual void MouseButtonDown( const MouseEvent& rMEvt ) override;
+ virtual void MouseButtonUp( const MouseEvent& rMEvt ) override;
+ virtual void Tracking( const TrackingEvent& rTEvt ) override;
+ virtual void KeyInput( const KeyEvent& rKEvt ) override;
+ virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override;
+ virtual void Resize() override;
+ virtual void StateChanged( StateChangedType nType ) override;
+ virtual void DataChanged( const DataChangedEvent& rDCEvt ) override;
+
+ void Slide();
+
+ void SetRangeMin(tools::Long nNewRange);
+ tools::Long GetRangeMin() const { return mnMinRange; }
+ void SetRangeMax(tools::Long nNewRange);
+ tools::Long GetRangeMax() const { return mnMaxRange; }
+ void SetRange( const Range& rRange );
+ void SetThumbPos( tools::Long nThumbPos );
+ tools::Long GetThumbPos() const { return mnThumbPos; }
+ void SetLineSize( tools::Long nNewSize ) { mnLineSize = nNewSize; }
+ tools::Long GetLineSize() const { return mnLineSize; }
+ void SetPageSize( tools::Long nNewSize ) { mnPageSize = nNewSize; }
+ tools::Long GetPageSize() const { return mnPageSize; }
+
+ Size CalcWindowSizePixel() const;
+
+ void SetSlideHdl( const Link<Slider*,void>& rLink ) { maSlideHdl = rLink; }
+};
+
+#endif // INCLUDED_VCL_SLIDER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/spin.hxx b/vcl/inc/spin.hxx
new file mode 100644
index 0000000000..7b7fbe11bd
--- /dev/null
+++ b/vcl/inc/spin.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SPIN_HXX
+#define INCLUDED_VCL_INC_SPIN_HXX
+
+#include <vcl/window.hxx>
+
+namespace tools { class Rectangle; }
+
+// Draw Spinners as found in a SpinButton. Some themes like gtk3 will draw +- elements here,
+// so these are only suitable in the context of SpinButtons
+void ImplDrawSpinButton(vcl::RenderContext& rRenderContext, vcl::Window* pWindow,
+ const tools::Rectangle& rUpperRect, const tools::Rectangle& rLowerRect,
+ bool bUpperIn, bool bLowerIn, bool bUpperEnabled = true, bool bLowerEnabled = true,
+ bool bHorz = false, bool bMirrorHorz = false);
+
+// Draw Up/Down buttons suitable for use in any context
+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 = false);
+
+
+#endif // INCLUDED_VCL_INC_SPIN_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/strhelper.hxx b/vcl/inc/strhelper.hxx
new file mode 100644
index 0000000000..7793418334
--- /dev/null
+++ b/vcl/inc/strhelper.hxx
@@ -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 .
+ */
+#ifndef INCLUDED_VCL_STRHELPER_HXX
+#define INCLUDED_VCL_STRHELPER_HXX
+
+#include <rtl/math.hxx>
+#include <rtl/ustring.hxx>
+
+namespace psp
+{
+ OUString GetCommandLineToken( int, const OUString& );
+ OString GetCommandLineToken(int, const OString&);
+ // gets one token of a unix command line style string
+ // doublequote, singlequote and singleleftquote protect their respective
+ // contents
+
+ int GetCommandLineTokenCount(const OUString&);
+ // returns number of tokens (zero if empty or whitespace only)
+
+ OUString WhitespaceToSpace( std::u16string_view, bool bProtect = true );
+ OString WhitespaceToSpace(std::string_view);
+ // returns a string with multiple adjacent occurrences of whitespace
+ // converted to a single space. if bProtect is sal_True (nonzero), then
+ // doublequote, singlequote and singleleftquote protect their respective
+ // contents
+
+
+ // parses the first double in the string; decimal is '.' only
+ inline double StringToDouble( std::u16string_view rStr )
+ {
+ return rtl::math::stringToDouble(rStr, u'.', u'\0');
+ }
+
+} // namespace
+
+#endif // INCLUDED_VCL_STRHELPER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/strings.hrc b/vcl/inc/strings.hrc
new file mode 100644
index 0000000000..6ce4854deb
--- /dev/null
+++ b/vcl/inc/strings.hrc
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_STRINGS_HRC
+#define INCLUDED_VCL_INC_STRINGS_HRC
+
+#define NC_(Context, String) TranslateId(Context, u8##String)
+
+#define SV_RESID_STRING_NOSELECTIONPOSSIBLE NC_("SV_RESID_STRING_NOSELECTIONPOSSIBLE", "[No selection possible]")
+
+#define SV_MENU_MAC_SERVICES NC_("SV_MENU_MAC_SERVICES", "Services")
+#define SV_MENU_MAC_HIDEAPP NC_("SV_MENU_MAC_HIDEAPP", "Hide %PRODUCTNAME")
+#define SV_MENU_MAC_HIDEALL NC_("SV_MENU_MAC_HIDEALL", "Hide Others")
+#define SV_MENU_MAC_SHOWALL NC_("SV_MENU_MAC_SHOWALL", "Show All")
+#define SV_MENU_MAC_QUITAPP NC_("SV_MENU_MAC_QUITAPP", "Quit %PRODUCTNAME")
+
+#define SV_HELPTEXT_CLOSE NC_("SV_HELPTEXT_CLOSE", "Close")
+#define SV_HELPTEXT_MINIMIZE NC_("SV_HELPTEXT_MINIMIZE", "Minimize")
+#define SV_HELPTEXT_MAXIMIZE NC_("SV_HELPTEXT_MAXIMIZE", "Maximize")
+#define SV_HELPTEXT_RESTORE NC_("SV_HELPTEXT_RESTORE", "Restore")
+#define SV_HELPTEXT_HELP NC_("SV_HELPTEXT_HELP", "Help")
+#define SV_HELPTEXT_SCREENSHOT NC_("SV_HELPTEXT_SCREENSHOT", "Take and annotate a screenshot")
+#define SV_HELPTEXT_FADEIN NC_("SV_HELPTEXT_FADEIN", "Show")
+#define SV_HELPTEXT_FADEOUT NC_("SV_HELPTEXT_FADEOUT", "Hide")
+#define SV_HELPTEXT_CLOSEDOCUMENT NC_("SV_HELPTEXT_CLOSEDOCUMENT", "Close Document")
+
+// To translators: This is used on buttons for platforms other than Windows, there should be a ~ mnemonic in this string
+#define SV_BUTTONTEXT_OK NC_("SV_BUTTONTEXT_OK", "~OK")
+// To translators: This is used on buttons for platforms other than windows, there should be a ~ mnemonic in this string
+#define SV_BUTTONTEXT_CANCEL NC_("SV_BUTTONTEXT_CANCEL", "~Cancel")
+// To translators: This is used on buttons for Windows, there should be no ~ mnemonic in this string
+#define SV_BUTTONTEXT_OK_NOMNEMONIC NC_("SV_BUTTONTEXT_OK_NOMNEMONIC", "OK")
+// To translators: This is used on buttons for Windows, there should be no ~ mnemonic in this string
+#define SV_BUTTONTEXT_CANCEL_NOMNEMONIC NC_("SV_BUTTONTEXT_CANCEL_NOMNEMONIC", "Cancel")
+#define SV_BUTTONTEXT_YES NC_("SV_BUTTONTEXT_YES", "~Yes")
+#define SV_BUTTONTEXT_NO NC_("SV_BUTTONTEXT_NO", "~No")
+#define SV_BUTTONTEXT_RETRY NC_("SV_BUTTONTEXT_RETRY", "~Retry")
+#define SV_BUTTONTEXT_HELP NC_("SV_BUTTONTEXT_HELP", "~Help")
+#define SV_BUTTONTEXT_CLOSE NC_("SV_BUTTONTEXT_CLOSE", "~Close")
+#define SV_BUTTONTEXT_MORE NC_("SV_BUTTONTEXT_MORE", "~More")
+#define SV_BUTTONTEXT_IGNORE NC_("SV_BUTTONTEXT_IGNORE", "~Ignore")
+#define SV_BUTTONTEXT_ABORT NC_("SV_BUTTONTEXT_ABORT", "~Abort")
+#define SV_BUTTONTEXT_LESS NC_("SV_BUTTONTEXT_LESS", "~Less")
+#define SV_BUTTONTEXT_SAVE NC_("SV_BUTTONTEXT_SAVE", "~Save")
+#define SV_BUTTONTEXT_OPEN NC_("SV_BUTTONTEXT_OPEN", "~Open")
+#define SV_BUTTONTEXT_SCREENSHOT NC_("SV_BUTTONTEXT_SCREENSHOT", "~Screenshot")
+
+#define SV_STDTEXT_SERVICENOTAVAILABLE NC_("SV_STDTEXT_SERVICENOTAVAILABLE", "The component (%s) could not be loaded.\nPlease start setup with the repair option.")
+
+#define SV_STDTEXT_ABOUT NC_("SV_STDTEXT_ABOUT", "About %PRODUCTNAME")
+#define SV_STDTEXT_PREFERENCES NC_("SV_STDTEXT_PREFERENCES", "Preferences...")
+#define SV_STDTEXT_ALLFILETYPES NC_("SV_STDTEXT_ALLFILETYPES", "Any type")
+
+#define SV_ACCESSERROR_NO_FONTS NC_("SV_ACCESSERROR_NO_FONTS", "No fonts could be found on the system.")
+
+#define SV_PRINT_NOPAGES NC_("SV_PRINT_NOPAGES", "No pages")
+#define SV_PRINT_NOPREVIEW NC_("SV_PRINT_NOPREVIEW", "Preview is disabled")
+#define SV_PRINT_TOFILE_TXT NC_("SV_PRINT_TOFILE_TXT", "Print to File...")
+#define SV_PRINT_DEFPRT_TXT NC_("SV_PRINT_DEFPRT_TXT", "Default printer")
+#define SV_PRINT_QUERYFAXNUMBER_TXT NC_("SV_PRINT_QUERYFAXNUMBER_TXT", "Please enter the fax number")
+#define SV_PRINT_CUSTOM_TXT NC_("SV_PRINT_CUSTOM_TXT", "Custom")
+
+#define SV_EDIT_WARNING_STR NC_("SV_EDIT_WARNING_STR", "The inserted text exceeded the maximum length of this text field. The text was truncated.")
+
+#define SV_APP_CPUTHREADS NC_("SV_APP_CPUTHREADS", "CPU threads: ")
+#define SV_APP_OSVERSION NC_("SV_APP_OSVERSION", "OS: ")
+#define SV_APP_UIRENDER NC_("SV_APP_UIRENDER", "UI render: ")
+#define SV_APP_SKIA_VULKAN NC_("SV_APP_SKIA_VULKAN", "Skia/Vulkan")
+#define SV_APP_SKIA_METAL NC_("SV_APP_SKIA_METAL", "Skia/Metal")
+#define SV_APP_SKIA_RASTER NC_("SV_APP_SKIA_RASTER", "Skia/Raster")
+#define SV_APP_DEFAULT NC_("SV_APP_DEFAULT", "default")
+
+#define SV_MSGBOX_INFO NC_("SV_MSGBOX_INFO", "Information")
+#define SV_MSGBOX_WARNING NC_("SV_MSGBOX_WARNING", "Warning")
+#define SV_MSGBOX_ERROR NC_("SV_MSGBOX_ERROR", "Error")
+#define SV_MSGBOX_QUERY NC_("SV_MSGBOX_QUERY", "Confirmation")
+
+#define STR_TEXTUNDO_DELPARA NC_("STR_TEXTUNDO_DELPARA", "delete line")
+#define STR_TEXTUNDO_CONNECTPARAS NC_("STR_TEXTUNDO_CONNECTPARAS", "delete multiple lines")
+#define STR_TEXTUNDO_SPLITPARA NC_("STR_TEXTUNDO_SPLITPARA", "insert multiple lines")
+#define STR_TEXTUNDO_INSERTCHARS NC_("STR_TEXTUNDO_INSERTCHARS", "insert '$1'")
+#define STR_TEXTUNDO_REMOVECHARS NC_("STR_TEXTUNDO_REMOVECHARS", "delete '$1'")
+
+// descriptions of accessible objects
+#define STR_SVT_ACC_DESC_TABLISTBOX NC_("STR_SVT_ACC_DESC_TABLISTBOX", "Row: %1, Column: %2")
+#define STR_SVT_ACC_EMPTY_FIELD NC_("STR_SVT_ACC_EMPTY_FIELD", "Empty Field")
+
+#define STR_SVT_CALENDAR_DAY NC_("STR_SVT_CALENDAR_DAY", "Day")
+#define STR_SVT_CALENDAR_WEEK NC_("STR_SVT_CALENDAR_WEEK", "Week")
+#define STR_SVT_CALENDAR_TODAY NC_("STR_SVT_CALENDAR_TODAY", "Today")
+
+#define STR_WIZDLG_ROADMAP_TITLE NC_("STR_WIZDLG_ROADMAP_TITLE", "Steps")
+#define STR_WIZDLG_FINISH NC_("STR_WIZDLG_FINISH", "~Finish")
+#define STR_WIZDLG_NEXT NC_("STR_WIZDLG_NEXT", "~Next >")
+#define STR_WIZDLG_PREVIOUS NC_("STR_WIZDLG_PREVIOUS", "< Bac~k")
+
+#define STR_SEPARATOR NC_("STR_SEPARATOR", "Separator")
+
+#define STR_FILEEXT_NONDEFAULT_ASK_TITLE NC_("STR_FILEEXT_NONDEFAULT_ASK_TITLE", "Default file formats not registered")
+#define STR_FILEEXT_NONDEFAULT_ASK_MSG NC_("STR_FILEEXT_NONDEFAULT_ASK_MSG", "The following file formats are not registered to be opened by default in %PRODUCTNAME:\n$1\nSelect OK if you want to change default file format registrations.")
+
+#define KEY_VERSION_CHECK NC_("KEY_VERSION_CHECK", "Warning: Not all of the imported EPS graphics could be saved at level1\nas some are at a higher level!")
+
+#define STR_GBU NC_("STR_GBU", "Graphics Backend used: %1")
+#define STR_PASSED NC_("STR_PASSED", "Passed Tests: %1")
+#define STR_QUIRKY NC_("STR_QUIRKY", "Quirky Tests: %1")
+#define STR_FAILED NC_("STR_FAILED", "Failed Tests: %1")
+#define STR_SKIPPED NC_("STR_SKIPPED", "Skipped Tests: %1")
+
+#define STR_UNSAVED_DOCUMENTS NC_("STR_UNSAVED_DOCUMENTS", "There are unsaved documents")
+
+#define STR_SPECIAL_CHARACTER_MENU_ENTRY NC_("editmenu|specialchar", "_Special Character...")
+
+#endif // INCLUDED_VCL_INC_STRINGS_HRC
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/strings.hxx b/vcl/inc/strings.hxx
new file mode 100644
index 0000000000..45e9b2af43
--- /dev/null
+++ b/vcl/inc/strings.hxx
@@ -0,0 +1,17 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_VCL_INC_STRINGS_HXX
+#define INCLUDED_VCL_INC_STRINGS_HXX
+
+#define SV_APP_VCLBACKEND "VCL: "
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/svdata.hxx b/vcl/inc/svdata.hxx
new file mode 100644
index 0000000000..fd7ae855b5
--- /dev/null
+++ b/vcl/inc/svdata.hxx
@@ -0,0 +1,483 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <o3tl/lru_map.hxx>
+#include <o3tl/hash_combine.hxx>
+#include <tools/fldunit.hxx>
+#include <unotools/options.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/image.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/print.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/window.hxx>
+#include <vcl/task.hxx>
+#include <LibreOfficeKit/LibreOfficeKitTypes.h>
+#include <unotools/resmgr.hxx>
+
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/i18n/XCharacterClassification.hpp>
+#include "vcleventlisteners.hxx"
+#include "print.h"
+#include "salwtype.hxx"
+#include "windowdev.hxx"
+#include "displayconnectiondispatch.hxx"
+
+#include <atomic>
+#include <mutex>
+#include <optional>
+#include <vector>
+#include <unordered_map>
+#include "schedulerimpl.hxx"
+#include <basegfx/DrawCommands.hxx>
+
+struct ImplPostEventData;
+struct ImplTimerData;
+struct ImplIdleData;
+struct ImplConfigData;
+namespace rtl
+{
+ class OStringBuffer;
+}
+namespace vcl::font
+{
+ class DirectFontSubstitution;
+ class PhysicalFontCollection;
+}
+struct ImplHotKey;
+struct ImplEventHook;
+class Point;
+class ImplAccelManager;
+class ImplFontCache;
+class HelpTextWindow;
+class ImplTBDragMgr;
+class ImplIdleMgr;
+class FloatingWindow;
+class AllSettings;
+class NotifyEvent;
+class Timer;
+class AutoTimer;
+class Idle;
+class Help;
+class PopupMenu;
+class Application;
+class OutputDevice;
+class SvFileStream;
+class SystemWindow;
+class WorkWindow;
+class Dialog;
+class VirtualDevice;
+class Printer;
+class SalFrame;
+class SalInstance;
+class SalSystem;
+class ImplPrnQueueList;
+class UnoWrapperBase;
+class GraphicConverter;
+class ImplWheelWindow;
+class SalTimer;
+class DockingManager;
+class VclEventListeners2;
+class SalData;
+class OpenGLContext;
+class UITestLogger;
+
+#define SV_ICON_ID_OFFICE 1
+#define SV_ICON_ID_TEXT 2
+#define SV_ICON_ID_TEXT_TEMPLATE 3
+#define SV_ICON_ID_SPREADSHEET 4
+#define SV_ICON_ID_SPREADSHEET_TEMPLATE 5
+#define SV_ICON_ID_DRAWING 6
+#define SV_ICON_ID_PRESENTATION 8
+#define SV_ICON_ID_MASTER_DOCUMENT 10
+#define SV_ICON_ID_TEMPLATE 11
+#define SV_ICON_ID_DATABASE 12
+#define SV_ICON_ID_FORMULA 13
+
+const FloatWinPopupFlags LISTBOX_FLOATWINPOPUPFLAGS = FloatWinPopupFlags::Down |
+ FloatWinPopupFlags::NoHorzPlacement | FloatWinPopupFlags::AllMouseButtonClose;
+
+namespace com::sun::star::datatransfer::clipboard { class XClipboard; }
+
+namespace vcl
+{
+ class DisplayConnectionDispatch;
+ class SettingsConfigItem;
+ class DeleteOnDeinitBase;
+ class Window;
+}
+
+namespace basegfx
+{
+ class SystemDependentDataManager;
+}
+
+class LocaleConfigurationListener final : public utl::ConfigurationListener
+{
+public:
+ virtual void ConfigurationChanged( utl::ConfigurationBroadcaster*, ConfigurationHints ) override;
+};
+
+typedef std::pair<VclPtr<vcl::Window>, ImplPostEventData *> ImplPostEventPair;
+
+struct ImplSVAppData
+{
+ ImplSVAppData();
+ ~ImplSVAppData();
+
+ std::optional<AllSettings> mxSettings; // Application settings
+ LocaleConfigurationListener* mpCfgListener = nullptr;
+ VclEventListeners maEventListeners; // listeners for vcl events (eg, extended toolkit)
+ std::vector<Link<VclWindowEvent&,bool> >
+ maKeyListeners; // listeners for key events only (eg, extended toolkit)
+ std::vector<ImplPostEventPair> maPostedEventList;
+ ImplAccelManager* mpAccelMgr = nullptr; // Accelerator Manager
+ std::optional<OUString> mxAppName; // Application name
+ std::optional<OUString> mxAppFileName; // Abs. Application FileName
+ std::optional<OUString> mxDisplayName; // Application Display Name
+ std::optional<OUString> mxToolkitName; // Toolkit Name
+ Help* mpHelp = nullptr; // Application help
+ VclPtr<PopupMenu> mpActivePopupMenu; // Actives Popup-Menu (in Execute)
+ VclPtr<ImplWheelWindow> mpWheelWindow; // WheelWindow
+ sal_uInt64 mnLastInputTime = 0; // GetLastInputTime()
+ sal_uInt16 mnDispatchLevel = 0; // DispatchLevel
+ sal_uInt16 mnModalMode = 0; // ModalMode Count
+ SystemWindowFlags mnSysWinMode = SystemWindowFlags(0); // Mode, when SystemWindows should be created
+ bool mbInAppMain = false; // is Application::Main() on stack
+ bool mbInAppExecute = false; // is Application::Execute() on stack
+ std::atomic<bool> mbAppQuit = false; // is Application::Quit() called, volatile because we read/write from different threads
+ bool mbSettingsInit = false; // true: Settings are initialized
+ DialogCancelMode meDialogCancel = DialogCancelMode::Off; // true: All Dialog::Execute() calls will be terminated immediately with return false
+ bool mbRenderToBitmaps = false; // set via svp / headless plugin
+ bool m_bUseSystemLoop = false;
+
+ DECL_STATIC_LINK(ImplSVAppData, ImplQuitMsg, void*, void);
+};
+
+/// Cache multiple scalings for the same bitmap
+struct ScaleCacheKey {
+ SalBitmap *mpBitmap;
+ Size maDestSize;
+ ScaleCacheKey(SalBitmap *pBitmap, const Size &aDestSize)
+ {
+ mpBitmap = pBitmap;
+ maDestSize = aDestSize;
+ }
+ ScaleCacheKey(const ScaleCacheKey &key)
+ {
+ mpBitmap = key.mpBitmap;
+ maDestSize = key.maDestSize;
+ }
+ bool operator==(ScaleCacheKey const& rOther) const
+ {
+ return mpBitmap == rOther.mpBitmap && maDestSize == rOther.maDestSize;
+ }
+};
+
+namespace std
+{
+template <> struct hash<ScaleCacheKey>
+{
+ std::size_t operator()(ScaleCacheKey const& k) const noexcept
+ {
+ std::size_t seed = 0;
+ o3tl::hash_combine(seed, k.mpBitmap);
+ o3tl::hash_combine(seed, k.maDestSize.getWidth());
+ o3tl::hash_combine(seed, k.maDestSize.getHeight());
+ return seed;
+ }
+};
+
+} // end std namespace
+
+typedef o3tl::lru_map<ScaleCacheKey, BitmapEx> lru_scale_cache;
+
+struct ImplSVGDIData
+{
+ ~ImplSVGDIData();
+
+ VclPtr<vcl::WindowOutputDevice> mpFirstWinGraphics; // First OutputDevice with a Frame Graphics
+ VclPtr<vcl::WindowOutputDevice> mpLastWinGraphics; // Last OutputDevice with a Frame Graphics
+ VclPtr<OutputDevice> mpFirstVirGraphics; // First OutputDevice with a VirtualDevice Graphics
+ VclPtr<OutputDevice> mpLastVirGraphics; // Last OutputDevice with a VirtualDevice Graphics
+ VclPtr<Printer> mpFirstPrnGraphics; // First OutputDevice with an InfoPrinter Graphics
+ VclPtr<Printer> mpLastPrnGraphics; // Last OutputDevice with an InfoPrinter Graphics
+ VclPtr<VirtualDevice> mpFirstVirDev; // First VirtualDevice
+ OpenGLContext* mpLastContext = nullptr; // Last OpenGLContext
+ VclPtr<Printer> mpFirstPrinter; // First Printer
+ std::unique_ptr<ImplPrnQueueList> mpPrinterQueueList; // List of all printer queue
+ std::shared_ptr<vcl::font::PhysicalFontCollection> mxScreenFontList; // Screen-Font-List
+ std::shared_ptr<ImplFontCache> mxScreenFontCache; // Screen-Font-Cache
+ lru_scale_cache maScaleCache = lru_scale_cache(10); // Cache for scaled images
+ vcl::font::DirectFontSubstitution* mpDirectFontSubst = nullptr; // Font-Substitutions defined in Tools->Options->Fonts
+ std::unique_ptr<GraphicConverter> mxGrfConverter; // Converter for graphics
+ tools::Long mnAppFontX = 0; // AppFont X-Numenator for 40/tel Width
+ tools::Long mnAppFontY = 0; // AppFont Y-Numenator for 80/tel Height
+ bool mbFontSubChanged = false; // true: FontSubstitution was changed between Begin/End
+
+ o3tl::lru_map<OUString, BitmapEx> maThemeImageCache = o3tl::lru_map<OUString, BitmapEx>(10);
+ o3tl::lru_map<OUString, gfx::DrawRoot> maThemeDrawCommandsCache = o3tl::lru_map<OUString, gfx::DrawRoot>(50);
+};
+
+struct ImplSVFrameData
+{
+ ~ImplSVFrameData();
+ VclPtr<vcl::Window> mpFirstFrame; // First FrameWindow
+ VclPtr<vcl::Window> mpActiveApplicationFrame; // the last active application frame, can be used as DefModalDialogParent if no focuswin set
+ VclPtr<WorkWindow> mpAppWin; // Application-Window
+
+ std::unique_ptr<UITestLogger> m_pUITestLogger;
+};
+
+struct ImplSVWinData
+{
+ ~ImplSVWinData();
+ VclPtr<vcl::Window> mpFocusWin; // window, that has the focus
+ VclPtr<vcl::Window> mpCaptureWin; // window, that has the mouse capture
+ VclPtr<vcl::Window> mpLastDeacWin; // Window, that need a deactivate (FloatingWindow-Handling)
+ VclPtr<FloatingWindow> mpFirstFloat; // First FloatingWindow in PopupMode
+ std::vector<VclPtr<Dialog>> mpExecuteDialogs; ///< Stack of dialogs that are Execute()'d - the last one is the top most one.
+ VclPtr<vcl::Window> mpExtTextInputWin; // Window, which is in ExtTextInput
+ VclPtr<vcl::Window> mpTrackWin; // window, that is in tracking mode
+ std::unique_ptr<AutoTimer> mpTrackTimer; // tracking timer
+ std::vector<Image> maMsgBoxImgList; // ImageList for MessageBox
+ VclPtr<vcl::Window> mpAutoScrollWin; // window, that is in AutoScrollMode mode
+ VclPtr<vcl::Window> mpLastWheelWindow; // window, that last received a mouse wheel event
+ SalWheelMouseEvent maLastWheelEvent; // the last received mouse wheel event
+
+ StartTrackingFlags mnTrackFlags = StartTrackingFlags::NONE; // tracking flags
+ StartAutoScrollFlags mnAutoScrollFlags = StartAutoScrollFlags::NONE; // auto scroll flags
+ bool mbNoDeactivate = false; // true: do not execute Deactivate
+ bool mbNoSaveFocus = false; // true: menus must not save/restore focus
+ bool mbIsLiveResize = false; // true: skip waiting for events and low priority timers
+};
+
+typedef std::vector< std::pair< OUString, FieldUnit > > FieldUnitStringList;
+
+struct ImplSVCtrlData
+{
+ std::vector<Image> maCheckImgList; // ImageList for CheckBoxes
+ std::vector<Image> maRadioImgList; // ImageList for RadioButtons
+ std::optional<Image> moDisclosurePlus;
+ std::optional<Image> moDisclosureMinus;
+ ImplTBDragMgr* mpTBDragMgr = nullptr; // DragMgr for ToolBox
+ sal_uInt16 mnCheckStyle = 0; // CheckBox-Style for ImageList-Update
+ sal_uInt16 mnRadioStyle = 0; // Radio-Style for ImageList-Update
+ Color mnLastCheckFColor; // Last FaceColor for CheckImage
+ Color mnLastCheckWColor; // Last WindowColor for CheckImage
+ Color mnLastCheckLColor; // Last LightColor for CheckImage
+ Color mnLastRadioFColor; // Last FaceColor for RadioImage
+ Color mnLastRadioWColor; // Last WindowColor for RadioImage
+ Color mnLastRadioLColor; // Last LightColor for RadioImage
+ FieldUnitStringList maFieldUnitStrings; // list with field units
+ FieldUnitStringList maCleanUnitStrings; // same list but with some "fluff" like spaces removed
+};
+
+struct ImplSVHelpData
+{
+ ~ImplSVHelpData();
+ bool mbContextHelp = false; // is ContextHelp enabled
+ bool mbExtHelp = false; // is ExtendedHelp enabled
+ bool mbExtHelpMode = false; // is in ExtendedHelp Mode
+ bool mbOldBalloonMode = false; // BalloonMode, before ExtHelpMode started
+ bool mbBalloonHelp = false; // is BalloonHelp enabled
+ bool mbQuickHelp = false; // is QuickHelp enabled
+ bool mbSetKeyboardHelp = false; // tiphelp was activated by keyboard
+ bool mbKeyboardHelp = false; // tiphelp was activated by keyboard
+ bool mbRequestingHelp = false; // In Window::RequestHelp
+ VclPtr<HelpTextWindow> mpHelpWin; // HelpWindow
+ sal_uInt64 mnLastHelpHideTime = 0; // ticks of last show
+};
+
+// "NWF" means "Native Widget Framework" and was the term used for the
+// idea that StarView/OOo "widgets" should *look* (and feel) like the
+// "native widgets" on each platform, even if not at all implemented
+// using them. See http://people.redhat.com/dcbw/ooo-nwf.html .
+
+struct ImplSVNWFData
+{
+ int mnStatusBarLowerRightOffset = 0; // amount in pixel to avoid in the lower righthand corner
+ int mnMenuFormatBorderX = 0; // horizontal inner popup menu border
+ int mnMenuFormatBorderY = 0; // vertical inner popup menu border
+ ::Color maMenuBarHighlightTextColor = COL_TRANSPARENT; // override highlight text color
+ // in menubar if not transparent
+ bool mbMenuBarDockingAreaCommonBG = false; // e.g. WinXP default theme
+ bool mbDockingAreaSeparateTB = false; // individual toolbar backgrounds
+ // instead of one for docking area
+ bool mbDockingAreaAvoidTBFrames = false; ///< don't draw frames around the individual toolbars if mbDockingAreaSeparateTB is false
+ bool mbFlatMenu = false; // no popup 3D border
+ bool mbNoFocusRects = false; // on Aqua/Gtk3 use native focus rendering, except for flat buttons
+ bool mbNoFocusRectsForFlatButtons = false; // on Gtk3 native focusing is also preferred for flat buttons
+ bool mbNoFrameJunctionForPopups = false; // on Gtk4 popups are done via popovers and a toolbar menu won't align to its toolitem, so
+ // omit the effort the creation a visual junction
+ bool mbCenteredTabs = false; // on Aqua, tabs are centered
+ bool mbNoActiveTabTextRaise = false; // on Aqua the text for the selected tab
+ // should not "jump up" a pixel
+ bool mbProgressNeedsErase = false; // set true for platforms that should draw the
+ // window background before drawing the native
+ // progress bar
+ bool mbCanDrawWidgetAnySize = false; // set to true currently on gtk
+
+ /// entire drop down listbox resembles a button, no textarea/button parts (as currently on Windows)
+ bool mbDDListBoxNoTextArea = false;
+ bool mbAutoAccel = false; // whether accelerators are only shown when Alt is held down
+ bool mbRolloverMenubar = false; // theming engine supports rollover in menubar
+ // gnome#768128 I cannot see a route under wayland at present to support
+ // floating toolbars that can be redocked because there's no way to track
+ // that the toolbar is over a dockable area.
+ bool mbCanDetermineWindowPosition = true;
+
+ int mnListBoxEntryMargin = 0;
+};
+
+struct BlendFrameCache
+{
+ Size m_aLastSize;
+ sal_uInt8 m_nLastAlpha;
+ Color m_aLastColorTopLeft;
+ Color m_aLastColorTopRight;
+ Color m_aLastColorBottomRight;
+ Color m_aLastColorBottomLeft;
+ BitmapEx m_aLastResult;
+
+ BlendFrameCache()
+ : m_aLastSize(0, 0)
+ , m_nLastAlpha(0)
+ , m_aLastColorTopLeft(COL_BLACK)
+ , m_aLastColorTopRight(COL_BLACK)
+ , m_aLastColorBottomRight(COL_BLACK)
+ , m_aLastColorBottomLeft(COL_BLACK)
+ {
+ }
+};
+
+struct ImplSchedulerContext
+{
+ ImplSchedulerData* mpFirstSchedulerData[PRIO_COUNT] = { nullptr, }; ///< list of all active tasks per priority
+ ImplSchedulerData* mpLastSchedulerData[PRIO_COUNT] = { nullptr, }; ///< last item of each mpFirstSchedulerData list
+ ImplSchedulerData* mpSchedulerStack = nullptr; ///< stack of invoked tasks
+ ImplSchedulerData* mpSchedulerStackTop = nullptr; ///< top most stack entry to detect needed rescheduling during pop
+ SalTimer* mpSalTimer = nullptr; ///< interface to sal event loop / system timer
+ sal_uInt64 mnTimerStart = 0; ///< start time of the timer
+ sal_uInt64 mnTimerPeriod = SAL_MAX_UINT64; ///< current timer period
+ std::mutex maMutex; ///< the "scheduler mutex" (see
+ ///< vcl/README.scheduler)
+ bool mbActive = true; ///< is the scheduler active?
+};
+
+struct ImplSVData
+{
+ ImplSVData();
+ ~ImplSVData();
+ SalData* mpSalData = nullptr;
+ SalInstance* mpDefInst = nullptr; // Default SalInstance
+ Application* mpApp = nullptr; // pApp
+ VclPtr<WorkWindow> mpDefaultWin; // Default-Window
+ bool mbDeInit = false; // Is VCL deinitializing
+ std::unique_ptr<SalSystem> mpSalSystem; // SalSystem interface
+ bool mbResLocaleSet = false; // SV-Resource-Manager
+ std::locale maResLocale; // Resource locale
+ ImplSchedulerContext maSchedCtx; // Data for class Scheduler
+ ImplSVAppData maAppData; // Data for class Application
+ ImplSVGDIData maGDIData; // Data for Output classes
+ ImplSVFrameData maFrameData; // Data for Frame classes
+ ImplSVWinData* mpWinData = nullptr; // Data for per-view Windows classes
+ ImplSVCtrlData maCtrlData; // Data for Control classes
+ ImplSVHelpData* mpHelpData; // Data for Help classes
+ ImplSVNWFData maNWFData;
+ UnoWrapperBase* mpUnoWrapper = nullptr;
+ VclPtr<vcl::Window> mpIntroWindow; // the splash screen
+ std::unique_ptr<DockingManager> mpDockingManager;
+ std::unique_ptr<BlendFrameCache> mpBlendFrameCache;
+
+ oslThreadIdentifier mnMainThreadId = 0;
+ rtl::Reference< vcl::DisplayConnectionDispatch > mxDisplayConnection;
+
+ css::uno::Reference< css::lang::XComponent > mxAccessBridge;
+ std::unique_ptr<vcl::SettingsConfigItem> mpSettingsConfigItem;
+ std::vector< vcl::DeleteOnDeinitBase* > maDeinitDeleteList;
+ std::unordered_map< int, OUString > maPaperNames;
+
+ css::uno::Reference<css::i18n::XCharacterClassification> m_xCharClass;
+
+#if defined _WIN32
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> m_xSystemClipboard;
+#endif
+
+ Link<LinkParamNone*,void> maDeInitHook;
+
+ // LOK & headless backend specific hooks
+ LibreOfficeKitPollCallback mpPollCallback = nullptr;
+ LibreOfficeKitWakeCallback mpWakeCallback = nullptr;
+ void *mpPollClosure = nullptr;
+
+ void dropCaches();
+ void dumpState(rtl::OStringBuffer &rState);
+};
+
+css::uno::Reference<css::i18n::XCharacterClassification> const& ImplGetCharClass();
+
+void ImplDeInitSVData();
+VCL_PLUGIN_PUBLIC basegfx::SystemDependentDataManager& ImplGetSystemDependentDataManager();
+VCL_PLUGIN_PUBLIC vcl::Window* ImplGetDefaultWindow();
+vcl::Window* ImplGetDefaultContextWindow();
+const std::locale& ImplGetResLocale();
+VCL_PLUGIN_PUBLIC OUString VclResId(TranslateId sContextAndId);
+DockingManager* ImplGetDockingManager();
+BlendFrameCache* ImplGetBlendFrameCache();
+void GenerateAutoMnemonicsOnHierarchy(const vcl::Window* pWindow);
+
+VCL_PLUGIN_PUBLIC ImplSVHelpData& ImplGetSVHelpData();
+
+VCL_DLLPUBLIC bool ImplCallPreNotify( NotifyEvent& rEvt );
+
+VCL_PLUGIN_PUBLIC ImplSVData* ImplGetSVData();
+VCL_PLUGIN_PUBLIC void ImplHideSplash();
+
+#ifdef _WIN32
+bool ImplInitAccessBridge();
+#endif
+
+const FieldUnitStringList& ImplGetFieldUnits();
+const FieldUnitStringList& ImplGetCleanedFieldUnits();
+
+struct ImplSVEvent
+{
+ void* mpData;
+ Link<void*,void> maLink;
+ VclPtr<vcl::Window> mpInstanceRef;
+ VclPtr<vcl::Window> mpWindow;
+ bool mbCall;
+};
+
+extern int nImplSysDialog;
+
+inline SalData* GetSalData() { return ImplGetSVData()->mpSalData; }
+inline void SetSalData(SalData* pData) { ImplGetSVData()->mpSalData = pData; }
+inline SalInstance* GetSalInstance() { return ImplGetSVData()->mpDefInst; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/svimpbox.hxx b/vcl/inc/svimpbox.hxx
new file mode 100644
index 0000000000..2274f2b811
--- /dev/null
+++ b/vcl/inc/svimpbox.hxx
@@ -0,0 +1,388 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_INC_SVIMPBOX_HXX
+#define INCLUDED_VCL_SOURCE_INC_SVIMPBOX_HXX
+
+#include <vcl/seleng.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/image.hxx>
+#include <vcl/svtaccessiblefactory.hxx>
+#include <vcl/vclevent.hxx>
+#include <vcl/toolkit/scrbar.hxx>
+#include <vcl/toolkit/treelistbox.hxx>
+#include <o3tl/enumarray.hxx>
+#include <memory>
+#include <vector>
+
+class SvLBoxButton;
+class SvTreeList;
+class SvImpLBox;
+class SvTreeListEntry;
+namespace comphelper::string { class NaturalStringSorter; }
+
+class ImpLBSelEng final : public FunctionSet
+{
+ SvImpLBox* pImp;
+ VclPtr<SvTreeListBox> pView;
+
+public:
+ ImpLBSelEng( SvImpLBox* pImp, SvTreeListBox* pView );
+ virtual ~ImpLBSelEng() override;
+ void BeginDrag() override;
+ void CreateAnchor() override;
+ void DestroyAnchor() override;
+ void SetCursorAtPoint( const Point& rPoint,
+ bool bDontSelectAtCursor=false ) override;
+ bool IsSelectionAtPoint( const Point& rPoint ) override;
+ void DeselectAtPoint( const Point& rPoint ) override;
+ void DeselectAll() override;
+};
+
+// Flags for nFlag
+enum class LBoxFlags {
+ NONE = 0x0000,
+ DeselectAll = 0x0002,
+ StartEditTimer = 0x0004, // MAC only
+ IgnoreSelect = 0x0008,
+ InResize = 0x0010,
+ RemovedEntryInvisible = 0x0020,
+ RemovedRecalcMostRight = 0x0040,
+ IgnoreChangedTabs = 0x0080,
+ InPaint = 0x0100,
+ EndScrollSetVisSize = 0x0200,
+ Filling = 0x0400,
+};
+namespace o3tl
+{
+ template<> struct typed_flags<LBoxFlags> : is_typed_flags<LBoxFlags, 0x07fe> {};
+}
+
+#define NODE_BMP_TABDIST_NOTVALID -2000000
+#define FIRST_ENTRY_TAB 1
+
+class SvImpLBox
+{
+friend class ImpLBSelEng;
+friend class SvTreeListBox;
+friend class SalInstanceTreeView;
+friend class IconView;
+private:
+ SvTreeList* m_pTree;
+ SvTreeListEntry* m_pAnchor;
+ SvTreeListEntry* m_pMostRightEntry;
+ SvLBoxButton* m_pActiveButton;
+ SvTreeListEntry* m_pActiveEntry;
+ SvLBoxTab* m_pActiveTab;
+
+ VclPtr<ScrollBarBox> m_aScrBarBox;
+
+ ::vcl::AccessibleFactoryAccess
+ m_aFactoryAccess;
+
+ static Image* s_pDefCollapsed;
+ static Image* s_pDefExpanded;
+ static oslInterlockedCount s_nImageRefCount; /// When 0 all static images will be destroyed
+
+ // Node Bitmaps
+ enum class ImageType
+ {
+ NodeExpanded = 0, // node is expanded ( usually a bitmap showing a minus )
+ NodeCollapsed, // node is collapsed ( usually a bitmap showing a plus )
+ EntryDefExpanded, // default for expanded entries
+ EntryDefCollapsed, // default for collapsed entries
+ LAST = EntryDefCollapsed
+ };
+
+ // all our images
+ o3tl::enumarray<ImageType, Image>
+ m_aNodeAndEntryImages;
+
+ ImpLBSelEng m_aFctSet;
+
+ tools::Long m_nNodeBmpWidth;
+ tools::Long m_nMostRight;
+ short m_nHorSBarHeight, m_nVerSBarWidth;
+
+ bool m_bUpdateMode : 1;
+ bool m_bSubLstOpLR : 1; // open/close sublist with cursor left/right, defaulted with false
+ bool mbForceMakeVisible;
+
+ Point m_aEditClickPos;
+ Idle m_aEditIdle;
+
+ std::unique_ptr<comphelper::string::NaturalStringSorter> m_pStringSorter;
+
+ std::vector< short > m_aContextBmpWidthVector;
+
+ DECL_LINK(EditTimerCall, Timer *, void);
+
+ void InvalidateEntriesFrom( tools::Long nY ) const;
+ bool IsLineVisible( tools::Long nY ) const;
+ void KeyLeftRight( tools::Long nDiff );
+
+ void DrawNet(vcl::RenderContext& rRenderContext);
+
+ // ScrollBar-Handler
+ DECL_LINK( ScrollUpDownHdl, ScrollBar*, void );
+ DECL_LINK( ScrollLeftRightHdl, ScrollBar*, void );
+ DECL_LINK( EndScrollHdl, ScrollBar*, void );
+
+ void SetNodeBmpWidth( const Image& );
+ void SetNodeBmpTabDistance();
+
+ // Selection-Engine
+ SvTreeListEntry* MakePointVisible( const Point& rPoint );
+
+ void SetAnchorSelection( SvTreeListEntry* pOld,
+ SvTreeListEntry* pNewCursor );
+ void BeginDrag();
+ bool ButtonDownCheckCtrl( const MouseEvent& rMEvt, SvTreeListEntry* pEntry );
+ bool MouseMoveCheckCtrl( const MouseEvent& rMEvt, SvTreeListEntry const * pEntry );
+ bool ButtonUpCheckCtrl( const MouseEvent& rMEvt );
+ bool ButtonDownCheckExpand( const MouseEvent&, SvTreeListEntry* );
+
+ bool EntryReallyHit(SvTreeListEntry* pEntry, const Point& rPos, tools::Long nLine);
+ void InitScrollBarBox();
+ SvLBoxTab* NextTab( SvLBoxTab const * );
+
+ void SetMostRight( SvTreeListEntry* pEntry );
+ void FindMostRight( SvTreeListEntry* pParent );
+ void FindMostRight_Impl( SvTreeListEntry* pParent );
+ void NotifyTabsChanged();
+
+ // if element at cursor can be expanded in general
+ bool IsExpandable() const;
+
+ static void implInitDefaultNodeImages();
+
+ void UpdateStringSorter();
+
+ short UpdateContextBmpWidthVector( SvTreeListEntry const * pEntry, short nWidth );
+ void UpdateContextBmpWidthMax( SvTreeListEntry const * pEntry );
+ void UpdateContextBmpWidthVectorFromMovedEntry( SvTreeListEntry* pEntry );
+
+ void ExpandAll();
+ void CollapseTo(SvTreeListEntry* pParentToCollapse);
+
+protected:
+ VclPtr<SvTreeListBox> m_pView;
+ VclPtr<ScrollBar> m_aHorSBar;
+ VclPtr<ScrollBar> m_aVerSBar;
+ SvTreeListEntry* m_pCursor;
+ SvTreeListEntry* m_pCursorOld;
+ SvTreeListEntry* m_pStartEntry;
+ ImplSVEvent* m_nCurUserEvent;
+ Size m_aOutputSize;
+ LBoxFlags m_nFlags;
+ WinBits m_nStyle;
+ bool mbNoAutoCurEntry; // disable the behavior of automatically selecting a "CurEntry" upon painting the control
+ SelectionEngine m_aSelEng;
+ sal_uLong m_nVisibleCount; // Number of lines in control
+ bool m_bInVScrollHdl : 1;
+ bool m_bSimpleTravel : 1; // is true if SelectionMode::Single
+ tools::Long m_nNextVerVisSize;
+ tools::Long m_nNodeBmpTabDistance; // typical smaller than 0
+
+ virtual tools::Long GetEntryLine(const SvTreeListEntry* pEntry) const;
+ virtual void CursorDown();
+ virtual void CursorUp();
+ virtual void PageDown( sal_uInt16 nDelta );
+ virtual void PageUp( sal_uInt16 nDelta );
+ // set Thumb to FirstEntryToDraw
+ virtual void SyncVerThumb();
+ virtual void AdjustScrollBars( Size& rSize );
+ virtual void InvalidateEntry( tools::Long nY ) const;
+
+ tools::Rectangle GetVisibleArea() const;
+ void SetCursor( SvTreeListEntry* pEntry, bool bForceNoSelect = false );
+ void PositionScrollBars( Size& rOSize, sal_uInt16 nMask );
+ void FindMostRight();
+ void FillView();
+ void ShowVerSBar();
+ void StopUserEvent();
+
+ DECL_LINK( MyUserEvent, void*, void);
+
+public:
+ SvImpLBox( SvTreeListBox* pView, SvTreeList*, WinBits nWinStyle );
+ virtual ~SvImpLBox();
+
+ void Clear();
+ void SetStyle( WinBits i_nWinStyle );
+ void SetNoAutoCurEntry( bool b );
+ void SetModel( SvTreeList* pModel ) { m_pTree = pModel;}
+
+ void EntryInserted( SvTreeListEntry*);
+ void RemovingEntry( SvTreeListEntry* pEntry );
+ void EntryRemoved();
+ void MovingEntry( SvTreeListEntry* pEntry );
+ void EntryMoved( SvTreeListEntry* pEntry );
+ void TreeInserted( SvTreeListEntry* pEntry );
+
+ void EntryExpanded( SvTreeListEntry* pEntry );
+ void EntryCollapsed( SvTreeListEntry* pEntry );
+ void CollapsingEntry( SvTreeListEntry* pEntry );
+ void EntrySelected( SvTreeListEntry* pEntry, bool bSelect );
+
+ virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect);
+ void MouseButtonDown( const MouseEvent& );
+ void MouseButtonUp( const MouseEvent& );
+ void MouseMove( const MouseEvent&);
+ virtual bool KeyInput( const KeyEvent& );
+ void Resize();
+ void GetFocus();
+ void LoseFocus();
+ virtual void UpdateAll();
+ void SetEntryHeight();
+ void InvalidateEntry( SvTreeListEntry* );
+ void RecalcFocusRect();
+
+ void SelectEntry( SvTreeListEntry* pEntry, bool bSelect );
+ void SetDragDropMode( DragDropMode eDDMode );
+ void SetSelectionMode( SelectionMode eSelMode );
+
+ virtual bool IsEntryInView( SvTreeListEntry* pEntry ) const;
+ virtual SvTreeListEntry* GetEntry( const Point& rPos ) const;
+ // returns last entry, if Pos below last entry
+ virtual SvTreeListEntry* GetClickedEntry( const Point& ) const;
+ SvTreeListEntry* GetCurEntry() const { return m_pCursor; }
+ void SetCurEntry( SvTreeListEntry* );
+ virtual Point GetEntryPosition(const SvTreeListEntry*) const;
+ void MakeVisible( SvTreeListEntry* pEntry, bool bMoveToTop = false );
+ void ScrollToAbsPos( tools::Long nPos );
+
+ void PaintDDCursor(SvTreeListEntry* pEntry, bool bShow);
+
+ // Images
+ inline Image& implGetImageLocation( const ImageType _eType );
+
+ inline void SetExpandedNodeBmp( const Image& _rImg );
+ inline void SetCollapsedNodeBmp( const Image& _rImg );
+
+ inline const Image& GetExpandedNodeBmp( );
+ inline const Image& GetCollapsedNodeBmp( );
+
+ inline void SetDefaultEntryExpBmp( const Image& _rImg );
+ inline void SetDefaultEntryColBmp( const Image& _rImg );
+ inline const Image& GetDefaultEntryExpBmp( );
+ inline const Image& GetDefaultEntryColBmp( );
+
+ static const Image& GetDefaultExpandedNodeImage( );
+ static const Image& GetDefaultCollapsedNodeImage( );
+
+ const Size& GetOutputSize() const { return m_aOutputSize;}
+ virtual void KeyUp( bool bPageUp );
+ virtual void KeyDown( bool bPageDown );
+ void Command( const CommandEvent& rCEvt );
+
+ void Invalidate();
+ void DestroyAnchor() { m_pAnchor=nullptr; m_aSelEng.Reset(); }
+ void SelAllDestrAnch( bool bSelect, bool bDestroyAnchor = true, bool bSingleSelToo = false );
+ void ShowCursor( bool bShow );
+
+ bool RequestHelp( const HelpEvent& rHEvt );
+ bool IsNodeButton( const Point& rPosPixel, const SvTreeListEntry* pEntry ) const;
+ void SetUpdateMode( bool bMode );
+ bool GetUpdateMode() const { return m_bUpdateMode; }
+ tools::Rectangle GetClipRegionRect() const;
+ bool HasHorScrollBar() const { return m_aHorSBar->IsVisible(); }
+ void CallEventListeners( VclEventId nEvent, void* pData = nullptr );
+
+ bool IsSelectable( const SvTreeListEntry* pEntry ) const;
+ void SetForceMakeVisible(bool bEnable) { mbForceMakeVisible = bEnable; }
+
+ // tdf#143114 allow to ask if CaptureOnButton is active
+ // (MouseButtonDown hit on SvLBoxButton, CaptureMouse() active)
+ bool IsCaptureOnButtonActive() const;
+};
+
+inline bool SvImpLBox::IsCaptureOnButtonActive() const
+{
+ return nullptr != m_pActiveButton && nullptr != m_pActiveEntry;
+}
+
+inline Image& SvImpLBox::implGetImageLocation( const ImageType _eType )
+{
+ return m_aNodeAndEntryImages[_eType];
+}
+
+inline void SvImpLBox::SetExpandedNodeBmp( const Image& rImg )
+{
+ implGetImageLocation( ImageType::NodeExpanded ) = rImg;
+ SetNodeBmpWidth( rImg );
+}
+
+inline void SvImpLBox::SetCollapsedNodeBmp( const Image& rImg )
+{
+ implGetImageLocation( ImageType::NodeCollapsed ) = rImg;
+ SetNodeBmpWidth( rImg );
+}
+
+inline const Image& SvImpLBox::GetExpandedNodeBmp( )
+{
+ return implGetImageLocation( ImageType::NodeExpanded );
+}
+
+inline const Image& SvImpLBox::GetCollapsedNodeBmp( )
+{
+ return implGetImageLocation( ImageType::NodeCollapsed );
+}
+
+inline void SvImpLBox::SetDefaultEntryExpBmp( const Image& _rImg )
+{
+ implGetImageLocation( ImageType::EntryDefExpanded ) = _rImg;
+}
+
+inline void SvImpLBox::SetDefaultEntryColBmp( const Image& _rImg )
+{
+ implGetImageLocation( ImageType::EntryDefCollapsed ) = _rImg;
+}
+
+inline const Image& SvImpLBox::GetDefaultEntryExpBmp( )
+{
+ return implGetImageLocation( ImageType::EntryDefExpanded );
+}
+
+inline const Image& SvImpLBox::GetDefaultEntryColBmp( )
+{
+ return implGetImageLocation( ImageType::EntryDefCollapsed );
+}
+
+inline Point SvImpLBox::GetEntryPosition(const SvTreeListEntry* pEntry) const
+{
+ return Point(0, GetEntryLine(pEntry));
+}
+
+inline bool SvImpLBox::IsLineVisible( tools::Long nY ) const
+{
+ bool bRet = true;
+ if ( nY < 0 || nY >= m_aOutputSize.Height() )
+ bRet = false;
+ return bRet;
+}
+
+inline void SvImpLBox::TreeInserted( SvTreeListEntry* pInsTree )
+{
+ EntryInserted( pInsTree );
+}
+
+#endif // INCLUDED_VCL_SOURCE_INC_SVIMPBOX_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/svsys.h b/vcl/inc/svsys.h
new file mode 100644
index 0000000000..3277c18cf1
--- /dev/null
+++ b/vcl/inc/svsys.h
@@ -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_INC_SVSYS_H
+#define INCLUDED_VCL_INC_SVSYS_H
+
+#include <config_features.h>
+
+#ifdef _WIN32
+#include "win/svsys.h"
+#elif defined MACOSX
+#include "osx/svsys.h"
+#elif defined IOS
+#elif defined ANDROID
+#elif defined HAIKU
+#elif !HAVE_FEATURE_UI
+#else
+#include "unx/svsys.h"
+#endif
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/test/GraphicsRenderTests.hxx b/vcl/inc/test/GraphicsRenderTests.hxx
new file mode 100644
index 0000000000..cdf835f48e
--- /dev/null
+++ b/vcl/inc/test/GraphicsRenderTests.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/.
+ *
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+
+namespace vcl::test
+{
+// Set and get currently running graphic render test. Some of them may need
+// special handling in the backend code, just like unittests do.
+void setActiveGraphicsRenderTest(const OUString& name);
+const OUString& activeGraphicsRenderTest();
+
+} // end namespace vcl::test
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/test/outputdevice.hxx b/vcl/inc/test/outputdevice.hxx
new file mode 100644
index 0000000000..8412dbaf8a
--- /dev/null
+++ b/vcl/inc/test/outputdevice.hxx
@@ -0,0 +1,316 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_OUTDEVTESTS_HXX
+#define INCLUDED_VCL_OUTDEVTESTS_HXX
+
+#include <vcl/virdev.hxx>
+#include <vcl/test/TestResult.hxx>
+
+namespace vcl::test {
+
+/** Common subclass for output device rendering tests.
+ */
+class VCL_DLLPUBLIC OutputDeviceTestCommon
+{
+protected:
+
+ ScopedVclPtr<VirtualDevice> mpVirtualDevice;
+ tools::Rectangle maVDRectangle;
+
+ static const Color constBackgroundColor;
+ static const Color constLineColor;
+ static const Color constFillColor;
+
+public:
+ OutputDeviceTestCommon();
+
+ OUString getRenderBackendName() const;
+
+ void initialSetup(tools::Long nWidth, tools::Long nHeight, Color aColor, bool bEnableAA = false, bool bAlphaVirtualDevice = false);
+
+ static TestResult checkRectangle(Bitmap& rBitmap);
+ static TestResult checkRectangleAA(Bitmap& rBitmap);
+ static TestResult checkFilledRectangle(Bitmap& rBitmap, bool useLineColor);
+ static TestResult checkLines(Bitmap& rBitmap);
+ static TestResult checkAALines(Bitmap& rBitmap);
+ static TestResult checkDiamond(Bitmap& rBitmap);
+
+ static TestResult checkInvertRectangle(Bitmap& rBitmap);
+ static TestResult checkInvertN50Rectangle(Bitmap& aBitmap);
+ static TestResult checkInvertTrackFrameRectangle(Bitmap& aBitmap);
+
+ static TestResult checkRectangles(Bitmap& rBitmap, std::vector<Color>& aExpectedColors);
+ static TestResult checkRectangle(Bitmap& rBitmap, int aLayerNumber, Color aExpectedColor);
+ static TestResult checkRectangles(Bitmap& rBitmap, bool aEnableAA = false);
+
+ static TestResult checkFilled(Bitmap& rBitmap, tools::Rectangle aRectangle, Color aExpectedColor);
+ static TestResult checkChecker(Bitmap& rBitmap, sal_Int32 nStartX, sal_Int32 nEndX,
+ sal_Int32 nStartY, sal_Int32 nEndY, std::vector<Color> const & rExpected);
+
+ static TestResult checkLinearGradient(Bitmap& bitmap);
+ static TestResult checkLinearGradientAngled(Bitmap& bitmap);
+ static TestResult checkLinearGradientBorder(Bitmap& bitmap);
+ static TestResult checkLinearGradientIntensity(Bitmap& bitmap);
+ static TestResult checkLinearGradientSteps(Bitmap& bitmap);
+ static TestResult checkAxialGradient(Bitmap& bitmap);
+ static TestResult checkRadialGradient(Bitmap& bitmap);
+ static TestResult checkRadialGradientOfs(Bitmap& bitmap);
+
+ static void createDiamondPoints(tools::Rectangle rRect, int nOffset,
+ Point& rPoint1, Point& rPoint2,
+ Point& rPoint3, Point& rPoint4);
+
+ static tools::Polygon createDropShapePolygon();
+ static basegfx::B2DPolygon createHalfEllipsePolygon();
+ static tools::Polygon createClosedBezierLoop(const tools::Rectangle& rRect);
+ static basegfx::B2DPolygon createOpenPolygon(const tools::Rectangle& rRect, int nOffset = 4);
+ static basegfx::B2DPolygon createOpenBezier();
+
+ static void createHorizontalVerticalDiagonalLinePoints(tools::Rectangle rRect,
+ Point& rHorizontalLinePoint1, Point& rHorizontalLinePoint2,
+ Point& rVerticalLinePoint1, Point& rVerticalLinePoint2,
+ Point& rDiagonalLinePoint1, Point& rDiagonalLinePoint2);
+ // tools
+ static tools::Rectangle alignToCenter(tools::Rectangle aRect1, tools::Rectangle aRect2);
+
+ static TestResult checkBezier(Bitmap& rBitmap);
+
+ static TestResult checkLineCapRound(Bitmap& rBitmap) { return checkLineCap(rBitmap, css::drawing::LineCap_ROUND); }
+ static TestResult checkLineCapSquare(Bitmap& rBitmap) { return checkLineCap(rBitmap, css::drawing::LineCap_SQUARE); }
+ static TestResult checkLineCapButt(Bitmap& rBitmap) { return checkLineCap(rBitmap, css::drawing::LineCap_BUTT); }
+
+ static TestResult checkLineJoinBevel(Bitmap& rBitmap) { return checkLineJoin(rBitmap, basegfx::B2DLineJoin::Bevel); }
+ static TestResult checkLineJoinRound(Bitmap& rBitmap) { return checkLineJoin(rBitmap, basegfx::B2DLineJoin::Round); }
+ static TestResult checkLineJoinMiter(Bitmap& rBitmap) { return checkLineJoin(rBitmap, basegfx::B2DLineJoin::Miter); }
+ static TestResult checkLineJoinNone(Bitmap& rBitmap) { return checkLineJoin(rBitmap, basegfx::B2DLineJoin::NONE); }
+ static TestResult checkDropShape(Bitmap& rBitmap, bool aEnableAA = false);
+ static TestResult checkHalfEllipse(Bitmap& rBitmap, bool aEnableAA = false);
+ static TestResult checkClosedBezier(Bitmap& rBitmap);
+ static TestResult checkFilledAsymmetricalDropShape(Bitmap& rBitmap);
+ static TestResult checkTextLocation(Bitmap& rBitmap);
+ static TestResult checkEvenOddRuleInIntersectingRecs(Bitmap &rBitmap);
+ static TestResult checkIntersectingRecs(Bitmap& rBitmap,int aLayerNumber, Color aExpectedColor);
+ static TestResult checkOpenPolygon(Bitmap& rBitmap, bool aEnableAA = false);
+ static TestResult checkOpenBezier(Bitmap& rBitmap);
+private:
+ static TestResult checkLineCap(Bitmap& rBitmap, css::drawing::LineCap lineCap);
+ static TestResult checkLineJoin(Bitmap& rBitmap, basegfx::B2DLineJoin lineJoin);
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestBitmap : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestBitmap() = default;
+
+ Bitmap setupDrawTransformedBitmap(vcl::PixelFormat aBitmapFormat,
+ bool isBitmapGreyScale = false);
+ Bitmap setupComplexDrawTransformedBitmap(vcl::PixelFormat aBitmapFormat,
+ bool isBitmapGreyScale = false);
+ Bitmap setupDrawBitmap(vcl::PixelFormat aBitmapFormat, bool isBitmapGreyScale = false);
+ Bitmap setupDrawBitmapExWithAlpha(vcl::PixelFormat aBitmapFormat);
+ Bitmap setupDrawMask(vcl::PixelFormat aBitmapFormat);
+ BitmapEx setupDrawBlend(vcl::PixelFormat aBitmapFormat);
+
+ static TestResult checkTransformedBitmap(Bitmap& rBitmap);
+ static TestResult checkComplexTransformedBitmap(Bitmap& rBitmap);
+ static TestResult checkBitmapExWithAlpha(Bitmap& rBitmap);
+ static TestResult checkMask(Bitmap& rBitmap);
+ static TestResult checkBlend(const BitmapEx& rBitmap);
+
+ static TestResult checkTransformedBitmap8bppGreyScale(Bitmap& rBitmap);
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestAnotherOutDev : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestAnotherOutDev() = default;
+
+ Bitmap setupDrawOutDev();
+ Bitmap setupDrawOutDevScaledClipped();
+ Bitmap setupDrawOutDevSelf();
+ Bitmap setupXOR();
+
+ static TestResult checkDrawOutDev(Bitmap& rBitmap);
+ static TestResult checkDrawOutDevScaledClipped(Bitmap& rBitmap);
+ static TestResult checkDrawOutDevSelf(Bitmap& rBitmap);
+ static TestResult checkXOR(Bitmap& rBitmap);
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestPixel : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestPixel() = default;
+
+ Bitmap setupRectangle(bool bEnableAA);
+ Bitmap setupRectangleOnSize1028();
+ Bitmap setupRectangleOnSize4096();
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestLine : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestLine() = default;
+
+ Bitmap setupRectangle(bool bEnableAA);
+ Bitmap setupRectangleOnSize1028();
+ Bitmap setupRectangleOnSize4096();
+ Bitmap setupDiamond();
+ Bitmap setupLines();
+ Bitmap setupAALines();
+
+ Bitmap setupDashedLine();
+ static TestResult checkDashedLine(Bitmap& rBitmap);
+
+ Bitmap setupLineCapRound() { return setupLineCap(css::drawing::LineCap_ROUND); }
+ Bitmap setupLineCapSquare() { return setupLineCap(css::drawing::LineCap_SQUARE); }
+ Bitmap setupLineCapButt() { return setupLineCap(css::drawing::LineCap_BUTT); }
+
+ Bitmap setupLineJoinBevel() { return setupLineJoin(basegfx::B2DLineJoin::Bevel); }
+ Bitmap setupLineJoinRound() { return setupLineJoin(basegfx::B2DLineJoin::Round); }
+ Bitmap setupLineJoinMiter() { return setupLineJoin(basegfx::B2DLineJoin::Miter); }
+ Bitmap setupLineJoinNone() { return setupLineJoin(basegfx::B2DLineJoin::NONE); }
+private:
+ Bitmap setupLineCap( css::drawing::LineCap lineCap );
+ Bitmap setupLineJoin( basegfx::B2DLineJoin lineJoin );
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestPolyLine : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestPolyLine() = default;
+
+ Bitmap setupRectangle(bool bEnableAA);
+ Bitmap setupDiamond();
+ Bitmap setupLines();
+ Bitmap setupAALines();
+ Bitmap setupDropShape();
+ Bitmap setupAADropShape();
+ Bitmap setupHalfEllipse(bool aEnableAA = false);
+ Bitmap setupClosedBezier();
+ Bitmap setupRectangleOnSize1028();
+ Bitmap setupRectangleOnSize4096();
+ Bitmap setupOpenPolygon();
+ Bitmap setupOpenBezier();
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestPolyLineB2D : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestPolyLineB2D() = default;
+
+ Bitmap setupRectangle(bool bEnableAA);
+ Bitmap setupDiamond();
+ Bitmap setupBezier();
+ Bitmap setupAABezier();
+ Bitmap setupHalfEllipse(bool aEnableAA = false);
+ Bitmap setupRectangleOnSize1028();
+ Bitmap setupRectangleOnSize4096();
+ Bitmap setupOpenPolygon();
+ Bitmap setupOpenBezier();
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestRect : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestRect() = default;
+
+ Bitmap setupRectangle(bool bEnableAA);
+ Bitmap setupFilledRectangle(bool useLineColor);
+ Bitmap setupRectangleOnSize1028();
+ Bitmap setupRectangleOnSize4096();
+ Bitmap setupInvert_NONE();
+ Bitmap setupInvert_N50();
+ Bitmap setupInvert_TrackFrame();
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestPolygon : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestPolygon() = default;
+
+ Bitmap setupRectangle(bool bEnableAA);
+ Bitmap setupFilledRectangle(bool useLineColor);
+ Bitmap setupDiamond();
+ Bitmap setupLines();
+ Bitmap setupAALines();
+ Bitmap setupDropShape();
+ Bitmap setupAADropShape();
+ Bitmap setupHalfEllipse(bool aEnableAA = false);
+ Bitmap setupClosedBezier();
+ Bitmap setupFilledAsymmetricalDropShape();
+ Bitmap setupRectangleOnSize1028();
+ Bitmap setupRectangleOnSize4096();
+ Bitmap setupOpenPolygon();
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestPolyPolygon : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestPolyPolygon() = default;
+
+ Bitmap setupRectangle(bool bEnableAA);
+ Bitmap setupFilledRectangle(bool useLineColor);
+ Bitmap setupIntersectingRectangles();
+ Bitmap setupRectangleOnSize1028();
+ Bitmap setupRectangleOnSize4096();
+ Bitmap setupOpenPolygon();
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestPolyPolygonB2D : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestPolyPolygonB2D() = default;
+
+ Bitmap setupRectangle(bool bEnableAA);
+ Bitmap setupFilledRectangle(bool useLineColor);
+ Bitmap setupIntersectingRectangles();
+ Bitmap setupRectangleOnSize1028();
+ Bitmap setupRectangleOnSize4096();
+ Bitmap setupOpenPolygon();
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestGradient : public OutputDeviceTestCommon
+{
+public:
+ OutputDeviceTestGradient() = default;
+
+ Bitmap setupLinearGradient();
+ Bitmap setupLinearGradientAngled();
+ Bitmap setupLinearGradientBorder();
+ Bitmap setupLinearGradientIntensity();
+ Bitmap setupLinearGradientSteps();
+ Bitmap setupAxialGradient();
+ Bitmap setupRadialGradient();
+ Bitmap setupRadialGradientOfs();
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestClip : public OutputDeviceTestCommon
+{
+public:
+ Bitmap setupClipRectangle();
+ Bitmap setupClipPolygon();
+ Bitmap setupClipPolyPolygon();
+ Bitmap setupClipB2DPolyPolygon();
+
+ static TestResult checkClip(Bitmap& rBitmap);
+};
+
+class VCL_DLLPUBLIC OutputDeviceTestText : public OutputDeviceTestCommon
+{
+public:
+ Bitmap setupTextBitmap();
+};
+
+} // end namespace vcl::test
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/textlayout.hxx b/vcl/inc/textlayout.hxx
new file mode 100644
index 0000000000..53462d0cc1
--- /dev/null
+++ b/vcl/inc/textlayout.hxx
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/long.hxx>
+#include <vcl/outdev.hxx>
+
+#include <memory>
+#include <vector>
+
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <com/sun/star/linguistic2/LinguServiceManager.hpp>
+
+class Control;
+
+namespace vcl
+{
+ class SAL_NO_VTABLE ITextLayout
+ {
+ public:
+ virtual tools::Long GetTextWidth( const OUString& _rText, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const = 0;
+ virtual void DrawText( const Point& _rStartPoint, const OUString& _rText, sal_Int32 _nStartIndex, sal_Int32 _nLength,
+ std::vector< tools::Rectangle >* _pVector, OUString* _pDisplayText ) = 0;
+ virtual tools::Long GetTextArray( const OUString& _rText, KernArray* _pDXArray, sal_Int32 _nStartIndex, sal_Int32 _nLength, bool bCaret = false ) const = 0;
+ virtual sal_Int32 GetTextBreak( const OUString& _rText, tools::Long _nMaxTextWidth, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const = 0;
+ virtual bool DecomposeTextRectAction() const = 0;
+
+ protected:
+ ~ITextLayout() COVERITY_NOEXCEPT_FALSE {}
+ };
+
+ class TextLayoutCommon : public ITextLayout
+ {
+ public:
+ OUString GetEllipsisString(OUString const& rOrigStr, tools::Long nMaxWidth, DrawTextFlags nStyle);
+
+ sal_Int32 BreakLinesWithIterator(const tools::Long nWidth, OUString const& rStr,
+ css::uno::Reference< css::linguistic2::XHyphenator > const& xHyph,
+ css::uno::Reference<css::i18n::XBreakIterator> const& xBI,
+ const bool bHyphenate,
+ const sal_Int32 nPos, sal_Int32 nBreakPos);
+
+ sal_Int32 BreakLinesSimple(const tools::Long nWidth, OUString const& rStr,
+ const sal_Int32 nPos, sal_Int32 nBreakPos, tools::Long& nLineWidth);
+
+ tools::Long GetTextLines(tools::Rectangle const& rRect, const tools::Long nTextHeight,
+ ImplMultiTextLineInfo& rLineInfo,
+ tools::Long nWidth, OUString const& rStr,
+ DrawTextFlags nStyle);
+
+ private:
+ OUString GetCenterEllipsisString(OUString const& rOrigStr, sal_Int32 nIndex, tools::Long nMaxWidth);
+ OUString GetEndEllipsisString(OUString const& rOrigStr, sal_Int32 nIndex, tools::Long nMaxWidth, bool bClipText);
+ OUString GetNewsEllipsisString(OUString const& rOrigStr, tools::Long nMaxWidth, DrawTextFlags nStyle);
+ };
+
+ /** is an implementation of the ITextLayout interface which simply delegates its calls to the respective
+ methods of an OutputDevice instance, without any inbetween magic.
+ */
+ class DefaultTextLayout final : public TextLayoutCommon
+ {
+ public:
+ DefaultTextLayout( OutputDevice& _rTargetDevice )
+ : m_rTargetDevice( _rTargetDevice )
+ {
+ }
+ virtual ~DefaultTextLayout();
+
+ // ITextLayout overridables
+ virtual tools::Long GetTextWidth( const OUString& _rText,
+ sal_Int32 _nStartIndex,
+ sal_Int32 _nLength ) 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 tools::Long GetTextArray( const OUString& _rText,
+ KernArray* _pDXArray,
+ sal_Int32 _nStartIndex,
+ sal_Int32 _nLength,
+ bool bCaret = false ) 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;
+
+ private:
+ OutputDevice& m_rTargetDevice;
+ };
+
+ class ReferenceDeviceTextLayout;
+ /** a class which allows rendering text of a Control onto a device, by taking into account the metrics of
+ a reference device.
+ */
+ class ControlTextRenderer final
+ {
+ public:
+ ControlTextRenderer( const Control& _rControl, OutputDevice& _rTargetDevice, OutputDevice& _rReferenceDevice );
+ ~ControlTextRenderer();
+
+ 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:
+ ControlTextRenderer( const ControlTextRenderer& ) = delete;
+ ControlTextRenderer& operator=( const ControlTextRenderer& ) = delete;
+
+ private:
+ ::std::unique_ptr< ReferenceDeviceTextLayout > m_pImpl;
+ };
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/textlineinfo.hxx b/vcl/inc/textlineinfo.hxx
new file mode 100644
index 0000000000..0d5f442892
--- /dev/null
+++ b/vcl/inc/textlineinfo.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_INC_TEXTLINEINFO_HXX
+#define INCLUDED_VCL_INC_TEXTLINEINFO_HXX
+
+#include <tools/long.hxx>
+
+#include <vector>
+
+class ImplTextLineInfo
+{
+private:
+ tools::Long mnWidth;
+ sal_Int32 mnIndex;
+ sal_Int32 mnLen;
+
+public:
+ ImplTextLineInfo( tools::Long nWidth, sal_Int32 nIndex, sal_Int32 nLen )
+ {
+ mnWidth = nWidth;
+ mnIndex = nIndex;
+ mnLen = nLen;
+ }
+
+ tools::Long GetWidth() const { return mnWidth; }
+ sal_Int32 GetIndex() const { return mnIndex; }
+ sal_Int32 GetLen() const { return mnLen; }
+};
+
+#define MULTITEXTLINEINFO_RESIZE 16
+
+class ImplMultiTextLineInfo
+{
+public:
+ ImplMultiTextLineInfo();
+ ~ImplMultiTextLineInfo();
+
+ void AddLine( const ImplTextLineInfo& );
+ void Clear();
+
+ const ImplTextLineInfo& GetLine( sal_Int32 nLine ) const
+ { return mvLines[nLine]; }
+ ImplTextLineInfo& GetLine( sal_Int32 nLine )
+ { return mvLines[nLine]; }
+ sal_Int32 Count() const { return mvLines.size(); }
+
+private:
+ ImplMultiTextLineInfo( const ImplMultiTextLineInfo& ) = delete;
+ ImplMultiTextLineInfo& operator=( const ImplMultiTextLineInfo& ) = delete;
+
+ std::vector<ImplTextLineInfo> mvLines;
+
+};
+
+#endif // INCLUDED_VCL_INC_TEXTLINEINFO_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/textrender.hxx b/vcl/inc/textrender.hxx
new file mode 100644
index 0000000000..eb4af536e4
--- /dev/null
+++ b/vcl/inc/textrender.hxx
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include "salgdi.hxx"
+
+class FontMetricData;
+class PhysicalFontCollection;
+namespace vcl::font { class PhysicalFontFace; }
+
+class TextRenderImpl
+{
+public:
+ // can't call ReleaseFonts here, as the destructor just calls this classes SetFont (pure virtual)!
+ virtual ~TextRenderImpl() {}
+
+ virtual void SetTextColor( Color nColor ) = 0;
+ virtual void SetFont(LogicalFontInstance*, int nFallbackLevel) = 0;
+ void ReleaseFonts() { SetFont(nullptr, 0); }
+ virtual void GetFontMetric( FontMetricDataRef&, int nFallbackLevel ) = 0;
+ virtual FontCharMapRef GetFontCharMap() const = 0;
+ virtual bool GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const = 0;
+ virtual void GetDevFontList( vcl::font::PhysicalFontCollection* ) = 0;
+ virtual void ClearDevFontCache() = 0;
+ virtual bool AddTempDevFont( vcl::font::PhysicalFontCollection*, const OUString& rFileURL, const OUString& rFontName ) = 0;
+
+ virtual std::unique_ptr<GenericSalLayout>
+ GetTextLayout(int nFallbackLevel) = 0;
+ virtual void DrawTextLayout(const GenericSalLayout&, const SalGraphics&) = 0;
+};
+
+/* vim:set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/toolbarvalue.hxx b/vcl/inc/toolbarvalue.hxx
new file mode 100644
index 0000000000..b2c2d6dcb1
--- /dev/null
+++ b/vcl/inc/toolbarvalue.hxx
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_TOOLBARVALUE_HXX
+#define INCLUDED_VCL_INC_TOOLBARVALUE_HXX
+
+#include <tools/gen.hxx>
+#include <vcl/salnativewidgets.hxx>
+
+/* Toolbarvalue:
+ *
+ * Value container for toolbars detailing the grip position
+ */
+class ToolbarValue final : public ImplControlValue
+{
+public:
+ ToolbarValue()
+ : ImplControlValue(ControlType::Toolbar, 0)
+ {
+ mbIsTopDockingArea = false;
+ }
+ virtual ~ToolbarValue() override;
+ virtual ToolbarValue* clone() const override;
+ ToolbarValue(ToolbarValue const&) = default;
+ ToolbarValue(ToolbarValue&&) = default;
+ ToolbarValue& operator=(ToolbarValue const&) = delete; // due to ImplControlValue
+ ToolbarValue& operator=(ToolbarValue&&) = delete; // due to ImplControlValue
+ tools::Rectangle maGripRect;
+ // indicates that this is the top aligned dockingarea
+ // adjacent to the menubar, only used on Windows
+ bool mbIsTopDockingArea;
+};
+
+#endif // INCLUDED_VCL_INC_TOOLBARVALUE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/toolbox.h b/vcl/inc/toolbox.h
new file mode 100644
index 0000000000..bda27560cb
--- /dev/null
+++ b/vcl/inc/toolbox.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_TOOLBOX_H
+#define INCLUDED_VCL_INC_TOOLBOX_H
+
+#include <vcl/ctrl.hxx>
+#include <vcl/toolbox.hxx>
+
+#include <optional>
+#include <vector>
+
+#define TB_DROPDOWNARROWWIDTH 11
+
+#define TB_MENUBUTTON_SIZE 12
+#define TB_MENUBUTTON_OFFSET 2
+
+namespace vcl { class Window; }
+
+struct ImplToolItem
+{
+ VclPtr<vcl::Window> mpWindow; //don't dispose mpWindow - we get copied around
+ bool mbNonInteractiveWindow;
+ void* mpUserData;
+ Image maImage;
+ Degree10 mnImageAngle;
+ bool mbMirrorMode;
+ OUString maText;
+ OUString maQuickHelpText;
+ OUString maHelpText;
+ OUString maCommandStr;
+ OUString maHelpId;
+ tools::Rectangle maRect;
+ tools::Rectangle maCalcRect;
+ /// Widget layout may request size; set it as the minimal size (like, the item will always have at least this size).
+ Size maMinimalItemSize;
+ /// The overall horizontal item size, including one or more of [image size + textlength + dropdown arrow]
+ Size maItemSize;
+ tools::Long mnSepSize;
+ tools::Long mnDropDownArrowWidth;
+ /// Size of the content (bitmap or text, without dropdown) that we have in the item.
+ Size maContentSize;
+ ToolBoxItemType meType;
+ ToolBoxItemBits mnBits;
+ TriState meState;
+ ToolBoxItemId mnId;
+ bool mbEnabled:1,
+ mbVisible:1,
+ mbEmptyBtn:1,
+ mbShowWindow:1,
+ mbBreak:1,
+ mbVisibleText:1, // indicates if text will definitely be drawn, influences dropdown pos
+ mbExpand:1;
+
+ ImplToolItem();
+ ImplToolItem( ToolBoxItemId nItemId, Image aImage,
+ ToolBoxItemBits nItemBits );
+ ImplToolItem( ToolBoxItemId nItemId, OUString aTxt,
+ OUString aCommand,
+ ToolBoxItemBits nItemBits );
+ ImplToolItem( ToolBoxItemId nItemId, Image aImage,
+ OUString aTxt,
+ ToolBoxItemBits nItemBits );
+
+ // returns the size of an item, taking toolbox orientation into account
+ // the default size is the precomputed size for standard items
+ // ie those that are just ordinary buttons (no windows or text etc.)
+ // bCheckMaxWidth indicates that item windows must not exceed maxWidth in which case they will be painted as buttons
+ Size GetSize( bool bHorz, bool bCheckMaxWidth, tools::Long maxWidth, const Size& rDefaultSize );
+
+ // only useful for buttons: returns if the text or image part or both can be drawn according to current button drawing style
+ void DetermineButtonDrawStyle( ButtonType eButtonType, bool& rbImage, bool& rbText ) const;
+
+ // returns the rectangle which contains the drop down arrow
+ // or an empty rect if there is none
+ // bHorz denotes the toolbox alignment
+ tools::Rectangle GetDropDownRect( bool bHorz ) const;
+
+ // returns sal_True if the toolbar item is currently clipped, which can happen for docked toolbars
+ bool IsClipped() const;
+
+ // returns sal_True if the toolbar item is currently hidden i.e. they are unchecked in the toolbar Customize menu
+ bool IsItemHidden() const;
+
+private:
+ void init(ToolBoxItemId nItemId, ToolBoxItemBits nItemBits, bool bEmptyBtn);
+};
+
+namespace vcl
+{
+
+struct ToolBoxLayoutData : public ControlLayoutData
+{
+ std::vector< ToolBoxItemId > m_aLineItemIds;
+};
+
+} /* namespace vcl */
+
+struct ImplToolBoxPrivateData
+{
+ std::optional<vcl::ToolBoxLayoutData> m_pLayoutData;
+ ToolBox::ImplToolItems m_aItems;
+
+ ImplToolBoxPrivateData();
+ ~ImplToolBoxPrivateData();
+
+ void ImplClearLayoutData() { m_pLayoutData.reset(); }
+
+ // called when dropdown items are clicked
+ Link<ToolBox *, void> maDropdownClickHdl;
+ Timer maDropdownTimer { "vcl::ToolBox mpData->maDropdownTimer" }; // for opening dropdown items on "long click"
+
+ // large or small buttons ?
+ ToolBoxButtonSize meButtonSize;
+
+ // the optional custom menu
+ VclPtr<PopupMenu> mpMenu;
+ ToolBoxMenuType maMenuType;
+
+ // called when menu button is clicked and before the popup menu is executed
+ Link<ToolBox *, void> maMenuButtonHdl;
+
+ // a dummy item representing the custom menu button
+ ImplToolItem maMenubuttonItem;
+ tools::Long mnMenuButtonWidth;
+
+ Wallpaper maDisplayBackground;
+
+ bool mbIsLocked:1, // keeps last lock state from ImplDockingWindowWrapper
+ mbAssumeDocked:1, // only used during calculations to override current floating/popup mode
+ mbAssumeFloating:1,
+ mbAssumePopupMode:1,
+ mbKeyInputDisabled:1, // no KEY input if all items disabled, closing/docking will be allowed though
+ mbIsPaintLocked:1, // don't allow paints
+ mbMenubuttonSelected:1, // menu button is highlighted
+ mbMenubuttonWasLastSelected:1, // menu button was highlighted when focus was lost
+ mbNativeButtons:1, // system supports native toolbar buttons
+ mbWillUsePopupMode:1, // this toolbox will be opened in popup mode
+ mbDropDownByKeyboard:1; // tells whether a dropdown was started by key input
+};
+
+#endif // INCLUDED_VCL_INC_TOOLBOX_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/treeglue.hxx b/vcl/inc/treeglue.hxx
new file mode 100644
index 0000000000..d445a533b4
--- /dev/null
+++ b/vcl/inc/treeglue.hxx
@@ -0,0 +1,171 @@
+/* -*- 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/toolkit/svtabbx.hxx>
+#include "svimpbox.hxx"
+
+//the default NotifyStartDrag is weird to me, and defaults to enabling all
+//possibilities when drag starts, while restricting it to some subset of
+//the configured drag drop mode would make more sense to me, but I'm not
+//going to change the baseclass
+
+class LclHeaderTabListBox final : public SvHeaderTabListBox
+{
+private:
+ Link<SvTreeListEntry*, bool> m_aEditingEntryHdl;
+ Link<std::pair<SvTreeListEntry*, OUString>, bool> m_aEditedEntryHdl;
+
+public:
+ LclHeaderTabListBox(vcl::Window* pParent, WinBits nWinStyle)
+ : SvHeaderTabListBox(pParent, nWinStyle)
+ {
+ }
+
+ void SetEditingEntryHdl(const Link<SvTreeListEntry*, bool>& rLink)
+ {
+ m_aEditingEntryHdl = rLink;
+ }
+
+ void SetEditedEntryHdl(const Link<std::pair<SvTreeListEntry*, OUString>, bool>& rLink)
+ {
+ m_aEditedEntryHdl = rLink;
+ }
+
+ virtual DragDropMode NotifyStartDrag() override { return GetDragDropMode(); }
+
+ virtual bool EditingEntry(SvTreeListEntry* pEntry) override
+ {
+ return m_aEditingEntryHdl.Call(pEntry);
+ }
+
+ virtual bool EditedEntry(SvTreeListEntry* pEntry, const OUString& rNewText) override
+ {
+ return m_aEditedEntryHdl.Call(std::pair<SvTreeListEntry*, OUString>(pEntry, rNewText));
+ }
+};
+
+class LclTabListBox final : public SvTabListBox
+{
+ Link<SvTreeListBox*, void> m_aModelChangedHdl;
+ Link<SvTreeListBox*, bool> m_aStartDragHdl;
+ Link<SvTreeListBox*, void> m_aEndDragHdl;
+ Link<SvTreeListEntry*, bool> m_aEditingEntryHdl;
+ Link<std::pair<SvTreeListEntry*, OUString>, bool> m_aEditedEntryHdl;
+
+public:
+ LclTabListBox(vcl::Window* pParent, WinBits nWinStyle)
+ : SvTabListBox(pParent, nWinStyle)
+ {
+ }
+
+ void SetModelChangedHdl(const Link<SvTreeListBox*, void>& rLink) { m_aModelChangedHdl = rLink; }
+ void SetStartDragHdl(const Link<SvTreeListBox*, bool>& rLink) { m_aStartDragHdl = rLink; }
+ void SetEndDragHdl(const Link<SvTreeListBox*, void>& rLink) { m_aEndDragHdl = rLink; }
+ void SetEditingEntryHdl(const Link<SvTreeListEntry*, bool>& rLink)
+ {
+ m_aEditingEntryHdl = rLink;
+ }
+ void SetEditedEntryHdl(const Link<std::pair<SvTreeListEntry*, OUString>, bool>& rLink)
+ {
+ m_aEditedEntryHdl = rLink;
+ }
+
+ virtual DragDropMode NotifyStartDrag() override { return GetDragDropMode(); }
+
+ virtual void StartDrag(sal_Int8 nAction, const Point& rPosPixel) override
+ {
+ if (m_aStartDragHdl.Call(this))
+ return;
+ SvTabListBox::StartDrag(nAction, rPosPixel);
+ }
+
+ virtual void DragFinished(sal_Int8 nDropAction) override
+ {
+ SvTabListBox::DragFinished(nDropAction);
+ m_aEndDragHdl.Call(this);
+ }
+
+ virtual void ModelHasCleared() override
+ {
+ SvTabListBox::ModelHasCleared();
+ m_aModelChangedHdl.Call(this);
+ }
+
+ virtual void ModelHasInserted(SvTreeListEntry* pEntry) override
+ {
+ SvTabListBox::ModelHasInserted(pEntry);
+ m_aModelChangedHdl.Call(this);
+ }
+
+ virtual void ModelHasInsertedTree(SvTreeListEntry* pEntry) override
+ {
+ SvTabListBox::ModelHasInsertedTree(pEntry);
+ m_aModelChangedHdl.Call(this);
+ }
+
+ virtual void ModelHasMoved(SvTreeListEntry* pSource) override
+ {
+ SvTabListBox::ModelHasMoved(pSource);
+ m_aModelChangedHdl.Call(this);
+ }
+
+ virtual void ModelHasRemoved(SvTreeListEntry* pEntry) override
+ {
+ SvTabListBox::ModelHasRemoved(pEntry);
+ m_aModelChangedHdl.Call(this);
+ }
+
+ SvTreeListEntry* GetTargetAtPoint(const Point& rPos, bool bHighLightTarget, bool bScroll = true)
+ {
+ SvTreeListEntry* pOldTargetEntry = pTargetEntry;
+ pTargetEntry = PosOverBody(rPos) ? pImpl->GetEntry(rPos) : nullptr;
+ if (pOldTargetEntry != pTargetEntry)
+ ImplShowTargetEmphasis(pOldTargetEntry, false);
+
+ if (bScroll)
+ {
+ // 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);
+ }
+ }
+ }
+
+ if (pTargetEntry && bHighLightTarget)
+ ImplShowTargetEmphasis(pTargetEntry, true);
+ return pTargetEntry;
+ }
+
+ virtual SvTreeListEntry* GetDropTarget(const Point& rPos) override
+ {
+ return GetTargetAtPoint(rPos, true);
+ }
+
+ virtual bool EditingEntry(SvTreeListEntry* pEntry) override
+ {
+ return m_aEditingEntryHdl.Call(pEntry);
+ }
+
+ virtual bool EditedEntry(SvTreeListEntry* pEntry, const OUString& rNewText) override
+ {
+ return m_aEditedEntryHdl.Call(std::pair<SvTreeListEntry*, OUString>(pEntry, rNewText));
+ }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/uiobject-internal.hxx b/vcl/inc/uiobject-internal.hxx
new file mode 100644
index 0000000000..accecb2e8f
--- /dev/null
+++ b/vcl/inc/uiobject-internal.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/.
+ */
+
+#include <memory>
+#include <vcl/uitest/uiobject.hxx>
+#include "wizdlg.hxx"
+
+class RoadmapWizard;
+
+class RoadmapWizardUIObject final : public WindowUIObject
+{
+ VclPtr<vcl::RoadmapWizard> mxRoadmapWizard;
+
+public:
+ RoadmapWizardUIObject(const VclPtr<vcl::RoadmapWizard>& xRoadmapWizard);
+ virtual ~RoadmapWizardUIObject() override;
+
+ virtual StringMap get_state() override;
+
+ virtual void execute(const OUString& rAction, const StringMap& rParameters) override;
+
+ static std::unique_ptr<UIObject> create(vcl::Window* pWindow);
+
+private:
+ virtual OUString get_name() const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/units.hrc b/vcl/inc/units.hrc
new file mode 100644
index 0000000000..677f9f5cef
--- /dev/null
+++ b/vcl/inc/units.hrc
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_UNITS_HRC
+#define INCLUDED_VCL_INC_UNITS_HRC
+
+#include <tools/fldunit.hxx>
+#include <unotools/resmgr.hxx>
+
+#define NC_(Context, String) TranslateId(Context, u8##String)
+
+std::pair<TranslateId, FieldUnit> SV_FUNIT_STRINGS[] =
+{
+ // To translators: This is the first entry of a sequence of measurement unit names
+ { NC_("SV_FUNIT_STRINGS", "mm"), FieldUnit::MM },
+ { NC_("SV_FUNIT_STRINGS", "cm"), FieldUnit::CM },
+ { NC_("SV_FUNIT_STRINGS", "m"), FieldUnit::M },
+ { NC_("SV_FUNIT_STRINGS", "km"), FieldUnit::KM },
+ { NC_("SV_FUNIT_STRINGS", "twips"), FieldUnit::TWIP },
+ { NC_("SV_FUNIT_STRINGS", "twip"), FieldUnit::TWIP },
+ { NC_("SV_FUNIT_STRINGS", "pt"), FieldUnit::POINT },
+ { NC_("SV_FUNIT_STRINGS", "pc"), FieldUnit::PICA },
+ /* To translators: double prime symbol for inch */
+ { NC_("SV_FUNIT_STRINGS", "″"), FieldUnit::INCH },
+ { NC_("SV_FUNIT_STRINGS", "\""), FieldUnit::INCH },
+ { NC_("SV_FUNIT_STRINGS", "in"), FieldUnit::INCH },
+ { NC_("SV_FUNIT_STRINGS", "inch"), FieldUnit::INCH },
+ /* To translators: prime symbol for foot */
+ { NC_("SV_FUNIT_STRINGS", "′"), FieldUnit::FOOT },
+ { NC_("SV_FUNIT_STRINGS", "'"), FieldUnit::FOOT },
+ { NC_("SV_FUNIT_STRINGS", "ft"), FieldUnit::FOOT },
+ { NC_("SV_FUNIT_STRINGS", "foot"), FieldUnit::FOOT },
+ { NC_("SV_FUNIT_STRINGS", "feet"), FieldUnit::FOOT },
+ { NC_("SV_FUNIT_STRINGS", "miles"), FieldUnit::MILE },
+ { NC_("SV_FUNIT_STRINGS", "mile"), FieldUnit::MILE },
+ { NC_("SV_FUNIT_STRINGS", "ch"), FieldUnit::CHAR },
+ { NC_("SV_FUNIT_STRINGS", "line"), FieldUnit::LINE },
+ { NC_("SV_FUNIT_STRINGS", "pixels"), FieldUnit::PIXEL },
+ { NC_("SV_FUNIT_STRINGS", "pixel"), FieldUnit::PIXEL },
+ /* To translators: degree */
+ { NC_("SV_FUNIT_STRINGS", "°"), FieldUnit::DEGREE },
+ { NC_("SV_FUNIT_STRINGS", "sec"), FieldUnit::SECOND },
+ // To translators: This is the last entry of the sequence of measurement unit names
+ { NC_("SV_FUNIT_STRINGS", "ms"), FieldUnit::MILLISECOND }
+};
+
+#endif // INCLUDED_VCL_INC_UNITS_HRC
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/cairotextrender.hxx b/vcl/inc/unx/cairotextrender.hxx
new file mode 100644
index 0000000000..50848ed19f
--- /dev/null
+++ b/vcl/inc/unx/cairotextrender.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <unx/freetypetextrender.hxx>
+
+class GenericSalLayout;
+class SalGraphics;
+struct CairoCommon;
+typedef struct _cairo cairo_t;
+typedef struct _cairo_font_options cairo_font_options_t;
+
+class VCL_DLLPUBLIC CairoTextRender final : public FreeTypeTextRenderImpl
+{
+private:
+ CairoCommon& mrCairoCommon;
+protected:
+ cairo_t* getCairoContext();
+ void releaseCairoContext(cairo_t* cr);
+ void clipRegion(cairo_t* cr);
+
+public:
+ virtual void DrawTextLayout(const GenericSalLayout&, const SalGraphics&) override;
+ CairoTextRender(CairoCommon& rCairoCommon);
+ virtual ~CairoTextRender();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/cpdmgr.hxx b/vcl/inc/unx/cpdmgr.hxx
new file mode 100644
index 0000000000..2806f1d09b
--- /dev/null
+++ b/vcl/inc/unx/cpdmgr.hxx
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <config_dbus.h>
+#include <config_gio.h>
+
+#if ENABLE_DBUS && ENABLE_GIO
+#include <gio/gio.h>
+#else
+typedef struct _GDBusProxy GDBusProxy;
+typedef struct _GDBusConnection GDBusConnection;
+#endif
+
+#include <printerinfomanager.hxx>
+#include "cupsmgr.hxx"
+
+#define BACKEND_DIR "/usr/share/print-backends"
+#define FRONTEND_INTERFACE "/usr/share/dbus-1/interfaces/org.openprinting.Frontend.xml"
+#define BACKEND_INTERFACE "/usr/share/dbus-1/interfaces/org.openprinting.Backend.xml"
+
+namespace psp
+{
+
+class PPDParser;
+
+struct CPDPrinter
+{
+ const char* id;
+ const char* name;
+ const char* info;
+ const char* location;
+ const char* make_and_model;
+ const char* printer_state;
+ const char* backend_name;
+ bool is_accepting_jobs;
+ GDBusProxy* backend;
+};
+
+class CPDManager final : public PrinterInfoManager
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ GDBusConnection * m_pConnection = nullptr;
+ bool m_aPrintersChanged = true;
+ std::vector<std::pair<std::string, gchar*>> m_tBackends;
+ std::unordered_map< std::string, GDBusProxy * > m_pBackends;
+ std::unordered_map< FILE*, OString, FPtrHash > m_aSpoolFiles;
+ std::unordered_map< OUString, CPDPrinter * > m_aCPDDestMap;
+ std::unordered_map< OUString, PPDContext > m_aDefaultContexts;
+#endif
+ CPDManager();
+ // Function called when CPDManager is destroyed
+ virtual ~CPDManager() override;
+
+ virtual void initialize() override;
+
+#if ENABLE_DBUS && ENABLE_GIO
+ static void onNameAcquired(GDBusConnection *connection, const gchar* name, gpointer user_data);
+ static void onNameLost (GDBusConnection *, const gchar *name, gpointer);
+ static void printerAdded (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data);
+ static void printerRemoved (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data);
+
+ static void getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, const OString& rJobName, int& rNumOptions, GVariant **arr );
+#endif
+
+public:
+#if ENABLE_DBUS && ENABLE_GIO
+ // Functions involved in initialization
+ GDBusProxy* getProxy(const std::string& target);
+ void addBackend( std::pair< std::string, GDBusProxy * > pair );
+ void addTempBackend(const std::pair<std::string, gchar*>& pair);
+ std::vector<std::pair<std::string, gchar*>> const & getTempBackends() const;
+ void addNewPrinter( const OUString&, const OUString&, CPDPrinter * );
+#endif
+
+ // Create CPDManager
+ static CPDManager* tryLoadCPD();
+
+ // Create a PPDParser for CPD Printers
+ const PPDParser* createCPDParser( const OUString& rPrinter );
+
+ // Functions related to printing
+ virtual FILE* startSpool( const OUString& rPrinterName, bool bQuickCommand ) override;
+ virtual bool endSpool( const OUString& rPrinterName, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber ) override;
+ virtual void setupJobContextData( JobData& rData ) override;
+
+ // check if the printer configuration has changed
+ virtual bool checkPrintersChanged( bool bWait ) override;
+};
+
+} // namespace psp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/cupsmgr.hxx b/vcl/inc/unx/cupsmgr.hxx
new file mode 100644
index 0000000000..fb172103bb
--- /dev/null
+++ b/vcl/inc/unx/cupsmgr.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <printerinfomanager.hxx>
+#include <osl/thread.h>
+#include <osl/mutex.hxx>
+
+class cups_dest_s;
+
+namespace psp
+{
+
+class PPDParser;
+
+struct FPtrHash
+{
+ size_t operator()(const FILE* pPtr) const
+ { return reinterpret_cast<size_t>(pPtr); }
+};
+
+class CUPSManager final : public PrinterInfoManager
+{
+ std::unordered_map< FILE*, OString, FPtrHash > m_aSpoolFiles;
+ int m_nDests;
+ cups_dest_s* m_pDests;
+ bool m_bNewDests;
+ std::unordered_map< OUString, int > m_aCUPSDestMap;
+
+ std::unordered_map< OUString, PPDContext > m_aDefaultContexts;
+
+ OString m_aUser;
+ /** this is a security risk, but the CUPS API demands
+ to deliver a pointer to a static buffer containing
+ the password, so this cannot be helped*/
+ OString m_aPassword;
+
+ osl::Mutex m_aCUPSMutex;
+ oslThread m_aDestThread;
+
+ osl::Mutex m_aGetPPDMutex;
+ bool m_bPPDThreadRunning;
+
+ CUPSManager();
+ virtual ~CUPSManager() override;
+
+ virtual void initialize() override;
+
+ static void getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, int& rNumOptions, void** rOptions );
+ void runDests();
+ OString threadedCupsGetPPD(const char* pPrinter);
+public:
+ static void runDestThread(void* pMgr);
+
+ static CUPSManager* tryLoadCUPS();
+
+ /// wraps cupsGetPPD, so unlink after use !
+ const PPDParser* createCUPSParser( const OUString& rPrinter );
+
+ const char* authenticateUser();
+
+ virtual FILE* startSpool( const OUString& rPrinterName, bool bQuickCommand ) override;
+ virtual bool endSpool( const OUString& rPrinterName, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber ) override;
+ virtual void setupJobContextData( JobData& rData ) override;
+
+ /// check if the printer configuration has changed
+ virtual bool checkPrintersChanged( bool bWait ) override;
+};
+
+} // namespace psp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/desktops.hxx b/vcl/inc/unx/desktops.hxx
new file mode 100644
index 0000000000..b40004230f
--- /dev/null
+++ b/vcl/inc/unx/desktops.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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <sal/types.h>
+
+enum SAL_DLLPUBLIC_RTTI DesktopType
+{
+ DESKTOP_NONE, // headless, i.e. no X connection at all
+ DESKTOP_UNKNOWN, // unknown desktop, simple WM, etc.
+ DESKTOP_GNOME,
+ DESKTOP_UNITY,
+ DESKTOP_XFCE,
+ DESKTOP_MATE,
+ DESKTOP_PLASMA5,
+ DESKTOP_PLASMA6,
+ DESKTOP_LXQT
+}; // keep in sync with desktop_strings[] in salplug.cxx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/fc_fontoptions.hxx b/vcl/inc/unx/fc_fontoptions.hxx
new file mode 100644
index 0000000000..73bcf3421b
--- /dev/null
+++ b/vcl/inc/unx/fc_fontoptions.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 .
+ */
+
+#pragma once
+
+#include <rtl/string.hxx>
+#include <vcl/dllapi.h>
+
+typedef struct _FcPattern FcPattern;
+class VCL_DLLPUBLIC FontConfigFontOptions
+{
+public:
+ FontConfigFontOptions(FcPattern* pPattern) :
+ mpPattern(pPattern) {}
+ ~FontConfigFontOptions();
+
+ void SyncPattern(const OString& rFileName, sal_uInt32 nFontFace, sal_uInt32 nFontVariation, bool bEmbolden);
+ FcPattern* GetPattern() const;
+ static void cairo_font_options_substitute(FcPattern* pPattern);
+private:
+ FcPattern* mpPattern;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/fontmanager.hxx b/vcl/inc/unx/fontmanager.hxx
new file mode 100644
index 0000000000..d39795dfa4
--- /dev/null
+++ b/vcl/inc/unx/fontmanager.hxx
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <o3tl/sorted_vector.hxx>
+#include <tools/fontenum.hxx>
+#include <vcl/dllapi.h>
+#include <vcl/timer.hxx>
+#include <com/sun/star/lang/Locale.hpp>
+#include <unx/fc_fontoptions.hxx>
+
+#include <font/PhysicalFontFace.hxx>
+
+#include <set>
+#include <memory>
+#include <string_view>
+#include <vector>
+#include <unordered_map>
+
+/*
+ * some words on metrics: every length returned by PrintFontManager and
+ * friends are PostScript afm style, that is they are 1/1000 font height
+ */
+
+class FontAttributes;
+class FontConfigFontOptions;
+namespace vcl::font
+{
+class FontSelectPattern;
+}
+namespace vcl { struct NameRecord; }
+class GenericUnixSalData;
+
+namespace psp {
+class PPDParser;
+
+typedef int fontID;
+
+// a class to manage printable fonts
+
+class VCL_PLUGIN_PUBLIC PrintFontManager
+{
+ struct PrintFont;
+ friend struct PrintFont;
+
+ struct VCL_DLLPRIVATE PrintFont
+ {
+ FontAttributes m_aFontAttributes;
+
+ int m_nDirectory; // atom containing system dependent path
+ OString m_aFontFile; // relative to directory
+ int m_nCollectionEntry; // 0 for regular fonts, 0 to ... for fonts stemming from collections
+ int m_nVariationEntry; // 0 for regular fonts, 0 to ... for fonts stemming from font variations
+
+ explicit PrintFont();
+ };
+
+ fontID m_nNextFontID;
+ std::unordered_map< fontID, PrintFont > m_aFonts;
+ // for speeding up findFontFileID
+ std::unordered_map< OString, o3tl::sorted_vector< fontID > >
+ m_aFontFileToFontID;
+
+ std::unordered_map< OString, int >
+ m_aDirToAtom;
+ std::unordered_map< int, OString > m_aAtomToDir;
+ int m_nNextDirAtom;
+
+ OString getFontFile(const PrintFont& rFont) const;
+
+ std::vector<PrintFont> analyzeFontFile(int nDirID, const OString& rFileName, const char *pFormat=nullptr) const;
+ bool analyzeSfntFile(PrintFont& rFont) const;
+ // finds the font id for the nFaceIndex face in this font file
+ // There may be multiple font ids for font collections
+ fontID findFontFileID(int nDirID, const OString& rFile, int nFaceIndex, int nVariationIndex) const;
+
+ // There may be multiple font ids for font collections
+ std::vector<fontID> findFontFileIDs( int nDirID, const OString& rFile ) const;
+
+ static FontFamily matchFamilyName( std::u16string_view rFamily );
+
+ OString getDirectory( int nAtom ) const;
+ int getDirectoryAtom( const OString& rDirectory );
+
+ /* try to initialize fonts from libfontconfig
+
+ called from <code>initialize()</code>
+ */
+ static void initFontconfig();
+ void countFontconfigFonts();
+ /* deinitialize fontconfig
+ */
+ static void deinitFontconfig();
+
+ /* register an application specific font directory for libfontconfig
+
+ since fontconfig is asked for font substitutes before OOo will check for font availability
+ and fontconfig will happily substitute fonts it doesn't know (e.g. "Arial Narrow" -> "DejaVu Sans Book"!)
+ it becomes necessary to tell the library about all the hidden font treasures
+ */
+ static void addFontconfigDir(const OString& rDirectory);
+
+ /* register an application specific font file for libfontconfig */
+ static void addFontconfigFile(const OString& rFile);
+
+ std::set<OString> m_aPreviousLangSupportRequests;
+ std::vector<OUString> m_aCurrentRequests;
+ Timer m_aFontInstallerTimer;
+
+ DECL_DLLPRIVATE_LINK( autoInstallFontLangSupport, Timer*, void );
+ PrintFontManager();
+public:
+ ~PrintFontManager();
+ friend class ::GenericUnixSalData;
+ static PrintFontManager& get(); // one instance only
+
+ // There may be multiple font ids for font collections
+ std::vector<fontID> addFontFile( std::u16string_view rFileUrl );
+
+ void initialize();
+
+ const PrintFont* getFont( fontID nID ) const
+ {
+ auto it = m_aFonts.find( nID );
+ return it == m_aFonts.end() ? nullptr : &it->second;
+ }
+
+ // returns the ids of all managed fonts.
+ void getFontList( std::vector< fontID >& rFontIDs );
+
+ // routines to get font info in small pieces
+
+ // get a specific fonts system dependent filename
+ OString getFontFileSysPath( fontID nFontID ) const
+ {
+ return getFontFile( *getFont( nFontID ) );
+ }
+
+ // get the ttc face number
+ int getFontFaceNumber( fontID nFontID ) const;
+
+ // get the ttc face variation
+ int getFontFaceVariation( fontID nFontID ) const;
+
+ // font administration functions
+
+ /* system dependent font matching
+
+ <p>
+ <code>matchFont</code> matches a pattern of font characteristics
+ and returns the closest match if possible. If a match was found
+ it will update rDFA to the found matching font.
+ </p>
+ <p>
+ implementation note: currently the function is only implemented
+ for fontconfig.
+ </p>
+
+ @param rDFA
+ out of the FontAttributes structure the following
+ fields will be used for the match:
+ <ul>
+ <li>family name</li>
+ <li>italic</li>
+ <li>width</li>
+ <li>weight</li>
+ <li>pitch</li>
+ </ul>
+
+ @param rLocale
+ if <code>rLocal</code> contains non empty strings the corresponding
+ locale will be used for font matching also; e.g. "Sans" can result
+ in different fonts in e.g. english and japanese
+ */
+ bool matchFont(FontAttributes& rDFA, const css::lang::Locale& rLocale);
+
+ static std::unique_ptr<FontConfigFontOptions> getFontOptions(const FontAttributes& rFontAttributes, int nSize);
+
+ void Substitute(vcl::font::FontSelectPattern &rPattern, OUString& rMissingCodes);
+
+};
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/freetype_glyphcache.hxx b/vcl/inc/unx/freetype_glyphcache.hxx
new file mode 100644
index 0000000000..7a13fca832
--- /dev/null
+++ b/vcl/inc/unx/freetype_glyphcache.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <unx/glyphcache.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <font/LogicalFontInstance.hxx>
+
+#include <glyphid.hxx>
+
+// FreetypeFontFile has the responsibility that a font file is only mapped once.
+// (#86621#) the old directly ft-managed solution caused it to be mapped
+// in up to nTTC*nSizes*nOrientation*nSynthetic times
+class FreetypeFontFile final
+{
+public:
+ bool Map();
+ void Unmap();
+
+ const unsigned char* GetBuffer() const { return mpFileMap; }
+ int GetFileSize() const { return mnFileSize; }
+ const OString& GetFileName() const { return maNativeFileName; }
+ int GetLangBoost() const { return mnLangBoost; }
+
+private:
+ friend class FreetypeManager;
+ explicit FreetypeFontFile( OString aNativeFileName );
+
+ const OString maNativeFileName;
+ unsigned char* mpFileMap;
+ int mnFileSize;
+ int mnRefCount;
+ int mnLangBoost;
+};
+
+// FreetypeFontInfo corresponds to an unscaled font face
+class FreetypeFontInfo final
+{
+public:
+ ~FreetypeFontInfo();
+
+ FT_FaceRec_* GetFaceFT();
+ void ReleaseFaceFT();
+
+ FreetypeFontFile* GetFontFile() const { return mpFontFile; }
+ const OString& GetFontFileName() const { return mpFontFile->GetFileName(); }
+ int GetFontFaceIndex() const { return mnFaceNum; }
+ int GetFontFaceVariation() const { return mnFaceVariation; }
+ sal_IntPtr GetFontId() const { return mnFontId; }
+ const FontAttributes& GetFontAttributes() const { return maDevFontAttributes; }
+
+ void AnnounceFont( vcl::font::PhysicalFontCollection* );
+
+private:
+ friend class FreetypeManager;
+ explicit FreetypeFontInfo(FontAttributes , FreetypeFontFile* const pFontFile,
+ int nFaceNum, int nFaceVariation, sal_IntPtr nFontId);
+
+ FT_FaceRec_* maFaceFT;
+ FreetypeFontFile* const mpFontFile;
+ const int mnFaceNum;
+ const int mnFaceVariation;
+ int mnRefCount;
+ sal_IntPtr mnFontId;
+ FontAttributes maDevFontAttributes;
+
+};
+
+class FreetypeFontFace final : public vcl::font::PhysicalFontFace
+{
+private:
+ FreetypeFontInfo* mpFreetypeFontInfo;
+
+public:
+ FreetypeFontFace( FreetypeFontInfo*, const FontAttributes& );
+
+ virtual rtl::Reference<LogicalFontInstance> CreateFontInstance(const vcl::font::FontSelectPattern&) const override;
+ virtual sal_IntPtr GetFontId() const override { return mpFreetypeFontInfo->GetFontId(); }
+
+ virtual hb_face_t* GetHbFace() const override;
+ virtual hb_blob_t* GetHbTable(hb_tag_t nTag) const override;
+
+ const std::vector<hb_variation_t>& GetVariations(const LogicalFontInstance&) const override;
+};
+
+class SAL_DLLPUBLIC_RTTI FreetypeFontInstance final : public LogicalFontInstance
+{
+ friend rtl::Reference<LogicalFontInstance> FreetypeFontFace::CreateFontInstance(const vcl::font::FontSelectPattern&) const;
+
+ std::unique_ptr<FreetypeFont> mxFreetypeFont;
+
+ explicit FreetypeFontInstance(const vcl::font::PhysicalFontFace& rPFF, const vcl::font::FontSelectPattern& rFSP);
+
+public:
+ virtual ~FreetypeFontInstance() override;
+
+ FreetypeFont& GetFreetypeFont() const { return *mxFreetypeFont; }
+
+ virtual bool GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/freetypetextrender.hxx b/vcl/inc/unx/freetypetextrender.hxx
new file mode 100644
index 0000000000..63568db498
--- /dev/null
+++ b/vcl/inc/unx/freetypetextrender.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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <textrender.hxx>
+
+class FreetypeFontInstance;
+
+// Generic implementation that uses freetype, but DrawTextLayout()
+// still needs implementing (e.g. by Cairo or Skia).
+class VCL_DLLPUBLIC FreeTypeTextRenderImpl : public TextRenderImpl
+{
+protected:
+ rtl::Reference<FreetypeFontInstance>
+ mpFreetypeFont[ MAX_FALLBACK ];
+
+ Color mnTextColor;
+
+public:
+ FreeTypeTextRenderImpl();
+ virtual ~FreeTypeTextRenderImpl() override;
+
+ virtual void SetTextColor( Color nColor ) override;
+ virtual void SetFont(LogicalFontInstance*, int nFallbackLevel) override;
+ virtual void GetFontMetric( FontMetricDataRef&, int nFallbackLevel ) override;
+ virtual FontCharMapRef GetFontCharMap() const override;
+ virtual bool GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const override;
+ virtual void GetDevFontList( vcl::font::PhysicalFontCollection* ) override;
+ virtual void ClearDevFontCache() override;
+ virtual bool AddTempDevFont( vcl::font::PhysicalFontCollection*, const OUString& rFileURL, const OUString& rFontName ) override;
+
+ virtual std::unique_ptr<GenericSalLayout>
+ GetTextLayout(int nFallbackLevel) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gendata.hxx b/vcl/inc/unx/gendata.hxx
new file mode 100644
index 0000000000..4949613c0b
--- /dev/null
+++ b/vcl/inc/unx/gendata.hxx
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <osl/socket.hxx>
+
+#include <svdata.hxx>
+
+#include <memory>
+
+#ifndef IOS
+class FreetypeManager;
+#endif
+class SalGenericDisplay;
+
+#ifndef IOS
+
+namespace psp
+{
+class PrintFontManager;
+class PrinterInfoManager;
+}
+
+// SalData is a bit of a mess. For ImplSVData we need a SalData base class.
+// Windows, MacOS and iOS implement their own SalData class, so there is no
+// way to do inheritance from the "top" in all plugins. We also really don't
+// want to rename GenericUnixSalData and don't want to reinterpret_cast some
+// dummy pointer everywhere, so this seems the only sensible solution.
+class VCL_PLUGIN_PUBLIC SalData
+{
+protected:
+ SalData();
+
+public:
+ virtual ~SalData();
+};
+
+#endif
+
+// This class is kind of a misnomer. What this class is mainly about is the
+// usage of Freetype and Fontconfig, which happens to match all *nix backends;
+// except that the osx and ios backends are *nix but don't use this.
+class VCL_PLUGIN_PUBLIC GenericUnixSalData : public SalData
+{
+#ifndef IOS
+ friend class ::psp::PrinterInfoManager;
+#endif
+
+ SalGenericDisplay* m_pDisplay;
+ // cached hostname to avoid slow lookup
+ OUString m_aHostname;
+ // for transient storage of unicode strings eg. 'u123' by input methods
+ OUString m_aUnicodeEntry;
+
+#ifndef IOS
+ std::unique_ptr<FreetypeManager> m_pFreetypeManager;
+ std::unique_ptr<psp::PrintFontManager> m_pPrintFontManager;
+ std::unique_ptr<psp::PrinterInfoManager> m_pPrinterInfoManager;
+#endif
+
+ void InitFreetypeManager();
+ void InitPrintFontManager();
+
+public:
+ GenericUnixSalData();
+ virtual ~GenericUnixSalData() override;
+ virtual void Dispose();
+
+ SalGenericDisplay* GetDisplay() const { return m_pDisplay; }
+ void SetDisplay(SalGenericDisplay* pDisp) { m_pDisplay = pDisp; }
+
+ const OUString& GetHostname()
+ {
+ if (m_aHostname.isEmpty())
+ osl_getLocalHostname(&m_aHostname.pData);
+ return m_aHostname;
+ }
+
+ OUString& GetUnicodeCommand() { return m_aUnicodeEntry; }
+
+#ifndef IOS
+
+ FreetypeManager* GetFreetypeManager()
+ {
+ if (!m_pFreetypeManager)
+ InitFreetypeManager();
+ return m_pFreetypeManager.get();
+ }
+
+ psp::PrintFontManager* GetPrintFontManager()
+ {
+ if (!m_pPrintFontManager)
+ InitPrintFontManager();
+ // PrintFontManager needs the FreetypeManager
+ assert(m_pFreetypeManager);
+ return m_pPrintFontManager.get();
+ }
+
+#endif
+
+ // Mostly useful for remote protocol backends
+ virtual void ErrorTrapPush() = 0;
+ virtual bool ErrorTrapPop(bool bIgnoreError = true) = 0; // true on error
+};
+
+inline GenericUnixSalData* GetGenericUnixSalData()
+{
+ return static_cast<GenericUnixSalData*>(ImplGetSVData()->mpSalData);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gendisp.hxx b/vcl/inc/unx/gendisp.hxx
new file mode 100644
index 0000000000..5ef7d445b2
--- /dev/null
+++ b/vcl/inc/unx/gendisp.hxx
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <salwtype.hxx>
+#include <vcl/dllapi.h>
+#include <salusereventlist.hxx>
+
+class SalFrame;
+class VCL_DLLPUBLIC SalGenericDisplay : public SalUserEventList
+{
+protected:
+ SalFrame* m_pCapture;
+
+ virtual void ProcessEvent( SalUserEvent aEvent ) override;
+
+public:
+ SalGenericDisplay();
+ virtual ~SalGenericDisplay() override;
+
+ void registerFrame( SalFrame* pFrame );
+ virtual void deregisterFrame( SalFrame* pFrame );
+ void emitDisplayChanged();
+
+ void SendInternalEvent( SalFrame* pFrame, void* pData, SalEvent nEvent = SalEvent::UserEvent );
+ void CancelInternalEvent( SalFrame* pFrame, void* pData, SalEvent nEvent );
+ bool DispatchInternalEvent( bool bHandleAllCurrentEvent = false );
+
+ bool MouseCaptured( const SalFrame *pFrameData ) const
+ { return m_pCapture == pFrameData; }
+ SalFrame* GetCaptureFrame() const
+ { return m_pCapture; }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/geninst.h b/vcl/inc/unx/geninst.h
new file mode 100644
index 0000000000..32f0a61d1c
--- /dev/null
+++ b/vcl/inc/unx/geninst.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <memory>
+#include <comphelper/solarmutex.hxx>
+#include <salinst.hxx>
+#include <svdata.hxx>
+#include <unx/genprn.h>
+
+class VCL_DLLPUBLIC SalYieldMutex : public comphelper::SolarMutex
+{
+public:
+ SalYieldMutex();
+ virtual ~SalYieldMutex() override;
+};
+
+/*
+ * Abstract generic class to build vclplugin's instance classes from
+ */
+class GenPspGraphics;
+namespace vcl::font
+{
+ class PhysicalFontCollection;
+}
+
+class VCL_DLLPUBLIC SalGenericInstance : public SalInstance
+{
+protected:
+ bool mbPrinterInit;
+
+public:
+ SalGenericInstance( std::unique_ptr<comphelper::SolarMutex> pMutex )
+ : SalInstance(std::move(pMutex)), mbPrinterInit(false) {}
+ virtual ~SalGenericInstance() override;
+
+ // Printing
+ virtual SalInfoPrinter* CreateInfoPrinter ( SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pSetupData ) override;
+ virtual void DestroyInfoPrinter ( SalInfoPrinter* pPrinter ) override;
+ virtual std::unique_ptr<SalPrinter> CreatePrinter ( SalInfoPrinter* pInfoPrinter ) override;
+ virtual void GetPrinterQueueInfo ( ImplPrnQueueList* pList ) override;
+ virtual void GetPrinterQueueState ( SalPrinterQueueInfo* pInfo ) override;
+ virtual OUString GetDefaultPrinter() override;
+ virtual void PostPrintersChanged() = 0;
+ virtual void updatePrinterUpdate() override;
+ virtual void jobEndedPrinterUpdate() override;
+ bool isPrinterInit() const { return mbPrinterInit; }
+ virtual std::unique_ptr<GenPspGraphics> CreatePrintGraphics() = 0;
+
+ virtual OUString getOSVersion() override;
+
+ // prolly belongs somewhere else ... just a font help
+ static void RegisterFontSubstitutors( vcl::font::PhysicalFontCollection* pFontCollection );
+
+protected:
+ static void configurePspInfoPrinter( PspSalInfoPrinter* pInfoPrinter,
+ SalPrinterQueueInfo const * pQueueInfo,
+ ImplJobSetup* pSetupData );
+};
+
+inline SalGenericInstance *GetGenericInstance()
+{
+ return static_cast<SalGenericInstance*>(GetSalInstance());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/genprn.h b/vcl/inc/unx/genprn.h
new file mode 100644
index 0000000000..d030c46143
--- /dev/null
+++ b/vcl/inc/unx/genprn.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <jobdata.hxx>
+#include <salprn.hxx>
+
+class GenPspGraphics;
+class VCL_DLLPUBLIC PspSalInfoPrinter : public SalInfoPrinter
+{
+public:
+ std::unique_ptr<GenPspGraphics> m_pGraphics;
+ psp::JobData m_aJobData;
+
+ PspSalInfoPrinter();
+ virtual ~PspSalInfoPrinter() override;
+
+ // override all pure virtual methods
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics( SalGraphics* pGraphics ) override;
+ virtual bool Setup( weld::Window* pFrame, ImplJobSetup* pSetupData ) override;
+ virtual bool SetPrinterData( ImplJobSetup* pSetupData ) override;
+ virtual bool SetData( JobSetFlags nFlags, ImplJobSetup* pSetupData ) override;
+ virtual void GetPageInfo( const ImplJobSetup* pSetupData,
+ tools::Long& rOutWidth, tools::Long& rOutHeight,
+ Point& rPageOffset,
+ Size& rPaperSize ) override;
+ virtual sal_uInt32 GetCapabilities( const ImplJobSetup* pSetupData, PrinterCapType nType ) override;
+ virtual sal_uInt16 GetPaperBinCount( const ImplJobSetup* pSetupData ) override;
+ virtual OUString GetPaperBinName( const ImplJobSetup* pSetupData, sal_uInt16 nPaperBin ) override;
+ virtual void InitPaperFormats( const ImplJobSetup* pSetupData ) override;
+ virtual int GetLandscapeAngle( const ImplJobSetup* pSetupData ) override;
+};
+
+class VCL_DLLPUBLIC PspSalPrinter : public SalPrinter
+{
+public:
+ SalInfoPrinter* m_pInfoPrinter;
+ psp::JobData m_aJobData;
+
+ PspSalPrinter( SalInfoPrinter *pPrinter );
+ virtual ~PspSalPrinter() override;
+
+ // override all pure virtual methods
+ virtual bool StartJob( const OUString* pFileName,
+ const OUString& rJobName,
+ const OUString& rAppName,
+ sal_uInt32 nCopies,
+ bool bCollate,
+ bool bDirect,
+ ImplJobSetup* pSetupData ) override;
+ virtual bool StartJob( const OUString*,
+ const OUString&,
+ const OUString&,
+ ImplJobSetup*,
+ vcl::PrinterController& i_rController ) override;
+ virtual bool EndJob() override;
+ virtual SalGraphics* StartPage( ImplJobSetup* pSetupData, bool bNewJobData ) override;
+ virtual void EndPage() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/genpspgraphics.h b/vcl/inc/unx/genpspgraphics.h
new file mode 100644
index 0000000000..cad46f77f5
--- /dev/null
+++ b/vcl/inc/unx/genpspgraphics.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <config_cairo_canvas.h>
+
+#include <salgdi.hxx>
+#include <sallayout.hxx>
+
+#include <unx/cairotextrender.hxx>
+
+#include <headless/SvpGraphicsBackend.hxx>
+#include <headless/CairoCommon.hxx>
+
+namespace vcl::font
+{
+class PhysicalFontFace;
+class PhysicalFontCollection;
+}
+
+namespace psp { struct JobData; }
+
+class FreetypeFontInstance;
+class FontAttributes;
+class SalInfoPrinter;
+class FontMetricData;
+
+class VCL_DLLPUBLIC GenPspGraphics final : public SalGraphicsAutoDelegateToImpl
+{
+
+ psp::JobData* m_pJobData;
+
+ CairoCommon m_aCairoCommon;
+ CairoTextRender m_aTextRenderImpl;
+ std::unique_ptr<SvpGraphicsBackend> m_pBackend;
+
+public:
+ GenPspGraphics();
+ virtual ~GenPspGraphics() override;
+
+ void Init(psp::JobData* pJob);
+
+ // override all pure virtual methods
+ virtual SalGraphicsImpl* GetImpl() const override
+ {
+ return m_pBackend.get();
+ }
+
+ virtual void GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY ) override;
+
+ virtual void SetTextColor( Color nColor ) override;
+ virtual void SetFont(LogicalFontInstance*, int nFallbackLevel) override;
+ virtual void GetFontMetric( FontMetricDataRef&, int nFallbackLevel ) override;
+ virtual FontCharMapRef GetFontCharMap() const override;
+ virtual bool GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const override;
+ virtual void GetDevFontList( vcl::font::PhysicalFontCollection* ) override;
+ // graphics must drop any cached font info
+ virtual void ClearDevFontCache() override;
+ virtual bool AddTempDevFont( vcl::font::PhysicalFontCollection*,
+ const OUString& rFileURL,
+ const OUString& rFontName ) override;
+
+ virtual std::unique_ptr<GenericSalLayout>
+ GetTextLayout(int nFallbackLevel) override;
+ virtual void DrawTextLayout( const GenericSalLayout& ) override;
+
+ virtual SystemGraphicsData GetGraphicsData() const override;
+
+#if ENABLE_CAIRO_CANVAS
+ virtual bool SupportsCairo() const override;
+ virtual cairo::SurfaceSharedPtr CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const override;
+ virtual cairo::SurfaceSharedPtr CreateSurface(const OutputDevice& rRefDevice, int x, int y, int width, int height) const override;
+ virtual cairo::SurfaceSharedPtr CreateBitmapSurface(const OutputDevice& rRefDevice, const BitmapSystemData& rData, const Size& rSize) const override;
+ virtual css::uno::Any GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& rSurface, const basegfx::B2ISize& rSize) const override;
+#endif // ENABLE_CAIRO_CANVAS
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gensys.h b/vcl/inc/unx/gensys.h
new file mode 100644
index 0000000000..ab240f05ec
--- /dev/null
+++ b/vcl/inc/unx/gensys.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <salsys.hxx>
+#include <vector>
+
+/*
+ * Helps de-tangle the rather horrible ShowNativeMessageBox API
+ */
+class VCL_DLLPUBLIC SalGenericSystem : public SalSystem
+{
+ public:
+ SalGenericSystem();
+ virtual ~SalGenericSystem() override;
+ virtual int ShowNativeDialog( const OUString& rTitle,
+ const OUString& rMessage,
+ const std::vector< OUString >& rButtons ) = 0;
+
+ virtual int ShowNativeMessageBox( const OUString& rTitle,
+ const OUString& rMessage) override;
+
+#if !defined(ANDROID) && !defined(IOS)
+ // Simple helpers for X11 WM_CLASS hints
+ static const char *getFrameResName();
+ static const char *getFrameClassName();
+#endif
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/glyphcache.hxx b/vcl/inc/unx/glyphcache.hxx
new file mode 100644
index 0000000000..122e880228
--- /dev/null
+++ b/vcl/inc/unx/glyphcache.hxx
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <memory>
+#include <freetype/config/ftheader.h>
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+
+#include <vcl/dllapi.h>
+#include <vcl/outdev.hxx>
+
+#include <fontattributes.hxx>
+#include <font/FontMetricData.hxx>
+#include <glyphid.hxx>
+
+#include <unordered_map>
+
+class FreetypeFont;
+class FreetypeFontFile;
+class FreetypeFontInstance;
+class FreetypeFontInfo;
+class FontConfigFontOptions;
+namespace vcl::font
+{
+class PhysicalFontCollection;
+}
+class FreetypeFont;
+class SvpGcpHelper;
+
+namespace basegfx { class B2DPolyPolygon; }
+namespace vcl { struct FontCapabilities; }
+
+ /**
+ * The FreetypeManager caches various aspects of Freetype fonts
+ *
+ * It mainly consists of two std::unordered_map lists, which hold the items of the cache.
+ *
+ * They form kind of a tree, with FreetypeFontFile as the roots, referenced by multiple FreetypeFontInfo
+ * entries, which are referenced by the FreetypeFont items.
+ *
+ * All of these items have reference counters, but these don't control the items life-cycle, but that of
+ * the managed resources.
+ *
+ * The respective resources are:
+ * FreetypeFontFile = holds the mmapped font file, as long as it's used by any FreetypeFontInfo.
+ * FreetypeFontInfo = holds the FT_FaceRec_ object, as long as it's used by any FreetypeFont.
+ * FreetypeFont = holds the FT_SizeRec_ and is owned by a FreetypeFontInstance
+ *
+ * FreetypeFontInfo therefore is embedded in the Freetype subclass of PhysicalFontFace.
+ * FreetypeFont is owned by FreetypeFontInstance, the Freetype subclass of LogicalFontInstance.
+ *
+ * Nowadays there is not really a reason to have separate files for the classes, as the FreetypeManager
+ * is just about handling of Freetype based fonts, not some abstract glyphs.
+ **/
+class VCL_DLLPUBLIC FreetypeManager final
+{
+public:
+ ~FreetypeManager();
+
+ static FreetypeManager& get();
+
+ void AddFontFile(const OString& rNormalizedName,
+ int nFaceNum, int nVariantNum,
+ sal_IntPtr nFontId,
+ const FontAttributes&);
+
+ void AnnounceFonts( vcl::font::PhysicalFontCollection* ) const;
+
+ void ClearFontCache();
+
+ FreetypeFont* CreateFont(FreetypeFontInstance* pLogicalFont);
+
+private:
+ // to access the constructor (can't use InitFreetypeManager function, because it's private?!)
+ friend class GenericUnixSalData;
+ explicit FreetypeManager();
+
+ static void InitFreetype();
+ FreetypeFontFile* FindFontFile(const OString& rNativeFileName);
+
+ typedef std::unordered_map<sal_IntPtr, std::shared_ptr<FreetypeFontInfo>> FontInfoList;
+ typedef std::unordered_map<const char*, std::unique_ptr<FreetypeFontFile>, rtl::CStringHash, rtl::CStringEqual> FontFileList;
+
+ FontInfoList m_aFontInfoList;
+
+ FontFileList m_aFontFileList;
+};
+
+class VCL_DLLPUBLIC FreetypeFont final
+{
+public:
+ ~FreetypeFont();
+
+ const OString& GetFontFileName() const;
+ int GetFontFaceIndex() const;
+ int GetFontFaceVariation() const;
+ bool TestFont() const { return mbFaceOk;}
+ FT_Face GetFtFace() const;
+ const FontConfigFontOptions* GetFontOptions() const;
+
+ void GetFontMetric(FontMetricDataRef const &) const;
+
+ bool GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const;
+ bool GetAntialiasAdvice() const;
+
+private:
+ friend class FreetypeFontInstance;
+ friend class FreetypeManager;
+
+ explicit FreetypeFont(FreetypeFontInstance&, std::shared_ptr<FreetypeFontInfo> rFontInfo);
+
+ void ApplyGlyphTransform(bool bVertical, FT_Glyph) const;
+
+ FreetypeFontInstance& mrFontInstance;
+
+ // 16.16 fixed point values used for a rotated font
+ tools::Long mnCos;
+ tools::Long mnSin;
+
+ int mnWidth;
+ int mnPrioAntiAlias;
+ std::shared_ptr<FreetypeFontInfo> mxFontInfo;
+ double mfStretch;
+ FT_FaceRec_* maFaceFT;
+ FT_SizeRec_* maSizeFT;
+
+ mutable std::unique_ptr<FontConfigFontOptions> mxFontOptions;
+
+ bool mbFaceOk;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gstsink.hxx b/vcl/inc/unx/gstsink.hxx
new file mode 100644
index 0000000000..2dff94b02c
--- /dev/null
+++ b/vcl/inc/unx/gstsink.hxx
@@ -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/.
+ *
+ */
+
+#pragma once
+
+#include <config_vclplug.h>
+
+#if ENABLE_GSTREAMER_1_0
+#include <gst/gst.h>
+#include <dlfcn.h>
+
+typedef GstElement* (*GstElementFactoryName)(const gchar*, const gchar*);
+
+static GstElementFactoryName gstElementFactoryNameSymbol()
+{
+ return reinterpret_cast<GstElementFactoryName>(dlsym(nullptr, "gst_element_factory_make"));
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gtk/atkbridge.hxx b/vcl/inc/unx/gtk/atkbridge.hxx
new file mode 100644
index 0000000000..e77a9ab571
--- /dev/null
+++ b/vcl/inc/unx/gtk/atkbridge.hxx
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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
+
+bool InitAtkBridge();
+void DeInitAtkBridge();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gtk/gloactiongroup.h b/vcl/inc/unx/gtk/gloactiongroup.h
new file mode 100644
index 0000000000..fb84122a60
--- /dev/null
+++ b/vcl/inc/unx/gtk/gloactiongroup.h
@@ -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/.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_LO_ACTION_GROUP (g_lo_action_group_get_type ())
+#define G_LO_ACTION_GROUP(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
+ G_TYPE_LO_ACTION_GROUP, GLOActionGroup))
+#define G_IS_LO_ACTION_GROUP(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
+ G_TYPE_LO_ACTION_GROUP))
+
+struct GLOActionGroupPrivate;
+
+struct GLOActionGroup
+{
+ /*< private >*/
+ GObject parent_instance;
+
+ GLOActionGroupPrivate *priv;
+};
+
+struct GLOActionGroupClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+
+ /*< private >*/
+ gpointer padding[12];
+};
+
+GType g_lo_action_group_get_type (void) G_GNUC_CONST;
+
+GLOActionGroup * g_lo_action_group_new (void);
+
+void g_lo_action_group_set_top_menu (GLOActionGroup *group,
+ gpointer top_menu);
+
+void g_lo_action_group_insert (GLOActionGroup *group,
+ const gchar *action_name,
+ gint item_id,
+ gboolean submenu);
+
+void g_lo_action_group_insert_stateful (GLOActionGroup *group,
+ const gchar *action_name,
+ gint item_id,
+ gboolean submenu,
+ const GVariantType *parameter_type,
+ const GVariantType *state_type,
+ GVariant *state_hint,
+ GVariant *state);
+
+void g_lo_action_group_set_action_enabled (GLOActionGroup *group,
+ const gchar *action_name,
+ gboolean enabled);
+
+void g_lo_action_group_remove (GLOActionGroup *group,
+ const gchar *action_name);
+
+void g_lo_action_group_clear (GLOActionGroup *group);
+
+G_END_DECLS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gtk/glomenu.h b/vcl/inc/unx/gtk/glomenu.h
new file mode 100644
index 0000000000..da41e9e4b8
--- /dev/null
+++ b/vcl/inc/unx/gtk/glomenu.h
@@ -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/.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+#define G_LO_MENU_ATTRIBUTE_ACCELERATOR "accel"
+#define G_LO_MENU_ATTRIBUTE_COMMAND "command"
+#define G_LO_MENU_ATTRIBUTE_SUBMENU_ACTION "submenu-action"
+
+G_BEGIN_DECLS
+
+#define G_TYPE_LO_MENU (g_lo_menu_get_type ())
+#define G_LO_MENU(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
+ G_TYPE_LO_MENU, GLOMenu))
+#define G_IS_LO_MENU(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
+ G_TYPE_LO_MENU))
+
+struct GLOMenu;
+
+class GtkSalMenuItem;
+
+GLIB_AVAILABLE_IN_2_32
+GType g_lo_menu_get_type (void) G_GNUC_CONST;
+GLIB_AVAILABLE_IN_2_32
+GLOMenu * g_lo_menu_new (void);
+
+gint g_lo_menu_get_n_items_from_section (GLOMenu *menu,
+ gint section);
+
+void g_lo_menu_insert (GLOMenu *menu,
+ gint position,
+ const gchar *label);
+
+void g_lo_menu_insert_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *label);
+
+void g_lo_menu_new_section (GLOMenu *menu,
+ gint position,
+ const gchar *label);
+
+void g_lo_menu_insert_section (GLOMenu *menu,
+ gint position,
+ const gchar *label,
+ GMenuModel *section);
+
+GLOMenu * g_lo_menu_get_section (GLOMenu *menu,
+ gint section);
+
+void g_lo_menu_remove (GLOMenu *menu,
+ gint position);
+
+void g_lo_menu_remove_from_section (GLOMenu *menu,
+ gint section,
+ gint position);
+
+void g_lo_menu_set_label (GLOMenu *menu,
+ gint position,
+ const gchar *label);
+
+void g_lo_menu_set_icon (GLOMenu *menu,
+ gint position,
+ const GIcon *icon);
+
+
+void g_lo_menu_set_label_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *label);
+
+void g_lo_menu_set_icon_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const GIcon *icon);
+
+gchar * g_lo_menu_get_label_from_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position);
+
+void g_lo_menu_set_action_and_target_value (GLOMenu *menu,
+ gint position,
+ const gchar *command,
+ GVariant *target_value);
+
+void g_lo_menu_set_action_and_target_value_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *command,
+ GVariant *target_value);
+
+void g_lo_menu_set_command_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *command);
+
+gchar * g_lo_menu_get_command_from_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position);
+
+void g_lo_menu_set_accelerator_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *accelerator);
+
+gchar * g_lo_menu_get_accelerator_from_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position);
+
+void g_lo_menu_new_submenu_in_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position);
+
+GLOMenu * g_lo_menu_get_submenu_from_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position);
+
+void g_lo_menu_set_submenu_action_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *action);
+
+G_END_DECLS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gtk/gtkbackend.hxx b/vcl/inc/unx/gtk/gtkbackend.hxx
new file mode 100644
index 0000000000..1317ad6f72
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtkbackend.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/.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#if defined(GDK_WINDOWING_X11)
+#if GTK_CHECK_VERSION(4, 0, 0)
+#include <gdk/x11/gdkx.h>
+#else
+#include <gdk/gdkx.h>
+#endif
+bool DLSYM_GDK_IS_X11_DISPLAY(GdkDisplay* pDisplay);
+#endif
+#if defined(GDK_WINDOWING_WAYLAND)
+#if GTK_CHECK_VERSION(4, 0, 0)
+#include <gdk/wayland/gdkwayland.h>
+#else
+#include <gdk/gdkwayland.h>
+#endif
+bool DLSYM_GDK_IS_WAYLAND_DISPLAY(GdkDisplay* pDisplay);
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gtk/gtkdata.hxx b/vcl/inc/unx/gtk/gtkdata.hxx
new file mode 100644
index 0000000000..704490e821
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtkdata.hxx
@@ -0,0 +1,367 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 GLIB_DISABLE_DEPRECATION_WARNINGS
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#if GTK_CHECK_VERSION(4,0,0)
+#include <gdk/x11/gdkx.h>
+#else
+#include <gdk/gdkx.h>
+#endif
+
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/XAccessibleEventListener.hpp>
+#include <unx/gendata.hxx>
+#include <unx/saldisp.hxx>
+#include <unx/gtk/gtksys.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <osl/conditn.hxx>
+#include <saltimer.hxx>
+#include <o3tl/enumarray.hxx>
+#include <unotools/weakref.hxx>
+
+#include <exception>
+#include <string_view>
+#include <vector>
+
+namespace com::sun::star::accessibility { class XAccessibleEventListener; }
+
+class GtkSalDisplay;
+class DocumentFocusListener;
+
+#if !GTK_CHECK_VERSION(4,0,0)
+typedef GdkWindow GdkSurface;
+typedef GdkWindowState GdkToplevelState;
+#endif
+
+inline void main_loop_run(GMainLoop* pLoop)
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gdk_threads_leave();
+#endif
+ g_main_loop_run(pLoop);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gdk_threads_enter();
+#endif
+}
+
+inline void css_provider_load_from_data(GtkCssProvider *css_provider,
+ const gchar *data,
+ gssize length)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_css_provider_load_from_data(css_provider, data, length);
+#else
+ gtk_css_provider_load_from_data(css_provider, data, length, nullptr);
+#endif
+}
+
+inline GtkWidget* widget_get_toplevel(GtkWidget* pWidget)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkRoot* pRoot = gtk_widget_get_root(pWidget);
+ return pRoot ? GTK_WIDGET(pRoot) : pWidget;
+#else
+ return gtk_widget_get_toplevel(pWidget);
+#endif
+}
+
+inline const char* image_get_icon_name(GtkImage *pImage)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_image_get_icon_name(pImage);
+#else
+ const gchar* icon_name;
+ gtk_image_get_icon_name(pImage, &icon_name, nullptr);
+ return icon_name;
+#endif
+}
+
+inline GtkWidget* widget_get_first_child(GtkWidget *pWidget)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_widget_get_first_child(pWidget);
+#else
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pWidget));
+ GList* pChild = g_list_first(pChildren);
+ GtkWidget* pRet = pChild ? static_cast<GtkWidget*>(pChild->data) : nullptr;
+ g_list_free(pChildren);
+ return pRet;
+#endif
+}
+
+inline void style_context_get_color(GtkStyleContext *pStyle, GdkRGBA *pColor)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_style_context_get_color(pStyle, pColor);
+#else
+ return gtk_style_context_get_color(pStyle, gtk_style_context_get_state(pStyle), pColor);
+#endif
+}
+
+inline GdkSurface* widget_get_surface(GtkWidget* pWidget)
+{
+#if GTK_CHECK_VERSION(4,0,0)
+ return gtk_native_get_surface(gtk_widget_get_native(pWidget));
+#else
+ return gtk_widget_get_window(pWidget);
+#endif
+}
+
+inline void widget_set_cursor(GtkWidget *pWidget, GdkCursor *pCursor)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_cursor(pWidget, pCursor);
+#else
+ gdk_window_set_cursor(gtk_widget_get_window(pWidget), pCursor);
+#endif
+}
+
+inline cairo_surface_t * surface_create_similar_surface(GdkSurface *pSurface,
+ cairo_content_t eContent,
+ int nWidth,
+ int nHeight)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gdk_surface_create_similar_surface(pSurface, eContent, nWidth, nHeight);
+#else
+ return gdk_window_create_similar_surface(pSurface, eContent, nWidth, nHeight);
+#endif
+}
+
+inline void im_context_set_client_widget(GtkIMContext *pIMContext, GtkWidget *pWidget)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_im_context_set_client_widget(pIMContext, pWidget);
+#else
+ gtk_im_context_set_client_window(pIMContext, pWidget ? gtk_widget_get_window(pWidget) : nullptr);
+#endif
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+typedef double gtk_coord;
+#else
+typedef int gtk_coord;
+#endif
+
+inline bool surface_get_device_position(GdkSurface* pSurface,
+ GdkDevice* pDevice,
+ double& x,
+ double& y,
+ GdkModifierType* pMask)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gdk_surface_get_device_position(pSurface, pDevice,
+ &x, &y,
+ pMask);
+#else
+ int nX(x), nY(y);
+ bool bRet = gdk_window_get_device_position(pSurface, pDevice,
+ &nX, &nY,
+ pMask);
+ x = nX;
+ y = nY;
+ return bRet;
+#endif
+}
+
+inline GdkGLContext* surface_create_gl_context(GdkSurface* pSurface)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gdk_surface_create_gl_context(pSurface, nullptr);
+#else
+ return gdk_window_create_gl_context(pSurface, nullptr);
+#endif
+}
+
+void set_buildable_id(GtkBuildable* pWidget, const OUString& rId);
+OUString get_buildable_id(GtkBuildable* pWidget);
+
+void container_remove(GtkWidget* pContainer, GtkWidget* pChild);
+void container_add(GtkWidget* pContainer, GtkWidget* pChild);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+typedef GtkClipboard GdkClipboard;
+#endif
+
+int getButtonPriority(std::u16string_view rType);
+
+class GtkSalTimer final : public SalTimer
+{
+ struct SalGtkTimeoutSource *m_pTimeout;
+public:
+ GtkSalTimer();
+ virtual ~GtkSalTimer() override;
+ virtual void Start( sal_uInt64 nMS ) override;
+ virtual void Stop() override;
+ bool Expired();
+
+ sal_uLong m_nTimeoutMS;
+};
+
+class DocumentFocusListener final :
+ public ::cppu::WeakImplHelper< css::accessibility::XAccessibleEventListener >
+{
+
+ o3tl::sorted_vector< css::uno::Reference< css::uno::XInterface > > m_aRefList;
+
+public:
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void attachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessible >& xAccessible
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void attachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessible >& xAccessible,
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& xContext
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void attachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessible >& xAccessible,
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& xContext,
+ sal_Int64 nStateSet
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void detachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessible >& xAccessible
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void detachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& xContext
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void detachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& xContext,
+ sal_Int64 nStateSet
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ static css::uno::Reference< css::accessibility::XAccessible > getAccessible(const css::lang::EventObject& aEvent );
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+
+ // XAccessibleEventListener
+ virtual void SAL_CALL notifyEvent( const css::accessibility::AccessibleEventObject& aEvent ) override;
+};
+
+class GtkSalData final : public GenericUnixSalData
+{
+ GSource* m_pUserEvent;
+ osl::Mutex m_aDispatchMutex;
+ osl::Condition m_aDispatchCondition;
+ std::exception_ptr m_aException;
+
+ unotools::WeakReference<DocumentFocusListener> m_xDocumentFocusListener;
+
+public:
+ GtkSalData();
+ virtual ~GtkSalData() override;
+
+ rtl::Reference<DocumentFocusListener> GetDocumentFocusListener();
+
+ void Init();
+ virtual void Dispose() override;
+
+ static void initNWF();
+ static void deInitNWF();
+
+ void TriggerUserEventProcessing();
+ void TriggerAllUserEventsProcessed();
+
+ bool Yield( bool bWait, bool bHandleAllCurrentEvents );
+ inline GdkDisplay *GetGdkDisplay();
+
+ virtual void ErrorTrapPush() override;
+ virtual bool ErrorTrapPop( bool bIgnoreError = true ) override;
+
+ inline GtkSalDisplay *GetGtkDisplay() const;
+ void setException(const std::exception_ptr& exception) { m_aException = exception; }
+};
+
+class GtkSalFrame;
+
+class GtkSalDisplay final : public SalGenericDisplay
+{
+ GtkSalSystem* m_pSys;
+ GdkDisplay* m_pGdkDisplay;
+ o3tl::enumarray<PointerStyle, GdkCursor*> m_aCursors;
+ bool m_bStartupCompleted;
+
+ GdkCursor* getFromSvg( OUString const & name, int nXHot, int nYHot );
+
+public:
+ GtkSalDisplay( GdkDisplay* pDisplay );
+ virtual ~GtkSalDisplay() override;
+
+ GdkDisplay* GetGdkDisplay() const { return m_pGdkDisplay; }
+
+ GtkSalSystem* getSystem() const { return m_pSys; }
+
+ GtkWidget* findGtkWidgetForNativeHandle(sal_uIntPtr hWindow) const;
+
+ virtual void deregisterFrame( SalFrame* pFrame ) override;
+ GdkCursor *getCursor( PointerStyle ePointerStyle );
+ virtual int CaptureMouse( SalFrame* pFrame );
+
+ SalX11Screen GetDefaultXScreen() { return m_pSys->GetDisplayDefaultXScreen(); }
+ AbsoluteScreenPixelSize GetScreenSize( int nDisplayScreen );
+
+ void startupNotificationCompleted() { m_bStartupCompleted = true; }
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ void screenSizeChanged( GdkScreen const * );
+ void monitorsChanged( GdkScreen const * );
+#endif
+
+ virtual void TriggerUserEventProcessing() override;
+ virtual void TriggerAllUserEventsProcessed() override;
+};
+
+inline GtkSalData* GetGtkSalData()
+{
+ return static_cast<GtkSalData*>(ImplGetSVData()->mpSalData);
+}
+inline GdkDisplay *GtkSalData::GetGdkDisplay()
+{
+ return GetGtkDisplay()->GetGdkDisplay();
+}
+
+GtkSalDisplay *GtkSalData::GetGtkDisplay() const
+{
+ return static_cast<GtkSalDisplay *>(GetDisplay());
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gtk/gtkframe.hxx b/vcl/inc/unx/gtk/gtkframe.hxx
new file mode 100644
index 0000000000..5fbf441310
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtkframe.hxx
@@ -0,0 +1,694 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <cairo.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#if !GTK_CHECK_VERSION(4,0,0)
+#include <gtk/gtkx.h>
+#endif
+#include <gdk/gdkkeysyms.h>
+
+#include <salframe.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/sysdata.hxx>
+#include <unx/saltype.h>
+#include <unx/sessioninhibitor.hxx>
+
+#include <tools/link.hxx>
+
+#include <com/sun/star/awt/XTopWindow.hpp>
+#include <com/sun/star/datatransfer/DataFlavor.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragSource.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+
+#include <list>
+#include <vector>
+
+#include <config_dbus.h>
+#include <config_gio.h>
+
+#include <headless/svpgdi.hxx>
+
+#include "gtkdata.hxx"
+
+class GtkSalGraphics;
+class GtkSalDisplay;
+
+typedef sal_uIntPtr GdkNativeWindow;
+class GtkInstDropTarget;
+class GtkInstDragSource;
+class GtkDnDTransferable;
+
+class GtkSalMenu;
+
+struct VclToGtkHelper;
+
+class GtkSalFrame final : public SalFrame
+{
+ struct IMHandler
+ {
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // Not all GTK Input Methods swallow key release
+ // events. Since they swallow the key press events and we
+ // are left with the key release events, we need to
+ // manually swallow those. To do this, we keep a list of
+ // the previous 10 key press events in each GtkSalFrame
+ // and when we get a key release that matches one of the
+ // key press events in our list, we swallow it.
+ struct PreviousKeyPress
+ {
+ GdkWindow *window;
+ gint8 send_event;
+ guint32 time;
+ guint state;
+ guint keyval;
+ guint16 hardware_keycode;
+ guint8 group;
+
+ PreviousKeyPress (GdkEventKey *event)
+ : window (nullptr),
+ send_event (0),
+ time (0),
+ state (0),
+ keyval (0),
+ hardware_keycode (0),
+ group (0)
+ {
+ if (event)
+ {
+ window = event->window;
+ send_event = event->send_event;
+ time = event->time;
+ state = event->state;
+ keyval = event->keyval;
+ hardware_keycode = event->hardware_keycode;
+ group = event->group;
+ }
+ }
+
+ PreviousKeyPress( const PreviousKeyPress& rPrev )
+ : window( rPrev.window ),
+ send_event( rPrev.send_event ),
+ time( rPrev.time ),
+ state( rPrev.state ),
+ keyval( rPrev.keyval ),
+ hardware_keycode( rPrev.hardware_keycode ),
+ group( rPrev.group )
+ {}
+
+ bool operator== (GdkEventKey const *event) const
+ {
+ return (event != nullptr)
+ && (event->window == window)
+ && (event->send_event == send_event)
+ // ignore non-Gdk state bits, e.g., these used by IBus
+ && ((event->state & GDK_MODIFIER_MASK) == (state & GDK_MODIFIER_MASK))
+ && (event->keyval == keyval)
+ && (event->hardware_keycode == hardware_keycode)
+ && (event->group == group)
+ && (event->time - time < 300)
+ ;
+ }
+ };
+#endif
+
+ GtkSalFrame* m_pFrame;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ std::list< PreviousKeyPress > m_aPrevKeyPresses;
+#endif
+ int m_nPrevKeyPresses; // avoid using size()
+ GtkIMContext* m_pIMContext;
+ bool m_bFocused;
+ bool m_bPreeditJustChanged;
+ SalExtTextInputEvent m_aInputEvent;
+ std::vector< ExtTextInputAttr > m_aInputFlags;
+
+ IMHandler( GtkSalFrame* );
+ ~IMHandler();
+
+ void createIMContext();
+ void deleteIMContext();
+ void updateIMSpotLocation();
+ void endExtTextInput( EndExtTextInputFlags nFlags );
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool handleKeyEvent( GdkEventKey* pEvent );
+#endif
+ void focusChanged( bool bFocusIn );
+
+ void doCallEndExtTextInput();
+ void sendEmptyCommit();
+
+ static void signalIMCommit( GtkIMContext*, gchar*, gpointer );
+ static gboolean signalIMDeleteSurrounding( GtkIMContext*, gint, gint, gpointer );
+ static void signalIMPreeditChanged( GtkIMContext*, gpointer );
+ static void signalIMPreeditEnd( GtkIMContext*, gpointer );
+ static void signalIMPreeditStart( GtkIMContext*, gpointer );
+ static gboolean signalIMRetrieveSurrounding( GtkIMContext*, gpointer );
+ };
+ friend struct IMHandler;
+
+ friend class GtkSalObjectWidgetClip;
+
+ SalX11Screen m_nXScreen;
+ GtkWidget* m_pWindow;
+ GtkHeaderBar* m_pHeaderBar;
+ GtkGrid* m_pTopLevelGrid;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkEventBox* m_pEventBox;
+ GtkFixed* m_pFixedContainer;
+ GtkFixed* m_pDrawingArea;
+#else
+ GtkOverlay* m_pOverlay;
+ GtkFixed* m_pFixedContainer;
+ GtkDrawingArea* m_pDrawingArea;
+ GtkEventControllerKey* m_pKeyController;
+ gulong m_nSettingChangedSignalId;
+#endif
+ gulong m_nPortalSettingChangedSignalId;
+ GDBusProxy* m_pSettingsPortal;
+ gulong m_nSessionClientSignalId;
+ GDBusProxy* m_pSessionManager;
+ GDBusProxy* m_pSessionClient;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkWindow* m_pForeignParent;
+ GdkNativeWindow m_aForeignParentWindow;
+ GdkWindow* m_pForeignTopLevel;
+ GdkNativeWindow m_aForeignTopLevelWindow;
+#endif
+ SalFrameStyleFlags m_nStyle;
+ GtkSalFrame* m_pParent;
+ std::list< GtkSalFrame* > m_aChildren;
+ GdkToplevelState m_nState;
+ SystemEnvData m_aSystemData;
+ std::unique_ptr<GtkSalGraphics> m_pGraphics;
+ bool m_bGraphics;
+ ModKeyFlags m_nKeyModifiers;
+ PointerStyle m_ePointerStyle;
+ SessionManagerInhibitor m_SessionManagerInhibitor;
+ gulong m_nSetFocusSignalId;
+ bool m_bFullscreen;
+ bool m_bDefaultPos;
+ bool m_bDefaultSize;
+ bool m_bTooltipBlocked;
+ OUString m_sWMClass;
+
+ std::unique_ptr<IMHandler> m_pIMHandler;
+
+ Size m_aMaxSize;
+ Size m_aMinSize;
+ tools::Rectangle m_aRestorePosSize;
+
+ OUString m_aTooltip;
+ tools::Rectangle m_aHelpArea;
+ tools::Rectangle m_aFloatRect;
+ FloatWinPopupFlags m_nFloatFlags;
+ bool m_bFloatPositioned;
+ tools::Long m_nWidthRequest;
+ tools::Long m_nHeightRequest;
+ cairo_region_t* m_pRegion;
+ GtkInstDropTarget* m_pDropTarget;
+ GtkInstDragSource* m_pDragSource;
+ bool m_bGeometryIsProvisional;
+ bool m_bIconSetWhileUnmapped;
+
+ GtkSalMenu* m_pSalMenu;
+
+#if ENABLE_DBUS && ENABLE_GIO
+ private:
+ friend void on_registrar_available (GDBusConnection*, const gchar*, const gchar*, gpointer);
+ friend void on_registrar_unavailable (GDBusConnection*, const gchar*, gpointer);
+#endif
+ guint m_nWatcherId;
+
+ void Init( SalFrame* pParent, SalFrameStyleFlags nStyle );
+ void Init( SystemParentData* pSysData );
+ void InitCommon();
+ void InvalidateGraphics();
+
+ // signals
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalButton( GtkWidget*, GdkEventButton*, gpointer );
+ static void signalStyleUpdated(GtkWidget*, gpointer);
+#else
+ static void signalStyleUpdated(GtkWidget*, const gchar* pSetting, gpointer);
+#endif
+ void DrawingAreaResized(GtkWidget* pWidget, int nWidth, int nHeight);
+ void DrawingAreaDraw(cairo_t *cr);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalDraw( GtkWidget*, cairo_t *cr, gpointer );
+ static void sizeAllocated(GtkWidget*, GdkRectangle *pAllocation, gpointer frame);
+#else
+ static void signalDraw(GtkDrawingArea*, cairo_t *cr, int width, int height, gpointer);
+ static void sizeAllocated(GtkWidget*, int nWidth, int nHeight, gpointer frame);
+#endif
+ static void signalRealize(GtkWidget*, gpointer frame);
+ static gboolean signalTooltipQuery(GtkWidget*, gint x, gint y,
+ gboolean keyboard_mode, GtkTooltip *tooltip,
+ gpointer frame);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static GdkDragAction signalDragMotion(GtkDropTargetAsync *dest, GdkDrop *drop, double x, double y, gpointer frame);
+ static void signalDragLeave(GtkDropTargetAsync *dest, GdkDrop *drop, gpointer frame);
+ static gboolean signalDragDrop(GtkDropTargetAsync* context, GdkDrop* drop, double x, double y, gpointer frame);
+
+ static void signalDragFailed(GdkDrag* drag, GdkDragCancelReason reason, gpointer frame);
+ static void signalDragDelete(GdkDrag* drag, gpointer frame);
+ static void signalDragEnd(GdkDrag* drag, gpointer frame);
+#else
+ static gboolean signalDragMotion(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
+ guint time, gpointer frame);
+ static gboolean signalDragDrop(GtkWidget* widget, GdkDragContext *context, gint x, gint y,
+ guint time, gpointer frame);
+ static void signalDragDropReceived(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
+ GtkSelectionData *data, guint ttype, guint time, gpointer frame);
+ static void signalDragLeave(GtkWidget *widget, GdkDragContext *context, guint time, gpointer frame);
+
+ static gboolean signalDragFailed(GtkWidget *widget, GdkDragContext *context, GtkDragResult result, gpointer frame);
+ static void signalDragDelete(GtkWidget *widget, GdkDragContext *context, gpointer frame);
+ static void signalDragEnd(GtkWidget *widget, GdkDragContext *context, gpointer frame);
+ static void signalDragDataGet(GtkWidget* widget, GdkDragContext* context, GtkSelectionData *data, guint info,
+ guint time, gpointer frame);
+
+#endif
+ static void gestureSwipe(GtkGestureSwipe* gesture, gdouble velocity_x, gdouble velocity_y, gpointer frame);
+ static void gestureLongPress(GtkGestureLongPress* gesture, gdouble x, gdouble y, gpointer frame);
+ bool DrawingAreaButton(SalEvent nEventType, int nEventX, int nEventY, int nButton, guint32 nTime, guint nState);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void gesturePressed(GtkGestureClick* gesture, int n_press, gdouble x, gdouble y, gpointer frame);
+ static void gestureReleased(GtkGestureClick* gesture, int n_press, gdouble x, gdouble y, gpointer frame);
+ void gestureButton(GtkGestureClick* gesture, SalEvent nEventType, gdouble x, gdouble y);
+#endif
+ void DrawingAreaFocusInOut(SalEvent nEventType);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalFocusEnter(GtkEventControllerFocus* pController, gpointer frame);
+ static void signalFocusLeave(GtkEventControllerFocus* pController, gpointer frame);
+#else
+ static gboolean signalFocus( GtkWidget*, GdkEventFocus*, gpointer );
+#endif
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void signalSetFocus(GtkWindow* pWindow, GtkWidget* pWidget, gpointer frame);
+#else
+ static void signalSetFocus(GtkWindow* pWindow, GParamSpec* pSpec, gpointer frame);
+#endif
+ void WindowMap();
+ void WindowUnmap();
+ bool WindowCloseRequest();
+ void DrawingAreaMotion(int nEventX, int nEventY, guint32 nTime, guint nState);
+ void DrawingAreaCrossing(SalEvent nEventType, int nEventX, int nEventY, guint32 nTime, guint nState);
+ void DrawingAreaScroll(double delta_x, double delta_y, int nEventX, int nEventY, guint32 nTime, guint nState);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ bool DrawingAreaKey(GtkEventControllerKey* pController, SalEvent nEventType, guint keyval, guint keycode, guint nState);
+
+ static void signalMap(GtkWidget*, gpointer);
+ static void signalUnmap(GtkWidget*, gpointer);
+
+ static gboolean signalDelete(GtkWidget*, gpointer);
+
+ static void signalMotion(GtkEventControllerMotion *controller, double x, double y, gpointer);
+
+ static gboolean signalScroll(GtkEventControllerScroll* pController, double delta_x, double delta_y, gpointer);
+
+ static void signalEnter(GtkEventControllerMotion *controller, double x, double y, gpointer);
+ static void signalLeave(GtkEventControllerMotion *controller, gpointer);
+
+ static gboolean signalKeyPressed(GtkEventControllerKey *controller, guint keyval, guint keycode, GdkModifierType state, gpointer);
+ static gboolean signalKeyReleased(GtkEventControllerKey *controller, guint keyval, guint keycode, GdkModifierType state, gpointer);
+
+ static void signalWindowState(GdkToplevel*, GParamSpec*, gpointer);
+#else
+ static gboolean signalMap( GtkWidget*, GdkEvent*, gpointer );
+ static gboolean signalUnmap( GtkWidget*, GdkEvent*, gpointer );
+
+ static gboolean signalDelete( GtkWidget*, GdkEvent*, gpointer );
+
+ static gboolean signalMotion( GtkWidget*, GdkEventMotion*, gpointer );
+
+ static gboolean signalScroll( GtkWidget*, GdkEvent*, gpointer );
+
+ static gboolean signalCrossing( GtkWidget*, GdkEventCrossing*, gpointer );
+
+ static gboolean signalKey( GtkWidget*, GdkEventKey*, gpointer );
+
+ static gboolean signalWindowState( GtkWidget*, GdkEvent*, gpointer );
+#endif
+
+ static bool signalZoomBegin(GtkGesture*, GdkEventSequence*, gpointer);
+ static bool signalZoomUpdate(GtkGesture*, GdkEventSequence*, gpointer);
+ static bool signalZoomEnd(GtkGesture*, GdkEventSequence*, gpointer);
+
+ static bool signalRotateBegin(GtkGesture*, GdkEventSequence*, gpointer);
+ static bool signalRotateUpdate(GtkGesture*, GdkEventSequence*, gpointer);
+ static bool signalRotateEnd(GtkGesture*, GdkEventSequence*, gpointer);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalConfigure( GtkWidget*, GdkEventConfigure*, gpointer );
+#endif
+ static void signalDestroy( GtkWidget*, gpointer );
+
+ void Center();
+ void SetDefaultSize();
+
+ bool doKeyCallback( guint state,
+ guint keyval,
+ guint16 hardware_keycode,
+ guint8 group,
+ sal_Unicode aOrigCode,
+ bool bDown,
+ bool bSendRelease
+ );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static GdkNativeWindow findTopLevelSystemWindow( GdkNativeWindow aWindow );
+#endif
+
+ static int m_nFloats;
+
+ bool isFloatGrabWindow() const
+ {
+ return
+ (m_nStyle & SalFrameStyleFlags::FLOAT) && // only a float can be floatgrab
+ !(m_nStyle & SalFrameStyleFlags::TOOLTIP) && // tool tips are not
+ !(m_nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION); // toolbars are also not
+ }
+
+ bool isChild( bool bPlug = true, bool bSysChild = true )
+ {
+ SalFrameStyleFlags nMask = SalFrameStyleFlags::NONE;
+ if( bPlug )
+ nMask |= SalFrameStyleFlags::PLUG;
+ if( bSysChild )
+ nMask |= SalFrameStyleFlags::SYSTEMCHILD;
+ return bool(m_nStyle & nMask);
+ }
+
+ //call gtk_window_resize
+ void window_resize(tools::Long nWidth, tools::Long nHeight);
+ //call gtk_widget_set_size_request
+ void widget_set_size_request(tools::Long nWidth, tools::Long nHeight);
+
+ void resizeWindow( tools::Long nWidth, tools::Long nHeight );
+ void moveWindow( tools::Long nX, tools::Long nY );
+
+ Size calcDefaultSize();
+
+ void setMinMaxSize();
+
+ void AllocateFrame();
+ void TriggerPaintEvent();
+
+ void updateWMClass();
+
+ enum class SetType { RetainSize, Fullscreen, UnFullscreen };
+
+ void SetScreen( unsigned int nNewScreen, SetType eType, tools::Rectangle const *pSize = nullptr );
+
+ void SetIcon(const char* pIcon);
+
+ bool HandleMenubarMnemonic(guint eState, guint nKeyval);
+
+ void ListenPortalSettings();
+
+ void ListenSessionManager();
+
+ void UpdateGeometryFromEvent(int x_root, int y_root, int nEventX, int nEventY);
+
+public:
+ cairo_surface_t* m_pSurface;
+ basegfx::B2IVector m_aFrameSize;
+ DamageHandler m_aDamageHandler;
+ std::vector<GdkEvent*> m_aPendingScrollEvents;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ Idle m_aSmoothScrollIdle;
+#endif
+ int m_nGrabLevel;
+ bool m_bSalObjectSetPosSize;
+ GtkSalFrame( SalFrame* pParent, SalFrameStyleFlags nStyle );
+ GtkSalFrame( SystemParentData* pSysData );
+
+ guint m_nMenuExportId;
+ guint m_nActionGroupExportId;
+ guint m_nHudAwarenessId;
+ std::vector<gulong> m_aMouseSignalIds;
+
+ void grabPointer(bool bGrab, bool bKeyboardAlso, bool bOwnerEvents);
+
+ static GtkSalDisplay* getDisplay();
+ static GdkDisplay* getGdkDisplay();
+ GtkWidget* getWindow() const { return m_pWindow; }
+ GtkFixed* getFixedContainer() const { return GTK_FIXED(m_pFixedContainer); }
+ GtkWidget* getMouseEventWidget() const;
+ GtkGrid* getTopLevelGridWidget() const { return m_pTopLevelGrid; }
+ const SalX11Screen& getXScreenNumber() const { return m_nXScreen; }
+ int GetDisplayScreen() const { return maGeometry.screen(); }
+ void updateScreenNumber();
+
+ cairo_t* getCairoContext() const;
+ void damaged(sal_Int32 nExtentsLeft, sal_Int32 nExtentsTop,
+ sal_Int32 nExtentsRight, sal_Int32 nExtentsBottom) const;
+
+ void registerDropTarget(GtkInstDropTarget* pDropTarget)
+ {
+ assert(!m_pDropTarget);
+ m_pDropTarget = pDropTarget;
+ }
+
+ void deregisterDropTarget(GtkInstDropTarget const * pDropTarget)
+ {
+ assert(m_pDropTarget == pDropTarget); (void)pDropTarget;
+ m_pDropTarget = nullptr;
+ }
+
+ void registerDragSource(GtkInstDragSource* pDragSource)
+ {
+ assert(!m_pDragSource);
+ m_pDragSource = pDragSource;
+ }
+
+ void deregisterDragSource(GtkInstDragSource const * pDragSource)
+ {
+ assert(m_pDragSource == pDragSource); (void)pDragSource;
+ m_pDragSource = nullptr;
+ }
+
+ void startDrag(const css::datatransfer::dnd::DragGestureEvent& rEvent,
+ const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
+ VclToGtkHelper& rConversionHelper,
+ GdkDragAction sourceActions);
+
+ void closePopup();
+
+ void addGrabLevel();
+ void removeGrabLevel();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ void nopaint_container_resize_children(GtkContainer*);
+
+ void LaunchAsyncScroll(GdkEvent const * pEvent);
+ DECL_LINK(AsyncScroll, Timer *, void);
+#endif
+
+ virtual ~GtkSalFrame() override;
+
+ // SalGraphics or NULL, but two Graphics for all SalFrames
+ // must be returned
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics( SalGraphics* pGraphics ) override;
+
+ // Event must be destroyed, when Frame is destroyed
+ // When Event is called, SalInstance::Yield() must be returned
+ virtual bool PostEvent(std::unique_ptr<ImplSVEvent> pData) override;
+
+ virtual void SetTitle( const OUString& rTitle ) override;
+ virtual void SetIcon( sal_uInt16 nIcon ) override;
+ virtual void SetMenu( SalMenu *pSalMenu ) override;
+ SalMenu* GetMenu();
+ void EnsureAppMenuWatch();
+
+ virtual void SetExtendedFrameStyle( SalExtStyle nExtStyle ) override;
+ // Before the window is visible, a resize event
+ // must be sent with the correct size
+ virtual void Show( bool bVisible, bool bNoActivate = false ) override;
+ // Set ClientSize and Center the Window to the desktop
+ // and send/post a resize message
+ virtual void SetMinClientSize( tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void SetMaxClientSize( tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags ) override;
+ virtual void GetClientSize( tools::Long& rWidth, tools::Long& rHeight ) override;
+ virtual void GetWorkArea( AbsoluteScreenPixelRectangle& rRect ) override;
+ virtual SalFrame* GetParent() const override;
+ virtual void SetWindowState(const vcl::WindowData*) override;
+ virtual bool GetWindowState(vcl::WindowData*) override;
+ virtual void ShowFullScreen( bool bFullScreen, sal_Int32 nDisplay ) override;
+ // Enable/Disable ScreenSaver, SystemAgents, ...
+ virtual void StartPresentation( bool bStart ) override;
+ // Show Window over all other Windows
+ virtual void SetAlwaysOnTop( bool bOnTop ) override;
+
+ // Window to top and grab focus
+ virtual void ToTop( SalFrameToTop nFlags ) override;
+
+ // this function can call with the same
+ // pointer style
+ virtual void SetPointer( PointerStyle ePointerStyle ) override;
+ virtual void CaptureMouse( bool bMouse ) override;
+ virtual void GrabFocus() override;
+ virtual void SetPointerPos( tools::Long nX, tools::Long nY ) override;
+
+ // flush output buffer
+ using SalFrame::Flush;
+ virtual void Flush() override;
+ // flush output buffer, wait till outstanding operations are done
+
+ virtual void SetInputContext( SalInputContext* pContext ) override;
+ virtual void EndExtTextInput( EndExtTextInputFlags nFlags ) override;
+
+ virtual OUString GetKeyName( sal_uInt16 nKeyCode ) override;
+ virtual bool MapUnicodeToKeyCode( sal_Unicode aUnicode, LanguageType aLangType, vcl::KeyCode& rKeyCode ) override;
+
+ // returns the input language used for the last key stroke
+ // may be LANGUAGE_DONTKNOW if not supported by the OS
+ virtual LanguageType GetInputLanguage() override;
+
+ virtual void UpdateSettings( AllSettings& rSettings ) override;
+
+ virtual void Beep() override;
+
+ // returns system data (most prominent: window handle)
+ virtual const SystemEnvData* GetSystemData() const override;
+
+ virtual void ResolveWindowHandle(SystemEnvData& rData) const override;
+
+ // get current modifier and button mask
+ virtual SalPointerState GetPointerState() override;
+
+ virtual KeyIndicatorState GetIndicatorState() override;
+
+ virtual void SimulateKeyPress( sal_uInt16 nKeyCode ) override;
+
+ // set new parent window
+ virtual void SetParent( SalFrame* pNewParent ) override;
+ // reparent window to act as a plugin; implementation
+ // may choose to use a new system window internally
+ // return false to indicate failure
+ virtual void SetPluginParent( SystemParentData* pNewParent ) override;
+
+ virtual void SetScreenNumber( unsigned int ) override;
+ virtual void SetApplicationID( const OUString &rWMClass ) override;
+
+ // shaped system windows
+ // set clip region to none (-> rectangular windows, normal state)
+ virtual void ResetClipRegion() override;
+ // start setting the clipregion consisting of nRects rectangles
+ virtual void BeginSetClipRegion( sal_uInt32 nRects ) override;
+ // add a rectangle to the clip region
+ virtual void UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+ // done setting up the clipregion
+ virtual void EndSetClipRegion() override;
+
+ virtual void PositionByToolkit(const tools::Rectangle& rRect, FloatWinPopupFlags nFlags) override;
+ virtual void SetModal(bool bModal) override;
+ virtual bool GetModal() const override;
+ void HideTooltip();
+ void BlockTooltip();
+ void UnblockTooltip();
+ virtual bool ShowTooltip(const OUString& rHelpText, const tools::Rectangle& rHelpArea) override;
+ virtual void* ShowPopover(const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea, QuickHelpFlags nFlags) override;
+ virtual bool UpdatePopover(void* nId, const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea) override;
+ virtual bool HidePopover(void* nId) override;
+ virtual weld::Window* GetFrameWeld() const override;
+ virtual void UpdateDarkMode() override;
+ virtual bool GetUseDarkMode() const override;
+ virtual bool GetUseReducedAnimation() const override;
+
+ static GtkSalFrame *getFromWindow( GtkWidget *pWindow );
+
+ static sal_uIntPtr GetNativeWindowHandle(GtkWidget *pWidget);
+
+ //Call the usual SalFrame Callback, but catch uno exceptions and delegate
+ //to GtkSalData to rethrow them after the gsignal is processed when its safe
+ //to do so again in our own code after the g_main_context_iteration call
+ //which triggers the gsignals.
+ bool CallCallbackExc(SalEvent nEvent, const void* pEvent) const;
+
+ // call gtk_widget_queue_draw on the drawing widget
+ void queue_draw();
+
+ static void KeyCodeToGdkKey(const vcl::KeyCode& rKeyCode,
+ guint* pGdkKeyCode, GdkModifierType *pGdkModifiers);
+
+ static guint32 GetLastInputEventTime();
+ static void UpdateLastInputEventTime(guint32 nUserInputTime);
+ static sal_uInt16 GetMouseModCode(guint nState);
+ static sal_uInt16 GetKeyCode(guint nKeyVal);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static guint GetKeyValFor(GdkKeymap* pKeyMap, guint16 hardware_keycode, guint8 group);
+#endif
+ static sal_uInt16 GetKeyModCode(guint nState);
+ static GdkEvent* makeFakeKeyPress(GtkWidget* pWidget);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static SalWheelMouseEvent GetWheelEvent(const GdkEventScroll& rEvent);
+ static gboolean NativeWidgetHelpPressed(GtkAccelGroup*, GObject*, guint,
+ GdkModifierType, gpointer pFrame);
+#endif
+ static OUString GetPreeditDetails(GtkIMContext* pIMContext, std::vector<ExtTextInputAttr>& rInputFlags, sal_Int32& rCursorPos, sal_uInt8& rCursorFlags);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gboolean event_controller_scroll_forward(GtkEventControllerScroll* pController, double delta_x, double delta_y);
+#endif
+
+ const cairo_font_options_t* get_font_options();
+
+ void SetColorScheme(GVariant* variant);
+
+ void SessionManagerInhibit(bool bStart, ApplicationInhibitFlags eType, std::u16string_view sReason, const char* application_id);
+
+ void DisallowCycleFocusOut();
+ bool IsCycleFocusOutDisallowed() const;
+ void AllowCycleFocusOut();
+};
+
+extern "C" {
+
+GType ooo_fixed_get_type();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+AtkObject* ooo_fixed_get_accessible(GtkWidget *obj);
+#endif
+
+} // extern "C"
+
+#if !GTK_CHECK_VERSION(3, 22, 0)
+enum GdkAnchorHints
+{
+ GDK_ANCHOR_FLIP_X = 1 << 0,
+ GDK_ANCHOR_FLIP_Y = 1 << 1,
+ GDK_ANCHOR_SLIDE_X = 1 << 2,
+ GDK_ANCHOR_SLIDE_Y = 1 << 3,
+ GDK_ANCHOR_RESIZE_X = 1 << 4,
+ GDK_ANCHOR_RESIZE_Y = 1 << 5,
+ GDK_ANCHOR_FLIP = GDK_ANCHOR_FLIP_X | GDK_ANCHOR_FLIP_Y,
+ GDK_ANCHOR_SLIDE = GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_SLIDE_Y,
+ GDK_ANCHOR_RESIZE = GDK_ANCHOR_RESIZE_X | GDK_ANCHOR_RESIZE_Y
+};
+#endif
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gtk/gtkgdi.hxx b/vcl/inc/unx/gtk/gtkgdi.hxx
new file mode 100644
index 0000000000..9d8bb26ce7
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtkgdi.hxx
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <config_cairo_canvas.h>
+
+#include <gtk/gtk.h>
+#include "gtkbackend.hxx"
+#include <gdk/gdkkeysyms.h>
+
+#include <unx/gtk/gtkframe.hxx>
+#include <ControlCacheKey.hxx>
+
+#include <headless/svpgdi.hxx>
+#include <textrender.hxx>
+
+enum class GtkControlPart
+{
+ ToplevelWindow,
+ Button,
+ LinkButton,
+ CheckButton,
+ CheckButtonCheck,
+ RadioButton,
+ RadioButtonRadio,
+ Entry,
+ Combobox,
+ ComboboxBox,
+ ComboboxBoxEntry,
+ ComboboxBoxButton,
+ ComboboxBoxButtonBox,
+ ComboboxBoxButtonBoxArrow,
+ Listbox,
+ ListboxBox,
+ ListboxBoxButton,
+ ListboxBoxButtonBox,
+ ListboxBoxButtonBoxArrow,
+ SpinButton,
+ SpinButtonUpButton,
+ SpinButtonDownButton,
+ ScrollbarVertical,
+ ScrollbarVerticalContents,
+ ScrollbarVerticalTrough,
+ ScrollbarVerticalSlider,
+ ScrollbarVerticalButton,
+ ScrollbarHorizontal,
+ ScrollbarHorizontalContents,
+ ScrollbarHorizontalTrough,
+ ScrollbarHorizontalSlider,
+ ScrollbarHorizontalButton,
+ ProgressBar,
+ ProgressBarTrough,
+ ProgressBarProgress,
+ Notebook,
+ NotebookHeader,
+ NotebookStack,
+ NotebookHeaderTabs,
+ NotebookHeaderTabsTab,
+ NotebookHeaderTabsTabLabel,
+ NotebookHeaderTabsTabActiveLabel,
+ NotebookHeaderTabsTabHoverLabel,
+ FrameBorder,
+ MenuBar,
+ MenuBarItem,
+ MenuWindow,
+ Menu,
+ MenuItem,
+ MenuItemLabel,
+ MenuItemArrow,
+ CheckMenuItem,
+ CheckMenuItemCheck,
+ RadioMenuItem,
+ RadioMenuItemRadio,
+ SeparatorMenuItem,
+ SeparatorMenuItemSeparator,
+};
+
+class GtkSalGraphics final : public SvpSalGraphics
+{
+ GtkSalFrame * const mpFrame;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool isNativeControlSupported(ControlType, ControlPart) override;
+ virtual bool drawNativeControl( ControlType nType, ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ ControlState nState, const ImplControlValue& aValue,
+ const OUString& rCaption,
+ const Color& rBackgroundColor ) override;
+ virtual bool getNativeControlRegion( ControlType nType, ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ ControlState nState,
+ const ImplControlValue& aValue,
+ const OUString& rCaption,
+ tools::Rectangle &rNativeBoundingRegion,
+ tools::Rectangle &rNativeContentRegion ) override;
+#endif
+ bool updateSettings(AllSettings&) override;
+ void handleDamage(const tools::Rectangle&) override;
+
+public:
+ GtkSalGraphics( GtkSalFrame *pFrame, GtkWidget *pWindow );
+
+#if ENABLE_CAIRO_CANVAS
+ virtual bool SupportsCairo() const override;
+ virtual cairo::SurfaceSharedPtr CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const override;
+ virtual cairo::SurfaceSharedPtr CreateSurface(const OutputDevice& rRefDevice, int x, int y, int width, int height) const override;
+#endif
+
+ void WidgetQueueDraw() const;
+
+ virtual void GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY) override;
+
+ virtual OUString getRenderBackendName() const override { return "gtk3svp"; }
+
+ GtkStyleContext* createStyleContext(GtkControlPart ePart);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkStyleContext* makeContext(GtkWidgetPath *pPath, GtkStyleContext *pParent);
+#endif
+private:
+ GtkWidget *mpWindow;
+ static GtkStyleContext *mpWindowStyle;
+ static GtkStyleContext *mpButtonStyle;
+ static GtkStyleContext *mpLinkButtonStyle;
+ static GtkStyleContext *mpEntryStyle;
+ static GtkStyleContext *mpTextViewStyle;
+ static GtkStyleContext *mpVScrollbarStyle;
+ static GtkStyleContext *mpVScrollbarContentsStyle;
+ static GtkStyleContext *mpVScrollbarTroughStyle;
+ static GtkStyleContext *mpVScrollbarSliderStyle;
+ static GtkStyleContext *mpVScrollbarButtonStyle;
+ static GtkStyleContext *mpHScrollbarStyle;
+ static GtkStyleContext *mpHScrollbarContentsStyle;
+ static GtkStyleContext *mpHScrollbarTroughStyle;
+ static GtkStyleContext *mpHScrollbarSliderStyle;
+ static GtkStyleContext *mpHScrollbarButtonStyle;
+ static GtkStyleContext *mpToolbarStyle;
+ static GtkStyleContext *mpToolButtonStyle;
+ static GtkStyleContext *mpToolbarSeparatorStyle;
+ static GtkStyleContext *mpCheckButtonStyle;
+ static GtkStyleContext *mpCheckButtonCheckStyle;
+ static GtkStyleContext *mpRadioButtonStyle;
+ static GtkStyleContext *mpRadioButtonRadioStyle;
+ static GtkStyleContext *mpSpinStyle;
+ static GtkStyleContext *mpSpinUpStyle;
+ static GtkStyleContext *mpSpinDownStyle;
+ static GtkStyleContext *mpComboboxStyle;
+ static GtkStyleContext *mpComboboxBoxStyle;
+ static GtkStyleContext *mpComboboxEntryStyle;
+ static GtkStyleContext *mpComboboxButtonStyle;
+ static GtkStyleContext *mpComboboxButtonBoxStyle;
+ static GtkStyleContext *mpComboboxButtonArrowStyle;
+ static GtkStyleContext *mpListboxStyle;
+ static GtkStyleContext *mpListboxBoxStyle;
+ static GtkStyleContext *mpListboxButtonStyle;
+ static GtkStyleContext *mpListboxButtonBoxStyle;
+ static GtkStyleContext *mpListboxButtonArrowStyle;
+ static GtkStyleContext *mpFrameInStyle;
+ static GtkStyleContext *mpFrameOutStyle;
+ static GtkStyleContext *mpFixedHoriLineStyle;
+ static GtkStyleContext *mpFixedVertLineStyle;
+ static GtkStyleContext *mpTreeHeaderButtonStyle;
+ static GtkStyleContext *mpProgressBarStyle;
+ static GtkStyleContext *mpProgressBarTroughStyle;
+ static GtkStyleContext *mpProgressBarProgressStyle;
+ static GtkStyleContext *mpNotebookStyle;
+ static GtkStyleContext *mpNotebookStackStyle;
+ static GtkStyleContext *mpNotebookHeaderStyle;
+ static GtkStyleContext *mpNotebookHeaderTabsStyle;
+ static GtkStyleContext *mpNotebookHeaderTabsTabStyle;
+ static GtkStyleContext *mpNotebookHeaderTabsTabLabelStyle;
+ static GtkStyleContext *mpNotebookHeaderTabsTabActiveLabelStyle;
+ static GtkStyleContext *mpNotebookHeaderTabsTabHoverLabelStyle;
+ static GtkStyleContext *mpMenuBarStyle;
+ static GtkStyleContext *mpMenuBarItemStyle;
+ static GtkStyleContext *mpMenuWindowStyle;
+ static GtkStyleContext *mpMenuStyle;
+ static GtkStyleContext *mpMenuItemStyle;
+ static GtkStyleContext *mpMenuItemLabelStyle;
+ static GtkStyleContext *mpMenuItemArrowStyle;
+ static GtkStyleContext *mpCheckMenuItemStyle;
+ static GtkStyleContext *mpCheckMenuItemCheckStyle;
+ static GtkStyleContext *mpRadioMenuItemStyle;
+ static GtkStyleContext *mpRadioMenuItemRadioStyle;
+ static GtkStyleContext *mpSeparatorMenuItemStyle;
+ static GtkStyleContext *mpSeparatorMenuItemSeparatorStyle;
+ static gint mnVerticalSeparatorMinWidth;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static tools::Rectangle NWGetScrollButtonRect( ControlPart nPart, tools::Rectangle aAreaRect );
+ static tools::Rectangle NWGetSpinButtonRect( ControlPart nPart, tools::Rectangle aAreaRect);
+ static tools::Rectangle NWGetComboBoxButtonRect(ControlType nType, ControlPart nPart, tools::Rectangle aAreaRect);
+
+ static void PaintScrollbar(GtkStyleContext *context,
+ cairo_t *cr,
+ const tools::Rectangle& rControlRectangle,
+ ControlPart nPart,
+ const ImplControlValue& aValue );
+ void PaintOneSpinButton( GtkStyleContext *context,
+ cairo_t *cr,
+ ControlPart nPart,
+ tools::Rectangle aAreaRect,
+ ControlState nState );
+ void PaintSpinButton(GtkStateFlags flags,
+ cairo_t *cr,
+ const tools::Rectangle& rControlRectangle,
+ ControlPart nPart,
+ const ImplControlValue& aValue);
+ static void PaintCombobox(GtkStateFlags flags,
+ cairo_t *cr,
+ const tools::Rectangle& rControlRectangle,
+ ControlType nType,
+ ControlPart nPart);
+ static void PaintCheckOrRadio(cairo_t *cr, GtkStyleContext *context,
+ const tools::Rectangle& rControlRectangle,
+ bool bIsCheck, bool bInMenu);
+
+ static void PaintCheck(cairo_t *cr, GtkStyleContext *context,
+ const tools::Rectangle& rControlRectangle, bool bInMenu);
+
+ static void PaintRadio(cairo_t *cr, GtkStyleContext *context,
+ const tools::Rectangle& rControlRectangle, bool bInMenu);
+#endif
+
+ static bool style_loaded;
+};
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gtk/gtkinst.hxx b/vcl/inc/unx/gtk/gtkinst.hxx
new file mode 100644
index 0000000000..1f9e328bb8
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtkinst.hxx
@@ -0,0 +1,349 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <stack>
+
+#include <unx/salinst.h>
+#include <unx/gensys.h>
+#include <headless/svpinst.hxx>
+#include <com/sun/star/datatransfer/DataFlavor.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragSource.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/awt/XWindow.hpp>
+#include <cppuhelper/compbase.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/weldutils.hxx>
+#include <gtk/gtk.h>
+
+vcl::Font pango_to_vcl(const PangoFontDescription* font, const css::lang::Locale& rLocale);
+
+class GenPspGraphics;
+class GtkYieldMutex final : public SalYieldMutex
+{
+ thread_local static std::stack<sal_uInt32> yieldCounts;
+
+public:
+ GtkYieldMutex() {}
+ void ThreadsEnter();
+ void ThreadsLeave();
+};
+
+class GtkSalFrame;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+gint gtk_dialog_run(GtkDialog *dialog);
+
+struct read_transfer_result
+{
+ enum { BlockSize = 8192 };
+ size_t nRead = 0;
+ bool bDone = false;
+
+ std::vector<sal_Int8> aVector;
+
+ static void read_block_async_completed(GObject* source, GAsyncResult* res, gpointer user_data);
+
+ OUString get_as_string() const;
+ css::uno::Sequence<sal_Int8> get_as_sequence() const;
+};
+
+#endif
+
+struct VclToGtkHelper
+{
+ std::vector<css::datatransfer::DataFlavor> aInfoToFlavor;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ std::vector<OString> FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats);
+#else
+ std::vector<GtkTargetEntry> FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats);
+#endif
+#if GTK_CHECK_VERSION(4, 0, 0)
+ void setSelectionData(const css::uno::Reference<css::datatransfer::XTransferable> &rTrans,
+ GdkContentProvider* provider,
+ const char* mime_type,
+ GOutputStream* stream,
+ int io_priority,
+ GCancellable* cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+#else
+ void setSelectionData(const css::uno::Reference<css::datatransfer::XTransferable> &rTrans,
+ GtkSelectionData *selection_data, guint info);
+#endif
+private:
+#if GTK_CHECK_VERSION(4, 0, 0)
+ OString makeGtkTargetEntry(const css::datatransfer::DataFlavor& rFlavor);
+#else
+ GtkTargetEntry makeGtkTargetEntry(const css::datatransfer::DataFlavor& rFlavor);
+#endif
+};
+
+class GtkTransferable : public cppu::WeakImplHelper<css::datatransfer::XTransferable>
+{
+protected:
+#if GTK_CHECK_VERSION(4, 0, 0)
+ std::map<OUString, OString> m_aMimeTypeToGtkType;
+#else
+ std::map<OUString, GdkAtom> m_aMimeTypeToGtkType;
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector(const char * const *targets, gint n_targets);
+#else
+ std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector(GdkAtom *targets, gint n_targets);
+#endif
+
+public:
+ virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override = 0;
+ virtual std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector() = 0;
+ virtual css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL getTransferDataFlavors() override;
+ virtual sal_Bool SAL_CALL isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor) override;
+};
+
+class GtkDnDTransferable;
+
+class GtkInstDropTarget final : public cppu::WeakComponentImplHelper<css::datatransfer::dnd::XDropTarget,
+ css::lang::XInitialization,
+ css::lang::XServiceInfo>
+{
+ osl::Mutex m_aMutex;
+ GtkSalFrame* m_pFrame;
+ GtkDnDTransferable* m_pFormatConversionRequest;
+ bool m_bActive;
+ bool m_bInDrag;
+ sal_Int8 m_nDefaultActions;
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> m_aListeners;
+public:
+ GtkInstDropTarget();
+ virtual ~GtkInstDropTarget() override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize(const css::uno::Sequence<css::uno::Any>& rArgs) override;
+ void deinitialize();
+
+ // XDropTarget
+ virtual void SAL_CALL addDropTargetListener(const css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>&) override;
+ virtual void SAL_CALL removeDropTargetListener(const css::uno::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;
+
+ sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override;
+
+ css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+ void fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtdee);
+ void fire_dragOver(const css::datatransfer::dnd::DropTargetDragEvent& dtde);
+ void fire_drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde);
+ void fire_dragExit(const css::datatransfer::dnd::DropTargetEvent& dte);
+
+ void SetFormatConversionRequest(GtkDnDTransferable *pRequest)
+ {
+ m_pFormatConversionRequest = pRequest;
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gboolean signalDragMotion(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time);
+ gboolean signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time);
+#else
+ GdkDragAction signalDragMotion(GtkDropTargetAsync *context, GdkDrop *drop, double x, double y);
+ gboolean signalDragDrop(GtkDropTargetAsync *context, GdkDrop *drop, double x, double y);
+#endif
+
+ void signalDragLeave(GtkWidget* pWidget);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ void signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time);
+#endif
+};
+
+class GtkInstDragSource final : public cppu::WeakComponentImplHelper<css::datatransfer::dnd::XDragSource,
+ css::lang::XInitialization,
+ css::lang::XServiceInfo>
+{
+ osl::Mutex m_aMutex;
+ GtkSalFrame* m_pFrame;
+ css::uno::Reference<css::datatransfer::dnd::XDragSourceListener> m_xListener;
+ css::uno::Reference<css::datatransfer::XTransferable> m_xTrans;
+ VclToGtkHelper m_aConversionHelper;
+public:
+ GtkInstDragSource()
+ : WeakComponentImplHelper(m_aMutex)
+ , m_pFrame(nullptr)
+ {
+ }
+
+ void set_datatransfer(const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
+ const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ std::vector<GtkTargetEntry> FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats);
+#endif
+
+ void setActiveDragSource();
+
+ virtual ~GtkInstDragSource() override;
+
+ // XDragSource
+ virtual sal_Bool SAL_CALL isDragImageSupported() override;
+ virtual sal_Int32 SAL_CALL getDefaultCursor(sal_Int8 dragAction) override;
+ virtual void SAL_CALL startDrag(
+ const css::datatransfer::dnd::DragGestureEvent& trigger, sal_Int8 sourceActions, sal_Int32 cursor, sal_Int32 image,
+ const css::uno::Reference< css::datatransfer::XTransferable >& transferable,
+ const css::uno::Reference< css::datatransfer::dnd::XDragSourceListener >& listener) override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize(const css::uno::Sequence<css::uno::Any >& rArguments) override;
+ void deinitialize();
+
+ OUString SAL_CALL getImplementationName() override;
+
+ sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override;
+
+ css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+ void dragFailed();
+ void dragDelete();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ void dragEnd(GdkDrag* drag);
+#else
+ void dragEnd(GdkDragContext* context);
+ void dragDataGet(GtkSelectionData *data, guint info);
+#endif
+
+ // For LibreOffice internal D&D we provide the Transferable without Gtk
+ // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
+ static GtkInstDragSource* g_ActiveDragSource;
+ css::uno::Reference<css::datatransfer::XTransferable> const & GetTransferable() const { return m_xTrans; }
+};
+
+enum SelectionType { SELECTION_CLIPBOARD = 0, SELECTION_PRIMARY = 1 };
+
+class GtkSalTimer;
+class GtkInstance final : public SvpSalInstance
+{
+public:
+ GtkInstance( std::unique_ptr<SalYieldMutex> pMutex );
+ virtual ~GtkInstance() override;
+ void EnsureInit();
+ virtual void AfterAppInit() override;
+
+ virtual SalFrame* CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle ) override;
+ virtual SalFrame* CreateChildFrame( SystemParentData* pParent, SalFrameStyleFlags nStyle ) override;
+ virtual SalObject* CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow ) override;
+ virtual SalSystem* CreateSalSystem() override;
+ virtual SalInfoPrinter* CreateInfoPrinter(SalPrinterQueueInfo* pPrinterQueueInfo, ImplJobSetup* pJobSetup) override;
+ virtual std::unique_ptr<SalPrinter> CreatePrinter( SalInfoPrinter* pInfoPrinter ) override;
+ virtual std::unique_ptr<SalMenu> CreateMenu( bool, Menu* ) override;
+ virtual std::unique_ptr<SalMenuItem> CreateMenuItem( const SalItemParams& ) override;
+ virtual SalTimer* CreateSalTimer() override;
+ virtual void AddToRecentDocumentList(const OUString& rFileUrl, const OUString& rMimeType, const OUString& rDocumentService) override;
+ virtual std::unique_ptr<SalVirtualDevice>
+ CreateVirtualDevice( SalGraphics&,
+ tools::Long &nDX, tools::Long &nDY,
+ DeviceFormat eFormat,
+ const SystemGraphicsData* = nullptr ) override;
+ virtual std::shared_ptr<SalBitmap> CreateSalBitmap() override;
+
+ virtual bool DoYield(bool bWait, bool bHandleAllCurrentEvents) override;
+ virtual bool AnyInput( VclInputFlags nType ) override;
+ // impossible to handle correctly, as "main thread" depends on the dispatch mutex
+ virtual bool IsMainThread() const override { return false; }
+
+ virtual std::unique_ptr<GenPspGraphics> CreatePrintGraphics() override;
+
+ virtual bool hasNativeFileSelection() const override { return true; }
+
+ virtual css::uno::Reference< css::ui::dialogs::XFilePicker2 >
+ createFilePicker( const css::uno::Reference< css::uno::XComponentContext >& ) override;
+ virtual css::uno::Reference< css::ui::dialogs::XFolderPicker2 >
+ createFolderPicker( const css::uno::Reference< css::uno::XComponentContext >& ) override;
+
+ virtual css::uno::Reference< css::uno::XInterface > CreateClipboard( const css::uno::Sequence< css::uno::Any >& i_rArguments ) override;
+ virtual css::uno::Reference<css::uno::XInterface> ImplCreateDragSource(const SystemEnvData*) override;
+ virtual css::uno::Reference<css::uno::XInterface> ImplCreateDropTarget(const SystemEnvData*) override;
+ virtual OpenGLContext* CreateOpenGLContext() override;
+ virtual std::unique_ptr<weld::Builder> CreateBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile) override;
+ virtual std::unique_ptr<weld::Builder> CreateInterimBuilder(vcl::Window* pParent, const OUString& rUIRoot, const OUString& rUIFile,
+ bool bAllowCycleFocusOut, sal_uInt64 nLOKWindowId = 0) override;
+ virtual weld::MessageDialog* CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType, VclButtonsType eButtonType, const OUString &rPrimaryMessage) override;
+ virtual weld::Window* GetFrameWeld(const css::uno::Reference<css::awt::XWindow>& rWindow) override;
+
+ virtual const cairo_font_options_t* GetCairoFontOptions() override;
+ const cairo_font_options_t* GetLastSeenCairoFontOptions() const;
+ void ResetLastSeenCairoFontOptions(const cairo_font_options_t* pOptions);
+
+ void RemoveTimer ();
+
+ void* CreateGStreamerSink(const SystemChildWindow*) override;
+
+private:
+ GtkSalTimer *m_pTimer;
+ css::uno::Reference<css::uno::XInterface> m_aClipboards[2];
+ bool IsTimerExpired();
+ bool bNeedsInit;
+ cairo_font_options_t* m_pLastCairoFontOptions;
+};
+
+inline GtkInstance* GetGtkInstance() { return static_cast<GtkInstance*>(GetSalInstance()); }
+
+class SalGtkXWindow final : public weld::TransportAsXWindow
+{
+private:
+ weld::Window* m_pWeldWidget;
+ GtkWidget* m_pWidget;
+public:
+
+ SalGtkXWindow(weld::Window* pWeldWidget, GtkWidget* pWidget)
+ : TransportAsXWindow(pWeldWidget)
+ , m_pWeldWidget(pWeldWidget)
+ , m_pWidget(pWidget)
+ {
+ }
+
+ virtual void clear() override
+ {
+ m_pWeldWidget = nullptr;
+ m_pWidget = nullptr;
+ TransportAsXWindow::clear();
+ }
+
+ GtkWidget* getGtkWidget() const
+ {
+ return m_pWidget;
+ }
+
+ weld::Window* getFrameWeld() const
+ {
+ return m_pWeldWidget;
+ }
+};
+
+GdkPixbuf* load_icon_by_name(const OUString& rIconName);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gtk/gtkobject.hxx b/vcl/inc/unx/gtk/gtkobject.hxx
new file mode 100644
index 0000000000..63544f56f8
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtkobject.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <tools/solar.h>
+#include <vcl/sysdata.hxx>
+#include <salobj.hxx>
+#include <unx/gtk/gtkframe.hxx>
+
+class GtkSalObjectBase : public SalObject
+{
+protected:
+ SystemEnvData m_aSystemData;
+ GtkWidget* m_pSocket;
+ GtkSalFrame* m_pParent;
+ cairo_region_t* m_pRegion;
+
+ void Init();
+
+public:
+ GtkSalObjectBase(GtkSalFrame* pParent);
+ virtual ~GtkSalObjectBase() override;
+
+ virtual void BeginSetClipRegion( sal_uInt32 nRects ) override;
+ virtual void UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+
+ virtual void SetForwardKey( bool bEnable ) override;
+
+ virtual const SystemEnvData* GetSystemData() const override;
+
+ virtual Size GetOptimalSize() const override;
+
+private:
+ // signals
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalButton( GtkWidget*, GdkEventButton*, gpointer );
+ static gboolean signalFocus( GtkWidget*, GdkEventFocus*, gpointer );
+#endif
+};
+
+// this attempts to clip the hosted native window using gdk_window_shape_combine_region
+class GtkSalObject final : public GtkSalObjectBase
+{
+ // signals
+ static void signalDestroy( GtkWidget*, gpointer );
+
+public:
+ GtkSalObject(GtkSalFrame* pParent, bool bShow);
+ virtual ~GtkSalObject() override;
+
+ // override all pure virtual methods
+ virtual void ResetClipRegion() override;
+ virtual void EndSetClipRegion() override;
+
+ virtual void SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void Show( bool bVisible ) override;
+ virtual void Reparent(SalFrame* pFrame) override;
+};
+
+// this attempts to clip the hosted native GtkWidget by using a GtkScrolledWindow as a viewport
+// only a rectangular area is going to work
+class GtkSalObjectWidgetClip final : public GtkSalObjectBase
+{
+ tools::Rectangle m_aRect;
+ tools::Rectangle m_aClipRect;
+ GtkWidget* m_pScrolledWindow;
+ GtkWidget* m_pViewPort;
+ GtkCssProvider* m_pBgCssProvider;
+
+ // signals
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalScroll(GtkWidget*, GdkEvent*, gpointer);
+#else
+ static gboolean signalScroll(GtkEventControllerScroll* pController, double delta_x, double delta_y, gpointer object);
+#endif
+ static void signalDestroy( GtkWidget*, gpointer );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool signal_scroll(GtkWidget* pScrolledWindow, GdkEvent* pEvent);
+#else
+ bool signal_scroll(GtkEventControllerScroll* pController, double delta_x, double delta_y);
+#endif
+
+ void ApplyClipRegion();
+
+ void SetViewPortBackground();
+
+ DECL_LINK(SettingsChangedHdl, VclWindowEvent&, void);
+
+public:
+ GtkSalObjectWidgetClip(GtkSalFrame* pParent, bool bShow);
+ virtual ~GtkSalObjectWidgetClip() override;
+
+ // override all pure virtual methods
+ virtual void ResetClipRegion() override;
+ virtual void EndSetClipRegion() override;
+
+ virtual void SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void Show( bool bVisible ) override;
+ virtual void Reparent(SalFrame* pFrame) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gtk/gtksalmenu.hxx b/vcl/inc/unx/gtk/gtksalmenu.hxx
new file mode 100644
index 0000000000..4157df9d3b
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtksalmenu.hxx
@@ -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/.
+ */
+
+#pragma once
+
+#include <config_dbus.h>
+#include <config_gio.h>
+
+#include <vector>
+#if ENABLE_GIO
+#include <gio/gio.h>
+#endif
+
+#include <salmenu.hxx>
+#include <unx/gtk/gtkframe.hxx>
+#include <unotools/tempfile.hxx>
+#include <vcl/idle.hxx>
+
+#include <unx/gtk/glomenu.h>
+#include <unx/gtk/gloactiongroup.h>
+
+class MenuItemList;
+class GtkSalMenuItem;
+
+class GtkSalMenu final : public SalMenu
+{
+private:
+ std::vector< GtkSalMenuItem* > maItems;
+ std::vector<std::pair<sal_uInt16, GtkWidget*>> maExtraButtons;
+ Idle maUpdateMenuBarIdle;
+
+ bool mbInActivateCallback;
+ bool mbMenuBar;
+ bool mbNeedsUpdate;
+ bool mbReturnFocusToDocument;
+ bool mbAddedGrab;
+ /// Even setting null icon on a menuitem can be expensive, so cache state to avoid that call
+ bool mbHasNullItemIcon = true;
+ GtkWidget* mpMenuBarContainerWidget;
+ std::unique_ptr<utl::TempFileNamed> mxPersonaImage;
+ BitmapEx maPersonaBitmap;
+ GtkWidget* mpMenuAllowShrinkWidget;
+ GtkWidget* mpMenuBarWidget;
+ GtkWidget* mpMenuWidget;
+ GtkCssProvider* mpMenuBarContainerProvider;
+ GtkCssProvider* mpMenuBarProvider;
+ GtkWidget* mpCloseButton;
+ VclPtr<Menu> mpVCLMenu;
+ GtkSalMenu* mpParentSalMenu;
+ GtkSalFrame* mpFrame;
+
+ // GMenuModel and GActionGroup attributes
+ GMenuModel* mpMenuModel;
+ GActionGroup* mpActionGroup;
+
+ void ImplUpdate(bool bRecurse, bool bRemoveDisabledEntries);
+ void ActivateAllSubmenus(Menu* pMenuBar);
+
+ DECL_LINK(MenuBarHierarchyChangeHandler, Timer*, void);
+
+ static GtkWidget* AddButton(GtkWidget *pImage);
+
+public:
+ GtkSalMenu( bool bMenuBar );
+ virtual ~GtkSalMenu() override;
+
+ virtual bool VisibleMenuBar() override; // must return TRUE to actually DISPLAY native menu bars
+ // otherwise only menu messages are processed (eg, OLE on Windows)
+
+ virtual void InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos ) override;
+ virtual void RemoveItem( unsigned nPos ) override;
+ virtual void SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned nPos ) override;
+ virtual void SetFrame( const SalFrame* pFrame ) override;
+ const GtkSalFrame* GetFrame() const;
+ virtual void CheckItem( unsigned nPos, bool bCheck ) override;
+ virtual void EnableItem( unsigned nPos, bool bEnable ) override;
+ virtual void ShowItem( unsigned nPos, bool bShow ) override;
+ virtual void SetItemText( unsigned nPos, SalMenuItem* pSalMenuItem, const OUString& rText ) override;
+ virtual void SetItemImage( unsigned nPos, SalMenuItem* pSalMenuItem, const Image& rImage) override;
+ virtual void SetAccelerator( unsigned nPos, SalMenuItem* pSalMenuItem, const vcl::KeyCode& rKeyCode, const OUString& rKeyName ) override;
+ virtual void GetSystemMenuData( SystemMenuData* pData ) override;
+
+ void SetMenu( Menu* pMenu ) { mpVCLMenu = pMenu; }
+ Menu* GetMenu() { return mpVCLMenu; }
+ void SetMenuModel(GMenuModel* pMenuModel);
+ unsigned GetItemCount() const { return maItems.size(); }
+ GtkSalMenuItem* GetItemAtPos( unsigned nPos ) { return maItems[ nPos ]; }
+ void SetActionGroup( GActionGroup* pActionGroup ) { mpActionGroup = pActionGroup; }
+ bool IsItemVisible( unsigned nPos );
+
+ void NativeSetItemText( unsigned nSection, unsigned nItemPos, const OUString& rText );
+ void NativeSetItemIcon( unsigned nSection, unsigned nItemPos, const Image& rImage );
+ bool NativeSetItemCommand( unsigned nSection,
+ unsigned nItemPos,
+ sal_uInt16 nId,
+ const gchar* aCommand,
+ MenuItemBits nBits,
+ bool bChecked,
+ bool bIsSubmenu );
+ void NativeSetEnableItem( gchar const * aCommand, gboolean bEnable );
+ void NativeCheckItem( unsigned nSection, unsigned nItemPos, MenuItemBits bits, gboolean bCheck );
+ void NativeSetAccelerator( unsigned nSection, unsigned nItemPos, const vcl::KeyCode& rKeyCode, std::u16string_view rKeyName );
+
+ static void DispatchCommand(const gchar* pMenuCommand);
+ static void Activate(const gchar* pMenuCommand);
+ static void Deactivate(const gchar* pMenuCommand);
+ void EnableUnity(bool bEnable);
+ virtual void ShowMenuBar( bool bVisible ) override;
+ bool PrepUpdate() const;
+ virtual void Update() override; // Update this menu only.
+ // Update full menu hierarchy from this menu.
+ void UpdateFull () { ActivateAllSubmenus(mpVCLMenu); }
+ // Clear ActionGroup and MenuModel from full menu hierarchy
+ void ClearActionGroupAndMenuModel();
+ GtkSalMenu* GetTopLevel();
+ void SetNeedsUpdate();
+
+ GtkWidget* GetMenuBarWidget() const { return mpMenuBarWidget; }
+ GtkWidget* GetMenuBarContainerWidget() const { return mpMenuBarContainerWidget; }
+
+ void CreateMenuBarWidget();
+ void DestroyMenuBarWidget();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gboolean SignalKey(GdkEventKey const * pEvent);
+#endif
+ void ReturnFocus();
+
+ virtual bool ShowNativePopupMenu(FloatingWindow * pWin, const tools::Rectangle& rRect, FloatWinPopupFlags nFlags) override;
+ virtual void ShowCloseButton(bool bShow) override;
+ virtual bool AddMenuBarButton( const SalMenuButtonItem& rNewItem ) override;
+ virtual void RemoveMenuBarButton( sal_uInt16 nId ) override;
+ virtual tools::Rectangle GetMenuBarButtonRectPixel( sal_uInt16 i_nItemId, SalFrame* i_pReferenceFrame ) override;
+ virtual bool CanGetFocus() const override;
+ virtual bool TakeFocus() override;
+ virtual int GetMenuBarHeight() const override;
+ virtual void ApplyPersona() override;
+};
+
+class GtkSalMenuItem final : public SalMenuItem
+{
+public:
+ GtkSalMenuItem( const SalItemParams* );
+ virtual ~GtkSalMenuItem() override;
+
+ GtkSalMenu* mpParentMenu; // The menu into which this menu item is inserted
+ GtkSalMenu* mpSubMenu; // Submenu of this item (if defined)
+ MenuItemType mnType; // Item type
+ sal_uInt16 mnId; // Item ID
+ bool mbVisible; // Item visibility.
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gtk/gtksys.hxx b/vcl/inc/unx/gtk/gtksys.hxx
new file mode 100644
index 0000000000..0dd756f7a7
--- /dev/null
+++ b/vcl/inc/unx/gtk/gtksys.hxx
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <unx/gensys.h>
+#include <gtk/gtk.h>
+#include <unx/saltype.h>
+#include <deque>
+
+class GtkSalSystem final : public SalGenericSystem
+{
+ GdkDisplay *mpDisplay;
+#if !GTK_CHECK_VERSION(4,0,0)
+ // Number of monitors for every active screen.
+ std::deque<std::pair<GdkScreen*, int> > maScreenMonitors;
+#endif
+public:
+ GtkSalSystem();
+ virtual ~GtkSalSystem() override;
+ static GtkSalSystem *GetSingleton();
+
+ virtual unsigned int GetDisplayScreenCount() override;
+ virtual unsigned int GetDisplayBuiltInScreen() override;
+ virtual AbsoluteScreenPixelRectangle GetDisplayScreenPosSizePixel(unsigned int nScreen) override;
+ virtual int ShowNativeDialog (const OUString& rTitle,
+ const OUString& rMessage,
+ const std::vector< OUString >& rButtons) override;
+ SalX11Screen GetDisplayDefaultXScreen()
+ { return getXScreenFromDisplayScreen( GetDisplayBuiltInScreen() ); }
+ SalX11Screen getXScreenFromDisplayScreen(unsigned int nDisplayScreen);
+#if !GTK_CHECK_VERSION(4,0,0)
+ void countScreenMonitors();
+ // We have a 'screen' number that is combined from screen-idx + monitor-idx
+ int getScreenIdxFromPtr (GdkScreen *pScreen);
+ int getScreenMonitorIdx (GdkScreen *pScreen, int nX, int nY);
+ GdkScreen *getScreenMonitorFromIdx (int nIdx, gint &nMonitor);
+#endif
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gtk/hudawareness.h b/vcl/inc/unx/gtk/hudawareness.h
new file mode 100644
index 0000000000..6c9c015d6a
--- /dev/null
+++ b/vcl/inc/unx/gtk/hudawareness.h
@@ -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/.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+typedef void (* HudAwarenessCallback) (gboolean hud_active,
+ gpointer user_data);
+
+guint hud_awareness_register (GDBusConnection *connection,
+ const gchar *object_path,
+ HudAwarenessCallback callback,
+ gpointer user_data,
+ GDestroyNotify notify,
+ GError **error);
+
+void hud_awareness_unregister (GDBusConnection *connection,
+ guint awareness_id);
+
+G_END_DECLS
diff --git a/vcl/inc/unx/helper.hxx b/vcl/inc/unx/helper.hxx
new file mode 100644
index 0000000000..97da8339e3
--- /dev/null
+++ b/vcl/inc/unx/helper.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <vector>
+
+#include <rtl/ustring.hxx>
+
+
+// forwards
+namespace osl { class File; }
+
+namespace psp
+{
+
+void getPrinterPathList( std::vector< OUString >& rPathList, const char* pSubDir );
+
+OUString const & getFontPath();
+
+// normalized path (equivalent to realpath)
+void normPath( OString& rPath );
+
+// splits rOrgPath into dirname and basename
+// rOrgPath will be subject to normPath
+void splitPath( OString& rOrgPath, OString& rDir, OString& rBase );
+
+enum class whichOfficePath { InstallationRootPath, UserPath, ConfigPath };
+
+OUString getOfficePath( whichOfficePath ePath );
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/i18n_cb.hxx b/vcl/inc/unx/i18n_cb.hxx
new file mode 100644
index 0000000000..4b498f3878
--- /dev/null
+++ b/vcl/inc/unx/i18n_cb.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <X11/Xlib.h>
+
+#include <salwtype.hxx>
+#include <vector>
+
+extern "C" {
+
+// xim callbacks
+void PreeditDoneCallback ( XIC ic, XPointer client_data, XPointer call_data);
+int PreeditStartCallback( XIC ic, XPointer client_data, XPointer call_data);
+void PreeditDrawCallback ( XIC ic, XPointer client_data,
+ XIMPreeditDrawCallbackStruct *call_data );
+void PreeditCaretCallback( XIC ic, XPointer client_data,
+ XIMPreeditCaretCallbackStruct *call_data );
+void GetPreeditSpotLocation(XIC ic, XPointer client_data);
+
+void StatusStartCallback (XIC ic, XPointer client_data, XPointer call_data);
+void StatusDoneCallback (XIC ic, XPointer client_data, XPointer call_data);
+void StatusDrawCallback (XIC ic, XPointer client_data,
+ XIMStatusDrawCallbackStruct *call_data);
+
+// keep informed if kinput2 crashed again
+void IC_IMDestroyCallback (XIM im, XPointer client_data, XPointer call_data);
+void IM_IMDestroyCallback (XIM im, XPointer client_data, XPointer call_data);
+
+Bool IsControlCode(sal_Unicode nChar);
+
+} /* extern "C" */
+
+struct preedit_text_t
+{
+ sal_Unicode *pUnicodeBuffer;
+ XIMFeedback *pCharStyle;
+ unsigned int nLength;
+ unsigned int nSize;
+ preedit_text_t()
+ : pUnicodeBuffer(nullptr)
+ , pCharStyle(nullptr)
+ , nLength(0)
+ , nSize(0)
+ {
+ }
+};
+
+class SalFrame;
+
+enum class PreeditStatus {
+ DontKnow = 0,
+ Active,
+ ActivationRequired,
+ StartPending
+};
+
+struct preedit_data_t
+{
+ SalFrame* pFrame;
+ PreeditStatus eState;
+ preedit_text_t aText;
+ SalExtTextInputEvent aInputEv;
+ std::vector< ExtTextInputAttr > aInputFlags;
+ preedit_data_t()
+ : pFrame(nullptr)
+ , eState(PreeditStatus::DontKnow)
+ {
+ }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/i18n_ic.hxx b/vcl/inc/unx/i18n_ic.hxx
new file mode 100644
index 0000000000..303b3fb2e4
--- /dev/null
+++ b/vcl/inc/unx/i18n_ic.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 "i18n_cb.hxx"
+
+enum class EndExtTextInputFlags;
+
+class SalI18N_InputContext
+{
+
+private:
+
+ Bool mbUseable; // system supports current locale ?
+ XIC maContext;
+
+ XIMStyle mnSupportedPreeditStyle;
+ XIMStyle mnStatusStyle;
+ XIMStyle mnPreeditStyle;
+
+ preedit_data_t maClientData;
+ XIMCallback maPreeditStartCallback;
+ XIMCallback maPreeditDoneCallback;
+ XIMCallback maPreeditDrawCallback;
+ XIMCallback maPreeditCaretCallback;
+ XIMCallback maCommitStringCallback;
+ XIMCallback maSwitchIMCallback;
+ XIMCallback maDestroyCallback;
+
+ XVaNestedList mpAttributes;
+ XVaNestedList mpStatusAttributes;
+ XVaNestedList mpPreeditAttributes;
+
+ bool SupportInputMethodStyle( XIMStyles const *pIMStyles );
+ static unsigned int GetWeightingOfIMStyle( XIMStyle n_style );
+ bool IsSupportedIMStyle( XIMStyle n_style ) const;
+
+public:
+
+ Bool UseContext() const { return mbUseable; }
+ bool IsPreeditMode() const { return maClientData.eState == PreeditStatus::Active; }
+ XIC GetContext() const { return maContext; }
+
+ void ExtendEventMask( ::Window aFocusWindow );
+ void SetICFocus( SalFrame* pFocusFrame );
+ void UnsetICFocus();
+ void HandleDestroyIM();
+
+ void EndExtTextInput();
+ void CommitKeyEvent( sal_Unicode const * pText, std::size_t nLength );
+ int UpdateSpotLocation();
+
+ void Map( SalFrame *pFrame );
+ void Unmap();
+
+ SalI18N_InputContext( SalFrame *aFrame );
+ ~SalI18N_InputContext();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/i18n_im.hxx b/vcl/inc/unx/i18n_im.hxx
new file mode 100644
index 0000000000..d283f54451
--- /dev/null
+++ b/vcl/inc/unx/i18n_im.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 <X11/Xlib.h>
+
+#define bUseInputMethodDefault True
+
+class SalI18N_InputMethod
+{
+ bool mbUseable; // system supports locale as well as status
+ // and preedit style ?
+ XIM maMethod;
+ XIMCallback maDestroyCallback;
+ XIMStyles *mpStyles;
+
+public:
+
+ Bool PosixLocale();
+ bool UseMethod() const { return mbUseable; }
+ XIM GetMethod() const { return maMethod; }
+ void HandleDestroyIM();
+ void CreateMethod( Display *pDisplay );
+ XIMStyles *GetSupportedStyles() { return mpStyles; }
+ void SetLocale();
+ bool FilterEvent( XEvent *pEvent, ::Window window );
+
+ SalI18N_InputMethod();
+ ~SalI18N_InputMethod();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/i18n_keysym.hxx b/vcl/inc/unx/i18n_keysym.hxx
new file mode 100644
index 0000000000..ca4e25e982
--- /dev/null
+++ b/vcl/inc/unx/i18n_keysym.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 .
+ */
+
+#pragma once
+
+#include <X11/X.h>
+
+#include <sal/types.h>
+
+/*
+ convert a keysym as defined in /usr/{X11R6|openwin}/include/X11/keysymdef.h
+ to unicode
+
+ supported charsets: (byte1 and byte2 are always 0x0)
+
+ Latin-1 Byte 3 = 0x00
+ Latin-2 Byte 3 = 0x01
+ Latin-3 Byte 3 = 0x02
+ Latin-4 Byte 3 = 0x03
+ Kana Byte 3 = 0x04
+ Arabic Byte 3 = 0x05
+ Cyrillic Byte 3 = 0x06
+ Greek Byte 3 = 0x07
+ Technical Byte 3 = 0x08
+ Special Byte 3 = 0x09
+ Publishing Byte 3 = 0x0a = 10
+ APL Byte 3 = 0x0b = 11
+ Hebrew Byte 3 = 0x0c = 12
+ Thai Byte 3 = 0x0d = 13
+ Korean Byte 3 = 0x0e = 14
+ Latin-9 Byte 3 = 0x13 = 19
+ Currency Byte 3 = 0x20 = 32
+ Keyboard Byte 3 = 0xff = 255
+
+ missing charsets:
+
+ Latin-8 Byte 3 = 0x12 = 18
+ Armenian Byte 3 = 0x14 = 20
+ Georgian Byte 3 = 0x15 = 21
+ Azeri Byte 3 = 0x16 = 22
+ Vietnamese Byte 3 = 0x1e = 30
+
+ of course not all keysyms can be mapped to a unicode code point
+*/
+
+sal_Unicode KeysymToUnicode(KeySym nKeySym);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/i18n_xkb.hxx b/vcl/inc/unx/i18n_xkb.hxx
new file mode 100644
index 0000000000..2fe9712458
--- /dev/null
+++ b/vcl/inc/unx/i18n_xkb.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <X11/Xlib.h>
+
+class SalI18N_KeyboardExtension
+{
+private:
+
+ bool mbUseExtension;
+ int mnEventBase;
+
+public:
+
+ SalI18N_KeyboardExtension( Display *pDisplay );
+
+ inline bool UseExtension() const ; // server and client support the
+ // extension
+ inline void UseExtension( bool bState );// used to disable the Extension
+
+ void Dispatch( XEvent *pEvent ); // keep track of group changes
+
+ inline int GetEventBase() const ;
+};
+
+inline bool
+SalI18N_KeyboardExtension::UseExtension() const
+{
+ return mbUseExtension;
+}
+
+inline void
+SalI18N_KeyboardExtension::UseExtension( bool bState )
+{
+ mbUseExtension = mbUseExtension && bState;
+}
+
+inline int
+SalI18N_KeyboardExtension::GetEventBase() const
+{
+ return mnEventBase;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/saldata.hxx b/vcl/inc/unx/saldata.hxx
new file mode 100644
index 0000000000..0e5928cb45
--- /dev/null
+++ b/vcl/inc/unx/saldata.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <X11/Xlib.h>
+
+#include <unx/saldisp.hxx>
+#include <unx/gendata.hxx>
+
+class SalXLib;
+class SalDisplay;
+class SalPrinter;
+
+class X11SalData final : public GenericUnixSalData
+{
+ struct XErrorStackEntry
+ {
+ bool m_bIgnore;
+ bool m_bWas;
+ XErrorHandler m_aHandler;
+ };
+ std::vector< XErrorStackEntry > m_aXErrorHandlerStack;
+ XIOErrorHandler m_aOrigXIOErrorHandler;
+
+ std::unique_ptr<SalXLib> pXLib_;
+
+public:
+ X11SalData();
+ virtual ~X11SalData() override;
+
+ virtual void Init();
+ virtual void Dispose() override;
+
+ void DeleteDisplay(); // for shutdown
+
+ SalXLib* GetLib() const { return pXLib_.get(); }
+
+ static void Timeout();
+
+ // X errors
+ virtual void ErrorTrapPush() override;
+ virtual bool ErrorTrapPop( bool bIgnoreError = true ) override;
+ void XError( Display *pDisp, XErrorEvent *pEvent );
+ bool HasXErrorOccurred() const
+ { return m_aXErrorHandlerStack.back().m_bWas; }
+ void ResetXErrorOccurred()
+ { m_aXErrorHandlerStack.back().m_bWas = false; }
+ void PushXErrorLevel( bool bIgnore );
+ void PopXErrorLevel();
+};
+
+X11SalData* GetX11SalData();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/saldisp.hxx b/vcl/inc/unx/saldisp.hxx
new file mode 100644
index 0000000000..0d642cb386
--- /dev/null
+++ b/vcl/inc/unx/saldisp.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <epoxy/glx.h>
+
+#include <rtl/string.hxx>
+#include <unx/saltype.h>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <sal/types.h>
+#include <cassert>
+#include <list>
+#include <vector>
+#include <tools/gen.hxx>
+#include <salwtype.hxx>
+#include <unx/gendata.hxx>
+#include <unx/gendisp.hxx>
+#include <o3tl/enumarray.hxx>
+
+#include <vclpluginapi.h>
+
+class SalDisplay;
+class SalColormap;
+class SalVisual;
+class SalXLib;
+
+
+/* From <X11/Intrinsic.h> */
+typedef unsigned long Pixel;
+
+class BitmapPalette;
+class SalFrame;
+class ColorMask;
+
+namespace vcl_sal { class WMAdaptor; }
+
+// server vendor
+
+typedef enum {
+ vendor_none = 0,
+ vendor_sun,
+ vendor_unknown
+} srv_vendor_t;
+
+extern "C" srv_vendor_t sal_GetServerVendor( Display *p_display );
+
+// MSB/Bigendian view (Color == RGB, r=0xFF0000, g=0xFF00, b=0xFF)
+
+class SalVisual : public XVisualInfo
+{
+public:
+ SalVisual();
+ SalVisual( const XVisualInfo* pXVI );
+
+ VisualID GetVisualId() const { return visualid; }
+ Visual *GetVisual() const { return visual; }
+ int GetClass() const { return c_class; }
+ int GetDepth() const { return depth; }
+};
+
+// A move-only flag, used by SalColormap to track ownership of its m_aVisual.visual:
+struct OwnershipFlag {
+ bool owner = false;
+
+ OwnershipFlag() = default;
+
+ OwnershipFlag(OwnershipFlag && other) noexcept: owner(other.owner) { other.owner = false; }
+
+ OwnershipFlag & operator =(OwnershipFlag && other) noexcept {
+ assert(&other != this);
+ owner = other.owner;
+ other.owner = false;
+ return *this;
+ }
+};
+
+class SalColormap
+{
+ const SalDisplay* m_pDisplay;
+ Colormap m_hColormap;
+ std::vector<Color> m_aPalette; // Pseudocolor
+ SalVisual m_aVisual;
+ OwnershipFlag m_aVisualOwnership;
+ Pixel m_nWhitePixel;
+ Pixel m_nBlackPixel;
+ Pixel m_nUsed; // Pseudocolor
+
+public:
+ SalColormap( const SalDisplay* pSalDisplay,
+ Colormap hColormap,
+ SalX11Screen nXScreen );
+ SalColormap( sal_uInt16 nDepth );
+ SalColormap();
+
+ ~SalColormap();
+
+ SalColormap(SalColormap &&) = default;
+ SalColormap & operator =(SalColormap &&) = default;
+
+ Colormap GetXColormap() const { return m_hColormap; }
+ const SalDisplay* GetDisplay() const { return m_pDisplay; }
+ inline Display* GetXDisplay() const;
+ const SalVisual& GetVisual() const { return m_aVisual; }
+ Pixel GetWhitePixel() const { return m_nWhitePixel; }
+ Pixel GetBlackPixel() const { return m_nBlackPixel; }
+
+ bool GetXPixels( XColor &rColor,
+ int r,
+ int g,
+ int b ) const;
+ inline bool GetXPixel( XColor &rColor,
+ int r,
+ int g,
+ int b ) const;
+};
+
+class SalI18N_InputMethod;
+
+typedef int(*YieldFunc)(int fd, void* data);
+
+class SalXLib
+{
+ timeval m_aTimeout;
+ sal_uLong m_nTimeoutMS;
+ int m_pTimeoutFDS[2];
+
+ int nFDs_;
+ fd_set aReadFDS_;
+ fd_set aExceptionFDS_;
+
+ Display *m_pDisplay;
+ std::unique_ptr<SalI18N_InputMethod> m_pInputMethod;
+
+public:
+ SalXLib();
+ ~SalXLib();
+ void Init();
+
+ bool Yield( bool bWait, bool bHandleAllCurrentEvents );
+ void Wakeup();
+ void TriggerUserEventProcessing();
+
+ void Insert( int fd, void* data,
+ YieldFunc pending,
+ YieldFunc queued,
+ YieldFunc handle );
+ void Remove( int fd );
+
+ void StartTimer( sal_uInt64 nMS );
+ void StopTimer();
+
+ bool CheckTimeout( bool bExecuteTimers = true );
+
+ SalI18N_InputMethod* GetInputMethod() const { return m_pInputMethod.get(); }
+ Display* GetDisplay() const { return m_pDisplay; }
+};
+
+class SalI18N_KeyboardExtension;
+class AttributeProvider;
+
+extern "C" {
+ typedef Bool(*X_if_predicate)(Display*,XEvent*,XPointer);
+}
+
+class GLX11Window final : public GLWindow
+{
+public:
+ Display* dpy;
+ int screen;
+ Window win;
+ XVisualInfo* vi;
+ GLXContext ctx;
+ OString GLXExtensions;
+
+ bool HasGLXExtension(const char* name) const;
+
+ GLX11Window();
+ virtual bool Synchronize(bool bOnoff) const override;
+ virtual ~GLX11Window() override;
+};
+
+class VCLPLUG_GEN_PUBLIC SalDisplay : public SalGenericDisplay
+{
+public:
+
+ struct ScreenData
+ {
+ bool m_bInit;
+
+ ::Window m_aRoot;
+ ::Window m_aRefWindow;
+ AbsoluteScreenPixelSize m_aSize;
+ SalVisual m_aVisual;
+ SalColormap m_aColormap;
+ GC m_aMonoGC;
+ GC m_aCopyGC;
+ GC m_aAndInvertedGC;
+ GC m_aAndGC;
+ GC m_aOrGC;
+ GC m_aStippleGC;
+ Pixmap m_hInvert50;
+
+ ScreenData() :
+ m_bInit( false ),
+ m_aRoot( None ),
+ m_aRefWindow( None ),
+ m_aMonoGC( None ),
+ m_aCopyGC( None ),
+ m_aAndInvertedGC( None ),
+ m_aAndGC( None ),
+ m_aOrGC( None ),
+ m_aStippleGC( None ),
+ m_hInvert50( None )
+ {}
+ };
+
+protected:
+ SalXLib *pXLib_;
+ SalI18N_KeyboardExtension *mpKbdExtension;
+
+ Display *pDisp_; // X Display
+
+ SalX11Screen m_nXDefaultScreen;
+ std::vector< ScreenData > m_aScreens;
+ ScreenData m_aInvalidScreenData;
+ Pair aResolution_; // [dpi]
+ sal_uLong nMaxRequestSize_; // [byte]
+
+ srv_vendor_t meServerVendor;
+
+ // until x bytes
+
+ o3tl::enumarray<PointerStyle, Cursor> aPointerCache_;
+
+ // Keyboard
+ bool bNumLockFromXS_; // Num Lock handled by X Server
+ int nNumLockIndex_; // modifier index in modmap
+ KeySym nShiftKeySym_; // first shift modifier
+ KeySym nCtrlKeySym_; // first control modifier
+ KeySym nMod1KeySym_; // first mod1 modifier
+
+ std::unique_ptr<vcl_sal::WMAdaptor> m_pWMAdaptor;
+
+ bool m_bXinerama;
+ std::vector< AbsoluteScreenPixelRectangle > m_aXineramaScreens;
+ std::vector< int > m_aXineramaScreenIndexMap;
+ std::list<SalObject*> m_aSalObjects;
+
+ mutable Time m_nLastUserEventTime; // mutable because changed on first access
+
+ virtual void Dispatch( XEvent *pEvent ) = 0;
+ void InitXinerama();
+ void InitRandR( ::Window aRoot ) const;
+ static void DeInitRandR();
+ void processRandREvent( XEvent* );
+
+ void doDestruct();
+ void addXineramaScreenUnique( int i, tools::Long i_nX, tools::Long i_nY, tools::Long i_nWidth, tools::Long i_nHeight );
+ Time GetEventTimeImpl( bool bAlwaysReget = false ) const;
+public:
+ static bool BestVisual(Display *pDisp, int nScreen, XVisualInfo &rVI);
+
+ SalDisplay( Display* pDisp );
+
+ virtual ~SalDisplay() override;
+
+ void Init();
+
+#ifdef DBG_UTIL
+ void PrintInfo() const;
+ void DbgPrintDisplayEvent(const char *pComment, const XEvent *pEvent) const;
+#endif
+
+ void Beep() const;
+
+ void ModifierMapping();
+ void SimulateKeyPress( sal_uInt16 nKeyCode );
+ KeyIndicatorState GetIndicatorState() const;
+ OUString GetKeyNameFromKeySym( KeySym keysym ) const;
+ OUString GetKeyName( sal_uInt16 nKeyCode ) const;
+ sal_uInt16 GetKeyCode( KeySym keysym, char*pcPrintable ) const;
+ KeySym GetKeySym( XKeyEvent *pEvent,
+ char *pPrintable,
+ int *pLen,
+ KeySym *pUnmodifiedKeySym,
+ Status *pStatus,
+ XIC = nullptr ) const;
+
+ Cursor GetPointer( PointerStyle ePointerStyle );
+ int CaptureMouse( SalFrame *pCapture );
+
+ ScreenData* initScreen( SalX11Screen nXScreen ) const;
+ const ScreenData& getDataForScreen( SalX11Screen nXScreen ) const
+ {
+ if( nXScreen.getXScreen() >= m_aScreens.size() )
+ return m_aInvalidScreenData;
+ if( ! m_aScreens[nXScreen.getXScreen()].m_bInit )
+ initScreen( nXScreen );
+ return m_aScreens[nXScreen.getXScreen()];
+ }
+
+ ::Window GetDrawable( SalX11Screen nXScreen ) const { return getDataForScreen( nXScreen ).m_aRefWindow; }
+ Display *GetDisplay() const { return pDisp_; }
+ const SalX11Screen& GetDefaultXScreen() const { return m_nXDefaultScreen; }
+ const AbsoluteScreenPixelSize& GetScreenSize( SalX11Screen nXScreen ) const { return getDataForScreen( nXScreen ).m_aSize; }
+ srv_vendor_t GetServerVendor() const { return meServerVendor; }
+ bool IsDisplay() const { return !!pXLib_; }
+ const SalColormap& GetColormap( SalX11Screen nXScreen ) const { return getDataForScreen(nXScreen).m_aColormap; }
+ const SalVisual& GetVisual( SalX11Screen nXScreen ) const { return getDataForScreen(nXScreen).m_aVisual; }
+ const Pair &GetResolution() const { return aResolution_; }
+ Time GetLastUserEventTime() const { return GetEventTimeImpl(); }
+ // this is an equivalent of gdk_x11_get_server_time()
+ Time GetX11ServerTime() const { return GetEventTimeImpl( true ); }
+
+ SalI18N_InputMethod* GetInputMethod() const { return pXLib_->GetInputMethod(); }
+ SalI18N_KeyboardExtension* GetKbdExtension() const { return mpKbdExtension; }
+ void SetKbdExtension(SalI18N_KeyboardExtension *pKbdExtension)
+ { mpKbdExtension = pKbdExtension; }
+ ::vcl_sal::WMAdaptor* getWMAdaptor() const { return m_pWMAdaptor.get(); }
+ bool IsXinerama() const { return m_bXinerama; }
+ const std::vector< AbsoluteScreenPixelRectangle >& GetXineramaScreens() const { return m_aXineramaScreens; }
+ ::Window GetRootWindow( SalX11Screen nXScreen ) const
+ { return getDataForScreen( nXScreen ).m_aRoot; }
+ unsigned int GetXScreenCount() const { return m_aScreens.size(); }
+
+ const SalFrameSet& getFrames() const { return m_aFrames; }
+
+ std::list< SalObject* >& getSalObjects() { return m_aSalObjects; }
+};
+
+inline Display *SalColormap::GetXDisplay() const
+{ return m_pDisplay->GetDisplay(); }
+
+class SalX11Display final : public SalDisplay
+{
+public:
+ SalX11Display( Display* pDisp );
+ virtual ~SalX11Display() override;
+
+ virtual void Dispatch( XEvent *pEvent ) override;
+ virtual void Yield();
+ virtual void TriggerUserEventProcessing() override;
+
+ bool IsEvent();
+ void SetupInput();
+};
+
+namespace vcl_sal {
+ // get foreign key names
+ OUString getKeysymReplacementName(
+ std::u16string_view pLang,
+ KeySym nSymbol );
+
+ inline SalDisplay *getSalDisplay(GenericUnixSalData const * data)
+ {
+ assert(data != nullptr);
+ return static_cast<SalDisplay *>(data->GetDisplay());
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/salframe.h b/vcl/inc/unx/salframe.h
new file mode 100644
index 0000000000..a6603f6777
--- /dev/null
+++ b/vcl/inc/unx/salframe.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <X11/Xlib.h>
+
+#include <unx/saltype.h>
+#include <unx/saldisp.hxx>
+#include <unx/sessioninhibitor.hxx>
+#include <salframe.hxx>
+#include <salwtype.hxx>
+
+#include <vcl/ptrstyle.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/timer.hxx>
+
+#include <list>
+
+class X11SalGraphics;
+class SalI18N_InputContext;
+
+namespace vcl_sal { class WMAdaptor; class NetWMAdaptor; class GnomeWMAdaptor; }
+
+// X11SalFrame
+enum class X11ShowState
+{
+ Unknown = -1,
+ Minimized = 0,
+ Normal = 1,
+ Hidden = 2
+};
+
+enum class WMWindowType
+{
+ Normal,
+ ModelessDialogue,
+ Utility,
+ Splash,
+ Toolbar,
+ Dock
+};
+
+class X11SalFrame final : public SalFrame
+{
+ friend class vcl_sal::WMAdaptor;
+ friend class vcl_sal::NetWMAdaptor;
+ friend class vcl_sal::GnomeWMAdaptor;
+
+ X11SalFrame* mpParent; // pointer to parent frame
+ // which should never obscure this frame
+ bool mbTransientForRoot;
+ std::list< X11SalFrame* > maChildren; // List of child frames
+
+ SalDisplay *pDisplay_;
+ SalX11Screen m_nXScreen;
+ ::Window mhWindow;
+ cairo_surface_t* mpSurface;
+ ::Window mhShellWindow;
+ ::Window mhForeignParent;
+ // window to fall back to when no longer in fullscreen mode
+ ::Window mhStackingWindow;
+ // window to listen for CirculateNotify events
+
+ Cursor hCursor_;
+ int nCaptured_; // is captured
+
+ std::unique_ptr<X11SalGraphics> pGraphics_; // current frame graphics
+ std::unique_ptr<X11SalGraphics> pFreeGraphics_; // first free frame graphics
+
+ bool mbSendExtKeyModChange;
+ ModKeyFlags mnExtKeyMod;
+
+ X11ShowState nShowState_; // show state
+ int nWidth_; // client width
+ int nHeight_; // client height
+ AbsoluteScreenPixelRectangle maRestorePosSize;
+ SalFrameStyleFlags nStyle_;
+ SalExtStyle mnExtStyle;
+ bool bAlwaysOnTop_;
+ bool bViewable_;
+ bool bMapped_;
+ bool bDefaultPosition_; // client is centered initially
+ bool m_bXEmbed;
+ int nVisibility_;
+ int m_nWorkArea;
+ bool m_bSetFocusOnMap;
+
+ SessionManagerInhibitor maSessionManagerInhibitor;
+ tools::Rectangle maPaintRegion;
+
+ Timer maAlwaysOnTopRaiseTimer;
+
+ // data for WMAdaptor
+ WMWindowType meWindowType;
+ bool mbMaximizedVert;
+ bool mbMaximizedHorz;
+ bool mbFullScreen;
+ bool m_bIsPartialFullScreen;
+
+ // icon id
+ int mnIconID;
+
+ OUString m_aTitle;
+
+ OUString m_sWMClass;
+
+ SystemEnvData maSystemChildData;
+
+ std::unique_ptr<SalI18N_InputContext> mpInputContext;
+ Bool mbInputFocus;
+
+ std::vector<XRectangle> m_vClipRectangles;
+
+ bool mPendingSizeEvent;
+
+ void GetPosSize( AbsoluteScreenPixelRectangle &rPosSize );
+ void SetSize ( const Size &rSize );
+ void Center();
+ void SetPosSize( const AbsoluteScreenPixelRectangle &rPosSize );
+ void Minimize();
+ void Maximize();
+ void Restore();
+
+ void RestackChildren( ::Window* pTopLevelWindows, int nTopLevelWindows );
+ void RestackChildren();
+
+ bool HandleKeyEvent ( XKeyEvent *pEvent );
+ bool HandleMouseEvent ( XEvent *pEvent );
+ bool HandleFocusEvent ( XFocusChangeEvent const *pEvent );
+ bool HandleExposeEvent ( XEvent const *pEvent );
+ bool HandleSizeEvent ( XConfigureEvent *pEvent );
+ bool HandleStateEvent ( XPropertyEvent const *pEvent );
+ bool HandleReparentEvent ( XReparentEvent *pEvent );
+ bool HandleClientMessage ( XClientMessageEvent*pEvent );
+
+ DECL_LINK( HandleAlwaysOnTopRaise, Timer*, void );
+
+ void createNewWindow( ::Window aParent, SalX11Screen nXScreen = SalX11Screen( -1 ) );
+ void updateScreenNumber();
+
+ void setXEmbedInfo();
+ void askForXEmbedFocus( sal_Int32 i_nTimeCode );
+
+ void updateWMClass();
+public:
+ X11SalFrame( SalFrame* pParent, SalFrameStyleFlags nSalFrameStyle, SystemParentData const * pSystemParent = nullptr );
+ virtual ~X11SalFrame() override;
+
+ bool Dispatch( XEvent *pEvent );
+ void Init( SalFrameStyleFlags nSalFrameStyle, SalX11Screen nScreen,
+ SystemParentData const * pParentData, bool bUseGeometry = false );
+
+ SalDisplay* GetDisplay() const
+ {
+ return pDisplay_;
+ }
+ Display *GetXDisplay() const
+ {
+ return pDisplay_->GetDisplay();
+ }
+ const SalX11Screen& GetScreenNumber() const { return m_nXScreen; }
+ ::Window GetWindow() const { return mhWindow; }
+ cairo_surface_t* GetSurface() const { return mpSurface; }
+ ::Window GetShellWindow() const { return mhShellWindow; }
+ ::Window GetForeignParent() const { return mhForeignParent; }
+ ::Window GetStackingWindow() const { return mhStackingWindow; }
+ void Close() const { CallCallback( SalEvent::Close, nullptr ); }
+ SalFrameStyleFlags GetStyle() const { return nStyle_; }
+
+ Cursor GetCursor() const { return hCursor_; }
+ bool IsCaptured() const { return nCaptured_ == 1; }
+#if !defined(__synchronous_extinput__)
+ void HandleExtTextEvent (XClientMessageEvent const *pEvent);
+#endif
+ bool IsOverrideRedirect() const;
+ bool IsChildWindow() const { return bool(nStyle_ & (SalFrameStyleFlags::PLUG|SalFrameStyleFlags::SYSTEMCHILD)); }
+ bool IsSysChildWindow() const { return bool(nStyle_ & SalFrameStyleFlags::SYSTEMCHILD); }
+ bool IsFloatGrabWindow() const;
+ SalI18N_InputContext* getInputContext() const { return mpInputContext.get(); }
+ bool hasFocus() const { return mbInputFocus; }
+
+ void beginUnicodeSequence();
+ bool appendUnicodeSequence( sal_Unicode );
+ bool endUnicodeSequence();
+
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics( SalGraphics* pGraphics ) override;
+
+ // call with true to clear graphics (setting None as drawable)
+ // call with false to setup graphics with window (GetWindow())
+ virtual void updateGraphics( bool bClear );
+
+ virtual bool PostEvent(std::unique_ptr<ImplSVEvent> pData) override;
+
+ virtual void SetTitle( const OUString& rTitle ) override;
+ virtual void SetIcon( sal_uInt16 nIcon ) override;
+ virtual void SetMenu( SalMenu* pMenu ) override;
+
+ virtual void SetExtendedFrameStyle( SalExtStyle nExtStyle ) override;
+ virtual void Show( bool bVisible, bool bNoActivate = false ) override;
+ virtual void SetMinClientSize( tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void SetMaxClientSize( tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags ) override;
+ virtual void GetClientSize( tools::Long& rWidth, tools::Long& rHeight ) override;
+ virtual void GetWorkArea( AbsoluteScreenPixelRectangle& rRect ) override;
+ virtual SalFrame* GetParent() const override;
+ virtual void SetWindowState(const vcl::WindowData*) override;
+ virtual bool GetWindowState(vcl::WindowData*) override;
+ virtual void ShowFullScreen( bool bFullScreen, sal_Int32 nMonitor ) override;
+ virtual void StartPresentation( bool bStart ) override;
+ virtual void SetAlwaysOnTop( bool bOnTop ) override;
+ virtual void ToTop( SalFrameToTop nFlags ) override;
+ virtual void SetPointer( PointerStyle ePointerStyle ) override;
+ virtual void CaptureMouse( bool bMouse ) override;
+ virtual void SetPointerPos( tools::Long nX, tools::Long nY ) override;
+ using SalFrame::Flush;
+ virtual void Flush() override;
+ virtual void SetInputContext( SalInputContext* pContext ) override;
+ virtual void EndExtTextInput( EndExtTextInputFlags nFlags ) override;
+ virtual OUString GetKeyName( sal_uInt16 nKeyCode ) override;
+ virtual bool MapUnicodeToKeyCode( sal_Unicode aUnicode, LanguageType aLangType, vcl::KeyCode& rKeyCode ) override;
+ virtual LanguageType GetInputLanguage() override;
+ virtual void UpdateSettings( AllSettings& rSettings ) override;
+ virtual void Beep() override;
+ virtual const SystemEnvData* GetSystemData() const override;
+ virtual SalPointerState GetPointerState() override;
+ virtual KeyIndicatorState GetIndicatorState() override;
+ virtual void SimulateKeyPress( sal_uInt16 nKeyCode ) override;
+ virtual void SetParent( SalFrame* pNewParent ) override;
+ virtual void SetPluginParent( SystemParentData* pNewParent ) override;
+
+ virtual void SetScreenNumber( unsigned int ) override;
+ virtual void SetApplicationID( const OUString &rWMClass ) override;
+
+ // shaped system windows
+ // set clip region to none (-> rectangular windows, normal state)
+ virtual void ResetClipRegion() override;
+ // start setting the clipregion consisting of nRects rectangles
+ virtual void BeginSetClipRegion( sal_uInt32 nRects ) override;
+ // add a rectangle to the clip region
+ virtual void UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+ // done setting up the clipregion
+ virtual void EndSetClipRegion() override;
+
+ /// @internal
+ void setPendingSizeEvent();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/salgdi.h b/vcl/inc/unx/salgdi.h
new file mode 100644
index 0000000000..261dd36651
--- /dev/null
+++ b/vcl/inc/unx/salgdi.h
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <X11/Xlib.h>
+
+#include <salgdi.hxx>
+#include <salgeom.hxx>
+#include <sallayout.hxx>
+
+#include <headless/CairoCommon.hxx>
+
+#include "saltype.h"
+#include "saldisp.hxx"
+
+#include <memory>
+
+/* From <X11/Intrinsic.h> */
+typedef unsigned long Pixel;
+
+class SalBitmap;
+class SalColormap;
+class SalDisplay;
+class SalFrame;
+class X11SalFrame;
+class X11SalVirtualDevice;
+class X11SkiaSalVirtualDevice;
+namespace vcl::font
+{
+class PhysicalFontCollection;
+class PhysicalFontFace;
+}
+class SalGraphicsImpl;
+class TextRenderImpl;
+
+namespace basegfx {
+ class B2DTrapezoid;
+}
+
+class X11Common
+{
+public:
+ Drawable m_hDrawable;
+ const SalColormap* m_pColormap;
+
+ X11Common();
+
+ const SalColormap& GetColormap() const { return *m_pColormap; }
+ const SalDisplay* GetDisplay() const { return GetColormap().GetDisplay(); }
+ const SalVisual& GetVisual() const { return GetColormap().GetVisual(); }
+ Display* GetXDisplay() const { return GetColormap().GetXDisplay(); }
+ Drawable GetDrawable() const { return m_hDrawable; }
+};
+
+class X11SalGraphics final : public SalGraphicsAutoDelegateToImpl
+{
+ friend class X11CairoSalGraphicsImpl;
+ friend class X11CairoTextRender;
+
+public:
+ X11SalGraphics();
+ virtual ~X11SalGraphics() COVERITY_NOEXCEPT_FALSE override;
+
+ void Init(X11SalFrame& rFrame, Drawable aDrawable, SalX11Screen nXScreen);
+ void Init(X11SalVirtualDevice *pVirtualDevice, SalColormap* pColormap = nullptr,
+ bool bDeleteColormap = false);
+ void Init( X11SkiaSalVirtualDevice *pVirtualDevice );
+ void DeInit();
+
+ virtual SalGraphicsImpl* GetImpl() const override;
+ SalGeometryProvider* GetGeometryProvider() const;
+ void SetDrawable(Drawable d, cairo_surface_t* surface, SalX11Screen nXScreen);
+
+ const SalX11Screen& GetScreenNumber() const { return m_nXScreen; }
+
+ void Flush();
+
+ // override all pure virtual methods
+ virtual void GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY ) override;
+
+ virtual void SetTextColor( Color nColor ) override;
+ virtual void SetFont(LogicalFontInstance*, int nFallbackLevel) override;
+ virtual void GetFontMetric( FontMetricDataRef&, int nFallbackLevel ) override;
+ virtual FontCharMapRef GetFontCharMap() const override;
+ virtual bool GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const override;
+ virtual void GetDevFontList( vcl::font::PhysicalFontCollection* ) override;
+ virtual void ClearDevFontCache() override;
+ virtual bool AddTempDevFont( vcl::font::PhysicalFontCollection*, const OUString& rFileURL, const OUString& rFontName ) override;
+
+ virtual std::unique_ptr<GenericSalLayout>
+ GetTextLayout(int nFallbackLevel) override;
+ virtual void DrawTextLayout( const GenericSalLayout& ) override;
+
+ virtual SystemGraphicsData GetGraphicsData() const override;
+
+#if ENABLE_CAIRO_CANVAS
+ virtual bool SupportsCairo() const override;
+ virtual cairo::SurfaceSharedPtr CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const override;
+ virtual cairo::SurfaceSharedPtr CreateSurface(const OutputDevice& rRefDevice, int x, int y, int width, int height) const override;
+ virtual cairo::SurfaceSharedPtr CreateBitmapSurface(const OutputDevice& rRefDevice, const BitmapSystemData& rData, const Size& rSize) const override;
+ virtual css::uno::Any GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& rSurface, const basegfx::B2ISize& rSize) const override;
+#endif // ENABLE_CAIRO_CANVAS
+
+private:
+ using SalGraphics::GetPixel;
+
+ void freeResources();
+
+ SalFrame* m_pFrame; // the SalFrame which created this Graphics or NULL
+ SalVirtualDevice* m_pVDev; // the SalVirtualDevice which created this Graphics or NULL
+
+
+ std::unique_ptr<SalColormap> m_pDeleteColormap;
+
+ SalX11Screen m_nXScreen;
+
+ std::unique_ptr<SalGraphicsImpl> mxImpl;
+ std::unique_ptr<TextRenderImpl> mxTextRenderImpl;
+ X11Common maX11Common;
+ CairoCommon maCairoCommon;
+
+public:
+ Drawable GetDrawable() const { return maX11Common.GetDrawable(); }
+ const SalDisplay* GetDisplay() const { return maX11Common.GetDisplay(); }
+ const SalVisual& GetVisual() const { return maX11Common.GetVisual(); }
+ Display* GetXDisplay() const { return maX11Common.GetXDisplay(); }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/salinst.h b/vcl/inc/unx/salinst.h
new file mode 100644
index 0000000000..e8f24e255c
--- /dev/null
+++ b/vcl/inc/unx/salinst.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <salinst.hxx>
+#include <unx/geninst.h>
+
+#include <X11/X.h>
+
+namespace com::sun::star::datatransfer::clipboard { class XClipboard; }
+class SalXLib;
+class X11SalGraphics;
+class SalX11Display;
+
+class X11SalInstance final : public SalGenericInstance
+{
+private:
+ std::unordered_map< Atom, css::uno::Reference< css::datatransfer::clipboard::XClipboard > > m_aInstances;
+
+ SalXLib *mpXLib;
+
+ virtual SalX11Display* CreateDisplay() const;
+
+public:
+ explicit X11SalInstance(std::unique_ptr<SalYieldMutex> pMutex);
+ virtual ~X11SalInstance() override;
+
+ virtual SalFrame* CreateChildFrame( SystemParentData* pParent, SalFrameStyleFlags nStyle ) override;
+ virtual SalFrame* CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle ) override;
+ virtual void DestroyFrame( SalFrame* pFrame ) override;
+
+ virtual SalObject* CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow ) override;
+ virtual void DestroyObject( SalObject* pObject ) override;
+
+ /// Gtk vclplug needs to pass GtkSalGraphics to X11SalVirtualDevice, so create it, and pass as pNewGraphics.
+ static std::unique_ptr<SalVirtualDevice> CreateX11VirtualDevice(const SalGraphics& rGraphics, tools::Long &nDX, tools::Long &nDY,
+ DeviceFormat eFormat, const SystemGraphicsData* pData, std::unique_ptr<X11SalGraphics> pNewGraphics);
+
+ virtual std::unique_ptr<SalVirtualDevice>
+ CreateVirtualDevice( SalGraphics& rGraphics,
+ tools::Long &nDX, tools::Long &nDY,
+ DeviceFormat eFormat, const SystemGraphicsData *pData = nullptr ) override;
+ virtual void PostPrintersChanged() override;
+ virtual std::unique_ptr<GenPspGraphics> CreatePrintGraphics() override;
+
+ virtual SalTimer* CreateSalTimer() override;
+ virtual SalSystem* CreateSalSystem() override;
+ virtual std::shared_ptr<SalBitmap> CreateSalBitmap() override;
+ virtual std::unique_ptr<SalSession> CreateSalSession() override;
+ virtual OpenGLContext* CreateOpenGLContext() override;
+
+ virtual bool DoYield(bool bWait, bool bHandleAllCurrentEvents) override;
+ virtual bool AnyInput( VclInputFlags nType ) override;
+ virtual bool IsMainThread() const override { return true; }
+
+ virtual OUString GetConnectionIdentifier() override;
+ void SetLib( SalXLib *pXLib ) { mpXLib = pXLib; }
+
+ virtual void AfterAppInit() override;
+
+ // dtrans implementation
+ virtual css::uno::Reference< css::uno::XInterface >
+ CreateClipboard( const css::uno::Sequence< css::uno::Any >& i_rArguments ) override;
+ virtual css::uno::Reference<css::uno::XInterface> ImplCreateDragSource(const SystemEnvData*) override;
+ virtual css::uno::Reference<css::uno::XInterface> ImplCreateDropTarget(const SystemEnvData*) override;
+ virtual void AddToRecentDocumentList(const OUString& rFileUrl, const OUString& rMimeType, const OUString& rDocumentService) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/salobj.h b/vcl/inc/unx/salobj.h
new file mode 100644
index 0000000000..f14af351eb
--- /dev/null
+++ b/vcl/inc/unx/salobj.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <X11/Xlib.h>
+
+#include <salobj.hxx>
+#include <vcl/sysdata.hxx>
+#include <memory>
+
+class SalClipRegion
+{
+
+public:
+
+ SalClipRegion();
+ ~SalClipRegion();
+
+ void BeginSetClipRegion( sal_uInt32 nRects );
+ void UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight );
+
+ XRectangle *EndSetClipRegion() {
+ return ClipRectangleList.get(); }
+ void ResetClipRegion() {
+ numClipRectangles = 0; }
+ int GetRectangleCount() const {
+ return numClipRectangles; }
+
+private:
+
+ std::unique_ptr<XRectangle[]>
+ ClipRectangleList;
+ int numClipRectangles;
+ int maxClipRectangles;
+};
+
+class X11SalObject final : public SalObject
+{
+ SystemEnvData maSystemChildData;
+ SalFrame* mpParent;
+ ::Window maParentWin;
+ ::Window maPrimary;
+ ::Window maSecondary;
+ Colormap maColormap;
+ SalClipRegion maClipRegion;
+ bool mbVisible;
+
+public:
+ static VCL_DLLPUBLIC bool Dispatch( XEvent* pEvent );
+ static VCL_DLLPUBLIC X11SalObject* CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow );
+
+ X11SalObject();
+ virtual ~X11SalObject() override;
+
+ // override all pure virtual methods
+ virtual void ResetClipRegion() override;
+ virtual void BeginSetClipRegion( sal_uInt32 nRects ) override;
+ virtual void UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void EndSetClipRegion() override;
+
+ virtual void SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void Show( bool bVisible ) override;
+ virtual void GrabFocus() override;
+
+ virtual void SetLeaveEnterBackgrounds(const css::uno::Sequence<css::uno::Any>& rLeaveArgs, const css::uno::Sequence<css::uno::Any>& rEnterArgs) override;
+
+ virtual const SystemEnvData* GetSystemData() const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/saltimer.h b/vcl/inc/unx/saltimer.h
new file mode 100644
index 0000000000..a83401f5b9
--- /dev/null
+++ b/vcl/inc/unx/saltimer.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <saltimer.hxx>
+
+class SalXLib;
+class X11SalTimer final : public SalTimer
+{
+ SalXLib *mpXLib;
+public:
+ X11SalTimer( SalXLib *pXLib ) : mpXLib( pXLib ) {}
+ virtual ~X11SalTimer() override;
+
+ // override all pure virtual methods
+ void Start( sal_uInt64 nMS ) override;
+ void Stop() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/saltype.h b/vcl/inc/unx/saltype.h
new file mode 100644
index 0000000000..1fde4bd779
--- /dev/null
+++ b/vcl/inc/unx/saltype.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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
+
+// an X11 screen index - this unpleasant construct is to allow
+// us to cleanly separate the 'DisplayScreen' concept - as used
+// in the public facing API, from X's idea of screen indices.
+// Both of these are plain unsigned integers called 'screen'
+class SalX11Screen {
+ unsigned int mnXScreen;
+public:
+ explicit SalX11Screen(unsigned int nXScreen) : mnXScreen( nXScreen ) {}
+ unsigned int getXScreen() const { return mnXScreen; }
+ bool operator==(const SalX11Screen &rOther) const { return rOther.mnXScreen == mnXScreen; }
+ bool operator!=(const SalX11Screen &rOther) const { return rOther.mnXScreen != mnXScreen; }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/salunx.h b/vcl/inc/unx/salunx.h
new file mode 100644
index 0000000000..a633378998
--- /dev/null
+++ b/vcl/inc/unx/salunx.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <tools/long.hxx>
+
+inline tools::Long Divide(tools::Long nDividend, tools::Long nDivisor)
+{
+ return (nDividend + nDivisor / 2) / nDivisor;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/salunxtime.h b/vcl/inc/unx/salunxtime.h
new file mode 100644
index 0000000000..b1fe0d8102
--- /dev/null
+++ b/vcl/inc/unx/salunxtime.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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
+
+#if defined LINUX || defined FREEBSD || \
+ defined NETBSD || defined OPENBSD || defined DRAGONFLY
+#include <sys/time.h>
+#endif
+#include <sal/types.h>
+
+inline bool operator >= ( const timeval &t1, const timeval &t2 )
+{
+ if( t1.tv_sec == t2.tv_sec )
+ return t1.tv_usec >= t2.tv_usec;
+ return t1.tv_sec > t2.tv_sec;
+}
+
+inline bool operator > ( const timeval &t1, const timeval &t2 )
+{
+ if( t1.tv_sec == t2.tv_sec )
+ return t1.tv_usec > t2.tv_usec;
+ return t1.tv_sec > t2.tv_sec;
+}
+
+inline timeval &operator -= ( timeval &t1, const timeval &t2 )
+{
+ if( t1.tv_usec < t2.tv_usec )
+ {
+ t1.tv_sec--;
+ t1.tv_usec += 1000000;
+ }
+ t1.tv_sec -= t2.tv_sec;
+ t1.tv_usec -= t2.tv_usec;
+ return t1;
+}
+
+inline timeval &operator += ( timeval &t1, sal_uIntPtr t2 )
+{
+ t1.tv_sec += t2 / 1000;
+ t1.tv_usec += (t2 % 1000) * 1000;
+ if( t1.tv_usec > 1000000 )
+ {
+ t1.tv_sec++;
+ t1.tv_usec -= 1000000;
+ }
+ return t1;
+}
+
+inline timeval operator - ( const timeval &t1, const timeval &t2 )
+{
+ timeval t0 = t1;
+ t0 -= t2;
+ return t0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/salvd.h b/vcl/inc/unx/salvd.h
new file mode 100644
index 0000000000..f85d536358
--- /dev/null
+++ b/vcl/inc/unx/salvd.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <X11/Xlib.h>
+
+#include <vcl/salgtype.hxx>
+
+#include <unx/saldisp.hxx>
+#include <unx/saltype.h>
+#include <salvd.hxx>
+
+#include <memory>
+
+class SalDisplay;
+class X11SalGraphics;
+typedef struct _cairo_surface cairo_surface_t;
+
+class X11SalVirtualDevice final : public SalVirtualDevice
+{
+ SalDisplay *pDisplay_;
+ std::unique_ptr<X11SalGraphics> pGraphics_;
+
+ Pixmap hDrawable_;
+ SalX11Screen m_nXScreen;
+
+ int nDX_;
+ int nDY_;
+ sal_uInt16 nDepth_;
+ bool bGraphics_; // is Graphics used
+ bool bExternPixmap_;
+ cairo_surface_t* m_pSurface;
+ bool m_bOwnsSurface; // nearly always true, except for edge case of tdf#127529
+
+public:
+ X11SalVirtualDevice(const SalGraphics& rGraphics, tools::Long &nDX, tools::Long &nDY,
+ DeviceFormat eFormat, const SystemGraphicsData *pData, std::unique_ptr<X11SalGraphics> pNewGraphics);
+
+ virtual ~X11SalVirtualDevice() override;
+
+ Display *GetXDisplay() const
+ {
+ return pDisplay_->GetDisplay();
+ }
+ SalDisplay *GetDisplay() const
+ {
+ return pDisplay_;
+ }
+ Pixmap GetDrawable() const { return hDrawable_; }
+ cairo_surface_t* GetSurface() const { return m_pSurface; }
+ sal_uInt16 GetDepth() const { return nDepth_; }
+ const SalX11Screen& GetXScreenNumber() const { return m_nXScreen; }
+
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics( SalGraphics* pGraphics ) override;
+
+ /// Set new size, without saving the old contents
+ virtual bool SetSize( tools::Long nNewDX, tools::Long nNewDY ) override;
+
+ // SalGeometryProvider
+ virtual tools::Long GetWidth() const override { return nDX_; }
+ virtual tools::Long GetHeight() const override { return nDY_; }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/sessioninhibitor.hxx b/vcl/inc/unx/sessioninhibitor.hxx
new file mode 100644
index 0000000000..ca4dc6a93a
--- /dev/null
+++ b/vcl/inc/unx/sessioninhibitor.hxx
@@ -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/.
+ */
+
+#pragma once
+
+#include <X11/Xlib.h>
+#include <X11/Xmd.h>
+
+#include <vcl/dllapi.h>
+
+#include <optional>
+#include <string_view>
+
+enum ApplicationInhibitFlags
+{
+ APPLICATION_INHIBIT_LOGOUT = (1 << 0),
+ APPLICATION_INHIBIT_IDLE = (1 << 3) // Inhibit the session being marked as idle
+};
+
+class VCL_PLUGIN_PUBLIC SessionManagerInhibitor
+{
+public:
+ void inhibit(bool bInhibit, std::u16string_view sReason, ApplicationInhibitFlags eType,
+ unsigned int window_system_id, std::optional<Display*> pDisplay,
+ const char* application_id = nullptr);
+
+private:
+ // These are all used as guint, however this header may be included
+ // in kde/tde/etc backends, where we would ideally avoid having
+ // any glib dependencies, hence the direct use of unsigned int.
+ std::optional<unsigned int> mnFDOSSCookie; // FDO ScreenSaver Inhibit
+ std::optional<unsigned int> mnFDOPMCookie; // FDO PowerManagement Inhibit
+ std::optional<unsigned int> mnGSMCookie;
+ std::optional<unsigned int> mnMSMCookie;
+
+ std::optional<int> mnXScreenSaverTimeout;
+
+#if !defined(__sun)
+ BOOL mbDPMSWasEnabled;
+ CARD16 mnDPMSStandbyTimeout;
+ CARD16 mnDPMSSuspendTimeout;
+ CARD16 mnDPMSOffTimeout;
+#endif
+
+ // There are a bunch of different dbus based inhibition APIs. Some call
+ // themselves ScreenSaver inhibition, some are PowerManagement inhibition,
+ // but they appear to have the same effect. There doesn't appear to be one
+ // all encompassing standard, hence we should just try all of them.
+ //
+ // The current APIs we have: (note: the list of supported environments is incomplete)
+ // FDSSO: org.freedesktop.ScreenSaver::Inhibit - appears to be supported only by KDE?
+ // FDOPM: org.freedesktop.PowerManagement.Inhibit::Inhibit - XFCE, (KDE) ?
+ // (KDE: doesn't inhibit screensaver, but does inhibit PowerManagement)
+ // GSM: org.gnome.SessionManager::Inhibit - gnome 3
+ // MSM: org.mate.Sessionmanager::Inhibit - Mate <= 1.10, is identical to GSM
+ // (This is replaced by the GSM interface from Mate 1.12 onwards)
+ //
+ // Note: the Uninhibit call has different spelling in FDOSS (UnInhibit) vs GSM (Uninhibit)
+ void inhibitFDOSS(bool bInhibit, const char* appname, const char* reason);
+ void inhibitFDOPM(bool bInhibit, const char* appname, const char* reason);
+ void inhibitGSM(bool bInhibit, const char* appname, const char* reason,
+ ApplicationInhibitFlags eType, unsigned int window_system_id);
+ void inhibitMSM(bool bInhibit, const char* appname, const char* reason,
+ ApplicationInhibitFlags eType, unsigned int window_system_id);
+
+ void inhibitXScreenSaver(bool bInhibit, Display* pDisplay);
+ static void inhibitXAutoLock(bool bInhibit, Display* pDisplay);
+ void inhibitDPMS(bool bInhibit, Display* pDisplay);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/sm.hxx b/vcl/inc/unx/sm.hxx
new file mode 100644
index 0000000000..16a3daff80
--- /dev/null
+++ b/vcl/inc/unx/sm.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <X11/SM/SMlib.h>
+
+#include <tools/link.hxx>
+#include <rtl/ustring.hxx>
+#include <memory>
+
+class ICEConnectionObserver;
+class SalSession;
+
+class SessionManagerClient
+{
+ static SalSession * m_pSession;
+ static std::unique_ptr< ICEConnectionObserver > m_xICEConnectionObserver;
+ static SmcConn m_pSmcConnection;
+ static OString m_aClientID;
+ static OString m_aTimeID;
+ static OString m_aClientTimeID;
+ static bool m_bDocSaveDone;
+
+ static void SaveYourselfProc( SmcConn connection,
+ SmPointer client_data,
+ int save_type,
+ Bool shutdown,
+ int interact_style,
+ Bool fast );
+ static void DieProc( SmcConn connection,
+ SmPointer client_data );
+ static void SaveCompleteProc( SmcConn connection,
+ SmPointer client_data );
+ static void ShutdownCanceledProc( SmcConn connection,
+ SmPointer client_data );
+ static void InteractProc( SmcConn connection,
+ SmPointer clientData );
+
+ static OString getPreviousSessionID();
+
+ DECL_STATIC_LINK( SessionManagerClient, ShutDownHdl, void*, void );
+ DECL_STATIC_LINK( SessionManagerClient, ShutDownCancelHdl, void*, void );
+ DECL_STATIC_LINK( SessionManagerClient, SaveYourselfHdl, void*, void );
+ DECL_STATIC_LINK( SessionManagerClient, InteractionHdl, void*, void );
+public:
+ static void open(SalSession * pSession);
+ static void close();
+
+ static bool checkDocumentsSaved();
+ static bool queryInteraction();
+ static void saveDone();
+ static void interactionDone( bool bCancelShutdown );
+
+ static OUString getExecName();
+ static const OString& getSessionID();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/svsys.h b/vcl/inc/unx/svsys.h
new file mode 100644
index 0000000000..00aec18a34
--- /dev/null
+++ b/vcl/inc/unx/svsys.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/XKBlib.h>
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/wmadaptor.hxx b/vcl/inc/unx/wmadaptor.hxx
new file mode 100644
index 0000000000..6d3ee56277
--- /dev/null
+++ b/vcl/inc/unx/wmadaptor.hxx
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <tools/gen.hxx>
+
+#include <X11/Xlib.h>
+
+#include "salframe.h"
+#include <vector>
+
+class SalDisplay;
+class X11SalFrame;
+
+namespace vcl_sal {
+
+class WMAdaptor
+{
+public:
+ enum WMAtom {
+ // atoms for types
+ UTF8_STRING,
+
+ // atoms for extended WM hints
+ NET_ACTIVE_WINDOW,
+ NET_SUPPORTED,
+ NET_SUPPORTING_WM_CHECK,
+ NET_WM_NAME,
+ NET_WM_DESKTOP,
+ NET_WM_ICON_NAME,
+ NET_WM_PID,
+ NET_WM_PING,
+ NET_WM_STATE,
+ NET_WM_STATE_MAXIMIZED_HORZ,
+ NET_WM_STATE_MAXIMIZED_VERT,
+ NET_WM_STATE_MODAL,
+ NET_WM_STATE_SKIP_PAGER,
+ NET_WM_STATE_SKIP_TASKBAR,
+ NET_WM_STATE_STAYS_ON_TOP,
+ NET_WM_STATE_STICKY,
+ NET_WM_STATE_FULLSCREEN,
+ NET_WM_STRUT,
+ NET_WM_STRUT_PARTIAL,
+ NET_WM_USER_TIME,
+ NET_WM_WINDOW_TYPE,
+ NET_WM_WINDOW_TYPE_DESKTOP,
+ NET_WM_WINDOW_TYPE_DIALOG,
+ NET_WM_WINDOW_TYPE_DOCK,
+ NET_WM_WINDOW_TYPE_MENU,
+ NET_WM_WINDOW_TYPE_NORMAL,
+ NET_WM_WINDOW_TYPE_TOOLBAR,
+ KDE_NET_WM_WINDOW_TYPE_OVERRIDE,
+ NET_WM_WINDOW_TYPE_SPLASH,
+ NET_WM_WINDOW_TYPE_UTILITY,
+ NET_NUMBER_OF_DESKTOPS,
+ NET_CURRENT_DESKTOP,
+ NET_WORKAREA,
+ NET_WM_ICON,
+
+ // atoms for Gnome WM hints
+ WIN_SUPPORTING_WM_CHECK,
+ WIN_PROTOCOLS,
+ WIN_WORKSPACE_COUNT,
+ WIN_WORKSPACE,
+ WIN_LAYER,
+ WIN_STATE,
+ WIN_HINTS,
+ WIN_APP_STATE,
+ WIN_EXPANDED_SIZE,
+ WIN_ICONS,
+ WIN_CLIENT_LIST,
+
+ // atoms for general WM hints
+ WM_STATE,
+ MOTIF_WM_HINTS,
+ WM_PROTOCOLS,
+ WM_DELETE_WINDOW,
+ WM_TAKE_FOCUS,
+ WM_CLIENT_LEADER,
+ WM_COMMAND,
+ WM_LOCALE_NAME,
+ WM_TRANSIENT_FOR,
+
+ // special atoms
+ SAL_QUITEVENT,
+ SAL_USEREVENT,
+ SAL_EXTTEXTEVENT,
+ SAL_GETTIMEEVENT,
+ VCL_SYSTEM_SETTINGS,
+ XSETTINGS,
+ XEMBED,
+ XEMBED_INFO,
+ NetAtomMax
+ };
+
+ /*
+ * flags for frame decoration
+ */
+ static const int decoration_Title = 0x00000001;
+ static const int decoration_Border = 0x00000002;
+ static const int decoration_Resize = 0x00000004;
+ static const int decoration_MinimizeBtn = 0x00000008;
+ static const int decoration_MaximizeBtn = 0x00000010;
+ static const int decoration_CloseBtn = 0x00000020;
+ static const int decoration_All = 0x10000000;
+
+protected:
+ SalDisplay* m_pSalDisplay; // Display to use
+ Display* m_pDisplay; // X Display of SalDisplay
+ OUString m_aWMName;
+ Atom m_aWMAtoms[ NetAtomMax];
+ int m_nDesktops;
+ bool m_bEqualWorkAreas;
+ ::std::vector< AbsoluteScreenPixelRectangle >
+ m_aWMWorkAreas;
+ bool m_bEnableAlwaysOnTopWorks;
+ bool m_bLegacyPartialFullscreen;
+ int m_nWinGravity;
+ int m_nInitWinGravity;
+ bool m_bWMshouldSwitchWorkspace;
+ bool m_bWMshouldSwitchWorkspaceInit;
+
+ WMAdaptor( SalDisplay * )
+;
+ void initAtoms();
+ bool getNetWmName();
+
+ /*
+ * returns whether this instance is useful
+ * only useful for createWMAdaptor
+ */
+ virtual bool isValid() const;
+
+ bool getWMshouldSwitchWorkspace() const;
+public:
+ virtual ~WMAdaptor();
+
+ /*
+ * creates a valid WMAdaptor instance for the SalDisplay
+ */
+ static std::unique_ptr<WMAdaptor> createWMAdaptor( SalDisplay* );
+
+ /*
+ * may return an empty string if the window manager could
+ * not be identified.
+ */
+ const OUString& getWindowManagerName() const
+ { return m_aWMName; }
+
+ /*
+ * gets the current work area/desktop number: [0,m_nDesktops[ or -1 if unknown
+ */
+ int getCurrentWorkArea() const;
+ /*
+ * gets the workarea the specified window is on (or -1)
+ */
+ int getWindowWorkArea( ::Window aWindow ) const;
+ /*
+ * gets the specified workarea
+ */
+ const AbsoluteScreenPixelRectangle& getWorkArea( int n ) const
+ { return m_aWMWorkAreas[n]; }
+
+ /*
+ * attempt to switch the desktop to a certain workarea (ie. virtual desktops)
+ */
+ void switchToWorkArea( int nWorkArea ) const;
+
+ /*
+ * sets window title
+ */
+ virtual void setWMName( X11SalFrame* pFrame, const OUString& rWMName ) const;
+
+ /*
+ * set NET_WM_PID
+ */
+ void setPID( X11SalFrame const * pFrame ) const;
+
+ /*
+ * set WM_CLIENT_MACHINE
+ */
+ void setClientMachine( X11SalFrame const * pFrame ) const;
+
+ void answerPing( X11SalFrame const *, XClientMessageEvent const * ) const;
+
+ /*
+ * maximizes frame
+ * maximization can be toggled in either direction
+ * to get the original position and size
+ * use maximizeFrame( pFrame, false, false )
+ */
+ virtual void maximizeFrame( X11SalFrame* pFrame, bool bHorizontal = true, bool bVertical = true ) const;
+ /*
+ * start/stop fullscreen mode on a frame
+ */
+ virtual void showFullScreen( X11SalFrame* pFrame, bool bFullScreen ) const;
+ /*
+ * tell whether legacy partial full screen handling is necessary
+ * see #i107249#: NET_WM_STATE_FULLSCREEN is not well defined, but de facto
+ * modern WM's interpret it the "right" way, namely they make "full screen"
+ * taking twin view or Xinerama into account and honor the positioning hints
+ * to see which screen actually was meant to use for fullscreen.
+ */
+ bool isLegacyPartialFullscreen() const
+ { return m_bLegacyPartialFullscreen; }
+ /*
+ * set _NET_WM_USER_TIME property, if NetWM
+ */
+ virtual void setUserTime( X11SalFrame* i_pFrame, tools::Long i_nUserTime ) const;
+
+ /*
+ * tells whether fullscreen mode is supported by WM
+ */
+ bool supportsFullScreen() const { return m_aWMAtoms[ NET_WM_STATE_FULLSCREEN ] != 0; }
+
+ /*
+ * set hints what decoration is needed;
+ * must be called before showing the frame
+ */
+ virtual void setFrameTypeAndDecoration( X11SalFrame* pFrame, WMWindowType eType, int nDecorationFlags, X11SalFrame* pTransientFrame ) const;
+
+ /*
+ * tells whether there is WM support for splash screens
+ */
+ bool supportsSplash() const { return m_aWMAtoms[ NET_WM_WINDOW_TYPE_SPLASH ] != 0; }
+
+ /*
+ * enables always on top or equivalent if possible
+ */
+ virtual void enableAlwaysOnTop( X11SalFrame* pFrame, bool bEnable ) const;
+
+ /*
+ * tells whether enableAlwaysOnTop actually works with this WM
+ */
+ bool isAlwaysOnTopOK() const { return m_bEnableAlwaysOnTopWorks; }
+
+ /*
+ * handle WM messages (especially WM state changes)
+ */
+ virtual int handlePropertyNotify( X11SalFrame* pFrame, XPropertyEvent* pEvent ) const;
+
+ /*
+ * called by SalFrame::Show: time to update state properties
+ */
+ virtual void frameIsMapping( X11SalFrame* ) const;
+
+ /*
+ * gets a WM atom
+ */
+ Atom getAtom( WMAtom eAtom ) const
+ { return m_aWMAtoms[ eAtom ]; }
+
+ int getPositionWinGravity () const
+ { return m_nWinGravity; }
+ int getInitWinGravity() const
+ { return m_nInitWinGravity; }
+
+ /*
+ * changes the transient hint of a window to reference frame
+ * if reference frame is NULL the root window is used instead
+ */
+ void changeReferenceFrame( X11SalFrame* pFrame, X11SalFrame const * pReferenceFrame ) const;
+
+ /*
+ * Requests the change of active window by sending
+ * _NET_ACTIVE_WINDOW message to the frame. The frame
+ * has to be mapped
+ */
+ void activateWindow( X11SalFrame const *pFrame, Time nTimestamp );
+};
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11/x11gdiimpl.h b/vcl/inc/unx/x11/x11gdiimpl.h
new file mode 100644
index 0000000000..f7c07a7e86
--- /dev/null
+++ b/vcl/inc/unx/x11/x11gdiimpl.h
@@ -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/.
+ */
+
+#pragma once
+
+#include <sal/types.h>
+
+class ControlCacheKey;
+
+class SAL_LOPLUGIN_ANNOTATE("crosscast") X11GraphicsImpl
+{
+public:
+ virtual ~X11GraphicsImpl(){};
+ virtual void Flush(){};
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11/x11sys.hxx b/vcl/inc/unx/x11/x11sys.hxx
new file mode 100644
index 0000000000..b48cf26e6a
--- /dev/null
+++ b/vcl/inc/unx/x11/x11sys.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_UNX_X11_X11SYS_HXX
+#define INCLUDED_VCL_INC_UNX_X11_X11SYS_HXX
+
+#include <unx/gensys.h>
+
+class X11SalSystem final : public SalGenericSystem
+{
+public:
+ X11SalSystem() {}
+ virtual ~X11SalSystem() override;
+
+ // override pure virtual methods
+ virtual unsigned int GetDisplayScreenCount() override;
+ virtual unsigned int GetDisplayBuiltInScreen() override;
+ virtual AbsoluteScreenPixelRectangle GetDisplayScreenPosSizePixel( unsigned int nScreen ) override;
+ virtual int ShowNativeDialog( const OUString& rTitle,
+ const OUString& rMessage,
+ const std::vector< OUString >& rButtons ) override;
+};
+
+#endif // INCLUDED_VCL_INC_UNX_X11_X11SYS_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11/xlimits.hxx b/vcl/inc/unx/x11/xlimits.hxx
new file mode 100644
index 0000000000..35cbd647e0
--- /dev/null
+++ b/vcl/inc/unx/x11/xlimits.hxx
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <X11/Xlib.h>
+
+Pixmap limitXCreatePixmap(Display *display, Drawable d, unsigned int width, unsigned int height, unsigned int depth);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/ase_curs.h b/vcl/inc/unx/x11_cursors/ase_curs.h
new file mode 100644
index 0000000000..878d1ff571
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/ase_curs.h
@@ -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 .
+ */
+#define ase_curs_width 32
+#define ase_curs_height 32
+#define ase_curs_x_hot 19
+#define ase_curs_y_hot 16
+static unsigned char ase_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x1c,0x0e,
+ 0x00,0x00,0x3e,0x1e,0x00,0x00,0x3e,0x7e,0x00,0x00,0x3e,0x1e,0x00,0x00,0x1c,
+ 0x0e,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/ase_mask.h b/vcl/inc/unx/x11_cursors/ase_mask.h
new file mode 100644
index 0000000000..258b2bc93e
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/ase_mask.h
@@ -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 .
+ */
+#define ase_mask_width 32
+#define ase_mask_height 32
+#define ase_mask_x_hot 19
+#define ase_mask_y_hot 16
+static unsigned char ase_mask_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x9c,0x0f,0x00,0x00,0x3e,0x1f,
+ 0x00,0x00,0x7f,0x7f,0x00,0x00,0x7f,0xff,0x00,0x00,0x7f,0x7f,0x00,0x00,0x3e,
+ 0x1f,0x00,0x00,0x9c,0x0f,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/asn_curs.h b/vcl/inc/unx/x11_cursors/asn_curs.h
new file mode 100644
index 0000000000..1e6170af49
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/asn_curs.h
@@ -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 .
+ */
+#define asn_curs_width 32
+#define asn_curs_height 32
+#define asn_curs_x_hot 16
+#define asn_curs_y_hot 12
+static unsigned char asn_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x80,0x03,
+ 0x00,0x00,0xc0,0x07,0x00,0x00,0xc0,0x07,0x00,0x00,0xe0,0x0f,0x00,0x00,0x20,
+ 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x03,0x00,0x00,
+ 0xc0,0x07,0x00,0x00,0xc0,0x07,0x00,0x00,0xc0,0x07,0x00,0x00,0x80,0x03,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/asn_mask.h b/vcl/inc/unx/x11_cursors/asn_mask.h
new file mode 100644
index 0000000000..c44b5eeb94
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/asn_mask.h
@@ -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 .
+ */
+#define asn_mask_width 32
+#define asn_mask_height 32
+#define asn_mask_x_hot 16
+#define asn_mask_y_hot 12
+static unsigned char asn_mask_bits[] = {
+ 0x00,0x00,0x01,0x00,0x00,0x80,0x03,0x00,0x00,0x80,0x03,0x00,0x00,0xc0,0x07,
+ 0x00,0x00,0xe0,0x0f,0x00,0x00,0xe0,0x0f,0x00,0x00,0xf0,0x1f,0x00,0x00,0xf0,
+ 0x1f,0x00,0x00,0x20,0x08,0x00,0x00,0x80,0x03,0x00,0x00,0xc0,0x07,0x00,0x00,
+ 0xe0,0x0f,0x00,0x00,0xe0,0x0f,0x00,0x00,0xe0,0x0f,0x00,0x00,0xc0,0x07,0x00,
+ 0x00,0x80,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/asne_curs.h b/vcl/inc/unx/x11_cursors/asne_curs.h
new file mode 100644
index 0000000000..cc757474e2
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/asne_curs.h
@@ -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 .
+ */
+#define asne_curs_width 32
+#define asne_curs_height 32
+#define asne_curs_x_hot 21
+#define asne_curs_y_hot 10
+static unsigned char asne_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3c,0x00,0x00,0x80,
+ 0x3f,0x00,0x00,0xc0,0x3f,0x00,0x00,0x00,0x3f,0x00,0x00,0x00,0x1c,0x00,0x00,
+ 0x00,0x1c,0x00,0x00,0x70,0x18,0x00,0x00,0xf8,0x08,0x00,0x00,0xf8,0x00,0x00,
+ 0x00,0xf8,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/asne_mask.h b/vcl/inc/unx/x11_cursors/asne_mask.h
new file mode 100644
index 0000000000..ebb80c0ba6
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/asne_mask.h
@@ -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 .
+ */
+#define asne_mask_width 32
+#define asne_mask_height 32
+#define asne_mask_x_hot 21
+#define asne_mask_y_hot 10
+static unsigned char asne_mask_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7c,0x00,0x00,0x80,0x7f,0x00,0x00,0xc0,
+ 0x7f,0x00,0x00,0xe0,0x7f,0x00,0x00,0xc0,0x7f,0x00,0x00,0x00,0x3f,0x00,0x00,
+ 0x70,0x3e,0x00,0x00,0xf8,0x3c,0x00,0x00,0xfc,0x1d,0x00,0x00,0xfc,0x09,0x00,
+ 0x00,0xfc,0x01,0x00,0x00,0xf8,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/asns_curs.h b/vcl/inc/unx/x11_cursors/asns_curs.h
new file mode 100644
index 0000000000..27c0ce7e7c
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/asns_curs.h
@@ -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 .
+ */
+#define asns_curs_width 32
+#define asns_curs_height 32
+#define asns_curs_x_hot 15
+#define asns_curs_y_hot 15
+static unsigned char asns_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x80,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0xc0,0x01,0x00,0x00,0xe0,
+ 0x03,0x00,0x00,0xe0,0x03,0x00,0x00,0xf0,0x07,0x00,0x00,0x10,0x04,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0x01,0x00,0x00,0xe0,0x03,0x00,
+ 0x00,0xe0,0x03,0x00,0x00,0xe0,0x03,0x00,0x00,0xc0,0x01,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x04,0x00,0x00,0xf0,0x07,0x00,0x00,0xe0,
+ 0x03,0x00,0x00,0xe0,0x03,0x00,0x00,0xc0,0x01,0x00,0x00,0x80,0x00,0x00,0x00,
+ 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/asns_mask.h b/vcl/inc/unx/x11_cursors/asns_mask.h
new file mode 100644
index 0000000000..d7d8a12534
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/asns_mask.h
@@ -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 .
+ */
+#define asns_mask_width 32
+#define asns_mask_height 32
+#define asns_mask_x_hot 15
+#define asns_mask_y_hot 15
+static unsigned char asns_mask_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,
+ 0x00,0x00,0xc0,0x01,0x00,0x00,0xc0,0x01,0x00,0x00,0xe0,0x03,0x00,0x00,0xf0,
+ 0x07,0x00,0x00,0xf0,0x07,0x00,0x00,0xf8,0x0f,0x00,0x00,0xf8,0x0f,0x00,0x00,
+ 0x10,0x04,0x00,0x00,0xc0,0x01,0x00,0x00,0xe0,0x03,0x00,0x00,0xf0,0x07,0x00,
+ 0x00,0xf0,0x07,0x00,0x00,0xf0,0x07,0x00,0x00,0xe0,0x03,0x00,0x00,0xc0,0x01,
+ 0x00,0x00,0x10,0x04,0x00,0x00,0xf8,0x0f,0x00,0x00,0xf8,0x0f,0x00,0x00,0xf0,
+ 0x07,0x00,0x00,0xf0,0x07,0x00,0x00,0xe0,0x03,0x00,0x00,0xc0,0x01,0x00,0x00,
+ 0xc0,0x01,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/asnswe_curs.h b/vcl/inc/unx/x11_cursors/asnswe_curs.h
new file mode 100644
index 0000000000..e746fc59b9
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/asnswe_curs.h
@@ -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 .
+ */
+#define asnswe_curs_width 32
+#define asnswe_curs_height 32
+#define asnswe_curs_x_hot 15
+#define asnswe_curs_y_hot 15
+static unsigned char asnswe_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x80,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0xc0,0x01,0x00,0x00,0xe0,
+ 0x03,0x00,0x00,0xe0,0x03,0x00,0x00,0xf0,0x07,0x00,0x00,0x10,0x04,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x06,0x30,0x00,0x80,0xc3,0xe1,0x00,0xc0,0xe3,0xe3,0x01,
+ 0xf0,0xe3,0xe3,0x07,0xc0,0xe3,0xe3,0x01,0x80,0xc3,0xe1,0x00,0x00,0x06,0x30,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x04,0x00,0x00,0xf0,0x07,0x00,0x00,0xe0,
+ 0x03,0x00,0x00,0xe0,0x03,0x00,0x00,0xc0,0x01,0x00,0x00,0x80,0x00,0x00,0x00,
+ 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/asnswe_mask.h b/vcl/inc/unx/x11_cursors/asnswe_mask.h
new file mode 100644
index 0000000000..69bb087d31
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/asnswe_mask.h
@@ -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 .
+ */
+#define asnswe_mask_width 32
+#define asnswe_mask_height 32
+#define asnswe_mask_x_hot 15
+#define asnswe_mask_y_hot 15
+static unsigned char asnswe_mask_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,
+ 0x00,0x00,0xc0,0x01,0x00,0x00,0xc0,0x01,0x00,0x00,0xe0,0x03,0x00,0x00,0xf0,
+ 0x07,0x00,0x00,0xf0,0x07,0x00,0x00,0xf8,0x0f,0x00,0x00,0xf8,0x0f,0x00,0x00,
+ 0x16,0x34,0x00,0x80,0xcf,0xf9,0x00,0xc0,0xe7,0xf3,0x01,0xf0,0xf7,0xf7,0x07,
+ 0xf8,0xf7,0xf7,0x0f,0xf0,0xf7,0xf7,0x07,0xc0,0xe7,0xf3,0x01,0x80,0xcf,0xf9,
+ 0x00,0x00,0x16,0x34,0x00,0x00,0xf8,0x0f,0x00,0x00,0xf8,0x0f,0x00,0x00,0xf0,
+ 0x07,0x00,0x00,0xf0,0x07,0x00,0x00,0xe0,0x03,0x00,0x00,0xc0,0x01,0x00,0x00,
+ 0xc0,0x01,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/asnw_curs.h b/vcl/inc/unx/x11_cursors/asnw_curs.h
new file mode 100644
index 0000000000..67df6fb7b8
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/asnw_curs.h
@@ -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 .
+ */
+#define asnw_curs_width 32
+#define asnw_curs_height 32
+#define asnw_curs_x_hot 10
+#define asnw_curs_y_hot 10
+static unsigned char asnw_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3c,0x00,0x00,0x00,0xfc,0x01,0x00,
+ 0x00,0xfc,0x03,0x00,0x00,0xfc,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x38,0x00,
+ 0x00,0x00,0x18,0x0e,0x00,0x00,0x10,0x1f,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,
+ 0x1f,0x00,0x00,0x00,0x0e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/asnw_mask.h b/vcl/inc/unx/x11_cursors/asnw_mask.h
new file mode 100644
index 0000000000..15bc43bcff
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/asnw_mask.h
@@ -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 .
+ */
+#define asnw_mask_width 32
+#define asnw_mask_height 32
+#define asnw_mask_x_hot 10
+#define asnw_mask_y_hot 10
+static unsigned char asnw_mask_bits[] = {
+ 0x00,0x00,0x00,0x00,0x3e,0x00,0x00,0x00,0xfe,0x01,0x00,0x00,0xfe,0x03,0x00,
+ 0x00,0xfe,0x07,0x00,0x00,0xfe,0x03,0x00,0x00,0xfc,0x00,0x00,0x00,0x7c,0x0e,
+ 0x00,0x00,0x3c,0x1f,0x00,0x00,0xb8,0x3f,0x00,0x00,0x90,0x3f,0x00,0x00,0x80,
+ 0x3f,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0x0e,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/ass_curs.h b/vcl/inc/unx/x11_cursors/ass_curs.h
new file mode 100644
index 0000000000..4335c18218
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/ass_curs.h
@@ -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 .
+ */
+#define ass_curs_width 32
+#define ass_curs_height 32
+#define ass_curs_x_hot 15
+#define ass_curs_y_hot 19
+static unsigned char ass_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0x01,0x00,0x00,0xe0,0x03,
+ 0x00,0x00,0xe0,0x03,0x00,0x00,0xe0,0x03,0x00,0x00,0xc0,0x01,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x04,0x00,0x00,0xf0,0x07,0x00,0x00,
+ 0xe0,0x03,0x00,0x00,0xe0,0x03,0x00,0x00,0xc0,0x01,0x00,0x00,0x80,0x00,0x00,
+ 0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/ass_mask.h b/vcl/inc/unx/x11_cursors/ass_mask.h
new file mode 100644
index 0000000000..1ba699bc42
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/ass_mask.h
@@ -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 .
+ */
+#define ass_mask_width 32
+#define ass_mask_height 32
+#define ass_mask_x_hot 15
+#define ass_mask_y_hot 19
+static unsigned char ass_mask_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0xc0,0x01,0x00,0x00,0xe0,0x03,0x00,0x00,0xf0,0x07,
+ 0x00,0x00,0xf0,0x07,0x00,0x00,0xf0,0x07,0x00,0x00,0xe0,0x03,0x00,0x00,0xc0,
+ 0x01,0x00,0x00,0x10,0x04,0x00,0x00,0xf8,0x0f,0x00,0x00,0xf8,0x0f,0x00,0x00,
+ 0xf0,0x07,0x00,0x00,0xf0,0x07,0x00,0x00,0xe0,0x03,0x00,0x00,0xc0,0x01,0x00,
+ 0x00,0xc0,0x01,0x00,0x00,0x80,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/asse_curs.h b/vcl/inc/unx/x11_cursors/asse_curs.h
new file mode 100644
index 0000000000..ea3607beaa
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/asse_curs.h
@@ -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 .
+ */
+#define asse_curs_width 32
+#define asse_curs_height 32
+#define asse_curs_x_hot 21
+#define asse_curs_y_hot 21
+static unsigned char asse_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x70,0x00,0x00,0x00,0xf8,0x00,0x00,0x00,0xf8,0x00,0x00,0x00,
+ 0xf8,0x08,0x00,0x00,0x70,0x18,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x1c,0x00,
+ 0x00,0x00,0x3f,0x00,0x00,0xc0,0x3f,0x00,0x00,0x80,0x3f,0x00,0x00,0x00,0x3c,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/asse_mask.h b/vcl/inc/unx/x11_cursors/asse_mask.h
new file mode 100644
index 0000000000..3d366d8e12
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/asse_mask.h
@@ -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 .
+ */
+#define asse_mask_width 32
+#define asse_mask_height 32
+#define asse_mask_x_hot 21
+#define asse_mask_y_hot 21
+static unsigned char asse_mask_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,
+ 0x00,0x00,0x00,0xf8,0x00,0x00,0x00,0xfc,0x01,0x00,0x00,0xfc,0x09,0x00,0x00,
+ 0xfc,0x1d,0x00,0x00,0xf8,0x3c,0x00,0x00,0x70,0x3e,0x00,0x00,0x00,0x3f,0x00,
+ 0x00,0xc0,0x7f,0x00,0x00,0xe0,0x7f,0x00,0x00,0xc0,0x7f,0x00,0x00,0x80,0x7f,
+ 0x00,0x00,0x00,0x7c,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/assw_curs.h b/vcl/inc/unx/x11_cursors/assw_curs.h
new file mode 100644
index 0000000000..fe5645c285
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/assw_curs.h
@@ -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 .
+ */
+#define assw_curs_width 32
+#define assw_curs_height 32
+#define assw_curs_x_hot 10
+#define assw_curs_y_hot 21
+static unsigned char assw_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x0e,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0x1f,0x00,0x00,0x10,0x1f,
+ 0x00,0x00,0x18,0x0e,0x00,0x00,0x38,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0xfc,
+ 0x00,0x00,0x00,0xfc,0x03,0x00,0x00,0xfc,0x01,0x00,0x00,0x3c,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/assw_mask.h b/vcl/inc/unx/x11_cursors/assw_mask.h
new file mode 100644
index 0000000000..959a0600c1
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/assw_mask.h
@@ -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 .
+ */
+#define assw_mask_width 32
+#define assw_mask_height 32
+#define assw_mask_x_hot 10
+#define assw_mask_y_hot 21
+static unsigned char assw_mask_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x00,
+ 0x00,0x00,0x1F,0x00,0x00,0x80,0x3F,0x00,0x00,0x90,0x3F,0x00,0x00,0xB8,0x3F,
+ 0x00,0x00,0x3C,0x1F,0x00,0x00,0x7C,0x0E,0x00,0x00,0xFC,0x00,0x00,0x00,0xFE,
+ 0x03,0x00,0x00,0xFE,0x07,0x00,0x00,0xFE,0x03,0x00,0x00,0xFE,0x01,0x00,0x00,
+ 0x3E,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/asw_curs.h b/vcl/inc/unx/x11_cursors/asw_curs.h
new file mode 100644
index 0000000000..b3b4a56c4a
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/asw_curs.h
@@ -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 .
+ */
+#define asw_curs_width 32
+#define asw_curs_height 32
+#define asw_curs_x_hot 12
+#define asw_curs_y_hot 15
+static unsigned char asw_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0x70,0x38,0x00,0x00,0x78,0x7c,0x00,0x00,
+ 0x7e,0x7c,0x00,0x00,0x78,0x7c,0x00,0x00,0x70,0x38,0x00,0x00,0xc0,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/asw_mask.h b/vcl/inc/unx/x11_cursors/asw_mask.h
new file mode 100644
index 0000000000..ad85d47f7c
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/asw_mask.h
@@ -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 .
+ */
+#define asw_mask_width 32
+#define asw_mask_height 32
+#define asw_mask_x_hot 12
+#define asw_mask_y_hot 15
+static unsigned char asw_mask_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,
+ 0x00,0x00,0x00,0xf0,0x39,0x00,0x00,0xf8,0x7c,0x00,0x00,0xfe,0xfe,0x00,0x00,
+ 0xff,0xfe,0x00,0x00,0xfe,0xfe,0x00,0x00,0xf8,0x7c,0x00,0x00,0xf0,0x39,0x00,
+ 0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/aswe_curs.h b/vcl/inc/unx/x11_cursors/aswe_curs.h
new file mode 100644
index 0000000000..0f7ed0fc1a
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/aswe_curs.h
@@ -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 .
+ */
+#define aswe_curs_width 32
+#define aswe_curs_height 32
+#define aswe_curs_x_hot 15
+#define aswe_curs_y_hot 15
+static unsigned char aswe_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x06,0x30,0x00,0x80,0xc3,0xe1,0x00,0xc0,0xe3,0xe3,0x01,
+ 0xf0,0xe3,0xe3,0x07,0xc0,0xe3,0xe3,0x01,0x80,0xc3,0xe1,0x00,0x00,0x06,0x30,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/aswe_mask.h b/vcl/inc/unx/x11_cursors/aswe_mask.h
new file mode 100644
index 0000000000..24e1050582
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/aswe_mask.h
@@ -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 .
+ */
+#define aswe_mask_width 32
+#define aswe_mask_height 32
+#define aswe_mask_x_hot 15
+#define aswe_mask_y_hot 15
+static unsigned char aswe_mask_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x06,0x30,0x00,0x80,0xcf,0xf9,0x00,0xc0,0xe7,0xf3,0x01,0xf0,0xf7,0xf7,0x07,
+ 0xf8,0xf7,0xf7,0x0f,0xf0,0xf7,0xf7,0x07,0xc0,0xe7,0xf3,0x01,0x80,0xcf,0xf9,
+ 0x00,0x00,0x06,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/chain_curs.h b/vcl/inc/unx/x11_cursors/chain_curs.h
new file mode 100644
index 0000000000..26c055225d
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/chain_curs.h
@@ -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 .
+ */
+#define chain_curs_width 32
+#define chain_curs_height 32
+#define chain_curs_x_hot 0
+#define chain_curs_y_hot 2
+static unsigned char chain_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x03,0x00,0x00,
+ 0x00,0x05,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x21,0x00,
+ 0x00,0x00,0x41,0x00,0x00,0x00,0x81,0x00,0x00,0x00,0x01,0x01,0x00,0x00,0x01,
+ 0x02,0x00,0x00,0x01,0x04,0x00,0x00,0x81,0x0f,0x00,0x00,0x91,0x00,0x00,0x00,
+ 0x99,0x00,0x00,0x00,0x25,0x01,0x00,0x00,0x23,0x01,0x00,0x00,0x41,0x3e,0xbf,
+ 0x0f,0x40,0x82,0x40,0x10,0x80,0x5c,0xae,0x23,0x80,0x24,0x91,0x24,0x00,0x23,
+ 0x91,0x28,0x80,0x24,0x91,0x28,0x80,0x24,0x91,0x24,0x80,0x98,0x4f,0x23,0x00,
+ 0x41,0x20,0x10,0x00,0x3e,0xde,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/chain_mask.h b/vcl/inc/unx/x11_cursors/chain_mask.h
new file mode 100644
index 0000000000..4b25c9c4b3
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/chain_mask.h
@@ -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 .
+ */
+#define chain_mask_width 32
+#define chain_mask_height 32
+static unsigned char chain_mask_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x03,0x00,0x00,
+ 0x00,0x07,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0x3f,0x00,
+ 0x00,0x00,0x7f,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x01,0x00,0x00,0xff,
+ 0x03,0x00,0x00,0xff,0x07,0x00,0x00,0xff,0x0f,0x00,0x00,0xff,0x00,0x00,0x00,
+ 0xff,0x00,0x00,0x00,0xe7,0x01,0x00,0x00,0xe3,0x01,0x00,0x00,0xc1,0x3f,0xbf,
+ 0x0f,0xc0,0xbf,0xff,0x1f,0x80,0xdf,0xff,0x3f,0x80,0xe7,0xf1,0x3c,0x00,0xe3,
+ 0xf1,0x38,0x80,0xe7,0xf1,0x38,0x80,0xe7,0xf1,0x3c,0x80,0xff,0xff,0x3f,0x00,
+ 0x7f,0xff,0x1f,0x00,0x3e,0xde,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/chainnot_curs.h b/vcl/inc/unx/x11_cursors/chainnot_curs.h
new file mode 100644
index 0000000000..9af6f79d35
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/chainnot_curs.h
@@ -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 .
+ */
+#define chainnot_curs_width 32
+#define chainnot_curs_height 32
+#define chainnot_curs_x_hot 2
+#define chainnot_curs_y_hot 2
+static unsigned char chainnot_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x80,0x1f,0x00,0x00,0xe0,0x7f,0x00,0x00,0xf0,0xf0,0x00,
+ 0x00,0x38,0xc0,0x01,0x00,0x7c,0x80,0x03,0x00,0xec,0x00,0x03,0x00,0xce,0x01,
+ 0x07,0x00,0x86,0x03,0x06,0x00,0x06,0x07,0x06,0x00,0x06,0x0e,0x06,0x00,0x06,
+ 0x1c,0x06,0x00,0x0e,0x38,0x07,0x00,0x0c,0x70,0x03,0x00,0x1c,0xe0,0x03,0x00,
+ 0x38,0xc0,0x01,0x00,0xf0,0xe0,0x00,0x00,0xe0,0x7f,0x00,0x00,0x80,0x9f,0xfc,
+ 0x3e,0x00,0x00,0x02,0x41,0x00,0x72,0xb9,0x8e,0x00,0x92,0x44,0x92,0x00,0x8c,
+ 0x44,0xa2,0x00,0x92,0x44,0xa2,0x00,0x92,0x44,0x92,0x00,0x62,0x3e,0x8d,0x00,
+ 0x04,0x81,0x40,0x00,0xf8,0x78,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/chainnot_mask.h b/vcl/inc/unx/x11_cursors/chainnot_mask.h
new file mode 100644
index 0000000000..e5e24e0c7e
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/chainnot_mask.h
@@ -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 .
+ */
+#define chainnot_mask_width 32
+#define chainnot_mask_height 32
+static unsigned char chainnot_mask_bits[] = {
+ 0x80,0x1f,0x00,0x00,0xe0,0x7f,0x00,0x00,0xf0,0xff,0x00,0x00,0xf8,0xff,0x01,
+ 0x00,0xfc,0xf0,0x03,0x00,0xfe,0xc0,0x07,0x00,0xfe,0x81,0x07,0x00,0xff,0x83,
+ 0x0f,0x00,0xcf,0x07,0x0f,0x00,0x8f,0x0f,0x0f,0x00,0x0f,0x1f,0x0f,0x00,0x0f,
+ 0x3e,0x0f,0x00,0x1f,0xfc,0x0f,0x00,0x1e,0xf8,0x07,0x00,0x3e,0xf0,0x07,0x00,
+ 0xfc,0xe0,0x03,0x00,0xf8,0xff,0x01,0x00,0xf0,0xff,0x00,0x00,0xe0,0xff,0xfc,
+ 0x3e,0x80,0xff,0xfe,0x7f,0x00,0x7e,0xff,0xff,0x00,0x9e,0xc7,0xf3,0x00,0x8c,
+ 0xc7,0xe3,0x00,0x9e,0xc7,0xe3,0x00,0x9e,0xc7,0xf3,0x00,0xfe,0xff,0xff,0x00,
+ 0xfc,0xfd,0x7f,0x00,0xf8,0x78,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/chart_curs.h b/vcl/inc/unx/x11_cursors/chart_curs.h
new file mode 100644
index 0000000000..367f6b05c9
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/chart_curs.h
@@ -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 .
+ */
+#define chart_curs_width 32
+#define chart_curs_height 32
+#define chart_curs_x_hot 15
+#define chart_curs_y_hot 16
+static unsigned char chart_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x80,0x00,0x00,0x00,
+ 0x80,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x80,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0xbf,0x7e,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,
+ 0x00,0x00,0x80,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x80,
+ 0x10,0x00,0x00,0x80,0x00,0x06,0x00,0x00,0x10,0x06,0x00,0x00,0x00,0x06,0x00,
+ 0x00,0x10,0x36,0x00,0x00,0xc0,0x36,0x00,0x00,0xd0,0x36,0x00,0x00,0xc0,0x36,
+ 0x00,0x00,0xf0,0x7f,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/chart_mask.h b/vcl/inc/unx/x11_cursors/chart_mask.h
new file mode 100644
index 0000000000..6f69770628
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/chart_mask.h
@@ -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 .
+ */
+#define chart_mask_width 32
+#define chart_mask_height 32
+#define chart_mask_x_hot 15
+#define chart_mask_y_hot 16
+static unsigned char chart_mask_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0xc0,0x01,0x00,0x00,0xc0,0x01,0x00,0x00,0xc0,0x01,0x00,0x00,
+ 0xc0,0x01,0x00,0x00,0xc0,0x01,0x00,0x00,0xc0,0x01,0x00,0x00,0xc0,0x01,0x00,
+ 0x80,0xff,0xff,0x00,0x80,0xff,0xff,0x00,0x80,0xff,0xff,0x00,0x00,0xc0,0x01,
+ 0x00,0x00,0xc0,0x01,0x00,0x00,0xc0,0x01,0x00,0x00,0xc0,0x39,0x00,0x00,0xc0,
+ 0x39,0x0f,0x00,0xc0,0x39,0x0f,0x00,0xc0,0x39,0x0f,0x00,0x00,0x38,0x7f,0x00,
+ 0x00,0xf8,0x7f,0x00,0x00,0xf8,0x7f,0x00,0x00,0xf8,0x7f,0x00,0x00,0xf8,0xff,
+ 0x00,0x00,0xf8,0xff,0x00,0x00,0xf8,0xff};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/copydata_curs.h b/vcl/inc/unx/x11_cursors/copydata_curs.h
new file mode 100644
index 0000000000..4cc36ebdeb
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/copydata_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define copydata_curs_width 32
+#define copydata_curs_height 32
+#define copydata_curs_x_hot 1
+#define copydata_curs_y_hot 1
+static unsigned char copydata_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
+ 0x7e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00,
+ 0xfe, 0x03, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00,
+ 0x66, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
+ 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x10, 0x53, 0x00, 0x00,
+ 0x28, 0xa3, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x10, 0xf0, 0x1f, 0x00, 0x08, 0xf0, 0x1f, 0x00, 0x10, 0xf0, 0x1e, 0x00,
+ 0xa8, 0xf2, 0x1e, 0x00, 0x50, 0x35, 0x18, 0x00, 0x00, 0xf0, 0x1e, 0x00,
+ 0x00, 0xf0, 0x1e, 0x00, 0x00, 0xf0, 0x1f, 0x00, 0x00, 0xf0, 0x1f, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/copydata_mask.h b/vcl/inc/unx/x11_cursors/copydata_mask.h
new file mode 100644
index 0000000000..a3538c9522
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/copydata_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define copydata_mask_width 32
+#define copydata_mask_height 32
+#define copydata_mask_x_hot 1
+#define copydata_mask_y_hot 1
+static unsigned char copydata_mask_bits[] = {
+ 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+ 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00,
+ 0xe0, 0x03, 0x00, 0x00, 0xf8, 0xff, 0x00, 0x00, 0xfc, 0xff, 0x01, 0x00,
+ 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0x3c, 0xf8, 0x3f, 0x00,
+ 0x3c, 0xf8, 0x3f, 0x00, 0x3c, 0xf8, 0x3f, 0x00, 0xfc, 0xff, 0x3f, 0x00,
+ 0xfc, 0xff, 0x3f, 0x00, 0xfc, 0xff, 0x3f, 0x00, 0xf8, 0xff, 0x3f, 0x00,
+ 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00,
+ 0x00, 0xf8, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/copydlnk_curs.h b/vcl/inc/unx/x11_cursors/copydlnk_curs.h
new file mode 100644
index 0000000000..df05429e95
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/copydlnk_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define copydlnk_curs_width 32
+#define copydlnk_curs_height 32
+#define copydlnk_curs_x_hot 1
+#define copydlnk_curs_y_hot 1
+static unsigned char copydlnk_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
+ 0x7e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00,
+ 0xfe, 0x03, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00,
+ 0x66, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
+ 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x10, 0x53, 0x00, 0x00,
+ 0x28, 0xa3, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0xf0, 0x01, 0x00, 0x00,
+ 0x30, 0xf1, 0x1f, 0x00, 0x10, 0xf1, 0x1f, 0x00, 0xd0, 0xf1, 0x1e, 0x00,
+ 0xf0, 0xf1, 0x1e, 0x00, 0x00, 0x34, 0x18, 0x00, 0x00, 0xf0, 0x1e, 0x00,
+ 0x00, 0xf0, 0x1e, 0x00, 0x00, 0xf0, 0x1f, 0x00, 0x00, 0xf0, 0x1f, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/copydlnk_mask.h b/vcl/inc/unx/x11_cursors/copydlnk_mask.h
new file mode 100644
index 0000000000..b2ffcca622
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/copydlnk_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define copydlnk_mask_width 32
+#define copydlnk_mask_height 32
+#define copydlnk_mask_x_hot 1
+#define copydlnk_mask_y_hot 1
+static unsigned char copydlnk_mask_bits[] = {
+ 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+ 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00,
+ 0xe0, 0x03, 0x00, 0x00, 0xf8, 0xff, 0x00, 0x00, 0xfc, 0xff, 0x01, 0x00,
+ 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0xf8, 0xff, 0x3f, 0x00,
+ 0xf8, 0xff, 0x3f, 0x00, 0xf8, 0xff, 0x3f, 0x00, 0xf8, 0xff, 0x3f, 0x00,
+ 0xf8, 0xff, 0x3f, 0x00, 0xf8, 0xff, 0x3f, 0x00, 0x00, 0xfe, 0x3f, 0x00,
+ 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00,
+ 0x00, 0xf8, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/copyfile_curs.h b/vcl/inc/unx/x11_cursors/copyfile_curs.h
new file mode 100644
index 0000000000..22d9fc6ffa
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/copyfile_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define copyfile_curs_width 32
+#define copyfile_curs_height 32
+#define copyfile_curs_x_hot 9
+#define copyfile_curs_y_hot 9
+static unsigned char copyfile_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x02, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0xfe, 0x04, 0x00, 0x00,
+ 0xfe, 0x02, 0x00, 0x00, 0xfe, 0x06, 0x00, 0x00, 0xfe, 0x0e, 0x00, 0x00,
+ 0xfe, 0x1e, 0x00, 0x00, 0xfe, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00,
+ 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x03, 0x00,
+ 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00,
+ 0x00, 0xc2, 0xe0, 0x3f, 0x00, 0xc0, 0xe0, 0x3f, 0x00, 0x80, 0xe1, 0x3d,
+ 0x00, 0x80, 0xe1, 0x3d, 0x00, 0x00, 0x63, 0x30, 0x00, 0x00, 0xe3, 0x3d,
+ 0x00, 0x00, 0xe0, 0x3d, 0x00, 0x00, 0xe0, 0x3f, 0x00, 0x00, 0xe0, 0x3f,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/copyfile_mask.h b/vcl/inc/unx/x11_cursors/copyfile_mask.h
new file mode 100644
index 0000000000..171d8b7bc3
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/copyfile_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define copyfile_mask_width 32
+#define copyfile_mask_height 32
+#define copyfile_mask_x_hot 9
+#define copyfile_mask_y_hot 9
+static unsigned char copyfile_mask_bits[] = {
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x1f, 0x00, 0x00, 0xff, 0x3f, 0x00, 0x00,
+ 0xff, 0x7f, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00,
+ 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00,
+ 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xf1, 0x7f,
+ 0x00, 0xff, 0xf1, 0x7f, 0x00, 0xe7, 0xf3, 0x7f, 0x00, 0xe0, 0xf3, 0x7f,
+ 0x00, 0xc0, 0xf7, 0x7f, 0x00, 0xc0, 0xf7, 0x7f, 0x00, 0x80, 0xf7, 0x7f,
+ 0x00, 0x80, 0xf7, 0x7f, 0x00, 0x00, 0xf0, 0x7f, 0x00, 0x00, 0xf0, 0x7f,
+ 0x00, 0x00, 0xf0, 0x7f, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/copyfiles_curs.h b/vcl/inc/unx/x11_cursors/copyfiles_curs.h
new file mode 100644
index 0000000000..2a80417958
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/copyfiles_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define copyfiles_curs_width 32
+#define copyfiles_curs_height 32
+#define copyfiles_curs_x_hot 8
+#define copyfiles_curs_y_hot 9
+static unsigned char copyfiles_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xe0, 0x0f, 0x00, 0x00, 0xe0, 0x2f, 0x00, 0x00,
+ 0xe8, 0x0f, 0x00, 0x00, 0xe8, 0x7f, 0x00, 0x00, 0xea, 0x7f, 0x00, 0x00,
+ 0xea, 0x7f, 0x00, 0x00, 0xea, 0x7f, 0x00, 0x00, 0x6a, 0x7e, 0x00, 0x00,
+ 0x6a, 0x7d, 0x00, 0x00, 0x6a, 0x7b, 0x00, 0x00, 0x6a, 0x77, 0x00, 0x00,
+ 0x6a, 0x6f, 0x00, 0x00, 0x6a, 0x5f, 0x00, 0x00, 0x0a, 0x3f, 0x00, 0x00,
+ 0x7a, 0x7f, 0x00, 0x00, 0x02, 0xff, 0x00, 0x00, 0x7e, 0xff, 0x01, 0x00,
+ 0x00, 0x3f, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00,
+ 0x00, 0x61, 0xe0, 0x3f, 0x00, 0x60, 0xe0, 0x3f, 0x00, 0xc0, 0xe0, 0x3d,
+ 0x00, 0xc0, 0xe0, 0x3d, 0x00, 0x80, 0x61, 0x30, 0x00, 0x80, 0xe1, 0x3d,
+ 0x00, 0x00, 0xe0, 0x3d, 0x00, 0x00, 0xe0, 0x3f, 0x00, 0x00, 0xe0, 0x3f,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/copyfiles_mask.h b/vcl/inc/unx/x11_cursors/copyfiles_mask.h
new file mode 100644
index 0000000000..02f2d4c12d
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/copyfiles_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define copyfiles_mask_width 32
+#define copyfiles_mask_height 32
+#define copyfiles_mask_x_hot 8
+#define copyfiles_mask_y_hot 9
+static unsigned char copyfiles_mask_bits[] = {
+ 0xf0, 0x1f, 0x00, 0x00, 0xf0, 0x7f, 0x00, 0x00, 0xfc, 0x7f, 0x00, 0x00,
+ 0xfc, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
+ 0xff, 0xff, 0x01, 0x00, 0xff, 0xff, 0x03, 0x00, 0xff, 0xff, 0x03, 0x00,
+ 0xff, 0xff, 0x03, 0x00, 0x80, 0x7f, 0x00, 0x00, 0x80, 0xff, 0xf0, 0x7f,
+ 0x80, 0xff, 0xf0, 0x7f, 0x80, 0xf3, 0xf1, 0x7f, 0x00, 0xf0, 0xf1, 0x7f,
+ 0x00, 0xe0, 0xf3, 0x7f, 0x00, 0xe0, 0xf3, 0x7f, 0x00, 0xc0, 0xf3, 0x7f,
+ 0x00, 0xc0, 0xf3, 0x7f, 0x00, 0x00, 0xf0, 0x7f, 0x00, 0x00, 0xf0, 0x7f,
+ 0x00, 0x00, 0xf0, 0x7f, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/copyflnk_curs.h b/vcl/inc/unx/x11_cursors/copyflnk_curs.h
new file mode 100644
index 0000000000..43629e23a8
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/copyflnk_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define copyflnk_curs_width 32
+#define copyflnk_curs_height 32
+#define copyflnk_curs_x_hot 9
+#define copyflnk_curs_y_hot 9
+static unsigned char copyflnk_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x02, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0x80, 0x04, 0x00, 0x00,
+ 0xbe, 0x02, 0x00, 0x00, 0xa6, 0x06, 0x00, 0x00, 0xa2, 0x0e, 0x00, 0x00,
+ 0xba, 0x1e, 0x00, 0x00, 0xbe, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00,
+ 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x03, 0x00,
+ 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00,
+ 0x00, 0xc2, 0xe0, 0x3f, 0x00, 0xc0, 0xe0, 0x3f, 0x00, 0x80, 0xe1, 0x3d,
+ 0x00, 0x80, 0xe1, 0x3d, 0x00, 0x00, 0x63, 0x30, 0x00, 0x00, 0xe3, 0x3d,
+ 0x00, 0x00, 0xe0, 0x3d, 0x00, 0x00, 0xe0, 0x3f, 0x00, 0x00, 0xe0, 0x3f,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/copyflnk_mask.h b/vcl/inc/unx/x11_cursors/copyflnk_mask.h
new file mode 100644
index 0000000000..cd17b334c8
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/copyflnk_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define copyflnk_mask_width 32
+#define copyflnk_mask_height 32
+#define copyflnk_mask_x_hot 9
+#define copyflnk_mask_y_hot 9
+static unsigned char copyflnk_mask_bits[] = {
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x1f, 0x00, 0x00, 0xff, 0x3f, 0x00, 0x00,
+ 0xff, 0x7f, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00,
+ 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00,
+ 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xf1, 0x7f,
+ 0x00, 0xff, 0xf1, 0x7f, 0x00, 0xe7, 0xf3, 0x7f, 0x00, 0xe0, 0xf3, 0x7f,
+ 0x00, 0xc0, 0xf7, 0x7f, 0x00, 0xc0, 0xf7, 0x7f, 0x00, 0x80, 0xf7, 0x7f,
+ 0x00, 0x80, 0xf7, 0x7f, 0x00, 0x00, 0xf0, 0x7f, 0x00, 0x00, 0xf0, 0x7f,
+ 0x00, 0x00, 0xf0, 0x7f, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/crook_curs.h b/vcl/inc/unx/x11_cursors/crook_curs.h
new file mode 100644
index 0000000000..989d43b549
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/crook_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define crook_curs_width 32
+#define crook_curs_height 32
+#define crook_curs_x_hot 15
+#define crook_curs_y_hot 14
+static unsigned char crook_curs_bits[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0x7c, 0x3e, 0xff, 0x7f, 0xbb, 0xdd, 0xfe,
+ 0x7f, 0xbb, 0xdd, 0xfe, 0xf3, 0xb6, 0x6d, 0xcf, 0xed, 0xb6, 0x6d, 0xb7,
+ 0xdd, 0x75, 0xae, 0xbb, 0xbb, 0x0b, 0xd0, 0xdd, 0xb7, 0xf1, 0x8f, 0xed,
+ 0x4f, 0x0e, 0x70, 0xf2, 0xbf, 0xf1, 0x8f, 0xfd, 0x5f, 0xfe, 0x7f, 0xfa,
+ 0xaf, 0xff, 0xff, 0xf5, 0xd7, 0xff, 0xff, 0xeb, 0xef, 0xff, 0xff, 0xf7,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/crook_mask.h b/vcl/inc/unx/x11_cursors/crook_mask.h
new file mode 100644
index 0000000000..6e30897e59
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/crook_mask.h
@@ -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 .
+ */
+#define crook_mask_width 32
+#define crook_mask_height 32
+static unsigned char crook_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x83, 0xc1, 0x00, 0x80, 0xc7, 0xe3, 0x01, 0xc0, 0xef, 0xf7, 0x03,
+ 0xcc, 0xef, 0xf7, 0x33, 0x9e, 0xff, 0xff, 0x79, 0xbf, 0xff, 0xff, 0xfd,
+ 0x77, 0xff, 0xff, 0xee, 0xee, 0xf6, 0x6f, 0x77, 0xfc, 0xff, 0xff, 0x3f,
+ 0xb8, 0xff, 0xff, 0x1d, 0xf0, 0xff, 0xff, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f,
+ 0xf8, 0x01, 0x80, 0x1f, 0x7c, 0x00, 0x00, 0x3e, 0x38, 0x00, 0x00, 0x1c,
+ 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/crop_curs.h b/vcl/inc/unx/x11_cursors/crop_curs.h
new file mode 100644
index 0000000000..f408422571
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/crop_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define crop_curs_width 32
+#define crop_curs_height 32
+#define crop_curs_x_hot 9
+#define crop_curs_y_hot 9
+static unsigned char crop_curs_bits[] = {
+ 0xff, 0x0f, 0xff, 0xff, 0xff, 0x6f, 0xff, 0xff, 0xff, 0x6f, 0xff, 0xff,
+ 0x07, 0x60, 0xf8, 0xff, 0xf7, 0x6f, 0xfb, 0xff, 0xf7, 0x6f, 0xfb, 0xff,
+ 0x37, 0x60, 0xf8, 0xff, 0xb7, 0x6f, 0xff, 0xff, 0xb7, 0x6f, 0xff, 0xff,
+ 0xb7, 0x6f, 0xff, 0xff, 0xb7, 0x6f, 0xff, 0xff, 0xb7, 0x6f, 0xff, 0xff,
+ 0x30, 0x60, 0xff, 0xff, 0xb6, 0x7f, 0xff, 0xff, 0xb6, 0x7f, 0xff, 0xff,
+ 0x30, 0x00, 0xff, 0xff, 0xb7, 0xff, 0xff, 0xff, 0xb7, 0xff, 0xff, 0xff,
+ 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/crop_mask.h b/vcl/inc/unx/x11_cursors/crop_mask.h
new file mode 100644
index 0000000000..10d3598af6
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/crop_mask.h
@@ -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 .
+ */
+#define crop_mask_width 32
+#define crop_mask_height 32
+static unsigned char crop_mask_bits[] = {
+ 0x00, 0xf8, 0x01, 0x00, 0x00, 0xf8, 0x01, 0x00, 0xfc, 0xff, 0x0f, 0x00,
+ 0xfc, 0xff, 0x0f, 0x00, 0xfc, 0xff, 0x0f, 0x00, 0xfc, 0xff, 0x0f, 0x00,
+ 0xfc, 0xff, 0x0f, 0x00, 0xfc, 0xff, 0x0f, 0x00, 0xfc, 0xf8, 0x01, 0x00,
+ 0xfc, 0xf8, 0x01, 0x00, 0xfc, 0xf8, 0x01, 0x00, 0xff, 0xff, 0x01, 0x00,
+ 0xff, 0xff, 0x01, 0x00, 0xff, 0xff, 0x01, 0x00, 0xff, 0xff, 0x01, 0x00,
+ 0xff, 0xff, 0x01, 0x00, 0xff, 0xff, 0x01, 0x00, 0xfc, 0x00, 0x00, 0x00,
+ 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/detective_curs.h b/vcl/inc/unx/x11_cursors/detective_curs.h
new file mode 100644
index 0000000000..265be0fa2f
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/detective_curs.h
@@ -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 .
+ */
+#define detective_curs_width 32
+#define detective_curs_height 32
+#define detective_curs_x_hot 12
+#define detective_curs_y_hot 13
+static unsigned char detective_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x38,0x00,
+ 0x00,0x00,0x10,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7c,
+ 0x00,0x00,0x00,0x83,0x01,0x00,0x80,0x00,0x02,0x00,0x80,0x10,0x02,0x00,0x40,
+ 0x38,0x04,0x00,0x40,0x7c,0x04,0x00,0x40,0xfe,0x04,0x00,0x40,0x38,0x04,0x00,
+ 0x40,0x38,0x04,0x00,0x80,0x38,0x02,0x00,0x80,0x00,0x02,0x00,0x00,0x83,0x07,
+ 0x00,0x00,0x7c,0x0e,0x00,0x00,0x00,0x1c,0x00,0x00,0x10,0x38,0x00,0x00,0x38,
+ 0x70,0x00,0x00,0x10,0x60,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/detective_mask.h b/vcl/inc/unx/x11_cursors/detective_mask.h
new file mode 100644
index 0000000000..411e8a39d2
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/detective_mask.h
@@ -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 .
+ */
+#define detective_mask_width 32
+#define detective_mask_height 32
+#define detective_mask_x_hot 12
+#define detective_mask_y_hot 13
+static unsigned char detective_mask_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x38,0x00,
+ 0x00,0x00,0x10,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7c,
+ 0x00,0x00,0x00,0xff,0x01,0x00,0x80,0xff,0x03,0x00,0x80,0xff,0x03,0x00,0xc0,
+ 0xff,0x07,0x00,0xc0,0xff,0x07,0x00,0xc0,0xff,0x07,0x00,0xc0,0xff,0x07,0x00,
+ 0xc0,0xff,0x07,0x00,0x80,0xff,0x03,0x00,0x80,0xff,0x03,0x00,0x00,0xff,0x07,
+ 0x00,0x00,0x7c,0x0e,0x00,0x00,0x00,0x1c,0x00,0x00,0x10,0x38,0x00,0x00,0x38,
+ 0x70,0x00,0x00,0x10,0x60,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawarc_curs.h b/vcl/inc/unx/x11_cursors/drawarc_curs.h
new file mode 100644
index 0000000000..17edc92db6
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawarc_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define drawarc_curs_width 32
+#define drawarc_curs_height 32
+#define drawarc_curs_x_hot 7
+#define drawarc_curs_y_hot 7
+static unsigned char drawarc_curs_bits[] = {
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x42, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawarc_mask.h b/vcl/inc/unx/x11_cursors/drawarc_mask.h
new file mode 100644
index 0000000000..6c0c01754d
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawarc_mask.h
@@ -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 .
+ */
+#define drawarc_mask_width 32
+#define drawarc_mask_height 32
+static unsigned char drawarc_mask_bits[] = {
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x3f, 0x7e, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x3f, 0x7e, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0xff, 0x00,
+ 0x00, 0x80, 0xe7, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00,
+ 0x00, 0x80, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawbezier_curs.h b/vcl/inc/unx/x11_cursors/drawbezier_curs.h
new file mode 100644
index 0000000000..5470e8d6dc
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawbezier_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define drawbezier_curs_width 32
+#define drawbezier_curs_height 32
+#define drawbezier_curs_x_hot 7
+#define drawbezier_curs_y_hot 7
+static unsigned char drawbezier_curs_bits[] = {
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x07, 0x00, 0x00, 0x88, 0x00,
+ 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00,
+ 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x0e, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawbezier_mask.h b/vcl/inc/unx/x11_cursors/drawbezier_mask.h
new file mode 100644
index 0000000000..b3b1282618
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawbezier_mask.h
@@ -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 .
+ */
+#define drawbezier_mask_width 32
+#define drawbezier_mask_height 32
+static unsigned char drawbezier_mask_bits[] = {
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x3f, 0x7e, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x3f, 0x7e, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x0e, 0x0f, 0x00, 0x00, 0x8e, 0x0f, 0x00, 0x00, 0xdc, 0x0f,
+ 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xe0, 0x00,
+ 0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, 0xbf, 0x03, 0x00, 0x00, 0x1f, 0x07,
+ 0x00, 0x00, 0x0f, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawcaption_curs.h b/vcl/inc/unx/x11_cursors/drawcaption_curs.h
new file mode 100644
index 0000000000..d16c2103b8
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawcaption_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define drawcaption_curs_width 32
+#define drawcaption_curs_height 32
+#define drawcaption_curs_x_hot 8
+#define drawcaption_curs_y_hot 8
+static unsigned char drawcaption_curs_bits[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
+ 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
+ 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0x02, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
+ 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xbe, 0xff, 0xff,
+ 0xff, 0x7e, 0x1f, 0xe0, 0xff, 0xff, 0xde, 0xef, 0xff, 0xff, 0xc1, 0xef,
+ 0xff, 0xff, 0xdf, 0xef, 0xff, 0xff, 0x1f, 0xe0, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawcaption_mask.h b/vcl/inc/unx/x11_cursors/drawcaption_mask.h
new file mode 100644
index 0000000000..24a6643a00
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawcaption_mask.h
@@ -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 .
+ */
+#define drawcaption_mask_width 32
+#define drawcaption_mask_height 32
+static unsigned char drawcaption_mask_bits[] = {
+ 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00,
+ 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00,
+ 0x80, 0x03, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0xff, 0xff, 0x01, 0x00,
+ 0xff, 0xff, 0x01, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00,
+ 0x80, 0x03, 0x00, 0x00, 0x80, 0x43, 0x00, 0x00, 0x80, 0xe3, 0xf0, 0x3f,
+ 0x80, 0xc3, 0xf1, 0x3f, 0x80, 0x83, 0xff, 0x3f, 0x00, 0x00, 0x7f, 0x38,
+ 0x00, 0x00, 0xfe, 0x3f, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, 0xf0, 0x3f,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawcirclecut_curs.h b/vcl/inc/unx/x11_cursors/drawcirclecut_curs.h
new file mode 100644
index 0000000000..35939eb26f
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawcirclecut_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define drawcirclecut_curs_width 32
+#define drawcirclecut_curs_height 32
+#define drawcirclecut_curs_x_hot 7
+#define drawcirclecut_curs_y_hot 7
+static unsigned char drawcirclecut_curs_bits[] = {
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00,
+ 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x41, 0x00,
+ 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x3c, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawcirclecut_mask.h b/vcl/inc/unx/x11_cursors/drawcirclecut_mask.h
new file mode 100644
index 0000000000..eeead07a42
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawcirclecut_mask.h
@@ -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 .
+ */
+#define drawcirclecut_mask_width 32
+#define drawcirclecut_mask_height 32
+static unsigned char drawcirclecut_mask_bits[] = {
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x3f, 0x7e, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x3f, 0x7e, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x1f, 0x00,
+ 0x00, 0x80, 0x3b, 0x00, 0x00, 0x80, 0x73, 0x00, 0x00, 0x80, 0xe3, 0x00,
+ 0x00, 0x80, 0xc3, 0x01, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x7e, 0x00,
+ 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawconnect_curs.h b/vcl/inc/unx/x11_cursors/drawconnect_curs.h
new file mode 100644
index 0000000000..adad711d14
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawconnect_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define drawconnect_curs_width 32
+#define drawconnect_curs_height 32
+#define drawconnect_curs_x_hot 7
+#define drawconnect_curs_y_hot 7
+static unsigned char drawconnect_curs_bits[] = {
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x80, 0x5f, 0x00, 0x00, 0x80, 0x70,
+ 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x87, 0x00, 0x00, 0x00, 0xfd, 0x00,
+ 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawconnect_mask.h b/vcl/inc/unx/x11_cursors/drawconnect_mask.h
new file mode 100644
index 0000000000..566a134c77
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawconnect_mask.h
@@ -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 .
+ */
+#define drawconnect_mask_width 32
+#define drawconnect_mask_height 32
+static unsigned char drawconnect_mask_bits[] = {
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x3f, 0x7e, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x3f, 0x7e, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8,
+ 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0xc0, 0xdf, 0x00, 0x00, 0xc0, 0xff,
+ 0x00, 0x80, 0xcf, 0xf9, 0x00, 0x80, 0xff, 0x01, 0x00, 0x80, 0xfd, 0x01,
+ 0x00, 0x80, 0xff, 0x01, 0x00, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawellipse_curs.h b/vcl/inc/unx/x11_cursors/drawellipse_curs.h
new file mode 100644
index 0000000000..36e8862634
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawellipse_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define drawellipse_curs_width 32
+#define drawellipse_curs_height 32
+#define drawellipse_curs_x_hot 7
+#define drawellipse_curs_y_hot 7
+static unsigned char drawellipse_curs_bits[] = {
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x42, 0x00,
+ 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x81, 0x00,
+ 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x3c, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawellipse_mask.h b/vcl/inc/unx/x11_cursors/drawellipse_mask.h
new file mode 100644
index 0000000000..304db762bd
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawellipse_mask.h
@@ -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 .
+ */
+#define drawellipse_mask_width 32
+#define drawellipse_mask_height 32
+static unsigned char drawellipse_mask_bits[] = {
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x3f, 0x7e, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x3f, 0x7e, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0xff, 0x00,
+ 0x00, 0x80, 0xe7, 0x01, 0x00, 0x80, 0xc3, 0x01, 0x00, 0x80, 0xc3, 0x01,
+ 0x00, 0x80, 0xe7, 0x01, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x7e, 0x00,
+ 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawfreehand_curs.h b/vcl/inc/unx/x11_cursors/drawfreehand_curs.h
new file mode 100644
index 0000000000..b00d9be980
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawfreehand_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define drawfreehand_curs_width 32
+#define drawfreehand_curs_height 32
+#define drawfreehand_curs_x_hot 8
+#define drawfreehand_curs_y_hot 8
+static unsigned char drawfreehand_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xfd, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x70, 0x00, 0x02,
+ 0x00, 0x88, 0x00, 0x02, 0x00, 0x84, 0x00, 0x01, 0x00, 0x84, 0xc0, 0x00,
+ 0x00, 0x04, 0x3f, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawfreehand_mask.h b/vcl/inc/unx/x11_cursors/drawfreehand_mask.h
new file mode 100644
index 0000000000..0e5d38ebd7
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawfreehand_mask.h
@@ -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 .
+ */
+#define drawfreehand_mask_width 32
+#define drawfreehand_mask_height 32
+static unsigned char drawfreehand_mask_bits[] = {
+ 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00,
+ 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00,
+ 0x80, 0x03, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0xff, 0xff, 0x01, 0x00,
+ 0xff, 0xff, 0x01, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00,
+ 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x08,
+ 0x80, 0x03, 0x00, 0x1c, 0x80, 0x73, 0x00, 0x0e, 0x00, 0xf8, 0x00, 0x07,
+ 0x00, 0xfc, 0x01, 0x07, 0x00, 0xce, 0xc1, 0x03, 0x00, 0xce, 0xff, 0x01,
+ 0x00, 0x8e, 0xff, 0x00, 0x00, 0x0e, 0x3f, 0x00, 0x00, 0x0e, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawline_curs.h b/vcl/inc/unx/x11_cursors/drawline_curs.h
new file mode 100644
index 0000000000..5376a66002
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawline_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define drawline_curs_width 32
+#define drawline_curs_height 32
+#define drawline_curs_x_hot 7
+#define drawline_curs_y_hot 7
+static unsigned char drawline_curs_bits[] = {
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x0c, 0x00,
+ 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawline_mask.h b/vcl/inc/unx/x11_cursors/drawline_mask.h
new file mode 100644
index 0000000000..f283ac7fad
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawline_mask.h
@@ -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 .
+ */
+#define drawline_mask_width 32
+#define drawline_mask_height 32
+static unsigned char drawline_mask_bits[] = {
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x3f, 0xfe, 0x00, 0x00, 0xbf, 0xfe, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x70,
+ 0xc0, 0x01, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0xc0, 0x0f,
+ 0x00, 0x00, 0xf0, 0x03, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x3f, 0x00,
+ 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawpie_curs.h b/vcl/inc/unx/x11_cursors/drawpie_curs.h
new file mode 100644
index 0000000000..777634e1c0
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawpie_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define drawpie_curs_width 32
+#define drawpie_curs_height 32
+#define drawpie_curs_x_hot 7
+#define drawpie_curs_y_hot 7
+static unsigned char drawpie_curs_bits[] = {
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0a, 0x00,
+ 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0xf9, 0x00,
+ 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x3c, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawpie_mask.h b/vcl/inc/unx/x11_cursors/drawpie_mask.h
new file mode 100644
index 0000000000..93ac75c2a8
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawpie_mask.h
@@ -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 .
+ */
+#define drawpie_mask_width 32
+#define drawpie_mask_height 32
+static unsigned char drawpie_mask_bits[] = {
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x3f, 0x7e, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x3f, 0x7e, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00,
+ 0x00, 0x80, 0x1f, 0x00, 0x00, 0x80, 0xff, 0x01, 0x00, 0x80, 0xff, 0x01,
+ 0x00, 0x80, 0xfb, 0x01, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x7e, 0x00,
+ 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawpolygon_curs.h b/vcl/inc/unx/x11_cursors/drawpolygon_curs.h
new file mode 100644
index 0000000000..7eebead2a8
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawpolygon_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define drawpolygon_curs_width 32
+#define drawpolygon_curs_height 32
+#define drawpolygon_curs_x_hot 7
+#define drawpolygon_curs_y_hot 7
+static unsigned char drawpolygon_curs_bits[] = {
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x83, 0x00,
+ 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00, 0x99, 0x00,
+ 0x00, 0x00, 0x89, 0x03, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x01, 0x01,
+ 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawpolygon_mask.h b/vcl/inc/unx/x11_cursors/drawpolygon_mask.h
new file mode 100644
index 0000000000..0865ce1f5d
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawpolygon_mask.h
@@ -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 .
+ */
+#define drawpolygon_mask_width 32
+#define drawpolygon_mask_height 32
+static unsigned char drawpolygon_mask_bits[] = {
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x3f, 0x7e, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x3f, 0x7e, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x1e, 0x06,
+ 0x00, 0x00, 0x1e, 0x07, 0x00, 0x00, 0xbe, 0x07, 0x00, 0x00, 0xfe, 0x07,
+ 0x00, 0x00, 0xfe, 0x1f, 0x00, 0x00, 0x7e, 0x1f, 0x00, 0x00, 0x3e, 0x1f,
+ 0x00, 0x00, 0x0e, 0x0e, 0x00, 0x00, 0x0e, 0x07, 0x00, 0x00, 0x0e, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawrect_curs.h b/vcl/inc/unx/x11_cursors/drawrect_curs.h
new file mode 100644
index 0000000000..4f98f355fb
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawrect_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define drawrect_curs_width 32
+#define drawrect_curs_height 32
+#define drawrect_curs_x_hot 7
+#define drawrect_curs_y_hot 7
+static unsigned char drawrect_curs_bits[] = {
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x81, 0x00,
+ 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x81, 0x00,
+ 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawrect_mask.h b/vcl/inc/unx/x11_cursors/drawrect_mask.h
new file mode 100644
index 0000000000..b00b06c915
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawrect_mask.h
@@ -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 .
+ */
+#define drawrect_mask_width 32
+#define drawrect_mask_height 32
+static unsigned char drawrect_mask_bits[] = {
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x3f, 0x7e, 0x00, 0x00, 0xbf, 0x7e, 0x00, 0x00, 0x3f, 0x7e, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x00, 0x80, 0xff, 0x01, 0x00, 0x80, 0xff, 0x01, 0x00, 0x80, 0xff, 0x01,
+ 0x00, 0x80, 0xc3, 0x01, 0x00, 0x80, 0xc3, 0x01, 0x00, 0x80, 0xff, 0x01,
+ 0x00, 0x80, 0xff, 0x01, 0x00, 0x80, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawtext_curs.h b/vcl/inc/unx/x11_cursors/drawtext_curs.h
new file mode 100644
index 0000000000..f530146d7a
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawtext_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define drawtext_curs_width 32
+#define drawtext_curs_height 32
+#define drawtext_curs_x_hot 8
+#define drawtext_curs_y_hot 8
+static unsigned char drawtext_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xfd, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x81, 0x0d, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00,
+ 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00,
+ 0x00, 0x80, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/drawtext_mask.h b/vcl/inc/unx/x11_cursors/drawtext_mask.h
new file mode 100644
index 0000000000..75c335bea4
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/drawtext_mask.h
@@ -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 .
+ */
+#define drawtext_mask_width 32
+#define drawtext_mask_height 32
+static unsigned char drawtext_mask_bits[] = {
+ 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00,
+ 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00,
+ 0x80, 0x03, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0xff, 0xff, 0x01, 0x00,
+ 0xff, 0xff, 0x01, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00,
+ 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0xc3, 0x1f, 0x00,
+ 0x80, 0xc3, 0x1f, 0x00, 0x80, 0xc3, 0x1f, 0x00, 0x00, 0x00, 0x07, 0x00,
+ 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0xc0, 0x1f, 0x00,
+ 0x00, 0xc0, 0x1f, 0x00, 0x00, 0xc0, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/fatcross_curs.h b/vcl/inc/unx/x11_cursors/fatcross_curs.h
new file mode 100644
index 0000000000..64322342a3
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/fatcross_curs.h
@@ -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 .
+ */
+#define fatcross_curs_width 32
+#define fatcross_curs_height 32
+#define fatcross_curs_x_hot 15
+#define fatcross_curs_y_hot 15
+static unsigned char fatcross_curs_bits[]
+ = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8,
+ 0x0f, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x08, 0x08, 0x00, 0x80,
+ 0x0f, 0xf8, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x0f, 0xf8, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x08,
+ 0x08, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/fatcross_mask.h b/vcl/inc/unx/x11_cursors/fatcross_mask.h
new file mode 100644
index 0000000000..d3db67d647
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/fatcross_mask.h
@@ -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 .
+ */
+#define fatcross_mask_width 32
+#define fatcross_mask_height 32
+#define fatcross_mask_x_hot 15
+#define fatcross_mask_y_hot 15
+static unsigned char fatcross_mask_bits[]
+ = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8,
+ 0x0f, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0x80,
+ 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00,
+ 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff,
+ 0x00, 0x80, 0xff, 0xff, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xf8,
+ 0x0f, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/fill_curs.h b/vcl/inc/unx/x11_cursors/fill_curs.h
new file mode 100644
index 0000000000..1cdb63410f
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/fill_curs.h
@@ -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 .
+ */
+#define fill_curs_width 32
+#define fill_curs_height 32
+#define fill_curs_x_hot 10
+#define fill_curs_y_hot 22
+static unsigned char fill_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x80,0x02,0x00,0x00,0x5c,0x0c,0x00,0x00,
+ 0x2e,0x12,0x00,0x00,0x17,0x38,0x00,0x00,0x0b,0x7c,0x00,0x00,0x5b,0xbe,0x00,
+ 0x00,0x27,0x9f,0x00,0x00,0xa7,0x4f,0x00,0x00,0xc7,0x27,0x00,0x00,0x87,0x13,
+ 0x00,0x00,0x06,0x09,0x00,0x00,0x06,0x06,0x00,0x00,0x04,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/fill_mask.h b/vcl/inc/unx/x11_cursors/fill_mask.h
new file mode 100644
index 0000000000..df5d4cdebf
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/fill_mask.h
@@ -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 .
+ */
+#define fill_mask_width 32
+#define fill_mask_height 32
+#define fill_mask_x_hot 10
+#define fill_mask_y_hot 22
+static unsigned char fill_mask_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x80,0x03,0x00,0x00,0xdc,0x0f,0x00,0x00,
+ 0xfe,0x1f,0x00,0x00,0xff,0x3f,0x00,0x00,0xff,0x7f,0x00,0x00,0xff,0xff,0x00,
+ 0x00,0xe7,0xff,0x00,0x00,0xe7,0x7f,0x00,0x00,0xc7,0x3f,0x00,0x00,0x87,0x1f,
+ 0x00,0x00,0x06,0x0f,0x00,0x00,0x06,0x06,0x00,0x00,0x04,0x00,0x00,0x00,0x04,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/hshear_curs.h b/vcl/inc/unx/x11_cursors/hshear_curs.h
new file mode 100644
index 0000000000..5497f05158
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/hshear_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define hshear_curs_width 32
+#define hshear_curs_height 32
+#define hshear_curs_x_hot 15
+#define hshear_curs_y_hot 15
+static unsigned char hshear_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
+ 0x00, 0x3c, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x0c, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/hshear_mask.h b/vcl/inc/unx/x11_cursors/hshear_mask.h
new file mode 100644
index 0000000000..c94277c6ab
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/hshear_mask.h
@@ -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 .
+ */
+#define hshear_mask_width 32
+#define hshear_mask_height 32
+static unsigned char hshear_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00,
+ 0x80, 0xff, 0xff, 0x01, 0x80, 0xff, 0xff, 0x01, 0x80, 0xff, 0xff, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0x01,
+ 0x80, 0xff, 0xff, 0x01, 0x80, 0xff, 0xff, 0x01, 0x00, 0x00, 0x3e, 0x00,
+ 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/invert50.h b/vcl/inc/unx/x11_cursors/invert50.h
new file mode 100644
index 0000000000..cae29c67e9
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/invert50.h
@@ -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 .
+ */
+#define invert50_width 32
+#define invert50_height 32
+static unsigned char invert50_bits[] = {
+ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/linkdata_curs.h b/vcl/inc/unx/x11_cursors/linkdata_curs.h
new file mode 100644
index 0000000000..8a4e6db387
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/linkdata_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define linkdata_curs_width 32
+#define linkdata_curs_height 32
+#define linkdata_curs_x_hot 1
+#define linkdata_curs_y_hot 1
+static unsigned char linkdata_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
+ 0x7e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00,
+ 0xfe, 0x03, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00,
+ 0x66, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
+ 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x10, 0x53, 0x00, 0x00,
+ 0x28, 0xa3, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x10, 0xf0, 0x1f, 0x00, 0x08, 0x70, 0x18, 0x00, 0x10, 0xf0, 0x18, 0x00,
+ 0xa8, 0x72, 0x18, 0x00, 0x50, 0x35, 0x1a, 0x00, 0x00, 0x30, 0x1f, 0x00,
+ 0x00, 0xb0, 0x1f, 0x00, 0x00, 0x70, 0x1f, 0x00, 0x00, 0xf0, 0x1f, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/linkdata_mask.h b/vcl/inc/unx/x11_cursors/linkdata_mask.h
new file mode 100644
index 0000000000..a1875a8e0a
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/linkdata_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define linkdata_mask_width 32
+#define linkdata_mask_height 32
+#define linkdata_mask_x_hot 1
+#define linkdata_mask_y_hot 1
+static unsigned char linkdata_mask_bits[] = {
+ 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+ 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00,
+ 0xe0, 0x03, 0x00, 0x00, 0xf8, 0xff, 0x00, 0x00, 0xfc, 0xff, 0x01, 0x00,
+ 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0x3c, 0xf8, 0x3f, 0x00,
+ 0x3c, 0xf8, 0x3f, 0x00, 0x3c, 0xf8, 0x3f, 0x00, 0xfc, 0xff, 0x3f, 0x00,
+ 0xfc, 0xff, 0x3f, 0x00, 0xfc, 0xff, 0x3f, 0x00, 0xf8, 0xff, 0x3f, 0x00,
+ 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00,
+ 0x00, 0xf8, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/linkfile_curs.h b/vcl/inc/unx/x11_cursors/linkfile_curs.h
new file mode 100644
index 0000000000..571d928d4e
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/linkfile_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define linkfile_curs_width 32
+#define linkfile_curs_height 32
+#define linkfile_curs_x_hot 9
+#define linkfile_curs_y_hot 9
+static unsigned char linkfile_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x02, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0xfe, 0x04, 0x00, 0x00,
+ 0xfe, 0x02, 0x00, 0x00, 0xfe, 0x06, 0x00, 0x00, 0xfe, 0x0e, 0x00, 0x00,
+ 0xfe, 0x1e, 0x00, 0x00, 0xfe, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00,
+ 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x03, 0x00,
+ 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00,
+ 0x00, 0xc2, 0xe0, 0x3f, 0x00, 0xc0, 0xe0, 0x30, 0x00, 0x80, 0xe1, 0x31,
+ 0x00, 0x80, 0xe1, 0x30, 0x00, 0x00, 0x63, 0x34, 0x00, 0x00, 0x63, 0x3e,
+ 0x00, 0x00, 0x60, 0x3f, 0x00, 0x00, 0xe0, 0x3e, 0x00, 0x00, 0xe0, 0x3f,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/linkfile_mask.h b/vcl/inc/unx/x11_cursors/linkfile_mask.h
new file mode 100644
index 0000000000..cbef413689
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/linkfile_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define linkfile_mask_width 32
+#define linkfile_mask_height 32
+#define linkfile_mask_x_hot 9
+#define linkfile_mask_y_hot 9
+static unsigned char linkfile_mask_bits[] = {
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x1f, 0x00, 0x00, 0xff, 0x3f, 0x00, 0x00,
+ 0xff, 0x7f, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00,
+ 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00,
+ 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xf1, 0x7f,
+ 0x00, 0xff, 0xf1, 0x7f, 0x00, 0xe7, 0xf3, 0x7f, 0x00, 0xe0, 0xf3, 0x7f,
+ 0x00, 0xc0, 0xf7, 0x7f, 0x00, 0xc0, 0xf7, 0x7f, 0x00, 0x80, 0xf7, 0x7f,
+ 0x00, 0x80, 0xf7, 0x7f, 0x00, 0x00, 0xf0, 0x7f, 0x00, 0x00, 0xf0, 0x7f,
+ 0x00, 0x00, 0xf0, 0x7f, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/magnify_curs.h b/vcl/inc/unx/x11_cursors/magnify_curs.h
new file mode 100644
index 0000000000..4ce3d6e5ae
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/magnify_curs.h
@@ -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 .
+ */
+#define magnify_curs_width 32
+#define magnify_curs_height 32
+#define magnify_curs_x_hot 12
+#define magnify_curs_y_hot 13
+static unsigned char magnify_curs_bits[] = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7c,0x00,0x00,0x00,0x83,
+ 0x01,0x00,0x80,0x00,0x02,0x00,0x40,0x00,0x04,0x00,0x40,0x00,0x04,0x00,0x20,
+ 0x00,0x08,0x00,0x20,0x00,0x08,0x00,0x20,0x00,0x08,0x00,0x20,0x00,0x08,0x00,
+ 0x20,0x00,0x08,0x00,0x40,0x00,0x04,0x00,0x40,0x00,0x04,0x00,0x80,0x00,0x06,
+ 0x00,0x00,0x83,0x0f,0x00,0x00,0x7c,0x1c,0x00,0x00,0x00,0x38,0x00,0x00,0x00,
+ 0x70,0x00,0x00,0x00,0xe0,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/magnify_mask.h b/vcl/inc/unx/x11_cursors/magnify_mask.h
new file mode 100644
index 0000000000..fcc34f8868
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/magnify_mask.h
@@ -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 .
+ */
+#define magnify_mask_width 32
+#define magnify_mask_height 32
+static unsigned char magnify_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00,
+ 0x00, 0xff, 0x01, 0x00, 0x80, 0xff, 0x03, 0x00, 0xc0, 0x83, 0x07, 0x00,
+ 0xe0, 0x00, 0x0e, 0x00, 0xe0, 0x00, 0x0e, 0x00, 0x70, 0x00, 0x1c, 0x00,
+ 0x70, 0x00, 0x1c, 0x00, 0x70, 0x00, 0x1c, 0x00, 0x70, 0x00, 0x1c, 0x00,
+ 0x70, 0x00, 0x1c, 0x00, 0xe0, 0x00, 0x0e, 0x00, 0xe0, 0x00, 0x0e, 0x00,
+ 0xc0, 0x83, 0x0f, 0x00, 0x80, 0xff, 0x1f, 0x00, 0x00, 0xff, 0x3f, 0x00,
+ 0x00, 0x7c, 0x7c, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0xf0, 0x01,
+ 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/mirror_curs.h b/vcl/inc/unx/x11_cursors/mirror_curs.h
new file mode 100644
index 0000000000..420ff31470
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/mirror_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define mirror_curs_width 32
+#define mirror_curs_height 32
+#define mirror_curs_x_hot 14
+#define mirror_curs_y_hot 12
+static unsigned char mirror_curs_bits[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xff, 0x03, 0xf8, 0xf5, 0xff,
+ 0xfb, 0xfb, 0xee, 0xff, 0x0b, 0xfa, 0xf5, 0xff, 0xeb, 0xfa, 0xfa, 0xff,
+ 0xeb, 0xfa, 0xfa, 0xff, 0xeb, 0x7a, 0xfd, 0xff, 0xeb, 0x7a, 0xfd, 0xff,
+ 0xeb, 0xba, 0x7e, 0xff, 0xeb, 0xba, 0xbe, 0xfe, 0xeb, 0x5a, 0x5f, 0xfd,
+ 0x0b, 0x5a, 0xaf, 0xfa, 0xfb, 0xab, 0xd7, 0xf5, 0x03, 0xa8, 0xeb, 0xeb,
+ 0xff, 0xd7, 0xf5, 0xf5, 0xff, 0xd7, 0xfa, 0xfa, 0xff, 0x6b, 0x7d, 0xfd,
+ 0xff, 0xeb, 0xba, 0xfe, 0xff, 0xf5, 0x55, 0xff, 0xff, 0xf5, 0xab, 0xff,
+ 0xff, 0xfa, 0xd7, 0xff, 0x7f, 0xf7, 0xef, 0xff, 0xff, 0xfa, 0xff, 0xff,
+ 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/mirror_mask.h b/vcl/inc/unx/x11_cursors/mirror_mask.h
new file mode 100644
index 0000000000..157accb3cd
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/mirror_mask.h
@@ -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 .
+ */
+#define mirror_mask_width 32
+#define mirror_mask_height 32
+static unsigned char mirror_mask_bits[] = {
+ 0x00, 0x00, 0x04, 0x00, 0xfe, 0x0f, 0x0e, 0x00, 0xfe, 0x0f, 0x1f, 0x00,
+ 0xfe, 0x8f, 0x3f, 0x00, 0xfe, 0x0f, 0x1f, 0x00, 0xfe, 0x8f, 0x0f, 0x00,
+ 0xbe, 0x8f, 0x0f, 0x00, 0xbe, 0xcf, 0x07, 0x00, 0xbe, 0xcf, 0x87, 0x00,
+ 0xbe, 0xef, 0xc3, 0x01, 0xbe, 0xef, 0xe3, 0x03, 0xfe, 0xff, 0xf1, 0x07,
+ 0xfe, 0xff, 0x79, 0x0f, 0xfe, 0xff, 0x3c, 0x1e, 0xfe, 0xff, 0x1e, 0x3c,
+ 0xfe, 0x7f, 0x0f, 0x1e, 0x00, 0xfc, 0x07, 0x0f, 0x00, 0xfe, 0x83, 0x07,
+ 0x00, 0xbe, 0xc7, 0x03, 0x00, 0x1f, 0xef, 0x01, 0x00, 0x1f, 0xfe, 0x00,
+ 0x80, 0x0f, 0x7c, 0x00, 0xc0, 0x1d, 0x38, 0x00, 0x80, 0x0f, 0x10, 0x00,
+ 0x00, 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/movebezierweight_curs.h b/vcl/inc/unx/x11_cursors/movebezierweight_curs.h
new file mode 100644
index 0000000000..fdae751275
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/movebezierweight_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define movebezierweight_curs_width 32
+#define movebezierweight_curs_height 32
+#define movebezierweight_curs_x_hot 0
+#define movebezierweight_curs_y_hot 0
+static unsigned char movebezierweight_curs_bits[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff,
+ 0xf1, 0xff, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xff,
+ 0x81, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff, 0x01, 0xfe, 0xff, 0xff,
+ 0x01, 0xfc, 0xff, 0xff, 0x81, 0xff, 0xff, 0xff, 0x91, 0xff, 0xff, 0xff,
+ 0x99, 0xff, 0xff, 0xef, 0x3d, 0xff, 0xff, 0xef, 0x3f, 0xff, 0xff, 0xef,
+ 0x7f, 0xfe, 0xff, 0xf7, 0x7f, 0xfe, 0xff, 0xf7, 0xff, 0xfc, 0xff, 0xfb,
+ 0xff, 0x7c, 0xff, 0xec, 0xff, 0xbf, 0x0e, 0xd7, 0xff, 0x7f, 0xf3, 0xef,
+ 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
+ 0xff, 0x7f, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/movebezierweight_mask.h b/vcl/inc/unx/x11_cursors/movebezierweight_mask.h
new file mode 100644
index 0000000000..08203fe767
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/movebezierweight_mask.h
@@ -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 .
+ */
+#define movebezierweight_mask_width 32
+#define movebezierweight_mask_height 32
+static unsigned char movebezierweight_mask_bits[] = {
+ 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x1f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00,
+ 0xff, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00,
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x10,
+ 0xff, 0x00, 0x00, 0x38, 0xe7, 0x01, 0x00, 0x38, 0xe3, 0x01, 0x00, 0x38,
+ 0xc0, 0x03, 0x00, 0x1c, 0xc0, 0x03, 0x00, 0x1c, 0x80, 0x87, 0x00, 0x1f,
+ 0x80, 0xc7, 0xf1, 0x3f, 0x80, 0xe7, 0xff, 0x7f, 0x00, 0xc0, 0xff, 0x38,
+ 0x00, 0x80, 0x0f, 0x10, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00,
+ 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00,
+ 0x00, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/movedata_curs.h b/vcl/inc/unx/x11_cursors/movedata_curs.h
new file mode 100644
index 0000000000..b253ce70ca
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/movedata_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define movedata_curs_width 32
+#define movedata_curs_height 32
+#define movedata_curs_x_hot 1
+#define movedata_curs_y_hot 1
+static unsigned char movedata_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
+ 0x7e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00,
+ 0xfe, 0x03, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00,
+ 0x66, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
+ 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x10, 0x53, 0x00, 0x00,
+ 0x28, 0xa3, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00,
+ 0x10, 0x40, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00,
+ 0xa8, 0xaa, 0x00, 0x00, 0x50, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/movedata_mask.h b/vcl/inc/unx/x11_cursors/movedata_mask.h
new file mode 100644
index 0000000000..d317b1556e
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/movedata_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define movedata_mask_width 32
+#define movedata_mask_height 32
+#define movedata_mask_x_hot 1
+#define movedata_mask_y_hot 1
+static unsigned char movedata_mask_bits[] = {
+ 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+ 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00,
+ 0xe0, 0x03, 0x00, 0x00, 0xf8, 0xff, 0x00, 0x00, 0xfc, 0xff, 0x01, 0x00,
+ 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0x3c, 0xe0, 0x01, 0x00,
+ 0x3c, 0xe0, 0x01, 0x00, 0x3c, 0xe0, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00,
+ 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0xf8, 0xff, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/movedlnk_curs.h b/vcl/inc/unx/x11_cursors/movedlnk_curs.h
new file mode 100644
index 0000000000..1e82e277a4
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/movedlnk_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define movedlnk_curs_width 32
+#define movedlnk_curs_height 32
+#define movedlnk_curs_x_hot 1
+#define movedlnk_curs_y_hot 1
+static unsigned char movedlnk_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
+ 0x7e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00,
+ 0xfe, 0x03, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00,
+ 0x66, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
+ 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x10, 0x53, 0x00, 0x00,
+ 0x28, 0xa3, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0xf0, 0x81, 0x00, 0x00,
+ 0x30, 0x41, 0x00, 0x00, 0x10, 0x81, 0x00, 0x00, 0xd0, 0x41, 0x00, 0x00,
+ 0xf0, 0xa9, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/movedlnk_mask.h b/vcl/inc/unx/x11_cursors/movedlnk_mask.h
new file mode 100644
index 0000000000..e56f9714ce
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/movedlnk_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define movedlnk_mask_width 32
+#define movedlnk_mask_height 32
+#define movedlnk_mask_x_hot 1
+#define movedlnk_mask_y_hot 1
+static unsigned char movedlnk_mask_bits[] = {
+ 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+ 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00,
+ 0xe0, 0x03, 0x00, 0x00, 0xf8, 0xff, 0x00, 0x00, 0xfc, 0xff, 0x01, 0x00,
+ 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0xf8, 0xe3, 0x01, 0x00,
+ 0xf8, 0xe3, 0x01, 0x00, 0xf8, 0xe3, 0x01, 0x00, 0xf8, 0xff, 0x01, 0x00,
+ 0xf8, 0xff, 0x01, 0x00, 0xf8, 0xff, 0x01, 0x00, 0x00, 0xfe, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/movefile_curs.h b/vcl/inc/unx/x11_cursors/movefile_curs.h
new file mode 100644
index 0000000000..3ffea197ff
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/movefile_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define movefile_curs_width 32
+#define movefile_curs_height 32
+#define movefile_curs_x_hot 9
+#define movefile_curs_y_hot 9
+static unsigned char movefile_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x02, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0xfe, 0x04, 0x00, 0x00,
+ 0xfe, 0x02, 0x00, 0x00, 0xfe, 0x06, 0x00, 0x00, 0xfe, 0x0e, 0x00, 0x00,
+ 0xfe, 0x1e, 0x00, 0x00, 0xfe, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00,
+ 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x03, 0x00,
+ 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00,
+ 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00,
+ 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/movefile_mask.h b/vcl/inc/unx/x11_cursors/movefile_mask.h
new file mode 100644
index 0000000000..ab74f25d80
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/movefile_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define movefile_mask_width 32
+#define movefile_mask_height 32
+#define movefile_mask_x_hot 9
+#define movefile_mask_y_hot 9
+static unsigned char movefile_mask_bits[] = {
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x1f, 0x00, 0x00, 0xff, 0x3f, 0x00, 0x00,
+ 0xff, 0x7f, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00,
+ 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00,
+ 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00,
+ 0x00, 0xff, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00, 0xe0, 0x03, 0x00,
+ 0x00, 0xc0, 0x07, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x00, 0x80, 0x07, 0x00,
+ 0x00, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/movefiles_curs.h b/vcl/inc/unx/x11_cursors/movefiles_curs.h
new file mode 100644
index 0000000000..d5c726a2ea
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/movefiles_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define movefiles_curs_width 32
+#define movefiles_curs_height 32
+#define movefiles_curs_x_hot 8
+#define movefiles_curs_y_hot 9
+static unsigned char movefiles_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xe0, 0x0f, 0x00, 0x00, 0xe0, 0x2f, 0x00, 0x00,
+ 0xe8, 0x0f, 0x00, 0x00, 0xe8, 0x7f, 0x00, 0x00, 0xea, 0x7f, 0x00, 0x00,
+ 0xea, 0x7f, 0x00, 0x00, 0xea, 0x7f, 0x00, 0x00, 0x6a, 0x7e, 0x00, 0x00,
+ 0x6a, 0x7d, 0x00, 0x00, 0x6a, 0x7b, 0x00, 0x00, 0x6a, 0x77, 0x00, 0x00,
+ 0x6a, 0x6f, 0x00, 0x00, 0x6a, 0x5f, 0x00, 0x00, 0x0a, 0x3f, 0x00, 0x00,
+ 0x7a, 0x7f, 0x00, 0x00, 0x02, 0xff, 0x00, 0x00, 0x7e, 0xff, 0x01, 0x00,
+ 0x00, 0x3f, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00,
+ 0x00, 0x61, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00,
+ 0x00, 0xc0, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/movefiles_mask.h b/vcl/inc/unx/x11_cursors/movefiles_mask.h
new file mode 100644
index 0000000000..c1683b3336
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/movefiles_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define movefiles_mask_width 32
+#define movefiles_mask_height 32
+#define movefiles_mask_x_hot 8
+#define movefiles_mask_y_hot 9
+static unsigned char movefiles_mask_bits[] = {
+ 0xf0, 0x1f, 0x00, 0x00, 0xf0, 0x7f, 0x00, 0x00, 0xfc, 0x7f, 0x00, 0x00,
+ 0xfc, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
+ 0xff, 0xff, 0x01, 0x00, 0xff, 0xff, 0x03, 0x00, 0xff, 0xff, 0x03, 0x00,
+ 0xff, 0xff, 0x03, 0x00, 0x80, 0x7f, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00,
+ 0x80, 0xff, 0x00, 0x00, 0x80, 0xf3, 0x01, 0x00, 0x00, 0xf0, 0x01, 0x00,
+ 0x00, 0xe0, 0x03, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00,
+ 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/moveflnk_curs.h b/vcl/inc/unx/x11_cursors/moveflnk_curs.h
new file mode 100644
index 0000000000..02d0c8145c
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/moveflnk_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define moveflnk_curs_width 32
+#define moveflnk_curs_height 32
+#define moveflnk_curs_x_hot 9
+#define moveflnk_curs_y_hot 9
+static unsigned char moveflnk_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x02, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0x80, 0x04, 0x00, 0x00,
+ 0xbe, 0x02, 0x00, 0x00, 0xa6, 0x06, 0x00, 0x00, 0xa2, 0x0e, 0x00, 0x00,
+ 0xba, 0x1e, 0x00, 0x00, 0xbe, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00,
+ 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x03, 0x00,
+ 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00,
+ 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00,
+ 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/moveflnk_mask.h b/vcl/inc/unx/x11_cursors/moveflnk_mask.h
new file mode 100644
index 0000000000..189c114431
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/moveflnk_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define moveflnk_mask_width 32
+#define moveflnk_mask_height 32
+#define moveflnk_mask_x_hot 9
+#define moveflnk_mask_y_hot 9
+static unsigned char moveflnk_mask_bits[] = {
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x1f, 0x00, 0x00, 0xff, 0x3f, 0x00, 0x00,
+ 0xff, 0x7f, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00,
+ 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00,
+ 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00,
+ 0x00, 0xff, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00, 0xe0, 0x03, 0x00,
+ 0x00, 0xc0, 0x07, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x00, 0x80, 0x07, 0x00,
+ 0x00, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/movepoint_curs.h b/vcl/inc/unx/x11_cursors/movepoint_curs.h
new file mode 100644
index 0000000000..7f85113ce8
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/movepoint_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define movepoint_curs_width 32
+#define movepoint_curs_height 32
+#define movepoint_curs_x_hot 0
+#define movepoint_curs_y_hot 0
+static unsigned char movepoint_curs_bits[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff,
+ 0xf1, 0xff, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xff,
+ 0x81, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff, 0x01, 0xfe, 0xff, 0xff,
+ 0x01, 0xfc, 0xff, 0xff, 0x81, 0xff, 0xff, 0xff, 0x91, 0xff, 0xff, 0xff,
+ 0x39, 0xff, 0xff, 0xff, 0x3d, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0xff, 0xff,
+ 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xfc, 0x83, 0xff, 0xff, 0xfc, 0x83, 0xff,
+ 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0x83, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/movepoint_mask.h b/vcl/inc/unx/x11_cursors/movepoint_mask.h
new file mode 100644
index 0000000000..aa16b5a56c
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/movepoint_mask.h
@@ -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 .
+ */
+#define movepoint_mask_width 32
+#define movepoint_mask_height 32
+static unsigned char movepoint_mask_bits[] = {
+ 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x1f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00,
+ 0xff, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00,
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xef, 0x01, 0x00, 0x00, 0xe7, 0x01, 0x00, 0x00, 0xc3, 0x03, 0x00, 0x00,
+ 0xc0, 0x03, 0xfe, 0x00, 0x80, 0x07, 0xfe, 0x00, 0x80, 0x07, 0xfe, 0x00,
+ 0x80, 0x07, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x00,
+ 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/nodrop_curs.h b/vcl/inc/unx/x11_cursors/nodrop_curs.h
new file mode 100644
index 0000000000..9582575180
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/nodrop_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define nodrop_curs_width 32
+#define nodrop_curs_height 32
+#define nodrop_curs_x_hot 9
+#define nodrop_curs_y_hot 9
+static unsigned char nodrop_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00,
+ 0xf8, 0x7f, 0x00, 0x00, 0x7c, 0xf8, 0x00, 0x00, 0x1c, 0xfc, 0x00, 0x00,
+ 0x1e, 0xfe, 0x01, 0x00, 0x0e, 0xdf, 0x01, 0x00, 0x8e, 0xcf, 0x01, 0x00,
+ 0xce, 0xc7, 0x01, 0x00, 0xee, 0xc3, 0x01, 0x00, 0xfe, 0xe1, 0x01, 0x00,
+ 0xfc, 0xe0, 0x00, 0x00, 0x7c, 0xf8, 0x00, 0x00, 0xf8, 0x7f, 0x00, 0x00,
+ 0xf0, 0x3f, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/nodrop_mask.h b/vcl/inc/unx/x11_cursors/nodrop_mask.h
new file mode 100644
index 0000000000..662a300645
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/nodrop_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define nodrop_mask_width 32
+#define nodrop_mask_height 32
+#define nodrop_mask_x_hot 9
+#define nodrop_mask_y_hot 9
+static unsigned char nodrop_mask_bits[] = {
+ 0xc0, 0x0f, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, 0xf8, 0x7f, 0x00, 0x00,
+ 0xfc, 0xff, 0x00, 0x00, 0xfe, 0xff, 0x01, 0x00, 0x7e, 0xfe, 0x01, 0x00,
+ 0x3f, 0xff, 0x03, 0x00, 0x9f, 0xff, 0x03, 0x00, 0xdf, 0xff, 0x03, 0x00,
+ 0xff, 0xef, 0x03, 0x00, 0xff, 0xe7, 0x03, 0x00, 0xff, 0xf3, 0x03, 0x00,
+ 0xfe, 0xf9, 0x01, 0x00, 0xfe, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x00, 0x00,
+ 0xf8, 0x7f, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/null_curs.h b/vcl/inc/unx/x11_cursors/null_curs.h
new file mode 100644
index 0000000000..ebeee4e6ff
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/null_curs.h
@@ -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 .
+ */
+#define nullcurs_width 4
+#define nullcurs_height 4
+#define nullcurs_x_hot 2
+#define nullcurs_y_hot 2
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/null_mask.h b/vcl/inc/unx/x11_cursors/null_mask.h
new file mode 100644
index 0000000000..71f08a94af
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/null_mask.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define nullmask_width 4
+#define nullmask_height 4
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/pivotcol_curs.h b/vcl/inc/unx/x11_cursors/pivotcol_curs.h
new file mode 100644
index 0000000000..a34520ab0c
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/pivotcol_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define pivotcol_curs_width 32
+#define pivotcol_curs_height 32
+#define pivotcol_curs_x_hot 7
+#define pivotcol_curs_y_hot 5
+static unsigned char pivotcol_curs_bits[] = {
+ 0xff, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x55, 0x01, 0x00, 0x00,
+ 0x29, 0x01, 0x00, 0x00, 0x15, 0x01, 0x00, 0x00, 0xa9, 0x00, 0x00, 0x00,
+ 0x95, 0x01, 0x00, 0x00, 0xa9, 0x02, 0x00, 0x00, 0x95, 0x04, 0x00, 0x00,
+ 0xa9, 0x08, 0x00, 0x00, 0x95, 0x10, 0x00, 0x00, 0xa9, 0x20, 0x00, 0x00,
+ 0x95, 0x40, 0x00, 0x00, 0xa9, 0x80, 0x00, 0x00, 0x95, 0x00, 0x01, 0x00,
+ 0xa9, 0xe0, 0x03, 0x00, 0x95, 0x2c, 0x00, 0x00, 0xbd, 0x4a, 0x00, 0x00,
+ 0xbf, 0x51, 0x00, 0x00, 0x80, 0x90, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00,
+ 0x00, 0x20, 0x01, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/pivotcol_mask.h b/vcl/inc/unx/x11_cursors/pivotcol_mask.h
new file mode 100644
index 0000000000..9571a031c8
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/pivotcol_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define pivotcol_mask_width 32
+#define pivotcol_mask_height 32
+#define pivotcol_mask_x_hot 7
+#define pivotcol_mask_y_hot 5
+static unsigned char pivotcol_mask_bits[] = {
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x1f, 0x00, 0x00, 0xff, 0x3f, 0x00, 0x00,
+ 0xff, 0x7f, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00,
+ 0xff, 0xff, 0x03, 0x00, 0xff, 0x3f, 0x00, 0x00, 0xff, 0x7b, 0x00, 0x00,
+ 0xff, 0x71, 0x00, 0x00, 0x80, 0xf0, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
+ 0x00, 0xe0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/pivotdel_curs.h b/vcl/inc/unx/x11_cursors/pivotdel_curs.h
new file mode 100644
index 0000000000..d4547e25f2
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/pivotdel_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define pivotdel_curs_width 32
+#define pivotdel_curs_height 32
+#define pivotdel_curs_x_hot 9
+#define pivotdel_curs_y_hot 8
+static unsigned char pivotdel_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x80, 0x01, 0x00,
+ 0x3c, 0xc0, 0x00, 0x00, 0x73, 0x6f, 0x07, 0x00, 0xe1, 0x30, 0x04, 0x00,
+ 0xc1, 0x1d, 0x04, 0x00, 0x81, 0x0f, 0x04, 0x00, 0x01, 0x07, 0x04, 0x00,
+ 0x81, 0x0f, 0x04, 0x00, 0xc1, 0x1d, 0x04, 0x00, 0xe1, 0x38, 0x04, 0x00,
+ 0x77, 0xaf, 0x07, 0x00, 0x78, 0x40, 0x00, 0x00, 0x3c, 0x80, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/pivotdel_mask.h b/vcl/inc/unx/x11_cursors/pivotdel_mask.h
new file mode 100644
index 0000000000..68868aeec8
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/pivotdel_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define pivotdel_mask_width 32
+#define pivotdel_mask_height 32
+#define pivotdel_mask_x_hot 9
+#define pivotdel_mask_y_hot 8
+static unsigned char pivotdel_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x80, 0x01, 0x00,
+ 0x3c, 0xc0, 0x00, 0x00, 0xff, 0xff, 0x07, 0x00, 0xff, 0xff, 0x07, 0x00,
+ 0xff, 0xff, 0x07, 0x00, 0xff, 0xff, 0x07, 0x00, 0xff, 0xff, 0x07, 0x00,
+ 0xff, 0xff, 0x07, 0x00, 0xff, 0xff, 0x07, 0x00, 0xff, 0xff, 0x07, 0x00,
+ 0xff, 0xff, 0x07, 0x00, 0x78, 0x40, 0x00, 0x00, 0x3c, 0x80, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/pivotfld_curs.h b/vcl/inc/unx/x11_cursors/pivotfld_curs.h
new file mode 100644
index 0000000000..287bc97091
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/pivotfld_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define pivotfld_curs_width 32
+#define pivotfld_curs_height 32
+#define pivotfld_curs_x_hot 8
+#define pivotfld_curs_y_hot 7
+static unsigned char pivotfld_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x07, 0x00,
+ 0x01, 0x00, 0x04, 0x00, 0x01, 0x00, 0x04, 0x00, 0x01, 0x00, 0x04, 0x00,
+ 0x01, 0x00, 0x04, 0x00, 0x01, 0x01, 0x04, 0x00, 0x01, 0x03, 0x04, 0x00,
+ 0x01, 0x05, 0x04, 0x00, 0x7f, 0xc9, 0x07, 0x00, 0x00, 0x11, 0x00, 0x00,
+ 0x00, 0x21, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00,
+ 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0xc1, 0x07, 0x00,
+ 0x00, 0x59, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0xa3, 0x00, 0x00,
+ 0x00, 0x21, 0x01, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x40, 0x02, 0x00,
+ 0x00, 0x80, 0x02, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/pivotfld_mask.h b/vcl/inc/unx/x11_cursors/pivotfld_mask.h
new file mode 100644
index 0000000000..0d52447229
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/pivotfld_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define pivotfld_mask_width 32
+#define pivotfld_mask_height 32
+#define pivotfld_mask_x_hot 8
+#define pivotfld_mask_y_hot 7
+static unsigned char pivotfld_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x07, 0x00,
+ 0xff, 0xff, 0x07, 0x00, 0xff, 0xff, 0x07, 0x00, 0xff, 0xff, 0x07, 0x00,
+ 0xff, 0xff, 0x07, 0x00, 0xff, 0xff, 0x07, 0x00, 0xff, 0xff, 0x07, 0x00,
+ 0xff, 0xff, 0x07, 0x00, 0xff, 0xff, 0x07, 0x00, 0x00, 0x1f, 0x00, 0x00,
+ 0x00, 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
+ 0x00, 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00,
+ 0x00, 0x7f, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x00, 0x00, 0xe3, 0x00, 0x00,
+ 0x00, 0xe1, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x03, 0x00,
+ 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/pivotrow_curs.h b/vcl/inc/unx/x11_cursors/pivotrow_curs.h
new file mode 100644
index 0000000000..4aec279194
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/pivotrow_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define pivotrow_curs_width 32
+#define pivotrow_curs_height 32
+#define pivotrow_curs_x_hot 8
+#define pivotrow_curs_y_hot 7
+static unsigned char pivotrow_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x07, 0x00,
+ 0x01, 0x00, 0x04, 0x00, 0x55, 0x55, 0x07, 0x00, 0xa9, 0xaa, 0x06, 0x00,
+ 0x55, 0x54, 0x07, 0x00, 0x29, 0xa9, 0x06, 0x00, 0x55, 0x53, 0x07, 0x00,
+ 0x29, 0xa5, 0x06, 0x00, 0x7f, 0xc9, 0x07, 0x00, 0x00, 0x11, 0x00, 0x00,
+ 0x00, 0x21, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00,
+ 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0xc1, 0x07, 0x00,
+ 0x00, 0x59, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0xa3, 0x00, 0x00,
+ 0x00, 0x21, 0x01, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x40, 0x02, 0x00,
+ 0x00, 0x80, 0x02, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/pivotrow_mask.h b/vcl/inc/unx/x11_cursors/pivotrow_mask.h
new file mode 100644
index 0000000000..ec9f7f2ba2
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/pivotrow_mask.h
@@ -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 .
+ */
+#define pivotrow_mask_width 32
+#define pivotrow_mask_height 32
+static unsigned char pivotrow_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x07, 0x00,
+ 0xff, 0xff, 0x07, 0x00, 0xff, 0xff, 0x07, 0x00, 0xff, 0xff, 0x07, 0x00,
+ 0xff, 0xff, 0x07, 0x00, 0xff, 0xff, 0x07, 0x00, 0xff, 0xff, 0x07, 0x00,
+ 0xff, 0xff, 0x07, 0x00, 0xff, 0xff, 0x07, 0x00, 0x00, 0x1f, 0x00, 0x00,
+ 0x00, 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
+ 0x00, 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00,
+ 0x00, 0x7f, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x00, 0x00, 0xe3, 0x00, 0x00,
+ 0x00, 0xe1, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x03, 0x00,
+ 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/rotate_curs.h b/vcl/inc/unx/x11_cursors/rotate_curs.h
new file mode 100644
index 0000000000..936f9f12b4
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/rotate_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define rotate_curs_width 32
+#define rotate_curs_height 32
+#define rotate_curs_x_hot 15
+#define rotate_curs_y_hot 15
+static unsigned char rotate_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0xc0, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0xd8, 0x00, 0x00,
+ 0x00, 0x44, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x80, 0x00, 0xc0, 0x01, 0x80, 0x00, 0xe0, 0x03, 0x80, 0x00, 0x80, 0x00,
+ 0x00, 0x01, 0x40, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x02, 0x20, 0x00,
+ 0x00, 0x04, 0x10, 0x00, 0x00, 0x18, 0x0c, 0x00, 0x00, 0xe0, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/rotate_mask.h b/vcl/inc/unx/x11_cursors/rotate_mask.h
new file mode 100644
index 0000000000..44804f00f9
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/rotate_mask.h
@@ -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 .
+ */
+#define rotate_mask_width 32
+#define rotate_mask_height 32
+static unsigned char rotate_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
+ 0x00, 0xe0, 0x01, 0x00, 0x00, 0xf8, 0x03, 0x00, 0x00, 0xfc, 0x01, 0x00,
+ 0x00, 0xfe, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00,
+ 0x80, 0x03, 0x00, 0x00, 0xc0, 0x01, 0x80, 0x00, 0xc0, 0x01, 0xc0, 0x01,
+ 0xc0, 0x01, 0xe0, 0x03, 0xc0, 0x01, 0xf0, 0x07, 0xc0, 0x01, 0xf0, 0x07,
+ 0x80, 0x03, 0xe0, 0x00, 0x80, 0x03, 0xe0, 0x00, 0x00, 0x07, 0x70, 0x00,
+ 0x00, 0x1e, 0x3c, 0x00, 0x00, 0xfc, 0x1f, 0x00, 0x00, 0xf8, 0x0f, 0x00,
+ 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/salcursors.h b/vcl/inc/unx/x11_cursors/salcursors.h
new file mode 100644
index 0000000000..afe8fc756f
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/salcursors.h
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unx/x11_cursors/nodrop_curs.h>
+#include <unx/x11_cursors/nodrop_mask.h>
+#include <unx/x11_cursors/magnify_curs.h>
+#include <unx/x11_cursors/magnify_mask.h>
+#include <unx/x11_cursors/rotate_curs.h>
+#include <unx/x11_cursors/rotate_mask.h>
+#include <unx/x11_cursors/hshear_curs.h>
+#include <unx/x11_cursors/hshear_mask.h>
+#include <unx/x11_cursors/vshear_curs.h>
+#include <unx/x11_cursors/vshear_mask.h>
+#include <unx/x11_cursors/drawline_curs.h>
+#include <unx/x11_cursors/drawline_mask.h>
+#include <unx/x11_cursors/drawrect_curs.h>
+#include <unx/x11_cursors/drawrect_mask.h>
+#include <unx/x11_cursors/drawpolygon_curs.h>
+#include <unx/x11_cursors/drawpolygon_mask.h>
+#include <unx/x11_cursors/drawbezier_curs.h>
+#include <unx/x11_cursors/drawbezier_mask.h>
+#include <unx/x11_cursors/drawarc_curs.h>
+#include <unx/x11_cursors/drawarc_mask.h>
+#include <unx/x11_cursors/drawpie_curs.h>
+#include <unx/x11_cursors/drawpie_mask.h>
+#include <unx/x11_cursors/drawcirclecut_curs.h>
+#include <unx/x11_cursors/drawcirclecut_mask.h>
+#include <unx/x11_cursors/drawellipse_curs.h>
+#include <unx/x11_cursors/drawellipse_mask.h>
+#include <unx/x11_cursors/drawconnect_curs.h>
+#include <unx/x11_cursors/drawconnect_mask.h>
+#include <unx/x11_cursors/drawtext_curs.h>
+#include <unx/x11_cursors/drawtext_mask.h>
+#include <unx/x11_cursors/mirror_curs.h>
+#include <unx/x11_cursors/mirror_mask.h>
+#include <unx/x11_cursors/crook_curs.h>
+#include <unx/x11_cursors/crook_mask.h>
+#include <unx/x11_cursors/crop_curs.h>
+#include <unx/x11_cursors/crop_mask.h>
+#include <unx/x11_cursors/movepoint_curs.h>
+#include <unx/x11_cursors/movepoint_mask.h>
+#include <unx/x11_cursors/movebezierweight_curs.h>
+#include <unx/x11_cursors/movebezierweight_mask.h>
+#include <unx/x11_cursors/drawfreehand_curs.h>
+#include <unx/x11_cursors/drawfreehand_mask.h>
+#include <unx/x11_cursors/drawcaption_curs.h>
+#include <unx/x11_cursors/drawcaption_mask.h>
+#include <unx/x11_cursors/movedata_curs.h>
+#include <unx/x11_cursors/movedata_mask.h>
+#include <unx/x11_cursors/copydata_curs.h>
+#include <unx/x11_cursors/copydata_mask.h>
+#include <unx/x11_cursors/linkdata_curs.h>
+#include <unx/x11_cursors/linkdata_mask.h>
+#include <unx/x11_cursors/movedlnk_curs.h>
+#include <unx/x11_cursors/movedlnk_mask.h>
+#include <unx/x11_cursors/copydlnk_curs.h>
+#include <unx/x11_cursors/copydlnk_mask.h>
+#include <unx/x11_cursors/movefile_curs.h>
+#include <unx/x11_cursors/movefile_mask.h>
+#include <unx/x11_cursors/copyfile_curs.h>
+#include <unx/x11_cursors/copyfile_mask.h>
+#include <unx/x11_cursors/linkfile_curs.h>
+#include <unx/x11_cursors/linkfile_mask.h>
+#include <unx/x11_cursors/moveflnk_curs.h>
+#include <unx/x11_cursors/moveflnk_mask.h>
+#include <unx/x11_cursors/copyflnk_curs.h>
+#include <unx/x11_cursors/copyflnk_mask.h>
+#include <unx/x11_cursors/movefiles_curs.h>
+#include <unx/x11_cursors/movefiles_mask.h>
+#include <unx/x11_cursors/copyfiles_curs.h>
+#include <unx/x11_cursors/copyfiles_mask.h>
+
+#include <unx/x11_cursors/chart_curs.h>
+#include <unx/x11_cursors/chart_mask.h>
+#include <unx/x11_cursors/detective_curs.h>
+#include <unx/x11_cursors/detective_mask.h>
+#include <unx/x11_cursors/pivotcol_curs.h>
+#include <unx/x11_cursors/pivotcol_mask.h>
+#include <unx/x11_cursors/pivotfld_curs.h>
+#include <unx/x11_cursors/pivotfld_mask.h>
+#include <unx/x11_cursors/pivotrow_curs.h>
+#include <unx/x11_cursors/pivotrow_mask.h>
+#include <unx/x11_cursors/pivotdel_curs.h>
+#include <unx/x11_cursors/pivotdel_mask.h>
+
+#include <unx/x11_cursors/chain_curs.h>
+#include <unx/x11_cursors/chain_mask.h>
+#include <unx/x11_cursors/chainnot_curs.h>
+#include <unx/x11_cursors/chainnot_mask.h>
+
+#include <unx/x11_cursors/ase_curs.h>
+#include <unx/x11_cursors/ase_mask.h>
+#include <unx/x11_cursors/asn_curs.h>
+#include <unx/x11_cursors/asn_mask.h>
+#include <unx/x11_cursors/asne_curs.h>
+#include <unx/x11_cursors/asne_mask.h>
+#include <unx/x11_cursors/asns_curs.h>
+#include <unx/x11_cursors/asns_mask.h>
+#include <unx/x11_cursors/asnswe_curs.h>
+#include <unx/x11_cursors/asnswe_mask.h>
+#include <unx/x11_cursors/asnw_curs.h>
+#include <unx/x11_cursors/asnw_mask.h>
+#include <unx/x11_cursors/ass_curs.h>
+#include <unx/x11_cursors/ass_mask.h>
+#include <unx/x11_cursors/asse_curs.h>
+#include <unx/x11_cursors/asse_mask.h>
+#include <unx/x11_cursors/assw_curs.h>
+#include <unx/x11_cursors/assw_mask.h>
+#include <unx/x11_cursors/asw_curs.h>
+#include <unx/x11_cursors/asw_mask.h>
+#include <unx/x11_cursors/aswe_curs.h>
+#include <unx/x11_cursors/aswe_mask.h>
+#include <unx/x11_cursors/null_curs.h>
+#include <unx/x11_cursors/null_mask.h>
+
+#include <unx/x11_cursors/fill_curs.h>
+#include <unx/x11_cursors/fill_mask.h>
+#include <unx/x11_cursors/vertcurs_curs.h>
+#include <unx/x11_cursors/vertcurs_mask.h>
+#include <unx/x11_cursors/tblsele_curs.h>
+#include <unx/x11_cursors/tblsele_mask.h>
+#include <unx/x11_cursors/tblsels_curs.h>
+#include <unx/x11_cursors/tblsels_mask.h>
+#include <unx/x11_cursors/tblselse_curs.h>
+#include <unx/x11_cursors/tblselse_mask.h>
+#include <unx/x11_cursors/tblselw_curs.h>
+#include <unx/x11_cursors/tblselw_mask.h>
+#include <unx/x11_cursors/tblselsw_curs.h>
+#include <unx/x11_cursors/tblselsw_mask.h>
+#include <unx/x11_cursors/wshide_curs.h>
+#include <unx/x11_cursors/wshide_mask.h>
+#include <unx/x11_cursors/wsshow_curs.h>
+#include <unx/x11_cursors/wsshow_mask.h>
+#include <unx/x11_cursors/fatcross_curs.h>
+#include <unx/x11_cursors/fatcross_mask.h>
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/tblsele_curs.h b/vcl/inc/unx/x11_cursors/tblsele_curs.h
new file mode 100644
index 0000000000..e8ca82dd51
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/tblsele_curs.h
@@ -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 .
+ */
+
+#define tblsele_curs_width 16
+#define tblsele_curs_height 16
+#define tblsele_curs_x_hot 14
+#define tblsele_curs_y_hot 8
+static unsigned char tblsele_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c,
+ 0x00, 0x1c, 0xfc, 0x3f, 0xfc, 0x7f, 0xfc, 0x3f, 0x00, 0x1c, 0x00, 0x0c,
+ 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/tblsele_mask.h b/vcl/inc/unx/x11_cursors/tblsele_mask.h
new file mode 100644
index 0000000000..6bb306e73c
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/tblsele_mask.h
@@ -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 .
+ */
+
+#define tblsele_mask_width 16
+#define tblsele_mask_height 16
+static unsigned char tblsele_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x0e, 0x00, 0x1e,
+ 0xfe, 0x3f, 0xfe, 0x7f, 0xfe, 0xff, 0xfe, 0x7f, 0xfe, 0x3f, 0x00, 0x1e,
+ 0x00, 0x0e, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/tblsels_curs.h b/vcl/inc/unx/x11_cursors/tblsels_curs.h
new file mode 100644
index 0000000000..54d37ddb0c
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/tblsels_curs.h
@@ -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 .
+ */
+
+#define tblsels_curs_width 16
+#define tblsels_curs_height 16
+#define tblsels_curs_x_hot 7
+#define tblsels_curs_y_hot 14
+static unsigned char tblsels_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01,
+ 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xf8, 0x0f, 0xf0, 0x07,
+ 0xe0, 0x03, 0xc0, 0x01, 0x80, 0x00, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/tblsels_mask.h b/vcl/inc/unx/x11_cursors/tblsels_mask.h
new file mode 100644
index 0000000000..3b6ae71184
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/tblsels_mask.h
@@ -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 .
+ */
+
+#define tblsels_mask_width 16
+#define tblsels_mask_height 16
+static unsigned char tblsels_mask_bits[] = {
+ 0x00, 0x00, 0xe0, 0x03, 0xe0, 0x03, 0xe0, 0x03, 0xe0, 0x03, 0xe0, 0x03,
+ 0xe0, 0x03, 0xe0, 0x03, 0xe0, 0x03, 0xfc, 0x1f, 0xfc, 0x1f, 0xf8, 0x0f,
+ 0xf0, 0x07, 0xe0, 0x03, 0xc0, 0x01, 0x80, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/tblselse_curs.h b/vcl/inc/unx/x11_cursors/tblselse_curs.h
new file mode 100644
index 0000000000..9bedaabcc5
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/tblselse_curs.h
@@ -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 .
+ */
+
+#define tblselse_curs_width 16
+#define tblselse_curs_height 16
+#define tblselse_curs_x_hot 14
+#define tblselse_curs_y_hot 14
+static unsigned char tblselse_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0xf0, 0x00,
+ 0xf0, 0x01, 0xe0, 0x03, 0xc0, 0x47, 0x80, 0x6f, 0x00, 0x7f, 0x00, 0x7e,
+ 0x00, 0x7c, 0x00, 0x7e, 0x00, 0x7f, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/tblselse_mask.h b/vcl/inc/unx/x11_cursors/tblselse_mask.h
new file mode 100644
index 0000000000..26cbc3282b
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/tblselse_mask.h
@@ -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 .
+ */
+
+#define tblselse_mask_width 16
+#define tblselse_mask_height 16
+static unsigned char tblselse_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0xf0, 0x00, 0xf8, 0x01,
+ 0xf8, 0x03, 0xf0, 0xc7, 0xe0, 0xef, 0xc0, 0xff, 0x80, 0xff, 0x00, 0xff,
+ 0x00, 0xfe, 0x00, 0xff, 0x80, 0xff, 0x80, 0xff };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/tblselsw_curs.h b/vcl/inc/unx/x11_cursors/tblselsw_curs.h
new file mode 100644
index 0000000000..c6f6dedf17
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/tblselsw_curs.h
@@ -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 .
+ */
+
+#define tblselsw_curs_width 16
+#define tblselsw_curs_height 16
+#define tblselsw_curs_x_hot 1
+#define tblselsw_curs_y_hot 14
+static unsigned char tblselsw_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x0f,
+ 0x80, 0x0f, 0xc0, 0x07, 0xe2, 0x03, 0xf6, 0x01, 0xfe, 0x00, 0x7e, 0x00,
+ 0x3e, 0x00, 0x7e, 0x00, 0xfe, 0x00, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/tblselsw_mask.h b/vcl/inc/unx/x11_cursors/tblselsw_mask.h
new file mode 100644
index 0000000000..eb9bd3c2d4
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/tblselsw_mask.h
@@ -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 .
+ */
+
+#define tblselsw_mask_width 16
+#define tblselsw_mask_height 16
+static unsigned char tblselsw_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x0f, 0x80, 0x1f,
+ 0xc0, 0x1f, 0xe3, 0x0f, 0xf7, 0x07, 0xff, 0x03, 0xff, 0x01, 0xff, 0x00,
+ 0x7f, 0x00, 0xff, 0x00, 0xff, 0x01, 0xff, 0x01 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/tblselw_curs.h b/vcl/inc/unx/x11_cursors/tblselw_curs.h
new file mode 100644
index 0000000000..97de234561
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/tblselw_curs.h
@@ -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 .
+ */
+
+#define tblselw_curs_width 16
+#define tblselw_curs_height 16
+#define tblselw_curs_x_hot 1
+#define tblselw_curs_y_hot 8
+static unsigned char tblselw_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x30, 0x00,
+ 0x38, 0x00, 0xfc, 0x3f, 0xfe, 0x3f, 0xfc, 0x3f, 0x38, 0x00, 0x30, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/tblselw_mask.h b/vcl/inc/unx/x11_cursors/tblselw_mask.h
new file mode 100644
index 0000000000..601fe5396e
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/tblselw_mask.h
@@ -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 .
+ */
+
+#define tblselw_mask_width 16
+#define tblselw_mask_height 16
+static unsigned char tblselw_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x70, 0x00, 0x78, 0x00,
+ 0xfc, 0x7f, 0xfe, 0x7f, 0xff, 0x7f, 0xfe, 0x7f, 0xfc, 0x7f, 0x78, 0x00,
+ 0x70, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/vertcurs_curs.h b/vcl/inc/unx/x11_cursors/vertcurs_curs.h
new file mode 100644
index 0000000000..88cc660bad
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/vertcurs_curs.h
@@ -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 .
+ */
+
+#define vertcurs_curs_width 16
+#define vertcurs_curs_height 16
+#define vertcurs_curs_x_hot 8
+#define vertcurs_curs_y_hot 8
+static unsigned char vertcurs_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x02, 0x40,
+ 0x06, 0x60, 0xfc, 0x3f, 0x06, 0x60, 0x02, 0x40, 0x02, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/vertcurs_mask.h b/vcl/inc/unx/x11_cursors/vertcurs_mask.h
new file mode 100644
index 0000000000..0fbb5d99e5
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/vertcurs_mask.h
@@ -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 .
+ */
+
+#define vertcurs_mask_width 16
+#define vertcurs_mask_height 16
+#define vertcurs_mask_x_hot 8
+#define vertcurs_mask_y_hot 8
+static unsigned char vertcurs_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x07, 0xe0, 0x0f, 0xf0,
+ 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0x0f, 0xf0, 0x07, 0xe0, 0x07, 0xe0,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/vshear_curs.h b/vcl/inc/unx/x11_cursors/vshear_curs.h
new file mode 100644
index 0000000000..3e3859cf62
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/vshear_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define vshear_curs_width 32
+#define vshear_curs_height 32
+#define vshear_curs_x_hot 15
+#define vshear_curs_y_hot 15
+static unsigned char vshear_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04, 0x00,
+ 0x00, 0x20, 0x04, 0x00, 0x00, 0x30, 0x04, 0x00, 0x00, 0x30, 0x04, 0x00,
+ 0x00, 0x38, 0x04, 0x00, 0x00, 0x38, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00,
+ 0x00, 0x20, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00,
+ 0x00, 0x20, 0x1c, 0x00, 0x00, 0x20, 0x1c, 0x00, 0x00, 0x20, 0x0c, 0x00,
+ 0x00, 0x20, 0x0c, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/vshear_mask.h b/vcl/inc/unx/x11_cursors/vshear_mask.h
new file mode 100644
index 0000000000..df7c51a9f9
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/vshear_mask.h
@@ -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 .
+ */
+#define vshear_mask_width 32
+#define vshear_mask_height 32
+static unsigned char vshear_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x0e, 0x00, 0x00, 0x70, 0x0e, 0x00,
+ 0x00, 0x70, 0x0e, 0x00, 0x00, 0x78, 0x0e, 0x00, 0x00, 0x78, 0x0e, 0x00,
+ 0x00, 0x7c, 0x0e, 0x00, 0x00, 0x7c, 0x0e, 0x00, 0x00, 0x7c, 0x0e, 0x00,
+ 0x00, 0x70, 0x0e, 0x00, 0x00, 0x70, 0x0e, 0x00, 0x00, 0x70, 0x3e, 0x00,
+ 0x00, 0x70, 0x3e, 0x00, 0x00, 0x70, 0x3e, 0x00, 0x00, 0x70, 0x1e, 0x00,
+ 0x00, 0x70, 0x1e, 0x00, 0x00, 0x70, 0x0e, 0x00, 0x00, 0x70, 0x0e, 0x00,
+ 0x00, 0x70, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/wshide_curs.h b/vcl/inc/unx/x11_cursors/wshide_curs.h
new file mode 100644
index 0000000000..8f6d815415
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/wshide_curs.h
@@ -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 .
+ */
+
+#define hidewhitespace_curs_width 16
+#define hidewhitespace_curs_height 16
+#define hidewhitespace_curs_x_hot 0
+#define hidewhitespace_curs_y_hot 10
+static unsigned char hidewhitespace_curs_bits[] = {
+ 0x00, 0x01, 0x00, 0x01, 0xC0, 0x07, 0x80, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0x01, 0x80, 0x01, 0x80,
+ 0x01, 0x80, 0x01, 0x80, 0xFF, 0xFF, 0x00, 0x01, 0x80, 0x03, 0xC0, 0x07, 0x00, 0x01, 0x00, 0x01,
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/wshide_mask.h b/vcl/inc/unx/x11_cursors/wshide_mask.h
new file mode 100644
index 0000000000..50bd0c3f02
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/wshide_mask.h
@@ -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 .
+ */
+
+#define hidewhitespace_mask_width 16
+#define hidewhitespace_mask_height 16
+#define hidewhitespace_mask_x_hot 0
+#define hidewhitespace_mask_y_hot 10
+static unsigned char hidewhitespace_mask_bits[] = {
+ 0x00, 0x01, 0x00, 0x01, 0xC0, 0x07, 0x80, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0x01, 0x80, 0x01, 0x80,
+ 0x01, 0x80, 0x01, 0x80, 0xFF, 0xFF, 0x00, 0x01, 0x80, 0x03, 0xC0, 0x07, 0x00, 0x01, 0x00, 0x01,
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/wsshow_curs.h b/vcl/inc/unx/x11_cursors/wsshow_curs.h
new file mode 100644
index 0000000000..8b30f6e8f4
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/wsshow_curs.h
@@ -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 .
+ */
+
+#define showwhitespace_curs_width 16
+#define showwhitespace_curs_height 16
+#define showwhitespace_curs_x_hot 0
+#define showwhitespace_curs_y_hot 10
+static unsigned char showwhitespace_curs_bits[] = {
+ 0x00, 0x01, 0x80, 0x03, 0xC0, 0x07, 0x00, 0x01, 0xFF, 0xFF, 0x01, 0x81, 0x01, 0x81, 0x01, 0x81,
+ 0x01, 0x81, 0x01, 0x81, 0x01, 0x81, 0xFF, 0xFF, 0x00, 0x01, 0xC0, 0x07, 0x80, 0x03, 0x00, 0x01,
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/x11_cursors/wsshow_mask.h b/vcl/inc/unx/x11_cursors/wsshow_mask.h
new file mode 100644
index 0000000000..bba14181d8
--- /dev/null
+++ b/vcl/inc/unx/x11_cursors/wsshow_mask.h
@@ -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 .
+ */
+
+#define showwhitespace_mask_width 16
+#define showwhitespace_mask_height 16
+#define showwhitespace_mask_x_hot 0
+#define showwhitespace_mask_y_hot 10
+static unsigned char showwhitespace_mask_bits[] = {
+ 0x00, 0x01, 0x80, 0x03, 0xC0, 0x07, 0x00, 0x01, 0xFF, 0xFF, 0x01, 0x81, 0x01, 0x81, 0x01, 0x81,
+ 0x01, 0x81, 0x01, 0x81, 0x01, 0x81, 0xFF, 0xFF, 0x00, 0x01, 0xC0, 0x07, 0x80, 0x03, 0x00, 0x01,
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/vcleventlisteners.hxx b/vcl/inc/vcleventlisteners.hxx
new file mode 100644
index 0000000000..5064e9170d
--- /dev/null
+++ b/vcl/inc/vcleventlisteners.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 .
+ */
+
+#pragma once
+
+#include <tools/link.hxx>
+#include <vcl/vclevent.hxx>
+
+#include <vector>
+
+class VclEventListeners
+{
+public:
+ void Call(VclSimpleEvent& rEvent) const;
+ void addListener(const Link<VclSimpleEvent&, void>& rListener);
+ void removeListener(const Link<VclSimpleEvent&, void>& rListener);
+
+private:
+ std::vector<Link<VclSimpleEvent&, void>> m_aListeners;
+ mutable bool m_updated = false;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/vclpluginapi.h b/vcl/inc/vclpluginapi.h
new file mode 100644
index 0000000000..4211a581c9
--- /dev/null
+++ b/vcl/inc/vclpluginapi.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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_VCLPLUGINAPI_H
+#define INCLUDED_VCL_INC_VCLPLUGINAPI_H
+
+#include <sal/config.h>
+#include <sal/types.h>
+
+#if defined VCLPLUG_GEN_IMPLEMENTATION
+#define VCLPLUG_GEN_PUBLIC SAL_DLLPUBLIC_EXPORT
+#else
+#define VCLPLUG_GEN_PUBLIC SAL_DLLPUBLIC_IMPORT
+#endif
+
+#if defined VCLPLUG_GTK_IMPLEMENTATION
+#define VCLPLUG_GTK_PUBLIC SAL_DLLPUBLIC_EXPORT
+#else
+#define VCLPLUG_GTK_PUBLIC SAL_DLLPUBLIC_IMPORT
+#endif
+
+#if defined VCLPLUG_KF_IMPLEMENTATION
+#define VCLPLUG_KF_PUBLIC SAL_DLLPUBLIC_EXPORT
+#else
+#define VCLPLUG_KF_PUBLIC SAL_DLLPUBLIC_IMPORT
+#endif
+
+#if defined VCLPLUG_OSX_IMPLEMENTATION
+#define VCLPLUG_OSX_PUBLIC SAL_DLLPUBLIC_EXPORT
+#else
+#define VCLPLUG_OSX_PUBLIC SAL_DLLPUBLIC_IMPORT
+#endif
+
+#if defined VCLPLUG_QT_IMPLEMENTATION
+#define VCLPLUG_QT_PUBLIC SAL_DLLPUBLIC_EXPORT
+#else
+#define VCLPLUG_QT_PUBLIC SAL_DLLPUBLIC_IMPORT
+#endif
+
+#if defined VCLPLUG_SVP_IMPLEMENTATION
+#define VCLPLUG_SVP_PUBLIC SAL_DLLPUBLIC_EXPORT
+#else
+#define VCLPLUG_SVP_PUBLIC SAL_DLLPUBLIC_IMPORT
+#endif
+
+#if defined VCLPLUG_WIN_IMPLEMENTATION
+#define VCLPLUG_WIN_PUBLIC SAL_DLLPUBLIC_EXPORT
+#else
+#define VCLPLUG_WIN_PUBLIC SAL_DLLPUBLIC_IMPORT
+#endif
+
+#if defined DESKTOP_DETECTOR_IMPLEMENTATION
+#define DESKTOP_DETECTOR_PUBLIC SAL_DLLPUBLIC_EXPORT
+#else
+#define DESKTOP_DETECTOR_PUBLIC SAL_DLLPUBLIC_IMPORT
+#endif
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/vclstatuslistener.hxx b/vcl/inc/vclstatuslistener.hxx
new file mode 100644
index 0000000000..bc10456650
--- /dev/null
+++ b/vcl/inc/vclstatuslistener.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/.
+ */
+
+#pragma once
+
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/processfactory.hxx>
+#include <vcl/vclptr.hxx>
+
+#include <com/sun/star/frame/XStatusListener.hpp>
+#include <com/sun/star/frame/XDispatch.hpp>
+#include <com/sun/star/frame/XDispatchProvider.hpp>
+#include <com/sun/star/frame/XFrame.hpp>
+#include <com/sun/star/util/URL.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+
+template <class T> class VclStatusListener final : public cppu::WeakImplHelper < css::frame::XStatusListener>
+{
+public:
+ VclStatusListener(T* widget, const css::uno::Reference<css::frame::XFrame>& rFrame, const OUString& aCommand);
+
+private:
+ VclPtr<T> mWidget; /** The widget on which actions are performed */
+
+ /** Dispatcher. Need to keep a reference to it as long as this StatusListener exists. */
+ css::uno::Reference<css::frame::XDispatch> mxDispatch;
+ css::util::URL maCommandURL;
+ css::uno::Reference<css::frame::XFrame> mxFrame;
+
+public:
+ void SAL_CALL statusChanged(const css::frame::FeatureStateEvent& rEvent) override;
+
+ void SAL_CALL disposing(const css::lang::EventObject& /*Source*/) override;
+
+ void startListening();
+
+ void dispose();
+};
+
+template<class T>
+VclStatusListener<T>::VclStatusListener(T* widget, const css::uno::Reference<css::frame::XFrame>& rFrame, const OUString& aCommand) :
+ mWidget(widget),
+ mxFrame(rFrame)
+{
+ css::uno::Reference<css::uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext();
+ maCommandURL.Complete = aCommand;
+ css::uno::Reference<css::util::XURLTransformer> xParser = css::util::URLTransformer::create(xContext);
+ xParser->parseStrict(maCommandURL);
+}
+
+template<class T>
+void VclStatusListener<T>::startListening()
+{
+ 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);
+}
+
+template<class T>
+void VclStatusListener<T>::statusChanged(const css::frame::FeatureStateEvent& rEvent)
+{
+ mWidget->statusChanged(rEvent);
+}
+
+template<class T>
+void VclStatusListener<T>::disposing(const css::lang::EventObject& /*Source*/)
+{
+ mxDispatch.clear();
+}
+
+template<class T>
+void VclStatusListener<T>::dispose()
+{
+ if (mxDispatch.is()) {
+ mxDispatch->removeStatusListener(this, maCommandURL);
+ mxDispatch.clear();
+ }
+ mxFrame.clear();
+ mWidget.clear();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/verticaltabctrl.hxx b/vcl/inc/verticaltabctrl.hxx
new file mode 100644
index 0000000000..c5942799b3
--- /dev/null
+++ b/vcl/inc/verticaltabctrl.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <memory>
+#include <string_view>
+
+#include <tools/json_writer.hxx>
+
+#include <vcl/toolkit/ivctrl.hxx>
+#include <vcl/layout.hxx>
+
+struct VerticalTabPageData;
+
+class VerticalTabControl final : public VclHBox
+{
+ VclPtr<SvtIconChoiceCtrl> m_xChooser;
+ VclPtr<VclVBox> m_xBox;
+
+ std::vector<std::unique_ptr<VerticalTabPageData>> maPageList;
+ OUString m_sCurrentPageId;
+
+ Link<VerticalTabControl*, void> m_aActivateHdl;
+ Link<VerticalTabControl*, bool> m_aDeactivateHdl;
+
+ DECL_LINK(ChosePageHdl_Impl, SvtIconChoiceCtrl*, void);
+
+ void ActivatePage();
+ bool DeactivatePage();
+
+ VerticalTabPageData* GetPageData(std::u16string_view rId) const;
+ VerticalTabPageData* GetPageData(const SvxIconChoiceCtrlEntry* pEntry) const;
+
+public:
+ VerticalTabControl(vcl::Window* pParent);
+ virtual ~VerticalTabControl() override;
+ virtual void dispose() override;
+
+ sal_uInt16 GetPageCount() const { return m_xChooser->GetEntryCount(); }
+
+ const OUString& GetCurPageId() const { return m_sCurrentPageId; }
+ void SetCurPageId(const OUString& rId);
+
+ sal_uInt16 GetPagePos(std::u16string_view rPageId) const;
+ const OUString& GetPageId(sal_uInt16 nIndex) const;
+ VclPtr<vcl::Window> GetPage(std::u16string_view rPageId) const;
+
+ void RemovePage(std::u16string_view rPageId);
+ void InsertPage(const OUString& rPageId, const OUString& rLabel, const Image& rImage,
+ const OUString& rTooltip, VclPtr<vcl::Window> xPage, int nPos = -1);
+
+ void SetActivatePageHdl(const Link<VerticalTabControl*, void>& rLink)
+ {
+ m_aActivateHdl = rLink;
+ }
+ void SetDeactivatePageHdl(const Link<VerticalTabControl*, bool>& rLink)
+ {
+ m_aDeactivateHdl = rLink;
+ }
+
+ OUString GetPageText(std::u16string_view rPageId) const;
+ void SetPageText(std::u16string_view rPageId, const OUString& rText);
+
+ vcl::Window* GetPageParent() { return m_xBox.get(); }
+
+ virtual void DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) override;
+
+ virtual FactoryFunction GetUITestFactory() const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/watchdog.hxx b/vcl/inc/watchdog.hxx
new file mode 100644
index 0000000000..2555104a6b
--- /dev/null
+++ b/vcl/inc/watchdog.hxx
@@ -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/.
+ */
+
+#pragma once
+
+#include <sal/config.h>
+#include <salhelper/thread.hxx>
+
+class WatchdogThread final : private salhelper::Thread
+{
+ WatchdogThread();
+ virtual void execute() override;
+
+public:
+ using salhelper::Thread::acquire;
+ using salhelper::Thread::release;
+ static void start();
+ static void stop();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/widgetdraw/WidgetDefinition.hxx b/vcl/inc/widgetdraw/WidgetDefinition.hxx
new file mode 100644
index 0000000000..4176cc6614
--- /dev/null
+++ b/vcl/inc/widgetdraw/WidgetDefinition.hxx
@@ -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/.
+ *
+ */
+
+#pragma once
+
+#include <vcl/dllapi.h>
+#include <memory>
+#include <rtl/ustring.hxx>
+#include <tools/color.hxx>
+#include <unordered_map>
+#include <vector>
+#include <cstddef>
+#include <o3tl/hash_combine.hxx>
+#include <vcl/salnativewidgets.hxx>
+
+namespace vcl
+{
+enum class WidgetDrawActionType
+{
+ RECTANGLE,
+ LINE,
+ IMAGE,
+ EXTERNAL
+};
+
+class VCL_DLLPUBLIC WidgetDrawAction
+{
+public:
+ WidgetDrawAction(WidgetDrawActionType aType)
+ : maType(aType)
+ {
+ }
+
+ WidgetDrawActionType maType;
+};
+
+class VCL_DLLPUBLIC WidgetDrawActionShape : public WidgetDrawAction
+{
+public:
+ WidgetDrawActionShape(WidgetDrawActionType aType)
+ : WidgetDrawAction(aType)
+ , mnStrokeWidth(-1)
+ {
+ }
+
+ Color maStrokeColor;
+ Color maFillColor;
+ sal_Int32 mnStrokeWidth;
+};
+
+class VCL_DLLPUBLIC WidgetDrawActionRectangle : public WidgetDrawActionShape
+{
+public:
+ sal_Int32 mnRx;
+ sal_Int32 mnRy;
+
+ float mfX1;
+ float mfY1;
+ float mfX2;
+ float mfY2;
+
+ WidgetDrawActionRectangle()
+ : WidgetDrawActionShape(WidgetDrawActionType::RECTANGLE)
+ , mnRx(0)
+ , mnRy(0)
+ , mfX1(0.0f)
+ , mfY1(0.0f)
+ , mfX2(1.0f)
+ , mfY2(1.0f)
+ {
+ }
+};
+
+class VCL_DLLPUBLIC WidgetDrawActionLine : public WidgetDrawActionShape
+{
+public:
+ float mfX1;
+ float mfY1;
+ float mfX2;
+ float mfY2;
+
+ WidgetDrawActionLine()
+ : WidgetDrawActionShape(WidgetDrawActionType::LINE)
+ , mfX1(0.0)
+ , mfY1(0.0)
+ , mfX2(0.0)
+ , mfY2(0.0)
+ {
+ }
+};
+
+class VCL_DLLPUBLIC WidgetDrawActionImage : public WidgetDrawAction
+{
+public:
+ OUString msSource;
+
+ WidgetDrawActionImage()
+ : WidgetDrawAction(WidgetDrawActionType::IMAGE)
+ {
+ }
+};
+
+class VCL_DLLPUBLIC WidgetDrawActionExternal : public WidgetDrawAction
+{
+public:
+ OUString msSource;
+
+ WidgetDrawActionExternal()
+ : WidgetDrawAction(WidgetDrawActionType::EXTERNAL)
+ {
+ }
+};
+
+struct VCL_DLLPUBLIC ControlTypeAndPart
+{
+ ControlType meType;
+ ControlPart mePart;
+
+ ControlTypeAndPart(ControlType eType, ControlPart ePart)
+ : meType(eType)
+ , mePart(ePart)
+ {
+ }
+
+ bool operator==(ControlTypeAndPart const& aOther) const
+ {
+ return meType == aOther.meType && mePart == aOther.mePart;
+ }
+};
+
+} // end vcl namespace
+
+namespace std
+{
+template <> struct VCL_DLLPUBLIC hash<vcl::ControlTypeAndPart>
+{
+ std::size_t operator()(vcl::ControlTypeAndPart const& rControlTypeAndPart) const noexcept
+ {
+ std::size_t seed = 0;
+ o3tl::hash_combine(seed, rControlTypeAndPart.meType);
+ o3tl::hash_combine(seed, rControlTypeAndPart.mePart);
+ return seed;
+ }
+};
+
+} // end std namespace
+
+namespace vcl
+{
+class WidgetDefinitionState
+{
+public:
+ OString msEnabled;
+ OString msFocused;
+ OString msPressed;
+ OString msRollover;
+ OString msDefault;
+ OString msSelected;
+ OString msButtonValue;
+ OString msExtra;
+
+ WidgetDefinitionState(OString sEnabled, OString sFocused, OString sPressed, OString sRollover,
+ OString sDefault, OString sSelected, OString sButtonValue,
+ OString sExtra);
+
+ std::vector<std::shared_ptr<WidgetDrawAction>> mpWidgetDrawActions;
+
+ void addDrawRectangle(Color aStrokeColor, sal_Int32 nStrokeWidth, Color aFillColor, float fX1,
+ float fY1, float fX2, float fY2, sal_Int32 nRx, sal_Int32 nRy);
+
+ void addDrawLine(Color aStrokeColor, sal_Int32 nStrokeWidth, float fX1, float fY1, float fX2,
+ float fY2);
+
+ void addDrawImage(OUString const& sSource);
+ void addDrawExternal(OUString const& sSource);
+};
+
+class VCL_DLLPUBLIC WidgetDefinitionPart
+{
+public:
+ sal_Int32 mnWidth;
+ sal_Int32 mnHeight;
+ sal_Int32 mnMarginWidth;
+ sal_Int32 mnMarginHeight;
+ OString msOrientation;
+
+ std::vector<std::shared_ptr<WidgetDefinitionState>> getStates(ControlType eType,
+ ControlPart ePart,
+ ControlState eState,
+ ImplControlValue const& rValue);
+
+ std::vector<std::shared_ptr<WidgetDefinitionState>> maStates;
+};
+
+class VCL_DLLPUBLIC WidgetDefinitionSettings
+{
+public:
+ OString msNoActiveTabTextRaise;
+ OString msCenteredTabs;
+ OString msListBoxEntryMargin;
+ OString msDefaultFontSize;
+ OString msTitleHeight;
+ OString msFloatTitleHeight;
+ OString msListBoxPreviewDefaultLogicWidth;
+ OString msListBoxPreviewDefaultLogicHeight;
+};
+
+class VCL_DLLPUBLIC WidgetDefinitionStyle
+{
+public:
+ Color maFaceColor;
+ Color maCheckedColor;
+ Color maLightColor;
+ Color maLightBorderColor;
+ Color maShadowColor;
+ Color maDarkShadowColor;
+ 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 maRadioCheckTextColor;
+ Color maGroupTextColor;
+ Color maLabelTextColor;
+ Color maWindowColor;
+ Color maWindowTextColor;
+ Color maDialogColor;
+ Color maDialogTextColor;
+ Color maWorkspaceColor;
+ Color maMonoColor;
+ Color maFieldColor;
+ Color maFieldTextColor;
+ Color maFieldRolloverTextColor;
+ Color maActiveColor;
+ Color maActiveTextColor;
+ Color maActiveBorderColor;
+ Color maDeactiveColor;
+ Color maDeactiveTextColor;
+ Color maDeactiveBorderColor;
+ Color maMenuColor;
+ Color maMenuBarColor;
+ Color maMenuBarRolloverColor;
+ Color maMenuBorderColor;
+ Color maMenuTextColor;
+ Color maMenuBarTextColor;
+ Color maMenuBarRolloverTextColor;
+ Color maMenuBarHighlightTextColor;
+ Color maMenuHighlightColor;
+ Color maMenuHighlightTextColor;
+ Color maHighlightColor;
+ Color maHighlightTextColor;
+ Color maActiveTabColor;
+ Color maInactiveTabColor;
+ Color maTabTextColor;
+ Color maTabRolloverTextColor;
+ Color maTabHighlightTextColor;
+ Color maDisableColor;
+ Color maHelpColor;
+ Color maHelpTextColor;
+ Color maLinkColor;
+ Color maVisitedLinkColor;
+ Color maToolTextColor;
+};
+
+class VCL_DLLPUBLIC WidgetDefinition
+{
+public:
+ std::shared_ptr<WidgetDefinitionStyle> mpStyle;
+ std::shared_ptr<WidgetDefinitionSettings> mpSettings;
+ std::unordered_map<ControlTypeAndPart, std::shared_ptr<WidgetDefinitionPart>> maDefinitions;
+ std::shared_ptr<WidgetDefinitionPart> getDefinition(ControlType eType, ControlPart ePart);
+};
+
+} // end vcl namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/widgetdraw/WidgetDefinitionReader.hxx b/vcl/inc/widgetdraw/WidgetDefinitionReader.hxx
new file mode 100644
index 0000000000..98e8154e15
--- /dev/null
+++ b/vcl/inc/widgetdraw/WidgetDefinitionReader.hxx
@@ -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/.
+ *
+ */
+
+#pragma once
+
+#include <vcl/dllapi.h>
+#include <widgetdraw/WidgetDefinition.hxx>
+#include <memory>
+#include <rtl/ustring.hxx>
+#include <tools/XmlWalker.hxx>
+
+namespace vcl
+{
+class VCL_DLLPUBLIC WidgetDefinitionReader
+{
+private:
+ OUString m_rDefinitionFile;
+ OUString m_rResourcePath;
+
+ void readDefinition(tools::XmlWalker& rWalker, WidgetDefinition& rWidgetDefinition,
+ ControlType eType);
+
+ void readPart(tools::XmlWalker& rWalker, std::shared_ptr<WidgetDefinitionPart> rpPart);
+
+ void readDrawingDefinition(tools::XmlWalker& rWalker,
+ const std::shared_ptr<WidgetDefinitionState>& rStates);
+
+public:
+ WidgetDefinitionReader(OUString aDefinitionFile, OUString aResourcePath);
+ bool read(WidgetDefinition& rWidgetDefinition);
+};
+
+} // end vcl namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/DWriteTextRenderer.hxx b/vcl/inc/win/DWriteTextRenderer.hxx
new file mode 100644
index 0000000000..d4bb45e589
--- /dev/null
+++ b/vcl/inc/win/DWriteTextRenderer.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <usp10.h>
+#include <d2d1.h>
+#include <dwrite.h>
+
+#include <win/winlayout.hxx>
+
+enum class D2DTextAntiAliasMode
+{
+ Default,
+ Aliased,
+ AntiAliased,
+ ClearType,
+};
+
+class D2DWriteTextOutRenderer : public TextOutRenderer
+{
+public:
+ explicit D2DWriteTextOutRenderer(bool bRenderingModeNatural);
+ virtual ~D2DWriteTextOutRenderer() override;
+
+ bool operator()(GenericSalLayout const &rLayout,
+ SalGraphics &rGraphics,
+ HDC hDC,
+ bool bRenderingModeNatural) override;
+
+ HRESULT BindDC(HDC hDC, tools::Rectangle const & rRect = tools::Rectangle(0, 0, 1, 1));
+
+ HRESULT CreateRenderTarget(bool bRenderingModeNatural);
+
+ bool Ready() const;
+
+ void applyTextAntiAliasMode(bool bRenderingModeNatural);
+
+ bool GetRenderingModeNatural() const { return mbRenderingModeNatural; }
+
+private:
+ // This is a singleton object disable copy ctor and assignment operator
+ D2DWriteTextOutRenderer(const D2DWriteTextOutRenderer &) = delete;
+ D2DWriteTextOutRenderer & operator = (const D2DWriteTextOutRenderer &) = delete;
+
+ IDWriteFontFace* GetDWriteFace(const WinFontInstance& rWinFont, float * lfSize) const;
+ bool performRender(GenericSalLayout const &rLayout, SalGraphics &rGraphics, HDC hDC, bool& bRetry, bool bRenderingModeNatural);
+
+ ID2D1Factory * mpD2DFactory;
+ IDWriteFactory * mpDWriteFactory;
+ ID2D1DCRenderTarget * mpRT;
+ const D2D1_RENDER_TARGET_PROPERTIES mRTProps;
+
+ bool mbRenderingModeNatural;
+ D2DTextAntiAliasMode meTextAntiAliasMode;
+};
+
+/**
+ * Sets and unsets the needed DirectWrite transform to support the font's horizontal scaling and
+ * rotation.
+ */
+class WinFontTransformGuard
+{
+public:
+ WinFontTransformGuard(ID2D1RenderTarget* pRenderTarget, float fHScale, const GenericSalLayout& rLayout, const D2D1_POINT_2F& rBaseline, bool bIsVertical);
+ ~WinFontTransformGuard();
+
+private:
+ ID2D1RenderTarget* mpRenderTarget;
+ D2D1::Matrix3x2F maTransform;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/dnd_source.hxx b/vcl/inc/win/dnd_source.hxx
new file mode 100644
index 0000000000..ea794f069f
--- /dev/null
+++ b/vcl/inc/win/dnd_source.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <com/sun/star/datatransfer/dnd/XDragSource.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragSourceContext.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <osl/mutex.hxx>
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <oleidl.h>
+
+#include <systools/win32/comtools.hxx>
+
+namespace com::sun::star::uno { class XComponentContext; }
+
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+using namespace cppu;
+using namespace osl;
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::datatransfer::dnd;
+
+class SourceContext;
+// RIGHT MOUSE BUTTON drag and drop not supported currently.
+// ALT modifier is considered to effect a user selection of effects
+class DragSource:
+ public cppu::BaseMutex,
+ public WeakComponentImplHelper<XDragSource, XInitialization, XServiceInfo>,
+ public IDropSource
+
+{
+ Reference<XComponentContext> m_xContext;
+ HWND m_hAppWindow;
+
+ // The mouse button that set off the drag and drop operation
+ short m_MouseButton;
+
+ // First starting a new drag and drop thread if
+ // the last one has finished
+ void StartDragImpl(
+ const DragGestureEvent& trigger,
+ sal_Int8 sourceActions,
+ sal_Int32 cursor,
+ sal_Int32 image,
+ const Reference<XTransferable >& trans,
+ const Reference<XDragSourceListener >& listener);
+
+public:
+ LONG m_RunningDndOperationCount;
+
+public:
+ // only valid for one dnd operation
+ // the thread ID of the thread which created the window
+ DWORD m_threadIdWindow;
+ // The context notifies the XDragSourceListener s
+ Reference<XDragSourceContext> m_currentContext;
+
+ // the wrapper for the Transferable ( startDrag)
+ IDataObjectPtr m_spDataObject;
+
+ sal_Int8 m_sourceActions;
+
+public:
+ explicit DragSource(const Reference<XComponentContext>& rxContext);
+ virtual ~DragSource() override;
+ DragSource(const DragSource&) = delete;
+ DragSource &operator= ( const DragSource&) = delete;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const Sequence< Any >& aArguments ) override;
+
+ // XDragSource
+ virtual sal_Bool SAL_CALL isDragImageSupported( ) override;
+ virtual sal_Int32 SAL_CALL getDefaultCursor( sal_Int8 dragAction ) override;
+ virtual void SAL_CALL startDrag( const DragGestureEvent& trigger,
+ sal_Int8 sourceActions,
+ sal_Int32 cursor,
+ sal_Int32 image,
+ const Reference<XTransferable >& trans,
+ const Reference<XDragSourceListener >& listener ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override;
+
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(
+ /* [in] */ REFIID riid,
+ /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject) override;
+
+ virtual ULONG STDMETHODCALLTYPE AddRef( ) override;
+
+ virtual ULONG STDMETHODCALLTYPE Release( ) override;
+
+ // IDropSource
+ virtual HRESULT STDMETHODCALLTYPE QueryContinueDrag(
+ /* [in] */ BOOL fEscapePressed,
+ /* [in] */ DWORD grfKeyState) override;
+
+ virtual HRESULT STDMETHODCALLTYPE GiveFeedback(
+ /* [in] */ DWORD dwEffect) override;
+
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/dnd_target.hxx b/vcl/inc/win/dnd_target.hxx
new file mode 100644
index 0000000000..d9d4d43d83
--- /dev/null
+++ b/vcl/inc/win/dnd_target.hxx
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/datatransfer/dnd/DropTargetDragEnterEvent.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/interfacecontainer.hxx>
+#include <osl/mutex.hxx>
+
+#include <oleidl.h>
+
+namespace com::sun::star::uno
+{
+class XComponentContext;
+}
+
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+using namespace cppu;
+using namespace osl;
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::datatransfer::dnd;
+
+// The client
+// has to call XComponent::dispose. The thread that calls initialize
+// must also execute the destruction of the instance. This is because
+// initialize calls OleInitialize and the destructor calls OleUninitialize.
+// If the service calls OleInitialize then it also calls OleUnitialize when
+// it is destroyed. Therefore no second instance may exist which was
+// created in the same thread and still needs OLE.
+class DropTarget : public cppu::BaseMutex,
+ public WeakComponentImplHelper<XInitialization, XDropTarget, XServiceInfo>
+
+{
+private:
+ friend unsigned __stdcall DndTargetOleSTAFunc(void* pParams);
+ // The native window which acts as drop target.
+ // It is set in initialize. In case RegisterDragDrop fails it is set
+ // to NULL
+ HWND m_hWnd; // set by initialize
+ // Holds the thread id of the thread which created the window that is the
+ // drop target. Only used when DropTarget::initialize is called from an MTA
+ // thread
+ DWORD m_threadIdWindow;
+ // This is the thread id of the OLE thread that is created in DropTarget::initialize
+ // when the calling thread is an MTA
+ unsigned m_threadIdTarget;
+ // The handle of the thread that is created in DropTarget::initialize
+ // when the calling thread is an MTA
+ HANDLE m_hOleThread;
+ // The thread id of the thread which called initialize. When the service dies
+ // than m_oleThreadId is used to determine if the service successfully called
+ // OleInitialize. If so then OleUninitialize has to be called.
+ DWORD m_oleThreadId;
+ // An Instance of IDropTargetImpl which receives calls from the system's drag
+ // and drop implementation. It delegate the calls to name alike functions in
+ // this class.
+ IDropTarget* m_pDropTarget;
+
+ Reference<XComponentContext> m_xContext;
+ // If m_bActive == sal_True then events are fired to XDropTargetListener s,
+ // none otherwise. The default value is sal_True.
+ bool m_bActive;
+ sal_Int8 m_nDefaultActions;
+
+ // This value is set when a XDropTargetListener calls accept or reject on
+ // the XDropTargetDropContext or XDropTargetDragContext.
+ // The values are from the DNDConstants group.
+ sal_Int8 m_nCurrentDropAction;
+ // This value is manipulated by the XDropTargetListener
+ sal_Int8 m_nLastDropAction;
+
+ Reference<XTransferable> m_currentData;
+ // The current action is used to determine if the USER
+ // action has changed (dropActionChanged)
+ // sal_Int8 m_userAction;
+ // Set by listeners when they call XDropTargetDropContext::dropComplete
+ bool m_bDropComplete;
+ Reference<XDropTargetDragContext> m_currentDragContext;
+ Reference<XDropTargetDropContext> m_currentDropContext;
+
+public:
+ explicit DropTarget(const Reference<XComponentContext>& rxContext);
+ virtual ~DropTarget() override;
+ DropTarget(DropTarget const&) = delete;
+ DropTarget& operator=(DropTarget const&) = delete;
+
+ // Overrides WeakComponentImplHelper::disposing which is called by
+ // WeakComponentImplHelper::dispose
+ // Must be called.
+ virtual void SAL_CALL disposing() override;
+ // XInitialization
+ virtual void SAL_CALL initialize(const Sequence<Any>& aArguments) override;
+
+ // XDropTarget
+ virtual void SAL_CALL addDropTargetListener(const Reference<XDropTargetListener>& dtl) override;
+ virtual void SAL_CALL
+ removeDropTargetListener(const Reference<XDropTargetListener>& dtl) override;
+ // Default is not active
+ virtual sal_Bool SAL_CALL isActive() override;
+ virtual void SAL_CALL setActive(sal_Bool isActive) override;
+ virtual sal_Int8 SAL_CALL getDefaultActions() override;
+ virtual void SAL_CALL setDefaultActions(sal_Int8 actions) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override;
+ virtual Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+ // Functions called from the IDropTarget implementation ( m_pDropTarget)
+ virtual HRESULT DragEnter(
+ /* [unique][in] */ IDataObject* pDataObj,
+ /* [in] */ DWORD grfKeyState,
+ /* [in] */ POINTL pt,
+ /* [out][in] */ DWORD* pdwEffect);
+
+ virtual HRESULT STDMETHODCALLTYPE DragOver(
+ /* [in] */ DWORD grfKeyState,
+ /* [in] */ POINTL pt,
+ /* [out][in] */ DWORD* pdwEffect);
+
+ virtual HRESULT STDMETHODCALLTYPE DragLeave();
+
+ virtual HRESULT STDMETHODCALLTYPE Drop(
+ /* [unique][in] */ IDataObject* pDataObj,
+ /* [in] */ DWORD grfKeyState,
+ /* [in] */ POINTL pt,
+ /* [out][in] */ DWORD* pdwEffect);
+
+ // Non - interface functions --------------------------------------------------
+ // XDropTargetDropContext delegated from DropContext
+
+ void _acceptDrop(sal_Int8 dropOperation, const Reference<XDropTargetDropContext>& context);
+ void _rejectDrop(const Reference<XDropTargetDropContext>& context);
+ void _dropComplete(bool success, const Reference<XDropTargetDropContext>& context);
+
+ // XDropTargetDragContext delegated from DragContext
+ void _acceptDrag(sal_Int8 dragOperation, const Reference<XDropTargetDragContext>& context);
+ void _rejectDrag(const Reference<XDropTargetDragContext>& context);
+
+protected:
+ // Gets the current action dependent on the pressed modifiers, the effects
+ // supported by the drop source (IDropSource) and the default actions of the
+ // drop target (XDropTarget, this class))
+ inline sal_Int8 getFilteredActions(DWORD grfKeyState, DWORD sourceActions);
+ // Only filters with the default actions
+ inline sal_Int8 getFilteredActions(DWORD grfKeyState);
+
+ void fire_drop(const DropTargetDropEvent& dte);
+ void fire_dragEnter(const DropTargetDragEnterEvent& dtde);
+ void fire_dragExit(const DropTargetEvent& dte);
+ void fire_dragOver(const DropTargetDragEvent& dtde);
+ void fire_dropActionChanged(const DropTargetDragEvent& dtde);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/salbmp.h b/vcl/inc/win/salbmp.h
new file mode 100644
index 0000000000..2edf291342
--- /dev/null
+++ b/vcl/inc/win/salbmp.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <tools/gen.hxx>
+#include <win/wincomp.hxx>
+#include <salbmp.hxx>
+#include <basegfx/utils/systemdependentdata.hxx>
+#include <memory>
+
+
+struct BitmapBuffer;
+class BitmapColor;
+class BitmapPalette;
+class SalGraphics;
+namespace Gdiplus { class Bitmap; }
+
+class WinSalBitmap final: public SalBitmap, public basegfx::SystemDependentDataHolder
+{
+private:
+ Size maSize;
+ HGLOBAL mhDIB;
+ HBITMAP mhDDB;
+
+ sal_uInt16 mnBitCount;
+
+ std::shared_ptr<Gdiplus::Bitmap> ImplCreateGdiPlusBitmap(const WinSalBitmap& rAlphaSource);
+ std::shared_ptr<Gdiplus::Bitmap> ImplCreateGdiPlusBitmap();
+
+public:
+
+ HGLOBAL ImplGethDIB() const { return mhDIB; }
+ HBITMAP ImplGethDDB() const { return mhDDB; }
+
+ std::shared_ptr< Gdiplus::Bitmap > ImplGetGdiPlusBitmap(const WinSalBitmap* pAlphaSource = nullptr) const;
+
+ static HGLOBAL ImplCreateDIB( const Size& rSize, vcl::PixelFormat ePixelFormat, const BitmapPalette& rPal );
+ static HANDLE ImplCopyDIBOrDDB( HANDLE hHdl, bool bDIB );
+ static sal_uInt16 ImplGetDIBColorCount( HGLOBAL hDIB );
+
+public:
+
+ WinSalBitmap();
+ virtual ~WinSalBitmap() override;
+
+ using SalBitmap::addOrReplaceSystemDependentData;
+ using SalBitmap::getSystemDependentData;
+ using SystemDependentDataHolder::addOrReplaceSystemDependentData;
+ using SystemDependentDataHolder::getSystemDependentData;
+
+public:
+
+ bool Create( HANDLE hBitmap );
+ virtual bool Create( const Size& rSize, vcl::PixelFormat ePixelFormat, const BitmapPalette& rPal ) override;
+ virtual bool Create( const SalBitmap& rSalBmpImpl ) override;
+ virtual bool Create( const SalBitmap& rSalBmpImpl, SalGraphics* pGraphics ) override;
+ virtual bool Create( const SalBitmap& rSalBmpImpl, vcl::PixelFormat eNewPixelFormat ) override;
+ virtual bool Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& rBitmapCanvas,
+ Size& rSize,
+ bool bMask = false ) override;
+
+ virtual void Destroy() override;
+
+ virtual Size GetSize() const override { return maSize; }
+ virtual sal_uInt16 GetBitCount() const override { return mnBitCount; }
+
+ virtual BitmapBuffer* AcquireBuffer( BitmapAccessMode nMode ) override;
+ virtual void ReleaseBuffer( BitmapBuffer* pBuffer, BitmapAccessMode nMode ) override;
+ virtual bool GetSystemData( BitmapSystemData& rData ) override;
+
+ virtual bool ScalingSupported() const override;
+ virtual bool Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag ) override;
+ virtual bool Replace( const Color& rSearchColor, const Color& rReplaceColor, sal_uInt8 nTol ) override;
+
+ virtual const basegfx::SystemDependentDataHolder* accessSystemDependentDataHolder() const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/saldata.hxx b/vcl/inc/win/saldata.hxx
new file mode 100644
index 0000000000..80acfaabec
--- /dev/null
+++ b/vcl/inc/win/saldata.hxx
@@ -0,0 +1,295 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <config_features.h>
+
+#include <array>
+#include <memory>
+#include <osl/module.h>
+
+#include <svdata.hxx>
+#include <salwtype.hxx>
+
+#include <systools/win32/comtools.hxx>
+#include <tools/long.hxx>
+
+#include <win/wincomp.hxx>
+
+#include <set>
+#include <map>
+
+class AutoTimer;
+class WinSalInstance;
+class WinSalObject;
+class WinSalFrame;
+class WinSalVirtualDevice;
+class WinSalPrinter;
+namespace vcl { class Font; }
+struct HDCCache;
+struct TempFontItem;
+class TextOutRenderer;
+#if HAVE_FEATURE_SKIA
+class SkiaControlsCache;
+#endif
+
+#define MAX_STOCKPEN 4
+#define MAX_STOCKBRUSH 4
+#define SAL_CLIPRECT_COUNT 16
+
+#define CACHESIZE_HDC 3
+#define CACHED_HDC_1 0
+#define CACHED_HDC_2 1
+#define CACHED_HDC_DRAW 2
+#define CACHED_HDC_DEFEXT 64
+
+struct HDCCache
+{
+ HDC mhDC = nullptr;
+ HPALETTE mhDefPal = nullptr;
+ HBITMAP mhDefBmp = nullptr;
+ HBITMAP mhSelBmp = nullptr;
+ HBITMAP mhActBmp = nullptr;
+};
+
+struct SalIcon
+{
+ int nId;
+ HICON hIcon;
+ HICON hSmallIcon;
+ SalIcon *pNext;
+};
+
+class SalData : public sal::systools::CoInitializeGuard
+{
+public:
+ SalData();
+ ~SalData();
+
+ // native widget framework
+ static void initNWF();
+ static void deInitNWF();
+
+ // fill maVKMap;
+ void initKeyCodeMap();
+
+ // checks if the menuhandle was created by VCL
+ bool IsKnownMenuHandle( HMENU hMenu );
+
+ bool mbResourcesAlreadyFreed;
+
+public:
+ HINSTANCE mhInst; // default instance handle
+ int mnCmdShow; // default frame show style
+ HPALETTE mhDitherPal; // dither palette
+ HGLOBAL mhDitherDIB; // dither memory handle
+ BYTE* mpDitherDIB; // dither memory
+ BYTE* mpDitherDIBData; // beginning of DIB data
+ std::unique_ptr<tools::Long[]> mpDitherDiff; // Dither mapping table
+ std::unique_ptr<BYTE[]> mpDitherLow; // Dither mapping table
+ std::unique_ptr<BYTE[]> mpDitherHigh; // Dither mapping table
+ HHOOK mhSalObjMsgHook; // hook to get interesting msg for SalObject
+ HWND mhWantLeaveMsg; // window handle, that want a MOUSELEAVE message
+ WinSalInstance* mpInstance;
+ WinSalFrame* mpFirstFrame; // pointer of first frame
+ WinSalObject* mpFirstObject; // pointer of first object window
+ WinSalVirtualDevice* mpFirstVD; // first VirDev
+ WinSalPrinter* mpFirstPrinter; // first printing printer
+ std::array<HDCCache, CACHESIZE_HDC> maHDCCache; // Cache for three DC's
+ HBITMAP mh50Bmp; // 50% Bitmap
+ HBRUSH mh50Brush; // 50% Brush
+ COLORREF maStockPenColorAry[MAX_STOCKPEN];
+ COLORREF maStockBrushColorAry[MAX_STOCKBRUSH];
+ HPEN mhStockPenAry[MAX_STOCKPEN];
+ HBRUSH mhStockBrushAry[MAX_STOCKBRUSH];
+ sal_uInt16 mnStockPenCount; // count of static pens
+ sal_uInt16 mnStockBrushCount; // count of static brushes
+ WPARAM mnSalObjWantKeyEvt; // KeyEvent that should be processed by SalObj-Hook
+ BYTE mnCacheDCInUse; // count of CacheDC in use
+ bool mbObjClassInit; // is SALOBJECTCLASS initialised
+ bool mbInPalChange; // is in WM_QUERYNEWPALETTE
+ DWORD mnAppThreadId; // Id from Application-Thread
+ SalIcon* mpFirstIcon; // icon cache, points to first icon, NULL if none
+ TempFontItem* mpSharedTempFontItem; // LibreOffice shared fonts
+ TempFontItem* mpOtherTempFontItem; // other temporary fonts (embedded?)
+ bool mbThemeChanged; // true if visual theme was changed: throw away theme handles
+ bool mbThemeMenuSupport;
+
+ // for GdiPlus GdiplusStartup/GdiplusShutdown
+ ULONG_PTR gdiplusToken;
+
+ std::set< HMENU > mhMenuSet; // keeps track of menu handles created by VCL, used by IsKnownMenuHandle()
+ std::map< UINT,sal_uInt16 > maVKMap; // map some dynamic VK_* entries
+
+ std::unique_ptr<TextOutRenderer> m_pD2DWriteTextOutRenderer;
+ // tdf#107205 need 2 instances because D2DWrite can't rotate text
+ std::unique_ptr<TextOutRenderer> m_pExTextOutRenderer;
+#if HAVE_FEATURE_SKIA
+ std::unique_ptr<SkiaControlsCache> m_pSkiaControlsCache;
+#endif
+};
+
+struct SalShlData
+{
+ HINSTANCE mhInst; // Instance of SAL-DLL
+ UINT mnWheelScrollLines; // WheelScrollLines
+ UINT mnWheelScrollChars; // WheelScrollChars
+};
+
+extern SalShlData aSalShlData;
+
+void ImplClearHDCCache( SalData* pData );
+HDC ImplGetCachedDC( sal_uLong nID, HBITMAP hBmp = nullptr );
+void ImplReleaseCachedDC( sal_uLong nID );
+
+void ImplReleaseTempFonts(SalData&, bool bAll);
+
+HCURSOR ImplLoadSalCursor( int nId );
+HBITMAP ImplLoadSalBitmap( int nId );
+bool ImplLoadSalIcon( int nId, HICON& rIcon, HICON& rSmallIcon );
+
+void ImplInitSalGDI();
+void ImplFreeSalGDI();
+
+void ImplSalYieldMutexAcquireWithWait( sal_uInt32 nCount = 1 );
+bool ImplSalYieldMutexTryToAcquire();
+void ImplSalYieldMutexRelease();
+
+LRESULT CALLBACK SalFrameWndProcW( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam );
+
+void SalTestMouseLeave();
+
+bool ImplHandleSalObjKeyMsg( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam );
+bool ImplHandleSalObjSysCharMsg( HWND hWnd, WPARAM wParam, LPARAM lParam );
+bool ImplHandleGlobalMsg( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam, LRESULT& rlResult );
+
+WinSalObject* ImplFindSalObject( HWND hWndChild );
+bool ImplSalPreDispatchMsg( const MSG* pMsg );
+void ImplSalPostDispatchMsg( const MSG* pMsg );
+
+void ImplSalLogFontToFontW( HDC hDC, const LOGFONTW& rLogFont, vcl::Font& rFont );
+
+rtl_TextEncoding ImplSalGetSystemEncoding();
+OUString ImplSalGetUniString(const char* pStr, sal_Int32 nLen = -1);
+int ImplSalWICompareAscii( const wchar_t* pStr1, const char* pStr2 );
+
+#define SAL_FRAME_WNDEXTRA sizeof( DWORD )
+#define SAL_FRAME_THIS GWLP_USERDATA
+#define SAL_FRAME_CLASSNAMEW L"SALFRAME"
+#define SAL_SUBFRAME_CLASSNAMEW L"SALSUBFRAME"
+#define SAL_TMPSUBFRAME_CLASSNAMEW L"SALTMPSUBFRAME"
+#define SAL_OBJECT_WNDEXTRA sizeof( DWORD )
+#define SAL_OBJECT_THIS GWLP_USERDATA
+#define SAL_OBJECT_CLASSNAMEW L"SALOBJECT"
+#define SAL_OBJECT_CHILDCLASSNAMEW L"SALOBJECTCHILD"
+#define SAL_COM_CLASSNAMEW L"SALCOMWND"
+
+// wParam == bWait; lParam == 0
+#define SAL_MSG_THREADYIELD (WM_USER+111)
+// wParam == 0; lParam == nMS
+#define SAL_MSG_STARTTIMER (WM_USER+113)
+// wParam == nFrameStyle; lParam == pParent; lResult == pFrame
+#define SAL_MSG_CREATEFRAME (WM_USER+114)
+// wParam == 0; lParam == 0
+#define SAL_MSG_DESTROYFRAME (WM_USER+115)
+// wParam == 0; lParam == pParent; lResult == pObject
+#define SAL_MSG_CREATEOBJECT (WM_USER+116)
+// wParam == 0; lParam == pObject;
+#define SAL_MSG_DESTROYOBJECT (WM_USER+117)
+// wParam == hWnd; lParam == 0; lResult == hDC
+#define SAL_MSG_GETCACHEDDC (WM_USER+120)
+// wParam == hWnd; lParam == 0
+#define SAL_MSG_RELEASEDC (WM_USER+121)
+// wParam == newParentHwnd; lParam == oldHwnd; lResult == newhWnd
+#define SAL_MSG_RECREATEHWND (WM_USER+122)
+// wParam == newParentHwnd; lParam == oldHwnd; lResult == newhWnd
+#define SAL_MSG_RECREATECHILDHWND (WM_USER+123)
+// wParam == 0; lParam == HWND;
+#define SAL_MSG_DESTROYHWND (WM_USER+124)
+
+// wParam == 0; lParam == pData
+#define SAL_MSG_USEREVENT (WM_USER+130)
+// wParam == 0; lParam == MousePosition relative to upper left of screen
+#define SAL_MSG_MOUSELEAVE (WM_USER+131)
+// NULL-Message, should not be processed
+#define SAL_MSG_DUMMY (WM_USER+132)
+// Used for SETFOCUS and KILLFOCUS
+// wParam == 0; lParam == 0
+#define SAL_MSG_POSTFOCUS (WM_USER+133)
+// wParam == wParam; lParam == lParam
+#define SAL_MSG_POSTQUERYNEWPAL (WM_USER+134)
+// wParam == wParam; lParam == lParam
+#define SAL_MSG_POSTPALCHANGED (WM_USER+135)
+// wParam == wParam; lParam == lParam
+#define SAL_MSG_POSTMOVE (WM_USER+136)
+// wParam == wParam; lParam == lParam
+#define SAL_MSG_POSTCALLSIZE (WM_USER+137)
+// wParam == pRECT; lParam == 0
+#define SAL_MSG_POSTPAINT (WM_USER+138)
+// wParam == 0; lParam == pFrame; lResult 0
+#define SAL_MSG_FORCEPALETTE (WM_USER+139)
+// wParam == 0; lParam == 0
+#define SAL_MSG_CAPTUREMOUSE (WM_USER+140)
+// wParam == 0; lParam == 0
+#define SAL_MSG_RELEASEMOUSE (WM_USER+141)
+// wParam == nFlags; lParam == 0
+#define SAL_MSG_TOTOP (WM_USER+142)
+// wParam == bVisible; lParam == 0
+#define SAL_MSG_SHOW (WM_USER+143)
+// wParam == 0; lParam == SalInputContext
+#define SAL_MSG_SETINPUTCONTEXT (WM_USER+144)
+// wParam == nFlags; lParam == 0
+#define SAL_MSG_ENDEXTTEXTINPUT (WM_USER+145)
+
+// SysChild-ToTop; wParam = 0; lParam = 0
+#define SALOBJ_MSG_TOTOP (WM_USER+160)
+// Used for SETFOCUS and KILLFOCUS
+// POSTFOCUS-Message; wParam == bFocus; lParam == 0
+#define SALOBJ_MSG_POSTFOCUS (WM_USER+161)
+
+// Call the Timer's callback from the main thread
+// wParam = 1 == run when yield is idle instead of direct
+#define SAL_MSG_TIMER_CALLBACK (WM_USER+162)
+// Stop the timer from the main thread; wParam = 0, lParam = 0
+#define SAL_MSG_STOPTIMER (WM_USER+163)
+// Start a real timer while GUI is blocked by native dialog
+#define SAL_MSG_FORCE_REAL_TIMER (WM_USER+164)
+
+inline void SetWindowPtr( HWND hWnd, WinSalFrame* pThis )
+{
+ SetWindowLongPtrW( hWnd, SAL_FRAME_THIS, reinterpret_cast<LONG_PTR>(pThis) );
+}
+
+inline WinSalFrame* GetWindowPtr( HWND hWnd )
+{
+ return reinterpret_cast<WinSalFrame*>(GetWindowLongPtrW( hWnd, SAL_FRAME_THIS ));
+}
+
+inline void SetSalObjWindowPtr( HWND hWnd, WinSalObject* pThis )
+{
+ SetWindowLongPtrW( hWnd, SAL_OBJECT_THIS, reinterpret_cast<LONG_PTR>(pThis) );
+}
+
+inline WinSalObject* GetSalObjWindowPtr( HWND hWnd )
+{
+ return reinterpret_cast<WinSalObject*>(GetWindowLongPtrW( hWnd, SAL_OBJECT_THIS ));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/salframe.h b/vcl/inc/win/salframe.h
new file mode 100644
index 0000000000..de72c089b5
--- /dev/null
+++ b/vcl/inc/win/salframe.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <string_view>
+
+#include <vcl/sysdata.hxx>
+#include <vcl/windowstate.hxx>
+#include <salframe.hxx>
+#include <svsys.h>
+
+class WinSalGraphics;
+
+class WinSalFrame final: public SalFrame
+{
+ vcl::WindowState m_eState;
+
+public:
+ HWND mhWnd; // Window handle
+ HCURSOR mhCursor; // cursor handle
+ HIMC mhDefIMEContext; // default IME-Context
+ WinSalGraphics* mpLocalGraphics; // current main thread frame graphics
+ WinSalGraphics* mpThreadGraphics; // current frame graphics for other threads (DCX_CACHE)
+ WinSalFrame* mpNextFrame; // pointer to next frame
+ HMENU mSelectedhMenu; // the menu where highlighting is currently going on
+ HMENU mLastActivatedhMenu; // the menu that was most recently opened
+ SystemEnvData maSysData; // system data
+ int mnShowState; // show state
+ int mnMinWidth; // min. client width in pixeln
+ int mnMinHeight; // min. client height in pixeln
+ int mnMaxWidth; // max. client width in pixeln
+ int mnMaxHeight; // max. client height in pixeln
+ RECT maFullScreenRect; // fullscreen rect
+ int mnFullScreenShowState; // fullscreen restore show state
+ bool mbFullScreenCaption; // WS_CAPTION reset in full screen mode.
+ UINT mnInputLang; // current Input Language
+ UINT mnInputCodePage; // current Input CodePage
+ SalFrameStyleFlags mnStyle; // style
+ bool mbGraphics; // is Graphics used
+ bool mbCaption; // has window a caption
+ bool mbBorder; // has window a border
+ bool mbFixBorder; // has window a fixed border
+ bool mbSizeBorder; // has window a sizeable border
+ bool mbNoIcon; // is a window without an icon
+ bool mbFloatWin; // is a FloatingWindow
+ bool mbPresentation; // TRUE: Presentation Mode running
+ bool mbInShow; // inside a show call
+ bool mbRestoreMaximize; // Restore-Maximize
+ bool mbInMoveMsg; // Move-Message is being processed
+ bool mbInSizeMsg; // Size-Message is being processed
+ bool mbFullScreenToolWin; // WS_EX_TOOLWINDOW reset in FullScreenMode
+ bool mbDefPos; // default-position
+ bool mbOverwriteState; // TRUE: possible to change WindowState
+ bool mbIME; // TRUE: We are in IME Mode
+ bool mbHandleIME; // TRUE: We are handling the IME-Messages
+ bool mbSpezIME; // TRUE: special IME
+ bool mbAtCursorIME; // TRUE: We are only handling some IME-Messages
+ bool mbCandidateMode; // TRUE: We are in Candidate-Mode
+ static bool mbInReparent; // TRUE: ignore focus lost and gain due to reparenting
+
+ RGNDATA* mpClipRgnData;
+ RECT* mpNextClipRect;
+ bool mbFirstClipRect;
+ sal_Int32 mnDisplay; // Display used for Fullscreen, 0 is primary monitor
+ bool mbPropertiesStored; // has values stored in the window property store
+
+ void updateScreenNumber();
+
+private:
+ void ImplSetParentFrame( HWND hNewParentWnd, bool bAsChild );
+ bool InitFrameGraphicsDC( WinSalGraphics *pGraphics, HDC hDC, HWND hWnd );
+ bool ReleaseFrameGraphicsDC( WinSalGraphics* pGraphics );
+
+public:
+ WinSalFrame();
+ virtual ~WinSalFrame() override;
+
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics( SalGraphics* pGraphics ) override;
+ virtual bool PostEvent(std::unique_ptr<ImplSVEvent> pData) override;
+ virtual void SetTitle( const OUString& rTitle ) override;
+ virtual void SetIcon( sal_uInt16 nIcon ) override;
+ virtual void SetMenu( SalMenu* pSalMenu ) override;
+ virtual void SetExtendedFrameStyle( SalExtStyle nExtStyle ) override;
+ virtual void Show( bool bVisible, bool bNoActivate = false ) override;
+ virtual void SetMinClientSize( tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void SetMaxClientSize( tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags ) override;
+ virtual void GetClientSize( tools::Long& rWidth, tools::Long& rHeight ) override;
+ virtual void GetWorkArea( AbsoluteScreenPixelRectangle& rRect ) override;
+ virtual SalFrame* GetParent() const override;
+ virtual void SetWindowState(const vcl::WindowData*) override;
+ virtual bool GetWindowState(vcl::WindowData*) override;
+ virtual void ShowFullScreen( bool bFullScreen, sal_Int32 nDisplay ) override;
+ virtual void StartPresentation( bool bStart ) override;
+ virtual void SetAlwaysOnTop( bool bOnTop ) override;
+ virtual void ToTop( SalFrameToTop nFlags ) override;
+ virtual void SetPointer( PointerStyle ePointerStyle ) override;
+ virtual void CaptureMouse( bool bMouse ) override;
+ virtual void SetPointerPos( tools::Long nX, tools::Long nY ) override;
+ using SalFrame::Flush;
+ virtual void Flush() override;
+ virtual void SetInputContext( SalInputContext* pContext ) override;
+ virtual void EndExtTextInput( EndExtTextInputFlags nFlags ) override;
+ virtual OUString GetKeyName( sal_uInt16 nKeyCode ) override;
+ virtual bool MapUnicodeToKeyCode( sal_Unicode aUnicode, LanguageType aLangType, vcl::KeyCode& rKeyCode ) override;
+ virtual LanguageType GetInputLanguage() override;
+ virtual void UpdateSettings( AllSettings& rSettings ) override;
+ virtual void Beep() override;
+ virtual const SystemEnvData* GetSystemData() const override;
+ virtual SalPointerState GetPointerState() override;
+ virtual KeyIndicatorState GetIndicatorState() override;
+ virtual void SimulateKeyPress( sal_uInt16 nKeyCode ) override;
+ virtual void SetParent( SalFrame* pNewParent ) override;
+ virtual void SetPluginParent( SystemParentData* pNewParent ) override;
+ virtual void SetScreenNumber( unsigned int ) override;
+ virtual void SetApplicationID( const OUString &rApplicationID ) override;
+ virtual void ResetClipRegion() override;
+ virtual void BeginSetClipRegion( sal_uInt32 nRects ) override;
+ virtual void UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void EndSetClipRegion() override;
+ virtual void UpdateDarkMode() override;
+ virtual bool GetUseDarkMode() const override;
+ virtual bool GetUseReducedAnimation() const override;
+
+ constexpr vcl::WindowState state() const { return m_eState; }
+ void UpdateFrameState();
+ constexpr bool isFullScreen() const { return bool(m_eState & vcl::WindowState::FullScreen); }
+};
+
+void ImplSalGetWorkArea( HWND hWnd, RECT *pRect, const RECT *pParentRect );
+
+bool UseDarkMode();
+bool OSSupportsDarkMode();
+
+// get foreign key names
+namespace vcl_sal {
+ OUString getKeysReplacementName(
+ std::u16string_view pLang,
+ LONG nSymbol );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/salgdi.h b/vcl/inc/win/salgdi.h
new file mode 100644
index 0000000000..80fafdeba5
--- /dev/null
+++ b/vcl/inc/win/salgdi.h
@@ -0,0 +1,382 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <sallayout.hxx>
+#include <salgeom.hxx>
+#include <salgdi.hxx>
+#include <font/LogicalFontInstance.hxx>
+#include <fontattributes.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <impfont.hxx>
+#include <vcl/fontcapabilities.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <systools/win32/comtools.hxx>
+
+#include <memory>
+#include <unordered_set>
+
+#ifndef INCLUDED_PRE_POST_WIN_H
+#define INCLUDED_PRE_POST_WIN_H
+# include <prewin.h>
+# include <postwin.h>
+#endif
+
+#include <hb-ot.h>
+#include <dwrite.h>
+
+namespace vcl::font
+{
+class PhysicalFontCollection;
+class FontSelectPattern;
+}
+class WinFontInstance;
+class ImplFontAttrCache;
+class SalGraphicsImpl;
+class WinSalGraphicsImplBase;
+class FontMetricData;
+
+#define RGB_TO_PALRGB(nRGB) ((nRGB)|0x02000000)
+#define PALRGB_TO_RGB(nPalRGB) ((nPalRGB)&0x00ffffff)
+
+// win32 specific physically available font face
+class WinFontFace final : public vcl::font::PhysicalFontFace
+{
+public:
+ explicit WinFontFace(const ENUMLOGFONTEXW&, const NEWTEXTMETRICW&);
+ ~WinFontFace() override;
+
+ rtl::Reference<LogicalFontInstance> CreateFontInstance( const vcl::font::FontSelectPattern& ) const override;
+ sal_IntPtr GetFontId() const override;
+ void SetFontId( sal_IntPtr nId ) { mnId = nId; }
+
+ BYTE GetCharSet() const { return meWinCharSet; }
+ BYTE GetPitchAndFamily() const { return mnPitchAndFamily; }
+
+ hb_blob_t* GetHbTable(hb_tag_t nTag) const override;
+
+ const std::vector<hb_variation_t>& GetVariations(const LogicalFontInstance&) const override;
+
+private:
+ sal_IntPtr mnId;
+
+ BYTE meWinCharSet;
+ BYTE mnPitchAndFamily;
+ LOGFONTW maLogFont;
+ mutable sal::systools::COMReference<IDWriteFontFace> mxDWFontFace;
+};
+
+/** Class that creates (and destroys) a compatible Device Context.
+
+This is to be used for GDI drawing into a DIB that we later use for a different
+drawing method, such as a texture for OpenGL drawing or surface for Skia drawing.
+*/
+class CompatibleDC
+{
+protected:
+ /// The compatible DC that we create for our purposes.
+ HDC mhCompatibleDC;
+
+ /// DIBSection that we use for the GDI drawing, and later obtain.
+ HBITMAP mhBitmap;
+
+ /// Return the previous bitmap to undo the SelectObject.
+ HBITMAP mhOrigBitmap;
+
+ /// DIBSection data.
+ sal_uInt32 *mpData;
+
+ /// Mapping between the GDI position and OpenGL, to use for OpenGL drawing.
+ SalTwoRect maRects;
+
+ /// The SalGraphicsImpl where we will draw. If null, we ignore the drawing, it means it happened directly to the DC...
+ WinSalGraphicsImplBase *mpImpl;
+
+ // If 'disable' is true, this class is a simple wrapper for drawing directly. Subclasses should use true.
+ CompatibleDC(SalGraphics &rGraphics, int x, int y, int width, int height, bool disable=true);
+
+public:
+ static std::unique_ptr< CompatibleDC > create(SalGraphics &rGraphics, int x, int y, int width, int height);
+
+ virtual ~CompatibleDC();
+
+ HDC getCompatibleHDC() { return mhCompatibleDC; }
+
+ SalTwoRect getTwoRect() const { return maRects; }
+
+ tools::Long getBitmapWidth() const { return maRects.mnSrcWidth; }
+ tools::Long getBitmapHeight() const { return maRects.mnSrcHeight; }
+
+ /// Reset the DC with the defined color.
+ void fill(sal_uInt32 color);
+};
+
+/**
+ * WinSalGraphics never owns the HDC it uses to draw, because the HDC can have
+ * various origins with different ways to correctly free it. And WinSalGraphics
+ * stores all default values (mhDef*) of the HDC, which must be restored when
+ * the HDC changes (setHDC) or the SalGraphics is destructed. So think of the
+ * HDC in terms of Rust's Borrowing semantics.
+ */
+class WinSalGraphics : public SalGraphics
+{
+ friend class WinSalGraphicsImpl;
+ friend class ScopedFont;
+
+protected:
+ std::unique_ptr<SalGraphicsImpl> mpImpl;
+ WinSalGraphicsImplBase * mWinSalGraphicsImplBase;
+
+private:
+ HDC mhLocalDC; // HDC
+ bool mbPrinter : 1; // is Printer
+ bool mbVirDev : 1; // is VirDev
+ bool mbWindow : 1; // is Window
+ bool mbScreen : 1; // is Screen compatible
+ HWND mhWnd; // Window-Handle, when Window-Graphics
+
+ rtl::Reference<WinFontInstance>
+ mpWinFontEntry[ MAX_FALLBACK ]; // pointer to the most recent font instance
+ HRGN mhRegion; // vcl::Region Handle
+ HPEN mhDefPen; // DefaultPen
+ HBRUSH mhDefBrush; // DefaultBrush
+ HFONT mhDefFont; // DefaultFont
+ HPALETTE mhDefPal; // DefaultPalette
+ COLORREF mnTextColor; // TextColor
+ RGNDATA* mpClipRgnData; // ClipRegion-Data
+ RGNDATA* mpStdClipRgnData; // Cache Standard-ClipRegion-Data
+ int mnPenWidth; // line width
+
+ inline static sal::systools::COMReference<IDWriteFactory> mxDWriteFactory;
+ inline static sal::systools::COMReference<IDWriteGdiInterop> mxDWriteGdiInterop;
+ inline static bool bDWriteDone = false;
+
+ // just call both from setHDC!
+ void InitGraphics();
+ void DeInitGraphics();
+
+public:
+ // Return HFONT, and whether the font is for vertical writing ( prefixed with '@' )
+ // and tmDescent value for adjusting offset in vertical writing mode.
+ std::tuple<HFONT,bool,sal_Int32> ImplDoSetFont(HDC hDC, vcl::font::FontSelectPattern const & i_rFont, const vcl::font::PhysicalFontFace * i_pFontFace, HFONT& o_rOldFont);
+
+ HDC getHDC() const { return mhLocalDC; }
+ // NOTE: this doesn't transfer ownership! See class comment.
+ void setHDC(HDC aNew);
+
+ HPALETTE getDefPal() const;
+ // returns the result from RealizePalette, otherwise 0 on success or GDI_ERROR
+ UINT setPalette(HPALETTE, BOOL bForceBkgd = TRUE);
+
+ HRGN getRegion() const;
+
+
+ enum Type
+ {
+ PRINTER,
+ VIRTUAL_DEVICE,
+ WINDOW,
+ SCREEN
+ };
+
+ static void getDWriteFactory(IDWriteFactory** pFactory, IDWriteGdiInterop** pInterop = nullptr);
+
+public:
+
+ HWND gethWnd();
+
+
+public:
+ explicit WinSalGraphics(WinSalGraphics::Type eType, bool bScreen, HWND hWnd,
+ SalGeometryProvider *pProvider);
+ virtual ~WinSalGraphics() override;
+
+ SalGraphicsImpl* GetImpl() const override;
+ WinSalGraphicsImplBase * getWinSalGraphicsImplBase() const { return mWinSalGraphicsImplBase; }
+ bool isPrinter() const;
+ bool isVirtualDevice() const;
+ bool isWindow() const;
+ bool isScreen() const;
+
+ void setHWND(HWND hWnd);
+ void Flush();
+
+protected:
+ virtual void setClipRegion( const vcl::Region& ) override;
+ // draw --> LineColor and FillColor and RasterOp and ClipRegion
+ virtual void drawPixel( tools::Long nX, tools::Long nY ) override;
+ virtual void drawPixel( tools::Long nX, tools::Long nY, Color nColor ) override;
+ virtual void drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 ) override;
+ virtual void drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry ) override;
+ virtual void drawPolygon( sal_uInt32 nPoints, const Point* pPtAry ) override;
+ virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, const Point** pPtAry ) override;
+ virtual void drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon&,
+ double fTransparency) override;
+ virtual bool drawPolyLine(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon&,
+ double fTransparency,
+ double fLineWidth,
+ const std::vector< double >* pStroke, // MM01
+ basegfx::B2DLineJoin,
+ css::drawing::LineCap,
+ double fMiterMinimumAngle,
+ bool bPixelSnapHairline) override;
+ virtual bool drawPolyLineBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry ) override;
+ virtual bool drawPolygonBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry ) override;
+ virtual bool drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints, const Point* const* pPtAry, const PolyFlags* const* pFlgAry ) override;
+ virtual bool drawGradient( const tools::PolyPolygon&, const Gradient& ) override;
+ virtual bool implDrawGradient(basegfx::B2DPolyPolygon const & rPolyPolygon, SalGradient const & rGradient) override;
+
+ // CopyArea --> No RasterOp, but ClipRegion
+ virtual void copyArea( tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX, tools::Long nSrcY, tools::Long nSrcWidth,
+ tools::Long nSrcHeight, bool bWindowInvalidate ) override;
+
+ // CopyBits and DrawBitmap --> RasterOp and ClipRegion
+ // CopyBits() --> pSrcGraphics == NULL, then CopyBits on same Graphics
+ virtual void copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) override;
+ virtual void drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap ) override;
+ virtual void drawBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransparentBitmap ) override;
+ virtual void drawMask( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ Color nMaskColor ) override;
+
+ virtual std::shared_ptr<SalBitmap> getBitmap( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+ virtual Color getPixel( tools::Long nX, tools::Long nY ) override;
+
+ // invert --> ClipRegion (only Windows or VirDevs)
+ virtual void invert( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, SalInvert nFlags) override;
+ virtual void invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags ) override;
+
+ virtual bool drawEPS( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, void* pPtr, sal_uInt32 nSize ) override;
+
+ // native widget rendering methods that require mirroring
+protected:
+ virtual bool isNativeControlSupported( ControlType nType, ControlPart nPart ) override;
+ virtual bool hitTestNativeControl( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion,
+ const Point& aPos, bool& rIsInside ) override;
+ virtual bool drawNativeControl( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion,
+ ControlState nState, const ImplControlValue& aValue,
+ const OUString& aCaption, const Color& rBackgroundColor ) override;
+ virtual bool getNativeControlRegion( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion, ControlState nState,
+ const ImplControlValue& aValue, const OUString& aCaption,
+ tools::Rectangle &rNativeBoundingRegion, tools::Rectangle &rNativeContentRegion ) override;
+
+public:
+ virtual bool blendBitmap( const SalTwoRect&,
+ const SalBitmap& rBitmap ) override;
+
+ virtual bool blendAlphaBitmap( const SalTwoRect&,
+ const SalBitmap& rSrcBitmap,
+ const SalBitmap& rMaskBitmap,
+ const SalBitmap& rAlphaBitmap ) override;
+
+ virtual bool drawAlphaBitmap( const SalTwoRect&,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap ) override;
+ virtual bool drawTransformedBitmap(
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap,
+ double fAlpha) override;
+
+ virtual bool hasFastDrawTransformedBitmap() const override;
+
+ virtual bool drawAlphaRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt8 nTransparency ) override;
+
+private:
+ // local helpers
+
+ void DrawTextLayout(const GenericSalLayout&, HDC, bool bUseDWrite, bool bRenderingModeNatural);
+
+public:
+ // public SalGraphics methods, the interface to the independent vcl part
+
+ // get device resolution
+ virtual void GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY ) override;
+ // get the depth of the device
+ virtual sal_uInt16 GetBitCount() const override;
+ // get the width of the device
+ virtual tools::Long GetGraphicsWidth() const override;
+
+ // set the clip region to empty
+ virtual void ResetClipRegion() override;
+
+ // set the line color to transparent (= don't draw lines)
+ virtual void SetLineColor() override;
+ // set the line color to a specific color
+ virtual void SetLineColor( Color nColor ) override;
+ // set the fill color to transparent (= don't fill)
+ virtual void SetFillColor() override;
+ // set the fill color to a specific color, shapes will be
+ // filled accordingly
+ virtual void SetFillColor( Color nColor ) override;
+ // enable/disable XOR drawing
+ virtual void SetXORMode( bool bSet, bool ) override;
+ // set line color for raster operations
+ virtual void SetROPLineColor( SalROPColor nROPColor ) override;
+ // set fill color for raster operations
+ virtual void SetROPFillColor( SalROPColor nROPColor ) override;
+ // set the text color to a specific color
+ virtual void SetTextColor( Color nColor ) override;
+ // set the font
+ virtual void SetFont( LogicalFontInstance*, int nFallbackLevel ) override;
+ // get the current font's metrics
+ virtual void GetFontMetric( FontMetricDataRef&, int nFallbackLevel ) override;
+ // get the repertoire of the current font
+ virtual FontCharMapRef GetFontCharMap() const override;
+ // get the layout capabilities of the current font
+ virtual bool GetFontCapabilities(vcl::FontCapabilities &rGetFontCapabilities) const override;
+ // graphics must fill supplied font list
+ virtual void GetDevFontList( vcl::font::PhysicalFontCollection* ) override;
+ // graphics must drop any cached font info
+ virtual void ClearDevFontCache() override;
+ virtual bool AddTempDevFont( vcl::font::PhysicalFontCollection*, const OUString& rFileURL, const OUString& rFontName ) override;
+
+ virtual std::unique_ptr<GenericSalLayout>
+ GetTextLayout(int nFallbackLevel) override;
+ virtual void DrawTextLayout( const GenericSalLayout& ) override;
+
+ virtual bool supportsOperation( OutDevSupportType ) const override;
+
+ virtual SystemGraphicsData GetGraphicsData() const override;
+
+ /// Update settings based on the platform values
+ static void updateSettingsNative( AllSettings& rSettings );
+};
+
+// Init/Deinit Graphics
+void ImplUpdateSysColorEntries();
+int ImplIsSysColorEntry( Color nColor );
+void ImplGetLogFontFromFontSelect( const vcl::font::FontSelectPattern&,
+ const vcl::font::PhysicalFontFace*, LOGFONTW& );
+
+#define MAX_64KSALPOINTS ((((sal_uInt16)0xFFFF)-8)/sizeof(POINTS))
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/salids.hrc b/vcl/inc/win/salids.hrc
new file mode 100644
index 0000000000..7212ee9022
--- /dev/null
+++ b/vcl/inc/win/salids.hrc
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_WIN_SALIDS_HRC
+#define INCLUDED_VCL_INC_WIN_SALIDS_HRC
+
+// Cursor
+#define SAL_RESID_POINTER_NULL 10000
+#define SAL_RESID_POINTER_MAGNIFY 10015
+#define SAL_RESID_POINTER_FILL 10016
+#define SAL_RESID_POINTER_ROTATE 10017
+#define SAL_RESID_POINTER_HSHEAR 10018
+#define SAL_RESID_POINTER_VSHEAR 10019
+#define SAL_RESID_POINTER_MIRROR 10020
+#define SAL_RESID_POINTER_CROOK 10021
+#define SAL_RESID_POINTER_CROP 10022
+#define SAL_RESID_POINTER_MOVEPOINT 10023
+#define SAL_RESID_POINTER_MOVEBEZIERWEIGHT 10024
+#define SAL_RESID_POINTER_MOVEDATA 10025
+#define SAL_RESID_POINTER_COPYDATA 10026
+#define SAL_RESID_POINTER_LINKDATA 10027
+#define SAL_RESID_POINTER_MOVEDATALINK 10028
+#define SAL_RESID_POINTER_COPYDATALINK 10029
+#define SAL_RESID_POINTER_MOVEFILE 10030
+#define SAL_RESID_POINTER_COPYFILE 10031
+#define SAL_RESID_POINTER_LINKFILE 10032
+#define SAL_RESID_POINTER_MOVEFILELINK 10033
+#define SAL_RESID_POINTER_COPYFILELINK 10034
+#define SAL_RESID_POINTER_MOVEFILES 10035
+#define SAL_RESID_POINTER_COPYFILES 10036
+#define SAL_RESID_POINTER_DRAW_LINE 10038
+#define SAL_RESID_POINTER_DRAW_RECT 10039
+#define SAL_RESID_POINTER_DRAW_POLYGON 10040
+#define SAL_RESID_POINTER_DRAW_BEZIER 10041
+#define SAL_RESID_POINTER_DRAW_ARC 10042
+#define SAL_RESID_POINTER_DRAW_PIE 10043
+#define SAL_RESID_POINTER_DRAW_CIRCLECUT 10044
+#define SAL_RESID_POINTER_DRAW_ELLIPSE 10045
+#define SAL_RESID_POINTER_DRAW_FREEHAND 10046
+#define SAL_RESID_POINTER_DRAW_CONNECT 10047
+#define SAL_RESID_POINTER_DRAW_TEXT 10048
+#define SAL_RESID_POINTER_DRAW_CAPTION 10049
+#define SAL_RESID_POINTER_CHART 10050
+#define SAL_RESID_POINTER_DETECTIVE 10051
+#define SAL_RESID_POINTER_PIVOT_COL 10052
+#define SAL_RESID_POINTER_PIVOT_ROW 10053
+#define SAL_RESID_POINTER_PIVOT_FIELD 10054
+#define SAL_RESID_POINTER_CHAIN 10055
+#define SAL_RESID_POINTER_CHAIN_NOTALLOWED 10056
+#define SAL_RESID_POINTER_AUTOSCROLL_N 10059
+#define SAL_RESID_POINTER_AUTOSCROLL_S 10060
+#define SAL_RESID_POINTER_AUTOSCROLL_W 10061
+#define SAL_RESID_POINTER_AUTOSCROLL_E 10062
+#define SAL_RESID_POINTER_AUTOSCROLL_NW 10063
+#define SAL_RESID_POINTER_AUTOSCROLL_NE 10064
+#define SAL_RESID_POINTER_AUTOSCROLL_SW 10065
+#define SAL_RESID_POINTER_AUTOSCROLL_SE 10066
+#define SAL_RESID_POINTER_AUTOSCROLL_NS 10067
+#define SAL_RESID_POINTER_AUTOSCROLL_WE 10068
+#define SAL_RESID_POINTER_AUTOSCROLL_NSWE 10069
+#define SAL_RESID_POINTER_TEXT_VERTICAL 10071
+#define SAL_RESID_POINTER_PIVOT_DELETE 10072
+#define SAL_RESID_POINTER_TAB_SELECT_S 10073
+#define SAL_RESID_POINTER_TAB_SELECT_E 10074
+#define SAL_RESID_POINTER_TAB_SELECT_SE 10075
+#define SAL_RESID_POINTER_TAB_SELECT_W 10076
+#define SAL_RESID_POINTER_TAB_SELECT_SW 10077
+#define SAL_RESID_POINTER_HIDEWHITESPACE 10079
+#define SAL_RESID_POINTER_SHOWWHITESPACE 10080
+#define SAL_RESID_POINTER_FATCROSS 10081
+
+#define SAL_RESID_BITMAP_50 11000
+
+#define SAL_RESID_ICON_DEFAULT 1
+
+#endif // INCLUDED_VCL_INC_WIN_SALIDS_HRC
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/salinst.h b/vcl/inc/win/salinst.h
new file mode 100644
index 0000000000..9c6ca82d38
--- /dev/null
+++ b/vcl/inc/win/salinst.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <osl/conditn.hxx>
+
+#include <salinst.hxx>
+
+class SalYieldMutex;
+
+class WinSalInstance : public SalInstance
+{
+public:
+ /// Instance Handle
+ HINSTANCE mhInst;
+ /// invisible Window so non-main threads can SendMessage() the main thread
+ HWND mhComWnd;
+
+ osl::Condition maWaitingYieldCond;
+ unsigned m_nNoYieldLock;
+
+public:
+ WinSalInstance();
+ virtual ~WinSalInstance() override;
+
+ virtual void AfterAppInit() override;
+ virtual SalFrame* CreateChildFrame( SystemParentData* pParent, SalFrameStyleFlags nStyle ) override;
+ virtual SalFrame* CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle ) override;
+ virtual void DestroyFrame( SalFrame* pFrame ) override;
+ virtual SalObject* CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow ) override;
+ virtual void DestroyObject( SalObject* pObject ) override;
+ virtual std::unique_ptr<SalVirtualDevice>
+ CreateVirtualDevice( SalGraphics& rGraphics,
+ tools::Long &nDX, tools::Long &nDY,
+ DeviceFormat eFormat, const SystemGraphicsData *pData = nullptr ) override;
+ virtual SalInfoPrinter* CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pSetupData ) override;
+ virtual void DestroyInfoPrinter( SalInfoPrinter* pPrinter ) override;
+ virtual std::unique_ptr<SalPrinter> CreatePrinter( SalInfoPrinter* pInfoPrinter ) override;
+ virtual void GetPrinterQueueInfo( ImplPrnQueueList* pList ) override;
+ virtual void GetPrinterQueueState( SalPrinterQueueInfo* pInfo ) override;
+ virtual OUString GetDefaultPrinter() override;
+ virtual SalTimer* CreateSalTimer() override;
+ virtual SalSystem* CreateSalSystem() override;
+ virtual std::shared_ptr<SalBitmap> CreateSalBitmap() override;
+ virtual bool IsMainThread() const override;
+
+ virtual bool DoYield(bool bWait, bool bHandleAllCurrentEvents) override;
+ virtual bool AnyInput( VclInputFlags nType ) override;
+ virtual std::unique_ptr<SalMenu> CreateMenu( bool bMenuBar, Menu* ) override;
+ virtual std::unique_ptr<SalMenuItem> CreateMenuItem( const SalItemParams & rItemData ) override;
+ virtual OpenGLContext* CreateOpenGLContext() override;
+ virtual OUString GetConnectionIdentifier() override;
+ virtual void AddToRecentDocumentList(const OUString& rFileUrl, const OUString& rMimeType, const OUString& rDocumentService) override;
+
+ virtual OUString getOSVersion() override;
+ virtual void BeforeAbort(const OUString&, bool) override;
+
+ static int WorkaroundExceptionHandlingInUSER32Lib(int nExcept, LPEXCEPTION_POINTERS pExceptionInfo);
+
+ virtual css::uno::Reference<css::uno::XInterface> ImplCreateDragSource(const SystemEnvData*) override;
+ virtual css::uno::Reference<css::uno::XInterface> ImplCreateDropTarget(const SystemEnvData*) override;
+};
+
+SalFrame* ImplSalCreateFrame( WinSalInstance* pInst, HWND hWndParent, SalFrameStyleFlags nSalFrameStyle );
+SalObject* ImplSalCreateObject( WinSalInstance* pInst, WinSalFrame* pParent );
+HWND ImplSalReCreateHWND( HWND hWndParent, HWND oldhWnd, bool bAsChild );
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/salmenu.h b/vcl/inc/win/salmenu.h
new file mode 100644
index 0000000000..7058d9c82b
--- /dev/null
+++ b/vcl/inc/win/salmenu.h
@@ -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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_WIN_SALMENU_H
+#define INCLUDED_VCL_INC_WIN_SALMENU_H
+
+#include <vcl/bitmap.hxx>
+#include <salmenu.hxx>
+
+class WinSalMenu : public SalMenu
+{
+public:
+ WinSalMenu();
+ virtual ~WinSalMenu() override;
+ virtual bool VisibleMenuBar() override; // must return TRUE to actually DISPLAY native menu bars
+ // otherwise only menu messages are processed (eg, OLE on Windows)
+
+ virtual void InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos ) override;
+ virtual void RemoveItem( unsigned nPos ) override;
+ virtual void SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned nPos ) override;
+ virtual void SetFrame( const SalFrame* pFrame ) override;
+ virtual void CheckItem( unsigned nPos, bool bCheck ) override;
+ virtual void EnableItem( unsigned nPos, bool bEnable ) override;
+ virtual void SetItemText( unsigned nPos, SalMenuItem* pSalMenuItem, const OUString& rText ) override;
+ virtual void SetItemImage( unsigned nPos, SalMenuItem* pSalMenuItem, const Image& rImage ) override;
+ virtual void SetAccelerator( unsigned nPos, SalMenuItem* pSalMenuItem, const vcl::KeyCode& rKeyCode, const OUString& rKeyName ) override;
+ virtual void GetSystemMenuData( SystemMenuData* pData ) override;
+
+ HMENU mhMenu; // the menu handle
+ bool mbMenuBar; // true for menu bars
+ HWND mhWnd; // the window handle where the menubar is attached, may be NULL
+ WinSalMenu *mpParentMenu; // the parent menu
+};
+
+class WinSalMenuItem : public SalMenuItem
+{
+public:
+ WinSalMenuItem();
+ virtual ~WinSalMenuItem() override;
+
+ MENUITEMINFOW mInfo;
+ void* mpMenu; // pointer to corresponding VCL menu
+ OUString mText; // the item text
+ OUString mAccelText; // the accelerator string
+ Bitmap maBitmap; // item image
+ int mnId; // item id
+ WinSalMenu* mpSalMenu; // the menu where this item is inserted
+};
+
+#endif // INCLUDED_VCL_INC_WIN_SALMENU_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/salobj.h b/vcl/inc/win/salobj.h
new file mode 100644
index 0000000000..37bcf03178
--- /dev/null
+++ b/vcl/inc/win/salobj.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <salobj.hxx>
+
+
+class WinSalObject : public SalObject
+{
+public:
+ HWND mhWnd; // Window handle
+ HWND mhWndChild; // Child Window handle
+ HWND mhLastFocusWnd; // Child-Window, which had the last focus
+ SystemEnvData maSysData; // SystemEnvData
+ RGNDATA* mpClipRgnData; // ClipRegion-Data
+ RGNDATA* mpStdClipRgnData; // Cache Standard-ClipRegion-Data
+ RECT* mpNextClipRect; // next ClipRegion-Rect
+ bool mbFirstClipRect; // Flag for first cliprect to insert
+ WinSalObject* mpNextObject; // pointer to next object
+
+ WinSalObject();
+ virtual ~WinSalObject() override;
+
+ virtual void ResetClipRegion() override;
+ virtual void BeginSetClipRegion( sal_uInt32 nRects ) override;
+ virtual void UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight) override;
+ virtual void EndSetClipRegion() override;
+ virtual void SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+ virtual void Show( bool bVisible ) override;
+ virtual void Enable( bool bEnable ) override;
+ virtual void GrabFocus() override;
+ virtual const SystemEnvData* GetSystemData() const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/salprn.h b/vcl/inc/win/salprn.h
new file mode 100644
index 0000000000..e1bbb665e2
--- /dev/null
+++ b/vcl/inc/win/salprn.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <salprn.hxx>
+
+
+// WNT3
+#define SAL_DRIVERDATA_SYSSIGN ((sal_uIntPtr)0x574E5433)
+
+#pragma pack( 1 )
+
+struct SalDriverData
+{
+ sal_uIntPtr mnSysSignature;
+ sal_uInt16 mnDriverOffset;
+ BYTE maDriverData[1];
+};
+
+#pragma pack()
+
+
+class WinSalGraphics;
+
+class WinSalInfoPrinter final : public SalInfoPrinter
+{
+public:
+ OUString maDriverName; // printer driver name
+ OUString maDeviceName; // printer device name
+ OUString maPortName; // printer port name
+
+private:
+ HDC m_hDC; ///< printer hdc
+ WinSalGraphics* m_pGraphics; ///< current Printer graphics
+ bool m_bGraphics; ///< is Graphics used
+
+public:
+ WinSalInfoPrinter();
+ virtual ~WinSalInfoPrinter() override;
+
+ void setHDC(HDC);
+
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics( SalGraphics* pGraphics ) override;
+ virtual bool Setup( weld::Window* pFrame, ImplJobSetup* pSetupData ) override;
+ virtual bool SetPrinterData( ImplJobSetup* pSetupData ) override;
+ virtual bool SetData( JobSetFlags nFlags, ImplJobSetup* pSetupData ) override;
+ virtual void GetPageInfo( const ImplJobSetup* pSetupData,
+ tools::Long& rOutWidth, tools::Long& rOutHeight,
+ Point& rPageOffset,
+ Size& rPaperSize ) override;
+ virtual sal_uInt32 GetCapabilities( const ImplJobSetup* pSetupData, PrinterCapType nType ) override;
+ virtual sal_uInt16 GetPaperBinCount( const ImplJobSetup* pSetupData ) override;
+ virtual OUString GetPaperBinName( const ImplJobSetup* pSetupData, sal_uInt16 nPaperBin ) override;
+ virtual void InitPaperFormats( const ImplJobSetup* pSetupData ) override;
+ virtual int GetLandscapeAngle( const ImplJobSetup* pSetupData ) override;
+};
+
+
+class WinSalPrinter : public SalPrinter
+{
+public:
+ std::unique_ptr<WinSalGraphics> mxGraphics; // current Printer graphics
+ WinSalInfoPrinter* mpInfoPrinter; // pointer to the compatible InfoPrinter
+ WinSalPrinter* mpNextPrinter; // next printing printer
+ HDC mhDC; // printer hdc
+ SalPrinterError mnError; // error code
+ sal_uInt32 mnCopies; // copies
+ bool mbCollate; // collated copies
+ bool mbAbort; // Job Aborted
+
+ bool mbValid;
+
+protected:
+ void DoEndDoc(HDC hDC);
+
+public:
+ WinSalPrinter();
+ virtual ~WinSalPrinter() override;
+
+ using SalPrinter::StartJob;
+ virtual bool StartJob( const OUString* pFileName,
+ const OUString& rJobName,
+ const OUString& rAppName,
+ sal_uInt32 nCopies,
+ bool bCollate,
+ bool bDirect,
+ ImplJobSetup* pSetupData ) override;
+ virtual bool EndJob() override;
+ virtual SalGraphics* StartPage( ImplJobSetup* pSetupData, bool bNewJobData ) override;
+ virtual void EndPage() override;
+ virtual SalPrinterError GetErrorCode() override;
+
+ void markInvalid();
+ bool isValid() const { return mbValid && mhDC; }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/salsys.h b/vcl/inc/win/salsys.h
new file mode 100644
index 0000000000..ae94bb9e1a
--- /dev/null
+++ b/vcl/inc/win/salsys.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 .
+ */
+
+#pragma once
+
+#include <salsys.hxx>
+
+#include <vector>
+#include <map>
+
+class WinSalSystem : public SalSystem
+{
+public:
+ struct DisplayMonitor
+ {
+ OUString m_aName;
+ AbsoluteScreenPixelRectangle m_aArea;
+
+ DisplayMonitor() {}
+ DisplayMonitor( const OUString& rName,
+ const AbsoluteScreenPixelRectangle& rArea )
+ : m_aName( rName ),
+ m_aArea( rArea )
+ {
+ }
+ };
+private:
+ std::vector<DisplayMonitor> m_aMonitors;
+ std::map<OUString, unsigned int> m_aDeviceNameToMonitor;
+ unsigned int m_nPrimary;
+public:
+ WinSalSystem() : m_nPrimary( 0 ) {}
+ virtual ~WinSalSystem() override;
+
+ virtual unsigned int GetDisplayScreenCount() override;
+ virtual unsigned int GetDisplayBuiltInScreen() override;
+ virtual AbsoluteScreenPixelRectangle GetDisplayScreenPosSizePixel( unsigned int nScreen ) override;
+ virtual int ShowNativeMessageBox( const OUString& rTitle,
+ const OUString& rMessage) override;
+ bool initMonitors();
+ // discards monitorinfo; used by WM_DISPLAYCHANGED handler
+ void clearMonitors();
+ const std::vector<DisplayMonitor>& getMonitors()
+ { initMonitors(); return m_aMonitors;}
+
+ bool handleMonitorCallback( sal_IntPtr /*HMONITOR*/,
+ sal_IntPtr /*HDC*/,
+ sal_IntPtr /*LPRECT*/ );
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/saltimer.h b/vcl/inc/win/saltimer.h
new file mode 100644
index 0000000000..827c08ab83
--- /dev/null
+++ b/vcl/inc/win/saltimer.h
@@ -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 <saltimer.hxx>
+
+class WinSalTimer final : public SalTimer, protected VersionedEvent
+{
+ // for access to Impl* functions
+ friend LRESULT CALLBACK SalComWndProc( HWND, UINT nMsg, WPARAM wParam, LPARAM lParam, bool& rDef );
+ // for access to GetNextVersionedEvent
+ friend void CALLBACK SalTimerProc( PVOID data, BOOLEAN );
+ // for access to ImplHandleElapsedTimer
+ friend bool ImplSalYield( bool bWait, bool bHandleAllCurrentEvents );
+
+ /**
+ * Identifier for our SetTimer based timer
+ */
+ static constexpr UINT_PTR m_aWmTimerId = 0xdeadbeef;
+
+ HANDLE m_nTimerId; ///< Windows timer id
+ bool m_bDirectTimeout; ///< timeout can be processed directly
+ bool m_bForceRealTimer; ///< enforce using a real timer for 0ms
+ bool m_bSetTimerRunning; ///< true, if a SetTimer is running
+
+ void ImplStart( sal_uInt64 nMS );
+ void ImplStop();
+ void ImplHandleTimerEvent( WPARAM aWPARAM );
+ void ImplHandleElapsedTimer();
+ void ImplHandle_WM_TIMER( WPARAM aWPARAM );
+
+public:
+ WinSalTimer();
+ virtual ~WinSalTimer() override;
+
+ virtual void Start(sal_uInt64 nMS) override;
+ virtual void Stop() override;
+
+ inline bool IsDirectTimeout() const;
+ inline bool HasTimerElapsed() const;
+
+ /**
+ * Enforces the usage of a real timer instead of the message queue
+ *
+ * Needed for Window resize processing, as this starts a modal event loop.
+ */
+ void SetForceRealTimer( bool bVal );
+ inline bool GetForceRealTimer() const;
+};
+
+inline bool WinSalTimer::IsDirectTimeout() const
+{
+ return m_bDirectTimeout;
+}
+
+inline bool WinSalTimer::HasTimerElapsed() const
+{
+ return m_bDirectTimeout || ExistsValidEvent();
+}
+
+inline bool WinSalTimer::GetForceRealTimer() const
+{
+ return m_bForceRealTimer;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/salvd.h b/vcl/inc/win/salvd.h
new file mode 100644
index 0000000000..66833f99f1
--- /dev/null
+++ b/vcl/inc/win/salvd.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+#include <win/scoped_gdi.hxx>
+
+#include <memory>
+
+#include <salvd.hxx>
+
+class WinSalGraphics;
+
+
+class WinSalVirtualDevice : public SalVirtualDevice
+{
+private:
+ HDC mhLocalDC; // HDC or 0 for Cache Device
+ ScopedHBITMAP mhBmp; // Memory Bitmap
+ HBITMAP mhDefBmp; // Default Bitmap
+ std::unique_ptr<WinSalGraphics> mpGraphics; // current VirDev graphics
+ WinSalVirtualDevice* mpNext; // next VirDev
+ sal_uInt16 mnBitCount; // BitCount (0 or 1)
+ bool mbGraphics; // is Graphics used
+ bool mbForeignDC; // uses a foreign DC instead of a bitmap
+ tools::Long mnWidth;
+ tools::Long mnHeight;
+
+public:
+ HDC getHDC() const { return mhLocalDC; }
+ WinSalGraphics* getGraphics() const { return mpGraphics.get(); }
+ void setGraphics(WinSalGraphics* pVirGraphics) { mpGraphics.reset(pVirGraphics); }
+ WinSalVirtualDevice* getNext() const { return mpNext; }
+
+ WinSalVirtualDevice(HDC hDC = nullptr, HBITMAP hBMP = nullptr, sal_uInt16 nBitCount = 0, bool bForeignDC = false, tools::Long nWidth = 0, tools::Long nHeight = 0);
+ virtual ~WinSalVirtualDevice() override;
+
+ virtual SalGraphics* AcquireGraphics() override;
+ virtual void ReleaseGraphics( SalGraphics* pGraphics ) override;
+ virtual bool SetSize( tools::Long nNewDX, tools::Long nNewDY ) override;
+
+ static HBITMAP ImplCreateVirDevBitmap(HDC hDC, tools::Long nDX, tools::Long nDY, sal_uInt16 nBitCount, void **ppDummy);
+
+ // SalGeometryProvider
+ virtual tools::Long GetWidth() const override { return mnWidth; }
+ virtual tools::Long GetHeight() const override { return mnHeight; }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/scoped_gdi.hxx b/vcl/inc/win/scoped_gdi.hxx
new file mode 100644
index 0000000000..86846e9860
--- /dev/null
+++ b/vcl/inc/win/scoped_gdi.hxx
@@ -0,0 +1,72 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <win/svsys.h>
+#include <win/wincomp.hxx>
+#include <win/saldata.hxx>
+
+#include <memory>
+
+template <typename H, auto DeleterFunc> struct GDIDeleter
+{
+ using pointer = H;
+ void operator()(H h) { DeleterFunc(h); }
+};
+
+template <typename H, auto DeleterFunc>
+using ScopedGDI = std::unique_ptr<H, GDIDeleter<H, DeleterFunc>>;
+
+using ScopedHBRUSH = ScopedGDI<HBRUSH, DeleteBrush>;
+using ScopedHRGN = ScopedGDI<HRGN, DeleteRegion>;
+using ScopedHDC = ScopedGDI<HDC, DeleteDC>;
+using ScopedHPEN = ScopedGDI<HPEN, DeletePen>;
+using ScopedHFONT = ScopedGDI<HFONT, DeleteFont>;
+using ScopedHBITMAP = ScopedGDI<HBITMAP, DeleteBitmap>;
+
+template <typename ScopedH, auto SelectorFunc> class ScopedSelectedGDI
+{
+public:
+ ScopedSelectedGDI(HDC hDC, typename ScopedH::pointer h)
+ : m_hDC(hDC)
+ , m_hSelectedH(h)
+ , m_hOrigH(SelectorFunc(hDC, h))
+ {
+ }
+
+ ~ScopedSelectedGDI() { SelectorFunc(m_hDC, m_hOrigH); }
+
+private:
+ HDC m_hDC;
+ ScopedH m_hSelectedH;
+ typename ScopedH::pointer m_hOrigH;
+};
+
+using ScopedSelectedHPEN = ScopedSelectedGDI<ScopedHPEN, SelectPen>;
+using ScopedSelectedHFONT = ScopedSelectedGDI<ScopedHFONT, SelectFont>;
+using ScopedSelectedHBRUSH = ScopedSelectedGDI<ScopedHBRUSH, SelectBrush>;
+
+template <sal_uLong ID> class ScopedCachedHDC
+{
+public:
+ explicit ScopedCachedHDC(HBITMAP hBitmap)
+ : m_hDC(ImplGetCachedDC(ID, hBitmap))
+ {
+ }
+
+ ~ScopedCachedHDC() { ImplReleaseCachedDC(ID); }
+
+ HDC get() const { return m_hDC; }
+
+private:
+ HDC m_hDC;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/win/svsys.h b/vcl/inc/win/svsys.h
new file mode 100644
index 0000000000..8a26bf6699
--- /dev/null
+++ b/vcl/inc/win/svsys.h
@@ -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 .
+ */
+
+#pragma once
+
+#ifdef _WIN32
+#ifndef INCLUDED_PRE_POST_WIN_H
+#define INCLUDED_PRE_POST_WIN_H
+#include <prewin.h>
+#include <postwin.h>
+#endif
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/wincomp.hxx b/vcl/inc/win/wincomp.hxx
new file mode 100644
index 0000000000..4383bc5b77
--- /dev/null
+++ b/vcl/inc/win/wincomp.hxx
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <string.h>
+
+
+// Adjustments for TypeChecking
+
+inline HPEN SelectPen( HDC hDC, HPEN hPen )
+{
+ return static_cast<HPEN>(SelectObject( hDC, static_cast<HGDIOBJ>(hPen) ));
+}
+
+inline void DeletePen( HPEN hPen )
+{
+ DeleteObject( static_cast<HGDIOBJ>(hPen) );
+}
+
+inline HPEN GetStockPen( int nObject )
+{
+ return static_cast<HPEN>(GetStockObject( nObject ));
+}
+
+inline HBRUSH SelectBrush( HDC hDC, HBRUSH hBrush )
+{
+ return static_cast<HBRUSH>(SelectObject( hDC, static_cast<HGDIOBJ>(hBrush) ));
+}
+
+inline void DeleteBrush( HBRUSH hBrush )
+{
+ DeleteObject( static_cast<HGDIOBJ>(hBrush) );
+}
+
+inline HBRUSH GetStockBrush( int nObject )
+{
+ return static_cast<HBRUSH>(GetStockObject( nObject ));
+}
+
+inline HFONT SelectFont( HDC hDC, HFONT hFont )
+{
+ return static_cast<HFONT>(SelectObject( hDC, static_cast<HGDIOBJ>(hFont) ));
+}
+
+inline void DeleteFont( HFONT hFont )
+{
+ DeleteObject( static_cast<HGDIOBJ>(hFont) );
+}
+
+inline HFONT GetStockFont( int nObject )
+{
+ return static_cast<HFONT>(GetStockObject( nObject ));
+}
+
+inline HBITMAP SelectBitmap( HDC hDC, HBITMAP hBitmap )
+{
+ return static_cast<HBITMAP>(SelectObject( hDC, static_cast<HGDIOBJ>(hBitmap) ));
+}
+
+inline void DeleteBitmap( HBITMAP hBitmap )
+{
+ DeleteObject( static_cast<HGDIOBJ>(hBitmap) );
+}
+
+inline void DeleteRegion( HRGN hRegion )
+{
+ DeleteObject( static_cast<HGDIOBJ>(hRegion) );
+}
+
+inline HPALETTE GetStockPalette( int nObject )
+{
+ return static_cast<HPALETTE>(GetStockObject( nObject ));
+}
+
+inline void DeletePalette( HPALETTE hPalette )
+{
+ DeleteObject( static_cast<HGDIOBJ>(hPalette) );
+}
+
+inline void SetWindowStyle( HWND hWnd, DWORD nStyle )
+{
+ SetWindowLongPtrW( hWnd, GWL_STYLE, nStyle );
+}
+
+inline DWORD GetWindowStyle( HWND hWnd )
+{
+ return GetWindowLongPtrW( hWnd, GWL_STYLE );
+}
+
+inline void SetWindowExStyle( HWND hWnd, DWORD nStyle )
+{
+ SetWindowLongPtrW( hWnd, GWL_EXSTYLE, nStyle );
+}
+
+inline DWORD GetWindowExStyle( HWND hWnd )
+{
+ return GetWindowLongPtrW( hWnd, GWL_EXSTYLE );
+}
+
+inline BOOL IsMinimized( HWND hWnd )
+{
+ return IsIconic( hWnd );
+}
+
+inline BOOL IsMaximized( HWND hWnd )
+{
+ return IsZoomed( hWnd );
+}
+
+inline void SetWindowFont( HWND hWnd, HFONT hFont, BOOL bRedraw )
+{
+ SendMessageW( hWnd, WM_SETFONT, reinterpret_cast<WPARAM>(hFont), MAKELPARAM(static_cast<UINT>(bRedraw),0) );
+}
+
+inline HFONT GetWindowFont( HWND hWnd )
+{
+ return reinterpret_cast<HFONT>(SendMessageW( hWnd, WM_GETFONT, 0, 0 ));
+}
+
+inline void SetClassCursor( HWND hWnd, HCURSOR hCursor )
+{
+ SetClassLongPtr( hWnd, GCLP_HCURSOR, reinterpret_cast<LONG_PTR>(hCursor) );
+}
+
+inline HCURSOR GetClassCursor( HWND hWnd )
+{
+ return reinterpret_cast<HCURSOR>(GetClassLongPtr( hWnd, GCLP_HCURSOR ));
+}
+
+inline void SetClassIcon( HWND hWnd, HICON hIcon )
+{
+ SetClassLongPtr( hWnd, GCLP_HICON, reinterpret_cast<LONG_PTR>(hIcon) );
+}
+
+inline HICON GetClassIcon( HWND hWnd )
+{
+ return reinterpret_cast<HICON>(GetClassLongPtr( hWnd, GCLP_HICON ));
+}
+
+inline HBRUSH SetClassBrush( HWND hWnd, HBRUSH hBrush )
+{
+ return reinterpret_cast<HBRUSH>(SetClassLongPtr( hWnd, GCLP_HBRBACKGROUND, reinterpret_cast<LONG_PTR>(hBrush) ));
+}
+
+inline HBRUSH GetClassBrush( HWND hWnd )
+{
+ return reinterpret_cast<HBRUSH>(GetClassLongPtr( hWnd, GCLP_HBRBACKGROUND ));
+}
+
+inline HINSTANCE GetWindowInstance( HWND hWnd )
+{
+ return reinterpret_cast<HINSTANCE>(GetWindowLongPtrW( hWnd, GWLP_HINSTANCE ));
+}
+
+
+#define MOUSEZ_CLASSNAME L"MouseZ" // wheel window class
+#define MOUSEZ_TITLE L"Magellan MSWHEEL" // wheel window title
+
+#define MSH_WHEELMODULE_CLASS (MOUSEZ_CLASSNAME)
+#define MSH_WHEELMODULE_TITLE (MOUSEZ_TITLE)
+
+#define MSH_SCROLL_LINES L"MSH_SCROLL_LINES_MSG"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/wingdiimpl.hxx b/vcl/inc/win/wingdiimpl.hxx
new file mode 100644
index 0000000000..94a1ec8f84
--- /dev/null
+++ b/vcl/inc/win/wingdiimpl.hxx
@@ -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/.
+ */
+
+#pragma once
+
+#include <win/salgdi.h>
+#include <ControlCacheKey.hxx>
+
+class ControlCacheKey;
+
+// Base class for some functionality that OpenGL/Skia/GDI backends must each implement.
+class WinSalGraphicsImplBase
+{
+public:
+ virtual ~WinSalGraphicsImplBase() {}
+
+ // If true is returned, the following functions are used for drawing controls.
+ virtual bool UseRenderNativeControl() const { return false; }
+ virtual bool TryRenderCachedNativeControl(const ControlCacheKey& /*rControlCacheKey*/,
+ int /*nX*/, int /*nY*/)
+ {
+ abort();
+ }
+ virtual bool RenderAndCacheNativeControl(CompatibleDC& /*rWhite*/, CompatibleDC& /*rBlack*/,
+ int /*nX*/, int /*nY*/,
+ ControlCacheKey& /*aControlCacheKey*/)
+ {
+ abort();
+ }
+
+ virtual void ClearDevFontCache() {}
+
+ virtual void Flush() {}
+
+ // Implementation for WinSalGraphics::DrawTextLayout().
+ // Returns true if handled, if false, then WinSalGraphics will handle it itself.
+ virtual bool DrawTextLayout(const GenericSalLayout&) { return false; }
+
+ virtual void ClearNativeControlCache() {}
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/win/winlayout.hxx b/vcl/inc/win/winlayout.hxx
new file mode 100644
index 0000000000..e1d66a0e1a
--- /dev/null
+++ b/vcl/inc/win/winlayout.hxx
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <rtl/ustring.hxx>
+
+#include <sallayout.hxx>
+#include <svsys.h>
+#include <win/salgdi.h>
+#include <o3tl/sorted_vector.hxx>
+
+// win32 specific logical font instance
+class WinFontInstance : public LogicalFontInstance
+{
+ friend rtl::Reference<LogicalFontInstance> WinFontFace::CreateFontInstance(const vcl::font::FontSelectPattern&) const;
+
+public:
+ ~WinFontInstance() override;
+
+ bool hasHScale() const;
+ float getHScale() const;
+
+ void SetGraphics(WinSalGraphics*);
+ WinSalGraphics* GetGraphics() const { return m_pGraphics; }
+
+ HFONT GetHFONT() const { return m_hFont; }
+ // Return true if the font is for vertical writing.
+ // I.e. the font name of the LOGFONT is prefixed with '@'.
+ bool IsCJKVerticalFont() const { return m_bIsCJKVerticalFont; }
+ sal_Int32 GetTmDescent() const { return m_nTmDescent; }
+
+ const WinFontFace * GetFontFace() const { return static_cast<const WinFontFace *>(LogicalFontInstance::GetFontFace()); }
+ WinFontFace * GetFontFace() { return static_cast<WinFontFace *>(LogicalFontInstance::GetFontFace()); }
+
+ bool GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const override;
+
+ IDWriteFontFace* GetDWFontFace() const;
+
+private:
+ explicit WinFontInstance(const WinFontFace&, const vcl::font::FontSelectPattern&);
+
+ virtual void ImplInitHbFont(hb_font_t*) override;
+
+ WinSalGraphics *m_pGraphics;
+ HFONT m_hFont;
+ bool m_bIsCJKVerticalFont;
+ sal_Int32 m_nTmDescent;
+ mutable sal::systools::COMReference<IDWriteFontFace> mxDWFontFace;
+};
+
+class TextOutRenderer
+{
+protected:
+ explicit TextOutRenderer() = default;
+ TextOutRenderer(const TextOutRenderer &) = delete;
+ TextOutRenderer & operator = (const TextOutRenderer &) = delete;
+
+public:
+ static TextOutRenderer & get(bool bUseDWrite, bool bRenderingModeNatural);
+
+ virtual ~TextOutRenderer() = default;
+
+ virtual bool operator ()(GenericSalLayout const &rLayout,
+ SalGraphics &rGraphics,
+ HDC hDC,
+ bool bRenderingModeNatural) = 0;
+};
+
+class ExTextOutRenderer : public TextOutRenderer
+{
+ ExTextOutRenderer(const ExTextOutRenderer &) = delete;
+ ExTextOutRenderer & operator = (const ExTextOutRenderer &) = delete;
+
+public:
+ explicit ExTextOutRenderer() = default;
+
+ bool operator ()(GenericSalLayout const &rLayout,
+ SalGraphics &rGraphics,
+ HDC hDC,
+ bool bRenderingModeNatural) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/window.h b/vcl/inc/window.h
new file mode 100644
index 0000000000..199fed977c
--- /dev/null
+++ b/vcl/inc/window.h
@@ -0,0 +1,440 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <tools/fract.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/inputctx.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+#include <vcl/settings.hxx>
+#include <o3tl/deleter.hxx>
+#include <o3tl/typed_flags_set.hxx>
+#include "windowdev.hxx"
+#include "salwtype.hxx"
+
+#include <optional>
+#include <list>
+#include <memory>
+#include <vector>
+#include <set>
+
+class FixedText;
+class VclSizeGroup;
+class VirtualDevice;
+namespace vcl::font { class PhysicalFontCollection; }
+class ImplFontCache;
+class VCLXWindow;
+namespace vcl { class WindowData; }
+class SalFrame;
+class SalObject;
+enum class MouseEventModifiers;
+enum class NotifyEventType;
+enum class ActivateModeFlags;
+enum class DialogControlFlags;
+enum class GetFocusFlags;
+enum class ParentClipMode;
+enum class SalEvent;
+
+namespace com::sun::star {
+ namespace accessibility {
+ class XAccessible;
+ class XAccessibleContext;
+ class XAccessibleEditableText;
+ }
+
+ namespace awt {
+ class XVclWindowPeer;
+ class XWindow;
+ }
+ namespace uno {
+ class Any;
+ class XInterface;
+ }
+ namespace datatransfer {
+ namespace clipboard {
+ class XClipboard;
+ }
+ namespace dnd {
+ class XDropTargetListener;
+ class XDragGestureRecognizer;
+ class XDragSource;
+ class XDropTarget;
+ }
+ }
+}
+
+VCL_DLLPUBLIC Size bestmaxFrameSizeForScreenSize(const Size &rScreenSize);
+
+//return true if this window and its stack of containers are all shown
+bool isVisibleInLayout(const vcl::Window *pWindow);
+
+//return true if this window and its stack of containers are all enabled
+bool isEnabledInLayout(const vcl::Window *pWindow);
+
+bool ImplWindowFrameProc( vcl::Window* pInst, SalEvent nEvent, const void* pEvent );
+
+MouseEventModifiers ImplGetMouseMoveMode( SalMouseEvent const * pEvent );
+
+MouseEventModifiers ImplGetMouseButtonMode( SalMouseEvent const * pEvent );
+
+struct ImplWinData
+{
+ std::optional<OUString>
+ mpExtOldText;
+ std::unique_ptr<ExtTextInputAttr[]>
+ mpExtOldAttrAry;
+ std::optional<tools::Rectangle>
+ mpCursorRect;
+ tools::Long mnCursorExtWidth;
+ bool mbVertical;
+ std::unique_ptr<tools::Rectangle[]>
+ mpCompositionCharRects;
+ tools::Long mnCompositionCharRects;
+ std::optional<tools::Rectangle>
+ mpFocusRect;
+ std::optional<tools::Rectangle>
+ mpTrackRect;
+ ShowTrackFlags mnTrackFlags;
+ sal_uInt16 mnIsTopWindow;
+ bool mbMouseOver; //< tracks mouse over for native widget paint effect
+ bool mbEnableNativeWidget; //< toggle native widget rendering
+ ::std::list< VclPtr<vcl::Window> >
+ maTopWindowChildren;
+
+ ImplWinData();
+ ~ImplWinData();
+};
+
+struct ImplFrameData
+{
+ Idle maPaintIdle; //< paint idle handler
+ Idle maResizeIdle; //< resize timer
+ InputContext maOldInputContext; //< last set Input Context
+ VclPtr<vcl::Window> mpNextFrame; //< next frame window
+ VclPtr<vcl::Window> mpFirstOverlap; //< first overlap vcl::Window
+ VclPtr<vcl::Window> mpFocusWin; //< focus window (is also set, when frame doesn't have the focus)
+ VclPtr<vcl::Window> mpMouseMoveWin; //< last window, where MouseMove() called
+ VclPtr<vcl::Window> mpMouseDownWin; //< last window, where MouseButtonDown() called
+ VclPtr<vcl::Window> mpTrackWin; //< window, that is in tracking mode
+ std::vector<VclPtr<vcl::Window> > maOwnerDrawList; //< List of system windows with owner draw decoration
+ std::shared_ptr<vcl::font::PhysicalFontCollection> mxFontCollection; //< Font-List for this frame
+ std::shared_ptr<ImplFontCache> mxFontCache; //< Font-Cache for this frame
+ sal_Int32 mnDPIX; //< Original Screen Resolution
+ sal_Int32 mnDPIY; //< Original Screen Resolution
+ ImplSVEvent * mnFocusId; //< FocusId for PostUserLink
+ ImplSVEvent * mnMouseMoveId; //< MoveId for PostUserLink
+ tools::Long mnLastMouseX; //< last x mouse position
+ tools::Long mnLastMouseY; //< last y mouse position
+ tools::Long mnBeforeLastMouseX; //< last but one x mouse position
+ tools::Long mnBeforeLastMouseY; //< last but one y mouse position
+ tools::Long mnFirstMouseX; //< first x mouse position by mousebuttondown
+ tools::Long mnFirstMouseY; //< first y mouse position by mousebuttondown
+ tools::Long mnLastMouseWinX; //< last x mouse position, rel. to pMouseMoveWin
+ tools::Long mnLastMouseWinY; //< last y mouse position, rel. to pMouseMoveWin
+ sal_uInt16 mnModalMode; //< frame based modal count (app based makes no sense anymore)
+ sal_uInt64 mnMouseDownTime; //< mouse button down time for double click
+ sal_uInt16 mnClickCount; //< mouse click count
+ sal_uInt16 mnFirstMouseCode; //< mouse code by mousebuttondown
+ sal_uInt16 mnMouseCode; //< mouse code
+ MouseEventModifiers mnMouseMode; //< mouse mode
+ bool mbHasFocus; //< focus
+ bool mbInMouseMove; //< is MouseMove on stack
+ bool mbMouseIn; //> is Mouse inside the frame
+ bool mbStartDragCalled; //< is command startdrag called
+ bool mbNeedSysWindow; //< set, when FrameSize <= IMPL_MIN_NEEDSYSWIN
+ bool mbMinimized; //< set, when FrameSize <= 0
+ bool mbStartFocusState; //< FocusState, when sending the event
+ bool mbInSysObjFocusHdl; //< within a SysChildren's GetFocus handler
+ bool mbInSysObjToTopHdl; //< within a SysChildren's ToTop handler
+ bool mbSysObjFocus; //< does a SysChild have focus
+ sal_Int32 mnTouchPanPosition;
+
+ css::uno::Reference< css::datatransfer::dnd::XDragSource > mxDragSource;
+ css::uno::Reference< css::datatransfer::dnd::XDropTarget > mxDropTarget;
+ css::uno::Reference< css::datatransfer::dnd::XDropTargetListener > mxDropTargetListener;
+ css::uno::Reference< css::datatransfer::clipboard::XClipboard > mxClipboard;
+
+ bool mbInternalDragGestureRecognizer;
+ bool mbDragging;
+ VclPtr<VirtualDevice> mpBuffer; ///< Buffer for the double-buffering
+ bool mbInBufferedPaint; ///< PaintHelper is in the process of painting into this buffer.
+ tools::Rectangle maBufferedRect; ///< Rectangle in the buffer that has to be painted to the screen.
+
+ ImplFrameData( vcl::Window *pWindow );
+};
+
+struct ImplAccessibleInfos
+{
+ sal_uInt16 nAccessibleRole;
+ std::optional<OUString>
+ pAccessibleName;
+ std::optional<OUString>
+ pAccessibleDescription;
+ VclPtr<vcl::Window> pLabeledByWindow;
+ VclPtr<vcl::Window> pLabelForWindow;
+
+ ImplAccessibleInfos();
+ ~ImplAccessibleInfos();
+};
+
+enum AlwaysInputMode { AlwaysInputNone = 0, AlwaysInputEnabled = 1 };
+
+enum class ImplPaintFlags {
+ NONE = 0x0000,
+ Paint = 0x0001,
+ PaintAll = 0x0002,
+ PaintAllChildren = 0x0004,
+ PaintChildren = 0x0008,
+ Erase = 0x0010,
+ CheckRtl = 0x0020,
+};
+namespace o3tl {
+ template<> struct typed_flags<ImplPaintFlags> : is_typed_flags<ImplPaintFlags, 0x003f> {};
+}
+
+
+class WindowImpl
+{
+private:
+ WindowImpl(const WindowImpl&) = delete;
+ WindowImpl& operator=(const WindowImpl&) = delete;
+public:
+ WindowImpl( vcl::Window& rWindow, WindowType );
+ ~WindowImpl();
+
+ VclPtr<vcl::WindowOutputDevice> mxOutDev;
+ std::unique_ptr<ImplWinData> mpWinData;
+ ImplFrameData* mpFrameData;
+ SalFrame* mpFrame;
+ SalObject* mpSysObj;
+ VclPtr<vcl::Window> mpFrameWindow;
+ VclPtr<vcl::Window> mpOverlapWindow;
+ VclPtr<vcl::Window> mpBorderWindow;
+ VclPtr<vcl::Window> mpClientWindow;
+ VclPtr<vcl::Window> mpParent;
+ VclPtr<vcl::Window> mpRealParent;
+ VclPtr<vcl::Window> mpFirstChild;
+ VclPtr<vcl::Window> mpLastChild;
+ VclPtr<vcl::Window> mpFirstOverlap;
+ VclPtr<vcl::Window> mpLastOverlap;
+ VclPtr<vcl::Window> mpPrev;
+ VclPtr<vcl::Window> mpNext;
+ VclPtr<vcl::Window> mpNextOverlap;
+ VclPtr<vcl::Window> mpLastFocusWindow;
+ VclPtr<PushButton> mpDlgCtrlDownWindow;
+ std::vector<Link<VclWindowEvent&,void>> maEventListeners;
+ int mnEventListenersIteratingCount;
+ std::set<Link<VclWindowEvent&,void>> maEventListenersDeleted;
+ std::vector<Link<VclWindowEvent&,void>> maChildEventListeners;
+ int mnChildEventListenersIteratingCount;
+ std::set<Link<VclWindowEvent&,void>> maChildEventListenersDeleted;
+ Link<vcl::Window&, bool> maHelpRequestHdl;
+ Link<vcl::Window&, bool> maMnemonicActivateHdl;
+ Link<tools::JsonWriter&, void> maDumpAsPropertyTreeHdl;
+
+ vcl::Cursor* mpCursor;
+ PointerStyle maPointer;
+ Fraction maZoom;
+ double mfPartialScrollX;
+ double mfPartialScrollY;
+ OUString maText;
+ std::optional<vcl::Font>
+ mpControlFont;
+ Color maControlForeground;
+ Color maControlBackground;
+ sal_Int32 mnLeftBorder;
+ sal_Int32 mnTopBorder;
+ sal_Int32 mnRightBorder;
+ sal_Int32 mnBottomBorder;
+ sal_Int32 mnWidthRequest;
+ sal_Int32 mnHeightRequest;
+ sal_Int32 mnOptimalWidthCache;
+ sal_Int32 mnOptimalHeightCache;
+ tools::Long mnX;
+ tools::Long mnY;
+ tools::Long mnAbsScreenX;
+ Point maPos;
+ OUString maHelpId;
+ OUString maHelpText;
+ OUString maQuickHelpText;
+ OUString maID;
+ InputContext maInputContext;
+ css::uno::Reference< css::awt::XVclWindowPeer > mxWindowPeer;
+ css::uno::Reference< css::accessibility::XAccessible > mxAccessible;
+ std::shared_ptr< VclSizeGroup > m_xSizeGroup;
+ std::vector<VclPtr<FixedText>> m_aMnemonicLabels;
+ std::unique_ptr<ImplAccessibleInfos> mpAccessibleInfos;
+ VCLXWindow* mpVCLXWindow;
+ vcl::Region maWinRegion; //< region to 'shape' the VCL window (frame coordinates)
+ vcl::Region maWinClipRegion; //< the (clipping) region that finally corresponds to the VCL window (frame coordinates)
+ vcl::Region maInvalidateRegion; //< region that has to be redrawn (frame coordinates)
+ std::unique_ptr<vcl::Region> mpChildClipRegion; //< child clip region if CLIPCHILDREN is set (frame coordinates)
+ vcl::Region* mpPaintRegion; //< only set during Paint() method call (window coordinates)
+ WinBits mnStyle;
+ WinBits mnPrevStyle;
+ WindowExtendedStyle mnExtendedStyle;
+ WindowType mnType;
+ ControlPart mnNativeBackground;
+ sal_uInt16 mnWaitCount;
+ ImplPaintFlags mnPaintFlags;
+ GetFocusFlags mnGetFocusFlags;
+ ParentClipMode mnParentClipMode;
+ ActivateModeFlags mnActivateMode;
+ DialogControlFlags mnDlgCtrlFlags;
+ AlwaysInputMode meAlwaysInputMode;
+ VclAlign meHalign;
+ VclAlign meValign;
+ VclPackType mePackType;
+ sal_Int32 mnPadding;
+ sal_Int32 mnGridHeight;
+ sal_Int32 mnGridLeftAttach;
+ sal_Int32 mnGridTopAttach;
+ sal_Int32 mnGridWidth;
+ sal_Int32 mnBorderWidth;
+ sal_Int32 mnMarginLeft;
+ sal_Int32 mnMarginRight;
+ sal_Int32 mnMarginTop;
+ sal_Int32 mnMarginBottom;
+ bool mbFrame:1,
+ mbBorderWin:1,
+ mbOverlapWin:1,
+ mbSysWin:1,
+ mbDialog:1,
+ mbDockWin:1,
+ mbFloatWin:1,
+ mbPushButton:1,
+ mbVisible:1,
+ mbDisabled:1,
+ mbInputDisabled:1,
+ mbNoUpdate:1,
+ mbNoParentUpdate:1,
+ mbActive:1,
+ mbReallyVisible:1,
+ mbReallyShown:1,
+ mbInInitShow:1,
+ mbChildPtrOverwrite:1,
+ mbNoPtrVisible:1,
+ mbPaintFrame:1,
+ mbInPaint:1,
+ mbMouseButtonDown:1,
+ mbMouseButtonUp:1,
+ mbKeyInput:1,
+ mbKeyUp:1,
+ mbCommand:1,
+ mbDefPos:1,
+ mbDefSize:1,
+ mbCallMove:1,
+ mbCallResize:1,
+ mbWaitSystemResize:1,
+ mbInitWinClipRegion:1,
+ mbInitChildRegion:1,
+ mbWinRegion:1,
+ mbClipChildren:1,
+ mbClipSiblings:1,
+ mbChildTransparent:1,
+ mbPaintTransparent:1,
+ mbMouseTransparent:1,
+ mbDlgCtrlStart:1,
+ mbFocusVisible:1,
+ mbTrackVisible:1,
+ mbUseNativeFocus:1,
+ mbNativeFocusVisible:1,
+ mbInShowFocus:1,
+ mbInHideFocus:1,
+ mbControlForeground:1,
+ mbControlBackground:1,
+ mbAlwaysOnTop:1,
+ mbCompoundControl:1,
+ mbCompoundControlHasFocus:1,
+ mbPaintDisabled:1,
+ mbAllResize:1,
+ mbInDispose:1,
+ mbExtTextInput:1,
+ mbInFocusHdl:1,
+ mbOverlapVisible:1,
+ mbCreatedWithToolkit:1,
+ mbToolBox:1,
+ mbSplitter:1,
+ mbSuppressAccessibilityEvents:1,
+ mbMenuFloatingWindow:1,
+ mbDrawSelectionBackground:1,
+ mbIsInTaskPaneList:1,
+ mbToolbarFloatingWindow:1,
+ mbHelpTextDynamic:1,
+ mbFakeFocusSet:1,
+ mbHexpand:1,
+ mbVexpand:1,
+ mbExpand:1,
+ mbFill:1,
+ mbSecondary:1,
+ mbNonHomogeneous:1,
+ mbDoubleBufferingRequested:1;
+
+ css::uno::Reference< css::uno::XInterface > mxDNDListenerContainer;
+
+ const vcl::ILibreOfficeKitNotifier* mpLOKNotifier; ///< To emit the LOK callbacks eg. for dialog tunneling.
+ vcl::LOKWindowId mnLOKWindowId; ///< ID of this specific window.
+ bool mbUseFrameData;
+};
+
+namespace vcl
+{
+/// Sets up the buffer to have settings matching the window, and restores the original state in the dtor.
+class VCL_DLLPUBLIC PaintBufferGuard
+{
+ ImplFrameData* mpFrameData;
+ VclPtr<vcl::Window> m_pWindow;
+ bool mbBackground;
+ Wallpaper maBackground;
+ AllSettings maSettings;
+ tools::Long mnOutOffX;
+ tools::Long mnOutOffY;
+ tools::Rectangle m_aPaintRect;
+public:
+ PaintBufferGuard(ImplFrameData* pFrameData, vcl::Window* pWindow);
+ ~PaintBufferGuard() COVERITY_NOEXCEPT_FALSE;
+ /// If this is called, then the dtor will also copy rRectangle to the window from the buffer, before restoring the state.
+ void SetPaintRect(const tools::Rectangle& rRectangle);
+ /// Returns either the frame's buffer or the window, in case of no buffering.
+ vcl::RenderContext* GetRenderContext();
+};
+typedef std::unique_ptr<PaintBufferGuard, o3tl::default_delete<PaintBufferGuard>> PaintBufferGuardPtr;
+}
+
+// helper methods
+
+bool ImplHandleMouseEvent( const VclPtr<vcl::Window>& xWindow, NotifyEventType nSVEvent, bool bMouseLeave,
+ tools::Long nX, tools::Long nY, sal_uInt64 nMsgTime,
+ sal_uInt16 nCode, MouseEventModifiers nMode );
+
+bool ImplLOKHandleMouseEvent( const VclPtr<vcl::Window>& xWindow, NotifyEventType nSVEvent, bool bMouseLeave,
+ tools::Long nX, tools::Long nY, sal_uInt64 nMsgTime,
+ sal_uInt16 nCode, MouseEventModifiers nMode, sal_uInt16 nClicks);
+
+void ImplHandleResize( vcl::Window* pWindow, tools::Long nNewWidth, tools::Long nNewHeight );
+
+VCL_DLLPUBLIC css::uno::Reference<css::accessibility::XAccessibleEditableText>
+FindFocusedEditableText(css::uno::Reference<css::accessibility::XAccessibleContext> const&);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/windowdev.hxx b/vcl/inc/windowdev.hxx
new file mode 100644
index 0000000000..a3d535646c
--- /dev/null
+++ b/vcl/inc/windowdev.hxx
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/outdev.hxx>
+
+namespace vcl
+{
+class WindowOutputDevice final : public ::OutputDevice
+{
+public:
+ WindowOutputDevice(vcl::Window& rOwnerWindow);
+ virtual ~WindowOutputDevice() override;
+ virtual void dispose() override;
+
+ size_t GetSyncCount() const override { return 0x000000ff; }
+ virtual void EnableRTL(bool bEnable = true) override;
+
+ void Flush() override;
+
+ void SaveBackground(VirtualDevice& rSaveDevice, const Point& rPos, const Size& rSize,
+ const Size&) const override;
+
+ css::awt::DeviceInfo GetDeviceInfo() const override;
+
+ virtual vcl::Region GetActiveClipRegion() const override;
+ virtual vcl::Region GetOutputBoundsClipRegion() const override;
+
+ virtual bool AcquireGraphics() const override;
+ virtual void ReleaseGraphics(bool bRelease = true) override;
+
+ Color GetBackgroundColor() const override;
+
+ using ::OutputDevice::SetSettings;
+ virtual void SetSettings(const AllSettings& rSettings) override;
+ void SetSettings(const AllSettings& rSettings, bool bChild);
+
+ bool CanEnableNativeWidget() const override;
+
+ /** Get the vcl::Window that this OutputDevice belongs to, if any */
+ virtual vcl::Window* GetOwnerWindow() const override { return mxOwnerWindow.get(); }
+
+ virtual css::uno::Reference<css::rendering::XCanvas>
+ ImplGetCanvas(bool bSpriteCanvas) const override;
+
+private:
+ virtual void InitClipRegion() override;
+
+ void ImplClearFontData(bool bNewFontLists) override;
+ void ImplRefreshFontData(bool bNewFontLists) override;
+ void ImplInitMapModeObjects() override;
+
+ virtual void CopyDeviceArea(SalTwoRect& aPosAry, bool bWindowInvalidate) override;
+ virtual const OutputDevice* DrawOutDevDirectCheck(const OutputDevice& rSrcDev) const override;
+ virtual void DrawOutDevDirectProcess(const OutputDevice& rSrcDev, SalTwoRect& rPosAry,
+ SalGraphics* pSrcGraphics) override;
+ virtual void ClipToPaintRegion(tools::Rectangle& rDstRect) override;
+ virtual bool UsePolyPolygonForComplexGradient() override;
+
+ VclPtr<vcl::Window> mxOwnerWindow;
+};
+
+}; // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/wizdlg.hxx b/vcl/inc/wizdlg.hxx
new file mode 100644
index 0000000000..56a9ed5261
--- /dev/null
+++ b/vcl/inc/wizdlg.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 .
+ */
+
+#pragma once
+
+#include <memory>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/roadmapwizard.hxx>
+#include <vcl/tabpage.hxx>
+
+struct ImplWizPageData
+{
+ ImplWizPageData* mpNext;
+ VclPtr<TabPage> mpPage;
+};
+
+namespace vcl
+{
+ struct RoadmapWizardImpl;
+ class RoadmapWizard;
+
+ namespace RoadmapWizardTypes
+ {
+ typedef VclPtr<TabPage> (* RoadmapPageFactory)( RoadmapWizard& );
+ };
+
+ //= RoadmapWizard
+
+ /** wizard for a roadmap
+
+ The basic new concept introduced is a <em>path</em>:<br/>
+ A <em>path</em> is a sequence of states, which are to be executed in a linear order.
+ Elements in the path can be skipped, depending on choices the user makes.
+
+ In the most simple wizards, you will have only one path consisting of <code>n</code> elements,
+ which are to be visited successively.
+
+ In a slightly more complex wizard, you will have one linear path, were certain
+ steps might be skipped due to user input. For instance, the user may decide to not specify
+ certain aspects of the to-be-created object (e.g. by unchecking a check box),
+ and the wizard then will simply disable the step which corresponds to this step.
+
+ In a yet more advanced wizards, you will have several paths of length <code>n1</code> and
+ <code>n2</code>, which share at least the first <code>k</code> states (where <code>k</code>
+ is at least 1), and an arbitrary number of other states.
+ */
+ class RoadmapWizard final : public Dialog
+ {
+ private:
+ Idle maWizardLayoutIdle;
+ Size maPageSize;
+ ImplWizPageData* mpFirstPage;
+ ImplWizButtonData* mpFirstBtn;
+ VclPtr<TabPage> mpCurTabPage;
+ VclPtr<PushButton> mpPrevBtn;
+ VclPtr<PushButton> mpNextBtn;
+ VclPtr<vcl::Window> mpViewWindow;
+ sal_uInt16 mnCurLevel;
+ sal_Int16 mnLeftAlignCount;
+ bool mbEmptyViewMargin;
+
+ DECL_LINK( ImplHandleWizardLayoutTimerHdl, Timer*, void );
+
+ // IMPORTANT:
+ // traveling pages should not be done by calling these base class member, some mechanisms of this class
+ // here (e.g. committing page data) depend on having full control over page traveling.
+ // So use the travelXXX methods if you need to travel
+
+ tools::Long LogicalCoordinateToPixel(int iCoordinate) const;
+ /**sets the number of buttons which should be left-aligned. Normally, buttons are right-aligned.
+
+ only to be used during construction, before any layouting happened
+ */
+ void SetLeftAlignedButtonCount( sal_Int16 _nCount );
+
+ void CalcAndSetSize();
+
+ public:
+ VclPtr<OKButton> m_pFinish;
+ VclPtr<CancelButton> m_pCancel;
+ VclPtr<PushButton> m_pNextPage;
+ VclPtr<PushButton> m_pPrevPage;
+ VclPtr<HelpButton> m_pHelp;
+
+ private:
+ std::unique_ptr<WizardMachineImplData> m_xWizardImpl;
+ // hold members in this structure to allow keeping compatible when members are added
+ std::unique_ptr<RoadmapWizardImpl> m_xRoadmapImpl;
+
+ public:
+ RoadmapWizard(vcl::Window* pParent, WinBits nStyle = WB_STDDIALOG, InitFlag eFlag = InitFlag::Default);
+ virtual ~RoadmapWizard( ) override;
+ virtual void dispose() override;
+
+ virtual void Resize() override;
+ virtual void StateChanged( StateChangedType nStateChange ) override;
+ virtual bool EventNotify( NotifyEvent& rNEvt ) override;
+
+ void ActivatePage();
+
+ virtual void queue_resize(StateChangedType eReason = StateChangedType::Layout) override;
+
+ bool ShowPage( sal_uInt16 nLevel );
+ void Finish( tools::Long nResult = 0 );
+ sal_uInt16 GetCurLevel() const { return mnCurLevel; }
+
+ void AddPage( TabPage* pPage );
+ void RemovePage( TabPage* pPage );
+ void SetPage( sal_uInt16 nLevel, TabPage* pPage );
+ TabPage* GetPage( sal_uInt16 nLevel ) const;
+
+ void AddButton( Button* pButton, tools::Long nOffset = 0 );
+ void RemoveButton( Button* pButton );
+ void AddButtonResponse( Button* pButton, int response);
+
+ void SetPageSizePixel( const Size& rSize ) { maPageSize = rSize; }
+ const Size& GetPageSizePixel() const { return maPageSize; }
+
+ void SetRoadmapHelpId( const OUString& _rId );
+ void SetRoadmapBitmap( const BitmapEx& maBitmap );
+
+ void InsertRoadmapItem(int nIndex, const OUString& rLabel, int nId, bool bEnabled);
+ void DeleteRoadmapItems();
+ int GetCurrentRoadmapItemID() const;
+ void SelectRoadmapItemByID(int nId, bool bGrabFocus = true);
+ void SetItemSelectHdl( const Link<LinkParamNone*,void>& _rHdl );
+ void ShowRoadmap(bool bShow);
+
+ FactoryFunction GetUITestFactory() const override;
+
+ private:
+
+ /// to override to create new pages
+ VclPtr<TabPage> createPage(WizardTypes::WizardState nState);
+
+ /// will be called when a new page is about to be displayed
+ void enterState(WizardTypes::WizardState _nState);
+
+ /** determine the next state to travel from the given one
+
+ This method ensures that traveling happens along the active path.
+
+ Return WZS_INVALID_STATE to prevent traveling.
+
+ @see activatePath
+ */
+ WizardTypes::WizardState determineNextState(WizardTypes::WizardState nCurrentState) const;
+
+ /// travel to the next state
+ void travelNext();
+
+ /// travel to the previous state
+ void travelPrevious();
+
+ /** removes a page from the history. Should be called when the page is being disabled
+ */
+ void removePageFromHistory(WizardTypes::WizardState nToRemove);
+
+ /** skips one or more states, until a given state is reached
+
+ The method behaves as if from the current state, <method>travelNext</method>s were called
+ successively, until <arg>_nTargetState</arg> is reached, but without actually creating or
+ displaying the \EDntermediate pages.
+
+ The skipped states appear in the state history, so <method>travelPrevious</method> will make use of them.
+
+ @return
+ <TRUE/> if and only if traveling was successful
+
+ @see skip
+ @see skipBackwardUntil
+ */
+ bool skipUntil(WizardTypes::WizardState nTargetState);
+
+ /** moves back one or more states, until a given state is reached
+
+ This method allows traveling backwards more than one state without actually showing the intermediate
+ states.
+
+ For instance, if you want to travel two steps backward at a time, you could used
+ two travelPrevious calls, but this would <em>show</em> both pages, which is not necessary,
+ since you're interested in the target page only. Using <member>skipBackwardUntil</member> relieves
+ you of this.
+
+ @return
+ <TRUE/> if and only if traveling was successful
+
+ @see skipUntil
+ @see skip
+ */
+ bool skipBackwardUntil(WizardTypes::WizardState nTargetState);
+
+ /** returns the current state of the machine
+
+ Vulgo, this is the identifier of the current tab page :)
+ */
+ WizardTypes::WizardState getCurrentState() const { return GetCurLevel(); }
+
+ /** returns a human readable name for a given state
+
+ There is a default implementation for this method, which returns the display name
+ as given in a call to describeState. If there is no description for the given state,
+ this is worth an assertion in a non-product build, and then an empty string is
+ returned.
+ */
+ OUString getStateDisplayName(WizardTypes::WizardState nState) const;
+
+ DECL_LINK( OnRoadmapItemSelected, LinkParamNone*, void );
+
+ /** updates the roadmap control to show the given path, as far as possible
+ (modulo conflicts with other paths)
+ */
+ void implUpdateRoadmap( );
+
+ public:
+ class AccessGuard
+ {
+ friend class RoadmapWizardTravelSuspension;
+ private:
+ AccessGuard() { }
+ };
+
+ void suspendTraveling( AccessGuard );
+ void resumeTraveling( AccessGuard );
+ bool isTravelingSuspended() const;
+
+ private:
+ void GetOrCreatePage(const WizardTypes::WizardState i_nState);
+
+ void ImplCalcSize( Size& rSize );
+ void ImplPosCtrls();
+ void ImplPosTabPage();
+ void ImplShowTabPage( TabPage* pPage );
+ TabPage* ImplGetPage( sal_uInt16 nLevel ) const;
+
+
+ DECL_LINK(OnNextPage, Button*, void);
+ DECL_LINK(OnPrevPage, Button*, void);
+ DECL_LINK(OnFinish, Button*, void);
+
+ void implConstruct( const WizardButtonFlags _nButtonFlags );
+
+ virtual void DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) override;
+ };
+
+ /// helper class to temporarily suspend any traveling in the wizard
+ class RoadmapWizardTravelSuspension
+ {
+ public:
+ RoadmapWizardTravelSuspension(RoadmapWizard& rWizard)
+ : m_pOWizard(&rWizard)
+ {
+ m_pOWizard->suspendTraveling(RoadmapWizard::AccessGuard());
+ }
+
+ ~RoadmapWizardTravelSuspension()
+ {
+ if (m_pOWizard)
+ m_pOWizard->resumeTraveling(RoadmapWizard::AccessGuard());
+ }
+
+ private:
+ VclPtr<RoadmapWizard> m_pOWizard;
+ };
+
+} // namespace vcl
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/DataFlavorMapping.cxx b/vcl/ios/DataFlavorMapping.cxx
new file mode 100644
index 0000000000..14bf0f6f43
--- /dev/null
+++ b/vcl/ios/DataFlavorMapping.cxx
@@ -0,0 +1,576 @@
+/* -*- Mode: ObjC; 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 <sal/config.h>
+
+#include "DataFlavorMapping.hxx"
+#include "HtmlFmtFlt.hxx"
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/datatransfer/XMimeContentType.hpp>
+#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <comphelper/processfactory.hxx>
+
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <osl/endian.h>
+
+#include <cassert>
+#include <cstring>
+
+#include <premac.h>
+#include <UIKit/UIKit.h>
+#include <MobileCoreServices/MobileCoreServices.h>
+#include <postmac.h>
+
+using namespace css::datatransfer;
+using namespace css::uno;
+using namespace css::lang;
+using namespace cppu;
+
+namespace
+{
+/* Determine whether or not a DataFlavor is valid.
+ */
+bool isValidFlavor(const DataFlavor& aFlavor)
+{
+ size_t len = aFlavor.MimeType.getLength();
+ Type dtype = aFlavor.DataType;
+ return ((len > 0)
+ && ((dtype == cppu::UnoType<Sequence<sal_Int8>>::get())
+ || (dtype == cppu::UnoType<OUString>::get())));
+}
+
+OUString NSStringToOUString(const NSString* cfString)
+{
+ assert(cfString && "Invalid parameter");
+
+ const char* utf8Str = [cfString UTF8String];
+ unsigned int len = rtl_str_getLength(utf8Str);
+
+ return OUString(utf8Str, len, RTL_TEXTENCODING_UTF8);
+}
+
+NSString* OUStringToNSString(const OUString& ustring)
+{
+ OString utf8Str = OUStringToOString(ustring, RTL_TEXTENCODING_UTF8);
+ return [NSString stringWithCString:utf8Str.getStr() encoding:NSUTF8StringEncoding];
+}
+
+NSString* PBTYPE_UTF8PLAINTEXT = (__bridge NSString*)kUTTypeUTF8PlainText;
+NSString* PBTYPE_RTF = (__bridge NSString*)kUTTypeRTF;
+NSString* PBTYPE_PNG = (__bridge NSString*)kUTTypePNG;
+NSString* PBTYPE_JPEG = (__bridge NSString*)kUTTypeJPEG;
+NSString* PBTYPE_HTML = (__bridge NSString*)kUTTypeHTML;
+NSString* PBTYPE_PDF = (__bridge NSString*)kUTTypePDF;
+
+const char* FLAVOR_SESX
+ = "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"";
+const char* FLAVOR_SLSDX = "application/"
+ "x-openoffice-linksrcdescriptor-xml;windows_formatname=\"Star Link "
+ "Source Descriptor (XML)\"";
+const char* FLAVOR_LSX
+ = "application/x-openoffice-link-source-xml;windows_formatname=\"Star Link Source (XML)\"";
+const char* FLAVOR_EOX
+ = "application/x-openoffice-embedded-obj-xml;windows_formatname=\"Star Embedded Object (XML)\"";
+const char* FLAVOR_SVXB
+ = "application/x-openoffice-svbx;windows_formatname=\"SVXB (StarView Bitmap/Animation)\"";
+const char* FLAVOR_GDIMF
+ = "application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\"";
+const char* FLAVOR_SODX = "application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star "
+ "Object Descriptor (XML)\"";
+struct FlavorMap
+{
+ NSString* SystemFlavor;
+ const char* OOoFlavor;
+ const char* HumanPresentableName;
+ bool DataTypeOUString; // sequence<byte> otherwise
+};
+
+// The SystemFlavor member is nil for the cases where there is no predefined pasteboard type UTI and
+// we use the internal MIME type (media type) also on the pasteboard. That is OK, there dos not seem
+// to be a requirement that the types are well-formed UTIs even on iOS. For an introduction to UTIs,
+// see for instance
+// https://alastairs-place.net/blog/2012/06/06/utis-are-better-than-you-think-and-heres-why/
+//
+// In those cases the MIME type might actually have parameters appended, separated by semicolons.
+// At least the FLAVOR_SODX one must have at least a typename="%PRODUCTNAME %PRODUCTVERSION
+// Spreadsheet" parameter (with macros expanded and translated) for LO to recognise it. See
+// lcl_TestFormat() in sc/source/ui/view/cellsh.cxx.
+
+static const FlavorMap flavorMap[]
+ = { { PBTYPE_UTF8PLAINTEXT, "text/plain;charset=utf-16", "Unicode Text (UTF-16)", true },
+ { PBTYPE_RTF, "text/rtf", "Rich Text Format", false },
+ { PBTYPE_PNG, "image/png", "Portable Network Graphics", false },
+ { PBTYPE_JPEG, "image/jpeg", "JPEG", false },
+ { PBTYPE_HTML, "text/html", "Plain HTML", false },
+ { PBTYPE_PDF, "application/pdf", "PDF File", false },
+ { nil, FLAVOR_SESX, "Star Embed Source (XML)", false },
+ { nil, FLAVOR_SLSDX, "Star Link Source Descriptor (XML)", false },
+ { nil, FLAVOR_LSX, "Star Link Source (XML)", false },
+ { nil, FLAVOR_EOX, "Star Embedded Object (XML)", false },
+ { nil, FLAVOR_SVXB, "SVXB (StarView Bitmap/Animation", false },
+ { nil, FLAVOR_GDIMF, "GDIMetaFile", false },
+ { nil, FLAVOR_SODX, "Star Object Descriptor (XML)", false } };
+
+#define SIZE_FLAVOR_MAP (sizeof(flavorMap) / sizeof(FlavorMap))
+
+inline bool isByteSequenceType(const Type& theType)
+{
+ return (theType == cppu::UnoType<Sequence<sal_Int8>>::get());
+}
+
+inline bool isOUStringType(const Type& theType)
+{
+ return (theType == cppu::UnoType<OUString>::get());
+}
+
+} // unnamed namespace
+
+/* A base class for other data provider.
+ */
+class DataProviderBaseImpl : public DataProvider
+{
+public:
+ DataProviderBaseImpl(const Any& data);
+ DataProviderBaseImpl(id data);
+ virtual ~DataProviderBaseImpl() override;
+
+protected:
+ Any mData;
+ //NSData* mSystemData;
+ id mSystemData;
+};
+
+DataProviderBaseImpl::DataProviderBaseImpl(const Any& data)
+ : mData(data)
+ , mSystemData(nil)
+{
+}
+
+DataProviderBaseImpl::DataProviderBaseImpl(id data)
+ : mSystemData(data)
+{
+ [mSystemData retain];
+}
+
+DataProviderBaseImpl::~DataProviderBaseImpl()
+{
+ if (mSystemData)
+ {
+ [mSystemData release];
+ }
+}
+
+class Utf8DataProvider : public DataProviderBaseImpl
+{
+public:
+ Utf8DataProvider(const Any& data);
+ Utf8DataProvider(NSData* data);
+
+ NSData* getSystemData() override;
+ Any getOOoData() override;
+};
+
+Utf8DataProvider::Utf8DataProvider(const Any& data)
+ : DataProviderBaseImpl(data)
+{
+}
+
+Utf8DataProvider::Utf8DataProvider(NSData* data)
+ : DataProviderBaseImpl(data)
+{
+}
+
+NSData* Utf8DataProvider::getSystemData()
+{
+ OUString ustr;
+ mData >>= ustr;
+
+ OString strUtf8;
+ ustr.convertToString(&strUtf8, RTL_TEXTENCODING_UTF8, OUSTRING_TO_OSTRING_CVTFLAGS);
+
+ return [NSData dataWithBytes:strUtf8.getStr() length:strUtf8.getLength()];
+}
+
+Any Utf8DataProvider::getOOoData()
+{
+ Any oOOData;
+
+ if (mSystemData)
+ {
+ oOOData <<= OUString(static_cast<const char*>([mSystemData bytes]), [mSystemData length],
+ RTL_TEXTENCODING_UTF8);
+ }
+ else
+ {
+ oOOData = mData;
+ }
+
+ return oOOData;
+}
+
+class ByteSequenceDataProvider : public DataProviderBaseImpl
+{
+public:
+ ByteSequenceDataProvider(const Any& data);
+ ByteSequenceDataProvider(NSData* data);
+
+ NSData* getSystemData() override;
+ Any getOOoData() override;
+};
+
+ByteSequenceDataProvider::ByteSequenceDataProvider(const Any& data)
+ : DataProviderBaseImpl(data)
+{
+}
+
+ByteSequenceDataProvider::ByteSequenceDataProvider(NSData* data)
+ : DataProviderBaseImpl(data)
+{
+}
+
+NSData* ByteSequenceDataProvider::getSystemData()
+{
+ Sequence<sal_Int8> rawData;
+ mData >>= rawData;
+
+ return [NSData dataWithBytes:rawData.getArray() length:rawData.getLength()];
+}
+
+Any ByteSequenceDataProvider::getOOoData()
+{
+ Any oOOData;
+
+ if (mSystemData)
+ {
+ unsigned int flavorDataLength = [mSystemData length];
+ Sequence<sal_Int8> byteSequence;
+ byteSequence.realloc(flavorDataLength);
+ memcpy(byteSequence.getArray(), [mSystemData bytes], flavorDataLength);
+ oOOData <<= byteSequence;
+ }
+ else
+ {
+ oOOData = mData;
+ }
+
+ return oOOData;
+}
+
+class HTMLFormatDataProvider : public DataProviderBaseImpl
+{
+public:
+ HTMLFormatDataProvider(NSData* data);
+
+ NSData* getSystemData() override;
+ Any getOOoData() override;
+};
+
+HTMLFormatDataProvider::HTMLFormatDataProvider(NSData* data)
+ : DataProviderBaseImpl(data)
+{
+}
+
+NSData* HTMLFormatDataProvider::getSystemData()
+{
+ Sequence<sal_Int8> textHtmlData;
+ mData >>= textHtmlData;
+
+ Sequence<sal_Int8> htmlFormatData = TextHtmlToHTMLFormat(textHtmlData);
+
+ return [NSData dataWithBytes:htmlFormatData.getArray() length:htmlFormatData.getLength()];
+}
+
+Any HTMLFormatDataProvider::getOOoData()
+{
+ Any oOOData;
+
+ if (mSystemData)
+ {
+ unsigned int flavorDataLength = [mSystemData length];
+ Sequence<sal_Int8> unkHtmlData;
+
+ unkHtmlData.realloc(flavorDataLength);
+ memcpy(unkHtmlData.getArray(), [mSystemData bytes], flavorDataLength);
+
+ Sequence<sal_Int8>* pPlainHtml = &unkHtmlData;
+ Sequence<sal_Int8> plainHtml;
+
+ if (isHTMLFormat(unkHtmlData))
+ {
+ plainHtml = HTMLFormatToTextHtml(unkHtmlData);
+ pPlainHtml = &plainHtml;
+ }
+
+ oOOData <<= *pPlainHtml;
+ }
+ else
+ {
+ oOOData = mData;
+ }
+
+ return oOOData;
+}
+
+DataFlavorMapper::DataFlavorMapper()
+{
+ Reference<XComponentContext> xContext = comphelper::getProcessComponentContext();
+ mrXMimeCntFactory = MimeContentTypeFactory::create(xContext);
+}
+
+DataFlavorMapper::~DataFlavorMapper()
+{
+ // release potential NSStrings
+ for (OfficeOnlyTypes::iterator it = maOfficeOnlyTypes.begin(); it != maOfficeOnlyTypes.end();
+ ++it)
+ {
+ [it->second release];
+ it->second = nil;
+ }
+}
+
+DataFlavor DataFlavorMapper::systemToOpenOfficeFlavor(const NSString* systemDataFlavor) const
+{
+ DataFlavor oOOFlavor;
+
+ for (size_t i = 0; i < SIZE_FLAVOR_MAP; i++)
+ {
+ if ((flavorMap[i].SystemFlavor == nil
+ && ([systemDataFlavor
+ isEqualToString:[NSString stringWithUTF8String:flavorMap[i].OOoFlavor]]
+ ||
+ [systemDataFlavor hasPrefix:[[NSString stringWithUTF8String:flavorMap[i].OOoFlavor]
+ stringByAppendingString:@";"]]))
+ || (flavorMap[i].SystemFlavor != nil &&
+ [systemDataFlavor
+ isEqualToString:const_cast<NSString*>(flavorMap[i].SystemFlavor)]))
+ {
+ if (flavorMap[i].SystemFlavor == nil)
+ oOOFlavor.MimeType = NSStringToOUString(systemDataFlavor);
+ else
+ oOOFlavor.MimeType = OUString::createFromAscii(flavorMap[i].OOoFlavor);
+ oOOFlavor.HumanPresentableName
+ = OUString::createFromAscii(flavorMap[i].HumanPresentableName);
+ oOOFlavor.DataType = flavorMap[i].DataTypeOUString
+ ? cppu::UnoType<OUString>::get()
+ : cppu::UnoType<Sequence<sal_Int8>>::get();
+ return oOOFlavor;
+ }
+ } // for
+
+ // look if this might be an internal type; if it comes in here it must have
+ // been through openOfficeToSystemFlavor before, so it should then be in the map
+ OUString aTryFlavor(NSStringToOUString(systemDataFlavor));
+ if (maOfficeOnlyTypes.find(aTryFlavor) != maOfficeOnlyTypes.end())
+ {
+ oOOFlavor.MimeType = aTryFlavor;
+ oOOFlavor.HumanPresentableName.clear();
+ oOOFlavor.DataType = cppu::UnoType<Sequence<sal_Int8>>::get();
+ }
+
+ return oOOFlavor;
+}
+
+NSString* DataFlavorMapper::openOfficeToSystemFlavor(const DataFlavor& oOOFlavor,
+ bool& rbInternal) const
+{
+ NSString* sysFlavor = nullptr;
+ rbInternal = false;
+
+ for (size_t i = 0; i < SIZE_FLAVOR_MAP; ++i)
+ {
+ if (oOOFlavor.MimeType.startsWith(OUString::createFromAscii(flavorMap[i].OOoFlavor)))
+ {
+ if (flavorMap[i].SystemFlavor != nil)
+ sysFlavor = flavorMap[i].SystemFlavor;
+ else
+ sysFlavor = OUStringToNSString(oOOFlavor.MimeType);
+ }
+ }
+
+ if (!sysFlavor)
+ {
+ // For some reason, if we allow text/html, we get an OSL_ENSURE failure in xmloff that
+ // apparently is a symptom of something being seriously wrong:
+ // xmloff/source/transform/OOo2Oasis.cxx:1925: duplicate doc handler
+ // Because is then followed a bit later by an assertion failure:
+ // Assertion failed: (!m_pFirst && !m_pLast && "There are still indices registered"), function ~SwContentIndexReg, file [...]/sw/source/core/bastyp/index.cxx, line 226
+
+ if (oOOFlavor.MimeType == "text/html")
+ return nil;
+
+ rbInternal = true;
+ OfficeOnlyTypes::const_iterator it = maOfficeOnlyTypes.find(oOOFlavor.MimeType);
+
+ if (it == maOfficeOnlyTypes.end())
+ sysFlavor = maOfficeOnlyTypes[oOOFlavor.MimeType]
+ = OUStringToNSString(oOOFlavor.MimeType);
+ else
+ sysFlavor = it->second;
+ }
+
+ return sysFlavor;
+}
+
+NSString* DataFlavorMapper::openOfficeImageToSystemFlavor()
+{
+ if ([[UIPasteboard generalPasteboard] containsPasteboardTypes:@[ PBTYPE_PNG ]])
+ return PBTYPE_PNG;
+ else if ([[UIPasteboard generalPasteboard] containsPasteboardTypes:@[ PBTYPE_JPEG ]])
+ return PBTYPE_JPEG;
+ else if ([[UIPasteboard generalPasteboard] containsPasteboardTypes:@[ PBTYPE_PDF ]])
+ return PBTYPE_PDF;
+ return @"";
+}
+
+DataProviderPtr_t
+DataFlavorMapper::getDataProvider(const NSString* systemFlavor,
+ Reference<XTransferable> const& rTransferable) const
+{
+ DataProviderPtr_t dp;
+
+ try
+ {
+ DataFlavor oOOFlavor = systemToOpenOfficeFlavor(systemFlavor);
+
+ Any data = rTransferable->getTransferData(oOOFlavor);
+
+ if (isByteSequenceType(data.getValueType()))
+ {
+ dp = DataProviderPtr_t(new ByteSequenceDataProvider(data));
+ }
+ else // Must be OUString type
+ {
+ SAL_WARN_IF(!isOUStringType(data.getValueType()), "vcl", "must be OUString type");
+ dp = DataProviderPtr_t(new Utf8DataProvider(data));
+ }
+ }
+ catch (const UnsupportedFlavorException& e)
+ {
+ SAL_WARN("vcl.ios.clipboard",
+ "DataFlavorMapper::getDataProvider(): Exception: " << e.Message);
+ // Somebody violates the contract of the clipboard
+ // interface @see XTransferable
+ }
+
+ return dp;
+}
+
+DataProviderPtr_t DataFlavorMapper::getDataProvider(const NSString* systemFlavor,
+ NSData* systemData)
+{
+ DataProviderPtr_t dp;
+
+ if (systemData == nil)
+ return dp;
+
+ if ([systemFlavor caseInsensitiveCompare:PBTYPE_UTF8PLAINTEXT] == NSOrderedSame)
+ {
+ dp = DataProviderPtr_t(new Utf8DataProvider(systemData));
+ }
+ else if ([systemFlavor caseInsensitiveCompare:PBTYPE_HTML] == NSOrderedSame)
+ {
+ dp = DataProviderPtr_t(new HTMLFormatDataProvider(systemData));
+ }
+ else
+ {
+ dp = DataProviderPtr_t(new ByteSequenceDataProvider(systemData));
+ }
+
+ return dp;
+}
+
+bool DataFlavorMapper::isValidMimeContentType(const OUString& contentType) const
+{
+ bool result = true;
+
+ try
+ {
+ Reference<XMimeContentType> xCntType(mrXMimeCntFactory->createMimeContentType(contentType));
+ }
+ catch (const IllegalArgumentException& e)
+ {
+ SAL_WARN("vcl.ios.clipboard",
+ "DataFlavorMapper::isValidMimeContentType(): Exception: " << e.Message);
+ result = false;
+ }
+
+ return result;
+}
+
+NSArray* DataFlavorMapper::flavorSequenceToTypesArray(
+ const css::uno::Sequence<css::datatransfer::DataFlavor>& flavors) const
+{
+ const sal_uInt32 nFlavors = flavors.getLength();
+ NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity:1];
+
+ bool bNeedDummyInternalFlavor(false);
+
+ for (sal_uInt32 i = 0; i < nFlavors; i++)
+ {
+ if (flavors[i].MimeType.startsWith("image/bmp"))
+ {
+ [array addObject:PBTYPE_PNG];
+ }
+ else
+ {
+ const NSString* str = openOfficeToSystemFlavor(flavors[i], bNeedDummyInternalFlavor);
+
+ if (str != nil)
+ {
+ [str retain];
+ [array addObject:str];
+ }
+ }
+ }
+
+ return [array autorelease];
+}
+
+css::uno::Sequence<css::datatransfer::DataFlavor>
+DataFlavorMapper::typesArrayToFlavorSequence(NSArray* types) const
+{
+ int nFormats = [types count];
+ Sequence<DataFlavor> flavors;
+
+ for (int i = 0; i < nFormats; i++)
+ {
+ NSString* sysFormat = [types objectAtIndex:i];
+ DataFlavor oOOFlavor = systemToOpenOfficeFlavor(sysFormat);
+
+ if (isValidFlavor(oOOFlavor))
+ {
+ flavors.realloc(flavors.getLength() + 1);
+ flavors.getArray()[flavors.getLength() - 1] = oOOFlavor;
+ SAL_INFO("vcl.ios.clipboard",
+ "Mapped " << [sysFormat UTF8String] << " to " << oOOFlavor.MimeType);
+ }
+ else
+ {
+ SAL_INFO("vcl.ios.clipboard",
+ "Was not able to map " << [sysFormat UTF8String] << " to an internal flavour");
+ }
+ }
+
+ return flavors;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/DataFlavorMapping.hxx b/vcl/ios/DataFlavorMapping.hxx
new file mode 100644
index 0000000000..dd115575f2
--- /dev/null
+++ b/vcl/ios/DataFlavorMapping.hxx
@@ -0,0 +1,124 @@
+/* -*- 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
+
+#include <com/sun/star/datatransfer/DataFlavor.hpp>
+#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/lang/XMultiComponentFactory.hpp>
+
+#include <premac.h>
+#import <UIKit/UIKit.h>
+#include <postmac.h>
+
+#include <memory>
+#include <unordered_map>
+
+/* An interface to get the clipboard data in either
+ system or OOo format.
+ */
+class DataProvider
+{
+public:
+ virtual ~DataProvider(){};
+
+ /* Get the clipboard data in the system format.
+ The caller has to retain/release the returned
+ CFDataRef on demand.
+ */
+ virtual NSData* getSystemData() = 0;
+
+ /* Get the clipboard data in OOo format.
+ */
+ virtual css::uno::Any getOOoData() = 0;
+};
+
+typedef std::unique_ptr<DataProvider> DataProviderPtr_t;
+
+class DataFlavorMapper
+{
+public:
+ /* Initialize a DataFavorMapper instance. Throws a RuntimeException in case the XMimeContentTypeFactory service
+ cannot be created.
+ */
+ DataFlavorMapper();
+ ~DataFlavorMapper();
+
+ /* Map a system data flavor to an OpenOffice data flavor.
+ Return an empty string if there is not suitable
+ mapping from a system data flavor to an OpenOffice data
+ flavor.
+ */
+ css::datatransfer::DataFlavor systemToOpenOfficeFlavor(const NSString* systemDataFlavor) const;
+
+ /* Map an OpenOffice data flavor to a system data flavor.
+ If there is no suitable mapping available NULL will
+ be returned.
+ */
+ NSString* openOfficeToSystemFlavor(const css::datatransfer::DataFlavor& oooDataFlavor,
+ bool& rbInternal) const;
+
+ /* Select the best available image data type
+ If there is no suitable mapping available NULL will
+ be returned.
+ */
+ static NSString* openOfficeImageToSystemFlavor();
+
+ /* Get a data provider which is able to provide the data 'rTransferable' offers in a format that can
+ be put on to the system clipboard.
+ */
+ DataProviderPtr_t getDataProvider(
+ const NSString* systemFlavor,
+ const css::uno::Reference<css::datatransfer::XTransferable>& rTransferable) const;
+
+ /* Get a data provider which is able to provide 'systemData' in the OOo expected format.
+ */
+ static DataProviderPtr_t getDataProvider(const NSString* systemFlavor, NSArray* systemData);
+
+ /* Get a data provider which is able to provide 'systemData' in the OOo expected format.
+ */
+ static DataProviderPtr_t getDataProvider(const NSString* systemFlavor, NSData* systemData);
+
+ /* Translate a sequence of DataFlavors into a NSArray of system types.
+ Only those DataFlavors for which a suitable mapping to a system
+ type exist will be contained in the returned types array.
+ */
+ NSArray* flavorSequenceToTypesArray(
+ const css::uno::Sequence<css::datatransfer::DataFlavor>& flavors) const;
+
+ /* Translate a NSArray of system types into a sequence of DataFlavors.
+ Only those types for which a suitable mapping to a DataFlavor
+ exist will be contained in the new DataFlavor Sequence.
+ */
+ css::uno::Sequence<css::datatransfer::DataFlavor>
+ typesArrayToFlavorSequence(NSArray* types) const;
+
+private:
+ /* Determines if the provided Mime content type is valid.
+ */
+ bool isValidMimeContentType(const OUString& contentType) const;
+
+private:
+ css::uno::Reference<css::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory;
+ typedef std::unordered_map<OUString, NSString*> OfficeOnlyTypes;
+ mutable OfficeOnlyTypes maOfficeOnlyTypes;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/HtmlFmtFlt.cxx b/vcl/ios/HtmlFmtFlt.cxx
new file mode 100644
index 0000000000..4f90ced3bc
--- /dev/null
+++ b/vcl/ios/HtmlFmtFlt.cxx
@@ -0,0 +1,172 @@
+/* -*- 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 "HtmlFmtFlt.hxx"
+
+#include <rtl/string.h>
+#include <osl/diagnose.h>
+
+#include <string>
+#include <sstream>
+#include <vector>
+#include <iomanip>
+#include <cassert>
+
+using namespace com::sun::star::uno;
+
+// converts the openoffice text/html clipboard format to the HTML Format
+// well known under MS Windows
+// the MS HTML Format has a header before the real html data
+
+// Version:1.0 Version number of the clipboard. Starting is 0.9
+// StartHTML: Byte count from the beginning of the clipboard to the start
+// of the context, or -1 if no context
+// EndHTML: Byte count from the beginning of the clipboard to the end
+// of the context, or -1 if no context
+// StartFragment: Byte count from the beginning of the clipboard to the
+// start of the fragment
+// EndFragment: Byte count from the beginning of the clipboard to the
+// end of the fragment
+// StartSelection: Byte count from the beginning of the clipboard to the
+// start of the selection
+// EndSelection: Byte count from the beginning of the clipboard to the
+// end of the selection
+
+// StartSelection and EndSelection are optional
+// The fragment should be preceded and followed by the HTML comments
+// <!--StartFragment--> and <!--EndFragment--> (no space between !-- and the
+// text
+
+namespace
+{
+std::string GetHtmlFormatHeader(size_t startHtml, size_t endHtml, size_t startFragment,
+ size_t endFragment)
+{
+ std::ostringstream htmlHeader;
+ htmlHeader << "Version:1.0" << '\r' << '\n';
+ htmlHeader << "StartHTML:" << std::setw(10) << std::setfill('0') << std::dec << startHtml
+ << '\r' << '\n';
+ htmlHeader << "EndHTML:" << std::setw(10) << std::setfill('0') << std::dec << endHtml << '\r'
+ << '\n';
+ htmlHeader << "StartFragment:" << std::setw(10) << std::setfill('0') << std::dec
+ << startFragment << '\r' << '\n';
+ htmlHeader << "EndFragment:" << std::setw(10) << std::setfill('0') << std::dec << endFragment
+ << '\r' << '\n';
+ return htmlHeader.str();
+}
+}
+
+// the office always writes the start and end html tag in upper cases and
+// without spaces both tags don't allow parameters
+const std::string TAG_HTML = std::string("<html>");
+const std::string TAG_END_HTML = std::string("</html>");
+
+// The body tag may have parameters so we need to search for the
+// closing '>' manually e.g. <BODY param> #92840#
+const std::string TAG_BODY = std::string("<body");
+const std::string TAG_END_BODY = std::string("</body");
+
+Sequence<sal_Int8> SAL_CALL TextHtmlToHTMLFormat(Sequence<sal_Int8> const& aTextHtml)
+{
+ OSL_ASSERT(aTextHtml.getLength() > 0);
+
+ if (aTextHtml.getLength() <= 0)
+ return Sequence<sal_Int8>();
+
+ // fill the buffer with dummy values to calc the exact length
+ std::string dummyHtmlHeader = GetHtmlFormatHeader(0, 0, 0, 0);
+ size_t lHtmlFormatHeader = dummyHtmlHeader.length();
+
+ std::string textHtml(reinterpret_cast<const char*>(aTextHtml.getConstArray()),
+ reinterpret_cast<const char*>(aTextHtml.getConstArray())
+ + aTextHtml.getLength());
+
+ std::string::size_type nStartHtml = textHtml.find(TAG_HTML) + lHtmlFormatHeader
+ - 1; // we start one before '<HTML>' Word 2000 does also so
+ std::string::size_type nEndHtml = textHtml.find(TAG_END_HTML) + lHtmlFormatHeader
+ + TAG_END_HTML.length()
+ + 1; // our SOffice 5.2 wants 2 behind </HTML>?
+
+ // The body tag may have parameters so we need to search for the
+ // closing '>' manually e.g. <BODY param> #92840#
+ std::string::size_type nStartFragment
+ = textHtml.find(">", textHtml.find(TAG_BODY)) + lHtmlFormatHeader + 1;
+ std::string::size_type nEndFragment = textHtml.find(TAG_END_BODY) + lHtmlFormatHeader;
+
+ std::string htmlFormat
+ = GetHtmlFormatHeader(nStartHtml, nEndHtml, nStartFragment, nEndFragment);
+ htmlFormat += textHtml;
+
+ Sequence<sal_Int8> byteSequence(htmlFormat.length() + 1); // space the trailing '\0'
+ memset(byteSequence.getArray(), 0, byteSequence.getLength());
+
+ memcpy(static_cast<void*>(byteSequence.getArray()),
+ static_cast<const void*>(htmlFormat.c_str()), htmlFormat.length());
+
+ return byteSequence;
+}
+
+const char* const HtmlStartTag = "<html";
+
+Sequence<sal_Int8> HTMLFormatToTextHtml(const Sequence<sal_Int8>& aHTMLFormat)
+{
+ assert(isHTMLFormat(aHTMLFormat) && "No HTML Format provided");
+
+ Sequence<sal_Int8>& nonconstHTMLFormatRef = const_cast<Sequence<sal_Int8>&>(aHTMLFormat);
+ char* dataStart = reinterpret_cast<char*>(nonconstHTMLFormatRef.getArray());
+ char* dataEnd = dataStart + nonconstHTMLFormatRef.getLength() - 1;
+ const char* htmlStartTag = strcasestr(dataStart, HtmlStartTag);
+
+ assert(htmlStartTag && "Seems to be no HTML at all");
+
+ // It doesn't seem to be HTML? Well then simply return what has been
+ // provided in non-debug builds
+ if (htmlStartTag == nullptr)
+ {
+ return aHTMLFormat;
+ }
+
+ sal_Int32 len = dataEnd - htmlStartTag;
+ Sequence<sal_Int8> plainHtmlData(len);
+
+ memcpy(static_cast<void*>(plainHtmlData.getArray()), htmlStartTag, len);
+
+ return plainHtmlData;
+}
+
+/* A simple format detection. We are just comparing the first few bytes
+ of the provided byte sequence to see whether or not it is the MS
+ Office Html format. If it shows that this is not reliable enough we
+ can improve this
+*/
+const char HtmlFormatStart[] = "Version:";
+int const HtmlFormatStartLen = (sizeof(HtmlFormatStart) - 1);
+
+bool isHTMLFormat(const Sequence<sal_Int8>& aHtmlSequence)
+{
+ if (aHtmlSequence.getLength() < HtmlFormatStartLen)
+ return false;
+
+ return rtl_str_compareIgnoreAsciiCase_WithLength(
+ HtmlFormatStart, HtmlFormatStartLen,
+ reinterpret_cast<const char*>(aHtmlSequence.getConstArray()), HtmlFormatStartLen)
+ == 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/HtmlFmtFlt.hxx b/vcl/ios/HtmlFmtFlt.hxx
new file mode 100644
index 0000000000..6a2cd6f646
--- /dev/null
+++ b/vcl/ios/HtmlFmtFlt.hxx
@@ -0,0 +1,38 @@
+/* -*- 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
+
+#include <com/sun/star/uno/Sequence.hxx>
+
+/* Transform plain HTML into the format expected by MS Office.
+ */
+css::uno::Sequence<sal_Int8> TextHtmlToHTMLFormat(css::uno::Sequence<sal_Int8> const& aTextHtml);
+
+/* Transform the MS Office HTML format into plain HTML.
+ */
+css::uno::Sequence<sal_Int8> HTMLFormatToTextHtml(const css::uno::Sequence<sal_Int8>& aHTMLFormat);
+
+/* Detects whether the given byte sequence contains the MS Office Html format.
+
+ @returns True if the MS Office Html format will be detected False otherwise.
+ */
+bool isHTMLFormat(const css::uno::Sequence<sal_Int8>& aHtmlSequence);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/clipboard.cxx b/vcl/ios/clipboard.cxx
new file mode 100644
index 0000000000..59209504da
--- /dev/null
+++ b/vcl/ios/clipboard.cxx
@@ -0,0 +1,182 @@
+/* -*- Mode: ObjC; 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 "ios/iosinst.hxx"
+#include "quartz/utils.h"
+
+#include "clipboard.hxx"
+
+#include "DataFlavorMapping.hxx"
+#include "iOSTransferable.hxx"
+#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+iOSClipboard::iOSClipboard()
+ : WeakComponentImplHelper<XSystemClipboard, XServiceInfo>(m_aMutex)
+{
+ auto xContext = comphelper::getProcessComponentContext();
+
+ mrXMimeCntFactory = css::datatransfer::MimeContentTypeFactory::create(xContext);
+
+ mpDataFlavorMapper.reset(new DataFlavorMapper());
+}
+
+iOSClipboard::~iOSClipboard() {}
+
+css::uno::Reference<css::datatransfer::XTransferable> SAL_CALL iOSClipboard::getContents()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ return css::uno::Reference<css::datatransfer::XTransferable>(
+ new iOSTransferable(mrXMimeCntFactory, mpDataFlavorMapper));
+}
+
+void SAL_CALL iOSClipboard::setContents(
+ const css::uno::Reference<css::datatransfer::XTransferable>& xTransferable,
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& /*xClipboardOwner*/)
+{
+ NSArray* types = xTransferable.is() ? mpDataFlavorMapper->flavorSequenceToTypesArray(
+ xTransferable->getTransferDataFlavors())
+ : [NSArray array];
+
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:1];
+ NSArray* array = @[ dict ];
+
+ for (sal_uInt32 i = 0; i < [types count]; ++i)
+ {
+ DataProviderPtr_t dp = mpDataFlavorMapper->getDataProvider(types[i], xTransferable);
+
+ if (dp.get() != nullptr)
+ {
+ NSData* pBoardData = (NSData*)dp->getSystemData();
+ dict[types[i]] = pBoardData;
+ }
+ }
+ SAL_INFO("vcl.ios.clipboard", "Setting pasteboard items: " << NSDictionaryKeysToOUString(dict));
+ [[UIPasteboard generalPasteboard] setItems:array options:@{}];
+
+ // We don't keep a copy of the clipboard contents around in-process, so fire the lost clipboard
+ // ownership event right away.
+ // fireLostClipboardOwnershipEvent(xClipboardOwner, xTransferable);
+
+ // fireClipboardChangedEvent(xTransferable);
+}
+
+OUString SAL_CALL iOSClipboard::getName() { return OUString(); }
+
+sal_Int8 SAL_CALL iOSClipboard::getRenderingCapabilities() { return 0; }
+
+void SAL_CALL iOSClipboard::addClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if (!listener.is())
+ throw css::lang::IllegalArgumentException(
+ "empty reference", static_cast<css::datatransfer::clipboard::XClipboardEx*>(this), 1);
+
+ mClipboardListeners.push_back(listener);
+}
+
+void SAL_CALL iOSClipboard::removeClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if (!listener.is())
+ throw css::lang::IllegalArgumentException(
+ "empty reference", static_cast<css::datatransfer::clipboard::XClipboardEx*>(this), 1);
+
+ mClipboardListeners.remove(listener);
+}
+
+void iOSClipboard::fireClipboardChangedEvent(
+ css::uno::Reference<css::datatransfer::XTransferable> xNewContents)
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ std::list<css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>> listeners(
+ mClipboardListeners);
+ css::datatransfer::clipboard::ClipboardEvent aEvent;
+
+ if (!listeners.empty())
+ {
+ aEvent = css::datatransfer::clipboard::ClipboardEvent(getXWeak(), xNewContents);
+ }
+
+ aGuard.clear();
+
+ while (!listeners.empty())
+ {
+ if (listeners.front().is())
+ {
+ try
+ {
+ listeners.front()->changedContents(aEvent);
+ }
+ catch (const css::uno::RuntimeException&)
+ {
+ }
+ }
+ listeners.pop_front();
+ }
+}
+
+void iOSClipboard::fireLostClipboardOwnershipEvent(
+ css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> const& oldOwner,
+ css::uno::Reference<css::datatransfer::XTransferable> const& oldContent)
+{
+ assert(oldOwner.is());
+
+ try
+ {
+ oldOwner->lostOwnership(static_cast<css::datatransfer::clipboard::XClipboardEx*>(this),
+ oldContent);
+ }
+ catch (const css::uno::RuntimeException&)
+ {
+ }
+}
+
+OUString SAL_CALL iOSClipboard::getImplementationName()
+{
+ return OUString("com.sun.star.datatransfer.clipboard.iOSClipboard");
+}
+
+sal_Bool SAL_CALL iOSClipboard::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL iOSClipboard::getSupportedServiceNames()
+{
+ return { OUString("com.sun.star.datatransfer.clipboard.SystemClipboard") };
+}
+
+css::uno::Reference<css::uno::XInterface>
+IosSalInstance::CreateClipboard(const css::uno::Sequence<css::uno::Any>&)
+{
+ return getXWeak(new iOSClipboard());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/clipboard.hxx b/vcl/ios/clipboard.hxx
new file mode 100644
index 0000000000..0868409126
--- /dev/null
+++ b/vcl/ios/clipboard.hxx
@@ -0,0 +1,107 @@
+/* -*- 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
+
+#include "DataFlavorMapping.hxx"
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <cppuhelper/compbase.hxx>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardOwner.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
+#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
+#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <cppuhelper/basemutex.hxx>
+#include <com/sun/star/lang/XMultiComponentFactory.hpp>
+
+#include <list>
+
+#include <premac.h>
+#import <UIKit/UIKit.h>
+#include <postmac.h>
+
+class iOSClipboard
+ : public ::cppu::BaseMutex,
+ public ::cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard,
+ css::lang::XServiceInfo>
+{
+public:
+ iOSClipboard();
+
+ virtual ~iOSClipboard() override;
+ iOSClipboard(const iOSClipboard&) = delete;
+ iOSClipboard& operator=(const iOSClipboard&) = delete;
+
+ // XClipboard
+
+ css::uno::Reference<css::datatransfer::XTransferable> SAL_CALL getContents() override;
+
+ void SAL_CALL setContents(
+ const css::uno::Reference<css::datatransfer::XTransferable>& xTransferable,
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& xClipboardOwner)
+ override;
+
+ OUString SAL_CALL getName() override;
+
+ // XClipboardEx
+
+ sal_Int8 SAL_CALL getRenderingCapabilities() override;
+
+ // XClipboardNotifier
+
+ void SAL_CALL addClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+ override;
+
+ void SAL_CALL removeClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+ override;
+
+ // XServiceInfo
+
+ OUString SAL_CALL getImplementationName() override;
+
+ sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override;
+
+ css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+private:
+ /* Notify the current clipboard owner that he is no longer the clipboard owner. */
+ void fireLostClipboardOwnershipEvent(
+ css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> const& oldOwner,
+ css::uno::Reference<css::datatransfer::XTransferable> const& oldContent);
+
+ /* Notify all registered XClipboardListener that the clipboard content has changed. */
+ void
+ fireClipboardChangedEvent(css::uno::Reference<css::datatransfer::XTransferable> xNewContents);
+
+private:
+ css::uno::Reference<css::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory;
+ std::list<css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>>
+ mClipboardListeners;
+ css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> mXClipboardOwner;
+ std::shared_ptr<DataFlavorMapper> mpDataFlavorMapper;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/dummies.cxx b/vcl/ios/dummies.cxx
new file mode 100644
index 0000000000..a1d3cbcb67
--- /dev/null
+++ b/vcl/ios/dummies.cxx
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 "ios/iosinst.hxx"
+#include "salprn.hxx"
+#include "quartz/salgdi.h"
+#include "headless/svpdata.hxx"
+#include "headless/svpinst.hxx"
+#include "unx/fontmanager.hxx"
+
+std::unique_ptr<SalPrinter> SvpSalInstance::CreatePrinter( SalInfoPrinter* /* pInfoPrinter */ )
+{
+ return nullptr;
+}
+
+OUString SvpSalInstance::GetDefaultPrinter()
+{
+ return OUString();
+}
+
+std::unique_ptr<GenPspGraphics> SvpSalInstance::CreatePrintGraphics()
+{
+ return nullptr;
+}
+
+void SvpSalInstance::PostPrintersChanged()
+{
+}
+
+SalInfoPrinter* SvpSalInstance::CreateInfoPrinter( SalPrinterQueueInfo* /* pQueueInfo */,
+ ImplJobSetup* /* pJobSetup */ )
+{
+ return NULL;
+}
+
+void SvpSalInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter )
+{
+ delete pPrinter;
+}
+
+void SvpSalInstance::GetPrinterQueueInfo( ImplPrnQueueList* /* pList */ )
+{
+}
+
+void SvpSalInstance::GetPrinterQueueState( SalPrinterQueueInfo* /* pInfo */ )
+{
+}
+
+std::unique_ptr<SalPrinter> SalGenericInstance::CreatePrinter( SalInfoPrinter* /* pInfoPrinter */ )
+{
+ return nullptr;
+}
+
+OUString SalGenericInstance::GetDefaultPrinter()
+{
+ return OUString();
+}
+
+void SalGenericInstance::PostPrintersChanged()
+{
+}
+
+SalInfoPrinter* SalGenericInstance::CreateInfoPrinter( SalPrinterQueueInfo* /* pQueueInfo */,
+ ImplJobSetup* /* pJobSetup */ )
+{
+ return NULL;
+}
+
+void SalGenericInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter )
+{
+ delete pPrinter;
+}
+
+void SalGenericInstance::GetPrinterQueueInfo( ImplPrnQueueList* /* pList */ )
+{
+}
+
+void SalGenericInstance::GetPrinterQueueState( SalPrinterQueueInfo* /* pInfo */ )
+{
+}
+
+void SalGenericInstance::updatePrinterUpdate()
+{
+}
+
+void SalGenericInstance::jobEndedPrinterUpdate()
+{
+}
+
+using namespace psp;
+
+bool AquaGraphicsBackend::drawNativeControl(ControlType /* nType */,
+ ControlPart /* nPart */,
+ const tools::Rectangle & /* rControlRegion */,
+ ControlState /* nState */,
+ const ImplControlValue & /* aValue */)
+{
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/iOSTransferable.cxx b/vcl/ios/iOSTransferable.cxx
new file mode 100644
index 0000000000..bfbc8a9afa
--- /dev/null
+++ b/vcl/ios/iOSTransferable.cxx
@@ -0,0 +1,184 @@
+/* -*- 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 <sal/config.h>
+
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <sal/log.hxx>
+#include <sal/types.h>
+#include <osl/diagnose.h>
+
+#include <quartz/utils.h>
+
+#include "iOSTransferable.hxx"
+
+#include "DataFlavorMapping.hxx"
+
+using namespace osl;
+using namespace cppu;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::lang;
+
+namespace
+{
+bool isValidFlavor(const DataFlavor& aFlavor)
+{
+ size_t len = aFlavor.MimeType.getLength();
+ Type dtype = aFlavor.DataType;
+ return ((len > 0)
+ && ((dtype == cppu::UnoType<Sequence<sal_Int8>>::get())
+ || (dtype == cppu::UnoType<OUString>::get())));
+}
+
+bool cmpAllContentTypeParameter(const Reference<XMimeContentType>& xLhs,
+ const Reference<XMimeContentType>& xRhs)
+{
+ Sequence<OUString> xLhsFlavors = xLhs->getParameters();
+ Sequence<OUString> xRhsFlavors = xRhs->getParameters();
+
+ // Stop here if the number of parameters is different already
+ if (xLhsFlavors.getLength() != xRhsFlavors.getLength())
+ return false;
+
+ try
+ {
+ OUString pLhs;
+ OUString pRhs;
+
+ for (sal_Int32 i = 0; i < xLhsFlavors.getLength(); i++)
+ {
+ pLhs = xLhs->getParameterValue(xLhsFlavors[i]);
+ pRhs = xRhs->getParameterValue(xLhsFlavors[i]);
+
+ if (!pLhs.equalsIgnoreAsciiCase(pRhs))
+ {
+ return false;
+ }
+ }
+ }
+ catch (IllegalArgumentException&)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+} // unnamed namespace
+
+iOSTransferable::iOSTransferable(const Reference<XMimeContentTypeFactory>& rXMimeCntFactory,
+ std::shared_ptr<DataFlavorMapper> pDataFlavorMapper)
+ : mrXMimeCntFactory(rXMimeCntFactory)
+ , mDataFlavorMapper(pDataFlavorMapper)
+{
+ initClipboardItemList();
+}
+
+iOSTransferable::~iOSTransferable() {}
+
+Any SAL_CALL iOSTransferable::getTransferData(const DataFlavor& aFlavor)
+{
+ if (!isValidFlavor(aFlavor) || !isDataFlavorSupported(aFlavor))
+ {
+ throw UnsupportedFlavorException("Unsupported data flavor",
+ static_cast<XTransferable*>(this));
+ }
+
+ bool bInternal(false);
+ NSString* sysFormat = (aFlavor.MimeType.startsWith("image/png"))
+ ? DataFlavorMapper::openOfficeImageToSystemFlavor()
+ : mDataFlavorMapper->openOfficeToSystemFlavor(aFlavor, bInternal);
+ DataProviderPtr_t dp;
+
+ NSData* sysData = [[UIPasteboard generalPasteboard] dataForPasteboardType:sysFormat];
+ if (!sysData)
+ {
+ // Related: gh#5908 throw an exception if the data flavor is nil
+ // If nil is returned, it can mean that the user has selected the
+ // "disallow" option and so we can't access the current clipboard
+ // contents. Also, by throwing an exception, the "allow or disallow"
+ // dialog will display again the next time the user tries to paste.
+ throw UnsupportedFlavorException("Data flavor is nil", static_cast<XTransferable*>(this));
+ }
+
+ dp = DataFlavorMapper::getDataProvider(sysFormat, sysData);
+
+ if (dp.get() == nullptr)
+ {
+ throw UnsupportedFlavorException("Unsupported data flavor",
+ static_cast<XTransferable*>(this));
+ }
+
+ return dp->getOOoData();
+}
+
+Sequence<DataFlavor> SAL_CALL iOSTransferable::getTransferDataFlavors() { return mFlavorList; }
+
+sal_Bool SAL_CALL iOSTransferable::isDataFlavorSupported(const DataFlavor& aFlavor)
+{
+ for (sal_Int32 i = 0; i < mFlavorList.getLength(); i++)
+ if (compareDataFlavors(aFlavor, mFlavorList[i]))
+ return true;
+
+ return false;
+}
+
+void iOSTransferable::initClipboardItemList()
+{
+ NSArray* pboardFormats = [[UIPasteboard generalPasteboard] pasteboardTypes];
+
+ if (pboardFormats == nullptr)
+ {
+ throw RuntimeException("Cannot get clipboard data", static_cast<XTransferable*>(this));
+ }
+
+ SAL_INFO("vcl.ios.clipboard", "Types on clipboard: " << NSStringArrayToOUString(pboardFormats));
+
+ mFlavorList = mDataFlavorMapper->typesArrayToFlavorSequence(pboardFormats);
+}
+
+/* Compares two DataFlavors. Returns true if both DataFlavor have the same media type
+ and the number of parameter and all parameter values do match otherwise false
+ is returned.
+ */
+bool iOSTransferable::compareDataFlavors(const DataFlavor& lhs, const DataFlavor& rhs)
+{
+ try
+ {
+ Reference<XMimeContentType> xLhs(mrXMimeCntFactory->createMimeContentType(lhs.MimeType));
+ Reference<XMimeContentType> xRhs(mrXMimeCntFactory->createMimeContentType(rhs.MimeType));
+
+ if (!xLhs->getFullMediaType().equalsIgnoreAsciiCase(xRhs->getFullMediaType())
+ || !cmpAllContentTypeParameter(xLhs, xRhs))
+ {
+ return false;
+ }
+ }
+ catch (IllegalArgumentException&)
+ {
+ OSL_FAIL("Invalid content type detected");
+ return false;
+ }
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/iOSTransferable.hxx b/vcl/ios/iOSTransferable.hxx
new file mode 100644
index 0000000000..91f4e440ee
--- /dev/null
+++ b/vcl/ios/iOSTransferable.hxx
@@ -0,0 +1,69 @@
+/* -*- 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
+
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/XMimeContentType.hpp>
+
+#include "DataFlavorMapping.hxx"
+
+#include <premac.h>
+#import <UIKit/UIKit.h>
+#include <postmac.h>
+
+#include <memory>
+#include <vector>
+
+class iOSTransferable : public ::cppu::WeakImplHelper<css::datatransfer::XTransferable>
+{
+public:
+ explicit iOSTransferable(
+ css::uno::Reference<css::datatransfer::XMimeContentTypeFactory> const& rXMimeCntFactory,
+ std::shared_ptr<DataFlavorMapper> pDataFlavorMapper);
+
+ virtual ~iOSTransferable() override;
+ iOSTransferable(const iOSTransferable&) = delete;
+ iOSTransferable& operator=(const iOSTransferable&) = delete;
+
+ // XTransferable
+
+ virtual css::uno::Any SAL_CALL
+ getTransferData(const css::datatransfer::DataFlavor& aFlavor) override;
+
+ css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL getTransferDataFlavors() override;
+
+ sal_Bool SAL_CALL isDataFlavorSupported(const css::datatransfer::DataFlavor& aFlavor) override;
+
+ // Helper functions not part of the XTransferable interface
+
+ void initClipboardItemList();
+
+ bool compareDataFlavors(const css::datatransfer::DataFlavor& lhs,
+ const css::datatransfer::DataFlavor& rhs);
+
+private:
+ css::uno::Sequence<css::datatransfer::DataFlavor> mFlavorList;
+ css::uno::Reference<css::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory;
+ std::shared_ptr<DataFlavorMapper> mDataFlavorMapper;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/iosinst.cxx b/vcl/ios/iosinst.cxx
new file mode 100644
index 0000000000..371d5c246c
--- /dev/null
+++ b/vcl/ios/iosinst.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 <premac.h>
+#include <UIKit/UIKit.h>
+#include <postmac.h>
+
+#include "ios/iosinst.hxx"
+#include "quartz/salgdi.h"
+#include "headless/svpdata.hxx"
+#include "headless/svpdummies.hxx"
+#include "quartz/utils.h"
+#include "quartz/SystemFontList.hxx"
+#include <vcl/layout.hxx>
+#include <vcl/settings.hxx>
+
+// Totally wrong of course but doesn't seem to harm much in the iOS app.
+static int viewWidth = 1, viewHeight = 1;
+
+void IosSalInstance::GetWorkArea( AbsoluteScreenPixelRectangle& rRect )
+{
+ rRect = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( 0, 0 ),
+ AbsoluteScreenPixelSize( viewWidth, viewHeight ) );
+}
+
+IosSalInstance *IosSalInstance::getInstance()
+{
+ if (!ImplGetSVData())
+ return NULL;
+ return static_cast<IosSalInstance *>(GetSalInstance());
+}
+
+IosSalInstance::IosSalInstance( std::unique_ptr<SalYieldMutex> pMutex )
+ : SvpSalInstance( std::move(pMutex) )
+{
+}
+
+IosSalInstance::~IosSalInstance()
+{
+}
+
+class IosSalSystem : public SvpSalSystem {
+public:
+ IosSalSystem() : SvpSalSystem() {}
+ virtual ~IosSalSystem() {}
+ virtual int ShowNativeDialog( const OUString& rTitle,
+ const OUString& rMessage,
+ const std::vector< OUString >& rButtons );
+};
+
+SalSystem *IosSalInstance::CreateSalSystem()
+{
+ return new IosSalSystem();
+}
+
+class IosSalFrame : public SvpSalFrame
+{
+public:
+ IosSalFrame( IosSalInstance *pInstance,
+ SalFrame *pParent,
+ SalFrameStyleFlags nSalFrameStyle)
+ : SvpSalFrame( pInstance, pParent, nSalFrameStyle )
+ {
+ if (pParent == NULL && viewWidth > 1 && viewHeight > 1)
+ SetPosSize(0, 0, viewWidth, viewHeight, SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT);
+ }
+
+ virtual void GetWorkArea( AbsoluteScreenPixelRectangle& rRect ) override
+ {
+ IosSalInstance::getInstance()->GetWorkArea( rRect );
+ }
+
+ virtual void ShowFullScreen( bool, sal_Int32 ) override
+ {
+ SetPosSize( 0, 0, viewWidth, viewHeight,
+ SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
+ }
+
+ virtual void UpdateSettings( AllSettings &rSettings ) override
+ {
+ // Clobber the UI fonts
+ vcl::Font aFont( "Helvetica", Size( 0, 10 ) );
+
+ StyleSettings aStyleSet = rSettings.GetStyleSettings();
+ aStyleSet.SetAppFont( aFont );
+ aStyleSet.SetHelpFont( aFont );
+ aStyleSet.SetMenuFont( aFont );
+ aStyleSet.SetToolFont( aFont );
+ aStyleSet.SetLabelFont( aFont );
+ aStyleSet.SetRadioCheckFont( aFont );
+ aStyleSet.SetPushButtonFont( aFont );
+ aStyleSet.SetFieldFont( aFont );
+ aStyleSet.SetIconFont( aFont );
+ aStyleSet.SetTabFont( aFont );
+ aStyleSet.SetGroupFont( aFont );
+
+ Color aBackgroundColor( 0xff, 0xff, 0xff );
+ aStyleSet.BatchSetBackgrounds( aBackgroundColor, false );
+ aStyleSet.SetMenuColor( aBackgroundColor );
+ aStyleSet.SetMenuBarColor( aBackgroundColor );
+ aStyleSet.SetDialogColor( aBackgroundColor );
+
+ rSettings.SetStyleSettings( aStyleSet );
+ }
+};
+
+SalFrame *IosSalInstance::CreateChildFrame( SystemParentData* pParent, SalFrameStyleFlags nStyle )
+{
+ (void)pParent;
+ return new IosSalFrame( this, NULL, nStyle );
+}
+
+SalFrame *IosSalInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
+{
+ return new IosSalFrame( this, pParent, nStyle );
+}
+
+SalData::SalData() :
+ mxRGBSpace( CGColorSpaceCreateDeviceRGB() ),
+ mxGraySpace( CGColorSpaceCreateDeviceGray() )
+{
+ SetSalData(this);
+}
+
+SalData::~SalData()
+{
+ CGColorSpaceRelease(mxRGBSpace);
+ CGColorSpaceRelease(mxGraySpace);
+}
+
+extern "C" SalInstance *create_SalInstance()
+{
+ IosSalInstance* pInstance = new IosSalInstance( std::make_unique<SvpSalYieldMutex>() );
+ new SvpSalData();
+ return pInstance;
+}
+
+int IosSalSystem::ShowNativeDialog( const OUString& rTitle,
+ const OUString& rMessage,
+ const std::vector< OUString >& rButtons )
+{
+ (void)rButtons;
+
+ NSLog(@"%@: %@", CreateNSString(rTitle), CreateNSString(rMessage));
+
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/salios.cxx b/vcl/ios/salios.cxx
new file mode 100644
index 0000000000..362fa258ea
--- /dev/null
+++ b/vcl/ios/salios.cxx
@@ -0,0 +1,585 @@
+/* -*- 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 .
+ */
+
+// This file contains the iOS-specific versions of the functions which were touched in the commit to
+// fix tdf#138122. The functions are here (for now) as they were before that commit. The
+// macOS-specific versions of these functions are in vcl/osx/salmacos.cxx.
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <vcl/bitmap.hxx>
+
+#include <ios/iosinst.hxx>
+#include <quartz/salbmp.h>
+#include <quartz/salgdi.h>
+#include <quartz/salvd.h>
+#include <quartz/utils.h>
+
+#include <svdata.hxx>
+
+// From salbmp.cxx
+
+bool QuartzSalBitmap::Create(CGLayerHolder const & rLayerHolder, int nBitmapBits, int nX, int nY, int nWidth, int nHeight, bool bFlipped)
+{
+ SAL_WARN_IF(!rLayerHolder.isSet(), "vcl", "QuartzSalBitmap::Create() from non-layered context");
+
+ // sanitize input parameters
+ if( nX < 0 ) {
+ nWidth += nX;
+ nX = 0;
+ }
+
+ if( nY < 0 ) {
+ nHeight += nY;
+ nY = 0;
+ }
+
+ const CGSize aLayerSize = CGLayerGetSize(rLayerHolder.get());
+
+ if( nWidth >= static_cast<int>(aLayerSize.width) - nX )
+ nWidth = static_cast<int>(aLayerSize.width) - nX;
+
+ if( nHeight >= static_cast<int>(aLayerSize.height) - nY )
+ nHeight = static_cast<int>(aLayerSize.height) - nY;
+
+ if( (nWidth < 0) || (nHeight < 0) )
+ nWidth = nHeight = 0;
+
+ // initialize properties
+ mnWidth = nWidth;
+ mnHeight = nHeight;
+ mnBits = nBitmapBits ? nBitmapBits : 32;
+
+ // initialize drawing context
+ CreateContext();
+
+ // copy layer content into the bitmap buffer
+ const CGPoint aSrcPoint = { static_cast<CGFloat>(-nX), static_cast<CGFloat>(-nY) };
+ if (maGraphicContext.isSet()) // remove warning
+ {
+ if( bFlipped )
+ {
+ CGContextTranslateCTM( maGraphicContext.get(), 0, +mnHeight );
+
+ CGContextScaleCTM( maGraphicContext.get(), +1, -1 );
+ }
+
+ CGContextDrawLayerAtPoint(maGraphicContext.get(), aSrcPoint, rLayerHolder.get());
+ }
+ return true;
+}
+
+// From salgdicommon.cxx
+
+void AquaGraphicsBackend::copyBits(const SalTwoRect& rPosAry, SalGraphics *pSrcGraphics)
+{
+ //from unix salgdi2.cxx
+ //[FIXME] find a better way to prevent calc from crashing when width and height are negative
+ if( rPosAry.mnSrcWidth <= 0 ||
+ rPosAry.mnSrcHeight <= 0 ||
+ rPosAry.mnDestWidth <= 0 ||
+ rPosAry.mnDestHeight <= 0 )
+ {
+ return;
+ }
+
+ // If called from idle layout, maContextHolder.get() is NULL, no idea what to do
+ if (!mrShared.maContextHolder.isSet())
+ return;
+
+ AquaSharedAttributes* pSrcShared = nullptr;
+
+ if (pSrcGraphics)
+ {
+ AquaSalGraphics* pSrc = static_cast<AquaSalGraphics*>(pSrcGraphics);
+ pSrcShared = &pSrc->getAquaGraphicsBackend()->GetShared();
+ }
+ else
+ pSrcShared = &mrShared;
+
+ // accelerate trivial operations
+ const bool bSameGraphics = (pSrcShared == &mrShared);
+
+ if( bSameGraphics &&
+ (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) &&
+ (rPosAry.mnSrcHeight == rPosAry.mnDestHeight))
+ {
+ // short circuit if there is nothing to do
+ if( (rPosAry.mnSrcX == rPosAry.mnDestX) &&
+ (rPosAry.mnSrcY == rPosAry.mnDestY))
+ {
+ return;
+ }
+ // use copyArea() if source and destination context are identical
+ copyArea( rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnSrcX, rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, false/*bWindowInvalidate*/ );
+ return;
+ }
+
+ mrShared.applyXorContext();
+ if (!bSameGraphics)
+ pSrcShared->applyXorContext();
+
+ SAL_WARN_IF (!pSrcShared->maLayer.isSet(), "vcl.quartz",
+ "AquaSalGraphics::copyBits() from non-layered graphics this=" << this);
+
+ const CGPoint aDstPoint = CGPointMake(+rPosAry.mnDestX - rPosAry.mnSrcX, rPosAry.mnDestY - rPosAry.mnSrcY);
+ if ((rPosAry.mnSrcWidth == rPosAry.mnDestWidth &&
+ rPosAry.mnSrcHeight == rPosAry.mnDestHeight) &&
+ (!mrShared.mnBitmapDepth || (aDstPoint.x + pSrcShared->mnWidth) <= mrShared.mnWidth)
+ && pSrcShared->maLayer.isSet()) // workaround for a Quartz crash
+ {
+ // in XOR mode the drawing context is redirected to the XOR mask
+ // if source and target are identical then copyBits() paints onto the target context though
+ CGContextHolder aCopyContext = mrShared.maContextHolder;
+ if (mrShared.mpXorEmulation && mrShared.mpXorEmulation->IsEnabled())
+ {
+ if (bSameGraphics)
+ {
+ aCopyContext.set(mrShared.mpXorEmulation->GetTargetContext());
+ }
+ }
+ aCopyContext.saveState();
+
+ const CGRect aDstRect = CGRectMake(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight);
+ CGContextClipToRect(aCopyContext.get(), aDstRect);
+
+ // draw at new destination
+ // NOTE: flipped drawing gets disabled for this, else the subimage would be drawn upside down
+ if (pSrcShared->isFlipped())
+ {
+ CGContextTranslateCTM(aCopyContext.get(), 0, +mrShared.mnHeight);
+ CGContextScaleCTM(aCopyContext.get(), +1, -1);
+ }
+
+ // TODO: pSrc->size() != this->size()
+ CGContextDrawLayerAtPoint(aCopyContext.get(), aDstPoint, pSrcShared->maLayer.get());
+
+ aCopyContext.restoreState();
+ // mark the destination rectangle as updated
+ refreshRect(aDstRect);
+ }
+ else
+ {
+ std::shared_ptr<SalBitmap> pBitmap;
+ if (pSrcGraphics)
+ pBitmap = pSrcGraphics->GetImpl()->getBitmap(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+ else
+ pBitmap = getBitmap(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+
+ if (pBitmap)
+ {
+ SalTwoRect aPosAry( rPosAry );
+ aPosAry.mnSrcX = 0;
+ aPosAry.mnSrcY = 0;
+ drawBitmap(aPosAry, *pBitmap);
+ }
+ }
+}
+
+void AquaGraphicsBackend::copyArea(tools::Long nDstX, tools::Long nDstY,tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight, bool /*bWindowInvalidate*/)
+{
+ SAL_WARN_IF (!mrShared.maLayer.isSet(), "vcl.quartz",
+ "AquaSalGraphics::copyArea() for non-layered graphics this=" << this);
+
+ if (!mrShared.maLayer.isSet())
+ return;
+
+ float fScale = mrShared.maLayer.getScale();
+
+ tools::Long nScaledSourceX = nSrcX * fScale;
+ tools::Long nScaledSourceY = nSrcY * fScale;
+
+ tools::Long nScaledTargetX = nDstX * fScale;
+ tools::Long nScaledTargetY = nDstY * fScale;
+
+ tools::Long nScaledSourceWidth = nSrcWidth * fScale;
+ tools::Long nScaledSourceHeight = nSrcHeight * fScale;
+
+ mrShared.applyXorContext();
+
+ mrShared.maContextHolder.saveState();
+
+ // in XOR mode the drawing context is redirected to the XOR mask
+ // copyArea() always works on the target context though
+ CGContextRef xCopyContext = mrShared.maContextHolder.get();
+
+ if (mrShared.mpXorEmulation && mrShared.mpXorEmulation->IsEnabled())
+ {
+ xCopyContext = mrShared.mpXorEmulation->GetTargetContext();
+ }
+
+ // If we have a scaled layer, we need to revert the scaling or else
+ // it will interfere with the coordinate calculation
+ CGContextScaleCTM(xCopyContext, 1.0 / fScale, 1.0 / fScale);
+
+ // drawing a layer onto its own context causes trouble on OSX => copy it first
+ // TODO: is it possible to get rid of this unneeded copy more often?
+ // e.g. on OSX>=10.5 only this situation causes problems:
+ // mnBitmapDepth && (aDstPoint.x + pSrc->mnWidth) > mnWidth
+
+ CGLayerHolder sSourceLayerHolder(mrShared.maLayer);
+ {
+ const CGSize aSrcSize = CGSizeMake(nScaledSourceWidth, nScaledSourceHeight);
+ sSourceLayerHolder.set(CGLayerCreateWithContext(xCopyContext, aSrcSize, nullptr));
+
+ const CGContextRef xSrcContext = CGLayerGetContext(sSourceLayerHolder.get());
+
+ CGPoint aSrcPoint = CGPointMake(-nScaledSourceX, -nScaledSourceY);
+ if (mrShared.isFlipped())
+ {
+ CGContextTranslateCTM(xSrcContext, 0, +nScaledSourceHeight);
+ CGContextScaleCTM(xSrcContext, +1, -1);
+ aSrcPoint.y = (nScaledSourceY + nScaledSourceHeight) - (mrShared.mnHeight * fScale);
+ }
+ CGContextSetBlendMode(xSrcContext, kCGBlendModeCopy);
+
+ CGContextDrawLayerAtPoint(xSrcContext, aSrcPoint, mrShared.maLayer.get());
+ }
+
+ // draw at new destination
+ const CGRect aTargetRect = CGRectMake(nScaledTargetX, nScaledTargetY, nScaledSourceWidth, nScaledSourceHeight);
+ CGContextSetBlendMode(xCopyContext, kCGBlendModeCopy);
+ CGContextDrawLayerInRect(xCopyContext, aTargetRect, sSourceLayerHolder.get());
+
+ mrShared.maContextHolder.restoreState();
+
+ // cleanup
+ if (sSourceLayerHolder.get() != mrShared.maLayer.get())
+ {
+ CGLayerRelease(sSourceLayerHolder.get());
+ }
+
+ // mark the destination rectangle as updated
+ mrShared.refreshRect(nDstX, nDstY, nSrcWidth, nSrcHeight);
+}
+
+void AquaSalGraphics::SetVirDevGraphics(SalVirtualDevice* pVirDev, CGLayerHolder const & rLayer, CGContextRef xContext,
+ int nBitmapDepth)
+{
+ SAL_INFO( "vcl.quartz", "SetVirDevGraphics() this=" << this << " layer=" << rLayer.get() << " context=" << xContext );
+
+ maShared.mbPrinter = false;
+ maShared.mbVirDev = true;
+
+ // set graphics properties
+ maShared.maLayer = rLayer;
+ maShared.maContextHolder.set(xContext);
+
+ maShared.mnBitmapDepth = nBitmapDepth;
+
+ maShared.mbForeignContext = xContext != NULL;
+
+ mpBackend->UpdateGeometryProvider(pVirDev);
+
+ // return early if the virdev is being destroyed
+ if (!xContext)
+ return;
+
+ // get new graphics properties
+ if (!maShared.maLayer.isSet())
+ {
+ maShared.mnWidth = CGBitmapContextGetWidth(maShared.maContextHolder.get());
+ maShared.mnHeight = CGBitmapContextGetHeight(maShared.maContextHolder.get());
+ }
+ else
+ {
+ const CGSize aSize = CGLayerGetSize(maShared.maLayer.get());
+ maShared.mnWidth = static_cast<int>(aSize.width);
+ maShared.mnHeight = static_cast<int>(aSize.height);
+ }
+
+ // prepare graphics for drawing
+ const CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
+ CGContextSetFillColorSpace(maShared.maContextHolder.get(), aCGColorSpace);
+ CGContextSetStrokeColorSpace(maShared.maContextHolder.get(), aCGColorSpace);
+
+ // re-enable XorEmulation for the new context
+ if (maShared.mpXorEmulation)
+ {
+ maShared.mpXorEmulation->SetTarget(maShared.mnWidth, maShared.mnHeight, maShared.mnBitmapDepth, maShared.maContextHolder.get(), maShared.maLayer.get());
+ if (maShared.mpXorEmulation->IsEnabled())
+ {
+ maShared.maContextHolder.set(maShared.mpXorEmulation->GetMaskContext());
+ }
+ }
+
+ // initialize stack of CGContext states
+ maShared.maContextHolder.saveState();
+ maShared.setState();
+}
+
+void XorEmulation::SetTarget( int nWidth, int nHeight, int nTargetDepth,
+ CGContextRef xTargetContext, CGLayerRef xTargetLayer )
+{
+ SAL_INFO( "vcl.quartz", "XorEmulation::SetTarget() this=" << this <<
+ " (" << nWidth << "x" << nHeight << ") depth=" << nTargetDepth <<
+ " context=" << xTargetContext << " layer=" << xTargetLayer );
+
+ // prepare to replace old mask+temp context
+ if( m_xMaskContext )
+ {
+ // cleanup the mask context
+ CGContextRelease( m_xMaskContext );
+ delete[] m_pMaskBuffer;
+ m_xMaskContext = nullptr;
+ m_pMaskBuffer = nullptr;
+
+ // cleanup the temp context if needed
+ if( m_xTempContext )
+ {
+ CGContextRelease( m_xTempContext );
+ delete[] m_pTempBuffer;
+ m_xTempContext = nullptr;
+ m_pTempBuffer = nullptr;
+ }
+ }
+
+ // return early if there is nothing more to do
+ if( !xTargetContext )
+ {
+ return;
+ }
+ // retarget drawing operations to the XOR mask
+ m_xTargetLayer = xTargetLayer;
+ m_xTargetContext = xTargetContext;
+
+ // prepare creation of matching CGBitmaps
+ CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
+ CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst;
+ int nBitDepth = nTargetDepth;
+ if( !nBitDepth )
+ {
+ nBitDepth = 32;
+ }
+ int nBytesPerRow = 4;
+ const size_t nBitsPerComponent = (nBitDepth == 16) ? 5 : 8;
+ if( nBitDepth <= 8 )
+ {
+ aCGColorSpace = GetSalData()->mxGraySpace;
+ aCGBmpInfo = kCGImageAlphaNone;
+ nBytesPerRow = 1;
+ }
+ nBytesPerRow *= nWidth;
+ m_nBufferLongs = (nHeight * nBytesPerRow + sizeof(sal_uLong)-1) / sizeof(sal_uLong);
+
+ // create a XorMask context
+ m_pMaskBuffer = new sal_uLong[ m_nBufferLongs ];
+ m_xMaskContext = CGBitmapContextCreate( m_pMaskBuffer,
+ nWidth, nHeight,
+ nBitsPerComponent, nBytesPerRow,
+ aCGColorSpace, aCGBmpInfo );
+ SAL_WARN_IF( !m_xMaskContext, "vcl.quartz", "mask context creation failed" );
+
+ // reset the XOR mask to black
+ memset( m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong) );
+
+ // a bitmap context will be needed for manual XORing
+ // create one unless the target context is a bitmap context
+ if( nTargetDepth )
+ {
+ m_pTempBuffer = static_cast<sal_uLong*>(CGBitmapContextGetData( m_xTargetContext ));
+ }
+ if( !m_pTempBuffer )
+ {
+ // create a bitmap context matching to the target context
+ m_pTempBuffer = new sal_uLong[ m_nBufferLongs ];
+ m_xTempContext = CGBitmapContextCreate( m_pTempBuffer,
+ nWidth, nHeight,
+ nBitsPerComponent, nBytesPerRow,
+ aCGColorSpace, aCGBmpInfo );
+ SAL_WARN_IF( !m_xTempContext, "vcl.quartz", "temp context creation failed" );
+ }
+
+ // initialize XOR mask context for drawing
+ CGContextSetFillColorSpace( m_xMaskContext, aCGColorSpace );
+ CGContextSetStrokeColorSpace( m_xMaskContext, aCGColorSpace );
+ CGContextSetShouldAntialias( m_xMaskContext, false );
+
+ // improve the XorMask's XOR emulation a little
+ // NOTE: currently only enabled for monochrome contexts
+ if( aCGColorSpace == GetSalData()->mxGraySpace )
+ {
+ CGContextSetBlendMode( m_xMaskContext, kCGBlendModeDifference );
+ }
+ // initialize the transformation matrix to the drawing target
+ const CGAffineTransform aCTM = CGContextGetCTM( xTargetContext );
+ CGContextConcatCTM( m_xMaskContext, aCTM );
+ if( m_xTempContext )
+ {
+ CGContextConcatCTM( m_xTempContext, aCTM );
+ }
+ // initialize the default XorMask graphics state
+ CGContextSaveGState( m_xMaskContext );
+}
+
+bool XorEmulation::UpdateTarget()
+{
+ SAL_INFO( "vcl.quartz", "XorEmulation::UpdateTarget() this=" << this );
+
+ if( !IsEnabled() )
+ {
+ return false;
+ }
+ // update the temp bitmap buffer if needed
+ if( m_xTempContext )
+ {
+ SAL_WARN_IF( m_xTargetContext == nullptr, "vcl.quartz", "Target layer is NULL");
+ CGContextDrawLayerAtPoint( m_xTempContext, CGPointZero, m_xTargetLayer );
+ }
+ // do a manual XOR with the XorMask
+ // this approach suffices for simple color manipulations
+ // and also the complex-clipping-XOR-trick used in metafiles
+ const sal_uLong* pSrc = m_pMaskBuffer;
+ sal_uLong* pDst = m_pTempBuffer;
+ for( int i = m_nBufferLongs; --i >= 0;)
+ {
+ *(pDst++) ^= *(pSrc++);
+ }
+ // write back the XOR results to the target context
+ if( m_xTempContext )
+ {
+ CGImageRef xXorImage = CGBitmapContextCreateImage( m_xTempContext );
+ const int nWidth = static_cast<int>(CGImageGetWidth( xXorImage ));
+ const int nHeight = static_cast<int>(CGImageGetHeight( xXorImage ));
+ // TODO: update minimal changerect
+ const CGRect aFullRect = CGRectMake(0, 0, nWidth, nHeight);
+ CGContextDrawImage( m_xTargetContext, aFullRect, xXorImage );
+ CGImageRelease( xXorImage );
+ }
+
+ // reset the XorMask to black again
+ // TODO: not needed for last update
+ memset( m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong) );
+
+ // TODO: return FALSE if target was not changed
+ return true;
+}
+
+/// From salvd.cxx
+
+void AquaSalVirtualDevice::Destroy()
+{
+ SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::Destroy() this=" << this << " mbForeignContext=" << mbForeignContext );
+
+ if( mbForeignContext )
+ {
+ // Do not delete mxContext that we have received from outside VCL
+ maLayer.set(nullptr);
+ return;
+ }
+
+ if (maLayer.isSet())
+ {
+ if( mpGraphics )
+ {
+ mpGraphics->SetVirDevGraphics(this, nullptr, nullptr);
+ }
+ CGLayerRelease(maLayer.get());
+ maLayer.set(nullptr);
+ }
+
+ if (maBitmapContext.isSet())
+ {
+ void* pRawData = CGBitmapContextGetData(maBitmapContext.get());
+ std::free(pRawData);
+ CGContextRelease(maBitmapContext.get());
+ maBitmapContext.set(nullptr);
+ }
+}
+
+bool AquaSalVirtualDevice::SetSize( tools::Long nDX, tools::Long nDY )
+{
+ SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
+ " (" << nDX << "x" << nDY << ") mbForeignContext=" << (mbForeignContext ? "YES" : "NO"));
+
+ if( mbForeignContext )
+ {
+ // Do not delete/resize mxContext that we have received from outside VCL
+ return true;
+ }
+
+ if (maLayer.isSet())
+ {
+ const CGSize aSize = CGLayerGetSize(maLayer.get());
+ if( (nDX == aSize.width) && (nDY == aSize.height) )
+ {
+ // Yay, we do not have to do anything :)
+ return true;
+ }
+ }
+
+ Destroy();
+
+ mnWidth = nDX;
+ mnHeight = nDY;
+
+ // create a CGLayer matching to the intended virdev usage
+ CGContextHolder xCGContextHolder;
+ if( mnBitmapDepth && (mnBitmapDepth < 16) )
+ {
+ mnBitmapDepth = 8; // TODO: are 1bit vdevs worth it?
+ const int nBytesPerRow = (mnBitmapDepth * nDX + 7) / 8;
+
+ void* pRawData = std::malloc( nBytesPerRow * nDY );
+ maBitmapContext.set(CGBitmapContextCreate( pRawData, nDX, nDY,
+ mnBitmapDepth, nBytesPerRow,
+ GetSalData()->mxGraySpace, kCGImageAlphaNone));
+ xCGContextHolder = maBitmapContext;
+ }
+ else
+ {
+ if (!xCGContextHolder.isSet())
+ {
+ // assert(Application::IsBitmapRendering());
+ mnBitmapDepth = 32;
+
+ const int nBytesPerRow = (mnBitmapDepth * nDX) / 8;
+ void* pRawData = std::malloc( nBytesPerRow * nDY );
+ const int nFlags = kCGImageAlphaNoneSkipFirst | kCGImageByteOrder32Little;
+ maBitmapContext.set(CGBitmapContextCreate(pRawData, nDX, nDY, 8, nBytesPerRow,
+ GetSalData()->mxRGBSpace, nFlags));
+ xCGContextHolder = maBitmapContext;
+ }
+ }
+
+ SAL_WARN_IF(!xCGContextHolder.isSet(), "vcl.quartz", "No context");
+
+ const CGSize aNewSize = { static_cast<CGFloat>(nDX), static_cast<CGFloat>(nDY) };
+ maLayer.set(CGLayerCreateWithContext(xCGContextHolder.get(), aNewSize, nullptr));
+
+ if (maLayer.isSet() && mpGraphics)
+ {
+ // get the matching Quartz context
+ CGContextRef xDrawContext = CGLayerGetContext( maLayer.get() );
+
+ // Here we pass the CGLayerRef that the CGLayerHolder maLayer holds as the first parameter
+ // to SetVirDevGraphics(). That parameter is of type CGLayerHolder, so what we actually pass
+ // is an implicitly constructed *separate* CGLayerHolder. Is that what we want? No idea.
+ // Possibly we could pass just maLayer as such? But doing that does not fix tdf#138122.
+ mpGraphics->SetVirDevGraphics(this, maLayer.get(), xDrawContext, mnBitmapDepth);
+ }
+
+ return maLayer.isSet();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/jsdialog/enabled.cxx b/vcl/jsdialog/enabled.cxx
new file mode 100644
index 0000000000..993a6f4568
--- /dev/null
+++ b/vcl/jsdialog/enabled.cxx
@@ -0,0 +1,395 @@
+/* -*- 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/string.hxx>
+#include <jsdialog/enabled.hxx>
+#include <vector>
+
+namespace jsdialog
+{
+bool isBuilderEnabled(std::u16string_view rUIFile, bool bMobile)
+{
+ // mobile only dialogs
+ if (bMobile)
+ {
+ if (// swriter
+ rUIFile == u"modules/swriter/ui/watermarkdialog.ui"
+ || rUIFile == u"modules/swriter/ui/wordcount-mobile.ui"
+ // svx
+ || rUIFile == u"svx/ui/findreplacedialog-mobile.ui")
+ {
+ return true;
+ }
+ }
+
+ if (// cui
+ rUIFile == u"cui/ui/areatabpage.ui"
+ || rUIFile == u"cui/ui/areadialog.ui"
+ || rUIFile == u"cui/ui/asiantypography.ui"
+ || rUIFile == u"cui/ui/borderpage.ui"
+ || rUIFile == u"cui/ui/bulletandposition.ui"
+ || rUIFile == u"cui/ui/cellalignment.ui"
+ || rUIFile == u"cui/ui/charnamepage.ui"
+ || rUIFile == u"cui/ui/colorpage.ui"
+ || rUIFile == u"cui/ui/colorpickerdialog.ui"
+ || rUIFile == u"cui/ui/croppage.ui"
+ || rUIFile == u"cui/ui/effectspage.ui"
+ || rUIFile == u"cui/ui/eventassigndialog.ui"
+ || rUIFile == u"cui/ui/fontfeaturesdialog.ui"
+ || rUIFile == u"cui/ui/formatcellsdialog.ui"
+ || rUIFile == u"cui/ui/formatnumberdialog.ui"
+ || rUIFile == u"cui/ui/gradientpage.ui"
+ || rUIFile == u"cui/ui/hatchpage.ui"
+ || rUIFile == u"cui/ui/hyperlinkdialog.ui"
+ || rUIFile == u"cui/ui/hyperlinkinternetpage.ui"
+ || rUIFile == u"cui/ui/hyperlinkmailpage.ui"
+ || rUIFile == u"cui/ui/imagetabpage.ui"
+ || rUIFile == u"cui/ui/linedialog.ui"
+ || rUIFile == u"cui/ui/lineendstabpage.ui"
+ || rUIFile == u"cui/ui/linestyletabpage.ui"
+ || rUIFile == u"cui/ui/linetabpage.ui"
+ || rUIFile == u"cui/ui/macroselectordialog.ui"
+ || rUIFile == u"cui/ui/numberingformatpage.ui"
+ || rUIFile == u"cui/ui/numberingoptionspage.ui"
+ || rUIFile == u"cui/ui/numberingpositionpage.ui"
+ || rUIFile == u"cui/ui/optlingupage.ui"
+ || rUIFile == u"cui/ui/pageformatpage.ui"
+ || rUIFile == u"cui/ui/paragalignpage.ui"
+ || rUIFile == u"cui/ui/paraindentspacing.ui"
+ || rUIFile == u"cui/ui/paratabspage.ui"
+ || rUIFile == u"cui/ui/password.ui"
+ || rUIFile == u"cui/ui/pastespecial.ui"
+ || rUIFile == u"cui/ui/patterntabpage.ui"
+ || rUIFile == u"cui/ui/pickbulletpage.ui"
+ || rUIFile == u"cui/ui/pickgraphicpage.ui"
+ || rUIFile == u"cui/ui/picknumberingpage.ui"
+ || rUIFile == u"cui/ui/pickoutlinepage.ui"
+ || rUIFile == u"cui/ui/positionpage.ui"
+ || rUIFile == u"cui/ui/positionsizedialog.ui"
+ || rUIFile == u"cui/ui/possizetabpage.ui"
+ || rUIFile == u"cui/ui/rotationtabpage.ui"
+ || rUIFile == u"cui/ui/shadowtabpage.ui"
+ || rUIFile == u"cui/ui/slantcornertabpage.ui"
+ || rUIFile == u"cui/ui/spinbox.ui"
+ || rUIFile == u"cui/ui/queryduplicatedialog.ui"
+ || rUIFile == u"cui/ui/similaritysearchdialog.ui"
+ || rUIFile == u"cui/ui/specialcharacters.ui"
+ || rUIFile == u"cui/ui/spellingdialog.ui"
+ || rUIFile == u"cui/ui/spelloptionsdialog.ui"
+ || rUIFile == u"cui/ui/splitcellsdialog.ui"
+ || rUIFile == u"cui/ui/textflowpage.ui"
+ || rUIFile == u"cui/ui/thesaurus.ui"
+ || rUIFile == u"cui/ui/transparencytabpage.ui"
+ || rUIFile == u"cui/ui/twolinespage.ui"
+ || rUIFile == u"cui/ui/widgettestdialog.ui"
+ || rUIFile == u"cui/ui/qrcodegen.ui"
+ // formula
+ || rUIFile == u"formula/ui/formuladialog.ui"
+ || rUIFile == u"formula/ui/functionpage.ui"
+ || rUIFile == u"formula/ui/parameter.ui"
+ || rUIFile == u"formula/ui/structpage.ui"
+ // scalc
+ || rUIFile == u"modules/scalc/ui/advancedfilterdialog.ui"
+ || rUIFile == u"modules/scalc/ui/analysisofvariancedialog.ui"
+ || rUIFile == u"modules/scalc/ui/chardialog.ui"
+ || rUIFile == u"modules/scalc/ui/chisquaretestdialog.ui"
+ || rUIFile == u"modules/scalc/ui/colwidthdialog.ui"
+ || rUIFile == u"modules/scalc/ui/conditionaleasydialog.ui"
+ || rUIFile == u"modules/scalc/ui/condformatmanager.ui"
+ || rUIFile == u"modules/scalc/ui/correlationdialog.ui"
+ || rUIFile == u"modules/scalc/ui/covariancedialog.ui"
+ || rUIFile == u"modules/scalc/ui/datafielddialog.ui"
+ || rUIFile == u"modules/scalc/ui/datafieldoptionsdialog.ui"
+ || rUIFile == u"modules/scalc/ui/definename.ui"
+ || rUIFile == u"modules/scalc/ui/deletecells.ui"
+ || rUIFile == u"modules/scalc/ui/deletecontents.ui"
+ || rUIFile == u"modules/scalc/ui/descriptivestatisticsdialog.ui"
+ || rUIFile == u"modules/scalc/ui/erroralerttabpage.ui"
+ || rUIFile == u"modules/scalc/ui/exponentialsmoothingdialog.ui"
+ || rUIFile == u"modules/scalc/ui/formatcellsdialog.ui"
+ || rUIFile == u"modules/scalc/ui/fourieranalysisdialog.ui"
+ || rUIFile == u"modules/scalc/ui/goalseekdlg.ui"
+ || rUIFile == u"modules/scalc/ui/groupdialog.ui"
+ || rUIFile == u"modules/scalc/ui/headerfootercontent.ui"
+ || rUIFile == u"modules/scalc/ui/headerfooterdialog.ui"
+ || rUIFile == u"modules/scalc/ui/insertcells.ui"
+ || rUIFile == u"modules/scalc/ui/managenamesdialog.ui"
+ || rUIFile == u"modules/scalc/ui/movingaveragedialog.ui"
+ || rUIFile == u"modules/scalc/ui/optimalcolwidthdialog.ui"
+ || rUIFile == u"modules/scalc/ui/optimalrowheightdialog.ui"
+ || rUIFile == u"modules/scalc/ui/pagetemplatedialog.ui"
+ || rUIFile == u"modules/scalc/ui/paratemplatedialog.ui"
+ || rUIFile == u"modules/scalc/ui/pivotfielddialog.ui"
+ || rUIFile == u"modules/scalc/ui/pivottablelayoutdialog.ui"
+ || rUIFile == u"modules/scalc/ui/regressiondialog.ui"
+ || rUIFile == u"modules/scalc/ui/rowheightdialog.ui"
+ || rUIFile == u"modules/scalc/ui/samplingdialog.ui"
+ || rUIFile == u"modules/scalc/ui/selectsource.ui"
+ || rUIFile == u"modules/scalc/ui/sheetprintpage.ui"
+ || rUIFile == u"modules/scalc/ui/simplerefdialog.ui"
+ || rUIFile == u"modules/scalc/ui/sortcriteriapage.ui"
+ || rUIFile == u"modules/scalc/ui/sortdialog.ui"
+ || rUIFile == u"modules/scalc/ui/sortkey.ui"
+ || rUIFile == u"modules/scalc/ui/sortoptionspage.ui"
+ || rUIFile == u"modules/scalc/ui/sparklinedialog.ui"
+ || rUIFile == u"modules/scalc/ui/standardfilterdialog.ui"
+ || rUIFile == u"modules/scalc/ui/textimportcsv.ui"
+ || rUIFile == u"modules/scalc/ui/ttestdialog.ui"
+ || rUIFile == u"modules/scalc/ui/ungroupdialog.ui"
+ || rUIFile == u"modules/scalc/ui/validationcriteriapage.ui"
+ || rUIFile == u"modules/scalc/ui/validationdialog.ui"
+ || rUIFile == u"modules/scalc/ui/validationhelptabpage.ui"
+ || rUIFile == u"modules/scalc/ui/ztestdialog.ui"
+ // schart
+ || rUIFile == u"modules/schart/ui/attributedialog.ui"
+ || rUIFile == u"modules/schart/ui/charttypedialog.ui"
+ || rUIFile == u"modules/schart/ui/datarangedialog.ui"
+ || rUIFile == u"modules/schart/ui/insertaxisdlg.ui"
+ || rUIFile == u"modules/schart/ui/inserttitledlg.ui"
+ || rUIFile == u"modules/schart/ui/smoothlinesdlg.ui"
+ || rUIFile == u"modules/schart/ui/steppedlinesdlg.ui"
+ || rUIFile == u"modules/schart/ui/tp_ChartType.ui"
+ || rUIFile == u"modules/schart/ui/tp_DataSource.ui"
+ || rUIFile == u"modules/schart/ui/tp_RangeChooser.ui"
+ || rUIFile == u"modules/schart/ui/tp_Trendline.ui"
+ || rUIFile == u"modules/schart/ui/wizelementspage.ui"
+ // sdraw
+ || rUIFile == u"modules/sdraw/ui/drawchardialog.ui"
+ || rUIFile == u"modules/sdraw/ui/drawpagedialog.ui"
+ || rUIFile == u"modules/sdraw/ui/drawparadialog.ui"
+ // simpress
+ || rUIFile == u"modules/simpress/ui/headerfooterdialog.ui"
+ || rUIFile == u"modules/simpress/ui/headerfootertab.ui"
+ // swriter
+ || rUIFile == u"modules/swriter/ui/bulletsandnumbering.ui"
+ || rUIFile == u"modules/swriter/ui/captionoptions.ui"
+ || rUIFile == u"modules/swriter/ui/characterproperties.ui"
+ || rUIFile == u"modules/swriter/ui/charurlpage.ui"
+ || rUIFile == u"modules/swriter/ui/columndialog.ui"
+ || rUIFile == u"modules/swriter/ui/columnpage.ui"
+ || rUIFile == u"modules/swriter/ui/contentcontroldlg.ui"
+ || rUIFile == u"modules/swriter/ui/contentcontrollistitemdlg.ui"
+ || rUIFile == u"modules/swriter/ui/dropcapspage.ui"
+ || rUIFile == u"modules/swriter/ui/dropdownfielddialog.ui"
+ || rUIFile == u"modules/swriter/ui/endnotepage.ui"
+ || rUIFile == u"modules/swriter/ui/footendnotedialog.ui"
+ || rUIFile == u"modules/swriter/ui/footnoteareapage.ui"
+ || rUIFile == u"modules/swriter/ui/footnotepage.ui"
+ || rUIFile == u"modules/swriter/ui/footnotesendnotestabpage.ui"
+ || rUIFile == u"modules/swriter/ui/formatsectiondialog.ui"
+ || rUIFile == u"modules/swriter/ui/formattablepage.ui"
+ || rUIFile == u"modules/swriter/ui/framedialog.ui"
+ || rUIFile == u"modules/swriter/ui/frmaddpage.ui"
+ || rUIFile == u"modules/swriter/ui/frmurlpage.ui"
+ || rUIFile == u"modules/swriter/ui/frmtypepage.ui"
+ || rUIFile == u"modules/swriter/ui/indentpage.ui"
+ || rUIFile == u"modules/swriter/ui/indexentry.ui"
+ || rUIFile == u"modules/swriter/ui/inforeadonlydialog.ui"
+ || rUIFile == u"modules/swriter/ui/insertbreak.ui"
+ || rUIFile == u"modules/swriter/ui/insertcaption.ui"
+ || rUIFile == u"modules/swriter/ui/insertsectiondialog.ui"
+ || rUIFile == u"modules/swriter/ui/linenumbering.ui"
+ || rUIFile == u"modules/swriter/ui/newuserindexdialog.ui"
+ || rUIFile == u"modules/swriter/ui/numparapage.ui"
+ || rUIFile == u"modules/swriter/ui/pagenumberdlg.ui"
+ || rUIFile == u"modules/swriter/ui/paradialog.ui"
+ || rUIFile == u"modules/swriter/ui/picturedialog.ui"
+ || rUIFile == u"modules/swriter/ui/picturepage.ui"
+ || rUIFile == u"modules/swriter/ui/sectionpage.ui"
+ || rUIFile == u"modules/swriter/ui/sortdialog.ui"
+ || rUIFile == u"modules/swriter/ui/splittable.ui"
+ || rUIFile == u"modules/swriter/ui/tablecolumnpage.ui"
+ || rUIFile == u"modules/swriter/ui/tableproperties.ui"
+ || rUIFile == u"modules/swriter/ui/tabletextflowpage.ui"
+ || rUIFile == u"modules/swriter/ui/templatedialog1.ui"
+ || rUIFile == u"modules/swriter/ui/templatedialog2.ui"
+ || rUIFile == u"modules/swriter/ui/templatedialog8.ui"
+ || rUIFile == u"modules/swriter/ui/textgridpage.ui"
+ || rUIFile == u"modules/swriter/ui/titlepage.ui"
+ || rUIFile == u"modules/swriter/ui/tocdialog.ui"
+ || rUIFile == u"modules/swriter/ui/tocentriespage.ui"
+ || rUIFile == u"modules/swriter/ui/tocindexpage.ui"
+ || rUIFile == u"modules/swriter/ui/tocstylespage.ui"
+ || rUIFile == u"modules/swriter/ui/translationdialog.ui"
+ || rUIFile == u"modules/swriter/ui/watermarkdialog.ui"
+ || rUIFile == u"modules/swriter/ui/wordcount.ui"
+ || rUIFile == u"modules/swriter/ui/wrappage.ui"
+ // sfx
+ || rUIFile == u"sfx/ui/cmisinfopage.ui"
+ || rUIFile == u"sfx/ui/custominfopage.ui"
+ || rUIFile == u"sfx/ui/descriptioninfopage.ui"
+ || rUIFile == u"sfx/ui/documentinfopage.ui"
+ || rUIFile == u"sfx/ui/documentpropertiesdialog.ui"
+ || rUIFile == u"sfx/ui/editdurationdialog.ui"
+ || rUIFile == u"svx/ui/headfootformatpage.ui"
+ || rUIFile == u"sfx/ui/linefragment.ui"
+ || rUIFile == u"sfx/ui/managestylepage.ui"
+ || rUIFile == u"sfx/ui/password.ui"
+ // svx
+ || rUIFile == u"svx/ui/acceptrejectchangesdialog.ui"
+ || rUIFile == u"svx/ui/accessibilitycheckdialog.ui"
+ || rUIFile == u"svx/ui/accessibilitycheckentry.ui"
+ || rUIFile == u"svx/ui/compressgraphicdialog.ui"
+ || rUIFile == u"svx/ui/findreplacedialog.ui"
+ || rUIFile == u"svx/ui/fontworkgallerydialog.ui"
+ || rUIFile == u"svx/ui/headfootformatpage.ui"
+ || rUIFile == u"svx/ui/redlinecontrol.ui"
+ || rUIFile == u"svx/ui/redlinefilterpage.ui"
+ || rUIFile == u"svx/ui/redlineviewpage.ui"
+ || rUIFile == u"svx/ui/themecoloreditdialog.ui"
+ || rUIFile == u"svx/ui/themedialog.ui"
+ // uui
+ || rUIFile == u"uui/ui/macrowarnmedium.ui"
+ // vcl
+ || rUIFile == u"vcl/ui/wizard.ui"
+ // filter
+ || rUIFile == u"filter/ui/pdfgeneralpage.ui"
+ || rUIFile == u"filter/ui/pdflinkspage.ui"
+ || rUIFile == u"filter/ui/pdfoptionsdialog.ui"
+ || rUIFile == u"filter/ui/pdfsecuritypage.ui"
+ || rUIFile == u"filter/ui/pdfsignpage.ui"
+ || rUIFile == u"filter/ui/pdfuserinterfacepage.ui"
+ || rUIFile == u"filter/ui/pdfviewpage.ui"
+ || rUIFile == u"filter/ui/warnpdfdialog.ui"
+ // writerperfect
+ || rUIFile == u"writerperfect/ui/exportepub.ui"
+ // xmlsec
+ || rUIFile == u"xmlsec/ui/certgeneral.ui"
+ || rUIFile == u"xmlsec/ui/certpage.ui"
+ || rUIFile == u"xmlsec/ui/digitalsignaturesdialog.ui"
+ || rUIFile == u"xmlsec/ui/viewcertdialog.ui"
+ )
+ {
+ return true;
+ }
+
+ const char* pEnabledDialog = getenv("SAL_JSDIALOG_ENABLE");
+ if (pEnabledDialog)
+ {
+ OUString sAllEnabledDialogs(pEnabledDialog, strlen(pEnabledDialog), RTL_TEXTENCODING_UTF8);
+ std::vector<OUString> aEnabledDialogsVector
+ = comphelper::string::split(sAllEnabledDialogs, ':');
+ for (const auto& rDialog : aEnabledDialogsVector)
+ {
+ if (rUIFile == rDialog)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool isBuilderEnabledForPopup(std::u16string_view rUIFile)
+{
+ if (// scalc
+ rUIFile == u"modules/scalc/ui/filterdropdown.ui"
+ || rUIFile == u"modules/scalc/ui/filterlist.ui"
+ || rUIFile == u"modules/scalc/ui/filtersubdropdown.ui"
+ || rUIFile == u"modules/scalc/ui/floatingborderstyle.ui"
+ || rUIFile == u"modules/scalc/ui/floatinglinestyle.ui"
+ // svt
+ || rUIFile == u"svt/ui/datewindow.ui"
+ || rUIFile == u"svt/ui/linewindow.ui"
+ // svx
+ || rUIFile == u"svx/ui/colorwindow.ui"
+ || rUIFile == u"svx/ui/currencywindow.ui"
+ || rUIFile == u"svx/ui/floatingareastyle.ui"
+ || rUIFile == u"svx/ui/floatinglineend.ui"
+ || rUIFile == u"svx/ui/floatinglineproperty.ui"
+ || rUIFile == u"svx/ui/floatinglinestyle.ui"
+ || rUIFile == u"svx/ui/fontworkalignmentcontrol.ui"
+ || rUIFile == u"svx/ui/fontworkcharacterspacingcontrol.ui"
+ || rUIFile == u"svx/ui/numberingwindow.ui"
+ || rUIFile == u"svx/ui/paralinespacingcontrol.ui"
+ || rUIFile == u"svx/ui/textcharacterspacingcontrol.ui"
+ || rUIFile == u"svx/ui/textunderlinecontrol.ui")
+ return true;
+
+ return false;
+}
+
+bool isBuilderEnabledForSidebar(std::u16string_view rUIFile)
+{
+ if (// scalc
+ rUIFile == u"modules/scalc/ui/functionpanel.ui"
+ || rUIFile == u"modules/scalc/ui/navigatorpanel.ui"
+ || rUIFile == u"modules/scalc/ui/sidebaralignment.ui"
+ || rUIFile == u"modules/scalc/ui/sidebarcellappearance.ui"
+ || rUIFile == u"modules/scalc/ui/sidebarnumberformat.ui"
+ // schart
+ || rUIFile == u"modules/schart/ui/sidebaraxis.ui"
+ || rUIFile == u"modules/schart/ui/sidebarelements.ui"
+ || rUIFile == u"modules/schart/ui/sidebarerrorbar.ui"
+ || rUIFile == u"modules/schart/ui/sidebarseries.ui"
+ || rUIFile == u"modules/schart/ui/sidebartype.ui"
+ // simpress
+ || rUIFile == u"modules/simpress/ui/customanimationfragment.ui"
+ || rUIFile == u"modules/simpress/ui/customanimationspanel.ui"
+ || rUIFile == u"modules/simpress/ui/layoutpanel.ui"
+ || rUIFile == u"modules/simpress/ui/masterpagepanel.ui"
+ || rUIFile == u"modules/simpress/ui/masterpagepanelall.ui"
+ || rUIFile == u"modules/simpress/ui/masterpagepanelrecent.ui"
+ || rUIFile == u"modules/simpress/ui/navigatorpanel.ui"
+ || rUIFile == u"modules/simpress/ui/sidebarslidebackground.ui"
+ || rUIFile == u"modules/simpress/ui/slidetransitionspanel.ui"
+ || rUIFile == u"modules/simpress/ui/tabledesignpanel.ui"
+ // smath
+ || rUIFile == u"modules/smath/ui/sidebarelements_math.ui"
+ || rUIFile == u"modules/smath/ui/sidebarproperties_math.ui"
+ // swriter
+ || rUIFile == u"modules/swriter/ui/managechangessidebar.ui"
+ || rUIFile == u"modules/swriter/ui/navigatorpanel.ui"
+ || rUIFile == u"modules/swriter/ui/pagefooterpanel.ui"
+ || rUIFile == u"modules/swriter/ui/pageformatpanel.ui"
+ || rUIFile == u"modules/swriter/ui/pageheaderpanel.ui"
+ || rUIFile == u"modules/swriter/ui/pagestylespanel.ui"
+ || rUIFile == u"modules/swriter/ui/sidebarstylepresets.ui"
+ || rUIFile == u"modules/swriter/ui/sidebartableedit.ui"
+ || rUIFile == u"modules/swriter/ui/sidebartheme.ui"
+ || rUIFile == u"modules/swriter/ui/sidebarwrap.ui"
+ // sfx
+ || rUIFile == u"sfx/ui/panel.ui"
+ || rUIFile == u"sfx/ui/templatepanel.ui"
+ // svx
+ || rUIFile == u"svx/ui/defaultshapespanel.ui"
+ || rUIFile == u"svx/ui/inspectortextpanel.ui"
+ || rUIFile == u"svx/ui/mediaplayback.ui"
+ || rUIFile == u"svx/ui/sidebararea.ui"
+ || rUIFile == u"svx/ui/sidebareffect.ui"
+ || rUIFile == u"svx/ui/sidebarempty.ui"
+ || rUIFile == u"svx/ui/sidebarfontwork.ui"
+ || rUIFile == u"svx/ui/sidebargallery.ui"
+ || rUIFile == u"svx/ui/sidebargraphic.ui"
+ || rUIFile == u"svx/ui/sidebarline.ui"
+ || rUIFile == u"svx/ui/sidebarlists.ui"
+ || rUIFile == u"svx/ui/sidebarparagraph.ui"
+ || rUIFile == u"svx/ui/sidebarpossize.ui"
+ || rUIFile == u"svx/ui/sidebarshadow.ui"
+ || rUIFile == u"svx/ui/sidebarstylespanel.ui"
+ || rUIFile == u"svx/ui/sidebartextpanel.ui")
+ return true;
+
+ return false;
+}
+
+bool isInterimBuilderEnabledForNotebookbar(std::u16string_view rUIFile)
+{
+ if (rUIFile == u"modules/scalc/ui/numberbox.ui"
+ || rUIFile == u"svx/ui/fontnamebox.ui"
+ || rUIFile == u"svx/ui/fontsizebox.ui"
+ || rUIFile == u"svx/ui/stylespreview.ui")
+ {
+ return true;
+ }
+
+ return false;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/jsdialog/executor.cxx b/vcl/jsdialog/executor.cxx
new file mode 100644
index 0000000000..7c36eb3bdc
--- /dev/null
+++ b/vcl/jsdialog/executor.cxx
@@ -0,0 +1,677 @@
+/* -*- 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 <jsdialog/jsdialogbuilder.hxx>
+#include <o3tl/string_view.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/jsdialog/executor.hxx>
+#include <sal/log.hxx>
+#include <rtl/uri.hxx>
+#include <boost/property_tree/json_parser.hpp>
+
+namespace jsdialog
+{
+StringMap jsonToStringMap(const char* pJSON)
+{
+ StringMap aArgs;
+ if (pJSON && pJSON[0] != '\0')
+ {
+ std::stringstream aStream(pJSON);
+ boost::property_tree::ptree aTree;
+ boost::property_tree::read_json(aStream, aTree);
+
+ for (const auto& rPair : aTree)
+ {
+ aArgs[OUString::fromUtf8(rPair.first)]
+ = OUString::fromUtf8(rPair.second.get_value<std::string>("."));
+ }
+ }
+ return aArgs;
+}
+
+void SendFullUpdate(const OUString& nWindowId, const OUString& rWidget)
+{
+ weld::Widget* pWidget = JSInstanceBuilder::FindWeldWidgetsMap(nWindowId, rWidget);
+ if (auto pJSWidget = dynamic_cast<BaseJSWidget*>(pWidget))
+ pJSWidget->sendFullUpdate();
+}
+
+void SendAction(const OUString& nWindowId, const OUString& rWidget,
+ std::unique_ptr<ActionDataMap> pData)
+{
+ weld::Widget* pWidget = JSInstanceBuilder::FindWeldWidgetsMap(nWindowId, rWidget);
+ if (auto pJSWidget = dynamic_cast<BaseJSWidget*>(pWidget))
+ pJSWidget->sendAction(std::move(pData));
+}
+
+bool ExecuteAction(const OUString& nWindowId, const OUString& rWidget, StringMap& rData)
+{
+ weld::Widget* pWidget = JSInstanceBuilder::FindWeldWidgetsMap(nWindowId, rWidget);
+
+ OUString sControlType = rData["type"];
+ OUString sAction = rData["cmd"];
+
+ if (sControlType == "responsebutton")
+ {
+ auto pButton = dynamic_cast<weld::Button*>(pWidget);
+ if (pWidget == nullptr || (pButton && !pButton->is_custom_handler_set()))
+ {
+ // welded wrapper not found - use response code instead
+ pWidget = JSInstanceBuilder::FindWeldWidgetsMap(nWindowId, "__DIALOG__");
+ sControlType = "dialog";
+ sAction = "response";
+ }
+ else
+ {
+ // welded wrapper for button found - use it
+ sControlType = "pushbutton";
+ }
+ }
+
+ if (pWidget != nullptr)
+ {
+ if (sAction == "grab_focus")
+ {
+ pWidget->grab_focus();
+ return true;
+ }
+
+ if (sControlType == "tabcontrol")
+ {
+ auto pNotebook = dynamic_cast<weld::Notebook*>(pWidget);
+ if (pNotebook)
+ {
+ if (sAction == "selecttab")
+ {
+ sal_Int32 page = o3tl::toInt32(rData["data"]);
+
+ OUString aCurrentPage = pNotebook->get_current_page_ident();
+ LOKTrigger::leave_page(*pNotebook, aCurrentPage);
+ pNotebook->set_current_page(page);
+ LOKTrigger::enter_page(*pNotebook, pNotebook->get_page_ident(page));
+
+ return true;
+ }
+ }
+ }
+ else if (sControlType == "combobox")
+ {
+ auto pCombobox = dynamic_cast<weld::ComboBox*>(pWidget);
+ if (pCombobox)
+ {
+ if (sAction == "selected")
+ {
+ OUString sSelectedData = rData["data"];
+ int separatorPos = sSelectedData.indexOf(';');
+ if (separatorPos > 0)
+ {
+ std::u16string_view entryPos = sSelectedData.subView(0, separatorPos);
+ sal_Int32 pos = o3tl::toInt32(entryPos);
+ pCombobox->set_active(pos);
+ LOKTrigger::trigger_changed(*pCombobox);
+ return true;
+ }
+ }
+ else if (sAction == "change")
+ {
+ // it might be other class than JSComboBox
+ auto pJSCombobox = dynamic_cast<JSComboBox*>(pWidget);
+ if (pJSCombobox)
+ pJSCombobox->set_entry_text_without_notify(rData["data"]);
+ else
+ pCombobox->set_entry_text(rData["data"]);
+ LOKTrigger::trigger_changed(*pCombobox);
+ return true;
+ }
+ else if (sAction == "render_entry")
+ {
+ auto pJSCombobox = dynamic_cast<JSComboBox*>(pWidget);
+ if (pJSCombobox)
+ {
+ // pos;dpix;dpiy
+ const OUString& sParams = rData["data"];
+ const OUString aPos = sParams.getToken(0, ';');
+ const OUString aDpiScaleX = sParams.getToken(1, ';');
+ const OUString aDpiScaleY = sParams.getToken(2, ';');
+
+ pJSCombobox->render_entry(o3tl::toInt32(aPos), o3tl::toInt32(aDpiScaleX),
+ o3tl::toInt32(aDpiScaleY));
+ }
+ return true;
+ }
+ }
+ }
+ else if (sControlType == "pushbutton")
+ {
+ auto pButton = dynamic_cast<weld::Button*>(pWidget);
+ if (pButton)
+ {
+ if (sAction == "click")
+ {
+ pButton->clicked();
+ return true;
+ }
+ else if (sAction == "toggle")
+ {
+ LOKTrigger::trigger_toggled(dynamic_cast<weld::Toggleable&>(*pWidget));
+ return true;
+ }
+ }
+ }
+ else if (sControlType == "menubutton")
+ {
+ auto pButton = dynamic_cast<weld::MenuButton*>(pWidget);
+ if (pButton)
+ {
+ if (sAction == "toggle")
+ {
+ if (pButton->get_active())
+ pButton->set_active(false);
+ else
+ pButton->set_active(true);
+
+ BaseJSWidget* pMenuButton = dynamic_cast<BaseJSWidget*>(pButton);
+ if (pMenuButton)
+ pMenuButton->sendUpdate(true);
+
+ return true;
+ }
+ else if (sAction == "select")
+ {
+ LOKTrigger::trigger_selected(*pButton, rData["data"]);
+ return true;
+ }
+ }
+ }
+ else if (sControlType == "checkbox")
+ {
+ auto pCheckButton = dynamic_cast<weld::CheckButton*>(pWidget);
+ if (pCheckButton)
+ {
+ if (sAction == "change")
+ {
+ bool bChecked = rData["data"] == "true";
+ pCheckButton->set_state(bChecked ? TRISTATE_TRUE : TRISTATE_FALSE);
+ LOKTrigger::trigger_toggled(*static_cast<weld::Toggleable*>(pCheckButton));
+ return true;
+ }
+ }
+ }
+ else if (sControlType == "drawingarea")
+ {
+ auto pArea = dynamic_cast<weld::DrawingArea*>(pWidget);
+ if (pArea)
+ {
+ if (sAction == "click" || sAction == "dblclick" || sAction == "mousemove"
+ || sAction == "mousedown" || sAction == "mouseup")
+ {
+ OUString sClickData = rData["data"];
+ int nSeparatorPos = sClickData.indexOf(';');
+ if (nSeparatorPos > 0)
+ {
+ // x;y
+ std::u16string_view nClickPosX = sClickData.subView(0, nSeparatorPos);
+ std::u16string_view nClickPosY = sClickData.subView(nSeparatorPos + 1);
+
+ if (nClickPosX.empty() || nClickPosY.empty())
+ return true;
+
+ double fPosX = o3tl::toDouble(nClickPosX);
+ double fPosY = o3tl::toDouble(nClickPosY);
+ OutputDevice& rRefDevice = pArea->get_ref_device();
+ // We send OutPutSize for the drawing area bitmap
+ // get_size_request is not necessarily updated
+ // therefore it may be incorrect.
+ Size size = rRefDevice.GetOutputSizePixel();
+ fPosX = fPosX * size.Width();
+ fPosY = fPosY * size.Height();
+
+ if (sAction == "click")
+ LOKTrigger::trigger_click(*pArea, Point(fPosX, fPosY));
+ else if (sAction == "dblclick")
+ LOKTrigger::trigger_dblclick(*pArea, Point(fPosX, fPosY));
+ else if (sAction == "mouseup")
+ LOKTrigger::trigger_mouse_up(*pArea, Point(fPosX, fPosY));
+ else if (sAction == "mousedown")
+ LOKTrigger::trigger_mouse_down(*pArea, Point(fPosX, fPosY));
+ else if (sAction == "mousemove")
+ LOKTrigger::trigger_mouse_move(*pArea, Point(fPosX, fPosY));
+ }
+
+ return true;
+ }
+ else if (sAction == "keypress")
+ {
+ sal_uInt32 nKeyNo = rData["data"].toUInt32();
+ LOKTrigger::trigger_key_press(*pArea, KeyEvent(nKeyNo, vcl::KeyCode(nKeyNo)));
+ LOKTrigger::trigger_key_release(*pArea, KeyEvent(nKeyNo, vcl::KeyCode(nKeyNo)));
+ return true;
+ }
+ else if (sAction == "textselection")
+ {
+ OUString sTextData = rData["data"];
+ int nSeparatorPos = sTextData.indexOf(';');
+ if (nSeparatorPos <= 0)
+ return true;
+
+ int nSeparator2Pos = sTextData.indexOf(';', nSeparatorPos + 1);
+ int nSeparator3Pos = 0;
+
+ if (nSeparator2Pos > 0)
+ {
+ // start;end;startPara;endPara
+ nSeparator3Pos = sTextData.indexOf(';', nSeparator2Pos + 1);
+ if (nSeparator3Pos <= 0)
+ return true;
+ }
+ else
+ {
+ // start;end
+ nSeparator2Pos = 0;
+ nSeparator3Pos = 0;
+ }
+
+ std::u16string_view aStartPos = sTextData.subView(0, nSeparatorPos);
+ std::u16string_view aEndPos
+ = sTextData.subView(nSeparatorPos + 1, nSeparator2Pos - nSeparatorPos + 1);
+
+ if (aStartPos.empty() || aEndPos.empty())
+ return true;
+
+ sal_Int32 nStart = o3tl::toInt32(aStartPos);
+ sal_Int32 nEnd = o3tl::toInt32(aEndPos);
+ sal_Int32 nStartPara = 0;
+ sal_Int32 nEndPara = 0;
+
+ // multiline case
+ if (nSeparator2Pos && nSeparator3Pos)
+ {
+ std::u16string_view aStartPara = sTextData.subView(
+ nSeparator2Pos + 1, nSeparator3Pos - nSeparator2Pos + 1);
+ std::u16string_view aEndPara = sTextData.subView(nSeparator3Pos + 1);
+
+ if (aStartPara.empty() || aEndPara.empty())
+ return true;
+
+ nStartPara = o3tl::toInt32(aStartPara);
+ nEndPara = o3tl::toInt32(aEndPara);
+ }
+
+ // pass information about paragraph number in the additional data
+ // handled in sc/source/ui/app/inputwin.cxx
+ Point* pParaPoint = new Point(nStartPara, nEndPara);
+ const void* pCmdData = pParaPoint;
+
+ Point aPos(nStart, nEnd);
+ CommandEvent aCEvt(aPos, CommandEventId::CursorPos, false, pCmdData);
+ LOKTrigger::command(*pArea, aCEvt);
+
+ return true;
+ }
+ }
+ }
+ else if (sControlType == "spinfield")
+ {
+ auto pSpinField = dynamic_cast<weld::SpinButton*>(pWidget);
+ if (pSpinField)
+ {
+ if (sAction == "change" || sAction == "value")
+ {
+ if (rData["data"] == "undefined")
+ return true;
+
+ // The Document will not scroll if that is in focus
+ // maybe we could send a message with: sAction == "grab_focus"
+ pWidget->grab_focus();
+
+ double nValue = o3tl::toDouble(rData["data"]);
+ pSpinField->set_value(nValue
+ * weld::SpinButton::Power10(pSpinField->get_digits()));
+ LOKTrigger::trigger_value_changed(*pSpinField);
+ return true;
+ }
+ if (sAction == "plus")
+ {
+ pSpinField->set_value(pSpinField->get_value() + 1);
+ LOKTrigger::trigger_value_changed(*pSpinField);
+ return true;
+ }
+ else if (sAction == "minus")
+ {
+ pSpinField->set_value(pSpinField->get_value() - 1);
+ LOKTrigger::trigger_value_changed(*pSpinField);
+ return true;
+ }
+ }
+
+ auto pFormattedField = dynamic_cast<weld::FormattedSpinButton*>(pWidget);
+ if (pFormattedField)
+ {
+ if (sAction == "change")
+ {
+ pFormattedField->set_text(rData["data"]);
+ LOKTrigger::trigger_changed(*pFormattedField);
+ LOKTrigger::trigger_value_changed(*pFormattedField);
+ return true;
+ }
+ }
+ }
+ else if (sControlType == "toolbox")
+ {
+ auto pToolbar = dynamic_cast<weld::Toolbar*>(pWidget);
+ if (pToolbar)
+ {
+ if (sAction == "click")
+ {
+ LOKTrigger::trigger_clicked(*pToolbar, rData["data"]);
+ return true;
+ }
+ else if (sAction == "togglemenu")
+ {
+ const OUString& sId = rData["data"];
+ bool bIsActive = pToolbar->get_menu_item_active(sId);
+ pToolbar->set_menu_item_active(sId, !bIsActive);
+ return true;
+ }
+ else if (sAction == "closemenu")
+ {
+ pToolbar->set_menu_item_active(rData["data"], false);
+ return true;
+ }
+ else if (sAction == "openmenu")
+ {
+ pToolbar->set_menu_item_active(rData["data"], true);
+ return true;
+ }
+ }
+ }
+ else if (sControlType == "edit")
+ {
+ auto pEdit = dynamic_cast<JSEntry*>(pWidget);
+ if (pEdit)
+ {
+ if (sAction == "change")
+ {
+ pEdit->set_text_without_notify(rData["data"]);
+ LOKTrigger::trigger_changed(*pEdit);
+ return true;
+ }
+ }
+
+ auto pTextView = dynamic_cast<JSTextView*>(pWidget);
+ if (pTextView)
+ {
+ if (sAction == "change")
+ {
+ int rStartPos, rEndPos;
+ pTextView->get_selection_bounds(rStartPos, rEndPos);
+ pTextView->set_text_without_notify(rData["data"]);
+ pTextView->select_region(rStartPos, rEndPos);
+ LOKTrigger::trigger_changed(*pTextView);
+ return true;
+ }
+ else if (sAction == "textselection")
+ {
+ // start;end
+ OUString sTextData = rData["data"];
+ int nSeparatorPos = sTextData.indexOf(';');
+ if (nSeparatorPos <= 0)
+ return true;
+
+ std::u16string_view aStartPos = sTextData.subView(0, nSeparatorPos);
+ std::u16string_view aEndPos = sTextData.subView(nSeparatorPos + 1);
+
+ if (aStartPos.empty() || aEndPos.empty())
+ return true;
+
+ sal_Int32 nStart = o3tl::toInt32(aStartPos);
+ sal_Int32 nEnd = o3tl::toInt32(aEndPos);
+
+ pTextView->select_region(nStart, nEnd);
+ LOKTrigger::trigger_changed(*pTextView);
+
+ return true;
+ }
+ }
+ }
+ else if (sControlType == "treeview")
+ {
+ auto pTreeView = dynamic_cast<JSTreeView*>(pWidget);
+ if (pTreeView)
+ {
+ if (sAction == "change")
+ {
+ OUString sDataJSON = rtl::Uri::decode(
+ rData["data"], rtl_UriDecodeMechanism::rtl_UriDecodeWithCharset,
+ RTL_TEXTENCODING_UTF8);
+ StringMap aMap(jsonToStringMap(
+ OUStringToOString(sDataJSON, RTL_TEXTENCODING_ASCII_US).getStr()));
+
+ sal_Int32 nRow = o3tl::toInt32(aMap["row"]);
+ bool bValue = aMap["value"] == "true";
+
+ pTreeView->set_toggle(nRow, bValue ? TRISTATE_TRUE : TRISTATE_FALSE);
+
+ return true;
+ }
+ else if (sAction == "select")
+ {
+ sal_Int32 nAbsPos = o3tl::toInt32(rData["data"]);
+
+ pTreeView->unselect_all();
+
+ std::unique_ptr<weld::TreeIter> itEntry(pTreeView->make_iterator());
+ if (pTreeView->get_iter_abs_pos(*itEntry, nAbsPos))
+ {
+ pTreeView->select(*itEntry);
+ pTreeView->set_cursor_without_notify(*itEntry);
+ }
+ else
+ SAL_WARN("vcl",
+ "No absolute position found for " << nAbsPos << " in treeview");
+ pTreeView->grab_focus();
+ LOKTrigger::trigger_changed(*pTreeView);
+ return true;
+ }
+ else if (sAction == "activate")
+ {
+ sal_Int32 nRow = o3tl::toInt32(rData["data"]);
+
+ pTreeView->unselect_all();
+ std::unique_ptr<weld::TreeIter> itEntry(pTreeView->make_iterator());
+ if (pTreeView->get_iter_abs_pos(*itEntry, nRow))
+ {
+ pTreeView->select(nRow);
+ pTreeView->set_cursor_without_notify(*itEntry);
+ }
+ else
+ SAL_WARN("vcl",
+ "No absolute position found for " << nRow << " in treeview");
+ pTreeView->grab_focus();
+ LOKTrigger::trigger_changed(*pTreeView);
+ LOKTrigger::trigger_row_activated(*pTreeView);
+ return true;
+ }
+ else if (sAction == "expand")
+ {
+ sal_Int32 nAbsPos = o3tl::toInt32(rData["data"]);
+ std::unique_ptr<weld::TreeIter> itEntry(pTreeView->make_iterator());
+ if (pTreeView->get_iter_abs_pos(*itEntry, nAbsPos))
+ {
+ pTreeView->set_cursor_without_notify(*itEntry);
+ pTreeView->grab_focus();
+ pTreeView->expand_row(*itEntry);
+ }
+ else
+ SAL_WARN("vcl",
+ "No absolute position found for " << nAbsPos << " in treeview");
+ return true;
+ }
+ else if (sAction == "collapse")
+ {
+ sal_Int32 nAbsPos = o3tl::toInt32(rData["data"]);
+ std::unique_ptr<weld::TreeIter> itEntry(pTreeView->make_iterator());
+ if (pTreeView->get_iter_abs_pos(*itEntry, nAbsPos))
+ {
+ pTreeView->set_cursor_without_notify(*itEntry);
+ pTreeView->grab_focus();
+ pTreeView->collapse_row(*itEntry);
+ }
+ else
+ SAL_WARN("vcl",
+ "No absolute position found for " << nAbsPos << " in treeview");
+ return true;
+ }
+ else if (sAction == "dragstart")
+ {
+ sal_Int32 nRow = o3tl::toInt32(rData["data"]);
+
+ pTreeView->select(nRow);
+ pTreeView->drag_start();
+
+ return true;
+ }
+ else if (sAction == "dragend")
+ {
+ pTreeView->drag_end();
+ return true;
+ }
+ }
+ }
+ else if (sControlType == "iconview")
+ {
+ auto pIconView = dynamic_cast<weld::IconView*>(pWidget);
+ if (pIconView)
+ {
+ if (sAction == "select")
+ {
+ sal_Int32 nPos = o3tl::toInt32(rData["data"]);
+
+ pIconView->select(nPos);
+ LOKTrigger::trigger_changed(*pIconView);
+
+ return true;
+ }
+ else if (sAction == "activate")
+ {
+ sal_Int32 nPos = o3tl::toInt32(rData["data"]);
+
+ pIconView->select(nPos);
+ LOKTrigger::trigger_changed(*pIconView);
+ LOKTrigger::trigger_item_activated(*pIconView);
+
+ return true;
+ }
+ }
+ }
+ else if (sControlType == "expander")
+ {
+ auto pExpander = dynamic_cast<weld::Expander*>(pWidget);
+ if (pExpander)
+ {
+ if (sAction == "toggle")
+ {
+ pExpander->set_expanded(!pExpander->get_expanded());
+ return true;
+ }
+ }
+ }
+ else if (sControlType == "dialog")
+ {
+ auto pDialog = dynamic_cast<weld::Dialog*>(pWidget);
+ if (pDialog)
+ {
+ if (sAction == "close")
+ {
+ pDialog->response(RET_CANCEL);
+ return true;
+ }
+ else if (sAction == "response")
+ {
+ sal_Int32 nResponse = o3tl::toInt32(rData["data"]);
+ pDialog->response(nResponse);
+ return true;
+ }
+ }
+ }
+ else if (sControlType == "popover")
+ {
+ auto pPopover = dynamic_cast<weld::Popover*>(pWidget);
+ if (pPopover)
+ {
+ if (sAction == "close")
+ {
+ LOKTrigger::trigger_closed(*pPopover);
+ return true;
+ }
+ }
+ }
+ else if (sControlType == "radiobutton")
+ {
+ auto pRadioButton = dynamic_cast<weld::RadioButton*>(pWidget);
+ if (pRadioButton)
+ {
+ if (sAction == "change")
+ {
+ bool bChecked = rData["data"] == "true";
+ pRadioButton->set_state(bChecked ? TRISTATE_TRUE : TRISTATE_FALSE);
+ LOKTrigger::trigger_toggled(*static_cast<weld::Toggleable*>(pRadioButton));
+ return true;
+ }
+ }
+ }
+ else if (sControlType == "scrolledwindow")
+ {
+ auto pScrolledWindow = dynamic_cast<JSScrolledWindow*>(pWidget);
+ if (pScrolledWindow)
+ {
+ if (sAction == "scrollv")
+ {
+ sal_Int32 nValue = o3tl::toInt32(rData["data"]);
+ pScrolledWindow->vadjustment_set_value_no_notification(nValue);
+ LOKTrigger::trigger_scrollv(*pScrolledWindow);
+ return true;
+ }
+ else if (sAction == "scrollh")
+ {
+ sal_Int32 nValue = o3tl::toInt32(rData["data"]);
+ pScrolledWindow->hadjustment_set_value_no_notification(nValue);
+ LOKTrigger::trigger_scrollh(*pScrolledWindow);
+ return true;
+ }
+ }
+ }
+ else if (sControlType == "calendar")
+ {
+ auto pCalendar = dynamic_cast<weld::Calendar*>(pWidget);
+ if (pCalendar && sAction == "selectdate")
+ {
+ // MM/DD/YYYY
+ OUString aDate = rData["data"];
+
+ if (aDate.getLength() < 10)
+ return false;
+
+ sal_Int32 aMonth = o3tl::toInt32(aDate.subView(0, 2));
+ sal_Int32 aDay = o3tl::toInt32(aDate.subView(3, 2));
+ sal_Int32 aYear = o3tl::toInt32(aDate.subView(6, 4));
+
+ pCalendar->set_date(Date(aDay, aMonth, aYear));
+ LOKTrigger::trigger_selected(*pCalendar);
+ LOKTrigger::trigger_activated(*pCalendar);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/jsdialog/jsdialogbuilder.cxx b/vcl/jsdialog/jsdialogbuilder.cxx
new file mode 100644
index 0000000000..48946baa15
--- /dev/null
+++ b/vcl/jsdialog/jsdialogbuilder.cxx
@@ -0,0 +1,2370 @@
+/* -*- 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 <jsdialog/jsdialogbuilder.hxx>
+#include <sal/log.hxx>
+#include <comphelper/base64.hxx>
+#include <comphelper/lok.hxx>
+#include <utility>
+#include <vcl/tabpage.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/toolkit/combobox.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/toolkit/treelistentry.hxx>
+#include <vcl/toolkit/vclmedit.hxx>
+#include <verticaltabctrl.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <messagedialog.hxx>
+#include <tools/json_writer.hxx>
+#include <o3tl/deleter.hxx>
+#include <memory>
+#include <boost/property_tree/json_parser.hpp>
+#include <vcl/jsdialog/executor.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+#include <tools/stream.hxx>
+
+#include <vcl/cvtgrf.hxx>
+
+#include <wizdlg.hxx>
+
+static std::map<OUString, vcl::Window*>& GetLOKPopupsMap()
+{
+ // Map to remember the LOKWindowId <-> vcl popup binding.
+ static std::map<OUString, vcl::Window*> s_aLOKPopupsMap;
+
+ return s_aLOKPopupsMap;
+}
+
+namespace
+{
+void response_help(vcl::Window* pWindow)
+{
+ ::Dialog* pDialog = dynamic_cast<::Dialog*>(pWindow);
+ if (!pDialog)
+ return;
+
+ vcl::Window* pButtonWindow = pDialog->get_widget_for_response(RET_HELP);
+ ::Button* pButton = dynamic_cast<::Button*>(pButtonWindow);
+ if (!pButton)
+ {
+ // Is it a wizard dialog?
+ vcl::RoadmapWizard* pWizard = dynamic_cast<vcl::RoadmapWizard*>(pWindow);
+ if (!pWizard || !pWizard->m_pHelp)
+ return;
+ pWizard->m_pHelp->Click();
+ return;
+ }
+
+ pButton->Click();
+}
+}
+
+JSDialogNotifyIdle::JSDialogNotifyIdle(VclPtr<vcl::Window> aNotifierWindow,
+ VclPtr<vcl::Window> aContentWindow,
+ const OUString& sTypeOfJSON)
+ : Idle("JSDialog notify")
+ , m_aNotifierWindow(std::move(aNotifierWindow))
+ , m_aContentWindow(std::move(aContentWindow))
+ , m_sTypeOfJSON(sTypeOfJSON)
+ , m_bForce(false)
+{
+ SetPriority(TaskPriority::POST_PAINT);
+}
+
+void JSDialogNotifyIdle::forceUpdate() { m_bForce = true; }
+
+void JSDialogNotifyIdle::send(tools::JsonWriter& aJsonWriter)
+{
+ if (!m_aNotifierWindow)
+ {
+ aJsonWriter.finishAndGetAsOString();
+ return;
+ }
+
+ const vcl::ILibreOfficeKitNotifier* pNotifier = m_aNotifierWindow->GetLOKNotifier();
+ if (pNotifier)
+ {
+ if (m_bForce || !aJsonWriter.isDataEquals(m_LastNotificationMessage))
+ {
+ m_bForce = false;
+ m_LastNotificationMessage = aJsonWriter.finishAndGetAsOString();
+ pNotifier->libreOfficeKitViewCallback(LOK_CALLBACK_JSDIALOG, m_LastNotificationMessage);
+ }
+ else
+ {
+ aJsonWriter.finishAndGetAsOString();
+ }
+ }
+ else
+ {
+ aJsonWriter.finishAndGetAsOString();
+ }
+}
+
+void JSDialogNotifyIdle::sendMessage(jsdialog::MessageType eType, VclPtr<vcl::Window> pWindow,
+ std::unique_ptr<jsdialog::ActionDataMap> pData)
+{
+ std::scoped_lock aGuard(m_aQueueMutex);
+
+ // we want only the latest update of same type
+ // TODO: also if we met full update - previous updates are not valid
+ auto it = m_aMessageQueue.begin();
+
+ while (it != m_aMessageQueue.end())
+ {
+ if (it->m_eType == eType && it->m_pWindow == pWindow)
+ {
+ // actions should be always sent, eg. rendering of custom entries in combobox
+ if (eType == jsdialog::MessageType::Action)
+ {
+ it++;
+ continue;
+ }
+ it = m_aMessageQueue.erase(it);
+ }
+ else
+ it++;
+ }
+
+ JSDialogMessageInfo aMessage(eType, pWindow, std::move(pData));
+ m_aMessageQueue.push_back(aMessage);
+}
+
+std::unique_ptr<tools::JsonWriter> JSDialogNotifyIdle::generateFullUpdate() const
+{
+ std::unique_ptr<tools::JsonWriter> aJsonWriter(new tools::JsonWriter());
+
+ if (!m_aContentWindow || !m_aNotifierWindow)
+ return aJsonWriter;
+
+ m_aContentWindow->DumpAsPropertyTree(*aJsonWriter);
+ if (m_aNotifierWindow)
+ aJsonWriter->put("id", m_aNotifierWindow->GetLOKWindowId());
+ aJsonWriter->put("jsontype", m_sTypeOfJSON);
+
+ return aJsonWriter;
+}
+
+std::unique_ptr<tools::JsonWriter>
+JSDialogNotifyIdle::generateWidgetUpdate(VclPtr<vcl::Window> pWindow) const
+{
+ std::unique_ptr<tools::JsonWriter> aJsonWriter(new tools::JsonWriter());
+
+ if (!pWindow || !m_aNotifierWindow)
+ return aJsonWriter;
+
+ aJsonWriter->put("jsontype", m_sTypeOfJSON);
+ aJsonWriter->put("action", "update");
+ if (m_aNotifierWindow)
+ aJsonWriter->put("id", m_aNotifierWindow->GetLOKWindowId());
+ {
+ auto aEntries = aJsonWriter->startNode("control");
+ pWindow->DumpAsPropertyTree(*aJsonWriter);
+ }
+
+ return aJsonWriter;
+}
+
+std::unique_ptr<tools::JsonWriter> JSDialogNotifyIdle::generateCloseMessage() const
+{
+ std::unique_ptr<tools::JsonWriter> aJsonWriter(new tools::JsonWriter());
+ if (m_aNotifierWindow)
+ aJsonWriter->put("id", m_aNotifierWindow->GetLOKWindowId());
+ aJsonWriter->put("jsontype", m_sTypeOfJSON);
+ aJsonWriter->put("action", "close");
+
+ return aJsonWriter;
+}
+
+std::unique_ptr<tools::JsonWriter>
+JSDialogNotifyIdle::generateActionMessage(VclPtr<vcl::Window> pWindow,
+ std::unique_ptr<jsdialog::ActionDataMap> pData) const
+{
+ std::unique_ptr<tools::JsonWriter> aJsonWriter(new tools::JsonWriter());
+
+ aJsonWriter->put("jsontype", m_sTypeOfJSON);
+ aJsonWriter->put("action", "action");
+ if (m_aNotifierWindow)
+ aJsonWriter->put("id", m_aNotifierWindow->GetLOKWindowId());
+
+ {
+ auto aDataNode = aJsonWriter->startNode("data");
+ aJsonWriter->put("control_id", pWindow->get_id());
+
+ for (auto it = pData->begin(); it != pData->end(); it++)
+ aJsonWriter->put(it->first, it->second);
+ }
+
+ return aJsonWriter;
+}
+
+std::unique_ptr<tools::JsonWriter>
+JSDialogNotifyIdle::generatePopupMessage(VclPtr<vcl::Window> pWindow, OUString sParentId,
+ OUString sCloseId) const
+{
+ std::unique_ptr<tools::JsonWriter> aJsonWriter(new tools::JsonWriter());
+
+ if (!pWindow || !m_aNotifierWindow)
+ return aJsonWriter;
+
+ if (!pWindow->GetParentWithLOKNotifier())
+ return aJsonWriter;
+
+ {
+ auto aChildren = aJsonWriter->startArray("children");
+ {
+ auto aStruct = aJsonWriter->startStruct();
+ pWindow->DumpAsPropertyTree(*aJsonWriter);
+ }
+ }
+
+ // try to get the position eg. for the autofilter
+ {
+ vcl::Window* pVclWindow = pWindow.get();
+ DockingWindow* pDockingWindow = dynamic_cast<DockingWindow*>(pVclWindow);
+ while (pVclWindow && !pDockingWindow)
+ {
+ pVclWindow = pVclWindow->GetParent();
+ pDockingWindow = dynamic_cast<DockingWindow*>(pVclWindow);
+ }
+
+ if (pDockingWindow)
+ {
+ Point aPos = pDockingWindow->GetFloatingPos();
+ aJsonWriter->put("posx", aPos.getX());
+ aJsonWriter->put("posy", aPos.getY());
+ if (!pDockingWindow->IsVisible())
+ aJsonWriter->put("visible", "false");
+ }
+ }
+
+ aJsonWriter->put("jsontype", "dialog");
+ aJsonWriter->put("type", "modalpopup");
+ aJsonWriter->put("cancellable", true);
+ aJsonWriter->put("popupParent", sParentId);
+ aJsonWriter->put("clickToClose", sCloseId);
+ aJsonWriter->put("id", pWindow->GetParentWithLOKNotifier()->GetLOKWindowId());
+
+ return aJsonWriter;
+}
+
+std::unique_ptr<tools::JsonWriter>
+JSDialogNotifyIdle::generateClosePopupMessage(OUString sWindowId) const
+{
+ std::unique_ptr<tools::JsonWriter> aJsonWriter(new tools::JsonWriter());
+
+ if (!m_aNotifierWindow)
+ return aJsonWriter;
+
+ aJsonWriter->put("jsontype", "dialog");
+ aJsonWriter->put("type", "modalpopup");
+ aJsonWriter->put("action", "close");
+ aJsonWriter->put("id", sWindowId);
+
+ return aJsonWriter;
+}
+
+void JSDialogNotifyIdle::Invoke()
+{
+ std::deque<JSDialogMessageInfo> aMessageQueue;
+ {
+ std::scoped_lock aGuard(m_aQueueMutex);
+
+ std::swap(aMessageQueue, m_aMessageQueue);
+ }
+
+ for (auto& rMessage : aMessageQueue)
+ {
+ jsdialog::MessageType eType = rMessage.m_eType;
+
+ if (m_sTypeOfJSON == "formulabar" && eType != jsdialog::MessageType::Action)
+ continue;
+
+ switch (eType)
+ {
+ case jsdialog::MessageType::FullUpdate:
+ send(*generateFullUpdate());
+ break;
+
+ case jsdialog::MessageType::WidgetUpdate:
+ send(*generateWidgetUpdate(rMessage.m_pWindow));
+ break;
+
+ case jsdialog::MessageType::Close:
+ send(*generateCloseMessage());
+ break;
+
+ case jsdialog::MessageType::Action:
+ send(*generateActionMessage(rMessage.m_pWindow, std::move(rMessage.m_pData)));
+ break;
+
+ case jsdialog::MessageType::Popup:
+ send(*generatePopupMessage(rMessage.m_pWindow,
+ (*rMessage.m_pData)[PARENT_ID ""_ostr],
+ (*rMessage.m_pData)[CLOSE_ID ""_ostr]));
+ break;
+
+ case jsdialog::MessageType::PopupClose:
+ send(*generateClosePopupMessage((*rMessage.m_pData)[WINDOW_ID ""_ostr]));
+ break;
+ }
+ }
+}
+
+void JSDialogNotifyIdle::clearQueue() { m_aMessageQueue.clear(); }
+
+JSDialogSender::~JSDialogSender() COVERITY_NOEXCEPT_FALSE
+{
+ sendClose();
+
+ if (mpIdleNotify)
+ mpIdleNotify->Stop();
+}
+
+void JSDialogSender::sendFullUpdate(bool bForce)
+{
+ if (!mpIdleNotify)
+ return;
+
+ if (bForce)
+ mpIdleNotify->forceUpdate();
+
+ mpIdleNotify->sendMessage(jsdialog::MessageType::FullUpdate, nullptr);
+ mpIdleNotify->Start();
+}
+
+void JSDialogSender::sendClose()
+{
+ if (!mpIdleNotify || !m_bCanClose)
+ return;
+
+ mpIdleNotify->clearQueue();
+ mpIdleNotify->sendMessage(jsdialog::MessageType::Close, nullptr);
+ flush();
+}
+
+void JSDialogSender::sendUpdate(VclPtr<vcl::Window> pWindow, bool bForce)
+{
+ if (!mpIdleNotify)
+ return;
+
+ if (bForce)
+ mpIdleNotify->forceUpdate();
+
+ mpIdleNotify->sendMessage(jsdialog::MessageType::WidgetUpdate, pWindow);
+ mpIdleNotify->Start();
+}
+
+void JSDialogSender::sendAction(VclPtr<vcl::Window> pWindow,
+ std::unique_ptr<jsdialog::ActionDataMap> pData)
+{
+ if (!mpIdleNotify)
+ return;
+
+ mpIdleNotify->sendMessage(jsdialog::MessageType::Action, pWindow, std::move(pData));
+ mpIdleNotify->Start();
+}
+
+void JSDialogSender::sendPopup(VclPtr<vcl::Window> pWindow, OUString sParentId, OUString sCloseId)
+{
+ if (!mpIdleNotify)
+ return;
+
+ std::unique_ptr<jsdialog::ActionDataMap> pData = std::make_unique<jsdialog::ActionDataMap>();
+ (*pData)[PARENT_ID ""_ostr] = sParentId;
+ (*pData)[CLOSE_ID ""_ostr] = sCloseId;
+ mpIdleNotify->sendMessage(jsdialog::MessageType::Popup, pWindow, std::move(pData));
+ mpIdleNotify->Start();
+}
+
+void JSDialogSender::sendClosePopup(vcl::LOKWindowId nWindowId)
+{
+ if (!mpIdleNotify)
+ return;
+
+ std::unique_ptr<jsdialog::ActionDataMap> pData = std::make_unique<jsdialog::ActionDataMap>();
+ (*pData)[WINDOW_ID ""_ostr] = OUString::number(nWindowId);
+ mpIdleNotify->sendMessage(jsdialog::MessageType::PopupClose, nullptr, std::move(pData));
+ flush();
+}
+
+namespace
+{
+vcl::Window* extract_sal_widget(weld::Widget* pParent)
+{
+ SalInstanceWidget* pInstanceWidget = dynamic_cast<SalInstanceWidget*>(pParent);
+ return pInstanceWidget ? pInstanceWidget->getWidget() : nullptr;
+}
+}
+
+// Drag and drop
+
+namespace
+{
+class JSDropTargetDropContext
+ : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDropContext>
+{
+public:
+ JSDropTargetDropContext() {}
+
+ // XDropTargetDropContext
+ virtual void SAL_CALL acceptDrop(sal_Int8 /*dragOperation*/) override {}
+
+ virtual void SAL_CALL rejectDrop() override {}
+
+ virtual void SAL_CALL dropComplete(sal_Bool /*bSuccess*/) override {}
+};
+}
+
+static JSTreeView* g_DragSource;
+
+JSDropTarget::JSDropTarget() {}
+
+void JSDropTarget::initialize(const css::uno::Sequence<css::uno::Any>& /*rArgs*/) {}
+
+void JSDropTarget::addDropTargetListener(
+ const css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>& xListener)
+{
+ std::unique_lock aGuard(m_aMutex);
+
+ m_aListeners.push_back(xListener);
+}
+
+void JSDropTarget::removeDropTargetListener(
+ const css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>& xListener)
+{
+ std::unique_lock aGuard(m_aMutex);
+
+ std::erase(m_aListeners, xListener);
+}
+
+sal_Bool JSDropTarget::isActive() { return false; }
+
+void JSDropTarget::setActive(sal_Bool /*active*/) {}
+
+sal_Int8 JSDropTarget::getDefaultActions() { return 0; }
+
+void JSDropTarget::setDefaultActions(sal_Int8 /*actions*/) {}
+
+OUString JSDropTarget::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.JSDropTarget";
+}
+
+sal_Bool JSDropTarget::supportsService(OUString const& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> JSDropTarget::getSupportedServiceNames()
+{
+ css::uno::Sequence<OUString> aRet{ "com.sun.star.datatransfer.dnd.JSDropTarget" };
+ return aRet;
+}
+
+void JSDropTarget::fire_drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde)
+{
+ std::unique_lock aGuard(m_aMutex);
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(
+ m_aListeners);
+ aGuard.unlock();
+
+ for (auto const& listener : aListeners)
+ {
+ listener->drop(dtde);
+ }
+}
+
+void JSDropTarget::fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde)
+{
+ std::unique_lock aGuard(m_aMutex);
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(
+ m_aListeners);
+ aGuard.unlock();
+
+ for (auto const& listener : aListeners)
+ {
+ listener->dragEnter(dtde);
+ }
+}
+
+OUString JSInstanceBuilder::getMapIdFromWindowId() const
+{
+ if (m_sTypeOfJSON == "sidebar" || m_sTypeOfJSON == "notebookbar"
+ || m_sTypeOfJSON == "formulabar")
+ return OUString::number(m_nWindowId) + m_sTypeOfJSON;
+ else
+ return OUString::number(m_nWindowId);
+}
+
+// used for dialogs
+JSInstanceBuilder::JSInstanceBuilder(weld::Widget* pParent, const OUString& rUIRoot,
+ const OUString& rUIFile, bool bPopup)
+ : SalInstanceBuilder(extract_sal_widget(pParent), rUIRoot, rUIFile)
+ , m_nWindowId(0)
+ , m_aParentDialog(nullptr)
+ , m_aContentWindow(nullptr)
+ , m_sTypeOfJSON("dialog")
+ , m_bHasTopLevelDialog(false)
+ , m_bIsNotebookbar(false)
+ , m_bSentInitialUpdate(false)
+ , m_bIsNestedBuilder(false)
+ , m_aWindowToRelease(nullptr)
+{
+ // when it is a popup we initialize sender in weld_popover
+ if (bPopup)
+ {
+ m_sTypeOfJSON = "popup";
+ return;
+ }
+
+ vcl::Window* pRoot = m_xBuilder->get_widget_root();
+
+ if (pRoot && pRoot->GetParent())
+ {
+ m_aParentDialog = pRoot->GetParent()->GetParentWithLOKNotifier();
+ if (m_aParentDialog)
+ m_nWindowId = m_aParentDialog->GetLOKWindowId();
+ InsertWindowToMap(getMapIdFromWindowId());
+ }
+
+ initializeSender(GetNotifierWindow(), GetContentWindow(), GetTypeOfJSON());
+}
+
+// used for sidebar panels
+JSInstanceBuilder::JSInstanceBuilder(weld::Widget* pParent, const OUString& rUIRoot,
+ const OUString& rUIFile, sal_uInt64 nLOKWindowId)
+ : SalInstanceBuilder(extract_sal_widget(pParent), rUIRoot, rUIFile)
+ , m_nWindowId(nLOKWindowId)
+ , m_aParentDialog(nullptr)
+ , m_aContentWindow(nullptr)
+ , m_sTypeOfJSON("sidebar")
+ , m_bHasTopLevelDialog(false)
+ , m_bIsNotebookbar(false)
+ , m_bSentInitialUpdate(false)
+ , m_bIsNestedBuilder(false)
+ , m_aWindowToRelease(nullptr)
+{
+ vcl::Window* pRoot = m_xBuilder->get_widget_root();
+
+ m_aParentDialog = pRoot->GetParentWithLOKNotifier();
+
+ if (rUIFile == "sfx/ui/panel.ui")
+ {
+ // builder for Panel, get SidebarDockingWindow as m_aContentWindow
+ m_aContentWindow = pRoot;
+ for (int i = 0; i < 7 && m_aContentWindow; i++)
+ m_aContentWindow = m_aContentWindow->GetParent();
+ }
+ else
+ {
+ // embedded fragments cannot send close message for whole sidebar
+ if (rUIFile == "modules/simpress/ui/customanimationfragment.ui")
+ m_bCanClose = false;
+
+ // builder for PanelLayout, get SidebarDockingWindow as m_aContentWindow
+ m_aContentWindow = pRoot;
+ for (int i = 0; i < 9 && m_aContentWindow; i++)
+ m_aContentWindow = m_aContentWindow->GetParent();
+ }
+
+ InsertWindowToMap(getMapIdFromWindowId());
+
+ initializeSender(GetNotifierWindow(), GetContentWindow(), GetTypeOfJSON());
+}
+
+// used for notebookbar
+JSInstanceBuilder::JSInstanceBuilder(vcl::Window* pParent, const OUString& rUIRoot,
+ const OUString& rUIFile,
+ const css::uno::Reference<css::frame::XFrame>& rFrame,
+ sal_uInt64 nWindowId)
+ : SalInstanceBuilder(pParent, rUIRoot, rUIFile, rFrame)
+ , m_nWindowId(0)
+ , m_aParentDialog(nullptr)
+ , m_aContentWindow(nullptr)
+ , m_sTypeOfJSON("notebookbar")
+ , m_bHasTopLevelDialog(false)
+ , m_bIsNotebookbar(false)
+ , m_bSentInitialUpdate(false)
+ , m_bIsNestedBuilder(false)
+ , m_aWindowToRelease(nullptr)
+{
+ vcl::Window* pRoot = m_xBuilder->get_widget_root();
+ if (pRoot && pRoot->GetParent())
+ {
+ m_aParentDialog = pRoot->GetParent()->GetParentWithLOKNotifier();
+ if (m_aParentDialog)
+ m_nWindowId = m_aParentDialog->GetLOKWindowId();
+ if (!m_nWindowId && nWindowId)
+ {
+ m_nWindowId = nWindowId;
+ m_bIsNotebookbar = true;
+ }
+ InsertWindowToMap(getMapIdFromWindowId());
+ }
+
+ initializeSender(GetNotifierWindow(), GetContentWindow(), GetTypeOfJSON());
+}
+
+// used for formulabar
+JSInstanceBuilder::JSInstanceBuilder(vcl::Window* pParent, const OUString& rUIRoot,
+ const OUString& rUIFile, sal_uInt64 nLOKWindowId)
+ : SalInstanceBuilder(pParent, rUIRoot, rUIFile)
+ , m_nWindowId(nLOKWindowId)
+ , m_aParentDialog(nullptr)
+ , m_aContentWindow(nullptr)
+ , m_sTypeOfJSON("formulabar")
+ , m_bHasTopLevelDialog(false)
+ , m_bIsNotebookbar(false)
+ , m_bSentInitialUpdate(false)
+ , m_bIsNestedBuilder(false)
+ , m_aWindowToRelease(nullptr)
+{
+ vcl::Window* pRoot = m_xBuilder->get_widget_root();
+ m_aContentWindow = pParent;
+ if (pRoot && pRoot->GetParent())
+ {
+ m_aParentDialog = pRoot->GetParent()->GetParentWithLOKNotifier();
+ InsertWindowToMap(getMapIdFromWindowId());
+ }
+
+ initializeSender(GetNotifierWindow(), GetContentWindow(), GetTypeOfJSON());
+}
+
+std::unique_ptr<JSInstanceBuilder> JSInstanceBuilder::CreateDialogBuilder(weld::Widget* pParent,
+ const OUString& rUIRoot,
+ const OUString& rUIFile)
+{
+ return std::make_unique<JSInstanceBuilder>(pParent, rUIRoot, rUIFile);
+}
+
+std::unique_ptr<JSInstanceBuilder> JSInstanceBuilder::CreateNotebookbarBuilder(
+ vcl::Window* pParent, const OUString& rUIRoot, const OUString& rUIFile,
+ const css::uno::Reference<css::frame::XFrame>& rFrame, sal_uInt64 nWindowId)
+{
+ return std::make_unique<JSInstanceBuilder>(pParent, rUIRoot, rUIFile, rFrame, nWindowId);
+}
+
+std::unique_ptr<JSInstanceBuilder> JSInstanceBuilder::CreateSidebarBuilder(weld::Widget* pParent,
+ const OUString& rUIRoot,
+ const OUString& rUIFile,
+ sal_uInt64 nLOKWindowId)
+{
+ return std::make_unique<JSInstanceBuilder>(pParent, rUIRoot, rUIFile, nLOKWindowId);
+}
+
+std::unique_ptr<JSInstanceBuilder> JSInstanceBuilder::CreatePopupBuilder(weld::Widget* pParent,
+ const OUString& rUIRoot,
+ const OUString& rUIFile)
+{
+ return std::make_unique<JSInstanceBuilder>(pParent, rUIRoot, rUIFile, true);
+}
+
+std::unique_ptr<JSInstanceBuilder>
+JSInstanceBuilder::CreateFormulabarBuilder(vcl::Window* pParent, const OUString& rUIRoot,
+ const OUString& rUIFile, sal_uInt64 nLOKWindowId)
+{
+ return std::make_unique<JSInstanceBuilder>(pParent, rUIRoot, rUIFile, nLOKWindowId);
+}
+
+JSInstanceBuilder::~JSInstanceBuilder()
+{
+ // tab page closed -> refresh parent window
+ if (m_bIsNestedBuilder && m_sTypeOfJSON == "dialog")
+ {
+ jsdialog::SendFullUpdate(OUString::number(m_nWindowId), "__DIALOG__");
+ }
+
+ if (m_sTypeOfJSON == "popup")
+ sendClosePopup(m_nWindowId);
+
+ if (m_aWindowToRelease)
+ {
+ m_aWindowToRelease->ReleaseLOKNotifier();
+ m_aWindowToRelease.clear();
+ }
+
+ if (m_nWindowId && (m_bHasTopLevelDialog || m_bIsNotebookbar))
+ {
+ GetLOKWeldWidgetsMap().erase(getMapIdFromWindowId());
+ }
+ else
+ {
+ auto it = GetLOKWeldWidgetsMap().find(getMapIdFromWindowId());
+ if (it != GetLOKWeldWidgetsMap().end())
+ {
+ std::for_each(m_aRememberedWidgets.begin(), m_aRememberedWidgets.end(),
+ [it](const OUString& sId) { it->second.erase(sId); });
+ }
+ }
+
+ GetLOKPopupsMap().erase(OUString::number(m_nWindowId));
+}
+
+std::map<OUString, WidgetMap>& JSInstanceBuilder::GetLOKWeldWidgetsMap()
+{
+ // Map to remember the LOKWindowId <-> weld widgets binding.
+ static std::map<OUString, WidgetMap> s_aLOKWeldBuildersMap;
+
+ return s_aLOKWeldBuildersMap;
+}
+
+weld::Widget* JSInstanceBuilder::FindWeldWidgetsMap(const OUString& nWindowId,
+ const OUString& rWidget)
+{
+ const auto it = GetLOKWeldWidgetsMap().find(nWindowId);
+
+ if (it != GetLOKWeldWidgetsMap().end())
+ {
+ auto widgetIt = it->second.find(rWidget);
+ if (widgetIt != it->second.end())
+ return widgetIt->second;
+ }
+
+ return nullptr;
+}
+
+void JSInstanceBuilder::InsertWindowToMap(const OUString& nWindowId)
+{
+ WidgetMap map;
+ auto it = GetLOKWeldWidgetsMap().find(nWindowId);
+ if (it == GetLOKWeldWidgetsMap().end())
+ GetLOKWeldWidgetsMap().insert({ nWindowId, map });
+}
+
+void JSInstanceBuilder::RememberWidget(OUString sId, weld::Widget* pWidget)
+{
+ // do not use the same id for two widgets inside one builder
+ // exception is sidebar where we base our full invalidation on that "Panel" id sharing
+ if (m_sTypeOfJSON != "sidebar")
+ {
+ static std::atomic<unsigned long long int> nNotRepeatIndex = 0;
+ auto aWindowIt = GetLOKWeldWidgetsMap().find(getMapIdFromWindowId());
+ if (aWindowIt != GetLOKWeldWidgetsMap().end())
+ {
+ auto aWidgetIt = aWindowIt->second.find(sId);
+ if (aWidgetIt != aWindowIt->second.end())
+ {
+ unsigned long long int nIndex = nNotRepeatIndex++;
+ // found duplicated it -> add some number to the id and apply to the widget
+ sId = sId + OUString::number(nIndex);
+ SalInstanceWidget* pSalWidget = dynamic_cast<SalInstanceWidget*>(pWidget);
+ assert(pSalWidget && "can only be a SalInstanceWidget");
+ vcl::Window* pVclWidget = pSalWidget->getWidget();
+ pVclWidget->set_id(pVclWidget->get_id() + OUString::number(nIndex));
+ }
+ }
+ }
+
+ RememberWidget(getMapIdFromWindowId(), sId, pWidget);
+ m_aRememberedWidgets.push_back(sId);
+}
+
+void JSInstanceBuilder::RememberWidget(const OUString& nWindowId, const OUString& id,
+ weld::Widget* pWidget)
+{
+ auto it = GetLOKWeldWidgetsMap().find(nWindowId);
+ if (it != GetLOKWeldWidgetsMap().end())
+ {
+ it->second.erase(id);
+ it->second.insert(WidgetMap::value_type(id, pWidget));
+ }
+}
+
+void JSInstanceBuilder::AddChildWidget(const OUString& nWindowId, const OUString& id,
+ weld::Widget* pWidget)
+{
+ auto it = GetLOKWeldWidgetsMap().find(nWindowId);
+ if (it != GetLOKWeldWidgetsMap().end())
+ {
+ it->second.erase(id);
+ it->second.insert(WidgetMap::value_type(id, pWidget));
+ }
+}
+
+void JSInstanceBuilder::RemoveWindowWidget(const OUString& nWindowId)
+{
+ auto it = JSInstanceBuilder::GetLOKWeldWidgetsMap().find(nWindowId);
+ if (it != JSInstanceBuilder::GetLOKWeldWidgetsMap().end())
+ {
+ JSInstanceBuilder::GetLOKWeldWidgetsMap().erase(it);
+ }
+}
+
+void JSInstanceBuilder::RememberPopup(const OUString& nWindowId, VclPtr<vcl::Window> pWidget)
+{
+ GetLOKPopupsMap()[nWindowId] = pWidget;
+}
+
+void JSInstanceBuilder::ForgetPopup(const OUString& nWindowId)
+{
+ auto it = GetLOKPopupsMap().find(nWindowId);
+ if (it != GetLOKPopupsMap().end())
+ GetLOKPopupsMap().erase(it);
+}
+
+vcl::Window* JSInstanceBuilder::FindPopup(const OUString& nWindowId)
+{
+ const auto it = GetLOKPopupsMap().find(nWindowId);
+
+ if (it != GetLOKPopupsMap().end())
+ return it->second;
+
+ return nullptr;
+}
+
+const OUString& JSInstanceBuilder::GetTypeOfJSON() const { return m_sTypeOfJSON; }
+
+VclPtr<vcl::Window>& JSInstanceBuilder::GetContentWindow()
+{
+ if (m_aContentWindow)
+ return m_aContentWindow;
+ else
+ return m_bHasTopLevelDialog ? m_aOwnedToplevel : m_aParentDialog;
+}
+
+VclPtr<vcl::Window>& JSInstanceBuilder::GetNotifierWindow()
+{
+ return m_bHasTopLevelDialog ? m_aOwnedToplevel : m_aParentDialog;
+}
+
+std::unique_ptr<weld::Dialog> JSInstanceBuilder::weld_dialog(const OUString& id)
+{
+ std::unique_ptr<weld::Dialog> pRet;
+ ::Dialog* pDialog = m_xBuilder->get<::Dialog>(id);
+
+ if (pDialog)
+ {
+ m_nWindowId = pDialog->GetLOKWindowId();
+ pDialog->SetLOKTunnelingState(false);
+
+ InsertWindowToMap(getMapIdFromWindowId());
+
+ assert(!m_aOwnedToplevel && "only one toplevel per .ui allowed");
+ m_aOwnedToplevel.set(pDialog);
+ m_xBuilder->drop_ownership(pDialog);
+ m_bHasTopLevelDialog = true;
+
+ pRet.reset(new JSDialog(this, pDialog, this, false));
+
+ RememberWidget("__DIALOG__", pRet.get());
+
+ initializeSender(GetNotifierWindow(), GetContentWindow(), GetTypeOfJSON());
+ m_bSentInitialUpdate = true;
+ }
+
+ return pRet;
+}
+
+std::unique_ptr<weld::Assistant> JSInstanceBuilder::weld_assistant(const OUString& id)
+{
+ vcl::RoadmapWizard* pDialog = m_xBuilder->get<vcl::RoadmapWizard>(id);
+ std::unique_ptr<JSAssistant> pRet(pDialog ? new JSAssistant(this, pDialog, this, false)
+ : nullptr);
+ if (pDialog)
+ {
+ m_nWindowId = pDialog->GetLOKWindowId();
+ pDialog->SetLOKTunnelingState(false);
+
+ InsertWindowToMap(getMapIdFromWindowId());
+
+ assert(!m_aOwnedToplevel && "only one toplevel per .ui allowed");
+ m_aOwnedToplevel.set(pDialog);
+ m_xBuilder->drop_ownership(pDialog);
+ m_bHasTopLevelDialog = true;
+
+ pRet.reset(new JSAssistant(this, pDialog, this, false));
+
+ RememberWidget("__DIALOG__", pRet.get());
+
+ initializeSender(GetNotifierWindow(), GetContentWindow(), GetTypeOfJSON());
+ m_bSentInitialUpdate = true;
+ }
+
+ return pRet;
+}
+
+std::unique_ptr<weld::MessageDialog> JSInstanceBuilder::weld_message_dialog(const OUString& id)
+{
+ std::unique_ptr<weld::MessageDialog> pRet;
+ ::MessageDialog* pMessageDialog = m_xBuilder->get<::MessageDialog>(id);
+
+ if (pMessageDialog)
+ {
+ m_nWindowId = pMessageDialog->GetLOKWindowId();
+ pMessageDialog->SetLOKTunnelingState(false);
+
+ InsertWindowToMap(getMapIdFromWindowId());
+
+ assert(!m_aOwnedToplevel && "only one toplevel per .ui allowed");
+ m_aOwnedToplevel.set(pMessageDialog);
+ m_xBuilder->drop_ownership(pMessageDialog);
+ m_bHasTopLevelDialog = true;
+
+ initializeSender(GetNotifierWindow(), GetContentWindow(), GetTypeOfJSON());
+ m_bSentInitialUpdate = true;
+ }
+
+ pRet.reset(pMessageDialog ? new JSMessageDialog(this, pMessageDialog, this, false) : nullptr);
+
+ if (pRet)
+ RememberWidget("__DIALOG__", pRet.get());
+
+ return pRet;
+}
+
+std::unique_ptr<weld::Container> JSInstanceBuilder::weld_container(const OUString& id)
+{
+ vcl::Window* pContainer = m_xBuilder->get<vcl::Window>(id);
+ auto pWeldWidget
+ = pContainer ? std::make_unique<JSContainer>(this, pContainer, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ if (!m_bSentInitialUpdate && pContainer)
+ {
+ m_bSentInitialUpdate = true;
+
+ // use parent builder to send update - avoid multiple calls from many builders
+ vcl::Window* pParent = pContainer->GetParent();
+ OUString sId = OUString::number(m_nWindowId);
+ while (pParent && !FindWeldWidgetsMap(sId, pParent->get_id()))
+ pParent = pParent->GetParent();
+
+ if (pParent)
+ jsdialog::SendFullUpdate(sId, pParent->get_id());
+
+ // this is nested builder, don't close parent dialog on destroy (eg. single tab page is closed)
+ m_bCanClose = false;
+ m_bIsNestedBuilder = true;
+ }
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::ScrolledWindow>
+JSInstanceBuilder::weld_scrolled_window(const OUString& id, bool bUserManagedScrolling)
+{
+ VclScrolledWindow* pScrolledWindow = m_xBuilder->get<VclScrolledWindow>(id);
+ auto pWeldWidget = pScrolledWindow
+ ? std::make_unique<JSScrolledWindow>(this, pScrolledWindow, this, false,
+ bUserManagedScrolling)
+ : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::Label> JSInstanceBuilder::weld_label(const OUString& id)
+{
+ Control* pLabel = m_xBuilder->get<Control>(id);
+ auto pWeldWidget = std::make_unique<JSLabel>(this, pLabel, this, false);
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::Button> JSInstanceBuilder::weld_button(const OUString& id)
+{
+ ::Button* pButton = m_xBuilder->get<::Button>(id);
+ auto pWeldWidget = pButton ? std::make_unique<JSButton>(this, pButton, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::LinkButton> JSInstanceBuilder::weld_link_button(const OUString& id)
+{
+ ::FixedHyperlink* pButton = m_xBuilder->get<::FixedHyperlink>(id);
+ auto pWeldWidget
+ = pButton ? std::make_unique<JSLinkButton>(this, pButton, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::ToggleButton> JSInstanceBuilder::weld_toggle_button(const OUString& id)
+{
+ ::PushButton* pButton = m_xBuilder->get<::PushButton>(id);
+ auto pWeldWidget
+ = pButton ? std::make_unique<JSToggleButton>(this, pButton, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::Entry> JSInstanceBuilder::weld_entry(const OUString& id)
+{
+ Edit* pEntry = m_xBuilder->get<Edit>(id);
+ auto pWeldWidget = pEntry ? std::make_unique<JSEntry>(this, pEntry, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::ComboBox> JSInstanceBuilder::weld_combo_box(const OUString& id)
+{
+ vcl::Window* pWidget = m_xBuilder->get<vcl::Window>(id);
+ ::ComboBox* pComboBox = dynamic_cast<::ComboBox*>(pWidget);
+ std::unique_ptr<weld::ComboBox> pWeldWidget;
+
+ if (pComboBox)
+ {
+ pWeldWidget = std::make_unique<JSComboBox>(this, pComboBox, this, false);
+ }
+ else
+ {
+ ListBox* pListBox = dynamic_cast<ListBox*>(pWidget);
+ pWeldWidget = pListBox ? std::make_unique<JSListBox>(this, pListBox, this, false) : nullptr;
+ }
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::Notebook> JSInstanceBuilder::weld_notebook(const OUString& id)
+{
+ std::unique_ptr<weld::Notebook> pWeldWidget;
+ vcl::Window* pNotebook = m_xBuilder->get(id);
+
+ if (pNotebook && pNotebook->GetType() == WindowType::TABCONTROL)
+ pWeldWidget
+ = std::make_unique<JSNotebook>(this, static_cast<TabControl*>(pNotebook), this, false);
+ else if (pNotebook->GetType() == WindowType::VERTICALTABCONTROL)
+ pWeldWidget = std::make_unique<JSVerticalNotebook>(
+ this, static_cast<VerticalTabControl*>(pNotebook), this, false);
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::SpinButton> JSInstanceBuilder::weld_spin_button(const OUString& id)
+{
+ FormattedField* pSpinButton = m_xBuilder->get<FormattedField>(id);
+ auto pWeldWidget
+ = pSpinButton ? std::make_unique<JSSpinButton>(this, pSpinButton, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::FormattedSpinButton>
+JSInstanceBuilder::weld_formatted_spin_button(const OUString& id)
+{
+ FormattedField* pSpinButton = m_xBuilder->get<FormattedField>(id);
+ auto pWeldWidget = pSpinButton
+ ? std::make_unique<JSFormattedSpinButton>(this, pSpinButton, this, false)
+ : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::CheckButton> JSInstanceBuilder::weld_check_button(const OUString& id)
+{
+ CheckBox* pCheckButton = m_xBuilder->get<CheckBox>(id);
+ auto pWeldWidget
+ = pCheckButton ? std::make_unique<JSCheckButton>(this, pCheckButton, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::DrawingArea>
+JSInstanceBuilder::weld_drawing_area(const OUString& id, const a11yref& rA11yImpl,
+ FactoryFunction pUITestFactoryFunction, void* pUserData)
+{
+ VclDrawingArea* pArea = m_xBuilder->get<VclDrawingArea>(id);
+ auto pWeldWidget = pArea ? std::make_unique<JSDrawingArea>(this, pArea, this, rA11yImpl,
+ pUITestFactoryFunction, pUserData)
+ : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::Toolbar> JSInstanceBuilder::weld_toolbar(const OUString& id)
+{
+ ToolBox* pToolBox = m_xBuilder->get<ToolBox>(id);
+ auto pWeldWidget
+ = pToolBox ? std::make_unique<JSToolbar>(this, pToolBox, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::TextView> JSInstanceBuilder::weld_text_view(const OUString& id)
+{
+ VclMultiLineEdit* pTextView = m_xBuilder->get<VclMultiLineEdit>(id);
+ auto pWeldWidget
+ = pTextView ? std::make_unique<JSTextView>(this, pTextView, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::TreeView> JSInstanceBuilder::weld_tree_view(const OUString& id)
+{
+ SvTabListBox* pTreeView = m_xBuilder->get<SvTabListBox>(id);
+ auto pWeldWidget
+ = pTreeView ? std::make_unique<JSTreeView>(this, pTreeView, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::Expander> JSInstanceBuilder::weld_expander(const OUString& id)
+{
+ VclExpander* pExpander = m_xBuilder->get<VclExpander>(id);
+ auto pWeldWidget
+ = pExpander ? std::make_unique<JSExpander>(this, pExpander, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::IconView> JSInstanceBuilder::weld_icon_view(const OUString& id)
+{
+ ::IconView* pIconView = m_xBuilder->get<::IconView>(id);
+ auto pWeldWidget
+ = pIconView ? std::make_unique<JSIconView>(this, pIconView, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::RadioButton> JSInstanceBuilder::weld_radio_button(const OUString& id)
+{
+ ::RadioButton* pRadioButton = m_xBuilder->get<::RadioButton>(id);
+ auto pWeldWidget
+ = pRadioButton ? std::make_unique<JSRadioButton>(this, pRadioButton, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::Frame> JSInstanceBuilder::weld_frame(const OUString& id)
+{
+ ::VclFrame* pFrame = m_xBuilder->get<::VclFrame>(id);
+ auto pWeldWidget = pFrame ? std::make_unique<JSFrame>(this, pFrame, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::MenuButton> JSInstanceBuilder::weld_menu_button(const OUString& id)
+{
+ ::MenuButton* pMenuButton = m_xBuilder->get<::MenuButton>(id);
+ auto pWeldWidget
+ = pMenuButton ? std::make_unique<JSMenuButton>(this, pMenuButton, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::Popover> JSInstanceBuilder::weld_popover(const OUString& id)
+{
+ DockingWindow* pDockingWindow = m_xBuilder->get<DockingWindow>(id);
+ JSPopover* pPopover
+ = pDockingWindow ? new JSPopover(this, pDockingWindow, this, false) : nullptr;
+ std::unique_ptr<weld::Popover> pWeldWidget(pPopover);
+ if (pDockingWindow)
+ {
+ assert(!m_aOwnedToplevel && "only one toplevel per .ui allowed");
+ m_aOwnedToplevel.set(pDockingWindow);
+ m_xBuilder->drop_ownership(pDockingWindow);
+
+ if (VclPtr<vcl::Window> pWin = pDockingWindow->GetParentWithLOKNotifier())
+ {
+ vcl::Window* pPopupRoot = pDockingWindow->GetChild(0);
+ pPopupRoot->SetLOKNotifier(pWin->GetLOKNotifier());
+ m_aParentDialog = pPopupRoot;
+ m_aWindowToRelease = pPopupRoot;
+ m_nWindowId = m_aParentDialog->GetLOKWindowId();
+
+ pPopover->set_window_id(m_nWindowId);
+ JSInstanceBuilder::RememberPopup(OUString::number(m_nWindowId), pDockingWindow);
+
+ InsertWindowToMap(getMapIdFromWindowId());
+ initializeSender(GetNotifierWindow(), GetContentWindow(), GetTypeOfJSON());
+ }
+ }
+
+ if (pWeldWidget)
+ RememberWidget("__POPOVER__", pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::Box> JSInstanceBuilder::weld_box(const OUString& id)
+{
+ VclBox* pContainer = m_xBuilder->get<VclBox>(id);
+ auto pWeldWidget
+ = pContainer ? std::make_unique<JSBox>(this, pContainer, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::Widget> JSInstanceBuilder::weld_widget(const OUString& id)
+{
+ vcl::Window* pWidget = m_xBuilder->get(id);
+ auto pWeldWidget
+ = pWidget ? std::make_unique<JSWidgetInstance>(this, pWidget, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::Image> JSInstanceBuilder::weld_image(const OUString& id)
+{
+ FixedImage* pImage = m_xBuilder->get<FixedImage>(id);
+
+ auto pWeldWidget = pImage ? std::make_unique<JSImage>(this, pImage, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+std::unique_ptr<weld::Calendar> JSInstanceBuilder::weld_calendar(const OUString& id)
+{
+ ::Calendar* pCalendar = m_xBuilder->get<::Calendar>(id);
+
+ auto pWeldWidget
+ = pCalendar ? std::make_unique<JSCalendar>(this, pCalendar, this, false) : nullptr;
+
+ if (pWeldWidget)
+ RememberWidget(id, pWeldWidget.get());
+
+ return pWeldWidget;
+}
+
+weld::MessageDialog*
+JSInstanceBuilder::CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType,
+ VclButtonsType eButtonType, const OUString& rPrimaryMessage,
+ const vcl::ILibreOfficeKitNotifier* pNotifier)
+{
+ SalInstanceWidget* pParentInstance = dynamic_cast<SalInstanceWidget*>(pParent);
+ SystemWindow* pParentWidget = pParentInstance ? pParentInstance->getSystemWindow() : nullptr;
+ VclPtrInstance<::MessageDialog> xMessageDialog(pParentWidget, rPrimaryMessage, eMessageType,
+ eButtonType);
+
+ if (pNotifier)
+ xMessageDialog->SetLOKNotifier(pNotifier);
+
+ pNotifier = xMessageDialog->GetLOKNotifier();
+ if (pNotifier)
+ {
+ tools::JsonWriter aJsonWriter;
+ xMessageDialog->DumpAsPropertyTree(aJsonWriter);
+ aJsonWriter.put("id", xMessageDialog->GetLOKWindowId());
+ aJsonWriter.put("jsontype", "dialog");
+ OString message(aJsonWriter.finishAndGetAsOString());
+ pNotifier->libreOfficeKitViewCallback(LOK_CALLBACK_JSDIALOG, message);
+
+ OUString sWindowId = OUString::number(xMessageDialog->GetLOKWindowId());
+ InsertWindowToMap(sWindowId);
+ xMessageDialog->SetLOKTunnelingState(false);
+
+ return new JSMessageDialog(xMessageDialog, nullptr, true);
+ }
+ else
+ SAL_WARN("vcl", "No notifier in JSInstanceBuilder::CreateMessageDialog");
+
+ return new JSMessageDialog(xMessageDialog, nullptr, true);
+}
+
+JSDialog::JSDialog(JSDialogSender* pSender, ::Dialog* pDialog, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : JSWidget<SalInstanceDialog, ::Dialog>(pSender, pDialog, pBuilder, bTakeOwnership)
+{
+}
+
+void JSDialog::collapse(weld::Widget* pEdit, weld::Widget* pButton)
+{
+ SalInstanceDialog::collapse(pEdit, pButton);
+ sendFullUpdate();
+}
+
+void JSDialog::undo_collapse()
+{
+ SalInstanceDialog::undo_collapse();
+ sendFullUpdate();
+}
+
+void JSDialog::response(int response)
+{
+ if (response == RET_HELP)
+ {
+ response_help(m_xWidget.get());
+ return;
+ }
+
+ sendClose();
+ SalInstanceDialog::response(response);
+}
+
+void JSAssistant::response(int response)
+{
+ if (response == RET_HELP)
+ {
+ response_help(m_xWidget.get());
+ return;
+ }
+
+ sendClose();
+ SalInstanceAssistant::response(response);
+}
+
+int JSDialog::run()
+{
+ sendFullUpdate(true);
+ int ret = SalInstanceDialog::run();
+ return ret;
+}
+
+bool JSDialog::runAsync(std::shared_ptr<weld::DialogController> aOwner,
+ const std::function<void(sal_Int32)>& rEndDialogFn)
+{
+ bool ret = SalInstanceDialog::runAsync(aOwner, rEndDialogFn);
+ sendFullUpdate();
+ return ret;
+}
+
+bool JSDialog::runAsync(std::shared_ptr<Dialog> const& rxSelf,
+ const std::function<void(sal_Int32)>& func)
+{
+ bool ret = SalInstanceDialog::runAsync(rxSelf, func);
+ sendFullUpdate();
+ return ret;
+}
+
+int JSAssistant::run()
+{
+ sendFullUpdate(true);
+ int ret = SalInstanceDialog::run();
+ return ret;
+}
+
+bool JSAssistant::runAsync(std::shared_ptr<weld::DialogController> aOwner,
+ const std::function<void(sal_Int32)>& rEndDialogFn)
+{
+ bool ret = SalInstanceDialog::runAsync(aOwner, rEndDialogFn);
+ sendFullUpdate();
+ return ret;
+}
+
+bool JSAssistant::runAsync(std::shared_ptr<Dialog> const& rxSelf,
+ const std::function<void(sal_Int32)>& func)
+{
+ bool ret = SalInstanceDialog::runAsync(rxSelf, func);
+ sendFullUpdate();
+ return ret;
+}
+
+weld::Button* JSDialog::weld_widget_for_response(int nResponse)
+{
+ PushButton* pButton
+ = dynamic_cast<::PushButton*>(m_xDialog->get_widget_for_response(nResponse));
+ auto pWeldWidget = pButton ? new JSButton(m_pSender, pButton, nullptr, false) : nullptr;
+
+ if (pWeldWidget)
+ {
+ auto pParentDialog = m_xDialog->GetParentWithLOKNotifier();
+ if (pParentDialog)
+ JSInstanceBuilder::RememberWidget(OUString::number(pParentDialog->GetLOKWindowId()),
+ pButton->get_id(), pWeldWidget);
+ }
+
+ return pWeldWidget;
+}
+
+weld::Button* JSAssistant::weld_widget_for_response(int nResponse)
+{
+ ::PushButton* pButton = nullptr;
+ JSButton* pWeldWidget = 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)
+ pWeldWidget = new JSButton(m_pSender, pButton, nullptr, false);
+
+ if (pWeldWidget)
+ {
+ auto pParentDialog = m_xWizard->GetParentWithLOKNotifier();
+ if (pParentDialog)
+ JSInstanceBuilder::RememberWidget(OUString::number(pParentDialog->GetLOKWindowId()),
+ pButton->get_id(), pWeldWidget);
+ }
+
+ return pWeldWidget;
+}
+
+JSAssistant::JSAssistant(JSDialogSender* pSender, vcl::RoadmapWizard* pDialog,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceAssistant, vcl::RoadmapWizard>(pSender, pDialog, pBuilder, bTakeOwnership)
+{
+}
+
+void JSAssistant::set_current_page(int nPage)
+{
+ SalInstanceAssistant::set_current_page(nPage);
+ sendFullUpdate();
+}
+
+void JSAssistant::set_current_page(const OUString& rIdent)
+{
+ SalInstanceAssistant::set_current_page(rIdent);
+ sendFullUpdate();
+}
+
+JSContainer::JSContainer(JSDialogSender* pSender, vcl::Window* pContainer,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceContainer, vcl::Window>(pSender, pContainer, pBuilder, bTakeOwnership)
+{
+}
+
+JSScrolledWindow::JSScrolledWindow(JSDialogSender* pSender, ::VclScrolledWindow* pContainer,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership,
+ bool bUserManagedScrolling)
+ : JSWidget<SalInstanceScrolledWindow, ::VclScrolledWindow>(
+ pSender, pContainer, pBuilder, bTakeOwnership, bUserManagedScrolling)
+{
+}
+
+void JSScrolledWindow::vadjustment_configure(int value, int lower, int upper, int step_increment,
+ int page_increment, int page_size)
+{
+ SalInstanceScrolledWindow::vadjustment_configure(value, lower, upper, step_increment,
+ page_increment, page_size);
+ sendUpdate();
+}
+
+void JSScrolledWindow::vadjustment_set_value(int value)
+{
+ SalInstanceScrolledWindow::vadjustment_set_value(value);
+ sendUpdate();
+}
+
+void JSScrolledWindow::vadjustment_set_value_no_notification(int value)
+{
+ SalInstanceScrolledWindow::vadjustment_set_value(value);
+}
+
+void JSScrolledWindow::vadjustment_set_page_size(int size)
+{
+ SalInstanceScrolledWindow::vadjustment_set_page_size(size);
+ sendUpdate();
+}
+
+void JSScrolledWindow::set_vpolicy(VclPolicyType eVPolicy)
+{
+ SalInstanceScrolledWindow::set_vpolicy(eVPolicy);
+ sendUpdate();
+}
+
+void JSScrolledWindow::hadjustment_configure(int value, int lower, int upper, int step_increment,
+ int page_increment, int page_size)
+{
+ SalInstanceScrolledWindow::hadjustment_configure(value, lower, upper, step_increment,
+ page_increment, page_size);
+ sendUpdate();
+}
+
+void JSScrolledWindow::hadjustment_set_value(int value)
+{
+ SalInstanceScrolledWindow::hadjustment_set_value(value);
+ sendUpdate();
+}
+
+void JSScrolledWindow::hadjustment_set_value_no_notification(int value)
+{
+ SalInstanceScrolledWindow::hadjustment_set_value(value);
+}
+
+void JSScrolledWindow::hadjustment_set_page_size(int size)
+{
+ SalInstanceScrolledWindow::hadjustment_set_page_size(size);
+ sendUpdate();
+}
+
+void JSScrolledWindow::set_hpolicy(VclPolicyType eVPolicy)
+{
+ SalInstanceScrolledWindow::set_hpolicy(eVPolicy);
+ sendUpdate();
+}
+
+JSLabel::JSLabel(JSDialogSender* pSender, Control* pLabel, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : JSWidget<SalInstanceLabel, Control>(pSender, pLabel, pBuilder, bTakeOwnership)
+{
+}
+
+void JSLabel::set_label(const OUString& rText)
+{
+ SalInstanceLabel::set_label(rText);
+ sendUpdate();
+};
+
+JSButton::JSButton(JSDialogSender* pSender, ::Button* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : JSWidget<SalInstanceButton, ::Button>(pSender, pButton, pBuilder, bTakeOwnership)
+{
+}
+
+JSLinkButton::JSLinkButton(JSDialogSender* pSender, ::FixedHyperlink* pButton,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceLinkButton, ::FixedHyperlink>(pSender, pButton, pBuilder, bTakeOwnership)
+{
+}
+
+JSToggleButton::JSToggleButton(JSDialogSender* pSender, ::PushButton* pButton,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceToggleButton, ::PushButton>(pSender, pButton, pBuilder, bTakeOwnership)
+{
+}
+
+JSEntry::JSEntry(JSDialogSender* pSender, ::Edit* pEntry, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : JSWidget<SalInstanceEntry, ::Edit>(pSender, pEntry, pBuilder, bTakeOwnership)
+{
+}
+
+void JSEntry::set_text(const OUString& rText)
+{
+ SalInstanceEntry::set_text(rText);
+ sendUpdate();
+}
+
+void JSEntry::set_text_without_notify(const OUString& rText) { SalInstanceEntry::set_text(rText); }
+
+void JSEntry::replace_selection(const OUString& rText)
+{
+ SalInstanceEntry::replace_selection(rText);
+ sendUpdate();
+}
+
+JSListBox::JSListBox(JSDialogSender* pSender, ::ListBox* pListBox, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : JSWidget<SalInstanceComboBoxWithoutEdit, ::ListBox>(pSender, pListBox, pBuilder,
+ bTakeOwnership)
+{
+}
+
+void JSListBox::insert(int pos, const OUString& rStr, const OUString* pId,
+ const OUString* pIconName, VirtualDevice* pImageSurface)
+{
+ SalInstanceComboBoxWithoutEdit::insert(pos, rStr, pId, pIconName, pImageSurface);
+ sendUpdate();
+}
+
+void JSListBox::remove(int pos)
+{
+ SalInstanceComboBoxWithoutEdit::remove(pos);
+ sendUpdate();
+}
+
+void JSListBox::set_active(int pos)
+{
+ SalInstanceComboBoxWithoutEdit::set_active(pos);
+ sendUpdate();
+}
+
+JSComboBox::JSComboBox(JSDialogSender* pSender, ::ComboBox* pComboBox, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : JSWidget<SalInstanceComboBoxWithEdit, ::ComboBox>(pSender, pComboBox, pBuilder,
+ bTakeOwnership)
+{
+}
+
+void JSComboBox::insert(int pos, const OUString& rStr, const OUString* pId,
+ const OUString* pIconName, VirtualDevice* pImageSurface)
+{
+ SalInstanceComboBoxWithEdit::insert(pos, rStr, pId, pIconName, pImageSurface);
+ sendUpdate();
+}
+
+void JSComboBox::remove(int pos)
+{
+ SalInstanceComboBoxWithEdit::remove(pos);
+ sendUpdate();
+}
+
+void JSComboBox::set_entry_text_without_notify(const OUString& rText)
+{
+ SalInstanceComboBoxWithEdit::set_entry_text(rText);
+}
+
+void JSComboBox::set_entry_text(const OUString& rText)
+{
+ SalInstanceComboBoxWithEdit::set_entry_text(rText);
+
+ std::unique_ptr<jsdialog::ActionDataMap> pMap = std::make_unique<jsdialog::ActionDataMap>();
+ (*pMap)[ACTION_TYPE ""_ostr] = "setText";
+ (*pMap)["text"_ostr] = rText;
+ sendAction(std::move(pMap));
+}
+
+void JSComboBox::set_active(int pos)
+{
+ if (pos == get_active())
+ return;
+
+ SalInstanceComboBoxWithEdit::set_active(pos);
+
+ std::unique_ptr<jsdialog::ActionDataMap> pMap = std::make_unique<jsdialog::ActionDataMap>();
+ (*pMap)[ACTION_TYPE ""_ostr] = "select";
+ (*pMap)["position"_ostr] = OUString::number(pos);
+ sendAction(std::move(pMap));
+}
+
+void JSComboBox::set_active_id(const OUString& rStr)
+{
+ sal_uInt16 nPos = find_id(rStr);
+ set_active(nPos);
+}
+
+bool JSComboBox::changed_by_direct_pick() const { return true; }
+
+void JSComboBox::render_entry(int pos, int dpix, int dpiy)
+{
+ ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITH_ALPHA);
+ pDevice->SetDPIX(96.0 * dpix / 100);
+ pDevice->SetDPIY(96.0 * dpiy / 100);
+
+ Size aRenderSize = signal_custom_get_size(*pDevice);
+ pDevice->SetOutputSize(aRenderSize);
+
+ signal_custom_render(*pDevice, tools::Rectangle(Point(0, 0), aRenderSize), false, get_id(pos));
+
+ BitmapEx aImage = pDevice->GetBitmapEx(Point(0, 0), aRenderSize);
+
+ 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);
+
+ std::unique_ptr<jsdialog::ActionDataMap> pMap = std::make_unique<jsdialog::ActionDataMap>();
+ (*pMap)[ACTION_TYPE ""_ostr] = "rendered_combobox_entry";
+ (*pMap)["pos"_ostr] = OUString::number(pos);
+ (*pMap)["image"_ostr] = aBuffer;
+ sendAction(std::move(pMap));
+ }
+}
+
+JSNotebook::JSNotebook(JSDialogSender* pSender, ::TabControl* pControl,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceNotebook, ::TabControl>(pSender, pControl, pBuilder, bTakeOwnership)
+{
+}
+
+void JSNotebook::remove_page(const OUString& rIdent)
+{
+ SalInstanceNotebook::remove_page(rIdent);
+ sendFullUpdate();
+}
+
+void JSNotebook::insert_page(const OUString& rIdent, const OUString& rLabel, int nPos)
+{
+ SalInstanceNotebook::insert_page(rIdent, rLabel, nPos);
+ sendFullUpdate();
+}
+
+JSVerticalNotebook::JSVerticalNotebook(JSDialogSender* pSender, ::VerticalTabControl* pControl,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceVerticalNotebook, ::VerticalTabControl>(pSender, pControl, pBuilder,
+ bTakeOwnership)
+{
+}
+
+void JSVerticalNotebook::remove_page(const OUString& rIdent)
+{
+ SalInstanceVerticalNotebook::remove_page(rIdent);
+ sendFullUpdate();
+}
+
+void JSVerticalNotebook::insert_page(const OUString& rIdent, const OUString& rLabel, int nPos)
+{
+ SalInstanceVerticalNotebook::insert_page(rIdent, rLabel, nPos);
+ sendFullUpdate();
+}
+
+JSSpinButton::JSSpinButton(JSDialogSender* pSender, ::FormattedField* pSpin,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceSpinButton, ::FormattedField>(pSender, pSpin, pBuilder, bTakeOwnership)
+{
+}
+
+void JSSpinButton::set_value(sal_Int64 value)
+{
+ SalInstanceSpinButton::set_value(value);
+
+ std::unique_ptr<jsdialog::ActionDataMap> pMap = std::make_unique<jsdialog::ActionDataMap>();
+ (*pMap)[ACTION_TYPE ""_ostr] = "setText";
+ (*pMap)["text"_ostr] = OUString::number(m_rFormatter.GetValue());
+ sendAction(std::move(pMap));
+}
+
+JSFormattedSpinButton::JSFormattedSpinButton(JSDialogSender* pSender, ::FormattedField* pSpin,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceFormattedSpinButton, ::FormattedField>(pSender, pSpin, pBuilder,
+ bTakeOwnership)
+{
+}
+
+void JSFormattedSpinButton::set_text(const OUString& rText)
+{
+ SalInstanceFormattedSpinButton::set_text(rText);
+ sendUpdate();
+}
+
+void JSFormattedSpinButton::set_text_without_notify(const OUString& rText)
+{
+ SalInstanceFormattedSpinButton::set_text(rText);
+}
+
+JSMessageDialog::JSMessageDialog(JSDialogSender* pSender, ::MessageDialog* pDialog,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceMessageDialog, ::MessageDialog>(pSender, pDialog, pBuilder,
+ bTakeOwnership)
+{
+}
+
+JSMessageDialog::JSMessageDialog(::MessageDialog* pDialog, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : JSWidget<SalInstanceMessageDialog, ::MessageDialog>(nullptr, pDialog, pBuilder,
+ bTakeOwnership)
+ , m_pOwnedSender(new JSDialogSender(pDialog, pDialog, "dialog"))
+{
+ m_pSender = m_pOwnedSender.get();
+
+ if (pBuilder)
+ return;
+
+ m_sWindowId = OUString::number(m_xMessageDialog->GetLOKWindowId());
+
+ if (::OKButton* pOKBtn
+ = dynamic_cast<::OKButton*>(m_xMessageDialog->get_widget_for_response(RET_OK)))
+ {
+ m_pOK.reset(new JSButton(m_pSender, pOKBtn, nullptr, false));
+ JSInstanceBuilder::AddChildWidget(m_sWindowId, pOKBtn->get_id(), m_pOK.get());
+ m_pOK->connect_clicked(LINK(this, JSMessageDialog, OKHdl));
+ }
+
+ if (::CancelButton* pCancelBtn
+ = dynamic_cast<::CancelButton*>(m_xMessageDialog->get_widget_for_response(RET_CANCEL)))
+ {
+ m_pCancel.reset(new JSButton(m_pSender, pCancelBtn, nullptr, false));
+ JSInstanceBuilder::AddChildWidget(m_sWindowId, pCancelBtn->get_id(), m_pCancel.get());
+ m_pCancel->connect_clicked(LINK(this, JSMessageDialog, CancelHdl));
+ }
+}
+
+JSMessageDialog::~JSMessageDialog()
+{
+ if (m_pOK || m_pCancel)
+ JSInstanceBuilder::RemoveWindowWidget(m_sWindowId);
+}
+
+void JSMessageDialog::RememberMessageDialog()
+{
+ static constexpr OUString sWidgetName = u"__DIALOG__"_ustr;
+ OUString sWindowId = OUString::number(m_xMessageDialog->GetLOKWindowId());
+ if (JSInstanceBuilder::FindWeldWidgetsMap(sWindowId, sWidgetName) != nullptr)
+ return;
+
+ JSInstanceBuilder::InsertWindowToMap(sWindowId);
+ JSInstanceBuilder::RememberWidget(sWindowId, sWidgetName, this);
+}
+
+int JSMessageDialog::run()
+{
+ if (GetLOKNotifier())
+ {
+ RememberMessageDialog();
+ sendFullUpdate();
+ }
+
+ int bRet = SalInstanceMessageDialog::run();
+ return bRet;
+}
+
+bool JSMessageDialog::runAsync(std::shared_ptr<weld::DialogController> aOwner,
+ const std::function<void(sal_Int32)>& rEndDialogFn)
+{
+ bool bRet = SalInstanceMessageDialog::runAsync(aOwner, rEndDialogFn);
+
+ RememberMessageDialog();
+ sendFullUpdate();
+
+ return bRet;
+}
+
+bool JSMessageDialog::runAsync(std::shared_ptr<Dialog> const& rxSelf,
+ const std::function<void(sal_Int32)>& rEndDialogFn)
+{
+ bool bRet = SalInstanceMessageDialog::runAsync(rxSelf, rEndDialogFn);
+
+ RememberMessageDialog();
+ sendFullUpdate();
+
+ return bRet;
+}
+
+IMPL_LINK_NOARG(JSMessageDialog, OKHdl, weld::Button&, void) { response(RET_OK); }
+
+IMPL_LINK_NOARG(JSMessageDialog, CancelHdl, weld::Button&, void) { response(RET_CANCEL); }
+
+void JSMessageDialog::set_primary_text(const OUString& rText)
+{
+ SalInstanceMessageDialog::set_primary_text(rText);
+ sendFullUpdate();
+}
+
+void JSMessageDialog::set_secondary_text(const OUString& rText)
+{
+ SalInstanceMessageDialog::set_secondary_text(rText);
+ sendFullUpdate();
+}
+
+void JSMessageDialog::response(int response)
+{
+ if (response == RET_HELP)
+ {
+ response_help(m_xWidget.get());
+ return;
+ }
+
+ sendClose();
+ SalInstanceMessageDialog::response(response);
+}
+
+JSCheckButton::JSCheckButton(JSDialogSender* pSender, ::CheckBox* pCheckBox,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceCheckButton, ::CheckBox>(pSender, pCheckBox, pBuilder, bTakeOwnership)
+{
+}
+
+void JSCheckButton::set_active(bool active)
+{
+ bool bWasActive = get_active();
+ SalInstanceCheckButton::set_active(active);
+ if (bWasActive != active)
+ sendUpdate();
+}
+
+JSDrawingArea::JSDrawingArea(JSDialogSender* pSender, VclDrawingArea* pDrawingArea,
+ SalInstanceBuilder* pBuilder, const a11yref& rAlly,
+ FactoryFunction pUITestFactoryFunction, void* pUserData)
+ : JSWidget<SalInstanceDrawingArea, VclDrawingArea>(pSender, pDrawingArea, pBuilder, rAlly,
+ std::move(pUITestFactoryFunction), pUserData,
+ false)
+{
+}
+
+void JSDrawingArea::queue_draw()
+{
+ SalInstanceDrawingArea::queue_draw();
+ sendUpdate();
+}
+
+void JSDrawingArea::queue_draw_area(int x, int y, int width, int height)
+{
+ SalInstanceDrawingArea::queue_draw_area(x, y, width, height);
+ sendUpdate();
+}
+
+JSToolbar::JSToolbar(JSDialogSender* pSender, ::ToolBox* pToolbox, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : JSWidget<SalInstanceToolbar, ::ToolBox>(pSender, pToolbox, pBuilder, bTakeOwnership)
+{
+}
+
+void JSToolbar::set_menu_item_active(const OUString& rIdent, bool bActive)
+{
+ bool bWasActive = get_menu_item_active(rIdent);
+ SalInstanceToolbar::set_menu_item_active(rIdent, bActive);
+
+ ToolBoxItemId nItemId = m_xToolBox->GetItemId(rIdent);
+ VclPtr<vcl::Window> pFloat = m_aFloats[nItemId];
+
+ if (!pFloat)
+ return;
+
+ // See WeldToolbarPopup : include/svtools/toolbarmenu.hxx
+ // TopLevel (Popover) -> Container -> main container of the popup
+ vcl::Window* pPopupRoot = pFloat->GetChild(0);
+ if (pPopupRoot)
+ pPopupRoot = pPopupRoot->GetChild(0);
+
+ if (pPopupRoot)
+ {
+ if (bActive)
+ {
+ JSInstanceBuilder::RememberPopup(OUString::number(pPopupRoot->GetLOKWindowId()),
+ pFloat);
+ sendPopup(pPopupRoot, m_xToolBox->get_id(), rIdent);
+ }
+ else if (bWasActive)
+ {
+ JSInstanceBuilder::ForgetPopup(OUString::number(pPopupRoot->GetLOKWindowId()));
+ sendClosePopup(pPopupRoot->GetLOKWindowId());
+ }
+ }
+}
+
+void JSToolbar::set_item_sensitive(const OUString& rIdent, bool bSensitive)
+{
+ bool bWasSensitive = get_item_sensitive(rIdent);
+ SalInstanceToolbar::set_item_sensitive(rIdent, bSensitive);
+ if (bWasSensitive != bSensitive)
+ sendUpdate();
+}
+
+void JSToolbar::set_item_icon_name(const OUString& rIdent, const OUString& rIconName)
+{
+ SalInstanceToolbar::set_item_icon_name(rIdent, rIconName);
+ sendUpdate();
+}
+
+JSTextView::JSTextView(JSDialogSender* pSender, ::VclMultiLineEdit* pTextView,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceTextView, ::VclMultiLineEdit>(pSender, pTextView, pBuilder,
+ bTakeOwnership)
+{
+}
+
+void JSTextView::set_text(const OUString& rText)
+{
+ SalInstanceTextView::set_text(rText);
+ sendUpdate();
+}
+
+void JSTextView::set_text_without_notify(const OUString& rText)
+{
+ SalInstanceTextView::set_text(rText);
+}
+
+void JSTextView::replace_selection(const OUString& rText)
+{
+ SalInstanceTextView::replace_selection(rText);
+ sendUpdate();
+}
+
+JSTreeView::JSTreeView(JSDialogSender* pSender, ::SvTabListBox* pTreeView,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceTreeView, ::SvTabListBox>(pSender, pTreeView, pBuilder, bTakeOwnership)
+{
+}
+
+void JSTreeView::set_toggle(int pos, TriState eState, int col)
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, 0);
+
+ while (pEntry && pos--)
+ pEntry = m_xTreeView->Next(pEntry);
+
+ if (pEntry)
+ {
+ SalInstanceTreeView::set_toggle(pEntry, eState, col);
+ signal_toggled(iter_col(SalInstanceTreeIter(pEntry), col));
+
+ sendUpdate();
+ }
+}
+
+void JSTreeView::set_toggle(const weld::TreeIter& rIter, TriState bOn, int col)
+{
+ SalInstanceTreeView::set_toggle(rIter, bOn, col);
+ sendUpdate();
+}
+
+void JSTreeView::select(int pos)
+{
+ assert(m_xTreeView->IsUpdateMode() && "don't select when frozen");
+ disable_notify_events();
+ if (pos == -1 || (pos == 0 && n_children() == 0))
+ m_xTreeView->SelectAll(false);
+ else
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, 0);
+
+ while (pEntry && pos--)
+ pEntry = m_xTreeView->Next(pEntry);
+
+ if (pEntry)
+ {
+ m_xTreeView->Select(pEntry, true);
+ m_xTreeView->MakeVisible(pEntry);
+ }
+ }
+ enable_notify_events();
+
+ std::unique_ptr<jsdialog::ActionDataMap> pMap = std::make_unique<jsdialog::ActionDataMap>();
+ (*pMap)[ACTION_TYPE ""_ostr] = "select";
+ (*pMap)["position"_ostr] = OUString::number(pos);
+ sendAction(std::move(pMap));
+}
+
+weld::TreeView* JSTreeView::get_drag_source() const { return g_DragSource; }
+
+void JSTreeView::drag_start() { g_DragSource = this; }
+
+void JSTreeView::drag_end()
+{
+ css::datatransfer::dnd::XDropTarget* xDropTarget = m_xDropTarget.get();
+ if (xDropTarget)
+ {
+ css::datatransfer::dnd::DropTargetDropEvent aEvent;
+ aEvent.Source = xDropTarget;
+ aEvent.Context = new JSDropTargetDropContext();
+ // dummy values
+ aEvent.LocationX = 50;
+ aEvent.LocationY = 50;
+ aEvent.DropAction = css::datatransfer::dnd::DNDConstants::ACTION_DEFAULT;
+ aEvent.SourceActions = css::datatransfer::dnd::DNDConstants::ACTION_DEFAULT;
+
+ m_xDropTarget->fire_drop(aEvent);
+
+ sendUpdate();
+ if (g_DragSource)
+ g_DragSource->sendUpdate();
+ }
+
+ g_DragSource = nullptr;
+}
+
+void JSTreeView::insert(const weld::TreeIter* pParent, int pos, const OUString* pStr,
+ const OUString* pId, const OUString* pIconName,
+ VirtualDevice* pImageSurface, bool bChildrenOnDemand, weld::TreeIter* pRet)
+{
+ SalInstanceTreeView::insert(pParent, pos, pStr, pId, pIconName, pImageSurface,
+ bChildrenOnDemand, pRet);
+
+ sendUpdate();
+}
+
+void JSTreeView::set_text(int row, const OUString& rText, int col)
+{
+ SalInstanceTreeView::set_text(row, rText, col);
+ sendUpdate();
+}
+
+void JSTreeView::set_text(const weld::TreeIter& rIter, const OUString& rStr, int col)
+{
+ SalInstanceTreeView::set_text(rIter, rStr, col);
+ sendUpdate();
+}
+
+void JSTreeView::remove(int pos)
+{
+ SalInstanceTreeView::remove(pos);
+ sendUpdate();
+}
+
+void JSTreeView::remove(const weld::TreeIter& rIter)
+{
+ SalInstanceTreeView::remove(rIter);
+ sendUpdate();
+}
+
+void JSTreeView::clear()
+{
+ SalInstanceTreeView::clear();
+ sendUpdate();
+}
+
+void JSTreeView::set_cursor_without_notify(const weld::TreeIter& rIter)
+{
+ SalInstanceTreeView::set_cursor(rIter);
+}
+
+void JSTreeView::set_cursor(const weld::TreeIter& rIter)
+{
+ SalInstanceTreeView::set_cursor(rIter);
+ sendUpdate();
+}
+
+void JSTreeView::set_cursor(int pos)
+{
+ SalInstanceTreeView::set_cursor(pos);
+ sendUpdate();
+}
+
+void JSTreeView::expand_row(const weld::TreeIter& rIter)
+{
+ bool bNotify = false;
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ if (!m_xTreeView->IsExpanded(rVclIter.iter))
+ bNotify = true;
+
+ SalInstanceTreeView::expand_row(rIter);
+
+ if (bNotify)
+ sendUpdate();
+}
+
+void JSTreeView::collapse_row(const weld::TreeIter& rIter)
+{
+ bool bNotify = false;
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ if (m_xTreeView->IsExpanded(rVclIter.iter))
+ bNotify = true;
+
+ SalInstanceTreeView::collapse_row(rIter);
+
+ if (bNotify)
+ sendUpdate();
+}
+
+JSExpander::JSExpander(JSDialogSender* pSender, ::VclExpander* pExpander,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceExpander, ::VclExpander>(pSender, pExpander, pBuilder, bTakeOwnership)
+{
+}
+
+void JSExpander::set_expanded(bool bExpand)
+{
+ SalInstanceExpander::set_expanded(bExpand);
+ sendUpdate();
+}
+
+JSIconView::JSIconView(JSDialogSender* pSender, ::IconView* pIconView, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : JSWidget<SalInstanceIconView, ::IconView>(pSender, pIconView, pBuilder, bTakeOwnership)
+{
+}
+
+void JSIconView::insert(int pos, const OUString* pStr, const OUString* pId,
+ const OUString* pIconName, weld::TreeIter* pRet)
+{
+ SalInstanceIconView::insert(pos, pStr, pId, pIconName, pRet);
+ sendUpdate();
+}
+
+void JSIconView::insert(int pos, const OUString* pStr, const OUString* pId,
+ const VirtualDevice* pIcon, weld::TreeIter* pRet)
+{
+ SalInstanceIconView::insert(pos, pStr, pId, pIcon, pRet);
+ sendUpdate();
+}
+
+void JSIconView::insert_separator(int pos, const OUString* pId)
+{
+ SalInstanceIconView::insert_separator(pos, pId);
+ sendUpdate();
+}
+
+void JSIconView::clear()
+{
+ SalInstanceIconView::clear();
+ sendUpdate();
+}
+
+void JSIconView::select(int pos)
+{
+ SalInstanceIconView::select(pos);
+
+ std::unique_ptr<jsdialog::ActionDataMap> pMap = std::make_unique<jsdialog::ActionDataMap>();
+ (*pMap)[ACTION_TYPE ""_ostr] = "select";
+ (*pMap)["position"_ostr] = OUString::number(pos);
+ sendAction(std::move(pMap));
+}
+
+void JSIconView::unselect(int pos)
+{
+ SalInstanceIconView::unselect(pos);
+ sendUpdate();
+}
+
+JSRadioButton::JSRadioButton(JSDialogSender* pSender, ::RadioButton* pRadioButton,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceRadioButton, ::RadioButton>(pSender, pRadioButton, pBuilder,
+ bTakeOwnership)
+{
+}
+
+void JSRadioButton::set_active(bool active)
+{
+ SalInstanceRadioButton::set_active(active);
+ sendUpdate();
+}
+
+JSFrame::JSFrame(JSDialogSender* pSender, ::VclFrame* pFrame, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : JSWidget<SalInstanceFrame, ::VclFrame>(pSender, pFrame, pBuilder, bTakeOwnership)
+{
+}
+
+JSMenuButton::JSMenuButton(JSDialogSender* pSender, ::MenuButton* pMenuButton,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstanceMenuButton, ::MenuButton>(pSender, pMenuButton, pBuilder, bTakeOwnership)
+{
+}
+
+void JSMenuButton::set_label(const OUString& rText)
+{
+ OUString aPreviousLabel = get_label();
+ SalInstanceMenuButton::set_label(rText);
+ if (aPreviousLabel != rText)
+ sendUpdate();
+}
+
+void JSMenuButton::set_image(VirtualDevice* pDevice)
+{
+ SalInstanceMenuButton::set_image(pDevice);
+ sendUpdate();
+}
+
+void JSMenuButton::set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage)
+{
+ SalInstanceMenuButton::set_image(rImage);
+ sendUpdate();
+}
+
+void JSMenuButton::set_active(bool bActive)
+{
+ SalInstanceMenuButton::set_active(bActive);
+
+ VclPtr<vcl::Window> pPopup = m_xMenuButton->GetPopover();
+ if (pPopup)
+ {
+ if (bActive)
+ sendPopup(pPopup->GetChild(0), m_xMenuButton->get_id(), m_xMenuButton->get_id());
+ else
+ sendClosePopup(pPopup->GetChild(0)->GetLOKWindowId());
+ }
+}
+
+JSPopover::JSPopover(JSDialogSender* pSender, DockingWindow* pDockingWindow,
+ SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : JSWidget<SalInstancePopover, DockingWindow>(pSender, pDockingWindow, pBuilder, bTakeOwnership)
+ , mnWindowId(0)
+{
+}
+
+void JSPopover::popup_at_rect(weld::Widget* pParent, const tools::Rectangle& rRect,
+ weld::Placement ePlace)
+{
+ SalInstancePopover::popup_at_rect(pParent, rRect, ePlace);
+ sendPopup(getWidget()->GetChild(0), "_POPOVER_", "_POPOVER_");
+}
+
+void JSPopover::popdown()
+{
+ vcl::Window* pPopup = JSInstanceBuilder::FindPopup(OUString::number(mnWindowId));
+
+ if (pPopup)
+ {
+ sendClosePopup(mnWindowId);
+ vcl::Window::GetDockingManager()->EndPopupMode(pPopup);
+ }
+
+ if (getWidget() && getWidget()->GetChild(0))
+ sendClosePopup(getWidget()->GetChild(0)->GetLOKWindowId());
+
+ SalInstancePopover::popdown();
+}
+
+JSBox::JSBox(JSDialogSender* pSender, VclBox* pBox, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : JSWidget<SalInstanceBox, VclBox>(pSender, pBox, pBuilder, bTakeOwnership)
+{
+}
+
+void JSBox::reorder_child(weld::Widget* pWidget, int nNewPosition)
+{
+ SalInstanceBox::reorder_child(pWidget, nNewPosition);
+ sendUpdate();
+}
+
+JSImage::JSImage(JSDialogSender* pSender, FixedImage* pImage, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : JSWidget<SalInstanceImage, FixedImage>(pSender, pImage, pBuilder, bTakeOwnership)
+{
+}
+
+void JSImage::set_image(VirtualDevice* pDevice)
+{
+ SalInstanceImage::set_image(pDevice);
+ sendUpdate();
+}
+
+void JSImage::set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage)
+{
+ SalInstanceImage::set_image(rImage);
+ sendUpdate();
+}
+
+JSCalendar::JSCalendar(JSDialogSender* pSender, ::Calendar* pCalendar, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : JSWidget<SalInstanceCalendar, ::Calendar>(pSender, pCalendar, pBuilder, bTakeOwnership)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/null/printerinfomanager.cxx b/vcl/null/printerinfomanager.cxx
new file mode 100644
index 0000000000..55d81e65a2
--- /dev/null
+++ b/vcl/null/printerinfomanager.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 <memory>
+#include <printerinfomanager.hxx>
+#include <unx/gendata.hxx>
+
+// needed since we declare a std::unique_ptr<SystemQueueInfo>
+namespace psp
+{
+ class SystemQueueInfo
+ {
+ };
+}
+
+using namespace psp;
+using namespace osl;
+
+PrinterInfoManager& PrinterInfoManager::get()
+{
+ GenericUnixSalData* pSalData = GetGenericUnixSalData();
+ if (!pSalData->m_pPrinterInfoManager)
+ pSalData->m_pPrinterInfoManager.reset(new PrinterInfoManager());
+ return *pSalData->m_pPrinterInfoManager;
+}
+
+PrinterInfoManager::PrinterInfoManager( Type eType ) :
+ m_pQueueInfo( nullptr ),
+ m_eType( eType ),
+ m_aSystemDefaultPaper( "A4" )
+{
+ // initSystemDefaultPaper();
+}
+
+PrinterInfoManager::~PrinterInfoManager()
+{
+
+}
+
+bool PrinterInfoManager::checkPrintersChanged( bool /* bWait */ )
+{
+ return false;
+}
+
+void PrinterInfoManager::initialize()
+{
+ // ???
+}
+
+void PrinterInfoManager::listPrinters( ::std::vector< OUString >& rVector ) const
+{
+ (void) this;
+
+ rVector.clear();
+}
+
+const PrinterInfo& PrinterInfoManager::getPrinterInfo( const OUString& /* rPrinter */ ) const
+{
+ static PrinterInfo aEmptyInfo;
+
+ (void) this;
+
+ return aEmptyInfo;
+}
+
+bool PrinterInfoManager::checkFeatureToken( const OUString& /* rPrinterName */, std::string_view /* pToken */ ) const
+{
+ (void) this;
+
+ return false;
+}
+
+FILE* PrinterInfoManager::startSpool( const OUString& /* rPrintername */, bool /* bQuickCommand */ )
+{
+ return nullptr;
+}
+
+bool PrinterInfoManager::endSpool( const OUString& /*rPrintername*/, const OUString& /*rJobTitle*/, FILE* /* pFile */, const JobData& /*rDocumentJobData*/, bool /*bBanner*/, const OUString& /*rFaxNumber*/ )
+{
+ return true;
+}
+
+void PrinterInfoManager::setupJobContextData( JobData& /* rData */ )
+{
+
+}
+
+void PrinterInfoManager::setDefaultPaper( PPDContext& /* rContext */ ) const
+{
+ (void) this;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DataFlavorMapping.cxx b/vcl/osx/DataFlavorMapping.cxx
new file mode 100644
index 0000000000..361e268bcd
--- /dev/null
+++ b/vcl/osx/DataFlavorMapping.cxx
@@ -0,0 +1,788 @@
+/* -*- 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 <sal/config.h>
+#include <sal/log.hxx>
+
+#include "DataFlavorMapping.hxx"
+#include "HtmlFmtFlt.hxx"
+#include "PictToBmpFlt.hxx"
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/datatransfer/XMimeContentType.hpp>
+#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <comphelper/processfactory.hxx>
+
+#include <rtl/ustring.hxx>
+#include <osl/endian.h>
+
+#include <cassert>
+#include <string.h>
+#include <string_view>
+
+#include <premac.h>
+#include <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::uno;
+using namespace com::sun::star::lang;
+using namespace cppu;
+
+namespace
+{
+ /* Determine whether or not a DataFlavor is valid.
+ */
+ bool isValidFlavor(const DataFlavor& aFlavor)
+ {
+ size_t len = aFlavor.MimeType.getLength();
+ Type dtype = aFlavor.DataType;
+ return ((len > 0) && ((dtype == cppu::UnoType<Sequence<sal_Int8>>::get()) || (dtype == cppu::UnoType<OUString>::get())));
+ }
+
+ OUString NSStringToOUString( const NSString* cfString)
+ {
+ assert(cfString && "Invalid parameter");
+
+ const char* utf8Str = [cfString UTF8String];
+ unsigned int len = rtl_str_getLength(utf8Str);
+
+ return OUString(utf8Str, len, RTL_TEXTENCODING_UTF8);
+ }
+
+ NSString* OUStringToNSString(std::u16string_view ustring)
+ {
+ OString utf8Str = OUStringToOString(ustring, RTL_TEXTENCODING_UTF8);
+ return [NSString stringWithCString: utf8Str.getStr() encoding: NSUTF8StringEncoding];
+ }
+
+ const char* FLAVOR_SESX = "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"";
+ const char* FLAVOR_SLSDX = "application/x-openoffice-linksrcdescriptor-xml;windows_formatname=\"Star Link Source Descriptor (XML)\"";
+ const char* FLAVOR_ESX = "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"";
+ const char* FLAVOR_LSX = "application/x-openoffice-link-source-xml;windows_formatname=\"Star Link Source (XML)\"";
+ const char* FLAVOR_EOX = "application/x-openoffice-embedded-obj-xml;windows_formatname=\"Star Embedded Object (XML)\"";
+ const char* FLAVOR_SVXB = "application/x-openoffice-svbx;windows_formatname=\"SVXB (StarView Bitmap/Animation)\"";
+ const char* FLAVOR_GDIMF = "application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\"";
+ const char* FLAVOR_WMF = "application/x-openoffice-wmf;windows_formatname=\"Image WMF\"";
+ const char* FLAVOR_EMF = "application/x-openoffice-emf;windows_formatname=\"Image EMF\"";
+ const char* FLAVOR_SODX = "application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star Object Descriptor (XML)\"";
+ const char* FLAVOR_LINK = "application/x-openoffice-link;windows_formatname=\"Link\"";
+ const char* FLAVOR_DUMMY_INTERNAL = "application/x-openoffice-internal";
+
+ struct FlavorMap
+ {
+ const NSString* SystemFlavor;
+ const char* OOoFlavor;
+ const char* HumanPresentableName;
+ bool DataTypeOUString; // sequence<byte> otherwise
+ };
+
+ // This is a list of the bidirectional mapping between (internal) MIME types and (system)
+ // pasteboard types.
+
+ // Only pasteboard types mentioned here will be recognized, mapped, and available for pasting in a
+ // fresh LibreOffice process. When copy-pasting in-process, the situation is different.
+
+ // Also MIME types not mentioned here will be stored on the pasteboard (using the same type name),
+ // though. But that is IMHO a bit pointless as they in general won't then be pasteable anyway in a
+ // new LibreOffice process. See the use of the maOfficeOnlyTypes array.
+
+ // The SystemFlavor member is nil for the cases where there is no predefined pasteboard type UTI
+ // and we use the internal MIME type (media type) also on the pasteboard. That is OK in macOS,
+ // there is no requirement that the types are well-formed UTIs. It is different on iOS, I think,
+ // though. For an introduction to UTIs, see for instance
+ // https://alastairs-place.net/blog/2012/06/06/utis-are-better-than-you-think-and-heres-why/
+ //
+ // In those cases the MIME type might actually have parameters appended, separated by semicolons.
+ // At least the FLAVOR_SODX one must have at least a typename="%PRODUCTNAME %PRODUCTVERSION
+ // Spreadsheet" parameter (with macros expanded and translated) for LO to recognise it. See
+ // lcl_TestFormat() in sc/source/ui/view/cellsh.cxx.
+
+ const FlavorMap flavorMap[] =
+ {
+ { NSPasteboardTypeString, "text/plain;charset=utf-16", "Unicode Text (UTF-16)", true },
+ { NSPasteboardTypeRTF, "text/rtf", "Rich Text Format", false },
+ { NSPasteboardTypePDF, "application/pdf", "PDF File", false },
+ { NSPasteboardTypeTIFF, "image/png", "Portable Network Graphics", false },
+ { NSPasteboardTypeHTML, "text/html", "Plain Html", false },
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSFilenamesPboardType' is deprecated: first deprecated in macOS 10.14 - Create
+ // multiple pasteboard items with NSPasteboardTypeFileURL or kUTTypeFileURL instead"
+ { NSFilenamesPboardType, "application/x-openoffice-filelist;windows_formatname=\"FileList\"", "FileList", false },
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ { nil, FLAVOR_SESX, "Star Embed Source (XML)", false },
+ { nil, FLAVOR_SLSDX, "Star Link Source Descriptor (XML)", false },
+ { nil, FLAVOR_ESX, "Star Embed Source (XML)", false },
+ { nil, FLAVOR_LSX, "Star Link Source (XML)", false },
+ { nil, FLAVOR_EOX, "Star Embedded Object (XML)", false },
+ { nil, FLAVOR_SVXB, "SVXB (StarView Bitmap/Animation", false },
+ { nil, FLAVOR_GDIMF, "GDIMetaFile", false },
+ { nil, FLAVOR_WMF, "Windows MetaFile", false },
+ { nil, FLAVOR_EMF, "Windows Enhanced MetaFile", false },
+ { nil, FLAVOR_SODX, "Star Object Descriptor (XML)", false },
+ { nil, FLAVOR_LINK, "Dynamic Data Exchange (DDE link)", false },
+ { nil, FLAVOR_DUMMY_INTERNAL, "internal data",false }
+ };
+
+ #define SIZE_FLAVOR_MAP (sizeof(flavorMap)/sizeof(FlavorMap))
+
+ bool isByteSequenceType(const Type& theType)
+ {
+ return (theType == cppu::UnoType<Sequence<sal_Int8>>::get());
+ }
+
+ bool isOUStringType(const Type& theType)
+ {
+ return (theType == cppu::UnoType<OUString>::get() );
+ }
+
+/* A base class for other data provider.
+ */
+class DataProviderBaseImpl : public DataProvider
+{
+public:
+ DataProviderBaseImpl(const Any& data);
+ DataProviderBaseImpl(id data);
+ virtual ~DataProviderBaseImpl() override;
+
+protected:
+ Any mData;
+ //NSData* mSystemData;
+ id mSystemData;
+};
+
+} // unnamed namespace
+
+DataProviderBaseImpl::DataProviderBaseImpl(const Any& data) :
+ mData(data),
+ mSystemData(nil)
+{
+}
+
+DataProviderBaseImpl::DataProviderBaseImpl(id data) :
+ mSystemData(data)
+{
+ [mSystemData retain];
+}
+
+DataProviderBaseImpl::~DataProviderBaseImpl()
+{
+ if (mSystemData)
+ {
+ [mSystemData release];
+ }
+}
+
+namespace {
+
+class UniDataProvider : public DataProviderBaseImpl
+{
+public:
+ UniDataProvider(const Any& data);
+
+ UniDataProvider(NSData* data);
+
+ virtual NSData* getSystemData() override;
+
+ virtual Any getOOoData() override;
+};
+
+}
+
+UniDataProvider::UniDataProvider(const Any& data) :
+ DataProviderBaseImpl(data)
+{
+}
+
+UniDataProvider::UniDataProvider(NSData* data) :
+ DataProviderBaseImpl(data)
+{
+}
+
+NSData* UniDataProvider::getSystemData()
+{
+ OUString ustr;
+ mData >>= ustr;
+
+ OString strUtf8;
+ ustr.convertToString(&strUtf8, RTL_TEXTENCODING_UTF8, OUSTRING_TO_OSTRING_CVTFLAGS);
+
+ return [NSData dataWithBytes: strUtf8.getStr() length: strUtf8.getLength()];
+}
+
+Any UniDataProvider::getOOoData()
+{
+ Any oOOData;
+
+ if (mSystemData)
+ {
+ oOOData <<= OUString(static_cast<const char*>([mSystemData bytes]),
+ [mSystemData length],
+ RTL_TEXTENCODING_UTF8);
+ }
+ else
+ {
+ oOOData = mData;
+ }
+
+ return oOOData;
+}
+
+namespace {
+
+class ByteSequenceDataProvider : public DataProviderBaseImpl
+{
+public:
+ ByteSequenceDataProvider(const Any& data);
+
+ ByteSequenceDataProvider(NSData* data);
+
+ virtual NSData* getSystemData() override;
+
+ virtual Any getOOoData() override;
+};
+
+}
+
+ByteSequenceDataProvider::ByteSequenceDataProvider(const Any& data) :
+ DataProviderBaseImpl(data)
+{
+}
+
+ByteSequenceDataProvider::ByteSequenceDataProvider(NSData* data) :
+ DataProviderBaseImpl(data)
+{
+}
+
+NSData* ByteSequenceDataProvider::getSystemData()
+{
+ Sequence<sal_Int8> rawData;
+ mData >>= rawData;
+
+ return [NSData dataWithBytes: rawData.getArray() length: rawData.getLength()];
+}
+
+Any ByteSequenceDataProvider::getOOoData()
+{
+ Any oOOData;
+
+ if (mSystemData)
+ {
+ unsigned int flavorDataLength = [mSystemData length];
+ Sequence<sal_Int8> byteSequence;
+ byteSequence.realloc(flavorDataLength);
+ memcpy(byteSequence.getArray(), [mSystemData bytes], flavorDataLength);
+ oOOData <<= byteSequence;
+ }
+ else
+ {
+ oOOData = mData;
+ }
+
+ return oOOData;
+}
+
+namespace {
+
+class HTMLFormatDataProvider : public DataProviderBaseImpl
+{
+public:
+ HTMLFormatDataProvider(NSData* data);
+
+ virtual NSData* getSystemData() override;
+
+ virtual Any getOOoData() override;
+};
+
+}
+
+HTMLFormatDataProvider::HTMLFormatDataProvider(NSData* data) :
+ DataProviderBaseImpl(data)
+{
+}
+
+NSData* HTMLFormatDataProvider::getSystemData()
+{
+ Sequence<sal_Int8> textHtmlData;
+ mData >>= textHtmlData;
+
+ Sequence<sal_Int8> htmlFormatData = TextHtmlToHTMLFormat(textHtmlData);
+
+ return [NSData dataWithBytes: htmlFormatData.getArray() length: htmlFormatData.getLength()];
+}
+
+Any HTMLFormatDataProvider::getOOoData()
+{
+ Any oOOData;
+
+ if (mSystemData)
+ {
+ unsigned int flavorDataLength = [mSystemData length];
+ Sequence<sal_Int8> unkHtmlData;
+
+ unkHtmlData.realloc(flavorDataLength);
+ memcpy(unkHtmlData.getArray(), [mSystemData bytes], flavorDataLength);
+
+ Sequence<sal_Int8>* pPlainHtml = &unkHtmlData;
+ Sequence<sal_Int8> plainHtml;
+
+ if (isHTMLFormat(unkHtmlData))
+ {
+ plainHtml = HTMLFormatToTextHtml(unkHtmlData);
+ pPlainHtml = &plainHtml;
+ }
+
+ oOOData <<= *pPlainHtml;
+ }
+ else
+ {
+ oOOData = mData;
+ }
+
+ return oOOData;
+}
+
+namespace {
+
+
+class PNGDataProvider : public DataProviderBaseImpl
+{
+ NSBitmapImageFileType meImageType;
+public:
+ PNGDataProvider( const Any&, NSBitmapImageFileType);
+
+ PNGDataProvider( NSData*, NSBitmapImageFileType);
+
+ virtual NSData* getSystemData() override;
+
+ virtual Any getOOoData() override;
+};
+
+}
+
+PNGDataProvider::PNGDataProvider( const Any& data, NSBitmapImageFileType eImageType) :
+ DataProviderBaseImpl(data),
+ meImageType( eImageType )
+{
+}
+
+PNGDataProvider::PNGDataProvider( NSData* data, NSBitmapImageFileType eImageType) :
+ DataProviderBaseImpl(data),
+ meImageType( eImageType )
+{
+}
+
+NSData* PNGDataProvider::getSystemData()
+{
+ Sequence<sal_Int8> pngData;
+ mData >>= pngData;
+
+ Sequence<sal_Int8> imgData;
+ NSData* sysData = nullptr;
+ if( PNGToImage( pngData, imgData, meImageType))
+ sysData = [NSData dataWithBytes: imgData.getArray() length: imgData.getLength()];
+
+ return sysData;
+}
+
+/* The AOO 'PCT' filter is not yet good enough to be used
+ and there is no flavor defined for exchanging 'PCT' with AOO
+ so we convert 'PCT' to a PNG and provide this to AOO
+*/
+Any PNGDataProvider::getOOoData()
+{
+ Any oOOData;
+
+ if( mSystemData)
+ {
+ const unsigned int flavorDataLength = [mSystemData length];
+ Sequence<sal_Int8> imgData( flavorDataLength);
+ memcpy( imgData.getArray(), [mSystemData bytes], flavorDataLength);
+
+ Sequence<sal_Int8> pngData;
+ if( ImageToPNG( imgData, pngData))
+ oOOData <<= pngData;
+ }
+ else
+ {
+ oOOData = mData;
+ }
+
+ return oOOData;
+}
+
+namespace {
+
+class FileListDataProvider : public DataProviderBaseImpl
+{
+public:
+ FileListDataProvider(const Any& data);
+ FileListDataProvider(NSArray* data);
+
+ virtual NSData* getSystemData() override;
+ virtual Any getOOoData() override;
+};
+
+}
+
+FileListDataProvider::FileListDataProvider(const Any& data) :
+ DataProviderBaseImpl(data)
+{
+}
+
+FileListDataProvider::FileListDataProvider(NSArray* data) :
+ DataProviderBaseImpl(data)
+{
+}
+
+NSData* FileListDataProvider::getSystemData()
+{
+ return [NSData data];
+}
+
+Any FileListDataProvider::getOOoData()
+{
+ Any oOOData;
+
+ if (mSystemData)
+ {
+ size_t length = [mSystemData count];
+ size_t lenSeqRequired = 0;
+
+ for (size_t i = 0; i < length; i++)
+ {
+ NSString* fname = [mSystemData objectAtIndex: i];
+ lenSeqRequired += [fname maximumLengthOfBytesUsingEncoding: NSUnicodeStringEncoding] + sizeof(unichar);
+ }
+
+ Sequence<sal_Int8> oOOFileList(lenSeqRequired);
+ unichar* pBuffer = reinterpret_cast<unichar*>(oOOFileList.getArray());
+ memset(pBuffer, 0, lenSeqRequired);
+
+ for (size_t i = 0; i < length; i++)
+ {
+ NSString* fname = [mSystemData objectAtIndex: i];
+ [fname getCharacters: pBuffer];
+ size_t l = [fname length];
+ pBuffer += l + 1;
+ }
+
+ oOOData <<= oOOFileList;
+ }
+ else
+ {
+ oOOData = mData;
+ }
+
+ return oOOData;
+}
+
+DataFlavorMapper::DataFlavorMapper()
+{
+ Reference<XComponentContext> xContext = comphelper::getProcessComponentContext();
+ mrXMimeCntFactory = MimeContentTypeFactory::create( xContext );
+}
+
+DataFlavorMapper::~DataFlavorMapper()
+{
+ // release potential NSStrings
+ for( OfficeOnlyTypes::iterator it = maOfficeOnlyTypes.begin(); it != maOfficeOnlyTypes.end(); ++it )
+ {
+ [it->second release];
+ it->second = nil;
+ }
+}
+
+DataFlavor DataFlavorMapper::systemToOpenOfficeFlavor( const NSString* systemDataFlavor) const
+{
+ DataFlavor oOOFlavor;
+
+ for (size_t i = 0; i < SIZE_FLAVOR_MAP; i++)
+ {
+ if ((flavorMap[i].SystemFlavor == nil && ([systemDataFlavor isEqualToString:[NSString stringWithUTF8String:flavorMap[i].OOoFlavor]]
+ ||
+ [systemDataFlavor hasPrefix:[[NSString stringWithUTF8String:flavorMap[i].OOoFlavor] stringByAppendingString:@";"]]))
+ ||
+ (flavorMap[i].SystemFlavor != nil && [systemDataFlavor isEqualToString:const_cast<NSString*>(flavorMap[i].SystemFlavor)]))
+ {
+ if (flavorMap[i].SystemFlavor == nil)
+ oOOFlavor.MimeType = NSStringToOUString(systemDataFlavor);
+ else
+ oOOFlavor.MimeType = OUString::createFromAscii(flavorMap[i].OOoFlavor);
+ oOOFlavor.HumanPresentableName = OUString::createFromAscii(flavorMap[i].HumanPresentableName);
+ oOOFlavor.DataType = flavorMap[i].DataTypeOUString ? cppu::UnoType<OUString>::get() : cppu::UnoType<Sequence<sal_Int8>>::get();
+ return oOOFlavor;
+ }
+ } // for
+
+ // look if this might be an internal type; if it comes in here it must have
+ // been through openOfficeToSystemFlavor before, so it should then be in the map
+ OUString aTryFlavor( NSStringToOUString( systemDataFlavor ) );
+ if( maOfficeOnlyTypes.find( aTryFlavor ) != maOfficeOnlyTypes.end() )
+ {
+ oOOFlavor.MimeType = aTryFlavor;
+ oOOFlavor.HumanPresentableName.clear();
+ oOOFlavor.DataType = cppu::UnoType<Sequence<sal_Int8>>::get();
+ }
+
+ return oOOFlavor;
+}
+
+const NSString* DataFlavorMapper::openOfficeToSystemFlavor( const DataFlavor& oOOFlavor, bool& rbInternal, bool bIsSystemClipboard ) const
+{
+ const NSString* sysFlavor = nullptr;
+ rbInternal = false;
+
+ for( size_t i = 0; i < SIZE_FLAVOR_MAP; ++i )
+ {
+ if (oOOFlavor.MimeType.startsWith(OUString::createFromAscii(flavorMap[i].OOoFlavor)))
+ {
+ // tdf#151679 Do not push FLAVOR_LINK to macOS general pasteboard
+ // When copying text from a Writer document and the FLAVOR_LINK
+ // flavor is pasted, Writer will edit the copied text in order
+ // to create a bookmark for DDE.
+ // The problem is that many macOS clipboard managers fetch *all*
+ // available flavors that are available in the macOS general
+ // pasteboard instead of just one flavor and this triggers the
+ // FLAVOR_LINK flavor's unusual editing behavor in Writer every
+ // time the user copies Writer text.
+ // Users have reported in tdf#1515679 that on macOS, Microsoft
+ // Writer, Excel, and PowerPoint do not recognize this flavor
+ // like is done on Windows so, in theory, we can just filter out
+ // this flavor when adding flavors to the macOS general pasteboard.
+ // With this change, the FLAVOR_LINK flavor will still be visible
+ // when copying and pasting within a single LibreOffice instance
+ // as well as when dragging from LibreOffice to other applications.
+ if (bIsSystemClipboard && !strcmp(FLAVOR_LINK, flavorMap[i].OOoFlavor))
+ return nullptr;
+
+ if (flavorMap[i].SystemFlavor != nil)
+ sysFlavor = flavorMap[i].SystemFlavor;
+ else
+ sysFlavor = OUStringToNSString(oOOFlavor.MimeType);
+ }
+ }
+
+ if(!sysFlavor)
+ {
+ rbInternal = true;
+ OfficeOnlyTypes::const_iterator it = maOfficeOnlyTypes.find( oOOFlavor.MimeType );
+
+ if( it == maOfficeOnlyTypes.end() )
+ sysFlavor = maOfficeOnlyTypes[ oOOFlavor.MimeType ] = OUStringToNSString( oOOFlavor.MimeType );
+ else
+ sysFlavor = it->second;
+ }
+
+ return sysFlavor;
+}
+
+NSString* DataFlavorMapper::openOfficeImageToSystemFlavor(NSPasteboard* pPasteboard)
+{
+ NSArray *supportedTypes = [NSArray arrayWithObjects: NSPasteboardTypeTIFF, nil];
+ NSString *sysFlavor = [pPasteboard availableTypeFromArray:supportedTypes];
+ return sysFlavor;
+}
+
+DataProviderPtr_t DataFlavorMapper::getDataProvider( const NSString* systemFlavor, Reference<XTransferable> const & rTransferable) const
+{
+ DataProviderPtr_t dp;
+
+ try
+ {
+ DataFlavor oOOFlavor = systemToOpenOfficeFlavor(systemFlavor);
+
+ Any data = rTransferable->getTransferData(oOOFlavor);
+
+ if (isByteSequenceType(data.getValueType()))
+ {
+ /*
+ the HTMLFormatDataProvider prepends segment information to HTML
+ this is useful for exchange with MS Word (which brings this stuff from Windows)
+ but annoying for other applications. Since this extension is not a standard datatype
+ on the Mac, let us not provide but provide normal HTML
+
+ if ([systemFlavor caseInsensitiveCompare: NSHTMLPboardType] == NSOrderedSame)
+ {
+ dp = DataProviderPtr_t(new HTMLFormatDataProvider(data));
+ }
+ else
+ */
+ if ([systemFlavor caseInsensitiveCompare: NSPasteboardTypeTIFF] == NSOrderedSame)
+ {
+ dp = DataProviderPtr_t( new PNGDataProvider( data, NSBitmapImageFileTypeTIFF));
+ }
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSFilenamesPboardType' is deprecated: first deprecated in macOS 10.14 - Create
+ // multiple pasteboard items with NSPasteboardTypeFileURL or kUTTypeFileURL instead"
+ else if ([systemFlavor caseInsensitiveCompare: NSFilenamesPboardType] == NSOrderedSame)
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ {
+ dp = DataProviderPtr_t(new FileListDataProvider(data));
+ }
+ else
+ {
+ dp = DataProviderPtr_t(new ByteSequenceDataProvider(data));
+ }
+ }
+ else // Must be OUString type
+ {
+ SAL_WARN_IF(
+ !isOUStringType(data.getValueType()), "vcl",
+ "must be OUString type");
+ dp = DataProviderPtr_t(new UniDataProvider(data));
+ }
+ }
+ catch( const UnsupportedFlavorException& e )
+ {
+ SAL_WARN( "vcl.osx.clipboard", "DataFlavorMapper::getDataProvider(): Exception: " << e.Message );
+ // Somebody violates the contract of the clipboard
+ // interface @see XTransferable
+ }
+
+ return dp;
+}
+
+DataProviderPtr_t DataFlavorMapper::getDataProvider( const NSString* /*systemFlavor*/, NSArray* systemData)
+{
+ return DataProviderPtr_t(new FileListDataProvider(systemData));
+}
+
+DataProviderPtr_t DataFlavorMapper::getDataProvider( const NSString* systemFlavor, NSData* systemData)
+{
+ DataProviderPtr_t dp;
+
+ if ([systemFlavor caseInsensitiveCompare: NSPasteboardTypeString] == NSOrderedSame)
+ {
+ dp = DataProviderPtr_t(new UniDataProvider(systemData));
+ }
+ else if ([systemFlavor caseInsensitiveCompare: NSPasteboardTypeHTML] == NSOrderedSame)
+ {
+ dp = DataProviderPtr_t(new HTMLFormatDataProvider(systemData));
+ }
+ else if ([systemFlavor caseInsensitiveCompare: NSPasteboardTypeTIFF] == NSOrderedSame)
+ {
+ dp = DataProviderPtr_t( new PNGDataProvider(systemData, NSBitmapImageFileTypeTIFF));
+ }
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSFilenamesPboardType' is deprecated: first deprecated in macOS 10.14 - Create multiple
+ // pasteboard items with NSPasteboardTypeFileURL or kUTTypeFileURL instead"
+ else if ([systemFlavor caseInsensitiveCompare: NSFilenamesPboardType] == NSOrderedSame)
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ {
+ //dp = DataProviderPtr_t(new FileListDataProvider(systemData));
+ }
+ else
+ {
+ dp = DataProviderPtr_t(new ByteSequenceDataProvider(systemData));
+ }
+
+ return dp;
+}
+
+bool DataFlavorMapper::isValidMimeContentType(const OUString& contentType) const
+{
+ bool result = true;
+
+ try
+ {
+ Reference<XMimeContentType> xCntType(mrXMimeCntFactory->createMimeContentType(contentType));
+ }
+ catch( const IllegalArgumentException& e )
+ {
+ SAL_WARN("vcl.osx.clipboard", "DataFlavorMapper::isValidMimeContentType(): Exception: " << e.Message);
+ result = false;
+ }
+
+ return result;
+}
+
+NSArray* DataFlavorMapper::flavorSequenceToTypesArray(const css::uno::Sequence<css::datatransfer::DataFlavor>& flavors, bool bIsSystemClipboard) const
+{
+ sal_uInt32 nFlavors = flavors.getLength();
+ NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity: 1];
+
+ bool bNeedDummyInternalFlavor(false);
+
+ for (sal_uInt32 i = 0; i < nFlavors; i++)
+ {
+ if( flavors[i].MimeType.startsWith("image/bmp") )
+ {
+ [array addObject: NSPasteboardTypeTIFF];
+ }
+ else
+ {
+ const NSString* str = openOfficeToSystemFlavor(flavors[i], bNeedDummyInternalFlavor, bIsSystemClipboard);
+
+ if (str != nullptr)
+ {
+ [str retain];
+ [array addObject: str];
+ }
+ }
+ }
+
+ // #i89462# #i90747#
+ // in case no system flavor was found to report
+ // report at least one so D&D between OOo targets works
+ if( [array count] == 0 || bNeedDummyInternalFlavor)
+ {
+ [array addObject: [NSString stringWithUTF8String: FLAVOR_DUMMY_INTERNAL]];
+ }
+
+ return [array autorelease];
+}
+
+css::uno::Sequence<css::datatransfer::DataFlavor> DataFlavorMapper::typesArrayToFlavorSequence(NSArray* types) const
+{
+ int nFormats = [types count];
+ Sequence<DataFlavor> flavors;
+
+ for (int i = 0; i < nFormats; i++)
+ {
+ NSString* sysFormat = [types objectAtIndex: i];
+ DataFlavor oOOFlavor = systemToOpenOfficeFlavor(sysFormat);
+
+ if (isValidFlavor(oOOFlavor))
+ {
+ flavors.realloc(flavors.getLength() + 1);
+ flavors.getArray()[flavors.getLength() - 1] = oOOFlavor;
+ }
+ }
+
+ return flavors;
+}
+
+NSArray* DataFlavorMapper::getAllSupportedPboardTypes()
+{
+ NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity: SIZE_FLAVOR_MAP];
+
+ for (sal_uInt32 i = 0; i < SIZE_FLAVOR_MAP; i++)
+ {
+ if (flavorMap[i].SystemFlavor != nil)
+ [array addObject: flavorMap[i].SystemFlavor];
+ else
+ [array addObject: [NSString stringWithUTF8String: flavorMap[i].OOoFlavor]];
+ }
+
+ return [array autorelease];
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DataFlavorMapping.hxx b/vcl/osx/DataFlavorMapping.hxx
new file mode 100644
index 0000000000..1d4d4219d3
--- /dev/null
+++ b/vcl/osx/DataFlavorMapping.hxx
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <com/sun/star/datatransfer/DataFlavor.hpp>
+#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/lang/XMultiComponentFactory.hpp>
+
+#include <premac.h>
+#import <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+#include <memory>
+#include <unordered_map>
+
+/* An interface to get the clipboard data in either
+ system or OOo format.
+ */
+class DataProvider
+{
+public:
+ virtual ~DataProvider() {};
+
+ /* Get the clipboard data in the system format.
+ The caller has to retain/release the returned
+ CFDataRef on demand.
+ */
+ virtual NSData* getSystemData() = 0;
+
+ /* Get the clipboard data in OOo format.
+ */
+ virtual css::uno::Any getOOoData() = 0;
+};
+
+typedef std::unique_ptr<DataProvider> DataProviderPtr_t;
+
+class DataFlavorMapper
+{
+public:
+ /* Initialize a DataFavorMapper instance. Throws a RuntimeException in case the XMimeContentTypeFactory service
+ cannot be created.
+ */
+ DataFlavorMapper();
+ ~DataFlavorMapper();
+
+ /* Map a system data flavor to an OpenOffice data flavor.
+ Return an empty string if there is not suitable
+ mapping from a system data flavor to an LibreOffice data
+ flavor.
+ */
+ css::datatransfer::DataFlavor systemToOpenOfficeFlavor( const NSString* systemDataFlavor) const;
+
+ /* Map an OpenOffice data flavor to a system data flavor.
+ If there is no suitable mapping available NULL will
+ be returned.
+ */
+ const NSString* openOfficeToSystemFlavor(const css::datatransfer::DataFlavor& oooDataFlavor, bool& rbInternal, bool bIsSystemClipboard = false) const;
+
+ /* Select the best available image data type
+ If there is no suitable mapping available NULL will
+ be returned.
+ */
+ static NSString* openOfficeImageToSystemFlavor(NSPasteboard* pPasteboard);
+
+ /* Get a data provider which is able to provide the data 'rTransferable' offers in a format that can
+ be put on to the system clipboard.
+ */
+ DataProviderPtr_t getDataProvider( const NSString* systemFlavor,
+ const css::uno::Reference< css::datatransfer::XTransferable > & rTransferable) const;
+
+ /* Get a data provider which is able to provide 'systemData' in the OOo expected format.
+ */
+ static DataProviderPtr_t getDataProvider( const NSString* systemFlavor, NSArray* systemData);
+
+ /* Get a data provider which is able to provide 'systemData' in the OOo expected format.
+ */
+ static DataProviderPtr_t getDataProvider( const NSString* systemFlavor, NSData* systemData);
+
+ /* Translate a sequence of DataFlavors into an NSArray of system types.
+ Only those DataFlavors for which a suitable mapping to a system
+ type exist will be contained in the returned types array.
+ */
+ NSArray* flavorSequenceToTypesArray(const css::uno::Sequence<css::datatransfer::DataFlavor>& flavors, bool bIsSystemClipboard = false) const;
+
+ /* Translate an NSArray of system types into a sequence of DataFlavors.
+ Only those types for which a suitable mapping to a DataFlavor
+ exist will be contained in the new DataFlavor Sequence.
+ */
+ css::uno::Sequence<css::datatransfer::DataFlavor> typesArrayToFlavorSequence(NSArray* types) const;
+
+ /* Returns an NSArray containing all pasteboard types supported by OOo
+ */
+ static NSArray* getAllSupportedPboardTypes();
+
+private:
+ /* Determines if the provided Mime content type is valid.
+ */
+ bool isValidMimeContentType(const OUString& contentType) const;
+
+private:
+ css::uno::Reference< css::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory;
+ typedef std::unordered_map< OUString, NSString* > OfficeOnlyTypes;
+ mutable OfficeOnlyTypes maOfficeOnlyTypes;
+};
+
+typedef std::shared_ptr<DataFlavorMapper> DataFlavorMapperPtr_t;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DragActionConversion.cxx b/vcl/osx/DragActionConversion.cxx
new file mode 100644
index 0000000000..d44c3384a6
--- /dev/null
+++ b/vcl/osx/DragActionConversion.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 "DragActionConversion.hxx"
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+
+using namespace com::sun::star::datatransfer::dnd;
+
+/* Convert office drag actions as defined in
+ <type>css::datatransfer::dnd::DNDConstants</type>
+ into system conform drag actions.
+ */
+unsigned int OfficeToSystemDragActions(sal_Int8 dragActions)
+{
+ unsigned int actions = NSDragOperationNone;
+
+ if (dragActions & DNDConstants::ACTION_COPY)
+ {
+ actions |= NSDragOperationCopy;
+ }
+
+ if (dragActions & DNDConstants::ACTION_MOVE)
+ {
+ actions |= NSDragOperationMove;
+ }
+
+ if (dragActions & DNDConstants::ACTION_LINK)
+ {
+ actions |= NSDragOperationLink;
+ }
+
+ return actions;
+}
+
+/* Convert system conform drag actions into office conform
+ drag actions as defined in
+ <type>css::datatransfer::dnd::DNDConstants</type>.
+ */
+sal_Int8 SystemToOfficeDragActions(unsigned int dragActions)
+{
+ sal_Int8 actions = DNDConstants::ACTION_NONE;
+
+ if (dragActions & NSDragOperationCopy)
+ {
+ actions |= DNDConstants::ACTION_COPY;
+ }
+
+ if (dragActions & NSDragOperationMove)
+ {
+ actions |= DNDConstants::ACTION_MOVE;
+ }
+
+ if (dragActions & NSDragOperationLink)
+ {
+ actions |= DNDConstants::ACTION_LINK;
+ }
+
+ // We map NSDragOperationGeneric to ACTION_DEFAULT to
+ // signal that we have to decide for a drag action
+ if (dragActions & NSDragOperationGeneric)
+ {
+ actions |= DNDConstants::ACTION_DEFAULT;
+ }
+
+ return actions;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DragActionConversion.hxx b/vcl/osx/DragActionConversion.hxx
new file mode 100644
index 0000000000..4435f18ee4
--- /dev/null
+++ b/vcl/osx/DragActionConversion.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 .
+ */
+
+#pragma once
+
+#include <sal/types.h>
+
+#include <premac.h>
+#import <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+/* Convert office drag actions as defined in
+ <type>css::datatransfer::dnd::DNDConstants</type>
+ into system conform drag actions.
+ */
+unsigned int OfficeToSystemDragActions(sal_Int8 dragActions);
+
+/* Convert system conform drag actions into office conform
+ drag actions as defined in
+ <type>css::datatransfer::dnd::DNDConstants</type>.
+ */
+sal_Int8 SystemToOfficeDragActions(unsigned int dragActions);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DragSource.cxx b/vcl/osx/DragSource.cxx
new file mode 100644
index 0000000000..fbe3b216a6
--- /dev/null
+++ b/vcl/osx/DragSource.cxx
@@ -0,0 +1,338 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/awt/MouseButton.hpp>
+
+#include <rtl/ustring.hxx>
+
+#include <cppuhelper/supportsservice.hxx>
+
+#include "DragSource.hxx"
+#include "DragSourceContext.hxx"
+#include "clipboard.hxx"
+#include "DragActionConversion.hxx"
+
+#include <osx/salframe.h>
+
+#include <cassert>
+
+using namespace cppu;
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::datatransfer::clipboard;
+using namespace com::sun::star::datatransfer::dnd;
+using namespace com::sun::star::datatransfer::dnd::DNDConstants;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::awt::MouseButton;
+using namespace com::sun::star::awt;
+using namespace com::sun::star::lang;
+using namespace comphelper;
+
+// For LibreOffice internal D&D we provide the Transferable without NSDragPboard
+// interference as a shortcut, see tdf#100097 for how dbaccess depends on this
+uno::Reference<XTransferable> DragSource::g_XTransferable;
+NSView* DragSource::g_DragSourceView = nil;
+bool DragSource::g_DropSuccessSet = false;
+bool DragSource::g_DropSuccess = false;
+
+static OUString dragSource_getImplementationName()
+{
+ return "com.sun.star.comp.datatransfer.dnd.OleDragSource_V1";
+}
+
+static Sequence<OUString> dragSource_getSupportedServiceNames()
+{
+ return { OUString("com.sun.star.datatransfer.dnd.OleDragSource") };
+}
+
+@implementation DragSourceHelper;
+
+-(DragSourceHelper*)initWithDragSource: (DragSource*) pds
+{
+ self = [super init];
+
+ if (self)
+ {
+ mDragSource = pds;
+ }
+
+ return self;
+}
+
+-(void)mouseDown: (NSEvent*)theEvent
+{
+ mDragSource->saveMouseEvent(theEvent);
+}
+
+-(void)mouseDragged: (NSEvent*)theEvent
+{
+ mDragSource->saveMouseEvent(theEvent);
+}
+
+-(unsigned int)draggingSourceOperationMaskForLocal: (BOOL)isLocal
+{
+ return mDragSource->getSupportedDragOperations(isLocal);
+}
+
+-(void)draggedImage:(NSImage*)anImage beganAt:(NSPoint)aPoint
+{
+ (void)anImage;
+ (void)aPoint;
+ DragSourceDragEvent dsde(mDragSource->getXWeak(),
+ new DragSourceContext,
+ mDragSource,
+ DNDConstants::ACTION_COPY,
+ DNDConstants::ACTION_COPY);
+
+ mDragSource->mXDragSrcListener->dragEnter(dsde);
+}
+
+-(void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
+{
+ (void)anImage;
+ (void)aPoint;
+ // an internal drop can accept the drop but fail with dropComplete( false )
+ // this is different than the Cocoa API
+ bool bDropSuccess = operation != NSDragOperationNone;
+ if( DragSource::g_DropSuccessSet )
+ bDropSuccess = DragSource::g_DropSuccess;
+
+ DragSourceDropEvent dsde(mDragSource->getXWeak(),
+ new DragSourceContext,
+ static_cast< XDragSource* >(mDragSource),
+ SystemToOfficeDragActions(operation),
+ bDropSuccess );
+
+ mDragSource->mXDragSrcListener->dragDropEnd(dsde);
+ mDragSource->mXDragSrcListener.clear();
+}
+
+-(void)draggedImage:(NSImage *)draggedImage movedTo:(NSPoint)screenPoint
+{
+ (void)draggedImage;
+ (void)screenPoint;
+ DragSourceDragEvent dsde(mDragSource->getXWeak(),
+ new DragSourceContext,
+ mDragSource,
+ DNDConstants::ACTION_COPY,
+ DNDConstants::ACTION_COPY);
+
+ mDragSource->mXDragSrcListener->dragOver(dsde);
+}
+
+@end
+
+DragSource::DragSource():
+ WeakComponentImplHelper<XDragSource, XInitialization, XServiceInfo>(m_aMutex),
+ mView(nullptr),
+ mpFrame(nullptr),
+ mLastMouseEventBeforeStartDrag(nil),
+ mDragSourceHelper(nil),
+ m_MouseButton(0)
+{
+}
+
+DragSource::~DragSource()
+{
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ [static_cast<id <MouseEventListener>>(mView) unregisterMouseEventListener: mDragSourceHelper];
+ [mDragSourceHelper release];
+}
+
+void SAL_CALL DragSource::initialize(const Sequence< Any >& aArguments)
+{
+ if (aArguments.getLength() < 2)
+ {
+ throw Exception("DragSource::initialize: Not enough parameter.",
+ getXWeak());
+ }
+
+ Any pNSView = aArguments[1];
+ sal_uInt64 tmp = 0;
+ pNSView >>= tmp;
+ mView = reinterpret_cast<NSView*>(tmp);
+
+ /* All SalFrameView the base class for all VCL system views inherits from
+ NSView in order to get mouse and other events. This is the only way to
+ get these events. In order to start a drag operation we need to provide
+ the mouse event which was the trigger. SalFrameView therefore implements
+ a hook mechanism so that we can get mouse events for our purpose.
+ */
+ if (![mView respondsToSelector: @selector(registerMouseEventListener:)] ||
+ ![mView respondsToSelector: @selector(unregisterMouseEventListener:)])
+ {
+ throw Exception("DragSource::initialize: Provided view doesn't support mouse listener",
+ getXWeak());
+ }
+ NSWindow* pWin = [mView window];
+ if( ! pWin || ![pWin respondsToSelector: @selector(getSalFrame)] )
+ {
+ throw Exception("DragSource::initialize: Provided view is not attached to a vcl frame",
+ getXWeak());
+ }
+ mpFrame = reinterpret_cast<AquaSalFrame*>([pWin performSelector: @selector(getSalFrame)]);
+
+ mDragSourceHelper = [[DragSourceHelper alloc] initWithDragSource: this];
+
+ if (mDragSourceHelper == nil)
+ {
+ throw Exception("DragSource::initialize: Cannot initialize DragSource",
+ getXWeak());
+ }
+
+ [static_cast<id <MouseEventListener>>(mView) registerMouseEventListener: mDragSourceHelper];
+}
+
+sal_Bool SAL_CALL DragSource::isDragImageSupported( )
+{
+ return true;
+}
+
+sal_Int32 SAL_CALL DragSource::getDefaultCursor( sal_Int8 /*dragAction*/ )
+{
+ return 0;
+}
+
+void SAL_CALL DragSource::startDrag(const DragGestureEvent& trigger,
+ sal_Int8 sourceActions,
+ sal_Int32 /*cursor*/,
+ sal_Int32 /*image*/,
+ const uno::Reference<XTransferable >& transferable,
+ const uno::Reference<XDragSourceListener >& listener )
+{
+ MutexGuard guard(m_aMutex);
+
+ assert(listener.is() && "DragSource::startDrag: No XDragSourceListener provided");
+ assert(transferable.is() && "DragSource::startDrag: No transferable provided");
+
+ trigger.Event >>= mMouseEvent;
+ m_MouseButton= mMouseEvent.Buttons;
+ mXDragSrcListener = listener;
+ mXCurrentContext = static_cast<XDragSourceContext*>(new DragSourceContext);
+ rtl::Reference<AquaClipboard> clipb(new AquaClipboard(nullptr, false));
+ g_XTransferable = transferable;
+ clipb->setContents(g_XTransferable, uno::Reference<XClipboardOwner>());
+ mDragSourceActions = sourceActions;
+ g_DragSourceView = mView;
+
+ NSSize sz;
+ sz.width = 5;
+ sz.height = 5;
+
+ NSImage* dragImage;
+ dragImage = [[NSImage alloc] initWithSize: sz];
+
+ NSRect bounds;
+ bounds.origin = NSMakePoint(0,0);
+ bounds.size = sz;
+
+ [dragImage lockFocus];
+ [[NSColor blackColor] set];
+ [NSBezierPath fillRect: bounds];
+ [dragImage unlockFocus];
+
+ NSPoint pInWnd = [mLastMouseEventBeforeStartDrag locationInWindow];
+ NSPoint p;
+ p = [mView convertPoint: pInWnd fromView: nil];
+ p.x = p.x - sz.width/2;
+ p.y = p.y - sz.height/2;
+
+ // reset drop success flags
+ g_DropSuccessSet = false;
+ g_DropSuccess = false;
+
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ //TODO: 10.7 dragImage:at:offset:event:pasteboard:source:slideBack:
+ [mView dragImage: dragImage
+ at: p
+ offset: NSMakeSize(0,0)
+ event: mLastMouseEventBeforeStartDrag
+ pasteboard: clipb->getPasteboard()
+ source: mDragSourceHelper
+ slideBack: true];
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ [dragImage release];
+
+ g_XTransferable.clear();
+ g_DragSourceView = nil;
+
+ // reset drop success flags
+ g_DropSuccessSet = false;
+ g_DropSuccess = false;
+}
+
+// In order to initiate a D&D operation we need to
+// provide the triggering mouse event which we get
+// from the SalFrameView that is associated with
+// this DragSource
+void DragSource::saveMouseEvent(NSEvent* theEvent)
+{
+ if (mLastMouseEventBeforeStartDrag != nil)
+ {
+ [mLastMouseEventBeforeStartDrag release];
+ }
+
+ mLastMouseEventBeforeStartDrag = theEvent;
+}
+
+/* isLocal indicates whether or not the DnD operation is OOo
+ internal.
+ */
+unsigned int DragSource::getSupportedDragOperations(bool isLocal) const
+{
+ unsigned int srcActions = OfficeToSystemDragActions(mDragSourceActions);
+
+ if (isLocal)
+ {
+ // Support NSDragOperation generic which means we can
+ // decide which D&D operation to choose. We map
+ // NSDragOperationGeneric to DNDConstants::ACTION_DEFAULT
+ // in SystemToOfficeDragActions to signal this and
+ // use it in DropTarget::determineDropAction
+ srcActions |= NSDragOperationGeneric;
+ }
+ else
+ {
+ // Mask out link and move operations on external DnD
+ srcActions &= ~(NSDragOperationMove | NSDragOperationLink);
+ }
+
+ return srcActions;
+}
+
+OUString SAL_CALL DragSource::getImplementationName( )
+{
+ return dragSource_getImplementationName();
+}
+
+sal_Bool SAL_CALL DragSource::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SAL_CALL DragSource::getSupportedServiceNames()
+{
+ return dragSource_getSupportedServiceNames();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DragSource.hxx b/vcl/osx/DragSource.hxx
new file mode 100644
index 0000000000..96a8fb48a8
--- /dev/null
+++ b/vcl/osx/DragSource.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <com/sun/star/datatransfer/dnd/XDragSource.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragSourceContext.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <cppuhelper/compbase.hxx>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <cppuhelper/basemutex.hxx>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <osl/thread.h>
+#include <com/sun/star/awt/MouseEvent.hpp>
+
+#include <premac.h>
+#import <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+class DragSource;
+class AquaSalFrame;
+
+/* The functions declared in this protocol are actually
+ declared in vcl/inc/osx/salframe.h. Because we want
+ to avoid importing VCL headers in UNO services and
+ on the other hand want to avoid warnings caused by
+ gcc complaining about unknowness of these functions
+ we declare them in a protocol here and cast at the
+ appropriate places.
+*/
+@protocol MouseEventListener
+-(void)registerMouseEventListener:(id)theHandler;
+-(void)unregisterMouseEventListener:(id)theHandler;
+@end
+
+@interface DragSourceHelper : NSObject
+{
+ DragSource* mDragSource;
+}
+
+-(DragSourceHelper*)initWithDragSource: (DragSource*) pds;
+
+-(void)mouseDown: (NSEvent*)theEvent;
+-(void)mouseDragged: (NSEvent*)theEvent;
+
+-(unsigned int)draggingSourceOperationMaskForLocal:(BOOL)isLocal;
+-(void)draggedImage:(NSImage*)anImage beganAt:(NSPoint)aPoint;
+-(void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation;
+-(void)draggedImage:(NSImage *)draggedImage movedTo:(NSPoint)screenPoint;
+
+@end
+
+class DragSource : public ::cppu::BaseMutex,
+ public ::cppu::WeakComponentImplHelper< css::datatransfer::dnd::XDragSource,
+ css::lang::XInitialization,
+ css::lang::XServiceInfo >
+{
+public:
+ DragSource();
+ virtual ~DragSource() override;
+ DragSource(const DragSource&) = delete;
+ DragSource& operator=(const DragSource&) = delete;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+ // XDragSource
+ virtual sal_Bool SAL_CALL isDragImageSupported( ) override;
+
+ virtual sal_Int32 SAL_CALL getDefaultCursor(sal_Int8 dragAction) override;
+
+ virtual void SAL_CALL startDrag( const css::datatransfer::dnd::DragGestureEvent& trigger,
+ sal_Int8 sourceActions,
+ sal_Int32 cursor,
+ sal_Int32 image,
+ const css::uno::Reference< css::datatransfer::XTransferable >& transferable,
+ const css::uno::Reference< css::datatransfer::dnd::XDragSourceListener >& listener ) 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;
+
+ void saveMouseEvent(NSEvent* theEvent);
+ unsigned int getSupportedDragOperations(bool isLocal) const;
+
+public:
+ // The context notifies the XDragSourceListeners
+ css::uno::Reference< css::datatransfer::dnd::XDragSourceContext > mXCurrentContext;
+
+ id mView;
+ AquaSalFrame* mpFrame;
+ NSEvent* mLastMouseEventBeforeStartDrag;
+ DragSourceHelper* mDragSourceHelper;
+ css::awt::MouseEvent mMouseEvent;
+ css::uno::Reference< css::datatransfer::XTransferable > mXTransferable;
+ css::uno::Reference< css::datatransfer::dnd::XDragSourceListener > mXDragSrcListener;
+ // The mouse button that set off the drag and drop operation
+ short m_MouseButton;
+ sal_Int8 mDragSourceActions;
+
+ static css::uno::Reference< css::datatransfer::XTransferable > g_XTransferable;
+ static NSView* g_DragSourceView;
+ static bool g_DropSuccessSet;
+ static bool g_DropSuccess;
+
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DragSourceContext.cxx b/vcl/osx/DragSourceContext.cxx
new file mode 100644
index 0000000000..253dc867d3
--- /dev/null
+++ b/vcl/osx/DragSourceContext.cxx
@@ -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 .
+ */
+
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+
+#include "DragSourceContext.hxx"
+
+using namespace com::sun::star::datatransfer::dnd;
+using namespace com::sun::star::datatransfer::dnd::DNDConstants;
+using namespace com::sun::star::uno;
+using namespace cppu;
+
+DragSourceContext::DragSourceContext() :
+ WeakComponentImplHelper<XDragSourceContext>(m_aMutex)
+{
+}
+
+DragSourceContext::~DragSourceContext()
+{
+}
+
+sal_Int32 SAL_CALL DragSourceContext::getCurrentCursor( )
+{
+ return 0;
+}
+
+void SAL_CALL DragSourceContext::setCursor( sal_Int32 /*cursorId*/ )
+{
+}
+
+void SAL_CALL DragSourceContext::setImage( sal_Int32 /*imageId*/ )
+{
+}
+
+void SAL_CALL DragSourceContext::transferablesFlavorsChanged( )
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DragSourceContext.hxx b/vcl/osx/DragSourceContext.hxx
new file mode 100644
index 0000000000..e1f986d3d4
--- /dev/null
+++ b/vcl/osx/DragSourceContext.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <com/sun/star/datatransfer/dnd/XDragSourceContext.hpp>
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/basemutex.hxx>
+
+// This class fires events to XDragSourceListener implementations.
+// Of that interface only dragDropEnd and dropActionChanged are called.
+// The functions dragEnter, dragExit and dragOver are not supported
+// currently.
+// An instance of SourceContext only lives as long as the drag and drop
+// operation lasts.
+class DragSourceContext: public cppu::BaseMutex,
+ public cppu::WeakComponentImplHelper<css::datatransfer::dnd::XDragSourceContext>
+{
+public:
+ DragSourceContext();
+ virtual ~DragSourceContext() override;
+ DragSourceContext(const DragSourceContext&) = delete;
+ DragSourceContext& operator=(const DragSourceContext&) = delete;
+
+ virtual sal_Int32 SAL_CALL getCurrentCursor( ) override;
+
+ virtual void SAL_CALL setCursor( sal_Int32 cursorId ) override;
+
+ virtual void SAL_CALL setImage( sal_Int32 imageId ) override;
+
+ virtual void SAL_CALL transferablesFlavorsChanged( ) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DropTarget.cxx b/vcl/osx/DropTarget.cxx
new file mode 100644
index 0000000000..d9e34030d1
--- /dev/null
+++ b/vcl/osx/DropTarget.cxx
@@ -0,0 +1,543 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/dnd/DropTargetDragEnterEvent.hpp>
+#include <cppuhelper/interfacecontainer.hxx>
+#include "clipboard.hxx"
+#include "DropTarget.hxx"
+#include "DragActionConversion.hxx"
+#include "DragSource.hxx"
+#include <rtl/ustring.h>
+#include <premac.h>
+#include <Carbon/Carbon.h>
+#include <postmac.h>
+#include <osx/salframe.h>
+#include <osx/salframeview.h>
+#include <cppuhelper/supportsservice.hxx>
+
+using namespace cppu;
+using namespace osl;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::datatransfer::dnd;
+using namespace com::sun::star::datatransfer::dnd::DNDConstants;
+using namespace com::sun::star::datatransfer::clipboard;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star;
+using namespace comphelper;
+
+static OUString dropTarget_getImplementationName()
+{
+ return "com.sun.star.comp.datatransfer.dnd.OleDropTarget_V1";
+}
+
+static Sequence<OUString> dropTarget_getSupportedServiceNames()
+{
+ return { OUString("com.sun.star.datatransfer.dnd.OleDropTarget") };
+}
+
+namespace /* private */
+{
+ // Cocoa's coordinate system has its origin lower-left, VCL's
+ // coordinate system upper-left hence we need to transform
+ // coordinates
+
+ void CocoaToVCL(NSPoint& rPoint, const NSRect& bounds)
+ {
+ rPoint.y = bounds.size.height - rPoint.y;
+ }
+}
+
+@implementation DropTargetHelper
+
+-(DropTargetHelper*)initWithDropTarget:(DropTarget*)pdt
+{
+ self = [super init];
+
+ if (self)
+ {
+ mDropTarget = pdt;
+ }
+
+ return self;
+}
+
+-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+{
+ return mDropTarget->draggingEntered(sender);
+}
+
+-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
+{
+ return mDropTarget->draggingUpdated(sender);
+}
+
+-(void)draggingExited:(id <NSDraggingInfo>)sender
+{
+ mDropTarget->draggingExited(sender);
+}
+
+-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
+{
+ (void) sender;
+ return DropTarget::prepareForDragOperation();
+}
+
+-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+{
+ (void) sender;
+ return mDropTarget->performDragOperation();
+}
+
+-(void)concludeDragOperation:(id <NSDraggingInfo>)sender
+{
+ mDropTarget->concludeDragOperation(sender);
+}
+
+@end
+
+DropTarget::DropTarget() :
+ WeakComponentImplHelper<XInitialization, XDropTarget, XDropTargetDragContext, XDropTargetDropContext, XServiceInfo>(m_aMutex),
+ mView(nil),
+ mpFrame(nullptr),
+ mDropTargetHelper(nil),
+ mbActive(false),
+ mDragSourceSupportedActions(DNDConstants::ACTION_NONE),
+ mSelectedDropAction(DNDConstants::ACTION_NONE),
+ mDefaultActions(DNDConstants::ACTION_COPY_OR_MOVE | DNDConstants::ACTION_LINK | DNDConstants::ACTION_DEFAULT)
+{
+ mDataFlavorMapper = std::make_shared<DataFlavorMapper>();
+}
+
+DropTarget::~DropTarget()
+{
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ [static_cast<id <DraggingDestinationHandler>>(mView) unregisterDraggingDestinationHandler:mDropTargetHelper];
+ [mDropTargetHelper release];
+}
+
+sal_Int8 DropTarget::determineDropAction(sal_Int8 dropActions, id sender) const
+{
+ sal_Int8 dropAct = dropActions;
+ bool srcAndDestEqual = false;
+
+ if ([sender draggingSource] != nil)
+ {
+ // Internal DnD
+ NSView* destView = [[sender draggingDestinationWindow] contentView];
+ srcAndDestEqual = (DragSource::g_DragSourceView == destView);
+ }
+
+ // If ACTION_DEFAULT is set this means NSDragOperationGeneric
+ // has been set and we map this to ACTION_MOVE or ACTION_COPY
+ // depending on whether or not source and dest are equal,
+ // this hopefully satisfies all parties
+ if( (dropActions == DNDConstants::ACTION_DEFAULT)
+ || ((dropActions == mDragSourceSupportedActions)
+ && !(~mDragSourceSupportedActions & DNDConstants::ACTION_COPY_OR_MOVE ) ) )
+ {
+ dropAct = srcAndDestEqual ? DNDConstants::ACTION_MOVE :
+ DNDConstants::ACTION_COPY;
+ }
+ // if more than one drop actions have been specified
+ // set ACTION_DEFAULT in order to let the drop target
+ // decide which one to use
+ else if (dropActions != DNDConstants::ACTION_NONE &&
+ dropActions != DNDConstants::ACTION_MOVE &&
+ dropActions != DNDConstants::ACTION_COPY &&
+ dropActions != DNDConstants::ACTION_LINK)
+ {
+ if (srcAndDestEqual)
+ {
+ dropAct = dropActions;
+ }
+ else // source and destination are different
+ {
+ if (dropActions & DNDConstants::ACTION_COPY)
+ dropAct = DNDConstants::ACTION_COPY;
+ else if (dropActions & DNDConstants::ACTION_MOVE)
+ dropAct = DNDConstants::ACTION_MOVE;
+ else if (dropActions & DNDConstants::ACTION_LINK)
+ dropAct = DNDConstants::ACTION_LINK;
+ }
+
+ dropAct |= DNDConstants::ACTION_DEFAULT;
+ }
+
+ return dropAct;
+}
+
+NSDragOperation DropTarget::draggingEntered(id sender)
+{
+ // Initially when DnD will be started no modifier key can be pressed yet
+ // thus we are getting all actions that the drag source supports, we save
+ // this value because later the system masks the drag source actions if
+ // a modifier key will be pressed
+ mDragSourceSupportedActions = SystemToOfficeDragActions([sender draggingSourceOperationMask]);
+
+ // Only if the drop target is really interested in the drag actions
+ // supported by the source
+ if (mDragSourceSupportedActions & mDefaultActions)
+ {
+ sal_Int8 currentAction = determineDropAction(mDragSourceSupportedActions, sender);
+
+ NSRect bounds = [mView bounds];
+ NSPoint mouseLoc = [NSEvent mouseLocation];
+
+ id wnd = [mView window];
+ NSPoint dragLocation = [mView convertPoint:[wnd convertRectFromScreen:NSMakeRect(mouseLoc.x, mouseLoc.y, 1, 1)].origin fromView:nil];
+
+ CocoaToVCL(dragLocation, bounds);
+
+ sal_Int32 posX = static_cast<sal_Int32>(dragLocation.x);
+ sal_Int32 posY = static_cast<sal_Int32>(dragLocation.y);
+
+ NSPasteboard* dragPboard = [sender draggingPasteboard];
+ mXCurrentDragClipboard = new AquaClipboard(dragPboard, false);
+
+ uno::Reference<XTransferable> xTransferable = DragSource::g_XTransferable.is() ?
+ DragSource::g_XTransferable : mXCurrentDragClipboard->getContents();
+
+ DropTargetDragEnterEvent dtdee(getXWeak(),
+ 0,
+ this,
+ currentAction,
+ posX,
+ posY,
+ mDragSourceSupportedActions,
+ xTransferable->getTransferDataFlavors());
+
+ fire_dragEnter(dtdee);
+ }
+
+ return OfficeToSystemDragActions(mSelectedDropAction);
+}
+
+NSDragOperation DropTarget::draggingUpdated(id sender)
+{
+ sal_Int8 currentDragSourceActions =
+ SystemToOfficeDragActions([sender draggingSourceOperationMask]);
+ NSDragOperation dragOp = NSDragOperationNone;
+
+ if (currentDragSourceActions & mDefaultActions)
+ {
+ sal_Int8 currentAction = determineDropAction(currentDragSourceActions, sender);
+ NSRect bounds = [mView bounds];
+ NSPoint mouseLoc = [NSEvent mouseLocation];
+
+ id wnd = [mView window];
+ NSPoint dragLocation = [mView convertPoint:[wnd convertRectFromScreen:NSMakeRect(mouseLoc.x, mouseLoc.y, 1, 1)].origin fromView:nil];
+
+ CocoaToVCL(dragLocation, bounds);
+
+ sal_Int32 posX = static_cast<sal_Int32>(dragLocation.x);
+ sal_Int32 posY = static_cast<sal_Int32>(dragLocation.y);
+
+ DropTargetDragEvent dtde(getXWeak(),
+ 0,
+ this,
+ currentAction,
+ posX,
+ posY,
+ mDragSourceSupportedActions);
+
+ fire_dragOver(dtde);
+
+ // drag over callbacks likely have rendered something
+ [mView setNeedsDisplay: true];
+
+ dragOp = OfficeToSystemDragActions(mSelectedDropAction);
+
+ //NSLog(@"Drag update: Source actions: %x proposed action %x selected action %x", mDragSourceSupportedActions, currentAction, mSelectedDropAction);
+ }
+
+ if (dragOp == NSDragOperationNone)
+ [[NSCursor operationNotAllowedCursor] set];
+ else if (dragOp == NSDragOperationCopy)
+ [[NSCursor dragCopyCursor] set];
+ else
+ [[NSCursor arrowCursor] set];
+
+ return dragOp;
+}
+
+void DropTarget::draggingExited(id /*sender*/)
+{
+ DropTargetEvent dte(getXWeak(), 0);
+ fire_dragExit(dte);
+ mDragSourceSupportedActions = DNDConstants::ACTION_NONE;
+ mSelectedDropAction = DNDConstants::ACTION_NONE;
+ [[NSCursor arrowCursor] set];
+}
+
+BOOL DropTarget::prepareForDragOperation()
+{
+ return true;
+}
+
+BOOL DropTarget::performDragOperation()
+{
+ bool bSuccess = false;
+
+ if (mSelectedDropAction != DNDConstants::ACTION_NONE)
+ {
+ uno::Reference<XTransferable> xTransferable = DragSource::g_XTransferable;
+
+ if (!DragSource::g_XTransferable.is())
+ {
+ xTransferable = mXCurrentDragClipboard->getContents();
+ }
+
+ NSRect bounds = [mView bounds];
+ NSPoint mouseLoc = [NSEvent mouseLocation];
+
+ id wnd = [mView window];
+ NSPoint dragLocation = [mView convertPoint:[wnd convertRectFromScreen:NSMakeRect(mouseLoc.x, mouseLoc.y, 1, 1)].origin fromView:nil];
+
+ CocoaToVCL(dragLocation, bounds);
+
+ sal_Int32 posX = static_cast<sal_Int32>(dragLocation.x);
+ sal_Int32 posY = static_cast<sal_Int32>(dragLocation.y);
+
+ DropTargetDropEvent dtde(getXWeak(),
+ 0,
+ this,
+ mSelectedDropAction,
+ posX,
+ posY,
+ mDragSourceSupportedActions,
+ xTransferable);
+
+ fire_drop(dtde);
+
+ bSuccess = true;
+ }
+
+ return bSuccess;
+}
+
+void DropTarget::concludeDragOperation(id /*sender*/)
+{
+ mDragSourceSupportedActions = DNDConstants::ACTION_NONE;
+ mSelectedDropAction = DNDConstants::ACTION_NONE;
+ mXCurrentDragClipboard.clear();
+ [[NSCursor arrowCursor] set];
+}
+
+// called from WeakComponentImplHelperX::dispose
+// WeakComponentImplHelper calls disposing before it destroys
+// itself.
+void SAL_CALL DropTarget::disposing()
+{
+}
+
+void SAL_CALL DropTarget::initialize(const Sequence< Any >& aArguments)
+{
+ if (aArguments.getLength() < 2)
+ {
+ throw RuntimeException("DropTarget::initialize: Cannot install window event handler",
+ getXWeak());
+ }
+
+ Any pNSView = aArguments[0];
+ sal_uInt64 tmp = 0;
+ pNSView >>= tmp;
+ mView = reinterpret_cast<id>(tmp);
+ mpFrame = [static_cast<SalFrameView*>(mView) getSalFrame];
+
+ mDropTargetHelper = [[DropTargetHelper alloc] initWithDropTarget: this];
+
+ [static_cast<id <DraggingDestinationHandler>>(mView) registerDraggingDestinationHandler:mDropTargetHelper];
+ [mView registerForDraggedTypes: DataFlavorMapper::getAllSupportedPboardTypes()];
+
+ id wnd = [mView window];
+ NSWindow* parentWnd = [wnd parentWindow];
+ unsigned int topWndStyle = (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable);
+ unsigned int wndStyles = [wnd styleMask] & topWndStyle;
+
+ if (parentWnd == nil && (wndStyles == topWndStyle))
+ {
+ [wnd registerDraggingDestinationHandler:mDropTargetHelper];
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSFilenamesPboardType' is deprecated: first deprecated in macOS 10.14 - Create
+ // multiple pasteboard items with NSPasteboardTypeFileURL or kUTTypeFileURL instead"
+ [wnd registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+}
+
+void SAL_CALL DropTarget::addDropTargetListener(const uno::Reference<XDropTargetListener>& dtl)
+{
+ rBHelper.addListener(cppu::UnoType<decltype(dtl)>::get(), dtl);
+}
+
+void SAL_CALL DropTarget::removeDropTargetListener(const uno::Reference<XDropTargetListener>& dtl)
+{
+ rBHelper.removeListener(cppu::UnoType<decltype(dtl)>::get(), dtl);
+}
+
+sal_Bool SAL_CALL DropTarget::isActive( )
+{
+ return mbActive;
+}
+
+void SAL_CALL DropTarget::setActive(sal_Bool active)
+{
+ mbActive = active;
+}
+
+sal_Int8 SAL_CALL DropTarget::getDefaultActions()
+{
+ return mDefaultActions;
+}
+
+void SAL_CALL DropTarget::setDefaultActions(sal_Int8 actions)
+{
+ OSL_ENSURE( actions < 8, "No valid default actions");
+ mDefaultActions= actions;
+}
+
+void SAL_CALL DropTarget::acceptDrag(sal_Int8 dragOperation)
+{
+ mSelectedDropAction = dragOperation;
+}
+
+void SAL_CALL DropTarget::rejectDrag()
+{
+ mSelectedDropAction = DNDConstants::ACTION_NONE;
+}
+
+void SAL_CALL DropTarget::acceptDrop(sal_Int8 dropOperation)
+{
+ mSelectedDropAction = dropOperation;
+}
+
+void SAL_CALL DropTarget::rejectDrop()
+{
+ mSelectedDropAction = DNDConstants::ACTION_NONE;
+}
+
+void SAL_CALL DropTarget::dropComplete(sal_Bool success)
+{
+ // Reset the internal transferable used as shortcut in case this is
+ // an internal D&D operation
+ DragSource::g_XTransferable.clear();
+ DragSource::g_DropSuccessSet = true;
+ DragSource::g_DropSuccess = success;
+}
+
+void DropTarget::fire_drop( const DropTargetDropEvent& dte)
+{
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer);
+ while( iter.hasMoreElements())
+ {
+ uno::Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
+
+ try { listener->drop( dte); }
+ catch(RuntimeException&) {}
+ }
+ }
+}
+
+void DropTarget::fire_dragEnter(const DropTargetDragEnterEvent& e)
+{
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer);
+ while( iter.hasMoreElements())
+ {
+ uno::Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
+
+ try { listener->dragEnter( e); }
+ catch (RuntimeException&) {}
+ }
+ }
+}
+
+void DropTarget::fire_dragExit(const DropTargetEvent& dte)
+{
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer);
+ while( iter.hasMoreElements())
+ {
+ uno::Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
+
+ try { listener->dragExit( dte); }
+ catch (RuntimeException&) {}
+ }
+ }
+}
+
+void DropTarget::fire_dragOver(const DropTargetDragEvent& dtde)
+{
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer );
+ while( iter.hasMoreElements())
+ {
+ uno::Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
+
+ try { listener->dragOver( dtde); }
+ catch (RuntimeException&) {}
+ }
+ }
+}
+
+void DropTarget::fire_dropActionChanged(const DropTargetDragEvent& dtde)
+{
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer);
+ while( iter.hasMoreElements())
+ {
+ uno::Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
+
+ try { listener->dropActionChanged( dtde); }
+ catch (RuntimeException&) {}
+ }
+ }
+}
+
+OUString SAL_CALL DropTarget::getImplementationName()
+{
+ return dropTarget_getImplementationName();
+}
+
+sal_Bool SAL_CALL DropTarget::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SAL_CALL DropTarget::getSupportedServiceNames( )
+{
+ return dropTarget_getSupportedServiceNames();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DropTarget.hxx b/vcl/osx/DropTarget.hxx
new file mode 100644
index 0000000000..ffc53a4e17
--- /dev/null
+++ b/vcl/osx/DropTarget.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include "DataFlavorMapping.hxx"
+#include <cppuhelper/compbase.hxx>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+
+#include <com/sun/star/datatransfer/dnd/XDropTargetListener.hpp>
+#include <com/sun/star/datatransfer/dnd/DropTargetDragEnterEvent.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTargetDragContext.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTargetDropContext.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <cppuhelper/basemutex.hxx>
+#include <com/sun/star/lang/XMultiComponentFactory.hpp>
+
+#include <premac.h>
+#import <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+class DropTarget;
+class AquaSalFrame;
+
+/* The functions declared in this protocol are actually
+ declared in vcl/inc/osx/salframe.h. Because we want
+ to avoid importing VCL headers in UNO services and
+ on the other hand want to avoid warnings caused by
+ gcc complaining about unknowness of these functions
+ we declare them in a protocol here and cast at the
+ appropriate places.
+*/
+@protocol DraggingDestinationHandler
+-(void)registerDraggingDestinationHandler:(id)theHandler;
+-(void)unregisterDraggingDestinationHandler:(id)theHandler;
+@end
+
+@interface DropTargetHelper : NSObject
+{
+ DropTarget* mDropTarget;
+}
+
+-(DropTargetHelper*)initWithDropTarget:(DropTarget*)pdt;
+
+-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender;
+-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender;
+-(void)draggingExited:(id <NSDraggingInfo>)sender;
+-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender;
+-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender;
+-(void)concludeDragOperation:(id <NSDraggingInfo>)sender;
+
+@end
+
+class DropTarget: public cppu::BaseMutex,
+ public cppu::WeakComponentImplHelper< css::lang::XInitialization,
+ css::datatransfer::dnd::XDropTarget,
+ css::datatransfer::dnd::XDropTargetDragContext,
+ css::datatransfer::dnd::XDropTargetDropContext,
+ css::lang::XServiceInfo >
+{
+public:
+ DropTarget();
+ virtual ~DropTarget() override;
+ DropTarget(const DropTarget&) = delete;
+ DropTarget& operator=(const DropTarget&) = delete;
+
+ // Overrides WeakComponentImplHelper::disposing which is called by
+ // WeakComponentImplHelper::dispose
+ // Must be called.
+ virtual void SAL_CALL disposing() override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+ // XDropTarget
+ virtual void SAL_CALL addDropTargetListener( const css::uno::Reference< css::datatransfer::dnd::XDropTargetListener >& dtl ) override;
+
+ virtual void SAL_CALL removeDropTargetListener( const css::uno::Reference< css::datatransfer::dnd::XDropTargetListener >& dtl ) override;
+
+ // Default is not active
+ virtual sal_Bool SAL_CALL isActive() override;
+ virtual void SAL_CALL setActive(sal_Bool isActive) override;
+ virtual sal_Int8 SAL_CALL getDefaultActions() override;
+ virtual void SAL_CALL setDefaultActions(sal_Int8 actions) override;
+
+ // XDropTargetDragContext
+ virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) override;
+ virtual void SAL_CALL rejectDrag() override;
+
+ // XDropTargetDragContext
+ virtual void SAL_CALL acceptDrop(sal_Int8 dropOperation) override;
+ virtual void SAL_CALL rejectDrop() override;
+ virtual void SAL_CALL dropComplete(sal_Bool success) 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;
+
+ // NSDraggingDestination protocol functions
+ NSDragOperation draggingEntered(id sender);
+ NSDragOperation draggingUpdated(id sender);
+ void draggingExited(id sender);
+ static BOOL prepareForDragOperation();
+ BOOL performDragOperation();
+ void concludeDragOperation(id sender);
+
+ /* If multiple actions are supported by the drag source and
+ the user did not choose a specific action by pressing a
+ modifier key choose a default action to be proposed to
+ the application.
+ */
+ sal_Int8 determineDropAction(sal_Int8 dropActions, id sender) const;
+
+private:
+ void fire_drop(const css::datatransfer::dnd::DropTargetDropEvent& dte);
+ void fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtdee);
+ void fire_dragExit(const css::datatransfer::dnd::DropTargetEvent& dte);
+ void fire_dragOver(const css::datatransfer::dnd::DropTargetDragEvent& dtde);
+ void fire_dropActionChanged(const css::datatransfer::dnd::DropTargetDragEvent& dtde);
+
+private:
+ css::uno::Reference< css::datatransfer::dnd::XDropTargetDragContext > mXCurrentDragContext;
+ css::uno::Reference< css::datatransfer::dnd::XDropTargetDropContext > mXCurrentDropContext;
+ css::uno::Reference< css::datatransfer::clipboard::XClipboard > mXCurrentDragClipboard;
+ DataFlavorMapperPtr_t mDataFlavorMapper;
+ id mView;
+ AquaSalFrame* mpFrame;
+ DropTargetHelper* mDropTargetHelper;
+ bool mbActive;
+ sal_Int8 mDragSourceSupportedActions;
+ sal_Int8 mSelectedDropAction;
+ sal_Int8 mDefaultActions;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/HtmlFmtFlt.cxx b/vcl/osx/HtmlFmtFlt.cxx
new file mode 100644
index 0000000000..3549ecd210
--- /dev/null
+++ b/vcl/osx/HtmlFmtFlt.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 "HtmlFmtFlt.hxx"
+
+#include <rtl/string.h>
+#include <osl/diagnose.h>
+
+#include <string>
+#include <sstream>
+#include <vector>
+#include <iomanip>
+#include <cassert>
+
+using namespace com::sun::star::uno;
+
+// converts the openoffice text/html clipboard format to the HTML Format
+// well known under MS Windows
+// the MS HTML Format has a header before the real html data
+
+// Version:1.0 Version number of the clipboard. Starting is 0.9
+// StartHTML: Byte count from the beginning of the clipboard to the start
+// of the context, or -1 if no context
+// EndHTML: Byte count from the beginning of the clipboard to the end
+// of the context, or -1 if no context
+// StartFragment: Byte count from the beginning of the clipboard to the
+// start of the fragment
+// EndFragment: Byte count from the beginning of the clipboard to the
+// end of the fragment
+// StartSelection: Byte count from the beginning of the clipboard to the
+// start of the selection
+// EndSelection: Byte count from the beginning of the clipboard to the
+// end of the selection
+
+// StartSelection and EndSelection are optional
+// The fragment should be preceded and followed by the HTML comments
+// <!--StartFragment--> and <!--EndFragment--> (no space between !-- and the
+// text
+
+namespace
+{
+std::string GetHtmlFormatHeader(size_t startHtml, size_t endHtml, size_t startFragment, size_t endFragment)
+{
+ std::ostringstream htmlHeader;
+ htmlHeader << "Version:1.0" << '\r' << '\n';
+ htmlHeader << "StartHTML:" << std::setw(10) << std::setfill('0') << std::dec << startHtml << '\r' << '\n';
+ htmlHeader << "EndHTML:" << std::setw(10) << std::setfill('0') << std::dec << endHtml << '\r' << '\n';
+ htmlHeader << "StartFragment:" << std::setw(10) << std::setfill('0') << std::dec << startFragment << '\r' << '\n';
+ htmlHeader << "EndFragment:" << std::setw(10) << std::setfill('0') << std::dec << endFragment << '\r' << '\n';
+ return htmlHeader.str();
+}
+
+}
+
+// the office always writes the start and end html tag in upper cases and
+// without spaces both tags don't allow parameters
+const std::string TAG_HTML("<html>");
+const std::string TAG_END_HTML("</html>");
+
+// The body tag may have parameters so we need to search for the
+// closing '>' manually e.g. <BODY param> #92840#
+const std::string TAG_BODY("<body");
+const std::string TAG_END_BODY("</body");
+
+Sequence<sal_Int8> TextHtmlToHTMLFormat(Sequence<sal_Int8> const & aTextHtml)
+{
+ OSL_ASSERT(aTextHtml.getLength() > 0);
+
+ if (aTextHtml.getLength() <= 0)
+ return Sequence<sal_Int8>();
+
+ // fill the buffer with dummy values to calc the exact length
+ std::string dummyHtmlHeader = GetHtmlFormatHeader(0, 0, 0, 0);
+ size_t lHtmlFormatHeader = dummyHtmlHeader.length();
+
+ std::string textHtml(
+ reinterpret_cast<const char*>(aTextHtml.getConstArray()),
+ reinterpret_cast<const char*>(aTextHtml.getConstArray()) + aTextHtml.getLength());
+
+ std::string::size_type nStartHtml = textHtml.find(TAG_HTML) + lHtmlFormatHeader - 1; // we start one before '<HTML>' Word 2000 does also so
+ std::string::size_type nEndHtml = textHtml.find(TAG_END_HTML) + lHtmlFormatHeader + TAG_END_HTML.length() + 1; // our SOffice 5.2 wants 2 behind </HTML>?
+
+ // The body tag may have parameters so we need to search for the
+ // closing '>' manually e.g. <BODY param> #92840#
+ std::string::size_type nStartFragment = textHtml.find(">", textHtml.find(TAG_BODY)) + lHtmlFormatHeader + 1;
+ std::string::size_type nEndFragment = textHtml.find(TAG_END_BODY) + lHtmlFormatHeader;
+
+ std::string htmlFormat = GetHtmlFormatHeader(nStartHtml, nEndHtml, nStartFragment, nEndFragment);
+ htmlFormat += textHtml;
+
+ Sequence<sal_Int8> byteSequence(htmlFormat.length() + 1); // space the trailing '\0'
+ memset(byteSequence.getArray(), 0, byteSequence.getLength());
+
+ memcpy(
+ static_cast<void*>(byteSequence.getArray()),
+ static_cast<const void*>(htmlFormat.c_str()),
+ htmlFormat.length());
+
+ return byteSequence;
+}
+
+const char* const HtmlStartTag = "<html";
+
+Sequence<sal_Int8> HTMLFormatToTextHtml(const Sequence<sal_Int8>& aHTMLFormat)
+{
+ assert(isHTMLFormat(aHTMLFormat) && "No HTML Format provided");
+
+ Sequence<sal_Int8>& nonconstHTMLFormatRef = const_cast< Sequence<sal_Int8>& >(aHTMLFormat);
+ char* dataStart = reinterpret_cast<char*>(nonconstHTMLFormatRef.getArray());
+ char* dataEnd = dataStart + nonconstHTMLFormatRef.getLength() - 1;
+ const char* htmlStartTag = strcasestr(dataStart, HtmlStartTag);
+
+ assert(htmlStartTag && "Seems to be no HTML at all");
+
+ // It doesn't seem to be HTML? Well then simply return what has been
+ // provided in non-debug builds
+ if (htmlStartTag == nullptr)
+ {
+ return aHTMLFormat;
+ }
+
+ sal_Int32 len = dataEnd - htmlStartTag;
+ Sequence<sal_Int8> plainHtmlData(len);
+
+ memcpy(static_cast<void*>(plainHtmlData.getArray()), htmlStartTag, len);
+
+ return plainHtmlData;
+}
+
+/* A simple format detection. We are just comparing the first few bytes
+ of the provided byte sequence to see whether or not it is the MS
+ Office Html format. If it shows that this is not reliable enough we
+ can improve this
+*/
+const char HtmlFormatStart[] = "Version:";
+int const HtmlFormatStartLen = sizeof(HtmlFormatStart) - 1;
+
+bool isHTMLFormat(const Sequence<sal_Int8>& aHtmlSequence)
+{
+ if (aHtmlSequence.getLength() < HtmlFormatStartLen)
+ return false;
+
+ return rtl_str_compareIgnoreAsciiCase_WithLength(HtmlFormatStart,
+ HtmlFormatStartLen,
+ reinterpret_cast<const char*>(aHtmlSequence.getConstArray()),
+ HtmlFormatStartLen) == 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/HtmlFmtFlt.hxx b/vcl/osx/HtmlFmtFlt.hxx
new file mode 100644
index 0000000000..5286efb1de
--- /dev/null
+++ b/vcl/osx/HtmlFmtFlt.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <com/sun/star/uno/Sequence.hxx>
+
+/* Transform plain HTML into the format expected by MS Office.
+ */
+css::uno::Sequence<sal_Int8> TextHtmlToHTMLFormat(css::uno::Sequence<sal_Int8> const& aTextHtml);
+
+/* Transform the MS Office HTML format into plain HTML.
+ */
+css::uno::Sequence<sal_Int8> HTMLFormatToTextHtml(const css::uno::Sequence<sal_Int8>& aHTMLFormat);
+
+/* Detects whether the given byte sequence contains the MS Office Html format.
+
+ @returns True if the MS Office Html format will be detected False otherwise.
+ */
+bool isHTMLFormat(const css::uno::Sequence<sal_Int8>& aHtmlSequence);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/OSXTransferable.cxx b/vcl/osx/OSXTransferable.cxx
new file mode 100644
index 0000000000..e55141a606
--- /dev/null
+++ b/vcl/osx/OSXTransferable.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 <sal/config.h>
+
+#include <sal/log.hxx>
+#include <utility>
+
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <sal/types.h>
+#include <osl/diagnose.h>
+
+#include "OSXTransferable.hxx"
+
+#include "DataFlavorMapping.hxx"
+
+#include <quartz/utils.h>
+
+using namespace cppu;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::lang;
+
+namespace
+{
+ bool isValidFlavor( const DataFlavor& aFlavor )
+ {
+ size_t len = aFlavor.MimeType.getLength();
+ Type dtype = aFlavor.DataType;
+ return ((len > 0) && ((dtype == cppu::UnoType<Sequence<sal_Int8>>::get()) || (dtype == cppu::UnoType<OUString>::get())));
+ }
+
+bool cmpAllContentTypeParameter(const Reference<XMimeContentType> & xLhs,
+ const Reference<XMimeContentType> & xRhs)
+{
+ Sequence<OUString> xLhsFlavors = xLhs->getParameters();
+ Sequence<OUString> xRhsFlavors = xRhs->getParameters();
+
+ // Stop here if the number of parameters is different already
+ if (xLhsFlavors.getLength() != xRhsFlavors.getLength())
+ return false;
+
+ try
+ {
+ OUString pLhs;
+ OUString pRhs;
+
+ for (sal_Int32 i = 0; i < xLhsFlavors.getLength(); i++)
+ {
+ pLhs = xLhs->getParameterValue(xLhsFlavors[i]);
+ pRhs = xRhs->getParameterValue(xLhsFlavors[i]);
+
+ if (!pLhs.equalsIgnoreAsciiCase(pRhs))
+ {
+ return false;
+ }
+ }
+ }
+ catch(IllegalArgumentException&)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+} // unnamed namespace
+
+OSXTransferable::OSXTransferable(const Reference<XMimeContentTypeFactory> & rXMimeCntFactory,
+ DataFlavorMapperPtr_t pDataFlavorMapper,
+ NSPasteboard* pasteboard) :
+ mrXMimeCntFactory(rXMimeCntFactory),
+ mDataFlavorMapper(pDataFlavorMapper),
+ mPasteboard(pasteboard)
+{
+ [mPasteboard retain];
+
+ initClipboardItemList();
+}
+
+OSXTransferable::~OSXTransferable()
+{
+ [mPasteboard release];
+}
+
+Any SAL_CALL OSXTransferable::getTransferData( const DataFlavor& aFlavor )
+{
+ if (!isValidFlavor(aFlavor) || !isDataFlavorSupported(aFlavor))
+ {
+ throw UnsupportedFlavorException("AquaClipboard: Unsupported data flavor",
+ static_cast<XTransferable*>(this));
+ }
+
+ bool bInternal(false);
+ NSString const * sysFormat =
+ (aFlavor.MimeType.startsWith("image/png"))
+ ? DataFlavorMapper::openOfficeImageToSystemFlavor( mPasteboard )
+ : mDataFlavorMapper->openOfficeToSystemFlavor(aFlavor, bInternal);
+ DataProviderPtr_t dp;
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSFilenamesPboardType' is deprecated: first deprecated in macOS 10.14 - Create multiple
+ // pasteboard items with NSPasteboardTypeFileURL or kUTTypeFileURL instead"
+ if ([sysFormat caseInsensitiveCompare: NSFilenamesPboardType] == NSOrderedSame)
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ {
+ NSArray* sysData = [mPasteboard propertyListForType: const_cast<NSString *>(sysFormat)];
+ dp = DataFlavorMapper::getDataProvider(sysFormat, sysData);
+ }
+ else
+ {
+ NSData* sysData = [mPasteboard dataForType: const_cast<NSString *>(sysFormat)];
+ dp = DataFlavorMapper::getDataProvider(sysFormat, sysData);
+ }
+
+ if (!dp)
+ {
+ throw UnsupportedFlavorException("AquaClipboard: Unsupported data flavor",
+ static_cast<XTransferable*>(this));
+ }
+
+ return dp->getOOoData();
+}
+
+Sequence< DataFlavor > SAL_CALL OSXTransferable::getTransferDataFlavors( )
+{
+ return mFlavorList;
+}
+
+sal_Bool SAL_CALL OSXTransferable::isDataFlavorSupported(const DataFlavor& aFlavor)
+{
+ for (const DataFlavor& rFlavor : std::as_const(mFlavorList))
+ if (compareDataFlavors(aFlavor, rFlavor))
+ return true;
+
+ return false;
+}
+
+void OSXTransferable::initClipboardItemList()
+{
+ NSArray* pboardFormats = [mPasteboard types];
+
+ if (pboardFormats == nullptr)
+ {
+ throw RuntimeException("AquaClipboard: Cannot get clipboard data",
+ static_cast<XTransferable*>(this));
+ }
+
+ SAL_INFO("vcl.osx.clipboard", "Types on pasteboard: " << NSStringArrayToOUString(pboardFormats));
+
+
+ mFlavorList = mDataFlavorMapper->typesArrayToFlavorSequence(pboardFormats);
+}
+
+/* Compares two DataFlavors. Returns true if both DataFlavor have the same media type
+ and the number of parameter and all parameter values do match otherwise false
+ is returned.
+ */
+bool OSXTransferable::compareDataFlavors(const DataFlavor& lhs, const DataFlavor& rhs )
+{
+ try
+ {
+ Reference<XMimeContentType> xLhs(mrXMimeCntFactory->createMimeContentType(lhs.MimeType));
+ Reference<XMimeContentType> xRhs(mrXMimeCntFactory->createMimeContentType(rhs.MimeType));
+
+ if (!xLhs->getFullMediaType().equalsIgnoreAsciiCase(xRhs->getFullMediaType()) ||
+ !cmpAllContentTypeParameter(xLhs, xRhs))
+ {
+ return false;
+ }
+ }
+ catch( IllegalArgumentException& )
+ {
+ OSL_FAIL( "Invalid content type detected" );
+ return false;
+ }
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/OSXTransferable.hxx b/vcl/osx/OSXTransferable.hxx
new file mode 100644
index 0000000000..e4a00b880e
--- /dev/null
+++ b/vcl/osx/OSXTransferable.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/XMimeContentType.hpp>
+
+#include "DataFlavorMapping.hxx"
+
+#include <premac.h>
+#import <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+#include <memory>
+#include <vector>
+
+class OSXTransferable : public ::cppu::WeakImplHelper<css::datatransfer::XTransferable>
+{
+public:
+ explicit OSXTransferable(css::uno::Reference< css::datatransfer::XMimeContentTypeFactory> const & rXMimeCntFactory,
+ DataFlavorMapperPtr_t pDataFlavorMapper,
+ NSPasteboard* pasteboard);
+
+ virtual ~OSXTransferable() override;
+ OSXTransferable(const OSXTransferable&) = delete;
+ OSXTransferable& operator=(const OSXTransferable&) = delete;
+
+ // XTransferable
+
+ virtual css::uno::Any SAL_CALL getTransferData( const css::datatransfer::DataFlavor& aFlavor ) override;
+
+ virtual css::uno::Sequence< css::datatransfer::DataFlavor > SAL_CALL getTransferDataFlavors( ) override;
+
+ virtual sal_Bool SAL_CALL isDataFlavorSupported( const css::datatransfer::DataFlavor& aFlavor ) override;
+
+ // Helper functions not part of the XTransferable interface
+
+ void initClipboardItemList();
+
+ //css::uno::Any getClipboardItemData(ClipboardItemPtr_t clipboardItem);
+
+ bool compareDataFlavors( const css::datatransfer::DataFlavor& lhs,
+ const css::datatransfer::DataFlavor& rhs );
+
+private:
+ css::uno::Sequence< css::datatransfer::DataFlavor > mFlavorList;
+ css::uno::Reference< css::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory;
+ DataFlavorMapperPtr_t mDataFlavorMapper;
+ NSPasteboard* mPasteboard;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/PictToBmpFlt.cxx b/vcl/osx/PictToBmpFlt.cxx
new file mode 100644
index 0000000000..a818cb5e75
--- /dev/null
+++ b/vcl/osx/PictToBmpFlt.cxx
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <premac.h>
+#include <Carbon/Carbon.h>
+#include <postmac.h>
+
+#include <string.h>
+
+#include "PictToBmpFlt.hxx"
+
+bool ImageToPNG( css::uno::Sequence<sal_Int8> const & rImgData,
+ css::uno::Sequence<sal_Int8>& rPngData)
+{
+ NSData* pData = [NSData dataWithBytesNoCopy: const_cast<sal_Int8 *>(rImgData.getConstArray()) length: rImgData.getLength() freeWhenDone: false];
+ if( !pData)
+ return false;
+
+ NSBitmapImageRep* pRep =[NSBitmapImageRep imageRepWithData: pData];
+ if( !pRep)
+ return false;
+
+ NSData* pOut = [pRep representationUsingType: NSBitmapImageFileTypePNG properties: @{ }];
+ if( !pOut)
+ return false;
+
+ const size_t nPngSize = [pOut length];
+ rPngData.realloc( nPngSize);
+ [pOut getBytes: rPngData.getArray() length: nPngSize];
+ return (nPngSize > 0);
+}
+
+bool PNGToImage( css::uno::Sequence<sal_Int8> const & rPngData,
+ css::uno::Sequence<sal_Int8>& rImgData,
+ NSBitmapImageFileType eOutFormat
+ )
+{
+ NSData* pData = [NSData dataWithBytesNoCopy: const_cast<sal_Int8*>(rPngData.getConstArray()) length: rPngData.getLength() freeWhenDone: false];
+ if( !pData)
+ return false;
+
+ NSBitmapImageRep* pRep = [NSBitmapImageRep imageRepWithData: pData];
+ if( !pRep)
+ return false;
+
+ NSData* pOut = [pRep representationUsingType: eOutFormat properties: @{ }];
+ if( !pOut)
+ return false;
+
+ const size_t nImgSize = [pOut length];
+ rImgData.realloc( nImgSize);
+ [pOut getBytes: rImgData.getArray() length: nImgSize];
+ return (nImgSize > 0);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/PictToBmpFlt.hxx b/vcl/osx/PictToBmpFlt.hxx
new file mode 100644
index 0000000000..d43146e03f
--- /dev/null
+++ b/vcl/osx/PictToBmpFlt.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 <com/sun/star/uno/Sequence.hxx>
+
+#include <premac.h>
+#include <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+bool ImageToPNG(css::uno::Sequence<sal_Int8> const& rImgData,
+ css::uno::Sequence<sal_Int8>& rPngData);
+
+bool PNGToImage(css::uno::Sequence<sal_Int8> const& rPngData,
+ css::uno::Sequence<sal_Int8>& rImgData, NSBitmapImageFileType eOutFormat);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/README.a11y b/vcl/osx/README.a11y
new file mode 100644
index 0000000000..4422713bc4
--- /dev/null
+++ b/vcl/osx/README.a11y
@@ -0,0 +1,7 @@
+Naming scheme:
+
+a11yXYZhelper: Helper class providing static methods
+
+a11yXYZwrapper: Wrapper around one (or two) UNO-interfaces
+
+a11ywrapperXYZ: Subclass of a11ywrapper for a specific AXRole
diff --git a/vcl/osx/a11yactionwrapper.h b/vcl/osx/a11yactionwrapper.h
new file mode 100644
index 0000000000..eb0141c805
--- /dev/null
+++ b/vcl/osx/a11yactionwrapper.h
@@ -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 <osx/osxvcltypes.h>
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yActionWrapper : NSObject
+{
+}
++ (NSArray*)actionNamesForElement:(AquaA11yWrapper*)wrapper;
++ (void)doAction:(NSString*)action ofElement:(AquaA11yWrapper*)wrapper;
++ (NSAccessibilityActionName)actionNameForSelector:(SEL)aSelector;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yactionwrapper.mm b/vcl/osx/a11yactionwrapper.mm
new file mode 100644
index 0000000000..9bea25c119
--- /dev/null
+++ b/vcl/osx/a11yactionwrapper.mm
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osx/salinst.h>
+#include <quartz/utils.h>
+
+#include "a11yactionwrapper.h"
+
+// Wrapper for XAccessibleAction
+
+@implementation AquaA11yActionWrapper : NSObject
+
++(NSString *)nativeActionNameFor:(NSString *)actionName {
+ // TODO: Optimize ?
+ // Use NSAccessibilityActionDescription
+ if ( [ actionName isEqualToString: @"press" ] ) {
+ return NSAccessibilityPressAction;
+ } else if ( [ actionName isEqualToString: @"togglePopup" ] ) {
+ return NSAccessibilityShowMenuAction;
+ } else if ( [ actionName isEqualToString: @"select" ] ) {
+ return NSAccessibilityPickAction;
+ } else if ( [ actionName isEqualToString: @"incrementLine" ] ) {
+ return NSAccessibilityIncrementAction;
+ } else if ( [ actionName isEqualToString: @"decrementLine" ] ) {
+ return NSAccessibilityDecrementAction;
+ } else if ( [ actionName isEqualToString: @"incrementBlock" ] ) {
+ return NSAccessibilityIncrementAction; // TODO ?
+ } else if ( [ actionName isEqualToString: @"decrementBlock" ] ) {
+ return NSAccessibilityDecrementAction; // TODO ?
+ } else if ( [ actionName isEqualToString: @"Browse" ] ) {
+ return NSAccessibilityPressAction; // TODO ?
+ } else {
+ return [ NSString string ];
+ }
+}
+
++(NSArray *)actionNamesForElement:(AquaA11yWrapper *)wrapper {
+ NSMutableArray * actionNames = [ [ NSMutableArray alloc ] init ];
+ if ( [ wrapper accessibleAction ] ) {
+ for ( int cnt = 0; cnt < [ wrapper accessibleAction ] -> getAccessibleActionCount(); cnt++ ) {
+ [ actionNames addObject: [ AquaA11yActionWrapper nativeActionNameFor: CreateNSString ( [ wrapper accessibleAction ] -> getAccessibleActionDescription ( cnt ) ) ] ];
+ }
+ }
+ return actionNames;
+}
+
++(void)doAction:(NSString *)action ofElement:(AquaA11yWrapper *)wrapper {
+ if ( [ wrapper accessibleAction ] ) {
+ for ( int cnt = 0; cnt < [ wrapper accessibleAction ] -> getAccessibleActionCount(); cnt++ ) {
+ if ( [ action isEqualToString: [ AquaA11yActionWrapper nativeActionNameFor: CreateNSString ( [ wrapper accessibleAction ] -> getAccessibleActionDescription ( cnt ) ) ] ] ) {
+ [ wrapper accessibleAction ] -> doAccessibleAction ( cnt );
+ break;
+ }
+ }
+ }
+}
+
++(NSAccessibilityActionName)actionNameForSelector:(SEL)aSelector
+{
+ NSAccessibilityActionName pRet = nil;
+
+ if ( aSelector == @selector(accessibilityPerformDecrement) ) {
+ pRet = NSAccessibilityDecrementAction;
+ } else if ( aSelector == @selector(accessibilityPerformIncrement) ) {
+ pRet = NSAccessibilityIncrementAction;
+ } else if ( aSelector == @selector(accessibilityPerformPick) ) {
+ pRet = NSAccessibilityPickAction;
+ } else if ( aSelector == @selector(accessibilityPerformPress) ) {
+ pRet = NSAccessibilityPressAction;
+ } else if ( aSelector == @selector(accessibilityPerformShowMenu) ) {
+ pRet = NSAccessibilityShowMenuAction;
+ }
+
+ return pRet;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ycomponentwrapper.h b/vcl/osx/a11ycomponentwrapper.h
new file mode 100644
index 0000000000..a63f327e43
--- /dev/null
+++ b/vcl/osx/a11ycomponentwrapper.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <osx/osxvcltypes.h>
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yComponentWrapper : NSObject
+{
+}
++ (id)sizeAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)positionAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)descriptionAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (void)addAttributeNamesTo:(NSMutableArray*)attributeNames;
++ (BOOL)isAttributeSettable:(NSString*)attribute forElement:(AquaA11yWrapper*)wrapper;
++ (void)setFocusedAttributeForElement:(AquaA11yWrapper*)wrapper to:(id)value;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ycomponentwrapper.mm b/vcl/osx/a11ycomponentwrapper.mm
new file mode 100644
index 0000000000..d9d6db1754
--- /dev/null
+++ b/vcl/osx/a11ycomponentwrapper.mm
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <quartz/utils.h>
+#include "a11ycomponentwrapper.h"
+#include "a11yrolehelper.h"
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::awt;
+using namespace ::com::sun::star::uno;
+
+// Wrapper for XAccessibleComponent and XAccessibleExtendedComponent
+
+@implementation AquaA11yComponentWrapper : NSObject
+
++(id)sizeAttributeForElement:(AquaA11yWrapper *)wrapper {
+ Size size = [ wrapper accessibleComponent ] -> getSize();
+ NSSize nsSize = NSMakeSize ( static_cast<float>(size.Width), static_cast<float>(size.Height) );
+ return [ NSValue valueWithSize: nsSize ];
+}
+
+// TODO: should be merged with AquaSalFrame::VCLToCocoa... to a general helper method
++(id)positionAttributeForElement:(AquaA11yWrapper *)wrapper {
+ // VCL coordinates are in upper-left-notation, Cocoa likes it the Cartesian way (lower-left)
+ NSRect screenRect = [ [ NSScreen mainScreen ] frame ];
+ Size size = [ wrapper accessibleComponent ] -> getSize();
+ Point location = [ wrapper accessibleComponent ] -> getLocationOnScreen();
+ NSPoint nsPoint = NSMakePoint ( static_cast<float>(location.X), static_cast<float>( screenRect.size.height - size.Height - location.Y ) );
+ return [ NSValue valueWithPoint: nsPoint ];
+}
+
++(id)descriptionAttributeForElement:(AquaA11yWrapper *)wrapper {
+ if ( [ wrapper accessibleExtendedComponent ] ) {
+ return CreateNSString ( [ wrapper accessibleExtendedComponent ] -> getToolTipText() );
+ } else {
+ return nil;
+ }
+}
+
++(void)addAttributeNamesTo:(NSMutableArray *)attributeNames {
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ [ attributeNames addObjectsFromArray: [ NSArray arrayWithObjects:
+ NSAccessibilitySizeAttribute,
+ NSAccessibilityPositionAttribute,
+ NSAccessibilityFocusedAttribute,
+ NSAccessibilityEnabledAttribute,
+ nil ] ];
+ [ pool release ];
+}
+
++(BOOL)isAttributeSettable:(NSString *)attribute forElement:(AquaA11yWrapper *)wrapper {
+ bool isSettable = false;
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ if ( [ attribute isEqualToString: NSAccessibilityFocusedAttribute ]
+ && ! [ [ AquaA11yRoleHelper getNativeRoleFrom: [ wrapper accessibleContext ] ] isEqualToString: NSAccessibilityScrollBarRole ]
+ && ! [ [ AquaA11yRoleHelper getNativeRoleFrom: [ wrapper accessibleContext ] ] isEqualToString: NSAccessibilityStaticTextRole ] ) {
+ isSettable = true;
+ }
+ [ pool release ];
+ return isSettable;
+}
+
++(void)setFocusedAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value {
+ if ( [ value boolValue ] == YES ) {
+ if ( [ wrapper accessibleContext ] -> getAccessibleRole() == AccessibleRole::COMBO_BOX ) {
+ // special treatment for comboboxes: find the corresponding PANEL and set focus to it
+ Reference < XAccessible > rxParent = [ wrapper accessibleContext ] -> getAccessibleParent();
+ if ( rxParent.is() ) {
+ Reference < XAccessibleContext > rxContext = rxParent->getAccessibleContext();
+ if ( rxContext.is() && rxContext -> getAccessibleRole() == AccessibleRole::PANEL ) {
+ Reference < XAccessibleComponent > rxComponent( rxParent -> getAccessibleContext(), UNO_QUERY );
+ if ( rxComponent.is() ) {
+ rxComponent -> grabFocus();
+ }
+ }
+ }
+ } else {
+ [ wrapper accessibleComponent ] -> grabFocus();
+ }
+ }
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yfactory.mm b/vcl/osx/a11yfactory.mm
new file mode 100644
index 0000000000..0783252c7e
--- /dev/null
+++ b/vcl/osx/a11yfactory.mm
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osx/salinst.h>
+#include <osx/a11yfactory.h>
+#include <osx/a11yfocustracker.hxx>
+
+#include "a11yfocuslistener.hxx"
+#include "a11yrolehelper.h"
+#include "a11ywrapperbutton.h"
+#include "a11ywrapperstatictext.h"
+#include "a11ywrappertextarea.h"
+#include "a11ywrappercheckbox.h"
+#include "a11ywrappercombobox.h"
+#include "a11ywrappergroup.h"
+#include "a11ywrapperlist.h"
+#include "a11ywrapperradiobutton.h"
+#include "a11ywrapperradiogroup.h"
+#include "a11ywrapperrow.h"
+#include "a11ywrapperscrollarea.h"
+#include "a11ywrapperscrollbar.h"
+#include "a11ywrappersplitter.h"
+#include "a11ywrappertabgroup.h"
+#include "a11ywrappertoolbar.h"
+#include "a11ytablewrapper.h"
+
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::uno;
+
+static bool enabled = false;
+
+@implementation AquaA11yFactory : NSObject
+
+#pragma mark -
+#pragma mark Wrapper Repository
+
++(NSMutableDictionary *)allWrapper {
+ static NSMutableDictionary * mdAllWrapper = nil;
+ if ( mdAllWrapper == nil ) {
+ mdAllWrapper = [ [ [ NSMutableDictionary alloc ] init ] retain ];
+ // initialize keyboard focus tracker
+ rtl::Reference< AquaA11yFocusListener > listener( AquaA11yFocusListener::get() );
+ TheAquaA11yFocusTracker().setFocusListener(listener);
+ enabled = true;
+ }
+ return mdAllWrapper;
+}
+
++(NSValue *)keyForAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext {
+ return [ NSValue valueWithPointer: rxAccessibleContext.get() ];
+}
+
++(NSValue *)keyForAccessibleContextAsRadioGroup: (Reference < XAccessibleContext >) rxAccessibleContext {
+ return [ NSValue valueWithPointer: ( rxAccessibleContext.get() + 2 ) ];
+}
+
++(AquaA11yWrapper *)wrapperForAccessible: (Reference < XAccessible >) rxAccessible {
+ if ( rxAccessible.is() ) {
+ Reference< XAccessibleContext > xAccessibleContext = rxAccessible->getAccessibleContext();
+ if( xAccessibleContext.is() ) {
+ return [ AquaA11yFactory wrapperForAccessibleContext: xAccessibleContext ];
+ }
+ }
+ return nil;
+}
+
++(AquaA11yWrapper *)wrapperForAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext {
+ return [ AquaA11yFactory wrapperForAccessibleContext: rxAccessibleContext createIfNotExists: YES asRadioGroup: NO ];
+}
+
++(AquaA11yWrapper *)wrapperForAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext createIfNotExists:(BOOL) bCreate {
+ return [ AquaA11yFactory wrapperForAccessibleContext: rxAccessibleContext createIfNotExists: bCreate asRadioGroup: NO ];
+}
+
++(AquaA11yWrapper *)wrapperForAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext createIfNotExists:(BOOL) bCreate asRadioGroup:(BOOL) asRadioGroup{
+ NSMutableDictionary * dAllWrapper = [ AquaA11yFactory allWrapper ];
+ NSValue * nKey = nil;
+ if ( asRadioGroup ) {
+ nKey = [ AquaA11yFactory keyForAccessibleContextAsRadioGroup: rxAccessibleContext ];
+ } else {
+ nKey = [ AquaA11yFactory keyForAccessibleContext: rxAccessibleContext ];
+ }
+ AquaA11yWrapper * aWrapper = static_cast<AquaA11yWrapper *>([ dAllWrapper objectForKey: nKey ]);
+ if ( aWrapper != nil ) {
+ [ aWrapper retain ];
+ } else if ( bCreate ) {
+ NSString * nativeRole = [ AquaA11yRoleHelper getNativeRoleFrom: rxAccessibleContext.get() ];
+ // TODO: reflection
+ if ( [ nativeRole isEqualToString: NSAccessibilityButtonRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperButton alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityTextAreaRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperTextArea alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityStaticTextRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperStaticText alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityComboBoxRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperComboBox alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityGroupRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperGroup alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityToolbarRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperToolbar alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityScrollAreaRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperScrollArea alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityTabGroupRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperTabGroup alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityScrollBarRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperScrollBar alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityCheckBoxRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperCheckBox alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityRadioGroupRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperRadioGroup alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityRadioButtonRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperRadioButton alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityRowRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperRow alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityListRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperList alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilitySplitterRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperSplitter alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityTableRole ] ) {
+ aWrapper = [ [ AquaA11yTableWrapper alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else {
+ aWrapper = [ [ AquaA11yWrapper alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ }
+ [ nativeRole release ];
+ [ aWrapper setActsAsRadioGroup: asRadioGroup ];
+ #if 0
+ /* #i102033# NSAccessibility does not seemt to know an equivalent for transient children.
+ That means we need to cache this, else e.g. tree list boxes are not accessible (moreover
+ it crashes by notifying dead objects - which would seemt o be another bug)
+
+ FIXME:
+ Unfortunately this can increase memory consumption drastically until the non transient parent
+ is destroyed and finally all the transients are released.
+ */
+ if ( ! (rxAccessibleContext -> getAccessibleStateSet() & AccessibleStateType::TRANSIENT ) )
+ #endif
+ {
+ [ dAllWrapper setObject: aWrapper forKey: nKey ];
+ }
+ }
+ return aWrapper;
+}
+
++(void)insertIntoWrapperRepository: (AquaA11yWrapper *) element forAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext {
+ NSMutableDictionary * dAllWrapper = [ AquaA11yFactory allWrapper ];
+ [ dAllWrapper setObject: element forKey: [ AquaA11yFactory keyForAccessibleContext: rxAccessibleContext ] ];
+}
+
++(void)removeFromWrapperRepositoryFor: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext {
+ // TODO: when RADIO_BUTTON search for associated RadioGroup-wrapper and delete that as well
+ AquaA11yWrapper * theWrapper = [ AquaA11yFactory wrapperForAccessibleContext: rxAccessibleContext createIfNotExists: NO ];
+ if ( theWrapper != nil ) {
+ NSAccessibilityPostNotification( theWrapper, NSAccessibilityUIElementDestroyedNotification );
+ [ [ AquaA11yFactory allWrapper ] removeObjectForKey: [ AquaA11yFactory keyForAccessibleContext: rxAccessibleContext ] ];
+ [ theWrapper release ];
+ }
+}
+
++(void)registerWrapper: (AquaA11yWrapper *) theWrapper {
+ if ( enabled && theWrapper ) {
+ // insertIntoWrapperRepository gets called from SalFrameView itself to bootstrap the bridge initially
+ [ theWrapper accessibleContext ];
+ }
+}
+
++(void)revokeWrapper: (AquaA11yWrapper *) theWrapper {
+ if ( enabled && theWrapper ) {
+ [ AquaA11yFactory removeFromWrapperRepositoryFor: [ theWrapper accessibleContext ] ];
+ }
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yfocuslistener.cxx b/vcl/osx/a11yfocuslistener.cxx
new file mode 100644
index 0000000000..bae851647e
--- /dev/null
+++ b/vcl/osx/a11yfocuslistener.cxx
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <osx/a11yfocustracker.hxx>
+#include <osx/a11yfactory.h>
+
+#include "a11yfocuslistener.hxx"
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::uno;
+
+rtl::Reference< AquaA11yFocusListener > AquaA11yFocusListener::theListener;
+
+rtl::Reference< AquaA11yFocusListener > const & AquaA11yFocusListener::get()
+{
+ if ( ! theListener.is() )
+ theListener = new AquaA11yFocusListener();
+
+ return theListener;
+}
+
+AquaA11yFocusListener::AquaA11yFocusListener() : m_focusedObject(nil)
+{
+}
+
+id AquaA11yFocusListener::getFocusedUIElement()
+{
+ if ( nil == m_focusedObject ) {
+ Reference< XAccessible > xAccessible( TheAquaA11yFocusTracker().getFocusedObject() );
+ try {
+ if( xAccessible.is() ) {
+ Reference< XAccessibleContext > xContext(xAccessible->getAccessibleContext());
+ if( xContext.is() )
+ m_focusedObject = [ AquaA11yFactory wrapperForAccessibleContext: xContext ];
+ }
+ } catch(const RuntimeException &) {
+ // intentionally do nothing ..
+ }
+ }
+
+ return m_focusedObject;
+}
+
+void
+AquaA11yFocusListener::focusedObjectChanged(const Reference< XAccessible >& xAccessible)
+{
+ if ( nil != m_focusedObject ) {
+ [ m_focusedObject release ];
+ m_focusedObject = nil;
+ }
+
+ try {
+ if( xAccessible.is() ) {
+ Reference< XAccessibleContext > xContext(xAccessible->getAccessibleContext());
+ if( xContext.is() )
+ {
+ m_focusedObject = [ AquaA11yFactory wrapperForAccessibleContext: xContext ];
+ NSAccessibilityPostNotification(m_focusedObject, NSAccessibilityFocusedUIElementChangedNotification);
+ }
+ }
+ } catch(const RuntimeException &) {
+ // intentionally do nothing ..
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yfocuslistener.hxx b/vcl/osx/a11yfocuslistener.hxx
new file mode 100644
index 0000000000..9cd6b656db
--- /dev/null
+++ b/vcl/osx/a11yfocuslistener.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <osx/keyboardfocuslistener.hxx>
+#include <osx/osxvcltypes.h>
+
+class AquaA11yFocusListener : public KeyboardFocusListener
+{
+ id m_focusedObject;
+
+ static rtl::Reference<AquaA11yFocusListener> theListener;
+
+ AquaA11yFocusListener();
+ virtual ~AquaA11yFocusListener() override{};
+
+public:
+ static rtl::Reference<AquaA11yFocusListener> const& get();
+
+ id getFocusedUIElement();
+
+ // KeyboardFocusListener
+ virtual void focusedObjectChanged(
+ const css::uno::Reference<css::accessibility::XAccessible>& xAccessible) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yfocustracker.cxx b/vcl/osx/a11yfocustracker.cxx
new file mode 100644
index 0000000000..2aaa8b0a8e
--- /dev/null
+++ b/vcl/osx/a11yfocustracker.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 <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/menu.hxx>
+
+#include <osx/a11yfocustracker.hxx>
+
+#include "documentfocuslistener.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+AquaA11yFocusTracker& TheAquaA11yFocusTracker()
+{
+ static AquaA11yFocusTracker SINGLETON;
+ return SINGLETON;
+}
+
+static vcl::Window *
+getWindow(const ::VclSimpleEvent *pEvent)
+{
+ return static_cast< const ::VclWindowEvent *> (pEvent)->GetWindow();
+}
+
+// callback function for Application::addEventListener
+
+void AquaA11yFocusTracker::WindowEventHandler(void * pThis, VclSimpleEvent& rEvent)
+{
+ AquaA11yFocusTracker *pFocusTracker = static_cast<AquaA11yFocusTracker *>(
+ pThis);
+ switch (rEvent.GetId())
+ {
+ case VclEventId::WindowPaint:
+ pFocusTracker-> toolbox_open_floater( getWindow(&rEvent) );
+ break;
+ case VclEventId::WindowGetFocus:
+ pFocusTracker->window_got_focus( getWindow(&rEvent) );
+ break;
+ case VclEventId::ObjectDying:
+ pFocusTracker->m_aDocumentWindowList.erase( getWindow(&rEvent) );
+ [[fallthrough]];
+ case VclEventId::ToolboxHighlightOff:
+ pFocusTracker->toolbox_highlight_off( getWindow(&rEvent) );
+ break;
+ case VclEventId::ToolboxHighlight:
+ pFocusTracker->toolbox_highlight_on( getWindow(&rEvent) );
+ break;
+ case VclEventId::TabpageActivate:
+ pFocusTracker->tabpage_activated( getWindow(&rEvent) );
+ break;
+ case VclEventId::MenuHighlight:
+ // Inspired by code in WindowEventHandler in
+ // vcl/unx/gtk/a11y/atkutil.cxx, find out what kind of event
+ // it is to avoid blindly using a static_cast and crash,
+ // fdo#47275.
+ if( const VclMenuEvent* pMenuEvent = dynamic_cast < const VclMenuEvent* > (&rEvent) )
+ {
+ pFocusTracker->menu_highlighted( pMenuEvent );
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+AquaA11yFocusTracker::AquaA11yFocusTracker() :
+ m_aWindowEventLink(this, WindowEventHandler),
+ m_xDocumentFocusListener(new DocumentFocusListener(*this))
+{
+ Application::AddEventListener(m_aWindowEventLink);
+ window_got_focus(Application::GetFocusWindow());
+}
+
+AquaA11yFocusTracker::~AquaA11yFocusTracker() {}
+
+void AquaA11yFocusTracker::setFocusedObject(const Reference< XAccessible >& xAccessible)
+{
+ if( xAccessible != m_xFocusedObject )
+ {
+ m_xFocusedObject = xAccessible;
+
+ if( m_aFocusListener.is() )
+ m_aFocusListener->focusedObjectChanged(xAccessible);
+ }
+}
+
+void AquaA11yFocusTracker::notify_toolbox_item_focus(ToolBox *pToolBox)
+{
+ Reference< XAccessible > xAccessible( pToolBox->GetAccessible() );
+
+ if( xAccessible.is() )
+ {
+ Reference< XAccessibleContext > xContext(xAccessible->getAccessibleContext());
+
+ if( xContext.is() )
+ {
+ try {
+ ToolBox::ImplToolItems::size_type nPos = pToolBox->GetItemPos( pToolBox->GetHighlightItemId() );
+ if( nPos != ToolBox::ITEM_NOTFOUND )
+ setFocusedObject( xContext->getAccessibleChild( nPos ) );
+ //TODO: ToolBox::ImplToolItems::size_type -> sal_Int32!
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "Accessible object has invalid index in parent");
+ }
+ }
+ }
+}
+
+void AquaA11yFocusTracker::toolbox_open_floater(vcl::Window *pWindow)
+{
+ bool bToolboxFound = false;
+ bool bFloatingWindowFound = false;
+ vcl::Window * pFloatingWindow = nullptr;
+ while ( pWindow != nullptr ) {
+ if ( pWindow->GetType() == WindowType::TOOLBOX ) {
+ bToolboxFound = true;
+ } else if ( pWindow->GetType() == WindowType::FLOATINGWINDOW ) {
+ bFloatingWindowFound = true;
+ pFloatingWindow = pWindow;
+ }
+ pWindow = pWindow->GetParent();
+ }
+ if ( bToolboxFound && bFloatingWindowFound ) {
+ Reference < XAccessible > rxAccessible = pFloatingWindow -> GetAccessible();
+ if ( ! rxAccessible.is() ) {
+ return;
+ }
+ Reference < XAccessibleContext > rxContext = rxAccessible -> getAccessibleContext();
+ if ( ! rxContext.is() ) {
+ return;
+ }
+ if ( rxContext -> getAccessibleChildCount() > 0 ) {
+ try {
+ Reference < XAccessible > rxAccessibleChild = rxContext -> getAccessibleChild( 0 );
+ if ( ! rxAccessibleChild.is() ) {
+ return;
+ }
+ setFocusedObject ( rxAccessibleChild );
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "No valid accessible objects in parent");
+ }
+ }
+ }
+}
+
+void AquaA11yFocusTracker::toolbox_highlight_on(vcl::Window *pWindow)
+{
+ // Make sure either the toolbox or its parent toolbox has the focus
+ if ( ! pWindow->HasFocus() )
+ {
+ ToolBox* pToolBoxParent = dynamic_cast< ToolBox * >( pWindow->GetParent() );
+ if ( ! pToolBoxParent || ! pToolBoxParent->HasFocus() )
+ return;
+ }
+
+ notify_toolbox_item_focus(static_cast <ToolBox *> (pWindow));
+}
+
+void AquaA11yFocusTracker::toolbox_highlight_off(vcl::Window const *pWindow)
+{
+ ToolBox* pToolBoxParent = dynamic_cast< ToolBox * >( pWindow->GetParent() );
+
+ // Notify when leaving sub toolboxes
+ if( pToolBoxParent && pToolBoxParent->HasFocus() )
+ notify_toolbox_item_focus( pToolBoxParent );
+}
+
+void AquaA11yFocusTracker::tabpage_activated(vcl::Window *pWindow)
+{
+ Reference< XAccessible > xAccessible( pWindow->GetAccessible() );
+
+ if( xAccessible.is() )
+ {
+ Reference< XAccessibleSelection > xSelection(xAccessible->getAccessibleContext(), UNO_QUERY);
+
+ if( xSelection.is() )
+ setFocusedObject( xSelection->getSelectedAccessibleChild(0) );
+ }
+}
+
+void AquaA11yFocusTracker::menu_highlighted(const VclMenuEvent *pEvent)
+{
+ Menu * pMenu = pEvent->GetMenu();
+
+ if( pMenu )
+ {
+ Reference< XAccessible > xAccessible( pMenu->GetAccessible() );
+
+ if( xAccessible.is() )
+ setFocusedObject( xAccessible );
+ }
+}
+
+void AquaA11yFocusTracker::window_got_focus(vcl::Window *pWindow)
+{
+ // The menu bar is handled through VclEventId::MenuHighlightED
+ if( ! pWindow || !pWindow->IsReallyVisible() || pWindow->GetType() == WindowType::MENUBARWINDOW )
+ return;
+
+ // ToolBoxes are handled through VclEventId::ToolboxHighlight
+ if( pWindow->GetType() == WindowType::TOOLBOX )
+ return;
+
+ if( pWindow->GetType() == WindowType::TABCONTROL )
+ {
+ tabpage_activated( pWindow );
+ return;
+ }
+
+ Reference< XAccessible > xAccessible(pWindow->GetAccessible());
+
+ if( ! xAccessible.is() )
+ return;
+
+ Reference< XAccessibleContext > xContext = xAccessible->getAccessibleContext();
+
+ if( ! xContext.is() )
+ return;
+
+ sal_Int64 nStateSet = xContext->getAccessibleStateSet();
+
+ if( ! nStateSet )
+ return;
+
+/* the UNO ToolBox wrapper does not (yet?) support XAccessibleSelection, so we
+ * need to add listeners to the children instead of re-using the tabpage stuff
+ */
+ if( (nStateSet & AccessibleStateType::FOCUSED) && (pWindow->GetType() != WindowType::TREELISTBOX) )
+ {
+ setFocusedObject( xAccessible );
+ }
+ else
+ {
+ if( m_aDocumentWindowList.insert(pWindow).second )
+ m_xDocumentFocusListener->attachRecursive(xAccessible, xContext, nStateSet);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ylistener.cxx b/vcl/osx/a11ylistener.cxx
new file mode 100644
index 0000000000..b49b450927
--- /dev/null
+++ b/vcl/osx/a11ylistener.cxx
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <osx/salinst.h>
+#include <osx/a11ylistener.hxx>
+#include <osx/a11yfactory.h>
+#include <osx/a11yfocustracker.hxx>
+#include <osx/a11ywrapper.h>
+
+#include "a11ytextwrapper.h"
+
+#include <com/sun/star/accessibility/AccessibleEventId.hpp>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp>
+#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::awt;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+static NSString * getTableNotification( const AccessibleEventObject& aEvent )
+{
+ AccessibleTableModelChange aChange;
+ NSString * notification = nil;
+
+ if( (aEvent.NewValue >>= aChange) &&
+ (aChange.Type == AccessibleTableModelChangeType::ROWS_INSERTED ||
+ aChange.Type == AccessibleTableModelChangeType::ROWS_REMOVED))
+ {
+ notification = NSAccessibilityRowCountChangedNotification;
+ }
+
+ return notification;
+}
+
+AquaA11yEventListener::AquaA11yEventListener(id wrapperObject, sal_Int16 role) : m_wrapperObject(wrapperObject), m_role(role)
+{
+ [ m_wrapperObject retain ];
+}
+
+AquaA11yEventListener::~AquaA11yEventListener()
+{
+ [ m_wrapperObject release ];
+}
+
+void SAL_CALL
+AquaA11yEventListener::disposing( const EventObject& )
+{
+ [ AquaA11yFactory removeFromWrapperRepositoryFor: [ static_cast<AquaA11yWrapper *>(m_wrapperObject) accessibleContext ] ];
+}
+
+void SAL_CALL
+AquaA11yEventListener::notifyEvent( const AccessibleEventObject& aEvent )
+{
+ NSString * notification = nil;
+ id element = m_wrapperObject;
+ ::css::awt::Rectangle bounds;
+
+ // TODO: NSAccessibilityValueChanged, NSAccessibilitySelectedRowsChangedNotification
+ switch( aEvent.EventId )
+ {
+ case AccessibleEventId::ACTIVE_DESCENDANT_CHANGED:
+ if( m_role != AccessibleRole::LIST ) {
+ Reference< XAccessible > xAccessible;
+ if( aEvent.NewValue >>= xAccessible )
+ TheAquaA11yFocusTracker().setFocusedObject( xAccessible );
+ }
+ break;
+
+ case AccessibleEventId::NAME_CHANGED:
+ notification = NSAccessibilityTitleChangedNotification;
+ break;
+
+ case AccessibleEventId::CHILD:
+ // only needed for tooltips (says Apple)
+ if ( m_role == AccessibleRole::TOOL_TIP ) {
+ if(aEvent.NewValue.hasValue()) {
+ notification = NSAccessibilityCreatedNotification;
+ } else if(aEvent.OldValue.hasValue()) {
+ notification = NSAccessibilityUIElementDestroyedNotification;
+ }
+ }
+ break;
+
+ case AccessibleEventId::INVALIDATE_ALL_CHILDREN:
+ // TODO: deprecate or remember all children
+ break;
+
+ case AccessibleEventId::BOUNDRECT_CHANGED:
+ bounds = [ element accessibleComponent ] -> getBounds();
+ if ( m_oldBounds.X != 0 && ( bounds.X != m_oldBounds.X || bounds.Y != m_oldBounds.Y ) ) {
+ NSAccessibilityPostNotification(element, NSAccessibilityMovedNotification); // post directly since both cases can happen simultaneously
+ }
+ if ( m_oldBounds.X != 0 && ( bounds.Width != m_oldBounds.Width || bounds.Height != m_oldBounds.Height ) ) {
+ NSAccessibilityPostNotification(element, NSAccessibilityResizedNotification); // post directly since both cases can happen simultaneously
+ }
+ m_oldBounds = bounds;
+ break;
+
+ case AccessibleEventId::SELECTION_CHANGED:
+ notification = NSAccessibilitySelectedChildrenChangedNotification;
+ break;
+
+ case AccessibleEventId::TEXT_SELECTION_CHANGED:
+ notification = NSAccessibilitySelectedTextChangedNotification;
+ break;
+
+ case AccessibleEventId::TABLE_MODEL_CHANGED:
+ notification = getTableNotification(aEvent);
+ break;
+
+ case AccessibleEventId::CARET_CHANGED:
+ notification = NSAccessibilitySelectedTextChangedNotification;
+ break;
+
+ case AccessibleEventId::TEXT_CHANGED:
+ notification = NSAccessibilityValueChangedNotification;
+ break;
+
+ default:
+ break;
+ }
+
+ if( nil != notification )
+ NSAccessibilityPostNotification(element, notification);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yrolehelper.h b/vcl/osx/a11yrolehelper.h
new file mode 100644
index 0000000000..db349ad38d
--- /dev/null
+++ b/vcl/osx/a11yrolehelper.h
@@ -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 <com/sun/star/accessibility/XAccessibleContext.hpp>
+
+@interface AquaA11yRoleHelper : NSObject
+{
+}
++(id)getNativeRoleFrom: (css::accessibility::XAccessibleContext *) accessibleContext;
++(id)getNativeSubroleFrom: (sal_Int16) nRole;
++(id)getRoleDescriptionFrom: (NSString *) role with: (NSString *) subRole;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yrolehelper.mm b/vcl/osx/a11yrolehelper.mm
new file mode 100644
index 0000000000..a1cf62f327
--- /dev/null
+++ b/vcl/osx/a11yrolehelper.mm
@@ -0,0 +1,296 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osx/a11yfactory.h>
+
+#include "a11yrolehelper.h"
+
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+
+#include <sal/log.hxx>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+@implementation AquaA11yRoleHelper
+
++(id)simpleMapNativeRoleFrom: (XAccessibleContext *) accessibleContext {
+ id nativeRole = nil;
+
+ if (accessibleContext == nullptr)
+ return nativeRole;
+
+ switch( accessibleContext -> getAccessibleRole() ) {
+#define MAP(a,b) \
+ case a: nativeRole = b; break
+
+ MAP( AccessibleRole::UNKNOWN, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::ALERT, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::BLOCK_QUOTE, NSAccessibilityTextAreaRole );
+ MAP( AccessibleRole::COLUMN_HEADER, NSAccessibilityColumnRole );
+ MAP( AccessibleRole::CANVAS, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::CHECK_BOX, NSAccessibilityCheckBoxRole );
+ MAP( AccessibleRole::CHECK_MENU_ITEM, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::COLOR_CHOOSER, NSAccessibilityColorWellRole ); // FIXME
+ MAP( AccessibleRole::COMBO_BOX, NSAccessibilityComboBoxRole );
+ MAP( AccessibleRole::DATE_EDITOR, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::DESKTOP_ICON, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::DESKTOP_PANE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::DIRECTORY_PANE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::DIALOG, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::DOCUMENT, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::EMBEDDED_OBJECT, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::END_NOTE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::FILE_CHOOSER, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::FILLER, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::FONT_CHOOSER, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::FOOTER, NSAccessibilityGroupRole ); // FIXME
+ MAP( AccessibleRole::FOOTNOTE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::FRAME, NSAccessibilityWindowRole );
+ MAP( AccessibleRole::GLASS_PANE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::GRAPHIC, NSAccessibilityImageRole );
+ MAP( AccessibleRole::GROUP_BOX, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::HEADER, NSAccessibilityGroupRole ); // FIXME
+ MAP( AccessibleRole::HEADING, NSAccessibilityTextAreaRole ); // FIXME
+ MAP( AccessibleRole::HYPER_LINK, NSAccessibilityLinkRole );
+ MAP( AccessibleRole::ICON, NSAccessibilityImageRole );
+ MAP( AccessibleRole::INTERNAL_FRAME, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::LABEL, NSAccessibilityStaticTextRole );
+ MAP( AccessibleRole::LAYERED_PANE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::LIST, NSAccessibilityMenuRole );
+ MAP( AccessibleRole::LIST_ITEM, NSAccessibilityMenuItemRole );
+ MAP( AccessibleRole::MENU, NSAccessibilityMenuRole );
+ MAP( AccessibleRole::MENU_BAR, NSAccessibilityMenuBarRole );
+ MAP( AccessibleRole::MENU_ITEM, NSAccessibilityMenuItemRole );
+ MAP( AccessibleRole::OPTION_PANE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::PAGE_TAB, NSAccessibilityButtonRole );
+ MAP( AccessibleRole::PAGE_TAB_LIST, NSAccessibilityTabGroupRole );
+ MAP( AccessibleRole::PANEL, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::PARAGRAPH, NSAccessibilityTextAreaRole );
+ MAP( AccessibleRole::PASSWORD_TEXT, NSAccessibilityTextFieldRole );
+ MAP( AccessibleRole::POPUP_MENU, NSAccessibilityMenuRole );
+ MAP( AccessibleRole::PUSH_BUTTON, NSAccessibilityButtonRole );
+ MAP( AccessibleRole::PROGRESS_BAR, NSAccessibilityProgressIndicatorRole );
+ MAP( AccessibleRole::RADIO_BUTTON, NSAccessibilityRadioButtonRole );
+ MAP( AccessibleRole::RADIO_MENU_ITEM, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::ROW_HEADER, NSAccessibilityRowRole );
+ MAP( AccessibleRole::ROOT_PANE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::SCROLL_BAR, NSAccessibilityScrollBarRole );
+ MAP( AccessibleRole::SCROLL_PANE, NSAccessibilityScrollAreaRole );
+ MAP( AccessibleRole::SHAPE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::SEPARATOR, NSAccessibilitySplitterRole ); // FIXME
+ MAP( AccessibleRole::SLIDER, NSAccessibilitySliderRole );
+ MAP( AccessibleRole::SPIN_BOX, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::SPLIT_PANE, NSAccessibilitySplitGroupRole );
+ MAP( AccessibleRole::STATUS_BAR, NSAccessibilityGroupRole ); // FIXME
+ MAP( AccessibleRole::TABLE, NSAccessibilityTableRole );
+ MAP( AccessibleRole::TABLE_CELL, NSAccessibilityTextFieldRole );
+ MAP( AccessibleRole::TEXT, NSAccessibilityTextAreaRole );
+ MAP( AccessibleRole::TEXT_FRAME, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::TOGGLE_BUTTON, NSAccessibilityCheckBoxRole );
+ MAP( AccessibleRole::TOOL_BAR, NSAccessibilityToolbarRole );
+ MAP( AccessibleRole::TOOL_TIP, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::TREE, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::VIEW_PORT, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::WINDOW, NSAccessibilityWindowRole );
+
+ MAP( AccessibleRole::BUTTON_DROPDOWN, NSAccessibilityMenuButtonRole );
+ MAP( AccessibleRole::BUTTON_MENU, NSAccessibilityMenuButtonRole );
+ MAP( AccessibleRole::CAPTION, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::CHART, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::FORM, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::IMAGE_MAP, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::NOTE, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::PAGE, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::RULER, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::SECTION, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::TREE_ITEM, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::TREE_TABLE, NSAccessibilityUnknownRole );
+
+ MAP( AccessibleRole::DOCUMENT_PRESENTATION, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::DOCUMENT_SPREADSHEET, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::DOCUMENT_TEXT, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::STATIC, NSAccessibilityStaticTextRole );
+ MAP( AccessibleRole::NOTIFICATION, NSAccessibilityStaticTextRole );
+
+#undef MAP
+ default:
+ break;
+ }
+ return nativeRole;
+}
+
++(id)getNativeRoleFrom: (XAccessibleContext *) accessibleContext {
+ id nativeRole = [ AquaA11yRoleHelper simpleMapNativeRoleFrom: accessibleContext ];
+ if ( accessibleContext -> getAccessibleRole() == AccessibleRole::LABEL ) {
+ if ( accessibleContext -> getAccessibleChildCount() > 0 ) {
+ [ nativeRole release ];
+ nativeRole = NSAccessibilityOutlineRole;
+ } else if ( accessibleContext -> getAccessibleParent().is() ) {
+ Reference < XAccessibleContext > rxParentContext = accessibleContext -> getAccessibleParent() -> getAccessibleContext();
+ if ( rxParentContext.is() ) {
+ NSString * roleParent = static_cast<NSString *>([ AquaA11yRoleHelper simpleMapNativeRoleFrom: rxParentContext.get() ]);
+ if ( [ roleParent isEqualToString: NSAccessibilityOutlineRole ] ) {
+ [ nativeRole release ];
+ nativeRole = NSAccessibilityRowRole;
+ }
+ [ roleParent release ];
+ }
+ }
+ } else if ( accessibleContext -> getAccessibleRole() == AccessibleRole::COMBO_BOX ) {
+ try {
+ Reference < XAccessible > rxAccessible = accessibleContext -> getAccessibleChild(0);
+ if ( rxAccessible.is() ) {
+ Reference < XAccessibleContext > rxAccessibleContext = rxAccessible -> getAccessibleContext();
+ if ( rxAccessibleContext.is() && rxAccessibleContext -> getAccessibleRole() == AccessibleRole::TEXT ) {
+ sal_Int64 nStateSet = rxAccessibleContext -> getAccessibleStateSet();
+ if ( !(nStateSet & AccessibleStateType::EDITABLE ) ) {
+ [ nativeRole release ];
+ nativeRole = NSAccessibilityPopUpButtonRole;
+ }
+ }
+ }
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "No valid accessible objects in parent");
+ }
+ }
+ return nativeRole;
+}
+
++(id)getNativeSubroleFrom: (sal_Int16) nRole {
+ id nativeSubrole = nil;
+ switch( nRole ) {
+#define MAP(a,b) \
+ case a: nativeSubrole = b; break
+
+ MAP( AccessibleRole::UNKNOWN, NSAccessibilityUnknownSubrole );
+ MAP( AccessibleRole::ALERT, NSAccessibilitySystemDialogSubrole );
+ MAP( AccessibleRole::BLOCK_QUOTE, @"" );
+ MAP( AccessibleRole::COLUMN_HEADER, @"" );
+ MAP( AccessibleRole::CANVAS, @"" );
+ MAP( AccessibleRole::CHECK_BOX, @"" );
+ MAP( AccessibleRole::CHECK_MENU_ITEM, @"" );
+ MAP( AccessibleRole::COLOR_CHOOSER, @"" );
+ MAP( AccessibleRole::COMBO_BOX, @"" );
+ MAP( AccessibleRole::DATE_EDITOR, @"" );
+ MAP( AccessibleRole::DESKTOP_ICON, @"" );
+ MAP( AccessibleRole::DESKTOP_PANE, @"" );
+ MAP( AccessibleRole::DIRECTORY_PANE, @"" );
+ MAP( AccessibleRole::DIALOG, NSAccessibilityDialogSubrole );
+ MAP( AccessibleRole::DOCUMENT, @"" );
+ MAP( AccessibleRole::EMBEDDED_OBJECT, @"" );
+ MAP( AccessibleRole::END_NOTE, @"" );
+ MAP( AccessibleRole::FILE_CHOOSER, @"" );
+ MAP( AccessibleRole::FILLER, @"" );
+ MAP( AccessibleRole::FONT_CHOOSER, @"" );
+ MAP( AccessibleRole::FOOTER, @"" );
+ MAP( AccessibleRole::FOOTNOTE, @"" );
+ MAP( AccessibleRole::FRAME, @"" );
+ MAP( AccessibleRole::GLASS_PANE, @"" );
+ MAP( AccessibleRole::GRAPHIC, @"" );
+ MAP( AccessibleRole::GROUP_BOX, @"" );
+ MAP( AccessibleRole::HEADER, @"" );
+ MAP( AccessibleRole::HEADING, @"" );
+ MAP( AccessibleRole::HYPER_LINK, NSAccessibilityTextLinkSubrole );
+ MAP( AccessibleRole::ICON, @"" );
+ MAP( AccessibleRole::INTERNAL_FRAME, @"" );
+ MAP( AccessibleRole::LABEL, @"" );
+ MAP( AccessibleRole::LAYERED_PANE, @"" );
+ MAP( AccessibleRole::LIST, @"" );
+ MAP( AccessibleRole::LIST_ITEM, NSAccessibilityOutlineRowSubrole );
+ MAP( AccessibleRole::MENU, @"" );
+ MAP( AccessibleRole::MENU_BAR, @"" );
+ MAP( AccessibleRole::MENU_ITEM, @"" );
+ MAP( AccessibleRole::OPTION_PANE, @"" );
+ MAP( AccessibleRole::PAGE_TAB, @"" );
+ MAP( AccessibleRole::PAGE_TAB_LIST, @"" );
+ MAP( AccessibleRole::PANEL, @"" );
+ MAP( AccessibleRole::PARAGRAPH, @"" );
+ MAP( AccessibleRole::PASSWORD_TEXT, NSAccessibilitySecureTextFieldSubrole );
+ MAP( AccessibleRole::POPUP_MENU, @"" );
+ MAP( AccessibleRole::PUSH_BUTTON, @"" );
+ MAP( AccessibleRole::PROGRESS_BAR, @"" );
+ MAP( AccessibleRole::RADIO_BUTTON, @"" );
+ MAP( AccessibleRole::RADIO_MENU_ITEM, @"" );
+ MAP( AccessibleRole::ROW_HEADER, @"" );
+ MAP( AccessibleRole::ROOT_PANE, @"" );
+ MAP( AccessibleRole::SCROLL_BAR, @"" );
+ MAP( AccessibleRole::SCROLL_PANE, @"" );
+ MAP( AccessibleRole::SHAPE, @"" );
+ MAP( AccessibleRole::SEPARATOR, @"" );
+ MAP( AccessibleRole::SLIDER, @"" );
+ MAP( AccessibleRole::SPIN_BOX, @"" );
+ MAP( AccessibleRole::SPLIT_PANE, @"" );
+ MAP( AccessibleRole::STATUS_BAR, @"" );
+ MAP( AccessibleRole::TABLE, @"" );
+ MAP( AccessibleRole::TABLE_CELL, @"" );
+ MAP( AccessibleRole::TEXT, @"" );
+ MAP( AccessibleRole::TEXT_FRAME, @"" );
+ MAP( AccessibleRole::TOGGLE_BUTTON, @"" );
+ MAP( AccessibleRole::TOOL_BAR, @"" );
+ MAP( AccessibleRole::TOOL_TIP, @"" );
+ MAP( AccessibleRole::TREE, @"" );
+ MAP( AccessibleRole::VIEW_PORT, @"" );
+ MAP( AccessibleRole::WINDOW, NSAccessibilityStandardWindowSubrole );
+
+ MAP( AccessibleRole::BUTTON_DROPDOWN, @"" );
+ MAP( AccessibleRole::BUTTON_MENU, @"" );
+ MAP( AccessibleRole::CAPTION, @"" );
+ MAP( AccessibleRole::CHART, @"" );
+ MAP( AccessibleRole::FORM, @"" );
+ MAP( AccessibleRole::IMAGE_MAP, @"" );
+ MAP( AccessibleRole::NOTE, @"" );
+ MAP( AccessibleRole::PAGE, @"" );
+ MAP( AccessibleRole::RULER, @"" );
+ MAP( AccessibleRole::SECTION, @"" );
+ MAP( AccessibleRole::TREE_ITEM, @"" );
+ MAP( AccessibleRole::TREE_TABLE, @"" );
+
+ MAP( AccessibleRole::DOCUMENT_PRESENTATION, @"" );
+ MAP( AccessibleRole::DOCUMENT_SPREADSHEET, @"" );
+ MAP( AccessibleRole::DOCUMENT_TEXT, @"" );
+
+ MAP( AccessibleRole::STATIC, @"" );
+ MAP( AccessibleRole::NOTIFICATION, @"" );
+
+#undef MAP
+ default:
+ break;
+ }
+ return nativeSubrole;
+}
+
++(id)getRoleDescriptionFrom: (NSString *) role with: (NSString *) subRole {
+ id roleDescription;
+ if ( [ subRole length ] == 0 )
+ roleDescription = NSAccessibilityRoleDescription( role, nil );
+ else
+ roleDescription = NSAccessibilityRoleDescription( role, subRole );
+ return roleDescription;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yselectionwrapper.h b/vcl/osx/a11yselectionwrapper.h
new file mode 100644
index 0000000000..3b62fbd903
--- /dev/null
+++ b/vcl/osx/a11yselectionwrapper.h
@@ -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 <osx/osxvcltypes.h>
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11ySelectionWrapper : NSObject
+{
+}
++ (id)selectedChildrenAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (void)addAttributeNamesTo:(NSMutableArray*)attributeNames;
++ (BOOL)isAttributeSettable:(NSString*)attribute forElement:(AquaA11yWrapper*)wrapper;
++ (void)setSelectedChildrenAttributeForElement:(AquaA11yWrapper*)wrapper to:(id)value;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yselectionwrapper.mm b/vcl/osx/a11yselectionwrapper.mm
new file mode 100644
index 0000000000..9d3beee2d3
--- /dev/null
+++ b/vcl/osx/a11yselectionwrapper.mm
@@ -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 <osx/salinst.h>
+#include <osx/a11yfactory.h>
+
+#include "a11yselectionwrapper.h"
+#include "a11ytablewrapper.h"
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::uno;
+
+@implementation AquaA11ySelectionWrapper : NSObject
+
++(id)selectedChildrenAttributeForElement:(AquaA11yWrapper *)wrapper
+{
+ Reference< XAccessibleSelection > xAccessibleSelection = [ wrapper accessibleSelection ];
+ if( xAccessibleSelection.is() )
+ {
+ NSMutableArray * children = [ [ NSMutableArray alloc ] init ];
+ try {
+ sal_Int64 n = xAccessibleSelection -> getSelectedAccessibleChildCount();
+
+ // Fix hanging when selecting a column or row in Calc
+ // When a Calc column is selected, the child count will be
+ // at least a million. Constructing that many C++ Calc objects
+ // takes several minutes even on a fast Silicon Mac so apply
+ // the maximum table cell limit here.
+ if ( n < 0 )
+ n = 0;
+ else if ( n > MAXIMUM_ACCESSIBLE_TABLE_CELLS )
+ n = MAXIMUM_ACCESSIBLE_TABLE_CELLS;
+
+ for ( sal_Int64 i=0 ; i < n ; ++i ) {
+ [ children addObject: [ AquaA11yFactory wrapperForAccessible: xAccessibleSelection -> getSelectedAccessibleChild( i ) ] ];
+ }
+
+ return children;
+
+ } catch ( Exception&)
+ {
+ }
+ }
+
+ return nil;
+}
+
+
++(void)addAttributeNamesTo:(NSMutableArray *)attributeNames
+{
+ [ attributeNames addObject: NSAccessibilitySelectedChildrenAttribute ];
+}
+
++(BOOL)isAttributeSettable:(NSString *)attribute forElement:(AquaA11yWrapper *)wrapper
+{
+ (void)wrapper;
+ if ( [ attribute isEqualToString: NSAccessibilitySelectedChildrenAttribute ] )
+ {
+ return YES;
+ }
+ else
+ {
+ return NO;
+ }
+}
+
++(void)setSelectedChildrenAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value
+{
+ Reference< XAccessibleSelection > xAccessibleSelection = [ wrapper accessibleSelection ];
+ try {
+ xAccessibleSelection -> clearAccessibleSelection();
+
+ unsigned c = [ value count ];
+ for ( unsigned i = 0 ; i < c ; ++i ) {
+ xAccessibleSelection -> selectAccessibleChild( [ [ value objectAtIndex: i ] accessibleContext ] -> getAccessibleIndexInParent() );
+ }
+ } catch ( Exception&) {
+ }
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ytablewrapper.h b/vcl/osx/a11ytablewrapper.h
new file mode 100644
index 0000000000..bc8ce4f39f
--- /dev/null
+++ b/vcl/osx/a11ytablewrapper.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <osx/a11ywrapper.h>
+
+#define MAXIMUM_ACCESSIBLE_TABLE_CELLS 1000
+
+@interface AquaA11yTableWrapper : AquaA11yWrapper
+{
+}
++ (id)childrenAttributeForElement:(AquaA11yTableWrapper*)wrapper;
++ (void)addAttributeNamesTo:(NSMutableArray*)attributeNames object:(AquaA11yWrapper*)pObject;
+
+- (id)rowsAttribute;
+- (id)columnsAttribute;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ytablewrapper.mm b/vcl/osx/a11ytablewrapper.mm
new file mode 100644
index 0000000000..1c155313c9
--- /dev/null
+++ b/vcl/osx/a11ytablewrapper.mm
@@ -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 <osx/a11yfactory.h>
+#include <sal/log.hxx>
+#include "a11ytablewrapper.h"
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::awt;
+using namespace ::com::sun::star::uno;
+
+@implementation AquaA11yTableWrapper : AquaA11yWrapper
+
++(id)childrenAttributeForElement:(AquaA11yTableWrapper *)wrapper
+{
+ XAccessibleTable * accessibleTable = [ wrapper accessibleTable ];
+ NSArray* pResult = nil;
+ if( accessibleTable )
+ {
+ NSMutableArray * cells = [ [ NSMutableArray alloc ] init ];
+ try
+ {
+ sal_Int32 nRows = accessibleTable->getAccessibleRowCount();
+ sal_Int32 nCols = accessibleTable->getAccessibleColumnCount();
+
+ // tdf#152648 Handle overflow when multiplying rows and columns
+ sal_Int64 nCells = static_cast<sal_Int64>(nRows) * static_cast<sal_Int64>(nCols);
+ if( nCells >= 0 && nCells < MAXIMUM_ACCESSIBLE_TABLE_CELLS )
+ {
+ // make all children visible to the hierarchy
+ for ( sal_Int32 rowCount = 0; rowCount < nRows; rowCount++ )
+ {
+ for ( sal_Int32 columnCount = 0; columnCount < nCols; columnCount++ )
+ {
+ Reference < XAccessible > rAccessibleCell = accessibleTable -> getAccessibleCellAt ( rowCount, columnCount );
+ if ( rAccessibleCell.is() )
+ {
+ id cell_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rAccessibleCell -> getAccessibleContext() ];
+ [ cells addObject: cell_wrapper ];
+ [ cell_wrapper release ];
+ }
+ }
+ }
+ }
+ else
+ {
+ XAccessibleComponent * accessibleComponent = [ wrapper accessibleComponent ];
+ // find out which cells are actually visible by determining the top-left-cell and the bottom-right-cell
+ Size tableSize = accessibleComponent -> getSize();
+ Point point;
+ point.X = 0;
+ point.Y = 0;
+ Reference < XAccessible > rAccessibleTopLeft = accessibleComponent -> getAccessibleAtPoint ( point );
+ point.X = tableSize.Width - 1;
+ point.Y = tableSize.Height - 1;
+ Reference < XAccessible > rAccessibleBottomRight = accessibleComponent -> getAccessibleAtPoint ( point );
+ if ( rAccessibleTopLeft.is() && rAccessibleBottomRight.is() )
+ {
+ sal_Int64 idxTopLeft = rAccessibleTopLeft -> getAccessibleContext() -> getAccessibleIndexInParent();
+ sal_Int64 idxBottomRight = rAccessibleBottomRight -> getAccessibleContext() -> getAccessibleIndexInParent();
+ sal_Int32 rowTopLeft = accessibleTable -> getAccessibleRow ( idxTopLeft );
+ sal_Int32 columnTopLeft = accessibleTable -> getAccessibleColumn ( idxTopLeft );
+ sal_Int32 rowBottomRight = accessibleTable -> getAccessibleRow ( idxBottomRight );
+ sal_Int32 columnBottomRight = accessibleTable -> getAccessibleColumn ( idxBottomRight );
+ SAL_WARN("vcl", "creating " << ((rowBottomRight - rowTopLeft) * (columnBottomRight - columnTopLeft)) << " cells");
+ // create an array containing the visible cells
+ for ( sal_Int32 rowCount = rowTopLeft; rowCount <= rowBottomRight; rowCount++ )
+ {
+ for ( sal_Int32 columnCount = columnTopLeft; columnCount <= columnBottomRight; columnCount++ )
+ {
+ Reference < XAccessible > rAccessibleCell = accessibleTable -> getAccessibleCellAt ( rowCount, columnCount );
+ if ( rAccessibleCell.is() )
+ {
+ id cell_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rAccessibleCell -> getAccessibleContext() ];
+ [ cells addObject: cell_wrapper ];
+ [ cell_wrapper release ];
+ }
+ }
+ }
+ }
+ }
+ pResult = NSAccessibilityUnignoredChildren( cells );
+ }
+ catch (const Exception &)
+ {
+ }
+ [cells autorelease];
+ }
+
+ return pResult;
+}
+
++(void)addAttributeNamesTo: (NSMutableArray *)attributeNames object: (AquaA11yWrapper*)pObject
+{
+ XAccessibleTable * accessibleTable = [ pObject accessibleTable ];
+ if( accessibleTable )
+ {
+ sal_Int32 nRows = accessibleTable->getAccessibleRowCount();
+ sal_Int32 nCols = accessibleTable->getAccessibleColumnCount();
+
+ // tdf#152648 Handle overflow when multiplying rows and columns
+ sal_Int64 nCells = static_cast<sal_Int64>(nRows) * static_cast<sal_Int64>(nCols);
+ if( nCells >= 0 && nCells < MAXIMUM_ACCESSIBLE_TABLE_CELLS )
+ {
+ [ attributeNames addObject: NSAccessibilityRowsAttribute ];
+ [ attributeNames addObject: NSAccessibilityColumnsAttribute ];
+ }
+ }
+}
+
+-(id)rowsAttribute
+{
+ NSArray* pResult = nil;
+
+ XAccessibleTable * accessibleTable = [ self accessibleTable ];
+ if( accessibleTable )
+ {
+ sal_Int32 nRows = accessibleTable->getAccessibleRowCount();
+ sal_Int32 nCols = accessibleTable->getAccessibleColumnCount();
+
+ // tdf#152648 Handle overflow when multiplying rows and columns
+ sal_Int64 nCells = static_cast<sal_Int64>(nRows) * static_cast<sal_Int64>(nCols);
+ if( nCells >= 0 && nCells < MAXIMUM_ACCESSIBLE_TABLE_CELLS )
+ {
+ NSMutableArray * cells = [ [ NSMutableArray alloc ] init ];
+ try
+ {
+ for( sal_Int32 n = 0; n < nRows; n++ )
+ {
+ Reference < XAccessible > rAccessibleCell = accessibleTable -> getAccessibleCellAt ( n, 0 );
+ if ( rAccessibleCell.is() )
+ {
+ id cell_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rAccessibleCell -> getAccessibleContext() ];
+ [ cells addObject: cell_wrapper ];
+ [ cell_wrapper release ];
+ }
+ }
+ pResult = NSAccessibilityUnignoredChildren( cells );
+ }
+ catch (const Exception &)
+ {
+ pResult = nil;
+ }
+ [ cells autorelease ];
+ }
+ }
+
+ return pResult;
+}
+
+-(id)columnsAttribute
+{
+ NSArray* pResult = nil;
+
+ XAccessibleTable * accessibleTable = [ self accessibleTable ];
+
+ if( accessibleTable )
+ {
+ sal_Int32 nRows = accessibleTable->getAccessibleRowCount();
+ sal_Int32 nCols = accessibleTable->getAccessibleColumnCount();
+
+ // tdf#152648 Handle overflow when multiplying rows and columns
+ sal_Int64 nCells = static_cast<sal_Int64>(nRows) * static_cast<sal_Int64>(nCols);
+ if( nCells >= 0 && nCells < MAXIMUM_ACCESSIBLE_TABLE_CELLS )
+ {
+ NSMutableArray * cells = [ [ NSMutableArray alloc ] init ];
+ try
+ {
+ // find out number of columns
+ for( sal_Int32 n = 0; n < nCols; n++ )
+ {
+ Reference < XAccessible > rAccessibleCell = accessibleTable -> getAccessibleCellAt ( 0, n );
+ if ( rAccessibleCell.is() )
+ {
+ id cell_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rAccessibleCell -> getAccessibleContext() ];
+ [ cells addObject: cell_wrapper ];
+ [ cell_wrapper release ];
+ }
+ }
+ pResult = NSAccessibilityUnignoredChildren( cells );
+ }
+ catch (const Exception &)
+ {
+ pResult = nil;
+ }
+ [ cells autorelease ];
+ }
+ }
+
+ return pResult;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ytextattributeswrapper.h b/vcl/osx/a11ytextattributeswrapper.h
new file mode 100644
index 0000000000..7df60a9383
--- /dev/null
+++ b/vcl/osx/a11ytextattributeswrapper.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yTextAttributesWrapper : NSObject
+{
+}
++(NSMutableAttributedString *)createAttributedStringForElement:(AquaA11yWrapper *)wrapper inOrigRange:(id)origRange;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ytextattributeswrapper.mm b/vcl/osx/a11ytextattributeswrapper.mm
new file mode 100644
index 0000000000..4404dc6463
--- /dev/null
+++ b/vcl/osx/a11ytextattributeswrapper.mm
@@ -0,0 +1,354 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osx/salinst.h>
+#include <quartz/utils.h>
+#include <quartz/salgdi.h>
+
+#include "a11ytextattributeswrapper.h"
+
+#include <com/sun/star/accessibility/AccessibleTextType.hpp>
+#include <com/sun/star/awt/FontUnderline.hpp>
+#include <com/sun/star/awt/FontWeight.hpp>
+#include <com/sun/star/awt/FontStrikeout.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+#include <com/sun/star/text/TextMarkupType.hpp>
+#include <com/sun/star/style/ParagraphAdjust.hpp>
+
+namespace css_awt = ::com::sun::star::awt;
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+// cannot use NSFontDescriptor as it has no notion of explicit NSUn{bold,italic}FontMask
+@interface AquaA11yFontDescriptor : NSObject
+{
+ NSString *_name;
+ NSFontTraitMask _traits;
+ CGFloat _size;
+}
+-(void)setName:(NSString*)name;
+-(void)setBold:(NSFontTraitMask)bold;
+-(void)setItalic:(NSFontTraitMask)italic;
+-(void)setSize:(CGFloat)size;
+-(NSFont*)font;
+@end
+
+@implementation AquaA11yFontDescriptor
+- (id)init
+{
+ if((self = [super init]))
+ {
+ _name = nil;
+ _traits = 0;
+ _size = 0.0;
+ }
+ return self;
+}
+
+- (id)initWithDescriptor:(AquaA11yFontDescriptor*)descriptor {
+ if((self = [super init]))
+ {
+ _name = [descriptor->_name retain];
+ _traits = descriptor->_traits;
+ _size = descriptor->_size;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [_name release];
+ [super dealloc];
+}
+
+-(void)setName:(NSString*)name {
+ if (_name != name) {
+ [name retain];
+ [_name release];
+ _name = name;
+ }
+}
+
+-(void)setBold:(NSFontTraitMask)bold {
+ _traits &= ~(NSBoldFontMask | NSUnboldFontMask);
+ _traits |= bold & (NSBoldFontMask | NSUnboldFontMask);
+};
+
+-(void)setItalic:(NSFontTraitMask)italic {
+ _traits &= ~(NSItalicFontMask | NSUnitalicFontMask);
+ _traits |= italic & (NSItalicFontMask | NSUnitalicFontMask);
+};
+
+-(void)setSize:(CGFloat)size { _size = size; }
+
+-(NSFont*)font {
+ return [[NSFontManager sharedFontManager] fontWithFamily:_name traits:_traits weight:0 size:_size];
+}
+@end
+
+@implementation AquaA11yTextAttributesWrapper : NSObject
+
++(int)convertUnderlineStyle:(PropertyValue)property {
+ int underlineStyle = NSUnderlineStyleNone;
+ sal_Int16 value = 0;
+ property.Value >>= value;
+ if ( value != ::css_awt::FontUnderline::NONE
+ && value != ::css_awt::FontUnderline::DONTKNOW) {
+ underlineStyle = NSUnderlineStyleSingle;
+ }
+ return underlineStyle;
+}
+
++(int)convertBoldStyle:(PropertyValue)property {
+ int boldStyle = NSUnboldFontMask;
+ float value = 0;
+ property.Value >>= value;
+ if ( value == ::css_awt::FontWeight::SEMIBOLD
+ || value == ::css_awt::FontWeight::BOLD
+ || value == ::css_awt::FontWeight::ULTRABOLD
+ || value == ::css_awt::FontWeight::BLACK ) {
+ boldStyle = NSBoldFontMask;
+ }
+ return boldStyle;
+}
+
++(int)convertItalicStyle:(PropertyValue)property {
+ int italicStyle = NSUnitalicFontMask;
+ ::css_awt::FontSlant value = property.Value.get< ::css_awt::FontSlant>();
+ if ( value == ::css_awt::FontSlant_ITALIC ) {
+ italicStyle = NSItalicFontMask;
+ }
+ return italicStyle;
+}
+
++(BOOL)isStrikethrough:(PropertyValue)property {
+ bool strikethrough = false;
+ sal_Int16 value = 0;
+ property.Value >>= value;
+ if ( value != ::css_awt::FontStrikeout::NONE
+ && value != ::css_awt::FontStrikeout::DONTKNOW ) {
+ strikethrough = true;
+ }
+ return strikethrough;
+}
+
++(BOOL)convertBoolean:(PropertyValue)property {
+ bool myBoolean = false;
+ bool value = false;
+ property.Value >>= value;
+ if ( value ) {
+ myBoolean = true;
+ }
+ return myBoolean;
+}
+
++(NSNumber *)convertShort:(PropertyValue)property {
+ sal_Int16 value = 0;
+ property.Value >>= value;
+ return [ NSNumber numberWithShort: value ];
+}
+
++(void)addColor:(Color)nColor forAttribute:(NSString *)attribute andRange:(NSRange)range toString:(NSMutableAttributedString *)string {
+ if( nColor == COL_TRANSPARENT )
+ return;
+ const RGBAColor aRGBAColor( nColor);
+ CGColorRef aColorRef = CGColorCreate ( CGColorSpaceCreateWithName ( kCGColorSpaceGenericRGB ), aRGBAColor.AsArray() );
+ [ string addAttribute: attribute value: reinterpret_cast<id>(aColorRef) range: range ];
+ CGColorRelease( aColorRef );
+}
+
++(void)addFont:(NSFont *)font toString:(NSMutableAttributedString *)string forRange:(NSRange)range {
+ if ( font != nil ) {
+ NSDictionary * fontDictionary = [ NSDictionary dictionaryWithObjectsAndKeys:
+ [ font fontName ], NSAccessibilityFontNameKey,
+ [ font familyName ], NSAccessibilityFontFamilyKey,
+ [ font displayName ], NSAccessibilityVisibleNameKey,
+ [ NSNumber numberWithFloat: [ font pointSize ] ], NSAccessibilityFontSizeKey,
+ nil
+ ];
+ [ string addAttribute: NSAccessibilityFontTextAttribute
+ value: fontDictionary
+ range: range
+ ];
+ }
+}
+
++(void)applyAttributesFrom:(Sequence < PropertyValue > const &)attributes toString:(NSMutableAttributedString *)string forRange:(NSRange)range fontDescriptor:(AquaA11yFontDescriptor*)fontDescriptor {
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ // vars
+ sal_Int32 underlineColor = 0;
+ bool underlineHasColor = false;
+ // add attributes to string
+ for ( const PropertyValue& property : attributes ) {
+ // TODO: NSAccessibilityAttachmentTextAttribute, NSAccessibilityLinkTextAttribute
+ // NSAccessibilityStrikethroughColorTextAttribute is unsupported by UNP-API
+ if ( property.Value.hasValue() ) {
+ if ( property.Name == "CharUnderline" ) {
+ int style = [ AquaA11yTextAttributesWrapper convertUnderlineStyle: property ];
+ if ( style != NSUnderlineStyleNone ) {
+ [ string addAttribute: NSAccessibilityUnderlineTextAttribute value: [ NSNumber numberWithInt: style ] range: range ];
+ }
+ } else if ( property.Name == "CharFontName" ) {
+ OUString fontname;
+ property.Value >>= fontname;
+ [fontDescriptor setName:CreateNSString(fontname)];
+ } else if ( property.Name == "CharWeight" ) {
+ [fontDescriptor setBold:[AquaA11yTextAttributesWrapper convertBoldStyle:property]];
+ } else if ( property.Name == "CharPosture" ) {
+ [fontDescriptor setItalic:[AquaA11yTextAttributesWrapper convertItalicStyle:property]];
+ } else if ( property.Name == "CharHeight" ) {
+ float size;
+ property.Value >>= size;
+ [fontDescriptor setSize:size];
+ } else if ( property.Name == "CharStrikeout" ) {
+ if ( [ AquaA11yTextAttributesWrapper isStrikethrough: property ] ) {
+ [ string addAttribute: NSAccessibilityStrikethroughTextAttribute value: [ NSNumber numberWithBool: YES ] range: range ];
+ }
+ } else if ( property.Name == "CharShadowed" ) {
+ if ( [ AquaA11yTextAttributesWrapper convertBoolean: property ] ) {
+ [ string addAttribute: NSAccessibilityShadowTextAttribute value: [ NSNumber numberWithBool: YES ] range: range ];
+ }
+ } else if ( property.Name == "CharUnderlineColor" ) {
+ property.Value >>= underlineColor;
+ } else if ( property.Name == "CharUnderlineHasColor" ) {
+ underlineHasColor = [ AquaA11yTextAttributesWrapper convertBoolean: property ];
+ } else if ( property.Name == "CharColor" ) {
+ [ AquaA11yTextAttributesWrapper addColor: Color(ColorTransparency, property.Value.get<sal_Int32>()) forAttribute: NSAccessibilityForegroundColorTextAttribute andRange: range toString: string ];
+ } else if ( property.Name == "CharBackColor" ) {
+ [ AquaA11yTextAttributesWrapper addColor: Color(ColorTransparency, property.Value.get<sal_Int32>()) forAttribute: NSAccessibilityBackgroundColorTextAttribute andRange: range toString: string ];
+ } else if ( property.Name == "CharEscapement" ) {
+ // values < zero mean subscript
+ // values > zero mean superscript
+ // this is true for both NSAccessibility-API and UNO-API
+ NSNumber * number = [ AquaA11yTextAttributesWrapper convertShort: property ];
+ if ( [ number shortValue ] != 0 ) {
+ [ string addAttribute: NSAccessibilitySuperscriptTextAttribute value: number range: range ];
+ }
+ } else if ( property.Name == "ParaAdjust" ) {
+ sal_Int32 alignment;
+ property.Value >>= alignment;
+ NSNumber *textAlignment = nil;
+ switch(static_cast<css::style::ParagraphAdjust>(alignment)) {
+ case css::style::ParagraphAdjust_RIGHT:
+ textAlignment = [NSNumber numberWithInteger:NSTextAlignmentRight];
+ break;
+ case css::style::ParagraphAdjust_CENTER:
+ textAlignment = [NSNumber numberWithInteger:NSTextAlignmentCenter];
+ break;
+ case css::style::ParagraphAdjust_BLOCK:
+ textAlignment = [NSNumber numberWithInteger:NSTextAlignmentJustified];
+ break;
+ case css::style::ParagraphAdjust_LEFT:
+ default:
+ textAlignment = [NSNumber numberWithInteger:NSTextAlignmentLeft];
+ break;
+ }
+ NSDictionary *paragraphStyle = [NSDictionary dictionaryWithObjectsAndKeys:textAlignment, @"AXTextAlignment", textAlignment, @"AXVisualTextAlignment", nil];
+ [string addAttribute:@"AXParagraphStyle" value:paragraphStyle range:range];
+ }
+ }
+ }
+ // add underline information
+ if ( underlineHasColor ) {
+ [ AquaA11yTextAttributesWrapper addColor: Color(ColorTransparency, underlineColor) forAttribute: NSAccessibilityUnderlineColorTextAttribute andRange: range toString: string ];
+ }
+ // add font information
+ NSFont * font = [fontDescriptor font];
+ [AquaA11yTextAttributesWrapper addFont:font toString:string forRange:range];
+ [ pool release ];
+}
+
++(void)addMarkup:(XAccessibleTextMarkup*)markup withType:(sal_Int32)type toString:(NSMutableAttributedString*)string inRange:(NSRange)range {
+ const sal_Int32 markupCount = markup->getTextMarkupCount(type);
+ for (sal_Int32 markupIndex = 0; markupIndex < markupCount; ++markupIndex) {
+ TextSegment markupSegment = markup->getTextMarkup(markupIndex, type);
+ NSRange markupRange = NSMakeRange(markupSegment.SegmentStart, markupSegment.SegmentEnd - markupSegment.SegmentStart);
+ markupRange = NSIntersectionRange(range, markupRange);
+ if (markupRange.length > 0) {
+ markupRange.location -= range.location;
+ switch(type) {
+ case css::text::TextMarkupType::SPELLCHECK: {
+ [string addAttribute:NSAccessibilityMisspelledTextAttribute value:[NSNumber numberWithBool:YES] range:markupRange];
+ [string addAttribute:@"AXMarkedMisspelled" value:[NSNumber numberWithBool:YES] range:markupRange];
+ break;
+ }
+ }
+ }
+ }
+}
+
++(void)addMarkup:(XAccessibleTextMarkup*)markup toString:(NSMutableAttributedString*)string inRange:(NSRange)range {
+ [AquaA11yTextAttributesWrapper addMarkup:markup withType:css::text::TextMarkupType::SPELLCHECK toString:string inRange:range];
+}
+
+// tdf#148453 Fix crash by turning off optimization for Objective-C selector
+// The default attributes sequence sometimes crashes when it is released but
+// only when compiler optimization is enabled, so disable optimization for the
+// +[AquaA11yTextAttributesWrapper createAttributedStringForElement] selector.
++(NSMutableAttributedString *)createAttributedStringForElement:(AquaA11yWrapper *)wrapper inOrigRange:(id)origRange __attribute__((optnone)) {
+ static const Sequence < OUString > emptySequence;
+ // vars
+ NSMutableAttributedString * string = nil;
+ int loc = [ origRange rangeValue ].location;
+ int len = [ origRange rangeValue ].length;
+ int endIndex = loc + len;
+ int currentIndex = loc;
+ try {
+ NSString * myString = CreateNSString ( [ wrapper accessibleText ] -> getText() ); // TODO: dirty fix for i87817
+ string = [ [ NSMutableAttributedString alloc ] initWithString: CreateNSString ( [ wrapper accessibleText ] -> getTextRange ( loc, loc + len ) ) ];
+ [ string autorelease ];
+ if ( [ wrapper accessibleTextAttributes ] && [myString characterAtIndex:0] != 57361) { // TODO: dirty fix for i87817
+ [ string beginEditing ];
+ // add default attributes for whole string
+ Sequence < PropertyValue > defaultAttributes = [ wrapper accessibleTextAttributes ] -> getDefaultAttributes ( emptySequence );
+ AquaA11yFontDescriptor *defaultFontDescriptor = [[AquaA11yFontDescriptor alloc] init];
+ [ AquaA11yTextAttributesWrapper applyAttributesFrom: defaultAttributes toString: string forRange: NSMakeRange ( 0, len ) fontDescriptor: defaultFontDescriptor ];
+ // add attributes for attribute run(s)
+ while ( currentIndex < endIndex ) {
+ TextSegment textSegment = [ wrapper accessibleText ] -> getTextAtIndex ( currentIndex, AccessibleTextType::ATTRIBUTE_RUN );
+ int endOfRange = endIndex > textSegment.SegmentEnd ? textSegment.SegmentEnd : endIndex;
+ NSRange rangeForAttributeRun = NSMakeRange ( currentIndex - loc , endOfRange - currentIndex );
+ // add run attributes
+ Sequence < PropertyValue > attributes = [ wrapper accessibleTextAttributes ] -> getRunAttributes ( currentIndex, emptySequence );
+ AquaA11yFontDescriptor *fontDescriptor = [[AquaA11yFontDescriptor alloc] initWithDescriptor:defaultFontDescriptor];
+ [ AquaA11yTextAttributesWrapper applyAttributesFrom: attributes toString: string forRange: rangeForAttributeRun fontDescriptor: fontDescriptor ];
+ [fontDescriptor release];
+ currentIndex = textSegment.SegmentEnd;
+ }
+ [defaultFontDescriptor release];
+ if ([wrapper accessibleTextMarkup])
+ [AquaA11yTextAttributesWrapper addMarkup:[wrapper accessibleTextMarkup] toString:string inRange:[origRange rangeValue]];
+ [ string endEditing ];
+ }
+ } catch ( IllegalArgumentException & ) {
+ // empty
+ } catch ( IndexOutOfBoundsException & ) {
+ // empty
+ } catch ( RuntimeException& ) {
+ // at least don't crash
+ }
+ return string;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ytextwrapper.h b/vcl/osx/a11ytextwrapper.h
new file mode 100644
index 0000000000..3449813231
--- /dev/null
+++ b/vcl/osx/a11ytextwrapper.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <osx/osxvcltypes.h>
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yTextWrapper : NSObject
+{
+}
++ (id)valueAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)numberOfCharactersAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)selectedTextAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)selectedTextRangeAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)visibleCharacterRangeAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)sharedTextUIElementsAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)sharedCharacterRangeAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)stringForRangeAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)range;
++ (id)attributedStringForRangeAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)range;
++ (id)rangeForIndexAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)index;
++ (id)rangeForPositionAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)point;
++ (id)boundsForRangeAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)range;
++ (id)styleRangeForIndexAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)index;
++ (id)rTFForRangeAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)range;
++ (id)lineForIndexAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)index;
++ (id)rangeForLineAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)line;
++ (void)addAttributeNamesTo:(NSMutableArray*)attributeNames;
++ (void)addParameterizedAttributeNamesTo:(NSMutableArray*)attributeNames;
++ (NSArray*)specialAttributeNames;
++ (NSArray*)specialParameterizedAttributeNames;
++ (BOOL)isAttributeSettable:(NSString*)attribute forElement:(AquaA11yWrapper*)wrapper;
++ (void)setVisibleCharacterRangeAttributeForElement:(AquaA11yWrapper*)wrapper to:(id)value;
++ (void)setSelectedTextRangeAttributeForElement:(AquaA11yWrapper*)wrapper to:(id)value;
++ (void)setSelectedTextAttributeForElement:(AquaA11yWrapper*)wrapper to:(id)value;
++ (void)setValueAttributeForElement:(AquaA11yWrapper*)wrapper to:(id)value;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ytextwrapper.mm b/vcl/osx/a11ytextwrapper.mm
new file mode 100644
index 0000000000..cfd4ae7c1c
--- /dev/null
+++ b/vcl/osx/a11ytextwrapper.mm
@@ -0,0 +1,296 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osx/salinst.h>
+#include <quartz/utils.h>
+#include "a11ytextwrapper.h"
+#include "a11ytextattributeswrapper.h"
+#include "a11yutil.h"
+
+#include <com/sun/star/accessibility/AccessibleTextType.hpp>
+#include <com/sun/star/awt/Rectangle.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::awt;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+// Wrapper for XAccessibleText, XAccessibleEditableText and XAccessibleMultiLineText
+
+@implementation AquaA11yTextWrapper : NSObject
+
++(id)valueAttributeForElement:(AquaA11yWrapper *)wrapper {
+ return CreateNSString ( [ wrapper accessibleText ] -> getText() );
+}
+
++(void)setValueAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value
+{
+ // TODO
+ (void)wrapper;
+ (void)value;
+}
+
++(id)numberOfCharactersAttributeForElement:(AquaA11yWrapper *)wrapper {
+ return [ NSNumber numberWithLong: [ wrapper accessibleText ] -> getCharacterCount() ];
+}
+
++(id)selectedTextAttributeForElement:(AquaA11yWrapper *)wrapper {
+ return CreateNSString ( [ wrapper accessibleText ] -> getSelectedText() );
+}
+
++(void)setSelectedTextAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value {
+ if ( [ wrapper accessibleEditableText ] ) {
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ OUString newText = GetOUString ( static_cast<NSString *>(value) );
+ NSRange selectedTextRange = [ [ AquaA11yTextWrapper selectedTextRangeAttributeForElement: wrapper ] rangeValue ];
+ try {
+ [ wrapper accessibleEditableText ] -> replaceText ( selectedTextRange.location, selectedTextRange.location + selectedTextRange.length, newText );
+ } catch ( const Exception & ) {
+ // empty
+ }
+ [ pool release ];
+ }
+}
+
++(id)selectedTextRangeAttributeForElement:(AquaA11yWrapper *)wrapper {
+ sal_Int32 start = [ wrapper accessibleText ] -> getSelectionStart();
+ sal_Int32 end = [ wrapper accessibleText ] -> getSelectionEnd();
+ if ( start != end ) {
+ return [ NSValue valueWithRange: NSMakeRange ( start, end - start ) ]; // true selection
+ } else {
+ sal_Int32 caretPos = [ wrapper accessibleText ] -> getCaretPosition();
+ if ( caretPos < 0 || caretPos > [ wrapper accessibleText ] -> getCharacterCount() ) {
+ return nil;
+ }
+ return [ NSValue valueWithRange: NSMakeRange ( caretPos, 0 ) ]; // insertion point
+ }
+}
+
++(void)setSelectedTextRangeAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value {
+ NSRange range = [ value rangeValue ];
+ try {
+ [ wrapper accessibleText ] -> setSelection ( range.location, range.location + range.length );
+ } catch ( const Exception & ) {
+ // empty
+ }
+}
+
++(id)visibleCharacterRangeAttributeForElement:(AquaA11yWrapper *)wrapper {
+ // the OOo a11y API returns only the visible portion...
+ return [ NSValue valueWithRange: NSMakeRange ( 0, [ wrapper accessibleText ] -> getCharacterCount() ) ];
+}
+
++(void)setVisibleCharacterRangeAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value
+{
+ // do nothing
+ (void)wrapper;
+ (void)value;
+}
+
++(id)sharedTextUIElementsAttributeForElement:(AquaA11yWrapper *)wrapper
+{
+ return [NSArray arrayWithObject:wrapper];
+}
+
++(id)sharedCharacterRangeAttributeForElement:(AquaA11yWrapper *)wrapper
+{
+ return [ NSValue valueWithRange: NSMakeRange ( 0, [wrapper accessibleText]->getCharacterCount() ) ];
+}
+
++(void)addAttributeNamesTo:(NSMutableArray *)attributeNames {
+ [ attributeNames addObjectsFromArray: [ AquaA11yTextWrapper specialAttributeNames ] ];
+}
+
++(NSArray *)specialAttributeNames {
+ return [ NSArray arrayWithObjects:
+ NSAccessibilityValueAttribute,
+ NSAccessibilityNumberOfCharactersAttribute,
+ NSAccessibilitySelectedTextAttribute,
+ NSAccessibilitySelectedTextRangeAttribute,
+ NSAccessibilityVisibleCharacterRangeAttribute,
+ NSAccessibilitySharedTextUIElementsAttribute,
+ NSAccessibilitySharedCharacterRangeAttribute,
+ nil ];
+}
+
++(void)addParameterizedAttributeNamesTo:(NSMutableArray *)attributeNames {
+ [ attributeNames addObjectsFromArray: [ AquaA11yTextWrapper specialParameterizedAttributeNames ] ];
+}
+
++(NSArray *)specialParameterizedAttributeNames {
+ return [ NSArray arrayWithObjects:
+ NSAccessibilityStringForRangeParameterizedAttribute,
+ NSAccessibilityAttributedStringForRangeParameterizedAttribute,
+ NSAccessibilityRangeForIndexParameterizedAttribute,
+ NSAccessibilityRangeForPositionParameterizedAttribute,
+ NSAccessibilityBoundsForRangeParameterizedAttribute,
+ NSAccessibilityStyleRangeForIndexParameterizedAttribute,
+ NSAccessibilityRTFForRangeParameterizedAttribute,
+ NSAccessibilityLineForIndexParameterizedAttribute,
+ NSAccessibilityRangeForLineParameterizedAttribute,
+ nil ];
+}
+
++(id)lineForIndexAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)index {
+ NSNumber * lineNumber = nil;
+ try {
+ sal_Int32 line = [ wrapper accessibleMultiLineText ] -> getLineNumberAtIndex ( static_cast<sal_Int32>([ index intValue ]) );
+ lineNumber = [ NSNumber numberWithInt: line ];
+ } catch ( IndexOutOfBoundsException & ) {
+ // empty
+ }
+ return lineNumber;
+}
+
++(id)rangeForLineAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)line {
+ NSValue * range = nil;
+ try {
+ TextSegment textSegment = [ wrapper accessibleMultiLineText ] -> getTextAtLineNumber ( [ line intValue ] );
+ range = [ NSValue valueWithRange: NSMakeRange ( textSegment.SegmentStart, textSegment.SegmentEnd - textSegment.SegmentStart ) ];
+ } catch ( IndexOutOfBoundsException & ) {
+ // empty
+ }
+ return range;
+}
+
++(id)stringForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range {
+ int loc = [ range rangeValue ].location;
+ int len = [ range rangeValue ].length;
+ NSMutableString * textRange = [ [ NSMutableString alloc ] init ];
+ try {
+ [ textRange appendString: CreateNSString ( [ wrapper accessibleText ] -> getTextRange ( loc, loc + len ) ) ];
+ } catch ( IndexOutOfBoundsException & ) {
+ // empty
+ }
+ return textRange;
+}
+
++(id)attributedStringForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range {
+ return [ AquaA11yTextAttributesWrapper createAttributedStringForElement: wrapper inOrigRange: range ];
+}
+
++(id)rangeForIndexAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)index {
+ NSValue * range = nil;
+ try {
+ TextSegment textSegment = [ wrapper accessibleText ] -> getTextBeforeIndex ( [ index intValue ], AccessibleTextType::GLYPH );
+ range = [ NSValue valueWithRange: NSMakeRange ( textSegment.SegmentStart, textSegment.SegmentEnd - textSegment.SegmentStart ) ];
+ } catch ( IndexOutOfBoundsException & ) {
+ // empty
+ } catch ( IllegalArgumentException & ) {
+ // empty
+ }
+ return range;
+}
+
++(id)rangeForPositionAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)point {
+ NSValue * value = nil;
+ css::awt::Point aPoint( [ AquaA11yUtil nsPointToVclPoint: point ]);
+ const css::awt::Point screenPos = [ wrapper accessibleComponent ] -> getLocationOnScreen();
+ aPoint.X -= screenPos.X;
+ aPoint.Y -= screenPos.Y;
+ sal_Int32 index = [ wrapper accessibleText ] -> getIndexAtPoint( aPoint );
+ if ( index > -1 ) {
+ value = [ AquaA11yTextWrapper rangeForIndexAttributeForElement: wrapper forParameter: [ NSNumber numberWithLong: index ] ];
+ }
+ return value;
+}
+
++(id)boundsForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range {
+ NSValue * rect = nil;
+ try {
+ // TODO: this is ugly!!!
+ // the UNP-API can only return the bounds for a single character, not for a range
+ int loc = [ range rangeValue ].location;
+ int len = [ range rangeValue ].length;
+ int minx = 0x7fffffff, miny = 0x7fffffff, maxx = 0, maxy = 0;
+ for ( int i = 0; i < len; i++ ) {
+ Rectangle vclRect = [ wrapper accessibleText ] -> getCharacterBounds ( loc + i );
+ if ( vclRect.X < minx ) {
+ minx = vclRect.X;
+ }
+ if ( vclRect.Y < miny ) {
+ miny = vclRect.Y;
+ }
+ if ( vclRect.Width + vclRect.X > maxx ) {
+ maxx = vclRect.Width + vclRect.X;
+ }
+ if ( vclRect.Height + vclRect.Y > maxy ) {
+ maxy = vclRect.Height + vclRect.Y;
+ }
+ }
+ if ( [ wrapper accessibleComponent ] ) {
+ // get location on screen (must be added since get CharacterBounds returns values relative to parent)
+ css::awt::Point screenPos = [ wrapper accessibleComponent ] -> getLocationOnScreen();
+ css::awt::Point pos ( minx + screenPos.X, miny + screenPos.Y );
+ css::awt::Point size ( maxx - minx, maxy - miny );
+ NSValue * nsPos = [ AquaA11yUtil vclPointToNSPoint: pos ];
+ rect = [ NSValue valueWithRect: NSMakeRect ( [ nsPos pointValue ].x, [ nsPos pointValue ].y - size.Y, size.X, size.Y ) ];
+ //printf("Range: %s --- Rect: %s\n", [ NSStringFromRange ( [ range rangeValue ] ) UTF8String ], [ NSStringFromRect ( [ rect rectValue ] ) UTF8String ]);
+ }
+ } catch ( IndexOutOfBoundsException & ) {
+ // empty
+ }
+ return rect;
+}
+
++(id)styleRangeForIndexAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)index {
+ NSValue * range = nil;
+ try {
+ TextSegment textSegment = [ wrapper accessibleText ] -> getTextAtIndex ( [ index intValue ], AccessibleTextType::ATTRIBUTE_RUN );
+ range = [ NSValue valueWithRange: NSMakeRange ( textSegment.SegmentStart, textSegment.SegmentEnd - textSegment.SegmentStart ) ];
+ } catch ( IndexOutOfBoundsException & ) {
+ // empty
+ } catch ( IllegalArgumentException & ) {
+ // empty
+ }
+ return range;
+}
+
++(id)rTFForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range {
+ NSData * rtfData = nil;
+ NSAttributedString * attrString = static_cast<NSAttributedString *>([ AquaA11yTextWrapper attributedStringForRangeAttributeForElement: wrapper forParameter: range ]);
+ if ( attrString != nil ) {
+ @try {
+ rtfData = [ attrString RTFFromRange: [ range rangeValue ] documentAttributes: @{NSDocumentTypeDocumentAttribute : NSRTFTextDocumentType} ];
+ } @catch ( NSException *) {
+ // empty
+ }
+ }
+ return rtfData;
+}
+
++(BOOL)isAttributeSettable:(NSString *)attribute forElement:(AquaA11yWrapper *)wrapper {
+ bool isSettable = false;
+ if ( [ attribute isEqualToString: NSAccessibilityValueAttribute ]
+ || [ attribute isEqualToString: NSAccessibilitySelectedTextAttribute ]
+ || [ attribute isEqualToString: NSAccessibilitySelectedTextRangeAttribute ]
+ || [ attribute isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute ] ) {
+ if ( ! [ [ wrapper accessibilityAttributeValue: NSAccessibilityRoleAttribute ] isEqualToString: NSAccessibilityStaticTextRole ] ) {
+ isSettable = true;
+ }
+ }
+ return isSettable;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yutil.h b/vcl/osx/a11yutil.h
new file mode 100644
index 0000000000..26b1e9faed
--- /dev/null
+++ b/vcl/osx/a11yutil.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <com/sun/star/awt/Point.hpp>
+
+@interface AquaA11yUtil : NSObject
+{
+}
++ (NSValue*)vclPointToNSPoint:(css::awt::Point)vclPoint;
++ (css::awt::Point)nsPointToVclPoint:(NSValue*)nsPoint;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yutil.mm b/vcl/osx/a11yutil.mm
new file mode 100644
index 0000000000..87edfc73cf
--- /dev/null
+++ b/vcl/osx/a11yutil.mm
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osx/osxvcltypes.h>
+#include <tools/long.hxx>
+
+#include "a11yutil.h"
+
+using namespace ::com::sun::star::awt;
+
+@implementation AquaA11yUtil : NSObject
+
+// TODO: should be merged with AquaSalFrame::VCLToCocoa... to a general helper method
++(NSValue *)vclPointToNSPoint:(Point)vclPoint {
+ // VCL coordinates are in upper-left-notation, Cocoa likes it the Cartesian way (lower-left)
+ NSRect screenRect = [ [ NSScreen mainScreen ] frame ];
+ NSPoint nsPoint = NSMakePoint ( static_cast<float>(vclPoint.X), static_cast<float>( screenRect.size.height - vclPoint.Y ) );
+ return [ NSValue valueWithPoint: nsPoint ];
+}
+
+// TODO: should be merged with AquaSalFrame::VCLToCocoa... to a general helper method
++(Point)nsPointToVclPoint:(NSValue *)nsPoint {
+ // VCL coordinates are in upper-left-notation, Cocoa likes it the Cartesian way (lower-left)
+ NSRect screenRect = [ [ NSScreen mainScreen ] frame ];
+ return Point ( static_cast<tools::Long>([ nsPoint pointValue ].x), static_cast<tools::Long>(screenRect.size.height - [ nsPoint pointValue ].y) );
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yvaluewrapper.h b/vcl/osx/a11yvaluewrapper.h
new file mode 100644
index 0000000000..232bd8ad6d
--- /dev/null
+++ b/vcl/osx/a11yvaluewrapper.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <osx/salinst.h>
+#include <osx/osxvcltypes.h>
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yValueWrapper : NSObject
+{
+}
++ (id)valueAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)minValueAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)maxValueAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (void)addAttributeNamesTo:(NSMutableArray*)attributeNames;
++ (BOOL)isAttributeSettable:(NSString*)attribute forElement:(AquaA11yWrapper*)wrapper;
++ (void)setValueAttributeForElement:(AquaA11yWrapper*)wrapper to:(id)value;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yvaluewrapper.mm b/vcl/osx/a11yvaluewrapper.mm
new file mode 100644
index 0000000000..0cf3786fbe
--- /dev/null
+++ b/vcl/osx/a11yvaluewrapper.mm
@@ -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 "a11yvaluewrapper.h"
+#include "a11ywrapperstatictext.h"
+
+using namespace ::com::sun::star::uno;
+
+// Wrapper for XAccessibleValue
+// Remember: A UNO-Value is a single numeric value. Regarding the Mac A11y-API, a value can be anything!
+
+@implementation AquaA11yValueWrapper : NSObject
+
++(id)valueAttributeForElement:(AquaA11yWrapper *)wrapper {
+ // TODO: Detect Type from Any
+ if ( [ wrapper accessibleValue ] ) {
+ sal_Int32 value = 0;
+ [ wrapper accessibleValue ] -> getCurrentValue() >>= value;
+ return [ NSNumber numberWithLong: value ];
+ }
+ return [ NSNumber numberWithLong: 0 ];
+}
+
++(id)minValueAttributeForElement:(AquaA11yWrapper *)wrapper {
+ // TODO: Detect Type from Any
+ if ( [ wrapper accessibleValue ] ) {
+ sal_Int32 value = 0;
+ [ wrapper accessibleValue ] -> getMinimumValue() >>= value;
+ return [ NSNumber numberWithLong: value ];
+ }
+ return [ NSNumber numberWithLong: 0 ];
+}
+
++(id)maxValueAttributeForElement:(AquaA11yWrapper *)wrapper {
+ // TODO: Detect Type from Any
+ if ( [ wrapper accessibleValue ] ) {
+ sal_Int32 value = 0;
+ [ wrapper accessibleValue ] -> getMaximumValue() >>= value;
+ return [ NSNumber numberWithLong: value ];
+ }
+ return [ NSNumber numberWithLong: 0 ];
+}
+
++(void)setValueAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value {
+ // TODO: Detect Type from NSNumber
+ if ( [ value isKindOfClass: [ NSNumber class ] ]
+ && [ wrapper accessibleValue ] ) {
+ NSNumber * number = static_cast<NSNumber *>(value);
+ Any numberAny ( [ number longValue ] );
+ [ wrapper accessibleValue ] -> setCurrentValue ( numberAny );
+ }
+}
+
++(void)addAttributeNamesTo:(NSMutableArray *)attributeNames {
+ [ attributeNames addObject: NSAccessibilityValueAttribute ];
+}
+
++(BOOL)isAttributeSettable:(NSString *)attribute forElement:(AquaA11yWrapper *)wrapper {
+ bool isSettable = false;
+ if ( [ wrapper accessibleValue ]
+ && [ attribute isEqualToString: NSAccessibilityValueAttribute ]
+ && ! [ wrapper isKindOfClass: [ AquaA11yWrapperStaticText class ] ] ) {
+ isSettable = true;
+ }
+ return isSettable;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapper.mm b/vcl/osx/a11ywrapper.mm
new file mode 100644
index 0000000000..df1d3690df
--- /dev/null
+++ b/vcl/osx/a11ywrapper.mm
@@ -0,0 +1,1616 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osx/salinst.h>
+#include <osx/saldata.hxx>
+
+#include <osx/a11ywrapper.h>
+#include <osx/a11ylistener.hxx>
+#include <osx/a11yfactory.h>
+#include <osx/a11yfocustracker.hxx>
+
+#include <quartz/salgdi.h>
+#include <quartz/utils.h>
+
+#include "a11yfocuslistener.hxx"
+#include "a11yactionwrapper.h"
+#include "a11ycomponentwrapper.h"
+#include "a11yselectionwrapper.h"
+#include "a11ytablewrapper.h"
+#include "a11ytextwrapper.h"
+#include "a11yvaluewrapper.h"
+#include "a11yrolehelper.h"
+
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/accessibility/XAccessibleRelationSet.hpp>
+#include <com/sun/star/accessibility/AccessibleRelationType.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::awt;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+@interface SalFrameWindow : NSWindow
+{
+}
+-(Reference<XAccessibleContext>)accessibleContext;
+@end
+
+static bool isPopupMenuOpen = false;
+
+static std::ostream &operator<<(std::ostream &s, NSObject *obj) {
+ return s << [[obj description] UTF8String];
+}
+
+@implementation AquaA11yWrapper
+
+#pragma mark -
+#pragma mark Init and dealloc
+
+-(id)init {
+ return [ super init ];
+}
+
+-(id)initWithAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext {
+ self = [ super init ];
+ if ( self ) {
+ [ self setDefaults: rxAccessibleContext ];
+ }
+ return self;
+}
+
+-(void) setDefaults: (Reference < XAccessibleContext >) rxAccessibleContext {
+ mActsAsRadioGroup = NO;
+ maReferenceWrapper.rAccessibleContext = rxAccessibleContext;
+ mIsTableCell = NO;
+ // Querying all supported interfaces
+ try {
+ // XAccessibleComponent
+ maReferenceWrapper.rAccessibleComponent.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleExtendedComponent
+ maReferenceWrapper.rAccessibleExtendedComponent.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleSelection
+ maReferenceWrapper.rAccessibleSelection.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleTable
+ maReferenceWrapper.rAccessibleTable.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleText
+ maReferenceWrapper.rAccessibleText.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleEditableText
+ maReferenceWrapper.rAccessibleEditableText.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleValue
+ maReferenceWrapper.rAccessibleValue.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleAction
+ maReferenceWrapper.rAccessibleAction.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleTextAttributes
+ maReferenceWrapper.rAccessibleTextAttributes.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleMultiLineText
+ maReferenceWrapper.rAccessibleMultiLineText.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleTextMarkup
+ maReferenceWrapper.rAccessibleTextMarkup.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleEventBroadcaster
+ #if 0
+ /* #i102033# NSAccessibility does not seemt to know an equivalent for transient children.
+ That means we need to cache this, else e.g. tree list boxes are not accessible (moreover
+ it crashes by notifying dead objects - which would seemt o be another bug)
+
+ FIXME:
+ Unfortunately this can increase memory consumption drastically until the non transient parent
+ is destroyed and finally all the transients are released.
+ */
+ if ( ! ( rxAccessibleContext -> getAccessibleStateSet() & AccessibleStateType::TRANSIENT ) )
+ #endif
+ {
+ Reference< XAccessibleEventBroadcaster > xBroadcaster(rxAccessibleContext, UNO_QUERY);
+ if( xBroadcaster.is() ) {
+ /*
+ * We intentionally do not hold a reference to the event listener in the wrapper object,
+ * but let the listener control the life cycle of the wrapper instead ..
+ */
+ xBroadcaster->addAccessibleEventListener( new AquaA11yEventListener( self, rxAccessibleContext -> getAccessibleRole() ) );
+ }
+ }
+ // TABLE_CELL
+ if ( rxAccessibleContext -> getAccessibleRole() == AccessibleRole::TABLE_CELL ) {
+ mIsTableCell = YES;
+ }
+ } catch ( const Exception ) {
+ }
+}
+
+#pragma mark -
+#pragma mark Utility Section
+
+// generates selectors for attribute name AXAttributeNameHere
+// (getter without parameter) attributeNameHereAttribute
+// (getter with parameter) attributeNameHereAttributeForParameter:
+// (setter) setAttributeNameHereAttributeForElement:to:
+-(SEL)selectorForAttribute:(NSString *)attribute asGetter:(BOOL)asGetter withGetterParameter:(BOOL)withGetterParameter {
+ SEL selector = static_cast<SEL>(nil);
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ @try {
+ // step 1: create method name from attribute name
+ NSMutableString * methodName = [ NSMutableString string ];
+ if ( ! asGetter ) {
+ [ methodName appendString: @"set" ];
+ }
+ NSRange const aRange = { 2, 1 };
+ NSString * firstChar = [ attribute substringWithRange: aRange ]; // drop leading "AX" and get first char
+ if ( asGetter ) {
+ [ methodName appendString: [ firstChar lowercaseString ] ];
+ } else {
+ [ methodName appendString: firstChar ];
+ }
+ [ methodName appendString: [ attribute substringFromIndex: 3 ] ]; // append rest of attribute name
+ // append rest of method name
+ [ methodName appendString: @"Attribute" ];
+ if ( ! asGetter ) {
+ [ methodName appendString: @"ForElement:to:" ];
+ } else if ( asGetter && withGetterParameter ) {
+ [ methodName appendString: @"ForParameter:" ];
+ }
+ // step 2: create selector
+ selector = NSSelectorFromString ( methodName );
+ } @catch ( id ) {
+ selector = static_cast<SEL>(nil);
+ }
+ [ pool release ];
+ return selector;
+}
+
+-(Reference < XAccessible >)getFirstRadioButtonInGroup {
+ Reference < XAccessibleRelationSet > rxAccessibleRelationSet = [ self accessibleContext ] -> getAccessibleRelationSet();
+ if( rxAccessibleRelationSet.is() )
+ {
+ AccessibleRelation relationMemberOf = rxAccessibleRelationSet -> getRelationByType ( AccessibleRelationType::MEMBER_OF );
+ if ( relationMemberOf.RelationType == AccessibleRelationType::MEMBER_OF && relationMemberOf.TargetSet.hasElements() )
+ return Reference < XAccessible > ( relationMemberOf.TargetSet[0], UNO_QUERY );
+ }
+ return Reference < XAccessible > ();
+}
+
+-(BOOL)isFirstRadioButtonInGroup {
+ Reference < XAccessible > rFirstMateAccessible = [ self getFirstRadioButtonInGroup ];
+ if ( rFirstMateAccessible.is() && rFirstMateAccessible -> getAccessibleContext().get() == [ self accessibleContext ] ) {
+ return YES;
+ }
+ return NO;
+}
+
+#pragma mark -
+#pragma mark Attribute Value Getters
+// ( called via Reflection by accessibilityAttributeValue )
+
+/*
+ Radiobutton grouping is done differently in NSAccessibility and the UNO-API. In UNO related radio buttons share an entry in their
+ RelationSet. In NSAccessibility the relationship is expressed through the hierarchy. An AXRadioGroup contains two or more AXRadioButton
+ objects. Since this group is not available in the UNO hierarchy, an extra wrapper is used for it. This wrapper shares almost all
+ attributes with the first radio button of the group, except for the role, subrole, role description, parent and children attributes.
+ So in this five methods there is a special treatment for radio buttons and groups.
+*/
+
+-(id)roleAttribute {
+ if ( mActsAsRadioGroup ) {
+ return NSAccessibilityRadioGroupRole;
+ }
+ else {
+ return [ AquaA11yRoleHelper getNativeRoleFrom: [ self accessibleContext ] ];
+ }
+}
+
+-(id)subroleAttribute {
+ if ( mActsAsRadioGroup ) {
+ return @"";
+ } else {
+ NSString * subRole = [ AquaA11yRoleHelper getNativeSubroleFrom: [ self accessibleContext ] -> getAccessibleRole() ];
+ if ( ! [ subRole isEqualToString: @"" ] ) {
+ return subRole;
+ } else {
+ [ subRole release ];
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ //TODO: 10.10 accessibilityAttributeValue:
+ return [ super accessibilityAttributeValue: NSAccessibilitySubroleAttribute ];
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+ }
+}
+
+-(id)titleAttribute {
+ return CreateNSString ( [ self accessibleContext ] -> getAccessibleName() );
+}
+
+-(id)descriptionAttribute {
+ if ( [ self accessibleContext ] -> getAccessibleRole() == AccessibleRole::COMBO_BOX ) {
+ return [ self titleAttribute ];
+ } else if ( [ self accessibleExtendedComponent ] ) {
+ return [ AquaA11yComponentWrapper descriptionAttributeForElement: self ];
+ } else {
+ return CreateNSString ( [ self accessibleContext ] -> getAccessibleDescription() );
+ }
+}
+
+-(id)enabledAttribute {
+ sal_Int64 nStateSet = [ self accessibleContext ] -> getAccessibleStateSet();
+ if ( nStateSet ) {
+ return [ NSNumber numberWithBool: ( (nStateSet & AccessibleStateType::ENABLED) != 0 ) ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)focusedAttribute {
+ if ( [ self accessibleContext ] -> getAccessibleRole() == AccessibleRole::COMBO_BOX ) {
+ id isFocused = nil;
+ Reference < XAccessible > rxParent = [ self accessibleContext ] -> getAccessibleParent();
+ if ( rxParent.is() ) {
+ Reference < XAccessibleContext > rxContext = rxParent -> getAccessibleContext();
+ if ( rxContext.is() ) {
+ sal_Int64 nStateSet = rxContext -> getAccessibleStateSet();
+ if ( nStateSet ) {
+ isFocused = [ NSNumber numberWithBool: ( ( nStateSet & AccessibleStateType::FOCUSED ) != 0 ) ];
+ }
+ }
+ }
+ return isFocused;
+ } else if ( [ self accessibleContext ] -> getAccessibleStateSet() ) {
+ sal_Int64 nStateSet = [ self accessibleContext ] -> getAccessibleStateSet();
+ return [ NSNumber numberWithBool: ( ( nStateSet & AccessibleStateType::FOCUSED ) != 0 ) ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)parentAttribute {
+ if ( [ self accessibleContext ] -> getAccessibleRole() == AccessibleRole::RADIO_BUTTON && ! mActsAsRadioGroup ) {
+ Reference < XAccessible > rxAccessible = [ self getFirstRadioButtonInGroup ];
+ if ( rxAccessible.is() && rxAccessible -> getAccessibleContext().is() ) {
+ Reference < XAccessibleContext > rxAccessibleContext = rxAccessible -> getAccessibleContext();
+ id parent_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rxAccessibleContext createIfNotExists: YES asRadioGroup: YES ];
+ [ parent_wrapper autorelease ];
+ return NSAccessibilityUnignoredAncestor( parent_wrapper );
+ }
+ return nil;
+ }
+ try {
+ Reference< XAccessible > xParent( [ self accessibleContext ] -> getAccessibleParent() );
+ if ( xParent.is() ) {
+ Reference< XAccessibleContext > xContext( xParent -> getAccessibleContext() );
+ if ( xContext.is() ) {
+ id parent_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: xContext ];
+ [ parent_wrapper autorelease ];
+ return NSAccessibilityUnignoredAncestor( parent_wrapper );
+ }
+ }
+ } catch (const Exception&) {
+ }
+
+ OSL_ASSERT( false );
+ return nil;
+}
+
+-(id)childrenAttribute {
+ if ( mActsAsRadioGroup ) {
+ NSMutableArray * children = [ [ NSMutableArray alloc ] init ];
+ Reference < XAccessibleRelationSet > rxAccessibleRelationSet = [ self accessibleContext ] -> getAccessibleRelationSet();
+ AccessibleRelation const relationMemberOf = rxAccessibleRelationSet -> getRelationByType ( AccessibleRelationType::MEMBER_OF );
+ if ( relationMemberOf.RelationType == AccessibleRelationType::MEMBER_OF && relationMemberOf.TargetSet.hasElements() ) {
+ for ( const auto& i : relationMemberOf.TargetSet ) {
+ Reference < XAccessible > rMateAccessible( i, UNO_QUERY );
+ if ( rMateAccessible.is() ) {
+ Reference< XAccessibleContext > rMateAccessibleContext( rMateAccessible -> getAccessibleContext() );
+ if ( rMateAccessibleContext.is() ) {
+ id wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rMateAccessibleContext ];
+ [ children addObject: wrapper ];
+ [ wrapper release ];
+ }
+ }
+ }
+ }
+ return children;
+ } else if ( [ self accessibleTable ] )
+ {
+ AquaA11yTableWrapper* pTable = [self isKindOfClass: [AquaA11yTableWrapper class]] ? static_cast<AquaA11yTableWrapper*>(self) : nil;
+ return [ AquaA11yTableWrapper childrenAttributeForElement: pTable ];
+ } else {
+ try {
+ NSMutableArray * children = [ [ NSMutableArray alloc ] init ];
+ Reference< XAccessibleContext > xContext( [ self accessibleContext ] );
+
+ try {
+ sal_Int64 cnt = xContext -> getAccessibleChildCount();
+ for ( sal_Int64 i = 0; i < cnt; i++ ) {
+ Reference< XAccessible > xChild( xContext -> getAccessibleChild( i ) );
+ if( xChild.is() ) {
+ Reference< XAccessibleContext > xChildContext( xChild -> getAccessibleContext() );
+ // the menubar is already accessible (including Apple- and Application-Menu) through NSApplication => omit it here
+ if ( xChildContext.is() && AccessibleRole::MENU_BAR != xChildContext -> getAccessibleRole() ) {
+ id wrapper = [ AquaA11yFactory wrapperForAccessibleContext: xChildContext ];
+ [ children addObject: wrapper ];
+ [ wrapper release ];
+ }
+ }
+ }
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "Accessible object has invalid index in parent");
+ }
+
+ // if not already acting as RadioGroup now is the time to replace RadioButtons with RadioGroups and remove RadioButtons
+ if ( ! mActsAsRadioGroup ) {
+ NSEnumerator * enumerator = [ children objectEnumerator ];
+ AquaA11yWrapper * element;
+ while ( ( element = static_cast<AquaA11yWrapper *>([ enumerator nextObject ]) ) ) {
+ if ( [ element accessibleContext ] -> getAccessibleRole() == AccessibleRole::RADIO_BUTTON ) {
+ if ( [ element isFirstRadioButtonInGroup ] ) {
+ id wrapper = [ AquaA11yFactory wrapperForAccessibleContext: [ element accessibleContext ] createIfNotExists: YES asRadioGroup: YES ];
+ [ children replaceObjectAtIndex: [ children indexOfObjectIdenticalTo: element ] withObject: wrapper ];
+ }
+ [ children removeObject: element ];
+ }
+ }
+ }
+
+ [ children autorelease ];
+ return NSAccessibilityUnignoredChildren( children );
+ } catch (const Exception &) {
+ // TODO: Log
+ return nil;
+ }
+ }
+}
+
+-(id)windowAttribute {
+ // go upstairs until reaching the broken connection
+ AquaA11yWrapper * aWrapper = self;
+ int loops = 0;
+ while ( [ aWrapper accessibleContext ] -> getAccessibleParent().is() ) {
+ AquaA11yWrapper *aTentativeParentWrapper = [ AquaA11yFactory wrapperForAccessibleContext: [ aWrapper accessibleContext ] -> getAccessibleParent() -> getAccessibleContext() ];
+ // Quick-and-dirty fix for infinite loop after fixing crash in
+ // fdo#47275
+ if ( aTentativeParentWrapper == aWrapper )
+ break;
+ // Even dirtier fix for infinite loop in fdo#55156
+ if ( loops++ == 100 )
+ break;
+ aWrapper = aTentativeParentWrapper;
+ [ aWrapper autorelease ];
+ }
+ // get associated NSWindow
+ NSWindow* theWindow = [ aWrapper windowForParent ];
+ return theWindow;
+}
+
+-(id)topLevelUIElementAttribute {
+ return [ self windowAttribute ];
+}
+
+-(id)sizeAttribute {
+ if ( [ self accessibleComponent ] ) {
+ return [ AquaA11yComponentWrapper sizeAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)positionAttribute {
+ if ( [ self accessibleComponent ] ) {
+ return [ AquaA11yComponentWrapper positionAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)helpAttribute {
+ return CreateNSString ( [ self accessibleContext ] -> getAccessibleDescription() );
+}
+
+-(id)roleDescriptionAttribute {
+ if ( mActsAsRadioGroup ) {
+ return [ AquaA11yRoleHelper getRoleDescriptionFrom: NSAccessibilityRadioGroupRole with: @"" ];
+ } else if( [ self accessibleContext ] -> getAccessibleRole() == AccessibleRole::RADIO_BUTTON ) {
+ // FIXME: VO should read this because of hierarchy, this is just a workaround
+ // get parent and its children
+ AquaA11yWrapper * parent = [ self parentAttribute ];
+ NSArray * children = [ parent childrenAttribute ];
+ // find index of self
+ int index = 1;
+ NSEnumerator * enumerator = [ children objectEnumerator ];
+ AquaA11yWrapper * child = nil;
+ while ( ( child = [ enumerator nextObject ] ) ) {
+ if ( self == child ) {
+ break;
+ }
+ index++;
+ }
+ // build string
+ NSNumber * nIndex = [ NSNumber numberWithInt: index ];
+ NSNumber * nGroupsize = [ NSNumber numberWithInt: [ children count ] ];
+ NSMutableString * value = [ [ NSMutableString alloc ] init ];
+ [ value appendString: @"radio button " ];
+ [ value appendString: [ nIndex stringValue ] ];
+ [ value appendString: @" of " ];
+ [ value appendString: [ nGroupsize stringValue ] ];
+ // clean up and return string
+ [ nIndex release ];
+ [ nGroupsize release ];
+ [ children release ];
+ return value;
+ } else {
+ return [ AquaA11yRoleHelper getRoleDescriptionFrom:
+ [ AquaA11yRoleHelper getNativeRoleFrom: [ self accessibleContext ] ]
+ with: [ AquaA11yRoleHelper getNativeSubroleFrom: [ self accessibleContext ] -> getAccessibleRole() ] ];
+ }
+}
+
+-(id)valueAttribute {
+ if ( [ [ self roleAttribute ] isEqualToString: NSAccessibilityMenuItemRole ] ) {
+ return nil;
+ } else if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper valueAttributeForElement: self ];
+ } else if ( [ self accessibleValue ] ) {
+ return [ AquaA11yValueWrapper valueAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)minValueAttribute {
+ if ( [ self accessibleValue ] ) {
+ return [ AquaA11yValueWrapper minValueAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)maxValueAttribute {
+ if ( [ self accessibleValue ] ) {
+ return [ AquaA11yValueWrapper maxValueAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)contentsAttribute {
+ return [ self childrenAttribute ];
+}
+
+-(id)selectedChildrenAttribute {
+ return [ AquaA11ySelectionWrapper selectedChildrenAttributeForElement: self ];
+}
+
+-(id)numberOfCharactersAttribute {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper numberOfCharactersAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)selectedTextAttribute {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper selectedTextAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)selectedTextRangeAttribute {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper selectedTextRangeAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)visibleCharacterRangeAttribute {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper visibleCharacterRangeAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)tabsAttribute {
+ return self; // TODO ???
+}
+
+-(id)sharedTextUIElementsAttribute {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper sharedTextUIElementsAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)sharedCharacterRangeAttribute {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper sharedCharacterRangeAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)expandedAttribute {
+ sal_Int64 nStateSet = [ self accessibleContext ] -> getAccessibleStateSet();
+ return [ NSNumber numberWithBool: ( ( nStateSet & AccessibleStateType::EXPANDED ) != 0 ) ];
+}
+
+-(id)selectedAttribute {
+ sal_Int64 nStateSet = [ self accessibleContext ] -> getAccessibleStateSet();
+ return [ NSNumber numberWithBool: ( ( nStateSet & AccessibleStateType::SELECTED ) != 0 ) ];
+}
+
+-(id)stringForRangeAttributeForParameter:(id)range {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper stringForRangeAttributeForElement: self forParameter: range ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)attributedStringForRangeAttributeForParameter:(id)range {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper attributedStringForRangeAttributeForElement: self forParameter: range ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)rangeForIndexAttributeForParameter:(id)index {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper rangeForIndexAttributeForElement: self forParameter: index ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)rangeForPositionAttributeForParameter:(id)point {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper rangeForPositionAttributeForElement: self forParameter: point ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)boundsForRangeAttributeForParameter:(id)range {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper boundsForRangeAttributeForElement: self forParameter: range ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)styleRangeForIndexAttributeForParameter:(id)index {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper styleRangeForIndexAttributeForElement: self forParameter: index ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)rTFForRangeAttributeForParameter:(id)range {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper rTFForRangeAttributeForElement: self forParameter: range ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)orientationAttribute {
+ NSString * orientation = nil;
+ sal_Int64 stateSet = [ self accessibleContext ] -> getAccessibleStateSet();
+ if ( stateSet & AccessibleStateType::HORIZONTAL ) {
+ orientation = NSAccessibilityHorizontalOrientationValue;
+ } else if ( stateSet & AccessibleStateType::VERTICAL ) {
+ orientation = NSAccessibilityVerticalOrientationValue;
+ }
+ return orientation;
+}
+
+-(id)titleUIElementAttribute {
+ if ( [ self accessibleContext ] -> getAccessibleRelationSet().is() ) {
+ NSString * title = [ self titleAttribute ];
+ id titleElement = nil;
+ if ( [ title length ] == 0 ) {
+ AccessibleRelation relationLabeledBy = [ self accessibleContext ] -> getAccessibleRelationSet() -> getRelationByType ( AccessibleRelationType::LABELED_BY );
+ if ( relationLabeledBy.RelationType == AccessibleRelationType::LABELED_BY && relationLabeledBy.TargetSet.hasElements() ) {
+ Reference < XAccessible > rxAccessible ( relationLabeledBy.TargetSet[0], UNO_QUERY );
+ titleElement = [ AquaA11yFactory wrapperForAccessibleContext: rxAccessible -> getAccessibleContext() ];
+ }
+ }
+ if ( title ) {
+ [ title release ];
+ }
+ return titleElement;
+ } else {
+ return nil;
+ }
+}
+
+-(id)servesAsTitleForUIElementsAttribute {
+ if ( [ self accessibleContext ] -> getAccessibleRelationSet().is() ) {
+ id titleForElement = nil;
+ AccessibleRelation relationLabelFor = [ self accessibleContext ] -> getAccessibleRelationSet() -> getRelationByType ( AccessibleRelationType::LABEL_FOR );
+ if ( relationLabelFor.RelationType == AccessibleRelationType::LABEL_FOR && relationLabelFor.TargetSet.hasElements() ) {
+ Reference < XAccessible > rxAccessible ( relationLabelFor.TargetSet[0], UNO_QUERY );
+ titleForElement = [ AquaA11yFactory wrapperForAccessibleContext: rxAccessible -> getAccessibleContext() ];
+ }
+ return titleForElement;
+ } else {
+ return nil;
+ }
+}
+
+-(id)lineForIndexAttributeForParameter:(id)index {
+ if ( [ self accessibleMultiLineText ] ) {
+ return [ AquaA11yTextWrapper lineForIndexAttributeForElement: self forParameter: index ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)rangeForLineAttributeForParameter:(id)line {
+ if ( [ self accessibleMultiLineText ] ) {
+ return [ AquaA11yTextWrapper rangeForLineAttributeForElement: self forParameter: line ];
+ } else {
+ return nil;
+ }
+}
+
+#pragma mark -
+#pragma mark Accessibility Protocol
+
+-(id)accessibilityAttributeValue:(NSString *)attribute {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityAttributeValue:" << attribute << "]");
+ // #i90575# guard NSAccessibility protocol against unwanted access
+ if ( isPopupMenuOpen ) {
+ return nil;
+ }
+
+ id value = nil;
+ // if we are no longer in the wrapper repository, we have been disposed
+ AquaA11yWrapper * theWrapper = [ AquaA11yFactory wrapperForAccessibleContext: [ self accessibleContext ] createIfNotExists: NO ];
+ if ( theWrapper || mIsTableCell ) {
+ try {
+ SEL methodSelector = [ self selectorForAttribute: attribute asGetter: YES withGetterParameter: NO ];
+ if ( [ self respondsToSelector: methodSelector ] ) {
+ value = [ self performSelector: methodSelector ];
+ }
+ } catch ( const DisposedException & ) {
+ mIsTableCell = NO; // just to be sure
+ [ AquaA11yFactory removeFromWrapperRepositoryFor: [ self accessibleContext ] ];
+ return nil;
+ } catch ( const Exception & ) {
+ // empty
+ }
+ }
+ if ( theWrapper ) {
+ [ theWrapper release ]; // the above called method calls retain on the returned Wrapper
+ }
+ return value;
+}
+
+-(BOOL)accessibilityIsIgnored {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityIsIgnored]");
+ // #i90575# guard NSAccessibility protocol against unwanted access
+ if ( isPopupMenuOpen ) {
+ return NO;
+ }
+ bool ignored = false;
+ try {
+ sal_Int16 nRole = [ self accessibleContext ] -> getAccessibleRole();
+ switch ( nRole ) {
+ //case AccessibleRole::PANEL:
+ case AccessibleRole::FRAME:
+ case AccessibleRole::ROOT_PANE:
+ case AccessibleRole::SEPARATOR:
+ case AccessibleRole::FILLER:
+ case AccessibleRole::DIALOG:
+ ignored = true;
+ break;
+ default:
+ ignored = ! ( [ self accessibleContext ] -> getAccessibleStateSet() & AccessibleStateType::VISIBLE );
+ break;
+ }
+ } catch ( DisposedException& ) {
+ ignored = true;
+ } catch ( RuntimeException& ) {
+ ignored = true;
+ }
+
+ return ignored; // TODO: to be completed
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityAttributeNames]");
+ // #i90575# guard NSAccessibility protocol against unwanted access
+ if ( isPopupMenuOpen ) {
+ return nil;
+ }
+ NSString * nativeSubrole = nil;
+ NSString * title = nil;
+ NSMutableArray * attributeNames = nil;
+ sal_Int64 nAccessibleChildren = 0;
+ try {
+ // Default Attributes
+ attributeNames = [ NSMutableArray arrayWithObjects:
+ NSAccessibilityRoleAttribute,
+ NSAccessibilityDescriptionAttribute,
+ NSAccessibilityParentAttribute,
+ NSAccessibilityWindowAttribute,
+ NSAccessibilityHelpAttribute,
+ NSAccessibilityTopLevelUIElementAttribute,
+ NSAccessibilityRoleDescriptionAttribute,
+ nil ];
+ nativeSubrole = static_cast<NSString *>([ AquaA11yRoleHelper getNativeSubroleFrom: [ self accessibleContext ] -> getAccessibleRole() ]);
+ title = static_cast<NSString *>([ self titleAttribute ]);
+ Reference < XAccessibleRelationSet > rxRelationSet = [ self accessibleContext ] -> getAccessibleRelationSet();
+ // Special Attributes depending on attribute values
+ if ( nativeSubrole && ! [ nativeSubrole isEqualToString: @"" ] ) {
+ [ attributeNames addObject: NSAccessibilitySubroleAttribute ];
+ }
+ try
+ {
+ nAccessibleChildren = [ self accessibleContext ] -> getAccessibleChildCount();
+ if ( nAccessibleChildren > 0 ) {
+ [ attributeNames addObject: NSAccessibilityChildrenAttribute ];
+ }
+ }
+ catch( DisposedException& ) {}
+ catch( RuntimeException& ) {}
+
+ if ( title && ! [ title isEqualToString: @"" ] ) {
+ [ attributeNames addObject: NSAccessibilityTitleAttribute ];
+ }
+ if ( [ title length ] == 0 && rxRelationSet.is() && rxRelationSet -> containsRelation ( AccessibleRelationType::LABELED_BY ) ) {
+ [ attributeNames addObject: NSAccessibilityTitleUIElementAttribute ];
+ }
+ if ( rxRelationSet.is() && rxRelationSet -> containsRelation ( AccessibleRelationType::LABEL_FOR ) ) {
+ [ attributeNames addObject: NSAccessibilityServesAsTitleForUIElementsAttribute ];
+ }
+ // Special Attributes depending on interface
+ if( [self accessibleContext ] -> getAccessibleRole() == AccessibleRole::TABLE )
+ [AquaA11yTableWrapper addAttributeNamesTo: attributeNames object: self];
+
+ if ( [ self accessibleText ] ) {
+ [ AquaA11yTextWrapper addAttributeNamesTo: attributeNames ];
+ }
+ if ( [ self accessibleComponent ] ) {
+ [ AquaA11yComponentWrapper addAttributeNamesTo: attributeNames ];
+ }
+ if ( [ self accessibleSelection ] ) {
+ [ AquaA11ySelectionWrapper addAttributeNamesTo: attributeNames ];
+ }
+ if ( [ self accessibleValue ] ) {
+ [ AquaA11yValueWrapper addAttributeNamesTo: attributeNames ];
+ }
+ if ( nativeSubrole ) {
+ [ nativeSubrole release ];
+ }
+ if ( title ) {
+ [ title release ];
+ }
+ // Related: tdf#153374 Don't release autoreleased attributeNames
+ return attributeNames;
+ } catch ( DisposedException & ) { // Object is no longer available
+ if ( nativeSubrole ) {
+ [ nativeSubrole release ];
+ }
+ if ( title ) {
+ [ title release ];
+ }
+ // Related: tdf#153374 Don't release autoreleased attributeNames
+ // Also, return an autoreleased empty array instead of a retained array.
+ [ AquaA11yFactory removeFromWrapperRepositoryFor: [ self accessibleContext ] ];
+ return [ NSArray array ];
+ }
+}
+
+-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityAttributeIsSettable:" << attribute << "]");
+ bool isSettable = false;
+ if ( [ self accessibleText ] ) {
+ isSettable = [ AquaA11yTextWrapper isAttributeSettable: attribute forElement: self ];
+ }
+ if ( ! isSettable && [ self accessibleComponent ] ) {
+ isSettable = [ AquaA11yComponentWrapper isAttributeSettable: attribute forElement: self ];
+ }
+ if ( ! isSettable && [ self accessibleSelection ] ) {
+ isSettable = [ AquaA11ySelectionWrapper isAttributeSettable: attribute forElement: self ];
+ }
+ if ( ! isSettable && [ self accessibleValue ] ) {
+ isSettable = [ AquaA11yValueWrapper isAttributeSettable: attribute forElement: self ];
+ }
+ return isSettable; // TODO: to be completed
+}
+
+-(NSArray *)accessibilityParameterizedAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityParameterizedAttributeNames]");
+ NSMutableArray * attributeNames = [ NSMutableArray array ];
+ // Special Attributes depending on interface
+ if ( [ self accessibleText ] ) {
+ [ AquaA11yTextWrapper addParameterizedAttributeNamesTo: attributeNames ];
+ }
+ return attributeNames; // TODO: to be completed
+}
+
+-(id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityAttributeValue:" << attribute << " forParameter:" << (static_cast<NSObject*>(parameter)) << "]");
+ SEL methodSelector = [ self selectorForAttribute: attribute asGetter: YES withGetterParameter: YES ];
+ if ( [ self respondsToSelector: methodSelector ] ) {
+ try {
+ return [ self performSelector: methodSelector withObject: parameter ];
+ } catch ( const DisposedException & ) {
+ mIsTableCell = NO; // just to be sure
+ [ AquaA11yFactory removeFromWrapperRepositoryFor: [ self accessibleContext ] ];
+ return nil;
+ } catch ( const Exception & ) {
+ // empty
+ }
+ }
+ return nil; // TODO: to be completed
+}
+
+-(BOOL)accessibilitySetOverrideValue:(id)value forAttribute:(NSString *)attribute
+{
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilitySetOverrideValue:" << (static_cast<NSObject*>(value)) << " forAttribute:" << attribute << "]");
+ return NO; // TODO
+}
+
+-(void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilitySetValue:" << (static_cast<NSObject*>(value)) << " forAttribute:" << attribute << "]");
+ SEL methodSelector = [ self selectorForAttribute: attribute asGetter: NO withGetterParameter: NO ];
+ if ( [ AquaA11yComponentWrapper respondsToSelector: methodSelector ] ) {
+ [ AquaA11yComponentWrapper performSelector: methodSelector withObject: self withObject: value ];
+ }
+ if ( [ AquaA11yTextWrapper respondsToSelector: methodSelector ] ) {
+ [ AquaA11yTextWrapper performSelector: methodSelector withObject: self withObject: value ];
+ }
+ if ( [ AquaA11ySelectionWrapper respondsToSelector: methodSelector ] ) {
+ [ AquaA11ySelectionWrapper performSelector: methodSelector withObject: self withObject: value ];
+ }
+ if ( [ AquaA11yValueWrapper respondsToSelector: methodSelector ] ) {
+ [ AquaA11yValueWrapper performSelector: methodSelector withObject: self withObject: value ];
+ }
+}
+
+-(id)accessibilityFocusedUIElement {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityFocusedUIElement]");
+ // #i90575# guard NSAccessibility protocol against unwanted access
+ if ( isPopupMenuOpen ) {
+ return nil;
+ }
+
+ // as this seems to be the first API call on a newly created SalFrameView object,
+ // make sure self gets registered in the repository ..
+ [ self accessibleContext ];
+
+ AquaA11yWrapper * focusedUIElement = AquaA11yFocusListener::get()->getFocusedUIElement();
+// AquaA11yWrapper * ancestor = focusedUIElement;
+
+ // Make sure the focused object is a descendant of self
+// do {
+// if( self == ancestor )
+ return focusedUIElement;
+
+// ancestor = [ ancestor accessibilityAttributeValue: NSAccessibilityParentAttribute ];
+// } while( nil != ancestor );
+
+ return self;
+}
+
+-(NSString *)accessibilityActionDescription:(NSString *)action {
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityActionDescription:" << action << "]");
+ return NSAccessibilityActionDescription(action);
+}
+
+-(AquaA11yWrapper *)actionResponder {
+ AquaA11yWrapper * wrapper = nil;
+ // get some information
+ NSString * role = static_cast<NSString *>([ self accessibilityAttributeValue: NSAccessibilityRoleAttribute ]);
+ id enabledAttr = [ self enabledAttribute ];
+ bool enabled = [ enabledAttr boolValue ];
+ NSView * parent = static_cast<NSView *>([ self accessibilityAttributeValue: NSAccessibilityParentAttribute ]);
+ AquaA11yWrapper * parentAsWrapper = nil;
+ if ( [ parent isKindOfClass: [ AquaA11yWrapper class ] ] ) {
+ parentAsWrapper = static_cast<AquaA11yWrapper *>(parent);
+ }
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ //TODO: 10.10 accessibilityAttributeValue:
+ NSString * parentRole = static_cast<NSString *>([ parent accessibilityAttributeValue: NSAccessibilityRoleAttribute ]);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ // if we are a textarea inside a combobox, then the combobox is the action responder
+ if ( enabled
+ && [ role isEqualToString: NSAccessibilityTextAreaRole ]
+ && [ parentRole isEqualToString: NSAccessibilityComboBoxRole ]
+ && parentAsWrapper ) {
+ wrapper = parentAsWrapper;
+ } else if ( enabled && [ self accessibleAction ] ) {
+ wrapper = self ;
+ }
+ [ parentRole release ];
+ [ enabledAttr release ];
+ [ role release ];
+ return wrapper;
+}
+
+-(void)accessibilityPerformAction:(NSString *)action {
+ [ self performAction: action ];
+}
+
+-(BOOL)performAction:(NSString *)action {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityPerformAction:" << action << "]");
+ AquaA11yWrapper * actionResponder = [ self actionResponder ];
+ if ( actionResponder ) {
+ [ AquaA11yActionWrapper doAction: action ofElement: actionResponder ];
+ return YES;
+ }
+ return NO;
+}
+
+-(NSArray *)accessibilityActionNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityActionNames]");
+ NSArray * actionNames = nil;
+ AquaA11yWrapper * actionResponder = [ self actionResponder ];
+ if ( actionResponder ) {
+ actionNames = [ AquaA11yActionWrapper actionNamesForElement: actionResponder ];
+ } else {
+ actionNames = [ [ NSArray alloc ] init ];
+ }
+ return actionNames;
+}
+
+#pragma mark -
+#pragma mark Hit Test
+
+-(BOOL)isViewElement:(NSObject *)viewElement hitByPoint:(NSPoint)point {
+ bool hit = false;
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ //TODO: 10.10 accessibilityAttributeValue:
+ NSValue * position = [ viewElement accessibilityAttributeValue: NSAccessibilityPositionAttribute ];
+ NSValue * size = [ viewElement accessibilityAttributeValue: NSAccessibilitySizeAttribute ];
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ if ( position && size ) {
+ float minX = [ position pointValue ].x;
+ float minY = [ position pointValue ].y;
+ float maxX = minX + [ size sizeValue ].width;
+ float maxY = minY + [ size sizeValue ].height;
+ if ( minX < point.x && maxX > point.x && minY < point.y && maxY > point.y ) {
+ hit = true;
+ }
+ }
+ [ pool release ];
+ return hit;
+}
+
+static Reference < XAccessibleContext > hitTestRunner ( css::awt::Point point,
+ Reference < XAccessibleContext > const & rxAccessibleContext ) {
+ Reference < XAccessibleContext > hitChild;
+ Reference < XAccessibleContext > emptyReference;
+ try {
+ Reference < XAccessibleComponent > rxAccessibleComponent ( rxAccessibleContext, UNO_QUERY );
+ if ( rxAccessibleComponent.is() ) {
+ css::awt::Point location = rxAccessibleComponent -> getLocationOnScreen();
+ css::awt::Point hitPoint ( point.X - location.X , point.Y - location.Y);
+ Reference < XAccessible > rxAccessible = rxAccessibleComponent -> getAccessibleAtPoint ( hitPoint );
+ if ( rxAccessible.is() && rxAccessible -> getAccessibleContext().is() &&
+ rxAccessible -> getAccessibleContext() -> getAccessibleChildCount() == 0 ) {
+ hitChild = rxAccessible -> getAccessibleContext();
+ }
+ }
+
+ // iterate the hierarchy looking doing recursive hit testing.
+ // apparently necessary as a special treatment for e.g. comboboxes
+ if ( !hitChild.is() ) {
+ bool bSafeToIterate = true;
+ sal_Int64 nCount = rxAccessibleContext -> getAccessibleChildCount();
+
+ if (nCount < 0 || nCount > SAL_MAX_UINT16 /* slow enough for anyone */)
+ bSafeToIterate = false;
+ else { // manages descendants is an horror from the a11y standards guys.
+ sal_Int64 nStateSet = rxAccessibleContext -> getAccessibleStateSet();
+ if ( nStateSet & AccessibleStateType::MANAGES_DESCENDANTS )
+ bSafeToIterate = false;
+ }
+
+ if( bSafeToIterate ) {
+ try {
+ for ( sal_Int64 i = 0; i < rxAccessibleContext -> getAccessibleChildCount(); i++ ) {
+ Reference < XAccessible > rxAccessibleChild = rxAccessibleContext -> getAccessibleChild ( i );
+ if ( rxAccessibleChild.is() && rxAccessibleChild -> getAccessibleContext().is() && rxAccessibleChild -> getAccessibleContext() -> getAccessibleRole() != AccessibleRole::LIST ) {
+ Reference < XAccessibleContext > myHitChild = hitTestRunner ( point, rxAccessibleChild -> getAccessibleContext() );
+ if ( myHitChild.is() ) {
+ hitChild = myHitChild;
+ break;
+ }
+ }
+ }
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "Accessible object has invalid index in parent");
+ }
+ }
+ }
+ } catch ( RuntimeException ) {
+ return emptyReference;
+ }
+ return hitChild;
+}
+
+-(id)accessibilityHitTest:(NSPoint)point {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityHitTest:" << point << "]");
+ static id wrapper = nil;
+ if ( nil != wrapper ) {
+ [ wrapper release ];
+ wrapper = nil;
+ }
+ Reference < XAccessibleContext > hitChild;
+ NSRect screenRect = [ [ NSScreen mainScreen ] frame ];
+ css::awt::Point hitPoint ( static_cast<sal_Int32>(point.x) , static_cast<sal_Int32>(screenRect.size.height - point.y) );
+ // check child windows first
+ NSWindow * window = static_cast<NSWindow *>([ self accessibilityAttributeValue: NSAccessibilityWindowAttribute ]);
+ NSArray * childWindows = [ window childWindows ];
+ if ( [ childWindows count ] > 0 ) {
+ NSWindow * element = nil;
+ NSEnumerator * enumerator = [ childWindows objectEnumerator ];
+ while ( ( element = [ enumerator nextObject ] ) && !hitChild.is() ) {
+ if ( [ element isKindOfClass: [ SalFrameWindow class ] ] && [ self isViewElement: element hitByPoint: point ] ) {
+ // we have a child window that is hit
+ Reference < XAccessibleRelationSet > relationSet = [ static_cast<SalFrameWindow *>(element) accessibleContext ] -> getAccessibleRelationSet();
+ if ( relationSet.is() && relationSet -> containsRelation ( AccessibleRelationType::SUB_WINDOW_OF )) {
+ // we have a valid relation to the parent element
+ AccessibleRelation const relation = relationSet -> getRelationByType ( AccessibleRelationType::SUB_WINDOW_OF );
+ for ( const auto & i : relation.TargetSet ) {
+ Reference < XAccessible > rxAccessible ( i, UNO_QUERY );
+ if ( rxAccessible.is() && rxAccessible -> getAccessibleContext().is() ) {
+ // hit test for children of parent
+ hitChild = hitTestRunner ( hitPoint, rxAccessible -> getAccessibleContext() );
+ if (hitChild.is())
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ // nothing hit yet, so check ourself
+ if ( ! hitChild.is() ) {
+ if ( !maReferenceWrapper.rAccessibleContext ) {
+ [ self setDefaults: [ self accessibleContext ] ];
+ }
+ hitChild = hitTestRunner ( hitPoint, maReferenceWrapper.rAccessibleContext );
+ }
+ if ( hitChild.is() ) {
+ wrapper = [ AquaA11yFactory wrapperForAccessibleContext: hitChild ];
+ }
+ if ( wrapper ) {
+ [ wrapper retain ]; // TODO: retain only when transient ?
+ }
+ return wrapper;
+}
+
+#pragma mark -
+#pragma mark Access Methods
+
+-(XAccessibleAction *)accessibleAction {
+ return maReferenceWrapper.rAccessibleAction.get();
+}
+
+-(XAccessibleContext *)accessibleContext {
+ return maReferenceWrapper.rAccessibleContext.get();
+}
+
+-(XAccessibleComponent *)accessibleComponent {
+ return maReferenceWrapper.rAccessibleComponent.get();
+}
+
+-(XAccessibleExtendedComponent *)accessibleExtendedComponent {
+ return maReferenceWrapper.rAccessibleExtendedComponent.get();
+}
+
+-(XAccessibleSelection *)accessibleSelection {
+ return maReferenceWrapper.rAccessibleSelection.get();
+}
+
+-(XAccessibleTable *)accessibleTable {
+ return maReferenceWrapper.rAccessibleTable.get();
+}
+
+-(XAccessibleText *)accessibleText {
+ return maReferenceWrapper.rAccessibleText.get();
+}
+
+-(XAccessibleEditableText *)accessibleEditableText {
+ return maReferenceWrapper.rAccessibleEditableText.get();
+}
+
+-(XAccessibleValue *)accessibleValue {
+ return maReferenceWrapper.rAccessibleValue.get();
+}
+
+-(XAccessibleTextAttributes *)accessibleTextAttributes {
+ return maReferenceWrapper.rAccessibleTextAttributes.get();
+}
+
+-(XAccessibleMultiLineText *)accessibleMultiLineText {
+ return maReferenceWrapper.rAccessibleMultiLineText.get();
+}
+
+-(XAccessibleTextMarkup *)accessibleTextMarkup {
+ return maReferenceWrapper.rAccessibleTextMarkup.get();
+}
+
+-(NSWindow*)windowForParent {
+ return nil;
+}
+
+-(void)setActsAsRadioGroup:(BOOL)actsAsRadioGroup {
+ mActsAsRadioGroup = actsAsRadioGroup;
+}
+
+-(BOOL)actsAsRadioGroup {
+ return mActsAsRadioGroup;
+}
+
++(void)setPopupMenuOpen:(BOOL)popupMenuOpen {
+ isPopupMenuOpen = popupMenuOpen;
+}
+
+// NSAccessibility selectors
+
+- (BOOL)isAccessibilityElement
+{
+ return ! [ self accessibilityIsIgnored ];
+}
+
+- (BOOL)isAccessibilityFocused
+{
+ NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityFocusedAttribute ];
+ if ( pNumber )
+ return [ pNumber boolValue ];
+ else
+ return NO;
+}
+
+- (id)accessibilityTopLevelUIElement
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityTopLevelUIElementAttribute ];
+}
+
+- (id)accessibilityValue
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityValueAttribute ];
+}
+
+- (NSArray *)accessibilityVisibleChildren
+{
+ return [ self accessibilityChildren ];
+}
+
+- (NSAccessibilitySubrole)accessibilitySubrole
+{
+ return [ self accessibilityAttributeValue: NSAccessibilitySubroleAttribute ];
+}
+
+- (NSString *)accessibilityTitle
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityTitleAttribute ];
+}
+
+- (id)accessibilityTitleUIElement
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityTitleUIElementAttribute ];
+}
+
+- (NSAccessibilityOrientation)accessibilityOrientation
+{
+ NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityOrientationAttribute ];
+ if ( pNumber )
+ return NSAccessibilityOrientation([ pNumber integerValue ]);
+ else
+ return NSAccessibilityOrientationUnknown;
+}
+
+- (id)accessibilityParent
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityParentAttribute ];
+}
+
+- (NSAccessibilityRole)accessibilityRole
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityRoleAttribute ];
+}
+
+- (NSString *)accessibilityRoleDescription
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityRoleDescriptionAttribute ];
+}
+
+- (BOOL)isAccessibilitySelected
+{
+ NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilitySelectedAttribute ];
+ if ( pNumber )
+ return [ pNumber boolValue ];
+ else
+ return NO;
+}
+
+- (NSArray *)accessibilitySelectedChildren
+{
+ return [ self accessibilityAttributeValue: NSAccessibilitySelectedChildrenAttribute ];
+}
+
+- (NSArray *)accessibilityServesAsTitleForUIElements
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityServesAsTitleForUIElementsAttribute ];
+}
+
+- (id)accessibilityMinValue
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityMinValueAttribute ];
+}
+
+- (id)accessibilityMaxValue
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityMaxValueAttribute ];
+}
+
+- (id)accessibilityWindow
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityWindowAttribute ];
+}
+
+- (NSString *)accessibilityHelp
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityHelpAttribute ];
+}
+
+- (BOOL)isAccessibilityExpanded
+{
+ NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityExpandedAttribute ];
+ if ( pNumber )
+ return [ pNumber boolValue ];
+ else
+ return NO;
+}
+
+- (BOOL)isAccessibilityEnabled
+{
+ NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityEnabledAttribute ];
+ if ( pNumber )
+ return [ pNumber boolValue ];
+ else
+ return NO;
+}
+
+- (NSArray *)accessibilityChildren
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityChildrenAttribute ];
+}
+
+- (NSArray <id<NSAccessibilityElement>> *)accessibilityChildrenInNavigationOrder
+{
+ return [ self accessibilityChildren ];
+}
+
+- (NSArray *)accessibilityContents
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityContentsAttribute ];
+}
+
+- (NSString *)accessibilityLabel
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityDescriptionAttribute ];
+}
+
+- (id)accessibilityApplicationFocusedUIElement
+{
+ return [ self accessibilityFocusedUIElement ];
+}
+
+- (BOOL)isAccessibilityDisclosed
+{
+ NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityDisclosingAttribute ];
+ if ( pNumber )
+ return [ pNumber boolValue ];
+ else
+ return NO;
+}
+
+- (id)accessibilityHorizontalScrollBar
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityHorizontalScrollBarAttribute ];
+}
+
+- (id)accessibilityVerticalScrollBar
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityVerticalScrollBarAttribute ];
+}
+
+- (NSArray *)accessibilityTabs
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityTabsAttribute ];
+}
+
+- (NSArray *)accessibilityColumns
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityColumnsAttribute ];
+}
+
+- (NSArray *)accessibilityRows
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityRowsAttribute ];
+}
+
+- (NSRange)accessibilitySharedCharacterRange
+{
+ NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilitySharedCharacterRangeAttribute ];
+ if ( pValue )
+ return [ pValue rangeValue ];
+ else
+ return NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSArray *)accessibilitySharedTextUIElements
+{
+ return [ self accessibilityAttributeValue: NSAccessibilitySharedTextUIElementsAttribute ];
+}
+
+- (NSRange)accessibilityVisibleCharacterRange
+{
+ NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityVisibleCharacterRangeAttribute ];
+ if ( pValue )
+ return [ pValue rangeValue ];
+ else
+ return NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSInteger)accessibilityNumberOfCharacters
+{
+ NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityNumberOfCharactersAttribute ];
+ if ( pNumber )
+ return [ pNumber integerValue ];
+ else
+ return 0;
+}
+
+- (NSString *)accessibilitySelectedText
+{
+ return [ self accessibilityAttributeValue: NSAccessibilitySelectedTextAttribute ];
+}
+
+- (NSRange)accessibilitySelectedTextRange
+{
+ NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilitySelectedTextRangeAttribute ];
+ if ( pValue )
+ return [ pValue rangeValue ];
+ else
+ return NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSAttributedString *)accessibilityAttributedStringForRange:(NSRange)aRange
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityAttributedStringForRangeParameterizedAttribute forParameter: [ NSValue valueWithRange: aRange ] ];
+}
+
+- (NSRange)accessibilityRangeForLine:(NSInteger)nLine
+{
+ NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityRangeForLineParameterizedAttribute forParameter: [NSNumber numberWithInteger: nLine ] ];
+ if ( pValue )
+ return [ pValue rangeValue ];
+ else
+ return NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSString *)accessibilityStringForRange:(NSRange)aRange
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityStringForRangeParameterizedAttribute forParameter: [ NSValue valueWithRange: aRange ] ];
+}
+
+- (NSRange)accessibilityRangeForPosition:(NSPoint)aPoint
+{
+ NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityRangeForPositionParameterizedAttribute forParameter: [ NSValue valueWithPoint: aPoint ] ];
+ if ( pValue )
+ return [ pValue rangeValue ];
+ else
+ return NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSRange)accessibilityRangeForIndex:(NSInteger)nIndex
+{
+ NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityRangeForIndexParameterizedAttribute forParameter: [ NSNumber numberWithInteger: nIndex ] ];
+ if ( pValue )
+ return [ pValue rangeValue ];
+ else
+ return NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSRect)accessibilityFrameForRange:(NSRange)aRange
+{
+ NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityBoundsForRangeParameterizedAttribute forParameter: [ NSValue valueWithRange: aRange ] ];
+ if ( pValue )
+ return [ pValue rectValue ];
+ else
+ return NSZeroRect;
+}
+
+- (NSData *)accessibilityRTFForRange:(NSRange)aRange
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityRTFForRangeParameterizedAttribute forParameter: [ NSValue valueWithRange: aRange ] ];
+}
+
+- (NSRange)accessibilityStyleRangeForIndex:(NSInteger)nIndex
+{
+ NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityStyleRangeForIndexParameterizedAttribute forParameter: [ NSNumber numberWithInteger: nIndex ] ];
+ if ( pValue )
+ return [ pValue rangeValue ];
+ else
+ return NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSInteger)accessibilityLineForIndex:(NSInteger)nIndex
+{
+ NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityLineForIndexParameterizedAttribute forParameter: [ NSNumber numberWithInteger: nIndex ] ];
+ if ( pNumber )
+ return [ pNumber integerValue ];
+ else
+ return 0;
+}
+
+- (BOOL)accessibilityPerformDecrement
+{
+ return [ self performAction: NSAccessibilityDecrementAction ];
+}
+
+- (BOOL)accessibilityPerformPick
+{
+ return [ self performAction: NSAccessibilityPickAction ];
+}
+
+- (BOOL)accessibilityPerformShowMenu
+{
+ return [ self performAction: NSAccessibilityShowMenuAction ];
+}
+
+- (BOOL)accessibilityPerformPress
+{
+ return [ self performAction: NSAccessibilityPressAction ];
+}
+
+- (BOOL)accessibilityPerformIncrement
+{
+ return [ self performAction: NSAccessibilityIncrementAction ];
+}
+
+// NSAccessibilityElement selectors
+
+- (NSRect)accessibilityFrame
+{
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ try {
+ XAccessibleComponent *pAccessibleComponent = [ self accessibleComponent ];
+ if ( pAccessibleComponent ) {
+ com::sun::star::awt::Point location = pAccessibleComponent->getLocationOnScreen();
+ com::sun::star::awt::Size size = pAccessibleComponent->getSize();
+ NSRect screenRect = sal::aqua::getTotalScreenBounds();
+ NSRect frame = NSMakeRect( float(location.X), float( screenRect.size.height - size.Height - location.Y ), float(size.Width), float(size.Height) );
+ return frame;
+ }
+ } catch ( DisposedException& ) {
+ } catch ( RuntimeException& ) {
+ }
+
+ return NSZeroRect;
+}
+
+- (BOOL)accessibilityNotifiesWhenDestroyed
+{
+ return YES;
+}
+
+- (BOOL)isAccessibilitySelectorAllowed:(SEL)aSelector
+{
+ if ( ! aSelector )
+ return NO;
+
+ // don't explicitly report (non-)expanded state when not expandable
+ if (aSelector == @selector(isAccessibilityExpanded))
+ {
+ const sal_Int64 nStateSet = [ self accessibleContext ] -> getAccessibleStateSet();
+ if (!( nStateSet & AccessibleStateType::EXPANDABLE))
+ return false;
+ }
+
+ if ( [ self respondsToSelector: aSelector ] ) {
+ // Ignore actions if action is not supported
+ NSAccessibilityActionName pActionName = [ AquaA11yActionWrapper actionNameForSelector: aSelector ];
+ if ( pActionName ) {
+ NSArray *pActionNames = [ self accessibilityActionNames ];
+ if ( ! pActionNames || ! [ pActionNames containsObject: pActionName ] )
+ return NO;
+ } else {
+ // Ignore "setAccessibility" selectors if attribute is not settable
+ static NSString *pSetPrefix = @"setAccessibility";
+ NSString *pSelName = NSStringFromSelector( aSelector );
+ if ( pSelName && [ pSelName hasPrefix: pSetPrefix ] && [ pSelName hasSuffix: @":" ] ) {
+ NSAccessibilityAttributeName pAttrName = [ pSelName substringToIndex: [ pSelName length ] - 1 ];
+ if ( pAttrName && [ pAttrName length ] > [ pSetPrefix length ] ) {
+ pAttrName = [ pAttrName substringFromIndex: [ pSetPrefix length ] ];
+ if ( pAttrName && [ pAttrName length ] ) {
+ pAttrName = [ @"AX" stringByAppendingString: pAttrName ];
+ if ( pAttrName && [ pAttrName length ] && ! [ self accessibilityIsAttributeSettable: pAttrName ] )
+ return NO;
+ }
+ }
+ }
+ }
+ }
+
+ return [ super isAccessibilitySelectorAllowed: aSelector ];
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperbutton.h b/vcl/osx/a11ywrapperbutton.h
new file mode 100644
index 0000000000..e4819e0c07
--- /dev/null
+++ b/vcl/osx/a11ywrapperbutton.h
@@ -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 <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperButton : AquaA11yWrapper
+{
+}
+- (id)valueAttribute;
+- (id)descriptionAttribute;
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperbutton.mm b/vcl/osx/a11ywrapperbutton.mm
new file mode 100644
index 0000000000..a2c0d0398f
--- /dev/null
+++ b/vcl/osx/a11ywrapperbutton.mm
@@ -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/svapp.hxx>
+#include <osx/salinst.h>
+
+#include "a11ywrapperbutton.h"
+#include "a11ytextwrapper.h"
+
+// Wrapper for AXButton role
+
+@implementation AquaA11yWrapperButton : AquaA11yWrapper
+
+-(id)valueAttribute {
+ return [ NSString string ]; // we propagate AXTitle, that's enough
+}
+
+-(id)descriptionAttribute {
+ return [ NSString string ]; // we propagate AXTitle, that's enough
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ if ( [ attributeNames containsObject: NSAccessibilityTitleAttribute ] ) {
+ [ attributeNames removeObject: NSAccessibilityDescriptionAttribute ];
+ } else {
+ [ attributeNames addObject: NSAccessibilityTitleAttribute ];
+ }
+ // Remove text-specific attributes
+ [ attributeNames removeObjectsInArray: [ AquaA11yTextWrapper specialAttributeNames ] ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappercheckbox.h b/vcl/osx/a11ywrappercheckbox.h
new file mode 100644
index 0000000000..ec677d8833
--- /dev/null
+++ b/vcl/osx/a11ywrappercheckbox.h
@@ -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 <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperCheckBox : AquaA11yWrapper
+{
+}
+- (id)valueAttribute;
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute;
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappercheckbox.mm b/vcl/osx/a11ywrappercheckbox.mm
new file mode 100644
index 0000000000..9e0f221985
--- /dev/null
+++ b/vcl/osx/a11ywrappercheckbox.mm
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <osx/salinst.h>
+
+#include "a11ywrappercheckbox.h"
+#include "a11yvaluewrapper.h"
+#include "a11ytextwrapper.h"
+
+// Wrapper for AXCheckbox role
+
+@implementation AquaA11yWrapperCheckBox : AquaA11yWrapper
+
+-(id)valueAttribute {
+ if ( [ self accessibleValue ] ) {
+ return [ AquaA11yValueWrapper valueAttributeForElement: self ];
+ }
+ return [ NSNumber numberWithInt: 0 ];
+}
+
+-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ if ( [ attribute isEqualToString: NSAccessibilityValueAttribute ] ) {
+ return NO;
+ }
+ return [ super accessibilityIsAttributeSettable: attribute ];
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Remove text-specific attributes
+ [ attributeNames removeObjectsInArray: [ AquaA11yTextWrapper specialAttributeNames ] ];
+ [ attributeNames addObject: NSAccessibilityValueAttribute ];
+ [ attributeNames addObject: NSAccessibilityMinValueAttribute ];
+ [ attributeNames addObject: NSAccessibilityMaxValueAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappercombobox.h b/vcl/osx/a11ywrappercombobox.h
new file mode 100644
index 0000000000..0f9e21ec09
--- /dev/null
+++ b/vcl/osx/a11ywrappercombobox.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+
+@interface AquaA11yWrapperComboBox : AquaA11yWrapper
+{
+ AquaA11yWrapper* textArea;
+}
+- (id)initWithAccessibleContext:
+ (css::uno::Reference<css::accessibility::XAccessibleContext>)anAccessibleContext;
+- (id)valueAttribute;
+- (id)numberOfCharactersAttribute;
+- (id)selectedTextAttribute;
+- (id)selectedTextRangeAttribute;
+- (id)visibleCharacterRangeAttribute;
+// Accessibility Protocol
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute;
+- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute;
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappercombobox.mm b/vcl/osx/a11ywrappercombobox.mm
new file mode 100644
index 0000000000..bfcef7275e
--- /dev/null
+++ b/vcl/osx/a11ywrappercombobox.mm
@@ -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/svapp.hxx>
+#include <osx/salinst.h>
+
+#include "a11ywrappercombobox.h"
+#include "a11yrolehelper.h"
+
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::uno;
+
+// Wrapper for AXCombobox role
+
+@implementation AquaA11yWrapperComboBox : AquaA11yWrapper
+
+#pragma mark -
+#pragma mark Specialized Init Method
+
+-(id)initWithAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext {
+ self = [ super initWithAccessibleContext: rxAccessibleContext ];
+ if ( self != nil )
+ {
+ textArea = nil;
+ }
+ return self;
+}
+
+#pragma mark -
+#pragma mark Private Helper Method
+
+-(AquaA11yWrapper *)textArea {
+ // FIXME: May cause problems when stored. Then get dynamically each time (bad performance!)
+ if ( textArea == nil ) {
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ NSArray * elementChildren = [ super childrenAttribute ];
+ if ( [ elementChildren count ] > 0 ) {
+ NSEnumerator * enumerator = [ elementChildren objectEnumerator ];
+ id child;
+ while ( ( child = [ enumerator nextObject ] ) ) {
+ AquaA11yWrapper * element = static_cast<AquaA11yWrapper *>(child);
+ if ( [ [ AquaA11yRoleHelper getNativeRoleFrom: [ element accessibleContext ] ] isEqualToString: NSAccessibilityTextAreaRole ] ) {
+ textArea = element;
+ break;
+ }
+ }
+ }
+ [ pool release ];
+ }
+ return textArea;
+}
+
+#pragma mark -
+#pragma mark Wrapped Attributes From Contained Text Area
+
+-(id)valueAttribute {
+ if ( [ self textArea ] != nil ) {
+ return [ [ self textArea ] valueAttribute ];
+ }
+ return @"";
+}
+
+-(id)numberOfCharactersAttribute {
+ if ( [ self textArea ] != nil ) {
+ return [ [ self textArea ] numberOfCharactersAttribute ];
+ }
+ return [ NSNumber numberWithInt: 0 ];
+}
+
+-(id)selectedTextAttribute {
+ if ( [ self textArea ] != nil ) {
+ return [ [ self textArea ] selectedTextAttribute ];
+ }
+ return @"";
+}
+
+-(id)selectedTextRangeAttribute {
+ if ( [ self textArea ] != nil ) {
+ return [ [ self textArea ] selectedTextRangeAttribute ];
+ }
+ return [ NSValue valueWithRange: NSMakeRange ( 0, 0 ) ];
+}
+
+-(id)visibleCharacterRangeAttribute {
+ if ( [ self textArea ] != nil ) {
+ return [ [ self textArea ] visibleCharacterRangeAttribute ];
+ }
+ return [ NSValue valueWithRange: NSMakeRange ( 0, 0 ) ];
+}
+
+#pragma mark -
+#pragma mark Accessibility Protocol
+
+-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ if ( [ self textArea ] != nil && (
+ [ attribute isEqualToString: NSAccessibilitySelectedTextAttribute ]
+ || [ attribute isEqualToString: NSAccessibilitySelectedTextRangeAttribute ]
+ || [ attribute isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute ] ) ) {
+ return [ [ self textArea ] accessibilityIsAttributeSettable: attribute ];
+ }
+ return [ super accessibilityIsAttributeSettable: attribute ];
+}
+
+-(void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ if ( [ self textArea ] != nil && (
+ [ attribute isEqualToString: NSAccessibilitySelectedTextAttribute ]
+ || [ attribute isEqualToString: NSAccessibilitySelectedTextRangeAttribute ]
+ || [ attribute isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute ] ) ) {
+ return [ [ self textArea ] accessibilitySetValue: value forAttribute: attribute ];
+ }
+ return [ super accessibilitySetValue: value forAttribute: attribute ];
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObjectsInArray: [ NSArray arrayWithObjects:
+ NSAccessibilityTitleAttribute,
+ NSAccessibilityChildrenAttribute,
+ nil ]
+ ];
+ [ attributeNames addObjectsFromArray: [ NSArray arrayWithObjects:
+ NSAccessibilityExpandedAttribute,
+ NSAccessibilityValueAttribute,
+ NSAccessibilityNumberOfCharactersAttribute,
+ NSAccessibilitySelectedTextAttribute,
+ NSAccessibilitySelectedTextRangeAttribute,
+ NSAccessibilityVisibleCharacterRangeAttribute,
+ nil ]
+ ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappergroup.h b/vcl/osx/a11ywrappergroup.h
new file mode 100644
index 0000000000..68d9742e79
--- /dev/null
+++ b/vcl/osx/a11ywrappergroup.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperGroup : AquaA11yWrapper
+{
+}
+- (id)titleUIElementAttribute;
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappergroup.mm b/vcl/osx/a11ywrappergroup.mm
new file mode 100644
index 0000000000..7ed70d47ba
--- /dev/null
+++ b/vcl/osx/a11ywrappergroup.mm
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <osx/salinst.h>
+#include "a11ywrappergroup.h"
+
+// Wrapper for AXGroup role
+
+@implementation AquaA11yWrapperGroup : AquaA11yWrapper
+
+-(id)titleUIElementAttribute {
+ return self; // TODO
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObjectsInArray: [ NSArray arrayWithObjects:
+ // NSAccessibilityTitleAttribute,
+ NSAccessibilityEnabledAttribute,
+ NSAccessibilitySelectedChildrenAttribute,
+ nil ]
+ ];
+ [ attributeNames addObject: NSAccessibilityContentsAttribute ];
+ [ attributeNames addObject: NSAccessibilityTitleUIElementAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperlist.h b/vcl/osx/a11ywrapperlist.h
new file mode 100644
index 0000000000..deb6de98de
--- /dev/null
+++ b/vcl/osx/a11ywrapperlist.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperList : AquaA11yWrapper
+{
+}
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperlist.mm b/vcl/osx/a11ywrapperlist.mm
new file mode 100644
index 0000000000..25b3fa37ac
--- /dev/null
+++ b/vcl/osx/a11ywrapperlist.mm
@@ -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/svapp.hxx>
+#include <osx/salinst.h>
+#include "a11ywrapperlist.h"
+
+using namespace ::com::sun::star::accessibility;
+
+// Wrapper for AXList role
+
+@implementation AquaA11yWrapperList : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames addObject: NSAccessibilityOrientationAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperradiobutton.h b/vcl/osx/a11ywrapperradiobutton.h
new file mode 100644
index 0000000000..357b9566b0
--- /dev/null
+++ b/vcl/osx/a11ywrapperradiobutton.h
@@ -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 <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperRadioButton : AquaA11yWrapper
+{
+}
+- (id)valueAttribute;
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute;
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperradiobutton.mm b/vcl/osx/a11ywrapperradiobutton.mm
new file mode 100644
index 0000000000..5022cc18c2
--- /dev/null
+++ b/vcl/osx/a11ywrapperradiobutton.mm
@@ -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 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+#include "a11ywrapperradiobutton.h"
+#include "a11ytextwrapper.h"
+#include "a11yvaluewrapper.h"
+
+// Wrapper for AXRadioButton role
+
+@implementation AquaA11yWrapperRadioButton : AquaA11yWrapper
+
+-(id)valueAttribute {
+ if ( [ self accessibleValue ] ) {
+ return [ AquaA11yValueWrapper valueAttributeForElement: self ];
+ }
+ return [ NSNumber numberWithInt: 0 ];
+}
+
+-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ if ( [ attribute isEqualToString: NSAccessibilityValueAttribute ] ) {
+ return NO;
+ }
+ return [ super accessibilityIsAttributeSettable: attribute ];
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObjectsInArray: [ AquaA11yTextWrapper specialAttributeNames ] ];
+ [ attributeNames addObject: NSAccessibilityMinValueAttribute ];
+ [ attributeNames addObject: NSAccessibilityMaxValueAttribute ];
+ [ attributeNames addObject: NSAccessibilityValueAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperradiogroup.h b/vcl/osx/a11ywrapperradiogroup.h
new file mode 100644
index 0000000000..0cbc1cf420
--- /dev/null
+++ b/vcl/osx/a11ywrapperradiogroup.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperRadioGroup : AquaA11yWrapper
+{
+}
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperradiogroup.mm b/vcl/osx/a11ywrapperradiogroup.mm
new file mode 100644
index 0000000000..9768dbbb69
--- /dev/null
+++ b/vcl/osx/a11ywrapperradiogroup.mm
@@ -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/svapp.hxx>
+#include <osx/salinst.h>
+#include "a11ywrapperradiogroup.h"
+#include "a11ytextwrapper.h"
+
+// Wrapper for AXRadioGroup role
+
+@implementation AquaA11yWrapperRadioGroup : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObjectsInArray: [ AquaA11yTextWrapper specialAttributeNames ] ];
+ [ attributeNames removeObject: NSAccessibilityTitleAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperrow.h b/vcl/osx/a11ywrapperrow.h
new file mode 100644
index 0000000000..7933bbef44
--- /dev/null
+++ b/vcl/osx/a11ywrapperrow.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperRow : AquaA11yWrapper
+{
+}
+- (id)disclosingAttribute;
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperrow.mm b/vcl/osx/a11ywrapperrow.mm
new file mode 100644
index 0000000000..0c140c82c6
--- /dev/null
+++ b/vcl/osx/a11ywrapperrow.mm
@@ -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 <vcl/svapp.hxx>
+#include <osx/salinst.h>
+
+#include "a11ywrapperrow.h"
+#include "a11ytextwrapper.h"
+
+// Wrapper for AXRow role
+
+@implementation AquaA11yWrapperRow : AquaA11yWrapper
+
+-(id)disclosingAttribute {
+ // TODO: implement
+ return nil;
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObjectsInArray: [ AquaA11yTextWrapper specialAttributeNames ] ];
+ [ attributeNames removeObject: NSAccessibilityTitleAttribute ];
+ [ attributeNames removeObject: NSAccessibilityEnabledAttribute ];
+ [ attributeNames removeObject: NSAccessibilityFocusedAttribute ];
+ [ attributeNames addObject: NSAccessibilitySelectedAttribute ];
+ [ attributeNames addObject: NSAccessibilityDisclosingAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperscrollarea.h b/vcl/osx/a11ywrapperscrollarea.h
new file mode 100644
index 0000000000..45af07c1e5
--- /dev/null
+++ b/vcl/osx/a11ywrapperscrollarea.h
@@ -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 <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperScrollArea : AquaA11yWrapper
+{
+}
+- (id)verticalScrollBarAttribute;
+- (id)horizontalScrollBarAttribute;
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperscrollarea.mm b/vcl/osx/a11ywrapperscrollarea.mm
new file mode 100644
index 0000000000..22037220d4
--- /dev/null
+++ b/vcl/osx/a11ywrapperscrollarea.mm
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <osx/salinst.h>
+
+#include "a11ywrapperscrollarea.h"
+#include "a11ywrapperscrollbar.h"
+#include "a11yrolehelper.h"
+
+// Wrapper for AXScrollArea role
+
+@implementation AquaA11yWrapperScrollArea : AquaA11yWrapper
+
+-(id)scrollBarWithOrientation:(NSString *)orientation {
+ AquaA11yWrapper * theScrollBar = nil;
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ NSArray * elementChildren = [ self accessibilityAttributeValue: NSAccessibilityChildrenAttribute ];
+ if ( [ elementChildren count ] > 0 ) {
+ NSEnumerator * enumerator = [ elementChildren objectEnumerator ];
+ id child;
+ while ( ( child = [ enumerator nextObject ] ) ) {
+ AquaA11yWrapper * element = static_cast<AquaA11yWrapper *>(child);
+ if ( [ element isKindOfClass: [ AquaA11yWrapperScrollBar class ] ] ) {
+ AquaA11yWrapperScrollBar * scrollBar = static_cast<AquaA11yWrapperScrollBar *>(element);
+ if ( [ [ scrollBar orientationAttribute ] isEqualToString: orientation ] ) {
+ theScrollBar = scrollBar;
+ break;
+ }
+ }
+ }
+ }
+ [ pool release ];
+ return theScrollBar;
+}
+
+-(id)verticalScrollBarAttribute {
+ return [ self scrollBarWithOrientation: NSAccessibilityVerticalOrientationValue ];
+}
+
+-(id)horizontalScrollBarAttribute {
+ return [ self scrollBarWithOrientation: NSAccessibilityHorizontalOrientationValue ];
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObject: NSAccessibilityEnabledAttribute ];
+ [ attributeNames addObjectsFromArray: [ NSArray arrayWithObjects:
+ NSAccessibilityContentsAttribute,
+ NSAccessibilityVerticalScrollBarAttribute,
+ NSAccessibilityHorizontalScrollBarAttribute,
+ nil ]
+ ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperscrollbar.h b/vcl/osx/a11ywrapperscrollbar.h
new file mode 100644
index 0000000000..82db782c48
--- /dev/null
+++ b/vcl/osx/a11ywrapperscrollbar.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperScrollBar : AquaA11yWrapper
+{
+}
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperscrollbar.mm b/vcl/osx/a11ywrapperscrollbar.mm
new file mode 100644
index 0000000000..4a4612d3bb
--- /dev/null
+++ b/vcl/osx/a11ywrapperscrollbar.mm
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <osx/salinst.h>
+
+#include "a11ywrapperscrollbar.h"
+
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+
+using namespace ::com::sun::star::accessibility;
+
+// Wrapper for AXScrollBar role
+
+@implementation AquaA11yWrapperScrollBar : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames addObject: NSAccessibilityOrientationAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappersplitter.h b/vcl/osx/a11ywrappersplitter.h
new file mode 100644
index 0000000000..78ecbb97ba
--- /dev/null
+++ b/vcl/osx/a11ywrappersplitter.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperSplitter : AquaA11yWrapper
+{
+}
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappersplitter.mm b/vcl/osx/a11ywrappersplitter.mm
new file mode 100644
index 0000000000..39ec496af2
--- /dev/null
+++ b/vcl/osx/a11ywrappersplitter.mm
@@ -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/svapp.hxx>
+#include <osx/salinst.h>
+#include "a11ywrappersplitter.h"
+
+using namespace ::com::sun::star::accessibility;
+
+// Wrapper for AXSplitter role
+
+@implementation AquaA11yWrapperSplitter : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames addObject: NSAccessibilityOrientationAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperstatictext.h b/vcl/osx/a11ywrapperstatictext.h
new file mode 100644
index 0000000000..549fd9f9bd
--- /dev/null
+++ b/vcl/osx/a11ywrapperstatictext.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperStaticText : AquaA11yWrapper
+{
+}
+- (id)titleAttribute;
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperstatictext.mm b/vcl/osx/a11ywrapperstatictext.mm
new file mode 100644
index 0000000000..114c4179e8
--- /dev/null
+++ b/vcl/osx/a11ywrapperstatictext.mm
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <osx/salinst.h>
+#include "a11ywrapperstatictext.h"
+
+// Wrapper for AXStaticText role
+
+@implementation AquaA11yWrapperStaticText : AquaA11yWrapper
+
+-(id)titleAttribute {
+ NSString * title = [ super titleAttribute ];
+ if ( [ title isEqualToString: [ super valueAttribute ] ] ) {
+ return [ NSString string ];
+ }
+ return title;
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObject: NSAccessibilityTitleAttribute ];
+ [ attributeNames removeObject: NSAccessibilitySharedTextUIElementsAttribute ];
+ [ attributeNames removeObject: NSAccessibilitySharedCharacterRangeAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappertabgroup.h b/vcl/osx/a11ywrappertabgroup.h
new file mode 100644
index 0000000000..50ac2d1171
--- /dev/null
+++ b/vcl/osx/a11ywrappertabgroup.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperTabGroup : AquaA11yWrapper
+{
+}
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappertabgroup.mm b/vcl/osx/a11ywrappertabgroup.mm
new file mode 100644
index 0000000000..3d32ccc041
--- /dev/null
+++ b/vcl/osx/a11ywrappertabgroup.mm
@@ -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 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+#include "a11ywrappertabgroup.h"
+
+// Wrapper for AXTabGroup role
+
+@implementation AquaA11yWrapperTabGroup : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames addObjectsFromArray: [ NSArray arrayWithObjects:
+ NSAccessibilityContentsAttribute,
+ NSAccessibilityTabsAttribute,
+ nil ]
+ ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappertextarea.h b/vcl/osx/a11ywrappertextarea.h
new file mode 100644
index 0000000000..dda30f1b38
--- /dev/null
+++ b/vcl/osx/a11ywrappertextarea.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperTextArea : AquaA11yWrapper
+{
+}
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappertextarea.mm b/vcl/osx/a11ywrappertextarea.mm
new file mode 100644
index 0000000000..354030fb9a
--- /dev/null
+++ b/vcl/osx/a11ywrappertextarea.mm
@@ -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/svapp.hxx>
+#include <osx/salinst.h>
+#include "a11ywrappertextarea.h"
+
+// Wrapper for AXTextArea role
+
+@implementation AquaA11yWrapperTextArea : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObject: NSAccessibilityTitleAttribute ];
+ [ attributeNames removeObject: NSAccessibilityEnabledAttribute ];
+ [ attributeNames addObject: NSAccessibilityChildrenAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappertoolbar.h b/vcl/osx/a11ywrappertoolbar.h
new file mode 100644
index 0000000000..9f521e5890
--- /dev/null
+++ b/vcl/osx/a11ywrappertoolbar.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperToolbar : AquaA11yWrapper
+{
+}
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappertoolbar.mm b/vcl/osx/a11ywrappertoolbar.mm
new file mode 100644
index 0000000000..28b5d01328
--- /dev/null
+++ b/vcl/osx/a11ywrappertoolbar.mm
@@ -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 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+#include "a11ywrappertoolbar.h"
+
+// Wrapper for AXToolbar role
+
+@implementation AquaA11yWrapperToolbar : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObjectsInArray: [ NSArray arrayWithObjects:
+ NSAccessibilityTitleAttribute,
+ NSAccessibilityEnabledAttribute,
+ nil ]
+ ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/clipboard.cxx b/vcl/osx/clipboard.cxx
new file mode 100644
index 0000000000..f5be6f86c3
--- /dev/null
+++ b/vcl/osx/clipboard.cxx
@@ -0,0 +1,353 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "clipboard.hxx"
+
+#include "DataFlavorMapping.hxx"
+#include "OSXTransferable.hxx"
+#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+using namespace css;
+
+@implementation EventListener;
+
+-(EventListener*)initWithAquaClipboard: (AquaClipboard*) pcb
+{
+ self = [super init];
+
+ if (self)
+ pAquaClipboard = pcb;
+
+ return self;
+}
+
+-(void)pasteboard:(NSPasteboard*)sender provideDataForType:(const NSString*)type
+{
+ if( pAquaClipboard )
+ pAquaClipboard->provideDataForType(sender, type);
+}
+
+-(void)applicationDidBecomeActive:(NSNotification*)aNotification
+{
+ if( pAquaClipboard )
+ pAquaClipboard->applicationDidBecomeActive(aNotification);
+}
+
+-(void)disposing
+{
+ pAquaClipboard = nullptr;
+}
+
+@end
+
+static OUString clipboard_getImplementationName()
+{
+ return "com.sun.star.datatransfer.clipboard.AquaClipboard";
+}
+
+static uno::Sequence<OUString> clipboard_getSupportedServiceNames()
+{
+ return { OUString("com.sun.star.datatransfer.clipboard.SystemClipboard") };
+}
+
+AquaClipboard::AquaClipboard(NSPasteboard* pasteboard, bool bUseSystemPasteboard)
+ : WeakComponentImplHelper<XSystemClipboard, XFlushableClipboard, XServiceInfo>(m_aMutex)
+ , mIsSystemPasteboard(bUseSystemPasteboard)
+{
+ uno::Reference<uno::XComponentContext> xContext = comphelper::getProcessComponentContext();
+
+ mrXMimeCntFactory = datatransfer::MimeContentTypeFactory::create(xContext);
+
+ mpDataFlavorMapper = std::make_shared<DataFlavorMapper>();
+
+ if (pasteboard != nullptr)
+ {
+ mPasteboard = pasteboard;
+ mIsSystemPasteboard = false;
+ }
+ else
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH //TODO: 10.13 NSDragPboard
+ mPasteboard = bUseSystemPasteboard ? [NSPasteboard generalPasteboard] :
+ [NSPasteboard pasteboardWithName: NSDragPboard];
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ if (mPasteboard == nil)
+ {
+ throw uno::RuntimeException("AquaClipboard: Cannot create Cocoa pasteboard",
+ static_cast<XClipboardEx*>(this));
+ }
+ }
+
+ [mPasteboard retain];
+
+ mEventListener = [[EventListener alloc] initWithAquaClipboard: this];
+
+ if (mEventListener == nil)
+ {
+ [mPasteboard release];
+
+ throw uno::RuntimeException(
+ "AquaClipboard: Cannot create pasteboard change listener",
+ static_cast<XClipboardEx*>(this));
+ }
+
+ if (mIsSystemPasteboard)
+ {
+ NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];
+
+ [notificationCenter addObserver: mEventListener
+ selector: @selector(applicationDidBecomeActive:)
+ name: @"NSApplicationDidBecomeActiveNotification"
+ object: [NSApplication sharedApplication]];
+ }
+
+ mPasteboardChangeCount = [mPasteboard changeCount];
+}
+
+AquaClipboard::~AquaClipboard()
+{
+ if (mIsSystemPasteboard)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver: mEventListener];
+ }
+
+ [mEventListener disposing];
+ [mEventListener release];
+ [mPasteboard release];
+}
+
+uno::Reference<datatransfer::XTransferable> SAL_CALL AquaClipboard::getContents()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ // tdf#144124 Detect if ownership has been lost
+ // The shortcut assumes that lost ownership notifications from the
+ // system clipboard will happen elsewhere. They do under normal
+ // conditions, but do not when some clipboard managers are running.
+ // So, explicitly check ownership to catch such cases.
+ if (mIsSystemPasteboard)
+ applicationDidBecomeActive(nullptr);
+
+ // Shortcut: If we are clipboard owner already we don't need
+ // to drag the data through the system clipboard
+ if (mXClipboardContent.is())
+ {
+ return mXClipboardContent;
+ }
+
+ return uno::Reference<datatransfer::XTransferable>(
+ new OSXTransferable(mrXMimeCntFactory,
+ mpDataFlavorMapper,
+ mPasteboard));
+}
+
+void SAL_CALL AquaClipboard::setContents(
+ uno::Reference<datatransfer::XTransferable> const & xTransferable,
+ uno::Reference<datatransfer::clipboard::XClipboardOwner> const & xClipboardOwner)
+{
+ NSArray* types = xTransferable.is() ?
+ mpDataFlavorMapper->flavorSequenceToTypesArray(xTransferable->getTransferDataFlavors(), true) :
+ [NSArray array];
+
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ uno::Reference<datatransfer::clipboard::XClipboardOwner> oldOwner(mXClipboardOwner);
+ mXClipboardOwner = xClipboardOwner;
+
+ uno::Reference<datatransfer::XTransferable> oldContent(mXClipboardContent);
+ mXClipboardContent = xTransferable;
+
+ mPasteboardChangeCount = [mPasteboard declareTypes: types owner: mEventListener];
+
+ aGuard.clear();
+
+ // if we are already the owner of the clipboard
+ // then fire lost ownership event
+ if (oldOwner.is())
+ {
+ fireLostClipboardOwnershipEvent(oldOwner, oldContent);
+ }
+
+ fireClipboardChangedEvent();
+}
+
+OUString SAL_CALL AquaClipboard::getName()
+{
+ return OUString();
+}
+
+sal_Int8 SAL_CALL AquaClipboard::getRenderingCapabilities()
+{
+ return 0;
+}
+
+void SAL_CALL AquaClipboard::addClipboardListener(uno::Reference<datatransfer::clipboard::XClipboardListener> const & listener)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if (!listener.is())
+ throw lang::IllegalArgumentException("empty reference",
+ static_cast<XClipboardEx*>(this), 1);
+
+ mClipboardListeners.push_back(listener);
+}
+
+void SAL_CALL AquaClipboard::removeClipboardListener(uno::Reference<datatransfer::clipboard::XClipboardListener> const & listener)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if (!listener.is())
+ throw lang::IllegalArgumentException("empty reference",
+ static_cast<XClipboardEx*>(this), 1);
+
+ mClipboardListeners.remove(listener);
+}
+
+void AquaClipboard::applicationDidBecomeActive(NSNotification*)
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ int currentPboardChgCount = [mPasteboard changeCount];
+
+ if (currentPboardChgCount != mPasteboardChangeCount)
+ {
+ mPasteboardChangeCount = currentPboardChgCount;
+
+ // Clear clipboard content and owner and send lostOwnership
+ // notification to the old clipboard owner as well as
+ // ClipboardChanged notification to any clipboard listener
+ uno::Reference<datatransfer::clipboard::XClipboardOwner> oldOwner(mXClipboardOwner);
+ mXClipboardOwner.clear();
+
+ uno::Reference<datatransfer::XTransferable> oldContent(mXClipboardContent);
+ mXClipboardContent.clear();
+
+ aGuard.clear();
+
+ if (oldOwner.is())
+ {
+ fireLostClipboardOwnershipEvent(oldOwner, oldContent);
+ }
+
+ fireClipboardChangedEvent();
+ }
+}
+
+void AquaClipboard::fireClipboardChangedEvent()
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ datatransfer::clipboard::ClipboardEvent aEvent;
+
+ if (!mClipboardListeners.empty())
+ {
+ aEvent = datatransfer::clipboard::ClipboardEvent(getXWeak(), getContents());
+ }
+
+ aGuard.clear();
+
+ for (auto const& rListener : mClipboardListeners)
+ {
+ if (rListener.is())
+ {
+ try
+ {
+ rListener->changedContents(aEvent);
+ }
+ catch (uno::RuntimeException& )
+ {}
+ }
+ }
+}
+
+void AquaClipboard::fireLostClipboardOwnershipEvent(
+ uno::Reference<datatransfer::clipboard::XClipboardOwner> const & rOldOwner,
+ uno::Reference<datatransfer::XTransferable> const & rOldContent)
+{
+ assert(rOldOwner.is());
+
+ try
+ {
+ rOldOwner->lostOwnership(static_cast<XClipboardEx*>(this), rOldContent);
+ }
+ catch(uno::RuntimeException&)
+ {}
+}
+
+void AquaClipboard::provideDataForType(NSPasteboard* sender, const NSString* type)
+{
+ if( mXClipboardContent.is() )
+ {
+ DataProviderPtr_t dp = mpDataFlavorMapper->getDataProvider(type, mXClipboardContent);
+ NSData* pBoardData = nullptr;
+
+ if (dp)
+ {
+ pBoardData = dp->getSystemData();
+ [sender setData: pBoardData forType:const_cast<NSString*>(type)];
+ }
+ }
+}
+
+void SAL_CALL AquaClipboard::flushClipboard()
+{
+ if (mXClipboardContent.is())
+ {
+ uno::Sequence<datatransfer::DataFlavor> flavorList = mXClipboardContent->getTransferDataFlavors();
+ sal_uInt32 nFlavors = flavorList.getLength();
+ bool bInternal(false);
+
+ for (sal_uInt32 i = 0; i < nFlavors; i++)
+ {
+ const NSString* sysType = mpDataFlavorMapper->openOfficeToSystemFlavor(flavorList[i], bInternal);
+
+ if (sysType != nullptr)
+ {
+ provideDataForType(mPasteboard, sysType);
+ }
+ }
+ mXClipboardContent.clear();
+ }
+}
+
+NSPasteboard* AquaClipboard::getPasteboard() const
+{
+ return mPasteboard;
+}
+
+OUString SAL_CALL AquaClipboard::getImplementationName()
+{
+ return clipboard_getImplementationName();
+}
+
+sal_Bool SAL_CALL AquaClipboard::supportsService(OUString const & rServiceName)
+{
+ return cppu::supportsService(this, rServiceName);
+}
+
+uno::Sequence<OUString> SAL_CALL AquaClipboard::getSupportedServiceNames()
+{
+ return clipboard_getSupportedServiceNames();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/clipboard.hxx b/vcl/osx/clipboard.hxx
new file mode 100644
index 0000000000..6213ce6921
--- /dev/null
+++ b/vcl/osx/clipboard.hxx
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 "DataFlavorMapping.hxx"
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <cppuhelper/compbase.hxx>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardOwner.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
+#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
+#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <cppuhelper/basemutex.hxx>
+#include <com/sun/star/lang/XMultiComponentFactory.hpp>
+
+#include <list>
+
+#include <premac.h>
+#import <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+class AquaClipboard;
+
+@interface EventListener : NSObject
+{
+ AquaClipboard* pAquaClipboard;
+}
+
+// Init the pasteboard change listener with a reference to the OfficeClipboard
+// instance
+- (EventListener*)initWithAquaClipboard: (AquaClipboard*) pcb;
+
+// Promise resolver function
+- (void)pasteboard:(NSPasteboard*)sender provideDataForType:(const NSString *)type;
+
+-(void)applicationDidBecomeActive:(NSNotification*)aNotification;
+
+-(void)disposing;
+@end
+
+class AquaClipboard : public ::cppu::BaseMutex,
+ public ::cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard,
+ css::datatransfer::clipboard::XFlushableClipboard,
+ css::lang::XServiceInfo>
+{
+public:
+ /* Create a clipboard instance.
+
+ @param pasteboard
+ If not equal NULL the instance will be instantiated with the provided
+ pasteboard reference and 'bUseSystemClipboard' will be ignored
+
+ @param bUseSystemClipboard
+ If 'pasteboard' is NULL 'bUseSystemClipboard' determines whether the
+ system clipboard will be created (bUseSystemClipboard == true) or if
+ the DragPasteboard if bUseSystemClipboard == false
+ */
+ AquaClipboard(NSPasteboard* pasteboard,
+ bool bUseSystemClipboard);
+
+ virtual ~AquaClipboard() override;
+ AquaClipboard(const AquaClipboard&) = delete;
+ AquaClipboard& operator=(const AquaClipboard&) = delete;
+
+ // XClipboard
+
+ virtual css::uno::Reference<css::datatransfer::XTransferable> SAL_CALL getContents() override;
+
+ virtual void SAL_CALL setContents(css::uno::Reference<css::datatransfer::XTransferable> const & xTransferable,
+ css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> const & xClipboardOwner) override;
+
+ virtual OUString SAL_CALL getName() override;
+
+ // XClipboardEx
+
+ virtual sal_Int8 SAL_CALL getRenderingCapabilities() override;
+
+ // XClipboardNotifier
+
+ virtual void SAL_CALL addClipboardListener(css::uno::Reference<css::datatransfer::clipboard::XClipboardListener> const & listener) override;
+ virtual void SAL_CALL removeClipboardListener(css::uno::Reference<css::datatransfer::clipboard::XClipboardListener> const & listener) override;
+
+ // XFlushableClipboard
+
+ virtual void SAL_CALL flushClipboard() 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;
+
+ /* Get a reference to the used pastboard.
+ */
+ NSPasteboard* getPasteboard() const;
+
+ /* Notify the current clipboard owner that he is no longer the clipboard owner.
+ */
+ void fireLostClipboardOwnershipEvent(css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> const & oldOwner,
+ css::uno::Reference<css::datatransfer::XTransferable> const & oldContent);
+
+ void pasteboardChangedOwner();
+
+ void provideDataForType(NSPasteboard* sender, const NSString* type);
+
+ void applicationDidBecomeActive(NSNotification* aNotification);
+
+private:
+
+ /* Notify all registered XClipboardListener that the clipboard content
+ has changed.
+ */
+ void fireClipboardChangedEvent();
+
+private:
+ css::uno::Reference<css::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory;
+ std::list<css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>> mClipboardListeners;
+ css::uno::Reference<css::datatransfer::XTransferable> mXClipboardContent;
+ css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> mXClipboardOwner;
+ DataFlavorMapperPtr_t mpDataFlavorMapper;
+ bool mIsSystemPasteboard;
+ NSPasteboard* mPasteboard;
+ int mPasteboardChangeCount;
+ EventListener* mEventListener;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/cuidraw.hxx b/vcl/osx/cuidraw.hxx
new file mode 100644
index 0000000000..de625ce0a8
--- /dev/null
+++ b/vcl/osx/cuidraw.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <premac.h>
+#include <Carbon/Carbon.h>
+#include <postmac.h>
+
+#include <config_features.h>
+
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+
+extern "C" {
+
+typedef CFTypeRef CUIRendererRef;
+
+void CUIDraw(
+ CUIRendererRef r, CGRect rect, CGContextRef ctx, CFDictionaryRef options,
+ CFDictionaryRef * result);
+
+}
+
+#endif
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/documentfocuslistener.cxx b/vcl/osx/documentfocuslistener.cxx
new file mode 100644
index 0000000000..44a3506f86
--- /dev/null
+++ b/vcl/osx/documentfocuslistener.cxx
@@ -0,0 +1,230 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "documentfocuslistener.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
+#include <com/sun/star/accessibility/AccessibleEventId.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+#include <sal/log.hxx>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+DocumentFocusListener::DocumentFocusListener(AquaA11yFocusTracker& rTracker) :
+ m_aFocusTracker(rTracker)
+{
+}
+
+void SAL_CALL
+DocumentFocusListener::disposing( const EventObject& aEvent )
+{
+ // Unref the object here, but do not remove as listener since the object
+ // might no longer be in a state that safely allows this.
+ if( aEvent.Source.is() )
+ m_aRefList.erase(aEvent.Source);
+}
+
+void SAL_CALL
+DocumentFocusListener::notifyEvent( const AccessibleEventObject& aEvent )
+{
+ try {
+ switch( aEvent.EventId )
+ {
+ case AccessibleEventId::STATE_CHANGED:
+ {
+ sal_Int64 nState = AccessibleStateType::INVALID;
+ aEvent.NewValue >>= nState;
+
+ if( AccessibleStateType::FOCUSED == nState )
+ m_aFocusTracker.setFocusedObject( getAccessible(aEvent) );
+ }
+ break;
+
+ case AccessibleEventId::CHILD:
+ {
+ Reference< XAccessible > xChild;
+ if( (aEvent.OldValue >>= xChild) && xChild.is() )
+ detachRecursive(xChild);
+
+ if( (aEvent.NewValue >>= xChild) && xChild.is() )
+ attachRecursive(xChild);
+ }
+ break;
+
+ case AccessibleEventId::INVALIDATE_ALL_CHILDREN:
+ {
+ Reference< XAccessible > xAccessible( getAccessible(aEvent) );
+ detachRecursive(xAccessible);
+ attachRecursive(xAccessible);
+ SAL_INFO("vcl", "Invalidate all children called" );
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "Focused object has invalid index in parent");
+ }
+}
+
+Reference< XAccessible > DocumentFocusListener::getAccessible(const EventObject& aEvent )
+{
+ Reference< XAccessible > xAccessible(aEvent.Source, UNO_QUERY);
+
+ if( xAccessible.is() )
+ return xAccessible;
+
+ Reference< XAccessibleContext > xContext(aEvent.Source, UNO_QUERY);
+
+ if( xContext.is() )
+ {
+ Reference< XAccessible > xParent( xContext->getAccessibleParent() );
+ if( xParent.is() )
+ {
+ Reference< XAccessibleContext > xParentContext( xParent->getAccessibleContext() );
+ if( xParentContext.is() )
+ {
+ try {
+ return xParentContext->getAccessibleChild( xContext->getAccessibleIndexInParent() );
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "Accessible object has invalid index in parent");
+ }
+ }
+ }
+ }
+
+ return Reference< XAccessible >();
+}
+
+void DocumentFocusListener::attachRecursive(const Reference< XAccessible >& xAccessible)
+{
+ Reference< XAccessibleContext > xContext = xAccessible->getAccessibleContext();
+
+ if( xContext.is() )
+ attachRecursive(xAccessible, xContext);
+}
+
+void DocumentFocusListener::attachRecursive(
+ const Reference< XAccessible >& xAccessible,
+ const Reference< XAccessibleContext >& xContext
+)
+{
+ if( xContext.is() )
+ {
+ sal_Int64 nStateSet = xContext->getAccessibleStateSet();
+
+ attachRecursive(xAccessible, xContext, nStateSet);
+ }
+}
+
+void DocumentFocusListener::attachRecursive(
+ const Reference< XAccessible >& xAccessible,
+ const Reference< XAccessibleContext >& xContext,
+ sal_Int64 nStateSet
+)
+{
+ if( nStateSet & AccessibleStateType::FOCUSED )
+ m_aFocusTracker.setFocusedObject( xAccessible );
+
+ Reference< XAccessibleEventBroadcaster > xBroadcaster(xContext, UNO_QUERY);
+
+ // If not already done, add the broadcaster to the list and attach as listener.
+ if( xBroadcaster.is() && m_aRefList.insert(xBroadcaster).second )
+ {
+ xBroadcaster->addAccessibleEventListener(static_cast< XAccessibleEventListener *>(this));
+
+ if( ! (nStateSet & AccessibleStateType::MANAGES_DESCENDANTS) )
+ {
+ try {
+ sal_Int64 n, nmax = xContext->getAccessibleChildCount();
+ for( n = 0; n < nmax; n++ )
+ {
+ Reference< XAccessible > xChild( xContext->getAccessibleChild( n ) );
+
+ if( xChild.is() )
+ attachRecursive(xChild);
+ }
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "Accessible object index does not exist in parent");
+ }
+ }
+ }
+}
+
+void DocumentFocusListener::detachRecursive(const Reference< XAccessible >& xAccessible)
+{
+ Reference< XAccessibleContext > xContext = xAccessible->getAccessibleContext();
+
+ if( xContext.is() )
+ detachRecursive(xAccessible, xContext);
+}
+
+void DocumentFocusListener::detachRecursive(
+ const Reference< XAccessible >& xAccessible,
+ const Reference< XAccessibleContext >& xContext
+)
+{
+ sal_Int64 nStateSet = xContext->getAccessibleStateSet();
+
+ detachRecursive(xAccessible, xContext, nStateSet);
+}
+
+void DocumentFocusListener::detachRecursive(
+ const Reference< XAccessible >&,
+ const Reference< XAccessibleContext >& xContext,
+ sal_Int64 nStateSet
+)
+{
+ Reference< XAccessibleEventBroadcaster > xBroadcaster(xContext, UNO_QUERY);
+
+ if( xBroadcaster.is() && 0 < m_aRefList.erase(xBroadcaster) )
+ {
+ xBroadcaster->removeAccessibleEventListener(static_cast< XAccessibleEventListener *>(this));
+
+ if( ! (nStateSet & AccessibleStateType::MANAGES_DESCENDANTS) )
+ {
+ try {
+ sal_Int64 n, nmax = xContext->getAccessibleChildCount();
+ for( n = 0; n < nmax; n++ )
+ {
+ Reference< XAccessible > xChild( xContext->getAccessibleChild( n ) );
+
+ if( xChild.is() )
+ detachRecursive(xChild);
+ }
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "Accessible object index does not exist in parent");
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/documentfocuslistener.hxx b/vcl/osx/documentfocuslistener.hxx
new file mode 100644
index 0000000000..00097a7ef9
--- /dev/null
+++ b/vcl/osx/documentfocuslistener.hxx
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <com/sun/star/accessibility/XAccessibleEventListener.hpp>
+
+#include <cppuhelper/implbase.hxx>
+
+#include <osx/a11yfocustracker.hxx>
+
+#include <o3tl/sorted_vector.hxx>
+
+
+class DocumentFocusListener :
+ public ::cppu::WeakImplHelper< css::accessibility::XAccessibleEventListener >
+{
+
+public:
+
+ explicit DocumentFocusListener(AquaA11yFocusTracker& rTracker);
+
+ /// @throws css::lang::IndexOutOfBoundsException
+ /// @throws css::uno::RuntimeException
+ void attachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessible >& xAccessible
+ );
+
+ /// @throws css::lang::IndexOutOfBoundsException
+ /// @throws css::uno::RuntimeException
+ void attachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessible >& xAccessible,
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& xContext
+ );
+
+ /// @throws css::lang::IndexOutOfBoundsException
+ /// @throws css::uno::RuntimeException
+ void attachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessible >& xAccessible,
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& xContext,
+ sal_Int64 nStateSet
+ );
+
+ /// @throws css::lang::IndexOutOfBoundsException
+ /// @throws css::uno::RuntimeException
+ void detachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessible >& xAccessible
+ );
+
+ /// @throws css::lang::IndexOutOfBoundsException
+ /// @throws css::uno::RuntimeException
+ void detachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessible >& xAccessible,
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& xContext
+ );
+
+ /// @throws css::lang::IndexOutOfBoundsException
+ /// @throws css::uno::RuntimeException
+ void detachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessible >& xAccessible,
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& xContext,
+ sal_Int64 nStateSet
+ );
+
+ /// @throws css::lang::IndexOutOfBoundsException
+ /// @throws css::uno::RuntimeException
+ static css::uno::Reference< css::accessibility::XAccessible > getAccessible(const css::lang::EventObject& aEvent );
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+
+ // XAccessibleEventListener
+ virtual void SAL_CALL notifyEvent( const css::accessibility::AccessibleEventObject& aEvent ) override;
+
+private:
+ o3tl::sorted_vector< css::uno::Reference< css::uno::XInterface > > m_aRefList;
+
+ AquaA11yFocusTracker& m_aFocusTracker;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/printaccessoryview.mm b/vcl/osx/printaccessoryview.mm
new file mode 100644
index 0000000000..95ace78d28
--- /dev/null
+++ b/vcl/osx/printaccessoryview.mm
@@ -0,0 +1,1264 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <i18nlangtag/languagetag.hxx>
+#include <o3tl/string_view.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <vcl/print.hxx>
+#include <vcl/image.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/unohelp.hxx>
+#include <vcl/settings.hxx>
+
+#include <osx/printview.h>
+#include <osx/salinst.h>
+#include <quartz/utils.h>
+
+#include <svdata.hxx>
+#include <strings.hrc>
+#include <printaccessoryview.hrc>
+
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <com/sun/star/i18n/WordType.hpp>
+
+#include <map>
+#include <string_view>
+#include <utility>
+
+using namespace vcl;
+using namespace com::sun::star;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::uno;
+
+namespace {
+
+class ControllerProperties;
+
+}
+
+@interface ControlTarget : NSObject
+{
+ ControllerProperties* mpController;
+}
+-(id)initWithControllerMap: (ControllerProperties*)pController;
+-(void)triggered:(id)pSender;
+-(void)triggeredNumeric:(id)pSender;
+-(void)dealloc;
+@end
+
+@interface AquaPrintPanelAccessoryController : NSViewController< NSPrintPanelAccessorizing >
+{
+ NSPrintOperation *mpPrintOperation;
+ vcl::PrinterController *mpPrinterController;
+ PrintAccessoryViewState *mpViewState;
+}
+
+-(void)forPrintOperation:(NSPrintOperation*)pPrintOp;
+-(void)withPrinterController:(vcl::PrinterController*)pController;
+-(void)withViewState:(PrintAccessoryViewState*)pState;
+
+-(NSPrintOperation*)printOperation;
+-(vcl::PrinterController*)printerController;
+-(PrintAccessoryViewState*)viewState;
+
+-(NSSet*)keyPathsForValuesAffectingPreview;
+-(NSArray*)localizedSummaryItems;
+
+-(sal_Int32)updatePrintOperation:(sal_Int32)pLastPageCount;
+
+@end
+
+@implementation AquaPrintPanelAccessoryController
+
+-(void)forPrintOperation:(NSPrintOperation*)pPrintOp
+ { mpPrintOperation = pPrintOp; }
+
+-(void)withPrinterController:(vcl::PrinterController*)pController
+ { mpPrinterController = pController; }
+
+-(void)withViewState:(PrintAccessoryViewState*)pState
+ { mpViewState = pState; }
+
+-(NSPrintOperation*)printOperation
+ { return mpPrintOperation; }
+
+-(vcl::PrinterController*)printerController
+ { return mpPrinterController; }
+
+-(PrintAccessoryViewState*)viewState
+ { return mpViewState; }
+
+-(NSSet*)keyPathsForValuesAffectingPreview
+{
+ return [ NSSet setWithObject:@"updatePrintOperation" ];
+}
+
+-(NSArray*)localizedSummaryItems
+{
+ return [ NSArray arrayWithObject:
+ [ NSDictionary dictionary ] ];
+}
+
+-(sal_Int32)updatePrintOperation:(sal_Int32)pLastPageCount
+{
+ // page range may be changed by option choice
+ sal_Int32 nPages = mpPrinterController->getFilteredPageCount();
+
+ mpViewState->bNeedRestart = false;
+ if( nPages != pLastPageCount )
+ {
+ #if OSL_DEBUG_LEVEL > 1
+ SAL_INFO( "vcl.osx.print", "number of pages changed" <<
+ " from " << pLastPageCount << " to " << nPages );
+ #endif
+ mpViewState->bNeedRestart = true;
+ }
+
+ NSTabView* pTabView = [[[self view] subviews] objectAtIndex:0];
+ NSTabViewItem* pItem = [pTabView selectedTabViewItem];
+ if( pItem )
+ mpViewState->nLastPage = [pTabView indexOfTabViewItem: pItem];
+ else
+ mpViewState->nLastPage = 0;
+
+ if( mpViewState->bNeedRestart )
+ {
+ // AppKit does not give a chance of changing the page count
+ // and don't let cancel the dialog either
+ // hack: send a cancel message to the modal window displaying views
+ NSWindow* pNSWindow = [NSApp modalWindow];
+ if( pNSWindow )
+ [pNSWindow cancelOperation: nil];
+ [[mpPrintOperation printInfo] setJobDisposition: NSPrintCancelJob];
+ }
+
+ return nPages;
+}
+
+@end
+
+namespace {
+
+class ControllerProperties
+{
+ std::map< int, OUString > maTagToPropertyName;
+ std::map< int, sal_Int32 > maTagToValueInt;
+ std::map< NSView*, NSView* > maViewPairMap;
+ std::vector< NSObject* > maViews;
+ int mnNextTag;
+ sal_Int32 mnLastPageCount;
+ AquaPrintPanelAccessoryController* mpAccessoryController;
+
+public:
+ ControllerProperties( AquaPrintPanelAccessoryController* i_pAccessoryController )
+ : mnNextTag( 0 )
+ , mnLastPageCount( [i_pAccessoryController printerController]->getFilteredPageCount() )
+ , mpAccessoryController( i_pAccessoryController )
+ {
+ static_assert( SAL_N_ELEMENTS(SV_PRINT_NATIVE_STRINGS) == 5, "resources not found" );
+ }
+
+ static OUString getMoreString()
+ {
+ return VclResId(SV_PRINT_NATIVE_STRINGS[3]);
+ }
+
+ static OUString getPrintSelectionString()
+ {
+ return VclResId(SV_PRINT_NATIVE_STRINGS[4]);
+ }
+
+ int addNameTag( const OUString& i_rPropertyName )
+ {
+ int nNewTag = mnNextTag++;
+ maTagToPropertyName[ nNewTag ] = i_rPropertyName;
+ return nNewTag;
+ }
+
+ int addNameAndValueTag( const OUString& i_rPropertyName, sal_Int32 i_nValue )
+ {
+ int nNewTag = mnNextTag++;
+ maTagToPropertyName[ nNewTag ] = i_rPropertyName;
+ maTagToValueInt[ nNewTag ] = i_nValue;
+ return nNewTag;
+ }
+
+ void addObservedControl( NSObject* i_pView )
+ {
+ maViews.push_back( i_pView );
+ }
+
+ void addViewPair( NSView* i_pLeft, NSView* i_pRight )
+ {
+ maViewPairMap[ i_pLeft ] = i_pRight;
+ maViewPairMap[ i_pRight ] = i_pLeft;
+ }
+
+ NSView* getPair( NSView* i_pLeft ) const
+ {
+ NSView* pRight = nil;
+ std::map< NSView*, NSView* >::const_iterator it = maViewPairMap.find( i_pLeft );
+ if( it != maViewPairMap.end() )
+ pRight = it->second;
+ return pRight;
+ }
+
+ void changePropertyWithIntValue( int i_nTag )
+ {
+ std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
+ std::map< int, sal_Int32 >::const_iterator value_it = maTagToValueInt.find( i_nTag );
+ if( name_it != maTagToPropertyName.end() && value_it != maTagToValueInt.end() )
+ {
+ vcl::PrinterController * mpController = [mpAccessoryController printerController];
+ PropertyValue* pVal = mpController->getValue( name_it->second );
+ if( pVal )
+ {
+ pVal->Value <<= value_it->second;
+ mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
+ }
+ }
+ }
+
+ void changePropertyWithIntValue( int i_nTag, sal_Int64 i_nValue )
+ {
+ std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
+ if( name_it != maTagToPropertyName.end() )
+ {
+ vcl::PrinterController * mpController = [mpAccessoryController printerController];
+ PropertyValue* pVal = mpController->getValue( name_it->second );
+ if( pVal )
+ {
+ pVal->Value <<= i_nValue;
+ mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
+ }
+ }
+ }
+
+ void changePropertyWithBoolValue( int i_nTag, bool i_bValue )
+ {
+ std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
+ if( name_it != maTagToPropertyName.end() )
+ {
+ vcl::PrinterController * mpController = [mpAccessoryController printerController];
+ PropertyValue* pVal = mpController->getValue( name_it->second );
+ if( pVal )
+ {
+ // ugly
+ if( name_it->second == "PrintContent" )
+ pVal->Value <<= i_bValue ? sal_Int32(2) : sal_Int32(0);
+ else
+ pVal->Value <<= i_bValue;
+
+ mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
+ }
+ }
+ }
+
+ void changePropertyWithStringValue( int i_nTag, const OUString& i_rValue )
+ {
+ std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
+ if( name_it != maTagToPropertyName.end() )
+ {
+ vcl::PrinterController * mpController = [mpAccessoryController printerController];
+ PropertyValue* pVal = mpController->getValue( name_it->second );
+ if( pVal )
+ {
+ pVal->Value <<= i_rValue;
+ mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
+ }
+ }
+ }
+
+ void updateEnableState()
+ {
+ for( std::vector< NSObject* >::iterator it = maViews.begin(); it != maViews.end(); ++it )
+ {
+ NSObject* pObj = *it;
+ NSControl* pCtrl = nil;
+ NSCell* pCell = nil;
+ if( [pObj isKindOfClass: [NSControl class]] )
+ pCtrl = static_cast<NSControl*>(pObj);
+ else if( [pObj isKindOfClass: [NSCell class]] )
+ pCell = static_cast<NSCell*>(pObj);
+
+ int nTag = pCtrl ? [pCtrl tag] :
+ pCell ? [pCell tag] :
+ -1;
+
+ std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( nTag );
+ if( name_it != maTagToPropertyName.end() && name_it->second != "PrintContent" )
+ {
+ vcl::PrinterController * mpController = [mpAccessoryController printerController];
+ bool bEnabled = mpController->isUIOptionEnabled( name_it->second ) ? YES : NO;
+ if( pCtrl )
+ {
+ [pCtrl setEnabled: bEnabled];
+ NSView* pOther = getPair( pCtrl );
+ if( pOther && [pOther isKindOfClass: [NSControl class]] )
+ [static_cast<NSControl*>(pOther) setEnabled: bEnabled];
+ }
+ else if( pCell )
+ [pCell setEnabled: bEnabled];
+ }
+ }
+ }
+
+};
+
+}
+
+static OUString filterAccelerator( OUString const & rText )
+{
+ OUStringBuffer aBuf( rText.getLength() );
+ for( sal_Int32 nIndex = 0; nIndex != -1; )
+ aBuf.append( o3tl::getToken( rText, 0, '~', nIndex ) );
+ return aBuf.makeStringAndClear();
+}
+
+@implementation ControlTarget
+
+-(id)initWithControllerMap: (ControllerProperties*)pController
+{
+ if( (self = [super init]) )
+ {
+ mpController = pController;
+ }
+ return self;
+}
+
+-(void)triggered:(id)pSender
+{
+ if( [pSender isMemberOfClass: [NSPopUpButton class]] )
+ {
+ NSPopUpButton* pBtn = static_cast<NSPopUpButton*>(pSender);
+ NSMenuItem* pSelected = [pBtn selectedItem];
+ if( pSelected )
+ {
+ int nTag = [pSelected tag];
+ mpController->changePropertyWithIntValue( nTag );
+ }
+ }
+ else if( [pSender isMemberOfClass: [NSButton class]] )
+ {
+ NSButton* pBtn = static_cast<NSButton*>(pSender);
+ int nTag = [pBtn tag];
+ mpController->changePropertyWithBoolValue( nTag, [pBtn state] == NSControlStateValueOn );
+ }
+ else if( [pSender isMemberOfClass: [NSMatrix class]] )
+ {
+ NSObject* pObj = [static_cast<NSMatrix*>(pSender) selectedCell];
+ if( [pObj isMemberOfClass: [NSButtonCell class]] )
+ {
+ NSButtonCell* pCell = static_cast<NSButtonCell*>(pObj);
+ int nTag = [pCell tag];
+ mpController->changePropertyWithIntValue( nTag );
+ }
+ }
+ else if( [pSender isMemberOfClass: [NSTextField class]] )
+ {
+ NSTextField* pField = static_cast<NSTextField*>(pSender);
+ int nTag = [pField tag];
+ OUString aValue = GetOUString( [pSender stringValue] );
+ mpController->changePropertyWithStringValue( nTag, aValue );
+ }
+ else
+ {
+ SAL_INFO( "vcl.osx.print", "Unsupported class" <<
+ ( [pSender class] ? [NSStringFromClass([pSender class]) UTF8String] : "nil" ) );
+ }
+ mpController->updateEnableState();
+}
+
+-(void)triggeredNumeric:(id)pSender
+{
+ if( [pSender isMemberOfClass: [NSTextField class]] )
+ {
+ NSTextField* pField = static_cast<NSTextField*>(pSender);
+ int nTag = [pField tag];
+ sal_Int64 nValue = [pField intValue];
+
+ NSView* pOther = mpController->getPair( pField );
+ if( pOther )
+ [static_cast<NSControl*>(pOther) setIntValue: nValue];
+
+ mpController->changePropertyWithIntValue( nTag, nValue );
+ }
+ else if( [pSender isMemberOfClass: [NSStepper class]] )
+ {
+ NSStepper* pStep = static_cast<NSStepper*>(pSender);
+ int nTag = [pStep tag];
+ sal_Int64 nValue = [pStep intValue];
+
+ NSView* pOther = mpController->getPair( pStep );
+ if( pOther )
+ [static_cast<NSControl*>(pOther) setIntValue: nValue];
+
+ mpController->changePropertyWithIntValue( nTag, nValue );
+ }
+ else
+ {
+ SAL_INFO( "vcl.osx.print", "Unsupported class" <<
+ ([pSender class] ? [NSStringFromClass([pSender class]) UTF8String] : "nil") );
+ }
+ mpController->updateEnableState();
+}
+
+-(void)dealloc
+{
+ delete mpController;
+ [super dealloc];
+}
+
+@end
+
+namespace {
+
+struct ColumnItem
+{
+ NSControl* pControl;
+ CGFloat nOffset;
+ NSControl* pSubControl;
+
+ ColumnItem( NSControl* i_pControl = nil, CGFloat i_nOffset = 0, NSControl* i_pSub = nil )
+ : pControl( i_pControl )
+ , nOffset( i_nOffset )
+ , pSubControl( i_pSub )
+ {}
+
+ CGFloat getWidth() const
+ {
+ CGFloat nWidth = 0;
+ if( pControl )
+ {
+ NSRect aCtrlRect = [pControl frame];
+ nWidth = aCtrlRect.size.width;
+ nWidth += nOffset;
+ if( pSubControl )
+ {
+ NSRect aSubRect = [pSubControl frame];
+ nWidth += aSubRect.size.width;
+ nWidth += aSubRect.origin.x - (aCtrlRect.origin.x + aCtrlRect.size.width);
+ }
+ }
+ return nWidth;
+ }
+};
+
+}
+
+static void adjustViewAndChildren( NSView* pNSView, NSSize& rMaxSize,
+ std::vector< ColumnItem >& rLeftColumn,
+ std::vector< ColumnItem >& rRightColumn
+ )
+{
+ // balance columns
+
+ // first get overall column widths
+ CGFloat nLeftWidth = 0;
+ CGFloat nRightWidth = 0;
+ for( size_t i = 0; i < rLeftColumn.size(); i++ )
+ {
+ CGFloat nW = rLeftColumn[i].getWidth();
+ if( nW > nLeftWidth )
+ nLeftWidth = nW;
+ }
+ for( size_t i = 0; i < rRightColumn.size(); i++ )
+ {
+ CGFloat nW = rRightColumn[i].getWidth();
+ if( nW > nRightWidth )
+ nRightWidth = nW;
+ }
+
+ // right align left column
+ for( size_t i = 0; i < rLeftColumn.size(); i++ )
+ {
+ if( rLeftColumn[i].pControl )
+ {
+ NSRect aCtrlRect = [rLeftColumn[i].pControl frame];
+ CGFloat nX = nLeftWidth - aCtrlRect.size.width;
+ if( rLeftColumn[i].pSubControl )
+ {
+ NSRect aSubRect = [rLeftColumn[i].pSubControl frame];
+ nX -= aSubRect.size.width + (aSubRect.origin.x - (aCtrlRect.origin.x + aCtrlRect.size.width));
+ aSubRect.origin.x = nLeftWidth - aSubRect.size.width;
+ [rLeftColumn[i].pSubControl setFrame: aSubRect];
+ }
+ aCtrlRect.origin.x = nX;
+ [rLeftColumn[i].pControl setFrame: aCtrlRect];
+ }
+ }
+
+ // left align right column
+ for( size_t i = 0; i < rRightColumn.size(); i++ )
+ {
+ if( rRightColumn[i].pControl )
+ {
+ NSRect aCtrlRect = [rRightColumn[i].pControl frame];
+ CGFloat nX = nLeftWidth + 3;
+ if( rRightColumn[i].pSubControl )
+ {
+ NSRect aSubRect = [rRightColumn[i].pSubControl frame];
+ aSubRect.origin.x = nX + aSubRect.origin.x - aCtrlRect.origin.x;
+ [rRightColumn[i].pSubControl setFrame: aSubRect];
+ }
+ aCtrlRect.origin.x = nX;
+ [rRightColumn[i].pControl setFrame: aCtrlRect];
+ }
+ }
+
+ NSArray* pSubViews = [pNSView subviews];
+ unsigned int nViews = [pSubViews count];
+ NSRect aUnion = NSZeroRect;
+
+ // get the combined frame of all subviews
+ for( unsigned int n = 0; n < nViews; n++ )
+ {
+ aUnion = NSUnionRect( aUnion, [[pSubViews objectAtIndex: n] frame] );
+ }
+
+ // move everything so it will fit
+ for( unsigned int n = 0; n < nViews; n++ )
+ {
+ NSView* pCurSubView = [pSubViews objectAtIndex: n];
+ NSRect aFrame = [pCurSubView frame];
+ aFrame.origin.x -= aUnion.origin.x - 5;
+ aFrame.origin.y -= aUnion.origin.y - 5;
+ [pCurSubView setFrame: aFrame];
+ }
+
+ // resize the view itself
+ aUnion.size.height += 10;
+ aUnion.size.width += 20;
+ [pNSView setFrameSize: aUnion.size];
+
+ if( aUnion.size.width > rMaxSize.width )
+ rMaxSize.width = aUnion.size.width;
+ if( aUnion.size.height > rMaxSize.height )
+ rMaxSize.height = aUnion.size.height;
+}
+
+static void adjustTabViews( NSTabView* pTabView, NSSize aTabSize )
+{
+ // loop over all contained tab pages
+ NSArray* pTabbedViews = [pTabView tabViewItems];
+ int nViews = [pTabbedViews count];
+ for( int i = 0; i < nViews; i++ )
+ {
+ NSTabViewItem* pItem = static_cast<NSTabViewItem*>([pTabbedViews objectAtIndex: i]);
+ NSView* pNSView = [pItem view];
+ if( pNSView )
+ {
+ NSRect aRect = [pNSView frame];
+ double nDiff = aTabSize.height - aRect.size.height;
+ aRect.size = aTabSize;
+ [pNSView setFrame: aRect];
+
+ NSArray* pSubViews = [pNSView subviews];
+ unsigned int nSubViews = [pSubViews count];
+
+ // move everything up
+ for( unsigned int n = 0; n < nSubViews; n++ )
+ {
+ NSView* pCurSubView = [pSubViews objectAtIndex: n];
+ NSRect aFrame = [pCurSubView frame];
+ aFrame.origin.y += nDiff;
+ // give separators the correct width
+ // separators are currently the only NSBoxes we use
+ if( [pCurSubView isMemberOfClass: [NSBox class]] )
+ {
+ aFrame.size.width = aTabSize.width - aFrame.origin.x - 10;
+ }
+ [pCurSubView setFrame: aFrame];
+ }
+ }
+ }
+}
+
+static NSControl* createLabel( const OUString& i_rText )
+{
+ NSString* pText = CreateNSString( i_rText );
+ NSRect aTextRect = { NSZeroPoint, {20, 15} };
+ NSTextField* pTextView = [[NSTextField alloc] initWithFrame: aTextRect];
+ [pTextView setFont: [NSFont controlContentFontOfSize: 0]];
+ [pTextView setEditable: NO];
+ [pTextView setSelectable: NO];
+ [pTextView setDrawsBackground: NO];
+ [pTextView setBordered: NO];
+ [pTextView setStringValue: pText];
+ [pTextView sizeToFit];
+ [pText release];
+ return pTextView;
+}
+
+static sal_Int32 findBreak( const OUString& i_rText, sal_Int32 i_nPos )
+{
+ sal_Int32 nRet = i_rText.getLength();
+ Reference< i18n::XBreakIterator > xBI( vcl::unohelper::CreateBreakIterator() );
+ if( xBI.is() )
+ {
+ i18n::Boundary aBoundary =
+ xBI->getWordBoundary( i_rText, i_nPos,
+ Application::GetSettings().GetLanguageTag().getLocale(),
+ i18n::WordType::ANYWORD_IGNOREWHITESPACES,
+ true );
+ nRet = aBoundary.endPos;
+ }
+ return nRet;
+}
+
+static void linebreakCell( NSCell* pBtn, const OUString& i_rText )
+{
+ NSString* pText = CreateNSString( i_rText );
+ [pBtn setTitle: pText];
+ [pText release];
+ NSSize aSize = [pBtn cellSize];
+ if( aSize.width > 280 )
+ {
+ // need two lines
+ sal_Int32 nLen = i_rText.getLength();
+ sal_Int32 nIndex = nLen / 2;
+ nIndex = findBreak( i_rText, nIndex );
+ if( nIndex < nLen )
+ {
+ OUStringBuffer aBuf( i_rText );
+ aBuf[nIndex] = '\n';
+ pText = CreateNSString( aBuf.makeStringAndClear() );
+ [pBtn setTitle: pText];
+ [pText release];
+ }
+ }
+}
+
+static void addSubgroup( NSView* pCurParent, CGFloat& rCurY, const OUString& rText )
+{
+ NSControl* pTextView = createLabel( rText );
+ [pCurParent addSubview: [pTextView autorelease]];
+ NSRect aTextRect = [pTextView frame];
+ // move to nCurY
+ aTextRect.origin.y = rCurY - aTextRect.size.height;
+ [pTextView setFrame: aTextRect];
+
+ NSRect aSepRect = { { aTextRect.size.width + 1, aTextRect.origin.y }, { 100, 6 } };
+ NSBox* pBox = [[NSBox alloc] initWithFrame: aSepRect];
+ [pBox setBoxType: NSBoxSeparator];
+ [pCurParent addSubview: [pBox autorelease]];
+
+ // update nCurY
+ rCurY = aTextRect.origin.y - 5;
+}
+
+static void addBool( NSView* pCurParent, CGFloat rCurX, CGFloat& rCurY, CGFloat nAttachOffset,
+ const OUString& rText, bool bEnabled,
+ const OUString& rProperty, bool bValue,
+ std::vector<ColumnItem >& rRightColumn,
+ ControllerProperties* pControllerProperties,
+ ControlTarget* pCtrlTarget
+ )
+{
+ NSRect aCheckRect = { { rCurX + nAttachOffset, 0 }, { 0, 15 } };
+ NSButton* pBtn = [[NSButton alloc] initWithFrame: aCheckRect];
+ [pBtn setButtonType: NSButtonTypeSwitch];
+ [pBtn setState: bValue ? NSControlStateValueOn : NSControlStateValueOff];
+ if( ! bEnabled )
+ [pBtn setEnabled: NO];
+ linebreakCell( [pBtn cell], rText );
+ [pBtn sizeToFit];
+
+ rRightColumn.push_back( ColumnItem( pBtn ) );
+
+ // connect target
+ [pBtn setTarget: pCtrlTarget];
+ [pBtn setAction: @selector(triggered:)];
+ int nTag = pControllerProperties->addNameTag( rProperty );
+ pControllerProperties->addObservedControl( pBtn );
+ [pBtn setTag: nTag];
+
+ aCheckRect = [pBtn frame];
+ // #i115837# add a murphy factor; it can apparently occasionally happen
+ // that sizeToFit does not a perfect job and that the button linebreaks again
+ // if - and only if - there is already a '\n' contained in the text and the width
+ // is minimally of
+ aCheckRect.size.width += 1;
+
+ // move to rCurY
+ aCheckRect.origin.y = rCurY - aCheckRect.size.height;
+ [pBtn setFrame: aCheckRect];
+
+ [pCurParent addSubview: [pBtn autorelease]];
+
+ // update rCurY
+ rCurY = aCheckRect.origin.y - 5;
+}
+
+static void addRadio( NSView* pCurParent, CGFloat rCurX, CGFloat& rCurY, CGFloat nAttachOffset,
+ const OUString& rText,
+ const OUString& rProperty, Sequence<OUString> const & rChoices, sal_Int32 nSelectValue,
+ std::vector<ColumnItem >& rLeftColumn,
+ std::vector<ColumnItem >& rRightColumn,
+ ControllerProperties* pControllerProperties,
+ ControlTarget* pCtrlTarget
+ )
+{
+ CGFloat nOff = 0;
+ if( rText.getLength() )
+ {
+ // add a label
+ NSControl* pTextView = createLabel( rText );
+ NSRect aTextRect = [pTextView frame];
+ aTextRect.origin.x = rCurX + nAttachOffset;
+ [pCurParent addSubview: [pTextView autorelease]];
+
+ rLeftColumn.push_back( ColumnItem( pTextView ) );
+
+ // move to nCurY
+ aTextRect.origin.y = rCurY - aTextRect.size.height;
+ [pTextView setFrame: aTextRect];
+
+ // update nCurY
+ rCurY = aTextRect.origin.y - 5;
+
+ // indent the radio group relative to the text
+ // nOff = 20;
+ }
+
+ // setup radio matrix
+ NSButtonCell* pProto = [[NSButtonCell alloc] init];
+
+ NSRect aRadioRect = { { rCurX + nOff, 0 },
+ { 280 - rCurX,
+ static_cast<CGFloat>(5*rChoices.getLength()) } };
+ [pProto setTitle: @"RadioButtonGroup"];
+ [pProto setButtonType: NSButtonTypeRadio];
+ NSMatrix* pMatrix = [[NSMatrix alloc] initWithFrame: aRadioRect
+ mode: NSRadioModeMatrix
+ prototype: static_cast<NSCell*>(pProto)
+ numberOfRows: rChoices.getLength()
+ numberOfColumns: 1];
+ // set individual titles
+ NSArray* pCells = [pMatrix cells];
+ for( sal_Int32 m = 0; m < rChoices.getLength(); m++ )
+ {
+ NSCell* pCell = [pCells objectAtIndex: m];
+ linebreakCell( pCell, filterAccelerator( rChoices[m] ) );
+ // connect target and action
+ [pCell setTarget: pCtrlTarget];
+ [pCell setAction: @selector(triggered:)];
+ int nTag = pControllerProperties->addNameAndValueTag( rProperty, m );
+ pControllerProperties->addObservedControl( pCell );
+ [pCell setTag: nTag];
+ // set current selection
+ if( nSelectValue == m )
+ [pMatrix selectCellAtRow: m column: 0];
+ }
+ [pMatrix sizeToFit];
+ aRadioRect = [pMatrix frame];
+
+ // move it down, so it comes to the correct position
+ aRadioRect.origin.y = rCurY - aRadioRect.size.height;
+ [pMatrix setFrame: aRadioRect];
+ [pCurParent addSubview: [pMatrix autorelease]];
+
+ rRightColumn.push_back( ColumnItem( pMatrix ) );
+
+ // update nCurY
+ rCurY = aRadioRect.origin.y - 5;
+
+ [pProto release];
+}
+
+static void addList( NSView* pCurParent, CGFloat& rCurX, CGFloat& rCurY, CGFloat /*nAttachOffset*/,
+ const OUString& rText,
+ const OUString& rProperty, Sequence<OUString> const & rChoices, sal_Int32 nSelectValue,
+ std::vector<ColumnItem >& rLeftColumn,
+ std::vector<ColumnItem >& rRightColumn,
+ ControllerProperties* pControllerProperties,
+ ControlTarget* pCtrlTarget
+ )
+{
+ // don't indent attached lists, looks bad in the existing cases
+ NSControl* pTextView = createLabel( rText );
+ [pCurParent addSubview: [pTextView autorelease]];
+ rLeftColumn.push_back( ColumnItem( pTextView ) );
+ NSRect aTextRect = [pTextView frame];
+ aTextRect.origin.x = rCurX /* + nAttachOffset*/;
+
+ // don't indent attached lists, looks bad in the existing cases
+ NSRect aBtnRect = { { rCurX /*+ nAttachOffset*/ + aTextRect.size.width, 0 }, { 0, 15 } };
+ NSPopUpButton* pBtn = [[NSPopUpButton alloc] initWithFrame: aBtnRect pullsDown: NO];
+
+ // iterate options
+ for( sal_Int32 m = 0; m < rChoices.getLength(); m++ )
+ {
+ NSString* pItemText = CreateNSString( rChoices[m] );
+ [pBtn addItemWithTitle: pItemText];
+ NSMenuItem* pItem = [pBtn itemWithTitle: pItemText];
+ int nTag = pControllerProperties->addNameAndValueTag( rProperty, m );
+ [pItem setTag: nTag];
+ [pItemText release];
+ }
+
+ [pBtn selectItemAtIndex: nSelectValue];
+
+ // add the button to observed controls for enabled state changes
+ // also add a tag just for this purpose
+ pControllerProperties->addObservedControl( pBtn );
+ [pBtn setTag: pControllerProperties->addNameTag( rProperty )];
+
+ [pBtn sizeToFit];
+ [pCurParent addSubview: [pBtn autorelease]];
+
+ rRightColumn.push_back( ColumnItem( pBtn ) );
+
+ // connect target and action
+ [pBtn setTarget: pCtrlTarget];
+ [pBtn setAction: @selector(triggered:)];
+
+ // move to nCurY
+ aBtnRect = [pBtn frame];
+ aBtnRect.origin.y = rCurY - aBtnRect.size.height;
+ [pBtn setFrame: aBtnRect];
+
+ // align label
+ aTextRect.origin.y = aBtnRect.origin.y + (aBtnRect.size.height - aTextRect.size.height)/2;
+ [pTextView setFrame: aTextRect];
+
+ // update rCurY
+ rCurY = aBtnRect.origin.y - 5;
+}
+
+static void addEdit( NSView* pCurParent, CGFloat rCurX, CGFloat& rCurY, CGFloat nAttachOffset,
+ std::u16string_view rCtrlType,
+ const OUString& rText,
+ const OUString& rProperty, const PropertyValue* pValue,
+ sal_Int64 nMinValue, sal_Int64 nMaxValue,
+ std::vector<ColumnItem >& rLeftColumn,
+ std::vector<ColumnItem >& rRightColumn,
+ ControllerProperties* pControllerProperties,
+ ControlTarget* pCtrlTarget
+ )
+{
+ CGFloat nOff = 0;
+ if( rText.getLength() )
+ {
+ // add a label
+ NSControl* pTextView = createLabel( rText );
+ [pCurParent addSubview: [pTextView autorelease]];
+
+ rLeftColumn.push_back( ColumnItem( pTextView ) );
+
+ // move to nCurY
+ NSRect aTextRect = [pTextView frame];
+ aTextRect.origin.x = rCurX + nAttachOffset;
+ aTextRect.origin.y = rCurY - aTextRect.size.height;
+ [pTextView setFrame: aTextRect];
+
+ // update nCurY
+ rCurY = aTextRect.origin.y - 5;
+
+ // and set the offset for the real edit field
+ nOff = aTextRect.size.width + 5;
+ }
+
+ NSRect aFieldRect = { { rCurX + nOff + nAttachOffset, 0 }, { 100, 25 } };
+ NSTextField* pFieldView = [[NSTextField alloc] initWithFrame: aFieldRect];
+ [pFieldView setEditable: YES];
+ [pFieldView setSelectable: YES];
+ [pFieldView setDrawsBackground: YES];
+ [pFieldView sizeToFit]; // FIXME: this does nothing
+ [pCurParent addSubview: [pFieldView autorelease]];
+
+ rRightColumn.push_back( ColumnItem( pFieldView ) );
+
+ // add the field to observed controls for enabled state changes
+ // also add a tag just for this purpose
+ pControllerProperties->addObservedControl( pFieldView );
+ int nTag = pControllerProperties->addNameTag( rProperty );
+ [pFieldView setTag: nTag];
+ // pControllerProperties->addNamedView( pFieldView, aPropertyName );
+
+ // move to nCurY
+ aFieldRect.origin.y = rCurY - aFieldRect.size.height;
+ [pFieldView setFrame: aFieldRect];
+
+ if( rCtrlType == u"Range" )
+ {
+ // add a stepper control
+ NSRect aStepFrame = { { aFieldRect.origin.x + aFieldRect.size.width + 5,
+ aFieldRect.origin.y },
+ { 15, aFieldRect.size.height } };
+ NSStepper* pStep = [[NSStepper alloc] initWithFrame: aStepFrame];
+ [pStep setIncrement: 1];
+ [pStep setValueWraps: NO];
+ [pStep setTag: nTag];
+ [pCurParent addSubview: [pStep autorelease]];
+
+ rRightColumn.back().pSubControl = pStep;
+
+ pControllerProperties->addObservedControl( pStep );
+ [pStep setTarget: pCtrlTarget];
+ [pStep setAction: @selector(triggered:)];
+
+ // constrain the text field to decimal numbers
+ NSNumberFormatter* pFormatter = [[NSNumberFormatter alloc] init];
+ [pFormatter setFormatterBehavior: NSNumberFormatterBehavior10_4];
+ [pFormatter setNumberStyle: NSNumberFormatterDecimalStyle];
+ [pFormatter setAllowsFloats: NO];
+ [pFormatter setMaximumFractionDigits: 0];
+ if( nMinValue != nMaxValue )
+ {
+ [pFormatter setMinimum: [[NSNumber numberWithInt: nMinValue] autorelease]];
+ [pStep setMinValue: nMinValue];
+ [pFormatter setMaximum: [[NSNumber numberWithInt: nMaxValue] autorelease]];
+ [pStep setMaxValue: nMaxValue];
+ }
+ [pFieldView setFormatter: pFormatter];
+
+ sal_Int64 nSelectVal = 0;
+ if( pValue && pValue->Value.hasValue() )
+ pValue->Value >>= nSelectVal;
+
+ [pFieldView setIntValue: nSelectVal];
+ [pStep setIntValue: nSelectVal];
+
+ pControllerProperties->addViewPair( pFieldView, pStep );
+ // connect target and action
+ [pFieldView setTarget: pCtrlTarget];
+ [pFieldView setAction: @selector(triggeredNumeric:)];
+ [pStep setTarget: pCtrlTarget];
+ [pStep setAction: @selector(triggeredNumeric:)];
+ }
+ else
+ {
+ // connect target and action
+ [pFieldView setTarget: pCtrlTarget];
+ [pFieldView setAction: @selector(triggered:)];
+
+ if( pValue && pValue->Value.hasValue() )
+ {
+ OUString aValue;
+ pValue->Value >>= aValue;
+ if( aValue.getLength() )
+ {
+ NSString* pText = CreateNSString( aValue );
+ [pFieldView setStringValue: pText];
+ [pText release];
+ }
+ }
+ }
+
+ // update nCurY
+ rCurY = aFieldRect.origin.y - 5;
+}
+
+@implementation AquaPrintAccessoryView
+
++(NSObject*)setupPrinterPanel: (NSPrintOperation*)pOp
+ withController: (vcl::PrinterController*)pController
+ withState: (PrintAccessoryViewState*)pState
+{
+ const Sequence< PropertyValue >& rOptions( pController->getUIOptions() );
+ if( rOptions.getLength() == 0 )
+ return nil;
+
+ NSRect aViewFrame = { NSZeroPoint, { 600, 400 } };
+ NSRect aTabViewFrame = aViewFrame;
+
+ NSView* pAccessoryView = [[NSView alloc] initWithFrame: aViewFrame];
+ NSTabView* pTabView = [[NSTabView alloc] initWithFrame: aTabViewFrame];
+ [pAccessoryView addSubview: [pTabView autorelease]];
+
+ // create the accessory controller
+ AquaPrintPanelAccessoryController* pAccessoryController =
+ [[AquaPrintPanelAccessoryController alloc] initWithNibName: nil bundle: nil];
+ [pAccessoryController setView: [pAccessoryView autorelease]];
+ [pAccessoryController forPrintOperation: pOp];
+ [pAccessoryController withPrinterController: pController];
+ [pAccessoryController withViewState: pState];
+
+ NSView* pCurParent = nullptr;
+ CGFloat nCurY = 0;
+ CGFloat nCurX = 0;
+ NSSize aMaxTabSize = NSZeroSize;
+
+ ControllerProperties* pControllerProperties = new ControllerProperties( pAccessoryController );
+ ControlTarget* pCtrlTarget = [[ControlTarget alloc] initWithControllerMap: pControllerProperties];
+
+ std::vector< ColumnItem > aLeftColumn, aRightColumn;
+
+ // ugly:
+ // prepend a "selection" checkbox if the properties have such a selection in PrintContent
+ bool bAddSelectionCheckBox = false, bSelectionBoxEnabled = false, bSelectionBoxChecked = false;
+
+ for( const PropertyValue & prop : rOptions )
+ {
+ Sequence< beans::PropertyValue > aOptProp;
+ prop.Value >>= aOptProp;
+
+ OUString aCtrlType;
+ OUString aPropertyName;
+ Sequence< OUString > aChoices;
+ Sequence< sal_Bool > aChoicesDisabled;
+ sal_Int32 aSelectionChecked = 0;
+ for( const beans::PropertyValue& rEntry : std::as_const(aOptProp) )
+ {
+ 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;
+ if( aPropertyName == "PrintContent" )
+ aVal.Value >>= aSelectionChecked;
+ }
+ }
+ if( aCtrlType == "Radio" &&
+ aPropertyName == "PrintContent" &&
+ aChoices.getLength() > 2 )
+ {
+ bAddSelectionCheckBox = true;
+ bSelectionBoxEnabled = aChoicesDisabled.getLength() < 2 || ! aChoicesDisabled[2];
+ bSelectionBoxChecked = (aSelectionChecked==2);
+ break;
+ }
+ }
+
+ for( const PropertyValue & prop : rOptions )
+ {
+ Sequence< beans::PropertyValue > aOptProp;
+ prop.Value >>= aOptProp;
+
+ // extract ui element
+ OUString aCtrlType;
+ OUString aText;
+ OUString aPropertyName;
+ OUString aGroupHint;
+ Sequence< OUString > aChoices;
+ sal_Int64 nMinValue = 0, nMaxValue = 0;
+ CGFloat nAttachOffset = 0;
+ bool bIgnore = false;
+
+ for( const beans::PropertyValue& rEntry : std::as_const(aOptProp) )
+ {
+ if( rEntry.Name == "Text" )
+ {
+ rEntry.Value >>= aText;
+ aText = filterAccelerator( aText );
+ }
+ else if( rEntry.Name == "ControlType" )
+ {
+ rEntry.Value >>= aCtrlType;
+ }
+ else if( rEntry.Name == "Choices" )
+ {
+ rEntry.Value >>= aChoices;
+ }
+ else if( rEntry.Name == "Property" )
+ {
+ PropertyValue aVal;
+ rEntry.Value >>= aVal;
+ aPropertyName = aVal.Name;
+ }
+ else if( rEntry.Name == "MinValue" )
+ {
+ rEntry.Value >>= nMinValue;
+ }
+ else if( rEntry.Name == "MaxValue" )
+ {
+ rEntry.Value >>= nMaxValue;
+ }
+ else if( rEntry.Name == "AttachToDependency" )
+ {
+ nAttachOffset = 20;
+ }
+ else if( rEntry.Name == "InternalUIOnly" )
+ {
+ bool bValue = false;
+ rEntry.Value >>= bValue;
+ bIgnore = bValue;
+ }
+ else if( rEntry.Name == "GroupingHint" )
+ {
+ rEntry.Value >>= aGroupHint;
+ }
+ }
+
+ if( aCtrlType == "Group" ||
+ aCtrlType == "Subgroup" ||
+ aCtrlType == "Radio" ||
+ aCtrlType == "List" ||
+ aCtrlType == "Edit" ||
+ aCtrlType == "Range" ||
+ aCtrlType == "Bool" )
+ {
+ bool bIgnoreSubgroup = false;
+
+ // with `setAccessoryView' method only one accessory view can be set
+ // so create this single accessory view as tabbed for grouping
+ if( aCtrlType == "Group"
+ || ! pCurParent
+ || ( aCtrlType == "Subgroup" && nCurY < -250 && ! bIgnore )
+ )
+ {
+ OUString aGroupTitle( aText );
+ if( aCtrlType == "Subgroup" )
+ aGroupTitle = ControllerProperties::getMoreString();
+
+ // set size of current parent
+ if( pCurParent )
+ adjustViewAndChildren( pCurParent, aMaxTabSize, aLeftColumn, aRightColumn );
+
+ // new tab item
+ if( ! aText.getLength() )
+ aText = "OOo";
+ NSString* pLabel = CreateNSString( aGroupTitle );
+ NSTabViewItem* pItem = [[NSTabViewItem alloc] initWithIdentifier: pLabel ];
+ [pItem setLabel: pLabel];
+ [pTabView addTabViewItem: pItem];
+ pCurParent = [[NSView alloc] initWithFrame: aTabViewFrame];
+ [pItem setView: pCurParent];
+ [pLabel release];
+
+ nCurX = 20; // reset indent
+ nCurY = 0; // reset Y
+ // clear columns
+ aLeftColumn.clear();
+ aRightColumn.clear();
+
+ if( bAddSelectionCheckBox )
+ {
+ addBool( pCurParent, nCurX, nCurY, 0,
+ ControllerProperties::getPrintSelectionString(), bSelectionBoxEnabled,
+ "PrintContent", bSelectionBoxChecked,
+ aRightColumn, pControllerProperties, pCtrlTarget );
+ bAddSelectionCheckBox = false;
+ }
+ }
+
+ if( aCtrlType == "Subgroup" && pCurParent )
+ {
+ bIgnoreSubgroup = bIgnore;
+ if( bIgnore )
+ continue;
+
+ addSubgroup( pCurParent, nCurY, aText );
+ }
+ else if( bIgnoreSubgroup || bIgnore )
+ {
+ continue;
+ }
+ else if( aCtrlType == "Bool" && pCurParent )
+ {
+ bool bVal = false;
+ PropertyValue* pVal = pController->getValue( aPropertyName );
+ if( pVal )
+ pVal->Value >>= bVal;
+ addBool( pCurParent, nCurX, nCurY, nAttachOffset,
+ aText, true, aPropertyName, bVal,
+ aRightColumn, pControllerProperties, pCtrlTarget );
+ }
+ else if( aCtrlType == "Radio" && pCurParent )
+ {
+ // get currently selected value
+ sal_Int32 nSelectVal = 0;
+ PropertyValue* pVal = pController->getValue( aPropertyName );
+ if( pVal && pVal->Value.hasValue() )
+ pVal->Value >>= nSelectVal;
+
+ addRadio( pCurParent, nCurX, nCurY, nAttachOffset,
+ aText, aPropertyName, aChoices, nSelectVal,
+ aLeftColumn, aRightColumn,
+ pControllerProperties, pCtrlTarget );
+ }
+ else if( aCtrlType == "List" && pCurParent )
+ {
+ PropertyValue* pVal = pController->getValue( aPropertyName );
+ sal_Int32 aSelectVal = 0;
+ if( pVal && pVal->Value.hasValue() )
+ pVal->Value >>= aSelectVal;
+
+ addList( pCurParent, nCurX, nCurY, nAttachOffset,
+ aText, aPropertyName, aChoices, aSelectVal,
+ aLeftColumn, aRightColumn,
+ pControllerProperties, pCtrlTarget );
+ }
+ else if( (aCtrlType == "Edit"
+ || aCtrlType == "Range") && pCurParent )
+ {
+ // current value
+ PropertyValue* pVal = pController->getValue( aPropertyName );
+ addEdit( pCurParent, nCurX, nCurY, nAttachOffset,
+ aCtrlType, aText, aPropertyName, pVal,
+ nMinValue, nMaxValue,
+ aLeftColumn, aRightColumn,
+ pControllerProperties, pCtrlTarget );
+ }
+ }
+ else
+ {
+ SAL_INFO( "vcl.osx.print", "Unsupported UI option \"" << aCtrlType << "\"");
+ }
+ }
+
+ pControllerProperties->updateEnableState();
+ adjustViewAndChildren( pCurParent, aMaxTabSize, aLeftColumn, aRightColumn );
+
+ // now reposition everything again so it is upper bound
+ adjustTabViews( pTabView, aMaxTabSize );
+
+ // find the minimum needed tab size
+ NSSize aTabCtrlSize = [pTabView minimumSize];
+ aTabCtrlSize.height += aMaxTabSize.height + 10;
+ if( aTabCtrlSize.width < aMaxTabSize.width + 10 )
+ aTabCtrlSize.width = aMaxTabSize.width + 10;
+ [pTabView setFrameSize: aTabCtrlSize];
+ aViewFrame.size.width = aTabCtrlSize.width + aTabViewFrame.origin.x;
+ aViewFrame.size.height = aTabCtrlSize.height + aTabViewFrame.origin.y;
+ [pAccessoryView setFrameSize: aViewFrame.size];
+
+ // get the print panel
+ NSPrintPanel* pPrintPanel = [pOp printPanel];
+ [pPrintPanel setOptions: [pPrintPanel options] | NSPrintPanelShowsPreview];
+ // add the accessory controller to the panel
+ [pPrintPanel addAccessoryController: [pAccessoryController autorelease]];
+
+ // set the current selected tab item
+ if( pState->nLastPage >= 0 && pState->nLastPage < [pTabView numberOfTabViewItems] )
+ [pTabView selectTabViewItemAtIndex: pState->nLastPage];
+
+ return pCtrlTarget;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/printview.mm b/vcl/osx/printview.mm
new file mode 100644
index 0000000000..b54e1b0561
--- /dev/null
+++ b/vcl/osx/printview.mm
@@ -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/print.hxx>
+
+#include <osx/printview.h>
+#include <osx/salprn.h>
+
+@implementation AquaPrintView
+
+-(id)initWithController: (vcl::PrinterController*)pController
+ withInfoPrinter: (AquaSalInfoPrinter*)pInfoPrinter
+{
+ NSRect aRect = { NSZeroPoint, [pInfoPrinter->getPrintInfo() paperSize] };
+ if( (self = [super initWithFrame: aRect]) != nil )
+ {
+ mpController = pController;
+ mpInfoPrinter = pInfoPrinter;
+ }
+ return self;
+}
+
+-(BOOL)knowsPageRange: (NSRangePointer)range
+{
+ range->location = 1;
+ range->length = mpInfoPrinter->getCurPageRangeCount();
+ return YES;
+}
+
+-(NSRect)rectForPage: (int)page
+{
+ NSSize aPaperSize = [mpInfoPrinter->getPrintInfo() paperSize];
+ int nWidth = static_cast<int>(aPaperSize.width);
+ // #i101108# sanity check
+ if( nWidth < 1 )
+ nWidth = 1;
+ NSRect aRect = { { static_cast<CGFloat>(page % nWidth),
+ static_cast<CGFloat>(page / nWidth) },
+ aPaperSize };
+ return aRect;
+}
+
+-(NSPoint)locationOfPrintRect: (NSRect)aRect
+{
+ (void)aRect;
+ return NSZeroPoint;
+}
+
+-(void)drawRect: (NSRect)rect
+{
+ mpInfoPrinter->setStartPageOffset( static_cast<int>(rect.origin.x),
+ static_cast<int>(rect.origin.y) );
+ NSSize aPaperSize = [mpInfoPrinter->getPrintInfo() paperSize];
+ int nPage = static_cast<int>(aPaperSize.width * rect.origin.y + rect.origin.x);
+
+ // page count is 1 based
+ if( nPage - 1 < (mpInfoPrinter->getCurPageRangeStart() + mpInfoPrinter->getCurPageRangeCount() ) )
+ mpController->printFilteredPage( nPage-1 );
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/res/MainMenu.nib/classes.nib b/vcl/osx/res/MainMenu.nib/classes.nib
new file mode 100644
index 0000000000..b9b4b09f6b
--- /dev/null
+++ b/vcl/osx/res/MainMenu.nib/classes.nib
@@ -0,0 +1,4 @@
+{
+ IBClasses = ({CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; });
+ IBVersion = 1;
+} \ No newline at end of file
diff --git a/vcl/osx/res/MainMenu.nib/info.nib b/vcl/osx/res/MainMenu.nib/info.nib
new file mode 100644
index 0000000000..856429aee5
--- /dev/null
+++ b/vcl/osx/res/MainMenu.nib/info.nib
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBDocumentLocation</key>
+ <string>135 107 356 240 0 0 1680 1028 </string>
+ <key>IBEditorPositions</key>
+ <dict>
+ <key>29</key>
+ <string>132 352 141 44 0 0 1680 1028 </string>
+ </dict>
+ <key>IBFramework Version</key>
+ <string>446.1</string>
+ <key>IBOpenObjects</key>
+ <array>
+ <integer>29</integer>
+ </array>
+ <key>IBSystem Version</key>
+ <string>8R2218</string>
+</dict>
+</plist>
diff --git a/vcl/osx/res/MainMenu.nib/keyedobjects.nib b/vcl/osx/res/MainMenu.nib/keyedobjects.nib
new file mode 100644
index 0000000000..d39d10119c
--- /dev/null
+++ b/vcl/osx/res/MainMenu.nib/keyedobjects.nib
Binary files differ
diff --git a/vcl/osx/saldata.cxx b/vcl/osx/saldata.cxx
new file mode 100644
index 0000000000..1f49d1ef1e
--- /dev/null
+++ b/vcl/osx/saldata.cxx
@@ -0,0 +1,281 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <osx/saldata.hxx>
+#include <osx/salnsmenu.h>
+#include <osx/salinst.h>
+#include <o3tl/enumarray.hxx>
+#include <tools/stream.hxx>
+#include <vcl/ImageTree.hxx>
+#include <vcl/settings.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <sal/log.hxx>
+#include <bitmaps.hlst>
+#include <cursor_hotspots.hxx>
+#include <quartz/salgdi.h>
+#include <quartz/SystemFontList.hxx>
+
+#import "apple_remote/RemoteMainController.h"
+
+oslThreadKey SalData::s_aAutoReleaseKey = nullptr;
+
+static void releasePool( void* pPool )
+{
+ if( pPool )
+ [static_cast<NSAutoreleasePool*>(pPool) release];
+}
+
+SalData::SalData()
+:
+ mpTimerProc( nullptr ),
+ mpInstance( nullptr ),
+ mpFirstObject( nullptr ),
+ mpFirstVD( nullptr ),
+ mpFirstPrinter( nullptr ),
+ mpStatusItem( nil ),
+ mxRGBSpace( CGColorSpaceCreateWithName(kCGColorSpaceSRGB) ),
+ mxGraySpace( CGColorSpaceCreateWithName(kCGColorSpaceGenericGrayGamma2_2) ),
+ maCursors(),
+ mbIsScrollbarDoubleMax( false ),
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+ mpAppleRemoteMainController( nullptr ),
+#endif
+ mpDockIconClickHandler( nil ),
+ mnDPIX( 0 ),
+ mnDPIY( 0 )
+{
+ SetSalData(this);
+ maCursors.fill( INVALID_CURSOR_PTR );
+ if( s_aAutoReleaseKey == nullptr )
+ s_aAutoReleaseKey = osl_createThreadKey( releasePool );
+}
+
+SalData::~SalData()
+{
+ CGColorSpaceRelease( mxRGBSpace );
+ CGColorSpaceRelease( mxGraySpace );
+ for( NSCursor* pCurs : maCursors )
+ {
+ if( pCurs && pCurs != INVALID_CURSOR_PTR )
+ [pCurs release];
+ }
+ if( s_aAutoReleaseKey )
+ {
+ // release the last pool
+ NSAutoreleasePool* pPool = reinterpret_cast<NSAutoreleasePool*>( osl_getThreadKeyData( s_aAutoReleaseKey ) );
+ if( pPool )
+ {
+ osl_setThreadKeyData( s_aAutoReleaseKey, nullptr );
+ [pPool release];
+ }
+
+ osl_destroyThreadKey( s_aAutoReleaseKey );
+ s_aAutoReleaseKey = nullptr;
+ }
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+ if ( mpAppleRemoteMainController )
+ [mpAppleRemoteMainController release];
+#endif
+
+ if( mpStatusItem )
+ {
+ [mpStatusItem release];
+ mpStatusItem = nil;
+ }
+ SetSalData( nullptr );
+}
+
+void SalData::ensureThreadAutoreleasePool()
+{
+ NSAutoreleasePool* pPool = nil;
+ if( s_aAutoReleaseKey )
+ {
+ pPool = reinterpret_cast<NSAutoreleasePool*>( osl_getThreadKeyData( s_aAutoReleaseKey ) );
+ if( ! pPool )
+ {
+ pPool = [[NSAutoreleasePool alloc] init];
+ osl_setThreadKeyData( s_aAutoReleaseKey, pPool );
+ }
+ }
+ else
+ {
+ OSL_FAIL( "no autorelease key" );
+ }
+}
+
+namespace {
+
+NSImage* load_icon_by_name(const OUString& rIconName)
+{
+ OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ OUString sUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
+ auto xMemStm = ImageTree::get().getImageStream(rIconName, sIconTheme, sUILang);
+ if (!xMemStm)
+ return nullptr;
+
+ auto data = xMemStm->GetData();
+ auto length = xMemStm->TellEnd();
+ NSData * byteData = [NSData dataWithBytes:data length:length];
+ NSBitmapImageRep * imageRep = [NSBitmapImageRep imageRepWithData:byteData];
+
+ NSImage * image = [[NSImage alloc] initWithSize:imageRep.size];
+ [image addRepresentation:imageRep];
+ return image;
+}
+
+}
+
+#define MAKE_CURSOR( vcl_name, name, name2 ) \
+ case vcl_name: \
+ aHotSpot = NSPoint{name##curs_x_hot, name##curs_y_hot}; \
+ aIconName = name2; \
+ break
+
+NSCursor* SalData::getCursor( PointerStyle i_eStyle )
+{
+ NSCursor* pCurs = maCursors[ i_eStyle ];
+ if( pCurs != INVALID_CURSOR_PTR )
+ return pCurs;
+
+ NSPoint aHotSpot;
+ OUString aIconName;
+
+ switch( i_eStyle )
+ {
+ // TODO
+ MAKE_CURSOR( PointerStyle::Wait, wait_, RID_CURSOR_WAIT );
+ MAKE_CURSOR( PointerStyle::NWSize, nwsize_, RID_CURSOR_NWSIZE );
+ MAKE_CURSOR( PointerStyle::NESize, nesize_, RID_CURSOR_NESIZE );
+ MAKE_CURSOR( PointerStyle::SWSize, swsize_, RID_CURSOR_SWSIZE );
+ MAKE_CURSOR( PointerStyle::SESize, sesize_, RID_CURSOR_SESIZE );
+ MAKE_CURSOR( PointerStyle::WindowNWSize, window_nwsize_, RID_CURSOR_WINDOW_NWSIZE );
+ MAKE_CURSOR( PointerStyle::WindowNESize, window_nesize_, RID_CURSOR_WINDOW_NESIZE );
+ MAKE_CURSOR( PointerStyle::WindowSWSize, window_swsize_, RID_CURSOR_WINDOW_SWSIZE );
+ MAKE_CURSOR( PointerStyle::WindowSESize, window_sesize_, RID_CURSOR_WINDOW_SESIZE );
+
+ MAKE_CURSOR( PointerStyle::Help, help_, RID_CURSOR_HELP );
+ MAKE_CURSOR( PointerStyle::Pen, pen_, RID_CURSOR_PEN );
+ MAKE_CURSOR( PointerStyle::Null, null, RID_CURSOR_NULL );
+ MAKE_CURSOR( PointerStyle::Magnify, magnify_, RID_CURSOR_MAGNIFY );
+ MAKE_CURSOR( PointerStyle::Fill, fill_, RID_CURSOR_FILL );
+ MAKE_CURSOR( PointerStyle::MoveData, movedata_, RID_CURSOR_MOVE_DATA );
+ MAKE_CURSOR( PointerStyle::CopyData, copydata_, RID_CURSOR_COPY_DATA );
+ MAKE_CURSOR( PointerStyle::MoveFile, movefile_, RID_CURSOR_MOVE_FILE );
+ MAKE_CURSOR( PointerStyle::CopyFile, copyfile_, RID_CURSOR_COPY_FILE );
+ MAKE_CURSOR( PointerStyle::MoveFiles, movefiles_, RID_CURSOR_MOVE_FILES );
+ MAKE_CURSOR( PointerStyle::CopyFiles, copyfiles_, RID_CURSOR_COPY_FILES );
+ MAKE_CURSOR( PointerStyle::NotAllowed, nodrop_, RID_CURSOR_NOT_ALLOWED );
+ MAKE_CURSOR( PointerStyle::Rotate, rotate_, RID_CURSOR_ROTATE );
+ MAKE_CURSOR( PointerStyle::HShear, hshear_, RID_CURSOR_H_SHEAR );
+ MAKE_CURSOR( PointerStyle::VShear, vshear_, RID_CURSOR_V_SHEAR );
+ MAKE_CURSOR( PointerStyle::DrawLine, drawline_, RID_CURSOR_DRAW_LINE );
+ MAKE_CURSOR( PointerStyle::DrawRect, drawrect_, RID_CURSOR_DRAW_RECT );
+ MAKE_CURSOR( PointerStyle::DrawPolygon, drawpolygon_, RID_CURSOR_DRAW_POLYGON );
+ MAKE_CURSOR( PointerStyle::DrawBezier, drawbezier_, RID_CURSOR_DRAW_BEZIER );
+ MAKE_CURSOR( PointerStyle::DrawArc, drawarc_, RID_CURSOR_DRAW_ARC );
+ MAKE_CURSOR( PointerStyle::DrawPie, drawpie_, RID_CURSOR_DRAW_PIE );
+ MAKE_CURSOR( PointerStyle::DrawCircleCut, drawcirclecut_, RID_CURSOR_DRAW_CIRCLE_CUT );
+ MAKE_CURSOR( PointerStyle::DrawEllipse, drawellipse_, RID_CURSOR_DRAW_ELLIPSE );
+ MAKE_CURSOR( PointerStyle::DrawConnect, drawconnect_, RID_CURSOR_DRAW_CONNECT );
+ MAKE_CURSOR( PointerStyle::DrawText, drawtext_, RID_CURSOR_DRAW_TEXT );
+ MAKE_CURSOR( PointerStyle::Mirror, mirror_, RID_CURSOR_MIRROR );
+ MAKE_CURSOR( PointerStyle::Crook, crook_, RID_CURSOR_CROOK );
+ MAKE_CURSOR( PointerStyle::Crop, crop_, RID_CURSOR_CROP );
+ MAKE_CURSOR( PointerStyle::MovePoint, movepoint_, RID_CURSOR_MOVE_POINT );
+ MAKE_CURSOR( PointerStyle::MoveBezierWeight, movebezierweight_, RID_CURSOR_MOVE_BEZIER_WEIGHT );
+ MAKE_CURSOR( PointerStyle::DrawFreehand, drawfreehand_, RID_CURSOR_DRAW_FREEHAND );
+ MAKE_CURSOR( PointerStyle::DrawCaption, drawcaption_, RID_CURSOR_DRAW_CAPTION );
+ MAKE_CURSOR( PointerStyle::LinkData, linkdata_, RID_CURSOR_LINK_DATA );
+ MAKE_CURSOR( PointerStyle::MoveDataLink, movedlnk_, RID_CURSOR_MOVE_DATA_LINK );
+ MAKE_CURSOR( PointerStyle::CopyDataLink, copydlnk_, RID_CURSOR_COPY_DATA_LINK );
+ MAKE_CURSOR( PointerStyle::LinkFile, linkfile_, RID_CURSOR_LINK_FILE );
+ MAKE_CURSOR( PointerStyle::MoveFileLink, moveflnk_, RID_CURSOR_MOVE_FILE_LINK );
+ MAKE_CURSOR( PointerStyle::CopyFileLink, copyflnk_, RID_CURSOR_COPY_FILE_LINK );
+ MAKE_CURSOR( PointerStyle::Chart, chart_, RID_CURSOR_CHART );
+ MAKE_CURSOR( PointerStyle::Detective, detective_, RID_CURSOR_DETECTIVE );
+ MAKE_CURSOR( PointerStyle::PivotCol, pivotcol_, RID_CURSOR_PIVOT_COLUMN );
+ MAKE_CURSOR( PointerStyle::PivotRow, pivotrow_, RID_CURSOR_PIVOT_ROW );
+ MAKE_CURSOR( PointerStyle::PivotField, pivotfld_, RID_CURSOR_PIVOT_FIELD );
+ MAKE_CURSOR( PointerStyle::PivotDelete, pivotdel_, RID_CURSOR_PIVOT_DELETE );
+ MAKE_CURSOR( PointerStyle::Chain, chain_, RID_CURSOR_CHAIN );
+ MAKE_CURSOR( PointerStyle::ChainNotAllowed, chainnot_, RID_CURSOR_CHAIN_NOT_ALLOWED );
+ MAKE_CURSOR( PointerStyle::AutoScrollN, asn_, RID_CURSOR_AUTOSCROLL_N );
+ MAKE_CURSOR( PointerStyle::AutoScrollS, ass_, RID_CURSOR_AUTOSCROLL_S );
+ MAKE_CURSOR( PointerStyle::AutoScrollW, asw_, RID_CURSOR_AUTOSCROLL_W );
+ MAKE_CURSOR( PointerStyle::AutoScrollE, ase_, RID_CURSOR_AUTOSCROLL_E );
+ MAKE_CURSOR( PointerStyle::AutoScrollNW, asnw_, RID_CURSOR_AUTOSCROLL_NW );
+ MAKE_CURSOR( PointerStyle::AutoScrollNE, asne_, RID_CURSOR_AUTOSCROLL_NE );
+ MAKE_CURSOR( PointerStyle::AutoScrollSW, assw_, RID_CURSOR_AUTOSCROLL_SW );
+ MAKE_CURSOR( PointerStyle::AutoScrollSE, asse_, RID_CURSOR_AUTOSCROLL_SE );
+ MAKE_CURSOR( PointerStyle::AutoScrollNS, asns_, RID_CURSOR_AUTOSCROLL_NS );
+ MAKE_CURSOR( PointerStyle::AutoScrollWE, aswe_, RID_CURSOR_AUTOSCROLL_WE );
+ MAKE_CURSOR( PointerStyle::AutoScrollNSWE, asnswe_, RID_CURSOR_AUTOSCROLL_NSWE );
+ MAKE_CURSOR( PointerStyle::TextVertical, vertcurs_, RID_CURSOR_TEXT_VERTICAL );
+
+ // #i32329#
+ MAKE_CURSOR( PointerStyle::TabSelectS, tblsels_, RID_CURSOR_TAB_SELECT_S );
+ MAKE_CURSOR( PointerStyle::TabSelectE, tblsele_, RID_CURSOR_TAB_SELECT_E );
+ MAKE_CURSOR( PointerStyle::TabSelectSE, tblselse_, RID_CURSOR_TAB_SELECT_SE );
+ MAKE_CURSOR( PointerStyle::TabSelectW, tblselw_, RID_CURSOR_TAB_SELECT_W );
+ MAKE_CURSOR( PointerStyle::TabSelectSW, tblselsw_, RID_CURSOR_TAB_SELECT_SW );
+
+ MAKE_CURSOR( PointerStyle::HideWhitespace, hidewhitespace_, RID_CURSOR_HIDE_WHITESPACE );
+ MAKE_CURSOR( PointerStyle::ShowWhitespace, showwhitespace_, RID_CURSOR_SHOW_WHITESPACE );
+
+ MAKE_CURSOR( PointerStyle::FatCross, fatcross_, RID_CURSOR_FATCROSS );
+
+ default:
+ SAL_WARN( "vcl", "pointer style " << static_cast<sal_Int32>(i_eStyle) << "not implemented" );
+ assert( false && "pointer style not implemented" );
+ break;
+ }
+
+ NSImage* theImage = load_icon_by_name(aIconName);
+ pCurs = [[NSCursor alloc] initWithImage: theImage hotSpot: aHotSpot];
+
+ maCursors[ i_eStyle ] = pCurs;
+ return pCurs;
+}
+
+NSStatusItem* SalData::getStatusItem()
+{
+ SalData* pData = GetSalData();
+ if( ! pData->mpStatusItem )
+ {
+ NSStatusBar* pStatBar =[NSStatusBar systemStatusBar];
+ if( pStatBar )
+ {
+ pData->mpStatusItem = [pStatBar statusItemWithLength: NSVariableStatusItemLength];
+ [pData->mpStatusItem retain];
+ OOStatusItemView* pView = [[OOStatusItemView alloc] init];
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'setView:' is deprecated: first deprecated in macOS 10.14 - Use the standard
+ // button property instead"
+ [pData->mpStatusItem setView: pView ];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ [pView display];
+ }
+ }
+ return pData->mpStatusItem;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salframe.cxx b/vcl/osx/salframe.cxx
new file mode 100644
index 0000000000..b02dbc5958
--- /dev/null
+++ b/vcl/osx/salframe.cxx
@@ -0,0 +1,2027 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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>
+
+#include <comphelper/fileurl.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/long.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+
+#include <osl/file.h>
+
+#include <vcl/event.hxx>
+#include <vcl/inputctx.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/settings.hxx>
+
+#include <osx/saldata.hxx>
+#include <quartz/salgdi.h>
+#include <osx/salframe.h>
+#include <osx/salmenu.h>
+#include <osx/salinst.h>
+#include <osx/salframeview.h>
+#include <osx/a11yfactory.h>
+#include <osx/runinmain.hxx>
+#include <quartz/utils.h>
+
+#include <salwtype.hxx>
+
+#include <premac.h>
+#include <objc/objc-runtime.h>
+// needed for theming
+// FIXME: move theming code to salnativewidgets.cxx
+#include <Carbon/Carbon.h>
+#include <quartz/CGHelpers.hxx>
+#include <postmac.h>
+
+
+const int nMinBlinkCursorDelay = 500;
+
+AquaSalFrame* AquaSalFrame::s_pCaptureFrame = nullptr;
+
+AquaSalFrame::AquaSalFrame( SalFrame* pParent, SalFrameStyleFlags salFrameStyle ) :
+ mpNSWindow(nil),
+ mpNSView(nil),
+ mpDockMenuEntry(nil),
+ mpGraphics(nullptr),
+ mpParent(nullptr),
+ mnMinWidth(0),
+ mnMinHeight(0),
+ mnMaxWidth(0),
+ mnMaxHeight(0),
+ mbGraphics(false),
+ mbFullScreen( false ),
+ mbShown(false),
+ mbInitShow(true),
+ mbPositioned(false),
+ mbSized(false),
+ mbPresentation( false ),
+ mnStyle( salFrameStyle ),
+ mnStyleMask( 0 ),
+ mnLastEventTime( 0 ),
+ mnLastModifierFlags( 0 ),
+ mpMenu( nullptr ),
+ mnExtStyle( 0 ),
+ mePointerStyle( PointerStyle::Arrow ),
+ mrClippingPath( nullptr ),
+ mnICOptions( InputContextFlags::NONE ),
+ mnBlinkCursorDelay( nMinBlinkCursorDelay ),
+ mbForceFlush( false )
+{
+ mpParent = dynamic_cast<AquaSalFrame*>(pParent);
+
+ initWindowAndView();
+
+ SalData* pSalData = GetSalData();
+ pSalData->mpInstance->insertFrame( this );
+ NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
+
+ // tdf#150177 Limit minimum blink cursor rate
+ // This bug occurs when the values for NSTextInsertionPointBlinkPeriodOn or
+ // NSTextInsertionPointBlinkPeriodOff are set to zero or close to zero.
+ // LibreOffice becomes very sluggish opening documents when either is set
+ // at 100 milliseconds or less so set the blink rate to the maximum of
+ // nMinBlinkCursorDelay, NSTextInsertionPointBlinkPeriodOn, and
+ // NSTextInsertionPointBlinkPeriodOff.
+ mnBlinkCursorDelay = nMinBlinkCursorDelay;
+ if (userDefaults != nil)
+ {
+ id setting = [userDefaults objectForKey: @"NSTextInsertionPointBlinkPeriodOn"];
+ if (setting && [setting isKindOfClass:[NSNumber class]])
+ mnBlinkCursorDelay = std::max(mnBlinkCursorDelay, [setting intValue]);
+
+ setting = [userDefaults objectForKey: @"NSTextInsertionPointBlinkPeriodOff"];
+ if (setting && [setting isKindOfClass:[NSNumber class]])
+ mnBlinkCursorDelay = std::max(mnBlinkCursorDelay, [setting intValue]);
+ }
+}
+
+AquaSalFrame::~AquaSalFrame()
+{
+ if (mbFullScreen)
+ doShowFullScreen(false, maGeometry.screen());
+
+ assert( GetSalData()->mpInstance->IsMainThread() );
+
+ // if the frame is destroyed and has the current menubar
+ // set the default menubar
+ if( mpMenu && mpMenu->mbMenuBar && AquaSalMenu::pCurrentMenuBar == mpMenu )
+ AquaSalMenu::setDefaultMenu();
+
+ // cleanup clipping stuff
+ doResetClipRegion();
+
+ [SalFrameView unsetMouseFrame: this];
+
+ SalData* pSalData = GetSalData();
+ pSalData->mpInstance->eraseFrame( this );
+ pSalData->maPresentationFrames.remove( this );
+
+ SAL_WARN_IF( this == s_pCaptureFrame, "vcl", "capture frame destroyed" );
+ if( this == s_pCaptureFrame )
+ s_pCaptureFrame = nullptr;
+
+ delete mpGraphics;
+
+ if( mpDockMenuEntry )
+ {
+ NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu();
+ // life cycle comment: the menu has ownership of the item, so no release
+ [pDock removeItem: mpDockMenuEntry];
+ if ([pDock numberOfItems] != 0
+ && [[pDock itemAtIndex: 0] isSeparatorItem])
+ {
+ [pDock removeItemAtIndex: 0];
+ }
+ }
+ if ( mpNSView ) {
+ if ([mpNSView isKindOfClass:[SalFrameView class]])
+ [static_cast<SalFrameView*>(mpNSView) revokeWrapper];
+ [mpNSView release];
+ }
+ if ( mpNSWindow )
+ [mpNSWindow release];
+}
+
+void AquaSalFrame::initWindowAndView()
+{
+ OSX_SALDATA_RUNINMAIN( initWindowAndView() )
+
+ // initialize mirroring parameters
+ // FIXME: screens changing
+ NSScreen* pNSScreen = [mpNSWindow screen];
+ if( pNSScreen == nil )
+ pNSScreen = [NSScreen mainScreen];
+ maScreenRect = [pNSScreen frame];
+
+ // calculate some default geometry
+ NSRect aVisibleRect = [pNSScreen visibleFrame];
+ CocoaToVCL( aVisibleRect );
+
+ maGeometry.setX(static_cast<sal_Int32>(aVisibleRect.origin.x + aVisibleRect.size.width / 10));
+ maGeometry.setY(static_cast<sal_Int32>(aVisibleRect.origin.y + aVisibleRect.size.height / 10));
+ maGeometry.setWidth(static_cast<sal_uInt32>(aVisibleRect.size.width * 0.8));
+ maGeometry.setHeight(static_cast<sal_uInt32>(aVisibleRect.size.height * 0.8));
+
+ // calculate style mask
+ if( (mnStyle & SalFrameStyleFlags::FLOAT) ||
+ (mnStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) )
+ mnStyleMask = NSWindowStyleMaskBorderless;
+ else if( mnStyle & SalFrameStyleFlags::DEFAULT )
+ {
+ mnStyleMask = NSWindowStyleMaskTitled |
+ NSWindowStyleMaskMiniaturizable |
+ NSWindowStyleMaskResizable |
+ NSWindowStyleMaskClosable;
+ // make default window "maximized"
+ maGeometry.setX(static_cast<sal_Int32>(aVisibleRect.origin.x));
+ maGeometry.setY(static_cast<sal_Int32>(aVisibleRect.origin.y));
+ maGeometry.setWidth(static_cast<sal_uInt32>(aVisibleRect.size.width));
+ maGeometry.setHeight(static_cast<sal_uInt32>(aVisibleRect.size.height));
+ mbPositioned = mbSized = true;
+ }
+ else
+ {
+ if( mnStyle & SalFrameStyleFlags::MOVEABLE )
+ {
+ mnStyleMask |= NSWindowStyleMaskTitled;
+ if( mpParent == nullptr )
+ mnStyleMask |= NSWindowStyleMaskMiniaturizable;
+ }
+ if( mnStyle & SalFrameStyleFlags::SIZEABLE )
+ mnStyleMask |= NSWindowStyleMaskResizable;
+ if( mnStyle & SalFrameStyleFlags::CLOSEABLE )
+ mnStyleMask |= NSWindowStyleMaskClosable;
+ // documentation says anything other than NSWindowStyleMaskBorderless (=0)
+ // should also include NSWindowStyleMaskTitled;
+ if( mnStyleMask != 0 )
+ mnStyleMask |= NSWindowStyleMaskTitled;
+ }
+
+ if (Application::IsBitmapRendering())
+ return;
+
+ // #i91990# support GUI-less (daemon) execution
+ @try
+ {
+ mpNSWindow = [[SalFrameWindow alloc] initWithSalFrame: this];
+ mpNSView = [[SalFrameView alloc] initWithSalFrame: this];
+ }
+ @catch ( id )
+ {
+ std::abort();
+ }
+
+ if( mnStyle & SalFrameStyleFlags::TOOLTIP )
+ [mpNSWindow setIgnoresMouseEvents: YES];
+ else
+ // Related: tdf#155092 mouse events are now handled by tracking areas
+ [mpNSWindow setAcceptsMouseMovedEvents: NO];
+ [mpNSWindow setHasShadow: YES];
+
+ [mpNSWindow setDelegate: static_cast<id<NSWindowDelegate> >(mpNSWindow)];
+
+ [mpNSWindow setRestorable:NO];
+
+ // tdf#155092 use tracking areas instead of tracking rectangles
+ // Apparently, the older, tracking rectangles selectors cause
+ // unexpected window resizing upon the first mouse down after the
+ // window has been manually resized so switch to the newer,
+ // tracking areas selectors. Also, the NSTrackingInVisibleRect
+ // option allows us to create one single tracking area that
+ // resizes itself automatically over the lifetime of the view.
+ // Note: for some unknown reason, both NSTrackingMouseMoved and
+ // NSTrackingAssumeInside are necessary options for this fix
+ // to work.
+ NSTrackingArea *pTrackingArea = [[NSTrackingArea alloc] initWithRect: [mpNSView bounds] options: ( NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingAssumeInside | NSTrackingInVisibleRect ) owner: mpNSView userInfo: nil];
+ [mpNSView addTrackingArea: pTrackingArea];
+ [pTrackingArea release];
+
+ maSysData.mpNSView = mpNSView;
+
+ UpdateFrameGeometry();
+
+ [mpNSWindow setContentView: mpNSView];
+}
+
+void AquaSalFrame::CocoaToVCL( NSRect& io_rRect, bool bRelativeToScreen )
+{
+ if( bRelativeToScreen )
+ io_rRect.origin.y = maScreenRect.size.height - (io_rRect.origin.y+io_rRect.size.height);
+ else
+ io_rRect.origin.y = maGeometry.height() - (io_rRect.origin.y+io_rRect.size.height);
+}
+
+void AquaSalFrame::VCLToCocoa( NSRect& io_rRect, bool bRelativeToScreen )
+{
+ if( bRelativeToScreen )
+ io_rRect.origin.y = maScreenRect.size.height - (io_rRect.origin.y+io_rRect.size.height);
+ else
+ io_rRect.origin.y = maGeometry.height() - (io_rRect.origin.y+io_rRect.size.height);
+}
+
+void AquaSalFrame::CocoaToVCL( NSPoint& io_rPoint, bool bRelativeToScreen )
+{
+ if( bRelativeToScreen )
+ io_rPoint.y = maScreenRect.size.height - io_rPoint.y;
+ else
+ io_rPoint.y = maGeometry.height() - io_rPoint.y;
+}
+
+void AquaSalFrame::VCLToCocoa( NSPoint& io_rPoint, bool bRelativeToScreen )
+{
+ if( bRelativeToScreen )
+ io_rPoint.y = maScreenRect.size.height - io_rPoint.y;
+ else
+ io_rPoint.y = maGeometry.height() - io_rPoint.y;
+}
+
+void AquaSalFrame::screenParametersChanged()
+{
+ OSX_SALDATA_RUNINMAIN( screenParametersChanged() )
+
+ sal::aqua::resetTotalScreenBounds();
+ sal::aqua::resetWindowScaling();
+
+ UpdateFrameGeometry();
+
+ if( mpGraphics )
+ mpGraphics->updateResolution();
+
+ if (!mbGeometryDidChange)
+ return;
+
+ CallCallback( SalEvent::DisplayChanged, nullptr );
+}
+
+SalGraphics* AquaSalFrame::AcquireGraphics()
+{
+ if ( mbGraphics )
+ return nullptr;
+
+ if ( !mpGraphics )
+ {
+ mpGraphics = new AquaSalGraphics;
+ mpGraphics->SetWindowGraphics( this );
+ }
+
+ mbGraphics = true;
+ return mpGraphics;
+}
+
+void AquaSalFrame::ReleaseGraphics( SalGraphics *pGraphics )
+{
+ SAL_WARN_IF( pGraphics != mpGraphics, "vcl", "graphics released on wrong frame" );
+ mbGraphics = false;
+}
+
+bool AquaSalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
+{
+ GetSalData()->mpInstance->PostEvent( this, pData.release(), SalEvent::UserEvent );
+ return true;
+}
+
+void AquaSalFrame::SetTitle(const OUString& rTitle)
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( SetTitle(rTitle) )
+
+ // #i113170# may not be the main thread if called from UNO API
+ SalData::ensureThreadAutoreleasePool();
+
+ NSString* pTitle = CreateNSString( rTitle );
+ [mpNSWindow setTitle: pTitle];
+
+ // create an entry in the dock menu
+ const SalFrameStyleFlags nAppWindowStyle = SalFrameStyleFlags::CLOSEABLE | SalFrameStyleFlags::MOVEABLE;
+ if( mpParent == nullptr &&
+ (mnStyle & nAppWindowStyle) == nAppWindowStyle )
+ {
+ if( mpDockMenuEntry == nullptr )
+ {
+ NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu();
+
+ if ([pDock numberOfItems] != 0) {
+ NSMenuItem* pTopItem = [pDock itemAtIndex: 0];
+ if ( [pTopItem hasSubmenu] )
+ [pDock insertItem: [NSMenuItem separatorItem] atIndex: 0];
+ }
+
+ mpDockMenuEntry = [pDock insertItemWithTitle: pTitle
+ action: @selector(dockMenuItemTriggered:)
+ keyEquivalent: @""
+ atIndex: 0];
+ [mpDockMenuEntry setTarget: mpNSWindow];
+
+ // TODO: image (either the generic window image or an icon
+ // check mark (for "main" window ?)
+ }
+ else
+ [mpDockMenuEntry setTitle: pTitle];
+ }
+
+ if (pTitle)
+ [pTitle release];
+}
+
+void AquaSalFrame::SetIcon( sal_uInt16 )
+{
+}
+
+void AquaSalFrame::SetRepresentedURL( const OUString& i_rDocURL )
+{
+ OSX_SALDATA_RUNINMAIN( SetRepresentedURL( i_rDocURL ) )
+
+ if( comphelper::isFileUrl(i_rDocURL) )
+ {
+ OUString aSysPath;
+ osl_getSystemPathFromFileURL( i_rDocURL.pData, &aSysPath.pData );
+ NSString* pStr = CreateNSString( aSysPath );
+ if( pStr )
+ {
+ [pStr autorelease];
+ [mpNSWindow setRepresentedFilename: pStr];
+ }
+ }
+}
+
+void AquaSalFrame::initShow()
+{
+ OSX_SALDATA_RUNINMAIN( initShow() )
+
+ mbInitShow = false;
+ if( ! mbPositioned && ! mbFullScreen )
+ {
+ AbsoluteScreenPixelRectangle aScreenRect;
+ GetWorkArea( aScreenRect );
+ if( mpParent ) // center relative to parent
+ {
+ // center on parent
+ tools::Long nNewX = mpParent->maGeometry.x() + (static_cast<tools::Long>(mpParent->maGeometry.width()) - static_cast<tools::Long>(maGeometry.width())) / 2;
+ if( nNewX < aScreenRect.Left() )
+ nNewX = aScreenRect.Left();
+ if (static_cast<tools::Long>(nNewX + maGeometry.width()) > aScreenRect.Right())
+ nNewX = aScreenRect.Right() - maGeometry.width() - 1;
+ tools::Long nNewY = mpParent->maGeometry.y() + (static_cast<tools::Long>(mpParent->maGeometry.height()) - static_cast<tools::Long>(maGeometry.height())) / 2;
+ if( nNewY < aScreenRect.Top() )
+ nNewY = aScreenRect.Top();
+ if( nNewY > aScreenRect.Bottom() )
+ nNewY = aScreenRect.Bottom() - maGeometry.height() - 1;
+ SetPosSize( nNewX - mpParent->maGeometry.x(),
+ nNewY - mpParent->maGeometry.y(),
+ 0, 0, SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y );
+ }
+ else if( ! (mnStyle & SalFrameStyleFlags::SIZEABLE) )
+ {
+ // center on screen
+ tools::Long nNewX = (aScreenRect.GetWidth() - maGeometry.width()) / 2;
+ tools::Long nNewY = (aScreenRect.GetHeight() - maGeometry.height()) / 2;
+ SetPosSize( nNewX, nNewY, 0, 0, SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y );
+ }
+ }
+
+ // make sure the view is present in the wrapper list before any children receive focus
+ if (mpNSView && [mpNSView isKindOfClass:[SalFrameView class]])
+ [static_cast<SalFrameView*>(mpNSView) registerWrapper];
+}
+
+void AquaSalFrame::SendPaintEvent( const tools::Rectangle* pRect )
+{
+ OSX_SALDATA_RUNINMAIN( SendPaintEvent( pRect ) )
+
+ SalPaintEvent aPaintEvt(0, 0, maGeometry.width(), maGeometry.height(), true);
+ if( pRect )
+ {
+ aPaintEvt.mnBoundX = pRect->Left();
+ aPaintEvt.mnBoundY = pRect->Top();
+ aPaintEvt.mnBoundWidth = pRect->GetWidth();
+ aPaintEvt.mnBoundHeight = pRect->GetHeight();
+ }
+
+ CallCallback(SalEvent::Paint, &aPaintEvt);
+}
+
+void AquaSalFrame::Show(bool bVisible, bool bNoActivate)
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( Show(bVisible, bNoActivate) )
+
+ // tdf#152173 Don't display tooltip windows when application is inactive
+ // Starting with macOS 13 Ventura, inactive applications receive mouse
+ // move events so when LibreOffice is inactive, a mouse move event causes
+ // a tooltip to be displayed. Since the tooltip window is attached to its
+ // parent window (to ensure that the tooltip is above the parent window),
+ // displaying a tooltip pulls the parent window in front of the windows
+ // of all other inactive applications.
+ // Also, don't display tooltips when mousing over non-key windows even if
+ // the application is active as the tooltip window will pull the non-key
+ // window in front of the key window.
+ if (bVisible && (mnStyle & SalFrameStyleFlags::TOOLTIP))
+ {
+ if (![NSApp isActive])
+ return;
+
+ if (mpParent)
+ {
+ // tdf#157565 show tooltip if any parent window is the key window
+ bool bKeyWindowFound = false;
+ NSWindow *pParent = mpParent->mpNSWindow;
+ while (pParent)
+ {
+ if ([pParent isKeyWindow])
+ {
+ bKeyWindowFound = true;
+ break;
+ }
+ pParent = [pParent parentWindow];
+ }
+ if (!bKeyWindowFound)
+ return;
+ }
+ }
+
+ mbShown = bVisible;
+ if(bVisible)
+ {
+ if( mbInitShow )
+ initShow();
+
+ CallCallback(SalEvent::Resize, nullptr);
+ // trigger filling our backbuffer
+ SendPaintEvent();
+
+ if( bNoActivate || [mpNSWindow canBecomeKeyWindow] == NO )
+ [mpNSWindow orderFront: NSApp];
+ else
+ [mpNSWindow makeKeyAndOrderFront: NSApp];
+
+ if( mpParent )
+ {
+ /* #i92674# #i96433# we do not want an invisible parent to show up (which adding a visible
+ child implicitly does). However we also do not want a parentless toolbar.
+
+ HACK: try to decide when we should not insert a child to its parent
+ floaters and ownerdraw windows have not yet shown up in cases where
+ we don't want the parent to become visible
+ */
+ if( mpParent->mbShown || (mnStyle & (SalFrameStyleFlags::OWNERDRAWDECORATION | SalFrameStyleFlags::FLOAT) ) )
+ {
+ [mpParent->mpNSWindow addChildWindow: mpNSWindow ordered: NSWindowAbove];
+ }
+ }
+
+ if( mbPresentation )
+ [mpNSWindow makeMainWindow];
+ }
+ else
+ {
+ // if the frame holding the current menubar gets hidden
+ // show the default menubar
+ if( mpMenu && mpMenu->mbMenuBar && AquaSalMenu::pCurrentMenuBar == mpMenu )
+ AquaSalMenu::setDefaultMenu();
+
+ // #i90440# #i94443# work around the focus going back to some other window
+ // if a child gets hidden for a parent window
+ if( mpParent && mpParent->mbShown && [mpNSWindow isKeyWindow] )
+ [mpParent->mpNSWindow makeKeyAndOrderFront: NSApp];
+
+ [SalFrameView unsetMouseFrame: this];
+ if( mpParent && [mpNSWindow parentWindow] == mpParent->mpNSWindow )
+ [mpParent->mpNSWindow removeChildWindow: mpNSWindow];
+
+ [mpNSWindow orderOut: NSApp];
+ }
+}
+
+void AquaSalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight )
+{
+ OSX_SALDATA_RUNINMAIN( SetMinClientSize( nWidth, nHeight ) )
+
+ mnMinWidth = nWidth;
+ mnMinHeight = nHeight;
+
+ if( mpNSWindow )
+ {
+ // Always add the decoration as the dimension concerns only
+ // the content rectangle
+ nWidth += maGeometry.leftDecoration() + maGeometry.rightDecoration();
+ nHeight += maGeometry.topDecoration() + maGeometry.bottomDecoration();
+
+ NSSize aSize = { static_cast<CGFloat>(nWidth), static_cast<CGFloat>(nHeight) };
+
+ // Size of full window (content+structure) although we only
+ // have the client size in arguments
+ [mpNSWindow setMinSize: aSize];
+ }
+}
+
+void AquaSalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight )
+{
+ OSX_SALDATA_RUNINMAIN( SetMaxClientSize( nWidth, nHeight ) )
+
+ mnMaxWidth = nWidth;
+ mnMaxHeight = nHeight;
+
+ if( mpNSWindow )
+ {
+ // Always add the decoration as the dimension concerns only
+ // the content rectangle
+ nWidth += maGeometry.leftDecoration() + maGeometry.rightDecoration();
+ nHeight += maGeometry.topDecoration() + maGeometry.bottomDecoration();
+
+ // Carbon windows can't have a size greater than 32767x32767
+ if (nWidth>32767) nWidth=32767;
+ if (nHeight>32767) nHeight=32767;
+
+ NSSize aSize = { static_cast<CGFloat>(nWidth), static_cast<CGFloat>(nHeight) };
+
+ // Size of full window (content+structure) although we only
+ // have the client size in arguments
+ [mpNSWindow setMaxSize: aSize];
+ }
+}
+
+void AquaSalFrame::GetClientSize( tools::Long& rWidth, tools::Long& rHeight )
+{
+ if (mbShown || mbInitShow || Application::IsBitmapRendering())
+ {
+ rWidth = maGeometry.width();
+ rHeight = maGeometry.height();
+ }
+ else
+ {
+ rWidth = 0;
+ rHeight = 0;
+ }
+}
+
+SalEvent AquaSalFrame::PreparePosSize(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags)
+{
+ SalEvent nEvent = SalEvent::NONE;
+ assert(mpNSWindow || Application::IsBitmapRendering());
+
+ if (nFlags & (SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y))
+ {
+ mbPositioned = true;
+ nEvent = SalEvent::Move;
+ }
+
+ if (nFlags & (SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT))
+ {
+ mbSized = true;
+ nEvent = (nEvent == SalEvent::Move) ? SalEvent::MoveResize : SalEvent::Resize;
+ }
+
+ if (Application::IsBitmapRendering())
+ {
+ if (nFlags & SAL_FRAME_POSSIZE_X)
+ maGeometry.setX(nX);
+ if (nFlags & SAL_FRAME_POSSIZE_Y)
+ maGeometry.setY(nY);
+ if (nFlags & SAL_FRAME_POSSIZE_WIDTH)
+ {
+ maGeometry.setWidth(nWidth);
+ if (mnMaxWidth > 0 && maGeometry.width() > mnMaxWidth)
+ maGeometry.setWidth(mnMaxWidth);
+ if (mnMinWidth > 0 && maGeometry.width() < mnMinWidth)
+ maGeometry.setWidth(mnMinWidth);
+ }
+ if (nFlags & SAL_FRAME_POSSIZE_HEIGHT)
+ {
+ maGeometry.setHeight(nHeight);
+ if (mnMaxHeight > 0 && maGeometry.height() > mnMaxHeight)
+ maGeometry.setHeight(mnMaxHeight);
+ if (mnMinHeight > 0 && maGeometry.height() < mnMinHeight)
+ maGeometry.setHeight(mnMinHeight);
+ }
+ if (nEvent != SalEvent::NONE)
+ CallCallback(nEvent, nullptr);
+ }
+
+ return nEvent;
+}
+
+void AquaSalFrame::SetWindowState(const vcl::WindowData* pState)
+{
+ if (!mpNSWindow && !Application::IsBitmapRendering())
+ return;
+
+ OSX_SALDATA_RUNINMAIN( SetWindowState( pState ) )
+
+ sal_uInt16 nFlags = 0;
+ nFlags |= ((pState->mask() & vcl::WindowDataMask::X) ? SAL_FRAME_POSSIZE_X : 0);
+ nFlags |= ((pState->mask() & vcl::WindowDataMask::Y) ? SAL_FRAME_POSSIZE_Y : 0);
+ nFlags |= ((pState->mask() & vcl::WindowDataMask::Width) ? SAL_FRAME_POSSIZE_WIDTH : 0);
+ nFlags |= ((pState->mask() & vcl::WindowDataMask::Height) ? SAL_FRAME_POSSIZE_HEIGHT : 0);
+
+ SalEvent nEvent = PreparePosSize(pState->x(), pState->y(), pState->width(), pState->height(), nFlags);
+ if (Application::IsBitmapRendering())
+ return;
+
+ // set normal state
+ NSRect aStateRect = [mpNSWindow frame];
+ aStateRect = [NSWindow contentRectForFrameRect: aStateRect styleMask: mnStyleMask];
+ CocoaToVCL(aStateRect);
+ if (pState->mask() & vcl::WindowDataMask::X)
+ aStateRect.origin.x = float(pState->x());
+ if (pState->mask() & vcl::WindowDataMask::Y)
+ aStateRect.origin.y = float(pState->y());
+ if (pState->mask() & vcl::WindowDataMask::Width)
+ aStateRect.size.width = float(pState->width());
+ if (pState->mask() & vcl::WindowDataMask::Height)
+ aStateRect.size.height = float(pState->height());
+ VCLToCocoa(aStateRect);
+ aStateRect = [NSWindow frameRectForContentRect: aStateRect styleMask: mnStyleMask];
+ [mpNSWindow setFrame: aStateRect display: NO];
+
+ if (pState->state() == vcl::WindowState::Minimized)
+ [mpNSWindow miniaturize: NSApp];
+ else if ([mpNSWindow isMiniaturized])
+ [mpNSWindow deminiaturize: NSApp];
+
+ /* ZOOMED is not really maximized (actually it toggles between a user set size and
+ the program specified one), but comes closest since the default behavior is
+ "maximized" if the user did not intervene
+ */
+ if (pState->state() == vcl::WindowState::Maximized)
+ {
+ if (![mpNSWindow isZoomed])
+ [mpNSWindow zoom: NSApp];
+ }
+ else
+ {
+ if ([mpNSWindow isZoomed])
+ [mpNSWindow zoom: NSApp];
+ }
+
+ // get new geometry
+ UpdateFrameGeometry();
+
+ // send event that we were moved/sized
+ if( nEvent != SalEvent::NONE )
+ CallCallback( nEvent, nullptr );
+
+ if (mbShown)
+ {
+ // trigger filling our backbuffer
+ SendPaintEvent();
+
+ // tell the system the views need to be updated
+ [mpNSWindow display];
+ }
+}
+
+bool AquaSalFrame::GetWindowState(vcl::WindowData* pState)
+{
+ if (!mpNSWindow)
+ {
+ if (Application::IsBitmapRendering())
+ {
+ pState->setMask(vcl::WindowDataMask::PosSizeState);
+ pState->setPosSize(maGeometry.posSize());
+ pState->setState(vcl::WindowState::Normal);
+ return true;
+ }
+ return false;
+ }
+
+ OSX_SALDATA_RUNINMAIN_UNION( GetWindowState( pState ), boolean )
+
+ pState->setMask(vcl::WindowDataMask::PosSizeState);
+
+ NSRect aStateRect = [mpNSWindow frame];
+ aStateRect = [NSWindow contentRectForFrameRect: aStateRect styleMask: mnStyleMask];
+ CocoaToVCL( aStateRect );
+ pState->setX(static_cast<sal_Int32>(aStateRect.origin.x));
+ pState->setY(static_cast<sal_Int32>(aStateRect.origin.y));
+ pState->setWidth(static_cast<sal_uInt32>(aStateRect.size.width));
+ pState->setHeight(static_cast<sal_uInt32>(aStateRect.size.height));
+
+ if( [mpNSWindow isMiniaturized] )
+ pState->setState(vcl::WindowState::Minimized);
+ else if( ! [mpNSWindow isZoomed] )
+ pState->setState(vcl::WindowState::Normal);
+ else
+ pState->setState(vcl::WindowState::Maximized);
+
+ return true;
+}
+
+void AquaSalFrame::SetScreenNumber(unsigned int nScreen)
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( SetScreenNumber( nScreen ) )
+
+ NSArray* pScreens = [NSScreen screens];
+ NSScreen* pScreen = nil;
+ if( pScreens && nScreen < [pScreens count] )
+ {
+ // get new screen frame
+ pScreen = [pScreens objectAtIndex: nScreen];
+ NSRect aNewScreen = [pScreen frame];
+
+ // get current screen frame
+ pScreen = [mpNSWindow screen];
+ if( pScreen )
+ {
+ NSRect aCurScreen = [pScreen frame];
+ if( aCurScreen.origin.x != aNewScreen.origin.x ||
+ aCurScreen.origin.y != aNewScreen.origin.y )
+ {
+ NSRect aFrameRect = [mpNSWindow frame];
+ aFrameRect.origin.x += aNewScreen.origin.x - aCurScreen.origin.x;
+ aFrameRect.origin.y += aNewScreen.origin.y - aCurScreen.origin.y;
+ [mpNSWindow setFrame: aFrameRect display: NO];
+ UpdateFrameGeometry();
+ }
+ }
+ }
+}
+
+void AquaSalFrame::SetApplicationID( const OUString &/*rApplicationID*/ )
+{
+}
+
+void AquaSalFrame::ShowFullScreen( bool bFullScreen, sal_Int32 nDisplay )
+{
+ doShowFullScreen(bFullScreen, nDisplay);
+}
+
+void AquaSalFrame::doShowFullScreen( bool bFullScreen, sal_Int32 nDisplay )
+{
+ if (!mpNSWindow)
+ {
+ if (Application::IsBitmapRendering() && bFullScreen)
+ SetPosSize(0, 0, 1024, 768, SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT);
+ return;
+ }
+
+ SAL_INFO("vcl.osx", __func__ << ": mbFullScreen=" << mbFullScreen << ", bFullScreen=" << bFullScreen);
+
+ if( mbFullScreen == bFullScreen )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( ShowFullScreen( bFullScreen, nDisplay ) )
+
+ mbFullScreen = bFullScreen;
+
+ if( bFullScreen )
+ {
+ // hide the dock and the menubar if we are on the menu screen
+ // which is always on index 0 according to documentation
+ bool bHideMenu = (nDisplay == 0);
+
+ NSRect aNewContentRect = NSZeroRect;
+ // get correct screen
+ NSScreen* pScreen = nil;
+ NSArray* pScreens = [NSScreen screens];
+ if( pScreens )
+ {
+ if( nDisplay >= 0 && o3tl::make_unsigned(nDisplay) < [pScreens count] )
+ pScreen = [pScreens objectAtIndex: nDisplay];
+ else
+ {
+ // this means span all screens
+ bHideMenu = true;
+ NSEnumerator* pEnum = [pScreens objectEnumerator];
+ while( (pScreen = [pEnum nextObject]) != nil )
+ {
+ NSRect aScreenRect = [pScreen frame];
+ if( aScreenRect.origin.x < aNewContentRect.origin.x )
+ {
+ aNewContentRect.size.width += aNewContentRect.origin.x - aScreenRect.origin.x;
+ aNewContentRect.origin.x = aScreenRect.origin.x;
+ }
+ if( aScreenRect.origin.y < aNewContentRect.origin.y )
+ {
+ aNewContentRect.size.height += aNewContentRect.origin.y - aScreenRect.origin.y;
+ aNewContentRect.origin.y = aScreenRect.origin.y;
+ }
+ if( aScreenRect.origin.x + aScreenRect.size.width > aNewContentRect.origin.x + aNewContentRect.size.width )
+ aNewContentRect.size.width = aScreenRect.origin.x + aScreenRect.size.width - aNewContentRect.origin.x;
+ if( aScreenRect.origin.y + aScreenRect.size.height > aNewContentRect.origin.y + aNewContentRect.size.height )
+ aNewContentRect.size.height = aScreenRect.origin.y + aScreenRect.size.height - aNewContentRect.origin.y;
+ }
+ }
+ }
+ if( aNewContentRect.size.width == 0 && aNewContentRect.size.height == 0 )
+ {
+ if( pScreen == nil )
+ pScreen = [mpNSWindow screen];
+ if( pScreen == nil )
+ pScreen = [NSScreen mainScreen];
+
+ aNewContentRect = [pScreen frame];
+ }
+
+ if( bHideMenu )
+ [NSMenu setMenuBarVisible:NO];
+
+ maFullScreenRect = [mpNSWindow frame];
+
+ [mpNSWindow setFrame: [NSWindow frameRectForContentRect: aNewContentRect styleMask: mnStyleMask] display: mbShown ? YES : NO];
+ }
+ else
+ {
+ [mpNSWindow setFrame: maFullScreenRect display: mbShown ? YES : NO];
+
+ // show the dock and the menubar
+ [NSMenu setMenuBarVisible:YES];
+ }
+
+ UpdateFrameGeometry();
+ if (mbShown)
+ {
+ CallCallback(SalEvent::MoveResize, nullptr);
+
+ // trigger filling our backbuffer
+ SendPaintEvent();
+ }
+}
+
+void AquaSalFrame::StartPresentation( bool bStart )
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( StartPresentation( bStart ) )
+
+ if( bStart )
+ {
+ GetSalData()->maPresentationFrames.push_back( this );
+ IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
+ kIOPMAssertionLevelOn,
+ CFSTR("LibreOffice presentation running"),
+ &mnAssertionID);
+ [mpNSWindow setLevel: NSPopUpMenuWindowLevel];
+ if( mbShown )
+ [mpNSWindow makeMainWindow];
+ }
+ else
+ {
+ GetSalData()->maPresentationFrames.remove( this );
+ IOPMAssertionRelease(mnAssertionID);
+ [mpNSWindow setLevel: NSNormalWindowLevel];
+ }
+}
+
+void AquaSalFrame::SetAlwaysOnTop( bool )
+{
+}
+
+void AquaSalFrame::ToTop(SalFrameToTop nFlags)
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( ToTop( nFlags ) )
+
+ if( ! (nFlags & SalFrameToTop::RestoreWhenMin) )
+ {
+ if( ! [mpNSWindow isVisible] || [mpNSWindow isMiniaturized] )
+ return;
+ }
+ if( nFlags & SalFrameToTop::GrabFocus )
+ [mpNSWindow makeKeyAndOrderFront: NSApp];
+ else
+ [mpNSWindow orderFront: NSApp];
+}
+
+NSCursor* AquaSalFrame::getCurrentCursor()
+{
+ OSX_SALDATA_RUNINMAIN_POINTER( getCurrentCursor(), NSCursor* )
+
+ NSCursor* pCursor = nil;
+ switch( mePointerStyle )
+ {
+ case PointerStyle::Text: pCursor = [NSCursor IBeamCursor]; break;
+ case PointerStyle::Cross: pCursor = [NSCursor crosshairCursor]; break;
+ case PointerStyle::Hand:
+ case PointerStyle::Move: pCursor = [NSCursor openHandCursor]; break;
+ case PointerStyle::NSize: pCursor = [NSCursor resizeUpCursor]; break;
+ case PointerStyle::SSize: pCursor = [NSCursor resizeDownCursor]; break;
+ case PointerStyle::ESize: pCursor = [NSCursor resizeRightCursor]; break;
+ case PointerStyle::WSize: pCursor = [NSCursor resizeLeftCursor]; break;
+ case PointerStyle::Arrow: pCursor = [NSCursor arrowCursor]; break;
+ case PointerStyle::VSplit:
+ case PointerStyle::VSizeBar:
+ case PointerStyle::WindowNSize:
+ case PointerStyle::WindowSSize:
+ pCursor = [NSCursor resizeUpDownCursor]; break;
+ case PointerStyle::HSplit:
+ case PointerStyle::HSizeBar:
+ case PointerStyle::WindowESize:
+ case PointerStyle::WindowWSize:
+ pCursor = [NSCursor resizeLeftRightCursor]; break;
+ case PointerStyle::RefHand: pCursor = [NSCursor pointingHandCursor]; break;
+
+ default:
+ pCursor = GetSalData()->getCursor( mePointerStyle );
+ if( pCursor == nil )
+ {
+ assert( false && "unmapped cursor" );
+ pCursor = [NSCursor arrowCursor];
+ }
+ break;
+ }
+ return pCursor;
+}
+
+void AquaSalFrame::SetPointer( PointerStyle ePointerStyle )
+{
+ if ( !mpNSWindow )
+ return;
+ if( ePointerStyle == mePointerStyle )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( SetPointer( ePointerStyle ) )
+
+ mePointerStyle = ePointerStyle;
+
+ [mpNSWindow invalidateCursorRectsForView: mpNSView];
+}
+
+void AquaSalFrame::SetPointerPos( tools::Long nX, tools::Long nY )
+{
+ OSX_SALDATA_RUNINMAIN( SetPointerPos( nX, nY ) )
+
+ // FIXME: use Cocoa functions
+ // FIXME: multiscreen support
+ CGPoint aPoint = { static_cast<CGFloat>(nX + maGeometry.x()), static_cast<CGFloat>(nY + maGeometry.y()) };
+ CGDirectDisplayID mainDisplayID = CGMainDisplayID();
+ CGDisplayMoveCursorToPoint( mainDisplayID, aPoint );
+}
+
+void AquaSalFrame::Flush()
+{
+ if( !(mbGraphics && mpGraphics && mpNSView && mbShown) )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( Flush() )
+
+ [mpNSView setNeedsDisplay: YES];
+
+ // outside of the application's event loop (e.g. IntroWindow)
+ // nothing would trigger paint event handling
+ // => fall back to synchronous painting
+ if( mbForceFlush || ImplGetSVData()->maAppData.mnDispatchLevel <= 0 )
+ {
+ mbForceFlush = false;
+ mpGraphics->Flush();
+ // Related: tdf#155266 skip redisplay of the view when forcing flush
+ // It appears that calling -[NSView display] overwhelms some Intel Macs
+ // so only flush the graphics and skip immediate redisplay of the view.
+ if( ImplGetSVData()->maAppData.mnDispatchLevel <= 0 )
+ [mpNSView display];
+ }
+}
+
+void AquaSalFrame::Flush( const tools::Rectangle& rRect )
+{
+ if( !(mbGraphics && mpGraphics && mpNSView && mbShown) )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( Flush( rRect ) )
+
+ NSRect aNSRect = { { static_cast<CGFloat>(rRect.Left()), static_cast<CGFloat>(rRect.Top()) }, { static_cast<CGFloat>(rRect.GetWidth()), static_cast<CGFloat>(rRect.GetHeight()) } };
+ VCLToCocoa( aNSRect, false );
+ [mpNSView setNeedsDisplayInRect: aNSRect];
+
+ // outside of the application's event loop (e.g. IntroWindow)
+ // nothing would trigger paint event handling
+ // => fall back to synchronous painting
+ if( mbForceFlush || ImplGetSVData()->maAppData.mnDispatchLevel <= 0 )
+ {
+ mbForceFlush = false;
+ mpGraphics->Flush( rRect );
+ // Related: tdf#155266 skip redisplay of the view when forcing flush
+ // It appears that calling -[NSView display] overwhelms some Intel Macs
+ // so only flush the graphics and skip immediate redisplay of the view.
+ if( ImplGetSVData()->maAppData.mnDispatchLevel <= 0 )
+ [mpNSView display];
+ }
+}
+
+void AquaSalFrame::SetInputContext( SalInputContext* pContext )
+{
+ if (!pContext)
+ {
+ mnICOptions = InputContextFlags::NONE;
+ return;
+ }
+
+ mnICOptions = pContext->mnOptions;
+
+ if(!(pContext->mnOptions & InputContextFlags::Text))
+ return;
+}
+
+void AquaSalFrame::EndExtTextInput( EndExtTextInputFlags nFlags )
+{
+ // tdf#82115 Commit uncommitted text when a popup menu is opened
+ // The Windows implementation of this method commits or discards the native
+ // input method session. It appears that very few, if any, macOS
+ // applications discard the uncommitted text when cancelling a session so
+ // always commit the uncommitted text.
+ SalFrameWindow *pWindow = static_cast<SalFrameWindow*>(mpNSWindow);
+ if (pWindow && [pWindow isKindOfClass:[SalFrameWindow class]])
+ [pWindow endExtTextInput:nFlags];
+}
+
+OUString AquaSalFrame::GetKeyName( sal_uInt16 nKeyCode )
+{
+ static std::map< sal_uInt16, OUString > aKeyMap;
+ if( aKeyMap.empty() )
+ {
+ sal_uInt16 i;
+ for( i = KEY_A; i <= KEY_Z; i++ )
+ aKeyMap[ i ] = OUString( sal_Unicode( 'A' + (i - KEY_A) ) );
+ for( i = KEY_0; i <= KEY_9; i++ )
+ aKeyMap[ i ] = OUString( sal_Unicode( '0' + (i - KEY_0) ) );
+ for( i = KEY_F1; i <= KEY_F26; i++ )
+ {
+ aKeyMap[ i ] = "F" + OUString::number(i - KEY_F1 + 1);
+ }
+
+ aKeyMap[ KEY_DOWN ] = OUString( u'\x21e3' );
+ aKeyMap[ KEY_UP ] = OUString( u'\x21e1' );
+ aKeyMap[ KEY_LEFT ] = OUString( u'\x21e0' );
+ aKeyMap[ KEY_RIGHT ] = OUString( u'\x21e2' );
+ aKeyMap[ KEY_HOME ] = OUString( u'\x2196' );
+ aKeyMap[ KEY_END ] = OUString( u'\x2198' );
+ aKeyMap[ KEY_PAGEUP ] = OUString( u'\x21de' );
+ aKeyMap[ KEY_PAGEDOWN ] = OUString( u'\x21df' );
+ aKeyMap[ KEY_RETURN ] = OUString( u'\x21a9' );
+ aKeyMap[ KEY_ESCAPE ] = "esc";
+ aKeyMap[ KEY_TAB ] = OUString( u'\x21e5' );
+ aKeyMap[ KEY_BACKSPACE ]= OUString( u'\x232b' );
+ aKeyMap[ KEY_SPACE ] = OUString( u'\x2423' );
+ aKeyMap[ KEY_DELETE ] = OUString( u'\x2326' );
+ aKeyMap[ KEY_ADD ] = "+";
+ aKeyMap[ KEY_SUBTRACT ] = "-";
+ aKeyMap[ KEY_DIVIDE ] = "/";
+ aKeyMap[ KEY_MULTIPLY ] = "*";
+ aKeyMap[ KEY_POINT ] = ".";
+ aKeyMap[ KEY_COMMA ] = ",";
+ aKeyMap[ KEY_LESS ] = "<";
+ aKeyMap[ KEY_GREATER ] = ">";
+ aKeyMap[ KEY_EQUAL ] = "=";
+ aKeyMap[ KEY_OPEN ] = OUString( u'\x23cf' );
+ aKeyMap[ KEY_TILDE ] = "~";
+ aKeyMap[ KEY_BRACKETLEFT ] = "[";
+ aKeyMap[ KEY_BRACKETRIGHT ] = "]";
+ aKeyMap[ KEY_SEMICOLON ] = ";";
+ aKeyMap[ KEY_QUOTERIGHT ] = "'";
+ aKeyMap[ KEY_RIGHTCURLYBRACKET ] = "}";
+ aKeyMap[ KEY_NUMBERSIGN ] = "#";
+ aKeyMap[ KEY_COLON ] = ":";
+
+ /* yet unmapped KEYCODES:
+ aKeyMap[ KEY_INSERT ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_CUT ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_COPY ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_PASTE ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_UNDO ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_REPEAT ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_FIND ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_PROPERTIES ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_FRONT ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_CONTEXTMENU ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_MENU ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_HELP ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_HANGUL_HANJA ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_DECIMAL ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_QUOTELEFT ]= OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_CAPSLOCK ]= OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_NUMLOCK ]= OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_SCROLLLOCK ]= OUString( sal_Unicode( ) );
+ */
+
+ }
+
+ OUStringBuffer aResult( 16 );
+
+ sal_uInt16 nUnmodifiedCode = (nKeyCode & KEY_CODE_MASK);
+ std::map< sal_uInt16, OUString >::const_iterator it = aKeyMap.find( nUnmodifiedCode );
+ if( it != aKeyMap.end() )
+ {
+ if( (nKeyCode & KEY_SHIFT) != 0 )
+ aResult.append( u'\x21e7' ); // shift
+ if( (nKeyCode & KEY_MOD1) != 0 )
+ aResult.append( u'\x2318' ); // command
+ if( (nKeyCode & KEY_MOD2) != 0 )
+ aResult.append( u'\x2325' ); // alternate
+ if( (nKeyCode & KEY_MOD3) != 0 )
+ aResult.append( u'\x2303' ); // control
+
+ aResult.append( it->second );
+ }
+
+ return aResult.makeStringAndClear();
+}
+
+static void getAppleScrollBarVariant(StyleSettings &rSettings)
+{
+ bool bIsScrollbarDoubleMax = true; // default is DoubleMax
+
+ CFStringRef AppleScrollBarType = CFSTR("AppleScrollBarVariant");
+ if( AppleScrollBarType )
+ {
+ CFStringRef ScrollBarVariant = static_cast<CFStringRef>(CFPreferencesCopyAppValue( AppleScrollBarType, kCFPreferencesCurrentApplication ));
+ if( ScrollBarVariant )
+ {
+ if( CFGetTypeID( ScrollBarVariant ) == CFStringGetTypeID() )
+ {
+ // TODO: check for the less important variants "DoubleMin" and "DoubleBoth" too
+ CFStringRef DoubleMax = CFSTR("DoubleMax");
+ if (DoubleMax)
+ {
+ if ( !CFStringCompare(ScrollBarVariant, DoubleMax, kCFCompareCaseInsensitive) )
+ bIsScrollbarDoubleMax = true;
+ else
+ bIsScrollbarDoubleMax = false;
+ CFRelease(DoubleMax);
+ }
+ }
+ CFRelease( ScrollBarVariant );
+ }
+ CFRelease(AppleScrollBarType);
+ }
+
+ GetSalData()->mbIsScrollbarDoubleMax = bIsScrollbarDoubleMax;
+
+ CFStringRef jumpScroll = CFSTR("AppleScrollerPagingBehavior");
+ if( jumpScroll )
+ {
+ CFBooleanRef jumpStr = static_cast<CFBooleanRef>(CFPreferencesCopyAppValue( jumpScroll, kCFPreferencesCurrentApplication ));
+ if( jumpStr )
+ {
+ if( CFGetTypeID( jumpStr ) == CFBooleanGetTypeID() )
+ rSettings.SetPrimaryButtonWarpsSlider(jumpStr == kCFBooleanTrue);
+ CFRelease( jumpStr );
+ }
+ CFRelease( jumpScroll );
+ }
+}
+
+static Color getNSBoxBackgroundColor(NSColor* pSysColor)
+{
+ // Figuring out what a NSBox will draw for windowBackground, etc. seems very difficult.
+ // So just draw to a 1x1 surface and read what actually gets drawn
+ // This is similar to getPixel
+#if defined OSL_BIGENDIAN
+ struct
+ {
+ unsigned char b, g, r, a;
+ } aPixel;
+#else
+ struct
+ {
+ unsigned char a, r, g, b;
+ } aPixel;
+#endif
+
+ // create a one-pixel bitmap context
+ CGContextRef xOnePixelContext = CGBitmapContextCreate(
+ &aPixel, 1, 1, 8, 32, GetSalData()->mxRGBSpace,
+ uint32_t(kCGImageAlphaNoneSkipFirst) | uint32_t(kCGBitmapByteOrder32Big));
+
+ NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:xOnePixelContext flipped:NO];
+
+ NSRect rect = { NSZeroPoint, NSMakeSize(1, 1) };
+ NSBox* pBox = [[NSBox alloc] initWithFrame: rect];
+
+ [pBox setBoxType: NSBoxCustom];
+ [pBox setFillColor: pSysColor];
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH // setBorderType first deprecated in macOS 10.15
+ [pBox setBorderType: NSNoBorder];
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ [pBox displayRectIgnoringOpacity: rect inContext: graphicsContext];
+
+ [pBox release];
+
+ CGContextRelease(xOnePixelContext);
+
+ return Color(aPixel.r, aPixel.g, aPixel.b);
+}
+
+static Color getColor( NSColor* pSysColor, const Color& rDefault, NSWindow* pWin )
+{
+ Color aRet( rDefault );
+ if( pSysColor )
+ {
+ // transform to RGB
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'colorUsingColorSpaceName:device:' is deprecated: first deprecated in macOS 10.14 -
+ // Use -colorUsingType: or -colorUsingColorSpace: instead"
+ NSColor* pRBGColor = [pSysColor colorUsingColorSpaceName: NSDeviceRGBColorSpace device: [pWin deviceDescription]];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ if( pRBGColor )
+ {
+ CGFloat r = 0, g = 0, b = 0, a = 0;
+ [pRBGColor getRed: &r green: &g blue: &b alpha: &a];
+ aRet = Color( int(r*255.999), int(g*255.999), int(b*255.999) );
+ }
+ }
+ return aRet;
+}
+
+static vcl::Font getFont( NSFont* pFont, sal_Int32 nDPIY, const vcl::Font& rDefault )
+{
+ vcl::Font aResult( rDefault );
+ if( pFont )
+ {
+ aResult.SetFamilyName( GetOUString( [pFont familyName] ) );
+ aResult.SetFontHeight( static_cast<int>(ceil([pFont pointSize] * 72.0 / static_cast<float>(nDPIY))) );
+ aResult.SetItalic( ([pFont italicAngle] != 0.0) ? ITALIC_NORMAL : ITALIC_NONE );
+ // FIMXE: bold ?
+ }
+
+ return aResult;
+}
+
+void AquaSalFrame::getResolution( sal_Int32& o_rDPIX, sal_Int32& o_rDPIY )
+{
+ OSX_SALDATA_RUNINMAIN( getResolution( o_rDPIX, o_rDPIY ) )
+
+ if( ! mpGraphics )
+ {
+ AcquireGraphics();
+ ReleaseGraphics( mpGraphics );
+ }
+ mpGraphics->GetResolution( o_rDPIX, o_rDPIY );
+}
+
+void AquaSalFrame::UpdateDarkMode()
+{
+ if (@available(macOS 10.14, iOS 13, *))
+ {
+ switch (MiscSettings::GetDarkMode())
+ {
+ case 0: // auto
+ default:
+ [NSApp setAppearance: nil];
+ break;
+ case 1: // light
+ [NSApp setAppearance: [NSAppearance appearanceNamed: NSAppearanceNameAqua]];
+ break;
+ case 2: // dark
+ [NSApp setAppearance: [NSAppearance appearanceNamed: NSAppearanceNameDarkAqua]];
+ break;
+ }
+ }
+}
+
+bool AquaSalFrame::GetUseDarkMode() const
+{
+ if (!mpNSView)
+ return false;
+ bool bUseDarkMode(false);
+ if (@available(macOS 10.14, iOS 13, *))
+ {
+ NSAppearanceName match = [mpNSView.effectiveAppearance bestMatchFromAppearancesWithNames: @[
+ NSAppearanceNameAqua, NSAppearanceNameDarkAqua]];
+ bUseDarkMode = [match isEqualToString: NSAppearanceNameDarkAqua];
+ }
+ return bUseDarkMode;
+}
+
+bool AquaSalFrame::GetUseReducedAnimation() const
+{
+ return [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceMotion];
+}
+
+// on OSX-Aqua the style settings are independent of the frame, so it does
+// not really belong here. Since the connection to the Appearance_Manager
+// is currently done in salnativewidgets.cxx this would be a good place.
+// On the other hand VCL's platform independent code currently only asks
+// SalFrames for system settings anyway, so moving the code somewhere else
+// doesn't make the anything cleaner for now
+void AquaSalFrame::UpdateSettings( AllSettings& rSettings )
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( UpdateSettings( rSettings ) )
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'lockFocus' is deprecated: first deprecated in macOS 10.14 - To draw, subclass NSView
+ // and implement -drawRect:; AppKit's automatic deferred display mechanism will call
+ // -drawRect: as necessary to display the view."
+ if (![mpNSView lockFocusIfCanDraw])
+ return;
+SAL_WNODEPRECATED_DECLARATIONS_POP
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'setCurrentAppearance:' is deprecated: first deprecated in macOS 12.0 - Use
+ // -performAsCurrentDrawingAppearance: to temporarily set the drawing appearance, or
+ // +currentDrawingAppearance to access the currently drawing appearance."
+ [NSAppearance setCurrentAppearance: mpNSView.effectiveAppearance];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ StyleSettings aStyleSettings = rSettings.GetStyleSettings();
+
+ bool bUseDarkMode(GetUseDarkMode());
+ OUString sThemeName(!bUseDarkMode ? u"sukapura" : u"sukapura_dark");
+ aStyleSettings.SetPreferredIconTheme(sThemeName, bUseDarkMode);
+
+ Color aControlBackgroundColor(getNSBoxBackgroundColor([NSColor controlBackgroundColor]));
+ Color aWindowBackgroundColor(getNSBoxBackgroundColor([NSColor windowBackgroundColor]));
+ Color aUnderPageBackgroundColor(getNSBoxBackgroundColor([NSColor underPageBackgroundColor]));
+
+ // Background Color
+ aStyleSettings.BatchSetBackgrounds( aWindowBackgroundColor, false );
+ aStyleSettings.SetLightBorderColor( aWindowBackgroundColor );
+
+ aStyleSettings.SetActiveTabColor(aWindowBackgroundColor);
+ Color aInactiveTabColor( aWindowBackgroundColor );
+ aInactiveTabColor.DecreaseLuminance( 32 );
+ aStyleSettings.SetInactiveTabColor( aInactiveTabColor );
+
+ Color aShadowColor = getColor( [NSColor systemGrayColor ],
+ aStyleSettings.GetShadowColor(), mpNSWindow );
+ aStyleSettings.SetShadowColor( aShadowColor );
+
+ // tdf#152284 for DarkMode brighten it, while darken for BrightMode
+ NSColor* pDarkColor = bUseDarkMode ? [[NSColor systemGrayColor] highlightWithLevel: 0.5]
+ : [[NSColor systemGrayColor] shadowWithLevel: 0.5];
+ Color aDarkShadowColor = getColor( pDarkColor, aStyleSettings.GetDarkShadowColor(), mpNSWindow );
+ aStyleSettings.SetDarkShadowColor(aDarkShadowColor);
+
+ // get the system font settings
+ vcl::Font aAppFont = aStyleSettings.GetAppFont();
+ sal_Int32 nDPIX = 72, nDPIY = 72;
+ getResolution( nDPIX, nDPIY );
+ aAppFont = getFont( [NSFont systemFontOfSize: 0], nDPIY, aAppFont );
+
+ aStyleSettings.SetToolbarIconSize( ToolbarIconSize::Large );
+
+ // TODO: better mapping of macOS<->LibreOffice font settings
+ vcl::Font aLabelFont( getFont( [NSFont labelFontOfSize: 0], nDPIY, aAppFont ) );
+ aStyleSettings.BatchSetFonts( aAppFont, aLabelFont );
+ vcl::Font aMenuFont( getFont( [NSFont menuFontOfSize: 0], nDPIY, aAppFont ) );
+ aStyleSettings.SetMenuFont( aMenuFont );
+
+ vcl::Font aTitleFont( getFont( [NSFont titleBarFontOfSize: 0], nDPIY, aAppFont ) );
+ aStyleSettings.SetTitleFont( aTitleFont );
+ aStyleSettings.SetFloatTitleFont( aTitleFont );
+
+ vcl::Font aTooltipFont(getFont([NSFont toolTipsFontOfSize: 0], nDPIY, aAppFont));
+ aStyleSettings.SetHelpFont(aTooltipFont);
+
+ Color aAccentColor( getColor( [NSColor controlAccentColor],
+ aStyleSettings.GetAccentColor(), mpNSWindow ) );
+ aStyleSettings.SetAccentColor( aAccentColor );
+
+ Color aHighlightColor( getColor( [NSColor selectedTextBackgroundColor],
+ aStyleSettings.GetHighlightColor(), mpNSWindow ) );
+ aStyleSettings.SetHighlightColor( aHighlightColor );
+ Color aHighlightTextColor( getColor( [NSColor selectedTextColor],
+ aStyleSettings.GetHighlightTextColor(), mpNSWindow ) );
+ aStyleSettings.SetHighlightTextColor( aHighlightTextColor );
+
+ aStyleSettings.SetLinkColor(getColor( [NSColor linkColor],
+ aStyleSettings.GetLinkColor(), mpNSWindow ) );
+ aStyleSettings.SetVisitedLinkColor(getColor( [NSColor purpleColor],
+ aStyleSettings.GetVisitedLinkColor(), mpNSWindow ) );
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ Color aMenuHighlightColor( getColor( [NSColor selectedMenuItemColor],
+ aStyleSettings.GetMenuHighlightColor(), mpNSWindow ) );
+#pragma clang diagnostic pop
+ aStyleSettings.SetMenuHighlightColor( aMenuHighlightColor );
+ Color aMenuHighlightTextColor( getColor( [NSColor selectedMenuItemTextColor],
+ aStyleSettings.GetMenuHighlightTextColor(), mpNSWindow ) );
+ aStyleSettings.SetMenuHighlightTextColor( aMenuHighlightTextColor );
+
+ aStyleSettings.SetMenuColor( aWindowBackgroundColor );
+ Color aMenuTextColor( getColor( [NSColor textColor],
+ aStyleSettings.GetMenuTextColor(), mpNSWindow ) );
+ aStyleSettings.SetMenuTextColor( aMenuTextColor );
+ aStyleSettings.SetMenuBarTextColor( aMenuTextColor );
+ aStyleSettings.SetMenuBarRolloverTextColor( aMenuTextColor );
+ aStyleSettings.SetMenuBarHighlightTextColor(aStyleSettings.GetMenuHighlightTextColor());
+
+ aStyleSettings.SetListBoxWindowBackgroundColor( aWindowBackgroundColor );
+ aStyleSettings.SetListBoxWindowTextColor( aMenuTextColor );
+ aStyleSettings.SetListBoxWindowHighlightColor( aMenuHighlightColor );
+ aStyleSettings.SetListBoxWindowHighlightTextColor( aMenuHighlightTextColor );
+
+ // FIXME: Starting with macOS Big Sur, coloring has changed. Currently there is no documentation which system color should be
+ // used for some button states and for selected tab text. As a workaround the current OS version has to be considered. This code
+ // has to be reviewed once issue is covered by documentation.
+
+ // Set text colors for buttons and their different status according to OS settings, typically white for selected buttons,
+ // black otherwise
+
+ NSOperatingSystemVersion aOSVersion = { .majorVersion = 10, .minorVersion = 16, .patchVersion = 0 };
+ Color aControlTextColor(getColor([NSColor controlTextColor], COL_BLACK, mpNSWindow ));
+ Color aSelectedControlTextColor(getColor([NSColor selectedControlTextColor], COL_BLACK, mpNSWindow ));
+ Color aAlternateSelectedControlTextColor(getColor([NSColor alternateSelectedControlTextColor], COL_WHITE, mpNSWindow ));
+ aStyleSettings.SetWindowColor(aWindowBackgroundColor);
+ aStyleSettings.SetListBoxWindowBackgroundColor(aWindowBackgroundColor);
+
+ aStyleSettings.SetDialogTextColor(aControlTextColor);
+ aStyleSettings.SetButtonTextColor(aControlTextColor);
+ aStyleSettings.SetActionButtonTextColor(aControlTextColor);
+ aStyleSettings.SetRadioCheckTextColor(aControlTextColor);
+ aStyleSettings.SetGroupTextColor(aControlTextColor);
+ aStyleSettings.SetLabelTextColor(aControlTextColor);
+ aStyleSettings.SetWindowTextColor(aControlTextColor);
+ aStyleSettings.SetFieldTextColor(aControlTextColor);
+
+ aStyleSettings.SetFieldRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetFieldColor(aControlBackgroundColor);
+ aStyleSettings.SetDefaultActionButtonTextColor(aAlternateSelectedControlTextColor);
+ aStyleSettings.SetFlatButtonTextColor(aControlTextColor);
+ aStyleSettings.SetDefaultButtonRolloverTextColor(aAlternateSelectedControlTextColor);
+ aStyleSettings.SetButtonRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetDefaultActionButtonRolloverTextColor(aAlternateSelectedControlTextColor);
+ aStyleSettings.SetActionButtonRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetFlatButtonRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetDefaultButtonPressedRolloverTextColor(aAlternateSelectedControlTextColor);
+ aStyleSettings.SetDefaultActionButtonPressedRolloverTextColor(aAlternateSelectedControlTextColor);
+ aStyleSettings.SetFlatButtonPressedRolloverTextColor(aControlTextColor);
+ if ([NSProcessInfo.processInfo isOperatingSystemAtLeastVersion: aOSVersion])
+ {
+ aStyleSettings.SetDefaultButtonTextColor(aAlternateSelectedControlTextColor);
+ aStyleSettings.SetButtonPressedRolloverTextColor(aSelectedControlTextColor);
+ aStyleSettings.SetActionButtonPressedRolloverTextColor(aSelectedControlTextColor);
+ }
+ else
+ {
+ aStyleSettings.SetButtonPressedRolloverTextColor(aAlternateSelectedControlTextColor);
+ aStyleSettings.SetActionButtonPressedRolloverTextColor(aAlternateSelectedControlTextColor);
+ aStyleSettings.SetDefaultButtonTextColor(aSelectedControlTextColor);
+ }
+
+ aStyleSettings.SetWorkspaceColor(aUnderPageBackgroundColor);
+
+ aStyleSettings.SetHelpColor(aControlBackgroundColor);
+ aStyleSettings.SetHelpTextColor(aControlTextColor);
+ aStyleSettings.SetToolTextColor(aControlTextColor);
+
+ // Set text colors for tabs according to OS settings
+
+ aStyleSettings.SetTabTextColor(aControlTextColor);
+ aStyleSettings.SetTabRolloverTextColor(aControlTextColor);
+ if ([NSProcessInfo.processInfo isOperatingSystemAtLeastVersion: aOSVersion])
+ aStyleSettings.SetTabHighlightTextColor(aSelectedControlTextColor);
+ else
+ aStyleSettings.SetTabHighlightTextColor(aAlternateSelectedControlTextColor);
+
+ aStyleSettings.SetCursorBlinkTime( mnBlinkCursorDelay );
+ aStyleSettings.SetCursorSize(1);
+
+ // no mnemonics on macOS
+ aStyleSettings.SetOptions( aStyleSettings.GetOptions() | StyleSettingsOptions::NoMnemonics );
+
+ getAppleScrollBarVariant(aStyleSettings);
+
+ // set scrollbar size
+ aStyleSettings.SetScrollBarSize( static_cast<tools::Long>([NSScroller scrollerWidthForControlSize:NSControlSizeRegular scrollerStyle:NSScrollerStyleLegacy]) );
+ // images in menus false for MacOSX
+ aStyleSettings.SetPreferredUseImagesInMenus( false );
+ aStyleSettings.SetHideDisabledMenuItems( true );
+ aStyleSettings.SetPreferredContextMenuShortcuts( false );
+
+ rSettings.SetStyleSettings( aStyleSettings );
+
+ // don't draw frame around each and every toolbar
+ ImplGetSVData()->maNWFData.mbDockingAreaAvoidTBFrames = true;
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'unlockFocus' is deprecated: first deprecated in macOS 10.14 - To draw, subclass NSView
+ // and implement -drawRect:; AppKit's automatic deferred display mechanism will call
+ // -drawRect: as necessary to display the view."
+ [mpNSView unlockFocus];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+}
+
+const SystemEnvData* AquaSalFrame::GetSystemData() const
+{
+ return &maSysData;
+}
+
+void AquaSalFrame::Beep()
+{
+ OSX_SALDATA_RUNINMAIN( Beep() )
+ NSBeep();
+}
+
+void AquaSalFrame::SetPosSize(
+ tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags)
+{
+ if (!mpNSWindow && !Application::IsBitmapRendering())
+ return;
+
+ OSX_SALDATA_RUNINMAIN( SetPosSize( nX, nY, nWidth, nHeight, nFlags ) )
+
+ SalEvent nEvent = PreparePosSize(nX, nY, nWidth, nHeight, nFlags);
+ if (Application::IsBitmapRendering())
+ return;
+
+ if( [mpNSWindow isMiniaturized] )
+ [mpNSWindow deminiaturize: NSApp]; // expand the window
+
+ NSRect aFrameRect = [mpNSWindow frame];
+ NSRect aContentRect = [NSWindow contentRectForFrameRect: aFrameRect styleMask: mnStyleMask];
+
+ // position is always relative to parent frame
+ NSRect aParentContentRect;
+
+ if( mpParent )
+ {
+ if( AllSettings::GetLayoutRTL() )
+ {
+ if( (nFlags & SAL_FRAME_POSSIZE_WIDTH) != 0 )
+ nX = static_cast<tools::Long>(mpParent->maGeometry.width()) - nWidth - 1 - nX;
+ else
+ nX = static_cast<tools::Long>(mpParent->maGeometry.width()) - aContentRect.size.width - 1 - nX;
+ }
+ NSRect aParentFrameRect = [mpParent->mpNSWindow frame];
+ aParentContentRect = [NSWindow contentRectForFrameRect: aParentFrameRect styleMask: mpParent->mnStyleMask];
+ }
+ else
+ aParentContentRect = maScreenRect; // use screen if no parent
+
+ CocoaToVCL( aContentRect );
+ CocoaToVCL( aParentContentRect );
+
+ bool bPaint = false;
+ if( (nFlags & (SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT)) != 0 )
+ {
+ if( nWidth != aContentRect.size.width || nHeight != aContentRect.size.height )
+ bPaint = true;
+ }
+
+ // use old window pos if no new pos requested
+ if( (nFlags & SAL_FRAME_POSSIZE_X) != 0 )
+ aContentRect.origin.x = nX + aParentContentRect.origin.x;
+ if( (nFlags & SAL_FRAME_POSSIZE_Y) != 0)
+ aContentRect.origin.y = nY + aParentContentRect.origin.y;
+
+ // use old size if no new size requested
+ if( (nFlags & SAL_FRAME_POSSIZE_WIDTH) != 0 )
+ aContentRect.size.width = nWidth;
+ if( (nFlags & SAL_FRAME_POSSIZE_HEIGHT) != 0)
+ aContentRect.size.height = nHeight;
+
+ VCLToCocoa( aContentRect );
+
+ // do not display yet, we need to update our backbuffer
+ {
+ [mpNSWindow setFrame: [NSWindow frameRectForContentRect: aContentRect styleMask: mnStyleMask] display: NO];
+ }
+
+ UpdateFrameGeometry();
+
+ if (nEvent != SalEvent::NONE)
+ CallCallback(nEvent, nullptr);
+
+ if( mbShown && bPaint )
+ {
+ // trigger filling our backbuffer
+ SendPaintEvent();
+
+ // now inform the system that the views need to be drawn
+ [mpNSWindow display];
+ }
+}
+
+void AquaSalFrame::GetWorkArea( AbsoluteScreenPixelRectangle& rRect )
+{
+ if (!mpNSWindow)
+ {
+ if (Application::IsBitmapRendering())
+ rRect = AbsoluteScreenPixelRectangle(AbsoluteScreenPixelPoint(0, 0), AbsoluteScreenPixelSize(1024, 768));
+ return;
+ }
+
+ OSX_SALDATA_RUNINMAIN( GetWorkArea( rRect ) )
+
+ NSScreen* pScreen = [mpNSWindow screen];
+ if( pScreen == nil )
+ pScreen = [NSScreen mainScreen];
+ NSRect aRect = [pScreen visibleFrame];
+ CocoaToVCL( aRect );
+ rRect.SetLeft( static_cast<tools::Long>(aRect.origin.x) );
+ rRect.SetTop( static_cast<tools::Long>(aRect.origin.y) );
+ rRect.SetRight( static_cast<tools::Long>(aRect.origin.x + aRect.size.width - 1) );
+ rRect.SetBottom( static_cast<tools::Long>(aRect.origin.y + aRect.size.height - 1) );
+}
+
+SalFrame::SalPointerState AquaSalFrame::GetPointerState()
+{
+ OSX_SALDATA_RUNINMAIN_UNION( GetPointerState(), state )
+
+ SalPointerState state;
+ state.mnState = 0;
+
+ // get position
+ NSPoint aPt = [mpNSWindow mouseLocationOutsideOfEventStream];
+ CocoaToVCL( aPt, false );
+ state.maPos = Point(static_cast<tools::Long>(aPt.x), static_cast<tools::Long>(aPt.y));
+
+ NSEvent* pCur = [NSApp currentEvent];
+ bool bMouseEvent = false;
+ if( pCur )
+ {
+ bMouseEvent = true;
+ switch( [pCur type] )
+ {
+ case NSEventTypeLeftMouseDown:
+ state.mnState |= MOUSE_LEFT;
+ break;
+ case NSEventTypeLeftMouseUp:
+ break;
+ case NSEventTypeRightMouseDown:
+ state.mnState |= MOUSE_RIGHT;
+ break;
+ case NSEventTypeRightMouseUp:
+ break;
+ case NSEventTypeOtherMouseDown:
+ state.mnState |= ([pCur buttonNumber] == 2) ? MOUSE_MIDDLE : 0;
+ break;
+ case NSEventTypeOtherMouseUp:
+ break;
+ case NSEventTypeMouseMoved:
+ break;
+ case NSEventTypeLeftMouseDragged:
+ state.mnState |= MOUSE_LEFT;
+ break;
+ case NSEventTypeRightMouseDragged:
+ state.mnState |= MOUSE_RIGHT;
+ break;
+ case NSEventTypeOtherMouseDragged:
+ state.mnState |= ([pCur buttonNumber] == 2) ? MOUSE_MIDDLE : 0;
+ break;
+ default:
+ bMouseEvent = false;
+ break;
+ }
+ }
+ if( bMouseEvent )
+ {
+ unsigned int nMask = static_cast<unsigned int>([pCur modifierFlags]);
+ if( (nMask & NSEventModifierFlagShift) != 0 )
+ state.mnState |= KEY_SHIFT;
+ if( (nMask & NSEventModifierFlagControl) != 0 )
+ state.mnState |= KEY_MOD3;
+ if( (nMask & NSEventModifierFlagOption) != 0 )
+ state.mnState |= KEY_MOD2;
+ if( (nMask & NSEventModifierFlagCommand) != 0 )
+ state.mnState |= KEY_MOD1;
+
+ }
+ else
+ {
+ // FIXME: replace Carbon by Cocoa
+ // Cocoa does not have an equivalent for GetCurrentEventButtonState
+ // and GetCurrentEventKeyModifiers.
+ // we could try to get away with tracking all events for modifierKeys
+ // and all mouse events for button state in VCL_NSApplication::sendEvent,
+ // but it is unclear whether this will get us the same result.
+ // leave in GetCurrentEventButtonState and GetCurrentEventKeyModifiers for now
+
+ // fill in button state
+ UInt32 nState = GetCurrentEventButtonState();
+ state.mnState = 0;
+ if( nState & 1 )
+ state.mnState |= MOUSE_LEFT; // primary button
+ if( nState & 2 )
+ state.mnState |= MOUSE_RIGHT; // secondary button
+ if( nState & 4 )
+ state.mnState |= MOUSE_MIDDLE; // tertiary button
+
+ // fill in modifier state
+ nState = GetCurrentEventKeyModifiers();
+ if( nState & shiftKey )
+ state.mnState |= KEY_SHIFT;
+ if( nState & controlKey )
+ state.mnState |= KEY_MOD3;
+ if( nState & optionKey )
+ state.mnState |= KEY_MOD2;
+ if( nState & cmdKey )
+ state.mnState |= KEY_MOD1;
+ }
+
+ return state;
+}
+
+KeyIndicatorState AquaSalFrame::GetIndicatorState()
+{
+ return KeyIndicatorState::NONE;
+}
+
+void AquaSalFrame::SimulateKeyPress( sal_uInt16 /*nKeyCode*/ )
+{
+}
+
+void AquaSalFrame::SetPluginParent( SystemParentData* )
+{
+ // plugin parent may be killed unexpectedly by
+ // plugging process;
+
+ //TODO: implement
+}
+
+bool AquaSalFrame::MapUnicodeToKeyCode( sal_Unicode , LanguageType , vcl::KeyCode& )
+{
+ // not supported yet
+ return false;
+}
+
+LanguageType AquaSalFrame::GetInputLanguage()
+{
+ //TODO: implement
+ return LANGUAGE_DONTKNOW;
+}
+
+void AquaSalFrame::SetMenu( SalMenu* pSalMenu )
+{
+ OSX_SALDATA_RUNINMAIN( SetMenu( pSalMenu ) )
+
+ AquaSalMenu* pMenu = static_cast<AquaSalMenu*>(pSalMenu);
+ SAL_WARN_IF( pMenu && !pMenu->mbMenuBar, "vcl", "setting non menubar on frame" );
+ mpMenu = pMenu;
+ if( mpMenu )
+ mpMenu->setMainMenu();
+}
+
+void AquaSalFrame::SetExtendedFrameStyle( SalExtStyle nStyle )
+{
+ if ( !mpNSWindow )
+ {
+ mnExtStyle = nStyle;
+ return;
+ }
+
+ OSX_SALDATA_RUNINMAIN( SetExtendedFrameStyle( nStyle ) )
+
+ if( (mnExtStyle & SAL_FRAME_EXT_STYLE_DOCMODIFIED) != (nStyle & SAL_FRAME_EXT_STYLE_DOCMODIFIED) )
+ [mpNSWindow setDocumentEdited: (nStyle & SAL_FRAME_EXT_STYLE_DOCMODIFIED) ? YES : NO];
+
+ mnExtStyle = nStyle;
+}
+
+SalFrame* AquaSalFrame::GetParent() const
+{
+ return mpParent;
+}
+
+void AquaSalFrame::SetParent( SalFrame* pNewParent )
+{
+ bool bShown = mbShown;
+ // remove from child list
+ if (bShown)
+ Show(false);
+ mpParent = static_cast<AquaSalFrame*>(pNewParent);
+ // insert to correct parent and paint
+ Show( bShown );
+}
+
+void AquaSalFrame::UpdateFrameGeometry()
+{
+ mbGeometryDidChange = false;
+
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( UpdateFrameGeometry() )
+
+ // keep in mind that view and window coordinates are lower left
+ // whereas vcl's are upper left
+
+ // update screen rect
+ NSScreen * pScreen = [mpNSWindow screen];
+ if( pScreen )
+ {
+ NSRect aNewScreenRect = [pScreen frame];
+ if (!NSEqualRects(maScreenRect, aNewScreenRect))
+ {
+ mbGeometryDidChange = true;
+ maScreenRect = aNewScreenRect;
+ }
+ NSArray* pScreens = [NSScreen screens];
+ if( pScreens )
+ {
+ unsigned int nNewDisplayScreenNumber = [pScreens indexOfObject: pScreen];
+ if (maGeometry.screen() != nNewDisplayScreenNumber)
+ {
+ mbGeometryDidChange = true;
+ maGeometry.setScreen(nNewDisplayScreenNumber);
+ }
+ }
+ }
+
+ NSRect aFrameRect = [mpNSWindow frame];
+ NSRect aContentRect = [NSWindow contentRectForFrameRect: aFrameRect styleMask: mnStyleMask];
+
+ NSRect aTrackRect = { NSZeroPoint, aContentRect.size };
+
+ if (!NSEqualRects(maTrackingRect, aTrackRect))
+ {
+ mbGeometryDidChange = true;
+ maTrackingRect = aTrackRect;
+ }
+
+ // convert to vcl convention
+ CocoaToVCL( aFrameRect );
+ CocoaToVCL( aContentRect );
+
+ if (!NSEqualRects(maContentRect, aContentRect) || !NSEqualRects(maFrameRect, aFrameRect))
+ {
+ mbGeometryDidChange = true;
+
+ maContentRect = aContentRect;
+ maFrameRect = aFrameRect;
+
+ maGeometry.setX(static_cast<sal_Int32>(aContentRect.origin.x));
+ maGeometry.setY(static_cast<sal_Int32>(aContentRect.origin.y));
+ maGeometry.setWidth(static_cast<sal_uInt32>(aContentRect.size.width));
+ maGeometry.setHeight(static_cast<sal_uInt32>(aContentRect.size.height));
+
+ maGeometry.setLeftDecoration(static_cast<sal_uInt32>(aContentRect.origin.x - aFrameRect.origin.x));
+ maGeometry.setRightDecoration(static_cast<sal_uInt32>((aFrameRect.origin.x + aFrameRect.size.width) -
+ (aContentRect.origin.x + aContentRect.size.width)));
+ maGeometry.setTopDecoration(static_cast<sal_uInt32>(aContentRect.origin.y - aFrameRect.origin.y));
+ maGeometry.setBottomDecoration(static_cast<sal_uInt32>((aFrameRect.origin.y + aFrameRect.size.height) -
+ (aContentRect.origin.y + aContentRect.size.height)));
+ }
+}
+
+void AquaSalFrame::CaptureMouse( bool bCapture )
+{
+ /* Remark:
+ we'll try to use a pidgin version of capture mouse
+ on MacOSX (neither carbon nor cocoa) there is a
+ CaptureMouse equivalent (in Carbon there is TrackMouseLocation
+ but this is useless to use since it is blocking)
+
+ However on cocoa the active frame seems to get mouse events
+ also outside the window, so we'll try to forward mouse events
+ to the capture frame in the hope that one of our frames
+ gets a mouse event.
+
+ This will break as soon as the user activates another app, but
+ a mouse click will normally lead to a release of the mouse anyway.
+
+ Let's see how far we get this way. Alternatively we could use one
+ large overlay window like we did for the carbon implementation,
+ however that is resource intensive.
+ */
+
+ if( bCapture )
+ s_pCaptureFrame = this;
+ else if( ! bCapture && s_pCaptureFrame == this )
+ s_pCaptureFrame = nullptr;
+}
+
+void AquaSalFrame::ResetClipRegion()
+{
+ doResetClipRegion();
+}
+
+void AquaSalFrame::doResetClipRegion()
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( ResetClipRegion() )
+
+ // release old path and indicate no clipping
+ CGPathRelease( mrClippingPath );
+ mrClippingPath = nullptr;
+
+ if( mpNSView && mbShown )
+ [mpNSView setNeedsDisplay: YES];
+ [mpNSWindow setOpaque: YES];
+ [mpNSWindow invalidateShadow];
+}
+
+void AquaSalFrame::BeginSetClipRegion( sal_uInt32 nRects )
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( BeginSetClipRegion( nRects ) )
+
+ // release old path
+ if( mrClippingPath )
+ {
+ CGPathRelease( mrClippingPath );
+ mrClippingPath = nullptr;
+ }
+
+ if( maClippingRects.size() > SAL_CLIPRECT_COUNT && nRects < maClippingRects.size() )
+ {
+ std::vector<CGRect>().swap(maClippingRects);
+ }
+ maClippingRects.clear();
+ maClippingRects.reserve( nRects );
+}
+
+void AquaSalFrame::UnionClipRegion(
+ tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ // #i113170# may not be the main thread if called from UNO API
+ SalData::ensureThreadAutoreleasePool();
+
+ if( nWidth && nHeight )
+ {
+ NSRect aRect = { { static_cast<CGFloat>(nX), static_cast<CGFloat>(nY) }, { static_cast<CGFloat>(nWidth), static_cast<CGFloat>(nHeight) } };
+ VCLToCocoa( aRect, false );
+ maClippingRects.push_back( CGRectMake(aRect.origin.x, aRect.origin.y, aRect.size.width, aRect.size.height) );
+ }
+}
+
+void AquaSalFrame::EndSetClipRegion()
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( EndSetClipRegion() )
+
+ if( ! maClippingRects.empty() )
+ {
+ mrClippingPath = CGPathCreateMutable();
+ CGPathAddRects( mrClippingPath, nullptr, maClippingRects.data(), maClippingRects.size() );
+ }
+ if( mpNSView && mbShown )
+ [mpNSView setNeedsDisplay: YES];
+ [mpNSWindow setOpaque: (mrClippingPath != nullptr) ? NO : YES];
+ [mpNSWindow setBackgroundColor: [NSColor clearColor]];
+ // shadow is invalidated when view gets drawn again
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salframeview.mm b/vcl/osx/salframeview.mm
new file mode 100644
index 0000000000..995eeb5749
--- /dev/null
+++ b/vcl/osx/salframeview.mm
@@ -0,0 +1,2595 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <sal/macros.h>
+#include <tools/helpers.hxx>
+#include <tools/long.hxx>
+#include <vcl/event.hxx>
+#include <vcl/inputctx.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/commandevent.hxx>
+
+#include <osx/a11yfactory.h>
+#include <osx/salframe.h>
+#include <osx/salframeview.h>
+#include <osx/salinst.h>
+#include <quartz/salgdi.h>
+#include <quartz/utils.h>
+
+#if HAVE_FEATURE_SKIA
+#include <vcl/skia/SkiaHelper.hxx>
+#endif
+
+#define WHEEL_EVENT_FACTOR 1.5
+
+static sal_uInt16 ImplGetModifierMask( unsigned int nMask )
+{
+ sal_uInt16 nRet = 0;
+ if( (nMask & NSEventModifierFlagShift) != 0 )
+ nRet |= KEY_SHIFT;
+ if( (nMask & NSEventModifierFlagControl) != 0 )
+ nRet |= KEY_MOD3;
+ if( (nMask & NSEventModifierFlagOption) != 0 )
+ nRet |= KEY_MOD2;
+ if( (nMask & NSEventModifierFlagCommand) != 0 )
+ nRet |= KEY_MOD1;
+ return nRet;
+}
+
+static sal_uInt16 ImplMapCharCode( sal_Unicode aCode )
+{
+ static sal_uInt16 aKeyCodeMap[ 128 ] =
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ KEY_BACKSPACE, KEY_TAB, KEY_RETURN, 0, 0, KEY_RETURN, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, KEY_TAB, 0, KEY_ESCAPE, 0, 0, 0, 0,
+ KEY_SPACE, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, KEY_MULTIPLY, KEY_ADD, KEY_COMMA, KEY_SUBTRACT, KEY_POINT, KEY_DIVIDE,
+ KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7,
+ KEY_8, KEY_9, 0, 0, KEY_LESS, KEY_EQUAL, KEY_GREATER, 0,
+ 0, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G,
+ KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O,
+ KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W,
+ KEY_X, KEY_Y, KEY_Z, 0, 0, 0, 0, 0,
+ KEY_QUOTELEFT, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G,
+ KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O,
+ KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W,
+ KEY_X, KEY_Y, KEY_Z, 0, 0, 0, KEY_TILDE, KEY_BACKSPACE
+ };
+
+ // Note: the mapping 0x7f should by rights be KEY_DELETE
+ // however if you press "backspace" 0x7f is reported
+ // whereas for "delete" 0xf728 gets reported
+
+ // Note: the mapping of 0x19 to KEY_TAB is because for unknown reasons
+ // tab alone is reported as 0x09 (as expected) but shift-tab is
+ // reported as 0x19 (end of medium)
+
+ static sal_uInt16 aFunctionKeyCodeMap[ 128 ] =
+ {
+ KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_F1, KEY_F2, KEY_F3, KEY_F4,
+ KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12,
+ KEY_F13, KEY_F14, KEY_F15, KEY_F16, KEY_F17, KEY_F18, KEY_F19, KEY_F20,
+ KEY_F21, KEY_F22, KEY_F23, KEY_F24, KEY_F25, KEY_F26, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, KEY_INSERT,
+ KEY_DELETE, KEY_HOME, 0, KEY_END, KEY_PAGEUP, KEY_PAGEDOWN, 0, 0,
+ 0, 0, 0, 0, 0, KEY_MENU, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, KEY_UNDO, KEY_REPEAT, KEY_FIND, KEY_HELP, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+ };
+
+ sal_uInt16 nKeyCode = 0;
+ if( aCode < SAL_N_ELEMENTS( aKeyCodeMap) )
+ nKeyCode = aKeyCodeMap[ aCode ];
+ else if( aCode >= 0xf700 && aCode < 0xf780 )
+ nKeyCode = aFunctionKeyCodeMap[ aCode - 0xf700 ];
+ return nKeyCode;
+}
+
+static sal_uInt16 ImplMapKeyCode(sal_uInt16 nKeyCode)
+{
+ /*
+ http://stackoverflow.com/questions/2080312/where-can-i-find-a-list-of-key-codes-for-use-with-cocoas-nsevent-class/2080324#2080324
+ /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h
+ */
+
+ static sal_uInt16 aKeyCodeMap[ 0x80 ] =
+ {
+ KEY_A, KEY_S, KEY_D, KEY_F, KEY_H, KEY_G, KEY_Z, KEY_X,
+ KEY_C, KEY_V, 0, KEY_B, KEY_Q, KEY_W, KEY_E, KEY_R,
+ KEY_Y, KEY_T, KEY_1, KEY_2, KEY_3, KEY_4, KEY_6, KEY_5,
+ KEY_EQUAL, KEY_9, KEY_7, KEY_SUBTRACT, KEY_8, KEY_0, KEY_BRACKETRIGHT, KEY_RIGHTCURLYBRACKET,
+ KEY_U, KEY_BRACKETLEFT, KEY_I, KEY_P, KEY_RETURN, KEY_L, KEY_J, KEY_QUOTERIGHT,
+ KEY_K, KEY_SEMICOLON, 0, KEY_COMMA, KEY_DIVIDE, KEY_N, KEY_M, KEY_POINT,
+ KEY_TAB, KEY_SPACE, KEY_QUOTELEFT, KEY_DELETE, 0, KEY_ESCAPE, 0, 0,
+ 0, KEY_CAPSLOCK, 0, 0, 0, 0, 0, 0,
+ KEY_F17, KEY_DECIMAL, 0, KEY_MULTIPLY, 0, KEY_ADD, 0, 0,
+ 0, 0, 0, KEY_DIVIDE, KEY_RETURN, 0, KEY_SUBTRACT, KEY_F18,
+ KEY_F19, KEY_EQUAL, 0, 0, 0, 0, 0, 0,
+ 0, 0, KEY_F20, 0, 0, 0, 0, 0,
+ KEY_F5, KEY_F6, KEY_F7, KEY_F3, KEY_F8, KEY_F9, 0, KEY_F11,
+ 0, KEY_F13, KEY_F16, KEY_F14, 0, KEY_F10, 0, KEY_F12,
+ 0, KEY_F15, KEY_HELP, KEY_HOME, KEY_PAGEUP, KEY_DELETE, KEY_F4, KEY_END,
+ KEY_F2, KEY_PAGEDOWN, KEY_F1, KEY_LEFT, KEY_RIGHT, KEY_DOWN, KEY_UP, 0
+ };
+
+ if (nKeyCode < SAL_N_ELEMENTS(aKeyCodeMap))
+ return aKeyCodeMap[nKeyCode];
+ return 0;
+}
+
+// store the frame the mouse last entered
+static AquaSalFrame* s_pMouseFrame = nullptr;
+// store the last pressed button for enter/exit events
+// which lack that information
+static sal_uInt16 s_nLastButton = 0;
+
+static AquaSalFrame* getMouseContainerFrame()
+{
+ AquaSalFrame* pDispatchFrame = nullptr;
+ NSArray* aWindows = [NSWindow windowNumbersWithOptions:0];
+ for(NSUInteger i = 0; i < [aWindows count] && ! pDispatchFrame; i++ )
+ {
+ NSWindow* pWin = [NSApp windowWithWindowNumber:[[aWindows objectAtIndex:i] integerValue]];
+ if( pWin && [pWin isMemberOfClass: [SalFrameWindow class]] && [static_cast<SalFrameWindow*>(pWin) containsMouse] )
+ pDispatchFrame = [static_cast<SalFrameWindow*>(pWin) getSalFrame];
+ }
+ return pDispatchFrame;
+}
+
+static NSArray *getMergedAccessibilityChildren(NSArray *pDefaultChildren, NSArray *pUnignoredChildrenToAdd)
+{
+ NSArray *pRet = pDefaultChildren;
+
+ if (pUnignoredChildrenToAdd && [pUnignoredChildrenToAdd count])
+ {
+ NSMutableArray *pNewChildren = [NSMutableArray arrayWithCapacity:(pRet ? [pRet count] : 0) + 1];
+ if (pNewChildren)
+ {
+ if (pRet)
+ [pNewChildren addObjectsFromArray:pRet];
+
+ for (AquaA11yWrapper *pWrapper : pUnignoredChildrenToAdd)
+ {
+ if (pWrapper && ![pNewChildren containsObject:pWrapper])
+ [pNewChildren addObject:pWrapper];
+ }
+
+ pRet = pNewChildren;
+ }
+ else
+ {
+ pRet = pUnignoredChildrenToAdd;
+ }
+ }
+
+ return pRet;
+}
+
+// Update ImplGetSVData()->mpWinData->mbIsLiveResize
+static void updateWinDataInLiveResize(bool bInLiveResize)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ assert( pSVData );
+ if ( pSVData )
+ {
+ if ( pSVData->mpWinData->mbIsLiveResize != bInLiveResize )
+ {
+ pSVData->mpWinData->mbIsLiveResize = bInLiveResize;
+ Scheduler::Wakeup();
+ }
+ }
+}
+
+@interface NSResponder (SalFrameWindow)
+-(BOOL)accessibilityIsIgnored;
+@end
+
+@implementation SalFrameWindow
+-(id)initWithSalFrame: (AquaSalFrame*)pFrame
+{
+ mDraggingDestinationHandler = nil;
+ mbInWindowDidResize = NO;
+ mpLiveResizeTimer = nil;
+ mpFrame = pFrame;
+ NSRect aRect = { { static_cast<CGFloat>(pFrame->maGeometry.x()), static_cast<CGFloat>(pFrame->maGeometry.y()) },
+ { static_cast<CGFloat>(pFrame->maGeometry.width()), static_cast<CGFloat>(pFrame->maGeometry.height()) } };
+ pFrame->VCLToCocoa( aRect );
+ NSWindow* pNSWindow = [super initWithContentRect: aRect
+ styleMask: mpFrame->getStyleMask()
+ backing: NSBackingStoreBuffered
+ defer: Application::IsHeadlessModeEnabled()];
+
+ // Disallow full-screen mode on macOS >= 10.11 where it is enabled by default. We don't want it
+ // for now as it will just be confused with LibreOffice's home-grown full-screen concept, with
+ // which it has nothing to do, and one can get into all kinds of weird states by using them
+ // intermixedly.
+
+ // Ideally we should use the system full-screen mode and adapt the code for the home-grown thing
+ // to be in sync with that instead. (And we would then not need the button to get out of
+ // full-screen mode, as the normal way to get out of it is to either click on the green bubble
+ // again, or invoke the keyboard command again.)
+
+ // (Confusingly, at the moment the home-grown full-screen mode is bound to Cmd+Shift+F, which is
+ // the keyboard command normally used in apps to get in and out of the system full-screen mode.)
+
+ // Disabling system full-screen mode makes the green button on the title bar (on macOS >= 10.11)
+ // show a plus sign instead, and clicking it becomes identical to double-clicking the title bar,
+ // i.e. it maximizes / unmaximises the window. Sure, that state can also be confused with LO's
+ // home-grown full-screen mode. Oh well.
+
+ [pNSWindow setCollectionBehavior: NSWindowCollectionBehaviorFullScreenNone];
+
+ // Disable window restoration until we support it directly
+ [pNSWindow setRestorable: NO];
+
+ // tdf#137468: Restrict to 24-bit RGB as that is all that we can
+ // handle anyway. HDR is far off in the future for LibreOffice.
+ [pNSWindow setDynamicDepthLimit: NO];
+ [pNSWindow setDepthLimit: NSWindowDepthTwentyfourBitRGB];
+
+ return static_cast<SalFrameWindow *>(pNSWindow);
+}
+
+-(void)clearLiveResizeTimer
+{
+ if ( mpLiveResizeTimer )
+ {
+ [mpLiveResizeTimer invalidate];
+ [mpLiveResizeTimer release];
+ mpLiveResizeTimer = nil;
+ }
+}
+
+-(void)dealloc
+{
+ [self clearLiveResizeTimer];
+ [super dealloc];
+}
+
+-(AquaSalFrame*)getSalFrame
+{
+ return mpFrame;
+}
+
+-(void)displayIfNeeded
+{
+ if( GetSalData() && GetSalData()->mpInstance )
+ {
+ SolarMutexGuard aGuard;
+ [super displayIfNeeded];
+ }
+}
+
+-(BOOL)containsMouse
+{
+ // is this event actually inside that NSWindow ?
+ NSPoint aPt = [NSEvent mouseLocation];
+ NSRect aFrameRect = [self frame];
+ bool bInRect = NSPointInRect( aPt, aFrameRect );
+ return bInRect;
+}
+
+-(BOOL)canBecomeKeyWindow
+{
+ if( (mpFrame->mnStyle &
+ ( SalFrameStyleFlags::FLOAT |
+ SalFrameStyleFlags::TOOLTIP |
+ SalFrameStyleFlags::INTRO
+ )) == SalFrameStyleFlags::NONE )
+ return YES;
+ if( mpFrame->mnStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
+ return YES;
+ if( mpFrame->mbFullScreen )
+ return YES;
+ return [super canBecomeKeyWindow];
+}
+
+-(void)windowDidBecomeKey: (NSNotification*)pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ static const SalFrameStyleFlags nGuessDocument = SalFrameStyleFlags::MOVEABLE|
+ SalFrameStyleFlags::SIZEABLE|
+ SalFrameStyleFlags::CLOSEABLE;
+
+ // Reset dark mode colors in HITheme controls after printing
+ // In dark mode, after an NSPrintOperation has completed, macOS draws
+ // HITheme controls with light mode colors so reset all dark mode
+ // colors when an NSWindow gains focus.
+ mpFrame->UpdateDarkMode();
+
+ if( mpFrame->mpMenu )
+ mpFrame->mpMenu->setMainMenu();
+ else if( ! mpFrame->mpParent &&
+ ( (mpFrame->mnStyle & nGuessDocument) == nGuessDocument || // set default menu for e.g. help
+ mpFrame->mbFullScreen ) ) // set default menu for e.g. presentation
+ {
+ AquaSalMenu::setDefaultMenu();
+ }
+ mpFrame->CallCallback( SalEvent::GetFocus, nullptr );
+ mpFrame->SendPaintEvent(); // repaint controls as active
+ }
+
+ // Prevent the same native input method popup that was cancelled in a
+ // previous call to [self windowDidResignKey:] from reappearing
+ [self endExtTextInput];
+}
+
+-(void)windowDidResignKey: (NSNotification*)pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ // Commit any uncommitted text and cancel the native input method session
+ // whenever a window loses focus like in Safari, Firefox, and Excel
+ [self endExtTextInput];
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->CallCallback(SalEvent::LoseFocus, nullptr);
+ mpFrame->SendPaintEvent(); // repaint controls as inactive
+ }
+}
+
+-(void)windowDidChangeScreen: (NSNotification*)pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ mpFrame->screenParametersChanged();
+}
+
+-(void)windowDidMove: (NSNotification*)pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->UpdateFrameGeometry();
+ mpFrame->CallCallback( SalEvent::Move, nullptr );
+ }
+}
+
+-(void)windowDidResize: (NSNotification*)pNotification
+{
+ SolarMutexGuard aGuard;
+
+ if ( mbInWindowDidResize )
+ return;
+
+ mbInWindowDidResize = YES;
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->UpdateFrameGeometry();
+ mpFrame->CallCallback( SalEvent::Resize, nullptr );
+
+ updateWinDataInLiveResize( [self inLiveResize] );
+ if ( ImplGetSVData()->mpWinData->mbIsLiveResize )
+ {
+#if HAVE_FEATURE_SKIA
+ // Related: tdf#152703 Eliminate empty window with Skia/Metal while resizing
+ // The window will clear its background so when Skia/Metal is
+ // enabled, explicitly flush the Skia graphics to the window
+ // during live resizing or else nothing will be drawn until after
+ // live resizing has ended.
+ // Also, flushing during [self windowDidResize:] eliminates flicker
+ // by forcing this window's SkSurface to recreate its underlying
+ // CAMetalLayer with the new size. Flushing in
+ // [self displayIfNeeded] does not eliminate flicker so apparently
+ // [self windowDidResize:] is called earlier.
+ if ( SkiaHelper::isVCLSkiaEnabled() )
+ {
+ AquaSalGraphics* pGraphics = mpFrame->mpGraphics;
+ if ( pGraphics )
+ pGraphics->Flush();
+ }
+#endif
+
+ // tdf#152703 Force relayout during live resizing of window
+ // During a live resize, macOS floods the application with
+ // windowDidResize: notifications so sending a paint event does
+ // not trigger redrawing with the new size.
+ // Instead, force relayout by dispatching all pending internal
+ // events and firing any pending timers.
+ // Also, Application::Reschedule() can potentially display a
+ // modal dialog which will cause a hang so temporarily disable
+ // live resize by clamping the window's minimum and maximum sizes
+ // to the current frame size which in Application::Reschedule().
+ NSRect aFrame = [self frame];
+ NSSize aMinSize = [self minSize];
+ NSSize aMaxSize = [self maxSize];
+ [self setMinSize:aFrame.size];
+ [self setMaxSize:aFrame.size];
+ Application::Reschedule( true );
+ [self setMinSize:aMinSize];
+ [self setMaxSize:aMaxSize];
+
+ if ( ImplGetSVData()->mpWinData->mbIsLiveResize )
+ {
+ // tdf#152703 Force repaint after live resizing ends
+ // Repost this notification so that this selector will be called
+ // at least once after live resizing ends
+ if ( !mpLiveResizeTimer )
+ {
+ mpLiveResizeTimer = [NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(windowDidResizeWithTimer:) userInfo:pNotification repeats:YES];
+ if ( mpLiveResizeTimer )
+ {
+ [mpLiveResizeTimer retain];
+
+ // The timer won't fire without a call to
+ // Application::Reschedule() unless we copy the fix for
+ // #i84055# from vcl/osx/saltimer.cxx and add the timer
+ // to the NSEventTrackingRunLoopMode run loop mode
+ [[NSRunLoop currentRunLoop] addTimer:mpLiveResizeTimer forMode:NSEventTrackingRunLoopMode];
+ }
+ }
+ }
+ }
+ else
+ {
+ [self clearLiveResizeTimer];
+ }
+
+ // tdf#158461 eliminate flicker during live resizing
+ // When using Skia/Metal, the window content will flicker while
+ // live resizing a window if we don't send a paint event.
+ mpFrame->SendPaintEvent();
+ }
+
+ mbInWindowDidResize = NO;
+}
+
+-(void)windowDidMiniaturize: (NSNotification*)pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->mbShown = false;
+ mpFrame->UpdateFrameGeometry();
+ mpFrame->CallCallback( SalEvent::Resize, nullptr );
+ }
+}
+
+-(void)windowDidDeminiaturize: (NSNotification*)pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->mbShown = true;
+ mpFrame->UpdateFrameGeometry();
+ mpFrame->CallCallback( SalEvent::Resize, nullptr );
+ }
+}
+
+-(BOOL)windowShouldClose: (NSNotification*)pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ bool bRet = true;
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ // #i84461# end possible input
+ [self endExtTextInput];
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->CallCallback( SalEvent::Close, nullptr );
+ bRet = false; // application will close the window or not, AppKit shouldn't
+ AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
+ assert( pTimer );
+ pTimer->handleWindowShouldClose();
+ }
+ }
+
+ return bRet;
+}
+
+-(void)windowDidEnterFullScreen: (NSNotification*)pNotification
+{
+ SolarMutexGuard aGuard;
+
+ if( !mpFrame || !AquaSalFrame::isAlive( mpFrame))
+ return;
+ mpFrame->mbFullScreen = true;
+ (void)pNotification;
+}
+
+-(void)windowDidExitFullScreen: (NSNotification*)pNotification
+{
+ SolarMutexGuard aGuard;
+
+ if( !mpFrame || !AquaSalFrame::isAlive( mpFrame))
+ return;
+ mpFrame->mbFullScreen = false;
+ (void)pNotification;
+}
+
+-(void)windowDidChangeBackingProperties:(NSNotification *)pNotification
+{
+ (void)pNotification;
+#if HAVE_FEATURE_SKIA
+ SolarMutexGuard aGuard;
+
+ sal::aqua::resetWindowScaling();
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ // tdf#147342 Notify Skia that the window's backing properties changed
+ if ( SkiaHelper::isVCLSkiaEnabled() )
+ {
+ AquaSalGraphics* pGraphics = mpFrame->mpGraphics;
+ if ( pGraphics )
+ pGraphics->WindowBackingPropertiesChanged();
+ }
+ }
+#endif
+}
+
+-(void)windowWillStartLiveResize:(NSNotification *)pNotification
+{
+ SolarMutexGuard aGuard;
+
+ updateWinDataInLiveResize(true);
+}
+
+-(void)windowDidEndLiveResize:(NSNotification *)pNotification
+{
+ SolarMutexGuard aGuard;
+
+ updateWinDataInLiveResize(false);
+}
+
+-(void)dockMenuItemTriggered: (id)sender
+{
+ (void)sender;
+ SolarMutexGuard aGuard;
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ mpFrame->ToTop( SalFrameToTop::RestoreWhenMin | SalFrameToTop::GrabFocus );
+}
+
+-(css::uno::Reference < css::accessibility::XAccessibleContext >)accessibleContext
+{
+ return mpFrame -> GetWindow() -> GetAccessible() -> getAccessibleContext();
+}
+
+-(BOOL)isIgnoredWindow
+{
+ SolarMutexGuard aGuard;
+
+ // Treat tooltip windows as ignored
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ return (mpFrame->mnStyle & SalFrameStyleFlags::TOOLTIP) != SalFrameStyleFlags::NONE;
+ return YES;
+}
+
+-(id)accessibilityApplicationFocusedUIElement
+{
+ return [self accessibilityFocusedUIElement];
+}
+
+-(id)accessibilityFocusedUIElement
+{
+ // Treat tooltip windows as ignored
+ if ([self isIgnoredWindow])
+ return nil;
+
+ return [super accessibilityFocusedUIElement];
+}
+
+-(BOOL)accessibilityIsIgnored
+{
+ // Treat tooltip windows as ignored
+ if ([self isIgnoredWindow])
+ return YES;
+
+ return [super accessibilityIsIgnored];
+}
+
+-(BOOL)isAccessibilityElement
+{
+ return ![self accessibilityIsIgnored];
+}
+
+-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+{
+ return [mDraggingDestinationHandler draggingEntered: sender];
+}
+
+-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
+{
+ return [mDraggingDestinationHandler draggingUpdated: sender];
+}
+
+-(void)draggingExited:(id <NSDraggingInfo>)sender
+{
+ [mDraggingDestinationHandler draggingExited: sender];
+}
+
+-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
+{
+ return [mDraggingDestinationHandler prepareForDragOperation: sender];
+}
+
+-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+{
+ return [mDraggingDestinationHandler performDragOperation: sender];
+}
+
+-(void)concludeDragOperation:(id <NSDraggingInfo>)sender
+{
+ [mDraggingDestinationHandler concludeDragOperation: sender];
+}
+
+-(void)registerDraggingDestinationHandler:(id)theHandler
+{
+ mDraggingDestinationHandler = theHandler;
+}
+
+-(void)unregisterDraggingDestinationHandler:(id)theHandler
+{
+ (void)theHandler;
+ mDraggingDestinationHandler = nil;
+}
+
+-(void)endExtTextInput
+{
+ [self endExtTextInput:EndExtTextInputFlags::Complete];
+}
+
+-(void)endExtTextInput:(EndExtTextInputFlags)nFlags
+{
+ SalFrameView *pView = static_cast<SalFrameView*>([self firstResponder]);
+ if (pView && [pView isKindOfClass:[SalFrameView class]])
+ [pView endExtTextInput:nFlags];
+}
+
+-(void)windowDidResizeWithTimer:(NSTimer *)pTimer
+{
+ if ( pTimer )
+ [self windowDidResize:[pTimer userInfo]];
+}
+
+@end
+
+@implementation SalFrameView
++(void)unsetMouseFrame: (AquaSalFrame*)pFrame
+{
+ if( pFrame == s_pMouseFrame )
+ s_pMouseFrame = nullptr;
+}
+
+-(id)initWithSalFrame: (AquaSalFrame*)pFrame
+{
+ if ((self = [super initWithFrame: [NSWindow contentRectForFrameRect: [pFrame->getNSWindow() frame] styleMask: pFrame->mnStyleMask]]) != nil)
+ {
+ mDraggingDestinationHandler = nil;
+ mpFrame = pFrame;
+ mpChildWrapper = nil;
+ mbNeedChildWrapper = NO;
+ mpLastEvent = nil;
+ mMarkedRange = NSMakeRange(NSNotFound, 0);
+ mSelectedRange = NSMakeRange(NSNotFound, 0);
+ mpMouseEventListener = nil;
+ mpLastSuperEvent = nil;
+ mfLastMagnifyTime = 0.0;
+
+ mbInEndExtTextInput = NO;
+ mbInCommitMarkedText = NO;
+ mpLastMarkedText = nil;
+ mbTextInputWantsNonRepeatKeyDown = NO;
+ }
+
+ return self;
+}
+
+-(void)dealloc
+{
+ [self clearLastEvent];
+ [self clearLastMarkedText];
+ [self revokeWrapper];
+
+ [super dealloc];
+}
+
+-(AquaSalFrame*)getSalFrame
+{
+ return mpFrame;
+}
+
+-(void)resetCursorRects
+{
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ // FIXME: does this leak the returned NSCursor of getCurrentCursor ?
+ const NSRect aRect = { NSZeroPoint, NSMakeSize(mpFrame->maGeometry.width(), mpFrame->maGeometry.height()) };
+ [self addCursorRect: aRect cursor: mpFrame->getCurrentCursor()];
+ }
+}
+
+-(BOOL)acceptsFirstResponder
+{
+ return YES;
+}
+
+-(BOOL)acceptsFirstMouse: (NSEvent*)pEvent
+{
+ (void)pEvent;
+ return YES;
+}
+
+-(BOOL)isOpaque
+{
+ if( !mpFrame)
+ return YES;
+ if( !AquaSalFrame::isAlive( mpFrame))
+ return YES;
+ if( !mpFrame->getClipPath())
+ return YES;
+ return NO;
+}
+
+-(void)drawRect: (NSRect)aRect
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ assert( pSVData );
+ if ( !pSVData )
+ return;
+
+ SolarMutexGuard aGuard;
+ if (!mpFrame || !AquaSalFrame::isAlive(mpFrame))
+ return;
+
+ updateWinDataInLiveResize([self inLiveResize]);
+
+ AquaSalGraphics* pGraphics = mpFrame->mpGraphics;
+ if (pGraphics)
+ {
+ pGraphics->UpdateWindow(aRect);
+ if (mpFrame->getClipPath())
+ [mpFrame->getNSWindow() invalidateShadow];
+ }
+}
+
+-(void)sendMouseEventToFrame: (NSEvent*)pEvent button:(sal_uInt16)nButton eventtype:(SalEvent)nEvent
+{
+ SolarMutexGuard aGuard;
+
+ AquaSalFrame* pDispatchFrame = AquaSalFrame::GetCaptureFrame();
+ bool bIsCaptured = false;
+ if( pDispatchFrame )
+ {
+ bIsCaptured = true;
+ if( nEvent == SalEvent::MouseLeave ) // no leave events if mouse is captured
+ nEvent = SalEvent::MouseMove;
+ }
+ else if( s_pMouseFrame )
+ pDispatchFrame = s_pMouseFrame;
+ else
+ pDispatchFrame = mpFrame;
+
+ /* #i81645# Cocoa reports mouse events while a button is pressed
+ to the window in which it was first pressed. This is reasonable and fine and
+ gets one around most cases where on other platforms one uses CaptureMouse or XGrabPointer,
+ however vcl expects mouse events to occur in the window the mouse is over, unless the
+ mouse is explicitly captured. So we need to find the window the mouse is actually
+ over for conformance with other platforms.
+ */
+ if( ! bIsCaptured && nButton && pDispatchFrame && AquaSalFrame::isAlive( pDispatchFrame ) )
+ {
+ // is this event actually inside that NSWindow ?
+ NSPoint aPt = [NSEvent mouseLocation];
+ NSRect aFrameRect = [pDispatchFrame->getNSWindow() frame];
+
+ if ( ! NSPointInRect( aPt, aFrameRect ) )
+ {
+ // no, it is not
+ // now we need to find the one it may be in
+ /* #i93756# we ant to get enumerate the application windows in z-order
+ to check if any contains the mouse. This could be elegantly done with this
+ code:
+
+ // use NSApp to check windows in ZOrder whether they contain the mouse pointer
+ NSWindow* pWindow = [NSApp makeWindowsPerform: @selector(containsMouse) inOrder: YES];
+ if( pWindow && [pWindow isMemberOfClass: [SalFrameWindow class]] )
+ pDispatchFrame = [(SalFrameWindow*)pWindow getSalFrame];
+
+ However if a non SalFrameWindow is on screen (like e.g. the file dialog)
+ it can be hit with the containsMouse selector, which it doesn't support.
+ Sadly NSApplication:makeWindowsPerform does not check (for performance reasons
+ I assume) whether a window supports a selector before sending it.
+ */
+ AquaSalFrame* pMouseFrame = getMouseContainerFrame();
+ if( pMouseFrame )
+ pDispatchFrame = pMouseFrame;
+ }
+ }
+
+ if( pDispatchFrame && AquaSalFrame::isAlive( pDispatchFrame ) )
+ {
+ pDispatchFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
+ pDispatchFrame->mnLastModifierFlags = [pEvent modifierFlags];
+
+ NSPoint aPt = [NSEvent mouseLocation];
+ pDispatchFrame->CocoaToVCL( aPt );
+
+ sal_uInt16 nModMask = ImplGetModifierMask( [pEvent modifierFlags] );
+ // #i82284# emulate ctrl left
+ if( nModMask == KEY_MOD3 && nButton == MOUSE_LEFT )
+ {
+ nModMask = 0;
+ nButton = MOUSE_RIGHT;
+ }
+
+ SalMouseEvent aEvent;
+ aEvent.mnTime = pDispatchFrame->mnLastEventTime;
+ aEvent.mnX = static_cast<tools::Long>(aPt.x) - pDispatchFrame->maGeometry.x();
+ aEvent.mnY = static_cast<tools::Long>(aPt.y) - pDispatchFrame->maGeometry.y();
+ aEvent.mnButton = nButton;
+ aEvent.mnCode = aEvent.mnButton | nModMask;
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = pDispatchFrame->maGeometry.width() - 1 - aEvent.mnX;
+
+ pDispatchFrame->CallCallback( nEvent, &aEvent );
+
+ // tdf#155266 force flush after scrolling
+ if (nButton == MOUSE_LEFT && nEvent == SalEvent::MouseMove)
+ mpFrame->mbForceFlush = true;
+ }
+}
+
+-(void)mouseDown: (NSEvent*)pEvent
+{
+ if ( mpMouseEventListener != nil &&
+ [mpMouseEventListener respondsToSelector: @selector(mouseDown:)])
+ {
+ [mpMouseEventListener mouseDown: [pEvent copyWithZone: nullptr]];
+ }
+
+ s_nLastButton = MOUSE_LEFT;
+ [self sendMouseEventToFrame:pEvent button:MOUSE_LEFT eventtype:SalEvent::MouseButtonDown];
+}
+
+-(void)mouseDragged: (NSEvent*)pEvent
+{
+ if ( mpMouseEventListener != nil &&
+ [mpMouseEventListener respondsToSelector: @selector(mouseDragged:)])
+ {
+ [mpMouseEventListener mouseDragged: [pEvent copyWithZone: nullptr]];
+ }
+ s_nLastButton = MOUSE_LEFT;
+ [self sendMouseEventToFrame:pEvent button:MOUSE_LEFT eventtype:SalEvent::MouseMove];
+}
+
+-(void)mouseUp: (NSEvent*)pEvent
+{
+ s_nLastButton = 0;
+ [self sendMouseEventToFrame:pEvent button:MOUSE_LEFT eventtype:SalEvent::MouseButtonUp];
+}
+
+-(void)mouseMoved: (NSEvent*)pEvent
+{
+ s_nLastButton = 0;
+ [self sendMouseEventToFrame:pEvent button:0 eventtype:SalEvent::MouseMove];
+}
+
+-(void)mouseEntered: (NSEvent*)pEvent
+{
+ s_pMouseFrame = mpFrame;
+
+ // #i107215# the only mouse events we get when inactive are enter/exit
+ // actually we would like to have all of them, but better none than some
+ if( [NSApp isActive] )
+ [self sendMouseEventToFrame:pEvent button:s_nLastButton eventtype:SalEvent::MouseMove];
+}
+
+-(void)mouseExited: (NSEvent*)pEvent
+{
+ if( s_pMouseFrame == mpFrame )
+ s_pMouseFrame = nullptr;
+
+ // #i107215# the only mouse events we get when inactive are enter/exit
+ // actually we would like to have all of them, but better none than some
+ if( [NSApp isActive] )
+ [self sendMouseEventToFrame:pEvent button:s_nLastButton eventtype:SalEvent::MouseLeave];
+}
+
+-(void)rightMouseDown: (NSEvent*)pEvent
+{
+ s_nLastButton = MOUSE_RIGHT;
+ [self sendMouseEventToFrame:pEvent button:MOUSE_RIGHT eventtype:SalEvent::MouseButtonDown];
+}
+
+-(void)rightMouseDragged: (NSEvent*)pEvent
+{
+ s_nLastButton = MOUSE_RIGHT;
+ [self sendMouseEventToFrame:pEvent button:MOUSE_RIGHT eventtype:SalEvent::MouseMove];
+}
+
+-(void)rightMouseUp: (NSEvent*)pEvent
+{
+ s_nLastButton = 0;
+ [self sendMouseEventToFrame:pEvent button:MOUSE_RIGHT eventtype:SalEvent::MouseButtonUp];
+}
+
+-(void)otherMouseDown: (NSEvent*)pEvent
+{
+ if( [pEvent buttonNumber] == 2 )
+ {
+ s_nLastButton = MOUSE_MIDDLE;
+ [self sendMouseEventToFrame:pEvent button:MOUSE_MIDDLE eventtype:SalEvent::MouseButtonDown];
+ }
+ else
+ s_nLastButton = 0;
+}
+
+-(void)otherMouseDragged: (NSEvent*)pEvent
+{
+ if( [pEvent buttonNumber] == 2 )
+ {
+ s_nLastButton = MOUSE_MIDDLE;
+ [self sendMouseEventToFrame:pEvent button:MOUSE_MIDDLE eventtype:SalEvent::MouseMove];
+ }
+ else
+ s_nLastButton = 0;
+}
+
+-(void)otherMouseUp: (NSEvent*)pEvent
+{
+ s_nLastButton = 0;
+ if( [pEvent buttonNumber] == 2 )
+ [self sendMouseEventToFrame:pEvent button:MOUSE_MIDDLE eventtype:SalEvent::MouseButtonUp];
+}
+
+- (void)magnifyWithEvent: (NSEvent*)pEvent
+{
+ SolarMutexGuard aGuard;
+
+ // TODO: ?? -(float)magnification;
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ const NSTimeInterval fMagnifyTime = [pEvent timestamp];
+ mpFrame->mnLastEventTime = static_cast<sal_uInt64>( fMagnifyTime * 1000.0 );
+ mpFrame->mnLastModifierFlags = [pEvent modifierFlags];
+
+ // check if this is a new series of magnify events
+ static const NSTimeInterval fMaxDiffTime = 0.3;
+ const bool bNewSeries = (fMagnifyTime - mfLastMagnifyTime > fMaxDiffTime);
+
+ if( bNewSeries )
+ mfMagnifyDeltaSum = 0.0;
+ mfMagnifyDeltaSum += [pEvent magnification];
+
+ mfLastMagnifyTime = [pEvent timestamp];
+// TODO: change to 0.1 when CommandWheelMode::ZOOM handlers allow finer zooming control
+ static const float fMagnifyFactor = 0.25*500; // steps are 500 times smaller for -magnification
+ static const float fMinMagnifyStep = 15.0 / fMagnifyFactor;
+ if( fabs(mfMagnifyDeltaSum) <= fMinMagnifyStep )
+ return;
+
+ // adapt NSEvent-sensitivity to application expectations
+ // TODO: rather make CommandWheelMode::ZOOM handlers smarter
+ const float fDeltaZ = mfMagnifyDeltaSum * fMagnifyFactor;
+ int nDeltaZ = FRound( fDeltaZ );
+ if( !nDeltaZ )
+ {
+ // handle new series immediately
+ if( !bNewSeries )
+ return;
+ nDeltaZ = (fDeltaZ >= 0.0) ? +1 : -1;
+ }
+ // eventually give credit for delta sum
+ mfMagnifyDeltaSum -= nDeltaZ / fMagnifyFactor;
+
+ NSPoint aPt = [NSEvent mouseLocation];
+ mpFrame->CocoaToVCL( aPt );
+
+ SalWheelMouseEvent aEvent;
+ aEvent.mnTime = mpFrame->mnLastEventTime;
+ aEvent.mnX = static_cast<tools::Long>(aPt.x) - mpFrame->maGeometry.x();
+ aEvent.mnY = static_cast<tools::Long>(aPt.y) - mpFrame->maGeometry.y();
+ aEvent.mnCode = ImplGetModifierMask( mpFrame->mnLastModifierFlags );
+ aEvent.mnCode |= KEY_MOD1; // we want zooming, no scrolling
+ aEvent.mbDeltaIsPixel = true;
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = mpFrame->maGeometry.width() - 1 - aEvent.mnX;
+
+ aEvent.mnDelta = nDeltaZ;
+ aEvent.mnNotchDelta = (nDeltaZ >= 0) ? +1 : -1;
+ if( aEvent.mnDelta == 0 )
+ aEvent.mnDelta = aEvent.mnNotchDelta;
+ aEvent.mbHorz = false;
+ sal_uInt32 nScrollLines = nDeltaZ;
+ if (nScrollLines == 0)
+ nScrollLines = 1;
+ aEvent.mnScrollLines = nScrollLines;
+ mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
+ }
+}
+
+- (void)rotateWithEvent: (NSEvent*)pEvent
+{
+ //Rotation : -(float)rotation;
+ // TODO: create new CommandType so rotation is available to the applications
+ (void)pEvent;
+}
+
+- (void)swipeWithEvent: (NSEvent*)pEvent
+{
+ SolarMutexGuard aGuard;
+
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
+ mpFrame->mnLastModifierFlags = [pEvent modifierFlags];
+
+ // merge pending scroll wheel events
+ CGFloat dX = 0.0;
+ CGFloat dY = 0.0;
+ for(;;)
+ {
+ dX += [pEvent deltaX];
+ dY += [pEvent deltaY];
+ NSEvent* pNextEvent = [NSApp nextEventMatchingMask: NSEventMaskScrollWheel
+ untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES ];
+ if( !pNextEvent )
+ break;
+ pEvent = pNextEvent;
+ }
+
+ NSPoint aPt = [NSEvent mouseLocation];
+ mpFrame->CocoaToVCL( aPt );
+
+ SalWheelMouseEvent aEvent;
+ aEvent.mnTime = mpFrame->mnLastEventTime;
+ aEvent.mnX = static_cast<tools::Long>(aPt.x) - mpFrame->maGeometry.x();
+ aEvent.mnY = static_cast<tools::Long>(aPt.y) - mpFrame->maGeometry.y();
+ aEvent.mnCode = ImplGetModifierMask( mpFrame->mnLastModifierFlags );
+ aEvent.mbDeltaIsPixel = true;
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = mpFrame->maGeometry.width() - 1 - aEvent.mnX;
+
+ if( dX != 0.0 )
+ {
+ aEvent.mnDelta = static_cast<tools::Long>(dX < 0 ? floor(dX) : ceil(dX));
+ aEvent.mnNotchDelta = (dX < 0) ? -1 : +1;
+ if( aEvent.mnDelta == 0 )
+ aEvent.mnDelta = aEvent.mnNotchDelta;
+ aEvent.mbHorz = true;
+ aEvent.mnScrollLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL;
+ mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
+ }
+ if( dY != 0.0 && AquaSalFrame::isAlive( mpFrame ))
+ {
+ aEvent.mnDelta = static_cast<tools::Long>(dY < 0 ? floor(dY) : ceil(dY));
+ aEvent.mnNotchDelta = (dY < 0) ? -1 : +1;
+ if( aEvent.mnDelta == 0 )
+ aEvent.mnDelta = aEvent.mnNotchDelta;
+ aEvent.mbHorz = false;
+ aEvent.mnScrollLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL;
+ mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
+ }
+ }
+}
+
+-(void)scrollWheel: (NSEvent*)pEvent
+{
+ SolarMutexGuard aGuard;
+
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
+ mpFrame->mnLastModifierFlags = [pEvent modifierFlags];
+
+ // merge pending scroll wheel events
+ CGFloat dX = 0.0;
+ CGFloat dY = 0.0;
+ for(;;)
+ {
+ dX += [pEvent deltaX];
+ dY += [pEvent deltaY];
+ NSEvent* pNextEvent = [NSApp nextEventMatchingMask: NSEventMaskScrollWheel
+ untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES ];
+ if( !pNextEvent )
+ break;
+ pEvent = pNextEvent;
+ }
+
+ NSPoint aPt = [NSEvent mouseLocation];
+ mpFrame->CocoaToVCL( aPt );
+
+ SalWheelMouseEvent aEvent;
+ aEvent.mnTime = mpFrame->mnLastEventTime;
+ aEvent.mnX = static_cast<tools::Long>(aPt.x) - mpFrame->maGeometry.x();
+ aEvent.mnY = static_cast<tools::Long>(aPt.y) - mpFrame->maGeometry.y();
+ aEvent.mnCode = ImplGetModifierMask( mpFrame->mnLastModifierFlags );
+ aEvent.mbDeltaIsPixel = false;
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = mpFrame->maGeometry.width() - 1 - aEvent.mnX;
+
+ if( dX != 0.0 )
+ {
+ aEvent.mnDelta = static_cast<tools::Long>(dX < 0 ? floor(dX) : ceil(dX));
+ aEvent.mnNotchDelta = (dX < 0) ? -1 : +1;
+ if( aEvent.mnDelta == 0 )
+ aEvent.mnDelta = aEvent.mnNotchDelta;
+ aEvent.mbHorz = true;
+ sal_uInt32 nScrollLines = fabs(dX) / WHEEL_EVENT_FACTOR;
+ if (nScrollLines == 0)
+ nScrollLines = 1;
+ aEvent.mnScrollLines = nScrollLines;
+
+ mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
+ }
+ if( dY != 0.0 && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ aEvent.mnDelta = static_cast<tools::Long>(dY < 0 ? floor(dY) : ceil(dY));
+ aEvent.mnNotchDelta = (dY < 0) ? -1 : +1;
+ if( aEvent.mnDelta == 0 )
+ aEvent.mnDelta = aEvent.mnNotchDelta;
+ aEvent.mbHorz = false;
+ sal_uInt32 nScrollLines = fabs(dY) / WHEEL_EVENT_FACTOR;
+ if (nScrollLines == 0)
+ nScrollLines = 1;
+ aEvent.mnScrollLines = nScrollLines;
+
+ mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
+ }
+
+ // tdf#155266 force flush after scrolling
+ mpFrame->mbForceFlush = true;
+ }
+}
+
+
+-(void)keyDown: (NSEvent*)pEvent
+{
+ SolarMutexGuard aGuard;
+
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ // Retain the event as it will be released sometime before a key up
+ // event is dispatched
+ [self clearLastEvent];
+ mpLastEvent = [pEvent retain];
+
+ mbInKeyInput = true;
+ mbNeedSpecialKeyHandle = false;
+ mbKeyHandled = false;
+
+ mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
+ mpFrame->mnLastModifierFlags = [pEvent modifierFlags];
+
+ if( ! [self handleKeyDownException: pEvent] )
+ {
+ sal_uInt16 nKeyCode = ImplMapKeyCode( [pEvent keyCode] );
+ if ( nKeyCode == KEY_DELETE && mbTextInputWantsNonRepeatKeyDown )
+ {
+ // tdf#42437 Enable press-and-hold special character input method
+ // Emulate the press-and-hold behavior of the TextEdit
+ // application by deleting the marked text when only the
+ // Delete key is pressed and keep the marked text when the
+ // Backspace key or Fn-Delete keys are pressed.
+ if ( [pEvent keyCode] == 51 )
+ {
+ [self deleteTextInputWantsNonRepeatKeyDown];
+ }
+ else
+ {
+ [self unmarkText];
+ mbKeyHandled = true;
+ mbInKeyInput = false;
+ }
+
+ [self endExtTextInput];
+ return;
+ }
+
+ NSArray* pArray = [NSArray arrayWithObject: pEvent];
+ [self interpretKeyEvents: pArray];
+
+ // Handle repeat key events by explicitly inserting the text if
+ // -[NSResponder interpretKeyEvents:] does not insert or mark any
+ // text. Note: do not do this step if there is uncommitted text.
+ // Related: tdf#42437 Skip special press-and-hold handling for action keys
+ // Pressing and holding action keys such as arrow keys must not be
+ // handled like pressing and holding a character key as it will
+ // insert unexpected text.
+ if ( !mbKeyHandled && !mpLastMarkedText && mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && [mpLastEvent isARepeat] )
+ {
+ NSString *pChars = [mpLastEvent characters];
+ if ( pChars )
+ [self insertText:pChars replacementRange:NSMakeRange( 0, [pChars length] )];
+ }
+ // tdf#42437 Enable press-and-hold special character input method
+ // Emulate the press-and-hold behavior of the TextEdit application
+ // by committing an empty string for key down events dispatched
+ // while the special character input method popup is displayed.
+ else if ( mpLastMarkedText && mbTextInputWantsNonRepeatKeyDown && mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && ![mpLastEvent isARepeat] )
+ {
+ // If the escape or return key is pressed, unmark the text to
+ // skip deletion of marked text
+ if ( nKeyCode == KEY_ESCAPE || nKeyCode == KEY_RETURN )
+ [self unmarkText];
+ [self insertText:[NSString string] replacementRange:NSMakeRange( NSNotFound, 0 )];
+ }
+ }
+
+ mbInKeyInput = false;
+ }
+}
+
+-(BOOL)handleKeyDownException:(NSEvent*)pEvent
+{
+ // check for a very special set of modified characters
+ NSString* pUnmodifiedString = [pEvent charactersIgnoringModifiers];
+
+ if( pUnmodifiedString && [pUnmodifiedString length] == 1 )
+ {
+ /* #i103102# key events with command and alternate don't make it through
+ interpretKeyEvents (why?). Try to dispatch them here first,
+ if not successful continue normally
+ */
+ if( (mpFrame->mnLastModifierFlags & (NSEventModifierFlagOption | NSEventModifierFlagCommand))
+ == (NSEventModifierFlagOption | NSEventModifierFlagCommand) )
+ {
+ if( [self sendSingleCharacter: mpLastEvent] )
+ return YES;
+ }
+ }
+ return NO;
+}
+
+-(void)flagsChanged: (NSEvent*)pEvent
+{
+ SolarMutexGuard aGuard;
+
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
+ mpFrame->mnLastModifierFlags = [pEvent modifierFlags];
+ }
+}
+
+-(void)insertText:(id)aString replacementRange:(NSRange)replacementRange
+{
+ (void) replacementRange; // FIXME: surely it must be used
+
+ SolarMutexGuard aGuard;
+
+ [self deleteTextInputWantsNonRepeatKeyDown];
+
+ // Ignore duplicate events that are sometimes posted during cancellation
+ // of the native input method session. This usually happens when
+ // [self endExtTextInput] is called from [self windowDidBecomeKey:] and,
+ // if the native input method popup, that was cancelled in a
+ // previous call to [self windowDidResignKey:], has reappeared. In such
+ // cases, the native input context posts the reappearing popup's
+ // uncommitted text.
+ if (mbInEndExtTextInput && !mbInCommitMarkedText)
+ return;
+
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ NSString* pInsert = nil;
+ if( [aString isKindOfClass: [NSAttributedString class]] )
+ pInsert = [aString string];
+ else
+ pInsert = aString;
+
+ int nLen = 0;
+ if( pInsert && ( nLen = [pInsert length] ) > 0 )
+ {
+ OUString aInsertString( GetOUString( pInsert ) );
+ // aCharCode initializer is safe since aInsertString will at least contain '\0'
+ sal_Unicode aCharCode = *aInsertString.getStr();
+
+ if( nLen == 1 &&
+ aCharCode < 0x80 &&
+ aCharCode > 0x1f &&
+ ! [self hasMarkedText ]
+ )
+ {
+ sal_uInt16 nKeyCode = ImplMapCharCode( aCharCode );
+ unsigned int nLastModifiers = mpFrame->mnLastModifierFlags;
+
+ // #i99567#
+ // find out the unmodified key code
+
+ // sanity check
+ if( mpLastEvent && ( [mpLastEvent type] == NSEventTypeKeyDown || [mpLastEvent type] == NSEventTypeKeyUp ) )
+ {
+ // get unmodified string
+ NSString* pUnmodifiedString = [mpLastEvent charactersIgnoringModifiers];
+ if( pUnmodifiedString && [pUnmodifiedString length] == 1 )
+ {
+ // map the unmodified key code
+ unichar keyChar = [pUnmodifiedString characterAtIndex: 0];
+ nKeyCode = ImplMapCharCode( keyChar );
+ }
+ nLastModifiers = [mpLastEvent modifierFlags];
+
+ }
+ // #i99567#
+ // applications and vcl's edit fields ignore key events with ALT
+ // however we're at a place where we know text should be inserted
+ // so it seems we need to strip the Alt modifier here
+ if( (nLastModifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption | NSEventModifierFlagCommand))
+ == NSEventModifierFlagOption )
+ {
+ nLastModifiers = 0;
+ }
+ [self sendKeyInputAndReleaseToFrame: nKeyCode character: aCharCode modifiers: nLastModifiers];
+ }
+ else
+ {
+ SalExtTextInputEvent aEvent;
+ aEvent.maText = aInsertString;
+ aEvent.mpTextAttr = nullptr;
+ aEvent.mnCursorPos = aInsertString.getLength();
+ aEvent.mnCursorFlags = 0;
+ mpFrame->CallCallback( SalEvent::ExtTextInput, &aEvent );
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+ }
+ }
+ else
+ {
+ SalExtTextInputEvent aEvent;
+ aEvent.maText.clear();
+ aEvent.mpTextAttr = nullptr;
+ aEvent.mnCursorPos = 0;
+ aEvent.mnCursorFlags = 0;
+ mpFrame->CallCallback( SalEvent::ExtTextInput, &aEvent );
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+
+ }
+ [self unmarkText];
+ }
+
+ // Mark event as handled even if the frame isn't valid like is done in
+ // [self setMarkedText:selectedRange:replacementRange:] and
+ // [self doCommandBySelector:]
+ mbKeyHandled = true;
+}
+
+-(void)insertTab: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_TAB character: '\t' modifiers: 0];
+}
+
+-(void)insertBacktab: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: (KEY_TAB | KEY_SHIFT) character: '\t' modifiers: 0];
+}
+
+-(void)moveLeft: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_LEFT character: 0 modifiers: 0];
+}
+
+-(void)moveLeftAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_LEFT character: 0 modifiers: NSEventModifierFlagShift];
+}
+
+-(void)moveBackwardAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_BACKWARD character: 0 modifiers: 0];
+}
+
+-(void)moveRight: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_RIGHT character: 0 modifiers: 0];
+}
+
+-(void)moveRightAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_RIGHT character: 0 modifiers: NSEventModifierFlagShift];
+}
+
+-(void)moveForwardAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_FORWARD character: 0 modifiers: 0];
+}
+
+-(void)moveWordLeft: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_WORD_BACKWARD character: 0 modifiers: 0];
+}
+
+-(void)moveWordBackward: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_WORD_BACKWARD character: 0 modifiers: 0];
+}
+
+-(void)moveWordBackwardAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD_BACKWARD character: 0 modifiers: 0];
+}
+
+-(void)moveWordLeftAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD_BACKWARD character: 0 modifiers: 0];
+}
+
+-(void)moveWordRight: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_WORD_FORWARD character: 0 modifiers: 0];
+}
+
+-(void)moveWordForward: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_WORD_FORWARD character: 0 modifiers: 0];
+}
+
+-(void)moveWordForwardAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD_FORWARD character: 0 modifiers: 0];
+}
+
+-(void)moveWordRightAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD_FORWARD character: 0 modifiers: 0];
+}
+
+-(void)moveToEndOfLine: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)moveToRightEndOfLine: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)moveToEndOfLineAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)moveToRightEndOfLineAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)moveToBeginningOfLine: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)moveToLeftEndOfLine: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)moveToBeginningOfLineAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)moveToLeftEndOfLineAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)moveToEndOfParagraph: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)moveToEndOfParagraphAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)moveParagraphForward: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)moveParagraphForwardAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)moveToBeginningOfParagraph: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)moveParagraphBackward: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)moveToBeginningOfParagraphAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)moveParagraphBackwardAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)moveToEndOfDocument: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_DOCUMENT character: 0 modifiers: 0];
+}
+
+-(void)scrollToEndOfDocument: (id)aSender
+{
+ (void)aSender;
+ // this is not exactly what we should do, but it makes "End" and "Shift-End" behave consistent
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_DOCUMENT character: 0 modifiers: 0];
+}
+
+-(void)moveToEndOfDocumentAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_DOCUMENT character: 0 modifiers: 0];
+}
+
+-(void)moveToBeginningOfDocument: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT character: 0 modifiers: 0];
+}
+
+-(void)scrollToBeginningOfDocument: (id)aSender
+{
+ (void)aSender;
+ // this is not exactly what we should do, but it makes "Home" and "Shift-Home" behave consistent
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT character: 0 modifiers: 0];
+}
+
+-(void)moveToBeginningOfDocumentAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT character: 0 modifiers: 0];
+}
+
+-(void)moveUp: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_UP character: 0 modifiers: 0];
+}
+
+-(void)moveDown: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_DOWN character: 0 modifiers: 0];
+}
+
+-(void)insertNewline: (id)aSender
+{
+ (void)aSender;
+ // #i91267# make enter and shift-enter work by evaluating the modifiers
+ [self sendKeyInputAndReleaseToFrame: KEY_RETURN character: '\n' modifiers: mpFrame->mnLastModifierFlags];
+}
+
+-(void)deleteBackward: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_BACKSPACE character: '\b' modifiers: 0];
+}
+
+-(void)deleteForward: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_DELETE character: 0x7f modifiers: 0];
+}
+
+-(void)deleteBackwardByDecomposingPreviousCharacter: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_BACKSPACE character: '\b' modifiers: 0];
+}
+
+-(void)deleteWordBackward: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_WORD_BACKWARD character: 0 modifiers: 0];
+}
+
+-(void)deleteWordForward: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_WORD_FORWARD character: 0 modifiers: 0];
+}
+
+-(void)deleteToBeginningOfLine: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_TO_BEGIN_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)deleteToEndOfLine: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_TO_END_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)deleteToBeginningOfParagraph: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)deleteToEndOfParagraph: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_TO_END_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)insertLineBreak: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::INSERT_LINEBREAK character: 0 modifiers: 0];
+}
+
+-(void)insertParagraphSeparator: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::INSERT_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)selectWord: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD character: 0 modifiers: 0];
+}
+
+-(void)selectLine: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_LINE character: 0 modifiers: 0];
+}
+
+-(void)selectParagraph: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)selectAll: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_ALL character: 0 modifiers: 0];
+}
+
+-(void)cancelOperation: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_ESCAPE character: 0x1b modifiers: 0];
+}
+
+-(void)noop: (id)aSender
+{
+ (void)aSender;
+ if( ! mbKeyHandled )
+ {
+ if( ! [self sendSingleCharacter:mpLastEvent] )
+ {
+ /* prevent recursion */
+ if( mpLastEvent != mpLastSuperEvent && [NSApp respondsToSelector: @selector(sendSuperEvent:)] )
+ {
+ id pLastSuperEvent = mpLastSuperEvent;
+ mpLastSuperEvent = mpLastEvent;
+ [NSApp performSelector:@selector(sendSuperEvent:) withObject: mpLastEvent];
+ mpLastSuperEvent = pLastSuperEvent;
+
+ std::map< NSEvent*, bool >::iterator it = GetSalData()->maKeyEventAnswer.find( mpLastEvent );
+ if( it != GetSalData()->maKeyEventAnswer.end() )
+ it->second = true;
+ }
+ }
+ }
+}
+
+-(BOOL)sendKeyInputAndReleaseToFrame: (sal_uInt16)nKeyCode character: (sal_Unicode)aChar
+{
+ return [self sendKeyInputAndReleaseToFrame: nKeyCode character: aChar modifiers: mpFrame->mnLastModifierFlags];
+}
+
+-(BOOL)sendKeyInputAndReleaseToFrame: (sal_uInt16)nKeyCode character: (sal_Unicode)aChar modifiers: (unsigned int)nMod
+{
+ return [self sendKeyToFrameDirect: nKeyCode character: aChar modifiers: nMod] ||
+ [self sendSingleCharacter: mpLastEvent];
+}
+
+-(BOOL)sendKeyToFrameDirect: (sal_uInt16)nKeyCode character: (sal_Unicode)aChar modifiers: (unsigned int)nMod
+{
+ SolarMutexGuard aGuard;
+
+ bool nRet = false;
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ SalKeyEvent aEvent;
+ aEvent.mnCode = nKeyCode | ImplGetModifierMask( nMod );
+ aEvent.mnCharCode = aChar;
+ aEvent.mnRepeat = FALSE;
+ nRet = mpFrame->CallCallback( SalEvent::KeyInput, &aEvent );
+ std::map< NSEvent*, bool >::iterator it = GetSalData()->maKeyEventAnswer.find( mpLastEvent );
+ if( it != GetSalData()->maKeyEventAnswer.end() )
+ it->second = nRet;
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ mpFrame->CallCallback( SalEvent::KeyUp, &aEvent );
+ }
+ return nRet;
+}
+
+
+-(BOOL)sendSingleCharacter: (NSEvent *)pEvent
+{
+ NSString* pUnmodifiedString = [pEvent charactersIgnoringModifiers];
+
+ if( pUnmodifiedString && [pUnmodifiedString length] == 1 )
+ {
+ unichar keyChar = [pUnmodifiedString characterAtIndex: 0];
+ sal_uInt16 nKeyCode = ImplMapCharCode( keyChar );
+ if (nKeyCode == 0)
+ {
+ sal_uInt16 nOtherKeyCode = [pEvent keyCode];
+ nKeyCode = ImplMapKeyCode(nOtherKeyCode);
+ }
+ if( nKeyCode != 0 )
+ {
+ // don't send code points in the private use area
+ if( keyChar >= 0xf700 && keyChar < 0xf780 )
+ keyChar = 0;
+ bool bRet = [self sendKeyToFrameDirect: nKeyCode character: keyChar modifiers: mpFrame->mnLastModifierFlags];
+ mbInKeyInput = false;
+
+ return bRet;
+ }
+ }
+ return NO;
+}
+
+
+// NSTextInput/NSTextInputClient protocol
+- (NSArray *)validAttributesForMarkedText
+{
+ return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName, nil];
+}
+
+- (BOOL)hasMarkedText
+{
+ bool bHasMarkedText;
+
+ bHasMarkedText = ( mMarkedRange.location != NSNotFound ) &&
+ ( mMarkedRange.length != 0 );
+ // hack to check keys like "Control-j"
+ if( mbInKeyInput )
+ {
+ mbNeedSpecialKeyHandle = true;
+ }
+
+ // FIXME:
+ // #i106901#
+ // if we come here outside of mbInKeyInput, this is likely to be because
+ // of the keyboard viewer. For unknown reasons having no marked range
+ // in this case causes a crash. So we say we have a marked range anyway
+ // This is a hack, since it is not understood what a) causes that crash
+ // and b) why we should have a marked range at this point.
+ if( ! mbInKeyInput )
+ bHasMarkedText = true;
+
+ return bHasMarkedText;
+}
+
+- (NSRange)markedRange
+{
+ // FIXME:
+ // #i106901#
+ // if we come here outside of mbInKeyInput, this is likely to be because
+ // of the keyboard viewer. For unknown reasons having no marked range
+ // in this case causes a crash. So we say we have a marked range anyway
+ // This is a hack, since it is not understood what a) causes that crash
+ // and b) why we should have a marked range at this point. Stop the native
+ // input method popup from appearing in the bottom left corner of the
+ // screen by returning the marked range if is valid when called outside of
+ // mbInKeyInput. If a zero length range is returned, macOS won't call
+ // [self firstRectForCharacterRange:actualRange:] for any newly appended
+ // uncommitted text.
+ if( ! mbInKeyInput )
+ return mMarkedRange.location != NSNotFound ? mMarkedRange : NSMakeRange( 0, 0 );
+
+ return [self hasMarkedText] ? mMarkedRange : NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSRange)selectedRange
+{
+ // tdf#42437 Enable press-and-hold special character input method
+ // Always return a valid range location. If the range location is
+ // NSNotFound, -[NSResponder interpretKeyEvents:] will not call
+ // [self firstRectForCharacterRange:actualRange:] and will not display the
+ // special character input method popup.
+ return ( mSelectedRange.location == NSNotFound ? NSMakeRange( 0, 0 ) : mSelectedRange );
+}
+
+- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange replacementRange:(NSRange)replacementRange
+{
+ (void) replacementRange; // FIXME - use it!
+
+ SolarMutexGuard aGuard;
+
+ [self deleteTextInputWantsNonRepeatKeyDown];
+
+ if( ![aString isKindOfClass:[NSAttributedString class]] )
+ aString = [[[NSAttributedString alloc] initWithString:aString] autorelease];
+
+ // Reset cached state
+ [self unmarkText];
+
+ int len = [aString length];
+ SalExtTextInputEvent aInputEvent;
+ if( len > 0 ) {
+ // Set the marked and selected ranges to the marked text and selected
+ // range parameters
+ mMarkedRange = NSMakeRange( 0, [aString length] );
+ if (selRange.location == NSNotFound || selRange.location >= mMarkedRange.length)
+ mSelectedRange = NSMakeRange( NSNotFound, 0 );
+ else
+ mSelectedRange = NSMakeRange( selRange.location, selRange.location + selRange.length > mMarkedRange.length ? mMarkedRange.length - selRange.location : selRange.length );
+
+ // If we are going to post uncommitted text, cache the string parameter
+ // as is needed in both [self endExtTextInput] and
+ // [self attributedSubstringForProposedRange:actualRange:]
+ mpLastMarkedText = [aString retain];
+
+ NSString *pString = [aString string];
+ OUString aInsertString( GetOUString( pString ) );
+ std::vector<ExtTextInputAttr> aInputFlags( std::max( 1, len ), ExtTextInputAttr::NONE );
+ int nSelectionStart = (mSelectedRange.location == NSNotFound ? len : mSelectedRange.location);
+ int nSelectionEnd = (mSelectedRange.location == NSNotFound ? len : mSelectedRange.location + selRange.length);
+ for ( int i = 0; i < len; i++ )
+ {
+ // Highlight all characters in the selected range. Normally
+ // uncommitted text is underlined but when an item is selected in
+ // the native input method popup or selecting a subblock of
+ // uncommitted text using the left or right arrow keys, the
+ // selection range is set and the selected range is either
+ // highlighted like in Excel or is bold underlined like in
+ // Safari. Highlighting the selected range was chosen because
+ // using bold and double underlines can get clipped making the
+ // selection range indistinguishable from the rest of the
+ // uncommitted text.
+ if (i >= nSelectionStart && i < nSelectionEnd)
+ {
+ aInputFlags[i] = ExtTextInputAttr::Highlight;
+ continue;
+ }
+
+ unsigned int nUnderlineValue;
+ NSRange effectiveRange;
+
+ effectiveRange = NSMakeRange(i, 1);
+ nUnderlineValue = [[aString attribute:NSUnderlineStyleAttributeName atIndex:i effectiveRange:&effectiveRange] unsignedIntValue];
+
+ switch (nUnderlineValue & 0xff) {
+ case NSUnderlineStyleSingle:
+ aInputFlags[i] = ExtTextInputAttr::Underline;
+ break;
+ case NSUnderlineStyleThick:
+ aInputFlags[i] = ExtTextInputAttr::BoldUnderline;
+ break;
+ case NSUnderlineStyleDouble:
+ aInputFlags[i] = ExtTextInputAttr::DoubleUnderline;
+ break;
+ default:
+ aInputFlags[i] = ExtTextInputAttr::Highlight;
+ break;
+ }
+ }
+
+ aInputEvent.maText = aInsertString;
+ aInputEvent.mnCursorPos = nSelectionStart;
+ aInputEvent.mnCursorFlags = 0;
+ aInputEvent.mpTextAttr = aInputFlags.data();
+ mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void *>(&aInputEvent) );
+ } else {
+ aInputEvent.maText.clear();
+ aInputEvent.mnCursorPos = 0;
+ aInputEvent.mnCursorFlags = 0;
+ aInputEvent.mpTextAttr = nullptr;
+ mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void *>(&aInputEvent) );
+ mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+ }
+ mbKeyHandled= true;
+}
+
+- (void)unmarkText
+{
+ [self clearLastMarkedText];
+
+ mSelectedRange = mMarkedRange = NSMakeRange(NSNotFound, 0);
+}
+
+- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
+{
+ (void) aRange;
+ (void) actualRange;
+
+ // FIXME - Implement
+ return nil;
+}
+
+- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
+{
+ (void)thePoint;
+ // FIXME
+ return 0;
+}
+
+- (NSInteger)conversationIdentifier
+{
+ return reinterpret_cast<long>(self);
+}
+
+- (void)doCommandBySelector:(SEL)aSelector
+{
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ if( (mpFrame->mnICOptions & InputContextFlags::Text) &&
+ aSelector != nullptr && [self respondsToSelector: aSelector] )
+ {
+ [self performSelector: aSelector];
+ }
+ else
+ {
+ [self sendSingleCharacter:mpLastEvent];
+ }
+ }
+
+ mbKeyHandled = true;
+}
+
+-(void)clearLastEvent
+{
+ if (mpLastEvent)
+ {
+ [mpLastEvent release];
+ mpLastEvent = nil;
+ }
+}
+
+-(void)clearLastMarkedText
+{
+ if (mpLastMarkedText)
+ {
+ [mpLastMarkedText release];
+ mpLastMarkedText = nil;
+ }
+
+ mbTextInputWantsNonRepeatKeyDown = NO;
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
+{
+ // FIXME - These should probably be used?
+ (void) aRange;
+ (void) actualRange;
+
+ SolarMutexGuard aGuard;
+
+ // tdf#42437 Enable press-and-hold special character input method
+ // Some text entry controls, such as Writer comments or the cell editor in
+ // Calc's Formula Bar, need to have an input method session open or else
+ // the returned position won't be anywhere near the text cursor. So,
+ // dispatch an empty SalEvent::ExtTextInput event, fetch the position,
+ // and then dispatch a SalEvent::EndExtTextInput event.
+ NSString *pNewMarkedText = nullptr;
+ NSString *pChars = [mpLastEvent characters];
+ bool bNeedsExtTextInput = ( pChars && mbInKeyInput && !mpLastMarkedText && mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && [mpLastEvent isARepeat] );
+ if ( bNeedsExtTextInput )
+ {
+ // tdf#154708 Preserve selection for repeating Shift-arrow on Japanese keyboard
+ // Skip the posting of SalEvent::ExtTextInput and
+ // SalEvent::EndExtTextInput events for private use area characters.
+ NSUInteger nLen = [pChars length];
+ auto const pBuf = std::make_unique<unichar[]>( nLen + 1 );
+ NSUInteger nBufLen = 0;
+ for ( NSUInteger i = 0; i < nLen; i++ )
+ {
+ unichar aChar = [pChars characterAtIndex:i];
+ if ( aChar >= 0xf700 && aChar < 0xf780 )
+ continue;
+
+ pBuf[nBufLen++] = aChar;
+ }
+ pBuf[nBufLen] = 0;
+
+ pNewMarkedText = [NSString stringWithCharacters:pBuf.get() length:nBufLen];
+ if (!pNewMarkedText || ![pNewMarkedText length])
+ bNeedsExtTextInput = false;
+ }
+
+ if ( bNeedsExtTextInput )
+ {
+ SalExtTextInputEvent aInputEvent;
+ aInputEvent.maText.clear();
+ aInputEvent.mnCursorPos = 0;
+ aInputEvent.mnCursorFlags = 0;
+ aInputEvent.mpTextAttr = nullptr;
+ if ( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void *>(&aInputEvent) );
+ }
+
+ SalExtTextInputPosEvent aPosEvent;
+ if ( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ mpFrame->CallCallback( SalEvent::ExtTextInputPos, static_cast<void *>(&aPosEvent) );
+
+ if ( bNeedsExtTextInput )
+ {
+ if ( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+
+ // tdf#42437 Enable press-and-hold special character input method
+ // Emulate the press-and-hold behavior of the TextEdit application by
+ // setting the marked text to the last key down event's characters. The
+ // characters will already have been committed by the special character
+ // input method so set the mbTextInputWantsNonRepeatKeyDown flag to
+ // indicate that the characters need to be deleted if the input method
+ // replaces the committed characters.
+ if ( pNewMarkedText )
+ {
+ [self unmarkText];
+ mpLastMarkedText = [[NSAttributedString alloc] initWithString:pNewMarkedText];
+ mSelectedRange = mMarkedRange = NSMakeRange( 0, [mpLastMarkedText length] );
+ mbTextInputWantsNonRepeatKeyDown = YES;
+ }
+ }
+
+ NSRect rect;
+
+ if ( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ rect.origin.x = aPosEvent.mnX + mpFrame->maGeometry.x();
+ rect.origin.y = aPosEvent.mnY + mpFrame->maGeometry.y() + 4; // add some space for underlines
+ rect.size.width = aPosEvent.mnWidth;
+ rect.size.height = aPosEvent.mnHeight;
+
+ mpFrame->VCLToCocoa( rect );
+ }
+ else
+ {
+ rect = NSMakeRect( aPosEvent.mnX, aPosEvent.mnY, aPosEvent.mnWidth, aPosEvent.mnHeight );
+ }
+
+ return rect;
+}
+
+-(id)parentAttribute {
+ return reinterpret_cast<NSView*>(mpFrame->getNSWindow());
+ //TODO: odd cast really needed for fdo#74121?
+}
+
+-(css::accessibility::XAccessibleContext *)accessibleContext
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibleContext];
+
+ return nil;
+}
+
+-(NSWindow*)windowForParent
+{
+ return mpFrame->getNSWindow();
+}
+
+-(void)registerMouseEventListener: (id)theListener
+{
+ mpMouseEventListener = theListener;
+}
+
+-(void)unregisterMouseEventListener: (id)theListener
+{
+ (void)theListener;
+ mpMouseEventListener = nil;
+}
+
+-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+{
+ return [mDraggingDestinationHandler draggingEntered: sender];
+}
+
+-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
+{
+ return [mDraggingDestinationHandler draggingUpdated: sender];
+}
+
+-(void)draggingExited:(id <NSDraggingInfo>)sender
+{
+ [mDraggingDestinationHandler draggingExited: sender];
+}
+
+-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
+{
+ return [mDraggingDestinationHandler prepareForDragOperation: sender];
+}
+
+-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+{
+ return [mDraggingDestinationHandler performDragOperation: sender];
+}
+
+-(void)concludeDragOperation:(id <NSDraggingInfo>)sender
+{
+ [mDraggingDestinationHandler concludeDragOperation: sender];
+}
+
+-(void)registerDraggingDestinationHandler:(id)theHandler
+{
+ mDraggingDestinationHandler = theHandler;
+}
+
+-(void)unregisterDraggingDestinationHandler:(id)theHandler
+{
+ (void)theHandler;
+ mDraggingDestinationHandler = nil;
+}
+
+-(void)endExtTextInput
+{
+ [self endExtTextInput:EndExtTextInputFlags::Complete];
+}
+
+-(void)endExtTextInput:(EndExtTextInputFlags)nFlags
+{
+ // Prevent recursion from any additional [self insertText:] calls that
+ // may be called when cancelling the native input method session
+ if (mbInEndExtTextInput)
+ return;
+
+ mbInEndExtTextInput = YES;
+
+ SolarMutexGuard aGuard;
+
+ NSTextInputContext *pInputContext = [NSTextInputContext currentInputContext];
+ if (pInputContext)
+ {
+ // Cancel the native input method session
+ [pInputContext discardMarkedText];
+
+ // Commit any uncommitted text. Note: when the delete key is used to
+ // remove all uncommitted characters, the marked range will be zero
+ // length but a SalEvent::EndExtTextInput must still be dispatched.
+ if (mpLastMarkedText && [mpLastMarkedText length] && mMarkedRange.location != NSNotFound && mpFrame && AquaSalFrame::isAlive(mpFrame))
+ {
+ // If there is any marked text, SalEvent::EndExtTextInput may leave
+ // the cursor hidden so commit the marked text to force the cursor
+ // to be visible.
+ mbInCommitMarkedText = YES;
+ if (nFlags & EndExtTextInputFlags::Complete)
+ {
+ // Retain the last marked text as it will be released in
+ // [self insertText:replacementText:]
+ NSAttributedString *pText = [mpLastMarkedText retain];
+ [self insertText:pText replacementRange:NSMakeRange(0, [mpLastMarkedText length])];
+ [pText release];
+ }
+ else
+ {
+ [self insertText:[NSString string] replacementRange:NSMakeRange(0, 0)];
+ }
+ mbInCommitMarkedText = NO;
+ }
+
+ [self unmarkText];
+
+ // If a different view is the input context's client, commit that
+ // view's uncommitted text as well
+ id<NSTextInputClient> pClient = [pInputContext client];
+ if (pClient != self)
+ {
+ SalFrameView *pView = static_cast<SalFrameView*>(pClient);
+ if ([pView isKindOfClass:[SalFrameView class]])
+ [pView endExtTextInput];
+ else
+ [pClient unmarkText];
+ }
+ }
+
+ mbInEndExtTextInput = NO;
+}
+
+-(void)deleteTextInputWantsNonRepeatKeyDown
+{
+ SolarMutexGuard aGuard;
+
+ // tdf#42437 Enable press-and-hold special character input method
+ // Emulate the press-and-hold behavior of the TextEdit application by
+ // dispatching backspace events to delete any marked characters. The
+ // special character input method commits the marked characters so we must
+ // delete the marked characters before the input method calls
+ // [self insertText:replacementRange:].
+ if (mbTextInputWantsNonRepeatKeyDown)
+ {
+ if ( mpLastMarkedText )
+ {
+ NSString *pChars = [mpLastMarkedText string];
+ if ( pChars )
+ {
+ NSUInteger nLength = [pChars length];
+ for ( NSUInteger i = 0; i < nLength; i++ )
+ [self deleteBackward:self];
+ }
+ }
+
+ [self unmarkText];
+ }
+}
+
+-(void)insertRegisteredWrapperIntoWrapperRepository
+{
+ SolarMutexGuard aGuard;
+
+ if (!mbNeedChildWrapper)
+ return;
+
+ vcl::Window *pWindow = mpFrame->GetWindow();
+ if (!pWindow)
+ return;
+
+ mbNeedChildWrapper = NO;
+
+ ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessibleContext > xAccessibleContext( pWindow->GetAccessible()->getAccessibleContext() );
+ assert(!mpChildWrapper);
+ mpChildWrapper = [[SalFrameViewA11yWrapper alloc] initWithParent:self accessibleContext:xAccessibleContext];
+ [AquaA11yFactory insertIntoWrapperRepository:mpChildWrapper forAccessibleContext:xAccessibleContext];
+}
+
+-(void)registerWrapper
+{
+ [self revokeWrapper];
+
+ mbNeedChildWrapper = YES;
+}
+
+-(void)revokeWrapper
+{
+ mbNeedChildWrapper = NO;
+
+ if (mpChildWrapper)
+ {
+ [AquaA11yFactory revokeWrapper:mpChildWrapper];
+ [mpChildWrapper setAccessibilityParent:nil];
+ [mpChildWrapper release];
+ mpChildWrapper = nil;
+ }
+}
+
+-(id)accessibilityAttributeValue:(NSString *)pAttribute
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityAttributeValue:pAttribute];
+ else
+ return nil;
+}
+
+-(BOOL)accessibilityIsIgnored
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityIsIgnored];
+ else
+ return YES;
+}
+
+-(NSArray *)accessibilityAttributeNames
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityAttributeNames];
+ else
+ return [NSArray array];
+}
+
+-(BOOL)accessibilityIsAttributeSettable:(NSString *)pAttribute
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityIsAttributeSettable:pAttribute];
+ else
+ return NO;
+}
+
+-(NSArray *)accessibilityParameterizedAttributeNames
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityParameterizedAttributeNames];
+ else
+ return [NSArray array];
+}
+
+-(BOOL)accessibilitySetOverrideValue:(id)pValue forAttribute:(NSString *)pAttribute
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilitySetOverrideValue:pValue forAttribute:pAttribute];
+ else
+ return NO;
+}
+
+-(void)accessibilitySetValue:(id)pValue forAttribute:(NSString *)pAttribute
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ [mpChildWrapper accessibilitySetValue:pValue forAttribute:pAttribute];
+}
+
+-(id)accessibilityAttributeValue:(NSString *)pAttribute forParameter:(id)pParameter
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityAttributeValue:pAttribute forParameter:pParameter];
+ else
+ return nil;
+}
+
+-(id)accessibilityFocusedUIElement
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityFocusedUIElement];
+ else
+ return nil;
+}
+
+-(NSString *)accessibilityActionDescription:(NSString *)pAction
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityActionDescription:pAction];
+ else
+ return nil;
+}
+
+-(void)accessibilityPerformAction:(NSString *)pAction
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ [mpChildWrapper accessibilityPerformAction:pAction];
+}
+
+-(NSArray *)accessibilityActionNames
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityActionNames];
+ else
+ return [NSArray array];
+}
+
+-(id)accessibilityHitTest:(NSPoint)aPoint
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityHitTest:aPoint];
+ else
+ return nil;
+}
+
+-(id)accessibilityParent
+{
+ return [self window];
+}
+
+-(NSArray *)accessibilityVisibleChildren
+{
+ return [self accessibilityChildren];
+}
+
+-(NSArray *)accessibilitySelectedChildren
+{
+ SolarMutexGuard aGuard;
+
+ NSArray *pRet = [super accessibilityChildren];
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ pRet = getMergedAccessibilityChildren(pRet, [mpChildWrapper accessibilitySelectedChildren]);
+
+ return pRet;
+}
+
+-(NSArray *)accessibilityChildren
+{
+ SolarMutexGuard aGuard;
+
+ NSArray *pRet = [super accessibilityChildren];
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ pRet = getMergedAccessibilityChildren(pRet, [mpChildWrapper accessibilityChildren]);
+
+ return pRet;
+}
+
+-(NSArray <id<NSAccessibilityElement>> *)accessibilityChildrenInNavigationOrder
+{
+ return [self accessibilityChildren];
+}
+
+@end
+
+@implementation SalFrameViewA11yWrapper
+
+-(id)initWithParent:(SalFrameView *)pParentView accessibleContext:(::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessibleContext >&)rxAccessibleContext
+{
+ [super init];
+
+ maReferenceWrapper.rAccessibleContext = rxAccessibleContext;
+
+ mpParentView = pParentView;
+ if (mpParentView)
+ {
+ [mpParentView retain];
+ [self setAccessibilityParent:mpParentView];
+ }
+
+ return self;
+}
+
+-(void)dealloc
+{
+ if (mpParentView)
+ [mpParentView release];
+
+ [super dealloc];
+}
+
+-(id)parentAttribute
+{
+ if (mpParentView)
+ return NSAccessibilityUnignoredAncestor(mpParentView);
+ else
+ return nil;
+}
+
+-(void)setAccessibilityParent:(id)pObject
+{
+ if (mpParentView)
+ {
+ [mpParentView release];
+ mpParentView = nil;
+ }
+
+ if (pObject && [pObject isKindOfClass:[SalFrameView class]])
+ {
+ mpParentView = static_cast<SalFrameView *>(pObject);
+ [mpParentView retain];
+ }
+
+ [super setAccessibilityParent:mpParentView];
+}
+
+-(id)windowAttribute
+{
+ if (mpParentView)
+ return [mpParentView window];
+ else
+ return nil;
+}
+
+-(NSWindow *)windowForParent
+{
+ return [self windowAttribute];
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salgdiutils.cxx b/vcl/osx/salgdiutils.cxx
new file mode 100644
index 0000000000..a944529321
--- /dev/null
+++ b/vcl/osx/salgdiutils.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 <sal/config.h>
+
+#include <cstdint>
+
+#include <sal/log.hxx>
+
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/range/b2drectangle.hxx>
+#include <basegfx/range/b2irange.hxx>
+#include <basegfx/vector/b2ivector.hxx>
+#include <vcl/svapp.hxx>
+
+#include <quartz/salgdi.h>
+#include <quartz/utils.h>
+#include <osx/salframe.h>
+#include <osx/saldata.hxx>
+
+#if HAVE_FEATURE_SKIA
+#include <tools/sk_app/mac/WindowContextFactory_mac.h>
+#include <vcl/skia/SkiaHelper.hxx>
+#endif
+
+static bool bTotalScreenBounds = false;
+static NSRect aTotalScreenBounds = NSZeroRect;
+
+// TODO: Scale will be set to 2.0f as default after implementation of full scaled display support . This will allow moving of
+// windows between non retina and retina displays without blurry text and graphics. Static variables have to be removed thereafter.
+
+// Currently scaled display support is not implemented for bitmaps. This will cause a slight performance degradation on displays
+// with single precision. To preserve performance for now, window scaling is only activated if at least one display with double
+// precision is present. Moving windows between displays is then possible without blurry text and graphics too. Adapting window
+// scaling when displays are added while application is running is not supported.
+
+static bool bWindowScaling = false;
+static float fWindowScale = 1.0f;
+
+namespace sal::aqua
+{
+NSRect getTotalScreenBounds()
+{
+ if (!bTotalScreenBounds)
+ {
+ aTotalScreenBounds = NSZeroRect;
+
+ NSArray *aScreens = [NSScreen screens];
+ if (aScreens != nullptr)
+ {
+ for (NSScreen *aScreen : aScreens)
+ {
+ // Calculate total screen bounds
+ NSRect aScreenFrame = [aScreen frame];
+ if (!NSIsEmptyRect(aScreenFrame))
+ {
+ if (NSIsEmptyRect(aTotalScreenBounds))
+ aTotalScreenBounds = aScreenFrame;
+ else
+ aTotalScreenBounds = NSUnionRect( aScreenFrame, aTotalScreenBounds );
+ }
+ }
+ bTotalScreenBounds = true;
+ }
+ }
+ return aTotalScreenBounds;
+}
+
+void resetTotalScreenBounds()
+{
+ bTotalScreenBounds = false;
+ getTotalScreenBounds();
+}
+
+float getWindowScaling()
+{
+ // Related: tdf#147342 Any changes to this function must be copied to the
+ // sk_app::GetBackingScaleFactor() function in the following file:
+ // workdir/UnpackedTarball/skia/tools/sk_app/mac/WindowContextFactory_mac.h
+ if (!bWindowScaling)
+ {
+ NSArray *aScreens = [NSScreen screens];
+ if (aScreens != nullptr)
+ {
+ for (NSScreen *aScreen : aScreens)
+ {
+ float fScale = [aScreen backingScaleFactor];
+ if (fScale > fWindowScale)
+ fWindowScale = fScale;
+ }
+ bWindowScaling = true;
+ }
+ if( const char* env = getenv("SAL_FORCE_HIDPI_SCALING"))
+ {
+ fWindowScale = atof(env);
+ bWindowScaling = true;
+ }
+ }
+ return fWindowScale;
+}
+
+void resetWindowScaling()
+{
+ // Related: tdf#147342 Force recalculation of the window scaling but keep
+ // the previous window scaling as the minimum so that we don't lose the
+ // resolution in cached images if a HiDPI monitor is disconnected and
+ // then reconnected.
+#if HAVE_FEATURE_SKIA
+ if ( SkiaHelper::isVCLSkiaEnabled() )
+ sk_app::ResetBackingScaleFactor();
+#endif
+ bWindowScaling = false;
+ getWindowScaling();
+}
+} // end aqua
+
+void AquaSalGraphics::SetWindowGraphics( AquaSalFrame* pFrame )
+{
+ maShared.mpFrame = pFrame;
+ maShared.mbWindow = true;
+ maShared.mbPrinter = false;
+ maShared.mbVirDev = false;
+ mpBackend->UpdateGeometryProvider(pFrame);
+}
+
+void AquaSalGraphics::SetPrinterGraphics( CGContextRef xContext, sal_Int32 nDPIX, sal_Int32 nDPIY )
+{
+ maShared.mbWindow = false;
+ maShared.mbPrinter = true;
+ maShared.mbVirDev = false;
+
+ maShared.maContextHolder.set(xContext);
+ mnRealDPIX = nDPIX;
+ mnRealDPIY = nDPIY;
+
+ // a previously set clip path is now invalid
+ maShared.unsetClipPath();
+
+ if (maShared.maContextHolder.isSet())
+ {
+ CGContextSetFillColorSpace( maShared.maContextHolder.get(), GetSalData()->mxRGBSpace );
+ CGContextSetStrokeColorSpace( maShared.maContextHolder.get(), GetSalData()->mxRGBSpace );
+ CGContextSaveGState( maShared.maContextHolder.get() );
+ maShared.setState();
+ }
+
+ mpBackend->UpdateGeometryProvider(nullptr);
+}
+
+void AquaSalGraphics::InvalidateContext()
+{
+ UnsetState();
+
+ CGContextRelease(maShared.maContextHolder.get());
+ CGContextRelease(maShared.maBGContextHolder.get());
+ CGContextRelease(maShared.maCSContextHolder.get());
+
+ maShared.maContextHolder.set(nullptr);
+ maShared.maCSContextHolder.set(nullptr);
+ maShared.maBGContextHolder.set(nullptr);
+}
+
+void AquaSalGraphics::UnsetState()
+{
+ if (maShared.maBGContextHolder.isSet())
+ {
+ CGContextRelease(maShared.maBGContextHolder.get());
+ maShared.maBGContextHolder.set(nullptr);
+ }
+ if (maShared.maCSContextHolder.isSet())
+ {
+ CGContextRelease(maShared.maCSContextHolder.get());
+ maShared.maBGContextHolder.set(nullptr);
+ }
+ if (maShared.maContextHolder.isSet())
+ {
+ maShared.maContextHolder.restoreState();
+ maShared.maContextHolder.set(nullptr);
+ }
+ maShared.unsetState();
+}
+
+/**
+ * (re-)create the off-screen maLayer we render everything to if
+ * necessary: eg. not initialized yet, or it has an incorrect size.
+ */
+bool AquaSharedAttributes::checkContext()
+{
+ if (mbWindow && mpFrame && (mpFrame->getNSWindow() || Application::IsBitmapRendering()))
+ {
+ const unsigned int nWidth = mpFrame->maGeometry.width();
+ const unsigned int nHeight = mpFrame->maGeometry.height();
+ const float fScale = sal::aqua::getWindowScaling();
+ CGLayerRef rReleaseLayer = nullptr;
+
+ // check if a new drawing context is needed (e.g. after a resize)
+ if( (unsigned(mnWidth) != nWidth) || (unsigned(mnHeight) != nHeight) )
+ {
+ mnWidth = nWidth;
+ mnHeight = nHeight;
+ // prepare to release the corresponding resources
+ if (maLayer.isSet())
+ {
+ rReleaseLayer = maLayer.get();
+ }
+ else if (maContextHolder.isSet())
+ {
+ CGContextRelease(maContextHolder.get());
+ }
+ CGContextRelease(maBGContextHolder.get());
+ CGContextRelease(maCSContextHolder.get());
+
+ maContextHolder.set(nullptr);
+ maBGContextHolder.set(nullptr);
+ maCSContextHolder.set(nullptr);
+ maLayer.set(nullptr);
+ }
+
+ if (!maContextHolder.isSet())
+ {
+ const int nBitmapDepth = 32;
+
+ float nScaledWidth = mnWidth * fScale;
+ float nScaledHeight = mnHeight * fScale;
+
+ const CGSize aLayerSize = { static_cast<CGFloat>(nScaledWidth), static_cast<CGFloat>(nScaledHeight) };
+
+ const int nBytesPerRow = (nBitmapDepth * nScaledWidth) / 8;
+ std::uint32_t nFlags = std::uint32_t(kCGImageAlphaNoneSkipFirst)
+ | std::uint32_t(kCGBitmapByteOrder32Host);
+ maBGContextHolder.set(CGBitmapContextCreate(
+ nullptr, nScaledWidth, nScaledHeight, 8, nBytesPerRow, GetSalData()->mxRGBSpace, nFlags));
+
+ maLayer.set(CGLayerCreateWithContext(maBGContextHolder.get(), aLayerSize, nullptr));
+ maLayer.setScale(fScale);
+
+ nFlags = std::uint32_t(kCGImageAlphaPremultipliedFirst)
+ | std::uint32_t(kCGBitmapByteOrder32Host);
+ maCSContextHolder.set(CGBitmapContextCreate(
+ nullptr, nScaledWidth, nScaledHeight, 8, nBytesPerRow, GetSalData()->mxRGBSpace, nFlags));
+
+ CGContextRef xDrawContext = CGLayerGetContext(maLayer.get());
+ maContextHolder = xDrawContext;
+
+ if (rReleaseLayer)
+ {
+ // copy original layer to resized layer
+ if (maContextHolder.isSet())
+ {
+ CGContextDrawLayerAtPoint(maContextHolder.get(), CGPointZero, rReleaseLayer);
+ }
+ CGLayerRelease(rReleaseLayer);
+ }
+
+ if (maContextHolder.isSet())
+ {
+ CGContextTranslateCTM(maContextHolder.get(), 0, nScaledHeight);
+ CGContextScaleCTM(maContextHolder.get(), 1.0, -1.0);
+ CGContextSetFillColorSpace(maContextHolder.get(), GetSalData()->mxRGBSpace);
+ CGContextSetStrokeColorSpace(maContextHolder.get(), GetSalData()->mxRGBSpace);
+ // apply a scale matrix so everything is auto-magically scaled
+ CGContextScaleCTM(maContextHolder.get(), fScale, fScale);
+ maContextHolder.saveState();
+ setState();
+
+ // re-enable XOR emulation for the new context
+ if (mpXorEmulation)
+ mpXorEmulation->SetTarget(mnWidth, mnHeight, mnBitmapDepth, maContextHolder.get(), maLayer.get());
+ }
+ }
+ }
+
+ SAL_WARN_IF(!maContextHolder.isSet() && !mbPrinter, "vcl", "<<<WARNING>>> AquaSalGraphics::CheckContext() FAILED!!!!");
+
+ return maContextHolder.isSet();
+}
+
+/**
+ * Blit the contents of our internal maLayer state to the
+ * associated window, if any; cf. drawRect event handling
+ * on the frame.
+ */
+void AquaSalGraphics::UpdateWindow( NSRect& )
+{
+ if (!maShared.mpFrame)
+ {
+ return;
+ }
+
+ NSGraphicsContext* pContext = [NSGraphicsContext currentContext];
+ if (maShared.maLayer.isSet() && pContext != nullptr)
+ {
+ CGContextHolder rCGContextHolder([pContext CGContext]);
+
+ rCGContextHolder.saveState();
+
+ // Related: tdf#155092 translate Y coordinate for height differences
+ // When in live resize, the NSView's height may have changed before
+ // the CGLayer has been resized. This causes the CGLayer's content
+ // to be drawn just above or below the top left corner of the view
+ // so translate the Y coordinate by any difference between the
+ // NSView's height and the CGLayer's height.
+ NSView *pView = maShared.mpFrame->mpNSView;
+ if (pView)
+ {
+ // Use the NSView's bounds, not its frame, to properly handle
+ // any rotation and/or scaling that might have been already
+ // applied to the view
+ CGFloat fTranslateY = [pView bounds].size.height - maShared.maLayer.getSizePoints().height;
+ CGContextTranslateCTM(rCGContextHolder.get(), 0, fTranslateY);
+ }
+
+ CGMutablePathRef rClip = maShared.mpFrame->getClipPath();
+ if (rClip)
+ {
+ CGContextBeginPath(rCGContextHolder.get());
+ CGContextAddPath(rCGContextHolder.get(), rClip );
+ CGContextClip(rCGContextHolder.get());
+ }
+
+ maShared.applyXorContext();
+
+ const CGSize aSize = maShared.maLayer.getSizePoints();
+ const CGRect aRect = CGRectMake(0, 0, aSize.width, aSize.height);
+ const CGRect aRectPoints = { CGPointZero, maShared.maLayer.getSizePixels() };
+ CGContextSetBlendMode(maShared.maCSContextHolder.get(), kCGBlendModeCopy);
+ CGContextDrawLayerInRect(maShared.maCSContextHolder.get(), aRectPoints, maShared.maLayer.get());
+
+ CGImageRef img = CGBitmapContextCreateImage(maShared.maCSContextHolder.get());
+ CGImageRef displayColorSpaceImage = CGImageCreateCopyWithColorSpace(img, [[maShared.mpFrame->getNSWindow() colorSpace] CGColorSpace]);
+ CGContextSetBlendMode(rCGContextHolder.get(), kCGBlendModeCopy);
+ CGContextDrawImage(rCGContextHolder.get(), aRect, displayColorSpaceImage);
+
+ CGImageRelease(img);
+ CGImageRelease(displayColorSpaceImage);
+
+ rCGContextHolder.restoreState();
+ }
+ else
+ {
+ SAL_WARN_IF(!maShared.mpFrame->mbInitShow, "vcl", "UpdateWindow called on uneligible graphics");
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salinst.cxx b/vcl/osx/salinst.cxx
new file mode 100644
index 0000000000..7f755bb618
--- /dev/null
+++ b/vcl/osx/salinst.cxx
@@ -0,0 +1,1093 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <condition_variable>
+#include <mutex>
+#include <utility>
+
+#include <config_features.h>
+
+#include <stdio.h>
+
+#include <comphelper/solarmutex.hxx>
+
+#include <comphelper/lok.hxx>
+
+#include <osl/process.h>
+
+#include <rtl/ustrbuf.hxx>
+#include <vclpluginapi.h>
+#include <vcl/QueueInfo.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/svmain.hxx>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+
+#include <osx/saldata.hxx>
+#include <osx/salinst.h>
+#include <osx/salframe.h>
+#include <osx/salobj.h>
+#include <osx/salsys.h>
+#include <quartz/salvd.h>
+#include <quartz/salbmp.h>
+#include <quartz/utils.h>
+#include <osx/salprn.h>
+#include <osx/saltimer.h>
+#include <osx/vclnsapp.h>
+#include <osx/runinmain.hxx>
+
+#include <print.h>
+#include <strings.hrc>
+
+#include <comphelper/processfactory.hxx>
+
+#include <com/sun/star/uri/ExternalUriReferenceTranslator.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <premac.h>
+#include <Foundation/Foundation.h>
+#include <ApplicationServices/ApplicationServices.h>
+#import "apple_remote/RemoteMainController.h"
+#include <apple_remote/RemoteControl.h>
+#include <postmac.h>
+
+#if HAVE_FEATURE_SKIA
+#include <vcl/skia/SkiaHelper.hxx>
+#include <skia/salbmp.hxx>
+#include <skia/osx/gdiimpl.hxx>
+#include <skia/osx/bitmap.hxx>
+#endif
+
+extern "C" {
+#include <crt_externs.h>
+}
+
+using namespace ::com::sun::star;
+
+static int* gpnInit = nullptr;
+static NSMenu* pDockMenu = nil;
+static bool bLeftMain = false;
+
+namespace {
+
+class AquaDelayedSettingsChanged : public Idle
+{
+ bool mbInvalidate;
+
+public:
+ AquaDelayedSettingsChanged( bool bInvalidate ) :
+ Idle("AquaSalInstance AquaDelayedSettingsChanged"),
+ mbInvalidate( bInvalidate )
+ {
+ }
+
+ virtual void Invoke() override
+ {
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ SalFrame *pAnyFrame = pInst->anyFrame();
+ if( pAnyFrame )
+ pAnyFrame->CallCallback( SalEvent::SettingsChanged, nullptr );
+
+ if( mbInvalidate )
+ {
+ for( auto pSalFrame : pInst->getFrames() )
+ {
+ AquaSalFrame* pFrame = static_cast<AquaSalFrame*>( pSalFrame );
+ if( pFrame->mbShown )
+ pFrame->SendPaintEvent();
+ }
+ }
+ delete this;
+ }
+};
+
+}
+
+static OUString& getFallbackPrinterName()
+{
+ static OUString aFallbackPrinter;
+
+ if ( aFallbackPrinter.isEmpty() )
+ {
+ aFallbackPrinter = VclResId( SV_PRINT_DEFPRT_TXT );
+ if ( aFallbackPrinter.isEmpty() )
+ aFallbackPrinter = "Printer";
+ }
+
+ return aFallbackPrinter;
+}
+
+void AquaSalInstance::delayedSettingsChanged( bool bInvalidate )
+{
+ osl::Guard< comphelper::SolarMutex > aGuard( *GetYieldMutex() );
+ AquaDelayedSettingsChanged* pIdle = new AquaDelayedSettingsChanged( bInvalidate );
+ pIdle->Start();
+}
+
+// the std::list<const ApplicationEvent*> must be available before any SalData/SalInst/etc. objects are ready
+std::list<const ApplicationEvent*> AquaSalInstance::aAppEventList;
+
+NSMenu* AquaSalInstance::GetDynamicDockMenu()
+{
+ if( ! pDockMenu && ! bLeftMain )
+ pDockMenu = [[NSMenu alloc] initWithTitle: @""];
+ return pDockMenu;
+}
+
+bool AquaSalInstance::isOnCommandLine( const OUString& rArg )
+{
+ sal_uInt32 nArgs = osl_getCommandArgCount();
+ for( sal_uInt32 i = 0; i < nArgs; i++ )
+ {
+ OUString aArg;
+ osl_getCommandArg( i, &aArg.pData );
+ if( aArg.equals( rArg ) )
+ return true;
+ }
+ return false;
+}
+
+void AquaSalInstance::AfterAppInit()
+{
+ [[NSNotificationCenter defaultCenter] addObserver: NSApp
+ selector: @selector(systemColorsChanged:)
+ name: NSSystemColorsDidChangeNotification
+ object: nil ];
+ [[NSNotificationCenter defaultCenter] addObserver: NSApp
+ selector: @selector(screenParametersChanged:)
+ name: NSApplicationDidChangeScreenParametersNotification
+ object: nil ];
+ // add observers for some settings changes that affect vcl's settings
+ // scrollbar variant
+ [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
+ selector: @selector(scrollbarVariantChanged:)
+ name: @"AppleAquaScrollBarVariantChanged"
+ object: nil ];
+ // scrollbar page behavior ("jump to here" or not)
+ [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
+ selector: @selector(scrollbarSettingsChanged:)
+ name: @"AppleNoRedisplayAppearancePreferenceChanged"
+ object: nil ];
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+ // Initialize Apple Remote
+ GetSalData()->mpAppleRemoteMainController = [[AppleRemoteMainController alloc] init];
+
+ [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
+ selector: @selector(applicationWillBecomeActive:)
+ name: @"AppleRemoteWillBecomeActive"
+ object: nil ];
+
+ [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
+ selector: @selector(applicationWillResignActive:)
+ name: @"AppleRemoteWillResignActive"
+ object: nil ];
+#endif
+
+ // HACK: When the first call to [NSSpellChecker sharedSpellChecker] (in
+ // lingucomponent/source/spellcheck/macosxspell/macspellimp.mm) is done both on a thread other
+ // than the main thread and with the SolarMutex erroneously locked, then that can lead to
+ // deadlock as [NSSpellChecker sharedSpellChecker] internally calls
+ // AppKit`-[NSSpellChecker init] ->
+ // AppKit`-[NSSpellChecker _fillSpellCheckerPopupButton:] ->
+ // AppKit`-[NSApplication(NSServicesMenuPrivate) _fillSpellCheckerPopupButton:] ->
+ // AppKit`-[NSMenu insertItem:atIndex:] ->
+ // Foundation`-[NSNotificationCenter postNotificationName:object:userInfo:] ->
+ // CoreFoundation`_CFXNotificationPost ->
+ // Foundation`-[NSOperation waitUntilFinished]
+ // waiting for work to be done on the main thread, but the main thread is typically already
+ // blocked (in some event handling loop) waiting to acquire the SolarMutex. The real solution
+ // would be to fix all the cases where a call to [NSSpellChecker sharedSpellChecker] in
+ // lingucomponent/source/spellcheck/macosxspell/macspellimp.mm is done while the SolarMutex is
+ // locked (somewhere up the call chain), but that appears to be rather difficult (see e.g.
+ // <https://bugs.documentfoundation.org/show_bug.cgi?id=151894> "FILEOPEN a Base Document with
+ // customized event for open a startform by 'open document' LO stuck"). So, at least for now,
+ // chicken out and do that first call to [NSSpellChecker sharedSpellChecker] upfront in a
+ // controlled environment:
+ [NSSpellChecker sharedSpellChecker];
+}
+
+SalYieldMutex::SalYieldMutex()
+ : m_aCodeBlock( nullptr )
+{
+}
+
+SalYieldMutex::~SalYieldMutex()
+{
+}
+
+void SalYieldMutex::doAcquire( sal_uInt32 nLockCount )
+{
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ if ( pInst && pInst->IsMainThread() )
+ {
+ if ( pInst->mbNoYieldLock )
+ return;
+ do {
+ RuninmainBlock block = nullptr;
+ {
+ std::unique_lock<std::mutex> g(m_runInMainMutex);
+ if (m_aMutex.tryToAcquire()) {
+ assert(m_aCodeBlock == nullptr);
+ m_wakeUpMain = false;
+ break;
+ }
+ // wait for doRelease() or RUNINMAIN_* to set the condition
+ m_aInMainCondition.wait(g, [this]() { return m_wakeUpMain; });
+ m_wakeUpMain = false;
+ std::swap(block, m_aCodeBlock);
+ }
+ if ( block )
+ {
+ assert( !pInst->mbNoYieldLock );
+ pInst->mbNoYieldLock = true;
+ block();
+ pInst->mbNoYieldLock = false;
+ Block_release( block );
+ std::scoped_lock<std::mutex> g(m_runInMainMutex);
+ assert(!m_resultReady);
+ m_resultReady = true;
+ m_aResultCondition.notify_all();
+ }
+ }
+ while ( true );
+ }
+ else
+ m_aMutex.acquire();
+ ++m_nCount;
+ --nLockCount;
+
+ comphelper::SolarMutex::doAcquire( nLockCount );
+}
+
+sal_uInt32 SalYieldMutex::doRelease( const bool bUnlockAll )
+{
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ if ( pInst->mbNoYieldLock && pInst->IsMainThread() )
+ return 1;
+ sal_uInt32 nCount;
+ {
+ std::scoped_lock<std::mutex> g(m_runInMainMutex);
+ // read m_nCount before doRelease
+ bool const isReleased(bUnlockAll || m_nCount == 1);
+ nCount = comphelper::SolarMutex::doRelease( bUnlockAll );
+ if (isReleased && !pInst->IsMainThread()) {
+ m_wakeUpMain = true;
+ m_aInMainCondition.notify_all();
+ }
+ }
+ return nCount;
+}
+
+bool SalYieldMutex::IsCurrentThread() const
+{
+ if ( !GetSalData()->mpInstance->mbNoYieldLock )
+ return comphelper::SolarMutex::IsCurrentThread();
+ else
+ return GetSalData()->mpInstance->IsMainThread();
+}
+
+// some convenience functions regarding the yield mutex, aka solar mutex
+
+bool ImplSalYieldMutexTryToAcquire()
+{
+ AquaSalInstance* pInst = GetSalData()->mpInstance;
+ if ( pInst )
+ return pInst->GetYieldMutex()->tryToAcquire();
+ else
+ return false;
+}
+
+void ImplSalYieldMutexRelease()
+{
+ AquaSalInstance* pInst = GetSalData()->mpInstance;
+ if ( pInst )
+ pInst->GetYieldMutex()->release();
+}
+
+extern "C" {
+VCLPLUG_OSX_PUBLIC SalInstance* create_SalInstance()
+{
+ SalData* pSalData = new SalData;
+
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ unlink([[NSString stringWithFormat:@"%@/Library/Saved Application State/%s.savedState/restorecount.plist", NSHomeDirectory(), MACOSX_BUNDLE_IDENTIFIER] UTF8String]);
+ unlink([[NSString stringWithFormat:@"%@/Library/Saved Application State/%s.savedState/restorecount.txt", NSHomeDirectory(), MACOSX_BUNDLE_IDENTIFIER] UTF8String]);
+ [ pool drain ];
+
+ // create our cocoa NSApplication
+ [VCL_NSApplication sharedApplication];
+
+ SalData::ensureThreadAutoreleasePool();
+
+ // put cocoa into multithreaded mode
+ [NSThread detachNewThreadSelector:@selector(enableCocoaThreads:) toTarget:[[CocoaThreadEnabler alloc] init] withObject:nil];
+
+ // activate our delegate methods
+ [NSApp setDelegate: NSApp];
+
+ SAL_WARN_IF( pSalData->mpInstance != nullptr, "vcl", "more than one instance created" );
+ AquaSalInstance* pInst = new AquaSalInstance;
+
+ // init instance (only one instance in this version !!!)
+ pSalData->mpInstance = pInst;
+ // this one is for outside AquaSalInstance::Yield
+ SalData::ensureThreadAutoreleasePool();
+ // no focus rects on NWF
+ ImplGetSVData()->maNWFData.mbNoFocusRects = true;
+ ImplGetSVData()->maNWFData.mbNoActiveTabTextRaise = true;
+ ImplGetSVData()->maNWFData.mbCenteredTabs = true;
+ ImplGetSVData()->maNWFData.mnStatusBarLowerRightOffset = 10;
+
+ return pInst;
+}
+}
+
+AquaSalInstance::AquaSalInstance()
+ : SalInstance(std::make_unique<SalYieldMutex>())
+ , mnActivePrintJobs( 0 )
+ , mbNoYieldLock( false )
+ , mbTimerProcessed( false )
+{
+ maMainThread = osl::Thread::getCurrentIdentifier();
+
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mxToolkitName = OUString("osx");
+ m_bSupportsOpenGL = true;
+
+ mpButtonCell = [[NSButtonCell alloc] init];
+ mpCheckCell = [[NSButtonCell alloc] init];
+ mpRadioCell = [[NSButtonCell alloc] init];
+ mpTextFieldCell = [[NSTextFieldCell alloc] initTextCell:@""];
+ mpComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
+ mpPopUpButtonCell = [[NSPopUpButtonCell alloc] init];
+ mpStepperCell = [[NSStepperCell alloc] init];
+ mpListNodeCell = [[NSButtonCell alloc] init];
+
+#if HAVE_FEATURE_SKIA
+ AquaSkiaSalGraphicsImpl::prepareSkia();
+#endif
+}
+
+AquaSalInstance::~AquaSalInstance()
+{
+ [NSApp stop: NSApp];
+ bLeftMain = true;
+ if( pDockMenu )
+ {
+ [pDockMenu release];
+ pDockMenu = nil;
+ }
+
+ [mpListNodeCell release];
+ [mpStepperCell release];
+ [mpPopUpButtonCell release];
+ [mpComboBoxCell release];
+ [mpTextFieldCell release];
+ [mpRadioCell release];
+ [mpCheckCell release];
+ [mpButtonCell release];
+
+#if HAVE_FEATURE_SKIA
+ SkiaHelper::cleanup();
+#endif
+}
+
+void AquaSalInstance::TriggerUserEventProcessing()
+{
+ dispatch_async(dispatch_get_main_queue(),^{
+ ImplNSAppPostEvent( AquaSalInstance::YieldWakeupEvent, NO );
+ });
+}
+
+void AquaSalInstance::ProcessEvent( SalUserEvent aEvent )
+{
+ aEvent.m_pFrame->CallCallback( aEvent.m_nEvent, aEvent.m_pData );
+ maWaitingYieldCond.set();
+}
+
+bool AquaSalInstance::IsMainThread() const
+{
+ return osl::Thread::getCurrentIdentifier() == maMainThread;
+}
+
+void AquaSalInstance::handleAppDefinedEvent( NSEvent* pEvent )
+{
+ AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
+ int nSubtype = [pEvent subtype];
+ switch( nSubtype )
+ {
+ case AppStartTimerEvent:
+ if ( pTimer )
+ pTimer->handleStartTimerEvent( pEvent );
+ break;
+ case AppExecuteSVMain:
+ {
+ int nRet = ImplSVMain();
+ if (gpnInit)
+ *gpnInit = nRet;
+ [NSApp stop: NSApp];
+ break;
+ }
+ case DispatchTimerEvent:
+ {
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ if ( pTimer && pInst )
+ pInst->mbTimerProcessed = pTimer->handleDispatchTimerEvent( pEvent );
+ break;
+ }
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+ case AppleRemoteControlEvent: // Defined in <apple_remote/RemoteMainController.h>
+ {
+ MediaCommand nCommand;
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ bool bIsFullScreenMode = false;
+
+ for( auto pSalFrame : pInst->getFrames() )
+ {
+ const AquaSalFrame* pFrame = static_cast<const AquaSalFrame*>( pSalFrame );
+ if ( pFrame->mbFullScreen )
+ {
+ bIsFullScreenMode = true;
+ break;
+ }
+ }
+
+ switch ([pEvent data1])
+ {
+ case kRemoteButtonPlay:
+ nCommand = bIsFullScreenMode ? MediaCommand::PlayPause : MediaCommand::Play;
+ break;
+
+ // kept for experimentation purpose (scheduled for future implementation)
+ // case kRemoteButtonMenu: nCommand = MediaCommand::Menu; break;
+
+ case kRemoteButtonPlus: nCommand = MediaCommand::VolumeUp; break;
+
+ case kRemoteButtonMinus: nCommand = MediaCommand::VolumeDown; break;
+
+ case kRemoteButtonRight: nCommand = MediaCommand::NextTrack; break;
+
+ case kRemoteButtonRight_Hold: nCommand = MediaCommand::NextTrackHold; break;
+
+ case kRemoteButtonLeft: nCommand = MediaCommand::PreviousTrack; break;
+
+ case kRemoteButtonLeft_Hold: nCommand = MediaCommand::Rewind; break;
+
+ case kRemoteButtonPlay_Hold: nCommand = MediaCommand::PlayHold; break;
+
+ case kRemoteButtonMenu_Hold: nCommand = MediaCommand::Stop; break;
+
+ // FIXME : not detected
+ case kRemoteButtonPlus_Hold:
+ case kRemoteButtonMinus_Hold:
+ break;
+
+ default:
+ break;
+ }
+ AquaSalFrame* pFrame = static_cast<AquaSalFrame*>( pInst->anyFrame() );
+ vcl::Window* pWindow = pFrame ? pFrame->GetWindow() : nullptr;
+ if( pWindow )
+ {
+ const Point aPoint;
+ CommandMediaData aMediaData(nCommand);
+ CommandEvent aCEvt( aPoint, CommandEventId::Media, false, &aMediaData );
+ NotifyEvent aNCmdEvt( NotifyEventType::COMMAND, pWindow, &aCEvt );
+
+ if ( !ImplCallPreNotify( aNCmdEvt ) )
+ pWindow->Command( aCEvt );
+ }
+
+ }
+ break;
+#endif
+
+ case YieldWakeupEvent:
+ // do nothing, fall out of Yield
+ break;
+
+ default:
+ OSL_FAIL( "unhandled NSEventTypeApplicationDefined event" );
+ break;
+ }
+}
+
+bool AquaSalInstance::RunInMainYield( bool bHandleAllCurrentEvents )
+{
+ OSX_SALDATA_RUNINMAIN_UNION( DoYield( false, bHandleAllCurrentEvents), boolean )
+
+ // PrinterController::removeTransparencies() calls this frequently on the
+ // main thread so reduce the severity from an assert so that printing still
+ // works in a debug builds
+ SAL_WARN_IF( true, "vcl", "Don't call this from the main thread!" );
+ return false;
+
+}
+
+static bool isWakeupEvent( NSEvent *pEvent )
+{
+ return NSEventTypeApplicationDefined == [pEvent type]
+ && AquaSalInstance::YieldWakeupEvent == static_cast<int>([pEvent subtype]);
+}
+
+bool AquaSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
+{
+ // ensure that the per thread autorelease pool is top level and
+ // will therefore not be destroyed by cocoa implicitly
+ SalData::ensureThreadAutoreleasePool();
+
+ // NSAutoreleasePool documentation suggests we should have
+ // an own pool for each yield level
+ ReleasePoolHolder aReleasePool;
+
+ // first, process current user events
+ // Related: tdf#152703 Eliminate potential blocking during live resize
+ // Only native events and timers need to be dispatched to redraw
+ // the window so skip dispatching user events when a window is in
+ // live resize
+ bool bHadEvent = ( !ImplGetSVData()->mpWinData->mbIsLiveResize && DispatchUserEvents( bHandleAllCurrentEvents ) );
+ if ( !bHandleAllCurrentEvents && bHadEvent )
+ return true;
+
+ // handle cocoa event queue
+ // cocoa events may be only handled in the thread the NSApp was created
+ if( IsMainThread() && mnActivePrintJobs == 0 )
+ {
+ // handle available events
+ NSEvent* pEvent = nil;
+ NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
+ mbTimerProcessed = false;
+
+ int noLoops = 0;
+ do
+ {
+ SolarMutexReleaser aReleaser;
+
+ pEvent = [NSApp nextEventMatchingMask: NSEventMaskAny
+ untilDate: [NSDate distantPast]
+ inMode: NSDefaultRunLoopMode
+ dequeue: YES];
+ if( pEvent )
+ {
+ // tdf#155092 don't dispatch left mouse up events during live resizing
+ // If this is a left mouse up event, dispatching this event
+ // will trigger tdf#155092 to occur in the next mouse down
+ // event. So do not dispatch this event and push it back onto
+ // the front of the event queue so no more events will be
+ // dispatched until live resizing ends. Surprisingly, live
+ // resizing appears to end in the next mouse down event.
+ if ( ImplGetSVData()->mpWinData->mbIsLiveResize && [pEvent type] == NSEventTypeLeftMouseUp )
+ {
+ [NSApp postEvent: pEvent atStart: YES];
+ return false;
+ }
+
+ [NSApp sendEvent: pEvent];
+ if ( isWakeupEvent( pEvent ) )
+ continue;
+ bHadEvent = true;
+ }
+
+ [NSApp updateWindows];
+
+ if ( !bHandleAllCurrentEvents || !pEvent || now < [pEvent timestamp] )
+ break;
+ // noelgrandin: I see sporadic hangs on the macos jenkins boxes, and the backtrace
+ // points to the this loop - let us see if breaking out of here after too many
+ // trips around helps.
+ noLoops++;
+ if (noLoops == 100)
+ break;
+ }
+ while( true );
+
+ AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
+ if ( !mbTimerProcessed && pTimer && pTimer->IsDirectTimeout() )
+ {
+ pTimer->handleTimerElapsed();
+ bHadEvent = true;
+ }
+
+ // if we had no event yet, wait for one if requested
+ // Related: tdf#152703 Eliminate potential blocking during live resize
+ // Some events and timers call Application::Reschedule() or
+ // Application::Yield() so don't block and wait for events when a
+ // window is in live resize
+ if( bWait && ! bHadEvent && !ImplGetSVData()->mpWinData->mbIsLiveResize )
+ {
+ SolarMutexReleaser aReleaser;
+
+ // attempt to fix macos jenkins hangs - part 3
+ // oox::xls::WorkbookFragment::finalizeImport() calls
+ // AquaSalInstance::DoYield() with bWait set to true. But
+ // since unit tests generally have no expected user generated
+ // events, we can end up blocking and waiting forever so
+ // don't block and wait when running unit tests.
+ pEvent = [NSApp nextEventMatchingMask: NSEventMaskAny
+ untilDate: SalInstance::IsRunningUnitTest() ? [NSDate distantPast] : [NSDate distantFuture]
+ inMode: NSDefaultRunLoopMode
+ dequeue: YES];
+ if( pEvent )
+ {
+ [NSApp sendEvent: pEvent];
+ if ( !isWakeupEvent( pEvent ) )
+ bHadEvent = true;
+ }
+ [NSApp updateWindows];
+ }
+
+ // collect update rectangles
+ for( auto pSalFrame : GetSalData()->mpInstance->getFrames() )
+ {
+ AquaSalFrame* pFrame = static_cast<AquaSalFrame*>( pSalFrame );
+ if( pFrame->mbShown && ! pFrame->maInvalidRect.IsEmpty() )
+ {
+ pFrame->Flush( pFrame->maInvalidRect );
+ pFrame->maInvalidRect.SetEmpty();
+ }
+ }
+
+ if ( bHadEvent )
+ maWaitingYieldCond.set();
+ }
+ else
+ {
+ bHadEvent = RunInMainYield( bHandleAllCurrentEvents );
+ if ( !bHadEvent && bWait )
+ {
+ // #i103162#
+ // wait until the main thread has dispatched an event
+ maWaitingYieldCond.reset();
+ SolarMutexReleaser aReleaser;
+ maWaitingYieldCond.wait();
+ }
+ }
+
+ // we get some apple events way too early
+ // before the application is ready to handle them,
+ // so their corresponding application events need to be delayed
+ // now is a good time to handle at least one of them
+ if( bWait && !aAppEventList.empty() && ImplGetSVData()->maAppData.mbInAppExecute )
+ {
+ // make sure that only one application event is active at a time
+ static bool bInAppEvent = false;
+ if( !bInAppEvent )
+ {
+ bInAppEvent = true;
+ // get the next delayed application event
+ const ApplicationEvent* pAppEvent = aAppEventList.front();
+ aAppEventList.pop_front();
+ // handle one application event (no recursion)
+ const ImplSVData* pSVData = ImplGetSVData();
+ pSVData->mpApp->AppEvent( *pAppEvent );
+ delete pAppEvent;
+ // allow the next delayed application event
+ bInAppEvent = false;
+ }
+ }
+
+ return bHadEvent;
+}
+
+bool AquaSalInstance::AnyInput( VclInputFlags nType )
+{
+ if( nType & VclInputFlags::APPEVENT )
+ {
+ if( ! aAppEventList.empty() )
+ return true;
+ if( nType == VclInputFlags::APPEVENT )
+ return false;
+ }
+
+ OSX_INST_RUNINMAIN_UNION( AnyInput( nType ), boolean )
+
+ if( nType & VclInputFlags::TIMER )
+ {
+ AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
+ if (pTimer && pTimer->IsTimerElapsed())
+ return true;
+ }
+
+ unsigned/*NSUInteger*/ nEventMask = 0;
+ if( nType & VclInputFlags::MOUSE)
+ {
+ nEventMask |=
+ NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown |
+ NSEventMaskLeftMouseUp | NSEventMaskRightMouseUp | NSEventMaskOtherMouseUp |
+ NSEventMaskLeftMouseDragged | NSEventMaskRightMouseDragged | NSEventMaskOtherMouseDragged |
+ NSEventMaskScrollWheel |
+ // NSEventMaskMouseMoved |
+ NSEventMaskMouseEntered | NSEventMaskMouseExited;
+
+ // Related: tdf#155266 stop delaying painting timer while swiping
+ // After fixing several flushing issues in tdf#155266, scrollbars
+ // still will not redraw until swiping has ended or paused when
+ // using Skia/Raster or Skia disabled. So, stop the delay by only
+ // including NSEventMaskScrollWheel if the current event type is
+ // not NSEventTypeScrollWheel.
+ NSEvent* pCurrentEvent = [NSApp currentEvent];
+ if( pCurrentEvent && [pCurrentEvent type] == NSEventTypeScrollWheel )
+ nEventMask &= ~NSEventMaskScrollWheel;
+ }
+
+ if( nType & VclInputFlags::KEYBOARD)
+ nEventMask |= NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged;
+ if( nType & VclInputFlags::OTHER)
+ nEventMask |= NSEventMaskTabletPoint | NSEventMaskApplicationDefined;
+ // TODO: VclInputFlags::PAINT / more VclInputFlags::OTHER
+ if( !bool(nType) )
+ return false;
+
+ NSEvent* pEvent = [NSApp nextEventMatchingMask: nEventMask untilDate: [NSDate distantPast]
+ inMode: NSDefaultRunLoopMode dequeue: NO];
+ return (pEvent != nullptr);
+}
+
+SalFrame* AquaSalInstance::CreateChildFrame( SystemParentData*, SalFrameStyleFlags /*nSalFrameStyle*/ )
+{
+ return nullptr;
+}
+
+SalFrame* AquaSalInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nSalFrameStyle )
+{
+ OSX_INST_RUNINMAIN_POINTER( CreateFrame( pParent, nSalFrameStyle ), SalFrame* )
+ return new AquaSalFrame( pParent, nSalFrameStyle );
+}
+
+void AquaSalInstance::DestroyFrame( SalFrame* pFrame )
+{
+ OSX_INST_RUNINMAIN( DestroyFrame( pFrame ) )
+ delete pFrame;
+}
+
+SalObject* AquaSalInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool /* bShow */ )
+{
+ if ( !pParent )
+ return nullptr;
+
+ OSX_INST_RUNINMAIN_POINTER( CreateObject( pParent, pWindowData, false ), SalObject* )
+ return new AquaSalObject( static_cast<AquaSalFrame*>(pParent), pWindowData );
+}
+
+void AquaSalInstance::DestroyObject( SalObject* pObject )
+{
+ OSX_INST_RUNINMAIN( DestroyObject( pObject ) )
+ delete pObject;
+}
+
+std::unique_ptr<SalPrinter> AquaSalInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
+{
+ return std::unique_ptr<SalPrinter>(new AquaSalPrinter( dynamic_cast<AquaSalInfoPrinter*>(pInfoPrinter) ));
+}
+
+void AquaSalInstance::GetPrinterQueueInfo( ImplPrnQueueList* pList )
+{
+ NSArray* pNames = [NSPrinter printerNames];
+ NSArray* pTypes = [NSPrinter printerTypes];
+ unsigned int nNameCount = pNames ? [pNames count] : 0;
+ unsigned int nTypeCount = pTypes ? [pTypes count] : 0;
+ SAL_WARN_IF( nTypeCount != nNameCount, "vcl", "type count not equal to printer count" );
+ for( unsigned int i = 0; i < nNameCount; i++ )
+ {
+ NSString* pName = [pNames objectAtIndex: i];
+ NSString* pType = i < nTypeCount ? [pTypes objectAtIndex: i] : nil;
+ if( pName )
+ {
+ std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo);
+ pInfo->maPrinterName = GetOUString( pName );
+ if( pType )
+ pInfo->maDriver = GetOUString( pType );
+ pInfo->mnStatus = PrintQueueFlags::NONE;
+ pInfo->mnJobs = 0;
+
+ pList->Add( std::move(pInfo) );
+ }
+ }
+
+ // tdf#151700 Prevent the non-native LibreOffice PrintDialog from
+ // displaying by creating a fake printer if there are no printers. This
+ // will allow the LibreOffice printing code to proceed with native
+ // NSPrintOperation which will display the native print panel.
+ if ( !nNameCount )
+ {
+ std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo);
+ pInfo->maPrinterName = getFallbackPrinterName();
+ pInfo->mnStatus = PrintQueueFlags::NONE;
+ pInfo->mnJobs = 0;
+
+ pList->Add( std::move(pInfo) );
+ }
+}
+
+void AquaSalInstance::GetPrinterQueueState( SalPrinterQueueInfo* )
+{
+}
+
+OUString AquaSalInstance::GetDefaultPrinter()
+{
+ // #i113170# may not be the main thread if called from UNO API
+ SalData::ensureThreadAutoreleasePool();
+
+ // WinSalInstance::GetDefaultPrinter() fetches current default printer
+ // on every call so do the same here
+ OUString aDefaultPrinter;
+ {
+ NSPrintInfo* pPI = [NSPrintInfo sharedPrintInfo];
+ SAL_WARN_IF( !pPI, "vcl", "no print info" );
+ if( pPI )
+ {
+ NSPrinter* pPr = [pPI printer];
+ SAL_WARN_IF( !pPr, "vcl", "no printer in default info" );
+ if( pPr )
+ {
+ // Related: tdf#151700 Return the name of the fake printer if
+ // there are no printers so that the LibreOffice printing code
+ // will be able to find the fake printer returned by
+ // AquaSalInstance::GetPrinterQueueInfo()
+ NSString* pDefName = [pPr name];
+ SAL_WARN_IF( !pDefName, "vcl", "printer has no name" );
+ if ( pDefName && [pDefName length])
+ aDefaultPrinter = GetOUString( pDefName );
+ else
+ aDefaultPrinter = getFallbackPrinterName();
+ }
+ }
+ }
+ return aDefaultPrinter;
+}
+
+SalInfoPrinter* AquaSalInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pSetupData )
+{
+ // #i113170# may not be the main thread if called from UNO API
+ SalData::ensureThreadAutoreleasePool();
+
+ SalInfoPrinter* pNewInfoPrinter = nullptr;
+ if( pQueueInfo )
+ {
+ pNewInfoPrinter = new AquaSalInfoPrinter( *pQueueInfo );
+ if( pSetupData )
+ pNewInfoPrinter->SetPrinterData( pSetupData );
+ }
+
+ return pNewInfoPrinter;
+}
+
+void AquaSalInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter )
+{
+ // #i113170# may not be the main thread if called from UNO API
+ SalData::ensureThreadAutoreleasePool();
+
+ delete pPrinter;
+}
+
+OUString AquaSalInstance::GetConnectionIdentifier()
+{
+ return OUString();
+}
+
+// We need to re-encode file urls because osl_getFileURLFromSystemPath converts
+// to UTF-8 before encoding non ascii characters, which is not what other apps expect.
+static OUString translateToExternalUrl(const OUString& internalUrl)
+{
+ uno::Reference< uno::XComponentContext > context(
+ comphelper::getProcessComponentContext());
+ return uri::ExternalUriReferenceTranslator::create(context)->translateToExternal(internalUrl);
+}
+
+// #i104525# many versions of OSX have problems with some URLs:
+// when an app requests OSX to add one of these URLs to the "Recent Items" list
+// then this app gets killed (TextEdit, Preview, etc. and also OOo)
+static bool isDangerousUrl( const OUString& rUrl )
+{
+ // use a heuristic that detects all known cases since there is no official comment
+ // on the exact impact and root cause of the OSX bug
+ const int nLen = rUrl.getLength();
+ const sal_Unicode* p = rUrl.getStr();
+ for( int i = 0; i < nLen-3; ++i, ++p ) {
+ if( p[0] != '%' )
+ continue;
+ // escaped percent?
+ if( (p[1] == '2') && (p[2] == '5') )
+ return true;
+ // escapes are considered to be UTF-8 encoded
+ // => check for invalid UTF-8 leading byte
+ if( (p[1] != 'f') && (p[1] != 'F') )
+ continue;
+ int cLowNibble = p[2];
+ if( (cLowNibble >= '0' ) && (cLowNibble <= '9'))
+ return false;
+ if( cLowNibble >= 'a' )
+ cLowNibble -= 'a' - 'A';
+ if( (cLowNibble < 'A') || (cLowNibble >= 'C'))
+ return true;
+ }
+
+ return false;
+}
+
+void AquaSalInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString& /*rMimeType*/, const OUString& /*rDocumentService*/)
+{
+ // Convert file URL for external use (see above)
+ OUString externalUrl = translateToExternalUrl(rFileUrl);
+ if( externalUrl.isEmpty() )
+ externalUrl = rFileUrl;
+
+ if( !externalUrl.isEmpty() && !isDangerousUrl( externalUrl ) )
+ {
+ NSString* pString = CreateNSString( externalUrl );
+ NSURL* pURL = [NSURL URLWithString: pString];
+
+ if( pURL )
+ {
+ NSDocumentController* pCtrl = [NSDocumentController sharedDocumentController];
+ [pCtrl noteNewRecentDocumentURL: pURL];
+ }
+ if( pString )
+ [pString release];
+ }
+}
+
+SalTimer* AquaSalInstance::CreateSalTimer()
+{
+ return new AquaSalTimer();
+}
+
+SalSystem* AquaSalInstance::CreateSalSystem()
+{
+ return new AquaSalSystem();
+}
+
+std::shared_ptr<SalBitmap> AquaSalInstance::CreateSalBitmap()
+{
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled())
+ return std::make_shared<SkiaSalBitmap>();
+ else
+#endif
+ return std::make_shared<QuartzSalBitmap>();
+}
+
+OUString AquaSalInstance::getOSVersion()
+{
+ NSString * versionString = nullptr;
+ NSDictionary * sysVersionDict = [ NSDictionary dictionaryWithContentsOfFile: @"/System/Library/CoreServices/SystemVersion.plist" ];
+ if ( sysVersionDict )
+ versionString = [ sysVersionDict valueForKey: @"ProductVersion" ];
+
+ OUString aVersion = "macOS ";
+ if ( versionString )
+ aVersion += OUString::fromUtf8( [ versionString UTF8String ] );
+ else
+ aVersion += "(unknown)";
+
+ return aVersion;
+}
+
+CGImageRef CreateCGImage( const Image& rImage )
+{
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled())
+ return SkiaHelper::createCGImage( rImage );
+#endif
+
+ BitmapEx aBmpEx( rImage.GetBitmapEx() );
+ Bitmap aBmp( aBmpEx.GetBitmap() );
+
+ if( aBmp.IsEmpty() || ! aBmp.ImplGetSalBitmap() )
+ return nullptr;
+
+ // simple case, no transparency
+ QuartzSalBitmap* pSalBmp = static_cast<QuartzSalBitmap*>(aBmp.ImplGetSalBitmap().get());
+
+ if( ! pSalBmp )
+ return nullptr;
+
+ CGImageRef xImage = nullptr;
+ if( !aBmpEx.IsAlpha() )
+ xImage = pSalBmp->CreateCroppedImage( 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight );
+ else
+ {
+ AlphaMask aAlphaMask( aBmpEx.GetAlphaMask() );
+ Bitmap aMask( aAlphaMask.GetBitmap() );
+ QuartzSalBitmap* pMaskBmp = static_cast<QuartzSalBitmap*>(aMask.ImplGetSalBitmap().get());
+ if( pMaskBmp )
+ xImage = pSalBmp->CreateWithMask( *pMaskBmp, 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight );
+ else
+ xImage = pSalBmp->CreateCroppedImage( 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight );
+ }
+
+ return xImage;
+}
+
+NSImage* CreateNSImage( const Image& rImage )
+{
+ CGImageRef xImage = CreateCGImage( rImage );
+
+ if( ! xImage )
+ return nil;
+
+ Size aSize( rImage.GetSizePixel() );
+ NSImage* pImage = [[NSImage alloc] initWithSize: NSMakeSize( aSize.Width(), aSize.Height() )];
+ if( pImage )
+ {
+ [pImage lockFocusFlipped:YES];
+ NSGraphicsContext* pContext = [NSGraphicsContext currentContext];
+ CGContextRef rCGContext = [pContext CGContext];
+
+ const CGRect aDstRect = { {0, 0}, { static_cast<CGFloat>(aSize.Width()), static_cast<CGFloat>(aSize.Height()) } };
+ CGContextDrawImage( rCGContext, aDstRect, xImage );
+
+ [pImage unlockFocus];
+ }
+
+ CGImageRelease( xImage );
+
+ return pImage;
+}
+
+bool AquaSalInstance::SVMainHook(int* pnInit)
+{
+ gpnInit = pnInit;
+
+ OUString aExeURL, aExe;
+ osl_getExecutableFile( &aExeURL.pData );
+ osl_getSystemPathFromFileURL( aExeURL.pData, &aExe.pData );
+ OString aByteExe( OUStringToOString( aExe, osl_getThreadTextEncoding() ) );
+
+#ifdef DEBUG
+ aByteExe += OString ( " NSAccessibilityDebugLogLevel 1" );
+ const char* pArgv[] = { aByteExe.getStr(), NULL };
+ NSApplicationMain( 3, pArgv );
+#else
+ const char* pArgv[] = { aByteExe.getStr(), nullptr };
+ NSApplicationMain( 1, pArgv );
+#endif
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salmacos.cxx b/vcl/osx/salmacos.cxx
new file mode 100644
index 0000000000..700b252cf4
--- /dev/null
+++ b/vcl/osx/salmacos.cxx
@@ -0,0 +1,529 @@
+/* -*- 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 .
+ */
+
+// This file contains the macOS-specific versions of the functions which were touched in the commit
+// to fix tdf#138122. The iOS-specific versions of these functions are kept (for now, when this
+// comment is written) as they were before that commit in vcl/ios/salios.cxx.
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <vcl/bitmap.hxx>
+
+#include <quartz/salbmp.h>
+#include <quartz/salgdi.h>
+#include <quartz/salvd.h>
+#include <quartz/utils.h>
+
+#include <osx/saldata.hxx>
+
+// From salbmp.cxx
+
+bool QuartzSalBitmap::Create(CGLayerHolder const & rLayerHolder, int nBitmapBits, int nX, int nY, int nWidth, int nHeight, bool bFlipped)
+{
+
+ // TODO: Bitmaps from scaled layers are reverted to single precision. This is a workaround only unless bitmaps with precision of
+ // source layer are implemented.
+
+ SAL_WARN_IF(!rLayerHolder.isSet(), "vcl", "QuartzSalBitmap::Create() from non-layered context");
+
+ // sanitize input parameters
+ if( nX < 0 ) {
+ nWidth += nX;
+ nX = 0;
+ }
+
+ if( nY < 0 ) {
+ nHeight += nY;
+ nY = 0;
+ }
+
+ CGSize aLayerSize = CGLayerGetSize(rLayerHolder.get());
+ const float fScale = rLayerHolder.getScale();
+ aLayerSize.width /= fScale;
+ aLayerSize.height /= fScale;
+
+ if( nWidth >= static_cast<int>(aLayerSize.width) - nX )
+ nWidth = static_cast<int>(aLayerSize.width) - nX;
+
+ if( nHeight >= static_cast<int>(aLayerSize.height) - nY )
+ nHeight = static_cast<int>(aLayerSize.height) - nY;
+
+ if( (nWidth < 0) || (nHeight < 0) )
+ nWidth = nHeight = 0;
+
+ // initialize properties
+ mnWidth = nWidth;
+ mnHeight = nHeight;
+ mnBits = nBitmapBits ? nBitmapBits : 32;
+
+ // initialize drawing context
+ CreateContext();
+
+ // copy layer content into the bitmap buffer
+ const CGPoint aSrcPoint = { static_cast<CGFloat>(-nX * fScale), static_cast<CGFloat>(-nY * fScale) };
+ if (maGraphicContext.isSet())
+ {
+ if( bFlipped )
+ {
+ CGContextTranslateCTM(maGraphicContext.get(), 0, +mnHeight);
+ CGContextScaleCTM(maGraphicContext.get(), +1, -1);
+ }
+ maGraphicContext.saveState();
+ CGContextScaleCTM(maGraphicContext.get(), 1 / fScale, 1 / fScale);
+ CGContextDrawLayerAtPoint(maGraphicContext.get(), aSrcPoint, rLayerHolder.get());
+ maGraphicContext.restoreState();
+ }
+ return true;
+}
+
+// From salgdicommon.cxx
+
+void AquaGraphicsBackend::copyBits(const SalTwoRect &rPosAry, SalGraphics *pSrcGraphics)
+{
+ AquaSharedAttributes* pSrcShared = nullptr;
+
+ if (pSrcGraphics)
+ {
+ AquaSalGraphics* pSrc = static_cast<AquaSalGraphics*>(pSrcGraphics);
+ pSrcShared = &pSrc->getAquaGraphicsBackend()->GetShared();
+ }
+ else
+ pSrcShared = &mrShared;
+
+ if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0 || rPosAry.mnDestHeight <= 0)
+ return;
+ if (!mrShared.maContextHolder.isSet())
+ return;
+
+ SAL_WARN_IF (!pSrcShared->maLayer.isSet(), "vcl.quartz", "AquaSalGraphics::copyBits() from non-layered graphics this=" << this);
+
+ // Layered graphics are copied by AquaSalGraphics::copyScaledArea() which is able to consider the layer's scaling.
+
+ if (pSrcShared->maLayer.isSet())
+ copyScaledArea(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnSrcX, rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, pSrcShared);
+ else
+ {
+ mrShared.applyXorContext();
+ pSrcShared->applyXorContext();
+ std::shared_ptr<SalBitmap> pBitmap = pSrcGraphics->GetImpl()->getBitmap(rPosAry.mnSrcX, rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+ if (pBitmap)
+ {
+ SalTwoRect aPosAry(rPosAry);
+ aPosAry.mnSrcX = 0;
+ aPosAry.mnSrcY = 0;
+ drawBitmap(aPosAry, *pBitmap);
+ }
+ }
+}
+
+void AquaGraphicsBackend::copyArea(tools::Long nDstX, tools::Long nDstY,tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight, bool)
+{
+ if (!mrShared.maContextHolder.isSet())
+ return;
+
+ // Functionality is implemented in protected member function AquaSalGraphics::copyScaledArea() which requires an additional
+ // parameter of type SalGraphics to be used in AquaSalGraphics::copyBits() too.
+
+ copyScaledArea(nDstX, nDstY, nSrcX, nSrcY, nSrcWidth, nSrcHeight, &mrShared);
+}
+
+void AquaGraphicsBackend::copyScaledArea(tools::Long nDstX, tools::Long nDstY,tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight, AquaSharedAttributes* pSrcShared)
+{
+ SAL_WARN_IF(!mrShared.maLayer.isSet(), "vcl.quartz",
+ "AquaSalGraphics::copyScaledArea() without graphics context or for non-layered graphics this=" << this);
+
+ if (!mrShared.maContextHolder.isSet() || !mrShared.maLayer.isSet())
+ return;
+
+ // Determine scaled geometry of source and target area assuming source and target area have the same scale
+
+ float fScale = mrShared.maLayer.getScale();
+ CGFloat nScaledSourceX = nSrcX * fScale;
+ CGFloat nScaledSourceY = nSrcY * fScale;
+ CGFloat nScaledTargetX = nDstX * fScale;
+ CGFloat nScaledTargetY = nDstY * fScale;
+ CGFloat nScaledSourceWidth = nSrcWidth * fScale;
+ CGFloat nScaledSourceHeight = nSrcHeight * fScale;
+
+ // Apply XOR context and get copy context from current graphics context or XOR context
+
+ mrShared.applyXorContext();
+ mrShared.maContextHolder.saveState();
+ CGContextRef xCopyContext = mrShared.maContextHolder.get();
+ if (mrShared.mpXorEmulation && mrShared.mpXorEmulation->IsEnabled())
+ xCopyContext = mrShared.mpXorEmulation->GetTargetContext();
+
+ // Set scale matrix of copy context to consider layer scaling
+
+ CGContextScaleCTM(xCopyContext, 1 / fScale, 1 / fScale);
+
+ // Creating an additional layer is required for drawing with the required scale and extent at the drawing destination
+ // thereafter.
+
+ CGLayerHolder aSourceLayerHolder(pSrcShared->maLayer);
+ const CGSize aSourceSize = CGSizeMake(nScaledSourceWidth, nScaledSourceHeight);
+ aSourceLayerHolder.set(CGLayerCreateWithContext(xCopyContext, aSourceSize, nullptr));
+ const CGContextRef xSourceContext = CGLayerGetContext(aSourceLayerHolder.get());
+ CGPoint aSrcPoint = CGPointMake(-nScaledSourceX, -nScaledSourceY);
+ if (pSrcShared->isFlipped())
+ {
+ CGContextTranslateCTM(xSourceContext, 0, nScaledSourceHeight);
+ CGContextScaleCTM(xSourceContext, 1, -1);
+ aSrcPoint.y = nScaledSourceY + nScaledSourceHeight - mrShared.mnHeight * fScale;
+ }
+ CGContextSetBlendMode(xSourceContext, kCGBlendModeCopy);
+ CGContextDrawLayerAtPoint(xSourceContext, aSrcPoint, pSrcShared->maLayer.get());
+
+ // Copy source area from additional layer to target area
+
+ const CGRect aTargetRect = CGRectMake(nScaledTargetX, nScaledTargetY, nScaledSourceWidth, nScaledSourceHeight);
+ CGContextSetBlendMode(xCopyContext, kCGBlendModeCopy);
+ CGContextDrawLayerInRect(xCopyContext, aTargetRect, aSourceLayerHolder.get());
+
+ // Housekeeping on exit
+
+ mrShared.maContextHolder.restoreState();
+ if (aSourceLayerHolder.get() != mrShared.maLayer.get())
+ CGLayerRelease(aSourceLayerHolder.get());
+
+ mrShared.refreshRect(nDstX, nDstY, nSrcWidth, nSrcHeight);
+}
+
+void AquaSalGraphics::SetVirDevGraphics(SalVirtualDevice* pVirDev, CGLayerHolder const &rLayer, CGContextRef xContext, int nBitmapDepth)
+{
+ SAL_INFO("vcl.quartz", "SetVirDevGraphics() this=" << this << " layer=" << rLayer.get() << " context=" << xContext);
+
+ // Set member variables
+
+ InvalidateContext();
+ maShared.mbWindow = false;
+ maShared.mbPrinter = false;
+ maShared.mbVirDev = true;
+ maShared.maLayer = rLayer;
+ maShared.mnBitmapDepth = nBitmapDepth;
+
+ mpBackend->UpdateGeometryProvider(pVirDev);
+
+ // Get size and scale from layer if set else from bitmap and sal::aqua::getWindowScaling(), which is used to determine
+ // scaling for direct graphics output too
+
+ CGSize aSize;
+ float fScale;
+ if (maShared.maLayer.isSet())
+ {
+ maShared.maContextHolder.set(CGLayerGetContext(maShared.maLayer.get()));
+ aSize = CGLayerGetSize(maShared.maLayer.get());
+ fScale = maShared.maLayer.getScale();
+ }
+ else
+ {
+ maShared.maContextHolder.set(xContext);
+ if (!xContext)
+ return;
+ aSize.width = CGBitmapContextGetWidth(xContext);
+ aSize.height = CGBitmapContextGetHeight(xContext);
+ fScale = sal::aqua::getWindowScaling();
+ }
+ maShared.mnWidth = aSize.width / fScale;
+ maShared.mnHeight = aSize.height / fScale;
+
+ // Set color space for fill and stroke
+
+ CGColorSpaceRef aColorSpace = GetSalData()->mxRGBSpace;
+ CGContextSetFillColorSpace(maShared.maContextHolder.get(), aColorSpace);
+ CGContextSetStrokeColorSpace(maShared.maContextHolder.get(), aColorSpace);
+
+ // Apply scale matrix to virtual device graphics
+
+ CGContextScaleCTM(maShared.maContextHolder.get(), fScale, fScale);
+
+ // Apply XOR emulation if required
+
+ if (maShared.mpXorEmulation)
+ {
+ maShared.mpXorEmulation->SetTarget(maShared.mnWidth, maShared.mnHeight, maShared.mnBitmapDepth, maShared.maContextHolder.get(), maShared.maLayer.get());
+ if (maShared.mpXorEmulation->IsEnabled())
+ maShared.maContextHolder.set(maShared.mpXorEmulation->GetMaskContext());
+ }
+
+ // Housekeeping on exit
+
+ maShared.maContextHolder.saveState();
+ maShared.setState();
+
+ SAL_INFO("vcl.quartz", "SetVirDevGraphics() this=" << this <<
+ " (" << maShared.mnWidth << "x" << maShared.mnHeight << ") fScale=" << fScale << " mnBitmapDepth=" << maShared.mnBitmapDepth);
+}
+
+void XorEmulation::SetTarget(int nWidth, int nHeight, int nTargetDepth, CGContextRef xTargetContext, CGLayerRef xTargetLayer)
+{
+ SAL_INFO("vcl.quartz", "XorEmulation::SetTarget() this=" << this <<
+ " (" << nWidth << "x" << nHeight << ") depth=" << nTargetDepth <<
+ " context=" << xTargetContext << " layer=" << xTargetLayer);
+
+ // Prepare to replace old mask and temporary context
+
+ if (m_xMaskContext)
+ {
+ CGContextRelease(m_xMaskContext);
+ delete[] m_pMaskBuffer;
+ m_xMaskContext = nullptr;
+ m_pMaskBuffer = nullptr;
+ if (m_xTempContext)
+ {
+ CGContextRelease(m_xTempContext);
+ delete[] m_pTempBuffer;
+ m_xTempContext = nullptr;
+ m_pTempBuffer = nullptr;
+ }
+ }
+
+ // Return early if there is nothing more to do
+
+ if (!xTargetContext)
+ return;
+
+ // Retarget drawing operations to the XOR mask
+
+ m_xTargetLayer = xTargetLayer;
+ m_xTargetContext = xTargetContext;
+
+ // Prepare creation of matching bitmaps
+
+ CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
+ CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst;
+ int nBitDepth = nTargetDepth;
+ if (!nBitDepth)
+ nBitDepth = 32;
+ int nBytesPerRow = 4;
+ const size_t nBitsPerComponent = (nBitDepth == 16) ? 5 : 8;
+ if (nBitDepth <= 8)
+ {
+ aCGColorSpace = GetSalData()->mxGraySpace;
+ aCGBmpInfo = kCGImageAlphaNone;
+ nBytesPerRow = 1;
+ }
+ float fScale = sal::aqua::getWindowScaling();
+ size_t nScaledWidth = nWidth * fScale;
+ size_t nScaledHeight = nHeight * fScale;
+ nBytesPerRow *= nScaledWidth;
+ m_nBufferLongs = (nScaledHeight * nBytesPerRow + sizeof(sal_uLong) - 1) / sizeof(sal_uLong);
+
+ // Create XOR mask context
+
+ m_pMaskBuffer = new sal_uLong[m_nBufferLongs];
+ m_xMaskContext = CGBitmapContextCreate(m_pMaskBuffer, nScaledWidth, nScaledHeight,
+ nBitsPerComponent, nBytesPerRow, aCGColorSpace, aCGBmpInfo);
+ SAL_WARN_IF(!m_xMaskContext, "vcl.quartz", "mask context creation failed");
+
+ // Reset XOR mask to black
+
+ memset(m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong));
+
+ // Create bitmap context for manual XOR unless target context is a bitmap context
+
+ if (nTargetDepth)
+ m_pTempBuffer = static_cast<sal_uLong*>(CGBitmapContextGetData(m_xTargetContext));
+ if (!m_pTempBuffer)
+ {
+ m_pTempBuffer = new sal_uLong[m_nBufferLongs];
+ m_xTempContext = CGBitmapContextCreate(m_pTempBuffer, nScaledWidth, nScaledHeight,
+ nBitsPerComponent, nBytesPerRow, aCGColorSpace, aCGBmpInfo);
+ SAL_WARN_IF(!m_xTempContext, "vcl.quartz", "temp context creation failed");
+ }
+
+ // Initialize XOR mask context for drawing
+
+ CGContextSetFillColorSpace(m_xMaskContext, aCGColorSpace);
+ CGContextSetStrokeColorSpace(m_xMaskContext, aCGColorSpace);
+ CGContextSetShouldAntialias(m_xMaskContext, false);
+
+ // Improve XOR emulation for monochrome contexts
+
+ if (aCGColorSpace == GetSalData()->mxGraySpace)
+ CGContextSetBlendMode(m_xMaskContext, kCGBlendModeDifference);
+
+ // Initialize XOR mask transformation matrix and apply scale matrix to consider layer scaling
+
+ const CGAffineTransform aCTM = CGContextGetCTM(xTargetContext);
+ CGContextConcatCTM(m_xMaskContext, aCTM);
+ if (m_xTempContext)
+ {
+ CGContextConcatCTM( m_xTempContext, aCTM );
+ CGContextScaleCTM(m_xTempContext, 1 / fScale, 1 / fScale);
+ }
+ CGContextSaveGState(m_xMaskContext);
+}
+
+bool XorEmulation::UpdateTarget()
+{
+ SAL_INFO("vcl.quartz", "XorEmulation::UpdateTarget() this=" << this);
+
+ if (!IsEnabled())
+ return false;
+
+ // Update temporary bitmap buffer
+
+ if (m_xTempContext)
+ {
+ SAL_WARN_IF(m_xTargetContext == nullptr, "vcl.quartz", "Target layer is NULL");
+ CGContextDrawLayerAtPoint(m_xTempContext, CGPointZero, m_xTargetLayer);
+ }
+
+ // XOR using XOR mask (sufficient for simple color manipulations as well as for complex XOR clipping used in metafiles)
+
+ const sal_uLong *pSrc = m_pMaskBuffer;
+ sal_uLong *pDst = m_pTempBuffer;
+ for (int i = m_nBufferLongs; --i >= 0;)
+ *(pDst++) ^= *(pSrc++);
+
+ // Write back XOR results to target context
+
+ if (m_xTempContext)
+ {
+ CGImageRef xXorImage = CGBitmapContextCreateImage(m_xTempContext);
+ size_t nWidth = CGImageGetWidth(xXorImage);
+ size_t nHeight = CGImageGetHeight(xXorImage);
+
+ // Set scale matrix of target context to consider layer scaling and update target context
+ // TODO: Update minimal change rectangle
+
+ const CGRect aFullRect = CGRectMake(0, 0, nWidth, nHeight);
+ CGContextSaveGState(m_xTargetContext);
+ float fScale = sal::aqua::getWindowScaling();
+ CGContextScaleCTM(m_xTargetContext, 1 / fScale, 1 / fScale);
+ CGContextDrawImage(m_xTargetContext, aFullRect, xXorImage);
+ CGContextRestoreGState(m_xTargetContext);
+ CGImageRelease(xXorImage);
+ }
+
+ // Reset XOR mask to black again
+ // TODO: Not needed for last update
+
+ memset(m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong));
+
+ // TODO: Return FALSE if target was not changed
+
+ return true;
+}
+
+// From salvd.cxx
+
+void AquaSalVirtualDevice::Destroy()
+{
+ SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::Destroy() this=" << this << " mbForeignContext=" << mbForeignContext );
+
+ if (mbForeignContext)
+ {
+ // Do not delete mxContext that we have received from outside VCL
+ maLayer.set(nullptr);
+ return;
+ }
+
+ if (maLayer.isSet())
+ {
+ if( mpGraphics )
+ {
+ mpGraphics->SetVirDevGraphics(this, nullptr, nullptr);
+ }
+ CGLayerRelease(maLayer.get());
+ maLayer.set(nullptr);
+ }
+
+ if (maBitmapContext.isSet())
+ {
+ CGContextRelease(maBitmapContext.get());
+ maBitmapContext.set(nullptr);
+ }
+}
+
+bool AquaSalVirtualDevice::SetSize(tools::Long nDX, tools::Long nDY)
+{
+ SAL_INFO("vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
+ " (" << nDX << "x" << nDY << ") mbForeignContext=" << (mbForeignContext ? "YES" : "NO"));
+
+ // Do not delete/resize graphics context if it has been received from outside VCL
+
+ if (mbForeignContext)
+ return true;
+
+ // Do not delete/resize graphics context if no change of geometry has been requested
+
+ float fScale;
+ if (maLayer.isSet())
+ {
+ fScale = maLayer.getScale();
+ const CGSize aSize = CGLayerGetSize(maLayer.get());
+ if ((nDX == aSize.width / fScale) && (nDY == aSize.height / fScale))
+ return true;
+ }
+
+ // Destroy graphics context if change of geometry has been requested
+
+ Destroy();
+
+ // Prepare new graphics context for initialization, use scaling independent of prior graphics context calculated by
+ // sal::aqua::getWindowScaling(), which is used to determine scaling for direct graphics output too
+
+ mnWidth = nDX;
+ mnHeight = nDY;
+ fScale = sal::aqua::getWindowScaling();
+ CGColorSpaceRef aColorSpace;
+ uint32_t nFlags;
+ if (mnBitmapDepth && (mnBitmapDepth < 16))
+ {
+ mnBitmapDepth = 8;
+ aColorSpace = GetSalData()->mxGraySpace;
+ nFlags = kCGImageAlphaNone;
+ }
+ else
+ {
+ mnBitmapDepth = 32;
+ aColorSpace = GetSalData()->mxRGBSpace;
+
+ nFlags = uint32_t(kCGImageAlphaNoneSkipFirst) | uint32_t(kCGBitmapByteOrder32Host);
+ }
+
+ // Allocate buffer for virtual device graphics as bitmap context to store graphics with highest required (scaled) resolution
+
+ size_t nScaledWidth = mnWidth * fScale;
+ size_t nScaledHeight = mnHeight * fScale;
+ size_t nBytesPerRow = mnBitmapDepth * nScaledWidth / 8;
+ maBitmapContext.set(CGBitmapContextCreate(nullptr, nScaledWidth, nScaledHeight, 8, nBytesPerRow, aColorSpace, nFlags));
+
+ SAL_INFO("vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
+ " fScale=" << fScale << " mnBitmapDepth=" << mnBitmapDepth);
+
+ CGSize aLayerSize = { static_cast<CGFloat>(nScaledWidth), static_cast<CGFloat>(nScaledHeight) };
+ maLayer.set(CGLayerCreateWithContext(maBitmapContext.get(), aLayerSize, nullptr));
+ maLayer.setScale(fScale);
+ mpGraphics->SetVirDevGraphics(this, maLayer, CGLayerGetContext(maLayer.get()), mnBitmapDepth);
+
+ SAL_WARN_IF(!maBitmapContext.isSet(), "vcl.quartz", "No context");
+
+ return maLayer.isSet();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salmenu.cxx b/vcl/osx/salmenu.cxx
new file mode 100644
index 0000000000..b3d02587f4
--- /dev/null
+++ b/vcl/osx/salmenu.cxx
@@ -0,0 +1,888 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <objc/objc-runtime.h>
+
+#include <rtl/ustrbuf.hxx>
+#include <tools/debug.hxx>
+#include <tools/long.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/window.hxx>
+#include <vcl/svapp.hxx>
+
+#include <osx/runinmain.hxx>
+#include <osx/saldata.hxx>
+#include <osx/salinst.h>
+#include <osx/salmenu.h>
+#include <osx/salnsmenu.h>
+#include <osx/salframe.h>
+#include <osx/a11ywrapper.h>
+#include <quartz/utils.h>
+#include <strings.hrc>
+#include <window.h>
+#include <vcl/mnemonic.hxx>
+
+namespace {
+
+void releaseButtonEntry( AquaSalMenu::MenuBarButtonEntry& i_rEntry )
+{
+ if( i_rEntry.mpNSImage )
+ {
+ [i_rEntry.mpNSImage release];
+ i_rEntry.mpNSImage = nil;
+ }
+ if( i_rEntry.mpToolTipString )
+ {
+ [i_rEntry.mpToolTipString release];
+ i_rEntry.mpToolTipString = nil;
+ }
+}
+
+}
+
+const AquaSalMenu* AquaSalMenu::pCurrentMenuBar = nullptr;
+
+@interface MainMenuSelector : NSObject
+{
+}
+-(void)showDialog: (ShowDialogId)nDialog;
+-(void)showPreferences: (id)sender;
+-(void)showAbout: (id)sender;
+@end
+
+@implementation MainMenuSelector
+-(void)showDialog: (ShowDialogId)nDialog
+{
+ if( AquaSalMenu::pCurrentMenuBar )
+ {
+ const AquaSalFrame* pFrame = AquaSalMenu::pCurrentMenuBar->mpFrame;
+ if( pFrame && AquaSalFrame::isAlive( pFrame ) )
+ {
+ pFrame->CallCallback( SalEvent::ShowDialog, reinterpret_cast<void*>(nDialog) );
+ }
+ }
+ else
+ {
+ OUString aDialog;
+ if( nDialog == ShowDialogId::About )
+ aDialog = "ABOUT";
+ else if( nDialog == ShowDialogId::Preferences )
+ aDialog = "PREFERENCES";
+ const ApplicationEvent* pAppEvent = new ApplicationEvent(
+ ApplicationEvent::Type::ShowDialog, aDialog);
+ AquaSalInstance::aAppEventList.push_back( pAppEvent );
+ }
+}
+
+-(void)showPreferences: (id) sender
+{
+ (void)sender;
+ SolarMutexGuard aGuard;
+
+ [self showDialog: ShowDialogId::Preferences];
+}
+-(void)showAbout: (id) sender
+{
+ (void)sender;
+ SolarMutexGuard aGuard;
+
+ [self showDialog: ShowDialogId::About];
+}
+@end
+
+// FIXME: currently this is leaked
+static MainMenuSelector* pMainMenuSelector = nil;
+
+static void initAppMenu()
+{
+ static bool bInitialized = false;
+ if (bInitialized)
+ return;
+ OSX_SALDATA_RUNINMAIN(initAppMenu())
+ bInitialized = true;
+
+ NSMenu* pAppMenu = nil;
+ NSMenuItem* pNewItem = nil;
+
+ NSMenu* pMainMenu = [[[NSMenu alloc] initWithTitle: @"Main Menu"] autorelease];
+ pNewItem = [pMainMenu addItemWithTitle: @"Application"
+ action: nil
+ keyEquivalent: @""];
+ pAppMenu = [[[NSMenu alloc] initWithTitle: @"Application"] autorelease];
+ [pNewItem setSubmenu: pAppMenu];
+ [NSApp setMainMenu: pMainMenu];
+
+ pMainMenuSelector = [[MainMenuSelector alloc] init];
+
+ // about
+ NSString* pString = CreateNSString(VclResId(SV_STDTEXT_ABOUT));
+ pNewItem = [pAppMenu addItemWithTitle: pString
+ action: @selector(showAbout:)
+ keyEquivalent: @""];
+ [pString release];
+ [pNewItem setTarget: pMainMenuSelector];
+
+ [pAppMenu addItem:[NSMenuItem separatorItem]];
+
+ // preferences
+ pString = CreateNSString(VclResId(SV_STDTEXT_PREFERENCES));
+ pNewItem = [pAppMenu addItemWithTitle: pString
+ action: @selector(showPreferences:)
+ keyEquivalent: @","];
+ [pString release];
+ [pNewItem setKeyEquivalentModifierMask: NSEventModifierFlagCommand];
+ [pNewItem setTarget: pMainMenuSelector];
+
+ [pAppMenu addItem:[NSMenuItem separatorItem]];
+
+ // Services item and menu
+ pString = CreateNSString(VclResId(SV_MENU_MAC_SERVICES));
+ pNewItem = [pAppMenu addItemWithTitle: pString
+ action: nil
+ keyEquivalent: @""];
+ NSMenu *servicesMenu = [[[NSMenu alloc] initWithTitle:@"Services"] autorelease];
+ [pNewItem setSubmenu: servicesMenu];
+ [NSApp setServicesMenu: servicesMenu];
+
+ [pAppMenu addItem:[NSMenuItem separatorItem]];
+
+ // Hide Application
+ pString = CreateNSString(VclResId(SV_MENU_MAC_HIDEAPP));
+ [pAppMenu addItemWithTitle: pString
+ action:@selector(hide:)
+ keyEquivalent:@"h"];
+ [pString release];
+
+ // Hide Others
+ pString = CreateNSString(VclResId(SV_MENU_MAC_HIDEALL));
+ [pAppMenu addItemWithTitle: pString
+ action:@selector(hideOtherApplications:)
+ keyEquivalent:@"h"];
+ [pString release];
+ [pNewItem setKeyEquivalentModifierMask: NSEventModifierFlagCommand | NSEventModifierFlagOption];
+
+ // Show All
+ pString = CreateNSString(VclResId(SV_MENU_MAC_SHOWALL));
+ [pAppMenu addItemWithTitle: pString
+ action:@selector(unhideAllApplications:)
+ keyEquivalent:@""];
+ [pString release];
+
+ [pAppMenu addItem:[NSMenuItem separatorItem]];
+
+ // Quit
+ pString = CreateNSString(VclResId(SV_MENU_MAC_QUITAPP));
+ [pAppMenu addItemWithTitle: pString
+ action:@selector(terminate:)
+ keyEquivalent:@"q"];
+ [pString release];
+}
+
+std::unique_ptr<SalMenu> AquaSalInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
+{
+ initAppMenu();
+
+ AquaSalMenu *pAquaSalMenu = new AquaSalMenu( bMenuBar );
+ pAquaSalMenu->mpVCLMenu = pVCLMenu;
+
+ return std::unique_ptr<SalMenu>(pAquaSalMenu);
+}
+
+std::unique_ptr<SalMenuItem> AquaSalInstance::CreateMenuItem( const SalItemParams & rItemData )
+{
+ AquaSalMenuItem *pSalMenuItem = new AquaSalMenuItem( &rItemData );
+
+ return std::unique_ptr<SalMenuItem>(pSalMenuItem);
+}
+
+/*
+ * AquaSalMenu
+ */
+
+AquaSalMenu::AquaSalMenu( bool bMenuBar ) :
+ mbMenuBar( bMenuBar ),
+ mpMenu( nil ),
+ mpFrame( nullptr ),
+ mpParentSalMenu( nullptr )
+{
+ if( ! mbMenuBar )
+ {
+ mpMenu = [[SalNSMenu alloc] initWithMenu: this];
+ [mpMenu setDelegate: reinterpret_cast< id<NSMenuDelegate> >(mpMenu)];
+ }
+ else
+ {
+ mpMenu = [NSApp mainMenu];
+ }
+ [mpMenu setAutoenablesItems: NO];
+}
+
+AquaSalMenu::~AquaSalMenu()
+{
+ // actually someone should have done AquaSalFrame::SetMenu( NULL )
+ // on our frame, alas it is not so
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) && mpFrame->mpMenu == this )
+ const_cast<AquaSalFrame*>(mpFrame)->mpMenu = nullptr;
+
+ // this should normally be empty already, but be careful...
+ for( size_t i = 0; i < maButtons.size(); i++ )
+ releaseButtonEntry( maButtons[i] );
+ maButtons.clear();
+
+ // is this leaking in some cases ? the release often leads to a duplicate release
+ // it seems the parent item gets ownership of the menu
+ if( mpMenu )
+ {
+ if( mbMenuBar )
+ {
+ if( pCurrentMenuBar == this )
+ {
+ // if the current menubar gets destroyed, set the default menubar
+ setDefaultMenu();
+ }
+ }
+ else
+ // the system may still hold a reference on mpMenu
+ {
+ // so set the pointer to this AquaSalMenu to NULL
+ // to protect from calling a dead object
+
+ // in ! mbMenuBar case our mpMenu is actually a SalNSMenu*
+ // so we can safely cast here
+ [static_cast<SalNSMenu*>(mpMenu) setSalMenu: nullptr];
+ /* #i89860# FIXME:
+ using [autorelease] here (and in AquaSalMenuItem::~AquaSalMenuItem)
+ instead of [release] fixes an occasional crash. That should
+ indicate that we release menus / menu items in the wrong order
+ somewhere, but I could not find that case.
+ */
+ [mpMenu autorelease];
+ }
+ }
+}
+
+bool AquaSalMenu::ShowNativePopupMenu(FloatingWindow * pWin, const tools::Rectangle& rRect, FloatWinPopupFlags nFlags)
+{
+ // set offsets for positioning
+ const float offset = 9.0;
+
+ // get the pointers
+ AquaSalFrame * pParentAquaSalFrame = static_cast<AquaSalFrame *>(pWin->ImplGetWindowImpl()->mpRealParent->ImplGetFrame());
+ NSWindow* pParentNSWindow = pParentAquaSalFrame->mpNSWindow;
+ NSView* pParentNSView = [pParentNSWindow contentView];
+ NSView* pPopupNSView = static_cast<AquaSalFrame *>(pWin->ImplGetWindow()->ImplGetFrame())->mpNSView;
+ NSRect popupFrame = [pPopupNSView frame];
+
+ // create frame rect
+ NSRect displayPopupFrame = NSMakeRect( rRect.Left()+(offset-1), rRect.Top()+(offset+1), popupFrame.size.width, 0 );
+ pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false);
+
+ // do the same strange semantics as vcl popup windows to arrive at a frame geometry
+ // in mirrored UI case; best done by actually executing the same code
+ sal_uInt16 nArrangeIndex;
+ pWin->SetPosPixel( FloatingWindow::ImplCalcPos( pWin, rRect, nFlags, nArrangeIndex ) );
+ displayPopupFrame.origin.x = pWin->ImplGetFrame()->maGeometry.x() - pParentAquaSalFrame->maGeometry.x() + offset;
+ displayPopupFrame.origin.y = pWin->ImplGetFrame()->maGeometry.y() - pParentAquaSalFrame->maGeometry.y() + offset;
+ pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false);
+
+ // #i111992# if this menu was opened due to a key event, prevent dispatching that yet again
+ if( [pParentNSView respondsToSelector: @selector(clearLastEvent)] )
+ [pParentNSView performSelector:@selector(clearLastEvent)];
+
+ // open popup menu
+ NSPopUpButtonCell * pPopUpButtonCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
+ [pPopUpButtonCell setMenu: mpMenu];
+ [pPopUpButtonCell selectItem:nil];
+ [AquaA11yWrapper setPopupMenuOpen: YES];
+ [pPopUpButtonCell performClickWithFrame:displayPopupFrame inView:pParentNSView];
+ [pPopUpButtonCell release];
+ [AquaA11yWrapper setPopupMenuOpen: NO];
+
+ return true;
+}
+
+int AquaSalMenu::getItemIndexByPos( sal_uInt16 nPos ) const
+{
+ int nIndex = 0;
+ if( nPos == MENU_APPEND )
+ nIndex = [mpMenu numberOfItems];
+ else
+ nIndex = sal::static_int_cast<int>( mbMenuBar ? nPos+1 : nPos );
+ return nIndex;
+}
+
+const AquaSalFrame* AquaSalMenu::getFrame() const
+{
+ const AquaSalMenu* pMenu = this;
+ while( pMenu && ! pMenu->mpFrame )
+ pMenu = pMenu->mpParentSalMenu;
+ return pMenu ? pMenu->mpFrame : nullptr;
+}
+
+void AquaSalMenu::unsetMainMenu()
+{
+ pCurrentMenuBar = nullptr;
+
+ // remove items from main menu
+ NSMenu* pMenu = [NSApp mainMenu];
+ for( int nItems = [pMenu numberOfItems]; nItems > 1; nItems-- )
+ [pMenu removeItemAtIndex: 1];
+}
+
+void AquaSalMenu::setMainMenu()
+{
+ SAL_WARN_IF( !mbMenuBar, "vcl", "setMainMenu on non menubar" );
+ if( mbMenuBar )
+ {
+ if( pCurrentMenuBar != this )
+ {
+ unsetMainMenu();
+ // insert our items
+ for( std::vector<AquaSalMenuItem *>::size_type i = 0; i < maItems.size(); i++ )
+ {
+ NSMenuItem* pItem = maItems[i]->mpMenuItem;
+ [mpMenu insertItem: pItem atIndex: i+1];
+ }
+ pCurrentMenuBar = this;
+
+ // change status item
+ statusLayout();
+ }
+ enableMainMenu( true );
+ }
+}
+
+void AquaSalMenu::setDefaultMenu()
+{
+ NSMenu* pMenu = [NSApp mainMenu];
+
+ unsetMainMenu();
+
+ // insert default items
+ std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
+ for( unsigned int i = 0, nAddItems = rFallbackMenu.size(); i < nAddItems; i++ )
+ {
+ NSMenuItem* pItem = rFallbackMenu[i];
+ if( [pItem menu] == nil )
+ [pMenu insertItem: pItem atIndex: i+1];
+ }
+}
+
+void AquaSalMenu::enableMainMenu( bool bEnable )
+{
+ NSMenu* pMainMenu = [NSApp mainMenu];
+ if( pMainMenu )
+ {
+ // enable/disable items from main menu
+ int nItems = [pMainMenu numberOfItems];
+ for( int n = 1; n < nItems; n++ )
+ {
+ NSMenuItem* pItem = [pMainMenu itemAtIndex: n];
+ [pItem setEnabled: bEnable ? YES : NO];
+ }
+ }
+}
+
+void AquaSalMenu::addFallbackMenuItem( NSMenuItem* pNewItem )
+{
+ initAppMenu();
+
+ std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
+
+ // prevent duplicate insertion
+ int nItems = rFallbackMenu.size();
+ for( int i = 0; i < nItems; i++ )
+ {
+ if( rFallbackMenu[i] == pNewItem )
+ return;
+ }
+
+ // push the item to the back and retain it
+ [pNewItem retain];
+ rFallbackMenu.push_back( pNewItem );
+
+ if( pCurrentMenuBar == nullptr )
+ setDefaultMenu();
+}
+
+void AquaSalMenu::removeFallbackMenuItem( NSMenuItem* pOldItem )
+{
+ std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
+
+ // find item
+ unsigned int nItems = rFallbackMenu.size();
+ for( unsigned int i = 0; i < nItems; i++ )
+ {
+ if( rFallbackMenu[i] == pOldItem )
+ {
+ // remove item and release
+ rFallbackMenu.erase( rFallbackMenu.begin() + i );
+ [pOldItem release];
+
+ if( pCurrentMenuBar == nullptr )
+ setDefaultMenu();
+
+ return;
+ }
+ }
+}
+
+bool AquaSalMenu::VisibleMenuBar()
+{
+ return true;
+}
+
+void AquaSalMenu::SetFrame( const SalFrame *pFrame )
+{
+ mpFrame = static_cast<const AquaSalFrame*>(pFrame);
+}
+
+void AquaSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos )
+{
+ OSX_SALDATA_RUNINMAIN(InsertItem(pSalMenuItem, nPos))
+
+ AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem);
+
+ pAquaSalMenuItem->mpParentMenu = this;
+ DBG_ASSERT( pAquaSalMenuItem->mpVCLMenu == nullptr ||
+ pAquaSalMenuItem->mpVCLMenu == mpVCLMenu ||
+ mpVCLMenu == nullptr,
+ "resetting menu ?" );
+ if( pAquaSalMenuItem->mpVCLMenu )
+ mpVCLMenu = pAquaSalMenuItem->mpVCLMenu;
+
+ if( nPos == MENU_APPEND || nPos == maItems.size() )
+ maItems.push_back( pAquaSalMenuItem );
+ else if( nPos < maItems.size() )
+ maItems.insert( maItems.begin() + nPos, pAquaSalMenuItem );
+ else
+ {
+ OSL_FAIL( "invalid item index in insert" );
+ return;
+ }
+
+ if( ! mbMenuBar || pCurrentMenuBar == this )
+ [mpMenu insertItem: pAquaSalMenuItem->mpMenuItem atIndex: getItemIndexByPos(nPos)];
+}
+
+void AquaSalMenu::RemoveItem( unsigned nPos )
+{
+ AquaSalMenuItem* pRemoveItem = nullptr;
+ if( nPos == MENU_APPEND || nPos == (maItems.size()-1) )
+ {
+ pRemoveItem = maItems.back();
+ maItems.pop_back();
+ }
+ else if( nPos < maItems.size() )
+ {
+ pRemoveItem = maItems[ nPos ];
+ maItems.erase( maItems.begin()+nPos );
+ }
+ else
+ {
+ OSL_FAIL( "invalid item index in remove" );
+ return;
+ }
+
+ pRemoveItem->mpParentMenu = nullptr;
+
+ if( ! mbMenuBar || pCurrentMenuBar == this )
+ [mpMenu removeItemAtIndex: getItemIndexByPos(nPos)];
+}
+
+void AquaSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned /*nPos*/ )
+{
+ AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem);
+ AquaSalMenu *subAquaSalMenu = static_cast<AquaSalMenu*>(pSubMenu);
+
+ if (subAquaSalMenu)
+ {
+ pAquaSalMenuItem->mpSubMenu = subAquaSalMenu;
+ if( subAquaSalMenu->mpParentSalMenu == nullptr )
+ {
+ subAquaSalMenu->mpParentSalMenu = this;
+ [pAquaSalMenuItem->mpMenuItem setSubmenu: subAquaSalMenu->mpMenu];
+
+ // set title of submenu
+ [subAquaSalMenu->mpMenu setTitle: [pAquaSalMenuItem->mpMenuItem title]];
+ }
+ else if( subAquaSalMenu->mpParentSalMenu != this )
+ {
+ // cocoa doesn't allow menus to be submenus of multiple
+ // menu items, so place a copy in the menu item instead ?
+ // let's hope that NSMenu copy does the right thing
+ NSMenu* pCopy = [subAquaSalMenu->mpMenu copy];
+ [pAquaSalMenuItem->mpMenuItem setSubmenu: pCopy];
+
+ // set title of submenu
+ [pCopy setTitle: [pAquaSalMenuItem->mpMenuItem title]];
+ }
+ }
+ else
+ {
+ if( pAquaSalMenuItem->mpSubMenu )
+ {
+ if( pAquaSalMenuItem->mpSubMenu->mpParentSalMenu == this )
+ pAquaSalMenuItem->mpSubMenu->mpParentSalMenu = nullptr;
+ }
+ pAquaSalMenuItem->mpSubMenu = nullptr;
+ [pAquaSalMenuItem->mpMenuItem setSubmenu: nil];
+ }
+}
+
+void AquaSalMenu::CheckItem( unsigned nPos, bool bCheck )
+{
+ if( nPos < maItems.size() )
+ {
+ NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
+ [pItem setState: bCheck ? NSControlStateValueOn : NSControlStateValueOff];
+ }
+}
+
+void AquaSalMenu::EnableItem( unsigned nPos, bool bEnable )
+{
+ if( nPos < maItems.size() )
+ {
+ NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
+ [pItem setEnabled: bEnable ? YES : NO];
+ }
+}
+
+void AquaSalMenu::SetItemImage( unsigned /*nPos*/, SalMenuItem* pSMI, const Image& rImage )
+{
+ AquaSalMenuItem* pSalMenuItem = static_cast<AquaSalMenuItem*>( pSMI );
+ if( ! pSalMenuItem || ! pSalMenuItem->mpMenuItem )
+ return;
+
+ NSImage* pImage = CreateNSImage( rImage );
+
+ [pSalMenuItem->mpMenuItem setImage: pImage];
+ if( pImage )
+ [pImage release];
+}
+
+void AquaSalMenu::SetItemText( unsigned /*i_nPos*/, SalMenuItem* i_pSalMenuItem, const OUString& i_rText )
+{
+ if (!i_pSalMenuItem)
+ return;
+
+ AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem *>(i_pSalMenuItem);
+
+ // Delete all mnemonics of mbMenuBar and CJK-style mnemonic
+ OUString aText = MnemonicGenerator::EraseAllMnemonicChars(i_rText);
+
+ if (aText.endsWith("...", &aText))
+ aText += u"\u2026";
+
+ NSString* pString = CreateNSString( aText );
+ if (pString)
+ {
+ [pAquaSalMenuItem->mpMenuItem setTitle: pString];
+ // if the menu item has a submenu, change its title as well
+ if (pAquaSalMenuItem->mpSubMenu)
+ [pAquaSalMenuItem->mpSubMenu->mpMenu setTitle: pString];
+ [pString release];
+ }
+}
+
+void AquaSalMenu::SetAccelerator( unsigned /*nPos*/, SalMenuItem* pSalMenuItem, const vcl::KeyCode& rKeyCode, const OUString& /*rKeyName*/ )
+{
+ sal_uInt16 nModifier;
+ sal_Unicode nCommandKey = 0;
+
+ sal_uInt16 nKeyCode=rKeyCode.GetCode();
+ if( nKeyCode )
+ {
+ if ((nKeyCode>=KEY_A) && (nKeyCode<=KEY_Z)) // letter A..Z
+ nCommandKey = nKeyCode-KEY_A + 'a';
+ else if ((nKeyCode>=KEY_0) && (nKeyCode<=KEY_9)) // numbers 0..9
+ nCommandKey = nKeyCode-KEY_0 + '0';
+ else if ((nKeyCode>=KEY_F1) && (nKeyCode<=KEY_F26)) // function keys F1..F26
+ nCommandKey = nKeyCode-KEY_F1 + NSF1FunctionKey;
+ else if( nKeyCode == KEY_REPEAT )
+ nCommandKey = NSRedoFunctionKey;
+ else if( nKeyCode == KEY_SPACE )
+ nCommandKey = ' ';
+ else
+ {
+ switch (nKeyCode)
+ {
+ case KEY_ADD:
+ nCommandKey='+';
+ break;
+ case KEY_SUBTRACT:
+ nCommandKey='-';
+ break;
+ case KEY_MULTIPLY:
+ nCommandKey='*';
+ break;
+ case KEY_DIVIDE:
+ nCommandKey='/';
+ break;
+ case KEY_POINT:
+ nCommandKey='.';
+ break;
+ case KEY_LESS:
+ nCommandKey='<';
+ break;
+ case KEY_GREATER:
+ nCommandKey='>';
+ break;
+ case KEY_EQUAL:
+ nCommandKey='=';
+ break;
+ case KEY_COLON:
+ nCommandKey=':';
+ break;
+ case KEY_NUMBERSIGN:
+ nCommandKey='#';
+ break;
+ case KEY_SEMICOLON:
+ nCommandKey=';';
+ break;
+ case KEY_BACKSPACE:
+ nCommandKey=u'\x232b';
+ break;
+ case KEY_PAGEUP:
+ nCommandKey=u'\x21de';
+ break;
+ case KEY_PAGEDOWN:
+ nCommandKey=u'\x21df';
+ break;
+ case KEY_UP:
+ nCommandKey=u'\x21e1';
+ break;
+ case KEY_DOWN:
+ nCommandKey=u'\x21e3';
+ break;
+ case KEY_RETURN:
+ nCommandKey=u'\x21a9';
+ break;
+ case KEY_BRACKETLEFT:
+ nCommandKey='[';
+ break;
+ case KEY_BRACKETRIGHT:
+ nCommandKey=']';
+ break;
+ }
+ }
+ }
+ else // not even a code ? nonsense -> ignore
+ return;
+
+ SAL_WARN_IF( !nCommandKey, "vcl", "unmapped accelerator key" );
+
+ nModifier=rKeyCode.GetModifier();
+
+ // should always use the command key
+ int nItemModifier = 0;
+
+ if (nModifier & KEY_SHIFT)
+ {
+ nItemModifier |= NSEventModifierFlagShift; // actually useful only for function keys
+ if( nKeyCode >= KEY_A && nKeyCode <= KEY_Z )
+ nCommandKey = nKeyCode - KEY_A + 'A';
+ }
+
+ if (nModifier & KEY_MOD1)
+ nItemModifier |= NSEventModifierFlagCommand;
+
+ if(nModifier & KEY_MOD2)
+ nItemModifier |= NSEventModifierFlagOption;
+
+ if(nModifier & KEY_MOD3)
+ nItemModifier |= NSEventModifierFlagControl;
+
+ AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem *>(pSalMenuItem);
+ NSString* pString = CreateNSString( OUString( &nCommandKey, 1 ) );
+ [pAquaSalMenuItem->mpMenuItem setKeyEquivalent: pString];
+ [pAquaSalMenuItem->mpMenuItem setKeyEquivalentModifierMask: nItemModifier];
+ if (pString)
+ [pString release];
+}
+
+void AquaSalMenu::GetSystemMenuData( SystemMenuData* )
+{
+}
+
+AquaSalMenu::MenuBarButtonEntry* AquaSalMenu::findButtonItem( sal_uInt16 i_nItemId )
+{
+ for( size_t i = 0; i < maButtons.size(); ++i )
+ {
+ if( maButtons[i].maButton.mnId == i_nItemId )
+ return &maButtons[i];
+ }
+ return nullptr;
+}
+
+void AquaSalMenu::statusLayout()
+{
+ if( GetSalData()->mpStatusItem )
+ {
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'view' is deprecated: first deprecated in macOS 10.14 - Use the standard button
+ // property instead"
+ NSView* pNSView = [GetSalData()->mpStatusItem view];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ if( [pNSView isMemberOfClass: [OOStatusItemView class]] ) // well of course it is
+ [static_cast<OOStatusItemView*>(pNSView) layout];
+ else
+ OSL_FAIL( "someone stole our status view" );
+ }
+}
+
+bool AquaSalMenu::AddMenuBarButton( const SalMenuButtonItem& i_rNewItem )
+{
+ if( ! mbMenuBar )
+ return false;
+
+ MenuBarButtonEntry* pEntry = findButtonItem( i_rNewItem.mnId );
+ if( pEntry )
+ {
+ releaseButtonEntry( *pEntry );
+ pEntry->maButton = i_rNewItem;
+ pEntry->mpNSImage = CreateNSImage( i_rNewItem.maImage );
+ if( i_rNewItem.maToolTipText.getLength() )
+ pEntry->mpToolTipString = CreateNSString( i_rNewItem.maToolTipText );
+ }
+ else
+ {
+ maButtons.push_back( MenuBarButtonEntry( i_rNewItem ) );
+ maButtons.back().mpNSImage = CreateNSImage( i_rNewItem.maImage );
+ maButtons.back().mpToolTipString = CreateNSString( i_rNewItem.maToolTipText );
+ }
+
+ // lazy create status item
+ SalData::getStatusItem();
+
+ if( pCurrentMenuBar == this )
+ statusLayout();
+
+ return true;
+}
+
+void AquaSalMenu::RemoveMenuBarButton( sal_uInt16 i_nId )
+{
+ MenuBarButtonEntry* pEntry = findButtonItem( i_nId );
+ if( pEntry )
+ {
+ releaseButtonEntry( *pEntry );
+ // note: vector guarantees that its contents are in a plain array
+ maButtons.erase( maButtons.begin() + (pEntry - maButtons.data()) );
+ }
+
+ if( pCurrentMenuBar == this )
+ statusLayout();
+}
+
+tools::Rectangle AquaSalMenu::GetMenuBarButtonRectPixel( sal_uInt16 i_nItemId, SalFrame* i_pReferenceFrame )
+{
+ if( ! i_pReferenceFrame || ! AquaSalFrame::isAlive( static_cast<AquaSalFrame*>(i_pReferenceFrame) ) )
+ return tools::Rectangle();
+
+ MenuBarButtonEntry* pEntry = findButtonItem( i_nItemId );
+
+ if( ! pEntry )
+ return tools::Rectangle();
+
+ NSStatusItem* pItem = SalData::getStatusItem();
+ if( ! pItem )
+ return tools::Rectangle();
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'view' is deprecated: first deprecated in macOS 10.14 - Use the standard button property
+ // instead"
+ NSView* pNSView = [pItem view];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ if( ! pNSView )
+ return tools::Rectangle();
+ NSWindow* pNSWin = [pNSView window];
+ if( ! pNSWin )
+ return tools::Rectangle();
+
+ NSRect aRect = [pNSWin convertRectToScreen:[pNSWin frame]];
+
+ // make coordinates relative to reference frame
+ static_cast<AquaSalFrame*>(i_pReferenceFrame)->CocoaToVCL( aRect.origin );
+ aRect.origin.x -= i_pReferenceFrame->maGeometry.x();
+ aRect.origin.y -= i_pReferenceFrame->maGeometry.y() + aRect.size.height;
+
+ return tools::Rectangle( Point(static_cast<tools::Long>(aRect.origin.x),
+ static_cast<tools::Long>(aRect.origin.y)
+ ),
+ Size( static_cast<tools::Long>(aRect.size.width),
+ static_cast<tools::Long>(aRect.size.height)
+ )
+ );
+}
+
+/*
+ * SalMenuItem
+ */
+
+AquaSalMenuItem::AquaSalMenuItem( const SalItemParams* pItemData ) :
+ mnId( pItemData->nId ),
+ mpVCLMenu( pItemData->pMenu ),
+ mpParentMenu( nullptr ),
+ mpSubMenu( nullptr ),
+ mpMenuItem( nil )
+{
+ if (pItemData->eType == MenuItemType::SEPARATOR)
+ {
+ mpMenuItem = [NSMenuItem separatorItem];
+ // these can go occasionally go in and out of a menu, ensure their lifecycle
+ // also for the release in AquaSalMenuItem destructor
+ [mpMenuItem retain];
+ }
+ else
+ {
+ mpMenuItem = [[SalNSMenuItem alloc] initWithMenuItem: this];
+ [mpMenuItem setEnabled: YES];
+
+ // peel mnemonics because on mac there are no such things for menu items
+ // Delete CJK-style mnemonics for the dropdown menu of the 'New button' and lower menu of 'File > New'
+ NSString* pString = CreateNSString(MnemonicGenerator::EraseAllMnemonicChars(pItemData->aText));
+ if (pString)
+ {
+ [mpMenuItem setTitle: pString];
+ [pString release];
+ }
+ // anything but a separator should set a menu to dispatch to
+ SAL_WARN_IF( !mpVCLMenu, "vcl", "no menu" );
+ }
+}
+
+AquaSalMenuItem::~AquaSalMenuItem()
+{
+ /* #i89860# FIXME:
+ using [autorelease] here (and in AquaSalMenu:::~AquaSalMenu) instead of
+ [release] fixes an occasional crash. That should indicate that we release
+ menus / menu items in the wrong order somewhere, but I
+ could not find that case.
+ */
+ if( mpMenuItem )
+ [mpMenuItem autorelease];
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salnativewidgets.cxx b/vcl/osx/salnativewidgets.cxx
new file mode 100644
index 0000000000..8a7e81fd5d
--- /dev/null
+++ b/vcl/osx/salnativewidgets.cxx
@@ -0,0 +1,1366 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <tools/long.hxx>
+#include <vcl/salnativewidgets.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/threadex.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/settings.hxx>
+
+#include <quartz/salgdi.h>
+#include <osx/salnativewidgets.h>
+#include <osx/saldata.hxx>
+#include <osx/salframe.h>
+
+#include <premac.h>
+#include <Carbon/Carbon.h>
+#include <postmac.h>
+
+#include "cuidraw.hxx"
+
+// presentation of native widgets consists of two important methods:
+
+// AquaSalGraphics::getNativeControlRegion to determine native rectangle in pixels to draw the widget
+// AquaSalGraphics::drawNativeControl to do the drawing operation itself
+
+// getNativeControlRegion has to calculate a content rectangle within it is safe to draw the widget. Furthermore a bounding rectangle
+// has to be calculated by getNativeControlRegion to consider adornments like a focus rectangle. As drawNativeControl uses Carbon
+// API calls, all widgets are drawn without text. Drawing of text is done separately by VCL on top of graphical Carbon widget
+// representation. drawNativeControl is called by VCL using content rectangle determined by getNativeControlRegion.
+
+// FIXME: when calculation bounding rectangle larger then content rectangle, text displayed by VCL will become misaligned. To avoid
+// misalignment bounding rectangle and content rectangle are calculated equally including adornments. Reduction of size for content
+// is done by drawNativeControl subsequently. Only exception is editbox: As other widgets have distinct ControlPart::SubEdit control
+// parts, editbox bounding rectangle and content rectangle are both calculated to reflect content area. Extending size for
+// adornments is done by drawNativeControl subsequently.
+
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+
+@interface NSWindow(CoreUIRendererPrivate)
++ (CUIRendererRef)coreUIRenderer;
+@end
+
+#endif
+
+static HIRect ImplGetHIRectFromRectangle(tools::Rectangle aRect)
+{
+ HIRect aHIRect;
+ aHIRect.origin.x = static_cast<float>(aRect.Left());
+ aHIRect.origin.y = static_cast<float>(aRect.Top());
+ aHIRect.size.width = static_cast<float>(aRect.GetWidth());
+ aHIRect.size.height = static_cast<float>(aRect.GetHeight());
+ return aHIRect;
+}
+
+static NSControlStateValue ImplGetButtonValue(ButtonValue aButtonValue)
+{
+ switch (aButtonValue)
+ {
+ case ButtonValue::On:
+ return NSControlStateValueOn;
+ case ButtonValue::Off:
+ case ButtonValue::DontKnow:
+ return NSControlStateValueOff;
+ case ButtonValue::Mixed:
+ default:
+ return NSControlStateValueMixed;
+ }
+}
+
+static bool AquaGetScrollRect(/* TODO: int nScreen, */
+ ControlPart nPart, const tools::Rectangle &rControlRect, tools::Rectangle &rResultRect)
+{
+ bool bRetVal = true;
+ rResultRect = rControlRect;
+ switch (nPart)
+ {
+ case ControlPart::ButtonUp:
+ rResultRect.SetBottom(rResultRect.Top());
+ break;
+ case ControlPart::ButtonDown:
+ rResultRect.SetTop(rResultRect.Bottom());
+ break;
+ case ControlPart::ButtonLeft:
+ rResultRect.SetRight(rResultRect.Left());
+ break;
+ case ControlPart::ButtonRight:
+ rResultRect.SetLeft(rResultRect.Right());
+ break;
+ case ControlPart::TrackHorzArea:
+ case ControlPart::TrackVertArea:
+ case ControlPart::ThumbHorz:
+ case ControlPart::ThumbVert:
+ case ControlPart::TrackHorzLeft:
+ case ControlPart::TrackHorzRight:
+ case ControlPart::TrackVertUpper:
+ case ControlPart::TrackVertLower:
+ break;
+ default:
+ bRetVal = false;
+ }
+ return bRetVal;
+}
+
+bool AquaSalGraphics::isNativeControlSupported(ControlType nType, ControlPart nPart)
+{
+ // native controls are now defaults. If you want to disable native controls, set the environment variable SAL_NO_NWF to
+ // something and VCL controls will be used as default again.
+
+ switch (nType)
+ {
+ case ControlType::Pushbutton:
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ case ControlType::ListNode:
+ if (nPart == ControlPart::Entire)
+ return true;
+ break;
+ case ControlType::Scrollbar:
+ if (nPart == ControlPart::DrawBackgroundHorz || nPart == ControlPart::DrawBackgroundVert
+ || nPart == ControlPart::Entire || nPart == ControlPart::HasThreeButtons)
+ return true;
+ break;
+ case ControlType::Slider:
+ if (nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea)
+ return true;
+ break;
+ case ControlType::Editbox:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::HasBackgroundTexture)
+ return true;
+ break;
+ case ControlType::MultilineEditbox:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::HasBackgroundTexture)
+ return true;
+ break;
+ case ControlType::Spinbox:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::AllButtons || nPart == ControlPart::HasBackgroundTexture)
+ return true;
+ break;
+ case ControlType::SpinButtons:
+ return false;
+ case ControlType::Combobox:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::HasBackgroundTexture)
+ return true;
+ break;
+ case ControlType::Listbox:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::ListboxWindow || nPart == ControlPart::HasBackgroundTexture
+ || nPart == ControlPart::SubEdit)
+ return true;
+ break;
+ case ControlType::TabItem:
+ case ControlType::TabPane:
+ case ControlType::TabBody:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::TabsDrawRtl || nPart == ControlPart::HasBackgroundTexture)
+ return true;
+ break;
+ case ControlType::Toolbar:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::DrawBackgroundHorz
+ || nPart == ControlPart::DrawBackgroundVert)
+ return true;
+ break;
+ case ControlType::WindowBackground:
+ if (nPart == ControlPart::BackgroundWindow || nPart == ControlPart::BackgroundDialog)
+ return true;
+ break;
+ case ControlType::Menubar:
+ if (nPart == ControlPart::Entire)
+ return true;
+ break;
+ case ControlType::Tooltip:
+ if (nPart == ControlPart::Entire)
+ return true;
+ break;
+ case ControlType::MenuPopup:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::MenuItem || nPart == ControlPart::MenuItemCheckMark
+ || nPart == ControlPart::MenuItemRadioMark)
+ return true;
+ break;
+ case ControlType::LevelBar:
+ case ControlType::Progress:
+ case ControlType::IntroProgress:
+ if (nPart == ControlPart::Entire)
+ return true;
+ break;
+ case ControlType::Frame:
+ if (nPart == ControlPart::Border)
+ return true;
+ break;
+ case ControlType::ListNet:
+ if (nPart == ControlPart::Entire)
+ return true;
+ break;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool AquaSalGraphics::hitTestNativeControl(ControlType nType, ControlPart nPart, const tools::Rectangle &rControlRegion,
+ const Point &rPos, bool& rIsInside)
+{
+ if (nType == ControlType::Scrollbar)
+ {
+ tools::Rectangle aRect;
+ bool bValid = AquaGetScrollRect(/* TODO: int nScreen, */
+ nPart, rControlRegion, aRect);
+ rIsInside = bValid && aRect.Contains(rPos);
+ return bValid;
+ }
+ return false;
+}
+
+static bool getEnabled(ControlState nState, AquaSalFrame* mpFrame)
+{
+
+ // there are non key windows which are children of key windows, e.g. autofilter configuration dialog or sidebar dropdown dialogs.
+ // To handle these windows correctly, parent frame's key window state is considered here additionally.
+
+ const bool bDrawActive = mpFrame == nullptr || [mpFrame->getNSWindow() isKeyWindow]
+ || mpFrame->mpParent == nullptr || [mpFrame->mpParent->getNSWindow() isKeyWindow];
+ if (!(nState & ControlState::ENABLED) || !bDrawActive)
+ {
+ return false;
+ }
+ return true;
+}
+
+bool AquaSalGraphics::drawNativeControl(ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle &rControlRegion,
+ ControlState nState,
+ const ImplControlValue &aValue,
+ const OUString &,
+ const Color&)
+{
+ return mpBackend->drawNativeControl(nType, nPart, rControlRegion, nState, aValue);
+}
+
+static void paintCell(NSCell* pBtn, const NSRect& bounds, bool bShowsFirstResponder, CGContextRef context, NSView* pView)
+{
+ //translate and scale because up side down otherwise
+ CGContextSaveGState(context);
+ CGContextTranslateCTM(context, bounds.origin.x, bounds.origin.y + bounds.size.height);
+ CGContextScaleCTM(context, 1, -1);
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:context flipped:NO]];
+
+ NSRect rect = { NSZeroPoint, bounds.size };
+
+ if ([pBtn isKindOfClass: [NSSliderCell class]])
+ {
+ // NSSliderCell doesn't seem to work with drawWithFrame(?), so draw the elements directly
+ [static_cast<NSSliderCell*>(pBtn)
+ drawBarInside: [static_cast<NSSliderCell*>(pBtn) barRectFlipped: NO] flipped: NO];
+ rect = [static_cast<NSSliderCell*>(pBtn) knobRectFlipped: NO];
+ [static_cast<NSSliderCell*>(pBtn) drawKnob: rect];
+ }
+ else
+ [pBtn drawWithFrame: rect inView: pView];
+
+ // setShowsFirstResponder apparently causes a hang when set on NSComboBoxCell
+ const bool bIsComboBox = [pBtn isMemberOfClass: [NSComboBoxCell class]];
+ if (!bIsComboBox)
+ [pBtn setShowsFirstResponder: bShowsFirstResponder];
+
+ if (bShowsFirstResponder)
+ {
+ NSSetFocusRingStyle(NSFocusRingOnly);
+
+ CGContextBeginTransparencyLayerWithRect(context, rect, nullptr);
+ if ([pBtn isMemberOfClass: [NSTextFieldCell class]])
+ {
+ // I wonder why NSTextFieldCell doesn't work for me in the default else branch.
+ // NSComboBoxCell works, and that derives from NSTextFieldCell, on the other
+ // hand setShowsFirstResponder causes a hangs when set on NSComboBoxCell
+ NSRect out = [pBtn focusRingMaskBoundsForFrame: rect inView: pView];
+ CGContextFillRect(context, out);
+ }
+ else if ([pBtn isKindOfClass: [NSSliderCell class]])
+ {
+ // Not getting anything useful for a NSSliderCell, so use the knob
+ [static_cast<NSSliderCell*>(pBtn) drawKnob: rect];
+ }
+ else
+ [pBtn drawFocusRingMaskWithFrame:rect inView: pView];
+
+ CGContextEndTransparencyLayer(context);
+ }
+
+ [NSGraphicsContext setCurrentContext:savedContext];
+ CGContextRestoreGState(context);
+}
+
+static void paintFocusRect(double radius, const NSRect& rect, CGContextRef context)
+{
+ NSRect bounds = rect;
+
+ CGPathRef path = CGPathCreateWithRoundedRect(bounds, radius, radius, nullptr);
+ CGContextSetStrokeColorWithColor(context, [NSColor keyboardFocusIndicatorColor].CGColor);
+ CGContextSetLineWidth(context, FOCUS_RING_WIDTH);
+ CGContextBeginPath(context);
+ CGContextAddPath(context, path);
+ CGContextStrokePath(context);
+ CFRelease(path);
+}
+
+@interface FixedWidthTabViewItem : NSTabViewItem {
+ int m_nWidth;
+}
+- (NSSize)sizeOfLabel: (BOOL)computeMin;
+- (void)setTabWidth: (int)nWidth;
+@end
+
+@implementation FixedWidthTabViewItem
+- (NSSize)sizeOfLabel: (BOOL)computeMin
+{
+ NSSize size = [super sizeOfLabel: computeMin];
+ size.width = m_nWidth;
+ return size;
+}
+- (void)setTabWidth: (int)nWidth
+{
+ m_nWidth = nWidth;
+}
+@end
+
+bool AquaGraphicsBackend::drawNativeControl(ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle &rControlRegion,
+ ControlState nState,
+ const ImplControlValue &aValue)
+{
+ if (!mrShared.checkContext())
+ return false;
+ mrShared.maContextHolder.saveState();
+ bool bOK = performDrawNativeControl(nType, nPart, rControlRegion, nState, aValue,
+ mrShared.maContextHolder.get(), mrShared.mpFrame);
+ mrShared.maContextHolder.restoreState();
+
+ tools::Rectangle buttonRect = rControlRegion;
+
+ // in most cases invalidating the whole control region instead of just the unclipped part of it is sufficient (and probably
+ // faster). However for the window background we should not unnecessarily enlarge the really changed rectangle since the
+ // difference is usually quite high. Background is always drawn as a whole since we don't know anything about its possible
+ // contents (see issue i90291).
+
+ if (nType == ControlType::WindowBackground)
+ {
+ CGRect aRect = {{0, 0}, {0, 0}};
+ if (mrShared.mxClipPath)
+ aRect = CGPathGetBoundingBox(mrShared.mxClipPath);
+ if (aRect.size.width != 0 && aRect.size.height != 0)
+ buttonRect.Intersection(tools::Rectangle(Point(static_cast<tools::Long>(aRect.origin.x),
+ static_cast<tools::Long>(aRect.origin.y)),
+ Size(static_cast<tools::Long>(aRect.size.width),
+ static_cast<tools::Long>(aRect.size.height))));
+ }
+ mrShared.refreshRect(buttonRect.Left(), buttonRect.Top(), buttonRect.GetWidth(), buttonRect.GetHeight());
+ return bOK;
+}
+
+static void drawBox(CGContextRef context, const NSRect& rc, NSColor* pColor)
+{
+ CGContextSaveGState(context);
+ CGContextTranslateCTM(context, rc.origin.x, rc.origin.y + rc.size.height);
+ CGContextScaleCTM(context, 1, -1);
+
+ NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
+
+ NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
+ NSBox* pBox = [[NSBox alloc] initWithFrame: rect];
+
+ [pBox setBoxType: NSBoxCustom];
+ [pBox setFillColor: pColor];
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH // setBorderType first deprecated in macOS 10.15
+ [pBox setBorderType: NSNoBorder];
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ [pBox displayRectIgnoringOpacity: rect inContext: graphicsContext];
+
+ [pBox release];
+
+ CGContextRestoreGState(context);
+}
+
+// if I don't crystallize this bg then the InvertCursor using kCGBlendModeDifference doesn't
+// work correctly and the cursor doesn't appear correctly
+static void drawEditableBackground(CGContextRef context, const NSRect& rc)
+{
+ CGContextSaveGState(context);
+ CGContextSetFillColorWithColor(context, [NSColor controlBackgroundColor].CGColor);
+ CGContextFillRect(context, rc);
+ CGContextRestoreGState(context);
+}
+
+// As seen in macOS 12.3.1. All a bit odd really.
+const int RoundedMargin[4] = { 6, 4, 0, 3 };
+
+bool AquaGraphicsBackendBase::performDrawNativeControl(ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle &rControlRegion,
+ ControlState nState,
+ const ImplControlValue &aValue,
+ CGContextRef context,
+ AquaSalFrame* mpFrame)
+{
+ bool bOK = false;
+ AquaSalInstance* pInst = GetSalData()->mpInstance;
+ HIRect rc = ImplGetHIRectFromRectangle(rControlRegion);
+ switch (nType)
+ {
+ case ControlType::Toolbar:
+ {
+ drawBox(context, rc, NSColor.windowBackgroundColor);
+ bOK = true;
+ }
+ break;
+ case ControlType::WindowBackground:
+ {
+ drawBox(context, rc, NSColor.windowBackgroundColor);
+ bOK = true;
+ }
+ break;
+ case ControlType::Tooltip:
+ {
+ rc.size.width += 2;
+ rc.size.height += 2;
+ drawBox(context, rc, NSColor.controlBackgroundColor);
+ bOK = true;
+ }
+ break;
+ case ControlType::Menubar:
+ case ControlType::MenuPopup:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::MenuItem || nPart == ControlPart::HasBackgroundTexture)
+ {
+ // FIXME: without this magical offset there is a 2 pixel black border on the right
+
+ rc.size.width += 2;
+ HIThemeMenuDrawInfo aMenuInfo;
+ aMenuInfo.version = 0;
+ aMenuInfo.menuType = kThemeMenuTypePullDown;
+ HIThemeMenuItemDrawInfo aMenuItemDrawInfo;
+
+ // grey theme when the item is selected is drawn here.
+
+ aMenuItemDrawInfo.itemType = kThemeMenuItemPlain;
+ if ((nPart == ControlPart::MenuItem) && (nState & ControlState::SELECTED))
+
+ // blue theme when the item is selected is drawn here.
+
+ aMenuItemDrawInfo.state = kThemeMenuSelected;
+ else
+
+ // normal color for non selected item
+
+ aMenuItemDrawInfo.state = kThemeMenuActive;
+
+ // repaints the background of the pull down menu
+
+ HIThemeDrawMenuBackground(&rc, &aMenuInfo, context, kHIThemeOrientationNormal);
+
+ // repaints the item either blue (selected) and/or grey (active only)
+
+ HIThemeDrawMenuItem(&rc, &rc, &aMenuItemDrawInfo, context, kHIThemeOrientationNormal, &rc);
+ bOK = true;
+ }
+ else if (nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark)
+ {
+ // checked, else it is not displayed (see vcl/source/window/menu.cxx)
+
+ if (nState & ControlState::PRESSED)
+ {
+ HIThemeTextInfo aTextInfo;
+ aTextInfo.version = 0;
+ aTextInfo.state = (nState & ControlState::ENABLED) ? kThemeStateInactive: kThemeStateActive;
+ aTextInfo.fontID = kThemeMenuItemMarkFont;
+ aTextInfo.horizontalFlushness = kHIThemeTextHorizontalFlushCenter;
+ aTextInfo.verticalFlushness = kHIThemeTextVerticalFlushTop;
+ aTextInfo.options = kHIThemeTextBoxOptionNone;
+ aTextInfo.truncationPosition = kHIThemeTextTruncationNone;
+
+ // aTextInfo.truncationMaxLines unused because of kHIThemeTextTruncationNone item highlighted
+
+ if (nState & ControlState::SELECTED) aTextInfo.state = kThemeStatePressed;
+ UniChar mark=(nPart == ControlPart::MenuItemCheckMark) ? kCheckUnicode: kBulletUnicode;
+ CFStringRef cfString = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, &mark, 1, kCFAllocatorNull);
+ HIThemeDrawTextBox(cfString, &rc, &aTextInfo, context, kHIThemeOrientationNormal);
+ if (cfString)
+ CFRelease(cfString);
+ bOK = true;
+ }
+ }
+ break;
+ case ControlType::Pushbutton:
+ {
+ NSControlSize eSizeKind = NSControlSizeRegular;
+ NSBezelStyle eBezelStyle = NSBezelStyleRounded;
+
+ PushButtonValue const *pPBVal = aValue.getType() == ControlType::Pushbutton ?
+ static_cast<PushButtonValue const *>(&aValue) : nullptr;
+
+ SInt32 nPaintHeight = rc.size.height;
+ if (rc.size.height <= PUSH_BUTTON_NORMAL_HEIGHT)
+ {
+ eSizeKind = NSControlSizeMini;
+ GetThemeMetric(kThemeMetricSmallPushButtonHeight, &nPaintHeight);
+ }
+ else if ((pPBVal && pPBVal->mbSingleLine) || rc.size.height < PUSH_BUTTON_NORMAL_HEIGHT * 3 / 2)
+ {
+ GetThemeMetric(kThemeMetricPushButtonHeight, &nPaintHeight);
+ }
+ else
+ {
+ // A simple square bezel style that can scale to any size
+ eBezelStyle = NSBezelStyleSmallSquare;
+ }
+
+ // translate the origin for controls with fixed paint height so content ends up somewhere sensible
+ rc.origin.y += (rc.size.height - nPaintHeight + 1) / 2;
+ rc.size.height = nPaintHeight;
+
+ NSButtonCell* pBtn = pInst->mpButtonCell;
+ pBtn.allowsMixedState = YES;
+
+ [pBtn setTitle: @""];
+ [pBtn setButtonType: NSButtonTypeMomentaryPushIn];
+ [pBtn setBezelStyle: eBezelStyle];
+ [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
+ [pBtn setEnabled: getEnabled(nState, mpFrame)];
+ [pBtn setFocusRingType: NSFocusRingTypeExterior];
+ [pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
+ [pBtn setControlSize: eSizeKind];
+ if (nState & ControlState::DEFAULT)
+ [pBtn setKeyEquivalent: @"\r"];
+ else
+ [pBtn setKeyEquivalent: @""];
+
+ if (eBezelStyle == NSBezelStyleRounded)
+ {
+ int nMargin = RoundedMargin[eSizeKind];
+ rc.origin.x -= nMargin;
+ rc.size.width += nMargin * 2;
+
+ rc.origin.x += FOCUS_RING_WIDTH / 2;
+ rc.size.width -= FOCUS_RING_WIDTH;
+ }
+
+ const bool bFocused(nState & ControlState::FOCUSED);
+ paintCell(pBtn, rc, bFocused, context, nullptr);
+
+ bOK = true;
+ }
+ break;
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ {
+ rc.size.width -= 2 * FOCUS_RING_WIDTH;
+ rc.size.height = RADIO_BUTTON_SMALL_SIZE;
+ rc.origin.x += FOCUS_RING_WIDTH;
+ rc.origin.y += FOCUS_RING_WIDTH;
+
+ NSButtonCell* pBtn = nType == ControlType::Checkbox ? pInst->mpCheckCell : pInst->mpRadioCell;
+ pBtn.allowsMixedState = YES;
+
+ [pBtn setTitle: @""];
+ [pBtn setButtonType: nType == ControlType::Checkbox ? NSButtonTypeSwitch : NSButtonTypeRadio];
+ [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
+ [pBtn setEnabled: getEnabled(nState, mpFrame)];
+ [pBtn setFocusRingType: NSFocusRingTypeExterior];
+ [pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
+
+ const bool bFocused(nState & ControlState::FOCUSED);
+ paintCell(pBtn, rc, bFocused, context, nullptr);
+
+ bOK = true;
+ }
+ break;
+ case ControlType::ListNode:
+ {
+ NSButtonCell* pBtn = pInst->mpListNodeCell;
+ pBtn.allowsMixedState = YES;
+
+ [pBtn setTitle: @""];
+ [pBtn setButtonType: NSButtonTypeOnOff];
+ [pBtn setBezelStyle: NSBezelStyleDisclosure];
+ [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
+ [pBtn setEnabled: getEnabled(nState, mpFrame)];
+ [pBtn setFocusRingType: NSFocusRingTypeExterior];
+
+ const bool bFocused(nState & ControlState::FOCUSED);
+ paintCell(pBtn, rc, bFocused, context, nullptr);
+
+ bOK = true;
+ }
+ break;
+ case ControlType::LevelBar:
+ {
+ NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
+ NSLevelIndicator* pBox = [[NSLevelIndicator alloc] initWithFrame:rect];
+ [pBox setLevelIndicatorStyle: NSLevelIndicatorStyleContinuousCapacity];
+ [pBox setMinValue: 0];
+ [pBox setMaxValue: rc.size.width];
+ [pBox setCriticalValue: rc.size.width * 35.0 / 100.0];
+ [pBox setWarningValue: rc.size.width * 70.0 / 100.0];
+ [pBox setDoubleValue: aValue.getNumericVal()];
+
+ CGContextSaveGState(context);
+ CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
+ [NSGraphicsContext setCurrentContext: graphicsContext];
+
+ [pBox drawRect: rect];
+
+ [NSGraphicsContext setCurrentContext: savedContext];
+
+ CGContextRestoreGState(context);
+
+ [pBox release];
+
+ bOK = true;
+ }
+ break;
+ case ControlType::Progress:
+ case ControlType::IntroProgress:
+ {
+ NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
+ NSProgressIndicator* pBox = [[NSProgressIndicator alloc] initWithFrame: rect];
+ [pBox setControlSize: (rc.size.height > MEDIUM_PROGRESS_INDICATOR_HEIGHT) ?
+ NSControlSizeRegular : NSControlSizeSmall];
+ [pBox setMinValue: 0];
+ [pBox setMaxValue: rc.size.width];
+ [pBox setDoubleValue: aValue.getNumericVal()];
+ pBox.usesThreadedAnimation = NO;
+ [pBox setIndeterminate: NO];
+
+ CGContextSaveGState(context);
+ CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
+ [NSGraphicsContext setCurrentContext: graphicsContext];
+
+ [pBox drawRect: rect];
+
+ [NSGraphicsContext setCurrentContext: savedContext];
+
+ CGContextRestoreGState(context);
+
+ [pBox release];
+
+ bOK = true;
+ }
+ break;
+ case ControlType::Slider:
+ {
+ const SliderValue *pSliderVal = static_cast<SliderValue const *>(&aValue);
+ if (nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea)
+ {
+ NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
+ NSSlider* pBox = [[NSSlider alloc] initWithFrame: rect];
+
+ [pBox setEnabled: getEnabled(nState, mpFrame)];
+ [pBox setVertical: nPart == ControlPart::TrackVertArea];
+ [pBox setMinValue: pSliderVal->mnMin];
+ [pBox setMaxValue: pSliderVal->mnMax];
+ [pBox setIntegerValue: pSliderVal->mnCur];
+ [pBox setSliderType: NSSliderTypeLinear];
+ [pBox setFocusRingType: NSFocusRingTypeExterior];
+
+ const bool bFocused(nState & ControlState::FOCUSED);
+ paintCell(pBox.cell, rc, bFocused, context, mpFrame->getNSView());
+
+ [pBox release];
+
+ bOK = true;
+ }
+ }
+ break;
+ case ControlType::Scrollbar:
+ {
+ const ScrollbarValue *pScrollbarVal = (aValue.getType() == ControlType::Scrollbar)
+ ? static_cast<const ScrollbarValue *>(&aValue) : nullptr;
+ if (nPart == ControlPart::DrawBackgroundVert || nPart == ControlPart::DrawBackgroundHorz)
+ {
+ drawBox(context, rc, NSColor.controlBackgroundColor);
+
+ NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
+ NSScroller* pBar = [[NSScroller alloc] initWithFrame: rect];
+
+ double range = pScrollbarVal->mnMax - pScrollbarVal->mnVisibleSize - pScrollbarVal->mnMin;
+ double value = range ? (pScrollbarVal->mnCur - pScrollbarVal->mnMin) / range : 0;
+
+ double length = pScrollbarVal->mnMax - pScrollbarVal->mnMin;
+ double proportion = pScrollbarVal->mnVisibleSize / length;
+
+ [pBar setEnabled: getEnabled(nState, mpFrame)];
+ [pBar setScrollerStyle: NSScrollerStyleLegacy];
+ [pBar setFloatValue: value];
+ [pBar setKnobProportion: proportion];
+ bool bPressed = (pScrollbarVal->mnThumbState & ControlState::ENABLED) &&
+ (pScrollbarVal->mnThumbState & ControlState::PRESSED);
+
+ CGContextSaveGState(context);
+ CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
+
+ NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext: graphicsContext];
+
+ // For not-pressed first draw without the knob and then
+ // draw just the knob but with 50% opaque which looks sort of
+ // right
+
+ [pBar drawKnobSlotInRect: rect highlight: NO];
+
+ NSBitmapImageRep* pImageRep = [pBar bitmapImageRepForCachingDisplayInRect: rect];
+
+ NSGraphicsContext* imageContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:pImageRep];
+ [NSGraphicsContext setCurrentContext: imageContext];
+
+ [pBar drawKnob];
+
+ [NSGraphicsContext setCurrentContext: graphicsContext];
+
+ NSImage* pImage = [[NSImage alloc] initWithSize: rect.size];
+ [pImage addRepresentation: pImageRep]; // takes ownership of pImageRep
+
+ [pImage drawInRect: rect fromRect: rect
+ operation: NSCompositingOperationSourceOver
+ fraction: bPressed ? 1.0 : 0.5];
+
+ [pImage release];
+
+ [NSGraphicsContext setCurrentContext:savedContext];
+
+ CGContextRestoreGState(context);
+
+ bOK = true;
+
+ [pBar release];
+ }
+ }
+ break;
+ case ControlType::TabPane:
+ {
+ NSTabView* pBox = [[NSTabView alloc] initWithFrame: rc];
+
+ SInt32 nOverlap;
+ GetThemeMetric(kThemeMetricTabFrameOverlap, &nOverlap);
+
+ // this calculation is probably more than a little dubious
+ rc.origin.x -= pBox.contentRect.origin.x - FOCUS_RING_WIDTH;
+ rc.size.width += rc.size.width - pBox.contentRect.size.width - 2 * FOCUS_RING_WIDTH;
+ double nTopBorder = pBox.contentRect.origin.y;
+ double nBottomBorder = rc.size.height - pBox.contentRect.size.height - nTopBorder;
+ double nExtraTop = (nTopBorder - nBottomBorder) / 2;
+ rc.origin.y -= (nTopBorder - nExtraTop + nOverlap);
+ rc.size.height += (nTopBorder - nExtraTop + nBottomBorder);
+
+ CGContextSaveGState(context);
+ CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
+
+ rc.origin.x = 0;
+ rc.origin.y = 0;
+
+ [pBox setBoundsOrigin: rc.origin];
+ [pBox setBoundsSize: rc.size];
+
+ // jam this in to force the tab contents area to be left undrawn, the ControlType::TabItem
+ // will be drawn in this space.
+ const TabPaneValue& rValue = static_cast<const TabPaneValue&>(aValue);
+ SInt32 nEndCapWidth;
+ GetThemeMetric(kThemeMetricLargeTabCapsWidth, &nEndCapWidth);
+ FixedWidthTabViewItem* pItem = [[[FixedWidthTabViewItem alloc] initWithIdentifier: @"tab"] autorelease];
+ [pItem setTabWidth: rValue.m_aTabHeaderRect.GetWidth() - 2 * nEndCapWidth];
+ [pBox addTabViewItem: pItem];
+
+ NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext: graphicsContext];
+
+ [pBox drawRect: rc];
+
+ [NSGraphicsContext setCurrentContext: savedContext];
+
+ [pBox release];
+
+ CGContextRestoreGState(context);
+
+ bOK = true;
+ }
+ break;
+ case ControlType::TabItem:
+ {
+ // first, last or middle tab
+
+ TabitemValue const * pTabValue = static_cast<TabitemValue const *>(&aValue);
+ TabitemFlags nAlignment = pTabValue->mnAlignment;
+
+ // TabitemFlags::LeftAligned (and TabitemFlags::RightAligned) for the leftmost (or rightmost) tab
+ // when there are several lines of tabs because there is only one first tab and one
+ // last tab and TabitemFlags::FirstInGroup (and TabitemFlags::LastInGroup) because when the
+ // line width is different from window width, there may not be TabitemFlags::RightAligned
+ int nPaintIndex = 1;
+ bool bSolo = false;
+ if (((nAlignment & TabitemFlags::LeftAligned) && (nAlignment & TabitemFlags::RightAligned))
+ || ((nAlignment & TabitemFlags::FirstInGroup) && (nAlignment & TabitemFlags::LastInGroup)))
+ {
+ nPaintIndex = 0;
+ bSolo = true;
+ }
+ else if ((nAlignment & TabitemFlags::LeftAligned) || (nAlignment & TabitemFlags::FirstInGroup))
+ nPaintIndex = !AllSettings::GetLayoutRTL() ? 0 : 2;
+ else if ((nAlignment & TabitemFlags::RightAligned) || (nAlignment & TabitemFlags::LastInGroup))
+ nPaintIndex = !AllSettings::GetLayoutRTL() ? 2 : 0;
+
+ int nCells = !bSolo ? 3 : 1;
+ NSRect ctrlrect = { NSZeroPoint, NSMakeSize(rc.size.width * nCells + FOCUS_RING_WIDTH, rc.size.height) };
+ NSSegmentedControl* pCtrl = [[NSSegmentedControl alloc] initWithFrame: ctrlrect];
+ [pCtrl setSegmentCount: nCells];
+ if (bSolo)
+ [pCtrl setWidth: rc.size.width + FOCUS_RING_WIDTH forSegment: 0];
+ else
+ {
+ [pCtrl setWidth: rc.size.width + FOCUS_RING_WIDTH/2 forSegment: 0];
+ [pCtrl setWidth: rc.size.width forSegment: 1];
+ [pCtrl setWidth: rc.size.width + FOCUS_RING_WIDTH/2 forSegment: 2];
+ }
+ [pCtrl setSelected: (nState & ControlState::SELECTED) ? YES : NO forSegment: nPaintIndex];
+ [pCtrl setFocusRingType: NSFocusRingTypeExterior];
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:context flipped:NO]];
+
+ NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
+ NSRect tabrect = { NSMakePoint(rc.size.width * nPaintIndex + FOCUS_RING_WIDTH / 2, 0),
+ NSMakeSize(rc.size.width, rc.size.height) };
+ NSBitmapImageRep* pImageRep = [pCtrl bitmapImageRepForCachingDisplayInRect: tabrect];
+ [pCtrl cacheDisplayInRect: tabrect toBitmapImageRep: pImageRep];
+
+ NSImage* pImage = [[NSImage alloc] initWithSize: rect.size];
+ [pImage addRepresentation: pImageRep]; // takes ownership of pImageRep
+
+ [pImage drawInRect: rc fromRect: rect
+ operation: NSCompositingOperationSourceOver
+ fraction: 1.0];
+
+ [pImage release];
+
+ [NSGraphicsContext setCurrentContext:savedContext];
+
+ [pCtrl release];
+
+ if (nState & ControlState::FOCUSED)
+ {
+ if (!bSolo)
+ {
+ if (nPaintIndex == 0)
+ {
+ rc.origin.x += FOCUS_RING_WIDTH / 2;
+ rc.size.width -= FOCUS_RING_WIDTH / 2;
+ }
+ else if (nPaintIndex == 2)
+ {
+ rc.size.width -= FOCUS_RING_WIDTH / 2;
+ rc.size.width -= FOCUS_RING_WIDTH / 2;
+ }
+ }
+
+ paintFocusRect(4.0, rc, context);
+ }
+ bOK=true;
+ }
+ break;
+ case ControlType::Editbox:
+ case ControlType::MultilineEditbox:
+ {
+ rc.size.width += 2 * EDITBOX_INSET_MARGIN;
+ if (nType == ControlType::Editbox)
+ rc.size.height = EDITBOX_HEIGHT;
+ else
+ rc.size.height += 2 * (EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN);
+ rc.origin.x -= EDITBOX_INSET_MARGIN;
+ rc.origin.y -= EDITBOX_INSET_MARGIN;
+
+ NSTextFieldCell* pBtn = pInst->mpTextFieldCell;
+
+ [pBtn setEnabled: getEnabled(nState, mpFrame)];
+ [pBtn setBezeled: YES];
+ [pBtn setEditable: YES];
+ [pBtn setFocusRingType: NSFocusRingTypeExterior];
+
+ drawEditableBackground(context, rc);
+ const bool bFocused(nState & ControlState::FOCUSED);
+ paintCell(pBtn, rc, bFocused, context, mpFrame->getNSView());
+
+ bOK = true;
+ }
+ break;
+ case ControlType::Combobox:
+ if (nPart == ControlPart::HasBackgroundTexture || nPart == ControlPart::Entire)
+ {
+ rc.origin.y += (rc.size.height - COMBOBOX_HEIGHT + 1) / 2;
+ rc.size.height = COMBOBOX_HEIGHT;
+
+ NSComboBoxCell* pBtn = pInst->mpComboBoxCell;
+
+ [pBtn setEnabled: getEnabled(nState, mpFrame)];
+ [pBtn setEditable: YES];
+ [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
+ [pBtn setFocusRingType: NSFocusRingTypeExterior];
+
+ {
+ rc.origin.x += 2;
+ rc.size.width -= 1;
+ }
+
+ drawEditableBackground(context, rc);
+ const bool bFocused(nState & ControlState::FOCUSED);
+ paintCell(pBtn, rc, bFocused, context, mpFrame->getNSView());
+
+ bOK = true;
+ }
+ break;
+ case ControlType::Listbox:
+
+ switch (nPart)
+ {
+ case ControlPart::Entire:
+ case ControlPart::ButtonDown:
+ {
+ rc.origin.y += (rc.size.height - LISTBOX_HEIGHT + 1) / 2;
+ rc.size.height = LISTBOX_HEIGHT;
+
+ NSPopUpButtonCell* pBtn = pInst->mpPopUpButtonCell;
+
+ [pBtn setTitle: @""];
+ [pBtn setEnabled: getEnabled(nState, mpFrame)];
+ [pBtn setFocusRingType: NSFocusRingTypeExterior];
+ [pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
+ if (nState & ControlState::DEFAULT)
+ [pBtn setKeyEquivalent: @"\r"];
+ else
+ [pBtn setKeyEquivalent: @""];
+
+ {
+ rc.size.width += 1;
+ }
+
+ const bool bFocused(nState & ControlState::FOCUSED);
+ paintCell(pBtn, rc, bFocused, context, nullptr);
+
+ bOK = true;
+ break;
+ }
+ case ControlPart::ListboxWindow:
+ {
+ NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
+ NSScrollView* pBox = [[NSScrollView alloc] initWithFrame: rect];
+ [pBox setBorderType: NSLineBorder];
+
+ CGContextSaveGState(context);
+ CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
+ [NSGraphicsContext setCurrentContext: graphicsContext];
+
+ [pBox drawRect: rect];
+
+ [NSGraphicsContext setCurrentContext: savedContext];
+
+ CGContextRestoreGState(context);
+
+ [pBox release];
+
+ bOK = true;
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ case ControlType::Spinbox:
+ if (nPart == ControlPart::Entire)
+ {
+ // text field
+
+ rc.size.width -= SPIN_BUTTON_WIDTH + 4 * FOCUS_RING_WIDTH;
+ rc.size.height = EDITBOX_HEIGHT;
+ rc.origin.x += FOCUS_RING_WIDTH;
+ rc.origin.y += FOCUS_RING_WIDTH;
+
+ NSTextFieldCell* pEdit = pInst->mpTextFieldCell;
+
+ [pEdit setEnabled: YES];
+ [pEdit setBezeled: YES];
+ [pEdit setEditable: YES];
+ [pEdit setFocusRingType: NSFocusRingTypeExterior];
+
+ drawEditableBackground(context, rc);
+ const bool bFocused(nState & ControlState::FOCUSED);
+ paintCell(pEdit, rc, bFocused, context, mpFrame->getNSView());
+
+ // buttons
+
+ const SpinbuttonValue *pSpinButtonVal = (aValue.getType() == ControlType::SpinButtons)
+ ? static_cast <const SpinbuttonValue *>(&aValue) : nullptr;
+ if (pSpinButtonVal)
+ {
+ ControlState nUpperState = pSpinButtonVal->mnUpperState;
+ ControlState nLowerState = pSpinButtonVal->mnLowerState;
+
+ rc.origin.x += rc.size.width + FOCUS_RING_WIDTH + 1;
+ rc.origin.y -= 1;
+ rc.size.width = SPIN_BUTTON_WIDTH;
+ rc.size.height = SPIN_LOWER_BUTTON_HEIGHT + SPIN_LOWER_BUTTON_HEIGHT;
+
+ NSStepperCell* pBtn = pInst->mpStepperCell;
+
+ [pBtn setTitle: @""];
+ [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
+ [pBtn setEnabled: (nUpperState & ControlState::ENABLED || nLowerState & ControlState::ENABLED) ?
+ YES : NO];
+ [pBtn setFocusRingType: NSFocusRingTypeExterior];
+ [pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
+
+ const bool bSpinFocused(nUpperState & ControlState::FOCUSED || nLowerState & ControlState::FOCUSED);
+ paintCell(pBtn, rc, bSpinFocused, context, nullptr);
+ }
+ bOK = true;
+ }
+ break;
+ case ControlType::Frame:
+ {
+ DrawFrameFlags nStyle = static_cast<DrawFrameFlags>(aValue.getNumericVal());
+ if (nPart == ControlPart::Border)
+ {
+ if (!(nStyle & DrawFrameFlags::Menu) && !(nStyle & DrawFrameFlags::WindowBorder))
+ {
+
+ // strange effects start to happen when HIThemeDrawFrame meets the border of the window.
+ // These can be avoided by clipping to the boundary of the frame (see issue 84756)
+
+ if (rc.origin.y + rc.size.height >= mpFrame->maGeometry.height() - 3)
+ {
+ CGMutablePathRef rPath = CGPathCreateMutable();
+ CGPathAddRect(rPath, nullptr,
+ CGRectMake(0, 0, mpFrame->maGeometry.width() - 1, mpFrame->maGeometry.height() - 1));
+ CGContextBeginPath(context);
+ CGContextAddPath(context, rPath);
+ CGContextClip(context);
+ CGPathRelease(rPath);
+ }
+ HIThemeFrameDrawInfo aTextDrawInfo;
+ aTextDrawInfo.version = 0;
+ aTextDrawInfo.kind = kHIThemeFrameListBox;
+ aTextDrawInfo.state = kThemeStateActive;
+ aTextDrawInfo.isFocused = false;
+ HIThemeDrawFrame(&rc, &aTextDrawInfo, context, kHIThemeOrientationNormal);
+ bOK = true;
+ }
+ }
+ }
+ break;
+ case ControlType::ListNet:
+
+ // do nothing as there isn't net for listviews on macOS
+
+ bOK = true;
+ break;
+ default:
+ break;
+ }
+
+ return bOK;
+}
+
+bool AquaSalGraphics::getNativeControlRegion(ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle &rControlRegion,
+ ControlState,
+ const ImplControlValue &aValue,
+ const OUString &,
+ tools::Rectangle &rNativeBoundingRegion,
+ tools::Rectangle &rNativeContentRegion)
+{
+ bool toReturn = false;
+ tools::Rectangle aCtrlBoundRect(rControlRegion);
+ short x = aCtrlBoundRect.Left();
+ short y = aCtrlBoundRect.Top();
+ short w, h;
+ switch (nType)
+ {
+ case ControlType::Pushbutton:
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ {
+ if (nType == ControlType::Pushbutton)
+ {
+ w = aCtrlBoundRect.GetWidth();
+ h = aCtrlBoundRect.GetHeight();
+ }
+ else
+ {
+ w = RADIO_BUTTON_SMALL_SIZE + 2 * FOCUS_RING_WIDTH + RADIO_BUTTON_TEXT_SEPARATOR;
+ h = RADIO_BUTTON_SMALL_SIZE + 2 * FOCUS_RING_WIDTH;
+ }
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ break;
+ case ControlType::LevelBar:
+ case ControlType::Progress:
+ {
+ tools::Rectangle aRect(aCtrlBoundRect);
+ if (aRect.GetHeight() < LARGE_PROGRESS_INDICATOR_HEIGHT)
+ aRect.SetBottom(aRect.Top() + MEDIUM_PROGRESS_INDICATOR_HEIGHT - 1);
+ else
+ aRect.SetBottom(aRect.Top() + LARGE_PROGRESS_INDICATOR_HEIGHT - 1);
+ rNativeBoundingRegion = aRect;
+ rNativeContentRegion = aRect;
+ toReturn = true;
+ }
+ break;
+ case ControlType::IntroProgress:
+ {
+ tools::Rectangle aRect(aCtrlBoundRect);
+ aRect.SetBottom(aRect.Top() + MEDIUM_PROGRESS_INDICATOR_HEIGHT - 1);
+ rNativeBoundingRegion = aRect;
+ rNativeContentRegion = aRect;
+ toReturn = true;
+ }
+ break;
+ case ControlType::Slider:
+ if (nPart == ControlPart::ThumbHorz)
+ {
+ w = SLIDER_WIDTH;
+ h = aCtrlBoundRect.GetHeight();
+ rNativeBoundingRegion = rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::ThumbVert)
+ {
+ w = aCtrlBoundRect.GetWidth();
+ h = SLIDER_HEIGHT;
+ rNativeBoundingRegion = rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ break;
+ case ControlType::Scrollbar:
+ {
+ tools::Rectangle aRect;
+ if (AquaGetScrollRect(nPart, aCtrlBoundRect, aRect))
+ {
+ toReturn = true;
+ rNativeBoundingRegion = aRect;
+ rNativeContentRegion = aRect;
+ }
+ }
+ break;
+ case ControlType::TabItem:
+ {
+ w = aCtrlBoundRect.GetWidth() + 2 * TAB_TEXT_MARGIN;
+ h = TAB_HEIGHT + 2;
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ break;
+ case ControlType::Editbox:
+ {
+ const tools::Long nBorderThickness = FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN;
+ // tdf#144241 don't return a negative width, expand the region to the min osx width
+ w = std::max(nBorderThickness * 2, aCtrlBoundRect.GetWidth());
+ h = EDITBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ w -= 2 * nBorderThickness;
+ h -= 2 * nBorderThickness;
+ x += nBorderThickness;
+ y += nBorderThickness;
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ break;
+ case ControlType::Combobox:
+ if (nPart == ControlPart::Entire)
+ {
+ w = aCtrlBoundRect.GetWidth();
+ h = COMBOBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::ButtonDown)
+ {
+ w = COMBOBOX_BUTTON_WIDTH + FOCUS_RING_WIDTH;
+ h = COMBOBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
+ x += aCtrlBoundRect.GetWidth() - w;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::SubEdit)
+ {
+ w = aCtrlBoundRect.GetWidth() - 2 * FOCUS_RING_WIDTH - COMBOBOX_BUTTON_WIDTH - COMBOBOX_BORDER_WIDTH
+ - 2 * COMBOBOX_TEXT_MARGIN;
+ h = COMBOBOX_HEIGHT - 2 * COMBOBOX_BORDER_WIDTH;
+ x += FOCUS_RING_WIDTH + COMBOBOX_BORDER_WIDTH + COMBOBOX_TEXT_MARGIN;
+ y += FOCUS_RING_WIDTH + COMBOBOX_BORDER_WIDTH;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ break;
+ case ControlType::Listbox:
+ if (nPart == ControlPart::Entire)
+ {
+ w = aCtrlBoundRect.GetWidth();
+ h = LISTBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::ButtonDown)
+ {
+ w = LISTBOX_BUTTON_WIDTH + FOCUS_RING_WIDTH;
+ h = LISTBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
+ x += aCtrlBoundRect.GetWidth() - w;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::SubEdit)
+ {
+ w = aCtrlBoundRect.GetWidth() - 2 * FOCUS_RING_WIDTH - LISTBOX_BUTTON_WIDTH - LISTBOX_BORDER_WIDTH
+ - 2 * LISTBOX_TEXT_MARGIN;
+ h = LISTBOX_HEIGHT - 2 * LISTBOX_BORDER_WIDTH;
+ x += FOCUS_RING_WIDTH + LISTBOX_BORDER_WIDTH + LISTBOX_TEXT_MARGIN;
+ y += FOCUS_RING_WIDTH + LISTBOX_BORDER_WIDTH;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::ListboxWindow)
+ {
+ w = aCtrlBoundRect.GetWidth() - 2;
+ h = aCtrlBoundRect.GetHeight() - 2;
+ x += 1;
+ y += 1;
+ rNativeBoundingRegion = aCtrlBoundRect;
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ break;
+ case ControlType::Spinbox:
+ if (nPart == ControlPart::Entire)
+ {
+ w = aCtrlBoundRect.GetWidth();
+ h = EDITBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
+ x += SPINBOX_OFFSET;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::SubEdit)
+ {
+ w = aCtrlBoundRect.GetWidth() - 4 * FOCUS_RING_WIDTH - SPIN_BUTTON_WIDTH - 2 * EDITBOX_BORDER_WIDTH
+ - 2 * EDITBOX_INSET_MARGIN;
+ h = EDITBOX_HEIGHT - 2 * (EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN);
+ x += FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN + SPINBOX_OFFSET;
+ y += FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::ButtonUp)
+ {
+ w = SPIN_BUTTON_WIDTH + 2 * FOCUS_RING_WIDTH;
+ h = SPIN_UPPER_BUTTON_HEIGHT + FOCUS_RING_WIDTH;
+ x += aCtrlBoundRect.GetWidth() - SPIN_BUTTON_WIDTH - 2 * FOCUS_RING_WIDTH + SPINBOX_OFFSET;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::ButtonDown)
+ {
+ w = SPIN_BUTTON_WIDTH + 2 * FOCUS_RING_WIDTH;
+ h = SPIN_LOWER_BUTTON_HEIGHT + FOCUS_RING_WIDTH;
+ x += aCtrlBoundRect.GetWidth() - SPIN_BUTTON_WIDTH - 2 * FOCUS_RING_WIDTH + SPINBOX_OFFSET;
+ y += FOCUS_RING_WIDTH + SPIN_UPPER_BUTTON_HEIGHT;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ break;
+ case ControlType::Frame:
+ {
+ DrawFrameStyle nStyle = static_cast<DrawFrameStyle>(aValue.getNumericVal() & 0x000f);
+ DrawFrameFlags nFlags = static_cast<DrawFrameFlags>(aValue.getNumericVal() & 0xfff0);
+ if (nPart == ControlPart::Border
+ && !(nFlags & (DrawFrameFlags::Menu | DrawFrameFlags::WindowBorder | DrawFrameFlags::BorderWindowBorder)))
+ {
+ tools::Rectangle aRect(aCtrlBoundRect);
+ if (nStyle == DrawFrameStyle::DoubleIn)
+ {
+ aRect.AdjustLeft(1);
+ aRect.AdjustTop(1);
+ // rRect.Right() -= 1;
+ // rRect.Bottom() -= 1;
+ }
+ else
+ {
+ aRect.AdjustLeft(1);
+ aRect.AdjustTop(1);
+ aRect.AdjustRight(-1);
+ aRect.AdjustBottom(-1);
+ }
+ rNativeContentRegion = aRect;
+ rNativeBoundingRegion = aRect;
+ toReturn = true;
+ }
+ }
+ break;
+ case ControlType::Menubar:
+ case ControlType::MenuPopup:
+ if (nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark)
+ {
+ w=10;
+ h=10;
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ break;
+ default:
+ break;
+ }
+ return toReturn;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salnsmenu.mm b/vcl/osx/salnsmenu.mm
new file mode 100644
index 0000000000..b2df2da7e5
--- /dev/null
+++ b/vcl/osx/salnsmenu.mm
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/window.hxx>
+
+#include <osx/salinst.h>
+#include <osx/saldata.hxx>
+#include <osx/salframe.h>
+#include <osx/salframeview.h>
+#include <osx/salmenu.h>
+#include <osx/salnsmenu.h>
+
+@implementation SalNSMenu
+-(id)initWithMenu: (AquaSalMenu*)pMenu
+{
+ mpMenu = pMenu;
+ return [super initWithTitle: [NSString string]];
+}
+
+-(void)menuNeedsUpdate: (NSMenu*)pMenu
+{
+ SolarMutexGuard aGuard;
+
+ if( mpMenu )
+ {
+ const AquaSalFrame* pFrame = mpMenu->getFrame();
+ if( pFrame && AquaSalFrame::isAlive( pFrame ) )
+ {
+ SalMenuEvent aMenuEvt;
+ aMenuEvt.mnId = 0;
+ aMenuEvt.mpMenu = mpMenu->mpVCLMenu;
+ if( aMenuEvt.mpMenu )
+ {
+ pFrame->CallCallback(SalEvent::MenuActivate, &aMenuEvt);
+ pFrame->CallCallback(SalEvent::MenuDeactivate, &aMenuEvt);
+ }
+ else
+ OSL_FAIL( "unconnected menu" );
+ }
+ else if( mpMenu->mpVCLMenu )
+ {
+ mpMenu->mpVCLMenu->Activate();
+ mpMenu->mpVCLMenu->Deactivate();
+
+ // Hide disabled items
+ NSArray* elements = [pMenu itemArray];
+ NSEnumerator* it = [elements objectEnumerator];
+ id element;
+ while ( ( element = [it nextObject] ) != nil )
+ {
+ NSMenuItem* item = static_cast< NSMenuItem* >( element );
+ if( ![item isSeparatorItem] )
+ [item setHidden: ![item isEnabled]];
+ }
+ }
+ }
+}
+
+-(void)setSalMenu: (AquaSalMenu*)pMenu
+{
+ mpMenu = pMenu;
+}
+@end
+
+@implementation SalNSMenuItem
+-(id)initWithMenuItem: (AquaSalMenuItem*)pMenuItem
+{
+ mpMenuItem = pMenuItem;
+ id ret = [super initWithTitle: [NSString string]
+ action: @selector(menuItemTriggered:)
+ keyEquivalent: [NSString string]];
+ [ret setTarget: self];
+ return ret;
+}
+-(void)menuItemTriggered: (id)aSender
+{
+ (void)aSender;
+ SolarMutexGuard aGuard;
+
+ // Commit uncommitted text before dispatching the selecting menu item. In
+ // certain cases such as selecting the Insert > Comment menu item in a
+ // Writer document while there is uncommitted text will call
+ // AquaSalFrame::EndExtTextInput() which will dispatch a
+ // SalEvent::EndExtTextInput event. Writer's handler for that event will
+ // delete the uncommitted text and then insert the committed text but
+ // LibreOffice will crash when deleting the uncommitted text because
+ // deletion of the text also removes and deletes the newly inserted
+ // comment.
+ NSWindow* pKeyWin = [NSApp keyWindow];
+ if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] )
+ [static_cast<SalFrameWindow*>(pKeyWin) endExtTextInput];
+
+ // tdf#49853 Keyboard shortcuts are also handled by the menu bar, but at least some of them
+ // must still end up in the view. This is necessary to handle common edit actions in docked
+ // windows (e.g. in toolbar fields).
+ NSEvent* pEvent = [NSApp currentEvent];
+ if( pEvent && [pEvent type] == NSEventTypeKeyDown )
+ {
+ unsigned int nModMask = ([pEvent modifierFlags] & (NSEventModifierFlagShift|NSEventModifierFlagControl|NSEventModifierFlagOption|NSEventModifierFlagCommand));
+ NSString* charactersIgnoringModifiers = [pEvent charactersIgnoringModifiers];
+ if( nModMask == NSEventModifierFlagCommand &&
+ ( [charactersIgnoringModifiers isEqualToString: @"v"] ||
+ [charactersIgnoringModifiers isEqualToString: @"c"] ||
+ [charactersIgnoringModifiers isEqualToString: @"x"] ||
+ [charactersIgnoringModifiers isEqualToString: @"a"] ||
+ [charactersIgnoringModifiers isEqualToString: @"z"] ) )
+ {
+ [[[NSApp keyWindow] contentView] keyDown: pEvent];
+ return;
+ }
+ }
+
+ const AquaSalFrame* pFrame = mpMenuItem->mpParentMenu ? mpMenuItem->mpParentMenu->getFrame() : nullptr;
+ if( pFrame && AquaSalFrame::isAlive( pFrame ) && ! pFrame->GetWindow()->IsInModalMode() )
+ {
+ SalMenuEvent aMenuEvt( mpMenuItem->mnId, mpMenuItem->mpVCLMenu );
+ pFrame->CallCallback(SalEvent::MenuCommand, &aMenuEvt);
+ }
+ else if( mpMenuItem->mpVCLMenu )
+ {
+ // if an item from submenu was selected. the corresponding Window does not exist because
+ // we use native popup menus, so we have to set the selected menuitem directly
+ // incidentally this of course works for top level popup menus, too
+ PopupMenu * pPopupMenu = dynamic_cast<PopupMenu *>(mpMenuItem->mpVCLMenu.get());
+ if( pPopupMenu )
+ {
+ // FIXME: revise this ugly code
+
+ // select handlers in vcl are dispatch on the original menu
+ // if not consumed by the select handler of the current menu
+ // however since only the starting menu ever came into Execute
+ // the hierarchy is not build up. Workaround this by getting
+ // the menu it should have been
+
+ // get started from hierarchy in vcl menus
+ AquaSalMenu* pParentMenu = mpMenuItem->mpParentMenu;
+ Menu* pCurMenu = mpMenuItem->mpVCLMenu;
+ while( pParentMenu && pParentMenu->mpVCLMenu )
+ {
+ pCurMenu = pParentMenu->mpVCLMenu;
+ pParentMenu = pParentMenu->mpParentSalMenu;
+ }
+
+ pPopupMenu->SetSelectedEntry( mpMenuItem->mnId );
+ pPopupMenu->ImplSelectWithStart( pCurMenu );
+ }
+ else
+ OSL_FAIL( "menubar item without frame !" );
+ }
+}
+@end
+
+@implementation OOStatusItemView
+-(void)drawRect: (NSRect)aRect
+{
+ NSGraphicsContext* pContext = [NSGraphicsContext currentContext];
+ [pContext saveGraphicsState];
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'drawStatusBarBackgroundInRect:withHighlight:' is deprecated: first deprecated in macOS
+ // 10.14 - Use the standard button instead which handles highlight drawing, making this
+ // method obsolete"
+ [SalData::getStatusItem() drawStatusBarBackgroundInRect: aRect withHighlight: NO];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ if( AquaSalMenu::pCurrentMenuBar )
+ {
+ const std::vector< AquaSalMenu::MenuBarButtonEntry >& rButtons( AquaSalMenu::pCurrentMenuBar->getButtons() );
+ NSRect aFrame = [self frame];
+ NSRect aImgRect = { { 2, 0 }, { 0, 0 } };
+ for( size_t i = 0; i < rButtons.size(); ++i )
+ {
+ const Size aPixSize = rButtons[i].maButton.maImage.GetSizePixel();
+ const NSRect aFromRect = { NSZeroPoint, NSMakeSize( aPixSize.Width(), aPixSize.Height()) };
+ aImgRect.origin.y = floor((aFrame.size.height - aFromRect.size.height)/2);
+ aImgRect.size = aFromRect.size;
+ if( rButtons[i].mpNSImage )
+ [rButtons[i].mpNSImage drawInRect: aImgRect fromRect: aFromRect operation: NSCompositingOperationSourceOver fraction: 1.0];
+ aImgRect.origin.x += aFromRect.size.width + 2;
+ }
+ }
+ [pContext restoreGraphicsState];
+}
+
+-(void)mouseUp: (NSEvent *)pEvent
+{
+ /* check if button goes up inside one of our status buttons */
+ if( AquaSalMenu::pCurrentMenuBar )
+ {
+ const std::vector< AquaSalMenu::MenuBarButtonEntry >& rButtons( AquaSalMenu::pCurrentMenuBar->getButtons() );
+ NSRect aFrame = [self frame];
+ NSRect aImgRect = { { 2, 0 }, { 0, 0 } };
+ NSPoint aMousePt = [pEvent locationInWindow];
+ for( size_t i = 0; i < rButtons.size(); ++i )
+ {
+ const Size aPixSize = rButtons[i].maButton.maImage.GetSizePixel();
+ const NSRect aFromRect = { NSZeroPoint, NSMakeSize( aPixSize.Width(), aPixSize.Height()) };
+ aImgRect.origin.y = (aFrame.size.height - aFromRect.size.height)/2;
+ aImgRect.size = aFromRect.size;
+ if( aMousePt.x >= aImgRect.origin.x && aMousePt.x <= (aImgRect.origin.x+aImgRect.size.width) &&
+ aMousePt.y >= aImgRect.origin.y && aMousePt.y <= (aImgRect.origin.y+aImgRect.size.height) )
+ {
+ if( AquaSalMenu::pCurrentMenuBar->mpFrame && AquaSalFrame::isAlive( AquaSalMenu::pCurrentMenuBar->mpFrame ) )
+ {
+ SalMenuEvent aMenuEvt( rButtons[i].maButton.mnId, AquaSalMenu::pCurrentMenuBar->mpVCLMenu );
+ AquaSalMenu::pCurrentMenuBar->mpFrame->CallCallback(SalEvent::MenuButtonCommand, &aMenuEvt);
+ }
+ return;
+ }
+
+ aImgRect.origin.x += aFromRect.size.width + 2;
+ }
+ }
+}
+
+-(void)layout
+{
+ NSStatusBar* pStatBar = [NSStatusBar systemStatusBar];
+ NSSize aSize = { 0, [pStatBar thickness] };
+ [self removeAllToolTips];
+ if( AquaSalMenu::pCurrentMenuBar )
+ {
+ const std::vector< AquaSalMenu::MenuBarButtonEntry >& rButtons( AquaSalMenu::pCurrentMenuBar->getButtons() );
+ if( ! rButtons.empty() )
+ {
+ aSize.width = 2;
+ for( size_t i = 0; i < rButtons.size(); ++i )
+ {
+ NSRect aImgRect = { { aSize.width,
+ static_cast<CGFloat>(floor((aSize.height-rButtons[i].maButton.maImage.GetSizePixel().Height())/2)) },
+ { static_cast<CGFloat>(rButtons[i].maButton.maImage.GetSizePixel().Width()),
+ static_cast<CGFloat>(rButtons[i].maButton.maImage.GetSizePixel().Height()) } };
+ if( rButtons[i].mpToolTipString )
+ [self addToolTipRect: aImgRect owner: rButtons[i].mpToolTipString userData: nullptr];
+ aSize.width += 2 + aImgRect.size.width;
+ }
+ }
+ }
+ [self setFrameSize: aSize];
+}
+@end
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salnstimer.mm b/vcl/osx/salnstimer.mm
new file mode 100644
index 0000000000..95be181644
--- /dev/null
+++ b/vcl/osx/salnstimer.mm
@@ -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 <sal/config.h>
+
+#include <osx/saltimer.h>
+#include <osx/salnstimer.h>
+#include <osx/salinst.h>
+#include <osx/saldata.hxx>
+#include <svdata.hxx>
+
+@implementation TimerCallbackCaller
+
+-(void)timerElapsed:(NSTimer*)pNSTimer
+{
+ (void) pNSTimer;
+ AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
+ if (pTimer)
+ pTimer->handleTimerElapsed();
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salobj.cxx b/vcl/osx/salobj.cxx
new file mode 100644
index 0000000000..6cf114f20c
--- /dev/null
+++ b/vcl/osx/salobj.cxx
@@ -0,0 +1,444 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/debug.hxx>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <opengl/zone.hxx>
+
+#include <osx/saldata.hxx>
+#include <osx/salframe.h>
+#include <osx/salinst.h>
+#include <osx/salobj.h>
+#include <osx/runinmain.hxx>
+
+#include <AppKit/NSOpenGLView.h>
+
+AquaSalObject::AquaSalObject( AquaSalFrame* pFrame, SystemWindowData const * pWindowData ) :
+ mpFrame( pFrame ),
+ mnClipX( -1 ),
+ mnClipY( -1 ),
+ mnClipWidth( -1 ),
+ mnClipHeight( -1 ),
+ mbClip( false ),
+ mnX( 0 ),
+ mnY( 0 ),
+ mnWidth( 20 ),
+ mnHeight( 20 )
+{
+ maSysData.mpNSView = nullptr;
+ maSysData.mbOpenGL = false;
+
+ NSRect aInitFrame = { NSZeroPoint, { 20, 20 } };
+ mpClipView = [[NSClipView alloc] initWithFrame: aInitFrame ];
+ if( mpClipView )
+ {
+ [mpFrame->getNSView() addSubview: mpClipView];
+ [mpClipView setHidden: YES];
+ }
+ if (pWindowData && pWindowData->bOpenGL)
+ {
+ maSysData.mbOpenGL = true;
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLPixelFormat' is deprecated: first deprecated in macOS 10.14 - Please use
+ // Metal or MetalKit."
+ NSOpenGLPixelFormat* pixFormat = nullptr;
+SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ if (pWindowData->bLegacy)
+ {
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLPixelFormatAttribute' is deprecated: first deprecated in macOS 10.14"
+ NSOpenGLPixelFormatAttribute const aAttributes[] =
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ {
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLPFADoubleBuffer' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFAAlphaSize' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFAColorSize' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFADepthSize' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFAMultisample' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFASampleBuffers' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPixelFormatAttribute' is deprecated: first deprecated in macOS
+ // 10.14",
+ // "'NSOpenGLPFASamples' is deprecated: first deprecated in macOS 10.14"
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFAAlphaSize, 8,
+ NSOpenGLPFAColorSize, 24,
+ NSOpenGLPFADepthSize, 24,
+ NSOpenGLPFAMultisample,
+ NSOpenGLPFASampleBuffers, NSOpenGLPixelFormatAttribute(1),
+ NSOpenGLPFASamples, NSOpenGLPixelFormatAttribute(4),
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ 0
+ };
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLPixelFormat' is deprecated: first deprecated in macOS 10.14 - Please
+ // use Metal or MetalKit."
+ pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:aAttributes];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+ else
+ {
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLPixelFormatAttribute' is deprecated: first deprecated in macOS 10.14"
+ NSOpenGLPixelFormatAttribute const aAttributes[] =
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ {
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLPFAOpenGLProfile' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLProfileVersion3_2Core' is deprecated: first deprecated in macOS
+ // 10.14",
+ // "'NSOpenGLPFADoubleBuffer' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFAAlphaSize' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFAColorSize' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFADepthSize' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFAMultisample' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFASampleBuffers' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPixelFormatAttribute' is deprecated: first deprecated in macOS
+ // 10.14",
+ // "'NSOpenGLPFASamples' is deprecated: first deprecated in macOS 10.14"
+ NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFAAlphaSize, 8,
+ NSOpenGLPFAColorSize, 24,
+ NSOpenGLPFADepthSize, 24,
+ NSOpenGLPFAMultisample,
+ NSOpenGLPFASampleBuffers, NSOpenGLPixelFormatAttribute(1),
+ NSOpenGLPFASamples, NSOpenGLPixelFormatAttribute(4),
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ 0
+ };
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLPixelFormat' is deprecated: first deprecated in macOS 10.14 - Please
+ // use Metal or MetalKit."
+ pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:aAttributes];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLView' is deprecated: first deprecated in macOS 10.14 - Please use MTKView
+ // instead."
+ maSysData.mpNSView = [[NSOpenGLView alloc] initWithFrame: aInitFrame pixelFormat:pixFormat];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+ else
+ {
+ maSysData.mpNSView = [[NSView alloc] initWithFrame: aInitFrame];
+ }
+
+ if( maSysData.mpNSView )
+ {
+ if( mpClipView )
+ [mpClipView setDocumentView: maSysData.mpNSView];
+ }
+}
+
+AquaSalObject::~AquaSalObject()
+{
+ assert( GetSalData()->mpInstance->IsMainThread() );
+
+ if( maSysData.mpNSView )
+ {
+ NSView *pView = maSysData.mpNSView;
+ [pView removeFromSuperview];
+ [pView release];
+ }
+ if( mpClipView )
+ {
+ [mpClipView removeFromSuperview];
+ [mpClipView release];
+ }
+}
+
+// Please note that the talk about QTMovieView below presumably refers
+// to stuff in the QuickTime avmedia thingie, and that QuickTime is
+// deprecated, not available for 64-bit code, and won't thus be used
+// in a "modern" build of LO anyway. So the relevance of the comment
+// is unclear.
+
+/*
+ sadly there seems to be no way to impose clipping on a child view,
+ especially a QTMovieView which seems to ignore the current context
+ completely. Also there is no real way to shape a window; on Aqua a
+ similar effect to non-rectangular windows is achieved by using a
+ non-opaque window and not painting where one wants the background
+ to shine through.
+
+ With respect to SalObject this leaves us to having an NSClipView
+ containing the child view. Even a QTMovieView respects the boundaries of
+ that, which gives us a clip "region" consisting of one rectangle.
+ This is gives us an 80% solution only, though.
+*/
+
+void AquaSalObject::ResetClipRegion()
+{
+ mbClip = false;
+ setClippedPosSize();
+}
+
+void AquaSalObject::BeginSetClipRegion( sal_uInt32 )
+{
+ mbClip = false;
+}
+
+void AquaSalObject::UnionClipRegion(
+ tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ if( mbClip )
+ {
+ if( nX < mnClipX )
+ {
+ mnClipWidth += mnClipX - nX;
+ mnClipX = nX;
+ }
+ if( nX + nWidth > mnClipX + mnClipWidth )
+ mnClipWidth = nX + nWidth - mnClipX;
+ if( nY < mnClipY )
+ {
+ mnClipHeight += mnClipY - nY;
+ mnClipY = nY;
+ }
+ if( nY + nHeight > mnClipY + mnClipHeight )
+ mnClipHeight = nY + nHeight - mnClipY;
+ }
+ else
+ {
+ mnClipX = nX;
+ mnClipY = nY;
+ mnClipWidth = nWidth;
+ mnClipHeight = nHeight;
+ mbClip = true;
+ }
+}
+
+void AquaSalObject::EndSetClipRegion()
+{
+ setClippedPosSize();
+}
+
+void AquaSalObject::SetPosSize(
+ tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ mnX = nX;
+ mnY = nY;
+ mnWidth = nWidth;
+ mnHeight = nHeight;
+ setClippedPosSize();
+}
+
+void AquaSalObject::setClippedPosSize()
+{
+ OSX_SALDATA_RUNINMAIN( setClippedPosSize() )
+
+ NSRect aViewRect = { NSZeroPoint, NSMakeSize( mnWidth, mnHeight) };
+ if( maSysData.mpNSView )
+ {
+ NSView* pNSView = maSysData.mpNSView;
+ [pNSView setFrame: aViewRect];
+ }
+
+ NSRect aClipViewRect = NSMakeRect( mnX, mnY, mnWidth, mnHeight);
+ NSPoint aClipPt = NSZeroPoint;
+ if( mbClip )
+ {
+ aClipViewRect.origin.x += mnClipX;
+ aClipViewRect.origin.y += mnClipY;
+ aClipViewRect.size.width = mnClipWidth;
+ aClipViewRect.size.height = mnClipHeight;
+ aClipPt.x = mnClipX;
+ if( mnClipY == 0 )
+ aClipPt.y = mnHeight - mnClipHeight;
+ }
+
+ mpFrame->VCLToCocoa( aClipViewRect, false );
+ [mpClipView setFrame: aClipViewRect];
+
+ [mpClipView scrollToPoint: aClipPt];
+}
+
+void AquaSalObject::Show( bool bVisible )
+{
+ if( !mpClipView )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( Show( bVisible ) )
+
+ [mpClipView setHidden: (bVisible ? NO : YES)];
+}
+
+const SystemEnvData* AquaSalObject::GetSystemData() const
+{
+ return &maSysData;
+}
+
+namespace {
+
+class AquaOpenGLContext : public OpenGLContext
+{
+public:
+ virtual void initWindow() override;
+
+private:
+ GLWindow m_aGLWin;
+
+ virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
+ virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLView' is deprecated: first deprecated in macOS 10.14 - Please use MTKView
+ // instead."
+ NSOpenGLView* getOpenGLView();
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ virtual bool ImplInit() override;
+ virtual SystemWindowData generateWinData(vcl::Window* pParent, bool bRequestLegacyContext) override;
+ virtual void makeCurrent() override;
+ virtual void destroyCurrentContext() override;
+ virtual void resetCurrent() override;
+ virtual void swapBuffers() override;
+};
+
+}
+
+void AquaOpenGLContext::resetCurrent()
+{
+ OSX_SALDATA_RUNINMAIN( resetCurrent() )
+
+ clearCurrent();
+
+ OpenGLZone aZone;
+
+ (void) this; // loplugin:staticmethods
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLContext' is deprecated: first deprecated in macOS 10.14 - Please use Metal or
+ // MetalKit."
+ [NSOpenGLContext clearCurrentContext];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+}
+
+void AquaOpenGLContext::makeCurrent()
+{
+ OSX_SALDATA_RUNINMAIN( makeCurrent() )
+
+ if (isCurrent())
+ return;
+
+ OpenGLZone aZone;
+
+ clearCurrent();
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLView' is deprecated: first deprecated in macOS 10.14 - Please use MTKView
+ // instead."
+ NSOpenGLView* pView = getOpenGLView();
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ [[pView openGLContext] makeCurrentContext];
+
+ registerAsCurrent();
+}
+
+void AquaOpenGLContext::swapBuffers()
+{
+ OSX_SALDATA_RUNINMAIN( swapBuffers() )
+
+ OpenGLZone aZone;
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLView' is deprecated: first deprecated in macOS 10.14 - Please use MTKView
+ // instead."
+ NSOpenGLView* pView = getOpenGLView();
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ [[pView openGLContext] flushBuffer];
+
+ BuffersSwapped();
+}
+
+SystemWindowData AquaOpenGLContext::generateWinData(vcl::Window* /*pParent*/, bool bRequestLegacyContext)
+{
+ SystemWindowData aWinData;
+ aWinData.bOpenGL = true;
+ aWinData.bLegacy = bRequestLegacyContext;
+ return aWinData;
+}
+
+void AquaOpenGLContext::destroyCurrentContext()
+{
+ OSX_SALDATA_RUNINMAIN( destroyCurrentContext() )
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLContext' is deprecated: first deprecated in macOS 10.14 - Please use Metal or
+ // MetalKit."
+ [NSOpenGLContext clearCurrentContext];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+}
+
+void AquaOpenGLContext::initWindow()
+{
+ OSX_SALDATA_RUNINMAIN( initWindow() )
+
+ if( !m_pChildWindow )
+ {
+ SystemWindowData winData = generateWinData(mpWindow, mbRequestLegacyContext);
+ m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
+ }
+
+ if (m_pChildWindow)
+ {
+ InitChildWindow(m_pChildWindow.get());
+ }
+}
+
+bool AquaOpenGLContext::ImplInit()
+{
+ OSX_SALDATA_RUNINMAIN_UNION( ImplInit(), boolean )
+
+ OpenGLZone aZone;
+
+ VCL_GL_INFO("OpenGLContext::ImplInit----start");
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLView' is deprecated: first deprecated in macOS 10.14 - Please use MTKView
+ // instead."
+ NSOpenGLView* pView = getOpenGLView();
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ [[pView openGLContext] makeCurrentContext];
+
+ bool bRet = InitGL();
+ InitGLDebugging();
+ return bRet;
+}
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLView' is deprecated: first deprecated in macOS 10.14 - Please use MTKView
+ // instead."
+NSOpenGLView* AquaOpenGLContext::getOpenGLView()
+SAL_WNODEPRECATED_DECLARATIONS_POP
+{
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLView' is deprecated: first deprecated in macOS 10.14 - Please use MTKView
+ // instead."
+ return reinterpret_cast<NSOpenGLView*>(m_pChildWindow->GetSystemData()->mpNSView);
+SAL_WNODEPRECATED_DECLARATIONS_POP
+}
+
+OpenGLContext* AquaSalInstance::CreateOpenGLContext()
+{
+ OSX_SALDATA_RUNINMAIN_POINTER( CreateOpenGLContext(), OpenGLContext* )
+ return new AquaOpenGLContext;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salprn.cxx b/vcl/osx/salprn.cxx
new file mode 100644
index 0000000000..9f9c8c08f3
--- /dev/null
+++ b/vcl/osx/salprn.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 <officecfg/Office/Common.hxx>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/print.hxx>
+#include <sal/macros.h>
+#include <osl/diagnose.h>
+#include <tools/long.hxx>
+
+#include <osx/salinst.h>
+#include <osx/salprn.h>
+#include <osx/printview.h>
+#include <quartz/salgdi.h>
+#include <osx/saldata.hxx>
+#include <quartz/utils.h>
+
+#include <jobset.h>
+#include <salptype.hxx>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+
+#include <algorithm>
+#include <cstdlib>
+
+using namespace vcl;
+using namespace com::sun::star;
+using namespace com::sun::star::beans;
+
+AquaSalInfoPrinter::AquaSalInfoPrinter( const SalPrinterQueueInfo& i_rQueue ) :
+ mpGraphics( nullptr ),
+ mbGraphics( false ),
+ mbJob( false ),
+ mpPrinter( nil ),
+ mpPrintInfo( nil ),
+ mePageOrientation( Orientation::Portrait ),
+ mnStartPageOffsetX( 0 ),
+ mnStartPageOffsetY( 0 ),
+ mnCurPageRangeStart( 0 ),
+ mnCurPageRangeCount( 0 )
+{
+ NSString* pStr = CreateNSString( i_rQueue.maPrinterName );
+ mpPrinter = [NSPrinter printerWithName: pStr];
+ [pStr release];
+
+ NSPrintInfo* pShared = [NSPrintInfo sharedPrintInfo];
+ if( pShared )
+ {
+ mpPrintInfo = [pShared copy];
+ [mpPrintInfo setPrinter: mpPrinter];
+ mePageOrientation = ([mpPrintInfo orientation] == NSPaperOrientationLandscape) ? Orientation::Landscape : Orientation::Portrait;
+ [mpPrintInfo setOrientation: NSPaperOrientationPortrait];
+ }
+
+ mpGraphics = new AquaSalGraphics(true);
+
+ const int nWidth = 100, nHeight = 100;
+ mpContextMemory.reset(new (std::nothrow) sal_uInt8[nWidth * 4 * nHeight]);
+
+ if (mpContextMemory)
+ {
+ mrContext = CGBitmapContextCreate(mpContextMemory.get(),
+ nWidth, nHeight, 8, nWidth * 4,
+ GetSalData()->mxRGBSpace, kCGImageAlphaNoneSkipFirst);
+ if( mrContext )
+ SetupPrinterGraphics( mrContext );
+ }
+}
+
+AquaSalInfoPrinter::~AquaSalInfoPrinter()
+{
+ delete mpGraphics;
+ if( mpPrintInfo )
+ [mpPrintInfo release];
+ if( mrContext )
+ CFRelease( mrContext );
+}
+
+void AquaSalInfoPrinter::SetupPrinterGraphics( CGContextRef i_rContext ) const
+{
+ if( mpGraphics )
+ {
+ if( mpPrintInfo )
+ {
+ // FIXME: get printer resolution
+ sal_Int32 nDPIX = 720, nDPIY = 720;
+ NSSize aPaperSize = [mpPrintInfo paperSize];
+
+ NSRect aImageRect = [mpPrintInfo imageablePageBounds];
+ if( mePageOrientation == Orientation::Portrait )
+ {
+ // move mirrored CTM back into paper
+ double dX = 0, dY = aPaperSize.height;
+ // move CTM to reflect imageable area
+ dX += aImageRect.origin.x;
+ dY -= aPaperSize.height - aImageRect.size.height - aImageRect.origin.y;
+ CGContextTranslateCTM( i_rContext, dX + mnStartPageOffsetX, dY - mnStartPageOffsetY );
+ // scale to be top/down and reflect our "virtual" DPI
+ CGContextScaleCTM( i_rContext, 72.0/double(nDPIX), -(72.0/double(nDPIY)) );
+ }
+ else
+ {
+ // move CTM to reflect imageable area
+ double dX = aImageRect.origin.x, dY = aPaperSize.height - aImageRect.size.height - aImageRect.origin.y;
+ CGContextTranslateCTM( i_rContext, -dX, -dY );
+ // turn by 90 degree
+ CGContextRotateCTM( i_rContext, M_PI/2 );
+ // move turned CTM back into paper
+ dX = aPaperSize.height;
+ dY = -aPaperSize.width;
+ CGContextTranslateCTM( i_rContext, dX + mnStartPageOffsetY, dY - mnStartPageOffsetX );
+ // scale to be top/down and reflect our "virtual" DPI
+ CGContextScaleCTM( i_rContext, -(72.0/double(nDPIY)), (72.0/double(nDPIX)) );
+ }
+ mpGraphics->SetPrinterGraphics( i_rContext, nDPIX, nDPIY );
+ }
+ else
+ OSL_FAIL( "no print info in SetupPrinterGraphics" );
+ }
+}
+
+SalGraphics* AquaSalInfoPrinter::AcquireGraphics()
+{
+ SalGraphics* pGraphics = mbGraphics ? nullptr : mpGraphics;
+ mbGraphics = true;
+ return pGraphics;
+}
+
+void AquaSalInfoPrinter::ReleaseGraphics( SalGraphics* )
+{
+ mbGraphics = false;
+}
+
+bool AquaSalInfoPrinter::Setup( weld::Window*, ImplJobSetup* )
+{
+ return false;
+}
+
+bool AquaSalInfoPrinter::SetPrinterData( ImplJobSetup* io_pSetupData )
+{
+ // FIXME: implement driver data
+ if( io_pSetupData && io_pSetupData->GetDriverData() )
+ return SetData( JobSetFlags::ALL, io_pSetupData );
+
+ bool bSuccess = true;
+
+ // set system type
+ io_pSetupData->SetSystem( JOBSETUP_SYSTEM_MAC );
+
+ // get paper format
+ if( mpPrintInfo )
+ {
+ NSSize aPaperSize = [mpPrintInfo paperSize];
+ double width = aPaperSize.width, height = aPaperSize.height;
+ // set paper
+ PaperInfo aInfo( PtTo10Mu( width ), PtTo10Mu( height ) );
+ aInfo.doSloppyFit();
+ io_pSetupData->SetPaperFormat( aInfo.getPaper() );
+ if( io_pSetupData->GetPaperFormat() == PAPER_USER )
+ {
+ io_pSetupData->SetPaperWidth( PtTo10Mu( width ) );
+ io_pSetupData->SetPaperHeight( PtTo10Mu( height ) );
+ }
+ else
+ {
+ io_pSetupData->SetPaperWidth( 0 );
+ io_pSetupData->SetPaperHeight( 0 );
+ }
+
+ // set orientation
+ io_pSetupData->SetOrientation( mePageOrientation );
+
+ io_pSetupData->SetPaperBin( 0 );
+ io_pSetupData->SetDriverData( std::make_unique<sal_uInt8[]>(4), 4 );
+ }
+ else
+ bSuccess = false;
+
+ return bSuccess;
+}
+
+void AquaSalInfoPrinter::setPaperSize( tools::Long i_nWidth, tools::Long i_nHeight, Orientation i_eSetOrientation )
+{
+
+ Orientation ePaperOrientation = Orientation::Portrait;
+ const PaperInfo* pPaper = matchPaper( i_nWidth, i_nHeight, ePaperOrientation );
+
+ if( pPaper )
+ {
+ NSString* pPaperName = [CreateNSString( OStringToOUString(PaperInfo::toPSName(pPaper->getPaper()), RTL_TEXTENCODING_ASCII_US) ) autorelease];
+ [mpPrintInfo setPaperName: pPaperName];
+ }
+ else if( i_nWidth > 0 && i_nHeight > 0 )
+ {
+ NSSize aPaperSize = { static_cast<CGFloat>(TenMuToPt(i_nWidth)), static_cast<CGFloat>(TenMuToPt(i_nHeight)) };
+ [mpPrintInfo setPaperSize: aPaperSize];
+ }
+ // this seems counterintuitive
+ mePageOrientation = i_eSetOrientation;
+}
+
+bool AquaSalInfoPrinter::SetData( JobSetFlags i_nFlags, ImplJobSetup* io_pSetupData )
+{
+ if( ! io_pSetupData || io_pSetupData->GetSystem() != JOBSETUP_SYSTEM_MAC )
+ return false;
+
+ if( mpPrintInfo )
+ {
+ if( i_nFlags & JobSetFlags::ORIENTATION )
+ mePageOrientation = io_pSetupData->GetOrientation();
+
+ if( i_nFlags & JobSetFlags::PAPERSIZE )
+ {
+ // set paper format
+ tools::Long width = 21000, height = 29700;
+ if( io_pSetupData->GetPaperFormat() == PAPER_USER )
+ {
+ // #i101108# sanity check
+ if( io_pSetupData->GetPaperWidth() && io_pSetupData->GetPaperHeight() )
+ {
+ width = io_pSetupData->GetPaperWidth();
+ height = io_pSetupData->GetPaperHeight();
+ }
+ }
+ else
+ {
+ PaperInfo aInfo( io_pSetupData->GetPaperFormat() );
+ width = aInfo.getWidth();
+ height = aInfo.getHeight();
+ }
+
+ setPaperSize( width, height, mePageOrientation );
+ }
+ }
+
+ return mpPrintInfo != nil;
+}
+
+sal_uInt16 AquaSalInfoPrinter::GetPaperBinCount( const ImplJobSetup* )
+{
+ return 0;
+}
+
+OUString AquaSalInfoPrinter::GetPaperBinName( const ImplJobSetup*, sal_uInt16 )
+{
+ return OUString();
+}
+
+sal_uInt32 AquaSalInfoPrinter::GetCapabilities( const ImplJobSetup*, PrinterCapType i_nType )
+{
+ switch( i_nType )
+ {
+ case PrinterCapType::SupportDialog:
+ return 0;
+ case PrinterCapType::Copies:
+ return 0xffff;
+ case PrinterCapType::CollateCopies:
+ return 0xffff;
+ case PrinterCapType::SetOrientation:
+ return 1;
+ case PrinterCapType::SetPaperSize:
+ return 1;
+ case PrinterCapType::SetPaper:
+ return 1;
+ case PrinterCapType::ExternalDialog:
+ return officecfg::Office::Common::Misc::UseSystemPrintDialog::get()
+ ? 1 : 0;
+ case PrinterCapType::PDF:
+ return 1;
+ case PrinterCapType::UsePullModel:
+ return 1;
+ default: break;
+ }
+ return 0;
+}
+
+void AquaSalInfoPrinter::GetPageInfo( const ImplJobSetup*,
+ tools::Long& o_rOutWidth, tools::Long& o_rOutHeight,
+ Point& rPageOffset,
+ Size& rPaperSize )
+{
+ if( mpPrintInfo )
+ {
+ sal_Int32 nDPIX = 72, nDPIY = 72;
+ mpGraphics->GetResolution( nDPIX, nDPIY );
+ const double fXScaling = static_cast<double>(nDPIX)/72.0,
+ fYScaling = static_cast<double>(nDPIY)/72.0;
+
+ NSSize aPaperSize = [mpPrintInfo paperSize];
+ rPaperSize.setWidth( static_cast<tools::Long>( double(aPaperSize.width) * fXScaling ) );
+ rPaperSize.setHeight( static_cast<tools::Long>( double(aPaperSize.height) * fYScaling ) );
+
+ NSRect aImageRect = [mpPrintInfo imageablePageBounds];
+ rPageOffset.setX( static_cast<tools::Long>( aImageRect.origin.x * fXScaling ) );
+ rPageOffset.setY( static_cast<tools::Long>( (aPaperSize.height - aImageRect.size.height - aImageRect.origin.y) * fYScaling ) );
+ o_rOutWidth = static_cast<tools::Long>( aImageRect.size.width * fXScaling );
+ o_rOutHeight = static_cast<tools::Long>( aImageRect.size.height * fYScaling );
+
+ if( mePageOrientation == Orientation::Landscape )
+ {
+ std::swap( o_rOutWidth, o_rOutHeight );
+ // swap width and height
+ tools::Long n = rPaperSize.Width();
+ rPaperSize.setWidth(rPaperSize.Height());
+ rPaperSize.setHeight(n);
+ // swap offset x and y
+ n = rPageOffset.X();
+ rPageOffset.setX(rPageOffset.Y());
+ rPageOffset.setY(n);
+ }
+ }
+}
+
+static Size getPageSize( vcl::PrinterController const & i_rController, sal_Int32 i_nPage )
+{
+ Size aPageSize;
+ uno::Sequence< PropertyValue > const aPageParms( i_rController.getPageParameters( i_nPage ) );
+ for( const PropertyValue & pv : aPageParms )
+ {
+ if ( pv.Name == "PageSize" )
+ {
+ awt::Size aSize;
+ pv.Value >>= aSize;
+ aPageSize.setWidth( aSize.Width );
+ aPageSize.setHeight( aSize.Height );
+ break;
+ }
+ }
+ return aPageSize;
+}
+
+bool AquaSalInfoPrinter::StartJob( const OUString* i_pFileName,
+ const OUString& i_rJobName,
+ ImplJobSetup* i_pSetupData,
+ vcl::PrinterController& i_rController
+ )
+{
+ if( mbJob )
+ return false;
+
+ bool bSuccess = false;
+ bool bWasAborted = false;
+ AquaSalInstance* pInst = GetSalData()->mpInstance;
+ PrintAccessoryViewState aAccViewState;
+ sal_Int32 nAllPages = 0;
+
+ // reset IsLastPage
+ i_rController.setLastPage( false );
+
+ // update job data
+ if( i_pSetupData )
+ SetData( JobSetFlags::ALL, i_pSetupData );
+
+ // do we want a progress panel ?
+ bool bShowProgressPanel = true;
+ beans::PropertyValue* pMonitor = i_rController.getValue( OUString( "MonitorVisible" ) );
+ if( pMonitor )
+ pMonitor->Value >>= bShowProgressPanel;
+ if( ! i_rController.isShowDialogs() )
+ bShowProgressPanel = false;
+
+ // possibly create one job for collated output
+ bool bSinglePrintJobs = i_rController.getPrinter()->IsSinglePrintJobs();
+
+ // FIXME: jobStarted() should be done after the print dialog has ended (if there is one)
+ // how do I know when that might be ?
+ i_rController.jobStarted();
+
+ int nCopies = i_rController.getPrinter()->GetCopyCount();
+ int nJobs = 1;
+ if( bSinglePrintJobs )
+ {
+ nJobs = nCopies;
+ nCopies = 1;
+ }
+
+ for( int nCurJob = 0; nCurJob < nJobs; nCurJob++ )
+ {
+ aAccViewState.bNeedRestart = true;
+ do
+ {
+ if( aAccViewState.bNeedRestart )
+ {
+ mnCurPageRangeStart = 0;
+ mnCurPageRangeCount = 0;
+ nAllPages = i_rController.getFilteredPageCount();
+ }
+
+ aAccViewState.bNeedRestart = false;
+
+ Size aCurSize( 21000, 29700 );
+ if( nAllPages > 0 )
+ {
+ mnCurPageRangeCount = 1;
+ aCurSize = getPageSize( i_rController, mnCurPageRangeStart );
+ Size aNextSize( aCurSize );
+
+ // print pages up to a different size
+ while( mnCurPageRangeCount + mnCurPageRangeStart < nAllPages )
+ {
+ aNextSize = getPageSize( i_rController, mnCurPageRangeStart + mnCurPageRangeCount );
+ if( aCurSize == aNextSize // same page size
+ ||
+ (aCurSize.Width() == aNextSize.Height() && aCurSize.Height() == aNextSize.Width()) // same size, but different orientation
+ )
+ {
+ mnCurPageRangeCount++;
+ }
+ else
+ break;
+ }
+ }
+ else
+ mnCurPageRangeCount = 0;
+
+ // now for the current run
+ mnStartPageOffsetX = mnStartPageOffsetY = 0;
+ // setup the paper size and orientation
+ // do this on our associated Printer object, since that is
+ // out interface to the applications which occasionally rely on the paper
+ // information (e.g. brochure printing scales to the found paper size)
+ // also SetPaperSizeUser has the advantage that we can share a
+ // platform independent paper matching algorithm
+ VclPtr<Printer> pPrinter( i_rController.getPrinter() );
+ pPrinter->SetMapMode( MapMode( MapUnit::Map100thMM ) );
+ pPrinter->SetPaperSizeUser( aCurSize );
+
+ // create view
+ NSView* pPrintView = [[AquaPrintView alloc] initWithController: &i_rController withInfoPrinter: this];
+
+ NSMutableDictionary* pPrintDict = [mpPrintInfo dictionary];
+
+ // set filename
+ if( i_pFileName )
+ {
+ [mpPrintInfo setJobDisposition: NSPrintSaveJob];
+ NSString* pPath = CreateNSString( *i_pFileName );
+ [pPrintDict setObject:[NSURL fileURLWithPath:pPath] forKey:NSPrintJobSavingURL];
+ [pPath release];
+ }
+
+ [pPrintDict setObject: [[NSNumber numberWithInt: nCopies] autorelease] forKey: NSPrintCopies];
+ if( nCopies > 1 )
+ [pPrintDict setObject: [[NSNumber numberWithBool: pPrinter->IsCollateCopy()] autorelease] forKey: NSPrintMustCollate];
+ [pPrintDict setObject: [[NSNumber numberWithBool: YES] autorelease] forKey: NSPrintDetailedErrorReporting];
+ [pPrintDict setObject: [[NSNumber numberWithInt: 1] autorelease] forKey: NSPrintFirstPage];
+ // #i103253# weird: for some reason, autoreleasing the value below like the others above
+ // leads do a double free malloc error. Why this value should behave differently from all the others
+ // is a mystery.
+ [pPrintDict setObject: [NSNumber numberWithInt: mnCurPageRangeCount] forKey: NSPrintLastPage];
+
+ // create print operation
+ NSPrintOperation* pPrintOperation = [NSPrintOperation printOperationWithView: pPrintView printInfo: mpPrintInfo];
+
+ if( pPrintOperation )
+ {
+ NSObject* pReleaseAfterUse = nil;
+ bool bShowPanel = !i_rController.isDirectPrint()
+ && (officecfg::Office::Common::Misc::UseSystemPrintDialog::
+ get())
+ && i_rController.isShowDialogs();
+ [pPrintOperation setShowsPrintPanel: bShowPanel ? YES : NO ];
+ [pPrintOperation setShowsProgressPanel: bShowProgressPanel ? YES : NO];
+
+ // set job title (since MacOSX 10.5)
+ if( [pPrintOperation respondsToSelector: @selector(setJobTitle:)] )
+ [pPrintOperation performSelector: @selector(setJobTitle:) withObject: [CreateNSString( i_rJobName ) autorelease]];
+
+ if( bShowPanel && mnCurPageRangeStart == 0 && nCurJob == 0) // only the first range of pages (in the first job) gets the accessory view
+ pReleaseAfterUse = [AquaPrintAccessoryView setupPrinterPanel: pPrintOperation withController: &i_rController withState: &aAccViewState];
+
+ bSuccess = true;
+ mbJob = true;
+ pInst->startedPrintJob();
+ bool wasSuccessful = [pPrintOperation runOperation];
+ pInst->endedPrintJob();
+ bSuccess = wasSuccessful;
+ bWasAborted = [[[pPrintOperation printInfo] jobDisposition] compare: NSPrintCancelJob] == NSOrderedSame;
+ mbJob = false;
+ if( pReleaseAfterUse )
+ [pReleaseAfterUse release];
+ }
+
+ mnCurPageRangeStart += mnCurPageRangeCount;
+ mnCurPageRangeCount = 1;
+ } while( aAccViewState.bNeedRestart || mnCurPageRangeStart + mnCurPageRangeCount < nAllPages );
+ }
+
+ // inform application that it can release its data
+ // this is awkward, but the XRenderable interface has no method for this,
+ // so we need to call XRenderable::render one last time with IsLastPage = true
+ i_rController.setLastPage( true );
+ GDIMetaFile aPageFile;
+ if( mrContext )
+ SetupPrinterGraphics( mrContext );
+ i_rController.getFilteredPageFile( 0, aPageFile );
+
+ i_rController.setJobState( bWasAborted
+ ? view::PrintableState_JOB_ABORTED
+ : view::PrintableState_JOB_SPOOLED );
+
+ mnCurPageRangeStart = mnCurPageRangeCount = 0;
+
+ return bSuccess;
+}
+
+bool AquaSalInfoPrinter::EndJob()
+{
+ mnStartPageOffsetX = mnStartPageOffsetY = 0;
+ mbJob = false;
+ return true;
+}
+
+bool AquaSalInfoPrinter::AbortJob()
+{
+ mbJob = false;
+
+ // FIXME: implementation
+ return false;
+}
+
+SalGraphics* AquaSalInfoPrinter::StartPage( ImplJobSetup* i_pSetupData, bool i_bNewJobData )
+{
+ if( i_bNewJobData && i_pSetupData )
+ SetPrinterData( i_pSetupData );
+
+ CGContextRef rContext = [[NSGraphicsContext currentContext] CGContext];
+
+ SetupPrinterGraphics( rContext );
+
+ return mpGraphics;
+}
+
+bool AquaSalInfoPrinter::EndPage()
+{
+ mpGraphics->InvalidateContext();
+ return true;
+}
+
+AquaSalPrinter::AquaSalPrinter( AquaSalInfoPrinter* i_pInfoPrinter ) :
+ mpInfoPrinter( i_pInfoPrinter )
+{
+}
+
+AquaSalPrinter::~AquaSalPrinter()
+{
+}
+
+bool AquaSalPrinter::StartJob( const OUString* i_pFileName,
+ const OUString& i_rJobName,
+ const OUString&,
+ ImplJobSetup* i_pSetupData,
+ vcl::PrinterController& i_rController )
+{
+ return mpInfoPrinter->StartJob( i_pFileName, i_rJobName, i_pSetupData, i_rController );
+}
+
+bool AquaSalPrinter::StartJob( const OUString* /*i_pFileName*/,
+ const OUString& /*i_rJobName*/,
+ const OUString& /*i_rAppName*/,
+ sal_uInt32 /*i_nCopies*/,
+ bool /*i_bCollate*/,
+ bool /*i_bDirect*/,
+ ImplJobSetup* )
+{
+ OSL_FAIL( "should never be called" );
+ return false;
+}
+
+bool AquaSalPrinter::EndJob()
+{
+ return mpInfoPrinter->EndJob();
+}
+
+SalGraphics* AquaSalPrinter::StartPage( ImplJobSetup* i_pSetupData, bool i_bNewJobData )
+{
+ return mpInfoPrinter->StartPage( i_pSetupData, i_bNewJobData );
+}
+
+void AquaSalPrinter::EndPage()
+{
+ mpInfoPrinter->EndPage();
+}
+
+void AquaSalInfoPrinter::InitPaperFormats( const ImplJobSetup* )
+{
+ m_aPaperFormats.clear();
+ m_bPapersInit = true;
+
+ if( mpPrinter )
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ //TODO: 10.9 statusForTable:, stringListForKey:inTable:
+ if( [mpPrinter statusForTable: @"PPD"] == NSPrinterTableOK )
+ {
+ NSArray* pPaperNames = [mpPrinter stringListForKey: @"PageSize" inTable: @"PPD"];
+ if( pPaperNames )
+ {
+ unsigned int nPapers = [pPaperNames count];
+ for( unsigned int i = 0; i < nPapers; i++ )
+ {
+ NSString* pPaper = [pPaperNames objectAtIndex: i];
+ // first try to match the name
+ OString aPaperName( [pPaper UTF8String] );
+ Paper ePaper = PaperInfo::fromPSName( aPaperName );
+ if( ePaper != PAPER_USER )
+ {
+ m_aPaperFormats.push_back( PaperInfo( ePaper ) );
+ }
+ else
+ {
+ NSSize aPaperSize = [mpPrinter pageSizeForPaper: pPaper];
+ if( aPaperSize.width > 0 && aPaperSize.height > 0 )
+ {
+ PaperInfo aInfo( PtTo10Mu( aPaperSize.width ),
+ PtTo10Mu( aPaperSize.height ) );
+ if( aInfo.getPaper() == PAPER_USER )
+ aInfo.doSloppyFit();
+ m_aPaperFormats.push_back( aInfo );
+ }
+ }
+ }
+ }
+ }
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+}
+
+const PaperInfo* AquaSalInfoPrinter::matchPaper( tools::Long i_nWidth, tools::Long i_nHeight, Orientation& o_rOrientation ) const
+{
+ if( ! m_bPapersInit )
+ const_cast<AquaSalInfoPrinter*>(this)->InitPaperFormats( nullptr );
+
+ const PaperInfo* pMatch = nullptr;
+ o_rOrientation = Orientation::Portrait;
+ for( int n = 0; n < 2 ; n++ )
+ {
+ for( size_t i = 0; i < m_aPaperFormats.size(); i++ )
+ {
+ if( std::abs( m_aPaperFormats[i].getWidth() - i_nWidth ) < 50 &&
+ std::abs( m_aPaperFormats[i].getHeight() - i_nHeight ) < 50 )
+ {
+ pMatch = &m_aPaperFormats[i];
+ return pMatch;
+ }
+ }
+ o_rOrientation = Orientation::Landscape;
+ std::swap( i_nWidth, i_nHeight );
+ }
+ return pMatch;
+}
+
+int AquaSalInfoPrinter::GetLandscapeAngle( const ImplJobSetup* )
+{
+ return 900;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salsys.cxx b/vcl/osx/salsys.cxx
new file mode 100644
index 0000000000..37ad48e7dc
--- /dev/null
+++ b/vcl/osx/salsys.cxx
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <tools/long.hxx>
+#include <vcl/stdtext.hxx>
+
+#include <osx/salsys.h>
+#include <osx/saldata.hxx>
+#include <osx/salinst.h>
+#include <quartz/utils.h>
+
+#include <strings.hrc>
+
+AquaSalSystem::~AquaSalSystem()
+{
+}
+
+unsigned int AquaSalSystem::GetDisplayScreenCount()
+{
+ NSArray* pScreens = [NSScreen screens];
+ return pScreens ? [pScreens count] : 1;
+}
+
+AbsoluteScreenPixelRectangle AquaSalSystem::GetDisplayScreenPosSizePixel( unsigned int nScreen )
+{
+ if (Application::IsBitmapRendering())
+ {
+ AbsoluteScreenPixelRectangle aRect;
+ if (nScreen == 0)
+ aRect = AbsoluteScreenPixelRectangle(AbsoluteScreenPixelPoint(0,0), AbsoluteScreenPixelSize(1024, 768));
+ return aRect;
+ }
+
+ NSArray* pScreens = [NSScreen screens];
+ AbsoluteScreenPixelRectangle aRet;
+ NSScreen* pScreen = nil;
+ if( pScreens && nScreen < [pScreens count] )
+ pScreen = [pScreens objectAtIndex: nScreen];
+ else
+ pScreen = [NSScreen mainScreen];
+
+ if( pScreen )
+ {
+ NSRect aFrame = [pScreen frame];
+ aRet = AbsoluteScreenPixelRectangle(
+ AbsoluteScreenPixelPoint( static_cast<tools::Long>(aFrame.origin.x), static_cast<tools::Long>(aFrame.origin.y) ),
+ AbsoluteScreenPixelSize( static_cast<tools::Long>(aFrame.size.width), static_cast<tools::Long>(aFrame.size.height) ) );
+ }
+ return aRet;
+}
+
+static NSString* getStandardString( StandardButtonType nButtonId, bool bUseResources )
+{
+ OUString aText;
+ if( bUseResources )
+ {
+ aText = GetStandardText( nButtonId );
+ }
+ if( aText.isEmpty() ) // this is for bad cases, we might be missing the vcl resource
+ {
+ switch( nButtonId )
+ {
+ case StandardButtonType::OK: aText = "OK";break;
+ case StandardButtonType::Abort: aText = "Abort";break;
+ case StandardButtonType::Cancel: aText = "Cancel";break;
+ case StandardButtonType::Retry: aText = "Retry";break;
+ case StandardButtonType::Yes: aText = "Yes";break;
+ case StandardButtonType::No: aText = "No";break;
+ default: break;
+ }
+ }
+ return aText.isEmpty() ? nil : CreateNSString( aText);
+}
+
+int AquaSalSystem::ShowNativeMessageBox( const OUString& rTitle,
+ const OUString& rMessage )
+{
+ NSString* pTitle = CreateNSString( rTitle );
+ NSString* pMessage = CreateNSString( rMessage );
+
+ NSString* pDefText = getStandardString( StandardButtonType::OK, false/*bUseResources*/ );
+
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH //TODO: 10.10 NSRunAlertPanel
+ int nResult = NSRunAlertPanel( pTitle, @"%@", pDefText, nil, nil, pMessage );
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ if( pTitle )
+ [pTitle release];
+ if( pMessage )
+ [pMessage release];
+ if( pDefText )
+ [pDefText release];
+
+ int nRet = 0;
+ if( nResult == 1 )
+ nRet = SALSYSTEM_SHOWNATIVEMSGBOX_BTN_OK;
+
+ return nRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/saltimer.cxx b/vcl/osx/saltimer.cxx
new file mode 100644
index 0000000000..8af7de2176
--- /dev/null
+++ b/vcl/osx/saltimer.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 <rtl/math.hxx>
+#include <tools/time.hxx>
+
+#include <osx/saltimer.h>
+#include <osx/salnstimer.h>
+#include <osx/saldata.hxx>
+#include <osx/salframe.h>
+#include <osx/salinst.h>
+
+
+void ImplNSAppPostEvent( short nEventId, BOOL bAtStart, int nUserData )
+{
+ ReleasePoolHolder aPool;
+ NSEvent* pEvent = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
+ location: NSZeroPoint
+ modifierFlags: 0
+ timestamp: [[NSProcessInfo processInfo] systemUptime]
+ windowNumber: 0
+ context: nil
+ subtype: nEventId
+ data1: nUserData
+ data2: 0];
+ assert( pEvent );
+ if ( nil == pEvent )
+ return;
+ if ( NO == bAtStart )
+ {
+ // nextEventMatchingMask has to run in the main thread!
+ assert([NSThread isMainThread]);
+
+ // Posting an event to the end of an empty queue fails,
+ // so we peek the queue and post to the start, if empty.
+ // Some Qt bugs even indicate nextEvent without dequeue
+ // sometimes blocks, so we dequeue and re-add the event.
+ NSEvent* pPeekEvent = [NSApp nextEventMatchingMask: NSEventMaskAny
+ untilDate: nil
+ inMode: NSDefaultRunLoopMode
+ dequeue: YES];
+ if ( nil == pPeekEvent )
+ bAtStart = YES;
+ else
+ [NSApp postEvent: pPeekEvent atStart: YES];
+ }
+ [NSApp postEvent: pEvent atStart: bAtStart];
+}
+
+void AquaSalTimer::queueDispatchTimerEvent( bool bAtStart )
+{
+ Stop();
+ m_bDirectTimeout = true;
+ ImplNSAppPostEvent( AquaSalInstance::DispatchTimerEvent,
+ bAtStart, GetNextEventVersion() );
+}
+
+void AquaSalTimer::Start( sal_uInt64 nMS )
+{
+ SalData* pSalData = GetSalData();
+
+ if( !pSalData->mpInstance->IsMainThread() )
+ {
+ ImplNSAppPostEvent( AquaSalInstance::AppStartTimerEvent, YES, nMS );
+ return;
+ }
+
+ m_bDirectTimeout = (0 == nMS) && !ImplGetSVData()->mpWinData->mbIsLiveResize;
+ if ( m_bDirectTimeout )
+ Stop();
+ else
+ {
+ NSTimeInterval aTI = double(nMS) / 1000.0;
+ if( m_pRunningTimer != nil )
+ {
+ if ([m_pRunningTimer isValid] && rtl::math::approxEqual(
+ [m_pRunningTimer timeInterval], aTI))
+ {
+ // set new fire date
+ [m_pRunningTimer setFireDate: [NSDate dateWithTimeIntervalSinceNow: aTI]];
+ }
+ else
+ Stop();
+ }
+ else
+ Stop();
+ if( m_pRunningTimer == nil )
+ {
+ m_pRunningTimer = [[NSTimer scheduledTimerWithTimeInterval: aTI
+ target: [[[TimerCallbackCaller alloc] init] autorelease]
+ selector: @selector(timerElapsed:)
+ userInfo: nil
+ repeats: NO
+ ] retain];
+ /* #i84055# add timer to tracking run loop mode,
+ so they also elapse while e.g. life resize
+ */
+ [[NSRunLoop currentRunLoop] addTimer: m_pRunningTimer forMode: NSEventTrackingRunLoopMode];
+ }
+ }
+}
+
+void AquaSalTimer::Stop()
+{
+ assert( GetSalData()->mpInstance->IsMainThread() );
+
+ if( m_pRunningTimer != nil )
+ {
+ [m_pRunningTimer invalidate];
+ [m_pRunningTimer release];
+ m_pRunningTimer = nil;
+ }
+ InvalidateEvent();
+}
+
+void AquaSalTimer::callTimerCallback()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ SolarMutexGuard aGuard;
+ m_bDirectTimeout = false;
+ if( pSVData->maSchedCtx.mpSalTimer )
+ pSVData->maSchedCtx.mpSalTimer->CallCallback();
+}
+
+void AquaSalTimer::handleTimerElapsed()
+{
+ if ( m_bDirectTimeout || ImplGetSVData()->mpWinData->mbIsLiveResize )
+ {
+ // Stop the timer, as it is just invalidated after the firing function
+ Stop();
+ callTimerCallback();
+ }
+ else
+ queueDispatchTimerEvent( true );
+}
+
+bool AquaSalTimer::handleDispatchTimerEvent( NSEvent *pEvent )
+{
+ bool bIsValidEvent = IsValidEventVersion( [pEvent data1] );
+ if ( bIsValidEvent )
+ callTimerCallback();
+ return bIsValidEvent;
+}
+
+void AquaSalTimer::handleStartTimerEvent( NSEvent* pEvent )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->maSchedCtx.mpSalTimer )
+ {
+ NSTimeInterval posted = [pEvent timestamp] + NSTimeInterval([pEvent data1])/1000.0;
+ NSTimeInterval current = [NSDate timeIntervalSinceReferenceDate];
+ sal_uLong nTimeoutMS = 0;
+ if( (posted - current) > 0.0 )
+ nTimeoutMS = ceil( (posted - current) * 1000 );
+ Start( nTimeoutMS );
+ }
+}
+
+bool AquaSalTimer::IsTimerElapsed() const
+{
+ assert( !((ExistsValidEvent() || m_bDirectTimeout) && m_pRunningTimer) );
+ if ( ExistsValidEvent() || m_bDirectTimeout )
+ return true;
+ if ( !m_pRunningTimer )
+ return false;
+ NSDate* pDt = [m_pRunningTimer fireDate];
+ return pDt && ([pDt timeIntervalSinceNow] < 0);
+}
+
+AquaSalTimer::AquaSalTimer( )
+ : m_pRunningTimer( nil )
+{
+}
+
+AquaSalTimer::~AquaSalTimer()
+{
+ Stop();
+}
+
+void AquaSalTimer::handleWindowShouldClose()
+{
+ // for whatever reason events get filtered on close, presumably by
+ // timestamp so post a new timeout event, if there was one queued...
+ if ( ExistsValidEvent() && !m_pRunningTimer )
+ queueDispatchTimerEvent( false );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/service_entry.cxx b/vcl/osx/service_entry.cxx
new file mode 100644
index 0000000000..849e73a77d
--- /dev/null
+++ b/vcl/osx/service_entry.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/svapp.hxx>
+#include <dndhelper.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <osx/saldata.hxx>
+#include <osx/salinst.h>
+
+#include "DragSource.hxx"
+#include "DropTarget.hxx"
+#include "clipboard.hxx"
+
+using namespace ::osl;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::cppu;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::datatransfer::clipboard;
+
+uno::Reference< XInterface > AquaSalInstance::CreateClipboard( const Sequence< Any >& i_rArguments )
+{
+ if ( Application::IsHeadlessModeEnabled() || IsRunningUnitTest() )
+ return SalInstance::CreateClipboard( i_rArguments );
+
+ SalData* pSalData = GetSalData();
+ if( ! pSalData->mxClipboard.is() )
+ pSalData->mxClipboard.set(static_cast< XClipboard* >(new AquaClipboard(nullptr, true)), UNO_QUERY);
+ return pSalData->mxClipboard;
+}
+
+uno::Reference<XInterface> AquaSalInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv)
+{
+ return vcl::OleDnDHelper(new DragSource(), reinterpret_cast<sal_IntPtr>(pSysEnv->mpNSView),
+ vcl::DragOrDrop::Drag);
+}
+
+uno::Reference<XInterface> AquaSalInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv)
+{
+ return vcl::OleDnDHelper(new DropTarget(), reinterpret_cast<sal_IntPtr>(pSysEnv->mpNSView),
+ vcl::DragOrDrop::Drop);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/vclnsapp.mm b/vcl/osx/vclnsapp.mm
new file mode 100644
index 0000000000..5daf923ce1
--- /dev/null
+++ b/vcl/osx/vclnsapp.mm
@@ -0,0 +1,481 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vector>
+
+#include <stdlib.h>
+
+#include <sal/main.h>
+#include <vcl/commandevent.hxx>
+#include <vcl/ImageTree.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+
+#include <osx/saldata.hxx>
+#include <osx/salframe.h>
+#include <osx/salframeview.h>
+#include <osx/salinst.h>
+#include <osx/vclnsapp.h>
+#include <quartz/utils.h>
+
+#include <premac.h>
+#include <objc/objc-runtime.h>
+#import "Carbon/Carbon.h"
+#import "apple_remote/RemoteControl.h"
+#include <postmac.h>
+
+
+@implementation CocoaThreadEnabler
+-(void)enableCocoaThreads:(id)param
+{
+ // do nothing, this is just to start an NSThread and therefore put
+ // Cocoa into multithread mode
+ (void)param;
+}
+@end
+
+// If you wonder how this VCL_NSApplication stuff works, one thing you
+// might have missed is that the NSPrincipalClass property in
+// desktop/macosx/Info.plist has the value VCL_NSApplication.
+
+@implementation VCL_NSApplication
+
+-(void)applicationDidFinishLaunching:(NSNotification*)pNotification
+{
+ (void)pNotification;
+
+ NSEvent* pEvent = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
+ location: NSZeroPoint
+ modifierFlags: 0
+ timestamp: [[NSProcessInfo processInfo] systemUptime]
+ windowNumber: 0
+ context: nil
+ subtype: AquaSalInstance::AppExecuteSVMain
+ data1: 0
+ data2: 0 ];
+ assert( pEvent );
+ [NSApp postEvent: pEvent atStart: NO];
+
+ if( [NSWindow respondsToSelector:@selector(allowsAutomaticWindowTabbing)] )
+ {
+ [NSWindow setAllowsAutomaticWindowTabbing:NO];
+ }
+
+ // listen to dark mode change
+ [NSApp addObserver:self forKeyPath:@"effectiveAppearance" options: 0 context: nil];
+}
+
+-(void)sendEvent:(NSEvent*)pEvent
+{
+ NSEventType eType = [pEvent type];
+ if( eType == NSEventTypeApplicationDefined )
+ {
+ AquaSalInstance::handleAppDefinedEvent( pEvent );
+ }
+ else if( eType == NSEventTypeKeyDown && ([pEvent modifierFlags] & NSEventModifierFlagCommand) != 0 )
+ {
+ NSWindow* pKeyWin = [NSApp keyWindow];
+ if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] )
+ {
+ // Commit uncommitted text before dispatching key shortcuts. In
+ // certain cases such as pressing Command-Option-C in a Writer
+ // document while there is uncommitted text will call
+ // AquaSalFrame::EndExtTextInput() which will dispatch a
+ // SalEvent::EndExtTextInput event. Writer's handler for that event
+ // will delete the uncommitted text and then insert the committed
+ // text but LibreOffice will crash when deleting the uncommitted
+ // text because deletion of the text also removes and deletes the
+ // newly inserted comment.
+ [static_cast<SalFrameWindow*>(pKeyWin) endExtTextInput];
+
+ AquaSalFrame* pFrame = [static_cast<SalFrameWindow*>(pKeyWin) getSalFrame];
+ unsigned int nModMask = ([pEvent modifierFlags] & (NSEventModifierFlagShift|NSEventModifierFlagControl|NSEventModifierFlagOption|NSEventModifierFlagCommand));
+ /*
+ * #i98949# - Cmd-M miniaturize window, Cmd-Option-M miniaturize all windows
+ */
+ if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"m"] )
+ {
+ if ( nModMask == NSEventModifierFlagCommand && ([pFrame->getNSWindow() styleMask] & NSWindowStyleMaskMiniaturizable) )
+ {
+ [pFrame->getNSWindow() performMiniaturize: nil];
+ return;
+ }
+
+ if ( nModMask == ( NSEventModifierFlagCommand | NSEventModifierFlagOption ) )
+ {
+ [NSApp miniaturizeAll: nil];
+ return;
+ }
+ }
+
+ // get information whether the event was handled; keyDown returns nothing
+ GetSalData()->maKeyEventAnswer[ pEvent ] = false;
+ bool bHandled = false;
+
+ // dispatch to view directly to avoid the key event being consumed by the menubar
+ // popup windows do not get the focus, so they don't get these either
+ // simplest would be dispatch this to the key window always if it is without parent
+ // however e.g. in document we want the menu shortcut if e.g. the stylist has focus
+ if( pFrame->mpParent && !(pFrame->mnStyle & SalFrameStyleFlags::FLOAT) )
+ {
+ [[pKeyWin contentView] keyDown: pEvent];
+ bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
+ }
+
+ // see whether the main menu consumes this event
+ // if not, we want to dispatch it ourselves. Unless we do this "trick"
+ // the main menu just beeps for an unknown or disabled key equivalent
+ // and swallows the event wholesale
+ NSMenu* pMainMenu = [NSApp mainMenu];
+ if( ! bHandled &&
+ (pMainMenu == nullptr || ! [NSMenu menuBarVisible] || ! [pMainMenu performKeyEquivalent: pEvent]) )
+ {
+ [[pKeyWin contentView] keyDown: pEvent];
+ bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
+ }
+ else
+ {
+ bHandled = true; // event handled already or main menu just handled it
+ }
+ GetSalData()->maKeyEventAnswer.erase( pEvent );
+
+ if( bHandled )
+ return;
+ }
+ else if( pKeyWin )
+ {
+ // #i94601# a window not of vcl's making has the focus.
+ // Since our menus do not invoke the usual commands
+ // try to play nice with native windows like the file dialog
+ // and emulate them
+ // precondition: this ONLY works because CMD-V (paste), CMD-C (copy) and CMD-X (cut) are
+ // NOT localized, that is the same in all locales. Should this be
+ // different in any locale, this hack will fail.
+ unsigned int nModMask = ([pEvent modifierFlags] & (NSEventModifierFlagShift|NSEventModifierFlagControl|NSEventModifierFlagOption|NSEventModifierFlagCommand));
+ if( nModMask == NSEventModifierFlagCommand )
+ {
+
+ if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"v"] )
+ {
+ if( [NSApp sendAction: @selector(paste:) to: nil from: nil] )
+ return;
+ }
+ else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"c"] )
+ {
+ if( [NSApp sendAction: @selector(copy:) to: nil from: nil] )
+ return;
+ }
+ else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"x"] )
+ {
+ if( [NSApp sendAction: @selector(cut:) to: nil from: nil] )
+ return;
+ }
+ else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"a"] )
+ {
+ if( [NSApp sendAction: @selector(selectAll:) to: nil from: nil] )
+ return;
+ }
+ else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"z"] )
+ {
+ if( [NSApp sendAction: @selector(undo:) to: nil from: nil] )
+ return;
+ }
+ }
+ else if( nModMask == (NSEventModifierFlagCommand|NSEventModifierFlagShift) )
+ {
+ if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"Z"] )
+ {
+ if( [NSApp sendAction: @selector(redo:) to: nil from: nil] )
+ return;
+ }
+ }
+ }
+ }
+ [super sendEvent: pEvent];
+}
+
+-(void)sendSuperEvent:(NSEvent*)pEvent
+{
+ [super sendEvent: pEvent];
+}
+
+-(NSMenu*)applicationDockMenu:(NSApplication *)sender
+{
+ (void)sender;
+ return AquaSalInstance::GetDynamicDockMenu();
+}
+
+-(BOOL)application: (NSApplication*)app openFile: (NSString*)pFile
+{
+ (void)app;
+ std::vector<OUString> aFile { GetOUString( pFile ) };
+ if( ! AquaSalInstance::isOnCommandLine( aFile[0] ) )
+ {
+ const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, std::move(aFile));
+ AquaSalInstance::aAppEventList.push_back( pAppEvent );
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ if( pInst )
+ pInst->TriggerUserEventProcessing();
+ }
+ return YES;
+}
+
+-(void)application: (NSApplication*) app openFiles: (NSArray*)files
+{
+ (void)app;
+ std::vector<OUString> aFileList;
+
+ NSEnumerator* it = [files objectEnumerator];
+ NSString* pFile = nil;
+
+ while( (pFile = [it nextObject]) != nil )
+ {
+ const OUString aFile( GetOUString( pFile ) );
+ if( ! AquaSalInstance::isOnCommandLine( aFile ) )
+ {
+ aFileList.push_back( aFile );
+ }
+ }
+
+ if( !aFileList.empty() )
+ {
+ // we have no back channel here, we have to assume success, in which case
+ // replyToOpenOrPrint does not need to be called according to documentation
+ // [app replyToOpenOrPrint: NSApplicationDelegateReplySuccess];
+ const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, std::move(aFileList));
+ AquaSalInstance::aAppEventList.push_back( pAppEvent );
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ if( pInst )
+ pInst->TriggerUserEventProcessing();
+ }
+}
+
+-(BOOL)application: (NSApplication*)app printFile: (NSString*)pFile
+{
+ (void)app;
+ std::vector<OUString> aFile { GetOUString(pFile) };
+ const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, std::move(aFile));
+ AquaSalInstance::aAppEventList.push_back( pAppEvent );
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ if( pInst )
+ pInst->TriggerUserEventProcessing();
+ return YES;
+}
+-(NSApplicationPrintReply)application: (NSApplication *) app printFiles:(NSArray *)files withSettings: (NSDictionary *)printSettings showPrintPanels:(BOOL)bShowPrintPanels
+{
+ (void)app;
+ (void)printSettings;
+ (void)bShowPrintPanels;
+ // currently ignores print settings a bShowPrintPanels
+ std::vector<OUString> aFileList;
+
+ NSEnumerator* it = [files objectEnumerator];
+ NSString* pFile = nil;
+
+ while( (pFile = [it nextObject]) != nil )
+ {
+ aFileList.push_back( GetOUString( pFile ) );
+ }
+ const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, std::move(aFileList));
+ AquaSalInstance::aAppEventList.push_back( pAppEvent );
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ if( pInst )
+ pInst->TriggerUserEventProcessing();
+ // we have no back channel here, we have to assume success
+ // correct handling would be NSPrintingReplyLater and then send [app replyToOpenOrPrint]
+ return NSPrintingSuccess;
+}
+
+-(void)applicationWillTerminate: (NSNotification *) aNotification
+{
+ (void)aNotification;
+ sal_detail_deinitialize();
+ _Exit(0);
+}
+
+-(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app
+{
+ (void)app;
+ NSApplicationTerminateReply aReply = NSTerminateNow;
+ {
+ SolarMutexGuard aGuard;
+
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ SalFrame *pAnyFrame = pInst->anyFrame();
+ if( pAnyFrame )
+ {
+ // the following QueryExit will likely present a message box, activate application
+ [NSApp activateIgnoringOtherApps: YES];
+ aReply = pAnyFrame->CallCallback( SalEvent::Shutdown, nullptr ) ? NSTerminateCancel : NSTerminateNow;
+ }
+
+ if( aReply == NSTerminateNow )
+ {
+ ApplicationEvent aEv(ApplicationEvent::Type::PrivateDoShutdown);
+ GetpApp()->AppEvent( aEv );
+ ImageTree::get().shutdown();
+ // DeInitVCL should be called in ImplSVMain - unless someone exits first which
+ // can occur in Desktop::doShutdown for example
+ }
+ }
+
+ return aReply;
+}
+
+-(void)observeValueForKeyPath: (NSString*) keyPath ofObject:(id)object
+ change: (NSDictionary<NSKeyValueChangeKey, id>*)change
+ context: (void*)context
+{
+ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+ if ([keyPath isEqualToString:@"effectiveAppearance"])
+ [self systemColorsChanged: nil];
+}
+
+-(void)systemColorsChanged: (NSNotification*) pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ SalFrame *pAnyFrame = pInst->anyFrame();
+ if( pAnyFrame )
+ pAnyFrame->CallCallback( SalEvent::SettingsChanged, nullptr );
+}
+
+-(void)screenParametersChanged: (NSNotification*) pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ for( auto pSalFrame : GetSalData()->mpInstance->getFrames() )
+ {
+ AquaSalFrame *pFrame = static_cast<AquaSalFrame*>( pSalFrame );
+ pFrame->screenParametersChanged();
+ }
+}
+
+-(void)scrollbarVariantChanged: (NSNotification*) pNotification
+{
+ (void)pNotification;
+ GetSalData()->mpInstance->delayedSettingsChanged( true );
+}
+
+-(void)scrollbarSettingsChanged: (NSNotification*) pNotification
+{
+ (void)pNotification;
+ GetSalData()->mpInstance->delayedSettingsChanged( false );
+}
+
+-(void)addFallbackMenuItem: (NSMenuItem*)pNewItem
+{
+ AquaSalMenu::addFallbackMenuItem( pNewItem );
+}
+
+-(void)removeFallbackMenuItem: (NSMenuItem*)pItem
+{
+ AquaSalMenu::removeFallbackMenuItem( pItem );
+}
+
+-(void)addDockMenuItem: (NSMenuItem*)pNewItem
+{
+ NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu();
+ [pDock insertItem: pNewItem atIndex: [pDock numberOfItems]];
+}
+
+// for Apple Remote implementation
+
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+- (void)applicationWillBecomeActive:(NSNotification *)pNotification
+{
+ (void)pNotification;
+ SalData* pSalData = GetSalData();
+ AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
+ if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
+ {
+ // [remoteControl startListening: self];
+ // does crash because the right thing to do is
+ // [pAppleRemoteCtrl->remoteControl startListening: self];
+ // but the instance variable 'remoteControl' is declared protected
+ // workaround : declare remoteControl instance variable as public in RemoteMainController.m
+
+ [pAppleRemoteCtrl->remoteControl startListening: self];
+#ifdef DEBUG
+ NSLog(@"Apple Remote will become active - Using remote controls");
+#endif
+ }
+ for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
+ it != pSalData->maPresentationFrames.end(); ++it )
+ {
+ NSWindow* pNSWindow = (*it)->getNSWindow();
+ [pNSWindow setLevel: NSPopUpMenuWindowLevel];
+ if( [pNSWindow isVisible] )
+ [pNSWindow orderFront: NSApp];
+ }
+}
+
+- (void)applicationWillResignActive:(NSNotification *)pNotification
+{
+ (void)pNotification;
+ SalData* pSalData = GetSalData();
+ AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
+ if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
+ {
+ // [remoteControl stopListening: self];
+ // does crash because the right thing to do is
+ // [pAppleRemoteCtrl->remoteControl stopListening: self];
+ // but the instance variable 'remoteControl' is declared protected
+ // workaround : declare remoteControl instance variable as public in RemoteMainController.m
+
+ [pAppleRemoteCtrl->remoteControl stopListening: self];
+#ifdef DEBUG
+ NSLog(@"Apple Remote will resign active - Releasing remote controls");
+#endif
+ }
+ for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
+ it != pSalData->maPresentationFrames.end(); ++it )
+ {
+ [(*it)->getNSWindow() setLevel: NSNormalWindowLevel];
+ }
+}
+#endif
+
+- (BOOL)applicationShouldHandleReopen: (NSApplication*)pApp hasVisibleWindows: (BOOL) bWinVisible
+{
+ (void)pApp;
+ (void)bWinVisible;
+ NSObject* pHdl = GetSalData()->mpDockIconClickHandler;
+ if( pHdl && [pHdl respondsToSelector: @selector(dockIconClicked:)] )
+ {
+ [pHdl performSelector:@selector(dockIconClicked:) withObject: self];
+ }
+ return YES;
+}
+
+-(void)setDockIconClickHandler: (NSObject*)pHandler
+{
+ GetSalData()->mpDockIconClickHandler = pHandler;
+}
+
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/afl-eventtesting/README.eventtesting b/vcl/qa/afl-eventtesting/README.eventtesting
new file mode 100644
index 0000000000..963bed1cdd
--- /dev/null
+++ b/vcl/qa/afl-eventtesting/README.eventtesting
@@ -0,0 +1,24 @@
+Notes on experimental afl driven ui fuzzing
+
+only keyboard events for now
+
+vcl/workben/eventtesting.writer is just serialized "hello" + ctrl+a + ctrl+b
+keystrokes to get things started
+
+vcl/workben/eventtesting.impress is a bit more involved and inserts text,
+a new slide via the menu, bullets and undos for all of that
+
+currently an arbitrary limit of 50 keystrokes before application quits in
+order to initially explore that shallow space
+
+writer:
+Xnest :1
+cp vcl/workben/eventtesting.writer eventtesting
+afl-fuzz -f eventtesting -t 10000 -i ~/fuzz/in.vcl -o ~/fuzz/out.vcl -d -T vcl -m 50000000 instdir/program/soffice.bin --nologo --writer --eventtesting --norestore --display :1
+
+impress:
+Xnest :1
+cp vcl/workben/eventtesting.impress eventtesting
+afl-fuzz -f eventtesting -t 10000 -i ~/fuzz/in.vcl -o ~/fuzz/out.vcl -d -T vcl -m 50000000 instdir/program/soffice.bin --nologo --impress --eventtesting --norestore --display :1
+
+This also works with --headless and no --display entry and thus no Xnest required
diff --git a/vcl/qa/afl-eventtesting/eventtesting.impress b/vcl/qa/afl-eventtesting/eventtesting.impress
new file mode 100644
index 0000000000..ac7991875a
--- /dev/null
+++ b/vcl/qa/afl-eventtesting/eventtesting.impress
Binary files differ
diff --git a/vcl/qa/afl-eventtesting/eventtesting.impress.crash-1 b/vcl/qa/afl-eventtesting/eventtesting.impress.crash-1
new file mode 100644
index 0000000000..f8802b5ad7
--- /dev/null
+++ b/vcl/qa/afl-eventtesting/eventtesting.impress.crash-1
Binary files differ
diff --git a/vcl/qa/afl-eventtesting/eventtesting.impress.crash-2 b/vcl/qa/afl-eventtesting/eventtesting.impress.crash-2
new file mode 100644
index 0000000000..d312939e47
--- /dev/null
+++ b/vcl/qa/afl-eventtesting/eventtesting.impress.crash-2
Binary files differ
diff --git a/vcl/qa/afl-eventtesting/eventtesting.impress.crash-3 b/vcl/qa/afl-eventtesting/eventtesting.impress.crash-3
new file mode 100644
index 0000000000..e6639bab1b
--- /dev/null
+++ b/vcl/qa/afl-eventtesting/eventtesting.impress.crash-3
Binary files differ
diff --git a/vcl/qa/afl-eventtesting/eventtesting.impress.crash-4 b/vcl/qa/afl-eventtesting/eventtesting.impress.crash-4
new file mode 100644
index 0000000000..3b0f9bc82b
--- /dev/null
+++ b/vcl/qa/afl-eventtesting/eventtesting.impress.crash-4
Binary files differ
diff --git a/vcl/qa/afl-eventtesting/eventtesting.impress.crash-5 b/vcl/qa/afl-eventtesting/eventtesting.impress.crash-5
new file mode 100644
index 0000000000..0221754537
--- /dev/null
+++ b/vcl/qa/afl-eventtesting/eventtesting.impress.crash-5
Binary files differ
diff --git a/vcl/qa/afl-eventtesting/eventtesting.writer b/vcl/qa/afl-eventtesting/eventtesting.writer
new file mode 100644
index 0000000000..b85a20356f
--- /dev/null
+++ b/vcl/qa/afl-eventtesting/eventtesting.writer
Binary files differ
diff --git a/vcl/qa/api/XGraphicTest.cxx b/vcl/qa/api/XGraphicTest.cxx
new file mode 100644
index 0000000000..df05c05f9f
--- /dev/null
+++ b/vcl/qa/api/XGraphicTest.cxx
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/graphic/GraphicType.hpp>
+#include <com/sun/star/graphic/GraphicProvider.hpp>
+#include <com/sun/star/graphic/XGraphicProvider.hpp>
+#include <com/sun/star/awt/Size.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertysequence.hxx>
+
+namespace
+{
+using namespace css;
+
+constexpr OUStringLiteral gaDataUrl = u"/vcl/qa/api/data/";
+
+class XGraphicTest : public test::BootstrapFixture
+{
+public:
+ XGraphicTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(gaDataUrl) + sFileName;
+ }
+
+ void testGraphic();
+ void testGraphicDescriptor();
+ void testGraphicProvider();
+
+ CPPUNIT_TEST_SUITE(XGraphicTest);
+ CPPUNIT_TEST(testGraphic);
+ CPPUNIT_TEST(testGraphicDescriptor);
+ CPPUNIT_TEST(testGraphicProvider);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+BitmapEx createBitmap()
+{
+ Bitmap aBitmap(Size(100, 50), vcl::PixelFormat::N24_BPP);
+ aBitmap.Erase(COL_LIGHTRED);
+
+ return BitmapEx(aBitmap);
+}
+
+void XGraphicTest::testGraphic()
+{
+ Graphic aGraphic;
+ uno::Reference<graphic::XGraphic> xGraphic = aGraphic.GetXGraphic();
+}
+
+void XGraphicTest::testGraphicDescriptor()
+{
+ Graphic aGraphic(createBitmap());
+ uno::Reference<graphic::XGraphic> xGraphic = aGraphic.GetXGraphic();
+ uno::Reference<beans::XPropertySet> xGraphicDescriptor(xGraphic, uno::UNO_QUERY_THROW);
+
+ //[property] byte GraphicType;
+ sal_Int8 nType;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("GraphicType") >>= nType);
+ CPPUNIT_ASSERT_EQUAL(graphic::GraphicType::PIXEL, nType);
+
+ //[property] string MimeType;
+ OUString sMimeType;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("MimeType") >>= sMimeType);
+ CPPUNIT_ASSERT_EQUAL(OUString("image/x-vclgraphic"), sMimeType);
+
+ //[optional, property] ::com::sun::star::awt::Size SizePixel;
+ awt::Size aSizePixel;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("SizePixel") >>= aSizePixel);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(100), aSizePixel.Width);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(50), aSizePixel.Height);
+
+ //[optional, property] ::com::sun::star::awt::Size Size100thMM;
+ awt::Size aSize100thMM;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("Size100thMM") >>= aSize100thMM);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aSize100thMM.Width);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aSize100thMM.Height);
+
+ //[optional, property] byte BitsPerPixel;
+ sal_Int8 nBitsPerPixel;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("BitsPerPixel") >>= nBitsPerPixel);
+ CPPUNIT_ASSERT_EQUAL(sal_Int8(24), nBitsPerPixel);
+
+ //[optional, property] boolean Transparent;
+ bool bTransparent;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("Transparent") >>= bTransparent);
+ CPPUNIT_ASSERT_EQUAL(false, bTransparent);
+
+ //[optional, property] boolean Alpha;
+ bool bAlpha;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("Alpha") >>= bAlpha);
+ CPPUNIT_ASSERT_EQUAL(false, bAlpha);
+
+ //[optional, property] boolean Animated;
+ bool bAnimated;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("Animated") >>= bAnimated);
+ CPPUNIT_ASSERT_EQUAL(false, bAnimated);
+}
+
+void XGraphicTest::testGraphicProvider()
+{
+ OUString aGraphicURL = getFullUrl(u"TestGraphic.png");
+
+ { // Load lazy
+ uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
+ uno::Reference<graphic::XGraphicProvider> xGraphicProvider;
+ xGraphicProvider.set(graphic::GraphicProvider::create(xContext), uno::UNO_SET_THROW);
+
+ auto aMediaProperties(comphelper::InitPropertySequence({
+ { "URL", uno::Any(aGraphicURL) },
+ { "LazyRead", uno::Any(true) },
+ { "LoadAsLink", uno::Any(false) },
+ }));
+
+ uno::Reference<graphic::XGraphic> xGraphic(
+ xGraphicProvider->queryGraphic(aMediaProperties));
+ CPPUNIT_ASSERT(xGraphic.is());
+ Graphic aGraphic(xGraphic);
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ uno::Reference<beans::XPropertySet> xGraphicDescriptor(xGraphic, uno::UNO_QUERY_THROW);
+
+ sal_Int8 nType;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("GraphicType") >>= nType);
+ CPPUNIT_ASSERT_EQUAL(graphic::GraphicType::PIXEL, nType);
+
+ awt::Size aSizePixel;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("SizePixel") >>= aSizePixel);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aSizePixel.Width);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aSizePixel.Height);
+
+ bool bLinked;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("Linked") >>= bLinked);
+ CPPUNIT_ASSERT_EQUAL(false, bLinked);
+
+ OUString sOriginURL;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("OriginURL") >>= sOriginURL);
+ CPPUNIT_ASSERT_EQUAL(OUString(), sOriginURL);
+
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ }
+
+ { // Load as link
+ uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
+ uno::Reference<graphic::XGraphicProvider> xGraphicProvider;
+ xGraphicProvider.set(graphic::GraphicProvider::create(xContext), uno::UNO_SET_THROW);
+
+ auto aMediaProperties(comphelper::InitPropertySequence({
+ { "URL", uno::Any(aGraphicURL) },
+ { "LazyRead", uno::Any(false) },
+ { "LoadAsLink", uno::Any(true) },
+ }));
+
+ uno::Reference<graphic::XGraphic> xGraphic(
+ xGraphicProvider->queryGraphic(aMediaProperties));
+ CPPUNIT_ASSERT(xGraphic.is());
+ Graphic aGraphic(xGraphic);
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ uno::Reference<beans::XPropertySet> xGraphicDescriptor(xGraphic, uno::UNO_QUERY_THROW);
+
+ sal_Int8 nType;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("GraphicType") >>= nType);
+ CPPUNIT_ASSERT_EQUAL(graphic::GraphicType::PIXEL, nType);
+
+ awt::Size aSizePixel;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("SizePixel") >>= aSizePixel);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aSizePixel.Width);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aSizePixel.Height);
+
+ bool bLinked;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("Linked") >>= bLinked);
+ CPPUNIT_ASSERT_EQUAL(true, bLinked);
+
+ OUString sOriginURL;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("OriginURL") >>= sOriginURL);
+ CPPUNIT_ASSERT_EQUAL(aGraphicURL, sOriginURL);
+ }
+
+ { // Load lazy and as link
+ uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
+ uno::Reference<graphic::XGraphicProvider> xGraphicProvider;
+ xGraphicProvider.set(graphic::GraphicProvider::create(xContext), uno::UNO_SET_THROW);
+
+ auto aMediaProperties(comphelper::InitPropertySequence({
+ { "URL", uno::Any(aGraphicURL) },
+ { "LazyRead", uno::Any(true) },
+ { "LoadAsLink", uno::Any(true) },
+ }));
+
+ uno::Reference<graphic::XGraphic> xGraphic(
+ xGraphicProvider->queryGraphic(aMediaProperties));
+ CPPUNIT_ASSERT(xGraphic.is());
+ Graphic aGraphic(xGraphic);
+
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ uno::Reference<beans::XPropertySet> xGraphicDescriptor(xGraphic, uno::UNO_QUERY_THROW);
+
+ sal_Int8 nType;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("GraphicType") >>= nType);
+ CPPUNIT_ASSERT_EQUAL(graphic::GraphicType::PIXEL, nType);
+
+ awt::Size aSizePixel;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("SizePixel") >>= aSizePixel);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aSizePixel.Width);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aSizePixel.Height);
+
+ bool bLinked;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("Linked") >>= bLinked);
+ CPPUNIT_ASSERT_EQUAL(true, bLinked);
+
+ OUString sOriginURL;
+ CPPUNIT_ASSERT(xGraphicDescriptor->getPropertyValue("OriginURL") >>= sOriginURL);
+ CPPUNIT_ASSERT_EQUAL(aGraphicURL, sOriginURL);
+
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ }
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(XGraphicTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/api/data/TestGraphic.png b/vcl/qa/api/data/TestGraphic.png
new file mode 100644
index 0000000000..fe0c3c8ae4
--- /dev/null
+++ b/vcl/qa/api/data/TestGraphic.png
Binary files differ
diff --git a/vcl/qa/cppunit/BackendTest.cxx b/vcl/qa/cppunit/BackendTest.cxx
new file mode 100644
index 0000000000..b45f5ee33f
--- /dev/null
+++ b/vcl/qa/cppunit/BackendTest.cxx
@@ -0,0 +1,1644 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <tools/stream.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <svdata.hxx>
+#include <salinst.hxx>
+#include <salgdi.hxx>
+
+#include <test/outputdevice.hxx>
+
+// Run tests from visualbackendtest ('bin/run visualbackendtest').
+class BackendTest : public test::BootstrapFixture
+{
+ // if enabled - check the result images with:
+ // "xdg-open ./workdir/CppunitTest/vcl_backend_test.test.core/"
+ static constexpr const bool mbExportBitmap = false;
+
+ void exportImage(OUString const& rsFilename, BitmapEx const& rBitmapEx)
+ {
+ if (mbExportBitmap)
+ {
+ BitmapEx aBitmapEx(rBitmapEx);
+ aBitmapEx.Scale(Size(128, 128), BmpScaleFlag::Fast);
+ SvFileStream aStream(rsFilename, StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter::GetGraphicFilter().compressAsPNG(aBitmapEx, aStream);
+ }
+ }
+
+ void exportImage(OUString const& rsFilename, Bitmap const& rBitmap)
+ {
+ if (mbExportBitmap)
+ {
+ Bitmap aBitmap(rBitmap);
+ aBitmap.Scale(Size(128, 128), BmpScaleFlag::Fast);
+ SvFileStream aStream(rsFilename, StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter::GetGraphicFilter().compressAsPNG(BitmapEx(aBitmap), aStream);
+ }
+ }
+
+ void exportDevice(const OUString& filename, const VclPtr<VirtualDevice>& device)
+ {
+ if (mbExportBitmap)
+ {
+ BitmapEx aBitmapEx(device->GetBitmapEx(Point(0, 0), device->GetOutputSizePixel()));
+ SvFileStream aStream(filename, StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter::GetGraphicFilter().compressAsPNG(aBitmapEx, aStream);
+ }
+ }
+
+ bool is32bppSupported() { return ImplGetSVData()->mpDefInst->supportsBitmap32(); }
+
+public:
+ BackendTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ // We need to enable tests ONE BY ONE as they fail because of backend bugs
+ // it is still important to have the test defined so we know the issues
+ // exist and we need to fix them. Consistent behaviour of our backends
+ // is of highest priority.
+
+ static bool assertBackendNameNotEmpty(const OUString& name)
+ {
+ // This ensures that all backends return a valid name.
+ assert(!name.isEmpty());
+ (void)name;
+ return true;
+ }
+
+// Check whether tests should fail depending on which backend is used
+// (not all work). If you want to disable just a specific test
+// for a specific backend, use something like
+// 'if(SHOULD_ASSERT && aOutDevTest.getRenderBackendName() != "skia")'.
+// The macro uses opt-out rather than opt-in so that this doesn't "pass"
+// silently in case a new backend is added.
+#define SHOULD_ASSERT \
+ (assertBackendNameNotEmpty(aOutDevTest.getRenderBackendName()) \
+ && aOutDevTest.getRenderBackendName() != "qt5" \
+ && aOutDevTest.getRenderBackendName() != "qt5svp" \
+ && aOutDevTest.getRenderBackendName() != "gtk3svp" \
+ && aOutDevTest.getRenderBackendName() != "aqua" \
+ && aOutDevTest.getRenderBackendName() != "gen" \
+ && aOutDevTest.getRenderBackendName() != "genpsp" \
+ && aOutDevTest.getRenderBackendName() != "win")
+
+#ifdef MACOSX
+ static OUString getRenderBackendName(OutputDevice* device)
+ {
+ assert(device);
+ return device->GetGraphics()->getRenderBackendName();
+ }
+#endif
+
+ void testDrawRectWithRectangle()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ exportImage("01-01_rectangle_test-rectangle.png", aBitmap);
+
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectWithPixel()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPixel aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ exportImage("01-02_rectangle_test-pixel.png", aBitmap);
+
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectWithLine()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ exportImage("01-03_rectangle_test-line.png", aBitmap);
+
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectWithPolygon()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ exportImage("01-04_rectangle_test-polygon.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectWithPolyLine()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ exportImage("01-05_rectangle_test-polyline.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectWithPolyLineB2D()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ exportImage("01-06_rectangle_test-polyline_b2d.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectWithPolyPolygon()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ exportImage("01-07_rectangle_test-polypolygon.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectWithPolyPolygonB2D()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPolyPolygonB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangles(aBitmap);
+ exportImage("01-08_rectangle_test-polypolygon_b2d.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectAAWithRectangle()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(true);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangleAA(aBitmap);
+ exportImage("02-01_rectangle_AA_test-rectangle.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectAAWithPixel()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPixel aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(true);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangleAA(aBitmap);
+ exportImage("02-02_rectangle_AA_test-pixel.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectAAWithLine()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(true);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangleAA(aBitmap);
+ exportImage("02-03_rectangle_AA_test-line.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectAAWithPolygon()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(true);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangleAA(aBitmap);
+ exportImage("02-04_rectangle_AA_test-polygon.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectAAWithPolyLine()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(true);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangleAA(aBitmap);
+ exportImage("02-05_rectangle_AA_test-polyline.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectAAWithPolyLineB2D()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(true);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangleAA(aBitmap);
+ exportImage("02-06_rectangle_AA_test-polyline_b2d.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectAAWithPolyPolygon()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(true);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangleAA(aBitmap);
+ exportImage("02-07_rectangle_AA_test-polypolygon.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawRectAAWithPolyPolygonB2D()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPolyPolygonB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRectangle(true);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkRectangleAA(aBitmap);
+ exportImage("02-08_rectangle_AA_test-polypolygon_b2d.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawFilledRectWithRectangle()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, false);
+ exportImage("03-01_filled_rectangle_test-rectangle_noline.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ aBitmap = aOutDevTest.setupFilledRectangle(true);
+ eResult = vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, true);
+ exportImage("03-01_filled_rectangle_test-rectangle_line.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawFilledRectWithPolygon()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, false);
+ exportImage("03-02_filled_rectangle_test-polygon_noline.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ aBitmap = aOutDevTest.setupFilledRectangle(true);
+ eResult = vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, true);
+ exportImage("03-02_filled_rectangle_test-polygon_line.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawFilledRectWithPolyPolygon()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, false);
+ exportImage("03-03_filled_rectangle_test-polypolygon_noline.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ aBitmap = aOutDevTest.setupFilledRectangle(true);
+ eResult = vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, true);
+ exportImage("03-03_filled_rectangle_test-polypolygon_line.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawFilledRectWithPolyPolygon2D()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPolyPolygonB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledRectangle(false);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, false);
+ exportImage("03-04_filled_rectangle_test-polypolygon_b2d_noline.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ aBitmap = aOutDevTest.setupFilledRectangle(true);
+ eResult = vcl::test::OutputDeviceTestCommon::checkFilledRectangle(aBitmap, true);
+ exportImage("03-04_filled_rectangle_test-polypolygon_b2d_line.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawDiamondWithPolygon()
+ {
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDiamond();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkDiamond(aBitmap);
+ exportImage("04-01_diamond_test-polygon.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawDiamondWithLine()
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDiamond();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkDiamond(aBitmap);
+ exportImage("04-02_diamond_test-line.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawDiamondWithPolyline()
+ {
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDiamond();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkDiamond(aBitmap);
+ exportImage("04-03_diamond_test-polyline.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawDiamondWithPolylineB2D()
+ {
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDiamond();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkDiamond(aBitmap);
+ exportImage("04-04_diamond_test-polyline_b2d.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawInvertWithRectangle()
+ {
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupInvert_NONE();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkInvertRectangle(aBitmap);
+ exportImage("05-01_invert_test-rectangle.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawInvertN50WithRectangle()
+ {
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupInvert_N50();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkInvertN50Rectangle(aBitmap);
+ exportImage("05-02_invert_N50_test-rectangle.png", aBitmap);
+ if (SHOULD_ASSERT && aOutDevTest.getRenderBackendName() != "svp")
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawInvertTrackFrameWithRectangle()
+ {
+ vcl::test::OutputDeviceTestRect aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupInvert_TrackFrame();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkInvertTrackFrameRectangle(aBitmap);
+ exportImage("05-03_invert_TrackFrame_test-rectangle.png", aBitmap);
+ if (SHOULD_ASSERT && aOutDevTest.getRenderBackendName() != "svp")
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawBezierWithPolylineB2D()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupBezier();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkBezier(aBitmap);
+ exportImage("06-01_bezier_test-polyline_b2d.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawBezierAAWithPolylineB2D()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupAABezier();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkBezier(aBitmap);
+ exportImage("07-01_bezier_AA_test-polyline_b2d.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawBitmap24bpp()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawBitmap(vcl::PixelFormat::N24_BPP);
+ exportImage("08-01_bitmap_test_24bpp.png", aBitmap);
+ auto eResult = vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap(aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawTransformedBitmap24bpp()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawTransformedBitmap(vcl::PixelFormat::N24_BPP);
+ auto eResult = vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap(aBitmap);
+ exportImage("08-02_transformed_bitmap_test_24bpp.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testComplexDrawTransformedBitmap24bpp()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupComplexDrawTransformedBitmap(vcl::PixelFormat::N24_BPP);
+ auto eResult = vcl::test::OutputDeviceTestBitmap::checkComplexTransformedBitmap(aBitmap);
+ exportImage("08-03_transformed_bitmap_test_24bpp.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawBitmapExWithAlpha24bpp()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawBitmapExWithAlpha(vcl::PixelFormat::N24_BPP);
+ auto eResult = vcl::test::OutputDeviceTestBitmap::checkBitmapExWithAlpha(aBitmap);
+ exportImage("08-04_bitmapex_with_alpha_test_24bpp.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawMask24bpp()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawMask(vcl::PixelFormat::N24_BPP);
+ auto eResult = vcl::test::OutputDeviceTestBitmap::checkMask(aBitmap);
+ exportImage("08-05_mask_test_24bpp.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawBlend24bpp()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ BitmapEx aBitmapEx = aOutDevTest.setupDrawBlend(vcl::PixelFormat::N24_BPP);
+ auto eResult = vcl::test::OutputDeviceTestBitmap::checkBlend(aBitmapEx);
+ exportImage("08-06_blend_test_24bpp.png", aBitmapEx);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawBitmap32bpp()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawBitmap(vcl::PixelFormat::N32_BPP);
+ exportImage("09-01_bitmap_test_32bpp.png", aBitmap);
+ auto eResult = vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap(aBitmap);
+ if (SHOULD_ASSERT && is32bppSupported())
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawTransformedBitmap32bpp()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawTransformedBitmap(vcl::PixelFormat::N32_BPP);
+ auto eResult = vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap(aBitmap);
+ exportImage("09-02_transformed_bitmap_test_32bpp.png", aBitmap);
+ if (SHOULD_ASSERT && is32bppSupported())
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawBitmapExWithAlpha32bpp()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawBitmapExWithAlpha(vcl::PixelFormat::N32_BPP);
+ auto eResult = vcl::test::OutputDeviceTestBitmap::checkBitmapExWithAlpha(aBitmap);
+ exportImage("09-03_bitmapex_with_alpha_test_32bpp.png", aBitmap);
+ if (SHOULD_ASSERT && is32bppSupported())
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawMask32bpp()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawMask(vcl::PixelFormat::N32_BPP);
+ auto eResult = vcl::test::OutputDeviceTestBitmap::checkMask(aBitmap);
+ exportImage("09-04_mask_test_32bpp.png", aBitmap);
+ if (SHOULD_ASSERT && is32bppSupported())
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawBlend32bpp()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ BitmapEx aBitmapEx = aOutDevTest.setupDrawBlend(vcl::PixelFormat::N32_BPP);
+ auto eResult = vcl::test::OutputDeviceTestBitmap::checkBlend(aBitmapEx);
+ exportImage("09-05_blend_test_32bpp.png", aBitmapEx);
+ if (SHOULD_ASSERT && is32bppSupported())
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawBitmap8bppGreyScale()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawBitmap(vcl::PixelFormat::N8_BPP, true);
+ exportImage("010-01_bitmap_test_8bpp_greyscale.png", aBitmap);
+ auto eResult
+ = vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap8bppGreyScale(aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawTransformedBitmap8bppGreyScale()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestBitmap aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawTransformedBitmap(vcl::PixelFormat::N8_BPP, true);
+ auto eResult
+ = vcl::test::OutputDeviceTestBitmap::checkTransformedBitmap8bppGreyScale(aBitmap);
+ exportImage("010-02_transformed_bitmap_test_8bpp_greyscale.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawXor()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestAnotherOutDev aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupXOR();
+ auto eResult = vcl::test::OutputDeviceTestAnotherOutDev::checkXOR(aBitmap);
+ exportImage("08-06_xor_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawTransformedBitmapExAlpha()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ ScopedVclPtrInstance<VirtualDevice> device;
+#ifdef MACOSX
+ // TODO: This unit test is not executed for macOS unless bitmap scaling is implemented
+ if (getRenderBackendName(device) == "aqua")
+ return;
+#endif
+ device->SetOutputSizePixel(Size(16, 16));
+ device->SetBackground(Wallpaper(COL_WHITE));
+ device->Erase();
+ Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);
+ {
+ // Fill the top left quarter with black.
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(COL_WHITE);
+ for (int i = 0; i < 8; ++i)
+ for (int j = 0; j < 8; ++j)
+ pWriteAccess->SetPixel(j, i, COL_BLACK);
+ }
+ BitmapEx aBitmapEx(aBitmap);
+ basegfx::B2DHomMatrix aMatrix;
+ // Draw with no transformation, only alpha change.
+ aMatrix.scale(16, 16);
+ device->DrawTransformedBitmapEx(aMatrix, aBitmapEx, 0.5);
+ BitmapEx result = device->GetBitmapEx(Point(0, 0), Size(16, 16));
+ CPPUNIT_ASSERT_EQUAL(Color(0x80, 0x80, 0x80), result.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, result.GetPixelColor(15, 15));
+ // Draw rotated and move to the bottom-left corner.
+ device->Erase();
+ aMatrix.identity();
+ aMatrix.scale(16, 16);
+ aMatrix.rotate(M_PI / 2);
+ aMatrix.translate(8, 8);
+ device->DrawTransformedBitmapEx(aMatrix, aBitmapEx, 0.5);
+ result = device->GetBitmap(Point(0, 0), Size(16, 16));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, result.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(Color(0x80, 0x80, 0x80), result.GetPixelColor(0, 15));
+ }
+
+ void testClipRectangle()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestClip aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupClipRectangle();
+ auto eResult = vcl::test::OutputDeviceTestClip::checkClip(aBitmap);
+ exportImage("09-01_clip_rectangle_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testClipPolygon()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestClip aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupClipPolygon();
+ auto eResult = vcl::test::OutputDeviceTestClip::checkClip(aBitmap);
+ exportImage("09-02_clip_polygon_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testClipPolyPolygon()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestClip aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupClipPolyPolygon();
+ auto eResult = vcl::test::OutputDeviceTestClip::checkClip(aBitmap);
+ exportImage("09-03_clip_polypolygon_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testClipB2DPolyPolygon()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestClip aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupClipB2DPolyPolygon();
+ auto eResult = vcl::test::OutputDeviceTestClip::checkClip(aBitmap);
+ exportImage("09-04_clip_b2dpolypolygon_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawOutDev()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestAnotherOutDev aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawOutDev();
+ auto eResult = vcl::test::OutputDeviceTestAnotherOutDev::checkDrawOutDev(aBitmap);
+ exportImage("10-01_draw_out_dev_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawOutDevScaledClipped()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestAnotherOutDev aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawOutDevScaledClipped();
+ auto eResult
+ = vcl::test::OutputDeviceTestAnotherOutDev::checkDrawOutDevScaledClipped(aBitmap);
+ exportImage("10-02_draw_out_dev_scaled_clipped_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawOutDevSelf()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestAnotherOutDev aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDrawOutDevSelf();
+ auto eResult = vcl::test::OutputDeviceTestAnotherOutDev::checkDrawOutDevSelf(aBitmap);
+ exportImage("10-03_draw_out_dev_self_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDashedLine()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDashedLine();
+ auto eResult = vcl::test::OutputDeviceTestLine::checkDashedLine(aBitmap);
+ exportImage("11-01_dashed_line_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testErase()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ {
+ // Create normal virtual device (no alpha).
+ ScopedVclPtr<VirtualDevice> device
+ = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(10, 10));
+ // Erase with white, check it's white.
+ device->SetBackground(Wallpaper(COL_WHITE));
+ device->Erase();
+ exportDevice("12-01_erase.png", device);
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(9, 9)));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(5, 5)));
+ // Erase with black, check it's black.
+ device->SetBackground(Wallpaper(COL_BLACK));
+ device->Erase();
+ exportDevice("12-02_erase.png", device);
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, device->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, device->GetPixel(Point(9, 9)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, device->GetPixel(Point(5, 5)));
+ // Erase with cyan, check it's cyan.
+ device->SetBackground(Wallpaper(COL_CYAN));
+ device->Erase();
+ exportDevice("12-03_erase.png", device);
+ CPPUNIT_ASSERT_EQUAL(COL_CYAN, device->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_CYAN, device->GetPixel(Point(9, 9)));
+ CPPUNIT_ASSERT_EQUAL(COL_CYAN, device->GetPixel(Point(5, 5)));
+ }
+ {
+ // Create virtual device with alpha.
+ ScopedVclPtr<VirtualDevice> device
+ = VclPtr<VirtualDevice>::Create(DeviceFormat::WITH_ALPHA);
+ device->SetOutputSizePixel(Size(10, 10));
+ // Erase with white, check it's white.
+ device->SetBackground(Wallpaper(COL_WHITE));
+ device->Erase();
+ exportDevice("12-04_erase.png", device);
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(9, 9)));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(5, 5)));
+ // Erase with black, check it's black.
+ device->SetBackground(Wallpaper(COL_BLACK));
+ device->Erase();
+ exportDevice("12-05_erase.png", device);
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, device->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, device->GetPixel(Point(9, 9)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, device->GetPixel(Point(5, 5)));
+ // Erase with cyan, check it's cyan.
+ device->SetBackground(Wallpaper(COL_CYAN));
+ device->Erase();
+ exportDevice("12-06_erase.png", device);
+ CPPUNIT_ASSERT_EQUAL(COL_CYAN, device->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_CYAN, device->GetPixel(Point(9, 9)));
+ CPPUNIT_ASSERT_EQUAL(COL_CYAN, device->GetPixel(Point(5, 5)));
+ // Erase with transparent, check it's transparent.
+ device->SetBackground(Wallpaper(COL_TRANSPARENT));
+ device->Erase();
+ exportDevice("12-07_erase.png", device);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), device->GetPixel(Point(0, 0)).GetAlpha());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), device->GetPixel(Point(9, 9)).GetAlpha());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), device->GetPixel(Point(5, 5)).GetAlpha());
+ }
+ }
+
+ void testLinearGradient()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLinearGradient();
+ auto eResult = vcl::test::OutputDeviceTestGradient::checkLinearGradient(aBitmap);
+ exportImage("13-01_linear_gradient_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testLinearGradientAngled()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLinearGradientAngled();
+ auto eResult = vcl::test::OutputDeviceTestGradient::checkLinearGradientAngled(aBitmap);
+ exportImage("13-02_linear_gradient_angled_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testLinearGradientBorder()
+ {
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLinearGradientBorder();
+ auto eResult = vcl::test::OutputDeviceTestGradient::checkLinearGradientBorder(aBitmap);
+ exportImage("13-03_linear_gradient_border_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testLinearGradientIntensity()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLinearGradientIntensity();
+ auto eResult = vcl::test::OutputDeviceTestGradient::checkLinearGradientIntensity(aBitmap);
+ exportImage("13-04_linear_gradient_intensity_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testLinearGradientSteps()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLinearGradientSteps();
+ auto eResult = vcl::test::OutputDeviceTestGradient::checkLinearGradientSteps(aBitmap);
+ exportImage("13-05_linear_gradient_steps_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testAxialGradient()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupAxialGradient();
+ auto eResult = vcl::test::OutputDeviceTestGradient::checkAxialGradient(aBitmap);
+ exportImage("13-06_axial_gradient_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testRadialGradient()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRadialGradient();
+ auto eResult = vcl::test::OutputDeviceTestGradient::checkRadialGradient(aBitmap);
+ exportImage("13-07_radial_gradient_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testRadialGradientOfs()
+ {
+ vcl::test::OutputDeviceTestGradient aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupRadialGradientOfs();
+ auto eResult = vcl::test::OutputDeviceTestGradient::checkRadialGradientOfs(aBitmap);
+ exportImage("13-08_radial_gradient_ofs_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testLineJoinBevel()
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineJoinBevel();
+ auto eResult = vcl::test::OutputDeviceTestLine::checkLineJoinBevel(aBitmap);
+ exportImage("14-01_line_join_bevel_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testLineJoinRound()
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineJoinRound();
+ auto eResult = vcl::test::OutputDeviceTestLine::checkLineJoinRound(aBitmap);
+ exportImage("14-02_line_join_round_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testLineJoinMiter()
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineJoinMiter();
+ auto eResult = vcl::test::OutputDeviceTestLine::checkLineJoinMiter(aBitmap);
+ exportImage("14-03_line_join_miter_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testLineJoinNone()
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineJoinNone();
+ auto eResult = vcl::test::OutputDeviceTestLine::checkLineJoinNone(aBitmap);
+ exportImage("14-04_line_join_none_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testLineCapRound()
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineCapRound();
+ auto eResult = vcl::test::OutputDeviceTestLine::checkLineCapRound(aBitmap);
+ exportImage("14-05_line_cap_round_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testLineCapSquare()
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineCapSquare();
+ auto eResult = vcl::test::OutputDeviceTestLine::checkLineCapSquare(aBitmap);
+ exportImage("14-06_line_cap_square_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testLineCapButt()
+ {
+ vcl::test::OutputDeviceTestLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupLineCapButt();
+ auto eResult = vcl::test::OutputDeviceTestLine::checkLineCapButt(aBitmap);
+ exportImage("14-07_line_cap_butt_test.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawDropShapeWithPolyline()
+ {
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDropShape();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkDropShape(aBitmap);
+ exportImage("15-01_drop_shape_test-polyline.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawDropShapeAAWithPolyline()
+ {
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupAADropShape();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkDropShape(aBitmap, true);
+ exportImage("15-02_drop_shape_AA_test-polyline.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawDropShapeWithPolygon()
+ {
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupDropShape();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkDropShape(aBitmap);
+ exportImage("16-01_drop_shape_test-polygon.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawDropShapeAAWithPolygon()
+ {
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupAADropShape();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkDropShape(aBitmap, true);
+ exportImage("16-02_drop_shape_AA_test-polygon.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawHaflEllipseWithPolyLine()
+ {
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupHalfEllipse();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkHalfEllipse(aBitmap);
+ exportImage("17-01_half_ellipse_test-polyline.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawHaflEllipseAAWithPolyLine()
+ {
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupHalfEllipse(true);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkHalfEllipse(aBitmap, true);
+ exportImage("17-02_half_ellipse_AA_test-polyline.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawHaflEllipseWithPolyLineB2D()
+ {
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupHalfEllipse();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkHalfEllipse(aBitmap);
+ exportImage("17-03_half_ellipse_test-polylineb2d.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawHaflEllipseAAWithPolyLineB2D()
+ {
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupHalfEllipse(true);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkHalfEllipse(aBitmap, true);
+ exportImage("17-03_half_ellipse_AA_test-polylineb2d.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawHaflEllipseWithPolygon()
+ {
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupHalfEllipse();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkHalfEllipse(aBitmap);
+ exportImage("17-04_half_ellipse_test-polygon.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawHaflEllipseAAWithPolygon()
+ {
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupHalfEllipse(true);
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkHalfEllipse(aBitmap, true);
+ exportImage("17-05_half_ellipse_AA_test-polygon.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testClosedBezierWithPolyline()
+ {
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupClosedBezier();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkClosedBezier(aBitmap);
+ exportImage("18-01_closed_bezier-polyline.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testClosedBezierWithPolygon()
+ {
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupClosedBezier();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkClosedBezier(aBitmap);
+ exportImage("18-02_closed_bezier-polygon.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testFilledAsymmetricalDropShape()
+ {
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupFilledAsymmetricalDropShape();
+ vcl::test::TestResult eResult
+ = vcl::test::OutputDeviceTestLine::checkFilledAsymmetricalDropShape(aBitmap);
+ exportImage("19-01_filled_drop_shape-polygon.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ // Test SalGraphics::blendBitmap() and blendAlphaBitmap() calls.
+ void testDrawBlendExtended()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ // Create virtual device with alpha.
+ ScopedVclPtr<VirtualDevice> device
+ = VclPtr<VirtualDevice>::Create(DeviceFormat::WITH_ALPHA);
+#ifdef MACOSX
+ // TODO: This unit test is not executed for macOS unless bitmap scaling is implemented
+ if (getRenderBackendName(device) == "aqua")
+ return;
+#endif
+ device->SetOutputSizePixel(Size(10, 10));
+ device->SetBackground(Wallpaper(COL_WHITE));
+ device->Erase();
+ Bitmap bitmap(Size(5, 5), vcl::PixelFormat::N24_BPP);
+ bitmap.Erase(COL_BLUE);
+ // No alpha, this will actually call SalGraphics::DrawBitmap(), but still check
+ // the alpha of the device is handled correctly.
+ device->DrawBitmapEx(Point(2, 2), BitmapEx(bitmap));
+ exportDevice("blend_extended_01.png", device);
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(2, 2)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(6, 6)));
+ // Check pixels outside of the bitmap aren't affected.
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(1, 1)));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(7, 7)));
+
+ device->Erase();
+ AlphaMask alpha(Size(5, 5));
+ alpha.Erase(0); // opaque
+ device->DrawBitmapEx(Point(2, 2), BitmapEx(bitmap, alpha));
+ exportDevice("blend_extended_02.png", device);
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(2, 2)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(6, 6)));
+
+ device->Erase();
+ alpha.Erase(255); // transparent
+ device->DrawBitmapEx(Point(2, 2), BitmapEx(bitmap, alpha));
+ exportDevice("blend_extended_03.png", device);
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(2, 2)));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(6, 6)));
+
+ // Skia optimizes bitmaps that have just been Erase()-ed, so explicitly
+ // set some pixels in the alpha to avoid this and have an actual bitmap
+ // as the alpha mask.
+ device->Erase();
+ alpha.Erase(255); // transparent
+ BitmapScopedWriteAccess alphaWrite(alpha);
+ alphaWrite->SetPixelIndex(0, 0, 255); // opaque
+ alphaWrite.reset();
+ device->DrawBitmapEx(Point(2, 2), BitmapEx(bitmap, alpha));
+ exportDevice("blend_extended_04.png", device);
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(2, 2)));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(6, 6)));
+ }
+
+ void testDrawAlphaBitmapMirrored()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ // Normal virtual device.
+ ScopedVclPtr<VirtualDevice> device
+ = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+#ifdef MACOSX
+ // TODO: This unit test is not executed for macOS unless bitmap scaling is implemented
+ if (getRenderBackendName(device) == "aqua")
+ return;
+#endif
+ // Virtual device with alpha.
+ ScopedVclPtr<VirtualDevice> alphaDevice
+ = VclPtr<VirtualDevice>::Create(DeviceFormat::WITH_ALPHA);
+ device->SetOutputSizePixel(Size(20, 20));
+ device->SetBackground(Wallpaper(COL_BLACK));
+ device->Erase();
+ alphaDevice->SetOutputSizePixel(Size(20, 20));
+ alphaDevice->SetBackground(Wallpaper(COL_BLACK));
+ alphaDevice->Erase();
+ Bitmap bitmap(Size(4, 4), vcl::PixelFormat::N24_BPP);
+ AlphaMask alpha(Size(4, 4));
+ bitmap.Erase(COL_LIGHTBLUE);
+ {
+ BitmapScopedWriteAccess writeAccess(bitmap);
+ writeAccess->SetPixel(3, 3, COL_LIGHTRED);
+ }
+ // alpha 127 will make COL_LIGHTRED -> COL_RED and the same for blue
+ alpha.Erase(127);
+ // Normal device.
+ device->DrawBitmapEx(Point(5, 5), Size(-4, -4), BitmapEx(bitmap));
+ device->DrawBitmapEx(Point(15, 15), Size(4, 4), BitmapEx(bitmap));
+ exportDevice("draw_alpha_bitmap_mirrored_01.png", device);
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, device->GetPixel(Point(18, 18)));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, device->GetPixel(Point(17, 18)));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, device->GetPixel(Point(2, 2)));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, device->GetPixel(Point(3, 2)));
+ device->Erase();
+ device->DrawBitmapEx(Point(5, 5), Size(-4, -4), BitmapEx(bitmap, alpha));
+ device->DrawBitmapEx(Point(15, 15), Size(4, 4), BitmapEx(bitmap, alpha));
+ exportDevice("draw_alpha_bitmap_mirrored_02.png", device);
+ CPPUNIT_ASSERT_EQUAL(COL_RED, device->GetPixel(Point(18, 18)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(17, 18)));
+ CPPUNIT_ASSERT_EQUAL(COL_RED, device->GetPixel(Point(2, 2)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(3, 2)));
+ device->Erase();
+ // Now with alpha device.
+ alphaDevice->DrawBitmapEx(Point(5, 5), Size(-4, -4), BitmapEx(bitmap));
+ alphaDevice->DrawBitmapEx(Point(15, 15), Size(4, 4), BitmapEx(bitmap));
+ exportDevice("draw_alpha_bitmap_mirrored_03.png", alphaDevice);
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, alphaDevice->GetPixel(Point(18, 18)));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, alphaDevice->GetPixel(Point(17, 18)));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, alphaDevice->GetPixel(Point(2, 2)));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, alphaDevice->GetPixel(Point(3, 2)));
+ alphaDevice->Erase();
+ alphaDevice->DrawBitmapEx(Point(5, 5), Size(-4, -4), BitmapEx(bitmap, alpha));
+ alphaDevice->DrawBitmapEx(Point(15, 15), Size(4, 4), BitmapEx(bitmap, alpha));
+ exportDevice("draw_alpha_bitmap_mirrored_04.png", alphaDevice);
+ CPPUNIT_ASSERT_EQUAL(COL_RED, alphaDevice->GetPixel(Point(18, 18)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, alphaDevice->GetPixel(Point(17, 18)));
+ CPPUNIT_ASSERT_EQUAL(COL_RED, alphaDevice->GetPixel(Point(2, 2)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, alphaDevice->GetPixel(Point(3, 2)));
+ alphaDevice->Erase();
+ }
+
+ void testDrawingText()
+ {
+#ifndef _WIN32
+ vcl::test::OutputDeviceTestText aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupTextBitmap();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkTextLocation(aBitmap);
+ exportImage("17-01_test_text_Drawing.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+#endif
+ }
+
+ void testEvenOddRuleInIntersectionRectangles()
+ {
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupIntersectingRectangles();
+ auto eResult
+ = vcl::test::OutputDeviceTestCommon::checkEvenOddRuleInIntersectingRecs(aBitmap);
+ exportImage("18-01_test_Even-Odd-rule_intersecting_Recs.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawOpenPolygonWithPolyLine()
+ {
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupOpenPolygon();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkOpenPolygon(aBitmap);
+ exportImage("19-01_open_polygon-polyline.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawOpenPolygonWithPolyLineB2D()
+ {
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupOpenPolygon();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkOpenPolygon(aBitmap);
+ exportImage("19-02_open_polygon-polyline_b2d.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawOpenPolygonWithPolygon()
+ {
+ vcl::test::OutputDeviceTestPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupOpenPolygon();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkOpenPolygon(aBitmap);
+ exportImage("19-03_open_polygon-polygon.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawOpenPolygonWithPolyPolygon()
+ {
+ vcl::test::OutputDeviceTestPolyPolygon aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupOpenPolygon();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkOpenPolygon(aBitmap);
+ exportImage("19-04_open_polygon-polypolygon.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawOpenPolygonWithPolyPolygonB2D()
+ {
+ vcl::test::OutputDeviceTestPolyPolygonB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupOpenPolygon();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkOpenPolygon(aBitmap);
+ exportImage("19-04_open_polygon-polypolygon_b2d.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawOpenBezierWithPolyLine()
+ {
+ vcl::test::OutputDeviceTestPolyLine aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupOpenBezier();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkOpenBezier(aBitmap);
+ exportImage("19-01_open_bezier-polyline.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawOpenBezierWithPolyLineB2D()
+ {
+ vcl::test::OutputDeviceTestPolyLineB2D aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupOpenBezier();
+ auto eResult = vcl::test::OutputDeviceTestCommon::checkOpenBezier(aBitmap);
+ exportImage("19-01_open_bezier-polyline_b2d.png", aBitmap);
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testTdf124848()
+ {
+ ScopedVclPtr<VirtualDevice> device
+ = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+#ifdef MACOSX
+ // TODO: This unit test is not executed for macOS unless bitmap scaling is implemented
+ if (getRenderBackendName(device) == "aqua")
+ return;
+#endif
+ device->SetOutputSizePixel(Size(100, 100));
+ device->SetBackground(Wallpaper(COL_WHITE));
+ device->Erase();
+ device->SetAntialiasing(AntialiasingFlags::Enable);
+ device->SetLineColor(COL_BLACK);
+ basegfx::B2DHomMatrix matrix;
+ // DrawPolyLine() would apply the whole matrix to the line width, making it negative
+ // in case of a larger rotation.
+ matrix.rotate(M_PI); //180 degrees
+ matrix.translate(100, 100);
+ CPPUNIT_ASSERT(device->DrawPolyLineDirect(matrix,
+ basegfx::B2DPolygon{ { 50, 50 }, { 50, 100 } },
+ 100, 0, nullptr, basegfx::B2DLineJoin::Miter));
+ exportDevice("tdf124848-1.png", device);
+ // 100px wide line should fill the entire width of the upper half
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, device->GetPixel(Point(2, 2)));
+
+ // Also check hairline.
+ device->Erase();
+ CPPUNIT_ASSERT(device->DrawPolyLineDirect(matrix,
+ basegfx::B2DPolygon{ { 50, 50 }, { 50, 100 } }, 0,
+ 0, nullptr, basegfx::B2DLineJoin::Miter));
+ exportDevice("tdf124848-2.png", device);
+ // 1px wide
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, device->GetPixel(Point(50, 20)));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(49, 20)));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(51, 20)));
+ }
+
+ void testTdf136171()
+ {
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ // Create virtual device with alpha.
+ ScopedVclPtr<VirtualDevice> device
+ = VclPtr<VirtualDevice>::Create(DeviceFormat::WITH_ALPHA);
+ device->SetOutputSizePixel(Size(10, 10));
+ device->SetBackground(Wallpaper(COL_WHITE));
+ device->Erase();
+ Bitmap bitmap(Size(10, 10), vcl::PixelFormat::N24_BPP);
+ bitmap.Erase(COL_BLUE);
+ basegfx::B2DHomMatrix matrix;
+ matrix.scale(bitmap.GetSizePixel().Width(),
+ bitmap.GetSizePixel().Height()); // draw as 10x10
+ // Draw a blue bitmap to the device. The bug was that there was no alpha, but OutputDevice::DrawTransformBitmapExDirect()
+ // supplied a fully opaque alpha done with Erase() on the alpha bitmap, and Skia backend didn't handle such alpha correctly.
+ device->DrawTransformedBitmapEx(matrix, BitmapEx(bitmap));
+ exportDevice("tdf136171.png", device);
+ // The whole virtual device content now should be blue.
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(9, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(0, 9)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(9, 9)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(4, 4)));
+ }
+
+ void testTdf145811()
+ {
+ // VCL may call copyArea()/copyBits() of backends even with coordinates partially
+ // outside of the device, so try various copying like that.
+ ScopedVclPtr<VirtualDevice> device1
+ = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device1->SetOutputSizePixel(Size(100, 100));
+ device1->SetBackground(Wallpaper(COL_YELLOW));
+ device1->Erase();
+ device1->SetLineColor(COL_BLUE);
+ device1->DrawPixel(Point(0, 0), COL_BLUE);
+ device1->DrawPixel(Point(99, 99), COL_BLUE);
+#ifdef MACOSX
+ // TODO: This unit test is not executed for macOS unless bitmap scaling is implemented
+ if (getRenderBackendName(device1) == "aqua")
+ return;
+#endif
+
+ // Plain 1:1 copy device1->device2.
+ ScopedVclPtr<VirtualDevice> device2
+ = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device2->SetOutputSizePixel(Size(100, 100));
+ device2->DrawOutDev(Point(0, 0), Size(100, 100), Point(0, 0), Size(100, 100), *device1);
+ exportDevice("tdf145811-1.png", device2);
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device2->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(1, 1)));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(98, 98)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device2->GetPixel(Point(99, 99)));
+
+ // For the rest call directly SalGraphics, because OutputDevice does range checking,
+ // but other code may call copyArea()/copyBits() of SalGraphics directly without range checking.
+ SalGraphics* graphics1 = device1->GetGraphics();
+ SalGraphics* graphics2 = device2->GetGraphics();
+
+ device2->DrawOutDev(Point(0, 0), Size(100, 100), Point(0, 0), Size(100, 100), *device1);
+ // Copy device1->device2 offset by 10,10.
+ graphics2->CopyBits(SalTwoRect(0, 0, 100, 100, 10, 10, 100, 100), *graphics1, *device2,
+ *device1);
+ exportDevice("tdf145811-2.png", device2);
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device2->GetPixel(Point(0, 0))); // unmodified
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(9, 9)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device2->GetPixel(Point(10, 10)));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(11, 11)));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(99, 99)));
+
+ device2->DrawOutDev(Point(0, 0), Size(100, 100), Point(0, 0), Size(100, 100), *device1);
+ // Copy area of device2 offset by 10,10.
+ graphics2->CopyArea(10, 10, 0, 0, 100, 100, *device1);
+ exportDevice("tdf145811-3.png", device2);
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device2->GetPixel(Point(0, 0))); // unmodified
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(9, 9)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device2->GetPixel(Point(10, 10)));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(11, 11)));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(99, 99)));
+
+ device2->DrawOutDev(Point(0, 0), Size(100, 100), Point(0, 0), Size(100, 100), *device1);
+ // Copy device1->device2 offset by -20,-20.
+ graphics2->CopyBits(SalTwoRect(0, 0, 100, 100, -20, -20, 100, 100), *graphics1, *device2,
+ *device1);
+ exportDevice("tdf145811-4.png", device2);
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(78, 78)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device2->GetPixel(Point(79, 79)));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(80, 80)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device2->GetPixel(Point(99, 99))); // unmodified
+
+ device2->DrawOutDev(Point(0, 0), Size(100, 100), Point(0, 0), Size(100, 100), *device1);
+ // Copy area of device2 offset by -20,-20.
+ graphics2->CopyArea(-20, -20, 0, 0, 100, 100, *device1);
+ exportDevice("tdf145811-5.png", device2);
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(78, 78)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device2->GetPixel(Point(79, 79)));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(80, 80)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device2->GetPixel(Point(99, 99))); // unmodified
+
+ device2->DrawOutDev(Point(0, 0), Size(100, 100), Point(0, 0), Size(100, 100), *device1);
+ // Copy device1->device2 offset by -10,-10 starting from -20,-20 at 150x150 size
+ // (i.e. outside in all directions).
+ graphics2->CopyBits(SalTwoRect(-20, -20, 150, 150, -30, -30, 150, 150), *graphics1,
+ *device2, *device1);
+ exportDevice("tdf145811-6.png", device2);
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(88, 88)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device2->GetPixel(Point(89, 89)));
+ // (90,90) and further originate from outside and may be garbage.
+
+ device2->DrawOutDev(Point(0, 0), Size(100, 100), Point(0, 0), Size(100, 100), *device1);
+ // Copy area of device2 offset by -10,-10 starting from -20,-20 at 150x150 size
+ // (i.e. outside in all directions).
+ graphics2->CopyArea(-30, -30, -20, -20, 150, 150, *device1);
+ exportDevice("tdf145811-7.png", device2);
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, device2->GetPixel(Point(88, 88)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, device2->GetPixel(Point(89, 89)));
+ // (90,90) and further originate from outside and may be garbage.
+ }
+
+ CPPUNIT_TEST_SUITE(BackendTest);
+ CPPUNIT_TEST(testDrawRectWithRectangle);
+ CPPUNIT_TEST(testDrawRectWithPixel);
+ CPPUNIT_TEST(testDrawRectWithLine);
+ CPPUNIT_TEST(testDrawRectWithPolygon);
+ CPPUNIT_TEST(testDrawRectWithPolyLine);
+ CPPUNIT_TEST(testDrawRectWithPolyLineB2D);
+ CPPUNIT_TEST(testDrawRectWithPolyPolygon);
+ CPPUNIT_TEST(testDrawRectWithPolyPolygonB2D);
+
+ CPPUNIT_TEST(testDrawRectAAWithRectangle);
+ CPPUNIT_TEST(testDrawRectAAWithPixel);
+ CPPUNIT_TEST(testDrawRectAAWithLine);
+ CPPUNIT_TEST(testDrawRectAAWithPolygon);
+ CPPUNIT_TEST(testDrawRectAAWithPolyLine);
+ CPPUNIT_TEST(testDrawRectAAWithPolyLineB2D);
+ CPPUNIT_TEST(testDrawRectAAWithPolyPolygon);
+ CPPUNIT_TEST(testDrawRectAAWithPolyPolygonB2D);
+
+ CPPUNIT_TEST(testDrawFilledRectWithRectangle);
+ CPPUNIT_TEST(testDrawFilledRectWithPolygon);
+ CPPUNIT_TEST(testDrawFilledRectWithPolyPolygon);
+ CPPUNIT_TEST(testDrawFilledRectWithPolyPolygon2D);
+
+ CPPUNIT_TEST(testDrawDiamondWithPolygon);
+ CPPUNIT_TEST(testDrawDiamondWithLine);
+ CPPUNIT_TEST(testDrawDiamondWithPolyline);
+ CPPUNIT_TEST(testDrawDiamondWithPolylineB2D);
+
+ CPPUNIT_TEST(testDrawInvertWithRectangle);
+ CPPUNIT_TEST(testDrawInvertN50WithRectangle);
+ CPPUNIT_TEST(testDrawInvertTrackFrameWithRectangle);
+
+ CPPUNIT_TEST(testDrawBezierWithPolylineB2D);
+ CPPUNIT_TEST(testDrawBezierAAWithPolylineB2D);
+
+ CPPUNIT_TEST(testDrawDropShapeWithPolyline);
+ CPPUNIT_TEST(testDrawDropShapeAAWithPolyline);
+
+ CPPUNIT_TEST(testDrawDropShapeWithPolygon);
+ CPPUNIT_TEST(testDrawDropShapeAAWithPolygon);
+
+ CPPUNIT_TEST(testDrawHaflEllipseWithPolyLine);
+ CPPUNIT_TEST(testDrawHaflEllipseAAWithPolyLine);
+ CPPUNIT_TEST(testDrawHaflEllipseWithPolyLineB2D);
+ CPPUNIT_TEST(testDrawHaflEllipseAAWithPolyLineB2D);
+ CPPUNIT_TEST(testDrawHaflEllipseWithPolygon);
+ CPPUNIT_TEST(testDrawHaflEllipseAAWithPolygon);
+
+ CPPUNIT_TEST(testClosedBezierWithPolyline);
+ CPPUNIT_TEST(testClosedBezierWithPolygon);
+
+ CPPUNIT_TEST(testFilledAsymmetricalDropShape);
+
+ CPPUNIT_TEST(testDrawBitmap24bpp);
+ CPPUNIT_TEST(testDrawTransformedBitmap24bpp);
+ CPPUNIT_TEST(testComplexDrawTransformedBitmap24bpp);
+ CPPUNIT_TEST(testDrawBitmapExWithAlpha24bpp);
+ CPPUNIT_TEST(testDrawMask24bpp);
+ CPPUNIT_TEST(testDrawBlend24bpp);
+
+ CPPUNIT_TEST(testDrawXor);
+
+ CPPUNIT_TEST(testDrawBitmap32bpp);
+ CPPUNIT_TEST(testDrawTransformedBitmap32bpp);
+ CPPUNIT_TEST(testDrawBitmapExWithAlpha32bpp);
+ CPPUNIT_TEST(testDrawMask32bpp);
+ CPPUNIT_TEST(testDrawBlend32bpp);
+
+ CPPUNIT_TEST(testDrawTransformedBitmap8bppGreyScale);
+ CPPUNIT_TEST(testDrawBitmap8bppGreyScale);
+
+ CPPUNIT_TEST(testDrawTransformedBitmapExAlpha);
+
+ CPPUNIT_TEST(testClipRectangle);
+ CPPUNIT_TEST(testClipPolygon);
+ CPPUNIT_TEST(testClipPolyPolygon);
+ CPPUNIT_TEST(testClipB2DPolyPolygon);
+
+ CPPUNIT_TEST(testDrawOutDev);
+ CPPUNIT_TEST(testDrawOutDevScaledClipped);
+ CPPUNIT_TEST(testDrawOutDevSelf);
+
+ CPPUNIT_TEST(testDashedLine);
+
+ CPPUNIT_TEST(testErase);
+
+ CPPUNIT_TEST(testLinearGradient);
+ CPPUNIT_TEST(testLinearGradientAngled);
+ CPPUNIT_TEST(testLinearGradientBorder);
+ CPPUNIT_TEST(testLinearGradientIntensity);
+ CPPUNIT_TEST(testLinearGradientSteps);
+ CPPUNIT_TEST(testAxialGradient);
+ CPPUNIT_TEST(testRadialGradient);
+ CPPUNIT_TEST(testRadialGradientOfs);
+
+ CPPUNIT_TEST(testLineCapRound);
+ CPPUNIT_TEST(testLineCapSquare);
+ CPPUNIT_TEST(testLineCapButt);
+ CPPUNIT_TEST(testLineJoinBevel);
+ CPPUNIT_TEST(testLineJoinRound);
+ CPPUNIT_TEST(testLineJoinMiter);
+ CPPUNIT_TEST(testLineJoinNone);
+
+ CPPUNIT_TEST(testDrawBlendExtended);
+ CPPUNIT_TEST(testDrawAlphaBitmapMirrored);
+
+ CPPUNIT_TEST(testDrawingText);
+ CPPUNIT_TEST(testEvenOddRuleInIntersectionRectangles);
+
+ CPPUNIT_TEST(testDrawOpenPolygonWithPolyLine);
+ CPPUNIT_TEST(testDrawOpenPolygonWithPolyLineB2D);
+ CPPUNIT_TEST(testDrawOpenPolygonWithPolygon);
+ CPPUNIT_TEST(testDrawOpenPolygonWithPolyPolygon);
+ CPPUNIT_TEST(testDrawOpenPolygonWithPolyPolygonB2D);
+
+ CPPUNIT_TEST(testDrawOpenBezierWithPolyLine);
+ CPPUNIT_TEST(testDrawOpenBezierWithPolyLineB2D);
+
+ CPPUNIT_TEST(testTdf124848);
+ CPPUNIT_TEST(testTdf136171);
+ CPPUNIT_TEST(testTdf145811);
+
+ CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BackendTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/BinaryDataContainerTest.cxx b/vcl/qa/cppunit/BinaryDataContainerTest.cxx
new file mode 100644
index 0000000000..2f72a9d182
--- /dev/null
+++ b/vcl/qa/cppunit/BinaryDataContainerTest.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 <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <vcl/BinaryDataContainer.hxx>
+
+using namespace css;
+
+namespace
+{
+class BinaryDataContainerTest : public CppUnit::TestFixture
+{
+ void testConstruct();
+
+ CPPUNIT_TEST_SUITE(BinaryDataContainerTest);
+ CPPUNIT_TEST(testConstruct);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void BinaryDataContainerTest::testConstruct()
+{
+ {
+ BinaryDataContainer aContainer;
+ CPPUNIT_ASSERT(aContainer.isEmpty());
+ CPPUNIT_ASSERT_EQUAL(size_t(0), aContainer.getSize());
+ }
+ {
+ // construct a data array
+ sal_uInt8 aTestByteArray[] = { 1, 2, 3, 4 };
+ SvMemoryStream stream(aTestByteArray, std::size(aTestByteArray), StreamMode::READ);
+
+ BinaryDataContainer aContainer(stream, std::size(aTestByteArray));
+
+ CPPUNIT_ASSERT(!aContainer.isEmpty());
+ CPPUNIT_ASSERT_EQUAL(size_t(4), aContainer.getSize());
+
+ // Test Copy
+ BinaryDataContainer aCopyOfContainer = aContainer;
+ CPPUNIT_ASSERT(!aCopyOfContainer.isEmpty());
+ CPPUNIT_ASSERT_EQUAL(size_t(4), aCopyOfContainer.getSize());
+ CPPUNIT_ASSERT_EQUAL(aCopyOfContainer.getData(), aContainer.getData());
+
+ // Test Move
+ BinaryDataContainer aMovedInContainer = std::move(aCopyOfContainer);
+ CPPUNIT_ASSERT(!aMovedInContainer.isEmpty());
+ CPPUNIT_ASSERT_EQUAL(size_t(4), aMovedInContainer.getSize());
+ CPPUNIT_ASSERT_EQUAL(aMovedInContainer.getData(), aContainer.getData());
+
+ CPPUNIT_ASSERT(aCopyOfContainer.isEmpty());
+ CPPUNIT_ASSERT_EQUAL(size_t(0), aCopyOfContainer.getSize());
+ }
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BinaryDataContainerTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/BitmapExTest.cxx b/vcl/qa/cppunit/BitmapExTest.cxx
new file mode 100644
index 0000000000..9b40df6caf
--- /dev/null
+++ b/vcl/qa/cppunit/BitmapExTest.cxx
@@ -0,0 +1,251 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <vcl/bitmapex.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+namespace
+{
+class BitmapExTest : public CppUnit::TestFixture
+{
+ void testGetPixelColor24_8();
+ void testGetPixelColor32();
+ void testTransformBitmapEx();
+ void testAlphaBlendWith();
+ void testCreateMask();
+ void testCombineMaskOr();
+
+ CPPUNIT_TEST_SUITE(BitmapExTest);
+ CPPUNIT_TEST(testGetPixelColor24_8);
+ CPPUNIT_TEST(testGetPixelColor32);
+ CPPUNIT_TEST(testTransformBitmapEx);
+ CPPUNIT_TEST(testAlphaBlendWith);
+ CPPUNIT_TEST(testCreateMask);
+ CPPUNIT_TEST(testCombineMaskOr);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void BitmapExTest::testGetPixelColor24_8()
+{
+ Bitmap aBitmap(Size(3, 3), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(Color(ColorTransparency, 0x00, 0x00, 0xFF, 0x00));
+ }
+ AlphaMask aMask(Size(3, 3));
+ {
+ BitmapScopedWriteAccess pWriteAccess(aMask);
+ pWriteAccess->Erase(Color(ColorTransparency, 0x00, 0xAA, 0xAA, 0xAA));
+ }
+
+ BitmapEx aBitmapEx(aBitmap, aMask);
+
+ CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xAA, 0x00, 0xFF, 0x00), aBitmapEx.GetPixelColor(0, 0));
+}
+
+void BitmapExTest::testGetPixelColor32()
+{
+ // Check backend capabilities and return from the test successfully
+ // if the backend doesn't support 32-bit bitmap
+ if (!ImplGetSVData()->mpDefInst->supportsBitmap32())
+ return;
+
+ Bitmap aBitmap(Size(3, 3), vcl::PixelFormat::N32_BPP);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(Color(ColorTransparency, 0xAA, 0x00, 0xFF, 0x00));
+ }
+
+ BitmapEx aBitmapEx(aBitmap);
+
+ CPPUNIT_ASSERT_EQUAL(Color(ColorTransparency, 0xAA, 0x00, 0xFF, 0x00),
+ aBitmapEx.GetPixelColor(0, 0));
+}
+
+void BitmapExTest::testTransformBitmapEx()
+{
+ Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(COL_WHITE);
+ for (int i = 0; i < 8; ++i)
+ {
+ for (int j = 0; j < 8; ++j)
+ {
+ pWriteAccess->SetPixel(i, j, COL_BLACK);
+ }
+ }
+ }
+ BitmapEx aBitmapEx(aBitmap);
+
+ basegfx::B2DHomMatrix aMatrix;
+ aMatrix.rotate(M_PI / 2);
+ BitmapEx aTransformed = aBitmapEx.TransformBitmapEx(16, 16, aMatrix);
+ aBitmap = aTransformed.GetBitmap();
+ BitmapScopedReadAccess pAccess(aBitmap);
+ for (int i = 0; i < 16; ++i)
+ {
+ for (int j = 0; j < 16; ++j)
+ {
+ BitmapColor aColor = pAccess->GetPixel(i, j);
+ std::stringstream ss;
+ ss << "Color is expected to be white or black, is '" << aColor.AsRGBHexString() << "'";
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expression: aColor == COL_WHITE || aColor == COL_BLACK
+ // - Color is expected to be white or black, is 'bfbfbf'
+ // i.e. smoothing introduced noise for a simple 90 deg rotation.
+ CPPUNIT_ASSERT_MESSAGE(ss.str(), aColor == COL_WHITE || aColor == COL_BLACK);
+ }
+ }
+}
+
+void BitmapExTest::testAlphaBlendWith()
+{
+ AlphaMask alpha(Size(1, 1));
+ AlphaMask bitmap(Size(1, 1));
+
+ // Just test a handful of combinations to make sure the algorithm doesn't
+ // change (as happened when I did some 32-bit alpha changes)
+
+ // Note that Erase() takes a transparency value, but we get alpha values in GetPixelIndex.
+
+ alpha.Erase(64);
+ bitmap.Erase(64);
+ alpha.BlendWith(bitmap);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(255 - 112),
+ BitmapScopedReadAccess(alpha)->GetPixelIndex(0, 0));
+
+ alpha.Erase(12);
+ bitmap.Erase(64);
+ alpha.BlendWith(bitmap);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(255 - 73),
+ BitmapScopedReadAccess(alpha)->GetPixelIndex(0, 0));
+
+ alpha.Erase(12);
+ bitmap.Erase(12);
+ alpha.BlendWith(bitmap);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(255 - 24),
+ BitmapScopedReadAccess(alpha)->GetPixelIndex(0, 0));
+
+ alpha.Erase(127);
+ bitmap.Erase(13);
+ alpha.BlendWith(bitmap);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(255 - 134),
+ BitmapScopedReadAccess(alpha)->GetPixelIndex(0, 0));
+
+ alpha.Erase(255);
+ bitmap.Erase(255);
+ alpha.BlendWith(bitmap);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(255 - 255),
+ BitmapScopedReadAccess(alpha)->GetPixelIndex(0, 0));
+
+ alpha.Erase(0);
+ bitmap.Erase(255);
+ alpha.BlendWith(bitmap);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(255 - 255),
+ BitmapScopedReadAccess(alpha)->GetPixelIndex(0, 0));
+
+ alpha.Erase(255);
+ bitmap.Erase(0);
+ alpha.BlendWith(bitmap);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(255 - 255),
+ BitmapScopedReadAccess(alpha)->GetPixelIndex(0, 0));
+
+ alpha.Erase(0);
+ bitmap.Erase(0);
+ alpha.BlendWith(bitmap);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(255 - 0),
+ BitmapScopedReadAccess(alpha)->GetPixelIndex(0, 0));
+}
+
+void BitmapExTest::testCreateMask()
+{
+ Bitmap aBitmap(Size(3, 3), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(COL_WHITE);
+ for (int i = 0; i < 3; ++i)
+ pWriteAccess->SetPixel(i, i, COL_RED);
+ }
+ aBitmap = aBitmap.CreateMask(COL_RED, 1);
+ BitmapScopedReadAccess pAccess(aBitmap);
+ // the output is a greyscale palette bitmap
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xff), pAccess->GetPixelIndex(0, 0));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), pAccess->GetPixelIndex(0, 1));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), pAccess->GetPixelIndex(0, 2));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), pAccess->GetPixelIndex(1, 0));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xff), pAccess->GetPixelIndex(1, 1));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), pAccess->GetPixelIndex(1, 2));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), pAccess->GetPixelIndex(2, 0));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), pAccess->GetPixelIndex(2, 1));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xff), pAccess->GetPixelIndex(2, 2));
+}
+
+void BitmapExTest::testCombineMaskOr()
+{
+ Bitmap aBitmap(Size(3, 3), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(COL_WHITE);
+ for (int i = 0; i < 3; ++i)
+ pWriteAccess->SetPixel(1, i, COL_RED);
+ }
+ AlphaMask aAlphaBitmap(Size(3, 3));
+ {
+ BitmapScopedWriteAccess pWriteAccess(aAlphaBitmap);
+ pWriteAccess->Erase(Color(0xff, 0xff, 0xff));
+ for (int i = 1; i < 3; ++i)
+ {
+ pWriteAccess->SetPixel(i, 0, Color(0x00, 0x00, 0x00));
+ pWriteAccess->SetPixel(i, 1, Color(0x80, 0x80, 0x80));
+ pWriteAccess->SetPixel(i, 0, Color(0xef, 0xef, 0xef));
+ }
+ }
+
+ {
+ AlphaMask aMask = aBitmap.CreateAlphaMask(COL_RED, 1);
+ BitmapScopedReadAccess pAccess(aMask);
+ // the output is a greyscale palette bitmap
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xff), pAccess->GetPixelIndex(0, 0));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xff), pAccess->GetPixelIndex(0, 1));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xff), pAccess->GetPixelIndex(0, 2));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), pAccess->GetPixelIndex(1, 0));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), pAccess->GetPixelIndex(1, 1));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), pAccess->GetPixelIndex(1, 2));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xff), pAccess->GetPixelIndex(2, 0));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xff), pAccess->GetPixelIndex(2, 1));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xff), pAccess->GetPixelIndex(2, 2));
+ }
+
+ BitmapEx aBitmapEx(aBitmap, aAlphaBitmap);
+ aBitmapEx.CombineMaskOr(COL_RED, 1);
+
+ CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xff, 0xff, 0xff, 0xff), aBitmapEx.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x00, 0x80, 0x00, 0x00), aBitmapEx.GetPixelColor(0, 1));
+ CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x00, 0xff, 0xff, 0xff), aBitmapEx.GetPixelColor(0, 2));
+ CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xff, 0xff, 0xff, 0xff), aBitmapEx.GetPixelColor(1, 0));
+ CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x00, 0x80, 0x00, 0x00), aBitmapEx.GetPixelColor(1, 1));
+ CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x00, 0xff, 0xff, 0xff), aBitmapEx.GetPixelColor(1, 2));
+ CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xff, 0xff, 0xff, 0xff), aBitmapEx.GetPixelColor(2, 0));
+ CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x00, 0x80, 0x00, 0x00), aBitmapEx.GetPixelColor(2, 1));
+ CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xff, 0xff, 0xff, 0xff), aBitmapEx.GetPixelColor(2, 2));
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BitmapExTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/BitmapFilterTest.cxx b/vcl/qa/cppunit/BitmapFilterTest.cxx
new file mode 100644
index 0000000000..0432041cac
--- /dev/null
+++ b/vcl/qa/cppunit/BitmapFilterTest.cxx
@@ -0,0 +1,283 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <tools/stream.hxx>
+#include <vcl/graphicfilter.hxx>
+
+#include <vcl/BitmapBasicMorphologyFilter.hxx>
+#include <vcl/BitmapFilterStackBlur.hxx>
+#include <BitmapSymmetryCheck.hxx>
+
+#include <chrono>
+#include <string_view>
+
+namespace
+{
+constexpr bool constWriteResultBitmap(false);
+constexpr bool constEnablePerformanceTest(false);
+
+class BitmapFilterTest : public test::BootstrapFixture
+{
+public:
+ BitmapFilterTest()
+ : test::BootstrapFixture(true, false)
+ {
+ }
+
+ void testBlurCorrectness();
+ void testBasicMorphology();
+ void testPerformance();
+ void testGenerateStripRanges();
+
+ CPPUNIT_TEST_SUITE(BitmapFilterTest);
+ CPPUNIT_TEST(testBlurCorrectness);
+ CPPUNIT_TEST(testBasicMorphology);
+ CPPUNIT_TEST(testPerformance);
+ CPPUNIT_TEST(testGenerateStripRanges);
+ CPPUNIT_TEST_SUITE_END();
+
+private:
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(u"vcl/qa/cppunit/data/") + sFileName;
+ }
+
+ BitmapEx loadBitmap(std::u16string_view sFileName)
+ {
+ Graphic aGraphic;
+ const OUString aURL(getFullUrl(sFileName));
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ ErrCode aResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, aResult);
+ return aGraphic.GetBitmapEx();
+ }
+
+ template <class BitmapT> // handle both Bitmap and BitmapEx
+ void savePNG(const OUString& sWhere, const BitmapT& rBmp)
+ {
+ SvFileStream aStream(sWhere, StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(BitmapEx(rBmp), aStream);
+ }
+};
+
+void BitmapFilterTest::testBlurCorrectness()
+{
+ // Setup test bitmap
+ Size aSize(41, 31);
+ Bitmap aBitmap24Bit(aSize, vcl::PixelFormat::N24_BPP);
+
+ ScanlineFormat scanlineFormat = ScanlineFormat::NONE;
+ auto ePixelFormat = aBitmap24Bit.getPixelFormat();
+
+ {
+ tools::Long aMargin1 = 1;
+ tools::Long aMargin2 = 3;
+ BitmapScopedWriteAccess aWriteAccess(aBitmap24Bit);
+ scanlineFormat = aWriteAccess->GetScanlineFormat();
+ aWriteAccess->Erase(COL_WHITE);
+ aWriteAccess->SetLineColor(COL_BLACK);
+
+ tools::Rectangle aRectangle1(aMargin1, aMargin1, aSize.Width() - 1 - aMargin1,
+ aSize.Height() - 1 - aMargin1);
+
+ tools::Rectangle aRectangle2(aMargin2, aMargin2, aSize.Width() - 1 - aMargin2,
+ aSize.Height() - 1 - aMargin2);
+
+ tools::Rectangle aRectangle3(aSize.Width() / 2, aSize.Height() / 2, aSize.Width() / 2,
+ aSize.Height() / 2);
+
+ aWriteAccess->DrawRect(aRectangle1);
+ aWriteAccess->DrawRect(aRectangle2);
+ aWriteAccess->DrawRect(aRectangle3);
+ }
+
+ if (constWriteResultBitmap)
+ {
+ savePNG("~/blurBefore.png", aBitmap24Bit);
+ }
+
+ // Perform blur
+ BitmapFilterStackBlur aBlurFilter(2);
+ aBitmap24Bit = aBlurFilter.filter(aBitmap24Bit);
+
+ // Check the result
+
+ if (constWriteResultBitmap)
+ {
+ savePNG("~/blurAfter.png", aBitmap24Bit);
+ }
+
+ // Check blurred bitmap parameters
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(41), aBitmap24Bit.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(31), aBitmap24Bit.GetSizePixel().Height());
+
+ CPPUNIT_ASSERT_EQUAL(ePixelFormat, aBitmap24Bit.getPixelFormat());
+
+ // Check that the bitmap is horizontally and vertically symmetrical
+ CPPUNIT_ASSERT(BitmapSymmetryCheck::check(aBitmap24Bit));
+
+ {
+ BitmapScopedReadAccess aReadAccess(aBitmap24Bit);
+ CPPUNIT_ASSERT_EQUAL(scanlineFormat, aReadAccess->GetScanlineFormat());
+ }
+}
+
+void BitmapFilterTest::testBasicMorphology()
+{
+ const BitmapEx aOrigBitmap = loadBitmap(u"testBasicMorphology.png");
+ const BitmapEx aRefBitmapDilated1 = loadBitmap(u"testBasicMorphologyDilated1.png");
+ const BitmapEx aRefBitmapDilated1Eroded1
+ = loadBitmap(u"testBasicMorphologyDilated1Eroded1.png");
+ const BitmapEx aRefBitmapDilated2 = loadBitmap(u"testBasicMorphologyDilated2.png");
+ const BitmapEx aRefBitmapDilated2Eroded1
+ = loadBitmap(u"testBasicMorphologyDilated2Eroded1.png");
+
+ BitmapEx aTransformBitmap = aOrigBitmap;
+ BitmapFilter::Filter(aTransformBitmap, BitmapDilateFilter(1));
+ if (constWriteResultBitmap)
+ savePNG("~/Dilated1.png", aTransformBitmap);
+ CPPUNIT_ASSERT_EQUAL(aRefBitmapDilated1.GetChecksum(), aTransformBitmap.GetChecksum());
+ BitmapFilter::Filter(aTransformBitmap, BitmapErodeFilter(1));
+ if (constWriteResultBitmap)
+ savePNG("~/Dilated1Eroded1.png", aTransformBitmap);
+ CPPUNIT_ASSERT_EQUAL(aRefBitmapDilated1Eroded1.GetChecksum(), aTransformBitmap.GetChecksum());
+
+ aTransformBitmap = aOrigBitmap;
+ BitmapFilter::Filter(aTransformBitmap, BitmapDilateFilter(2));
+ if (constWriteResultBitmap)
+ savePNG("~/Dilated2.png", aTransformBitmap);
+ CPPUNIT_ASSERT_EQUAL(aRefBitmapDilated2.GetChecksum(), aTransformBitmap.GetChecksum());
+ BitmapFilter::Filter(aTransformBitmap, BitmapErodeFilter(1));
+ if (constWriteResultBitmap)
+ savePNG("~/Dilated2Eroded1.png", aTransformBitmap);
+ CPPUNIT_ASSERT_EQUAL(aRefBitmapDilated2Eroded1.GetChecksum(), aTransformBitmap.GetChecksum());
+}
+
+void BitmapFilterTest::testPerformance()
+{
+ if (!constEnablePerformanceTest)
+ return;
+
+ Size aSize(4000, 3000); // A rather common picture size
+
+ // Prepare bitmap
+ Bitmap aBigBitmap(aSize, vcl::PixelFormat::N24_BPP);
+ {
+ tools::Long aMargin = 500;
+ BitmapScopedWriteAccess aWriteAccess(aBigBitmap);
+ aWriteAccess->Erase(COL_WHITE);
+ aWriteAccess->SetLineColor(COL_BLACK);
+ aWriteAccess->SetFillColor(COL_BLACK);
+ tools::Rectangle aRectangle(aMargin, aMargin, aSize.Width() - 1 - aMargin,
+ aSize.Height() - 1 - aMargin);
+
+ aWriteAccess->DrawRect(aRectangle);
+ }
+
+ int nIterations = 10;
+ auto start = std::chrono::high_resolution_clock::now();
+ Bitmap aResult;
+ for (int i = 0; i < nIterations; i++)
+ {
+ BitmapFilterStackBlur aBlurFilter(250);
+ aResult = aBlurFilter.filter(aBigBitmap);
+ }
+ auto end = std::chrono::high_resolution_clock::now();
+ auto elapsed = (end - start) / nIterations;
+
+ if (constWriteResultBitmap)
+ {
+ std::unique_ptr<SvFileStream> pStream(
+ new SvFileStream("~/BlurBigPerformance.png", StreamMode::WRITE | StreamMode::TRUNC));
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(BitmapEx(aResult), *pStream);
+
+ pStream.reset(new SvFileStream("~/BlurBigPerformance.txt", StreamMode::WRITE));
+ pStream->WriteOString("Blur average time: ");
+ pStream->WriteOString(OString::number(
+ std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count()));
+ pStream->WriteOString("\n");
+ }
+}
+
+void BitmapFilterTest::testGenerateStripRanges()
+{
+ {
+ constexpr tools::Long nFirstIndex = 0;
+ constexpr tools::Long nLastIndex = 100;
+ constexpr tools::Long nStripSize = 32;
+
+ std::vector<std::tuple<tools::Long, tools::Long, bool>> aRanges;
+
+ vcl::bitmap::generateStripRanges<nStripSize>(
+ nFirstIndex, nLastIndex,
+ [&](tools::Long const nStart, tools::Long const nEnd, bool const bLast) {
+ aRanges.emplace_back(nStart, nEnd, bLast);
+ });
+
+ CPPUNIT_ASSERT_EQUAL(size_t(4), aRanges.size());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), std::get<0>(aRanges[0]));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(31), std::get<1>(aRanges[0]));
+ CPPUNIT_ASSERT_EQUAL(false, std::get<2>(aRanges[0]));
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(32), std::get<0>(aRanges[1]));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(63), std::get<1>(aRanges[1]));
+ CPPUNIT_ASSERT_EQUAL(false, std::get<2>(aRanges[1]));
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(64), std::get<0>(aRanges[2]));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(95), std::get<1>(aRanges[2]));
+ CPPUNIT_ASSERT_EQUAL(false, std::get<2>(aRanges[2]));
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(96), std::get<0>(aRanges[3]));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), std::get<1>(aRanges[3]));
+ CPPUNIT_ASSERT_EQUAL(true, std::get<2>(aRanges[3]));
+ }
+
+ {
+ constexpr tools::Long nFirstIndex = 0;
+ constexpr tools::Long nLastIndex = 95;
+ constexpr tools::Long nStripSize = 32;
+
+ std::vector<std::tuple<tools::Long, tools::Long, bool>> aRanges;
+
+ vcl::bitmap::generateStripRanges<nStripSize>(
+ nFirstIndex, nLastIndex,
+ [&](tools::Long const nStart, tools::Long const nEnd, bool const bLast) {
+ aRanges.emplace_back(nStart, nEnd, bLast);
+ });
+
+ CPPUNIT_ASSERT_EQUAL(size_t(3), aRanges.size());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), std::get<0>(aRanges[0]));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(31), std::get<1>(aRanges[0]));
+ CPPUNIT_ASSERT_EQUAL(false, std::get<2>(aRanges[0]));
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(32), std::get<0>(aRanges[1]));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(63), std::get<1>(aRanges[1]));
+ CPPUNIT_ASSERT_EQUAL(false, std::get<2>(aRanges[1]));
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(64), std::get<0>(aRanges[2]));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(95), std::get<1>(aRanges[2]));
+ CPPUNIT_ASSERT_EQUAL(true, std::get<2>(aRanges[2]));
+ }
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BitmapFilterTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/BitmapProcessorTest.cxx b/vcl/qa/cppunit/BitmapProcessorTest.cxx
new file mode 100644
index 0000000000..874419092d
--- /dev/null
+++ b/vcl/qa/cppunit/BitmapProcessorTest.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/.
+ */
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/alpha.hxx>
+
+#include <bitmap/BitmapDisabledImageFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+namespace
+{
+class BitmapProcessorTest : public CppUnit::TestFixture
+{
+ void testDisabledImage();
+
+ CPPUNIT_TEST_SUITE(BitmapProcessorTest);
+ CPPUNIT_TEST(testDisabledImage);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void BitmapProcessorTest::testDisabledImage()
+{
+ {
+ Bitmap aBitmap(Size(3, 3), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(Color(ColorTransparency, 0x00, 0x00, 0xFF, 0x00));
+ }
+ BitmapEx aBitmapEx(aBitmap);
+ BitmapDisabledImageFilter aDisabledImageFilter;
+ BitmapEx aDisabledBitmapEx(aDisabledImageFilter.execute(aBitmapEx));
+ Bitmap aDisabledBitmap(aDisabledBitmapEx.GetBitmap());
+ {
+ BitmapScopedReadAccess pReadAccess(aDisabledBitmap);
+ Color aColor(pReadAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(Color(0x00C5C5C5), aColor);
+ }
+ }
+
+ {
+ Bitmap aBitmap(Size(3, 3), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(Color(ColorTransparency, 0x00, 0x00, 0xFF, 0x00));
+ }
+ AlphaMask aMask(Size(3, 3));
+ {
+ BitmapScopedWriteAccess pWriteAccess(aMask);
+ pWriteAccess->Erase(Color(ColorTransparency, 0x00, 0xAA, 0xAA, 0xAA));
+ }
+
+ BitmapEx aBitmapEx(aBitmap, aMask);
+ BitmapDisabledImageFilter aDisabledImageFilter;
+ BitmapEx aDisabledBitmapEx(aDisabledImageFilter.execute(aBitmapEx));
+
+ Bitmap aDisabledBitmap(aDisabledBitmapEx.GetBitmap());
+ {
+ BitmapScopedReadAccess pReadAccess(aDisabledBitmap);
+ Color aColor(pReadAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(Color(0x00C5C5C5), aColor);
+ }
+ AlphaMask aDisabledAlphaMask(aDisabledBitmapEx.GetAlphaMask());
+ {
+ BitmapScopedReadAccess pReadAccess(aDisabledAlphaMask);
+ Color aColor(pReadAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(Color(0x0000AA), aColor);
+ }
+ }
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BitmapProcessorTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/BitmapScaleTest.cxx b/vcl/qa/cppunit/BitmapScaleTest.cxx
new file mode 100644
index 0000000000..9bb6a48e48
--- /dev/null
+++ b/vcl/qa/cppunit/BitmapScaleTest.cxx
@@ -0,0 +1,312 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <vcl/bitmap.hxx>
+
+#include <tools/stream.hxx>
+#include <vcl/graphicfilter.hxx>
+
+#include <BitmapSymmetryCheck.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+namespace
+{
+class BitmapScaleTest : public CppUnit::TestFixture
+{
+ void testScale();
+ void testScale2();
+ void testScaleSymmetry();
+
+ CPPUNIT_TEST_SUITE(BitmapScaleTest);
+ CPPUNIT_TEST(testScale);
+ CPPUNIT_TEST(testScale2);
+ CPPUNIT_TEST(testScaleSymmetry);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+bool checkBitmapColor(Bitmap const& rBitmap, Color const& rExpectedColor)
+{
+ bool bResult = true;
+ Bitmap aBitmap(rBitmap);
+ BitmapScopedReadAccess pReadAccess(aBitmap);
+ tools::Long nHeight = pReadAccess->Height();
+ tools::Long nWidth = pReadAccess->Width();
+ for (tools::Long y = 0; y < nHeight; ++y)
+ {
+ Scanline pScanlineRead = pReadAccess->GetScanline(y);
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ Color aColor = pReadAccess->GetPixelFromData(pScanlineRead, x);
+ if (aColor != rExpectedColor)
+ bResult = false;
+ }
+ }
+
+ return bResult;
+}
+
+void assertColorsAreSimilar(int maxDifference, int line, const BitmapColor& expected,
+ const BitmapColor& actual)
+{
+ // Check that the two colors match or are reasonably similar.
+ if (expected.GetColorError(actual) <= maxDifference)
+ return;
+
+ std::stringstream stream;
+ stream << "Line: " << line;
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(stream.str(), expected, actual);
+}
+
+void BitmapScaleTest::testScale()
+{
+ const bool bExportBitmap(false);
+ using tools::Rectangle;
+
+ static const BmpScaleFlag scaleMethods[]
+ = { BmpScaleFlag::Default, BmpScaleFlag::Fast, BmpScaleFlag::BestQuality,
+ BmpScaleFlag::Interpolate, BmpScaleFlag::Lanczos, BmpScaleFlag::BiCubic,
+ BmpScaleFlag::BiLinear };
+ for (BmpScaleFlag scaleMethod : scaleMethods)
+ {
+ struct ScaleSize
+ {
+ Size srcSize;
+ Size destSize;
+ };
+ static const ScaleSize scaleSizes[]
+ = { // test no-op
+ { Size(16, 16), Size(16, 16) },
+ // powers of 2 (OpenGL may use texture atlas)
+ { Size(16, 16), Size(14, 14) },
+ { Size(14, 14), Size(16, 16) }, // both upscaling and downscaling
+ // "random" sizes
+ { Size(18, 18), Size(14, 14) },
+ { Size(14, 14), Size(18, 18) },
+ // different x/y ratios
+ { Size(16, 30), Size(14, 18) },
+ { Size(14, 18), Size(16, 30) },
+ // ratio larger than 16 (triggers different paths in some OpenGL algorithms)
+ { Size(18 * 20, 18 * 20), Size(14, 14) },
+ { Size(14, 14), Size(18 * 20, 18 * 20) },
+ // Boundary cases.
+ { Size(1, 1), Size(1, 1) },
+ { Size(16, 1), Size(12, 1) },
+ { Size(1, 16), Size(1, 12) }
+ };
+ for (const ScaleSize& scaleSize : scaleSizes)
+ {
+ OString testStr = "Testing scale (" + scaleSize.srcSize.toString() + ")->("
+ + scaleSize.destSize.toString() + "), method "
+ + OString::number(static_cast<int>(scaleMethod));
+ fprintf(stderr, "%s\n", testStr.getStr());
+ Bitmap bitmap(scaleSize.srcSize, vcl::PixelFormat::N24_BPP);
+ {
+ // Fill each quarter of the source bitmap with a different color,
+ // and center with yet another color.
+ BitmapScopedWriteAccess writeAccess(bitmap);
+ const int halfW = scaleSize.srcSize.getWidth() / 2;
+ const int halfH = scaleSize.srcSize.getHeight() / 2;
+ const Size aSize(std::max(halfW, 1), std::max(halfH, 1));
+
+ writeAccess->SetFillColor(COL_GREEN);
+ writeAccess->FillRect(Rectangle(Point(0, 0), aSize));
+ writeAccess->SetFillColor(COL_RED);
+ writeAccess->FillRect(Rectangle(Point(0, halfH), aSize));
+ writeAccess->SetFillColor(COL_YELLOW);
+ writeAccess->FillRect(Rectangle(Point(halfW, 0), aSize));
+ writeAccess->SetFillColor(COL_BLACK);
+ writeAccess->FillRect(Rectangle(Point(halfW, halfH), aSize));
+ writeAccess->SetFillColor(COL_BLUE);
+ writeAccess->FillRect(Rectangle(Point(halfW / 2, halfH / 2), aSize));
+ }
+ if (bExportBitmap)
+ {
+ SvFileStream aStream("~/scale_before.png", StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(BitmapEx(bitmap), aStream);
+ }
+ CPPUNIT_ASSERT(bitmap.Scale(scaleSize.destSize, scaleMethod));
+ if (bExportBitmap)
+ {
+ SvFileStream aStream("~/scale_after.png", StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(BitmapEx(bitmap), aStream);
+ }
+ CPPUNIT_ASSERT_EQUAL(scaleSize.destSize, bitmap.GetSizePixel());
+ const int lastW = scaleSize.destSize.getWidth() - 1;
+ const int lastH = scaleSize.destSize.getHeight() - 1;
+ if (scaleSize.srcSize.getWidth() == 1 && scaleSize.srcSize.getHeight() == 1)
+ {
+ BitmapReadAccess readAccess(bitmap);
+ assertColorsAreSimilar(2, __LINE__, COL_BLUE, readAccess.GetColor(0, 0));
+ assertColorsAreSimilar(2, __LINE__, COL_BLUE, readAccess.GetColor(lastH, 0));
+ assertColorsAreSimilar(2, __LINE__, COL_BLUE, readAccess.GetColor(0, lastW));
+ assertColorsAreSimilar(2, __LINE__, COL_BLUE, readAccess.GetColor(lastH, lastW));
+ assertColorsAreSimilar(2, __LINE__, COL_BLUE,
+ readAccess.GetColor(lastH / 2, lastW / 2));
+ }
+ else if (lastW && lastH)
+ {
+ // Scaling should keep each quarter of the resulting bitmap have the same color,
+ // so check that color in each corner of the result bitmap is the same color,
+ // or reasonably close (some algorithms may alter the color very slightly).
+ BitmapReadAccess readAccess(bitmap);
+ assertColorsAreSimilar(2, __LINE__, COL_GREEN, readAccess.GetColor(0, 0));
+ assertColorsAreSimilar(2, __LINE__, COL_RED, readAccess.GetColor(lastH, 0));
+ assertColorsAreSimilar(2, __LINE__, COL_YELLOW, readAccess.GetColor(0, lastW));
+ assertColorsAreSimilar(2, __LINE__, COL_BLACK, readAccess.GetColor(lastH, lastW));
+ assertColorsAreSimilar(2, __LINE__, COL_BLUE,
+ readAccess.GetColor(lastH / 2, lastW / 2));
+ }
+ else if (lastW)
+ {
+ BitmapReadAccess readAccess(bitmap);
+ assertColorsAreSimilar(2, __LINE__, COL_RED, readAccess.GetColor(0, 0));
+ assertColorsAreSimilar(2, __LINE__, COL_BLACK, readAccess.GetColor(0, lastW));
+ assertColorsAreSimilar(2, __LINE__, COL_BLUE, readAccess.GetColor(0, lastW / 2));
+ }
+ else if (lastH)
+ {
+ BitmapReadAccess readAccess(bitmap);
+ assertColorsAreSimilar(2, __LINE__, COL_YELLOW, readAccess.GetColor(0, 0));
+ assertColorsAreSimilar(2, __LINE__, COL_BLACK, readAccess.GetColor(lastH, 0));
+ assertColorsAreSimilar(2, __LINE__, COL_BLUE, readAccess.GetColor(lastH / 2, 0));
+ }
+ }
+ }
+}
+
+void BitmapScaleTest::testScale2()
+{
+ const bool bExportBitmap(false);
+
+ Bitmap aBitmap24Bit(Size(4096, 4096), vcl::PixelFormat::N24_BPP);
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, aBitmap24Bit.getPixelFormat());
+ Color aBitmapColor = COL_YELLOW;
+ {
+ BitmapScopedWriteAccess aWriteAccess(aBitmap24Bit);
+ aWriteAccess->Erase(aBitmapColor);
+ }
+
+ if (bExportBitmap)
+ {
+ SvFileStream aStream("scale_before.png", StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(BitmapEx(aBitmap24Bit), aStream);
+ }
+
+ // Scale - 65x65
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(4096), aBitmap24Bit.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(4096), aBitmap24Bit.GetSizePixel().Height());
+ Bitmap aScaledBitmap = aBitmap24Bit;
+ aScaledBitmap.Scale(Size(65, 65));
+
+ if (bExportBitmap)
+ {
+ SvFileStream aStream("scale_after_65x65.png", StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(BitmapEx(aScaledBitmap), aStream);
+ }
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(65), aScaledBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(65), aScaledBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT(checkBitmapColor(aScaledBitmap, aBitmapColor));
+
+ // Scale - 64x64
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(4096), aBitmap24Bit.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(4096), aBitmap24Bit.GetSizePixel().Height());
+ aScaledBitmap = aBitmap24Bit;
+ aScaledBitmap.Scale(Size(64, 64));
+
+ if (bExportBitmap)
+ {
+ SvFileStream aStream("scale_after_64x64.png", StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(BitmapEx(aScaledBitmap), aStream);
+ }
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(64), aScaledBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(64), aScaledBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT(checkBitmapColor(aScaledBitmap, aBitmapColor));
+
+ // Scale - 63x63
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(4096), aBitmap24Bit.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(4096), aBitmap24Bit.GetSizePixel().Height());
+ aScaledBitmap = aBitmap24Bit;
+ aScaledBitmap.Scale(Size(63, 63));
+
+ if (bExportBitmap)
+ {
+ SvFileStream aStream("scale_after_63x63.png", StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(BitmapEx(aScaledBitmap), aStream);
+ }
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(63), aScaledBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(63), aScaledBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT(checkBitmapColor(aScaledBitmap, aBitmapColor));
+}
+
+void BitmapScaleTest::testScaleSymmetry()
+{
+ const bool bExportBitmap(false);
+
+ Bitmap aBitmap24Bit(Size(10, 10), vcl::PixelFormat::N24_BPP);
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, aBitmap24Bit.getPixelFormat());
+
+ {
+ BitmapScopedWriteAccess aWriteAccess(aBitmap24Bit);
+ aWriteAccess->Erase(COL_WHITE);
+ aWriteAccess->SetLineColor(COL_BLACK);
+ aWriteAccess->DrawRect(tools::Rectangle(1, 1, 8, 8));
+ aWriteAccess->DrawRect(tools::Rectangle(3, 3, 6, 6));
+ }
+
+ BitmapSymmetryCheck aBitmapSymmetryCheck;
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(10), aBitmap24Bit.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(10), aBitmap24Bit.GetSizePixel().Height());
+
+ // Check symmetry of the bitmap
+ CPPUNIT_ASSERT(BitmapSymmetryCheck::check(aBitmap24Bit));
+
+ if (bExportBitmap)
+ {
+ SvFileStream aStream("~/scale_before.png", StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(BitmapEx(aBitmap24Bit), aStream);
+ }
+
+ aBitmap24Bit.Scale(2, 2, BmpScaleFlag::Fast);
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(20), aBitmap24Bit.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(20), aBitmap24Bit.GetSizePixel().Height());
+
+ // After scaling the bitmap should still be symmetrical. This check guarantees that
+ // scaling doesn't misalign the bitmap.
+ CPPUNIT_ASSERT(BitmapSymmetryCheck::check(aBitmap24Bit));
+
+ if (bExportBitmap)
+ {
+ SvFileStream aStream("~/scale_after.png", StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(BitmapEx(aBitmap24Bit), aStream);
+ }
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BitmapScaleTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/BitmapTest.cxx b/vcl/qa/cppunit/BitmapTest.cxx
new file mode 100644
index 0000000000..e00f2e0dc8
--- /dev/null
+++ b/vcl/qa/cppunit/BitmapTest.cxx
@@ -0,0 +1,722 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+
+#include <config_features.h>
+
+#include <rtl/strbuf.hxx>
+
+#include <vcl/BitmapTools.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/skia/SkiaHelper.hxx>
+#include <vcl/BitmapMonochromeFilter.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/Octree.hxx>
+#include <salinst.hxx>
+#include <svdata.hxx>
+
+#include <unordered_map>
+
+namespace
+{
+class BitmapTest : public CppUnit::TestFixture
+{
+ void testCreation();
+ void testEmpty();
+ void testMonochrome();
+ void testN8Greyscale();
+ void testConvert();
+ void testCRC();
+ void testGreyPalette();
+ void testCustom8BitPalette();
+ void testErase();
+ void testBitmap32();
+ void testOctree();
+ void testEmptyAccess();
+ void testDitherSize();
+ void testMirror();
+ void testCrop();
+ void testCroppedDownsampledBitmap();
+
+ CPPUNIT_TEST_SUITE(BitmapTest);
+ CPPUNIT_TEST(testCreation);
+ CPPUNIT_TEST(testEmpty);
+ CPPUNIT_TEST(testMonochrome);
+ CPPUNIT_TEST(testConvert);
+ CPPUNIT_TEST(testN8Greyscale);
+ CPPUNIT_TEST(testCRC);
+ CPPUNIT_TEST(testGreyPalette);
+ CPPUNIT_TEST(testCustom8BitPalette);
+ CPPUNIT_TEST(testErase);
+ CPPUNIT_TEST(testBitmap32);
+ CPPUNIT_TEST(testOctree);
+ CPPUNIT_TEST(testEmptyAccess);
+ CPPUNIT_TEST(testDitherSize);
+ CPPUNIT_TEST(testMirror);
+ CPPUNIT_TEST(testCrop);
+ CPPUNIT_TEST(testCroppedDownsampledBitmap);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void assertColorsAreSimilar(int maxDifference, const std::string& message,
+ const BitmapColor& expected, const BitmapColor& actual)
+{
+ // Check that the two colors match or are reasonably similar.
+ if (expected.GetColorError(actual) <= maxDifference)
+ return;
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(message, expected, actual);
+}
+
+void BitmapTest::testCreation()
+{
+ {
+ Bitmap aBmp;
+ Size aSize = aBmp.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong width", static_cast<tools::Long>(0), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong height", static_cast<tools::Long>(0), aSize.Height());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong pref size", Size(), aBmp.GetPrefSize());
+ CPPUNIT_ASSERT_MESSAGE("Not empty", aBmp.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong pixel format", vcl::PixelFormat::INVALID,
+ aBmp.getPixelFormat());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong byte size", static_cast<sal_Int64>(0),
+ aBmp.GetSizeBytes());
+ }
+
+ {
+ Bitmap aBmp(Size(10, 10), vcl::PixelFormat::N8_BPP);
+ Size aSize = aBmp.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong width", static_cast<tools::Long>(10), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong height", static_cast<tools::Long>(10), aSize.Height());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong pref size", Size(), aBmp.GetPrefSize());
+ CPPUNIT_ASSERT_MESSAGE("Empty bitmap", !aBmp.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong pixel format", vcl::PixelFormat::N8_BPP,
+ aBmp.getPixelFormat());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong byte size", static_cast<sal_Int64>(100),
+ aBmp.GetSizeBytes());
+ }
+
+ {
+ Bitmap aBmp(Size(10, 10), vcl::PixelFormat::N24_BPP);
+ Size aSize = aBmp.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong width", static_cast<tools::Long>(10), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong height", static_cast<tools::Long>(10), aSize.Height());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong pref size", Size(), aBmp.GetPrefSize());
+ CPPUNIT_ASSERT_MESSAGE("Empty bitmap", !aBmp.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong pixel format", vcl::PixelFormat::N24_BPP,
+ aBmp.getPixelFormat());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong byte size", static_cast<sal_Int64>(300),
+ aBmp.GetSizeBytes());
+ }
+
+ // Check backend capabilities and return from the test successfully
+ // if the backend doesn't support 32-bit bitmap
+ if (ImplGetSVData()->mpDefInst->supportsBitmap32())
+ {
+ Bitmap aBmp(Size(10, 10), vcl::PixelFormat::N32_BPP);
+ Size aSize = aBmp.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong width", static_cast<tools::Long>(10), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong height", static_cast<tools::Long>(10), aSize.Height());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong pref size", Size(), aBmp.GetPrefSize());
+ CPPUNIT_ASSERT_MESSAGE("Empty bitmap", !aBmp.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong pixel format", vcl::PixelFormat::N32_BPP,
+ aBmp.getPixelFormat());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong byte size", sal_Int64(400), aBmp.GetSizeBytes());
+ }
+}
+
+void BitmapTest::testEmpty()
+{
+ Bitmap aBitmap(Size(10, 10), vcl::PixelFormat::N8_BPP);
+ aBitmap.Erase(COL_LIGHTGRAYBLUE);
+
+ CPPUNIT_ASSERT(!aBitmap.IsEmpty());
+
+ aBitmap.SetEmpty();
+ CPPUNIT_ASSERT(aBitmap.IsEmpty());
+}
+
+Bitmap createTestBitmap()
+{
+ Bitmap aBmp(Size(4, 4), vcl::PixelFormat::N24_BPP);
+ BitmapWriteAccess aBmpAccess(aBmp);
+
+ // row 1
+ aBmpAccess.SetPixel(0, 0, BitmapColor(COL_BLACK));
+ aBmpAccess.SetPixel(0, 1, BitmapColor(COL_BLUE));
+ aBmpAccess.SetPixel(0, 2, BitmapColor(COL_GREEN));
+ aBmpAccess.SetPixel(0, 3, BitmapColor(COL_CYAN));
+
+ // row 2
+ aBmpAccess.SetPixel(1, 0, BitmapColor(COL_RED));
+ aBmpAccess.SetPixel(1, 1, BitmapColor(COL_MAGENTA));
+ aBmpAccess.SetPixel(1, 2, BitmapColor(COL_BROWN));
+ aBmpAccess.SetPixel(1, 3, BitmapColor(COL_GRAY));
+
+ // row 3
+ aBmpAccess.SetPixel(2, 0, BitmapColor(COL_LIGHTGRAY));
+ aBmpAccess.SetPixel(2, 1, BitmapColor(COL_LIGHTBLUE));
+ aBmpAccess.SetPixel(2, 2, BitmapColor(COL_LIGHTGREEN));
+ aBmpAccess.SetPixel(2, 3, BitmapColor(COL_LIGHTCYAN));
+
+ // row 4
+ aBmpAccess.SetPixel(3, 0, BitmapColor(COL_LIGHTRED));
+ aBmpAccess.SetPixel(3, 1, BitmapColor(COL_LIGHTMAGENTA));
+ aBmpAccess.SetPixel(3, 2, BitmapColor(COL_YELLOW));
+ aBmpAccess.SetPixel(3, 3, BitmapColor(COL_WHITE));
+
+ return aBmp;
+}
+
+void BitmapTest::testMonochrome()
+{
+ Bitmap aBmp = createTestBitmap();
+
+ BitmapEx aBmpEx(aBmp);
+ BitmapFilter::Filter(aBmpEx, BitmapMonochromeFilter(63));
+ aBmp = aBmpEx.GetBitmap();
+ BitmapReadAccess aBmpReadAccess(aBmp);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Black pixel wrong monochrome value", BitmapColor(COL_BLACK),
+ aBmpReadAccess.GetColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Blue pixel wrong monochrome value", BitmapColor(COL_BLACK),
+ aBmpReadAccess.GetColor(0, 1));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Green pixel wrong monochrome value", BitmapColor(COL_WHITE),
+ aBmpReadAccess.GetColor(0, 2));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Cyan pixel wrong monochrome value", BitmapColor(COL_WHITE),
+ aBmpReadAccess.GetColor(0, 3));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Red pixel wrong monochrome value", BitmapColor(COL_BLACK),
+ aBmpReadAccess.GetColor(1, 0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Magenta pixel wrong monochrome value", BitmapColor(COL_BLACK),
+ aBmpReadAccess.GetColor(1, 1));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Brown pixel wrong monochrome value", BitmapColor(COL_WHITE),
+ aBmpReadAccess.GetColor(1, 2));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Gray pixel wrong monochrome value", BitmapColor(COL_WHITE),
+ aBmpReadAccess.GetColor(1, 3));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Light gray pixel wrong monochrome value", BitmapColor(COL_WHITE),
+ aBmpReadAccess.GetColor(2, 0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Light blue pixel wrong monochrome value", BitmapColor(COL_BLACK),
+ aBmpReadAccess.GetColor(2, 1));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Light green pixel wrong monochrome value", BitmapColor(COL_WHITE),
+ aBmpReadAccess.GetColor(2, 2));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Light cyan pixel wrong monochrome value", BitmapColor(COL_WHITE),
+ aBmpReadAccess.GetColor(2, 3));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Light red pixel wrong monochrome value", BitmapColor(COL_WHITE),
+ aBmpReadAccess.GetColor(3, 0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Light magenta pixel wrong monochrome value",
+ BitmapColor(COL_WHITE), aBmpReadAccess.GetColor(3, 1));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Yellow pixel wrong monochrome value", BitmapColor(COL_WHITE),
+ aBmpReadAccess.GetColor(3, 2));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("White pixel wrong monochrome value", BitmapColor(COL_WHITE),
+ aBmpReadAccess.GetColor(3, 3));
+}
+
+void BitmapTest::testN8Greyscale()
+{
+ Bitmap aBmp = createTestBitmap();
+ BitmapPalette aGreyscalePalette = Bitmap::GetGreyPalette(256);
+
+ aBmp.Convert(BmpConversion::N8BitGreys);
+ BitmapReadAccess aBmpReadAccess(aBmp);
+
+ assertColorsAreSimilar(1, "Black pixel wrong 8-bit greyscale value", aGreyscalePalette[0],
+ aBmpReadAccess.GetColor(0, 0));
+ assertColorsAreSimilar(1, "Blue pixel wrong 8-bit greyscale value", aGreyscalePalette[14],
+ aBmpReadAccess.GetColor(0, 1));
+ assertColorsAreSimilar(1, "Green pixel wrong 8-bit greyscale value", aGreyscalePalette[75],
+ aBmpReadAccess.GetColor(0, 2));
+ assertColorsAreSimilar(1, "Cyan pixel wrong 8-bit greyscale value", aGreyscalePalette[89],
+ aBmpReadAccess.GetColor(0, 3));
+ assertColorsAreSimilar(1, "Red pixel wrong 8-bit greyscale value", aGreyscalePalette[38],
+ aBmpReadAccess.GetColor(1, 0));
+ assertColorsAreSimilar(1, "Magenta pixel wrong 8-bit greyscale value", aGreyscalePalette[52],
+ aBmpReadAccess.GetColor(1, 1));
+ assertColorsAreSimilar(1, "Brown pixel wrong 8-bit greyscale value", aGreyscalePalette[114],
+ aBmpReadAccess.GetColor(1, 2));
+ assertColorsAreSimilar(1, "Gray pixel wrong 8-bit greyscale value", aGreyscalePalette[128],
+ aBmpReadAccess.GetColor(1, 3));
+ assertColorsAreSimilar(1, "Light gray pixel wrong 8-bit greyscale value",
+ aGreyscalePalette[192], aBmpReadAccess.GetColor(2, 0));
+ assertColorsAreSimilar(1, "Light blue pixel wrong 8-bit greyscale value", aGreyscalePalette[27],
+ aBmpReadAccess.GetColor(2, 1));
+ assertColorsAreSimilar(1, "Light green pixel wrong 8-bit greyscale value",
+ aGreyscalePalette[150], aBmpReadAccess.GetColor(2, 2));
+ assertColorsAreSimilar(1, "Light cyan pixel wrong 8-bit greyscale value",
+ aGreyscalePalette[178], aBmpReadAccess.GetColor(2, 3));
+ assertColorsAreSimilar(1, "Light red pixel wrong 8-bit greyscale value", aGreyscalePalette[76],
+ aBmpReadAccess.GetColor(3, 0));
+ assertColorsAreSimilar(1, "Light magenta pixel wrong 8-bit greyscale value",
+ aGreyscalePalette[104], aBmpReadAccess.GetColor(3, 1));
+ assertColorsAreSimilar(1, "Yellow pixel wrong 8-bit greyscale value", aGreyscalePalette[227],
+ aBmpReadAccess.GetColor(3, 2));
+ assertColorsAreSimilar(1, "White pixel wrong 8-bit greyscale value", aGreyscalePalette[255],
+ aBmpReadAccess.GetColor(3, 3));
+}
+
+void BitmapTest::testConvert()
+{
+ Bitmap aBitmap(Size(10, 10), vcl::PixelFormat::N8_BPP);
+
+ aBitmap.Erase(COL_LIGHTGRAYBLUE);
+
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat());
+ {
+ BitmapScopedReadAccess pReadAccess(aBitmap);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(8), pReadAccess->GetBitCount());
+#if defined MACOSX || defined IOS
+ if (SkiaHelper::isVCLSkiaEnabled())
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(12), pReadAccess->GetScanlineSize());
+ else
+ //it would be nice to find and change the stride for quartz to be the same as everyone else
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(10), pReadAccess->GetScanlineSize());
+#else
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(12), pReadAccess->GetScanlineSize());
+#endif
+ CPPUNIT_ASSERT(pReadAccess->HasPalette());
+ const BitmapColor& rColor = pReadAccess->GetPaletteColor(pReadAccess->GetPixelIndex(1, 1));
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(204), sal_Int32(rColor.GetRed()));
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(204), sal_Int32(rColor.GetGreen()));
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(255), sal_Int32(rColor.GetBlue()));
+ }
+
+ aBitmap.Convert(BmpConversion::N24Bit);
+
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, aBitmap.getPixelFormat());
+ {
+ BitmapScopedReadAccess pReadAccess(aBitmap);
+ // 24 bit Bitmap on SVP backend can now use 24bit RGB everywhere.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(24), pReadAccess->GetBitCount());
+
+ if (SkiaHelper::isVCLSkiaEnabled()) // aligned to 4 bytes
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(32), pReadAccess->GetScanlineSize());
+ else
+#if defined LINUX || defined FREEBSD
+ {
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(32), pReadAccess->GetScanlineSize());
+ }
+#elif defined(_WIN32)
+ {
+ // GDI Scanlines padded to DWORD multiples, it seems
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(32), pReadAccess->GetScanlineSize());
+ }
+#else
+ {
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(30), pReadAccess->GetScanlineSize());
+ }
+#endif
+
+ CPPUNIT_ASSERT(!pReadAccess->HasPalette());
+ Color aColor = pReadAccess->GetPixel(0, 0);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(204), sal_Int32(aColor.GetRed()));
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(204), sal_Int32(aColor.GetGreen()));
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(255), sal_Int32(aColor.GetBlue()));
+ }
+}
+
+typedef std::unordered_map<sal_uInt64, const char*> CRCHash;
+
+void checkAndInsert(CRCHash& rHash, sal_uInt64 nCRC, const char* pLocation)
+{
+ auto it = rHash.find(nCRC);
+ if (it != rHash.end())
+ {
+ OString aBuf = OString::Concat("CRC collision between ") + pLocation + " and " + it->second
+ + " hash is 0x" + OString::number(static_cast<sal_Int64>(nCRC), 16);
+ CPPUNIT_FAIL(aBuf.getStr());
+ }
+ rHash[nCRC] = pLocation;
+}
+
+void checkAndInsert(CRCHash& rHash, Bitmap const& rBmp, const char* pLocation)
+{
+ checkAndInsert(rHash, rBmp.GetChecksum(), pLocation);
+}
+
+Bitmap getAsBitmap(VclPtr<OutputDevice> const& pOut)
+{
+ return pOut->GetBitmap(Point(), pOut->GetOutputSizePixel());
+}
+
+void BitmapTest::testCRC()
+{
+ CRCHash aCRCs;
+
+ Bitmap aBitmap(Size(1023, 759), vcl::PixelFormat::N24_BPP);
+ aBitmap.Erase(COL_BLACK);
+ checkAndInsert(aCRCs, aBitmap, "black bitmap");
+ aBitmap.Invert();
+ checkAndInsert(aCRCs, aBitmap, "white bitmap");
+
+ ScopedVclPtrInstance<VirtualDevice> aVDev;
+ aVDev->SetBackground(Wallpaper(COL_WHITE));
+ aVDev->SetOutputSizePixel(Size(1023, 759));
+
+#if 0 // disabled for now - oddly breaks on OS/X - but why ?
+ Bitmap aWhiteCheck = getAsBitmap(aVDev);
+ CPPUNIT_ASSERT(aCRCs.find(aWhiteCheck.GetChecksum()) != aCRCs.end());
+#endif
+
+ // a 1x1 black & white checkerboard
+ aVDev->DrawCheckered(Point(), aVDev->GetOutputSizePixel(), 1, Color(0, 0, 1));
+ Bitmap aChecker = getAsBitmap(aVDev);
+ checkAndInsert(aCRCs, aChecker, "checkerboard");
+ aChecker.Invert();
+ checkAndInsert(aCRCs, aChecker, "inverted checkerboard");
+}
+
+void BitmapTest::testGreyPalette()
+{
+ {
+ BitmapPalette aPalette = Bitmap::GetGreyPalette(2);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong number of palette entries", static_cast<sal_uInt16>(2),
+ aPalette.GetEntryCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 1 wrong", BitmapColor(0, 0, 0), aPalette[0]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 2 wrong", BitmapColor(255, 255, 255), aPalette[1]);
+ }
+
+ {
+ BitmapPalette aPalette = Bitmap::GetGreyPalette(4);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong number of palette entries", static_cast<sal_uInt16>(4),
+ aPalette.GetEntryCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 1 wrong", BitmapColor(0, 0, 0), aPalette[0]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 2 wrong", BitmapColor(85, 85, 85), aPalette[1]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 3 wrong", BitmapColor(170, 170, 170), aPalette[2]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 4 wrong", BitmapColor(255, 255, 255), aPalette[3]);
+ }
+
+ {
+ BitmapPalette aPalette = Bitmap::GetGreyPalette(16);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong number of palette entries", static_cast<sal_uInt16>(16),
+ aPalette.GetEntryCount());
+ // this is a *real* specific number of greys, incremented in units of 17 so may
+ // as well test them all...
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 1 wrong", BitmapColor(0, 0, 0), aPalette[0]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 2 wrong", BitmapColor(17, 17, 17), aPalette[1]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 3 wrong", BitmapColor(34, 34, 34), aPalette[2]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 4 wrong", BitmapColor(51, 51, 51), aPalette[3]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 5 wrong", BitmapColor(68, 68, 68), aPalette[4]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 6 wrong", BitmapColor(85, 85, 85), aPalette[5]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 7 wrong", BitmapColor(102, 102, 102), aPalette[6]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 8 wrong", BitmapColor(119, 119, 119), aPalette[7]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 9 wrong", BitmapColor(136, 136, 136), aPalette[8]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 10 wrong", BitmapColor(153, 153, 153), aPalette[9]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 11 wrong", BitmapColor(170, 170, 170), aPalette[10]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 12 wrong", BitmapColor(187, 187, 187), aPalette[11]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 13 wrong", BitmapColor(204, 204, 204), aPalette[12]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 14 wrong", BitmapColor(221, 221, 221), aPalette[13]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 15 wrong", BitmapColor(238, 238, 238), aPalette[14]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 16 wrong", BitmapColor(255, 255, 255), aPalette[15]);
+ }
+
+ {
+ BitmapPalette aPalette = Bitmap::GetGreyPalette(256);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong number of palette entries",
+ static_cast<sal_uInt16>(256), aPalette.GetEntryCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 1 wrong", BitmapColor(0, 0, 0), aPalette[0]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 127 wrong", BitmapColor(127, 127, 127), aPalette[127]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Entry 255 wrong", BitmapColor(255, 255, 255), aPalette[255]);
+ }
+}
+
+void BitmapTest::testCustom8BitPalette()
+{
+ BitmapPalette aCustomPalette;
+ aCustomPalette.SetEntryCount(256);
+ for (sal_uInt16 i = 0; i < 256; i++)
+ {
+ aCustomPalette[i] = BitmapColor(sal_uInt8(i), sal_uInt8(0xCC), sal_uInt8(0x22));
+ }
+ Bitmap aBitmap(Size(3, 2), vcl::PixelFormat::N8_BPP, &aCustomPalette);
+
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap);
+ pAccess->SetPixelIndex(0, 0, 0);
+ pAccess->SetPixelIndex(0, 1, 1);
+ pAccess->SetPixelIndex(0, 2, 2);
+
+ pAccess->SetPixelIndex(1, 0, 253);
+ pAccess->SetPixelIndex(1, 1, 254);
+ pAccess->SetPixelIndex(1, 2, 255);
+ }
+
+ {
+ BitmapScopedReadAccess pAccess(aBitmap);
+ CPPUNIT_ASSERT_EQUAL(0, int(pAccess->GetPixelIndex(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x00, 0xCC, 0x22), pAccess->GetColor(0, 0));
+
+ CPPUNIT_ASSERT_EQUAL(1, int(pAccess->GetPixelIndex(0, 1)));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x01, 0xCC, 0x22), pAccess->GetColor(0, 1));
+
+ CPPUNIT_ASSERT_EQUAL(2, int(pAccess->GetPixelIndex(0, 2)));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x02, 0xCC, 0x22), pAccess->GetColor(0, 2));
+
+ CPPUNIT_ASSERT_EQUAL(253, int(pAccess->GetPixelIndex(1, 0)));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFD, 0xCC, 0x22), pAccess->GetColor(1, 0));
+
+ CPPUNIT_ASSERT_EQUAL(254, int(pAccess->GetPixelIndex(1, 1)));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFE, 0xCC, 0x22), pAccess->GetColor(1, 1));
+
+ CPPUNIT_ASSERT_EQUAL(255, int(pAccess->GetPixelIndex(1, 2)));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xFF, 0xCC, 0x22), pAccess->GetColor(1, 2));
+ }
+}
+
+void BitmapTest::testErase()
+{
+ Bitmap aBitmap(Size(3, 3), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(Color(0x11, 0x22, 0x33));
+ }
+ {
+ BitmapScopedReadAccess pReadAccess(aBitmap);
+ BitmapColor aColor(pReadAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x11, 0x22, 0x33, 0x00), aColor);
+ }
+}
+
+void BitmapTest::testBitmap32()
+{
+ // Check backend capabilities and return from the test successfully
+ // if the backend doesn't support 32-bit bitmap
+ if (!ImplGetSVData()->mpDefInst->supportsBitmap32())
+ return;
+
+ Bitmap aBitmap(Size(3, 3), vcl::PixelFormat::N32_BPP);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(Color(ColorTransparency, 0xFF, 0x11, 0x22, 0x33));
+ pWriteAccess->SetPixel(1, 1, BitmapColor(ColorTransparency, 0x44, 0xFF, 0xBB, 0x00));
+ pWriteAccess->SetPixel(2, 2, BitmapColor(ColorTransparency, 0x99, 0x77, 0x66, 0x55));
+ }
+ {
+ BitmapScopedReadAccess pReadAccess(aBitmap);
+ BitmapColor aColor = pReadAccess->GetPixel(0, 0);
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0x00, 0x00, 0xFF), aColor);
+
+ aColor = pReadAccess->GetPixel(1, 1);
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x44, 0xFF, 0xBB, 0x00), aColor);
+
+ aColor = pReadAccess->GetPixel(2, 2);
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x99, 0x77, 0x66, 0x55), aColor);
+ }
+}
+
+void BitmapTest::testOctree()
+{
+ Size aSize(1000, 100);
+ Bitmap aBitmap(aSize, vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ for (tools::Long y = 0; y < aSize.Height(); ++y)
+ {
+ for (tools::Long x = 0; x < aSize.Width(); ++x)
+ {
+ double fPercent = double(x) / double(aSize.Width());
+ pWriteAccess->SetPixel(y, x,
+ BitmapColor(255.0 * fPercent, 64.0 + (128.0 * fPercent),
+ 255.0 - 255.0 * fPercent));
+ }
+ }
+ }
+
+ {
+ // Reduce to 1 color
+ BitmapScopedReadAccess pAccess(aBitmap);
+ Octree aOctree(*pAccess, 1);
+ auto aBitmapPalette = aOctree.GetPalette();
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), aBitmapPalette.GetEntryCount());
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x7e, 0x7f, 0x7f), aBitmapPalette[0]);
+ }
+
+ {
+ // Reduce to 4 color
+ BitmapScopedReadAccess pAccess(aBitmap);
+ Octree aOctree(*pAccess, 4);
+ auto aBitmapPalette = aOctree.GetPalette();
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(4), aBitmapPalette.GetEntryCount());
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x7f, 0x7f, 0x7f), aBitmapPalette[0]);
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x3e, 0x5f, 0xbf), aBitmapPalette[1]);
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x7f, 0x80, 0x7f), aBitmapPalette[2]);
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0xbe, 0x9f, 0x3f), aBitmapPalette[3]);
+ }
+
+ {
+ // Reduce to 256 color
+ BitmapScopedReadAccess pAccess(aBitmap);
+ Octree aOctree(*pAccess, 256);
+ auto aBitmapPalette = aOctree.GetPalette();
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(74), aBitmapPalette.GetEntryCount());
+ }
+}
+
+void BitmapTest::testEmptyAccess()
+{
+ Bitmap empty;
+ BitmapInfoAccess access(empty);
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), access.Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), access.Height());
+}
+
+void BitmapTest::testDitherSize()
+{
+ // no need to do anything for a 1x1 pixel bitmap
+ {
+ Bitmap aBitmap(Size(1, 1), vcl::PixelFormat::N24_BPP);
+ CPPUNIT_ASSERT(aBitmap.Dither());
+ }
+
+ // cannot dither a bitmap with a width of 2 or 3 pixels
+ {
+ Bitmap aBitmap(Size(2, 4), vcl::PixelFormat::N24_BPP);
+ CPPUNIT_ASSERT(!aBitmap.Dither());
+ }
+
+ {
+ Bitmap aBitmap(Size(3, 4), vcl::PixelFormat::N24_BPP);
+ CPPUNIT_ASSERT(!aBitmap.Dither());
+ }
+
+ // cannot dither a bitmap with a height of 2 pixels
+ {
+ Bitmap aBitmap(Size(4, 2), vcl::PixelFormat::N24_BPP);
+ CPPUNIT_ASSERT(!aBitmap.Dither());
+ }
+
+ // only dither bitmaps with a width > 3 pixels and height > 2 pixels
+ {
+ Bitmap aBitmap(Size(4, 3), vcl::PixelFormat::N24_BPP);
+ CPPUNIT_ASSERT(aBitmap.Dither());
+ }
+}
+
+void BitmapTest::testMirror()
+{
+ vcl::PixelFormat bppArray[]
+ = { vcl::PixelFormat::N8_BPP, vcl::PixelFormat::N24_BPP, vcl::PixelFormat::N32_BPP };
+
+ for (vcl::PixelFormat bpp : bppArray)
+ {
+ Bitmap bitmap(Size(11, 11), bpp);
+ {
+ bitmap.Erase(COL_MAGENTA);
+ BitmapWriteAccess write(bitmap);
+ if (write.HasPalette())
+ {
+ // Note that SetPixel() and GetColor() take arguments as Y,X.
+ write.SetPixel(0, 0, BitmapColor(write.GetBestPaletteIndex(COL_BLACK)));
+ write.SetPixel(10, 0, BitmapColor(write.GetBestPaletteIndex(COL_WHITE)));
+ write.SetPixel(0, 10, BitmapColor(write.GetBestPaletteIndex(COL_RED)));
+ write.SetPixel(10, 10, BitmapColor(write.GetBestPaletteIndex(COL_BLUE)));
+ write.SetPixel(5, 0, BitmapColor(write.GetBestPaletteIndex(COL_GREEN)));
+ write.SetPixel(0, 5, BitmapColor(write.GetBestPaletteIndex(COL_YELLOW)));
+ }
+ else
+ {
+ write.SetPixel(0, 0, COL_BLACK);
+ write.SetPixel(10, 0, COL_WHITE);
+ write.SetPixel(0, 10, COL_RED);
+ write.SetPixel(10, 10, COL_BLUE);
+ write.SetPixel(5, 0, COL_GREEN);
+ write.SetPixel(0, 5, COL_YELLOW);
+ }
+ }
+ bitmap.Mirror(BmpMirrorFlags::Horizontal);
+ {
+ BitmapReadAccess read(bitmap);
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_BLACK), read.GetColor(0, 10));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_WHITE), read.GetColor(10, 10));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_RED), read.GetColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_BLUE), read.GetColor(10, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_GREEN), read.GetColor(5, 10));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_YELLOW), read.GetColor(0, 5));
+ }
+ bitmap.Mirror(BmpMirrorFlags::Vertical);
+ {
+ BitmapReadAccess read(bitmap);
+ // Now is effectively mirrored in both directions.
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_BLACK), read.GetColor(10, 10));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_WHITE), read.GetColor(0, 10));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_RED), read.GetColor(10, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_BLUE), read.GetColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_GREEN), read.GetColor(5, 10));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_YELLOW), read.GetColor(10, 5));
+ }
+ bitmap.Mirror(BmpMirrorFlags::Vertical | BmpMirrorFlags::Horizontal);
+ {
+ BitmapReadAccess read(bitmap);
+ // Now is back the original.
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_BLACK), read.GetColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_WHITE), read.GetColor(10, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_RED), read.GetColor(0, 10));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_BLUE), read.GetColor(10, 10));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_GREEN), read.GetColor(5, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_YELLOW), read.GetColor(0, 5));
+ }
+ }
+}
+
+void BitmapTest::testCroppedDownsampledBitmap()
+{
+ Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);
+ Bitmap aDownsampledBmp(vcl::bitmap::GetDownsampledBitmap(Size(10, 10), Point(20, 20),
+ Size(5, 5), aBitmap, 72, 72));
+ CPPUNIT_ASSERT(aDownsampledBmp.IsEmpty());
+}
+
+void BitmapTest::testCrop()
+{
+ Bitmap aBitmap(Bitmap(Size(16, 16), vcl::PixelFormat::N24_BPP));
+
+ {
+ Bitmap aCroppedBmp(aBitmap);
+ CPPUNIT_ASSERT_MESSAGE("Crop was fully outside of bitmap bounds",
+ !aCroppedBmp.Crop(tools::Rectangle(Point(20, 20), Size(5, 5))));
+ CPPUNIT_ASSERT_EQUAL(Size(16, 16), aCroppedBmp.GetSizePixel());
+ }
+
+ {
+ Bitmap aCroppedBmp(aBitmap);
+ CPPUNIT_ASSERT_MESSAGE("Crop same size as bitmap",
+ !aCroppedBmp.Crop(tools::Rectangle(Point(0, 0), Size(16, 16))));
+ CPPUNIT_ASSERT_EQUAL(Size(16, 16), aCroppedBmp.GetSizePixel());
+ }
+
+ {
+ Bitmap aCroppedBmp(aBitmap);
+ CPPUNIT_ASSERT_MESSAGE("Crop larger than bitmap",
+ !aCroppedBmp.Crop(tools::Rectangle(Point(0, 0), Size(100, 100))));
+ CPPUNIT_ASSERT_EQUAL(Size(16, 16), aCroppedBmp.GetSizePixel());
+ }
+
+ {
+ Bitmap aCroppedBmp(aBitmap);
+ CPPUNIT_ASSERT_MESSAGE("Crop partially overcrops bitmap",
+ aCroppedBmp.Crop(tools::Rectangle(Point(10, 10), Size(100, 100))));
+ CPPUNIT_ASSERT_EQUAL(Size(6, 6), aCroppedBmp.GetSizePixel());
+ }
+
+ {
+ Bitmap aCroppedBmp(aBitmap);
+ CPPUNIT_ASSERT_MESSAGE("Crop inside bitmap",
+ aCroppedBmp.Crop(tools::Rectangle(Point(5, 5), Size(10, 10))));
+ CPPUNIT_ASSERT_EQUAL(Size(10, 10), aCroppedBmp.GetSizePixel());
+ }
+}
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BitmapTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/BmpFilterTest.cxx b/vcl/qa/cppunit/BmpFilterTest.cxx
new file mode 100644
index 0000000000..3669f99071
--- /dev/null
+++ b/vcl/qa/cppunit/BmpFilterTest.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/.
+ */
+
+#include <test/bootstrapfixture.hxx>
+
+#include <vcl/bitmapex.hxx>
+#include <tools/stream.hxx>
+#include <filter/BmpReader.hxx>
+#include <unotools/tempfile.hxx>
+
+class BmpFilterTest : public test::BootstrapFixture
+{
+public:
+ OUString maDataUrl;
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(maDataUrl) + sFileName;
+ }
+ BmpFilterTest()
+ : maDataUrl(u"/vcl/qa/cppunit/data/"_ustr)
+ {
+ }
+};
+
+CPPUNIT_TEST_FIXTURE(BmpFilterTest, testBMP_RGB_888)
+{
+ SvFileStream aFileStream(getFullUrl(u"BMP_R8G8B8.bmp"), StreamMode::READ);
+ Graphic aGraphic;
+ CPPUNIT_ASSERT(BmpReader(aFileStream, aGraphic));
+ auto aBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, aBitmap.getPixelFormat());
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(9, 9));
+ CPPUNIT_ASSERT_EQUAL(Color(0x72, 0xd1, 0xc8), aBitmap.GetPixelColor(1, 1));
+ CPPUNIT_ASSERT_EQUAL(Color(0x72, 0xd1, 0xc8), aBitmap.GetPixelColor(8, 8));
+}
+
+CPPUNIT_TEST_FIXTURE(BmpFilterTest, testBMP_RGB_565)
+{
+ SvFileStream aFileStream(getFullUrl(u"BMP_R5G6B5.bmp"), StreamMode::READ);
+ Graphic aGraphic;
+ CPPUNIT_ASSERT(BmpReader(aFileStream, aGraphic));
+ auto aBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, aBitmap.getPixelFormat());
+
+ // White is not completely white
+ //CPPUNIT_ASSERT_EQUAL(Color(0xf8, 0xfc, 0xf8), aBitmap.GetPixelColor(0, 0));
+ //CPPUNIT_ASSERT_EQUAL(Color(0xf8, 0xfc, 0xf8), aBitmap.GetPixelColor(9, 9));
+
+ //CPPUNIT_ASSERT_EQUAL(Color(0x70, 0xd0, 0xc0), aBitmap.GetPixelColor(1, 1));
+ //CPPUNIT_ASSERT_EQUAL(Color(0x70, 0xd0, 0xc0), aBitmap.GetPixelColor(8, 8));
+}
+
+CPPUNIT_TEST_FIXTURE(BmpFilterTest, testBMP_32_ARGB_8888)
+{
+ SvFileStream aFileStream(getFullUrl(u"BMP_A8R8G8B8.bmp"), StreamMode::READ);
+ Graphic aGraphic;
+ CPPUNIT_ASSERT(BmpReader(aFileStream, aGraphic));
+ auto aBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Height());
+
+ //CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, aBitmap.GetPixelColor(0, 0));
+ //CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aBitmap.GetPixelColor(2, 0));
+ //CPPUNIT_ASSERT_EQUAL(COL_YELLOW, aBitmap.GetPixelColor(0, 2));
+ //CPPUNIT_ASSERT_EQUAL(COL_LIGHTGREEN, aBitmap.GetPixelColor(2, 2));
+}
+
+CPPUNIT_TEST_FIXTURE(BmpFilterTest, testBMP_Paint_24_RGB_888)
+{
+ SvFileStream aFileStream(getFullUrl(u"BMP_Paint_24bit.bmp"), StreamMode::READ);
+ Graphic aGraphic;
+ CPPUNIT_ASSERT(BmpReader(aFileStream, aGraphic));
+ auto aBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, aBitmap.getPixelFormat());
+
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, aBitmap.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aBitmap.GetPixelColor(2, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, aBitmap.GetPixelColor(0, 2));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTGREEN, aBitmap.GetPixelColor(2, 2));
+}
+
+CPPUNIT_TEST_FIXTURE(BmpFilterTest, testBMP_Index_1BPP)
+{
+ SvFileStream aFileStream(getFullUrl(u"BMP_Paint_1bit.bmp"), StreamMode::READ);
+ Graphic aGraphic;
+ CPPUNIT_ASSERT(BmpReader(aFileStream, aGraphic));
+ auto aBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat());
+
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, aBitmap.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, aBitmap.GetPixelColor(0, 2));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, aBitmap.GetPixelColor(2, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(2, 2));
+}
+
+CPPUNIT_TEST_FIXTURE(BmpFilterTest, testBMP_Index_4BPP)
+{
+ SvFileStream aFileStream(getFullUrl(u"BMP_Paint_4bit.bmp"), StreamMode::READ);
+ Graphic aGraphic;
+ CPPUNIT_ASSERT(BmpReader(aFileStream, aGraphic));
+ auto aBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat());
+
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, aBitmap.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aBitmap.GetPixelColor(2, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, aBitmap.GetPixelColor(0, 2));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTGREEN, aBitmap.GetPixelColor(2, 2));
+}
+
+CPPUNIT_TEST_FIXTURE(BmpFilterTest, testBMP_Index_8BPP)
+{
+ SvFileStream aFileStream(getFullUrl(u"BMP_Paint_8bit.bmp"), StreamMode::READ);
+ Graphic aGraphic;
+ CPPUNIT_ASSERT(BmpReader(aFileStream, aGraphic));
+ auto aBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat());
+
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, aBitmap.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aBitmap.GetPixelColor(2, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, aBitmap.GetPixelColor(0, 2));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTGREEN, aBitmap.GetPixelColor(2, 2));
+}
+
+CPPUNIT_TEST_FIXTURE(BmpFilterTest, testBMP_Index_8BPP_RLE)
+{
+ SvFileStream aFileStream(getFullUrl(u"BMP_8bit_RLE.bmp"), StreamMode::READ);
+ Graphic aGraphic;
+ CPPUNIT_ASSERT(BmpReader(aFileStream, aGraphic));
+ auto aBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat());
+
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, aBitmap.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aBitmap.GetPixelColor(2, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, aBitmap.GetPixelColor(0, 2));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTGREEN, aBitmap.GetPixelColor(2, 2));
+}
+
+CPPUNIT_TEST_FIXTURE(BmpFilterTest, testBMP_V4)
+{
+ SvFileStream aFileStream(getFullUrl(u"BMP_RLE.bmp"), StreamMode::READ);
+ Graphic aGraphic;
+ CPPUNIT_ASSERT(BmpReader(aFileStream, aGraphic));
+ auto aBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat());
+
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, aBitmap.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aBitmap.GetPixelColor(2, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, aBitmap.GetPixelColor(0, 2));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTGREEN, aBitmap.GetPixelColor(2, 2));
+}
+
+CPPUNIT_TEST_FIXTURE(BmpFilterTest, testBMP_V3)
+{
+ SvFileStream aFileStream(getFullUrl(u"BMP_RLE_V3.bmp"), StreamMode::READ);
+ Graphic aGraphic;
+ CPPUNIT_ASSERT(BmpReader(aFileStream, aGraphic));
+ auto aBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat());
+
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, aBitmap.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aBitmap.GetPixelColor(2, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, aBitmap.GetPixelColor(0, 2));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTGREEN, aBitmap.GetPixelColor(2, 2));
+}
+
+CPPUNIT_TEST_FIXTURE(BmpFilterTest, testBMP_V2)
+{
+ SvFileStream aFileStream(getFullUrl(u"BMP_RLE_V2.bmp"), StreamMode::READ);
+ Graphic aGraphic;
+ CPPUNIT_ASSERT(BmpReader(aFileStream, aGraphic));
+ auto aBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat());
+
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, aBitmap.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aBitmap.GetPixelColor(2, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, aBitmap.GetPixelColor(0, 2));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTGREEN, aBitmap.GetPixelColor(2, 2));
+}
+
+CPPUNIT_TEST_FIXTURE(BmpFilterTest, testTdf73523)
+{
+ SvFileStream aFileStream(getFullUrl(u"tdf73523.bmp"), StreamMode::READ);
+ Graphic aGraphic;
+ CPPUNIT_ASSERT(BmpReader(aFileStream, aGraphic));
+ auto aBitmap = aGraphic.GetBitmapEx();
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: c[80000000]
+ // - Actual : c[00000000]
+ // i.e. the pixel is red not black
+ CPPUNIT_ASSERT_EQUAL(COL_RED, aBitmap.GetPixelColor(0, 0));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/FontFeatureTest.cxx b/vcl/qa/cppunit/FontFeatureTest.cxx
new file mode 100644
index 0000000000..9d47ceb170
--- /dev/null
+++ b/vcl/qa/cppunit/FontFeatureTest.cxx
@@ -0,0 +1,441 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+#include <config_features.h>
+#include <config_fonts.h>
+#include <cppunit/TestAssert.h>
+
+#include <vcl/font/Feature.hxx>
+#include <vcl/font/FeatureParser.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/svapp.hxx>
+
+class FontFeatureTest : public test::BootstrapFixture
+{
+public:
+ FontFeatureTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ void testGetFontFeaturesGraphite();
+ void testGetFontFeaturesOpenType();
+ void testGetFontFeaturesOpenTypeEnum();
+ void testParseFeature();
+
+ CPPUNIT_TEST_SUITE(FontFeatureTest);
+ CPPUNIT_TEST(testGetFontFeaturesGraphite);
+ CPPUNIT_TEST(testGetFontFeaturesOpenType);
+ CPPUNIT_TEST(testGetFontFeaturesOpenTypeEnum);
+ CPPUNIT_TEST(testParseFeature);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void FontFeatureTest::testGetFontFeaturesGraphite()
+{
+#if HAVE_MORE_FONTS
+ ScopedVclPtrInstance<VirtualDevice> aVDev(*Application::GetDefaultDevice(),
+ DeviceFormat::WITH_ALPHA);
+ aVDev->SetOutputSizePixel(Size(10, 10));
+
+ OUString aFontName("Linux Libertine G");
+ CPPUNIT_ASSERT(aVDev->IsFontAvailable(aFontName));
+
+ vcl::Font aFont = aVDev->GetFont();
+ aFont.SetFamilyName(aFontName);
+ aFont.SetWeight(FontWeight::WEIGHT_NORMAL);
+ aFont.SetItalic(FontItalic::ITALIC_NORMAL);
+ aFont.SetWidthType(FontWidth::WIDTH_NORMAL);
+ aVDev->SetFont(aFont);
+
+ std::vector<vcl::font::Feature> rFontFeatures;
+ CPPUNIT_ASSERT(aVDev->GetFontFeatures(rFontFeatures));
+
+ OUString aFeaturesString;
+ for (vcl::font::Feature const& rFeature : rFontFeatures)
+ {
+ aFeaturesString += vcl::font::featureCodeAsString(rFeature.m_nCode) + " ";
+ }
+
+ CPPUNIT_ASSERT_EQUAL(size_t(53), rFontFeatures.size());
+
+ CPPUNIT_ASSERT_EQUAL(OUString("c2sc case dlig fina frac hlig liga lnum "
+ "locl onum pnum sa01 sa02 sa03 sa04 sa05 "
+ "sa06 sa07 sa08 salt sinf smcp ss01 ss02 "
+ "ss03 sups tnum zero ingl cpsp lith litt "
+ "itlc para algn arti circ dash dbls foot "
+ "frsp grkn hang lng minu nfsp name quot "
+ "texm thou vari caps ligc "),
+ aFeaturesString);
+
+ // Check C2SC feature
+ {
+ vcl::font::Feature& rFeature = rFontFeatures[0];
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("c2sc"), rFeature.m_nCode);
+
+ vcl::font::FeatureDefinition& rFracFeatureDefinition = rFeature.m_aDefinition;
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("c2sc"), rFracFeatureDefinition.getCode());
+ CPPUNIT_ASSERT(!rFracFeatureDefinition.getDescription().isEmpty());
+ CPPUNIT_ASSERT_EQUAL(vcl::font::FeatureParameterType::BOOL,
+ rFracFeatureDefinition.getType());
+
+ CPPUNIT_ASSERT_EQUAL(size_t(0), rFracFeatureDefinition.getEnumParameters().size());
+ }
+
+ // Check FRAC feature
+ {
+ vcl::font::Feature& rFeature = rFontFeatures[4];
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("frac"), rFeature.m_nCode);
+
+ vcl::font::FeatureDefinition& rFracFeatureDefinition = rFeature.m_aDefinition;
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("frac"), rFracFeatureDefinition.getCode());
+ CPPUNIT_ASSERT(!rFracFeatureDefinition.getDescription().isEmpty());
+ CPPUNIT_ASSERT_EQUAL(vcl::font::FeatureParameterType::ENUM,
+ rFracFeatureDefinition.getType());
+
+ CPPUNIT_ASSERT_EQUAL(size_t(3), rFracFeatureDefinition.getEnumParameters().size());
+
+ vcl::font::FeatureParameter const& rParameter1
+ = rFracFeatureDefinition.getEnumParameters()[0];
+ CPPUNIT_ASSERT_EQUAL(uint32_t(0), rParameter1.getCode());
+ CPPUNIT_ASSERT(!rParameter1.getDescription().isEmpty());
+
+ vcl::font::FeatureParameter const& rParameter2
+ = rFracFeatureDefinition.getEnumParameters()[1];
+ CPPUNIT_ASSERT_EQUAL(uint32_t(1), rParameter2.getCode());
+ CPPUNIT_ASSERT(!rParameter2.getDescription().isEmpty());
+
+ vcl::font::FeatureParameter const& rParameter3
+ = rFracFeatureDefinition.getEnumParameters()[2];
+ CPPUNIT_ASSERT_EQUAL(uint32_t(2), rParameter3.getCode());
+ CPPUNIT_ASSERT(!rParameter2.getDescription().isEmpty());
+ }
+
+ aVDev.disposeAndClear();
+#endif // HAVE_MORE_FONTS
+}
+
+void FontFeatureTest::testGetFontFeaturesOpenType()
+{
+#if HAVE_MORE_FONTS
+ ScopedVclPtrInstance<VirtualDevice> aVDev(*Application::GetDefaultDevice(),
+ DeviceFormat::WITH_ALPHA);
+ aVDev->SetOutputSizePixel(Size(10, 10));
+
+ OUString aFontName("Amiri");
+ CPPUNIT_ASSERT(aVDev->IsFontAvailable(aFontName));
+
+ vcl::Font aFont = aVDev->GetFont();
+ aFont.SetFamilyName(aFontName);
+ aFont.SetWeight(FontWeight::WEIGHT_NORMAL);
+ aFont.SetItalic(FontItalic::ITALIC_NORMAL);
+ aFont.SetWidthType(FontWidth::WIDTH_NORMAL);
+ aVDev->SetFont(aFont);
+
+ std::vector<vcl::font::Feature> rFontFeatures;
+ CPPUNIT_ASSERT(aVDev->GetFontFeatures(rFontFeatures));
+
+ OUString aFeaturesString;
+ for (vcl::font::Feature const& rFeature : rFontFeatures)
+ aFeaturesString += vcl::font::featureCodeAsString(rFeature.m_nCode) + " ";
+
+ CPPUNIT_ASSERT_EQUAL(size_t(17), rFontFeatures.size());
+
+ CPPUNIT_ASSERT_EQUAL(OUString("calt calt dnom liga numr pnum ss01 ss02 "
+ "ss03 ss04 ss05 ss06 ss07 ss08 kern kern "
+ "ss05 "),
+ aFeaturesString);
+
+ // Check ss01 feature
+ {
+ vcl::font::Feature& rFeature = rFontFeatures[6];
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("ss01"), rFeature.m_nCode);
+
+ vcl::font::FeatureDefinition& rFeatureDefinition = rFeature.m_aDefinition;
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("ss01"), rFeatureDefinition.getCode());
+ CPPUNIT_ASSERT_EQUAL(OUString("Low Baa dot following a Raa or Waw"),
+ rFeatureDefinition.getDescription());
+ CPPUNIT_ASSERT_EQUAL(vcl::font::FeatureParameterType::BOOL, rFeatureDefinition.getType());
+
+ CPPUNIT_ASSERT_EQUAL(size_t(0), rFeatureDefinition.getEnumParameters().size());
+ }
+
+ aVDev.disposeAndClear();
+#endif // HAVE_MORE_FONTS
+}
+
+void FontFeatureTest::testGetFontFeaturesOpenTypeEnum()
+{
+#if HAVE_MORE_FONTS
+ ScopedVclPtrInstance<VirtualDevice> aVDev(*Application::GetDefaultDevice(),
+ DeviceFormat::WITH_ALPHA);
+ aVDev->SetOutputSizePixel(Size(10, 10));
+
+ OUString aFontName("Reem Kufi");
+ CPPUNIT_ASSERT(aVDev->IsFontAvailable(aFontName));
+
+ vcl::Font aFont = aVDev->GetFont();
+ aFont.SetFamilyName(aFontName);
+ aFont.SetWeight(FontWeight::WEIGHT_NORMAL);
+ aFont.SetItalic(FontItalic::ITALIC_NORMAL);
+ aFont.SetWidthType(FontWidth::WIDTH_NORMAL);
+ aVDev->SetFont(aFont);
+
+ std::vector<vcl::font::Feature> rFontFeatures;
+ CPPUNIT_ASSERT(aVDev->GetFontFeatures(rFontFeatures));
+
+ OUString aFeaturesString;
+ for (vcl::font::Feature const& rFeature : rFontFeatures)
+ aFeaturesString += vcl::font::featureCodeAsString(rFeature.m_nCode) + " ";
+
+ CPPUNIT_ASSERT_EQUAL(size_t(10), rFontFeatures.size());
+
+ CPPUNIT_ASSERT_EQUAL(OUString("aalt case cv01 cv02 cv03 frac ordn sups "
+ "zero kern "),
+ aFeaturesString);
+
+ // Check aalt feature
+ {
+ vcl::font::Feature& rFeature = rFontFeatures[0];
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("aalt"), rFeature.m_nCode);
+
+ vcl::font::FeatureDefinition& rFeatureDefinition = rFeature.m_aDefinition;
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("aalt"), rFeatureDefinition.getCode());
+ CPPUNIT_ASSERT(!rFeatureDefinition.getDescription().isEmpty());
+ CPPUNIT_ASSERT_EQUAL(vcl::font::FeatureParameterType::ENUM, rFeatureDefinition.getType());
+
+ CPPUNIT_ASSERT_EQUAL(size_t(3), rFeatureDefinition.getEnumParameters().size());
+
+ vcl::font::FeatureParameter const& rParameter1 = rFeatureDefinition.getEnumParameters()[0];
+ CPPUNIT_ASSERT_EQUAL(uint32_t(0), rParameter1.getCode());
+ CPPUNIT_ASSERT(!rParameter1.getDescription().isEmpty());
+
+ vcl::font::FeatureParameter const& rParameter2 = rFeatureDefinition.getEnumParameters()[1];
+ CPPUNIT_ASSERT_EQUAL(uint32_t(1), rParameter2.getCode());
+ CPPUNIT_ASSERT(!rParameter2.getDescription().isEmpty());
+
+ vcl::font::FeatureParameter const& rParameter3 = rFeatureDefinition.getEnumParameters()[2];
+ CPPUNIT_ASSERT_EQUAL(uint32_t(2), rParameter3.getCode());
+ CPPUNIT_ASSERT(!rParameter2.getDescription().isEmpty());
+ }
+
+ aVDev.disposeAndClear();
+#endif // HAVE_MORE_FONTS
+}
+
+void FontFeatureTest::testParseFeature()
+{
+ { // No font features specified
+ vcl::font::FeatureParser aParser(u"Font name with no features");
+ CPPUNIT_ASSERT_EQUAL(size_t(0), aParser.getFeatures().size());
+ }
+ { // One feature specified, no value
+ vcl::font::FeatureParser aParser(u"Font name:abcd");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(1), aFeatures[0].m_nValue);
+ }
+ { // One feature specified, explicit value
+ vcl::font::FeatureParser aParser(u"Font name:abcd=5");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(5), aFeatures[0].m_nValue);
+ }
+ { // One feature specified, explicit zero value
+ vcl::font::FeatureParser aParser(u"Font name:abcd=0");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(0), aFeatures[0].m_nValue);
+ }
+ { // One feature specified, using plus prefix
+ vcl::font::FeatureParser aParser(u"Font name:+abcd");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(1), aFeatures[0].m_nValue);
+ }
+ { // One feature specified, using minus prefix
+ vcl::font::FeatureParser aParser(u"Font name:-abcd");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(0), aFeatures[0].m_nValue);
+ }
+ { // One feature specified, with empty character range
+ vcl::font::FeatureParser aParser(u"Font name:abcd[]");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(1), aFeatures[0].m_nValue);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(0), aFeatures[0].m_nStart);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(-1), aFeatures[0].m_nEnd);
+ }
+ { // One feature specified, with empty character range
+ vcl::font::FeatureParser aParser(u"Font name:abcd[:]");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(1), aFeatures[0].m_nValue);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(0), aFeatures[0].m_nStart);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(-1), aFeatures[0].m_nEnd);
+ }
+ { // One feature specified, with start character range
+ vcl::font::FeatureParser aParser(u"Font name:abcd[3:]");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(1), aFeatures[0].m_nValue);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(3), aFeatures[0].m_nStart);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(-1), aFeatures[0].m_nEnd);
+ }
+ { // One feature specified, with end character range
+ vcl::font::FeatureParser aParser(u"Font name:abcd[:3]");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(1), aFeatures[0].m_nValue);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(0), aFeatures[0].m_nStart);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(3), aFeatures[0].m_nEnd);
+ }
+ { // One feature specified, with character range
+ vcl::font::FeatureParser aParser(u"Font name:abcd[3:6]");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(1), aFeatures[0].m_nValue);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(3), aFeatures[0].m_nStart);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(6), aFeatures[0].m_nEnd);
+ }
+ { // One feature specified, with character range
+ vcl::font::FeatureParser aParser(u"Font name:abcd[3]");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(1), aFeatures[0].m_nValue);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(3), aFeatures[0].m_nStart);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(4), aFeatures[0].m_nEnd);
+ }
+ { // One feature specified, with character range and value
+ vcl::font::FeatureParser aParser(u"Font name:abcd[3:6]=2");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(2), aFeatures[0].m_nValue);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(3), aFeatures[0].m_nStart);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(6), aFeatures[0].m_nEnd);
+ }
+ { // One feature specified, with character range and 0 value
+ vcl::font::FeatureParser aParser(u"Font name:abcd[3:6]=0");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(0), aFeatures[0].m_nValue);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(3), aFeatures[0].m_nStart);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(6), aFeatures[0].m_nEnd);
+ }
+ { // One feature specified, with character range and minus prefix
+ vcl::font::FeatureParser aParser(u"Font name:-abcd[3:6]");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(0), aFeatures[0].m_nValue);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(3), aFeatures[0].m_nStart);
+ CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(6), aFeatures[0].m_nEnd);
+ }
+ { // One feature specified, with CSS on
+ vcl::font::FeatureParser aParser(u"Font name:\"abcd\" on");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(1), aFeatures[0].m_nValue);
+ }
+ { // One feature specified, with CSS off
+ vcl::font::FeatureParser aParser(u"Font name:'abcd' off");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(0), aFeatures[0].m_nValue);
+ }
+ { // One feature specified, with CSS value
+ vcl::font::FeatureParser aParser(u"Font name:\"abcd\" 2");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(2), aFeatures[0].m_nValue);
+ }
+ { // Multiple features specified, no values
+ vcl::font::FeatureParser aParser(u"Font name:abcd&bcde&efgh");
+ CPPUNIT_ASSERT_EQUAL(size_t(3), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(1), aFeatures[0].m_nValue);
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("bcde"), aFeatures[1].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(1), aFeatures[1].m_nValue);
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("efgh"), aFeatures[2].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(1), aFeatures[2].m_nValue);
+ }
+ {
+ // Multiple features specified, explicit values
+ // Only 4 char parameter names supported - "toolong" is too long and ignored
+ vcl::font::FeatureParser aParser(u"Font name:abcd=1&bcde=0&toolong=1&cdef=3");
+ CPPUNIT_ASSERT_EQUAL(size_t(3), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(1), aFeatures[0].m_nValue);
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("bcde"), aFeatures[1].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(0), aFeatures[1].m_nValue);
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("cdef"), aFeatures[2].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(3), aFeatures[2].m_nValue);
+ }
+ {
+ // Special case - "lang" is parsed specially and access separately not as a feature.
+
+ vcl::font::FeatureParser aParser(u"Font name:abcd=1&lang=slo");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
+ auto aFeatures = aParser.getFeatures();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].m_nTag);
+ CPPUNIT_ASSERT_EQUAL(uint32_t(1), aFeatures[0].m_nValue);
+
+ CPPUNIT_ASSERT_EQUAL(OUString("slo"), aParser.getLanguage());
+ }
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(FontFeatureTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/GraphicDescriptorTest.cxx b/vcl/qa/cppunit/GraphicDescriptorTest.cxx
new file mode 100644
index 0000000000..a0bbd02716
--- /dev/null
+++ b/vcl/qa/cppunit/GraphicDescriptorTest.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 <unotest/bootstrapfixturebase.hxx>
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+
+#include <vcl/graphicfilter.hxx>
+#include <tools/stream.hxx>
+
+using namespace css;
+
+namespace
+{
+class GraphicDescriptorTest : public test::BootstrapFixtureBase
+{
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(u"/vcl/qa/cppunit/data/") + sFileName;
+ }
+ void testDetectPNG();
+ void testDetectJPG();
+ void testDetectGIF();
+ void testDetectTIF();
+ void testDetectBMP();
+ void testDetectWEBP();
+ void testDetectEMF();
+ void testDetectWMF();
+
+ CPPUNIT_TEST_SUITE(GraphicDescriptorTest);
+ CPPUNIT_TEST(testDetectPNG);
+ CPPUNIT_TEST(testDetectJPG);
+ CPPUNIT_TEST(testDetectGIF);
+ CPPUNIT_TEST(testDetectTIF);
+ CPPUNIT_TEST(testDetectBMP);
+ CPPUNIT_TEST(testDetectWEBP);
+ CPPUNIT_TEST(testDetectEMF);
+ CPPUNIT_TEST(testDetectWMF);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+BitmapEx createBitmap()
+{
+ Bitmap aBitmap(Size(100, 100), vcl::PixelFormat::N24_BPP);
+ aBitmap.Erase(COL_LIGHTRED);
+
+ return BitmapEx(aBitmap);
+}
+
+void createBitmapAndExportForType(SvStream& rStream, std::u16string_view sType)
+{
+ BitmapEx aBitmapEx = createBitmap();
+
+ uno::Sequence<beans::PropertyValue> aFilterData;
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ sal_uInt16 nFilterFormat = rGraphicFilter.GetExportFormatNumberForShortName(sType);
+ rGraphicFilter.ExportGraphic(aBitmapEx, u"none", rStream, nFilterFormat, &aFilterData);
+
+ rStream.Seek(STREAM_SEEK_TO_BEGIN);
+}
+
+void GraphicDescriptorTest::testDetectPNG()
+{
+ SvMemoryStream aStream;
+ createBitmapAndExportForType(aStream, u"png");
+
+ GraphicDescriptor aDescriptor(aStream, nullptr);
+ aDescriptor.Detect(true);
+
+ CPPUNIT_ASSERT_EQUAL(GraphicFileFormat::PNG, aDescriptor.GetFileFormat());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aDescriptor.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aDescriptor.GetSizePixel().Height());
+}
+
+void GraphicDescriptorTest::testDetectJPG()
+{
+ SvMemoryStream aStream;
+ createBitmapAndExportForType(aStream, u"jpg");
+
+ GraphicDescriptor aDescriptor(aStream, nullptr);
+ aDescriptor.Detect(true);
+
+ CPPUNIT_ASSERT_EQUAL(GraphicFileFormat::JPG, aDescriptor.GetFileFormat());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aDescriptor.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aDescriptor.GetSizePixel().Height());
+}
+
+void GraphicDescriptorTest::testDetectGIF()
+{
+ SvMemoryStream aStream;
+ createBitmapAndExportForType(aStream, u"gif");
+
+ GraphicDescriptor aDescriptor(aStream, nullptr);
+ aDescriptor.Detect(true);
+
+ CPPUNIT_ASSERT_EQUAL(GraphicFileFormat::GIF, aDescriptor.GetFileFormat());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aDescriptor.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aDescriptor.GetSizePixel().Height());
+}
+
+void GraphicDescriptorTest::testDetectTIF()
+{
+ SvMemoryStream aStream;
+ createBitmapAndExportForType(aStream, u"tif");
+
+ GraphicDescriptor aDescriptor(aStream, nullptr);
+ aDescriptor.Detect(true);
+
+ CPPUNIT_ASSERT_EQUAL(GraphicFileFormat::TIF, aDescriptor.GetFileFormat());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aDescriptor.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aDescriptor.GetSizePixel().Height());
+}
+
+void GraphicDescriptorTest::testDetectBMP()
+{
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ SvFileStream aFileStream(getFullUrl(u"graphic-descriptor-mapmode.bmp"), StreamMode::READ);
+
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aFileStream);
+
+ CPPUNIT_ASSERT(!aGraphic.isAvailable());
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 2 (MapUnit::MapMM)
+ // - Actual : 0 (MapUnit::Map100thMM)
+ // i.e. lazy load and load created different map modes, breaking the contour polygon code in
+ // Writer.
+ CPPUNIT_ASSERT_EQUAL(MapUnit::MapMM, aGraphic.GetPrefMapMode().GetMapUnit());
+ aGraphic.makeAvailable();
+ CPPUNIT_ASSERT_EQUAL(MapUnit::MapMM, aGraphic.GetPrefMapMode().GetMapUnit());
+}
+
+void GraphicDescriptorTest::testDetectWEBP()
+{
+ SvMemoryStream aStream;
+ createBitmapAndExportForType(aStream, u"webp");
+
+ GraphicDescriptor aDescriptor(aStream, nullptr);
+ aDescriptor.Detect(true);
+
+ CPPUNIT_ASSERT_EQUAL(GraphicFileFormat::WEBP, aDescriptor.GetFileFormat());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aDescriptor.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aDescriptor.GetSizePixel().Height());
+}
+
+void GraphicDescriptorTest::testDetectEMF()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.emf"), StreamMode::READ);
+ GraphicDescriptor aDescriptor(aFileStream, nullptr);
+ aDescriptor.Detect(true);
+ CPPUNIT_ASSERT_EQUAL(GraphicFileFormat::EMF, aDescriptor.GetFileFormat());
+ // Test that Bounds/Frame values are fetched from header
+ CPPUNIT_ASSERT_EQUAL(tools::Long(142), aDescriptor.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(142), aDescriptor.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(300), aDescriptor.GetSize_100TH_MM().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(300), aDescriptor.GetSize_100TH_MM().Height());
+}
+
+void GraphicDescriptorTest::testDetectWMF()
+{
+ // Test placeable wmf
+ {
+ SvFileStream aFileStream(m_directories.getURLFromSrc(u"/emfio/qa/cppunit/wmf/data/")
+ + "tdf88163-wrong-font-size.wmf",
+ StreamMode::READ);
+ GraphicDescriptor aDescriptor(aFileStream, nullptr);
+ aDescriptor.Detect(true);
+ CPPUNIT_ASSERT_EQUAL(GraphicFileFormat::WMF, aDescriptor.GetFileFormat());
+ }
+ // Test non-placeable wmf
+ {
+ SvFileStream aFileStream(m_directories.getURLFromSrc(u"/emfio/qa/cppunit/wmf/data/")
+ + "tdf88163-non-placeable.wmf",
+ StreamMode::READ);
+ GraphicDescriptor aDescriptor(aFileStream, nullptr);
+ aDescriptor.Detect(true);
+ CPPUNIT_ASSERT_EQUAL(GraphicFileFormat::WMF, aDescriptor.GetFileFormat());
+ }
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(GraphicDescriptorTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/GraphicFormatDetectorTest.cxx b/vcl/qa/cppunit/GraphicFormatDetectorTest.cxx
new file mode 100644
index 0000000000..13c612e46e
--- /dev/null
+++ b/vcl/qa/cppunit/GraphicFormatDetectorTest.cxx
@@ -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/.
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <unotest/bootstrapfixturebase.hxx>
+
+#include <graphic/GraphicFormatDetector.hxx>
+#include <graphic/DetectorTools.hxx>
+
+#include <tools/stream.hxx>
+#include <o3tl/string_view.hxx>
+
+using namespace css;
+
+namespace
+{
+class GraphicFormatDetectorTest : public test::BootstrapFixtureBase
+{
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(u"/vcl/qa/cppunit/data/") + sFileName;
+ }
+
+ void testDetectMET();
+ void testDetectBMP();
+ void testDetectWMF();
+ void testDetectWMZ();
+ void testDetectPCX();
+ void testDetectJPG();
+ void testDetectPNG();
+ void testDetectAPNG();
+ void testDetectGIF();
+ void testDetectPSD();
+ void testDetectTGA();
+ void testDetectTIF();
+ void testDetectXBM();
+ void testDetectXPM();
+ void testDetectSVG();
+ void testDetectSVGZ();
+ void testDetectPDF();
+ void testDetectEPS();
+ void testDetectWEBP();
+ void testDetectEMF();
+ void testDetectEMZ();
+ void testMatchArray();
+ void testCheckArrayForMatchingStrings();
+
+ CPPUNIT_TEST_SUITE(GraphicFormatDetectorTest);
+ CPPUNIT_TEST(testDetectMET);
+ CPPUNIT_TEST(testDetectBMP);
+ CPPUNIT_TEST(testDetectWMF);
+ CPPUNIT_TEST(testDetectWMZ);
+ CPPUNIT_TEST(testDetectPCX);
+ CPPUNIT_TEST(testDetectJPG);
+ CPPUNIT_TEST(testDetectPNG);
+ CPPUNIT_TEST(testDetectAPNG);
+ CPPUNIT_TEST(testDetectGIF);
+ CPPUNIT_TEST(testDetectPSD);
+ CPPUNIT_TEST(testDetectTGA);
+ CPPUNIT_TEST(testDetectTIF);
+ CPPUNIT_TEST(testDetectXBM);
+ CPPUNIT_TEST(testDetectXPM);
+ CPPUNIT_TEST(testDetectSVG);
+ CPPUNIT_TEST(testDetectSVGZ);
+ CPPUNIT_TEST(testDetectPDF);
+ CPPUNIT_TEST(testDetectEPS);
+ CPPUNIT_TEST(testDetectWEBP);
+ CPPUNIT_TEST(testDetectEMF);
+ CPPUNIT_TEST(testDetectEMZ);
+ CPPUNIT_TEST(testMatchArray);
+ CPPUNIT_TEST(testCheckArrayForMatchingStrings);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void GraphicFormatDetectorTest::testDetectMET()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.met"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "MET");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkMET());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("MET"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectBMP()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.bmp"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "BMP");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkBMP());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("BMP"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectWMF()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.wmf"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "WMF");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkWMF());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("WMF"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectWMZ()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.wmz"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "WMZ");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkWMF());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("WMZ"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectPCX()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.pcx"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "PCX");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkPCX());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("PCX"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectJPG()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.jpg"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "JPG");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkJPG());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("JPG"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectPNG()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.png"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "PNG");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkPNG());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("PNG"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectAPNG()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.apng"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "APNG");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkAPNG());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("APNG"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectGIF()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.gif"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "GIF");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkGIF());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("GIF"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectPSD()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.psd"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "PSD");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkPSD());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("PSD"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectTGA()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.tga"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "TGA");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkTGA());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension("TGA"); // detection is based on extension only
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("TGA"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectTIF()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.tif"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "TIF");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkTIF());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("TIF"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectXBM()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.xbm"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "XBM");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkXBM());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("XBM"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectXPM()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.xpm"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "XPM");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkXPM());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("XPM"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectSVG()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.svg"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "SVG");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkSVG());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("SVG"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectSVGZ()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.svgz"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "SVGZ");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkSVG());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("SVGZ"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectPDF()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.pdf"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "PDF");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkPDF());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("PDF"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectEPS()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.eps"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "EPS");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkEPS());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("EPS"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectWEBP()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.webp"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "WEBP");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkWEBP());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("WEBP"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectEMF()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.emf"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "EMF");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkEMF());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("EMF"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testDetectEMZ()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.emz"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "EMZ");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkEMF());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(vcl::peekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("EMZ"), rFormatExtension);
+}
+
+void GraphicFormatDetectorTest::testMatchArray()
+{
+ std::string aString("<?xml version=\"1.0\" standalone=\"no\"?>\n"
+ "<svg width=\"5cm\" height=\"4cm\" version=\"1.1\"\n"
+ "xmlns=\"http://www.w3.org/2000/svg\">\n"
+ "</svg>");
+
+ const char* pCompleteStringPointer = aString.c_str();
+ const char* pMatchPointer;
+ int nCheckSize = aString.size();
+
+ // Check beginning of the input string
+ pMatchPointer = vcl::matchArrayWithString(pCompleteStringPointer, nCheckSize, "<?xml"_ostr);
+ CPPUNIT_ASSERT(pMatchPointer != nullptr);
+ CPPUNIT_ASSERT_EQUAL(0, int(pMatchPointer - pCompleteStringPointer));
+ CPPUNIT_ASSERT_EQUAL(true, o3tl::starts_with(pMatchPointer, "<?xml"));
+
+ // Check middle of the input string
+ pMatchPointer = vcl::matchArrayWithString(aString.c_str(), nCheckSize, "version"_ostr);
+ CPPUNIT_ASSERT(pMatchPointer != nullptr);
+ CPPUNIT_ASSERT_EQUAL(6, int(pMatchPointer - pCompleteStringPointer));
+ CPPUNIT_ASSERT_EQUAL(true, o3tl::starts_with(pMatchPointer, "version"));
+
+ pMatchPointer = vcl::matchArrayWithString(aString.c_str(), nCheckSize, "<svg"_ostr);
+ CPPUNIT_ASSERT(pMatchPointer != nullptr);
+ CPPUNIT_ASSERT_EQUAL(38, int(pMatchPointer - pCompleteStringPointer));
+ CPPUNIT_ASSERT_EQUAL(true, o3tl::starts_with(pMatchPointer, "<svg"));
+
+ // Check end of the input string
+ pMatchPointer = vcl::matchArrayWithString(aString.c_str(), nCheckSize, "/svg>"_ostr);
+ CPPUNIT_ASSERT(pMatchPointer != nullptr);
+ CPPUNIT_ASSERT_EQUAL(119, int(pMatchPointer - pCompleteStringPointer));
+ CPPUNIT_ASSERT_EQUAL(true, o3tl::starts_with(pMatchPointer, "/svg>"));
+
+ // Check that non-existing search string
+ pMatchPointer = vcl::matchArrayWithString(aString.c_str(), nCheckSize, "none"_ostr);
+ CPPUNIT_ASSERT(pMatchPointer == nullptr);
+}
+
+void GraphicFormatDetectorTest::testCheckArrayForMatchingStrings()
+{
+ std::string aString("<?xml version=\"1.0\" standalone=\"no\"?>\n"
+ "<svg width=\"5cm\" height=\"4cm\" version=\"1.1\"\n"
+ "xmlns=\"http://www.w3.org/2000/svg\">\n"
+ "</svg>");
+ const char* pCompleteStringPointer = aString.c_str();
+ int nCheckSize = aString.size();
+ bool bResult;
+
+ // check beginning string
+ bResult
+ = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize, { "<?xml"_ostr });
+ CPPUNIT_ASSERT_EQUAL(true, bResult);
+
+ // check ending string
+ bResult
+ = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize, { "/svg>"_ostr });
+ CPPUNIT_ASSERT_EQUAL(true, bResult);
+
+ // check middle string
+ bResult
+ = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize, { "version"_ostr });
+ CPPUNIT_ASSERT_EQUAL(true, bResult);
+
+ // check beginning and then ending string
+ bResult = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize,
+ { "<?xml"_ostr, "/svg>"_ostr });
+ CPPUNIT_ASSERT_EQUAL(true, bResult);
+
+ // check ending and then beginning string
+ bResult = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize,
+ { "/svg>"_ostr, "<?xml"_ostr });
+ CPPUNIT_ASSERT_EQUAL(false, bResult);
+
+ // check middle strings
+ bResult = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize,
+ { "version"_ostr, "<svg"_ostr });
+ CPPUNIT_ASSERT_EQUAL(true, bResult);
+
+ // check beginning, middle and ending strings
+ bResult = vcl::checkArrayForMatchingStrings(
+ pCompleteStringPointer, nCheckSize,
+ { "<?xml"_ostr, "version"_ostr, "<svg"_ostr, "/svg>"_ostr });
+ CPPUNIT_ASSERT_EQUAL(true, bResult);
+
+ // check non-existing
+ bResult
+ = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize, { "none"_ostr });
+ CPPUNIT_ASSERT_EQUAL(false, bResult);
+
+ // check non-existing on the beginning
+ bResult = vcl::checkArrayForMatchingStrings(
+ pCompleteStringPointer, nCheckSize,
+ { "none"_ostr, "version"_ostr, "<svg"_ostr, "/svg>"_ostr });
+ CPPUNIT_ASSERT_EQUAL(false, bResult);
+
+ // check non-existing on the end
+ bResult = vcl::checkArrayForMatchingStrings(
+ pCompleteStringPointer, nCheckSize,
+ { "<?xml"_ostr, "version"_ostr, "<svg"_ostr, "none"_ostr });
+ CPPUNIT_ASSERT_EQUAL(false, bResult);
+
+ // check non-existing after the end
+ bResult = vcl::checkArrayForMatchingStrings(pCompleteStringPointer, nCheckSize,
+ { "<?xml"_ostr, "/svg>"_ostr, "none"_ostr });
+ CPPUNIT_ASSERT_EQUAL(false, bResult);
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(GraphicFormatDetectorTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/GraphicNativeMetadataTest.cxx b/vcl/qa/cppunit/GraphicNativeMetadataTest.cxx
new file mode 100644
index 0000000000..49bf55fa9f
--- /dev/null
+++ b/vcl/qa/cppunit/GraphicNativeMetadataTest.cxx
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <cppunit/TestAssert.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <unotest/bootstrapfixturebase.hxx>
+
+#include <vcl/GraphicNativeMetadata.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <tools/stream.hxx>
+
+using namespace css;
+
+namespace
+{
+class GraphicNativeMetadataTest : public test::BootstrapFixtureBase
+{
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(u"/vcl/qa/cppunit/data/") + sFileName;
+ }
+
+ void testReadFromGraphic();
+ void testExifRotationJpeg();
+
+ CPPUNIT_TEST_SUITE(GraphicNativeMetadataTest);
+ CPPUNIT_TEST(testReadFromGraphic);
+ CPPUNIT_TEST(testExifRotationJpeg);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void GraphicNativeMetadataTest::testReadFromGraphic()
+{
+ SvFileStream aFileStream(getFullUrl(u"Exif1_180.jpg"), StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+
+ // don't load the graphic, but try to get the metadata
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aFileStream);
+
+ {
+ GraphicNativeMetadata aMetadata;
+ aMetadata.read(aFileStream);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(1800), aMetadata.getRotation().get());
+ // just the metadata shouldn't make the graphic available
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ }
+
+ // now load, and it should still work the same
+ {
+ aGraphic.makeAvailable();
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ GraphicNativeMetadata aMetadata;
+ aMetadata.read(aFileStream);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(1800), aMetadata.getRotation().get());
+ }
+}
+
+void GraphicNativeMetadataTest::testExifRotationJpeg()
+{
+ {
+ // No rotation in metadata
+ SvFileStream aFileStream(getFullUrl(u"Exif1.jpg"), StreamMode::READ);
+ GraphicNativeMetadata aMetadata;
+ aMetadata.read(aFileStream);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(0), aMetadata.getRotation().get());
+ }
+ {
+ // Rotation 90 degree clock-wise = 270 degree counter-clock-wise
+ SvFileStream aFileStream(getFullUrl(u"Exif1_090CW.jpg"), StreamMode::READ);
+ GraphicNativeMetadata aMetadata;
+ aMetadata.read(aFileStream);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(2700), aMetadata.getRotation().get());
+ }
+ {
+ // Rotation 180 degree
+ SvFileStream aFileStream(getFullUrl(u"Exif1_180.jpg"), StreamMode::READ);
+ GraphicNativeMetadata aMetadata;
+ aMetadata.read(aFileStream);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(1800), aMetadata.getRotation().get());
+ }
+ {
+ // Rotation 270 degree clock-wise = 90 degree counter-clock-wise
+ SvFileStream aFileStream(getFullUrl(u"Exif1_270CW.jpg"), StreamMode::READ);
+ GraphicNativeMetadata aMetadata;
+ aMetadata.read(aFileStream);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(900), aMetadata.getRotation().get());
+ }
+}
+
+} // anonymous namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(GraphicNativeMetadataTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/GraphicTest.cxx b/vcl/qa/cppunit/GraphicTest.cxx
new file mode 100644
index 0000000000..557ac41df2
--- /dev/null
+++ b/vcl/qa/cppunit/GraphicTest.cxx
@@ -0,0 +1,1364 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <config_oox.h>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/graphic/XGraphicTransformer.hpp>
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+#include <test/bootstrapfixture.hxx>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <tools/stream.hxx>
+#include <unotest/directories.hxx>
+#include <comphelper/DirectoryHelper.hxx>
+#include <comphelper/hash.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <unotools/tempfile.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/wmf.hxx>
+
+#include <impgraph.hxx>
+#include <graphic/GraphicFormatDetector.hxx>
+
+#if USE_TLS_NSS
+#include <nss.h>
+#endif
+
+using namespace css;
+
+class GraphicTest : public test::BootstrapFixture
+{
+public:
+ ~GraphicTest();
+};
+
+GraphicTest::~GraphicTest()
+{
+#if USE_TLS_NSS
+ NSS_Shutdown();
+#endif
+}
+
+namespace
+{
+BitmapEx createBitmap(bool alpha = false)
+{
+ Bitmap aBitmap(Size(120, 100), vcl::PixelFormat::N24_BPP);
+ aBitmap.Erase(COL_LIGHTRED);
+
+ aBitmap.SetPrefSize(Size(6000, 5000));
+ aBitmap.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+
+ if (alpha)
+ {
+ sal_uInt8 uAlphaValue = 0x80;
+ AlphaMask aAlphaMask(Size(120, 100), &uAlphaValue);
+
+ return BitmapEx(aBitmap, aAlphaMask);
+ }
+ else
+ {
+ return BitmapEx(aBitmap);
+ }
+}
+
+void createBitmapAndExportForType(SvStream& rStream, std::u16string_view sType, bool alpha)
+{
+ BitmapEx aBitmapEx = createBitmap(alpha);
+
+ uno::Sequence<beans::PropertyValue> aFilterData;
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ sal_uInt16 nFilterFormat = rGraphicFilter.GetExportFormatNumberForShortName(sType);
+ rGraphicFilter.ExportGraphic(aBitmapEx, u"none", rStream, nFilterFormat, &aFilterData);
+
+ rStream.Seek(STREAM_SEEK_TO_BEGIN);
+}
+
+Graphic makeUnloadedGraphic(std::u16string_view sType, bool alpha = false)
+{
+ SvMemoryStream aStream;
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ createBitmapAndExportForType(aStream, sType, alpha);
+ return rGraphicFilter.ImportUnloadedGraphic(aStream);
+}
+
+std::string toHexString(const std::vector<unsigned char>& a)
+{
+ std::stringstream aStrm;
+ for (auto& i : a)
+ {
+ aStrm << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(i);
+ }
+
+ return aStrm.str();
+}
+
+std::vector<unsigned char> calculateHash(SvStream* pStream)
+{
+ comphelper::Hash aHashEngine(comphelper::HashType::SHA1);
+ const sal_uInt32 nSize(pStream->remainingSize());
+ std::vector<sal_uInt8> aData(nSize);
+ aHashEngine.update(aData.data(), nSize);
+ return aHashEngine.finalize();
+}
+
+bool checkBitmap(Graphic& rGraphic)
+{
+ bool bResult = true;
+
+ Bitmap aBitmap(rGraphic.GetBitmapEx().GetBitmap());
+ {
+ BitmapScopedReadAccess pReadAccess(aBitmap);
+ for (tools::Long y = 0; y < rGraphic.GetSizePixel().Height(); y++)
+ {
+ for (tools::Long x = 0; x < rGraphic.GetSizePixel().Width(); x++)
+ {
+ if (pReadAccess->HasPalette())
+ {
+ sal_uInt32 nIndex = pReadAccess->GetPixelIndex(y, x);
+ Color aColor = pReadAccess->GetPaletteColor(nIndex);
+ bResult &= (aColor == Color(0xff, 0x00, 0x00));
+ }
+ else
+ {
+ Color aColor = pReadAccess->GetPixel(y, x);
+ bResult &= (aColor == Color(0xff, 0x00, 0x00));
+ }
+ }
+ }
+ }
+
+ return bResult;
+}
+
+constexpr OUString DATA_DIRECTORY = u"/vcl/qa/cppunit/data/"_ustr;
+constexpr OUString PDFEXPORT_DATA_DIRECTORY = u"/vcl/qa/cppunit/pdfexport/data/"_ustr;
+
+Graphic loadGraphic(std::u16string_view const& rFilename)
+{
+ test::Directories aDirectories;
+ OUString aFilename = aDirectories.getURLFromSrc(DATA_DIRECTORY) + rFilename;
+ SvFileStream aFileStream(aFilename, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+
+ Graphic aGraphic;
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, rGraphicFilter.ImportGraphic(aGraphic, u"", aFileStream,
+ GRFILTER_FORMAT_DONTKNOW));
+ return aGraphic;
+}
+
+Graphic importUnloadedGraphic(std::u16string_view const& rFilename)
+{
+ test::Directories aDirectories;
+ OUString aFilename = aDirectories.getURLFromSrc(DATA_DIRECTORY) + rFilename;
+ SvFileStream aFileStream(aFilename, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ return rGraphicFilter.ImportUnloadedGraphic(aFileStream);
+}
+
+int getEmfPlusActionsCount(const Graphic& graphic)
+{
+ const GDIMetaFile& metafile = graphic.GetGDIMetaFile();
+ int emfPlusCount = 0;
+ for (size_t i = 0; i < metafile.GetActionSize(); ++i)
+ {
+ MetaAction* action = metafile.GetAction(i);
+ if (action->GetType() == MetaActionType::COMMENT)
+ {
+ const MetaCommentAction* commentAction = static_cast<const MetaCommentAction*>(action);
+ if (commentAction->GetComment() == "EMF_PLUS")
+ ++emfPlusCount;
+ }
+ }
+ return emfPlusCount;
+}
+
+int getPolygonActionsCount(const Graphic& graphic)
+{
+ const GDIMetaFile& metafile = graphic.GetGDIMetaFile();
+ int polygonCount = 0;
+ for (size_t i = 0; i < metafile.GetActionSize(); ++i)
+ {
+ MetaAction* action = metafile.GetAction(i);
+ if (action->GetType() == MetaActionType::POLYGON)
+ ++polygonCount;
+ }
+ return polygonCount;
+}
+
+} //namespace
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testUnloadedGraphic)
+{
+ // make unloaded test graphic
+ Graphic aGraphic = makeUnloadedGraphic(u"png");
+ Graphic aGraphic2 = aGraphic;
+
+ // check available
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic2.isAvailable());
+
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic2.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic2.isAvailable());
+
+ // check GetSizePixel doesn't load graphic
+ aGraphic = makeUnloadedGraphic(u"png");
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(120), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // check GetPrefSize doesn't load graphic
+ CPPUNIT_ASSERT_EQUAL(tools::Long(6000), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(5000), aGraphic.GetPrefSize().Height());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // check GetSizeBytes loads graphic
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT(aGraphic.GetSizeBytes() > 0);
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ //check Type
+ aGraphic = makeUnloadedGraphic(u"png");
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testUnloadedGraphicLoadingPng)
+{
+ Graphic aGraphic = makeUnloadedGraphic(u"png");
+
+ // check available
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(120), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT(aGraphic.GetSizeBytes() > 0);
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ CPPUNIT_ASSERT_EQUAL(true, checkBitmap(aGraphic));
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testUnloadedGraphicLoadingGif)
+{
+ Graphic aGraphic = makeUnloadedGraphic(u"gif");
+
+ // check available
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(120), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT(aGraphic.GetSizeBytes() > 0);
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ CPPUNIT_ASSERT_EQUAL(true, checkBitmap(aGraphic));
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testUnloadedGraphicLoadingJpg)
+{
+ Graphic aGraphic = makeUnloadedGraphic(u"jpg");
+
+ // check available
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(120), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT(aGraphic.GetSizeBytes() > 0);
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ CPPUNIT_ASSERT_EQUAL(false, checkBitmap(aGraphic));
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testUnloadedGraphicLoadingTif)
+{
+ Graphic aGraphic = makeUnloadedGraphic(u"tif");
+
+ // check available
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(120), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT(aGraphic.GetSizeBytes() > 0);
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ CPPUNIT_ASSERT_EQUAL(true, checkBitmap(aGraphic));
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testUnloadedGraphicLoadingWebp)
+{
+ Graphic aGraphic = makeUnloadedGraphic(u"webp");
+
+ // check available
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(120), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT(aGraphic.GetSizeBytes() > 0);
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ CPPUNIT_ASSERT_EQUAL(true, checkBitmap(aGraphic));
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testUnloadedGraphicWmf)
+{
+ // Create some in-memory WMF data, set its own preferred size to 99x99.
+ BitmapEx aBitmapEx = createBitmap();
+ SvMemoryStream aStream;
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ sal_uInt16 nFilterFormat = rGraphicFilter.GetExportFormatNumberForShortName(u"wmf");
+ Graphic aGraphic(aBitmapEx);
+ aGraphic.SetPrefSize(Size(99, 99));
+ aGraphic.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ rGraphicFilter.ExportGraphic(aGraphic, u"none", aStream, nFilterFormat);
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ // Now lazy-load this WMF data, with a custom preferred size of 42x42.
+ Size aMtfSize100(42, 42);
+ aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream, 0, &aMtfSize100);
+ aGraphic.makeAvailable();
+
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 42x42
+ // - Actual : 99x99
+ // i.e. the custom preferred size was lost after lazy-load.
+ CPPUNIT_ASSERT_EQUAL(Size(42, 42), aGraphic.GetPrefSize());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testUnloadedGraphicAlpha)
+{
+ // make unloaded test graphic with alpha
+ Graphic aGraphic = makeUnloadedGraphic(u"png", true);
+
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.IsAlpha());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.IsTransparent());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // make unloaded test graphic without alpha
+ aGraphic = makeUnloadedGraphic(u"png", false);
+
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.IsAlpha());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.IsTransparent());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testUnloadedGraphicSizeUnit)
+{
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(DATA_DIRECTORY) + "inch-size.emf";
+ Size aMtfSize100(42, 42);
+ SvFileStream aStream(aURL, StreamMode::READ);
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream, 0, &aMtfSize100);
+
+ CPPUNIT_ASSERT_EQUAL(Size(42, 42), aGraphic.GetPrefSize());
+
+ // Force it to swap in
+ aGraphic.makeAvailable();
+
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 400x363
+ // - Actual : 42x42
+ // i.e. a mm100 size was used as a hint and the inch size was set for a non-matching unit.
+ CPPUNIT_ASSERT_EQUAL(Size(400, 363), aGraphic.GetPrefSize());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testWMFRoundtrip)
+{
+ // Load a WMF file.
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(u"vcl/qa/cppunit/data/roundtrip.wmf");
+ SvFileStream aStream(aURL, StreamMode::READ);
+ sal_uInt64 nExpectedSize = aStream.TellEnd();
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+
+ // Save as WMF.
+ utl::TempFileNamed aTempFile;
+ aTempFile.EnableKillingFile();
+ sal_uInt16 nFormat = rGraphicFilter.GetExportFormatNumberForShortName(u"WMF");
+ SvStream& rOutStream = *aTempFile.GetStream(StreamMode::READWRITE);
+ rGraphicFilter.ExportGraphic(aGraphic, u"", rOutStream, nFormat);
+
+ // Check if we preserved the WMF data perfectly.
+ sal_uInt64 nActualSize = rOutStream.TellEnd();
+
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 6475
+ // - Actual : 2826
+ // i.e. we lost some of the WMF data on roundtrip.
+ CPPUNIT_ASSERT_EQUAL(nExpectedSize, nActualSize);
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testWMFWithEmfPlusRoundtrip)
+{
+ // Load a WMF file.
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(u"vcl/qa/cppunit/data/wmf-embedded-emfplus.wmf");
+ SvFileStream aStream(aURL, StreamMode::READ);
+ sal_uInt64 nExpectedSize = aStream.TellEnd();
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+
+ CPPUNIT_ASSERT_GREATER(0, getEmfPlusActionsCount(aGraphic));
+ CPPUNIT_ASSERT_EQUAL(0, getPolygonActionsCount(aGraphic));
+
+ for (bool useConvertMetafile : { false, true })
+ {
+ // Save as WMF.
+ utl::TempFileNamed aTempFile;
+ SvStream& rOutStream = *aTempFile.GetStream(StreamMode::READWRITE);
+ if (useConvertMetafile)
+ ConvertGraphicToWMF(aGraphic, rOutStream, nullptr);
+ else
+ {
+ sal_uInt16 nFormat = rGraphicFilter.GetExportFormatNumberForShortName(u"WMF");
+ rGraphicFilter.ExportGraphic(aGraphic, u"", rOutStream, nFormat);
+ }
+ CPPUNIT_ASSERT_EQUAL(nExpectedSize, rOutStream.TellEnd());
+
+ rOutStream.Seek(0);
+ Graphic aNewGraphic = rGraphicFilter.ImportUnloadedGraphic(rOutStream);
+ // Check that reading the WMF back preserves the EMF+ actions in it.
+ CPPUNIT_ASSERT_GREATER(0, getEmfPlusActionsCount(aNewGraphic));
+ // EmfReader::ReadEnhWMF() drops non-EMF+ drawing actions if EMF+ is found.
+ CPPUNIT_ASSERT_EQUAL(0, getPolygonActionsCount(aNewGraphic));
+
+ // With EMF+ disabled there should be no EMF+ actions.
+ auto aVectorGraphicData = std::make_shared<VectorGraphicData>(
+ aNewGraphic.GetGfxLink().getDataContainer(), VectorGraphicDataType::Wmf);
+ aVectorGraphicData->setEnableEMFPlus(false);
+ Graphic aNoEmfPlusGraphic(aVectorGraphicData);
+ CPPUNIT_ASSERT_EQUAL(0, getEmfPlusActionsCount(aNoEmfPlusGraphic));
+ CPPUNIT_ASSERT_GREATER(0, getPolygonActionsCount(aNoEmfPlusGraphic));
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testEmfToWmfConversion)
+{
+ // Load EMF data.
+ GraphicFilter aGraphicFilter;
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(DATA_DIRECTORY) + "to-wmf.emf";
+ SvFileStream aStream(aURL, StreamMode::READ);
+ Graphic aGraphic;
+ // This similar to an application/x-openoffice-wmf mime type in manifest.xml in the ODF case.
+ sal_uInt16 nFormatEMF = aGraphicFilter.GetImportFormatNumberForShortName(u"EMF");
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE,
+ aGraphicFilter.ImportGraphic(aGraphic, u"", aStream, nFormatEMF));
+ CPPUNIT_ASSERT_EQUAL(VectorGraphicDataType::Emf, aGraphic.getVectorGraphicData()->getType());
+
+ // Save as WMF.
+ sal_uInt16 nFilterType = aGraphicFilter.GetExportFormatNumberForShortName(u"WMF");
+ SvMemoryStream aGraphicStream;
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE,
+ aGraphicFilter.ExportGraphic(aGraphic, u"", aGraphicStream, nFilterType));
+ aGraphicStream.Seek(0);
+ vcl::GraphicFormatDetector aDetector(aGraphicStream, OUString());
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkWMF());
+
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: WMF
+ // - Actual : EMF
+ // i.e. EMF data was requested to be converted to WMF, but the output was still EMF.
+ CPPUNIT_ASSERT_EQUAL(OUString("WMF"),
+ vcl::getImportFormatShortName(aDetector.getMetadata().mnFormat));
+
+ // Import the WMF result and check for traces of EMF+ in it.
+ Graphic aWmfGraphic;
+ aGraphicStream.Seek(0);
+ sal_uInt16 nFormatWMF = aGraphicFilter.GetImportFormatNumberForShortName(u"WMF");
+ CPPUNIT_ASSERT_EQUAL(
+ ERRCODE_NONE, aGraphicFilter.ImportGraphic(aWmfGraphic, u"", aGraphicStream, nFormatWMF));
+ int nCommentCount = 0;
+ for (size_t i = 0; i < aWmfGraphic.GetGDIMetaFile().GetActionSize(); ++i)
+ {
+ MetaAction* pAction = aWmfGraphic.GetGDIMetaFile().GetAction(i);
+ if (pAction->GetType() == MetaActionType::COMMENT)
+ {
+ auto pComment = static_cast<MetaCommentAction*>(pAction);
+ if (pComment->GetComment().startsWith("EMF_PLUS"))
+ {
+ ++nCommentCount;
+ }
+ }
+ }
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected less or equal than: 4
+ // - Actual : 8
+ // i.e. even more EMF+ comments were left in the WMF output. The ideal would be to get this down
+ // to 0, though.
+ CPPUNIT_ASSERT_LESSEQUAL(4, nCommentCount);
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testSwappingGraphic_PNG_WithGfxLink)
+{
+ // Prepare Graphic from a PNG image first
+ Graphic aGraphic = makeUnloadedGraphic(u"png");
+
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(120), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetSizePixel().Height());
+
+ BitmapChecksum aChecksumBeforeSwapping = aGraphic.GetChecksum();
+
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.IsGfxLink());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(319), aGraphic.GetGfxLink().GetDataSize());
+
+ // We loaded the Graphic and made it available
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+ // Get the declared byte size of the graphic
+ sal_uLong rByteSize = aGraphic.GetSizeBytes();
+
+ // Check the swap file (shouldn't exist)
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->getSwapFileStream() == nullptr);
+
+ // Swapping out
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->swapOut());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // Byte size doesn't change when we swapped out
+ CPPUNIT_ASSERT_EQUAL(rByteSize, aGraphic.GetSizeBytes());
+
+ // Check the swap file (still shouldn't exist)
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->getSwapFileStream() == nullptr);
+
+ // Let's swap in
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ CPPUNIT_ASSERT_EQUAL(aChecksumBeforeSwapping, aGraphic.GetChecksum());
+
+ // Check the bitmap
+ CPPUNIT_ASSERT_EQUAL(tools::Long(120), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(true, checkBitmap(aGraphic));
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testSwappingGraphic_PNG_WithoutGfxLink)
+{
+ // Prepare Graphic from a PNG image first
+
+ // Make sure to construct the Graphic from BitmapEx, so that we
+ // don't have the GfxLink present.
+ Graphic aGraphic(makeUnloadedGraphic(u"png").GetBitmapEx());
+
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(120), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetSizePixel().Height());
+
+ BitmapChecksum aChecksumBeforeSwapping = aGraphic.GetChecksum();
+
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.IsGfxLink());
+
+ // We loaded the Graphic and made it available
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ // Get the declared byte size of the graphic
+ sal_uLong rByteSize = aGraphic.GetSizeBytes();
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->getSwapFileStream() == nullptr);
+
+ // Swapping out
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->swapOut());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // Byte size doesn't change when we swapped out
+ CPPUNIT_ASSERT_EQUAL(rByteSize, aGraphic.GetSizeBytes());
+
+ // Let's check the swap file
+
+ { // Check the swap file content
+ SvStream* pStream = aGraphic.ImplGetImpGraphic()->getSwapFileStream();
+ pStream->Seek(0);
+
+ // Check size of the stream
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(36079), pStream->remainingSize());
+
+ std::vector<unsigned char> aHash = calculateHash(pStream);
+ CPPUNIT_ASSERT_EQUAL(std::string("9347511e3b80dfdfaadf91a3bdef55a8ae85552b"),
+ toHexString(aHash));
+ }
+
+ // SWAP IN
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ // reset the checksum to make sure we don't get the cached value
+ aGraphic.ImplGetImpGraphic()->resetChecksum();
+ CPPUNIT_ASSERT_EQUAL(aChecksumBeforeSwapping, aGraphic.GetChecksum());
+
+ // File shouldn't be available anymore
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->getSwapFileStream() == nullptr);
+
+ // Check the bitmap
+ CPPUNIT_ASSERT_EQUAL(tools::Long(120), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetSizePixel().Height());
+
+ CPPUNIT_ASSERT_EQUAL(true, checkBitmap(aGraphic));
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testSwappingGraphicProperties_PNG_WithGfxLink)
+{
+ // Prepare Graphic from a PNG image
+ Graphic aGraphic = makeUnloadedGraphic(u"png");
+
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ // Origin URL
+ aGraphic.setOriginURL("Origin URL");
+ CPPUNIT_ASSERT_EQUAL(OUString("Origin URL"), aGraphic.getOriginURL());
+
+ //Set PrefMapMode
+ CPPUNIT_ASSERT_EQUAL(MapUnit::Map100thMM, aGraphic.GetPrefMapMode().GetMapUnit());
+ aGraphic.SetPrefMapMode(MapMode(MapUnit::MapTwip));
+ CPPUNIT_ASSERT_EQUAL(MapUnit::MapTwip, aGraphic.GetPrefMapMode().GetMapUnit());
+
+ // Set the PrefSize
+ CPPUNIT_ASSERT_EQUAL(tools::Long(6000), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(5000), aGraphic.GetPrefSize().Height());
+ aGraphic.SetPrefSize(Size(200, 100));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(200), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetPrefSize().Height());
+
+ // SWAP OUT
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->swapOut());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // Check properties
+ CPPUNIT_ASSERT_EQUAL(OUString("Origin URL"), aGraphic.getOriginURL());
+ CPPUNIT_ASSERT_EQUAL(MapUnit::MapTwip, aGraphic.GetPrefMapMode().GetMapUnit());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(200), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetPrefSize().Height());
+
+ // SWAP IN
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ // Check properties
+ CPPUNIT_ASSERT_EQUAL(OUString("Origin URL"), aGraphic.getOriginURL());
+ CPPUNIT_ASSERT_EQUAL(MapUnit::MapTwip, aGraphic.GetPrefMapMode().GetMapUnit());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(200), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetPrefSize().Height());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testSwappingGraphicProperties_PNG_WithoutGfxLink)
+{
+ // Prepare Graphic from a PNG image
+ Graphic aGraphic(makeUnloadedGraphic(u"png").GetBitmapEx());
+
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ // Origin URL
+ aGraphic.setOriginURL("Origin URL");
+ CPPUNIT_ASSERT_EQUAL(OUString("Origin URL"), aGraphic.getOriginURL());
+
+ //Set PrefMapMode
+ CPPUNIT_ASSERT_EQUAL(MapUnit::Map100thMM, aGraphic.GetPrefMapMode().GetMapUnit());
+ aGraphic.SetPrefMapMode(MapMode(MapUnit::MapTwip));
+ CPPUNIT_ASSERT_EQUAL(MapUnit::MapTwip, aGraphic.GetPrefMapMode().GetMapUnit());
+
+ // Set the PrefSize
+ CPPUNIT_ASSERT_EQUAL(tools::Long(6000), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(5000), aGraphic.GetPrefSize().Height());
+ aGraphic.SetPrefSize(Size(200, 100));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(200), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetPrefSize().Height());
+
+ // SWAP OUT
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->swapOut());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // Check properties
+ CPPUNIT_ASSERT_EQUAL(OUString("Origin URL"), aGraphic.getOriginURL());
+ CPPUNIT_ASSERT_EQUAL(MapUnit::MapTwip, aGraphic.GetPrefMapMode().GetMapUnit());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(200), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetPrefSize().Height());
+
+ // SWAP IN
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ // Check properties
+ CPPUNIT_ASSERT_EQUAL(OUString("Origin URL"), aGraphic.getOriginURL());
+ CPPUNIT_ASSERT_EQUAL(MapUnit::MapTwip, aGraphic.GetPrefMapMode().GetMapUnit());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(200), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetPrefSize().Height());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testSwappingVectorGraphic_SVG_WithGfxLink)
+{
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(DATA_DIRECTORY) + "SimpleExample.svg";
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ // Loaded into "prepared" state
+
+ // Check that the state is as expected
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // Load the vector graphic
+ auto pVectorData = aGraphic.getVectorGraphicData();
+ CPPUNIT_ASSERT_EQUAL(true, bool(pVectorData));
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ CPPUNIT_ASSERT_EQUAL(size_t(223), pVectorData->getBinaryDataContainer().getSize());
+
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.IsGfxLink());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(223), aGraphic.GetGfxLink().GetDataSize());
+
+ // Remember checksum so we can compare after swapping back in again
+ BitmapChecksum aBitmapChecksumBeforeSwapping = aGraphic.GetBitmapEx().GetChecksum();
+
+ // Check we are not swapped out yet
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ // Get the declared byte size of the graphic
+ sal_uLong rByteSize = aGraphic.GetSizeBytes();
+ CPPUNIT_ASSERT_EQUAL(sal_uLong(223), rByteSize);
+
+ // Make sure we don't have a file
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->getSwapFileStream() == nullptr);
+
+ // SWAP OUT the Graphic and make sure it's not available currently
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->swapOut());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // We use GfxLink so no swap file in this case
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->getSwapFileStream() == nullptr);
+
+ // Byte size doesn't change when we swapped out
+ CPPUNIT_ASSERT_EQUAL(rByteSize, aGraphic.GetSizeBytes());
+
+ // SWAP IN
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ // Compare that the checksum of the bitmap is still the same
+ CPPUNIT_ASSERT_EQUAL(aBitmapChecksumBeforeSwapping, aGraphic.GetBitmapEx().GetChecksum());
+
+ // Byte size shouldn't change
+ CPPUNIT_ASSERT_EQUAL(rByteSize, aGraphic.GetSizeBytes());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testSwappingVectorGraphic_SVG_WithoutGfxLink)
+{
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(DATA_DIRECTORY) + "SimpleExample.svg";
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+
+ Graphic aInputGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ CPPUNIT_ASSERT_EQUAL(size_t(223),
+ aInputGraphic.getVectorGraphicData()->getBinaryDataContainer().getSize());
+
+ // Create graphic
+ Graphic aGraphic(aInputGraphic.getVectorGraphicData());
+
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(true, bool(aGraphic.getVectorGraphicData()));
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ CPPUNIT_ASSERT_EQUAL(size_t(223),
+ aGraphic.getVectorGraphicData()->getBinaryDataContainer().getSize());
+
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.IsGfxLink());
+
+ BitmapChecksum aBitmapChecksumBeforeSwapping = aGraphic.GetBitmapEx().GetChecksum();
+
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ // Get the declared byte size of the graphic
+ sal_uLong rByteSize = aGraphic.GetSizeBytes();
+ CPPUNIT_ASSERT_EQUAL(sal_uLong(223), rByteSize);
+
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->getSwapFileStream() == nullptr);
+
+ // Swapping out
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->swapOut());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // Byte size doesn't change when we swapped out
+ // TODO: In case we don't trigger GetBitmapEx (above) the size is 0
+ CPPUNIT_ASSERT_EQUAL(rByteSize, aGraphic.GetSizeBytes());
+
+ // Let's check the swap file
+ {
+ // Check the swap file content
+ SvStream* pStream = aGraphic.ImplGetImpGraphic()->getSwapFileStream();
+ pStream->Seek(0);
+
+ // Check size of the stream
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(247), pStream->remainingSize());
+
+ std::vector<unsigned char> aHash = calculateHash(pStream);
+ CPPUNIT_ASSERT_EQUAL(std::string("666820973fd95e6cd9e7bc5f1c53732acbc99326"),
+ toHexString(aHash));
+ }
+
+ // Let's swap in
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ // Check the Graphic
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(true, bool(aGraphic.getVectorGraphicData()));
+
+ size_t nVectorByteSize = aGraphic.getVectorGraphicData()->getBinaryDataContainer().getSize();
+ CPPUNIT_ASSERT_EQUAL(size_t(223), nVectorByteSize);
+
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.IsGfxLink());
+
+ CPPUNIT_ASSERT_EQUAL(aBitmapChecksumBeforeSwapping, aGraphic.GetBitmapEx().GetChecksum());
+
+ // File shouldn't be available anymore
+ CPPUNIT_ASSERT_EQUAL(static_cast<SvStream*>(nullptr),
+ aGraphic.ImplGetImpGraphic()->getSwapFileStream());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testSwappingGraphicProperties_SVG_WithGfxLink)
+{
+ // FIXME: the DPI check should be removed when either (1) the test is fixed to work with
+ // non-default DPI; or (2) unit tests on Windows are made to use svp VCL plugin.
+ if (!IsDefaultDPI())
+ return;
+
+ // We check that Graphic properties like MapMode, PrefSize are properly
+ // restored through a swap cycle
+
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(DATA_DIRECTORY) + "SimpleExample.svg";
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ // Loaded into "prepared" state
+
+ // Load the vector graphic
+ auto pVectorData = aGraphic.getVectorGraphicData();
+ CPPUNIT_ASSERT_EQUAL(true, bool(pVectorData));
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ // Origin URL
+ aGraphic.setOriginURL("Origin URL");
+ CPPUNIT_ASSERT_EQUAL(OUString("Origin URL"), aGraphic.getOriginURL());
+
+ // Check size in pixels
+ CPPUNIT_ASSERT_EQUAL(tools::Long(51), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(51), aGraphic.GetSizePixel().Height());
+
+ // Set and check the PrefSize
+ CPPUNIT_ASSERT_EQUAL(tools::Long(1349), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(1349), aGraphic.GetPrefSize().Height());
+ aGraphic.SetPrefSize(Size(200, 100));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(200), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetPrefSize().Height());
+
+ // SWAP OUT the Graphic and make sure it's not available currently
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->swapOut());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // Check properties
+ CPPUNIT_ASSERT_EQUAL(OUString("Origin URL"), aGraphic.getOriginURL());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(200), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetPrefSize().Height());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(51), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(51), aGraphic.GetSizePixel().Height());
+
+ // SWAP IN
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ // Check properties
+ CPPUNIT_ASSERT_EQUAL(OUString("Origin URL"), aGraphic.getOriginURL());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(200), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetPrefSize().Height());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(51), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(51), aGraphic.GetSizePixel().Height());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testSwappingGraphicProperties_SVG_WithoutGfxLink)
+{
+ // FIXME: the DPI check should be removed when either (1) the test is fixed to work with
+ // non-default DPI; or (2) unit tests on Windows are made to use svp VCL plugin.
+ if (!IsDefaultDPI())
+ return;
+
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(DATA_DIRECTORY) + "SimpleExample.svg";
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+
+ Graphic aInputGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ CPPUNIT_ASSERT_EQUAL(size_t(223),
+ aInputGraphic.getVectorGraphicData()->getBinaryDataContainer().getSize());
+
+ // Create graphic
+ Graphic aGraphic(aInputGraphic.getVectorGraphicData());
+
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(true, bool(aGraphic.getVectorGraphicData()));
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.IsGfxLink());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ // Origin URL
+ aGraphic.setOriginURL("Origin URL");
+ CPPUNIT_ASSERT_EQUAL(OUString("Origin URL"), aGraphic.getOriginURL());
+
+ // Check size in pixels
+ CPPUNIT_ASSERT_EQUAL(tools::Long(51), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(51), aGraphic.GetSizePixel().Height());
+
+ // Set and check the PrefSize
+ CPPUNIT_ASSERT_EQUAL(tools::Long(1349), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(1349), aGraphic.GetPrefSize().Height());
+ aGraphic.SetPrefSize(Size(200, 100));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(200), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetPrefSize().Height());
+
+ // SWAP OUT the Graphic and make sure it's not available currently
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->swapOut());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ CPPUNIT_ASSERT_EQUAL(OUString("Origin URL"), aGraphic.getOriginURL());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(200), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetPrefSize().Height());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(51), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(51), aGraphic.GetSizePixel().Height());
+
+ // SWAP IN
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ CPPUNIT_ASSERT_EQUAL(OUString("Origin URL"), aGraphic.getOriginURL());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(200), aGraphic.GetPrefSize().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphic.GetPrefSize().Height());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(51), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(51), aGraphic.GetSizePixel().Height());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testSwappingVectorGraphic_PDF_WithGfxLink)
+{
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(PDFEXPORT_DATA_DIRECTORY) + "SimpleMultiPagePDF.pdf";
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // Load the vector graphic
+ CPPUNIT_ASSERT_EQUAL(true, bool(aGraphic.getVectorGraphicData()));
+
+ // Set the page index
+ aGraphic.getVectorGraphicData()->setPageIndex(1);
+
+ CPPUNIT_ASSERT_EQUAL(VectorGraphicDataType::Pdf, aGraphic.getVectorGraphicData()->getType());
+ CPPUNIT_ASSERT_EQUAL(size_t(17693),
+ aGraphic.getVectorGraphicData()->getBinaryDataContainer().getSize());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aGraphic.getVectorGraphicData()->getPageIndex());
+
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ // SWAP OUT
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->swapOut());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // SWAP IN
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aGraphic.getVectorGraphicData()->getPageIndex());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testSwappingVectorGraphic_PDF_WithoutGfxLink)
+{
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(PDFEXPORT_DATA_DIRECTORY) + "SimpleMultiPagePDF.pdf";
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aInputGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+
+ // Create graphic
+ Graphic aGraphic(aInputGraphic.getVectorGraphicData());
+
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, bool(aGraphic.getVectorGraphicData()));
+
+ // Set the page index
+ aGraphic.getVectorGraphicData()->setPageIndex(1);
+
+ CPPUNIT_ASSERT_EQUAL(VectorGraphicDataType::Pdf, aGraphic.getVectorGraphicData()->getType());
+ CPPUNIT_ASSERT_EQUAL(size_t(17693),
+ aGraphic.getVectorGraphicData()->getBinaryDataContainer().getSize());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aGraphic.getVectorGraphicData()->getPageIndex());
+
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ // SWAP OUT
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->swapOut());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // SWAP IN
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aGraphic.getVectorGraphicData()->getPageIndex());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testSwappingAnimationGraphic_GIF_WithGfxLink)
+{
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(DATA_DIRECTORY) + "123_Numbers.gif";
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ // Loaded into "prepared" state
+
+ // Check that the state is as expected
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.IsGfxLink());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(124), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(146), aGraphic.GetSizePixel().Height());
+
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(1515), aGraphic.GetGfxLink().GetDataSize());
+
+ // Remember checksum so we can compare after swapping back in again
+ BitmapChecksum aBitmapChecksumBeforeSwapping = aGraphic.GetBitmapEx().GetChecksum();
+
+ // Check we are not swapped out yet
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ // Get the declared byte size of the graphic
+ sal_uLong rByteSize = aGraphic.GetSizeBytes();
+ CPPUNIT_ASSERT_EQUAL(sal_uLong(89552), rByteSize);
+
+ // Make sure we don't have a file
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->getSwapFileStream() == nullptr);
+
+ // SWAP OUT the Graphic and make sure it's not available currently
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->swapOut());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // We use GfxLink so no swap file in this case
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->getSwapFileStream() == nullptr);
+
+ // Byte size doesn't change when we swapped out
+ CPPUNIT_ASSERT_EQUAL(rByteSize, aGraphic.GetSizeBytes());
+
+ // SWAP IN
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ // Compare that the checksum of the bitmap is still the same
+ CPPUNIT_ASSERT_EQUAL(aBitmapChecksumBeforeSwapping, aGraphic.GetBitmapEx().GetChecksum());
+
+ // Byte size shouldn't change
+ CPPUNIT_ASSERT_EQUAL(rByteSize, aGraphic.GetSizeBytes());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testSwappingAnimationGraphic_GIF_WithoutGfxLink)
+{
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(DATA_DIRECTORY) + "123_Numbers.gif";
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aInputGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ Graphic aGraphic(aInputGraphic.GetAnimation());
+
+ // Check animation graphic
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.IsAnimated());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(124), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(146), aGraphic.GetSizePixel().Height());
+
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.IsGfxLink());
+
+ // We loaded the Graphic and made it available
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ // Get the declared byte size of the graphic
+ sal_uLong rByteSize = aGraphic.GetSizeBytes();
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->getSwapFileStream() == nullptr);
+
+ // SWAP OUT
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->swapOut());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+
+ // Byte size doesn't change when we swapped out
+ CPPUNIT_ASSERT_EQUAL(rByteSize, aGraphic.GetSizeBytes());
+
+ // Let's check the swap file
+ {
+ // Check the swap file content
+ SvStream* pStream = aGraphic.ImplGetImpGraphic()->getSwapFileStream();
+ pStream->Seek(0);
+
+ // Check size of the stream
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(15139), pStream->remainingSize());
+
+ std::vector<unsigned char> aHash = calculateHash(pStream);
+ CPPUNIT_ASSERT_EQUAL(std::string("ecae5354edd9cf98553eb3153e44181f56d35338"),
+ toHexString(aHash));
+ }
+
+ // SWAP IN
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.makeAvailable());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.isAvailable());
+ CPPUNIT_ASSERT_EQUAL(false, aGraphic.ImplGetImpGraphic()->isSwappedOut());
+
+ // File shouldn't be available anymore
+ CPPUNIT_ASSERT_EQUAL(static_cast<SvStream*>(nullptr),
+ aGraphic.ImplGetImpGraphic()->getSwapFileStream());
+
+ // Check the bitmap
+ CPPUNIT_ASSERT_EQUAL(tools::Long(124), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(146), aGraphic.GetSizePixel().Height());
+
+ // Byte size is still the same
+ CPPUNIT_ASSERT_EQUAL(rByteSize, aGraphic.GetSizeBytes());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testLoadMET)
+{
+ Graphic aGraphic = loadGraphic(u"TypeDetectionExample.met");
+ CPPUNIT_ASSERT_EQUAL(GraphicType::GdiMetafile, aGraphic.GetType());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testLoadBMP)
+{
+ Graphic aGraphic = loadGraphic(u"TypeDetectionExample.bmp");
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aGraphic.GetSizePixel().Height());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testLoadPSD)
+{
+ Graphic aGraphic = loadGraphic(u"TypeDetectionExample.psd");
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aGraphic.GetSizePixel().Height());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testLoadTGA)
+{
+ Graphic aGraphic = loadGraphic(u"TypeDetectionExample.tga");
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aGraphic.GetSizePixel().Height());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testLoadXBM)
+{
+ Graphic aGraphic = loadGraphic(u"TypeDetectionExample.xbm");
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aGraphic.GetSizePixel().Height());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testLoadXPM)
+{
+ Graphic aGraphic = loadGraphic(u"TypeDetectionExample.xpm");
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aGraphic.GetSizePixel().Height());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testLoadPCX)
+{
+ Graphic aGraphic = loadGraphic(u"TypeDetectionExample.pcx");
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aGraphic.GetSizePixel().Height());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testLoadEPS)
+{
+ Graphic aGraphic = loadGraphic(u"TypeDetectionExample.eps");
+ CPPUNIT_ASSERT_EQUAL(GraphicType::GdiMetafile, aGraphic.GetType());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testLoadWEBP)
+{
+ Graphic aGraphic = loadGraphic(u"TypeDetectionExample.webp");
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aGraphic.GetSizePixel().Height());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testLoadSVGZ)
+{
+ Graphic aGraphic = loadGraphic(u"TypeDetectionExample.svgz");
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ const auto[scalingX, scalingY] = getDPIScaling();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100 * scalingX), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100 * scalingY), aGraphic.GetSizePixel().Height());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testTdf156016)
+{
+ // Without the fix in place, this test would have failed with
+ // - Expected: 0x0(Error Area:Io Class:NONE Code:0)
+ // - Actual : 0x8203(Error Area:Vcl Class:General Code:3)
+ Graphic aGraphic = loadGraphic(u"tdf156016.svg");
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ const auto[scalingX, scalingY] = getDPIScaling();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100 * scalingX), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100 * scalingY), aGraphic.GetSizePixel().Height());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testTdf149545)
+{
+ // Without the fix in place, this test would have failed with
+ // - Expected: 0x0(Error Area:Io Class:NONE Code:0)
+ // - Actual : 0x8203(Error Area:Vcl Class:General Code:3)
+ Graphic aGraphic = loadGraphic(u"tdf149545.svg");
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ const auto[scalingX, scalingY] = getDPIScaling();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100 * scalingX), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100 * scalingY), aGraphic.GetSizePixel().Height());
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testAvailableThreaded)
+{
+ Graphic jpgGraphic1 = importUnloadedGraphic(u"TypeDetectionExample.jpg");
+ Graphic jpgGraphic2 = importUnloadedGraphic(u"Exif1.jpg");
+ Graphic pngGraphic1 = importUnloadedGraphic(u"TypeDetectionExample.png");
+ Graphic pngGraphic2 = importUnloadedGraphic(u"testBasicMorphology.png");
+ std::vector<Graphic*> graphics = { &jpgGraphic1, &jpgGraphic2, &pngGraphic1, &pngGraphic2 };
+ std::vector<Size> sizes;
+ for (auto& graphic : graphics)
+ {
+ CPPUNIT_ASSERT(!graphic->isAvailable());
+ sizes.push_back(graphic->GetSizePixel());
+ }
+ GraphicFilter& graphicFilter = GraphicFilter::GetGraphicFilter();
+ graphicFilter.MakeGraphicsAvailableThreaded(graphics);
+ int i = 0;
+ for (auto& graphic : graphics)
+ {
+ CPPUNIT_ASSERT(graphic->isAvailable());
+ CPPUNIT_ASSERT_EQUAL(sizes[i], graphic->GetSizePixel());
+ ++i;
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(GraphicTest, testColorChangeToTransparent)
+{
+ Graphic aGraphic = importUnloadedGraphic(u"testColorChange-red-linear-gradient.png");
+
+ auto xGraphic = aGraphic.GetXGraphic();
+ uno::Reference<graphic::XGraphicTransformer> xGraphicTransformer{ xGraphic, uno::UNO_QUERY };
+ ::Color nColorFrom{ ColorTransparency, 0x00, 0xFF, 0x00, 0x00 };
+ ::Color nColorTo{ ColorTransparency, 0xFF, 0xFF, 0x00, 0x00 };
+ sal_uInt8 nTolerance{ 15 };
+
+ auto xGraphicAfter = xGraphicTransformer->colorChange(
+ xGraphic, static_cast<sal_Int32>(nColorFrom), nTolerance, static_cast<sal_Int32>(nColorTo),
+ static_cast<sal_Int8>(nColorTo.GetAlpha()));
+
+ Graphic aGraphicAfter{ xGraphicAfter };
+ const BitmapEx& rBitmapAfter = aGraphicAfter.GetBitmapExRef();
+ const BitmapEx& rBitmapBefore = aGraphic.GetBitmapExRef();
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: rgba[ff000000]
+ // - Actual : rgba[f00000ff]
+ // i.e. the color change to transparent didn't apply correctly
+ CPPUNIT_ASSERT_EQUAL(nColorTo, rBitmapAfter.GetPixelColor(386, 140));
+
+ // Test if color stayed same on 410,140
+ // colorChange with nTolerance 15 shouldn't change this pixel.
+ CPPUNIT_ASSERT_EQUAL(rBitmapBefore.GetPixelColor(410, 140),
+ rBitmapAfter.GetPixelColor(410, 140));
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/PDFDocumentTest.cxx b/vcl/qa/cppunit/PDFDocumentTest.cxx
new file mode 100644
index 0000000000..1223eebcbd
--- /dev/null
+++ b/vcl/qa/cppunit/PDFDocumentTest.cxx
@@ -0,0 +1,605 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+
+#include <vcl/filter/pdfdocument.hxx>
+
+class PDFDocumentTest : public test::BootstrapFixture, public unotest::MacrosTest
+{
+public:
+ PDFDocumentTest() = default;
+};
+
+constexpr OUString DATA_DIRECTORY = u"/vcl/qa/cppunit/data/"_ustr;
+
+CPPUNIT_TEST_FIXTURE(PDFDocumentTest, testParseBasicPDF)
+{
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "basic.pdf";
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(aURL, StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aPages.size());
+
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
+ CPPUNIT_ASSERT(pResources);
+
+ vcl::filter::PDFObjectElement* pTest = pResources->LookupObject("Test"_ostr);
+ CPPUNIT_ASSERT(pTest);
+
+ vcl::filter::PDFObjectElement* pTestArray1 = pTest->LookupObject("TestArray1"_ostr);
+ CPPUNIT_ASSERT(pTestArray1);
+ {
+ CPPUNIT_ASSERT_EQUAL(size_t(5), pTestArray1->GetArray()->GetElements().size());
+ }
+
+ vcl::filter::PDFObjectElement* pTestArray2 = pTest->LookupObject("TestArray2"_ostr);
+ CPPUNIT_ASSERT(pTestArray2);
+ {
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pTestArray2->GetArray()->GetElements().size());
+ }
+
+ vcl::filter::PDFObjectElement* pTestDictionary = pTest->LookupObject("TestDictionary"_ostr);
+ {
+ sal_uInt64 nOffset = pTestDictionary->GetDictionaryOffset();
+ sal_uInt64 nLength = pTestDictionary->GetDictionaryLength();
+
+ aStream.Seek(nOffset);
+ std::vector<char> aBuffer(nLength + 1, 0);
+ aStream.ReadBytes(aBuffer.data(), nLength);
+ OString aString(aBuffer.data());
+
+ CPPUNIT_ASSERT_EQUAL(
+ "/TestReference 7 0 R/TestNumber "
+ "123/TestName/SomeName/TestDictionary<</Key/Value>>/TestArray[1 2 3]"_ostr,
+ aString);
+ }
+
+ CPPUNIT_ASSERT(pTestDictionary);
+ {
+ auto const& rItems = pTestDictionary->GetDictionaryItems();
+ CPPUNIT_ASSERT_EQUAL(size_t(5), rItems.size());
+ auto* pReference = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pTestDictionary->Lookup("TestReference"_ostr));
+ CPPUNIT_ASSERT(pReference);
+ CPPUNIT_ASSERT_EQUAL(7, pReference->GetObjectValue());
+
+ auto* pNumber = dynamic_cast<vcl::filter::PDFNumberElement*>(
+ pTestDictionary->Lookup("TestNumber"_ostr));
+ CPPUNIT_ASSERT(pNumber);
+ CPPUNIT_ASSERT_EQUAL(123.0, pNumber->GetValue());
+
+ auto* pName
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pTestDictionary->Lookup("TestName"_ostr));
+ CPPUNIT_ASSERT(pName);
+ CPPUNIT_ASSERT_EQUAL("SomeName"_ostr, pName->GetValue());
+
+ auto* pDictionary = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pTestDictionary->Lookup("TestDictionary"_ostr));
+ CPPUNIT_ASSERT(pDictionary);
+
+ auto* pArray = dynamic_cast<vcl::filter::PDFArrayElement*>(
+ pTestDictionary->Lookup("TestArray"_ostr));
+ CPPUNIT_ASSERT(pArray);
+
+ // Check offsets and lengths
+ {
+ sal_uInt64 nOffset
+ = pTestDictionary->GetDictionary()->GetKeyOffset("TestReference"_ostr);
+ sal_uInt64 nLength
+ = pTestDictionary->GetDictionary()->GetKeyValueLength("TestReference"_ostr);
+
+ aStream.Seek(nOffset);
+ std::vector<char> aBuffer(nLength + 1, 0);
+ aStream.ReadBytes(aBuffer.data(), nLength);
+ OString aString(aBuffer.data());
+
+ CPPUNIT_ASSERT_EQUAL("TestReference 7 0 R"_ostr, aString);
+ }
+ {
+ sal_uInt64 nOffset = pTestDictionary->GetDictionary()->GetKeyOffset("TestNumber"_ostr);
+ sal_uInt64 nLength
+ = pTestDictionary->GetDictionary()->GetKeyValueLength("TestNumber"_ostr);
+
+ aStream.Seek(nOffset);
+ std::vector<char> aBuffer(nLength + 1, 0);
+ aStream.ReadBytes(aBuffer.data(), nLength);
+ OString aString(aBuffer.data());
+
+ CPPUNIT_ASSERT_EQUAL("TestNumber 123"_ostr, aString);
+ }
+ {
+ sal_uInt64 nOffset = pTestDictionary->GetDictionary()->GetKeyOffset("TestName"_ostr);
+ sal_uInt64 nLength
+ = pTestDictionary->GetDictionary()->GetKeyValueLength("TestName"_ostr);
+
+ aStream.Seek(nOffset);
+ std::vector<char> aBuffer(nLength + 1, 0);
+ aStream.ReadBytes(aBuffer.data(), nLength);
+ OString aString(aBuffer.data());
+
+ CPPUNIT_ASSERT_EQUAL("TestName/SomeName"_ostr, aString);
+ }
+ {
+ sal_uInt64 nOffset
+ = pTestDictionary->GetDictionary()->GetKeyOffset("TestDictionary"_ostr);
+ sal_uInt64 nLength
+ = pTestDictionary->GetDictionary()->GetKeyValueLength("TestDictionary"_ostr);
+
+ aStream.Seek(nOffset);
+ std::vector<char> aBuffer(nLength + 1, 0);
+ aStream.ReadBytes(aBuffer.data(), nLength);
+ OString aString(aBuffer.data());
+
+ CPPUNIT_ASSERT_EQUAL("TestDictionary<</Key/Value>>"_ostr, aString);
+ }
+ {
+ sal_uInt64 nOffset = pTestDictionary->GetDictionary()->GetKeyOffset("TestArray"_ostr);
+ sal_uInt64 nLength
+ = pTestDictionary->GetDictionary()->GetKeyValueLength("TestArray"_ostr);
+
+ aStream.Seek(nOffset);
+ std::vector<char> aBuffer(nLength + 1, 0);
+ aStream.ReadBytes(aBuffer.data(), nLength);
+ OString aString(aBuffer.data());
+
+ CPPUNIT_ASSERT_EQUAL("TestArray[1 2 3]"_ostr, aString);
+ }
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PDFDocumentTest, testParseDocumentWithNullAsWhitespace)
+{
+ // tdf#140606
+ // Bug document contained a null, which cause the parser to panic,
+ // but other PDF readers can handle the file well.
+
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "DocumentWithNull.pdf";
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(aURL, StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+}
+
+namespace
+{
+vcl::filter::PDFObjectElement*
+addObjectElement(std::vector<std::unique_ptr<vcl::filter::PDFElement>>& rElements,
+ vcl::filter::PDFDocument& rDocument, int nObjectNumber, int nGenerationNumber)
+{
+ auto pObject = std::make_unique<vcl::filter::PDFObjectElement>(rDocument, nObjectNumber,
+ nGenerationNumber);
+ auto pObjectPtr = pObject.get();
+ rElements.push_back(std::move(pObject));
+ return pObjectPtr;
+}
+
+vcl::filter::PDFTrailerElement*
+addTrailerObjectElement(std::vector<std::unique_ptr<vcl::filter::PDFElement>>& rElements,
+ vcl::filter::PDFDocument& rDocument)
+{
+ auto pTrailer = std::make_unique<vcl::filter::PDFTrailerElement>(rDocument);
+ auto pTrailerPtr = pTrailer.get();
+ rElements.push_back(std::move(pTrailer));
+ return pTrailerPtr;
+}
+void addEndObjectElement(std::vector<std::unique_ptr<vcl::filter::PDFElement>>& rElements)
+{
+ rElements.push_back(std::make_unique<vcl::filter::PDFEndObjectElement>());
+}
+
+void addDictionaryElement(std::vector<std::unique_ptr<vcl::filter::PDFElement>>& rElements)
+{
+ rElements.push_back(std::make_unique<vcl::filter::PDFDictionaryElement>());
+}
+
+void addEndDictionaryElement(std::vector<std::unique_ptr<vcl::filter::PDFElement>>& rElements)
+{
+ rElements.push_back(std::make_unique<vcl::filter::PDFEndDictionaryElement>());
+}
+
+void addNameElement(std::vector<std::unique_ptr<vcl::filter::PDFElement>>& rElements,
+ OString const& rName)
+{
+ auto pNameElement = std::make_unique<vcl::filter::PDFNameElement>();
+ pNameElement->SetValue(rName);
+ rElements.push_back(std::move(pNameElement));
+}
+
+vcl::filter::PDFNumberElement*
+addNumberElement(std::vector<std::unique_ptr<vcl::filter::PDFElement>>& rElements, double fNumber)
+{
+ auto pNumberElement = std::make_unique<vcl::filter::PDFNumberElement>();
+ auto pNumberElementPtr = pNumberElement.get();
+ pNumberElement->SetValue(fNumber);
+ rElements.push_back(std::move(pNumberElement));
+ return pNumberElementPtr;
+}
+
+void addReferenceElement(std::vector<std::unique_ptr<vcl::filter::PDFElement>>& rElements,
+ vcl::filter::PDFDocument& rDocument,
+ vcl::filter::PDFNumberElement* pNumber1,
+ vcl::filter::PDFNumberElement* pNumber2)
+{
+ auto pReferenceElement
+ = std::make_unique<vcl::filter::PDFReferenceElement>(rDocument, *pNumber1, *pNumber2);
+ rElements.push_back(std::move(pReferenceElement));
+}
+
+void addArrayElement(std::vector<std::unique_ptr<vcl::filter::PDFElement>>& rElements,
+ vcl::filter::PDFObjectElement* pObjectPointer)
+{
+ auto pArray = std::make_unique<vcl::filter::PDFArrayElement>(pObjectPointer);
+ rElements.push_back(std::move(pArray));
+}
+
+void addEndArrayElement(std::vector<std::unique_ptr<vcl::filter::PDFElement>>& rElements)
+{
+ rElements.push_back(std::make_unique<vcl::filter::PDFEndArrayElement>());
+}
+
+} // end anonymous namespace
+
+CPPUNIT_TEST_FIXTURE(PDFDocumentTest, testParseEmptyDictionary)
+{
+ std::vector<std::unique_ptr<vcl::filter::PDFElement>> aElements;
+ vcl::filter::PDFDocument aDocument;
+ addObjectElement(aElements, aDocument, 1, 0);
+ addDictionaryElement(aElements);
+ addEndDictionaryElement(aElements);
+ addEndObjectElement(aElements);
+
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElements[0].get());
+ CPPUNIT_ASSERT(pObject);
+
+ vcl::filter::PDFObjectParser aParser(aElements);
+ aParser.parse(pObject);
+
+ CPPUNIT_ASSERT(pObject->GetDictionary());
+ CPPUNIT_ASSERT_EQUAL(size_t(0), pObject->GetDictionary()->GetItems().size());
+}
+
+CPPUNIT_TEST_FIXTURE(PDFDocumentTest, testParseDictionaryWithName)
+{
+ std::vector<std::unique_ptr<vcl::filter::PDFElement>> aElements;
+ vcl::filter::PDFDocument aDocument;
+ {
+ addObjectElement(aElements, aDocument, 1, 0);
+ addDictionaryElement(aElements);
+ addNameElement(aElements, "Test"_ostr);
+ addNumberElement(aElements, 30.0);
+ addEndDictionaryElement(aElements);
+ addEndObjectElement(aElements);
+ }
+
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElements[0].get());
+ CPPUNIT_ASSERT(pObject);
+
+ vcl::filter::PDFObjectParser aParser(aElements);
+ aParser.parse(pObject);
+
+ CPPUNIT_ASSERT(pObject->GetDictionary());
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pObject->GetDictionary()->GetItems().size());
+ auto& rItems = pObject->GetDictionary()->GetItems();
+ auto pNumberElement = dynamic_cast<vcl::filter::PDFNumberElement*>(rItems.at("Test"_ostr));
+ CPPUNIT_ASSERT(pNumberElement);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(30.0, pNumberElement->GetValue(), 1e-4);
+}
+
+CPPUNIT_TEST_FIXTURE(PDFDocumentTest, testParseDictionaryNested)
+{
+ std::vector<std::unique_ptr<vcl::filter::PDFElement>> aElements;
+ vcl::filter::PDFDocument aDocument;
+ {
+ addObjectElement(aElements, aDocument, 1, 0);
+ addDictionaryElement(aElements);
+
+ addNameElement(aElements, "Nested1"_ostr);
+ addDictionaryElement(aElements);
+ {
+ addNameElement(aElements, "Nested2"_ostr);
+ addDictionaryElement(aElements);
+ {
+ addNameElement(aElements, "SomeOtherKey"_ostr);
+ addNameElement(aElements, "SomeOtherValue"_ostr);
+ }
+ addEndDictionaryElement(aElements);
+ }
+ addEndDictionaryElement(aElements);
+
+ addNameElement(aElements, "SomeOtherKey"_ostr);
+ addNameElement(aElements, "SomeOtherValue"_ostr);
+
+ addEndObjectElement(aElements);
+ }
+
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElements[0].get());
+ CPPUNIT_ASSERT(pObject);
+
+ vcl::filter::PDFObjectParser aParser(aElements);
+ aParser.parse(pObject);
+
+ CPPUNIT_ASSERT(pObject->GetDictionary());
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pObject->GetDictionary()->GetItems().size());
+ CPPUNIT_ASSERT(pObject->Lookup("Nested1"_ostr));
+ CPPUNIT_ASSERT(pObject->Lookup("SomeOtherKey"_ostr));
+}
+
+CPPUNIT_TEST_FIXTURE(PDFDocumentTest, testParseEmptyArray)
+{
+ std::vector<std::unique_ptr<vcl::filter::PDFElement>> aElements;
+ vcl::filter::PDFDocument aDocument;
+ {
+ auto pObjectPtr = addObjectElement(aElements, aDocument, 1, 0);
+ addArrayElement(aElements, pObjectPtr);
+ addEndArrayElement(aElements);
+ addEndObjectElement(aElements);
+ }
+
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElements[0].get());
+ CPPUNIT_ASSERT(pObject);
+
+ vcl::filter::PDFObjectParser aParser(aElements);
+ aParser.parse(pObject);
+
+ CPPUNIT_ASSERT(pObject->GetArray());
+ CPPUNIT_ASSERT_EQUAL(size_t(0), pObject->GetArray()->GetElements().size());
+}
+
+CPPUNIT_TEST_FIXTURE(PDFDocumentTest, testParseArrayWithSimpleElements)
+{
+ std::vector<std::unique_ptr<vcl::filter::PDFElement>> aElements;
+ vcl::filter::PDFDocument aDocument;
+
+ {
+ auto pObjectPtr = addObjectElement(aElements, aDocument, 1, 0);
+ addArrayElement(aElements, pObjectPtr);
+ addNameElement(aElements, "Test"_ostr);
+ addNumberElement(aElements, 30.0);
+ addEndArrayElement(aElements);
+ addEndObjectElement(aElements);
+ }
+
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElements[0].get());
+ CPPUNIT_ASSERT(pObject);
+
+ vcl::filter::PDFObjectParser aParser(aElements);
+ aParser.parse(pObject);
+
+ CPPUNIT_ASSERT(pObject->GetArray());
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pObject->GetArray()->GetElements().size());
+}
+
+CPPUNIT_TEST_FIXTURE(PDFDocumentTest, testParseArrayNestedWithNumbers)
+{
+ std::vector<std::unique_ptr<vcl::filter::PDFElement>> aElements;
+ vcl::filter::PDFDocument aDocument;
+
+ // [ 1 [ 10 ] 2 ]
+ {
+ auto pObjectPtr = addObjectElement(aElements, aDocument, 1, 0);
+ addArrayElement(aElements, pObjectPtr);
+ {
+ addNumberElement(aElements, 1.0);
+ addArrayElement(aElements, pObjectPtr);
+ addNumberElement(aElements, 10.0);
+ addEndArrayElement(aElements);
+ addNumberElement(aElements, 2.0);
+ }
+ addEndArrayElement(aElements);
+ addEndObjectElement(aElements);
+ }
+
+ // Assert
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElements[0].get());
+ CPPUNIT_ASSERT(pObject);
+
+ vcl::filter::PDFObjectParser aParser(aElements);
+ aParser.parse(pObject);
+
+ CPPUNIT_ASSERT(pObject->GetArray());
+ CPPUNIT_ASSERT_EQUAL(size_t(3), pObject->GetArray()->GetElements().size());
+ auto pRootArray = pObject->GetArray();
+
+ auto pNumber1 = dynamic_cast<vcl::filter::PDFNumberElement*>(pRootArray->GetElement(0));
+ CPPUNIT_ASSERT(pNumber1);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, pNumber1->GetValue(), 1e-4);
+
+ auto pArray3 = dynamic_cast<vcl::filter::PDFArrayElement*>(pRootArray->GetElement(1));
+ CPPUNIT_ASSERT(pArray3);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pArray3->GetElements().size());
+
+ auto pNumber2 = dynamic_cast<vcl::filter::PDFNumberElement*>(pRootArray->GetElement(2));
+ CPPUNIT_ASSERT(pNumber1);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, pNumber2->GetValue(), 1e-4);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PDFDocumentTest, testParseArrayNestedWithNames)
+{
+ std::vector<std::unique_ptr<vcl::filter::PDFElement>> aElements;
+ vcl::filter::PDFDocument aDocument;
+
+ // [/Inner1/Inner2[/Inner31][/Inner41/Inner42[/Inner431/Inner432]][/Inner51[/Inner521]]]
+
+ {
+ auto pObjectPtr = addObjectElement(aElements, aDocument, 1, 0);
+ addArrayElement(aElements, pObjectPtr);
+ {
+ addNameElement(aElements, "Inner1"_ostr);
+ addNameElement(aElements, "Inner2"_ostr);
+
+ addArrayElement(aElements, pObjectPtr);
+ {
+ addNameElement(aElements, "Inner31"_ostr);
+ }
+ addEndArrayElement(aElements);
+
+ addArrayElement(aElements, pObjectPtr);
+ {
+ addNameElement(aElements, "Inner41"_ostr);
+ addNameElement(aElements, "Inner42"_ostr);
+ addArrayElement(aElements, pObjectPtr);
+ {
+ addNameElement(aElements, "Inner431"_ostr);
+ addNameElement(aElements, "Inner432"_ostr);
+ }
+ addEndArrayElement(aElements);
+ }
+ addEndArrayElement(aElements);
+
+ addArrayElement(aElements, pObjectPtr);
+ {
+ addNameElement(aElements, "Inner51"_ostr);
+ addArrayElement(aElements, pObjectPtr);
+ {
+ addNameElement(aElements, "Inner521"_ostr);
+ }
+ addEndArrayElement(aElements);
+ }
+ addEndArrayElement(aElements);
+ }
+ addEndArrayElement(aElements);
+ addEndObjectElement(aElements);
+ }
+
+ // Assert
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElements[0].get());
+ CPPUNIT_ASSERT(pObject);
+
+ vcl::filter::PDFObjectParser aParser(aElements);
+ aParser.parse(pObject);
+
+ CPPUNIT_ASSERT(pObject->GetArray());
+ CPPUNIT_ASSERT_EQUAL(size_t(5), pObject->GetArray()->GetElements().size());
+ auto pRootArray = pObject->GetArray();
+
+ auto pName1 = dynamic_cast<vcl::filter::PDFNameElement*>(pRootArray->GetElement(0));
+ CPPUNIT_ASSERT(pName1);
+ CPPUNIT_ASSERT_EQUAL("Inner1"_ostr, pName1->GetValue());
+
+ auto pName2 = dynamic_cast<vcl::filter::PDFNameElement*>(pRootArray->GetElement(1));
+ CPPUNIT_ASSERT(pName2);
+ CPPUNIT_ASSERT_EQUAL("Inner2"_ostr, pName2->GetValue());
+
+ auto pArray3 = dynamic_cast<vcl::filter::PDFArrayElement*>(pRootArray->GetElement(2));
+ CPPUNIT_ASSERT(pArray3);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pArray3->GetElements().size());
+
+ auto pInner31 = dynamic_cast<vcl::filter::PDFNameElement*>(pArray3->GetElement(0));
+ CPPUNIT_ASSERT(pInner31);
+ CPPUNIT_ASSERT_EQUAL("Inner31"_ostr, pInner31->GetValue());
+
+ auto pArray4 = dynamic_cast<vcl::filter::PDFArrayElement*>(pRootArray->GetElement(3));
+ CPPUNIT_ASSERT(pArray4);
+ CPPUNIT_ASSERT_EQUAL(size_t(3), pArray4->GetElements().size());
+
+ auto pInner41 = dynamic_cast<vcl::filter::PDFNameElement*>(pArray4->GetElement(0));
+ CPPUNIT_ASSERT(pInner41);
+ CPPUNIT_ASSERT_EQUAL("Inner41"_ostr, pInner41->GetValue());
+
+ auto pInner42 = dynamic_cast<vcl::filter::PDFNameElement*>(pArray4->GetElement(1));
+ CPPUNIT_ASSERT(pInner42);
+ CPPUNIT_ASSERT_EQUAL("Inner42"_ostr, pInner42->GetValue());
+
+ auto pArray43 = dynamic_cast<vcl::filter::PDFArrayElement*>(pArray4->GetElement(2));
+ CPPUNIT_ASSERT(pArray43);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pArray43->GetElements().size());
+
+ auto pInner431 = dynamic_cast<vcl::filter::PDFNameElement*>(pArray43->GetElement(0));
+ CPPUNIT_ASSERT(pInner431);
+ CPPUNIT_ASSERT_EQUAL("Inner431"_ostr, pInner431->GetValue());
+
+ auto pInner432 = dynamic_cast<vcl::filter::PDFNameElement*>(pArray43->GetElement(1));
+ CPPUNIT_ASSERT(pInner432);
+ CPPUNIT_ASSERT_EQUAL("Inner432"_ostr, pInner432->GetValue());
+
+ auto pArray5 = dynamic_cast<vcl::filter::PDFArrayElement*>(pRootArray->GetElement(4));
+ CPPUNIT_ASSERT(pArray5);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pArray5->GetElements().size());
+
+ auto pInner51 = dynamic_cast<vcl::filter::PDFNameElement*>(pArray5->GetElement(0));
+ CPPUNIT_ASSERT(pInner51);
+ CPPUNIT_ASSERT_EQUAL("Inner51"_ostr, pInner51->GetValue());
+
+ auto pArray52 = dynamic_cast<vcl::filter::PDFArrayElement*>(pArray5->GetElement(1));
+ CPPUNIT_ASSERT(pArray52);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pArray52->GetElements().size());
+
+ auto pInner521 = dynamic_cast<vcl::filter::PDFNameElement*>(pArray52->GetElement(0));
+ CPPUNIT_ASSERT(pInner521);
+ CPPUNIT_ASSERT_EQUAL("Inner521"_ostr, pInner521->GetValue());
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PDFDocumentTest, testParseTrailer)
+{
+ std::vector<std::unique_ptr<vcl::filter::PDFElement>> aElements;
+ vcl::filter::PDFDocument aDocument;
+
+ {
+ addTrailerObjectElement(aElements, aDocument);
+ addDictionaryElement(aElements);
+ addNameElement(aElements, "Size"_ostr);
+ addNumberElement(aElements, 11.0);
+ addEndDictionaryElement(aElements);
+ }
+ {
+ auto pTrailer = dynamic_cast<vcl::filter::PDFTrailerElement*>(aElements[0].get());
+ CPPUNIT_ASSERT(pTrailer);
+
+ vcl::filter::PDFObjectParser aParser(aElements);
+ aParser.parse(pTrailer);
+
+ CPPUNIT_ASSERT(pTrailer->GetDictionary());
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pTrailer->GetDictionary()->GetItems().size());
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PDFDocumentTest, testParseTrailerWithReference)
+{
+ std::vector<std::unique_ptr<vcl::filter::PDFElement>> aElements;
+ vcl::filter::PDFDocument aDocument;
+
+ {
+ addTrailerObjectElement(aElements, aDocument);
+ addDictionaryElement(aElements);
+ addNameElement(aElements, "Reference"_ostr);
+ auto pNumberElement1 = addNumberElement(aElements, 11.0);
+ auto pNumberElement2 = addNumberElement(aElements, 0.0);
+ addReferenceElement(aElements, aDocument, pNumberElement1, pNumberElement2);
+ addEndDictionaryElement(aElements);
+ }
+ {
+ auto pTrailer = dynamic_cast<vcl::filter::PDFTrailerElement*>(aElements[0].get());
+ CPPUNIT_ASSERT(pTrailer);
+
+ vcl::filter::PDFObjectParser aParser(aElements);
+ aParser.parse(pTrailer);
+
+ CPPUNIT_ASSERT(pTrailer->GetDictionary());
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pTrailer->GetDictionary()->GetItems().size());
+ auto pElement = pTrailer->Lookup("Reference"_ostr);
+ CPPUNIT_ASSERT(pElement);
+ auto pReference = dynamic_cast<vcl::filter::PDFReferenceElement*>(pElement);
+ CPPUNIT_ASSERT(pReference);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(11.0, pReference->GetObjectValue(), 1e-4);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, pReference->GetGenerationValue(), 1e-4);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/PDFiumLibraryTest.cxx b/vcl/qa/cppunit/PDFiumLibraryTest.cxx
new file mode 100644
index 0000000000..966c44a3f5
--- /dev/null
+++ b/vcl/qa/cppunit/PDFiumLibraryTest.cxx
@@ -0,0 +1,454 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <string_view>
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <unotest/bootstrapfixturebase.hxx>
+#include <unotest/directories.hxx>
+#include <unotools/datetime.hxx>
+
+#include <com/sun/star/util/DateTime.hpp>
+
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <tools/stream.hxx>
+
+#include <vcl/filter/PDFiumLibrary.hxx>
+#include <vcl/pdfread.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+
+class PDFiumLibraryTest : public test::BootstrapFixtureBase
+{
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(u"/vcl/qa/cppunit/data/") + sFileName;
+ }
+
+ void testDocument();
+ void testPages();
+ void testPageObjects();
+ void testAnnotationsMadeInEvince();
+ void testAnnotationsMadeInAcrobat();
+ void testAnnotationsDifferentTypes();
+ void testTools();
+ void testFormFields();
+
+ CPPUNIT_TEST_SUITE(PDFiumLibraryTest);
+ CPPUNIT_TEST(testDocument);
+ CPPUNIT_TEST(testPages);
+ CPPUNIT_TEST(testPageObjects);
+ CPPUNIT_TEST(testAnnotationsMadeInEvince);
+ CPPUNIT_TEST(testAnnotationsMadeInAcrobat);
+ CPPUNIT_TEST(testAnnotationsDifferentTypes);
+ CPPUNIT_TEST(testTools);
+ CPPUNIT_TEST(testFormFields);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void PDFiumLibraryTest::testDocument()
+{
+ OUString aURL = getFullUrl(u"Pangram.pdf");
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ aGraphic.makeAvailable();
+
+ auto pVectorGraphicData = aGraphic.getVectorGraphicData();
+ CPPUNIT_ASSERT(pVectorGraphicData);
+ CPPUNIT_ASSERT_EQUAL(VectorGraphicDataType::Pdf, pVectorGraphicData->getType());
+
+ auto& rDataContainer = pVectorGraphicData->getBinaryDataContainer();
+
+ auto pPdfium = vcl::pdf::PDFiumLibrary::get();
+ CPPUNIT_ASSERT(pPdfium);
+ auto pDocument
+ = pPdfium->openDocument(rDataContainer.getData(), rDataContainer.getSize(), OString());
+ CPPUNIT_ASSERT(pDocument);
+
+ CPPUNIT_ASSERT_EQUAL(1, pDocument->getPageCount());
+
+ auto aSize = pDocument->getPageSize(0);
+ CPPUNIT_ASSERT_EQUAL(612.0, aSize.getWidth());
+ CPPUNIT_ASSERT_EQUAL(792.0, aSize.getHeight());
+}
+
+void PDFiumLibraryTest::testPages()
+{
+ OUString aURL = getFullUrl(u"Pangram.pdf");
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ aGraphic.makeAvailable();
+
+ auto pVectorGraphicData = aGraphic.getVectorGraphicData();
+ CPPUNIT_ASSERT(pVectorGraphicData);
+ CPPUNIT_ASSERT_EQUAL(VectorGraphicDataType::Pdf, pVectorGraphicData->getType());
+
+ auto& rDataContainer = pVectorGraphicData->getBinaryDataContainer();
+
+ auto pPdfium = vcl::pdf::PDFiumLibrary::get();
+ auto pDocument
+ = pPdfium->openDocument(rDataContainer.getData(), rDataContainer.getSize(), OString());
+ CPPUNIT_ASSERT(pDocument);
+
+ CPPUNIT_ASSERT_EQUAL(1, pDocument->getPageCount());
+
+ auto pPage = pDocument->openPage(0);
+ CPPUNIT_ASSERT(pPage);
+}
+
+void PDFiumLibraryTest::testPageObjects()
+{
+ OUString aURL = getFullUrl(u"Pangram.pdf");
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ aGraphic.makeAvailable();
+
+ auto pVectorGraphicData = aGraphic.getVectorGraphicData();
+ CPPUNIT_ASSERT(pVectorGraphicData);
+ CPPUNIT_ASSERT_EQUAL(VectorGraphicDataType::Pdf, pVectorGraphicData->getType());
+
+ auto& rDataContainer = pVectorGraphicData->getBinaryDataContainer();
+
+ auto pPdfium = vcl::pdf::PDFiumLibrary::get();
+ auto pDocument
+ = pPdfium->openDocument(rDataContainer.getData(), rDataContainer.getSize(), OString());
+ CPPUNIT_ASSERT(pDocument);
+
+ CPPUNIT_ASSERT_EQUAL(1, pDocument->getPageCount());
+
+ auto pPage = pDocument->openPage(0);
+ CPPUNIT_ASSERT(pPage);
+
+ CPPUNIT_ASSERT_EQUAL(12, pPage->getObjectCount());
+
+ auto pPageObject = pPage->getObject(0);
+ auto pTextPage = pPage->getTextPage();
+
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Text, pPageObject->getType());
+
+ CPPUNIT_ASSERT_EQUAL(OUString("The quick, brown fox jumps over a lazy dog. DJs flock by when "
+ "MTV ax quiz prog. Junk MTV quiz "),
+ pPageObject->getText(pTextPage));
+
+ CPPUNIT_ASSERT_EQUAL(12.0, pPageObject->getFontSize());
+ CPPUNIT_ASSERT_EQUAL(OUString("Liberation Serif"), pPageObject->getFontName());
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFTextRenderMode::Fill, pPageObject->getTextRenderMode());
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, pPageObject->getFillColor());
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, pPageObject->getStrokeColor());
+
+ basegfx::B2DHomMatrix aMatrix = pPageObject->getMatrix();
+ // Ignore translation, ensure there is no rotate/scale.
+ aMatrix.set(0, 2, 0);
+ aMatrix.set(1, 2, 0);
+ CPPUNIT_ASSERT_EQUAL(true, aMatrix.isIdentity());
+
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(057.01, pPageObject->getBounds().getMinX(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(721.51, pPageObject->getBounds().getMinY(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(539.48, pPageObject->getBounds().getMaxX(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(732.54, pPageObject->getBounds().getMaxY(), 1E-2);
+}
+
+void PDFiumLibraryTest::testAnnotationsMadeInEvince()
+{
+ OUString aURL = getFullUrl(u"PangramWithAnnotations.pdf");
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ aGraphic.makeAvailable();
+
+ auto pVectorGraphicData = aGraphic.getVectorGraphicData();
+ CPPUNIT_ASSERT(pVectorGraphicData);
+ CPPUNIT_ASSERT_EQUAL(VectorGraphicDataType::Pdf, pVectorGraphicData->getType());
+
+ auto& rDataContainer = pVectorGraphicData->getBinaryDataContainer();
+
+ auto pPdfium = vcl::pdf::PDFiumLibrary::get();
+ auto pDocument
+ = pPdfium->openDocument(rDataContainer.getData(), rDataContainer.getSize(), OString());
+ CPPUNIT_ASSERT(pDocument);
+
+ CPPUNIT_ASSERT_EQUAL(1, pDocument->getPageCount());
+
+ auto pPage = pDocument->openPage(0);
+ CPPUNIT_ASSERT(pPage);
+
+ CPPUNIT_ASSERT_EQUAL(2, pPage->getAnnotationCount());
+
+ {
+ auto pAnnotation = pPage->getAnnotation(0);
+ CPPUNIT_ASSERT(pAnnotation);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Text, pAnnotation->getSubType());
+
+ OUString aPopupString = pAnnotation->getString(vcl::pdf::constDictionaryKeyTitle);
+ CPPUNIT_ASSERT_EQUAL(OUString("quikee"), aPopupString);
+
+ OUString aContentsString = pAnnotation->getString(vcl::pdf::constDictionaryKeyContents);
+ CPPUNIT_ASSERT_EQUAL(OUString("Annotation test"), aContentsString);
+
+ CPPUNIT_ASSERT_EQUAL(true, pAnnotation->hasKey(vcl::pdf::constDictionaryKeyPopup));
+ auto pPopupAnnotation = pAnnotation->getLinked(vcl::pdf::constDictionaryKeyPopup);
+ CPPUNIT_ASSERT(pPopupAnnotation);
+
+ CPPUNIT_ASSERT_EQUAL(1, pPage->getAnnotationIndex(pPopupAnnotation));
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Popup, pPopupAnnotation->getSubType());
+
+ OUString sDateTimeString
+ = pAnnotation->getString(vcl::pdf::constDictionaryKeyModificationDate);
+ CPPUNIT_ASSERT_EQUAL(OUString("D:20200612201322+02'00"), sDateTimeString);
+ }
+
+ {
+ auto pAnnotation = pPage->getAnnotation(1);
+ CPPUNIT_ASSERT(pAnnotation);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Popup, pAnnotation->getSubType());
+ }
+}
+
+void PDFiumLibraryTest::testAnnotationsMadeInAcrobat()
+{
+ OUString aURL = getFullUrl(u"PangramAcrobatAnnotations.pdf");
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ aGraphic.makeAvailable();
+
+ auto pVectorGraphicData = aGraphic.getVectorGraphicData();
+ CPPUNIT_ASSERT(pVectorGraphicData);
+ CPPUNIT_ASSERT_EQUAL(VectorGraphicDataType::Pdf, pVectorGraphicData->getType());
+
+ auto& rDataContainer = pVectorGraphicData->getBinaryDataContainer();
+
+ auto pPdfium = vcl::pdf::PDFiumLibrary::get();
+ auto pDocument
+ = pPdfium->openDocument(rDataContainer.getData(), rDataContainer.getSize(), OString());
+ CPPUNIT_ASSERT(pDocument);
+
+ CPPUNIT_ASSERT_EQUAL(1, pDocument->getPageCount());
+
+ auto pPage = pDocument->openPage(0);
+ CPPUNIT_ASSERT(pPage);
+
+ CPPUNIT_ASSERT_EQUAL(4, pPage->getAnnotationCount());
+
+ {
+ auto pAnnotation = pPage->getAnnotation(0);
+ CPPUNIT_ASSERT(pAnnotation);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Text, pAnnotation->getSubType());
+
+ OUString aPopupString = pAnnotation->getString(vcl::pdf::constDictionaryKeyTitle);
+ CPPUNIT_ASSERT_EQUAL(OUString("quikee"), aPopupString);
+
+ OUString aContentsString = pAnnotation->getString(vcl::pdf::constDictionaryKeyContents);
+ CPPUNIT_ASSERT_EQUAL(OUString("YEEEY"), aContentsString);
+
+ CPPUNIT_ASSERT_EQUAL(true, pAnnotation->hasKey(vcl::pdf::constDictionaryKeyPopup));
+ auto pPopupAnnotation = pAnnotation->getLinked(vcl::pdf::constDictionaryKeyPopup);
+ CPPUNIT_ASSERT(pPopupAnnotation);
+
+ CPPUNIT_ASSERT_EQUAL(1, pPage->getAnnotationIndex(pPopupAnnotation));
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Popup, pPopupAnnotation->getSubType());
+ }
+
+ {
+ auto pAnnotation = pPage->getAnnotation(1);
+ CPPUNIT_ASSERT(pAnnotation);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Popup, pAnnotation->getSubType());
+ }
+
+ {
+ auto pAnnotation = pPage->getAnnotation(2);
+ CPPUNIT_ASSERT(pAnnotation);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Text, pAnnotation->getSubType());
+
+ OUString aPopupString = pAnnotation->getString(vcl::pdf::constDictionaryKeyTitle);
+ CPPUNIT_ASSERT_EQUAL(OUString("quikee"), aPopupString);
+
+ OUString aContentsString = pAnnotation->getString(vcl::pdf::constDictionaryKeyContents);
+ CPPUNIT_ASSERT_EQUAL(OUString("Note"), aContentsString);
+
+ CPPUNIT_ASSERT_EQUAL(true, pAnnotation->hasKey(vcl::pdf::constDictionaryKeyPopup));
+ auto pPopupAnnotation = pAnnotation->getLinked(vcl::pdf::constDictionaryKeyPopup);
+ CPPUNIT_ASSERT(pPopupAnnotation);
+
+ CPPUNIT_ASSERT_EQUAL(3, pPage->getAnnotationIndex(pPopupAnnotation));
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Popup, pPopupAnnotation->getSubType());
+ }
+
+ {
+ auto pAnnotation = pPage->getAnnotation(3);
+ CPPUNIT_ASSERT(pAnnotation);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Popup, pAnnotation->getSubType());
+ }
+}
+
+void PDFiumLibraryTest::testFormFields()
+{
+ // Given a document with a form field that looks like plain text:
+ OUString aURL = getFullUrl(u"form-fields.pdf");
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ SvMemoryStream aMemory;
+ aMemory.WriteStream(aFileStream);
+ aMemory.Seek(0);
+
+ // When rendering its first (and only) page to a bitmap:
+ std::vector<BitmapEx> aBitmaps;
+ int nRet = vcl::RenderPDFBitmaps(aMemory.GetData(), aMemory.GetSize(), aBitmaps);
+ CPPUNIT_ASSERT(nRet);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aBitmaps.size());
+
+ // Then make sure the bitmap contains that text:
+ Bitmap aBitmap = aBitmaps[0].GetBitmap();
+ BitmapReadAccess aAccess(aBitmap);
+ Size aSize = aBitmap.GetSizePixel();
+ std::set<sal_uInt32> aColors;
+ for (tools::Long y = 0; y < aSize.Height(); ++y)
+ {
+ for (tools::Long x = 0; x < aSize.Width(); ++x)
+ {
+ aColors.insert(static_cast<sal_uInt32>(aAccess.GetPixel(y, x)));
+ }
+ }
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected greater than: 1
+ // - Actual : 1
+ // i.e. at least black text and white background is expected (possibly more, due to
+ // anti-aliasing), but nothing was rendered.
+ CPPUNIT_ASSERT_GREATER(static_cast<size_t>(1), aColors.size());
+}
+
+void PDFiumLibraryTest::testAnnotationsDifferentTypes()
+{
+ OUString aURL = getFullUrl(u"PangramWithMultipleTypeOfAnnotations.pdf");
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ aGraphic.makeAvailable();
+
+ auto pVectorGraphicData = aGraphic.getVectorGraphicData();
+ CPPUNIT_ASSERT(pVectorGraphicData);
+ CPPUNIT_ASSERT_EQUAL(VectorGraphicDataType::Pdf, pVectorGraphicData->getType());
+
+ auto& rDataContainer = pVectorGraphicData->getBinaryDataContainer();
+
+ auto pPdfium = vcl::pdf::PDFiumLibrary::get();
+ auto pDocument
+ = pPdfium->openDocument(rDataContainer.getData(), rDataContainer.getSize(), OString());
+ CPPUNIT_ASSERT(pDocument);
+
+ CPPUNIT_ASSERT_EQUAL(1, pDocument->getPageCount());
+
+ auto pPage = pDocument->openPage(0);
+ CPPUNIT_ASSERT(pPage);
+
+ CPPUNIT_ASSERT_EQUAL(6, pPage->getAnnotationCount());
+
+ {
+ auto pAnnotation = pPage->getAnnotation(0);
+ CPPUNIT_ASSERT(pAnnotation);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::FreeText, pAnnotation->getSubType());
+ CPPUNIT_ASSERT_EQUAL(0, pAnnotation->getObjectCount());
+ OUString aContentsString = pAnnotation->getString(vcl::pdf::constDictionaryKeyContents);
+ CPPUNIT_ASSERT_EQUAL(OUString("Inline Note"), aContentsString);
+ auto const& rLineGeometry = pAnnotation->getLineGeometry();
+ CPPUNIT_ASSERT_EQUAL(true, rLineGeometry.empty());
+ }
+
+ {
+ auto pAnnotation = pPage->getAnnotation(1);
+ CPPUNIT_ASSERT(pAnnotation);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Ink, pAnnotation->getSubType());
+ CPPUNIT_ASSERT_EQUAL(0, pAnnotation->getObjectCount());
+ OUString aContentsString = pAnnotation->getString(vcl::pdf::constDictionaryKeyContents);
+ CPPUNIT_ASSERT_EQUAL(OUString("Freehand Text"), aContentsString);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pAnnotation->getInkStrokes().size());
+ auto const& aInkStrokes = pAnnotation->getInkStrokes();
+ auto const& aPoints = aInkStrokes[0];
+ CPPUNIT_ASSERT_EQUAL(size_t(74), aPoints.size());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0f, pAnnotation->getBorderWidth(), 1E-2);
+ auto const& rLineGeometry = pAnnotation->getLineGeometry();
+ CPPUNIT_ASSERT_EQUAL(true, rLineGeometry.empty());
+ }
+
+ {
+ auto pAnnotation = pPage->getAnnotation(2);
+ CPPUNIT_ASSERT(pAnnotation);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Line, pAnnotation->getSubType());
+ CPPUNIT_ASSERT_EQUAL(0, pAnnotation->getObjectCount());
+ OUString aContentsString = pAnnotation->getString(vcl::pdf::constDictionaryKeyContents);
+ CPPUNIT_ASSERT_EQUAL(OUString("Line Text"), aContentsString);
+ auto const& rLineGeometry = pAnnotation->getLineGeometry();
+ CPPUNIT_ASSERT_EQUAL(false, rLineGeometry.empty());
+ }
+
+ {
+ auto pAnnotation = pPage->getAnnotation(3);
+ CPPUNIT_ASSERT(pAnnotation);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Polygon, pAnnotation->getSubType());
+ CPPUNIT_ASSERT_EQUAL(0, pAnnotation->getObjectCount());
+ CPPUNIT_ASSERT_EQUAL(true, pAnnotation->hasKey("Vertices"_ostr));
+ OUString aContentsString = pAnnotation->getString(vcl::pdf::constDictionaryKeyContents);
+ CPPUNIT_ASSERT_EQUAL(OUString("Polygon Text"), aContentsString);
+ auto const& aVertices = pAnnotation->getVertices();
+ CPPUNIT_ASSERT_EQUAL(size_t(3), aVertices.size());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0f, pAnnotation->getBorderWidth(), 1E-2);
+ auto const& rLineGeometry = pAnnotation->getLineGeometry();
+ CPPUNIT_ASSERT_EQUAL(true, rLineGeometry.empty());
+ }
+
+ {
+ auto pAnnotation = pPage->getAnnotation(4);
+ CPPUNIT_ASSERT(pAnnotation);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Circle, pAnnotation->getSubType());
+ CPPUNIT_ASSERT_EQUAL(0, pAnnotation->getObjectCount());
+ OUString aContentsString = pAnnotation->getString(vcl::pdf::constDictionaryKeyContents);
+ CPPUNIT_ASSERT_EQUAL(OUString("Ellipse Text"), aContentsString);
+ auto const& rLineGeometry = pAnnotation->getLineGeometry();
+ CPPUNIT_ASSERT_EQUAL(true, rLineGeometry.empty());
+ }
+
+ {
+ auto pAnnotation = pPage->getAnnotation(5);
+ CPPUNIT_ASSERT(pAnnotation);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Square, pAnnotation->getSubType());
+ CPPUNIT_ASSERT_EQUAL(0, pAnnotation->getObjectCount());
+ OUString aContentsString = pAnnotation->getString(vcl::pdf::constDictionaryKeyContents);
+ CPPUNIT_ASSERT_EQUAL(OUString("Rectangle Text"), aContentsString);
+ CPPUNIT_ASSERT_EQUAL(Color(0xFF, 0xE0, 0x00), pAnnotation->getColor());
+ CPPUNIT_ASSERT_EQUAL(false, pAnnotation->hasKey(vcl::pdf::constDictionaryKeyInteriorColor));
+ auto const& rLineGeometry = pAnnotation->getLineGeometry();
+ CPPUNIT_ASSERT_EQUAL(true, rLineGeometry.empty());
+ }
+}
+
+void PDFiumLibraryTest::testTools()
+{
+ OUString sConverted = vcl::pdf::convertPdfDateToISO8601(u"D:20200612201322+02'00");
+
+ css::util::DateTime aDateTime;
+ CPPUNIT_ASSERT(utl::ISO8601parseDateTime(sConverted, aDateTime));
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(2020), aDateTime.Year);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(6), aDateTime.Month);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(12), aDateTime.Day);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(20), aDateTime.Hours);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(13), aDateTime.Minutes);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(22), aDateTime.Seconds);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(0), aDateTime.NanoSeconds);
+ CPPUNIT_ASSERT_EQUAL(false, bool(aDateTime.IsUTC));
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(PDFiumLibraryTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/ScanlineToolsTest.cxx b/vcl/qa/cppunit/ScanlineToolsTest.cxx
new file mode 100644
index 0000000000..97233f2692
--- /dev/null
+++ b/vcl/qa/cppunit/ScanlineToolsTest.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/.
+ */
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <bitmap/ScanlineTools.hxx>
+
+namespace
+{
+class ScanlineToolsTest : public CppUnit::TestFixture
+{
+ void ScanlineTransformer_32_ARGB();
+ void ScanlineTransformer_24_BGR();
+ void ScanlineTransformer_8bit_Palette();
+ void ScanlineTransformer_4bit_Palette();
+ void ScanlineTransformer_1bit_Palette();
+
+ CPPUNIT_TEST_SUITE(ScanlineToolsTest);
+ CPPUNIT_TEST(ScanlineTransformer_32_ARGB);
+ CPPUNIT_TEST(ScanlineTransformer_24_BGR);
+ CPPUNIT_TEST(ScanlineTransformer_8bit_Palette);
+ CPPUNIT_TEST(ScanlineTransformer_4bit_Palette);
+ CPPUNIT_TEST(ScanlineTransformer_1bit_Palette);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void ScanlineToolsTest::ScanlineTransformer_32_ARGB()
+{
+ BitmapPalette aPalette;
+ std::unique_ptr<vcl::bitmap::ScanlineTransformer> pScanlineTransformer
+ = vcl::bitmap::getScanlineTransformer(32, aPalette);
+
+ std::vector<sal_uInt8> aScanLine(5 * 4, 0); // 5 * 4 BytesPerPixel
+ pScanlineTransformer->startLine(aScanLine.data());
+
+ std::vector<Color> aColors{
+ Color(ColorAlpha, 255, 10, 250, 120), Color(ColorAlpha, 205, 30, 230, 110),
+ Color(ColorAlpha, 155, 50, 210, 100), Color(ColorAlpha, 105, 70, 190, 90),
+ Color(ColorAlpha, 55, 90, 170, 80),
+ };
+
+ for (Color const& aColor : aColors)
+ {
+ pScanlineTransformer->writePixel(aColor);
+ }
+
+ std::vector<sal_uInt8> aExpectedBytes{ 255, 10, 250, 120, 205, 30, 230, 110, 155, 50,
+ 210, 100, 105, 70, 190, 90, 55, 90, 170, 80 };
+
+ for (size_t i = 0; i < aScanLine.size(); ++i)
+ {
+ CPPUNIT_ASSERT_EQUAL(int(aExpectedBytes[i]), int(aScanLine[i]));
+ }
+}
+
+void ScanlineToolsTest::ScanlineTransformer_24_BGR()
+{
+ BitmapPalette aPalette;
+ std::unique_ptr<vcl::bitmap::ScanlineTransformer> pScanlineTransformer
+ = vcl::bitmap::getScanlineTransformer(24, aPalette);
+
+ std::vector<sal_uInt8> aScanLine(5 * 3, 0); // 5 * 3 BytesPerPixel
+ pScanlineTransformer->startLine(aScanLine.data());
+
+ std::vector<Color> aColors{
+ Color(ColorTransparency, 0, 10, 250, 120), Color(ColorTransparency, 50, 30, 230, 110),
+ Color(ColorTransparency, 100, 50, 210, 100), Color(ColorTransparency, 150, 70, 190, 90),
+ Color(ColorTransparency, 200, 90, 170, 80),
+ };
+
+ for (Color const& aColor : aColors)
+ {
+ pScanlineTransformer->writePixel(aColor);
+ }
+
+ std::vector<sal_uInt8> aExpectedBytes{ 120, 250, 10, 110, 230, 30, 100, 210,
+ 50, 90, 190, 70, 80, 170, 90 };
+
+ for (size_t i = 0; i < aScanLine.size(); ++i)
+ {
+ CPPUNIT_ASSERT_EQUAL(int(aExpectedBytes[i]), int(aScanLine[i]));
+ }
+}
+
+void ScanlineToolsTest::ScanlineTransformer_8bit_Palette()
+{
+ std::vector<Color> aColors{
+ Color(ColorTransparency, 0, 10, 250, 120), Color(ColorTransparency, 50, 30, 230, 110),
+ Color(ColorTransparency, 100, 50, 210, 100), Color(ColorTransparency, 150, 70, 190, 90),
+ Color(ColorTransparency, 200, 90, 170, 80),
+ };
+
+ BitmapPalette aPalette(256);
+ for (size_t i = 0; i < aColors.size(); ++i)
+ aPalette[i] = aColors[i];
+
+ std::unique_ptr<vcl::bitmap::ScanlineTransformer> pScanlineTransformer
+ = vcl::bitmap::getScanlineTransformer(8, aPalette);
+
+ std::vector<sal_uInt8> aScanLine(5, 0); // 5 * 1 BytesPerPixel
+ pScanlineTransformer->startLine(aScanLine.data());
+
+ for (Color const& aColor : aColors)
+ {
+ pScanlineTransformer->writePixel(aColor);
+ }
+
+ std::vector<sal_uInt8> aExpectedBytes{ 0, 1, 2, 3, 4 };
+
+ for (size_t i = 0; i < aScanLine.size(); ++i)
+ {
+ CPPUNIT_ASSERT_EQUAL(int(aExpectedBytes[i]), int(aScanLine[i]));
+ }
+
+ pScanlineTransformer->startLine(aScanLine.data());
+
+ for (Color const& rColor : aColors)
+ {
+ Color aColor = pScanlineTransformer->readPixel();
+ CPPUNIT_ASSERT_EQUAL(rColor, aColor);
+ }
+}
+
+void ScanlineToolsTest::ScanlineTransformer_4bit_Palette()
+{
+ std::vector<Color> aColors{
+ Color(10, 250, 120), Color(30, 230, 110), Color(50, 210, 100),
+ Color(70, 190, 90), Color(90, 170, 80), Color(110, 150, 70),
+ };
+
+ BitmapPalette aPalette(16);
+ for (size_t i = 0; i < aColors.size(); ++i)
+ {
+ aPalette[i] = aColors[i];
+ }
+
+ std::unique_ptr<vcl::bitmap::ScanlineTransformer> pScanlineTransformer
+ = vcl::bitmap::getScanlineTransformer(4, aPalette);
+
+ std::vector<sal_uInt8> aScanLine(3, 0); // 6 * 0.5 BytesPerPixel
+ pScanlineTransformer->startLine(aScanLine.data());
+
+ for (Color const& aColor : aColors)
+ {
+ pScanlineTransformer->writePixel(aColor);
+ }
+
+ std::vector<sal_uInt8> aExpectedBytes{ 0x01, 0x23, 0x45 };
+
+ for (size_t i = 0; i < aScanLine.size(); ++i)
+ {
+ CPPUNIT_ASSERT_EQUAL(int(aExpectedBytes[i]), int(aScanLine[i]));
+ }
+
+ pScanlineTransformer->startLine(aScanLine.data());
+
+ for (Color const& rColor : aColors)
+ {
+ Color aColor = pScanlineTransformer->readPixel();
+ CPPUNIT_ASSERT_EQUAL(rColor, aColor);
+ }
+}
+
+void ScanlineToolsTest::ScanlineTransformer_1bit_Palette()
+{
+ std::vector<Color> aColors{
+ Color(10, 250, 120), Color(30, 230, 110), Color(50, 210, 100), Color(70, 190, 90),
+ Color(90, 170, 80), Color(110, 150, 70), Color(130, 130, 60), Color(150, 110, 50),
+ Color(170, 90, 40), Color(190, 70, 30), Color(210, 50, 20), Color(230, 30, 10),
+ Color(250, 10, 0),
+ };
+
+ BitmapPalette aPalette(2);
+ aPalette[0] = Color(10, 250, 120);
+ aPalette[1] = Color(110, 150, 70);
+
+ std::unique_ptr<vcl::bitmap::ScanlineTransformer> pScanlineTransformer
+ = vcl::bitmap::getScanlineTransformer(1, aPalette);
+
+ std::vector<sal_uInt8> aScanLine(2, 0); // 13 * 1/8 BytesPerPixel
+ pScanlineTransformer->startLine(aScanLine.data());
+
+ for (Color const& aColor : aColors)
+ {
+ pScanlineTransformer->writePixel(aColor);
+ }
+
+ std::vector<sal_uInt8> aExpectedBytes{
+ // We expect 3x index 0 and 10x index 1 => 000 111111111
+ 0x1f, // 0001 1111
+ 0xf8 // 1111 1000
+ };
+
+ for (size_t i = 0; i < aScanLine.size(); ++i)
+ {
+ CPPUNIT_ASSERT_EQUAL(int(aExpectedBytes[i]), int(aScanLine[i]));
+ }
+
+ pScanlineTransformer->startLine(aScanLine.data());
+
+ std::vector<Color> aColorsExpected{
+ Color(10, 250, 120), Color(10, 250, 120), Color(10, 250, 120), Color(110, 150, 70),
+ Color(110, 150, 70), Color(110, 150, 70), Color(110, 150, 70), Color(110, 150, 70),
+ Color(110, 150, 70), Color(110, 150, 70), Color(110, 150, 70), Color(110, 150, 70),
+ Color(110, 150, 70),
+ };
+
+ for (Color const& rColor : aColorsExpected)
+ {
+ Color aColor = pScanlineTransformer->readPixel();
+ CPPUNIT_ASSERT_EQUAL(rColor, aColor);
+ }
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ScanlineToolsTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/TypeSerializerTest.cxx b/vcl/qa/cppunit/TypeSerializerTest.cxx
new file mode 100644
index 0000000000..b27f1c3c82
--- /dev/null
+++ b/vcl/qa/cppunit/TypeSerializerTest.cxx
@@ -0,0 +1,394 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <config_oox.h>
+#include <config_features.h>
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+
+#include <unotest/directories.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/gdimtf.hxx>
+#include <comphelper/hash.hxx>
+#include <tools/vcompat.hxx>
+#include <comphelper/fileformat.h>
+#include <tools/fract.hxx>
+
+#include <vcl/TypeSerializer.hxx>
+
+#if USE_TLS_NSS
+#include <nss.h>
+#endif
+
+namespace
+{
+constexpr OUString DATA_DIRECTORY = u"/vcl/qa/cppunit/data/"_ustr;
+
+std::vector<unsigned char> calculateHash(SvStream& rStream)
+{
+ rStream.Seek(STREAM_SEEK_TO_BEGIN);
+ comphelper::Hash aHashEngine(comphelper::HashType::SHA1);
+ const sal_uInt32 nSize(rStream.remainingSize());
+ std::vector<sal_uInt8> aData(nSize);
+ aHashEngine.update(aData.data(), nSize);
+ return aHashEngine.finalize();
+}
+
+std::string toHexString(const std::vector<unsigned char>& a)
+{
+ std::stringstream aStrm;
+ for (auto& i : a)
+ {
+ aStrm << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(i);
+ }
+
+ return aStrm.str();
+}
+
+class TypeSerializerTest : public CppUnit::TestFixture
+{
+public:
+ ~TypeSerializerTest();
+
+private:
+ void testGradient();
+ void testGraphic_Vector();
+ void testGraphic_Bitmap_NoGfxLink();
+ void testGraphic_Animation();
+ void testGraphic_GDIMetaFile();
+ void testMapMode();
+
+ CPPUNIT_TEST_SUITE(TypeSerializerTest);
+ CPPUNIT_TEST(testGradient);
+ CPPUNIT_TEST(testGraphic_Vector);
+ CPPUNIT_TEST(testGraphic_Bitmap_NoGfxLink);
+ CPPUNIT_TEST(testGraphic_Animation);
+ CPPUNIT_TEST(testGraphic_GDIMetaFile);
+ CPPUNIT_TEST(testMapMode);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+TypeSerializerTest::~TypeSerializerTest()
+{
+#if USE_TLS_NSS
+ NSS_Shutdown();
+#endif
+}
+
+void TypeSerializerTest::testGradient()
+{
+ Gradient aGradient(css::awt::GradientStyle_RADIAL, Color(0xFF, 0x00, 0x00),
+ Color(0x00, 0xFF, 0x00));
+ aGradient.SetAngle(900_deg10);
+ aGradient.SetBorder(5);
+ aGradient.SetOfsX(11);
+ aGradient.SetOfsY(12);
+ aGradient.SetStartIntensity(21);
+ aGradient.SetEndIntensity(22);
+ aGradient.SetSteps(30);
+
+ SvMemoryStream aStream;
+ TypeSerializer aSerializer(aStream);
+ aSerializer.writeGradient(aGradient);
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ Gradient aReadGradient;
+ aSerializer.readGradient(aReadGradient);
+ CPPUNIT_ASSERT_EQUAL(css::awt::GradientStyle_RADIAL, aReadGradient.GetStyle());
+ CPPUNIT_ASSERT_EQUAL(Color(0xFF, 0x00, 0x00), aReadGradient.GetStartColor());
+ CPPUNIT_ASSERT_EQUAL(Color(0x00, 0xFF, 0x00), aReadGradient.GetEndColor());
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(900), aReadGradient.GetAngle().get());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(5), aReadGradient.GetBorder());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(11), aReadGradient.GetOfsX());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(12), aReadGradient.GetOfsY());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(21), aReadGradient.GetStartIntensity());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(22), aReadGradient.GetEndIntensity());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(30), aReadGradient.GetSteps());
+}
+
+void TypeSerializerTest::testGraphic_Vector()
+{
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(DATA_DIRECTORY) + "SimpleExample.svg";
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ aGraphic.makeAvailable();
+ BitmapChecksum aChecksum = aGraphic.getVectorGraphicData()->GetChecksum();
+
+ // Test TypeSerializer - Native Format 5
+ {
+ SvMemoryStream aMemoryStream;
+ aMemoryStream.SetVersion(SOFFICE_FILEFORMAT_50);
+ aMemoryStream.SetCompressMode(SvStreamCompressFlags::NATIVE);
+ {
+ TypeSerializer aSerializer(aMemoryStream);
+ aSerializer.writeGraphic(aGraphic);
+ }
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(290), aMemoryStream.remainingSize());
+ std::vector<unsigned char> aHash = calculateHash(aMemoryStream);
+ CPPUNIT_ASSERT_EQUAL(std::string("ee55ab6faa73b61b68bc3d5628d95f0d3c528e2a"),
+ toHexString(aHash));
+
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ sal_uInt32 nType;
+ aMemoryStream.ReadUInt32(nType);
+ CPPUNIT_ASSERT_EQUAL(COMPAT_FORMAT('N', 'A', 'T', '5'), nType);
+
+ // Read it back
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ Graphic aNewGraphic;
+ {
+ TypeSerializer aSerializer(aMemoryStream);
+ aSerializer.readGraphic(aNewGraphic);
+ }
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aNewGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(aChecksum, aNewGraphic.getVectorGraphicData()->GetChecksum());
+ }
+
+ // Test TypeSerializer - Normal
+ {
+ SvMemoryStream aMemoryStream;
+ {
+ TypeSerializer aSerializer(aMemoryStream);
+ aSerializer.writeGraphic(aGraphic);
+ }
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(233), aMemoryStream.remainingSize());
+ std::vector<unsigned char> aHash = calculateHash(aMemoryStream);
+ CPPUNIT_ASSERT_EQUAL(std::string("c2bed2099ce617f1cc035701de5186f0d43e3064"),
+ toHexString(aHash));
+
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ sal_uInt32 nType;
+ aMemoryStream.ReadUInt32(nType);
+ CPPUNIT_ASSERT_EQUAL(createMagic('s', 'v', 'g', '0'), nType);
+
+ // Read it back
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ Graphic aNewGraphic;
+ {
+ TypeSerializer aSerializer(aMemoryStream);
+ aSerializer.readGraphic(aNewGraphic);
+ }
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aNewGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(aChecksum, aNewGraphic.getVectorGraphicData()->GetChecksum());
+ }
+}
+
+void TypeSerializerTest::testGraphic_Bitmap_NoGfxLink()
+{
+ Bitmap aBitmap(Size(10, 10), vcl::PixelFormat::N24_BPP);
+ aBitmap.Erase(COL_LIGHTGRAYBLUE);
+ BitmapEx aBitmapEx(aBitmap);
+ Graphic aGraphic(aBitmapEx);
+
+ // Test TypeSerializer
+ {
+ SvMemoryStream aMemoryStream;
+ {
+ TypeSerializer aSerializer(aMemoryStream);
+ aSerializer.writeGraphic(aGraphic);
+ }
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(383), aMemoryStream.remainingSize());
+ std::vector<unsigned char> aHash = calculateHash(aMemoryStream);
+ CPPUNIT_ASSERT_EQUAL(std::string("da831418499146d51bf245fadf60b9111faa76c2"),
+ toHexString(aHash));
+
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ sal_uInt16 nType;
+ aMemoryStream.ReadUInt16(nType);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(0x4D42), nType); // Magic written with WriteDIBBitmapEx
+
+ // Read it back
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ Graphic aNewGraphic;
+ {
+ TypeSerializer aSerializer(aMemoryStream);
+ aSerializer.readGraphic(aNewGraphic);
+ }
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aNewGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(aBitmapEx.GetChecksum(), aNewGraphic.GetBitmapExRef().GetChecksum());
+ }
+}
+
+void TypeSerializerTest::testGraphic_Animation()
+{
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(DATA_DIRECTORY) + "123_Numbers.gif";
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ aGraphic.makeAvailable();
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(true, aGraphic.IsAnimated());
+
+ // Test TypeSerializer
+ {
+ SvMemoryStream aMemoryStream;
+ {
+ TypeSerializer aSerializer(aMemoryStream);
+ aSerializer.writeGraphic(aGraphic);
+ }
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(15123), aMemoryStream.remainingSize());
+ std::vector<unsigned char> aHash = calculateHash(aMemoryStream);
+ CPPUNIT_ASSERT_EQUAL(std::string("86e02d37ab5e9c96fbeda717f62bc6e35dec3a70"),
+ toHexString(aHash));
+
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ sal_uInt16 nType;
+ aMemoryStream.ReadUInt16(nType);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(0x4D42), nType);
+
+ // Read it back
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ Graphic aNewGraphic;
+ {
+ TypeSerializer aSerializer(aMemoryStream);
+ aSerializer.readGraphic(aNewGraphic);
+ }
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aNewGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(true, aNewGraphic.IsAnimated());
+ }
+
+ // Test TypeSerializer - Native Format 5
+ {
+ SvMemoryStream aMemoryStream;
+ aMemoryStream.SetVersion(SOFFICE_FILEFORMAT_50);
+ aMemoryStream.SetCompressMode(SvStreamCompressFlags::NATIVE);
+ {
+ TypeSerializer aSerializer(aMemoryStream);
+ aSerializer.writeGraphic(aGraphic);
+ }
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(1582), aMemoryStream.remainingSize());
+ std::vector<unsigned char> aHash = calculateHash(aMemoryStream);
+ CPPUNIT_ASSERT_EQUAL(std::string("da3b9600340fa80a895f2107357e4ab65a9292eb"),
+ toHexString(aHash));
+
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ sal_uInt32 nType;
+ aMemoryStream.ReadUInt32(nType);
+ CPPUNIT_ASSERT_EQUAL(COMPAT_FORMAT('N', 'A', 'T', '5'), nType);
+
+ // Read it back
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ Graphic aNewGraphic;
+ {
+ TypeSerializer aSerializer(aMemoryStream);
+ aSerializer.readGraphic(aNewGraphic);
+ }
+ CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aNewGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL(true, aNewGraphic.IsAnimated());
+ }
+}
+
+void TypeSerializerTest::testGraphic_GDIMetaFile()
+{
+ GDIMetaFile aGDIMetaFile;
+ {
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ pVirtualDev->SetConnectMetaFile(&aGDIMetaFile);
+ Size aVDSize(10, 10);
+ pVirtualDev->SetOutputSizePixel(aVDSize);
+ pVirtualDev->SetBackground(Wallpaper(COL_LIGHTRED));
+ pVirtualDev->Erase();
+ pVirtualDev->DrawPixel(Point(4, 4));
+ }
+ Graphic aGraphic(aGDIMetaFile);
+ CPPUNIT_ASSERT_EQUAL(GraphicType::GdiMetafile, aGraphic.GetType());
+
+ // Test TypeSerializer
+ {
+ SvMemoryStream aMemoryStream;
+ {
+ TypeSerializer aSerializer(aMemoryStream);
+ aSerializer.writeGraphic(aGraphic);
+ }
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(229), aMemoryStream.remainingSize());
+ std::vector<unsigned char> aHash = calculateHash(aMemoryStream);
+ CPPUNIT_ASSERT_EQUAL(std::string("144c518e5149d61ab4bc34643df820372405d61d"),
+ toHexString(aHash));
+
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ char aIdCharArray[7] = { 0, 0, 0, 0, 0, 0, 0 };
+ aMemoryStream.ReadBytes(aIdCharArray, 6);
+ OString sID(aIdCharArray);
+ CPPUNIT_ASSERT_EQUAL("VCLMTF"_ostr, sID);
+
+ // Read it back
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ Graphic aNewGraphic;
+ {
+ TypeSerializer aSerializer(aMemoryStream);
+ aSerializer.readGraphic(aNewGraphic);
+ }
+ CPPUNIT_ASSERT_EQUAL(GraphicType::GdiMetafile, aNewGraphic.GetType());
+ }
+}
+
+void TypeSerializerTest::testMapMode()
+{
+ { // "simple" case - only map unit is set, IsSimple = true
+ MapMode aMapMode(MapUnit::Map100thMM);
+
+ SvMemoryStream aStream;
+ TypeSerializer aSerializer(aStream);
+ aSerializer.writeMapMode(aMapMode);
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ MapMode aReadMapMode;
+ aSerializer.readMapMode(aReadMapMode);
+ CPPUNIT_ASSERT_EQUAL(MapUnit::Map100thMM, aReadMapMode.GetMapUnit());
+ CPPUNIT_ASSERT_EQUAL(true, aReadMapMode.IsSimple());
+ }
+ { // "complex" case - map unit, origin and scale are set, IsSimple = false
+ MapMode aMapMode(MapUnit::MapTwip, Point(5, 10), Fraction(1, 2), Fraction(2, 3));
+
+ SvMemoryStream aStream;
+ TypeSerializer aSerializer(aStream);
+ aSerializer.writeMapMode(aMapMode);
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ MapMode aReadMapMode;
+ aSerializer.readMapMode(aReadMapMode);
+ CPPUNIT_ASSERT_EQUAL(MapUnit::MapTwip, aReadMapMode.GetMapUnit());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(5), aReadMapMode.GetOrigin().X());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aReadMapMode.GetOrigin().Y());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aReadMapMode.GetScaleX().GetNumerator());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aReadMapMode.GetScaleX().GetDenominator());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aReadMapMode.GetScaleY().GetNumerator());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(3), aReadMapMode.GetScaleY().GetDenominator());
+ CPPUNIT_ASSERT_EQUAL(false, aReadMapMode.IsSimple());
+ }
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TypeSerializerTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/VectorGraphicSearchTest.cxx b/vcl/qa/cppunit/VectorGraphicSearchTest.cxx
new file mode 100644
index 0000000000..59ce0fed5e
--- /dev/null
+++ b/vcl/qa/cppunit/VectorGraphicSearchTest.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/.
+ */
+
+#include <config_features.h>
+
+#include <string_view>
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <unotest/bootstrapfixturebase.hxx>
+#include <unotest/directories.hxx>
+
+#include <vcl/VectorGraphicSearch.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <tools/stream.hxx>
+
+class VectorGraphicSearchTest : public test::BootstrapFixtureBase
+{
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(u"/vcl/qa/cppunit/data/") + sFileName;
+ }
+
+ void test();
+ void testNextPrevious();
+ void testSearchStringChange();
+ void testSearchMatchWholeWord();
+ void testSearchMatchCase();
+
+ CPPUNIT_TEST_SUITE(VectorGraphicSearchTest);
+ CPPUNIT_TEST(test);
+ CPPUNIT_TEST(testNextPrevious);
+ CPPUNIT_TEST(testSearchStringChange);
+ CPPUNIT_TEST(testSearchMatchWholeWord);
+ CPPUNIT_TEST(testSearchMatchCase);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void VectorGraphicSearchTest::test()
+{
+ OUString aURL = getFullUrl(u"Pangram.pdf");
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ aGraphic.makeAvailable();
+
+ VectorGraphicSearch aSearch(aGraphic);
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.search("lazy"));
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(34, aSearch.index());
+
+ basegfx::B2DSize aSize = aSearch.pageSize();
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(21590.00, aSize.getWidth(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(27940.00, aSize.getHeight(), 1E-2);
+
+ auto aRectangles = aSearch.getTextRectangles();
+ CPPUNIT_ASSERT_EQUAL(size_t(4), aRectangles.size());
+
+ // Check first and last
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(8078.61, aRectangles[0].getMinX(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(8179.36, aRectangles[0].getMaxX(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(2101.56, aRectangles[0].getMinY(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(2395.36, aRectangles[0].getMaxY(), 1E-2);
+
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(8565.86, aRectangles[3].getMinX(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(8770.76, aRectangles[3].getMaxX(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(2201.05, aRectangles[3].getMinY(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(2486.37, aRectangles[3].getMaxY(), 1E-2);
+
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(817, aSearch.index());
+
+ aRectangles = aSearch.getTextRectangles();
+ CPPUNIT_ASSERT_EQUAL(size_t(4), aRectangles.size());
+
+ // Check first and last
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(6562.23, aRectangles[0].getMinX(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(6662.98, aRectangles[0].getMaxX(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(5996.23, aRectangles[0].getMinY(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(6290.02, aRectangles[0].getMaxY(), 1E-2);
+
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(7049.48, aRectangles[3].getMinX(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(7254.38, aRectangles[3].getMaxX(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(6095.71, aRectangles[3].getMinY(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(6381.04, aRectangles[3].getMaxY(), 1E-2);
+}
+
+// Test next and previous work as expected to move
+// between search matches.
+void VectorGraphicSearchTest::testNextPrevious()
+{
+ OUString aURL = getFullUrl(u"Pangram.pdf");
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ aGraphic.makeAvailable();
+
+ { // Start from the beginning of the page
+ VectorGraphicSearch aSearch(aGraphic);
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.search("lazy"));
+
+ // no previous - we are at the begin
+ CPPUNIT_ASSERT_EQUAL(false, aSearch.previous());
+ CPPUNIT_ASSERT_EQUAL(0, aSearch.index()); // nothing was yet found, so it is 0
+
+ // next - first position found
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(34, aSearch.index());
+
+ // next - second position found
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(817, aSearch.index());
+
+ // next - not found, index unchanged
+ CPPUNIT_ASSERT_EQUAL(false, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(817, aSearch.index());
+
+ // previous - first position
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.previous());
+ CPPUNIT_ASSERT_EQUAL(34, aSearch.index());
+
+ // previous - not found, index unchanged
+ CPPUNIT_ASSERT_EQUAL(false, aSearch.previous());
+ CPPUNIT_ASSERT_EQUAL(34, aSearch.index());
+
+ // next - second position found
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(817, aSearch.index());
+ }
+
+ { // Start from the end of the page
+ VectorGraphicSearch aSearch(aGraphic);
+ CPPUNIT_ASSERT_EQUAL(true,
+ aSearch.search("lazy", { SearchStartPosition::End, false, false }));
+
+ // no next - we are at the end
+ CPPUNIT_ASSERT_EQUAL(false, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(0, aSearch.index()); // nothing was yet found, so it is 0
+
+ // previous - second position found
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.previous());
+ CPPUNIT_ASSERT_EQUAL(817, aSearch.index());
+
+ // previous - first position found
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.previous());
+ CPPUNIT_ASSERT_EQUAL(34, aSearch.index());
+
+ // previous - not found, index unchanged
+ CPPUNIT_ASSERT_EQUAL(false, aSearch.previous());
+ CPPUNIT_ASSERT_EQUAL(34, aSearch.index());
+
+ // next - second position
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(817, aSearch.index());
+
+ // next - not found, index unchanged
+ CPPUNIT_ASSERT_EQUAL(false, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(817, aSearch.index());
+
+ // previous - first match found
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.previous());
+ CPPUNIT_ASSERT_EQUAL(34, aSearch.index());
+ }
+}
+
+void VectorGraphicSearchTest::testSearchStringChange()
+{
+ OUString aURL = getFullUrl(u"Pangram.pdf");
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ aGraphic.makeAvailable();
+
+ VectorGraphicSearch aSearch(aGraphic);
+
+ // Set search to "lazy"
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.search("lazy"));
+
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(34, aSearch.index());
+
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(817, aSearch.index());
+
+ // Change search to "fox"
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.search("fox"));
+
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(822, aSearch.index());
+
+ // Change search to "Quick"
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.search("Quick"));
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.previous());
+ CPPUNIT_ASSERT_EQUAL(784, aSearch.index());
+}
+
+void VectorGraphicSearchTest::testSearchMatchWholeWord()
+{
+ OUString aURL = getFullUrl(u"Pangram.pdf");
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ aGraphic.makeAvailable();
+
+ {
+ VectorGraphicSearch aSearch(aGraphic);
+ // Search, whole word disabled - "Flummoxed" - found
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.search("Flummoxed"));
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(618, aSearch.index());
+ }
+ {
+ VectorGraphicSearch aSearch(aGraphic);
+ // Search, whole word disabled - "Flummo" - found
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.search("Flummo"));
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(618, aSearch.index());
+ }
+ {
+ VectorGraphicSearch aSearch(aGraphic);
+ // Search, whole word enabled - "Flummoxed" - found
+ CPPUNIT_ASSERT_EQUAL(
+ true, aSearch.search("Flummoxed", { SearchStartPosition::Begin, false, true }));
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(618, aSearch.index());
+ }
+ {
+ VectorGraphicSearch aSearch(aGraphic);
+ // Search, whole word enabled - "Flummo" - not found
+ CPPUNIT_ASSERT_EQUAL(true,
+ aSearch.search("Flummo", { SearchStartPosition::Begin, false, true }));
+ CPPUNIT_ASSERT_EQUAL(false, aSearch.next());
+ }
+}
+
+void VectorGraphicSearchTest::testSearchMatchCase()
+{
+ OUString aURL = getFullUrl(u"Pangram.pdf");
+ SvFileStream aStream(aURL, StreamMode::READ);
+ GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(aStream);
+ aGraphic.makeAvailable();
+
+ {
+ VectorGraphicSearch aSearch(aGraphic);
+ // Search "Flummoxed" - case insensitive - found
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.search("Flummoxed"));
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(618, aSearch.index());
+ }
+
+ {
+ VectorGraphicSearch aSearch(aGraphic);
+ // Search "FLUMMOXED" - case insensitive - found
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.search("FLUMMOXED"));
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(618, aSearch.index());
+ }
+
+ {
+ VectorGraphicSearch aSearch(aGraphic);
+ // Search "Flummoxed" - case sensitive - found
+ CPPUNIT_ASSERT_EQUAL(
+ true, aSearch.search("Flummoxed", { SearchStartPosition::Begin, true, false }));
+ CPPUNIT_ASSERT_EQUAL(true, aSearch.next());
+ CPPUNIT_ASSERT_EQUAL(618, aSearch.index());
+ }
+
+ {
+ VectorGraphicSearch aSearch(aGraphic);
+ // Search to "FLUMMOXED" - case sensitive - not found
+ CPPUNIT_ASSERT_EQUAL(
+ true, aSearch.search("FLUMMOXED", { SearchStartPosition::Begin, true, false }));
+ CPPUNIT_ASSERT_EQUAL(false, aSearch.next());
+ }
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VectorGraphicSearchTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/XpmFilterTest.cxx b/vcl/qa/cppunit/XpmFilterTest.cxx
new file mode 100644
index 0000000000..9dff029668
--- /dev/null
+++ b/vcl/qa/cppunit/XpmFilterTest.cxx
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+
+#include <vcl/bitmapex.hxx>
+#include <tools/stream.hxx>
+#include <filter/XpmReader.hxx>
+#include <unotools/tempfile.hxx>
+
+class XpmFilterTest : public test::BootstrapFixture
+{
+public:
+ OUString maDataUrl;
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(maDataUrl) + sFileName;
+ }
+ XpmFilterTest()
+ : maDataUrl(u"/vcl/qa/cppunit/data/"_ustr)
+ {
+ }
+};
+
+CPPUNIT_TEST_FIXTURE(XpmFilterTest, testXPM_8bit)
+{
+ SvFileStream aFileStream(getFullUrl(u"XPM_8.xpm"), StreamMode::READ);
+ Graphic aGraphic;
+ CPPUNIT_ASSERT(ImportXPM(aFileStream, aGraphic));
+ auto aBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat());
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, aBitmap.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aBitmap.GetPixelColor(2, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, aBitmap.GetPixelColor(0, 2));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTGREEN, aBitmap.GetPixelColor(2, 2));
+}
+
+CPPUNIT_TEST_FIXTURE(XpmFilterTest, testXPM_4bit)
+{
+ SvFileStream aFileStream(getFullUrl(u"XPM_4.xpm"), StreamMode::READ);
+ Graphic aGraphic;
+ CPPUNIT_ASSERT(ImportXPM(aFileStream, aGraphic));
+ auto aBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat());
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, aBitmap.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aBitmap.GetPixelColor(2, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, aBitmap.GetPixelColor(0, 2));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTGREEN, aBitmap.GetPixelColor(2, 2));
+}
+
+CPPUNIT_TEST_FIXTURE(XpmFilterTest, testXPM_1bit)
+{
+ SvFileStream aFileStream(getFullUrl(u"XPM_1.xpm"), StreamMode::READ);
+ Graphic aGraphic;
+ CPPUNIT_ASSERT(ImportXPM(aFileStream, aGraphic));
+ auto aBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat());
+ CPPUNIT_ASSERT_EQUAL(Color(0xffffff), aBitmap.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(Color(0x72d1c8), aBitmap.GetPixelColor(1, 1));
+ CPPUNIT_ASSERT_EQUAL(Color(0x72d1c8), aBitmap.GetPixelColor(8, 8));
+ CPPUNIT_ASSERT_EQUAL(Color(0xffffff), aBitmap.GetPixelColor(9, 9));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/a11y/atspi2/atspi2.cxx b/vcl/qa/cppunit/a11y/atspi2/atspi2.cxx
new file mode 100644
index 0000000000..04d885d623
--- /dev/null
+++ b/vcl/qa/cppunit/a11y/atspi2/atspi2.cxx
@@ -0,0 +1,503 @@
+/* -*- 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 "atspi2.hxx"
+
+#include <com/sun/star/accessibility/AccessibleRelationType.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/XAccessibleExtendedAttributes.hpp>
+
+#include <comphelper/propertyvalue.hxx>
+#include <o3tl/string_view.hxx>
+#include <sfx2/zoomitem.hxx>
+#include <unotest/macros_test.hxx>
+
+#include <test/a11y/AccessibilityTools.hxx>
+
+#include "atspiwrapper.hxx"
+
+using namespace css;
+
+// from gtk3/a11y/atkwrapper.cxx
+static AtspiRole mapToAtspiRole(sal_Int16 nRole, sal_Int64 nStates)
+{
+ switch (nRole)
+ {
+#define MAP(lo, atspi) \
+ case accessibility::AccessibleRole::lo: \
+ return ATSPI_ROLE_##atspi
+#define MAP_DIRECT(a) MAP(a, a)
+
+ MAP_DIRECT(UNKNOWN);
+ MAP_DIRECT(ALERT);
+ MAP_DIRECT(BLOCK_QUOTE);
+ MAP_DIRECT(COLUMN_HEADER);
+ MAP_DIRECT(CANVAS);
+ MAP_DIRECT(CHECK_BOX);
+ MAP_DIRECT(CHECK_MENU_ITEM);
+ MAP_DIRECT(COLOR_CHOOSER);
+ MAP_DIRECT(COMBO_BOX);
+ MAP_DIRECT(DATE_EDITOR);
+ MAP_DIRECT(DESKTOP_ICON);
+ MAP(DESKTOP_PANE, DESKTOP_FRAME);
+ MAP_DIRECT(DIRECTORY_PANE);
+ MAP_DIRECT(DIALOG);
+ MAP(DOCUMENT, DOCUMENT_FRAME);
+ MAP(EMBEDDED_OBJECT, EMBEDDED);
+ MAP(END_NOTE, FOOTNOTE);
+ MAP_DIRECT(FILE_CHOOSER);
+ MAP_DIRECT(FILLER);
+ MAP_DIRECT(FONT_CHOOSER);
+ MAP_DIRECT(FOOTER);
+ MAP_DIRECT(FOOTNOTE);
+ MAP_DIRECT(FRAME);
+ MAP_DIRECT(GLASS_PANE);
+ MAP(GRAPHIC, IMAGE);
+ MAP(GROUP_BOX, GROUPING);
+ MAP_DIRECT(HEADER);
+ MAP_DIRECT(HEADING);
+ MAP(HYPER_LINK, LINK);
+ MAP_DIRECT(ICON);
+ MAP_DIRECT(INTERNAL_FRAME);
+ MAP_DIRECT(LABEL);
+ MAP_DIRECT(LAYERED_PANE);
+ MAP_DIRECT(LIST);
+ MAP_DIRECT(LIST_ITEM);
+ MAP_DIRECT(MENU);
+ MAP_DIRECT(MENU_BAR);
+ MAP_DIRECT(MENU_ITEM);
+ MAP_DIRECT(OPTION_PANE);
+ MAP_DIRECT(PAGE_TAB);
+ MAP_DIRECT(PAGE_TAB_LIST);
+ MAP_DIRECT(PANEL);
+ MAP_DIRECT(PARAGRAPH);
+ MAP_DIRECT(PASSWORD_TEXT);
+ MAP_DIRECT(POPUP_MENU);
+ MAP_DIRECT(PUSH_BUTTON);
+ MAP_DIRECT(PROGRESS_BAR);
+ MAP_DIRECT(RADIO_BUTTON);
+ MAP_DIRECT(RADIO_MENU_ITEM);
+ MAP_DIRECT(ROW_HEADER);
+ MAP_DIRECT(ROOT_PANE);
+ MAP_DIRECT(SCROLL_BAR);
+ MAP_DIRECT(SCROLL_PANE);
+ MAP(SHAPE, PANEL);
+ MAP_DIRECT(SEPARATOR);
+ MAP_DIRECT(SLIDER);
+ MAP(SPIN_BOX, SPIN_BUTTON);
+ MAP_DIRECT(SPLIT_PANE);
+ MAP_DIRECT(STATUS_BAR);
+ MAP_DIRECT(TABLE);
+ MAP_DIRECT(TABLE_CELL);
+ MAP_DIRECT(TEXT);
+ MAP(TEXT_FRAME, PANEL);
+ MAP_DIRECT(TOGGLE_BUTTON);
+ MAP_DIRECT(TOOL_BAR);
+ MAP_DIRECT(TOOL_TIP);
+ MAP_DIRECT(TREE);
+ MAP(VIEW_PORT, VIEWPORT);
+ MAP_DIRECT(WINDOW);
+#if ATSPI_ROLE_COUNT > 130 /* ATSPI_ROLE_PUSH_BUTTON_MENU is 129 */
+ MAP(BUTTON_MENU, PUSH_BUTTON_MENU);
+#else
+ MAP(BUTTON_MENU, PUSH_BUTTON);
+#endif
+ MAP_DIRECT(CAPTION);
+ MAP_DIRECT(CHART);
+ MAP(EDIT_BAR, EDITBAR);
+ MAP_DIRECT(FORM);
+ MAP_DIRECT(IMAGE_MAP);
+ MAP(NOTE, COMMENT);
+ MAP_DIRECT(PAGE);
+ MAP_DIRECT(RULER);
+ MAP_DIRECT(SECTION);
+ MAP_DIRECT(TREE_ITEM);
+ MAP_DIRECT(TREE_TABLE);
+ MAP_DIRECT(COMMENT);
+ MAP(COMMENT_END, UNKNOWN);
+ MAP_DIRECT(DOCUMENT_PRESENTATION);
+ MAP_DIRECT(DOCUMENT_SPREADSHEET);
+ MAP_DIRECT(DOCUMENT_TEXT);
+ MAP_DIRECT(STATIC);
+ MAP_DIRECT(NOTIFICATION);
+
+#undef MAP_DIRECT
+#undef MAP
+ case css::accessibility::AccessibleRole::BUTTON_DROPDOWN:
+ if (nStates & css::accessibility::AccessibleStateType::CHECKABLE)
+ return ATSPI_ROLE_TOGGLE_BUTTON;
+ return ATSPI_ROLE_PUSH_BUTTON;
+ default:
+ SAL_WARN("vcl.gtk", "Unmapped accessible role: " << nRole);
+ return ATSPI_ROLE_UNKNOWN;
+ }
+}
+
+static AtspiStateType mapAtspiState(sal_Int64 nState)
+{
+ // A perfect / complete mapping ...
+ switch (nState)
+ {
+#define MAP(lo, atspi) \
+ case accessibility::AccessibleStateType::lo: \
+ return ATSPI_STATE_##atspi
+#define MAP_DIRECT(a) MAP(a, a)
+
+ MAP_DIRECT(INVALID);
+ MAP_DIRECT(ACTIVE);
+ MAP_DIRECT(ARMED);
+ MAP_DIRECT(BUSY);
+ MAP_DIRECT(CHECKABLE);
+ MAP_DIRECT(CHECKED);
+ MAP_DIRECT(EDITABLE);
+ MAP_DIRECT(ENABLED);
+ MAP_DIRECT(EXPANDABLE);
+ MAP_DIRECT(EXPANDED);
+ MAP_DIRECT(FOCUSABLE);
+ MAP_DIRECT(FOCUSED);
+ MAP_DIRECT(HORIZONTAL);
+ MAP_DIRECT(ICONIFIED);
+ MAP_DIRECT(INDETERMINATE);
+ MAP_DIRECT(MANAGES_DESCENDANTS);
+ MAP_DIRECT(MODAL);
+ MAP_DIRECT(MULTI_LINE);
+ MAP(MULTI_SELECTABLE, MULTISELECTABLE);
+ MAP_DIRECT(OPAQUE);
+ MAP_DIRECT(PRESSED);
+ MAP_DIRECT(RESIZABLE);
+ MAP_DIRECT(SELECTABLE);
+ MAP_DIRECT(SELECTED);
+ MAP_DIRECT(SENSITIVE);
+ MAP_DIRECT(SHOWING);
+ MAP_DIRECT(SINGLE_LINE);
+ MAP_DIRECT(STALE);
+ MAP_DIRECT(TRANSIENT);
+ MAP_DIRECT(VERTICAL);
+ MAP_DIRECT(VISIBLE);
+ MAP(DEFAULT, IS_DEFAULT);
+ // a spelling error ...
+ MAP(DEFUNC, DEFUNCT);
+
+#undef MAP_DIRECT
+#undef MAP
+
+ default:
+ //Mis-use ATK_STATE_LAST_DEFINED to check if a state is unmapped
+ //NOTE! Do not report it
+ return ATSPI_STATE_LAST_DEFINED;
+ }
+}
+
+static AtspiRelationType mapRelationType(sal_Int16 nRelation)
+{
+ switch (nRelation)
+ {
+ case accessibility::AccessibleRelationType::CONTENT_FLOWS_FROM:
+ return ATSPI_RELATION_FLOWS_FROM;
+ case accessibility::AccessibleRelationType::CONTENT_FLOWS_TO:
+ return ATSPI_RELATION_FLOWS_TO;
+ case accessibility::AccessibleRelationType::CONTROLLED_BY:
+ return ATSPI_RELATION_CONTROLLED_BY;
+ case accessibility::AccessibleRelationType::CONTROLLER_FOR:
+ return ATSPI_RELATION_CONTROLLER_FOR;
+ case accessibility::AccessibleRelationType::LABEL_FOR:
+ return ATSPI_RELATION_LABEL_FOR;
+ case accessibility::AccessibleRelationType::LABELED_BY:
+ return ATSPI_RELATION_LABELLED_BY;
+ case accessibility::AccessibleRelationType::MEMBER_OF:
+ return ATSPI_RELATION_MEMBER_OF;
+ case accessibility::AccessibleRelationType::SUB_WINDOW_OF:
+ return ATSPI_RELATION_SUBWINDOW_OF;
+ case accessibility::AccessibleRelationType::NODE_CHILD_OF:
+ return ATSPI_RELATION_NODE_CHILD_OF;
+ }
+
+ return ATSPI_RELATION_NULL;
+}
+
+static std::string debugString(const Atspi::Accessible& pAtspiAccessible)
+{
+ CPPUNIT_NS::OStringStream ost;
+
+ ost << "(" << static_cast<const void*>(pAtspiAccessible.get()) << ")";
+ if (pAtspiAccessible)
+ {
+ ost << " role=\"" << pAtspiAccessible.getRoleName() << '"';
+ ost << " name=\"" << pAtspiAccessible.getName() << '"';
+ ost << " description=\"" << pAtspiAccessible.getDescription() << '"';
+ }
+
+ return ost.str();
+}
+
+static void dumpAtspiTree(const Atspi::Accessible& pAcc, const int depth = 0)
+{
+ std::cout << debugString(pAcc) << std::endl;
+
+ sal_Int32 i = 0;
+ for (const auto& pChild : pAcc)
+ {
+ for (auto j = decltype(depth){ 0 }; j < depth; j++)
+ std::cout << " ";
+ std::cout << " * child " << i++ << ": ";
+ dumpAtspiTree(pChild, depth + 1);
+ }
+}
+
+void Atspi2TestTree::compareObjects(const uno::Reference<accessibility::XAccessible>& xLOAccessible,
+ const Atspi::Accessible& pAtspiAccessible,
+ const sal_uInt16 recurseFlags)
+{
+ if (recurseFlags != RecurseFlags::NONE)
+ std::cout << "checking " << debugString(pAtspiAccessible) << " against "
+ << AccessibilityTools::debugString(xLOAccessible) << std::endl;
+
+ CPPUNIT_ASSERT(xLOAccessible);
+ CPPUNIT_ASSERT(pAtspiAccessible);
+
+ auto xLOContext = xLOAccessible->getAccessibleContext();
+
+ /* role: we translate to ATSPI role, because the value was created by LO already and converted
+ * to ATK, which in turn converts it to ATSPI. However, ATK and ATSPI are roughly equivalent
+ * (ATK basically follows ATSPI), but LO's internal might have more complex mappings that can't
+ * be represented with a round trip. */
+ const AtspiRole nLORole
+ = mapToAtspiRole(xLOContext->getAccessibleRole(), xLOContext->getAccessibleStateSet());
+ const auto nAtspiRole = pAtspiAccessible.getRole();
+ CPPUNIT_ASSERT_EQUAL(nLORole, nAtspiRole);
+ /* name (no need to worry about debugging suffixes as AccessibilityTools::nameEquals does, as
+ * that will also be part of the name sent to ATSPI) */
+ CPPUNIT_ASSERT_EQUAL(xLOContext->getAccessibleName(),
+ OUString::fromUtf8(pAtspiAccessible.getName()));
+ // description
+ CPPUNIT_ASSERT_EQUAL(xLOContext->getAccessibleDescription(),
+ OUString::fromUtf8(pAtspiAccessible.getDescription()));
+
+ // parent relationship (this is conditional as the ATSPI tree has additional parents, as well as
+ // because we don't want to recurse up the tree)
+ if (recurseFlags & RecurseFlags::PARENT)
+ {
+ // index in parent
+ CPPUNIT_ASSERT_EQUAL(xLOContext->getAccessibleIndexInParent(),
+ sal_Int64(pAtspiAccessible.getIndexInParent()));
+
+ // parent (well, that's making things a lot more expensive...)
+ compareObjects(xLOContext->getAccessibleParent(), pAtspiAccessible.getParent(),
+ RecurseFlags::NONE);
+ }
+
+ // state set
+ const auto loStateSet = xLOContext->getAccessibleStateSet();
+ const auto atspiStateSet = pAtspiAccessible.getStateSet();
+ const auto nBits
+ = (sizeof(decltype(loStateSet)) * 8) - (std::is_signed_v<decltype(loStateSet)> ? 1 : 0);
+ for (auto shift = decltype(nBits){ 0 }; shift < nBits; shift++)
+ {
+ const auto loState = decltype(loStateSet){ 1 } << shift;
+ const auto atspiState = mapAtspiState(loState);
+
+ // ignore a state that does not map to Atspi
+ if (atspiState == ATSPI_STATE_LAST_DEFINED)
+ continue;
+
+ /* FIXME: The ATK implementation in LO adds FOCUSED if the obj == atk_get_focus_object()
+ * (see atkwrapper.cxx::wrapper_ref_state_set()), but there seem to be some bug (or delay?
+ * as it's done in idle) in the tracking, so we can end up with extra FOCUSED states on the
+ * Atspi side. To work around that, we skip the case where it's not set on LO's side */
+ if (atspiState == ATSPI_STATE_FOCUSED && !(loStateSet & loState))
+ continue;
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Unmatched state: " + Atspi::State::getName(atspiState),
+ (loStateSet & loState) != 0,
+ atspiStateSet.contains(atspiState));
+ }
+
+ // attributes
+ if (auto xLOAttrs
+ = uno::Reference<accessibility::XAccessibleExtendedAttributes>(xLOContext, uno::UNO_QUERY))
+ {
+ // see atktextattributes.cxx:attribute_set_new_from_extended_attributes
+ const uno::Any anyVal = xLOAttrs->getExtendedAttributes();
+ OUString sExtendedAttrs;
+ anyVal >>= sExtendedAttrs;
+ sal_Int32 nIndex = 0;
+
+ const auto atspiAttrs = pAtspiAccessible.getAttributes();
+
+ do
+ {
+ OUString sProperty = sExtendedAttrs.getToken(0, ';', nIndex);
+
+ sal_Int32 nColonPos = 0;
+ const OString sPropertyName = OUStringToOString(
+ o3tl::getToken(sProperty, 0, ':', nColonPos), RTL_TEXTENCODING_UTF8);
+ const OString sPropertyValue = OUStringToOString(
+ o3tl::getToken(sProperty, 0, ':', nColonPos), RTL_TEXTENCODING_UTF8);
+
+ const auto atspiAttrIter = atspiAttrs.find(std::string(sPropertyName));
+ CPPUNIT_ASSERT_MESSAGE(std::string("Missing attribute: ") + sPropertyName.getStr(),
+ atspiAttrIter != atspiAttrs.end());
+ CPPUNIT_ASSERT_EQUAL(std::string_view(sPropertyName),
+ std::string_view(atspiAttrIter->first));
+ CPPUNIT_ASSERT_EQUAL(std::string_view(sPropertyValue),
+ std::string_view(atspiAttrIter->second));
+ } while (nIndex >= 0 && nIndex < sExtendedAttrs.getLength());
+ }
+
+ // relations
+ const auto xLORelationSet = xLOContext->getAccessibleRelationSet();
+ const auto aAtspiRelationSet = pAtspiAccessible.getRelationSet();
+ const auto nLORelationCount = xLORelationSet.is() ? xLORelationSet->getRelationCount() : 0;
+ CPPUNIT_ASSERT_EQUAL(nLORelationCount, sal_Int32(aAtspiRelationSet.size()));
+ for (auto i = decltype(nLORelationCount){ 0 }; i < nLORelationCount; i++)
+ {
+ const auto xLORelation = xLORelationSet->getRelation(i);
+ const auto pAtspiRelation = aAtspiRelationSet[i];
+ const auto nLOTargetsCount = xLORelation.TargetSet.getLength();
+
+ CPPUNIT_ASSERT_EQUAL(mapRelationType(xLORelation.RelationType),
+ pAtspiRelation.getRelationType());
+ CPPUNIT_ASSERT_EQUAL(nLOTargetsCount, static_cast<sal_Int32>(pAtspiRelation.getNTargets()));
+
+ if (recurseFlags & RecurseFlags::RELATIONS_TARGETS)
+ {
+ for (auto j = decltype(nLOTargetsCount){ 0 }; j < nLOTargetsCount; j++)
+ {
+ uno::Reference<accessibility::XAccessible> xLOTarget(xLORelation.TargetSet[j],
+ uno::UNO_QUERY_THROW);
+ compareObjects(xLOTarget, pAtspiRelation.getTarget(j), RecurseFlags::NONE);
+ }
+ }
+ }
+
+ // other interfaces
+ if (auto xLOText = uno::Reference<accessibility::XAccessibleText>(xLOContext, uno::UNO_QUERY))
+ {
+ Atspi::Text pAtspiText;
+ CPPUNIT_ASSERT_NO_THROW(pAtspiText = pAtspiAccessible.queryText());
+ compareTextObjects(xLOText, pAtspiText);
+ }
+
+ // TODO: more checks here...
+}
+
+void Atspi2TestTree::compareTrees(const uno::Reference<accessibility::XAccessible>& xLOAccessible,
+ const Atspi::Accessible& xAtspiAccessible, const int depth)
+{
+ sal_uInt16 recurseFlags = RecurseFlags::ALL;
+ if (depth == 0)
+ recurseFlags ^= RecurseFlags::PARENT;
+ compareObjects(xLOAccessible, xAtspiAccessible, recurseFlags);
+
+ if (!xLOAccessible || !xAtspiAccessible)
+ return;
+
+ auto xLOContext = xLOAccessible->getAccessibleContext();
+ CPPUNIT_ASSERT(xLOContext);
+
+ const auto nLOChildCount = xLOContext->getAccessibleChildCount();
+ const auto nAtspiChildCount = decltype(nLOChildCount){ xAtspiAccessible.getChildCount() };
+ /* We use >= instead of == because GTK exposes scrollbar objects LO doesn't. We possibly
+ * should check better than merely accept more children, but it's probably OK if there are
+ * *more* children as viewed by ATSPI, rather than less. And we're comparing them anyway. */
+ CPPUNIT_ASSERT_GREATEREQUAL(nLOChildCount, nAtspiChildCount);
+
+ for (auto nthChild = decltype(nLOChildCount){ 0 }; nthChild < nLOChildCount; nthChild++)
+ {
+ for (auto i = decltype(depth){ 0 }; i < depth; i++)
+ std::cout << " ";
+ std::cout << "* child " << nthChild << ": ";
+ compareTrees(xLOContext->getAccessibleChild(nthChild),
+ xAtspiAccessible.getChildAtIndex(nthChild), depth + 1);
+ }
+
+ /* We need to scrolling test here, because they might modify the tree and invalidate children,
+ * so we can't do it from the children themselves as they might get disposed during the test */
+ if (nLOChildCount > 0
+ && accessibility::AccessibleRole::DOCUMENT_TEXT == xLOContext->getAccessibleRole())
+ {
+ testSwScroll(xLOContext, xAtspiAccessible);
+ }
+}
+
+// gets the nth child of @p pAcc and check its role is @p role
+static Atspi::Accessible getDescendentAtPath(const Atspi::Accessible& xAcc, int nthChild,
+ AtspiRole role)
+{
+ CPPUNIT_ASSERT(xAcc);
+ CPPUNIT_ASSERT_GREATER(nthChild, xAcc.getChildCount());
+ auto xChild = xAcc.getChildAtIndex(nthChild);
+ CPPUNIT_ASSERT(xChild);
+ CPPUNIT_ASSERT_EQUAL(role, xChild.getRole());
+ return xChild;
+}
+
+// gets the nth child of @p pAcc and check its role is @p role, then gets the nth child of that one, etc.
+template <typename... Ts>
+static Atspi::Accessible getDescendentAtPath(const Atspi::Accessible& xAcc, int nthChild,
+ AtspiRole role, Ts... args)
+{
+ return getDescendentAtPath(getDescendentAtPath(xAcc, nthChild, role), args...);
+}
+
+CPPUNIT_TEST_FIXTURE(Atspi2TestTree, Test1)
+{
+ loadFromSrc(u"vcl/qa/cppunit/a11y/atspi2/testdocuments/ecclectic.fodt"_ustr);
+
+ /* FIXME: We zoom out for everything to fit in the view not to have off-screen children
+ * that the controller code fails to clean up properly in some situations.
+ * Once the root issue is fixed in LO, remove this.
+ * Note that zooming out like so, and not having off-screen children, renders the
+ * Atspi2TestTree::testSwScroll() test useless as it has nothing to scroll into view. */
+ unotest::MacrosTest::dispatchCommand(mxDocument, ".uno:ZoomPage", {});
+ unotest::MacrosTest::dispatchCommand(
+ mxDocument, ".uno:ViewLayout",
+ {
+ comphelper::makePropertyValue("ViewLayout.Columns", sal_Int16(2)),
+ comphelper::makePropertyValue("ViewLayout.BookMode", false),
+ });
+ /* HACK: verify the whole content of the document is actually visible (nothing overflows)
+ * after zooming out above */
+ const auto xLODocContext = getDocumentAccessibleContext();
+ const auto xLODocFirstChild = xLODocContext->getAccessibleChild(0);
+ CPPUNIT_ASSERT(xLODocFirstChild.is());
+ CPPUNIT_ASSERT(
+ !getFirstRelationTargetOfType(xLODocFirstChild->getAccessibleContext(),
+ accessibility::AccessibleRelationType::CONTENT_FLOWS_FROM));
+ const auto nLODocChildCount = xLODocContext->getAccessibleChildCount();
+ const auto xLODocLastChild = xLODocContext->getAccessibleChild(nLODocChildCount - 1);
+ CPPUNIT_ASSERT(xLODocLastChild.is());
+ CPPUNIT_ASSERT(
+ !getFirstRelationTargetOfType(xLODocLastChild->getAccessibleContext(),
+ accessibility::AccessibleRelationType::CONTENT_FLOWS_TO));
+ // END HACK
+
+ auto xContext = getWindowAccessibleContext();
+ CPPUNIT_ASSERT(xContext.is());
+
+ //~ dumpA11YTree(xContext);
+
+ // get the window manager frame
+ auto xAtspiWindow = getDescendentAtPath(m_pAtspiApp, 0, ATSPI_ROLE_FRAME);
+ CPPUNIT_ASSERT(xAtspiWindow);
+ dumpAtspiTree(xAtspiWindow);
+
+ /* The ATSPI representation has extra nodes around the relevant ones, which look like leftovers
+ * from the start center. Ignore those and dive directly to the meaningful node (which is the
+ * 1st child of the 2nd child of the 1st child -- ask me how I know) */
+ auto xAtspiPane = getDescendentAtPath(xAtspiWindow, 0, ATSPI_ROLE_PANEL, 1, ATSPI_ROLE_PANEL, 0,
+ ATSPI_ROLE_ROOT_PANE);
+
+ compareTrees(uno::Reference<accessibility::XAccessible>(mxWindow, uno::UNO_QUERY_THROW),
+ xAtspiPane);
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/qa/cppunit/a11y/atspi2/atspi2.hxx b/vcl/qa/cppunit/a11y/atspi2/atspi2.hxx
new file mode 100644
index 0000000000..87c0b698f3
--- /dev/null
+++ b/vcl/qa/cppunit/a11y/atspi2/atspi2.hxx
@@ -0,0 +1,45 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/XAccessibleText.hpp>
+#include <com/sun/star/uno/Reference.h>
+
+#include "atspi2testbase.hxx"
+#include "atspiwrapper.hxx"
+
+class Atspi2TestTree : public Atspi2TestBase
+{
+protected:
+ enum RecurseFlags : sal_uInt16
+ {
+ NONE = 0,
+ PARENT = 1 << 0,
+ RELATIONS_TARGETS = 1 << 1,
+ ALL = 0xffff
+ };
+
+ static sal_Int64
+ swChildIndex(css::uno::Reference<css::accessibility::XAccessibleContext> xContext);
+ static void
+ testSwScroll(const css::uno::Reference<css::accessibility::XAccessibleContext>& xLOContext,
+ const Atspi::Accessible& pAtspiAccessible);
+ static void
+ compareObjects(const css::uno::Reference<css::accessibility::XAccessible>& xLOAccessible,
+ const Atspi::Accessible& pAtspiAccessible, const sal_uInt16 recurseFlags);
+ static void
+ compareTrees(const css::uno::Reference<css::accessibility::XAccessible>& xLOAccessible,
+ const Atspi::Accessible& xAtspiAccessible, const int depth = 0);
+ static void
+ compareTextObjects(const css::uno::Reference<css::accessibility::XAccessibleText>& xLOText,
+ const Atspi::Text& pAtspiText);
+};
diff --git a/vcl/qa/cppunit/a11y/atspi2/atspi2testbase.hxx b/vcl/qa/cppunit/a11y/atspi2/atspi2testbase.hxx
new file mode 100644
index 0000000000..bcde764aa9
--- /dev/null
+++ b/vcl/qa/cppunit/a11y/atspi2/atspi2testbase.hxx
@@ -0,0 +1,97 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <vcl/svapp.hxx>
+
+#include <test/a11y/accessibletestbase.hxx>
+#include <test/a11y/AccessibilityTools.hxx>
+
+#include "atspiwrapper.hxx"
+
+/**
+ * @brief Base class for AT-SPI2 tests.
+ *
+ * This provides the basis for interacting with AT-SPI2, including getting the object representing
+ * our application, and facility for obtaining a top-level window of that app.
+ */
+class Atspi2TestBase : public test::AccessibleTestBase
+{
+protected:
+ Atspi::Accessible m_pAtspiApp;
+
+ static Atspi::Accessible getApp(const std::string_view appName)
+ {
+ std::cout << "Looking for AT-SPI application \"" << appName << "\"" << std::endl;
+ const auto nDesktops = atspi_get_desktop_count();
+ for (auto desktopId = decltype(nDesktops){ 0 }; desktopId < nDesktops; desktopId++)
+ {
+ Atspi::Accessible desktop(atspi_get_desktop(desktopId));
+
+ for (auto&& child : desktop)
+ {
+ if (!child) // is that useful?
+ continue;
+ if (child.getRole() != ATSPI_ROLE_APPLICATION)
+ continue;
+ const auto name = child.getName();
+ std::cout << "Found desktop child: " << name << std::endl;
+ if (appName != name)
+ {
+ continue;
+ }
+ return std::move(child);
+ }
+ }
+ return nullptr;
+ }
+
+ static Atspi::Accessible getSelfApp()
+ {
+ const auto appFileName = Application::GetAppFileName();
+ const auto slash = appFileName.lastIndexOf('/');
+ const auto baseName = (slash >= 0) ? OUString(appFileName.subView(slash + 1)) : appFileName;
+ return getApp(baseName.getLength() > 0 ? baseName.toUtf8().getStr() : "cppunittester");
+ }
+
+protected:
+ Atspi::Accessible getWindow(const std::string_view windowName)
+ {
+ for (auto&& child : m_pAtspiApp)
+ {
+ const auto name = child.getName();
+ std::cout << "Found window: " << name << std::endl;
+ if (windowName == name)
+ return std::move(child);
+ }
+ return nullptr;
+ }
+
+public:
+ Atspi2TestBase()
+ {
+ if (!atspi_is_initialized())
+ atspi_init();
+
+ // increase timeout to avoid spurious avoid CI failures
+ atspi_set_timeout(2500, 15000);
+ }
+
+ virtual void setUp() override
+ {
+ test::AccessibleTestBase::setUp();
+
+ AccessibilityTools::Await([this]() {
+ m_pAtspiApp = getSelfApp();
+ return bool(m_pAtspiApp);
+ });
+ assert(m_pAtspiApp);
+ }
+};
diff --git a/vcl/qa/cppunit/a11y/atspi2/atspi2text.cxx b/vcl/qa/cppunit/a11y/atspi2/atspi2text.cxx
new file mode 100644
index 0000000000..eb0e2aa7e5
--- /dev/null
+++ b/vcl/qa/cppunit/a11y/atspi2/atspi2text.cxx
@@ -0,0 +1,1017 @@
+/* -*- 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 <com/sun/star/accessibility/AccessibleRelationType.hpp>
+#include <com/sun/star/accessibility/AccessibleTextType.hpp>
+#include <com/sun/star/accessibility/XAccessibleComponent.hpp>
+#include <com/sun/star/accessibility/XAccessibleText.hpp>
+#include <com/sun/star/accessibility/XAccessibleTextAttributes.hpp>
+#include <com/sun/star/accessibility/XAccessibleTextMarkup.hpp>
+
+#include <com/sun/star/awt/FontSlant.hpp>
+#include <com/sun/star/awt/FontStrikeout.hpp>
+#include <com/sun/star/awt/FontUnderline.hpp>
+#include <com/sun/star/style/CaseMap.hpp>
+#include <com/sun/star/style/LineSpacing.hpp>
+#include <com/sun/star/style/LineSpacingMode.hpp>
+#include <com/sun/star/style/ParagraphAdjust.hpp>
+#include <com/sun/star/style/TabStop.hpp>
+#include <com/sun/star/text/FontRelief.hpp>
+#include <com/sun/star/text/WritingMode2.hpp>
+#include <com/sun/star/text/TextMarkupType.hpp>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <tools/UnitConversion.hxx>
+#include <rtl/character.hxx>
+
+#include <test/a11y/AccessibilityTools.hxx>
+
+#include "atspi2.hxx"
+#include "atspiwrapper.hxx"
+
+using namespace css;
+
+namespace
+{
+/** @brief Helper class to check text attributes are properly exported to Atspi.
+ *
+ * This kind of duplicates most of the logic in atktextattributes.cxx, but if we want to check the
+ * values are correct (which includes whether they are properly updated for example), we have to do
+ * this, even though it means quite some processing for some of the attributes.
+ * This has to be kept in sync with how atktextattributes.cxx exposes those attributes. */
+class AttributesChecker
+{
+private:
+ uno::Reference<accessibility::XAccessibleText> mxLOText;
+ Atspi::Text mxAtspiText;
+
+public:
+ AttributesChecker(const uno::Reference<accessibility::XAccessibleText>& xLOText,
+ const Atspi::Text& xAtspiText)
+ : mxLOText(xLOText)
+ , mxAtspiText(xAtspiText)
+ {
+ }
+
+private:
+ // helper to validate a value represented as a single float in ATSPI
+ static bool implCheckFloat(std::string_view atspiValue, float expected)
+ {
+ float f;
+ char dummy;
+
+ CPPUNIT_ASSERT_EQUAL(1, sscanf(atspiValue.data(), "%g%c", &f, &dummy));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, f, 1e-4);
+
+ return true;
+ }
+
+ // helper to check simple mappings between LO and ATSPI
+ template <typename T>
+ static bool implCheckMapping(const T loValue, const std::string_view atspiValue,
+ const std::unordered_map<T, std::string_view>& map,
+ const bool retIfMissing = false)
+ {
+ const auto& iter = map.find(loValue);
+ if (iter != map.end())
+ {
+ CPPUNIT_ASSERT_EQUAL(iter->second, atspiValue);
+ return true;
+ }
+ return retIfMissing;
+ }
+
+ // checkers, see atktextattributes.cxx
+ bool checkBoolean(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ if (property.Value.get<bool>())
+ CPPUNIT_ASSERT_EQUAL(std::string_view("true"), atspiValue);
+ else
+ CPPUNIT_ASSERT_EQUAL(std::string_view("false"), atspiValue);
+
+ return true;
+ }
+
+ bool checkString(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ CPPUNIT_ASSERT_EQUAL(property.Value.get<OUString>(), OUString::fromUtf8(atspiValue));
+ return true;
+ }
+
+ bool checkFloat(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ return implCheckFloat(atspiValue, property.Value.get<float>());
+ }
+
+ bool checkVariant(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ if (property.Value.get<short>() == style::CaseMap::SMALLCAPS)
+ CPPUNIT_ASSERT_EQUAL(std::string_view("small_caps"), atspiValue);
+ else
+ CPPUNIT_ASSERT_EQUAL(std::string_view("normal"), atspiValue);
+
+ return true;
+ }
+
+ // See Scale2String
+ bool checkScale(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ double v;
+ char dummy;
+
+ CPPUNIT_ASSERT_EQUAL(1, sscanf(atspiValue.data(), "%lg%c", &v, &dummy));
+ CPPUNIT_ASSERT_EQUAL(property.Value.get<sal_Int16>(), sal_Int16(v * 100));
+
+ return true;
+ }
+
+ // see Escapement2VerticalAlign
+ bool checkVerticalAlign(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ const sal_Int16 n = property.Value.get<sal_Int16>();
+
+ if (n == 0)
+ CPPUNIT_ASSERT_EQUAL(std::string_view("baseline"), atspiValue);
+ else if (n == -101)
+ CPPUNIT_ASSERT_EQUAL(std::string_view("sub"), atspiValue);
+ else if (n == 101)
+ CPPUNIT_ASSERT_EQUAL(std::string_view("super"), atspiValue);
+ else
+ {
+ int v;
+ char dummy;
+ CPPUNIT_ASSERT_EQUAL(1, sscanf(atspiValue.data(), "%d%%%c", &v, &dummy));
+ CPPUNIT_ASSERT_EQUAL(int(n), v);
+ }
+
+ return true;
+ }
+
+ bool checkColor(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ auto color = property.Value.get<sal_Int32>();
+
+ if (color == -1) // automatic, use the component's color
+ {
+ uno::Reference<accessibility::XAccessibleComponent> xComponent(mxLOText,
+ uno::UNO_QUERY);
+ if (xComponent.is())
+ {
+ if (property.Name == u"CharBackColor")
+ color = xComponent->getBackground();
+ else if (property.Name == u"CharColor")
+ color = xComponent->getForeground();
+ }
+ }
+
+ if (color != -1)
+ {
+ unsigned int r, g, b;
+ char dummy;
+
+ CPPUNIT_ASSERT_EQUAL(3, sscanf(atspiValue.data(), "%u,%u,%u%c", &r, &g, &b, &dummy));
+ CPPUNIT_ASSERT_EQUAL((color & 0xFFFFFF),
+ (static_cast<sal_Int32>(r) << 16 | static_cast<sal_Int32>(g) << 8
+ | static_cast<sal_Int32>(b)));
+ return true;
+ }
+
+ return false;
+ }
+
+ // See LineSpacing2LineHeight
+ bool checkLineHeight(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ const auto lineSpacing = property.Value.get<style::LineSpacing>();
+ char dummy;
+
+ if (lineSpacing.Mode == style::LineSpacingMode::PROP)
+ {
+ int h;
+
+ CPPUNIT_ASSERT_EQUAL(1, sscanf(atspiValue.data(), "%d%%%c", &h, &dummy));
+ CPPUNIT_ASSERT_EQUAL(lineSpacing.Height, sal_Int16(h));
+ }
+ else if (lineSpacing.Mode == style::LineSpacingMode::FIX)
+ {
+ double pt;
+
+ CPPUNIT_ASSERT_EQUAL(1, sscanf(atspiValue.data(), "%lgpt%c", &pt, &dummy));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(convertMm100ToPoint<double>(lineSpacing.Height), pt, 1e-4);
+ CPPUNIT_ASSERT_EQUAL(lineSpacing.Height, sal_Int16(convertPointToMm100(pt)));
+ }
+ else
+ return false;
+
+ return true;
+ }
+
+ bool checkStretch(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ const auto n = property.Value.get<sal_Int16>();
+
+ if (n < 0)
+ CPPUNIT_ASSERT_EQUAL(std::string_view("condensed"), atspiValue);
+ else if (n > 0)
+ CPPUNIT_ASSERT_EQUAL(std::string_view("expanded"), atspiValue);
+ else
+ CPPUNIT_ASSERT_EQUAL(std::string_view("normal"), atspiValue);
+
+ return true;
+ }
+
+ bool checkStyle(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ return implCheckMapping(
+ property.Value.get<awt::FontSlant>(), atspiValue,
+ { { awt::FontSlant_NONE, std::string_view("normal") },
+ { awt::FontSlant_OBLIQUE, std::string_view("oblique") },
+ { awt::FontSlant_ITALIC, std::string_view("italic") },
+ { awt::FontSlant_REVERSE_OBLIQUE, std::string_view("reverse oblique") },
+ { awt::FontSlant_REVERSE_ITALIC, std::string_view("reverse italic") } });
+ }
+
+ bool checkJustification(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ return implCheckMapping(static_cast<style::ParagraphAdjust>(property.Value.get<short>()),
+ atspiValue,
+ { { style::ParagraphAdjust_LEFT, std::string_view("left") },
+ { style::ParagraphAdjust_RIGHT, std::string_view("right") },
+ { style::ParagraphAdjust_BLOCK, std::string_view("fill") },
+ { style::ParagraphAdjust_STRETCH, std::string_view("fill") },
+ { style::ParagraphAdjust_CENTER, std::string_view("center") } });
+ }
+
+ bool checkShadow(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ if (property.Value.get<bool>())
+ CPPUNIT_ASSERT_EQUAL(std::string_view("black"), atspiValue);
+ else
+ CPPUNIT_ASSERT_EQUAL(std::string_view("none"), atspiValue);
+
+ return true;
+ }
+
+ bool checkLanguage(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ auto aLocale = property.Value.get<lang::Locale>();
+ LanguageTag aLanguageTag(aLocale);
+
+ CPPUNIT_ASSERT_EQUAL(OUString(aLanguageTag.getLanguage() + "-"
+ + aLanguageTag.getCountry().toAsciiLowerCase()),
+ OUString::fromUtf8(atspiValue));
+
+ return true;
+ }
+
+ bool checkTextRotation(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ return implCheckFloat(atspiValue, property.Value.get<sal_Int16>() / 10.0f);
+ }
+
+ bool checkWeight(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ return implCheckFloat(atspiValue, property.Value.get<float>() * 4);
+ }
+
+ bool checkCMMValue(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ double v;
+ char dummy;
+
+ // CMM is 1/100th of a mm
+ CPPUNIT_ASSERT_EQUAL(1, sscanf(atspiValue.data(), "%lgmm%c", &v, &dummy));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(property.Value.get<sal_Int32>() * 0.01, v, 1e-4);
+
+ return true;
+ }
+
+ bool checkDirection(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ return implCheckMapping(property.Value.get<sal_Int16>(), atspiValue,
+ { { text::WritingMode2::TB_LR, std::string_view("ltr") },
+ { text::WritingMode2::LR_TB, std::string_view("ltr") },
+ { text::WritingMode2::TB_RL, std::string_view("rtl") },
+ { text::WritingMode2::RL_TB, std::string_view("rtl") },
+ { text::WritingMode2::PAGE, std::string_view("none") } });
+ }
+
+ bool checkWritingMode(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ return implCheckMapping(property.Value.get<sal_Int16>(), atspiValue,
+ { { text::WritingMode2::TB_LR, std::string_view("tb-lr") },
+ { text::WritingMode2::LR_TB, std::string_view("lr-tb") },
+ { text::WritingMode2::TB_RL, std::string_view("tb-rl") },
+ { text::WritingMode2::RL_TB, std::string_view("rl-tb") },
+ { text::WritingMode2::PAGE, std::string_view("none") } });
+ }
+
+ static const beans::PropertyValue*
+ findProperty(const uno::Sequence<beans::PropertyValue>& properties, std::u16string_view name)
+ {
+ auto prop = std::find_if(properties.begin(), properties.end(),
+ [name](auto& p) { return p.Name == name; });
+ if (prop == properties.end())
+ prop = nullptr;
+ return prop;
+ }
+
+ // same as findProperty() above, but with a fast path is @p property is a match
+ static const beans::PropertyValue*
+ findProperty(const beans::PropertyValue* property,
+ const uno::Sequence<beans::PropertyValue>& properties, std::u16string_view name)
+ {
+ if (property->Name == name)
+ return property;
+ return findProperty(properties, name);
+ }
+
+ bool checkFontEffect(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>& loProperties)
+ {
+ if (auto charContoured = findProperty(&property, loProperties, u"CharContoured");
+ charContoured && charContoured->Value.get<bool>())
+ {
+ CPPUNIT_ASSERT_EQUAL(std::string_view("outline"), atspiValue);
+ return true;
+ }
+
+ if (auto charRelief = findProperty(&property, loProperties, u"CharRelief"))
+ {
+ return implCheckMapping(charRelief->Value.get<sal_Int16>(), atspiValue,
+ { { text::FontRelief::NONE, std::string_view("none") },
+ { text::FontRelief::EMBOSSED, std::string_view("emboss") },
+ { text::FontRelief::ENGRAVED, std::string_view("engrave") } },
+ true);
+ }
+
+ return false;
+ }
+
+ bool checkTextDecoration(std::string_view atspiValue, const beans::PropertyValue&,
+ const uno::Sequence<beans::PropertyValue>& loProperties)
+ {
+ if (atspiValue == "none")
+ {
+ if (auto prop = findProperty(loProperties, u"CharFlash"))
+ CPPUNIT_ASSERT_EQUAL(false, prop->Value.get<bool>());
+ if (auto prop = findProperty(loProperties, u"CharUnderline"))
+ CPPUNIT_ASSERT_EQUAL(css::awt::FontUnderline::NONE, prop->Value.get<sal_Int16>());
+ if (auto prop = findProperty(loProperties, u"CharStrikeout"))
+ CPPUNIT_ASSERT(prop->Value.get<sal_Int16>() == css::awt::FontStrikeout::NONE
+ || prop->Value.get<sal_Int16>()
+ == css::awt::FontStrikeout::DONTKNOW);
+ }
+ else
+ {
+ sal_Int32 nIndex = 0;
+ const auto atspiValueString = OUString::fromUtf8(atspiValue);
+
+ do
+ {
+ OUString atspiToken = atspiValueString.getToken(0, ' ', nIndex);
+ const beans::PropertyValue* prop;
+
+ if (atspiToken == "blink")
+ {
+ CPPUNIT_ASSERT((prop = findProperty(loProperties, u"CharFlash")));
+ CPPUNIT_ASSERT_EQUAL(true, prop->Value.get<bool>());
+ }
+ else if (atspiToken == "underline")
+ {
+ CPPUNIT_ASSERT((prop = findProperty(loProperties, u"CharUnderline")));
+ CPPUNIT_ASSERT(prop->Value.get<sal_Int16>() != css::awt::FontUnderline::NONE);
+ }
+ else if (atspiToken == "underline")
+ {
+ CPPUNIT_ASSERT((prop = findProperty(loProperties, u"CharStrikeout")));
+ CPPUNIT_ASSERT(prop->Value.get<sal_Int16>() != css::awt::FontStrikeout::NONE);
+ CPPUNIT_ASSERT(prop->Value.get<sal_Int16>()
+ != css::awt::FontStrikeout::DONTKNOW);
+ }
+ else
+ {
+ CPPUNIT_ASSERT_MESSAGE(
+ OUString("Unknown text decoration \"" + atspiToken).toUtf8().getStr(),
+ false);
+ }
+ } while (nIndex > 0);
+ }
+
+ return true;
+ }
+
+ static bool implCheckTabStops(std::string_view atspiValue, const beans::PropertyValue& property,
+ const bool defaultTabs)
+ {
+ uno::Sequence<style::TabStop> theTabStops;
+
+ if (property.Value >>= theTabStops)
+ {
+ sal_Unicode lastFillChar = ' ';
+ const char* p = atspiValue.data();
+
+ for (const auto& rTabStop : std::as_const(theTabStops))
+ {
+ if ((style::TabAlign_DEFAULT == rTabStop.Alignment) != defaultTabs)
+ continue;
+
+ const char* tab_align = "";
+ switch (rTabStop.Alignment)
+ {
+ case style::TabAlign_LEFT:
+ tab_align = "left ";
+ break;
+ case style::TabAlign_CENTER:
+ tab_align = "center ";
+ break;
+ case style::TabAlign_RIGHT:
+ tab_align = "right ";
+ break;
+ case style::TabAlign_DECIMAL:
+ tab_align = "decimal ";
+ break;
+ default:
+ break;
+ }
+
+ const char* lead_char = "";
+ if (rTabStop.FillChar != lastFillChar)
+ {
+ lastFillChar = rTabStop.FillChar;
+ switch (lastFillChar)
+ {
+ case ' ':
+ lead_char = "blank ";
+ break;
+
+ case '.':
+ lead_char = "dotted ";
+ break;
+
+ case '-':
+ lead_char = "dashed ";
+ break;
+
+ case '_':
+ lead_char = "lined ";
+ break;
+
+ default:
+ lead_char = "custom ";
+ break;
+ }
+ }
+
+ // check this matches "<lead_char><tab_align><position>mm"
+ CPPUNIT_ASSERT_EQUAL(0, strncmp(p, lead_char, strlen(lead_char)));
+ p += strlen(lead_char);
+ CPPUNIT_ASSERT_EQUAL(0, strncmp(p, tab_align, strlen(tab_align)));
+ p += strlen(tab_align);
+ float atspiPosition;
+ int nConsumed;
+ CPPUNIT_ASSERT_EQUAL(1, sscanf(p, "%gmm%n", &atspiPosition, &nConsumed));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(float(rTabStop.Position * 0.01f), atspiPosition, 1e-4);
+ p += nConsumed;
+
+ if (*p)
+ {
+ CPPUNIT_ASSERT_EQUAL(' ', *p);
+ p++;
+ }
+ }
+
+ // make sure there isn't garbage at the end
+ CPPUNIT_ASSERT_EQUAL(char(0), *p);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ bool checkDefaultTabStops(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ return implCheckTabStops(atspiValue, property, true);
+ }
+
+ bool checkTabStops(std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>&)
+ {
+ return implCheckTabStops(atspiValue, property, false);
+ }
+
+public:
+ // runner code
+ bool check(const uno::Sequence<beans::PropertyValue>& xLOAttributeList,
+ const std::unordered_map<std::string, std::string>& xAtspiAttributeList)
+ {
+ const struct
+ {
+ const char* loName;
+ const char* atspiName;
+ bool (AttributesChecker::*checkValue)(
+ std::string_view atspiValue, const beans::PropertyValue& property,
+ const uno::Sequence<beans::PropertyValue>& loAttributeList);
+ } atspiMap[]
+ = { // LO name AT-SPI name check function
+ { "CharBackColor", "bg-color", &AttributesChecker::checkColor },
+ { "CharCaseMap", "variant", &AttributesChecker::checkVariant },
+ { "CharColor", "fg-color", &AttributesChecker::checkColor },
+ { "CharContoured", "font-effect", &AttributesChecker::checkFontEffect },
+ { "CharEscapement", "vertical-align", &AttributesChecker::checkVerticalAlign },
+ { "CharFlash", "text-decoration", &AttributesChecker::checkTextDecoration },
+ { "CharFontName", "family-name", &AttributesChecker::checkString },
+ { "CharHeight", "size", &AttributesChecker::checkFloat },
+ { "CharHidden", "invisible", &AttributesChecker::checkBoolean },
+ { "CharKerning", "stretch", &AttributesChecker::checkStretch },
+ { "CharLocale", "language", &AttributesChecker::checkLanguage },
+ { "CharPosture", "style", &AttributesChecker::checkStyle },
+ { "CharRelief", "font-effect", &AttributesChecker::checkFontEffect },
+ { "CharRotation", "text-rotation", &AttributesChecker::checkTextRotation },
+ { "CharScaleWidth", "scale", &AttributesChecker::checkScale },
+ { "CharShadowed", "text-shadow", &AttributesChecker::checkShadow },
+ { "CharStrikeout", "text-decoration", &AttributesChecker::checkTextDecoration },
+ { "CharUnderline", "text-decoration", &AttributesChecker::checkTextDecoration },
+ { "CharWeight", "weight", &AttributesChecker::checkWeight },
+ { "MMToPixelRatio", "mm-to-pixel-ratio", &AttributesChecker::checkFloat },
+ { "ParaAdjust", "justification", &AttributesChecker::checkJustification },
+ { "ParaBottomMargin", "pixels-below-lines", &AttributesChecker::checkCMMValue },
+ { "ParaFirstLineIndent", "indent", &AttributesChecker::checkCMMValue },
+ { "ParaLeftMargin", "left-margin", &AttributesChecker::checkCMMValue },
+ { "ParaLineSpacing", "line-height", &AttributesChecker::checkLineHeight },
+ { "ParaRightMargin", "right-margin", &AttributesChecker::checkCMMValue },
+ { "ParaStyleName", "paragraph-style", &AttributesChecker::checkString },
+ { "ParaTabStops", "tab-interval", &AttributesChecker::checkDefaultTabStops },
+ { "ParaTabStops", "tab-stops", &AttributesChecker::checkTabStops },
+ { "ParaTopMargin", "pixels-above-lines", &AttributesChecker::checkCMMValue },
+ { "WritingMode", "direction", &AttributesChecker::checkDirection },
+ { "WritingMode", "writing-mode", &AttributesChecker::checkWritingMode }
+ };
+
+ for (const auto& prop : xLOAttributeList)
+ {
+ std::cout << "found run attribute: " << prop.Name << "=" << prop.Value << std::endl;
+
+ /* we need to loop on all entries because there might be more than one for a single
+ * property */
+ for (const auto& entry : atspiMap)
+ {
+ if (!prop.Name.equalsAscii(entry.loName))
+ continue;
+
+ const auto atspiIter = xAtspiAttributeList.find(entry.atspiName);
+ /* we use an empty value if there isn't one, which can happen if the value cannot
+ * be represented by Atspi, or if the actual LO value is also empty */
+ std::string atspiValue;
+ if (atspiIter != xAtspiAttributeList.end())
+ atspiValue = atspiIter->second;
+
+ std::cout << " matching atspi attribute is: " << entry.atspiName << "="
+ << atspiValue << std::endl;
+ CPPUNIT_ASSERT(
+ std::invoke(entry.checkValue, this, atspiValue, prop, xLOAttributeList));
+ }
+ }
+
+ return true;
+ }
+};
+}
+
+/* LO doesn't implement it itself, but ATK provides a fallback. Add a test here merely for the
+ * future when we have a direct AT-SPI implementation for e.g. GTK4.
+ * Just like atk-adaptor, we compute the bounding box by combining extents for each character
+ * in the range */
+static awt::Rectangle getRangeBounds(const uno::Reference<accessibility::XAccessibleText>& xText,
+ sal_Int32 startOffset, sal_Int32 endOffset)
+{
+ awt::Rectangle bounds;
+
+ for (auto offset = startOffset; offset < endOffset; offset++)
+ {
+ const auto chBounds = xText->getCharacterBounds(offset);
+ if (offset == 0)
+ bounds = chBounds;
+ else
+ {
+ const auto x = std::min(bounds.X, chBounds.X);
+ const auto y = std::min(bounds.Y, chBounds.Y);
+ bounds.Width = std::max(bounds.X + bounds.Width, chBounds.X + chBounds.Width) - x;
+ bounds.Height = std::max(bounds.Y + bounds.Height, chBounds.Y + chBounds.Height) - y;
+ bounds.X = x;
+ bounds.Y = y;
+ }
+ }
+
+ return bounds;
+}
+
+void Atspi2TestTree::compareTextObjects(
+ const uno::Reference<accessibility::XAccessibleText>& xLOText, const Atspi::Text& pAtspiText)
+{
+ CPPUNIT_ASSERT_EQUAL(xLOText->getCharacterCount(), sal_Int32(pAtspiText.getCharacterCount()));
+ CPPUNIT_ASSERT_EQUAL(xLOText->getCaretPosition(), sal_Int32(pAtspiText.getCaretOffset()));
+ CPPUNIT_ASSERT_EQUAL(xLOText->getText(), OUString::fromUtf8(pAtspiText.getText(0, -1)));
+
+ const auto characterCount = xLOText->getCharacterCount();
+ auto offset = decltype(characterCount){ 0 };
+ auto atspiPosition = Atspi::Point{ 0, 0 };
+
+ AttributesChecker attributesChecker(xLOText, pAtspiText);
+
+ auto xLOTextAttrs
+ = uno::Reference<accessibility::XAccessibleTextAttributes>(xLOText, uno::UNO_QUERY);
+ // default text attributes
+ if (xLOTextAttrs.is())
+ {
+ const auto aAttributeList = xLOTextAttrs->getDefaultAttributes(uno::Sequence<OUString>());
+ const auto atspiAttributeList = pAtspiText.getDefaultAttributes();
+
+ attributesChecker.check(aAttributeList, atspiAttributeList);
+ }
+
+ if (characterCount > 0)
+ {
+ const auto atspiComponent = pAtspiText.queryComponent();
+ atspiPosition = atspiComponent.getPosition(ATSPI_COORD_TYPE_WINDOW);
+ }
+
+ // text run attributes
+ uno::Reference<accessibility::XAccessibleTextMarkup> xTextMarkup(xLOText, uno::UNO_QUERY);
+ while (offset < characterCount)
+ {
+ // message for the assertions so we know where it comes from
+ OString offsetMsg(OString::Concat("in ") + AccessibilityTools::debugString(xLOText).c_str()
+ + " at offset " + OString::number(offset));
+
+ uno::Sequence<beans::PropertyValue> aAttributeList;
+
+ if (xLOTextAttrs.is())
+ aAttributeList = xLOTextAttrs->getRunAttributes(offset, uno::Sequence<OUString>());
+ else
+ aAttributeList = xLOText->getCharacterAttributes(offset, uno::Sequence<OUString>());
+
+ int atspiStartOffset = 0, atspiEndOffset = 0;
+ const auto atspiAttributeList
+ = pAtspiText.getAttributeRun(offset, false, &atspiStartOffset, &atspiEndOffset);
+
+ accessibility::TextSegment aTextSegment
+ = xLOText->getTextAtIndex(offset, accessibility::AccessibleTextType::ATTRIBUTE_RUN);
+
+ /* Handle misspelled text and tracked changes as atktext.cxx does as it affects the run
+ * boundaries. Also check the attributes are properly forwarded. */
+ if (xTextMarkup.is())
+ {
+ const struct
+ {
+ sal_Int32 markupType;
+ const char* atspiAttribute;
+ const char* atspiValue;
+ } aTextMarkupTypes[]
+ = { { text::TextMarkupType::SPELLCHECK, "text-spelling", "misspelled" },
+ { text::TextMarkupType::TRACK_CHANGE_INSERTION, "text-tracked-change",
+ "insertion" },
+ { text::TextMarkupType::TRACK_CHANGE_DELETION, "text-tracked-change",
+ "deletion" },
+ { text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE, "text-tracked-change",
+ "attribute-change" } };
+
+ for (const auto& aTextMarkupType : aTextMarkupTypes)
+ {
+ const auto nTextMarkupCount
+ = xTextMarkup->getTextMarkupCount(aTextMarkupType.markupType);
+ if (nTextMarkupCount <= 0)
+ continue;
+
+ for (auto nTextMarkupIndex = decltype(nTextMarkupCount){ 0 };
+ nTextMarkupIndex < nTextMarkupCount; ++nTextMarkupIndex)
+ {
+ const auto aMarkupTextSegment
+ = xTextMarkup->getTextMarkup(nTextMarkupIndex, aTextMarkupType.markupType);
+ if (aMarkupTextSegment.SegmentStart > offset)
+ {
+ aTextSegment.SegmentEnd
+ = ::std::min(aTextSegment.SegmentEnd, aMarkupTextSegment.SegmentStart);
+ break; // no further iteration.
+ }
+ else if (offset < aMarkupTextSegment.SegmentEnd)
+ {
+ // text markup at <offset>
+ aTextSegment.SegmentStart = ::std::max(aTextSegment.SegmentStart,
+ aMarkupTextSegment.SegmentStart);
+ aTextSegment.SegmentEnd
+ = ::std::min(aTextSegment.SegmentEnd, aMarkupTextSegment.SegmentEnd);
+ // check the attribute is set
+ const auto atspiIter
+ = atspiAttributeList.find(aTextMarkupType.atspiAttribute);
+ CPPUNIT_ASSERT_MESSAGE(offsetMsg.getStr(),
+ atspiIter != atspiAttributeList.end());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(offsetMsg.getStr(),
+ std::string_view(aTextMarkupType.atspiValue),
+ std::string_view(atspiIter->second));
+ break; // no further iteration needed.
+ }
+ else
+ {
+ aTextSegment.SegmentStart
+ = ::std::max(aTextSegment.SegmentStart, aMarkupTextSegment.SegmentEnd);
+ // continue iteration.
+ }
+ }
+ }
+ }
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(offsetMsg.getStr(), aTextSegment.SegmentStart,
+ sal_Int32(atspiStartOffset));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(offsetMsg.getStr(), aTextSegment.SegmentEnd,
+ sal_Int32(atspiEndOffset));
+
+ attributesChecker.check(aAttributeList, atspiAttributeList);
+
+ CPPUNIT_ASSERT_MESSAGE(offsetMsg.getStr(), aTextSegment.SegmentEnd > offset);
+ offset = aTextSegment.SegmentEnd;
+ }
+
+ // loop over each character
+ for (offset = 0; offset < characterCount;)
+ {
+ const auto aTextSegment
+ = xLOText->getTextAtIndex(offset, accessibility::AccessibleTextType::CHARACTER);
+ OString offsetMsg(OString::Concat("in ") + AccessibilityTools::debugString(xLOText).c_str()
+ + " at offset " + OString::number(offset));
+
+ // getCharacterAtOffset()
+ sal_Int32 nChOffset = 0;
+ sal_Int32 cp = aTextSegment.SegmentText.iterateCodePoints(&nChOffset);
+ /* do not check unpaired surrogates, because they are unlikely to make any sense and LO's
+ * GTK VCL doesn't like them */
+ if (!rtl::isSurrogate(cp))
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(offsetMsg.getStr(), cp,
+ pAtspiText.getCharacterAtOffset(offset));
+
+ // getTextAtOffset()
+ const struct
+ {
+ sal_Int16 loTextType;
+ AtspiTextBoundaryType atspiBoundaryType;
+ } textTypeMap[] = {
+ { accessibility::AccessibleTextType::CHARACTER, ATSPI_TEXT_BOUNDARY_CHAR },
+ { accessibility::AccessibleTextType::WORD, ATSPI_TEXT_BOUNDARY_WORD_START },
+ { accessibility::AccessibleTextType::SENTENCE, ATSPI_TEXT_BOUNDARY_SENTENCE_START },
+ { accessibility::AccessibleTextType::LINE, ATSPI_TEXT_BOUNDARY_LINE_START },
+ };
+ for (const auto& pair : textTypeMap)
+ {
+ auto loTextSegment = xLOText->getTextAtIndex(offset, pair.loTextType);
+ const auto atspiTextRange = pAtspiText.getTextAtOffset(offset, pair.atspiBoundaryType);
+
+ // for WORD there's adjustments to be made, see atktext.cxx:adjust_boundaries()
+ if (pair.loTextType == accessibility::AccessibleTextType::WORD
+ && !loTextSegment.SegmentText.isEmpty())
+ {
+ // Determine the start index of the next segment
+ const auto loTextSegmentBehind
+ = xLOText->getTextBehindIndex(loTextSegment.SegmentEnd, pair.loTextType);
+ if (!loTextSegmentBehind.SegmentText.isEmpty())
+ loTextSegment.SegmentEnd = loTextSegmentBehind.SegmentStart;
+ else
+ loTextSegment.SegmentEnd = xLOText->getCharacterCount();
+
+ loTextSegment.SegmentText
+ = xLOText->getTextRange(loTextSegment.SegmentStart, loTextSegment.SegmentEnd);
+ }
+
+ OString boundaryMsg(offsetMsg + " with boundary type "
+ + Atspi::TextBoundaryType::getName(pair.atspiBoundaryType).c_str());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(boundaryMsg.getStr(), loTextSegment.SegmentText,
+ OUString::fromUtf8(atspiTextRange.content));
+ /* if the segment is empty, LO API gives -1 offsets, but maps to 0 for AT-SPI. This is
+ * fine, AT-SPI doesn't really say what the offsets should be when the text is empty */
+ if (!loTextSegment.SegmentText.isEmpty())
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(boundaryMsg.getStr(), loTextSegment.SegmentStart,
+ sal_Int32(atspiTextRange.startOffset));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(boundaryMsg.getStr(), loTextSegment.SegmentEnd,
+ sal_Int32(atspiTextRange.endOffset));
+ }
+ }
+
+ // character bounds
+ const auto loRect = xLOText->getCharacterBounds(offset);
+ auto atspiRect = pAtspiText.getCharacterExtents(offset, ATSPI_COORD_TYPE_WINDOW);
+ atspiRect.x -= atspiPosition.x;
+ atspiRect.y -= atspiPosition.y;
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(offsetMsg.getStr(), loRect.Y, sal_Int32(atspiRect.y));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(offsetMsg.getStr(), loRect.Height,
+ sal_Int32(atspiRect.height));
+ /* for some reason getCharacterBounds() might return negative widths in some cases
+ * (including a space at the end of a right-justified line), and ATK will then adjust
+ * the X and width values to positive to workaround RTL issues (see
+ * https://bugzilla.gnome.org/show_bug.cgi?id=102954), so we work around that */
+ if (loRect.Width < 0)
+ {
+ /* ATK will make x += width; width *= -1, but we don't really want to depend on the
+ * ATK behavior so we allow it to match as well */
+ CPPUNIT_ASSERT_MESSAGE(offsetMsg.getStr(),
+ loRect.X == sal_Int32(atspiRect.x)
+ || loRect.X + loRect.Width == sal_Int32(atspiRect.x));
+ CPPUNIT_ASSERT_MESSAGE(offsetMsg.getStr(),
+ loRect.Width == sal_Int32(atspiRect.width)
+ || -loRect.Width == sal_Int32(atspiRect.width));
+ }
+ else
+ {
+ // normal case
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(offsetMsg.getStr(), loRect.X, sal_Int32(atspiRect.x));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(offsetMsg.getStr(), loRect.Width,
+ sal_Int32(atspiRect.width));
+ }
+
+ // indexAtPoint()
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ offsetMsg.getStr(), xLOText->getIndexAtPoint(awt::Point(loRect.X, loRect.Y)),
+ sal_Int32(pAtspiText.getOffsetAtPoint(
+ atspiPosition.x + loRect.X, atspiPosition.y + loRect.Y, ATSPI_COORD_TYPE_WINDOW)));
+
+ CPPUNIT_ASSERT_MESSAGE(offsetMsg.getStr(), aTextSegment.SegmentEnd > offset);
+ offset = aTextSegment.SegmentEnd;
+ }
+
+ // getRangeExtents() -- ATK doesn't like empty ranges, so only test when not empty
+ if (characterCount > 0)
+ {
+ const auto loRangeBounds = getRangeBounds(xLOText, 0, characterCount);
+ const auto atspiRangeExtents
+ = pAtspiText.getRangeExtents(0, characterCount, ATSPI_COORD_TYPE_WINDOW);
+ CPPUNIT_ASSERT_EQUAL(loRangeBounds.X, sal_Int32(atspiRangeExtents.x - atspiPosition.x));
+ CPPUNIT_ASSERT_EQUAL(loRangeBounds.Y, sal_Int32(atspiRangeExtents.y - atspiPosition.y));
+ CPPUNIT_ASSERT_EQUAL(loRangeBounds.Width, sal_Int32(atspiRangeExtents.width));
+ CPPUNIT_ASSERT_EQUAL(loRangeBounds.Height, sal_Int32(atspiRangeExtents.height));
+ }
+
+ // selection (LO only have one selection, so some of the API doesn't really make sense)
+ CPPUNIT_ASSERT_EQUAL(xLOText->getSelectionEnd() != xLOText->getSelectionStart() ? 1 : 0,
+ pAtspiText.getNSelections());
+
+ const auto atspiSelection = pAtspiText.getSelection(0);
+ CPPUNIT_ASSERT_EQUAL(xLOText->getSelectionStart(), sal_Int32(atspiSelection.startOffset));
+ CPPUNIT_ASSERT_EQUAL(xLOText->getSelectionEnd(), sal_Int32(atspiSelection.endOffset));
+
+ /* We need to take extra care with setSelection() because it could result to scrolling, which
+ * might result in node destruction, which can mess up the parent's children enumeration.
+ * So we only test nodes that are neither the first nor last child in its parent, hoping that
+ * means it won't require scrolling to show the end of the selection. */
+ uno::Reference<accessibility::XAccessibleContext> xLOContext(xLOText, uno::UNO_QUERY_THROW);
+ const auto nIndexInParent = xLOContext->getAccessibleIndexInParent();
+ if (characterCount && nIndexInParent > 0
+ && nIndexInParent + 1 < xLOContext->getAccessibleParent()
+ ->getAccessibleContext()
+ ->getAccessibleChildCount()
+ && pAtspiText.setSelection(0, 0, characterCount))
+ {
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), xLOText->getSelectionStart());
+ CPPUNIT_ASSERT_EQUAL(characterCount, xLOText->getSelectionEnd());
+ // try and restore previous selection, if any
+ CPPUNIT_ASSERT(xLOText->setSelection(std::max(0, atspiSelection.startOffset),
+ std::max(0, atspiSelection.endOffset)));
+ }
+
+ // scrollSubstringTo() is tested in the parent, because it might dispose ourselves otherwise.
+
+ // TODO: more checks here...
+}
+
+#if HAVE_ATSPI2_SCROLL_TO
+// like getFirstRelationTargetOfType() but for Atspi objects
+static Atspi::Accessible
+atspiGetFirstRelationTargetOfType(const Atspi::Accessible& pAtspiAccessible,
+ const AtspiRelationType relationType)
+{
+ for (const auto& rel : pAtspiAccessible.getRelationSet())
+ {
+ if (rel.getRelationType() == relationType && rel.getNTargets() > 0)
+ return rel.getTarget(0);
+ }
+
+ return nullptr;
+}
+#endif // HAVE_ATSPI2_SCROLL_TO
+
+/**
+ * @brief Gets the index of a Writer child hopping through flows-from relationships
+ * @param xContext The accessible context to locate
+ * @returns The index of @c xContext in the flows-from chain
+ *
+ * Gets the index of a child in its parent regardless of whether it is on screen or not.
+ *
+ * @warning This relying on the flows-from relationships, it only works for the connected nodes,
+ * and might not work for e.g. frames.
+ */
+sal_Int64 Atspi2TestTree::swChildIndex(uno::Reference<accessibility::XAccessibleContext> xContext)
+{
+ for (sal_Int64 n = 0;; n++)
+ {
+ auto xPrev = getFirstRelationTargetOfType(
+ xContext, accessibility::AccessibleRelationType::CONTENT_FLOWS_FROM);
+ if (!xPrev.is())
+ return n;
+ xContext = xPrev;
+ }
+}
+
+/**
+ * @brief tests scrolling in Writer.
+ * @param xLOContext The @c XAccessibleContext for the writer document
+ * @param xAtspiAccessible The AT-SPI2 equivalent of @c xLOContext.
+ *
+ * Test scrolling (currently XAccessibleText::scrollSubstringTo()) in Writer.
+ */
+void Atspi2TestTree::testSwScroll(
+ const uno::Reference<accessibility::XAccessibleContext>& xLOContext,
+ const Atspi::Accessible& xAtspiAccessible)
+{
+#if HAVE_ATSPI2_SCROLL_TO
+ /* Currently LO only implements SCROLL_ANYWHERE, so to be sure we need to find something
+ * offscreen and try and bring it in. LO only has implementation for SwAccessibleParagraph,
+ * so we find the last child, and then try and find a FLOWS_TO relationship -- that's a hack
+ * based on how LO exposes offscreen children, e.g. not as "real" children. Once done so, we
+ * have to make sure the child is now on screen, so we should find it in the children list. We
+ * cannot rely on anything we had still being visible, as it could very well have scrolled it to
+ * the top. */
+ assert(accessibility::AccessibleRole::DOCUMENT_TEXT == xLOContext->getAccessibleRole());
+
+ auto nLOChildCount = xLOContext->getAccessibleChildCount();
+ if (nLOChildCount <= 0)
+ return;
+
+ // find the first off-screen text child
+ auto xLONextContext = xLOContext->getAccessibleChild(nLOChildCount - 1)->getAccessibleContext();
+ uno::Reference<accessibility::XAccessibleText> xLONextText;
+ unsigned int nAfterLast = 0;
+ do
+ {
+ xLONextContext = getFirstRelationTargetOfType(
+ xLONextContext, accessibility::AccessibleRelationType::CONTENT_FLOWS_TO);
+ xLONextText.set(xLONextContext, uno::UNO_QUERY);
+ nAfterLast++;
+ } while (xLONextContext.is() && !xLONextText.is());
+
+ if (!xLONextText.is())
+ return; // we have nothing off-screen to scroll to
+
+ // get the global index of the off-screen child so we can match it later
+ auto nLOChildIndex = swChildIndex(xLONextContext);
+
+ // find the corresponding Atspi child to call the API on
+ auto xAtspiNextChild = xAtspiAccessible.getChildAtIndex(nLOChildCount - 1);
+ while (nAfterLast-- > 0 && xAtspiNextChild)
+ xAtspiNextChild
+ = atspiGetFirstRelationTargetOfType(xAtspiNextChild, ATSPI_RELATION_FLOWS_TO);
+ /* the child ought to be found and implement the same interfaces, otherwise there's a problem
+ * in LO <> Atspi child mapping */
+ CPPUNIT_ASSERT(xAtspiNextChild);
+ const auto xAtspiNextText = xAtspiNextChild.queryText();
+
+ // scroll the child into view
+ CPPUNIT_ASSERT(xAtspiNextText.scrollSubstringTo(0, 1, ATSPI_SCROLL_ANYWHERE));
+
+ // now, check that the nLOChildIndex is in the visible area (among the regular children)
+ nLOChildCount = xLOContext->getAccessibleChildCount();
+ CPPUNIT_ASSERT_GREATER(sal_Int64(0), nLOChildCount);
+ const auto nLOFirstChildIndex
+ = swChildIndex(xLOContext->getAccessibleChild(0)->getAccessibleContext());
+
+ CPPUNIT_ASSERT_LESSEQUAL(nLOChildIndex, nLOFirstChildIndex);
+ CPPUNIT_ASSERT_GREATER(nLOChildIndex, nLOFirstChildIndex + nLOChildCount);
+#else // !HAVE_ATSPI2_SCROLL_TO
+ // unused
+ (void)xLOContext;
+ (void)xAtspiAccessible;
+#endif // !HAVE_ATSPI2_SCROLL_TO
+}
diff --git a/vcl/qa/cppunit/a11y/atspi2/atspiwrapper.cxx b/vcl/qa/cppunit/a11y/atspi2/atspiwrapper.cxx
new file mode 100644
index 0000000000..fd80174268
--- /dev/null
+++ b/vcl/qa/cppunit/a11y/atspi2/atspiwrapper.cxx
@@ -0,0 +1,22 @@
+/* -*- 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 "atspiwrapper.hxx"
+
+using namespace Atspi;
+
+Accessible Relation::getTarget(int i) const { return invoke(atspi_relation_get_target, i); }
+
+Component Accessible::queryComponent() const
+{
+ return queryInterface<Component>(atspi_accessible_get_component_iface);
+}
+Text Accessible::queryText() const { return queryInterface<Text>(atspi_accessible_get_text_iface); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/qa/cppunit/a11y/atspi2/atspiwrapper.hxx b/vcl/qa/cppunit/a11y/atspi2/atspiwrapper.hxx
new file mode 100644
index 0000000000..635f9b76d4
--- /dev/null
+++ b/vcl/qa/cppunit/a11y/atspi2/atspiwrapper.hxx
@@ -0,0 +1,784 @@
+/* -*- 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/.
+ */
+
+/* C++ wrapper for libatspi, so to make it less obnoxious to use */
+
+/**
+ * Adding a new wrapper
+ *
+ * To wrap a new Atspi type (let's say, AtspiCollection), you need to:
+ *
+ * 1. Add <tt>DEFINE_GOBJECT_CAST(AtspiCollection, ATSPI_TYPE_COLLECTION)</tt> near the similar
+ * ones. This creates <tt>Atspi::cast<AtspiCollection*>(p)</tt> so that such a cast based on the
+ * C++ type is checked using the GType type system.
+ * 2. Add a declaration for the new wrapper class above Atspi::Accessible
+ * (<tt>class Collection;</tt>) so it can be used in step 3.
+ * 3. Add <tt>Atspi::Accessible::queryCollection()</tt> method. Its definition has to be in the
+ * source file as it requires a complete type for the wrapper class. The body just calls
+ * <tt>queryInterface<Collection>(atspi_accessible_get_collection_iface);</tt> and returns
+ * its value.
+ * 4. Add the definition of the new wrapper class:
+ * <tt>class Collection : public Accessible { ... }</tt>
+ * Use the existing wrappers as inspiration, but basically:
+ * - Define the constructor that only chains up to the parent
+ * - Define each wrapper method, which generally only have to call one of the <tt>invoke()</tt>
+ * helpers to wrap the C calls. There are a few, depending on some details of the C call:
+ * - @c GObjectWrapperBase::invoke(): this is the most basic one, that just calls the C method
+ * on @c GObjectWrapperBase::get() with the given arguments. Use this for calls not
+ * throwing an exception and either returning a plain value, or something not handled by
+ * one of the others below.
+ * - @c AtspiWrapperBase::invokeThrow(): like @c GObjectWrapperBase::invoke(), but for C calls
+ * that take a @c GError argument for throwing exceptions. @c invokeThrow() will
+ * transform any C exception into a a C++ exception (@c css::uno::RuntimeException)
+ * - @c AtspiWrapperBase::strInvoke(): like @c AtspiWrapperBase::invokeThrow(), but manages a
+ * C string (@c char*) return value as an @c std::string. Use this for C calls returning
+ * a C string.
+ * - @c AtspiWrapperBase::garrayInvoke(): like @c AtspiWrapperBase::invokeThrow(), but manages
+ * a @c GArray return value as an @c std::vector. Use this for C calls returning a
+ * @p GArray.
+ * - @c AtspiWrapperBase::hashMapInvoke(): like @c AtspiWrapperBase::invokeThrow(), but manages
+ * a @c GHashTable return value as an @c std::unordered_map. Use this for C calls
+ * returning a @p GHashTable.
+ * - @c AtspiWrapperBase::strHashMapInvoke(): identical to @c AtspiWrapperBase::hashMapInvoke()
+ * using C strings for keys and values.
+ * .
+ * If none of those match the exact return type of the C call to wrap, use
+ * @c AtspiWrapperBase::invokeThrow() or even @c GObjectWrapperBase::invoke(), and manually
+ * manage the result value. You can use Atspi::gmem functions to help. Basically the idea is
+ * that you always return a self-managing object to make memory management easy (whereas it's
+ * obnoxiously hard with plain C Atspi API).
+ */
+
+#pragma once
+
+#include <vector>
+#include <unordered_map>
+#include <boost/type_traits/function_traits.hpp>
+
+#include <atspi/atspi.h>
+#include <com/sun/star/container/NoSuchElementException.hpp>
+#include <com/sun/star/uno/RuntimeException.hpp>
+
+#include <cppunit/TestAssert.h>
+
+#include <config_atspi.h>
+
+namespace Atspi
+{
+/** @brief Helpers for managing GLib memory in a more C++-style */
+namespace gmem
+{
+/** @brief Wraps a pointer to free with @c g_free() in a @c std::unique_ptr */
+template <typename T> static inline auto unique_gmem(T* ptr)
+{
+ return std::unique_ptr<T, decltype(&g_free)>(ptr, &g_free);
+}
+
+/** @brief Wraps a @c GArray to free with @c g_array_unref() in a @c std::unique_ptr */
+static inline auto unique_garray(GArray* p)
+{
+ return std::unique_ptr<GArray, decltype(&g_array_unref)>(p, &g_array_unref);
+}
+
+/** @brief Wraps a @c GHashTable to free with @c g_hash_table_unref() in a @c std::unique_ptr */
+static inline auto unique_ghashtable(GHashTable* p)
+{
+ return std::unique_ptr<GHashTable, decltype(&g_hash_table_unref)>(p, &g_hash_table_unref);
+}
+}
+
+// --- GObject cast wrappers based on type: usage is cast<AtspiAccessible*>(pCInstance)
+#define DEFINE_GOBJECT_CAST(CType, GType) \
+ template <typename P, typename T, std::enable_if_t<std::is_same_v<P, CType*>, int> = 1> \
+ P cast(T* pInstance) \
+ { \
+ return G_TYPE_CHECK_INSTANCE_CAST(pInstance, GType, std::remove_pointer_t<P>); \
+ }
+
+DEFINE_GOBJECT_CAST(AtspiStateSet, ATSPI_TYPE_STATE_SET)
+DEFINE_GOBJECT_CAST(AtspiRelation, ATSPI_TYPE_RELATION)
+DEFINE_GOBJECT_CAST(AtspiAccessible, ATSPI_TYPE_ACCESSIBLE)
+DEFINE_GOBJECT_CAST(AtspiComponent, ATSPI_TYPE_COMPONENT)
+DEFINE_GOBJECT_CAST(AtspiText, ATSPI_TYPE_TEXT)
+
+#undef DEFINE_GOBJECT_CAST
+// --- end GObject cast wrappers
+
+class GLibEnumBase
+{
+protected:
+ /**
+ * @brief Retrieves the string representation of an enumeration value
+ * @param gt The @c GType for the enumeration
+ * @param value The enumeration value for which to get the name for
+ * @param fallback Fallback value in case @p values falls outside the enumeration
+ * @returns A string representing @p value
+ */
+ static std::string glibEnumValueName(GType gt, gint value,
+ std::string_view fallback = "unknown")
+ {
+ auto klass = static_cast<GEnumClass*>(g_type_class_ref(gt));
+ auto enum_value = g_enum_get_value(klass, value);
+ std::string ret(enum_value ? enum_value->value_name : fallback);
+ g_type_class_unref(klass);
+ return ret;
+ }
+};
+
+class Role : private GLibEnumBase
+{
+public:
+ static std::string getName(AtspiRole role)
+ {
+ return glibEnumValueName(atspi_role_get_type(), role);
+ }
+};
+
+class State : private GLibEnumBase
+{
+public:
+ static std::string getName(AtspiStateType state)
+ {
+ return glibEnumValueName(atspi_state_type_get_type(), state);
+ }
+};
+
+class TextGranularity : private GLibEnumBase
+{
+public:
+ static std::string getName(AtspiTextGranularity granularity)
+ {
+ return glibEnumValueName(atspi_text_granularity_get_type(), granularity);
+ }
+};
+
+class TextBoundaryType : private GLibEnumBase
+{
+public:
+ static std::string getName(AtspiTextBoundaryType boundaryType)
+ {
+ return glibEnumValueName(atspi_text_boundary_type_get_type(), boundaryType);
+ }
+};
+
+/**
+ * @brief Base class for GObject wrappers
+ *
+ * This leverages std::shared_ptr as a cheap way of wrapping a raw pointer, and its deleter as a
+ * mean of using g_object_unref() to cleanup. This is sub-optimal as it maintains a separate
+ * refcount to the GObject one, but it's easy.
+ */
+template <class T> class GObjectWrapperBase : public std::shared_ptr<T>
+{
+public:
+ /* this is the boundary of C++ type safety, so we can have inheritance working
+ * properly with the C types. This should still be safe as it uses cast() which should be
+ * defined for each using type with DEFINE_GOBJECT_CAST(), which uses GType validation */
+ template <typename P = T*> P get() const { return cast<P>(std::shared_ptr<T>::get()); }
+
+protected:
+ /**
+ * @brief Calls the C function @p f on the C object wrapped by @p this
+ * @param f The C function to call
+ * @param args Additional arguments to @p f
+ * @returns The return value from @p f
+ *
+ * Calls the C function @p f similar to <tt>f(get(), args...)</tt>. Care is taken of
+ * transforming @c get() to the type actually expected as the first argument of @p f, using
+ * @c get<TypeOfFsFirstArgument>(), which performs a runtime verification of the conversion.
+ *
+ * @note The type verification on whether @p f actually takes what get() returns is performed
+ * at runtime, so there will be no compilation error or warning if trying to use an
+ * incompatible C function. A check will however be performed at runtime, at least
+ * helping diagnose a possible invalid conversion.
+ */
+ template <typename F, typename... Ts> inline auto invoke(F f, Ts... args) const
+ {
+ using FT = std::remove_pointer_t<F>;
+ const auto p = get<typename boost::function_traits<FT>::arg1_type>();
+ return f(p, args...);
+ }
+
+private:
+ static void deleter(T* p)
+ {
+ if (p)
+ g_object_unref(p);
+ }
+
+public:
+ /**
+ * @param pObj The raw GObject to wrap
+ * @param takeRef Whether to take ownership of the object or not. If set to @c false, it will
+ * call @c g_object_ref(pAcc) to acquire a new reference to the GObject.
+ */
+ GObjectWrapperBase(T* pObj = nullptr, bool takeRef = true)
+ : std::shared_ptr<T>(pObj, deleter)
+ {
+ if (pObj && !takeRef)
+ g_object_ref(pObj);
+ }
+};
+
+/** @brief AtspiStateSet C++ wrapper */
+class StateSet : public GObjectWrapperBase<AtspiStateSet>
+{
+public:
+ using GObjectWrapperBase::GObjectWrapperBase;
+
+ void add(const AtspiStateType t) { invoke(atspi_state_set_add, t); }
+ StateSet compare(const StateSet& other) const
+ {
+ return StateSet(invoke(atspi_state_set_compare, other.get()));
+ }
+ bool contains(const AtspiStateType t) const { return invoke(atspi_state_set_contains, t); }
+ bool operator==(const StateSet& other) const
+ {
+ return invoke(atspi_state_set_equals, other.get());
+ }
+ std::vector<AtspiStateType> getStates() const
+ {
+ auto garray = gmem::unique_garray(invoke(atspi_state_set_get_states));
+ std::vector<AtspiStateType> states;
+ for (auto i = decltype(garray->len){ 0 }; i < garray->len; i++)
+ {
+ states.push_back(g_array_index(garray, decltype(states)::value_type, i));
+ }
+ return states;
+ }
+ bool empty() const { return invoke(atspi_state_set_is_empty); }
+ void remove(AtspiStateType t) { invoke(atspi_state_set_remove, t); }
+ void setByName(const std::string_view name, bool enable)
+ {
+ invoke(atspi_state_set_set_by_name, name.data(), enable);
+ }
+};
+
+class Accessible;
+
+/** @brief AtspiRelation C++ wrapper */
+class Relation : public GObjectWrapperBase<AtspiRelation>
+{
+public:
+ using GObjectWrapperBase::GObjectWrapperBase;
+
+ AtspiRelationType getRelationType() const { return invoke(atspi_relation_get_relation_type); }
+ int getNTargets() const { return invoke(atspi_relation_get_n_targets); }
+ Accessible getTarget(int i) const;
+};
+
+/* intermediate base just for splitting out the *invoke* helpers implementations, so the actual
+ * user-targeted class can hold only the actual API */
+template <class T> class AtspiWrapperBase : public GObjectWrapperBase<T>
+{
+protected:
+ using GObjectWrapperBase<T>::invoke;
+
+ /**
+ * @brief Calls the throwing C function @p f on the C object wrapped by @p this
+ * @param f The C function to call
+ * @param args Additional arguments to @p f
+ * @returns The raw return value from @p f
+ * @throws css::uno::RuntimeException Exception @c GError are translated to
+ *
+ * This wrapper calls @p f with parameters @p args and an additional @c GError parameter to
+ * catch C exception, transforming them into C++ exceptions of type
+ * @c css::uno::RuntimeException.
+ *
+ * @see invoke()
+ */
+ template <typename F, typename... Ts> inline auto invokeThrow(F f, Ts... args) const
+ {
+ GError* err = nullptr;
+ auto ret = invoke(f, args..., &err);
+ if (err)
+ {
+ throw css::uno::RuntimeException(OUString::fromUtf8(err->message));
+ }
+ return ret;
+ }
+
+ /**
+ * @brief Calls the throwing C function @p f on the C object wrapped by @p this and returns a string
+ * @param f The C function to call
+ * @param args Additional arguments to @p f
+ * @tparam E the type of exception to throw if @p f returns @c null, defaults to
+ * @c css::uno::RuntimeException
+ * @returns A string holding the return value from @p f
+ * @throws css::uno::RuntimeException See invokeThrow()
+ * @throws E Exception to use if @p f returns null with no other error
+ *
+ * Just like @c invokeThrow(), but wraps the return value in an @c std::string and manages the
+ * lifetime of the C function return value.
+ *
+ * As @c std::string cannot represent a @c null value, if @p f returned such a value without
+ * throwing an exception, this method will throw an exception of type @p E
+ *
+ * @see invokeThrow()
+ */
+ template <typename E = css::uno::RuntimeException, typename F, typename... Ts>
+ inline std::string strInvoke(F f, Ts... args) const
+ {
+ auto r = invokeThrow(f, args...);
+
+ /* if the API returned NULL without throwing, use the specified exception because a nullptr
+ * std::string is not valid, and std::logic_error isn't gonna be very useful to the caller */
+ if (!r)
+ throw E();
+
+ return gmem::unique_gmem(r).get();
+ }
+
+ /**
+ * @brief Calls the throwing C function @p f on the C object wrapped by @p this and returns a vector
+ * @param f The C function to call
+ * @param args Additional arguments to @p f
+ * @tparam Vi The type of the members of the @c GArray @p f returns
+ * @tparam Vo The type of the members of the returned vector
+ * @returns A vector holding the return value from @p f
+ * @throws css::uno::RuntimeException See invokeThrow()
+ *
+ * Just like @c invokeThrow(), but wraps the return in an @c std::vector<Vo> and manages the
+ * lifetime of the C function return value.
+ *
+ * @p Vi has to be implicitly convertible to @p Vo. A typical usage could be
+ * <tt>garrayInvoke<AtspiAccessible*, Accessible>(...)</tt>, which would transform a @c GArray
+ * of @c AtspiAccessible* to an @c std::vector of @c Atspi::Accessible.
+ *
+ * @warning You have to get @p Vi right, there is no way to validate this type is correct or
+ * not, so you won't get a compilation error nor even a warning if you give the wrong
+ * type here.
+ *
+ * @see invokeThrow()
+ */
+ template <typename Vi, typename Vo, typename F, typename... Ts>
+ inline std::vector<Vo> garrayInvoke(F f, Ts... args) const
+ {
+ auto garray = gmem::unique_garray(invokeThrow(f, args...));
+ std::vector<Vo> vec;
+ for (auto i = decltype(garray->len){ 0 }; i < garray->len; i++)
+ vec.push_back(g_array_index(garray, Vi, i));
+ return vec;
+ }
+
+ /**
+ * @brief Wraps an AT-SPI call returning a GHashTable
+ * @tparam Ki Type of the keys in the wrapped hash table
+ * @tparam Vi Type of the values in the wrapped hash table
+ * @tparam Ko Type of the keys in the wrapper map (this must be convertible from Ki)
+ * @tparam Vo Type of the values in the wrapper map (this must be convertible from Kv)
+ * @param f The function to call
+ * @param args Arguments to pass to @p f
+ * @returns A @c std::unordered_map holding the data returned by @p f.
+ *
+ * @see invokeThrow()
+ * @see strHashMapInvoke()
+ */
+ template <typename Ki, typename Vi, typename Ko, typename Vo, typename F, typename... Ts>
+ inline std::unordered_map<Ko, Vo> hashMapInvoke(F f, Ts... args) const
+ {
+ auto ghash = gmem::unique_ghashtable(invokeThrow(f, args...));
+ std::unordered_map<Ko, Vo> map;
+ GHashTableIter iter;
+ g_hash_table_iter_init(&iter, ghash.get());
+ gpointer key, value;
+ while (g_hash_table_iter_next(&iter, &key, &value))
+ {
+ map.emplace(static_cast<Ki>(key), static_cast<Vi>(value));
+ }
+ return map;
+ }
+
+ /**
+ * @brief Just like @c hashMapInvoke() but already specialized for strings
+ * @param f The C function to call
+ * @param args Arguments to @p f
+ * @returns A @c std::unordered_map holding the data returned by @p f
+ *
+ * This is exactly the same as
+ * <tt>hashMapInvoke<gchar*, gchar*, std::string, std::string>(f, args...)</tt>
+ */
+ template <typename F, typename... Ts> inline auto strHashMapInvoke(F f, Ts... args) const
+ {
+ return hashMapInvoke<gchar*, gchar*, std::string, std::string>(f, args...);
+ }
+
+public:
+ using GObjectWrapperBase<T>::GObjectWrapperBase;
+};
+
+class Component;
+class Text;
+
+/**
+ * @brief AtspiAccessible C++ wrapper
+ *
+ * This is a wrapper for the AtspiAccessible GObject class to make it a bit nicer to use in C++,
+ * including a proper class with methods, regular exceptions, easy memory management, and an
+ * iterator to enumerate children.
+ *
+ * As this class actually inherits from std::shared_ptr, you can easily use @c get() to retrieve
+ * the wrapped pointer in case you need to, e.g. if some specific API is missing. However, take
+ * care of memory management on that object not to have it destroyed early (e.g. don't let anyone
+ * call @c g_object_unref() on it if they didn't call g_object_ref() first).
+ *
+ * To use it, just wrap an initial AtspiAccessible using the class constructor.
+ * @code
+ * Atspi::Accessible desktop(atspi_get_desktop(0));
+ * for (auto&& app: desktop) {
+ * std::cout << app->getName() << std::endl;
+ * }
+ * @endcode
+ *
+ * For details on the specific methods, see the C Atspi documentation.
+ */
+class Accessible : public AtspiWrapperBase<AtspiAccessible>
+{
+public:
+ using AtspiWrapperBase<AtspiAccessible>::AtspiWrapperBase;
+
+ void setCacheMask(AtspiCache mask) const { invoke(atspi_accessible_set_cache_mask, mask); }
+ void clearCache() const { invoke(atspi_accessible_clear_cache); }
+
+ AtspiRole getRole() const { return invokeThrow(atspi_accessible_get_role); }
+ std::string getRoleName() const { return strInvoke(atspi_accessible_get_role_name); }
+ std::string getName() const { return strInvoke(atspi_accessible_get_name); }
+ std::string getDescription() const { return strInvoke(atspi_accessible_get_description); }
+
+ int getChildCount() const { return invokeThrow(atspi_accessible_get_child_count); }
+ Accessible getChildAtIndex(int idx) const
+ {
+ return Accessible(invokeThrow(atspi_accessible_get_child_at_index, idx));
+ }
+ int getIndexInParent() const { return invokeThrow(atspi_accessible_get_index_in_parent); }
+ Accessible getParent() const { return Accessible(invokeThrow(atspi_accessible_get_parent)); }
+
+ StateSet getStateSet() const { return StateSet(invoke(atspi_accessible_get_state_set)); }
+
+ std::unordered_map<std::string, std::string> getAttributes() const
+ {
+ return strHashMapInvoke(atspi_accessible_get_attributes);
+ }
+
+ std::vector<Relation> getRelationSet() const
+ {
+ return garrayInvoke<AtspiRelation*, Relation>(atspi_accessible_get_relation_set);
+ }
+
+private:
+ template <class I, typename F> I queryInterface(F f) const
+ {
+ auto pIface = invoke(f);
+ if (pIface)
+ return I(pIface);
+ throw css::uno::RuntimeException("Not implemented");
+ }
+
+public:
+ Component queryComponent() const;
+ Text queryText() const;
+
+ // convenience extensions
+ class iterator
+ {
+ public:
+ using iterator_category = std::input_iterator_tag;
+ using value_type = Accessible;
+ using difference_type = std::ptrdiff_t;
+ using reference = value_type&;
+ using pointer = value_type*;
+
+ private:
+ const Accessible* m_pAccessible;
+ int m_idx;
+
+ public:
+ explicit iterator(const Accessible* pAccessible, int idx = 0)
+ : m_pAccessible(pAccessible)
+ , m_idx(idx)
+ {
+ }
+
+ iterator(iterator const& other)
+ : m_pAccessible(other.m_pAccessible)
+ , m_idx(other.m_idx)
+ {
+ }
+
+ iterator& operator++()
+ {
+ m_idx++;
+ return *this;
+ }
+
+ iterator operator++(int)
+ {
+ iterator other = *this;
+ ++(*this);
+ return other;
+ }
+
+ bool operator==(iterator other) const
+ {
+ return m_idx == other.m_idx && m_pAccessible == other.m_pAccessible;
+ }
+ bool operator!=(iterator other) const { return !(*this == other); }
+ value_type operator*() const
+ {
+ assert(m_idx < m_pAccessible->getChildCount());
+ return m_pAccessible->getChildAtIndex(m_idx);
+ }
+ };
+
+ iterator begin() const { return iterator(this); }
+ iterator end() const
+ {
+ if (!get())
+ return iterator(this);
+ return iterator(this, std::max(0, getChildCount()));
+ }
+};
+
+// we just use ATSPI's own structure here, but pass it by value
+using Rect = AtspiRect;
+using Point = AtspiPoint;
+
+/** @brief AtspiComponent C++ wrapper */
+class Component : public Accessible
+{
+public:
+ Component(AtspiComponent* pObj = nullptr, bool takeRef = true)
+ : Accessible(cast<AtspiAccessible*>(pObj), takeRef)
+ {
+ }
+
+ bool contains(int x, int y, AtspiCoordType coordType) const
+ {
+ return invokeThrow(atspi_component_contains, x, y, coordType);
+ }
+ Accessible getAccessibleAtPoint(int x, int y, AtspiCoordType coordType) const
+ {
+ return invokeThrow(atspi_component_get_accessible_at_point, x, y, coordType);
+ }
+ Rect getExtents(AtspiCoordType coordType) const
+ {
+ return *gmem::unique_gmem(invokeThrow(atspi_component_get_extents, coordType));
+ }
+ Point getPosition(AtspiCoordType coordType) const
+ {
+ return *gmem::unique_gmem(invokeThrow(atspi_component_get_position, coordType));
+ }
+ Point getSize() const { return *gmem::unique_gmem(invokeThrow(atspi_component_get_size)); }
+ AtspiComponentLayer getLayer() const { return invokeThrow(atspi_component_get_layer); }
+ short getMdiZOrder() const { return invokeThrow(atspi_component_get_mdi_z_order); }
+ bool grabFocus() const { return invokeThrow(atspi_component_grab_focus); }
+ double getAlpha() const { return invokeThrow(atspi_component_get_alpha); }
+
+#if HAVE_ATSPI2_SCROLL_TO
+ bool scrollTo(AtspiScrollType scrollType) const
+ {
+ return invokeThrow(atspi_component_scroll_to, scrollType);
+ }
+ bool scrollToPoint(AtspiCoordType coordType, int x, int y) const
+ {
+ return invokeThrow(atspi_component_scroll_to_point, coordType, x, y);
+ }
+#endif // HAVE_ATSPI2_SCROLL_TO
+};
+
+/** @brief AtspiText C++ wrapper */
+class Text : public Accessible
+{
+public:
+ Text(AtspiText* pObj = nullptr, bool takeRef = true)
+ : Accessible(cast<AtspiAccessible*>(pObj), takeRef)
+ {
+ }
+
+ /** Wrapper for AtspiRange
+ *
+ * This is not actually required, but helps make this more C++-y (by allowing TextRange to
+ * inherit it) and more LibreOffice-y (by having camelCase names) */
+ struct Range
+ {
+ int startOffset;
+ int endOffset;
+
+ Range(int startOffset_, int endOffset_)
+ : startOffset(startOffset_)
+ , endOffset(endOffset_)
+ {
+ }
+
+ Range(const AtspiRange* r)
+ : startOffset(r->start_offset)
+ , endOffset(r->end_offset)
+ {
+ }
+ };
+
+ /** Wrapper for AtspiTextRange */
+ struct TextRange : Range
+ {
+ std::string content;
+
+ TextRange(int startOffset_, int endOffset_, std::string_view content_)
+ : Range(startOffset_, endOffset_)
+ , content(content_)
+ {
+ }
+
+ TextRange(const AtspiTextRange* r)
+ : Range(r->start_offset, r->end_offset)
+ , content(r->content)
+ {
+ }
+ };
+
+ int getCharacterCount() const { return invokeThrow(atspi_text_get_character_count); }
+ std::string getText(int startOffset, int endOffset) const
+ {
+ return strInvoke(atspi_text_get_text, startOffset, endOffset);
+ }
+ int getCaretOffset() const { return invokeThrow(atspi_text_get_caret_offset); }
+
+ std::unordered_map<std::string, std::string> getTextAttributes(int offset, int* startOffset,
+ int* endOffset) const
+ {
+ return strHashMapInvoke(atspi_text_get_text_attributes, offset, startOffset, endOffset);
+ }
+
+ std::unordered_map<std::string, std::string>
+ getAttributeRun(int offset, bool includeDefaults, int* startOffset, int* endOffset) const
+ {
+ return strHashMapInvoke(atspi_text_get_attribute_run, offset, includeDefaults, startOffset,
+ endOffset);
+ }
+
+ std::unordered_map<std::string, std::string> getDefaultAttributes() const
+ {
+ return strHashMapInvoke(atspi_text_get_default_attributes);
+ }
+
+ std::string getTextAttributeValue(int offset, std::string_view name) const
+ {
+ return strInvoke<css::container::NoSuchElementException>(
+ atspi_text_get_text_attribute_value, offset, const_cast<char*>(name.data()));
+ }
+
+protected:
+ /** Like @c invokeThrow() on C calls returning an @c AtspiTextRange */
+ template <typename F, typename... Ts> inline TextRange invokeTextRange(F f, Ts... args) const
+ {
+ struct deleter
+ {
+ void operator()(AtspiTextRange* ptr)
+ {
+ g_free(ptr->content);
+ g_free(ptr);
+ }
+ };
+
+ std::unique_ptr<AtspiTextRange, deleter> r(invokeThrow(f, args...));
+ return r.get();
+ }
+
+public:
+ TextRange getStringAtOffset(int offset, AtspiTextGranularity granularity) const
+ {
+ return invokeTextRange(atspi_text_get_string_at_offset, offset, granularity);
+ }
+
+ /* the next 3 are deprecated, but LO doesn't implement getStringAtOffset() itself so it's a lot
+ * trickier to test for */
+ TextRange getTextBeforeOffset(int offset, AtspiTextBoundaryType boundary) const
+ {
+ return invokeTextRange(atspi_text_get_text_before_offset, offset, boundary);
+ }
+ TextRange getTextAtOffset(int offset, AtspiTextBoundaryType boundary) const
+ {
+ return invokeTextRange(atspi_text_get_text_at_offset, offset, boundary);
+ }
+ TextRange getTextAfterOffset(int offset, AtspiTextBoundaryType boundary) const
+ {
+ return invokeTextRange(atspi_text_get_text_after_offset, offset, boundary);
+ }
+
+ sal_Int32 getCharacterAtOffset(int offset) const
+ {
+ return invokeThrow(atspi_text_get_character_at_offset, offset);
+ }
+
+ Rect getCharacterExtents(int offset, AtspiCoordType type) const
+ {
+ return *gmem::unique_gmem(invokeThrow(atspi_text_get_character_extents, offset, type));
+ }
+
+ int getOffsetAtPoint(int x, int y, AtspiCoordType type) const
+ {
+ return invokeThrow(atspi_text_get_offset_at_point, x, y, type);
+ }
+
+ Rect getRangeExtents(int startOffset, int endOffset, AtspiCoordType type) const
+ {
+ return *gmem::unique_gmem(
+ invokeThrow(atspi_text_get_range_extents, startOffset, endOffset, type));
+ }
+
+ // getBoundedRanges() ?
+
+ int getNSelections() const { return invokeThrow(atspi_text_get_n_selections); }
+
+ Range getSelection(gint selectionNum) const
+ {
+ return gmem::unique_gmem(invokeThrow(atspi_text_get_selection, selectionNum)).get();
+ }
+
+ bool addSelection(int startOffset, int endOffset) const
+ {
+ return invokeThrow(atspi_text_add_selection, startOffset, endOffset);
+ }
+
+ bool removeSelection(int selectionNum) const
+ {
+ return invokeThrow(atspi_text_remove_selection, selectionNum);
+ }
+
+ bool setSelection(int selectionNum, int startOffset, int endOffset) const
+ {
+ return invokeThrow(atspi_text_set_selection, selectionNum, startOffset, endOffset);
+ }
+
+#if HAVE_ATSPI2_SCROLL_TO
+ bool scrollSubstringTo(int startOffset, int endOffset, AtspiScrollType type) const
+ {
+ return invokeThrow(atspi_text_scroll_substring_to, startOffset, endOffset, type);
+ }
+
+ bool scrollSubstringToPoint(int startOffset, int endOffset, AtspiCoordType coords, gint x,
+ gint y) const
+ {
+ return invokeThrow(atspi_text_scroll_substring_to_point, startOffset, endOffset, coords, x,
+ y);
+ }
+#endif // HAVE_ATSPI2_SCROLL_TO
+};
+}
+
+// CppUnit integration
+CPPUNIT_NS_BEGIN
+template <> struct assertion_traits<AtspiRole>
+{
+ static bool equal(const AtspiRole a, const AtspiRole b) { return a == b; }
+
+ static std::string toString(const AtspiRole role) { return Atspi::Role::getName(role); }
+};
+CPPUNIT_NS_END
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/qa/cppunit/a11y/atspi2/testdocuments/ecclectic.fodt b/vcl/qa/cppunit/a11y/atspi2/testdocuments/ecclectic.fodt
new file mode 100644
index 0000000000..e67ded542f
--- /dev/null
+++ b/vcl/qa/cppunit/a11y/atspi2/testdocuments/ecclectic.fodt
@@ -0,0 +1,258 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:creation-date>2023-04-25T17:37:52.159526391</meta:creation-date><dc:date>2023-06-14T10:55:24.745981841</dc:date><meta:editing-duration>P6DT23H42M46S</meta:editing-duration><meta:editing-cycles>21</meta:editing-cycles><meta:generator>LibreOfficeDev/7.7.0.0.alpha0$Linux_X86_64 LibreOffice_project/713f814ae7d4ab409dfdaa4d432d2fb61122e811</meta:generator><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="2" meta:paragraph-count="11" meta:word-count="501" meta:character-count="3116" meta:non-whitespace-character-count="2626"/></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="DejaVu Sans" svg:font-family="'DejaVu Sans'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="FreeSans1" svg:font-family="FreeSans" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/>
+ <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman"/>
+ <style:font-face style:name="Liberation Serif1" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif1" fo:font-size="12pt" fo:language="fr" fo:country="FR" style:letter-kerning="true" style:font-name-asian="DejaVu Sans" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="FreeSans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="page"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif1" fo:font-size="12pt" fo:language="fr" fo:country="FR" style:letter-kerning="true" style:font-name-asian="DejaVu Sans" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="FreeSans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" fo:keep-with-next="always"/>
+ <style:text-properties style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt" style:font-name-asian="DejaVu Sans" style:font-family-asian="'DejaVu Sans'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="FreeSans1" style:font-family-complex="FreeSans" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
+ </style:style>
+ <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
+ <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.247cm" style:contextual-spacing="false" fo:line-height="115%"/>
+ </style:style>
+ <style:style style:name="Heading_20_3" style:display-name="Heading 3" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="3" style:list-style-name="" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.247cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false"/>
+ <style:text-properties style:font-name="Liberation Serif1" fo:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="14pt" fo:font-weight="bold" style:font-name-asian="DejaVu Sans" style:font-family-asian="'DejaVu Sans'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-weight-asian="bold" style:font-name-complex="FreeSans1" style:font-family-complex="FreeSans" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt" style:font-weight-complex="bold"/>
+ </style:style>
+ <style:style style:name="Strong_20_Emphasis" style:display-name="Strong Emphasis" style:family="text">
+ <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
+ </style:style>
+ <style:style style:name="Line_20_numbering" style:display-name="Line numbering" style:family="text"/>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:style-name="Line_20_numbering" text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
+ <loext:theme loext:name="Office Theme">
+ <loext:theme-colors loext:name="LibreOffice">
+ <loext:color loext:name="dark1" loext:color="#000000"/>
+ <loext:color loext:name="light1" loext:color="#ffffff"/>
+ <loext:color loext:name="dark2" loext:color="#000000"/>
+ <loext:color loext:name="light2" loext:color="#ffffff"/>
+ <loext:color loext:name="accent1" loext:color="#18a303"/>
+ <loext:color loext:name="accent2" loext:color="#0369a3"/>
+ <loext:color loext:name="accent3" loext:color="#a33e03"/>
+ <loext:color loext:name="accent4" loext:color="#8e03a3"/>
+ <loext:color loext:name="accent5" loext:color="#c99c00"/>
+ <loext:color loext:name="accent6" loext:color="#c9211e"/>
+ <loext:color loext:name="hyperlink" loext:color="#0000ee"/>
+ <loext:color loext:name="followed-hyperlink" loext:color="#551a8b"/>
+ </loext:theme-colors>
+ </loext:theme>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Text_20_body">
+ <style:paragraph-properties fo:margin-left="1cm" fo:margin-right="0cm" fo:line-height="200%" fo:text-align="justify" style:justify-single-word="false" fo:text-indent="1cm" style:auto-text-indent="false" style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="P2" style:family="paragraph" style:parent-style-name="Text_20_body">
+ <style:text-properties style:font-name="Liberation Serif1"/>
+ </style:style>
+ <style:style style:name="T1" style:family="text">
+ <style:text-properties fo:font-style="italic" style:font-style-asian="italic" style:font-style-complex="italic"/>
+ </style:style>
+ <style:style style:name="T2" style:family="text">
+ <style:text-properties fo:font-variant="small-caps"/>
+ </style:style>
+ <style:style style:name="T3" style:family="text">
+ <style:text-properties style:font-relief="engraved"/>
+ </style:style>
+ <style:style style:name="T4" style:family="text">
+ <style:text-properties style:text-overline-style="solid" style:text-overline-type="double" style:text-overline-width="auto" style:text-overline-color="font-color"/>
+ </style:style>
+ <style:style style:name="T5" style:family="text">
+ <style:text-properties style:text-outline="true" style:text-blinking="true"/>
+ </style:style>
+ <style:style style:name="T6" style:family="text">
+ <style:text-properties style:text-rotation-angle="90" style:text-rotation-scale="line-height"/>
+ </style:style>
+ <style:style style:name="T7" style:family="text">
+ <style:text-properties fo:letter-spacing="0.194cm"/>
+ </style:style>
+ <style:style style:name="T8" style:family="text">
+ <style:text-properties style:text-position="super 58%" fo:background-color="transparent" loext:char-shading-value="0"/>
+ </style:style>
+ <style:style style:name="T9" style:family="text">
+ <style:text-properties style:text-position="sub 58%"/>
+ </style:style>
+ <style:style style:name="T10" style:family="text">
+ <style:text-properties fo:background-color="#ff6600" loext:char-shading-value="0"/>
+ </style:style>
+ <style:style style:name="T11" style:family="text">
+ <style:text-properties fo:background-color="transparent" loext:char-shading-value="0" loext:shadow="#808080 0.176cm 0.176cm"/>
+ </style:style>
+ <style:style style:name="T12" style:family="text">
+ <style:text-properties style:font-name="Liberation Serif"/>
+ </style:style>
+ <style:style style:name="T13" style:family="text">
+ <style:text-properties style:font-name="Liberation Serif1"/>
+ </style:style>
+ <style:style style:name="T14" style:family="text">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="T15" style:family="text">
+ <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
+ </style:style>
+ <style:style style:name="Sect1" style:family="section">
+ <style:section-properties fo:margin-left="0cm" fo:margin-right="0cm" style:editable="false">
+ <style:columns fo:column-count="1" fo:column-gap="0cm"/>
+ </style:section-properties>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text text:use-soft-page-breaks="true">
+ <text:tracked-changes text:track-changes="false">
+ <text:changed-region xml:id="ct93860566703600" text:id="ct93860566703600">
+ <text:deletion>
+ <office:change-info>
+ <dc:creator>Unknown Author</dc:creator>
+ <dc:date>2023-06-14T10:49:34</dc:date>
+ </office:change-info>
+ </text:deletion>
+ </text:changed-region>
+ <text:changed-region xml:id="ct93860566704768" text:id="ct93860566704768">
+ <text:insertion>
+ <office:change-info>
+ <dc:creator>Unknown Author</dc:creator>
+ <dc:date>2023-06-14T10:49:34</dc:date>
+ </office:change-info>
+ </text:insertion>
+ </text:changed-region>
+ <text:changed-region xml:id="ct93860566705104" text:id="ct93860566705104">
+ <text:deletion>
+ <office:change-info>
+ <dc:creator>Unknown Author</dc:creator>
+ <dc:date>2023-06-14T10:49:46</dc:date>
+ </office:change-info>
+ </text:deletion>
+ </text:changed-region>
+ <text:changed-region xml:id="ct93860566691808" text:id="ct93860566691808">
+ <text:insertion>
+ <office:change-info>
+ <dc:creator>Unknown Author</dc:creator>
+ <dc:date>2023-06-14T10:49:46</dc:date>
+ </office:change-info>
+ </text:insertion>
+ </text:changed-region>
+ <text:changed-region xml:id="ct93860566693104" text:id="ct93860566693104">
+ <text:format-change>
+ <office:change-info>
+ <dc:creator>Unknown Author</dc:creator>
+ <dc:date>2023-06-14T10:49:59</dc:date>
+ </office:change-info>
+ </text:format-change>
+ </text:changed-region>
+ </text:tracked-changes>
+ <text:sequence-decls>
+ <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+ </text:sequence-decls>
+ <text:h text:style-name="Heading_20_3" text:outline-level="3">The standard Lorem Ipsum passage, used since the 1500s</text:h>
+ <text:section text:style-name="Sect1" text:name="Translation">
+ <text:p text:style-name="Text_20_body">"Lorem <text:span text:style-name="T1">ipsum dolor</text:span> sit <text:span text:style-name="Strong_20_Emphasis">amet</text:span>, consectetur <text:span text:style-name="T2">adipiscing</text:span> elit, <text:span text:style-name="T3">sed</text:span> do <text:span text:style-name="T4">eiusmod</text:span> tempor <text:span text:style-name="T5">incididunt</text:span> ut labore et <text:span text:style-name="T6">dolore</text:span> magna aliqua. Ut enim ad minim <text:span text:style-name="T7">veniam</text:span>, quis <text:span text:style-name="T8">nostrud</text:span> <text:span text:style-name="T9">exercitation</text:span> ullamco <text:span text:style-name="T10">laboris</text:span> nisi ut <text:span text:style-name="T11">aliquip</text:span> ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."</text:p>
+ <text:h text:style-name="Heading_20_3" text:outline-level="3">Section 1.10.32 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC</text:h>
+ <text:p text:style-name="P1">"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto qui dolorem eum fugiat quo voluptas nulla pariatur?"</text:p>
+ <text:h text:style-name="Heading_20_3" text:outline-level="3">1914 translation by H. Rackham</text:h>
+ <text:p text:style-name="Text_20_body">"But I must <text:change-start text:change-id="ct93860566703600"/>explain to<text:change-end text:change-id="ct93860566703600"/><text:change-start text:change-id="ct93860566704768"/><text:span text:style-name="T14">tell</text:span><text:change-end text:change-id="ct93860566704768"/> you how all this <text:change-start text:change-id="ct93860566705104"/>mistaken<text:change-end text:change-id="ct93860566705104"/><text:change-start text:change-id="ct93860566691808"/><text:span text:style-name="T14">absurd</text:span><text:change-end text:change-id="ct93860566691808"/> idea of denouncing pleasure and praising <text:change-start text:change-id="ct93860566693104"/><text:span text:style-name="T15">pain</text:span><text:change-end text:change-id="ct93860566693104"/> was born and I will give you a complete account of the system, and expound the actual teachings of the annoying consequences, or one who avoids a pain that produces no resultant pleasure?"</text:p>
+ </text:section>
+ <text:p text:style-name="Text_20_body"><text:span text:style-name="T12">Unicode &lt; U+FFFF: fl</text:span><text:span text:style-name="T13"><text:line-break/>SMP Unicode (&gt; U+FFFF): 🜂🜃</text:span></text:p>
+ <text:p text:style-name="P2"/>
+ <text:p text:style-name="P2"/>
+ <text:p text:style-name="P2"/>
+ <text:h text:style-name="Heading_20_3" text:outline-level="3">Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC</text:h>
+ <text:p text:style-name="Text_20_body">"At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat."</text:p>
+ <text:h text:style-name="Heading_20_3" text:outline-level="3"><text:soft-page-break/>1914 translation by H. Rackham</text:h>
+ <text:p text:style-name="Text_20_body">"On the other hand, we denounce with righteous indignation and dislike men who are so beguiled and demoralized by the charms of pleasure of the moment, so blinded by desire, that they cannot foresee the pain and trouble that are bound to ensue; and equal blame belongs to those who fail in their duty through weakness of will, which is the same as saying through shrinking from toil and pain. These cases are perfectly simple and easy to distinguish. In a free hour, when our power of choice is untrammelled and when nothing prevents our being able to do what we like best, every pleasure is to be welcomed and every pain avoided. But in certain circumstances and owing to the claims of duty or the obligations of business it will frequently occur that pleasures have to be repudiated and annoyances accepted. The wise man therefore always holds in these matters to this principle of selection: he rejects pleasures to secure other greater pleasures, or else he endures pains to avoid worse pains."</text:p>
+ <text:p text:style-name="Text_20_body"/>
+ </office:text>
+ </office:body>
+</office:document> \ No newline at end of file
diff --git a/vcl/qa/cppunit/animation.cxx b/vcl/qa/cppunit/animation.cxx
new file mode 100644
index 0000000000..bbedacbdf3
--- /dev/null
+++ b/vcl/qa/cppunit/animation.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/.
+ */
+
+#include <test/bootstrapfixture.hxx>
+#include <cppunit/TestAssert.h>
+
+#include <vcl/animate/Animation.hxx>
+
+class VclAnimationTest : public test::BootstrapFixture
+{
+public:
+ VclAnimationTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ void testFrameCount();
+ void testDisplaySize();
+
+ CPPUNIT_TEST_SUITE(VclAnimationTest);
+ CPPUNIT_TEST(testFrameCount);
+ CPPUNIT_TEST(testDisplaySize);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void VclAnimationTest::testFrameCount()
+{
+ Animation aAnimation;
+
+ CPPUNIT_ASSERT_EQUAL(size_t(0), aAnimation.Count());
+
+ aAnimation.Insert(
+ AnimationFrame(BitmapEx(Size(3, 4), vcl::PixelFormat::N24_BPP), Point(0, 0), Size(3, 4)));
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aAnimation.Count());
+
+ aAnimation.Insert(
+ AnimationFrame(BitmapEx(Size(3, 3), vcl::PixelFormat::N24_BPP), Point(0, 0), Size(10, 10)));
+ CPPUNIT_ASSERT_EQUAL(size_t(2), aAnimation.Count());
+
+ aAnimation.Clear();
+ CPPUNIT_ASSERT_EQUAL(size_t(0), aAnimation.Count());
+}
+
+void VclAnimationTest::testDisplaySize()
+{
+ Animation aAnimation;
+ CPPUNIT_ASSERT_EQUAL(Size(0, 0), aAnimation.GetDisplaySizePixel());
+
+ aAnimation.Insert(
+ AnimationFrame(BitmapEx(Size(3, 4), vcl::PixelFormat::N24_BPP), Point(0, 0), Size(3, 4)));
+ CPPUNIT_ASSERT_EQUAL(Size(3, 4), aAnimation.GetDisplaySizePixel());
+
+ aAnimation.Insert(AnimationFrame(BitmapEx(Size(10, 10), vcl::PixelFormat::N24_BPP), Point(0, 0),
+ Size(10, 10)));
+ CPPUNIT_ASSERT_EQUAL(Size(10, 10), aAnimation.GetDisplaySizePixel());
+
+ aAnimation.Clear();
+ CPPUNIT_ASSERT_EQUAL(Size(0, 0), aAnimation.GetDisplaySizePixel());
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclAnimationTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/animationrenderer.cxx b/vcl/qa/cppunit/animationrenderer.cxx
new file mode 100644
index 0000000000..93f80970f2
--- /dev/null
+++ b/vcl/qa/cppunit/animationrenderer.cxx
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+#include <cppunit/TestAssert.h>
+
+#include <vcl/animate/Animation.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <animate/AnimationRenderer.hxx>
+
+namespace
+{
+class TestRenderingContext : public OutputDevice
+{
+public:
+ TestRenderingContext()
+ : OutputDevice(OutDevType::OUTDEV_VIRDEV)
+ {
+ }
+
+ void SaveBackground(VirtualDevice&, const Point&, const Size&, const Size&) const override {}
+ bool AcquireGraphics() const override { return true; }
+ void ReleaseGraphics(bool) override {}
+ bool UsePolyPolygonForComplexGradient() override { return false; }
+};
+
+Animation createAnimation()
+{
+ Animation aAnimation;
+
+ aAnimation.Insert(
+ AnimationFrame(BitmapEx(Size(3, 4), vcl::PixelFormat::N24_BPP), Point(0, 0), Size(10, 10)));
+ aAnimation.Insert(
+ AnimationFrame(BitmapEx(Size(3, 3), vcl::PixelFormat::N24_BPP), Point(0, 0), Size(10, 10)));
+
+ return aAnimation;
+}
+}
+
+class VclAnimationRendererTest : public test::BootstrapFixture
+{
+public:
+ VclAnimationRendererTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+};
+
+CPPUNIT_TEST_FIXTURE(VclAnimationRendererTest, testMatching)
+{
+ Animation aTestAnim = createAnimation();
+ ScopedVclPtrInstance<TestRenderingContext> pTestRC;
+
+ AnimationRenderer* pAnimationRenderer
+ = new AnimationRenderer(&aTestAnim, pTestRC, Point(0, 0), Size(10, 10), 5);
+ CPPUNIT_ASSERT(pAnimationRenderer->matches(pTestRC, 5));
+ CPPUNIT_ASSERT(!pAnimationRenderer->matches(pTestRC, 10));
+
+ // caller ID of 0 only matches the OutputDevice
+ CPPUNIT_ASSERT(pAnimationRenderer->matches(pTestRC, 0));
+}
+
+CPPUNIT_TEST_FIXTURE(VclAnimationRendererTest, testDrawToPos)
+{
+ Animation aTestAnim = createAnimation();
+ ScopedVclPtrInstance<VirtualDevice> pTestRC;
+
+ AnimationRenderer* pAnimationRenderer
+ = new AnimationRenderer(&aTestAnim, pTestRC.get(), Point(0, 0), Size(10, 10), 5);
+ pAnimationRenderer->drawToIndex(0);
+ pAnimationRenderer->drawToIndex(1);
+ pAnimationRenderer->drawToIndex(2);
+ pAnimationRenderer->drawToIndex(10);
+
+ CPPUNIT_ASSERT_EQUAL(Size(1, 1), pTestRC->GetOutputSizePixel());
+}
+
+CPPUNIT_TEST_FIXTURE(VclAnimationRendererTest, testGetPosSizeWindow)
+{
+ Animation aTestAnim = createAnimation();
+ ScopedVclPtrInstance<TestRenderingContext> pTestRC;
+
+ AnimationRenderer* pAnimationRenderer
+ = new AnimationRenderer(&aTestAnim, pTestRC, Point(0, 0), Size(10, 10), 5);
+ AnimationFrame aAnimBmp(BitmapEx(Size(3, 4), vcl::PixelFormat::N24_BPP), Point(0, 0),
+ Size(10, 10));
+ Point aPos;
+ Size aSize;
+
+ pAnimationRenderer->getPosSize(aAnimBmp, aPos, aSize);
+
+ CPPUNIT_ASSERT_EQUAL(Point(0, 0), aPos);
+ CPPUNIT_ASSERT_EQUAL(Size(10, 10), aSize);
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/app/test_IconThemeInfo.cxx b/vcl/qa/cppunit/app/test_IconThemeInfo.cxx
new file mode 100644
index 0000000000..ff1ad985b1
--- /dev/null
+++ b/vcl/qa/cppunit/app/test_IconThemeInfo.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/.
+ */
+
+#include <stdexcept>
+
+#include <rtl/ustring.hxx>
+#include <vcl/IconThemeInfo.hxx>
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+using namespace vcl;
+
+class IconThemeInfoTest : public CppUnit::TestFixture
+{
+ void
+ UpperCaseDisplayNameIsReturnedForNonDefaultId();
+
+ void
+ ImagesZipIsNotValid();
+
+ void
+ ImagesColibreZipIsValid();
+
+ void
+ ThemeIdIsDetectedFromFileNameWithUnderscore();
+
+ void
+ ExceptionIsThrownWhenIdCannotBeDetermined1();
+
+ void
+ ExceptionIsThrownWhenIdCannotBeDetermined2();
+
+ // Adds code needed to register the test suite
+ CPPUNIT_TEST_SUITE(IconThemeInfoTest);
+ CPPUNIT_TEST(UpperCaseDisplayNameIsReturnedForNonDefaultId);
+ CPPUNIT_TEST(ThemeIdIsDetectedFromFileNameWithUnderscore);
+ CPPUNIT_TEST(ImagesZipIsNotValid);
+ CPPUNIT_TEST(ImagesColibreZipIsValid);
+ CPPUNIT_TEST(ExceptionIsThrownWhenIdCannotBeDetermined1);
+ CPPUNIT_TEST(ExceptionIsThrownWhenIdCannotBeDetermined2);
+
+ // End of test suite definition
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void
+IconThemeInfoTest::UpperCaseDisplayNameIsReturnedForNonDefaultId()
+{
+ OUString displayName = vcl::IconThemeInfo::ThemeIdToDisplayName("katze");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("theme id is properly uppercased", OUString("Katze"), displayName);
+}
+
+void
+IconThemeInfoTest::ImagesZipIsNotValid()
+{
+ bool valid = vcl::IconThemeInfo::UrlCanBeParsed(u"file://images.zip");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("images.zip is not a valid theme name", false, valid);
+}
+
+void
+IconThemeInfoTest::ImagesColibreZipIsValid()
+{
+ bool valid = vcl::IconThemeInfo::UrlCanBeParsed(u"file://images_colibre.zip");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("images_colibre.zip is a valid theme name", true, valid);
+}
+
+void
+IconThemeInfoTest::ThemeIdIsDetectedFromFileNameWithUnderscore()
+{
+ OUString sname = vcl::IconThemeInfo::FileNameToThemeId(u"images_colibre.zip");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("'colibre' theme id is returned for 'images_colibre.zip'", OUString("colibre"), sname);
+}
+
+void
+IconThemeInfoTest::ExceptionIsThrownWhenIdCannotBeDetermined1()
+{
+ bool thrown = false;
+ try {
+ vcl::IconThemeInfo::FileNameToThemeId(u"images_colibre");
+ }
+ catch (std::runtime_error&) {
+ thrown = true;
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Exception was thrown",true, thrown);
+}
+
+void
+IconThemeInfoTest::ExceptionIsThrownWhenIdCannotBeDetermined2()
+{
+ bool thrown = false;
+ try {
+ vcl::IconThemeInfo::FileNameToThemeId(u"image_colibre.zip");
+ }
+ catch (std::runtime_error&) {
+ thrown = true;
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Exception was thrown", true, thrown);
+}
+
+// Put the test suite in the registry
+CPPUNIT_TEST_SUITE_REGISTRATION(IconThemeInfoTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/app/test_IconThemeScanner.cxx b/vcl/qa/cppunit/app/test_IconThemeScanner.cxx
new file mode 100644
index 0000000000..d02fe752ce
--- /dev/null
+++ b/vcl/qa/cppunit/app/test_IconThemeScanner.cxx
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <stdexcept>
+
+#include <rtl/ustring.hxx>
+#include <IconThemeScanner.hxx>
+#include <vcl/IconThemeInfo.hxx>
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+class IconThemeScannerTest : public CppUnit::TestFixture
+{
+ void
+ AddedThemeIsFoundById();
+
+ void
+ AddedThemeInfoIsReturned();
+
+ void
+ ExceptionIsThrownIfInvalidInfoIsRequested();
+
+ // Adds code needed to register the test suite
+ CPPUNIT_TEST_SUITE(IconThemeScannerTest);
+ CPPUNIT_TEST(AddedThemeIsFoundById);
+ CPPUNIT_TEST(AddedThemeInfoIsReturned);
+ CPPUNIT_TEST(ExceptionIsThrownIfInvalidInfoIsRequested);
+
+ // End of test suite definition
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void
+IconThemeScannerTest::AddedThemeIsFoundById()
+{
+ vcl::IconThemeScanner scanner;
+ scanner.AddIconThemeByPath("file:://images_katze.zip");
+ OUString id = vcl::IconThemeInfo::FileNameToThemeId(u"images_katze.zip");
+ bool found = scanner.IconThemeIsInstalled(id);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("icon theme could be added by url", true, found);
+}
+
+void
+IconThemeScannerTest::AddedThemeInfoIsReturned()
+{
+ vcl::IconThemeScanner scanner;
+ OUString theme("file:://images_katze.zip");
+ scanner.AddIconThemeByPath(theme);
+ OUString id = vcl::IconThemeInfo::FileNameToThemeId(u"images_katze.zip");
+ const vcl::IconThemeInfo& info = scanner.GetIconThemeInfo(id);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("'katze' icon theme is found from id", theme, info.GetUrlToFile());
+}
+
+void
+IconThemeScannerTest::ExceptionIsThrownIfInvalidInfoIsRequested()
+{
+ vcl::IconThemeScanner scanner;
+ scanner.AddIconThemeByPath("file:://images_katze.zip");
+ bool thrown = false;
+ try
+ {
+ scanner.GetIconThemeInfo("hund");
+ }
+ catch (const std::runtime_error&)
+ {
+ thrown = true;
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Exception is thrown if invalid theme info is requested", true, thrown);
+}
+
+// Put the test suite in the registry
+CPPUNIT_TEST_SUITE_REGISTRATION(IconThemeScannerTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/app/test_IconThemeSelector.cxx b/vcl/qa/cppunit/app/test_IconThemeSelector.cxx
new file mode 100644
index 0000000000..7e053ffd1e
--- /dev/null
+++ b/vcl/qa/cppunit/app/test_IconThemeSelector.cxx
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <IconThemeSelector.hxx>
+
+#include <vcl/IconThemeInfo.hxx>
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+
+class IconThemeSelectorTest : public CppUnit::TestFixture
+{
+#ifndef _WIN32 //default theme on Windows is Colibre independently from any desktop environment
+ void BreezeIsReturnedForKde5Desktop();
+ void ElementaryIsReturnedForGnomeDesktop();
+ void ThemeIsOverriddenByPreferredTheme();
+ void ThemeIsOverriddenByHighContrastMode();
+ void NotInstalledThemeDoesNotOverride();
+ void InstalledThemeIsFound();
+ void FirstThemeIsReturnedIfRequestedThemeIsNotFound();
+ void FallbackThemeIsReturnedForEmptyInput();
+ void DifferentPreferredThemesAreInequal();
+ void DifferentHighContrastModesAreInequal();
+ static std::vector<vcl::IconThemeInfo> GetFakeInstalledThemes();
+#endif
+
+ // Adds code needed to register the test suite
+ CPPUNIT_TEST_SUITE(IconThemeSelectorTest);
+
+#ifndef _WIN32
+ CPPUNIT_TEST(BreezeIsReturnedForKde5Desktop);
+ CPPUNIT_TEST(ElementaryIsReturnedForGnomeDesktop);
+ CPPUNIT_TEST(ThemeIsOverriddenByPreferredTheme);
+ CPPUNIT_TEST(ThemeIsOverriddenByHighContrastMode);
+ CPPUNIT_TEST(NotInstalledThemeDoesNotOverride);
+ CPPUNIT_TEST(InstalledThemeIsFound);
+ CPPUNIT_TEST(FirstThemeIsReturnedIfRequestedThemeIsNotFound);
+ CPPUNIT_TEST(FallbackThemeIsReturnedForEmptyInput);
+ CPPUNIT_TEST(DifferentPreferredThemesAreInequal);
+ CPPUNIT_TEST(DifferentHighContrastModesAreInequal);
+#endif
+
+ // End of test suite definition
+ CPPUNIT_TEST_SUITE_END();
+};
+
+#ifndef _WIN32
+
+/*static*/ std::vector<vcl::IconThemeInfo>
+IconThemeSelectorTest::GetFakeInstalledThemes()
+{
+ std::vector<vcl::IconThemeInfo> r;
+ vcl::IconThemeInfo a;
+ a.mThemeId = "breeze";
+ r.push_back(a);
+ a.mThemeId = "elementary";
+ r.push_back(a);
+ a.mThemeId = "colibre";
+ r.push_back(a);
+ a.mThemeId = "sifr";
+ r.push_back(a);
+ return r;
+}
+
+void
+IconThemeSelectorTest::BreezeIsReturnedForKde5Desktop()
+{
+ std::vector<vcl::IconThemeInfo> themes = GetFakeInstalledThemes();
+ vcl::IconThemeSelector s;
+ OUString r = s.SelectIconThemeForDesktopEnvironment(themes, "plasma5");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("'breeze' theme is returned for Plasma 5 desktop", OUString("breeze"), r);
+}
+
+void
+IconThemeSelectorTest::ElementaryIsReturnedForGnomeDesktop()
+{
+ std::vector<vcl::IconThemeInfo> themes = GetFakeInstalledThemes();
+ vcl::IconThemeSelector s;
+ OUString r = s.SelectIconThemeForDesktopEnvironment(themes, "gnome");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("'elementary' theme is returned for gnome desktop", OUString("elementary"), r);
+}
+
+void
+IconThemeSelectorTest::ThemeIsOverriddenByPreferredTheme()
+{
+ vcl::IconThemeSelector s;
+ OUString preferred("breeze");
+ s.SetPreferredIconTheme(preferred, false);
+ std::vector<vcl::IconThemeInfo> themes = GetFakeInstalledThemes();
+ OUString selected = s.SelectIconThemeForDesktopEnvironment(themes, "gnome");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("'elementary' theme is overridden by breeze", preferred, selected);
+}
+
+void
+IconThemeSelectorTest::ThemeIsOverriddenByHighContrastMode()
+{
+ vcl::IconThemeSelector s;
+ s.SetUseHighContrastTheme(true);
+ std::vector<vcl::IconThemeInfo> themes = GetFakeInstalledThemes();
+ OUString selected = s.SelectIconTheme(themes, "breeze");
+ bool sifr = selected.startsWith("sifr");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("'breeze' theme is overridden by high contrast mode", true, sifr);
+ s.SetUseHighContrastTheme(false);
+ selected = s.SelectIconTheme(themes, "breeze");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("'breeze' theme is no longer overridden by high contrast mode",
+ OUString("breeze"), selected);
+}
+
+void
+IconThemeSelectorTest::NotInstalledThemeDoesNotOverride()
+{
+ vcl::IconThemeSelector s;
+ s.SetPreferredIconTheme("breeze_foo", false);
+ std::vector<vcl::IconThemeInfo> themes = GetFakeInstalledThemes();
+ OUString selected = s.SelectIconTheme(themes, "colibre");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("'colibre' theme is not overridden by 'breeze_foo'", OUString("colibre"), selected);
+}
+
+void
+IconThemeSelectorTest::InstalledThemeIsFound()
+{
+ vcl::IconThemeSelector s;
+ std::vector<vcl::IconThemeInfo> themes = GetFakeInstalledThemes();
+ OUString selected = s.SelectIconTheme(themes, "colibre");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("'colibre' theme is found", OUString("colibre"), selected);
+}
+
+void
+IconThemeSelectorTest::FirstThemeIsReturnedIfRequestedThemeIsNotFound()
+{
+ vcl::IconThemeSelector s;
+ std::vector<vcl::IconThemeInfo> themes = GetFakeInstalledThemes();
+ OUString selected = s.SelectIconTheme(themes, "breeze_foo");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("'breeze' theme is found", themes.front().GetThemeId(), selected);
+}
+
+void
+IconThemeSelectorTest::FallbackThemeIsReturnedForEmptyInput()
+{
+ vcl::IconThemeSelector s;
+ OUString selected = s.SelectIconTheme(std::vector<vcl::IconThemeInfo>(), "colibre");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("fallback is returned for empty input",
+ vcl::IconThemeSelector::FALLBACK_LIGHT_ICON_THEME_ID, selected);
+}
+
+void
+IconThemeSelectorTest::DifferentHighContrastModesAreInequal()
+{
+ vcl::IconThemeSelector s1;
+ vcl::IconThemeSelector s2;
+ s1.SetUseHighContrastTheme(true);
+ s2.SetUseHighContrastTheme(false);
+ bool equal = (s1 == s2);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Different high contrast modes are detected as inequal", false, equal);
+}
+
+void
+IconThemeSelectorTest::DifferentPreferredThemesAreInequal()
+{
+ vcl::IconThemeSelector s1;
+ vcl::IconThemeSelector s2;
+ s1.SetPreferredIconTheme("breeze", false);
+ s2.SetUseHighContrastTheme(true);
+ bool equal = (s1 == s2);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Different preferred themes are detected as inequal", false, equal);
+}
+
+#endif
+
+// Put the test suite in the registry
+CPPUNIT_TEST_SUITE_REGISTRATION(IconThemeSelectorTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/bitmapcolor.cxx b/vcl/qa/cppunit/bitmapcolor.cxx
new file mode 100644
index 0000000000..f62e19dabc
--- /dev/null
+++ b/vcl/qa/cppunit/bitmapcolor.cxx
@@ -0,0 +1,263 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+// bootstrap stuff
+#include <test/bootstrapfixture.hxx>
+
+#include <vcl/BitmapColor.hxx>
+
+namespace
+{
+class BitmapColorTest : public test::BootstrapFixture
+{
+public:
+ BitmapColorTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ void defaultConstructor();
+ void colorValueConstructor();
+ void colorClassConstructor();
+ void setValue();
+ void invert();
+ void getLuminance();
+
+ CPPUNIT_TEST_SUITE(BitmapColorTest);
+ CPPUNIT_TEST(defaultConstructor);
+ CPPUNIT_TEST(colorValueConstructor);
+ CPPUNIT_TEST(colorClassConstructor);
+ CPPUNIT_TEST(setValue);
+ CPPUNIT_TEST(invert);
+ CPPUNIT_TEST(getLuminance);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void BitmapColorTest::defaultConstructor()
+{
+ BitmapColor aBmpColor;
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Red wrong", static_cast<sal_uInt8>(0), aBmpColor.GetRed());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Green wrong", static_cast<sal_uInt8>(0), aBmpColor.GetGreen());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Blue wrong", static_cast<sal_uInt8>(0), aBmpColor.GetBlue());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Transparency wrong", static_cast<sal_uInt8>(255),
+ aBmpColor.GetAlpha());
+}
+
+void BitmapColorTest::colorValueConstructor()
+{
+ {
+ BitmapColor aBmpColor(0, 0, 0);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Red wrong", static_cast<sal_uInt8>(0), aBmpColor.GetRed());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Green wrong", static_cast<sal_uInt8>(0),
+ aBmpColor.GetGreen());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Blue wrong", static_cast<sal_uInt8>(0), aBmpColor.GetBlue());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Transparency wrong", static_cast<sal_uInt8>(255),
+ aBmpColor.GetAlpha());
+ }
+
+ {
+ BitmapColor aBmpColor(128, 128, 128);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Red wrong", static_cast<sal_uInt8>(128), aBmpColor.GetRed());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Green wrong", static_cast<sal_uInt8>(128),
+ aBmpColor.GetGreen());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Blue wrong", static_cast<sal_uInt8>(128),
+ aBmpColor.GetBlue());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Transparency wrong", static_cast<sal_uInt8>(255),
+ aBmpColor.GetAlpha());
+ }
+
+ {
+ BitmapColor aBmpColor(255, 255, 255);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Red wrong", static_cast<sal_uInt8>(255), aBmpColor.GetRed());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Green wrong", static_cast<sal_uInt8>(255),
+ aBmpColor.GetGreen());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Blue wrong", static_cast<sal_uInt8>(255),
+ aBmpColor.GetBlue());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Transparency wrong", static_cast<sal_uInt8>(255),
+ aBmpColor.GetAlpha());
+ }
+}
+
+void BitmapColorTest::colorClassConstructor()
+{
+ {
+ BitmapColor aBmpColor(Color(0, 0, 0));
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Red wrong", static_cast<sal_uInt8>(0), aBmpColor.GetRed());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Green wrong", static_cast<sal_uInt8>(0),
+ aBmpColor.GetGreen());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Blue wrong", static_cast<sal_uInt8>(0), aBmpColor.GetBlue());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Transparency wrong", static_cast<sal_uInt8>(255),
+ aBmpColor.GetAlpha());
+ }
+
+ {
+ BitmapColor aBmpColor(Color(127, 127, 127));
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Red wrong", static_cast<sal_uInt8>(127), aBmpColor.GetRed());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Green wrong", static_cast<sal_uInt8>(127),
+ aBmpColor.GetGreen());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Blue wrong", static_cast<sal_uInt8>(127),
+ aBmpColor.GetBlue());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Transparency wrong", static_cast<sal_uInt8>(255),
+ aBmpColor.GetAlpha());
+ }
+
+ {
+ BitmapColor aBmpColor(Color(255, 255, 255));
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Red wrong", static_cast<sal_uInt8>(255), aBmpColor.GetRed());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Green wrong", static_cast<sal_uInt8>(255),
+ aBmpColor.GetGreen());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Blue wrong", static_cast<sal_uInt8>(255),
+ aBmpColor.GetBlue());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Transparency wrong", static_cast<sal_uInt8>(255),
+ aBmpColor.GetAlpha());
+ }
+
+ // Transparency / Alpha
+ {
+ BitmapColor aBmpColor(Color(ColorTransparency, 255, 128, 64, 0));
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Red wrong", static_cast<sal_uInt8>(128), aBmpColor.GetRed());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Green wrong", static_cast<sal_uInt8>(64),
+ aBmpColor.GetGreen());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Blue wrong", static_cast<sal_uInt8>(0), aBmpColor.GetBlue());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Transparency wrong", static_cast<sal_uInt8>(0),
+ aBmpColor.GetAlpha());
+ }
+}
+
+void BitmapColorTest::setValue()
+{
+ BitmapColor aBmpColor;
+
+ aBmpColor.SetRed(127);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(127), aBmpColor.GetRed());
+
+ aBmpColor.SetGreen(127);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(127), aBmpColor.GetGreen());
+
+ aBmpColor.SetBlue(127);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(127), aBmpColor.GetBlue());
+}
+
+void BitmapColorTest::invert()
+{
+ BitmapColor aBmpColor(255, 255, 255);
+ BitmapColor aInvertedColor(aBmpColor);
+ aInvertedColor.Invert();
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(0), aInvertedColor.GetRed());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(0), aInvertedColor.GetGreen());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(0), aInvertedColor.GetBlue());
+}
+
+void BitmapColorTest::getLuminance()
+{
+ {
+ BitmapColor aBmpColor(COL_BLACK);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(0), aBmpColor.GetLuminance());
+ }
+
+ {
+ BitmapColor aBmpColor(COL_BLUE);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(14), aBmpColor.GetLuminance());
+ }
+
+ {
+ BitmapColor aBmpColor(COL_GREEN);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(75), aBmpColor.GetLuminance());
+ }
+
+ {
+ BitmapColor aBmpColor(COL_CYAN);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(90), aBmpColor.GetLuminance());
+ }
+
+ {
+ BitmapColor aBmpColor(COL_RED);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(38), aBmpColor.GetLuminance());
+ }
+
+ {
+ BitmapColor aBmpColor(COL_MAGENTA);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(52), aBmpColor.GetLuminance());
+ }
+
+ {
+ BitmapColor aBmpColor(COL_BROWN);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(113), aBmpColor.GetLuminance());
+ }
+
+ {
+ BitmapColor aBmpColor(COL_GRAY);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(128), aBmpColor.GetLuminance());
+ }
+
+ {
+ BitmapColor aBmpColor(COL_LIGHTGRAY);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(192), aBmpColor.GetLuminance());
+ }
+
+ {
+ BitmapColor aBmpColor(COL_LIGHTBLUE);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(28), aBmpColor.GetLuminance());
+ }
+
+ {
+ BitmapColor aBmpColor(COL_LIGHTGREEN);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(150), aBmpColor.GetLuminance());
+ }
+
+ {
+ BitmapColor aBmpColor(COL_LIGHTCYAN);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(179), aBmpColor.GetLuminance());
+ }
+
+ {
+ BitmapColor aBmpColor(COL_LIGHTRED);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(75), aBmpColor.GetLuminance());
+ }
+
+ {
+ BitmapColor aBmpColor(COL_LIGHTMAGENTA);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(104), aBmpColor.GetLuminance());
+ }
+
+ {
+ BitmapColor aBmpColor(COL_YELLOW);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(226), aBmpColor.GetLuminance());
+ }
+
+ {
+ BitmapColor aBmpColor(COL_WHITE);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(255), aBmpColor.GetLuminance());
+ }
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BitmapColorTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/bitmaprender/BitmapRenderTest.cxx b/vcl/qa/cppunit/bitmaprender/BitmapRenderTest.cxx
new file mode 100644
index 0000000000..991f202cef
--- /dev/null
+++ b/vcl/qa/cppunit/bitmaprender/BitmapRenderTest.cxx
@@ -0,0 +1,286 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+
+#include <vcl/virdev.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/svapp.hxx>
+
+#include <tools/stream.hxx>
+
+#include <vcl/graphicfilter.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+constexpr OUStringLiteral gaDataUrl = u"/vcl/qa/cppunit/bitmaprender/data/";
+
+class BitmapRenderTest : public test::BootstrapFixture
+{
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(gaDataUrl) + sFileName;
+ }
+
+public:
+ BitmapRenderTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ void testTdf104141();
+ void testTdf113918();
+ void testDrawAlphaBitmapEx();
+ void testAlphaVirtualDevice();
+ void testTdf116888();
+
+ CPPUNIT_TEST_SUITE(BitmapRenderTest);
+ CPPUNIT_TEST(testTdf104141);
+ CPPUNIT_TEST(testTdf113918);
+ CPPUNIT_TEST(testDrawAlphaBitmapEx);
+ CPPUNIT_TEST(testAlphaVirtualDevice);
+ CPPUNIT_TEST(testTdf116888);
+
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void BitmapRenderTest::testTdf104141()
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ pVDev->SetOutputSizePixel(Size(400, 400));
+ pVDev->SetBackground(Wallpaper(COL_GREEN));
+ pVDev->Erase();
+
+ // Load animated GIF and draw it on green background
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic;
+ const OUString aURL(getFullUrl(u"tdf104141.gif"));
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+ BitmapEx aBitmap = aGraphic.GetBitmapEx();
+ pVDev->DrawBitmapEx(Point(20, 20), aBitmap);
+
+ // Check drawing results: ensure that it contains transparent
+ // (greenish) pixels
+ const Color aColor = pVDev->GetPixel(Point(21, 21));
+ CPPUNIT_ASSERT(aColor.GetGreen() > 10 * aColor.GetRed());
+ CPPUNIT_ASSERT(aColor.GetGreen() > 10 * aColor.GetBlue());
+}
+
+void BitmapRenderTest::testTdf113918()
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ pVDev->SetOutputSizePixel(Size(2480, 3508));
+ pVDev->SetBackground(Wallpaper(COL_GREEN));
+ pVDev->Erase();
+
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic;
+ const OUString aURL(getFullUrl(u"tdf113918.png"));
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+ BitmapEx aBitmap = aGraphic.GetBitmapEx();
+ pVDev->DrawBitmapEx(Point(0, 0), aBitmap);
+
+ // Ensure that image is drawn with white background color from palette
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, pVDev->GetPixel(Point(21, 21)));
+
+ // Ensure that image is drawn with gray text color from palette
+ const Color aColor = pVDev->GetPixel(Point(1298, 1368));
+ CPPUNIT_ASSERT_EQUAL(aColor.GetGreen(), aColor.GetRed());
+ CPPUNIT_ASSERT_EQUAL(aColor.GetGreen(), aColor.GetBlue());
+ CPPUNIT_ASSERT(aColor.GetGreen() > 100);
+}
+
+#if defined(_WIN32) || defined(IOS)
+
+namespace
+{
+int deltaColor(BitmapColor aColor1, BitmapColor aColor2)
+{
+ int deltaR = std::abs(aColor1.GetRed() - aColor2.GetRed());
+ int deltaG = std::abs(aColor1.GetGreen() - aColor2.GetGreen());
+ int deltaB = std::abs(aColor1.GetBlue() - aColor2.GetBlue());
+
+ return std::max(std::max(deltaR, deltaG), deltaB);
+}
+}
+
+#endif
+
+void BitmapRenderTest::testDrawAlphaBitmapEx()
+{
+// TODO: This unit test is not executed for macOS unless bitmap scaling is implemented
+#ifndef MACOSX
+ if (getDefaultDeviceBitCount() < 24)
+ return;
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ pVDev->SetOutputSizePixel(Size(8, 8));
+ pVDev->SetBackground(Wallpaper(COL_WHITE));
+ pVDev->Erase();
+
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, pVDev->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, pVDev->GetPixel(Point(1, 1)));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, pVDev->GetPixel(Point(2, 2)));
+
+ SvFileStream aFileStream(getFullUrl(u"ImageRGBA.png"), StreamMode::READ);
+
+ vcl::PngImageReader aPngReader(aFileStream);
+ BitmapEx aBitmapEx;
+ aPngReader.read(aBitmapEx);
+
+ // Check backend capabilities, if the backend support 32-bit bitmap
+ if (ImplGetSVData()->mpDefInst->supportsBitmap32())
+ {
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N32_BPP, aBitmapEx.GetBitmap().getPixelFormat());
+ }
+ else
+ {
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, aBitmapEx.GetBitmap().getPixelFormat());
+ CPPUNIT_ASSERT_EQUAL(true, aBitmapEx.IsAlpha());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmapEx.GetAlphaMask().getPixelFormat());
+ }
+
+ // Check the bitmap has pixels we expect
+ CPPUNIT_ASSERT_EQUAL(Color(ColorTransparency, 0xFF, 0x00, 0x00, 0x00),
+ aBitmapEx.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(Color(ColorTransparency, 0x00, 0xFF, 0xFF, 0x00),
+ aBitmapEx.GetPixelColor(1, 1));
+ CPPUNIT_ASSERT_EQUAL(Color(ColorTransparency, 0x7F, 0x00, 0xFF, 0x00),
+ aBitmapEx.GetPixelColor(2, 2));
+
+ pVDev->DrawBitmapEx(Point(), aBitmapEx);
+
+ CPPUNIT_ASSERT_EQUAL(Color(0xFF, 0xFF, 0xFF), pVDev->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(Color(0xFF, 0xFF, 0x00), pVDev->GetPixel(Point(1, 1)));
+
+#if defined(_WIN32) || defined(MACOSX) || defined(IOS)
+ // sometimes on Windows we get rounding error in blending so let's ignore this on Windows for now.
+ CPPUNIT_ASSERT_LESS(2, deltaColor(Color(0x7F, 0xFF, 0x7F), pVDev->GetPixel(Point(2, 2))));
+#else
+ CPPUNIT_ASSERT_EQUAL(Color(0x7F, 0xFF, 0x7F), pVDev->GetPixel(Point(2, 2)));
+#endif
+#endif
+}
+
+void BitmapRenderTest::testAlphaVirtualDevice()
+{
+// TODO: This unit test is not executed for macOS unless bitmap scaling is implemented
+#ifndef MACOSX
+ // Create an alpha virtual device
+ ScopedVclPtr<VirtualDevice> pAlphaVirtualDevice(
+ VclPtr<VirtualDevice>::Create(*Application::GetDefaultDevice(), DeviceFormat::WITH_ALPHA));
+
+ // Set it up
+ pAlphaVirtualDevice->SetOutputSizePixel(Size(4, 4));
+ pAlphaVirtualDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
+ pAlphaVirtualDevice->Erase();
+
+ // Get a BitmapEx from the VirDev -> Colors should have alpha
+ BitmapEx aBitmap = pAlphaVirtualDevice->GetBitmapEx(Point(), Size(4, 4));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Height());
+ Color aColor = aBitmap.GetPixelColor(1, 1);
+ CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, aColor);
+
+ // Draw an opaque pixel to the VirDev
+ pAlphaVirtualDevice->DrawPixel(Point(1, 1), Color(0x0022ff55));
+
+ aColor = pAlphaVirtualDevice->GetPixel(Point(1, 1));
+ // Read back the opaque pixel
+#if defined _WIN32
+ CPPUNIT_ASSERT_LESS(6, deltaColor(Color(0x0022ff55), aColor));
+#else
+ CPPUNIT_ASSERT_EQUAL(Color(0x0022ff55), aColor);
+#endif
+
+ // Read back the BitmapEx and check the opaque pixel
+ aBitmap = pAlphaVirtualDevice->GetBitmapEx(Point(), Size(4, 4));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Height());
+
+ aColor = aBitmap.GetPixelColor(1, 1);
+#if defined _WIN32
+ CPPUNIT_ASSERT_LESS(6, deltaColor(Color(0x0022ff55), aColor));
+#else
+ CPPUNIT_ASSERT_EQUAL(Color(0x0022ff55), aColor);
+#endif
+
+ // Draw an semi-transparent pixel
+ pAlphaVirtualDevice->DrawPixel(Point(0, 0), Color(ColorTransparency, 0x44, 0x22, 0xff, 0x55));
+
+ aColor = pAlphaVirtualDevice->GetPixel(Point(0, 0));
+ // Read back the semi-transparent pixel
+#if defined _WIN32
+ CPPUNIT_ASSERT_LESS(6, deltaColor(Color(ColorTransparency, 0x4422FF55), aColor));
+#else
+ CPPUNIT_ASSERT_EQUAL(Color(ColorTransparency, 0x4422FF55), aColor);
+#endif
+
+ // Read back the BitmapEx and check the semi-transparent pixel
+ aBitmap = pAlphaVirtualDevice->GetBitmapEx(Point(), Size(4, 4));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), aBitmap.GetSizePixel().Height());
+
+ aColor = aBitmap.GetPixelColor(0, 0);
+#if defined _WIN32
+ CPPUNIT_ASSERT_LESS(6, deltaColor(Color(ColorTransparency, 0x4422FF55), aColor));
+#else
+ CPPUNIT_ASSERT_EQUAL(Color(ColorTransparency, 0x4422FF55), aColor);
+#endif
+#endif
+}
+
+void BitmapRenderTest::testTdf116888()
+{
+ // The image is a 8bit image with a non-grayscale palette. In OpenGL mode
+ // pdf export of the image was broken, because OpenGLSalBitmap::ReadTexture()
+ // didn't handle 8bit non-grayscale and moreover OpenGLSalBitmap::AcquireBuffer()
+ // didn't properly release mpUserBuffer after ReadTexture() failure.
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic;
+ const OUString aURL(getFullUrl(u"tdf116888.gif"));
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+ Bitmap aBitmap = aGraphic.GetBitmapEx().GetBitmap();
+ CPPUNIT_ASSERT(!aBitmap.IsEmpty());
+ aBitmap.Scale(0.8, 0.8); // This scaling discards mpUserData,
+ BitmapScopedReadAccess pAccess(aBitmap); // forcing ReadTexture() here.
+ // Check that there is mpUserBuffer content.
+ CPPUNIT_ASSERT(pAccess);
+ const ScanlineFormat eFormat = pAccess->GetScanlineFormat();
+ CPPUNIT_ASSERT_EQUAL(ScanlineFormat::N8BitPal, eFormat);
+ CPPUNIT_ASSERT(!aBitmap.HasGreyPaletteAny());
+ // HACK: Some rendering backends change white to #FEFEFE while scaling for some reason.
+ // That is pretty much white too in practice, so adjust for that.
+ BitmapColor white(COL_WHITE);
+ if (pAccess->GetColor(0, 0) == Color(0xfe, 0xfe, 0xfe))
+ white = Color(0xfe, 0xfe, 0xfe);
+ // Check that the image contents are also valid.
+ CPPUNIT_ASSERT_EQUAL(white, pAccess->GetColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(white, pAccess->GetColor(0, pAccess->Width() - 1));
+ CPPUNIT_ASSERT_EQUAL(white, pAccess->GetColor(pAccess->Height() - 1, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_BLACK),
+ pAccess->GetColor(pAccess->Height() - 1, pAccess->Width() - 1));
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BitmapRenderTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/bitmaprender/data/ImageRGBA.png b/vcl/qa/cppunit/bitmaprender/data/ImageRGBA.png
new file mode 100644
index 0000000000..7a8399293c
--- /dev/null
+++ b/vcl/qa/cppunit/bitmaprender/data/ImageRGBA.png
Binary files differ
diff --git a/vcl/qa/cppunit/bitmaprender/data/tdf104141.gif b/vcl/qa/cppunit/bitmaprender/data/tdf104141.gif
new file mode 100644
index 0000000000..d6390fdaa3
--- /dev/null
+++ b/vcl/qa/cppunit/bitmaprender/data/tdf104141.gif
Binary files differ
diff --git a/vcl/qa/cppunit/bitmaprender/data/tdf113918.png b/vcl/qa/cppunit/bitmaprender/data/tdf113918.png
new file mode 100644
index 0000000000..dd49897d99
--- /dev/null
+++ b/vcl/qa/cppunit/bitmaprender/data/tdf113918.png
Binary files differ
diff --git a/vcl/qa/cppunit/bitmaprender/data/tdf116888.gif b/vcl/qa/cppunit/bitmaprender/data/tdf116888.gif
new file mode 100644
index 0000000000..2953109491
--- /dev/null
+++ b/vcl/qa/cppunit/bitmaprender/data/tdf116888.gif
Binary files differ
diff --git a/vcl/qa/cppunit/blocklistparsertest.cxx b/vcl/qa/cppunit/blocklistparsertest.cxx
new file mode 100644
index 0000000000..689e8d9c9e
--- /dev/null
+++ b/vcl/qa/cppunit/blocklistparsertest.cxx
@@ -0,0 +1,174 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/types.h>
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+
+#include <unotest/bootstrapfixturebase.hxx>
+
+#include <driverblocklist.hxx>
+
+using namespace DriverBlocklist;
+
+namespace
+{
+
+class BlocklistParserTest : public test::BootstrapFixtureBase
+{
+ void testParse();
+ void testEvaluate();
+ void testVulkan();
+
+ CPPUNIT_TEST_SUITE(BlocklistParserTest);
+ CPPUNIT_TEST(testParse);
+ CPPUNIT_TEST(testEvaluate);
+ CPPUNIT_TEST(testVulkan);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void BlocklistParserTest::testParse()
+{
+ std::vector<DriverInfo> aDriveInfos;
+
+ Parser aBlocklistParser(m_directories.getURLFromSrc(u"vcl/qa/cppunit/") + "test_blocklist_parse.xml",
+ aDriveInfos, VersionType::OpenGL);
+ aBlocklistParser.parse();
+
+ size_t const n = aDriveInfos.size();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(16), n);
+
+ size_t i = 0;
+
+ for (bool bIsAllowlisted : {true, false})
+ {
+ DriverInfo& aDriveInfo = aDriveInfos[i++];
+ CPPUNIT_ASSERT_EQUAL(bIsAllowlisted, aDriveInfo.mbAllowlisted);
+ CPPUNIT_ASSERT_EQUAL(GetVendorId(VendorAll), aDriveInfo.maAdapterVendor); // "all"
+ CPPUNIT_ASSERT_EQUAL(VersionComparisonOp::DRIVER_LESS_THAN, aDriveInfo.meComparisonOp);
+ CPPUNIT_ASSERT_EQUAL(OpenGLVersion(10,20,30,40), aDriveInfo.mnDriverVersion);
+
+ aDriveInfo = aDriveInfos[i++];
+ CPPUNIT_ASSERT_EQUAL(bIsAllowlisted, aDriveInfo.mbAllowlisted);
+ CPPUNIT_ASSERT_EQUAL(GetVendorId(VendorNVIDIA), aDriveInfo.maAdapterVendor);
+ CPPUNIT_ASSERT_EQUAL(VersionComparisonOp::DRIVER_EQUAL, aDriveInfo.meComparisonOp);
+
+ aDriveInfo = aDriveInfos[i++];
+ CPPUNIT_ASSERT_EQUAL(bIsAllowlisted, aDriveInfo.mbAllowlisted);
+ CPPUNIT_ASSERT_EQUAL(GetVendorId(VendorMicrosoft), aDriveInfo.maAdapterVendor);
+ CPPUNIT_ASSERT_EQUAL(VersionComparisonOp::DRIVER_NOT_EQUAL, aDriveInfo.meComparisonOp);
+
+ aDriveInfo = aDriveInfos[i++];
+ CPPUNIT_ASSERT_EQUAL(bIsAllowlisted, aDriveInfo.mbAllowlisted);
+ CPPUNIT_ASSERT_EQUAL(OUString("0xcafe"), aDriveInfo.maAdapterVendor);
+ CPPUNIT_ASSERT_EQUAL(VersionComparisonOp::DRIVER_NOT_EQUAL, aDriveInfo.meComparisonOp);
+
+ aDriveInfo = aDriveInfos[i++];
+ CPPUNIT_ASSERT_EQUAL(bIsAllowlisted, aDriveInfo.mbAllowlisted);
+ CPPUNIT_ASSERT_EQUAL(GetVendorId(VendorAll), aDriveInfo.maAdapterVendor);
+ CPPUNIT_ASSERT_EQUAL(VersionComparisonOp::DRIVER_BETWEEN_EXCLUSIVE, aDriveInfo.meComparisonOp);
+
+ aDriveInfo = aDriveInfos[i++];
+ CPPUNIT_ASSERT_EQUAL(bIsAllowlisted, aDriveInfo.mbAllowlisted);
+ CPPUNIT_ASSERT_EQUAL(GetVendorId(VendorAll), aDriveInfo.maAdapterVendor);
+ CPPUNIT_ASSERT_EQUAL(VersionComparisonOp::DRIVER_BETWEEN_INCLUSIVE, aDriveInfo.meComparisonOp);
+
+ aDriveInfo = aDriveInfos[i++];
+ CPPUNIT_ASSERT_EQUAL(bIsAllowlisted, aDriveInfo.mbAllowlisted);
+ CPPUNIT_ASSERT_EQUAL(GetVendorId(VendorAll), aDriveInfo.maAdapterVendor);
+ CPPUNIT_ASSERT_EQUAL(VersionComparisonOp::DRIVER_BETWEEN_INCLUSIVE_START, aDriveInfo.meComparisonOp);
+
+ aDriveInfo = aDriveInfos[i++];
+ CPPUNIT_ASSERT_EQUAL(bIsAllowlisted, aDriveInfo.mbAllowlisted);
+ CPPUNIT_ASSERT_EQUAL(GetVendorId(VendorAll), aDriveInfo.maAdapterVendor);
+ CPPUNIT_ASSERT_EQUAL(VersionComparisonOp::DRIVER_COMPARISON_IGNORED, aDriveInfo.meComparisonOp);
+ }
+}
+
+void BlocklistParserTest::testEvaluate()
+{
+ std::vector<DriverInfo> aDriveInfos;
+
+ Parser aBlocklistParser(m_directories.getURLFromSrc(u"vcl/qa/cppunit/") + "test_blocklist_evaluate.xml",
+ aDriveInfos, VersionType::OpenGL);
+ aBlocklistParser.parse();
+
+ OUString vendorAMD = GetVendorId(VendorAMD);
+ OUString vendorNVIDIA = GetVendorId(VendorNVIDIA);
+ OUString vendorIntel = GetVendorId(VendorIntel);
+ OUString vendorMicrosoft = GetVendorId(VendorMicrosoft);
+
+ // Check OS
+ CPPUNIT_ASSERT_EQUAL(false, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, u"10.20.30.40", vendorNVIDIA, "all", DRIVER_OS_WINDOWS_7));
+ CPPUNIT_ASSERT_EQUAL(false, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, u"10.20.30.40", vendorNVIDIA, "all", DRIVER_OS_WINDOWS_8));
+ CPPUNIT_ASSERT_EQUAL(false, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, u"10.20.30.40", vendorNVIDIA, "all", DRIVER_OS_WINDOWS_10));
+
+ // Check generic OS
+ CPPUNIT_ASSERT_EQUAL(false, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, u"10.20.30.50", vendorMicrosoft, "all", DRIVER_OS_WINDOWS_10));
+ CPPUNIT_ASSERT_EQUAL(true, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, u"10.20.30.50", vendorMicrosoft, "all", DRIVER_OS_LINUX));
+ CPPUNIT_ASSERT_EQUAL(true, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, u"10.20.30.50", vendorMicrosoft, "all", DRIVER_OS_OSX_10_7));
+ CPPUNIT_ASSERT_EQUAL(true, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, u"10.20.30.50", vendorMicrosoft, "all", DRIVER_OS_OSX_10_8));
+
+ // Check Vendors
+ CPPUNIT_ASSERT_EQUAL(true, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, u"10.20.30.40", vendorMicrosoft, "all", DRIVER_OS_WINDOWS_7));
+ CPPUNIT_ASSERT_EQUAL(true, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, u"10.20.30.40", vendorMicrosoft, "all", DRIVER_OS_WINDOWS_10));
+
+ // Check Versions
+ CPPUNIT_ASSERT_EQUAL(true, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, u"10.20.30.39", vendorAMD, "all", DRIVER_OS_WINDOWS_7));
+ CPPUNIT_ASSERT_EQUAL(false, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, u"10.20.30.40", vendorAMD, "all", DRIVER_OS_WINDOWS_7));
+ CPPUNIT_ASSERT_EQUAL(false, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, u"10.20.30.41", vendorAMD, "all", DRIVER_OS_WINDOWS_7));
+
+ // Check
+ CPPUNIT_ASSERT_EQUAL(true, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::OpenGL, u"9.17.10.4229", vendorIntel, "all", DRIVER_OS_WINDOWS_7));
+
+
+}
+
+void BlocklistParserTest::testVulkan()
+{
+ std::vector<DriverInfo> aDriveInfos;
+
+ Parser aBlocklistParser(m_directories.getURLFromSrc(u"vcl/qa/cppunit/") + "test_blocklist_vulkan.xml",
+ aDriveInfos, VersionType::Vulkan);
+ aBlocklistParser.parse();
+
+ OUString vendorAMD = GetVendorId(VendorAMD);
+
+ // Check Versions
+ CPPUNIT_ASSERT_EQUAL(false, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::Vulkan, u"1.2.3", vendorAMD, "all", DRIVER_OS_ALL));
+ CPPUNIT_ASSERT_EQUAL(true, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::Vulkan, u"1.2.2", vendorAMD, "all", DRIVER_OS_ALL));
+ CPPUNIT_ASSERT_EQUAL(false, FindBlocklistedDeviceInList(
+ aDriveInfos, VersionType::Vulkan, u"1.2.20", vendorAMD, "all", DRIVER_OS_ALL));
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BlocklistParserTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/builder/demo.ui b/vcl/qa/cppunit/builder/demo.ui
new file mode 100644
index 0000000000..28d6fddb9e
--- /dev/null
+++ b/vcl/qa/cppunit/builder/demo.ui
@@ -0,0 +1,1883 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.1 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkListStore" id="liststore1">
+ <columns>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0">[None]</col>
+ </row>
+ <row>
+ <col id="0">Normal</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="liststore2">
+ <columns>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0">1</col>
+ </row>
+ <row>
+ <col id="0">2</col>
+ </row>
+ <row>
+ <col id="0">3</col>
+ </row>
+ <row>
+ <col id="0">4</col>
+ </row>
+ <row>
+ <col id="0">5</col>
+ </row>
+ <row>
+ <col id="0">6</col>
+ </row>
+ <row>
+ <col id="0">7</col>
+ </row>
+ <row>
+ <col id="0">8</col>
+ </row>
+ <row>
+ <col id="0">9</col>
+ </row>
+ <row>
+ <col id="0">10</col>
+ </row>
+ <row>
+ <col id="0">1 - 10</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkDialog" id="dialog1">
+ <property name="can-focus">False</property>
+ <property name="border-width">5</property>
+ <property name="type-hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="can-focus">False</property>
+ <property name="layout-style">end</property>
+ <child>
+ <object class="GtkButton" id="cancel">
+ <property name="label" translatable="yes" context="stock">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="ok">
+ <property name="label" translatable="yes" context="stock">_OK</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack-type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="notebook1">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <!-- n-columns=3 n-rows=3 -->
+ <object class="GtkGrid" id="grid1">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="row-spacing">4</property>
+ <property name="column-spacing">2</property>
+ <property name="row-homogeneous">True</property>
+ <property name="column-homogeneous">True</property>
+ <child>
+ <object class="GtkLabel" id="labelfoo">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">cell 1.1</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">cell 3.3</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">2</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">A label that spans three rows</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box5">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton1">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="truncate-multiline">True</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combobox1">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box4">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkButton" id="button4">
+ <property name="label">EXPAND</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="tooltip-text">A tooltip example</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button5">
+ <property name="label">FILL</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box3">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkButton" id="button3">
+ <property name="label">button</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton1">
+ <property name="label">radiobutton</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="active">True</property>
+ <property name="draw-indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="checkbutton1">
+ <property name="label">checkbutton</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label-xalign">0</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkBox" id="box2">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">left</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">right</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry1">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="activates-default">True</property>
+ <property name="truncate-multiline">True</property>
+ <property name="text">an edit control</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Frame Label</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="Tab1">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">page 1</property>
+ </object>
+ <packing>
+ <property name="tab-fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box6">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkFrame" id="frame2">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label-xalign">0</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <!-- n-columns=4 n-rows=5 -->
+ <object class="GtkGrid" id="grid2">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="column-spacing">5</property>
+ <property name="row-homogeneous">True</property>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">_Number of title pages</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">NF_PAGE_COUNT</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Place title pages at</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">pages</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">2</property>
+ <property name="top-attach">2</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="NF_PAGE_COUNT">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="truncate-multiline">True</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="NF_PAGE_START">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can-focus">True</property>
+ <property name="numeric">True</property>
+ <property name="truncate-multiline">True</property>
+ <accessibility>
+ <relation type="labelled-by" target="RB_PAGE_START"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="left-attach">2</property>
+ <property name="top-attach">4</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="RB_USE_EXISTING_PAGES">
+ <property name="label">Converting existing pages to title pages</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <property name="group">RB_INSERT_NEW_PAGES</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ <property name="width">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="RB_INSERT_NEW_PAGES">
+ <property name="label">Insert new title pages</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <property name="group">RB_USE_EXISTING_PAGES</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ <property name="width">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="RB_DOCUMENT_START">
+ <property name="label">Document Start</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <property name="group">RB_PAGE_START</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">3</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="RB_PAGE_START">
+ <property name="label">Page</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <property name="group">RB_DOCUMENT_START</property>
+ <accessibility>
+ <relation type="label-for" target="NF_PAGE_START"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Make Title Pages</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame3">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label-xalign">0</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkBox" id="box7">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="orientation">vertical</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkCheckButton" id="CB_RESTART_NUMBERING">
+ <property name="label">Reset Page Numbering after title pages</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box8">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkLabel" id="FT_PAGE_COUNT">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Page number</property>
+ <accessibility>
+ <relation type="label-for" target="NF_RESTART_NUMBERING"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="NF_RESTART_NUMBERING">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can-focus">True</property>
+ <property name="numeric">True</property>
+ <property name="truncate-multiline">True</property>
+ <accessibility>
+ <relation type="labelled-by" target="FT_PAGE_COUNT"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="CB_SET_PAGE_NUMBER">
+ <property name="label">Set Page Number for first title page</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box9">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkLabel" id="FT_PAGE_PAGES">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Page number</property>
+ <accessibility>
+ <relation type="label-for" target="NF_SET_PAGE_NUMBER"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="NF_SET_PAGE_NUMBER">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can-focus">True</property>
+ <property name="numeric">True</property>
+ <property name="truncate-multiline">True</property>
+ <accessibility>
+ <relation type="labelled-by" target="FT_PAGE_PAGES"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Page Numbering</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame4">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label-xalign">0</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkBox" id="box10">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <child>
+ <object class="GtkComboBox" id="LB_PAGE_PROPERTIES">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="PB_PAGE_PROPERTIES">
+ <property name="label">Edit...</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Edit Page Properties</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="Tab2">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">page 2</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab-fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame5">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label-xalign">0</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkBox" id="box11">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="2">
+ <property name="label">Line break</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="active">True</property>
+ <property name="draw-indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="3">
+ <property name="label">Column break</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <property name="group">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="1">
+ <property name="label">Page break</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <property name="group">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="4">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Style</property>
+ <property name="mnemonic-widget">5</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="5">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="model">liststore1</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="7">
+ <property name="label">Change page number</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <accessibility>
+ <relation type="label-for" target="8"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box12">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkSpinButton" id="8">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="truncate-multiline">True</property>
+ <accessibility>
+ <relation type="labelled-by" target="7"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="6">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Type</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="Tab3">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">page 3</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab-fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box13">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkFrame" id="frame6">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label-xalign">0</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkBox" id="box14">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="15">
+ <property name="label">Optimal</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="active">True</property>
+ <property name="draw-indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="16">
+ <property name="label">Fit width and height</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <property name="group">15</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="17">
+ <property name="label">Fit width</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <property name="group">15</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="18">
+ <property name="label">100%</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <property name="group">15</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box15">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkRadioButton" id="19">
+ <property name="label">Variable</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <property name="group">15</property>
+ <accessibility>
+ <relation type="label-for" target="20"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="20">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="truncate-multiline">True</property>
+ <accessibility>
+ <relation type="labelled-by" target="19"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="14">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Zoom Factor</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame7">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label-xalign">0</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkBox" id="box16">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="22">
+ <property name="label">Automatic</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="active">True</property>
+ <property name="draw-indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="23">
+ <property name="label">Single page</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <property name="group">22</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box17">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkRadioButton" id="24">
+ <property name="label">Columns</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <property name="group">22</property>
+ <accessibility>
+ <relation type="label-for" target="25"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="25">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="truncate-multiline">True</property>
+ <accessibility>
+ <relation type="labelled-by" target="24"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="26">
+ <property name="label">Book mode</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="margin-start">10</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="21">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">View Layout</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">page 4</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab-fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box18">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">10</property>
+ <property name="margin-end">10</property>
+ <child>
+ <object class="GtkFrame" id="frame8">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label-xalign">0</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkTreeView" id="treeview1">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="margin-start">12</property>
+ <property name="model">liststore2</property>
+ <property name="headers-visible">False</property>
+ <property name="search-column">0</property>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label15">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Level</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame9">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label-xalign">0</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkBox" id="box19">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkBox" id="box20">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <!-- n-columns=2 n-rows=9 -->
+ <object class="GtkGrid" id="grid3">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="column-spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="label16">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Paragraph Style</property>
+ <property name="mnemonic-widget">combobox2</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combobox2">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label17">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Number</property>
+ <property name="mnemonic-widget">combobox3</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label18">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Character Style</property>
+ <property name="mnemonic-widget">combobox4</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label19">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Show sublevels</property>
+ <property name="mnemonic-widget">spinbutton2</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label20">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Separator</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">5</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combobox3">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combobox4">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton2">
+ <property name="visible">True</property>
+ <property name="truncate-multiline">True</property>
+ <property name="can-focus">True</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry2">
+ <property name="visible">True</property>
+ <property name="truncate-multiline">True</property>
+ <property name="can-focus">True</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry3">
+ <property name="visible">True</property>
+ <property name="truncate-multiline">True</property>
+ <property name="can-focus">True</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton3">
+ <property name="visible">True</property>
+ <property name="truncate-multiline">True</property>
+ <property name="can-focus">True</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label23">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Start at</property>
+ <property name="mnemonic-widget">spinbutton3</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label22">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">20</property>
+ <property name="label">After</property>
+ <property name="mnemonic-widget">entry3</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label21">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">20</property>
+ <property name="label">Before</property>
+ <property name="mnemonic-widget">entry2</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">6</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkDrawingArea" id="drawingarea1">
+ <property name="width-request">150</property>
+ <property name="height-request">200</property>
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label24">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Numbering</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">page 5</property>
+ </object>
+ <packing>
+ <property name="position">4</property>
+ <property name="tab-fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box21">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">10</property>
+ <property name="margin-end">10</property>
+ <child>
+ <object class="GtkFrame" id="frame10">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label-xalign">0</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkTreeView" id="treeview2">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="margin-start">12</property>
+ <property name="model">liststore2</property>
+ <property name="headers-visible">False</property>
+ <property name="search-column">0</property>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label26">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Level</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame11">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label-xalign">0</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <!-- n-columns=2 n-rows=6 -->
+ <object class="GtkGrid" id="grid4">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="row-spacing">10</property>
+ <property name="column-spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="label27">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Numbering followed by</property>
+ <property name="mnemonic-widget">combobox5</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label28">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Numbering Alignment</property>
+ <property name="mnemonic-widget">combobox6</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label29">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Aligned at</property>
+ <property name="mnemonic-widget">spinbutton6</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label30">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Indent at</property>
+ <property name="mnemonic-widget">spinbutton4</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combobox5">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton4">
+ <property name="visible">True</property>
+ <property name="truncate-multiline">True</property>
+ <property name="can-focus">True</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label31">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">at</property>
+ <property name="mnemonic-widget">spinbutton5</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton5">
+ <property name="visible">True</property>
+ <property name="truncate-multiline">True</property>
+ <property name="can-focus">True</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton6">
+ <property name="visible">True</property>
+ <property name="truncate-multiline">True</property>
+ <property name="can-focus">True</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combobox6">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkDrawingArea" id="drawingarea2">
+ <property name="width-request">300</property>
+ <property name="height-request">150</property>
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button6">
+ <property name="label">Default</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="valign">end</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">5</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label32">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Position and Spacing</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label25">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">page 6</property>
+ </object>
+ <packing>
+ <property name="position">5</property>
+ <property name="tab-fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkExpander" id="expander1">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="resize-toplevel">True</property>
+ <child>
+ <!-- n-columns=2 n-rows=3 -->
+ <object class="GtkGrid" id="grid5">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="row-spacing">200</property>
+ <property name="column-spacing">400</property>
+ <child>
+ <object class="GtkLabel" id="label34">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">label</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label35">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">label</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label36">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">label</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label37">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">label</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label38">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">label</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label39">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">label</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label40">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">Details</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label33">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">page 7</property>
+ </object>
+ <packing>
+ <property name="position">6</property>
+ <property name="tab-fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">cancel</action-widget>
+ <action-widget response="-5">ok</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/vcl/qa/cppunit/canvasbitmaptest.cxx b/vcl/qa/cppunit/canvasbitmaptest.cxx
new file mode 100644
index 0000000000..91db2e4f73
--- /dev/null
+++ b/vcl/qa/cppunit/canvasbitmaptest.cxx
@@ -0,0 +1,778 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+// bootstrap stuff
+#include <test/bootstrapfixture.hxx>
+
+#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 <com/sun/star/rendering/XIntegerReadOnlyBitmap.hpp>
+#include <com/sun/star/rendering/XIntegerBitmapColorSpace.hpp>
+#include <com/sun/star/rendering/XBitmapPalette.hpp>
+
+#include <cppuhelper/implbase.hxx>
+#include <rtl/ref.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/canvastools.hxx>
+#include <vcl/bitmapex.hxx>
+
+#include <canvasbitmap.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <algorithm>
+
+using namespace ::com::sun::star;
+using namespace vcl::unotools;
+
+namespace com::sun::star::rendering
+{
+
+static bool operator==( const RGBColor& rLHS, const ARGBColor& rRHS )
+{
+ return rLHS.Red == rRHS.Red && rLHS.Green == rRHS.Green && rLHS.Blue == rRHS.Blue;
+}
+
+}
+
+namespace
+{
+
+class CanvasBitmapTest : public test::BootstrapFixture
+{
+public:
+ CanvasBitmapTest() : BootstrapFixture(true, false) {}
+
+ void runTest();
+
+ CPPUNIT_TEST_SUITE(CanvasBitmapTest);
+ CPPUNIT_TEST(runTest);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+bool rangeCheck( const rendering::RGBColor& rColor )
+{
+ return rColor.Red < 0.0 || rColor.Red > 1.0 ||
+ rColor.Green < 0.0 || rColor.Green > 1.0 ||
+ rColor.Blue < 0.0 || rColor.Blue > 1.0;
+}
+
+void checkCanvasBitmap( const rtl::Reference<VclCanvasBitmap>& xBmp,
+ const char* msg,
+ int nOriginalDepth )
+{
+ SAL_INFO("vcl", "Testing " << msg << ", with depth " << nOriginalDepth);
+
+ BitmapEx aContainedBmpEx( xBmp->getBitmapEx() );
+ Bitmap aContainedBmp( aContainedBmpEx.GetBitmap() );
+ int nDepth = nOriginalDepth;
+ int extraBpp = 0;
+
+ {
+ BitmapScopedReadAccess pAcc( aContainedBmp );
+ nDepth = pAcc->GetBitCount();
+ if( pAcc->GetScanlineFormat() == ScanlineFormat::N32BitTcMask )
+ extraBpp = 8; // the format has 8 unused bits
+ }
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Original bitmap size not (200,200)",
+ Size(200,200), aContainedBmp.GetSizePixel());
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Original bitmap size via API not (200,200)",
+ sal_Int32(200), xBmp->getSize().Width);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Original bitmap size via API not (200,200)",
+ sal_Int32(200), xBmp->getSize().Height);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "alpha state mismatch",
+ aContainedBmpEx.IsAlpha(), bool(xBmp->hasAlpha()));
+
+ CPPUNIT_ASSERT_MESSAGE( "getScaledBitmap() failed",
+ xBmp->getScaledBitmap( geometry::RealSize2D(500,500), false ).is());
+
+ rendering::IntegerBitmapLayout aLayout;
+ uno::Sequence<sal_Int8> aPixelData = xBmp->getData(aLayout, geometry::IntegerRectangle2D(0,0,1,1));
+
+ const sal_Int32 nExpectedBitsPerPixel(
+ (aContainedBmpEx.IsAlpha() ? std::max(8,nDepth)+8 : nDepth) + extraBpp);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "# scanlines not 1",
+ static_cast<sal_Int32>(1), aLayout.ScanLines);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "# scanline bytes mismatch",
+ static_cast<sal_Int32>((nExpectedBitsPerPixel+7)/8), aLayout.ScanLineBytes);
+ CPPUNIT_ASSERT_MESSAGE( "# scanline stride mismatch",
+ aLayout.ScanLineStride == (nExpectedBitsPerPixel+7)/8 ||
+ aLayout.ScanLineStride == -(nExpectedBitsPerPixel+7)/8);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "# plane stride not 0",
+ static_cast<sal_Int32>(0), aLayout.PlaneStride);
+
+ CPPUNIT_ASSERT_MESSAGE( "Color space not there",
+ aLayout.ColorSpace.is());
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Palette existence does not conform to bitmap",
+ (nDepth <= 8), aLayout.Palette.is());
+
+ uno::Sequence<sal_Int8> aPixelData2 = xBmp->getPixel( aLayout, geometry::IntegerPoint2D(0,0) );
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "getData and getPixel did not return same amount of data",
+ aPixelData.getLength(), aPixelData2.getLength());
+
+ aPixelData = xBmp->getData(aLayout, geometry::IntegerRectangle2D(0,0,200,1));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "# scanlines not 1 for getPixel",
+ static_cast<sal_Int32>(1), aLayout.ScanLines);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "# scanline bytes mismatch for getPixel",
+ static_cast<sal_Int32>((200*nExpectedBitsPerPixel+7)/8), aLayout.ScanLineBytes);
+ CPPUNIT_ASSERT_MESSAGE( "# scanline stride mismatch for getPixel",
+ aLayout.ScanLineStride == (200*nExpectedBitsPerPixel+7)/8 ||
+ aLayout.ScanLineStride == -(200*nExpectedBitsPerPixel+7)/8);
+
+ uno::Sequence< rendering::RGBColor > aRGBColors = xBmp->convertIntegerToRGB( aPixelData );
+ uno::Sequence< rendering::ARGBColor > aARGBColors = xBmp->convertIntegerToARGB( aPixelData );
+
+ const rendering::RGBColor* pRGBStart ( aRGBColors.getConstArray() );
+ const rendering::RGBColor* pRGBEnd ( aRGBColors.getConstArray()+aRGBColors.getLength() );
+ const rendering::ARGBColor* pARGBStart( aARGBColors.getConstArray() );
+ std::pair<const rendering::RGBColor*,
+ const rendering::ARGBColor*> aRes = std::mismatch( pRGBStart, pRGBEnd, pARGBStart );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "argb and rgb colors are not equal",
+ pRGBEnd, aRes.first);
+
+ CPPUNIT_ASSERT_MESSAGE( "rgb colors are not within [0,1] range",
+ std::none_of(pRGBStart,pRGBEnd,&rangeCheck));
+
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ "First pixel is not white", 1.0, pRGBStart[0].Red, 1E-12);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ "First pixel is not white", 1.0, pRGBStart[0].Green, 1E-12);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ "First pixel is not white", 1.0, pRGBStart[0].Blue, 1E-12);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ "Second pixel is not opaque", 1.0, pARGBStart[1].Alpha, 1E-12);
+ if( aContainedBmpEx.IsAlpha() )
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "First pixel is not fully transparent",
+ 0.0, pARGBStart[0].Alpha);
+ }
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Second pixel is not black",
+ 0.0, pRGBStart[1].Red);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Second pixel is not black",
+ 0.0, pRGBStart[1].Green);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Second pixel is not black",
+ 0.0, pRGBStart[1].Blue);
+
+ if( nOriginalDepth > 8 )
+ {
+ const Color aCol(COL_GREEN);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "Sixth pixel is not green (red component)",
+ vcl::unotools::toDoubleColor(aCol.GetRed()), pRGBStart[5].Red);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "Sixth pixel is not green (green component)",
+ vcl::unotools::toDoubleColor(aCol.GetGreen()), pRGBStart[5].Green);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "Sixth pixel is not green (blue component)",
+ vcl::unotools::toDoubleColor(aCol.GetBlue()), pRGBStart[5].Blue);
+ }
+ else if( nDepth <= 8 )
+ {
+ uno::Reference<rendering::XBitmapPalette> xPal = xBmp->getPalette();
+ CPPUNIT_ASSERT_MESSAGE( "8bit or less: missing palette",
+ xPal.is());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Palette incorrect entry count",
+ static_cast<sal_Int32>(1 << nOriginalDepth), xPal->getNumberOfEntries());
+ uno::Sequence<double> aIndex;
+ CPPUNIT_ASSERT_MESSAGE( "Palette is not read-only",
+ !xPal->setIndex(aIndex,true,0));
+ CPPUNIT_ASSERT_MESSAGE( "Palette entry 0 is not opaque",
+ xPal->getIndex(aIndex,0));
+ CPPUNIT_ASSERT_MESSAGE( "Palette has no valid color space",
+ xPal->getColorSpace().is());
+ }
+
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ "150th pixel is not white", 1.0, pRGBStart[150].Red, 1E-12);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ "150th pixel is not white", 1.0, pRGBStart[150].Green, 1E-12);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ "150th pixel is not white", 1.0, pRGBStart[150].Blue, 1E-12);
+
+ if( nOriginalDepth <= 8 )
+ return;
+
+ uno::Sequence<sal_Int8> aPixel3, aPixel4;
+
+ const Color aCol(COL_GREEN);
+ uno::Sequence<rendering::ARGBColor> aARGBColor
+ {
+ {
+ 1.0,
+ vcl::unotools::toDoubleColor(aCol.GetRed()),
+ vcl::unotools::toDoubleColor(aCol.GetGreen()),
+ vcl::unotools::toDoubleColor(aCol.GetBlue())
+ }
+ };
+
+ uno::Sequence<rendering::RGBColor> aRGBColor
+ {
+ {
+ vcl::unotools::toDoubleColor(aCol.GetRed()),
+ vcl::unotools::toDoubleColor(aCol.GetGreen()),
+ vcl::unotools::toDoubleColor(aCol.GetBlue())
+ }
+ };
+
+ aPixel3 = xBmp->convertIntegerFromARGB( aARGBColor );
+ aPixel4 = xBmp->getPixel( aLayout, geometry::IntegerPoint2D(5,0) );
+ CPPUNIT_ASSERT_MESSAGE( "Green pixel from bitmap mismatch with manually converted green pixel",
+ bool(aPixel3 == aPixel4));
+
+ if( !aContainedBmpEx.IsAlpha() )
+ {
+ aPixel3 = xBmp->convertIntegerFromRGB( aRGBColor );
+ CPPUNIT_ASSERT_MESSAGE( "Green pixel from bitmap mismatch with manually RGB-converted green pixel",
+ bool(aPixel3 == aPixel4));
+ }
+
+}
+
+class TestBitmap : public cppu::WeakImplHelper< rendering::XIntegerReadOnlyBitmap,
+ rendering::XBitmapPalette,
+ rendering::XIntegerBitmapColorSpace >
+{
+private:
+ geometry::IntegerSize2D maSize;
+ uno::Sequence<sal_Int8> maComponentTags;
+ uno::Sequence<sal_Int32> maComponentBitCounts;
+ rendering::IntegerBitmapLayout maLayout;
+ const sal_Int32 mnBitsPerPixel;
+
+ // XBitmap
+ virtual geometry::IntegerSize2D SAL_CALL getSize() override { return maSize; }
+ virtual sal_Bool SAL_CALL hasAlpha( ) override { return mnBitsPerPixel != 8; }
+ virtual uno::Reference< rendering::XBitmap > SAL_CALL getScaledBitmap( const geometry::RealSize2D&,
+ sal_Bool ) override { return this; }
+
+ // XIntegerReadOnlyBitmap
+ virtual uno::Sequence< ::sal_Int8 > SAL_CALL getData( rendering::IntegerBitmapLayout& bitmapLayout,
+ const geometry::IntegerRectangle2D& rect ) override
+ {
+ CPPUNIT_ASSERT_MESSAGE( "X1 out of bounds", rect.X1 >= 0 );
+ CPPUNIT_ASSERT_MESSAGE( "Y1 out of bounds", rect.Y1 >= 0 );
+ CPPUNIT_ASSERT_MESSAGE( "X2 out of bounds", rect.X2 <= maSize.Width );
+ CPPUNIT_ASSERT_MESSAGE( "Y2 out of bounds", rect.Y2 <= maSize.Height );
+
+ bitmapLayout = getMemoryLayout();
+
+ const sal_Int32 nWidth = rect.X2-rect.X1;
+ const sal_Int32 nHeight = rect.Y2-rect.Y1;
+ const sal_Int32 nScanlineLen = (nWidth * mnBitsPerPixel + 7)/8;
+ uno::Sequence<sal_Int8> aRes( nScanlineLen * nHeight );
+ sal_Int8* pOut = aRes.getArray();
+
+ bitmapLayout.ScanLines = nHeight;
+ bitmapLayout.ScanLineBytes =
+ bitmapLayout.ScanLineStride= nScanlineLen;
+
+ if( mnBitsPerPixel == 8 )
+ {
+ for( sal_Int32 y=0; y<nHeight; ++y )
+ {
+ for( sal_Int32 x=0; x<nWidth; ++x )
+ pOut[ y*nScanlineLen + x ] = sal_Int8(x);
+ }
+ }
+ else
+ {
+ for( sal_Int32 y=0; y<nHeight; ++y )
+ {
+ for( sal_Int32 x=0; x<nWidth; ++x )
+ {
+ pOut[ y*nScanlineLen + 4*x ] = sal_Int8(rect.X1);
+ pOut[ y*nScanlineLen + 4*x + 1 ] = sal_Int8(rect.Y2);
+ pOut[ y*nScanlineLen + 4*x + 2 ] = sal_Int8(x);
+ pOut[ y*nScanlineLen + 4*x + 3 ] = sal_Int8(rect.Y1);
+ }
+ }
+ }
+
+ return aRes;
+ }
+
+ virtual uno::Sequence< ::sal_Int8 > SAL_CALL getPixel( rendering::IntegerBitmapLayout&,
+ const geometry::IntegerPoint2D& ) override
+ {
+ CPPUNIT_ASSERT_MESSAGE("getPixel: method not implemented", false);
+ return uno::Sequence< sal_Int8 >();
+ }
+
+ /// @throws uno::RuntimeException
+ uno::Reference< rendering::XBitmapPalette > getPalette( )
+ {
+ uno::Reference< XBitmapPalette > aRet;
+ if( mnBitsPerPixel == 8 )
+ aRet.set(this);
+ return aRet;
+ }
+
+ virtual rendering::IntegerBitmapLayout SAL_CALL getMemoryLayout( ) override
+ {
+ rendering::IntegerBitmapLayout aLayout( maLayout );
+
+ const sal_Int32 nScanlineLen = (maSize.Width * mnBitsPerPixel + 7)/8;
+
+ aLayout.ScanLines = maSize.Height;
+ aLayout.ScanLineBytes =
+ aLayout.ScanLineStride= nScanlineLen;
+ aLayout.Palette = getPalette();
+ aLayout.ColorSpace.set( this );
+
+ return aLayout;
+ }
+
+ // XBitmapPalette
+ virtual sal_Int32 SAL_CALL getNumberOfEntries() override
+ {
+ CPPUNIT_ASSERT_MESSAGE( "Got palette getNumberOfEntries interface call without handing out palette",
+ getPalette().is() );
+
+ return 255;
+ }
+
+ virtual sal_Bool SAL_CALL getIndex( uno::Sequence< double >& entry,
+ ::sal_Int32 nIndex ) override
+ {
+ CPPUNIT_ASSERT_MESSAGE( "Got palette getIndex interface call without handing out palette",
+ getPalette().is() );
+ CPPUNIT_ASSERT_MESSAGE( "getIndex: index out of range",
+ nIndex >= 0 );
+ CPPUNIT_ASSERT_MESSAGE( "getIndex: index out of range",
+ nIndex < 256 );
+ entry = colorToStdColorSpaceSequence(
+ Color(sal_uInt8(nIndex),
+ sal_uInt8(nIndex),
+ sal_uInt8(nIndex)) );
+
+ return true; // no palette transparency here.
+ }
+
+ virtual sal_Bool SAL_CALL setIndex( const uno::Sequence< double >&,
+ sal_Bool,
+ ::sal_Int32 nIndex ) override
+ {
+ CPPUNIT_ASSERT_MESSAGE( "Got palette setIndex interface call without handing out palette",
+ getPalette().is());
+ CPPUNIT_ASSERT_MESSAGE( "setIndex: index out of range",
+ nIndex >= 0);
+ CPPUNIT_ASSERT_MESSAGE( "setIndex: index out of range",
+ nIndex < 256);
+ return false;
+ }
+
+ virtual uno::Reference< rendering::XColorSpace > SAL_CALL getColorSpace( ) override
+ {
+ // this is the method from XBitmapPalette. Return palette color
+ // space here
+ static uno::Reference<rendering::XColorSpace> aColorSpace =
+ vcl::unotools::createStandardColorSpace();
+ return aColorSpace;
+ }
+
+ // XIntegerBitmapColorSpace
+ virtual ::sal_Int8 SAL_CALL getType( ) override
+ {
+ return rendering::ColorSpaceType::RGB;
+ }
+
+ virtual uno::Sequence< sal_Int8 > SAL_CALL getComponentTags( ) override
+ {
+ return maComponentTags;
+ }
+
+ virtual ::sal_Int8 SAL_CALL getRenderingIntent( ) override
+ {
+ return rendering::RenderingIntent::PERCEPTUAL;
+ }
+
+ virtual uno::Sequence< beans::PropertyValue > SAL_CALL getProperties() override
+ {
+ CPPUNIT_ASSERT_MESSAGE("getProperties: method not implemented", false );
+ return uno::Sequence< ::beans::PropertyValue >();
+ }
+
+ virtual uno::Sequence< double > SAL_CALL convertColorSpace( const uno::Sequence< double >&,
+ const uno::Reference< rendering::XColorSpace >& ) override
+ {
+ CPPUNIT_ASSERT_MESSAGE("convertColorSpace: method not implemented", false);
+ return uno::Sequence< double >();
+ }
+
+ virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertToRGB( const uno::Sequence< double >& ) override
+ {
+ CPPUNIT_ASSERT_MESSAGE("convertToRGB: method not implemented", false);
+ return uno::Sequence< rendering::RGBColor >();
+ }
+
+ virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToARGB( const uno::Sequence< double >& ) override
+ {
+ CPPUNIT_ASSERT_MESSAGE("convertToARGB: method not implemented", false);
+ return uno::Sequence< rendering::ARGBColor >();
+ }
+
+ virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToPARGB( const uno::Sequence< double >& ) override
+ {
+ CPPUNIT_ASSERT_MESSAGE("convertToPARGB: method not implemented", false);
+ return uno::Sequence< rendering::ARGBColor >();
+ }
+
+ virtual uno::Sequence< double > SAL_CALL convertFromRGB( const uno::Sequence< rendering::RGBColor >& ) override
+ {
+ CPPUNIT_ASSERT_MESSAGE("convertFromRGB: method not implemented", false);
+ return uno::Sequence< double >();
+ }
+
+ virtual uno::Sequence< double > SAL_CALL convertFromARGB( const uno::Sequence< rendering::ARGBColor >& ) override
+ {
+ CPPUNIT_ASSERT_MESSAGE("convertFromARGB: this method is not expected to be called!", false);
+ return uno::Sequence< double >();
+ }
+
+ virtual uno::Sequence< double > SAL_CALL convertFromPARGB( const uno::Sequence< rendering::ARGBColor >& ) override
+ {
+ CPPUNIT_ASSERT_MESSAGE("convertFromPARGB: this method is not expected to be called!", false);
+ return uno::Sequence< double >();
+ }
+
+ virtual ::sal_Int32 SAL_CALL getBitsPerPixel( ) override
+ {
+ return mnBitsPerPixel;
+ }
+
+ virtual uno::Sequence< ::sal_Int32 > SAL_CALL getComponentBitCounts( ) override
+ {
+ return maComponentBitCounts;
+ }
+
+ virtual ::sal_Int8 SAL_CALL getEndianness( ) override
+ {
+ return util::Endianness::LITTLE;
+ }
+
+ virtual uno::Sequence< double > SAL_CALL convertFromIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& ,
+ const uno::Reference< rendering::XColorSpace >& ) override
+ {
+ CPPUNIT_ASSERT_MESSAGE("convertFromIntegerColorSpace: method not implemented", false);
+ return uno::Sequence< double >();
+ }
+
+ virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertToIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& ,
+ const uno::Reference< rendering::XIntegerBitmapColorSpace >& ) override
+ {
+ CPPUNIT_ASSERT_MESSAGE("convertToIntegerColorSpace: method not implemented", false);
+ return uno::Sequence< sal_Int8 >();
+ }
+
+ virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertIntegerToRGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override
+ {
+ const uno::Sequence< rendering::ARGBColor > aTemp( convertIntegerToARGB(deviceColor) );
+ uno::Sequence< rendering::RGBColor > aRes( aTemp.getLength() );
+ std::transform(aTemp.begin(), aTemp.end(), aRes.getArray(),
+ [](const rendering::ARGBColor& rColor) {
+ return rendering::RGBColor(rColor.Red,
+ rColor.Green,
+ rColor.Blue);
+ });
+
+ return aRes;
+ }
+
+ virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertIntegerToARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override
+ {
+ const std::size_t nLen( deviceColor.getLength() );
+ const sal_Int32 nBytesPerPixel(mnBitsPerPixel == 8 ? 1 : 4);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("number of channels no multiple of pixel element count",
+ 0, static_cast<int>(nLen%nBytesPerPixel));
+
+ uno::Sequence< rendering::ARGBColor > aRes( nLen / nBytesPerPixel );
+
+ if( getPalette().is() )
+ {
+ std::transform(deviceColor.begin(), deviceColor.end(), aRes.getArray(),
+ [](sal_Int8 nIn) {
+ auto fColor = vcl::unotools::toDoubleColor(nIn);
+ return rendering::ARGBColor(1.0, fColor, fColor, fColor);
+ });
+ }
+ else
+ {
+ rendering::ARGBColor* pOut( aRes.getArray() );
+ for( std::size_t i=0; i<nLen; i+=4 )
+ {
+ *pOut++ = rendering::ARGBColor(
+ vcl::unotools::toDoubleColor(deviceColor[i+3]),
+ vcl::unotools::toDoubleColor(deviceColor[i+0]),
+ vcl::unotools::toDoubleColor(deviceColor[i+1]),
+ vcl::unotools::toDoubleColor(deviceColor[i+2]));
+ }
+ }
+
+ return aRes;
+ }
+
+ virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertIntegerToPARGB(
+ const uno::Sequence< ::sal_Int8 >& deviceColor) override
+ {
+ const std::size_t nLen( deviceColor.getLength() );
+ const sal_Int32 nBytesPerPixel(mnBitsPerPixel == 8 ? 1 : 4);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("number of channels no multiple of pixel element count",
+ 0, static_cast<int>(nLen%nBytesPerPixel));
+
+ uno::Sequence< rendering::ARGBColor > aRes( nLen / nBytesPerPixel );
+
+ if( getPalette().is() )
+ {
+ std::transform(deviceColor.begin(), deviceColor.end(), aRes.getArray(),
+ [](sal_Int8 nIn) {
+ auto fColor = vcl::unotools::toDoubleColor(nIn);
+ return rendering::ARGBColor(1.0, fColor, fColor, fColor);
+ });
+ }
+ else
+ {
+ rendering::ARGBColor* pOut( aRes.getArray() );
+ for( std::size_t i=0; i<nLen; i+=4 )
+ {
+ const double fAlpha=vcl::unotools::toDoubleColor(deviceColor[i+3]);
+ *pOut++ = rendering::ARGBColor(
+ fAlpha,
+ fAlpha*vcl::unotools::toDoubleColor(deviceColor[i+0]),
+ fAlpha*vcl::unotools::toDoubleColor(deviceColor[i+1]),
+ fAlpha*vcl::unotools::toDoubleColor(deviceColor[i+2]));
+ }
+ }
+
+ return aRes;
+ }
+
+ virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromRGB(
+ const uno::Sequence< rendering::RGBColor >&) override
+ {
+ CPPUNIT_ASSERT_MESSAGE("convertIntegerFromRGB: method not implemented", false);
+ return uno::Sequence< sal_Int8 >();
+ }
+
+ virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromARGB( const uno::Sequence< rendering::ARGBColor >& ) override
+ {
+ CPPUNIT_ASSERT_MESSAGE("convertIntegerFromARGB: method not implemented", false);
+ return uno::Sequence< sal_Int8 >();
+ }
+
+ virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromPARGB( const uno::Sequence< rendering::ARGBColor >& ) override
+ {
+ CPPUNIT_ASSERT_MESSAGE("convertIntegerFromPARGB: method not implemented", false);
+ return uno::Sequence< sal_Int8 >();
+ }
+
+public:
+ TestBitmap( const geometry::IntegerSize2D& rSize, bool bPalette ) :
+ maSize(rSize),
+ maComponentTags(),
+ maComponentBitCounts(),
+ maLayout(),
+ mnBitsPerPixel( bPalette ? 8 : 32 )
+ {
+ if( bPalette )
+ {
+ maComponentTags = { rendering::ColorComponentTag::INDEX };
+ maComponentBitCounts = { 8 };
+ }
+ else
+ {
+ maComponentTags.realloc(4);
+ sal_Int8* pTags = maComponentTags.getArray();
+ pTags[0] = rendering::ColorComponentTag::RGB_BLUE;
+ pTags[1] = rendering::ColorComponentTag::RGB_GREEN;
+ pTags[2] = rendering::ColorComponentTag::RGB_RED;
+ pTags[3] = rendering::ColorComponentTag::ALPHA;
+
+ maComponentBitCounts.realloc(4);
+ sal_Int32* pCounts = maComponentBitCounts.getArray();
+ pCounts[0] = 8;
+ pCounts[1] = 8;
+ pCounts[2] = 8;
+ pCounts[3] = 8;
+ }
+
+ maLayout.ScanLines = 0;
+ maLayout.ScanLineBytes = 0;
+ maLayout.ScanLineStride = 0;
+ maLayout.PlaneStride = 0;
+ maLayout.ColorSpace.clear();
+ maLayout.Palette.clear();
+ maLayout.IsMsbFirst = false;
+ }
+};
+
+void CanvasBitmapTest::runTest()
+{
+ static vcl::PixelFormat ePixelFormatArray[] =
+ {
+ vcl::PixelFormat::N8_BPP,
+ vcl::PixelFormat::N24_BPP
+ };
+
+ // Testing VclCanvasBitmap wrapper
+
+ for (auto const pixelFormat : ePixelFormatArray)
+ {
+ const sal_uInt16 nDepth = sal_uInt16(pixelFormat);
+ Bitmap aBitmap(Size(200,200), pixelFormat);
+ aBitmap.Erase(COL_WHITE);
+ {
+ BitmapScopedWriteAccess pAcc(aBitmap);
+ if( pAcc.get() )
+ {
+ BitmapColor aBlack(0);
+ BitmapColor aWhite(0);
+ if( pAcc->HasPalette() )
+ {
+ aBlack.SetIndex( sal::static_int_cast<sal_Int8>(pAcc->GetBestPaletteIndex(BitmapColor(0,0,0))) );
+ aWhite.SetIndex( sal::static_int_cast<sal_Int8>(pAcc->GetBestPaletteIndex(BitmapColor(255,255,255))) );
+ }
+ else
+ {
+ aBlack = COL_BLACK;
+ aWhite = COL_WHITE;
+ }
+ pAcc->SetFillColor(COL_GREEN);
+ pAcc->FillRect(tools::Rectangle(0,0,100,100));
+ pAcc->SetPixel(0,0,aWhite);
+ pAcc->SetPixel(0,1,aBlack);
+ pAcc->SetPixel(0,2,aWhite);
+ }
+ }
+
+ rtl::Reference<VclCanvasBitmap> xBmp( new VclCanvasBitmap(BitmapEx(aBitmap)) );
+
+ checkCanvasBitmap( xBmp, "single bitmap", nDepth );
+
+ AlphaMask aMask(Size(200,200));
+ aMask.Erase(255);
+ {
+ BitmapScopedWriteAccess pAcc(aMask);
+ if( pAcc.get() )
+ {
+ pAcc->SetFillColor(COL_ALPHA_OPAQUE);
+ pAcc->FillRect(tools::Rectangle(0,0,100,100));
+ pAcc->SetPixel(0,0,BitmapColor(0));
+ pAcc->SetPixel(0,1,BitmapColor(255));
+ pAcc->SetPixel(0,2,BitmapColor(0));
+ }
+ }
+
+ xBmp.set( new VclCanvasBitmap(BitmapEx(aBitmap,aMask)) );
+
+ checkCanvasBitmap( xBmp, "masked bitmap", nDepth );
+
+ AlphaMask aAlpha(Size(200,200));
+ aAlpha.Erase(0);
+ {
+ BitmapScopedWriteAccess pAcc(aAlpha);
+ if( pAcc )
+ {
+ pAcc->SetFillColor(COL_ALPHA_OPAQUE);
+ pAcc->FillRect(tools::Rectangle(0,0,100,100));
+ pAcc->SetPixel(0,0,BitmapColor(0));
+ pAcc->SetPixel(0,1,BitmapColor(255));
+ pAcc->SetPixel(0,2,BitmapColor(0));
+ }
+ }
+
+ xBmp.set( new VclCanvasBitmap(BitmapEx(aBitmap,aAlpha)) );
+
+ checkCanvasBitmap( xBmp, "alpha bitmap", nDepth );
+ }
+
+ // Testing XBitmap import
+
+ uno::Reference< rendering::XIntegerReadOnlyBitmap > xTestBmp(
+ new TestBitmap( geometry::IntegerSize2D(10,10), true ));
+
+ BitmapEx aBmp = vcl::unotools::bitmapExFromXBitmap(xTestBmp);
+ CPPUNIT_ASSERT_MESSAGE( "Palette bitmap is alpha",
+ !aBmp.IsAlpha());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Bitmap does not have size (10,10)",
+ Size(10,10), aBmp.GetSizePixel());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Bitmap does not have the expected pixel format",
+ vcl::PixelFormat::N8_BPP, aBmp.getPixelFormat());
+ {
+ Bitmap aBitmap = aBmp.GetBitmap();
+ BitmapScopedReadAccess pBmpAcc(aBitmap);
+
+ CPPUNIT_ASSERT_MESSAGE( "Bitmap has invalid BitmapReadAccess",
+ pBmpAcc );
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("(0,0) incorrect content",
+ BitmapColor(0), pBmpAcc->GetPixel(0,0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("(2,2) incorrect content",
+ BitmapColor(2), pBmpAcc->GetPixel(2,2));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("(9,2) incorrect content",
+ BitmapColor(9), pBmpAcc->GetPixel(2,9));
+ }
+
+ xTestBmp.set( new TestBitmap( geometry::IntegerSize2D(10,10), false ));
+
+ aBmp = vcl::unotools::bitmapExFromXBitmap(xTestBmp);
+ CPPUNIT_ASSERT_MESSAGE( "Palette bitmap has no alpha",
+ aBmp.IsAlpha());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Bitmap does not have size (10,10)",
+ Size(10,10), aBmp.GetSizePixel());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Bitmap does not have the expected pixel format",
+ vcl::PixelFormat::N24_BPP, aBmp.getPixelFormat());
+ {
+ Bitmap aBitmap = aBmp.GetBitmap();
+ BitmapScopedReadAccess pBmpAcc(aBitmap);
+ AlphaMask aBitmapAlpha = aBmp.GetAlphaMask();
+ BitmapScopedReadAccess pAlphaAcc(aBitmapAlpha);
+
+ CPPUNIT_ASSERT_MESSAGE( "Bitmap has invalid BitmapReadAccess",
+ pBmpAcc);
+ CPPUNIT_ASSERT_MESSAGE( "Bitmap has invalid alpha BitmapReadAccess",
+ pAlphaAcc);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("(0,0) incorrect content",
+ BitmapColor(0,1,0), pBmpAcc->GetPixel(0,0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("(0,0) incorrect alpha content",
+ BitmapColor(0), pAlphaAcc->GetPixel(0,0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("(2,2) incorrect content",
+ BitmapColor(0,3,2), pBmpAcc->GetPixel(2,2));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("(2,2) incorrect alpha content",
+ BitmapColor(2), pAlphaAcc->GetPixel(2,2));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("(9,2) incorrect content",
+ BitmapColor(0,3,9), pBmpAcc->GetPixel(2,9));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("(9,2) correct alpha content",
+ BitmapColor(2), pAlphaAcc->GetPixel(2,9));
+ }
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(CanvasBitmapTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/cjktext.cxx b/vcl/qa/cppunit/cjktext.cxx
new file mode 100644
index 0000000000..8dbed792b9
--- /dev/null
+++ b/vcl/qa/cppunit/cjktext.cxx
@@ -0,0 +1,248 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <osl/file.hxx>
+#include <osl/process.h>
+#include <test/bootstrapfixture.hxx>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+
+#include <vcl/BitmapReadAccess.hxx>
+#include <comphelper/errcode.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+
+#include <ImplLayoutArgs.hxx>
+#include <TextLayoutCache.hxx>
+#include <salgdi.hxx>
+
+class VclCjkTextTest : public test::BootstrapFixture
+{
+ // if enabled - check the result images with:
+ // "xdg-open ./workdir/CppunitTest/vcl_cjk_test.test.core/"
+ static constexpr const bool mbExportBitmap = false;
+
+ void exportDevice(const OUString& filename, const VclPtr<VirtualDevice>& device)
+ {
+ if (mbExportBitmap)
+ {
+ BitmapEx aBitmapEx(device->GetBitmapEx(Point(0, 0), device->GetOutputSizePixel()));
+ OUString cwd;
+ CPPUNIT_ASSERT_EQUAL(osl_Process_E_None, osl_getProcessWorkingDir(&cwd.pData));
+ OUString url;
+ CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None,
+ osl::FileBase::getAbsoluteFileURL(cwd, filename, url));
+ SvFileStream aStream(url, StreamMode::WRITE | StreamMode::TRUNC);
+ CPPUNIT_ASSERT_EQUAL(
+ ERRCODE_NONE, GraphicFilter::GetGraphicFilter().compressAsPNG(aBitmapEx, aStream));
+ }
+ }
+
+ void testVerticalText();
+
+public:
+ VclCjkTextTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ CPPUNIT_TEST_SUITE(VclCjkTextTest);
+ CPPUNIT_TEST(testVerticalText);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+// Similar to getCharacterBaseWidth but this time from the top, for U+30E8 (it's straight at the top, not at the bottom).
+static tools::Long getCharacterTopWidth(VirtualDevice* device, const Point& start)
+{
+ Bitmap bitmap = device->GetBitmap(Point(), device->GetOutputSizePixel());
+ BitmapScopedReadAccess access(bitmap);
+ tools::Long y = start.Y();
+ while (y < bitmap.GetSizePixel().Height() && access->GetColor(y, start.X()) != COL_BLACK)
+ ++y;
+ if (y >= bitmap.GetSizePixel().Height())
+ return -1;
+ tools::Long xmin = start.X();
+ while (xmin >= 0 && access->GetColor(y, xmin) != COL_WHITE)
+ --xmin;
+ tools::Long xmax = start.X();
+ while (xmax < bitmap.GetSizePixel().Width() && access->GetColor(y, xmax) != COL_WHITE)
+ ++xmax;
+ return xmax - xmin + 1;
+}
+
+// The same, but from the right side, for U+30E8 (it's straight on the right side, not the left one).
+static tools::Long getCharacterRightSideHeight(VirtualDevice* device, const Point& start)
+{
+ Bitmap bitmap = device->GetBitmap(Point(), device->GetOutputSizePixel());
+ BitmapScopedReadAccess access(bitmap);
+ tools::Long x = start.X();
+ while (x >= 0 && access->GetColor(start.Y(), x) != COL_BLACK)
+ --x;
+ if (x < 0)
+ return -1;
+ tools::Long ymin = start.Y();
+ while (ymin >= 0 && access->GetColor(ymin, x) != COL_WHITE)
+ --ymin;
+ tools::Long ymax = start.Y();
+ while (ymax < bitmap.GetSizePixel().Width() && access->GetColor(ymax, x) != COL_WHITE)
+ ++ymax;
+ return ymax - ymin + 1;
+}
+
+// Like testSimpleText() but for a vertical character, here namely U+30E8 (katakana letter yo),
+// chosen because it's a fairly simple shape (looks like horizontally mirrored E) that should
+// have the right and top lines being straight. Well, and also chosen because I actually
+// do not have much clue about CJK.
+// IMPORTANT: If you modify this, modify also the void VclTextTest::testSimpleText().
+void VclCjkTextTest::testVerticalText()
+{
+ OUString text(u"\x30e8"_ustr);
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(100, 100));
+ device->SetBackground(Wallpaper(COL_WHITE));
+ // Disable AA, to make all pixels be black or white.
+ device->SetAntialiasing(AntialiasingFlags::DisableText);
+
+ // Bail out on all backends that do not work (or I didn't test). Opt-out rather than opt-in
+ // to make sure new backends fail initially.
+ if (device->GetGraphics()->getRenderBackendName() == "qt5"
+ || device->GetGraphics()->getRenderBackendName() == "qt5svp"
+ || device->GetGraphics()->getRenderBackendName() == "gtk3svp"
+ || device->GetGraphics()->getRenderBackendName() == "aqua"
+ || device->GetGraphics()->getRenderBackendName() == "gen"
+#ifdef MACOSX // vertical fonts are broken on Mac with or without Skia
+ || device->GetGraphics()->getRenderBackendName() == "skia"
+#endif
+ || device->GetGraphics()->getRenderBackendName() == "genpsp")
+ return;
+
+ // We do not ship any CJK fonts, so try to find a common one that is usable for the test.
+ vcl::Font baseFont;
+ vcl::Font font;
+ bool fontFound = false;
+ for (const char* ptrfontName :
+ { "Droid Sans Japanese", "Baekmuk Gulim", "Microsoft JhengHei", "Microsoft YaHei",
+ "MS PGothic", "Hiragino Sans", "Arial Unicode MS" })
+ {
+ OUString fontName = OUString::fromUtf8(ptrfontName);
+ if (!device->IsFontAvailable(fontName))
+ continue;
+ baseFont = vcl::Font(fontName, "Book", Size(0, 36));
+ baseFont.SetLanguage(LANGUAGE_JAPANESE);
+ baseFont.SetVertical(true);
+ baseFont.SetOrientation(2700_deg10);
+ if (device->HasGlyphs(baseFont, text) == -1) // -1 means no glyph is missing
+ {
+ fontFound = true;
+ break;
+ }
+ }
+ if (!fontFound)
+ {
+ SAL_WARN("vcl",
+ "Could not find a font for VclCjkTextTest::testVerticalText, skipping test.");
+ return;
+ }
+
+ font = baseFont;
+ font.SetFontSize(Size(0, 36));
+ device->Erase();
+ device->SetFont(font);
+ device->DrawText(Point(90, 10), text);
+ exportDevice("vertical-text-36.png", device);
+ // Height of U+30E8 with font 36 size should be roughly 28 pixels,
+ // but since we don't know which font will be used, allow even more range.
+ tools::Long height36 = getCharacterRightSideHeight(device, Point(99, 22));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(tools::Long(28), height36, 6);
+ tools::Long width36 = getCharacterTopWidth(device, Point(65, 0));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(tools::Long(25), width36, 6);
+
+ // Horizontal writing of vertical glyphs. For some reason in this case
+ // the font is not set to be vertical.
+ font.SetOrientation(0_deg10);
+ font.SetVertical(false);
+ device->Erase();
+ device->SetFont(font);
+ device->DrawText(Point(10, 10), text);
+ exportDevice("vertical-text-36-0deg.png", device);
+ // Here width and height should be the same, since the glyphs actually
+ // not rotated compared to the vertical writing.
+ tools::Long height36Rotated = getCharacterRightSideHeight(device, Point(99, 35));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(height36, height36Rotated, 1);
+ tools::Long width36Rotated = getCharacterTopWidth(device, Point(25, 0));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(width36, width36Rotated, 2);
+
+ font = baseFont;
+ font.SetFontSize(Size(0, 72));
+ device->Erase();
+ device->SetFont(font);
+ device->DrawText(Point(90, 10), text);
+ exportDevice("vertical-text-72.png", device);
+ // Font size is doubled, so pixel sizes should also roughly double.
+ tools::Long height72 = getCharacterRightSideHeight(device, Point(99, 35));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(height36 * 2, height72, 4);
+ tools::Long width72 = getCharacterTopWidth(device, Point(40, 0));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(width36 * 2, width72, 4);
+
+ font.SetOrientation(0_deg10);
+ font.SetVertical(false);
+ device->Erase();
+ device->SetFont(font);
+ device->DrawText(Point(10, 10), text);
+ exportDevice("vertical-text-72-0deg.png", device);
+ tools::Long height72Rotated = getCharacterRightSideHeight(device, Point(99, 60));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(height72, height72Rotated, 1);
+ tools::Long width72Rotated = getCharacterTopWidth(device, Point(45, 0));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(width72, width72Rotated, 1);
+
+ // On Windows scaling of vertical glyphs is broken.
+ if (device->GetGraphics()->getRenderBackendName() == "gdi")
+ return;
+
+ // Test width scaled to 200%.
+ font = baseFont;
+ font.SetFontSize(Size(72, 36));
+#ifdef _WIN32
+ // TODO: What is the proper way to draw 200%-wide text? This is needed on Windows
+ // but it breaks Linux.
+ font.SetAverageFontWidth(2 * font.GetOrCalculateAverageFontWidth());
+#endif
+ device->Erase();
+ device->SetFont(font);
+ device->DrawText(Point(90, 10), text);
+ // Double "width" with vertical text makes the height doubled.
+ exportDevice("vertical-text-36-200pct.png", device);
+ tools::Long height36pct200 = getCharacterRightSideHeight(device, Point(99, 35));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(height36 * 2, height36pct200, 4);
+ tools::Long width36pct200 = getCharacterTopWidth(device, Point(65, 0));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(width36, width36pct200, 2);
+
+ // Test width scaled to 50%.
+ font = baseFont;
+ font.SetFontSize(Size(18, 36));
+#ifdef _WIN32
+ font.SetAverageFontWidth(0.5 * font.GetOrCalculateAverageFontWidth());
+#endif
+ device->Erase();
+ device->SetFont(font);
+ device->DrawText(Point(90, 10), text);
+ exportDevice("vertical-text-36-50pct.png", device);
+ tools::Long height36pct50 = getCharacterRightSideHeight(device, Point(99, 16));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(height36 / 2, height36pct50, 2);
+ tools::Long width36pct50 = getCharacterTopWidth(device, Point(65, 0));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(width36, width36pct50, 2);
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclCjkTextTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/complextext.cxx b/vcl/qa/cppunit/complextext.cxx
new file mode 100644
index 0000000000..8a847def87
--- /dev/null
+++ b/vcl/qa/cppunit/complextext.cxx
@@ -0,0 +1,614 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_features.h>
+#include <config_fonts.h>
+
+#include <ostream>
+#include <vector>
+#include <tools/long.hxx>
+#include <vcl/glyphitemcache.hxx>
+
+#if HAVE_MORE_FONTS
+// must be declared before inclusion of test/bootstrapfixture.hxx
+static std::ostream& operator<<(std::ostream& rStream, const std::vector<sal_Int32>& rVec);
+#endif
+#include <test/bootstrapfixture.hxx>
+
+#include <vcl/wrkwin.hxx>
+#include <vcl/virdev.hxx>
+// workaround MSVC2015 issue with std::unique_ptr
+#include <sallayout.hxx>
+#include <salgdi.hxx>
+
+
+#include <ImplLayoutArgs.hxx>
+
+#if HAVE_MORE_FONTS
+static std::ostream& operator<<(std::ostream& rStream, const std::vector<sal_Int32>& rVec)
+{
+ rStream << "{ ";
+ for (size_t i = 0; i < rVec.size() - 1; i++)
+ rStream << rVec[i] << ", ";
+ rStream << rVec.back();
+ rStream << " }";
+ return rStream;
+}
+#endif
+
+class VclComplexTextTest : public test::BootstrapFixture
+{
+#if !defined _WIN32
+ OUString maDataUrl = u"/vcl/qa/cppunit/data/"_ustr;
+
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(maDataUrl) + sFileName;
+ }
+
+protected:
+ bool addFont(OutputDevice* pOutDev, std::u16string_view sFileName,
+ std::u16string_view sFamilyName)
+ {
+ OutputDevice::ImplClearAllFontData(true);
+ bool bAdded = pOutDev->AddTempDevFont(getFullUrl(sFileName), OUString(sFamilyName));
+ OutputDevice::ImplRefreshAllFontData(true);
+ return bAdded;
+ }
+#endif
+
+public:
+ VclComplexTextTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+};
+
+CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testArabic)
+{
+#if HAVE_MORE_FONTS
+ OUString aOneTwoThree(u"واحÙدْ إثÙنين ثلاثةٌ"_ustr);
+
+ vcl::Font aFont("DejaVu Sans", "Book", Size(0, 2048));
+
+ ScopedVclPtrInstance<VirtualDevice> pOutDev;
+ pOutDev->SetFont( aFont );
+
+ // absolute character widths AKA text array.
+ tools::Long nRefTextWidth = 12595;
+ std::vector<sal_Int32> aRefCharWidths = { 989, 1558, 2824, 2824, 3899,
+ 3899, 4550, 5119, 5689, 5689, 6307, 6925, 8484, 9135, 9705, 10927,
+ 10927, 11497, 12595, 12595 };
+ KernArray aCharWidths;
+ tools::Long nTextWidth = pOutDev->GetTextArray(aOneTwoThree, &aCharWidths);
+
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths.get_subunit_array());
+ // this sporadically returns 75 or 74 on some of the windows tinderboxes eg. tb73
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, nTextWidth);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back());
+
+ // text advance width and line height
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, pOutDev->GetTextWidth(aOneTwoThree));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(2384), pOutDev->GetTextHeight());
+
+ // exact bounding rectangle, not essentially the same as text width/height
+ tools::Rectangle aBoundRect;
+ pOutDev->GetTextBoundRect(aBoundRect, aOneTwoThree);
+ CPPUNIT_ASSERT_EQUAL(tools::Long(145), aBoundRect.Left());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(212), aBoundRect.Top());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(12294), aBoundRect.GetWidth());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(2279), aBoundRect.getOpenHeight());
+
+ // normal orientation
+ tools::Rectangle aInput;
+ tools::Rectangle aRect = pOutDev->GetTextRect( aInput, aOneTwoThree );
+
+ // now rotate 270 degrees
+ vcl::Font aRotated( aFont );
+ aRotated.SetOrientation( 2700_deg10 );
+ pOutDev->SetFont( aRotated );
+ tools::Rectangle aRectRot = pOutDev->GetTextRect( aInput, aOneTwoThree );
+
+ // Check that we did do the rotation...
+ CPPUNIT_ASSERT_EQUAL( aRectRot.GetWidth(), aRect.GetHeight() );
+ CPPUNIT_ASSERT_EQUAL( aRectRot.GetHeight(), aRect.GetWidth() );
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testTdf95650)
+{
+ static constexpr OUStringLiteral aTxt =
+ u"\u0131\u0302\u0504\u4E44\u3031\u3030\u3531\u2D30"
+ "\u3037\u0706\u0908\u0B0A\u0D0C\u0F0E\u072E\u100A"
+ "\u0D11\u1312\u0105\u020A\u0512\u1403\u030C\u1528"
+ "\u2931\u632E\u7074\u0D20\u0E0A\u100A\uF00D\u0D20"
+ "\u030A\u0C0B\u20E0\u0A0D";
+ ScopedVclPtrInstance<VirtualDevice> pOutDev;
+ // Check that the following executes without failing assertion
+ pOutDev->ImplLayout(aTxt, 9, 1, Point(), 0, {}, {}, SalLayoutFlags::BiDiRtl);
+}
+
+static void checkCompareGlyphs( const SalLayoutGlyphs& aGlyphs1, const SalLayoutGlyphs& aGlyphs2,
+ const std::string& message )
+{
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(message, aGlyphs1.IsValid(), aGlyphs2.IsValid());
+ // And check it's the same.
+ for( int level = 0; level < MAX_FALLBACK; ++level )
+ {
+ const std::string messageLevel( Concat2View(OString::Concat(std::string_view(message))
+ + ", level: " + OString::number(level)) );
+ if( aGlyphs1.Impl(level) == nullptr)
+ {
+ CPPUNIT_ASSERT_MESSAGE(messageLevel, aGlyphs2.Impl(level) == nullptr);
+ continue;
+ }
+ const SalLayoutGlyphsImpl* g1 = aGlyphs1.Impl(level);
+ const SalLayoutGlyphsImpl* g2 = aGlyphs2.Impl(level);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(messageLevel, g1->GetFont().get(), g2->GetFont().get());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(messageLevel, g1->size(), g2->size());
+ for( size_t i = 0; i < g1->size(); ++i )
+ {
+ const bool equal = (*g1)[i].isLayoutEquivalent((*g2)[i]);
+ CPPUNIT_ASSERT_MESSAGE(messageLevel, equal);
+ }
+ }
+}
+
+static void testCachedGlyphs( const OUString& aText, const OUString& aFontName )
+{
+ const std::string message( OUString("Font: " + aFontName + ", text: '" + aText + "'").toUtf8() );
+ ScopedVclPtrInstance<VirtualDevice> pOutputDevice;
+ vcl::Font aFont( aFontName, Size(0, 12));
+ pOutputDevice->SetFont( aFont );
+ SalLayoutGlyphsCache::self()->clear();
+ // Get the glyphs for the text.
+ std::unique_ptr<SalLayout> pLayout1 = pOutputDevice->ImplLayout(
+ aText, 0, aText.getLength(), Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly);
+ SalLayoutGlyphs aGlyphs1 = pLayout1->GetGlyphs();
+ // Reuse the cached glyphs to get glyphs again.
+ std::unique_ptr<SalLayout> pLayout2 = pOutputDevice->ImplLayout(
+ aText, 0, aText.getLength(), Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly, nullptr, &aGlyphs1);
+ SalLayoutGlyphs aGlyphs2 = pLayout2->GetGlyphs();
+ checkCompareGlyphs(aGlyphs1, aGlyphs2, message + " (reuse)");
+ // Get cached glyphs from SalLayoutGlyphsCache.
+ const SalLayoutGlyphs* aGlyphs3 = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
+ pOutputDevice, aText, 0, aText.getLength(), 0);
+ CPPUNIT_ASSERT_MESSAGE(message, aGlyphs3 != nullptr);
+ checkCompareGlyphs(aGlyphs1, *aGlyphs3, message + " (cache)");
+}
+
+// Check that caching using SalLayoutGlyphs gives same results as without caching.
+// This should preferably use fonts that come with LO.
+CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testCaching)
+{
+ // Just something basic, no font fallback.
+ testCachedGlyphs( "test", "Dejavu Sans" );
+ // This font does not have latin characters, will need fallback.
+ testCachedGlyphs( "test", "Noto Kufi Arabic" );
+ // see tdf#103492
+ testCachedGlyphs( u"يوس٠My name is"_ustr, "Liberation Sans");
+}
+
+static void testCachedGlyphsSubstring( const OUString& aText, const OUString& aFontName, bool rtl )
+{
+ const std::string prefix( OUString("Font: " + aFontName + ", text: '" + aText + "'").toUtf8() );
+ ScopedVclPtrInstance<VirtualDevice> pOutputDevice;
+ // BiDiStrong is needed, otherwise SalLayoutGlyphsImpl::cloneCharRange() will not do anything.
+ vcl::text::ComplexTextLayoutFlags layoutFlags = vcl::text::ComplexTextLayoutFlags::BiDiStrong;
+ if(rtl)
+ layoutFlags |= vcl::text::ComplexTextLayoutFlags::BiDiRtl;
+ pOutputDevice->SetLayoutMode( layoutFlags );
+ vcl::Font aFont( aFontName, Size(0, 12));
+ pOutputDevice->SetFont( aFont );
+ SalLayoutGlyphsCache::self()->clear();
+ std::shared_ptr<const vcl::text::TextLayoutCache> layoutCache = OutputDevice::CreateTextLayoutCache(aText);
+ // Get the glyphs for the entire text once, to ensure the cache can built subsets from it.
+ pOutputDevice->ImplLayout( aText, 0, aText.getLength(), Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly,
+ layoutCache.get());
+ // Now check for all subsets. Some of them possibly do not make sense in practice, but the code
+ // should cope with them.
+ for( sal_Int32 len = 1; len <= aText.getLength(); ++len )
+ for( sal_Int32 pos = 0; pos < aText.getLength() - len; ++pos )
+ {
+ std::string message = prefix + " (" + std::to_string(pos) + "/" + std::to_string(len) + ")";
+ std::unique_ptr<SalLayout> pLayout1 = pOutputDevice->ImplLayout(
+ aText, pos, len, Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly, layoutCache.get());
+ SalLayoutGlyphs aGlyphs1 = pLayout1->GetGlyphs();
+ const SalLayoutGlyphs* aGlyphs2 = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
+ pOutputDevice, aText, pos, len, 0, layoutCache.get());
+ CPPUNIT_ASSERT_MESSAGE(message, aGlyphs2 != nullptr);
+ checkCompareGlyphs(aGlyphs1, *aGlyphs2, message);
+ }
+
+}
+
+// Check that SalLayoutGlyphsCache works properly when it builds a subset
+// of glyphs using SalLayoutGlyphsImpl::cloneCharRange().
+// This should preferably use fonts that come with LO.
+CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testCachingSubstring)
+{
+ // Just something basic.
+ testCachedGlyphsSubstring( "test", "Dejavu Sans", false );
+ // And complex arabic text, taken from tdf104649.docx .
+ OUString text(u"Ùصل (پاره 2): درخواست حاجت از ديگران Ùˆ برآوردن حاجت ديگران 90"_ustr);
+ testCachedGlyphsSubstring( text, "Dejavu Sans", true );
+ // The text is RTL, but Writer will sometimes try to lay it out as LTR, for whatever reason
+ // (tdf#149264)./ So make sure that gets handled properly too (SalLayoutGlyphsCache should
+ // not use glyph subsets in that case).
+ testCachedGlyphsSubstring( text, "Dejavu Sans", false );
+}
+
+CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testCaret)
+{
+#if HAVE_MORE_FONTS
+ // Test caret placement in fonts *without* ligature carets in GDEF table.
+
+ // Set font size to its UPEM to decrease rounding issues
+ vcl::Font aFont("DejaVu Sans", "Book", Size(0, 2048));
+
+ ScopedVclPtrInstance<VirtualDevice> pOutDev;
+ pOutDev->SetFont( aFont );
+
+ OUString aText;
+ KernArray aCharWidths;
+ std::vector<sal_Int32> aRefCharWidths;
+ tools::Long nTextWidth, nTextWidth2, nRefTextWidth;
+
+ // A. RTL text
+ aText = u"لا بلا"_ustr;
+
+ // 1) Regular DX array, the ligature width is given to the first components
+ // and the next ones are all zero width.
+ nRefTextWidth = 3611;
+ aRefCharWidths = { 1168, 1168, 1819, 2389, 3611, 3611 };
+ nTextWidth = pOutDev->GetTextArray(aText, &aCharWidths, 0, -1, /*bCaret*/false);
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths.get_subunit_array());
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, nTextWidth);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back());
+
+ // 2) Caret placement DX array, ligature width is distributed over its
+ // components.
+ aRefCharWidths = { 584, 1168, 1819, 2389, 3000, 3611 };
+ nTextWidth = pOutDev->GetTextArray(aText, &aCharWidths, 0, -1, /*bCaret*/true);
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths.get_subunit_array());
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, nTextWidth);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back());
+
+ // 3) caret placement with combining marks, they should not add to ligature
+ // component count.
+ aText = u"لَاَ بلَاَ"_ustr;
+ aRefCharWidths = { 584, 584, 1168, 1168, 1819, 2389, 3000, 3000, 3611, 3611 };
+ nTextWidth2 = pOutDev->GetTextArray(aText, &aCharWidths, 0, -1, /*bCaret*/true);
+ CPPUNIT_ASSERT_EQUAL(aCharWidths[0], aCharWidths[1]);
+ CPPUNIT_ASSERT_EQUAL(aCharWidths[2], aCharWidths[3]);
+ CPPUNIT_ASSERT_EQUAL(aCharWidths[6], aCharWidths[7]);
+ CPPUNIT_ASSERT_EQUAL(aCharWidths[8], aCharWidths[9]);
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths.get_subunit_array());
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, nTextWidth2);
+ CPPUNIT_ASSERT_EQUAL(nTextWidth, nTextWidth2);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back());
+
+ // B. LTR text
+ aText = u"fi fl ffi ffl"_ustr;
+
+ // 1) Regular DX array, the ligature width is given to the first components
+ // and the next ones are all zero width.
+ nRefTextWidth = 8493;
+ aRefCharWidths = { 1290, 1290, 1941, 3231, 3231, 3882, 5862, 5862, 5862, 6513, 8493, 8493, 8493 };
+ nTextWidth = pOutDev->GetTextArray(aText, &aCharWidths, 0, -1, /*bCaret*/false);
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths.get_subunit_array());
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, nTextWidth);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back());
+
+ // 2) Caret placement DX array, ligature width is distributed over its
+ // components.
+ aRefCharWidths = { 645, 1290, 1941, 2586, 3231, 3882, 4542, 5202, 5862, 6513, 7173, 7833, 8493 };
+ nTextWidth = pOutDev->GetTextArray(aText, &aCharWidths, 0, -1, /*bCaret*/true);
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths.get_subunit_array());
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, nTextWidth);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back());
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testGdefCaret)
+{
+#if HAVE_MORE_FONTS
+ // Test caret placement in fonts *with* ligature carets in GDEF table.
+
+ ScopedVclPtrInstance<VirtualDevice> pOutDev;
+
+ vcl::Font aFont;
+ OUString aText;
+ KernArray aCharWidths;
+ std::vector<sal_Int32> aRefCharWidths;
+ tools::Long nTextWidth, nTextWidth2, nRefTextWidth;
+
+ // A. RTL text
+ // Set font size to its UPEM to decrease rounding issues
+ aFont = vcl::Font("Noto Sans Arabic", "Regular", Size(0, 1000));
+ pOutDev->SetFont(aFont);
+
+ aText = u"لا بلا"_ustr;
+
+ // 1) Regular DX array, the ligature width is given to the first components
+ // and the next ones are all zero width.
+ nRefTextWidth = 1710;
+ aRefCharWidths= { 582, 582, 842, 1111, 1710, 1710 };
+ nTextWidth = pOutDev->GetTextArray(aText, &aCharWidths, 0, -1, /*bCaret*/false);
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths.get_subunit_array());
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, nTextWidth);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back());
+
+ // 2) Caret placement DX array, ligature width is distributed over its
+ // components.
+ aRefCharWidths = { 291, 582, 842, 1111, 1410, 1710 };
+ nTextWidth = pOutDev->GetTextArray(aText, &aCharWidths, 0, -1, /*bCaret*/true);
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths.get_subunit_array());
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, nTextWidth);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back());
+
+ // 3) caret placement with combining marks, they should not add to ligature
+ // component count.
+ aText = u"لَاَ بلَاَ"_ustr;
+ aRefCharWidths = { 291, 291, 582, 582, 842, 1111, 1410, 1410, 1710, 1710 };
+ nTextWidth2 = pOutDev->GetTextArray(aText, &aCharWidths, 0, -1, /*bCaret*/true);
+ CPPUNIT_ASSERT_EQUAL(aCharWidths[0], aCharWidths[1]);
+ CPPUNIT_ASSERT_EQUAL(aCharWidths[2], aCharWidths[3]);
+ CPPUNIT_ASSERT_EQUAL(aCharWidths[6], aCharWidths[7]);
+ CPPUNIT_ASSERT_EQUAL(aCharWidths[8], aCharWidths[9]);
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths.get_subunit_array());
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, nTextWidth2);
+ CPPUNIT_ASSERT_EQUAL(nTextWidth, nTextWidth2);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back());
+
+ // B. LTR text
+ // Set font size to its UPEM to decrease rounding issues
+ aFont = vcl::Font("Amiri", "Regular", Size(0, 1000));
+ pOutDev->SetFont(aFont);
+
+ aText = u"fi ffi fl ffl fb ffb"_ustr;
+
+ // 1) Regular DX array, the ligature width is given to the first components
+ // and the next ones are all zero width.
+ nRefTextWidth = 5996;
+ aRefCharWidths = { 519, 519, 811, 1606, 1606, 1606, 1898, 2439, 2439, 2731,
+ 3544, 3544, 3544, 3836, 4634, 4634, 4926, 5996, 5996, 5996 };
+ nTextWidth = pOutDev->GetTextArray(aText, &aCharWidths, 0, -1, /*bCaret*/false);
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths.get_subunit_array());
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, nTextWidth);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back());
+
+ // 2) Caret placement DX array, ligature width is distributed over its
+ // components.
+ aRefCharWidths = { 269, 519, 811, 1080, 1348, 1606, 1898, 2171, 2439, 2731,
+ 3004, 3278, 3544, 3836, 4138, 4634, 4926, 5199, 5494, 5996 };
+ nTextWidth = pOutDev->GetTextArray(aText, &aCharWidths, 0, -1, /*bCaret*/true);
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths.get_subunit_array());
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, nTextWidth);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back());
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testTdf152048)
+{
+#if HAVE_MORE_FONTS
+ OUString aText(u"می‌شود"_ustr);
+
+ vcl::Font aFont(u"Noto Naskh Arabic"_ustr, u"Regular"_ustr, Size(0, 2048));
+
+ ScopedVclPtrInstance<VirtualDevice> pOutDev;
+ pOutDev->SetFont(aFont);
+
+ // get an compare the default text array
+ std::vector<sal_Int32> aRefCharWidths{ 934, 2341, 2341, 3689, 4647, 5495 };
+ tools::Long nRefTextWidth(5495);
+
+ KernArray aCharWidths;
+ tools::Long nTextWidth = pOutDev->GetTextArray(aText, &aCharWidths);
+
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths.get_subunit_array());
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, nTextWidth);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back());
+
+ // Simulate Kashida insertion using Kashida array and extending text array
+ // to have room for Kashida.
+ std::vector<sal_Bool> aKashidaArray{ false, false, false, true, false, false };
+ auto nKashida = 4000;
+
+ aCharWidths.set(3, aCharWidths[3] + nKashida);
+ aCharWidths.set(4, aCharWidths[4] + nKashida);
+ aCharWidths.set(5, aCharWidths[5] + nKashida);
+ auto pLayout = pOutDev->ImplLayout(aText, 0, -1, Point(0, 0), 0, aCharWidths, aKashidaArray);
+
+ // Without the fix this fails with:
+ // - Expected: 393
+ // - Actual : 511
+ CPPUNIT_ASSERT_EQUAL(double(nRefTextWidth + nKashida), pLayout->GetTextWidth());
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testTdf152048_2)
+{
+#if HAVE_MORE_FONTS
+ vcl::Font aFont(u"Noto Naskh Arabic"_ustr, u"Regular"_ustr, Size(0, 72));
+
+ ScopedVclPtrInstance<VirtualDevice> pOutDev;
+ pOutDev->SetFont(aFont);
+
+ // get an compare the default text array
+ KernArray aCharWidths;
+ auto nTextWidth = pOutDev->GetTextArray(u"ع a ع"_ustr, &aCharWidths);
+
+ // Text width should always be equal to the width of the last glyph in the
+ // kern array.
+ // Without the fix this fails with:
+ // - Expected: 158
+ // - Actual : 118
+ CPPUNIT_ASSERT_EQUAL(aCharWidths.back(), sal_Int32(nTextWidth));
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testTdf153440)
+{
+#if HAVE_MORE_FONTS
+ vcl::Font aFont(u"Noto Naskh Arabic"_ustr, u"Regular"_ustr, Size(0, 72));
+
+ ScopedVclPtrInstance<VirtualDevice> pOutDev;
+ pOutDev->SetFont(aFont);
+
+#if !defined _WIN32 // TODO: Fails on jenkins but passes locally
+ // Add an emoji font so that we are sure a font will be found for the
+ // emoji. The font is subset and supports only 🌿.
+ bool bAdded = addFont(pOutDev, u"tdf153440.ttf", u"Noto Emoji");
+ CPPUNIT_ASSERT_EQUAL(true, bAdded);
+#endif
+
+ for (auto& aString : { u"ع 🌿 ع", u"a 🌿 a" })
+ {
+ OUString aText(aString);
+ bool bRTL = aText.startsWith(u"ع");
+
+ auto pLayout = pOutDev->ImplLayout(aText, 0, -1, Point(0, 0), 0, {}, {});
+
+ int nStart = 0;
+ basegfx::B2DPoint aPos;
+ const GlyphItem* pGlyphItem;
+ while (pLayout->GetNextGlyph(&pGlyphItem, aPos, nStart))
+ {
+ // Assert glyph ID is not 0, if it is 0 then font fallback didn’t
+ // happen.
+ CPPUNIT_ASSERT(pGlyphItem->glyphId());
+
+ // Assert that we are indeed doing RTL layout for RTL text since
+ // the bug does not happen for LTR text.
+ CPPUNIT_ASSERT_EQUAL(bRTL, pGlyphItem->IsRTLGlyph());
+ }
+ }
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testMixedCJKLatinScript_glyph_advancements)
+{
+#if HAVE_MORE_FONTS
+#if !defined _WIN32
+ OUString aTestScript(u"æ ¹æ®10.1(37BA) Eng"_ustr);
+
+ ScopedVclPtrInstance<VirtualDevice> pOutDev;
+ // note you can only run this once and it was designed for tdf#107718
+ bool bAdded = addFont(pOutDev, u"tdf107718.otf", u"Source Han Sans");
+ CPPUNIT_ASSERT_EQUAL(true, bAdded);
+
+ vcl::Font aFont(u"Source Han Sans"_ustr, u"Regular"_ustr, Size(0, 72));
+ pOutDev->SetFont( aFont );
+
+ vcl::Font aFallbackFont("DejaVu Sans", "Book", Size(0, 72));
+ pOutDev->ForceFallbackFont(aFallbackFont);
+
+ // absolute character widths AKA text array.
+ tools::Long nRefTextWidth = 704;
+ std::vector<sal_Int32> aRefCharWidths = { 72, 144, 190, 236, 259, 305, 333, 379, 425, 474, 523, 551, 567, 612, 658, 704 };
+ KernArray aCharWidths;
+ tools::Long nTextWidth = pOutDev->GetTextArray(aTestScript, &aCharWidths);
+
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths.get_subunit_array());
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, nTextWidth);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back());
+
+ // text advance width and line height
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, pOutDev->GetTextWidth(aTestScript));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(105), pOutDev->GetTextHeight());
+#endif
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testTdf107718)
+{
+#if HAVE_MORE_FONTS
+#if !defined _WIN32 // TODO: Fails on jenkins but passes locally
+ vcl::Font aFont(u"Source Han Sans"_ustr, u"Regular"_ustr, Size(0, 72));
+
+ ScopedVclPtrInstance<VirtualDevice> pOutDev;
+
+ OUString aText(u"\u4E16\u1109\u1168\u11BC\u302E"_ustr);
+ for (bool bVertical : { false, true })
+ {
+ aFont.SetVertical(bVertical);
+ pOutDev->SetFont(aFont);
+
+ auto pLayout = pOutDev->ImplLayout(aText, 0, -1, Point(0, 0), 0, {}, {});
+
+ int nStart = 0;
+ basegfx::B2DPoint aPos;
+ const GlyphItem* pGlyphItem;
+ while (pLayout->GetNextGlyph(&pGlyphItem, aPos, nStart))
+ {
+ // Check that we found a font for all characters, a zero glyph ID
+ // means no font was found so the rest of the test would be
+ // meaningless.
+ CPPUNIT_ASSERT(pGlyphItem->glyphId());
+
+ // Assert that we are indeed doing vertical layout for vertical
+ // font since the bug does not happen for horizontal text.
+ CPPUNIT_ASSERT_EQUAL(bVertical, pGlyphItem->IsVertical());
+
+ // For the second glyph, assert that it is a composition of characters 1 to 4
+ // Without the fix this fails with:
+ // - Expected: 4
+ // - Actual : 1
+ if (nStart == 2)
+ {
+ CPPUNIT_ASSERT_EQUAL(1, pGlyphItem->charPos());
+ CPPUNIT_ASSERT_EQUAL(4, pGlyphItem->charCount());
+ }
+ }
+
+ // Assert there are only three glyphs
+ // Without the fix this fails with:
+ // - Expected: 3
+ // - Actual : 5
+ CPPUNIT_ASSERT_EQUAL(3, nStart);
+ }
+#endif
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testTdf107612)
+{
+#if HAVE_MORE_FONTS
+ vcl::Font aFont(u"DejaVu Sans"_ustr, u"Book"_ustr, Size(0, 72));
+
+ ScopedVclPtrInstance<VirtualDevice> pOutDev;
+ pOutDev->SetFont(aFont);
+
+ auto pLayout = pOutDev->ImplLayout(u"a\u202F\u1823"_ustr, 0, -1, Point(0, 0), 0, {}, {});
+
+ // If font fallback happened, then the returned layout must be a
+ // MultiSalLayout instance.
+ auto pMultiLayout = dynamic_cast<MultiSalLayout*>(pLayout.get());
+ CPPUNIT_ASSERT(pMultiLayout);
+
+ auto pFallbackRuns = pMultiLayout->GetFallbackRuns();
+ CPPUNIT_ASSERT(!pFallbackRuns->IsEmpty());
+
+ bool bRTL;
+ int nCharPos = -1;
+ std::vector<sal_Int32> aFallbacks;
+ while (pFallbackRuns->GetNextPos(&nCharPos, &bRTL))
+ aFallbacks.push_back(nCharPos);
+
+ // Assert that U+202F is included in the fallback run.
+ // Without the fix this fails with:
+ // - Expected: { 1, 2 }
+ // - Actual : { 2 }
+ std::vector<sal_Int32> aExpctedFallbacks = { 1, 2 };
+ CPPUNIT_ASSERT_EQUAL(aExpctedFallbacks, aExpctedFallbacks);
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/data/123_Numbers.gif b/vcl/qa/cppunit/data/123_Numbers.gif
new file mode 100644
index 0000000000..2e47e28cb4
--- /dev/null
+++ b/vcl/qa/cppunit/data/123_Numbers.gif
Binary files differ
diff --git a/vcl/qa/cppunit/data/BMP_8bit_RLE.bmp b/vcl/qa/cppunit/data/BMP_8bit_RLE.bmp
new file mode 100644
index 0000000000..de20e4f2be
--- /dev/null
+++ b/vcl/qa/cppunit/data/BMP_8bit_RLE.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/data/BMP_A8R8G8B8.bmp b/vcl/qa/cppunit/data/BMP_A8R8G8B8.bmp
new file mode 100644
index 0000000000..9e789b7982
--- /dev/null
+++ b/vcl/qa/cppunit/data/BMP_A8R8G8B8.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/data/BMP_Paint_1bit.bmp b/vcl/qa/cppunit/data/BMP_Paint_1bit.bmp
new file mode 100644
index 0000000000..1112bd67ef
--- /dev/null
+++ b/vcl/qa/cppunit/data/BMP_Paint_1bit.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/data/BMP_Paint_24bit.bmp b/vcl/qa/cppunit/data/BMP_Paint_24bit.bmp
new file mode 100644
index 0000000000..41a2eb2456
--- /dev/null
+++ b/vcl/qa/cppunit/data/BMP_Paint_24bit.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/data/BMP_Paint_4bit.bmp b/vcl/qa/cppunit/data/BMP_Paint_4bit.bmp
new file mode 100644
index 0000000000..69c91a6353
--- /dev/null
+++ b/vcl/qa/cppunit/data/BMP_Paint_4bit.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/data/BMP_Paint_8bit.bmp b/vcl/qa/cppunit/data/BMP_Paint_8bit.bmp
new file mode 100644
index 0000000000..eb48327ef2
--- /dev/null
+++ b/vcl/qa/cppunit/data/BMP_Paint_8bit.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/data/BMP_R5G6B5.bmp b/vcl/qa/cppunit/data/BMP_R5G6B5.bmp
new file mode 100644
index 0000000000..ac5531c142
--- /dev/null
+++ b/vcl/qa/cppunit/data/BMP_R5G6B5.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/data/BMP_R8G8B8.bmp b/vcl/qa/cppunit/data/BMP_R8G8B8.bmp
new file mode 100644
index 0000000000..5197e42a74
--- /dev/null
+++ b/vcl/qa/cppunit/data/BMP_R8G8B8.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/data/BMP_RLE.bmp b/vcl/qa/cppunit/data/BMP_RLE.bmp
new file mode 100644
index 0000000000..ceb843988c
--- /dev/null
+++ b/vcl/qa/cppunit/data/BMP_RLE.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/data/BMP_RLE_V2.bmp b/vcl/qa/cppunit/data/BMP_RLE_V2.bmp
new file mode 100644
index 0000000000..a500761404
--- /dev/null
+++ b/vcl/qa/cppunit/data/BMP_RLE_V2.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/data/BMP_RLE_V3.bmp b/vcl/qa/cppunit/data/BMP_RLE_V3.bmp
new file mode 100644
index 0000000000..870b8d563d
--- /dev/null
+++ b/vcl/qa/cppunit/data/BMP_RLE_V3.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/data/DocumentWithNull.pdf b/vcl/qa/cppunit/data/DocumentWithNull.pdf
new file mode 100644
index 0000000000..f6d9269573
--- /dev/null
+++ b/vcl/qa/cppunit/data/DocumentWithNull.pdf
Binary files differ
diff --git a/vcl/qa/cppunit/data/Exif1.jpg b/vcl/qa/cppunit/data/Exif1.jpg
new file mode 100644
index 0000000000..a81425e758
--- /dev/null
+++ b/vcl/qa/cppunit/data/Exif1.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/data/Exif1_090CW.jpg b/vcl/qa/cppunit/data/Exif1_090CW.jpg
new file mode 100644
index 0000000000..bb8a81a163
--- /dev/null
+++ b/vcl/qa/cppunit/data/Exif1_090CW.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/data/Exif1_180.jpg b/vcl/qa/cppunit/data/Exif1_180.jpg
new file mode 100644
index 0000000000..b18b70f139
--- /dev/null
+++ b/vcl/qa/cppunit/data/Exif1_180.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/data/Exif1_270CW.jpg b/vcl/qa/cppunit/data/Exif1_270CW.jpg
new file mode 100644
index 0000000000..f10764114d
--- /dev/null
+++ b/vcl/qa/cppunit/data/Exif1_270CW.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/data/Pangram.pdf b/vcl/qa/cppunit/data/Pangram.pdf
new file mode 100644
index 0000000000..0714fda4e4
--- /dev/null
+++ b/vcl/qa/cppunit/data/Pangram.pdf
Binary files differ
diff --git a/vcl/qa/cppunit/data/PangramAcrobatAnnotations.pdf b/vcl/qa/cppunit/data/PangramAcrobatAnnotations.pdf
new file mode 100644
index 0000000000..f97003a7f3
--- /dev/null
+++ b/vcl/qa/cppunit/data/PangramAcrobatAnnotations.pdf
Binary files differ
diff --git a/vcl/qa/cppunit/data/PangramWithAnnotations.pdf b/vcl/qa/cppunit/data/PangramWithAnnotations.pdf
new file mode 100644
index 0000000000..f69ddd9870
--- /dev/null
+++ b/vcl/qa/cppunit/data/PangramWithAnnotations.pdf
Binary files differ
diff --git a/vcl/qa/cppunit/data/PangramWithMultipleTypeOfAnnotations.pdf b/vcl/qa/cppunit/data/PangramWithMultipleTypeOfAnnotations.pdf
new file mode 100644
index 0000000000..68037d0a2d
--- /dev/null
+++ b/vcl/qa/cppunit/data/PangramWithMultipleTypeOfAnnotations.pdf
Binary files differ
diff --git a/vcl/qa/cppunit/data/SimpleExample.svg b/vcl/qa/cppunit/data/SimpleExample.svg
new file mode 100644
index 0000000000..6890b5456c
--- /dev/null
+++ b/vcl/qa/cppunit/data/SimpleExample.svg
@@ -0,0 +1,4 @@
+<svg width="50" height="50" version="1.1" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
+ <rect x="0" y="0" width="50" height="50" fill="#aaaaaa"/>
+ <rect x="5" y="5" width="40" height="40" fill="#ff44aa"/>
+</svg>
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.apng b/vcl/qa/cppunit/data/TypeDetectionExample.apng
new file mode 100644
index 0000000000..445b7acaf4
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.apng
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.bmp b/vcl/qa/cppunit/data/TypeDetectionExample.bmp
new file mode 100644
index 0000000000..5197e42a74
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.emf b/vcl/qa/cppunit/data/TypeDetectionExample.emf
new file mode 100644
index 0000000000..571a25c81b
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.emf
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.emz b/vcl/qa/cppunit/data/TypeDetectionExample.emz
new file mode 100644
index 0000000000..dd3b3b2f1f
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.emz
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.eps b/vcl/qa/cppunit/data/TypeDetectionExample.eps
new file mode 100644
index 0000000000..7f0db47bc8
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.eps
@@ -0,0 +1,82 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: cairo 1.16.0 (https://cairographics.org)
+%%CreationDate: Sat May 2 14:29:27 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%BoundingBox: 0 1 7 8
+%%EndComments
+%%BeginProlog
+50 dict begin
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageBoundingBox: 0 1 7 8
+%%EndPageSetup
+q 0 1 7 7 rectclip
+1 0 0 -1 0 8 cm q
+0.956863 0.831373 0.266667 rg
+3.75 0.75 m 5.41 0.75 6.75 2.09 6.75 3.75 c 6.75 5.41 5.41 6.75 3.75 6.75
+ c 2.09 6.75 0.75 5.41 0.75 3.75 c 0.75 2.09 2.09 0.75 3.75 0.75 c h
+3.75 0.75 m f
+Q Q
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.gif b/vcl/qa/cppunit/data/TypeDetectionExample.gif
new file mode 100644
index 0000000000..b33eb4f909
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.gif
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.jpg b/vcl/qa/cppunit/data/TypeDetectionExample.jpg
new file mode 100644
index 0000000000..b8436eaa18
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.met b/vcl/qa/cppunit/data/TypeDetectionExample.met
new file mode 100644
index 0000000000..7635e841fd
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.met
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.pcx b/vcl/qa/cppunit/data/TypeDetectionExample.pcx
new file mode 100644
index 0000000000..6393234556
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.pcx
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.pdf b/vcl/qa/cppunit/data/TypeDetectionExample.pdf
new file mode 100644
index 0000000000..b68bff5e1f
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.pdf
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.png b/vcl/qa/cppunit/data/TypeDetectionExample.png
new file mode 100644
index 0000000000..f73f5fd749
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.png
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.psd b/vcl/qa/cppunit/data/TypeDetectionExample.psd
new file mode 100644
index 0000000000..8282b14fdf
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.psd
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.svg b/vcl/qa/cppunit/data/TypeDetectionExample.svg
new file mode 100644
index 0000000000..e23e44c238
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.svg
@@ -0,0 +1,4 @@
+<svg width="10" height="10" version="1.1" viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg">
+ <rect x="0" y="0" width="10" height="10" fill="#ffffff"/>
+ <rect x="1" y="1" width="8" height="8" fill="#72d1c8"/>
+</svg>
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.svgz b/vcl/qa/cppunit/data/TypeDetectionExample.svgz
new file mode 100644
index 0000000000..ef04021fd0
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.svgz
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.tga b/vcl/qa/cppunit/data/TypeDetectionExample.tga
new file mode 100644
index 0000000000..870c88b107
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.tga
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.tif b/vcl/qa/cppunit/data/TypeDetectionExample.tif
new file mode 100644
index 0000000000..dc74dc958b
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.tif
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.webp b/vcl/qa/cppunit/data/TypeDetectionExample.webp
new file mode 100644
index 0000000000..e85ae12163
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.webp
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.wmf b/vcl/qa/cppunit/data/TypeDetectionExample.wmf
new file mode 100644
index 0000000000..7ed7069282
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.wmz b/vcl/qa/cppunit/data/TypeDetectionExample.wmz
new file mode 100644
index 0000000000..2435f20baf
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.wmz
Binary files differ
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.xbm b/vcl/qa/cppunit/data/TypeDetectionExample.xbm
new file mode 100644
index 0000000000..b40d1a45e6
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.xbm
@@ -0,0 +1,5 @@
+#define sample_width 10
+#define sample_height 10
+static unsigned char sample_bits[] = {
+ 0x00, 0x00, 0xfe, 0x01, 0xfe, 0x01, 0xfe, 0x01, 0xfe, 0x01, 0xfe, 0x01,
+ 0xfe, 0x01, 0xfe, 0x01, 0xfe, 0x01, 0x00, 0x00 };
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.xpm b/vcl/qa/cppunit/data/TypeDetectionExample.xpm
new file mode 100644
index 0000000000..7b9b94359c
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.xpm
@@ -0,0 +1,15 @@
+/* XPM */
+static char * sample_xpm[] = {
+"10 10 2 1",
+" c #FFFFFF",
+". c #72D1C8",
+" ",
+" ........ ",
+" ........ ",
+" ........ ",
+" ........ ",
+" ........ ",
+" ........ ",
+" ........ ",
+" ........ ",
+" "};
diff --git a/vcl/qa/cppunit/data/XPM_1.xpm b/vcl/qa/cppunit/data/XPM_1.xpm
new file mode 100644
index 0000000000..7b9b94359c
--- /dev/null
+++ b/vcl/qa/cppunit/data/XPM_1.xpm
@@ -0,0 +1,15 @@
+/* XPM */
+static char * sample_xpm[] = {
+"10 10 2 1",
+" c #FFFFFF",
+". c #72D1C8",
+" ",
+" ........ ",
+" ........ ",
+" ........ ",
+" ........ ",
+" ........ ",
+" ........ ",
+" ........ ",
+" ........ ",
+" "};
diff --git a/vcl/qa/cppunit/data/XPM_4.xpm b/vcl/qa/cppunit/data/XPM_4.xpm
new file mode 100644
index 0000000000..f958aa9a1f
--- /dev/null
+++ b/vcl/qa/cppunit/data/XPM_4.xpm
@@ -0,0 +1,23 @@
+/* XPM */
+static char * XPM_4_xpm[] = {
+"4 4 16 1",
+" c #000000",
+". c #800000",
+"+ c #008000",
+"@ c #808000",
+"# c #000080",
+"$ c #800080",
+"% c #008080",
+"& c #808080",
+"* c #C0C0C0",
+"= c #FF0000",
+"- c #00FF00",
+"; c #FFFF00",
+"> c #0000FF",
+", c #FF00FF",
+"' c #00FFFF",
+") c #FFFFFF",
+">>==",
+">>==",
+";;--",
+";;--"};
diff --git a/vcl/qa/cppunit/data/XPM_8.xpm b/vcl/qa/cppunit/data/XPM_8.xpm
new file mode 100644
index 0000000000..82c354dfd1
--- /dev/null
+++ b/vcl/qa/cppunit/data/XPM_8.xpm
@@ -0,0 +1,263 @@
+/* XPM */
+static char * XPM_4_xpm[] = {
+"4 4 256 2",
+" c #000000",
+". c #800000",
+"+ c #008000",
+"@ c #808000",
+"# c #000080",
+"$ c #800080",
+"% c #008080",
+"& c #C0C0C0",
+"* c #C0DCC0",
+"= c #A6CAF0",
+"- c #402000",
+"; c #602000",
+"> c #802000",
+", c #A02000",
+"' c #C02000",
+") c #E02000",
+"! c #004000",
+"~ c #204000",
+"{ c #404000",
+"] c #604000",
+"^ c #804000",
+"/ c #A04000",
+"( c #C04000",
+"_ c #E04000",
+": c #006000",
+"< c #206000",
+"[ c #406000",
+"} c #606000",
+"| c #806000",
+"1 c #A06000",
+"2 c #C06000",
+"3 c #E06000",
+"4 c #008000",
+"5 c #208000",
+"6 c #408000",
+"7 c #608000",
+"8 c #808000",
+"9 c #A08000",
+"0 c #C08000",
+"a c #E08000",
+"b c #00A000",
+"c c #20A000",
+"d c #40A000",
+"e c #60A000",
+"f c #80A000",
+"g c #A0A000",
+"h c #C0A000",
+"i c #E0A000",
+"j c #00C000",
+"k c #20C000",
+"l c #40C000",
+"m c #60C000",
+"n c #80C000",
+"o c #A0C000",
+"p c #C0C000",
+"q c #E0C000",
+"r c #00E000",
+"s c #20E000",
+"t c #40E000",
+"u c #60E000",
+"v c #80E000",
+"w c #A0E000",
+"x c #C0E000",
+"y c #E0E000",
+"z c #000040",
+"A c #200040",
+"B c #400040",
+"C c #600040",
+"D c #800040",
+"E c #A00040",
+"F c #C00040",
+"G c #E00040",
+"H c #002040",
+"I c #202040",
+"J c #402040",
+"K c #602040",
+"L c #802040",
+"M c #A02040",
+"N c #C02040",
+"O c #E02040",
+"P c #004040",
+"Q c #204040",
+"R c #404040",
+"S c #604040",
+"T c #804040",
+"U c #A04040",
+"V c #C04040",
+"W c #E04040",
+"X c #006040",
+"Y c #206040",
+"Z c #406040",
+"` c #606040",
+" . c #806040",
+".. c #A06040",
+"+. c #C06040",
+"@. c #E06040",
+"#. c #008040",
+"$. c #208040",
+"%. c #408040",
+"&. c #608040",
+"*. c #808040",
+"=. c #A08040",
+"-. c #C08040",
+";. c #E08040",
+">. c #00A040",
+",. c #20A040",
+"'. c #40A040",
+"). c #60A040",
+"!. c #80A040",
+"~. c #A0A040",
+"{. c #C0A040",
+"]. c #E0A040",
+"^. c #00C040",
+"/. c #20C040",
+"(. c #40C040",
+"_. c #60C040",
+":. c #80C040",
+"<. c #A0C040",
+"[. c #C0C040",
+"}. c #E0C040",
+"|. c #00E040",
+"1. c #20E040",
+"2. c #40E040",
+"3. c #60E040",
+"4. c #80E040",
+"5. c #A0E040",
+"6. c #C0E040",
+"7. c #E0E040",
+"8. c #000080",
+"9. c #200080",
+"0. c #400080",
+"a. c #600080",
+"b. c #800080",
+"c. c #A00080",
+"d. c #C00080",
+"e. c #E00080",
+"f. c #002080",
+"g. c #202080",
+"h. c #402080",
+"i. c #602080",
+"j. c #802080",
+"k. c #A02080",
+"l. c #C02080",
+"m. c #E02080",
+"n. c #004080",
+"o. c #204080",
+"p. c #404080",
+"q. c #604080",
+"r. c #804080",
+"s. c #A04080",
+"t. c #C04080",
+"u. c #E04080",
+"v. c #006080",
+"w. c #206080",
+"x. c #406080",
+"y. c #606080",
+"z. c #806080",
+"A. c #A06080",
+"B. c #C06080",
+"C. c #E06080",
+"D. c #008080",
+"E. c #208080",
+"F. c #408080",
+"G. c #608080",
+"H. c #808080",
+"I. c #A08080",
+"J. c #C08080",
+"K. c #E08080",
+"L. c #00A080",
+"M. c #20A080",
+"N. c #40A080",
+"O. c #60A080",
+"P. c #80A080",
+"Q. c #A0A080",
+"R. c #C0A080",
+"S. c #E0A080",
+"T. c #00C080",
+"U. c #20C080",
+"V. c #40C080",
+"W. c #60C080",
+"X. c #80C080",
+"Y. c #A0C080",
+"Z. c #C0C080",
+"`. c #E0C080",
+" + c #00E080",
+".+ c #20E080",
+"++ c #40E080",
+"@+ c #60E080",
+"#+ c #80E080",
+"$+ c #A0E080",
+"%+ c #C0E080",
+"&+ c #E0E080",
+"*+ c #0000C0",
+"=+ c #2000C0",
+"-+ c #4000C0",
+";+ c #6000C0",
+">+ c #8000C0",
+",+ c #A000C0",
+"'+ c #C000C0",
+")+ c #E000C0",
+"!+ c #0020C0",
+"~+ c #2020C0",
+"{+ c #4020C0",
+"]+ c #6020C0",
+"^+ c #8020C0",
+"/+ c #A020C0",
+"(+ c #C020C0",
+"_+ c #E020C0",
+":+ c #0040C0",
+"<+ c #2040C0",
+"[+ c #4040C0",
+"}+ c #6040C0",
+"|+ c #8040C0",
+"1+ c #A040C0",
+"2+ c #C040C0",
+"3+ c #E040C0",
+"4+ c #0060C0",
+"5+ c #2060C0",
+"6+ c #4060C0",
+"7+ c #6060C0",
+"8+ c #8060C0",
+"9+ c #A060C0",
+"0+ c #C060C0",
+"a+ c #E060C0",
+"b+ c #0080C0",
+"c+ c #2080C0",
+"d+ c #4080C0",
+"e+ c #6080C0",
+"f+ c #8080C0",
+"g+ c #A080C0",
+"h+ c #C080C0",
+"i+ c #E080C0",
+"j+ c #00A0C0",
+"k+ c #20A0C0",
+"l+ c #40A0C0",
+"m+ c #60A0C0",
+"n+ c #80A0C0",
+"o+ c #A0A0C0",
+"p+ c #C0A0C0",
+"q+ c #E0A0C0",
+"r+ c #00C0C0",
+"s+ c #20C0C0",
+"t+ c #40C0C0",
+"u+ c #60C0C0",
+"v+ c #80C0C0",
+"w+ c #A0C0C0",
+"x+ c #FFFBF0",
+"y+ c #A0A0A4",
+"z+ c #808080",
+"A+ c #FF0000",
+"B+ c #00FF00",
+"C+ c #FFFF00",
+"D+ c #0000FF",
+"E+ c #FF00FF",
+"F+ c #00FFFF",
+"G+ c #FFFFFF",
+"D+D+A+A+",
+"D+D+A+A+",
+"C+C+B+B+",
+"C+C+B+B+"};
diff --git a/vcl/qa/cppunit/data/basic.pdf b/vcl/qa/cppunit/data/basic.pdf
new file mode 100644
index 0000000000..0d68be4bf5
--- /dev/null
+++ b/vcl/qa/cppunit/data/basic.pdf
@@ -0,0 +1,71 @@
+%PDF-1.2
+%µ¶
+
+1 0 obj
+<</Type/Page/Parent 5 0 R/Resources 3 0 R/Contents 2 0 R>>
+endobj
+
+2 0 obj
+<</Length 57>>
+stream
+BT
+/F1 24 Tf
+1 0 0 1 260 254 Tm
+0.5 g
+(Hello World)Tj
+ET
+
+endstream
+endobj
+
+3 0 obj
+<</ProcSet[/PDF/Text]/Font<</F1 4 0 R>>/Test 7 0 R>>
+endobj
+
+4 0 obj
+<</Type/Font/Subtype/Type1/Name/F1/BaseFont/Helvetica>>
+endobj
+
+5 0 obj
+<</Type/Pages/Kids[1 0 R]/Count 1/MediaBox[0 0 612 446]>>
+endobj
+
+6 0 obj
+<</Type/Catalog/Pages 5 0 R>>
+endobj
+
+7 0 obj
+<</TestArray1 8 0 R/TestArray2 9 0 R/TestDictionary 10 0 R>>
+endobj
+
+8 0 obj
+[/Inner1/Inner2[/Inner31][/Inner41/Inner42[/Inner431/Inner432]][/Inner51[/Inner521]]]
+endobj
+
+9 0 obj
+[/TestReference 7 0 R]
+endobj
+
+10 0 obj
+<</TestReference 7 0 R/TestNumber 123/TestName/SomeName/TestDictionary<</Key/Value>>/TestArray[1 2 3]>>
+endobj
+
+xref
+0 11
+0000000000 65536 f
+0000000016 00000 n
+0000000091 00000 n
+0000000197 00000 n
+0000000266 00000 n
+0000000338 00000 n
+0000000412 00000 n
+0000000458 00000 n
+0000000535 00000 n
+0000000637 00000 n
+0000000676 00000 n
+
+trailer
+<</Size 11/Root 6 0 R>>
+startxref
+797
+%%EOF
diff --git a/vcl/qa/cppunit/data/basicSource.pdf b/vcl/qa/cppunit/data/basicSource.pdf
new file mode 100644
index 0000000000..2cde317fc4
--- /dev/null
+++ b/vcl/qa/cppunit/data/basicSource.pdf
@@ -0,0 +1,60 @@
+%PDF-1.2
+
+%Fix with "mutool clean vcl/qa/cppunit/data/basicSource.pdf vcl/qa/cppunit/data/basic.pdf"
+
+1 0 obj
+<< /Type /Page /Parent 5 0 R /Resources 3 0 R /Contents 2 0 R>>
+endobj
+
+2 0 obj
+<</Length 57>>
+stream
+BT
+/F1 24 Tf
+1 0 0 1 260 254 Tm
+0.5 g
+(Hello World)Tj
+ET
+endstream
+endobj
+
+3 0 obj
+<<
+/ProcSet [/PDF /Text ]
+/Font << /F1 4 0 R >>
+/Test 7 0 R
+>>
+endobj
+
+4 0 obj
+<< /Type /Font /Subtype /Type1 /Name /F1 /BaseFont /Helvetica >>
+endobj
+
+5 0 obj
+<< /Type /Pages /Kids [ 1 0 R ] /Count 1 /MediaBox [ 0 0 612 446 ]>>
+endobj
+
+6 0 obj
+<< /Type /Catalog /Pages 5 0 R>>
+endobj
+
+7 0 obj
+<< /TestArray1 8 0 R /TestArray2 9 0 R /TestDictionary 10 0 R >>
+endobj
+
+8 0 obj
+[ /Inner1 /Inner2 [/Inner31] [/Inner41 /Inner42 [/Inner431 /Inner432] ] [ /Inner51 [/Inner521] ] ]
+endobj
+
+9 0 obj
+[ /TestReference 7 0 R ]
+endobj
+
+10 0 obj
+<< /TestReference 7 0 R /TestNumber 123 /TestName /SomeName /TestDictionary << /Key /Value >> /TestArray [1 2 3] >>
+endobj
+
+trailer
+<</Root 6 0 R>>
+
+%%EOF
diff --git a/vcl/qa/cppunit/data/form-fields.pdf b/vcl/qa/cppunit/data/form-fields.pdf
new file mode 100644
index 0000000000..a014b36c98
--- /dev/null
+++ b/vcl/qa/cppunit/data/form-fields.pdf
@@ -0,0 +1,95 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+ /Type /Catalog
+ /Pages 5 0 R
+>>
+endobj
+
+2 0 obj <<
+ /Length 0
+>>
+stream
+endstream
+endobj
+
+3 0 obj <<
+ /Font <<
+ /TT1 4 0 R
+ >>
+>>
+endobj
+
+4 0 obj <<
+ /Type /Font
+ /Subtype /Type1
+ /Name /TT1
+ /BaseFont/Helvetica
+>>
+endobj
+
+5 0 obj <<
+ /Type /Pages
+ /Kids [6 0 R]
+ /Count 1
+ /MediaBox [ 0 0 612 446 ]
+>>
+endobj
+
+6 0 obj <<
+ /Type /Page
+ /Parent 5 0 R
+ /Resources 3 0 R
+ /Contents 2 0 R
+ /Annots [7 0 R]
+>>
+endobj
+
+7 0 obj <<
+ /Type /Annot
+ /Subtype /Widget
+ /T (T)
+ /V (V)
+ /DA (/Helv 0 Tf 0 g)
+ /Rect [ 0 0 612 446 ]
+ /FT /Tx
+ /AP <<
+ /N 8 0 R
+ >>
+>>
+endobj
+
+8 0 obj <<
+ /Type /XObject
+ /Subtype /Form
+ /Matrix [1.0 0.0 0.0 1.0 0.0 0.0]
+ /Resources 3 0 R
+ /BBox [ 0 0 612 446 ]
+ /Length 55
+>>
+stream
+ BT
+ /TT1 24 Tf
+ 1 0 0 1 260 254 Tm
+ (test)Tj
+ ET
+endstream
+endobj
+xref
+0 9
+0000000000 65535 f
+0000000015 00000 n
+0000000069 00000 n
+0000000121 00000 n
+0000000174 00000 n
+0000000259 00000 n
+0000000351 00000 n
+0000000458 00000 n
+0000000616 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 9
+>>
+startxref
+836
+%%EOF
diff --git a/vcl/qa/cppunit/data/graphic-descriptor-mapmode.bmp b/vcl/qa/cppunit/data/graphic-descriptor-mapmode.bmp
new file mode 100644
index 0000000000..f65c10ea2b
--- /dev/null
+++ b/vcl/qa/cppunit/data/graphic-descriptor-mapmode.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/data/inch-size.emf b/vcl/qa/cppunit/data/inch-size.emf
new file mode 100644
index 0000000000..ac5a1b805c
--- /dev/null
+++ b/vcl/qa/cppunit/data/inch-size.emf
Binary files differ
diff --git a/vcl/qa/cppunit/data/roundtrip.wmf b/vcl/qa/cppunit/data/roundtrip.wmf
new file mode 100644
index 0000000000..83210546c3
--- /dev/null
+++ b/vcl/qa/cppunit/data/roundtrip.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/data/tdf107718.otf b/vcl/qa/cppunit/data/tdf107718.otf
new file mode 100644
index 0000000000..b892a29a88
--- /dev/null
+++ b/vcl/qa/cppunit/data/tdf107718.otf
Binary files differ
diff --git a/vcl/qa/cppunit/data/tdf107718.otf.readme b/vcl/qa/cppunit/data/tdf107718.otf.readme
new file mode 100644
index 0000000000..22f656f96d
--- /dev/null
+++ b/vcl/qa/cppunit/data/tdf107718.otf.readme
@@ -0,0 +1,11 @@
+This is a subset copy of Source Han Sans font licensed under Open Font License and
+obtained from (the Static Super OTC):
+
+ https://github.com/adobe-fonts/source-han-sans/releases/tag/2.004R
+
+And subset using hb-subset to contain only the one glyph used in the test:
+
+ hb-subset SourceHanSans.ttc --face-index=25 --unicodes="4E16,1109,1168,11BC,302E,C185,0020" -o tdf107718.otf
+
+U+C185 is not directly used in the test but we need its glyphs. The space is
+added to the subset as it seems needed to get the font to work on Windows.
diff --git a/vcl/qa/cppunit/data/tdf149545.svg b/vcl/qa/cppunit/data/tdf149545.svg
new file mode 100644
index 0000000000..39fd956142
--- /dev/null
+++ b/vcl/qa/cppunit/data/tdf149545.svg
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<svg version="1.1" baseProfile="basic" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg-root" width="100%" height="100%" viewBox="0 0 99 99">
+<rect x="0" y="0" width="99" height="99" fill="#000000"/>
+</svg>
+
+
diff --git a/vcl/qa/cppunit/data/tdf153440.ttf b/vcl/qa/cppunit/data/tdf153440.ttf
new file mode 100644
index 0000000000..933e74733b
--- /dev/null
+++ b/vcl/qa/cppunit/data/tdf153440.ttf
Binary files differ
diff --git a/vcl/qa/cppunit/data/tdf153440.ttf.readme b/vcl/qa/cppunit/data/tdf153440.ttf.readme
new file mode 100644
index 0000000000..a4fc4465bd
--- /dev/null
+++ b/vcl/qa/cppunit/data/tdf153440.ttf.readme
@@ -0,0 +1,12 @@
+This is a subset copy of Noto Emoji font licensed under Open Font License and
+obtained from:
+
+ https://fonts.google.com/noto/specimen/Noto+Emoji
+
+And subset using hb-subset to contain only the one glyph used in the test:
+
+ hb-subset static/NotoEmoji-Regular.ttf --unicodes="1F33F,0020" --drop-tables=GSUB,STAT,vhea,vmtx -o tdf153440.ttf
+
+The space is added to the subset as it seems needed to get the font to work on
+Windows. (The --drop-tables argument is not necessary be saves a few bytes of
+stuff we don’t need.)
diff --git a/vcl/qa/cppunit/data/tdf156016.svg b/vcl/qa/cppunit/data/tdf156016.svg
new file mode 100644
index 0000000000..e54d786fe7
--- /dev/null
+++ b/vcl/qa/cppunit/data/tdf156016.svg
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd">
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<!--======================================================================-->
+<svg version="1.1" baseProfile="basic" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg-root" width="100%" height="100%" viewBox="0 0 99 99">
+<rect x="0" y="0" width="99" height="99" fill="#000000"/>
+</svg>
+
+
diff --git a/vcl/qa/cppunit/data/tdf73523.bmp b/vcl/qa/cppunit/data/tdf73523.bmp
new file mode 100644
index 0000000000..f4364ea3e8
--- /dev/null
+++ b/vcl/qa/cppunit/data/tdf73523.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/data/testBasicMorphology.png b/vcl/qa/cppunit/data/testBasicMorphology.png
new file mode 100644
index 0000000000..5db565779f
--- /dev/null
+++ b/vcl/qa/cppunit/data/testBasicMorphology.png
Binary files differ
diff --git a/vcl/qa/cppunit/data/testBasicMorphologyDilated1.png b/vcl/qa/cppunit/data/testBasicMorphologyDilated1.png
new file mode 100644
index 0000000000..ba335bab3c
--- /dev/null
+++ b/vcl/qa/cppunit/data/testBasicMorphologyDilated1.png
Binary files differ
diff --git a/vcl/qa/cppunit/data/testBasicMorphologyDilated1Eroded1.png b/vcl/qa/cppunit/data/testBasicMorphologyDilated1Eroded1.png
new file mode 100644
index 0000000000..3b10a949af
--- /dev/null
+++ b/vcl/qa/cppunit/data/testBasicMorphologyDilated1Eroded1.png
Binary files differ
diff --git a/vcl/qa/cppunit/data/testBasicMorphologyDilated2.png b/vcl/qa/cppunit/data/testBasicMorphologyDilated2.png
new file mode 100644
index 0000000000..30d90757ea
--- /dev/null
+++ b/vcl/qa/cppunit/data/testBasicMorphologyDilated2.png
Binary files differ
diff --git a/vcl/qa/cppunit/data/testBasicMorphologyDilated2Eroded1.png b/vcl/qa/cppunit/data/testBasicMorphologyDilated2Eroded1.png
new file mode 100644
index 0000000000..a506577da4
--- /dev/null
+++ b/vcl/qa/cppunit/data/testBasicMorphologyDilated2Eroded1.png
Binary files differ
diff --git a/vcl/qa/cppunit/data/testColorChange-red-linear-gradient.png b/vcl/qa/cppunit/data/testColorChange-red-linear-gradient.png
new file mode 100644
index 0000000000..6f2a9d0abf
--- /dev/null
+++ b/vcl/qa/cppunit/data/testColorChange-red-linear-gradient.png
Binary files differ
diff --git a/vcl/qa/cppunit/data/to-wmf.emf b/vcl/qa/cppunit/data/to-wmf.emf
new file mode 100644
index 0000000000..e1a7b9f9e5
--- /dev/null
+++ b/vcl/qa/cppunit/data/to-wmf.emf
Binary files differ
diff --git a/vcl/qa/cppunit/data/wmf-embedded-emfplus.wmf b/vcl/qa/cppunit/data/wmf-embedded-emfplus.wmf
new file mode 100644
index 0000000000..1e7f75b198
--- /dev/null
+++ b/vcl/qa/cppunit/data/wmf-embedded-emfplus.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/dndtest.cxx b/vcl/qa/cppunit/dndtest.cxx
new file mode 100644
index 0000000000..9120c19c0e
--- /dev/null
+++ b/vcl/qa/cppunit/dndtest.cxx
@@ -0,0 +1,311 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unotest/filters-test.hxx>
+#include <test/bootstrapfixture.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+
+#include <cppuhelper/implbase.hxx>
+
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragSourceListener.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTargetListener.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragGestureListener.hpp>
+
+using namespace ::com::sun::star::io;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::datatransfer::clipboard;
+using namespace ::com::sun::star::datatransfer::dnd;
+
+class MyWin : public WorkWindow
+{
+public:
+ MyWin( vcl::Window* pParent, WinBits nWinStyle );
+
+ void MouseMove( const MouseEvent& rMEvt );
+ void MouseButtonDown( const MouseEvent& rMEvt );
+ void MouseButtonUp( const MouseEvent& rMEvt );
+ void KeyInput( const KeyEvent& rKEvt );
+ void KeyUp( const KeyEvent& rKEvt );
+ void Paint( const Rectangle& rRect );
+ void Resize();
+};
+
+class MyDragAndDropListener: public ::cppu::WeakImplHelper < XDropTargetListener, XDragGestureListener, XDragSourceListener >
+{
+ vcl::Window * m_pWindow;
+
+public:
+
+ explicit MyDragAndDropListener( vcl::Window * pWindow ) : m_pWindow( pWindow ) {};
+
+ virtual void SAL_CALL dragGestureRecognized( const DragGestureEvent& dge ) throw(RuntimeException);
+ virtual void SAL_CALL drop( const DropTargetDropEvent& dtde ) throw(RuntimeException);
+ virtual void SAL_CALL dragEnter( const DropTargetDragEnterEvent& dtde ) throw(RuntimeException);
+ virtual void SAL_CALL dragExit( const DropTargetEvent& dte ) throw(RuntimeException);
+ virtual void SAL_CALL dragOver( const DropTargetDragEvent& dtde ) throw(RuntimeException);
+ virtual void SAL_CALL dropActionChanged( const DropTargetDragEvent& dtde ) throw(RuntimeException);
+ virtual void SAL_CALL dragDropEnd( const DragSourceDropEvent& dsde ) throw(RuntimeException);
+ virtual void SAL_CALL dragEnter( const DragSourceDragEvent& dsdee ) throw(RuntimeException);
+ virtual void SAL_CALL dragExit( const DragSourceEvent& dse ) throw(RuntimeException);
+ virtual void SAL_CALL dragOver( const DragSourceDragEvent& dsde ) throw(RuntimeException);
+ virtual void SAL_CALL dropActionChanged( const DragSourceDragEvent& dsde ) throw(RuntimeException);
+ virtual void SAL_CALL disposing( const EventObject& eo ) throw(RuntimeException);
+};
+
+class MyInfoBox : public InfoBox
+{
+
+public:
+
+ explicit MyInfoBox( vcl::Window* pParent );
+};
+
+class MyListBox : public ListBox
+{
+
+public:
+
+ explicit MyListBox( vcl::Window* pParent );
+};
+
+class StringTransferable : public ::cppu::WeakImplHelper< XTransferable >
+{
+ const OUString m_aData;
+ Sequence< DataFlavor > m_aFlavorList;
+
+public:
+ explicit StringTransferable( const OUString& rString ) : m_aData( rString ), m_aFlavorList( 1 )
+ {
+ DataFlavor df;
+
+ df.MimeType = "text/plain;charset=utf-16";
+ df.DataType = cppu::UnoType<OUString>::get();
+
+ m_aFlavorList[0] = df;
+ };
+
+ virtual Any SAL_CALL getTransferData( const DataFlavor& aFlavor ) throw(UnsupportedFlavorException, IOException, RuntimeException);
+ virtual Sequence< DataFlavor > SAL_CALL getTransferDataFlavors( ) throw(RuntimeException);
+ virtual bool SAL_CALL isDataFlavorSupported( const DataFlavor& aFlavor ) throw(RuntimeException);
+};
+
+class VclDnDTest : public test::BootstrapFixture
+{
+public:
+ VclDnDTest() : BootstrapFixture(true, false) {}
+
+ /// Play with drag and drop
+ void testDnD();
+
+ CPPUNIT_TEST_SUITE(VclDnDTest);
+ CPPUNIT_TEST(testDnD);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void VclDnDTest::testDnD()
+{
+ MyWin aMainWin( NULL, WB_APP | WB_STDWORK );
+ aMainWin.SetText( OUString( "Drag And Drop - Workbench" ) );
+ aMainWin.Show();
+
+ // test the clipboard code
+ Reference< XClipboard > xClipboard = aMainWin.GetClipboard();
+ CPPUNIT_ASSERT_MESSAGE("System clipboard not available",
+ xClipboard.is());
+
+ MyInfoBox aInfoBox( &aMainWin );
+ aInfoBox.Execute();
+
+ MyListBox aListBox( &aMainWin );
+ aListBox.setPosSizePixel( 10, 10, 100, 100 );
+ aListBox.InsertEntry( OUString("TestItem"));
+ aListBox.Show();
+}
+
+MyWin::MyWin( vcl::Window* pParent, WinBits nWinStyle ) :
+ WorkWindow( pParent, nWinStyle )
+{
+ Reference< XDropTargetListener > xListener = new MyDragAndDropListener( this );
+
+ Reference< XDropTarget > xDropTarget = GetDropTarget();
+ if( xDropTarget.is() )
+ {
+ xDropTarget->addDropTargetListener( xListener );
+ xDropTarget->setActive( true );
+ }
+
+ Reference< XDragGestureRecognizer > xRecognizer = GetDragGestureRecognizer();
+ if( xRecognizer.is() )
+ xRecognizer->addDragGestureListener( Reference< XDragGestureListener > ( xListener, UNO_QUERY ) );
+}
+
+void MyWin::MouseMove( const MouseEvent& rMEvt )
+{
+ WorkWindow::MouseMove( rMEvt );
+}
+
+void MyWin::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ WorkWindow::MouseButtonDown( rMEvt );
+}
+
+void MyWin::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ WorkWindow::MouseButtonUp( rMEvt );
+}
+
+void MyWin::KeyInput( const KeyEvent& rKEvt )
+{
+ WorkWindow::KeyInput( rKEvt );
+}
+
+void MyWin::KeyUp( const KeyEvent& rKEvt )
+{
+ WorkWindow::KeyUp( rKEvt );
+}
+
+void MyWin::Paint( const Rectangle& rRect )
+{
+ WorkWindow::Paint( rRect );
+}
+
+void MyWin::Resize()
+{
+ WorkWindow::Resize();
+}
+
+void SAL_CALL MyDragAndDropListener::dragGestureRecognized( const DragGestureEvent& dge ) throw(RuntimeException)
+{
+ Reference< XDragSource > xDragSource( dge.DragSource, UNO_QUERY );
+ xDragSource->startDrag( dge, -1, 0, 0, new StringTransferable( OUString("TestString") ), this );
+}
+
+void SAL_CALL MyDragAndDropListener::drop( const DropTargetDropEvent& dtde ) throw(RuntimeException)
+{
+ dtde.Context->dropComplete( true );
+}
+
+void SAL_CALL MyDragAndDropListener::dragEnter( const DropTargetDragEnterEvent& dtdee ) throw(RuntimeException)
+{
+ dtdee.Context->acceptDrag( dtdee.DropAction );
+}
+
+void SAL_CALL MyDragAndDropListener::dragExit( const DropTargetEvent& ) throw(RuntimeException)
+{
+}
+
+void SAL_CALL MyDragAndDropListener::dragOver( const DropTargetDragEvent& dtde ) throw(RuntimeException)
+{
+ dtde.Context->acceptDrag( dtde.DropAction );
+}
+
+void SAL_CALL MyDragAndDropListener::dropActionChanged( const DropTargetDragEvent& dtde ) throw(RuntimeException)
+{
+ dtde.Context->acceptDrag( dtde.DropAction );
+}
+
+void SAL_CALL MyDragAndDropListener::dragDropEnd( const DragSourceDropEvent& ) throw(RuntimeException)
+{
+}
+
+void SAL_CALL MyDragAndDropListener::dragEnter( const DragSourceDragEvent& ) throw(RuntimeException)
+{
+}
+
+void SAL_CALL MyDragAndDropListener::dragExit( const DragSourceEvent& ) throw(RuntimeException)
+{
+}
+
+void SAL_CALL MyDragAndDropListener::dragOver( const DragSourceDragEvent& ) throw(RuntimeException)
+{
+}
+
+void SAL_CALL MyDragAndDropListener::dropActionChanged( const DragSourceDragEvent& ) throw(RuntimeException)
+{
+}
+
+void SAL_CALL MyDragAndDropListener::disposing( const EventObject& ) throw(RuntimeException)
+{
+}
+
+MyInfoBox::MyInfoBox( vcl::Window* pParent ) : InfoBox( pParent,
+ OUString("dragging over this box should result in another window id in the drag log.") )
+{
+ Reference< XDropTargetListener > xListener = new MyDragAndDropListener( this );
+
+ Reference< XDropTarget > xDropTarget = GetDropTarget();
+ if( xDropTarget.is() )
+ {
+ xDropTarget->addDropTargetListener( xListener );
+ xDropTarget->setActive( true );
+ }
+
+ Reference< XDragGestureRecognizer > xRecognizer = GetDragGestureRecognizer();
+ if( xRecognizer.is() )
+ xRecognizer->addDragGestureListener( Reference< XDragGestureListener > ( xListener, UNO_QUERY ) );
+};
+
+MyListBox::MyListBox( vcl::Window* pParent ) : ListBox( pParent )
+{
+ Reference< XDropTargetListener > xListener = new MyDragAndDropListener( this );
+
+ Reference< XDropTarget > xDropTarget = GetDropTarget();
+ if( xDropTarget.is() )
+ {
+ xDropTarget->addDropTargetListener( xListener );
+ xDropTarget->setActive( true );
+ }
+
+ Reference< XDragGestureRecognizer > xRecognizer = GetDragGestureRecognizer();
+ if( xRecognizer.is() )
+ xRecognizer->addDragGestureListener( Reference< XDragGestureListener > ( xListener, UNO_QUERY ) );
+};
+
+Any SAL_CALL StringTransferable::getTransferData( const DataFlavor& )
+ throw(UnsupportedFlavorException, IOException, RuntimeException)
+{
+ return makeAny( m_aData );
+}
+
+Sequence< DataFlavor > SAL_CALL StringTransferable::getTransferDataFlavors( )
+ throw(RuntimeException)
+{
+ return m_aFlavorList;
+}
+
+bool SAL_CALL StringTransferable::isDataFlavorSupported( const DataFlavor& )
+ throw(RuntimeException)
+{
+ return true;
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclDnDTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/drawmode.cxx b/vcl/qa/cppunit/drawmode.cxx
new file mode 100644
index 0000000000..3dce4a7af6
--- /dev/null
+++ b/vcl/qa/cppunit/drawmode.cxx
@@ -0,0 +1,364 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+#include <cppunit/TestAssert.h>
+
+#include <tools/color.hxx>
+
+#include <vcl/font.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/settings.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <drawmode.hxx>
+
+class VclDrawModeTest : public test::BootstrapFixture
+{
+public:
+ VclDrawModeTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ void testDrawModeLineColor();
+ void testDrawModeFillColor();
+ void testDrawModeHatchColor();
+ void testDrawModeTextColor();
+ void testDrawModeFontColor();
+ void testDrawModeBitmapEx();
+
+ CPPUNIT_TEST_SUITE(VclDrawModeTest);
+
+ CPPUNIT_TEST(testDrawModeLineColor);
+ CPPUNIT_TEST(testDrawModeFillColor);
+ CPPUNIT_TEST(testDrawModeHatchColor);
+ CPPUNIT_TEST(testDrawModeTextColor);
+ CPPUNIT_TEST(testDrawModeFontColor);
+ CPPUNIT_TEST(testDrawModeBitmapEx);
+
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void VclDrawModeTest::testDrawModeLineColor()
+{
+ const Color aColor = COL_RED;
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ const StyleSettings aStyleSettings;
+
+ CPPUNIT_ASSERT_EQUAL(
+ COL_BLACK, vcl::drawmode::GetLineColor(aColor, DrawModeFlags::BlackLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ COL_WHITE, vcl::drawmode::GetLineColor(aColor, DrawModeFlags::WhiteLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ Color(cLum, cLum, cLum),
+ vcl::drawmode::GetLineColor(aColor, DrawModeFlags::GrayLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aStyleSettings.GetWindowTextColor(),
+ vcl::drawmode::GetLineColor(aColor, DrawModeFlags::SettingsLine, aStyleSettings));
+
+ Color aTransparentRed = COL_RED;
+ aTransparentRed.SetAlpha(100);
+
+ CPPUNIT_ASSERT_EQUAL(
+ aTransparentRed,
+ vcl::drawmode::GetLineColor(aTransparentRed, DrawModeFlags::BlackLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aTransparentRed,
+ vcl::drawmode::GetLineColor(aTransparentRed, DrawModeFlags::WhiteLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aTransparentRed,
+ vcl::drawmode::GetLineColor(aTransparentRed, DrawModeFlags::GrayLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aTransparentRed,
+ vcl::drawmode::GetLineColor(aTransparentRed, DrawModeFlags::SettingsLine, aStyleSettings));
+
+ // noops
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetLineColor(aColor, DrawModeFlags::NoFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetLineColor(aColor, DrawModeFlags::BlackFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetLineColor(aColor, DrawModeFlags::WhiteFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetLineColor(aColor, DrawModeFlags::GrayFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetLineColor(aColor, DrawModeFlags::SettingsFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetLineColor(aColor, DrawModeFlags::BlackText, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetLineColor(aColor, DrawModeFlags::WhiteText, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetLineColor(aColor, DrawModeFlags::GrayText, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetLineColor(aColor, DrawModeFlags::SettingsText, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetLineColor(aColor, DrawModeFlags::BlackBitmap, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetLineColor(aColor, DrawModeFlags::WhiteBitmap, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetLineColor(aColor, DrawModeFlags::GrayBitmap, aStyleSettings));
+}
+
+void VclDrawModeTest::testDrawModeFillColor()
+{
+ const Color aColor = COL_RED;
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ const StyleSettings aStyleSettings;
+
+ CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, vcl::drawmode::GetFillColor(aColor, DrawModeFlags::NoFill,
+ aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ COL_BLACK, vcl::drawmode::GetFillColor(aColor, DrawModeFlags::BlackFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ COL_WHITE, vcl::drawmode::GetFillColor(aColor, DrawModeFlags::WhiteFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ Color(cLum, cLum, cLum),
+ vcl::drawmode::GetFillColor(aColor, DrawModeFlags::GrayFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aStyleSettings.GetWindowColor(),
+ vcl::drawmode::GetFillColor(aColor, DrawModeFlags::SettingsFill, aStyleSettings));
+
+ Color aTransparentRed = COL_RED;
+ aTransparentRed.SetAlpha(100);
+
+ CPPUNIT_ASSERT_EQUAL(
+ aTransparentRed,
+ vcl::drawmode::GetFillColor(aTransparentRed, DrawModeFlags::BlackLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aTransparentRed,
+ vcl::drawmode::GetFillColor(aTransparentRed, DrawModeFlags::WhiteLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aTransparentRed,
+ vcl::drawmode::GetFillColor(aTransparentRed, DrawModeFlags::GrayLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aTransparentRed,
+ vcl::drawmode::GetFillColor(aTransparentRed, DrawModeFlags::SettingsLine, aStyleSettings));
+
+ // noops
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetFillColor(aColor, DrawModeFlags::BlackLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetFillColor(aColor, DrawModeFlags::WhiteLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetFillColor(aColor, DrawModeFlags::GrayLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetFillColor(aColor, DrawModeFlags::SettingsLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetFillColor(aColor, DrawModeFlags::BlackText, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetFillColor(aColor, DrawModeFlags::WhiteText, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetFillColor(aColor, DrawModeFlags::GrayText, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetFillColor(aColor, DrawModeFlags::SettingsText, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetFillColor(aColor, DrawModeFlags::BlackBitmap, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetFillColor(aColor, DrawModeFlags::WhiteBitmap, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetFillColor(aColor, DrawModeFlags::GrayBitmap, aStyleSettings));
+}
+
+void VclDrawModeTest::testDrawModeHatchColor()
+{
+ const Color aColor = COL_RED;
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ const StyleSettings aStyleSettings;
+
+ CPPUNIT_ASSERT_EQUAL(
+ COL_BLACK, vcl::drawmode::GetHatchColor(aColor, DrawModeFlags::BlackLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ COL_WHITE, vcl::drawmode::GetHatchColor(aColor, DrawModeFlags::WhiteLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ Color(cLum, cLum, cLum),
+ vcl::drawmode::GetHatchColor(aColor, DrawModeFlags::GrayLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aStyleSettings.GetWindowTextColor(),
+ vcl::drawmode::GetHatchColor(aColor, DrawModeFlags::SettingsLine, aStyleSettings));
+
+ // noops
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetHatchColor(aColor, DrawModeFlags::NoFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetHatchColor(aColor, DrawModeFlags::BlackFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetHatchColor(aColor, DrawModeFlags::WhiteFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetHatchColor(aColor, DrawModeFlags::GrayFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetHatchColor(aColor, DrawModeFlags::SettingsFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetHatchColor(aColor, DrawModeFlags::BlackText, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetHatchColor(aColor, DrawModeFlags::WhiteText, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetHatchColor(aColor, DrawModeFlags::GrayText, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetHatchColor(aColor, DrawModeFlags::SettingsText, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetHatchColor(aColor, DrawModeFlags::BlackBitmap, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetHatchColor(aColor, DrawModeFlags::WhiteBitmap, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetHatchColor(aColor, DrawModeFlags::GrayBitmap, aStyleSettings));
+}
+
+void VclDrawModeTest::testDrawModeTextColor()
+{
+ const Color aColor = COL_RED;
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ const StyleSettings aStyleSettings;
+
+ CPPUNIT_ASSERT_EQUAL(
+ COL_BLACK, vcl::drawmode::GetTextColor(aColor, DrawModeFlags::BlackText, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ COL_WHITE, vcl::drawmode::GetTextColor(aColor, DrawModeFlags::WhiteText, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ Color(cLum, cLum, cLum),
+ vcl::drawmode::GetTextColor(aColor, DrawModeFlags::GrayText, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aStyleSettings.GetWindowTextColor(),
+ vcl::drawmode::GetTextColor(aColor, DrawModeFlags::SettingsText, aStyleSettings));
+
+ // noops
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetTextColor(aColor, DrawModeFlags::BlackLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetTextColor(aColor, DrawModeFlags::WhiteLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetTextColor(aColor, DrawModeFlags::GrayLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetTextColor(aColor, DrawModeFlags::SettingsLine, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetTextColor(aColor, DrawModeFlags::NoFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetTextColor(aColor, DrawModeFlags::BlackFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetTextColor(aColor, DrawModeFlags::WhiteFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetTextColor(aColor, DrawModeFlags::GrayFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetTextColor(aColor, DrawModeFlags::SettingsFill, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetTextColor(aColor, DrawModeFlags::BlackBitmap, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetTextColor(aColor, DrawModeFlags::WhiteBitmap, aStyleSettings));
+ CPPUNIT_ASSERT_EQUAL(
+ aColor, vcl::drawmode::GetTextColor(aColor, DrawModeFlags::GrayBitmap, aStyleSettings));
+}
+
+void VclDrawModeTest::testDrawModeFontColor()
+{
+ const StyleSettings aStyleSettings;
+
+ vcl::Font aFont;
+ aFont.SetFillColor(COL_RED);
+
+ // black text and fill
+ aFont.SetTransparent(false);
+ vcl::Font aTestFont = vcl::drawmode::GetFont(
+ aFont, DrawModeFlags::BlackText | DrawModeFlags::BlackFill, aStyleSettings);
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, aTestFont.GetColor());
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, aTestFont.GetFillColor());
+
+ aFont.SetTransparent(true);
+ aTestFont = vcl::drawmode::GetFont(aFont, DrawModeFlags::BlackText | DrawModeFlags::BlackFill,
+ aStyleSettings);
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, aTestFont.GetColor());
+ CPPUNIT_ASSERT_EQUAL(COL_RED, aTestFont.GetFillColor());
+
+ // white text and fill
+ aFont.SetTransparent(false);
+ aTestFont = vcl::drawmode::GetFont(aFont, DrawModeFlags::WhiteText | DrawModeFlags::WhiteFill,
+ aStyleSettings);
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, aTestFont.GetColor());
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, aTestFont.GetFillColor());
+
+ aFont.SetTransparent(true);
+ aTestFont = vcl::drawmode::GetFont(aFont, DrawModeFlags::WhiteText | DrawModeFlags::WhiteFill,
+ aStyleSettings);
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, aTestFont.GetColor());
+ CPPUNIT_ASSERT_EQUAL(COL_RED, aTestFont.GetFillColor());
+
+ // gray text and fill
+ const sal_uInt8 cTextLum = aFont.GetColor().GetLuminance();
+ const Color aTextGray(cTextLum, cTextLum, cTextLum);
+ const sal_uInt8 cFillLum = aFont.GetFillColor().GetLuminance();
+ const Color aFillGray(cFillLum, cFillLum, cFillLum);
+
+ aFont.SetTransparent(false);
+ aTestFont = vcl::drawmode::GetFont(aFont, DrawModeFlags::GrayText | DrawModeFlags::GrayFill,
+ aStyleSettings);
+ CPPUNIT_ASSERT_EQUAL(aTextGray, aTestFont.GetColor());
+ CPPUNIT_ASSERT_EQUAL(aFillGray, aTestFont.GetFillColor());
+
+ aFont.SetTransparent(true);
+ aTestFont = vcl::drawmode::GetFont(aFont, DrawModeFlags::GrayText | DrawModeFlags::GrayFill,
+ aStyleSettings);
+ CPPUNIT_ASSERT_EQUAL(aTextGray, aTestFont.GetColor());
+ CPPUNIT_ASSERT_EQUAL(COL_RED, aTestFont.GetFillColor());
+
+ // no text fill
+ aFont.SetTransparent(false);
+ aTestFont = vcl::drawmode::GetFont(aFont, DrawModeFlags::NoFill, aStyleSettings);
+ CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, aTestFont.GetFillColor());
+
+ aFont.SetTransparent(true);
+ aTestFont = vcl::drawmode::GetFont(aFont, DrawModeFlags::NoFill, aStyleSettings);
+ CPPUNIT_ASSERT_EQUAL(COL_RED, aTestFont.GetFillColor());
+
+ // white text and fill
+ aFont.SetTransparent(false);
+ aTestFont = vcl::drawmode::GetFont(
+ aFont, DrawModeFlags::SettingsText | DrawModeFlags::SettingsFill, aStyleSettings);
+ CPPUNIT_ASSERT_EQUAL(aStyleSettings.GetWindowTextColor(), aTestFont.GetColor());
+ CPPUNIT_ASSERT_EQUAL(aStyleSettings.GetWindowColor(), aTestFont.GetFillColor());
+
+ aFont.SetTransparent(true);
+ aTestFont = vcl::drawmode::GetFont(
+ aFont, DrawModeFlags::SettingsText | DrawModeFlags::SettingsFill, aStyleSettings);
+ CPPUNIT_ASSERT_EQUAL(aStyleSettings.GetWindowTextColor(), aTestFont.GetColor());
+ CPPUNIT_ASSERT_EQUAL(COL_RED, aTestFont.GetFillColor());
+}
+
+void VclDrawModeTest::testDrawModeBitmapEx()
+{
+ const StyleSettings aStyleSettings;
+
+ Bitmap aBmp(Size(1, 1), vcl::PixelFormat::N24_BPP);
+ BitmapWriteAccess(aBmp).SetPixel(0, 0, BitmapColor(COL_RED));
+
+ BitmapEx aBmpEx(aBmp);
+
+ {
+ BitmapEx aResultBitmapEx(vcl::drawmode::GetBitmapEx(aBmpEx, DrawModeFlags::GrayBitmap));
+ Bitmap aResultBitmap(aResultBitmapEx.GetBitmap());
+ BitmapScopedReadAccess pReadAccess(aResultBitmap);
+
+ const BitmapColor& rColor = pReadAccess->GetColor(0, 0);
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x26, 0x26, 0x26), rColor);
+ }
+
+ // any other operation other than DrawModeFlags::GrayBitmap is a noop
+ {
+ BitmapEx aResultBitmapEx(vcl::drawmode::GetBitmapEx(aBmpEx, DrawModeFlags::NoFill));
+ Bitmap aResultBitmap(aResultBitmapEx.GetBitmap());
+ BitmapScopedReadAccess pReadAccess(aResultBitmap);
+
+ const BitmapColor& rColor = pReadAccess->GetColor(0, 0);
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_RED), rColor);
+ }
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclDrawModeTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/errorhandler.cxx b/vcl/qa/cppunit/errorhandler.cxx
new file mode 100644
index 0000000000..eaa62c7f5d
--- /dev/null
+++ b/vcl/qa/cppunit/errorhandler.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/.
+ */
+
+#include <test/bootstrapfixture.hxx>
+#include <cppunit/TestAssert.h>
+
+#include <vcl/errinf.hxx>
+
+class ErrorHandlerTest;
+
+namespace {
+
+class MockErrorHandler : private ErrorHandler
+{
+ friend ErrorHandlerTest;
+
+protected:
+ virtual bool CreateString(const ErrCodeMsg&, OUString &rErrString) const override
+ {
+ rErrString = "Non-dynamic error";
+ return true;
+ }
+};
+
+}
+
+class ErrorHandlerTest : public test::BootstrapFixture
+{
+public:
+ ErrorHandlerTest() : BootstrapFixture(true, false) {}
+
+ void testGetErrorString();
+
+ CPPUNIT_TEST_SUITE(ErrorHandlerTest);
+ CPPUNIT_TEST(testGetErrorString);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void ErrorHandlerTest::testGetErrorString()
+{
+ MockErrorHandler aErrHdlr;
+ OUString aErrStr;
+
+ CPPUNIT_ASSERT_MESSAGE("GetErrorString(ERRCODE_ABORT, aErrStr) should return false",
+ !ErrorHandler::GetErrorString(ERRCODE_ABORT, aErrStr));
+ // normally protected, but MockErrorHandler is a friend of this class
+ aErrHdlr.CreateString(ERRCODE_ABORT, aErrStr);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("error message should be non-dynamic", OUString("Non-dynamic error"), aErrStr);
+
+ CPPUNIT_ASSERT_MESSAGE("GetErrorString(ERRCODE_NONE, aErrStr) should return false",
+ !ErrorHandler::GetErrorString(ERRCODE_NONE, aErrStr));
+ aErrHdlr.CreateString(ERRCODE_NONE, aErrStr);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("error message should be non-dynamic", OUString("Non-dynamic error"), aErrStr);
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ErrorHandlerTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/filter/igif/data/logic-lazy-read.gif b/vcl/qa/cppunit/filter/igif/data/logic-lazy-read.gif
new file mode 100644
index 0000000000..3d18891f7b
--- /dev/null
+++ b/vcl/qa/cppunit/filter/igif/data/logic-lazy-read.gif
Binary files differ
diff --git a/vcl/qa/cppunit/filter/igif/igif.cxx b/vcl/qa/cppunit/filter/igif/igif.cxx
new file mode 100644
index 0000000000..4c730247f4
--- /dev/null
+++ b/vcl/qa/cppunit/filter/igif/igif.cxx
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+
+#include <tools/stream.hxx>
+#include <unotest/directories.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+
+using namespace com::sun::star;
+
+namespace
+{
+constexpr OUStringLiteral DATA_DIRECTORY = u"/vcl/qa/cppunit/filter/igif/data/";
+
+/// Covers vcl/source/filter/igif/ fixes.
+class Test : public test::BootstrapFixture
+{
+};
+
+CPPUNIT_TEST_FIXTURE(Test, testLogicLazyRead)
+{
+ GraphicFilter aGraphicFilter;
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(DATA_DIRECTORY) + "logic-lazy-read.gif";
+ SvFileStream aStream(aURL, StreamMode::READ);
+ Graphic aGraphic = aGraphicFilter.ImportUnloadedGraphic(aStream);
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 0
+ // - Actual : 10
+ // i.e. the preferred unit was pixels, not mm100.
+ CPPUNIT_ASSERT_EQUAL(MapUnit::Map100thMM, aGraphic.GetPrefMapMode().GetMapUnit());
+}
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/filter/ipdf/data/add-visible-signature-last-page.pdf b/vcl/qa/cppunit/filter/ipdf/data/add-visible-signature-last-page.pdf
new file mode 100644
index 0000000000..c321abd095
--- /dev/null
+++ b/vcl/qa/cppunit/filter/ipdf/data/add-visible-signature-last-page.pdf
@@ -0,0 +1,111 @@
+%PDF-1.6
+%äüöß
+2 0 obj
+<</Length 3 0 R/Filter/FlateDecode>>
+stream
+xœíT»n1 ìõ¬]âK/ÎI‘8Ø‚M€¸ñïg´»Òù\®\‡ÃÝPÔÎnf¡—ô—2eÎÚ¨ˆp¯Bµ¿ÏéÇýIBãóü”2Ž”~§#Qw´ÑDɶèÆ¿#ãgz¼K™ÝkµéM¤–ÁkRؼ7å¦
++Rw 2QÎÝÉ#XQ˲sv`7Vܸ$íÁ}d˜°ÔFÚœTXI+Š8²+]H£°×Nž+kC†£­"À†ö‚TÃtaðçÆ@+"]9ZY ¨ÉÅú«Rœqö aœ!êìO< Ò«Ä
+d\Š)WiK…O.×H/\Ýè$Á°¹Ì¡ºÏŽga˜-Ëê/Üw-ö ç!¸¡£Â9cDcõÔ
+¶ΩÒÄPéÔqeœ:/†± t­qìiö0·8{œ{¾N10Ç\N9U¸ñÒ¸Aoݵ¥Óš­Z/¹_b7Fß¾ìÄ ¾_aû_Ÿîütç»ó{ºÿßëÒ!•àJ@ÒACj½‰l4#¥µ}Ô¢öÛHš‘ë­·ÌëÕýÎGåc;2ÝÓ?cQ@
+endstream
+endobj
+
+3 0 obj
+426
+endobj
+
+5 0 obj
+<</Length 6 0 R/Filter/FlateDecode>>
+stream
+xœ­;1 D{ŸÂõ–Äqr$º…‚ðZØf¯O¼á#Q#+r&ž¼‘™x #‡‚Y„ª Zí}>ÂaÀ;zÍgà6
+xƒn «šð­:dúàüÖ8 À”’±¶aQÝ^D,;7#M3qñ„ĵ1• ±¦Æ¬$Íó}ùý5Á+'p¬Î5³y^Šq·YW]ÚÙ¶u®LÝÃ#>"Bù
+endstream
+endobj
+
+6 0 obj
+167
+endobj
+
+8 0 obj
+<<
+>>
+endobj
+
+9 0 obj
+<</Font 8 0 R
+/ProcSet[/PDF/Text]
+>>
+endobj
+
+1 0 obj
+<</Type/Page/Parent 7 0 R/Resources 9 0 R/MediaBox[0 0 612 792]/Group<</S/Transparency/CS/DeviceRGB/I true>>/Contents 2 0 R>>
+endobj
+
+4 0 obj
+<</Type/Page/Parent 7 0 R/Resources 9 0 R/MediaBox[0 0 612 792]/Group<</S/Transparency/CS/DeviceRGB/I true>>/Contents 5 0 R>>
+endobj
+
+10 0 obj
+<</Count 2/First 11 0 R/Last 12 0 R
+>>
+endobj
+
+11 0 obj
+<</Count 0/Title<FEFF005000610067006500200031>
+/Dest[1 0 R/XYZ 0 792 0]/Parent 10 0 R/Next 12 0 R>>
+endobj
+
+12 0 obj
+<</Count 0/Title<FEFF005000610067006500200032>
+/Dest[4 0 R/XYZ 0 792 0]/Parent 10 0 R/Prev 11 0 R>>
+endobj
+
+7 0 obj
+<</Type/Pages
+/Resources 9 0 R
+/MediaBox[ 0 0 612 792 ]
+/Kids[ 1 0 R 4 0 R ]
+/Count 2>>
+endobj
+
+13 0 obj
+<</Type/Catalog/Pages 7 0 R
+/OpenAction[1 0 R /XYZ null null 0]
+/Outlines 10 0 R
+>>
+endobj
+
+14 0 obj
+<</Author<FEFF004D0069006B006C006F0073002000560061006A006E0061>
+/Creator<FEFF0044007200610077>
+/Producer<FEFF004C0069006200720065004F0066006600690063006500440065007600200037002E0031>
+/CreationDate(D:20200624113559+02'00')>>
+endobj
+
+xref
+0 15
+0000000000 65535 f
+0000000869 00000 n
+0000000019 00000 n
+0000000516 00000 n
+0000001011 00000 n
+0000000536 00000 n
+0000000774 00000 n
+0000001443 00000 n
+0000000794 00000 n
+0000000816 00000 n
+0000001153 00000 n
+0000001209 00000 n
+0000001326 00000 n
+0000001547 00000 n
+0000001648 00000 n
+trailer
+<</Size 15/Root 13 0 R
+/Info 14 0 R
+/ID [ <F52D3902B7388C216897409EFCC78884>
+<F52D3902B7388C216897409EFCC78884> ]
+/DocChecksum /67E881EB92900640250E0931504CE95E
+>>
+startxref
+1889
+%%EOF
diff --git a/vcl/qa/cppunit/filter/ipdf/data/array-mixed-numbers-and-elements.pdf b/vcl/qa/cppunit/filter/ipdf/data/array-mixed-numbers-and-elements.pdf
new file mode 100644
index 0000000000..01030ecf88
--- /dev/null
+++ b/vcl/qa/cppunit/filter/ipdf/data/array-mixed-numbers-and-elements.pdf
@@ -0,0 +1,55 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+ /Type /Catalog
+ /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+ /Type /Pages
+ /MediaBox [0 0 200 300]
+ /Count 1
+ /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+ /Type /Page
+ /Parent 2 0 R
+ /Contents 4 0 R
+ /Test [1 4 0 R 3 false 5 (Lieral) 7 <90>]
+>>
+endobj
+4 0 obj <<
+ /Length 188
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f
+0000000015 00000 n
+0000000068 00000 n
+0000000157 00000 n
+0000000270 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 5
+>>
+startxref
+510
+%%EOF
diff --git a/vcl/qa/cppunit/filter/ipdf/data/comment-end.pdf b/vcl/qa/cppunit/filter/ipdf/data/comment-end.pdf
new file mode 100644
index 0000000000..6f1ad86f5c
--- /dev/null
+++ b/vcl/qa/cppunit/filter/ipdf/data/comment-end.pdf
@@ -0,0 +1,69 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+ /Type /Catalog
+ /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+ /Type /Pages
+ /MediaBox [0 0 200 300]
+ /Count 1
+ /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+ /Type /Page
+ /Parent 2 0 R
+ /Contents 4 0 R
+>>
+endobj
+4 0 obj <<
+ /Length 4
+>>
+stream
+q
+Q
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f
+0000000015 00000 n
+0000000068 00000 n
+0000000157 00000 n
+0000000226 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 5
+ /Prev 541
+>>
+startxref
+280
+%%EOF %%TEST
+4 0 obj <<
+ /Length 5 0 R
+>>
+stream
+q
+Q
+endstream
+endobj
+5 0 obj
+4
+endobj
+xref
+0 6
+0000000000 65535 f
+0000000015 00000 n
+0000000068 00000 n
+0000000157 00000 n
+0000000466 00000 n
+0000000524 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 6
+>>
+startxref
+280
+%%EOF
diff --git a/vcl/qa/cppunit/filter/ipdf/data/dict-array-dict.pdf b/vcl/qa/cppunit/filter/ipdf/data/dict-array-dict.pdf
new file mode 100644
index 0000000000..73de3117b9
--- /dev/null
+++ b/vcl/qa/cppunit/filter/ipdf/data/dict-array-dict.pdf
@@ -0,0 +1,55 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+ /Type /Catalog
+ /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+ /Type /Pages
+ /MediaBox [0 0 200 300]
+ /Count 1
+ /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+ /Type /Page
+ /Parent 2 0 R
+ /Contents 4 0 R
+ /Key[<</InnerKey 42>>]
+>>
+endobj
+4 0 obj <<
+ /Length 188
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f
+0000000015 00000 n
+0000000068 00000 n
+0000000157 00000 n
+0000000251 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 5
+>>
+startxref
+491
+%%EOF
diff --git a/vcl/qa/cppunit/filter/ipdf/data/real-numbers.pdf b/vcl/qa/cppunit/filter/ipdf/data/real-numbers.pdf
new file mode 100644
index 0000000000..409360c546
--- /dev/null
+++ b/vcl/qa/cppunit/filter/ipdf/data/real-numbers.pdf
@@ -0,0 +1,55 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+ /Type /Catalog
+ /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+ /Type /Pages
+ /MediaBox [0 0 200 300]
+ /Count 1
+ /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+ /Type /Page
+ /Parent 2 0 R
+ /Contents 4 0 R
+>>
+endobj
+4 0 obj <<
+ /Length 188
+ /Test [.00 1.00 .00 1.00 .00 1.00]
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f
+0000000015 00000 n
+0000000068 00000 n
+0000000157 00000 n
+0000000226 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 5
+>>
+startxref
+503
+%%EOF
diff --git a/vcl/qa/cppunit/filter/ipdf/ipdf.cxx b/vcl/qa/cppunit/filter/ipdf/ipdf.cxx
new file mode 100644
index 0000000000..dbe7ada758
--- /dev/null
+++ b/vcl/qa/cppunit/filter/ipdf/ipdf.cxx
@@ -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/.
+ */
+
+#include <test/unoapi_test.hxx>
+
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/drawing/XDrawView.hpp>
+#include <com/sun/star/view/XSelectionSupplier.hpp>
+#include <com/sun/star/xml/crypto/SEInitializer.hpp>
+
+#include <comphelper/propertyvalue.hxx>
+#include <unotools/tempfile.hxx>
+#include <sfx2/sfxbasemodel.hxx>
+#include <svx/svdview.hxx>
+#include <sfx2/viewsh.hxx>
+#include <svx/signaturelinehelper.hxx>
+#include <sfx2/objsh.hxx>
+#include <vcl/filter/PDFiumLibrary.hxx>
+#include <vcl/filter/pdfdocument.hxx>
+
+using namespace ::com::sun::star;
+
+/// Covers vcl/source/filter/ipdf/ fixes.
+class VclFilterIpdfTest : public UnoApiTest
+{
+private:
+ uno::Reference<xml::crypto::XSEInitializer> mxSEInitializer;
+ uno::Reference<xml::crypto::XXMLSecurityContext> mxSecurityContext;
+
+public:
+ VclFilterIpdfTest()
+ : UnoApiTest("/vcl/qa/cppunit/filter/ipdf/data/")
+ {
+ }
+
+ void setUp() override;
+ void tearDown() override;
+ uno::Reference<xml::crypto::XXMLSecurityContext>& getSecurityContext()
+ {
+ return mxSecurityContext;
+ }
+};
+
+void VclFilterIpdfTest::setUp()
+{
+ UnoApiTest::setUp();
+ MacrosTest::setUpNssGpg(m_directories, "vcl_filter_ipdf");
+
+ mxSEInitializer = xml::crypto::SEInitializer::create(mxComponentContext);
+ mxSecurityContext = mxSEInitializer->createSecurityContext(OUString());
+}
+
+void VclFilterIpdfTest::tearDown()
+{
+ MacrosTest::tearDownNssGpg();
+
+ UnoApiTest::tearDown();
+}
+
+CPPUNIT_TEST_FIXTURE(VclFilterIpdfTest, testPDFAddVisibleSignatureLastPage)
+{
+ // FIXME: the DPI check should be removed when either (1) the test is fixed to work with
+ // non-default DPI; or (2) unit tests on Windows are made to use svp VCL plugin.
+ if (!IsDefaultDPI())
+ return;
+ // Given: copy the test document to a temporary file, as it'll be modified.
+ createTempCopy(u"add-visible-signature-last-page.pdf");
+
+ // Open it.
+ uno::Sequence<beans::PropertyValue> aArgs = { comphelper::makePropertyValue("ReadOnly", true) };
+ mxComponent
+ = loadFromDesktop(maTempFile.GetURL(), "com.sun.star.drawing.DrawingDocument", aArgs);
+ SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
+ CPPUNIT_ASSERT(pBaseModel);
+ SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
+ CPPUNIT_ASSERT(pObjectShell);
+
+ // Add a signature line to the 2nd page.
+ uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
+ uno::Reference<drawing::XShape> xShape(
+ xFactory->createInstance("com.sun.star.drawing.GraphicObjectShape"), uno::UNO_QUERY);
+ xShape->setPosition(awt::Point(1000, 15000));
+ xShape->setSize(awt::Size(10000, 10000));
+ uno::Reference<drawing::XDrawPagesSupplier> xSupplier(mxComponent, uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPages> xDrawPages = xSupplier->getDrawPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), xDrawPages->getCount());
+
+ uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawView> xController(xModel->getCurrentController(), uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(1), uno::UNO_QUERY);
+ xController->setCurrentPage(xDrawPage);
+ xDrawPage->add(xShape);
+
+ // Select it and assign a certificate.
+ uno::Reference<view::XSelectionSupplier> xSelectionSupplier(pBaseModel->getCurrentController(),
+ uno::UNO_QUERY);
+ xSelectionSupplier->select(uno::Any(xShape));
+ auto xEnv = getSecurityContext()->getSecurityEnvironment();
+ auto xCert = GetValidCertificate(xEnv->getPersonalCertificates(), xEnv);
+ if (!xCert)
+ {
+ return;
+ }
+ SdrView* pView = SfxViewShell::Current()->GetDrawView();
+ svx::SignatureLineHelper::setShapeCertificate(pView, xCert);
+
+ // the document is modified now, but Sign function can't show SaveAs dialog
+ // in unit test, so just clear the modified
+ pObjectShell->SetModified(false);
+
+ // When: do the actual signing.
+ pObjectShell->SignDocumentContentUsingCertificate(xCert);
+
+ // Then: count the # of shapes on the signature widget/annotation.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ if (!pPdfDocument)
+ return;
+ // Last page.
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/1);
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 1
+ // - Actual : 0
+ // i.e. the signature was there, but it was on the first page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getAnnotationCount());
+ std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnot = pPdfPage->getAnnotation(0);
+ CPPUNIT_ASSERT_EQUAL(4, pAnnot->getObjectCount());
+}
+
+CPPUNIT_TEST_FIXTURE(VclFilterIpdfTest, testDictArrayDict)
+{
+ // Load a file that has markup like this:
+ // 3 0 obj <<
+ // /Key[<</InnerKey 42>>]
+ // >>
+ OUString aSourceURL = createFileURL(u"dict-array-dict.pdf");
+ SvFileStream aFile(aSourceURL, StreamMode::READ);
+ vcl::filter::PDFDocument aDocument;
+ CPPUNIT_ASSERT(aDocument.Read(aFile));
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT(!aPages.empty());
+ vcl::filter::PDFObjectElement* pPage = aPages[0];
+ auto pKey = dynamic_cast<vcl::filter::PDFArrayElement*>(pPage->Lookup("Key"_ostr));
+
+ // Without the accompanying fix in place, this test would have failed, because the value of Key
+ // was a dictionary element, not an array element.
+ CPPUNIT_ASSERT(pKey);
+}
+
+CPPUNIT_TEST_FIXTURE(VclFilterIpdfTest, testRealNumbers)
+{
+ // Load a file that has markup like this:
+ // 4 0 obj <<
+ // /Test [.00 1.00 .00 1.00 .00 1.00]
+ // >>
+ OUString aSourceURL = createFileURL(u"real-numbers.pdf");
+ SvFileStream aFile(aSourceURL, StreamMode::READ);
+ vcl::filter::PDFDocument aDocument;
+
+ // Without the accompanying fix in place, this test would have failed, because the parser
+ // stopped when it saw an unexpected "." character.
+ CPPUNIT_ASSERT(aDocument.Read(aFile));
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT(!aPages.empty());
+}
+
+CPPUNIT_TEST_FIXTURE(VclFilterIpdfTest, testCommentEnd)
+{
+ // Load the test document:
+ // - it has two xrefs
+ // - second xref has an updated page content object with an indirect length
+ // - last startxref refers to the first xref
+ // - first xref has a /Prev to the second xref
+ // - first xref is terminated by a \r, which is not followed by a newline
+ // this means that if reading doesn't stop at the end of the first xref, then we'll try to look
+ // up the offset of the length object, which we don't yet have
+ OUString aSourceURL = createFileURL(u"comment-end.pdf");
+ SvFileStream aFile(aSourceURL, StreamMode::READ);
+ vcl::filter::PDFDocument aDocument;
+
+ // Without the accompanying fix in place, this test would have failed, because Tokenize() didn't
+ // stop at the end of the first xref.
+ CPPUNIT_ASSERT(aDocument.Read(aFile));
+}
+
+CPPUNIT_TEST_FIXTURE(VclFilterIpdfTest, testMixedArrayWithNumbers)
+{
+ // Load a file that has markup like this:
+ // 3 0 obj <<
+ // /Test [1 4 0 R 3 false 5 (Lieral) 7 <90>]
+ // >>
+ OUString aSourceURL = createFileURL(u"array-mixed-numbers-and-elements.pdf");
+ SvFileStream aFile(aSourceURL, StreamMode::READ);
+ vcl::filter::PDFDocument aDocument;
+ CPPUNIT_ASSERT(aDocument.Read(aFile));
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT(!aPages.empty());
+ vcl::filter::PDFObjectElement* pPage = aPages[0];
+ auto pTest = dynamic_cast<vcl::filter::PDFArrayElement*>(pPage->Lookup("Test"_ostr));
+ std::vector<vcl::filter::PDFElement*> aElements = pTest->GetElements();
+
+ // Without the accompanying fix in place, this test would have failed with
+ // the array containing the wrong number of elements and in the incorrect order
+ CPPUNIT_ASSERT_EQUAL(8, static_cast<int>(aElements.size()));
+ CPPUNIT_ASSERT(dynamic_cast<vcl::filter::PDFNumberElement*>(aElements[0]));
+ CPPUNIT_ASSERT(dynamic_cast<vcl::filter::PDFNumberElement*>(aElements[2]));
+ CPPUNIT_ASSERT(dynamic_cast<vcl::filter::PDFNumberElement*>(aElements[4]));
+ CPPUNIT_ASSERT(dynamic_cast<vcl::filter::PDFNumberElement*>(aElements[6]));
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/font.cxx b/vcl/qa/cppunit/font.cxx
new file mode 100644
index 0000000000..25550a95f6
--- /dev/null
+++ b/vcl/qa/cppunit/font.cxx
@@ -0,0 +1,219 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+#include <cppunit/TestAssert.h>
+
+#include <vcl/font.hxx>
+
+#include <font/EmphasisMark.hxx>
+
+class VclFontTest : public test::BootstrapFixture
+{
+public:
+ VclFontTest() : BootstrapFixture(true, false) {}
+
+ void testName();
+ void testWeight();
+ void testWidthType();
+ void testPitch();
+ void testItalic();
+ void testAlignment();
+ void testQuality();
+ void testEmphasisMarkShouldBePosAboveWhenSimplifiedChinese();
+ void testEmphasisMarkShouldBePosAboveWhenNotSimplifiedChinese();
+ void testEmphasisMarkInitAsNone();
+ void testEmphasisMarkInitAsDot();
+ void testEmphasisMarkInitAsDisc();
+ void testEmphasisMarkInitAsAccent();
+ void testEmphasisMarkInitAsStyle();
+
+ CPPUNIT_TEST_SUITE(VclFontTest);
+ CPPUNIT_TEST(testName);
+ CPPUNIT_TEST(testWeight);
+ CPPUNIT_TEST(testWidthType);
+ CPPUNIT_TEST(testPitch);
+ CPPUNIT_TEST(testItalic);
+ CPPUNIT_TEST(testAlignment);
+ CPPUNIT_TEST(testQuality);
+ CPPUNIT_TEST(testEmphasisMarkShouldBePosAboveWhenSimplifiedChinese);
+ CPPUNIT_TEST(testEmphasisMarkShouldBePosAboveWhenNotSimplifiedChinese);
+ CPPUNIT_TEST(testEmphasisMarkInitAsNone);
+ CPPUNIT_TEST(testEmphasisMarkInitAsDot);
+ CPPUNIT_TEST(testEmphasisMarkInitAsDisc);
+ CPPUNIT_TEST(testEmphasisMarkInitAsAccent);
+ CPPUNIT_TEST(testEmphasisMarkInitAsStyle);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void VclFontTest::testName()
+{
+ vcl::Font aFont;
+
+ CPPUNIT_ASSERT_MESSAGE( "Family name should be empty", aFont.GetFamilyName().isEmpty());
+ CPPUNIT_ASSERT_MESSAGE( "Style name should be empty", aFont.GetStyleName().isEmpty());
+ aFont.SetFamilyName("Test family name");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Family name should not be empty", OUString("Test family name"), aFont.GetFamilyName());
+ aFont.SetStyleName("Test style name");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Style name should not be empty", OUString("Test style name"), aFont.GetStyleName());
+}
+
+void VclFontTest::testWeight()
+{
+ vcl::Font aFont;
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Weight should be WEIGHT_DONTKNOW", FontWeight::WEIGHT_DONTKNOW, aFont.GetWeight());
+
+ aFont.SetWeight(FontWeight::WEIGHT_BLACK);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Weight should be WEIGHT_BLACK", FontWeight::WEIGHT_BLACK, aFont.GetWeight());
+}
+
+void VclFontTest::testWidthType()
+{
+ vcl::Font aFont;
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Font width should be WIDTH_DONTKNOW", FontWidth::WIDTH_DONTKNOW, aFont.GetWidthType());
+
+ aFont.SetWidthType(FontWidth::WIDTH_EXPANDED);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Font width should be EXPANDED", FontWidth::WIDTH_EXPANDED, aFont.GetWidthType());
+}
+
+void VclFontTest::testItalic()
+{
+ vcl::Font aFont;
+
+ // shouldn't this be set to ITALIC_DONTKNOW? currently it defaults to ITALIC_NONE
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Italic should be ITALIC_NONE", FontItalic::ITALIC_NONE, aFont.GetItalic());
+
+ aFont.SetItalic(FontItalic::ITALIC_NORMAL);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Italic should be EXPANDED", FontItalic::ITALIC_NORMAL, aFont.GetItalic());
+}
+
+
+void VclFontTest::testAlignment()
+{
+ vcl::Font aFont;
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Text alignment should be ALIGN_TOP", TextAlign::ALIGN_TOP, aFont.GetAlignment());
+
+ aFont.SetAlignment(TextAlign::ALIGN_BASELINE);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Text alignment should be ALIGN_BASELINE", TextAlign::ALIGN_BASELINE, aFont.GetAlignment());
+}
+
+
+void VclFontTest::testPitch()
+{
+ vcl::Font aFont;
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Pitch should be PITCH_DONTKNOW", FontPitch::PITCH_DONTKNOW, aFont.GetPitch());
+
+ aFont.SetPitch(FontPitch::PITCH_FIXED);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Pitch should be PITCH_FIXED", FontPitch::PITCH_FIXED, aFont.GetPitch());
+}
+
+void VclFontTest::testQuality()
+{
+ vcl::Font aFont;
+
+ CPPUNIT_ASSERT_EQUAL( int(0), aFont.GetQuality() );
+
+ aFont.SetQuality( 100 );
+ CPPUNIT_ASSERT_EQUAL( int(100), aFont.GetQuality() );
+
+ aFont.IncreaseQualityBy( 50 );
+ CPPUNIT_ASSERT_EQUAL( int(150), aFont.GetQuality() );
+
+ aFont.DecreaseQualityBy( 100 );
+ CPPUNIT_ASSERT_EQUAL( int(50), aFont.GetQuality() );
+}
+
+void VclFontTest::testEmphasisMarkShouldBePosAboveWhenSimplifiedChinese()
+{
+ vcl::Font aFont;
+ aFont.SetLanguage(LANGUAGE_CHINESE_SIMPLIFIED);
+ aFont.SetEmphasisMark(FontEmphasisMark::Accent);
+
+ CPPUNIT_ASSERT_MESSAGE("Emphasis not positioned below", (aFont.GetEmphasisMarkStyle() & FontEmphasisMark::PosBelow));
+ CPPUNIT_ASSERT_MESSAGE("Accent mark not kept", (aFont.GetEmphasisMarkStyle() & FontEmphasisMark::Accent));
+}
+
+void VclFontTest::testEmphasisMarkShouldBePosAboveWhenNotSimplifiedChinese()
+{
+ vcl::Font aFont;
+ aFont.SetLanguage(LANGUAGE_ENGLISH);
+ aFont.SetEmphasisMark(FontEmphasisMark::Accent);
+
+ CPPUNIT_ASSERT_MESSAGE("Emphasis not positioned above", (aFont.GetEmphasisMarkStyle() & FontEmphasisMark::PosAbove));
+ CPPUNIT_ASSERT_MESSAGE("Accent mark not kept", (aFont.GetEmphasisMarkStyle() & FontEmphasisMark::Accent));
+}
+
+void VclFontTest::testEmphasisMarkInitAsNone()
+{
+ vcl::font::EmphasisMark aEmphasisMark(FontEmphasisMark::NONE, 5, 96);
+
+ CPPUNIT_ASSERT_MESSAGE("Shape not a polyline", !aEmphasisMark.IsShapePolyLine());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Shape wrong", tools::PolyPolygon(), aEmphasisMark.GetShape());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect1 not correct", tools::Rectangle(), aEmphasisMark.GetRect1());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect2 not correct", tools::Rectangle(), aEmphasisMark.GetRect2());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("y offset wrong", tools::Long(1), aEmphasisMark.GetYOffset());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("width wrong", tools::Long(0), aEmphasisMark.GetWidth());
+}
+
+void VclFontTest::testEmphasisMarkInitAsDot()
+{
+ vcl::font::EmphasisMark aEmphasisMark(FontEmphasisMark::Dot, 5, 96);
+
+ CPPUNIT_ASSERT_MESSAGE("Shape not a polyline", !aEmphasisMark.IsShapePolyLine());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Shape wrong", tools::PolyPolygon(), aEmphasisMark.GetShape());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect1 not correct", tools::Rectangle(Point(), Size(2, 2)), aEmphasisMark.GetRect1());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect2 not correct", tools::Rectangle(), aEmphasisMark.GetRect2());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("y offset wrong", tools::Long(3), aEmphasisMark.GetYOffset());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("width wrong", tools::Long(2), aEmphasisMark.GetWidth());
+}
+
+void VclFontTest::testEmphasisMarkInitAsDisc()
+{
+ vcl::font::EmphasisMark aEmphasisMark(FontEmphasisMark::Disc, 5, 96);
+
+ CPPUNIT_ASSERT_MESSAGE("Shape not a polyline", !aEmphasisMark.IsShapePolyLine());
+// something wrong with polypolygon equality checking!
+// CPPUNIT_ASSERT_EQUAL_MESSAGE("Shape not disc with radius of 2", tools::PolyPolygon(tools::Polygon(Point(2, 2), 2, 2)), aEmphasisMark.GetShape());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect1 not correct", tools::Rectangle(), aEmphasisMark.GetRect1());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect2 not correct", tools::Rectangle(), aEmphasisMark.GetRect2());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("y offset wrong", tools::Long(4), aEmphasisMark.GetYOffset());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("width wrong", tools::Long(4), aEmphasisMark.GetWidth());
+}
+
+void VclFontTest::testEmphasisMarkInitAsAccent()
+{
+ vcl::font::EmphasisMark aEmphasisMark(FontEmphasisMark::Accent, 5, 96);
+
+ CPPUNIT_ASSERT_MESSAGE("Shape not a polyline", !aEmphasisMark.IsShapePolyLine());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect1 not correct", tools::Rectangle(), aEmphasisMark.GetRect1());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect2 not correct", tools::Rectangle(), aEmphasisMark.GetRect2());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("y offset wrong", tools::Long(4), aEmphasisMark.GetYOffset());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("width wrong", tools::Long(4), aEmphasisMark.GetWidth());
+}
+
+void VclFontTest::testEmphasisMarkInitAsStyle()
+{
+ vcl::font::EmphasisMark aEmphasisMark(FontEmphasisMark::Style, 5, 96);
+
+ CPPUNIT_ASSERT_MESSAGE("Shape not a polyline", !aEmphasisMark.IsShapePolyLine());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect1 not correct", tools::Rectangle(), aEmphasisMark.GetRect1());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect2 not correct", tools::Rectangle(), aEmphasisMark.GetRect2());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("y offset wrong", tools::Long(1), aEmphasisMark.GetYOffset());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("width wrong", tools::Long(0), aEmphasisMark.GetWidth());
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclFontTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/fontcharmap.cxx b/vcl/qa/cppunit/fontcharmap.cxx
new file mode 100644
index 0000000000..26a2fe9a0a
--- /dev/null
+++ b/vcl/qa/cppunit/fontcharmap.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 <test/bootstrapfixture.hxx>
+
+#include <vcl/fontcharmap.hxx>
+
+class VclFontCharMapTest : public test::BootstrapFixture
+{
+public:
+ VclFontCharMapTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ void testDefaultFontCharMap();
+
+ CPPUNIT_TEST_SUITE(VclFontCharMapTest);
+ CPPUNIT_TEST(testDefaultFontCharMap);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void VclFontCharMapTest::testDefaultFontCharMap()
+{
+ FontCharMapRef xfcmap(new FontCharMap()); // gets default map
+
+ CPPUNIT_ASSERT(xfcmap->IsDefaultMap());
+
+ sal_uInt32 nStartBMPPlane = xfcmap->GetFirstChar();
+ sal_uInt32 nStartSupBMPPlane = xfcmap->GetNextChar(0xD800);
+ sal_uInt32 nEndBMPPlane = xfcmap->GetLastChar();
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(0x0020), nStartBMPPlane);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(0xE000), nStartSupBMPPlane);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(0xFFF0 - 1), nEndBMPPlane);
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclFontCharMapTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/fontmetric.cxx b/vcl/qa/cppunit/fontmetric.cxx
new file mode 100644
index 0000000000..bf2585ac38
--- /dev/null
+++ b/vcl/qa/cppunit/fontmetric.cxx
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+#include <cppunit/TestAssert.h>
+
+#include <vcl/metric.hxx>
+
+class VclFontMetricTest : public test::BootstrapFixture
+{
+public:
+ VclFontMetricTest() : BootstrapFixture(true, false) {}
+
+ void testFullstopCenteredFlag();
+ void testSpacings();
+ void testSlant();
+ void testBulletOffset();
+ void testEqualityOperator();
+
+ CPPUNIT_TEST_SUITE(VclFontMetricTest);
+ CPPUNIT_TEST(testFullstopCenteredFlag);
+ CPPUNIT_TEST(testSpacings);
+ CPPUNIT_TEST(testSlant);
+ CPPUNIT_TEST(testBulletOffset);
+ CPPUNIT_TEST(testEqualityOperator);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void VclFontMetricTest::testFullstopCenteredFlag()
+{
+ // default constructor should set scalable flag to false
+ FontMetric aFontMetric;
+
+ CPPUNIT_ASSERT_MESSAGE( "Fullstop centered flag should be false after default constructor called", !aFontMetric.IsFullstopCentered() );
+
+ aFontMetric.SetFullstopCenteredFlag(true);
+
+ CPPUNIT_ASSERT_MESSAGE( "Fullstop centered flag should be true", aFontMetric.IsFullstopCentered() );
+}
+
+void VclFontMetricTest::testSpacings()
+{
+ // default constructor should set scalable flag to false
+ FontMetric aFontMetric;
+
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aFontMetric.GetAscent());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aFontMetric.GetDescent());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aFontMetric.GetExternalLeading());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aFontMetric.GetInternalLeading());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aFontMetric.GetLineHeight());
+
+
+ aFontMetric.SetAscent( 100 );
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(100), aFontMetric.GetAscent());
+
+ aFontMetric.SetDescent( 100 );
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(100), aFontMetric.GetDescent());
+
+ aFontMetric.SetExternalLeading( 100 );
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(100), aFontMetric.GetExternalLeading());
+
+ aFontMetric.SetInternalLeading( 100 );
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(100), aFontMetric.GetInternalLeading());
+
+ aFontMetric.SetLineHeight( 100 );
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(100), aFontMetric.GetLineHeight());
+}
+
+void VclFontMetricTest::testSlant()
+{
+ // default constructor should set scalable flag to false
+ FontMetric aFontMetric;
+
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aFontMetric.GetSlant());
+
+ aFontMetric.SetSlant( 45 );
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(45), aFontMetric.GetSlant());
+}
+
+void VclFontMetricTest::testBulletOffset()
+{
+ // default constructor should set scalable flag to false
+ FontMetric aFontMetric;
+
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aFontMetric.GetBulletOffset());
+
+ aFontMetric.SetBulletOffset( 45 );
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(45), aFontMetric.GetBulletOffset());
+}
+
+void VclFontMetricTest::testEqualityOperator()
+{
+ // default constructor should set scalable flag to false
+ FontMetric aLhs, aRhs;
+
+ aLhs.SetFullstopCenteredFlag(true);
+ aRhs.SetFullstopCenteredFlag(true);
+ CPPUNIT_ASSERT_MESSAGE( "Fullstop centered flag set same, aLhs == aRhs failed", aLhs.operator ==(aRhs) );
+ CPPUNIT_ASSERT_MESSAGE( "Fullstop centered flag set same, aLhs != aRhs succeeded", !aLhs.operator !=(aRhs) );
+
+ aLhs.SetExternalLeading(10);
+ aRhs.SetExternalLeading(10);
+ CPPUNIT_ASSERT_MESSAGE( "External leading set same, aLHS == aRhs failed", aLhs.operator ==(aRhs) );
+ CPPUNIT_ASSERT_MESSAGE( "External leading set same, aLHS != aRhs succeeded", !aLhs.operator !=(aRhs) );
+
+ aLhs.SetInternalLeading(10);
+ aRhs.SetInternalLeading(10);
+ CPPUNIT_ASSERT_MESSAGE( "Internal leading set same, aLHS == aRhs failed", aLhs.operator ==(aRhs) );
+ CPPUNIT_ASSERT_MESSAGE( "Internal leading set same, aLHS != aRhs succeeded", !aLhs.operator !=(aRhs) );
+
+ aLhs.SetAscent( 100 );
+ aRhs.SetAscent( 100 );
+ CPPUNIT_ASSERT_MESSAGE( "Ascent set same, aLHS == aRhs failed", aLhs.operator ==(aRhs) );
+ CPPUNIT_ASSERT_MESSAGE( "Ascent set same, aLHS != aRhs succeeded", !aLhs.operator !=(aRhs) );
+
+ aLhs.SetDescent( 100 );
+ aRhs.SetDescent( 100 );
+ CPPUNIT_ASSERT_MESSAGE( "Descent set same, aLHS == aRhs failed", aLhs.operator ==(aRhs));
+ CPPUNIT_ASSERT_MESSAGE( "Descent set same, aLHS != aRhs succeeded", !aLhs.operator !=(aRhs) );
+
+ aLhs.SetSlant( 100 );
+ aRhs.SetSlant( 100 );
+ CPPUNIT_ASSERT_MESSAGE( "Slant set same, aLHS == aRhs failed", aLhs.operator ==(aRhs));
+ CPPUNIT_ASSERT_MESSAGE( "Slant set same, aLHS != aRhs succeeded", !aLhs.operator !=(aRhs) );
+}
+
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclFontMetricTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/fontmocks.hxx b/vcl/qa/cppunit/fontmocks.hxx
new file mode 100644
index 0000000000..8eac463c14
--- /dev/null
+++ b/vcl/qa/cppunit/fontmocks.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/.
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <font/FontSelectPattern.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <font/PhysicalFontFaceCollection.hxx>
+#include <fontattributes.hxx>
+#include <font/LogicalFontInstance.hxx>
+
+class TestFontInstance : public LogicalFontInstance
+{
+public:
+ TestFontInstance(vcl::font::PhysicalFontFace const& rFontFace,
+ vcl::font::FontSelectPattern const& rFontSelectPattern)
+ : LogicalFontInstance(rFontFace, rFontSelectPattern)
+ {
+ }
+
+ bool GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const override
+ {
+ return true;
+ }
+};
+
+class TestFontFace : public vcl::font::PhysicalFontFace
+{
+public:
+ TestFontFace(sal_uIntPtr nId)
+ : vcl::font::PhysicalFontFace(FontAttributes())
+ , mnFontId(nId)
+ {
+ }
+
+ TestFontFace(FontAttributes const& rFontAttributes, sal_uIntPtr nId)
+ : vcl::font::PhysicalFontFace(rFontAttributes)
+ , mnFontId(nId)
+ {
+ }
+
+ rtl::Reference<LogicalFontInstance>
+ CreateFontInstance(vcl::font::FontSelectPattern const& rFontSelectPattern) const override
+ {
+ return new TestFontInstance(*this, rFontSelectPattern);
+ }
+
+ sal_IntPtr GetFontId() const override { return mnFontId; }
+ FontCharMapRef GetFontCharMap() const override { return FontCharMap::GetDefaultMap(false); }
+ bool GetFontCapabilities(vcl::FontCapabilities&) const override { return true; }
+
+private:
+ sal_IntPtr mnFontId;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/qa/cppunit/gen/data/tdf121120.png b/vcl/qa/cppunit/gen/data/tdf121120.png
new file mode 100644
index 0000000000..8e48fba385
--- /dev/null
+++ b/vcl/qa/cppunit/gen/data/tdf121120.png
Binary files differ
diff --git a/vcl/qa/cppunit/gen/gen.cxx b/vcl/qa/cppunit/gen/gen.cxx
new file mode 100644
index 0000000000..56a9d5b6ea
--- /dev/null
+++ b/vcl/qa/cppunit/gen/gen.cxx
@@ -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/.
+ */
+
+#include <test/unoapi_test.hxx>
+
+#include <sfx2/objsh.hxx>
+#include <sfx2/sfxbasemodel.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/virdev.hxx>
+
+using namespace com::sun::star;
+
+/// This test uses the gen backend (i.e. intentionally not the svp one, which is the default.)
+class GenTest : public UnoApiTest
+{
+public:
+ GenTest()
+ : UnoApiTest("/vcl/qa/cppunit/gen/data/")
+ {
+ }
+
+ Bitmap load(const char* pName)
+ {
+ loadFromFile(OUString::createFromAscii(pName));
+ SfxBaseModel* pModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
+ CPPUNIT_ASSERT(pModel);
+ SfxObjectShell* pShell = pModel->GetObjectShell();
+ std::shared_ptr<GDIMetaFile> xMetaFile = pShell->GetPreviewMetaFile();
+ BitmapEx aResultBitmap;
+ CPPUNIT_ASSERT(xMetaFile->CreateThumbnail(aResultBitmap));
+ return aResultBitmap.GetBitmap();
+ }
+};
+
+CPPUNIT_TEST_FIXTURE(GenTest, testTdf121120)
+{
+ Bitmap aBitmap = load("tdf121120.png");
+ BitmapScopedReadAccess pAccess(aBitmap);
+ const Size& rSize = aBitmap.GetSizePixel();
+ Color aColor(pAccess->GetPixel(rSize.getHeight() / 2, rSize.getWidth() / 2));
+ // Without the accompanying fix in place, this test would have failed with 'Expected: 255;
+ // Actual : 1'. I.e. center of the preview (which has the background color) was ~black, not
+ // white.
+ CPPUNIT_ASSERT_EQUAL(0xff, int(aColor.GetRed()));
+ CPPUNIT_ASSERT_EQUAL(0xff, int(aColor.GetBlue()));
+ CPPUNIT_ASSERT_EQUAL(0xff, int(aColor.GetGreen()));
+}
+
+/// Test that drawing a line preview to a bitmap is not lost.
+CPPUNIT_TEST_FIXTURE(GenTest, testTdf107966)
+{
+ // Set up the virtual device: white background.
+ ScopedVclPtr<VirtualDevice> pVirtualDevice(VclPtr<VirtualDevice>::Create());
+ pVirtualDevice->SetLineColor();
+ MapMode aMapMode;
+ aMapMode.SetMapUnit(MapUnit::MapTwip);
+ pVirtualDevice->SetMapMode(aMapMode);
+ pVirtualDevice->SetOutputSizePixel(Size(90, 15));
+ pVirtualDevice->SetFillColor(Color(255, 255, 255));
+ pVirtualDevice->DrawRect(tools::Rectangle(Point(), Size(1350, 225)));
+ pVirtualDevice->SetFillColor(Color(0, 0, 0));
+ AntialiasingFlags nOldAA = pVirtualDevice->GetAntialiasing();
+ pVirtualDevice->SetAntialiasing(nOldAA & ~AntialiasingFlags::Enable);
+
+ // Paint a black polygon on it.
+ basegfx::B2DPolygon aPolygon;
+ aPolygon.append(basegfx::B2DPoint(0, 15));
+ aPolygon.append(basegfx::B2DPoint(1350, 15));
+ aPolygon.append(basegfx::B2DPoint(1350, 0));
+ aPolygon.append(basegfx::B2DPoint(0, 0));
+ pVirtualDevice->DrawPolygon(aPolygon);
+
+ // Make sure that the polygon is visible.
+ Bitmap aBitmap = pVirtualDevice->GetBitmap(Point(), Size(1350, 15));
+ BitmapScopedReadAccess pAccess(aBitmap);
+ Color aPixel(pAccess->GetPixel(0, 0));
+ // Without the accompanying fix in place, this test would have failed with 'Expected: 000000;
+ // Actual: ffffff', i.e. the top left pixel was white, not black.
+ CPPUNIT_ASSERT_EQUAL(OUString("000000"), aPixel.AsRGBHexString());
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/gradient.cxx b/vcl/qa/cppunit/gradient.cxx
new file mode 100644
index 0000000000..4af26d0be5
--- /dev/null
+++ b/vcl/qa/cppunit/gradient.cxx
@@ -0,0 +1,256 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+#include <test/outputdevice.hxx>
+
+#include <sal/log.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/vector/b2enums.hxx>
+
+#include <vcl/gradient.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/print.hxx>
+#include <vcl/rendercontext/RasterOp.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bufferdevice.hxx>
+#include <window.h>
+
+class VclGradientTest : public test::BootstrapFixture
+{
+public:
+ VclGradientTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ void testAddGradientActions_rect_linear();
+ void testAddGradientActions_rect_axial();
+ void testAddGradientActions_rect_complex();
+
+ CPPUNIT_TEST_SUITE(VclGradientTest);
+ CPPUNIT_TEST(testAddGradientActions_rect_linear);
+ CPPUNIT_TEST(testAddGradientActions_rect_axial);
+ CPPUNIT_TEST(testAddGradientActions_rect_complex);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+static size_t TestLinearStripes(GDIMetaFile& rMtf, size_t nTimes, size_t nIndex)
+{
+ nIndex++;
+ MetaAction* pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action (start)", MetaActionType::FILLCOLOR,
+ pAction->GetType());
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action (start)", MetaActionType::POLYGON,
+ pAction->GetType());
+
+ for (size_t i = 0; i < nTimes - 1; i++)
+ {
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action", MetaActionType::FILLCOLOR,
+ pAction->GetType());
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action", MetaActionType::POLYGON,
+ pAction->GetType());
+ }
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action (end)", MetaActionType::FILLCOLOR,
+ pAction->GetType());
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action (end)", MetaActionType::POLYGON,
+ pAction->GetType());
+
+ return nIndex;
+}
+
+void VclGradientTest::testAddGradientActions_rect_linear()
+{
+ GDIMetaFile aMtf;
+ tools::Rectangle aRect(Point(10, 10), Size(40, 40));
+ Gradient aGradient(css::awt::GradientStyle_LINEAR, COL_RED, COL_WHITE);
+ aGradient.SetBorder(100);
+
+ aGradient.AddGradientActions(aRect, aMtf);
+
+ size_t nIndex = 0;
+
+ MetaAction* pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a push action", MetaActionType::PUSH, pAction->GetType());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a rectangular intersect clip action",
+ MetaActionType::ISECTRECTCLIPREGION, pAction->GetType());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line color action", MetaActionType::LINECOLOR,
+ pAction->GetType());
+
+ TestLinearStripes(aMtf, 3, nIndex);
+}
+
+static size_t TestAxialStripes(GDIMetaFile& rMtf, size_t nTimes, size_t nIndex)
+{
+ nIndex++;
+ MetaAction* pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action", MetaActionType::FILLCOLOR,
+ pAction->GetType());
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action", MetaActionType::POLYGON,
+ pAction->GetType());
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action", MetaActionType::POLYGON,
+ pAction->GetType());
+
+ for (size_t i = 0; i < nTimes - 1; i++)
+ {
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action", MetaActionType::FILLCOLOR,
+ pAction->GetType());
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action", MetaActionType::POLYGON,
+ pAction->GetType());
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action", MetaActionType::POLYGON,
+ pAction->GetType());
+ }
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action", MetaActionType::FILLCOLOR,
+ pAction->GetType());
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action", MetaActionType::POLYGON,
+ pAction->GetType());
+
+ return nIndex;
+}
+
+void VclGradientTest::testAddGradientActions_rect_axial()
+{
+ GDIMetaFile aMtf;
+ tools::Rectangle aRect(Point(10, 10), Size(40, 40));
+ Gradient aGradient(css::awt::GradientStyle_AXIAL, COL_RED, COL_WHITE);
+ aGradient.SetBorder(100);
+
+ aGradient.AddGradientActions(aRect, aMtf);
+
+ size_t nIndex = 0;
+
+ MetaAction* pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a push action", MetaActionType::PUSH, pAction->GetType());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a rectangular intersect clip action",
+ MetaActionType::ISECTRECTCLIPREGION, pAction->GetType());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line color action", MetaActionType::LINECOLOR,
+ pAction->GetType());
+
+ TestAxialStripes(aMtf, 3, nIndex);
+}
+
+static size_t TestComplexStripes(GDIMetaFile& rMtf, size_t nTimes, size_t nIndex)
+{
+ nIndex++;
+ MetaAction* pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action", MetaActionType::FILLCOLOR,
+ pAction->GetType());
+
+ for (size_t i = 1; i < nTimes; i++)
+ {
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polypolygon action", MetaActionType::POLYPOLYGON,
+ pAction->GetType());
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action", MetaActionType::FILLCOLOR,
+ pAction->GetType());
+ }
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action", MetaActionType::FILLCOLOR,
+ pAction->GetType());
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polypolygon action", MetaActionType::POLYGON,
+ pAction->GetType());
+
+ return nIndex;
+}
+
+void VclGradientTest::testAddGradientActions_rect_complex()
+{
+ GDIMetaFile aMtf;
+ tools::Rectangle aRect(Point(10, 10), Size(40, 40));
+ Gradient aGradient(css::awt::GradientStyle_SQUARE, COL_RED, COL_WHITE);
+ aGradient.SetBorder(10);
+
+ aGradient.AddGradientActions(aRect, aMtf);
+
+ size_t nIndex = 0;
+
+ MetaAction* pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a push action", MetaActionType::PUSH, pAction->GetType());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a rectangular intersect clip action",
+ MetaActionType::ISECTRECTCLIPREGION, pAction->GetType());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line color action", MetaActionType::LINECOLOR,
+ pAction->GetType());
+
+ TestComplexStripes(aMtf, 40, nIndex);
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclGradientTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/graphicfilter/data/README b/vcl/qa/cppunit/graphicfilter/data/README
new file mode 100644
index 0000000000..0f660b9061
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/README
@@ -0,0 +1,16 @@
+Files with the string `BID`, `CVE`, `EDB` and `RC4` in their name
+are encrypted to avoid problems with virus checkers on source code download.
+
+# Installation on Debian/Ubuntu
+
+ $ sudo apt install mcrypt
+
+# Usage
+
+To get access to the plain files for manual testing (unencrypt):
+
+ $ mdecrypt --bare -a arcfour -o hex -k 435645 -s 3 CVE-2015-0848-1.wmf
+
+To create new tests (encrypt):
+
+ $ mcrypt --bare -a arcfour -o hex -k 435645 -s 3 foo.doc
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2004-0691-1.bmp b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2004-0691-1.bmp
new file mode 100644
index 0000000000..d77db57829
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2004-0691-1.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2006-0006-1.bmp b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2006-0006-1.bmp
new file mode 100644
index 0000000000..4cfbdfff83
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2006-0006-1.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2007-2244-1.bmp b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2007-2244-1.bmp
new file mode 100644
index 0000000000..289cf8c0e9
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2007-2244-1.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2007-3741-1.bmp b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2007-3741-1.bmp
new file mode 100644
index 0000000000..84ac054db5
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2007-3741-1.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2007-3741-2.bmp b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2007-3741-2.bmp
new file mode 100644
index 0000000000..a6aed5983d
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2007-3741-2.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2008-1097-1.bmp b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2008-1097-1.bmp
new file mode 100644
index 0000000000..76aaecf97e
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2008-1097-1.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2008-5870-1.bmp b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2008-5870-1.bmp
new file mode 100644
index 0000000000..d223dde288
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2008-5870-1.bmp
@@ -0,0 +1 @@
+ î¬.Gx©ŠKØ'seë2Ï~°Œè.G1Ì-”è‚#á›ø1†Y!ðÜÊ¢/ÙDVñ \ No newline at end of file
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2016-10504-1.bmp b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2016-10504-1.bmp
new file mode 100644
index 0000000000..b733b24845
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/CVE-2016-10504-1.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/fail/EDB-24743-1.bmp b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/EDB-24743-1.bmp
new file mode 100644
index 0000000000..2b58d1035c
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/EDB-24743-1.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/fail/EDB-24743-4.bmp b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/EDB-24743-4.bmp
new file mode 100644
index 0000000000..cfe7e40f67
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/EDB-24743-4.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/fail/afl-sample-bad-rle-1.bmp b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/afl-sample-bad-rle-1.bmp
new file mode 100644
index 0000000000..1ca6e0008b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/afl-sample-bad-rle-1.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/fail/nodict-compress.bmp b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/nodict-compress.bmp
new file mode 100644
index 0000000000..a75d6ebae5
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/fail/nodict-compress.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/bmp/indeterminate/.gitignore
new file mode 100644
index 0000000000..583b009c7c
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/indeterminate/.gitignore
@@ -0,0 +1 @@
+*.wmf-*
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/bmp/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/pass/CVE-2014-1947-1.bmp b/vcl/qa/cppunit/graphicfilter/data/bmp/pass/CVE-2014-1947-1.bmp
new file mode 100644
index 0000000000..3815a1c13c
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/pass/CVE-2014-1947-1.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/pass/EDB-22680-1.bmp b/vcl/qa/cppunit/graphicfilter/data/bmp/pass/EDB-22680-1.bmp
new file mode 100644
index 0000000000..88b11ad578
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/pass/EDB-22680-1.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/bmp/pass/crash-1.bmp b/vcl/qa/cppunit/graphicfilter/data/bmp/pass/crash-1.bmp
new file mode 100644
index 0000000000..84b6c35c87
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/bmp/pass/crash-1.bmp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/dxf/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/dxf/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/dxf/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/dxf/fail/CVE-2010-1681-1.dxf b/vcl/qa/cppunit/graphicfilter/data/dxf/fail/CVE-2010-1681-1.dxf
new file mode 100644
index 0000000000..b4629d9dc1
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/dxf/fail/CVE-2010-1681-1.dxf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/dxf/fail/hang-1.dxf b/vcl/qa/cppunit/graphicfilter/data/dxf/fail/hang-1.dxf
new file mode 100644
index 0000000000..d97edbb29f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/dxf/fail/hang-1.dxf
@@ -0,0 +1 @@
+99 \ No newline at end of file
diff --git a/vcl/qa/cppunit/graphicfilter/data/dxf/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/dxf/indeterminate/.gitignore
new file mode 100644
index 0000000000..98457bc105
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/dxf/indeterminate/.gitignore
@@ -0,0 +1 @@
+*.dxf-*
diff --git a/vcl/qa/cppunit/graphicfilter/data/dxf/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/dxf/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/dxf/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/dxf/pass/bigangle-1.dxf b/vcl/qa/cppunit/graphicfilter/data/dxf/pass/bigangle-1.dxf
new file mode 100644
index 0000000000..3c47414475
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/dxf/pass/bigangle-1.dxf
@@ -0,0 +1,143 @@
+99
+170141183460469231731687303715884105718
+ 2
+BL0C
+ 0
+SECTION
+ 2
+ENTITIES
+ 0111 N9.0
+0
+ARC
+111 N9
+ 1
+I
+ 39
+9
+130
+
+ 51
+1758770265ded1a13ecce6855794837535bf766a7fRC
+111 N9.0007
+ 39
+9
+130
+10
+ 2
+BL0C
+ 0
+SECTION
+ 2
+ENTITIES
+ 0111 N9.0
+0
+ARC
+111 N9
+ 1
+I
+ 39
+9
+130
+
+ 51
+9705
+0
+ARC
+111 39
+9
+130
+
+ 51
+183460469231731687303715884105718
+ 2
+BL0C
+ 0
+SECTION
+ 2
+ENTITIES
+ 0111 N9.0
+0
+ARC
+111 N9
+ 1
+I
+ 39
+9
+130
+
+ 51
+1758770265ded1a13ecce6855794837535bf766a7fRC
+111 N9.0007
+ 39
+9
+130
+10
+ 2
+BL0C
+ 0
+SECTION
+ 2
+ENTITIES
+ 0111 N9.0
+0
+ARC
+111 N9
+ 1
+I
+ 39
+9
+130
+
+ 51
+9705
+0
+ARC
+111 39
+9
+130
+
+ 51
+197
+0
+ARC
+111 N9.00÷dL07
+ 39
+9
+130
+
+ 56
+15
+ 8
+
+0
+EOF
+111 N9.0
+ 56*
+15
+ 8
+
+0
+EO?
+111 197
+0
+ARC
+111 N9.00÷dL07
+ 39
+9
+130
+
+ 56
+15
+ 8
+
+0
+EOF
+111 N9.0
+ 56*
+15
+ 8
+
+0
+EO?
+111 N9.000
+3 N 2S 79 799
diff --git a/vcl/qa/cppunit/graphicfilter/data/dxf/pass/loop-1.dxf b/vcl/qa/cppunit/graphicfilter/data/dxf/pass/loop-1.dxf
new file mode 100644
index 0000000000..e3277e69b8
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/dxf/pass/loop-1.dxf
@@ -0,0 +1,17320 @@
+ 0
+SECTION
+ 2
+HEADER
+ 9
+$ACADVER
+ 1
+AC1015
+ 9
+$ACADMAINTVER
+ 70
+ 6
+ 9
+$DWGCODEPAGE
+ 3
+ANSI_1252
+ 9
+$INSBASE
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$EXTMIN
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$EXTMAX
+ 10
+297.0
+ 20
+420.0
+ 30
+0.0
+ 9
+$LIMMIN
+ 10
+0.0
+ 20
+0.0
+ 9
+$LIMMAX
+ 10
+297.0
+ 20
+420.0
+ 9
+$ORTHOMODE
+ 70
+ 0
+ 9
+$REGENMODE
+ 70
+ 1
+ 9
+$FILL«ODE
+ 70
+ 1
+ 9
+$QTEXTMODE
+ 70
+ 0
+ 9
+$MIRRTEXT
+ 70
+ 1
+ 9
+$LTSCALE
+ 40
+1.0
+ 9
+$ATTMODE
+ 70
+ 1
+ 9
+$TEXTSIZE
+ 40
+3.5
+ 9
+$TRACEWID
+ 40
+1.0
+ 9
+$TEXTSTYLE
+ 7
+Standard
+ 9
+$CLAYER
+ 8
+0
+ 9
+$CELTYPE
+ 6
+ByLayer
+ 9
+$CECOLOR
+ 62
+ 256
+ 9
+$CELTSCALE
+ 40
+1.0
+ 9
+$DISPSILH
+ 70
+ 0
+ 9
+$DIMSCALE
+ 40
+1.0
+ 9
+$DIMASZ
+ 40
+3.0
+ 9
+$DIMEXO
+ 40
+1.0
+ 9
+$DIMDLI
+ 40
+3.75
+ 9
+$DIMRND
+ 40
+0.0
+ 9
+$DIMDLE
+ 40
+0.0
+ 9
+$DIMEXE
+ 40
+1.0
+ 9
+$DIMTP
+ 40
+0.0
+ 9
+$DIMTM
+ 40
+0.0
+ 9
+$DIMTXT
+ 40
+3.5
+ 9
+$DIMCEN
+ 40
+2.5
+ 9
+$DIMTSZ
+ 40
+0.0
+ 9
+$DIMTOL
+ 70
+ 0
+ 9
+$DIMLIM
+ 70
+ 0
+ 9
+$DIMTIH
+ 70
+ 1
+ 9
+$DIMTOH
+ 70
+ 1
+ 9
+$DIMSE1
+ 70
+ 0
+ 9
+$DIMSE2
+ 70
+ 0
+ 9
+$DIMTAD
+ 70
+ 1
+ 9
+$DIMZIN
+ 70
+ 0
+ 9
+$DIMBLK
+ 1
+
+ 9
+$DIMASO
+ 70
+ 1
+ 9
+$DIMSHO
+ 70
+ 1
+ 9
+$DIMPOST
+ 1
+
+ 9
+$DIMAPOST
+ 1
+
+ 9
+$DIMALT
+ 70
+ 0
+ 9
+$DIMALTD
+ 70
+ 3
+ 9
+$DIMALTF
+ 40
+0.0393700787
+ 9
+$DIMLFAC
+ 40
+100.0
+ 9
+$DIMTOFL
+ 70
+ 1
+ 9
+$DIMTVP
+ 40
+0.0
+ 9
+$DIMTIX
+ 70
+ 0
+ 9
+$DIMSOXD
+ 70
+ 0
+ 9
+$DIMSAH
+ 70
+ 0
+ 9
+$DIMBLK1
+ 1
+
+ 9
+$DIMBLK2
+ 1
+
+ 9
+$DIMSTYLE
+ 2
+ISO-25
+ 9
+$DIMCLRD
+ 70
+ 0
+ 9
+$DIMCLRE
+ 70
+ 0
+ 9
+$DIMCLRT
+ 70
+ 0
+ 9
+$DIMTFAC
+ 40
+1.0
+ 9
+$DIMGAP
+ 40
+1.524
+ 9
+$DIMJUST
+ 70
+ 0
+ 9
+$DIMSD1
+ 70
+ 0
+ 9
+$DIMSD2
+ 70
+ 0
+ 9
+$DIMTOLJ
+ 70
+ 0
+ 9
+$DIMTZIN
+ 70
+ 0
+ 9
+$DIMALTZ
+ 70
+ 0
+ 9
+$DIMALTTZ
+ 70
+ 0
+ 9
+$DIMUPT
+ 70
+ 0
+ 9
+$DIMDEC
+ 70
+ 3
+ 9
+$DIMTDEC
+ 70
+ 3
+ 9
+$DIMALTU
+ 70
+ 2
+ 9
+$DIMALTTD
+ 70
+ 3
+ 9
+$DIMTXSTY
+ 7
+Standard
+ 9
+$DIMAUNIT
+ 70
+ 0
+ 9
+$DIMADEC
+ 70
+ 2
+ 9
+$DIMALTRND
+ 40
+0.0
+ 9
+$DIMAZIN
+ 70
+ 0
+ 9
+$DIMDSEP
+ 70
+ 44
+ 9
+$DIMATFIT
+ 70
+ 3
+ 9
+$DIMFRAC
+ 70
+ 0
+ 9
+$DIMLDRBLK
+ 1
+
+ 9
+$DIMLUNIT
+ 70
+ 2
+ 9
+$DIMLWD
+ 70
+ -2
+ 9
+$DIMLWE
+ 70
+ -2
+ 9
+$DIMTMOVE
+ 70
+ 0
+ 9
+$LUNITS
+ 70
+ 2
+ 9
+$LUPREC
+ 70
+ 2
+ 9
+$SKETCHINC
+ 40
+1.0
+ 9
+$FILLETRAD
+ 40
+10.0
+ 9
+$AUNITS
+ 70
+ 0
+ 9
+$AUPREC
+ 70
+ 2
+ 9
+$MENU
+ 1
+.
+ 9
+$ELEVATION
+ 40
+0.0
+ 9
+$PELEVATION
+ 40
+0.0
+ 9
+$THICKNESS
+ 40
+0.0
+ 9
+$LIMCHECK
+ 70
+ 0
+ 9
+$CHAMFERA
+ 40
+10.0
+ 9
+$CHAMFERB
+ 40
+10.0
+ 9
+$CHAMFERC
+ 40
+20.0
+ 9
+$CHAMFERD
+ 40
+0.0
+ 9
+$SKPOLY
+ 70
+ 0
+ 9
+$TDCREATE
+ 40
+2456962.662635255
+ 9
+$TDUCREATE
+ 40
+2456962.620968588
+ 9
+$TDUPDATE
+ 40
+2456962.662636401
+ 9
+$TDUUPDATE
+ 40
+2456962.620969734
+ 9
+$TDINDWG
+ 40
+0.0000000116
+ 9
+$TDUSRTIMER
+ 40
+0.0000000116
+ 9
+$USRTIMER
+ 70
+ 1
+ 9
+$ANGBASE
+ 50
+0.0
+ 9
+$ANGDIR
+ 70
+ 0
+ 9
+$PDMODE
+ 70
+ 0
+ 9
+$PDSIZE
+ 40
+-1.0
+ 9
+$PLINEWID
+ 40
+0.0
+ 9
+$SPLFRAME
+ 70
+ 0
+ 9
+$SPLINETYPE
+ 70
+ 6
+ 9
+$SPLINESEGS
+ 70
+ 8
+ 9
+$HANDSEED
+ 5
+5C0
+ 9
+$SURFTAB1
+ 70
+ 6
+ 9
+$SURFTAB2
+ 70
+ 6
+ 9
+$SURFTYPE
+ 70
+ 6
+ 9
+$SURFU
+ 70
+ 6
+ 9
+$SURFV
+ 70
+ 6
+ 9
+$UCSBASE
+ 2
+
+ 9
+$UCSNAME
+ 2
+
+ 9
+$UCSORG
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$UCSXDIR
+ 10
+1.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$UCSYDIR
+ 10
+0.0
+ 20
+1.0
+ 30
+0.0
+ 9
+$UCSORTHOREF
+ 2
+
+ 9
+$UCSORTHOVIEW
+ 70
+ 0
+ 9
+$UCSORGTOP
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$UCSORGBOTTOM
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$UCSORGLEFT
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$UCSORGRIGHT
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$UCSORGFRONT
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$UCSORGBACK
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$PUCSBASE
+ 2
+
+ 9
+$PUCSNAME
+ 2
+
+ 9
+$PUCSORG
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$PUCSXDIR
+ 10
+1.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$PUCSYDIR
+ 10
+0.0
+ 20
+1.0
+ 30
+0.0
+ 9
+$PUCSORTHOREF
+ 2
+
+ 9
+$PUCSORTHOVIEW
+ 70
+ 0
+ 9
+$PUCSORGTOP
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$PUCSORGBOTTOM
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$PUCSORGLEFT
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$PUCSORGRIGHT
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$PUCSORGFRONT
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$PUCSORGBACK
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$USERI1
+ 70
+ 0
+ 9
+$USERI2
+ 70
+ 0
+ 9
+$USERI3
+ 70
+ 0
+ 9
+$USERI4
+ 70
+ 0
+ 9
+$USERI5
+ 70
+ 0
+ 9
+$USERR1
+ 40
+0.0
+ 9
+$USERR2
+ 40
+0.0
+ 9
+$USERR3
+ 40
+0.0
+ 9
+$USERR4
+ 40
+0.0
+ 9
+$USERR5
+ 40
+0.0
+ 9
+$WORLDVIEW
+ 70
+ 1
+ 9
+$SHADEDGE
+ 70
+ 3
+ 9
+$SHADEDIF
+ 70
+ 70
+ 9
+$TILEMODE
+ 70
+ 1
+ 9
+$MAXACTVP
+ 70
+ 64
+ 9
+$PINSBASE
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$PLIMCHECK
+ 70
+ 0
+ 9
+$PEXTMIN
+ 10
+1.0000000000E+20
+ 20
+1.0000000000E+20
+ 30
+1.0000000000E+20
+ 9
+$PEXTMAX
+ 10
+-1.0000000000E+20
+ 20
+-1.0000000000E+20
+ 30
+-1.0000000000E+20
+ 9
+$PLIMMIN
+ 10
+0.0
+ 20
+0.0
+ 9
+$PLIMMAX
+ 10
+420.0
+ 20
+297.0
+ 9
+$UNITMODE
+ 70
+ 0
+ 9
+$VISRETAIN
+ 70
+ 1
+ 9
+$PLINEGEN
+ 70
+ 0
+ 9
+$PSLTSCALE
+ 70
+ 1
+ 9
+$TREEDEPTH
+ 70
+ 3020
+ 9
+$CMLSTYLE
+ 2
+Standard
+ 9
+$CMLJUST
+ 70
+ 0
+ 9
+$CMLSCALE
+ 40
+20.0
+ 9
+$PROXYGRAPHICS
+ 70
+ 1
+ 9
+$MEASUREMENT
+ 70
+ 1
+ 9
+$CELWEIGHT
+370
+ -1
+ 9
+$ENDCAPS
+280
+ 0
+ 9
+$JOINSTYLE
+280
+ 0
+ 9
+$LWDISPLAY
+290
+ 1
+ 9
+$INSUNITS
+ 70
+ 4
+ 9
+$HYPERLINKBASE
+ 1
+
+ 9
+$STYLESHEET
+ 1
+
+ 9
+$XEDIT
+290
+ 1
+ 9
+$CEPSNTYPE
+380
+ 0
+ 9
+$PSTYLEMODE
+290
+ 1
+ 9
+$FINGERPRINTGUID
+ 2
+{12852F08-F252-4439-8DDF-34F6DEA5435B}
+ 9
+$VERSIONGUID
+ 2
+{FAEB1C32-E019-11D5-929B-00C0DF256EC4}
+ 9
+$EXTNAMES
+290
+ 1
+ 9
+$PSVPSCALE
+ 40
+0.0
+ 9
+$OLESTARTUP
+290
+ 0
+ 0
+ENDSEC
+ 0
+SECTION
+ 2
+CLASSES
+ 0
+CLASS
+ 1
+ACDBDICTIONARYWDFLT
+ 2
+AcDbDictionaryWithDefault
+ 3
+ObjectDBX Classes
+ 90
+ 0
+280
+ 0
+281
+ 0
+ 0
+CLASS
+ 1
+VISUALSTYLE
+ 2
+AcDbVisualStyle
+ 3
+ObjectDBX Classes
+ 90
+ 4095
+280
+ 0
+281
+ 0
+ 0
+CLASS
+ 1
+MATERIAL
+ 2
+AcDbMaterial
+ 3
+ObjectDBX Classes
+ 90
+ 1153
+280
+ 0
+281
+ 0
+ 0
+CLASS
+ 1
+ACDBPLACEHOLDER
+ 2
+AcDbPlaceHolder
+ 3
+ObjectDBX Classes
+ 90
+ 0
+280
+ 0
+281
+ 0
+ 0
+CLASS
+ 1
+LAYOUT
+ 2
+AcDbLayout
+ 3
+ObjectDBX Classes
+ 90
+ 0
+280
+ 0
+281
+ 0
+ 0
+ENDSEC
+ 0
+SECTION
+ 2
+TABLES
+ 0
+TABLE
+ 2
+VPORT
+ 5
+8
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 1
+ 0
+VPORT
+ 5
+29
+330
+8
+100
+AcDbSymbolTableRecord
+100
+AcDbViewportTableRecord
+ 2
+*Active
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 11
+1.0
+ 21
+1.0
+ 12
+148.5
+ 22
+210.0
+ 13
+0.0
+ 23
+0.0
+ 14
+10.0
+ 24
+10.0
+ 15
+10.0
+ 25
+10.0
+ 16
+0.0
+ 26
+0.0
+ 36
+1.0
+ 17
+0.0
+ 27
+0.0
+ 37
+0.0
+ 40
+420.0
+ 41
+0.7071428571
+ 42
+50.0
+ 43
+0.0
+ 44
+0.0
+ 50
+0.0
+ 51
+0.0
+ 71
+ 0
+ 72
+ 2000
+ 73
+ 1
+ 74
+ 3
+ 75
+ 0
+ 76
+ 0
+ 77
+ 0
+ 78
+ 0
+281
+ 0
+ 65
+ 1
+110
+0.0
+120
+0.0
+130
+0.0
+111
+1.0
+121
+0.0
+131
+0.0
+112
+0.0
+122
+1.0
+132
+0.0
+ 79
+ 0
+146
+0.0
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+LTYPE
+ 5
+5
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 6
+ 0
+LTYPE
+ 5
+14
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+ByBlock
+ 70
+ 0
+ 3
+
+ 72
+ 65
+ 73
+ 0
+ 40
+0.0
+ 0
+LTYPE
+ 5
+15
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+ByLayer
+ 70
+ 0
+ 3
+
+ 72
+ 65
+ 73
+ 0
+ 40
+0.0
+ 0
+LTYPE
+ 5
+16
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+Continuous
+ 70
+ 0
+ 3
+Solid line
+ 72
+ 65
+ 73
+ 0
+ 40
+0.0
+ 0
+LTYPE
+ 5
+3F
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+HIDDEN
+ 70
+ 0
+ 3
+Hidden __ __ __ __ __ __ __ __ __ __ __ __ __ __
+ 72
+ 65
+ 73
+ 2
+ 40
+1.905
+ 49
+1.27
+ 74
+ 0
+ 49
+-0.635
+ 74
+ 0
+ 0
+LTYPE
+ 5
+40
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+PHANTOM
+ 70
+ 0
+ 3
+Phantom ______ __ __ ______ __ __ ______
+ 72
+ 65
+ 73
+ 6
+ 40
+12.7
+ 49
+6.35
+ 74
+ 0
+ 49
+-1.27
+ 74
+ 0
+ 49
+1.27
+ 74
+ 0
+ 49
+-1.27
+ 74
+ 0
+ 49
+1.27
+ 74
+ 0
+ 49
+-1.27
+ 74
+ 0
+ 0
+LTYPE
+ 5
+41
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+CENTER
+ 70
+ 0
+ 3
+Center ____ _ ____ _ ____ _ ____ _ ____ _ ____
+ 72
+ 65
+ 73
+ 4
+ 40
+10.16
+ 49
+6.35
+ 74
+ 0
+ 49
+-1.27
+ 74
+ 0
+ 49
+1.27
+ 74
+ 0
+ 49
+-1.27
+ 74
+ 0
+ 0
+LTYPE
+ 5
+42
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+CENTERX2
+ 70
+ 0
+ 3
+Center (2x) ________ __ ________ __ _____
+ 72
+ 65
+ 73
+ 4
+ 40
+20.32
+ 49
+12.7
+ 74
+ 0
+ 49
+-2.54
+ 74
+ 0
+ 49
+2.54
+ 74
+ 0
+ 49
+-2.54
+ 74
+ 0
+ 0
+LTYPE
+ 5
+43
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DOT2
+ 70
+ 0
+ 3
+Dot (.5x) ........................................
+ 72
+ 65
+ 73
+ 2
+ 40
+0.635
+ 49
+0.0
+ 74
+ 0
+ 49
+-0.635
+ 74
+ 0
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+LAYER
+ 5
+2
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 8
+ 0
+LAYER
+ 5
+10
+330
+2
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+ 2
+0
+ 70
+ 0
+ 62
+ 7
+ 6
+Continuous
+370
+ -3
+390
+F
+ 0
+LAYER
+ 5
+44
+330
+2
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+ 2
+TEKENLIJN
+ 70
+ 0
+ 62
+ 7
+ 6
+Continuous
+370
+ -3
+390
+F
+ 0
+LAYER
+ 5
+45
+330
+2
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+ 2
+HARTLIJN
+ 70
+ 0
+ 62
+ 7
+ 6
+CENTER
+370
+ -3
+390
+F
+ 0
+LAYER
+ 5
+46
+330
+2
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+ 2
+STIPPELIJN
+ 70
+ 0
+ 62
+ 7
+ 6
+HIDDEN
+370
+ 15
+390
+F
+ 0
+LAYER
+ 5
+47
+330
+2
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+ 2
+DIKKE_MAATLIJN
+ 70
+ 0
+ 62
+ 7
+ 6
+Continuous
+370
+ 158
+390
+F
+ 0
+LAYER
+ 5
+48
+330
+2
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+ 2
+STUKLIJST
+ 70
+ 0
+ 62
+ 7
+ 6
+Continuous
+370
+ 15
+390
+F
+ 0
+LAYER
+ 5
+49
+330
+2
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+ 2
+MAATLIJN
+ 70
+ 0
+ 62
+ 7
+ 6
+Continuous
+370
+ -3
+390
+F
+ 0
+LAYER
+ 5
+4A
+330
+2
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+ 2
+TEKST
+ 70
+ 0
+ 62
+ 7
+ 6
+Continuous
+370
+ 15
+390
+F
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+STYLE
+ 5
+3
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 3
+ 0
+STYLE
+ 5
+11
+330
+3
+100
+AcDbSymbolTableRecord
+100
+AcDbTextStyleTableRecord
+ 2
+Standard
+ 70
+ 0
+ 40
+0.0
+ 41
+1.0
+ 50
+0.0
+ 71
+ 0
+ 42
+3.5
+ 3
+txt
+ 4
+
+ 0
+STYLE
+ 5
+54
+330
+3
+100
+AcDbSymbolTableRecord
+100
+AcDbTextStyleTableRecord
+ 2
+SLDTEXTSTYLE0
+ 70
+ 0
+ 40
+0.0
+ 41
+0.75
+ 50
+0.0
+ 71
+ 0
+ 42
+0.2
+ 3
+TXT
+ 4
+
+ 0
+STYLE
+ 5
+56A
+330
+3
+100
+AcDbSymbolTableRecord
+100
+AcDbTextStyleTableRecord
+ 2
+SLDTEXTSTYLE1
+ 70
+ 0
+ 40
+0.0
+ 41
+0.7
+ 50
+0.0
+ 71
+ 0
+ 42
+0.2
+ 3
+TXT
+ 4
+
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+VIEW
+ 5
+6
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 0
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+UCS
+ 5
+7
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 0
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+APPID
+ 5
+9
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 1
+ 0
+APPID
+ 5
+12
+330
+9
+100
+AcDbSymbolTableRecord
+100
+AcDbRegAppTableRecord
+ 2
+ACAD
+ 70
+ 0
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+DIMSTYLE
+ 5
+A
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 4
+100
+AcDbDimStyleTable
+ 0
+DIMSTYLE
+105
+27
+330
+A
+100
+AcDbSymbolTableRecord
+100
+AcDbDimStyleTableRecord
+ 2
+ISO-25
+ 70
+ 0
+ 41
+2.5
+ 42
+0.625
+ 43
+3.75
+ 44
+1.25
+ 73
+ 0
+ 74
+ 0
+ 77
+ 1
+ 78
+ 8
+140
+2.5
+141
+2.5
+143
+0.0393700787
+147
+0.625
+171
+ 3
+172
+ 1
+178
+ 0
+271
+ 2
+272
+ 2
+274
+ 3
+278
+ 44
+283
+ 0
+284
+ 8
+340
+11
+ 0
+DIMSTYLE
+105
+501
+102
+{ACAD_REACTORS
+330
+500
+102
+}
+330
+A
+100
+AcDbSymbolTableRecord
+100
+AcDbDimStyleTableRecord
+ 2
+SLDDIMSTYLE0
+ 70
+ 0
+ 41
+3.0
+ 44
+0.0
+ 45
+0.000000001
+ 73
+ 0
+ 74
+ 0
+ 77
+ 1
+ 78
+ 12
+ 79
+ 3
+140
+3.5
+144
+2.5
+147
+0.875
+172
+ 1
+173
+ 1
+178
+ 0
+271
+ 3
+272
+ 3
+276
+ 2
+284
+ 12
+289
+ 0
+340
+54
+ 0
+DIMSTYLE
+105
+50E
+102
+{ACAD_REACTORS
+330
+50D
+330
+51A
+330
+526
+330
+56C
+330
+578
+330
+584
+102
+}
+330
+A
+100
+AcDbSymbolTableRecord
+100
+AcDbDimStyleTableRecord
+ 2
+SLDDIMSTYLE1
+ 70
+ 0
+ 41
+3.0
+ 44
+0.0
+ 45
+0.000000001
+ 73
+ 0
+ 74
+ 0
+ 77
+ 1
+ 78
+ 12
+ 79
+ 3
+140
+3.5
+144
+2.5
+147
+0.875
+172
+ 1
+173
+ 1
+178
+ 0
+271
+ 0
+272
+ 0
+276
+ 2
+284
+ 12
+289
+ 0
+340
+54
+ 0
+DIMSTYLE
+105
+533
+102
+{ACAD_REACTORS
+330
+532
+330
+53C
+330
+545
+330
+54E
+330
+557
+330
+561
+102
+}
+330
+A
+100
+AcDbSymbolTableRecord
+100
+AcDbDimStyleTableRecord
+ 2
+SLDDIMSTYLE2
+ 70
+ 0
+ 41
+3.0
+ 44
+0.0
+ 45
+0.000000001
+ 73
+ 0
+ 74
+ 0
+ 77
+ 1
+ 78
+ 12
+ 79
+ 3
+140
+3.5
+144
+2.5
+147
+0.875
+173
+ 1
+178
+ 0
+271
+ 3
+272
+ 3
+276
+ 2
+284
+ 12
+340
+54
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+BLOCK_RECORD
+ 5
+1
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 15
+ 0
+BLOCK_RECORD
+ 5
+1F
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+*Model_Space
+340
+22
+ 0
+BLOCK_RECORD
+ 5
+1B
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+*Paper_Space
+340
+1E
+ 0
+BLOCK_RECORD
+ 5
+23
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+*Paper_Space0
+340
+26
+ 0
+BLOCK_RECORD
+ 5
+56
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+SW_BROKEN_VIEW_0
+340
+0
+102
+{BLKREFS
+331
+59
+102
+}
+ 0
+BLOCK_RECORD
+ 5
+502
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+50F
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_1
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+51B
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_2
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+527
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_3
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+534
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_4
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+53D
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_5
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+546
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_6
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+54F
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_7
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+558
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_8
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+562
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_9
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+56D
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_10
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+579
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_11
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+585
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_12
+340
+0
+ 0
+ENDTAB
+ 0
+ENDSEC
+ 0
+SECTION
+ 2
+BLOCKS
+ 0
+BLOCK
+ 5
+20
+330
+1F
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+*Model_Space
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+*Model_Space
+ 1
+
+ 0
+ENDBLK
+ 5
+21
+330
+1F
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+1C
+330
+1B
+100
+AcDbEntity
+ 67
+ 1
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+*Paper_Space
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+*Paper_Space
+ 1
+
+ 0
+ENDBLK
+ 5
+1D
+330
+1B
+100
+AcDbEntity
+ 67
+ 1
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+24
+330
+23
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+*Paper_Space0
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+*Paper_Space0
+ 1
+
+ 0
+ENDBLK
+ 5
+25
+330
+23
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+57
+330
+56
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+SW_BROKEN_VIEW_0
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+SW_BROKEN_VIEW_0
+ 1
+
+ 0
+ENDBLK
+ 5
+58
+330
+56
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+503
+330
+502
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D
+ 1
+
+ 0
+SOLID
+ 5
+505
+330
+502
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.4299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+194.9299255838
+ 21
+25.7821787182
+ 31
+0.0
+ 12
+195.9299255838
+ 22
+25.7821787182
+ 32
+0.0
+ 13
+195.9299255838
+ 23
+25.7821787182
+ 33
+0.0
+ 0
+SOLID
+ 5
+506
+330
+502
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.4299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+195.9299255838
+ 21
+44.9821787182
+ 31
+0.0
+ 12
+194.9299255838
+ 22
+44.9821787182
+ 32
+0.0
+ 13
+194.9299255838
+ 23
+44.9821787182
+ 33
+0.0
+ 0
+LINE
+ 5
+507
+330
+502
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+143.0299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+196.4299255838
+ 21
+28.7821787182
+ 31
+0.0
+ 0
+LINE
+ 5
+508
+330
+502
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+149.0299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+196.4299255838
+ 21
+41.9821787182
+ 31
+0.0
+ 0
+LINE
+ 5
+509
+330
+502
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+195.4299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+195.4299255838
+ 21
+22.7821787182
+ 31
+0.0
+ 0
+LINE
+ 5
+50A
+330
+502
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+195.4299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+195.4299255838
+ 21
+41.9821787182
+ 31
+0.0
+ 0
+LINE
+ 5
+50B
+330
+502
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+195.4299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+195.4299255838
+ 21
+47.9821787182
+ 31
+0.0
+ 0
+MTEXT
+ 5
+50C
+330
+502
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+190.9181886692
+ 20
+31.991019801
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+33
+ 7
+SLDTEXTSTYLE0
+ 11
+6.123233995736766E-17
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+504
+330
+502
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+510
+330
+50F
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_1
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_1
+ 1
+
+ 0
+SOLID
+ 5
+512
+330
+50F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+171.4299255838
+ 20
+280.0124004364
+ 30
+0.0
+ 11
+170.9299255838
+ 21
+277.0124004364
+ 31
+0.0
+ 12
+171.9299255838
+ 22
+277.0124004364
+ 32
+0.0
+ 13
+171.9299255838
+ 23
+277.0124004364
+ 33
+0.0
+ 0
+SOLID
+ 5
+513
+330
+50F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+171.4299255838
+ 20
+287.499722104
+ 30
+0.0
+ 11
+171.9299255838
+ 21
+290.499722104
+ 31
+0.0
+ 12
+170.9299255838
+ 22
+290.499722104
+ 32
+0.0
+ 13
+170.9299255838
+ 23
+290.499722104
+ 33
+0.0
+ 0
+LINE
+ 5
+514
+330
+50F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+150.4168593775
+ 20
+280.0124004364
+ 30
+0.0
+ 11
+172.4299255838
+ 21
+280.0124004364
+ 31
+0.0
+ 0
+LINE
+ 5
+515
+330
+50F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+148.8555696261
+ 20
+287.499722104
+ 30
+0.0
+ 11
+172.4299255838
+ 21
+287.499722104
+ 31
+0.0
+ 0
+LINE
+ 5
+516
+330
+50F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+280.0124004364
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+267.6770872868
+ 31
+0.0
+ 0
+LINE
+ 5
+517
+330
+50F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+280.0124004364
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+287.499722104
+ 31
+0.0
+ 0
+LINE
+ 5
+518
+330
+50F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+287.499722104
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+293.499722104
+ 31
+0.0
+ 0
+MTEXT
+ 5
+519
+330
+50F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+166.9181886692
+ 20
+267.6770872868
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+19
+ 7
+SLDTEXTSTYLE0
+ 11
+6.123233995736766E-17
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+511
+330
+50F
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+51C
+330
+51B
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_2
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_2
+ 1
+
+ 0
+SOLID
+ 5
+51E
+330
+51B
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+171.4299255838
+ 20
+202.8200717467
+ 30
+0.0
+ 11
+170.9299255838
+ 21
+199.8200717467
+ 31
+0.0
+ 12
+171.9299255838
+ 22
+199.8200717467
+ 32
+0.0
+ 13
+171.9299255838
+ 23
+199.8200717467
+ 33
+0.0
+ 0
+SOLID
+ 5
+51F
+330
+51B
+370
+ 0
+100
+AcDbCircle
+ 10
+149.1879091276
+ 20
+124.2049907824
+ 30
+0.0
+ 40
+0.0800276369171186
+100
+AcDbArc
+ 50
+31.2357600789
+ 51
+89.9882535152
+ 0
+LINE
+ 5
+3A8
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.2563360204
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+149.2563360204
+ 21
+124.2464899752
+ 31
+0.0
+ 0
+LINE
+ 5
+3A9
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2162960043
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+134.2162960043
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+LINE
+ 5
+3AA
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2162960043
+ 20
+124.2850184176
+ 30
+0.0
+ 11
+134.1499254873
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+LINE
+ 5
+3AB
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.4299252202
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+133.4299252202
+ 21
+124.2555737231
+ 31
+0.0
+ 0
+LWPOLYLINE
+ 5
+3AC
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbPolyline
+ 90
+ 4
+ 70
+ 128
+ 43
+0.0
+ 10
+149.2569536736
+ 20
+124.2454528544
+ 10
+149.2656842639
+ 20
+124.2305516932
+ 10
+149.2736712863
+ 20
+124.2169141505
+ 10
+149.2808976044
+ 20
+124.204575989
+ 0
+LINE
+ 5
+3AD
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.3099258115
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+149.3099258115
+ 21
+124.1757392619
+ 31
+0.0
+ 0
+LINE
+ 5
+3AE
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.2563360204
+ 20
+124.2464899752
+ 30
+0.0
+ 11
+149.2569536736
+ 21
+124.2454528544
+ 31
+0.0
+ 0
+LINE
+ 5
+3AF
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2299253514
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+134.2299253514
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+LINE
+ 5
+3B0
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2299253514
+ 20
+124.2850184176
+ 30
+0.0
+ 11
+134.2162960043
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+LINE
+ 5
+3B1
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.5099256804
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+148.5099256804
+ 21
+123.090755913
+ 31
+0.0
+ 0
+LINE
+ 5
+3B2 5
+4FF
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+133.4299255838
+ 20
+660.3011936068
+ 30
+0.0
+ 0
+DIMENSION
+ 5
+500
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D
+ 10
+195.4299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+195.4299255838
+ 21
+34.5771309945
+ 31
+0.0
+ 70
+ 160
+ 1
+33
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE0
+100
+AcDbAlignedDimension
+ 13
+142.0299255838
+ 23
+28.7821787182
+ 33
+0.0
+ 14
+148.0299255838
+ 24
+41.9821787182
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+50D
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_1
+ 10
+171.4299255838
+ 20
+287.499722104
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+271.6850735678
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+149.4168593775
+ 23
+280.0124004364
+ 33
+0.0
+ 14
+147.8555696261
+ 24
+287.499722104
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+51A
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_2
+ 10
+171.4299255838
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+194.8678981433
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+149.4168593775
+ 23
+202.8200717467
+ 33
+0.0
+ 14
+147.8555696261
+ 24
+210.3073934143
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+526
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_3
+ 10
+171.4299255838
+ 20
+133.7256838344
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+113.1236101725
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+149.4168593775
+ 23
+126.2783621669
+ 33
+0.0
+ 14
+148.0299255838
+ 24
+133.7256838344
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+532
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_4
+ 10
+195.4299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+195.4299255838
+ 21
+84.1586135498
+ 31
+0.0
+ 70
+ 160
+ 1
+675,902
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+148.0299255838
+ 23
+133.7256838344
+ 33
+0.0
+ 14
+148.0299255838
+ 24
+41.9821787182
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+53C
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_5
+ 10
+195.3191971725
+ 20
+133.7649839595
+ 30
+0.0
+ 11
+195.2817915125
+ 21
+178.7746730971
+ 31
+0.0
+ 70
+ 160
+ 1
+524,500
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+147.8555696261
+ 23
+210.3073934143
+ 33
+0.0
+ 14
+148.0299255838
+ 24
+133.7256838344
+ 34
+0.0
+ 50
+-89.9523838979
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+545
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_6
+ 10
+195.2555696261
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+195.2555696261
+ 21
+246.0726158741
+ 31
+0.0
+ 70
+ 160
+ 1
+524,400
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+147.8555696261
+ 23
+287.499722104
+ 33
+0.0
+ 14
+147.8555696261
+ 24
+210.3073934143
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+54E
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_7
+ 10
+195.2555696261
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+195.2555696261
+ 21
+357.7845521004
+ 31
+0.0
+ 70
+ 160
+ 1
+545,698
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+147.8555696261
+ 23
+287.499722104
+ 33
+0.0
+ 14
+145.8378589248
+ 24
+397.2202084954
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+557
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_8
+ 10
+219.4299255838
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+219.4299255838
+ 21
+226.31135989
+ 31
+0.0
+ 70
+ 160
+ 1
+Kozijnmaat =
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+142.0299255838
+ 23
+28.7821787182
+ 33
+0.0
+ 14
+145.8378589248
+ 24
+397.2202084954
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+561
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_9
+ 10
+99.2299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+99.2299255838
+ 21
+214.5361780403
+ 31
+0.0
+ 70
+ 160
+ 1
+DMH =
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+133.4299255838
+ 23
+383.8202084954
+ 33
+0.0
+ 14
+142.0299255838
+ 24
+28.7821787182
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+56C
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_10
+ 10
+117.2299255838
+ 20
+148.4450158005
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+157.3719824392
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+133.5499255838
+ 23
+147.8850158005
+ 33
+0.0
+ 14
+133.5499255838
+ 24
+148.4450158005
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+578
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_11
+ 10
+117.2299255838
+ 20
+224.9867253803
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+236.5334651005
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+133.5499255838
+ 23
+224.4267253803
+ 33
+0.0
+ 14
+133.5499255838
+ 24
+224.9867253803
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+584
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_12
+ 10
+117.2299255838
+ 20
+302.17905407
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+311.8110919194
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+133.5499255838
+ 23
+301.61905407
+ 33
+0.0
+ 14
+133.5499255838
+ 24
+302.17905407
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+LINE
+ 5
+590
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+103.7777066237
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+103.4
+ 51
+89.9999492362
+ 0
+ARC
+ 5
+3D7
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+137.5163691973
+ 20
+133.4850123101
+ 30
+0.0
+ 40
+0.1200011213146664
+100
+AcDbArc
+ 50
+138.497247328
+ 51
+269.9999068087
+ 0
+ARC
+ 5
+3D8
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+136.355993356
+ 20
+132.6849348127
+ 30
+0.0
+ 40
+0.0800841035922697
+100
+AcDbArc
+ 50
+90.0150168159
+ 51
+113.8359930711
+ 0
+ARC
+ 5
+3D9
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+135.0299508788
+ 20
+135.6849288285
+ 30
+0.0
+ 40
+3.199908694378492
+100
+AcDbArc
+ 50
+269.9995452672
+ 51
+293.8464037785
+ 0
+LINE
+ 5
+3DA
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+135.0299254825
+ 20
+132.4850201342
+ 30
+0.0
+ 11
+134.2499256899
+ 21
+132.4850201342
+ 31
+0.0
+ 0
+ARC
+ 5
+3DB
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.2499379652
+ 20
+132.3650080634
+ 30
+0.0
+ 40
+0.1200120715024033
+100
+AcDbArc
+ 50
+90.0058604254
+ 51
+179.9980525768
+ 0
+LINE
+ 5
+3DC
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.1299258938
+ 20
+132.3650121424
+ 30
+0.0
+ 11
+134.1299258938
+ 21
+132.2075128291
+ 31
+0.0
+ 0
+ARC
+ 5
+3DD
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.2499411967
+ 20
+132.2075174672
+ 30
+0.0
+ 40
+0.1200153029850342
+100
+AcDbArc
+ 50
+180.0022142502
+ 51
+218.9973251432
+ 0
+LINE
+ 5
+3DE
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.1566682627
+ 20
+132.1319937442
+ 30
+0.0
+ 11
+134.4031833877
+ 21
+131.8275689814
+ 31
+0.0
+ 0
+ARC
+ 5
+3DF
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.3099104538
+ 20
+131.7520452584
+ 30
+0.0
+ 40
+0.1200153029849757
+100
+AcDbArc
+ 50
+0.0022142501
+ 51
+38.9973251432
+ 0
+LINE
+ 5
+3E0
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.4299257567
+ 20
+131.7520498965
+ 30
+0.0
+ 11
+134.4299257567
+ 21
+127.9414653514
+ 31
+0.0
+ 0
+ARC
+ 5
+3E1
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.3099104538
+ 20
+127.9414699895
+ 30
+0.0
+ 40
+0.1200153029849757
+100
+AcDbArc
+ 50
+321.0026748568
+ 51
+359.9977857499
+ 0
+LINE
+ 5
+3E2
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.4031833877
+ 20
+127.8659462664
+ 30
+0.0
+ 11
+134.05666806
+ 21
+127.4380445216
+ 31
+0.0
+ 0
+ARC
+ 5
+3E3
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.149940994
+ 20
+127.3625207985
+ 30
+0.0
+ 40
+0.120015302984857
+100
+AcDbArc
+ 50
+141.0026748568
+ 51
+179.9977857499
+ 0
+LINE
+ 5
+3E4
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.0299256911
+ 20
+127.3625254367
+ 30
+0.0
+ 11
+134.0299256911
+ 21
+126.1250138018
+ 31
+0.0
+ 0
+ARC
+ 5
+3E5
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.1499186836
+ 20
+126.1250107233
+ 30
+0.0
+ 40
+0.1199929925412997
+100
+AcDbArc
+ 50
+179.9985300323
+ 51
+270.0032486934
+ 0
+LINE
+ 5
+3E6
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.1499254873
+ 20
+126.005017731
+ 30
+0.0
+ 11
+136.2299256792
+ 21
+126.005017731
+ 31
+0.0
+ 0
+ARC
+ 5
+3E7
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+136.229912767
+ 20
+125.8850050222
+ 30
+0.0
+ 40
+0.1200127094438369
+100
+AcDbArc
+ 50
+0.0079431648
+ 51
+89.9938355609
+ 0
+LINE
+ 5
+3E8
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+136.3499254753
+ 20
+125.8850216601
+ 30
+0.0
+ 11
+136.3499254753
+ 21
+125.2050161097
+ 31
+0.0
+ 0
+ARC
+ 5
+3E9
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+136.4699184679
+ 20
+125.2050130312
+ 30
+0.0
+ 40
+0.1199929925783876
+100
+AcDbArc
+ 50
+179.9985300396
+ 51
+270.0034265556
+ 0
+LINE
+ 5
+3EA
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+136.469925644
+ 20
+125.0850200389
+ 30
+0.0
+ 11
+148.7099253406
+ 21
+125.0850200389
+ 31
+0.0
+ 0
+ARC
+ 5
+3EB
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.7099328893
+ 20
+125.2050130312
+ 30
+0.0
+ 40
+0.119992992617387
+100
+AcDbArc
+ 50
+269.9963955831
+ 51
+0.0014699527
+ 0
+LINE
+ 5
+3EC
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.8299258818
+ 20
+125.2050161097
+ 30
+0.0
+ 11
+148.8299258818
+ 21
+125.9183764193
+ 31
+0.0
+ 0
+ARC
+ 5
+3ED
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.709927865
+ 20
+125.91837716
+ 30
+0.0
+ 40
+0.1199980168837526
+100
+AcDbArc
+ 50
+359.9996463459
+ 51
+53.9732887541
+ 0
+LINE
+ 5
+3EE
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.7805061807
+ 20
+126.0154247019
+ 30
+0.0
+ 11
+146.6793448498
+ 21
+127.5435447428
+ 31
+0.0
+ 0
+ARC
+ 5
+3EF
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+146.749919111
+ 20
+127.6405897595
+ 30
+0.0
+ 40
+0.1199935898327405
+100
+AcDbArc
+ 50
+179.9984405241
+ 51
+233.9741452813
+ 0
+LINE
+ 5
+3F0
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+146.6299255212
+ 20
+127.6405930255
+ 30
+0.0
+ 11
+146.6299255212
+ 21
+133.3159088824
+ 31
+0.0
+ 0
+ARC
+ 5
+3F1
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+146.5099232936
+ 20
+133.315903341
+ 30
+0.0
+ 40
+0.120002227728508
+100
+AcDbArc
+ 50
+0.0026457597
+ 51
+20.999682824
+ 0
+LINE
+ 5
+3F2
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+146.6219552626
+ 20
+133.3589076731
+ 30
+0.0
+ 11
+146.3471007367
+ 21
+134.0749263499
+ 31
+0.0
+ 0
+ARC
+ 5
+3F3
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+146.2350683839
+ 20
+134.0319208293
+ 30
+0.0
+ 40
+0.1200030119081427
+100
+AcDbArc
+ 50
+21.0001469018
+ 51
+134.9966517297
+ 0
+LINE
+ 5
+3F4
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+146.1502183994
+ 20
+134.1167807315
+ 30
+0.0
+ 11
+144.8336009701
+ 21
+132.8001618121
+ 31
+0.0
+ 0
+ARC
+ 5
+3F5
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+144.748758296
+ 20
+132.8850047843
+ 30
+0.0
+ 40
+0.1199858712428317
+100
+AcDbArc
+ 50
+269.995164801
+ 51
+314.9998993415
+ 0
+LINE
+ 5
+3F6
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+144.7487481703
+ 20
+132.7650189135
+ 30
+0.0
+ 11
+136.3559723665
+ 21
+132.7650189135
+ 31
+0.0
+ 0
+ARC
+ 5
+3F7
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+133.3499200157
+ 20
+124.3450103265
+ 30
+0.0
+ 40
+0.1199960615305855
+100
+AcDbArc
+ 50
+127.9778213362
+ 51
+315.5786816839
+ 0
+ARC
+ 5
+3F8
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+133.492770837
+ 20
+124.204971188
+ 30
+0.0
+ 40
+0.0800472312450756
+100
+AcDbArc
+ 50
+90.0117105688
+ 51
+135.5556160835
+ 0
+LINE
+ 5
+3F9
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.4927544763
+ 20
+124.2850184176
+ 30
+0.0
+ 11
+134.1499254873
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+ARC
+ 5
+3FA
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.1499294027
+ 20
+124.3650139485
+ 30
+0.0
+ 40
+0.0799955310302896
+100
+AcDbArc
+ 50
+269.9971956253
+ 51
+15.9984086718
+ 0
+LINE
+ 5
+3FB
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2268266549
+ 20
+124.3870615695
+ 30
+0.0
+ 11
+134.0812475254
+ 21
+124.8947620127
+ 31
+0.0
+ 0
+ARC
+ 5
+3FC
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.465753153
+ 20
+125.0050135586
+ 30
+0.0
+ 40
+0.3999999762120172
+100
+AcDbArc
+ 50
+90.0000765526
+ 51
+195.9994948625
+ 0
+LINE
+ 5
+3FD
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.4657526185
+ 20
+125.4050135348
+ 30
+0.0
+ 11
+134.8818720152
+ 21
+125.4050135348
+ 31
+0.0
+ 0
+ARC
+ 5
+3FE
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.8818895266
+ 20
+125.0050340998
+ 30
+0.0
+ 40
+0.3999794353998879
+100
+AcDbArc
+ 50
+15.9988052851
+ 51
+90.0025084456
+ 0
+LINE
+ 5
+3FF
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+135.2663767358
+ 20
+125.1152753565
+ 30
+0.0
+ 11
+135.3382822891
+ 21
+124.864506695
+ 31
+0.0
+ 0
+ARC
+ 5
+400
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+136.1072824299
+ 20
+125.085006887
+ 30
+0.0
+ 40
+0.7999884694317702
+100
+AcDbArc
+ 50
+195.9995146278
+ 51
+270.000666096
+ 0
+LINE
+ 5
+401
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+136.1072917303
+ 20
+124.2850184176
+ 30
+0.0
+ 11
+144.2699256187
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+ARC
+ 5
+402
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+144.2699168791
+ 20
+124.2050096278
+ 30
+0.0
+ 40
+0.0800087902519152
+100
+AcDbArc
+ 50
+0.0053247404
+ 51
+89.9937414529
+ 0
+LINE
+ 5
+403
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+144.349925669
+ 20
+124.2050170634
+ 30
+0.0
+ 11
+144.349925669
+ 21
+123.9650130007
+ 31
+0.0
+ 0
+ARC
+ 5
+404
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+144.4299341409
+ 20
+123.9650201183
+ 30
+0.0
+ 40
+0.0800084721904046
+100
+AcDbArc
+ 50
+180.0050970011
+ 51
+269.9938357899
+ 0
+LINE
+ 5
+405
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+144.4299255331
+ 20
+123.8850116465
+ 30
+0.0
+ 11
+144.669925498
+ 21
+123.8850116465
+ 31
+0.0
+ 0
+ARC
+ 5
+406
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+144.6699166267
+ 20
+123.9650207543
+ 30
+0.0
+ 40
+0.0800091083155471
+100
+AcDbArc
+ 50
+270.0063528841
+ 51
+359.9944475208
+ 0
+LINE
+ 5
+407
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+144.7499257346
+ 20
+123.9650130007
+ 30
+0.0
+ 11
+144.7499257346
+ 21
+124.2050170634
+ 31
+0.0
+ 0
+ARC
+ 5
+408
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+144.8299342065
+ 20
+124.2050099459
+ 30
+0.0
+ 40
+0.0800084721903386
+100
+AcDbArc
+ 50
+90.00616421
+ 51
+179.9949029989
+ 0
+LINE
+ 5
+409
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+144.8299255987
+ 20
+124.2850184176
+ 30
+0.0
+ 11
+147.7288805713
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+ARC
+ 5
+40A
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+147.7288779357
+ 20
+123.8850159947
+ 30
+0.0
+ 40
+0.4000024229248144
+100
+AcDbArc
+ 50
+345.0003781315
+ 51
+89.9996224743
+ 0
+LINE
+ 5
+40B
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.1152512898
+ 20
+123.7814902995
+ 30
+0.0
+ 11
+147.6108663549
+ 21
+121.8991040919
+ 31
+0.0
+ 0
+ARC
+ 5
+40C
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+147.1754788065
+ 20
+122.0157535212
+ 30
+0.0
+ 40
+0.4507431714908194
+100
+AcDbArc
+ 50
+188.9989377763
+ 51
+345.0015117857
+ 0
+LINE
+ 5
+40D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+146.7302837243
+ 20
+121.9452500079
+ 30
+0.0
+ 11
+146.7030935754
+ 21
+122.1169233058
+ 31
+0.0
+ 0
+ARC
+ 5
+40E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+146.5055559119
+ 20
+122.0856369716
+ 30
+0.0
+ 40
+0.1999999080912635
+100
+AcDbArc
+ 50
+8.9998420876
+ 51
+161.9998367828
+ 0
+LINE
+ 5
+40F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+146.3153448721
+ 20
+122.1474408839
+ 30
+0.0
+ 11
+145.8463118245
+ 21
+120.7038998339
+ 31
+0.0
+ 0
+ARC
+ 5
+410
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircleTrace
+ 10
+195.2555696261
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+194.7555696261
+ 21
+394.2202084954
+ 31
+0.0
+ 12
+195.7555696261
+ 22
+394.2202084954
+ 32
+0.0
+ 13
+195.7555696261
+ 23
+394.2202084954
+ 33
+0.0
+ 0
+LINE
+ 5
+554
+330
+54F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+148.8555696261
+ 20
+287.499722104
+ 30
+0.0
+ 11
+196.2555696261
+ 21
+287.499722104
+ 31
+0.0
+ 0
+LINE
+ 5
+555
+330
+54F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+146.8378589248
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+196.2555696261
+ 21
+397.2202084954
+ 31
+0.0
+ 0
+LINE
+ 5
+556
+330
+54F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+195.2555696261
+ 20
+287.499722104
+ 30
+0.0
+ 11
+195.2555696261
+ 21
+397.2202084954
+ 31
+0.0
+ 0
+ENDBLK
+ 5
+551
+330
+54F
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+559
+330
+558
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_8
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_8
+ 1
+
+ 0
+SOLID
+ 5
+55B
+330
+558
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+219.4299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+219.9299255838
+ 21
+31.7821787182
+ 31
+0.0
+ 12
+218.9299255838
+ 22
+31.7821787182
+ 32
+0.0
+ 13
+218.9299255838
+ 23
+31.7821787182
+ 33
+0.0
+ 0
+SOLID
+ 5
+55C
+330
+558
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+219.4299255838
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+218.9299255838
+ 21
+394.2202084954
+ 31
+0.0
+ 12
+219.9299255838
+ 22
+394.2202084954
+ 32
+0.0
+ 13
+219.9299255838
+ 23
+394.2202084954
+ 33
+0.0
+ 0
+LINE
+ 5
+55D
+330
+558
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+143.0299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+220.4299255838
+ 21
+28.7821787182
+ 31
+0.0
+ 0
+LINE
+ 5
+55E
+330
+558
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+146.8378589248
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+220.4299255838
+ 21
+397.2202084954
+ 31
+0.0
+ 0
+LINE
+ 5
+55F
+330
+558
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+219.4299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+219.4299255838
+ 21
+397.2202084954
+ 31
+0.0
+ 0
+MTEXT
+ 5
+560
+330
+558
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+214.9181886692
+ 20
+211.8228183688
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+Kozijnmaat =
+ 7
+SLDTEXTSTYLE0
+ 11
+6.123233995736766E-17
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+55A
+330
+558
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+563
+330
+562
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_9
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_9
+ 1
+
+ 0
+SOLID
+ 5
+565
+330
+562
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+99.2299255838
+ 20
+383.8202084954
+ 30
+0.0
+ 11
+98.7299255838
+ 21
+380.8202084954
+ 31
+0.0
+ 12
+99.7299255838
+ 22
+380.8202084954
+ 32
+0.0
+ 13
+99.7299255838
+ 23
+380.8202084954
+ 33
+0.0
+ 0
+SOLID
+ 5
+566
+330
+562
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+99.2299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+99.7299255838
+ 21
+31.7821787182
+ 31
+0.0
+ 12
+98.7299255838
+ 22
+31.7821787182
+ 32
+0.0
+ 13
+98.7299255838
+ 23
+31.7821787182
+ 33
+0.0
+ 0
+LINE
+ 5
+567
+330
+562
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+132.4299255838
+ 20
+383.8202084954
+ 30
+0.0
+ 11
+98.2299255838
+ 21
+383.8202084954
+ 31
+0.0
+ 0
+LINE
+ 5
+568
+330
+562
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+141.0299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+98.2299255838
+ 21
+28.7821787182
+ 31
+0.0
+ 0
+LINE
+ 5
+569
+330
+562
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+99.2299255838
+ 20
+383.8202084954
+ 30
+0.0
+ 11
+99.2299255838
+ 21
+28.7821787182
+ 31
+0.0
+ 0
+MTEXT
+ 5
+56B
+330
+562
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+94.7181886692
+ 20
+207.001456174
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+DMH =
+ 7
+SLDTEXTSTYLE1
+ 11
+-7.044195017643634E-15
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+564
+330
+562
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+56E
+330
+56D
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_10
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_10
+ 1
+
+ 0
+SOLID
+ 5
+570
+330
+56D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+117.2299255838
+ 20
+147.8850158005
+ 30
+0.0
+ 11
+116.7299255838
+ 21
+144.8850158005
+ 31
+0.0
+ 12
+117.7299255838
+ 22
+144.8850158005
+ 32
+0.0
+ 13
+117.7299255838
+ 23
+144.8850158005
+ 33
+0.0
+ 0
+SOLID
+ 5
+571
+330
+56D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+117.2299255838
+ 20
+148.4450158005
+ 30
+0.0
+ 11
+117.7299255838
+ 21
+151.4450158005
+ 31
+0.0
+ 12
+116.7299255838
+ 22
+151.4450158005
+ 32
+0.0
+ 13
+116.7299255838
+ 23
+151.4450158005
+ 33
+0.0
+ 0
+LINE
+ 5
+572
+330
+56D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+132.5499255838
+ 20
+147.8850158005
+ 30
+0.0
+ 11
+116.2299255838
+ 21
+147.8850158005
+ 31
+0.0
+ 0
+LINE
+ 5
+573
+330
+56D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+132.5499255838
+ 20
+148.4450158005
+ 30
+0.0
+ 11
+116.2299255838
+ 21
+148.4450158005
+ 31
+0.0
+ 0
+LINE
+ 5
+574
+330
+56D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+147.8850158005
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+141.8850158005
+ 31
+0.0
+ 0
+LINE
+ 5
+575
+330
+56D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+147.8850158005
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+148.4450158005
+ 31
+0.0
+ 0
+LINE
+ 5
+576
+330
+56D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+148.4450158005
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+160.0869130071
+ 31
+0.0
+ 0
+MTEXT
+ 5
+577
+330
+56D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+112.7181886692
+ 20
+154.6570518713
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+1
+ 7
+SLDTEXTSTYLE0
+ 11
+-7.044195017643634E-15
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+56F
+330
+56D
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+57A
+330
+579
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_11
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_11
+ 1
+
+ 0
+SOLID
+ 5
+57C
+330
+579
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+117.2299255838
+ 20
+224.4267253803
+ 30
+0.0
+ 11
+116.7299255838
+ 21
+221.4267253803
+ 31
+0.0
+ 12
+117.7299255838
+ 22
+221.4267253803
+ 32
+0.0
+ 13
+117.7299255838
+ 23
+221.4267253803
+ 33
+0.0
+ 0
+SOLID
+ 5
+57D
+330
+579
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+117.2299255838
+ 20
+224.9867253803
+ 30
+0.0
+ 11
+117.7299255838
+ 21
+227.9867253803
+ 31
+0.0
+ 12
+116.7299255838
+ 22
+227.9867253803
+ 32
+0.0
+ 13
+116.7299255838
+ 23
+227.9867253803
+ 33
+0.0
+ 0
+LINE
+ 5
+57E
+330
+579
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+132.5499255838
+ 20
+224.4267253803
+ 30
+0.0
+ 11
+116.2299255838
+ 21
+224.4267253803
+ 31
+0.0
+ 0
+LINE
+ 5
+57F
+330
+579
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+132.5499255838
+ 20
+224.9867253803
+ 30
+0.0
+ 11
+116.2299255838
+ 21
+224.9867253803
+ 31
+0.0
+ 0
+LINE
+ 5
+580
+330
+579
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+224.4267253803
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+218.4267253803
+ 31
+0.0
+ 0
+LINE
+ 5
+581
+330
+579
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+224.4267253803
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+224.9867253803
+ 31
+0.0
+ 0
+LINE
+ 5
+582
+330
+579
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+224.9867253803
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+239.2483956684
+ 31
+0.0
+ 0
+MTEXT
+ 5
+583
+330
+579
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+112.7181886692
+ 20
+233.8185345326
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+1
+ 7
+SLDTEXTSTYLE0
+ 11
+-7.044195017643634E-15
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+57B
+330
+579
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+586
+330
+585
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_12
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_12
+ 1
+
+ 0
+SOLID
+ 5
+588
+330
+585
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+117.2299255838
+ 20
+301.61905407
+ 30
+0.0
+ 11
+116.7299255838
+ 21
+298.61905407
+ 31
+0.0
+ 12
+117.7299255838
+ 22
+298.61905407
+ 32
+0.0
+ 13
+117.7299255838
+ 23
+298.61905407
+ 33
+0.0
+ 0
+SOLID
+ 5
+589
+330
+585
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+117.2299255838
+ 20
+302.17905407
+ 30
+0.0
+ 11
+117.7299255838
+ 21
+305.17905407
+ 31
+0.0
+ 12
+116.7299255838
+ 22
+305.17905407
+ 32
+0.0
+ 13
+116.7299255838
+ 23
+305.17905407
+ 33
+0.0
+ 0
+LINE
+ 5
+58A
+330
+585
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+132.5499255838
+ 20
+301.61905407
+ 30
+0.0
+ 11
+116.2299255838
+ 21
+301.61905407
+ 31
+0.0
+ 0
+LINE
+ 5
+58B
+330
+585
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+132.5499255838
+ 20
+302.17905407
+ 30
+0.0
+ 11
+116.2299255838
+ 21
+302.17905407
+ 31
+0.0
+ 0
+LINE
+ 5
+58C
+330
+585
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+301.61905407
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+295.61905407
+ 31
+0.0
+ 0
+LINE
+ 5
+58D
+330
+585
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+301.61905407
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+302.17905407
+ 31
+0.0
+ 0
+LINE
+ 5
+58E
+330
+585
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+302.17905407
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+314.5260224873
+ 31
+0.0
+ 0
+MTEXT
+ 5
+58F
+330
+585
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+112.7181886692
+ 20
+309.0961613515
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+1
+ 7
+SLDTEXTSTYLE0
+ 11
+-7.044195017643634E-15
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+587
+330
+585
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+ENDSEC
+ 0
+SECTION
+ 2
+ENTITIES
+ 0
+LINE
+ 5
+4B
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+123.9140543182
+ 20
+383.8493547742
+ 30
+0.0
+ 11
+160.598524565
+ 21
+383.8493547742
+ 31
+0.0
+ 0
+LINE
+ 5
+4C
+330
+1F
+100
+AcDbEntity
+ 8
+STUKLIJST
+100
+AcDbLine
+ 10
+10.3087825773
+ 20
+9.2198227322
+ 30
+0.0
+ 11
+10.3087825773
+ 21
+409.2698227322
+ 31
+0.0
+ 0
+LINE
+ 5
+4D
+330
+1F
+100
+AcDbEntity
+ 8
+STUKLIJST
+100
+AcDbLine
+ 10
+10.3087825773
+ 20
+9.2198227322
+ 30
+0.0
+ 11
+287.198438446
+ 21
+9.2198227322
+ 31
+0.0
+ 0
+LINE
+ 5
+4E
+330
+1F
+100
+AcDbEntity
+ 8
+STUKLIJST
+100
+AcDbLine
+ 10
+287.198438446
+ 20
+409.2698227322
+ 30
+0.0
+ 11
+287.198438446
+ 21
+9.2198227322
+ 31
+0.0
+ 0
+LINE
+ 5
+4F
+330
+1F
+100
+AcDbEntity
+ 8
+STUKLIJST
+100
+AcDbLine
+ 10
+287.198438446
+ 20
+409.2698227322
+ 30
+0.0
+ 11
+10.3087825773
+ 21
+409.2698227322
+ 31
+0.0
+ 0
+LINE
+ 5
+50
+330
+1F
+100
+AcDbEntity
+ 8
+MAATLIJN
+100
+AcDbLine
+ 10
+4.3087825773
+ 20
+3.2198227322
+ 30
+0.0
+ 11
+4.3087825773
+ 21
+415.2698227322
+ 31
+0.0
+ 0
+LINE
+ 5
+51
+330
+1F
+100
+AcDbEntity
+ 8
+MAATLIJN
+100
+AcDbLine
+ 10
+4.3087825773
+ 20
+415.2698227322
+ 30
+0.0
+ 11
+293.4090035115
+ 21
+415.2698227322
+ 31
+0.0
+ 0
+LINE
+ 5
+52
+330
+1F
+100
+AcDbEntity
+ 8
+MAATLIJN
+100
+AcDbLine
+ 10
+293.4090035115
+ 20
+415.2698227322
+ 30
+0.0
+ 11
+293.4090035115
+ 21
+3.2198227322
+ 31
+0.0
+ 0
+LINE
+ 5
+53
+330
+1F
+100
+AcDbEntity
+ 8
+MAATLIJN
+100
+AcDbLine
+ 10
+293.4090035115
+ 20
+3.2198227322
+ 30
+0.0
+ 11
+4.3087825773
+ 21
+3.2198227322
+ 31
+0.0
+ 0
+MTEXT
+ 5
+55
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+211.9617900851
+ 20
+18.9651870984
+ 30
+0.0
+ 40
+4.2333333333
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+Alum. deurblad - 4 secties
+ 7
+SLDTEXTSTYLE0
+ 73
+ 1
+ 44
+1.0
+ 0
+INSERT
+ 5
+59
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+100
+AcDbBlockReference
+ 2
+SW_BROKEN_VIEW_0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 0
+LINE
+ 5
+5A
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.9420049658
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+148.9420049658
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+5B
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.1445668807
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+149.1445668807
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+5C
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.0406432023
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+149.0406432023
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+5D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.0406432023
+ 20
+359.2202013088
+ 30
+0.0
+ 11
+148.9420049658
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+5E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.9137577285
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+133.9137577285
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+5F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.797846066
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+133.797846066
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+60
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.6992078294
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+133.6992078294
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+61
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.797846066
+ 20
+359.2202013088
+ 30
+0.0
+ 11
+133.6992078294
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+62
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.5952848961
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+133.5952848961
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+ARC
+ 5
+63
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.1879324767
+ 20
+359.1402151319
+ 30
+0.0
+ 40
+0.0799861772139263
+100
+AcDbArc
+ 50
+31.2190407028
+ 51
+90.0049728915
+ 0
+LINE
+ 5
+64
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.2563360204
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+149.2563360204
+ 21
+359.1816728664
+ 31
+0.0
+ 0
+LINE
+ 5
+65
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2162960043
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+134.2162960043
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+66
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2162960043
+ 20
+359.2202013088
+ 30
+0.0
+ 11
+134.1499254873
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+67
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.4299252202
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+133.4299252202
+ 21
+359.1907804561
+ 31
+0.0
+ 0
+ARC
+ 5
+68
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+127.793914781
+ 20
+346.5773729462
+ 30
+0.0
+ 40
+24.8898367051569
+100
+AcDbArc
+ 50
+30.3127339119
+ 51
+30.4217624082
+ 0
+LINE
+ 5
+69
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.3099258115
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+149.3099258115
+ 21
+359.1109102321
+ 31
+0.0
+ 0
+LINE
+ 5
+6A
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.2563360204
+ 20
+359.1816728664
+ 30
+0.0
+ 11
+149.2569536736
+ 21
+359.1806238247
+ 31
+0.0
+ 0
+LINE
+ 5
+6B
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2299253514
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+134.2299253514
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+6C
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2299253514
+ 20
+359.2202013088
+ 30
+0.0
+ 11
+134.2162960043
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+6D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.5099256804
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+148.5099256804
+ 21
+358.0259626461
+ 31
+0.0
+ 0
+LINE
+ 5
+6E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.6299254169
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+134.6299254169
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+6F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.6299254169
+ 20
+359.2202013088
+ 30
+0.0
+ 11
+134.2299253514
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+70
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.0299256762
+ 20
+359.2202013088
+ 30
+0.0
+ 11
+143.0299256762
+ 21
+353.6202257229
+ 31
+0.0
+ 0
+LINE
+ 5
+71
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.0299256762
+ 20
+353.6202257229
+ 30
+0.0
+ 11
+147.3280240615
+ 21
+353.6202257229
+ 31
+0.0
+ 0
+LINE
+ 5
+72
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+147.3280240615
+ 20
+353.6202257229
+ 30
+0.0
+ 11
+147.5545917471
+ 21
+354.4657533718
+ 31
+0.0
+ 0
+LINE
+ 5
+73
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+141.5499255826
+ 20
+359.2202013088
+ 30
+0.0
+ 11
+141.3099255843
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+74
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+138.9099255969
+ 20
+359.2202013088
+ 30
+0.0
+ 11
+134.6299254169
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+ARC
+ 5
+75
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+137.1595398236
+ 20
+361.9409267861
+ 30
+0.0
+ 40
+0.3992677940247622
+100
+AcDbArc
+ 50
+89.9681903289
+ 51
+114.7477262168
+ 0
+LINE
+ 5
+76
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+137.1597614904
+ 20
+362.3401945186
+ 30
+0.0
+ 11
+139.7899255922
+ 21
+362.3401945186
+ 31
+0.0
+ 0
+ARC
+ 5
+77
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+139.7899227961
+ 20
+361.9401917488
+ 30
+0.0
+ 40
+0.4000027697985571
+100
+AcDbArc
+ 50
+0.0045494665
+ 51
+89.9995994903
+ 0
+LINE
+ 5
+78
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+140.1899255646
+ 20
+361.9402235103
+ 30
+0.0
+ 11
+140.1899255646
+ 21
+360.4202097011
+ 31
+0.0
+ 0
+ARC
+ 5
+79
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+140.5899283325
+ 20
+360.4201937773
+ 30
+0.0
+ 40
+0.4000027681863966
+100
+AcDbArc
+ 50
+179.9977190938
+ 51
+269.999606256
+ 0
+LINE
+ 5
+7A
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+140.5899255836
+ 20
+360.0201910091
+ 30
+0.0
+ 11
+142.2699255703
+ 21
+360.0201910091
+ 31
+0.0
+ 0
+ARC
+ 5
+7B
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+142.2699229338
+ 20
+360.4201936183
+ 30
+0.0
+ 40
+0.4000026092087915
+100
+AcDbArc
+ 50
+270.0003776416
+ 51
+0.0023036787
+ 0
+LINE
+ 5
+7C
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+142.6699255427
+ 20
+360.4202097011
+ 30
+0.0
+ 11
+142.6699255427
+ 21
+361.9402235103
+ 31
+0.0
+ 0
+ARC
+ 5
+7D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+143.0699283112
+ 20
+361.9401917488
+ 30
+0.0
+ 40
+0.4000027697974622
+100
+AcDbArc
+ 50
+90.0003871694
+ 51
+179.9954505335
+ 0
+LINE
+ 5
+7E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.0699256082
+ 20
+362.3401945186
+ 30
+0.0
+ 11
+148.5899255445
+ 21
+362.3401945186
+ 31
+0.0
+ 0
+ARC
+ 5
+7F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5899336175
+ 20
+362.4601862448
+ 30
+0.0
+ 40
+0.1199917263967713
+100
+AcDbArc
+ 50
+269.9961451292
+ 51
+0.0134590356
+ 0
+LINE
+ 5
+80
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.7099253406
+ 20
+362.4602144314
+ 30
+0.0
+ 11
+148.7099253406
+ 21
+366.669732864
+ 31
+0.0
+ 0
+ARC
+ 5
+81
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5098175737
+ 20
+366.669705415
+ 30
+0.0
+ 40
+0.2001077687858761
+100
+AcDbArc
+ 50
+0.0078593224
+ 51
+46.1736252111
+ 0
+ARC
+ 5
+82
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.229892261
+ 20
+367.4202030254
+ 30
+0.0
+ 40
+0.8399663770856335
+100
+AcDbArc
+ 50
+133.8120979057
+ 51
+226.1879020943
+ 0
+ARC
+ 5
+83
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5098175737
+ 20
+368.1707006358
+ 30
+0.0
+ 40
+0.200107768785933
+100
+AcDbArc
+ 50
+313.8263747889
+ 51
+359.9921406775
+ 0
+LINE
+ 5
+84
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.7099253406
+ 20
+368.1706731868
+ 30
+0.0
+ 11
+148.7099253406
+ 21
+370.3305070949
+ 31
+0.0
+ 0
+ARC
+ 5
+85
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5900202217
+ 20
+370.3305234822
+ 30
+0.0
+ 40
+0.1199051200450481
+100
+AcDbArc
+ 50
+359.9921694514
+ 51
+45.0183972297
+ 0
+LINE
+ 5
+86
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.6747787168
+ 20
+370.4153364254
+ 30
+0.0
+ 11
+148.5050731172
+ 21
+370.58504277
+ 31
+0.0
+ 0
+ARC
+ 5
+87
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5900489089
+ 20
+370.6699467703
+ 30
+0.0
+ 40
+0.1201231635795685
+100
+AcDbArc
+ 50
+180.0128717119
+ 51
+224.9757867508
+ 0
+LINE
+ 5
+88
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.4699257483
+ 20
+370.6699197841
+ 30
+0.0
+ 11
+148.4699257483
+ 21
+371.770507629
+ 31
+0.0
+ 0
+ARC
+ 5
+89
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5898274113
+ 20
+371.7705247037
+ 30
+0.0
+ 40
+0.1199016642176248
+100
+AcDbArc
+ 50
+134.9804150373
+ 51
+180.0081592821
+ 0
+LINE
+ 5
+8A
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.5050731172
+ 20
+371.8553369594
+ 30
+0.0
+ 11
+148.6747787168
+ 21
+372.025043304
+ 31
+0.0
+ 0
+ARC
+ 5
+8B
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5897987133
+ 20
+372.1099479939
+ 30
+0.0
+ 40
+0.1201266304909027
+100
+AcDbArc
+ 50
+315.0254004562
+ 51
+359.9867997393
+ 0
+LINE
+ 5
+8C
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.7099253406
+ 20
+372.1099203182
+ 30
+0.0
+ 11
+148.7099253406
+ 21
+374.2697542263
+ 31
+0.0
+ 0
+ARC
+ 5
+8D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5100322735
+ 20
+374.2697706431
+ 30
+0.0
+ 40
+0.1998930677679371
+100
+AcDbArc
+ 50
+359.9952944114
+ 51
+46.1998513567
+ 0
+ARC
+ 5
+8E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.229948176
+ 20
+375.0202005459
+ 30
+0.0
+ 40
+0.840022291756649
+100
+AcDbArc
+ 50
+133.8137242063
+ 51
+226.1862757937
+ 0
+ARC
+ 5
+8F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5100322233
+ 20
+375.7706304833
+ 30
+0.0
+ 40
+0.1998931275890775
+100
+AcDbArc
+ 50
+313.8001521475
+ 51
+0.018363319
+ 0
+LINE
+ 5
+90
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.7099253406
+ 20
+375.7706945491
+ 30
+0.0
+ 11
+148.7099253406
+ 21
+392.9185693813
+ 31
+0.0
+ 0
+ARC
+ 5
+91
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.3098818552
+ 20
+392.9185180141
+ 30
+0.0
+ 40
+0.4000434887682899
+100
+AcDbArc
+ 50
+0.0073570038
+ 51
+106.4508778333
+ 0
+ARC
+ 5
+92
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+147.6299231955
+ 20
+395.2202061714
+ 30
+0.0
+ 40
+1.999979879929223
+100
+AcDbArc
+ 50
+230.0551007785
+ 51
+286.4594915024
+ 0
+ARC
+ 5
+93
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+146.0888728608
+ 20
+393.3797645665
+ 30
+0.0
+ 40
+0.4004495854783311
+100
+AcDbArc
+ 50
+50.0825069136
+ 51
+89.9794164413
+ 0
+LINE
+ 5
+94
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+146.0890167227
+ 20
+393.7802141262
+ 30
+0.0
+ 11
+136.7708344778
+ 21
+393.7802141262
+ 31
+0.0
+ 0
+ARC
+ 5
+95
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+136.7709783396
+ 20
+393.3797645665
+ 30
+0.0
+ 40
+0.4004495854783879
+100
+AcDbArc
+ 50
+90.0205835587
+ 51
+129.9174930864
+ 0
+ARC
+ 5
+96
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+135.2299278051
+ 20
+395.220206584
+ 30
+0.0
+ 40
+1.999980324488907
+100
+AcDbArc
+ 50
+253.5405071009
+ 51
+309.9448960222
+ 0
+ARC
+ 5
+97
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.5499689727
+ 20
+392.9185180141
+ 30
+0.0
+ 40
+0.4000434887683444
+100
+AcDbArc
+ 50
+73.5491221667
+ 51
+179.9926429962
+ 0
+LINE
+ 5
+98
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.1499254873
+ 20
+392.9185693813
+ 30
+0.0
+ 11
+134.1499254873
+ 21
+361.3064115596
+ 31
+0.0
+ 0
+ARC
+ 5
+99
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.3499114332
+ 20
+361.3063661517
+ 30
+0.0
+ 40
+0.1999859510592641
+100
+AcDbArc
+ 50
+179.9869906558
+ 51
+294.7406415951
+ 0
+LINE
+ 5
+9A
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.4336078336
+ 20
+361.1247366024
+ 30
+0.0
+ 11
+136.9923968633
+ 21
+362.3035257412
+ 31
+0.0
+ 0
+ARC
+ 5
+9B
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+135.2299441549
+ 20
+395.2202094278
+ 30
+0.0
+ 40
+1.999982807792111
+100
+AcDbArc
+ 50
+346.3277484711
+ 51
+21.510375133
+ 0
+ARC
+ 5
+9C
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+137.3676151968
+ 20
+394.7002149549
+ 30
+0.0
+ 40
+0.2000244053689593
+100
+AcDbArc
+ 50
+166.3324477609
+ 51
+269.9922055192
+ 0
+LINE
+ 5
+9D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+145.4922632148
+ 20
+394.5001905513
+ 30
+0.0
+ 11
+137.3675879856
+ 21
+394.5001905513
+ 31
+0.0
+ 0
+ARC
+ 5
+9E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+145.4922360036
+ 20
+394.7002149549
+ 30
+0.0
+ 40
+0.2000244053689451
+100
+AcDbArc
+ 50
+270.0077944808
+ 51
+13.6675522392
+ 0
+ARC
+ 5
+9F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+147.6299070455
+ 20
+395.2202094278
+ 30
+0.0
+ 40
+1.999982807792221
+100
+AcDbArc
+ 50
+158.489624867
+ 51
+193.6722515289
+ 0
+ARC
+ 5
+A0
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+145.3970883777
+ 20
+396.1002000547
+ 30
+0.0
+ 40
+0.3999885893651534
+100
+AcDbArc
+ 50
+338.4905744645
+ 51
+90.0012269037
+ 0
+LINE
+ 5
+A1
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.9099256714
+ 20
+396.500188644
+ 30
+0.0
+ 11
+145.3970798125
+ 21
+396.500188644
+ 31
+0.0
+ 0
+ARC
+ 5
+A2
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+143.9099748178
+ 20
+396.5802378337
+ 30
+0.0
+ 40
+0.0800492048011358
+100
+AcDbArc
+ 50
+180.0257061046
+ 51
+269.9648231328
+ 0
+LINE
+ 5
+A3
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.8299256211
+ 20
+397.1401994777
+ 30
+0.0
+ 11
+143.8299256211
+ 21
+396.5802019191
+ 31
+0.0
+ 0
+ARC
+ 5
+A4
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+143.9098934092
+ 20
+397.1402449584
+ 30
+0.0
+ 40
+0.0799678010352043
+100
+AcDbArc
+ 50
+89.9768845512
+ 51
+180.0325862115
+ 0
+LINE
+ 5
+A5
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+145.8378587683
+ 20
+397.2202127529
+ 30
+0.0
+ 11
+143.9099256714
+ 21
+397.2202127529
+ 31
+0.0
+ 0
+ARC
+ 5
+A6
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+145.8378250788
+ 20
+396.8201414766
+ 30
+0.0
+ 40
+0.4000712776531297
+100
+AcDbArc
+ 50
+35.0061485493
+ 51
+89.9951751956
+ 0
+LINE
+ 5
+A7
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+146.7657630166
+ 20
+396.1923902584
+ 30
+0.0
+ 11
+146.1655196567
+ 21
+397.0496481014
+ 31
+0.0
+ 0
+ARC
+ 5
+A8
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+146.667516349
+ 20
+396.1235761057
+ 30
+0.0
+ 40
+0.1199491362526079
+100
+AcDbArc
+ 50
+316.8039486115
+ 51
+35.0082542764
+ 0
+ARC
+ 5
+A9
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+147.6299254989
+ 20
+395.2202090721
+ 30
+0.0
+ 40
+1.200014262087242
+100
+AcDbArc
+ 50
+136.8133643338
+ 51
+43.1866356662
+ 0
+ARC
+ 5
+AA
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5923363035
+ 20
+396.1235762241
+ 30
+0.0
+ 40
+0.1199504236850838
+100
+AcDbArc
+ 50
+144.9922455228
+ 51
+223.1955515893
+ 0
+LINE
+ 5
+AB
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.1713189354
+ 20
+397.1596067501
+ 30
+0.0
+ 11
+148.4940879812
+ 21
+396.1923902584
+ 31
+0.0
+ 0
+ARC
+ 5
+AC
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.2877619365
+ 20
+397.0780497932
+ 30
+0.0
+ 40
+0.1421636723032836
+100
+AcDbArc
+ 50
+0.0071750237
+ 51
+144.9925091982
+ 0
+LINE
+ 5
+AD
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.4299256077
+ 20
+375.5001848293
+ 30
+0.0
+ 11
+149.4299256077
+ 21
+397.078067596
+ 31
+0.0
+ 0
+ARC
+ 5
+AE
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.0299484881
+ 20
+375.500185302
+ 30
+0.0
+ 40
+0.3999771195635114
+100
+AcDbArc
+ 50
+292.617691446
+ 51
+359.9999322798
+ 0
+ARC
+ 5
+AF
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.2299252955
+ 20
+375.0202005459
+ 30
+0.0
+ 40
+0.1199998892853004
+100
+AcDbArc
+ 50
+112.6196870788
+ 51
+247.3803129212
+ 0
+ARC
+ 5
+B0
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.0299484881
+ 20
+374.5402157897
+ 30
+0.0
+ 40
+0.3999771195634261
+100
+AcDbArc
+ 50
+0.0000677202
+ 51
+67.382308554
+ 0
+LINE
+ 5
+B1
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.4299256077
+ 20
+371.973878677
+ 30
+0.0
+ 11
+149.4299256077
+ 21
+374.5402162624
+ 31
+0.0
+ 0
+ARC
+ 5
+B2
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.3098934127
+ 20
+371.9739249372
+ 30
+0.0
+ 40
+0.1200322038601951
+100
+AcDbArc
+ 50
+284.0483064245
+ 51
+359.9779182862
+ 0
+LINE
+ 5
+B3
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.2808216085
+ 20
+371.8429391933
+ 30
+0.0
+ 11
+149.3390300145
+ 21
+371.8574827266
+ 31
+0.0
+ 0
+ARC
+ 5
+B4
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.309957026
+ 20
+371.7264971484
+ 30
+0.0
+ 40
+0.1200317557485873
+100
+AcDbArc
+ 50
+104.0477772241
+ 51
+180.000758526
+ 0
+LINE
+ 5
+B5
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.1899252703
+ 20
+370.7138841701
+ 30
+0.0
+ 11
+149.1899252703
+ 21
+371.7264955593
+ 31
+0.0
+ 0
+ARC
+ 5
+B6
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.3099570381
+ 20
+370.7139302833
+ 30
+0.0
+ 40
+0.120031776642215
+ 0
+ARC
+ 5
+46F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.3098818552
+ 20
+46.4038720687
+ 30
+0.0
+ 40
+0.4000434887682782
+100
+AcDbArc
+ 50
+253.5491221667
+ 51
+359.9926429962
+ 0
+LINE
+ 5
+470
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.7099253406
+ 20
+63.5516955336
+ 30
+0.0
+ 11
+148.7099253406
+ 21
+46.4038207015
+ 31
+0.0
+ 0
+ARC
+ 5
+471
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5098175737
+ 20
+63.5516680847
+ 30
+0.0
+ 40
+0.2001077687861043
+100
+AcDbArc
+ 50
+0.0078593225
+ 51
+46.1736252111
+ 0
+ARC
+ 5
+472
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.229948176
+ 20
+64.3021895369
+ 30
+0.0
+ 40
+0.8400222917564932
+100
+AcDbArc
+ 50
+133.8137242063
+ 51
+226.1862757937
+ 0
+ARC
+ 5
+473
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5100322735
+ 20
+65.0526194397
+ 30
+0.0
+ 40
+0.1998930677682871
+100
+AcDbArc
+ 50
+313.8001486433
+ 51
+0.0047055886
+ 0
+LINE
+ 5
+474
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.7099253406
+ 20
+67.2124697646
+ 30
+0.0
+ 11
+148.7099253406
+ 21
+65.0526358565
+ 31
+0.0
+ 0
+ARC
+ 5
+475
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5897987133
+ 20
+67.2124420889
+ 30
+0.0
+ 40
+0.1201266304911527
+100
+AcDbArc
+ 50
+0.0132002608
+ 51
+44.9745995438
+ 0
+LINE
+ 5
+476
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.5050731172
+ 20
+67.4670531234
+ 30
+0.0
+ 11
+148.6747787168
+ 21
+67.2973467788
+ 31
+0.0
+ 0
+ARC
+ 5
+477
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5898274113
+ 20
+67.5518653791
+ 30
+0.0
+ 40
+0.1199016642178358
+100
+AcDbArc
+ 50
+179.9918407179
+ 51
+225.0195849627
+ 0
+LINE
+ 5
+478
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.4699257483
+ 20
+68.6524702987
+ 30
+0.0
+ 11
+148.4699257483
+ 21
+67.5518824538
+ 31
+0.0
+ 0
+ARC
+ 5
+479
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5900489089
+ 20
+68.6524433125
+ 30
+0.0
+ 40
+0.1201231635794379
+100
+AcDbArc
+ 50
+135.0242132492
+ 51
+179.9871282881
+ 0
+LINE
+ 5
+47A
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.6747787168
+ 20
+68.9070536574
+ 30
+0.0
+ 11
+148.5050731172
+ 21
+68.7373473128
+ 31
+0.0
+ 0
+ARC
+ 5
+47B
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5900202217
+ 20
+68.9918666006
+ 30
+0.0
+ 40
+0.1199051200450481
+100
+AcDbArc
+ 50
+314.9816027702
+ 51
+0.0078305486
+ 0
+LINE
+ 5
+47C
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.7099253406
+ 20
+71.151716896
+ 30
+0.0
+ 11
+148.7099253406
+ 21
+68.9918829879
+ 31
+0.0
+ 0
+ARC
+ 5
+47D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5098175737
+ 20
+71.151689447
+ 30
+0.0
+ 40
+0.200107768785911
+100
+AcDbArc
+ 50
+0.0078593225
+ 51
+46.1736252111
+ 0
+ARC
+ 5
+47E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.229892261
+ 20
+71.9021870574
+ 30
+0.0
+ 40
+0.8399663770856326
+100
+AcDbArc
+ 50
+133.8120979057
+ 51
+226.1879020943
+ 0
+ARC
+ 5
+47F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5098175737
+ 20
+72.6526846678
+ 30
+0.0
+ 40
+0.200107768785933
+100
+AcDbArc
+ 50
+313.8263747889
+ 51
+359.9921406776
+ 0
+LINE
+ 5
+480
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.7099253406
+ 20
+76.8621756514
+ 30
+0.0
+ 11
+148.7099253406
+ 21
+72.6526572188
+ 31
+0.0
+ 0
+ARC
+ 5
+481
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5899336175
+ 20
+76.862203838
+ 30
+0.0
+ 40
+0.1199917263968139
+100
+AcDbArc
+ 50
+359.9865409644
+ 51
+90.0038548708
+ 0
+LINE
+ 5
+482
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.0699256082
+ 20
+76.9821955642
+ 30
+0.0
+ 11
+148.5899255445
+ 21
+76.9821955642
+ 31
+0.0
+ 0
+ARC
+ 5
+483
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+143.0699283112
+ 20
+77.382198334
+ 30
+0.0
+ 40
+0.4000027697974338
+100
+AcDbArc
+ 50
+180.0045494665
+ 51
+269.9996128306
+ 0
+LINE
+ 5
+484
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+142.6699255427
+ 20
+78.9021803817
+ 30
+0.0
+ 11
+142.6699255427
+ 21
+77.3821665725
+ 31
+0.0
+ 0
+ARC
+ 5
+485
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+142.2699229338
+ 20
+78.9021964645
+ 30
+0.0
+ 40
+0.4000026092089052
+100
+AcDbArc
+ 50
+359.9976963213
+ 51
+89.9996223584
+ 0
+LINE
+ 5
+486
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+140.5899255836
+ 20
+79.3021990737
+ 30
+0.0
+ 11
+142.2699255703
+ 21
+79.3021990737
+ 31
+0.0
+ 0
+ARC
+ 5
+487
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+140.5899283325
+ 20
+78.9021963055
+ 30
+0.0
+ 40
+0.4000027681864251
+100
+AcDbArc
+ 50
+90.000393744
+ 51
+180.0022809062
+ 0
+LINE
+ 5
+488
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+140.1899255646
+ 20
+77.3821665725
+ 30
+0.0
+ 11
+140.1899255646
+ 21
+78.9021803817
+ 31
+0.0
+ 0
+ARC
+ 5
+489
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+139.7899227961
+ 20
+77.382198334
+ 30
+0.0
+ 40
+0.400002769798557
+100
+AcDbArc
+ 50
+270.0004005097
+ 51
+359.9954505335
+ 0
+LINE
+ 5
+48A
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+137.1597614904
+ 20
+76.9821955642
+ 30
+0.0
+ 11
+139.7899255922
+ 21
+76.9821955642
+ 31
+0.0
+ 0
+ARC
+ 5
+48B
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+137.1595398236
+ 20
+77.3814632967
+ 30
+0.0
+ 40
+0.3992677940260214
+100
+AcDbArc
+ 50
+245.2522737832
+ 51
+270.0318096711
+ 0
+LINE
+ 5
+48C
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.4336078336
+ 20
+78.1976534804
+ 30
+0.0
+ 11
+136.9923968633
+ 21
+77.0188643416
+ 31
+0.0
+ 0
+ARC
+ 5
+48D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.3499584368
+ 20
+78.0159505357
+ 30
+0.0
+ 40
+0.2000329515294041
+100
+AcDbArc
+ 50
+65.2803842384
+ 51
+179.9919835107
+ 0
+LINE
+ 5
+48E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+137.4627713879
+ 20
+42.8222014388
+ 30
+0.0
+ 11
+138.949925529
+ 21
+42.8222014388
+ 31
+0.0
+ 0
+ARC
+ 5
+48F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+138.9498760641
+ 20
+42.7421519306
+ 30
+0.0
+ 40
+0.0800495234356318
+100
+AcDbArc
+ 50
+0.0259339259
+ 51
+89.9645953115
+ 0
+LINE
+ 5
+490
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+139.0299255793
+ 20
+42.7421881637
+ 30
+0.0
+ 11
+139.0299255793
+ 21
+42.1821906051
+ 31
+0.0
+ 0
+ARC
+ 5
+491
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+138.9499574737
+ 20
+42.182145442
+ 30
+0.0
+ 40
+0.0799681184311334
+100
+AcDbArc
+ 50
+269.9771121806
+ 51
+0.032358582
+ 0
+LINE
+ 5
+492
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+138.949925529
+ 20
+42.1021773299
+ 30
+0.0
+ 11
+137.0219920596
+ 21
+42.1021773299
+ 31
+0.0
+ 0
+ARC
+ 5
+493
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+137.0220261217
+ 20
+42.5022486063
+ 30
+0.0
+ 40
+0.400071277792755
+100
+AcDbArc
+ 50
+215.0061485542
+ 51
+269.9951218307
+ 0
+LINE
+ 5
+494
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+136.6943315437
+ 20
+42.2727419814
+ 30
+0.0
+ 11
+136.0940881839
+ 21
+43.1299998244
+ 31
+0.0
+ 0
+ARC
+ 5
+495
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+136.1923348514
+ 20
+43.1988139771
+ 30
+0.0
+ 40
+0.1199491362525765
+100
+AcDbArc
+ 50
+136.8039486115
+ 51
+215.0082542764
+ 0
+ARC
+ 5
+496
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+135.2299255153
+ 20
+44.1021809094
+ 30
+0.0
+ 40
+1.20001432860059
+100
+AcDbArc
+ 50
+316.8133739453
+ 51
+223.1866260547
+ 0
+ARC
+ 5
+497
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.2675178339
+ 20
+43.1988140955
+ 30
+0.0
+ 40
+0.1199478488608696
+100
+AcDbArc
+ 50
+324.9912459259
+ 51
+43.1965511861
+ 0
+LINE
+ 5
+498
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.3657628467
+ 20
+43.1299998244
+ 30
+0.0
+ 11
+133.6885318925
+ 21
+42.1627833327
+ 31
+0.0
+ 0
+ARC
+ 5
+499
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+133.5720888914
+ 20
+42.2443402896
+ 30
+0.0
+ 40
+0.1421636723032335
+100
+AcDbArc
+ 50
+180.0071750236
+ 51
+324.9925091983
+ 0
+LINE
+ 5
+49A
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.4299252202
+ 20
+42.2443224868
+ 30
+0.0
+ 11
+133.4299252202
+ 21
+79.6323134383
+ 31
+0.0
+ 0
+ARC
+ 5
+49B
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+133.0298319476
+ 20
+79.6322623339
+ 30
+0.0
+ 40
+0.4000932758665615
+100
+AcDbArc
+ 50
+0.0073184602
+ 51
+52.0135000174
+ 0
+ARC
+ 5
+49C
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+133.3499255703
+ 20
+80.0421799816
+ 30
+0.0
+ 40
+0.1199955680613621
+100
+AcDbArc
+ 50
+44.424867041
+ 51
+232.0186299389
+ 0
+ARC
+ 5
+49D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+133.4927816435
+ 20
+80.1822896055
+ 30
+0.0
+ 40
+0.080100836150903
+100
+AcDbArc
+ 50
+224.4724320243
+ 51
+269.9805674286
+ 0
+LINE
+ 5
+49E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.4927544763
+ 20
+80.102188774
+ 30
+0.0
+ 11
+134.1499254873
+ 21
+80.102188774
+ 31
+0.0
+ 0
+ARC
+ 5
+49F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.1499294027
+ 20
+80.0221932431
+ 30
+0.0
+ 40
+0.0799955310303124
+100
+AcDbArc
+ 50
+344.0015913282
+ 51
+90.0028043747
+ 0
+LINE
+ 5
+4A0
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2268266549
+ 20
+80.0001456221
+ 30
+0.0
+ 11
+134.042500781
+ 21
+79.3573214492
+ 31
+0.0
+ 0
+ARC
+ 5
+4A1
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.2347360924
+ 20
+79.3021720325
+ 30
+0.0
+ 40
+0.1999896824440735
+100
+AcDbArc
+ 50
+163.9926116413
+ 51
+245.2696135787
+ 0
+LINE
+ 5
+4A2
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.1510706415
+ 20
+79.1205241164
+ 30
+0.0
+ 11
+137.1502664705
+ 21
+77.7388884505
+ 31
+0.0
+ 0
+ARC
+ 5
+4A3
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+137.317737162
+ 20
+78.1024627412
+ 30
+0.0
+ 40
+0.4002907660301162
+100
+AcDbArc
+ 50
+245.2681140451
+ 51
+269.98476511
+ 0
+LINE
+ 5
+4A4
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+137.3176307251
+ 20
+77.7021719893
+ 30
+0.0
+ 11
+139.1499255618
+ 21
+77.7021719893
+ 31
+0.0
+ 0
+ARC
+ 5
+4A5
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+139.1499227005
+ 20
+78.1021749165
+ 30
+0.0
+ 40
+0.4000029271641257
+100
+AcDbArc
+ 50
+270.0004098464
+ 51
+0.0022581337
+ 0
+LINE
+ 5
+4A6
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+139.5499256273
+ 20
+78.1021906814
+ 30
+0.0
+ 11
+139.5499256273
+ 21
+78.9021803817
+ 31
+0.0
+ 0
+ARC
+ 5
+4A7
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+139.1499227005
+ 20
+78.9021961465
+ 30
+0.0
+ 40
+0.4000029271641541
+100
+AcDbArc
+ 50
+359.9977418663
+ 51
+89.9995901536
+ 0
+LINE
+ 5
+4A8
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+139.1499255618
+ 20
+79.3021990737
+ 30
+0.0
+ 11
+138.9099255969
+ 21
+79.3021990737
+ 31
+0.0
+ 0
+ARC
+ 5
+4A9
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+138.9099203808
+ 20
+79.7021939238
+ 30
+0.0
+ 40
+0.3999948501927155
+100
+AcDbArc
+ 50
+89.9992528415
+ 51
+270.0007471585
+ 0
+LINE
+ 5
+4AA
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+138.9099255969
+ 20
+80.102188774
+ 30
+0.0
+ 11
+141.3099255843
+ 21
+80.102188774
+ 31
+0.0
+ 0
+ARC
+ 5
+4AB
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+141.4299255835
+ 20
+80.1021688621
+ 30
+0.0
+ 40
+0.1200000008033507
+100
+AcDbArc
+ 50
+179.9904927603
+ 51
+0.0095072397
+ 0
+LINE
+ 5
+4AC
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+141.5499255826
+ 20
+80.102188774
+ 30
+0.0
+ 11
+144.2299255004
+ 21
+80.102188774
+ 31
+0.0
+ 0
+ARC
+ 5
+4AD
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+144.2299329932
+ 20
+80.2221814513
+ 30
+0.0
+ 40
+0.1199926775530223
+100
+AcDbArc
+ 50
+269.9964221996
+ 51
+359.9902360528
+ 0
+LINE
+ 5
+4AE
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+144.349925669
+ 20
+80.222161003
+ 30
+0.0
+ 11
+144.349925669
+ 21
+80.3821875533
+ 31
+0.0
+ 0
+ARC
+ 5
+4AF
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+144.4699183448
+ 20
+80.382167105
+ 30
+0.0
+ 40
+0.1199926775144228
+100
+AcDbArc
+ 50
+89.9966000615
+ 51
+179.9902360605
+ 0
+LINE
+ 5
+4B0
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 99255659
+ 21
+158.8450193141
+ 31
+0.0
+ 0
+ARC
+ 5
+366
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+144.629912391
+ 20
+158.7250059711
+ 30
+0.0
+ 40
+0.1200133437027802
+100
+AcDbArc
+ 50
+0.0025547204
+ 51
+89.9937101469
+ 0
+LINE
+ 5
+367
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+144.7499257346
+ 20
+158.7250113223
+ 30
+0.0
+ 11
+144.7499257346
+ 21
+158.445012543
+ 31
+0.0
+ 0
+ARC
+ 5
+368
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+143.9499198813
+ 20
+158.445016775
+ 30
+0.0
+ 40
+0.8000058532867297
+100
+AcDbArc
+ 50
+270.0004098155
+ 51
+359.9996969061
+ 0
+LINE
+ 5
+369
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.9499256035
+ 20
+157.6450109217
+ 30
+0.0
+ 11
+143.7099256386
+ 21
+157.6450109217
+ 31
+0.0
+ 0
+ARC
+ 5
+36A
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+143.7099078321
+ 20
+157.2450286629
+ 30
+0.0
+ 40
+0.3999822592042693
+100
+AcDbArc
+ 50
+89.9974492852
+ 51
+180.0018036581
+ 0
+LINE
+ 5
+36B
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.3099255731
+ 20
+157.2450160716
+ 30
+0.0
+ 11
+143.3099255731
+ 21
+156.4450144503
+ 31
+0.0
+ 0
+ARC
+ 5
+36C
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+143.7099281818
+ 20
+156.4450222089
+ 30
+0.0
+ 40
+0.4000026087843038
+100
+AcDbArc
+ 50
+180.0011113328
+ 51
+269.9996357238
+ 0
+LINE
+ 5
+36D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.7099256386
+ 20
+156.0450196002
+ 30
+0.0
+ 11
+148.7099253406
+ 21
+156.0450196002
+ 31
+0.0
+ 0
+ARC
+ 5
+36E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.7099328893
+ 20
+156.1650125925
+ 30
+0.0
+ 40
+0.1199929926174386
+100
+AcDbArc
+ 50
+269.9963955831
+ 51
+0.0014699526
+ 0
+LINE
+ 5
+36F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.8299258818
+ 20
+156.165015671
+ 30
+0.0
+ 11
+148.8299258818
+ 21
+157.5693249438
+ 31
+0.0
+ 0
+ARC
+ 5
+370
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.7098943691
+ 20
+157.569311977
+ 30
+0.0
+ 40
+0.1200315134707315
+100
+AcDbArc
+ 50
+0.0061895736
+ 51
+64.4733233418
+ 0
+LINE
+ 5
+371
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.761619704
+ 20
+157.6776265834
+ 30
+0.0
+ 11
+148.4608449807
+ 21
+157.8211903308
+ 31
+0.0
+ 0
+ARC
+ 5
+372
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5125576602
+ 20
+157.9295010743
+ 30
+0.0
+ 40
+0.1200225744513657
+100
+AcDbArc
+ 50
+195.0053952559
+ 51
+244.4779803301
+ 0
+LINE
+ 5
+373
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.3966276815
+ 20
+157.8984260295
+ 30
+0.0
+ 11
+147.6108663549
+ 21
+160.8309268687
+ 31
+0.0
+ 0
+ARC
+ 5
+374
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+147.1754788065
+ 20
+160.7142774394
+ 30
+0.0
+ 40
+0.4507431714908047
+100
+AcDbArc
+ 50
+14.9984882143
+ 51
+171.0010622237
+ 0
+LINE
+ 5
+375
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+146.7302837243
+ 20
+160.7847809527
+ 30
+0.0
+ 11
+146.7030935754
+ 21
+160.6131076548
+ 31
+0.0
+ 0
+ARC
+ 5
+376
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+146.5055559119
+ 20
+160.644393989
+ 30
+0.0
+ 40
+0.1999999080912547
+100
+AcDbArc
+ 50
+198.0001632172
+ 51
+351.0001579124
+ 0
+LINE
+ 5
+377
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+146.3153448721
+ 20
+160.5825900767
+ 30
+0.0
+ 11
+145.8463118245
+ 21
+162.0261311267
+ 31
+0.0
+ 0
+ARC
+ 5
+378
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+146.1506496208
+ 20
+162.1250192095
+ 30
+0.0
+ 40
+0.3200005424109761
+100
+AcDbArc
+ 50
+23.0000921291
+ 51
+198.0004663253
+ 0
+LINE
+ 5
+379
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+146.4452114721
+ 20
+162.2500538562
+ 30
+0.0
+ 11
+146.5646275153
+ 21
+161.9687199328
+ 31
+0.0
+ 0
+ARC
+ 5
+37A
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+146.9328278996
+ 20
+162.1250205588
+ 30
+0.0
+ 40
+0.4000017608535774
+100
+AcDbArc
+ 50
+203.0011649826
+ 51
+14.9999803862
+ 0
+LINE
+ 5
+37B
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+147.3191999664
+ 20
+162.2285485003
+ 30
+0.0
+ 11
+147.1380212178
+ 21
+162.9047155116
+ 31
+0.0
+ 0
+ARC
+ 5
+37C
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+147.2539203196
+ 20
+162.9357712365
+ 30
+0.0
+ 40
+0.1199877487818145
+100
+AcDbArc
+ 50
+127.997310757
+ 51
+195.0003017269
+ 0
+LINE
+ 5
+37D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+147.1800529232
+ 20
+163.03032634
+ 30
+0.0
+ 11
+147.4331305196
+ 21
+163.2280587886
+ 31
+0.0
+ 0
+ARC
+ 5
+37E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+147.482384254
+ 20
+163.1650243775
+ 30
+0.0
+ 40
+0.0799954206548413
+100
+AcDbArc
+ 50
+14.9903109854
+ 51
+128.0033046167
+ 0
+LINE
+ 5
+37F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+147.5596573969
+ 20
+163.1857156489
+ 30
+0.0
+ 11
+148.8140375187
+ 21
+158.5043072436
+ 31
+0.0
+ 0
+ARC
+ 5
+380
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.8913172899
+ 20
+158.5250198997
+ 30
+0.0
+ 40
+0.0800073569232727
+100
+AcDbArc
+ 50
+195.0038763407
+ 51
+269.9961833848
+ 0
+LINE
+ 5
+381
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.1546171179
+ 20
+157.4790596697
+ 30
+0.0
+ 11
+137.2058146467
+ 21
+156.0562610362
+ 31
+0.0
+ 0
+LINE
+ 5
+382
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.7197578718
+ 20
+138.1291627619
+ 30
+0.0
+ 11
+148.1495865454
+ 21
+138.0822777484
+ 31
+0.0
+ 0
+ARC
+ 5
+383
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.7099336428
+ 20
+138.2487521509
+ 30
+0.0
+ 40
+0.1199922390625343
+100
+AcDbArc
+ 50
+274.6962840302
+ 51
+0.000654307
+ 0
+LINE
+ 5
+384
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.8299258818
+ 20
+155.2850126956
+ 30
+0.0
+ 11
+148.8299258818
+ 21
+138.2487535212
+ 31
+0.0
+ 0
+ARC
+ 5
+385
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.7099125383
+ 20
+155.2850073444
+ 30
+0.0
+ 40
+0.1200133436339415
+100
+AcDbArc
+ 50
+0.0025547068
+ 51
+89.9938880295
+ 0
+LINE
+ 5
+386
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.0699256082
+ 20
+155.4050206874
+ 30
+0.0
+ 11
+148.7099253406
+ 21
+155.4050206874
+ 31
+0.0
+ 0
+ARC
+ 5
+387
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+143.0699079607
+ 20
+155.8050031051
+ 30
+0.0
+ 40
+0.3999824181675279
+100
+AcDbArc
+ 50
+179.9982191144
+ 51
+270.0025279423
+ 0
+LINE
+ 5
+388
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+142.6699255427
+ 20
+157.2450160716
+ 30
+0.0
+ 11
+142.6699255427
+ 21
+155.8050155375
+ 31
+0.0
+ 0
+ARC
+ 5
+389
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+142.2699432837
+ 20
+157.2450286629
+ 30
+0.0
+ 40
+0.3999822591972353
+100
+AcDbArc
+ 50
+359.9981963414
+ 51
+90.002537375
+ 0
+LINE
+ 5
+38A
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+141.7499255688
+ 20
+157.6450109217
+ 30
+0.0
+ 11
+142.2699255703
+ 21
+157.6450109217
+ 31
+0.0
+ 0
+ARC
+ 5
+38B
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+141.6299255748
+ 20
+157.645014845
+ 30
+0.0
+ 40
+0.1199999941223236
+100
+AcDbArc
+ 50
+180.001873219
+ 51
+359.998126781
+ 0
+LINE
+ 5
+38C
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+141.3499255833
+ 20
+157.6450109217
+ 30
+0.0
+ 11
+141.5099255807
+ 21
+157.6450109217
+ 31
+0.0
+ 0
+ARC
+ 5
+38D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+141.2299255842
+ 20
+157.6450148501
+ 30
+0.0
+ 40
+0.1199999992156321
+100
+AcDbArc
+ 50
+180.0018756508
+ 51
+359.9981243492
+ 0
+LINE
+ 5
+38E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+140.5899255836
+ 20
+157.6450109217
+ 30
+0.0
+ 11
+141.109925585
+ 21
+157.6450109217
+ 31
+0.0
+ 0
+ARC
+ 5
+38F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+140.5899079826
+ 20
+157.245028504
+ 30
+0.0
+ 40
+0.3999824181641026
+100
+AcDbArc
+ 50
+89.9974787276
+ 51
+180.0017808858
+ 0
+LINE
+ 5
+390
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+140.1899255646
+ 20
+155.8050155375
+ 30
+0.0
+ 11
+140.1899255646
+ 21
+157.2450160716
+ 31
+0.0
+ 0
+ARC
+ 5
+391
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+139.7899431466
+ 20
+155.8050031051
+ 30
+0.0
+ 40
+0.3999824181605363
+100
+AcDbArc
+ 50
+269.9974853975
+ 51
+0.001780886
+ 0
+LINE
+ 5
+392
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2699252834
+ 20
+155.4050206874
+ 30
+0.0
+ 11
+139.7899255922
+ 21
+155.4050206874
+ 31
+0.0
+ 0
+ARC
+ 5
+393
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.2699388309
+ 20
+155.2850073443
+ 30
+0.0
+ 40
+0.1200133437736084
+100
+AcDbArc
+ 50
+90.0064677366
+ 51
+179.9974452656
+ 0
+LINE
+ 5
+394
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.1499254873
+ 20
+154.7275066111
+ 30
+0.0
+ 11
+134.1499254873
+ 21
+155.2850126956
+ 31
+0.0
+ 0
+ARC
+ 5
+395
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.2699407901
+ 20
+154.7275112492
+ 30
+0.0
+ 40
+0.1200153029849757
+100
+AcDbArc
+ 50
+180.0022142501
+ 51
+218.9973251432
+ 0
+LINE
+ 5
+396
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.4031833877
+ 20
+154.3722629283
+ 30
+0.0
+ 11
+134.1766678562
+ 21
+154.6519875262
+ 31
+0.0
+ 0
+ARC
+ 5
+397
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.3099758432
+ 20
+154.2967623625
+ 30
+0.0
+ 40
+0.1199499136121352
+100
+AcDbArc
+ 50
+359.99684824
+ 51
+39.0083836716
+ 0
+LINE
+ 5
+398
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.4299257567
+ 20
+150.296747658
+ 30
+0.0
+ 11
+134.4299257567
+ 21
+154.2967557643
+ 31
+0.0
+ 0
+ARC
+ 5
+399
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.3099104538
+ 20
+150.2967522961
+ 30
+0.0
+ 40
+0.12001530298499
+100
+AcDbArc
+ 50
+321.0026748568
+ 51
+359.9977857498
+ 0
+LINE
+ 5
+39A
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.1766678562
+ 20
+149.9415039752
+ 30
+0.0
+ 11
+134.4031833877
+ 21
+150.2212285731
+ 31
+0.0
+ 0
+ARC
+ 5
+39B
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.2698754103
+ 20
+149.8660034041
+ 30
+0.0
+ 40
+0.1199499244803327
+100
+AcDbArc
+ 50
+140.9916172207
+ 51
+180.0088433859
+ 0
+LINE
+ 5
+39C
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.1499254873
+ 20
+149.1650104258
+ 30
+0.0
+ 11
+134.1499254873
+ 21
+149.8659848902
+ 31
+0.0
+ 0
+ARC
+ 5
+39D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+134.2699184798
+ 20
+149.1650073473
+ 30
+0.0
+ 40
+0.1199929925413281
+100
+AcDbArc
+ 50
+179.9985300323
+ 51
+270.0032486934
+ 0
+LINE
+ 5
+39E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.9420049658
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+148.9420049658
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+LINE
+ 5
+39F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.1445668807
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+149.1445668807
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+LINE
+ 5
+3A0
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.0406432023
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+149.0406432023
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+LINE
+ 5
+3A1
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.0406432023
+ 20
+124.2850184176
+ 30
+0.0
+ 11
+148.9420049658
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+LINE
+ 5
+3A2
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.9137577285
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+133.9137577285
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+LINE
+ 5
+3A3
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.797846066
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+133.797846066
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+LINE
+ 5
+3A4
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.6992078294
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+133.6992078294
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+LINE
+ 5
+3A5
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.797846066
+ 20
+124.2850184176
+ 30
+0.0
+ 11
+133.6992078294
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+LINE
+ 5
+3A6
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.5952848961
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+133.5952848961
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+ARC
+ 5
+3A7
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.1879091276
+ 20
+124.2049907824
+ 30
+0.0
+ 40
+0.0800276369171186
+100
+AcDbArc
+ 50
+31.2357600789
+ 51
+89.9882535152
+ 0
+LINE
+ 5
+3A8
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.2563360204
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+149.2563360204
+ 21
+124.2464899752
+ 31
+0.0
+ 0
+LINE
+ 5
+3A9
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2162960043
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+134.2162960043
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+LINE
+ 5
+3AA
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2162960043
+ 20
+124.2850184176
+ 30
+0.0
+ 11
+134.1499254873
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+LINE
+ 5
+3AB
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.4299252202
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+133.4299252202
+ 21
+124.2555737231
+ 31
+0.0
+ 0
+LWPOLYLINE
+ 5
+3AC
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbPolyline
+ 90
+ 4
+ 70
+ 128
+ 43
+0.0
+ 10
+149.2569536736
+ 20
+124.2454528544
+ 10
+149.2656842639
+ 20
+124.2305516932
+ 10
+149.2736712863
+ 20
+124.2169141505
+ 10
+149.2808976044
+ 20
+124.204575989
+ 0
+LINE
+ 5
+3AD
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.3099258115
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+149.3099258115
+ 21
+124.1757392619
+ 31
+0.0
+ 0
+LINE
+ 5
+3AE
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.2563360204
+ 20
+124.2464899752
+ 30
+0.0
+ 11
+149.2569536736
+ 21
+124.2454528544
+ 31
+0.0
+ 0
+LINE
+ 5
+3AF
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2299253514
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+134.2299253514
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+LINE
+ 5
+3B0
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2299253514
+ 20
+124.2850184176
+ 30
+0.0
+ 11
+134.2162960043
+ 21
+124.2850184176
+ 31
+0.0
+ 0
+LINE
+ 5
+3B1
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.5099256804
+ 20
+105.7777046893
+ 30
+0.0
+ 11
+148.5099256804
+ 21
+123.090755913
+ 31
+0.0
+ 0
+LINE
+ 5
+3B2 5
+4FF
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+133.4299255838
+ 20
+660.3011936068
+ 30
+0.0
+ 0
+DIMENSION
+ 5
+500
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D
+ 10
+195.4299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+195.4299255838
+ 21
+34.5771309945
+ 31
+0.0
+ 70
+ 160
+ 1
+33
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE0
+100
+AcDbAlignedDimension
+ 13
+142.0299255838
+ 23
+28.7821787182
+ 33
+0.0
+ 14
+148.0299255838
+ 24
+41.9821787182
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+50D
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_1
+ 10
+171.4299255838
+ 20
+287.499722104
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+271.6850735678
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+149.4168593775
+ 23
+280.0124004364
+ 33
+0.0
+ 14
+147.8555696261
+ 24
+287.499722104
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+51A
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_2
+ 10
+171.4299255838
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+194.8678981433
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+149.4168593775
+ 23
+202.8200717467
+ 33
+0.0
+ 14
+147.8555696261
+ 24
+210.3073934143
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+526
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_3
+ 10
+171.4299255838
+ 20
+133.7256838344
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+113.1236101725
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+149.4168593775
+ 23
+126.2783621669
+ 33
+0.0
+ 14
+148.0299255838
+ 24
+133.7256838344
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+532
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_4
+ 10
+195.4299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+195.4299255838
+ 21
+84.1586135498
+ 31
+0.0
+ 70
+ 160
+ 1
+675,902
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+148.0299255838
+ 23
+133.7256838344
+ 33
+0.0
+ 14
+148.0299255838
+ 24
+41.9821787182
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+53C
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_5
+ 10
+195.3191971725
+ 20
+133.7649839595
+ 30
+0.0
+ 11
+195.2817915125
+ 21
+178.7746730971
+ 31
+0.0
+ 70
+ 160
+ 1
+524,500
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+147.8555696261
+ 23
+210.3073934143
+ 33
+0.0
+ 14
+148.0299255838
+ 24
+133.7256838344
+ 34
+0.0
+ 50
+-89.9523838979
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+545
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_6
+ 10
+195.2555696261
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+195.2555696261
+ 21
+246.0726158741
+ 31
+0.0
+ 70
+ 160
+ 1
+524,400
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+147.8555696261
+ 23
+287.499722104
+ 33
+0.0
+ 14
+147.8555696261
+ 24
+210.3073934143
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+54E
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_7
+ 10
+195.2555696261
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+195.2555696261
+ 21
+357.7845521004
+ 31
+0.0
+ 70
+ 160
+ 1
+545,698
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+147.8555696261
+ 23
+287.499722104
+ 33
+0.0
+ 14
+145.8378589248
+ 24
+397.2202084954
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+557
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_8
+ 10
+219.4299255838
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+219.4299255838
+ 21
+226.31135989
+ 31
+0.0
+ 70
+ 160
+ 1
+Kozijnmaat =
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+142.0299255838
+ 23
+28.7821787182
+ 33
+0.0
+ 14
+145.8378589248
+ 24
+397.2202084954
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+561
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_9
+ 10
+99.2299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+99.2299255838
+ 21
+214.5361780403
+ 31
+0.0
+ 70
+ 160
+ 1
+DMH =
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+133.4299255838
+ 23
+383.8202084954
+ 33
+0.0
+ 14
+142.0299255838
+ 24
+28.7821787182
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+56C
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_10
+ 10
+117.2299255838
+ 20
+148.4450158005
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+157.3719824392
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+133.5499255838
+ 23
+147.8850158005
+ 33
+0.0
+ 14
+133.5499255838
+ 24
+148.4450158005
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+578
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_11
+ 10
+117.2299255838
+ 20
+224.9867253803
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+236.5334651005
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+133.5499255838
+ 23
+224.4267253803
+ 33
+0.0
+ 14
+133.5499255838
+ 24
+224.9867253803
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+584
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_12
+ 10
+117.2299255838
+ 20
+302.17905407
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+311.8110919194
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+133.5499255838
+ 23
+301.61905407
+ 33
+0.0
+ 14
+133.5499255838
+ 24
+302.17905407
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+LINE
+ 5
+590
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+103.7777066237
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+103.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+591
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+103.7777066237
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+105.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+592
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+105.7777066237
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+103.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+593
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+103.7777066237
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+101.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+594
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+101.7777066237
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+103.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+595
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+103.7777066237
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+103.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+596
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+105.7777066237
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+105.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+597
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+105.7777066237
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+107.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+598
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+107.7777066237
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+105.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+599
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+105.7777066237
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+103.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+59A
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+103.7777066237
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+105.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+59B
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+105.7777066237
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+105.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+59C
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+179.2366515846
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+179.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+59D
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+179.2366515846
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+181.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+59E
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+181.2366515846
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+179.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+59F
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 31
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+179.2366515846
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+177.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A0
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+177.2366515846
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+179.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A1
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+179.2366515846
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+179.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A2
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+181.2366515846
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+181.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A3
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+181.2366515846
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+183.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A4
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+183.2366515846
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+181.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A5
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+181.2366515846
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+179.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A6
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+179.2366515846
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+181.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A7
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+181.2366515846
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+181.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A8
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+256.4750445598
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+256.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5A9
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+256.4750445598
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+258.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AA
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+258.4750445598
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+256.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AB
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+256.4750445598
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+254.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AC
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+254.4750445598
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+256.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AD
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+256.4750445598
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+256.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AE
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+258.4750445598
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+258.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AF
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+258.4750445598
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+260.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5B0
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+260.4750445598
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+258.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5B1
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+258.4750445598
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+256.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5B2
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+256.4750445598
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+258.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5B3
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+258.4750445598
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+258.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5B4
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+334.7369117996
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+334.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5B5
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+334.7369117996
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+336.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5B6
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+336.7369117996
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+334.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5B7
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+334.7369117996
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+332.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5B8
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+332.7369117996
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+334.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5B9
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+334.7369117996
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+334.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5BA
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+336.7369117996
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+336.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5BB
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+336.7369117996
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+338.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5BC
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+338.7369117996
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+336.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5BD
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+336.7369117996
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+334.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5BE
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+334.7369117996
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+336.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5BF
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+336.7369117996
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+336.7369117996
+ 31
+0.0
+ 0
+ENDSEC
+ 0
+SECTION
+ 2
+OBJECTS
+ 0
+DICTIONARY
+ 5
+C
+330
+0
+100
+AcDbDictionary
+281
+ 1
+ 3
+ACAD_GROUP
+350
+D
+ 3
+ACAD_LAYOUT
+350
+1A
+ 3
+ACAD_MLINESTYLE
+350
+17
+ 3
+ACAD_PLOTSETTINGS
+350
+19
+ 3
+ACAD_PLOTSTYLENAME
+350
+E
+ 0
+DICTIONARY
+ 5
+D
+102
+{ACAD_REACTORS
+330
+C
+102
+}
+330
+C
+100
+AcDbDictionary
+281
+ 1
+ 0
+DICTIONARY
+ 5
+1A
+102
+{ACAD_REACTORS
+330
+C
+102
+}
+330
+C
+100
+AcDbDictionary
+281
+ 1
+ 3
+Layout1
+350
+1E
+ 3
+Layout2
+350
+26
+ 3
+Model
+350
+22
+ 0
+DICTIONARY
+ 5
+17
+102
+{ACAD_REACTORS
+330
+C
+102
+}
+330
+C
+100
+AcDbDictionary
+281
+ 1
+ 3
+Standard
+350
+18
+ 0
+DICTIONARY
+ 5
+19
+102
+{ACAD_REACTORS
+330
+C
+102
+}
+330
+C
+100
+AcDbDictionary
+281
+ 1
+ 0
+ACDBDICTIONARYWDFLT
+ 5
+E
+102
+{ACAD_REACTORS
+330
+C
+102
+}
+330
+C
+100
+AcDbDictionary
+281
+ 1
+ 3
+Normal
+350
+F
+100
+AcDbDictionaryWithDefault
+340
+F
+ 0
+LAYOUT
+ 5
+1E
+102
+{ACAD_REACTORS
+330
+1A
+102
+}
+330
+1A
+100
+AcDbPlotSettings
+ 1
+
+ 2
+none_device
+ 4
+
+ 6
+
+ 40
+0.0
+ 41
+0.0
+ 42
+0.0
+ 43
+0.0
+ 44
+0.0
+ 45
+0.0
+ 46
+0.0
+ 47
+0.0
+ 48
+0.0
+ 49
+0.0
+140
+0.0
+141
+0.0
+142
+1.0
+143
+1.0
+ 70
+ 688
+ 72
+ 0
+ 73
+ 0
+ 74
+ 5
+ 7
+
+ 75
+ 16
+147
+1.0
+148
+0.0
+149
+0.0
+100
+AcDbLayout
+ 1
+Layout1
+ 70
+ 1
+ 71
+ 1
+ 10
+0.0
+ 20
+0.0
+ 11
+420.0
+ 21
+297.0
+ 12
+0.0
+ 22
+0.0
+ 32
+0.0
+ 14
+1.0000000000E+20
+ 24
+1.0000000000E+20
+ 34
+1.0000000000E+20
+ 15
+-1.0000000000E+20
+ 25
+-1.0000000000E+20
+ 35
+-1.0000000000E+20
+146
+0.0
+ 13
+0.0
+ 23
+0.0
+ 33
+0.0
+ 16
+1.0
+ 26
+0.0
+ 36
+0.0
+ 17
+0.0
+ 27
+1.0
+ 37
+0.0
+ 76
+ 0
+330
+1B
+ 0
+LAYOUT
+ 5
+26
+102
+{ACAD_REACTORS
+330
+1A
+102
+}
+330
+1A
+100
+AcDbPlotSettings
+ 1
+
+ 2
+none_device
+ 4
+
+ 6
+
+ 40
+0.0
+ 41
+0.0
+ 42
+0.0
+ 43
+0.0
+ 44
+0.0
+ 45
+0.0
+ 46
+0.0
+ 47
+0.0
+ 48
+0.0
+ 49
+0.0
+140
+0.0
+141
+0.0
+142
+1.0
+143
+1.0
+ 70
+ 688
+ 72
+ 0
+ 73
+ 0
+ 74
+ 5
+ 7
+
+ 75
+ 16
+147
+1.0
+148
+0.0
+149
+0.0
+100
+AcDbLayout
+ 1
+Layout2
+ 70
+ 1
+ 71
+ 2
+ 10
+0.0
+ 20
+0.0
+ 11
+0.0
+ 21
+0.0
+ 12
+0.0
+ 22
+0.0
+ 32
+0.0
+ 14
+0.0
+ 24
+0.0
+ 34
+0.0
+ 15
+0.0
+ 25
+0.0
+ 35
+0.0
+146
+0.0
+ 13
+0.0
+ 23
+0.0
+ 33
+0.0
+ 16
+1.0
+ 26
+0.0
+ 36
+0.0
+ 17
+0.0
+ 27
+1.0
+ 37
+0.0
+ 76
+ 0
+330
+23
+ 0
+LAYOUT
+ 5
+22
+102
+{ACAD_REACTORS
+330
+1A
+102
+}
+330
+1A
+100
+AcDbPlotSettings
+ 1
+
+ 2
+none_device
+ 4
+
+ 6
+
+ 40
+0.0
+ 41
+0.0
+ 42
+0.0
+ 43
+0.0
+ 44
+0.0
+ 45
+0.0
+ 46
+0.0
+ 47
+0.0
+ 48
+0.0
+ 49
+0.0
+140
+0.0
+141
+0.0
+142
+1.0
+143
+1.0
+ 70
+ 1712
+ 72
+ 0
+ 73
+ 0
+ 74
+ 0
+ 7
+
+ 75
+ 0
+147
+1.0
+148
+0.0
+149
+0.0
+100
+AcDbLayout
+ 1
+Model
+ 70
+ 1
+ 71
+ 0
+ 10
+0.0
+ 20
+0.0
+ 11
+297.0
+ 21
+420.0
+ 12
+0.0
+ 22
+0.0
+ 32
+0.0
+ 14
+0.0
+ 24
+0.0
+ 34
+0.0
+ 15
+297.0
+ 25
+420.0
+ 35
+0.0
+146
+0.0
+ 13
+0.0
+ 23
+0.0
+ 33
+0.0
+ 16
+1.0
+ 26
+0.0
+ 36
+0.0
+ 17
+0.0
+ 27
+1.0
+ 37
+0.0
+ 76
+ 0
+330
+1F
+331
+29
+ 0
+MLINESTYLE
+ 5
+18
+102
+{ACAD_REACTORS
+330
+17
+102
+}
+330
+17
+100
+AcDbMlineStyle
+ 2
+Standard
+ 70
+ 0
+ 3
+
+ 62
+ 256
+ 51
+90.0
+ 52
+90.0
+ 71
+ 2
+ 49
+0.5
+ 62
+ 256
+ 6
+BYLAYER
+ 49
+-0.5
+ 62
+ 256
+ 6
+BYLAYER
+ 0
+ACDBPLACEHOLDER
+ 5
+F
+102
+{ACAD_REACTORS
+330
+E
+102
+}
+330
+E
+ 0
+ENDSEC
+ 0
+EOF
diff --git a/vcl/qa/cppunit/graphicfilter/data/dxf/pass/loop-2.dxf b/vcl/qa/cppunit/graphicfilter/data/dxf/pass/loop-2.dxf
new file mode 100644
index 0000000000..961dd35a16
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/dxf/pass/loop-2.dxf
@@ -0,0 +1,13974 @@
+ 0
+SECTION*
+ 2
+HEADER
+ 9
+$ACADVER
+ 1
+AC1015
+ 9
+$ACADMAINTVER
+ 70
+ 6
+ 9
+$DWGCODEPAGE
+ 3
+ANSI_1252
+ 9
+$INSBASE
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$EXTMIN
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 9
+$EXTMAX
+ 10
+297.0
+ 20
+420.0
+ 30
+0.0
+ 9
+$LIMMIN
+ 10
+0.0
+ 20
+0.0
+ 9
+$LIMMAX
+ 10
+297.0
+ 20
+420.0
+ 9
+$ORTHOMODE
+ 70
+ 0
+ 9
+$REGENMODE
+ 70
+ 1
+ 9
+$FILL«ODE
+ 70
+ 1
+ 9
+$QTEXTMODE
+ 70
+ 0
+ 9
+$MIRRTEXT
+ 70
+ 1
+ 0
+DIMSTYLE
+105
+501
+102
+{ACAD_REACTORS
+330
+500
+102
+}
+330
+A
+100
+AcDbSymbolTableRecord
+100
+AcDbDimStyleTableRecord
+ 2
+SLDDIMSTYLE0
+ 70
+ 0
+ 41
+3.0
+ 44
+0.0
+ 45
+0.000000001
+ 73
+ 0
+ 74
+ 0
+ 77
+ 1
+ 78
+ 12
+ 79
+ 3
+140
+3.5
+144
+2.5
+147
+0.875
+172
+ 1
+173
+ 1
+178
+ 0
+271
+ 3
+272
+ 3
+276
+ 2
+284
+ 12
+289
+ 0
+340
+54
+ 0
+DIMSTYLE
+105
+50E
+102
+{ACAD_REACTORS
+330
+50D
+330
+51A
+330
+526
+330
+56C
+330
+578
+330
+584
+102
+}
+330
+A
+100
+AcDbSymbolTableRecord
+100
+AcDbDimStyleTableRecord
+ 2
+SLDDIMSTYLE1
+ 70
+ 0
+ 41
+3.0
+ 44
+0.0
+ 45
+0.000000001
+ 73
+ 0
+ 74
+ 0
+ 77
+ 1
+ 78
+ 12
+ 79
+ 3
+140
+3.5
+144
+2.5
+147
+0.875
+172
+ 1
+173
+ 1
+178
+ 0
+271
+ 0
+272
+ 0
+276
+ 2
+284
+ 12
+289
+ 0
+340
+54
+ 0
+DIMSTYLE
+105
+533
+102
+{ACAD_REACTORS
+330
+532
+330
+53C
+330
+545
+330
+54E
+330
+557
+330
+561
+102
+}
+330
+A
+100
+AcDbSymbolTableRecord
+100
+AcDbDimStyleTableRecord
+ 2
+SLDDIMSTYLE2
+ 70
+ 0
+ 41
+3.0
+ 44
+0.0
+ 45
+0.000000001
+ 73
+ 0
+ 74
+ 0
+ 77
+ 1
+ 78
+ 12
+ 79
+ 3
+140
+3.5
+144
+2.5
+147
+0.875
+173
+ 1
+178
+ 0
+271
+ 3
+272
+ 3
+276
+ 2
+284
+ 12
+340
+54
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+BLOCK_RECORD
+ 5
+1
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 15
+ 0
+BLOCK_RECORD
+ 5
+1F
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+*Model_Space
+340
+22
+ 0
+BLOCK_RECORD
+ 5
+1B
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+*Paper_Space
+340
+1E
+ 0
+BLOCK_RECORD
+ 5
+23
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+*Paper_Space0
+340
+26
+ 0
+BLOCK_RECORD
+ 5
+56
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+SW_BROKEN_VIEW_0
+340
+0
+102
+{BLKREFS
+331
+59
+102
+}
+ 0
+BLOCK_RECORD
+ 5
+502
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+50F
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_1
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+51B
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_2
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+527
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_3
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+534
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_4
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+53D
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_5
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+546
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_6
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+54F
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_7
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+558
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_8
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+562
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_9
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+56D
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_10
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+579
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_11
+340
+0
+ 0
+BLOCK_RECORD
+ 5
+585
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+_D_12
+340
+0
+ 0
+ENDTAB
+ 0
+ENDSEC
+ 0
+SECTION
+ 2
+BLOCKS
+ 0
+BLOCK
+ 5
+20
+330
+1F
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+*Model_Space
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+*Model_Space
+ 1
+
+ 0
+ENDBLK
+ 5
+21
+330
+1F
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+1C
+330
+1B
+100
+AcDbEntity
+ 67
+ 1
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+*Paper_Space
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+*Paper_Space
+ 1
+
+ 0
+ENDBLK
+ 5
+1D
+330
+1B
+100
+AcDbEntity
+ 67
+ 1
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+24
+330
+23
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+*Paper_Space0
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+*Paper_Space0
+ 1
+
+ 0
+ENDBLË
+ 5
+25
+330
+23
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+57
+330
+56
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+SW_BROKEN_VIEW_0
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+SW_BROKEN_VIEW_0
+ 1
+
+ 0
+ENDBLK
+ 5
+58
+330
+56
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+503
+330
+502
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D
+ 1
+
+ 0
+SOLID
+ 5
+505
+330
+502
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.4299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+194.9299255838
+ 21
+25.7821787182
+ 31
+0.0
+ 12
+195.9299255838
+ 22
+25.7821787182
+ 32
+0.0
+ 13
+195.9299255838
+ 23
+25.7821787182
+ 33
+0.0
+ 0
+SOLID
+ 5
+506
+330
+502
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.4299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+195.9299255838
+ 21
+44.9821787182
+ 31
+0.0
+ 12
+194.9299255838
+ 22
+44.9821787182
+ 32
+0.0
+ 13
+194.9299255838
+ 23
+44.9821787182
+ 33
+0.0
+ 0
+LINE
+ 5
+507
+330
+502
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+143.0299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+196.4299255838
+ 21
+28.7821787182
+ 31
+0.0
+ 0
+LINE
+ 5
+508
+330
+502
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+149.0299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+196.4299255838
+ 21
+41.9821787182
+ 31
+0.0
+ 0
+LINE
+ 5
+509
+330
+502
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+195.4299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+195.4299255838
+ 21
+22.7821787182
+ 31
+0.0
+ 0
+LINE
+ 5
+50A
+330
+502
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+195.4299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+195.4299255838
+ 21
+41.9821787182
+ 31
+0.0
+ 0
+LINE
+ 5
+50B
+330
+502
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+195.4299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+195.4299255838
+ 21
+47.9821787182
+ 31
+0.0
+ 0
+MTEXT
+ 5
+50C
+330
+502
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+190.9181886692
+ 20
+31.991019801
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+33
+ 7
+SLDTEXTSTYLE0
+ 11
+6.123233995736766E-17
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+504
+330
+502
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+510
+330
+50F
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_1
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_1
+ 1
+
+ 0
+SOLID
+ 5
+512
+330
+50F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+171.4299255838
+ 20
+280.0124004364
+ 30
+0.0
+ 11
+170.9299255838
+ 21
+277.0124004364
+ 31
+0.0
+ 12
+171.9299255838
+ 22
+277.0124004364
+ 32
+0.0
+ 13
+171.9299255838
+ 23
+277.0124004364
+ 33
+0.0
+ 0
+SOLID
+ 5
+513
+330
+50F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+171.4299255838
+ 20
+287.499722104
+ 30
+0.0
+ 11
+171.9299255838
+ 21
+290.499722104
+ 31
+0.0
+ 12
+170.9299255838
+ 22
+290.499722104
+ 32
+0.0
+ 13
+170.9299255838
+ 23
+290.499722104
+ 33
+0.0
+ 0
+LINE
+ 5
+514
+330
+50F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+150.4168593775
+ 20
+280.0124004364
+ 30
+0.0
+ 11
+172.4299255838
+ 21
+280.0124004364
+ 31
+0.0
+ 0
+LINE
+ 5
+515
+330
+50F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+148.8555696261
+ 20
+287.499722104
+ 30
+0.0
+ 11
+172.4299255838
+ 21
+287.499722104
+ 31
+0.0
+ 0
+LINE
+ 5
+516
+330
+50F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+280.0124004364
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+267.6770872868
+ 31
+0.0
+ 0
+LINE
+ 5
+517
+330
+50F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+280.0124004364
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+287.499722104
+ 31
+0.0
+ 0
+LINE
+ 5
+518
+330
+50F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+287.499722104
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+293.499722104
+ 31
+0.0
+ 0
+MTEXT
+ 5
+519
+330
+50F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+166.9181886692
+ 20
+267.6770872868
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+19
+ 7
+SLDTEXTSTYLE0
+ 11
+6.123233995736766E-17
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+511
+330
+50F
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+51C
+330
+51B
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_2
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_2
+ 1
+
+ 0
+SOLID
+ 5
+51E
+330
+51B
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+171.4299255838
+ 20
+202.8200717467
+ 30
+0.0
+ 11
+170.9299255838
+ 21
+199.8200717467
+ 31
+0.0
+ 12
+1715696261
+ 21
+284.499722104
+ 31
+0.0
+ 12
+195.7555696261
+ 22
+284.499722104
+ 32
+0.0
+ 13
+195.7555696261
+ 23
+284.499722104
+ 33
+0.0
+ 0
+SOLID
+ 5
+54A
+330
+546
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.2555696261
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+195.7555696261
+ 21
+213.3073934143
+ 31
+0.0
+ 12
+194.7555696261
+ 22
+213.3073934143
+ 32
+0.0
+ 13
+194.7555696261
+ 23
+213.3073934143
+ 33
+0.0
+ 0
+LINE
+ 5
+54B
+330
+546
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+148.8555696261
+ 20
+287.499722104
+ 30
+0.0
+ 11
+196.2555696261
+ 21
+287.499722104
+ 31
+0.0
+ 0
+LINE
+ 5
+54C
+330
+546
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+148.8555696261
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+196.2555696261
+ 21
+210.3073934143
+ 31
+0.0
+ 0
+LINE
+ 5
+54D
+330
+546
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+195.2555696261
+ 20
+287.499722104
+ 30
+0.0
+ 11
+195.2555696261
+ 21
+210.3073934143
+ 31
+0.0
+ 0
+ENDBLK
+ 5
+548
+330
+546
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+550
+330
+54F
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_7
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_7
+ 1
+
+ 0
+SOLID
+ 5
+552
+330
+54F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.2555696261
+ 20
+287.499722104
+ 30
+0.0
+ 11
+195.7555696261
+ 21
+290.499722104
+ 31
+0.0
+ 12
+194.7555696261
+ 22
+290.499722104
+ 32
+0.0
+ 13
+194.7555696261
+ 23
+290.499722104
+ 33
+0.0
+ 0
+SOLID
+ 5
+553
+330
+54F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.2555696261
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+194.7555696261
+ 21
+394.2202084954
+ 31
+0.0
+ 12
+195.7555696261
+ 22
+394.2202084954
+ 32
+0.0
+ 13
+195.7555696261
+ 23
+394.2202084954
+ 33
+0.0
+ 0
+LINE
+ 5
+554
+330
+54F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+148.8555696261
+ 20
+287.499722104
+ 30
+0.0
+ 11
+196.2555696261
+ 21
+287.499722104
+ 31
+0.0
+ 0
+LINE
+ 5
+555
+330
+54F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+146.8378589248
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+196.2555696261
+ 21
+397.2202084954
+ 31
+0.0
+ 0
+LINE
+ 5
+556
+330
+54F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+195.2555696261
+ 20
+287.499722104
+ 30
+0.0
+ 11
+195.2555696261
+ 21
+397.2202084954
+ 31
+0.0
+ 0
+ENDBLK
+ 5
+551
+330
+54F
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+559
+330
+558
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_8
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_8
+ 1
+
+ 0
+SOLID
+ 5
+55B
+330
+558
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+219.4299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+219.9299255838
+ 21
+31.7821787182
+ 31
+0.0
+ 12
+218.9299255838
+ 22
+31.7821787182
+ 32
+0.0
+ 13
+218.9299255838
+ 23
+31.7821787182
+ 33
+0.0
+ 0
+SOLID
+ 5
+55C
+330
+558
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+219.4299255838
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+218.9299255838
+ 21
+394.2202084954
+ 31
+0.0
+ 12
+219.9299255838
+ 22
+394.2202084954
+ 32
+0.0
+ 13
+219.9299255838
+ 23
+394.2202084954
+ 33
+0.0
+ 0
+LINE
+ 5
+5527
+ 20
+360.4202097011
+ 30
+0.0
+ 11
+142.6699255427
+ 21
+361.9402235103
+ 31
+0.0
+ 0
+ARC
+ 5
+7D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+143.0699283112
+ 20
+361.9401917488
+ 30
+0.0
+ 40
+0.4000027697974622
+100
+AcDbArc
+ 50
+90.0003871694
+ 51
+179.9954505335
+ 0
+LINE
+ 5
+7E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.0699256082
+ 20
+362.3401945186
+ 30
+0.0
+ 11
+148.5899255445
+ 21
+362.3401945186
+ 31
+0.0
+ 0
+ARC
+ 5
+7F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5899336175
+ 20
+362.4601862448
+ 30
+0.0
+ 40
+0.1199917263967713
+100
+AcDbArc
+ 50
+269.9961451292
+ 51
+0.0134590356
+ 0
+LINE
+ 5
+80
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.7099253406
+ 20
+362.4602144314
+ 30
+0.0
+ 11
+148.7099253406
+ 21
+366.669732864
+ 31
+0.0
+ 0
+ARC
+ 5
+81
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5098175737
+ 20
+366.669705415
+ 30
+0.0
+ 40
+0.2001077687858761
+100
+AcDbArc
+ 50
+0.0078593224
+ 51
+46.1736252111
+ 0
+ARC
+ 5
+82
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.229892261
+ 20
+367.4202030254
+ 30
+0.0
+ 40
+0.8399663770856335
+100
+AcDbArc
+ 50
+133.8120979057
+ 51
+226.1879020943
+ 0
+ARC
+ 5
+83
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5098175737
+ 20
+368.1707006358
+ 30
+0.0
+ 40
+0.200107768785933
+100
+AcDbArc
+ 50
+313.8263747889
+ 51
+359.9921406775
+ 0
+LINE
+ 5
+84
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.7099253406
+ 20
+368.1706731868
+ 30
+0.0
+ 11
+148.7099253406
+ 21
+370.3305070949
+ 31
+0.0
+ 0
+ARC
+ 5
+85
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5900202217
+ 20
+370.3305234822
+ 30
+0.0
+ 40
+0.1199051200450481
+100
+AcDbArc
+ 50
+359.9921694514
+ 51
+45.0183972297
+ 0
+LINE
+ 5
+86
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.6747787168
+ 20
+370.4153364254
+ 30
+0.0
+ 11
+148.5050731172
+ 21
+370.58504277
+ 31
+0.0
+ 0
+ARC
+ 5
+87
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5900489089
+ 20
+370.6699467703
+ 30
+0.0
+ 40
+0.1201231635795685
+100
+AcDbArc
+ 50
+180.0128717119
+ 51
+224.9757867508
+ 0
+LINE
+ 5
+88
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.4699257483
+ 20
+370.669919ontinuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.0299484826
+ 20
+72.3821479784
+ 30
+0.0
+ 40
+0.399977127854635
+100
+AcDbArc
+ 50
+292.6176918057
+ 51
+0.0067619015
+ 0
+ARC
+ 5
+4D2
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.2299252955
+ 20
+71.9021632155
+ 30
+0.0
+ 40
+0.1199998892853113
+100
+AcDbArc
+ 50
+112.6196870788
+ 51
+247.3803129212
+ 0
+ARC
+ 5
+4D3
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.0299484881
+ 20
+71.4221784593
+ 30
+0.0
+ 40
+0.3999771195634846
+100
+AcDbArc
+ 50
+0.0000677203
+ 51
+67.382308554
+ 0
+LINE
+ 5
+4D4
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.4299256077
+ 20
+71.4221789321
+ 30
+0.0
+ 11
+149.4299256077
+ 21
+68.8558890304
+ 31
+0.0
+ 0
+ARC
+ 5
+4D5
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.3098934248
+ 20
+68.8558875882
+ 30
+0.0
+ 40
+0.1200321828299822
+100
+AcDbArc
+ 50
+284.0483029705
+ 51
+0.0006883757
+ 0
+LINE
+ 5
+4D6
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.3390300145
+ 20
+68.7394453963
+ 30
+0.0
+ 11
+149.2808216085
+ 21
+68.724901863
+ 31
+0.0
+ 0
+ARC
+ 5
+4D7
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.3099570381
+ 20
+68.6084597995
+ 30
+0.0
+ 40
+0.1200317766406951
+100
+AcDbArc
+ 50
+104.0477806555
+ 51
+179.9779884139
+ 0
+LINE
+ 5
+4D8
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.1899252703
+ 20
+68.6085059127
+ 30
+0.0
+ 11
+149.1899252703
+ 21
+67.5958945235
+ 31
+0.0
+ 0
+ARC
+ 5
+4D9
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.309957026
+ 20
+67.5958929344
+ 30
+0.0
+ 40
+0.1200317557485991
+100
+AcDbArc
+ 50
+179.999241474
+ 51
+255.952222776
+ 0
+LINE
+ 5
+4DA
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.2808216085
+ 20
+67.4794508895
+ 30
+0.0
+ 11
+149.3390300145
+ 21
+67.4649073562
+ 31
+0.0
+ 0
+ARC
+ 5
+4DB
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.3098934248
+ 20
+67.3484651642
+ 30
+0.0
+ 40
+0.1200321828298969
+100
+AcDbArc
+ 50
+359.9993116244
+ 51
+75.9516970296
+ 0
+LINE
+ 5
+4DC
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.4299256077
+ 20
+67.3484637221
+ 30
+0.0
+ 11
+149.4299256077
+ 21
+64.7821738204
+ 31
+0.0
+ 0
+ARC
+ 5
+4DD
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.0299484881
+ 20
+64.7821742931
+ 30
+0.0
+ 40
+0.3999771195634605
+100
+AcDbArc
+ 50
+292.617691446
+ 51
+359.9999322797
+ 0
+ARC
+ 5
+4DE
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.2299252955
+ 20
+64.3021895369
+ 30
+0.0
+ 40
+0.1199998892853769
+100
+AcDbArc
+ 50
+112.6196870788
+ 51
+247.3803129212
+ 0
+ARC
+ 5
+4DF
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.0299484826
+ 20
+63.8222047741
+ 30
+0.0
+ 40
+0.3999771278546918
+100
+AcDbArc
+ 50
+359.9932380985
+ 51
+67.3823081943
+ 0
+LINE
+ 5
+4E0
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.4299256077
+ 20
+63.8221575698
+ 30
+0.0
+ 11
+149.4299256077
+ 21
+42.2443224868
+ 31
+0.0
+ 0
+ARC
+ 5
+4E1
+330
+1F
+100
+AcDbEnt
+ 21
+254.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AC
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+254.4750445598
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+256.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AD
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+256.4750445598
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+256.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AE
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+258.4750445598
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+258.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AF
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+258.4750445598
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+260.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5B0
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+260.4750445598
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+258.4750.9299255838
+ 22
+199.8200717467
+ 32
+0.0
+ 13
+171.9299255838
+ 23
+199.8200717467
+ 33
+0.0
+ 0
+SOLID
+ 5
+51F
+330
+51B
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+171.4299255838
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+171.9299255838
+ 21
+213.3073934143
+ 31
+0.0
+ 12
+170.9299255838
+ 22
+213.3073934143
+ 32
+0.0
+ 13
+170.9299255838
+ 23
+213.3073934143
+ 33
+0.0
+ 0
+LINE
+ 5
+520
+330
+51B
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+150.4168593775
+ 20
+202.8200717467
+ 30
+0.0
+ 11
+172.4299255838
+ 21
+202.8200717467
+ 31
+0.0
+ 0
+LINE
+ 5
+521
+330
+51B
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+148.8555696261
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+172.4299255838
+ 21
+210.3073934143
+ 31
+0.0
+ 0
+LINE
+ 5
+522
+330
+51B
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+202.8200717467
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+190.8599118622
+ 31
+0.0
+ 0
+LINE
+ 5
+523
+330
+51B
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+202.8200717467
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+210.3073934143
+ 31
+0.0
+ 0
+LINE
+ 5
+524
+330
+51B
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+216.3073934143
+ 31
+0.0
+ 0
+MTEXT
+ 5
+525
+330
+51B
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+166.9181886692
+ 20
+190.8599118622
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+19
+ 7
+SLDTEXTSTYLE0
+ 11
+6.123233995736766E-17
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+51D
+330
+51B
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+528
+330
+527
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_3
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_3
+ 1
+
+ 0
+SOLID
+ 5
+52A
+330
+527
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+171.4299255838
+ 20
+126.2783621669
+ 30
+0.0
+ 11
+170.9299255838
+ 21
+123.2783621669
+ 31
+0.0
+ 12
+171.9299255838
+ 22
+123.2783621669
+ 32
+0.0
+ 13
+171.9299255838
+ 23
+123.2783621669
+ 33
+0.0
+ 0
+SOLID
+ 5
+52B
+330
+527
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+171.4299255838
+ 20
+133.7256838344
+ 30
+0.0
+ 11
+171.9299255838
+ 21
+136.7256838344
+ 31
+0.0
+ 12
+170.9299255838
+ 22
+136.7256838344
+ 32
+0.0
+ 13
+170.9299255838
+ 23
+136.7256838344
+ 33
+0.0
+ 0
+LINE
+ 5
+52C
+330
+527
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+150.4168593775
+ 20
+126.2783621669
+ 30
+0.0
+ 11
+172.4299255838
+ 21
+126.2783621669
+ 31
+0.0
+ 0
+LINE
+ 5
+52D
+330
+527
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+149.0299255838
+ 20
+133.7256838344
+ 30
+0.0
+ 11
+172.4299255838
+ 21
+133.7256838344
+ 31
+0.0
+ 0
+LINE
+ 5
+52E
+330
+527
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+126.2783621669
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+109.1156238914
+ 31
+0.0
+ 0
+LINE
+ 5
+52F
+330
+527
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+126.2783621669
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+133.7256838344
+ 31
+0.0
+ 0
+LINE
+ 5
+530
+330
+527
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+133.7256838344
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+139.7256838344
+ 31
+0.0
+ 0
+MTEXT
+ 5
+531
+330
+527
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+166.9181886692
+ 20
+109.1156238914
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+19
+ 7
+SLDTEXTSTYLE0
+ 11
+6.123233995736766E-17
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+529
+330
+527
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+535
+330
+534
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_4
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_4
+ 1
+
+ 0
+SOLID
+ 5
+537
+330
+534
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.4299255838
+ 20
+133.7256838344
+ 30
+0.0
+ 11
+194.9299255838
+ 21
+130.7256838344
+ 31
+0.0
+ 12
+195.9299255838
+ 22
+130.7256838344
+ 32
+0.0
+ 13
+195.9299255838
+ 23
+130.7256838344
+ 33
+0.0
+ 0
+SOLID
+ 5
+538
+330
+534
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.4299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+195.9299255838
+ 21
+44.9821787182
+ 31
+0.0
+ 12
+194.9299255838
+ 22
+44.9821787182
+ 32
+0.0
+ 13
+194.9299255838
+ 23
+44.9821787182
+ 33
+0.0
+ 0
+LINE
+ 5
+539
+330
+534
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+149.0299255838
+ 20
+133.7256838344
+ 30
+0.0
+ 11
+196.4299255838
+ 21
+133.7256838344
+ 31
+0.0
+ 0
+LINE
+ 5
+53A
+330
+534
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+149.0299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+196.4299255838
+ 21
+41.9821787182
+ 31
+0.0
+ 0
+LINE
+ 5
+53B
+330
+534
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+195.4299255838
+ 20
+133.7256838344
+ 30
+0.0
+ 11
+195.4299255838
+ 21
+41.9821787182
+ 31
+0.0
+ 0
+ENDBLK
+ 5
+536
+330
+534
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+53E
+330
+53D
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_5
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_5
+ 1
+
+ 0
+SOLID
+ 5
+540
+330
+53D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.2555532575
+ 20
+210.3467855475
+ 30
+0.0
+ 11
+194.7580466032
+ 21
+207.3463710546
+ 31
+0.0
+ 12
+195.7580462578
+ 22
+207.3472021123
+ 32
+0.0
+ 13
+195.7580462578
+ 23
+207.3472021123
+ 33
+0.0
+ 0
+SOLID
+ 5
+541
+330
+53D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.3191971725
+ 20
+133.7649839595
+ 30
+0.0
+ 11
+195.8167038268
+ 21
+136.7653984523
+ 31
+0.0
+ 12
+194.8167041722
+ 22
+136.7645673947
+ 32
+0.0
+ 13
+194.8167041722
+ 23
+136.7645673947
+ 33
+0.0
+ 0
+LINE
+ 5
+542
+330
+53D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+148.8555692807
+ 20
+210.3082244719
+ 30
+0.0
+ 11
+196.2555529122
+ 21
+210.3476166051
+ 31
+0.0
+ 0
+LINE
+ 5
+543
+330
+53D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+149.029925238ity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.2877619365
+ 20
+42.2443402896
+ 30
+0.0
+ 40
+0.1421636723032661
+100
+AcDbArc
+ 50
+215.0074908018
+ 51
+359.9928249763
+ 0
+LINE
+ 5
+4E2
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.1713189354
+ 20
+42.1627833327
+ 30
+0.0
+ 11
+148.4940879812
+ 21
+43.1299998244
+ 31
+0.0
+ 0
+ARC
+ 5
+4E3
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5923363035
+ 20
+43.1988138586
+ 30
+0.0
+ 40
+0.1199504236850504
+100
+AcDbArc
+ 50
+136.8044484108
+ 51
+215.0077544772
+ 0
+ARC
+ 5
+4E4
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+147.6299254989
+ 20
+44.1021810107
+ 30
+0.0
+ 40
+1.200014262087228
+100
+AcDbArc
+ 50
+316.8133643338
+ 51
+223.1866356662
+ 0
+ARC
+ 5
+4E5
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+146.667516349
+ 20
+43.1988139771
+ 30
+0.0
+ 40
+0.1199491362526016
+100
+AcDbArc
+ 50
+324.9917457236
+ 51
+43.1960513885
+ 0
+LINE
+ 5
+4E6
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+146.7657630166
+ 20
+43.1299998244
+ 30
+0.0
+ 11
+146.1655196567
+ 21
+42.2727419814
+ 31
+0.0
+ 0
+ARC
+ 5
+4E7
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+145.8378250788
+ 20
+42.5022486061
+ 30
+0.0
+ 40
+0.4000712776530942
+100
+AcDbArc
+ 50
+270.0048248044
+ 51
+324.9938514507
+ 0
+LINE
+ 5
+4E8
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+145.8378587683
+ 20
+42.1021773299
+ 30
+0.0
+ 11
+143.9099256714
+ 21
+42.1021773299
+ 31
+0.0
+ 0
+ARC
+ 5
+4E9
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+143.9098934092
+ 20
+42.1821451244
+ 30
+0.0
+ 40
+0.0799678010352872
+100
+AcDbArc
+ 50
+179.9674137886
+ 51
+270.0231154488
+ 0
+LINE
+ 5
+4EA
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.8299256211
+ 20
+42.1821906051
+ 30
+0.0
+ 11
+143.8299256211
+ 21
+42.7421881637
+ 31
+0.0
+ 0
+ARC
+ 5
+4EB
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+143.9099748178
+ 20
+42.7421522491
+ 30
+0.0
+ 40
+0.080049204801129
+100
+AcDbArc
+ 50
+90.0351768672
+ 51
+179.9742938954
+ 0
+LINE
+ 5
+4EC
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.9099256714
+ 20
+42.8222014388
+ 30
+0.0
+ 11
+145.3970798125
+ 21
+42.8222014388
+ 31
+0.0
+ 0
+ARC
+ 5
+4ED
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+145.3970883777
+ 20
+43.2221900281
+ 30
+0.0
+ 40
+0.3999885893651605
+100
+AcDbArc
+ 50
+269.9987730963
+ 51
+21.5094255355
+ 0
+ARC
+ 5
+4EE
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+147.6299070455
+ 20
+44.102180655
+ 30
+0.0
+ 40
+1.999982807792672
+100
+AcDbArc
+ 50
+166.3277484711
+ 51
+201.510375133
+ 0
+ARC
+ 5
+4EF
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+145.4922360036
+ 20
+44.6221751279
+ 30
+0.0
+ 40
+0.2000244053689678
+100
+AcDbArc
+ 50
+346.3324477609
+ 51
+89.9922055192
+ 0
+LINE
+ 5
+4F0
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+145.4922632148
+ 20
+44.8221995315
+ 30
+0.0
+ 11
+137.3675879856
+ 21
+44.8221995315
+ 31
+0.0
+ 0
+ARC
+ 5
+4F1
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+137.3676151968
+ 20
+44.6221751279
+ 30
+0.0
+ 40
+0.2000244053689526
+100
+AcDbArc
+ 50
+90.0077944808
+ 51
+193.6675522391
+ 0
+ARC
+ 5
+4F2
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+135.2299441549
+ 20
+44.102180655
+ 30
+0.0
+ 40
+1.999982807792147
+100
+AcDbArc
+ 50
+338.489624867
+ 51
+13.672251529
+ 0
+ARC
+ 5
+4F3
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+137.4627628227
+ 20
+43.2221900281
+ 30
+0.0
+ 40
+0.399988589365143
+100
+AcDbArc
+ 50
+158.4905744645
+ 51
+270.0012269037
+ 0
+LINE
+ 5
+4F4
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 8
+370
+ 0
+100
+AcDbLine
+ 10
+148.8260940482
+ 20
+80.1358534774
+ 30
+0.0
+ 11
+148.8260940482
+ 21
+103.7777020415
+ 31
+0.0
+ 0
+LINE
+ 5
+4F5
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 8
+370
+ 0
+100
+AcDbLine
+ 10
+133.4835157563
+ 20
+80.1027132949
+ 30
+0.0
+ 11
+133.4835157563
+ 21
+103.7777020415
+ 31
+0.0
+ 0
+LINE
+ 5
+4F6
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 8
+370
+ 0
+100
+AcDbLine
+ 10
+148.5235550275
+ 20
+81.2455489119
+ 30
+0.0
+ 11
+148.5235550275
+ 21
+103.7777020415
+ 31
+0.0
+ 0
+LINE
+ 5
+4F7
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 8
+370
+ 0
+100
+AcDbLine
+ 10
+148.1099256148
+ 20
+82.7892615279
+ 30
+0.0
+ 11
+148.1099256148
+ 21
+103.7777020415
+ 31
+0.0
+ 0
+POINT
+ 5
+4F8
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+142.0299255838
+ 20
+-247.6988063932
+ 30
+0.0
+ 0
+POINT
+ 5
+4F9
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+149.4168593775
+ 20
+447.9345399732
+ 30
+0.0
+ 0
+POINT
+ 5
+4FA
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+149.4168593775
+ 20
+238.1745399732
+ 30
+0.0
+ 0
+POINT
+ 5
+4FB
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+147.8555696261
+ 20
+245.6618616408
+ 30
+0.0
+ 0
+POINT
+ 5
+4FC
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+147.8555696261
+ 20
+455.4218616408
+ 30
+0.0
+ 0
+POINT
+ 5
+4FD
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+148.0299255838
+ 20
+35.8618616408
+ 30
+0.0
+ 0
+POINT
+ 5
+4FE
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+149.4168593775
+ 20
+28.4145399732
+ 30
+0.0
+ 0
+POINT
+ 5
+4FF
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+133.4299255838
+ 20
+660.3011936068
+ 30
+0.0
+ 0
+DIMENSION
+ 5
+500
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D
+ 10
+195.4299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+195.4299255838
+ 21
+34.5771309945
+ 31
+0.0
+ 70
+ 160
+ 1
+33
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE0
+100
+AcDbAlignedDimension
+ 13
+142.0299255838
+ 23
+28.7821787182
+ 33
+0.0
+ 14
+148.0299255838
+ 24
+41.9821787182
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+50D
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_1
+ 10
+171.4299255838
+ 20
+287.499722104
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+271.6850735678
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+149.4168593775
+ 23
+280.0124004364
+ 33
+0.0
+ 14
+147.8555696261
+ 24
+287.499722104
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+51A
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_2
+ 10
+171.4299255838
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+194.8678981433
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+149.4168593775
+ 23
+202.8200717467
+ 33
+0.0
+ 14
+147.8555696261
+ 24
+210.3073934143
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+526
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_3
+ 10
+171.4299255838
+ 20
+133.7256838344
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+113.1236101725
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+149.4168593775
+ 23
+126.2783621669
+ 33
+0.0
+ 14
+148.0299255838
+ 24
+133.7256838344
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+532
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_4
+ 10
+195.4299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+195.4299255838
+ 21
+84.1586135498
+ 31
+0.0
+ 70
+ 160
+ 1
+675,902
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+148.0299255838
+ 23
+133.7256838344
+ 33
+0.0
+ 14
+148.0299255838
+ 24
+41.9821787182
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+53C
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_5
+ 10
+195.3191971725
+ 20
+133.7649839595
+ 30
+0.0
+ 11
+195.2817915125
+ 21
+178.7746730971
+ 31
+0.0
+ 70
+ 160
+ 1
+524,500
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+147.8555696261
+ 23
+210.3073934143
+ 33
+0.0
+ 14
+148.0299255838
+ 24
+133.7256838344
+ 34
+0.0
+ 50
+-89.9523838979
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+545
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_6
+ 10
+195.2555696261
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+195.2555696261
+ 21
+246.0726158741
+ 31
+0.0
+ 70
+ 160
+ 1
+524,400
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+147.8555696261
+ 23
+287.499722104
+ 33
+0.0
+ 14
+147.8555696261
+ 24
+210.3073934143
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+54E
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_7
+ 10
+195.2555696261
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+195.2555696261
+ 21
+357.7845521004
+ 31
+0.0
+ 70
+ 160
+ 1
+545,698
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+147.8555696261
+ 23
+287.499722104
+ 33
+0.0
+ 14
+145.8378589248
+ 24
+397.2202084954
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+557
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_8
+ 10
+219.4299255838
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+219.4299255838
+ 21
+226.31135989
+ 31
+0.0
+ 70
+ 160
+ 1
+Kozijnmaat =
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+142.0299255838
+ 23
+28.7821787182
+ 33
+0.0
+ 14
+145.8378589248
+ 24
+397.2202084954
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+561
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_9
+ 10
+99.2299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+99.2299255838
+ 21
+214.5361780403
+ 31
+0.0
+ 70
+ 160
+ 1
+DMH =
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+133.4299255838
+ 23
+383.8202084954
+ 33
+0.0
+ 14
+142.0299255838
+ 24
+28.7821787182
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+56C
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_10
+ 10
+117.2299255838
+ 20
+148.4450158005
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+157.3719824392
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+133.5499255838
+ 23
+147.8850158005
+ 33
+0.0
+ 14
+133.5499255838
+ 24
+148.4450158005
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+578
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_11
+ 10
+117.2299255838
+ 20
+224.9867253803
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+236.5334651005
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+133.5499255838
+ 23
+224.4267253803
+ 33
+0.0
+ 14
+133.5499255838
+ 24
+224.9867253803
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+584
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_12
+ 10
+117.2299255838
+ 20
+302.17905407
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+311.8110919194
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+133.5499255838
+ 23
+301.61905407
+ 33
+0.0
+ 14
+133.5499255838
+ 24
+302.17905407
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+LINE
+ 5
+590
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+103.7777066237
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+103.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+591
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+103.7777066237
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+105.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+592
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+105.7777066237
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+103.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+593
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+103.7777066237
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+101.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+594
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+101.7777066237
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+103.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+595
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+103.7777066237
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+103.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+596
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+105.7777066237
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+105.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+597
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+105.7777066237
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+107.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+598
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+107.7777066237
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+105.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+599
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+105.7777066237
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+103.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+59A
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+103.7777066237
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+105.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+59B
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+105.7777066237
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+105.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+59C
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+179.2366515846
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+179.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+59D
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+179.2366515846
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+181.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+59E
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+181.2366515846
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+179.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+59F
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+179.2366515846
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+177.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A0
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+177.2366515846
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+179.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A1
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+179.2366515846
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+179.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A2
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+181.2366515846
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+181.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A3
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+181.2366515846
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+183.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A4
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+183.2366515846
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+181.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A5
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+181.2366515846
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+179.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A6
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+179.2366515846
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+181.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A7
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+181.2366515846
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+181.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A8
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+256.4750445598
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+256.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5A9
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+256.4750445598
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+258.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AA
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+258.4750445598
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+256.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AB
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+256.4750445598
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+254.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AC
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+254.4750445598
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+256.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AD
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+256.4750445598
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+256.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AE
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+258.4750445598
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+258.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AF
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+258.4750445598
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+260.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5B0
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+260.4750445598
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+258.4750.9299255838
+ 22
+199.8200717467
+ 32
+0.0
+ 13
+171.9299255838
+ 23
+199.8200717467
+ 33
+0.0
+ 0
+SOLID
+ 5
+51F
+330
+51B
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+171.4299255838
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+171.9299255838
+ 21
+213.3073934143
+ 31
+0.0
+ 12
+170.9299255838
+ 22
+213.3073934143
+ 32
+0.0
+ 13
+170.9299255838
+ 23
+213.3073934143
+ 33
+0.0
+ 0
+LINE
+ 5
+520
+330
+51B
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+150.4168593775
+ 20
+202.8200717467
+ 30
+0.0
+ 11
+172.4299255838
+ 21
+202.8200717467
+ 31
+0.0
+ 0
+LINE
+ 5
+521
+330
+51B
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+148.8555696261
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+172.4299255838
+ 21
+210.3073934143
+ 31
+0.0
+ 0
+LINE
+ 5
+522
+330
+51B
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+202.8200717467
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+190.8599118622
+ 31
+0.0
+ 0
+LINE
+ 5
+523
+330
+51B
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+202.8200717467
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+210.3073934143
+ 31
+0.0
+ 0
+LINE
+ 5
+524
+330
+51B
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+216.3073934143
+ 31
+0.0
+ 0
+MTEXT
+ 5
+525
+330
+51B
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+166.9181886692
+ 20
+190.8599118622
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+19
+ 7
+SLDTEXTSTYLE0
+ 11
+6.123233995736766E-17
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+51D
+330
+51B
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+528
+330
+527
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_3
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_3
+ 1
+
+ 0
+SOLID
+ 5
+52A
+330
+527
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+171.4299255838
+ 20
+126.2783621669
+ 30
+0.0
+ 11
+170.9299255838
+ 21
+123.2783621669
+ 31
+0.0
+ 12
+171.9299255838
+ 22
+123.2783621669
+ 32
+0.0
+ 13
+171.9299255838
+ 23
+123.2783621669
+ 33
+0.0
+ 0
+SOLID
+ 5
+52B
+330
+527
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+171.4299255838
+ 20
+133.7256838344
+ 30
+0.0
+ 11
+171.9299255838
+ 21
+136.7256838344
+ 31
+0.0
+ 12
+170.9299255838
+ 22
+136.7256838344
+ 32
+0.0
+ 13
+170.9299255838
+ 23
+136.7256838344
+ 33
+0.0
+ 0
+LINE
+ 5
+52C
+330
+527
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+150.4168593775
+ 20
+126.2783621669
+ 30
+0.0
+ 11
+172.4299255838
+ 21
+126.2783621669
+ 31
+0.0
+ 0
+LINE
+ 5
+52D
+330
+527
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+149.0299255838
+ 20
+133.7256838344
+ 30
+0.0
+ 11
+172.4299255838
+ 21
+133.7256838344
+ 31
+0.0
+ 0
+LINE
+ 5
+52E
+330
+527
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+126.2783621669
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+109.1156238914
+ 31
+0.0
+ 0
+LINE
+ 5
+52F
+330
+527
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+126.2783621669
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+133.7256838344
+ 31
+0.0
+ 0
+LINE
+ 5
+530
+330
+527
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+171.4299255838
+ 20
+133.7256838344
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+139.7256838344
+ 31
+0.0
+ 0
+MTEXT
+ 5
+531
+330
+527
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+166.9181886692
+ 20
+109.1156238914
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+19
+ 7
+SLDTEXTSTYLE0
+ 11
+6.123233995736766E-17
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+529
+330
+527
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+535
+330
+534
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_4
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_4
+ 1
+
+ 0
+SOLID
+ 5
+537
+330
+534
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.4299255838
+ 20
+133.7256838344
+ 30
+0.0
+ 11
+194.9299255838
+ 21
+130.7256838344
+ 31
+0.0
+ 12
+195.9299255838
+ 22
+130.7256838344
+ 32
+0.0
+ 13
+195.9299255838
+ 23
+130.7256838344
+ 33
+0.0
+ 0
+SOLID
+ 5
+538
+330
+534
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.4299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+195.9299255838
+ 21
+44.9821787182
+ 31
+0.0
+ 12
+194.9299255838
+ 22
+44.9821787182
+ 32
+0.0
+ 13
+194.9299255838
+ 23
+44.9821787182
+ 33
+0.0
+ 0
+LINE
+ 5
+539
+330
+534
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+149.0299255838
+ 20
+133.7256838344
+ 30
+0.0
+ 11
+196.4299255838
+ 21
+133.7256838344
+ 31
+0.0
+ 0
+LINE
+ 5
+53A
+330
+534
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+149.0299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+196.4299255838
+ 21
+41.9821787182
+ 31
+0.0
+ 0
+LINE
+ 5
+53B
+330
+534
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+195.4299255838
+ 20
+133.7256838344
+ 30
+0.0
+ 11
+195.4299255838
+ 21
+41.9821787182
+ 31
+0.0
+ 0
+ENDBLK
+ 5
+536
+330
+534
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+53E
+330
+53D
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_5
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_5
+ 1
+
+ 0
+SOLID
+ 5
+540
+330
+53D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.2555532575
+ 20
+210.3467855475
+ 30
+0.0
+ 11
+194.7580466032
+ 21
+207.3463710546
+ 31
+0.0
+ 12
+195.7580462578
+ 22
+207.3472021123
+ 32
+0.0
+ 13
+195.7580462578
+ 23
+207.3472021123
+ 33
+0.0
+ 0
+SOLID
+ 5
+541
+330
+53D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.3191971725
+ 20
+133.7649839595
+ 30
+0.0
+ 11
+195.8167038268
+ 21
+136.7653984523
+ 31
+0.0
+ 12
+194.8167041722
+ 22
+136.7645673947
+ 32
+0.0
+ 13
+194.8167041722
+ 23
+136.7645673947
+ 33
+0.0
+ 0
+LINE
+ 5
+542
+330
+53D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+148.8555692807
+ 20
+210.3082244719
+ 30
+0.0
+ 11
+196.2555529122
+ 21
+210.3476166051
+ 31
+0.0
+ 0
+LINE
+ 5
+543
+330
+53D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+149.0299252385
+ 20
+133.7265148921
+ 30
+0.0
+ 11
+196.3191968272
+ 21
+133.7658150172
+ 31
+0.0
+ 0
+LINE
+ 5
+544
+330
+53D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+195.2555532575
+ 20
+210.3467855475
+ 30
+0.0
+ 11
+195.3191971725
+ 21
+133.7649839595
+ 31
+0.0
+ 0
+ENDBLK
+ 5
+53F
+330
+53D
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+547
+330
+546
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_6
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_6
+ 1
+
+ 0
+SOLID
+ 5
+549
+330
+546
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.2555696261
+ 20
+287.499722104
+ 30
+0.0
+ 11
+194.7555696261
+ 21
+284.499722104
+ 31
+0.0
+ 12
+195.7555696261
+ 22
+284.499722104
+ 32
+0.0
+ 13
+195.7555696261
+ 23
+284.499722104
+ 33
+0.0
+ 0
+SOLID
+ 5
+54A
+330
+546
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.2555696261
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+195.7555696261
+ 21
+213.3073934143
+ 31
+0.0
+ 12
+194.7555696261
+ 22
+213.3073934143
+ 32
+0.0
+ 13
+194.7555696261
+ 23
+213.3073934143
+ 33
+0.0
+ 0
+LINE
+ 5
+54B
+330
+546
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+148.8555696261
+ 20
+287.499722104
+ 30
+0.0
+ 11
+196.2555696261
+ 21
+287.499722104
+ 31
+0.0
+ 0
+LINE
+ 5
+54C
+330
+546
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+148.8555696261
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+196.2555696261
+ 21
+210.3073934143
+ 31
+0.0
+ 0
+LINE
+ 5
+54D
+330
+546
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+195.2555696261
+ 20
+287.499722104
+ 30
+0.0
+ 11
+195.2555696261
+ 21
+210.3073934143
+ 31
+0.0
+ 0
+ENDBLK
+ 5
+548
+330
+546
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+550
+330
+54F
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_7
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_7
+ 1
+
+ 0
+SOLID
+ 5
+552
+330
+54F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.2555696261
+ 20
+287.499722104
+ 30
+0.0
+ 11
+195.7555696261
+ 21
+290.499722104
+ 31
+0.0
+ 12
+194.7555696261
+ 22
+290.499722104
+ 32
+0.0
+ 13
+194.7555696261
+ 23
+290.499722104
+ 33
+0.0
+ 0
+SOLID
+ 5
+553
+330
+54F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+195.2555696261
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+194.7555696261
+ 21
+394.2202084954
+ 31
+0.0
+ 12
+195.7555696261
+ 22
+394.2202084954
+ 32
+0.0
+ 13
+195.7555696261
+ 23
+394.2202084954
+ 33
+0.0
+ 0
+LINE
+ 5
+554
+330
+54F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+148.8555696261
+ 20
+287.499722104
+ 30
+0.0
+ 11
+196.2555696261
+ 21
+287.499722104
+ 31
+0.0
+ 0
+LINE
+ 5
+555
+330
+54F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+146.8378589248
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+196.2555696261
+ 21
+397.2202084954
+ 31
+0.0
+ 0
+LINE
+ 5
+556
+330
+54F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+195.2555696261
+ 20
+287.499722104
+ 30
+0.0
+ 11
+195.2555696261
+ 21
+397.2202084954
+ 31
+0.0
+ 0
+ENDBLK
+ 5
+551
+330
+54F
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+559
+330
+558
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_8
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_8
+ 1
+
+ 0
+SOLID
+ 5
+55B
+330
+558
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+219.4299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+219.9299255838
+ 21
+31.7821787182
+ 31
+0.0
+ 12
+218.9299255838
+ 22
+31.7821787182
+ 32
+0.0
+ 13
+218.9299255838
+ 23
+31.7821787182
+ 33
+0.0
+ 0
+SOLID
+ 5
+55C
+330
+558
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+219.4299255838
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+218.9299255838
+ 21
+394.2202084954
+ 31
+0.0
+ 12
+219.9299255838
+ 22
+394.2202084954
+ 32
+0.0
+ 13
+219.9299255838
+ 23
+394.2202084954
+ 33
+0.0
+ 0
+LINE
+ 5
+55D
+330
+558
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+143.0299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+220.4299255838
+ 21
+28.7821787182
+ 31
+0.0
+ 0
+LINE
+ 5
+55E
+330
+558
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+146.8378589248
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+220.4299255838
+ 21
+397.2202084954
+ 31
+0.0
+ 0
+LINE
+ 5
+55F
+330
+558
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+219.4299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+219.4299255838
+ 21
+397.2202084954
+ 31
+0.0
+ 0
+MTEXT
+ 5
+560
+330
+558
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+214.9181886692
+ 20
+211.8228183688
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+Kozijnmaat =
+ 7
+SLDTEXTSTYLE0
+ 11
+6.123233995736766E-17
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+55A
+330
+558
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+563
+330
+562
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_9
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_9
+ 1
+
+ 0
+SOLID
+ 5
+565
+330
+562
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+99.2299255838
+ 20
+383.8202084954
+ 30
+0.0
+ 11
+98.7299255838
+ 21
+380.8202084954
+ 31
+0.0
+ 12
+99.7299255838
+ 22
+380.8202084954
+ 32
+0.0
+ 13
+99.7299255838
+ 23
+380.8202084954
+ 33
+0.0
+ 0
+SOLID
+ 5
+566
+330
+562
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+99.2299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+99.7299255838
+ 21
+31.7821787182
+ 31
+0.0
+ 12
+98.7299255838
+ 22
+31.7821787182
+ 32
+0.0
+ 13
+98.7299255838
+ 23
+31.7821787182
+ 33
+0.0
+ 0
+LINE
+ 5
+567
+330
+562
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+132.4299255838
+ 20
+383.8202084954
+ 30
+0.0
+ 11
+98.2299255838
+ 21
+383.8202084954
+ 31
+0.0
+ 0
+LINE
+ 5
+568
+330
+562
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+141.0299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+98.2299255838
+ 21
+28.7821787182
+ 31
+0.0
+ 0
+LINE
+ 5
+569
+330
+562
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+99.2299255838
+ 20
+383.8202084954
+ 30
+0.0
+ 11
+99.2299255838
+ 21
+28.7821787182
+ 31
+0.0
+ 0
+MTEXT
+ 5
+56B
+330
+562
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+94.7181886692
+ 20
+207.001456174
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+DMH =
+ 7
+SLDTEXTSTYLE1
+ 11
+-7.044195017643634E-15
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+564
+330
+562
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+56E
+330
+56D
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_10
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_10
+ 1
+
+ 0
+SOLID
+ 5
+570
+330
+56D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+117.2299255838
+ 20
+147.8850158005
+ 30
+0.0
+ 11
+116.7299255838
+ 21
+144.8850158005
+ 31
+0.0
+ 12
+117.7299255838
+ 22
+144.8850158005
+ 32
+0.0
+ 13
+117.7299255838
+ 23
+144.8850158005
+ 33
+0.0
+ 0
+SOLID
+ 5
+571
+330
+56D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+117.2299255838
+ 20
+148.4450158005
+ 30
+0.0
+ 11
+117.7299255838
+ 21
+151.4450158005
+ 31
+0.0
+ 12
+116.7299255838
+ 22
+151.4450158005
+ 32
+0.0
+ 13
+116.7299255838
+ 23
+151.4450158005
+ 33
+0.0
+ 0
+LINE
+ 5
+572
+330
+56D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+132.5499255838
+ 20
+147.8850158005
+ 30
+0.0
+ 11
+116.2299255838
+ 21
+147.8850158005
+ 31
+0.0
+ 0
+LINE
+ 5
+573
+330
+56D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+132.5499255838
+ 20
+148.4450158005
+ 30
+0.0
+ 11
+116.2299255838
+ 21
+148.4450158005
+ 31
+0.0
+ 0
+LINE
+ 5
+574
+330
+56D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+147.8850158005
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+141.8850158005
+ 31
+0.0
+ 0
+LINE
+ 5
+575
+330
+56D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+147.8850158005
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+148.4450158005
+ 31
+0.0
+ 0
+LINE
+ 5
+576
+330
+56D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+148.4450158005
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+160.0869130071
+ 31
+0.0
+ 0
+MTEXT
+ 5
+577
+330
+56D
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+112.7181886692
+ 20
+154.6570518713
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+1
+ 7
+SLDTEXTSTYLE0
+ 11
+-7.044195017643634E-15
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+56F
+330
+56D
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+57A
+330
+579
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_11
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_11
+ 1
+
+ 0
+SOLID
+ 5
+57C
+330
+579
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+117.2299255838
+ 20
+224.4267253803
+ 30
+0.0
+ 11
+116.7299255838
+ 21
+221.4267253803
+ 31
+0.0
+ 12
+117.7299255838
+ 22
+221.4267253803
+ 32
+0.0
+ 13
+117.7299255838
+ 23
+221.4267253803
+ 33
+0.0
+ 0
+SOLID
+ 5
+57D
+330
+579
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+117.2299255838
+ 20
+224.9867253803
+ 30
+0.0
+ 11
+117.7299255838
+ 21
+227.9867253803
+ 31
+0.0
+ 12
+116.7299255838
+ 22
+227.9867253803
+ 32
+0.0
+ 13
+116.7299255838
+ 23
+227.9867253803
+ 33
+0.0
+ 0
+LINE
+ 5
+57E
+330
+579
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+132.5499255838
+ 20
+224.4267253803
+ 30
+0.0
+ 11
+116.2299255838
+ 21
+224.4267253803
+ 31
+0.0
+ 0
+LINE
+ 5
+57F
+330
+579
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+132.5499255838
+ 20
+224.9867253803
+ 30
+0.0
+ 11
+116.2299255838
+ 21
+224.9867253803
+ 31
+0.0
+ 0
+LINE
+ 5
+580
+330
+579
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+224.4267253803
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+218.4267253803
+ 31
+0.0
+ 0
+LINE
+ 5
+581
+330
+579
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+224.4267253803
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+224.9867253803
+ 31
+0.0
+ 0
+LINE
+ 5
+582
+330
+579
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+224.9867253803
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+239.2483956684
+ 31
+0.0
+ 0
+MTEXT
+ 5
+583
+330
+579
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+112.7181886692
+ 20
+233.8185345326
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+1
+ 7
+SLDTEXTSTYLE0
+ 11
+-7.044195017643634E-15
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+57B
+330
+579
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+586
+330
+585
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+_D_12
+ 70
+ 0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 3
+_D_12
+ 1
+
+ 0
+SOLID
+ 5
+588
+330
+585
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+117.2299255838
+ 20
+301.61905407
+ 30
+0.0
+ 11
+116.7299255838
+ 21
+298.61905407
+ 31
+0.0
+ 12
+117.7299255838
+ 22
+298.61905407
+ 32
+0.0
+ 13
+117.7299255838
+ 23
+298.61905407
+ 33
+0.0
+ 0
+SOLID
+ 5
+589
+330
+585
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbTrace
+ 10
+117.2299255838
+ 20
+302.17905407
+ 30
+0.0
+ 11
+117.7299255838
+ 21
+305.17905407
+ 31
+0.0
+ 12
+116.7299255838
+ 22
+305.17905407
+ 32
+0.0
+ 13
+116.7299255838
+ 23
+305.17905407
+ 33
+0.0
+ 0
+LINE
+ 5
+58A
+330
+585
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+132.5499255838
+ 20
+301.61905407
+ 30
+0.0
+ 11
+116.2299255838
+ 21
+301.61905407
+ 31
+0.0
+ 0
+LINE
+ 5
+58B
+330
+585
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+132.5499255838
+ 20
+302.17905407
+ 30
+0.0
+ 11
+116.2299255838
+ 21
+302.17905407
+ 31
+0.0
+ 0
+LINE
+ 5
+58C
+330
+585
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+301.61905407
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+295.61905407
+ 31
+0.0
+ 0
+LINE
+ 5
+58D
+330
+585
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+301.61905407
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+302.17905407
+ 31
+0.0
+ 0
+LINE
+ 5
+58E
+330
+585
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+117.2299255838
+ 20
+302.17905407
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+314.5260224873
+ 31
+0.0
+ 0
+MTEXT
+ 5
+58F
+330
+585
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+112.7181886692
+ 20
+309.0961613515
+ 30
+0.04
+ 40
+3.5
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+1
+ 7
+SLDTEXTSTYLE0
+ 11
+-7.044195017643634E-15
+ 21
+1.0
+ 31
+0.0
+ 73
+ 1
+ 44
+1.0
+ 0
+ENDBLK
+ 5
+587
+330
+585
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+ENDSEC
+ 0
+SECTION
+ 2
+ENTITIES
+ 0
+LINE
+ 5
+4B
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbLine
+ 10
+123.9140543182
+ 20
+383.8493547742
+ 30
+0.0
+ 11
+160.598524565
+ 21
+383.8493547742
+ 31
+0.0
+ 0
+LINE
+ 5
+4C
+330
+1F
+100
+AcDbEntity
+ 8
+STUKLIJST
+100
+AcDbLine
+ 10
+10.3087825773
+ 20
+9.2198227322
+ 30
+0.0
+ 11
+10.3087825773
+ 21
+409.2698227322
+ 31
+0.0
+ 0
+LINE
+ 5
+4D
+330
+1F
+100
+AcDbEntity
+ 8
+STUKLIJST
+100
+AcDbLine
+ 10
+10.3087825773
+ 20
+9.2198227322
+ 30
+0.0
+ 11
+287.198438446
+ 21
+9.2198227322
+ 31
+0.0
+ 0
+LINE
+ 5
+4E
+330
+1F
+100
+AcDbEntity
+ 8
+STUKLIJST
+100
+AcDbLine
+ 10
+287.198438446
+ 20
+409.2698227322
+ 30
+0.0
+ 11
+287.198438446
+ 21
+9.2198227322
+ 31
+0.0
+ 0
+LINE
+ 5
+4F
+330
+1F
+100
+AcDbEntity
+ 8
+STUKLIJST
+100
+AcDbLine
+ 10
+287.198438446
+ 20
+409.2698227322
+ 30
+0.0
+ 11
+10.3087825773
+ 21
+409.2698227322
+ 31
+0.0
+ 0
+LINE
+ 5
+50
+330
+1F
+100
+AcDbEntity
+ 8
+MAATLIJN
+100
+AcDbLine
+ 10
+4.3087825773
+ 20
+3.2198227322
+ 30
+0.0
+ 11
+4.3087825773
+ 21
+415.2698227322
+ 31
+0.0
+ 0
+LINE
+ 5
+51
+330
+1F
+100
+AcDbEntity
+ 8
+MAATLIJN
+100
+AcDbLine
+ 10
+4.3087825773
+ 20
+415.2698227322
+ 30
+0.0
+ 11
+293.4090035115
+ 21
+415.2698227322
+ 31
+0.0
+ 0
+LINE
+ 5
+52
+330
+1F
+100
+AcDbEntity
+ 8
+MAATLIJN
+100
+AcDbLine
+ 10
+293.4090035115
+ 20
+415.2698227322
+ 30
+0.0
+ 11
+293.4090035115
+ 21
+3.2198227322
+ 31
+0.0
+ 0
+LINE
+ 5
+53
+330
+1F
+100
+AcDbEntity
+ 8
+MAATLIJN
+100
+AcDbLine
+ 10
+293.4090035115
+ 20
+3.2198227322
+ 30
+0.0
+ 11
+4.3087825773
+ 21
+3.2198227322
+ 31
+0.0
+ 0
+MTEXT
+ 5
+55
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbMText
+ 10
+211.9617900851
+ 20
+18.9651870984
+ 30
+0.0
+ 40
+4.2333333333
+ 41
+0.0
+ 71
+ 1
+ 72
+ 1
+ 1
+Alum. deurblad - 4 secties
+ 7
+SLDTEXTSTYLE0
+ 73
+ 1
+ 44
+1.0
+ 0
+INSERT
+ 5
+59
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+100
+AcDbBlockReference
+ 2
+SW_BROKEN_VIEW_0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+ 0
+LINE
+ 5
+5A
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.9420049658
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+148.9420049658
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+5B
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.1445668807
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+149.1445668807
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+5C
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.0406432023
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+149.0406432023
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+5D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.0406432023
+ 20
+359.2202013088
+ 30
+0.0
+ 11
+148.9420049658
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+5E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.9137577285
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+133.9137577285
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+5F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.797846066
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+133.797846066
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+60
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.6992078294
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+133.6992078294
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+61
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.797846066
+ 20
+359.2202013088
+ 30
+0.0
+ 11
+133.6992078294
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+62
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.5952848961
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+133.5952848961
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+ARC
+ 5
+63
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.1879324767
+ 20
+359.1402151319
+ 30
+0.0
+ 40
+0.0799861772139263
+100
+AcDbArc
+ 50
+31.2190407028
+ 51
+90.0049728915
+ 0
+LINE
+ 5
+64
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.2563360204
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+149.2563360204
+ 21
+359.1816728664
+ 31
+0.0
+ 0
+LINE
+ 5
+65
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2162960043
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+134.2162960043
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+66
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2162960043
+ 20
+359.2202013088
+ 30
+0.0
+ 11
+134.1499254873
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+67
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+133.4299252202
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+133.4299252202
+ 21
+359.1907804561
+ 31
+0.0
+ 0
+ARC
+ 5
+68
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+127.793914781
+ 20
+346.5773729462
+ 30
+0.0
+ 40
+24.8898367051569
+100
+AcDbArc
+ 50
+30.3127339119
+ 51
+30.4217624082
+ 0
+LINE
+ 5
+69
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.3099258115
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+149.3099258115
+ 21
+359.1109102321
+ 31
+0.0
+ 0
+LINE
+ 5
+6A
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.2563360204
+ 20
+359.1816728664
+ 30
+0.0
+ 11
+149.2569536736
+ 21
+359.1806238247
+ 31
+0.0
+ 0
+LINE
+ 5
+6B
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2299253514
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+134.2299253514
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+6C
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.2299253514
+ 20
+359.2202013088
+ 30
+0.0
+ 11
+134.2162960043
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+6D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.5099256804
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+148.5099256804
+ 21
+358.0259626461
+ 31
+0.0
+ 0
+LINE
+ 5
+6E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.6299254169
+ 20
+336.7369001461
+ 30
+0.0
+ 11
+134.6299254169
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+6F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+134.6299254169
+ 20
+359.2202013088
+ 30
+0.0
+ 11
+134.2299253514
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+70
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.0299256762
+ 20
+359.2202013088
+ 30
+0.0
+ 11
+143.0299256762
+ 21
+353.6202257229
+ 31
+0.0
+ 0
+LINE
+ 5
+71
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.0299256762
+ 20
+353.6202257229
+ 30
+0.0
+ 11
+147.3280240615
+ 21
+353.6202257229
+ 31
+0.0
+ 0
+LINE
+ 5
+72
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+147.3280240615
+ 20
+353.6202257229
+ 30
+0.0
+ 11
+147.5545917471
+ 21
+354.4657533718
+ 31
+0.0
+ 0
+LINE
+ 5
+73
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+141.5499255826
+ 20
+359.2202013088
+ 30
+0.0
+ 11
+141.3099255843
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+LINE
+ 5
+74
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+138.9099255969
+ 20
+359.2202013088
+ 30
+0.0
+ 11
+134.6299254169
+ 21
+359.2202013088
+ 31
+0.0
+ 0
+ARC
+ 5
+75
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+137.1595398236
+ 20
+361.9409267861
+ 30
+0.0
+ 40
+0.3992677940247622
+100
+AcDbArc
+ 50
+89.9681903289
+ 51
+114.7477262168
+ 0
+LINE
+ 5
+76
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+137.1597614904
+ 20
+362.3401945186
+ 30
+0.0
+ 11
+139.7899255922
+ 21
+362.3401945186
+ 31
+0.0
+ 0
+ARC
+ 5
+77
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+139.7899227961
+ 20
+361.9401917488
+ 30
+0.0
+ 40
+0.4000027697985571
+100
+AcDbArc
+ 50
+0.0045494665
+ 51
+89.9995994903
+ 0
+LINE
+ 5
+78
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+140.1899255646
+ 20
+361.9402235103
+ 30
+0.0
+ 11
+140.1899255646
+ 21
+360.4202097011
+ 31
+0.0
+ 0
+ARC
+ 5
+79
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+140.5899283325
+ 20
+360.4201937773
+ 30
+0.0
+ 40
+0.4000027681863966
+100
+AcDbArc
+ 50
+179.9977190938
+ 51
+269.999606256
+ 0
+LINE
+ 5
+7A
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+140.5899255836
+ 20
+360.0201910091
+ 30
+0.0
+ 11
+142.2699255703
+ 21
+360.0201910091
+ 31
+0.0
+ 0
+ARC
+ 5
+7B
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+142.2699229338
+ 20
+360.4201936183
+ 30
+0.0
+ 40
+0.4000026092087915
+100
+AcDbArc
+ 50
+270.0003776416
+ 51
+0.0023036787
+ 0
+LINE
+ 5
+7C
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+142.6699255427
+ 20
+360.4202097011
+ 30
+0.0
+ 11
+142.6699255427
+ 21
+361.9402235103
+ 31
+0.0
+ 0
+ARC
+ 5
+7D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+143.0699283112
+ 20
+361.9401917488
+ 30
+0.0
+ 40
+0.4000027697974622
+100
+AcDbArc
+ 50
+90.0003871694
+ 51
+179.9954505335
+ 0
+LINE
+ 5
+7E
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.0699256082
+ 20
+362.3401945186
+ 30
+0.0
+ 11
+148.5899255445
+ 21
+362.3401945186
+ 31
+0.0
+ 0
+ARC
+ 5
+7F
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5899336175
+ 20
+362.4601862448
+ 30
+0.0
+ 40
+0.1199917263967713
+100
+AcDbArc
+ 50
+269.9961451292
+ 51
+0.0134590356
+ 0
+LINE
+ 5
+80
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.7099253406
+ 20
+362.4602144314
+ 30
+0.0
+ 11
+148.7099253406
+ 21
+366.669732864
+ 31
+0.0
+ 0
+ARC
+ 5
+81
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5098175737
+ 20
+366.669705415
+ 30
+0.0
+ 40
+0.2001077687858761
+100
+AcDbArc
+ 50
+0.0078593224
+ 51
+46.1736252111
+ 0
+ARC
+ 5
+82
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.229892261
+ 20
+367.4202030254
+ 30
+0.0
+ 40
+0.8399663770856335
+100
+AcDbArc
+ 50
+133.8120979057
+ 51
+226.1879020943
+ 0
+ARC
+ 5
+83
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5098175737
+ 20
+368.1707006358
+ 30
+0.0
+ 40
+0.200107768785933
+100
+AcDbArc
+ 50
+313.8263747889
+ 51
+359.9921406775
+ 0
+LINE
+ 5
+84
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.7099253406
+ 20
+368.1706731868
+ 30
+0.0
+ 11
+148.7099253406
+ 21
+370.3305070949
+ 31
+0.0
+ 0
+ARC
+ 5
+85
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5900202217
+ 20
+370.3305234822
+ 30
+0.0
+ 40
+0.1199051200450481
+100
+AcDbArc
+ 50
+359.9921694514
+ 51
+45.0183972297
+ 0
+LINE
+ 5
+86
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.6747787168
+ 20
+370.4153364254
+ 30
+0.0
+ 11
+148.5050731172
+ 21
+370.58504277
+ 31
+0.0
+ 0
+ARC
+ 5
+87
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5900489089
+ 20
+370.6699467703
+ 30
+0.0
+ 40
+0.1201231635795685
+100
+AcDbArc
+ 50
+180.0128717119
+ 51
+224.9757867508
+ 0
+LINE
+ 5
+88
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+148.4699257483
+ 20
+370.669919ontinuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.0299484826
+ 20
+72.3821479784
+ 30
+0.0
+ 40
+0.399977127854635
+100
+AcDbArc
+ 50
+292.6176918057
+ 51
+0.0067619015
+ 0
+ARC
+ 5
+4D2
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.2299252955
+ 20
+71.9021632155
+ 30
+0.0
+ 40
+0.1199998892853113
+100
+AcDbArc
+ 50
+112.6196870788
+ 51
+247.3803129212
+ 0
+ARC
+ 5
+4D3
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.0299484881
+ 20
+71.4221784593
+ 30
+0.0
+ 40
+0.3999771195634846
+100
+AcDbArc
+ 50
+0.0000677203
+ 51
+67.382308554
+ 0
+LINE
+ 5
+4D4
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.4299256077
+ 20
+71.4221789321
+ 30
+0.0
+ 11
+149.4299256077
+ 21
+68.8558890304
+ 31
+0.0
+ 0
+ARC
+ 5
+4D5
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.3098934248
+ 20
+68.8558875882
+ 30
+0.0
+ 40
+0.1200321828299822
+100
+AcDbArc
+ 50
+284.0483029705
+ 51
+0.0006883757
+ 0
+LINE
+ 5
+4D6
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.3390300145
+ 20
+68.7394453963
+ 30
+0.0
+ 11
+149.2808216085
+ 21
+68.724901863
+ 31
+0.0
+ 0
+ARC
+ 5
+4D7
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.3099570381
+ 20
+68.6084597995
+ 30
+0.0
+ 40
+0.1200317766406951
+100
+AcDbArc
+ 50
+104.0477806555
+ 51
+179.9779884139
+ 0
+LINE
+ 5
+4D8
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.1899252703
+ 20
+68.6085059127
+ 30
+0.0
+ 11
+149.1899252703
+ 21
+67.5958945235
+ 31
+0.0
+ 0
+ARC
+ 5
+4D9
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.309957026
+ 20
+67.5958929344
+ 30
+0.0
+ 40
+0.1200317557485991
+100
+AcDbArc
+ 50
+179.999241474
+ 51
+255.952222776
+ 0
+LINE
+ 5
+4DA
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.2808216085
+ 20
+67.4794508895
+ 30
+0.0
+ 11
+149.3390300145
+ 21
+67.4649073562
+ 31
+0.0
+ 0
+ARC
+ 5
+4DB
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.3098934248
+ 20
+67.3484651642
+ 30
+0.0
+ 40
+0.1200321828298969
+100
+AcDbArc
+ 50
+359.9993116244
+ 51
+75.9516970296
+ 0
+LINE
+ 5
+4DC
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.4299256077
+ 20
+67.3484637221
+ 30
+0.0
+ 11
+149.4299256077
+ 21
+64.7821738204
+ 31
+0.0
+ 0
+ARC
+ 5
+4DD
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.0299484881
+ 20
+64.7821742931
+ 30
+0.0
+ 40
+0.3999771195634605
+100
+AcDbArc
+ 50
+292.617691446
+ 51
+359.9999322797
+ 0
+ARC
+ 5
+4DE
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.2299252955
+ 20
+64.3021895369
+ 30
+0.0
+ 40
+0.1199998892853769
+100
+AcDbArc
+ 50
+112.6196870788
+ 51
+247.3803129212
+ 0
+ARC
+ 5
+4DF
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.0299484826
+ 20
+63.8222047741
+ 30
+0.0
+ 40
+0.3999771278546918
+100
+AcDbArc
+ 50
+359.9932380985
+ 51
+67.3823081943
+ 0
+LINE
+ 5
+4E0
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.4299256077
+ 20
+63.8221575698
+ 30
+0.0
+ 11
+149.4299256077
+ 21
+42.2443224868
+ 31
+0.0
+ 0
+ARC
+ 5
+4E1
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+149.2877619365
+ 20
+42.2443402896
+ 30
+0.0
+ 40
+0.1421636723032661
+100
+AcDbArc
+ 50
+215.0074908018
+ 51
+359.9928249763
+ 0
+LINE
+ 5
+4E2
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+149.1713189354
+ 20
+42.1627833327
+ 30
+0.0
+ 11
+148.4940879812
+ 21
+43.1299998244
+ 31
+0.0
+ 0
+ARC
+ 5
+4E3
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+148.5923363035
+ 20
+43.1988138586
+ 30
+0.0
+ 40
+0.1199504236850504
+100
+AcDbArc
+ 50
+136.8044484108
+ 51
+215.0077544772
+ 0
+ARC
+ 5
+4E4
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+147.6299254989
+ 20
+44.1021810107
+ 30
+0.0
+ 40
+1.200014262087228
+100
+AcDbArc
+ 50
+316.8133643338
+ 51
+223.1866356662
+ 0
+ARC
+ 5
+4E5
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+146.667516349
+ 20
+43.1988139771
+ 30
+0.0
+ 40
+0.1199491362526016
+100
+AcDbArc
+ 50
+324.9917457236
+ 51
+43.1960513885
+ 0
+LINE
+ 5
+4E6
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+146.7657630166
+ 20
+43.1299998244
+ 30
+0.0
+ 11
+146.1655196567
+ 21
+42.2727419814
+ 31
+0.0
+ 0
+ARC
+ 5
+4E7
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+145.8378250788
+ 20
+42.5022486061
+ 30
+0.0
+ 40
+0.4000712776530942
+100
+AcDbArc
+ 50
+270.0048248044
+ 51
+324.9938514507
+ 0
+LINE
+ 5
+4E8
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+145.8378587683
+ 20
+42.1021773299
+ 30
+0.0
+ 11
+143.9099256714
+ 21
+42.1021773299
+ 31
+0.0
+ 0
+ARC
+ 5
+4E9
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+143.9098934092
+ 20
+42.1821451244
+ 30
+0.0
+ 40
+0.0799678010352872
+100
+AcDbArc
+ 50
+179.9674137886
+ 51
+270.0231154488
+ 0
+LINE
+ 5
+4EA
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.8299256211
+ 20
+42.1821906051
+ 30
+0.0
+ 11
+143.8299256211
+ 21
+42.7421881637
+ 31
+0.0
+ 0
+ARC
+ 5
+4EB
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+143.9099748178
+ 20
+42.7421522491
+ 30
+0.0
+ 40
+0.080049204801129
+100
+AcDbArc
+ 50
+90.0351768672
+ 51
+179.9742938954
+ 0
+LINE
+ 5
+4EC
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+143.9099256714
+ 20
+42.8222014388
+ 30
+0.0
+ 11
+145.3970798125
+ 21
+42.8222014388
+ 31
+0.0
+ 0
+ARC
+ 5
+4ED
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+145.3970883777
+ 20
+43.2221900281
+ 30
+0.0
+ 40
+0.3999885893651605
+100
+AcDbArc
+ 50
+269.9987730963
+ 51
+21.5094255355
+ 0
+ARC
+ 5
+4EE
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+147.6299070455
+ 20
+44.102180655
+ 30
+0.0
+ 40
+1.999982807792672
+100
+AcDbArc
+ 50
+166.3277484711
+ 51
+201.510375133
+ 0
+ARC
+ 5
+4EF
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+145.4922360036
+ 20
+44.6221751279
+ 30
+0.0
+ 40
+0.2000244053689678
+100
+AcDbArc
+ 50
+346.3324477609
+ 51
+89.9922055192
+ 0
+LINE
+ 5
+4F0
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbLine
+ 10
+145.4922632148
+ 20
+44.8221995315
+ 30
+0.0
+ 11
+137.3675879856
+ 21
+44.8221995315
+ 31
+0.0
+ 0
+ARC
+ 5
+4F1
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+137.3676151968
+ 20
+44.6221751279
+ 30
+0.0
+ 40
+0.2000244053689526
+100
+AcDbArc
+ 50
+90.0077944808
+ 51
+193.6675522391
+ 0
+ARC
+ 5
+4F2
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+135.2299441549
+ 20
+44.102180655
+ 30
+0.0
+ 40
+1.999982807792147
+100
+AcDbArc
+ 50
+338.489624867
+ 51
+13.672251529
+ 0
+ARC
+ 5
+4F3
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 7
+370
+ 0
+100
+AcDbCircle
+ 10
+137.4627628227
+ 20
+43.2221900281
+ 30
+0.0
+ 40
+0.399988589365143
+100
+AcDbArc
+ 50
+158.4905744645
+ 51
+270.0012269037
+ 0
+LINE
+ 5
+4F4
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 8
+370
+ 0
+100
+AcDbLine
+ 10
+148.8260940482
+ 20
+80.1358534774
+ 30
+0.0
+ 11
+148.8260940482
+ 21
+103.7777020415
+ 31
+0.0
+ 0
+LINE
+ 5
+4F5
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 8
+370
+ 0
+100
+AcDbLine
+ 10
+133.4835157563
+ 20
+80.1027132949
+ 30
+0.0
+ 11
+133.4835157563
+ 21
+103.7777020415
+ 31
+0.0
+ 0
+LINE
+ 5
+4F6
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 8
+370
+ 0
+100
+AcDbLine
+ 10
+148.5235550275
+ 20
+81.2455489119
+ 30
+0.0
+ 11
+148.5235550275
+ 21
+103.7777020415
+ 31
+0.0
+ 0
+LINE
+ 5
+4F7
+330
+1F
+100
+AcDbEntity
+ 8
+0
+ 6
+Continuous
+ 62
+ 8
+370
+ 0
+100
+AcDbLine
+ 10
+148.1099256148
+ 20
+82.7892615279
+ 30
+0.0
+ 11
+148.1099256148
+ 21
+103.7777020415
+ 31
+0.0
+ 0
+POINT
+ 5
+4F8
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+142.0299255838
+ 20
+-247.6988063932
+ 30
+0.0
+ 0
+POINT
+ 5
+4F9
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+149.4168593775
+ 20
+447.9345399732
+ 30
+0.0
+ 0
+POINT
+ 5
+4FA
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+149.4168593775
+ 20
+238.1745399732
+ 30
+0.0
+ 0
+POINT
+ 5
+4FB
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+147.8555696261
+ 20
+245.6618616408
+ 30
+0.0
+ 0
+POINT
+ 5
+4FC
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+147.8555696261
+ 20
+455.4218616408
+ 30
+0.0
+ 0
+POINT
+ 5
+4FD
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+148.0299255838
+ 20
+35.8618616408
+ 30
+0.0
+ 0
+POINT
+ 5
+4FE
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+149.4168593775
+ 20
+28.4145399732
+ 30
+0.0
+ 0
+POINT
+ 5
+4FF
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbPoint
+ 10
+133.4299255838
+ 20
+660.3011936068
+ 30
+0.0
+ 0
+DIMENSION
+ 5
+500
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D
+ 10
+195.4299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+195.4299255838
+ 21
+34.5771309945
+ 31
+0.0
+ 70
+ 160
+ 1
+33
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE0
+100
+AcDbAlignedDimension
+ 13
+142.0299255838
+ 23
+28.7821787182
+ 33
+0.0
+ 14
+148.0299255838
+ 24
+41.9821787182
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+50D
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_1
+ 10
+171.4299255838
+ 20
+287.499722104
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+271.6850735678
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+149.4168593775
+ 23
+280.0124004364
+ 33
+0.0
+ 14
+147.8555696261
+ 24
+287.499722104
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+51A
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_2
+ 10
+171.4299255838
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+194.8678981433
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+149.4168593775
+ 23
+202.8200717467
+ 33
+0.0
+ 14
+147.8555696261
+ 24
+210.3073934143
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+526
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_3
+ 10
+171.4299255838
+ 20
+133.7256838344
+ 30
+0.0
+ 11
+171.4299255838
+ 21
+113.1236101725
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+149.4168593775
+ 23
+126.2783621669
+ 33
+0.0
+ 14
+148.0299255838
+ 24
+133.7256838344
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+532
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_4
+ 10
+195.4299255838
+ 20
+41.9821787182
+ 30
+0.0
+ 11
+195.4299255838
+ 21
+84.1586135498
+ 31
+0.0
+ 70
+ 160
+ 1
+675,902
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+148.0299255838
+ 23
+133.7256838344
+ 33
+0.0
+ 14
+148.0299255838
+ 24
+41.9821787182
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+53C
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_5
+ 10
+195.3191971725
+ 20
+133.7649839595
+ 30
+0.0
+ 11
+195.2817915125
+ 21
+178.7746730971
+ 31
+0.0
+ 70
+ 160
+ 1
+524,500
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+147.8555696261
+ 23
+210.3073934143
+ 33
+0.0
+ 14
+148.0299255838
+ 24
+133.7256838344
+ 34
+0.0
+ 50
+-89.9523838979
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+545
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_6
+ 10
+195.2555696261
+ 20
+210.3073934143
+ 30
+0.0
+ 11
+195.2555696261
+ 21
+246.0726158741
+ 31
+0.0
+ 70
+ 160
+ 1
+524,400
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+147.8555696261
+ 23
+287.499722104
+ 33
+0.0
+ 14
+147.8555696261
+ 24
+210.3073934143
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+54E
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_7
+ 10
+195.2555696261
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+195.2555696261
+ 21
+357.7845521004
+ 31
+0.0
+ 70
+ 160
+ 1
+545,698
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+147.8555696261
+ 23
+287.499722104
+ 33
+0.0
+ 14
+145.8378589248
+ 24
+397.2202084954
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+557
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_8
+ 10
+219.4299255838
+ 20
+397.2202084954
+ 30
+0.0
+ 11
+219.4299255838
+ 21
+226.31135989
+ 31
+0.0
+ 70
+ 160
+ 1
+Kozijnmaat =
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+142.0299255838
+ 23
+28.7821787182
+ 33
+0.0
+ 14
+145.8378589248
+ 24
+397.2202084954
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+561
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_9
+ 10
+99.2299255838
+ 20
+28.7821787182
+ 30
+0.0
+ 11
+99.2299255838
+ 21
+214.5361780403
+ 31
+0.0
+ 70
+ 160
+ 1
+DMH =
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE2
+100
+AcDbAlignedDimension
+ 13
+133.4299255838
+ 23
+383.8202084954
+ 33
+0.0
+ 14
+142.0299255838
+ 24
+28.7821787182
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+56C
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_10
+ 10
+117.2299255838
+ 20
+148.4450158005
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+157.3719824392
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+133.5499255838
+ 23
+147.8850158005
+ 33
+0.0
+ 14
+133.5499255838
+ 24
+148.4450158005
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+578
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_11
+ 10
+117.2299255838
+ 20
+224.9867253803
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+236.5334651005
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+133.5499255838
+ 23
+224.4267253803
+ 33
+0.0
+ 14
+133.5499255838
+ 24
+224.9867253803
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+DIMENSION
+ 5
+584
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+100
+AcDbDimension
+ 2
+_D_12
+ 10
+117.2299255838
+ 20
+302.17905407
+ 30
+0.0
+ 11
+117.2299255838
+ 21
+311.8110919194
+ 31
+0.0
+ 70
+ 160
+ 1
+<>
+ 71
+ 2
+ 42
+-1.0
+ 3
+SLDDIMSTYLE1
+100
+AcDbAlignedDimension
+ 13
+133.5499255838
+ 23
+301.61905407
+ 33
+0.0
+ 14
+133.5499255838
+ 24
+302.17905407
+ 34
+0.0
+ 50
+90.0
+100
+AcDbRotatedDimension
+ 0
+LINE
+ 5
+590
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+103.7777066237
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+103.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+591
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+103.7777066237
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+105.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+592
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+105.7777066237
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+103.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+593
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+103.7777066237
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+101.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+594
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+101.7777066237
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+103.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+595
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+103.7777066237
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+103.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+596
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+105.7777066237
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+105.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+597
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+105.7777066237
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+107.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+598
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+107.7777066237
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+105.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+599
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+105.7777066237
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+103.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+59A
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+103.7777066237
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+105.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+59B
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+105.7777066237
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+105.7777066237
+ 31
+0.0
+ 0
+LINE
+ 5
+59C
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+179.2366515846
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+179.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+59D
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+179.2366515846
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+181.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+59E
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+181.2366515846
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+179.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+59F
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+179.2366515846
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+177.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A0
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+177.2366515846
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+179.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A1
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+179.2366515846
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+179.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A2
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+181.2366515846
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+181.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A3
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+181.2366515846
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+183.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A4
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+183.2366515846
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+181.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A5
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+181.2366515846
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+179.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A6
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+179.2366515846
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+181.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A7
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+181.2366515846
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+181.2366515846
+ 31
+0.0
+ 0
+LINE
+ 5
+5A8
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+256.4750445598
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+256.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5A9
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+256.4750445598
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+258.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AA
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+258.4750445598
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+256.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AB
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+256.4750445598
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+254.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AC
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+254.4750445598
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+256.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AD
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+256.4750445598
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+256.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AE
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+258.4750445598
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+258.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5AF
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+258.4750445598
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+260.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5B0
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+260.4750445598
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+258.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5B1
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+258.4750445598
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+256.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5B2
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+256.4750445598
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+258.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5B3
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+258.4750445598
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+258.4750445598
+ 31
+0.0
+ 0
+LINE
+ 5
+5B4
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+334.7369117996
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+334.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5B5
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+334.7369117996
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+336.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5B6
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+336.7369117996
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+334.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5B7
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+334.7369117996
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+332.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5B8
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+332.7369117996
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+334.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5B9
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+334.7369117996
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+334.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5BA
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+152.4849255838
+ 20
+336.7369117996
+ 30
+0.0
+ 11
+142.3699255838
+ 21
+336.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5BB
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+142.3699255838
+ 20
+336.7369117996
+ 30
+0.0
+ 11
+143.3699255838
+ 21
+338.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5BC
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+143.3699255838
+ 20
+338.7369117996
+ 30
+0.0
+ 11
+141.3699255838
+ 21
+336.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5BD
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+141.3699255838
+ 20
+336.7369117996
+ 30
+0.0
+ 11
+139.3699255838
+ 21
+334.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5BE
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+139.3699255838
+ 20
+334.7369117996
+ 30
+0.0
+ 11
+140.3699255838
+ 21
+336.7369117996
+ 31
+0.0
+ 0
+LINE
+ 5
+5BF
+330
+1F
+100
+AcDbEntity
+ 8
+HARTLIJN
+ 62
+ 7
+100
+AcDbLine
+ 10
+140.3699255838
+ 20
+336.7369117996
+ 30
+0.0
+ 11
+130.2549255838
+ 21
+336.7369117996
+ 31
+0.0
+ 0
+ENDSEC
+ 0
+SECTION
+ 2
+OBJECTS
+ 0
+DICTIONARY
+ 5
+C
+330
+0
+100
+AcDbDictionary
+281
+ 1
+ 3
+ACAD_GROUP
+350
+D
+ 3
+ACAD_LAYOUT
+350
+1A
+ 3
+ACAD_MLINESTYLE
+350
+17
+ 3
+ACAD_PLOTSETTINGS
+350
+19
+ 3
+ACAD_PLOTSTYLENAME
+350
+E
+ 0
+DICTIONARY
+ 5
+D
+102
+{ACAD_REACTORS
+330
+C
+102
+}
+330
+C
+100
+AcDbDictionary
+281
+ 1
+ 0
+DICTIONARY
+ 5
+1A
+102
+{ACAD_REACTORS
+330
+C
+102
+}
+330
+C
+100
+AcDbDictionary
+281
+ 1
+ 3
+Layout1
+350
+1E
+ 3
+Layout2
+350
+26
+ 3
+Model
+350
+22
+ 0
+DICTIONARY
+ 5
+17
+102
+{ACAD_REACTORS
+330
+C
+102
+}
+330
+C
+100
+AcDbDictionary
+281
+ 1
+ 3
+Standard
+350
+18
+ 0
+DICTIONARY
+ 5
+19
+102
+{ACAD_REACTORS
+330
+C
+102
+}
+330
+C
+100
+AcDbDictionary
+281
+ 1
+ 0
+ACDBDICTIONARYWDFLT
+ 5
+E
+102
+{ACAD_REACTORS
+330
+C
+102
+}
+330
+C
+100
+AcDbDictionary
+281
+ 1
+ 3
+Normal
+350
+F
+100
+AcDbDictionaryWithDefault
+340
+F
+ 0
+LAYOUT
+ 5
+1E
+102
+{ACAD_REACTORS
+330
+1A
+102
+}
+330
+1A
+100
+AcDbPlotSettings
+ 1
+
+ 2
+none_device
+ 4
+
+ 6
+
+ 40
+0.0
+ 41
+0.0
+ 42
+0.0
+ 43
+0.0
+ 44
+0.0
+ 45
+0.0
+ 46
+0.0
+ 47
+0.0
+ 48
+0.0
+ 49
+0.0
+140
+0.0
+141
+0.0
+142
+1.0
+143
+1.0
+ 70
+ 688
+ 72
+ 0
+ 73
+ 0
+ 74
+ 5
+ 7
+
+ 75
+ 16
+147
+1.0
+148
+0.0
+149
+0.0
+100
+AcDbLayout
+ 1
+Layout1
+ 70
+ 1
+ 71
+ 1
+ 10
+0.0
+ 20
+0.0
+ 11
+420.0
+ 21
+297.0
+ 12
+0.0
+ 22
+0.0
+ 32
+0.0
+ 14
+1.0000000000E+20
+ 24
+1.0000000000E+20
+ 34
+1.0000000000E+20
+ 15
+-1.0000000000E+20
+ 25
+-1.0000000000E+20
+ 35
+-1.0000000000E+20
+146
+0.0
+ 13
+0.0
+ 23
+0.0
+ 33
+0.0
+ 16
+1.0
+ 26
+0.0
+ 36
+0.0
+ 17
+0.0
+ 27
+1.0
+ 37
+0.0
+ 76
+ 0
+330
+1B
+ 0
+LAYOUT
+ 5
+26
+102
+{ACAD_REACTORS
+330
+1A
+102
+}
+330
+1A
+100
+AcDbPlotSettings
+ 1
+
+ 2
+none_device
+ 4
+
+ 6
+
+ 40
+0.0
+ 41
+0.0
+ 42
+0.0
+ 43
+0.0
+ 44
+0.0
+ 45
+0.0
+ 46
+0.0
+ 47
+0.0
+ 48
+0.0
+ 49
+0.0
+140
+0.0
+141
+0.0
+142
+1.0
+143
+1.0
+ 70
+ 688
+ 72
+ 0
+ 73
+ 0
+ 74
+ 5
+ 7
+
+ 75
+ 16
+147
+1.0
+148
+0.0
+149
+0.0
+100
+AcDbLayout
+ 1
+Layout2
+ 70
+ 1
+ 71
+ 2
+ 10
+0.0
+ 20
+0.0
+ 11
+0.0
+ 21
+0.0
+ 12
+0.0
+ 22
+0.0
+ 32
+0.0
+ 14
+0.0
+ 24
+0.0
+ 34
+0.0
+ 15
+0.0
+ 25
+0.0
+ 35
+0.0
+146
+0.0
+ 13
+0.0
+ 23
+0.0
+ 33
+0.0
+ 16
+1.0
+ 26
+0.0
+ 36
+0.0
+ 17
+0.0
+ 27
+1.0
+ 37
+0.0
+ 76
+ 0
+330
+23
+ 0
+LAYOUT
+ 5
+22
+102
+{ACAD_REACTORS
+330
+1A
+102
+}
+330
+1A
+100
+AcDbPlotSettings
+ 1
+
+ 2
+none_device
+ 4
+
+ 6
+
+ 40
+0.0
+ 41
+0.0
+ 42
+0.0
+ 43
+0.0
+ 44
+0.0
+ 45
+0.0
+ 46
+0.0
+ 47
+0.0
+ 48
+0.0
+ 49
+0.0
+140
+0.0
+141
+0.0
+142
+1.0
+143
+1.0
+ 70
+ 1712
+ 72
+ 0
+ 73
+ 0
+ 74
+ 0
+ 7
+
+ 75
+ 0
+147
+1.0
+148
+0.0
+149
+0.0
+100
+AcDbLayout
+ 1
+Model
+ 70
+ 1
+ 71
+ 0
+ 10
+0.0
+ 20
+0.0
+ 11
+297.0
+ 21
+420.0
+ 12
+0.0
+ 22
+0.0
+ 32
+0.0
+ 14
+0.0
+ 24
+0.0
+ 34
+0.0
+ 15
+297.0
+ 25
+420.0
+ 35
+0.0
+146
+0.0
+ 13
+0.0
+ 23
+0.0
+ 33
+0.0
+ 16
+1.0
+ 26
+0.0
+ 36
+0.0
+ 17
+0.0
+ 27
+1.0
+ 37
+0.0
+ 76
+ 0
+330
+1F
+331
+29
+ 0
+MLINESTYLE
+ 5
+18
+102
+{ACAD_REACTORS
+330
+17
+102
+}
+330
+17
+100
+AcDbMlineStyle
+ 2
+Standard
+ 70
+ 0
+ 3
+
+ 62
+ 256
+ 51
+90.0
+ 52
+90.0
+ 71
+ 2
+ 49
+0.5
+ 62
+ 256
+ 6
+BYLAYER
+ 49
+-0.5
+ 62
+ 256
+ 6
+BYLAYER
+ 0
+ACDBPLACEHOLDER
+ 5
+F
+102
+{ACAD_REACTORS
+330
+E
+102
+}
+330
+E
+ 0
+ENDSEC
+ 0
+EOF
diff --git a/vcl/qa/cppunit/graphicfilter/data/dxf/pass/pyramid.dxf b/vcl/qa/cppunit/graphicfilter/data/dxf/pass/pyramid.dxf
new file mode 100644
index 0000000000..65cd5f838c
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/dxf/pass/pyramid.dxf
@@ -0,0 +1,25008 @@
+0
+SECTION
+2
+HEADER
+9
+$ACADVER
+1
+AC1014
+9
+$ACADMAINTVER
+70
+8
+9
+$DWGCODEPAGE
+3
+ANSI_1252
+9
+$INSBASE
+10
+0.0
+20
+0.0
+30
+0.0
+9
+$EXTMIN
+10
+1.000000E+20
+20
+1.000000E+20
+30
+1.000000E+20
+9
+$EXTMAX
+10
+-1.000000E+20
+20
+-1.000000E+20
+30
+-1.000000E+20
+9
+$LIMMIN
+10
+0.0
+20
+0.0
+9
+$LIMMAX
+10
+12.0
+20
+9.0
+9
+$ORTHOMODE
+70
+0
+9
+$REGENMODE
+70
+1
+9
+$FILLMODE
+70
+1
+9
+$QTEXTMODE
+70
+0
+9
+$MIRRTEXT
+70
+1
+9
+$DRAGMODE
+70
+2
+9
+$LTSCALE
+40
+1.0
+9
+$OSMODE
+70
+0
+9
+$ATTMODE
+70
+1
+9
+$TEXTSIZE
+40
+0.2
+9
+$TRACEWID
+40
+0.05
+9
+$TEXTSTYLE
+7
+STANDARD
+9
+$CLAYER
+8
+0
+9
+$CELTYPE
+6
+BYLAYER
+9
+$CECOLOR
+62
+256
+9
+$CELTSCALE
+40
+1.0
+9
+$DELOBJ
+70
+1
+9
+$DISPSILH
+70
+0
+9
+$DIMSCALE
+40
+1.0
+9
+$DIMASZ
+40
+0.18
+9
+$DIMEXO
+40
+0.0625
+9
+$DIMDLI
+40
+0.38
+9
+$DIMRND
+40
+0.0
+9
+$DIMDLE
+40
+0.0
+9
+$DIMEXE
+40
+0.18
+9
+$DIMTP
+40
+0.0
+9
+$DIMTM
+40
+0.0
+9
+$DIMTXT
+40
+0.18
+9
+$DIMCEN
+40
+0.09
+9
+$DIMTSZ
+40
+0.0
+9
+$DIMTOL
+70
+0
+9
+$DIMLIM
+70
+0
+9
+$DIMTIH
+70
+1
+9
+$DIMTOH
+70
+1
+9
+$DIMSE1
+70
+0
+9
+$DIMSE2
+70
+0
+9
+$DIMTAD
+70
+0
+9
+$DIMZIN
+70
+0
+9
+$DIMBLK
+1
+
+9
+$DIMASO
+70
+1
+9
+$DIMSHO
+70
+1
+9
+$DIMPOST
+1
+
+9
+$DIMAPOST
+1
+
+9
+$DIMALT
+70
+0
+9
+$DIMALTD
+70
+2
+9
+$DIMALTF
+40
+25.4
+9
+$DIMLFAC
+40
+1.0
+9
+$DIMTOFL
+70
+0
+9
+$DIMTVP
+40
+0.0
+9
+$DIMTIX
+70
+0
+9
+$DIMSOXD
+70
+0
+9
+$DIMSAH
+70
+0
+9
+$DIMBLK1
+1
+
+9
+$DIMBLK2
+1
+
+9
+$DIMSTYLE
+2
+STANDARD
+9
+$DIMCLRD
+70
+0
+9
+$DIMCLRE
+70
+0
+9
+$DIMCLRT
+70
+0
+9
+$DIMTFAC
+40
+1.0
+9
+$DIMGAP
+40
+0.09
+9
+$DIMJUST
+70
+0
+9
+$DIMSD1
+70
+0
+9
+$DIMSD2
+70
+0
+9
+$DIMTOLJ
+70
+1
+9
+$DIMTZIN
+70
+0
+9
+$DIMALTZ
+70
+0
+9
+$DIMALTTZ
+70
+0
+9
+$DIMFIT
+70
+3
+9
+$DIMUPT
+70
+0
+9
+$DIMUNIT
+70
+2
+9
+$DIMDEC
+70
+4
+9
+$DIMTDEC
+70
+4
+9
+$DIMALTU
+70
+2
+9
+$DIMALTTD
+70
+2
+9
+$DIMTXSTY
+7
+STANDARD
+9
+$DIMAUNIT
+70
+0
+9
+$LUNITS
+70
+2
+9
+$LUPREC
+70
+4
+9
+$SKETCHINC
+40
+0.1
+9
+$FILLETRAD
+40
+0.5
+9
+$AUNITS
+70
+0
+9
+$AUPREC
+70
+0
+9
+$MENU
+1
+.
+9
+$ELEVATION
+40
+0.0
+9
+$PELEVATION
+40
+0.0
+9
+$THICKNESS
+40
+0.0
+9
+$LIMCHECK
+70
+0
+9
+$BLIPMODE
+70
+0
+9
+$CHAMFERA
+40
+0.5
+9
+$CHAMFERB
+40
+0.5
+9
+$CHAMFERC
+40
+1.0
+9
+$CHAMFERD
+40
+0.0
+9
+$SKPOLY
+70
+1
+9
+$TDCREATE
+40
+2451008.519973958
+9
+$TDUPDATE
+40
+2451008.523538426
+9
+$TDINDWG
+40
+0.0024055556
+9
+$TDUSRTIMER
+40
+0.0024055556
+9
+$USRTIMER
+70
+1
+9
+$ANGBASE
+50
+0.0
+9
+$ANGDIR
+70
+0
+9
+$PDMODE
+70
+0
+9
+$PDSIZE
+40
+0.0
+9
+$PLINEWID
+40
+0.0
+9
+$COORDS
+70
+1
+9
+$SPLFRAME
+70
+0
+9
+$SPLINETYPE
+70
+6
+9
+$SPLINESEGS
+70
+8
+9
+$ATTDIA
+70
+0
+9
+$ATTREQ
+70
+1
+9
+$HANDLING
+70
+1
+9
+$HANDSEED
+5
+5B
+9
+$SURFTAB1
+70
+6
+9
+$SURFTAB2
+70
+6
+9
+$SURFTYPE
+70
+6
+9
+$SURFU
+70
+6
+9
+$SURFV
+70
+6
+9
+$UCSNAME
+2
+
+9
+$UCSORG
+10
+0.0
+20
+0.0
+30
+0.0
+9
+$UCSXDIR
+10
+1.0
+20
+0.0
+30
+0.0
+9
+$UCSYDIR
+10
+0.0
+20
+1.0
+30
+0.0
+9
+$PUCSNAME
+2
+
+9
+$PUCSORG
+10
+0.0
+20
+0.0
+30
+0.0
+9
+$PUCSXDIR
+10
+1.0
+20
+0.0
+30
+0.0
+9
+$PUCSYDIR
+10
+0.0
+20
+1.0
+30
+0.0
+9
+$USERI1
+70
+0
+9
+$USERI2
+70
+0
+9
+$USERI3
+70
+0
+9
+$USERI4
+70
+0
+9
+$USERI5
+70
+0
+9
+$USERR1
+40
+0.0
+9
+$USERR2
+40
+0.0
+9
+$USERR3
+40
+0.0
+9
+$USERR4
+40
+0.0
+9
+$USERR5
+40
+0.0
+9
+$WORLDVIEW
+70
+1
+9
+$SHADEDGE
+70
+3
+9
+$SHADEDIF
+70
+70
+9
+$TILEMODE
+70
+1
+9
+$MAXACTVP
+70
+48
+9
+$PINSBASE
+10
+0.0
+20
+0.0
+30
+0.0
+9
+$PLIMCHECK
+70
+0
+9
+$PEXTMIN
+10
+1.000000E+20
+20
+1.000000E+20
+30
+1.000000E+20
+9
+$PEXTMAX
+10
+-1.000000E+20
+20
+-1.000000E+20
+30
+-1.000000E+20
+9
+$PLIMMIN
+10
+0.0
+20
+0.0
+9
+$PLIMMAX
+10
+12.0
+20
+9.0
+9
+$UNITMODE
+70
+0
+9
+$VISRETAIN
+70
+1
+9
+$PLINEGEN
+70
+0
+9
+$PSLTSCALE
+70
+1
+9
+$TREEDEPTH
+70
+3020
+9
+$PICKSTYLE
+70
+1
+9
+$CMLSTYLE
+2
+STANDARD
+9
+$CMLJUST
+70
+0
+9
+$CMLSCALE
+40
+1.0
+9
+$PROXYGRAPHICS
+70
+1
+9
+$MEASUREMENT
+70
+0
+0
+ENDSEC
+0
+SECTION
+2
+CLASSES
+0
+ENDSEC
+0
+SECTION
+2
+TABLES
+0
+TABLE
+2
+VPORT
+5
+8
+100
+AcDbSymbolTable
+70
+3
+0
+VPORT
+5
+5A
+100
+AcDbSymbolTableRecord
+100
+AcDbViewportTableRecord
+2
+*ACTIVE
+70
+0
+10
+0.0
+20
+0.0
+11
+1.0
+21
+1.0
+12
+7.623626
+22
+4.5
+13
+0.0
+23
+0.0
+14
+0.5
+24
+0.5
+15
+0.5
+25
+0.5
+16
+0.0
+26
+0.0
+36
+1.0
+17
+0.0
+27
+0.0
+37
+0.0
+40
+9.0
+41
+1.694139
+42
+50.0
+43
+0.0
+44
+0.0
+50
+0.0
+51
+0.0
+71
+0
+72
+100
+73
+1
+74
+1
+75
+0
+76
+0
+77
+0
+78
+0
+0
+ENDTAB
+0
+TABLE
+2
+LTYPE
+5
+5
+100
+AcDbSymbolTable
+70
+1
+0
+LTYPE
+5
+13
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+2
+BYBLOCK
+70
+0
+3
+
+72
+65
+73
+0
+40
+0.0
+0
+LTYPE
+5
+14
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+2
+BYLAYER
+70
+0
+3
+
+72
+65
+73
+0
+40
+0.0
+0
+LTYPE
+5
+15
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+2
+CONTINUOUS
+70
+0
+3
+Solid line
+72
+65
+73
+0
+40
+0.0
+0
+ENDTAB
+0
+TABLE
+2
+LAYER
+5
+2
+100
+AcDbSymbolTable
+70
+10
+0
+LAYER
+5
+F
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+2
+0
+70
+0
+62
+7
+6
+CONTINUOUS
+0
+LAYER
+5
+4C
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+2
+FRAMES
+70
+0
+62
+2
+6
+CONTINUOUS
+0
+LAYER
+5
+4D
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+2
+JOINTS
+70
+0
+62
+2
+6
+CONTINUOUS
+0
+LAYER
+5
+4E
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+2
+SHELLS
+70
+0
+62
+1
+6
+CONTINUOUS
+0
+LAYER
+5
+4F
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+2
+GRIDS
+70
+0
+62
+8
+6
+CONTINUOUS
+0
+LAYER
+5
+50
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+2
+PLANES
+70
+0
+62
+5
+6
+CONTINUOUS
+0
+LAYER
+5
+51
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+2
+ASOLIDS
+70
+0
+62
+6
+6
+CONTINUOUS
+0
+LAYER
+5
+52
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+2
+SOLIDS
+70
+0
+62
+7
+6
+CONTINUOUS
+0
+LAYER
+5
+53
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+2
+TEXT
+70
+0
+62
+94
+6
+CONTINUOUS
+0
+LAYER
+5
+58
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+2
+NLLINKS
+70
+0
+62
+3
+6
+CONTINUOUS
+0
+ENDTAB
+0
+TABLE
+2
+STYLE
+5
+3
+100
+AcDbSymbolTable
+70
+1
+0
+STYLE
+5
+10
+100
+AcDbSymbolTableRecord
+100
+AcDbTextStyleTableRecord
+2
+STANDARD
+70
+0
+40
+0.0
+41
+1.0
+50
+0.0
+71
+0
+42
+0.2
+3
+txt
+4
+
+0
+ENDTAB
+0
+TABLE
+2
+VIEW
+5
+6
+100
+AcDbSymbolTable
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+UCS
+5
+7
+100
+AcDbSymbolTable
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+APPID
+5
+9
+100
+AcDbSymbolTable
+70
+1
+0
+APPID
+5
+11
+100
+AcDbSymbolTableRecord
+100
+AcDbRegAppTableRecord
+2
+ACAD
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+DIMSTYLE
+5
+A
+100
+AcDbSymbolTable
+70
+1
+0
+DIMSTYLE
+105
+1D
+100
+AcDbSymbolTableRecord
+100
+AcDbDimStyleTableRecord
+2
+STANDARD
+70
+0
+3
+
+4
+
+5
+
+6
+
+7
+
+40
+1.0
+41
+0.18
+42
+0.0625
+43
+0.38
+44
+0.18
+45
+0.0
+46
+0.0
+47
+0.0
+48
+0.0
+140
+0.18
+141
+0.09
+142
+0.0
+143
+25.4
+144
+1.0
+145
+0.0
+146
+1.0
+147
+0.09
+71
+0
+72
+0
+73
+1
+74
+1
+75
+0
+76
+0
+77
+0
+78
+0
+170
+0
+171
+2
+172
+0
+173
+0
+174
+0
+175
+0
+176
+0
+177
+0
+178
+0
+270
+2
+271
+4
+272
+4
+273
+2
+274
+2
+340
+10
+275
+0
+280
+0
+281
+0
+282
+0
+283
+1
+284
+0
+285
+0
+286
+0
+287
+3
+288
+0
+0
+ENDTAB
+0
+TABLE
+2
+BLOCK_RECORD
+5
+1
+100
+AcDbSymbolTable
+70
+0
+0
+BLOCK_RECORD
+5
+19
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+2
+*MODEL_SPACE
+0
+BLOCK_RECORD
+5
+16
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+2
+*PAPER_SPACE
+0
+ENDTAB
+0
+ENDSEC
+0
+SECTION
+2
+BLOCKS
+0
+BLOCK
+5
+1A
+100
+AcDbEntity
+8
+0
+100
+AcDbBlockBegin
+2
+*MODEL_SPACE
+70
+0
+10
+0.0
+20
+0.0
+30
+0.0
+3
+*MODEL_SPACE
+1
+
+0
+ENDBLK
+5
+1B
+100
+AcDbEntity
+8
+0
+100
+AcDbBlockEnd
+0
+BLOCK
+5
+17
+100
+AcDbEntity
+67
+1
+8
+0
+100
+AcDbBlockBegin
+2
+*PAPER_SPACE
+70
+0
+10
+0.0
+20
+0.0
+30
+0.0
+3
+*PAPER_SPACE
+1
+
+0
+ENDBLK
+5
+18
+100
+AcDbEntity
+67
+1
+8
+0
+100
+AcDbBlockEnd
+0
+ENDSEC
+0
+SECTION
+2
+ENTITIES
+0
+POLYLINE
+5
+B1
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B2
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B3
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B4
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B5
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B6
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B7
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B8
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B9
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B10
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B11
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B12
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B13
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B14
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B15
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B16
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B17
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B18
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B19
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-684
+30
+240
+70
+64
+0
+SEQEND
+5
+B20
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B21
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B22
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B23
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B24
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B25
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B26
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B27
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B28
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B29
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B30
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B31
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B32
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B33
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B34
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B35
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B36
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B37
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B38
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B39
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-648
+30
+480
+70
+64
+0
+SEQEND
+5
+B40
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B41
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B42
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B43
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B44
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B45
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B46
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B47
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B48
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B49
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B50
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B51
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B52
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B53
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B54
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B55
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B56
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B57
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B58
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B59
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-612
+30
+720
+70
+64
+0
+SEQEND
+5
+B60
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B61
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B62
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-576
+30
+960
+70
+64
+0
+VERTEX
+5
+B63
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B64
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B65
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-576
+30
+960
+70
+64
+0
+VERTEX
+5
+B66
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B67
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B68
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B69
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B70
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B71
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B72
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B73
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B74
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B75
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B76
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-576
+30
+960
+70
+64
+0
+VERTEX
+5
+B77
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B78
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B79
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-576
+30
+960
+70
+64
+0
+SEQEND
+5
+B80
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B81
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B82
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B83
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B84
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B85
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B86
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B87
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B88
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B89
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B90
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B91
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B92
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B93
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B94
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B95
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B96
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B97
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B98
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B99
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-432
+30
+240
+70
+64
+0
+SEQEND
+5
+B100
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B101
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B102
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B103
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B104
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B105
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B106
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B107
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B108
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B109
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B110
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B111
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B112
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B113
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B114
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B115
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B116
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B117
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B118
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B119
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-432
+30
+480
+70
+64
+0
+SEQEND
+5
+B120
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B121
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B122
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B123
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B124
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B125
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B126
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B127
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B128
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B129
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B130
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B131
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B132
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B133
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B134
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B135
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B136
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B137
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B138
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B139
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-432
+30
+720
+70
+64
+0
+SEQEND
+5
+B140
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B141
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B142
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B143
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B144
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B145
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B146
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B147
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B148
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B149
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B150
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B151
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B152
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B153
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B154
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B155
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B156
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B157
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B158
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B159
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-432
+30
+960
+70
+64
+0
+SEQEND
+5
+B160
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B161
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B162
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B163
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B164
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B165
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B166
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B167
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B168
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B169
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B170
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B171
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B172
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B173
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B174
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B175
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B176
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B177
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B178
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B179
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-180
+30
+240
+70
+64
+0
+SEQEND
+5
+B180
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B181
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B182
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B183
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B184
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B185
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B186
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B187
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B188
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B189
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B190
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B191
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B192
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B193
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B194
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B195
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B196
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B197
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B198
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B199
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-216
+30
+480
+70
+64
+0
+SEQEND
+5
+B200
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B201
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B202
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B203
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B204
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B205
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B206
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B207
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B208
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B209
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B210
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B211
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B212
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B213
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B214
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B215
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B216
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B217
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B218
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B219
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-252
+30
+720
+70
+64
+0
+SEQEND
+5
+B220
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B221
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B222
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B223
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B224
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B225
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B226
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B227
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B228
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B229
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B230
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B231
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B232
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B233
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B234
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B235
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B236
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B237
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B238
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B239
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-288
+30
+960
+70
+64
+0
+SEQEND
+5
+B240
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B241
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B242
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B243
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B244
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B245
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B246
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B247
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B248
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B249
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B250
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B251
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B252
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B253
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B254
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B255
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B256
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B257
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B258
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B259
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+72
+30
+240
+70
+64
+0
+SEQEND
+5
+B260
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B261
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B262
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B263
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B264
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B265
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B266
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B267
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B268
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B269
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B270
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B271
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B272
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B273
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B274
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B275
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B276
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B277
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B278
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B279
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+0
+30
+480
+70
+64
+0
+SEQEND
+5
+B280
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B281
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B282
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B283
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B284
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B285
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B286
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B287
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B288
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B289
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B290
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B291
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B292
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B293
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B294
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B295
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B296
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B297
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B298
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B299
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-72
+30
+720
+70
+64
+0
+SEQEND
+5
+B300
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B301
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B302
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B303
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B304
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B305
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B306
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B307
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B308
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B309
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B310
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B311
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B312
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B313
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B314
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B315
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B316
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B317
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B318
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B319
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+-144
+30
+960
+70
+64
+0
+SEQEND
+5
+B320
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B321
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B322
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B323
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B324
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B325
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B326
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B327
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B328
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B329
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+720
+30
+0
+70
+64
+0
+VERTEX
+5
+B330
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-480
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B331
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B332
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+720
+30
+0
+70
+64
+0
+VERTEX
+5
+B333
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B334
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B335
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B336
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B337
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B338
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B339
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+324
+30
+240
+70
+64
+0
+SEQEND
+5
+B340
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B341
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B342
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B343
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B344
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B345
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B346
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B347
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B348
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B349
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B350
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-444
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B351
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B352
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B353
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B354
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B355
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B356
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B357
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B358
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B359
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+216
+30
+480
+70
+64
+0
+SEQEND
+5
+B360
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B361
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B362
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B363
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B364
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B365
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B366
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B367
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B368
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B369
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B370
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-408
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B371
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B372
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B373
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B374
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B375
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B376
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B377
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B378
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B379
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+108
+30
+720
+70
+64
+0
+SEQEND
+5
+B380
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B381
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B382
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B383
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B384
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B385
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B386
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B387
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B388
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B389
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B390
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-372
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B391
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B392
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B393
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B394
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B395
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B396
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B397
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B398
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B399
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-336
+20
+0
+30
+960
+70
+64
+0
+SEQEND
+5
+B400
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B401
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B402
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B403
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B404
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B405
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B406
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B407
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B408
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B409
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B410
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B411
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B412
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B413
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B414
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B415
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B416
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B417
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B418
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B419
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-684
+30
+240
+70
+64
+0
+SEQEND
+5
+B420
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B421
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B422
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B423
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B424
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B425
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B426
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B427
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B428
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B429
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B430
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B431
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B432
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B433
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B434
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B435
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B436
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B437
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B438
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B439
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-648
+30
+480
+70
+64
+0
+SEQEND
+5
+B440
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B441
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B442
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B443
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B444
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B445
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B446
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B447
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B448
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B449
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B450
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B451
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B452
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B453
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B454
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B455
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B456
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B457
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B458
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B459
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-612
+30
+720
+70
+64
+0
+SEQEND
+5
+B460
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B461
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B462
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-576
+30
+960
+70
+64
+0
+VERTEX
+5
+B463
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B464
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B465
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-576
+30
+960
+70
+64
+0
+VERTEX
+5
+B466
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B467
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B468
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B469
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B470
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B471
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B472
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B473
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B474
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B475
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B476
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-576
+30
+960
+70
+64
+0
+VERTEX
+5
+B477
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B478
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B479
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-576
+30
+960
+70
+64
+0
+SEQEND
+5
+B480
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B481
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B482
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B483
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B484
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B485
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B486
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B487
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B488
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B489
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B490
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B491
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B492
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B493
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B494
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B495
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B496
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B497
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B498
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B499
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+SEQEND
+5
+B500
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B501
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B502
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B503
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B504
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B505
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B506
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B507
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B508
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B509
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B510
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B511
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B512
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B513
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B514
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B515
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B516
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B517
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B518
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B519
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+SEQEND
+5
+B520
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B521
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B522
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B523
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B524
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B525
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B526
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B527
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B528
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B529
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B530
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B531
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B532
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B533
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B534
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B535
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B536
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B537
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B538
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B539
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+SEQEND
+5
+B540
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B541
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B542
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B543
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B544
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B545
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B546
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B547
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B548
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B549
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B550
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B551
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B552
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B553
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B554
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B555
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B556
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B557
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B558
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B559
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-432
+30
+960
+70
+64
+0
+SEQEND
+5
+B560
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B561
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B562
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B563
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B564
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B565
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B566
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B567
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B568
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B569
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B570
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B571
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B572
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B573
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B574
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B575
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B576
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B577
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B578
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B579
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+SEQEND
+5
+B580
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B581
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B582
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B583
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B584
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B585
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B586
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B587
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B588
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B589
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B590
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B591
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B592
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B593
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B594
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B595
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B596
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B597
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B598
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B599
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+SEQEND
+5
+B600
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B601
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B602
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B603
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B604
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B605
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B606
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B607
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B608
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B609
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B610
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B611
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B612
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B613
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B614
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B615
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B616
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B617
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B618
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B619
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+SEQEND
+5
+B620
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B621
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B622
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B623
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B624
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B625
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B626
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B627
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B628
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B629
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B630
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B631
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B632
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B633
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B634
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B635
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B636
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B637
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B638
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B639
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-288
+30
+960
+70
+64
+0
+SEQEND
+5
+B640
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B641
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B642
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B643
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B644
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B645
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B646
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B647
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B648
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B649
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B650
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B651
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B652
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B653
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B654
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B655
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B656
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B657
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B658
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B659
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+SEQEND
+5
+B660
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B661
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B662
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B663
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B664
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B665
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B666
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B667
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B668
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B669
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B670
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B671
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B672
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B673
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B674
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B675
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B676
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B677
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B678
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B679
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+SEQEND
+5
+B680
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B681
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B682
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B683
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B684
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B685
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B686
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B687
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B688
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B689
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B690
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B691
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B692
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B693
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B694
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B695
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B696
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B697
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B698
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B699
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+SEQEND
+5
+B700
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B701
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B702
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B703
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B704
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B705
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B706
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B707
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B708
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B709
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B710
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B711
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B712
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B713
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B714
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B715
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B716
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B717
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B718
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B719
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+-144
+30
+960
+70
+64
+0
+SEQEND
+5
+B720
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B721
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B722
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B723
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B724
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B725
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B726
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B727
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B728
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B729
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+720
+30
+0
+70
+64
+0
+VERTEX
+5
+B730
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-160
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B731
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B732
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+720
+30
+0
+70
+64
+0
+VERTEX
+5
+B733
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B734
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B735
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B736
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B737
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B738
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B739
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+SEQEND
+5
+B740
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B741
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B742
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B743
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B744
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B745
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B746
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B747
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B748
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B749
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B750
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-144
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B751
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B752
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B753
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B754
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B755
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B756
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B757
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B758
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B759
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+SEQEND
+5
+B760
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B761
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B762
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B763
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B764
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B765
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B766
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B767
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B768
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B769
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B770
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-128
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B771
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B772
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B773
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B774
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B775
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B776
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B777
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B778
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B779
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+SEQEND
+5
+B780
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B781
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B782
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B783
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B784
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B785
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B786
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B787
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B788
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B789
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B790
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-112
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B791
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B792
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B793
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B794
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B795
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B796
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B797
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B798
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B799
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+-96
+20
+0
+30
+960
+70
+64
+0
+SEQEND
+5
+B800
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B801
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B802
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B803
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B804
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B805
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B806
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B807
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B808
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B809
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B810
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B811
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B812
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B813
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+-720
+30
+0
+70
+64
+0
+VERTEX
+5
+B814
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B815
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B816
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B817
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B818
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B819
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-684
+30
+240
+70
+64
+0
+SEQEND
+5
+B820
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B821
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B822
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B823
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B824
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B825
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B826
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B827
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B828
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B829
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B830
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B831
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B832
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B833
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-684
+30
+240
+70
+64
+0
+VERTEX
+5
+B834
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B835
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B836
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B837
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B838
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B839
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-648
+30
+480
+70
+64
+0
+SEQEND
+5
+B840
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B841
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B842
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B843
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B844
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B845
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B846
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B847
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B848
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B849
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B850
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B851
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B852
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B853
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-648
+30
+480
+70
+64
+0
+VERTEX
+5
+B854
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B855
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B856
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B857
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B858
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B859
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-612
+30
+720
+70
+64
+0
+SEQEND
+5
+B860
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B861
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B862
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-576
+30
+960
+70
+64
+0
+VERTEX
+5
+B863
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B864
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B865
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-576
+30
+960
+70
+64
+0
+VERTEX
+5
+B866
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B867
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B868
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B869
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B870
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B871
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B872
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B873
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-612
+30
+720
+70
+64
+0
+VERTEX
+5
+B874
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B875
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B876
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-576
+30
+960
+70
+64
+0
+VERTEX
+5
+B877
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B878
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B879
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-576
+30
+960
+70
+64
+0
+SEQEND
+5
+B880
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B881
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B882
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B883
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B884
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B885
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B886
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B887
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B888
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B889
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B890
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B891
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B892
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B893
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+-432
+30
+0
+70
+64
+0
+VERTEX
+5
+B894
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B895
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B896
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B897
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B898
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B899
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+SEQEND
+5
+B900
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B901
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B902
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B903
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B904
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B905
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B906
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B907
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B908
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B909
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B910
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B911
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B912
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B913
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-432
+30
+240
+70
+64
+0
+VERTEX
+5
+B914
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B915
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B916
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B917
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B918
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B919
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+SEQEND
+5
+B920
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B921
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B922
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B923
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B924
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B925
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B926
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B927
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B928
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B929
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B930
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B931
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B932
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B933
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-432
+30
+480
+70
+64
+0
+VERTEX
+5
+B934
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B935
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B936
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B937
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B938
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B939
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+SEQEND
+5
+B940
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B941
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B942
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B943
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B944
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B945
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B946
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B947
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B948
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B949
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B950
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B951
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B952
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B953
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-432
+30
+720
+70
+64
+0
+VERTEX
+5
+B954
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B955
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B956
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-432
+30
+960
+70
+64
+0
+VERTEX
+5
+B957
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B958
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B959
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-432
+30
+960
+70
+64
+0
+SEQEND
+5
+B960
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B961
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B962
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B963
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B964
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B965
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B966
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B967
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B968
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B969
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B970
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B971
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B972
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B973
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+-144
+30
+0
+70
+64
+0
+VERTEX
+5
+B974
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B975
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B976
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B977
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B978
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B979
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+SEQEND
+5
+B980
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B981
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B982
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B983
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B984
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B985
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B986
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B987
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B988
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B989
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B990
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B991
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B992
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B993
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+-180
+30
+240
+70
+64
+0
+VERTEX
+5
+B994
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B995
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B996
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B997
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B998
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B999
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+SEQEND
+5
+B1000
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B1001
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B1002
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B1003
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1004
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1005
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B1006
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1007
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1008
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1009
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B1010
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1011
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1012
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B1013
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+-216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1014
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1015
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1016
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B1017
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1018
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1019
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+SEQEND
+5
+B1020
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B1021
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B1022
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B1023
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B1024
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B1025
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B1026
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B1027
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B1028
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B1029
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1030
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B1031
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B1032
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1033
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-252
+30
+720
+70
+64
+0
+VERTEX
+5
+B1034
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B1035
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B1036
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-288
+30
+960
+70
+64
+0
+VERTEX
+5
+B1037
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B1038
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B1039
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-288
+30
+960
+70
+64
+0
+SEQEND
+5
+B1040
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B1041
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B1042
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B1043
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B1044
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B1045
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B1046
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B1047
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B1048
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1049
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B1050
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B1051
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1052
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B1053
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+144
+30
+0
+70
+64
+0
+VERTEX
+5
+B1054
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1055
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1056
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B1057
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1058
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1059
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+SEQEND
+5
+B1060
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B1061
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B1062
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B1063
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B1064
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B1065
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B1066
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B1067
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B1068
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1069
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1070
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B1071
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1072
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1073
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+72
+30
+240
+70
+64
+0
+VERTEX
+5
+B1074
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1075
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1076
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B1077
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1078
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1079
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+SEQEND
+5
+B1080
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B1081
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B1082
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1083
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B1084
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B1085
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1086
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B1087
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B1088
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1089
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1090
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B1091
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1092
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1093
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+0
+30
+480
+70
+64
+0
+VERTEX
+5
+B1094
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1095
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1096
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1097
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1098
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1099
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+SEQEND
+5
+B1100
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B1101
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B1102
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B1103
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1104
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1105
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B1106
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1107
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1108
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B1109
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1110
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1111
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B1112
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1113
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+-72
+30
+720
+70
+64
+0
+VERTEX
+5
+B1114
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B1115
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B1116
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+-144
+30
+960
+70
+64
+0
+VERTEX
+5
+B1117
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B1118
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B1119
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+-144
+30
+960
+70
+64
+0
+SEQEND
+5
+B1120
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B1121
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B1122
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1123
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B1124
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B1125
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1126
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B1127
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B1128
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B1129
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+720
+30
+0
+70
+64
+0
+VERTEX
+5
+B1130
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+160
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B1131
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B1132
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+720
+30
+0
+70
+64
+0
+VERTEX
+5
+B1133
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+480
+20
+432
+30
+0
+70
+64
+0
+VERTEX
+5
+B1134
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B1135
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B1136
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1137
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B1138
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B1139
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+SEQEND
+5
+B1140
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B1141
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B1142
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1143
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1144
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1145
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1146
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1147
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1148
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B1149
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B1150
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+156
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1151
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B1152
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+576
+30
+240
+70
+64
+0
+VERTEX
+5
+B1153
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+456
+20
+324
+30
+240
+70
+64
+0
+VERTEX
+5
+B1154
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B1155
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B1156
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1157
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B1158
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B1159
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+SEQEND
+5
+B1160
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B1161
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B1162
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1163
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1164
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1165
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1166
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1167
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1168
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B1169
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B1170
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+152
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1171
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B1172
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+432
+30
+480
+70
+64
+0
+VERTEX
+5
+B1173
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+432
+20
+216
+30
+480
+70
+64
+0
+VERTEX
+5
+B1174
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B1175
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B1176
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1177
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B1178
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B1179
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+SEQEND
+5
+B1180
+100
+AcDbEntity
+8
+SOLIDS
+0
+POLYLINE
+5
+B1181
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbPolygonMesh
+66
+1
+10
+0
+20
+0
+30
+0
+70
+16
+71
+6
+72
+3
+0
+VERTEX
+5
+B1182
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B1183
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1184
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1185
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B1186
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1187
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1188
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B1189
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B1190
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+148
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1191
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B1192
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+288
+30
+720
+70
+64
+0
+VERTEX
+5
+B1193
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+408
+20
+108
+30
+720
+70
+64
+0
+VERTEX
+5
+B1194
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B1195
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B1196
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+384
+20
+0
+30
+960
+70
+64
+0
+VERTEX
+5
+B1197
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B1198
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+144
+30
+960
+70
+64
+0
+VERTEX
+5
+B1199
+100
+AcDbEntity
+8
+SOLIDS
+100
+AcDbVertex
+100
+AcDbPolygonMeshVertex
+10
+144
+20
+0
+30
+960
+70
+64
+0
+SEQEND
+5
+B1200
+100
+AcDbEntity
+8
+SOLIDS
+0
+ENDSEC
+0
+SECTION
+2
+OBJECTS
+0
+DICTIONARY
+5
+C
+100
+AcDbDictionary
+3
+ACAD_GROUP
+350
+D
+3
+ACAD_MLINESTYLE
+350
+E
+0
+DICTIONARY
+5
+D
+102
+{ACAD_REACTORS
+330
+C
+102
+}
+100
+AcDbDictionary
+0
+DICTIONARY
+5
+E
+102
+{ACAD_REACTORS
+330
+C
+102
+}
+100
+AcDbDictionary
+3
+STANDARD
+350
+1C
+0
+MLINESTYLE
+5
+1C
+102
+{ACAD_REACTORS
+330
+E
+102
+}
+100
+AcDbMlineStyle
+2
+STANDARD
+70
+0
+3
+
+62
+0
+51
+90.0
+52
+90.0
+71
+2
+49
+0.5
+62
+256
+6
+BYLAYER
+49
+-0.5
+62
+256
+6
+BYLAYER
+0
+ENDSEC
+0
+EOF
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/emf/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/fail/CVE-2004-0209-1.emf b/vcl/qa/cppunit/graphicfilter/data/emf/fail/CVE-2004-0209-1.emf
new file mode 100644
index 0000000000..a511da43ad
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/fail/CVE-2004-0209-1.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/fail/CVE-2008-1083-1.emf b/vcl/qa/cppunit/graphicfilter/data/emf/fail/CVE-2008-1083-1.emf
new file mode 100644
index 0000000000..dd57d9102c
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/fail/CVE-2008-1083-1.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/fail/CVE-2009-1217-1.emf b/vcl/qa/cppunit/graphicfilter/data/emf/fail/CVE-2009-1217-1.emf
new file mode 100644
index 0000000000..8fa6e93779
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/fail/CVE-2009-1217-1.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/fail/crash-2.emf b/vcl/qa/cppunit/graphicfilter/data/emf/fail/crash-2.emf
new file mode 100644
index 0000000000..6adabec8b3
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/fail/crash-2.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/fail/crash-3.emf b/vcl/qa/cppunit/graphicfilter/data/emf/fail/crash-3.emf
new file mode 100644
index 0000000000..92da5f05ac
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/fail/crash-3.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/fail/fdo71307-2.emf b/vcl/qa/cppunit/graphicfilter/data/emf/fail/fdo71307-2.emf
new file mode 100644
index 0000000000..b89db21c2d
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/fail/fdo71307-2.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/fail/hang-1.emf b/vcl/qa/cppunit/graphicfilter/data/emf/fail/hang-1.emf
new file mode 100644
index 0000000000..634fccdc0f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/fail/hang-1.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/fail/hang-2.emf b/vcl/qa/cppunit/graphicfilter/data/emf/fail/hang-2.emf
new file mode 100644
index 0000000000..e3baf3bfa7
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/fail/hang-2.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/fail/slow-moveclip-1.emf b/vcl/qa/cppunit/graphicfilter/data/emf/fail/slow-moveclip-1.emf
new file mode 100644
index 0000000000..ef4c6a0097
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/fail/slow-moveclip-1.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/emf/indeterminate/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/indeterminate/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/emf/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2008-1087-1.emf b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2008-1087-1.emf
new file mode 100644
index 0000000000..c71739a50b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2008-1087-1.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2008-2245-1.emf b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2008-2245-1.emf
new file mode 100644
index 0000000000..746e85e847
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2008-2245-1.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0168-1.emf b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0168-1.emf
new file mode 100644
index 0000000000..fbd546cb00
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0168-1.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0168-2.emf b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0168-2.emf
new file mode 100644
index 0000000000..40f24b41d3
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0168-2.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0169-1.emf b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0169-1.emf
new file mode 100644
index 0000000000..dcc2a66f74
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0169-1.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0169-2.emf b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0169-2.emf
new file mode 100644
index 0000000000..b82444a97f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0169-2.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0169-3.emf b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0169-3.emf
new file mode 100644
index 0000000000..8e67ce5ae9
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0169-3.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0170-1.emf b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0170-1.emf
new file mode 100644
index 0000000000..b17ce70614
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-0170-1.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-3301-1.emf b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-3301-1.emf
new file mode 100644
index 0000000000..0991bba4fe
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-3301-1.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-3303-1.emf b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-3303-1.emf
new file mode 100644
index 0000000000..78d030daa5
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-3303-1.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-3304-1.emf b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-3304-1.emf
new file mode 100644
index 0000000000..fadbb75162
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/pass/CVE-2016-3304-1.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/pass/crash-1.emf b/vcl/qa/cppunit/graphicfilter/data/emf/pass/crash-1.emf
new file mode 100644
index 0000000000..bbc0285db6
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/pass/crash-1.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/pass/crash-2.emf b/vcl/qa/cppunit/graphicfilter/data/emf/pass/crash-2.emf
new file mode 100644
index 0000000000..a522132388
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/pass/crash-2.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/emf/pass/fdo38580-3.emf b/vcl/qa/cppunit/graphicfilter/data/emf/pass/fdo38580-3.emf
new file mode 100644
index 0000000000..0af6c749bd
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/emf/pass/fdo38580-3.emf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/eps/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/eps/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/eps/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/eps/fail/CVE-2009-4195-1.eps b/vcl/qa/cppunit/graphicfilter/data/eps/fail/CVE-2009-4195-1.eps
new file mode 100644
index 0000000000..5ae189f151
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/eps/fail/CVE-2009-4195-1.eps
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/eps/fail/short-1.eps b/vcl/qa/cppunit/graphicfilter/data/eps/fail/short-1.eps
new file mode 100644
index 0000000000..4b38b782f6
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/eps/fail/short-1.eps
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/eps/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/eps/indeterminate/.gitignore
new file mode 100644
index 0000000000..b2a2eb0476
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/eps/indeterminate/.gitignore
@@ -0,0 +1 @@
+*.eps-*
diff --git a/vcl/qa/cppunit/graphicfilter/data/eps/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/eps/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/eps/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/eps/pass/CVE-2013-4979-1.eps b/vcl/qa/cppunit/graphicfilter/data/eps/pass/CVE-2013-4979-1.eps
new file mode 100644
index 0000000000..ae6c6aad7e
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/eps/pass/CVE-2013-4979-1.eps
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/eps/pass/fdo13433-4.eps b/vcl/qa/cppunit/graphicfilter/data/eps/pass/fdo13433-4.eps
new file mode 100644
index 0000000000..6ca427f86f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/eps/pass/fdo13433-4.eps
@@ -0,0 +1,667 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Title: /home/amg/newtest05.eps
+%%Creator: matplotlib version 0.98.0, http://matplotlib.sourceforge.net/
+%%CreationDate: Sat Jul 19 09:49:37 2008
+%%Orientation: portrait
+%%BoundingBox: 13 175 598 616
+%%EndComments
+%%BeginProlog
+/mpldict 7 dict def
+mpldict begin
+/m { moveto } bind def
+/l { lineto } bind def
+/r { rlineto } bind def
+/c { curveto } bind def
+/cl { closepath } bind def
+/box {
+m
+1 index 0 r
+0 exch r
+neg 0 r
+cl
+} bind def
+/clipbox {
+box
+clip
+newpath
+} bind def
+end
+%%EndProlog
+mpldict begin
+13.5 175.5 translate
+585 441 0 0 clipbox
+1.000 setlinewidth
+0 setlinejoin
+2 setlinecap
+[] 0 setdash
+1.000 setgray
+gsave
+0 0 m
+585 0 l
+585 441 l
+0 441 l
+0 0 l
+gsave
+fill
+grestore
+stroke
+grestore
+0.000 setgray
+gsave
+73.125 44.1 m
+526.5 44.1 l
+526.5 396.9 l
+73.125 396.9 l
+73.125 44.1 l
+gsave
+1.000 setgray
+fill
+grestore
+stroke
+grestore
+0.500 setlinewidth
+0 setlinecap
+gsave
+453.4 352.8 73.12 44.1 clipbox
+/o {
+gsave
+newpath
+translate
+0 -3 m
+0.795609 -3 1.55874 -2.6839 2.12132 -2.12132 c
+2.6839 -1.55874 3 -0.795609 3 0 c
+3 0.795609 2.6839 1.55874 2.12132 2.12132 c
+1.55874 2.6839 0.795609 3 0 3 c
+-0.795609 3 -1.55874 2.6839 -2.12132 2.12132 c
+-2.6839 1.55874 -3 0.795609 -3 0 c
+-3 -0.795609 -2.6839 -1.55874 -2.12132 -2.12132 c
+-1.55874 -2.6839 -0.795609 -3 0 -3 c
+cl
+gsave
+1.000 0.000 0.000 setrgbcolor
+fill
+grestore
+stroke
+grestore
+} bind def
+73.1 92.5 o
+77.7 206 o
+82.2 204 o
+86.7 293 o
+91.3 189 o
+95.8 276 o
+100 250 o
+105 226 o
+109 240 o
+114 329 o
+118 250 o
+123 226 o
+128 99 o
+132 195 o
+137 241 o
+141 168 o
+146 166 o
+150 301 o
+155 294 o
+159 223 o
+164 220 o
+168 184 o
+173 205 o
+177 242 o
+182 320 o
+186 239 o
+191 306 o
+196 272 o
+200 96.1 o
+205 293 o
+209 265 o
+214 233 o
+218 231 o
+223 138 o
+227 204 o
+232 278 o
+236 271 o
+241 165 o
+245 199 o
+250 239 o
+254 234 o
+259 306 o
+264 304 o
+268 246 o
+273 172 o
+277 180 o
+282 246 o
+286 146 o
+291 97.8 o
+295 241 o
+300 141 o
+304 242 o
+309 177 o
+313 189 o
+318 142 o
+322 216 o
+327 273 o
+332 261 o
+336 173 o
+341 223 o
+345 282 o
+350 285 o
+354 90.1 o
+359 241 o
+363 344 o
+368 187 o
+372 172 o
+377 224 o
+381 300 o
+386 237 o
+390 107 o
+395 249 o
+400 263 o
+404 146 o
+409 162 o
+413 228 o
+418 252 o
+422 166 o
+427 255 o
+431 92.7 o
+436 277 o
+440 204 o
+445 226 o
+449 356 o
+454 277 o
+458 247 o
+463 244 o
+468 272 o
+472 286 o
+477 259 o
+481 332 o
+486 138 o
+490 212 o
+495 203 o
+499 163 o
+504 374 o
+508 371 o
+513 223 o
+517 126 o
+522 205 o
+grestore
+gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+0 4 l
+stroke
+grestore
+} bind def
+73.1 44.1 o
+grestore
+gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+0 -4 l
+stroke
+grestore
+} bind def
+73.1 397 o
+grestore
+gsave
+/Helvetica-Narrow findfont
+12.0 scalefont
+setfont
+70.389000 31.664000 translate
+0.000000 rotate
+0.000000 0.000000 m /zero glyphshow
+grestore
+ gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+0 4 l
+stroke
+grestore
+} bind def
+164 44.1 o
+grestore
+gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+0 -4 l
+stroke
+grestore
+} bind def
+164 397 o
+grestore
+gsave
+/Helvetica-Narrow findfont
+12.0 scalefont
+setfont
+158.328000 31.436000 translate
+0.000000 rotate
+0.000000 0.000000 m /two glyphshow
+5.472000 0.000000 m /zero glyphshow
+grestore
+ gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+0 4 l
+stroke
+grestore
+} bind def
+254 44.1 o
+grestore
+gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+0 -4 l
+stroke
+grestore
+} bind def
+254 397 o
+grestore
+gsave
+/Helvetica-Narrow findfont
+12.0 scalefont
+setfont
+249.003000 31.436000 translate
+0.000000 rotate
+0.000000 0.000000 m /four glyphshow
+5.472000 0.000000 m /zero glyphshow
+grestore
+ gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+0 4 l
+stroke
+grestore
+} bind def
+345 44.1 o
+grestore
+gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+0 -4 l
+stroke
+grestore
+} bind def
+345 397 o
+grestore
+gsave
+/Helvetica-Narrow findfont
+12.0 scalefont
+setfont
+339.678000 31.664000 translate
+0.000000 rotate
+0.000000 0.000000 m /six glyphshow
+5.472000 0.000000 m /zero glyphshow
+grestore
+ gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+0 4 l
+stroke
+grestore
+} bind def
+436 44.1 o
+grestore
+gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+0 -4 l
+stroke
+grestore
+} bind def
+436 397 o
+grestore
+gsave
+/Helvetica-Narrow findfont
+12.0 scalefont
+setfont
+430.353000 31.664000 translate
+0.000000 rotate
+0.000000 0.000000 m /eight glyphshow
+5.472000 0.000000 m /zero glyphshow
+grestore
+ gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+0 4 l
+stroke
+grestore
+} bind def
+526 44.1 o
+grestore
+gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+0 -4 l
+stroke
+grestore
+} bind def
+526 397 o
+grestore
+gsave
+/Helvetica-Narrow findfont
+12.0 scalefont
+setfont
+518.292000 31.436000 translate
+0.000000 rotate
+0.000000 0.000000 m /one glyphshow
+5.472000 0.000000 m /zero glyphshow
+10.944000 0.000000 m /zero glyphshow
+grestore
+ 1.000 setlinewidth
+gsave
+gsave
+/Helvetica-Narrow findfont
+12.0 scalefont
+setfont
+296.531 17.82 moveto
+0.0 rotate
+(X)
+0.000 0.000 0.000 setrgbcolor
+show
+grestore
+stroke
+grestore
+0.500 setlinewidth
+gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+4 0 l
+stroke
+grestore
+} bind def
+73.1 44.1 o
+grestore
+gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+-4 0 l
+stroke
+grestore
+} bind def
+526 44.1 o
+grestore
+gsave
+/Helvetica-Narrow findfont
+12.0 scalefont
+setfont
+57.905000 39.882000 translate
+0.000000 rotate
+0.000000 0.000000 m /minus glyphshow
+5.748000 0.000000 m /three glyphshow
+grestore
+ gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+4 0 l
+stroke
+grestore
+} bind def
+73.1 103 o
+grestore
+gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+-4 0 l
+stroke
+grestore
+} bind def
+526 103 o
+grestore
+gsave
+/Helvetica-Narrow findfont
+12.0 scalefont
+setfont
+57.905000 98.682000 translate
+0.000000 rotate
+0.000000 0.000000 m /minus glyphshow
+5.748000 0.000000 m /two glyphshow
+grestore
+ gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+4 0 l
+stroke
+grestore
+} bind def
+73.1 162 o
+grestore
+gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+-4 0 l
+stroke
+grestore
+} bind def
+526 162 o
+grestore
+gsave
+/Helvetica-Narrow findfont
+12.0 scalefont
+setfont
+57.905000 157.482000 translate
+0.000000 rotate
+0.000000 0.000000 m /minus glyphshow
+5.748000 0.000000 m /one glyphshow
+grestore
+ gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+4 0 l
+stroke
+grestore
+} bind def
+73.1 221 o
+grestore
+gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+-4 0 l
+stroke
+grestore
+} bind def
+526 221 o
+grestore
+gsave
+/Helvetica-Narrow findfont
+12.0 scalefont
+setfont
+63.653000 216.282000 translate
+0.000000 rotate
+0.000000 0.000000 m /zero glyphshow
+grestore
+ gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+4 0 l
+stroke
+grestore
+} bind def
+73.1 279 o
+grestore
+gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+-4 0 l
+stroke
+grestore
+} bind def
+526 279 o
+grestore
+gsave
+/Helvetica-Narrow findfont
+12.0 scalefont
+setfont
+63.653000 275.082000 translate
+0.000000 rotate
+0.000000 0.000000 m /one glyphshow
+grestore
+ gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+4 0 l
+stroke
+grestore
+} bind def
+73.1 338 o
+grestore
+gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+-4 0 l
+stroke
+grestore
+} bind def
+526 338 o
+grestore
+gsave
+/Helvetica-Narrow findfont
+12.0 scalefont
+setfont
+63.653000 333.882000 translate
+0.000000 rotate
+0.000000 0.000000 m /two glyphshow
+grestore
+ gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+4 0 l
+stroke
+grestore
+} bind def
+73.1 397 o
+grestore
+gsave
+/o {
+gsave
+newpath
+translate
+0 0 m
+-4 0 l
+stroke
+grestore
+} bind def
+526 397 o
+grestore
+gsave
+/Helvetica-Narrow findfont
+12.0 scalefont
+setfont
+63.653000 392.682000 translate
+0.000000 rotate
+0.000000 0.000000 m /three glyphshow
+grestore
+ 1.000 setlinewidth
+gsave
+gsave
+/Helvetica-Narrow findfont
+12.0 scalefont
+setfont
+52.905 217.218 moveto
+90.0 rotate
+(Y)
+0.000 0.000 0.000 setrgbcolor
+show
+grestore
+stroke
+grestore
+2 setlinecap
+gsave
+73.125 44.1 m
+526.5 44.1 l
+526.5 396.9 l
+73.125 396.9 l
+73.125 44.1 l
+stroke
+grestore
+0 setlinecap
+gsave
+gsave
+/Helvetica-Narrow findfont
+14.0 scalefont
+setfont
+277.798 404.798 moveto
+0.0 rotate
+(Test plot)
+0.000 0.000 0.000 setrgbcolor
+show
+grestore
+stroke
+grestore
+
+end
+showpage
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/gif/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/fail/CVE-2007-3958-1.gif b/vcl/qa/cppunit/graphicfilter/data/gif/fail/CVE-2007-3958-1.gif
new file mode 100644
index 0000000000..7e84566e94
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/fail/CVE-2007-3958-1.gif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/fail/CVE-2008-5937-1.gif b/vcl/qa/cppunit/graphicfilter/data/gif/fail/CVE-2008-5937-1.gif
new file mode 100644
index 0000000000..cbefd01625
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/fail/CVE-2008-5937-1.gif
@@ -0,0 +1 @@
+””&‡VâusØ [eë21oæX \ No newline at end of file
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/fail/EBD-36334-1.gif b/vcl/qa/cppunit/graphicfilter/data/gif/fail/EBD-36334-1.gif
new file mode 100644
index 0000000000..a8f51b6ef9
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/fail/EBD-36334-1.gif
@@ -0,0 +1,2 @@
+””&X©=ŠÜØ¿;ekbÎ~§6à*^1Ì.”Ä„#᛾fLt€wüO zØâjA÷–F®HT©Øî
+ÞeŸ€Ô ?AäUõaŒÈ»L*ÖVÉqd¦&ó`©6~[­ŠÓ…j™ÜæÏøªÖ`¦µo§D9ëÚ.>4ùÓ \ No newline at end of file
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/fail/EBD-36335-1.gif b/vcl/qa/cppunit/graphicfilter/data/gif/fail/EBD-36335-1.gif
new file mode 100644
index 0000000000..8f0f4fdb7f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/fail/EBD-36335-1.gif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/fail/EDB-23279-1.gif b/vcl/qa/cppunit/graphicfilter/data/gif/fail/EDB-23279-1.gif
new file mode 100644
index 0000000000..d81d3b084f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/fail/EDB-23279-1.gif
@@ -0,0 +1 @@
+””&H©1ŠÎØ'[ek2Ι~Ømé®ß1L-h£a[^¦Î.Þð!7¢/&»VOÊ»·BB^ïuËÃî±³È2k]YnEåG)qâ¿ \ No newline at end of file
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/fail/too-small-1.gif b/vcl/qa/cppunit/graphicfilter/data/gif/fail/too-small-1.gif
new file mode 100644
index 0000000000..26b35e63b2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/fail/too-small-1.gif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/gif/indeterminate/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/indeterminate/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/gif/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/pass/CVE-2007-6715-1.gif b/vcl/qa/cppunit/graphicfilter/data/gif/pass/CVE-2007-6715-1.gif
new file mode 100644
index 0000000000..63426f9d80
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/pass/CVE-2007-6715-1.gif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/pass/CVE-2008-3013-1.gif b/vcl/qa/cppunit/graphicfilter/data/gif/pass/CVE-2008-3013-1.gif
new file mode 100644
index 0000000000..e92a316e4b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/pass/CVE-2008-3013-1.gif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/pass/CVE-2011-2131-1.gif b/vcl/qa/cppunit/graphicfilter/data/gif/pass/CVE-2011-2131-1.gif
new file mode 100644
index 0000000000..190c7b0791
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/pass/CVE-2011-2131-1.gif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/pass/CVE-2012-0282-1.gif b/vcl/qa/cppunit/graphicfilter/data/gif/pass/CVE-2012-0282-1.gif
new file mode 100644
index 0000000000..cf4f30c21b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/pass/CVE-2012-0282-1.gif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/pass/EDB-19333-1.gif b/vcl/qa/cppunit/graphicfilter/data/gif/pass/EDB-19333-1.gif
new file mode 100644
index 0000000000..53d2ca01e8
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/pass/EDB-19333-1.gif
@@ -0,0 +1 @@
+””&t©Š};'[ek2Ι~Ømé®ß1L-h£a[^¦Î.Þð!7¢/&»VOÊ»·BB^ïuËÃî±³È2k]Ynyå°G)‹Ê¿˜ð…‘jkš×Bà:Š’åå¢d#|åµÀu«\#ÑL—®í¢¡µê@Eßý˜ \ No newline at end of file
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/pass/afl-sample-short-read-1.gif b/vcl/qa/cppunit/graphicfilter/data/gif/pass/afl-sample-short-read-1.gif
new file mode 100644
index 0000000000..7cb2a03d53
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/pass/afl-sample-short-read-1.gif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/pass/afl-sample-short-read-2.gif b/vcl/qa/cppunit/graphicfilter/data/gif/pass/afl-sample-short-read-2.gif
new file mode 100644
index 0000000000..cddbdc357f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/pass/afl-sample-short-read-2.gif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/pass/crash-1.gif b/vcl/qa/cppunit/graphicfilter/data/gif/pass/crash-1.gif
new file mode 100644
index 0000000000..860f9e1d8f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/pass/crash-1.gif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/pass/crash-2.gif b/vcl/qa/cppunit/graphicfilter/data/gif/pass/crash-2.gif
new file mode 100644
index 0000000000..b7265f807d
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/pass/crash-2.gif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/gif/pass/sf_3e0068c9b19bb548826bed0599f65745-15940-minimized.gif b/vcl/qa/cppunit/graphicfilter/data/gif/pass/sf_3e0068c9b19bb548826bed0599f65745-15940-minimized.gif
new file mode 100644
index 0000000000..47f5d4341b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/gif/pass/sf_3e0068c9b19bb548826bed0599f65745-15940-minimized.gif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/jpg/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-1.jpg b/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-1.jpg
new file mode 100644
index 0000000000..c03c8529c7
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-1.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-2.jpg b/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-2.jpg
new file mode 100644
index 0000000000..1a24da3229
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-2.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-3.jpg b/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-3.jpg
new file mode 100644
index 0000000000..794ff52e41
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-3.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-4.jpg b/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-4.jpg
new file mode 100644
index 0000000000..8911646fac
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-4.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-5.jpg b/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-5.jpg
new file mode 100644
index 0000000000..c5373df433
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-1097-5.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-5314-1.jpg b/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-5314-1.jpg
new file mode 100644
index 0000000000..33bbe9b5f9
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/fail/CVE-2008-5314-1.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/fail/crash-1.jpg b/vcl/qa/cppunit/graphicfilter/data/jpg/fail/crash-1.jpg
new file mode 100644
index 0000000000..e783bd33ee
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/fail/crash-1.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/jpg/indeterminate/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/indeterminate/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-1.jpg b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-1.jpg
new file mode 100644
index 0000000000..3d9481aca9
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-1.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-2.jpg b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-2.jpg
new file mode 100644
index 0000000000..5eb27ffb52
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-2.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-3.jpg b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-3.jpg
new file mode 100644
index 0000000000..4917f207f3
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-3.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-4.jpg b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-4.jpg
new file mode 100644
index 0000000000..9d26db0050
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-4.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-5.jpg b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-5.jpg
new file mode 100644
index 0000000000..bc668d3e3f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2004-0200-5.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2017-9614-1.jpg b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2017-9614-1.jpg
new file mode 100644
index 0000000000..675881f364
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/CVE-2017-9614-1.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/pass/EDB-24743-2.jpg b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/EDB-24743-2.jpg
new file mode 100644
index 0000000000..01e7fe16f4
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/EDB-24743-2.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/pass/EDB-24743-3.jpg b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/EDB-24743-3.jpg
new file mode 100644
index 0000000000..4753ed8db1
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/EDB-24743-3.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/jpg/pass/fatalerror-1.jpg b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/fatalerror-1.jpg
new file mode 100644
index 0000000000..c6ee535059
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/jpg/pass/fatalerror-1.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/met/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/met/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/met/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/met/fail/afl-divide-zero-1.met b/vcl/qa/cppunit/graphicfilter/data/met/fail/afl-divide-zero-1.met
new file mode 100644
index 0000000000..62ccf48f6d
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/met/fail/afl-divide-zero-1.met
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/met/fail/afl-msan-1.met b/vcl/qa/cppunit/graphicfilter/data/met/fail/afl-msan-1.met
new file mode 100644
index 0000000000..18877582a4
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/met/fail/afl-msan-1.met
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/met/fail/crash-1.met b/vcl/qa/cppunit/graphicfilter/data/met/fail/crash-1.met
new file mode 100644
index 0000000000..c46b4a9f16
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/met/fail/crash-1.met
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/met/fail/hang-1.met b/vcl/qa/cppunit/graphicfilter/data/met/fail/hang-1.met
new file mode 100644
index 0000000000..c1a095d3fa
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/met/fail/hang-1.met
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/met/fail/hang-2.met b/vcl/qa/cppunit/graphicfilter/data/met/fail/hang-2.met
new file mode 100644
index 0000000000..e807d584e3
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/met/fail/hang-2.met
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/met/fail/hang-3.met b/vcl/qa/cppunit/graphicfilter/data/met/fail/hang-3.met
new file mode 100644
index 0000000000..84b432e63f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/met/fail/hang-3.met
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/met/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/met/indeterminate/.gitignore
new file mode 100644
index 0000000000..8276f4291e
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/met/indeterminate/.gitignore
@@ -0,0 +1 @@
+*.met-*
diff --git a/vcl/qa/cppunit/graphicfilter/data/met/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/met/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/met/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/met/pass/sample.met b/vcl/qa/cppunit/graphicfilter/data/met/pass/sample.met
new file mode 100644
index 0000000000..7635e841fd
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/met/pass/sample.met
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pbm/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/pbm/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pbm/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/pbm/fail/crash-1.pbm b/vcl/qa/cppunit/graphicfilter/data/pbm/fail/crash-1.pbm
new file mode 100644
index 0000000000..9ddcddfe66
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pbm/fail/crash-1.pbm
@@ -0,0 +1,6 @@
+P3
+30000000000000000000000000000000 1
+255
+103 79 59
+ 95 7P 55
+ 87 67 51
diff --git a/vcl/qa/cppunit/graphicfilter/data/pbm/fail/hang-1.pbm b/vcl/qa/cppunit/graphicfilter/data/pbm/fail/hang-1.pbm
new file mode 100644
index 0000000000..21742d204c
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pbm/fail/hang-1.pbm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pbm/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/pbm/indeterminate/.gitignore
new file mode 100644
index 0000000000..e9c5b1765b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pbm/indeterminate/.gitignore
@@ -0,0 +1 @@
+*.ppm-*
diff --git a/vcl/qa/cppunit/graphicfilter/data/pbm/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/pbm/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pbm/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/pbm/pass/rhbz160429-1.pbm b/vcl/qa/cppunit/graphicfilter/data/pbm/pass/rhbz160429-1.pbm
new file mode 100644
index 0000000000..d6e3fc6349
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pbm/pass/rhbz160429-1.pbm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pcd/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/pcd/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pcd/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/pcd/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/pcd/indeterminate/.gitignore
new file mode 100644
index 0000000000..23ad7d1556
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pcd/indeterminate/.gitignore
@@ -0,0 +1 @@
+*.pcd-*
diff --git a/vcl/qa/cppunit/graphicfilter/data/pcd/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/pcd/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pcd/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/pcd/pass/blank-square.pcd b/vcl/qa/cppunit/graphicfilter/data/pcd/pass/blank-square.pcd
new file mode 100644
index 0000000000..a626b5f230
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pcd/pass/blank-square.pcd
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pcx/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/pcx/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pcx/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/pcx/fail/CVE-2007-3741-1.pcx b/vcl/qa/cppunit/graphicfilter/data/pcx/fail/CVE-2007-3741-1.pcx
new file mode 100644
index 0000000000..915f38aec2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pcx/fail/CVE-2007-3741-1.pcx
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pcx/fail/CVE-2007-3741-2.pcx b/vcl/qa/cppunit/graphicfilter/data/pcx/fail/CVE-2007-3741-2.pcx
new file mode 100644
index 0000000000..9c8a751a48
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pcx/fail/CVE-2007-3741-2.pcx
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pcx/fail/CVE-2007-3741-3.pcx b/vcl/qa/cppunit/graphicfilter/data/pcx/fail/CVE-2007-3741-3.pcx
new file mode 100644
index 0000000000..41175653a4
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pcx/fail/CVE-2007-3741-3.pcx
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pcx/fail/CVE-2008-1097-1.pcx b/vcl/qa/cppunit/graphicfilter/data/pcx/fail/CVE-2008-1097-1.pcx
new file mode 100644
index 0000000000..c55c64ed9a
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pcx/fail/CVE-2008-1097-1.pcx
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pcx/fail/hang-1.pcx b/vcl/qa/cppunit/graphicfilter/data/pcx/fail/hang-1.pcx
new file mode 100644
index 0000000000..73798ea561
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pcx/fail/hang-1.pcx
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pcx/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/pcx/indeterminate/.gitignore
new file mode 100644
index 0000000000..f73b097981
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pcx/indeterminate/.gitignore
@@ -0,0 +1 @@
+*.pcx-*
diff --git a/vcl/qa/cppunit/graphicfilter/data/pcx/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/pcx/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pcx/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/pcx/pass/rhbz469075-1.pcx b/vcl/qa/cppunit/graphicfilter/data/pcx/pass/rhbz469075-1.pcx
new file mode 100644
index 0000000000..d928c08908
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pcx/pass/rhbz469075-1.pcx
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pict/clipping-problem.pct b/vcl/qa/cppunit/graphicfilter/data/pict/clipping-problem.pct
new file mode 100644
index 0000000000..37fe66c80f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pict/clipping-problem.pct
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pict/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/pict/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pict/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/pict/fail/CVE-2008-1097-1.pct b/vcl/qa/cppunit/graphicfilter/data/pict/fail/CVE-2008-1097-1.pct
new file mode 100644
index 0000000000..73943c9d79
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pict/fail/CVE-2008-1097-1.pct
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pict/fail/CVE-2012-0277-1.pct b/vcl/qa/cppunit/graphicfilter/data/pict/fail/CVE-2012-0277-1.pct
new file mode 100644
index 0000000000..5683a55b5f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pict/fail/CVE-2012-0277-1.pct
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pict/fail/CVE-2013-2577-1.pct b/vcl/qa/cppunit/graphicfilter/data/pict/fail/CVE-2013-2577-1.pct
new file mode 100644
index 0000000000..1e1f6d5b5f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pict/fail/CVE-2013-2577-1.pct
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pict/fail/EDB-19332-1.pct b/vcl/qa/cppunit/graphicfilter/data/pict/fail/EDB-19332-1.pct
new file mode 100644
index 0000000000..5f87403050
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pict/fail/EDB-19332-1.pct
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pict/fail/exception-1.pct b/vcl/qa/cppunit/graphicfilter/data/pict/fail/exception-1.pct
new file mode 100644
index 0000000000..f9cd85a4ae
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pict/fail/exception-1.pct
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pict/fail/hang-1.pct b/vcl/qa/cppunit/graphicfilter/data/pict/fail/hang-1.pct
new file mode 100644
index 0000000000..735ce0aca7
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pict/fail/hang-1.pct
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pict/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/pict/indeterminate/.gitignore
new file mode 100644
index 0000000000..1bdee77373
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pict/indeterminate/.gitignore
@@ -0,0 +1,2 @@
+*.pict-*
+*.pct-*
diff --git a/vcl/qa/cppunit/graphicfilter/data/pict/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/pict/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pict/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/pict/pass/ooo25876-2.pct b/vcl/qa/cppunit/graphicfilter/data/pict/pass/ooo25876-2.pct
new file mode 100644
index 0000000000..9807e36f74
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pict/pass/ooo25876-2.pct
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/pict/pass/tdf92789.pct b/vcl/qa/cppunit/graphicfilter/data/pict/pass/tdf92789.pct
new file mode 100644
index 0000000000..2d6f0d8848
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/pict/pass/tdf92789.pct
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/png/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2004-0597-1.png b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2004-0597-1.png
new file mode 100644
index 0000000000..fa90a296f9
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2004-0597-1.png
@@ -0,0 +1,3 @@
+Àœë#Mb£Š}ÕÔo7ë2ÎË~X¨á.^TÿwBè„!õ›žf1±°ƒÿ»±sé ‘tšùgšça2bA±Õð‡ÁËHbè—"8àî|†ìeGf­S$N0nI€Öªõ
+Ôç0"ð—JG°zÀ¤Ü¢(s?d)À"Ëÿ‘GE¢×F¯–9~}–ÇrÕ TÎp?áÅÂ*¿ìò·¥ckµ$E"ŒXï¯8á¾=2±T_3³v¿™#é –á$Hh4«‰JÑKiÝŠJÿ&7r…ú€…Ï=uŠ¯ù69KÙjãûäÎçèÿëWh{‘é½Ï$· dVÅÜ[îÐЖ™Êy\à%Žº%†Ç¾H® meÛÃÞ+ “Á}€ÀgXI¡2ñ>‰*Ä«õ&ù˜Õú›Í· )†Ì¸6ÔpU‚TjODhÙ¶1™éù-ÄÔ<WµŒUR±Kø591Òþ¦«M“„?
+~˜æ*Nr¡Ìu;µãÀkh©ÉXˆÔà{ÖßÔ¤»' Ów©ìF[—ÛÒKèRÓf§y›‹O¹¨%0´©iháx׃‹€wz¿4dT.¥@ŒXm4¦Þi¤íô÷pçð¬Z¼¾^±ßy‘˜ÝÂЯú`®ºÎ_YŸ¬? …t‹uw4\kÁd¬J~m˜‹gú`<2ìl²Ñn¦ÒãùÞ*ð òök h*n÷„w7ƒ‘!“YIßP+hK†Ø*Ôž`õ?Ëâç˜ü \ No newline at end of file
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2005-0633-1.png b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2005-0633-1.png
new file mode 100644
index 0000000000..d0644d1397
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2005-0633-1.png
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2006-7210-1.png b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2006-7210-1.png
new file mode 100644
index 0000000000..9b30cc38c7
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2006-7210-1.png
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2007-2365-1.png b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2007-2365-1.png
new file mode 100644
index 0000000000..b9ff67bb8b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2007-2365-1.png
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2009-1511-1.png b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2009-1511-1.png
new file mode 100644
index 0000000000..592fda10aa
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2009-1511-1.png
@@ -0,0 +1 @@
+Àœë#Mb£Š}ÕÔo7ë2Í~\íá._舄Ã{ÜÚß'p|&êFàà¨/û§§‚ô¬ \ No newline at end of file
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2016-0951-2.png b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2016-0951-2.png
new file mode 100644
index 0000000000..38899f7336
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2016-0951-2.png
@@ -0,0 +1 @@
+Àœë#Mb£Š}ÕÔo7ë2ΰ~XÍù.^@Jäiè„#åüß+p¨%çHh¢/Ù¦ô‹0!0õ‚ òþõ{åﶌ[¦ÜB…]2ÓŒ*[»@Κâæ}¾{` RötÔ´Ž|}ø·Ï3ëÁ€N=aè‰DúITÇgI‰ã!³…Ò¨C]ËõÍÀ†ûïÚ åˆíEr–GOXÕö°9ò Ì“øˆÏ^Œ;²0/A î±ìî)¢O²"vg Óº¹jõ”«1•»èá¨{b*[¼›o:Ù–ƒw*^_ˉœ öi8˜¨¼d°q?]þ0Û}È´‰ á$õ}Ñê|6Es0”x%mL¤ à-Àm¹÷÷ë:Ó ÒÄæ \ No newline at end of file
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2016-0952-1.png b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2016-0952-1.png
new file mode 100644
index 0000000000..7848d5ad56
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2016-0952-1.png
@@ -0,0 +1 @@
+Àœë#Mb£Š}ÕÔo7ë2Δ~Xéè._ló!©è„#åüß+p¨%çHh¢/ÙG%òƒL¹¹Ö69lÂ˜R?¦nCG³x¥öP¹¢&ŒØÈœîs0­ÄÉÈ- C¤ Ê99$8ôênœ;GF£Ô¾c \ No newline at end of file
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2016-0952-2.png b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2016-0952-2.png
new file mode 100644
index 0000000000..5494ca00e3
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/fail/CVE-2016-0952-2.png
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/fail/EDB-34720-1.png b/vcl/qa/cppunit/graphicfilter/data/png/fail/EDB-34720-1.png
new file mode 100644
index 0000000000..3165f2c5e2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/fail/EDB-34720-1.png
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/fail/ofz32026.png b/vcl/qa/cppunit/graphicfilter/data/png/fail/ofz32026.png
new file mode 100644
index 0000000000..290671ecb4
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/fail/ofz32026.png
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/png/indeterminate/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/indeterminate/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/png/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/pass/CVE-2016-0951-1.png b/vcl/qa/cppunit/graphicfilter/data/png/pass/CVE-2016-0951-1.png
new file mode 100644
index 0000000000..b5e6220dcd
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/pass/CVE-2016-0951-1.png
@@ -0,0 +1 @@
+Àœë#Mb£Š}ÕÔo7ë2Ε~Xèë._ÁÍãâè„#åüß+p¨±çHh¢/ÙG%òƒL¹¹Ö69lÂî±µ˜R?Y‘Š’¼¸ÖæôÑ)RÈ€ôAÙólþ7½„*…’[.óïþ{òýH –Þá‚ÒçZ#ᣠ\ No newline at end of file
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/pass/afl-sample-IDAT.png b/vcl/qa/cppunit/graphicfilter/data/png/pass/afl-sample-IDAT.png
new file mode 100644
index 0000000000..b116a92ecd
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/pass/afl-sample-IDAT.png
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/pass/afl-sample-Z_NEED_DICT.png b/vcl/qa/cppunit/graphicfilter/data/png/pass/afl-sample-Z_NEED_DICT.png
new file mode 100644
index 0000000000..db8e7a834a
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/pass/afl-sample-Z_NEED_DICT.png
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/pass/black.png b/vcl/qa/cppunit/graphicfilter/data/png/pass/black.png
new file mode 100644
index 0000000000..cbba93bedd
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/pass/black.png
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/png/pass/invalid-chunk.png b/vcl/qa/cppunit/graphicfilter/data/png/pass/invalid-chunk.png
new file mode 100644
index 0000000000..1c45c7689e
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/png/pass/invalid-chunk.png
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/ppm/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/ppm/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/ppm/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/ppm/fail/CVE-2008-1097-1.ppm b/vcl/qa/cppunit/graphicfilter/data/ppm/fail/CVE-2008-1097-1.ppm
new file mode 100644
index 0000000000..457289577d
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/ppm/fail/CVE-2008-1097-1.ppm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/ppm/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/ppm/indeterminate/.gitignore
new file mode 100644
index 0000000000..e9c5b1765b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/ppm/indeterminate/.gitignore
@@ -0,0 +1 @@
+*.ppm-*
diff --git a/vcl/qa/cppunit/graphicfilter/data/ppm/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/ppm/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/ppm/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/ppm/pass/fdo19811-2.ppm b/vcl/qa/cppunit/graphicfilter/data/ppm/pass/fdo19811-2.ppm
new file mode 100644
index 0000000000..828b972569
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/ppm/pass/fdo19811-2.ppm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/psd/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/psd/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/psd/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/psd/fail/CVE-2007-3741-1.psd b/vcl/qa/cppunit/graphicfilter/data/psd/fail/CVE-2007-3741-1.psd
new file mode 100644
index 0000000000..59b690063b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/psd/fail/CVE-2007-3741-1.psd
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/psd/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/psd/indeterminate/.gitignore
new file mode 100644
index 0000000000..49b8ba044f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/psd/indeterminate/.gitignore
@@ -0,0 +1 @@
+*.psd-*
diff --git a/vcl/qa/cppunit/graphicfilter/data/psd/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/psd/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/psd/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/psd/pass/blank-square.psd b/vcl/qa/cppunit/graphicfilter/data/psd/pass/blank-square.psd
new file mode 100644
index 0000000000..fc811da006
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/psd/pass/blank-square.psd
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/psd/pass/hang-1.psd b/vcl/qa/cppunit/graphicfilter/data/psd/pass/hang-1.psd
new file mode 100644
index 0000000000..8f557dd80d
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/psd/pass/hang-1.psd
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/psd/pass/rhbz899670-1.psd b/vcl/qa/cppunit/graphicfilter/data/psd/pass/rhbz899670-1.psd
new file mode 100644
index 0000000000..ce8de8493e
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/psd/pass/rhbz899670-1.psd
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/psd/tdf142629.psd b/vcl/qa/cppunit/graphicfilter/data/psd/tdf142629.psd
new file mode 100644
index 0000000000..097536bcc4
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/psd/tdf142629.psd
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/ras/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/ras/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/ras/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/ras/fail/CVE-2007-2356-1.ras b/vcl/qa/cppunit/graphicfilter/data/ras/fail/CVE-2007-2356-1.ras
new file mode 100644
index 0000000000..c00c27016b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/ras/fail/CVE-2007-2356-1.ras
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/ras/fail/CVE-2008-1097-1.ras b/vcl/qa/cppunit/graphicfilter/data/ras/fail/CVE-2008-1097-1.ras
new file mode 100644
index 0000000000..effd654ac5
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/ras/fail/CVE-2008-1097-1.ras
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/ras/fail/crash-1.ras b/vcl/qa/cppunit/graphicfilter/data/ras/fail/crash-1.ras
new file mode 100644
index 0000000000..d1abbaefcb
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/ras/fail/crash-1.ras
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/ras/fail/hang-1.ras b/vcl/qa/cppunit/graphicfilter/data/ras/fail/hang-1.ras
new file mode 100644
index 0000000000..44dec67607
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/ras/fail/hang-1.ras
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/ras/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/ras/indeterminate/.gitignore
new file mode 100644
index 0000000000..60147ad08a
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/ras/indeterminate/.gitignore
@@ -0,0 +1 @@
+*.ras-*
diff --git a/vcl/qa/cppunit/graphicfilter/data/ras/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/ras/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/ras/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/ras/pass/marbles.ras b/vcl/qa/cppunit/graphicfilter/data/ras/pass/marbles.ras
new file mode 100644
index 0000000000..9b3fb0d853
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/ras/pass/marbles.ras
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/svm/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/svm/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/svm/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-1.svm b/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-1.svm
new file mode 100644
index 0000000000..2fce465f76
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-1.svm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-2.svm b/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-2.svm
new file mode 100644
index 0000000000..1b3cd14167
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-2.svm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-3.svm b/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-3.svm
new file mode 100644
index 0000000000..b4afeb09f2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-3.svm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-4.svm b/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-4.svm
new file mode 100644
index 0000000000..c2f14d4a0e
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-4.svm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-5.svm b/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-5.svm
new file mode 100644
index 0000000000..835e7f6683
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-5.svm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-6.svm b/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-6.svm
new file mode 100644
index 0000000000..8d6d94e126
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/svm/fail/mapmode-6.svm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/svm/fail/ofz7165-1.svm b/vcl/qa/cppunit/graphicfilter/data/svm/fail/ofz7165-1.svm
new file mode 100644
index 0000000000..ad722ea13a
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/svm/fail/ofz7165-1.svm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/svm/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/svm/indeterminate/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/svm/indeterminate/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/svm/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/svm/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/svm/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/svm/pass/leak-1.svm b/vcl/qa/cppunit/graphicfilter/data/svm/pass/leak-1.svm
new file mode 100644
index 0000000000..14dbea0809
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/svm/pass/leak-1.svm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/svm/pass/ofz-timeout-1.svm b/vcl/qa/cppunit/graphicfilter/data/svm/pass/ofz-timeout-1.svm
new file mode 100644
index 0000000000..5b61891be6
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/svm/pass/ofz-timeout-1.svm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/svm/pass/ofz32885-1.svm b/vcl/qa/cppunit/graphicfilter/data/svm/pass/ofz32885-1.svm
new file mode 100644
index 0000000000..b4d5126b95
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/svm/pass/ofz32885-1.svm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/svm/pass/ofz45269-1.svm b/vcl/qa/cppunit/graphicfilter/data/svm/pass/ofz45269-1.svm
new file mode 100644
index 0000000000..4dceee3a9d
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/svm/pass/ofz45269-1.svm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/svm/pass/ofz45583-1.svm b/vcl/qa/cppunit/graphicfilter/data/svm/pass/ofz45583-1.svm
new file mode 100644
index 0000000000..d730a1bccf
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/svm/pass/ofz45583-1.svm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tga/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/tga/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tga/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/tga/fail/CVE-2012-3755-1.tga b/vcl/qa/cppunit/graphicfilter/data/tga/fail/CVE-2012-3755-1.tga
new file mode 100644
index 0000000000..963e5d3aed
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tga/fail/CVE-2012-3755-1.tga
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tga/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/tga/indeterminate/.gitignore
new file mode 100644
index 0000000000..38bf024a9b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tga/indeterminate/.gitignore
@@ -0,0 +1 @@
+*.tga-*
diff --git a/vcl/qa/cppunit/graphicfilter/data/tga/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/tga/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tga/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/tga/pass/fdo14924-5.tga b/vcl/qa/cppunit/graphicfilter/data/tga/pass/fdo14924-5.tga
new file mode 100644
index 0000000000..c3b38f313f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tga/pass/fdo14924-5.tga
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tga/pass/fdo14924-6.tga b/vcl/qa/cppunit/graphicfilter/data/tga/pass/fdo14924-6.tga
new file mode 100644
index 0000000000..92b7200537
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tga/pass/fdo14924-6.tga
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/blue16.tif b/vcl/qa/cppunit/graphicfilter/data/tiff/blue16.tif
new file mode 100644
index 0000000000..284594d34d
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/blue16.tif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/blue8.tif b/vcl/qa/cppunit/graphicfilter/data/tiff/blue8.tif
new file mode 100644
index 0000000000..29d17dcb32
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/blue8.tif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/BID-51132-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/BID-51132-1.tiff
new file mode 100644
index 0000000000..929ac15f16
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/BID-51132-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2006-3459-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2006-3459-1.tiff
new file mode 100644
index 0000000000..323fb17619
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2006-3459-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2007-2217-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2007-2217-1.tiff
new file mode 100644
index 0000000000..e078b73d0b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2007-2217-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2009-2285-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2009-2285-1.tiff
new file mode 100644
index 0000000000..896de2b7b0
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2009-2285-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2010-2482-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2010-2482-1.tiff
new file mode 100644
index 0000000000..59e3c7ed6f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2010-2482-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2013-3906-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2013-3906-1.tiff
new file mode 100644
index 0000000000..a7704996cb
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2013-3906-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2013-5575-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2013-5575-1.tiff
new file mode 100644
index 0000000000..ae3cee16c0
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2013-5575-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-10688-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-10688-1.tiff
new file mode 100644
index 0000000000..a41eb50b98
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-10688-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-18013-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-18013-1.tiff
new file mode 100644
index 0000000000..c023320b64
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-18013-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-9147-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-9147-1.tiff
new file mode 100644
index 0000000000..d59119e4f4
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-9147-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-9936-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-9936-1.tiff
new file mode 100644
index 0000000000..372a33b6f1
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-9936-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-9937-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-9937-1.tiff
new file mode 100644
index 0000000000..b12bf6bd4d
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2017-9937-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2022-1210-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2022-1210-1.tiff
new file mode 100644
index 0000000000..78460c790c
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/CVE-2022-1210-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/EBD-22681-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/EBD-22681-1.tiff
new file mode 100644
index 0000000000..614051dd74
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/EBD-22681-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/EDB-24743-5.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/EDB-24743-5.tiff
new file mode 100644
index 0000000000..5f5feed59f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/EDB-24743-5.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-1.tiff
new file mode 100644
index 0000000000..9e9d192f9c
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-2.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-2.tiff
new file mode 100644
index 0000000000..8aaede4a7a
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-2.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-3.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-3.tiff
new file mode 100644
index 0000000000..712e780a94
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-3.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-4.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-4.tiff
new file mode 100644
index 0000000000..7c5402e76f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-4.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-5.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-5.tiff
new file mode 100644
index 0000000000..97433c3545
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-5.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-6.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-6.tiff
new file mode 100644
index 0000000000..2b2846df0c
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-6.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-7.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-7.tiff
new file mode 100644
index 0000000000..0ca75a0a79
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-crash-7.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-1.tiff
new file mode 100644
index 0000000000..5b5b72d4a0
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-10.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-10.tiff
new file mode 100644
index 0000000000..ad722fc5c9
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-10.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-2.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-2.tiff
new file mode 100644
index 0000000000..9924b38981
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-2.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-3.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-3.tiff
new file mode 100644
index 0000000000..8de41b59f2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-3.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-4.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-4.tiff
new file mode 100644
index 0000000000..e51e391922
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-4.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-5.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-5.tiff
new file mode 100644
index 0000000000..1123daceeb
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-5.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-6.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-6.tiff
new file mode 100644
index 0000000000..fc65d92d27
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-6.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-7.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-7.tiff
new file mode 100644
index 0000000000..cbac073268
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-7.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-8.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-8.tiff
new file mode 100644
index 0000000000..2ee04554da
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-8.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-9.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-9.tiff
new file mode 100644
index 0000000000..719f5931c3
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/RC4-hang-9.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/fail/ofz47589.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/ofz47589.tiff
new file mode 100644
index 0000000000..936afbd992
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/fail/ofz47589.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/green16.tif b/vcl/qa/cppunit/graphicfilter/data/tiff/green16.tif
new file mode 100644
index 0000000000..28ae344d82
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/green16.tif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/green8.tif b/vcl/qa/cppunit/graphicfilter/data/tiff/green8.tif
new file mode 100644
index 0000000000..945ddc7a04
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/green8.tif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/tiff/indeterminate/.gitignore
new file mode 100644
index 0000000000..9c056f0969
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/indeterminate/.gitignore
@@ -0,0 +1,2 @@
+*.tiff-*
+*.tif-*
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/tiff/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2005-1544-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2005-1544-1.tiff
new file mode 100644
index 0000000000..a985d63d71
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2005-1544-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2006-2656-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2006-2656-1.tiff
new file mode 100644
index 0000000000..c61878e9b6
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2006-2656-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2012-0276-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2012-0276-1.tiff
new file mode 100644
index 0000000000..ea6dd77bb3
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2012-0276-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2012-0276-2.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2012-0276-2.tiff
new file mode 100644
index 0000000000..a66476b7da
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2012-0276-2.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2012-2027-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2012-2027-1.tiff
new file mode 100644
index 0000000000..ea6dd77bb3
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/pass/CVE-2012-2027-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/pass/multi-page-1.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/pass/multi-page-1.tiff
new file mode 100644
index 0000000000..8eb7c80780
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/pass/multi-page-1.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/pass/tdf149417.tiff b/vcl/qa/cppunit/graphicfilter/data/tiff/pass/tdf149417.tiff
new file mode 100644
index 0000000000..07c0d8972f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/pass/tdf149417.tiff
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/red16.tif b/vcl/qa/cppunit/graphicfilter/data/tiff/red16.tif
new file mode 100644
index 0000000000..9ec1e8d3fa
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/red16.tif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/red8.tif b/vcl/qa/cppunit/graphicfilter/data/tiff/red8.tif
new file mode 100644
index 0000000000..be41707aff
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/red8.tif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/tdf115863.tif b/vcl/qa/cppunit/graphicfilter/data/tiff/tdf115863.tif
new file mode 100644
index 0000000000..b5e2dd9e4b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/tdf115863.tif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/tdf126460.tif b/vcl/qa/cppunit/graphicfilter/data/tiff/tdf126460.tif
new file mode 100644
index 0000000000..ffdf6a1d56
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/tdf126460.tif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/tdf138818.tif b/vcl/qa/cppunit/graphicfilter/data/tiff/tdf138818.tif
new file mode 100644
index 0000000000..bb3e51011b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/tdf138818.tif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/tdf149418.tif b/vcl/qa/cppunit/graphicfilter/data/tiff/tdf149418.tif
new file mode 100644
index 0000000000..dd451e001a
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/tdf149418.tif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/tiff/tdf74331.tif b/vcl/qa/cppunit/graphicfilter/data/tiff/tdf74331.tif
new file mode 100644
index 0000000000..702b8218ca
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/tiff/tdf74331.tif
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossless.webp b/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossless.webp
new file mode 100644
index 0000000000..abb67cc5f4
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossless.webp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossy.webp b/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossy.webp
new file mode 100644
index 0000000000..c587b1bb22
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossy.webp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/webp/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/webp/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/webp/indeterminate/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/webp/indeterminate/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossless.webp b/vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossless.webp
new file mode 100644
index 0000000000..b22ebc3b28
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossless.webp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossy.webp b/vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossy.webp
new file mode 100644
index 0000000000..c118febb0e
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossy.webp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/webp/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/webp/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2005-2123-1.wmf-0.009-676 b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2005-2123-1.wmf-0.009-676
new file mode 100644
index 0000000000..49d3ddf28b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2005-2123-1.wmf-0.009-676
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2005-2124-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2005-2124-1.wmf
new file mode 100644
index 0000000000..ac546ce5b1
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2005-2124-1.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2005-4560-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2005-4560-1.wmf
new file mode 100644
index 0000000000..aab34004e9
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2005-4560-1.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2006-0143-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2006-0143-1.wmf
new file mode 100644
index 0000000000..b68b7403c3
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2006-0143-1.wmf
@@ -0,0 +1 @@
+HUÛ¬.DZ©Š|Ød[eë2Ë~Sïb&[1Ì-kèÀg¥ßÚ"uZJjÞë<í‘êweƒù»·üÿâŠÏÂî±¼ \ No newline at end of file
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2006-0143-2.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2006-0143-2.wmf
new file mode 100644
index 0000000000..370abe0e24
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2006-0143-2.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2007-1238-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2007-1238-1.wmf
new file mode 100644
index 0000000000..10da327425
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2007-1238-1.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2007-1245-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2007-1245-1.wmf
new file mode 100644
index 0000000000..10da327425
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/CVE-2007-1245-1.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/RC4-hang-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/RC4-hang-1.wmf
new file mode 100644
index 0000000000..87319c2d44
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/RC4-hang-1.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/RC4-hang-2.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/RC4-hang-2.wmf
new file mode 100644
index 0000000000..ff20cad78c
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/RC4-hang-2.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/RC4-hang-3.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/RC4-hang-3.wmf
new file mode 100644
index 0000000000..20a70fa678
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/RC4-hang-3.wmf
@@ -0,0 +1,5 @@
+HUÒ¬BGx©Š}ؘؤ`{3Χ~§é._1Ì,´Ê„"àšžf[Ch!64¢/ÙLV°Ê@H½½¡Îs0=3±¼Èk]ÙiuåC)N¯F—)­#™É°‹ðÅÁ÷-hK§DŽz8ôê
+Õ~ " –Þ¤†Ñ‚³‚!>øø¼Šù¯iŸÐHh“‚×®¦cCߨ?j‡‡aC °Ùε
+®¨#êBqŒoìb뀜Ûa6œTâ_‰–ljWõ+¦³ o'”P¨eSÑ%­^̈«%©'™…æÅ_VÇKpJ62AFéÔÅ<+q‰›³Iˆjbö¶n} {êgÌÆ"–û!ˆk[3ÀA²Ã+t}s
+¢-·Ò>m=÷÷¢~HZ…§(¾®Þ” Òßfß[ÿcŒ ¬36œ§‹Š†_G¯„ߎ\äÉV@BzyâGßd2Înƒ‡‹)|3 ÃŽcz™¼îâD÷‰¿Ýæ2_æ¨&pN‘xø9YÝXDž`ýFh¿4+·õC^§b×Ë?o
+@Öƒˆq \ No newline at end of file
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/RC4-hang-4.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/RC4-hang-4.wmf
new file mode 100644
index 0000000000..045f1f45eb
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/RC4-hang-4.wmf
@@ -0,0 +1,3 @@
+ž˜6.Gx©ŠGÓ;>³eë2Î 8Yíà-Ñ1Ì-‘èŠ#ᛞv5."Þ2¢/Ùf_°ÔL½B¡ÎŒÏÄê±´Èi]YowÙ¼(ŽçKP¹
+)W`1a¨%#X„mi×-ã
+HŽ‘n<ônÕ~  –ÖáË•©Œ¡Z!> Ø|xíHf}ÐHœôx;ºëcCa¹?jlG‘CLO9VƵ$˜è#äBq—]a¦Ök Ý.²w°Åx”}þ†ˆÆùº¤ß c„461ŠJ‘pHŽÑ«!©'f‡æ¥7VÇWpJp2AFvGÍ<7jú \ No newline at end of file
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/bitcount-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/bitcount-1.wmf
new file mode 100644
index 0000000000..2ec88066f5
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/bitcount-1.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/exttextout-2.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/exttextout-2.wmf
new file mode 100644
index 0000000000..02c72ad88f
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/exttextout-2.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/facename-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/facename-1.wmf
new file mode 100644
index 0000000000..29c534fdcc
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/facename-1.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/ofz35149-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/ofz35149-1.wmf
new file mode 100644
index 0000000000..f91e3a9fb0
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/ofz35149-1.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/ofz35150-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/ofz35150-1.wmf
new file mode 100644
index 0000000000..2d15619819
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/ofz35150-1.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/ofz5942-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/ofz5942-1.wmf
new file mode 100644
index 0000000000..f9a72867c5
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/ofz5942-1.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/fail/seek-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/seek-1.wmf
new file mode 100644
index 0000000000..e2fac15234
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/fail/seek-1.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/wmf/indeterminate/.gitignore
new file mode 100644
index 0000000000..583b009c7c
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/indeterminate/.gitignore
@@ -0,0 +1 @@
+*.wmf-*
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/wmf/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/pass/CVE-2005-2123-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/pass/CVE-2005-2123-1.wmf
new file mode 100644
index 0000000000..e70664e64a
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/pass/CVE-2005-2123-1.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/pass/CVE-2006-4071-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/pass/CVE-2006-4071-1.wmf
new file mode 100644
index 0000000000..cdb09c6b21
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/pass/CVE-2006-4071-1.wmf
@@ -0,0 +1 @@
+HUÛ¬.DZ©Š¡üI2ÆwÉ~¤ïé._1Ì-œè„#™žf1.!Þ0¢/Ù¸T¸ÊDH½½¡NÏÂî±¼ \ No newline at end of file
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/pass/CVE-2007-1090-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/pass/CVE-2007-1090-1.wmf
new file mode 100644
index 0000000000..7864da572c
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/pass/CVE-2007-1090-1.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/pass/CVE-2015-0848-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/pass/CVE-2015-0848-1.wmf
new file mode 100644
index 0000000000..1512a2256b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/pass/CVE-2015-0848-1.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/pass/exttextout-1.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/pass/exttextout-1.wmf
new file mode 100644
index 0000000000..365a247a71
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/pass/exttextout-1.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/wmf/pass/noheader.wmf b/vcl/qa/cppunit/graphicfilter/data/wmf/pass/noheader.wmf
new file mode 100644
index 0000000000..bfd7e20de9
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/wmf/pass/noheader.wmf
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/xbm/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/xbm/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/xbm/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/xbm/fail/crash-1.xbm b/vcl/qa/cppunit/graphicfilter/data/xbm/fail/crash-1.xbm
new file mode 100644
index 0000000000..9d2a434707
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/xbm/fail/crash-1.xbm
@@ -0,0 +1,12 @@
+#define?te_width 31
+#define te_height = {
+ 0x0e, 0x20, 0x02, 0x38, 0x11, 0x70, 0x07, 0x 0x44,
+ 0x11, 1x2e, 0x3a, 0x44, 0x8e, 0x24, 0x92, 0x38, 0xdf, 0x25, 0xd2,0x7d,
+ 0x91, 0x24 0x92, 0x44, 0x95: 0x24, 0x92, 0x54, 0xf5, 0x7f, 0xff, 0x57,
+ 0x95, 0x24, 0x92, 0x54, 0x95, 0x 4,54, 0x95, 0x24, 0x92, 0x54,
+ 0x95, 0x24,x54, 0x95, 0x24, 0x92, 0x54, 0x95, 0x24, 0x92, 0x54,
+ 0x95, 0x24, 0x92, 0x54, 0x95, 0x24, 0x92,0x54, 0x95, 0x24, 0x92, 0x54,
+ 0x95, 0x24,ÏÏÏÏÏÏÏ 0x92, 0x54, 0x95, 0x24, 0x92, 0x54, 0xf5, 0x7f, 0xff, 0x57,
+ 0x95,0x24, 0x92, 0x54, 0x95, 0x24, 0x92, 0x54, 0, 0x54, 0xf5, 0xd= {
+ 25, 0xd2, 0x7d,
+0x00, 0x7c }; \ No newline at end of file
diff --git a/vcl/qa/cppunit/graphicfilter/data/xbm/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/xbm/indeterminate/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/xbm/indeterminate/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/xbm/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/xbm/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/xbm/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/xbm/pass/grafix4.xbm b/vcl/qa/cppunit/graphicfilter/data/xbm/pass/grafix4.xbm
new file mode 100644
index 0000000000..aad9f03050
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/xbm/pass/grafix4.xbm
@@ -0,0 +1,2011 @@
+#define Grafix1_width 485
+#define Grafix1_height 395
+static char Grafix1_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x07, 0x00, 0x1c, 0xfc, 0x80, 0x81, 0x03, 0x30, 0x70, 0x00, 0x00, 0x00,
+ 0xe0, 0x00, 0x0c, 0xdc, 0x00, 0x70, 0x00, 0x37, 0x07, 0x00, 0x38, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x80, 0x01, 0x00,
+ 0x00, 0x06, 0x00, 0x18, 0xd6, 0x80, 0xc1, 0x02, 0x30, 0x58, 0x00, 0x00,
+ 0x00, 0xb0, 0x00, 0x0c, 0xd6, 0x00, 0x60, 0x80, 0x35, 0x06, 0x00, 0x30,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x80, 0x01,
+ 0x00, 0x00, 0x06, 0x00, 0x18, 0xc2, 0x00, 0x40, 0x00, 0x00, 0x08, 0x00,
+ 0x00, 0x00, 0x10, 0x00, 0x00, 0x02, 0x00, 0x60, 0x80, 0x00, 0x06, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x80,
+ 0x01, 0x00, 0x00, 0x06, 0x00, 0x18, 0xc3, 0x00, 0x60, 0x00, 0x00, 0x0c,
+ 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x03, 0x00, 0x60, 0xc0, 0x00, 0x06,
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x58,
+ 0xf0, 0xe1, 0x63, 0xc1, 0x87, 0x0f, 0x9f, 0xcf, 0xcc, 0xf1, 0xe1, 0x3b,
+ 0x3e, 0xe0, 0x01, 0x0e, 0x7f, 0xf8, 0x8e, 0xef, 0xe0, 0x63, 0xe6, 0x3b,
+ 0xe6, 0xe0, 0x33, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x64, 0x98, 0x99, 0x91, 0x61, 0x66, 0x86, 0x19, 0xc3, 0x9e, 0x61, 0x98,
+ 0x31, 0x0c, 0x30, 0x03, 0x8c, 0x19, 0x66, 0x0c, 0xc3, 0x98, 0x61, 0xcf,
+ 0x30, 0x46, 0x98, 0xb1, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x4c, 0x8c, 0x19, 0x31, 0x31, 0x66, 0xc4, 0x18, 0xc3, 0x99, 0x61,
+ 0x18, 0x31, 0x0c, 0x18, 0x00, 0xd8, 0x18, 0x46, 0x0c, 0xc3, 0x18, 0xe1,
+ 0xcc, 0x30, 0x26, 0x18, 0x71, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x1c, 0x8c, 0x19, 0x71, 0x30, 0x66, 0xc4, 0x18, 0xc3, 0x98,
+ 0x61, 0x18, 0x31, 0x0c, 0x18, 0x00, 0x70, 0x18, 0x46, 0x0c, 0xc3, 0x18,
+ 0x61, 0xcc, 0x30, 0x16, 0x18, 0x31, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x38, 0x8c, 0x99, 0xe1, 0x30, 0x66, 0xc6, 0x18, 0xc3,
+ 0x98, 0x61, 0x98, 0x31, 0x0c, 0x18, 0x00, 0x70, 0x18, 0x66, 0x0c, 0xc3,
+ 0x98, 0x61, 0xcc, 0x30, 0x1e, 0x98, 0x31, 0x06, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x70, 0x8c, 0xf1, 0xc0, 0x31, 0xc6, 0xc3, 0x18,
+ 0xc3, 0x98, 0x61, 0xf0, 0x30, 0x0c, 0x18, 0x00, 0x70, 0x18, 0x3c, 0x0c,
+ 0xc3, 0xf0, 0x60, 0xcc, 0x30, 0x36, 0xf0, 0x30, 0x06, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x64, 0x8c, 0x11, 0x90, 0x31, 0x46, 0xc0,
+ 0x18, 0xc3, 0x98, 0x61, 0x10, 0x30, 0x0c, 0x18, 0x00, 0xd8, 0x18, 0x04,
+ 0x0c, 0xc3, 0x10, 0x60, 0xcc, 0x30, 0x66, 0x10, 0x30, 0x06, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x4c, 0x98, 0xf1, 0x31, 0x61, 0xc6,
+ 0x87, 0x19, 0xc3, 0x98, 0x61, 0xf0, 0x31, 0x0c, 0x30, 0x03, 0x8c, 0x19,
+ 0x7c, 0x0c, 0xc3, 0xf0, 0x61, 0xcc, 0x30, 0xc6, 0xf0, 0x31, 0x06, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x34, 0xf0, 0xf3, 0xd7, 0xc0,
+ 0xcf, 0x1f, 0xbf, 0xe7, 0xb9, 0xf1, 0xf0, 0x37, 0x1e, 0xe0, 0x01, 0x86,
+ 0x3f, 0xfc, 0x8d, 0xc7, 0xf0, 0xf7, 0xfc, 0x31, 0xc7, 0xf1, 0x7f, 0x0e,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x08, 0x06,
+ 0x00, 0x20, 0x18, 0x00, 0x00, 0x80, 0x01, 0x08, 0x36, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x82, 0x0d, 0xc0, 0x08, 0x06, 0x00, 0x30, 0x00, 0x08, 0x06,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x18,
+ 0x04, 0x00, 0x60, 0x10, 0x00, 0x00, 0x80, 0x01, 0x18, 0x34, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x06, 0x0d, 0xc0, 0x18, 0x04, 0x00, 0x30, 0x00, 0x18,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x38, 0x02, 0x00, 0xe0, 0x08, 0x00, 0x00, 0xe0, 0x00, 0x38, 0x1e, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x8e, 0x07, 0x70, 0x38, 0x02, 0x00, 0x1c, 0x00,
+ 0x38, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0xf0, 0x01, 0x00, 0xc0, 0x07, 0x00, 0x00, 0x60, 0x00, 0xf0, 0x0d,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x03, 0x30, 0xf0, 0x01, 0x00, 0x0c,
+ 0x00, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xff, 0x7f, 0x00, 0xf0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xfc, 0x55, 0x55, 0x55, 0x55, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xbc, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xea, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x7c, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xb8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x1e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbe, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x70, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0xab, 0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x2a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x60, 0x00, 0x80, 0x7f, 0x44, 0x44, 0x44, 0xc4, 0x7f, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x50, 0x55, 0x7f, 0x77, 0x77, 0x77, 0x77, 0x77, 0xf7,
+ 0x5f, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0xfe, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0xf1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xfa, 0xaf, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x47, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xea, 0xab, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1d, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xdf, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xfd, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x45, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x0f,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xe3, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xfa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x3a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x91, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8e, 0xdc,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0x5d, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x45,
+ 0x46, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
+ 0xa2, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1c, 0x71, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0d, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x8b, 0xd8, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x7d, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xe0, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x38, 0x22, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xae,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x16, 0x91, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x89, 0xe8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xea, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x6a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x44, 0x74, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0xc4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x22, 0x7a, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x57, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x07,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x11, 0x17, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x89, 0x88, 0xab, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xea, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x44, 0xc4, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x22, 0x62, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x57, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x11, 0x19,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0d, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x88,
+ 0xae, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xba,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x44,
+ 0xc4, 0x45, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+ 0x22, 0x62, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0xf7, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0xff, 0x11, 0x11, 0x1d, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x88, 0x88, 0xac, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xea, 0x89, 0x88, 0x88, 0xe8, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xae, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x3a,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x44, 0x44, 0x46, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x7c, 0x44, 0x44, 0x44, 0x44, 0x46, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x23, 0xa2, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0x23, 0x22, 0x22, 0x22, 0x22, 0xae, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xba, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x11, 0xd1, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x79, 0x11, 0x11, 0x11, 0x11, 0x11, 0x19, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x51, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0x88, 0xe8, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0x8f, 0x88, 0x88, 0x88, 0x88, 0x88, 0xd8, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x44, 0x54, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0xe4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x64,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x22, 0xae, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x3a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0xa2, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xae,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x06, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x11, 0x13, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x17, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x91, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x50, 0x0d, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0xc8, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x89, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0x7d, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x75, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x44, 0x74, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x74, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x45, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xa0, 0xea, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x22, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x2a, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11,
+ 0x17, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x17, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x15, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x40, 0x55,
+ 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88,
+ 0x88, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x89, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xdc, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x74, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x4c, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x0c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x80, 0xaa, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x26, 0xa2, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x2e,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x13, 0x19, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x13, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x80, 0xaa, 0xaa, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0x89, 0xac, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xea, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xb8,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x0a, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x44, 0x47, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x64, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x64, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x55, 0x55, 0x75, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0xe2, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x77, 0x3f, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x62, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0xf7, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x35, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x31, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x51, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0xaa, 0xaa, 0xaa, 0x0e, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0xb8, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x8b, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0xc8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xae, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x30,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x46, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x54, 0x55, 0x55,
+ 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x77, 0x77, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0xa2, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x77, 0x57, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0xa8,
+ 0xaa, 0xaa, 0xaa, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xa8, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x8e, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xab, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xea, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x5c,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x46, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x46, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x50, 0x55, 0x55, 0x55, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x76, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x23, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x76, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
+ 0x30, 0x13, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x15,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0xa0, 0xaa, 0xaa, 0xaa, 0xaa, 0x1e, 0x00, 0x00, 0x00,
+ 0x00, 0x98, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xea, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0xa8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
+ 0x00, 0x00, 0x6c, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x74,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x54, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0xa0, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x01,
+ 0x00, 0x00, 0x00, 0xbc, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0x2a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0xa2, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x16, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x17, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x51, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x1d, 0x00, 0x00, 0x80, 0xdf, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0x5d, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x45, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x80, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x01, 0x00, 0xb8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0x6a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xea, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x44, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x31, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x15, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x1d, 0x00, 0xd7, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x9d, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xd8, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x15, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x80, 0x60, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x54, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x6a, 0xe0, 0xba, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x26, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xe2,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x2a, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x11, 0x10,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x13, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x91, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x54, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5f,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x89, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0xdf, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0e, 0x4c, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0xa8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xb1, 0xae, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x6a, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0xb2, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x60, 0x12, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0xd1, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0xa8, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0x3a, 0x80, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0x8a, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xaf, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xba, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x0c, 0x00, 0x47, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x5c, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x50, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x03, 0x80, 0x76, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x77, 0x23, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x62, 0x77, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x80, 0x19, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91, 0x13,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x51, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0xa0, 0xaa, 0xaa, 0xaa, 0xaa, 0x3a, 0x00, 0xc0, 0xa8, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0xbc, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x40, 0xc4, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x4f, 0x44, 0x44,
+ 0x44, 0xc4, 0x47, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x40, 0x55, 0x55, 0x55, 0x55, 0x03, 0x00, 0x20, 0x22, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x62, 0x37, 0x22,
+ 0x22, 0x22, 0x22, 0x7e, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x0d, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x30, 0x11,
+ 0x13, 0x11, 0x11, 0x11, 0x11, 0x11, 0x31, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x19, 0x51,
+ 0x11, 0x11, 0x11, 0x11, 0xd1, 0x17, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x80, 0xaa, 0xaa, 0xaa, 0x6a, 0x00, 0x00, 0x90,
+ 0x88, 0xac, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x9a, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xae,
+ 0xea, 0x88, 0x88, 0x88, 0x88, 0x88, 0xf8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x1a, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00,
+ 0x50, 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44, 0x44, 0x4c, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4,
+ 0x45, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x47, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x05, 0x00,
+ 0x00, 0x28, 0x22, 0x72, 0x77, 0x77, 0x77, 0x77, 0x77, 0x2f, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x62, 0x77, 0xf7, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x7a, 0x77, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x57, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x15, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x18, 0x11, 0x31, 0x11, 0x11, 0x11, 0x11, 0x11, 0x15, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x31, 0x11, 0x91, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91, 0x13,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x80, 0xaa, 0xaa, 0xea,
+ 0x00, 0x00, 0x00, 0x88, 0x88, 0xc8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x8e,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0xb8, 0xaa, 0xaa, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0xac, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0x2a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x46, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x4c, 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x74, 0x44, 0x44, 0x44, 0x44, 0x44, 0x14, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xaa,
+ 0xaa, 0x0a, 0x00, 0x00, 0x00, 0x26, 0x22, 0x22, 0xab, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0xaa, 0xaa, 0xaa, 0x23, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0xa2, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xba, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x6a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x12, 0x11, 0x11, 0x13, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x3d, 0x11, 0x11, 0x11, 0x11, 0x31, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x54, 0xd5, 0x01, 0x00, 0x00, 0x00, 0x8a, 0x88, 0x88, 0xdc, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xdd, 0xdd, 0xdd, 0x89, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0xc8, 0xdd, 0xdd, 0xdd, 0xdd, 0x7d, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x46, 0x44, 0x44, 0x4c,
+ 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x47, 0x44, 0x44, 0x44, 0x24,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0xa8, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x23, 0x22, 0x22,
+ 0xba, 0xaa, 0xaa, 0xaa, 0x6a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xb2, 0xaa, 0xaa, 0xaa, 0x23,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xae, 0xaa, 0xaa, 0xaa,
+ 0xea, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x01, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x51, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x19, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x19, 0x11, 0x11,
+ 0x11, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x00, 0x00, 0x00, 0x80, 0x89,
+ 0x88, 0x88, 0xe8, 0xdd, 0xdd, 0xdd, 0xdd, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xdc, 0xdd, 0xdd,
+ 0xdd, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xe8, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x03,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x64, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x46, 0x44,
+ 0x44, 0x44, 0x45, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4,
+ 0x45, 0x44, 0x44, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x22, 0x22, 0x22, 0xe2, 0xaa, 0xaa, 0xaa, 0x2a, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xab,
+ 0xaa, 0xaa, 0xaa, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0x06, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x11, 0x11, 0x11, 0x91, 0x11, 0x11, 0x11, 0x31, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91,
+ 0x11, 0x11, 0x11, 0x91, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x15, 0x11, 0x11, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xc0, 0x88, 0x88, 0x88, 0x88, 0xdd, 0xdd, 0xdd, 0xbd, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0xc8, 0xdd, 0xdd, 0xdd, 0xdd, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0xd8, 0xdd, 0xdd, 0xdd, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x05, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x44, 0x44, 0x44, 0x44, 0x45, 0x44, 0x44, 0x64,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x64, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x54, 0x44, 0x44, 0x44, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x22, 0x22, 0x22, 0x22, 0xab, 0xaa, 0xaa,
+ 0x3a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0xb2, 0xaa, 0xaa, 0xaa, 0xaa, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0xa2, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x0a, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x11, 0x11, 0x11, 0x11, 0x13, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x19, 0x11, 0x11, 0x11, 0x91, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x31, 0x11, 0x11, 0x11, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x88, 0x88, 0x88, 0x88, 0xaa,
+ 0xaa, 0xaa, 0x9a, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0xa8, 0xaa, 0xaa, 0xaa, 0xaa, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xc8, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x0a, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x22, 0x22, 0x22,
+ 0x22, 0x76, 0x77, 0x77, 0x2f, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x76, 0x77, 0x77, 0x77, 0x77, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xa2, 0x77, 0x77,
+ 0x77, 0x57, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x15, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x11, 0x11,
+ 0x11, 0x11, 0x19, 0x11, 0x11, 0x19, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x51,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91, 0x11,
+ 0x11, 0x11, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0xa8, 0xaa, 0xaa, 0x8a, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xea, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0xab, 0xaa, 0xaa, 0xae, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x2a,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c,
+ 0x44, 0x44, 0x44, 0x44, 0x54, 0x44, 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x45, 0x44, 0x44, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x28, 0x22, 0x22, 0x22, 0x22, 0x72, 0x77, 0x77, 0x2f, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x62, 0x77, 0x77,
+ 0x77, 0x77, 0x77, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x77, 0x77, 0x77, 0x57, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x35, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x14, 0x11, 0x11, 0x11, 0x11, 0x31, 0x11, 0x11, 0x19, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x51, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x8c, 0x88, 0x88, 0x88, 0x88, 0xa8, 0xaa, 0xaa, 0x8a, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xa8,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0x6a, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x44, 0x44, 0x64, 0x44, 0x44, 0x4c,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x64, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x44, 0x44, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x26, 0x22, 0x22, 0x22, 0x22, 0x62, 0x77, 0x77,
+ 0x2f, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x76, 0x77, 0x77, 0x77, 0x77, 0x37, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x77, 0x77, 0x77, 0x5f, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x11, 0x11, 0x11, 0x11, 0x51, 0x11,
+ 0x11, 0x19, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x31, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x13, 0x11, 0x11, 0x11, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x88, 0x88, 0x88, 0x88, 0xc8,
+ 0xaa, 0xaa, 0x8a, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xaa, 0xaa, 0xaa, 0xba,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x44, 0x44, 0x44, 0x44,
+ 0xc4, 0x44, 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x54, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x46, 0x44, 0x44,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0xa2, 0xaa, 0xaa, 0x2a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0xe2, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x3a, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xaa, 0xaa,
+ 0xaa, 0xba, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x01, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x19, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x31, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x13,
+ 0x11, 0x11, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0xdd, 0xdd, 0x8d, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xd8, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0x9d, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0xde, 0xdd, 0xdd, 0x5d, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x46, 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x54, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x46, 0x44, 0x44, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x23, 0x22, 0x22, 0x22, 0x22, 0x22, 0xaa, 0xaa, 0x2a, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xae, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0x3a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x15, 0x11, 0x19, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x13, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x19, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x13, 0x11, 0x11, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, 0xdc, 0xdd, 0x8d, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0x8d, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0xdd, 0xdd, 0xdd, 0x7d, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x05, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x45, 0x44, 0x44, 0x44, 0x44, 0x44, 0x4c, 0x44, 0x4c,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x44, 0x44, 0x24, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x23, 0x22, 0x22, 0x22, 0x22, 0x22, 0xaa, 0xaa,
+ 0x2a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xa2,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x2a, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x06, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x19, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x51, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x19, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x31, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0xd8, 0xdd, 0x8d, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0xe8, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x8d, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xdd, 0xdd, 0xdd, 0x7d,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x0d, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x64, 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x54, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x44, 0x44,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0xa2, 0xaa, 0x3a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x26, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xab, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x0a, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x51, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x15,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0xc8, 0xaa, 0x9a, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0x8e, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0x0a, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x54, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x46, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0xc4, 0x44, 0x44, 0x44, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x10, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x23, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x77, 0x37, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x77, 0x23, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0xe2, 0x77, 0x77, 0x77, 0x77, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x15, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x51, 0x11, 0x11, 0x11, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xaa, 0x9a, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xc8, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0xc8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x1a, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x45, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x54,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x64, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x24, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x23, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x7a,
+ 0x37, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x72, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x77, 0xf7, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x62, 0x77, 0x77, 0x77, 0x77, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x35, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x31, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x31, 0x11, 0x11, 0x11, 0x31, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0xa8, 0xea, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0xa8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xea, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xa8, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x2a, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x64, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x64, 0x44, 0x44, 0x44,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x62, 0x77, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x76, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x37, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x72, 0x77, 0x77,
+ 0x77, 0x77, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x35, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x91, 0x91, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x13, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x31, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xab, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xa8,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0x6a, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc6, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x54, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x4c, 0x44, 0x44, 0x44, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x23, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xae, 0x23, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0xe2, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0x2a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0xae, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0x6a, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x51, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x15, 0x11, 0x11, 0x11, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x8a, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xe8, 0x8b,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xe8, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0x8d, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0xde, 0xdd, 0xdd, 0xdd, 0x5d, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x46, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x46, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x54, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x46, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x46, 0x44, 0x44, 0x44, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0xa2, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0xab, 0xaa, 0xaa, 0xaa, 0xba, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x19, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x8e, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xdc,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x89, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xdd, 0xdd, 0xdd, 0xdd, 0x5d,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x46, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x3a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xe2, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0xd9, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x51, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x51, 0x11, 0x11,
+ 0x11, 0x11, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xd8, 0x8b, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xbd, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xe8, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0x5d, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0xd5, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x54, 0x4c, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x64,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x64,
+ 0x44, 0x44, 0x44, 0x44, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x24, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xa2, 0x3a, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0xa2, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0x3a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0xb2, 0xaa, 0xaa, 0xaa, 0xaa, 0xae, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x18, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x31, 0x51,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x31, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xe8,
+ 0xaa, 0x89, 0x88, 0x88, 0x88, 0x88, 0xb8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0x8a, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0xa8, 0xaa, 0xaa, 0xaa, 0xaa, 0xae, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x48, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x46, 0x44, 0x44, 0x44, 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x30, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0xe2, 0x77, 0x3f, 0x22, 0x22, 0x22, 0x22, 0x76, 0x77, 0x77, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x27, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x76, 0x77, 0x77, 0x77, 0x77, 0x57, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x91, 0x11, 0xf1, 0x11, 0x11, 0x11, 0x11, 0x15, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x13, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x13, 0x11, 0x11, 0x11, 0x11, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0xab, 0xaa, 0x8f, 0x88, 0x88, 0x88, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x8a, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x44, 0x5c, 0x44, 0x44, 0x44, 0x45,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x44, 0x44, 0x44, 0x44,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x77, 0x77, 0x77, 0x22, 0x22, 0xa2,
+ 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x23, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xa2, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x57, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x13, 0x11, 0x91, 0x11, 0x11,
+ 0x91, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x51, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xac, 0xaa, 0xaa, 0x8e,
+ 0x88, 0xc8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xa8, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x4c, 0x44, 0x44,
+ 0x74, 0x44, 0x64, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x54, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x7a, 0x77,
+ 0x77, 0xf7, 0x27, 0x62, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0xf7,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x7a,
+ 0x77, 0x77, 0x77, 0x77, 0x77, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0xd5, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x39, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x51, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x19, 0x11, 0x11, 0x11, 0x11, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0xa8, 0xaa, 0xaa, 0xaa, 0xea, 0xb8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xea, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0xac, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x64, 0x44, 0x44, 0x44, 0x44, 0x4f, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x64, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x46, 0x44, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0xe2, 0xaa, 0xaa, 0xaa, 0xaa, 0xae, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0x2a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xea, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x51, 0x11, 0x11, 0x11, 0x11, 0xf5, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x31, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x91, 0x11, 0x11, 0x11, 0x11, 0x11, 0x51, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0xc8, 0xdd, 0xdd, 0xdd, 0xdd, 0x8d, 0xdf, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x9d, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0xc8, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x7d, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x46, 0x7c,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x54, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x74, 0x44, 0x44, 0x44, 0x44, 0x44, 0x24,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x27, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xa2, 0xab, 0xaa, 0xaa, 0xaa, 0x23,
+ 0xa2, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x3a, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xba, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x6a, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x14, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91,
+ 0x11, 0x11, 0x3d, 0x11, 0x11, 0x11, 0x11, 0x11, 0x19, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x15, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x8d,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xdf, 0xdd, 0xdd,
+ 0xdd, 0x88, 0x88, 0xc8, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x8d, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xdf, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0x5d, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18,
+ 0x54, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x46, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x5e, 0x44, 0x44, 0x44, 0x44, 0x4c, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xa8, 0x3a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xae,
+ 0xaa, 0xaa, 0x6a, 0x22, 0x22, 0x22, 0xe2, 0xaa, 0xaa, 0xaa, 0xaa, 0x2a,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xe2, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xae, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0x2a, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x06, 0x38, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x19, 0x11, 0x11, 0x51, 0x11, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11,
+ 0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x19, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x57, 0xbd, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0xd8, 0xdd, 0xdd, 0xbd, 0x88, 0x88, 0x88, 0x88, 0xf8, 0xdd, 0xdd,
+ 0xdd, 0x8d, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xdc,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x57, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x35, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xc0, 0x00, 0x70, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x54, 0x44, 0x44, 0x64, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x47,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x47, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xe0, 0xaa, 0x6a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0xa2, 0xaa, 0xaa, 0x3a, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0xfa, 0xaa, 0xaa, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0xe2, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xab, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x2a, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x60, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x31, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x1f, 0x11, 0x13, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x31, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xaa, 0xea, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0xe8, 0xaa, 0xaa, 0x8a, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0xe8, 0xab, 0x8a, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0xae, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x1a, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x4c, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x7e, 0x46, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0xc4, 0x45, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x55, 0xd5, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xe2, 0x77, 0x77, 0x27, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xe2, 0x3f, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x7a, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x15, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91, 0x11, 0x11, 0x15,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0xf3, 0x1f, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0xd1, 0x17, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0xaa, 0xaa,
+ 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xab, 0xaa,
+ 0x8e, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xaa, 0xfa, 0x8f,
+ 0x88, 0x88, 0x88, 0x88, 0xbe, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0x1a, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x46, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45,
+ 0x44, 0x46, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x46, 0x44,
+ 0xfc, 0xff, 0x7f, 0xe4, 0xff, 0x45, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x08, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50,
+ 0x55, 0xd5, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x77, 0x77, 0x23, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x76,
+ 0x77, 0x77, 0x77, 0xf7, 0x7f, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x77, 0x77, 0x57, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x0d, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x13, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x13, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x08, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xb0, 0xaa, 0xaa, 0x8d, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0xac, 0xaa, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xae, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x06, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x00, 0x00, 0x49, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x4c, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x45, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x50, 0x55, 0x55, 0x31, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x7a, 0xf7, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x57, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x05, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x12, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x03, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0xaa, 0xaa, 0xa2, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xa8, 0xea, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xab, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x02, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x42, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x64, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0xaa, 0xaa, 0x42, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xe2, 0x6a, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xab, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x01, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x84,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x51, 0x31, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x51,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x55, 0x55,
+ 0x85, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xc8, 0xbd,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0x7d, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x08, 0x46, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4,
+ 0x64, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0,
+ 0xaa, 0xaa, 0x0a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0xa2, 0x3a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xab,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xba, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x08, 0x14, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x50, 0x55, 0x55, 0x0d, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x8d, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x50, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x4e, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xb0, 0xaa, 0xaa, 0x0a, 0x20, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x26, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0xa2, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x2a, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x20, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x15, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x91, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x55, 0x55, 0x15, 0xc0, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x8c, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x15, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x80, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x4c, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xaa, 0xaa, 0x1a, 0x00,
+ 0x23, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x2a, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xa2, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x0a, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10,
+ 0x00, 0x12, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x13, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xaa, 0xaa,
+ 0x1a, 0x00, 0x8c, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x9a,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x06,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x20, 0x00, 0x58, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x65, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+ 0x55, 0x55, 0x35, 0x00, 0x20, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x37, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xa2, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x03, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x20, 0x00, 0x40, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x31, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xa0, 0xaa, 0xaa, 0x2a, 0x00, 0x80, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0xea, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xea, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x45, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x60, 0x55, 0x55, 0x35, 0x00, 0x00, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x62, 0xf7, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0xa2, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x57, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0xd5, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x14, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x51, 0x91, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x91, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xaa, 0xaa, 0x2a, 0x00, 0x00, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xa8, 0xaa, 0x89, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xae, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x2a, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x50, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x64, 0x44, 0x45, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x55, 0x55, 0x55, 0x00,
+ 0x00, 0x20, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x72, 0x77, 0x23, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xa2, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x15, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0xc0, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x13,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xaa, 0xaa,
+ 0x6a, 0x00, 0x00, 0x80, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, 0xb8, 0xaa,
+ 0x8a, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xea, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x0a, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x46, 0x44, 0x44, 0x44, 0x44, 0x44, 0x4c,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x14, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0,
+ 0xaa, 0xaa, 0x6a, 0x00, 0x00, 0x00, 0x2c, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0xaa, 0xaa, 0x26, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xa2, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x15, 0x11, 0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x55, 0x55, 0x55, 0x00, 0x00, 0x00, 0xa0, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0xdc, 0xdd, 0x8d, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x46, 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xc0, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x80, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0xaa, 0xaa, 0x2a, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0xa2, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xea, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x13, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x19, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x91, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x55, 0x55, 0xd5, 0x00, 0x00, 0x00,
+ 0x00, 0x8c, 0x88, 0x88, 0x88, 0x88, 0xdd, 0xdd, 0x8d, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0x5d, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x35, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x4c, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x4c, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xaa, 0xaa, 0xaa, 0x00,
+ 0x00, 0x00, 0x00, 0x30, 0x22, 0x22, 0x22, 0xe2, 0xaa, 0xaa, 0x2a, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xa2, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x1a, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x60, 0x11, 0x11, 0x11, 0x31, 0x11, 0x11, 0x19,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x55, 0x55,
+ 0x55, 0x01, 0x00, 0x00, 0x00, 0x80, 0x89, 0x88, 0x88, 0xf8, 0xdd, 0xdd,
+ 0x8d, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0x5d, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x05, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x46, 0x44, 0x44, 0x54, 0x44,
+ 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xc4, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x34, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0xaa, 0xaa, 0xaa, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x22, 0x22, 0xaa,
+ 0xaa, 0xaa, 0x2a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xa2, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xae, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x11,
+ 0x19, 0x11, 0x11, 0x19, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x91,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0xaa, 0xaa, 0xaa, 0x01, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x88,
+ 0x88, 0xac, 0xaa, 0xaa, 0x8a, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xab,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x45, 0x44, 0x46, 0x44, 0x44, 0x4c, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x64,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x03, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x26, 0x22, 0x77, 0x77, 0x77, 0x2f, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x5f, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x18, 0x91, 0x11, 0x11, 0x11, 0x19, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0xaa, 0xaa, 0x06, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xe0, 0xc8, 0xaa, 0xaa, 0xaa, 0x8e, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x75, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x05,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x77, 0x77, 0x77, 0x27, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x77, 0x77, 0x5f, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x11, 0x11, 0x11, 0x15,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa,
+ 0xaa, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0xaa, 0xaa, 0xaa,
+ 0x8e, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xab, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xea, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x47, 0x44,
+ 0x44, 0x46, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x56, 0x55, 0x55, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x55, 0x7d,
+ 0x77, 0x77, 0x23, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x77,
+ 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x5f, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x35, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x30, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x13, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x03, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0,
+ 0xaa, 0xea, 0xab, 0xaa, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xea, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x0a,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x4e, 0xc4, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x46, 0x44, 0x44, 0x44, 0x44, 0x44, 0x1c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0x6a, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xac, 0xaa, 0xaa, 0xfa, 0x6a, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xab, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x03, 0x00, 0x00, 0x80, 0x13, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x13, 0x11, 0x11, 0x11, 0x11, 0x71, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x55, 0x55, 0x55, 0x01, 0x00,
+ 0x00, 0x00, 0x80, 0x55, 0x55, 0x55, 0x55, 0xbd, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0xde, 0xdd, 0xdd, 0xdd, 0xdd, 0x5f, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0xc2, 0x45, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xe4, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xac, 0xaa, 0xaa, 0xaa,
+ 0x06, 0x00, 0x00, 0x00, 0xb8, 0xaa, 0xaa, 0xaa, 0xaa, 0x01, 0x3e, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xae, 0xaa, 0xaa, 0xaa, 0xbe,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00,
+ 0x00, 0x18, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0xe0,
+ 0x13, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x15, 0x11, 0x11, 0xf1,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x55,
+ 0x55, 0x55, 0x35, 0x00, 0x00, 0x80, 0x55, 0x55, 0x55, 0x55, 0x1f, 0x00,
+ 0x00, 0xfc, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xdc, 0xdd, 0xdd,
+ 0x5f, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x80, 0x01,
+ 0x00, 0x00, 0x80, 0x4f, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x4c, 0x44,
+ 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xa8, 0xaa, 0xaa, 0xaa, 0xaa, 0x3e, 0xc0, 0xab, 0xaa, 0xaa, 0xaa, 0x6a,
+ 0x00, 0x00, 0x00, 0x00, 0xf0, 0x23, 0x22, 0x22, 0x22, 0x22, 0x22, 0xaa,
+ 0xea, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x3f, 0x00, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x99, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x58, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x88, 0x88, 0x88,
+ 0x88, 0x7f, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff,
+ 0xff, 0x7f, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xb0, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xb0, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x2a,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x3a, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x05, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x57, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xab, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x0a,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xac, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xac, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x06, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xb0, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x58, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x70, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0xaa, 0xaa, 0xaa, 0xaa, 0x0a, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xa0, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x6a, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x55, 0x55, 0x55, 0x55,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x0d, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xaa, 0xaa,
+ 0xaa, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0,
+ 0xaa, 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x56, 0x55, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x57, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x18, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xac, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x75, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x06, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xa0, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x6a,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xae, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x58, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x1d, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xb0, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xea, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x07, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0xc0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x55, 0x55, 0x55, 0x3d,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0xaa, 0xaa,
+ 0xea, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00,
+ 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x7f, 0xf5, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xc0, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20};
diff --git a/vcl/qa/cppunit/graphicfilter/data/xpm/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/xpm/fail/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/xpm/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/xpm/fail/gentoo22729-1.xpm b/vcl/qa/cppunit/graphicfilter/data/xpm/fail/gentoo22729-1.xpm
new file mode 100644
index 0000000000..ca2d777a16
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/xpm/fail/gentoo22729-1.xpm
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/xpm/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/xpm/indeterminate/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/xpm/indeterminate/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/xpm/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/xpm/pass/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/xpm/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/xpm/pass/tdf111925-1.xpm b/vcl/qa/cppunit/graphicfilter/data/xpm/pass/tdf111925-1.xpm
new file mode 100644
index 0000000000..b5eab3e33b
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/xpm/pass/tdf111925-1.xpm
@@ -0,0 +1,306 @@
+/* XPM */
+static char * example_xpm[] = {
+"150 100 203 2",
+" c #E6DECA",
+". c #E5DDC9",
+"+ c #E4DCC8",
+"@ c #E3DBC7",
+"# c #E3DAC5",
+"$ c #E2D9C4",
+"% c #E1D8C3",
+"& c #E1D7C1",
+"* c #DFD6C1",
+"= c #DFD5BF",
+"- c #DED4BE",
+"; c #DDD3BD",
+"> c #DDD2BB",
+", c #DCD1BA",
+"' c #DBCFB9",
+") c #DACFB8",
+"! c #D9CDB7",
+"~ c #D8CDB6",
+"{ c #D6CAB2",
+"] c #D7CBB3",
+"^ c #D7CCB5",
+"/ c #D5C9B1",
+"( c #D4C8B0",
+"_ c #D3C7AF",
+": c #D3C6AD",
+"< c #D2C5AC",
+"[ c #D1C3AB",
+"} c #D0C2AA",
+"| c #CFC1A9",
+"1 c #CEC0A8",
+"2 c #CDBFA7",
+"3 c #CBBDA5",
+"4 c #CCBEA6",
+"5 c #CABBA2",
+"6 c #C9BAA1",
+"7 c #CBBCA3",
+"8 c #C8B9A0",
+"9 c #C7B89F",
+"0 c #C6B79E",
+"a c #C5B69D",
+"b c #C5B59B",
+"c c #C4B49A",
+"d c #C3B399",
+"e c #C2B298",
+"f c #C1B197",
+"g c #C0B096",
+"h c #BFAF95",
+"i c #BEAE94",
+"j c #BDAD93",
+"k c #BDAC91",
+"l c #BCAB90",
+"m c #BBAA8F",
+"n c #BAA98E",
+"o c #B9A88D",
+"p c #B2A186",
+"q c #B3A287",
+"r c #B8A78C",
+"s c #B6A58A",
+"t c #B2A084",
+"u c #B09E82",
+"v c #AF9D81",
+"w c #B5A489",
+"x c #7F6B4B",
+"y c #5F4E2D",
+"z c #5E4D2C",
+"A c #614F2D",
+"B c #A39175",
+"C c #9D8B6F",
+"D c #978265",
+"E c #927E5E",
+"F c #8C7858",
+"G c #857151",
+"H c #7A6746",
+"I c #776443",
+"J c #887454",
+"K c #E7DFCB",
+"L c #1E1800",
+"M c #161100",
+"N c #221B00",
+"O c #AB997D",
+"P c #9A876A",
+"Q c #948060",
+"R c #8A7656",
+"S c #816D4D",
+"T c #796645",
+"U c #715E3D",
+"V c #685534",
+"W c #584826",
+"X c #725F3E",
+"Y c #B19F83",
+"Z c #E8E0CC",
+"` c #B7A68B",
+" . c #120F00",
+".. c #000000",
+"+. c #AC9A7E",
+"@. c #937F5F",
+"#. c #826E4E",
+"$. c #6F5D3D",
+"%. c #E9E1CD",
+"&. c #AD9B7F",
+"*. c #9B896D",
+"=. c #847050",
+"-. c #7A6848",
+";. c #6A5838",
+">. c #61502F",
+",. c #5B4A29",
+"'. c #746140",
+"). c #AE9C80",
+"!. c #968164",
+"~. c #8E7A5A",
+"{. c #897555",
+"]. c #8B7757",
+"^. c #836F4F",
+"/. c #7D6949",
+"(. c #9F8D71",
+"_. c #907C5C",
+":. c #A9977B",
+"<. c #A08E72",
+"[. c #9A8568",
+"}. c #988366",
+"|. c #A29074",
+"1. c #9B886B",
+"2. c #958161",
+"3. c #695635",
+"4. c #665534",
+"5. c #756241",
+"6. c #9C8A6E",
+"7. c #625130",
+"8. c #766342",
+"9. c #6B5939",
+"0. c #7E6A4A",
+"a. c #6A5736",
+"b. c #786544",
+"c. c #A79579",
+"d. c #998467",
+"e. c #B4A388",
+"f. c #A59377",
+"g. c #493A13",
+"h. c #3A2D00",
+"i. c #413509",
+"j. c #362B00",
+"k. c #6D5B3B",
+"l. c #52431E",
+"m. c #382D00",
+"n. c #282000",
+"o. c #403306",
+"p. c #5D4C2B",
+"q. c #73603F",
+"r. c #4A3B14",
+"s. c #1A1500",
+"t. c #544422",
+"u. c #8F7B5B",
+"v. c #7C6948",
+"w. c #51421D",
+"x. c #3C3000",
+"y. c #534321",
+"z. c #241C00",
+"A. c #3E3202",
+"B. c #655433",
+"C. c #574725",
+"D. c #50411C",
+"E. c #53441F",
+"F. c #6E5C3C",
+"G. c #554523",
+"H. c #4D3F19",
+"I. c #4F3F19",
+"J. c #A49276",
+"K. c #A8967A",
+"L. c #302600",
+"M. c #473911",
+"N. c #0E0B00",
+"O. c #3F3203",
+"P. c #080600",
+"Q. c #42340A",
+"R. c #4B3C15",
+"S. c #43350B",
+"T. c #877353",
+"U. c #5C4B2A",
+"V. c #46370E",
+"W. c #4C3E18",
+"X. c #322700",
+"Y. c #50401A",
+"Z. c #645332",
+"`. c #2A2100",
+" + c #2E2500",
+".+ c #645230",
+"++ c #A69478",
+"@+ c #867252",
+"#+ c #5A4826",
+"$+ c #463810",
+"%+ c #A18F73",
+"&+ c #806C4C",
+"*+ c #403204",
+"=+ c #413407",
+"-+ c #9E8C70",
+";+ c #958263",
+">+ c #AA987C",
+",+ c #5A4928",
+"'+ c #564624",
+")+ c #4C3D16",
+"!+ c #483912",
+"~+ c #6C5A3A",
+"{+ c #8D7959",
+" ",
+" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
+" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
+" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + + + + + . . . . . . . . . . . . . . . . . . + + + + + + + . . . . . . . . . . . . . . . . . . + + + + + + + + + + + . . . . + . . . . . ",
+" . . . . . . + . . . . . . . . . . . . . . . . . . . . . . . . + + + + + + + + . . . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @ @ @ @ @ @ @ @ + + + + + + + . . . . . . . . ",
+" . . . . . + . + + + @ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @ @ @ @ @ @ # # # # # # # # # # @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ # # # # # # # # # # @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ # # # # # # # # # # # # # # @ @ @ @ + @ + + . + . . . . ",
+" . . . . + . + + + + @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ # # # # # # # # # # # # # # # # $ $ $ $ $ % % % % $ $ $ $ $ # # # # # # # # # # # $ $ $ $ $ $ % % % $ $ $ $ $ # # # # # # # # # # # $ $ $ $ % % % % % % % % % $ $ % $ # # # @ @ @ + + + . . . . . . ",
+" . . . . . + . + @ + @ @ # # # # $ $ $ $ $ $ $ # # # # # # # # # # # $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ % % % & & & & * * * * * * & & & & % % % $ $ $ $ $ % % % % & & & * * * * * * & & & & % % % $ $ $ $ $ % % % & & & * * * * * = = * * * * & & & % % $ $ # # @ @ + + . + . . . . ",
+" . . . . + + + + @ @ # # $ $ % % % % % % % % % % % % % $ $ $ % % % % % % & & & & & & & & & & & & & & * * * = * = = = = = = = = = * * = * * * & & & & & * * * = * * = = = = = = = = * * = * * * & & & & & * * * = * = = = - - - - - - - - = = * = * * & % % $ # # # @ @ + + . . . . ",
+" . . . . + + @ @ @ # # % % % & & & * * * * * * * * * & & & & & & * * * * = * * * * * * * * * * * * = * = - - ; ; > ; > > , > > > > ; ; - - = = * * * * * = = - - ; ; > ; > > > > ; > ; ; - - = = * * * * * = = - - - ; ; > , ' ' ' ' ' , , > > - - - = * * & & % $ # # @ + + + . . . . ",
+" . . . . + + @ @ # # $ % & & = = * = = = - - - - = = = = = = = = = = = - - - - ; ; ; ; ; ; ; ; ; ; ; ; ; > , ' ' ) ) ) ! ! ! ! ! ! ! ) ' ' , > > ; - - - ; > > , ' ) ) ! ! ! ! ! ! ! ! ) ' ' , > > ; - - ; ; > > , ) ) ) ! ! ! ! ~ ~ ~ ! ! ! ! ) ) ' > ; - = * * & % $ # # @ + + + . . . . ",
+" . . . . + + + # # $ % % & * * = - ; ; ; > > , , > > > ; > ; ; ; ; > ; > , ' ' ) ) ) ) ) ) ) ) ) ) ) ) ) ! ! ! ! ~ ~ { { { { { { { { ] ~ ~ ! ! ) ) ) ) ) ) ' ) ! ! ~ ~ ^ ] { { { { / { { ~ ~ ! ! ) ) ) ) ) ) ) ! ! ) ~ ^ ^ { { / { { / { { / { ] ^ ~ ! ! ) ' ; ; = * * & % $ # # + + + . . . ",
+" . . . + + @ @ # $ % & = * = ; > , ) ) ) ! ! ! ! ! ! ! ! ) ) ) ) ) ! ! ! ) ! ! ~ ~ ^ ^ ^ ^ ^ ^ ~ ~ ~ ^ ^ ] { / { / ( ( _ _ _ ( _ _ : ( ( / { { ] ^ ^ ~ ~ ~ ~ ^ { { { / ( ( _ _ _ _ _ _ ( / / { { ] ^ ^ ~ ~ ~ ~ ^ { { { / ( _ _ _ : _ _ : _ _ _ ( / / { { ~ ! ) ' > - = * & % $ # @ @ + + . . . ",
+" . . . . . @ @ # $ % * * = - ; , ) ! ! ~ ^ ^ { { ] ] { ^ ^ ~ ~ ~ ~ ^ ^ ] { / { { / / ( _ ( _ ( ( ( ( ( ( _ _ _ _ _ < [ [ [ } | | | } [ < < : _ _ ( / / / { / ( ( _ _ _ < [ [ [ } } } } } [ [ : _ _ ( / / / { / ( ( _ _ _ < [ } } 1 | | | | 1 1 } [ < : _ / { ] ~ ! ) > ; * * & % # # @ + + . . ",
+" . . . + + @ @ # % & * * - > ' ) ! ~ { / / / / ( ( ( ( ( / ( ( ( / ( ( _ _ ( _ : [ [ [ [ < < [ [ [ [ [ [ < [ } 1 1 1 2 2 2 3 3 3 3 3 2 2 1 | | [ [ < : _ _ _ _ < < [ 1 | 2 2 2 2 3 3 2 1 2 1 | | [ [ < : _ _ _ _ < < } | 1 1 2 3 3 4 3 3 4 3 3 2 2 | | < < _ ( { ^ ! ) , - * * & $ # # + + + . . . ",
+" . . . + @ # $ $ & * = ; > ' ) ~ { { ( _ _ : < [ [ [ [ [ [ < < < [ [ [ [ [ | | | 1 2 2 2 1 2 2 2 2 1 1 2 2 2 2 3 4 5 5 6 5 6 6 6 6 6 6 5 7 4 2 1 1 | | | | | | 1 2 1 3 3 5 5 5 5 5 6 5 5 7 5 4 2 1 1 | | | | | | 1 2 2 4 5 7 5 5 8 8 6 8 8 6 5 5 5 4 2 2 | [ : ( { ] ! ) , - = * % $ # @ @ . . . ",
+" . . . + + @ # $ % * * - ; ) ! ] { / ( < [ [ | | | 1 1 1 1 1 1 1 1 1 2 2 1 2 3 3 5 5 5 5 5 6 6 5 5 7 5 5 5 5 5 5 8 6 9 0 0 a a 0 0 a 0 0 9 9 8 5 5 7 3 3 4 4 3 4 3 7 5 5 8 9 0 0 0 0 0 0 0 9 6 5 5 5 7 3 3 4 4 3 4 3 7 5 5 6 8 0 0 0 a b a a a 0 9 9 6 7 3 2 1 [ < ( { ] ) ' > = * & % # @ + + + . . . ",
+" . . . @ # $ $ % * = ; ' ! ] { ( _ < | 1 2 2 3 3 3 3 7 7 7 7 3 5 7 5 5 6 5 8 8 9 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 b a c c d c c e c c d c b a 0 9 8 6 8 5 5 5 5 8 6 8 9 0 a b c c d d d c c c a 0 9 8 6 8 5 5 5 5 8 6 8 9 0 a b c d d e e e c c c c 0 9 9 5 7 4 1 } < ( { ~ ) ' ; = * % $ # @ + + . . . ",
+" . . + + + @ $ % & * - , ) ~ { ( : < | 1 3 5 5 5 5 6 8 8 8 8 8 8 9 9 0 0 0 a b a b c c d c c c c d d c c d c c d d f f f g g g g g g g f e d c c c b 0 0 0 0 0 0 a b c c d e e g f g f g e e d c c a b 0 0 0 0 0 0 a b c c d f g g g g g g g g f d c b 0 9 6 5 4 1 | < ( ] ~ ) , = * & $ # @ + + + . . ",
+" . . . + + @ # $ % * = ; ) ~ ] ( : } 1 4 7 5 8 0 9 0 0 a a a b b a b c c c d e d f f f g g g g g g g g g g g g g h h i i i j j j j j j i h h g g e c d c c c b c c d d e g g h h i h h i h g g f f e d c c b c c c c d d e f g h h i i j j i i i g g g c c 0 9 8 5 4 1 < _ / ^ ) , - = * % $ # @ + + . . ",
+" . . . . @ # # % * * ; , ) ^ / _ [ 1 4 7 5 8 0 0 a c c d d d e d f e f f f g g i i i i j j j j j j j j j j j j k j j k l k l l l l l k k j j i i g g f f g e e f g g g h h i j j j k k j j j j i i h g f f g e e f g g g h h j j j j l k l j j j j h h f e b a 9 5 7 2 } : ( ] ! ) ; = * % $ @ @ + . . . ",
+" . . + + @ $ % * * ; ' ! ] ( : } 4 3 5 9 0 b d d e f g g g g g h i i j j j j j j k l m l l l l l m l l l l l m m m m n m m m m m m m m l m k j j i h i h h h i h h i j j k k l l m m l l l k k j j i h i h h h i h h j j k j k l m m m m l l k k j i g f d c a 8 5 3 1 [ ( { ^ ' , - & & % # @ + . . . ",
+" . . + + @ # $ % * = , ' ^ { ( [ | 3 5 8 0 b c e g g i i j j j j k k k l k m m m m m n m n m n m n m m n n m m m m m n n n n n n n n m m m m l k l j j j j j j j j j j k k m l m m m m m m m m l l k j j j j j j j j j j k k l m m m m m m m m m k j i h g e c b 0 5 3 1 } _ ( ] ! , ; = & % $ @ + + . . . ",
+" . . + + @ # $ & & = , ) ^ ( : [ 1 7 5 0 b d e g h i j l k m l l m m m n m m m n n m m n n n o o o n n o o o n o n n n n n o o n n m m n m m m m m m l k k k k k l k l l m m m m m m m m m m m m m l l l l k l k k l k l l m m m m m m m m m m m l k j i h f c c a 9 7 4 | : ( ] ! ' ; = & % # @ + + + . . ",
+" . . + + @ # $ & * - , ) ] ( [ | 2 5 8 0 c e g h j j k m m m n n m n n n n n n n n n n n n n n m n n n m n n n n n m m n m m m m m m m m m m m m m m m m m m l m m l m m m m m m m m m m m m m m m m m m m m m l m m l m m m m m m m m m m m m m l k j j h g d c a 8 5 3 1 [ _ ] ~ ' ; = * % $ # @ + + . . ",
+". . . . @ @ # % * = - ' ~ / ( < 1 4 6 0 b e f g j j l l m m m n n n o n n n m m n m m m m m m m l m m m m m m m m m m l m m m l l m m m m m m m m m m m m m m m m m m m m l m m m m m m m m m m m m m m m m m m m m m m m m l m m l l l l m m l l l k j j h g f d b 0 6 7 1 [ _ / ^ ' , = & & $ @ @ + . . ",
+". . . . + @ $ % * = > ' ~ / ( } 1 7 6 0 c e g h j k l m m n n n n n n m m m m m l l k k k j j j j j j j k k k k k j j j j j j j j j k k k l m m m m m m m m m m m m l m l l l k k k k l l l l m m m m m m m m m m m m l m l k l k k k k k l l k k k j j j i g f d c 0 5 3 1 } : / ^ ) , - * % $ # @ + . . ",
+". . . . + @ # % * = , ' ~ / : } 1 7 8 0 c e g i j k l m m n n m m m m m l k k j j i i h g g g g g g g g g g h h g g g g g g g g h i i j j j k k l m m m m m m m m l k k k j j j j j j j j j k k l l m m m m m m m m l k k k j j j j j j j j j j j j j j i h g g d c 0 9 5 4 | < ( ] ! ' - * & % # @ + . . ",
+". . + + # $ % & = , ' ^ / < | 1 5 9 b c f g i j k l m m m m m l l k j j i h g g g e d d d d c c c c c d d d d d d d c c d d d e e g g g h j j j k l k l l k k k k j j j h h g g g h g h i i j j k k k l l l k k k k j i i h h h g g g h g h h i i j i i h g f d c a 9 6 3 | < _ { ~ ' ; * * % # # + + + . ",
+". . + + # $ & & = , ' ^ ( : | 4 5 9 a c e g i j j k k l l k k j i h h g f e d c c a 0 0 0 0 9 9 9 9 0 0 0 0 0 0 0 0 0 0 0 a a b c c d e g g h i i j j j j j j j i h h g g f e e e e f f g g h i i j j j j j j j j i h g g f f e f e e f f g g g h g h h g g f e c 0 9 5 4 1 [ _ / ^ ) , = & & $ @ @ + . . ",
+". . + @ @ $ % & - , ' ^ ( : 1 2 5 9 a c e g h i j j j j j i i g g f e c b a 0 9 8 5 5 5 5 7 7 7 7 7 7 7 7 5 5 5 5 5 5 5 5 6 8 9 9 0 a c c d e g g g i h h h h g g f e d c c b c b c c c d d e g g g h h h h h h g g f e d c c c b c c c c d e f g g g g g g f d c b 9 8 7 2 | < / ^ ) , - * & $ # @ + + . ",
+". . + @ @ # % & = , ! ] ( < | 4 5 9 a c e f g h i i h h h g f e d b a 0 9 6 5 7 4 2 2 1 1 | | | | | | | | 1 1 1 1 1 1 2 2 4 3 7 5 6 8 0 0 b c c e e f f f f e e d c b a 0 0 0 9 9 0 0 0 a b c c d e f g f f f e e d c c a a 0 0 0 0 0 0 a b c d d e f g g f f d d b 0 6 5 4 | < ( ] ~ ' ; * * % $ # @ + + ",
+". . + + @ @ $ & * - , ! ] ( < | 2 5 8 0 c d e g g g g g f e e c b 0 9 8 5 3 4 1 1 | [ < : : _ _ ( ( ( ( _ _ _ _ : : < < < } } | 1 2 4 7 5 6 9 0 a b c c c c c b b a 0 9 8 6 5 5 5 5 5 5 8 8 9 0 0 a c c c c c c b b 0 0 9 8 8 6 5 5 5 6 8 9 9 a b c d d f f f e d c b 0 8 5 3 1 [ ( / ^ ' , - * & $ # @ + + ",
+". . + + @ @ $ & * - , ! { ( : 1 2 5 8 0 b c d e e f e e d c a 0 8 6 5 3 1 1 } < _ ( ( / { ] ^ ] ] ] ] ] ] ] { { ] { { / ( ( _ : < } | 1 4 3 5 5 8 9 9 0 0 0 0 0 8 8 5 5 7 3 4 2 2 2 2 4 3 7 5 6 6 8 0 0 0 0 0 0 9 8 8 5 5 7 3 3 4 4 3 3 7 5 5 9 0 a b c d d e e d c c a 9 5 7 2 | < / ] ! ' - = * % $ # @ + ",
+". . + + @ # $ & & - , ! ] / : 1 4 5 6 0 a c c d d c c c b 0 9 6 5 3 1 | [ : ( ( ] ^ ~ ! ) ) ' ) ' ' ' ' ) ) ) ' ) ) ! ~ ^ ^ ] { ( ( : [ } 1 2 4 7 7 5 5 5 5 5 5 3 3 2 2 1 1 } | } } | | 1 1 2 4 3 7 5 5 5 5 5 5 5 3 3 2 2 1 | | | | | 1 1 4 7 5 6 0 0 b c d d e d c c a 0 8 5 4 | [ _ / ^ ) , - * & $ $ @ + ",
+". . + + @ # $ & & = , ! ] ( : | 2 7 8 9 a b c c c c a 0 0 8 5 3 2 1 [ : ( / ] ~ ! ' , , > ; ; - - - - - - - ; ; ; > > , , ' ! ! ^ { / ( _ < } } 1 1 1 2 2 2 1 1 1 | } [ < : _ _ ( _ _ : < < } | | 1 2 2 2 2 2 1 1 1 | | [ < : : : : < < } 1 1 3 5 6 9 0 b c c d d d c b 0 8 6 3 2 | < ( ] ! ) ; = * % $ $ @ ",
+". . + + @ # $ % & = , ! { / : | 2 7 5 9 0 a b b a a 0 9 6 5 4 1 } : ( / ] ~ ) ' > - - = * & & & % % % % & & & & * * = = - ; > , ) ! ~ ] { ( _ _ < [ } } } } [ [ < _ _ ( ( / / { { { / / / ( _ : : < [ } } } } } [ < : _ ( ( ( ( ( ( ( ( _ < } 1 4 5 5 9 0 a c c c c c b a 0 6 7 4 | } ( ( ] ) ' - * & % $ # ",
+". . + + @ # $ % & = , ! ] / : | 1 3 5 9 0 0 0 a 0 9 8 5 7 4 1 } : ( { ^ ) ' > - = * & % $ # # # # @ @ # # # # # $ $ % % & * = - ; > ' ! ~ ^ ] / ( ( ( ( ( ( ( ( ( / { ] ^ ^ ~ ~ ~ ~ ~ ^ ] ^ { / ( ( ( ( ( ( ( ( ( ( / { { ] ( ( ( ( ( / ( _ : [ 1 4 5 6 0 0 b c c c c c b 0 9 5 3 1 } < ( ] ^ ) ; = * & $ # ",
+". . + + @ # $ & & = , ! ] / : | 2 5 6 9 0 a a a a 0 9 5 3 1 [ : ( ] ~ ' , - = & % $ # @ @ + + + . . . . + + + + + @ @ # $ $ % & = - ; > , ) ! ~ ^ ] ] ] { ] ] ^ ^ ~ ! ) ' , , ' , ' , , ' ) ! ~ ^ ^ ] ] { { ] ] ^ ^ ~ ! ! | m p q q m 1 { / ( : [ 1 3 5 8 0 0 b c c c c b 0 0 8 5 3 1 } _ ( ^ ) ' ; = * % $ ",
+". . + + @ @ $ & & = , ! ] / : 2 a i m r s q t u v v w b 1 } : ( ] ~ ' > - = % % # @ + . . . . . + + @ # $ % & * = ; > , , ' ' ) ) ) ) ' ' , > , > ; ; - - - - - ; ; > , ' , ' ' ) ) ) ) ' ' , , > ' j x y z A x l ] ^ / ( < | 1 3 5 9 0 a b c c c c b a 8 6 3 2 } [ ( ] ^ ) ; = * % $ ",
+". . + + @ @ $ & * - , ) ] / : 6 t B C D E F G x H I J s 1 < ( / ~ ) , - * % # # + . . K K K K K K K K K K K K K . . + @ # $ % & & * = - ; ; ; > > > ; ; - - = = * * & & & & & * * = = - - ; ; > > > > ; ; - - = , s y L M N A w ^ ) ^ / _ < 1 4 7 6 0 a b c c c c b 0 9 8 7 4 1 [ _ / ] ) , - * & $ ",
+". . + @ @ # % & = , ! ^ / : 0 O P Q R S T U V y W X Y 1 _ ( ] ) , ; * % $ @ + . K K Z Z Z Z Z Z Z Z Z Z Z Z Z Z K K K . . + @ @ # $ % % & & * * * * * * * & & % % $ $ $ $ # $ $ $ $ % % & & * * * * * * * * & & % - ` z ...M y s ) ' ! ] ( : } 1 3 6 9 0 b c c c c c a 0 9 5 3 1 } < ( ] ! ' ; = * & ",
+". . + @ # $ % & = , ) ~ / : 9 +.P @.R #.T $.V z W X Y 1 ( / ~ ' > - & $ # + . K K Z Z %.%.%.%.%.%.%.%.%.%.%.Z Z Z Z K K K . + + @ @ # # $ $ $ $ $ $ $ $ # # # @ @ @ @ + + @ @ @ @ # # # $ $ $ $ $ $ $ $ $ $ # # * r z ...M z s ' > ' ~ { ( [ | 4 5 8 0 a c c c c c c 0 0 6 7 4 1 [ _ ] ^ ' , = * % ",
+". . + + # $ % & = > ' ~ / _ 9 &.*.Q F =.-.X ;.>.,.'.t | ( ] ! , - * % # @ . . K Z Z %.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.Z Z Z K K . . + + + @ @ @ @ @ @ + + + + . . . . . . . . . . . + + + @ @ @ @ @ @ @ @ + + + % o y ...M y ` > - , ! ] ( : } 2 7 6 0 a c c d d c b a 0 8 5 4 1 [ _ ( ] ) , - * & ",
+". . . + # # % & * > ' ~ ] ( 8 ).C !.~.{.].{.^./.H R r [ / ^ ) > - & % # + . K Z Z %.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.Z Z Z Z K K K . . . . . . . . . . . . . . . . . . . . . . . $ n y ...M y r ; - > ) ~ / _ } 1 3 6 9 0 b c c d c d a 0 8 5 3 2 | : / ^ ! ' - * & ",
+". . . . + @ # % & * > , ! ] ( 6 v (.D _.Q :.q q p p r 6 < _ / ^ ! ' = $ # + . . . + + . . Z Z Z K . . . K K . . . . . Z K K K + + @ @ @ . . @ # # # + . . + @ @ @ . K K @ @ @ @ @ + + @ # # # @ . K @ m y ...M y r - = ; ' ^ / < 1 3 5 9 0 b c d d d c c b 0 0 5 3 1 } : / ] ) ' ; = & ",
+". . . . @ @ # % & * > , ~ ] ( 5 Y <.[.@.}.s 9 6 5 7 4 | 1 g ` w s l 2 ; @ + . & [ g l l h 2 & Z * } c j h 9 / ^ b l l l a > Z Z > b m m m c ) ; | e j d [ * + * } d j c _ $ K K Z K , b m m m c ! ; 1 f j e | - + @ m A ...M y r - * - ' ~ _ 5 j q u u q m g d d d c c b 0 0 5 3 1 } : / ^ ! ' ; = & ",
+" . . + + @ # $ & * - , ! ] ( 5 q |.1.2.[.r 9 6 5 7 1 | 0 <.H 3.4.5.6.5 & + % 3 *.X >.7.8.B ( . , k !.I V 9.S (.O 0.7.A 7.^.a a ^.7.A 7.S r o !.I a.T *.0 { j !.b.;.H (.4 @ Z %. a ^.7.A 7.S o r Q 5.3.b.!.j , K + m A ...M A o = & - , ( d c.~.T X I G d.&.l f d c c b 0 0 5 3 1 } : / ] ) ' ; = & ",
+" . . + + @ @ $ % * = , ) ~ / 3 e.f.C !.d.&.s s e.n 8 } 3 O 5.g.h.i.9.:.( $ ( c.4.j.M h.k.t , - n ^.l.m.L n.o.p.q.r.M .L 7.k . . k 7.L .s.t.u.v.w.m.N x.y D :.=.y.m.z.A.B.c.^ K %.. k 7.L .s.C.@.0.D.j.N m.E.G l = Z + m A ...M A o = * - ^ e [.F.G.H.I.E.z $.R J.m e c b c 0 9 5 3 2 | : / ^ ! ' - * & ",
+" . . + + # # % & = > , ! { 4 ` K.(.[.2.2.E F J @.m [ < c }.p.A.L.M.x n ( r H o.N.s.y.d.| $ 5 u.I.s.........z.O.N .... .A l . . l A ...P.x.t.Q.M ....P.m.,.;.R.L ....P.S.T.5 %.. l A ...P.o.U.M.s.......L D.E 3 . . m A ...M A o * * ; 3 C V M.i.V.R.D.t.p.9.J K.j c c a 9 9 5 3 1 [ _ ( ] ' , = & & ",
+" . . . . @ @ $ % & * ; , ! ] 1 o O |.1.2.~.G v.5.=.s } _ [ r =.W.X.X.t.@.p _.Y.M N.Q.S h & > v Z.n.......P.N.N.N....... .A l . . l A ..... .z.s.P.......N.`.m.N P....... +U f . %.. l A .....M +L N.P.......`.V p - + l A ...M A o * * ~ w I M.A.S.y.B.a.>.z .+5.Q q d c 0 9 6 5 2 1 < _ ] ^ ' , = * % ",
+" . . . + + @ # % & & - , ' ^ } l &.++C D _.T.0.8.@+s | _ ( 4 c.9.o.`.m.A #.p.`.P. +4.O ! . < !.W.N...N.h.D.#+D.h.N..... .A l . . l A .....N $+l.V.L ......N M.E.$+N ....s.Z.j . %.. l A ..... .x.w.#+D.m.N...N.I.d._ @ l A ...M A o * = 1 1.W x.A.D.x C C ^.V Z.F.@+:.h a 0 8 5 3 2 } < ( ] ! ' ; = * & ",
+" . . + + @ # $ % & * ; , ! [ i Y :.%+[.@.].#.H J ` [ ( / / c Q C.X.z.x.g.m.N.s.D.2.4 + 9 &+*+P...m.;.*.v *.a.m..... .A l . . l A ...P.$+=.(.^.V.P...P.M.@+%+@+M.P... .>.l . %.. l A .....m.9.6.v 1.3.j...P.=+^.8 # l A ...M A o * - c G g.m.*+z *.j l *.X 4.k.&+(.k 0 9 6 5 4 1 [ _ / ^ ) ' - * & $ ",
+" . . . + @ $ % & & - , ) : g q +.B -+C P ;+_.}.k [ ( / ~ ( s /.$+z.L s. .N.=+v.m & Z . f $. +..N.I.d.4 ; 4 d.I.N... .A l . . l A ... .W O } >+W ... .W O [ O W ... .A l . %.. l A ...N.Y.[.2 ; 3 }.I.N...L.X e # l A ...M A o * ; k '.i.j.*+z D +.O d.X V F./.[.n 0 0 5 3 2 | < _ / ^ ' , - * & % ",
+" . . . + + @ @ $ % & = > ' ( d s v c.c.` i j m i 7 : / ] ~ ! 1 %+y j.s. .N.z.p.J.{ K Z . j 4.L .. .,+Y = K = Y ,+ ... .A l . . l A ... .y o = o y ... .y o = o y ... .A l . %.. l A ... .,.t = K = Y ,+ ...N V i $ l A ...M A o * ; o a.x.j.A.R..+F.X $.4.V $./.!.r 0 6 5 4 1 [ < ( ^ ! ' ; = * & $ ",
+" . . . + + @ # $ % & * ; , ( c r u :.O e 1 | | | < _ / ] ~ ) / q U O.L M N.j.F.r * Z Z . k .+s... .z ` # Z # ` z ... .A l . . l A ... .A m $ m A ... .A m $ m A ... .A l . %.. l A ... .z r # Z # ` z ...s..+j $ l A ...M A o * ; r 4.m.j.A.Q.R.D.'+p..+a.X /.;+o 0 5 5 2 1 [ ( / ^ ) , ; = & % $ ",
+" . . . + + @ $ % & * = , / b o p O &.c | [ < < _ ( / ] ! ) 1 %+A j.L M N.z.p.J.{ K Z . j V N .. .#+v - K = u ,+ ... .A l . . l A ... .A l $ l A ... .A l $ l A ... .A l . %.. l A ... .,+Y = K - v #+ ...N V j $ l A ...M A o * ; o 9.A.j.A.R..+F.'.b./.S J ~.-+l 9 5 7 2 | : ( / ^ ' , = * & % # ",
+" . . . @ @ # $ & * = > ] 0 m q +.&.c | | } [ [ _ ( ] ~ ( ` 0.M.`.n.X.L N.=+v.m & Z . e X L...N.H.!.7 - 4 }.I.N... .A l . . l A ... .A l $ l A ... .A l $ l A ... .A l . %.. l A ...N.I.}.2 - 7 !.H.N...L.X e # l A ...M A o * - j I Q.j.o.p.2.O v u u v t q o c 6 5 4 2 } : ( { ~ ' , = * & % # ",
+" . . . + @ @ # $ % & * > ] 0 m q &.+.m f h j l g 2 / ] ] b ;+#+h.L.M.p.M.L L D.2.4 + 8 ^.=+P...j.V P v *.;.m..... .A l . . l A ... .A l $ l A ... .A l $ l A ... .A l . %.. l A .....m.;.*.v P V j...P.=+^.6 # l A ...M A o * - a {.)+m.o.,.;+n a e o t Y q m b 6 5 3 1 } _ ( ] ^ ' ; = * & $ $ ",
+" . . . + @ @ # $ % & * > ] 0 m q +.++f.<.P ;+_.d.h ( { 1 :.F.S.X.i.F.}.I =+N. +4.O ! : }.I.N...N.m.D.#+w.h.N..... .A l . . l A ... .A l $ l A ... .A l $ l A ... .A l . %.. l A ..... .x.w.#+D.m.N... .Y.[._ @ l A ...M A o * = | -+,.A.*+H.q.[.:.f.D {.{.@.++h 6 5 4 2 } : ( { ^ ' , = * & $ # ",
+" . . . @ @ # $ & * = > ] 0 m q +.B *.2.F =.-.{.o : < o @+w.A.x.p.1.f |.z n.N.Q.S i * > t 4.n.......P.N. .N....... .A l . . l A ... .A l $ l A ... .A l $ l A ... .A l . %.. l A .....s.j.N .P.......L.;.q - @ m A ...M A o = = ~ r -.r.o.S.D.Z.U '.U F.5.{.c.f 8 5 7 2 } < ( / ^ ' > - & & % # ",
+" . . . + + @ $ % & * = , / b n p >+|.P @.].S T J r } b P .+S.O.I.T.g ! c R r. .s.y.}.1 % 5 _.Y.s.......P. +Q.n..... .A l . . l A ... .A m % m A ... .A m % m A ... .A l . %.. l A ...P.i.>.g.s.......N E.Q 3 @ @ m y ...M y r - = > 4 %+9.g.S.!+H.y.,+7.9.v.D w b 9 5 7 2 1 [ _ / ^ ' , - * & % $ ",
+" . . . + + @ # $ % & * ; ' ( c r Y :.%+[.@.R S T T.r 7 ).H D.i.M.X ).] % ! t $.x.M h.~+Y ' ; n =.w.j.L n.i.7.T W.s. .L 7.k . . l 7.L .L 7.l % l 7.L .L 7.l % l 7.L .L 7.k . %.+ m A ... .'+Q 0.Y.X.N h.t.J j - @ m >.L .N >.o - = > ^ c 6.'.#+D.D.t.z ;.-.Q ).g 0 0 6 5 4 1 } : / ] ! ' ; = * & # ",
+" . . . + + @ @ $ % & = > ' / 9 k s u >+B *.D E F !.l b B &+F.;.I -+5 ; % & 1 (.X A >.5.|.: # ! m 2.5.4.9.^.B &.0.7.A 7.^.b . . b #.7.A 7.#.c $ c #.7.A 7.#.c $ c #.7.A 7.^.b . Z + m A ... .z v s E X V I D j ' @ + # c S >.y >.S g ; - , ! ( c O E /.5.5.0.{+C t i c a 0 8 5 3 2 | < _ / ^ ' , - * & % ",
+" . . . @ @ # % & * - , ! ] < 4 5 0 d g j m n r k 5 4 f r w s m 7 ! * & % ; } i r o k 5 ; + # > 2 f l j 0 ( { c m m m c ' ' c m m m c ) + ) c n m m c ) + ) c m m m c ' K @ m y ... .y r ~ 4 g k f 1 ; @ @ @ @ ~ e r r r g / - ; , ~ ] : 5 j w Y v Y e.l f c c c 0 0 9 5 4 1 } _ / ] ) ' ; = & % ",
+" . . . + + @ # % * = - , ! ] / _ [ 1 1 4 3 3 3 4 4 1 [ < : ( / ^ ) > ; = = * * - = = = & $ $ $ % & & & & % # # % $ $ $ $ + + + + $ $ % % % @ @ # & & & & % @ @ @ $ $ $ $ $ + + . $ n y ... .y r * % * * * & % $ % $ % % - - ; ; , ; , ' ) ^ / _ } 2 5 8 0 b c c c d c c a 0 9 6 3 4 1 [ ( ] ^ ) ; = * & ",
+" . . . + + @ # $ & & = , ' ~ ] ( : [ | 1 4 4 3 4 4 1 1 } < ( / ] ~ ' , ; ; - = * * & & & % % % % % % $ $ $ # # # @ @ @ + + + + + + @ @ @ @ # # # # $ $ $ # # # # @ @ @ + + + + + + % r y ... .z r = $ % % % % % % & & & & * * = = ; > , ) ~ { ( : [ 1 3 5 8 0 a c c d c c c a a 9 6 3 1 } _ ( ] ) , - * & ",
+" . . . + @ # # $ & * - , ) ^ / ( : | 1 4 3 7 5 7 7 3 2 1 } < ( / ] ^ ! ) ' , , > > ; ; ; - - - - = = = * * * & % % % $ $ $ # # $ $ $ $ % % & & & * * * * * & & & % % $ $ # # # # # = r z ... .z s > = = = - - - - - ; ; ; > > , , ) ! ~ ^ / ( : [ 1 4 7 8 0 a c d d e e d d c a 9 8 7 1 } < ( ] ! ' - * * ",
+" . . . . + @ $ % & * ; , ! ] ( : [ | 2 4 7 5 5 5 5 7 4 2 1 } : _ ( { ] ^ ~ ! ) ) ) ' ' ' ' ' ' ' , , , > > ; - - = = * * & & & & * * * = = - - ; ; ; ; ; ; ; - - = * * & & & & & & ; s z ... .z e.! > , , , ' ' ' , ' ' ' ) ) ! ! ^ ^ { ( ( : [ | 2 7 5 9 a b c e f f f f d c c a 9 5 4 1 < _ ] ~ ' ; * * ",
+" . . + + @ # % * = > , ~ { ( : } 1 4 7 5 5 8 8 6 5 5 3 4 1 | [ : _ ( / / { ] ] ] ^ ^ ^ ^ ^ ^ ~ ~ ~ ! ) ) ' , , , > > ; ; ; ; ; ; ; > , , , , ' ' ' ' ' ' ' , , , > ; ; - - - - - ' w y L .N y q ] ! ! ~ ~ ^ ^ ^ ^ ^ ^ ^ ] ] { { / ( ( : < } 1 2 7 5 9 0 b d e f g g g g f e c b 0 6 3 2 } _ ( ] ' , - * ",
+" . . + + @ # $ % & = > ' ~ { _ [ | 1 3 5 8 8 8 9 9 8 6 5 5 3 2 1 | } [ < : : _ _ ( ( ( ( ( ( ( ( ( / / { ] ] ^ ~ ! ! ) ' ' ' ' ' ' ) ) ! ~ ^ ^ ] ] ^ ^ ^ ] ^ ^ ~ ! ) ' , , > > > > ! k x z p.z /.n ( { / / ( ( ( ( ( ( ( ( ( _ : : < [ } } 1 2 4 7 6 9 0 b c d f g g h g g g e c b 0 8 3 1 } : / ^ ) , - * ",
+" . . + + @ # $ & & - , ) ~ { _ [ 1 3 7 6 8 0 0 0 0 0 9 8 6 5 7 7 4 2 1 1 1 | | } } } } } } } [ [ < < : : _ ( ( / / { ] ] ^ ^ ^ ^ ^ ] { / ( / ( ( ( ( ( ( ( ( / / { ] ^ ~ ! ~ ! ! ~ ~ 1 n t Y u r 5 : _ : < [ [ } } } [ } } } | 1 1 1 1 2 4 7 5 5 9 0 a c c e f g g h i h g g e d c a 8 7 2 | < ( ] ! ' ; * ",
+" . . + + @ # $ % * = , ) ~ / _ [ 1 3 5 6 0 0 0 b b b a 0 0 9 8 6 5 5 5 7 7 7 7 3 3 4 3 3 3 4 4 4 2 1 1 1 | } } < : : _ ( ( ( ( ( ( _ : : < [ } | } } } } } } < : : _ ( / / / { { / ( ( [ } } 1 1 | 1 1 1 1 2 4 4 3 3 3 3 3 3 7 3 7 7 5 6 6 9 0 0 a c c e g g h i j j i i h g f d c 0 8 5 4 1 < ( ] ! ' - * ",
+" . . + + @ @ $ % * = , ' ^ / _ [ 2 3 5 8 0 a b c c c c c b b a a 0 0 0 9 9 9 9 9 8 8 9 9 8 8 8 6 5 6 5 5 7 3 4 2 1 1 | } | } } | } | 1 1 1 2 4 4 3 3 3 3 4 4 2 1 1 } [ [ : : _ _ : < [ [ | | 1 2 3 7 5 5 6 6 8 8 9 9 9 9 9 9 0 9 0 0 0 0 a b c c d e f g h i j j j j j i i g f e c a 0 5 4 1 [ ( ] ! ' - * ",
+" . . + + @ @ $ % * = , ) ^ / _ } 2 7 5 8 0 b c d d e e e d d d c c c c c c c b c c c c b b b a b a 0 0 0 9 8 6 5 5 5 3 4 3 4 4 3 4 3 7 5 5 5 6 8 8 8 8 6 8 5 5 7 3 4 2 1 1 | | | | 1 1 2 4 3 5 5 6 9 0 0 0 a a b b c c b c c c c c c d d d e f g g h h i j j j k k j j j j g g e c b 8 5 3 1 [ ( ] ! ' - * ",
+" . . + + @ # $ & * - , ) ^ / : | 1 7 5 9 0 c d e f f g g g g g f f g f g g f g g f f f f f f f f e e d c c b b a 0 0 9 8 6 8 8 6 8 9 9 0 0 0 a b b b b a a 0 0 8 6 5 5 7 3 4 4 4 4 3 7 5 6 8 9 0 a b c c d e f f f g g f g g g g g g h g h i i i j j j k k l l l l l k j j h f f c 0 9 5 4 | [ ( ] ) ' - * ",
+" . . + + @ # $ & & - , ) ^ / : [ 2 7 5 0 0 c e f g g h h h i i h i i i i i i i i i i i i i j i i i h g g f f e d c c c b b b b b b c c d d d e f f e e e d d c c a 0 0 9 6 6 6 6 6 8 8 0 a a c c e f g g g h i i i j j j j j j j j j j j j k k k k l l m m m m l l m k j j g g e c 0 8 7 2 } : / ^ ) ' - * ",
+" . . + + @ # $ % & = , ' ~ / _ [ 1 3 5 9 a c d g g h i j j j j j k k k k l l l l l m m m m l l l k k j j j j i h g g g g f e e f g g g g g h i i i i i h h g g f d d c b a 0 a a 0 b b c c e f g g i j j k k k l l l m m m m m m m m m m m m m m m m m n m m m m m k k j i g f c b 0 6 5 1 | _ ( ^ ) ' - & ",
+" . . + + @ # % * = , , ^ { _ [ 1 4 5 9 a c e g h i j j k l l l m m m m m m m m n m m m m m n m m m m l m l k k j j j i j i i j i j j j k j k k k k k j j j i h g f f e d c d d c d e f g g h i j k k l m m m m n m n n m n n n n o n n n o n n n n n n n n m m m k k i h g e c a 0 5 7 2 [ : / ] ) , - * ",
+" . . . . + @ $ % * * ; ' ~ ] ( < | 2 5 8 0 c d g h j j k l l m m n n n n o o o o o o r o o r r o o o o n m n n m m m l l l k k l l m l m m m m m m m m m m l k j j i i g g g g g g g g h i j k l l m m n n n o o r o r r r r r r r r r o o r r r o o o o m m m m k j j i g f d b 0 8 5 4 | : ( { ! ) , = * ",
+" . . . + @ # # % & * - , ) ^ / _ } 2 5 8 0 b d f g i j l l m m m n o o o o o r r o r r r r r r r o o o o o o o n n m n n n m m n m m n n n n n n n n m m m m l l k j j j i i i i i j j j k k m m m m n o o o r r r r r r r r r r r r o o r o o o n n n m m m m k j j h g g e c a 9 5 3 1 [ : ( ] ! , ; = & ",
+" . . . + + @ # $ & * - > ) ^ / _ < 1 3 5 9 b c e g h j l l m m m n n n o o o o o o o n o n n n n n n n n o n n n n o o o o n n o o o o n o n n n n n n n m m m m l k k k j j j j j k k l l m l m m m m m m n m n m n n n n o o o o n n n m n m m m m m m l k j j h h g f e c 0 9 8 7 2 1 < ( { ^ ) , - * & ",
+" . . + + + @ $ % * = ; ' ! ] ( < | 2 7 8 0 b d f h i j k m m n m m n m m m m m n m m m m m m m m m m m n m n m n n o n o n n o n n n n n n n m n m m m m m m m l l l l l l l l l l l l l l m l m m m m m m m m m m m m m m m m m l m l m l m k k l k k j j j h g g e c b a 9 6 5 4 1 } : ( { ) ) ; = * & ",
+" . . . @ # # % * * - , ' ^ ( : [ 1 3 5 8 0 c d g g j j k k l m l l m l l m k l l k k l l l k k l k l m l m m m m n m m n n m m m m m m m m l l m l l k l l l m l l l m l l l l l l l l m l m l k k l k k j k j k l j j j j j j j j j j j j i j i h h g g f f e c b b 0 9 5 7 4 1 } < ( ] ! ! ' - * * % ",
+" . . . + + @ # $ & * = ; ) ! ^ ( : | 1 3 8 0 b c e g h i j j j j j k k k j i j j i h i h h h g i h i j i j j j j k k k k k k l k k l k j k j j j j j j j j j j k l k l l l l l l l k k k j k j j j i i i i h g g g h g g g h h h h g g g g g g g f e e d c c c b 0 0 9 6 7 4 1 | < < ( { ~ ) ' - = * & $ ",
+" . . . + + + @ # $ * * - , ) ^ { ( [ | 4 5 6 0 b c f f g g i i i h h g g g g f e e e f d d d e d f e g f f g g g g i h i i i i j h g g g g g g g g g g h g h i i j k j k k l m k k j j j j h g g g g f e e e c d c d c c c d d d d d c c c c c b b c a 0 0 9 0 6 6 5 5 4 2 1 | < _ ( { ~ ) ) ; = * & % # ",
+" . . . + + @ # $ & * * ; ' ! ] ( : [ 1 4 5 8 0 a c c d e g g f g e e d d c b a a a 0 0 0 0 0 0 0 a 0 b b c c c d c d e e e e e d d c c c c c c c d c c e g f h g h j j j j j j j j h h g f f d c b b a 0 0 0 0 0 0 9 9 9 9 0 0 0 0 0 9 9 0 8 8 8 9 5 5 6 5 5 7 3 2 1 | | < : _ / { ~ ! ' ; - * * % $ # ",
+" . . . + + + @ # % & * = ; ) ~ ] ( _ } 1 3 5 9 9 a b c c d c c c c c 0 0 0 9 8 8 6 6 5 6 5 5 6 5 6 6 6 8 8 0 0 0 0 0 0 b b 0 0 0 0 0 0 9 9 9 9 9 9 a a b b c e d f g g h h h h g g e e c c b a 0 0 9 6 5 5 6 7 5 5 5 5 5 5 7 7 7 7 7 7 3 3 3 3 3 3 2 2 2 2 1 1 1 [ [ < _ _ / / ^ ~ ! ' ; = * * % $ $ # ",
+" . . + + @ # $ % & * ; ' ) ^ ] ( : } 2 3 5 5 9 0 a 0 0 a 0 0 9 8 6 6 5 5 3 3 3 2 2 2 2 2 2 2 2 2 3 3 3 7 5 5 5 5 6 5 5 6 5 5 5 5 5 5 5 5 5 7 5 5 6 8 9 0 a c c e f g f f g d c c b a 0 9 8 5 5 7 3 4 2 2 2 1 1 1 1 1 1 1 1 1 1 1 | | | 1 1 | 1 } } [ [ [ : : _ ( / / { ^ ! ! ) , ; - * * % $ # @ @ ",
+" . . . + + @ # $ $ & * * ; ' ) ] { ( : } 1 4 5 5 6 9 8 8 9 6 6 5 7 3 3 2 2 | 1 } [ [ [ } } } [ [ } [ } 1 1 | 1 1 1 1 2 2 1 1 1 1 1 1 1 1 1 1 1 2 2 4 7 5 5 6 9 a a b c c c c b a a 0 8 6 5 7 4 1 | | } [ [ [ < [ : : : : : : : : : : _ _ _ _ _ _ : _ _ ( ( { { { ] ~ ! ! ! ' , ; - * = & % $ # @ @ @ ",
+" . . . + + + @ # % & * = ; ) ! ] { ( : } 1 2 4 7 7 5 5 7 7 4 4 1 1 | [ [ : _ _ ( ( / ( / / ( ( ( _ ( _ _ _ _ : : : : : : : : : : _ _ _ : : < [ [ | | 1 4 3 5 6 8 9 0 0 0 0 9 8 5 5 3 2 1 | | < < _ _ ( / / { / { { { { / / / / / / / { { { { { ] { ^ ~ ~ ! ! ! ! ) ' , ; - = * = * & % $ $ @ @ @ . ",
+" . . . . . + @ # $ % * * - ; ' ! ] / ( < [ | 1 1 2 1 2 1 2 1 | [ [ : _ / / { { ] ^ ^ ~ ~ ~ ~ ~ ^ ^ ^ ] { / { / / { { { { { / / / / / / { / / / _ _ < [ } 1 4 3 7 5 5 5 5 5 5 7 3 4 1 } [ < _ ( / { ] { ~ ~ ! ~ ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ) ) ) ' ) ' , > ; ; = = * = * & % $ $ # # @ + . + ",
+" . . . . . @ @ # $ % & * = , ) ! ] { ( _ < < [ | | | [ < [ : ( / { { ^ ! ! ! ) ) ) ' ' ' ' ' ) ' ) ) ) ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ~ { { / _ _ < | | 1 1 4 3 3 4 1 1 | [ < _ ( { / ~ ! ! ! ) ) ' , , > > > > > > > > > > > > > > > > > ; ; ; ; - - - = = = * * & % % $ $ # @ @ @ + . . . ",
+" . . + + + # # $ % & * - , ) ! ~ { / ( ( _ _ < : _ ( _ / { { ~ ! ! ) , > ; - - - = - = - - - ; ; ; ; > > ; > > > > > > ; ; ; ; > > , ' ) ! ! ^ { / ( _ < < [ | | | | [ < < _ ( { { ~ ! ) ' , ; ; ; - - - = = = = = = = = = = = = = = = = = * * * = = * * * & & % % $ $ # # @ @ + @ + . . . ",
+" . . + + + @ # $ & * * - > ' ! ~ ] { { / / / / { { ] ^ ! ! ' ' ; - - * * = * * * * * * * * * = = = * * = = = = = = = * * * = = = - - ; > ' ) ) ~ ] { / ( _ _ _ _ _ _ ( { { ] ~ ! ' , ; - = = * * & * & & & & & & & & & & & & & & & & & & & & % % % % % $ $ $ $ # # # @ @ + @ + . + . . . ",
+" . . . . . + @ @ # $ & * * - ; ' ) ! ! ~ ^ ] ] ^ ~ ! ) ) ' , ; - = * * * % % % % % % % % % % % % % % % & & & & & & & & & & & & & & & * * = - ; , ' ! ! ~ ] { { { { { { ] ~ ! ! ) , ; - * * & & & % % % $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ # # # # # # @ @ @ @ + @ + + . + . . . . ",
+" . . . . . + @ @ # $ & * * = - ; ' ) ) ! ! ! ! ) ' ' > - = = = * & $ $ $ # # # # # # # # # # # # # # # $ $ $ $ $ $ $ $ $ $ $ $ $ % % & * = = = - > , ) ! ) ! ! ! ! ) ) ) , > - = * * & % % # # # # # @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ + + + + + + + . . . . . . . ",
+" . . . . + @ @ # $ % & * * = - ; ; > > > > ; ; - = = = * & % $ # # @ @ @ @ @ + + + + + @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ # # $ $ % & & * * - ; ; > , ' ' ' ' , > ; ; = * * & % % $ # # @ @ @ + + + + + + + + + + + + + + + + + + + + + + + + + + + . . . . + . . . . . ",
+" . . + . + @ @ $ $ % & & * * = = - - - - = = * * & & % # # # @ @ + + + + . . . . . . . + + + + + + + + + + + + + + + + + + @ @ # # $ % % & * * * = - - ; ; - - = * * * & % % $ # @ @ + + + + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
+" . . . + . + + @ # # $ % % & * * * * * * & * & % % $ $ # @ @ @ + + + . . . . . . . . . . . . . . . . . . . . . . . . . + + + + @ @ # # $ % % & * * = * * * * = * * & % % $ # # @ @ + + + + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "};
diff --git a/vcl/qa/cppunit/graphicfilter/filters-dxf-test.cxx b/vcl/qa/cppunit/graphicfilter/filters-dxf-test.cxx
new file mode 100644
index 0000000000..7a13350082
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/filters-dxf-test.cxx
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <unotest/filters-test.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <tools/stream.hxx>
+#include <vcl/graph.hxx>
+#include <filter/DxfReader.hxx>
+
+using namespace css;
+
+/* Implementation of Filters test */
+
+class DxfFilterTest
+ : public test::FiltersTest
+ , public test::BootstrapFixture
+{
+public:
+ DxfFilterTest() : BootstrapFixture(true, false) {}
+
+ virtual bool load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int) override;
+
+ /**
+ * Ensure CVEs remain unbroken
+ */
+ void testCVEs();
+
+ CPPUNIT_TEST_SUITE(DxfFilterTest);
+ CPPUNIT_TEST(testCVEs);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+bool DxfFilterTest::load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int)
+{
+ SvFileStream aFileStream(rURL, StreamMode::READ);
+ Graphic aGraphic;
+ return ImportDxfGraphic(aFileStream, aGraphic);
+}
+
+void DxfFilterTest::testCVEs()
+{
+#ifndef DISABLE_CVE_TESTS
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/dxf/"));
+#endif
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(DxfFilterTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/graphicfilter/filters-eps-test.cxx b/vcl/qa/cppunit/graphicfilter/filters-eps-test.cxx
new file mode 100644
index 0000000000..34d7bab5d4
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/filters-eps-test.cxx
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <unotest/filters-test.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <tools/stream.hxx>
+#include <vcl/graph.hxx>
+#include <filter/EpsReader.hxx>
+
+using namespace css;
+
+/* Implementation of Filters test */
+
+class EpsFilterTest
+ : public test::FiltersTest
+ , public test::BootstrapFixture
+{
+public:
+ EpsFilterTest() : BootstrapFixture(true, false) {}
+
+ virtual bool load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int) override;
+
+ /**
+ * Ensure CVEs remain unbroken
+ */
+ void testCVEs();
+
+ CPPUNIT_TEST_SUITE(EpsFilterTest);
+ CPPUNIT_TEST(testCVEs);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+bool EpsFilterTest::load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int)
+{
+ SvFileStream aFileStream(rURL, StreamMode::READ);
+ Graphic aGraphic;
+ return ImportEpsGraphic(aFileStream, aGraphic);
+}
+
+void EpsFilterTest::testCVEs()
+{
+#ifndef DISABLE_CVE_TESTS
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/eps/"));
+#endif
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(EpsFilterTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/graphicfilter/filters-met-test.cxx b/vcl/qa/cppunit/graphicfilter/filters-met-test.cxx
new file mode 100644
index 0000000000..ff00920ed3
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/filters-met-test.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/.
+ */
+
+#include <unotest/filters-test.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <tools/stream.hxx>
+#include <vcl/graph.hxx>
+#include <filter/MetReader.hxx>
+
+using namespace css;
+
+/* Implementation of Filters test */
+
+class MetFilterTest
+ : public test::FiltersTest
+ , public test::BootstrapFixture
+{
+public:
+ MetFilterTest() : BootstrapFixture(true, false) {}
+
+ virtual bool load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int) override;
+
+ /**
+ * Ensure CVEs remain unbroken
+ */
+ void testCVEs();
+
+ CPPUNIT_TEST_SUITE(MetFilterTest);
+ CPPUNIT_TEST(testCVEs);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+bool MetFilterTest::load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int)
+{
+ SvFileStream aFileStream(rURL, StreamMode::READ);
+ Graphic aGraphic;
+ return ImportMetGraphic(aFileStream, aGraphic);
+}
+
+void MetFilterTest::testCVEs()
+{
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/met/"));
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(MetFilterTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/graphicfilter/filters-pcd-test.cxx b/vcl/qa/cppunit/graphicfilter/filters-pcd-test.cxx
new file mode 100644
index 0000000000..bd100c8f20
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/filters-pcd-test.cxx
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <unotest/filters-test.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <tools/stream.hxx>
+#include <vcl/graph.hxx>
+#include <filter/PcdReader.hxx>
+
+using namespace css;
+
+/* Implementation of Filters test */
+
+class PcdFilterTest
+ : public test::FiltersTest
+ , public test::BootstrapFixture
+{
+public:
+ PcdFilterTest() : BootstrapFixture(true, false) {}
+
+ virtual bool load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int) override;
+
+ /**
+ * Ensure CVEs remain unbroken
+ */
+ void testCVEs();
+
+ CPPUNIT_TEST_SUITE(PcdFilterTest);
+ CPPUNIT_TEST(testCVEs);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+bool PcdFilterTest::load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int)
+{
+ SvFileStream aFileStream(rURL, StreamMode::READ);
+ Graphic aGraphic;
+ return ImportPcdGraphic(aFileStream, aGraphic, nullptr);
+}
+
+void PcdFilterTest::testCVEs()
+{
+#ifndef DISABLE_CVE_TESTS
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/pcd/"));
+#endif
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(PcdFilterTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/graphicfilter/filters-pcx-test.cxx b/vcl/qa/cppunit/graphicfilter/filters-pcx-test.cxx
new file mode 100644
index 0000000000..5297631517
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/filters-pcx-test.cxx
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <unotest/filters-test.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <tools/stream.hxx>
+#include <vcl/graph.hxx>
+#include <filter/PcxReader.hxx>
+
+using namespace css;
+
+/* Implementation of Filters test */
+
+class PcxFilterTest
+ : public test::FiltersTest
+ , public test::BootstrapFixture
+{
+public:
+ PcxFilterTest() : BootstrapFixture(true, false) {}
+
+ virtual bool load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int) override;
+
+ /**
+ * Ensure CVEs remain unbroken
+ */
+ void testCVEs();
+
+ CPPUNIT_TEST_SUITE(PcxFilterTest);
+ CPPUNIT_TEST(testCVEs);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+bool PcxFilterTest::load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int)
+{
+ SvFileStream aFileStream(rURL, StreamMode::READ);
+ Graphic aGraphic;
+ return ImportPcxGraphic(aFileStream, aGraphic);
+}
+
+void PcxFilterTest::testCVEs()
+{
+#ifndef DISABLE_CVE_TESTS
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/pcx/"));
+#endif
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(PcxFilterTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/graphicfilter/filters-pict-test.cxx b/vcl/qa/cppunit/graphicfilter/filters-pict-test.cxx
new file mode 100644
index 0000000000..ebce81f9ab
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/filters-pict-test.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 <unotest/filters-test.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <test/xmltesttools.hxx>
+#include <tools/stream.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/metaactiontypes.hxx>
+
+#include <filter/PictReader.hxx>
+
+using namespace ::com::sun::star;
+
+/* Implementation of Filters test */
+
+class PictFilterTest
+ : public test::FiltersTest
+ , public test::BootstrapFixture
+ , public XmlTestTools
+{
+public:
+ PictFilterTest() : BootstrapFixture(true, false) {}
+
+ virtual bool load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int) override;
+
+ OUString pictURL()
+ {
+ return m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/pict/");
+ }
+
+ /**
+ * Ensure CVEs remain unbroken
+ */
+ void testCVEs();
+
+ void testDontClipTooMuch();
+
+ CPPUNIT_TEST_SUITE(PictFilterTest);
+ CPPUNIT_TEST(testCVEs);
+ CPPUNIT_TEST(testDontClipTooMuch);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+bool PictFilterTest::load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int)
+{
+ SvFileStream aFileStream(rURL, StreamMode::READ);
+ Graphic aGraphic;
+ return ImportPictGraphic(aFileStream, aGraphic);
+}
+
+void PictFilterTest::testCVEs()
+{
+#ifndef DISABLE_CVE_TESTS
+ testDir(OUString(), pictURL());
+#endif
+}
+
+void PictFilterTest::testDontClipTooMuch()
+{
+ SvFileStream aFileStream(pictURL() + "clipping-problem.pct", StreamMode::READ);
+ GDIMetaFile aGDIMetaFile;
+ ReadPictFile(aFileStream, aGDIMetaFile);
+
+ MetafileXmlDump dumper;
+ dumper.filterAllActionTypes();
+ dumper.filterActionType(MetaActionType::CLIPREGION, false);
+ xmlDocUniquePtr pDoc = dumpAndParse(dumper, aGDIMetaFile);
+
+ CPPUNIT_ASSERT (pDoc);
+
+ assertXPath(pDoc, "/metafile/clipregion[5]"_ostr, "top"_ostr, "0");
+ assertXPath(pDoc, "/metafile/clipregion[5]"_ostr, "left"_ostr, "0");
+ assertXPath(pDoc, "/metafile/clipregion[5]"_ostr, "bottom"_ostr, "empty");
+ assertXPath(pDoc, "/metafile/clipregion[5]"_ostr, "right"_ostr, "empty");
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(PictFilterTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/graphicfilter/filters-ppm-test.cxx b/vcl/qa/cppunit/graphicfilter/filters-ppm-test.cxx
new file mode 100644
index 0000000000..c9c93a687d
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/filters-ppm-test.cxx
@@ -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/.
+ */
+
+#include <unotest/filters-test.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <tools/stream.hxx>
+#include <vcl/graph.hxx>
+#include <filter/PbmReader.hxx>
+
+using namespace ::com::sun::star;
+
+/* Implementation of Filters test */
+
+class PpmFilterTest
+ : public test::FiltersTest
+ , public test::BootstrapFixture
+{
+public:
+ PpmFilterTest() : BootstrapFixture(true, false) {}
+
+ virtual bool load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int) override;
+
+ /**
+ * Ensure CVEs remain unbroken
+ */
+ void testCVEs();
+
+ CPPUNIT_TEST_SUITE(PpmFilterTest);
+ CPPUNIT_TEST(testCVEs);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+bool PpmFilterTest::load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int)
+{
+ SvFileStream aFileStream(rURL, StreamMode::READ);
+ Graphic aGraphic;
+ return ImportPbmGraphic(aFileStream, aGraphic);
+}
+
+void PpmFilterTest::testCVEs()
+{
+#ifndef DISABLE_CVE_TESTS
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/ppm/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/pbm/"));
+#endif
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(PpmFilterTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/graphicfilter/filters-psd-test.cxx b/vcl/qa/cppunit/graphicfilter/filters-psd-test.cxx
new file mode 100644
index 0000000000..bf181f811e
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/filters-psd-test.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/.
+ */
+
+#include <unotest/filters-test.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <tools/stream.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <filter/PsdReader.hxx>
+
+using namespace css;
+
+/* Implementation of Filters test */
+
+class PsdFilterTest
+ : public test::FiltersTest
+ , public test::BootstrapFixture
+{
+public:
+ PsdFilterTest() : BootstrapFixture(true, false) {}
+
+ virtual bool load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int) override;
+
+ OUString getUrl() const
+ {
+ return m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/psd/");
+ }
+
+ /**
+ * Ensure CVEs remain unbroken
+ */
+ void testCVEs();
+ void testTdf142629();
+
+ CPPUNIT_TEST_SUITE(PsdFilterTest);
+ CPPUNIT_TEST(testCVEs);
+ CPPUNIT_TEST(testTdf142629);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+bool PsdFilterTest::load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int)
+{
+ SvFileStream aFileStream(rURL, StreamMode::READ);
+ Graphic aGraphic;
+ return ImportPsdGraphic(aFileStream, aGraphic);
+}
+
+void PsdFilterTest::testCVEs()
+{
+#ifndef DISABLE_CVE_TESTS
+ testDir(OUString(), getUrl());
+#endif
+}
+
+void PsdFilterTest::testTdf142629()
+{
+ OUString aURL = getUrl() + "tdf142629.psd";
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ Graphic aGraphic;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+
+ // Without the fix in place, the following asserts would have failed
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+
+ CPPUNIT_ASSERT(aGraphic.IsAlpha());
+ CPPUNIT_ASSERT(aGraphic.IsTransparent());
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(PsdFilterTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/graphicfilter/filters-ras-test.cxx b/vcl/qa/cppunit/graphicfilter/filters-ras-test.cxx
new file mode 100644
index 0000000000..d986c7d0db
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/filters-ras-test.cxx
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <unotest/filters-test.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <tools/stream.hxx>
+#include <vcl/graph.hxx>
+#include <filter/RasReader.hxx>
+
+using namespace css;
+
+/* Implementation of Filters test */
+
+class RasFilterTest
+ : public test::FiltersTest
+ , public test::BootstrapFixture
+{
+public:
+ RasFilterTest() : BootstrapFixture(true, false) {}
+
+ virtual bool load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int) override;
+
+ /**
+ * Ensure CVEs remain unbroken
+ */
+ void testCVEs();
+
+ CPPUNIT_TEST_SUITE(RasFilterTest);
+ CPPUNIT_TEST(testCVEs);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+bool RasFilterTest::load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int)
+{
+ SvFileStream aFileStream(rURL, StreamMode::READ);
+ Graphic aGraphic;
+ return ImportRasGraphic(aFileStream, aGraphic);
+}
+
+void RasFilterTest::testCVEs()
+{
+#ifndef DISABLE_CVE_TESTS
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/ras/"));
+#endif
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(RasFilterTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/graphicfilter/filters-test.cxx b/vcl/qa/cppunit/graphicfilter/filters-test.cxx
new file mode 100644
index 0000000000..e065cf6b5d
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/filters-test.cxx
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <cstdlib>
+
+#include <unotest/filters-test.hxx>
+#include <test/bootstrapfixture.hxx>
+
+#include <comphelper/fileformat.h>
+#include <comphelper/propertyvalue.hxx>
+
+#include <vcl/graphicfilter.hxx>
+#include <tools/stream.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+
+using namespace ::com::sun::star;
+
+/* Implementation of Filters test */
+
+class VclFiltersTest :
+ public test::FiltersTest,
+ public test::BootstrapFixture
+{
+ std::unique_ptr<GraphicFilter> mpGraphicFilter;
+public:
+ VclFiltersTest() :
+ BootstrapFixture(true, false),
+ mpGraphicFilter(new GraphicFilter(false))
+ {}
+
+ virtual bool load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int) override;
+
+ void checkExportImport(std::u16string_view aFilterShortName);
+
+ /**
+ * Ensure CVEs remain unbroken
+ */
+ void testCVEs();
+
+ void testScaling();
+ void testExportImport();
+
+ CPPUNIT_TEST_SUITE(VclFiltersTest);
+ CPPUNIT_TEST(testCVEs);
+ CPPUNIT_TEST(testScaling);
+ CPPUNIT_TEST(testExportImport);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+bool VclFiltersTest::load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int)
+{
+ SvFileStream aFileStream(rURL, StreamMode::READ);
+ Graphic aGraphic;
+ bool bRetval(ERRCODE_NONE == mpGraphicFilter->ImportGraphic(aGraphic, rURL, aFileStream));
+
+ if (!bRetval)
+ {
+ // if error occurred, we are done
+ return bRetval;
+ }
+
+ // if not and we have an embedded Vector Graphic Data, trigger it's interpretation
+ // to check for error. Graphic with VectorGraphicData (Svg/Emf/Wmf) load without error
+ // as long as one of the three types gets detected. Thus, cycles like load/save in
+ // other format will work (what may be wanted). For the test framework it was indirectly
+ // intended to trigger an error when load in the sense of deep data interpretation fails,
+ // so we need to trigger this here
+ if (aGraphic.getVectorGraphicData())
+ {
+ if (aGraphic.getVectorGraphicData()->getRange().isEmpty())
+ {
+ // invalid file or file with no content
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void VclFiltersTest::testScaling()
+{
+ for (BmpScaleFlag i = BmpScaleFlag::Default; i <= BmpScaleFlag::BiLinear; i = static_cast<BmpScaleFlag>(static_cast<int>(i) + 1))
+ {
+ Bitmap aBitmap(Size(413, 409), vcl::PixelFormat::N24_BPP);
+ BitmapEx aBitmapEx( aBitmap );
+
+ fprintf( stderr, "scale with type %d\n", int( i ) );
+ CPPUNIT_ASSERT( aBitmapEx.Scale( 0.1937046, 0.193154, i ) );
+ Size aAfter( aBitmapEx.GetSizePixel() );
+ fprintf( stderr, "size %" SAL_PRIdINT64 ", %" SAL_PRIdINT64 "\n", sal_Int64(aAfter.Width()), sal_Int64(aAfter.Height()) );
+ CPPUNIT_ASSERT( std::abs (aAfter.Height() - aAfter.Width()) <= 1 );
+ }
+}
+
+void VclFiltersTest::checkExportImport(std::u16string_view aFilterShortName)
+{
+ Bitmap aBitmap(Size(100, 100), vcl::PixelFormat::N24_BPP);
+ aBitmap.Erase(COL_WHITE);
+
+ SvMemoryStream aStream;
+ aStream.SetVersion( SOFFICE_FILEFORMAT_CURRENT );
+
+ css::uno::Sequence< css::beans::PropertyValue > aFilterData{
+ comphelper::makePropertyValue("Interlaced", sal_Int32(0)),
+ comphelper::makePropertyValue("Compression", sal_Int32(1)),
+ comphelper::makePropertyValue("Quality", sal_Int32(90))
+ };
+
+ sal_uInt16 aFilterType = mpGraphicFilter->GetExportFormatNumberForShortName(aFilterShortName);
+ mpGraphicFilter->ExportGraphic(BitmapEx(aBitmap), u"", aStream, aFilterType, &aFilterData );
+
+ CPPUNIT_ASSERT(aStream.Tell() > 0);
+
+ aStream.Seek( STREAM_SEEK_TO_BEGIN );
+
+ Graphic aLoadedGraphic;
+ mpGraphicFilter->ImportGraphic( aLoadedGraphic, u"", aStream );
+
+ BitmapEx aLoadedBitmapEx = aLoadedGraphic.GetBitmapEx();
+ Size aSize = aLoadedBitmapEx.GetSizePixel();
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aSize.Height());
+}
+
+void VclFiltersTest::testExportImport()
+{
+ fprintf(stderr, "Check ExportImport JPG\n");
+ checkExportImport(u"jpg");
+ fprintf(stderr, "Check ExportImport PNG\n");
+ checkExportImport(u"png");
+ fprintf(stderr, "Check ExportImport BMP\n");
+ checkExportImport(u"bmp");
+ fprintf(stderr, "Check ExportImport TIF\n");
+ checkExportImport(u"tif");
+ fprintf(stderr, "Check ExportImport WEBP\n");
+ checkExportImport(u"webp");
+}
+
+void VclFiltersTest::testCVEs()
+{
+#ifndef DISABLE_CVE_TESTS
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/wmf/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/emf/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/png/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/jpg/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/gif/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/bmp/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/xbm/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/xpm/"));
+
+ testDir(OUString(),
+ m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/svm/"));
+#endif
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclFiltersTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/graphicfilter/filters-tga-test.cxx b/vcl/qa/cppunit/graphicfilter/filters-tga-test.cxx
new file mode 100644
index 0000000000..d0611aa56c
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/filters-tga-test.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 <unotest/filters-test.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <tools/stream.hxx>
+#include <vcl/graph.hxx>
+#include <filter/TgaReader.hxx>
+
+using namespace ::com::sun::star;
+
+/* Implementation of Filters test */
+
+class TgaFilterTest
+ : public test::FiltersTest
+ , public test::BootstrapFixture
+{
+public:
+ TgaFilterTest() : BootstrapFixture(true, false) {}
+
+ virtual bool load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int) override;
+
+ /**
+ * Ensure CVEs remain unbroken
+ */
+ void testCVEs();
+
+ CPPUNIT_TEST_SUITE(TgaFilterTest);
+ CPPUNIT_TEST(testCVEs);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+bool TgaFilterTest::load(const OUString &,
+ const OUString &rURL, const OUString &,
+ SfxFilterFlags, SotClipboardFormatId, unsigned int)
+{
+ SvFileStream aFileStream(rURL, StreamMode::READ);
+ Graphic aGraphic;
+ return ImportTgaGraphic(aFileStream, aGraphic);
+}
+
+void TgaFilterTest::testCVEs()
+{
+#ifndef DISABLE_CVE_TESTS
+ testDir(OUString(), m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/tga/"));
+#endif
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TgaFilterTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/graphicfilter/filters-tiff-test.cxx b/vcl/qa/cppunit/graphicfilter/filters-tiff-test.cxx
new file mode 100644
index 0000000000..4e7585398e
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/filters-tiff-test.cxx
@@ -0,0 +1,317 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <unotest/filters-test.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <tools/stream.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <graphic/GraphicFormatDetector.hxx>
+
+#include <filter/TiffReader.hxx>
+
+using namespace ::com::sun::star;
+
+/* Implementation of Filters test */
+
+class TiffFilterTest : public test::FiltersTest, public test::BootstrapFixture
+{
+public:
+ TiffFilterTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ virtual bool load(const OUString&, const OUString& rURL, const OUString&, SfxFilterFlags,
+ SotClipboardFormatId, unsigned int) override;
+
+ OUString getUrl() const
+ {
+ return m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/tiff/");
+ }
+
+ /**
+ * Ensure CVEs remain unbroken
+ */
+ void testCVEs();
+ void testTdf126460();
+ void testTdf115863();
+ void testTdf138818();
+ void testTdf149418();
+ void testTdf74331();
+ void testRoundtrip();
+ void testRGB8bits();
+ void testRGB16bits();
+
+ CPPUNIT_TEST_SUITE(TiffFilterTest);
+ CPPUNIT_TEST(testCVEs);
+ CPPUNIT_TEST(testTdf126460);
+ CPPUNIT_TEST(testTdf115863);
+ CPPUNIT_TEST(testTdf138818);
+ CPPUNIT_TEST(testTdf149418);
+ CPPUNIT_TEST(testTdf74331);
+ CPPUNIT_TEST(testRoundtrip);
+ CPPUNIT_TEST(testRGB8bits);
+ CPPUNIT_TEST(testRGB16bits);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+bool TiffFilterTest::load(const OUString&, const OUString& rURL, const OUString&, SfxFilterFlags,
+ SotClipboardFormatId, unsigned int)
+{
+ SvFileStream aFileStream(rURL, StreamMode::READ);
+ Graphic aGraphic;
+ return ImportTiffGraphicImport(aFileStream, aGraphic);
+}
+
+void TiffFilterTest::testCVEs()
+{
+#ifndef DISABLE_CVE_TESTS
+ testDir(OUString(), getUrl());
+#endif
+}
+
+void TiffFilterTest::testTdf126460()
+{
+ OUString aURL = getUrl() + "tdf126460.tif";
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ Graphic aGraphic;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+
+ // Without the fix in place, the following asserts would have failed
+ CPPUNIT_ASSERT(aGraphic.IsAlpha());
+ CPPUNIT_ASSERT(aGraphic.IsTransparent());
+}
+
+void TiffFilterTest::testTdf115863()
+{
+ OUString aURL = getUrl() + "tdf115863.tif";
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ Graphic aGraphic;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 0x0(Error Area:Io Class:NONE Code:0)
+ // - Actual : 0x8203(Error Area:Vcl Class:General Code:3)
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+
+ Bitmap aBitmap = aGraphic.GetBitmapEx().GetBitmap();
+ Size aSize = aBitmap.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(528), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(618), aSize.Height());
+}
+
+void TiffFilterTest::testTdf138818()
+{
+ OUString aURL = getUrl() + "tdf138818.tif";
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ Graphic aGraphic;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 46428
+ // - Actual : 45951
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(46428), aGraphic.GetGfxLink().GetDataSize());
+}
+
+void TiffFilterTest::testTdf149418()
+{
+ OUString aURL = getUrl() + "tdf149418.tif";
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ Graphic aGraphic;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 50938
+ // - Actual : 50029
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(50938), aGraphic.GetGfxLink().GetDataSize());
+}
+
+void TiffFilterTest::testTdf74331()
+{
+ OUString aURL = getUrl() + "tdf74331.tif";
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ Graphic aGraphic;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+
+ Bitmap aBitmap = aGraphic.GetBitmapEx().GetBitmap();
+ Size aSize = aBitmap.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(200), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(200), aSize.Height());
+
+ BitmapScopedReadAccess pReadAccess(aBitmap);
+
+ // Check the image contains different kinds of grays
+ int nGrayCount = 0;
+ int nGray3Count = 0;
+ int nGray7Count = 0;
+ int nLightGrayCount = 0;
+
+ for (tools::Long nX = 1; nX < aSize.Width() - 1; ++nX)
+ {
+ for (tools::Long nY = 1; nY < aSize.Height() - 1; ++nY)
+ {
+ const Color aColor = pReadAccess->GetColor(nY, nX);
+ if (aColor == COL_GRAY)
+ ++nGrayCount;
+ else if (aColor == COL_GRAY3)
+ ++nGray3Count;
+ else if (aColor == COL_GRAY7)
+ ++nGray7Count;
+ else if (aColor == COL_LIGHTGRAY)
+ ++nLightGrayCount;
+ }
+ }
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 313
+ // - Actual : 0
+ CPPUNIT_ASSERT_EQUAL(313, nGrayCount);
+ CPPUNIT_ASSERT_EQUAL(71, nGray3Count);
+ CPPUNIT_ASSERT_EQUAL(227, nGray7Count);
+ CPPUNIT_ASSERT_EQUAL(165, nLightGrayCount);
+}
+
+void TiffFilterTest::testRoundtrip()
+{
+ Bitmap aBitmap(Size(2, 2), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap);
+ pAccess->SetPixel(0, 0, COL_WHITE);
+ pAccess->SetPixel(0, 1, COL_BLACK);
+ pAccess->SetPixel(1, 0, COL_LIGHTRED);
+ pAccess->SetPixel(1, 1, COL_LIGHTGREEN);
+ }
+
+ SvMemoryStream aStream;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ sal_uInt16 nFilterFormat = rFilter.GetExportFormatNumberForShortName(u"tif");
+ rFilter.ExportGraphic(Graphic(BitmapEx(aBitmap)), u"none", aStream, nFilterFormat);
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ Graphic aGraphic;
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, u"none", aStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+ CPPUNIT_ASSERT_EQUAL(GfxLinkType::NativeTif, aGraphic.GetGfxLink().GetType());
+ Bitmap aResultBitmap = aGraphic.GetBitmapEx().GetBitmap();
+ CPPUNIT_ASSERT_EQUAL(Size(2, 2), aResultBitmap.GetSizePixel());
+
+ {
+ BitmapScopedReadAccess pAccess(aResultBitmap);
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, Color(pAccess->GetPixel(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, Color(pAccess->GetPixel(0, 1)));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, Color(pAccess->GetPixel(1, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTGREEN, Color(pAccess->GetPixel(1, 1)));
+ }
+
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+ vcl::GraphicFormatDetector aDetector(aStream, "");
+
+ CPPUNIT_ASSERT_EQUAL(true, aDetector.detect());
+ CPPUNIT_ASSERT_EQUAL(true, aDetector.checkTIF());
+ CPPUNIT_ASSERT_EQUAL(u"TIF"_ustr,
+ vcl::getImportFormatShortName(aDetector.getMetadata().mnFormat));
+}
+
+void TiffFilterTest::testRGB8bits()
+{
+ const std::initializer_list<std::u16string_view> aNames = {
+ u"red8.tif",
+ u"green8.tif",
+ u"blue8.tif",
+ };
+
+ for (const auto& rName : aNames)
+ {
+ OUString aURL = getUrl() + rName;
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ Graphic aGraphic;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+
+ Bitmap aBitmap = aGraphic.GetBitmapEx().GetBitmap();
+ Size aSize = aBitmap.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aSize.Height());
+
+ BitmapScopedReadAccess pReadAccess(aBitmap);
+ const Color aColor = pReadAccess->GetColor(5, 5);
+
+ if (rName == u"red8.tif")
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aColor);
+ else if (rName == u"green8.tif")
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTGREEN, aColor);
+ else
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, aColor);
+ }
+}
+
+void TiffFilterTest::testRGB16bits()
+{
+ const std::initializer_list<std::u16string_view> aNames = {
+ u"red16.tif",
+ u"green16.tif",
+ u"blue16.tif",
+ };
+
+ for (const auto& rName : aNames)
+ {
+ OUString aURL = getUrl() + rName;
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ Graphic aGraphic;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+
+ Bitmap aBitmap = aGraphic.GetBitmapEx().GetBitmap();
+ Size aSize = aBitmap.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(10), aSize.Height());
+
+ BitmapScopedReadAccess pReadAccess(aBitmap);
+ const Color aColor = pReadAccess->GetColor(5, 5);
+
+ if (rName == u"red16.tif")
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aColor);
+ else if (rName == u"green16.tif")
+ // tdf#142151: Without the fix in place, this test would have failed with
+ // - Expected: rgba[00ff00ff]
+ // - Actual : rgba[000000ff]
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTGREEN, aColor);
+ else
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, aColor);
+ }
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TiffFilterTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/graphicfilter/filters-webp-test.cxx b/vcl/qa/cppunit/graphicfilter/filters-webp-test.cxx
new file mode 100644
index 0000000000..d3c43f4819
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/filters-webp-test.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/.
+ */
+
+#include <unotest/filters-test.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <tools/stream.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <graphic/GraphicFormatDetector.hxx>
+#include <filter/WebpReader.hxx>
+#include <comphelper/propertyvalue.hxx>
+
+using namespace css;
+
+/* Implementation of Filters test */
+
+class WebpFilterTest : public test::FiltersTest, public test::BootstrapFixture
+{
+public:
+ WebpFilterTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ virtual bool load(const OUString&, const OUString& rURL, const OUString&, SfxFilterFlags,
+ SotClipboardFormatId, unsigned int) override;
+
+ /**
+ * Ensure CVEs remain unbroken
+ */
+ void testCVEs();
+
+ void testRoundtripLossless();
+ void testRoundtripLossy();
+ void testReadAlphaLossless();
+ void testReadAlphaLossy();
+ void testReadNoAlphaLossless();
+ void testReadNoAlphaLossy();
+
+ CPPUNIT_TEST_SUITE(WebpFilterTest);
+ CPPUNIT_TEST(testCVEs);
+ CPPUNIT_TEST(testRoundtripLossless);
+ CPPUNIT_TEST(testRoundtripLossy);
+ CPPUNIT_TEST(testReadAlphaLossless);
+ CPPUNIT_TEST(testReadAlphaLossy);
+ CPPUNIT_TEST(testReadNoAlphaLossless);
+ CPPUNIT_TEST(testReadNoAlphaLossy);
+ CPPUNIT_TEST_SUITE_END();
+
+private:
+ void testRoundtrip(bool lossy);
+ void testRead(std::u16string_view rName, bool lossy, bool alpha);
+};
+
+bool WebpFilterTest::load(const OUString&, const OUString& rURL, const OUString&, SfxFilterFlags,
+ SotClipboardFormatId, unsigned int)
+{
+ SvFileStream aFileStream(rURL, StreamMode::READ);
+ Graphic aGraphic;
+ return ImportWebpGraphic(aFileStream, aGraphic);
+}
+
+void WebpFilterTest::testCVEs()
+{
+#ifndef DISABLE_CVE_TESTS
+ testDir(OUString(), m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/webp/"));
+#endif
+}
+
+void WebpFilterTest::testRoundtripLossless() { testRoundtrip(false); }
+
+void WebpFilterTest::testRoundtripLossy() { testRoundtrip(true); }
+
+void WebpFilterTest::testRoundtrip(bool lossy)
+{
+ // Do not use just 2x2, lossy saving would change colors.
+ Bitmap aBitmap(Size(20, 20), vcl::PixelFormat::N24_BPP);
+ AlphaMask aAlpha(Size(20, 20));
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap);
+ pAccess->SetFillColor(COL_WHITE);
+ pAccess->FillRect(tools::Rectangle(Point(0, 0), Size(10, 10)));
+ pAccess->SetFillColor(COL_BLACK);
+ pAccess->FillRect(tools::Rectangle(Point(10, 0), Size(10, 10)));
+ pAccess->SetFillColor(COL_LIGHTRED);
+ pAccess->FillRect(tools::Rectangle(Point(0, 10), Size(10, 10)));
+ pAccess->SetFillColor(COL_BLUE);
+ pAccess->FillRect(tools::Rectangle(Point(10, 10), Size(10, 10)));
+ BitmapScopedWriteAccess pAccessAlpha(aAlpha);
+ pAccessAlpha->SetFillColor(BitmapColor(0)); // opaque
+ pAccessAlpha->FillRect(tools::Rectangle(Point(0, 0), Size(10, 10)));
+ pAccessAlpha->FillRect(tools::Rectangle(Point(10, 0), Size(10, 10)));
+ pAccessAlpha->FillRect(tools::Rectangle(Point(0, 10), Size(10, 10)));
+ pAccessAlpha->SetFillColor(BitmapColor(64, 64, 64));
+ pAccessAlpha->FillRect(tools::Rectangle(Point(10, 10), Size(10, 10)));
+ }
+ BitmapEx aBitmapEx(aBitmap, aAlpha);
+
+ SvMemoryStream aStream;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ sal_uInt16 nFilterFormat = rFilter.GetExportFormatNumberForShortName(u"webp");
+ css::uno::Sequence<css::beans::PropertyValue> aFilterData{
+ comphelper::makePropertyValue("Lossless", !lossy),
+ comphelper::makePropertyValue("Quality", sal_Int32(100))
+ };
+ rFilter.ExportGraphic(Graphic(aBitmapEx), u"none", aStream, nFilterFormat, &aFilterData);
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ Graphic aGraphic;
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, u"none", aStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+ CPPUNIT_ASSERT_EQUAL(GfxLinkType::NativeWebp, aGraphic.GetGfxLink().GetType());
+ BitmapEx aResultBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(Size(20, 20), aResultBitmap.GetSizePixel());
+ CPPUNIT_ASSERT(aResultBitmap.IsAlpha());
+
+ {
+ Bitmap tmpBitmap = aResultBitmap.GetBitmap();
+ BitmapScopedReadAccess pAccess(tmpBitmap);
+ // Note that x,y are swapped.
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, Color(pAccess->GetPixel(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, Color(pAccess->GetPixel(0, 19)));
+ if (lossy)
+ {
+ CPPUNIT_ASSERT_LESS(sal_uInt16(3),
+ pAccess->GetPixel(19, 0).GetColorError(COL_LIGHTRED));
+ CPPUNIT_ASSERT_LESS(sal_uInt16(3), pAccess->GetPixel(19, 19).GetColorError(COL_BLUE));
+ }
+ else
+ {
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, Color(pAccess->GetPixel(19, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, Color(pAccess->GetPixel(19, 19)));
+ }
+ AlphaMask tmpAlpha = aResultBitmap.GetAlphaMask();
+ BitmapScopedReadAccess pAccessAlpha(tmpAlpha);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), pAccessAlpha->GetPixelIndex(0, 0));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), pAccessAlpha->GetPixelIndex(0, 19));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), pAccessAlpha->GetPixelIndex(19, 0));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(64), pAccessAlpha->GetPixelIndex(19, 19));
+ }
+
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+ vcl::GraphicFormatDetector aDetector(aStream, "");
+
+ CPPUNIT_ASSERT_EQUAL(true, aDetector.detect());
+ CPPUNIT_ASSERT_EQUAL(true, aDetector.checkWEBP());
+ CPPUNIT_ASSERT_EQUAL(u"WEBP"_ustr,
+ vcl::getImportFormatShortName(aDetector.getMetadata().mnFormat));
+}
+
+void WebpFilterTest::testReadAlphaLossless() { testRead(u"alpha_lossless.webp", false, true); }
+
+void WebpFilterTest::testReadAlphaLossy() { testRead(u"alpha_lossy.webp", true, true); }
+
+void WebpFilterTest::testReadNoAlphaLossless() { testRead(u"noalpha_lossless.webp", false, false); }
+
+void WebpFilterTest::testReadNoAlphaLossy() { testRead(u"noalpha_lossy.webp", true, false); }
+
+void WebpFilterTest::testRead(std::u16string_view rName, bool lossy, bool alpha)
+{
+ // Read a file created in GIMP and check it's read correctly.
+ OUString file
+ = m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/webp/") + rName;
+ SvFileStream aFileStream(file, StreamMode::READ);
+ Graphic aGraphic;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, u"none", aFileStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+ CPPUNIT_ASSERT_EQUAL(GfxLinkType::NativeWebp, aGraphic.GetGfxLink().GetType());
+ BitmapEx aResultBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(Size(10, 10), aResultBitmap.GetSizePixel());
+ CPPUNIT_ASSERT_EQUAL(alpha, aResultBitmap.IsAlpha());
+
+ {
+ Bitmap tmpBitmap = aResultBitmap.GetBitmap();
+ BitmapScopedReadAccess pAccess(tmpBitmap);
+ // Note that x,y are swapped.
+ if (lossy)
+ CPPUNIT_ASSERT_LESS(sal_uInt16(2), pAccess->GetPixel(0, 0).GetColorError(COL_LIGHTRED));
+ else
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, Color(pAccess->GetPixel(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, Color(pAccess->GetPixel(9, 9)));
+ if (alpha)
+ {
+ AlphaMask tmpAlpha = aResultBitmap.GetAlphaMask();
+ BitmapScopedReadAccess pAccessAlpha(tmpAlpha);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), pAccessAlpha->GetPixelIndex(0, 0));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(255), pAccessAlpha->GetPixelIndex(0, 9));
+ }
+ }
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(WebpFilterTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/jpeg/JpegReaderTest.cxx b/vcl/qa/cppunit/jpeg/JpegReaderTest.cxx
new file mode 100644
index 0000000000..f83d58d51d
--- /dev/null
+++ b/vcl/qa/cppunit/jpeg/JpegReaderTest.cxx
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <unotest/bootstrapfixturebase.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <tools/stream.hxx>
+
+constexpr OUStringLiteral gaDataUrl(u"/vcl/qa/cppunit/jpeg/data/");
+
+class JpegReaderTest : public test::BootstrapFixtureBase
+{
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(gaDataUrl) + sFileName;
+ }
+
+ Graphic loadJPG(const OUString& aURL);
+
+public:
+ void testReadRGB();
+ void testReadGray();
+ void testReadCMYK();
+ void testTdf138950();
+
+ CPPUNIT_TEST_SUITE(JpegReaderTest);
+ CPPUNIT_TEST(testReadRGB);
+ CPPUNIT_TEST(testReadGray);
+ CPPUNIT_TEST(testReadCMYK);
+ CPPUNIT_TEST(testTdf138950);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+static int deltaColor(BitmapColor aColor1, BitmapColor aColor2)
+{
+ int deltaR = std::abs(aColor1.GetRed() - aColor2.GetRed());
+ int deltaG = std::abs(aColor1.GetGreen() - aColor2.GetGreen());
+ int deltaB = std::abs(aColor1.GetBlue() - aColor2.GetBlue());
+
+ return std::max(std::max(deltaR, deltaG), deltaB);
+}
+
+static bool checkRect(Bitmap& rBitmap, int aLayerNumber, tools::Long nAreaHeight,
+ tools::Long nAreaWidth, Color aExpectedColor, int nMaxDelta)
+{
+ BitmapScopedWriteAccess pAccess(rBitmap);
+
+ tools::Long nWidth = std::min(nAreaWidth, pAccess->Width());
+ tools::Long nHeight = std::min(nAreaHeight, pAccess->Height());
+
+ tools::Long firstX = 0 + aLayerNumber;
+ tools::Long firstY = 0 + aLayerNumber;
+
+ tools::Long lastX = nWidth - 1 - aLayerNumber;
+ tools::Long lastY = nHeight - 1 - aLayerNumber;
+
+ int delta;
+
+ for (tools::Long y = firstY; y <= lastY; y++)
+ {
+ Color aColorFirst = pAccess->GetPixel(y, firstX);
+ delta = deltaColor(aColorFirst, aExpectedColor);
+ if (delta > nMaxDelta)
+ return false;
+
+ Color aColorLast = pAccess->GetPixel(y, lastX);
+ delta = deltaColor(aColorLast, aExpectedColor);
+ if (delta > nMaxDelta)
+ return false;
+ }
+ for (tools::Long x = firstX; x <= lastX; x++)
+ {
+ Color aColorFirst = pAccess->GetPixel(firstY, x);
+ delta = deltaColor(aColorFirst, aExpectedColor);
+ if (delta > nMaxDelta)
+ return false;
+
+ Color aColorLast = pAccess->GetPixel(lastY, x);
+ delta = deltaColor(aColorLast, aExpectedColor);
+ if (delta > nMaxDelta)
+ return false;
+ }
+ return true;
+}
+
+static int getNumberOfImageComponents(const Graphic& rGraphic)
+{
+ GfxLink aLink = rGraphic.GetGfxLink();
+ SvMemoryStream aMemoryStream(const_cast<sal_uInt8*>(aLink.GetData()), aLink.GetDataSize(),
+ StreamMode::READ | StreamMode::WRITE);
+ GraphicDescriptor aDescriptor(aMemoryStream, nullptr);
+ CPPUNIT_ASSERT(aDescriptor.Detect(true));
+ return aDescriptor.GetNumberOfImageComponents();
+}
+
+Graphic JpegReaderTest::loadJPG(const OUString& aURL)
+{
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic;
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+ return aGraphic;
+}
+
+void JpegReaderTest::testReadRGB()
+{
+ Graphic aGraphic = loadJPG(getFullUrl(u"JPEGTestRGB.jpeg"));
+ Bitmap aBitmap = aGraphic.GetBitmapEx().GetBitmap();
+ Size aSize = aBitmap.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(12), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(12), aSize.Height());
+
+ int nMaxDelta = 1; // still acceptable color error
+ CPPUNIT_ASSERT(checkRect(aBitmap, 0, 8, 8, Color(0xff, 0xff, 0xff), nMaxDelta));
+ CPPUNIT_ASSERT(checkRect(aBitmap, 1, 8, 8, Color(0xff, 0x00, 0x00), nMaxDelta));
+ CPPUNIT_ASSERT(checkRect(aBitmap, 2, 8, 8, Color(0x00, 0xff, 0x00), nMaxDelta));
+ CPPUNIT_ASSERT(checkRect(aBitmap, 3, 8, 8, Color(0x00, 0x00, 0xff), nMaxDelta));
+
+ CPPUNIT_ASSERT_EQUAL(3, getNumberOfImageComponents(aGraphic));
+}
+
+void JpegReaderTest::testReadGray()
+{
+ Graphic aGraphic = loadJPG(getFullUrl(u"JPEGTestGray.jpeg"));
+ Bitmap aBitmap = aGraphic.GetBitmapEx().GetBitmap();
+ Size aSize = aBitmap.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(12), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(12), aSize.Height());
+
+ aBitmap.Convert(
+ BmpConversion::N24Bit); // convert to 24bit so we don't need to deal with palette
+
+ int nMaxDelta = 1;
+ CPPUNIT_ASSERT(checkRect(aBitmap, 0, 8, 8, Color(0xff, 0xff, 0xff), nMaxDelta));
+ CPPUNIT_ASSERT(checkRect(aBitmap, 1, 8, 8, Color(0x36, 0x36, 0x36), nMaxDelta));
+ CPPUNIT_ASSERT(checkRect(aBitmap, 2, 8, 8, Color(0xb6, 0xb6, 0xb6), nMaxDelta));
+ CPPUNIT_ASSERT(checkRect(aBitmap, 3, 8, 8, Color(0x12, 0x12, 0x12), nMaxDelta));
+
+ CPPUNIT_ASSERT_EQUAL(1, getNumberOfImageComponents(aGraphic));
+}
+
+void JpegReaderTest::testReadCMYK()
+{
+ Graphic aGraphic = loadJPG(getFullUrl(u"JPEGTestCMYK.jpeg"));
+ Bitmap aBitmap = aGraphic.GetBitmapEx().GetBitmap();
+ Size aSize = aBitmap.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(12), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(12), aSize.Height());
+
+ int maxDelta = 1;
+ CPPUNIT_ASSERT(checkRect(aBitmap, 0, 8, 8, Color(0xff, 0xff, 0xff), maxDelta));
+ CPPUNIT_ASSERT(checkRect(aBitmap, 1, 8, 8, Color(0xff, 0x00, 0x00), maxDelta));
+ CPPUNIT_ASSERT(checkRect(aBitmap, 2, 8, 8, Color(0x00, 0xff, 0x00), maxDelta));
+ CPPUNIT_ASSERT(checkRect(aBitmap, 3, 8, 8, Color(0x00, 0x00, 0xff), maxDelta));
+
+ CPPUNIT_ASSERT_EQUAL(4, getNumberOfImageComponents(aGraphic));
+}
+
+void JpegReaderTest::testTdf138950()
+{
+ Graphic aGraphic = loadJPG(getFullUrl(u"tdf138950.jpeg"));
+ Bitmap aBitmap = aGraphic.GetBitmapEx().GetBitmap();
+ Size aSize = aBitmap.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(720), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(1280), aSize.Height());
+
+ BitmapScopedReadAccess pReadAccess(aBitmap);
+ int nBlackCount = 0;
+ for (tools::Long nY = 0; nY < aSize.Height(); ++nY)
+ {
+ for (tools::Long nX = 0; nX < aSize.Width(); ++nX)
+ {
+ const Color aColor = pReadAccess->GetColor(nY, nX);
+ if ((aColor.GetRed() == 0x00) && (aColor.GetGreen() == 0x00)
+ && (aColor.GetBlue() == 0x00))
+ ++nBlackCount;
+ }
+ }
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 0
+ // - Actual : 921600
+ CPPUNIT_ASSERT_EQUAL(0, nBlackCount);
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(JpegReaderTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/jpeg/JpegWriterTest.cxx b/vcl/qa/cppunit/jpeg/JpegWriterTest.cxx
new file mode 100644
index 0000000000..c20dd9e8bc
--- /dev/null
+++ b/vcl/qa/cppunit/jpeg/JpegWriterTest.cxx
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <unotest/bootstrapfixturebase.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <tools/stream.hxx>
+#include <graphic/GraphicFormatDetector.hxx>
+
+constexpr OUStringLiteral gaDataUrl(u"/vcl/qa/cppunit/jpeg/data/");
+
+class JpegWriterTest : public test::BootstrapFixtureBase
+{
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(gaDataUrl) + sFileName;
+ }
+
+ BitmapEx load(const OUString& aURL);
+ BitmapEx roundtripJPG(const BitmapEx& bitmap);
+ BitmapEx roundtripJPG(const OUString& aURL);
+
+public:
+ void testWrite8BitGrayscale();
+ void testWrite8BitNonGrayscale();
+
+ CPPUNIT_TEST_SUITE(JpegWriterTest);
+ CPPUNIT_TEST(testWrite8BitGrayscale);
+ CPPUNIT_TEST(testWrite8BitNonGrayscale);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+BitmapEx JpegWriterTest::load(const OUString& aURL)
+{
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic;
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+ return aGraphic.GetBitmapEx();
+}
+
+BitmapEx JpegWriterTest::roundtripJPG(const OUString& aURL) { return roundtripJPG(load(aURL)); }
+
+BitmapEx JpegWriterTest::roundtripJPG(const BitmapEx& bitmap)
+{
+ // EXPORT JPEG
+ SvMemoryStream aStream;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ sal_uInt16 exportFormatJPG = rFilter.GetExportFormatNumberForShortName(JPG_SHORTNAME);
+ Graphic aExportGraphic(bitmap);
+ ErrCode bResult = rFilter.ExportGraphic(aExportGraphic, u"memory", aStream, exportFormatJPG);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+ //Detect the magic bytes - we need to be sure the file is actually a JPEG
+ aStream.Seek(0);
+ vcl::GraphicFormatDetector aDetector(aStream, "");
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkJPG());
+ // IMPORT JPEG
+ aStream.Seek(0);
+ Graphic aImportGraphic;
+ sal_uInt16 importFormatJPG = rFilter.GetImportFormatNumberForShortName(JPG_SHORTNAME);
+ bResult = rFilter.ImportGraphic(aImportGraphic, u"memory", aStream, importFormatJPG);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+ return aImportGraphic.GetBitmapEx();
+}
+
+void JpegWriterTest::testWrite8BitGrayscale()
+{
+ Bitmap bitmap = roundtripJPG(getFullUrl(u"8BitGrayscale.jpg")).GetBitmap();
+ BitmapScopedReadAccess access(bitmap);
+ const ScanlineFormat format = access->GetScanlineFormat();
+ // Check that it's still 8bit grayscale.
+ CPPUNIT_ASSERT_EQUAL(ScanlineFormat::N8BitPal, format);
+ CPPUNIT_ASSERT(bitmap.HasGreyPalette8Bit());
+ // Check that the content is valid.
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_WHITE), access->GetColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_WHITE), access->GetColor(0, access->Width() - 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_WHITE), access->GetColor(access->Height() - 1, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_BLACK),
+ access->GetColor(access->Height() - 1, access->Width() - 1));
+}
+
+void JpegWriterTest::testWrite8BitNonGrayscale()
+{
+ Bitmap bitmap = roundtripJPG(getFullUrl(u"8BitNonGrayscale.gif")).GetBitmap();
+ BitmapScopedReadAccess access(bitmap);
+ const ScanlineFormat format = access->GetScanlineFormat();
+ // Check that it's still 8bit grayscale.
+ CPPUNIT_ASSERT_EQUAL(ScanlineFormat::N8BitPal, format);
+ // The original image has grayscale palette, just with entries in a different order.
+ // Do not check for grayscale 8bit, the roundtrip apparently fixes that. What's important
+ // is the content.
+ CPPUNIT_ASSERT(bitmap.HasGreyPaletteAny());
+ // CPPUNIT_ASSERT(bitmap.HasGreyPalette8Bit());
+ // Check that the content is valid.
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_WHITE), access->GetColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_WHITE), access->GetColor(0, access->Width() - 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_WHITE), access->GetColor(access->Height() - 1, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_BLACK),
+ access->GetColor(access->Height() - 1, access->Width() - 1));
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(JpegWriterTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/jpeg/data/8BitGrayscale.jpg b/vcl/qa/cppunit/jpeg/data/8BitGrayscale.jpg
new file mode 100644
index 0000000000..91541e4a85
--- /dev/null
+++ b/vcl/qa/cppunit/jpeg/data/8BitGrayscale.jpg
Binary files differ
diff --git a/vcl/qa/cppunit/jpeg/data/8BitNonGrayscale.gif b/vcl/qa/cppunit/jpeg/data/8BitNonGrayscale.gif
new file mode 100644
index 0000000000..2953109491
--- /dev/null
+++ b/vcl/qa/cppunit/jpeg/data/8BitNonGrayscale.gif
Binary files differ
diff --git a/vcl/qa/cppunit/jpeg/data/JPEGTestCMYK.jpeg b/vcl/qa/cppunit/jpeg/data/JPEGTestCMYK.jpeg
new file mode 100644
index 0000000000..81cca025ea
--- /dev/null
+++ b/vcl/qa/cppunit/jpeg/data/JPEGTestCMYK.jpeg
Binary files differ
diff --git a/vcl/qa/cppunit/jpeg/data/JPEGTestGray.jpeg b/vcl/qa/cppunit/jpeg/data/JPEGTestGray.jpeg
new file mode 100644
index 0000000000..014825f42e
--- /dev/null
+++ b/vcl/qa/cppunit/jpeg/data/JPEGTestGray.jpeg
Binary files differ
diff --git a/vcl/qa/cppunit/jpeg/data/JPEGTestRGB.jpeg b/vcl/qa/cppunit/jpeg/data/JPEGTestRGB.jpeg
new file mode 100644
index 0000000000..3cfe1dda25
--- /dev/null
+++ b/vcl/qa/cppunit/jpeg/data/JPEGTestRGB.jpeg
Binary files differ
diff --git a/vcl/qa/cppunit/jpeg/data/tdf138950.jpeg b/vcl/qa/cppunit/jpeg/data/tdf138950.jpeg
new file mode 100644
index 0000000000..d0296077c6
--- /dev/null
+++ b/vcl/qa/cppunit/jpeg/data/tdf138950.jpeg
Binary files differ
diff --git a/vcl/qa/cppunit/lifecycle.cxx b/vcl/qa/cppunit/lifecycle.cxx
new file mode 100644
index 0000000000..eaeed28b9b
--- /dev/null
+++ b/vcl/qa/cppunit/lifecycle.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/.
+ */
+
+#include <test/bootstrapfixture.hxx>
+
+#include <vcl/wrkwin.hxx>
+#include <vcl/toolkit/edit.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/toolkit/combobox.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/toolkit/field.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/scheduler.hxx>
+#include <com/sun/star/awt/XWindow.hpp>
+#include <com/sun/star/awt/XVclWindowPeer.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+
+class LifecycleTest : public test::BootstrapFixture
+{
+ void testWidgets(vcl::Window *pParent);
+
+public:
+ LifecycleTest() : BootstrapFixture(true, false) {}
+
+ void testCast();
+ void testVirtualDevice();
+ void testMultiDispose();
+ void testIsolatedWidgets();
+ void testParentedWidgets();
+ void testChildDispose();
+ void testPostDispose();
+ void testLeakage();
+ void testToolkit();
+
+ CPPUNIT_TEST_SUITE(LifecycleTest);
+ CPPUNIT_TEST(testCast);
+ CPPUNIT_TEST(testVirtualDevice);
+ CPPUNIT_TEST(testMultiDispose);
+ CPPUNIT_TEST(testIsolatedWidgets);
+ CPPUNIT_TEST(testParentedWidgets);
+ CPPUNIT_TEST(testChildDispose);
+ CPPUNIT_TEST(testPostDispose);
+ CPPUNIT_TEST(testLeakage);
+ CPPUNIT_TEST(testToolkit);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+// A compile time sanity check
+void LifecycleTest::testCast()
+{
+ ScopedVclPtrInstance< PushButton > xButton( nullptr, 0 );
+ ScopedVclPtr<vcl::Window> xWindow(xButton);
+
+ ScopedVclPtrInstance< MetricField > xField( nullptr, 0 );
+ ScopedVclPtr<SpinField> xSpin(xField);
+ ScopedVclPtr<Edit> xEdit(xField);
+
+// the following line should NOT compile
+// VclPtr<PushButton> xButton2(xWindow);
+}
+
+void LifecycleTest::testVirtualDevice()
+{
+ VclPtr<VirtualDevice> pVDev = VclPtr< VirtualDevice >::Create();
+ ScopedVclPtrInstance< VirtualDevice > pVDev2;
+ VclPtrInstance<VirtualDevice> pVDev3;
+ VclPtrInstance<VirtualDevice> pVDev4(DeviceFormat::WITHOUT_ALPHA);
+ CPPUNIT_ASSERT(!!pVDev);
+ CPPUNIT_ASSERT(!!pVDev2);
+ CPPUNIT_ASSERT(!!pVDev3);
+ CPPUNIT_ASSERT(!!pVDev4);
+ pVDev.disposeAndClear();
+ pVDev4.disposeAndClear();
+}
+
+void LifecycleTest::testMultiDispose()
+{
+ VclPtrInstance<WorkWindow> xWin(nullptr, WB_APP|WB_STDWORK);
+ CPPUNIT_ASSERT(xWin);
+ xWin->disposeOnce();
+ xWin->disposeOnce();
+ xWin->disposeOnce();
+ CPPUNIT_ASSERT(!xWin->GetWindow(GetWindowType::Parent));
+ CPPUNIT_ASSERT(!xWin->GetChild(0));
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0), xWin->GetChildCount());
+}
+
+void LifecycleTest::testWidgets(vcl::Window *pParent)
+{
+ {
+ ScopedVclPtrInstance< PushButton > aPtr( pParent );
+ (void)aPtr; // silence unused variable warning
+ }
+ {
+ ScopedVclPtrInstance< OKButton > aPtr( pParent );
+ (void)aPtr; // silence unused variable warning
+ }
+ {
+ ScopedVclPtrInstance< CancelButton > aPtr( pParent );
+ (void)aPtr; // silence unused variable warning
+ }
+ {
+ ScopedVclPtrInstance< HelpButton > aPtr( pParent );
+ (void)aPtr; // silence unused variable warning
+ }
+
+ // Some widgets really insist on adoption.
+ if (pParent)
+ {
+ {
+ ScopedVclPtrInstance< CheckBox > aPtr( pParent );
+ (void)aPtr; // silence unused variable warning
+ }
+ {
+ ScopedVclPtrInstance< Edit > aPtr( pParent );
+ (void)aPtr; // silence unused variable warning
+ }
+ {
+ ScopedVclPtrInstance< ComboBox > aPtr( pParent );
+ (void)aPtr; // silence unused variable warning
+ }
+ {
+ ScopedVclPtrInstance< RadioButton > aPtr( pParent, true, 0 );
+ (void)aPtr; // silence unused variable warning
+ }
+ }
+}
+
+void LifecycleTest::testIsolatedWidgets()
+{
+ testWidgets(nullptr);
+}
+
+void LifecycleTest::testParentedWidgets()
+{
+ ScopedVclPtrInstance<WorkWindow> xWin(nullptr, WB_APP|WB_STDWORK);
+ CPPUNIT_ASSERT(xWin);
+ xWin->Show();
+ testWidgets(xWin);
+}
+
+namespace {
+
+class DisposableChild : public vcl::Window
+{
+public:
+ explicit DisposableChild(vcl::Window *pParent) : vcl::Window(pParent) {}
+ virtual ~DisposableChild() override
+ {
+ disposeOnce();
+ }
+};
+
+}
+
+void LifecycleTest::testChildDispose()
+{
+ VclPtrInstance<WorkWindow> xWin(nullptr, WB_APP|WB_STDWORK);
+ CPPUNIT_ASSERT(xWin);
+ VclPtrInstance< DisposableChild > xChild( xWin.get() );
+ xWin->Show();
+ xChild->disposeOnce();
+ xWin->disposeOnce();
+}
+
+void LifecycleTest::testPostDispose()
+{
+ VclPtrInstance<WorkWindow> xWin(nullptr, WB_APP|WB_STDWORK);
+ xWin->disposeOnce();
+
+ // check selected methods continue to work post-dispose
+ CPPUNIT_ASSERT(!xWin->GetParent());
+ xWin->Show();
+ CPPUNIT_ASSERT(!xWin->IsReallyShown());
+ CPPUNIT_ASSERT(!xWin->IsEnabled());
+ CPPUNIT_ASSERT(!xWin->IsInputEnabled());
+ CPPUNIT_ASSERT(!xWin->GetChild(0));
+ CPPUNIT_ASSERT(!xWin->GetWindow(GetWindowType::Parent));
+}
+
+namespace {
+
+template <class vcl_type>
+class LeakTestClass : public vcl_type
+{
+ bool &mrDeleted;
+public:
+ template<typename... Arg>
+ LeakTestClass(bool &bDeleted, Arg &&... arg) :
+ vcl_type(std::forward<Arg>(arg)...),
+ mrDeleted(bDeleted)
+ {
+ mrDeleted = false;
+ }
+ ~LeakTestClass()
+ {
+ mrDeleted = true;
+ }
+};
+
+class LeakTestObject
+{
+ bool mbDeleted;
+ VclPtr<vcl::Window> mxRef;
+ void *mpRef;
+ LeakTestObject()
+ : mbDeleted(false)
+ , mpRef(nullptr)
+ {
+ }
+public:
+ template<typename vcl_type, typename... Arg> static LeakTestObject *
+ Create(Arg &&... arg)
+ {
+ LeakTestObject *pNew = new LeakTestObject();
+ pNew->mxRef = VclPtr< LeakTestClass< vcl_type > >::Create( pNew->mbDeleted,
+ std::forward<Arg>(arg)...);
+ pNew->mpRef = static_cast<void *>(static_cast<vcl::Window *>(pNew->mxRef));
+ return pNew;
+ }
+ const VclPtr<vcl::Window>& getRef() const { return mxRef; }
+ void disposeAndClear()
+ {
+ mxRef.disposeAndClear();
+ }
+ void assertDeleted()
+ {
+ if (!mbDeleted)
+ {
+ OUStringBuffer aMsg = "Type '";
+ vcl::Window *pWin = static_cast<vcl::Window *>(mpRef);
+ aMsg.appendAscii(typeid(*pWin).name());
+ aMsg.append("' not freed after dispose");
+ CPPUNIT_FAIL(OUStringToOString(aMsg.makeStringAndClear(),
+ RTL_TEXTENCODING_UTF8).getStr());
+ }
+ }
+};
+
+}
+
+void LifecycleTest::testLeakage()
+{
+ std::vector<LeakTestObject *> aObjects;
+
+ // Create objects
+ aObjects.push_back(LeakTestObject::Create<WorkWindow>(nullptr, WB_APP|WB_STDWORK));
+ VclPtr<vcl::Window> xParent = aObjects.back()->getRef();
+
+ aObjects.push_back(LeakTestObject::Create<PushButton>(xParent));
+ aObjects.push_back(LeakTestObject::Create<CheckBox>(xParent));
+ aObjects.push_back(LeakTestObject::Create<Edit>(xParent));
+ aObjects.push_back(LeakTestObject::Create<ComboBox>(xParent));
+ aObjects.push_back(LeakTestObject::Create<RadioButton>(xParent, true, 0));
+
+ { // something that looks like a dialog
+ aObjects.push_back(LeakTestObject::Create<Dialog>(xParent,WB_CLIPCHILDREN|WB_MOVEABLE|WB_3DLOOK|WB_CLOSEABLE|WB_SIZEABLE));
+ VclPtr<vcl::Window> xDlgParent = aObjects.back()->getRef();
+ aObjects.push_back(LeakTestObject::Create<VclVBox>(xDlgParent));
+ VclPtr<vcl::Window> xVBox = aObjects.back()->getRef();
+ aObjects.push_back(LeakTestObject::Create<VclVButtonBox>(xVBox));
+ }
+
+ aObjects.push_back(LeakTestObject::Create<Dialog>(xParent, u"PrintProgressDialog"_ustr, "vcl/ui/printprogressdialog.ui"));
+ xParent.clear();
+
+ for (auto i = aObjects.rbegin(); i != aObjects.rend(); ++i)
+ (*i)->getRef()->Show();
+
+ for (auto i = aObjects.rbegin(); i != aObjects.rend(); ++i)
+ (*i)->disposeAndClear();
+
+ for (auto i = aObjects.begin(); i != aObjects.end(); ++i)
+ (*i)->assertDeleted();
+
+ for (auto i = aObjects.begin(); i != aObjects.end(); ++i)
+ delete *i;
+}
+
+void LifecycleTest::testToolkit()
+{
+ LeakTestObject *pVclWin = LeakTestObject::Create<WorkWindow>(nullptr, WB_APP|WB_STDWORK);
+ css::uno::Reference<css::awt::XWindow> xWindow(pVclWin->getRef()->GetComponentInterface(), css::uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xWindow.is());
+
+ // test UNO dispose
+ css::uno::Reference<css::lang::XComponent> xWinComponent = xWindow;
+ CPPUNIT_ASSERT(xWinComponent.is());
+ CPPUNIT_ASSERT(!pVclWin->getRef()->isDisposed());
+ xWinComponent->dispose();
+ CPPUNIT_ASSERT(pVclWin->getRef()->isDisposed());
+
+ // test UNO cleanup
+ xWinComponent.clear();
+ xWindow.clear();
+ pVclWin->disposeAndClear();
+ pVclWin->assertDeleted();
+
+ delete pVclWin;
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(LifecycleTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/logicalfontinstance.cxx b/vcl/qa/cppunit/logicalfontinstance.cxx
new file mode 100644
index 0000000000..6d5bbc4daf
--- /dev/null
+++ b/vcl/qa/cppunit/logicalfontinstance.cxx
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+#include <cppunit/TestAssert.h>
+
+#include <vcl/virdev.hxx>
+#include <vcl/font.hxx>
+
+#include <font/LogicalFontInstance.hxx>
+
+#include <memory>
+
+class VclLogicalFontInstanceTest : public test::BootstrapFixture
+{
+public:
+ VclLogicalFontInstanceTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ void testglyphboundrect();
+
+ CPPUNIT_TEST_SUITE(VclLogicalFontInstanceTest);
+ CPPUNIT_TEST(testglyphboundrect);
+
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void VclLogicalFontInstanceTest::testglyphboundrect()
+{
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(1000, 1000));
+ device->SetFont(vcl::Font("Liberation Sans", Size(0, 110)));
+
+ const LogicalFontInstance* pFontInstance = device->GetFontInstance();
+
+ tools::Rectangle aBoundRect;
+ const auto LATIN_SMALL_LETTER_B = 0x0062;
+ pFontInstance->GetGlyphBoundRect(pFontInstance->GetGlyphIndex(LATIN_SMALL_LETTER_B), aBoundRect,
+ false);
+
+ const tools::Long nExpectedX = 7;
+ const tools::Long nExpectedY = -80;
+ const tools::Long nExpectedWidth = 51;
+ const tools::Long nExpectedHeight = 83;
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("x of glyph is wrong", nExpectedX, aBoundRect.getX());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("y of glyph is wrong", nExpectedY, aBoundRect.getY());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("height of glyph of wrong", nExpectedWidth, aBoundRect.GetWidth());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("width of glyph of wrong", nExpectedHeight,
+ aBoundRect.GetHeight());
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclLogicalFontInstanceTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/qa/cppunit/mnemonic.cxx b/vcl/qa/cppunit/mnemonic.cxx
new file mode 100644
index 0000000000..9b6ce7d073
--- /dev/null
+++ b/vcl/qa/cppunit/mnemonic.cxx
@@ -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/.
+ */
+
+#include <sal/config.h>
+
+#include <o3tl/cppunittraitshelper.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <cppunit/TestAssert.h>
+
+#include <vcl/mnemonic.hxx>
+
+class VclMnemonicTest : public test::BootstrapFixture
+{
+public:
+ VclMnemonicTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ void testMnemonic();
+ void testRemoveMnemonicFromString();
+ void testRemoveDoubleMarkedMnemonicFromString();
+ void testRemoveMultipleMnemonicsFromString();
+ void testRemoveDoubleMarkingsThenMnemonicFromString();
+ void testRemoveMnemonicThenDoubleMarkingsFromString();
+ void testRemoveMnemonicFromEndOfString();
+ void testRemoveNoMnemonicFromString();
+
+ CPPUNIT_TEST_SUITE(VclMnemonicTest);
+ CPPUNIT_TEST(testMnemonic);
+ CPPUNIT_TEST(testRemoveMnemonicFromString);
+ CPPUNIT_TEST(testRemoveDoubleMarkedMnemonicFromString);
+ CPPUNIT_TEST(testRemoveMultipleMnemonicsFromString);
+ CPPUNIT_TEST(testRemoveDoubleMarkingsThenMnemonicFromString);
+ CPPUNIT_TEST(testRemoveMnemonicThenDoubleMarkingsFromString);
+ CPPUNIT_TEST(testRemoveMnemonicFromEndOfString);
+ CPPUNIT_TEST(testRemoveNoMnemonicFromString);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void VclMnemonicTest::testMnemonic()
+{
+ MnemonicGenerator aGenerator;
+
+ {
+ OUString sResult = aGenerator.CreateMnemonic(u"ßa"_ustr);
+ CPPUNIT_ASSERT_EQUAL(u'~', sResult[1]);
+ }
+
+ {
+ static constexpr OUStringLiteral TEST = u"\u4E00b";
+ OUString sResult = aGenerator.CreateMnemonic(TEST);
+ CPPUNIT_ASSERT_EQUAL(u'~', sResult[1]);
+ }
+
+ {
+ static constexpr OUString TEST = u"\u4E00"_ustr;
+ OUString sResult = aGenerator.CreateMnemonic(TEST);
+ CPPUNIT_ASSERT_EQUAL(OUString("(~C)"), sResult.copy(sResult.getLength() - 4));
+ sResult = MnemonicGenerator::EraseAllMnemonicChars(sResult);
+ CPPUNIT_ASSERT_EQUAL(TEST, sResult);
+ }
+}
+
+void VclMnemonicTest::testRemoveMnemonicFromString()
+{
+ sal_Int32 nMnemonicIndex;
+ OUString sNonMnemonicString = removeMnemonicFromString("this is a ~test", nMnemonicIndex);
+ CPPUNIT_ASSERT_EQUAL(OUString("this is a test"), sNonMnemonicString);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(10), nMnemonicIndex);
+}
+
+void VclMnemonicTest::testRemoveDoubleMarkedMnemonicFromString()
+{
+ sal_Int32 nMnemonicIndex;
+ OUString sNonMnemonicString = removeMnemonicFromString("this ~~is a test", nMnemonicIndex);
+ CPPUNIT_ASSERT_EQUAL(OUString("this ~is a test"), sNonMnemonicString);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1), nMnemonicIndex);
+}
+
+void VclMnemonicTest::testRemoveMultipleMnemonicsFromString()
+{
+ sal_Int32 nMnemonicIndex;
+ OUString sNonMnemonicString = removeMnemonicFromString("t~his is a ~test", nMnemonicIndex);
+ CPPUNIT_ASSERT_EQUAL(OUString("this is a test"), sNonMnemonicString);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), nMnemonicIndex);
+}
+
+void VclMnemonicTest::testRemoveDoubleMarkingsThenMnemonicFromString()
+{
+ sal_Int32 nMnemonicIndex;
+ OUString sNonMnemonicString = removeMnemonicFromString("t~~his is a ~test", nMnemonicIndex);
+ CPPUNIT_ASSERT_EQUAL(OUString("t~his is a test"), sNonMnemonicString);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(11), nMnemonicIndex);
+}
+
+void VclMnemonicTest::testRemoveMnemonicThenDoubleMarkingsFromString()
+{
+ sal_Int32 nMnemonicIndex;
+ OUString sNonMnemonicString = removeMnemonicFromString("t~his is a ~~test", nMnemonicIndex);
+ CPPUNIT_ASSERT_EQUAL(OUString("this is a ~test"), sNonMnemonicString);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), nMnemonicIndex);
+}
+
+void VclMnemonicTest::testRemoveMnemonicFromEndOfString()
+{
+ sal_Int32 nMnemonicIndex;
+ OUString sNonMnemonicString = removeMnemonicFromString("this is a test~", nMnemonicIndex);
+ CPPUNIT_ASSERT_EQUAL(OUString("this is a test~"), sNonMnemonicString);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1), nMnemonicIndex);
+}
+
+void VclMnemonicTest::testRemoveNoMnemonicFromString()
+{
+ sal_Int32 nMnemonicIndex;
+ OUString sNonMnemonicString = removeMnemonicFromString("this is a test", nMnemonicIndex);
+ CPPUNIT_ASSERT_EQUAL(OUString("this is a test"), sNonMnemonicString);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1), nMnemonicIndex);
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclMnemonicTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/outdev.cxx b/vcl/qa/cppunit/outdev.cxx
new file mode 100644
index 0000000000..12f1dea4e5
--- /dev/null
+++ b/vcl/qa/cppunit/outdev.cxx
@@ -0,0 +1,2081 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+#include <test/outputdevice.hxx>
+
+#include <sal/log.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/vector/b2enums.hxx>
+
+#include <vcl/gradient.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/print.hxx>
+#include <vcl/rendercontext/RasterOp.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bufferdevice.hxx>
+#include <window.h>
+
+const size_t INITIAL_SETUP_ACTION_COUNT = 5;
+
+class VclOutdevTest : public test::BootstrapFixture
+{
+public:
+ VclOutdevTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+};
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testGetReadableFontColorPrinter)
+{
+ ScopedVclPtrInstance<Printer> pPrinter;
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, pPrinter->GetReadableFontColor(COL_WHITE, COL_WHITE));
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testGetReadableFontColorWindow)
+{
+ ScopedVclPtrInstance<vcl::Window> pWindow(nullptr, WB_APP | WB_STDWORK);
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE,
+ pWindow->GetOutDev()->GetReadableFontColor(COL_WHITE, COL_BLACK));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK,
+ pWindow->GetOutDev()->GetReadableFontColor(COL_WHITE, COL_WHITE));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE,
+ pWindow->GetOutDev()->GetReadableFontColor(COL_BLACK, COL_BLACK));
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testPrinterBackgroundColor)
+{
+ ScopedVclPtrInstance<Printer> pPrinter;
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, pPrinter->GetBackgroundColor());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testWindowBackgroundColor)
+{
+ ScopedVclPtrInstance<vcl::Window> pWindow(nullptr, WB_APP | WB_STDWORK);
+ pWindow->SetBackground(Wallpaper(COL_WHITE));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, pWindow->GetBackgroundColor());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testVirtualDevice)
+{
+// TODO: This unit test is not executed for macOS unless bitmap scaling is implemented
+#ifndef MACOSX
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ pVDev->SetOutputSizePixel(Size(32, 32));
+ pVDev->SetBackground(Wallpaper(COL_WHITE));
+
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, pVDev->GetBackgroundColor());
+
+ pVDev->Erase();
+ pVDev->DrawPixel(Point(1, 2), COL_BLUE);
+ pVDev->DrawPixel(Point(31, 30), COL_RED);
+
+ Size aSize = pVDev->GetOutputSizePixel();
+ CPPUNIT_ASSERT_EQUAL(Size(32, 32), aSize);
+
+ Bitmap aBmp = pVDev->GetBitmap(Point(), aSize);
+
+#if 0
+ OUString rFileName("/tmp/foo-unx.png");
+ try {
+ vcl::PNGWriter aWriter( aBmp );
+ SvFileStream sOutput( rFileName, StreamMode::WRITE );
+ aWriter.Write( sOutput );
+ sOutput.Close();
+ } catch (...) {
+ SAL_WARN("vcl", "Error writing png to " << rFileName);
+ }
+#endif
+
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, pVDev->GetPixel(Point(0, 0)));
+#if !defined _WIN32 //TODO: various failures on Windows tinderboxes
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, pVDev->GetPixel(Point(1, 2)));
+ CPPUNIT_ASSERT_EQUAL(COL_RED, pVDev->GetPixel(Point(31, 30)));
+#endif
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, pVDev->GetPixel(Point(30, 31)));
+
+ // Gotcha: y and x swap for BitmapReadAccess: deep joy.
+ BitmapScopedReadAccess pAcc(aBmp);
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, static_cast<Color>(pAcc->GetPixel(0, 0)));
+#if !defined _WIN32 //TODO: various failures on Windows tinderboxes
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<Color>(pAcc->GetPixel(2, 1)));
+ CPPUNIT_ASSERT_EQUAL(COL_RED, static_cast<Color>(pAcc->GetPixel(30, 31)));
+#endif
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, static_cast<Color>(pAcc->GetPixel(31, 30)));
+
+#if 0
+ VclPtr<vcl::Window> pWin = VclPtr<WorkWindow>::Create( (vcl::Window *)nullptr );
+ CPPUNIT_ASSERT( pWin );
+ OutputDevice *pOutDev = pWin.get();
+#endif
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testUseAfterDispose)
+{
+ // Create a virtual device, enable map mode then dispose it.
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ pVDev->EnableMapMode();
+
+ pVDev->disposeOnce();
+
+ // Make sure that these don't crash after dispose.
+ pVDev->GetInverseViewTransformation();
+
+ pVDev->GetViewTransformation();
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawInvertedBitmap)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetRasterOp(RasterOp::Invert);
+ pVDev->DrawBitmap(Point(0, 0), Size(10, 10), Point(0, 0), Size(10, 10), aBitmap,
+ MetaActionType::BMP);
+
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::RASTEROP, pAction->GetType());
+ auto pRasterOpAction = static_cast<MetaRasterOpAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(RasterOp::Invert, pRasterOpAction->GetRasterOp());
+
+ pAction = aMtf.GetAction(1);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::RECT, pAction->GetType());
+ auto pRectAction = static_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(Point(0, 0), Size(10, 10)), pRectAction->GetRect());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawBlackBitmap)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);
+ aBitmap.Erase(COL_RED);
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetDrawMode(DrawModeFlags::BlackBitmap);
+ pVDev->DrawBitmap(Point(0, 0), Size(10, 10), Point(0, 0), Size(10, 10), aBitmap,
+ MetaActionType::BMP);
+
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::PUSH, pAction->GetType());
+ auto pPushAction = static_cast<MetaPushAction*>(pAction);
+ bool bLineFillFlag
+ = ((vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR) == pPushAction->GetFlags());
+ CPPUNIT_ASSERT_MESSAGE("Push flags not LINECOLOR | FILLCOLOR", bLineFillFlag);
+
+ pAction = aMtf.GetAction(1);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::LINECOLOR, pAction->GetType());
+ auto pLineColorAction = static_cast<MetaLineColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, pLineColorAction->GetColor());
+
+ pAction = aMtf.GetAction(2);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::FILLCOLOR, pAction->GetType());
+ auto pFillColorAction = static_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, pFillColorAction->GetColor());
+
+ pAction = aMtf.GetAction(3);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::RECT, pAction->GetType());
+ auto pRectAction = static_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(Point(0, 0), Size(10, 10)), pRectAction->GetRect());
+
+ pAction = aMtf.GetAction(4);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::POP, pAction->GetType());
+
+ // test to see if the color is black
+ Bitmap aBlackBmp(pVDev->GetBitmap(Point(0, 0), Size(10, 10)));
+ BitmapScopedReadAccess pReadAccess(aBlackBmp);
+ const BitmapColor& rColor = pReadAccess->GetColor(0, 0);
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_BLACK), rColor);
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawWhiteBitmap)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetDrawMode(DrawModeFlags::WhiteBitmap);
+ pVDev->DrawBitmap(Point(0, 0), Size(10, 10), Point(0, 0), Size(10, 10), aBitmap,
+ MetaActionType::BMP);
+
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::PUSH, pAction->GetType());
+ auto pPushAction = static_cast<MetaPushAction*>(pAction);
+ bool bLineFillFlag
+ = ((vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR) == pPushAction->GetFlags());
+ CPPUNIT_ASSERT_MESSAGE("Push flags not LINECOLOR | FILLCOLOR", bLineFillFlag);
+
+ pAction = aMtf.GetAction(1);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::LINECOLOR, pAction->GetType());
+ auto pLineColorAction = static_cast<MetaLineColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, pLineColorAction->GetColor());
+
+ pAction = aMtf.GetAction(2);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::FILLCOLOR, pAction->GetType());
+ auto pFillColorAction = static_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, pFillColorAction->GetColor());
+
+ pAction = aMtf.GetAction(3);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::RECT, pAction->GetType());
+ auto pRectAction = static_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(Point(0, 0), Size(10, 10)), pRectAction->GetRect());
+
+ pAction = aMtf.GetAction(4);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::POP, pAction->GetType());
+
+ // test to see if the color is white
+ Bitmap aWhiteBmp(pVDev->GetBitmap(Point(0, 0), Size(10, 10)));
+ BitmapScopedReadAccess pReadAccess(aWhiteBmp);
+ const BitmapColor& rColor = pReadAccess->GetColor(0, 0);
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_WHITE), rColor);
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawBitmap)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->DrawBitmap(Point(0, 0), Size(10, 10), Point(0, 0), Size(10, 10), aBitmap,
+ MetaActionType::BMP);
+
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::BMP, pAction->GetType());
+ auto pBmpAction = static_cast<MetaBmpAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(Size(16, 16), pBmpAction->GetBitmap().GetSizePixel());
+ CPPUNIT_ASSERT_EQUAL(Point(0, 0), pBmpAction->GetPoint());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawScaleBitmap)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->DrawBitmap(Point(5, 5), Size(10, 10), Point(0, 0), Size(10, 10), aBitmap,
+ MetaActionType::BMPSCALE);
+
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::BMPSCALE, pAction->GetType());
+ auto pBmpScaleAction = static_cast<MetaBmpScaleAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(Size(16, 16), pBmpScaleAction->GetBitmap().GetSizePixel());
+ CPPUNIT_ASSERT_EQUAL(Point(5, 5), pBmpScaleAction->GetPoint());
+ CPPUNIT_ASSERT_EQUAL(Size(10, 10), pBmpScaleAction->GetSize());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawScalePartBitmap)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->DrawBitmap(Point(0, 0), Size(10, 10), Point(5, 5), Size(10, 10), aBitmap,
+ MetaActionType::BMPSCALEPART);
+
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::BMPSCALEPART, pAction->GetType());
+ auto pBmpScalePartAction = static_cast<MetaBmpScalePartAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(Size(16, 16), pBmpScalePartAction->GetBitmap().GetSizePixel());
+ CPPUNIT_ASSERT_EQUAL(Point(5, 5), pBmpScalePartAction->GetSrcPoint());
+ CPPUNIT_ASSERT_EQUAL(Size(10, 10), pBmpScalePartAction->GetSrcSize());
+ CPPUNIT_ASSERT_EQUAL(Point(0, 0), pBmpScalePartAction->GetDestPoint());
+ CPPUNIT_ASSERT_EQUAL(Size(10, 10), pBmpScalePartAction->GetDestSize());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawGrayBitmap)
+{
+ // draw a red 1x1 bitmap
+ Bitmap aBmp(Size(1, 1), vcl::PixelFormat::N24_BPP);
+ aBmp.Erase(COL_RED);
+
+ // check to ensure that the bitmap is red
+ {
+ BitmapScopedReadAccess pReadAccess(aBmp);
+ const BitmapColor& rColor = pReadAccess->GetColor(0, 0);
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_RED), rColor);
+ }
+
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ pVDev->SetDrawMode(DrawModeFlags::GrayBitmap);
+ pVDev->DrawBitmap(Point(0, 0), Size(1, 1), Point(0, 0), Size(1, 1), aBmp, MetaActionType::BMP);
+
+ // should be a grey
+ Bitmap aVDevBmp(pVDev->GetBitmap(Point(), Size(1, 1)));
+ {
+ BitmapScopedReadAccess pReadAccess(aVDevBmp);
+ const BitmapColor& rColor = pReadAccess->GetColor(0, 0);
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(0x26, 0x26, 0x26), rColor);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawTransformedBitmapEx)
+{
+ // Create a virtual device, and connect a metafile to it.
+ // Also create a 16x16 bitmap.
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);
+ {
+ // Fill the top left quarter with black.
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(COL_WHITE);
+ for (int i = 0; i < 8; ++i)
+ {
+ for (int j = 0; j < 8; ++j)
+ {
+ pWriteAccess->SetPixel(j, i, COL_BLACK);
+ }
+ }
+ }
+ BitmapEx aBitmapEx(aBitmap);
+ basegfx::B2DHomMatrix aMatrix;
+ aMatrix.scale(8, 8);
+ // Rotate 90 degrees clockwise, so the black part goes to the top right.
+ aMatrix.rotate(M_PI / 2);
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ // Draw the rotated bitmap on the vdev.
+ pVDev->DrawTransformedBitmapEx(aMatrix, aBitmapEx);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aMtf.GetActionSize());
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::BMPEXSCALE, pAction->GetType());
+ auto pBitmapAction = static_cast<MetaBmpExScaleAction*>(pAction);
+ const BitmapEx& rBitmapEx = pBitmapAction->GetBitmapEx();
+ Size aTransformedSize = rBitmapEx.GetSizePixel();
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 16x16
+ // - Actual : 8x8
+ // I.e. the bitmap before scaling was already scaled down, just because it was rotated.
+ CPPUNIT_ASSERT_EQUAL(Size(16, 16), aTransformedSize);
+
+ aBitmap = rBitmapEx.GetBitmap();
+ BitmapScopedReadAccess pAccess(aBitmap);
+ for (int i = 0; i < 16; ++i)
+ {
+ for (int j = 0; j < 16; ++j)
+ {
+ BitmapColor aColor = pAccess->GetPixel(j, i);
+ Color aExpected = i >= 8 && j < 8 ? COL_BLACK : COL_WHITE;
+ std::stringstream ss;
+ ss << "Color is expected to be ";
+ ss << ((aExpected == COL_WHITE) ? "white" : "black");
+ ss << ", is " << aColor.AsRGBHexString();
+ ss << " (row " << j << ", col " << i << ")";
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: c[00000000]
+ // - Actual : c[ffffff00]
+ // - Color is expected to be black, is ffffff (row 0, col 8)
+ // i.e. the top right quarter of the image was not fully black, there was a white first
+ // row.
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(ss.str(), aExpected, Color(aColor));
+ }
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawTransformedBitmapExFlip)
+{
+ // Create a virtual device, and connect a metafile to it.
+ // Also create a 16x16 bitmap.
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);
+ {
+ // Fill the top left quarter with black.
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(COL_WHITE);
+ for (int i = 0; i < 8; ++i)
+ {
+ for (int j = 0; j < 8; ++j)
+ {
+ pWriteAccess->SetPixel(j, i, COL_BLACK);
+ }
+ }
+ }
+ BitmapEx aBitmapEx(aBitmap);
+ basegfx::B2DHomMatrix aMatrix;
+ // Negative y scale: bitmap should be upside down, so the black part goes to the bottom left.
+ aMatrix.scale(8, -8);
+ // Rotate 90 degrees clockwise, so the black part goes back to the top left.
+ aMatrix.rotate(M_PI / 2);
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ // Draw the scaled and rotated bitmap on the vdev.
+ pVDev->DrawTransformedBitmapEx(aMatrix, aBitmapEx);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aMtf.GetActionSize());
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::BMPEXSCALE, pAction->GetType());
+ auto pBitmapAction = static_cast<MetaBmpExScaleAction*>(pAction);
+ const BitmapEx& rBitmapEx = pBitmapAction->GetBitmapEx();
+
+ aBitmap = rBitmapEx.GetBitmap();
+ BitmapScopedReadAccess pAccess(aBitmap);
+ int nX = 8 * 0.25;
+ int nY = 8 * 0.25;
+ BitmapColor aColor = pAccess->GetPixel(nY, nX);
+ std::stringstream ss;
+ ss << "Color is expected to be black, is " << aColor.AsRGBHexString();
+ ss << " (row " << nY << ", col " << nX << ")";
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: c[00000000]
+ // - Actual : c[ffffff00]
+ // - Color is expected to be black, is ffffff (row 2, col 2)
+ // i.e. the top left quarter of the image was not black, due to a missing flip.
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(ss.str(), COL_BLACK, Color(aColor));
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testRTL)
+{
+ ScopedVclPtrInstance<vcl::Window> pWindow(nullptr, WB_APP | WB_STDWORK);
+ pWindow->EnableRTL();
+ vcl::RenderContext& rRenderContext = *pWindow->GetOutDev();
+ vcl::BufferDevice pBuffer(pWindow, rRenderContext);
+
+ // Without the accompanying fix in place, this test would have failed, because the RTL status
+ // from pWindow was not propagated to pBuffer.
+ CPPUNIT_ASSERT(pBuffer->IsRTLEnabled());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testRTLGuard)
+{
+ ScopedVclPtrInstance<vcl::Window> pWindow(nullptr, WB_APP | WB_STDWORK);
+ pWindow->EnableRTL();
+ pWindow->RequestDoubleBuffering(true);
+ ImplFrameData* pFrameData = pWindow->ImplGetWindowImpl()->mpFrameData;
+ vcl::PaintBufferGuard aGuard(pFrameData, pWindow);
+ // Without the accompanying fix in place, this test would have failed, because the RTL status
+ // from pWindow was not propagated to aGuard.
+ CPPUNIT_ASSERT(aGuard.GetRenderContext()->IsRTLEnabled());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDefaultFillColor)
+{
+ // Create a virtual device, and connect a metafile to it.
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ CPPUNIT_ASSERT(pVDev->IsFillColor());
+ CPPUNIT_ASSERT_EQUAL(Color(0xFF, 0xFF, 0xFF), pVDev->GetFillColor());
+
+ pVDev->SetFillColor();
+ CPPUNIT_ASSERT(!pVDev->IsFillColor());
+ CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, pVDev->GetFillColor());
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::FILLCOLOR, pAction->GetType());
+ auto pFillAction = static_cast<MetaFillColorAction*>(pAction);
+ const Color& rColor = pFillAction->GetColor();
+ CPPUNIT_ASSERT_EQUAL(Color(), rColor);
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testTransparentFillColor)
+{
+ // Create a virtual device, and connect a metafile to it.
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ CPPUNIT_ASSERT(pVDev->IsFillColor());
+ CPPUNIT_ASSERT_EQUAL(Color(0xFF, 0xFF, 0xFF), pVDev->GetFillColor());
+
+ pVDev->SetFillColor(COL_TRANSPARENT);
+ CPPUNIT_ASSERT(!pVDev->IsFillColor());
+ CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, pVDev->GetFillColor());
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::FILLCOLOR, pAction->GetType());
+ auto pFillAction = static_cast<MetaFillColorAction*>(pAction);
+ const Color& rColor = pFillAction->GetColor();
+ CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, rColor);
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testFillColor)
+{
+ // Create a virtual device, and connect a metafile to it.
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ CPPUNIT_ASSERT(pVDev->IsFillColor());
+ CPPUNIT_ASSERT_EQUAL(Color(0xFF, 0xFF, 0xFF), pVDev->GetFillColor());
+
+ pVDev->SetFillColor(COL_RED);
+ CPPUNIT_ASSERT(pVDev->IsFillColor());
+ CPPUNIT_ASSERT_EQUAL(COL_RED, pVDev->GetFillColor());
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::FILLCOLOR, pAction->GetType());
+ auto pFillAction = static_cast<MetaFillColorAction*>(pAction);
+ const Color& rColor = pFillAction->GetColor();
+ CPPUNIT_ASSERT_EQUAL(COL_RED, rColor);
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDefaultLineColor)
+{
+ // Create a virtual device, and connect a metafile to it.
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ CPPUNIT_ASSERT(pVDev->IsLineColor());
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, pVDev->GetLineColor());
+
+ pVDev->SetLineColor();
+ CPPUNIT_ASSERT(!pVDev->IsLineColor());
+ CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, pVDev->GetLineColor());
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::LINECOLOR, pAction->GetType());
+ auto pLineAction = static_cast<MetaLineColorAction*>(pAction);
+ const Color& rColor = pLineAction->GetColor();
+ CPPUNIT_ASSERT_EQUAL(Color(), rColor);
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testTransparentLineColor)
+{
+ // Create a virtual device, and connect a metafile to it.
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ CPPUNIT_ASSERT(pVDev->IsLineColor());
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, pVDev->GetLineColor());
+
+ pVDev->SetLineColor(COL_TRANSPARENT);
+ CPPUNIT_ASSERT(!pVDev->IsLineColor());
+ CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, pVDev->GetLineColor());
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::LINECOLOR, pAction->GetType());
+ auto pLineAction = static_cast<MetaLineColorAction*>(pAction);
+ const Color& rColor = pLineAction->GetColor();
+ CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, rColor);
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testLineColor)
+{
+ // Create a virtual device, and connect a metafile to it.
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ CPPUNIT_ASSERT(pVDev->IsLineColor());
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, pVDev->GetLineColor());
+
+ pVDev->SetLineColor(COL_RED);
+ CPPUNIT_ASSERT(pVDev->IsLineColor());
+ CPPUNIT_ASSERT_EQUAL(COL_RED, pVDev->GetLineColor());
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::LINECOLOR, pAction->GetType());
+ auto pLineAction = static_cast<MetaLineColorAction*>(pAction);
+ const Color& rColor = pLineAction->GetColor();
+ CPPUNIT_ASSERT_EQUAL(COL_RED, rColor);
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testFont)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ // Use Dejavu fonts, they are shipped with LO, so they should be ~always available.
+ // Use Sans variant for simpler glyph shapes (no serifs).
+ vcl::Font font("DejaVu Sans", "Book", Size(0, 36));
+ font.SetColor(COL_BLACK);
+ font.SetFillColor(COL_RED);
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetFont(font);
+ bool bSameFont(font == pVDev->GetFont());
+ CPPUNIT_ASSERT_MESSAGE("Font is not the same", bSameFont);
+
+ // four actions:
+ // 1. Font action
+ // 2. Text alignment action
+ // 3. Text fill color action
+ // 4. As not COL_TRANSPARENT (means use system font color), font color action
+ size_t nActionsExpected = 4;
+ CPPUNIT_ASSERT_EQUAL(nActionsExpected, aMtf.GetActionSize());
+
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::FONT, pAction->GetType());
+ auto pFontAction = static_cast<MetaFontAction*>(pAction);
+ bool bSameMetaFont = (font == pFontAction->GetFont());
+ CPPUNIT_ASSERT_MESSAGE("Metafile font is not the same", bSameMetaFont);
+
+ pAction = aMtf.GetAction(1);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::TEXTALIGN, pAction->GetType());
+ auto pTextAlignAction = static_cast<MetaTextAlignAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(font.GetAlignment(), pTextAlignAction->GetTextAlign());
+
+ pAction = aMtf.GetAction(2);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::TEXTFILLCOLOR, pAction->GetType());
+ auto pTextFillColorAction = static_cast<MetaTextFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(COL_RED, pTextFillColorAction->GetColor());
+
+ pAction = aMtf.GetAction(3);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::TEXTCOLOR, pAction->GetType());
+ auto pTextColorAction = static_cast<MetaTextColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, pTextColorAction->GetColor());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testTransparentFont)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ // Use Dejavu fonts, they are shipped with LO, so they should be ~always available.
+ // Use Sans variant for simpler glyph shapes (no serifs).
+ vcl::Font font("DejaVu Sans", "Book", Size(0, 36));
+ font.SetColor(COL_TRANSPARENT);
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetFont(font);
+
+ // three actions as it sets the colour to the default system color (and doesn't add a text color action):
+ // 1. Font action
+ // 2. Text alignment action
+ // 3. Text fill color action
+ size_t nActionsExpected = 3;
+ CPPUNIT_ASSERT_EQUAL(nActionsExpected, aMtf.GetActionSize());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDefaultRefPoint)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetRefPoint();
+
+ CPPUNIT_ASSERT(!pVDev->IsRefPoint());
+ CPPUNIT_ASSERT_EQUAL(Point(), pVDev->GetRefPoint());
+
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::REFPOINT, pAction->GetType());
+ auto pRefPointAction = static_cast<MetaRefPointAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(Point(), pRefPointAction->GetRefPoint());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testRefPoint)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetRefPoint(Point(10, 20));
+
+ CPPUNIT_ASSERT(pVDev->IsRefPoint());
+ CPPUNIT_ASSERT_EQUAL(Point(10, 20), pVDev->GetRefPoint());
+
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::REFPOINT, pAction->GetType());
+ auto pRefPointAction = static_cast<MetaRefPointAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(Point(10, 20), pRefPointAction->GetRefPoint());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testRasterOp)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetRasterOp(RasterOp::Invert);
+
+ CPPUNIT_ASSERT_EQUAL(RasterOp::Invert, pVDev->GetRasterOp());
+ CPPUNIT_ASSERT(pVDev->IsLineColor());
+ CPPUNIT_ASSERT(pVDev->IsFillColor());
+
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::RASTEROP, pAction->GetType());
+ auto pRasterOpAction = static_cast<MetaRasterOpAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(RasterOp::Invert, pRasterOpAction->GetRasterOp());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testOutputFlag)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ CPPUNIT_ASSERT(pVDev->IsOutputEnabled());
+ CPPUNIT_ASSERT(pVDev->IsDeviceOutputNecessary());
+
+ pVDev->EnableOutput(false);
+
+ CPPUNIT_ASSERT(!pVDev->IsOutputEnabled());
+ CPPUNIT_ASSERT(!pVDev->IsDeviceOutputNecessary());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testAntialias)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ CPPUNIT_ASSERT_EQUAL(AntialiasingFlags::NONE, pVDev->GetAntialiasing());
+
+ pVDev->SetAntialiasing(AntialiasingFlags::Enable);
+
+ CPPUNIT_ASSERT_EQUAL(AntialiasingFlags::Enable, pVDev->GetAntialiasing());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawMode)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ CPPUNIT_ASSERT_EQUAL(DrawModeFlags::Default, pVDev->GetDrawMode());
+
+ pVDev->SetDrawMode(DrawModeFlags::BlackLine);
+
+ CPPUNIT_ASSERT_EQUAL(DrawModeFlags::BlackLine, pVDev->GetDrawMode());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testLayoutMode)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ CPPUNIT_ASSERT_EQUAL(vcl::text::ComplexTextLayoutFlags::Default, pVDev->GetLayoutMode());
+
+ pVDev->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiRtl);
+
+ CPPUNIT_ASSERT_EQUAL(vcl::text::ComplexTextLayoutFlags::BiDiRtl, pVDev->GetLayoutMode());
+
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::LAYOUTMODE, pAction->GetType());
+ auto pLayoutModeAction = static_cast<MetaLayoutModeAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(vcl::text::ComplexTextLayoutFlags::BiDiRtl,
+ pLayoutModeAction->GetLayoutMode());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDigitLanguage)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ CPPUNIT_ASSERT_EQUAL(LANGUAGE_SYSTEM, pVDev->GetDigitLanguage());
+
+ pVDev->SetDigitLanguage(LANGUAGE_GERMAN);
+
+ CPPUNIT_ASSERT_EQUAL(LANGUAGE_GERMAN, pVDev->GetDigitLanguage());
+
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL(MetaActionType::TEXTLANGUAGE, pAction->GetType());
+ auto pTextLanguageAction = static_cast<MetaTextLanguageAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL(LANGUAGE_GERMAN, pTextLanguageAction->GetTextLanguage());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testStackFunctions)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->Push();
+ MetaAction* pAction = aMtf.GetAction(0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Push action", MetaActionType::PUSH, pAction->GetType());
+
+ pVDev->SetLineColor(COL_RED);
+ pVDev->SetFillColor(COL_GREEN);
+ pVDev->SetTextColor(COL_BROWN);
+ pVDev->SetTextFillColor(COL_BLUE);
+ pVDev->SetTextLineColor(COL_MAGENTA);
+ pVDev->SetOverlineColor(COL_YELLOW);
+ pVDev->SetTextAlign(TextAlign::ALIGN_TOP);
+ pVDev->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiRtl);
+ pVDev->SetDigitLanguage(LANGUAGE_FRENCH);
+ pVDev->SetRasterOp(RasterOp::N0);
+ pVDev->SetMapMode(MapMode(MapUnit::MapTwip));
+ pVDev->SetRefPoint(Point(10, 10));
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Text color", COL_BROWN, pVDev->GetTextColor());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Text fill color", COL_BLUE, pVDev->GetTextFillColor());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Text line color", COL_MAGENTA, pVDev->GetTextLineColor());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Text overline color", COL_YELLOW, pVDev->GetOverlineColor());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Layout mode", vcl::text::ComplexTextLayoutFlags::BiDiRtl,
+ pVDev->GetLayoutMode());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Language", LANGUAGE_FRENCH, pVDev->GetDigitLanguage());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Raster operation", RasterOp::N0, pVDev->GetRasterOp());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Map mode", MapMode(MapUnit::MapTwip), pVDev->GetMapMode());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Ref point", Point(10, 10), pVDev->GetRefPoint());
+
+ pVDev->Pop();
+ pAction = aMtf.GetAction(13);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Pop action", MetaActionType::POP, pAction->GetType());
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Default line color", COL_BLACK, pVDev->GetLineColor());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Default fill color", COL_WHITE, pVDev->GetFillColor());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Default text color", COL_BLACK, pVDev->GetTextColor());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Default text fill color", Color(ColorTransparency, 0xFFFFFFFF),
+ pVDev->GetTextFillColor());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Default text line color", Color(ColorTransparency, 0xFFFFFFFF),
+ pVDev->GetTextLineColor());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Default overline color", Color(ColorTransparency, 0xFFFFFFFF),
+ pVDev->GetOverlineColor());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Default layout mode", vcl::text::ComplexTextLayoutFlags::Default,
+ pVDev->GetLayoutMode());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Default language", LANGUAGE_SYSTEM, pVDev->GetDigitLanguage());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Default raster operation", RasterOp::OverPaint,
+ pVDev->GetRasterOp());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Default map mode", MapMode(MapUnit::MapPixel),
+ pVDev->GetMapMode());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Default ref point", Point(0, 0), pVDev->GetRefPoint());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testSystemTextColor)
+{
+ {
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+
+ pVDev->SetSystemTextColor(SystemTextColorFlags::NONE, true);
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, pVDev->GetTextColor());
+ pVDev->SetSystemTextColor(SystemTextColorFlags::Mono, false);
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, pVDev->GetTextColor());
+ }
+
+ {
+ ScopedVclPtrInstance<Printer> pPrinter;
+ pPrinter->SetSystemTextColor(SystemTextColorFlags::NONE, true);
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, pPrinter->GetTextColor());
+ }
+}
+
+namespace
+{
+class WaveLineTester : public OutputDevice
+{
+public:
+ WaveLineTester()
+ : OutputDevice(OUTDEV_VIRDEV)
+ {
+ }
+
+ bool AcquireGraphics() const { return true; }
+ void ReleaseGraphics(bool) {}
+ bool UsePolyPolygonForComplexGradient() { return false; }
+
+ bool testShouldDrawWavePixelAsRect(tools::Long nLineWidth)
+ {
+ return shouldDrawWavePixelAsRect(nLineWidth);
+ }
+
+ Size testGetWaveLineSize(tools::Long nLineWidth) { return GetWaveLineSize(nLineWidth); }
+};
+
+class WaveLineTesterPrinter : public Printer
+{
+public:
+ WaveLineTesterPrinter() {}
+
+ bool AcquireGraphics() const { return true; }
+ void ReleaseGraphics(bool) {}
+ bool UsePolyPolygonForComplexGradient() { return false; }
+
+ Size testGetWaveLineSize(tools::Long nLineWidth) { return GetWaveLineSize(nLineWidth); }
+};
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testShouldDrawWavePixelAsRect)
+{
+ ScopedVclPtrInstance<WaveLineTester> pTestOutDev;
+
+ CPPUNIT_ASSERT(!pTestOutDev->testShouldDrawWavePixelAsRect(0));
+ CPPUNIT_ASSERT(!pTestOutDev->testShouldDrawWavePixelAsRect(1));
+
+ CPPUNIT_ASSERT(pTestOutDev->testShouldDrawWavePixelAsRect(10));
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testGetWaveLineSize)
+{
+ {
+ ScopedVclPtrInstance<WaveLineTester> pTestOutDev;
+
+ pTestOutDev->SetDPIX(96);
+ pTestOutDev->SetDPIY(96);
+
+ CPPUNIT_ASSERT_EQUAL(Size(1, 1), pTestOutDev->testGetWaveLineSize(0));
+ CPPUNIT_ASSERT_EQUAL(Size(1, 1), pTestOutDev->testGetWaveLineSize(1));
+
+ CPPUNIT_ASSERT_EQUAL(Size(10, 10), pTestOutDev->testGetWaveLineSize(10));
+ }
+
+ {
+ ScopedVclPtrInstance<WaveLineTesterPrinter> pTestOutDev;
+
+ pTestOutDev->SetDPIX(96);
+ pTestOutDev->SetDPIY(96);
+
+ CPPUNIT_ASSERT_EQUAL(Size(0, 0), pTestOutDev->testGetWaveLineSize(0));
+ CPPUNIT_ASSERT_EQUAL(Size(1, 1), pTestOutDev->testGetWaveLineSize(1));
+
+ CPPUNIT_ASSERT_EQUAL(Size(10, 10), pTestOutDev->testGetWaveLineSize(10));
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testErase)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ // this actually triggers Erase()
+ pVDev->SetOutputSizePixel(Size(10, 10));
+ pVDev->Erase();
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line color action (start)", MetaActionType::LINECOLOR,
+ pAction->GetType());
+
+ pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action (start)", MetaActionType::FILLCOLOR,
+ pAction->GetType());
+
+ pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 2);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a rect action", MetaActionType::RECT, pAction->GetType());
+
+ pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 3);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line color action (end)", MetaActionType::LINECOLOR,
+ pAction->GetType());
+
+ pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 4);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action (end)", MetaActionType::FILLCOLOR,
+ pAction->GetType());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawPixel)
+{
+ {
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ // triggers an Erase()
+ pVDev->SetOutputSizePixel(Size(10, 10));
+ pVDev->SetLineColor(COL_RED);
+ pVDev->DrawPixel(Point(0, 0), COL_GREEN);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Color not green", COL_GREEN, pVDev->GetPixel(Point(0, 0)));
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a pixel action", MetaActionType::PIXEL,
+ pAction->GetType());
+ MetaPixelAction* pPixelAction = dynamic_cast<MetaPixelAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Pixel action has incorrect position", Point(0, 0),
+ pPixelAction->GetPoint());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Pixel action is wrong color", COL_GREEN,
+ pPixelAction->GetColor());
+ }
+
+ {
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetOutputSizePixel(Size(1, 1));
+ pVDev->SetLineColor(COL_RED);
+ pVDev->DrawPixel(Point(0, 0));
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Color not red", COL_RED, pVDev->GetPixel(Point(0, 0)));
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a point action", MetaActionType::POINT,
+ pAction->GetType());
+ MetaPointAction* pPointAction = dynamic_cast<MetaPointAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Pixel action has incorrect position", Point(0, 0),
+ pPointAction->GetPoint());
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawLine)
+{
+ {
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetOutputSizePixel(Size(10, 100));
+ pVDev->DrawLine(Point(0, 0), Point(0, 50));
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line action", MetaActionType::LINE, pAction->GetType());
+ MetaLineAction* pLineAction = dynamic_cast<MetaLineAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Line start has incorrect position", Point(0, 0),
+ pLineAction->GetStartPoint());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Line start has incorrect position", Point(0, 50),
+ pLineAction->GetEndPoint());
+ }
+
+ {
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ LineInfo aLineInfo(LineStyle::Dash, 10);
+ aLineInfo.SetDashCount(5);
+ aLineInfo.SetDashLen(10);
+ aLineInfo.SetDotCount(3);
+ aLineInfo.SetDotLen(13);
+ aLineInfo.SetDistance(8);
+ aLineInfo.SetLineJoin(basegfx::B2DLineJoin::Bevel);
+ aLineInfo.SetLineCap(css::drawing::LineCap_BUTT);
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+ pVDev->DrawLine(Point(0, 0), Point(0, 50), aLineInfo);
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line action", MetaActionType::LINE, pAction->GetType());
+ MetaLineAction* pLineAction = dynamic_cast<MetaLineAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Line start has incorrect position", Point(0, 0),
+ pLineAction->GetStartPoint());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Line start has incorrect position", Point(0, 50),
+ pLineAction->GetEndPoint());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Dash count wrong", static_cast<sal_uInt16>(5),
+ pLineAction->GetLineInfo().GetDashCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Dash len wrong", static_cast<double>(10),
+ pLineAction->GetLineInfo().GetDashLen());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Dot count wrong", static_cast<sal_uInt16>(3),
+ pLineAction->GetLineInfo().GetDotCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Dot len wrong", static_cast<double>(13),
+ pLineAction->GetLineInfo().GetDotLen());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Distance wrong", static_cast<double>(8),
+ pLineAction->GetLineInfo().GetDistance());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Line join", basegfx::B2DLineJoin::Bevel,
+ pLineAction->GetLineInfo().GetLineJoin());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Line cap", css::drawing::LineCap_BUTT,
+ pLineAction->GetLineInfo().GetLineCap());
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawRect)
+{
+ {
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+ pVDev->DrawRect(tools::Rectangle(Point(0, 0), Size(50, 60)));
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a rect action", MetaActionType::RECT, pAction->GetType());
+ MetaRectAction* pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rectangle wrong", tools::Rectangle(Point(0, 0), Size(50, 60)),
+ pRectAction->GetRect());
+ }
+
+ {
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+ pVDev->DrawRect(tools::Rectangle(Point(0, 0), Size(50, 60)), 5, 10);
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a rect action", MetaActionType::ROUNDRECT,
+ pAction->GetType());
+ MetaRoundRectAction* pRectAction = dynamic_cast<MetaRoundRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rectangle wrong", tools::Rectangle(Point(0, 0), Size(50, 60)),
+ pRectAction->GetRect());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Horizontal round rect wrong", static_cast<sal_uInt32>(5),
+ pRectAction->GetHorzRound());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Vertical round rect wrong", static_cast<sal_uInt32>(10),
+ pRectAction->GetVertRound());
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawEllipse)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+ pVDev->DrawEllipse(tools::Rectangle(Point(0, 0), Size(50, 60)));
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a ellipse action", MetaActionType::ELLIPSE,
+ pAction->GetType());
+ MetaEllipseAction* pEllipseAction = dynamic_cast<MetaEllipseAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Ellipse rect wrong", tools::Rectangle(Point(0, 0), Size(50, 60)),
+ pEllipseAction->GetRect());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawPie)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ tools::Rectangle aRect(Point(0, 0), Size(50, 60));
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+ pVDev->DrawPie(aRect, aRect.TopRight(), aRect.TopCenter());
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a pie action", MetaActionType::PIE, pAction->GetType());
+ MetaPieAction* pPieAction = dynamic_cast<MetaPieAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Pie rect wrong", aRect, pPieAction->GetRect());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Pie start point wrong", aRect.TopRight(),
+ pPieAction->GetStartPoint());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Pie end point wrong", aRect.TopCenter(),
+ pPieAction->GetEndPoint());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawChord)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ tools::Rectangle aRect(Point(21, 22), Size(4, 4));
+ pVDev->SetOutputSizePixel(Size(100, 100));
+ pVDev->DrawChord(aRect, Point(30, 31), Point(32, 33));
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a chord action", MetaActionType::CHORD, pAction->GetType());
+ MetaChordAction* pChordAction = dynamic_cast<MetaChordAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Chord rect wrong", aRect, pChordAction->GetRect());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Chord start point wrong", Point(30, 31),
+ pChordAction->GetStartPoint());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Chord end point wrong", Point(32, 33),
+ pChordAction->GetEndPoint());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawArc)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ tools::Rectangle aRect(Point(1, 2), Size(4, 4));
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+ pVDev->DrawArc(aRect, Point(10, 11), Point(12, 13));
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a arc action", MetaActionType::ARC, pAction->GetType());
+ MetaArcAction* pArcAction = dynamic_cast<MetaArcAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Arc rect wrong", aRect, pArcAction->GetRect());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Arc start point wrong", Point(10, 11),
+ pArcAction->GetStartPoint());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Arc end point wrong", Point(12, 13), pArcAction->GetEndPoint());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawCheckered)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+ pVDev->DrawCheckered(Point(0, 0), Size(100, 100), 20, COL_BLACK, COL_WHITE);
+
+ size_t nIndex = INITIAL_SETUP_ACTION_COUNT;
+
+ MetaAction* pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not push action", MetaActionType::PUSH, pAction->GetType());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line color", MetaActionType::LINECOLOR, pAction->GetType());
+
+ // Row 1
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 1, rect 1",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ MetaFillColorAction* pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 1, rect 1", COL_WHITE,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not first rect, row 1", MetaActionType::RECT, pAction->GetType());
+ MetaRectAction* pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 1, row 1 not correct",
+ tools::Rectangle(Point(0, 0), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 1, rect 2",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 1, rect 1", COL_BLACK,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not second rect, row 1", MetaActionType::RECT,
+ pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 2, row 1 not correct",
+ tools::Rectangle(Point(0, 20), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 1, rect 3",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 1, rect 1", COL_WHITE,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not third rect, row 1", MetaActionType::RECT, pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 3, row 1 not correct",
+ tools::Rectangle(Point(0, 40), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 1, rect 4",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 1, rect 4", COL_BLACK,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not fourth rect, row 1", MetaActionType::RECT,
+ pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 4, row 1 not correct",
+ tools::Rectangle(Point(0, 60), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 1, rect 5",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 1, rect 5", COL_WHITE,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not fifth rect, row 1", MetaActionType::RECT, pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 5, row 1 not correct",
+ tools::Rectangle(Point(0, 80), Size(21, 21)),
+ pRectAction->GetRect());
+
+ // Row 2
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 2, rect 1",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 2, rect 1", COL_BLACK,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, first rect", MetaActionType::RECT, pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 1, row 2 not correct",
+ tools::Rectangle(Point(20, 0), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 2, rect 2",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 2, rect 1", COL_WHITE,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, second rect", MetaActionType::RECT,
+ pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 2, row 2 not correct",
+ tools::Rectangle(Point(20, 20), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 2, rect 3",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 2, rect 1", COL_BLACK,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, third rect", MetaActionType::RECT, pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 3, row 2 not correct",
+ tools::Rectangle(Point(20, 40), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 2, rect 4",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 2, rect 4", COL_WHITE,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, fourth rect", MetaActionType::RECT,
+ pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 4, row 2 not correct",
+ tools::Rectangle(Point(20, 60), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 2, rect 5",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 2, rect 5", COL_BLACK,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, fifth rect", MetaActionType::RECT, pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 5, row 2 not correct",
+ tools::Rectangle(Point(20, 80), Size(21, 21)),
+ pRectAction->GetRect());
+
+ // Row 3
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 3, rect 1",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 3, rect 1", COL_WHITE,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 3, first rect", MetaActionType::RECT, pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 1, row 3 not correct",
+ tools::Rectangle(Point(40, 0), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 3, rect 2",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 3, rect 1", COL_BLACK,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 3, second rect", MetaActionType::RECT,
+ pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 2, row 3 not correct",
+ tools::Rectangle(Point(40, 20), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 3, rect 3",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 3, rect 1", COL_WHITE,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 3, third rect", MetaActionType::RECT, pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 3, row 3 not correct",
+ tools::Rectangle(Point(40, 40), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 3, rect 4",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 3, rect 4", COL_BLACK,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 3, fourth rect", MetaActionType::RECT,
+ pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 4, row 3 not correct",
+ tools::Rectangle(Point(40, 60), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 3, rect 5",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 3, rect 5", COL_WHITE,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 3, fifth rect", MetaActionType::RECT, pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 5, row 3 not correct",
+ tools::Rectangle(Point(40, 80), Size(21, 21)),
+ pRectAction->GetRect());
+
+ // Row 4
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 4, rect 1",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 4, rect 1", COL_BLACK,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 4, first rect", MetaActionType::RECT, pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 1, row 4 not correct",
+ tools::Rectangle(Point(60, 0), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 4, rect 2",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 4, rect 1", COL_WHITE,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 4, second rect", MetaActionType::RECT,
+ pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 2, row 2 not correct",
+ tools::Rectangle(Point(60, 20), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 4, rect 3",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 4, rect 1", COL_BLACK,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 4, third rect", MetaActionType::RECT, pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 3, row 4 not correct",
+ tools::Rectangle(Point(60, 40), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 4, rect 4",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 4, rect 4", COL_WHITE,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 4, fourth rect", MetaActionType::RECT,
+ pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 4, row 4 not correct",
+ tools::Rectangle(Point(60, 60), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 4, rect 5",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 4, rect 5", COL_BLACK,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 4, fifth rect", MetaActionType::RECT, pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 5, row 4 not correct",
+ tools::Rectangle(Point(60, 80), Size(21, 21)),
+ pRectAction->GetRect());
+
+ // Row 5
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 5, rect 1",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 5, rect 1", COL_WHITE,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 5, first rect", MetaActionType::RECT, pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 1, row 5 not correct",
+ tools::Rectangle(Point(80, 0), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 5, rect 2",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 5, rect 1", COL_BLACK,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, second rect", MetaActionType::RECT,
+ pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 2, row 2 not correct",
+ tools::Rectangle(Point(80, 20), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 2, rect 3",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 2, rect 1", COL_WHITE,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, third rect", MetaActionType::RECT, pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 3, row 2 not correct",
+ tools::Rectangle(Point(80, 40), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 2, rect 4",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 2, rect 4", COL_BLACK,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, fourth rect", MetaActionType::RECT,
+ pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 4, row 2 not correct",
+ tools::Rectangle(Point(80, 60), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 2, rect 5",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 2, rect 5", COL_WHITE,
+ pFillColorAction->GetColor());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, fifth rect", MetaActionType::RECT, pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 5, row 2 not correct",
+ tools::Rectangle(Point(80, 80), Size(21, 21)),
+ pRectAction->GetRect());
+
+ nIndex++;
+ pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not pop", MetaActionType::POP, pAction->GetType());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawBorder)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+ pVDev->DrawBorder(tools::Rectangle(Point(0, 0), Size(50, 60)));
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line color action (light gray)", MetaActionType::LINECOLOR,
+ pAction->GetType());
+ MetaLineColorAction* pLineColorAction = dynamic_cast<MetaLineColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not light gray", COL_LIGHTGRAY, pLineColorAction->GetColor());
+
+ pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a rect action (light gray border)", MetaActionType::RECT,
+ pAction->GetType());
+ MetaRectAction* pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rectangle wrong", tools::Rectangle(Point(1, 1), Size(49, 59)),
+ pRectAction->GetRect());
+
+ pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 2);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line color action (gray)", MetaActionType::LINECOLOR,
+ pAction->GetType());
+ pLineColorAction = dynamic_cast<MetaLineColorAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not gray", COL_GRAY, pLineColorAction->GetColor());
+
+ pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 3);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a rect action (gray border)", MetaActionType::RECT,
+ pAction->GetType());
+ pRectAction = dynamic_cast<MetaRectAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Rectangle wrong", tools::Rectangle(Point(0, 0), Size(49, 59)),
+ pRectAction->GetRect());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawWaveLine)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+ pVDev->DrawWaveLine(Point(0, 0), Point(50, 0));
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a bitmap action", MetaActionType::BMPEXSCALEPART,
+ pAction->GetType());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawPolyLine)
+{
+ {
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+ tools::Polygon aPolygon(vcl::test::OutputDeviceTestCommon::createClosedBezierLoop(
+ tools::Rectangle(Point(10, 10), Size(80, 8))));
+
+ pVDev->DrawPolyLine(aPolygon);
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action", MetaActionType::POLYLINE,
+ pAction->GetType());
+ MetaPolyLineAction* pPolyLineAction = dynamic_cast<MetaPolyLineAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Polygon in polyline action is wrong", aPolygon,
+ pPolyLineAction->GetPolygon());
+ }
+
+ {
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+
+ tools::Polygon aPolygon(vcl::test::OutputDeviceTestCommon::createClosedBezierLoop(
+ tools::Rectangle(Point(10, 10), Size(80, 8))));
+
+ LineInfo aLineInfo(LineStyle::Dash, 10);
+ aLineInfo.SetDashCount(5);
+ aLineInfo.SetDashLen(10);
+ aLineInfo.SetDotCount(3);
+ aLineInfo.SetDotLen(13);
+ aLineInfo.SetDistance(8);
+ aLineInfo.SetLineJoin(basegfx::B2DLineJoin::Bevel);
+ aLineInfo.SetLineCap(css::drawing::LineCap_BUTT);
+
+ pVDev->DrawPolyLine(aPolygon, aLineInfo);
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action", MetaActionType::POLYLINE,
+ pAction->GetType());
+ MetaPolyLineAction* pPolyLineAction = dynamic_cast<MetaPolyLineAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Polygon in polyline action is wrong", aPolygon,
+ pPolyLineAction->GetPolygon());
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Dash count wrong", static_cast<sal_uInt16>(5),
+ pPolyLineAction->GetLineInfo().GetDashCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Dash len wrong", static_cast<double>(10),
+ pPolyLineAction->GetLineInfo().GetDashLen());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Dot count wrong", static_cast<sal_uInt16>(3),
+ pPolyLineAction->GetLineInfo().GetDotCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Dot len wrong", static_cast<double>(13),
+ pPolyLineAction->GetLineInfo().GetDotLen());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Distance wrong", static_cast<double>(8),
+ pPolyLineAction->GetLineInfo().GetDistance());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Line join", basegfx::B2DLineJoin::Bevel,
+ pPolyLineAction->GetLineInfo().GetLineJoin());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Line cap", css::drawing::LineCap_BUTT,
+ pPolyLineAction->GetLineInfo().GetLineCap());
+ }
+
+ {
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+
+ basegfx::B2DPolygon aPolygon(vcl::test::OutputDeviceTestCommon::createClosedBezierLoop(
+ tools::Rectangle(Point(10, 10), Size(80, 8)))
+ .getB2DPolygon());
+
+ LineInfo aLineInfo(LineStyle::Dash, 10);
+ aLineInfo.SetDashCount(5);
+ aLineInfo.SetDashLen(10);
+ aLineInfo.SetDotCount(3);
+ aLineInfo.SetDotLen(13);
+ aLineInfo.SetDistance(8);
+ aLineInfo.SetLineJoin(basegfx::B2DLineJoin::Bevel);
+ aLineInfo.SetLineCap(css::drawing::LineCap_BUTT);
+
+ pVDev->DrawPolyLine(aPolygon, 3, basegfx::B2DLineJoin::Bevel, css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0));
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action", MetaActionType::POLYLINE,
+ pAction->GetType());
+ MetaPolyLineAction* pPolyLineAction = dynamic_cast<MetaPolyLineAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Polygon in polyline action is wrong", aPolygon,
+ pPolyLineAction->GetPolygon().getB2DPolygon());
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Width wrong", static_cast<double>(3),
+ pPolyLineAction->GetLineInfo().GetWidth());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Dash count wrong", static_cast<sal_uInt16>(0),
+ pPolyLineAction->GetLineInfo().GetDashCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Dash len wrong", static_cast<double>(0),
+ pPolyLineAction->GetLineInfo().GetDashLen());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Dot count wrong", static_cast<sal_uInt16>(0),
+ pPolyLineAction->GetLineInfo().GetDotCount());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Dot len wrong", static_cast<double>(0),
+ pPolyLineAction->GetLineInfo().GetDotLen());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Distance wrong", static_cast<double>(0),
+ pPolyLineAction->GetLineInfo().GetDistance());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Line join", basegfx::B2DLineJoin::Bevel,
+ pPolyLineAction->GetLineInfo().GetLineJoin());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Line cap", css::drawing::LineCap_BUTT,
+ pPolyLineAction->GetLineInfo().GetLineCap());
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawPolygon)
+{
+ {
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+ tools::Polygon aPolygon(vcl::test::OutputDeviceTestCommon::createClosedBezierLoop(
+ tools::Rectangle(Point(10, 10), Size(80, 8))));
+
+ pVDev->DrawPolygon(aPolygon);
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action", MetaActionType::POLYGON,
+ pAction->GetType());
+ MetaPolygonAction* pPolygonAction = dynamic_cast<MetaPolygonAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Polygon in polygon action is wrong", aPolygon,
+ pPolygonAction->GetPolygon());
+ }
+
+ {
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+ tools::Polygon aPolygon(vcl::test::OutputDeviceTestCommon::createClosedBezierLoop(
+ tools::Rectangle(Point(10, 10), Size(80, 8))));
+
+ pVDev->DrawPolygon(aPolygon);
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action", MetaActionType::POLYGON,
+ pAction->GetType());
+ MetaPolygonAction* pPolygonAction = dynamic_cast<MetaPolygonAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Polygon in polygon action is wrong", aPolygon,
+ pPolygonAction->GetPolygon());
+ }
+}
+
+static tools::PolyPolygon createPolyPolygon()
+{
+ tools::Polygon aPolygon(4);
+
+ aPolygon.SetPoint(Point(1, 8), 0);
+ aPolygon.SetPoint(Point(2, 7), 1);
+ aPolygon.SetPoint(Point(3, 6), 2);
+ aPolygon.SetPoint(Point(4, 5), 3);
+
+ tools::PolyPolygon aPolyPolygon(aPolygon);
+ aPolyPolygon.Optimize(PolyOptimizeFlags::CLOSE);
+
+ return aPolyPolygon;
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawPolyPolygon)
+{
+ {
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+
+ tools::PolyPolygon aPolyPolygon = createPolyPolygon();
+
+ pVDev->DrawPolyPolygon(aPolyPolygon);
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polypolygon action", MetaActionType::POLYPOLYGON,
+ pAction->GetType());
+
+ MetaPolyPolygonAction* pPolyPolygonAction = dynamic_cast<MetaPolyPolygonAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not the same polypolygon in polypolygon action", aPolyPolygon,
+ pPolyPolygonAction->GetPolyPolygon());
+ }
+
+ {
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+
+ tools::PolyPolygon aPolyPolygon = createPolyPolygon();
+
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(aPolyPolygon.getB2DPolyPolygon());
+
+ pVDev->DrawPolyPolygon(aB2DPolyPolygon);
+
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polypolygon action", MetaActionType::POLYPOLYGON,
+ pAction->GetType());
+
+ /* these should match, but the equality operator does not work on PolyPolygon for some reason
+
+ MetaPolyPolygonAction* pPolyPolygonAction = dynamic_cast<MetaPolyPolygonAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not the same polypolygon in polypolygon action", aPolyPolygon,
+ pPolyPolygonAction->GetPolyPolygon());
+ */
+ }
+}
+
+static size_t ClipGradientTest(GDIMetaFile& rMtf, size_t nIndex)
+{
+ MetaAction* pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a comment action", MetaActionType::COMMENT,
+ pAction->GetType());
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a gradientex action", MetaActionType::GRADIENTEX,
+ pAction->GetType());
+
+ // clip gradient
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a push action", MetaActionType::PUSH, pAction->GetType());
+ MetaPushAction* pPushAction = dynamic_cast<MetaPushAction*>(pAction);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not using CLIPREGION push flags", vcl::PushFlags::CLIPREGION,
+ pPushAction->GetFlags());
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a CLIPREGION action", MetaActionType::CLIPREGION,
+ pAction->GetType());
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a gradient action", MetaActionType::GRADIENT,
+ pAction->GetType());
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a pop action", MetaActionType::POP, pAction->GetType());
+
+ nIndex++;
+ pAction = rMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a comment action", MetaActionType::COMMENT,
+ pAction->GetType());
+
+ return nIndex;
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawGradient_drawmode)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+ pVDev->SetDrawMode(DrawModeFlags::BlackGradient);
+
+ tools::Rectangle aRect(Point(10, 10), Size(40, 40));
+ pVDev->DrawGradient(aRect, Gradient());
+ MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a push action (drawmode is black gradient)",
+ MetaActionType::PUSH, pAction->GetType());
+ MetaPushAction* pPushAction = dynamic_cast<MetaPushAction*>(pAction);
+ vcl::PushFlags eFlags = vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR;
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Push flags wrong (drawmode is black gradient)", eFlags,
+ pPushAction->GetFlags());
+
+ pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line color action (drawmode is black gradient)",
+ MetaActionType::LINECOLOR, pAction->GetType());
+ pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 2);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action (drawmode is black gradient)",
+ MetaActionType::FILLCOLOR, pAction->GetType());
+ pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 3);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polypolygon action (drawmode is black gradient)",
+ MetaActionType::POLYPOLYGON, pAction->GetType());
+ pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 4);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a pop action (drawmode is black gradient)",
+ MetaActionType::POP, pAction->GetType());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawGradient_rect_linear)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ tools::Rectangle aRect(Point(10, 10), Size(40, 40));
+ pVDev->SetOutputSizePixel(Size(100, 100));
+
+ Gradient aGradient(css::awt::GradientStyle_LINEAR, COL_RED, COL_WHITE);
+ aGradient.SetBorder(100);
+
+ pVDev->DrawGradient(aRect, aGradient);
+
+ size_t nIndex = INITIAL_SETUP_ACTION_COUNT;
+
+ MetaAction* pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a gradient action (rectangle area)", MetaActionType::GRADIENT,
+ pAction->GetType());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawGradient_rect_axial)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ tools::Rectangle aRect(Point(10, 10), Size(40, 40));
+ pVDev->SetOutputSizePixel(Size(100, 100));
+
+ Gradient aGradient(css::awt::GradientStyle_AXIAL, COL_RED, COL_WHITE);
+ aGradient.SetBorder(100);
+
+ pVDev->DrawGradient(aRect, aGradient);
+
+ size_t nIndex = INITIAL_SETUP_ACTION_COUNT;
+
+ MetaAction* pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a gradient action (rectangle area)", MetaActionType::GRADIENT,
+ pAction->GetType());
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawGradient_polygon_linear)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ tools::PolyPolygon aPolyPolygon = createPolyPolygon();
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+
+ Gradient aGradient(css::awt::GradientStyle_LINEAR, COL_RED, COL_WHITE);
+ aGradient.SetBorder(100);
+
+ pVDev->DrawGradient(aPolyPolygon, aGradient);
+
+ ClipGradientTest(aMtf, INITIAL_SETUP_ACTION_COUNT);
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawGradient_polygon_axial)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ tools::PolyPolygon aPolyPolygon = createPolyPolygon();
+
+ pVDev->SetOutputSizePixel(Size(100, 100));
+
+ Gradient aGradient(css::awt::GradientStyle_AXIAL, COL_RED, COL_WHITE);
+ aGradient.SetBorder(100);
+
+ pVDev->DrawGradient(aPolyPolygon, aGradient);
+
+ ClipGradientTest(aMtf, INITIAL_SETUP_ACTION_COUNT);
+}
+
+CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawGradient_rect_complex)
+{
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ aMtf.Record(pVDev.get());
+
+ tools::Rectangle aRect(Point(10, 10), Size(40, 40));
+ pVDev->SetOutputSizePixel(Size(1000, 1000));
+
+ Gradient aGradient(css::awt::GradientStyle_SQUARE, COL_RED, COL_WHITE);
+ aGradient.SetBorder(10);
+ pVDev->DrawGradient(aRect, aGradient);
+
+ size_t nIndex = INITIAL_SETUP_ACTION_COUNT;
+
+ MetaAction* pAction = aMtf.GetAction(nIndex);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a gradient action (rectangle area)", MetaActionType::GRADIENT,
+ pAction->GetType());
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/pdfexport/data/6m-wide.odg b/vcl/qa/cppunit/pdfexport/data/6m-wide.odg
new file mode 100644
index 0000000000..49fb9bfff9
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/6m-wide.odg
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/BrownFoxLazyDog.odt b/vcl/qa/cppunit/pdfexport/data/BrownFoxLazyDog.odt
new file mode 100644
index 0000000000..2e87c33c71
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/BrownFoxLazyDog.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/ComplexContentDictionary.pdf b/vcl/qa/cppunit/pdfexport/data/ComplexContentDictionary.pdf
new file mode 100644
index 0000000000..950eef1e1a
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/ComplexContentDictionary.pdf
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/Description PDF Export test .odt b/vcl/qa/cppunit/pdfexport/data/Description PDF Export test .odt
new file mode 100644
index 0000000000..78f05b09e9
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/Description PDF Export test .odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/LO_Lbl_Lbody_bug_report.fodt b/vcl/qa/cppunit/pdfexport/data/LO_Lbl_Lbody_bug_report.fodt
new file mode 100644
index 0000000000..ed4cf89584
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/LO_Lbl_Lbody_bug_report.fodt
@@ -0,0 +1,125 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:initial-creator>Saku Seppälä</meta:initial-creator><meta:creation-date>2023-10-11T16:51:28.618000000</meta:creation-date><dc:date>2023-10-19T12:10:56.924262307</dc:date><meta:editing-duration>PT7M5S</meta:editing-duration><meta:editing-cycles>5</meta:editing-cycles><meta:generator>LibreOfficeDev/24.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/0080580e754a3a080ae0b914945368307d5c29fe</meta:generator><meta:print-date>2023-10-11T17:00:34.425000000</meta:print-date><meta:printed-by>PDF files: Saku Seppälä</meta:printed-by><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="2" meta:word-count="9" meta:character-count="48" meta:non-whitespace-character-count="42"/></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="Arial Unicode MS1" svg:font-family="'Arial Unicode MS'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/>
+ <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Microsoft YaHei" svg:font-family="'Microsoft YaHei'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="NSimSun" svg:font-family="NSimSun" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="fi" fo:country="FI" style:letter-kerning="true" style:font-name-asian="NSimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Arial Unicode MS1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="page"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="fi" fo:country="FI" style:letter-kerning="true" style:font-name-asian="NSimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Arial Unicode MS1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" fo:keep-with-next="always"/>
+ <style:text-properties style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt" style:font-name-asian="Microsoft YaHei" style:font-family-asian="'Microsoft YaHei'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="Arial Unicode MS1" style:font-family-complex="'Arial Unicode MS'" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
+ </style:style>
+ <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
+ <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.247cm" style:contextual-spacing="false" fo:line-height="115%"/>
+ </style:style>
+ <style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="1" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false"/>
+ <style:text-properties fo:font-size="18pt" fo:font-weight="bold" style:font-size-asian="18pt" style:font-weight-asian="bold" style:font-size-complex="18pt" style:font-weight-complex="bold"/>
+ </style:style>
+ <style:style style:name="Heading_20_2" style:display-name="Heading 2" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="2" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.353cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false"/>
+ <style:text-properties fo:font-size="16pt" fo:font-weight="bold" style:font-size-asian="16pt" style:font-weight-asian="bold" style:font-size-complex="16pt" style:font-weight-complex="bold"/>
+ </style:style>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="1">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Heading_20_1">
+ <style:text-properties/>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text>
+ <text:h text:style-name="P1" text:outline-level="1"><text:bookmark-start text:name="__RefHeading___Toc449_2941960903"/>This Is First Heading<text:bookmark-end text:name="__RefHeading___Toc449_2941960903"/></text:h>
+ <text:h text:style-name="Heading_20_2" text:outline-level="2">Second level without label</text:h>
+ </office:text>
+ </office:body>
+</office:document>
diff --git a/vcl/qa/cppunit/pdfexport/data/LinkWithFly.fodt b/vcl/qa/cppunit/pdfexport/data/LinkWithFly.fodt
new file mode 100644
index 0000000000..5c14cee1f2
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/LinkWithFly.fodt
@@ -0,0 +1,137 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:creation-date>2023-10-25T14:48:54.649558008</meta:creation-date><dc:title>test</dc:title><meta:print-date>2023-10-25T14:49:57.194410546</meta:print-date><meta:printed-by>PDF files</meta:printed-by><dc:date>2023-10-25T14:54:03.818423519</dc:date><meta:editing-duration>PT4M6S</meta:editing-duration><meta:editing-cycles>2</meta:editing-cycles><meta:generator>LibreOfficeDev/24.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/70bf29987ceb90cd3988fc2dfc4cad2a0163fe17</meta:generator><meta:document-statistic meta:table-count="0" meta:image-count="1" meta:object-count="0" meta:page-count="1" meta:paragraph-count="2" meta:word-count="2" meta:character-count="114" meta:non-whitespace-character-count="114"/></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Noto Sans CJK SC" svg:font-family="'Noto Sans CJK SC'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Noto Sans1" svg:font-family="'Noto Sans'" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Noto Sans CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Noto Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="page"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Noto Sans CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Noto Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text">
+ <style:text-properties fo:color="#000080" loext:opacity="100%" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"/>
+ </style:style>
+ <style:style style:name="Graphics" style:family="graphic">
+ <style:graphic-properties text:anchor-type="paragraph" svg:x="0cm" svg:y="0cm" style:wrap="dynamic" style:number-wrapped-paragraphs="no-limit" style:wrap-contour="false" style:vertical-pos="top" style:vertical-rel="paragraph" style:horizontal-pos="center" style:horizontal-rel="paragraph" fo:background-color="transparent" draw:fill="none"/>
+ </style:style>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="fr1" style:family="graphic" style:parent-style-name="Graphics">
+ <style:graphic-properties style:run-through="foreground" style:wrap="parallel" style:number-wrapped-paragraphs="no-limit" style:wrap-contour="false" style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" style:mirror="none" fo:clip="rect(0cm, 0cm, 0cm, 0cm)" draw:luminance="0%" draw:contrast="0%" draw:red="0%" draw:green="0%" draw:blue="0%" draw:gamma="100%" draw:color-inversion="false" draw:image-opacity="100%" draw:color-mode="standard"/>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text>
+ <text:sequence-decls>
+ <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+ </text:sequence-decls>
+ <text:p text:style-name="Standard"><text:a xlink:type="simple" xlink:href="https://www.mozilla.org/en-US/firefox/119.0/releasenotes/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link">https://www.mozilla.org/en-US<draw:frame draw:style-name="fr1" draw:name="Image1" text:anchor-type="char" svg:x="5.318cm" svg:y="0.056cm" svg:width="6.364cm" svg:height="6.364cm" draw:z-index="0"><draw:image draw:mime-type="image/png">
+ <office:binary-data>iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAABGdBTUEAANbY1E9YMgAAABl0
+ RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAFpSURBVHjaYvz//z8DtQBAADER
+ o+jjZGuibAQIICZiDOK/cgzFwEnrV/4HYXS1AAHERIxBR58yMiAb2DtzM1b1AAHERIxBIIBu
+ IDYAEEBMxBjE0bgdxcBL3vcZLl16jaEPIICYiDFIU9MSw8BeoeUYhgEEEBMxBnFx8WE1EN3L
+ AAHERIxBIECMgQABxAhKtPgM+vbtE9xmGP/69eMMP+o9wWLW0kD9OlYM/LlHGQECiAndoKg/
+ USgGgTTmdS8C0yA+zIUgdeguBAggljtWdQwMVkDXACWMjd0ZXRun/Id5DWTA9C23GSaVxoEN
+ zISoARvoamnBYF2/hPHs2Z3/z0JdDhBADCBvIuPkhsn/QeDr14//QWwQjY0PVYeiFyCA8OaA
+ 3cdPoEQAiI8PAAQQEwMVAUAAsWATBAX0jx9fsWrAJQ4CAAGE1TBQwOMC9+9fwikHEEBYDQPF
+ IAzIe8TglEMHAAHESM2SFiDAADEwCe4BJwcYAAAAAElFTkSuQmCC
+ </office:binary-data>
+ </draw:image>
+ <svg:title>house</svg:title>
+ </draw:frame>/firefox/119.0/releasenotes/</text:a></text:p>
+ <text:p text:style-name="P1"><text:a xlink:type="simple" xlink:href="https://www.mozilla.org/en-US/firefox/118.0/releasenotes/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link">https://www.mozilla.org/en-US/firefox/118.0/releasenotes/</text:a></text:p>
+ </office:text>
+ </office:body>
+</office:document>
diff --git a/vcl/qa/cppunit/pdfexport/data/PDFWithImages.pdf b/vcl/qa/cppunit/pdfexport/data/PDFWithImages.pdf
new file mode 100644
index 0000000000..315cdc36ef
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/PDFWithImages.pdf
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/PDF_export_with_formcontrol.fodt b/vcl/qa/cppunit/pdfexport/data/PDF_export_with_formcontrol.fodt
new file mode 100644
index 0000000000..eda699e27f
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/PDF_export_with_formcontrol.fodt
@@ -0,0 +1,174 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:initial-creator>A. Spielhoff</meta:initial-creator><meta:creation-date>2020-09-12T10:51:34.438117571</meta:creation-date><dc:date>2023-10-11T12:40:15.543658302</dc:date><meta:editing-duration>PT7H23M50S</meta:editing-duration><meta:editing-cycles>98</meta:editing-cycles><meta:generator>LibreOfficeDev/7.5.7.0.0$Linux_X86_64 LibreOffice_project/0325c0aa2d3e6df97ff554ca540d316273fd149a</meta:generator><meta:print-date>2023-09-23T14:07:35.317591779</meta:print-date><meta:printed-by>PDF-Dateien: A Spielhoff</meta:printed-by><meta:document-statistic meta:table-count="8" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="15" meta:word-count="129" meta:character-count="882" meta:non-whitespace-character-count="767"/></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="Arial" svg:font-family="Arial" style:font-family-generic="swiss"/>
+ <style:font-face style:name="Arial2" svg:font-family="Arial" style:font-adornments="Kursiv" style:font-family-generic="swiss"/>
+ <style:font-face style:name="SimSun" svg:font-family="SimSun" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" draw:start-line-spacing-horizontal="0.1114in" draw:start-line-spacing-vertical="0.1114in" draw:end-line-spacing-horizontal="0.1114in" draw:end-line-spacing-vertical="0.1114in" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" loext:color-lum-mod="100%" loext:color-lum-off="0%" style:font-name="Arial" fo:font-size="11pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="SimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Arial" style:font-size-complex="11pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="0.4925in" style:writing-mode="page"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Arial" fo:font-size="11pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="SimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Arial" style:font-size-complex="11pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
+ <style:paragraph-properties fo:margin-top="0in" fo:margin-bottom="0.0972in" style:contextual-spacing="false" fo:line-height="115%"/>
+ </style:style>
+ <style:style style:name="Footnote_20_Symbol" style:display-name="Footnote Symbol" style:family="text"/>
+ <style:style style:name="Footnote_20_anchor" style:display-name="Footnote anchor" style:family="text">
+ <style:text-properties style:text-position="super 58%"/>
+ </style:style>
+ <style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text">
+ <style:text-properties fo:color="#0000ff" loext:opacity="100%" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"/>
+ </style:style>
+ <style:style style:name="Visited_20_Internet_20_Link" style:display-name="Visited Internet Link" style:family="text">
+ <style:text-properties fo:color="#954f72" loext:opacity="100%" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"/>
+ </style:style>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" text:citation-style-name="Footnote_20_Symbol" text:citation-body-style-name="Footnote_20_anchor" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:number-lines="false" text:offset="0.1965in" style:num-format="1" text:number-position="left" text:increment="5"/>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="P13" style:family="paragraph" style:parent-style-name="Text_20_body">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="P14" style:family="paragraph" style:parent-style-name="Text_20_body">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="P15" style:family="paragraph" style:parent-style-name="Text_20_body" style:master-page-name="Standard">
+ <style:paragraph-properties style:page-number="auto"/>
+ </style:style>
+ <style:style style:name="P28" style:family="paragraph">
+ <style:paragraph-properties fo:text-align="start"/>
+ <style:text-properties fo:color="#3465a4" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Arial2" fo:font-size="11pt" fo:font-style="italic" style:text-underline-style="none"/>
+ </style:style>
+ <style:style style:name="T1" style:family="text">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="T5" style:family="text">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="T6" style:family="text">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="gr1" style:family="graphic">
+ <style:graphic-properties fo:background-color="#f5f5f5" fo:border="solid #3465a4" style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="from-top" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" draw:wrap-influence-on-position="once-concurrent" loext:allow-overlap="true" style:flow-with-text="false"/>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="8.2681in" fo:page-height="11.6929in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.3937in" fo:margin-bottom="0.3937in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.278in" style:layout-grid-ruby-height="0.139in" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0in" loext:margin-gutter="0in">
+ <style:columns fo:column-count="1" fo:column-gap="0in"/>
+ <style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text>
+ <office:forms form:automatic-focus="false" form:apply-design-mode="false">
+ <form:form form:name="Formular" form:apply-filter="true" form:command-type="table" form:control-implementation="ooo:com.sun.star.form.component.Form" office:target-frame="">
+ <form:properties>
+ <form:property form:property-name="PropertyChangeNotificationEnabled" office:value-type="boolean" office:boolean-value="true"/>
+ <form:property form:property-name="TargetURL" office:value-type="string" office:string-value=""/>
+ </form:properties>
+ <form:textarea form:name="XXXX-" form:control-implementation="ooo:com.sun.star.form.component.TextField" xml:id="control1" form:id="control1" form:tab-stop="false" form:input-required="false" form:convert-empty-to-null="true">
+ <form:properties>
+ <form:property form:property-name="ControlTypeinMSO" office:value-type="float" office:value="0"/>
+ <form:property form:property-name="DefaultControl" office:value-type="string" office:string-value="com.sun.star.form.control.TextField"/>
+ <form:property form:property-name="MultiLine" office:value-type="boolean" office:boolean-value="true"/>
+ <form:property form:property-name="ObjIDinMSO" office:value-type="float" office:value="65535"/>
+ </form:properties>
+ </form:textarea>
+ </form:form>
+ </office:forms>
+ <text:sequence-decls>
+ <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+ </text:sequence-decls>
+ <text:p text:style-name="P15">This <text:span text:style-name="T1">t</text:span>ext document contains some links and a text control.</text:p>
+ <text:p text:style-name="P13"><text:span text:style-name="T1">When exporting the document to PDF in LO 7.5.</text:span><text:span text:style-name="T6">3.2</text:span><text:span text:style-name="T1"> or newer the links won't have the right target any more. First link to "Kläranlage" will open last link to "#pano=24", </text:span><text:span text:style-name="T5">second link won't open anything and third link will open "Mechanische Vorreinigung"</text:span></text:p>
+ <text:p text:style-name="Text_20_body"><text:a xlink:type="simple" xlink:href="https://klexikon.zum.de/wiki/Kläranlage" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link">https://klexikon.zum.de/wiki/Kläranlage</text:a></text:p>
+ <text:p text:style-name="Text_20_body"><text:a xlink:type="simple" xlink:href="https://de.wikipedia.org/wiki/Kläranlage#Mechanische_Vorreinigung" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link">https://de.wikipedia.org/wiki/Kläranlage#Mechanische_Vorreinigung</text:a></text:p>
+ <text:p text:style-name="Text_20_body"><text:a xlink:type="simple" xlink:href="https://vr-easy.com/tour/usr/220113-virtuellerschulausflug/#pano=24" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link">https://vr-easy.com/tour/usr/220113-virtuellerschulausflug/#pano=24</text:a></text:p>
+ <text:p text:style-name="Text_20_body">Here a form control for getting possibility to input content.</text:p>
+ <text:p text:style-name="Text_20_body"><draw:control text:anchor-type="as-char" svg:y="-0.3146in" draw:z-index="0" draw:name="Form1" draw:style-name="gr1" draw:text-style-name="P28" svg:width="6.3776in" svg:height="1.7717in" draw:control="control1"/></text:p>
+ <text:p text:style-name="Text_20_body">When deleting the form control links will work as expected.</text:p>
+ <text:p text:style-name="P14">Up to LO 7.5.2.2 this bug won't appear.</text:p>
+ </office:text>
+ </office:body>
+</office:document>
diff --git a/vcl/qa/cppunit/pdfexport/data/SimpleMultiPagePDF.pdf b/vcl/qa/cppunit/pdfexport/data/SimpleMultiPagePDF.pdf
new file mode 100644
index 0000000000..af665fcba8
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/SimpleMultiPagePDF.pdf
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/SimpleTOC.fodt b/vcl/qa/cppunit/pdfexport/data/SimpleTOC.fodt
new file mode 100644
index 0000000000..6e7e88ffe9
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/SimpleTOC.fodt
@@ -0,0 +1,323 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:initial-creator>Gabor LO-Dev</meta:initial-creator><meta:creation-date>2023-10-18T22:16:34.201000000</meta:creation-date><dc:date>2023-10-18T22:23:10.997000000</dc:date><dc:creator>Gabor LO-Dev</dc:creator><meta:editing-duration>PT6M37S</meta:editing-duration><meta:editing-cycles>5</meta:editing-cycles><meta:generator>LibreOfficeDev/24.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/0080580e754a3a080ae0b914945368307d5c29fe</meta:generator><meta:print-date>2023-10-18T22:22:25.716000000</meta:print-date><meta:printed-by>PDF files: Gabor LO-Dev</meta:printed-by><dc:title>Multipage-TOC</dc:title><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="2" meta:paragraph-count="10" meta:word-count="245" meta:character-count="1660" meta:non-whitespace-character-count="1425"/></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/>
+ <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Lucida Sans" svg:font-family="'Lucida Sans'" style:font-family-generic="swiss"/>
+ <style:font-face style:name="Lucida Sans1" svg:font-family="'Lucida Sans'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Microsoft YaHei" svg:font-family="'Microsoft YaHei'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="NSimSun" svg:font-family="NSimSun" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="NSimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lucida Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="page"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="NSimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lucida Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" fo:keep-with-next="always"/>
+ <style:text-properties style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt" style:font-name-asian="Microsoft YaHei" style:font-family-asian="'Microsoft YaHei'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="Lucida Sans1" style:font-family-complex="'Lucida Sans'" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
+ </style:style>
+ <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
+ <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.247cm" style:contextual-spacing="false" fo:line-height="115%"/>
+ </style:style>
+ <style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index">
+ <style:paragraph-properties text:number-lines="false" text:line-number="0"/>
+ <style:text-properties style:font-size-asian="12pt" style:font-name-complex="Lucida Sans" style:font-family-complex="'Lucida Sans'" style:font-family-generic-complex="swiss"/>
+ </style:style>
+ <style:style style:name="Heading_20_3" style:display-name="Heading 3" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="3" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.247cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false"/>
+ <style:text-properties fo:font-size="14pt" fo:font-weight="bold" style:font-size-asian="14pt" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-weight-complex="bold"/>
+ </style:style>
+ <style:style style:name="Heading_20_2" style:display-name="Heading 2" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="2" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.353cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false"/>
+ <style:text-properties fo:font-size="16pt" fo:font-weight="bold" style:font-size-asian="16pt" style:font-weight-asian="bold" style:font-size-complex="16pt" style:font-weight-complex="bold"/>
+ </style:style>
+ <style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="1" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false"/>
+ <style:text-properties fo:font-size="18pt" fo:font-weight="bold" style:font-size-asian="18pt" style:font-weight-asian="bold" style:font-size-complex="18pt" style:font-weight-complex="bold"/>
+ </style:style>
+ <style:style style:name="Index_20_Heading" style:display-name="Index Heading" style:family="paragraph" style:parent-style-name="Heading" style:class="index">
+ <style:paragraph-properties fo:margin-left="0cm" fo:text-indent="0cm" style:auto-text-indent="false" text:number-lines="false" text:line-number="0"/>
+ <style:text-properties fo:font-size="16pt" fo:font-weight="bold" style:font-size-asian="16pt" style:font-weight-asian="bold" style:font-size-complex="16pt" style:font-weight-complex="bold"/>
+ </style:style>
+ <style:style style:name="Contents_20_Heading" style:display-name="Contents Heading" style:family="paragraph" style:parent-style-name="Index_20_Heading" style:class="index">
+ <style:paragraph-properties fo:margin-left="0cm" fo:text-indent="0cm" style:auto-text-indent="false" text:number-lines="false" text:line-number="0"/>
+ <style:text-properties fo:font-size="16pt" fo:font-weight="bold" style:font-size-asian="16pt" style:font-weight-asian="bold" style:font-size-complex="16pt" style:font-weight-complex="bold"/>
+ </style:style>
+ <style:style style:name="Contents_20_1" style:display-name="Contents 1" style:family="paragraph" style:parent-style-name="Index" style:class="index">
+ <style:paragraph-properties fo:margin-left="0cm" fo:text-indent="0cm" style:auto-text-indent="false">
+ <style:tab-stops>
+ <style:tab-stop style:position="17.59cm" style:type="right" style:leader-style="dotted" style:leader-text="."/>
+ </style:tab-stops>
+ </style:paragraph-properties>
+ </style:style>
+ <style:style style:name="Contents_20_2" style:display-name="Contents 2" style:family="paragraph" style:parent-style-name="Index" style:class="index">
+ <style:paragraph-properties fo:margin-left="0.499cm" fo:text-indent="0cm" style:auto-text-indent="false">
+ <style:tab-stops>
+ <style:tab-stop style:position="17.09cm" style:type="right" style:leader-style="dotted" style:leader-text="."/>
+ </style:tab-stops>
+ </style:paragraph-properties>
+ </style:style>
+ <style:style style:name="Contents_20_3" style:display-name="Contents 3" style:family="paragraph" style:parent-style-name="Index" style:class="index">
+ <style:paragraph-properties fo:margin-left="1cm" fo:text-indent="0cm" style:auto-text-indent="false">
+ <style:tab-stops>
+ <style:tab-stop style:position="16.589cm" style:type="right" style:leader-style="dotted" style:leader-text="."/>
+ </style:tab-stops>
+ </style:paragraph-properties>
+ </style:style>
+ <style:style style:name="Index_20_Link" style:display-name="Index Link" style:family="text"/>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Contents_20_1">
+ <style:paragraph-properties>
+ <style:tab-stops>
+ <style:tab-stop style:position="17.59cm" style:type="right" style:leader-style="dotted" style:leader-text="."/>
+ </style:tab-stops>
+ </style:paragraph-properties>
+ </style:style>
+ <style:style style:name="P2" style:family="paragraph" style:parent-style-name="Contents_20_2">
+ <style:paragraph-properties>
+ <style:tab-stops>
+ <style:tab-stop style:position="17.09cm" style:type="right" style:leader-style="dotted" style:leader-text="."/>
+ </style:tab-stops>
+ </style:paragraph-properties>
+ </style:style>
+ <style:style style:name="P3" style:family="paragraph" style:parent-style-name="Contents_20_3">
+ <style:paragraph-properties>
+ <style:tab-stops>
+ <style:tab-stop style:position="16.589cm" style:type="right" style:leader-style="dotted" style:leader-text="."/>
+ </style:tab-stops>
+ </style:paragraph-properties>
+ </style:style>
+ <style:style style:name="P4" style:family="paragraph" style:parent-style-name="Standard">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="P5" style:family="paragraph" style:parent-style-name="Standard">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="P6" style:family="paragraph" style:parent-style-name="Standard">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="P7" style:family="paragraph" style:parent-style-name="Standard">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="Sect1" style:family="section">
+ <style:section-properties style:editable="false">
+ <style:columns fo:column-count="1" fo:column-gap="0cm"/>
+ </style:section-properties>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="21.59cm" fo:page-height="27.94cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text text:use-soft-page-breaks="true">
+ <text:sequence-decls>
+ <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+ </text:sequence-decls>
+ <text:h text:style-name="Heading_20_1" text:outline-level="1"><text:bookmark-start text:name="__RefHeading___Toc1_3097472582"/>One<text:bookmark-end text:name="__RefHeading___Toc1_3097472582"/></text:h>
+ <text:p text:style-name="P4">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum consequat mi quis pretium semper. Proin luctus orci ac neque venenatis, quis commodo dolor posuere. Curabitur dignissim sapien quis cursus egestas. Donec blandit auctor arcu, nec pellentesque eros molestie eget. In consectetur aliquam hendrerit. Sed cursus mauris vitae ligula pellentesque, non pellentesque urna aliquet. Fusce placerat mauris enim, nec rutrum purus semper vel. Praesent tincidunt neque eu pellentesque pharetra. Fusce pellentesque est orci.</text:p>
+ <text:h text:style-name="Heading_20_2" text:outline-level="2"><text:bookmark-start text:name="__RefHeading___Toc3_3097472582"/>Two<text:bookmark-end text:name="__RefHeading___Toc3_3097472582"/></text:h>
+ <text:p text:style-name="P4">Integer sodales tincidunt tristique. Sed a metus posuere, adipiscing nunc et, viverra odio. Donec auctor molestie sem, sit amet tristique lectus hendrerit sed. Cras sodales nisl sed orci mattis iaculis. Nunc eget dolor accumsan, pharetra risus a, vestibulum mauris. Nunc vulputate lobortis mollis. Vivamus nec tellus faucibus, tempor magna nec, facilisis felis. Donec commodo enim a vehicula pellentesque. Nullam vehicula vestibulum est vel ultricies.</text:p>
+ <text:h text:style-name="Heading_20_3" text:outline-level="3"><text:bookmark-start text:name="__RefHeading___Toc5_3097472582"/>Three<text:bookmark-end text:name="__RefHeading___Toc5_3097472582"/></text:h>
+ <text:p text:style-name="P7">Aliquam velit massa, laoreet vel leo nec, volutpat facilisis eros. Donec consequat arcu ut diam tempor luctus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent vitae lacus vel leo sodales pharetra a a nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nam luctus tempus nibh, fringilla dictum augue consectetur eget. Curabitur at ante sit amet tortor pharetra molestie eu nec ante. Mauris tincidunt, nibh eu sollicitudin molestie, dolor sapien congue tortor, a pulvinar sapien turpis sed ante. Donec nec est elementum, euismod nulla in, mollis nunc.</text:p>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:p text:style-name="P6"/>
+ <text:table-of-content text:style-name="Sect1" text:name="Table of Contents1">
+ <text:table-of-content-source text:outline-level="10">
+ <text:index-title-template text:style-name="Contents_20_Heading">Table of Contents</text:index-title-template>
+ <text:table-of-content-entry-template text:outline-level="1" text:style-name="Contents_20_1">
+ <text:index-entry-link-start text:style-name="Index_20_Link"/>
+ <text:index-entry-chapter/>
+ <text:index-entry-text/>
+ <text:index-entry-tab-stop style:type="right" style:leader-char="."/>
+ <text:index-entry-page-number/>
+ <text:index-entry-link-end/>
+ </text:table-of-content-entry-template>
+ <text:table-of-content-entry-template text:outline-level="2" text:style-name="Contents_20_2">
+ <text:index-entry-link-start text:style-name="Index_20_Link"/>
+ <text:index-entry-chapter/>
+ <text:index-entry-text/>
+ <text:index-entry-tab-stop style:type="right" style:leader-char="."/>
+ <text:index-entry-page-number/>
+ <text:index-entry-link-end/>
+ </text:table-of-content-entry-template>
+ <text:table-of-content-entry-template text:outline-level="3" text:style-name="Contents_20_3">
+ <text:index-entry-link-start text:style-name="Index_20_Link"/>
+ <text:index-entry-chapter/>
+ <text:index-entry-text/>
+ <text:index-entry-tab-stop style:type="right" style:leader-char="."/>
+ <text:index-entry-page-number/>
+ <text:index-entry-link-end/>
+ </text:table-of-content-entry-template>
+ <text:table-of-content-entry-template text:outline-level="4" text:style-name="Contents_20_4">
+ <text:index-entry-link-start text:style-name="Index_20_Link"/>
+ <text:index-entry-chapter/>
+ <text:index-entry-text/>
+ <text:index-entry-tab-stop style:type="right" style:leader-char="."/>
+ <text:index-entry-page-number/>
+ <text:index-entry-link-end/>
+ </text:table-of-content-entry-template>
+ <text:table-of-content-entry-template text:outline-level="5" text:style-name="Contents_20_5">
+ <text:index-entry-link-start text:style-name="Index_20_Link"/>
+ <text:index-entry-chapter/>
+ <text:index-entry-text/>
+ <text:index-entry-tab-stop style:type="right" style:leader-char="."/>
+ <text:index-entry-page-number/>
+ <text:index-entry-link-end/>
+ </text:table-of-content-entry-template>
+ <text:table-of-content-entry-template text:outline-level="6" text:style-name="Contents_20_6">
+ <text:index-entry-link-start text:style-name="Index_20_Link"/>
+ <text:index-entry-chapter/>
+ <text:index-entry-text/>
+ <text:index-entry-tab-stop style:type="right" style:leader-char="."/>
+ <text:index-entry-page-number/>
+ <text:index-entry-link-end/>
+ </text:table-of-content-entry-template>
+ <text:table-of-content-entry-template text:outline-level="7" text:style-name="Contents_20_7">
+ <text:index-entry-link-start text:style-name="Index_20_Link"/>
+ <text:index-entry-chapter/>
+ <text:index-entry-text/>
+ <text:index-entry-tab-stop style:type="right" style:leader-char="."/>
+ <text:index-entry-page-number/>
+ <text:index-entry-link-end/>
+ </text:table-of-content-entry-template>
+ <text:table-of-content-entry-template text:outline-level="8" text:style-name="Contents_20_8">
+ <text:index-entry-link-start text:style-name="Index_20_Link"/>
+ <text:index-entry-chapter/>
+ <text:index-entry-text/>
+ <text:index-entry-tab-stop style:type="right" style:leader-char="."/>
+ <text:index-entry-page-number/>
+ <text:index-entry-link-end/>
+ </text:table-of-content-entry-template>
+ <text:table-of-content-entry-template text:outline-level="9" text:style-name="Contents_20_9">
+ <text:index-entry-link-start text:style-name="Index_20_Link"/>
+ <text:index-entry-chapter/>
+ <text:index-entry-text/>
+ <text:index-entry-tab-stop style:type="right" style:leader-char="."/>
+ <text:index-entry-page-number/>
+ <text:index-entry-link-end/>
+ </text:table-of-content-entry-template>
+ <text:table-of-content-entry-template text:outline-level="10" text:style-name="Contents_20_10">
+ <text:index-entry-link-start text:style-name="Index_20_Link"/>
+ <text:index-entry-chapter/>
+ <text:index-entry-text/>
+ <text:index-entry-tab-stop style:type="right" style:leader-char="."/>
+ <text:index-entry-page-number/>
+ <text:index-entry-link-end/>
+ </text:table-of-content-entry-template>
+ </text:table-of-content-source>
+ <text:index-body>
+ <text:index-title text:style-name="Sect1" text:name="Table of Contents1_Head">
+ <text:p text:style-name="Contents_20_Heading">Table of Contents</text:p>
+ </text:index-title>
+ <text:p text:style-name="P1"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc1_3097472582" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link">One<text:tab/>1</text:a></text:p>
+ <text:p text:style-name="P2"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc3_3097472582" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link">Two<text:tab/>1</text:a></text:p>
+ <text:p text:style-name="P3"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc5_3097472582" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link"><text:soft-page-break/>Three<text:tab/>1</text:a></text:p>
+ </text:index-body>
+ </text:table-of-content>
+ <text:p text:style-name="P5"/>
+ </office:text>
+ </office:body>
+</office:document> \ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/data/TableTH_test_LibreOfficeWriter7.3.3_HeaderRow-HeadersInTopRow.fodt b/vcl/qa/cppunit/pdfexport/data/TableTH_test_LibreOfficeWriter7.3.3_HeaderRow-HeadersInTopRow.fodt
new file mode 100644
index 0000000000..1b13a4fd50
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/TableTH_test_LibreOfficeWriter7.3.3_HeaderRow-HeadersInTopRow.fodt
@@ -0,0 +1,560 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:initial-creator>Christophe Strobbe</meta:initial-creator><meta:creation-date>2022-05-14T18:39:40.868000000</meta:creation-date><meta:editing-cycles>18</meta:editing-cycles><meta:editing-duration>PT21M55S</meta:editing-duration><dc:title>Test File for Simple Tables With or Without a Properly Marked Header Row</dc:title><dc:date>2022-05-17T16:43:48.919000000</dc:date><dc:creator>Christophe Strobbe</dc:creator><meta:generator>LibreOfficeDev/7.5.0.0.alpha0$Linux_X86_64 LibreOffice_project/54178896aba2ded7b57479dad435607b73dc89fa</meta:generator><meta:document-statistic meta:table-count="3" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="28" meta:word-count="329" meta:character-count="1794" meta:non-whitespace-character-count="1502"/></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/>
+ <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="MS Gothic" svg:font-family="'MS Gothic'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="OpenSymbol" svg:font-family="OpenSymbol" style:font-charset="x-symbol"/>
+ <style:font-face style:name="Segoe UI" svg:font-family="'Segoe UI'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Tahoma1" svg:font-family="Tahoma" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="GB" style:letter-kerning="true" style:font-name-asian="Segoe UI" style:font-size-asian="10.5pt" style:language-asian="zxx" style:country-asian="none" style:font-name-complex="Tahoma1" style:font-size-complex="12pt" style:language-complex="zxx" style:country-complex="none"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="page"/>
+ <style:text-properties style:use-window-font-color="true" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="GB" style:letter-kerning="true" style:font-name-asian="Segoe UI" style:font-size-asian="10.5pt" style:language-asian="zxx" style:country-asian="none" style:font-name-complex="Tahoma1" style:font-size-complex="12pt" style:language-complex="zxx" style:country-complex="none" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" fo:keep-with-next="always"/>
+ <style:text-properties style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt" style:font-name-asian="MS Gothic" style:font-family-asian="'MS Gothic'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="Tahoma1" style:font-family-complex="Tahoma" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
+ </style:style>
+ <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
+ <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.247cm" style:contextual-spacing="false" fo:line-height="115%"/>
+ </style:style>
+ <style:style style:name="Title" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="chapter">
+ <style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
+ <style:text-properties fo:font-size="28pt" fo:font-weight="bold" style:font-size-asian="28pt" style:font-weight-asian="bold" style:font-size-complex="28pt" style:font-weight-complex="bold"/>
+ </style:style>
+ <style:style style:name="Table_20_Contents" style:display-name="Table Contents" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+ <style:paragraph-properties fo:orphans="0" fo:widows="0" text:number-lines="false" text:line-number="0"/>
+ </style:style>
+ <style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="1" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false"/>
+ <style:text-properties fo:font-size="18pt" fo:font-weight="bold" style:font-size-asian="18pt" style:font-weight-asian="bold" style:font-size-complex="18pt" style:font-weight-complex="bold"/>
+ </style:style>
+ <style:style style:name="Table_20_Heading" style:display-name="Table Heading" style:family="paragraph" style:parent-style-name="Table_20_Contents" style:class="extra">
+ <style:paragraph-properties fo:text-align="center" style:justify-single-word="false" text:number-lines="false" text:line-number="0"/>
+ <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
+ </style:style>
+ <style:style style:name="Heading_20_2" style:display-name="Heading 2" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="2" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.353cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false"/>
+ <style:text-properties fo:font-size="16pt" fo:font-weight="bold" style:font-size-asian="16pt" style:font-weight-asian="bold" style:font-size-complex="16pt" style:font-weight-complex="bold"/>
+ </style:style>
+ <style:style style:name="Bullet_20_Symbols" style:display-name="Bullet Symbols" style:family="text">
+ <style:text-properties style:font-name="OpenSymbol" fo:font-family="OpenSymbol" style:font-charset="x-symbol" style:font-name-asian="OpenSymbol" style:font-family-asian="OpenSymbol" style:font-charset-asian="x-symbol" style:font-name-complex="OpenSymbol" style:font-family-complex="OpenSymbol" style:font-charset-complex="x-symbol"/>
+ </style:style>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
+ <style:style style:name="Default_20_Style.1" style:display-name="Default Style.1" style:family="table-cell">
+ <style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-top="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
+ </style:style>
+ <style:style style:name="Default_20_Style.2" style:display-name="Default Style.2" style:family="table-cell">
+ <style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
+ </style:style>
+ <style:style style:name="Default_20_Style.3" style:display-name="Default Style.3" style:family="table-cell">
+ <style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
+ </style:style>
+ <style:style style:name="Default_20_Style.4" style:display-name="Default Style.4" style:family="table-cell">
+ <style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-right="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
+ </style:style>
+ <style:style style:name="Default_20_Style.5" style:display-name="Default Style.5" style:family="table-cell">
+ <style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
+ </style:style>
+ <style:style style:name="Default_20_Style.6" style:display-name="Default Style.6" style:family="table-cell">
+ <style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
+ </style:style>
+ <style:style style:name="Default_20_Style.7" style:display-name="Default Style.7" style:family="table-cell">
+ <style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
+ </style:style>
+ <style:style style:name="Default_20_Style.8" style:display-name="Default Style.8" style:family="table-cell">
+ <style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
+ </style:style>
+ <style:style style:name="Default_20_Style.9" style:display-name="Default Style.9" style:family="table-cell">
+ <style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
+ </style:style>
+ <style:style style:name="Default_20_Style.10" style:display-name="Default Style.10" style:family="table-cell">
+ <style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-right="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
+ </style:style>
+ <style:style style:name="Default_20_Style.11" style:display-name="Default Style.11" style:family="table-cell">
+ <style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-top="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
+ </style:style>
+ <style:style style:name="Default_20_Style.12" style:display-name="Default Style.12" style:family="table-cell">
+ <style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-right="0.51pt solid #000000" fo:border-top="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
+ </style:style>
+ <style:style style:name="Default_20_Style.13" style:display-name="Default Style.13" style:family="table-cell">
+ <style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
+ </style:style>
+ <style:style style:name="Default_20_Style.14" style:display-name="Default Style.14" style:family="table-cell">
+ <style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-right="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
+ </style:style>
+ <style:style style:name="Default_20_Style.15" style:display-name="Default Style.15" style:family="table-cell">
+ <style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-top="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
+ </style:style>
+ <style:style style:name="Default_20_Style.16" style:display-name="Default Style.16" style:family="table-cell">
+ <style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
+ </style:style>
+ <table:table-template table:name="Default Style" table:first-row-end-column="row" table:first-row-start-column="row" table:last-row-end-column="row" table:last-row-start-column="row">
+ <table:first-row table:style-name="Default_20_Style.1"/>
+ <table:last-row table:style-name="Default_20_Style.2"/>
+ <table:first-column table:style-name="Default_20_Style.3"/>
+ <table:last-column table:style-name="Default_20_Style.4"/>
+ <table:body table:style-name="Default_20_Style.9"/>
+ <table:even-rows table:style-name="Default_20_Style.5"/>
+ <table:odd-rows table:style-name="Default_20_Style.6"/>
+ <table:even-columns table:style-name="Default_20_Style.7"/>
+ <table:odd-columns table:style-name="Default_20_Style.8"/>
+ <table:background table:style-name="Default_20_Style.10"/>
+ </table:table-template>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="Table1" style:family="table">
+ <style:table-properties style:width="17cm" table:align="margins"/>
+ </style:style>
+ <style:style style:name="Table1.A" style:family="table-column">
+ <style:table-column-properties style:column-width="5.667cm" style:rel-column-width="21845*"/>
+ </style:style>
+ <style:style style:name="Table1.1" style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:style>
+ <style:style style:name="Table1.A1" style:family="table-cell">
+ <style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="none" fo:border-top="0.5pt solid #000000" fo:border-bottom="0.5pt solid #000000" style:writing-mode="page">
+ <style:background-image/>
+ </style:table-cell-properties>
+ </style:style>
+ <style:style style:name="Table1.C1" style:family="table-cell">
+ <style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border="0.5pt solid #000000" style:writing-mode="page">
+ <style:background-image/>
+ </style:table-cell-properties>
+ </style:style>
+ <style:style style:name="Table1.A2" style:family="table-cell">
+ <style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.5pt solid #000000" style:writing-mode="page">
+ <style:background-image/>
+ </style:table-cell-properties>
+ </style:style>
+ <style:style style:name="Table1.C2" style:family="table-cell">
+ <style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="0.5pt solid #000000" fo:border-top="none" fo:border-bottom="0.5pt solid #000000" style:writing-mode="page">
+ <style:background-image/>
+ </style:table-cell-properties>
+ </style:style>
+ <style:style style:name="Table2" style:family="table">
+ <style:table-properties table:align="margins"/>
+ </style:style>
+ <style:style style:name="Table2.A" style:family="table-column">
+ <style:table-column-properties style:rel-column-width="21845*"/>
+ </style:style>
+ <style:style style:name="Table2.A1" style:family="table-cell">
+ <style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="none" fo:border-top="0.5pt solid #000000" fo:border-bottom="0.5pt solid #000000" style:writing-mode="page">
+ <style:background-image/>
+ </style:table-cell-properties>
+ </style:style>
+ <style:style style:name="Table2.C1" style:family="table-cell">
+ <style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border="0.5pt solid #000000" style:writing-mode="page">
+ <style:background-image/>
+ </style:table-cell-properties>
+ </style:style>
+ <style:style style:name="Table2.A2" style:family="table-cell">
+ <style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.5pt solid #000000" style:writing-mode="page">
+ <style:background-image/>
+ </style:table-cell-properties>
+ </style:style>
+ <style:style style:name="Table2.C2" style:family="table-cell">
+ <style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="0.5pt solid #000000" fo:border-top="none" fo:border-bottom="0.5pt solid #000000" style:writing-mode="page">
+ <style:background-image/>
+ </style:table-cell-properties>
+ </style:style>
+ <style:style style:name="Table3" style:family="table">
+ <style:table-properties table:align="margins"/>
+ </style:style>
+ <style:style style:name="Table3.A" style:family="table-column">
+ <style:table-column-properties style:rel-column-width="21845*"/>
+ </style:style>
+ <style:style style:name="Table3.A1" style:family="table-cell">
+ <style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="none" fo:border-top="0.5pt solid #000000" fo:border-bottom="0.5pt solid #000000" style:writing-mode="page">
+ <style:background-image/>
+ </style:table-cell-properties>
+ </style:style>
+ <style:style style:name="Table3.C1" style:family="table-cell">
+ <style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border="0.5pt solid #000000" style:writing-mode="page">
+ <style:background-image/>
+ </style:table-cell-properties>
+ </style:style>
+ <style:style style:name="Table3.A2" style:family="table-cell">
+ <style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.5pt solid #000000" style:writing-mode="page">
+ <style:background-image/>
+ </style:table-cell-properties>
+ </style:style>
+ <style:style style:name="Table3.C2" style:family="table-cell">
+ <style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="0.5pt solid #000000" fo:border-top="none" fo:border-bottom="0.5pt solid #000000" style:writing-mode="page">
+ <style:background-image/>
+ </style:table-cell-properties>
+ </style:style>
+ <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Table_20_Contents">
+ <style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
+ <style:text-properties fo:color="#000000" style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Serif" fo:font-size="12pt" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:font-size-asian="12pt" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-size-complex="12pt" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-overline-style="none" style:text-overline-color="font-color"/>
+ </style:style>
+ <style:style style:name="P4" style:family="paragraph" style:parent-style-name="Standard" style:list-style-name="L1"/>
+ <style:style style:name="P6" style:family="paragraph" style:parent-style-name="Standard" style:list-style-name="L2"/>
+ <style:style style:name="P7" style:family="paragraph" style:parent-style-name="Standard" style:list-style-name="L3"/>
+ <text:list-style style:name="L1">
+ <text:list-level-style-bullet text:level="1" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="•">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.27cm" fo:text-indent="-0.635cm" fo:margin-left="1.27cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="2" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â—¦">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.905cm" fo:text-indent="-0.635cm" fo:margin-left="1.905cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="3" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â–ª">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="2.54cm" fo:text-indent="-0.635cm" fo:margin-left="2.54cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="4" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="•">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="3.175cm" fo:text-indent="-0.635cm" fo:margin-left="3.175cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="5" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â—¦">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="3.81cm" fo:text-indent="-0.635cm" fo:margin-left="3.81cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="6" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â–ª">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="4.445cm" fo:text-indent="-0.635cm" fo:margin-left="4.445cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="7" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="•">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="5.08cm" fo:text-indent="-0.635cm" fo:margin-left="5.08cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="8" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â—¦">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="5.715cm" fo:text-indent="-0.635cm" fo:margin-left="5.715cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="9" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â–ª">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="6.35cm" fo:text-indent="-0.635cm" fo:margin-left="6.35cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="10" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="•">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="6.985cm" fo:text-indent="-0.635cm" fo:margin-left="6.985cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ </text:list-style>
+ <text:list-style style:name="L2">
+ <text:list-level-style-bullet text:level="1" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="•">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.27cm" fo:text-indent="-0.635cm" fo:margin-left="1.27cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="2" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â—¦">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.905cm" fo:text-indent="-0.635cm" fo:margin-left="1.905cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="3" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â–ª">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="2.54cm" fo:text-indent="-0.635cm" fo:margin-left="2.54cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="4" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="•">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="3.175cm" fo:text-indent="-0.635cm" fo:margin-left="3.175cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="5" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â—¦">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="3.81cm" fo:text-indent="-0.635cm" fo:margin-left="3.81cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="6" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â–ª">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="4.445cm" fo:text-indent="-0.635cm" fo:margin-left="4.445cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="7" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="•">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="5.08cm" fo:text-indent="-0.635cm" fo:margin-left="5.08cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="8" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â—¦">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="5.715cm" fo:text-indent="-0.635cm" fo:margin-left="5.715cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="9" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â–ª">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="6.35cm" fo:text-indent="-0.635cm" fo:margin-left="6.35cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="10" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="•">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="6.985cm" fo:text-indent="-0.635cm" fo:margin-left="6.985cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ </text:list-style>
+ <text:list-style style:name="L3">
+ <text:list-level-style-bullet text:level="1" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="•">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.27cm" fo:text-indent="-0.635cm" fo:margin-left="1.27cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="2" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â—¦">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.905cm" fo:text-indent="-0.635cm" fo:margin-left="1.905cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="3" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â–ª">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="2.54cm" fo:text-indent="-0.635cm" fo:margin-left="2.54cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="4" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="•">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="3.175cm" fo:text-indent="-0.635cm" fo:margin-left="3.175cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="5" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â—¦">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="3.81cm" fo:text-indent="-0.635cm" fo:margin-left="3.81cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="6" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â–ª">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="4.445cm" fo:text-indent="-0.635cm" fo:margin-left="4.445cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="7" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="•">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="5.08cm" fo:text-indent="-0.635cm" fo:margin-left="5.08cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="8" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â—¦">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="5.715cm" fo:text-indent="-0.635cm" fo:margin-left="5.715cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="9" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="â–ª">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="6.35cm" fo:text-indent="-0.635cm" fo:margin-left="6.35cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="10" text:style-name="Bullet_20_Symbols" style:num-suffix="." text:bullet-char="•">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="6.985cm" fo:text-indent="-0.635cm" fo:margin-left="6.985cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-bullet>
+ </text:list-style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text>
+ <text:sequence-decls>
+ <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+ </text:sequence-decls>
+ <text:p text:style-name="Title"><text:title>Test File for Simple Tables With or Without a Properly Marked Header Row</text:title></text:p>
+ <text:h text:style-name="Heading_20_1" text:outline-level="1">Table with Header Row (Repeat First Row)</text:h>
+ <table:table table:name="Table1" table:style-name="Table1" table:template-name="Default Style">
+ <table:table-column table:style-name="Table1.A" table:number-columns-repeated="3"/>
+ <table:table-header-rows>
+ <table:table-row table:style-name="Table1.1">
+ <table:table-cell table:style-name="Table1.A1" office:value-type="string">
+ <text:p text:style-name="P1"/>
+ </table:table-cell>
+ <table:table-cell table:style-name="Table1.A1" office:value-type="string">
+ <text:p text:style-name="P1">Average starting score</text:p>
+ </table:table-cell>
+ <table:table-cell table:style-name="Table1.C1" office:value-type="string">
+ <text:p text:style-name="P1">Average improved score</text:p>
+ </table:table-cell>
+ </table:table-row>
+ </table:table-header-rows>
+ <table:table-row table:style-name="Table1.1">
+ <table:table-cell table:style-name="Table1.A2" office:value-type="string">
+ <text:p text:style-name="P1">Images</text:p>
+ </table:table-cell>
+ <table:table-cell table:style-name="Table1.A2" office:value-type="string">
+ <text:p text:style-name="P1">35%</text:p>
+ </table:table-cell>
+ <table:table-cell table:style-name="Table1.C2" office:value-type="string">
+ <text:p text:style-name="P1">91%</text:p>
+ </table:table-cell>
+ </table:table-row>
+ </table:table>
+ <text:list text:style-name="L1">
+ <text:list-item>
+ <text:p text:style-name="P4">This table is fine and the LibreOffice Accessibility Checker does not flag it.</text:p>
+ </text:list-item>
+ <text:list-item>
+ <text:p text:style-name="P4">After exporting the file to PDF (PDF/UA), the first row is tagged as a TR with tree TH elements. This is as expected.</text:p>
+ </text:list-item>
+ <text:list-item>
+ <text:p text:style-name="P4">When the PDF file is checked with PAC2021, there is an error for each TH element because the scope attribute is set to “None†instead of “Columnâ€. This is a bug in LibreOffice’s PDF export.</text:p>
+ </text:list-item>
+ </text:list>
+ <text:h text:style-name="Heading_20_1" text:outline-level="1">Table with Table Heading Style in First Row (Repeat First Row not set)</text:h>
+ <table:table table:name="Table2" table:style-name="Table2" table:template-name="Default Style">
+ <table:table-column table:style-name="Table2.A" table:number-columns-repeated="3"/>
+ <table:table-row>
+ <table:table-cell table:style-name="Table2.A1" office:value-type="string">
+ <text:p text:style-name="Table_20_Heading"/>
+ </table:table-cell>
+ <table:table-cell table:style-name="Table2.A1" office:value-type="string">
+ <text:p text:style-name="Table_20_Heading">Average starting score</text:p>
+ </table:table-cell>
+ <table:table-cell table:style-name="Table2.C1" office:value-type="string">
+ <text:p text:style-name="Table_20_Heading">Average improved score</text:p>
+ </table:table-cell>
+ </table:table-row>
+ <table:table-row>
+ <table:table-cell table:style-name="Table2.A2" office:value-type="string">
+ <text:p text:style-name="P1">Images</text:p>
+ </table:table-cell>
+ <table:table-cell table:style-name="Table2.A2" office:value-type="string">
+ <text:p text:style-name="P1">35%</text:p>
+ </table:table-cell>
+ <table:table-cell table:style-name="Table2.C2" office:value-type="string">
+ <text:p text:style-name="P1">91%</text:p>
+ </table:table-cell>
+ </table:table-row>
+ </table:table>
+ <text:list text:style-name="L2">
+ <text:list-item>
+ <text:p text:style-name="P6">For this table, the LibreOffice Accessibility Checker should warn the author that the first row should be marked as a repeatable row in the Table Properties.</text:p>
+ </text:list-item>
+ <text:list-item>
+ <text:p text:style-name="P6">After exporting the file to PDF (PDF/UA), the first row is tagged as a TR with tree TH elements. This is good, even though it is not what was expected.</text:p>
+ </text:list-item>
+ <text:list-item>
+ <text:p text:style-name="P6">When the PDF file is checked with PAC2021, there is an error for each TH element because the scope attribute is set to “None†instead of “Columnâ€. This is a bug in LibreOffice’s PDF export.</text:p>
+ </text:list-item>
+ </text:list>
+ <text:h text:style-name="Heading_20_1" text:outline-level="1">Table with Heading 2 Style in First Row (Repeat First Row not set)</text:h>
+ <table:table table:name="Table3" table:style-name="Table3" table:template-name="Default Style">
+ <table:table-column table:style-name="Table3.A" table:number-columns-repeated="3"/>
+ <table:table-row>
+ <table:table-cell table:style-name="Table3.A1" office:value-type="string">
+ <text:h text:style-name="Heading_20_2" text:outline-level="2"/>
+ </table:table-cell>
+ <table:table-cell table:style-name="Table3.A1" office:value-type="string">
+ <text:h text:style-name="Heading_20_2" text:outline-level="2">Average starting score</text:h>
+ </table:table-cell>
+ <table:table-cell table:style-name="Table3.C1" office:value-type="string">
+ <text:h text:style-name="Heading_20_2" text:outline-level="2">Average improved score</text:h>
+ </table:table-cell>
+ </table:table-row>
+ <table:table-row>
+ <table:table-cell table:style-name="Table3.A2" office:value-type="string">
+ <text:p text:style-name="P1">Images</text:p>
+ </table:table-cell>
+ <table:table-cell table:style-name="Table3.A2" office:value-type="string">
+ <text:p text:style-name="P1">35%</text:p>
+ </table:table-cell>
+ <table:table-cell table:style-name="Table3.C2" office:value-type="string">
+ <text:p text:style-name="P1">91%</text:p>
+ </table:table-cell>
+ </table:table-row>
+ </table:table>
+ <text:list text:style-name="L3">
+ <text:list-item>
+ <text:p text:style-name="P7">Error flagged by the LibreOffice Accessibility Checker: “Tables must not contain headings.†This error is justified.</text:p>
+ </text:list-item>
+ <text:list-item>
+ <text:p text:style-name="P7">After exporting the file to PDF (PDF/UA), the first row is tagged as a TR with tree TD elements, each of which contains a H2 element. This is as expected: Heading x styles should not be used to mark table headings.</text:p>
+ </text:list-item>
+ <text:list-item>
+ <text:p text:style-name="P7">When the PDF file is checked with PAC2021, no errors are reported for this table. This is a shortcoming of PAC2021, which is outside the scope of LibreOffice.</text:p>
+ </text:list-item>
+ </text:list>
+ </office:text>
+ </office:body>
+</office:document> \ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/data/WG7100-Preface.odt b/vcl/qa/cppunit/pdfexport/data/WG7100-Preface.odt
new file mode 100644
index 0000000000..05e2bb1840
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/WG7100-Preface.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/alternativeText.fodp b/vcl/qa/cppunit/pdfexport/data/alternativeText.fodp
new file mode 100644
index 0000000000..d77b631408
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/alternativeText.fodp
@@ -0,0 +1,489 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xmlns:smil="urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0" xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.presentation">
+ <office:meta><meta:creation-date>2022-05-19T20:19:11.610154378</meta:creation-date><dc:date>2022-05-20T10:09:03.143295257</dc:date><meta:editing-duration>PT4M52S</meta:editing-duration><meta:editing-cycles>5</meta:editing-cycles><meta:generator>LibreOfficeDev/7.4.0.0.alpha1$Linux_X86_64 LibreOffice_project/5f2dac9510bb511eaf08418342218000945cf180</meta:generator><meta:document-statistic meta:object-count="24"/></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="Bitstream Vera Sans" svg:font-family="'Bitstream Vera Sans'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="DejaVu Sans" svg:font-family="'DejaVu Sans'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Lohit Devanagari" svg:font-family="'Lohit Devanagari'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Noto Sans" svg:font-family="'Noto Sans'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Noto Sans CJK SC" svg:font-family="'Noto Sans CJK SC'" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <draw:gradient draw:name="Filled" draw:style="linear" draw:start-color="#ffffff" draw:end-color="#cccccc" draw:start-intensity="100%" draw:end-intensity="100%" draw:angle="30deg" draw:border="0%"/>
+ <draw:gradient draw:name="Filled_20_Blue" draw:display-name="Filled Blue" draw:style="linear" draw:start-color="#729fcf" draw:end-color="#355269" draw:start-intensity="100%" draw:end-intensity="100%" draw:angle="30deg" draw:border="0%"/>
+ <draw:gradient draw:name="Filled_20_Green" draw:display-name="Filled Green" draw:style="linear" draw:start-color="#77bc65" draw:end-color="#127622" draw:start-intensity="100%" draw:end-intensity="100%" draw:angle="30deg" draw:border="0%"/>
+ <draw:gradient draw:name="Filled_20_Red" draw:display-name="Filled Red" draw:style="linear" draw:start-color="#ff6d6d" draw:end-color="#c9211e" draw:start-intensity="100%" draw:end-intensity="100%" draw:angle="30deg" draw:border="0%"/>
+ <draw:gradient draw:name="Filled_20_Yellow" draw:display-name="Filled Yellow" draw:style="linear" draw:start-color="#ffde59" draw:end-color="#b47804" draw:start-intensity="100%" draw:end-intensity="100%" draw:angle="30deg" draw:border="0%"/>
+ <draw:gradient draw:name="Shapes" draw:style="rectangular" draw:cx="50%" draw:cy="50%" draw:start-color="#cccccc" draw:end-color="#ffffff" draw:start-intensity="100%" draw:end-intensity="100%" draw:angle="0deg" draw:border="0%"/>
+ <draw:marker draw:name="Arrow" svg:viewBox="0 0 20 30" svg:d="M10 0l-10 30h20z"/>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:punctuation-wrap="simple" style:line-break="strict" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" loext:color-lum-mod="100%" loext:color-lum-off="0%" style:font-name="Liberation Serif" fo:font-size="24pt" fo:language="es" fo:country="ES" style:font-name-asian="DejaVu Sans" style:font-size-asian="24pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Bitstream Vera Sans" style:font-size-complex="24pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:style style:name="standard" style:family="graphic">
+ <style:graphic-properties draw:stroke="solid" svg:stroke-width="0cm" svg:stroke-color="#3465a4" draw:marker-start-width="0.2cm" draw:marker-start-center="false" draw:marker-end-width="0.2cm" draw:marker-end-center="false" draw:fill="solid" draw:fill-color="#729fcf" draw:textarea-horizontal-align="justify" fo:padding-top="0.125cm" fo:padding-bottom="0.125cm" fo:padding-left="0.25cm" fo:padding-right="0.25cm" fo:wrap-option="wrap" draw:shadow="hidden" draw:shadow-offset-x="0.2cm" draw:shadow-offset-y="0.2cm" draw:shadow-color="#808080">
+ <text:list-style style:name="standard">
+ <text:list-level-style-bullet text:level="1" text:bullet-char="â—">
+ <style:list-level-properties text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="2" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="0.6cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="3" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="1.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="4" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="1.8cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="5" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="2.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="6" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="3cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="7" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="3.6cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="8" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="4.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="9" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="4.8cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="10" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="5.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ </text:list-style>
+ </style:graphic-properties>
+ <style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-top="0cm" fo:margin-bottom="0cm" fo:line-height="100%" fo:text-indent="0cm"/>
+ <style:text-properties fo:font-variant="normal" fo:text-transform="none" style:use-window-font-color="true" loext:opacity="0%" loext:color-lum-mod="100%" loext:color-lum-off="0%" style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="18pt" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:letter-kerning="true" style:font-name-asian="Noto Sans CJK SC" style:font-family-asian="'Noto Sans CJK SC'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="18pt" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-name-complex="Lohit Devanagari" style:font-family-complex="'Lohit Devanagari'" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="18pt" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-emphasize="none" style:font-relief="none" style:text-overline-style="none" style:text-overline-color="font-color"/>
+ </style:style>
+ <style:style style:name="objectwithoutfill" style:family="graphic" style:parent-style-name="standard">
+ <style:graphic-properties draw:fill="none"/>
+ </style:style>
+ <style:style style:name="Object_20_with_20_no_20_fill_20_and_20_no_20_line" style:display-name="Object with no fill and no line" style:family="graphic" style:parent-style-name="standard">
+ <style:graphic-properties draw:stroke="none" draw:fill="none"/>
+ </style:style>
+ <style:style style:name="Text" style:family="graphic">
+ <style:graphic-properties draw:stroke="solid" svg:stroke-color="#cccccc" draw:fill="solid" draw:fill-color="#eeeeee"/>
+ <style:text-properties style:font-name="Noto Sans" fo:font-family="'Noto Sans'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ </style:style>
+ <style:style style:name="A4" style:family="graphic" style:parent-style-name="Text">
+ <style:graphic-properties draw:fill="none"/>
+ <style:text-properties fo:font-size="18pt"/>
+ </style:style>
+ <style:style style:name="Title_20_A4" style:display-name="Title A4" style:family="graphic" style:parent-style-name="A4">
+ <style:graphic-properties draw:stroke="none"/>
+ <style:text-properties fo:font-size="44pt"/>
+ </style:style>
+ <style:style style:name="Heading_20_A4" style:display-name="Heading A4" style:family="graphic" style:parent-style-name="A4">
+ <style:graphic-properties draw:stroke="none"/>
+ <style:text-properties fo:font-size="24pt"/>
+ </style:style>
+ <style:style style:name="Text_20_A4" style:display-name="Text A4" style:family="graphic" style:parent-style-name="A4">
+ <style:graphic-properties draw:stroke="none"/>
+ </style:style>
+ <style:style style:name="A4" style:family="graphic" style:parent-style-name="Text">
+ <style:graphic-properties draw:fill="none"/>
+ <style:text-properties fo:font-size="18pt"/>
+ </style:style>
+ <style:style style:name="Title_20_A0" style:display-name="Title A0" style:family="graphic" style:parent-style-name="A4">
+ <style:graphic-properties draw:stroke="none"/>
+ <style:text-properties fo:font-size="96pt"/>
+ </style:style>
+ <style:style style:name="Heading_20_A0" style:display-name="Heading A0" style:family="graphic" style:parent-style-name="A4">
+ <style:graphic-properties draw:stroke="none"/>
+ <style:text-properties fo:font-size="71.9000015258789pt"/>
+ </style:style>
+ <style:style style:name="Text_20_A0" style:display-name="Text A0" style:family="graphic" style:parent-style-name="A4">
+ <style:graphic-properties draw:stroke="none"/>
+ </style:style>
+ <style:style style:name="Graphic" style:family="graphic">
+ <style:graphic-properties draw:fill="solid" draw:fill-color="#ffffff"/>
+ <style:text-properties style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="18pt"/>
+ </style:style>
+ <style:style style:name="Shapes" style:family="graphic" style:parent-style-name="Graphic">
+ <style:graphic-properties draw:stroke="none" draw:fill="gradient" draw:fill-gradient-name="Shapes"/>
+ <style:text-properties fo:font-size="14pt" fo:font-weight="bold"/>
+ </style:style>
+ <style:style style:name="Filled" style:family="graphic" style:parent-style-name="Shapes">
+ <style:graphic-properties draw:fill="gradient" draw:fill-gradient-name="Filled"/>
+ </style:style>
+ <style:style style:name="Filled_20_Blue" style:display-name="Filled Blue" style:family="graphic" style:parent-style-name="Filled">
+ <style:graphic-properties draw:fill-gradient-name="Filled_20_Blue"/>
+ <style:text-properties fo:color="#ffffff" loext:opacity="100%" loext:color-lum-mod="100%" loext:color-lum-off="0%"/>
+ </style:style>
+ <style:style style:name="Filled_20_Green" style:display-name="Filled Green" style:family="graphic" style:parent-style-name="Filled">
+ <style:graphic-properties draw:fill-gradient-name="Filled_20_Green"/>
+ <style:text-properties fo:color="#ffffff" loext:opacity="100%" loext:color-lum-mod="100%" loext:color-lum-off="0%" style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ </style:style>
+ <style:style style:name="Filled_20_Red" style:display-name="Filled Red" style:family="graphic" style:parent-style-name="Filled">
+ <style:graphic-properties draw:fill-gradient-name="Filled_20_Red"/>
+ <style:text-properties fo:color="#ffffff" loext:opacity="100%" loext:color-lum-mod="100%" loext:color-lum-off="0%"/>
+ </style:style>
+ <style:style style:name="Filled_20_Yellow" style:display-name="Filled Yellow" style:family="graphic" style:parent-style-name="Filled">
+ <style:graphic-properties draw:fill-gradient-name="Filled_20_Yellow"/>
+ <style:text-properties fo:color="#ffffff" loext:opacity="100%" loext:color-lum-mod="100%" loext:color-lum-off="0%"/>
+ </style:style>
+ <style:style style:name="Outlined" style:family="graphic" style:parent-style-name="Shapes">
+ <style:graphic-properties draw:stroke="solid" svg:stroke-width="0.081cm" svg:stroke-color="#000000" draw:fill="none"/>
+ </style:style>
+ <style:style style:name="Outlined_20_Blue" style:display-name="Outlined Blue" style:family="graphic" style:parent-style-name="Outlined">
+ <style:graphic-properties svg:stroke-color="#355269"/>
+ <style:text-properties fo:color="#355269" loext:opacity="100%" loext:color-lum-mod="100%" loext:color-lum-off="0%"/>
+ </style:style>
+ <style:style style:name="Outlined_20_Green" style:display-name="Outlined Green" style:family="graphic" style:parent-style-name="Outlined">
+ <style:graphic-properties svg:stroke-color="#127622"/>
+ <style:text-properties fo:color="#127622" loext:opacity="100%" loext:color-lum-mod="100%" loext:color-lum-off="0%"/>
+ </style:style>
+ <style:style style:name="Outlined_20_Red" style:display-name="Outlined Red" style:family="graphic" style:parent-style-name="Outlined">
+ <style:graphic-properties svg:stroke-color="#c9211e"/>
+ <style:text-properties fo:color="#c9211e" loext:opacity="100%" loext:color-lum-mod="100%" loext:color-lum-off="0%"/>
+ </style:style>
+ <style:style style:name="Outlined_20_Yellow" style:display-name="Outlined Yellow" style:family="graphic" style:parent-style-name="Outlined">
+ <style:graphic-properties draw:stroke="solid" svg:stroke-color="#b47804"/>
+ <style:text-properties fo:color="#b47804" loext:opacity="100%" loext:color-lum-mod="100%" loext:color-lum-off="0%"/>
+ </style:style>
+ <style:style style:name="Lines" style:family="graphic" style:parent-style-name="Graphic">
+ <style:graphic-properties draw:stroke="solid" svg:stroke-color="#000000" draw:fill="none"/>
+ </style:style>
+ <style:style style:name="Arrow_20_Line" style:display-name="Arrow Line" style:family="graphic" style:parent-style-name="Lines">
+ <style:graphic-properties draw:marker-start="Arrow" draw:marker-start-width="0.2cm" draw:marker-end="Arrow" draw:marker-end-width="0.2cm" draw:show-unit="true"/>
+ </style:style>
+ <style:style style:name="Arrow_20_Dashed" style:display-name="Arrow Dashed" style:family="graphic" style:parent-style-name="Lines">
+ <style:graphic-properties draw:stroke="dash"/>
+ </style:style>
+ <style:style style:name="Default-background" style:family="presentation">
+ <style:graphic-properties draw:stroke="none" draw:fill="none"/>
+ <style:text-properties style:letter-kerning="true"/>
+ </style:style>
+ <style:style style:name="Default-backgroundobjects" style:family="presentation">
+ <style:graphic-properties draw:textarea-horizontal-align="justify" draw:shadow="hidden" draw:shadow-offset-x="0.2cm" draw:shadow-offset-y="0.2cm" draw:shadow-color="#808080"/>
+ <style:text-properties style:letter-kerning="true"/>
+ </style:style>
+ <style:style style:name="Default-notes" style:family="presentation">
+ <style:graphic-properties draw:stroke="none" draw:fill="none"/>
+ <style:paragraph-properties fo:margin-left="0.6cm" fo:margin-right="0cm" fo:text-indent="-0.6cm"/>
+ <style:text-properties fo:font-variant="normal" fo:text-transform="none" style:use-window-font-color="true" loext:opacity="0%" loext:color-lum-mod="100%" loext:color-lum-off="0%" style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="20pt" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:letter-kerning="true" fo:background-color="transparent" style:font-name-asian="Noto Sans CJK SC" style:font-family-asian="'Noto Sans CJK SC'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="20pt" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-name-complex="Lohit Devanagari" style:font-family-complex="'Lohit Devanagari'" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="20pt" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-emphasize="none" style:font-relief="none" style:text-overline-style="none" style:text-overline-color="font-color"/>
+ </style:style>
+ <style:style style:name="Default-outline1" style:family="presentation">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:auto-grow-height="false" draw:fit-to-size="false" style:shrink-to-fit="true">
+ <text:list-style style:name="Default-outline1">
+ <text:list-level-style-bullet text:level="1" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="0.3cm" text:min-label-width="0.9cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="2" text:bullet-char="–">
+ <style:list-level-properties text:space-before="1.5cm" text:min-label-width="0.9cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="75%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="3" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="2.8cm" text:min-label-width="0.8cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="4" text:bullet-char="–">
+ <style:list-level-properties text:space-before="4.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="75%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="5" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="5.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="6" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="6.6cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="7" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="7.8cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="8" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="9cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="9" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="10.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="10" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="11.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ </text:list-style>
+ </style:graphic-properties>
+ <style:paragraph-properties fo:margin-top="0.5cm" fo:margin-bottom="0cm"/>
+ <style:text-properties fo:font-variant="normal" fo:text-transform="none" style:use-window-font-color="true" loext:opacity="0%" loext:color-lum-mod="100%" loext:color-lum-off="0%" style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="32pt" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:letter-kerning="true" fo:background-color="transparent" style:font-name-asian="Noto Sans CJK SC" style:font-family-asian="'Noto Sans CJK SC'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="32pt" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-name-complex="Lohit Devanagari" style:font-family-complex="'Lohit Devanagari'" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="32pt" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-emphasize="none" style:font-relief="none" style:text-overline-style="none" style:text-overline-color="font-color"/>
+ </style:style>
+ <style:style style:name="Default-outline2" style:family="presentation" style:parent-style-name="Default-outline1">
+ <style:paragraph-properties fo:margin-top="0.4cm" fo:margin-bottom="0cm"/>
+ <style:text-properties fo:font-size="28pt" style:font-size-asian="28pt" style:font-size-complex="28pt"/>
+ </style:style>
+ <style:style style:name="Default-outline3" style:family="presentation" style:parent-style-name="Default-outline2">
+ <style:paragraph-properties fo:margin-top="0.3cm" fo:margin-bottom="0cm"/>
+ <style:text-properties fo:font-size="24pt" style:font-size-asian="24pt" style:font-size-complex="24pt"/>
+ </style:style>
+ <style:style style:name="Default-outline4" style:family="presentation" style:parent-style-name="Default-outline3">
+ <style:paragraph-properties fo:margin-top="0.2cm" fo:margin-bottom="0cm"/>
+ <style:text-properties fo:font-size="20pt" style:font-size-asian="20pt" style:font-size-complex="20pt"/>
+ </style:style>
+ <style:style style:name="Default-outline5" style:family="presentation" style:parent-style-name="Default-outline4">
+ <style:paragraph-properties fo:margin-top="0.1cm" fo:margin-bottom="0cm"/>
+ <style:text-properties fo:font-size="20pt" style:font-size-asian="20pt" style:font-size-complex="20pt"/>
+ </style:style>
+ <style:style style:name="Default-outline6" style:family="presentation" style:parent-style-name="Default-outline5">
+ <style:paragraph-properties fo:margin-top="0.1cm" fo:margin-bottom="0cm"/>
+ <style:text-properties fo:font-size="20pt" style:font-size-asian="20pt" style:font-size-complex="20pt"/>
+ </style:style>
+ <style:style style:name="Default-outline7" style:family="presentation" style:parent-style-name="Default-outline6">
+ <style:paragraph-properties fo:margin-top="0.1cm" fo:margin-bottom="0cm"/>
+ <style:text-properties fo:font-size="20pt" style:font-size-asian="20pt" style:font-size-complex="20pt"/>
+ </style:style>
+ <style:style style:name="Default-outline8" style:family="presentation" style:parent-style-name="Default-outline7">
+ <style:paragraph-properties fo:margin-top="0.1cm" fo:margin-bottom="0cm"/>
+ <style:text-properties fo:font-size="20pt" style:font-size-asian="20pt" style:font-size-complex="20pt"/>
+ </style:style>
+ <style:style style:name="Default-outline9" style:family="presentation" style:parent-style-name="Default-outline8">
+ <style:paragraph-properties fo:margin-top="0.1cm" fo:margin-bottom="0cm"/>
+ <style:text-properties fo:font-size="20pt" style:font-size-asian="20pt" style:font-size-complex="20pt"/>
+ </style:style>
+ <style:style style:name="Default-subtitle" style:family="presentation">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:textarea-vertical-align="middle">
+ <text:list-style style:name="Default-subtitle">
+ <text:list-level-style-bullet text:level="1" text:bullet-char="â—">
+ <style:list-level-properties text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="2" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="0.6cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="3" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="1.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="4" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="1.8cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="5" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="2.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="6" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="3cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="7" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="3.6cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="8" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="4.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="9" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="4.8cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="10" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="5.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ </text:list-style>
+ </style:graphic-properties>
+ <style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:text-align="center" fo:text-indent="0cm"/>
+ <style:text-properties fo:font-variant="normal" fo:text-transform="none" style:use-window-font-color="true" loext:opacity="0%" loext:color-lum-mod="100%" loext:color-lum-off="0%" style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="32pt" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:letter-kerning="true" fo:background-color="transparent" style:font-name-asian="Noto Sans CJK SC" style:font-family-asian="'Noto Sans CJK SC'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="32pt" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-name-complex="Lohit Devanagari" style:font-family-complex="'Lohit Devanagari'" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="32pt" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-emphasize="none" style:font-relief="none" style:text-overline-style="none" style:text-overline-color="font-color"/>
+ </style:style>
+ <style:style style:name="Default-title" style:family="presentation">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:textarea-vertical-align="middle">
+ <text:list-style style:name="Default-title">
+ <text:list-level-style-bullet text:level="1" text:bullet-char="â—">
+ <style:list-level-properties text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="2" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="0.6cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="3" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="1.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="4" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="1.8cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="5" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="2.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="6" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="3cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="7" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="3.6cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="8" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="4.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="9" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="4.8cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="10" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="5.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ </text:list-style>
+ </style:graphic-properties>
+ <style:paragraph-properties fo:text-align="center"/>
+ <style:text-properties fo:font-variant="normal" fo:text-transform="none" style:use-window-font-color="true" loext:opacity="0%" loext:color-lum-mod="100%" loext:color-lum-off="0%" style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="44pt" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:letter-kerning="true" fo:background-color="transparent" style:font-name-asian="Noto Sans CJK SC" style:font-family-asian="'Noto Sans CJK SC'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="44pt" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-name-complex="Lohit Devanagari" style:font-family-complex="'Lohit Devanagari'" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="44pt" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-emphasize="none" style:font-relief="none" style:text-overline-style="none" style:text-overline-color="font-color"/>
+ </style:style>
+ <style:presentation-page-layout style:name="AL0T26">
+ <presentation:placeholder presentation:object="handout" svg:x="2.058cm" svg:y="1.743cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ <presentation:placeholder presentation:object="handout" svg:x="15.414cm" svg:y="1.743cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ <presentation:placeholder presentation:object="handout" svg:x="2.058cm" svg:y="3.612cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ <presentation:placeholder presentation:object="handout" svg:x="15.414cm" svg:y="3.612cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ <presentation:placeholder presentation:object="handout" svg:x="2.058cm" svg:y="5.481cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ <presentation:placeholder presentation:object="handout" svg:x="15.414cm" svg:y="5.481cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ </style:presentation-page-layout>
+ <style:presentation-page-layout style:name="AL1T0">
+ <presentation:placeholder presentation:object="title" svg:x="2.058cm" svg:y="1.743cm" svg:width="23.912cm" svg:height="3.507cm"/>
+ <presentation:placeholder presentation:object="subtitle" svg:x="2.058cm" svg:y="5.838cm" svg:width="23.912cm" svg:height="13.23cm"/>
+ </style:presentation-page-layout>
+ </office:styles>
+ <office:automatic-styles>
+ <style:page-layout style:name="PM0">
+ <style:page-layout-properties fo:margin-top="0cm" fo:margin-bottom="0cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:page-width="21cm" fo:page-height="29.7cm" style:print-orientation="portrait"/>
+ </style:page-layout>
+ <style:page-layout style:name="PM1">
+ <style:page-layout-properties fo:margin-top="0cm" fo:margin-bottom="0cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:page-width="28cm" fo:page-height="15.75cm" style:print-orientation="landscape"/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="border" draw:fill="none"/>
+ </style:style>
+ <style:style style:name="dp2" style:family="drawing-page">
+ <style:drawing-page-properties presentation:display-header="true" presentation:display-footer="true" presentation:display-page-number="false" presentation:display-date-time="true"/>
+ </style:style>
+ <style:style style:name="dp3" style:family="drawing-page">
+ <style:drawing-page-properties presentation:background-visible="true" presentation:background-objects-visible="true" presentation:display-footer="true" presentation:display-page-number="false" presentation:display-date-time="true"/>
+ </style:style>
+ <style:style style:name="gr1" style:family="graphic" style:parent-style-name="standard">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:fill-color="#ffffff" draw:auto-grow-height="false" fo:min-height="1.485cm"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="gr2" style:family="graphic" style:parent-style-name="standard">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:fill-color="#ffffff" draw:textarea-vertical-align="bottom" draw:auto-grow-height="false" fo:min-height="1.485cm"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="gr3" style:family="graphic" style:parent-style-name="Object_20_with_20_no_20_fill_20_and_20_no_20_line">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:textarea-horizontal-align="center" draw:textarea-vertical-align="middle" draw:color-mode="standard" draw:luminance="0%" draw:contrast="0%" draw:gamma="100%" draw:red="0%" draw:green="0%" draw:blue="0%" fo:clip="rect(0cm, 0cm, 0cm, 0cm)" draw:image-opacity="100%" style:mirror="none"/>
+ </style:style>
+ <style:style style:name="gr4" style:family="graphic">
+ <style:graphic-properties style:protect="size"/>
+ </style:style>
+ <style:style style:name="pr1" style:family="presentation" style:parent-style-name="Default-backgroundobjects">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:fill-color="#ffffff" draw:auto-grow-height="false" fo:min-height="1.086cm"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="pr2" style:family="presentation" style:parent-style-name="Default-backgroundobjects">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:fill-color="#ffffff" draw:auto-grow-height="false" fo:min-height="1.485cm"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="pr3" style:family="presentation" style:parent-style-name="Default-backgroundobjects">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:fill-color="#ffffff" draw:textarea-vertical-align="bottom" draw:auto-grow-height="false" fo:min-height="1.485cm"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="pr4" style:family="presentation" style:parent-style-name="Default-notes">
+ <style:graphic-properties draw:fill-color="#ffffff" fo:min-height="13.364cm"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="P1" style:family="paragraph">
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="P3" style:family="paragraph">
+ <style:paragraph-properties fo:text-align="end" style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="T1" style:family="text">
+ <style:text-properties fo:font-size="14pt" style:font-size-asian="14pt" style:font-size-complex="14pt"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <draw:layer-set>
+ <draw:layer draw:name="layout"/>
+ <draw:layer draw:name="background"/>
+ <draw:layer draw:name="backgroundobjects"/>
+ <draw:layer draw:name="controls"/>
+ <draw:layer draw:name="measurelines"/>
+ </draw:layer-set>
+ <style:handout-master presentation:presentation-page-layout-name="AL0T26" style:page-layout-name="PM0" draw:style-name="dp2">
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="5.061cm" svg:x="1cm" svg:y="3.742cm"/>
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="5.061cm" svg:x="1cm" svg:y="12.318cm"/>
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="5.061cm" svg:x="1cm" svg:y="20.894cm"/>
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="5.061cm" svg:x="11cm" svg:y="3.742cm"/>
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="5.061cm" svg:x="11cm" svg:y="12.318cm"/>
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="5.061cm" svg:x="11cm" svg:y="20.894cm"/>
+ <draw:frame draw:style-name="gr1" draw:text-style-name="P2" draw:layer="backgroundobjects" svg:width="9.113cm" svg:height="1.484cm" svg:x="0cm" svg:y="0cm" presentation:class="header">
+ <draw:text-box>
+ <text:p text:style-name="P1"><text:span text:style-name="T1"><presentation:header/></text:span></text:p>
+ </draw:text-box>
+ </draw:frame>
+ <draw:frame draw:style-name="gr1" draw:text-style-name="P2" draw:layer="backgroundobjects" svg:width="9.113cm" svg:height="1.484cm" svg:x="11.886cm" svg:y="0cm" presentation:class="date-time">
+ <draw:text-box>
+ <text:p text:style-name="P3"><text:span text:style-name="T1"><presentation:date-time/></text:span></text:p>
+ </draw:text-box>
+ </draw:frame>
+ <draw:frame draw:style-name="gr2" draw:text-style-name="P2" draw:layer="backgroundobjects" svg:width="9.113cm" svg:height="1.484cm" svg:x="0cm" svg:y="28.215cm" presentation:class="footer">
+ <draw:text-box>
+ <text:p text:style-name="P1"><text:span text:style-name="T1"><presentation:footer/></text:span></text:p>
+ </draw:text-box>
+ </draw:frame>
+ <draw:frame draw:style-name="gr2" draw:text-style-name="P2" draw:layer="backgroundobjects" svg:width="9.113cm" svg:height="1.484cm" svg:x="11.886cm" svg:y="28.215cm" presentation:class="page-number">
+ <draw:text-box>
+ <text:p text:style-name="P3"><text:span text:style-name="T1"><text:page-number>&lt;number&gt;</text:page-number></text:span></text:p>
+ </draw:text-box>
+ </draw:frame>
+ </style:handout-master>
+ </office:master-styles>
+ <office:body>
+ <office:presentation>
+ <draw:page draw:name="page1" draw:style-name="dp3" draw:master-page-name="Default" presentation:presentation-page-layout-name="AL1T0">
+ <draw:frame draw:style-name="gr3" draw:text-style-name="P10" draw:layer="layout" svg:width="17.752cm" svg:height="2.832cm" svg:x="6.248cm" svg:y="4.168cm">
+ <draw:image draw:mime-type="image/png">
+ <office:binary-data>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAAEElEQVR4nGJgAQAAAP//AwAA
+ BgAFV7+r1AAAAABJRU5ErkJggg==
+ </office:binary-data>
+ <text:p/>
+ </draw:image>
+ <svg:title>This is the text alternative</svg:title>
+ <svg:desc>This is the description</svg:desc>
+ </draw:frame>
+ <presentation:notes draw:style-name="dp2">
+ <draw:page-thumbnail draw:style-name="gr4" draw:layer="layout" svg:width="19.798cm" svg:height="11.136cm" svg:x="0.6cm" svg:y="2.257cm" draw:page-number="1" presentation:class="page"/>
+ <draw:frame presentation:style-name="pr4" draw:text-style-name="P11" draw:layer="layout" svg:width="16.799cm" svg:height="13.364cm" svg:x="2.1cm" svg:y="14.107cm" presentation:class="notes" presentation:placeholder="true">
+ <draw:text-box/>
+ </draw:frame>
+ </presentation:notes>
+ </draw:page>
+ <presentation:settings presentation:mouse-visible="false"/>
+ </office:presentation>
+ </office:body>
+</office:document>
diff --git a/vcl/qa/cppunit/pdfexport/data/bitmap-scaledown.odt b/vcl/qa/cppunit/pdfexport/data/bitmap-scaledown.odt
new file mode 100644
index 0000000000..da9b167fd9
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/bitmap-scaledown.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/content-control-rtl.docx b/vcl/qa/cppunit/pdfexport/data/content-control-rtl.docx
new file mode 100644
index 0000000000..1b5fba4555
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/content-control-rtl.docx
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/flowframe_null_ptr_deref.sample b/vcl/qa/cppunit/pdfexport/data/flowframe_null_ptr_deref.sample
new file mode 100644
index 0000000000..4610ae5d18
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/flowframe_null_ptr_deref.sample
@@ -0,0 +1,654 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+
+<head>
+ <head>
+ <head>
+ <head>
+ <title>KDE::Enterprise Homepage - Business Directory</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <meta http-equiv="Content-Style-Type" content="text/css" />
+
+ <meta http-equiv="pics-label" content='(pics-1.1 "http://www.icra.org/ratingsv02.html" comment "ICRAonline DE v2.0" l gen true for "http://www.kde.org" r (nz 1 vz 1 lz 1 oz 1 cb 1) "http://www.rsac.org/ratingsv01.html" l gen true for "http://www.kde.org" r (n 0 s 0 v 0 l 0))' />
+
+ <meta name="trademark" content="KDE e.V." />
+ <meta name="description" content="K Desktop Environment Homepage, KDE.org" />
+ <meta name="MSSmartTagsPreventParsing" content="true" />
+ <meta name="robots" content="all" />
+
+ <link rel="shortcut icon" href="favicon.ico" />
+
+<link rel="stylesheet" media="screen" type="text/css" title="Default: KDE Window Colors" href="/media/styles/standard.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Black and white" href="/media/styles/blackwhite.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Blue, mellow" href="/media/styles/bluemellow.css" /><head>
+ <title>KDE::Enterprise Homepage - Business Directory</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <meta http-equiv="Content-Style-Type" content="text/css" />
+
+ <meta http-equiv="pics-label" content='(pics-1.1 "http://www.icra.org/ratingsv02.html" comment "ICRAonline DE v2.0" l gen true for "http://www.kde.org" r (nz 1 vz 1 lz 1 oz 1 cb 1) "http://www.rsac.org/ratingsv01.html" l gen true for "http://www.kde.org" r (n 0 s 0 v 0 l 0))' />
+
+ <meta name="trademark" content="KDE e.V." />
+ <meta name="description" content="K Desktop Environment Homepage, KDE.org" />
+ <meta name="MSSmartTagsPreventParsing" content="true" />
+ <meta name="robots" content="all" />
+
+ <link rel="shortcut icon" href="favicon.ico" />
+
+<link rel="stylesheet" media="screen" type="text/css" title="Default: KDE Window Colors" href="/media/styles/standard.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Black and white" href="/media/styles/blackwhite.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Blue, mellow" href="/media/styles/bluemellow.css" /><head>
+ <title>KDE::Enterprise Homepage - Business Directory</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <meta http-equiv="Content-Style-Type" content="text/css" />
+
+ <meta http-equiv="pics-label" content='(pics-1.1 "http://www.icra.org/ratingsv02.html" comment "ICRAonline DE v2.0" l gen true for "http://www.kde.org" r (nz 1 vz 1 lz 1 oz 1 cb 1) "http://www.rsac.org/ratingsv01.html" l gen true for "http://www.kde.org" r (n 0 s 0 v 0 l 0))' />
+
+ <meta name="trademark" content="KDE e.V." />
+ <meta name="description" content="K Desktop Environment Homepage, KDE.org" />
+ <meta name="MSSmartTagsPreventParsing" content="true" />
+ <meta name="robots" content="all" />
+
+ <link rel="shortcut icon" href="favicon.ico" />
+
+<link rel="stylesheet" media="screen" type="text/css" title="Default: KDE Window Colors" href="/media/styles/standard.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Black and white" href="/media/styles/blackwhite.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Blue, mellow" href="/media/styles/bluemellow.css" /><head>
+ <title>KDE::Enterprise Homepage - Business Directory</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <meta http-equiv="Content-Style-Type" content="text/css" />
+
+ <meta http-equiv="pics-label" content='(pics-1.1 "http://www.icra.org/ratingsv02.html" comment "ICRAonline DE v2.0" l gen true for "http://www.kde.org" r (nz 1 vz 1 lz 1 oz 1 cb 1) "http://www.rsac.org/ratingsv01.html" l gen true for "http://www.kde.org" r (n 0 s 0 v 0 l 0))' />
+
+ <meta name="trademark" content="KDE e.V." />
+ <meta name="description" content="K Desktop Environment Homepage, KDE.org" />
+ <meta name="MSSmartTagsPreventParsing" content="true" />
+ <meta name="robots" content="all" />
+
+ <link rel="shortcut icon" href="favicon.ico" />
+
+<link rel="stylesheet" media="screen" type="text/css" title="Default: KDE Window Colors" href="/media/styles/standard.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Black and white" href="/media/styles/blackwhite.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Blue, mellow" href="/media/styles/bluemellow.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Classic Blue" href="/media/styles/classic.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Orange" href="/media/styles/endres.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Yellow" href="/media/styles/yellow.css" />
+
+ <link rel="stylesheet" media="print" type="text/css" href="/media/styles/print.css" />
+</head><link rel="alternate stylesheet" media="screen" type="text/css" title="Classic Blue" href="/media/styles/classic.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Orange" href="/media/styles/endres.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Yellow" href="/media/styles/yellow.css" />
+
+ <link rel="stylesheet" media="print" type="text/css" href="/media/styles/print.css" />
+</head><link rel="alternate stylesheet" media="screen" type="text/css" title="Classic Blue" href="/media/styles/classic.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Orange" href="/media/styles/endres.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Yellow" href="/media/styles/yellow.css" />
+
+ <link rel="stylesheet" media="print" type="text/css" href="/media/styles/print.css" />
+</head><link rel="alternate stylesheet" media="screen" type="text/css" title="Classic Blue" href="/media/styles/classic.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Orange" href="/media/styles/endres.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Yellow" href="/media/styles/yellow.css" />
+
+ <link rel="stylesheet" media="print" type="text/css" href="/media/styles/print.css" />
+</head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <meta http-equiv="Content-Style-Type" content="text/css" />
+
+ <meta http-equiv="pics-label" content='(pics-1.1 "http://www.icra.org/ratingsv02.html" comment "ICRAonline DE v2.0" l gen true for "http://www.kde.org" r (nz 1 vz 1 lz 1 oz 1 cb 1) "http://www.rsac.org/ratingsv01.html" l gen true for "http://www.kde.org" r (n 0 s 0 v 0 l 0))' />
+
+ <meta name="trademark" content="KDE e.V." />
+ <meta name="description" content="K Desktop Environment Homepage, KDE.org" />
+ <meta name="MSSmartTagsPreventParsing" content="true" />
+ <meta name="robots" content="all" />
+
+ <link rel="shortcut icon" href="favicon.ico" />
+
+<link rel="stylesheet" media="screen" type="text/css" title="Default: KDE Window Colors" href="/media/styles/standard.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Black and white" href="/media/styles/blackwhite.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Blue, mellow" href="/media/styles/bluemellow.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Classic Blue" href="/media/styles/classic.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Orange" href="/media/styles/endres.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Yellow" href="/media/styles/yellow.css" />
+
+ <link rel="stylesheet" media="print" type="text/css" href="/media/styles/print.css" />
+</head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <meta http-equiv="Content-Style-Type" content="text/css" />
+
+ <meta http-equiv="pics-label" content='(pics-1.1 "http://www.icra.org/ratingsv02.html" comment "ICRAonline DE v2.0" l gen true for "http://www.kde.org" r (nz 1 vz 1 lz 1 oz 1 cb 1) "http://www.rsac.org/ratingsv01.html" l gen true for "http://www.kde.org" r (n 0 s 0 v 0 l 0))' />
+
+ <meta name="trademark" content="KDE e.V." />
+ <meta name="description" content="K Desktop Environment Homepage, KDE.org" />
+ <meta name="MSSmartTagsPreventParsing" content="true" />
+ <meta name="robots" content="all" />
+
+ <link rel="shortcut icon" href="favicon.ico" />
+
+<link rel="stylesheet" media="screen" type="text/css" title="Default: KDE Window Colors" href="/media/styles/standard.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Black and white" href="/media/styles/blackwhite.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Blue, mellow" href="/media/styles/bluemellow.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Classic Blue" href="/media/styles/classic.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Orange" href="/media/styles/endres.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Yellow" href="/media/styles/yellow.css" />
+
+ <link rel="stylesheet" media="print" type="text/css" href="/media/styles/print.css" />
+</head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <meta http-equiv="Content-Style-Type" content="text/css" />
+
+ <meta http-equiv="pics-label" content='(pics-1.1 "http://www.icra.org/ratingsv02.html" comment "ICRAonline DE v2.0" l gen true for "http://www.kde.org" r (nz 1 vz 1 lz 1 oz 1 cb 1) "http://www.rsac.org/ratingsv01.html" l gen true for "http://www.kde.org" r (n 0 s 0 v 0 l 0))' />
+
+ <meta name="trademark" content="KDE e.V." />
+ <meta name="description" content="K Desktop Environment Homepage, KDE.org" />
+ <meta name="MSSmartTagsPreventParsing" content="true" />
+ <meta name="robots" content="all" />
+
+ <link rel="shortcut icon" href="favicon.ico" />
+
+<link rel="stylesheet" media="screen" type="text/css" title="Default: KDE Window Colors" href="/media/styles/standard.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Black and white" href="/media/styles/blackwhite.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Blue, mellow" href="/media/styles/bluemellow.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Classic Blue" href="/media/styles/classic.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Orange" href="/media/styles/endres.css" />
+<link rel="alternate stylesheet" media="screen" type="text/css" title="Yellow" href="/media/styles/yellow.css" />
+
+ <link rel="stylesheet" media="print" type="text/css" href="/media/styles/print.css" />
+</head>
+
+<body id="enterprisekdeorg"><table border="0" cellpadding="0" cellspacing="0" width="100%">
+<tr id="header">
+ <td id="headerpath">
+ <span class="invisible"><a href="#main" accesskey="2"><span class="invisible"><a href="#main" accesskey="2"><span class="invisible"><a href="#main" accesskey="2"><span class="invisible"><a href="#main" accesskey="2">Skip to Content</a> |</span></a> |</span></a> |</span></a> |</span>
+ Location: <a href="/" accesskey="1">KDE::Enterprise Homepage</a> / Business Directory </td>
+
+ <td id="headermenu">
+ <a href="/media/settings.php" accesskey="7" title="Customize content and look &amp; feel of the KDE.org Webpages">Settings</a><a href="/media/settings.php" accesskey="7" title="Customize content and look &amp; feel of the KDE.org Webpages">Settings</a> |
+ <a href="http://www.kde.org/family/" accesskey="3" title="A complete structural overview of the KDE.org web pages">Sitemap</a> |
+ <a href="http://www.kde.org/documentation/" accesskey="6" title="Having problems? Read the documentation">Help</a> |
+ <a href="http://www.kde.org/contact/" accesskey="10" title="Contact information for all areas of KDE">Contact Us</a>
+ </td>
+</tr>
+
+<tr id="logo">
+ <td valign="top" colspan="2">
+ <a href="../"><img alt="KDE::Enterprise Homepage" src="/media/images/kde_logo.jpg" width="296" height="79" border="0" /></a>
+ </td>
+
+ </tr>
+
+</table><table border="0" cellpadding="0" cellspacing="0" width="100%">
+<tr>
+ <td valign="top" class="menuheader" height="0"></td>
+
+ <td id="contentcolumn" valign="top" rowspan="2">
+ <div id="contentheader">&nbsp;</div>
+ <div class="invisible"><a href="#navigation" accesskey="5">Skip to Link Menu</a></div>
+ <div id="content"><div style="width:100%;">
+ <a name="main" />
+
+ <h1>Business Directory</h1>
+
+<h4>A problem has been detected with your submission</h4>
+<p>In the <b>Business Name</b> box you did not enter a valid name.</p>
+<p>Please correct the problem below and resubmit.</p>
+ <form action="addbiz.php" method="POST">
+ <table cellpadding="5">
+ <tr>
+ <td><font color="#ff0000">--&gt; </font></td>
+ <td>Business Name</td>
+ <td>
+ <input type="text" name="nameBox" maxlength="20" value="">
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>Contact Name</td>
+ <td>
+ <input type="text" name="contactBox" maxlength="50" value="Waldo Bastian">
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>Email Address</td>
+ <td>
+ <input type="text" name="emailBox" maxlength="50" value="bastian@kde.org">
+ <input type="checkbox" name="showEmailBox" value="">Publish e-mail address </td>
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>Website</td>
+ <td>
+ <input type="text" name="websiteBox" maxlength="50" value="">
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>Address</td>
+ <td>
+ <input type="text" name="add1Box" maxlength="50" value="9 Le Val Renard">
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td></td>
+ <td>
+ <input type="text" name="add2Box" maxlength="50" value="Taden">
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td></td>
+ <td>
+ <input type="text" name="add3Box" maxlength="50" value="">
+ <p>
+ If more than one address, please add your head office.
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>Postcode / Zip Code</td>
+ <td>
+ <input type="text" name="postcodeBox" maxlength="20" value="22100">
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>Country</td>
+ <td>
+ <select name="countryBox">
+<OPTION>Unspecified</OPTION>
+<OPTION>Afghanistan</OPTION>
+<OPTION>Albania</OPTION>
+<OPTION>Algeria</OPTION>
+<OPTION>Angola</OPTION>
+<OPTION>Anguilla</OPTION>
+<OPTION>Antarctic</OPTION>
+<OPTION>Antigua and Barbuda</OPTION>
+<OPTION>Argentina</OPTION>
+<OPTION>Armenia</OPTION>
+<OPTION>Aruba</OPTION>
+<OPTION>Australia</OPTION>
+<OPTION>Austria</OPTION>
+<OPTION>Azerbaijan</OPTION>
+<OPTION>Bahamas</OPTION>
+<OPTION>Bahrain</OPTION>
+<OPTION>Bangladesh</OPTION>
+<OPTION>Barbados</OPTION>
+<OPTION>Belarus</OPTION>
+<OPTION>Belgium</OPTION>
+<OPTION>Belize</OPTION>
+<OPTION>Benin</OPTION>
+<OPTION>Bermuda</OPTION>
+<OPTION>Bolivia</OPTION>
+<OPTION>Bosia and Herzegovina</OPTION>
+<OPTION>Botswana</OPTION>
+<OPTION>Bouvet Island</OPTION>
+<OPTION>Brazil</OPTION>
+<OPTION>British Indian Ocean Territory</OPTION>
+<OPTION>Brunei Darussalam</OPTION>
+<OPTION>Bulgaria</OPTION>
+<OPTION>Burkina Faso</OPTION>
+<OPTION>Burundi</OPTION>
+<OPTION>Cambodia</OPTION>
+<OPTION>Cameroon</OPTION>
+<OPTION>Canada</OPTION>
+<OPTION>Cape Verde</OPTION>
+<OPTION>Cayman Islands</OPTION>
+<OPTION>Central African Republic</OPTION>
+<OPTION>Chad</OPTION>
+<OPTION>Chile</OPTION>
+<OPTION>China</OPTION>
+<OPTION>Christmas Island</OPTION>
+<OPTION>Colombia</OPTION>
+<OPTION>Comoros</OPTION>
+<OPTION>Congo</OPTION>
+<OPTION>Cook Islands</OPTION>
+<OPTION>Costa Rica</OPTION>
+<OPTION>Cote D'Ivoire</OPTION>
+<OPTION>Croatia</OPTION>
+<OPTION>Cuba</OPTION>
+<OPTION>Cyprus</OPTION>
+<OPTION>Czech Republic</OPTION>
+<OPTION>Denmark</OPTION>
+<OPTION>Djibouti</OPTION>
+<OPTION>Dominica</OPTION>
+<OPTION>Dominican Republic</OPTION>
+<OPTION>East Timor</OPTION>
+<OPTION>Ecuador</OPTION>
+<OPTION>Egypt</OPTION>
+<OPTION>El Salvador</OPTION>
+<OPTION>Equatorial Guinea</OPTION>
+<OPTION>Estonia</OPTION>
+<OPTION>Ethiopia</OPTION>
+<OPTION>Falkland Islands (Malvinas)</OPTION>
+<OPTION>Fiji</OPTION>
+<OPTION>Finland</OPTION>
+<OPTION selected>France</OPTION>
+<OPTION>French Guiana</OPTION>
+<OPTION>French Polynesia</OPTION>
+<OPTION>French Southern Territories</OPTION>
+<OPTION>Gabon</OPTION>
+<OPTION>Gambia</OPTION>
+<OPTION>Georgia</OPTION>
+<OPTION>Germany</OPTION>
+<OPTION>Ghana</OPTION>
+<OPTION>Gibraltar</OPTION>
+<OPTION>Greece</OPTION>
+<OPTION>Greenland</OPTION>
+<OPTION>Grenada</OPTION>
+<OPTION>Guadeloupe</OPTION>
+<OPTION>Guatemala</OPTION>
+<OPTION>Guinea</OPTION>
+<OPTION>Guinea-Bissau</OPTION>
+<OPTION>Guyana</OPTION>
+<OPTION>Haiti</OPTION>
+<OPTION>Honduras</OPTION>
+<OPTION>Hong Kong</OPTION>
+<OPTION>Hungary</OPTION>
+<OPTION>Iceland</OPTION>
+<OPTION>India</OPTION>
+<OPTION>Indonesia</OPTION>
+<OPTION>Iran, Islamic Republic of</OPTION>
+<OPTION>Iraq</OPTION>
+<OPTION>Ireland</OPTION>
+<OPTION>Israel</OPTION>
+<OPTION>Italy</OPTION>
+<OPTION>Jamaica</OPTION>
+<OPTION>Japan</OPTION>
+<OPTION>Jordan</OPTION>
+<OPTION>Kazakhstan</OPTION>
+<OPTION>Kenya</OPTION>
+<OPTION>Kiribati</OPTION>
+<OPTION>Korea, Democratic People's Republic of</OPTION>
+<OPTION>Korea, Republic of</OPTION>
+<OPTION>Kuwait</OPTION>
+<OPTION>Kyrgyzstan</OPTION>
+<OPTION>Lao People's Democratic Republic</OPTION>
+<OPTION>Latvia</OPTION>
+<OPTION>Lebanon</OPTION>
+<OPTION>Lesotho</OPTION>
+<OPTION>Liberia</OPTION>
+<OPTION>Libyan Arab Jamahiriya</OPTION>
+<OPTION>Lithuania</OPTION>
+<OPTION>Luxembourg</OPTION>
+<OPTION>Macau</OPTION>
+<OPTION>Macedonia, the Former Yugoslav Republic of</OPTION>
+<OPTION>Madagascar</OPTION>
+<OPTION>Malawi</OPTION>
+<OPTION>Malaysia</OPTION>
+<OPTION>Maldives</OPTION>
+<OPTION>Mali</OPTION>
+<OPTION>Malta</OPTION>
+<OPTION>Marshall Islands</OPTION>
+<OPTION>Martinique</OPTION>
+<OPTION>Mauritania</OPTION>
+<OPTION>Mauritius</OPTION>
+<OPTION>Mexico</OPTION>
+<OPTION>Mirconesia, Federated States of</OPTION>
+<OPTION>Moldova, Republic of</OPTION>
+<OPTION>Mongolia</OPTION>
+<OPTION>Morocco</OPTION>
+<OPTION>Mozambique</OPTION>
+<OPTION>Myanmar</OPTION>
+<OPTION>Namibia</OPTION>
+<OPTION>Nauru</OPTION>
+<OPTION>Nepal</OPTION>
+<OPTION>Netherlands</OPTION>
+<OPTION>Netherlands Antilles</OPTION>
+<OPTION>New Caledonia</OPTION>
+<OPTION>New Zealand</OPTION>
+<OPTION>Nicaragua</OPTION>
+<OPTION>Niger</OPTION>
+<OPTION>Nigeria</OPTION>
+<OPTION>Norway</OPTION>
+<OPTION>Oman</OPTION>
+<OPTION>Pakistan</OPTION>
+<OPTION>Panama</OPTION>
+<OPTION>Papua New Guinea</OPTION>
+<OPTION>Paraguay</OPTION>
+<OPTION>Peru</OPTION>
+<OPTION>Philippines</OPTION>
+<OPTION>Pitcairn</OPTION>
+<OPTION>Poland</OPTION>
+<OPTION>Portugal</OPTION>
+<OPTION>Puerto Rico</OPTION>
+<OPTION>Qatar</OPTION>
+<OPTION>Reunion</OPTION>
+<OPTION>Romania</OPTION>
+<OPTION>Russian Federation</OPTION>
+<OPTION>Rwanda</OPTION>
+<OPTION>Saint Kitts and Nevis</OPTION>
+<OPTION>Saint Lucia</OPTION>
+<OPTION>Saint Pierre and Miquelon</OPTION>
+<OPTION>Saint Vincent and the Grenadines</OPTION>
+<OPTION>Samoa</OPTION>
+<OPTION>Sao Tome and Principe</OPTION>
+<OPTION>Saudi Arabia</OPTION>
+<OPTION>Senegal</OPTION>
+<OPTION>Seychelles</OPTION>
+<OPTION>Sierra Leone</OPTION>
+<OPTION>Singapore</OPTION>
+<OPTION>Slovakia</OPTION>
+<OPTION>Slovenia</OPTION>
+<OPTION>Solomon Islands</OPTION>
+<OPTION>Somalia</OPTION>
+<OPTION>South Africa</OPTION>
+<OPTION>South Georgia and the South Sandwich Islands</OPTION>
+<OPTION>Spain</OPTION>
+<OPTION>Sri Lanka</OPTION>
+<OPTION>Sudan</OPTION>
+<OPTION>Suriname</OPTION>
+<OPTION>Swaziland</OPTION>
+<OPTION>Sweden</OPTION>
+<OPTION>Switzerland</OPTION>
+<OPTION>Syrian Arab Republic</OPTION>
+<OPTION>Taiwan</OPTION>
+<OPTION>Tajikistan</OPTION>
+<OPTION>Tanzania, United Republic of</OPTION>
+<OPTION>Thailand</OPTION>
+<OPTION>Togo</OPTION>
+<OPTION>Tokelau</OPTION>
+<OPTION>Tonga</OPTION>
+<OPTION>Trinidad and Tobago</OPTION>
+<OPTION>Tunisia</OPTION>
+<OPTION>Turkey</OPTION>
+<OPTION>Turkmenistan</OPTION>
+<OPTION>Turks and Caicos Islands</OPTION>
+<OPTION>Tuvalu</OPTION>
+<OPTION>Uganda</OPTION>
+<OPTION>Ukraine</OPTION>
+<OPTION>United Arab Emirates</OPTION>
+<OPTION>United Kingdom</OPTION>
+<OPTION>United States</OPTION>
+<OPTION>United States Minor Outlying Islands</OPTION>
+<OPTION>Uruguay</OPTION>
+<OPTION>Uzbekistan</OPTION>
+<OPTION>Vanuatu</OPTION>
+<OPTION>Venezuela</OPTION>
+<OPTION>Viet Nam</OPTION>
+<OPTION>Virgin Islands, British</OPTION>
+<OPTION>Western Sahara</OPTION>
+<OPTION>Yemen</OPTION>
+<OPTION>Yugoslavia</OPTION>
+<OPTION>Zaire</OPTION>
+<OPTION>Zambia</OPTION>
+<OPTION>Zimbabwe</OPTION>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>Phone Number</td>
+ <td>
+ <input type="text" name="phoneBox" maxlength="20" value="000000">
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>Fax Number</td>
+ <td>
+ <input type="text" name="faxBox" maxlength="20" value="111111">
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>Business Services</td>
+ <td>
+ <table>
+ <tr>
+ <td>On Site Support</td>
+ <td><input type="checkbox" name="onsitesupportBox" maxlength="20" value="1" ></td>
+ </tr>
+ <tr>
+ <td>Phone Support</td>
+ <td><input type="checkbox" name="phonesupportBox" maxlength="20" value="1" "></td>
+ </tr>
+ <tr>
+ <td>Distribution</td>
+ <td><input type="checkbox" name="distributionBox" maxlength="20" value="1" "></td>
+ </tr>
+ <tr>
+ <td>Free software development</td>
+ <td><input type="checkbox" name="freedevBox" maxlength="20" value="1" "></td>
+ </tr>
+ <tr>
+ <td>Commercial development</td>
+ <td><input type="checkbox" name="commdevBox" maxlength="20" value="1" "></td>
+ </tr>
+ <tr>
+ <td>Consulting</td>
+ <td><input type="checkbox" name="consultingBox" maxlength="20" value="1" "></td>
+ </tr>
+ <tr>
+ <td>Retail</td>
+ <td><input type="checkbox" name="retailBox" maxlength="20" value="1" "></td>
+ </tr>
+ <tr>
+ <td>Other</td>
+ <td>
+ <input type="text" name="otherservBox" maxlength="20" value="1" checked ">
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>Comments</td>
+ <td>
+ <textarea cols="50" rows="10" name="commentsBox">Hello?</textarea>
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td></td>
+ <td>
+ <input type="submit" name="submit" value="Add!">
+ <input type="reset" name="reset">
+ </td>
+ </tr>
+ </table>
+ </form>
+ <p>
+ <a href="./">Back to the Business Directory</a>
+ </div></div>
+
+
+ </td>
+ </tr>
+ <tr>
+ <td valign="top" id="leftmenu" width="25%">
+ <a name="navigation"></a>
+ <h2 id="h_inform">Inform</h2>
+<ul>
+<li>
+<a href="/">Home</a>
+</li>
+<li>
+<a href="http://www.kde.org/">KDE Home</a>
+</li>
+<li>
+<a href="/news.php">News</a>
+</li>
+<li>
+<a href="/info">Information</a>
+</li>
+<li>
+<a href="/faq">FAQ</a>
+</li>
+<li>
+<a href="http://events.kde.org/calendar/index.phtml">Events</a>
+</li>
+<li>
+<a href="/mailinglist">Mailing List</a>
+</li>
+</ul>
+<h2 id="h_kdeenterprise">KDE::Enterprise</h2>
+<ul>
+<li>
+<a href="/articles/">Articles</a>
+</li>
+<li>
+<a href="/bizcase">Business Cases</a>
+</li>
+<li>
+<a href="/bizdir">Business Directory</a>
+</li>
+<li>
+<a href="/interviews/">Interviews</a>
+</li>
+<li>
+<a href="/focus/">Focus On...</a>
+</li>
+<li>
+<a href="/bizcontact">Business Contacts</a>
+</li>
+</ul>
+
+
+ <h2 id="t_search">Search</h2>
+<div id="search">
+<form method="get" name="Searchform" action="/media/search.php">
+<span class="invisible"><label for="Input" accesskey="4">Search:</label></span>
+<input type="text" size="10" name="q" id="Input" value="" /><br />
+<span class="invisible"><label for="Select"> in </label></span>
+<select size="1" name="Select" id="Select" title="Select what or where you want to search">
+<option value="kdenet" selected="selected">kde.org</option>
+<option value="kdelook">kde-look.org</option>
+<option value="kdedoc">Documentation</option>
+</select><br />
+<input type="submit" value=" Search " name="Button" id="searchButton" />
+</form>
+</div>
+
+ <div class="invisible">
+ <a href="http://www.kde.org/" accesskey="8">KDE Home</a> |
+ <a href="http://accessibility.kde.org/" accesskey="9">KDE Accessibility Home</a> |
+ <a href="/media/accesskeys.php" accesskey="0">Description of Access Keys</a>
+ </div>
+ </td>
+</tr>
+
+
+<tr>
+ <td colspan="2">
+ <div id="footer">
+ Maintained by <a href="mailto:webmaster@kde.org">enterprise.kde.org Webmaster</a><br />
+ KDE and K Desktop Environment are trademarks of <a href="http://www.kde.org/areas/kde-ev/" title="Homepage of the KDE non-profit Organization">KDE e.V</a> |
+ <a href="http://www.kde.org/contact/impressum.php">Legal</a>
+ </div>
+ </td>
+</tr>
+
+</table></body>
+
+</html>
diff --git a/vcl/qa/cppunit/pdfexport/data/forcepoint71.key b/vcl/qa/cppunit/pdfexport/data/forcepoint71.key
new file mode 100644
index 0000000000..716fe58480
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/forcepoint71.key
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/forcepoint80-1.rtf b/vcl/qa/cppunit/pdfexport/data/forcepoint80-1.rtf
new file mode 100644
index 0000000000..5d20b1e886
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/forcepoint80-1.rtf
@@ -0,0 +1 @@
+{\rtf1\cnsi \ansicpg1252\stshfloch0{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green102\blue204;\red102\green99\blue98;\red143\green142\blue142;\red75\green60\blue52;}{\fonttbl{\f0\froman\cpg1257\fcharset186 DejaVu Serif;}{\f1\froman\cpg0\fcharset0 DejaVu Serif;}{\f2\froman\cpg1252\fcharset0 DejaVu Serif;}{\f3\fswiss\cpg1257\fcharset186 DejaVu Sans;}{\f4\fswiss\cpg0\fcharset0 DejaVu Sans;}{\f5\fswiss\cpg1252\fcharset0 DejaVu Sans;}{\f6\fro-De\cpg1257\fcharset186 jaVu Sans;}{\f7\froman\cpg0\fcharset0 Times New Roman;}{\f8\froman\cpg1252\fcharset0 Times New Roman;}{\f9\fmodern\cpg1257\fcharset186 Courier New;}{\f10\fmodern\cpg0\fcharset0 Courier New;}{\f11\fmodern\cpg1252\fcharset0 Courier New;}}{\stylesheet {\ql Normal;}{\s1\sbasedon0\ql\fi-120\sl-276\slmult0 Style1;}{\s2\sbasedon0\qj\fi560\sl-300\slmult0 Style2;}{\s3\sbasedon0\ql\sl-264\slmult0 Style3;}{\s4\sbasedon0\ql\fi-1088\sl-320\slmult0 Style4;}{\s5\sbasedon0\ql Style5;}{\s6\sbasedon0\ql Style6;}{\s7\sbasedon0\ql\sl-320\slmult0 Style7;}{\s8\sbasedon0\qr Style8;}{\s9\sbasedon0\ql Style9;}{\s10\sbasedon0\qj\sl-264\slmult0 Style10;}{\s11\sbasedon0\qj\sl-298\slmult0 Style11;}{\s12\sbasedon0\qj\sl-288\slmult0 Style12;}{\s13\sbasedon0\ql Style13;}{\s14\sbasedon0\ql\fi600\sl-329\slmult0 Style14;}{\s15\sbasedon0\ql\sl-240\slmult0 Style15;}{\s16\sbasedon0\ql Style16;}{\s17\sbasedon0\ql\fi576\sl-328\slmult0 Style17;}{\s18\sbasedon0\ql Style18;}{\s19\sbasedon0\ql\sl-271\slmult0 Style19;}{\s20\sbasedon0\ql\fi2406\sl-339\slmult0 Style20;}{\s21\sbasedon0\ql Style21;}{\s22\sbasedon0\ql\fi2821\sl-161\slmult0 Style22;}{\s23\sbasedon0\ql\fi568\sl-306\slmult0 Style23;}{\s24\sbasedon0\ql Style24;}{\s25\sbasedon0\ql Style25;}{\s26\sbasedon0\ql Style26;}{\s27\sbasedon0\ql Style27;}{\s28\sbasedon0\qj\sl-271\slmult0 Style28;}{\s29\sbasedon0\ql\fi568\sl-322\slmult0 Style29;}{\s30\sbasedon0\ql Style30;}{\s31\sbasedon0\ql Style31;}{\s32\sbasedon0\ql Style32;}{\s33\sbasedon0\ql Style33;}{\s34\sbasedon0\ql Style34;}{\s35\sbasedon0\ql Style35;}{\*\cs36\additive Default Paragraph Font;}{\*\cs37\sbasedon36\additive\b\charscalex120\f2\fs58 Font Style37;}{\*\cs38\sbasedon36\additive\b\charscalex30\f2\fs400 Font Style38;}{\*\cs39\sbasedon36\additive\i\f2\fs22 Font Style39;}{\*\cs40\sbasedon36\additive\scaps\f8\fs8 Font Style40;}{\*\cs41\sbasedon36\additive\i\b\f2\fs10 Font Style41;}{\*\cs42\sbasedon36\additive\f5\fs8 Font Style42;}{\*\cs43\sbasedon36\additive\b\expndtw200\f2\fs10 Font Style43;}{\*\cs44\sbasedon36\additive\f2\fs22 Font Style44;}{\*\cs45\sbasedon36\additive\b\expndtw-10\f2\fs22 Font Style45;}{\*\cs46\sbasedon36\additive\scaps\f2\fs20 Font Style46;}{\*\cs47\sbasedon36\additive\charscalex20\f2\fs104 Font Style47;}{\*\cs48\sbasedon36\additive\b\f2\fs20 Font Style48;}{\*\cs49\sbasedon36\additive\f2\fs22 Font Style49;}{\*\cs50\sbasedon36\additive\b\f2\fs38 Font Style50;}{\*\cs51\sbasedon36\additive\f2\fs20 Font Style51;}{\*\cs52\sbasedon36\additive\expndtw-20\f2\fs30 Font Style52;}{\*\cs53\sbasedon36\additive\i\expndtw-10\f2\fs22 Font Style53;}{\*\cs54\sbasedon36\additive\i\expndtw-20\f2\fs22 Font Style54;}{\*\cs55\sbasedon36\additive\f2\fs22 Font Style55;}{\*\cs56\sbasedon36\additive\f2\fs18 Font Style56;}{\*\cs57\sbasedon36\additive\b\expndtw10\f2\fs16 Font Style57;}{\*\cs58\sbasedon36\additive\b\f2\fs20 Font Style58;}{\*\cs59\sbasedon36\additive\i\b\expndtw-10\f11\fs22 Font Style59;}{\*\cs60\sbasedon36\additive\f2\fs20 Font Style60;}{\*\cs61\sbasedon36\additive\f2\fs12 Font Style61;}{\*\cs62\sbasedon36\additive\f2\fs18 Font Style62;}{\*\cs63\sbasedon36\additive\i\expndtw-20\f2\fs22 Font Style63;}{\*\cs64\sbasedon36\additive\b\expndtw-20\f2\fs26 Font Style64;}}\fet2{\*\ftnsep {\chftnsep\par }}{\*\aftnsep {\chftnsep\par }}\expshrtn\widowctrl\paperw20384\paperh12312\margl360\margr360\margt360\margb360 \sectd \sbknone\pgwsxn20384\pghsxn12312\marglsxn360\margrsxn360\margtsxn360\margbsxn360\titlepg\cols2\colno1\colw8632\colsr2104\colno2\colw8928 {\headerf }{\footerf }{\header \pard \plain \s11\ql\li720\fi0\ri9\sb0\sa0\tx2120 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 \'e1\f0\cchs186 Ma\ul b\f0\cchs186 \'fb\f0\cchs186 s vaikai:\ul0 {\charscalex100\expndtw0\tab }\ul ir galimyb\f0\cchs186 \'eb\f0\cchs186 s\par }{\footer \pard \plain \s11\qj\li0\fi0\ri0\sb0\sa0 \pvpara\phcol\posnegx0\posnegy0\absw0\absh-254\nowrap\dfrmtxtx34\dfrmtxty0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 {\field{\*\fldinst{PAGE}}{\fldrslt 100}}\par \pard \plain \s21\qr\li720\fi0\ri9{\sl-240\slmult0\fs20\par }\sb90\sa0 \cs61\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs12 101\par }\pard\sl-4680\slmult0\sb0\keepn\pvpara\dropcapli7\dropcapt1{\b\i0\ul0\cf0\highlight0\charscalex30\f0\fs400{i}\par}\pard \plain \s1\ql\li880\fi-880\ri0\sb0\sa0\sl-272\slmult0 \cs37\i0\strike0\nosupersub\b\scaps0\charscalex120\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs58 Hfc \cs55\â0\super\charscalex100\fs22 mddj8\nosupersub \super tUn\nosupersub \super tam\nosupersub \super dkrts\nosupersub identifikacijos proced\f0\cchs186 \'fb\f0\cchs186 ras, vadinasi \cs60\fs20 ivuiiy* toje \cs55\fs22 programoje \cs53\i\expndtw-10 asmenys \cs55\i0\expndtw0 turi mok\f0\cchs186 \'eb\f0\cchs186 ti naudotis identifikacijos \f0\cchs186 \'e1\f0\cchs186 ran-\cs60\fs20 *>*\f0\cchs186 \'ab\'f8\f0\cchs186 geb\f0\cchs186 \'eb\f0\cchs186 ti \cs55\fs22 analizuoti mokini\f0\cchs186 \'f8\f0\cchs186 veiklos produktus ir pati mokymosi proces\f0\cchs186 \'e0\f0\cchs186 , \cs60\f0\cchs186\fs20 \'95\f0\cchs186 tpaftistant \cs55\fs22 kriterijus, pagal kuriuos vaikas gali b\f0\cchs186 \'fb\f0\cchs186 ti priskirtas gabi\f0\cchs186 \'f8\f0\cchs186 j\f0\cchs186 \'f8\f0\cchs186 grupei. \cs60\fs20 Vargu, ar \cs55\fs22 bc specialaus \cs53\i\expndtw-10 pasirengimo \cs55\i0\expndtw0 gali dirbti mokytojas. Pavyzd\f0\cchs186 \'fe\f0\cchs186 iui, Clark modelyje numatyta, kad mokytojas turi b\f0\cchs186 \'fb\f0\cchs186 ti \f0\cchs186 \'e1\f0\cchs186 vald\f0\cchs186 \'e6\f0\cchs186 s \f0\cchs186 \'e1\f0\cchs186 tampos klas\f0\cchs186 \'eb\f0\cchs186 je suma\f0\cchs186 \'fe\f0\cchs186 i\-nimo strategijas (autogenin\f0\cchs186 \'e6\f0\cchs186 treniruot\f0\cchs186 \'e6\f0\cchs186 , meditacij\f0\cchs186 \'e0\f0\cchs186 ), turi geb\f0\cchs186 \'eb\f0\cchs186 ti parinkti mo\-kymo med\f0\cchs186 \'fe\f0\cchs186 iag\f0\cchs186 \'e0\f0\cchs186 , pratimus ir u\f0\cchs186 \'fe\f0\cchs186 duotis, kurioms atlikti b\f0\cchs186 \'fb\f0\cchs186 t\f0\cchs186 \'f8\f0\cchs186 naudojama abiej\f0\cchs186 \'f8 \f0\cchs186 smegen\f0\cchs186 \'f8\f0\cchs186 pusrutuli\f0\cchs186 \'f8\f0\cchs186 veikla, geb\f0\cchs186 \'eb\f0\cchs186 ti \f0\cchs186 \'e1\f0\cchs186 ugdymo turin\f0\cchs186 \'e1\f0\cchs186 \f0\cchs186 \'e1\f0\cchs186 traukti pratimus, kurie, panaudojant fizin\f0\cchs186 \'e1\f0\cchs186 k\f0\cchs186 \'fb\f0\cchs186 n\f0\cchs186 \'e0\f0\cchs186 , abstraktaus ir simbolinio lygmens \cs39\i informacij\f0\cchs186 \'e0\f0\cchs186 , \cs55\i0 trans\-formuoja \f0\cchs186 \'e1\f0\cchs186 konkretesn\f0\cchs186 \'e1\f0\cchs186 lygmen\f0\cchs186 \'e1\f0\cchs186 ir pan. Kai kurie autoriai, pavyzd\f0\cchs186 \'fe\f0\cchs186 iui, Wil-liams, netgi nurodo mokymo strategijas, kurias \cs39\i naudoti savo \cs55\i0 darbe turi mok\f0\cchs186 \'eb\f0\cchs186 ti \cs60\fs20 mokytojas, \cs55\fs22 kiti savo darbo komand\f0\cchs186 \'e0\f0\cchs186 apmoko. Renzulli modelio realizavimo komand\f0\cchs186 \'e0\f0\cchs186 sudaro ne tik mokytojai, bet ir t\f0\cchs186 \'eb\f0\cchs186 vai, bendruomen\f0\cchs186 \'eb\f0\cchs186 s ekspertai, mo\-kyklos administracija ir kt. personalas. Modelio veikimo efektyvum\f0\cchs186 \'e0\f0\cchs186 vertina \cs53\i\expndtw-10 visi \cs39\expndtw0 jo \cs53\expndtw-10 dalyviai, pagal tokios analiz\f0\cchs186 \'eb\f0\cchs186 s \cs55\i0\expndtw0 rezultatus bei mokini\f0\cchs186 \'f8\f0\cchs186 pageidavimus ar pakitusius poreikius ugdymo programa modifikuojama.\par \pard \plain \s2\ql\li576\fi560\ri0\sb0\sa0\sl-296\slmult0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Atskirai reik\f0\cchs186 \'eb\f0\cchs186 t\f0\cchs186 \'f8\f0\cchs186 pamin\f0\cchs186 \'eb\f0\cchs186 ti Taylor model\f0\cchs186 \'e1\f0\cchs186 . Jis vadinamas ir muititalento, ir k\f0\cchs186 \'fb\f0\cchs186 rybinio bei kritinio m\f0\cchs186 \'e0\f0\cchs186 stymo modeliu. \cs39\i Laikoma, kad \cs55\i0 bent vienai (j\f0\cchs186 \'f8\f0\cchs186 nuro\-domos net devynios) gabumo sri\f0\cchs186 \'e8\f0\cchs186 iai yra gabus kiekvienas vaikas. Toks gabi\f0\cchs186 \'f8 \f0\cchs186 vaik\f0\cchs186 \'f8\f0\cchs186 \cs53\i\expndtw-10 ugdymo modelis tur\f0\cchs186 \'eb\f0\cchs186 t\f0\cchs186 \'f8\f0\cchs186 b\f0\cchs186 \'fb\f0\cchs186 ti \cs39\expndtw0 priimtinas ir \cs55\i0\f0\cchs186 \'fe\f0\cchs186 mon\f0\cchs186 \'eb\f0\cchs186 ms, kurie mano, jog visi vaikai \cs60\fs20 turi \cs55\fs22 tam \cs53\i\expndtw-10 tikr\f0\cchs186 \'f8\f0\cchs186 \cs55\i0\expndtw0 gabum\f0\cchs186 \'f8\f0\cchs186 , ir d\f0\cchs186 \'eb\f0\cchs186 l koki\f0\cchs186 \'f8\f0\cchs186 nors prie\f0\cchs186 \'fe\f0\cchs186 as\f0\cchs186 \'e8\f0\cchs186 i\f0\cchs186 \'f8\f0\cchs186 nenori pripa\-\cs60\f0\cchs186\fs20 \'fe\f0\cchs186 inti \cs55\fs22 gabum\f0\cchs186 \'f8\f0\cchs186 \cs53\i\expndtw-10 lygmens \cs55\i0\expndtw0 skirtum\f0\cchs186 \'f8\f0\cchs186 . Modelio autorius nurodo \f0\cchs186 \'f0\f0\cchs186 ias gabum\f0\cchs186 \'f8\f0\cchs186 sritis: akademin\f0\cchs186 \'eb\f0\cchs186 s srities, \cs53\i\expndtw-10 produktyviojo \cs55\i0\expndtw0 m\f0\cchs186 \'e0\f0\cchs186 stymo, komuniJcavimo, prognozavimo, \cs53\i\expndtw-10 sprendim\f0\cchs186 \'f8\f0\cchs186 pri\f0\cchs186 \'eb\f0\cchs186 mimo, plan\f0\cchs186 \'f8\f0\cchs186 \f0\cchs186 \'e1\f0\cchs186 gyvendinimo, \cs55\i0\expndtw0\f0\cchs186 \'fe\f0\cchs186 mogi\f0\cchs186 \'f0\f0\cchs186 k\f0\cchs186 \'f8\f0\cchs186 j\f0\cchs186 \'f8\f0\cchs186 ry\f0\cchs186 \'f0\f0\cchs186 i\f0\cchs186 \'f8\f0\cchs186 , galimybi\f0\cchs186 \'f8\f0\cchs186 \f0\cchs186 \'e1\'fe\f0\cchs186 vel\-gimo \cs39\i\expndtw-20 (ir.\expndtw0 2.2.1 \cs53\expndtw-10 lentel\f0\cchs186 \'e6\f0\cchs186 ).\par \pard \plain \s11\qj\li6976\fi0\ri0{\sl-240\slmult0\fs20\par }\sb72\sa0 \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 2.2.1 \cs55 lentel\f0\cchs186 \'eb\par \pard \plain \s8\qj\li0\fi0\ri0\sb0\sa0 \pvpara\phcol\posnegx6464\posnegy688\absw0\absh-256\dfrmtxtx40\dfrmtxty0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 U\f0\cchs186 \'fe\f0\cchs186 duo\f0\cchs186 \'e8\f0\cchs186 i\f0\cchs186 \'f8\f0\cchs186 pavyzd\f0\cchs186 \'fe\f0\cchs186 iai\par \pard \plain \s4\qc\li2112\fi0\ri2232\sb0\sa0\sl-320\slmult0 \cs53\b0\strike0\nosupersubLi\scaps0\charscalex100\expndtw-10\dn0\f0\cchs186\lang1063\langfe1063\æs22 Gabum\f0\cchs186 \'f8\f0\cchs186 \cs44\i0\expndtw0 ugdymas pagal Taylor \cs55 model\f0\cchs186 \'e1 \f0\cchs186 (pagal TayJor, 1986)\par \pard \plain \s9\qj\li0\fi0\ri0\sb0\sa0 \pvpara\phcol\posnegx2648\posnegy368\absw0\absh-328\dfrmtxtx40\dfrmtxty0 \cs53\b0\strike0\nosupersub\i\scaps0\charscalex100\expndtw-10\dn0\f0\cchs186\lang1063\langfe1063\fs22 Geb\f0\cchs186 \'eb\f0\cchs186 jimas \cs55\i0\expndtw0 susikurti \cs39\i atitinka- \{\f0\cchs186 \'84\f0\cchs186 Surinkite informacij\f0\cchs186 \'e0\f0\cchs186 apie... ii\par \pard \plain \s6\qj\li304\fi0\ri0\sb16\sa0 \cs63\b0\strike0\nosupersub\i\scaps0\charscalex100\expndtw-20\dn0\f0\cchs186\lang1063\langfe1063\fs22 I \cs53\expndtw-10 Gabum\f0\cchs186 \'f8\f0\cchs186 sritis Paai\f0\cchs186 \'f0\f0\cchs186 kinimas\par \pard \plain \s10\qj\li0\fi0\ri0\sb0\sa0\sl-264\slmult0 \pvpara\phcol\posnegx6032\posnegy328\absw2600\absh-544\dfrmtxtx40\dfrmtxty0 \cs53\b0\strike0\nosupersub\i\scaps0\charscalex100\expndtw-10\dn0\f0\cchs186\lang1063\langfe1063\fs22 j mos temos \f0\cchs186 \'fe\f0\cchs186 ini\f0\cchs186 \'f8\f0\cchs186 ir \f0\cchs186 \'e1\f0\cchs186 g\f0\cchs186 \'fb\f0\cchs186 d\f0\cchs186 \'fe\f0\cchs186 i\f0\cchs186 \'f8 \cs55\i0\expndtw0\f0\cchs186 'baz\f0\cchs186 \'e6\f0\cchs186 .\par \pard \plain \s12\qj\li0\fi0\ri0\sb0\sa0\sl-288\slmult0 \pvpara\phcol\posnegx5672\posnegy384\absw2960\absh-584\dfrmtxtx40\dfrmtxty0 \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 kuo \f0\cchs186 \'e1\f0\cchs186 vairesni\f0\cchs186 \'80008\f0\cchs186 \f0\cchs186 \'f0\f0\cchs186 altini\f0\cchs186 \'f8\f0\cchs186 ir j\f0\cchs186 \'e0\f0\cchs186 i\f0\cchs186 \'f0\f0\cchs186 a\-nalizuokite".\par \pard \plain \s11\ql\li0\fi0\ri0\sb0\sa0\sl-296\slmult0\tlul\tx2520 \pvpara\phcol\posnegx5856\posnegy896\absw2776\absh-1656\dfrmtxtx40\dfrmtxty0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 f Geb\f0\cchs186 \'eb\f0\cchs186 jimas generuoti \cs60\fs20 ne-\line \cs55\fs22 f \f0\cchs186 \'e1\f0\cchs186 prastas, originalias i\f0\cchs186 \'eb\f0\cchs186 /as,\line \cs60\fs20 f\cs53\i\expndtw-10\fs22 pasteb\f0\cchs186 \'eb\'fb\f0\cchs186 naujus \cs55\i0\expndtw0 s\f0\cchs186 \'e0\f0\cchs186 ry\f0\cchs186 \'f0\f0\cchs186 ius,\line \cs53\i\expndtw-10 | papildyti detal\f0\cchs186 \'eb\f0\cchs186 mis, \cs55\i0\expndtw0 kurios\line \cs53\i\expndtw-10\f0\cchs186 \'c1\f0\cchs186 pa\f0\cchs186 \'e1\f0\cchs186 vairioa id\f0\cchs186 \'eb\f0\cchs186 jas,\expndtw0 {\charscalex100\expndtw0\tab }\par \pard \plain \s6\qj\li232\fi0\ri0\sb0\sa0 \cs53\b0\strike0\nosupersub\i\scaps0\charscalex100\expndtw-10\dn0\f0\cchs186\lang1063\langfe1063\fs22 'Akademin\f0\cchs186 \'eb\par \pard \plain \s12\qj\li0\fi0\ri0\sb0\sa0\sl-272\slmult0 \pvpara\phcol\posnegx5264\posnegy160\absw3368\absh-648\dfrmtxtx40\dfrmtxty0 \cs39\b0\strike0\nosupersub\i\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 \'84\f0\cchs186 Sukurkite, kuo \cs60\i0\fs20 pakeisti \f0\cchs186 \'e1\f0\cchs186 prastas energijos r\f0\cchs186 \'fb\'f0\f0\cchs186 is".\par \pard \plain \s7\ql\li0\fi0\ri0\sb0\sa0\sl-320\slmult0 \cs39\b0\strike0\nosupersub\i\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 (K\f0\cchs186 \'fb\f0\cchs186 rybos, produktyvioio\par \pard \plain \s13\qj\li5816\fi0\ri0\sb88\sa0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 2.2.1 icntcU\par \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx1848 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx4792 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8376 \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0\sl-240\slmult0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Gabum\f0\cchs186 \'f8\f0\cchs186 \cs60\fs20 sritis\par \pard \plain \s15\ql\li0\fi40\ri0\sb0\sa0\sl-240\slmult0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Sprendim\f0\cchs186 \'f8 \f0\cchs186 pri\f0\cchs186 \'eb\f0\cchs186 mimo\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0\sl-248\slmult0\tlul\tx2736 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Paai\f0\cchs186 \'f0\f0\cchs186 kinimas {\charscalex100\expndtw0\tab }Ll\par \pard \plain \s15\ql\li0\fi0\ri0\sb0\sa0\sl-248\slmult0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Geb\f0\cchs186 \'eb\f0\cchs186 jimas rasti alternatyvas, \cs55\fs22 l.,\par \pard \plain \s15\ql\li32\fi-32\ri0\sb0\sa0\sl-248\slmult0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 jas \f0\cchs186 \'e1\f0\cchs186 vertinti ir priimti tinka- \cs55\fs22 t \cs60\fs20 r m\f0\cchs186 \'e0\f0\cchs186 problemos sprendim\f0\cchs186 \'e0\f0\cchs186 . 1 \cs41\b\i\f0\cchs186\fs10 \'e1\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0\sl-264\slmult0 \intbl \cs5u\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 J\f0\cchs186 \'fe\f0\cchs186 duo\f0\cchs186 \'e8\f0\cchs186 i\f0\cchs186 \'f8\f0\cchs186 pavyzd\f0\cchs186 \'fe\f0\cchs186 iai Nurodykite \cs60\fs20 savo \cs55\fs22 m\f0\cchs186 \'eb\f0\cchs186 giamiausi\f0\cchs186 \'e0\f0\cchs186 \cs60\fs20 I\par \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0\sl-264\slmult0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 a\f0\cchs186 \'f0\f0\cchs186 y toj\f0\cchs186 \'e0\f0\cchs186 , ai\f0\cchs186 \'f0\f0\cchs186 kiai pagr\f0\cchs186 \'e1\f0\cchs186 sdami at- t \cs60\fs20 rumentais \cs55\fs22 savo pasirinkim\f0\cchs186 \'e0\f0\cchs186 it\par \pard \plain \s15\ql\li0\fi0\ri0\sb0\sa0\sl-264\slmult0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 itmetim\f0\cchs186 \'e0\f0\cchs186 ".\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx1848 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx4792 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8376 \pard \plain \s15\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Planavimo\cell \pard \plain \s15\ql\li16\fi-16\ri0\sb0\sa0\sl-248\slmult0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Geb\f0\cchs186 \'eb\f0\cchs186 jimas numatyti kelius ir b\f0\cchs186 \'fb\f0\cchs186 dus id\f0\cchs186 \'eb\f0\cchs186 jai \f0\cchs186 \'e1\f0\cchs186 gyvendinti, nustatant, koki\f0\cchs186 \'f8\f0\cchs186 reikia resur- \cs55\fs22 1 \cs60\fs20 s\f0\cchs186 \'f8\f0\cchs186 , \f0\cchs186 \'e1\'fe\f0\cchs186 velgiant, kokios gali\-mos problemos, ir numatant \f0\cchs186 \'fe\f0\cchs186 ingsnius j\f0\cchs186 \'f8\f0\cchs186 sprendimui, to\-kiu b\f0\cchs186 \'fb\f0\cchs186 du patobulinant plan\f0\cchs186 \'e0\f0\cchs186 .\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0\sl-256\slmult0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 .I\f0\cchs186 \'f0\f0\cchs186 studijav\f0\cchs186 \'e6\f0\cchs186 \cs55\fs22 mitus apie \f0\cchs186 \'f0\f0\cchs186 ik\f0\cchs186 \'f0\f0\cchs186 nos\-\cs60\fs20 parnius, \cs55\fs22 sukurkite tyrimo plan\f0\cchs186 \'e0\f0\cchs186 \cs62\fs18 1\par \pard \plain \s15\ql\li0\fi0\ri0\sb0\sa0\sl-256\slmult0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 savo draug\f0\cchs186 \'f8\f0\cchs186 \cs55\fs22 nuomonei \cs60\fs20 apie \f0\cchs186 \'f0\f0\cchs186 iuos gyv\f0\cchs186 \'fb\f0\cchs186 nus i\f0\cchs186 \'f0\f0\cchs186 tirti\super 41\nosupersub , \f0\cchs186 \'84\f0\cchs186 Sukurkite akcijos prie\f0\cchs186 \'f0\f0\cchs186 \cs55\fs22 r\f0\cchs186 \'fb\f0\cchs186 kym\f0\cchs186 \'e0\f0\cchs186 \cs62\fs18 1 \cs60\fs20 plan\f0\cchs186 \'e0\f0\cchs186 ".\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx1848 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx4792 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8376 \pard \plain \s15\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Prognozavimo\cell \pard \plain \s15\ql\li0\fi8\ri0\sb0\sa0\sl-248\slmult0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Geb\f0\cchs186 \'eb\f0\cchs186 jimas numatyti ir pati\-krinti prie\f0\cchs186 \'fe\f0\cchs186 asties ir pasekm\f0\cchs186 \'eb\f0\cchs186 s s\f0\cchs186 \'e0\f0\cchs186 ry\f0\cchs186 \'f0\f0\cchs186 ius.\cell \pard \plain \s15\ql\li0\fi0\ri0\sb0\sa0\sl-264\slmult0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 \'84\f0\cchs186 I\f0\cchs186 \'f0\f0\cchs186 studijuokite N sutart\f0\cchs186 \'e1\f0\cchs186 ir nurodykite \f0\cchs186 \'e1\f0\cchs186 vairias galimas jos i ratifikavimo pasekmes".\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx1848 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx4792 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8376 \pard \plain \s15\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Komunikavimo\cell \pard \plain \s15\ql\li0\fi16\ri0\sb0\sa0\sl-256\slmult0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Geb\f0\cchs186 \'eb\f0\cchs186 jimas reik\f0\cchs186 \'f0\f0\cchs186 ti savo id\f0\cchs186 \'eb\f0\cchs186 jas, jausmus, poreikius ir suprasti kitus, vartojant verbalin\f0\cchs186 \'e6\f0\cchs186 ir neverbalin\f0\cchs186 \'e6\f0\cchs186 kalb\f0\cchs186 \'e0\f0\cchs186 .\cell \pard \plain \s15\ql\li0\fi0\ri0\sb0\sa0\sl-264\slmult0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 \'84\f0\cchs186 Pademonstruokite savo jaus- \cs55\fs22 1 \cs60\fs20 mus, kai su\f0\cchs186 \'fe\f0\cchs186 inote apie ... \f0\cchs186 \'84\f0\cchs186 Papasakokite apie \f0\cchs186 \'e1\f0\cchs186 vyk\f0\cchs186 \'e1\f0\cchs186 i\f0\cchs186 \'f0\f0\cchs186 vaiko i ir i\f0\cchs186 \'f0\f0\cchs186 suaugusiojo pozicini". i\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx1848 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx4792 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8376 \pard \plain \s15\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Plan\f0\cchs186 \'f8\par \pard \plain \s15\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 \'e1\f0\cchs186 gyvendinimo\cell \pard \plain \s15\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Geb\f0\cchs186 \'eb\f0\cchs186 jimas \f0\cchs186 \'e1\f0\cchs186 gyvendinti plan\f0\cchs186 \'e0\cell \pard \plain \s16\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs43\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw200\dn0\f0\cchs186\lang1063\langfe1063\fs10 \'97\f0\cchs186 \f0\cchs186 \'97\f0\cchs186 \f1\cchs186 \u9632\'3f\f0\cchs186 - \f0\cchs186 \'97\'97\f0\cchs186  \expndtw0 *-\expndtw200 \expndtw0 L-\expndtw200 \expndtw0 -\expndtw200 \cs42\b0\expndtw0\f3\cchs186\fs8 i \f4\cchs186 \u9632\'3f\f3\cchs186 \f4\cchs186 \u9632\'3f\f3\cchs186 \cs41\b\i\f0\cchs186\fs10 a \cs40\b0\i0\scaps\f6\cchs186\fs8 - ii \f7\cchs186 \u9632\'3f\f6\cchs186 \cs62\scaps0\f0\cchs186\fs18 1\par \pard \plain \s15\ql\li0\fi0\ri0\sb0\sa0\sl-264\slmult0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 \'84\f0\cchs186 Sukurkite ir \f0\cchs186 \'e1\f0\cchs186 gyvendinkite N 1 renginio plan\f0\cchs186 \'e0\f0\cchs186 ".\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx1848 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx4792 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8376 \pard \plain \s15\ql\li32\fi-32\ri0\sb0\sa0\sl-280\slmult0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Galimybi\f0\cchs186 \'f8 \'e1\'fe\f0\cchs186 velgimo\cell \pard \plain \s15\ql\li0\fi40\ri0\sb0\sa0\sl-272\slmult0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Geb\f0\cchs186 \'eb\f0\cchs186 jimas identifikuoti gali\-myb\f0\cchs186 \'e6\f0\cchs186 ir ja pasinaudoti.\cell \pard \plain \s15\ql\li0\fi0\ri0\sb0\sa0\sl-272\slmult0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 \'84\f0\cchs186 I\f0\cchs186 \'f0\f0\cchs186 tirkite ispan\f0\cchs186 \'f8\f0\cchs186 kalbos mokymo(si) poreik\f0\cchs186 \'e1\f0\cchs186 mokykloje ir inicijuokite atitinkamos pro\-gramos \f0\cchs186 \'e1\f0\cchs186 gyvendinim\f0\cchs186 \'e0\f0\cchs186 ".\cell \pard\intbl\row \pard\pard \plain \s14\ql\li0\fi600\ri0\sb232\sa0\sl-328\slmult0 \cs44\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Taylor siekia ugdyti kuo daugiau gabum\f0\cchs186 \'f8\f0\cchs186 , kuriais, jo manymu, pasi\f0\cchs186 \'fe\f0\cchs186 ymi vaikai. Tad kuo \f0\cchs186 \'e1\f0\cchs186 vairesniems vaik\f0\cchs186 \'f8\f0\cchs186 gabumams mokytojas skirs d\f0\cchs186 \'eb\f0\cchs186 mes\f0\cchs186 \'e1\f0\cchs186 , tuo daugiau galimybi\f0\cchs186 \'f8\f0\cchs186 , kad vaikas ras srit\f0\cchs186 \'e1\f0\cchs186 , kurios \f0\cchs186 \'fe\f0\cchs186 inovu jis gali tapti. Ta\f0\cchs186 \'e8\f0\cchs186 iau pir\-masis \f0\cchs186 \'f0\f0\cchs186 io modelio etapas - tai vis d\f0\cchs186 \'eb\f0\cchs186 lto akademini\f0\cchs186 \'f8\f0\cchs186 gabum\f0\cchs186 \'f8\f0\cchs186 ugdymas. Kitas \f0\cchs186 \'fe\f0\cchs186 ingsnis, skirtas mokytojui apsispr\f0\cchs186 \'e6\f0\cchs186 sti, kuri\f0\cchs186 \'e0\f0\cchs186 i\f0\cchs186 \'f0\f0\cchs186 likusi\f0\cchs186 \'f8\f0\cchs186 a\f0\cchs186 \'f0\f0\cchs186 tuoni\f0\cchs186 \'f8\f0\cchs186 gabum\f0\cchs186 \'f8\f0\cchs186 sri\f0\cchs186 \'e8\f0\cchs186 i\f0\cchs186 \'f8 \f0\cchs186 jis imasi ugdyti. Paprastai seka, anot autoriaus, tur\f0\cchs186 \'eb\f0\cchs186 t\f0\cchs186 \'f8\f0\cchs186 b\f0\cchs186 \'fb\f0\cchs186 ti tokia: produktyvusis m\f0^cchs186 \'e0\f0\cchs186 stymas, planavimas, sprendim\f0\cchs186 \'f8\f0\cchs186 pri\f0\cchs186 \'eb\f0\cchs186 mimas, pian\f0\cchs186 \'f8\f0\cchs186 \f0\cchs186 \'e1\f0\cchs186 gyvendinimas, \f0\cchs186 \'e1\'fe\f0\cchs186 valgos. Akivaizdu, jog dirbti pagal \f0\cchs186 \'f0\'e1\f0\cchs186 model\f0\cchs186 \'e1\f0\cchs186 mokytojas tur\f0\cchs186 \'eb\f0\cchs186 t\f0\cchs186 \'f8\f0\cchs186 b\f0\cchs186 \'fb\f0\cchs186 ti specialiai pasi\-reng\f0\cchs186 \'e6\f0\cchs186 s, tad modelio autorius si\f0\cchs186 \'fb\f0\cchs186 lo kelet\f0\cchs186 \'e0\f0\cchs186 mokytojo darbo metod\f0\cchs186 \'f8\f0\cchs186 . Pirmuouiu atveju si\f0\cchs186 \'fb\f0\cchs186 loma papildom\f0\cchs186 \'f8\f0\cchs186 gabum\f0\cchs186 \'f8\f0\cchs186 ugdym\f0\cchs186 \'e0\f0\cchs186 organizuoti baigiantis pamokoms. \f0\cchs186 \'c1\f0\cchs186 prastinis mokymo turinys \f0\cchs186 \'84\f0\cchs186 suspaud\f0\cchs186 \'fe\f0\cchs186 iamas" laike, o atsiradusias 5\f0\cchs186 \'97\f0\cchs186 10 laisvuj minu\f0\cchs186 \'e8\f0\cchs186 i\f0\cchs186 \'f8\f0\cchs186 mokytojas gali i\f0\cchs186 \'f0\f0\cchs186 naudoti pasirinkto gabumo ugdymui. Kai \cs48\b\fs20 mokytojui \cs44\b0\fs22 atrodo, kad jau pasiekta tam tikra pa\f0\cchs186 \'fe\f0\cchs186 anga ugdant pirm\f0\cchs186 \'e0\f0\cchs186 j\f0\cchs186 \'e1\f0\cchs186 i\f0\cchs186 \'f0\f0\cchs186 \f0\cchs186 \'e1\f0\cchs186 o pasirinkt\f0\cchs186 \'f8\f0\cchs186 ga\-\par \sect\soctd \sbkpage\pgwsxn20855\pghsxn13316\marglsxn1080\margrsxn8026\margtsxn360\margbsxn360\cols2\colno1\colw7936\colsr2482\colno2\colw1329 {\header \pard \plain \s11\ql\li0\fi0\ri-7657\sb0\sa0\tx2245 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 \'e1\f0\cchs186 Ma\ul b\f0\cchs186 \'fb\f0\cchs186 s vaikai:\ul0 {\charscalex100\expndtw0\tab }\ul ir galimyb\f0\cchs186 \'eb\f0\cchs186 s\par }{\footer \par \plain \s11\qj\li0\fi0\ri0\sb0\sa0 \pvpara\phcol\posnegx-720\posnegy0\absw0\absh-254\nowrap\dfrmtxtx34\dfrmtxty0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 {\field{\*\fldinst{PAGE}}{\fldrslt 100}}\par \pard \plain \s21\qr\li0\fi0\ri-7657{\sl-240\slmult0\fs20\par }\sb90\sa0 \cs61\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs12 101\par }{\shp {\*\shpinst \chpleft13358\shptop593\shpright14748\shpbottom830\shpfhdr0\shpfblwtxt0\shpbxmargin\shpbypara\shpwr1\shpz1\shplid1{\sp{\sn fFlipH}{\sv 0}}{\sp{\sn fFlipV}{\sv 0}}{\sp{\sn fLayoutInCell}{\sv 1}}{\sp{\sn fAllowOverlap}{\sv 1}}{\sp{\sn dxWrapDistLeft}{\sv 21590}}{\sp{\sn dxWrapDistRight}{\sv 21590}}{\sp{\sn dyWrapDistTop}{\sv 317500}}{\sp{\sn dyWrapDistBottom}{\sv 198755}}{\sp{\sn shapeType}{\sv 202}}{\sp{\sn fLine}{\sv 0}}{\sp{\sn fPrint}{\sv 1}}{\sp{\sn fFilled}{\sv 0}}{\sp{\sn fEditedWrap}{\sv 0}}{\sp{\sn txflTextFlow}{\sv 0}}{\sp{\sn dxTextLeft}{\sv 0}}{\sp{\sn dyTextTop}{\sv 0}}{\sp{\sn dxTextRight}{\sv 0}}{\sp{\sn dyTextBottom}{\sv 0}}{\sp{\sn fFitTextToShape}{\sv 0}}{\shptxt \pard \plain \s31\qj\li0\fi0\ri0\sb0\sa0 \cs58\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Rai\f0\cchs186 \'f0\f0\cchs186 kos \cs51\b0 b\f0\cchs186 \'fb\f0\cchs186 dai\par }}}{\shp {\*\shpinst \shpleft11859\shptop534\shpright13172\shpbottom2169\shpfhdr0\shpfblwtxt0\shpbxmargin\shpbypara\shpwr1\shpz0\shplid0{\sp{\sn fFlipH}{\sv 0}}{\sp{\sn fFlipV}{\sv 0}}{\sp{\sn fLayoutInCell}{\sv 1}}{\sp{\sn fAllowOverlap}{\sv 1}}{\sp{\sn dxWrapDistLeft}{\sv 21590}}{\sp{\sn dxWrapDistRight}{\sv 21590}}{\sp{\sn dyWrapDistTop}{\sv 279400}}{\sp{\sn dyWrapDistBottom}{\sv 0}}{\sp{\sn shapeType}{\sv 202}}{\sp{\sn fLine}{\sv 0}}{\sp{\sn fPrint}{\sv 1}}{\sp{\sn fFilled}{\sv 0}}{\sp{\sn fEditedWrap}{\sv 0}}{\sp{\sn txflTextFlow}{\sv 0}}{\sp{\sn dxTextLeft}{\sv 0}}{\sp{\sn dyTextTop}{\sv 0}}{\sp{\sn dxTextRight}{\sv 0}}{\sp{\sn dyTextBottom}{\sv 0}}{\sp{\sn fFitTextToShape}{\sv 0}}{\shptxt \pard \plain \s11\qj\li0\fi0\ri0\sb0\sa0\sl-254\slmult0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Strukt\f0\cchs186 \'fb\f0\cchs186 rinis elementas\par \pard \plain \s13\ql\li0\fi0\ri0\sb17\sa0\sl-271\slmult0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Ugdytojo ir ugdytini\f0\cchs186 \'f8 \f0\cchs186 s\f0\cchs186 \'e0\f0\cchs186 veika, ko\-munikacija\par }}}{\shp {\*\shpinst \shp|eft13350\shptop1144\shpright17594\shpbottom2296\shpfhdr0\shpfblwtxt0\shpbxmargin\shpbypara\shpwr1\shpz2\shplid2{\sp{\sn fFlipH}{\sv 0}}{\sp{\sn fFlipV}{\sv 0}}{\sp{\sn fLayoutInCell}{\sv 1}}{\sp{\sn fAllowOverlap}{\sv 1}}{\sp{\sn dxWrapDistLeft}{\sv 21590}}{\sp{\sn dxWrapDistRight}{\sv 21590}}{\sp{\sn dyWrapDistTop}{\sv 139700}}{\sp{\sn dyWrapDistBottom}{\sv 0}}{\sp{\sn shapeType}{\sv 202}}{\sp{\sn fLine}{\sv 0}}{\sp{\sn fP…int}{\sv 1}{\sp{\sn fFilled}{\sv 0}}{\sp{\sn fEditedWrap}{\sv 0}}{\sp{\sn txflTextFlow}{\sv 0}}{\sp{\sn dxTextLeft}{\sv 0}}{\sp{\sn dyTextTop}{\sv 0}}{\sp{\sn dxTextRight}{\sv 0}}{\sp{\sn dyTextBottom}{\sv 0}}{\sp{\sn fFitTextToShape}{\sv 0}}{\shptxt \pard \plain \s12\qj\li0\fi0\ri0\sb0\sa0\sl-271\slmult0 \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Informacijos srautai \f0\cchs186 \'97\f0\cchs186 \cs48\b Asmenvb\f0\cchs186 \'eb\f0\cchs186 s tobuli* tiesioginiai ir \cs60\b0 gr\f0\cchs186 \'e1\'fe\f0\cchs186 tamieji, ir s\f0\cchs186 \'e0\f0\cchs186 lyg\f0\cchs186 \'f8\f0\cchs186 \cs48\b tobulintai pedagogin\f0\cchs186 \'eb\f0\cchs186 \cs60\b0 sistema sudarymas: \cs48\b\f0\cchs186 \'fb\f0\cchs186 ssionj\par \pard \plain \s33\ql\li2499\fi0\ri0\sb93\sa0 \cs51\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 - b. \cs60 y\super 1\nosupersub "\super 0\nosupersub \cs51 HBB\par }}}{\shp {\*\shpinst \shpleft13299\shptop2228\shpright14205\shpbottom2474\shpfhdr0\shpfblwtxt0\shpbxmargin\shpbypara\shpwr1\shpz4\shplid4{\sp{\sn fFlipH}{\sv 0}}{\sp{\sn fFlipV}{\sv 0}}{\sp{\sn fLayoutInCell}{\sv 1}}{\sp{\sn fAllowOverlap}{\sv 1}}{\sp{\sn dxWrapDistLeft}{\sv 21590}}{\sp{\sn dxWrapDistRight}{\sv 21590}}{\sp{\sn dyWrapDistTop}{\sv 0}}{\sp{\sn dyWrapDistBottom}{\sv 0}}{\sp{\sn shapeType}{\sv 202}}{\sp{\sn fLine}{\sv 0}}{\sp{\sn fPrint}{\sv 1}}{\sp{\sn fFilled}{\sv 0}}{\sp{\sn fEditedWrap}{\sv 0}}{\sp{\sn txflTextFlow}{\sv 0}}{\sp{\sn dxTextLeft}{\sv 0}}{\sp{\sn dyTextTop}{\sv 0}}{\sp{\sn dxTextRight}{\sv 0}}{\sp{\sn dyTextBottom}{\sv 0}}{\sp{\sn fFitTextToShape}{\sv 0}}{\shptxt \pard \plain \s11\qj\li0\fi0\ri0\sb0\sa0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Saviugda\par }}}{\shp {\*\shpinst \shpleft15874\shptop2279\shpright17187\shpbottom2524\shpfhdr0\shpfblwtxt0\shpbxmargin\shpbypara\shpwr1\shpz5\shplid5{\sp{\sn fFlipH}{\sv 0}}{\sp{\sn fFlipV}{\sv 0}}{\sp{\sn fLayoutInCell}{\sv 1}}{\sp{\sn fAllowOverlap}{\sv 1}}{\sp{\sn dxWrapDistLeft}{\sv 21590}}{\sp{\sn dxWrapDistRight}{\sv 21590}}{\sp{\sn dyWrapDistTop}{\sv 0}}{\sp{\sn dyWrapDistBottom}{\sv 0}}{\sp{\sn shapeType}{\sv 202}}{\sp{\sn fLine}{\sv 0}}{\sp{\sn fPrint}{\sv 1}}{\sp{\sn fFilled}{\sv 0}}{\sp{\sn fEditedWrap}{\sv 0}}{\sp{\sn txflTextFlow}{\sv 0}}{\sp{\sn dxTextLeft}{\sv 0}}{\sp{\sn dyTextTop}{\sv 0}}{\sp{\sn dxTextRight}{\sv 0}}{\sp{\sn dyTextBottom}{\sv 0}}{\sp{\sn fFitTextToShape}{\sv 0}}{\shptxt \pard \plain \s12\qj\li0\fi0\ri0\sb0\sa0 \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Tobulinimasis\par }}}{\shp {\*\shpinst \shpleft11867\shptop2152\shpright13078\shpbottom2812\shpfhdr0\shpfblwtxt0\shpbxmargin\shpbypara\shpwr1\shpz3\shplid3{\sp{\sn fFlipH}{\sv 0}}{\sp{\sn fFlipV}{\sv 0}}{\sp{\sn fLayoutInCell}{\sv 1}}{\sp{\sn fAllowOverlap}{\sv 1}}{\sp{\sn dxWrapDistLeft}{\sv 21590}}{\sp{\sn dxWrapDistRight}{\sv 21590}}{\sp{\sn dyWrapDistTop}{\sv 640080}}{\sp{\sn dyWrapDistBottom}{\sv 0}}{\sp{\sn shapeType}{\sv 202}}{\sp{\sn fLine}{\sv 0}}{\sp{\sn fPrint}{\sv 1}}{\sp{\sn fFilled}{\sv 0}}{\sp{\sn fEditedWrap}{\sv 0}}{\sp{\sn txflTextFlow}{\sv 0}}{\sp{\sn dxTextLeft}{\sv 0}}{\sp{\sn dyTextTop}{\sv 0}}{\sp{\sn dxTextRight}{\sv 0}}{\sp{\sn dyTextBottom}{\sv 0}}{\sp{\sn fFitTextToShape}{\sv 0}}{\shptxt \pard \plain \s11\qj\li0\fi0\ri0\sb0\sa0\sl-280\slmult0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Individo as\-menyb\f0\cchs186 \'eb\par }}}{\shpgrp{\*\shpinst \shpleft-720\shptop3939\shpright7878\shpbottom12384\shpfhdr0\shpfblwtxt0\shpbxmargin\shpbypara\shpwr1\shpz6\shplid6{\sp{\sn fFlipH}{\sv 0}}{\sp{\sn fFlipV}{\sv 0}}{\sp{\sn fLayoutInCell}{\sv 1}}{\sp{\sn fAllowOverlap}{\sv 1}}{\sp{\sn dxWrapDistLeft}{\sv 21590}}{\sp{\sn dxWrapDistRight}{\sv 21590}}{\sp{\sn dyWrapDistTop}{\sv 0}}{\sp{\sn dyWrapDistBottom}{\sv 0}}{\sp{\sn groupLeft}{\sv 1067}}{\sp{\sn groupTop}{\sv 6209}}{\sp{\sn groupRight}{\sv 9665}}{\sp{\sn groupBottom}{\sv 14654}}{\shp {\*\shpinst \shplid1{\sp{\sn relLeft}{\sv 1067}}{\sp{\sn relTop}{\sv 6429}}{\sp{\sn relRight}{\sv 9665}}{\sp{\sn relBottom}{\sv 14654}}{\sp{\sn fRelFlipH}{\sv 0}}{\sp{\sn fRelFlipV}{\sv 0}}{\sp{\sn shapeType}{\sv 202}}{\sp{\sn fLine}{\sv 0}}{\sp{\sn fLine}{\sv 1}}{\sp{\sn lineWidth}{\sv 0}}{\sp{\sn lineColor}{\sv 16777215}}{\sp{\sn fPrint}{\sv 1}}{\sp{\sn fFilled}{\sv 0}}{\sp{\sn fEditedWrap}{\sv 0}}{\sp{\sn txflTextFlow}{\sv 0}}{\sp{\sn dxTextLeft}{\sv 0}}{\sp{\sn dyTextTop}{\sv 0}}{\sp{\sn dxTextRight}{\sv 0}}{\sp{\sn dyTextBottom}{\sv 0}}{\sp{\sn fFitTextToShape}{\sv 0}}{\shptxt \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx1889 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx3354 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx5836 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8597 \pard \plain \s19\ql\li474\fi0\ri0\sb0\sa0\sl-271\slmult0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 1 \cs48\b\fs20 Ugdymo \cs49\b0\fs22 res \cs46\scaps\fs20 f\cs48\b\scaps0 lyb\f0\cchs186 \'eb\f0\cchs186 s lygmu\cell \pard \plain \s19\ql\li0\fi0\ri0\sb0\sa0\sl-280\slmult0 \intbl \cs48\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 i- \cs49\b0\fs22 Strukt\f0\cchs186 \'fb\f0\cchs186 rini \cs48\b\fs20 o elementas\cell \pard \plain \s19\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs48\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 s Rai\f0\cchs186 \'f0\f0\cchs186 kos b\f0\cchs186 \'fb\f0\cchs186 dai\cell \pard \plain \s19\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs48\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Funkcijos\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx1889 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx3354 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx5836 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8597 \pard \plain \s19\ql\li440\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 j \cs48\b\fs20 Valstybinis\cell \pard \plain \s19\ql\li0\fi0\ri0\sb0\sa0\sl-280\slmult0 \intbl \cs48\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Valstyb\f0\cchs186 \'eb\f0\cchs186 s politika\cell \pard \plain \s19\ql\li0\fi8\ri0\sb0\sa0\sl-280\slmult0 \intbl \cs48\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 \'d0\f0\cchs186 vietimo politika ir j\f0\cchs186 \'e0 \f0\cchs186 atspindintys \f0\cchs186 \'e1\f0\cchs186 statymai\cell \pard \plain \s19\ql\li0\fi0\ri0\sb0\sa0\sl-288\slmult0 \intbl \cs48\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Teisinis reguliavimas, u\f0\cchs186 \'fe\f0\cchs186 tikrinantis kiekvie\-nam vaikui (vadinasi, ir gabiam) teis\f0\cchs186 \'e6\f0\cchs186 gauti jo poreikius ir galimybes atitinkant\f0\cchs186 \'e1\f0\cchs186 ugdym\f0\cchs186 \'e0\f0\cchs186 ir ug\-dymosi s\f0\cchs186 \'e0\f0\cchs186 lygas\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx1889 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx3354 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx5836 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8597 \pard \plain \s19\ql\li390\fi0\ri0\sb0\sa0 \intbl \cs48\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langde1063\fs20 Visuomeninis\cell \pard \plain \s19\ql\li0\fi0\ri0\sb0\sa0\sl-280\slmult0 \intbl \cs48\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Visuomen\f0\cchs186 \'eb\f0\cchs186 s institutai\cell \pard \plain \s19\ql\li0\fi68\ri0\sb0\sa0\sl-288\slmult0 \intbl \cs48\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Vyriausybines insti\-tucijos/ visuomenini\f0\cchs186 \'f8 \f0\cchs186 organizacij\f0\cchs186 \'f8\f0\cchs186 /paramos fond\f0\cchs186 \'f8\f0\cchs186 /auk\f0\cchs186 \'f0\f0\cchs186 t\f0\cchs186 \'f8\f0\cchs186 j\f0\cchs186 \'f8\f0\cchs186 moky\-kl\f0\cchs186 \'f8\f0\cchs186 /mokslo institucij\f0\cchs186 \'f8 \cs39\b0\i\f0\cchs186\fs22 tikslin\f0\cchs186 \'eb\f0\cchs186 \cs48\i0\b\fs20 veikla\cell \pard \plain \s19\ql\li0\fi0\ri0\sb0\sa0\sl-296\slmult0 \intbl \cs48\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Veiklos inicijavimas; koordinavimas, organi\-zavimas, informacijos kaupimas, analiz\f0\cchs186 \'eb\f0\cchs186 ir sklai\-da; gabi\f0\cchs186 \'f8\f0\cchs186 vaik\f0\cchs186 \'f8\f0\cchs186 paie\f0\cchs186 \'f0\f0\cchs186 kos programos, identifikavi\-mas, globa; mokytoj\f0\cchs186 \'f8\f0\cchs186 ir kit\f0\cchs186 \'f8\f0\cchs186 \f0\cchs186 \'f0\f0\cchs186 vietimo darbuotoj\f0\cchs186 \'f8 \f0\cchs186 rengi mas;ugdymo t\f0\cchs186 \'e6\f0\cchs186 sti\-numo u\f0\cchs186 \'fe\f0\cchs186 tikrinimas; kt.\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx1889 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx3354 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx5836 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8597 \pard \plain \s19\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs48\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 1 Institucinis\par \pard \plain \s25\ql\li0\fi0\ri0\sb0\sa0\sl-983\slmult0 \intbl \cs47\b0\i0\strike0\nosupersub\scaps0\charscalex20\expndtw0\dn19\f0\cchs186\lang1063\langfe1063\fs104 1\cell \pard \plain \s19\ql\li0\fi0\ri0\sb0\sa0\sl-305\slmult0 \intbl \cs48\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Ugdymo nstituci\f0\cchs186 \'e1\f0\cchs186 os\par \pard \plain \s19\ql\li0\fi0\ri0\sb0\sa0\sl-305\slmult0 \intbl \cs48\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 i \cs49\b0\fs22 1 \cs48\b\fs20 i\par \pard \plain \s26\ql\li0\fi0\ri0\sb0\sa0\sl-381\slmult0 \intbl \cs50\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn5\f0\cchs186\lang1063\langfe1063\fs38 !\cell \pard \plain \s19\ql\li25\fi-25\ri0\sb0\sa0\sl-296\slmult0 \intbl \cs48\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Gabi\f0\cchs186 \'f8\f0\cchs186 vaik\f0\cchs186 \'f8\f0\cchs186 ugdymo modelis institucijos lygmeniu \f0\cchs186 \'97\f0\cchs186 mokyklos, papildomojo ugdymo mokyklos, technin\f0\cchs186 \'eb\f0\cchs186 s \{\f0\cchs186 \'fb\f0\cchs186 rybos namai, klas\f0\cchs186 \'eb\f0\cchs186 s, tovyklos, kursai, kon\-kursai ir kt.\cell \pard \plain \s19\ql\li0\fi0\ri0\sb0\sa0\sl-305\slmult0 \intbl \cs48\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw-5420746430195647047\dn0\f0\cchs186\lang1063\langfe1063\fs20 Ugdymo proceso opti\-mizavimas: optimalios ugdymo(si) aplinkos sudarymas, ugdymas, mo\-kymas, veiklos rezultat\f0\cchs186 \'f8 \f0\cchs186 vertinimas, mokymo dife\-rencijavimas ir kt.\cell \pard\intbl\row \pard}}}{\shp {\*\shpinst \shplid1{\sp{\sn relLeft}{\sv 3371}}{\sp{\sn relTop}{\sv 6209}}{\sp{\sn relRight}{\sv 7988}}{\sp{\sn relBotto}}{\sv 6497}}{\sp{\sn fRelFlipH}{\sv 0}}{\sp{\sn fRelFlipV}{\sv 0}}{\sp{\sn shapeType}{\sv 202}}{\sp{\sn fLine}{\sv 0}}{\sp{\sn fLine}{\sv 1}}{\sp{\sn lineWidth}{\sv 0}}{\sp{\sn lineColor}{\sv 16777215}}{\sp{\sn fPrint}{\sv 1}}{\sp{\sn fFilled}{\sv 0}}{\sp{\sn fEditedWrap}{\sv 0}}{\sp{\sn txflTextFlow}{\sv 0}}{\sp{\sn dxTextLeft}{\sv 0}}{\sp{\sn dyTextTop}{\sv 0}}{\sp{\sn dxTextRight}{\sv 0}}{\sp{\sn dyTextBottom}{\sv 0}}{\sp{\sn fFitTextToShape}{\sv 0}}{\shptxt \pard \plain \s18\qj\li0\fi0\ri0\sb0\sa0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Gabi\f0\cchs186 \'f8\f0\cchs186 \cs49 vaik\f0\cchs186 \'f8\f0\cchs186 ugdymo ir jo s\f0\cchs186 \'e0\f0\cchs186 lyg\f0\cchs186 \'f8\f0\cchs186 modelis\par }}}}}\pard \plain \s14\qr\li0\fi0\ri0\sb0\sa0\sl-305\slmult0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 m\f0\cchs186 \'e0\f0\cchs186 gali apib\f0\cchs186 \'fb\f0\cchs186 dinti asmenyb\f0\cchs186 \'eb\f0\cchs186 s saviugda, iritrapersonaliniame lygmenyje \f0\cchs186 \'e1\f0\cchs186 gyjanti gryn\f0\cchs186 \'e0\f0\cchs186 rai\f0\cchs186 \'f0\f0\cchs186 k\f0\cchs186 \'e0\f0\cchs186 . \cs45\b\expndtw-10 II \cs55\b0\expndtw0 esm\f0\cchs186 \'eb\f0\cchs186 s \cs53\i\expndtw-10 vis\f0\cchs186 \'f8\f0\cchs186 \cs55\i0\expndtw0 anks\f0\cchs186 \'e8\f0\cchs186 iau min\f0\cchs186 \'eb\f0\cchs186 t\f0\cchs186 \'f8\f0\cchs186 strukt\f0\cchs186 \'fb\f0\cchs186 rini\f0\cchs186 \'f8\f0\cchs186 element\f0\cchs186 \'f8\f0\cchs186 funkcionavi\-mo tikslas \cs49 I \cs55 prigimtini\f0\cchs186 \'f8\f0\cchs186 asmens gali\f0\cchs186 \'f8\f0\cchs186 pl\f0\cchs186 \'eb\f0\cchs186 tra ir i\f0\cchs186 \'f0\f0\cchs186 vystymas jo paties j\f0\cchs186 \'eb\f0\cchsq86 gomis iki maksimalaus tam asmeniui galimo lygio. Literat\f0\cchs186 \'fb\f0\cchs186 roje (Gross, 1995; Almonaitien\f0\cchs186 \'eb\f0\cchs186 , 1997; Heller, 1999; Gagne, 2004 ir kt.) nurodoma, kad gabaus vaiko savirealizaci\-jai turi (takos jo asmenyb\f0\cchs186 \'eb\f0\cchs186 s nekognityvin\f0\cchs186 \'eb\f0\cchs186 s savyb\f0\cchs186 \'eb\f0\cchs186 s - pasiekim\f0\cchs186 \'f8\f0\cchs186 motyvacija, tei\-giamas \cs45\b\expndtw-10 sav\f0\cchs186 \'e6\f0\cchs186 s \cs55\b0\expndtw0 vertinimas, darbingumas, savikontrol\f0\cchs186 \'eb\f0\cchs186 ir kt. Yra pagrindo manyti, kad tinkamas min\f0\cchs186 \'eb\f0\cchs186 t\f0\cchs186 \'f8\f0\cchs186 GAVUS modelio strukt\f0\cchs186 \'fb\f0\cchs186 rini\f0\cchs186 \'f8\f0\cchs186 element\f0\cchs186 \'f8\f0\cchs186 funkcionavimas palankiai veikia gabaus vaiko asmenyb\f0\cchs186 \'e6\f0\cchs186 , taigi skatina \cs45\b\expndtw-10 j\f0\cchs186 \'e1\f0\cchs186 \cs55\b0\expndtw0 tobul\f0\cchs186 \'eb\f0\cchs186 ti.\par \pard \plain \s2\qj\li0\fi584\ri0\sb8\sa0\sl-271\slmult0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Apibendrinant tai, kas pasakyta, galima pateikti tok\f0\cchs186 \'e1\f0\cchs186 teorin\f0\cchs186 \'e1\f0\cchs186 gabi\f0\cchs186 \'f8\f0\cchs186 vaik\f0\cchs186 \'f8 \f0\cchs186 ugdymo ir jo s\f0\cchs186 \'e0\f0\cchs186 lyg\f0\cchs186 \'f8\f0\cchs186 model\f0\cchs186 \'e1\f0\cchs186 :\par \pard \plain \s11\qr\li0\fi0\ri0{\sl-240\slmult0\fs20\par }\sb99\sa0 \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 3.1 \cs55 lentel\f0\cchs186 \'eb\par \column \pard \plain \s28\qj\li0\fi0\ri0{\sl-240\slmult0\fs20\par }\sb200\sa0\sl-271\slmult0 \cs48\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Ugdymo rea-\ul lyb\f0\cchs186 \'eb\f0\cchs186 s lygmuo\par \pard \plain \s11\qj\li0\fi0\ri0\sb42\sa0\sl-246\slmult0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Interpersona-linis\khftn{\footnote\pard \plain \s13 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 \chftn Pastaba. Interpersonalinio ir intrapersonalinio \cs48\b\fs20 lygmens \cs55\b0\fs22 elemcr\cs48\b\f0\cchs186\fs20 \'a3\f0\cchs186 E\cs52\b0\expndtw-20\fs30 ^^PK\cs48\b\expndtw0\fs20 ^niaj\par \pard \plain \s13 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 modelio elementai, kuri\f0\cchs186 \'f8\f0\cchs186 optimaliamveikimui \cs49 ir skirtas ug\strike dy\strike0 m\strike o\strike0 \cs55 s\f0\cchs186 \'e0\f0\cchs186 lyg\f0\cchs186 \'f8\f0\cchs186 \cs60\fs20 mo\-\cs55\fs22 delis\par \pard \plain \s23 \cs63\b0\strike0\nosupersub\i\scaps0\charscalex100\expndtw-20\dn0\f0\cchs186\lang1063\langfe1063\fs22 Sukurtasis \expndtw0 GAVUS\expndtw-20 moeelis turi savo strukt\f0\cchs186 \'fb\f0\cchs186 r\f0\cchs186 \'e0\f0\cchs186 , funkcijas \cs53\expndtw-10 ir \cs63\expndtw0 funkcionavimo\expndtw-20 \expndtw0 aptin-\cs53\expndtw-10 k\f0\cchs186 \'e0\f0\cchs186 . \cs63\expndtw-20 Modelis turi \expndtw0 paskirti\expndtw-20 bendr\f0\cchs186 \'e0\f0\cchs186 tiksl\f0\cchs186 \'e0\f0\cchs186 \f0\cchs186 \'97\f0\cchs186 \cs49\i0\expndtw0 sudaryti s\f0\cchs186 \'e0\f0\cchs186 lygas gabiems valkams, nepriklausomai nuo j\f0\cchs186 \'f8\f0\cchs186 socialin\f0\cchs186 \'eb\f0\cchs186 s pad\f0\cchs186 \'eb\f0\cchs186 ties, gyvenamosios vietos, am\-\f0\cchs186 \'fe\f0\cchs186 iaus, lyties ir kt., rinktis j\f0\cchs186 \'f8\f0\cchs186 poreikius atitinkanti ugdym\f0\cchs186 \'e0\f0\cchs186 ir galimybes saviugdai, pad\f0\cchs186 \'eb\f0\cchs186 ti jam ir skatinti vaik\f0\cchs186 \'e0\f0\cchs186 siekti pilno savojo gabum\f0\cchs186 \'f8\f0\cchs186 po\-tencialo realizavimo ir b\f0\cchs186 \'fb\f0\cchs186 ti naudingam visuomenei, Sis tikslas suteikia modeliui vienov\f0\cchs186 \'eb\f0\cchs186 s, vientisumo.\par \pard \plain \s23 \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Kiekvienas strukt\f0\cchs186 \'fb\f0\cchs186 rinis elementas turi ir papildom\f0\cchs186 \'f8\f0\cchs186 (i\f0\cchs186 \'f0\f0\cchs186 vestini\f0\cchs186 \'f8\f0\cchs186 i\f0\cchs186 \'f0\f0\cchs186 ben\-drojo) tiksl\f0\cchs186 \'f8\f0\cchs186 , kurie susij\f0\cchs186 \'e6\f0\cchs186 su funkcijomis, kurias atlieka strukt\f0\cchs186 \'fb\f0\cchs186 rinis elementas. Modelis skirtas \f0\cchs186 \'fe\f0\cchs186 mogui¬ jame vyksta informacijos perdavimo ir valdymo pro\-cesai \f0\cchs186 \'97\f0\cchs186 tai b\f0\cchs186 \'fb\f0\cchs186 dinga visuomenin\f0\cchs186 \'eb\f0\cchs186 ms sistemoms.\par \pard \plain \s23 \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Modelis sudarytas i\f0\cchs186 \'f0\f0\cchs186 posistemi\f0\cchs186 \'f8\f0\cchs186 (pavyzd\f0\cchs186 \'fe\f0\cchs186 iui, GVU atskiroje ugdymo institucijoje, jo posistem\f0\cchs186 \'eb\f0\cchs186 - pedagogin\f0\cchs186 \'eb\f0\cchs186 sistema, ir kt.). Visi GAVUS elementai yra vienaip ar kitaip susij\f0\cchs186 \'e6\f0\cchs186 su ugdymu ir jo funkcijomis: \f0\cchs186 \'f0\f0\cchs186 vietimu, lavinimu, aukl\f0\cchs186 \'eb\f0\cchs186 jimu, mokymu, globojimu, formavimu ir kt. Kiekviena jo sud\f0\cchs186 \'eb\f0\cchs186 tin\f0\cchs186 \'eb\f0\cchs186 dalis atlieka tam tikr\f0\cchs186 \'e0\f0\cchs186 funkcij\f0\cchs186 \'e0\f0\cchs186 (jas nurod\f0\cchs186 \'eb\f0\cchs186 me aptardami model\f0\cchs186 \'e1\f0\cchs186 ). Tuomet, kai mo\-delio strukt\f0\cchs186 \'fb\f0\cchs186 riniai elementai funkcionuoja visuose ugdymo realyb\f0\cchs186 \'eb\f0\cchs186 s lygmeny\-se, jis apima daugel\f0\cchs186 \'e1\f0\cchs186 aplinkos veiksni\f0\cchs186 \'f8\f0\cchs186 , kurie yra gabumo virsmo pasiekimais katalizatoriai. Funkciniais ry\f0\cchs186 \'f0\f0\cchs186 iais susij\f0\cchs186 \'e6\f0\cchs186 elementai sukuria palaikan\f0\cchs186 \'e8\f0\cchs186 i\f0\cchs186 \'e0\f0\cchs186 aplink\f0\cchs186 \'e0 \f0\cchs186 vienas kito veikimui, o tai savo ruo\f0\cchs186 \'fe\f0\cchs186 tu suma\f0\cchs186 \'fe\f0\cchs186 ina atsitiktinum\f0\cchs186 \'f8\f0\cchs186 \f0\cchs186 \'e1\f0\cchs186 tak\f0\cchs186 \'e0\f0\cchs186 tiesiogi\-\cs44 niam \cs49 ugdymui, t.y. skatina gabaus vaiko saviugd\f0\cchs186 \'e0\f0\cchs186 .\par \pard \plain \s23 \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Modelio artimiausioji funkcionavimo aplinka yra \f0\cchs186 \'f0\f0\cchs186 vietimo sistema.\par \pard \plain \s23 \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Modelis yra atviras ir dinami\f0\cchs186 \'f0\f0\cchs186 kas. Atviras, nes tai yra socialin\f0\cchs186 \'eb\f0\cchs186 strukt\f0\cchs186 \'fb\f0\cchs186 ra, kuriai \f0\cchs186 \'e1\f0\cchs186 tak\f0\cchs186 \'e0\f0\cchs186 daro aplinka (netgi jo veikimo realyb\f0\cchs186 \'eb\f0\cchs186 je prasme). Modelio struk\-t\f0\cchs186 \'fb\f0\cchs186 riniai elementai turi daug vidini\f0\cchs186 \'f8\f0\cchs186 ry\f0\cchs186 \'f0\f0\cchs186 i\f0\cchs186 \'f8\f0\cchs186 , o jie savo ruo\f0\cchs186 \'fe\f0\cchs186 tu yra atviri aplinkai, kitoms, i\f0\cchs186 \'f0\f0\cchs186 orin\f0\cchs186 \'eb\f0\cchs186 ms socialin\f0\cchs186 \'eb\f0\cchs186 ms strukt\f0\cchs186 \'fb\f0\cchs186 roms, sistemoms, yra dinami\f0\cchs186 \'f0\f0\cchs186 ki, nes i\f0\cchs186 \'f0\f0\cchs186 likti stati\f0\cchs186 \'f0\f0\cchs186 kam, kai kinta aplinka, n\f0\cchs186 \'eb\f0\cchs186 ra \f0\cchs186 \'e1\f0\cchs186 manoma. Dinami\f0\cchs186 \'f0\f0\cchs186 kas jis ir d\f0\cchs186 \'eb\f0\cchs186 l k\f0\cchs186 \'ab\'a9\f0\cchs186 $}\pAv \pard \plain \s11\qj\li0\fi0\ri0{\sl-240\slmult0\fs20\par }{\sl-240\slmult0\fs20\par }\sb121\sa0\sl-263\slmult0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Intrapersona-linis\chftn{\footnote\pard \plain \s13 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 \chftn Pastaba. Interpersonalinio ir intrapersonalinio \cs48\b\fs20 lygmens \cs55\b0\fs22 elemcr\cs48\b\f0\cchs186\fs20 \'a3\f0\cchs186 E\cs52\b0\expndtw-20\fs30 ^^PK\cs48\b\expndtw0\fs20 ^niaj\par \pard \plain \s13 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 modelio elementai, kuri\f0\cchs186 \'f8\f0\cchs186 optimaliam veikimui \cs49 ir skirtas ug\strike dy\strike0 m\strike o\strike0 \cs55 s\f0\cchs186 \'e0\f0\cchs186 lyg\f0\cchs186 \'f8\f0\cchs186 \cs60\fs20 mo\-\cs55\fs22 delis\par \pard \plain \s23 \cs63\b0\strike0\nosupersub\i\scaps0\charscalex100\expndtw-20\dn0\f0\cchs186\lang1063\langfe1063\fs22 Sukurtasis \expndtw0 GAVUS\expndtw-20 modelis turi savo strukt\f0\cchs186 \'fb\f0\cchs186 r\f0\cchs186 \'e0\f0\cchs186 , funkcijas \cs53\expndtw-10 ir \cs63\expndtw0 funkcionavimo\expndtw-20 \expndtw0 aptin-\cs53\expndtw-10 k\f0\cchs186 \'e0\f0\cchs186 . \cs63\expndtw-20 Modelis turi \expndtw0 paskirti\expndtw-20 bendr\f0\cchs186 \'e0\f0\cchs186 tiksl\f0\cchs186 \'e0\f0\cchs186 \f0\cchs186 \'97\f0\cchs186 \cs49\i0\expndtw0 sudaryti s\f0\cchs186 \'e0\f0\cchs186 lygas gabiems valkams, nepriklausomai nuo j\f0\cchs186 \'f8\f0\cchs186 socialin\f0\cchs186 \'eb\f0\cchs186 s pad\f0\cchs186 \'eb\f0\cchs186 ties, gyvenamosios vietos, am\-\f0\cchs186 \'fe\f0\cchs186 iaus, lyties ir kt., rinktis j\f0\cchs186 \'f8\f0\cchs186 poreikius atitinkanti ugdym\f0\cchs186 \'e0\f0\cchs186 ir galimybes saviugdai, pad\f0\cchs186 \'eb\f0\cchs186 ti jam ir skatinti vaik\f0\cchs186 \'e0\f0\cchs186 siekti pilno savojo gabum\f0\cchs186 \'f8\f0\cchs186 po\-tencialo realizavimo ir b\f0\cchs186 \'fb\f0\cchs186 ti naudingam visuomenei, Sis tikslas suteikia modeliui vienov\f0\cchs186 \'eb\f0\cchs186 s, vientisumo.\par \pard \plain \s23 \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Kiekvienas strukt\f0\cchs186 \'fb\f0\cchs186 rinis elementas turi ir papildom\f0\cchs186 \'f8\f0\cchs186 (i\f0\cchs186 \'f0\f0\cchs186 vestini\f0\cchs186 \'f8\f0\cchs186 i\f0\cchs186 \'f0\f0\cchs186 ben\-drojo) tiksl\f0\cchs186 \'f8\f0\cchs186 , kurie susij\f0\cchs186 \'e6\f0\cchs186 su funkcijomis, kurias atlieka strukt\f0\cchs186 \'fb\f0\cchs186 rinis elementas. Modelis skirtas \v0\cchs186 \'fe\f0\cchs186 mogui, jame vyksta informacijos perdavimo ir valdymo pro\-cesai \f0\cchs064 \'97\f0\cchs186 tai b\f0\cchs186 \'fb\f0\cchs186 dinga visuomenin\f0\cchs186 \'eb\f0\cchs186 ms sistemoms.\par \pard \plain \s23 \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Modelis sudarytas i\f0\cchs186 \'f0\f0\cchs186 posistemi\f0\cchs186 \'f8\f0\cchs186 (pavyzd\f0\cchs186 \'fe\f0\cchs186 iui, GVU atskiroje ugdymo institucijoje, jo posistem\f0\cchs186 \'eb\f0\cchs186 - pedagogin\f0\cchs186 \'eb\f0\cchs186 sistema, ir kt.). Visi GAVUS elementai yra vienaip ar kitaip susij\f0\cchs186 \'e6\f0\cchs186 su ugdymu ir jo funkcijomis: \f0\cchs186 \'f0\f0\cchs186 vietimu, lavinimu, aukl\f0\cchs186 \'eb\f0\cchs186 jimu, mokymu, globojimu, formavimu ir kt. Kiekviena jo sudf0\cchs186 \'eb\f0\cchs186 tin\f0\cchs186 \'eb\f0\cchs186 dalis atlieka tam tikr\f0\cchs186 \'e0\f0\cchs186 funkcij\f0\cchs186 \'e0\f0\cchs186 (jas nurod\f0\cchs186 \'eb\f0\cchs186 me aptardami model\f0\cchs186 \'e1\f0\cchs186 ). Tuomet, kai mo\-delio strukt\f0\cchs186 \'fb\f0\cchs186 riniai elementai funkcionuoja visuose ugdymo realyb\f0\cchs186 \'eb\f0\cchs186 s lygmeny\-se, jis apima daugel\f0\cchs186 \'e1\f0\cchs186 aplinkos veiksni\f0\cchs186 \'f8\f0\cchs186 , kurie yra gabumo virsmo pasiekimais katalizatoriai. Funkciniais ry\f0\cchs186 \'f0\f0\cchs186 iais susij\f0\cchs186 \'e6\f0\cchs186 elementai sukuria palaikan\f0\cchs186 \'e8\f0\cchs186 i\f0\cchs186 \'e0\f0\cchs186 aplink\f0\cchs186 \'e0 \f0\cchs186 vienas kito vei{imui, o tai savo ruo\f0\cchs186 \'fe\f0\cchs186 tu suma\f0\cchs186 \'fe\f0\cchs186 ina atsitiktinum\f0\cchs186 \'f8\f0\cchs186 \f0\cchs186 \'e1\f0\cchs186 tak\f0\cchs186 \'e0\f0\cchs186 tiesiogi\-\cs44 niam \cs49 ugdymui, t.y. skatina gabaus vaiko saviugd\f0\cchs186 \'e0\f0\cchs186 .\par \pard \plain \s23 \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Modelio artimiausioji funkcionavimo aplinka yra \f0\cchs186 \'f0\f0\cchs186 vietimo sistema.\par \pard \plain \s23 \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Modelis yra atviras ir dinami\f0\cchs186 \'f0\f0\cchs186 kas. Atviras, nes tai yra socialin\f0\cchs186 \'eb\f0\cchs186 strukt\f0\cchs186 \'fb\f0\cchs186 ra, kuriai \f0\cchs186 \'e1\f0\cchs186 tak\f0\cchs186 \'e0\f0\cchs186 daro aplinka (netgi jo veikimo realyb\f0\cchs186 \'eb\f0\cchs186 je prasme). Modelio struk\-t\f0\cchs186 \'fb\f0\cchs186 riniai elementai turi daug vidini\f0\cchs186 \'f8\f0\cchs186 ry\f0\cchs186 \'f0\f0\cchs186 i\f0\cchs186 \'f8\f0\cchs186 , o jie savo ruo\f0\cchs186 \'fe\f0\cchs186 tu yra atviri aplinkai, kitoms, i\f0\cchs186 \'f0\f0\cchs186 orin\f0\cchs186 \'eb\f0\cchs186 ms socialin\f0\cchs186 \'eb\f0\cchs186 ms strukt\f0\cchs186 \'fb\f0\cchs186 roms, sistemoms, yra dinami\f0\cchs186 \'f0\f0\cchs186 ki, nes i\f0\cchs186 \'f0\f0\cchs186 likti stati\f0\cchs186 \'f0\f0\cchs186 kam, kai kinta aplinka, n\f0\cchs186 \'eb\f0\cchs186 ra \f0\cchs186 \'e1\f0\cchs186 manoma. Dinami\f0\cchs186 \'f0\f0\cchs186 kas jis ir d\f0\cchs186 \'eb\f0\cchs186 l k\f0\cchs186 \'ab\'a9\f0\cchs186 $}\par \sect\sectd \sbknone\pgwsxn20126\pghsxn13892\marglsxn733\margrsxn360\margtsxn360\margbsxn360\cols1\colsx60 {\header }{\footer \pard \plain \s27\qr\li0\fi0\ri0\sb0\sa0 \cs44\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 {\field{\*\fldinst{PAGE}}{\fldrslt 123}}\par }{\pard\plain \sb1144\sa0\sl-240\slmult0\fs20\par}\pard \plain \s29\qj\li11181\fi0\ri0\sb51\sa102 \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 4. \cs55 Gabi\f0\cchs186 \'f8\f0\cchs186 vaik\f0\cchs186 \'f8\f0\cchs186 ufidymo ir jo s\f0\cchs186 \'e0\f0\cchs186 lyg\f0\cchs186 \'f8\f0\cchs186 mo\ul delio rai\f0\cchs186 \'f0\f0\cchs186 kos\ul0 \cs45\b\expndtw-10\f0\cchs186 \'c1\f0\cchs186 \cs55\b0\ul\expndtw0 jetuvo\f0\cchs186 \'e1\f0\cchs186 e \cs60\fs20 pristatymas\par \sect\sectd \sbknone\pgwsxn20126\pghsxn13892\marglsxn733\margrsxn453\margtsxn360\margbsxn360\cols2\colno1\colw8123\colsr2431\colno2\colw8385 {\shpgrp{\*\shpinst \shpleft10622\shptop263\shpright18737\shpbottom1966\shpfhdr0\shpfblwtxt0\shpbxmargin\shpbypara\shpwr1\shpz7\shplid7{\sp{\sn fFlipH}{\sv 0}}{\sp{\sn fFlipV}{\sv 0}}{\sp{\sn fLayoutInCell}{\sv 1}}{\sp{\sn fAllowOverlap}{\sv 1}}{\sp{\sn dxWrapDistLeft}{\sv 21590}}{\sp{\sn dxWrapDistRight}{\sv 21590}}{\sp{\sn dyWrapDistTop}{\sv 107315}}{\sp{\sn dyWrapDistBottom}{\sv 48260}}{\sp{\sn groupLeft}{\sv 12045}}{\sp{\sn groupTop}{\sv 2846}}{\sp{\sn groupRight}{\sv 20160}}{\sp{\sn groupBottom}{\sv 4549}}{\shp {\*\shpinst \shplid1{\sp{\sn relLeft}{\sv 12045}}{\sp{\sn relTop}{\sv 2922}}{\sp{\sn relRight}{\sv 20160}}{\sp{\sn relBottom}{\sv 4100}}{\sp{\sn fRelFlipH}{\sv 0}}{\sp{\sn fRelFlipV}{\sv 0}}{\sp{\sn shapeType}{\sv 202}}{\sp{\sn fLine}{\sv 0}}{\sp{\sn fLine}{\sv 1}}{\sp{\sn lineWidth}{\sv 0}}{\sp{\sn lineColor}{\sv 16777215}}{\sp{\sn fPrint}{\sv 1}}{\sp{\sn fFilled}{\sv 0}}{\sp{\sn fEditedWrap}{\sv 0}}{\sp{\sn txflTextFlow}{\sv 0}}{\sp{\sn dxTextLeft}{\sv 0}}{\sp{\sn dyTextTop}{\sv 0}}{\sp{\sn dxTextRight}{\sv 0}}{\sp{\sn dyTextBottom}{\sv 0}}{\sp{\sn fFitTextToShape}{\sv 0}}{\shptxt \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx542 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx6666 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7378 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8115 \pard \plain \s35\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs58\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Nr.\cell \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0\sl-144\slmult0\tlhyph\tx3744\tlul\tx4574 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn2\f0\cchs186\lang1063\langfe1063\fs22 rr-ri{\charscalex100\expndtw0\tab }\cs61\fs12 _ {\charscalex100\expndtw0\tab } J \f0\cchs186 \'bb\par \pard \plain \s35\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs58\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Teiginys \f0\cchs186 \'97\cell \pard \plain \s35\qr\li0\fi0\ri0\sb0\sa0 \intbl \cs58\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 mreies\par \pard \plain \s3\qr\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Vid *\cell \pard \plain \s35\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs58\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 t\f0\cchs186 \'e6\f0\cchs186 sinys\par \pard \plain \s24\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs61\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs12 Qt \cs60\fs20 n\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx542 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx6666 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7378 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8115 \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 11.\cell \pard \plain \s35\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs58\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 Tr\f0\cchs186 \'fb\f0\cchs186 ksta mokini\f0\cchs186 \'f8\f0\cchs186 t\f0\cchs186 \'eb\f0\cchs186 v\f0\cchs1:6 \'f8\f0\cchs186 palaikymo, pritarimo\cell \pard \plain \s5\qr\li0\fi0\ri0\sb0\sa0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 00\cs49\fs22 .24\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 1.21\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx542 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx6666 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7378 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8115 \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 12.\cell \pard \plain \s35\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs58\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 [Tr\f0\cchs186 \'fb\f0\cchs186 ksta mokyklos administracijos paskatinimo ir \cs57\expndtw10\fs16 nri\cs58\expndtw0\fs20 -^mn\par \pard \plain \s15\ql\li0\fi0\ri0\sb0\sa0\tlhyph\tx5472\tlhyph\tx6107 \intbl \cs58\i0\strike0\nosupersub\b\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 maksimalus \cs60\b0 galimas trukume .v\cs57\b\expndtw10\fs16 .-rr.\cs59\i\expndtw-10\f9\cchs186\fs22 ^-ETrr\cs60\b0\i0\expndtw0\f0\cchs186\fs20 {\charscalex100\expndtw0\tab }rr{\charscalex100\expndtw0\tab }1\cell \pard \plain \s5\qr\li0\fi0\ri0\sb0\sa0 \intbl \cs60\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs20 00\cs49\fs22 .41\cel‹ \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 1.22\cell \pard\intbl\row \pard}}}{\shp {\*\shpinst \shplid1{\sp{\sn relLeft}{\sv 18025}}{\sp{\sn relTop}{\sv 2846}}{\sp{\sn relRight}{\sv 20152}}{\sp{\sn relBottom}{\sv 3117}}{\sp{\sn fRelFlipH}{\sv 0}}{\sp{\sn fRelFlipV}{\sv 0}}{\sp{\sn shapeType}{\sv 202}}{\sp{\sn fLine}{\sv 0}}{\sp{\sn fLine}{\sv 1}}{\sp{\sn lineWidth}{\sv 0}}{\sp{\sn lineColor}{\sv 16777215}}{\sp{\sn fPrint}vs\n\{s\{p{ }1s} fFilled}{\sv 0}}{\sp{\sn fEditedWrap}{\sv 0}}{\sp{\sn txflTextFlow}{\sv 0}}{\sp{\sn dxTextLeft}{\sv 0}}{\sp{\sn dyTextTop}{\sv 0}}{\sp{\sn dxTextRight}{\sv 0}}{\sp{\sn dyTextBottom}{\sv 0}}{\sp{\sn fFitTextToShape}{\sv 0}}{\shptxt \pard \plain \s11\ql\li0\fi0\ri0\sb0\sa0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 4.2.3 lentel\f0\cchs186 \'eb\f0\cchs186 s t\f0\cchs186 \'e6\f0\cchs186 sinys\par }}}{\shp {\*\shpinst \shplid1{\sp{\sn relLeft}{\sv 12629}}{\sp{\sn relTop}{\sv 4074}}{\sp{\sn relRight}{\sv 20100}}{\sp{\sn relBottom}{\sv 4549}}{\sp{\sn fRelFlipH}{\sv 0}}{\sp{\sn fRelFlipV}{\sv 0}}{\sp{\sn shapeType}{\sv 202}}{\sp{\sn fLine}{\sv 0}}{\sp{\sn fLine}{\sv 1}}{\sp{\sn lineWidth}{\sv 0}}{\sp{\sn lineColor}{\sv 16777215}}{\sp{\sn fPrint}{\sv 1}}{\sp{\sn fFilled}{\sv 0}}{\sp{\sn fEditedWrap}{\sv 0}}{\sp{\sn txflTextFlow}{\sv 0}}{\sp{\sn dxTextLeft}{\sv 0}}{\sp{\sn dyTextTop}{\sv 0}}{\sp{\sn dxTextRight}{\sv 0}}{\sp{\sn dyTextBottom}{\sv 0}}{\sp{\sn fFitTextToShape}{\sv 0}}{\shptxt \pard \plain \s22\ql\li0\fi2821\ri0\sb0\sa0\sl-161\slmult0\tlhyph\tx3456\tldot\tx3812 \cs62\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs18 _ ,{\charscalex100\expndtw0\tab }{\charscalex100\expndtw0\tab }.\f0\cchs186 \'84\'ab\f0\cchs186 , , icigu \expndtw20 trukumoexpndtw0 \cs55fs22 visi\f0\cchs186 \'f0\f0\cchs186 kai nejau\f0\cchs186 -\line \'e8\f0\cchs186 iama \f0\cchs186 \'97\f0\cchs186 vertinimas \cs49\f0\cchs186 \'84\f0\cchs186 -2".\par }}}}}{\shpgrp{\*\shpinst \shpleft-373\shptop6683\shpright8098\shpbottom12511\shpfhdr0\shpfblwtxt0\shpbxmargin\shpbypara\shpwr2\shpwrk3\shpz8\shplid8{\sp{\sn fFlipH}{\sv 0}}{\sp{\sn fFlipV}{\sv 0}}{\sp{\sn fLayoutInCell}{\sv 1}}{\sp{\sn fAllowOverlap}{\sv 1}}{\sp{\sn dxWrapDistLeft}{\sv 21590}}{\sp{\sn dxWrapDistRight}{\sv 21590}}{\sp{\sn dyWrapDistTop}{\sv 0}}{\sp{\sn dyWrapDistBottom}{\sv 0}}{\sp{\sn groupLeft}{\sv 1050}}{\sp{\sn groupTop}{\sv 9267}}{\sp{\sn groupRight}{\sv 9521}}{\sp{\sn groupBottom}{\sv 15095}}{\shp {\*\shpinst \shplid1{\sp{\sn relLeft}{\sv 1050}}{\sp{\sn relTop}{\sv 10275}}{\sp{\sn relRight}{\sv 9521}}{\sp{\sn relBottom}{\sv 15095}}{\sp{\sn fRelFlipH}{\sv 0}}{\sp{\sn fRelFlipV}{\sv 0}}{\sp{\sn shapeType}{\sv 202}}{\sp{\sn fLine}{\sv 0}}{\sp{\sn fLine}{\sv 1}}{\sp{\sn lineWidth}{\sv 0}}{\sp{\sn lineColor}{\sv 16777215}}{\sp{\sn fPrint}{\sv 1}}{\sp{\sn fFilled}{\sv 0}}{\sp{\sn fEditedWrap}{\sv 0}}{\sp{\sn txflTextFlow}{\sv 0}}{\sp{\sn dxTextLeft}{\sv 0}}{\sp{\sn dyTextTop}{\sv 0}}{\sp{\sn dxTextRight}{\sv 0}}{\sp{\sn dyTextBottom}{\sv 0}}{\sp{\sn fFitTextToShape}{\sv 0}}{\shptxt \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx661 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7014 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7751 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdr÷15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8471 \pard \plain \s30\ql\li246\fi0\ri0\sb0\sa0 \intbl \cs44\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Nr\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\strike0\nosupersub\scaps0\chrlex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Teiginys\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Vid.*\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 St. n.\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx661 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7014 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7751 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8471 \pard \plain \s30\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs44\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 1 \super 1\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Tr\f0\cchs186 \'fb\f0\cchs186 ksta u\f0\cchs186 \'fe\f0\cchs186 mokes\f0\cchs186 \'e8\f0\cchs186 io u\f0\cchs186 \'fe\f0\cchs186 \cs56\fs18 papildom\f0\cchs186 \'e0\f0\cchs186 \cs55\fs22 darb\f0\cchs186 \'e0\f0\cchs186 ,.\cell \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 1.43\cell \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 0.84\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx661 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7014 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7751 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8471 \pard \plain \s30\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs44\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 12.\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Tr\f0\cchs186 \'fb\f0\cchs186 ksta materialinio paskatinimo itin gabiems mokiniams.\cell \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 1.35\cell \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 0.87\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx661 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7014 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7751 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8471 \pard \plain \s30\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs44\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 13.\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0\sl-288\slmult0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Tr\f0\cchs186 \'fb\f0\cchs186 ksta bendros itin gabi\f0\cchs186 \'f8\f0\cchs186 vaik\f0\cchs186 \'f8\f0\cchs186 ugdymo politikos visos i\f0\cchs186 \'f0\f0\cchs186 liet \cs52\expndtw-20\fs30 j \cs44\expndtw0\fs22 mastu.\cell \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 1.27\cell \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 0.85\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx661 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7014 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7751 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8471 \pard \plain \s30\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs44\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 14.\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 [Tr\f0\cchs186 \'fb\f0\cchs186 ksta apr\f0\cchs186 \'fb\f0\cchs186 pinimo reikalinga mokymo metodine med\f0\cchs186 \'fe\f0\cchs186 iaga\cell \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 1.22\cell \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 0.96\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx661 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7014 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7751 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8471 \pard \plain \s30\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs44\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 15.\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0\sl-296\slmult0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Tr\f0\cchs186 \'fb\f0\cchs186 ksta kvalifikacijos k\f0\cchs186 \'eb\f0\cchs186 limo kurs\f0\cchs186 \'f8\f0\cchs186 itin gabi\f0\cchs186 \'f8\f0\cchs186 vaik\f0\cchs186 \'f8\f0\cchs186 ugdymo \cs44 tems.\cell \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 1\cs49 .20\cell \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 0.91\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx661 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7014 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrsbrdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7751 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8471 \pard \plain \s30\ql\li0\fi0\ri0\sb0\sa0\sl-407\slmult0 \intbl \cs52\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw-20\up2\f0\cchs186\lang1063\langfe1063\fs30 j \cs44\expndtw0\fs22 6. \cs55 j\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0\sl-313\slmult0 \intbl \cs44\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Tr\f0\cchs186 \'fb\f0\cchs186 ksta \cs55 bendradarbiavimo tu auk\f0\cchs186 \'f0\f0\cchs186 t\f0\cchs186 \'f8\f0\cchs186 j\f0\cchs186 \'f8\f0\cchs186 mokykl\f0\cchs186 \'f8\f0\cchs186 d\f0\cchs186 \'eb\f0\cchs186 stytojais, \cs44 mokalininkaif.\cell \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 1\cs49 .04\cell \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 0.99\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7014 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7751 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8471 \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0\sl-313\slmult0 \intbl \cs44\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 7. \cs55 1 \cs44 Tr\f0\cchs186 \'fb\f0\cchs186 ksta literatu \expndtw-20 rot\expndtw0 \cs54\i\expndtw-20 apie itin \cs55\i0\expndtw0 gabi\f0\cchs186 \'f8\f0\cchs186 vaik\f0\cchs186 \'f8\f0\cchs186 pa\f0\cchs186 \'fe\f0\cchs186 inim\f0\cchs186 \'e0\f0\cchs186 , ugdym\f0\cchs186 \'e0\f0\cchs186 , \cs52\expndtw-20\fs30 j \cs55\expndtw0\fs22 psichologij\f0\cchs186 \'e0\f0\cchs186 , \cs44 kir\f0\cchs186 \'f8\f0\cchs186 \cs55\f0\cchs186 \'f0\f0\cchs186 ali\f0\cchs186 \'f8\f0\cchs186 patirt j.\cell \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 1\cs49 .00\cell \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 0.97\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7014 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7751 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8471 \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 8. \cs55 j Tr\f0\cchs186 \'fb\f0\cchs186 ksta \cs44 bendradarbiavimo \cs55 su patyrusiais \f0\cchs186 \'f0\f0\cchs186 ioje srityje kolegomis,\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 0.88\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 1.02\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx661 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7014 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7751 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8471 \pard \plain \s32\ql\li0\fi0\ri0\sb0\sa0\sl-356\slmult0 \intbl \cs52\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw-20\dn4\f0\cchs186\lang1063\langfe1063\fs30 1 \cs49\expndtw0\fs22 1\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtu0\dn0\f0\cchs186\lang1063\langfe1063\fs22 'r\f0\cchs186 \'fb\f0\cchs186 ksta \cs44 tinkamo \cs55 pasirengimo auk\f0\cchs186 \'f0\f0\cchs186 tojoje mokykloje.\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 0.56\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 1.17\cell \pard\intbl\row \trowd\trgaph40 \trrh0 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx661 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7014 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx7751 \clvertalt\clbrdrt\brdrs\brdrw15\clbrdrl\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\cltxlrtb\cellx8471 \pard \plain \s5\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs49\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 10. 7\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 r\f0\cchs186 \'fb\f0\cchs186 ksta galimybi\f0\cchs186 \'f8\f0\cchs186 \cs44 naudotis \cs55 kompiuteriu ir internetu.\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 -0.19\cell \pard \plain \s3\ql\li0\fi0\ri0\sb0\sa0 \intbl \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 1.33\cell \pard\intbl\row \pard}}}{\shp {\*\shpinst \shplid1{\sp{\sn relLeft}{\sv 2151}}{\sp{\sn relTop}{\sv 9267}}{\sp{\sn relRight}{\sv 8724}}{\sp{\sn relBottom}{\sv 10300}}{\sp{\sn fRelFlipH}{\sv 0}}{\sp{\sn fRelFlipV}{\sv 0}}{\sp{\sn shapeType}{\sv 202}}{\sp{\sn fLine}{\sv 0}}{\sp{\sn fLine}{\sv 1}}{\sp{\sn lineWidth}{\sv 0}}{\sp{\sn lineColor}{\sv 16777215}}{\sp{\sn fPrint}{\sv 1}}{\sp{\sn fFilled}{\sv 0}}{\sp{\sn fEditedWrap}{\sv 0}}{\sp{\sn txflTextFlow}{\sv 0}}{\sp{\sn dxTextLeft}{\sv 0}}{\sp{\sn dyTextTop}{\sv 0}}{\sp{\sn dxTextRight}{\sv 0}}{\sp{\sn dyTextBottom}{\sv 0}}{\sp{\sn fFitTextToShape}{\sv 0}}{\shptxt \pard \plain \s2\qj\li0\fi0\ri0\sb0\sa0\sl-330\slmult0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 Duomenys apie tai, ko mokytojams tr\f0\cchs186 \'fb\f0\cchs186 ksta darbui su itin\par \pard \plain \s20\ql\li0\fi2406\ri0\sb0\sa0\sl-330\slmult0\tlul\tx805 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 gabiais vaikais\line \lang1033 {\charscalex100\expndtw0\tab }\ul\lang1063 (Narkevi\f0\cchs186 \'e8\f0\cchs186 ien\f0\cchs186 \'eb\f0\cchs186 , Janilionis,\ul0 Almonaitien\f0\cchs186 \'eb\f0\cchs186 , \cs49 2002)\par }}}}}\pard \plain \s11\qj\li0\fi0\ri0\sb0\sa0\sl-322\slmult0 \cs55\b0\i0\strike0\nosupersub\scaps0\charscalex100\expndtw0\dn0\f0\cchs186\lang1063\langfe1063\fs22 niams, galima manyti, kad jaunesni\f0\cchs186 \'f8\f0\cchs186 j\f0\cchs186 \'f8\f0\cchs186 klasi\f0\cchs186 \'f8\f0\cchs186 gabi\f0\cchs186 \'f8\f0\cchs186 mokini\fcchs186 \'f8\f0\cchs186 ugdymo rezulta\-tai \f0\cchs186\'fe\f0\cchs186 inomi ir fiksuojami tik klases ir mokyklos (atskirais atvejais savivaldyb\f0\cchs186 \'eb\f0\cchs186 s) lygmeniu. be to, olimpiad\f0\cchs186 \'f8\f0\cchs186 rezultatai - tai tik pasiekimai, \cs49 o \cs55 kas daroma klas\f0\cchs186 \'eb\-\f0\cchs186 je, \cs53\i\expndtw-10 mokykloje, \cs55\i0\expndtw0 kad tie pasiekimai b\f0\cchs186 \'fb\f0\cchs186 t\f0\cchs186 \'f8\f0\cchs186 tokie, ir \cs49 ar \cs55 tie pasiekimai \f0\cchs186 \'97\f0\cchs186 tai mokytojo ir mokinio darbo rezultatas ar tik mokinio potencialo \{vertinimas, informaci\-jos taip pat neturime arba turime labai nedaug \cs49 (\f0\cchs186 \'fe\f0\cchs186 r. \cs55 Narkevi\f0\cchs186 \'e8\f0\cchs186 ien\f0\cchs186 \'eb\f0\cchs186 , \cs49 1997,1998, \cs55 2003, Narkevi\f0\cchs186 \'e8\f0\cchs186 ien\f0\cchs186 \'eb\f0\cchs186 , Janilionis, Almonaitien\f0\cchs186 \'eb\f0\cchs186 , \cs49 2002).\par \pard \plain \s2\qj\li0\fi576\ri0\sb0\sa0\sl-322\slmult0 \cs63\b0\strike0\nosupersub\i\scaps0\charscalex100\expndtw-20\dn0\f0\cchs186\lang1063\langfe1063\fs22 Mokytoj\f0\cchs186 \'f8\f0\cchs186 rengimas. \cs55\i0\expndtw0 N\f0\cchs186 \'eb\f0\cchs186 vienas Lietuvos universitetas, rengiantis mokyto\-jus, nesi\f0\cchs186 \'fb\f0\cchs186 lo pirmos ar antros pakopos studij\f0\cchs186 \'f8\f0\cchs186 programos, kurios paskirtis b\f0\cchs186 \'fb\f0\cchs186 t\f0\cchs186 \'f8 \f0\cchs186 ai\f0\cchs186 \'f0\f0\cchs186 kiai orientuota \f0
diff --git a/vcl/qa/cppunit/pdfexport/data/form-font-name.odt b/vcl/qa/cppunit/pdfexport/data/form-font-name.odt
new file mode 100644
index 0000000000..a7430c9a8a
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/form-font-name.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/formcontrol.fodt b/vcl/qa/cppunit/pdfexport/data/formcontrol.fodt
new file mode 100644
index 0000000000..f6ec845852
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/formcontrol.fodt
@@ -0,0 +1,192 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:creation-date>2023-04-03T17:28:30.031698782</meta:creation-date><dc:date>2023-04-03T17:48:01.654889994</dc:date><meta:editing-duration>PT19M34S</meta:editing-duration><meta:editing-cycles>3</meta:editing-cycles><meta:generator>LibreOfficeDev/7.6.0.0.alpha0$Linux_X86_64 LibreOffice_project/581bc338cb60e9511c2f870acfbb7ec3593a582d</meta:generator><dc:title>dummy</dc:title><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="0" meta:word-count="0" meta:character-count="0" meta:non-whitespace-character-count="0"/></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Lohit Devanagari1" svg:font-family="'Lohit Devanagari'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Source Han Serif CN" svg:font-family="'Source Han Serif CN'" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Source Han Serif CN" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lohit Devanagari1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="page"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Source Han Serif CN" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lohit Devanagari1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
+ <loext:theme loext:name="Office">
+ <loext:color-table loext:name="LibreOffice">
+ <loext:color loext:name="dk1" loext:color="#000000"/>
+ <loext:color loext:name="lt1" loext:color="#ffffff"/>
+ <loext:color loext:name="dk2" loext:color="#000000"/>
+ <loext:color loext:name="lt2" loext:color="#ffffff"/>
+ <loext:color loext:name="accent1" loext:color="#18a303"/>
+ <loext:color loext:name="accent2" loext:color="#0369a3"/>
+ <loext:color loext:name="accent3" loext:color="#a33e03"/>
+ <loext:color loext:name="accent4" loext:color="#8e03a3"/>
+ <loext:color loext:name="accent5" loext:color="#c99c00"/>
+ <loext:color loext:name="accent6" loext:color="#c9211e"/>
+ <loext:color loext:name="hlink" loext:color="#0000ee"/>
+ <loext:color loext:name="folHlink" loext:color="#551a8b"/>
+ </loext:color-table>
+ </loext:theme>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="P1" style:family="paragraph">
+ <style:paragraph-properties fo:text-align="start"/>
+ </style:style>
+ <style:style style:name="gr1" style:family="graphic">
+ <style:graphic-properties draw:textarea-vertical-align="middle" style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph"/>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm2" style:page-usage="left">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm3" style:page-usage="right">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm4">
+ <style:page-layout-properties fo:page-width="22.901cm" fo:page-height="11.4cm" style:num-format="1" style:print-orientation="landscape" fo:margin-top="0cm" fo:margin-bottom="0cm" fo:margin-left="0cm" fo:margin-right="0cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm5">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="1cm" fo:margin-bottom="1cm" fo:margin-left="2cm" fo:margin-right="1cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm6">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm7">
+ <style:page-layout-properties fo:page-width="29.7cm" fo:page-height="21.001cm" style:num-format="1" style:print-orientation="landscape" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text>
+ <office:forms form:automatic-focus="false" form:apply-design-mode="false">
+ <form:form form:name="Form" form:apply-filter="true" form:command-type="table" form:control-implementation="ooo:com.sun.star.form.component.Form" office:target-frame="">
+ <form:properties>
+ <form:property form:property-name="PropertyChangeNotificationEnabled" office:value-type="boolean" office:boolean-value="true"/>
+ <form:property form:property-name="TargetURL" office:value-type="string" office:string-value=""/>
+ </form:properties>
+ <form:checkbox form:name="Check Box 1" form:control-implementation="ooo:com.sun.star.form.component.CheckBox" xml:id="control1" form:id="control1" form:label="Check Box" form:input-required="false" form:image-position="center">
+ <form:properties>
+ <form:property form:property-name="ControlTypeinMSO" office:value-type="float" office:value="0"/>
+ <form:property form:property-name="DefaultControl" office:value-type="string" office:string-value="com.sun.star.form.control.CheckBox"/>
+ <form:property form:property-name="HelpText" office:value-type="string" office:string-value="helpful text"/>
+ <form:property form:property-name="ObjIDinMSO" office:value-type="float" office:value="65535"/>
+ <form:property form:property-name="SecondaryRefValue" office:value-type="string" office:string-value=""/>
+ </form:properties>
+ </form:checkbox>
+ </form:form>
+ </office:forms>
+ <text:sequence-decls>
+ <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+ </text:sequence-decls>
+ <text:p text:style-name="Standard"><draw:control text:anchor-type="paragraph" draw:z-index="0" draw:name="Control 1" draw:style-name="gr1" draw:text-style-name="P1" svg:width="3.003cm" svg:height="1.04cm" svg:x="1.381cm" svg:y="0.651cm" draw:control="control1">
+ <svg:title>textuelle alternative</svg:title>
+ <svg:desc>a box to check</svg:desc>
+ </draw:control></text:p>
+ </office:text>
+ </office:body>
+</office:document> \ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/data/grouped-shape.fodt b/vcl/qa/cppunit/pdfexport/data/grouped-shape.fodt
new file mode 100644
index 0000000000..af506d9688
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/grouped-shape.fodt
@@ -0,0 +1,213 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:initial-creator>Gabor Kelemen LO</meta:initial-creator><meta:creation-date>2023-04-19T23:36:32.150000000</meta:creation-date><dc:date>2023-07-20T19:34:33.405705509</dc:date><meta:editing-duration>PT7M23S</meta:editing-duration><meta:editing-cycles>4</meta:editing-cycles><meta:generator>LibreOfficeDev/24.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/9c21d86f20871f2840fe390c5f365de48d48258a</meta:generator><dc:title>Grouped shapes a11y example</dc:title><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="0" meta:word-count="0" meta:character-count="0" meta:non-whitespace-character-count="0"/></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Lucida Sans1" svg:font-family="'Lucida Sans'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="NSimSun" svg:font-family="NSimSun" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="NSimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lucida Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="page"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="NSimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lucida Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
+ <loext:theme loext:name="Office Theme">
+ <loext:theme-colors loext:name="LibreOffice">
+ <loext:color loext:name="dark1" loext:color="#000000"/>
+ <loext:color loext:name="light1" loext:color="#ffffff"/>
+ <loext:color loext:name="dark2" loext:color="#000000"/>
+ <loext:color loext:name="light2" loext:color="#ffffff"/>
+ <loext:color loext:name="accent1" loext:color="#18a303"/>
+ <loext:color loext:name="accent2" loext:color="#0369a3"/>
+ <loext:color loext:name="accent3" loext:color="#a33e03"/>
+ <loext:color loext:name="accent4" loext:color="#8e03a3"/>
+ <loext:color loext:name="accent5" loext:color="#c99c00"/>
+ <loext:color loext:name="accent6" loext:color="#c9211e"/>
+ <loext:color loext:name="hyperlink" loext:color="#0000ee"/>
+ <loext:color loext:name="followed-hyperlink" loext:color="#551a8b"/>
+ </loext:theme-colors>
+ </loext:theme>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="P1" style:family="paragraph">
+ <loext:graphic-properties draw:fill="solid" draw:fill-color="#00a933"/>
+ </style:style>
+ <style:style style:name="P2" style:family="paragraph">
+ <loext:graphic-properties draw:fill="solid" draw:fill-color="#00a933"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="P3" style:family="paragraph">
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="gr1" style:family="graphic">
+ <style:graphic-properties style:writing-mode="lr-tb" loext:decorative="false" style:run-through="foreground" style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" draw:wrap-influence-on-position="once-concurrent" loext:allow-overlap="true" style:flow-with-text="false"/>
+ </style:style>
+ <style:style style:name="gr2" style:family="graphic">
+ <style:graphic-properties draw:fill="solid" draw:fill-color="#00a933" draw:textarea-horizontal-align="justify" draw:textarea-vertical-align="middle" draw:auto-grow-height="false" fo:min-height="3.247cm" fo:min-width="4.089cm" loext:decorative="false" style:run-through="foreground"/>
+ </style:style>
+ <style:style style:name="gr3" style:family="graphic">
+ <style:graphic-properties draw:fill="solid" draw:fill-color="#00a933" draw:textarea-horizontal-align="justify" draw:textarea-vertical-align="middle" draw:auto-grow-height="false" fo:min-height="1.762cm" fo:min-width="1.762cm" style:writing-mode="lr-tb" loext:decorative="false" style:run-through="foreground"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="gr4" style:family="graphic">
+ <style:graphic-properties draw:textarea-horizontal-align="justify" draw:textarea-vertical-align="middle" draw:auto-grow-height="false" fo:min-height="3.247cm" fo:min-width="4.089cm" loext:decorative="false" style:run-through="foreground"/>
+ </style:style>
+ <style:style style:name="gr5" style:family="graphic">
+ <style:graphic-properties draw:textarea-horizontal-align="justify" draw:textarea-vertical-align="middle" draw:auto-grow-height="false" fo:min-height="1.762cm" fo:min-width="1.762cm" style:writing-mode="lr-tb" loext:decorative="false" style:run-through="foreground"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="21.59cm" fo:page-height="27.94cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm2" style:page-usage="left">
+ <style:page-layout-properties fo:page-width="21.59cm" fo:page-height="27.94cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm3" style:page-usage="right">
+ <style:page-layout-properties fo:page-width="21.59cm" fo:page-height="27.94cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm4">
+ <style:page-layout-properties fo:page-width="22.901cm" fo:page-height="11.4cm" style:num-format="1" style:print-orientation="landscape" fo:margin-top="0cm" fo:margin-bottom="0cm" fo:margin-left="0cm" fo:margin-right="0cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm5">
+ <style:page-layout-properties fo:page-width="21.59cm" fo:page-height="27.94cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="1cm" fo:margin-bottom="1cm" fo:margin-left="2cm" fo:margin-right="1cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm6">
+ <style:page-layout-properties fo:page-width="21.59cm" fo:page-height="27.94cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm7">
+ <style:page-layout-properties fo:page-width="27.94cm" fo:page-height="21.59cm" style:num-format="1" style:print-orientation="landscape" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text>
+ <text:sequence-decls>
+ <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+ </text:sequence-decls>
+ <text:p text:style-name="Standard"><draw:g text:anchor-type="paragraph" draw:z-index="0" draw:name="DrawObject1" draw:style-name="gr1">
+ <svg:title>Two rectangles</svg:title>
+ <svg:desc>Grouped</svg:desc><draw:custom-shape draw:name="Shape 1" draw:style-name="gr4" svg:width="4.089cm" svg:height="3.248cm" svg:x="0.4cm" svg:y="0.362cm">
+ <text:p/>
+ <draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:type="rectangle" draw:enhanced-path="M 0 0 L 21600 0 21600 21600 0 21600 0 0 Z N"/>
+ </draw:custom-shape><draw:custom-shape draw:name="Shape 2" draw:style-name="gr5" draw:text-style-name="P3" svg:width="1.763cm" svg:height="1.763cm" svg:x="6.565cm" svg:y="1.692cm">
+ <text:p/>
+ <draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:type="rectangle" draw:enhanced-path="M 0 0 L 21600 0 21600 21600 0 21600 0 0 Z N"/>
+ </draw:custom-shape>
+ </draw:g><draw:g text:anchor-type="paragraph" draw:z-index="1" draw:name="DrawObject 1" draw:style-name="gr1">
+ <svg:title>these ones are green</svg:title><draw:custom-shape draw:name="Shape 5" draw:style-name="gr2" draw:text-style-name="P1" svg:width="4.089cm" svg:height="3.248cm" svg:x="4.586cm" svg:y="7.41cm">
+ <text:p/>
+ <draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:type="rectangle" draw:enhanced-path="M 0 0 L 21600 0 21600 21600 0 21600 0 0 Z N"/>
+ </draw:custom-shape><draw:custom-shape draw:name="Shape 6" draw:style-name="gr3" draw:text-style-name="P2" svg:width="1.763cm" svg:height="1.763cm" svg:x="10.751cm" svg:y="8.74cm">
+ <text:p/>
+ <draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:type="rectangle" draw:enhanced-path="M 0 0 L 21600 0 21600 21600 0 21600 0 0 Z N"/>
+ </draw:custom-shape>
+ </draw:g></text:p>
+ </office:text>
+ </office:body>
+</office:document> \ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/data/image-hyperlink-alttext.fodt b/vcl/qa/cppunit/pdfexport/data/image-hyperlink-alttext.fodt
new file mode 100644
index 0000000000..211d3a7e5e
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/image-hyperlink-alttext.fodt
@@ -0,0 +1,195 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:initial-creator>Gabor Kelemen LO</meta:initial-creator><meta:creation-date>2023-04-19T18:19:46.288000000</meta:creation-date><dc:date>2023-04-20T15:37:33.898000000</dc:date><dc:creator>Gabor Kelemen LO</dc:creator><meta:editing-duration>PT4H18M23S</meta:editing-duration><meta:editing-cycles>8</meta:editing-cycles><meta:generator>LibreOfficeDev/24.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/255ea355f14bdb3efd78f5cfada88c8ac0accfb8</meta:generator><dc:title>Ship image with hyperlink</dc:title><meta:document-statistic meta:table-count="0" meta:image-count="1" meta:object-count="0" meta:page-count="2" meta:paragraph-count="1" meta:word-count="3" meta:character-count="33" meta:non-whitespace-character-count="31"/></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Lucida Sans1" svg:font-family="'Lucida Sans'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="NSimSun" svg:font-family="NSimSun" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="NSimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lucida Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="page"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="NSimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lucida Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <style:style style:name="Graphics" style:family="graphic">
+ <style:graphic-properties text:anchor-type="paragraph" svg:x="0cm" svg:y="0cm" style:wrap="dynamic" style:number-wrapped-paragraphs="no-limit" style:wrap-contour="false" style:vertical-pos="top" style:vertical-rel="paragraph" style:horizontal-pos="center" style:horizontal-rel="paragraph" draw:fill="none"/>
+ </style:style>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
+ <loext:theme loext:name="Office Theme">
+ <loext:theme-colors loext:name="LibreOffice">
+ <loext:color loext:name="dark1" loext:color="#000000"/>
+ <loext:color loext:name="light1" loext:color="#ffffff"/>
+ <loext:color loext:name="dark2" loext:color="#000000"/>
+ <loext:color loext:name="light2" loext:color="#ffffff"/>
+ <loext:color loext:name="accent1" loext:color="#18a303"/>
+ <loext:color loext:name="accent2" loext:color="#0369a3"/>
+ <loext:color loext:name="accent3" loext:color="#a33e03"/>
+ <loext:color loext:name="accent4" loext:color="#8e03a3"/>
+ <loext:color loext:name="accent5" loext:color="#c99c00"/>
+ <loext:color loext:name="accent6" loext:color="#c9211e"/>
+ <loext:color loext:name="hyperlink" loext:color="#0000ee"/>
+ <loext:color loext:name="followed-hyperlink" loext:color="#551a8b"/>
+ </loext:theme-colors>
+ </loext:theme>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="T1" style:family="text">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="fr1" style:family="graphic" style:parent-style-name="Graphics">
+ <style:graphic-properties style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" style:mirror="none" fo:clip="rect(0cm, 0cm, 0cm, 0cm)" draw:luminance="0%" draw:contrast="0%" draw:red="0%" draw:green="0%" draw:blue="0%" draw:gamma="100%" draw:color-inversion="false" draw:image-opacity="100%" draw:color-mode="standard" draw:wrap-influence-on-position="once-concurrent" loext:allow-overlap="true"/>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="21.59cm" fo:page-height="27.94cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm2" style:page-usage="left">
+ <style:page-layout-properties fo:page-width="21.59cm" fo:page-height="27.94cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm3" style:page-usage="right">
+ <style:page-layout-properties fo:page-width="21.59cm" fo:page-height="27.94cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm4">
+ <style:page-layout-properties fo:page-width="22.901cm" fo:page-height="11.4cm" style:num-format="1" style:print-orientation="landscape" fo:margin-top="0cm" fo:margin-bottom="0cm" fo:margin-left="0cm" fo:margin-right="0cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm5">
+ <style:page-layout-properties fo:page-width="21.59cm" fo:page-height="27.94cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="1cm" fo:margin-bottom="1cm" fo:margin-left="2cm" fo:margin-right="1cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm6">
+ <style:page-layout-properties fo:page-width="21.59cm" fo:page-height="27.94cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm7">
+ <style:page-layout-properties fo:page-width="27.94cm" fo:page-height="21.59cm" style:num-format="1" style:print-orientation="landscape" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text>
+ <text:sequence-decls>
+ <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+ </text:sequence-decls>
+ <text:p text:style-name="P1">Image with <text:span text:style-name="T1">hyperlink!</text:span>description:<draw:a xlink:type="simple" xlink:href="https://bugs.documentfoundation.org/" office:name="Ship to Bugzilla"><draw:frame draw:style-name="fr1" draw:name="Image2" text:anchor-type="char" svg:x="2.769cm" svg:y="0.409cm" svg:width="10.804cm" svg:height="7.634cm" draw:z-index="0"><draw:image draw:mime-type="image/png">
+ <office:binary-data>iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAABGdBTUEAANbY1E9YMgAAABl0
+ RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAFpSURBVHjaYvz//z8DtQBAADER
+ o+jjZGuibAQIICZiDOK/cgzFwEnrV/4HYXS1AAHERIxBR58yMiAb2DtzM1b1AAHERIxBIIBu
+ IDYAEEBMxBjE0bgdxcBL3vcZLl16jaEPIICYiDFIU9MSw8BeoeUYhgEEEBMxBnFx8WE1EN3L
+ AAHERIxBIECMgQABxAhKtPgM+vbtE9xmGP/69eMMP+o9wWLW0kD9OlYM/LlHGQECiAndoKg/
+ USgGgTTmdS8C0yA+zIUgdeguBAggljtWdQwMVkDXACWMjd0ZXRun/Id5DWTA9C23GSaVxoEN
+ zISoARvoamnBYF2/hPHs2Z3/z0JdDhBADCBvIuPkhsn/QeDr14//QWwQjY0PVYeiFyCA8OaA
+ 3cdPoEQAiI8PAAQQEwMVAUAAsWATBAX0jx9fsWrAJQ4CAAGE1TBQwOMC9+9fwikHEEBYDQPF
+ IAzIe8TglEMHAAHESM2SFiDAADEwCe4BJwcYAAAAAElFTkSuQmCC
+ </office:binary-data>
+ </draw:image>
+ <svg:title>Ship drawing</svg:title>
+ <svg:desc>Very cute</svg:desc>
+ </draw:frame></draw:a></text:p>
+ </office:text>
+ </office:body>
+</office:document> \ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/data/image-shape.fodt b/vcl/qa/cppunit/pdfexport/data/image-shape.fodt
new file mode 100644
index 0000000000..a0bcfeb0eb
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/image-shape.fodt
@@ -0,0 +1,141 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:initial-creator>Gabor Kelemen LO</meta:initial-creator><meta:creation-date>2022-10-12T16:49:07.114000000</meta:creation-date><dc:date>2022-11-18T15:28:13.568307995</dc:date><meta:editing-duration>PT3M39S</meta:editing-duration><meta:editing-cycles>2</meta:editing-cycles><meta:generator>LibreOfficeDev/7.5.0.0.alpha0$Linux_X86_64 LibreOffice_project/867afb8f1cc153fba2c28c85deedda74bd077cf0</meta:generator><meta:document-statistic meta:table-count="0" meta:image-count="1" meta:object-count="0" meta:page-count="1" meta:paragraph-count="2" meta:word-count="291" meta:character-count="1541" meta:non-whitespace-character-count="1250"/></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Lucida Sans1" svg:font-family="'Lucida Sans'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="NSimSun" svg:font-family="NSimSun" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="NSimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lucida Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="page"/>
+ <style:text-properties style:use-window-font-color="true" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="NSimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lucida Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <style:style style:name="Graphics" style:family="graphic">
+ <style:graphic-properties text:anchor-type="paragraph" svg:x="0cm" svg:y="0cm" style:wrap="dynamic" style:number-wrapped-paragraphs="no-limit" style:wrap-contour="false" style:vertical-pos="top" style:vertical-rel="paragraph" style:horizontal-pos="center" style:horizontal-rel="paragraph"/>
+ </style:style>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="fr1" style:family="graphic" style:parent-style-name="Graphics">
+ <style:graphic-properties style:horizontal-pos="center" style:horizontal-rel="paragraph" style:mirror="none" fo:clip="rect(0cm, 0cm, 0cm, 0cm)" draw:luminance="0%" draw:contrast="0%" draw:red="0%" draw:green="0%" draw:blue="0%" draw:gamma="100%" draw:color-inversion="false" draw:image-opacity="100%" draw:color-mode="standard" draw:wrap-influence-on-position="once-concurrent"/>
+ </style:style>
+ <style:style style:name="gr1" style:family="graphic">
+ <style:graphic-properties draw:textarea-horizontal-align="justify" draw:textarea-vertical-align="middle" draw:auto-grow-height="false" fo:min-height="2.249cm" fo:min-width="5.212cm" style:run-through="foreground" style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" draw:wrap-influence-on-position="once-concurrent" style:flow-with-text="false"/>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="21.59cm" fo:page-height="27.94cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text>
+ <text:sequence-decls>
+ <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+ </text:sequence-decls>
+ <text:p text:style-name="Standard"><draw:custom-shape text:anchor-type="paragraph" draw:z-index="1" draw:name="Shape 1" draw:style-name="gr1" svg:width="5.213cm" svg:height="2.25cm" svg:x="4.299cm" svg:y="12.448cm">
+ <svg:title>This is a blue...</svg:title>
+ <svg:desc>...rectangle</svg:desc>
+ <text:p/>
+ <draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:type="rectangle" draw:enhanced-path="M 0 0 L 21600 0 21600 21600 0 21600 0 0 Z N"/>
+ </draw:custom-shape><draw:frame draw:style-name="fr1" draw:name="Image1" text:anchor-type="char" svg:width="11.748cm" svg:height="8.996cm" draw:z-index="0"><draw:image draw:mime-type="image/png">
+ <office:binary-data>iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAABGdBTUEAANbY1E9YMgAAABl0
+ RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAFpSURBVHjaYvz//z8DtQBAADER
+ o+jjZGuibAQIICZiDOK/cgzFwEnrV/4HYXS1AAHERIxBR58yMiAb2DtzM1b1AAHERIxBIIBu
+ IDYAEEBMxBjE0bgdxcBL3vcZLl16jaEPIICYiDFIU9MSw8BeoeUYhgEEEBMxBnFx8WE1EN3L
+ AAHERIxBIECMgQABxAhKtPgM+vbtE9xmGP/69eMMP+o9wWLW0kD9OlYM/LlHGQECiAndoKg/
+ USgGgTTmdS8C0yA+zIUgdeguBAggljtWdQwMVkDXACWMjd0ZXRun/Id5DWTA9C23GSaVxoEN
+ zISoARvoamnBYF2/hPHs2Z3/z0JdDhBADCBvIuPkhsn/QeDr14//QWwQjY0PVYeiFyCA8OaA
+ 3cdPoEQAiI8PAAQQEwMVAUAAsWATBAX0jx9fsWrAJQ4CAAGE1TBQwOMC9+9fwikHEEBYDQPF
+ IAzIe8TglEMHAAHESM2SFiDAADEwCe4BJwcYAAAAAElFTkSuQmCC
+ </office:binary-data>
+ </draw:image>
+ <svg:title>Image of a house</svg:title>
+ <svg:desc>nice drawing, isn't it?</svg:desc>
+ </draw:frame>He heard quiet steps behind him. That didn't bode well.</text:p>
+ <text:p text:style-name="Standard"><text:s/></text:p>
+ <text:p text:style-name="Standard"/>
+ </office:text>
+ </office:body>
+</office:document> \ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/data/justified-arabic-kashida.odt b/vcl/qa/cppunit/pdfexport/data/justified-arabic-kashida.odt
new file mode 100644
index 0000000000..6ca6ad1965
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/justified-arabic-kashida.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/link-wrong-page-partial.odg b/vcl/qa/cppunit/pdfexport/data/link-wrong-page-partial.odg
new file mode 100644
index 0000000000..1fad913e04
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/link-wrong-page-partial.odg
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/link-wrong-page.odp b/vcl/qa/cppunit/pdfexport/data/link-wrong-page.odp
new file mode 100644
index 0000000000..b6787aff66
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/link-wrong-page.odp
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/master.odm b/vcl/qa/cppunit/pdfexport/data/master.odm
new file mode 100644
index 0000000000..74016352b7
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/master.odm
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/nestedsection.fodt b/vcl/qa/cppunit/pdfexport/data/nestedsection.fodt
new file mode 100644
index 0000000000..ab743058f5
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/nestedsection.fodt
@@ -0,0 +1,132 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:creation-date>2023-08-31T13:12:47.478843606</meta:creation-date><dc:date>2023-08-31T19:59:09.088439334</dc:date><meta:editing-duration>PT5M46S</meta:editing-duration><meta:editing-cycles>6</meta:editing-cycles><meta:generator>LibreOfficeDev/24.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/f72e62cf27db17505de57abe93127f8b8d40eb29</meta:generator><meta:print-date>2023-08-31T19:31:58.495592060</meta:print-date><meta:printed-by>PDF files</meta:printed-by><dc:title>title</dc:title><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="3" meta:paragraph-count="6" meta:word-count="6" meta:character-count="6" meta:non-whitespace-character-count="6"/></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Lohit Devanagari1" svg:font-family="'Lohit Devanagari'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Noto Sans CJK SC" svg:font-family="'Noto Sans CJK SC'" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Noto Sans CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lohit Devanagari1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="page"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Noto Sans CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lohit Devanagari1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="P2" style:family="paragraph" style:parent-style-name="Standard">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="P3" style:family="paragraph" style:parent-style-name="Standard">
+ <style:paragraph-properties fo:break-before="page"/>
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="Sect1" style:family="section">
+ <style:section-properties style:editable="false">
+ <style:columns fo:column-count="1" fo:column-gap="0cm"/>
+ </style:section-properties>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text text:use-soft-page-breaks="true">
+ <text:sequence-decls>
+ <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+ </text:sequence-decls>
+ <text:section text:style-name="Sect1" text:name="Section1">
+ <text:p text:style-name="P2">1</text:p>
+ <text:p text:style-name="P3">2</text:p>
+ <text:section text:style-name="Sect1" text:name="Section2">
+ <text:p text:style-name="P2">3</text:p>
+ </text:section>
+ <text:p text:style-name="P2">4</text:p>
+ <text:p text:style-name="P3">5</text:p>
+ </text:section>
+ <text:p text:style-name="P2">6</text:p>
+ </office:text>
+ </office:body>
+</office:document>
diff --git a/vcl/qa/cppunit/pdfexport/data/pdf-image-annots.odg b/vcl/qa/cppunit/pdfexport/data/pdf-image-annots.odg
new file mode 100644
index 0000000000..6dee0145c5
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/pdf-image-annots.odg
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/pdf-image-hyperlink.odg b/vcl/qa/cppunit/pdfexport/data/pdf-image-hyperlink.odg
new file mode 100644
index 0000000000..aa0f89300b
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/pdf-image-hyperlink.odg
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/pdf-image-resource-inline-xobject-ref.pdf b/vcl/qa/cppunit/pdfexport/data/pdf-image-resource-inline-xobject-ref.pdf
new file mode 100644
index 0000000000..739a80c476
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/pdf-image-resource-inline-xobject-ref.pdf
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/pdf-image-rotate-180.pdf b/vcl/qa/cppunit/pdfexport/data/pdf-image-rotate-180.pdf
new file mode 100644
index 0000000000..981ca32061
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/pdf-image-rotate-180.pdf
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/rectangles.pdf b/vcl/qa/cppunit/pdfexport/data/rectangles.pdf
new file mode 100644
index 0000000000..6911d229aa
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/rectangles.pdf
@@ -0,0 +1,54 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+ /Type /Catalog
+ /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+ /Type /Pages
+ /MediaBox [0 0 200 300]
+ /Count 1
+ /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+ /Type /Page
+ /Parent 2 0 R
+ /Contents 4 0 R
+>>
+endobj
+4 0 obj <<
+ /Length 188
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f
+0000000015 00000 n
+0000000068 00000 n
+0000000157 00000 n
+0000000226 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 5
+>>
+startxref
+466
+%%EOF
diff --git a/vcl/qa/cppunit/pdfexport/data/reduce-image.fodt b/vcl/qa/cppunit/pdfexport/data/reduce-image.fodt
new file mode 100644
index 0000000000..b5737ae27e
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/reduce-image.fodt
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<office:document xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ooo="http://openoffice.org/2004/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:styles>
+ <style:default-style style:family="graphic">
+ </style:default-style>
+ <style:style style:name="Graphics" style:family="graphic">
+ </style:style>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="fr1" style:family="graphic" style:parent-style-name="Graphics">
+ <style:graphic-properties style:run-through="foreground" style:horizontal-pos="center" style:horizontal-rel="paragraph" style:mirror="none" fo:clip="rect(0cm, 0cm, 0cm, 0cm)" draw:luminance="0%" draw:contrast="0%" draw:red="0%" draw:green="0%" draw:blue="0%" draw:gamma="100%" draw:color-inversion="false" draw:image-opacity="100%" draw:color-mode="standard"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:body>
+ <office:text>
+ <text:p text:style-name="Standard"><draw:frame draw:style-name="fr1" draw:name="Image1" text:anchor-type="char" svg:width="0.041cm" svg:height="0.041cm" draw:z-index="0"><draw:image loext:mime-type="image/png"><office:binary-data>iVBORw0KGgoAAAANSUhEUgAAAKAAAACgCAYAAACLz2ctAAAABmJLR0QA/wD/AP+gvaeTAAAA
+ CXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5AEVDCUTfvEVdAAAAYhJREFUeNrt3bEJwzAQ
+ htFckjLreP8Bso5bc2lTpBDG4kfkvQUE5uNUGEnV3TdIufsECBABggARIAgQAYIAESAIEAGC
+ ABEgCBABggARIAgQAYIAESAIEAGCABEgCBABggARIHx7phZ+vF9D13Id217WXW9dExBbMAgQ
+ AYIAESAIEAGCABEgCBABIkAQIAIEASJAECAChKnq6hfTR88gsKarz46YgNiCESAIEAGCABEg
+ CBABwlSx27FStzHxW+oPlgmILRgBggARIAgQAYIAESAIEAGCABEgCBABggARIAgQAYIAESAI
+ EAGCABEgCBABggARIAgQAYIAESACBAEiQBAgAgQBIkAQIAIEASJAECACBAEiQBAgi4u9mJ56
+ oRsTEASIAEGACBABggARIAiQP1LdmR8So39Cjm0v6663rgmILRgEiABBgAgQBIgAQYAIEASI
+ ABEgCBABggARIAgQAcJUsTMhYAIiQAQIAkSAIEAECAJEgCBABAgCRIAgQAQIAkSAIEAECKd9
+ AEYENxB/sygQAAAAAElFTkSuQmCC
+ </office:binary-data></draw:image></draw:frame></text:p>
+ </office:text>
+ </office:body>
+</office:document>
diff --git a/vcl/qa/cppunit/pdfexport/data/reduce-small-image.fodt b/vcl/qa/cppunit/pdfexport/data/reduce-small-image.fodt
new file mode 100644
index 0000000000..99ff22746e
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/reduce-small-image.fodt
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<office:document xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ooo="http://openoffice.org/2004/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:styles>
+ <style:default-style style:family="graphic">
+ </style:default-style>
+ <style:style style:name="Graphics" style:family="graphic">
+ </style:style>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="fr1" style:family="graphic" style:parent-style-name="Graphics">
+ <style:graphic-properties style:run-through="foreground" style:horizontal-pos="center" style:horizontal-rel="paragraph" style:mirror="none" fo:clip="rect(0cm, 0cm, 0cm, 0cm)" draw:luminance="0%" draw:contrast="0%" draw:red="0%" draw:green="0%" draw:blue="0%" draw:gamma="100%" draw:color-inversion="false" draw:image-opacity="100%" draw:color-mode="standard"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:body>
+ <office:text>
+ <text:p text:style-name="Standard"><draw:frame draw:style-name="fr1" draw:name="Image1" text:anchor-type="char" svg:width="0.041cm" svg:height="0.041cm" draw:z-index="0"><draw:image loext:mime-type="image/png"><office:binary-data>iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMklEQVR42mP4//8/AyWYYXAZ
+ wHSK+z8pbOoaAJIgBWM1gFh/jxqAxwCKYmHgE9KAZSYAhK3Dgc2FxfUAAAAASUVORK5CYII=
+ </office:binary-data></draw:image></draw:frame></text:p>
+ </office:text>
+ </office:body>
+</office:document>
diff --git a/vcl/qa/cppunit/pdfexport/data/ref-to-kids.pdf b/vcl/qa/cppunit/pdfexport/data/ref-to-kids.pdf
new file mode 100644
index 0000000000..fdda33f782
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/ref-to-kids.pdf
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/softhyphen_pdf.odt b/vcl/qa/cppunit/pdfexport/data/softhyphen_pdf.odt
new file mode 100644
index 0000000000..ecd7795377
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/softhyphen_pdf.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/spanlist.fodt b/vcl/qa/cppunit/pdfexport/data/spanlist.fodt
new file mode 100644
index 0000000000..31096c6ccf
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/spanlist.fodt
@@ -0,0 +1,207 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:creation-date>2023-09-01T15:10:27.502479496</meta:creation-date><dc:date>2023-09-01T17:33:02.178300225</dc:date><meta:editing-duration>PT15M42S</meta:editing-duration><meta:editing-cycles>6</meta:editing-cycles><meta:generator>LibreOfficeDev/24.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/f72e62cf27db17505de57abe93127f8b8d40eb29</meta:generator><dc:title>spans</dc:title><meta:print-date>2023-09-01T17:30:16.229603024</meta:print-date><meta:printed-by>PDF files</meta:printed-by><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="2" meta:paragraph-count="6" meta:word-count="66" meta:character-count="326" meta:non-whitespace-character-count="252"/></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="DejaVu Sans1" svg:font-family="'DejaVu Sans'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Noto Sans CJK SC" svg:font-family="'Noto Sans CJK SC'" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Noto Sans CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="DejaVu Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="page"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Noto Sans CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="DejaVu Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <style:style style:name="Footnote" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+ <style:paragraph-properties fo:margin-left="0.6cm" fo:text-indent="-0.6cm" style:auto-text-indent="false" text:number-lines="false" text:line-number="0"/>
+ <style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
+ </style:style>
+ <style:style style:name="Numbering_20_Symbols" style:display-name="Numbering Symbols" style:family="text"/>
+ <style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text">
+ <style:text-properties fo:color="#000080" loext:opacity="100%" />
+ </style:style>
+ <style:style style:name="Footnote_20_Symbol" style:display-name="Footnote Symbol" style:family="text"/>
+ <style:style style:name="Footnote_20_anchor" style:display-name="Footnote anchor" style:family="text">
+ <style:text-properties style:text-position="super 58%"/>
+ </style:style>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" text:citation-style-name="Footnote_20_Symbol" text:citation-body-style-name="Footnote_20_anchor" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Footnote">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="P2" style:family="paragraph" style:parent-style-name="Standard" style:list-style-name="L1">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="P3" style:family="paragraph" style:parent-style-name="Standard" style:list-style-name="L1">
+ <style:text-properties fo:language="en" fo:country="GB"/>
+ </style:style>
+ <style:style style:name="P5" style:family="paragraph" style:parent-style-name="Standard" style:list-style-name="L1">
+ <style:text-properties style:text-line-through-style="solid" style:text-line-through-type="single" fo:language="en" fo:country="GB"/>
+ </style:style>
+ <style:style style:name="T2" style:family="text">
+ <style:text-properties fo:font-size="14pt" style:font-size-asian="14pt" style:font-size-complex="14pt"/>
+ </style:style>
+ <style:style style:name="T3" style:family="text">
+ <style:text-properties fo:font-style="italic" style:font-style-asian="italic" style:font-style-complex="italic"/>
+ </style:style>
+ <style:style style:name="T4" style:family="text">
+ <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
+ </style:style>
+ <text:list-style style:name="L1">
+ <text:list-level-style-number text:level="1" text:style-name="Numbering_20_Symbols" loext:num-list-format="%1%." style:num-suffix="." style:num-format="1">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.27cm" fo:text-indent="-0.635cm" fo:margin-left="1.27cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-number>
+ <text:list-level-style-number text:level="2" text:style-name="Numbering_20_Symbols" loext:num-list-format="%2%." style:num-suffix="." style:num-format="1">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.905cm" fo:text-indent="-0.635cm" fo:margin-left="1.905cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-number>
+ <text:list-level-style-number text:level="3" text:style-name="Numbering_20_Symbols" loext:num-list-format="%3%." style:num-suffix="." style:num-format="1">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="2.54cm" fo:text-indent="-0.635cm" fo:margin-left="2.54cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-number>
+ <text:list-level-style-number text:level="4" text:style-name="Numbering_20_Symbols" loext:num-list-format="%4%." style:num-suffix="." style:num-format="1">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="3.175cm" fo:text-indent="-0.635cm" fo:margin-left="3.175cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-number>
+ <text:list-level-style-number text:level="5" text:style-name="Numbering_20_Symbols" loext:num-list-format="%5%." style:num-suffix="." style:num-format="1">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="3.81cm" fo:text-indent="-0.635cm" fo:margin-left="3.81cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-number>
+ <text:list-level-style-number text:level="6" text:style-name="Numbering_20_Symbols" loext:num-list-format="%6%." style:num-suffix="." style:num-format="1">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="4.445cm" fo:text-indent="-0.635cm" fo:margin-left="4.445cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-number>
+ <text:list-level-style-number text:level="7" text:style-name="Numbering_20_Symbols" loext:num-list-format="%7%." style:num-suffix="." style:num-format="1">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="5.08cm" fo:text-indent="-0.635cm" fo:margin-left="5.08cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-number>
+ <text:list-level-style-number text:level="8" text:style-name="Numbering_20_Symbols" loext:num-list-format="%8%." style:num-suffix="." style:num-format="1">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="5.715cm" fo:text-indent="-0.635cm" fo:margin-left="5.715cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-number>
+ <text:list-level-style-number text:level="9" text:style-name="Numbering_20_Symbols" loext:num-list-format="%9%." style:num-suffix="." style:num-format="1">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="6.35cm" fo:text-indent="-0.635cm" fo:margin-left="6.35cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-number>
+ <text:list-level-style-number text:level="10" text:style-name="Numbering_20_Symbols" loext:num-list-format="%10%." style:num-suffix="." style:num-format="1">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="6.985cm" fo:text-indent="-0.635cm" fo:margin-left="6.985cm"/>
+ </style:list-level-properties>
+ </text:list-level-style-number>
+ </text:list-style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="10.5cm" fo:page-height="14.801cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text text:use-soft-page-breaks="true">
+ <text:sequence-decls>
+ <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+ </text:sequence-decls>
+ <text:list text:style-name="L1">
+ <text:list-item>
+ <text:p text:style-name="P3">The <text:span text:style-name="T4">first</text:span> item in the <text:span text:style-name="T2">list</text:span>, with about 2 <text:span text:style-name="T4">lines</text:span> of en-GB text</text:p>
+ </text:list-item>
+ <text:list-item>
+ <text:p text:style-name="P2">The <text:span text:style-name="T2">second</text:span> item <text:span text:style-name="T3">in</text:span> <text:span text:style-name="T4">the</text:span> list<text:line-break/><text:line-break/><text:line-break/><text:line-break/><text:line-break/><text:line-break/><text:line-break/><text:line-break/><text:line-break/><text:line-break/><text:line-break/><text:line-break/><text:line-break/><text:line-break/><text:line-break/><text:line-break/><text:line-break/><text:line-break/><text:line-break/><text:soft-page-break/><text:line-break/>has many linebreaks and ends on page 2</text:p>
+ </text:list-item>
+ <text:list-item>
+ <text:p text:style-name="P3">The third item in the list does have a <text:a xlink:type="simple" xlink:href="http://example.com/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link">hyperlink</text:a> and a footnote<text:note text:id="ftn0" text:note-class="footnote"><text:note-citation>1</text:note-citation><text:note-body>
+ <text:p text:style-name="P1">footnote</text:p></text:note-body></text:note> so it is 4 lines of en-GB text</text:p>
+ </text:list-item>
+ <text:list-item>
+ <text:p text:style-name="P5">item 4 has strikeout formatting on both lines</text:p>
+ </text:list-item>
+ </text:list>
+ </office:text>
+ </office:body>
+</office:document>
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf103492.odt b/vcl/qa/cppunit/pdfexport/data/tdf103492.odt
new file mode 100644
index 0000000000..88b8dc146f
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf103492.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf105093.odp b/vcl/qa/cppunit/pdfexport/data/tdf105093.odp
new file mode 100644
index 0000000000..82ce29cda0
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf105093.odp
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf105461.odp b/vcl/qa/cppunit/pdfexport/data/tdf105461.odp
new file mode 100644
index 0000000000..9c86a3bd79
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf105461.odp
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf105954.odt b/vcl/qa/cppunit/pdfexport/data/tdf105954.odt
new file mode 100644
index 0000000000..ba5c96de68
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf105954.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf105972.fodt b/vcl/qa/cppunit/pdfexport/data/tdf105972.fodt
new file mode 100644
index 0000000000..b9f1e29f55
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf105972.fodt
@@ -0,0 +1,175 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:creation-date>2022-05-17T10:53:27.044915889</meta:creation-date><dc:date>2022-05-18T18:02:56.717773462</dc:date><meta:editing-duration>PT3M</meta:editing-duration><meta:editing-cycles>6</meta:editing-cycles><meta:generator>LibreOfficeDev/7.4.0.0.alpha1$Linux_X86_64 LibreOffice_project/9e37c70c5c7413aee31f13fcd154cae30153e8ae</meta:generator><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="2" meta:word-count="0" meta:character-count="0" meta:non-whitespace-character-count="0"/></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Lohit Devanagari1" svg:font-family="'Lohit Devanagari'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Noto Serif CJK SC" svg:font-family="'Noto Serif CJK SC'" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" draw:start-line-spacing-horizontal="0.1114in" draw:start-line-spacing-vertical="0.1114in" draw:end-line-spacing-horizontal="0.1114in" draw:end-line-spacing-vertical="0.1114in" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" loext:color-lum-mod="100%" loext:color-lum-off="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lohit Devanagari1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="0.4925in" style:writing-mode="page"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lohit Devanagari1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:number-lines="false" text:offset="0.1965in" style:num-format="1" text:number-position="left" text:increment="5"/>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="gr1" style:family="graphic" style:data-style-name="C10048">
+ <style:graphic-properties style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" draw:wrap-influence-on-position="once-concurrent" loext:allow-overlap="true" style:flow-with-text="false"/>
+ </style:style>
+ <style:style style:name="gr2" style:family="graphic" style:data-style-name="C63">
+ <style:graphic-properties style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="middle" style:vertical-rel="line" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" draw:wrap-influence-on-position="once-concurrent" loext:allow-overlap="true" style:flow-with-text="false"/>
+ </style:style>
+ <style:style style:name="gr3" style:family="graphic">
+ <style:graphic-properties style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="middle" style:vertical-rel="line" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" draw:wrap-influence-on-position="once-concurrent" loext:allow-overlap="true" style:flow-with-text="false"/>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="8.5in" fo:page-height="11in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.278in" style:layout-grid-ruby-height="0.139in" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0in" loext:margin-gutter="0in">
+ <style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ <number:time-style style:name="C63" number:language="en" number:country="US">
+ <number:hours number:style="long"/>
+ <number:text>:</number:text>
+ <number:minutes number:style="long"/>
+ <number:text>:</number:text>
+ <number:seconds number:style="long"/>
+ <number:text> </number:text>
+ <number:am-pm/>
+ </number:time-style>
+ <number:date-style style:name="C10048" number:language="de" number:country="DE">
+ <number:year/>
+ <number:text>-</number:text>
+ <number:month number:style="long"/>
+ <number:text>-</number:text>
+ <number:day number:style="long"/>
+ </number:date-style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text>
+ <office:forms form:automatic-focus="false" form:apply-design-mode="false">
+ <form:form form:name="Form" form:apply-filter="true" form:command-type="table" form:control-implementation="ooo:com.sun.star.form.component.Form" office:target-frame="">
+ <form:properties>
+ <form:property form:property-name="PropertyChangeNotificationEnabled" office:value-type="boolean" office:boolean-value="true"/>
+ <form:property form:property-name="TargetURL" office:value-type="string" office:string-value=""/>
+ </form:properties>
+ <form:formatted-text form:name="CurrencyField" form:control-implementation="ooo:com.sun.star.form.component.CurrencyField" xml:id="control1" form:id="control1" form:current-value="1234" form:input-required="false" form:validation="true" form:min-value="-1000000" form:max-value="1000000">
+ <form:properties>
+ <form:property form:property-name="ControlTypeinMSO" office:value-type="float" office:value="0"/>
+ <form:property form:property-name="CurrencySymbol" office:value-type="string" office:string-value="€"/>
+ <form:property form:property-name="DecimalAccuracy" office:value-type="float" office:value="4"/>
+ <form:property form:property-name="DefaultControl" office:value-type="string" office:string-value="com.sun.star.form.control.CurrencyField"/>
+ <form:property form:property-name="MouseWheelBehavior" office:value-type="float" office:value="0"/>
+ <form:property form:property-name="ObjIDinMSO" office:value-type="float" office:value="65535"/>
+ <form:property form:property-name="PrependCurrencySymbol" office:value-type="boolean" office:boolean-value="true"/>
+ </form:properties>
+ </form:formatted-text>
+ <form:time form:name="TimeField" form:control-implementation="ooo:com.sun.star.form.component.TimeField" xml:id="control2" form:id="control2" form:value="P0D" form:current-value="PT11H" form:max-value="PT23H58M58S" form:input-required="false" form:validation="true">
+ <form:properties>
+ <form:property form:property-name="ControlTypeinMSO" office:value-type="float" office:value="0"/>
+ <form:property form:property-name="DefaultControl" office:value-type="string" office:string-value="com.sun.star.form.control.TimeField"/>
+ <form:property form:property-name="MouseWheelBehavior" office:value-type="float" office:value="0"/>
+ <form:property form:property-name="ObjIDinMSO" office:value-type="float" office:value="65535"/>
+ <form:property form:property-name="Text" office:value-type="string" office:string-value="11:00:00AM"/>
+ </form:properties>
+ </form:time>
+ <form:date form:name="DateField" form:control-implementation="ooo:com.sun.star.form.component.DateField" xml:id="control3" form:id="control3" form:current-value="2022-05-19" form:min-value="1800-01-01" form:input-required="false" form:validation="true">
+ <form:properties>
+ <form:property form:property-name="ControlTypeinMSO" office:value-type="float" office:value="0"/>
+ <form:property form:property-name="DefaultControl" office:value-type="string" office:string-value="com.sun.star.form.control.DateField"/>
+ <form:property form:property-name="MouseWheelBehavior" office:value-type="float" office:value="0"/>
+ <form:property form:property-name="ObjIDinMSO" office:value-type="float" office:value="65535"/>
+ <form:property form:property-name="Text" office:value-type="string" office:string-value="22-05-19"/>
+ </form:properties>
+ </form:date>
+ </form:form>
+ </office:forms>
+ <text:sequence-decls>
+ <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+ </text:sequence-decls>
+ <text:p text:style-name="Standard"><draw:control text:anchor-type="as-char" draw:z-index="0" draw:name="Control 1" draw:style-name="gr3" draw:text-style-name="P1" svg:width="2.563in" svg:height="0.5732in" draw:control="control1"/></text:p>
+ <text:p text:style-name="Standard"/>
+ <text:p text:style-name="Standard"><draw:control text:anchor-type="paragraph" draw:z-index="2" draw:name="Control 3" draw:style-name="gr1" draw:text-style-name="P1" svg:width="2.4543in" svg:height="0.5732in" svg:x="-0.0102in" svg:y="0.9374in" draw:control="control3"/><draw:control text:anchor-type="as-char" draw:z-index="1" draw:name="Control 2" draw:style-name="gr2" draw:text-style-name="P1" svg:width="2.4543in" svg:height="0.5732in" draw:control="control2"/></text:p>
+ </office:text>
+ </office:body>
+</office:document> \ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf106059.odt b/vcl/qa/cppunit/pdfexport/data/tdf106059.odt
new file mode 100644
index 0000000000..a2c1803783
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf106059.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf106206.odt b/vcl/qa/cppunit/pdfexport/data/tdf106206.odt
new file mode 100644
index 0000000000..3581157de4
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf106206.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf106693.odt b/vcl/qa/cppunit/pdfexport/data/tdf106693.odt
new file mode 100644
index 0000000000..a2c1803783
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf106693.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf106702.odt b/vcl/qa/cppunit/pdfexport/data/tdf106702.odt
new file mode 100644
index 0000000000..da3b7e8145
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf106702.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf106972-pdf17.odt b/vcl/qa/cppunit/pdfexport/data/tdf106972-pdf17.odt
new file mode 100644
index 0000000000..d46c93dffb
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf106972-pdf17.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf106972.odt b/vcl/qa/cppunit/pdfexport/data/tdf106972.odt
new file mode 100644
index 0000000000..3fa76c49fa
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf106972.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf107013.odt b/vcl/qa/cppunit/pdfexport/data/tdf107013.odt
new file mode 100644
index 0000000000..644e65c6de
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf107013.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf107018.odt b/vcl/qa/cppunit/pdfexport/data/tdf107018.odt
new file mode 100644
index 0000000000..3bfc7b2d73
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf107018.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf107089.odt b/vcl/qa/cppunit/pdfexport/data/tdf107089.odt
new file mode 100644
index 0000000000..5aaaab944a
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf107089.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf107868.odt b/vcl/qa/cppunit/pdfexport/data/tdf107868.odt
new file mode 100644
index 0000000000..8309f4b878
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf107868.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf108963.odp b/vcl/qa/cppunit/pdfexport/data/tdf108963.odp
new file mode 100644
index 0000000000..246c0c72ae
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf108963.odp
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf109143.odt b/vcl/qa/cppunit/pdfexport/data/tdf109143.odt
new file mode 100644
index 0000000000..7d9afa3789
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf109143.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf113143.odp b/vcl/qa/cppunit/pdfexport/data/tdf113143.odp
new file mode 100644
index 0000000000..5f8a1b10e2
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf113143.odp
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf114256.ods b/vcl/qa/cppunit/pdfexport/data/tdf114256.ods
new file mode 100644
index 0000000000..1e30a7e31f
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf114256.ods
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf115117-1.odt b/vcl/qa/cppunit/pdfexport/data/tdf115117-1.odt
new file mode 100644
index 0000000000..63fe82946e
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf115117-1.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf115117-2.odt b/vcl/qa/cppunit/pdfexport/data/tdf115117-2.odt
new file mode 100644
index 0000000000..c1e1f6d439
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf115117-2.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf115262.ods b/vcl/qa/cppunit/pdfexport/data/tdf115262.ods
new file mode 100644
index 0000000000..b401a74ce9
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf115262.ods
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf115967.odt b/vcl/qa/cppunit/pdfexport/data/tdf115967.odt
new file mode 100644
index 0000000000..ff538af5f5
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf115967.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf118244_radioButtonGroup.odt b/vcl/qa/cppunit/pdfexport/data/tdf118244_radioButtonGroup.odt
new file mode 100644
index 0000000000..caabc4987c
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf118244_radioButtonGroup.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf121615.odt b/vcl/qa/cppunit/pdfexport/data/tdf121615.odt
new file mode 100644
index 0000000000..7d2a87cf0e
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf121615.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf121962.odt b/vcl/qa/cppunit/pdfexport/data/tdf121962.odt
new file mode 100644
index 0000000000..a831b11361
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf121962.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf124272.odt b/vcl/qa/cppunit/pdfexport/data/tdf124272.odt
new file mode 100644
index 0000000000..54d4dcb2a1
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf124272.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf127217.odt b/vcl/qa/cppunit/pdfexport/data/tdf127217.odt
new file mode 100644
index 0000000000..470600a0aa
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf127217.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf128445.odp b/vcl/qa/cppunit/pdfexport/data/tdf128445.odp
new file mode 100644
index 0000000000..adabdb2235
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf128445.odp
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf128630.odp b/vcl/qa/cppunit/pdfexport/data/tdf128630.odp
new file mode 100644
index 0000000000..d216504b73
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf128630.odp
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf129085.docx b/vcl/qa/cppunit/pdfexport/data/tdf129085.docx
new file mode 100644
index 0000000000..6ac21d8f28
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf129085.docx
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf135192-1.fodp b/vcl/qa/cppunit/pdfexport/data/tdf135192-1.fodp
new file mode 100644
index 0000000000..6168dd2a28
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf135192-1.fodp
@@ -0,0 +1,239 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xmlns:smil="urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0" xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.presentation">
+ <office:meta><meta:creation-date>2020-07-27T11:44:30.444393550</meta:creation-date><meta:editing-duration>PT2M31S</meta:editing-duration><meta:editing-cycles>2</meta:editing-cycles><meta:generator>LibreOfficeDev/7.5.0.0.alpha1$Linux_X86_64 LibreOffice_project/0c0fef37f8ea5528584324f72328f23b7fcc92d7</meta:generator><dc:title>Impress</dc:title><dc:date>2020-07-27T11:47:01.552127285</dc:date><meta:document-statistic meta:object-count="29"/></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="Amiri" svg:font-family="Amiri" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="DejaVu Sans" svg:font-family="'DejaVu Sans'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <draw:gradient draw:name="Filled" draw:style="linear" draw:start-color="#ffffff" draw:end-color="#cccccc" draw:start-intensity="100%" draw:end-intensity="100%" draw:angle="30deg" draw:border="0%"/>
+ <draw:gradient draw:name="Filled_20_Blue" draw:display-name="Filled Blue" draw:style="linear" draw:start-color="#729fcf" draw:end-color="#355269" draw:start-intensity="100%" draw:end-intensity="100%" draw:angle="30deg" draw:border="0%"/>
+ <draw:gradient draw:name="Filled_20_Green" draw:display-name="Filled Green" draw:style="linear" draw:start-color="#77bc65" draw:end-color="#127622" draw:start-intensity="100%" draw:end-intensity="100%" draw:angle="30deg" draw:border="0%"/>
+ <draw:gradient draw:name="Filled_20_Red" draw:display-name="Filled Red" draw:style="linear" draw:start-color="#ff6d6d" draw:end-color="#c9211e" draw:start-intensity="100%" draw:end-intensity="100%" draw:angle="30deg" draw:border="0%"/>
+ <draw:gradient draw:name="Filled_20_Yellow" draw:display-name="Filled Yellow" draw:style="linear" draw:start-color="#ffde59" draw:end-color="#b47804" draw:start-intensity="100%" draw:end-intensity="100%" draw:angle="30deg" draw:border="0%"/>
+ <draw:gradient draw:name="Shapes" draw:style="rectangular" draw:cx="50%" draw:cy="50%" draw:start-color="#cccccc" draw:end-color="#ffffff" draw:start-intensity="100%" draw:end-intensity="100%" draw:angle="0deg" draw:border="0%"/>
+ <draw:marker draw:name="Arrow" svg:viewBox="0 0 20 30" svg:d="M10 0l-10 30h20z"/>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465af" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" style:writing-mode="lr-tb"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:punctuation-wrap="simple" style:line-break="strict" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" loext:color-lum-mod="100%" loext:color-lum-off="0%" style:font-name="Liberation Sans" fo:font-size="24pt" fo:language="en" fo:country="GB" style:font-name-asian="Amiri" style:font-size-asian="24pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Amiri" style:font-size-complex="24pt" style:language-complex="ar" style:country-complex="SA"/>
+ </style:default-style>
+ <style:style style:name="standard" style:family="graphic">
+ <style:graphic-properties draw:stroke="solid" svg:stroke-width="0cm" svg:stroke-color="#3465a4" draw:marker-start-width="0.2cm" draw:marker-start-center="false" draw:marker-end-width="0.2cm" draw:marker-end-center="false" draw:fill="solid" draw:fill-color="#729fcf" draw:textarea-horizontal-align="justify" fo:padding-top="0.125cm" fo:padding-bottom="0.125cm" fo:padding-left="0.25cm" fo:padding-right="0.25cm" draw:shadow="hidden" draw:shadow-offset-x="0.2cm" draw:shadow-offset-y="0.2cm" draw:shadow-color="#808080">
+ <text:list-style style:name="standard">
+ <text:list-level-style-bullet text:level="1" text:bullet-char="â—">
+ <style:list-level-properties text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="2" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="0.6cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="3" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="1.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="4" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="1.8cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="5" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="2.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="6" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="3cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="7" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="3.6cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="8" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="4.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="9" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="4.8cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="10" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="5.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ </text:list-style>
+ </style:graphic-properties>
+ <style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-top="0cm" fo:margin-bottom="0cm" fo:line-height="100%" fo:text-indent="0cm"/>
+ <style:text-properties fo:font-variant="normal" fo:text-transform="none" style:use-window-font-color="true" loext:opacity="0%" loext:color-lum-mod="100%" loext:color-lum-off="0%" style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="18pt" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:letter-kerning="true" style:text-emphasize="none" style:font-relief="none" style:text-overline-style="none" style:text-overline-color="font-color"/>
+ </style:style>
+ <style:style style:name="default" style:family="table-cell">
+ <loext:graphic-properties draw:fill="solid" draw:fill-color="#ccccff" draw:textarea-horizontal-align="left" draw:textarea-vertical-align="top" fo:padding-top="0.13cm" fo:padding-bottom="0.13cm" fo:padding-left="0.25cm" fo:padding-right="0.25cm"/>
+ <style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-top="0cm" fo:margin-bottom="0cm" fo:line-height="100%" fo:text-indent="0cm" fo:border="0.03pt solid #ffffff"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" loext:color-lum-mod="100%" loext:color-lum-off="0%" style:font-name="DejaVu Sans" fo:font-family="'DejaVu Sans'" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="18pt" style:letter-kerning="true" style:font-name-asian="Amiri" style:font-family-asian="Amiri" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="18pt" style:font-name-complex="Amiri" style:font-family-complex="Amiri" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="18pt"/>
+ </style:style>
+ <style:style style:name="gray1" style:family="table-cell" style:parent-style-name="default">
+ <loext:graphic-properties draw:fill="solid" draw:fill-color="#e6e6e6"/>
+ </style:style>
+ <style:style style:name="gray2" style:family="table-cell" style:parent-style-name="default">
+ <loext:graphic-properties draw:fill="solid" draw:fill-color="#cccccc"/>
+ </style:style>
+ <style:style style:name="gray3" style:family="table-cell" style:parent-style-name="default">
+ <loext:graphic-properties draw:fill="solid" draw:fill-color="#b3b3b3"/>
+ </style:style>
+ <table:table-template table:name="default">
+ <table:first-row table:style-name="gray3"/>
+ <table:last-row table:style-name="gray3"/>
+ <table:first-column table:style-name="gray3"/>
+ <table:last-column table:style-name="gray3"/>
+ <table:body table:style-name="gray1"/>
+ <table:odd-rows table:style-name="gray2"/>
+ <table:odd-columns table:style-name="gray2"/>
+ </table:table-template>
+ <style:style style:name="Impress1-notes" style:family="presentation">
+ <style:graphic-properties draw:stroke="none" draw:fill="none"/>
+ <style:paragraph-properties fo:margin-left="0.6cm" fo:margin-right="0cm" fo:text-indent="0cm"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" loext:color-lum-mod="100%" loext:color-lum-off="0%" style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="20pt" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:letter-kerning="true" style:text-emphasize="none" style:font-relief="none" style:text-overline-style="none" style:text-overline-color="font-color"/>
+ </style:style>
+ <style:presentation-page-layout style:name="AL0T26">
+ <presentation:placeholder presentation:object="handout" svg:x="2.058cm" svg:y="1.743cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ <presentation:placeholder presentation:object="handout" svg:x="15.414cm" svg:y="1.743cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ <presentation:placeholder presentation:object="handout" svg:x="2.058cm" svg:y="3.612cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ <presentation:placeholder presentation:object="handout" svg:x="15.414cm" svg:y="3.612cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ <presentation:placeholder presentation:object="handout" svg:x="2.058cm" svg:y="5.481cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ <presentation:placeholder presentation:object="handout" svg:x="15.414cm" svg:y="5.481cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ </style:presentation-page-layout>
+ <style:presentation-page-layout style:name="AL1T1">
+ <presentation:placeholder presentation:object="title" svg:x="2.058cm" svg:y="1.743cm" svg:width="23.912cm" svg:height="3.507cm"/>
+ <presentation:placeholder presentation:object="outline" svg:x="2.058cm" svg:y="5.838cm" svg:width="23.912cm" svg:height="13.23cm"/>
+ </style:presentation-page-layout>
+ </office:styles>
+ <office:automatic-styles>
+ <style:page-layout style:name="PM0">
+ <style:page-layout-properties fo:margin-top="0cm" fo:margin-bottom="0cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:page-width="21cm" fo:page-height="29.7cm" style:print-orientation="portrait"/>
+ </style:page-layout>
+ <style:page-layout style:name="PM1">
+ <style:page-layout-properties fo:margin-top="0cm" fo:margin-bottom="0cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:page-width="28cm" fo:page-height="21cm" style:print-orientation="landscape"/>
+ </style:page-layout>
+ <style:style style:name="dp2" style:family="drawing-page">
+ <style:drawing-page-properties presentation:display-header="true" presentation:display-footer="true" presentation:display-page-number="false" presentation:display-date-time="true"/>
+ </style:style>
+ <style:style style:name="dp3" style:family="drawing-page">
+ <style:drawing-page-properties presentation:background-visible="true" presentation:background-objects-visible="true" presentation:display-footer="true" presentation:display-page-number="true" presentation:display-date-time="true"/>
+ </style:style>
+ <style:style style:name="gr1" style:family="graphic" style:parent-style-name="standard">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:fill-color="#ffffff" draw:auto-grow-height="false" fo:min-height="1.485cm"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="gr2" style:family="graphic" style:parent-style-name="standard">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:fill-color="#ffffff" draw:textarea-vertical-align="bottom" draw:auto-grow-height="false" fo:min-height="1.485cm"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="gr3" style:family="graphic">
+ <style:graphic-properties style:protect="size"/>
+ </style:style>
+ <style:style style:name="pr5" style:family="presentation" style:parent-style-name="Impress1-notes">
+ <style:graphic-properties draw:fill-color="#ffffff" draw:auto-grow-height="true" fo:min-height="13.364cm"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="co1" style:family="table-column">
+ <style:table-column-properties style:column-width="6cm" style:use-optimal-column-width="false"/>
+ </style:style>
+ <style:style style:name="ro1" style:family="table-row">
+ <style:table-row-properties style:row-height="1.742cm" style:use-optimal-row-height="false"/>
+ </style:style>
+ <style:style style:name="P1" style:family="paragraph">
+ <style:text-properties fo:font-size="14pt"/>
+ </style:style>
+ <style:style style:name="P3" style:family="paragraph">
+ <style:paragraph-properties fo:text-align="end"/>
+ <style:text-properties fo:font-size="14pt"/>
+ </style:style>
+ <style:style style:name="T1" style:family="text">
+ <style:text-properties fo:font-size="14pt"/>
+ </style:style>
+ <style:style style:name="T2" style:family="text">
+ <style:text-properties fo:font-size="18pt" style:font-size-asian="18pt" style:font-size-complex="18pt"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <draw:layer-set>
+ <draw:layer draw:name="layout"/>
+ <draw:layer draw:name="background"/>
+ <draw:layer draw:name="backgroundobjects"/>
+ <draw:layer draw:name="controls"/>
+ <draw:layer draw:name="measurelines"/>
+ </draw:layer-set>
+ <style:handout-master presentation:presentation-page-layout-name="AL0T26" style:page-layout-name="PM0" draw:style-name="dp2">
+ <draw:frame draw:style-name="gr1" draw:text-style-name="P2" draw:layer="backgroundobjects" svg:width="9.113cm" svg:height="1.484cm" svg:x="0cm" svg:y="0cm" presentation:class="header">
+ <draw:text-box>
+ <text:p text:style-name="P1"><text:span text:style-name="T1"><presentation:header/></text:span></text:p>
+ </draw:text-box>
+ </draw:frame>
+ <draw:frame draw:style-name="gr1" draw:text-style-name="P4" draw:layer="backgroundobjects" svg:width="9.113cm" svg:height="1.484cm" svg:x="11.886cm" svg:y="0cm" presentation:class="date-time">
+ <draw:text-box>
+ <text:p text:style-name="P3"><text:span text:style-name="T1"><presentation:date-time/></text:span></text:p>
+ </draw:text-box>
+ </draw:frame>
+ <draw:frame draw:style-name="gr2" draw:text-style-name="P2" draw:layer="backgroundobjects" svg:width="9.113cm" svg:height="1.484cm" svg:x="0cm" svg:y="28.215cm" presentation:class="footer">
+ <draw:text-box>
+ <text:p text:style-name="P1"><text:span text:style-name="T1"><presentation:footer/></text:span></text:p>
+ </draw:text-box>
+ </draw:frame>
+ <draw:frame draw:style-name="gr2" draw:text-style-name="P4" draw:layer="backgroundobjects" svg:width="9.113cm" svg:height="1.484cm" svg:x="11.886cm" svg:y="28.215cm" presentation:class="page-number">
+ <draw:text-box>
+ <text:p text:style-name="P3"><text:span text:style-name="T1"><text:page-number>&lt;number&gt;</text:page-number></text:span></text:p>
+ </draw:text-box>
+ </draw:frame>
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="6.749cm" svg:x="1cm" svg:y="2.898cm"/>
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="6.749cm" svg:x="1cm" svg:y="11.474cm"/>
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="6.749cm" svg:x="1cm" svg:y="20.05cm"/>
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="6.749cm" svg:x="11cm" svg:y="2.898cm"/>
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="6.749cm" svg:x="11cm" svg:y="11.474cm"/>
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="6.749cm" svg:x="11cm" svg:y="20.05cm"/>
+ </style:handout-master>
+ </office:master-styles>
+ <office:body>
+ <office:presentation>
+ <draw:page draw:name="page1" draw:style-name="dp3" draw:master-page-name="Impress1" presentation:presentation-page-layout-name="AL1T1">
+ <office:forms form:automatic-focus="false" form:apply-design-mode="false"/>
+ <draw:frame draw:style-name="standard" draw:layer="layout" svg:width="17.999cm" svg:height="3.483cm" svg:x="2cm" svg:y="6cm" presentation:class="table" presentation:user-transformed="true">
+ <table:table table:template-name="default" table:use-first-row-styles="true" table:use-banding-rows-styles="true">
+ <table:table-column table:style-name="co1"/>
+ <table:table-column table:style-name="co1"/>
+ <table:table-column table:style-name="co1"/>
+ <table:table-row table:style-name="ro1" table:default-cell-style-name="gray3">
+ <table:table-cell/>
+ <table:table-cell>
+ <text:p><text:span text:style-name="T2">Average starting score</text:span></text:p>
+ </table:table-cell>
+ <table:table-cell>
+ <text:p><text:span text:style-name="T2">Average improved score</text:span></text:p>
+ </table:table-cell>
+ </table:table-row>
+ <table:table-row table:style-name="ro1" table:default-cell-style-name="gray2">
+ <table:table-cell>
+ <text:p><text:span text:style-name="T2">Images</text:span></text:p>
+ </table:table-cell>
+ <table:table-cell>
+ <text:p><text:span text:style-name="T2">35%</text:span></text:p>
+ </table:table-cell>
+ <table:table-cell>
+ <text:p><text:span text:style-name="T2">91%</text:span></text:p>
+ </table:table-cell>
+ </table:table-row>
+ </table:table>
+ </draw:frame>
+ <presentation:notes draw:style-name="dp2">
+ <draw:page-thumbnail draw:style-name="gr3" draw:layer="layout" svg:width="14.848cm" svg:height="11.136cm" svg:x="3.075cm" svg:y="2.257cm" draw:page-number="1" presentation:class="page"/>
+ <draw:frame presentation:style-name="pr5" draw:text-style-name="P5" draw:layer="layout" svg:width="16.799cm" svg:height="13.364cm" svg:x="2.1cm" svg:y="14.107cm" presentation:class="notes" presentation:placeholder="true">
+ <draw:text-box/>
+ </draw:frame>
+ </presentation:notes>
+ </draw:page>
+ <presentation:settings presentation:mouse-visible="false"/>
+ </office:presentation>
+ </office:body>
+</office:document> \ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf135346.ods b/vcl/qa/cppunit/pdfexport/data/tdf135346.ods
new file mode 100644
index 0000000000..5f696e5504
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf135346.ods
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf139065.odt b/vcl/qa/cppunit/pdfexport/data/tdf139065.odt
new file mode 100644
index 0000000000..f8ffa26b2f
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf139065.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf139736-1.odt b/vcl/qa/cppunit/pdfexport/data/tdf139736-1.odt
new file mode 100644
index 0000000000..f17f603788
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf139736-1.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf141171.odt b/vcl/qa/cppunit/pdfexport/data/tdf141171.odt
new file mode 100644
index 0000000000..951f69541a
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf141171.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf144222.ods b/vcl/qa/cppunit/pdfexport/data/tdf144222.ods
new file mode 100644
index 0000000000..7b572d301a
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf144222.ods
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf145274.docx b/vcl/qa/cppunit/pdfexport/data/tdf145274.docx
new file mode 100644
index 0000000000..5b3b6afecf
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf145274.docx
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf145873.pptx b/vcl/qa/cppunit/pdfexport/data/tdf145873.pptx
new file mode 100644
index 0000000000..7f5eb135c9
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf145873.pptx
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf147027.ods b/vcl/qa/cppunit/pdfexport/data/tdf147027.ods
new file mode 100644
index 0000000000..24e1fde9ae
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf147027.ods
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf147164.odp b/vcl/qa/cppunit/pdfexport/data/tdf147164.odp
new file mode 100644
index 0000000000..1b593348ea
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf147164.odp
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf148442.odt b/vcl/qa/cppunit/pdfexport/data/tdf148442.odt
new file mode 100644
index 0000000000..10a5abf48e
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf148442.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf148706.odt b/vcl/qa/cppunit/pdfexport/data/tdf148706.odt
new file mode 100644
index 0000000000..974bb97433
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf148706.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf150846.txt b/vcl/qa/cppunit/pdfexport/data/tdf150846.txt
new file mode 100644
index 0000000000..ce01362503
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf150846.txt
@@ -0,0 +1 @@
+hello
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf150931.ods b/vcl/qa/cppunit/pdfexport/data/tdf150931.ods
new file mode 100644
index 0000000000..633362c614
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf150931.ods
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf152231.fodt b/vcl/qa/cppunit/pdfexport/data/tdf152231.fodt
new file mode 100644
index 0000000000..26b5d329b2
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf152231.fodt
@@ -0,0 +1,208 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:initial-creator>Gabor Kelemen LO</meta:initial-creator><meta:creation-date>2022-11-22T00:38:49.808000000</meta:creation-date><dc:date>2022-11-22T01:02:17.524000000</dc:date><dc:creator>Gabor Kelemen LO</dc:creator><meta:editing-duration>PT23M25S</meta:editing-duration><meta:editing-cycles>11</meta:editing-cycles><meta:generator>LibreOfficeDev/24.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/3d1f9c90605623e5c7e7dd2d28f87aaa45fb9c86</meta:generator><dc:title>aaa</dc:title><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="12" meta:word-count="291" meta:character-count="1539" meta:non-whitespace-character-count="1250"/><meta:print-date>2023-06-30T17:07:42.601941685</meta:print-date><meta:printed-by>PDF files</meta:printed-by></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Lucida Sans1" svg:font-family="'Lucida Sans'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="NSimSun1" svg:font-family="NSimSun" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="NSimSun1" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lucida Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="page"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="NSimSun1" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lucida Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <style:style style:name="Line_20_numbering" style:display-name="Line numbering" style:family="text"/>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:style-name="Line_20_numbering" text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
+ <loext:theme loext:name="Office Theme">
+ <loext:theme-colors loext:name="LibreOffice">
+ <loext:color loext:name="dark1" loext:color="#000000"/>
+ <loext:color loext:name="light1" loext:color="#ffffff"/>
+ <loext:color loext:name="dark2" loext:color="#000000"/>
+ <loext:color loext:name="light2" loext:color="#ffffff"/>
+ <loext:color loext:name="accent1" loext:color="#18a303"/>
+ <loext:color loext:name="accent2" loext:color="#0369a3"/>
+ <loext:color loext:name="accent3" loext:color="#a33e03"/>
+ <loext:color loext:name="accent4" loext:color="#8e03a3"/>
+ <loext:color loext:name="accent5" loext:color="#c99c00"/>
+ <loext:color loext:name="accent6" loext:color="#c9211e"/>
+ <loext:color loext:name="hyperlink" loext:color="#0000ee"/>
+ <loext:color loext:name="followed-hyperlink" loext:color="#551a8b"/>
+ </loext:theme-colors>
+ </loext:theme>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="P2" style:family="paragraph">
+ <style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-top="0cm" fo:margin-bottom="0cm" fo:line-height="100%" fo:text-align="start" fo:text-indent="0cm" style:text-autospace="ideograph-alpha" style:punctuation-wrap="simple" style:line-break="strict" loext:tab-stop-distance="0cm" style:writing-mode="lr-tb">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties fo:hyphenate="false" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false"/>
+ </style:style>
+ <style:style style:name="T1" style:family="text">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="T2" style:family="text">
+ <style:text-properties fo:font-variant="normal" fo:text-transform="none" style:use-window-font-color="true" loext:opacity="0%" style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:text-position="0% 100%" style:font-name="Liberation Serif" fo:font-size="10pt" fo:letter-spacing="normal" fo:language="de" fo:country="DE" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:text-underline-mode="continuous" style:text-overline-mode="continuous" style:text-line-through-mode="continuous" style:letter-kerning="true" fo:background-color="transparent" style:font-name-asian="NSimSun1" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-name-complex="Lucida Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-emphasize="none" style:text-scale="100%" style:font-relief="none" style:text-overline-style="none" style:text-overline-color="font-color"/>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm2">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm3" style:page-usage="left">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm4" style:page-usage="right">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm5">
+ <style:page-layout-properties fo:page-width="22.901cm" fo:page-height="11.4cm" style:num-format="1" style:print-orientation="landscape" fo:margin-top="0cm" fo:margin-bottom="0cm" fo:margin-left="0cm" fo:margin-right="0cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm6">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="1cm" fo:margin-bottom="1cm" fo:margin-left="2cm" fo:margin-right="1cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm7">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm8">
+ <style:page-layout-properties fo:page-width="29.7cm" fo:page-height="21.001cm" style:num-format="1" style:print-orientation="landscape" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text>
+ <text:sequence-decls>
+ <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+ </text:sequence-decls>
+ <text:p text:style-name="Standard">He heard quiet steps behind him. </text:p>
+ <text:p text:style-name="Standard">That didn't bode well. </text:p>
+ <text:p text:style-name="Standard">Who could be following him this late at night and in this deadbeat part of town?</text:p>
+ <text:p text:style-name="Standard"><text:span text:style-name="T1">A</text:span>nd at this particular moment, just after he pulled off the big time and was making off with the greenbacks. </text:p>
+ <text:p text:style-name="Standard">Was there another crook who'd had the same idea, and was now watching him and waiting for a chance to grab the fruit of his labor? </text:p>
+ <text:p text:style-name="Standard">Or did the steps behind him mean that one of many law officers in town was on to him and just waiting to pounce and snap those cuffs on his wrists? </text:p>
+ <text:p text:style-name="Standard"><office:annotation office:name="__Annotation__234_1396804301" loext:resolved="false">
+ <dc:creator>Gabor Kelemen LO</dc:creator>
+ <dc:date>2022-11-22T00:56:44.315000000</dc:date>
+ <meta:creator-initials>GK</meta:creator-initials>
+ <text:p text:style-name="P2"><text:span text:style-name="T2">This is a comment</text:span></text:p>
+ </office:annotation>He nervously looked all around.<office:annotation-end office:name="__Annotation__234_1396804301"/> </text:p>
+ <text:p text:style-name="Standard">Suddenly he saw the alley. </text:p>
+ <text:p text:style-name="Standard">Like lightning he darted off to the left and disappeared between the two warehouses almost falling </text:p>
+ <text:p text:style-name="Standard">over the trash can lying in the middle of the sidewalk. </text:p>
+ <text:p text:style-name="Standard">He tried to nervously tap his way along in the inky </text:p>
+ <text:p text:style-name="P1">darkness and suddenly stiffened: it was a dead-end, he would have to go back the way he had come. The steps got louder and louder, he saw the black outline of a figure coming around the corner. Is this the end of the line? he thought pressing himself back against the wall trying to make himself invisible in the dark, was all that planning and energy wasted? He was dripping with sweat now, cold and wet, he could smell the fear coming off his clothes. Suddenly next to him, with a barely noticeable squeak, a door swung quietly to and fro in the night's breeze. Could this be the haven he'd prayed for? Slowly he slid toward the door, pressing himself more and more into the wall, into the dark, away from his enemy. Would this door save his hide?</text:p>
+ </office:text>
+ </office:body>
+</office:document> \ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf154549.odt b/vcl/qa/cppunit/pdfexport/data/tdf154549.odt
new file mode 100644
index 0000000000..09ed1324ec
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf154549.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf154982.odt b/vcl/qa/cppunit/pdfexport/data/tdf154982.odt
new file mode 100644
index 0000000000..a35ffb861a
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf154982.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf155161.odt b/vcl/qa/cppunit/pdfexport/data/tdf155161.odt
new file mode 100644
index 0000000000..1d22bb7c4d
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf155161.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf155190.odt b/vcl/qa/cppunit/pdfexport/data/tdf155190.odt
new file mode 100644
index 0000000000..51930ad299
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf155190.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf156685.docx b/vcl/qa/cppunit/pdfexport/data/tdf156685.docx
new file mode 100644
index 0000000000..a3eddf8459
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf156685.docx
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf157679.pptx b/vcl/qa/cppunit/pdfexport/data/tdf157679.pptx
new file mode 100644
index 0000000000..ca82491c21
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf157679.pptx
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf157816.fodt b/vcl/qa/cppunit/pdfexport/data/tdf157816.fodt
new file mode 100644
index 0000000000..5288f3aa75
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf157816.fodt
@@ -0,0 +1,175 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:creation-date>2020-12-04T17:01:40.586000000</meta:creation-date><dc:title>LibreOffice</dc:title><meta:editing-duration>P18DT13H30M55S</meta:editing-duration><meta:editing-cycles>102</meta:editing-cycles><meta:generator>LibreOfficeDev/24.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/70bf29987ceb90cd3988fc2dfc4cad2a0163fe17</meta:generator><dc:date>2023-10-18T20:11:26.174000000</dc:date><meta:print-date>2023-10-18T20:36:10.265000000</meta:print-date><meta:printed-by>PDF files</meta:printed-by><meta:document-statistic meta:table-count="0" meta:image-count="5" meta:object-count="0" meta:page-count="1" meta:paragraph-count="12" meta:word-count="61" meta:character-count="412" meta:non-whitespace-character-count="347"/><meta:template xlink:type="simple" xlink:actuate="onRequest" xlink:title="+1.Fit_Vorlage_V0.04" xlink:href="../H:/AppData/Local/Microsoft/Windows/INetCache/AppData/Roaming/LibreOffice/4/user/template/SH/+1.Fit_Vorlage_V0.04.ott" meta:date="2020-12-15T11:12:58.522000000"/></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="Arial" svg:font-family="Arial" style:font-adornments="Standard" style:font-family-generic="swiss" style:font-pitch="variable"/>
+ <style:font-face style:name="Lucida Sans1" svg:font-family="'Lucida Sans'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="NSimSun" svg:font-family="NSimSun" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Trebuchet MS" svg:font-family="'Trebuchet MS'" style:font-family-generic="swiss"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Trebuchet MS" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="NSimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lucida Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.249cm" style:writing-mode="lr-tb"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Trebuchet MS" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="NSimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lucida Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2"/>
+ <style:text-properties style:font-name="Arial" fo:font-family="Arial" style:font-style-name="Standard" style:font-family-generic="swiss" style:font-pitch="variable"/>
+ </style:style>
+ <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
+ <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.247cm" style:contextual-spacing="false" fo:line-height="100%"/>
+ </style:style>
+ <style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+ <style:paragraph-properties fo:margin-top="0.212cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
+ <style:text-properties fo:font-size="10pt"/>
+ </style:style>
+ <style:style style:name="Illustration" style:family="paragraph" style:parent-style-name="Caption" style:class="extra" style:master-page-name="">
+ <style:paragraph-properties style:page-number="auto"/>
+ <style:text-properties fo:font-size="8pt"/>
+ </style:style>
+ <style:style style:name="Line_20_numbering" style:display-name="Line numbering" style:family="text"/>
+ <style:style style:name="Frame" style:family="graphic">
+ <style:graphic-properties text:anchor-type="paragraph" svg:x="0cm" svg:y="0cm" fo:margin-left="0.201cm" fo:margin-right="0.201cm" fo:margin-top="0.199cm" fo:margin-bottom="0.499cm" style:wrap="parallel" style:number-wrapped-paragraphs="no-limit" style:wrap-contour="false" style:vertical-pos="top" style:vertical-rel="paragraph-content" style:horizontal-pos="center" style:horizontal-rel="paragraph-content" fo:background-color="transparent" draw:fill="none" draw:fill-color="#729fcf" fo:padding="0.15cm" fo:border="0.06pt solid #000000" draw:wrap-influence-on-position="once-concurrent" loext:allow-overlap="true">
+ <style:columns fo:column-count="1" fo:column-gap="0cm"/>
+ </style:graphic-properties>
+ </style:style>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="1">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" loext:num-list-format="%1%.%2%" style:num-format="1" text:display-levels="2">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" loext:num-list-format="%1%.%2%.%3%" style:num-format="1" text:display-levels="3">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" loext:num-list-format="%1%.%2%.%3%.%4%" style:num-format="1" text:display-levels="4">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:style-name="Line_20_numbering" text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
+ <style:default-page-layout>
+ <style:page-layout-properties style:writing-mode="lr-tb" style:layout-grid-standard-mode="true"/>
+ </style:default-page-layout>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="P3" style:family="paragraph" style:parent-style-name="Text_20_body">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="P4" style:family="paragraph" style:parent-style-name="Text_20_body">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="P5" style:family="paragraph" style:parent-style-name="Text_20_body" style:master-page-name="_2b_1.Fit_5f_ErsteSeite">
+ <style:paragraph-properties style:page-number="auto"/>
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="T6" style:family="text">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="T7" style:family="text">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="T8" style:family="text">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="T9" style:family="text">
+ <style:text-properties/>
+ </style:style>
+ <style:style style:name="fr3" style:family="graphic" style:parent-style-name="Frame">
+ <style:graphic-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-top="0cm" fo:margin-bottom="1cm" style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" fo:padding="0cm" fo:border="none"/>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="1cm" fo:margin-left="2.499cm" fo:margin-right="1.499cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="44" style:layout-grid-base-height="0.55cm" style:layout-grid-ruby-height="0cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="true" style:layout-grid-display="true" style:layout-grid-base-width="0.37cm" style:layout-grid-snap-to="true" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:page-layout style:name="pm2">
+ <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="44" style:layout-grid-base-height="0.55cm" style:layout-grid-ruby-height="0cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="true" style:layout-grid-display="true" style:layout-grid-base-width="0.37cm" style:layout-grid-snap-to="true" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ <style:master-page style:name="_2b_1.Fit_5f_ErsteSeite" style:display-name="+1.Fit_ErsteSeite" style:page-layout-name="pm2" draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text>
+ <text:sequence-decls>
+ <text:sequence-decl text:display-outline-level="1" text:separation-character="." text:name="Illustration"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+ <text:sequence-decl text:display-outline-level="1" text:separation-character="." text:name="TippNr"/>
+ </text:sequence-decls>
+ <text:p text:style-name="P5"><draw:frame draw:style-name="fr3" draw:name="Rahmen13" text:anchor-type="char" svg:x="5.142cm" svg:y="0.079cm" svg:width="11.829cm" draw:z-index="0">
+ <draw:text-box fo:min-height="9.114cm">
+ <text:p text:style-name="Illustration">Abbildung <text:sequence text:ref-name="refIllustration0" text:name="Illustration" text:formula="ooow:Illustration+1" style:num-format="1">1</text:sequence>: <text:span text:style-name="T7">Haus!</text:span></text:p>
+ </draw:text-box>
+ </draw:frame><text:span text:style-name="T6">Extras </text:span><text:span text:style-name="T8">fdsfsdf dfs sdf dsfsd fs fsd sdfsd fsgf gfhf gfhgfrt fd fdg ddfg dfgd fdfg df dfg <text:s/></text:span></text:p>
+ <text:p text:style-name="P4"><text:span text:style-name="T9">szíj</text:span><text:span text:style-name="T8">fdgdf </text:span><text:span text:style-name="T9">f</text:span><text:span text:style-name="T8">dfg dfg gdfgdfg dggdf fd</text:span></text:p>
+ <text:p text:style-name="P3"><text:span text:style-name="T7">aa</text:span><text:span text:style-name="T9">ds fsdfsd sdf sd fsd sdffsdf</text:span><text:span text:style-name="T7"> </text:span><text:bookmark-ref text:reference-format="chapter" text:ref-name="__RefHeading___Toc1501_2152971747">Error: Reference source not found</text:bookmark-ref><text:bookmark-ref text:reference-format="text" text:ref-name="__RefHeading___Toc1501_2152971747">Error: Reference source not found</text:bookmark-ref><text:s/><text:span text:style-name="T7">bb </text:span><text:span text:style-name="T9">dsfsd sdfsdfsd</text:span>.</text:p>
+ </office:text>
+ </office:body>
+</office:document> \ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf48707-1.fodt b/vcl/qa/cppunit/pdfexport/data/tdf48707-1.fodt
new file mode 100644
index 0000000000..02286e5051
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf48707-1.fodt
@@ -0,0 +1,290 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:initial-creator>Scott Wills</meta:initial-creator><meta:creation-date>2011-08-03T07:12:28</meta:creation-date><dc:date>2023-07-03T14:56:12.773653822</dc:date><meta:editing-duration>PT1M52S</meta:editing-duration><meta:editing-cycles>2</meta:editing-cycles><meta:generator>LibreOffice/7.5.4.2$MacOSX_X86_64 LibreOffice_project/36ccfdc35048b057fd9854c757a8b67ec53977b6</meta:generator><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="1" meta:word-count="1" meta:character-count="3" meta:non-whitespace-character-count="3"/></office:meta>
+ <office:settings>
+ <config:config-item-set config:name="ooo:view-settings">
+ <config:config-item config:name="ViewAreaTop" config:type="long">0</config:config-item>
+ <config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item>
+ <config:config-item config:name="ViewAreaWidth" config:type="long">22666</config:config-item>
+ <config:config-item config:name="ViewAreaHeight" config:type="long">10624</config:config-item>
+ <config:config-item config:name="ShowRedlineChanges" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="InBrowseMode" config:type="boolean">false</config:config-item>
+ <config:config-item-map-indexed config:name="Views">
+ <config:config-item-map-entry>
+ <config:config-item config:name="ViewId" config:type="string">view2</config:config-item>
+ <config:config-item config:name="ViewLeft" config:type="long">10463</config:config-item>
+ <config:config-item config:name="ViewTop" config:type="long">2501</config:config-item>
+ <config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item>
+ <config:config-item config:name="VisibleTop" config:type="long">0</config:config-item>
+ <config:config-item config:name="VisibleRight" config:type="long">22664</config:config-item>
+ <config:config-item config:name="VisibleBottom" config:type="long">10622</config:config-item>
+ <config:config-item config:name="ZoomType" config:type="short">3</config:config-item>
+ <config:config-item config:name="ViewLayoutColumns" config:type="short">0</config:config-item>
+ <config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ZoomFactor" config:type="short">167</config:config-item>
+ <config:config-item config:name="IsSelectedFrame" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="KeepRatio" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AnchoredTextOverflowLegacy" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="LegacySingleLineFontwork" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="ConnectorUseSnapRect" config:type="boolean">false</config:config-item>
+ </config:config-item-map-entry>
+ </config:config-item-map-indexed>
+ </config:config-item-set>
+ <config:config-item-set config:name="ooo:configuration-settings">
+ <config:config-item config:name="PrintControls" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintPageBackground" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintGraphics" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintProspectRTL" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintFaxName" config:type="string"/>
+ <config:config-item config:name="PrintProspect" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintReversed" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintEmptyPages" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="NoNumberingShowFollowBy" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="DisableOffPagePositioning" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="HyphenateURLs" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="MsWordCompMinLineHeightByFly" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AutoFirstLineIndentDisregardLineSpace" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="WordLikeWrapForAsCharFlys" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="FrameAutowidthWithMorePara" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="HeaderSpacingBelowLastPara" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintAnnotationMode" config:type="short">0</config:config-item>
+ <config:config-item config:name="ImagePreferredDPI" config:type="int">0</config:config-item>
+ <config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ProtectBookmarks" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ContinuousEndnotes" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="UnbreakableNumberings" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PropLineSpacingShrinksFirstLine" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ApplyParagraphMarkFormatToNumbering" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="TreatSingleColumnBreakAsPageBreak" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedComplexScriptFonts" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="ProtectFields" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedOnlyUsedFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrinterSetup" config:type="base64Binary"/>
+ <config:config-item config:name="StylesNoDefault" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="OutlineLevelYieldsNumbering" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="RedlineProtectionKey" config:type="base64Binary"/>
+ <config:config-item config:name="CollapseEmptyCellPara" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintBlackFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedSystemFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="IgnoreTabsAndBlanksForLineCalculation" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintRightPages" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="SmallCapsPercentage66" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="MathBaselineAlignment" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmptyDbFieldHidesPara" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ClippedPictures" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="TabAtLeftIndentForParagraphsInList" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ConsiderTextWrapOnObjPos" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="TabOverflow" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="DropCapPunctuation" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="IsLabelDocument" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbeddedDatabaseName" config:type="string"/>
+ <config:config-item config:name="UnxForceZeroExtLeading" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="FloattableNomargins" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="DoNotCaptureDrawObjsOnPage" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="GutterAtTop" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="SurroundTextWrapSmall" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="SaveGlobalDocumentLinks" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="CurrentDatabaseCommandType" config:type="int">0</config:config-item>
+ <config:config-item config:name="TableRowKeep" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="DoNotResetParaAttrsForNumFont" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="TabOverMargin" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="IgnoreFirstLineIndentInNumbering" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AlignTabStopPosition" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="UseFormerTextWrapping" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="UseOldNumbering" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="BackgroundParaOverDrawings" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintTextPlaceholder" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="TabOverSpacing" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="CurrentDatabaseCommand" config:type="string"/>
+ <config:config-item config:name="AddParaLineSpacingToTableCells" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="FootnoteInColumnToPageEnd" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AddParaSpacingToTableCells" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AddVerticalFrameOffsets" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ApplyUserData" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintHiddenText" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="RsidRoot" config:type="int">821660</config:config-item>
+ <config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/>
+ <config:config-item config:name="PrinterPaperFromSetup" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedAsianScriptFonts" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintPaperFromSetup" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrinterIndependentLayout" config:type="string">high-resolution</config:config-item>
+ <config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item>
+ <config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
+ <config:config-item config:name="FieldAutoUpdate" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="IsKernAsianPunctuation" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintLeftPages" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="SaveThumbnail" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AddParaTableSpacing" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintDrawings" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="EmbedLatinScriptFonts" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AddExternalLeading" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrinterName" config:type="string"/>
+ <config:config-item config:name="DoNotJustifyLinesWithManualBreak" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ChartAutoUpdate" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="Rsid" config:type="int">850755</config:config-item>
+ <config:config-item config:name="AddFrameOffsets" config:type="boolean">false</config:config-item>
+ </config:config-item-set>
+ </office:settings>
+ <office:scripts>
+ <office:script script:language="ooo:Basic">
+ <ooo:libraries xmlns:ooo="http://openoffice.org/2004/office" xmlns:xlink="http://www.w3.org/1999/xlink"/>
+ </office:script>
+ </office:scripts>
+ <office:font-face-decls>
+ <style:font-face style:name="Arial" svg:font-family="Arial" style:font-family-generic="swiss" style:font-pitch="variable"/>
+ <style:font-face style:name="Lohit Hindi" svg:font-family="&apos;Lohit Hindi&apos;"/>
+ <style:font-face style:name="Lohit Hindi1" svg:font-family="&apos;Lohit Hindi&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Times New Roman" svg:font-family="&apos;Times New Roman&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="WenQuanYi Micro Hei" svg:font-family="&apos;WenQuanYi Micro Hei&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#000000" draw:fill-color="#99ccff" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" draw:start-line-spacing-horizontal="0.1114in" draw:start-line-spacing-vertical="0.1114in" draw:end-line-spacing-horizontal="0.1114in" draw:end-line-spacing-vertical="0.1114in" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" loext:color-lum-mod="100%" loext:color-lum-off="0%" style:font-name="Times New Roman" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="WenQuanYi Micro Hei" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lohit Hindi1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="0.5in" style:writing-mode="page"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Times New Roman" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="WenQuanYi Micro Hei" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lohit Hindi1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.1665in" fo:margin-bottom="0.0835in" style:contextual-spacing="false" fo:keep-with-next="always"/>
+ <style:text-properties style:font-name="Arial" fo:font-family="Arial" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt" style:font-name-asian="WenQuanYi Micro Hei" style:font-family-asian="&apos;WenQuanYi Micro Hei&apos;" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="Lohit Hindi1" style:font-family-complex="&apos;Lohit Hindi&apos;" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
+ </style:style>
+ <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
+ <style:paragraph-properties fo:margin-top="0in" fo:margin-bottom="0.0835in" style:contextual-spacing="false"/>
+ </style:style>
+ <style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list">
+ <style:text-properties style:font-size-asian="12pt" style:font-name-complex="Lohit Hindi" style:font-family-complex="&apos;Lohit Hindi&apos;"/>
+ </style:style>
+ <style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+ <style:paragraph-properties fo:margin-top="0.0835in" fo:margin-bottom="0.0835in" style:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
+ <style:text-properties fo:font-size="12pt" fo:font-style="italic" style:font-size-asian="12pt" style:font-style-asian="italic" style:font-name-complex="Lohit Hindi" style:font-family-complex="&apos;Lohit Hindi&apos;" style:font-size-complex="12pt" style:font-style-complex="italic"/>
+ </style:style>
+ <style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index">
+ <style:paragraph-properties text:number-lines="false" text:line-number="0"/>
+ <style:text-properties style:font-size-asian="12pt" style:font-name-complex="Lohit Hindi" style:font-family-complex="&apos;Lohit Hindi&apos;"/>
+ </style:style>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.3in" fo:text-indent="-0.3in" fo:margin-left="0.3in"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.4in" fo:text-indent="-0.4in" fo:margin-left="0.4in"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.5in" fo:text-indent="-0.5in" fo:margin-left="0.5in"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.6in" fo:text-indent="-0.6in" fo:margin-left="0.6in"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.7in" fo:text-indent="-0.7in" fo:margin-left="0.7in"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.8in" fo:text-indent="-0.8in" fo:margin-left="0.8in"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.9in" fo:text-indent="-0.9in" fo:margin-left="0.9in"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1in" fo:text-indent="-1in" fo:margin-left="1in"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.1in" fo:text-indent="-1.1in" fo:margin-left="1.1in"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.2in" fo:text-indent="-1.2in" fo:margin-left="1.2in"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:number-lines="false" text:offset="0.1965in" style:num-format="1" text:number-position="left" text:increment="5"/>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard">
+ <style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
+ <style:text-properties style:font-name="Arial" fo:font-size="24pt" style:font-size-asian="24pt" style:font-size-complex="24pt"/>
+ </style:style>
+ <style:style style:name="T1" style:family="text">
+ <style:text-properties fo:color="#ff0000" loext:opacity="100%" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"/>
+ </style:style>
+ <style:style style:name="T2" style:family="text">
+ <style:text-properties fo:color="#ff0000" loext:opacity="100%" style:text-overline-style="solid" style:text-overline-width="auto" style:text-overline-color="font-color"/>
+ </style:style>
+ <style:style style:name="T3" style:family="text">
+ <style:text-properties fo:color="#ff0000" loext:opacity="100%" style:text-overline-style="solid" style:text-overline-width="auto" style:text-overline-color="#ff0000"/>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="8.5in" fo:page-height="11in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.278in" style:layout-grid-ruby-height="0.139in" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0in" loext:margin-gutter="0in">
+ <style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text>
+ <text:sequence-decls>
+ <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+ </text:sequence-decls>
+ <text:p text:style-name="P1"><text:span text:style-name="T1">A</text:span><text:span text:style-name="T2">B</text:span><text:span text:style-name="T3">C</text:span></text:p>
+ </office:text>
+ </office:body>
+</office:document> \ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf48707-2.fodt b/vcl/qa/cppunit/pdfexport/data/tdf48707-2.fodt
new file mode 100644
index 0000000000..2fc8287bab
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf48707-2.fodt
@@ -0,0 +1,335 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:creation-date>2021-05-08T05:14:15.295503248</meta:creation-date><dc:date>2021-05-08T05:18:15.188776430</dc:date><meta:editing-duration>PT2M4S</meta:editing-duration><meta:editing-cycles>2</meta:editing-cycles><meta:generator>LibreOffice/7.5.4.2$MacOSX_X86_64 LibreOffice_project/36ccfdc35048b057fd9854c757a8b67ec53977b6</meta:generator><meta:document-statistic meta:table-count="1" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="1" meta:word-count="3" meta:character-count="14" meta:non-whitespace-character-count="12"/></office:meta>
+ <office:settings>
+ <config:config-item-set config:name="ooo:view-settings">
+ <config:config-item config:name="ViewAreaTop" config:type="long">0</config:config-item>
+ <config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item>
+ <config:config-item config:name="ViewAreaWidth" config:type="long">37110</config:config-item>
+ <config:config-item config:name="ViewAreaHeight" config:type="long">17392</config:config-item>
+ <config:config-item config:name="ShowRedlineChanges" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="InBrowseMode" config:type="boolean">false</config:config-item>
+ <config:config-item-map-indexed config:name="Views">
+ <config:config-item-map-entry>
+ <config:config-item config:name="ViewId" config:type="string">view2</config:config-item>
+ <config:config-item config:name="ViewLeft" config:type="long">10054</config:config-item>
+ <config:config-item config:name="ViewTop" config:type="long">2501</config:config-item>
+ <config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item>
+ <config:config-item config:name="VisibleTop" config:type="long">0</config:config-item>
+ <config:config-item config:name="VisibleRight" config:type="long">37109</config:config-item>
+ <config:config-item config:name="VisibleBottom" config:type="long">17390</config:config-item>
+ <config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
+ <config:config-item config:name="ViewLayoutColumns" config:type="short">1</config:config-item>
+ <config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ZoomFactor" config:type="short">102</config:config-item>
+ <config:config-item config:name="IsSelectedFrame" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="KeepRatio" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AnchoredTextOverflowLegacy" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="LegacySingleLineFontwork" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="ConnectorUseSnapRect" config:type="boolean">false</config:config-item>
+ </config:config-item-map-entry>
+ </config:config-item-map-indexed>
+ </config:config-item-set>
+ <config:config-item-set config:name="ooo:configuration-settings">
+ <config:config-item config:name="PrintControls" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintPageBackground" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintGraphics" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintProspectRTL" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintFaxName" config:type="string"/>
+ <config:config-item config:name="PrintProspect" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintReversed" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintEmptyPages" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="NoNumberingShowFollowBy" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="DisableOffPagePositioning" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="HyphenateURLs" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="MsWordCompMinLineHeightByFly" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AutoFirstLineIndentDisregardLineSpace" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="WordLikeWrapForAsCharFlys" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="FrameAutowidthWithMorePara" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="HeaderSpacingBelowLastPara" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintAnnotationMode" config:type="short">0</config:config-item>
+ <config:config-item config:name="ImagePreferredDPI" config:type="int">0</config:config-item>
+ <config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ProtectBookmarks" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ContinuousEndnotes" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="UnbreakableNumberings" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PropLineSpacingShrinksFirstLine" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="ApplyParagraphMarkFormatToNumbering" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="TreatSingleColumnBreakAsPageBreak" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedComplexScriptFonts" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="ProtectFields" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedOnlyUsedFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrinterSetup" config:type="base64Binary"/>
+ <config:config-item config:name="StylesNoDefault" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="OutlineLevelYieldsNumbering" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="RedlineProtectionKey" config:type="base64Binary"/>
+ <config:config-item config:name="CollapseEmptyCellPara" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintBlackFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedSystemFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="IgnoreTabsAndBlanksForLineCalculation" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintRightPages" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="SmallCapsPercentage66" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="MathBaselineAlignment" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="EmptyDbFieldHidesPara" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="ClippedPictures" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="TabAtLeftIndentForParagraphsInList" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ConsiderTextWrapOnObjPos" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="TabOverflow" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="DropCapPunctuation" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="IsLabelDocument" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbeddedDatabaseName" config:type="string"/>
+ <config:config-item config:name="UnxForceZeroExtLeading" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="FloattableNomargins" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="DoNotCaptureDrawObjsOnPage" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="GutterAtTop" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="SurroundTextWrapSmall" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="SaveGlobalDocumentLinks" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="CurrentDatabaseCommandType" config:type="int">0</config:config-item>
+ <config:config-item config:name="TableRowKeep" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="DoNotResetParaAttrsForNumFont" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="TabOverMargin" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="IgnoreFirstLineIndentInNumbering" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AlignTabStopPosition" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="UseFormerTextWrapping" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="UseOldNumbering" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="BackgroundParaOverDrawings" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintTextPlaceholder" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="TabOverSpacing" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="CurrentDatabaseCommand" config:type="string"/>
+ <config:config-item config:name="AddParaLineSpacingToTableCells" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="FootnoteInColumnToPageEnd" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AddParaSpacingToTableCells" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AddVerticalFrameOffsets" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ApplyUserData" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintHiddenText" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="RsidRoot" config:type="int">1346600</config:config-item>
+ <config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/>
+ <config:config-item config:name="PrinterPaperFromSetup" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedAsianScriptFonts" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintPaperFromSetup" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrinterIndependentLayout" config:type="string">high-resolution</config:config-item>
+ <config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item>
+ <config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
+ <config:config-item config:name="FieldAutoUpdate" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="IsKernAsianPunctuation" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintLeftPages" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="SaveThumbnail" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AddParaTableSpacing" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintDrawings" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="EmbedLatinScriptFonts" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AddExternalLeading" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrinterName" config:type="string"/>
+ <config:config-item config:name="DoNotJustifyLinesWithManualBreak" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ChartAutoUpdate" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="Rsid" config:type="int">1496896</config:config-item>
+ <config:config-item config:name="AddFrameOffsets" config:type="boolean">false</config:config-item>
+ </config:config-item-set>
+ </office:settings>
+ <office:scripts>
+ <office:script script:language="ooo:Basic">
+ <ooo:libraries xmlns:ooo="http://openoffice.org/2004/office" xmlns:xlink="http://www.w3.org/1999/xlink"/>
+ </office:script>
+ </office:scripts>
+ <office:font-face-decls>
+ <style:font-face style:name="FreeSans" svg:font-family="FreeSans" style:font-family-generic="swiss"/>
+ <style:font-face style:name="FreeSans1" svg:font-family="FreeSans" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Liberation Sans" svg:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="swiss" style:font-pitch="variable"/>
+ <style:font-face style:name="Liberation Serif" svg:font-family="&apos;Liberation Serif&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="WenQuanYi Micro Hei" svg:font-family="&apos;WenQuanYi Micro Hei&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" draw:start-line-spacing-horizontal="0.1114in" draw:start-line-spacing-vertical="0.1114in" draw:end-line-spacing-horizontal="0.1114in" draw:end-line-spacing-vertical="0.1114in" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" loext:color-lum-mod="100%" loext:color-lum-off="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="GB" style:letter-kerning="true" style:font-name-asian="WenQuanYi Micro Hei" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="FreeSans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="0.4925in" style:writing-mode="page"/>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="GB" style:letter-kerning="true" style:font-name-asian="WenQuanYi Micro Hei" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="FreeSans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
+ </style:default-style>
+ <style:default-style style:family="table">
+ <style:table-properties table:border-model="collapsing"/>
+ </style:default-style>
+ <style:default-style style:family="table-row">
+ <style:table-row-properties fo:keep-together="auto"/>
+ </style:default-style>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.1665in" fo:margin-bottom="0.0835in" style:contextual-spacing="false" fo:keep-with-next="always"/>
+ <style:text-properties style:font-name="Liberation Sans" fo:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt" style:font-name-asian="WenQuanYi Micro Hei" style:font-family-asian="&apos;WenQuanYi Micro Hei&apos;" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="FreeSans1" style:font-family-complex="FreeSans" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
+ </style:style>
+ <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
+ <style:paragraph-properties fo:margin-top="0in" fo:margin-bottom="0.0972in" style:contextual-spacing="false" fo:line-height="115%"/>
+ </style:style>
+ <style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list">
+ <style:text-properties style:font-size-asian="12pt" style:font-name-complex="FreeSans" style:font-family-complex="FreeSans" style:font-family-generic-complex="swiss"/>
+ </style:style>
+ <style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+ <style:paragraph-properties fo:margin-top="0.0835in" fo:margin-bottom="0.0835in" style:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
+ <style:text-properties fo:font-size="12pt" fo:font-style="italic" style:font-size-asian="12pt" style:font-style-asian="italic" style:font-name-complex="FreeSans" style:font-family-complex="FreeSans" style:font-family-generic-complex="swiss" style:font-size-complex="12pt" style:font-style-complex="italic"/>
+ </style:style>
+ <style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index">
+ <style:paragraph-properties text:number-lines="false" text:line-number="0"/>
+ <style:text-properties style:font-size-asian="12pt" style:font-name-complex="FreeSans" style:font-family-complex="FreeSans" style:font-family-generic-complex="swiss"/>
+ </style:style>
+ <style:style style:name="Table_20_Contents" style:display-name="Table Contents" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+ <style:paragraph-properties fo:orphans="0" fo:widows="0" text:number-lines="false" text:line-number="0"/>
+ </style:style>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+ <style:list-level-label-alignment text:label-followed-by="listtab"/>
+ </style:list-level-properties>
+ </text:outline-level-style>
+ </text:outline-style>
+ <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+ <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+ <text:linenumbering-configuration text:number-lines="false" text:offset="0.1965in" style:num-format="1" text:number-position="left" text:increment="5"/>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="Table1" style:family="table">
+ <style:table-properties style:width="6.6931in" table:align="margins"/>
+ </style:style>
+ <style:style style:name="Table1.A" style:family="table-column">
+ <style:table-column-properties style:column-width="3.3465in" style:rel-column-width="32767*"/>
+ </style:style>
+ <style:style style:name="Table1.B" style:family="table-column">
+ <style:table-column-properties style:column-width="3.3465in" style:rel-column-width="32768*"/>
+ </style:style>
+ <style:style style:name="Table1.A1" style:family="table-cell">
+ <style:table-cell-properties fo:padding="0.0194in" fo:border-left="3.05pt solid #ff0000" fo:border-right="none" fo:border-top="3.05pt solid #ff0000" fo:border-bottom="3.05pt solid #ff0000" style:writing-mode="page"/>
+ </style:style>
+ <style:style style:name="Table1.B1" style:family="table-cell">
+ <style:table-cell-properties fo:padding="0.0194in" fo:border="3.05pt solid #ff0000" style:writing-mode="page"/>
+ </style:style>
+ <style:style style:name="Table1.A2" style:family="table-cell">
+ <style:table-cell-properties fo:padding="0.0194in" fo:border-left="3.05pt solid #ff0000" fo:border-right="none" fo:border-top="none" fo:border-bottom="3.05pt solid #ff0000" style:writing-mode="page"/>
+ </style:style>
+ <style:style style:name="Table1.B2" style:family="table-cell">
+ <style:table-cell-properties fo:padding="0.0194in" fo:border-left="3.05pt solid #ff0000" fo:border-right="3.05pt solid #ff0000" fo:border-top="none" fo:border-bottom="3.05pt solid #ff0000" style:writing-mode="page"/>
+ </style:style>
+ <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard">
+ <style:text-properties officeooo:rsid="00148c28" officeooo:paragraph-rsid="00148c28"/>
+ </style:style>
+ <style:style style:name="T1" style:family="text">
+ <style:text-properties style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"/>
+ </style:style>
+ <style:style style:name="T2" style:family="text">
+ <style:text-properties style:text-overline-style="solid" style:text-overline-width="auto" style:text-overline-color="font-color"/>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="8.2681in" fo:page-height="11.6929in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.278in" style:layout-grid-ruby-height="0.139in" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0in" loext:margin-gutter="0in">
+ <style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+ </style:page-layout-properties>
+ <style:header-style/>
+ <style:footer-style/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text>
+ <text:sequence-decls>
+ <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+ <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+ </text:sequence-decls>
+ <text:p text:style-name="Standard"/>
+ <table:table table:name="Table1" table:style-name="Table1">
+ <table:table-column table:style-name="Table1.A"/>
+ <table:table-column table:style-name="Table1.B"/>
+ <table:table-row>
+ <table:table-cell table:style-name="Table1.A1" office:value-type="string">
+ <text:p text:style-name="Table_20_Contents"/>
+ </table:table-cell>
+ <table:table-cell table:style-name="Table1.B1" office:value-type="string">
+ <text:p text:style-name="Table_20_Contents"/>
+ </table:table-cell>
+ </table:table-row>
+ <table:table-row>
+ <table:table-cell table:style-name="Table1.A2" office:value-type="string">
+ <text:p text:style-name="Table_20_Contents"/>
+ </table:table-cell>
+ <table:table-cell table:style-name="Table1.B2" office:value-type="string">
+ <text:p text:style-name="Table_20_Contents"/>
+ </table:table-cell>
+ </table:table-row>
+ </table:table>
+ <text:p text:style-name="Standard"/>
+ <text:p text:style-name="Standard"/>
+ <text:p text:style-name="Standard"/>
+ <text:p text:style-name="P1">A <text:span text:style-name="T1">little</text:span> <text:span text:style-name="T2">test</text:span>.</text:p>
+ <text:p text:style-name="P1"/>
+ </office:text>
+ </office:body>
+</office:document> \ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf66597-1.odt b/vcl/qa/cppunit/pdfexport/data/tdf66597-1.odt
new file mode 100644
index 0000000000..e5cc9bc0b3
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf66597-1.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf66597-2.odt b/vcl/qa/cppunit/pdfexport/data/tdf66597-2.odt
new file mode 100644
index 0000000000..3d7b5e59cc
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf66597-2.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf66597-3.odt b/vcl/qa/cppunit/pdfexport/data/tdf66597-3.odt
new file mode 100644
index 0000000000..6db91fe81b
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf66597-3.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf84283.doc b/vcl/qa/cppunit/pdfexport/data/tdf84283.doc
new file mode 100644
index 0000000000..dc48cfaae0
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf84283.doc
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf99680-2.odt b/vcl/qa/cppunit/pdfexport/data/tdf99680-2.odt
new file mode 100644
index 0000000000..47c370004d
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf99680-2.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf99680.odt b/vcl/qa/cppunit/pdfexport/data/tdf99680.odt
new file mode 100644
index 0000000000..de12f9baa1
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf99680.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/toc-link.fodt b/vcl/qa/cppunit/pdfexport/data/toc-link.fodt
new file mode 100644
index 0000000000..ab29e88a47
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/toc-link.fodt
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:styles>
+ <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+ <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text"/>
+ <style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:default-outline-level="1" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm"/>
+ </style:style>
+ <style:style style:name="Contents_20_Heading" style:display-name="Contents Heading" style:family="paragraph" style:parent-style-name="Heading" style:class="index"/>
+ <style:style style:name="Contents_20_1" style:display-name="Contents 1" style:family="paragraph" style:parent-style-name="Index" style:class="index">
+ <style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:text-indent="0cm" style:auto-text-indent="false"/>
+ </style:style>
+ <style:style style:name="Index_20_Link" style:display-name="Index Link" style:family="text"/>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="Sect1" style:family="section"/>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="21.59cm" fo:page-height="27.94cm" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm"/>
+ </style:page-layout>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1"/>
+ </office:master-styles>
+ <office:body>
+ <office:text>
+ <text:table-of-content text:style-name="Sect1" text:protected="true" text:name="Table of Contents">
+ <text:table-of-content-source text:outline-level="10">
+ <text:index-title-template text:style-name="Contents_20_Heading">Table of Contents</text:index-title-template>
+ <text:table-of-content-entry-template text:outline-level="1" text:style-name="Contents_20_1">
+ <text:index-entry-link-start/>
+ <text:index-entry-text/>
+ <text:index-entry-link-end/>
+ </text:table-of-content-entry-template>
+ </text:table-of-content-source>
+ <text:index-body>
+ <text:index-title text:style-name="Sect1" text:name="Table of Contents_Head">
+ <text:p text:style-name="Contents_20_Heading">Table of Contents</text:p>
+ </text:index-title>
+ <text:p text:style-name="Contents_20_1"><text:a xlink:type="simple" xlink:href="#__RefHeading_Toc">Heading 1<text:tab/>1</text:a></text:p>
+ </text:index-body>
+ </text:table-of-content>
+ <text:h text:style-name="Heading_20_1" text:outline-level="1"><text:bookmark-start text:name="__RefHeading_Toc"/>Heading 1<text:bookmark-end text:name="__RefHeading_Toc"/></text:h>
+ </office:text>
+ </office:body>
+</office:document>
diff --git a/vcl/qa/cppunit/pdfexport/data/transparentshape.fodp b/vcl/qa/cppunit/pdfexport/data/transparentshape.fodp
new file mode 100644
index 0000000000..2fddc3ac0d
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/transparentshape.fodp
@@ -0,0 +1,439 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xmlns:smil="urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0" xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.presentation">
+ <office:meta><meta:creation-date>2023-10-10T13:14:06.573003910</meta:creation-date><dc:date>2023-10-10T13:16:02.094663208</dc:date><meta:editing-duration>PT1M57S</meta:editing-duration><meta:editing-cycles>1</meta:editing-cycles><meta:document-statistic meta:object-count="27"/><meta:generator>LibreOfficeDev/24.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/1aa3fb5816925ce1bc28aa17a26b8d8c2f2036f5</meta:generator></office:meta>
+ <office:font-face-decls>
+ <style:font-face style:name="DejaVu Sans" svg:font-family="'DejaVu Sans'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Lohit Devanagari" svg:font-family="'Lohit Devanagari'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Noto Sans CJK SC" svg:font-family="'Noto Sans CJK SC'" style:font-family-generic="system" style:font-pitch="variable"/>
+ <style:font-face style:name="Noto Sans1" svg:font-family="'Noto Sans'" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+ <style:default-style style:family="graphic">
+ <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:punctuation-wrap="simple" style:line-break="strict" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
+ <style:tab-stops/>
+ </style:paragraph-properties>
+ <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="24pt" fo:language="de" fo:country="DE" style:font-name-asian="DejaVu Sans" style:font-size-asian="24pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Noto Sans1" style:font-size-complex="24pt" style:language-complex="hi" style:country-complex="IN"/>
+ </style:default-style>
+ <style:style style:name="standard" style:family="graphic">
+ <style:graphic-properties draw:stroke="solid" svg:stroke-width="0cm" svg:stroke-color="#3465a4" draw:marker-start-width="0.2cm" draw:marker-start-center="false" draw:marker-end-width="0.2cm" draw:marker-end-center="false" draw:fill="solid" draw:fill-color="#729fcf" draw:textarea-horizontal-align="justify" fo:padding-top="0.125cm" fo:padding-bottom="0.125cm" fo:padding-left="0.25cm" fo:padding-right="0.25cm" fo:wrap-option="wrap" draw:shadow="hidden" draw:shadow-offset-x="0.2cm" draw:shadow-offset-y="0.2cm" draw:shadow-color="#808080">
+ <text:list-style style:name="standard">
+ <text:list-level-style-bullet text:level="1" text:bullet-char="â—">
+ <style:list-level-properties text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="2" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="0.6cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="3" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="1.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="4" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="1.8cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="5" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="2.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="6" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="3cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="7" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="3.6cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="8" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="4.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="9" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="4.8cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="10" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="5.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ </text:list-style>
+ </style:graphic-properties>
+ <style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-top="0cm" fo:margin-bottom="0cm" fo:line-height="100%" fo:text-indent="0cm"/>
+ <style:text-properties fo:font-variant="normal" fo:text-transform="none" style:use-window-font-color="true" loext:opacity="0%" style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="18pt" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:letter-kerning="true" style:font-name-asian="Noto Sans CJK SC" style:font-family-asian="'Noto Sans CJK SC'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="18pt" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-name-complex="Lohit Devanagari" style:font-family-complex="'Lohit Devanagari'" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="18pt" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-emphasize="none" style:font-relief="none" style:text-overline-style="none" style:text-overline-color="font-color"/>
+ </style:style>
+ <style:style style:name="Default-backgroundobjects" style:family="presentation">
+ <style:graphic-properties draw:textarea-horizontal-align="justify" draw:shadow="hidden" draw:shadow-offset-x="0.2cm" draw:shadow-offset-y="0.2cm" draw:shadow-color="#808080"/>
+ <style:text-properties style:letter-kerning="true"/>
+ </style:style>
+ <style:style style:name="Default-notes" style:family="presentation">
+ <style:graphic-properties draw:stroke="none" draw:fill="none"/>
+ <style:paragraph-properties fo:margin-left="0.6cm" fo:margin-right="0cm" fo:text-indent="-0.6cm"/>
+ <style:text-properties fo:font-variant="normal" fo:text-transform="none" style:use-window-font-color="true" loext:opacity="0%" style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="20pt" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:letter-kerning="true" fo:background-color="transparent" style:font-name-asian="Noto Sans CJK SC" style:font-family-asian="'Noto Sans CJK SC'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="20pt" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-name-complex="Lohit Devanagari" style:font-family-complex="'Lohit Devanagari'" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="20pt" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-emphasize="none" style:font-relief="none" style:text-overline-style="none" style:text-overline-color="font-color"/>
+ </style:style>
+ <style:style style:name="Default-outline1" style:family="presentation">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:auto-grow-height="false" draw:fit-to-size="false" style:shrink-to-fit="true">
+ <text:list-style style:name="Default-outline1">
+ <text:list-level-style-bullet text:level="1" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="0.3cm" text:min-label-width="0.9cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="2" text:bullet-char="–">
+ <style:list-level-properties text:space-before="1.5cm" text:min-label-width="0.9cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="75%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="3" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="2.8cm" text:min-label-width="0.8cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="4" text:bullet-char="–">
+ <style:list-level-properties text:space-before="4.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="75%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="5" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="5.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="6" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="6.6cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="7" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="7.8cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="8" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="9cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="9" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="10.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="10" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="11.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ </text:list-style>
+ </style:graphic-properties>
+ <style:paragraph-properties fo:margin-top="0.5cm" fo:margin-bottom="0cm"/>
+ <style:text-properties fo:font-variant="normal" fo:text-transform="none" style:use-window-font-color="true" loext:opacity="0%" style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="32pt" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:letter-kerning="true" fo:background-color="transparent" style:font-name-asian="Noto Sans CJK SC" style:font-family-asian="'Noto Sans CJK SC'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="32pt" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-name-complex="Lohit Devanagari" style:font-family-complex="'Lohit Devanagari'" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="32pt" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-emphasize="none" style:font-relief="none" style:text-overline-style="none" style:text-overline-color="font-color"/>
+ </style:style>
+ <style:style style:name="Default-title" style:family="presentation">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:textarea-vertical-align="middle">
+ <text:list-style style:name="Default-title">
+ <text:list-level-style-bullet text:level="1" text:bullet-char="â—">
+ <style:list-level-properties text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="2" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="0.6cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="3" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="1.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="4" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="1.8cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="5" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="2.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="6" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="3cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="7" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="3.6cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="8" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="4.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="9" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="4.8cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="10" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="5.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ </text:list-style>
+ </style:graphic-properties>
+ <style:paragraph-properties fo:text-align="center"/>
+ <style:text-properties fo:font-variant="normal" fo:text-transform="none" style:use-window-font-color="true" loext:opacity="0%" style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="44pt" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:letter-kerning="true" fo:background-color="transparent" style:font-name-asian="Noto Sans CJK SC" style:font-family-asian="'Noto Sans CJK SC'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="44pt" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-name-complex="Lohit Devanagari" style:font-family-complex="'Lohit Devanagari'" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="44pt" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-emphasize="none" style:font-relief="none" style:text-overline-style="none" style:text-overline-color="font-color"/>
+ </style:style>
+ <style:presentation-page-layout style:name="AL0T26">
+ <presentation:placeholder presentation:object="handout" svg:x="2.058cm" svg:y="1.743cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ <presentation:placeholder presentation:object="handout" svg:x="15.414cm" svg:y="1.743cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ <presentation:placeholder presentation:object="handout" svg:x="2.058cm" svg:y="3.612cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ <presentation:placeholder presentation:object="handout" svg:x="15.414cm" svg:y="3.612cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ <presentation:placeholder presentation:object="handout" svg:x="2.058cm" svg:y="5.481cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ <presentation:placeholder presentation:object="handout" svg:x="15.414cm" svg:y="5.481cm" svg:width="10.556cm" svg:height="-0.231cm"/>
+ </style:presentation-page-layout>
+ <style:presentation-page-layout style:name="AL1T0">
+ <presentation:placeholder presentation:object="title" svg:x="2.058cm" svg:y="1.743cm" svg:width="23.912cm" svg:height="3.507cm"/>
+ <presentation:placeholder presentation:object="subtitle" svg:x="2.058cm" svg:y="5.838cm" svg:width="23.912cm" svg:height="13.23cm"/>
+ </style:presentation-page-layout>
+ <style:presentation-page-layout style:name="AL2T1">
+ <presentation:placeholder presentation:object="title" svg:x="2.058cm" svg:y="1.743cm" svg:width="23.912cm" svg:height="3.507cm"/>
+ <presentation:placeholder presentation:object="outline" svg:x="2.058cm" svg:y="5.838cm" svg:width="23.912cm" svg:height="13.23cm"/>
+ </style:presentation-page-layout>
+ </office:styles>
+ <office:automatic-styles>
+ <style:page-layout style:name="PM0">
+ <style:page-layout-properties fo:margin-top="0cm" fo:margin-bottom="0cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:page-width="21cm" fo:page-height="29.7cm" style:print-orientation="portrait"/>
+ </style:page-layout>
+ <style:page-layout style:name="PM1">
+ <style:page-layout-properties fo:margin-top="0cm" fo:margin-bottom="0cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:page-width="28cm" fo:page-height="15.75cm" style:print-orientation="landscape"/>
+ </style:page-layout>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="border" draw:fill="none"/>
+ </style:style>
+ <style:style style:name="dp2" style:family="drawing-page">
+ <style:drawing-page-properties presentation:display-header="true" presentation:display-footer="true" presentation:display-page-number="false" presentation:display-date-time="true"/>
+ </style:style>
+ <style:style style:name="dp3" style:family="drawing-page">
+ <style:drawing-page-properties presentation:background-visible="true" presentation:background-objects-visible="true" presentation:display-footer="true" presentation:display-page-number="false" presentation:display-date-time="true"/>
+ </style:style>
+ <style:style style:name="gr1" style:family="graphic" style:parent-style-name="standard">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:fill-color="#ffffff" draw:auto-grow-height="false" fo:min-height="1.485cm" loext:decorative="false"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="gr2" style:family="graphic" style:parent-style-name="standard">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:fill-color="#ffffff" draw:textarea-vertical-align="bottom" draw:auto-grow-height="false" fo:min-height="1.485cm" loext:decorative="false"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="gr3" style:family="graphic" style:parent-style-name="standard">
+ <style:graphic-properties draw:fill="solid" draw:fill-color="#729fcf" draw:opacity="50%" draw:textarea-horizontal-align="justify" draw:textarea-vertical-align="middle" draw:auto-grow-height="false" fo:min-height="3.75cm" fo:min-width="12cm" draw:shadow-opacity="50%" loext:decorative="false"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="gr4" style:family="graphic">
+ <style:graphic-properties style:protect="size" loext:decorative="false"/>
+ </style:style>
+ <style:style style:name="pr1" style:family="presentation" style:parent-style-name="Default-backgroundobjects">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:fill-color="#ffffff" draw:auto-grow-height="false" fo:min-height="1.086cm" loext:decorative="false"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="pr2" style:family="presentation" style:parent-style-name="Default-backgroundobjects">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:fill-color="#ffffff" draw:auto-grow-height="false" fo:min-height="1.485cm" loext:decorative="false"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="pr3" style:family="presentation" style:parent-style-name="Default-backgroundobjects">
+ <style:graphic-properties draw:stroke="none" draw:fill="none" draw:fill-color="#ffffff" draw:textarea-vertical-align="bottom" draw:auto-grow-height="false" fo:min-height="1.485cm" loext:decorative="false"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="pr4" style:family="presentation" style:parent-style-name="Default-notes">
+ <style:graphic-properties draw:fill-color="#ffffff" fo:min-height="13.364cm" loext:decorative="false"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="pr5" style:family="presentation" style:parent-style-name="Default-outline1">
+ <style:graphic-properties fo:min-height="8.884cm" loext:decorative="false"/>
+ <style:paragraph-properties style:writing-mode="lr-tb"/>
+ </style:style>
+ <style:style style:name="P1" style:family="paragraph">
+ <style:text-properties fo:font-size="14pt" style:font-size-asian="14pt" style:font-size-complex="14pt"/>
+ </style:style>
+ <style:style style:name="P2" style:family="paragraph">
+ <loext:graphic-properties draw:fill="none" draw:fill-color="#ffffff"/>
+ <style:text-properties fo:font-size="14pt" style:font-size-asian="14pt" style:font-size-complex="14pt"/>
+ </style:style>
+ <style:style style:name="P3" style:family="paragraph">
+ <style:paragraph-properties fo:text-align="end"/>
+ <style:text-properties fo:font-size="14pt" style:font-size-asian="14pt" style:font-size-complex="14pt"/>
+ </style:style>
+ <style:style style:name="P4" style:family="paragraph">
+ <loext:graphic-properties draw:fill="none" draw:fill-color="#ffffff"/>
+ <style:paragraph-properties fo:text-align="end"/>
+ <style:text-properties fo:font-size="14pt" style:font-size-asian="14pt" style:font-size-complex="14pt"/>
+ </style:style>
+ <style:style style:name="P5" style:family="paragraph">
+ <style:paragraph-properties fo:text-align="center"/>
+ <style:text-properties fo:font-size="14pt" style:font-size-asian="14pt" style:font-size-complex="14pt"/>
+ </style:style>
+ <style:style style:name="P6" style:family="paragraph">
+ <loext:graphic-properties draw:fill="none" draw:fill-color="#ffffff"/>
+ <style:paragraph-properties fo:text-align="center"/>
+ <style:text-properties fo:font-size="14pt" style:font-size-asian="14pt" style:font-size-complex="14pt"/>
+ </style:style>
+ <style:style style:name="P7" style:family="paragraph">
+ <style:paragraph-properties fo:text-align="center"/>
+ </style:style>
+ <style:style style:name="P8" style:family="paragraph">
+ <loext:graphic-properties draw:fill="solid" draw:fill-color="#729fcf" draw:opacity="50%"/>
+ <style:paragraph-properties fo:text-align="center"/>
+ </style:style>
+ <style:style style:name="P9" style:family="paragraph">
+ <loext:graphic-properties draw:fill-color="#ffffff"/>
+ </style:style>
+ <text:list-style style:name="L3">
+ <text:list-level-style-bullet text:level="1" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="0.3cm" text:min-label-width="0.9cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="2" text:bullet-char="–">
+ <style:list-level-properties text:space-before="1.5cm" text:min-label-width="0.9cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="75%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="3" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="2.8cm" text:min-label-width="0.8cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="4" text:bullet-char="–">
+ <style:list-level-properties text:space-before="4.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="75%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="5" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="5.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="6" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="6.6cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="7" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="7.8cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="8" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="9cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="9" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="10.2cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ <text:list-level-style-bullet text:level="10" text:bullet-char="â—">
+ <style:list-level-properties text:space-before="11.4cm" text:min-label-width="0.6cm"/>
+ <style:text-properties fo:font-family="OpenSymbol" style:use-window-font-color="true" fo:font-size="45%"/>
+ </text:list-level-style-bullet>
+ </text:list-style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <draw:layer-set>
+ <draw:layer draw:name="layout"/>
+ <draw:layer draw:name="background"/>
+ <draw:layer draw:name="backgroundobjects"/>
+ <draw:layer draw:name="controls"/>
+ <draw:layer draw:name="measurelines"/>
+ </draw:layer-set>
+ <style:handout-master presentation:presentation-page-layout-name="AL0T26" style:page-layout-name="PM0" draw:style-name="dp2">
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="5.061cm" svg:x="1cm" svg:y="3.742cm"/>
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="5.061cm" svg:x="1cm" svg:y="12.318cm"/>
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="5.061cm" svg:x="1cm" svg:y="20.894cm"/>
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="5.061cm" svg:x="11cm" svg:y="3.742cm"/>
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="5.061cm" svg:x="11cm" svg:y="12.318cm"/>
+ <draw:page-thumbnail draw:layer="backgroundobjects" svg:width="8.999cm" svg:height="5.061cm" svg:x="11cm" svg:y="20.894cm"/>
+ <draw:frame draw:style-name="gr1" draw:text-style-name="P2" draw:layer="backgroundobjects" svg:width="9.113cm" svg:height="1.484cm" svg:x="0cm" svg:y="0cm" presentation:class="header">
+ <draw:text-box>
+ <text:p text:style-name="P1"><presentation:header/></text:p>
+ </draw:text-box>
+ </draw:frame>
+ <draw:frame draw:style-name="gr1" draw:text-style-name="P4" draw:layer="backgroundobjects" svg:width="9.113cm" svg:height="1.484cm" svg:x="11.886cm" svg:y="0cm" presentation:class="date-time">
+ <draw:text-box>
+ <text:p text:style-name="P3"><presentation:date-time/></text:p>
+ </draw:text-box>
+ </draw:frame>
+ <draw:frame draw:style-name="gr2" draw:text-style-name="P2" draw:layer="backgroundobjects" svg:width="9.113cm" svg:height="1.484cm" svg:x="0cm" svg:y="28.215cm" presentation:class="footer">
+ <draw:text-box>
+ <text:p text:style-name="P1"><presentation:footer/></text:p>
+ </draw:text-box>
+ </draw:frame>
+ <draw:frame draw:style-name="gr2" draw:text-style-name="P4" draw:layer="backgroundobjects" svg:width="9.113cm" svg:height="1.484cm" svg:x="11.886cm" svg:y="28.215cm" presentation:class="page-number">
+ <draw:text-box>
+ <text:p text:style-name="P3"><text:page-number>&lt;number&gt;</text:page-number></text:p>
+ </draw:text-box>
+ </draw:frame>
+ </style:handout-master>
+ <style:master-page style:name="Default" style:page-layout-name="PM1" draw:style-name="dp1">
+ <draw:frame presentation:style-name="Default-title" draw:layer="backgroundobjects" svg:width="25.199cm" svg:height="2.629cm" svg:x="1.4cm" svg:y="0.628cm" presentation:class="title" presentation:placeholder="true">
+ <draw:text-box/>
+ </draw:frame>
+ <draw:frame presentation:style-name="Default-outline1" draw:layer="backgroundobjects" svg:width="25.199cm" svg:height="9.134cm" svg:x="1.4cm" svg:y="3.685cm" presentation:class="outline" presentation:placeholder="true">
+ <draw:text-box/>
+ </draw:frame>
+ <draw:frame presentation:style-name="pr1" draw:text-style-name="P2" draw:layer="backgroundobjects" svg:width="6.523cm" svg:height="1.085cm" svg:x="1.4cm" svg:y="14.348cm" presentation:class="date-time">
+ <draw:text-box>
+ <text:p text:style-name="P1"><presentation:date-time/></text:p>
+ </draw:text-box>
+ </draw:frame>
+ <draw:frame presentation:style-name="pr1" draw:text-style-name="P6" draw:layer="backgroundobjects" svg:width="8.875cm" svg:height="1.085cm" svg:x="9.576cm" svg:y="14.348cm" presentation:class="footer">
+ <draw:text-box>
+ <text:p text:style-name="P5"><presentation:footer/></text:p>
+ </draw:text-box>
+ </draw:frame>
+ <draw:frame presentation:style-name="pr1" draw:text-style-name="P4" draw:layer="backgroundobjects" svg:width="6.523cm" svg:height="1.085cm" svg:x="20.076cm" svg:y="14.348cm" presentation:class="page-number">
+ <draw:text-box>
+ <text:p text:style-name="P3"><text:page-number>&lt;number&gt;</text:page-number></text:p>
+ </draw:text-box>
+ </draw:frame>
+ <presentation:notes style:page-layout-name="PM0">
+ <draw:page-thumbnail presentation:style-name="Default-title" draw:layer="backgroundobjects" svg:width="19.798cm" svg:height="11.136cm" svg:x="0.6cm" svg:y="2.257cm" presentation:class="page"/>
+ <draw:frame presentation:style-name="Default-notes" draw:layer="backgroundobjects" svg:width="16.799cm" svg:height="13.364cm" svg:x="2.1cm" svg:y="14.107cm" presentation:class="notes" presentation:placeholder="true">
+ <draw:text-box/>
+ </draw:frame>
+ <draw:frame presentation:style-name="pr2" draw:text-style-name="P2" draw:layer="backgroundobjects" svg:width="9.113cm" svg:height="1.484cm" svg:x="0cm" svg:y="0cm" presentation:class="header">
+ <draw:text-box>
+ <text:p text:style-name="P1"><presentation:header/></text:p>
+ </draw:text-box>
+ </draw:frame>
+ <draw:frame presentation:style-name="pr2" draw:text-style-name="P4" draw:layer="backgroundobjects" svg:width="9.113cm" svg:height="1.484cm" svg:x="11.886cm" svg:y="0cm" presentation:class="date-time">
+ <draw:text-box>
+ <text:p text:style-name="P3"><presentation:date-time/></text:p>
+ </draw:text-box>
+ </draw:frame>
+ <draw:frame presentation:style-name="pr3" draw:text-style-name="P2" draw:layer="backgroundobjects" svg:width="9.113cm" svg:height="1.484cm" svg:x="0cm" svg:y="28.215cm" presentation:class="footer">
+ <draw:text-box>
+ <text:p text:style-name="P1"><presentation:footer/></text:p>
+ </draw:text-box>
+ </draw:frame>
+ <draw:frame presentation:style-name="pr3" draw:text-style-name="P4" draw:layer="backgroundobjects" svg:width="9.113cm" svg:height="1.484cm" svg:x="11.886cm" svg:y="28.215cm" presentation:class="page-number">
+ <draw:text-box>
+ <text:p text:style-name="P3"><text:page-number>&lt;number&gt;</text:page-number></text:p>
+ </draw:text-box>
+ </draw:frame>
+ </presentation:notes>
+ </style:master-page>
+ </office:master-styles>
+ <office:body>
+ <office:presentation>
+ <draw:page draw:name="page1" draw:style-name="dp3" draw:master-page-name="Default" presentation:presentation-page-layout-name="AL1T0">
+ <draw:custom-shape draw:style-name="gr3" draw:text-style-name="P8" draw:layer="layout" svg:width="12.5cm" svg:height="4cm" svg:x="7.5cm" svg:y="3.257cm">
+ <text:p text:style-name="P7">Transparent shape</text:p>
+ <draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:type="rectangle" draw:enhanced-path="M 0 0 L 21600 0 21600 21600 0 21600 0 0 Z N"/>
+ </draw:custom-shape>
+ <presentation:notes draw:style-name="dp2">
+ <draw:page-thumbnail draw:style-name="gr4" draw:layer="layout" svg:width="19.798cm" svg:height="11.136cm" svg:x="0.6cm" svg:y="2.257cm" draw:page-number="1" presentation:class="page"/>
+ <draw:frame presentation:style-name="pr4" draw:text-style-name="P9" draw:layer="layout" svg:width="16.799cm" svg:height="13.364cm" svg:x="2.1cm" svg:y="14.107cm" presentation:class="notes" presentation:placeholder="true">
+ <draw:text-box/>
+ </draw:frame>
+ </presentation:notes>
+ </draw:page>
+ <draw:page draw:name="page2" draw:style-name="dp3" draw:master-page-name="Default" presentation:presentation-page-layout-name="AL2T1">
+ <draw:frame presentation:style-name="pr5" draw:layer="layout" svg:width="25.199cm" svg:height="9.134cm" svg:x="1.4cm" svg:y="3.685cm" presentation:class="outline" presentation:user-transformed="true">
+ <draw:text-box>
+ <text:list text:style-name="L3">
+ <text:list-item>
+ <text:p>Nothing transparent here</text:p>
+ </text:list-item>
+ </text:list>
+ </draw:text-box>
+ </draw:frame>
+ <presentation:notes draw:style-name="dp2">
+ <draw:page-thumbnail draw:style-name="gr4" draw:layer="layout" svg:width="19.798cm" svg:height="11.136cm" svg:x="0.6cm" svg:y="2.257cm" draw:page-number="2" presentation:class="page"/>
+ <draw:frame presentation:style-name="pr4" draw:text-style-name="P9" draw:layer="layout" svg:width="16.799cm" svg:height="13.364cm" svg:x="2.1cm" svg:y="14.107cm" presentation:class="notes" presentation:placeholder="true">
+ <draw:text-box/>
+ </draw:frame>
+ </presentation:notes>
+ </draw:page>
+ <presentation:settings presentation:mouse-visible="false"/>
+ </office:presentation>
+ </office:body>
+</office:document> \ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/data/vid.odt b/vcl/qa/cppunit/pdfexport/data/vid.odt
new file mode 100644
index 0000000000..fc34ab8f19
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/vid.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/wide_page1.fodt b/vcl/qa/cppunit/pdfexport/data/wide_page1.fodt
new file mode 100644
index 0000000000..9e7390a8e2
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/wide_page1.fodt
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:officeooo="http://openoffice.org/2009/office" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:styles>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="P1" style:family="paragraph" style:master-page-name="very_5f_wide"/>
+ <style:style style:name="P2" style:family="paragraph" style:master-page-name="Standard">
+ <style:paragraph-properties fo:break-before="page"/>
+ </style:style>
+ <style:style style:name="gr1" style:family="graphic">
+ <style:graphic-properties draw:fill="none" draw:textarea-horizontal-align="justify" draw:textarea-vertical-align="middle" draw:auto-grow-height="false" fo:min-height="257mm" fo:min-width="170mm" loext:decorative="false" style:run-through="foreground" style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="from-top" style:vertical-rel="page-content" style:horizontal-pos="from-left" style:horizontal-rel="page-content"/>
+ </style:style>
+ <style:style style:name="gr2" style:family="graphic">
+ <style:graphic-properties draw:fill="none" draw:textarea-horizontal-align="justify" draw:textarea-vertical-align="middle" draw:auto-grow-height="false" fo:min-height="170mm" fo:min-width="5060mm" loext:decorative="false" style:run-through="foreground" style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="from-top" style:vertical-rel="page-content" style:horizontal-pos="from-left" style:horizontal-rel="page-content"/>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="210mm" fo:page-height="297mm" style:print-orientation="portrait" fo:margin-top="20mm" fo:margin-bottom="20mm" fo:margin-left="20mm" fo:margin-right="20mm"/>
+ </style:page-layout>
+ <style:page-layout style:name="pm2">
+ <style:page-layout-properties fo:page-width="5100mm" fo:page-height="210mm" style:print-orientation="landscape" fo:margin-top="20mm" fo:margin-bottom="20mm" fo:margin-left="20mm" fo:margin-right="20mm"/>
+ </style:page-layout>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1"/>
+ <style:master-page style:name="very_5f_wide" style:display-name="very_wide" style:page-layout-name="pm2"/>
+ </office:master-styles>
+ <office:body>
+ <office:text>
+ <text:p text:style-name="P1"><draw:custom-shape text:anchor-type="paragraph" draw:z-index="0" draw:name="Shape 1" draw:style-name="gr2" svg:width="5060mm" svg:height="170mm" svg:x="0mm" svg:y="0mm">
+ <text:p/>
+ <draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:type="rectangle" draw:enhanced-path="M 0 0 L 21600 0 21600 21600 0 21600 0 0 Z N"/>
+ </draw:custom-shape></text:p>
+ <text:p text:style-name="P2"><draw:custom-shape text:anchor-type="paragraph" draw:z-index="1" draw:name="Shape 2" draw:style-name="gr1" svg:width="170mm" svg:height="257mm" svg:x="0mm" svg:y="0mm">
+ <text:p/>
+ <draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:type="rectangle" draw:enhanced-path="M 0 0 L 21600 0 21600 21600 0 21600 0 0 Z N"/>
+ </draw:custom-shape></text:p>
+ </office:text>
+ </office:body>
+</office:document> \ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
new file mode 100644
index 0000000000..d5515facb5
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
@@ -0,0 +1,2774 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <string_view>
+
+#include <config_fonts.h>
+
+#include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/view/XPrintable.hpp>
+
+#include <comphelper/propertysequence.hxx>
+#include <test/unoapi_test.hxx>
+#include <unotools/mediadescriptor.hxx>
+#include <unotools/tempfile.hxx>
+#include <vcl/filter/pdfdocument.hxx>
+#include <tools/zcodec.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <vcl/filter/PDFiumLibrary.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+/// Tests the PDF export filter.
+class PdfExportTest : public UnoApiTest
+{
+protected:
+ utl::MediaDescriptor aMediaDescriptor;
+
+public:
+ PdfExportTest()
+ : UnoApiTest("/vcl/qa/cppunit/pdfexport/data/")
+ {
+ }
+
+ void saveAsPDF(std::u16string_view rFile);
+ void load(std::u16string_view rFile, vcl::filter::PDFDocument& rDocument,
+ bool bUseTaggedPDF = true);
+};
+
+void PdfExportTest::saveAsPDF(std::u16string_view rFile)
+{
+ // Import the bugdoc and export as PDF.
+ loadFromFile(rFile);
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+}
+
+void PdfExportTest::load(std::u16string_view rFile, vcl::filter::PDFDocument& rDocument,
+ bool bUseTaggedPDF)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "UseTaggedPDF", uno::Any(bUseTaggedPDF) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(rFile);
+
+ // Parse the export result.
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(rDocument.Read(aStream));
+}
+
+/// Tests that a pdf image is roundtripped back to PDF as a vector format.
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106059)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ // Explicitly enable the usage of the reference XObject markup.
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "UseReferenceXObject", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"tdf106059.odt");
+
+ // Parse the export result.
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // Assert that the XObject in the page resources dictionary is a reference XObject.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjects);
+ // The page has one image.
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
+ vcl::filter::PDFObjectElement* pReferenceXObject
+ = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pReferenceXObject);
+ // The image is a reference XObject.
+ // This dictionary key was missing, so the XObject wasn't a reference one.
+ CPPUNIT_ASSERT(pReferenceXObject->Lookup("Ref"_ostr));
+}
+
+/// Tests export of PDF images without reference XObjects.
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106693)
+{
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf106693.odt", aDocument);
+
+ // Assert that the XObject in the page resources dictionary is a form XObject.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjects);
+ // The page has one image.
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
+ vcl::filter::PDFObjectElement* pXObject
+ = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pXObject);
+ // The image is a form XObject.
+ auto pSubtype = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT(pSubtype);
+ CPPUNIT_ASSERT_EQUAL("Form"_ostr, pSubtype->GetValue());
+ // This failed: UseReferenceXObject was ignored and Ref was always created.
+ CPPUNIT_ASSERT(!pXObject->Lookup("Ref"_ostr));
+
+ // Assert that the form object refers to an inner form object, not a
+ // bitmap.
+ auto pInnerResources
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"_ostr));
+ CPPUNIT_ASSERT(pInnerResources);
+ auto pInnerXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pInnerResources->LookupElement("XObject"_ostr));
+ CPPUNIT_ASSERT(pInnerXObjects);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pInnerXObjects->GetItems().size());
+ vcl::filter::PDFObjectElement* pInnerXObject
+ = pInnerXObjects->LookupObject(pInnerXObjects->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pInnerXObject);
+ auto pInnerSubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pInnerXObject->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT(pInnerSubtype);
+ // This failed: this was Image (bitmap), not Form (vector).
+ CPPUNIT_ASSERT_EQUAL("Form"_ostr, pInnerSubtype->GetValue());
+}
+
+/// Tests that text highlight from Impress is not lost.
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf105461)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf105461.odp");
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // Make sure there is a filled rectangle inside.
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ int nYellowPathCount = 0;
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPdfPageObject = pPdfPage->getObject(i);
+ if (pPdfPageObject->getType() != vcl::pdf::PDFPageObjectType::Path)
+ continue;
+
+ if (pPdfPageObject->getFillColor() == COL_YELLOW)
+ ++nYellowPathCount;
+ }
+
+ // This was 0, the page contained no yellow paths.
+ CPPUNIT_ASSERT_EQUAL(1, nYellowPathCount);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf107868)
+{
+// No need to run it on Windows, since it would use GDI printing, and not trigger PDF export
+// which is the intent of the test.
+// FIXME: Why does this fail on macOS?
+#if !defined MACOSX && !defined _WIN32
+
+ // Import the bugdoc and print to PDF.
+ loadFromFile(u"tdf107868.odt");
+ uno::Reference<view::XPrintable> xPrintable(mxComponent, uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xPrintable.is());
+ uno::Sequence<beans::PropertyValue> aOptions(comphelper::InitPropertySequence(
+ { { "FileName", uno::Any(maTempFile.GetURL()) }, { "Wait", uno::Any(true) } }));
+ xPrintable->print(aOptions);
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ if (!pPdfDocument)
+ // Printing to PDF failed in a non-interesting way, e.g. CUPS is not
+ // running, there is no printer defined, etc.
+ return;
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // Make sure there is no filled rectangle inside.
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ int nWhitePathCount = 0;
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPdfPageObject = pPdfPage->getObject(i);
+ if (pPdfPageObject->getType() != vcl::pdf::PDFPageObjectType::Path)
+ continue;
+
+ if (pPdfPageObject->getFillColor() == COL_WHITE)
+ ++nWhitePathCount;
+ }
+
+ // This was 4, the page contained 4 white paths at problematic positions.
+ CPPUNIT_ASSERT_EQUAL(0, nWhitePathCount);
+#endif
+}
+
+/// Tests that embedded video from Impress is not exported as a linked one.
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf105093)
+{
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf105093.odp", aDocument);
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ // Get page annotations.
+ auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
+ CPPUNIT_ASSERT(pAnnots);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pAnnots->GetElements().size());
+ auto pAnnotReference
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[0]);
+ CPPUNIT_ASSERT(pAnnotReference);
+ vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject();
+ CPPUNIT_ASSERT(pAnnot);
+ CPPUNIT_ASSERT_EQUAL(
+ "Annot"_ostr,
+ static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr))->GetValue());
+
+ // Get the Action -> Rendition -> MediaClip -> FileSpec.
+ auto pAction = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"_ostr));
+ CPPUNIT_ASSERT(pAction);
+ auto pRendition
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAction->LookupElement("R"_ostr));
+ CPPUNIT_ASSERT(pRendition);
+ auto pMediaClip
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pRendition->LookupElement("C"_ostr));
+ CPPUNIT_ASSERT(pMediaClip);
+ auto pFileSpec
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pMediaClip->LookupElement("D"_ostr));
+ CPPUNIT_ASSERT(pFileSpec);
+ // Make sure the filespec refers to an embedded file.
+ // This key was missing, the embedded video was handled as a linked one.
+ CPPUNIT_ASSERT(pFileSpec->LookupElement("EF"_ostr));
+}
+
+/// Tests export of non-PDF images.
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106206)
+{
+ // Import the bugdoc and export as PDF.
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf106206.odt", aDocument);
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ // The page has a stream.
+ vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
+ CPPUNIT_ASSERT(pContents);
+ vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+ // Uncompress it.
+ SvMemoryStream aUncompressed;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ rObjectStream.Seek(0);
+ aZCodec.Decompress(rObjectStream, aUncompressed);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+
+ // Make sure there is an image reference there.
+ OString aImage("/Im"_ostr);
+ auto pStart = static_cast<const char*>(aUncompressed.GetData());
+ const char* pEnd = pStart + aUncompressed.GetSize();
+ auto it = std::search(pStart, pEnd, aImage.getStr(), aImage.getStr() + aImage.getLength());
+ CPPUNIT_ASSERT(it != pEnd);
+
+ // And also that it's not an invalid one.
+ OString aInvalidImage("/Im0"_ostr);
+ it = std::search(pStart, pEnd, aInvalidImage.getStr(),
+ aInvalidImage.getStr() + aInvalidImage.getLength());
+ // This failed, object #0 was referenced.
+ CPPUNIT_ASSERT(bool(it == pEnd));
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf127217)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf127217.odt");
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // The page has one annotation.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getAnnotationCount());
+ std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnot = pPdfPage->getAnnotation(0);
+
+ // Without the fix in place, this test would have failed here
+ CPPUNIT_ASSERT(!pAnnot->hasKey("DA"_ostr));
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf109143)
+{
+ // Import the bugdoc and export as PDF.
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf109143.odt", aDocument);
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ // Get access to the only image on the only page.
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjects);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
+ vcl::filter::PDFObjectElement* pXObject
+ = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pXObject);
+
+ // Make sure it's re-compressed.
+ auto pLength = dynamic_cast<vcl::filter::PDFNumberElement*>(pXObject->Lookup("Length"_ostr));
+ CPPUNIT_ASSERT(pLength);
+ int nLength = pLength->GetValue();
+ // This failed: cropped TIFF-in-JPEG wasn't re-compressed, so crop was
+ // lost. Size was 59416, now is 11827.
+ CPPUNIT_ASSERT(nLength < 50000);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106972)
+{
+ // Import the bugdoc and export as PDF.
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf106972.odt", aDocument);
+
+ // Get access to the only form object on the only page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjects);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
+ vcl::filter::PDFObjectElement* pXObject
+ = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pXObject);
+
+ // Get access to the only image inside the form object.
+ auto pFormResources
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"_ostr));
+ CPPUNIT_ASSERT(pFormResources);
+ auto pImages = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pFormResources->LookupElement("XObject"_ostr));
+ CPPUNIT_ASSERT(pImages);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pImages->GetItems().size());
+ vcl::filter::PDFObjectElement* pImage
+ = pImages->LookupObject(pImages->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pImage);
+
+ // Assert resources of the image.
+ auto pImageResources
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pImage->Lookup("Resources"_ostr));
+ CPPUNIT_ASSERT(pImageResources);
+ // This failed: the PDF image had no Font resource.
+ CPPUNIT_ASSERT(pImageResources->LookupElement("Font"_ostr));
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106972Pdf17)
+{
+ // Import the bugdoc and export as PDF.
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf106972-pdf17.odt", aDocument);
+
+ // Get access to the only image on the only page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjects);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
+ vcl::filter::PDFObjectElement* pXObject
+ = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pXObject);
+
+ // Assert that we now attempt to preserve the original PDF data, even if
+ // the original input was PDF >= 1.4.
+ CPPUNIT_ASSERT(pXObject->Lookup("Resources"_ostr));
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testSofthyphenPos)
+{
+// No need to run it on Windows, since it would use GDI printing, and not trigger PDF export
+// which is the intent of the test.
+// FIXME: Why does this fail on macOS?
+#if !defined MACOSX && !defined _WIN32
+
+ // Import the bugdoc and print to PDF.
+ loadFromFile(u"softhyphen_pdf.odt");
+ uno::Reference<view::XPrintable> xPrintable(mxComponent, uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xPrintable.is());
+ uno::Sequence<beans::PropertyValue> aOptions(comphelper::InitPropertySequence(
+ { { "FileName", uno::Any(maTempFile.GetURL()) }, { "Wait", uno::Any(true) } }));
+ xPrintable->print(aOptions);
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ if (!pPdfDocument)
+ // Printing to PDF failed in a non-interesting way, e.g. CUPS is not
+ // running, there is no printer defined, etc.
+ return;
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // tdf#96892 incorrect fractional part of font size caused soft-hyphen to
+ // be positioned inside preceding text (incorrect = 11.1, correct = 11.05)
+
+ // there are 3 texts currently, for line 1, soft-hyphen, line 2
+ bool haveText(false);
+
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPdfPageObject = pPdfPage->getObject(i);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Text, pPdfPageObject->getType());
+ haveText = true;
+ double const size = pPdfPageObject->getFontSize();
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(11.05, size, 1E-06);
+ }
+
+ CPPUNIT_ASSERT(haveText);
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf107013)
+{
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf107013.odt", aDocument);
+
+ // Get access to the only image on the only page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjects);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
+ vcl::filter::PDFObjectElement* pXObject
+ = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
+ // This failed, the reference to the image was created, but not the image.
+ CPPUNIT_ASSERT(pXObject);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf107018)
+{
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf107018.odt", aDocument);
+
+ // Get access to the only image on the only page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjects);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
+ vcl::filter::PDFObjectElement* pXObject
+ = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pXObject);
+
+ // Get access to the form object inside the image.
+ auto pXObjectResources
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"_ostr));
+ CPPUNIT_ASSERT(pXObjectResources);
+ auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pXObjectResources->LookupElement("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjectForms);
+ vcl::filter::PDFObjectElement* pForm
+ = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pForm);
+
+ // Get access to Resources -> Font -> F1 of the form.
+ auto pFormResources
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pForm->Lookup("Resources"_ostr));
+ CPPUNIT_ASSERT(pFormResources);
+ auto pFonts = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pFormResources->LookupElement("Font"_ostr));
+ CPPUNIT_ASSERT(pFonts);
+ auto pF1Ref = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFonts->LookupElement("F1"_ostr));
+ CPPUNIT_ASSERT(pF1Ref);
+ vcl::filter::PDFObjectElement* pF1 = pF1Ref->LookupObject();
+ CPPUNIT_ASSERT(pF1);
+
+ // Check that Foo -> Bar of the font is of type Pages.
+ auto pFontFoo = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pF1->Lookup("Foo"_ostr));
+ CPPUNIT_ASSERT(pFontFoo);
+ auto pBar
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFontFoo->LookupElement("Bar"_ostr));
+ CPPUNIT_ASSERT(pBar);
+ vcl::filter::PDFObjectElement* pObject = pBar->LookupObject();
+ CPPUNIT_ASSERT(pObject);
+ auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pName);
+ // This was "XObject", reference in a nested dictionary wasn't updated when
+ // copying the page stream of a PDF image.
+ CPPUNIT_ASSERT_EQUAL("Pages"_ostr, pName->GetValue());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf148706)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf148706.odt");
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // The page has one annotation.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getAnnotationCount());
+ std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnot = pPdfPage->getAnnotation(0);
+
+ CPPUNIT_ASSERT(pAnnot->hasKey("V"_ostr));
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFObjectType::String, pAnnot->getValueType("V"_ostr));
+ OUString aV = pAnnot->getString("V"_ostr);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 1821.84
+ // - Actual :
+ CPPUNIT_ASSERT_EQUAL(OUString("1821.84"), aV);
+
+ CPPUNIT_ASSERT(pAnnot->hasKey("DV"_ostr));
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFObjectType::String, pAnnot->getValueType("DV"_ostr));
+ OUString aDV = pAnnot->getString("DV"_ostr);
+
+ CPPUNIT_ASSERT_EQUAL(OUString("1821.84"), aDV);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf107089)
+{
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf107089.odt", aDocument);
+
+ // Get access to the only image on the only page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjects);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
+ vcl::filter::PDFObjectElement* pXObject
+ = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pXObject);
+
+ // Get access to the form object inside the image.
+ auto pXObjectResources
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"_ostr));
+ CPPUNIT_ASSERT(pXObjectResources);
+ auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pXObjectResources->LookupElement("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjectForms);
+ vcl::filter::PDFObjectElement* pForm
+ = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pForm);
+
+ // Make sure 'Hello' is part of the form object's stream.
+ vcl::filter::PDFStreamElement* pStream = pForm->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream aObjectStream;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ pStream->GetMemory().Seek(0);
+ aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+ aObjectStream.Seek(0);
+ OString aHello("Hello"_ostr);
+ auto pStart = static_cast<const char*>(aObjectStream.GetData());
+ const char* pEnd = pStart + aObjectStream.GetSize();
+ auto it = std::search(pStart, pEnd, aHello.getStr(), aHello.getStr() + aHello.getLength());
+ // This failed, 'Hello' was part only a mixed compressed/uncompressed stream, i.e. garbage.
+ CPPUNIT_ASSERT(it != pEnd);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf99680)
+{
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf99680.odt", aDocument);
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ // The page 1 has a stream.
+ vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
+ CPPUNIT_ASSERT(pContents);
+ vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+
+ // Uncompress it.
+ SvMemoryStream aUncompressed;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ rObjectStream.Seek(0);
+ aZCodec.Decompress(rObjectStream, aUncompressed);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+
+ // tdf#130150 See infos in task - short: tdf#99680 was not the
+ // correct fix, so empty clip regions are valid - allow again in tests
+ // Make sure there are no empty clipping regions.
+ // OString aEmptyRegion("0 0 m h W* n");
+ // auto it = std::search(pStart, pEnd, aEmptyRegion.getStr(), aEmptyRegion.getStr() + aEmptyRegion.getLength());
+ // CPPUNIT_ASSERT_EQUAL_MESSAGE("Empty clipping region detected!", it, pEnd);
+
+ // Count save graphic state (q) and restore (Q) operators
+ // and ensure their amount is equal
+ auto pStart = static_cast<const char*>(aUncompressed.GetData());
+ const char* pEnd = pStart + aUncompressed.GetSize();
+ size_t nSaveCount = std::count(pStart, pEnd, 'q');
+ size_t nRestoreCount = std::count(pStart, pEnd, 'Q');
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Save/restore graphic state operators count mismatch!", nSaveCount,
+ nRestoreCount);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf99680_2)
+{
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf99680-2.odt", aDocument);
+
+ // For each document page
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aPages.size());
+ for (size_t nPageNr = 0; nPageNr < aPages.size(); nPageNr++)
+ {
+ // Get page contents and stream.
+ vcl::filter::PDFObjectElement* pContents = aPages[nPageNr]->LookupObject("Contents"_ostr);
+ CPPUNIT_ASSERT(pContents);
+ vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+
+ // Uncompress the stream.
+ SvMemoryStream aUncompressed;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ rObjectStream.Seek(0);
+ aZCodec.Decompress(rObjectStream, aUncompressed);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+
+ // tdf#130150 See infos in task - short: tdf#99680 was not the
+ // correct fix, so empty clip regions are valid - allow again in tests
+ // Make sure there are no empty clipping regions.
+ // OString aEmptyRegion("0 0 m h W* n");
+ // auto it = std::search(pStart, pEnd, aEmptyRegion.getStr(), aEmptyRegion.getStr() + aEmptyRegion.getLength());
+ // CPPUNIT_ASSERT_EQUAL_MESSAGE("Empty clipping region detected!", it, pEnd);
+
+ // Count save graphic state (q) and restore (Q) operators
+ // and ensure their amount is equal
+ auto pStart = static_cast<const char*>(aUncompressed.GetData());
+ const char* pEnd = pStart + aUncompressed.GetSize();
+ size_t nSaveCount = std::count(pStart, pEnd, 'q');
+ size_t nRestoreCount = std::count(pStart, pEnd, 'Q');
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Save/restore graphic state operators count mismatch!",
+ nSaveCount, nRestoreCount);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf108963)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf108963.odp");
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // Test page size (28x15.75 cm, was 1/100th mm off, tdf#112690)
+ // bad: MediaBox[0 0 793.672440944882 446.428346456693]
+ // good: MediaBox[0 0 793.700787401575 446.456692913386]
+ const double aWidth = pPdfPage->getWidth();
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(793.7, aWidth, 0.01);
+ const double aHeight = pPdfPage->getHeight();
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(446.46, aHeight, 0.01);
+
+ // Make sure there is a filled rectangle inside.
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ int nYellowPathCount = 0;
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPdfPageObject = pPdfPage->getObject(i);
+ if (pPdfPageObject->getType() != vcl::pdf::PDFPageObjectType::Path)
+ continue;
+
+ if (pPdfPageObject->getFillColor() == COL_YELLOW)
+ {
+ ++nYellowPathCount;
+ // The path described a yellow rectangle, but it was not rotated.
+ int nSegments = pPdfPageObject->getPathSegmentCount();
+ CPPUNIT_ASSERT_EQUAL(5, nSegments);
+ std::unique_ptr<vcl::pdf::PDFiumPathSegment> pSegment
+ = pPdfPageObject->getPathSegment(0);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Moveto, pSegment->getType());
+ basegfx::B2DPoint aPoint = pSegment->getPoint();
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(245, aPoint.getX(), 0.999);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(244, aPoint.getY(), 0.999);
+ CPPUNIT_ASSERT(!pSegment->isClosed());
+
+ pSegment = pPdfPageObject->getPathSegment(1);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Lineto, pSegment->getType());
+ aPoint = pSegment->getPoint();
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(275, aPoint.getX(), 0.999);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(267, aPoint.getY(), 0.999);
+ CPPUNIT_ASSERT(!pSegment->isClosed());
+
+ pSegment = pPdfPageObject->getPathSegment(2);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Lineto, pSegment->getType());
+ aPoint = pSegment->getPoint();
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(287, aPoint.getX(), 0.999);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(251, aPoint.getY(), 0.999);
+ CPPUNIT_ASSERT(!pSegment->isClosed());
+
+ pSegment = pPdfPageObject->getPathSegment(3);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Lineto, pSegment->getType());
+ aPoint = pSegment->getPoint();
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(257, aPoint.getX(), 0.999);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(228, aPoint.getY(), 0.999);
+ CPPUNIT_ASSERT(!pSegment->isClosed());
+
+ pSegment = pPdfPageObject->getPathSegment(4);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Lineto, pSegment->getType());
+ aPoint = pSegment->getPoint();
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(245, aPoint.getX(), 0.999);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(244, aPoint.getY(), 0.999);
+ CPPUNIT_ASSERT(pSegment->isClosed());
+ }
+ }
+
+ CPPUNIT_ASSERT_EQUAL(1, nYellowPathCount);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testAlternativeText)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
+
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "UseTaggedPDF", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"alternativeText.fodp");
+
+ // Parse the export result.
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ for (const auto& aElement : aDocument.GetElements())
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
+ if (!pObject)
+ continue;
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
+ if (pType && pType->GetValue() == "StructElem")
+ {
+ auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("S"_ostr));
+ if (pS && pS->GetValue() == "Figure")
+ {
+ CPPUNIT_ASSERT_EQUAL(u"This is the text alternative - This is the description"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pObject->Lookup("Alt"_ostr))));
+ }
+ }
+ }
+
+ // tdf#67866 check that Catalog contains Lang
+ auto* pCatalog = aDocument.GetCatalog();
+ CPPUNIT_ASSERT(pCatalog);
+ auto* pCatalogDictionary = pCatalog->GetDictionary();
+ CPPUNIT_ASSERT(pCatalogDictionary);
+ auto pLang = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
+ pCatalogDictionary->LookupElement("Lang"_ostr));
+ CPPUNIT_ASSERT(pLang);
+ CPPUNIT_ASSERT_EQUAL("en-US"_ostr, pLang->GetValue());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf105972)
+{
+ vcl::filter::PDFDocument aDocument;
+ // Loading fails with tagged PDF enabled
+ load(u"tdf105972.fodt", aDocument, false);
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
+ CPPUNIT_ASSERT(pAnnots);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), pAnnots->GetElements().size());
+
+ sal_uInt32 nTextFieldCount = 0;
+ for (const auto& aElement : aDocument.GetElements())
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
+ if (!pObject)
+ continue;
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("FT"_ostr));
+ if (pType && pType->GetValue() == "Tx")
+ {
+ ++nTextFieldCount;
+
+ auto pT
+ = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pObject->Lookup("T"_ostr));
+ CPPUNIT_ASSERT(pT);
+ auto pAA = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pObject->Lookup("AA"_ostr));
+ CPPUNIT_ASSERT(pAA);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), pAA->GetItems().size());
+ auto pF
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAA->LookupElement("F"_ostr));
+ CPPUNIT_ASSERT(pF);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), pF->GetItems().size());
+
+ if (nTextFieldCount == 1)
+ {
+ CPPUNIT_ASSERT_EQUAL("CurrencyField"_ostr, pT->GetValue());
+
+ auto pJS = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
+ pF->LookupElement("JS"_ostr));
+ CPPUNIT_ASSERT_EQUAL("AFNumber_Format\\(4, 0, 0, 0, \"\\\\u20ac\",true\\);"_ostr,
+ pJS->GetValue());
+ }
+ else if (nTextFieldCount == 2)
+ {
+ CPPUNIT_ASSERT_EQUAL("TimeField"_ostr, pT->GetValue());
+
+ auto pJS = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
+ pF->LookupElement("JS"_ostr));
+ CPPUNIT_ASSERT_EQUAL("AFTime_FormatEx\\(\"h:MM:sstt\"\\);"_ostr, pJS->GetValue());
+ }
+ else
+ {
+ CPPUNIT_ASSERT_EQUAL("DateField"_ostr, pT->GetValue());
+
+ auto pJS = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
+ pF->LookupElement("JS"_ostr));
+ CPPUNIT_ASSERT_EQUAL("AFDate_FormatEx\\(\"yy-mm-dd\"\\);"_ostr, pJS->GetValue());
+ }
+ }
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf148442)
+{
+ vcl::filter::PDFDocument aDocument;
+ // Loading fails with tagged PDF enabled
+ load(u"tdf148442.odt", aDocument, false);
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
+ CPPUNIT_ASSERT(pAnnots);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), pAnnots->GetElements().size());
+
+ sal_uInt32 nBtnCount = 0;
+ for (const auto& aElement : aDocument.GetElements())
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
+ if (!pObject)
+ continue;
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("FT"_ostr));
+ if (pType && pType->GetValue() == "Btn")
+ {
+ ++nBtnCount;
+ auto pT
+ = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pObject->Lookup("T"_ostr));
+ CPPUNIT_ASSERT(pT);
+ auto pAS = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("AS"_ostr));
+ CPPUNIT_ASSERT(pAS);
+
+ auto pAP = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pObject->Lookup("AP"_ostr));
+ CPPUNIT_ASSERT(pAP);
+ auto pN
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAP->LookupElement("N"_ostr));
+ CPPUNIT_ASSERT(pN);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), pN->GetItems().size());
+
+ if (nBtnCount == 1)
+ {
+ CPPUNIT_ASSERT_EQUAL("Checkbox1"_ostr, pT->GetValue());
+ CPPUNIT_ASSERT_EQUAL("Yes"_ostr, pAS->GetValue());
+ CPPUNIT_ASSERT(!pN->GetItems().count("ref"_ostr));
+ CPPUNIT_ASSERT(pN->GetItems().count("Yes"_ostr));
+ CPPUNIT_ASSERT(pN->GetItems().count("Off"_ostr));
+ }
+ else if (nBtnCount == 2)
+ {
+ CPPUNIT_ASSERT_EQUAL("Checkbox2"_ostr, pT->GetValue());
+ CPPUNIT_ASSERT_EQUAL("Yes"_ostr, pAS->GetValue());
+
+ // Without the fix in place, this test would have failed here
+ CPPUNIT_ASSERT(pN->GetItems().count("ref"_ostr));
+ CPPUNIT_ASSERT(!pN->GetItems().count("Yes"_ostr));
+ CPPUNIT_ASSERT(pN->GetItems().count("Off"_ostr));
+ }
+ else
+ {
+ CPPUNIT_ASSERT_EQUAL("Checkbox3"_ostr, pT->GetValue());
+ CPPUNIT_ASSERT_EQUAL("Off"_ostr, pAS->GetValue());
+ CPPUNIT_ASSERT(pN->GetItems().count("ref"_ostr));
+ CPPUNIT_ASSERT(!pN->GetItems().count("Yes"_ostr));
+
+ // tdf#143612: Without the fix in place, this test would have failed here
+ CPPUNIT_ASSERT(!pN->GetItems().count("Off"_ostr));
+ CPPUNIT_ASSERT(pN->GetItems().count("refOff"_ostr));
+ }
+ }
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf118244_radioButtonGroup)
+{
+ vcl::filter::PDFDocument aDocument;
+ // Loading fails with tagged PDF enabled
+ load(u"tdf118244_radioButtonGroup.odt", aDocument, false);
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ // There are eight radio buttons.
+ auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
+ CPPUNIT_ASSERT(pAnnots);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("# of radio buttons", static_cast<size_t>(8),
+ pAnnots->GetElements().size());
+
+ sal_uInt32 nRadioGroups = 0;
+ for (const auto& aElement : aDocument.GetElements())
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
+ if (!pObject)
+ continue;
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("FT"_ostr));
+ if (pType && pType->GetValue() == "Btn")
+ {
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject->Lookup("Kids"_ostr));
+ if (pKids)
+ {
+ size_t expectedSize = 2;
+ ++nRadioGroups;
+ if (nRadioGroups == 3)
+ expectedSize = 3;
+ CPPUNIT_ASSERT_EQUAL(expectedSize, pKids->GetElements().size());
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("# of radio groups", sal_uInt32(3), nRadioGroups);
+}
+
+/// Test writing ToUnicode CMAP for LTR ligatures.
+// This requires Carlito font, if it is missing the test will most likely
+// fail.
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115117_1)
+{
+#if HAVE_MORE_FONTS
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf115117-1.odt", aDocument);
+
+ vcl::filter::PDFObjectElement* pToUnicode = nullptr;
+
+ // Get access to ToUnicode of the first font
+ for (const auto& aElement : aDocument.GetElements())
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
+ if (!pObject)
+ continue;
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
+ if (pType && pType->GetValue() == "Font")
+ {
+ auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObject->Lookup("ToUnicode"_ostr));
+ CPPUNIT_ASSERT(pToUnicodeRef);
+ pToUnicode = pToUnicodeRef->LookupObject();
+ break;
+ }
+ }
+
+ CPPUNIT_ASSERT(pToUnicode);
+ auto pStream = pToUnicode->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream aObjectStream;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ pStream->GetMemory().Seek(0);
+ aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+ aObjectStream.Seek(0);
+ // The first values, <01> <02> etc., are glyph ids, they might change order
+ // if we changed how font subsets are created.
+ // The second values, <00740069> etc., are Unicode code points in hex,
+ // <00740069> is U+0074 and U+0069 i.e. "ti" which is a ligature in
+ // Carlito/Calibri. This test is failing if any of the second values
+ // changed which means we are not detecting ligatures and writing CMAP
+ // entries for them correctly. If glyph order in the subset changes then
+ // the order here will changes and the PDF has to be carefully inspected to
+ // ensure that the new values are correct before updating the string below.
+ OString aCmap("9 beginbfchar\n"
+ "<01> <00740069>\n"
+ "<02> <0020>\n"
+ "<03> <0074>\n"
+ "<04> <0065>\n"
+ "<05> <0073>\n"
+ "<06> <00660069>\n"
+ "<07> <0066006C>\n"
+ "<08> <006600660069>\n"
+ "<09> <00660066006C>\n"
+ "endbfchar"_ostr);
+ auto pStart = static_cast<const char*>(aObjectStream.GetData());
+ const char* pEnd = pStart + aObjectStream.GetSize();
+ auto it = std::search(pStart, pEnd, aCmap.getStr(), aCmap.getStr() + aCmap.getLength());
+ CPPUNIT_ASSERT(it != pEnd);
+#endif
+}
+
+/// Test writing ToUnicode CMAP for RTL ligatures.
+// This requires DejaVu Sans font, if it is missing the test will most likely
+// fail.
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115117_2)
+{
+#if HAVE_MORE_FONTS
+ // See the comments in testTdf115117_1() for explanation.
+
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf115117-2.odt", aDocument);
+
+ vcl::filter::PDFObjectElement* pToUnicode = nullptr;
+
+ for (const auto& aElement : aDocument.GetElements())
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
+ if (!pObject)
+ continue;
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
+ if (pType && pType->GetValue() == "Font")
+ {
+ auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObject->Lookup("ToUnicode"_ostr));
+ CPPUNIT_ASSERT(pToUnicodeRef);
+ pToUnicode = pToUnicodeRef->LookupObject();
+ break;
+ }
+ }
+
+ CPPUNIT_ASSERT(pToUnicode);
+ auto pStream = pToUnicode->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream aObjectStream;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ pStream->GetMemory().Seek(0);
+ aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+ aObjectStream.Seek(0);
+ OString aCmap("7 beginbfchar\n"
+ "<01> <06440627>\n"
+ "<02> <0020>\n"
+ "<03> <0641>\n"
+ "<04> <0642>\n"
+ "<05> <0648>\n"
+ "<06> <06440627>\n"
+ "<07> <0628>\n"
+ "endbfchar"_ostr);
+ auto pStart = static_cast<const char*>(aObjectStream.GetData());
+ const char* pEnd = pStart + aObjectStream.GetSize();
+ auto it = std::search(pStart, pEnd, aCmap.getStr(), aCmap.getStr() + aCmap.getLength());
+ CPPUNIT_ASSERT(it != pEnd);
+#endif
+}
+
+/// Text extracting LTR text with ligatures.
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115117_1a)
+{
+#if HAVE_MORE_FONTS
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf115117-1.odt");
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
+ CPPUNIT_ASSERT(pPdfTextPage);
+
+ // Extract the text from the page. This pdfium API is a bit higher level
+ // than we want and might apply heuristic that give false positive, but it
+ // is a good approximation in addition to the check in testTdf115117_1().
+ int nChars = pPdfTextPage->countChars();
+ CPPUNIT_ASSERT_EQUAL(44, nChars);
+
+ std::vector<sal_uInt32> aChars(nChars);
+ for (int i = 0; i < nChars; i++)
+ aChars[i] = pPdfTextPage->getUnicode(i);
+ OUString aActualText(aChars.data(), aChars.size());
+ CPPUNIT_ASSERT_EQUAL(OUString("ti ti test ti\r\nti test fi fl ffi ffl test fi"), aActualText);
+#endif
+}
+
+/// Test extracting RTL text with ligatures.
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115117_2a)
+{
+#if HAVE_MORE_FONTS
+ // See the comments in testTdf115117_1a() for explanation.
+
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf115117-2.odt");
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
+ CPPUNIT_ASSERT(pPdfTextPage);
+
+ int nChars = pPdfTextPage->countChars();
+ CPPUNIT_ASSERT_EQUAL(13, nChars);
+
+ std::vector<sal_uInt32> aChars(nChars);
+ for (int i = 0; i < nChars; i++)
+ aChars[i] = pPdfTextPage->getUnicode(i);
+ OUString aActualText(aChars.data(), aChars.size());
+ CPPUNIT_ASSERT_EQUAL(u"\u0627\u0644 \u0628\u0627\u0644 \u0648\u0642\u0641 \u0627\u0644"_ustr,
+ aActualText);
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf154549)
+{
+// FIXME: On Windows, the number of chars is 4 instead of 3
+#ifndef _WIN32
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf154549.odt");
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
+ CPPUNIT_ASSERT(pPdfTextPage);
+
+ int nChars = pPdfTextPage->countChars();
+
+ CPPUNIT_ASSERT_EQUAL(3, nChars);
+
+ std::vector<sal_uInt32> aChars(nChars);
+ for (int i = 0; i < nChars; i++)
+ aChars[i] = pPdfTextPage->getUnicode(i);
+ OUString aActualText(aChars.data(), aChars.size());
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: ִبي
+ // - Actual : بִي
+ CPPUNIT_ASSERT_EQUAL(u"\u05B4\u0628\u064A"_ustr, aActualText);
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf150846)
+{
+ // Without the fix in place, this test would have failed with
+ // An uncaught exception of type com.sun.star.io.IOException
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf150846.txt");
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
+ CPPUNIT_ASSERT(pPdfTextPage);
+
+ int nChars = pPdfTextPage->countChars();
+
+ CPPUNIT_ASSERT_EQUAL(5, nChars);
+
+ std::vector<sal_uInt32> aChars(nChars);
+ for (int i = 0; i < nChars; i++)
+ aChars[i] = pPdfTextPage->getUnicode(i);
+ OUString aActualText(aChars.data(), aChars.size());
+ CPPUNIT_ASSERT_EQUAL(u"hello"_ustr, aActualText);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf103492)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf103492.odt");
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has two page.
+ CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage1 = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage1);
+
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage1 = pPdfPage1->getTextPage();
+ CPPUNIT_ASSERT(pPdfTextPage1);
+
+ int nChars1 = pPdfTextPage1->countChars();
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 15
+ // - Actual : 18
+ CPPUNIT_ASSERT_EQUAL(15, nChars1);
+
+ std::vector<sal_uInt32> aChars1(nChars1);
+ for (int i = 0; i < nChars1; i++)
+ aChars1[i] = pPdfTextPage1->getUnicode(i);
+ OUString aActualText1(aChars1.data(), aChars1.size());
+ CPPUNIT_ASSERT_EQUAL(u"يوس٠My name is"_ustr, aActualText1);
+
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage2 = pPdfDocument->openPage(/*nIndex=*/1);
+ CPPUNIT_ASSERT(pPdfPage2);
+
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage2 = pPdfPage2->getTextPage();
+ CPPUNIT_ASSERT(pPdfTextPage2);
+
+ int nChars2 = pPdfTextPage2->countChars();
+
+ CPPUNIT_ASSERT_EQUAL(15, nChars2);
+
+ std::vector<sal_uInt32> aChars2(nChars2);
+ for (int i = 0; i < nChars2; i++)
+ aChars2[i] = pPdfTextPage2->getUnicode(i);
+ OUString aActualText2(aChars2.data(), aChars2.size());
+ CPPUNIT_ASSERT_EQUAL(u"My name is يوسÙ"_ustr, aActualText2);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf145274)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf145274.docx");
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+ auto pPage = pPdfDocument->openPage(0);
+ CPPUNIT_ASSERT(pPage);
+
+ int nPageObjectCount = pPage->getObjectCount();
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 6
+ // - Actual : 4
+ CPPUNIT_ASSERT_EQUAL(6, nPageObjectCount);
+
+ auto pTextPage = pPage->getTextPage();
+
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPage->getObject(i);
+ if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Text)
+ continue;
+
+ CPPUNIT_ASSERT_EQUAL(11.0, pPageObject->getFontSize());
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFTextRenderMode::Fill, pPageObject->getTextRenderMode());
+ CPPUNIT_ASSERT_EQUAL(COL_RED, pPageObject->getFillColor());
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf156685)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf156685.docx");
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+ auto pPage = pPdfDocument->openPage(0);
+ CPPUNIT_ASSERT(pPage);
+
+ int nPageObjectCount = pPage->getObjectCount();
+
+ CPPUNIT_ASSERT_EQUAL(9, nPageObjectCount);
+
+ auto pTextPage = pPage->getTextPage();
+
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPage->getObject(i);
+ if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Text)
+ continue;
+
+ CPPUNIT_ASSERT_EQUAL(11.0, pPageObject->getFontSize());
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFTextRenderMode::Fill, pPageObject->getTextRenderMode());
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: rgba[000000ff]
+ // - Actual : rgba[ffffffff]
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, pPageObject->getFillColor());
+ }
+}
+
+/// Test writing ToUnicode CMAP for doubly encoded glyphs.
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf66597_1)
+{
+#if HAVE_MORE_FONTS
+ // This requires Amiri font, if it is missing the test will fail.
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf66597-1.odt", aDocument);
+
+ {
+ // Get access to ToUnicode of the first font
+ vcl::filter::PDFObjectElement* pToUnicode = nullptr;
+ for (const auto& aElement : aDocument.GetElements())
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
+ if (!pObject)
+ continue;
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
+ if (pType && pType->GetValue() == "Font")
+ {
+ auto pName
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"_ostr));
+ auto aName = pName->GetValue().copy(7); // skip the subset id
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", "Amiri-Regular"_ostr, aName);
+
+ auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObject->Lookup("ToUnicode"_ostr));
+ CPPUNIT_ASSERT(pToUnicodeRef);
+ pToUnicode = pToUnicodeRef->LookupObject();
+ break;
+ }
+ }
+
+ CPPUNIT_ASSERT(pToUnicode);
+ auto pStream = pToUnicode->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream aObjectStream;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ pStream->GetMemory().Seek(0);
+ aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+ aObjectStream.Seek(0);
+ // The <01> is glyph id, <2044> is code point.
+ // The document has two characters <2044><2215><2044>, but the font
+ // reuses the same glyph for U+2044 and U+2215 so we should have a single
+ // CMAP entry for the U+2044, and U+2215 will be handled with ActualText
+ // (tested below).
+ std::string aCmap("1 beginbfchar\n"
+ "<01> <2044>\n"
+ "endbfchar");
+ std::string aData(static_cast<const char*>(aObjectStream.GetData()),
+ aObjectStream.GetSize());
+ auto nPos = aData.find(aCmap);
+ CPPUNIT_ASSERT(nPos != std::string::npos);
+ }
+
+ {
+ auto aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+ // Get page contents and stream.
+ auto pContents = aPages[0]->LookupObject("Contents"_ostr);
+ CPPUNIT_ASSERT(pContents);
+ auto pStream = pContents->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ auto& rObjectStream = pStream->GetMemory();
+
+ // Uncompress the stream.
+ SvMemoryStream aUncompressed;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ rObjectStream.Seek(0);
+ aZCodec.Decompress(rObjectStream, aUncompressed);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+
+ // Make sure the expected ActualText is present.
+ std::string aData(static_cast<const char*>(aUncompressed.GetData()),
+ aUncompressed.GetSize());
+
+ std::string aActualText("/Span<</ActualText<");
+ size_t nCount = 0;
+ size_t nPos = 0;
+ while ((nPos = aData.find(aActualText, nPos)) != std::string::npos)
+ {
+ nCount++;
+ nPos += aActualText.length();
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("The should be one ActualText entry!", static_cast<size_t>(1),
+ nCount);
+
+ aActualText = "/Span<</ActualText<FEFF2215>>>";
+ nPos = aData.find(aActualText);
+ CPPUNIT_ASSERT_MESSAGE("ActualText not found!", nPos != std::string::npos);
+ }
+#endif
+}
+
+/// Test writing ActualText for RTL many to one glyph to Unicode mapping.
+// This requires Reem Kufi font, if it is missing the test will fail.
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf66597_2)
+{
+#if HAVE_MORE_FONTS
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf66597-2.odt", aDocument);
+
+ {
+ // Get access to ToUnicode of the first font
+ vcl::filter::PDFObjectElement* pToUnicode = nullptr;
+ for (const auto& aElement : aDocument.GetElements())
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
+ if (!pObject)
+ continue;
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
+ if (pType && pType->GetValue() == "Font")
+ {
+ auto pName
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"_ostr));
+ auto aName = pName->GetValue().copy(7); // skip the subset id
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", "ReemKufi-Regular"_ostr,
+ aName);
+
+ auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObject->Lookup("ToUnicode"_ostr));
+ CPPUNIT_ASSERT(pToUnicodeRef);
+ pToUnicode = pToUnicodeRef->LookupObject();
+ break;
+ }
+ }
+
+ CPPUNIT_ASSERT(pToUnicode);
+ auto pStream = pToUnicode->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream aObjectStream;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ pStream->GetMemory().Seek(0);
+ aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+ aObjectStream.Seek(0);
+ std::string aCmap("8 beginbfchar\n"
+ "<02> <0632>\n"
+ "<03> <0020>\n"
+ "<04> <0648>\n"
+ "<05> <0647>\n"
+ "<06> <062F>\n"
+ "<08> <062C>\n"
+ "<0A> <0628>\n"
+ "<0C> <0623>\n"
+ "endbfchar");
+ std::string aData(static_cast<const char*>(aObjectStream.GetData()),
+ aObjectStream.GetSize());
+ auto nPos = aData.find(aCmap);
+ CPPUNIT_ASSERT(nPos != std::string::npos);
+ }
+
+ {
+ auto aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+ // Get page contents and stream.
+ auto pContents = aPages[0]->LookupObject("Contents"_ostr);
+ CPPUNIT_ASSERT(pContents);
+ auto pStream = pContents->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ auto& rObjectStream = pStream->GetMemory();
+
+ // Uncompress the stream.
+ SvMemoryStream aUncompressed;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ rObjectStream.Seek(0);
+ aZCodec.Decompress(rObjectStream, aUncompressed);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+
+ // Make sure the expected ActualText is present.
+ std::string aData(static_cast<const char*>(aUncompressed.GetData()),
+ aUncompressed.GetSize());
+
+ std::vector<std::string> aCodes({ "0632", "062C", "0628", "0623" });
+ std::string aActualText("/Span<</ActualText<");
+ size_t nCount = 0;
+ size_t nPos = 0;
+ while ((nPos = aData.find(aActualText, nPos)) != std::string::npos)
+ {
+ nCount++;
+ nPos += aActualText.length();
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Number of ActualText entries does not match!", aCodes.size(),
+ nCount);
+
+ for (const auto& aCode : aCodes)
+ {
+ aActualText = "/Span<</ActualText<FEFF" + aCode + ">>>";
+ nPos = aData.find(aActualText);
+ CPPUNIT_ASSERT_MESSAGE("ActualText not found for " + aCode, nPos != std::string::npos);
+ }
+ }
+#endif
+}
+
+/// Test writing ActualText for LTR many to one glyph to Unicode mapping.
+// This requires Gentium Basic font, if it is missing the test will fail.
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf66597_3)
+{
+#if HAVE_MORE_FONTS
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf66597-3.odt", aDocument);
+
+ {
+ // Get access to ToUnicode of the first font
+ vcl::filter::PDFObjectElement* pToUnicode = nullptr;
+ for (const auto& aElement : aDocument.GetElements())
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
+ if (!pObject)
+ continue;
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
+ if (pType && pType->GetValue() == "Font")
+ {
+ auto pName
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"_ostr));
+ auto aName = pName->GetValue().copy(7); // skip the subset id
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", "GentiumBasic"_ostr, aName);
+
+ auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObject->Lookup("ToUnicode"_ostr));
+ CPPUNIT_ASSERT(pToUnicodeRef);
+ pToUnicode = pToUnicodeRef->LookupObject();
+ break;
+ }
+ }
+
+ CPPUNIT_ASSERT(pToUnicode);
+ auto pStream = pToUnicode->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream aObjectStream;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ pStream->GetMemory().Seek(0);
+ aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+ aObjectStream.Seek(0);
+ std::string aCmap("2 beginbfchar\n"
+ "<01> <1ECB0331030B>\n"
+ "<05> <0020>\n"
+ "endbfchar");
+ std::string aData(static_cast<const char*>(aObjectStream.GetData()),
+ aObjectStream.GetSize());
+ auto nPos = aData.find(aCmap);
+ CPPUNIT_ASSERT(nPos != std::string::npos);
+ }
+
+ {
+ auto aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+ // Get page contents and stream.
+ auto pContents = aPages[0]->LookupObject("Contents"_ostr);
+ CPPUNIT_ASSERT(pContents);
+ auto pStream = pContents->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ auto& rObjectStream = pStream->GetMemory();
+
+ // Uncompress the stream.
+ SvMemoryStream aUncompressed;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ rObjectStream.Seek(0);
+ aZCodec.Decompress(rObjectStream, aUncompressed);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+
+ // Make sure the expected ActualText is present.
+ std::string aData(static_cast<const char*>(aUncompressed.GetData()),
+ aUncompressed.GetSize());
+
+ std::string aActualText("/Span<</ActualText<FEFF1ECB0331030B>>>");
+ size_t nCount = 0;
+ size_t nPos = 0;
+ while ((nPos = aData.find(aActualText, nPos)) != std::string::npos)
+ {
+ nCount++;
+ nPos += aActualText.length();
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Number of ActualText entries does not match!",
+ static_cast<size_t>(4), nCount);
+ }
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf105954)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence(
+ { { "ReduceImageResolution", uno::Any(true) },
+ { "MaxImageResolution", uno::Any(static_cast<sal_Int32>(300)) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"tdf105954.odt");
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // There is a single image on the page.
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ CPPUNIT_ASSERT_EQUAL(1, nPageObjectCount);
+
+ // Check width of the image.
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(/*index=*/0);
+ Size aMeta = pPageObject->getImageSize(*pPdfPage);
+ // This was 2000, i.e. the 'reduce to 300 DPI' request was ignored.
+ // This is now around 238 (228 on macOS).
+ CPPUNIT_ASSERT_LESS(static_cast<tools::Long>(250), aMeta.getWidth());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157679)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
+ saveAsPDF(u"tdf157679.pptx");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 3
+ // - Actual : 5
+ CPPUNIT_ASSERT_EQUAL(3, pPdfPage->getObjectCount());
+
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ // Check there are not Text objects
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
+ CPPUNIT_ASSERT(pPageObject->getType() != vcl::pdf::PDFPageObjectType::Text);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf128445)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
+ saveAsPDF(u"tdf128445.odp");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 7
+ // - Actual : 6
+ CPPUNIT_ASSERT_EQUAL(7, pPdfPage->getObjectCount());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf128630)
+{
+ // FIXME: the DPI check should be removed when either (1) the test is fixed to work with
+ // non-default DPI; or (2) unit tests on Windows are made to use svp VCL plugin.
+ if (!IsDefaultDPI())
+ return;
+
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
+ saveAsPDF(u"tdf128630.odp");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+ // Assert the size of the only bitmap on the page.
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
+ if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
+ continue;
+
+ std::unique_ptr<vcl::pdf::PDFiumBitmap> pBitmap = pPageObject->getImageBitmap();
+ CPPUNIT_ASSERT(pBitmap);
+ int nWidth = pBitmap->getWidth();
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 466
+ // - Actual : 289
+ // i.e. the rotated + scaled arrow was more thin than it should be.
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(466, nWidth, 1);
+ int nHeight = pBitmap->getHeight();
+ CPPUNIT_ASSERT_EQUAL(nWidth, nHeight);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106702)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf106702.odt");
+
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has two pages.
+ CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
+
+ // First page already has the correct image position.
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+ int nExpected = 0;
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
+ if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
+ continue;
+
+ // Top, but upside down.
+ nExpected = pPageObject->getBounds().getMaxY();
+ break;
+ }
+
+ // Second page had an incorrect image position.
+ pPdfPage = pPdfDocument->openPage(/*nIndex=*/1);
+ CPPUNIT_ASSERT(pPdfPage);
+ int nActual = 0;
+ nPageObjectCount = pPdfPage->getObjectCount();
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
+ if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
+ continue;
+
+ // Top, but upside down.
+ nActual = pPageObject->getBounds().getMaxY();
+ break;
+ }
+
+ // This failed, vertical pos is 818 points, was 1674 (outside visible page
+ // bounds).
+ CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf113143)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
+ uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
+ { "ExportNotesPages", uno::Any(true) },
+ // ReduceImageResolution is on by default and that hides the bug we
+ // want to test.
+ { "ReduceImageResolution", uno::Any(false) },
+ // Set a custom PDF version.
+ { "SelectPdfVersion", uno::Any(static_cast<sal_Int32>(16)) },
+ }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"tdf113143.odp");
+
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has two pages.
+ CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
+
+ // First has the original (larger) image.
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+ int nLarger = 0;
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
+ if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
+ continue;
+
+ nLarger = pPageObject->getBounds().getWidth();
+ break;
+ }
+
+ // Second page has the scaled (smaller) image.
+ pPdfPage = pPdfDocument->openPage(/*nIndex=*/1);
+ CPPUNIT_ASSERT(pPdfPage);
+ int nSmaller = 0;
+ nPageObjectCount = pPdfPage->getObjectCount();
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
+ if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
+ continue;
+
+ nSmaller = pPageObject->getBounds().getWidth();
+ break;
+ }
+
+ // This failed, both were 319, now nSmaller is 169.
+ CPPUNIT_ASSERT_LESS(nLarger, nSmaller);
+
+ // The following check used to fail in the past, header was "%PDF-1.5":
+ maMemory.Seek(0);
+ OString aExpectedHeader("%PDF-1.6"_ostr);
+ OString aHeader(read_uInt8s_ToOString(maMemory, aExpectedHeader.getLength()));
+ CPPUNIT_ASSERT_EQUAL(aExpectedHeader, aHeader);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testForcePoint71)
+{
+ // I just care it doesn't crash
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"forcepoint71.key");
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testForcePoint80)
+{
+ // printing asserted in SwCellFrame::FindStartEndOfRowSpanCell
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"forcepoint80-1.rtf");
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testForcePoint3)
+{
+ // printing asserted in SwFrame::GetNextSctLeaf()
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"flowframe_null_ptr_deref.sample");
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf84283)
+{
+ // Without the fix in place, this test would have crashed
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf84283.doc");
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115262)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("calc_pdf_Export");
+ saveAsPDF(u"tdf115262.ods");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ CPPUNIT_ASSERT_EQUAL(8, pPdfDocument->getPageCount());
+
+ // Get the 6th page.
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/5);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // Look up the position of the first image and the 400th row.
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ int nFirstImageTop = 0;
+ int nRowTop = 0;
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
+ // Top, but upside down.
+ float fTop = pPageObject->getBounds().getMaxY();
+
+ if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Image)
+ {
+ nFirstImageTop = fTop;
+ }
+ else if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
+ {
+ OUString sText = pPageObject->getText(pTextPage);
+ if (sText == "400")
+ nRowTop = fTop;
+ }
+ }
+ // Make sure that the top of the "400" is below the top of the image (in
+ // bottom-right-corner-based PDF coordinates).
+ // This was: expected less than 144, actual is 199.
+ CPPUNIT_ASSERT_LESS(nFirstImageTop, nRowTop);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf121962)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf121962.odt");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+ // Get the first page
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
+
+ // Make sure the table sum is displayed as "0", not faulty expression.
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
+ if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Text)
+ continue;
+ OUString sText = pPageObject->getText(pTextPage);
+ CPPUNIT_ASSERT(sText != "** Expression is faulty **");
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf139065)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf139065.odt");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 15
+ // - Actual : 6
+ CPPUNIT_ASSERT_EQUAL(15, pPdfPage->getObjectCount());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"tdf157816.fodt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ vcl::filter::PDFObjectElement* pDocument(nullptr);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject1)
+ continue;
+ auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
+ if (pType1 && pType1->GetValue() == "StructElem")
+ {
+ auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
+ if (pS1 && pS1->GetValue() == "Document")
+ {
+ pDocument = pObject1;
+ }
+ }
+ }
+ CPPUNIT_ASSERT(pDocument);
+
+ auto pKidsD = dynamic_cast<vcl::filter::PDFArrayElement*>(pDocument->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKidsD);
+ // assume there are no MCID ref at this level
+ auto pKidsDv = pKidsD->GetElements();
+ auto pRefKidD2 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsDv[2]);
+ CPPUNIT_ASSERT(pRefKidD2);
+ auto pObjectD2 = pRefKidD2->LookupObject();
+ CPPUNIT_ASSERT(pObjectD2);
+ auto pTypeD2 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD2->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD2->GetValue());
+ auto pSD2 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD2->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Text#20body"_ostr, pSD2->GetValue());
+
+ auto pKidsD2 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD2->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKidsD2);
+ auto pKidsD2v = pKidsD2->GetElements();
+ auto pRefKidD20 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD2v[0]);
+ // MCID for text
+ CPPUNIT_ASSERT(!pRefKidD20);
+ auto pRefKidD21 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD2v[1]);
+ // MCID for text
+ CPPUNIT_ASSERT(!pRefKidD21);
+
+ auto pRefKidD22 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD2v[2]);
+ CPPUNIT_ASSERT(pRefKidD22);
+ auto pObjectD22 = pRefKidD22->LookupObject();
+ CPPUNIT_ASSERT(pObjectD22);
+ auto pTypeD22 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD22->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD22->GetValue());
+ auto pSD22 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD22->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD22->GetValue());
+ {
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD22->Lookup("K"_ostr));
+ auto nMCID(0);
+ auto nRef(0);
+ for (size_t i = 0; i < pKids->GetElements().size(); ++i)
+ {
+ auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
+ auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
+ if (pNum)
+ {
+ ++nMCID;
+ }
+ if (pObjR)
+ {
+ ++nRef;
+ auto pOType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
+ auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObjR->LookupElement("Obj"_ostr));
+ auto pAnnot = pAnnotRef->LookupObject();
+ auto pAType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
+ auto pASubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
+ auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pAnnot->Lookup("Contents"_ostr));
+ CPPUNIT_ASSERT_EQUAL(
+ u"Error: Reference source not found"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
+ auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
+ pAnnot->Lookup("StructParent"_ostr));
+ CPPUNIT_ASSERT(pStructParent); // every link must have it!
+ auto pARect
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
+ CPPUNIT_ASSERT(pARect);
+ const auto& rElements = pARect->GetElements();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
+ const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
+ CPPUNIT_ASSERT(pNumL);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(95.143, pNumL->GetValue(), 1e-3);
+ const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
+ CPPUNIT_ASSERT(pNumT);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(674.589, pNumT->GetValue(), 1e-3);
+ const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
+ CPPUNIT_ASSERT(pNumR);
+ // this changed to the end of the text, not the start of the fly
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(187.207, pNumR->GetValue(), 1e-3);
+ const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
+ CPPUNIT_ASSERT(pNumB);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(688.389, pNumB->GetValue(), 1e-3);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+ }
+
+ auto pRefKidD23 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD2v[3]);
+ CPPUNIT_ASSERT(pRefKidD23);
+ auto pObjectD23 = pRefKidD23->LookupObject();
+ CPPUNIT_ASSERT(pObjectD23);
+ auto pTypeD23 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD23->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD23->GetValue());
+ auto pSD23 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD23->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD23->GetValue());
+ {
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD23->Lookup("K"_ostr));
+ auto nMCID(0);
+ auto nRef(0);
+ for (size_t i = 0; i < pKids->GetElements().size(); ++i)
+ {
+ auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
+ auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
+ if (pNum)
+ {
+ ++nMCID;
+ }
+ if (pObjR)
+ {
+ ++nRef;
+ auto pOType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
+ auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObjR->LookupElement("Obj"_ostr));
+ auto pAnnot = pAnnotRef->LookupObject();
+ auto pAType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
+ auto pASubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
+ auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pAnnot->Lookup("Contents"_ostr));
+ CPPUNIT_ASSERT_EQUAL(
+ u"Error: Reference source not found"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
+ auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
+ pAnnot->Lookup("StructParent"_ostr));
+ CPPUNIT_ASSERT(pStructParent); // every link must have it!
+ auto pARect
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
+ CPPUNIT_ASSERT(pARect);
+ const auto& rElements = pARect->GetElements();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
+ const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
+ CPPUNIT_ASSERT(pNumL);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3);
+ const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
+ CPPUNIT_ASSERT(pNumT);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(660.789, pNumT->GetValue(), 1e-3);
+ const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
+ CPPUNIT_ASSERT(pNumR);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(146.157, pNumR->GetValue(), 1e-3);
+ const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
+ CPPUNIT_ASSERT(pNumB);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(674.589, pNumB->GetValue(), 1e-3);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+ }
+
+ auto pRefKidD24 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD2v[4]);
+ CPPUNIT_ASSERT(pRefKidD24);
+ auto pObjectD24 = pRefKidD24->LookupObject();
+ CPPUNIT_ASSERT(pObjectD24);
+ auto pTypeD24 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD24->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD24->GetValue());
+ auto pSD24 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD24->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD24->GetValue());
+ {
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD24->Lookup("K"_ostr));
+ auto nMCID(0);
+ auto nRef(0);
+ for (size_t i = 0; i < pKids->GetElements().size(); ++i)
+ {
+ auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
+ auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
+ if (pNum)
+ {
+ ++nMCID;
+ }
+ if (pObjR)
+ {
+ ++nRef;
+ auto pOType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
+ auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObjR->LookupElement("Obj"_ostr));
+ auto pAnnot = pAnnotRef->LookupObject();
+ auto pAType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
+ auto pASubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
+ auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pAnnot->Lookup("Contents"_ostr));
+ CPPUNIT_ASSERT_EQUAL(
+ u"Error: Reference source not found"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
+ auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
+ pAnnot->Lookup("StructParent"_ostr));
+ CPPUNIT_ASSERT(pStructParent); // every link must have it!
+ auto pARect
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
+ CPPUNIT_ASSERT(pARect);
+ const auto& rElements = pARect->GetElements();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
+ const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
+ CPPUNIT_ASSERT(pNumL);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(146.093, pNumL->GetValue(), 1e-3);
+ const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
+ CPPUNIT_ASSERT(pNumT);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(660.789, pNumT->GetValue(), 1e-3);
+ const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
+ CPPUNIT_ASSERT(pNumR);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(179.457, pNumR->GetValue(), 1e-3);
+ const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
+ CPPUNIT_ASSERT(pNumB);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(674.589, pNumB->GetValue(), 1e-3);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+ }
+
+ auto pRefKidD25 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD2v[5]);
+ CPPUNIT_ASSERT(pRefKidD25);
+ auto pObjectD25 = pRefKidD25->LookupObject();
+ CPPUNIT_ASSERT(pObjectD25);
+ auto pTypeD25 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD25->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD25->GetValue());
+ auto pSD25 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD25->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD25->GetValue());
+ {
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD25->Lookup("K"_ostr));
+ auto nMCID(0);
+ auto nRef(0);
+ for (size_t i = 0; i < pKids->GetElements().size(); ++i)
+ {
+ auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
+ auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
+ if (pNum)
+ {
+ ++nMCID;
+ }
+ if (pObjR)
+ {
+ ++nRef;
+ auto pOType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
+ auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObjR->LookupElement("Obj"_ostr));
+ auto pAnnot = pAnnotRef->LookupObject();
+ auto pAType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
+ auto pASubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
+ auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pAnnot->Lookup("Contents"_ostr));
+ CPPUNIT_ASSERT_EQUAL(
+ u"Error: Reference source not found"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
+ auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
+ pAnnot->Lookup("StructParent"_ostr));
+ CPPUNIT_ASSERT(pStructParent); // every link must have it!
+ auto pARect
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
+ CPPUNIT_ASSERT(pARect);
+ const auto& rElements = pARect->GetElements();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
+ const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
+ CPPUNIT_ASSERT(pNumL);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3);
+ const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
+ CPPUNIT_ASSERT(pNumT);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(646.989, pNumT->GetValue(), 1e-3);
+ const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
+ CPPUNIT_ASSERT(pNumR);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(174.757, pNumR->GetValue(), 1e-3);
+ const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
+ CPPUNIT_ASSERT(pNumB);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(660.789, pNumB->GetValue(), 1e-3);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+ }
+
+ auto pRefKidD26 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD2v[6]);
+ CPPUNIT_ASSERT(pRefKidD26);
+ auto pObjectD26 = pRefKidD26->LookupObject();
+ CPPUNIT_ASSERT(pObjectD26);
+ auto pTypeD26 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD26->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD26->GetValue());
+ auto pSD26 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD26->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD26->GetValue());
+ {
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD26->Lookup("K"_ostr));
+ auto nMCID(0);
+ auto nRef(0);
+ for (size_t i = 0; i < pKids->GetElements().size(); ++i)
+ {
+ auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
+ auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
+ if (pNum)
+ {
+ ++nMCID;
+ }
+ if (pObjR)
+ {
+ ++nRef;
+ auto pOType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
+ auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObjR->LookupElement("Obj"_ostr));
+ auto pAnnot = pAnnotRef->LookupObject();
+ auto pAType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
+ auto pASubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
+ auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pAnnot->Lookup("Contents"_ostr));
+ CPPUNIT_ASSERT_EQUAL(
+ u"Error: Reference source not found"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
+ auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
+ pAnnot->Lookup("StructParent"_ostr));
+ CPPUNIT_ASSERT(pStructParent); // every link must have it!
+ auto pARect
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
+ CPPUNIT_ASSERT(pARect);
+ const auto& rElements = pARect->GetElements();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
+ const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
+ CPPUNIT_ASSERT(pNumL);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3);
+ const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
+ CPPUNIT_ASSERT(pNumT);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(633.189, pNumT->GetValue(), 1e-3);
+ const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
+ CPPUNIT_ASSERT(pNumR);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(86.807, pNumR->GetValue(), 1e-3);
+ const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
+ CPPUNIT_ASSERT(pNumB);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(646.989, pNumB->GetValue(), 1e-3);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+ }
+
+ auto pRefKidD27 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD2v[7]);
+ // MCID for text
+ CPPUNIT_ASSERT(!pRefKidD27);
+
+ // the problem was that in addition to the 5 links with SE there were 3 more
+ auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
+ CPPUNIT_ASSERT(pAnnots);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(5), pAnnots->GetElements().size());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"LinkWithFly.fodt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ vcl::filter::PDFObjectElement* pDocument(nullptr);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject1)
+ continue;
+ auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
+ if (pType1 && pType1->GetValue() == "StructElem")
+ {
+ auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
+ if (pS1 && pS1->GetValue() == "Document")
+ {
+ pDocument = pObject1;
+ }
+ }
+ }
+ CPPUNIT_ASSERT(pDocument);
+
+ auto pKidsD = dynamic_cast<vcl::filter::PDFArrayElement*>(pDocument->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKidsD);
+ // assume there are no MCID ref at this level
+ auto pKidsDv = pKidsD->GetElements();
+ auto pRefKidD0 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsDv[0]);
+ CPPUNIT_ASSERT(pRefKidD0);
+ auto pObjectD0 = pRefKidD0->LookupObject();
+ CPPUNIT_ASSERT(pObjectD0);
+ auto pTypeD0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD0->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD0->GetValue());
+ auto pSD0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD0->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pSD0->GetValue());
+
+ auto pKidsD0 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD0->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKidsD0);
+ auto pKidsD0v = pKidsD0->GetElements();
+
+ auto pRefKidD00 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD0v[0]);
+ CPPUNIT_ASSERT(pRefKidD00);
+ auto pObjectD00 = pRefKidD00->LookupObject();
+ CPPUNIT_ASSERT(pObjectD00);
+ auto pTypeD00 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD00->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD00->GetValue());
+ auto pSD00 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD00->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD00->GetValue());
+ {
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD00->Lookup("K"_ostr));
+ auto nMCID(0);
+ auto nRef(0);
+ for (size_t i = 0; i < pKids->GetElements().size(); ++i)
+ {
+ auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
+ auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
+ if (pNum)
+ {
+ ++nMCID;
+ }
+ if (pObjR)
+ {
+ ++nRef;
+ auto pOType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
+ auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObjR->LookupElement("Obj"_ostr));
+ auto pAnnot = pAnnotRef->LookupObject();
+ auto pAType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
+ auto pASubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
+ auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pAnnot->Lookup("Contents"_ostr));
+ CPPUNIT_ASSERT_EQUAL(
+ u"https://www.mozilla.org/en-US/firefox/119.0/releasenotes/"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
+ auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
+ pAnnot->Lookup("StructParent"_ostr));
+ CPPUNIT_ASSERT(pStructParent); // every link must have it!
+ auto pARect
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
+ CPPUNIT_ASSERT(pARect);
+ const auto& rElements = pARect->GetElements();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
+ const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
+ CPPUNIT_ASSERT(pNumL);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3);
+ const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
+ CPPUNIT_ASSERT(pNumT);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumT->GetValue(), 1e-3);
+ const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
+ CPPUNIT_ASSERT(pNumR);
+ // this changed to the end of the text, not the start of the fly
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(191.707, pNumR->GetValue(), 1e-3);
+ const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
+ CPPUNIT_ASSERT(pNumB);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(785.189, pNumB->GetValue(), 1e-3);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+ }
+
+ auto pRefKidD01 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD0v[1]);
+ CPPUNIT_ASSERT(pRefKidD01);
+ auto pObjectD01 = pRefKidD01->LookupObject();
+ CPPUNIT_ASSERT(pObjectD01);
+ auto pTypeD01 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD01->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD01->GetValue());
+ auto pSD01 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD01->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD01->GetValue());
+ {
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD01->Lookup("K"_ostr));
+ auto nMCID(0);
+ auto nRef(0);
+ for (size_t i = 0; i < pKids->GetElements().size(); ++i)
+ {
+ auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
+ auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
+ if (pNum)
+ {
+ ++nMCID;
+ }
+ if (pObjR)
+ {
+ ++nRef;
+ auto pOType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
+ auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObjR->LookupElement("Obj"_ostr));
+ auto pAnnot = pAnnotRef->LookupObject();
+ auto pAType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
+ auto pASubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
+ auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pAnnot->Lookup("Contents"_ostr));
+ CPPUNIT_ASSERT_EQUAL(
+ u"https://www.mozilla.org/en-US/firefox/119.0/releasenotes/"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
+ auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
+ pAnnot->Lookup("StructParent"_ostr));
+ CPPUNIT_ASSERT(pStructParent); // every link must have it!
+ auto pARect
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
+ CPPUNIT_ASSERT(pARect);
+ const auto& rElements = pARect->GetElements();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
+ const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
+ CPPUNIT_ASSERT(pNumL);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(387.843, pNumL->GetValue(), 1e-3);
+ const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
+ CPPUNIT_ASSERT(pNumT);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumT->GetValue(), 1e-3);
+ const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
+ CPPUNIT_ASSERT(pNumR);
+ // this changed to the end of the text, not the start of the fly
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(534.407, pNumR->GetValue(), 1e-3);
+ const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
+ CPPUNIT_ASSERT(pNumB);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(785.189, pNumB->GetValue(), 1e-3);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+ }
+
+ auto pRefKidD02 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD0v[2]);
+ CPPUNIT_ASSERT(pRefKidD02);
+ auto pObjectD02 = pRefKidD02->LookupObject();
+ CPPUNIT_ASSERT(pObjectD02);
+ auto pTypeD02 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD02->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD02->GetValue());
+ auto pSD02 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD02->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Figure"_ostr, pSD02->GetValue());
+
+ auto pRefKidD1 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsDv[1]);
+ CPPUNIT_ASSERT(pRefKidD1);
+ auto pObjectD1 = pRefKidD1->LookupObject();
+ CPPUNIT_ASSERT(pObjectD1);
+ auto pTypeD1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD1->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD1->GetValue());
+ auto pSD1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD1->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pSD1->GetValue());
+
+ auto pKidsD1 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD1->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKidsD1);
+ auto pKidsD1v = pKidsD1->GetElements();
+
+ auto pRefKidD10 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD1v[0]);
+ CPPUNIT_ASSERT(pRefKidD10);
+ auto pObjectD10 = pRefKidD10->LookupObject();
+ CPPUNIT_ASSERT(pObjectD10);
+ auto pTypeD10 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD10->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD10->GetValue());
+ auto pSD10 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD10->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD10->GetValue());
+ {
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD10->Lookup("K"_ostr));
+ auto nMCID(0);
+ auto nRef(0);
+ for (size_t i = 0; i < pKids->GetElements().size(); ++i)
+ {
+ auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
+ auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
+ if (pNum)
+ {
+ ++nMCID;
+ }
+ if (pObjR)
+ {
+ ++nRef;
+ auto pOType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
+ auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObjR->LookupElement("Obj"_ostr));
+ auto pAnnot = pAnnotRef->LookupObject();
+ auto pAType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
+ auto pASubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
+ auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pAnnot->Lookup("Contents"_ostr));
+ CPPUNIT_ASSERT_EQUAL(
+ u"https://www.mozilla.org/en-US/firefox/118.0/releasenotes/"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
+ auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
+ pAnnot->Lookup("StructParent"_ostr));
+ CPPUNIT_ASSERT(pStructParent); // every link must have it!
+ auto pARect
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
+ CPPUNIT_ASSERT(pARect);
+ const auto& rElements = pARect->GetElements();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
+ const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
+ CPPUNIT_ASSERT(pNumL);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3);
+ const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
+ CPPUNIT_ASSERT(pNumT);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(757.589, pNumT->GetValue(), 1e-3);
+ const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
+ CPPUNIT_ASSERT(pNumR);
+ // this changed to the end of the text, not the start of the fly
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(191.707, pNumR->GetValue(), 1e-3);
+ const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
+ CPPUNIT_ASSERT(pNumB);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumB->GetValue(), 1e-3);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+ }
+
+ auto pRefKidD11 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD1v[1]);
+ CPPUNIT_ASSERT(pRefKidD11);
+ auto pObjectD11 = pRefKidD11->LookupObject();
+ CPPUNIT_ASSERT(pObjectD11);
+ auto pTypeD11 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD11->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD11->GetValue());
+ auto pSD11 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD11->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD11->GetValue());
+ {
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD11->Lookup("K"_ostr));
+ auto nMCID(0);
+ auto nRef(0);
+ for (size_t i = 0; i < pKids->GetElements().size(); ++i)
+ {
+ auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
+ auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
+ if (pNum)
+ {
+ ++nMCID;
+ }
+ if (pObjR)
+ {
+ ++nRef;
+ auto pOType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
+ auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObjR->LookupElement("Obj"_ostr));
+ auto pAnnot = pAnnotRef->LookupObject();
+ auto pAType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
+ auto pASubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
+ auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pAnnot->Lookup("Contents"_ostr));
+ CPPUNIT_ASSERT_EQUAL(
+ u"https://www.mozilla.org/en-US/firefox/118.0/releasenotes/"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
+ auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
+ pAnnot->Lookup("StructParent"_ostr));
+ CPPUNIT_ASSERT(pStructParent); // every link must have it!
+ auto pARect
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
+ CPPUNIT_ASSERT(pARect);
+ const auto& rElements = pARect->GetElements();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
+ const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
+ CPPUNIT_ASSERT(pNumL);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(387.843, pNumL->GetValue(), 1e-3);
+ const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
+ CPPUNIT_ASSERT(pNumT);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(757.589, pNumT->GetValue(), 1e-3);
+ const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
+ CPPUNIT_ASSERT(pNumR);
+ // this changed to the end of the text, not the start of the fly
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(534.407, pNumR->GetValue(), 1e-3);
+ const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
+ CPPUNIT_ASSERT(pNumB);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumB->GetValue(), 1e-3);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+ }
+
+ // the problem was that in addition to the 4 links with SE there was 1 more
+ auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
+ CPPUNIT_ASSERT(pAnnots);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), pAnnots->GetElements().size());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115967)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf115967.odt");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+ // Get the first page
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
+
+ // Make sure the elements inside a formula in a RTL document are exported
+ // LTR ( m=750abc ) and not RTL ( m=057cba )
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ OUString sText;
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
+ if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Text)
+ continue;
+ OUString sChar = pPageObject->getText(pTextPage);
+ sText += o3tl::trim(sChar);
+ }
+ CPPUNIT_ASSERT_EQUAL(OUString("m=750abc"), sText);
+}
+
+} // end anonymous namespace
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
new file mode 100644
index 0000000000..d2811f32fb
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
@@ -0,0 +1,4858 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <string_view>
+
+#include <config_fonts.h>
+#include <osl/process.h>
+
+#include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/text/XDocumentIndexesSupplier.hpp>
+#include <com/sun/star/util/XRefreshable.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/text/XTextDocument.hpp>
+#include <com/sun/star/document/XFilter.hpp>
+#include <com/sun/star/document/XExporter.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+
+#include <comphelper/scopeguard.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <test/unoapi_test.hxx>
+#include <unotools/mediadescriptor.hxx>
+#include <unotools/tempfile.hxx>
+#include <vcl/filter/pdfdocument.hxx>
+#include <tools/zcodec.hxx>
+#include <tools/XmlWalker.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <unotools/streamwrap.hxx>
+#include <rtl/math.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <vcl/filter/PDFiumLibrary.hxx>
+#include <comphelper/propertyvalue.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+/// Tests the PDF export filter.
+class PdfExportTest2 : public UnoApiTest
+{
+protected:
+ utl::MediaDescriptor aMediaDescriptor;
+
+public:
+ PdfExportTest2()
+ : UnoApiTest("/vcl/qa/cppunit/pdfexport/data/")
+ {
+ }
+
+ void saveAsPDF(std::u16string_view rFile);
+ void load(std::u16string_view rFile, vcl::filter::PDFDocument& rDocument,
+ bool bUseTaggedPDF = true);
+};
+
+void PdfExportTest2::saveAsPDF(std::u16string_view rFile)
+{
+ // Import the bugdoc and export as PDF.
+ loadFromFile(rFile);
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+}
+
+void PdfExportTest2::load(std::u16string_view rFile, vcl::filter::PDFDocument& rDocument,
+ bool bUseTaggedPDF)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "UseTaggedPDF", uno::Any(bUseTaggedPDF) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(rFile);
+
+ // Parse the export result.
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(rDocument.Read(aStream));
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf124272)
+{
+ // Import the bugdoc and export as PDF.
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf124272.odt", aDocument);
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ // The page has a stream.
+ vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
+ CPPUNIT_ASSERT(pContents);
+ vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+ // Uncompress it.
+ SvMemoryStream aUncompressed;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ rObjectStream.Seek(0);
+ aZCodec.Decompress(rObjectStream, aUncompressed);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+
+ OString aBitmap("Q q 299.899 782.189 m\n"
+ "55.2 435.889 l 299.899 435.889 l 299.899 782.189 l\n"
+ "h"_ostr);
+
+ auto pStart = static_cast<const char*>(aUncompressed.GetData());
+ const char* pEnd = pStart + aUncompressed.GetSize();
+ auto it = std::search(pStart, pEnd, aBitmap.getStr(), aBitmap.getStr() + aBitmap.getLength());
+ CPPUNIT_ASSERT(it != pEnd);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf121615)
+{
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf121615.odt", aDocument);
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ // Get access to the only image on the only page.
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjects);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
+ vcl::filter::PDFObjectElement* pXObject
+ = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pXObject);
+ vcl::filter::PDFStreamElement* pStream = pXObject->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+
+ // Load the embedded image.
+ rObjectStream.Seek(0);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic;
+ sal_uInt16 format;
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, u"import", rObjectStream,
+ GRFILTER_FORMAT_DONTKNOW, &format);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+
+ // The image should be grayscale 8bit JPEG.
+ sal_uInt16 jpegFormat = rFilter.GetImportFormatNumberForShortName(JPG_SHORTNAME);
+ CPPUNIT_ASSERT(jpegFormat != GRFILTER_FORMAT_NOTFOUND);
+ CPPUNIT_ASSERT_EQUAL(jpegFormat, format);
+ BitmapEx aBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(200), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(300), aBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat());
+ // tdf#121615 was caused by broken handling of data width with 8bit color,
+ // so the test image has some black in the bottomright corner, check it's there
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(0, 299));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(199, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, aBitmap.GetPixelColor(199, 299));
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf141171)
+{
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf141171.odt", aDocument);
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ // Get access to the only image on the only page.
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjects);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
+ vcl::filter::PDFObjectElement* pXObject
+ = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pXObject);
+ vcl::filter::PDFStreamElement* pStream = pXObject->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+
+ // Load the embedded image.
+ rObjectStream.Seek(0);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic;
+ sal_uInt16 format;
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, u"import", rObjectStream,
+ GRFILTER_FORMAT_DONTKNOW, &format);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+
+ // The image should be grayscale 8bit JPEG.
+ sal_uInt16 jpegFormat = rFilter.GetImportFormatNumberForShortName(JPG_SHORTNAME);
+ CPPUNIT_ASSERT(jpegFormat != GRFILTER_FORMAT_NOTFOUND);
+ CPPUNIT_ASSERT_EQUAL(jpegFormat, format);
+ BitmapEx aBitmap = aGraphic.GetBitmapEx();
+ Size aSize = aBitmap.GetSizePixel();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(878), aSize.Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(127), aSize.Height());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat());
+
+ for (tools::Long nX = 0; nX < aSize.Width(); ++nX)
+ {
+ for (tools::Long nY = 0; nY < aSize.Height(); ++nY)
+ {
+ // Check all pixels in the image are white
+ // Without the fix in place, this test would have failed with
+ // - Expected: Color: R:255 G:255 B:255 A:0
+ // - Actual : Color: R:0 G:0 B:0 A:0
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(nX, nY));
+ }
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf129085)
+{
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf129085.docx", aDocument);
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ // Get access to the only image on the only page.
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
+
+ // Without the fix in place, this test would have failed here
+ CPPUNIT_ASSERT(pXObjects);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
+ vcl::filter::PDFObjectElement* pXObject
+ = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pXObject);
+ vcl::filter::PDFStreamElement* pStream = pXObject->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+
+ // Load the embedded image.
+ rObjectStream.Seek(0);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic;
+ sal_uInt16 format;
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, u"import", rObjectStream,
+ GRFILTER_FORMAT_DONTKNOW, &format);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+
+ sal_uInt16 jpegFormat = rFilter.GetImportFormatNumberForShortName(JPG_SHORTNAME);
+ CPPUNIT_ASSERT(jpegFormat != GRFILTER_FORMAT_NOTFOUND);
+ CPPUNIT_ASSERT_EQUAL(jpegFormat, format);
+ BitmapEx aBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(tools::Long(884), aBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(925), aBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, aBitmap.getPixelFormat());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTocLink)
+{
+ // Load the Writer document.
+ loadFromFile(u"toc-link.fodt");
+
+ // Update the ToC.
+ uno::Reference<text::XDocumentIndexesSupplier> xDocumentIndexesSupplier(mxComponent,
+ uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xDocumentIndexesSupplier.is());
+
+ uno::Reference<util::XRefreshable> xToc(
+ xDocumentIndexesSupplier->getDocumentIndexes()->getByIndex(0), uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xToc.is());
+
+ xToc->refresh();
+
+ // Save as PDF.
+ save("writer_pdf_Export");
+
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // Ensure there is a link on the first page (in the ToC).
+ // Without the accompanying fix in place, this test would have failed, as the page contained no
+ // links.
+ CPPUNIT_ASSERT(pPdfPage->hasLinks());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testReduceSmallImage)
+{
+ // Load the Writer document.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"reduce-small-image.fodt");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+ CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount());
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(0);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Image, pPageObject->getType());
+
+ // Make sure we don't scale down a tiny bitmap.
+ std::unique_ptr<vcl::pdf::PDFiumBitmap> pBitmap = pPageObject->getImageBitmap();
+ CPPUNIT_ASSERT(pBitmap);
+ int nWidth = pBitmap->getWidth();
+ int nHeight = pBitmap->getHeight();
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 16
+ // - Actual : 6
+ // i.e. the image was scaled down to 300 DPI, even if it had tiny size.
+ CPPUNIT_ASSERT_EQUAL(16, nWidth);
+ CPPUNIT_ASSERT_EQUAL(16, nHeight);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf114256)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("calc_pdf_Export");
+ saveAsPDF(u"tdf114256.ods");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 13
+ // - Actual : 0
+ CPPUNIT_ASSERT_EQUAL(13, pPdfPage->getObjectCount());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf150931)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("calc_pdf_Export");
+ saveAsPDF(u"tdf150931.ods");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ // Without the fix in place, this test would have failed with
+ // - Expected: 15
+ // - Actual : 16
+ CPPUNIT_ASSERT_EQUAL(16, nPageObjectCount);
+
+ int nYellowPathCount = 0;
+ int nBlackPathCount = 0;
+ int nGrayPathCount = 0;
+ int nRedPathCount = 0;
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPdfPageObject = pPdfPage->getObject(i);
+ if (pPdfPageObject->getType() != vcl::pdf::PDFPageObjectType::Path)
+ continue;
+
+ int nSegments = pPdfPageObject->getPathSegmentCount();
+ CPPUNIT_ASSERT_EQUAL(5, nSegments);
+
+ if (pPdfPageObject->getFillColor() == COL_YELLOW)
+ ++nYellowPathCount;
+ else if (pPdfPageObject->getFillColor() == COL_BLACK)
+ ++nBlackPathCount;
+ else if (pPdfPageObject->getFillColor() == COL_GRAY)
+ ++nGrayPathCount;
+ else if (pPdfPageObject->getFillColor() == COL_LIGHTRED)
+ ++nRedPathCount;
+ }
+
+ CPPUNIT_ASSERT_EQUAL(3, nYellowPathCount);
+ CPPUNIT_ASSERT_EQUAL(3, nRedPathCount);
+ CPPUNIT_ASSERT_EQUAL(3, nGrayPathCount);
+ CPPUNIT_ASSERT_EQUAL(3, nBlackPathCount);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf147027)
+{
+ // FIXME: the DPI check should be removed when either (1) the test is fixed to work with
+ // non-default DPI; or (2) unit tests on Windows are made to use svp VCL plugin.
+ if (!IsDefaultDPI())
+ return;
+
+ // Load the Calc document.
+ aMediaDescriptor["FilterName"] <<= OUString("calc_pdf_Export");
+ saveAsPDF(u"tdf147027.ods");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 778
+ // - Actual : 40
+ CPPUNIT_ASSERT_EQUAL(778, pPdfPage->getObjectCount());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf135346)
+{
+ // Load the Calc document.
+ aMediaDescriptor["FilterName"] <<= OUString("calc_pdf_Export");
+ saveAsPDF(u"tdf135346.ods");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 56
+ // - Actual : 0
+ CPPUNIT_ASSERT_EQUAL(56, pPdfPage->getObjectCount());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf147164)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
+ saveAsPDF(u"tdf147164.odp");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/1);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 22
+ // - Actual : 16
+ CPPUNIT_ASSERT_EQUAL(22, pPdfPage->getObjectCount());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testReduceImage)
+{
+ // Load the Writer document.
+ loadFromFile(u"reduce-image.fodt");
+
+ // Save as PDF.
+ uno::Reference<css::lang::XMultiServiceFactory> xFactory = getMultiServiceFactory();
+ uno::Reference<document::XFilter> xFilter(
+ xFactory->createInstance("com.sun.star.document.PDFFilter"), uno::UNO_QUERY);
+ uno::Reference<document::XExporter> xExporter(xFilter, uno::UNO_QUERY);
+ xExporter->setSourceDocument(mxComponent);
+
+ SvFileStream aOutputStream(maTempFile.GetURL(), StreamMode::WRITE);
+ uno::Reference<io::XOutputStream> xOutputStream(new utl::OStreamWrapper(aOutputStream));
+
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "ReduceImageResolution", uno::Any(false) } }));
+
+ // This is intentionally in an "unlucky" order, output stream comes before filter data.
+ uno::Sequence<beans::PropertyValue> aDescriptor(comphelper::InitPropertySequence({
+ { "FilterName", uno::Any(OUString("writer_pdf_Export")) },
+ { "OutputStream", uno::Any(xOutputStream) },
+ { "FilterData", uno::Any(aFilterData) },
+ }));
+ xFilter->filter(aDescriptor);
+ aOutputStream.Close();
+
+ // Parse the PDF: get the image.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+ CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount());
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(0);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Image, pPageObject->getType());
+
+ // Make sure we don't scale down a bitmap.
+ std::unique_ptr<vcl::pdf::PDFiumBitmap> pBitmap = pPageObject->getImageBitmap();
+ CPPUNIT_ASSERT(pBitmap);
+ int nWidth = pBitmap->getWidth();
+ int nHeight = pBitmap->getHeight();
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 160
+ // - Actual : 6
+ // i.e. the image was scaled down even with ReduceImageResolution=false.
+ CPPUNIT_ASSERT_EQUAL(160, nWidth);
+ CPPUNIT_ASSERT_EQUAL(160, nHeight);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testLinkWrongPage)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
+ saveAsPDF(u"link-wrong-page.odp");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has 2 pages.
+ CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
+
+ // First page should have 1 link (2nd slide, 1st was hidden).
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // Without the accompanying fix in place, this test would have failed, as the link of the first
+ // page went to the second page due to the hidden first slide.
+ CPPUNIT_ASSERT(pPdfPage->hasLinks());
+
+ // Second page should have no links (3rd slide).
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage2 = pPdfDocument->openPage(/*nIndex=*/1);
+ CPPUNIT_ASSERT(pPdfPage2);
+ CPPUNIT_ASSERT(!pPdfPage2->hasLinks());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testLinkWrongPagePartial)
+{
+ // Given a Draw document with 3 pages, a link on the 2nd page:
+ // When exporting that the 2nd and 3rd page to pdf:
+ uno::Sequence<beans::PropertyValue> aFilterData = {
+ comphelper::makePropertyValue("PageRange", OUString("2-3")),
+ };
+ aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export");
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"link-wrong-page-partial.odg");
+
+ // Then make sure the we have a link on the 1st page, but not on the 2nd one:
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+ // Without the accompanying fix in place, this test would have failed, as the link was on the
+ // 2nd page instead.
+ CPPUNIT_ASSERT(pPdfPage->hasLinks());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage2 = pPdfDocument->openPage(/*nIndex=*/1);
+ CPPUNIT_ASSERT(pPdfPage2);
+ CPPUNIT_ASSERT(!pPdfPage2->hasLinks());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testPageRange)
+{
+ // Given a document with 3 pages:
+ // When exporting that document to PDF, skipping the first page:
+ aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export");
+ aMediaDescriptor["FilterOptions"]
+ <<= OUString("{\"PageRange\":{\"type\":\"string\",\"value\":\"2-\"}}");
+ saveAsPDF(u"link-wrong-page-partial.odg");
+
+ // Then make sure the resulting PDF has 2 pages:
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 2
+ // - Actual : 3
+ // i.e. FilterOptions was ignored.
+ CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testLargePage)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export");
+ saveAsPDF(u"6m-wide.odg");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has 1 page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ // Check the value (not the unit) of the page size.
+ basegfx::B2DSize aSize = pPdfDocument->getPageSize(0);
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 8503.94
+ // - Actual : 17007.875
+ // i.e. the value for 600 cm was larger than the 14 400 limit set in the spec.
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(8503.94, aSize.getWidth(), 0.01);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testPdfImageResourceInlineXObjectRef)
+{
+ // Create an empty document.
+ mxComponent = loadFromDesktop("private:factory/swriter");
+ uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
+ uno::Reference<text::XText> xText = xTextDocument->getText();
+ uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
+
+ // Insert the PDF image.
+ uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xGraphicObject(
+ xFactory->createInstance("com.sun.star.text.TextGraphicObject"), uno::UNO_QUERY);
+ OUString aURL = createFileURL(u"pdf-image-resource-inline-xobject-ref.pdf");
+ xGraphicObject->setPropertyValue("GraphicURL", uno::Any(aURL));
+ uno::Reference<drawing::XShape> xShape(xGraphicObject, uno::UNO_QUERY);
+ xShape->setSize(awt::Size(1000, 1000));
+ uno::Reference<text::XTextContent> xTextContent(xGraphicObject, uno::UNO_QUERY);
+ xText->insertTextContent(xCursor->getStart(), xTextContent, /*bAbsorb=*/false);
+
+ // Save as PDF.
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+ // Make sure that the page -> form -> form has a child image.
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+ CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount());
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(0);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pPageObject->getType());
+ // 2: white background and the actual object.
+ CPPUNIT_ASSERT_EQUAL(2, pPageObject->getFormObjectCount());
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pFormObject = pPageObject->getFormObject(1);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pFormObject->getType());
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 1
+ // - Actual : 0
+ // i.e. the sub-form was missing its image.
+ CPPUNIT_ASSERT_EQUAL(1, pFormObject->getFormObjectCount());
+
+ // Check if the inner form object (original page object in the pdf image) has the correct
+ // rotation.
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pInnerFormObject = pFormObject->getFormObject(0);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pInnerFormObject->getType());
+ CPPUNIT_ASSERT_EQUAL(1, pInnerFormObject->getFormObjectCount());
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pImage = pInnerFormObject->getFormObject(0);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Image, pImage->getType());
+ basegfx::B2DHomMatrix aMat = pInnerFormObject->getMatrix();
+ basegfx::B2DTuple aScale;
+ basegfx::B2DTuple aTranslate;
+ double fRotate = 0;
+ double fShearX = 0;
+ aMat.decompose(aScale, aTranslate, fRotate, fShearX);
+ int nRotateDeg = basegfx::rad2deg(fRotate);
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: -90
+ // - Actual : 0
+ // i.e. rotation was lost on pdf export.
+ CPPUNIT_ASSERT_EQUAL(-90, nRotateDeg);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testDefaultVersion)
+{
+ // Create an empty document.
+ mxComponent = loadFromDesktop("private:factory/swriter");
+
+ // Save as PDF.
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ int nFileVersion = pPdfDocument->getFileVersion();
+ CPPUNIT_ASSERT_EQUAL(17, nFileVersion);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testVersion15)
+{
+ // Create an empty document.
+ mxComponent = loadFromDesktop("private:factory/swriter");
+
+ // Save as PDF.
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence(
+ { { "SelectPdfVersion", uno::Any(static_cast<sal_Int32>(15)) } }));
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ int nFileVersion = pPdfDocument->getFileVersion();
+ CPPUNIT_ASSERT_EQUAL(15, nFileVersion);
+}
+
+// Check round-trip of importing and exporting the PDF with PDFium filter,
+// which imports the PDF document as multiple PDFs as graphic object.
+// Each page in the document has one PDF graphic object which content is
+// the corresponding page in the PDF. When such a document is exported,
+// the PDF graphic gets embedded into the exported PDF document (as a
+// Form XObject).
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testMultiPagePDF)
+{
+// setenv only works on unix based systems
+#ifndef _WIN32
+ // We need to enable PDFium import (and make sure to disable after the test)
+ bool bResetEnvVar = false;
+ if (getenv("LO_IMPORT_USE_PDFIUM") == nullptr)
+ {
+ bResetEnvVar = true;
+ setenv("LO_IMPORT_USE_PDFIUM", "1", false);
+ }
+ comphelper::ScopeGuard aPDFiumEnvVarGuard([&]() {
+ if (bResetEnvVar)
+ unsetenv("LO_IMPORT_USE_PDFIUM");
+ });
+
+ // Load the PDF and save as PDF
+ vcl::filter::PDFDocument aDocument;
+ load(u"SimpleMultiPagePDF.pdf", aDocument);
+
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aPages.size());
+
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
+ CPPUNIT_ASSERT(pResources);
+
+ auto pXObjects
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjects);
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3),
+ pXObjects->GetItems().size()); // 3 PDFs as Form XObjects
+
+ std::vector<OString> rIDs;
+ for (auto const& rPair : pXObjects->GetItems())
+ {
+ rIDs.push_back(rPair.first);
+ }
+
+ // Let's check the embedded PDF pages - just make sure the size differs,
+ // which should indicate we don't have 3 times the same page.
+
+ { // embedded PDF page 1
+ vcl::filter::PDFObjectElement* pXObject1 = pXObjects->LookupObject(rIDs[0]);
+ CPPUNIT_ASSERT(pXObject1);
+ CPPUNIT_ASSERT_EQUAL("Im21"_ostr, rIDs[0]);
+
+ auto pSubtype1
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject1->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT(pSubtype1);
+ CPPUNIT_ASSERT_EQUAL("Form"_ostr, pSubtype1->GetValue());
+
+ auto pXObjectResources
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject1->Lookup("Resources"_ostr));
+ CPPUNIT_ASSERT(pXObjectResources);
+ auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pXObjectResources->LookupElement("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjectForms);
+ vcl::filter::PDFObjectElement* pForm
+ = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pForm);
+
+ vcl::filter::PDFStreamElement* pStream = pForm->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+ rObjectStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ // Just check that the size of the page stream is what is expected.
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(230), rObjectStream.remainingSize());
+ }
+
+ { // embedded PDF page 2
+ vcl::filter::PDFObjectElement* pXObject2 = pXObjects->LookupObject(rIDs[1]);
+ CPPUNIT_ASSERT(pXObject2);
+ CPPUNIT_ASSERT_EQUAL("Im27"_ostr, rIDs[1]);
+
+ auto pSubtype2
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject2->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT(pSubtype2);
+ CPPUNIT_ASSERT_EQUAL("Form"_ostr, pSubtype2->GetValue());
+
+ auto pXObjectResources
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject2->Lookup("Resources"_ostr));
+ CPPUNIT_ASSERT(pXObjectResources);
+ auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pXObjectResources->LookupElement("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjectForms);
+ vcl::filter::PDFObjectElement* pForm
+ = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pForm);
+
+ vcl::filter::PDFStreamElement* pStream = pForm->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+ rObjectStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ // Just check that the size of the page stream is what is expected
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(309), rObjectStream.remainingSize());
+ }
+
+ { // embedded PDF page 3
+ vcl::filter::PDFObjectElement* pXObject3 = pXObjects->LookupObject(rIDs[2]);
+ CPPUNIT_ASSERT(pXObject3);
+ CPPUNIT_ASSERT_EQUAL("Im5"_ostr, rIDs[2]);
+
+ auto pSubtype3
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject3->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT(pSubtype3);
+ CPPUNIT_ASSERT_EQUAL("Form"_ostr, pSubtype3->GetValue());
+
+ auto pXObjectResources
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject3->Lookup("Resources"_ostr));
+ CPPUNIT_ASSERT(pXObjectResources);
+ auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pXObjectResources->LookupElement("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjectForms);
+ vcl::filter::PDFObjectElement* pForm
+ = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pForm);
+
+ vcl::filter::PDFStreamElement* pStream = pForm->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+ rObjectStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ // Just check that the size of the page stream is what is expected
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(193), rObjectStream.remainingSize());
+ }
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testFormFontName)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"form-font-name.odt");
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // The page has one annotation.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getAnnotationCount());
+ std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnot = pPdfPage->getAnnotation(0);
+
+ // Examine the default appearance.
+ CPPUNIT_ASSERT(pAnnot->hasKey("DA"_ostr));
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFObjectType::String, pAnnot->getValueType("DA"_ostr));
+ OUString aDA = pAnnot->getString("DA"_ostr);
+
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 0 0 0 rg /TiRo 12 Tf
+ // - Actual : 0 0 0 rg /F2 12 Tf
+ // i.e. Liberation Serif was exposed as a form font as-is, without picking the closest built-in
+ // font.
+ CPPUNIT_ASSERT_EQUAL(OUString("0 0 0 rg /TiRo 12 Tf"), aDA);
+}
+
+// Check we don't have duplicated objects when we reexport the PDF multiple
+// times or the size will exponentially increase over time.
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testReexportPDF)
+{
+// setenv only works on unix based systems
+#ifndef _WIN32
+ // We need to enable PDFium import (and make sure to disable after the test)
+ bool bResetEnvVar = false;
+ if (getenv("LO_IMPORT_USE_PDFIUM") == nullptr)
+ {
+ bResetEnvVar = true;
+ setenv("LO_IMPORT_USE_PDFIUM", "1", false);
+ }
+ comphelper::ScopeGuard aPDFiumEnvVarGuard([&]() {
+ if (bResetEnvVar)
+ unsetenv("LO_IMPORT_USE_PDFIUM");
+ });
+
+ // Load the PDF and save as PDF
+ vcl::filter::PDFDocument aDocument;
+ load(u"PDFWithImages.pdf", aDocument);
+
+ // Assert that the XObject in the page resources dictionary is a reference XObject.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+
+ // The document has 2 pages.
+ CPPUNIT_ASSERT_EQUAL(size_t(2), aPages.size());
+
+ // PAGE 1
+ {
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
+ CPPUNIT_ASSERT(pResources);
+
+ auto pXObjects
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjects);
+
+ std::vector<OString> rIDs;
+ for (auto const& rPair : pXObjects->GetItems())
+ rIDs.push_back(rPair.first);
+
+ CPPUNIT_ASSERT_EQUAL(size_t(2), rIDs.size());
+
+ std::vector<int> aBitmapRefs1;
+ std::vector<int> aBitmapRefs2;
+
+ {
+ // FORM object 1
+ OString aID = rIDs[0];
+ CPPUNIT_ASSERT_EQUAL("Im14"_ostr, aID);
+ vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(aID);
+ CPPUNIT_ASSERT(pXObject);
+
+ auto pSubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT(pSubtype);
+ CPPUNIT_ASSERT_EQUAL("Form"_ostr, pSubtype->GetValue());
+
+ auto pInnerResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pXObject->Lookup("Resources"_ostr));
+ CPPUNIT_ASSERT(pInnerResources);
+ auto pInnerXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pInnerResources->LookupElement("XObject"_ostr));
+ CPPUNIT_ASSERT(pInnerXObjects);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pInnerXObjects->GetItems().size());
+ OString aInnerObjectID = pInnerXObjects->GetItems().begin()->first;
+ CPPUNIT_ASSERT_EQUAL("Im15"_ostr, aInnerObjectID);
+
+ vcl::filter::PDFObjectElement* pInnerXObject
+ = pInnerXObjects->LookupObject(aInnerObjectID);
+ CPPUNIT_ASSERT(pInnerXObject);
+
+ auto pInnerSubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pInnerXObject->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT(pInnerSubtype);
+ CPPUNIT_ASSERT_EQUAL("Form"_ostr, pInnerSubtype->GetValue());
+
+ auto pInnerInnerResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pInnerXObject->Lookup("Resources"_ostr));
+ CPPUNIT_ASSERT(pInnerInnerResources);
+ auto pInnerInnerXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pInnerInnerResources->LookupElement("XObject"_ostr));
+ CPPUNIT_ASSERT(pInnerInnerXObjects);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pInnerInnerXObjects->GetItems().size());
+
+ std::vector<OString> aBitmapIDs1;
+ for (auto const& rPair : pInnerInnerXObjects->GetItems())
+ aBitmapIDs1.push_back(rPair.first);
+
+ {
+ CPPUNIT_ASSERT_EQUAL("Im11"_ostr, aBitmapIDs1[0]);
+ auto pRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pInnerInnerXObjects->LookupElement(aBitmapIDs1[0]));
+ CPPUNIT_ASSERT(pRef);
+ aBitmapRefs1.push_back(pRef->GetObjectValue());
+ CPPUNIT_ASSERT_EQUAL(0, pRef->GetGenerationValue());
+
+ vcl::filter::PDFObjectElement* pBitmap
+ = pInnerInnerXObjects->LookupObject(aBitmapIDs1[0]);
+ CPPUNIT_ASSERT(pBitmap);
+ auto pBitmapSubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pBitmap->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT(pBitmapSubtype);
+ CPPUNIT_ASSERT_EQUAL("Image"_ostr, pBitmapSubtype->GetValue());
+ }
+ {
+ CPPUNIT_ASSERT_EQUAL("Im5"_ostr, aBitmapIDs1[1]);
+ auto pRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pInnerInnerXObjects->LookupElement(aBitmapIDs1[1]));
+ CPPUNIT_ASSERT(pRef);
+ aBitmapRefs1.push_back(pRef->GetObjectValue());
+ CPPUNIT_ASSERT_EQUAL(0, pRef->GetGenerationValue());
+
+ vcl::filter::PDFObjectElement* pBitmap
+ = pInnerInnerXObjects->LookupObject(aBitmapIDs1[1]);
+ CPPUNIT_ASSERT(pBitmap);
+ auto pBitmapSubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pBitmap->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT(pBitmapSubtype);
+ CPPUNIT_ASSERT_EQUAL("Image"_ostr, pBitmapSubtype->GetValue());
+ }
+ }
+
+ {
+ // FORM object 2
+ OString aID = rIDs[1];
+ CPPUNIT_ASSERT_EQUAL("Im5"_ostr, aID);
+ vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(aID);
+ CPPUNIT_ASSERT(pXObject);
+
+ auto pSubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT(pSubtype);
+ CPPUNIT_ASSERT_EQUAL("Form"_ostr, pSubtype->GetValue());
+
+ auto pInnerResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pXObject->Lookup("Resources"_ostr));
+ CPPUNIT_ASSERT(pInnerResources);
+ auto pInnerXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pInnerResources->LookupElement("XObject"_ostr));
+ CPPUNIT_ASSERT(pInnerXObjects);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pInnerXObjects->GetItems().size());
+ OString aInnerObjectID = pInnerXObjects->GetItems().begin()->first;
+ CPPUNIT_ASSERT_EQUAL("Im6"_ostr, aInnerObjectID);
+
+ vcl::filter::PDFObjectElement* pInnerXObject
+ = pInnerXObjects->LookupObject(aInnerObjectID);
+ CPPUNIT_ASSERT(pInnerXObject);
+
+ auto pInnerSubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pInnerXObject->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT(pInnerSubtype);
+ CPPUNIT_ASSERT_EQUAL("Form"_ostr, pInnerSubtype->GetValue());
+
+ auto pInnerInnerResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pInnerXObject->Lookup("Resources"_ostr));
+ CPPUNIT_ASSERT(pInnerInnerResources);
+ auto pInnerInnerXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pInnerInnerResources->LookupElement("XObject"_ostr));
+ CPPUNIT_ASSERT(pInnerInnerXObjects);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pInnerInnerXObjects->GetItems().size());
+
+ std::vector<OString> aBitmapIDs2;
+ for (auto const& rPair : pInnerInnerXObjects->GetItems())
+ aBitmapIDs2.push_back(rPair.first);
+
+ {
+ CPPUNIT_ASSERT_EQUAL("Im11"_ostr, aBitmapIDs2[0]);
+ auto pRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pInnerInnerXObjects->LookupElement(aBitmapIDs2[0]));
+ CPPUNIT_ASSERT(pRef);
+ aBitmapRefs2.push_back(pRef->GetObjectValue());
+ CPPUNIT_ASSERT_EQUAL(0, pRef->GetGenerationValue());
+
+ vcl::filter::PDFObjectElement* pBitmap
+ = pInnerInnerXObjects->LookupObject(aBitmapIDs2[0]);
+ CPPUNIT_ASSERT(pBitmap);
+ auto pBitmapSubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pBitmap->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT(pBitmapSubtype);
+ CPPUNIT_ASSERT_EQUAL("Image"_ostr, pBitmapSubtype->GetValue());
+ }
+ {
+ CPPUNIT_ASSERT_EQUAL("Im5"_ostr, aBitmapIDs2[1]);
+ auto pRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pInnerInnerXObjects->LookupElement(aBitmapIDs2[1]));
+ CPPUNIT_ASSERT(pRef);
+ aBitmapRefs2.push_back(pRef->GetObjectValue());
+ CPPUNIT_ASSERT_EQUAL(0, pRef->GetGenerationValue());
+
+ vcl::filter::PDFObjectElement* pBitmap
+ = pInnerInnerXObjects->LookupObject(aBitmapIDs2[1]);
+ CPPUNIT_ASSERT(pBitmap);
+ auto pBitmapSubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pBitmap->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT(pBitmapSubtype);
+ CPPUNIT_ASSERT_EQUAL("Image"_ostr, pBitmapSubtype->GetValue());
+ }
+ }
+ // Ref should point to the same bitmap
+ CPPUNIT_ASSERT_EQUAL(aBitmapRefs1[0], aBitmapRefs2[0]);
+ CPPUNIT_ASSERT_EQUAL(aBitmapRefs1[1], aBitmapRefs2[1]);
+ }
+
+#endif
+}
+
+// Check we correctly copy more complex resources (Fonts describing
+// glyphs in recursive arrays) to the target PDF
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testReexportDocumentWithComplexResources)
+{
+// setenv only works on unix based systems
+#ifndef _WIN32
+ // We need to enable PDFium import (and make sure to disable after the test)
+ bool bResetEnvVar = false;
+ if (getenv("LO_IMPORT_USE_PDFIUM") == nullptr)
+ {
+ bResetEnvVar = true;
+ setenv("LO_IMPORT_USE_PDFIUM", "1", false);
+ }
+ comphelper::ScopeGuard aPDFiumEnvVarGuard([&]() {
+ if (bResetEnvVar)
+ unsetenv("LO_IMPORT_USE_PDFIUM");
+ });
+
+ // Load the PDF and save as PDF
+ vcl::filter::PDFDocument aDocument;
+ load(u"ComplexContentDictionary.pdf", aDocument);
+
+ // Assert that the XObject in the page resources dictionary is a reference XObject.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aPages.size());
+
+ // Go directly to the Font object (24 0) (number could change if we change how PDF export works)
+ auto pFont = aDocument.LookupObject(24);
+ CPPUNIT_ASSERT(pFont);
+
+ // Check it is the Font object (Type = Font)
+ auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pFont->GetDictionary()->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT(pName);
+ CPPUNIT_ASSERT_EQUAL("Font"_ostr, pName->GetValue());
+
+ // Check BaseFont is what we expect
+ auto pBaseFont = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pFont->GetDictionary()->LookupElement("BaseFont"_ostr));
+ CPPUNIT_ASSERT(pBaseFont);
+ CPPUNIT_ASSERT_EQUAL("HOTOMR+Calibri,Italic"_ostr, pBaseFont->GetValue());
+
+ // Check and get the W array
+ auto pWArray = dynamic_cast<vcl::filter::PDFArrayElement*>(
+ pFont->GetDictionary()->LookupElement("W"_ostr));
+ CPPUNIT_ASSERT(pWArray);
+ CPPUNIT_ASSERT_EQUAL(size_t(26), pWArray->GetElements().size());
+
+ // Check the content of W array
+ // ObjectCopier didn't copy this array correctly and the document
+ // had glyphs at the wrong places
+ {
+ // first 2 elements
+ auto pNumberAtIndex0 = dynamic_cast<vcl::filter::PDFNumberElement*>(pWArray->GetElement(0));
+ CPPUNIT_ASSERT(pNumberAtIndex0);
+ CPPUNIT_ASSERT_EQUAL(3.0, pNumberAtIndex0->GetValue());
+
+ auto pArrayAtIndex1 = dynamic_cast<vcl::filter::PDFArrayElement*>(pWArray->GetElement(1));
+ CPPUNIT_ASSERT(pArrayAtIndex1);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pArrayAtIndex1->GetElements().size());
+
+ {
+ auto pNumber
+ = dynamic_cast<vcl::filter::PDFNumberElement*>(pArrayAtIndex1->GetElement(0));
+ CPPUNIT_ASSERT(pNumber);
+ CPPUNIT_ASSERT_EQUAL(226.0, pNumber->GetValue());
+ }
+
+ // last 2 elements
+ auto pNumberAtIndex24
+ = dynamic_cast<vcl::filter::PDFNumberElement*>(pWArray->GetElement(24));
+ CPPUNIT_ASSERT(pNumberAtIndex24);
+ CPPUNIT_ASSERT_EQUAL(894.0, pNumberAtIndex24->GetValue());
+
+ auto pArrayAtIndex25 = dynamic_cast<vcl::filter::PDFArrayElement*>(pWArray->GetElement(25));
+ CPPUNIT_ASSERT(pArrayAtIndex25);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pArrayAtIndex25->GetElements().size());
+
+ {
+ auto pNumber1
+ = dynamic_cast<vcl::filter::PDFNumberElement*>(pArrayAtIndex25->GetElement(0));
+ CPPUNIT_ASSERT(pNumber1);
+ CPPUNIT_ASSERT_EQUAL(303.0, pNumber1->GetValue());
+
+ auto pNumber2
+ = dynamic_cast<vcl::filter::PDFNumberElement*>(pArrayAtIndex25->GetElement(1));
+ CPPUNIT_ASSERT(pNumber2);
+ CPPUNIT_ASSERT_EQUAL(303.0, pNumber2->GetValue());
+ }
+ }
+#endif
+}
+
+// Tests that at export the PDF has the PDF/UA metadata properly set
+// when we enable PDF/UA support.
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testPdfUaMetadata)
+{
+ // Import a basic document (document doesn't really matter)
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"BrownFoxLazyDog.odt");
+
+ // Parse the export result.
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ auto* pCatalog = aDocument.GetCatalog();
+ CPPUNIT_ASSERT(pCatalog);
+ auto* pCatalogDictionary = pCatalog->GetDictionary();
+ CPPUNIT_ASSERT(pCatalogDictionary);
+ auto* pMetadataObject = pCatalogDictionary->LookupObject("Metadata"_ostr);
+ CPPUNIT_ASSERT(pMetadataObject);
+ auto* pMetadataDictionary = pMetadataObject->GetDictionary();
+ auto* pType = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pMetadataDictionary->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT(pType);
+ CPPUNIT_ASSERT_EQUAL("Metadata"_ostr, pType->GetValue());
+
+ auto* pStreamObject = pMetadataObject->GetStream();
+ CPPUNIT_ASSERT(pStreamObject);
+ auto& rStream = pStreamObject->GetMemory();
+ rStream.Seek(0);
+
+ // Search for the PDF/UA marker in the metadata
+
+ tools::XmlWalker aWalker;
+ CPPUNIT_ASSERT(aWalker.open(&rStream));
+ CPPUNIT_ASSERT_EQUAL("xmpmeta"_ostr, aWalker.name());
+
+ bool bPdfUaMarkerFound = false;
+ OString aPdfUaPart;
+
+ aWalker.children();
+ while (aWalker.isValid())
+ {
+ if (aWalker.name() == "RDF"
+ && aWalker.namespaceHref() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
+ {
+ aWalker.children();
+ while (aWalker.isValid())
+ {
+ if (aWalker.name() == "Description"
+ && aWalker.namespaceHref() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
+ {
+ aWalker.children();
+ while (aWalker.isValid())
+ {
+ if (aWalker.name() == "part"
+ && aWalker.namespaceHref() == "http://www.aiim.org/pdfua/ns/id/")
+ {
+ aPdfUaPart = aWalker.content();
+ bPdfUaMarkerFound = true;
+ }
+ aWalker.next();
+ }
+ aWalker.parent();
+ }
+ aWalker.next();
+ }
+ aWalker.parent();
+ }
+ aWalker.next();
+ }
+ aWalker.parent();
+
+ CPPUNIT_ASSERT(bPdfUaMarkerFound);
+ CPPUNIT_ASSERT_EQUAL("1"_ostr, aPdfUaPart);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf139736)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) },
+ { "SelectPdfVersion", uno::Any(sal_Int32(17)) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"tdf139736-1.odt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
+ CPPUNIT_ASSERT(pContents);
+ vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+ // Uncompress it.
+ SvMemoryStream aUncompressed;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ rObjectStream.Seek(0);
+ aZCodec.Decompress(rObjectStream, aUncompressed);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+
+ auto pStart = static_cast<const char*>(aUncompressed.GetData());
+ const char* const pEnd = pStart + aUncompressed.GetSize();
+
+ enum
+ {
+ Default,
+ Artifact,
+ ArtifactProps1,
+ ArtifactProps2,
+ Tagged
+ } state
+ = Default;
+
+ auto nLine(0);
+ auto nTagged(0);
+ auto nArtifacts(0);
+ while (true)
+ {
+ ++nLine;
+ auto const pLine = ::std::find(pStart, pEnd, '\n');
+ if (pLine == pEnd)
+ {
+ break;
+ }
+ std::string_view const line(pStart, pLine - pStart);
+ pStart = pLine + 1;
+ if (!line.empty() && line[0] != '%')
+ {
+ ::std::cerr << nLine << ": " << line << "\n";
+ if (line == "/Artifact BMC")
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
+ state = Artifact;
+ ++nArtifacts;
+ }
+ else if (o3tl::starts_with(line, "/Artifact <<"))
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
+ // check header/footer properties
+ CPPUNIT_ASSERT_EQUAL(std::string_view("/Type/Pagination"), line.substr(12));
+ state = ArtifactProps1;
+ ++nArtifacts;
+ }
+ else if (state == ArtifactProps1)
+ {
+ CPPUNIT_ASSERT_EQUAL(std::string_view("/Subtype/Header"), line);
+ state = ArtifactProps2;
+ }
+ else if (state == ArtifactProps2 && line == ">> BDC")
+ {
+ state = Artifact;
+ }
+ else if (line == "/Standard<</MCID 0>>BDC")
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
+ state = Tagged;
+ ++nTagged;
+ }
+ else if (line == "EMC")
+ {
+ CPPUNIT_ASSERT_MESSAGE("unexpected end", state != Default);
+ state = Default;
+ }
+ else if (nLine > 1) // first line is expected "0.1 w"
+ {
+ CPPUNIT_ASSERT_MESSAGE("unexpected content outside MCS", state != Default);
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unclosed MCS", Default, state);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nTagged)>(1), nTagged); // text in body
+ // 1 image and 1 frame and 1 header text; arbitrary number of aux stuff like borders
+ CPPUNIT_ASSERT(nArtifacts >= 3);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf152231)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) },
+ { "ExportNotesInMargin", uno::Any(true) },
+ { "SelectPdfVersion", uno::Any(sal_Int32(17)) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"tdf152231.fodt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
+ CPPUNIT_ASSERT(pContents);
+ vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+ // Uncompress it.
+ SvMemoryStream aUncompressed;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ rObjectStream.Seek(0);
+ aZCodec.Decompress(rObjectStream, aUncompressed);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+
+ auto pStart = static_cast<const char*>(aUncompressed.GetData());
+ const char* const pEnd = pStart + aUncompressed.GetSize();
+
+ enum
+ {
+ Default,
+ Artifact,
+ Tagged
+ } state
+ = Default;
+
+ auto nLine(0);
+ auto nTagged(0);
+ auto nArtifacts(0);
+ while (true)
+ {
+ ++nLine;
+ auto const pLine = ::std::find(pStart, pEnd, '\n');
+ if (pLine == pEnd)
+ {
+ break;
+ }
+ std::string_view const line(pStart, pLine - pStart);
+ pStart = pLine + 1;
+ if (!line.empty() && line[0] != '%')
+ {
+ ::std::cerr << nLine << ": " << line << "\n";
+ if (line == "/Artifact BMC")
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
+ state = Artifact;
+ ++nArtifacts;
+ }
+ else if (o3tl::starts_with(line, "/Standard<</MCID "))
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
+ state = Tagged;
+ ++nTagged;
+ }
+ else if (line == "EMC")
+ {
+ CPPUNIT_ASSERT_MESSAGE("unexpected end", state != Default);
+ state = Default;
+ }
+ else if (nLine > 1) // first line is expected "0.1 w"
+ {
+ CPPUNIT_ASSERT_MESSAGE("unexpected content outside MCS", state != Default);
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unclosed MCS", Default, state);
+ CPPUNIT_ASSERT(nTagged >= 12); // text in body
+ // 1 annotation
+ CPPUNIT_ASSERT(nArtifacts >= 1);
+
+ auto nPara(0);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject)
+ continue;
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
+ if (pType && pType->GetValue() == "StructElem")
+ {
+ auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("S"_ostr));
+ if (pS && pS->GetValue() == "Standard")
+ {
+ ++nPara;
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids);
+ // one problem was that some StructElem were missing kids
+ CPPUNIT_ASSERT(!pKids->GetElements().empty());
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nPara)>(12), nPara);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf152235)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence(
+ { { "PDFUACompliance", uno::Any(true) },
+ { "Watermark", uno::Any(OUString("kendy")) },
+ // need to set a font to avoid assertions about missing "Helvetica"
+ { "WatermarkFontName", uno::Any(OUString("Liberation Sans")) },
+ { "SelectPdfVersion", uno::Any(sal_Int32(17)) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ mxComponent = loadFromDesktop("private:factory/swriter");
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
+ CPPUNIT_ASSERT(pContents);
+ vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+ // Uncompress it.
+ SvMemoryStream aUncompressed;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ rObjectStream.Seek(0);
+ aZCodec.Decompress(rObjectStream, aUncompressed);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+
+ auto pStart = static_cast<const char*>(aUncompressed.GetData());
+ const char* const pEnd = pStart + aUncompressed.GetSize();
+
+ enum
+ {
+ Default,
+ Artifact,
+ Tagged
+ } state
+ = Default;
+
+ auto nLine(0);
+ auto nTagged(0);
+ auto nArtifacts(0);
+ while (true)
+ {
+ ++nLine;
+ auto const pLine = ::std::find(pStart, pEnd, '\n');
+ if (pLine == pEnd)
+ {
+ break;
+ }
+ std::string_view const line(pStart, pLine - pStart);
+ pStart = pLine + 1;
+ if (!line.empty() && line[0] != '%')
+ {
+ ::std::cerr << nLine << ": " << line << "\n";
+ if (o3tl::starts_with(line, "/Artifact "))
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
+ state = Artifact;
+ ++nArtifacts;
+ }
+ else if (o3tl::starts_with(line, "/Standard<</MCID "))
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
+ state = Tagged;
+ ++nTagged;
+ }
+ else if (line == "EMC")
+ {
+ CPPUNIT_ASSERT_MESSAGE("unexpected end", state != Default);
+ state = Default;
+ }
+ else if (nLine > 1) // first line is expected "0.1 w"
+ {
+ CPPUNIT_ASSERT_MESSAGE("unexpected content outside MCS", state != Default);
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unclosed MCS", Default, state);
+ CPPUNIT_ASSERT(nTagged >= 0); // text in body
+ CPPUNIT_ASSERT(nArtifacts >= 2); // 1 watermark + 1 other thing
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf149140)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"TableTH_test_LibreOfficeWriter7.3.3_HeaderRow-HeadersInTopRow.fodt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ int nTH(0);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject)
+ continue;
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
+ if (pType && pType->GetValue() == "StructElem")
+ {
+ auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("S"_ostr));
+ if (pS && pS->GetValue() == "TH")
+ {
+ int nTable(0);
+ auto pAttrs
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject->Lookup("A"_ostr));
+ CPPUNIT_ASSERT(pAttrs != nullptr);
+ for (const auto& rAttrRef : pAttrs->GetElements())
+ {
+ auto pAttrDict = dynamic_cast<vcl::filter::PDFDictionaryElement*>(rAttrRef);
+ CPPUNIT_ASSERT(pAttrDict != nullptr);
+ auto pOwner = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pAttrDict->LookupElement("O"_ostr));
+ CPPUNIT_ASSERT(pOwner != nullptr);
+ if (pOwner->GetValue() == "Table")
+ {
+ auto pScope = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pAttrDict->LookupElement("Scope"_ostr));
+ CPPUNIT_ASSERT(pScope != nullptr);
+ CPPUNIT_ASSERT_EQUAL("Column"_ostr, pScope->GetValue());
+ ++nTable;
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(int(1), nTable);
+ ++nTH;
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(int(6), nTH);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testNestedSection)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"nestedsection.fodt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // the assert needs 2 follows to reproduce => 3 pages
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aPages.size());
+
+ auto nDoc(0);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject1)
+ continue;
+ auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
+ if (pType1 && pType1->GetValue() == "StructElem")
+ {
+ auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
+ if (pS1 && pS1->GetValue() == "Document")
+ {
+ auto pKids1
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids1);
+ // assume there are no MCID ref at this level
+ auto pKids1v = pKids1->GetElements();
+ auto pRefKid10 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1v[0]);
+ CPPUNIT_ASSERT(pRefKid10);
+ auto pObject10 = pRefKid10->LookupObject();
+ CPPUNIT_ASSERT(pObject10);
+ auto pType10
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject10->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType10->GetValue());
+ auto pS10 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject10->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Sect"_ostr, pS10->GetValue());
+
+ auto pKids10
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject10->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids10);
+ // assume there are no MCID ref at this level
+ auto pKids10v = pKids10->GetElements();
+
+ auto pRefKid100 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids10v[0]);
+ CPPUNIT_ASSERT(pRefKid100);
+ auto pObject100 = pRefKid100->LookupObject();
+ CPPUNIT_ASSERT(pObject100);
+ auto pType100
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject100->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType100->GetValue());
+ auto pS100
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject100->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS100->GetValue());
+
+ auto pRefKid101 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids10v[1]);
+ CPPUNIT_ASSERT(pRefKid101);
+ auto pObject101 = pRefKid101->LookupObject();
+ CPPUNIT_ASSERT(pObject101);
+ auto pType101
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject101->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType101->GetValue());
+ auto pS101
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject101->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS101->GetValue());
+
+ auto pRefKid102 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids10v[2]);
+ CPPUNIT_ASSERT(pRefKid102);
+ auto pObject102 = pRefKid102->LookupObject();
+ CPPUNIT_ASSERT(pObject102);
+ auto pType102
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject102->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType102->GetValue());
+ auto pS102
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject102->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Sect"_ostr, pS102->GetValue());
+
+ auto pKids102
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject102->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids102);
+ // assume there are no MCID ref at this level
+ auto pKids102v = pKids102->GetElements();
+
+ auto pRefKid1020 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids102v[0]);
+ CPPUNIT_ASSERT(pRefKid1020);
+ auto pObject1020 = pRefKid1020->LookupObject();
+ CPPUNIT_ASSERT(pObject1020);
+ auto pType1020
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1020->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1020->GetValue());
+ auto pS1020
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1020->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS1020->GetValue());
+
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pKids102v.size());
+
+ auto pRefKid103 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids10v[3]);
+ CPPUNIT_ASSERT(pRefKid103);
+ auto pObject103 = pRefKid103->LookupObject();
+ CPPUNIT_ASSERT(pObject103);
+ auto pType103
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject103->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType103->GetValue());
+ auto pS103
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject103->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS103->GetValue());
+
+ auto pRefKid104 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids10v[4]);
+ CPPUNIT_ASSERT(pRefKid104);
+ auto pObject104 = pRefKid104->LookupObject();
+ CPPUNIT_ASSERT(pObject104);
+ auto pType104
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject104->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType104->GetValue());
+ auto pS104
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject104->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS104->GetValue());
+
+ CPPUNIT_ASSERT_EQUAL(size_t(5), pKids10v.size());
+
+ auto pRefKid11 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1v[1]);
+ CPPUNIT_ASSERT(pRefKid11);
+ auto pObject11 = pRefKid11->LookupObject();
+ CPPUNIT_ASSERT(pObject11);
+ auto pType11
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType11->GetValue());
+ auto pS11 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS11->GetValue());
+
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pKids1v.size());
+ ++nDoc;
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nDoc)>(1), nDoc);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf157817)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"SimpleTOC.fodt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aPages.size());
+
+ vcl::filter::PDFObjectElement* pTOC(nullptr);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject1)
+ continue;
+ auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
+ if (pType1 && pType1->GetValue() == "StructElem")
+ {
+ auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
+ if (pS1 && pS1->GetValue() == "TOC")
+ {
+ pTOC = pObject1;
+ }
+ }
+ }
+ CPPUNIT_ASSERT(pTOC);
+
+ auto pKidsT = dynamic_cast<vcl::filter::PDFArrayElement*>(pTOC->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKidsT);
+ // assume there are no MCID ref at this level
+ auto pKidsTv = pKidsT->GetElements();
+ auto pRefKidT0 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsTv[0]);
+ CPPUNIT_ASSERT(pRefKidT0);
+ auto pObjectT0 = pRefKidT0->LookupObject();
+ CPPUNIT_ASSERT(pObjectT0);
+ auto pTypeT0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT0->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeT0->GetValue());
+ auto pST0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT0->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Caption"_ostr, pST0->GetValue());
+
+ auto pKidsT0 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectT0->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKidsT0);
+ auto pKidsT0v = pKidsT0->GetElements();
+ auto pRefKidT00 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsT0v[0]);
+ CPPUNIT_ASSERT(pRefKidT00);
+ auto pObjectT00 = pRefKidT00->LookupObject();
+ CPPUNIT_ASSERT(pObjectT00);
+ auto pTypeT00 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT00->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeT00->GetValue());
+ auto pST00 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT00->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Contents#20Heading"_ostr, pST00->GetValue());
+
+ auto pRefKidT1 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsTv[1]);
+ CPPUNIT_ASSERT(pRefKidT1);
+ auto pObjectT1 = pRefKidT1->LookupObject();
+ CPPUNIT_ASSERT(pObjectT1);
+ auto pTypeT1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT1->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeT1->GetValue());
+ auto pST1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT1->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("TOCI"_ostr, pST1->GetValue());
+
+ auto pKidsT1 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectT1->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKidsT1);
+ auto pKidsT1v = pKidsT1->GetElements();
+
+ auto pRefKidT10 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsT1v[0]);
+ CPPUNIT_ASSERT(pRefKidT10);
+ auto pObjectT10 = pRefKidT10->LookupObject();
+ CPPUNIT_ASSERT(pObjectT10);
+ auto pTypeT10 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT10->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeT10->GetValue());
+ auto pST10 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT10->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Contents#201"_ostr, pST10->GetValue());
+
+ auto pKidsT10 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectT10->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKidsT10);
+ auto pKidsT10v = pKidsT10->GetElements();
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pKidsT10v.size());
+
+ // there is one and only one Link
+ auto pRefKidT100 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsT10v[0]);
+ CPPUNIT_ASSERT(pRefKidT100);
+ auto pObjectT100 = pRefKidT100->LookupObject();
+ CPPUNIT_ASSERT(pObjectT100);
+ auto pTypeT100 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT100->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeT100->GetValue());
+ auto pST100 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT100->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pST100->GetValue());
+
+ auto pRefKidT2 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsTv[1]);
+ CPPUNIT_ASSERT(pRefKidT2);
+ auto pObjectT2 = pRefKidT2->LookupObject();
+ CPPUNIT_ASSERT(pObjectT2);
+ auto pTypeT2 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT2->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeT2->GetValue());
+ auto pST2 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT2->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("TOCI"_ostr, pST2->GetValue());
+
+ auto pKidsT2 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectT2->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKidsT2);
+ auto pKidsT2v = pKidsT2->GetElements();
+
+ auto pRefKidT20 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsT2v[0]);
+ CPPUNIT_ASSERT(pRefKidT20);
+ auto pObjectT20 = pRefKidT20->LookupObject();
+ CPPUNIT_ASSERT(pObjectT20);
+ auto pTypeT20 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT20->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeT20->GetValue());
+ auto pST20 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT20->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Contents#201"_ostr, pST20->GetValue());
+
+ auto pKidsT20 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectT20->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKidsT20);
+ auto pKidsT20v = pKidsT20->GetElements();
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pKidsT20v.size());
+
+ // there is one and only one Link
+ auto pRefKidT200 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsT20v[0]);
+ CPPUNIT_ASSERT(pRefKidT200);
+ auto pObjectT200 = pRefKidT200->LookupObject();
+ CPPUNIT_ASSERT(pObjectT200);
+ auto pTypeT200 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT200->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeT200->GetValue());
+ auto pST200 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT200->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pST200->GetValue());
+
+ auto pRefKidT3 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsTv[1]);
+ CPPUNIT_ASSERT(pRefKidT3);
+ auto pObjectT3 = pRefKidT3->LookupObject();
+ CPPUNIT_ASSERT(pObjectT3);
+ auto pTypeT3 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT3->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeT3->GetValue());
+ auto pST3 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT3->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("TOCI"_ostr, pST3->GetValue());
+
+ auto pKidsT3 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectT3->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKidsT3);
+ auto pKidsT3v = pKidsT3->GetElements();
+
+ auto pRefKidT30 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsT3v[0]);
+ CPPUNIT_ASSERT(pRefKidT30);
+ auto pObjectT30 = pRefKidT30->LookupObject();
+ CPPUNIT_ASSERT(pObjectT30);
+ auto pTypeT30 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT30->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeT30->GetValue());
+ auto pST30 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT30->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Contents#201"_ostr, pST30->GetValue());
+
+ auto pKidsT30 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectT30->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKidsT30);
+ auto pKidsT30v = pKidsT30->GetElements();
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pKidsT30v.size());
+
+ // there is one and only one Link
+ auto pRefKidT300 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsT30v[0]);
+ CPPUNIT_ASSERT(pRefKidT300);
+ auto pObjectT300 = pRefKidT300->LookupObject();
+ CPPUNIT_ASSERT(pObjectT300);
+ auto pTypeT300 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT300->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeT300->GetValue());
+ auto pST300 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectT300->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pST300->GetValue());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf135638)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"image-shape.fodt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ int nFigure(0);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject)
+ continue;
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
+ if (pType && pType->GetValue() == "StructElem")
+ {
+ auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("S"_ostr));
+ if (pS && pS->GetValue() == "Figure")
+ {
+ auto pAttrDict
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pObject->Lookup("A"_ostr));
+ CPPUNIT_ASSERT(pAttrDict != nullptr);
+ auto pOwner = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pAttrDict->LookupElement("O"_ostr));
+ CPPUNIT_ASSERT(pOwner != nullptr);
+ CPPUNIT_ASSERT_EQUAL("Layout"_ostr, pOwner->GetValue());
+ auto pBBox = dynamic_cast<vcl::filter::PDFArrayElement*>(
+ pAttrDict->LookupElement("BBox"_ostr));
+ CPPUNIT_ASSERT(pBBox != nullptr);
+ if (nFigure == 0)
+ {
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(
+ 139.5,
+ dynamic_cast<vcl::filter::PDFNumberElement*>(pBBox->GetElements()[0])
+ ->GetValue(),
+ 0.01);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(
+ 480.3,
+ dynamic_cast<vcl::filter::PDFNumberElement*>(pBBox->GetElements()[1])
+ ->GetValue(),
+ 0.01);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(
+ 472.5,
+ dynamic_cast<vcl::filter::PDFNumberElement*>(pBBox->GetElements()[2])
+ ->GetValue(),
+ 0.01);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(
+ 735.3,
+ dynamic_cast<vcl::filter::PDFNumberElement*>(pBBox->GetElements()[3])
+ ->GetValue(),
+ 0.01);
+ }
+ else
+ {
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(
+ 178.45,
+ dynamic_cast<vcl::filter::PDFNumberElement*>(pBBox->GetElements()[0])
+ ->GetValue(),
+ 0.01);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(
+ 318.65,
+ dynamic_cast<vcl::filter::PDFNumberElement*>(pBBox->GetElements()[1])
+ ->GetValue(),
+ 0.01);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(
+ 326.35,
+ dynamic_cast<vcl::filter::PDFNumberElement*>(pBBox->GetElements()[2])
+ ->GetValue(),
+ 0.01);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(
+ 382.55,
+ dynamic_cast<vcl::filter::PDFNumberElement*>(pBBox->GetElements()[3])
+ ->GetValue(),
+ 0.01);
+ }
+ ++nFigure;
+ }
+ }
+ }
+ // the first one is a Writer image, 2nd one SdrRectObj
+ CPPUNIT_ASSERT_EQUAL(int(2), nFigure);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf157703)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"LO_Lbl_Lbody_bug_report.fodt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ vcl::filter::PDFObjectElement* pDocument(nullptr);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject1)
+ continue;
+ auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
+ if (pType1 && pType1->GetValue() == "StructElem")
+ {
+ auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
+ if (pS1 && pS1->GetValue() == "Document")
+ {
+ pDocument = pObject1;
+ }
+ }
+ }
+ CPPUNIT_ASSERT(pDocument);
+
+ auto pKidsD = dynamic_cast<vcl::filter::PDFArrayElement*>(pDocument->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKidsD);
+ // assume there are no MCID ref at this level
+ auto pKidsDv = pKidsD->GetElements();
+ auto pRefKidD0 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsDv[0]);
+ CPPUNIT_ASSERT(pRefKidD0);
+ auto pObjectD0 = pRefKidD0->LookupObject();
+ CPPUNIT_ASSERT(pObjectD0);
+ auto pTypeD0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD0->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD0->GetValue());
+ auto pSD0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD0->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("H1"_ostr, pSD0->GetValue());
+
+ auto pKidsD0 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD0->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKidsD0);
+ auto pKidsD0v = pKidsD0->GetElements();
+ auto pRefKidD00 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD0v[0]);
+ // MCID for label
+ CPPUNIT_ASSERT(!pRefKidD00);
+
+ // MCID for text
+ auto pRefKidD01 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD0v[1]);
+ CPPUNIT_ASSERT(!pRefKidD01);
+
+ auto pRefKidD1 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsDv[1]);
+ CPPUNIT_ASSERT(pRefKidD1);
+ auto pObjectD1 = pRefKidD1->LookupObject();
+ CPPUNIT_ASSERT(pObjectD1);
+ auto pTypeD1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD1->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD1->GetValue());
+ auto pSD1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD1->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("H2"_ostr, pSD1->GetValue());
+
+ auto pKidsD1 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD1->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKidsD1);
+ auto pKidsD1v = pKidsD1->GetElements();
+
+ // MCID for text
+ auto pRefKidD11 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD1v[0]);
+ CPPUNIT_ASSERT(!pRefKidD11);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testSpans)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"spanlist.fodt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aPages.size());
+
+ auto nDoc(0);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject1)
+ continue;
+ auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
+ if (pType1 && pType1->GetValue() == "StructElem")
+ {
+ auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
+ if (pS1 && pS1->GetValue() == "Document")
+ {
+ auto pKids1
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids1);
+ // assume there are no MCID ref at this level
+ auto vKids1 = pKids1->GetElements();
+ CPPUNIT_ASSERT_EQUAL(size_t(2), vKids1.size());
+ auto pRefKid10 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids1[0]);
+ CPPUNIT_ASSERT(pRefKid10);
+ auto pObject10 = pRefKid10->LookupObject();
+ CPPUNIT_ASSERT(pObject10);
+ auto pType10
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject10->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType10->GetValue());
+ auto pS10 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject10->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("L"_ostr, pS10->GetValue());
+
+ auto pKids10
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject10->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids10);
+ // assume there are no MCID ref at this level
+ auto vKids10 = pKids10->GetElements();
+ CPPUNIT_ASSERT_EQUAL(size_t(4), vKids10.size());
+
+ auto pRefKid100 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids10[0]);
+ CPPUNIT_ASSERT(pRefKid100);
+ auto pObject100 = pRefKid100->LookupObject();
+ CPPUNIT_ASSERT(pObject100);
+ auto pType100
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject100->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType100->GetValue());
+ auto pS100
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject100->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("LI"_ostr, pS100->GetValue());
+
+ auto pKids100
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject100->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids100);
+ // assume there are no MCID ref at this level
+ auto vKids100 = pKids100->GetElements();
+ CPPUNIT_ASSERT_EQUAL(size_t(2), vKids100.size());
+
+ auto pRefKid1000 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids100[0]);
+ CPPUNIT_ASSERT(pRefKid1000);
+ auto pObject1000 = pRefKid1000->LookupObject();
+ CPPUNIT_ASSERT(pObject1000);
+ auto pType1000
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1000->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1000->GetValue());
+ auto pS1000
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1000->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Lbl"_ostr, pS1000->GetValue());
+
+ auto pRefKid1001 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids100[1]);
+ CPPUNIT_ASSERT(pRefKid1001);
+ auto pObject1001 = pRefKid1001->LookupObject();
+ CPPUNIT_ASSERT(pObject1001);
+ auto pType1001
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1001->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1001->GetValue());
+ auto pS1001
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1001->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("LBody"_ostr, pS1001->GetValue());
+ auto pKids1001
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1001->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids1001);
+ // assume there are no MCID ref at this level
+ auto vKids1001 = pKids1001->GetElements();
+ CPPUNIT_ASSERT_EQUAL(size_t(1), vKids1001.size());
+
+ auto pRefKid10010 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids1001[0]);
+ CPPUNIT_ASSERT(pRefKid10010);
+ auto pObject10010 = pRefKid10010->LookupObject();
+ CPPUNIT_ASSERT(pObject10010);
+ auto pType10010
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject10010->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType10010->GetValue());
+ auto pS10010
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject10010->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS10010->GetValue());
+ auto pKids10010
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject10010->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids10010);
+ // assume there are no MCID ref at this level
+ auto vKids10010 = pKids10010->GetElements();
+ // only one span
+ CPPUNIT_ASSERT_EQUAL(size_t(1), vKids10010.size());
+
+ auto pRefKid100100 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids10010[0]);
+ CPPUNIT_ASSERT(pRefKid100100);
+ auto pObject100100 = pRefKid100100->LookupObject();
+ CPPUNIT_ASSERT(pObject100100);
+ auto pType100100 = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pObject100100->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType100100->GetValue());
+ auto pS100100
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject100100->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Span"_ostr, pS100100->GetValue());
+ // this span exists because of lang
+ auto pLang100100 = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
+ pObject100100->Lookup("Lang"_ostr));
+ CPPUNIT_ASSERT_EQUAL("en-GB"_ostr, pLang100100->GetValue());
+
+ auto pRefKid101 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids10[1]);
+ CPPUNIT_ASSERT(pRefKid101);
+ auto pObject101 = pRefKid101->LookupObject();
+ CPPUNIT_ASSERT(pObject101);
+ auto pType101
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject101->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType101->GetValue());
+ auto pS101
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject101->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("LI"_ostr, pS101->GetValue());
+
+ auto pKids101
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject101->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids101);
+ // assume there are no MCID ref at this level
+ auto vKids101 = pKids101->GetElements();
+ CPPUNIT_ASSERT_EQUAL(size_t(2), vKids101.size());
+
+ auto pRefKid1010 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids101[0]);
+ CPPUNIT_ASSERT(pRefKid1010);
+ auto pObject1010 = pRefKid1010->LookupObject();
+ CPPUNIT_ASSERT(pObject1010);
+ auto pType1010
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1010->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1010->GetValue());
+ auto pS1010
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1010->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Lbl"_ostr, pS1010->GetValue());
+
+ auto pRefKid1011 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids101[1]);
+ CPPUNIT_ASSERT(pRefKid1011);
+ auto pObject1011 = pRefKid1011->LookupObject();
+ CPPUNIT_ASSERT(pObject1011);
+ auto pType1011
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1011->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1011->GetValue());
+ auto pS1011
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1011->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("LBody"_ostr, pS1011->GetValue());
+
+ auto pKids1011
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1011->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids1011);
+ // assume there are no MCID ref at this level
+ auto vKids1011 = pKids1011->GetElements();
+ CPPUNIT_ASSERT_EQUAL(size_t(1), vKids1011.size());
+
+ auto pRefKid10110 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids1011[0]);
+ CPPUNIT_ASSERT(pRefKid10110);
+ auto pObject10110 = pRefKid10110->LookupObject();
+ CPPUNIT_ASSERT(pObject10110);
+ auto pType10110
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject10110->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType10110->GetValue());
+ auto pS10110
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject10110->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS10110->GetValue());
+ auto pKids10110
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject10110->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids10110);
+ auto vKids10110 = pKids10110->GetElements();
+ // only MCIDs, no span
+ for (size_t i = 0; i < vKids10110.size(); ++i)
+ {
+ auto pKid = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids10110[i]);
+ CPPUNIT_ASSERT(!pKid);
+ }
+
+ auto pRefKid102 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids10[2]);
+ CPPUNIT_ASSERT(pRefKid102);
+ auto pObject102 = pRefKid102->LookupObject();
+ CPPUNIT_ASSERT(pObject102);
+ auto pType102
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject102->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType102->GetValue());
+ auto pS102
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject102->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("LI"_ostr, pS102->GetValue());
+
+ auto pKids102
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject102->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids102);
+ // assume there are no MCID ref at this level
+ auto vKids102 = pKids102->GetElements();
+ CPPUNIT_ASSERT_EQUAL(size_t(2), vKids102.size());
+
+ auto pRefKid1020 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids102[0]);
+ CPPUNIT_ASSERT(pRefKid1020);
+ auto pObject1020 = pRefKid1020->LookupObject();
+ CPPUNIT_ASSERT(pObject1020);
+ auto pType1020
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1020->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1020->GetValue());
+ auto pS1020
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1020->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Lbl"_ostr, pS1020->GetValue());
+
+ auto pRefKid1021 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids102[1]);
+ CPPUNIT_ASSERT(pRefKid1021);
+ auto pObject1021 = pRefKid1021->LookupObject();
+ CPPUNIT_ASSERT(pObject1021);
+ auto pType1021
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1021->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1021->GetValue());
+ auto pS1021
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1021->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("LBody"_ostr, pS1021->GetValue());
+
+ auto pKids1021
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1021->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids1021);
+ // assume there are no MCID ref at this level
+ auto vKids1021 = pKids1021->GetElements();
+ CPPUNIT_ASSERT_EQUAL(size_t(1), vKids1021.size());
+
+ auto pRefKid10210 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids1021[0]);
+ CPPUNIT_ASSERT(pRefKid10210);
+ auto pObject10210 = pRefKid10210->LookupObject();
+ CPPUNIT_ASSERT(pObject10210);
+ auto pType10210
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject10210->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType10210->GetValue());
+ auto pS10210
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject10210->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS10210->GetValue());
+ auto pKids10210
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject10210->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids10210);
+ // assume there are no MCID ref at this level
+ auto vKids10210 = pKids10210->GetElements();
+ // 2 span and a hyperlink
+ CPPUNIT_ASSERT_EQUAL(size_t(3), vKids10210.size());
+
+ auto pRefKid102100 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids10210[0]);
+ CPPUNIT_ASSERT(pRefKid102100);
+ auto pObject102100 = pRefKid102100->LookupObject();
+ CPPUNIT_ASSERT(pObject102100);
+ auto pType102100 = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pObject102100->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType102100->GetValue());
+ auto pS102100
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject102100->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Span"_ostr, pS102100->GetValue());
+ auto pKids102100
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject102100->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids102100);
+ auto vKids102100 = pKids102100->GetElements();
+ for (size_t i = 0; i < vKids102100.size(); ++i)
+ {
+ auto pKid = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids102100[i]);
+ CPPUNIT_ASSERT(!pKid);
+ }
+
+ auto pRefKid102101 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids10210[1]);
+ CPPUNIT_ASSERT(pRefKid102101);
+ auto pObject102101 = pRefKid102101->LookupObject();
+ CPPUNIT_ASSERT(pObject102101);
+ auto pType102101 = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pObject102101->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType102101->GetValue());
+ auto pS102101
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject102101->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pS102101->GetValue());
+ auto pKids102101
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject102101->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids102101);
+ auto vKids102101 = pKids102101->GetElements();
+ auto nRef(0);
+ for (size_t i = 0; i < vKids102101.size(); ++i)
+ {
+ auto pKid = dynamic_cast<vcl::filter::PDFDictionaryElement*>(vKids102101[i]);
+ if (pKid)
+ {
+ ++nRef; // annotation
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+
+ auto pRefKid102102 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids10210[2]);
+ CPPUNIT_ASSERT(pRefKid102102);
+ auto pObject102102 = pRefKid102102->LookupObject();
+ CPPUNIT_ASSERT(pObject102102);
+ auto pType102102 = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pObject102102->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType102102->GetValue());
+ auto pS102102
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject102102->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Span"_ostr, pS102102->GetValue());
+ auto pKids102102
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject102102->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids102102);
+ auto vKids102102 = pKids102102->GetElements();
+ // there is a footnote
+ auto nFtn(0);
+ for (size_t i = 0; i < vKids102102.size(); ++i)
+ {
+ auto pKid = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids102102[i]);
+ if (pKid)
+ {
+ auto pObject = pKid->LookupObject();
+ CPPUNIT_ASSERT(pObject);
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pObject->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType->GetValue());
+ auto pS
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pS->GetValue());
+ ++nFtn;
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nFtn)>(1), nFtn);
+
+ auto pRefKid103 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids10[3]);
+ CPPUNIT_ASSERT(pRefKid103);
+ auto pObject103 = pRefKid103->LookupObject();
+ CPPUNIT_ASSERT(pObject103);
+ auto pType103
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject103->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType103->GetValue());
+ auto pS103
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject103->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("LI"_ostr, pS103->GetValue());
+
+ auto pKids103
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject103->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids103);
+ // assume there are no MCID ref at this level
+ auto vKids103 = pKids103->GetElements();
+ CPPUNIT_ASSERT_EQUAL(size_t(2), vKids103.size());
+
+ auto pRefKid1030 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids103[0]);
+ CPPUNIT_ASSERT(pRefKid1030);
+ auto pObject1030 = pRefKid1030->LookupObject();
+ CPPUNIT_ASSERT(pObject1030);
+ auto pType1030
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1030->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1030->GetValue());
+ auto pS1030
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1030->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Lbl"_ostr, pS1030->GetValue());
+
+ auto pRefKid1031 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids103[1]);
+ CPPUNIT_ASSERT(pRefKid1031);
+ auto pObject1031 = pRefKid1031->LookupObject();
+ CPPUNIT_ASSERT(pObject1031);
+ auto pType1031
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1031->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1031->GetValue());
+ auto pS1031
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1031->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("LBody"_ostr, pS1031->GetValue());
+
+ auto pKids1031
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1031->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids1031);
+ // assume there are no MCID ref at this level
+ auto vKids1031 = pKids1031->GetElements();
+ CPPUNIT_ASSERT_EQUAL(size_t(1), vKids1031.size());
+
+ auto pRefKid10310 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids1031[0]);
+ CPPUNIT_ASSERT(pRefKid10310);
+ auto pObject10310 = pRefKid10310->LookupObject();
+ CPPUNIT_ASSERT(pObject10310);
+ auto pType10310
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject10310->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType10310->GetValue());
+ auto pS10310
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject10310->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS10310->GetValue());
+ auto pKids10310
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject10310->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids10310);
+ // assume there are no MCID ref at this level
+ auto vKids10310 = pKids10310->GetElements();
+ // only one span, following a MCID for some strike-out gap
+ CPPUNIT_ASSERT_EQUAL(size_t(2), vKids10310.size());
+
+ auto pRefKid103100 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids10310[0]);
+ CPPUNIT_ASSERT(!pRefKid103100);
+
+ auto pRefKid103101 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids10310[1]);
+ CPPUNIT_ASSERT(pRefKid103101);
+ auto pObject103101 = pRefKid103101->LookupObject();
+ CPPUNIT_ASSERT(pObject103101);
+ auto pType103101 = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pObject103101->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType103101->GetValue());
+ auto pS103101
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject103101->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Span"_ostr, pS103101->GetValue());
+ auto pDictA103101 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
+ pObject103101->Lookup("A"_ostr));
+ CPPUNIT_ASSERT(pDictA103101 != nullptr);
+ CPPUNIT_ASSERT_EQUAL("Layout"_ostr, dynamic_cast<vcl::filter::PDFNameElement*>(
+ pDictA103101->LookupElement("O"_ostr))
+ ->GetValue());
+ CPPUNIT_ASSERT_EQUAL("LineThrough"_ostr,
+ dynamic_cast<vcl::filter::PDFNameElement*>(
+ pDictA103101->LookupElement("TextDecorationType"_ostr))
+ ->GetValue());
+
+ // now the footnote container - following the list
+ auto pRefKid11 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids1[1]);
+ CPPUNIT_ASSERT(pRefKid11);
+ auto pObject11 = pRefKid11->LookupObject();
+ CPPUNIT_ASSERT(pObject11);
+ auto pType11
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType11->GetValue());
+ auto pS11 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Div"_ostr, pS11->GetValue());
+
+ auto pKids11
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject11->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids11);
+ // assume there are no MCID ref at this level
+ auto vKids11 = pKids11->GetElements();
+ CPPUNIT_ASSERT_EQUAL(size_t(1), vKids11.size());
+
+ auto pRefKid110 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids11[0]);
+ CPPUNIT_ASSERT(pRefKid110);
+ auto pObject110 = pRefKid110->LookupObject();
+ CPPUNIT_ASSERT(pObject110);
+ auto pType110
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject110->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType110->GetValue());
+ auto pS110
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject110->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Note"_ostr, pS110->GetValue());
+
+ auto pKids110
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject110->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids110);
+ // assume there are no MCID ref at this level
+ auto vKids110 = pKids110->GetElements();
+ CPPUNIT_ASSERT_EQUAL(size_t(2), vKids110.size());
+
+ auto pRefKid1100 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids110[0]);
+ CPPUNIT_ASSERT(pRefKid1100);
+ auto pObject1100 = pRefKid1100->LookupObject();
+ CPPUNIT_ASSERT(pObject1100);
+ auto pType1100
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1100->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1100->GetValue());
+ auto pS1100
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1100->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Lbl"_ostr, pS1100->GetValue());
+
+ auto pKids1100
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1100->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids1100);
+ // assume there are no MCID ref at this level
+ auto vKids1100 = pKids1100->GetElements();
+ CPPUNIT_ASSERT_EQUAL(size_t(1), vKids1100.size());
+
+ auto pRefKid11000 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids1100[0]);
+ CPPUNIT_ASSERT(pRefKid11000);
+ auto pObject11000 = pRefKid11000->LookupObject();
+ CPPUNIT_ASSERT(pObject11000);
+ auto pType11000
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11000->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType11000->GetValue());
+ auto pS11000
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11000->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pS11000->GetValue());
+
+ auto pRefKid1101 = dynamic_cast<vcl::filter::PDFReferenceElement*>(vKids110[1]);
+ CPPUNIT_ASSERT(pRefKid1101);
+ auto pObject1101 = pRefKid1101->LookupObject();
+ CPPUNIT_ASSERT(pObject1101);
+ auto pType1101
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1101->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1101->GetValue());
+ auto pS1101
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1101->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Footnote"_ostr, pS1101->GetValue());
+
+ ++nDoc;
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nDoc)>(1), nDoc);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf157182)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
+ { "PDFUACompliance", uno::Any(true) },
+ // only happens with PDF/A-1
+ { "SelectPdfVersion", uno::Any(static_cast<sal_Int32>(1)) },
+ }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+
+ saveAsPDF(u"transparentshape.fodp");
+
+ // just check this does not crash or assert
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf57423)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"Description PDF Export test .odt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ int nFigure(0);
+ int nFormula(0);
+ int nDiv(0);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject)
+ continue;
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
+ if (pType && pType->GetValue() == "StructElem")
+ {
+ auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("S"_ostr));
+ if (pS && pS->GetValue() == "Figure")
+ {
+ switch (nFigure)
+ {
+ case 2:
+ CPPUNIT_ASSERT_EQUAL(u"QR Code - Tells how to get to Mosegaard"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pObject->Lookup("Alt"_ostr))));
+ break;
+ case 0:
+ CPPUNIT_ASSERT_EQUAL(u"Title: Arrows - Description: Explains the "
+ u"different arrow appearances"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pObject->Lookup("Alt"_ostr))));
+ break;
+ case 1:
+ CPPUNIT_ASSERT_EQUAL(
+ u"My blue triangle - Does not need further description"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pObject->Lookup("Alt"_ostr))));
+ break;
+ }
+ ++nFigure;
+ }
+ if (pS && pS->GetValue() == "Formula")
+ {
+ CPPUNIT_ASSERT_EQUAL(
+ u"Equation 1 - Now we give the full description of eq 1 here"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pObject->Lookup("Alt"_ostr))));
+ ++nFormula;
+ }
+ if (pS && pS->GetValue() == "Div")
+ {
+ switch (nDiv)
+ {
+ case 0:
+ CPPUNIT_ASSERT_EQUAL(u"This frame has a description"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pObject->Lookup("Alt"_ostr))));
+ break;
+ case 1:
+ // no properties set on this
+ CPPUNIT_ASSERT(!pObject->Lookup("Alt"_ostr));
+ break;
+ case 2:
+ CPPUNIT_ASSERT_EQUAL(u"My textbox - Has a light background"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pObject->Lookup("Alt"_ostr))));
+ break;
+ case 3:
+ CPPUNIT_ASSERT_EQUAL(u"Hey! There is no alternate text for Frame "
+ u"// but maybe not needed?"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pObject->Lookup("Alt"_ostr))));
+ break;
+ }
+ ++nDiv;
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(int(3), nFigure);
+ CPPUNIT_ASSERT_EQUAL(int(1), nFormula);
+ CPPUNIT_ASSERT_EQUAL(int(4), nDiv);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf154982)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"tdf154982.odt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ int nFigure(0);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject)
+ continue;
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
+ if (pType && pType->GetValue() == "StructElem")
+ {
+ auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("S"_ostr));
+ if (pS && pS->GetValue() == "Figure")
+ {
+ switch (nFigure)
+ {
+ case 0:
+ CPPUNIT_ASSERT_EQUAL(u"Here comes the signature - Please sign here"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pObject->Lookup("Alt"_ostr))));
+ break;
+ case 1:
+ CPPUNIT_ASSERT_EQUAL(u"Home"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pObject->Lookup("Alt"_ostr))));
+ break;
+ }
+
+ // the problem was that the figures in the hell layer were not
+ // below their anchor paragraphs in the structure tree
+ auto pParentRef
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("P"_ostr));
+ CPPUNIT_ASSERT(pParentRef);
+ auto pParent(pParentRef->LookupObject());
+ CPPUNIT_ASSERT(pParent);
+ auto pParentType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pParent->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pParentType->GetValue());
+ auto pParentS
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pParent->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pParentS->GetValue());
+
+ auto pPParentRef
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pParent->Lookup("P"_ostr));
+ CPPUNIT_ASSERT(pPParentRef);
+ auto pPParent(pPParentRef->LookupObject());
+ CPPUNIT_ASSERT(pPParent);
+ auto pPParentType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pPParent->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pPParentType->GetValue());
+ auto pPParentS
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pPParent->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Document"_ostr, pPParentS->GetValue());
+ ++nFigure;
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(int(2), nFigure);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf157397)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"PDF_export_with_formcontrol.fodt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ vcl::filter::PDFObjectElement* pDocument(nullptr);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject1)
+ continue;
+ auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
+ if (pType1 && pType1->GetValue() == "StructElem")
+ {
+ auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
+ if (pS1 && pS1->GetValue() == "Document")
+ {
+ pDocument = pObject1;
+ }
+ }
+ }
+ CPPUNIT_ASSERT(pDocument);
+
+ auto pKids1 = dynamic_cast<vcl::filter::PDFArrayElement*>(pDocument->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids1);
+ // assume there are no MCID ref at this level
+ auto pKids1v = pKids1->GetElements();
+ auto pRefKid12 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1v[2]);
+ CPPUNIT_ASSERT(pRefKid12);
+ auto pObject12 = pRefKid12->LookupObject();
+ CPPUNIT_ASSERT(pObject12);
+ auto pType12 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject12->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType12->GetValue());
+ auto pS12 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject12->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Text#20body"_ostr, pS12->GetValue());
+
+ auto pKids12 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject12->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids12);
+ // assume there are no MCID ref at this level
+ auto pKids12v = pKids12->GetElements();
+ auto pRefKid120 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids12v[0]);
+ CPPUNIT_ASSERT(pRefKid120);
+ auto pObject120 = pRefKid120->LookupObject();
+ CPPUNIT_ASSERT(pObject120);
+ auto pType120 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject120->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType120->GetValue());
+ auto pS120 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject120->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pS120->GetValue());
+
+ {
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject120->Lookup("K"_ostr));
+ auto nMCID(0);
+ auto nRef(0);
+ for (size_t i = 0; i < pKids->GetElements().size(); ++i)
+ {
+ auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
+ auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
+ if (pNum)
+ {
+ ++nMCID;
+ }
+ if (pObjR)
+ {
+ ++nRef;
+ auto pOType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
+ auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObjR->LookupElement("Obj"_ostr));
+ auto pAnnot = pAnnotRef->LookupObject();
+ auto pAType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
+ auto pASubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
+ auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pAnnot->Lookup("Contents"_ostr));
+ CPPUNIT_ASSERT_EQUAL(
+ u"https://klexikon.zum.de/wiki/Kläranlage"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
+ auto pAA
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"_ostr));
+ CPPUNIT_ASSERT(pAA);
+ auto pAAType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAA->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Action"_ostr, pAAType->GetValue());
+ auto pAAS
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAA->LookupElement("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("URI"_ostr, pAAS->GetValue());
+ auto pAAURI = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
+ pAA->LookupElement("URI"_ostr));
+ CPPUNIT_ASSERT_EQUAL("https://klexikon.zum.de/wiki/Kl%C3%A4ranlage"_ostr,
+ pAAURI->GetValue());
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+ }
+
+ auto pRefKid13 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1v[3]);
+ CPPUNIT_ASSERT(pRefKid13);
+ auto pObject13 = pRefKid13->LookupObject();
+ CPPUNIT_ASSERT(pObject13);
+ auto pType13 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject13->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType13->GetValue());
+ auto pS13 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject13->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Text#20body"_ostr, pS13->GetValue());
+
+ auto pKids13 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject13->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids13);
+ // assume there are no MCID ref at this level
+ auto pKids13v = pKids13->GetElements();
+ auto pRefKid130 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids13v[0]);
+ CPPUNIT_ASSERT(pRefKid130);
+ auto pObject130 = pRefKid130->LookupObject();
+ CPPUNIT_ASSERT(pObject130);
+ auto pType130 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject130->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType130->GetValue());
+ auto pS130 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject130->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pS130->GetValue());
+
+ {
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject130->Lookup("K"_ostr));
+ auto nMCID(0);
+ auto nRef(0);
+ for (size_t i = 0; i < pKids->GetElements().size(); ++i)
+ {
+ auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
+ auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
+ if (pNum)
+ {
+ ++nMCID;
+ }
+ if (pObjR)
+ {
+ ++nRef;
+ auto pOType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
+ auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObjR->LookupElement("Obj"_ostr));
+ auto pAnnot = pAnnotRef->LookupObject();
+ auto pAType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
+ auto pASubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
+ auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pAnnot->Lookup("Contents"_ostr));
+ CPPUNIT_ASSERT_EQUAL(
+ u"https://de.wikipedia.org/wiki/Kläranlage#Mechanische_Vorreinigung"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
+ auto pAA
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"_ostr));
+ CPPUNIT_ASSERT(pAA);
+ auto pAAType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAA->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Action"_ostr, pAAType->GetValue());
+ auto pAAS
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAA->LookupElement("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("URI"_ostr, pAAS->GetValue());
+ auto pAAURI = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
+ pAA->LookupElement("URI"_ostr));
+ CPPUNIT_ASSERT_EQUAL(
+ "https://de.wikipedia.org/wiki/Kl%C3%A4ranlage#Mechanische_Vorreinigung"_ostr,
+ pAAURI->GetValue());
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+ }
+
+ auto pRefKid14 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1v[4]);
+ CPPUNIT_ASSERT(pRefKid14);
+ auto pObject14 = pRefKid14->LookupObject();
+ CPPUNIT_ASSERT(pObject14);
+ auto pType14 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject14->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType14->GetValue());
+ auto pS14 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject14->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Text#20body"_ostr, pS14->GetValue());
+
+ auto pKids14 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject14->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids14);
+ // assume there are no MCID ref at this level
+ auto pKids14v = pKids14->GetElements();
+ auto pRefKid140 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids14v[0]);
+ CPPUNIT_ASSERT(pRefKid140);
+ auto pObject140 = pRefKid140->LookupObject();
+ CPPUNIT_ASSERT(pObject140);
+ auto pType140 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject140->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType140->GetValue());
+ auto pS140 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject140->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pS140->GetValue());
+
+ {
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject140->Lookup("K"_ostr));
+ auto nMCID(0);
+ auto nRef(0);
+ for (size_t i = 0; i < pKids->GetElements().size(); ++i)
+ {
+ auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
+ auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
+ if (pNum)
+ {
+ ++nMCID;
+ }
+ if (pObjR)
+ {
+ ++nRef;
+ auto pOType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
+ auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObjR->LookupElement("Obj"_ostr));
+ auto pAnnot = pAnnotRef->LookupObject();
+ auto pAType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
+ auto pASubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
+ auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pAnnot->Lookup("Contents"_ostr));
+ CPPUNIT_ASSERT_EQUAL(
+ u"https://vr-easy.com/tour/usr/220113-virtuellerschulausflug/#pano=24"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
+ auto pAA
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"_ostr));
+ CPPUNIT_ASSERT(pAA);
+ auto pAAType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAA->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Action"_ostr, pAAType->GetValue());
+ auto pAAS
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAA->LookupElement("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("URI"_ostr, pAAS->GetValue());
+ auto pAAURI = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
+ pAA->LookupElement("URI"_ostr));
+ CPPUNIT_ASSERT_EQUAL(
+ "https://vr-easy.com/tour/usr/220113-virtuellerschulausflug/#pano=24"_ostr,
+ pAAURI->GetValue());
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+ }
+
+ auto pRefKid16 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1v[6]);
+ CPPUNIT_ASSERT(pRefKid16);
+ auto pObject16 = pRefKid16->LookupObject();
+ CPPUNIT_ASSERT(pObject16);
+ auto pType16 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject16->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType16->GetValue());
+ auto pS16 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject16->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Text#20body"_ostr, pS16->GetValue());
+
+ auto pKids16 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject16->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids16);
+ // assume there are no MCID ref at this level
+ auto pKids16v = pKids16->GetElements();
+ auto pRefKid160 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids16v[0]);
+ CPPUNIT_ASSERT(pRefKid160);
+ auto pObject160 = pRefKid160->LookupObject();
+ CPPUNIT_ASSERT(pObject160);
+ auto pType160 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject160->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType160->GetValue());
+ auto pS160 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject160->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Form"_ostr, pS160->GetValue());
+ auto pA160Dict = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pObject160->Lookup("A"_ostr));
+ CPPUNIT_ASSERT(pA160Dict);
+ auto pA160O = dynamic_cast<vcl::filter::PDFNameElement*>(pA160Dict->LookupElement("O"_ostr));
+ CPPUNIT_ASSERT(pA160O);
+ CPPUNIT_ASSERT_EQUAL("PrintField"_ostr, pA160O->GetValue());
+ auto pA160Role
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pA160Dict->LookupElement("Role"_ostr));
+ CPPUNIT_ASSERT(pA160Role);
+ CPPUNIT_ASSERT_EQUAL("tv"_ostr, pA160Role->GetValue());
+
+ {
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject160->Lookup("K"_ostr));
+ auto nMCID(0);
+ auto nRef(0);
+ for (size_t i = 0; i < pKids->GetElements().size(); ++i)
+ {
+ auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
+ auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
+ if (pNum)
+ {
+ ++nMCID;
+ }
+ if (pObjR)
+ {
+ ++nRef;
+ auto pOType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
+ auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObjR->LookupElement("Obj"_ostr));
+ auto pAnnot = pAnnotRef->LookupObject();
+ auto pAType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
+ auto pASubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Widget"_ostr, pASubtype->GetValue());
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf135192)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"tdf135192-1.fodp");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ int nTable(0);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject1)
+ continue;
+ auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
+ if (pType1 && pType1->GetValue() == "StructElem")
+ {
+ auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
+ if (pS1 && pS1->GetValue() == "Table")
+ {
+ int nTR(0);
+ auto pKids1
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids1);
+ // there can be additional children, such as MCID ref
+ for (auto pKid1 : pKids1->GetElements())
+ {
+ auto pRefKid1 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKid1);
+ if (pRefKid1)
+ {
+ auto pObject2 = pRefKid1->LookupObject();
+ if (pObject2)
+ {
+ auto pType2 = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pObject2->Lookup("Type"_ostr));
+ if (pType2 && pType2->GetValue() == "StructElem")
+ {
+ auto pS2 = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pObject2->Lookup("S"_ostr));
+ if (pS2 && pS2->GetValue() == "TR")
+ {
+ int nTD(0);
+ auto pKids2 = dynamic_cast<vcl::filter::PDFArrayElement*>(
+ pObject2->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids2);
+ for (auto pKid2 : pKids2->GetElements())
+ {
+ auto pRefKid2
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pKid2);
+ if (pRefKid2)
+ {
+ auto pObject3 = pRefKid2->LookupObject();
+ if (pObject3)
+ {
+ auto pType3
+ = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pObject3->Lookup("Type"_ostr));
+ if (pType3 && pType3->GetValue() == "StructElem")
+ {
+ auto pS3 = dynamic_cast<
+ vcl::filter::PDFNameElement*>(
+ pObject3->Lookup("S"_ostr));
+ if (nTR == 0 && pS3 && pS3->GetValue() == "TH")
+ {
+ int nOTable(0);
+ auto pAttrs = dynamic_cast<
+ vcl::filter::PDFArrayElement*>(
+ pObject3->Lookup("A"_ostr));
+ CPPUNIT_ASSERT(pAttrs != nullptr);
+ for (const auto& rAttrRef :
+ pAttrs->GetElements())
+ {
+ auto pAttrDict = dynamic_cast<
+ vcl::filter::PDFDictionaryElement*>(
+ rAttrRef);
+ CPPUNIT_ASSERT(pAttrDict != nullptr);
+ auto pOwner = dynamic_cast<
+ vcl::filter::PDFNameElement*>(
+ pAttrDict->LookupElement("O"_ostr));
+ CPPUNIT_ASSERT(pOwner != nullptr);
+ if (pOwner->GetValue() == "Table")
+ {
+ auto pScope = dynamic_cast<
+ vcl::filter::PDFNameElement*>(
+ pAttrDict->LookupElement(
+ "Scope"_ostr));
+ CPPUNIT_ASSERT(pScope != nullptr);
+ CPPUNIT_ASSERT_EQUAL(
+ "Column"_ostr,
+ pScope->GetValue());
+ ++nOTable;
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(int(1), nOTable);
+ ++nTD;
+ }
+ else if (nTR != 0 && pS3
+ && pS3->GetValue() == "TD")
+ {
+ ++nTD;
+ }
+ }
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(int(3), nTD);
+ ++nTR;
+ }
+ }
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(int(2), nTR);
+ ++nTable;
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(int(1), nTable);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf154955)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ saveAsPDF(u"grouped-shape.fodt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
+ CPPUNIT_ASSERT(pContents);
+ vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+ // Uncompress it.
+ SvMemoryStream aUncompressed;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ rObjectStream.Seek(0);
+ aZCodec.Decompress(rObjectStream, aUncompressed);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+
+ auto pStart = static_cast<const char*>(aUncompressed.GetData());
+ const char* const pEnd = pStart + aUncompressed.GetSize();
+
+ enum
+ {
+ Default,
+ Artifact,
+ Tagged
+ } state
+ = Default;
+
+ auto nLine(0);
+ auto nTagged(0);
+ auto nArtifacts(0);
+ while (true)
+ {
+ ++nLine;
+ auto const pLine = ::std::find(pStart, pEnd, '\n');
+ if (pLine == pEnd)
+ {
+ break;
+ }
+ std::string_view const line(pStart, pLine - pStart);
+ pStart = pLine + 1;
+ if (!line.empty() && line[0] != '%')
+ {
+ ::std::cerr << nLine << ": " << line << "\n";
+ if (o3tl::starts_with(line, "/Artifact "))
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
+ state = Artifact;
+ ++nArtifacts;
+ }
+ else if (o3tl::starts_with(line, "/Figure<</MCID "))
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
+ state = Tagged;
+ ++nTagged;
+ }
+ else if (line == "EMC")
+ {
+ CPPUNIT_ASSERT_MESSAGE("unexpected end", state != Default);
+ state = Default;
+ }
+ else if (nLine > 1) // first line is expected "0.1 w"
+ {
+ CPPUNIT_ASSERT_MESSAGE("unexpected content outside MCS", state != Default);
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unclosed MCS", Default, state);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nTagged)>(2), nTagged);
+ CPPUNIT_ASSERT(nArtifacts >= 1);
+
+ int nFigure(0);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject)
+ continue;
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
+ if (pType && pType->GetValue() == "StructElem")
+ {
+ auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("S"_ostr));
+ if (pS && pS->GetValue() == "Figure")
+ {
+ switch (nFigure)
+ {
+ case 0:
+ CPPUNIT_ASSERT_EQUAL(u"Two rectangles - Grouped"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pObject->Lookup("Alt"_ostr))));
+ break;
+ case 1:
+ CPPUNIT_ASSERT_EQUAL(u"these ones are green"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pObject->Lookup("Alt"_ostr))));
+ break;
+ }
+
+ auto pParentRef
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("P"_ostr));
+ CPPUNIT_ASSERT(pParentRef);
+ auto pParent(pParentRef->LookupObject());
+ CPPUNIT_ASSERT(pParent);
+ auto pParentType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pParent->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pParentType->GetValue());
+ auto pParentS
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pParent->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pParentS->GetValue());
+
+ ++nFigure;
+ }
+ }
+ }
+ // the problem was that there were 4 shapes (the sub-shapes of the 2 groups)
+ CPPUNIT_ASSERT_EQUAL(int(2), nFigure);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf155190)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+
+ saveAsPDF(u"tdf155190.odt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ auto nDiv(0);
+ auto nFigure(0);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject1)
+ continue;
+ auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
+
+ auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
+ // start with the text box
+ if (pType1 && pType1->GetValue() == "StructElem" && pS1 && pS1->GetValue() == "Div")
+ {
+ ++nDiv;
+ auto pKids1 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids1);
+ for (auto pKid1 : pKids1->GetElements())
+ {
+ auto pRefKid1 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKid1);
+ if (pRefKid1)
+ {
+ auto pObject2 = pRefKid1->LookupObject();
+ CPPUNIT_ASSERT(pObject2);
+ auto pType2
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject2->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pType2);
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType2->GetValue());
+ auto pS2
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject2->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("FigureCaption"_ostr, pS2->GetValue());
+ auto pKids2
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject2->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids2);
+ // there are additional children, MCID ref
+ for (auto pKid2 : pKids2->GetElements())
+ {
+ auto pRefKid2 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKid2);
+ if (pRefKid2)
+ {
+ auto pObject3 = pRefKid2->LookupObject();
+ CPPUNIT_ASSERT(pObject3);
+ auto pType3 = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pObject3->Lookup("Type"_ostr));
+ if (pType3 && pType3->GetValue() == "StructElem")
+ {
+ auto pS3 = dynamic_cast<vcl::filter::PDFNameElement*>(
+ pObject3->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Figure"_ostr, pS3->GetValue());
+ auto pAlt = dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pObject3->Lookup("Alt"_ostr));
+ CPPUNIT_ASSERT_EQUAL(
+ OUString("Picture of apples"),
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAlt));
+ auto pKids3 = dynamic_cast<vcl::filter::PDFArrayElement*>(
+ pObject3->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids3);
+ // the problem was that this didn't reference an MCID
+ CPPUNIT_ASSERT(!pKids3->GetElements().empty());
+ ++nFigure;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nDiv)>(1), nDiv);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nDiv)>(1), nFigure);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testMediaShapeAnnot)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+
+ saveAsPDF(u"vid.odt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
+ CPPUNIT_ASSERT(pAnnots);
+
+ // There should be one annotation
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pAnnots->GetElements().size());
+ auto pAnnotReference
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[0]);
+ CPPUNIT_ASSERT(pAnnotReference);
+ // check /Annot - produced by sw
+ vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject();
+ CPPUNIT_ASSERT(pAnnot);
+ CPPUNIT_ASSERT_EQUAL(
+ "Annot"_ostr,
+ static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr))->GetValue());
+ CPPUNIT_ASSERT_EQUAL(
+ "Screen"_ostr,
+ static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr))->GetValue());
+
+ auto pA = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"_ostr));
+ CPPUNIT_ASSERT(pA);
+ auto pR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pA->LookupElement("R"_ostr));
+ CPPUNIT_ASSERT(pR);
+ auto pC = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pR->LookupElement("C"_ostr));
+ CPPUNIT_ASSERT(pC);
+ auto pCT = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pC->LookupElement("CT"_ostr));
+ CPPUNIT_ASSERT_EQUAL("video/webm"_ostr, pCT->GetValue());
+ auto pD = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pC->LookupElement("D"_ostr));
+ CPPUNIT_ASSERT(pD);
+ auto pDesc = dynamic_cast<vcl::filter::PDFHexStringElement*>(pD->LookupElement("Desc"_ostr));
+ CPPUNIT_ASSERT(pDesc);
+ CPPUNIT_ASSERT_EQUAL(OUString("alternativloser text\nand some description"),
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pDesc));
+ auto pAlt = dynamic_cast<vcl::filter::PDFArrayElement*>(pC->LookupElement("Alt"_ostr));
+ CPPUNIT_ASSERT(pAlt);
+ auto pLang = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pAlt->GetElement(0));
+ CPPUNIT_ASSERT_EQUAL(""_ostr, pLang->GetValue());
+ auto pAltText = dynamic_cast<vcl::filter::PDFHexStringElement*>(pAlt->GetElement(1));
+ CPPUNIT_ASSERT_EQUAL(OUString("alternativloser text\nand some description"),
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAltText));
+
+ auto pStructParent
+ = dynamic_cast<vcl::filter::PDFNumberElement*>(pAnnot->Lookup("StructParent"_ostr));
+ CPPUNIT_ASSERT(pStructParent);
+
+ vcl::filter::PDFReferenceElement* pStructElemRef(nullptr);
+
+ // check ParentTree to find StructElem
+ auto nRoots(0);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject1)
+ continue;
+ auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
+ if (pType1 && pType1->GetValue() == "StructTreeRoot")
+ {
+ ++nRoots;
+ auto pParentTree = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObject1->Lookup("ParentTree"_ostr));
+ CPPUNIT_ASSERT(pParentTree);
+ auto pNumTree = pParentTree->LookupObject();
+ CPPUNIT_ASSERT(pNumTree);
+ auto pNums = dynamic_cast<vcl::filter::PDFArrayElement*>(pNumTree->Lookup("Nums"_ostr));
+ CPPUNIT_ASSERT(pNums);
+ auto nFound(0);
+ for (size_t i = 0; i < pNums->GetElements().size(); i += 2)
+ {
+ auto pI = dynamic_cast<vcl::filter::PDFNumberElement*>(pNums->GetElement(i));
+ if (pI->GetValue() == pStructParent->GetValue())
+ {
+ ++nFound;
+ CPPUNIT_ASSERT(i < pNums->GetElements().size() - 1);
+ pStructElemRef
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pNums->GetElement(i + 1));
+ CPPUNIT_ASSERT(pStructElemRef);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nFound)>(1), nFound);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRoots)>(1), nRoots);
+
+ // check /StructElem - produced by drawinglayer
+ CPPUNIT_ASSERT(pStructElemRef);
+ auto pStructElem(pStructElemRef->LookupObject());
+ CPPUNIT_ASSERT(pStructElem);
+
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pStructElem->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType->GetValue());
+ auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pStructElem->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pS->GetValue());
+ auto pSEAlt = dynamic_cast<vcl::filter::PDFHexStringElement*>(pStructElem->Lookup("Alt"_ostr));
+ CPPUNIT_ASSERT_EQUAL(OUString("alternativloser text - and some description"),
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pSEAlt));
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pStructElem->Lookup("K"_ostr));
+ auto nMCID(0);
+ auto nRef(0);
+ for (size_t i = 0; i < pKids->GetElements().size(); ++i)
+ {
+ auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
+ auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
+ if (pNum)
+ {
+ ++nMCID;
+ }
+ if (pObjR)
+ {
+ ++nRef;
+ auto pOType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
+ auto pAnnotRef
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObjR->LookupElement("Obj"_ostr));
+ CPPUNIT_ASSERT_EQUAL(pAnnot, pAnnotRef->LookupObject());
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testFlyFrameHyperlinkAnnot)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+
+ saveAsPDF(u"image-hyperlink-alttext.fodt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
+ CPPUNIT_ASSERT(pAnnots);
+
+ // There should be one annotation
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pAnnots->GetElements().size());
+ auto pAnnotReference
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[0]);
+ CPPUNIT_ASSERT(pAnnotReference);
+ // check /Annot - produced by sw
+ vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject();
+ CPPUNIT_ASSERT(pAnnot);
+ CPPUNIT_ASSERT_EQUAL(
+ "Annot"_ostr,
+ static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr))->GetValue());
+ CPPUNIT_ASSERT_EQUAL(
+ "Link"_ostr,
+ static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr))->GetValue());
+
+ auto pContents
+ = dynamic_cast<vcl::filter::PDFHexStringElement*>(pAnnot->Lookup("Contents"_ostr));
+ CPPUNIT_ASSERT_EQUAL(OUString("Image2"),
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pContents));
+
+ auto pStructParent
+ = dynamic_cast<vcl::filter::PDFNumberElement*>(pAnnot->Lookup("StructParent"_ostr));
+ CPPUNIT_ASSERT(pStructParent);
+
+ vcl::filter::PDFReferenceElement* pStructElemRef(nullptr);
+
+ // check ParentTree to find StructElem
+ auto nRoots(0);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject1)
+ continue;
+ auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
+ if (pType1 && pType1->GetValue() == "StructTreeRoot")
+ {
+ ++nRoots;
+ auto pParentTree = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObject1->Lookup("ParentTree"_ostr));
+ CPPUNIT_ASSERT(pParentTree);
+ auto pNumTree = pParentTree->LookupObject();
+ CPPUNIT_ASSERT(pNumTree);
+ auto pNums = dynamic_cast<vcl::filter::PDFArrayElement*>(pNumTree->Lookup("Nums"_ostr));
+ CPPUNIT_ASSERT(pNums);
+ auto nFound(0);
+ for (size_t i = 0; i < pNums->GetElements().size(); i += 2)
+ {
+ auto pI = dynamic_cast<vcl::filter::PDFNumberElement*>(pNums->GetElement(i));
+ if (pI->GetValue() == pStructParent->GetValue())
+ {
+ ++nFound;
+ CPPUNIT_ASSERT(i < pNums->GetElements().size() - 1);
+ pStructElemRef
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pNums->GetElement(i + 1));
+ CPPUNIT_ASSERT(pStructElemRef);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nFound)>(1), nFound);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRoots)>(1), nRoots);
+
+ // check /StructElem - produced by sw painting code
+ CPPUNIT_ASSERT(pStructElemRef);
+ auto pStructElem(pStructElemRef->LookupObject());
+ CPPUNIT_ASSERT(pStructElem);
+
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pStructElem->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType->GetValue());
+ auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pStructElem->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Link"_ostr, pS->GetValue());
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pStructElem->Lookup("K"_ostr));
+ auto nMCID(0);
+ auto nRef(0);
+ for (size_t i = 0; i < pKids->GetElements().size(); ++i)
+ {
+ auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
+ auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
+ if (pNum)
+ {
+ ++nMCID;
+ }
+ if (pObjR)
+ {
+ ++nRef;
+ auto pOType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
+ auto pAnnotRef
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObjR->LookupElement("Obj"_ostr));
+ CPPUNIT_ASSERT_EQUAL(pAnnot, pAnnotRef->LookupObject());
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+
+ // the Link is inside a Figure
+ auto pParentRef
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pStructElem->Lookup("P"_ostr));
+ CPPUNIT_ASSERT(pParentRef);
+ auto pParent(pParentRef->LookupObject());
+ CPPUNIT_ASSERT(pParent);
+ auto pParentType = dynamic_cast<vcl::filter::PDFNameElement*>(pParent->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pParentType->GetValue());
+ auto pParentS = dynamic_cast<vcl::filter::PDFNameElement*>(pParent->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Figure"_ostr, pParentS->GetValue());
+ auto pAlt = dynamic_cast<vcl::filter::PDFHexStringElement*>(pParent->Lookup("Alt"_ostr));
+ CPPUNIT_ASSERT_EQUAL(OUString("Ship drawing - Very cute"),
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAlt));
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testFormControlAnnot)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+
+ saveAsPDF(u"formcontrol.fodt");
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
+ CPPUNIT_ASSERT(pAnnots);
+
+ // There should be one annotation
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pAnnots->GetElements().size());
+ auto pAnnotReference
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[0]);
+ CPPUNIT_ASSERT(pAnnotReference);
+ // check /Annot
+ vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject();
+ CPPUNIT_ASSERT(pAnnot);
+ CPPUNIT_ASSERT_EQUAL(
+ "Annot"_ostr,
+ static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr))->GetValue());
+ CPPUNIT_ASSERT_EQUAL(
+ "Widget"_ostr,
+ static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr))->GetValue());
+ auto pT = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pAnnot->Lookup("T"_ostr));
+ CPPUNIT_ASSERT(pT);
+ CPPUNIT_ASSERT_EQUAL("Check Box 1"_ostr, pT->GetValue());
+ auto pTU = dynamic_cast<vcl::filter::PDFHexStringElement*>(pAnnot->Lookup("TU"_ostr));
+ CPPUNIT_ASSERT(pTU);
+ CPPUNIT_ASSERT_EQUAL(OUString("helpful text"),
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pTU));
+
+ auto pStructParent
+ = dynamic_cast<vcl::filter::PDFNumberElement*>(pAnnot->Lookup("StructParent"_ostr));
+ CPPUNIT_ASSERT(pStructParent);
+
+ vcl::filter::PDFReferenceElement* pStructElemRef(nullptr);
+
+ // check ParentTree to find StructElem
+ auto nRoots(0);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject1)
+ continue;
+ auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
+ if (pType1 && pType1->GetValue() == "StructTreeRoot")
+ {
+ ++nRoots;
+ auto pParentTree = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pObject1->Lookup("ParentTree"_ostr));
+ CPPUNIT_ASSERT(pParentTree);
+ auto pNumTree = pParentTree->LookupObject();
+ CPPUNIT_ASSERT(pNumTree);
+ auto pNums = dynamic_cast<vcl::filter::PDFArrayElement*>(pNumTree->Lookup("Nums"_ostr));
+ CPPUNIT_ASSERT(pNums);
+ auto nFound(0);
+ for (size_t i = 0; i < pNums->GetElements().size(); i += 2)
+ {
+ auto pI = dynamic_cast<vcl::filter::PDFNumberElement*>(pNums->GetElement(i));
+ if (pI->GetValue() == pStructParent->GetValue())
+ {
+ ++nFound;
+ CPPUNIT_ASSERT(i < pNums->GetElements().size() - 1);
+ pStructElemRef
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pNums->GetElement(i + 1));
+ CPPUNIT_ASSERT(pStructElemRef);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nFound)>(1), nFound);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRoots)>(1), nRoots);
+
+ // check /StructElem
+ CPPUNIT_ASSERT(pStructElemRef);
+ auto pStructElem(pStructElemRef->LookupObject());
+ CPPUNIT_ASSERT(pStructElem);
+
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pStructElem->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType->GetValue());
+ auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pStructElem->Lookup("S"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Form"_ostr, pS->GetValue());
+ auto pAlt = dynamic_cast<vcl::filter::PDFHexStringElement*>(pStructElem->Lookup("Alt"_ostr));
+ CPPUNIT_ASSERT_EQUAL(OUString("textuelle alternative - a box to check"),
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAlt));
+ auto pA = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pStructElem->Lookup("A"_ostr));
+ CPPUNIT_ASSERT(pA);
+ auto pO = dynamic_cast<vcl::filter::PDFNameElement*>(pA->LookupElement("O"_ostr));
+ CPPUNIT_ASSERT(pO);
+ CPPUNIT_ASSERT_EQUAL("PrintField"_ostr, pO->GetValue());
+ auto pRole = dynamic_cast<vcl::filter::PDFNameElement*>(pA->LookupElement("Role"_ostr));
+ CPPUNIT_ASSERT(pRole);
+ CPPUNIT_ASSERT_EQUAL("cb"_ostr, pRole->GetValue());
+ auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pStructElem->Lookup("K"_ostr));
+ auto nMCID(0);
+ auto nRef(0);
+ for (size_t i = 0; i < pKids->GetElements().size(); ++i)
+ {
+ auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
+ auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
+ if (pNum)
+ {
+ ++nMCID;
+ }
+ if (pObjR)
+ {
+ ++nRef;
+ auto pOType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
+ CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
+ auto pAnnotRef
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObjR->LookupElement("Obj"_ostr));
+ CPPUNIT_ASSERT_EQUAL(pAnnot, pAnnotRef->LookupObject());
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf142129)
+{
+ loadFromFile(u"master.odm");
+
+ // update linked section
+ dispatchCommand(mxComponent, ".uno:UpdateAllLinks", {});
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // Enable Outlines export
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "ExportBookmarks", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result.
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ auto* pCatalog = aDocument.GetCatalog();
+ CPPUNIT_ASSERT(pCatalog);
+ auto* pCatalogDictionary = pCatalog->GetDictionary();
+ CPPUNIT_ASSERT(pCatalogDictionary);
+ auto* pOutlinesObject = pCatalogDictionary->LookupObject("Outlines"_ostr);
+ CPPUNIT_ASSERT(pOutlinesObject);
+ auto* pOutlinesDictionary = pOutlinesObject->GetDictionary();
+#if 0
+ // Type isn't actually written currently
+ auto* pType
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pOutlinesDictionary->LookupElement("Type"));
+ CPPUNIT_ASSERT(pType);
+ CPPUNIT_ASSERT_EQUAL(OString("Outlines"), pType->GetValue());
+#endif
+
+ auto* pFirst = dynamic_cast<vcl::filter::PDFReferenceElement*>(
+ pOutlinesDictionary->LookupElement("First"_ostr));
+ CPPUNIT_ASSERT(pFirst);
+ auto* pFirstD = pFirst->LookupObject()->GetDictionary();
+ CPPUNIT_ASSERT(pFirstD);
+ //CPPUNIT_ASSERT_EQUAL(OString("Outlines"), dynamic_cast<vcl::filter::PDFNameElement*>(pFirstD->LookupElement("Type"))->GetValue());
+ CPPUNIT_ASSERT_EQUAL(u"Preface"_ustr, ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pFirstD->LookupElement("Title"_ostr))));
+
+ auto* pFirst1
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFirstD->LookupElement("First"_ostr));
+ CPPUNIT_ASSERT(pFirst1);
+ auto* pFirst1D = pFirst1->LookupObject()->GetDictionary();
+ CPPUNIT_ASSERT(pFirst1D);
+ // here is a hidden section with headings "Copyright" etc.; check that
+ // there are no outline entries for it
+ //CPPUNIT_ASSERT_EQUAL(OString("Outlines"), dynamic_cast<vcl::filter::PDFNameElement*>(pFirst1D->LookupElement("Type"))->GetValue());
+ CPPUNIT_ASSERT_EQUAL(u"Who is this book for?"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pFirst1D->LookupElement("Title"_ostr))));
+
+ auto* pFirst2
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFirst1D->LookupElement("Next"_ostr));
+ auto* pFirst2D = pFirst2->LookupObject()->GetDictionary();
+ CPPUNIT_ASSERT(pFirst2D);
+ //CPPUNIT_ASSERT_EQUAL(OString("Outlines"), dynamic_cast<vcl::filter::PDFNameElement*>(pFirst2D->LookupElement("Type"))->GetValue());
+ CPPUNIT_ASSERT_EQUAL(u"What\u2019s in this book?"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pFirst2D->LookupElement("Title"_ostr))));
+
+ auto* pFirst3
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFirst2D->LookupElement("Next"_ostr));
+ auto* pFirst3D = pFirst3->LookupObject()->GetDictionary();
+ CPPUNIT_ASSERT(pFirst3D);
+ //CPPUNIT_ASSERT_EQUAL(OString("Outlines"), dynamic_cast<vcl::filter::PDFNameElement*>(pFirst3D->LookupElement("Type"))->GetValue());
+ CPPUNIT_ASSERT_EQUAL(u"Minimum requirements for using LibreOffice"_ustr,
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(
+ pFirst3D->LookupElement("Title"_ostr))));
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<vcl::filter::PDFElement*>(nullptr),
+ pFirst3D->LookupElement("Next"_ostr));
+ CPPUNIT_ASSERT_EQUAL(static_cast<vcl::filter::PDFElement*>(nullptr),
+ pFirstD->LookupElement("Next"_ostr));
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testPdfImageRotate180)
+{
+ // Create an empty document.
+ mxComponent = loadFromDesktop("private:factory/swriter");
+ uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
+ uno::Reference<text::XText> xText = xTextDocument->getText();
+ uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
+
+ // Insert the PDF image.
+ uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xGraphicObject(
+ xFactory->createInstance("com.sun.star.text.TextGraphicObject"), uno::UNO_QUERY);
+ OUString aURL = createFileURL(u"pdf-image-rotate-180.pdf");
+ xGraphicObject->setPropertyValue("GraphicURL", uno::Any(aURL));
+ uno::Reference<drawing::XShape> xShape(xGraphicObject, uno::UNO_QUERY);
+ xShape->setSize(awt::Size(1000, 1000));
+ uno::Reference<text::XTextContent> xTextContent(xGraphicObject, uno::UNO_QUERY);
+ xText->insertTextContent(xCursor->getStart(), xTextContent, /*bAbsorb=*/false);
+
+ // Save as PDF.
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+ // Make sure that the page -> form -> form has a child image.
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+ CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount());
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(0);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pPageObject->getType());
+ // 2: white background and the actual object.
+ CPPUNIT_ASSERT_EQUAL(2, pPageObject->getFormObjectCount());
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pFormObject = pPageObject->getFormObject(1);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pFormObject->getType());
+ CPPUNIT_ASSERT_EQUAL(1, pFormObject->getFormObjectCount());
+
+ // Check if the inner form object (original page object in the pdf image) has the correct
+ // rotation.
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pInnerFormObject = pFormObject->getFormObject(0);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pInnerFormObject->getType());
+ CPPUNIT_ASSERT_EQUAL(1, pInnerFormObject->getFormObjectCount());
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pImage = pInnerFormObject->getFormObject(0);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Image, pImage->getType());
+ basegfx::B2DHomMatrix aMat = pInnerFormObject->getMatrix();
+ basegfx::B2DTuple aScale;
+ basegfx::B2DTuple aTranslate;
+ double fRotate = 0;
+ double fShearX = 0;
+ aMat.decompose(aScale, aTranslate, fRotate, fShearX);
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: -1
+ // - Actual : 1
+ // i.e. the 180 degrees rotation didn't happen (via a combination of horizontal + vertical
+ // flip).
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, aScale.getX(), 0.01);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf144222)
+{
+// Assume Windows has the font for U+4E2D
+#ifdef _WIN32
+ aMediaDescriptor["FilterName"] <<= OUString("calc_pdf_Export");
+ saveAsPDF(u"tdf144222.ods");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
+ CPPUNIT_ASSERT(pTextPage);
+
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ const OUString sChar = u"\u4E2D"_ustr;
+ basegfx::B2DRectangle aRect1, aRect2;
+ int nCount = 0;
+
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPdfPageObject = pPdfPage->getObject(i);
+ if (pPdfPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
+ {
+ ++nCount;
+ OUString sText = pPdfPageObject->getText(pTextPage);
+ if (sText == sChar)
+ aRect1 = pPdfPageObject->getBounds();
+ else
+ aRect2 = pPdfPageObject->getBounds();
+ }
+ }
+
+ CPPUNIT_ASSERT_EQUAL(2, nCount);
+ CPPUNIT_ASSERT(!aRect1.isEmpty());
+ CPPUNIT_ASSERT(!aRect2.isEmpty());
+ CPPUNIT_ASSERT(!aRect1.overlaps(aRect2));
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf145873)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
+ saveAsPDF(u"tdf145873.pptx");
+
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+ int nPageObjectCount = pPdfPage->getObjectCount();
+
+ // tdf#145873: Without the fix #1 in place, this test would have failed with
+ // - Expected: 66
+ // - Actual : 3
+ CPPUNIT_ASSERT_EQUAL(66, nPageObjectCount);
+
+ auto pObject = pPdfPage->getObject(4);
+ CPPUNIT_ASSERT_MESSAGE("no object", pObject != nullptr);
+
+ // tdf#145873: Without the fix #2 in place, this test would have failed with
+ // - Expected: 13.40
+ // - Actual : 3.57...
+ // - Delta : 0.1
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(13.40, pObject->getBounds().getWidth(), 0.1);
+ // - Expected: 13.79
+ // - Actual : 3.74...
+ // - Delta : 0.1
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(13.79, pObject->getBounds().getHeight(), 0.1);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testPdfImageHyperlink)
+{
+ // Given a Draw file, containing a PDF image, which has a hyperlink in it:
+ aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export");
+
+ // When saving to PDF:
+ saveAsPDF(u"pdf-image-hyperlink.odg");
+
+ // Then make sure that link is preserved:
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+ // Without the accompanying fix in place, this test would have failed, the hyperlink of the PDF
+ // image was lost.
+ CPPUNIT_ASSERT(pPdfPage->hasLinks());
+
+ // Also test the precision of the form XObject.
+ // Given a full-page form XObject, page height is 27.94 cm (792 points):
+ // When writing the reciprocal of the object height to PDF:
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pFormObject;
+ for (int i = 0; i < pPdfPage->getObjectCount(); ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pObject = pPdfPage->getObject(i);
+ if (pObject->getType() == vcl::pdf::PDFPageObjectType::Form)
+ {
+ pFormObject = std::move(pObject);
+ break;
+ }
+ }
+ CPPUNIT_ASSERT(pFormObject);
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pInnerFormObject;
+ for (int i = 0; i < pFormObject->getFormObjectCount(); ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pObject = pFormObject->getFormObject(i);
+ if (pObject->getType() == vcl::pdf::PDFPageObjectType::Form)
+ {
+ pInnerFormObject = std::move(pObject);
+ break;
+ }
+ }
+ CPPUNIT_ASSERT(pInnerFormObject);
+ // Then make sure that enough digits are used, so the point size is unchanged:
+ basegfx::B2DHomMatrix aMatrix = pInnerFormObject->getMatrix();
+ basegfx::B2DTuple aScale;
+ basegfx::B2DTuple aTranslate;
+ double fRotate{};
+ double fShearX{};
+ aMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 0.0012626264
+ // - Actual : 0.00126
+ // i.e. the rounded reciprocal was 794 points, not the original 792.
+ CPPUNIT_ASSERT_EQUAL(0.0012626264, rtl::math::round(aScale.getY(), 10));
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testURIs)
+{
+ struct
+ {
+ OUString in;
+ OString out;
+ bool relativeFsys;
+ } URIs[] = { {
+ "http://example.com/",
+ "http://example.com/"_ostr,
+ true,
+ },
+ {
+ "file://localfile.odt/",
+ "file://localfile.odt/"_ostr,
+ true,
+ },
+ {
+ // tdf 143216
+ "http://username:password@example.com",
+ "http://username:password@example.com"_ostr,
+ true,
+ },
+ {
+ "git://git.example.org/project/example",
+ "git://git.example.org/project/example"_ostr,
+ true,
+ },
+ {
+ // The odt/pdf gets substituted due to 'ConvertOOoTargetToPDFTarget'
+ "filebypath.odt",
+ "filebypath.pdf"_ostr,
+ true,
+ },
+ {
+ // The odt/pdf gets substituted due to 'ConvertOOoTargetToPDFTarget'
+ // but this time with ExportLinksRelativeFsys off the path is added
+ "filebypath.odt",
+ OUStringToOString(utl::GetTempNameBaseDirectory(), RTL_TEXTENCODING_UTF8)
+ + "filebypath.pdf",
+ false,
+ },
+ {
+ // This also gets made relative due to 'ExportLinksRelativeFsys'
+ utl::GetTempNameBaseDirectory() + "fileintempdir.odt",
+ "fileintempdir.pdf"_ostr,
+ true,
+ } };
+
+ // Create an empty document.
+ // Note: The test harness gets very upset if we try and create multiple
+ // documents, or recreate it; so reuse one instance for all the links
+ mxComponent = loadFromDesktop("private:factory/swriter");
+ uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
+ uno::Reference<text::XText> xText = xTextDocument->getText();
+ uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
+ xText->insertString(xCursor, "Test pdf", /*bAbsorb=*/false);
+
+ // Set the name so it can do relative name replacement
+ uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
+ xModel->attachResource(maTempFile.GetURL(), xModel->getArgs());
+
+ for (unsigned int i = 0; i < (sizeof(URIs) / sizeof(URIs[0])); i++)
+ {
+ // Test the filename rewriting
+ uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
+ { "ExportLinksRelativeFsys", uno::Any(URIs[i].relativeFsys) },
+ { "ConvertOOoTargetToPDFTarget", uno::Any(true) },
+ }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+
+ // Add a link (based on testNestedHyperlink in rtfexport3)
+ xCursor->gotoStart(/*bExpand=*/false);
+ xCursor->gotoEnd(/*bExpand=*/true);
+ uno::Reference<beans::XPropertySet> xCursorProps(xCursor, uno::UNO_QUERY);
+ xCursorProps->setPropertyValue("HyperLinkURL", uno::Any(URIs[i].in));
+
+ // Save as PDF.
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Use the filter rather than the pdfium route, as per the tdf105093 test, it's
+ // easier to parse the annotations
+ vcl::filter::PDFDocument aDocument;
+
+ // Parse the export result.
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+ auto pAnnots
+ = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
+ CPPUNIT_ASSERT(pAnnots);
+
+ // There should be one annotation
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pAnnots->GetElements().size());
+ auto pAnnotReference
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[0]);
+ CPPUNIT_ASSERT(pAnnotReference);
+ vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject();
+ CPPUNIT_ASSERT(pAnnot);
+ // We're expecting something like /Type /Annot /A << /Type /Action /S /URI /URI (path)
+ CPPUNIT_ASSERT_EQUAL(
+ "Annot"_ostr,
+ static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr))->GetValue());
+ CPPUNIT_ASSERT_EQUAL(
+ "Link"_ostr,
+ static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr))->GetValue());
+ auto pAction = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"_ostr));
+ CPPUNIT_ASSERT(pAction);
+ auto pURIElem = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
+ pAction->LookupElement("URI"_ostr));
+ CPPUNIT_ASSERT(pURIElem);
+ // Check it matches
+ CPPUNIT_ASSERT_EQUAL(URIs[i].out, pURIElem->GetValue());
+ // tdf#148934 check a11y
+ CPPUNIT_ASSERT_EQUAL(
+ OUString("Test pdf"),
+ ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
+ *dynamic_cast<vcl::filter::PDFHexStringElement*>(pAnnot->Lookup("Contents"_ostr))));
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testPdfImageAnnots)
+{
+ // Given a document with a PDF image that has 2 comments (popup, text) and a hyperlink:
+ aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export");
+
+ // When saving to PDF:
+ saveAsPDF(u"pdf-image-annots.odg");
+
+ // Then make sure only the hyperlink is kept, since Draw itself has its own comments:
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 1
+ // - Actual : 3
+ // i.e. not only the hyperlink but also the 2 comments were exported, leading to duplication.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getAnnotationCount());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testPdfImageEncryption)
+{
+ // Given an empty document, with an inserted PDF image:
+ mxComponent = loadFromDesktop("private:factory/swriter");
+ uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
+ uno::Reference<text::XText> xText = xTextDocument->getText();
+ uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
+ uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xGraphicObject(
+ xFactory->createInstance("com.sun.star.text.TextGraphicObject"), uno::UNO_QUERY);
+ OUString aURL = createFileURL(u"rectangles.pdf");
+ xGraphicObject->setPropertyValue("GraphicURL", uno::Any(aURL));
+ uno::Reference<drawing::XShape> xShape(xGraphicObject, uno::UNO_QUERY);
+ xShape->setSize(awt::Size(1000, 1000));
+ uno::Reference<text::XTextContent> xTextContent(xGraphicObject, uno::UNO_QUERY);
+ xText->insertTextContent(xCursor->getStart(), xTextContent, /*bAbsorb=*/false);
+
+ // When saving as encrypted PDF:
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ uno::Sequence<beans::PropertyValue> aFilterData = {
+ comphelper::makePropertyValue("EncryptFile", true),
+ comphelper::makePropertyValue("DocumentOpenPassword", OUString("secret")),
+ };
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Then make sure that the image is not lost:
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport("secret"_ostr);
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+ CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount());
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(0);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pPageObject->getType());
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 2
+ // - Actual : 0
+ // i.e. instead of the white background and the actual form child, the image was lost due to
+ // missing encryption.
+ CPPUNIT_ASSERT_EQUAL(2, pPageObject->getFormObjectCount());
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testBitmapScaledown)
+{
+ // FIXME: the DPI check should be removed when either (1) the test is fixed to work with
+ // non-default DPI; or (2) unit tests on Windows are made to use svp VCL plugin.
+ if (!IsDefaultDPI())
+ return;
+
+ // Given a document with an upscaled and rotated barcode bitmap in it:
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+
+ // When saving as PDF:
+ saveAsPDF(u"bitmap-scaledown.odt");
+
+ // Then verify that the bitmap is not downscaled:
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
+ if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
+ continue;
+
+ std::unique_ptr<vcl::pdf::PDFiumBitmap> pBitmap = pPageObject->getImageBitmap();
+ CPPUNIT_ASSERT(pBitmap);
+ // In-file sizes: good is 2631x380, bad is 1565x14.
+ int nWidth = pBitmap->getWidth();
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 2616
+ // - Actual : 1565
+ // i.e. the bitmap in the pdf result was small enough to be blurry.
+ CPPUNIT_ASSERT_EQUAL(2616, nWidth);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf139627)
+{
+#if HAVE_MORE_FONTS
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"justified-arabic-kashida.odt");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ // 7 objects, 3 text, others are path
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ CPPUNIT_ASSERT_EQUAL(7, nPageObjectCount);
+
+ // 3 text objects
+ OUString sText[3];
+
+ /* With "Noto Sans Arabic" font, these are the X ranges on Linux:
+ 0: ( 61.75 - 415.94)
+ 1: (479.70 - 422.40)
+ 2: (209.40 - 453.2)
+ */
+ basegfx::B2DRectangle aRect[3];
+
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject;
+
+ int nTextObjectCount = 0;
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ pPageObject = pPdfPage->getObject(i);
+ CPPUNIT_ASSERT_MESSAGE("no object", pPageObject != nullptr);
+ if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
+ {
+ sText[nTextObjectCount] = pPageObject->getText(pTextPage);
+ aRect[nTextObjectCount] = pPageObject->getBounds();
+ ++nTextObjectCount;
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(3, nTextObjectCount);
+
+ // Text: جÙـرم (which means "mass" in Persian)
+ // Rendered as (left to right): "reh + mim" - "kasreh" - "jeh + tatweel"
+ int rehmim = 0, kasreh = 1, jehtatweel = 2;
+
+ CPPUNIT_ASSERT_EQUAL(u"رم"_ustr, sText[rehmim].trim());
+ CPPUNIT_ASSERT_EQUAL(u""_ustr, sText[kasreh].trim());
+ CPPUNIT_ASSERT_EQUAL(u""_ustr, sText[jehtatweel].trim());
+
+ // "Kasreh" should be within "jeh" character
+ CPPUNIT_ASSERT_GREATER(aRect[jehtatweel].getMinX(), aRect[kasreh].getMinX());
+ CPPUNIT_ASSERT_LESS(aRect[jehtatweel].getMaxX(), aRect[kasreh].getMaxX());
+
+ // "Tatweel" should cover "jeh" and "reh"+"mim" to avoid gap
+ // Checking right gap
+ //CPPUNIT_ASSERT_GREATER(aRect[jehtatweel].getMinX(), aRect[tatweel].getMaxX());
+ // Checking left gap
+ // Kashida fails to reach to rehmim before the series of patches starting
+ // with 3901e029bd39575f700e69a73818565d62226a23. The visible symptom is
+ // a gap in the left of Kashida.
+ CPPUNIT_ASSERT_LESS(aRect[rehmim].getMaxX(), aRect[jehtatweel].getMinX());
+
+ // Overlappings of Kashida and surrounding characters is ~9% of the width
+ // of the "jeh" character, while using "Noto Arabic Sans" font in this
+ // specific example.
+ // We set the hard limit of 10% here.
+ CPPUNIT_ASSERT_LESS(0.1, fabs(aRect[rehmim].getMaxX() - aRect[jehtatweel].getMinX())
+ / aRect[jehtatweel].getWidth());
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testRexportRefToKids)
+{
+ // We need to enable PDFium import (and make sure to disable after the test)
+ bool bResetEnvVar = false;
+ if (getenv("LO_IMPORT_USE_PDFIUM") == nullptr)
+ {
+ bResetEnvVar = true;
+ osl_setEnvironment(OUString("LO_IMPORT_USE_PDFIUM").pData, OUString("1").pData);
+ }
+ comphelper::ScopeGuard aPDFiumEnvVarGuard([&]() {
+ if (bResetEnvVar)
+ osl_clearEnvironment(OUString("LO_IMPORT_USE_PDFIUM").pData);
+ });
+
+ // Load the PDF and save as PDF
+ vcl::filter::PDFDocument aDocument;
+ load(u"ref-to-kids.pdf", aDocument);
+
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(size_t(5), aPages.size());
+
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
+ CPPUNIT_ASSERT(pResources);
+
+ auto pXObjects
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
+ CPPUNIT_ASSERT(pXObjects);
+
+ // Without the fix LookupObject for all /Im's will fail.
+ for (auto const& rPair : pXObjects->GetItems())
+ {
+ if (rPair.first.startsWith("Im"))
+ CPPUNIT_ASSERT(pXObjects->LookupObject(rPair.first));
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testRexportFilterSingletonArray)
+{
+ // We need to enable PDFium import (and make sure to disable after the test)
+ bool bResetEnvVar = false;
+ if (getenv("LO_IMPORT_USE_PDFIUM") == nullptr)
+ {
+ bResetEnvVar = true;
+ osl_setEnvironment(OUString("LO_IMPORT_USE_PDFIUM").pData, OUString("1").pData);
+ }
+ comphelper::ScopeGuard aPDFiumEnvVarGuard([&]() {
+ if (bResetEnvVar)
+ osl_clearEnvironment(OUString("LO_IMPORT_USE_PDFIUM").pData);
+ });
+
+ // Load the PDF and save as PDF
+ vcl::filter::PDFDocument aDocument;
+ // Loading fails with tagged PDF enabled
+ load(u"ref-to-kids.pdf", aDocument, false);
+
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(size_t(5), aPages.size());
+
+ // Directly go to the inner XObject Im5 that contains the rectangle drawings.
+ auto pInnerIm = aDocument.LookupObject(5);
+ CPPUNIT_ASSERT(pInnerIm);
+
+ auto pFilter = dynamic_cast<vcl::filter::PDFNameElement*>(pInnerIm->Lookup("Filter"_ostr));
+ CPPUNIT_ASSERT(pFilter);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Filter must be FlateDecode", "FlateDecode"_ostr,
+ pFilter->GetValue());
+
+ vcl::filter::PDFStreamElement* pStream = pInnerIm->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+ // Uncompress it.
+ SvMemoryStream aUncompressed;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ rObjectStream.Seek(0);
+ aZCodec.Decompress(rObjectStream, aUncompressed);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+
+ // Without the fix, the stream is doubly compressed,
+ // hence one decompression will not yield the "re" expressions.
+ auto pStart = static_cast<const char*>(aUncompressed.GetData());
+ const char* pEnd = pStart + aUncompressed.GetSize();
+ OString aImage = "100 0 30 50 re B*\n70 67 50 30 re B*\n"_ostr;
+ auto it = std::search(pStart, pEnd, aImage.getStr(), aImage.getStr() + aImage.getLength());
+ CPPUNIT_ASSERT(it != pEnd);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testRexportMediaBoxOrigin)
+{
+ // We need to enable PDFium import (and make sure to disable after the test)
+ bool bResetEnvVar = false;
+ if (getenv("LO_IMPORT_USE_PDFIUM") == nullptr)
+ {
+ bResetEnvVar = true;
+ osl_setEnvironment(OUString("LO_IMPORT_USE_PDFIUM").pData, OUString("1").pData);
+ }
+ comphelper::ScopeGuard aPDFiumEnvVarGuard([&]() {
+ if (bResetEnvVar)
+ osl_clearEnvironment(OUString("LO_IMPORT_USE_PDFIUM").pData);
+ });
+
+ // Load the PDF and save as PDF
+ vcl::filter::PDFDocument aDocument;
+ load(u"ref-to-kids.pdf", aDocument);
+
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(size_t(5), aPages.size());
+
+ // Directly go to the inner XObject Im12 that contains the rectangle drawings in page 2.
+ auto pInnerIm = aDocument.LookupObject(12);
+ CPPUNIT_ASSERT(pInnerIm);
+
+ constexpr sal_Int32 aOrigin[2] = { -800, -600 };
+ sal_Int32 aSize[2] = { 0, 0 };
+
+ auto pBBox = dynamic_cast<vcl::filter::PDFArrayElement*>(pInnerIm->Lookup("BBox"_ostr));
+ CPPUNIT_ASSERT(pBBox);
+ const auto& rElements2 = pBBox->GetElements();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements2.size());
+ for (sal_Int32 nIdx = 0; nIdx < 4; ++nIdx)
+ {
+ const auto* pNumElement = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements2[nIdx]);
+ CPPUNIT_ASSERT(pNumElement);
+ if (nIdx < 2)
+ CPPUNIT_ASSERT_EQUAL(aOrigin[nIdx], static_cast<sal_Int32>(pNumElement->GetValue()));
+ else
+ aSize[nIdx - 2] = static_cast<sal_Int32>(pNumElement->GetValue()) - aOrigin[nIdx - 2];
+ }
+
+ auto pMatrix = dynamic_cast<vcl::filter::PDFArrayElement*>(pInnerIm->Lookup("Matrix"_ostr));
+ CPPUNIT_ASSERT(pMatrix);
+ const auto& rElements = pMatrix->GetElements();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(6), rElements.size());
+ sal_Int32 aMatTranslate[6]
+ = { // Rotation by $\theta$ $cos(\theta), sin(\theta), -sin(\theta), cos(\theta)$
+ 0, -1, 1, 0,
+ // Translate x,y
+ -aOrigin[1] - aSize[1] / 2 + aSize[0] / 2, aOrigin[0] + aSize[0] / 2 + aSize[1] / 2
+ };
+
+ for (sal_Int32 nIdx = 0; nIdx < 6; ++nIdx)
+ {
+ const auto* pNumElement = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[nIdx]);
+ CPPUNIT_ASSERT(pNumElement);
+ CPPUNIT_ASSERT_EQUAL(aMatTranslate[nIdx], static_cast<sal_Int32>(pNumElement->GetValue()));
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testRexportResourceItemReference)
+{
+ // We need to enable PDFium import (and make sure to disable after the test)
+ bool bResetEnvVar = false;
+ if (getenv("LO_IMPORT_USE_PDFIUM") == nullptr)
+ {
+ bResetEnvVar = true;
+ osl_setEnvironment(OUString("LO_IMPORT_USE_PDFIUM").pData, OUString("1").pData);
+ }
+ comphelper::ScopeGuard aPDFiumEnvVarGuard([&]() {
+ if (bResetEnvVar)
+ osl_clearEnvironment(OUString("LO_IMPORT_USE_PDFIUM").pData);
+ });
+
+ // Load the PDF and save as PDF
+ vcl::filter::PDFDocument aDocument;
+ load(u"ref-to-kids.pdf", aDocument);
+
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(size_t(5), aPages.size());
+
+ // Directly go to the inner XObject Im12 that has reference to Font in page 2.
+ auto pInnerIm = aDocument.LookupObject(12);
+ CPPUNIT_ASSERT(pInnerIm);
+
+ auto pResources
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pInnerIm->Lookup("Resources"_ostr));
+ CPPUNIT_ASSERT(pResources);
+ auto pFontsReference
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pResources->LookupElement("Font"_ostr));
+ CPPUNIT_ASSERT(pFontsReference);
+
+ auto pFontsObject = pFontsReference->LookupObject();
+ CPPUNIT_ASSERT(pFontsObject);
+
+ auto pFontDict
+ = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pFontsObject->Lookup("FF132"_ostr));
+ CPPUNIT_ASSERT(pFontDict);
+
+ auto pFontDescriptor = pFontDict->LookupObject("FontDescriptor"_ostr);
+ CPPUNIT_ASSERT(pFontDescriptor);
+
+ auto pFontWidths = pFontDict->LookupObject("Widths"_ostr);
+ CPPUNIT_ASSERT(pFontWidths);
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf152246)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"content-control-rtl.docx");
+
+ // Parse the export result.
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ // Position array
+ constexpr double aPos[5][4] = { { 56.699, 707.701, 131.401, 721.499 },
+ { 198.499, 707.701, 273.201, 721.499 },
+ { 303.349, 680.101, 378.051, 693.899 },
+ { 480.599, 680.101, 555.301, 693.899 },
+ { 56.699, 652.501, 131.401, 666.299 } };
+
+ // Get page annotations.
+ auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
+ CPPUNIT_ASSERT(pAnnots);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(5), pAnnots->GetElements().size());
+ for (sal_Int32 i = 0; i < 5; ++i)
+ {
+ auto pAnnotReference
+ = dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[i]);
+ CPPUNIT_ASSERT(pAnnotReference);
+ vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject();
+ CPPUNIT_ASSERT(pAnnot);
+ CPPUNIT_ASSERT_EQUAL(
+ "Annot"_ostr,
+ static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr))->GetValue());
+ CPPUNIT_ASSERT_EQUAL(
+ "Widget"_ostr,
+ static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr))->GetValue());
+
+ auto pRect = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
+ CPPUNIT_ASSERT(pRect);
+ const auto& rElements = pRect->GetElements();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
+ for (sal_Int32 nIdx = 0; nIdx < 4; ++nIdx)
+ {
+ const auto* pNumElement = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[nIdx]);
+ CPPUNIT_ASSERT(pNumElement);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(aPos[i][nIdx], pNumElement->GetValue(), 1e-6);
+ }
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf155161)
+{
+// TODO: We seem to get a fallback font on Windows
+#ifndef _WIN32
+ vcl::filter::PDFDocument aDocument;
+ load(u"tdf155161.odt", aDocument);
+
+ // Check that all fonts in the document are Type 3 fonts
+ int nFonts = 0;
+ for (const auto& aElement : aDocument.GetElements())
+ {
+ auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
+ if (!pObject)
+ continue;
+ auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
+ if (pType && pType->GetValue() == "Font")
+ {
+ auto pSubtype
+ = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Subtype"_ostr));
+ CPPUNIT_ASSERT(pSubtype);
+ CPPUNIT_ASSERT_EQUAL("Type3"_ostr, pSubtype->GetValue());
+
+ auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Name"_ostr));
+ CPPUNIT_ASSERT(pName);
+ CPPUNIT_ASSERT_EQUAL("Cantarell-Regular"_ostr, pName->GetValue());
+
+ nFonts++;
+ }
+ }
+
+#ifdef MACOSX
+ // There must be two fonts
+ CPPUNIT_ASSERT_EQUAL(2, nFonts);
+#else
+ // But it seems that embedded variable fonts don’t register all supported
+ // styles on Linux, so the bold and regular text use the same regular font.
+ CPPUNIT_ASSERT(nFonts);
+#endif
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf48707_1)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf48707-1.fodt");
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+ auto pPage = pPdfDocument->openPage(0);
+ CPPUNIT_ASSERT(pPage);
+
+ int nPageObjectCount = pPage->getObjectCount();
+
+ CPPUNIT_ASSERT_EQUAL(6, nPageObjectCount);
+
+ auto pTextPage = pPage->getTextPage();
+
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPage->getObject(i);
+ // The text and path objects (underline and overline) should all be red.
+ if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, pPageObject->getFillColor());
+ else
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, pPageObject->getStrokeColor());
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf48707_2)
+{
+ // Import the bugdoc and export as PDF.
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf48707-2.fodt");
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+ auto pPage = pPdfDocument->openPage(0);
+ CPPUNIT_ASSERT(pPage);
+
+ int nPageObjectCount = pPage->getObjectCount();
+
+ CPPUNIT_ASSERT_EQUAL(13, nPageObjectCount);
+
+ auto pTextPage = pPage->getTextPage();
+
+ for (int i = 0; i < nPageObjectCount; ++i)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPage->getObject(i);
+ if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Path)
+ continue;
+
+ // The table-like paths should be red, underline and overline should be black.
+ if (i >= 8)
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, pPageObject->getStrokeColor());
+ else
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, pPageObject->getStrokeColor());
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf156528)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"wide_page1.fodt");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // The document has two pages
+ CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
+
+ // 1st page (5100 mm width x 210 mm high, UserUnit = 2)
+ auto pPdfPage = pPdfDocument->openPage(0);
+ CPPUNIT_ASSERT(pPdfPage);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(o3tl::convert(5100.0 / 2, o3tl::Length::mm, o3tl::Length::pt),
+ pPdfPage->getWidth(), 1);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(o3tl::convert(210.0 / 2, o3tl::Length::mm, o3tl::Length::pt),
+ pPdfPage->getHeight(), 1);
+
+ // 1 object (rectangle 5060 mm width x 170 mm high, UserUnit = 2)
+ CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount());
+ auto pRect = pPdfPage->getObject(0);
+ CPPUNIT_ASSERT(pRect);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Path, pRect->getType());
+ auto bounds = pRect->getBounds();
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(o3tl::convert(5060.0 / 2, o3tl::Length::mm, o3tl::Length::pt),
+ bounds.getWidth(), 1);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(o3tl::convert(170.0 / 2, o3tl::Length::mm, o3tl::Length::pt),
+ bounds.getHeight(), 1);
+
+ // 2nd page (210 mm width x 297 mm high, UserUnit = 1)
+ pPdfPage = pPdfDocument->openPage(1);
+ CPPUNIT_ASSERT(pPdfPage);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(o3tl::convert(210.0, o3tl::Length::mm, o3tl::Length::pt),
+ pPdfPage->getWidth(), 1);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(o3tl::convert(297.0, o3tl::Length::mm, o3tl::Length::pt),
+ pPdfPage->getHeight(), 1);
+
+ // 1 object (rectangle 170 mm width x 257 mm high, UserUnit = 1)
+ CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount());
+ pRect = pPdfPage->getObject(0);
+ CPPUNIT_ASSERT(pRect);
+ CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Path, pRect->getType());
+ bounds = pRect->getBounds();
+ // Without the fix, this would fail with
+ // - Expected: 481.889763779528
+ // - Actual : 241.925001144409
+ // - Delta : 1
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(o3tl::convert(170.0, o3tl::Length::mm, o3tl::Length::pt),
+ bounds.getWidth(), 1);
+ //
+ // - Expected: 728.503937007874
+ // - Actual : 365.25
+ // - Delta : 1
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(o3tl::convert(257.0, o3tl::Length::mm, o3tl::Length::pt),
+ bounds.getHeight(), 1);
+}
+
+} // end anonymous namespace
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/physicalfontcollection.cxx b/vcl/qa/cppunit/physicalfontcollection.cxx
new file mode 100644
index 0000000000..bff91d021f
--- /dev/null
+++ b/vcl/qa/cppunit/physicalfontcollection.cxx
@@ -0,0 +1,551 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+#include <cppunit/TestAssert.h>
+
+#include <font/PhysicalFontCollection.hxx>
+#include <font/PhysicalFontFamily.hxx>
+
+#include "fontmocks.hxx"
+
+#include <memory>
+
+constexpr int FONTID = 1;
+
+class VclPhysicalFontCollectionTest : public test::BootstrapFixture
+{
+public:
+ VclPhysicalFontCollectionTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ void testShouldCreateAndAddFontFamilyToCollection();
+ void testShouldFindFontFamily();
+ void testShouldNotFindFontFamily();
+ void testShouldFindFontFamilyByTokenNames();
+ void testShouldFindNoFamilyWithWorthlessAttributes();
+ void testShouldFindCJKFamily();
+ void testShouldNotFindCJKFamily();
+ void testShouldFindStarsymbolFamily();
+ void testShouldFindOpensymbolFamilyWithMultipleSymbolFamilies();
+ void testShouldFindSymboltypeFamily();
+ void testShouldFindSymbolFamilyByMatchType();
+ void testImpossibleSymbolFamily();
+ void testShouldNotFindSymbolFamily();
+ void testShouldMatchFamilyName();
+ void testShouldMatchBrushScriptFamily();
+ void testShouldNotMatchBrushScriptFamily();
+ void testShouldMatchFixedFamily();
+ void testShouldNotMatchFixedFamily();
+ void testShouldMatchSerifFamily();
+ void testShouldNotMatchSerifFamily();
+ void testShouldMatchSansSerifFamily();
+ void testShouldNotMatchSansSerifFamily();
+ void testShouldMatchDecorativeFamily();
+ void testShouldFindTitlingFamily();
+ void testShouldFindCapitalsFamily();
+ void testShouldFindFamilyName();
+ void testShouldFindOtherStyleFamily();
+ void testShouldNotFindOtherStyleFamily();
+ void testShouldFindSchoolbookFamily();
+
+ CPPUNIT_TEST_SUITE(VclPhysicalFontCollectionTest);
+ CPPUNIT_TEST(testShouldCreateAndAddFontFamilyToCollection);
+ CPPUNIT_TEST(testShouldFindFontFamily);
+ CPPUNIT_TEST(testShouldNotFindFontFamily);
+ CPPUNIT_TEST(testShouldFindFontFamilyByTokenNames);
+ CPPUNIT_TEST(testShouldFindNoFamilyWithWorthlessAttributes);
+ CPPUNIT_TEST(testShouldFindCJKFamily);
+ CPPUNIT_TEST(testShouldNotFindCJKFamily);
+ CPPUNIT_TEST(testShouldFindStarsymbolFamily);
+ CPPUNIT_TEST(testShouldFindOpensymbolFamilyWithMultipleSymbolFamilies);
+ CPPUNIT_TEST(testShouldFindSymboltypeFamily);
+ CPPUNIT_TEST(testShouldFindSymbolFamilyByMatchType);
+ CPPUNIT_TEST(testImpossibleSymbolFamily);
+ CPPUNIT_TEST(testShouldNotFindSymbolFamily);
+ CPPUNIT_TEST(testShouldMatchFamilyName);
+ CPPUNIT_TEST(testShouldMatchBrushScriptFamily);
+ CPPUNIT_TEST(testShouldNotMatchBrushScriptFamily);
+ CPPUNIT_TEST(testShouldMatchFixedFamily);
+ CPPUNIT_TEST(testShouldNotMatchFixedFamily);
+ CPPUNIT_TEST(testShouldMatchSerifFamily);
+ CPPUNIT_TEST(testShouldNotMatchSerifFamily);
+ CPPUNIT_TEST(testShouldMatchSansSerifFamily);
+ CPPUNIT_TEST(testShouldNotMatchSansSerifFamily);
+ CPPUNIT_TEST(testShouldMatchDecorativeFamily);
+ CPPUNIT_TEST(testShouldFindTitlingFamily);
+ CPPUNIT_TEST(testShouldFindCapitalsFamily);
+ CPPUNIT_TEST(testShouldFindFamilyName);
+ CPPUNIT_TEST(testShouldFindOtherStyleFamily);
+ CPPUNIT_TEST(testShouldNotFindOtherStyleFamily);
+ CPPUNIT_TEST(testShouldFindSchoolbookFamily);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void VclPhysicalFontCollectionTest::testShouldCreateAndAddFontFamilyToCollection()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Empty font collection", static_cast<int>(0),
+ aFontCollection.Count());
+
+ // please note that fonts created this way are NOT normalized and will not be found if you search for them in the collection
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindOrCreateFontFamily("Test Font Family Name");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Does not have only one font family in collection",
+ static_cast<int>(1), aFontCollection.Count());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Font family name not correct", OUString("Test Font Family Name"),
+ pFontFamily->GetSearchName());
+
+ vcl::font::PhysicalFontFamily* pFontFamily2
+ = aFontCollection.FindOrCreateFontFamily("Test Font Family Name");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Still only one font family in collection", static_cast<int>(1),
+ aFontCollection.Count());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Font family name not correct", OUString("Test Font Family Name"),
+ pFontFamily2->GetSearchName());
+}
+
+void VclPhysicalFontCollectionTest::testShouldFindFontFamily()
+{
+ // note: you must normalize the search family name (first parameter of PhysicalFontFamily constructor)
+ vcl::font::PhysicalFontCollection aFontCollection;
+ aFontCollection.FindOrCreateFontFamily(GetEnglishSearchFontName(u"Test Font Family Name"));
+
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindFontFamily(u"Test Font Family Name");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Font family name not correct",
+ GetEnglishSearchFontName(u"Test Font Family Name"),
+ pFontFamily->GetSearchName());
+}
+
+void VclPhysicalFontCollectionTest::testShouldNotFindFontFamily()
+{
+ // note: you must normalize the search family name (first parameter of PhysicalFontFamily constructor)
+ vcl::font::PhysicalFontCollection aFontCollection;
+ aFontCollection.FindOrCreateFontFamily(GetEnglishSearchFontName(u"Test Font Family Name"));
+
+ CPPUNIT_ASSERT(!aFontCollection.FindFontFamily(u"blah"));
+}
+
+void VclPhysicalFontCollectionTest::testShouldFindFontFamilyByTokenNames()
+{
+ // note: you must normalize the search family name (first parameter of PhysicalFontFamily constructor)
+ vcl::font::PhysicalFontCollection aFontCollection;
+ aFontCollection.FindOrCreateFontFamily(GetEnglishSearchFontName(u"Test Font Family Name"));
+
+ OUString sTokenNames(GetEnglishSearchFontName(u"Test Font Family Name;"));
+ sTokenNames += GetEnglishSearchFontName(u"Test 2");
+
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindFontFamilyByTokenNames(u"Test Font Family Name");
+ CPPUNIT_ASSERT_MESSAGE("Did not find the font family", pFontFamily);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Font family name incorrect",
+ GetEnglishSearchFontName(u"Test Font Family Name"),
+ pFontFamily->GetSearchName());
+}
+
+static void AddNormalFontFace(vcl::font::PhysicalFontFamily* const pFontFamily,
+ OUString const& rFamilyName)
+{
+ FontAttributes aFontAttr;
+ aFontAttr.SetFamilyName(rFamilyName);
+ aFontAttr.SetWeight(WEIGHT_NORMAL);
+ pFontFamily->AddFontFace(new TestFontFace(aFontAttr, FONTID));
+}
+
+void VclPhysicalFontCollectionTest::testShouldFindNoFamilyWithWorthlessAttributes()
+{
+ // note: you must normalize the search family name (first parameter of PhysicalFontFamily constructor)
+ vcl::font::PhysicalFontCollection aFontCollection;
+ aFontCollection.FindOrCreateFontFamily(GetEnglishSearchFontName(u"Test Font Family Name"));
+
+ CPPUNIT_ASSERT(!aFontCollection.FindFontFamilyByAttributes(ImplFontAttrs::None, WEIGHT_NORMAL,
+ WIDTH_NORMAL, ITALIC_NONE, u""));
+}
+
+void VclPhysicalFontCollectionTest::testShouldFindCJKFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+
+ // interestingly, you need to normalize the name still
+ vcl::font::PhysicalFontFamily* pFontFamily = aFontCollection.FindOrCreateFontFamily(
+ GetEnglishSearchFontName(u"시험")); // Korean for "test"
+ AddNormalFontFace(pFontFamily, u"시험"_ustr);
+
+ vcl::font::PhysicalFontFamily* pCJKFamily = aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::CJK, WEIGHT_NORMAL, WIDTH_NORMAL, ITALIC_NORMAL, u"");
+ CPPUNIT_ASSERT_MESSAGE("family not found", pCJKFamily);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("cjk family not found", GetEnglishSearchFontName(u"시험"),
+ pCJKFamily->GetSearchName());
+}
+
+void VclPhysicalFontCollectionTest::testShouldNotFindCJKFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindOrCreateFontFamily(GetEnglishSearchFontName(u"No CJK characters"));
+ AddNormalFontFace(pFontFamily, "No CJK characters");
+
+ CPPUNIT_ASSERT_MESSAGE("family found",
+ !aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::CJK, WEIGHT_NORMAL, WIDTH_NORMAL, ITALIC_NONE, u""));
+}
+
+void VclPhysicalFontCollectionTest::testShouldFindStarsymbolFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindOrCreateFontFamily("starsymbol");
+ AddNormalFontFace(pFontFamily, "starsymbol");
+
+ CPPUNIT_ASSERT_MESSAGE("starsymbol created", pFontFamily);
+
+ vcl::font::PhysicalFontFamily* pStarsymbolFamily = aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::Symbol, WEIGHT_NORMAL, WIDTH_NORMAL, ITALIC_NORMAL, u"");
+ CPPUNIT_ASSERT_MESSAGE("family not found", pStarsymbolFamily);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("starsymbol family not found", OUString("starsymbol"),
+ pStarsymbolFamily->GetSearchName());
+}
+
+void VclPhysicalFontCollectionTest::testShouldFindOpensymbolFamilyWithMultipleSymbolFamilies()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+
+ vcl::font::PhysicalFontFamily* pOpenSymbolFamily
+ = aFontCollection.FindOrCreateFontFamily("opensymbol");
+ AddNormalFontFace(pOpenSymbolFamily, "opensymbol");
+
+ vcl::font::PhysicalFontFamily* pWingDingsFontFamily
+ = aFontCollection.FindOrCreateFontFamily("wingdings");
+ AddNormalFontFace(pWingDingsFontFamily, "wingdings");
+
+ vcl::font::PhysicalFontFamily* pStarsymbolFamily = aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::Symbol, WEIGHT_NORMAL, WIDTH_NORMAL, ITALIC_NORMAL, u"");
+ CPPUNIT_ASSERT_MESSAGE("family not found", pStarsymbolFamily);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("opensymbol family not found", OUString("opensymbol"),
+ pStarsymbolFamily->GetSearchName());
+}
+
+void VclPhysicalFontCollectionTest::testShouldFindSymboltypeFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindOrCreateFontFamily("testsymbol");
+
+ AddNormalFontFace(pFontFamily, "testsymbol");
+
+ vcl::font::PhysicalFontFamily* pSymbolFamily = aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::Symbol, WEIGHT_NORMAL, WIDTH_NORMAL, ITALIC_NORMAL, u"");
+ CPPUNIT_ASSERT_MESSAGE("family not found", pSymbolFamily);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("test symbol family not found", OUString("testsymbol"),
+ pSymbolFamily->GetSearchName());
+}
+
+void VclPhysicalFontCollectionTest::testShouldFindSymbolFamilyByMatchType()
+{
+ // TODO: figure out how to test matchtype with ImplFontAttrs::Full
+
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily = aFontCollection.FindOrCreateFontFamily("symbols");
+
+ FontAttributes aFontAttr;
+ aFontAttr.SetFamilyName("symbols");
+ aFontAttr.SetMicrosoftSymbolEncoded(true);
+ aFontAttr.SetWeight(WEIGHT_NORMAL);
+ pFontFamily->AddFontFace(new TestFontFace(aFontAttr, FONTID));
+
+ vcl::font::PhysicalFontFamily* pSymbolFamily = aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::Symbol, WEIGHT_NORMAL, WIDTH_NORMAL, ITALIC_NORMAL, u"");
+ CPPUNIT_ASSERT_MESSAGE("family not found", pSymbolFamily);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("test symbol family not found", OUString("symbols"),
+ pSymbolFamily->GetSearchName());
+}
+
+void VclPhysicalFontCollectionTest::testImpossibleSymbolFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindOrCreateFontFamily("testsymbolfamily");
+
+ FontAttributes aFontAttr;
+ aFontAttr.SetFamilyName("testsymbolfamily");
+ aFontAttr.SetMicrosoftSymbolEncoded(true);
+ aFontAttr.SetWeight(WEIGHT_NORMAL);
+ TestFontFace* pFontFace = new TestFontFace(aFontAttr, FONTID);
+ pFontFamily->AddFontFace(pFontFace);
+
+ CPPUNIT_ASSERT_MESSAGE("match for family not possible, but was found anyway",
+ !aFontCollection.FindFontFamilyByAttributes(ImplFontAttrs::Normal,
+ WEIGHT_NORMAL, WIDTH_NORMAL,
+ ITALIC_NORMAL, u""));
+}
+
+void VclPhysicalFontCollectionTest::testShouldNotFindSymbolFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily = aFontCollection.FindOrCreateFontFamily("symbol");
+ AddNormalFontFace(pFontFamily, "symbol");
+
+ CPPUNIT_ASSERT_MESSAGE(
+ "Family found", !aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::Normal, WEIGHT_NORMAL, WIDTH_NORMAL, ITALIC_NONE, u""));
+}
+
+void VclPhysicalFontCollectionTest::testShouldMatchFamilyName()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+
+ // note that for this test, it is irrelevant what the search name is for PhysicalFontFamily,
+ // the font searches the family name and uses the search parameter of FindFontFamilyByAttributes()
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindOrCreateFontFamily(u"Matching family name"_ustr);
+ AddNormalFontFace(pFontFamily, GetEnglishSearchFontName(u"Matching family name"));
+
+ CPPUNIT_ASSERT_MESSAGE("No family found",
+ aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::Normal, WEIGHT_NORMAL, WIDTH_NORMAL, ITALIC_NONE,
+ u"Matching family name"));
+}
+
+void VclPhysicalFontCollectionTest::testShouldMatchBrushScriptFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily = aFontCollection.FindOrCreateFontFamily("script");
+ AddNormalFontFace(pFontFamily, "script");
+
+ CPPUNIT_ASSERT_MESSAGE("Brush script family not found",
+ aFontCollection.FindFontFamilyByAttributes(ImplFontAttrs::BrushScript,
+ WEIGHT_NORMAL, WIDTH_NORMAL,
+ ITALIC_NONE, u""));
+}
+
+void VclPhysicalFontCollectionTest::testShouldNotMatchBrushScriptFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindOrCreateFontFamily("testfamily");
+ AddNormalFontFace(pFontFamily, "testfamily");
+
+ CPPUNIT_ASSERT_MESSAGE("Brush script family found",
+ !aFontCollection.FindFontFamilyByAttributes(ImplFontAttrs::BrushScript,
+ WEIGHT_NORMAL, WIDTH_NORMAL,
+ ITALIC_NONE, u""));
+}
+
+void VclPhysicalFontCollectionTest::testShouldMatchFixedFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+
+ // note that for this test, it is irrelevant what the search name is for PhysicalFontFamily,
+ // the font searches the family name and uses the search parameter of FindFontFamilyByAttributes()
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindOrCreateFontFamily(GetEnglishSearchFontName(u"Matching family name"));
+
+ FontAttributes aFontAttr;
+ aFontAttr.SetFamilyName(GetEnglishSearchFontName(u"Matching family name"));
+ aFontAttr.SetFamilyType(FAMILY_MODERN);
+ aFontAttr.SetWeight(WEIGHT_NORMAL);
+ TestFontFace* pFontFace = new TestFontFace(aFontAttr, FONTID);
+ pFontFamily->AddFontFace(pFontFace);
+
+ CPPUNIT_ASSERT_MESSAGE("No fixed family found",
+ aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::Fixed, WEIGHT_NORMAL, WIDTH_NORMAL, ITALIC_NONE,
+ GetEnglishSearchFontName(u"Matching family name")));
+}
+
+void VclPhysicalFontCollectionTest::testShouldNotMatchFixedFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+
+ // note that for this test, it is irrelevant what the search name is for PhysicalFontFamily,
+ // the font searches the family name and uses the search parameter of FindFontFamilyByAttributes()
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindOrCreateFontFamily(GetEnglishSearchFontName(u"Matching family name"));
+
+ FontAttributes aFontAttr;
+ aFontAttr.SetFamilyName(GetEnglishSearchFontName(u"Matching family name"));
+ aFontAttr.SetFamilyType(FAMILY_SWISS);
+ aFontAttr.SetWeight(WEIGHT_NORMAL);
+ TestFontFace* pFontFace = new TestFontFace(aFontAttr, FONTID);
+ pFontFamily->AddFontFace(pFontFace);
+
+ CPPUNIT_ASSERT_MESSAGE("Fixed family found",
+ aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::Fixed, WEIGHT_NORMAL, WIDTH_NORMAL, ITALIC_NONE,
+ GetEnglishSearchFontName(u"Matching family name")));
+}
+
+void VclPhysicalFontCollectionTest::testShouldMatchSerifFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily = aFontCollection.FindOrCreateFontFamily("serif");
+
+ FontAttributes aFontAttr;
+ aFontAttr.SetFamilyName("serif");
+ aFontAttr.SetFamilyType(FAMILY_ROMAN);
+ aFontAttr.SetWeight(WEIGHT_NORMAL);
+ TestFontFace* pFontFace = new TestFontFace(aFontAttr, FONTID);
+ pFontFamily->AddFontFace(pFontFace);
+
+ CPPUNIT_ASSERT_MESSAGE("Serif family not found", aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::Serif, WEIGHT_NORMAL,
+ WIDTH_NORMAL, ITALIC_NONE, u""));
+}
+
+void VclPhysicalFontCollectionTest::testShouldNotMatchSerifFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindOrCreateFontFamily("sansseriftest");
+
+ FontAttributes aFontAttr;
+ aFontAttr.SetFamilyName("sansseriftest");
+ aFontAttr.SetFamilyType(FAMILY_SWISS);
+ aFontAttr.SetWeight(WEIGHT_NORMAL);
+ TestFontFace* pFontFace = new TestFontFace(aFontAttr, FONTID);
+ pFontFamily->AddFontFace(pFontFace);
+
+ CPPUNIT_ASSERT_MESSAGE("Serif family found", !aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::Serif, WEIGHT_NORMAL,
+ WIDTH_NORMAL, ITALIC_NONE, u""));
+}
+
+void VclPhysicalFontCollectionTest::testShouldMatchSansSerifFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindOrCreateFontFamily("sansserif");
+
+ FontAttributes aFontAttr;
+ aFontAttr.SetFamilyName("sansserif");
+ aFontAttr.SetFamilyType(FAMILY_SWISS);
+ aFontAttr.SetWeight(WEIGHT_NORMAL);
+ TestFontFace* pFontFace = new TestFontFace(aFontAttr, FONTID);
+ pFontFamily->AddFontFace(pFontFace);
+
+ CPPUNIT_ASSERT_MESSAGE("SansSerif family not found",
+ aFontCollection.FindFontFamilyByAttributes(ImplFontAttrs::SansSerif,
+ WEIGHT_NORMAL, WIDTH_NORMAL,
+ ITALIC_NONE, u""));
+}
+
+void VclPhysicalFontCollectionTest::testShouldNotMatchSansSerifFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily = aFontCollection.FindOrCreateFontFamily("serif");
+
+ FontAttributes aFontAttr;
+ aFontAttr.SetFamilyName("serif");
+ aFontAttr.SetFamilyType(FAMILY_ROMAN);
+ aFontAttr.SetWeight(WEIGHT_NORMAL);
+ TestFontFace* pFontFace = new TestFontFace(aFontAttr, FONTID);
+ pFontFamily->AddFontFace(pFontFace);
+
+ CPPUNIT_ASSERT_MESSAGE("SansSerif family found", !aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::SansSerif, WEIGHT_NORMAL,
+ WIDTH_NORMAL, ITALIC_NONE, u""));
+}
+
+void VclPhysicalFontCollectionTest::testShouldMatchDecorativeFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindOrCreateFontFamily("decorative");
+
+ FontAttributes aFontAttr;
+ aFontAttr.SetFamilyName("decorative");
+ aFontAttr.SetFamilyType(FAMILY_DECORATIVE);
+ aFontAttr.SetWeight(WEIGHT_MEDIUM);
+ TestFontFace* pFontFace = new TestFontFace(aFontAttr, FONTID);
+ pFontFamily->AddFontFace(pFontFace);
+
+ CPPUNIT_ASSERT_MESSAGE("Decorative family not found",
+ aFontCollection.FindFontFamilyByAttributes(ImplFontAttrs::Decorative,
+ WEIGHT_NORMAL, WIDTH_NORMAL,
+ ITALIC_NORMAL, u""));
+}
+
+void VclPhysicalFontCollectionTest::testShouldFindTitlingFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindOrCreateFontFamily("testtitling");
+ AddNormalFontFace(pFontFamily, "testtitling");
+
+ CPPUNIT_ASSERT_MESSAGE("Titling family not found", aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::Titling, WEIGHT_NORMAL,
+ WIDTH_NORMAL, ITALIC_NORMAL, u""));
+}
+
+void VclPhysicalFontCollectionTest::testShouldFindCapitalsFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily = aFontCollection.FindOrCreateFontFamily("testcaps");
+ AddNormalFontFace(pFontFamily, "testcaps");
+
+ CPPUNIT_ASSERT_MESSAGE("All-caps family not found", aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::Capitals, WEIGHT_NORMAL,
+ WIDTH_NORMAL, ITALIC_NORMAL, u""));
+}
+
+void VclPhysicalFontCollectionTest::testShouldFindFamilyName()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindOrCreateFontFamily(GetEnglishSearchFontName(u"Test font name"));
+ AddNormalFontFace(pFontFamily, GetEnglishSearchFontName(u"Test font name"));
+
+ CPPUNIT_ASSERT_MESSAGE("Cannot find font name",
+ aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::Capitals, WEIGHT_NORMAL, WIDTH_NORMAL, ITALIC_NORMAL,
+ GetEnglishSearchFontName(u"Test font name")));
+}
+
+void VclPhysicalFontCollectionTest::testShouldFindOtherStyleFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindOrCreateFontFamily("testoldstyle");
+ AddNormalFontFace(pFontFamily, "testoldstyle");
+
+ CPPUNIT_ASSERT_MESSAGE("Did not find font name", aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::OtherStyle, WEIGHT_NORMAL,
+ WIDTH_NORMAL, ITALIC_NORMAL, u""));
+}
+
+void VclPhysicalFontCollectionTest::testShouldNotFindOtherStyleFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily = aFontCollection.FindOrCreateFontFamily("monotype");
+ AddNormalFontFace(pFontFamily, "monotype");
+
+ CPPUNIT_ASSERT_MESSAGE("Found font name", !aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::OtherStyle, WEIGHT_NORMAL,
+ WIDTH_NORMAL, ITALIC_NORMAL, u""));
+}
+
+void VclPhysicalFontCollectionTest::testShouldFindSchoolbookFamily()
+{
+ vcl::font::PhysicalFontCollection aFontCollection;
+ vcl::font::PhysicalFontFamily* pFontFamily
+ = aFontCollection.FindOrCreateFontFamily("testschoolbook");
+ AddNormalFontFace(pFontFamily, "testschoolbook");
+
+ CPPUNIT_ASSERT_MESSAGE("Did not find font name", aFontCollection.FindFontFamilyByAttributes(
+ ImplFontAttrs::Schoolbook, WEIGHT_NORMAL,
+ WIDTH_NORMAL, ITALIC_NORMAL, u""));
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclPhysicalFontCollectionTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/physicalfontface.cxx b/vcl/qa/cppunit/physicalfontface.cxx
new file mode 100644
index 0000000000..87fdc382bc
--- /dev/null
+++ b/vcl/qa/cppunit/physicalfontface.cxx
@@ -0,0 +1,301 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+#include <cppunit/TestAssert.h>
+
+#include <tools/degree.hxx>
+
+#include <vcl/font.hxx>
+
+#include <font/PhysicalFontFace.hxx>
+#include <fontattributes.hxx>
+
+#include "fontmocks.hxx"
+
+#include <memory>
+
+const sal_IntPtr FONTID = 1;
+
+class VclPhysicalFontFaceTest : public test::BootstrapFixture
+{
+public:
+ VclPhysicalFontFaceTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ void testShouldCompareAsLesserFontFaceWithShorterWidth();
+ void testShouldCompareAsGreaterFontFaceWithLongerWidth();
+ void testShouldCompareAsLesserFontFaceWithLighterWeight();
+ void testShouldCompareAsGreaterFontFaceWithHeavierWeight();
+ void testShouldCompareAsLesserFontFaceWithLesserItalics();
+ void testShouldCompareAsGreaterFontFaceWithGreaterItalics();
+ void testShouldCompareAsGreaterFontFaceWitHigherAlphabeticalFamilyName();
+ void testShouldCompareAsGreaterFontFaceWitLesserAlphabeticalFamilyName();
+ void testShouldCompareAsGreaterFontFaceWithHigherAlphabeticalStyleName();
+ void testShouldCompareAsGreaterFontFaceWithLesserAlphabeticalStyleName();
+ void testShouldCompareAsSameFontFace();
+ void testMatchStatusValue();
+
+ CPPUNIT_TEST_SUITE(VclPhysicalFontFaceTest);
+ CPPUNIT_TEST(testShouldCompareAsLesserFontFaceWithShorterWidth);
+ CPPUNIT_TEST(testShouldCompareAsGreaterFontFaceWithLongerWidth);
+ CPPUNIT_TEST(testShouldCompareAsLesserFontFaceWithLighterWeight);
+ CPPUNIT_TEST(testShouldCompareAsGreaterFontFaceWithHeavierWeight);
+ CPPUNIT_TEST(testShouldCompareAsLesserFontFaceWithLesserItalics);
+ CPPUNIT_TEST(testShouldCompareAsGreaterFontFaceWithGreaterItalics);
+ CPPUNIT_TEST(testShouldCompareAsGreaterFontFaceWitHigherAlphabeticalFamilyName);
+ CPPUNIT_TEST(testShouldCompareAsGreaterFontFaceWitLesserAlphabeticalFamilyName);
+ CPPUNIT_TEST(testShouldCompareAsGreaterFontFaceWithHigherAlphabeticalStyleName);
+ CPPUNIT_TEST(testShouldCompareAsGreaterFontFaceWithLesserAlphabeticalStyleName);
+ CPPUNIT_TEST(testShouldCompareAsSameFontFace);
+ CPPUNIT_TEST(testMatchStatusValue);
+
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void VclPhysicalFontFaceTest::testShouldCompareAsLesserFontFaceWithShorterWidth()
+{
+ FontAttributes aFontAttrsShorterWidth;
+ aFontAttrsShorterWidth.SetWidthType(WIDTH_CONDENSED);
+ rtl::Reference<TestFontFace> aTestedFontFace(new TestFontFace(aFontAttrsShorterWidth, FONTID));
+
+ FontAttributes aFontAttrsLongerWidth;
+ aFontAttrsLongerWidth.SetWidthType(WIDTH_NORMAL);
+ rtl::Reference<TestFontFace> aComparedToFontFace(
+ new TestFontFace(aFontAttrsLongerWidth, FONTID));
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1),
+ aTestedFontFace->CompareIgnoreSize(*aComparedToFontFace));
+}
+
+void VclPhysicalFontFaceTest::testShouldCompareAsGreaterFontFaceWithLongerWidth()
+{
+ FontAttributes aFontAttrsLongerWidth;
+ aFontAttrsLongerWidth.SetWidthType(WIDTH_NORMAL);
+ rtl::Reference<TestFontFace> aTestedFontFace(new TestFontFace(aFontAttrsLongerWidth, FONTID));
+
+ FontAttributes aFontAttrsShorterWidth;
+ aFontAttrsShorterWidth.SetWidthType(WIDTH_CONDENSED);
+ rtl::Reference<TestFontFace> aComparedToFontFace(
+ new TestFontFace(aFontAttrsShorterWidth, FONTID));
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1),
+ aTestedFontFace->CompareIgnoreSize(*aComparedToFontFace));
+}
+
+void VclPhysicalFontFaceTest::testShouldCompareAsLesserFontFaceWithLighterWeight()
+{
+ FontAttributes aFontAttrsLighterWeight;
+ aFontAttrsLighterWeight.SetWidthType(WIDTH_NORMAL);
+ aFontAttrsLighterWeight.SetWeight(WEIGHT_THIN);
+ rtl::Reference<TestFontFace> aTestedFontFace(new TestFontFace(aFontAttrsLighterWeight, FONTID));
+
+ FontAttributes aFontAttrsHeavierWeight;
+ aFontAttrsHeavierWeight.SetWeight(WEIGHT_BOLD);
+ aFontAttrsHeavierWeight.SetWidthType(WIDTH_NORMAL);
+ rtl::Reference<TestFontFace> aComparedToFontFace(
+ new TestFontFace(aFontAttrsHeavierWeight, FONTID));
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1),
+ aTestedFontFace->CompareIgnoreSize(*aComparedToFontFace));
+}
+
+void VclPhysicalFontFaceTest::testShouldCompareAsGreaterFontFaceWithHeavierWeight()
+{
+ FontAttributes aFontAttrsHeavierWeight;
+ aFontAttrsHeavierWeight.SetWidthType(WIDTH_NORMAL);
+ aFontAttrsHeavierWeight.SetWeight(WEIGHT_BOLD);
+ rtl::Reference<TestFontFace> aTestedFontFace(new TestFontFace(aFontAttrsHeavierWeight, FONTID));
+
+ FontAttributes aFontAttrsLighterWeight;
+ aFontAttrsLighterWeight.SetWidthType(WIDTH_NORMAL);
+ aFontAttrsLighterWeight.SetWeight(WEIGHT_THIN);
+ rtl::Reference<TestFontFace> aComparedToFontFace(
+ new TestFontFace(aFontAttrsLighterWeight, FONTID));
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1),
+ aTestedFontFace->CompareIgnoreSize(*aComparedToFontFace));
+}
+
+void VclPhysicalFontFaceTest::testShouldCompareAsLesserFontFaceWithLesserItalics()
+{
+ FontAttributes aFontAttrsLesserItalics;
+ aFontAttrsLesserItalics.SetWidthType(WIDTH_NORMAL);
+ aFontAttrsLesserItalics.SetWeight(WEIGHT_THIN);
+ rtl::Reference<TestFontFace> aTestedFontFace(new TestFontFace(aFontAttrsLesserItalics, FONTID));
+
+ FontAttributes aFontAttrsGreaterItalics;
+ aFontAttrsGreaterItalics.SetWeight(WEIGHT_BOLD);
+ aFontAttrsGreaterItalics.SetWidthType(WIDTH_NORMAL);
+ rtl::Reference<TestFontFace> aComparedToFontFace(
+ new TestFontFace(aFontAttrsGreaterItalics, FONTID));
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1),
+ aTestedFontFace->CompareIgnoreSize(*aComparedToFontFace));
+}
+
+void VclPhysicalFontFaceTest::testShouldCompareAsGreaterFontFaceWithGreaterItalics()
+{
+ FontAttributes aFontAttrsGreaterItalics;
+ aFontAttrsGreaterItalics.SetWidthType(WIDTH_NORMAL);
+ aFontAttrsGreaterItalics.SetWeight(WEIGHT_BOLD);
+ rtl::Reference<TestFontFace> aTestedFontFace(
+ new TestFontFace(aFontAttrsGreaterItalics, FONTID));
+
+ FontAttributes aFontAttrsLesserItalics;
+ aFontAttrsLesserItalics.SetWidthType(WIDTH_NORMAL);
+ aFontAttrsLesserItalics.SetWeight(WEIGHT_THIN);
+ rtl::Reference<TestFontFace> aComparedToFontFace(
+ new TestFontFace(aFontAttrsLesserItalics, FONTID));
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1),
+ aTestedFontFace->CompareIgnoreSize(*aComparedToFontFace));
+}
+
+void VclPhysicalFontFaceTest::testShouldCompareAsGreaterFontFaceWitHigherAlphabeticalFamilyName()
+{
+ FontAttributes aFontAttrs1;
+ aFontAttrs1.SetWidthType(WIDTH_NORMAL);
+ aFontAttrs1.SetWeight(WEIGHT_NORMAL);
+ aFontAttrs1.SetFamilyName("B family");
+ rtl::Reference<TestFontFace> aTestedFontFace(new TestFontFace(aFontAttrs1, FONTID));
+
+ FontAttributes aFontAttrs2;
+ aFontAttrs2.SetWidthType(WIDTH_NORMAL);
+ aFontAttrs2.SetWeight(WEIGHT_NORMAL);
+ aFontAttrs2.SetFamilyName("A Family");
+ rtl::Reference<TestFontFace> aComparedToFontFace(new TestFontFace(aFontAttrs2, FONTID));
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1),
+ aTestedFontFace->CompareIgnoreSize(*aComparedToFontFace));
+}
+
+void VclPhysicalFontFaceTest::testShouldCompareAsGreaterFontFaceWitLesserAlphabeticalFamilyName()
+{
+ FontAttributes aFontAttrs1;
+ aFontAttrs1.SetWidthType(WIDTH_NORMAL);
+ aFontAttrs1.SetWeight(WEIGHT_NORMAL);
+ aFontAttrs1.SetFamilyName("A family");
+ rtl::Reference<TestFontFace> aTestedFontFace(new TestFontFace(aFontAttrs1, FONTID));
+
+ FontAttributes aFontAttrs2;
+ aFontAttrs2.SetWidthType(WIDTH_NORMAL);
+ aFontAttrs2.SetWeight(WEIGHT_NORMAL);
+ aFontAttrs2.SetFamilyName("B Family");
+ rtl::Reference<TestFontFace> aComparedToFontFace(new TestFontFace(aFontAttrs2, FONTID));
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1),
+ aTestedFontFace->CompareIgnoreSize(*aComparedToFontFace));
+}
+
+void VclPhysicalFontFaceTest::testShouldCompareAsGreaterFontFaceWithHigherAlphabeticalStyleName()
+{
+ FontAttributes aFontAttrs1;
+ aFontAttrs1.SetWidthType(WIDTH_NORMAL);
+ aFontAttrs1.SetWeight(WEIGHT_NORMAL);
+ aFontAttrs1.SetFamilyName("DejaVu Sans");
+ aFontAttrs1.SetStyleName("B Style");
+ rtl::Reference<TestFontFace> aTestedFontFace(new TestFontFace(aFontAttrs1, FONTID));
+
+ FontAttributes aFontAttrs2;
+ aFontAttrs2.SetWidthType(WIDTH_NORMAL);
+ aFontAttrs2.SetWeight(WEIGHT_NORMAL);
+ aFontAttrs2.SetFamilyName("DejaVu Sans");
+ aFontAttrs2.SetStyleName("A Style");
+ rtl::Reference<TestFontFace> aComparedToFontFace(new TestFontFace(aFontAttrs2, FONTID));
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1),
+ aTestedFontFace->CompareIgnoreSize(*aComparedToFontFace));
+}
+
+void VclPhysicalFontFaceTest::testShouldCompareAsGreaterFontFaceWithLesserAlphabeticalStyleName()
+{
+ FontAttributes aFontAttrs1;
+ aFontAttrs1.SetWidthType(WIDTH_NORMAL);
+ aFontAttrs1.SetWeight(WEIGHT_NORMAL);
+ aFontAttrs1.SetFamilyName("DejaVu Sans");
+ aFontAttrs1.SetStyleName("A Style");
+ rtl::Reference<TestFontFace> aTestedFontFace(new TestFontFace(aFontAttrs1, FONTID));
+
+ FontAttributes aFontAttrs2;
+ aFontAttrs2.SetWidthType(WIDTH_NORMAL);
+ aFontAttrs2.SetWeight(WEIGHT_NORMAL);
+ aFontAttrs2.SetFamilyName("DejaVu Sans");
+ aFontAttrs2.SetStyleName("B Style");
+ rtl::Reference<TestFontFace> aComparedToFontFace(new TestFontFace(aFontAttrs2, FONTID));
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1),
+ aTestedFontFace->CompareIgnoreSize(*aComparedToFontFace));
+}
+
+void VclPhysicalFontFaceTest::testShouldCompareAsSameFontFace()
+{
+ FontAttributes aFontAttrs1;
+ aFontAttrs1.SetWidthType(WIDTH_NORMAL);
+ aFontAttrs1.SetWeight(WEIGHT_NORMAL);
+ aFontAttrs1.SetFamilyName("DejaVu Sans");
+ aFontAttrs1.SetStyleName("Style");
+ rtl::Reference<TestFontFace> aTestedFontFace(new TestFontFace(aFontAttrs1, FONTID));
+
+ FontAttributes aFontAttrs2;
+ aFontAttrs2.SetWidthType(WIDTH_NORMAL);
+ aFontAttrs2.SetWeight(WEIGHT_NORMAL);
+ aFontAttrs2.SetFamilyName("DejaVu Sans");
+ aFontAttrs2.SetStyleName("Style");
+ rtl::Reference<TestFontFace> aComparedToFontFace(new TestFontFace(aFontAttrs2, FONTID));
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0),
+ aTestedFontFace->CompareIgnoreSize(*aComparedToFontFace));
+}
+
+void VclPhysicalFontFaceTest::testMatchStatusValue()
+{
+ FontAttributes aFontAttrs;
+ aFontAttrs.SetFamilyName("DejaVu Sans");
+ aFontAttrs.SetStyleName("Book");
+ aFontAttrs.SetPitch(FontPitch::PITCH_VARIABLE);
+ aFontAttrs.SetWidthType(WIDTH_NORMAL);
+ aFontAttrs.SetWeight(WEIGHT_BOLD);
+ rtl::Reference<TestFontFace> aTestedFontFace(new TestFontFace(aFontAttrs, FONTID));
+
+ std::unique_ptr<OUString> pTargetStyleName(new OUString("Book"));
+ vcl::font::FontMatchStatus aFontMatchStatus = { 0, pTargetStyleName.get() };
+
+ vcl::Font aTestFont("DejaVu Sans", "Book", Size(0, 36));
+
+ vcl::font::FontSelectPattern aFSP(aTestFont, "DejaVu Sans", Size(0, 36), 36, true);
+ aFSP.mbEmbolden = false;
+ aFSP.mnOrientation = Degree10(10);
+ aFSP.SetWeight(WEIGHT_BOLD);
+ aFSP.SetPitch(FontPitch::PITCH_VARIABLE);
+ aFSP.maTargetName = "DejaVu Sans";
+
+ const int EXPECTED_FAMILY = 240'000;
+ const int EXPECTED_STYLE = 120'000;
+ const int EXPECTED_PITCH = 20'000;
+ const int EXPECTED_WIDTHTYPE = 400;
+ const int EXPECTED_WEIGHT = 1'000;
+ const int EXPECTED_ITALIC = 900;
+ const int EXPECTED_ORIENTATION = 80;
+
+ const int EXPECTED_MATCH = EXPECTED_FAMILY + EXPECTED_STYLE + EXPECTED_PITCH
+ + EXPECTED_WIDTHTYPE + EXPECTED_WEIGHT + EXPECTED_ITALIC
+ + EXPECTED_ORIENTATION;
+
+ CPPUNIT_ASSERT(aTestedFontFace->IsBetterMatch(aFSP, aFontMatchStatus));
+ CPPUNIT_ASSERT_EQUAL(EXPECTED_MATCH, aFontMatchStatus.mnFaceMatch);
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclPhysicalFontFaceTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/qa/cppunit/physicalfontfacecollection.cxx b/vcl/qa/cppunit/physicalfontfacecollection.cxx
new file mode 100644
index 0000000000..358a5b9943
--- /dev/null
+++ b/vcl/qa/cppunit/physicalfontfacecollection.cxx
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+#include <cppunit/TestAssert.h>
+
+#include <rtl/ref.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+
+#include <vcl/fontcapabilities.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <vcl/virdev.hxx>
+
+#include "fontmocks.hxx"
+
+class VclPhysicalFontFaceCollectionTest : public test::BootstrapFixture
+{
+public:
+ VclPhysicalFontFaceCollectionTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ void testShouldGetFontId();
+
+ CPPUNIT_TEST_SUITE(VclPhysicalFontFaceCollectionTest);
+ CPPUNIT_TEST(testShouldGetFontId);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void VclPhysicalFontFaceCollectionTest::testShouldGetFontId()
+{
+ vcl::font::PhysicalFontFaceCollection aCollection;
+ aCollection.Add(new TestFontFace(1988756));
+
+ rtl::Reference<vcl::font::PhysicalFontFace> pActual(new TestFontFace(1988756));
+ rtl::Reference<vcl::font::PhysicalFontFace> pExpected = aCollection.Get(0);
+
+ CPPUNIT_ASSERT_EQUAL(pExpected->GetFontId(), pActual->GetFontId());
+ CPPUNIT_ASSERT_EQUAL(1, aCollection.Count());
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclPhysicalFontFaceCollectionTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/physicalfontfamily.cxx b/vcl/qa/cppunit/physicalfontfamily.cxx
new file mode 100644
index 0000000000..a720cda0c5
--- /dev/null
+++ b/vcl/qa/cppunit/physicalfontfamily.cxx
@@ -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/.
+ */
+
+#include <sal/config.h>
+
+#include <test/bootstrapfixture.hxx>
+#include <cppunit/TestAssert.h>
+
+#include <tools/fontenum.hxx>
+#include <unotools/fontcfg.hxx>
+#include <o3tl/sorted_vector.hxx>
+
+#include <vcl/virdev.hxx>
+
+#include <font/PhysicalFontFamily.hxx>
+
+#include "fontmocks.hxx"
+
+using namespace vcl::font;
+
+class VclPhysicalFontFamilyTest : public test::BootstrapFixture
+{
+public:
+ VclPhysicalFontFamilyTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ void testCreateFontFamily();
+ void testAddFontFace_Default();
+ void testAddOneFontFace();
+ void testAddTwoFontFaces();
+
+ CPPUNIT_TEST_SUITE(VclPhysicalFontFamilyTest);
+ CPPUNIT_TEST(testCreateFontFamily);
+ CPPUNIT_TEST(testAddFontFace_Default);
+ CPPUNIT_TEST(testAddOneFontFace);
+ CPPUNIT_TEST(testAddTwoFontFaces);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void VclPhysicalFontFamilyTest::testCreateFontFamily()
+{
+ PhysicalFontFamily aFamily("Test font face");
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Family name", OUString(""), aFamily.GetFamilyName());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Search name", OUString("Test font face"),
+ aFamily.GetSearchName());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Min quality", -1, aFamily.GetMinQuality());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Type faces", FontTypeFaces::NONE, aFamily.GetTypeFaces());
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Match family name", OUString(""), aFamily.GetMatchFamilyName());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Match type", ImplFontAttrs::None, aFamily.GetMatchType());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Match weight", WEIGHT_DONTKNOW, aFamily.GetMatchWeight());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Match width", WIDTH_DONTKNOW, aFamily.GetMatchWidth());
+}
+
+void VclPhysicalFontFamilyTest::testAddFontFace_Default()
+{
+ PhysicalFontFamily aFamily("Test font face");
+
+ aFamily.AddFontFace(new TestFontFace(1));
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Family name", OUString(""), aFamily.GetFamilyName());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Search name", OUString("Test font face"),
+ aFamily.GetSearchName());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Min quality", 0, aFamily.GetMinQuality());
+ FontTypeFaces eTypeFace
+ = FontTypeFaces::Scalable | FontTypeFaces::NoneSymbol | FontTypeFaces::NoneItalic;
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Type faces", eTypeFace, aFamily.GetTypeFaces());
+}
+
+void VclPhysicalFontFamilyTest::testAddOneFontFace()
+{
+ PhysicalFontFamily aFamily("Test font face");
+
+ FontAttributes aFontAttrs;
+ aFontAttrs.SetFamilyName("Test font face");
+ aFontAttrs.SetFamilyType(FontFamily::FAMILY_ROMAN);
+ aFontAttrs.SetPitch(FontPitch::PITCH_VARIABLE);
+ aFontAttrs.SetItalic(FontItalic::ITALIC_NONE);
+ aFontAttrs.SetQuality(10);
+ aFontAttrs.SetWeight(FontWeight::WEIGHT_BOLD);
+ aFontAttrs.SetWidthType(FontWidth::WIDTH_EXPANDED);
+
+ aFamily.AddFontFace(new TestFontFace(aFontAttrs, 1));
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Family name", OUString("Test font face"),
+ aFamily.GetFamilyName());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Search name", OUString("Test font face"),
+ aFamily.GetSearchName());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Min quality", 10, aFamily.GetMinQuality());
+ FontTypeFaces eTypeFace = FontTypeFaces::Scalable | FontTypeFaces::NoneSymbol
+ | FontTypeFaces::Bold | FontTypeFaces::NoneItalic;
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Type faces", eTypeFace, aFamily.GetTypeFaces());
+}
+
+void VclPhysicalFontFamilyTest::testAddTwoFontFaces()
+{
+ PhysicalFontFamily aFamily("Test font face");
+
+ FontAttributes aFontAttrs;
+ aFontAttrs.SetFamilyName("Test font face");
+ aFontAttrs.SetFamilyType(FontFamily::FAMILY_ROMAN);
+ aFontAttrs.SetPitch(FontPitch::PITCH_VARIABLE);
+ aFontAttrs.SetItalic(FontItalic::ITALIC_NONE);
+ aFontAttrs.SetQuality(10);
+ aFontAttrs.SetWeight(FontWeight::WEIGHT_THIN);
+ aFontAttrs.SetWidthType(FontWidth::WIDTH_EXPANDED);
+
+ aFamily.AddFontFace(new TestFontFace(aFontAttrs, 1));
+
+ aFontAttrs.SetFamilyName("Test font face");
+ aFontAttrs.SetFamilyType(FontFamily::FAMILY_ROMAN);
+ aFontAttrs.SetPitch(FontPitch::PITCH_VARIABLE);
+ aFontAttrs.SetItalic(FontItalic::ITALIC_NORMAL);
+ aFontAttrs.SetQuality(5);
+ aFontAttrs.SetWeight(FontWeight::WEIGHT_BOLD);
+ aFontAttrs.SetWidthType(FontWidth::WIDTH_CONDENSED);
+
+ aFamily.AddFontFace(new TestFontFace(aFontAttrs, 2));
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Family name", OUString("Test font face"),
+ aFamily.GetFamilyName());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Search name", OUString("Test font face"),
+ aFamily.GetSearchName());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Min quality", 5, aFamily.GetMinQuality());
+ FontTypeFaces eTypeFace = FontTypeFaces::Scalable | FontTypeFaces::NoneSymbol
+ | FontTypeFaces::Light | FontTypeFaces::Bold
+ | FontTypeFaces::NoneItalic | FontTypeFaces::Italic;
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Type faces", eTypeFace, aFamily.GetTypeFaces());
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclPhysicalFontFamilyTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/qa/cppunit/png/PngFilterTest.cxx b/vcl/qa/cppunit/png/PngFilterTest.cxx
new file mode 100644
index 0000000000..2e900ec41d
--- /dev/null
+++ b/vcl/qa/cppunit/png/PngFilterTest.cxx
@@ -0,0 +1,2084 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <test/bootstrapfixture.hxx>
+#include <tools/stream.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+#include <vcl/filter/PngImageWriter.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/BitmapMonochromeFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <vcl/alpha.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <unotools/tempfile.hxx>
+
+using namespace css;
+
+namespace
+{
+struct Case
+{
+ tools::Long mnWidth;
+ tools::Long mnHeight;
+ sal_uInt16 mnBpp;
+ bool mbHasPalette;
+ bool mbIsAlpha;
+};
+// Checks that a pngs BitmapEx is the same after reading and
+// after writing. Takes a vector of function pointers if there's need to test
+// special cases
+void checkImportExportPng(const OUString& sFilePath, const Case& aCase)
+{
+ SvFileStream aFileStream(sFilePath, StreamMode::READ);
+ SvMemoryStream aExportStream;
+ BitmapEx aImportedBitmapEx;
+ BitmapEx aExportedImportedBitmapEx;
+
+ bool bOpenOk = !aFileStream.GetError() && aFileStream.GetBufferSize() > 0;
+ CPPUNIT_ASSERT_MESSAGE(OString("Failed to open file: " + sFilePath.toUtf8()).getStr(), bOpenOk);
+
+ // Read the png from the file
+ {
+ vcl::PngImageReader aPngReader(aFileStream);
+ bool bReadOk = aPngReader.read(aImportedBitmapEx);
+ CPPUNIT_ASSERT_MESSAGE(OString("Failed to read png from: " + sFilePath.toUtf8()).getStr(),
+ bReadOk);
+ Bitmap aImportedBitmap = aImportedBitmapEx.GetBitmap();
+ BitmapScopedInfoAccess pAccess(aImportedBitmap);
+ auto nActualWidth = aImportedBitmapEx.GetSizePixel().Width();
+ auto nActualHeight = aImportedBitmapEx.GetSizePixel().Height();
+ auto nActualBpp = vcl::pixelFormatBitCount(aImportedBitmapEx.GetBitmap().getPixelFormat());
+ auto bActualHasPalette = pAccess->HasPalette();
+ auto bActualIsAlpha = aImportedBitmapEx.IsAlpha();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ OString("Width comparison failed for exported png:" + sFilePath.toUtf8()).getStr(),
+ aCase.mnWidth, nActualWidth);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ OString("Height comparison failed for exported png:" + sFilePath.toUtf8()).getStr(),
+ aCase.mnHeight, nActualHeight);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ OString("Bpp comparison failed for exported png:" + sFilePath.toUtf8()).getStr(),
+ aCase.mnBpp, nActualBpp);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ OString("HasPalette comparison failed for exported png:" + sFilePath.toUtf8()).getStr(),
+ aCase.mbHasPalette, bActualHasPalette);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ OString("IsAlpha comparison failed for exported png:" + sFilePath.toUtf8()).getStr(),
+ aCase.mbIsAlpha, bActualIsAlpha);
+ }
+
+ // Write the imported png to a stream
+ {
+ vcl::PngImageWriter aPngWriter(aExportStream);
+ bool bWriteOk = aPngWriter.write(aImportedBitmapEx);
+ CPPUNIT_ASSERT_MESSAGE(OString("Failed to write png: " + sFilePath.toUtf8()).getStr(),
+ bWriteOk);
+ aExportStream.Seek(0);
+ }
+
+ // Read the png again from the exported stream
+ {
+ vcl::PngImageReader aPngReader(aExportStream);
+ bool bReadOk = aPngReader.read(aExportedImportedBitmapEx);
+ CPPUNIT_ASSERT_MESSAGE(
+ OString("Failed to read exported png: " + sFilePath.toUtf8()).getStr(), bReadOk);
+ Bitmap aExportedImportedBitmap = aExportedImportedBitmapEx.GetBitmap();
+ BitmapScopedInfoAccess pAccess(aExportedImportedBitmap);
+ auto nActualWidth = aExportedImportedBitmapEx.GetSizePixel().Width();
+ auto nActualHeight = aExportedImportedBitmapEx.GetSizePixel().Height();
+ auto nActualBpp
+ = vcl::pixelFormatBitCount(aExportedImportedBitmapEx.GetBitmap().getPixelFormat());
+ auto bActualHasPalette = pAccess->HasPalette();
+ auto bActualIsAlpha = aExportedImportedBitmapEx.IsAlpha();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ OString("Width comparison failed for exported png:" + sFilePath.toUtf8()).getStr(),
+ aCase.mnWidth, nActualWidth);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ OString("Height comparison failed for exported png:" + sFilePath.toUtf8()).getStr(),
+ aCase.mnHeight, nActualHeight);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ OString("Bpp comparison failed for exported png:" + sFilePath.toUtf8()).getStr(),
+ aCase.mnBpp, nActualBpp);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ OString("HasPalette comparison failed for exported png:" + sFilePath.toUtf8()).getStr(),
+ aCase.mbHasPalette, bActualHasPalette);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ OString("IsAlpha comparison failed for exported png:" + sFilePath.toUtf8()).getStr(),
+ aCase.mbIsAlpha, bActualIsAlpha);
+ }
+
+ // Compare imported and exported BitmapEx
+ // This compares size, inner bitmap and alpha mask
+ bool bIsSame = (aExportedImportedBitmapEx == aImportedBitmapEx);
+ CPPUNIT_ASSERT_MESSAGE(
+ OString("Import->Export png test failed for png: " + sFilePath.toUtf8()).getStr(), bIsSame);
+}
+
+// Checks that aPngReader.read returns false on corrupted files
+void checkImportCorruptedPng(const OUString& sFilePath)
+{
+ SvFileStream aFileStream(sFilePath, StreamMode::READ);
+ BitmapEx aImportedBitmapEx;
+
+ bool bOpenOk = !aFileStream.GetError() && aFileStream.GetBufferSize() > 0;
+ CPPUNIT_ASSERT_MESSAGE(OString("Failed to open file: " + sFilePath.toUtf8()).getStr(), bOpenOk);
+ vcl::PngImageReader aPngReader(aFileStream);
+ bool bReadOk = aPngReader.read(aImportedBitmapEx);
+ // Make sure this file was not read successfully
+ CPPUNIT_ASSERT_MESSAGE(
+ OString("Corrupted png should not be opened: " + sFilePath.toUtf8()).getStr(), !bReadOk);
+}
+}
+
+class PngFilterTest : public test::BootstrapFixture
+{
+ // Should keep the temp files (should be false)
+ static constexpr bool bKeepTemp = true;
+
+ OUString maDataUrl;
+
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(maDataUrl) + sFileName;
+ }
+
+public:
+ PngFilterTest()
+ : BootstrapFixture(true, false)
+ , maDataUrl("/vcl/qa/cppunit/png/data/")
+ {
+ }
+
+ void testPng();
+ void testApng();
+ void testPngSuite();
+ void testMsGifInPng();
+ void testPngRoundtrip8BitGrey();
+ void testPngRoundtrip24();
+ void testPngRoundtrip24_8();
+ void testPngRoundtrip32();
+ void testPngWrite8BitRGBPalette();
+ void testTdf153180MonochromeFilterPngExport();
+ void testDump();
+
+ CPPUNIT_TEST_SUITE(PngFilterTest);
+ CPPUNIT_TEST(testPng);
+ CPPUNIT_TEST(testApng);
+ CPPUNIT_TEST(testPngSuite);
+ CPPUNIT_TEST(testMsGifInPng);
+ CPPUNIT_TEST(testPngRoundtrip8BitGrey);
+ CPPUNIT_TEST(testPngRoundtrip24);
+ CPPUNIT_TEST(testPngRoundtrip24_8);
+ CPPUNIT_TEST(testPngRoundtrip32);
+ CPPUNIT_TEST(testPngWrite8BitRGBPalette);
+ CPPUNIT_TEST(testDump);
+ CPPUNIT_TEST(testTdf153180MonochromeFilterPngExport);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void PngFilterTest::testPng()
+{
+ for (const OUString& aFileName : { OUString("rect-1bit-pal.png") })
+ {
+ SvFileStream aFileStream(getFullUrl(aFileName), StreamMode::READ);
+
+ vcl::PngImageReader aPngReader(aFileStream);
+ BitmapEx aBitmapEx;
+ aPngReader.read(aBitmapEx);
+
+ Bitmap aBitmap = aBitmapEx.GetBitmap();
+ {
+ BitmapScopedReadAccess pAccess(aBitmap);
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), pAccess->Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), pAccess->Height());
+
+ if (pAccess->GetBitCount() == 24 || pAccess->GetBitCount() == 32)
+ {
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0xFF, 0x00),
+ pAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0xFF, 0x00),
+ pAccess->GetPixel(3, 3));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0xFF, 0x00),
+ pAccess->GetPixel(3, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0xFF, 0x00),
+ pAccess->GetPixel(0, 3));
+
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0x00, 0x00, 0x00),
+ pAccess->GetPixel(1, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0x00, 0x00, 0x00),
+ pAccess->GetPixel(1, 2));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0x00, 0x00, 0x00),
+ pAccess->GetPixel(2, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0x00, 0x00, 0x00),
+ pAccess->GetPixel(2, 2));
+ }
+ else
+ {
+ CPPUNIT_ASSERT_MESSAGE("Bitmap is not 24 or 32 bit.", false);
+ }
+ }
+ }
+
+ OUString aFilenames[] = {
+ OUString("color-rect-8bit-RGB.png"),
+ OUString("color-rect-8bit-RGB-interlaced.png"),
+ OUString("color-rect-4bit-pal.png"),
+ };
+
+ for (const OUString& aFileName : aFilenames)
+ {
+ SvFileStream aFileStream(getFullUrl(aFileName), StreamMode::READ);
+
+ vcl::PngImageReader aPngReader(aFileStream);
+ BitmapEx aBitmapEx;
+ aPngReader.read(aBitmapEx);
+
+ Bitmap aBitmap = aBitmapEx.GetBitmap();
+ {
+ BitmapScopedReadAccess pAccess(aBitmap);
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), pAccess->Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), pAccess->Height());
+ if (pAccess->GetBitCount() == 24 || pAccess->GetBitCount() == 32)
+ {
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0xFF, 0x00),
+ pAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0xFF, 0x00),
+ pAccess->GetPixel(3, 3));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0xFF, 0x00),
+ pAccess->GetPixel(3, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0xFF, 0x00),
+ pAccess->GetPixel(0, 3));
+
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0x00, 0x00, 0x00),
+ pAccess->GetPixel(1, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0xFF, 0x00, 0x00),
+ pAccess->GetPixel(1, 2));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0x00, 0xFF, 0x00),
+ pAccess->GetPixel(2, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0x00, 0x00),
+ pAccess->GetPixel(2, 2));
+ }
+ else
+ {
+ CPPUNIT_ASSERT_MESSAGE("Bitmap is not 24 or 32 bit.", false);
+ }
+ }
+ }
+ for (const OUString& aFileName : { OUString("alpha-rect-8bit-RGBA.png") })
+ {
+ SvFileStream aFileStream(getFullUrl(aFileName), StreamMode::READ);
+
+ vcl::PngImageReader aPngReader(aFileStream);
+ BitmapEx aBitmapEx;
+ aPngReader.read(aBitmapEx);
+
+ Bitmap aBitmap = aBitmapEx.GetBitmap();
+ {
+ BitmapScopedReadAccess pAccess(aBitmap);
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), pAccess->Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), pAccess->Height());
+
+ if (pAccess->GetBitCount() == 24)
+ {
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0xFF, 0x00),
+ pAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0xFF, 0x00),
+ pAccess->GetPixel(3, 3));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0xFF, 0x00),
+ pAccess->GetPixel(3, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0xFF, 0x00),
+ pAccess->GetPixel(0, 3));
+
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0x00, 0x00, 0x00),
+ pAccess->GetPixel(1, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0xFF, 0x00, 0x00),
+ pAccess->GetPixel(1, 2));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0x00, 0xFF, 0x00),
+ pAccess->GetPixel(2, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0x00, 0x00),
+ pAccess->GetPixel(2, 2));
+
+ AlphaMask aAlpha = aBitmapEx.GetAlphaMask();
+ {
+ BitmapScopedReadAccess pAlphaAccess(aAlpha);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(8), pAlphaAccess->GetBitCount());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), pAlphaAccess->Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), pAlphaAccess->Height());
+
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0x00, 0x7F, 0x00),
+ pAlphaAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0x00, 0x7F, 0x00),
+ pAlphaAccess->GetPixel(3, 3));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0x00, 0x7F, 0x00),
+ pAlphaAccess->GetPixel(3, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0x00, 0x7F, 0x00),
+ pAlphaAccess->GetPixel(0, 3));
+
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0x00, 0xBF, 0x00),
+ pAlphaAccess->GetPixel(1, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0x00, 0x3F, 0x00),
+ pAlphaAccess->GetPixel(1, 2));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0x00, 0x3F, 0x00),
+ pAlphaAccess->GetPixel(2, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0x00, 0xBF, 0x00),
+ pAlphaAccess->GetPixel(2, 2));
+ }
+ }
+ else if (pAccess->GetBitCount() == 32)
+ {
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0xFF, 0x80),
+ pAccess->GetPixel(0, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0xFF, 0x80),
+ pAccess->GetPixel(3, 3));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0xFF, 0x80),
+ pAccess->GetPixel(3, 0));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0xFF, 0x80),
+ pAccess->GetPixel(0, 3));
+
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0x00, 0x00, 0x40),
+ pAccess->GetPixel(1, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0xFF, 0x00, 0xC0),
+ pAccess->GetPixel(1, 2));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0x00, 0x00, 0xFF, 0xC0),
+ pAccess->GetPixel(2, 1));
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(ColorTransparency, 0xFF, 0xFF, 0x00, 0x40),
+ pAccess->GetPixel(2, 2));
+ }
+ else
+ {
+ CPPUNIT_ASSERT_MESSAGE("Bitmap is not 24 or 32 bit.", false);
+ }
+ }
+ }
+}
+
+void PngFilterTest::testApng()
+{
+ SvFileStream aFileStream(getFullUrl(u"apng_simple.apng"), StreamMode::READ);
+ vcl::PngImageReader aPngReader(aFileStream);
+ Graphic aGraphic;
+ bool bSuccess = aPngReader.read(aGraphic);
+ CPPUNIT_ASSERT(bSuccess);
+ CPPUNIT_ASSERT(aGraphic.IsAnimated());
+ CPPUNIT_ASSERT_EQUAL(size_t(2), aGraphic.GetAnimation().GetAnimationFrames().size());
+
+ AnimationFrame aFrame1 = *aGraphic.GetAnimation().GetAnimationFrames()[0];
+ AnimationFrame aFrame2 = *aGraphic.GetAnimation().GetAnimationFrames()[1];
+
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, aFrame1.maBitmapEx.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(Color(0x72d1c8), aFrame1.maBitmapEx.GetPixelColor(2, 2));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aFrame2.maBitmapEx.GetPixelColor(0, 0));
+
+ // Roundtrip the APNG
+ SvMemoryStream aOutStream;
+ vcl::PngImageWriter aPngWriter(aOutStream);
+ bSuccess = aPngWriter.write(aGraphic);
+ CPPUNIT_ASSERT(bSuccess);
+
+ aOutStream.Seek(STREAM_SEEK_TO_BEGIN);
+ vcl::PngImageReader aPngReader2(aOutStream);
+ Graphic aGraphic2;
+ bSuccess = aPngReader2.read(aGraphic2);
+ CPPUNIT_ASSERT(bSuccess);
+ CPPUNIT_ASSERT(aGraphic2.IsAnimated());
+ CPPUNIT_ASSERT_EQUAL(size_t(2), aGraphic2.GetAnimation().GetAnimationFrames().size());
+
+ AnimationFrame aFrame1Roundtripped = *aGraphic2.GetAnimation().GetAnimationFrames()[0];
+ AnimationFrame aFrame2Roundtripped = *aGraphic2.GetAnimation().GetAnimationFrames()[1];
+
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, aFrame1Roundtripped.maBitmapEx.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(Color(0x72d1c8), aFrame1Roundtripped.maBitmapEx.GetPixelColor(2, 2));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aFrame2Roundtripped.maBitmapEx.GetPixelColor(0, 0));
+
+ // Make sure the two frames have the same properties
+ CPPUNIT_ASSERT_EQUAL(aFrame1.maPositionPixel, aFrame1Roundtripped.maPositionPixel);
+ CPPUNIT_ASSERT_EQUAL(aFrame1.maSizePixel, aFrame1Roundtripped.maSizePixel);
+ CPPUNIT_ASSERT_EQUAL(aFrame1.mnWait, aFrame1Roundtripped.mnWait);
+ CPPUNIT_ASSERT_EQUAL(aFrame1.meDisposal, aFrame1Roundtripped.meDisposal);
+ CPPUNIT_ASSERT_EQUAL(aFrame1.meBlend, aFrame1Roundtripped.meBlend);
+
+ CPPUNIT_ASSERT_EQUAL(aFrame2.maPositionPixel, aFrame2Roundtripped.maPositionPixel);
+ CPPUNIT_ASSERT_EQUAL(aFrame2.maSizePixel, aFrame2Roundtripped.maSizePixel);
+ CPPUNIT_ASSERT_EQUAL(aFrame2.mnWait, aFrame2Roundtripped.mnWait);
+ CPPUNIT_ASSERT_EQUAL(aFrame2.meDisposal, aFrame2Roundtripped.meDisposal);
+ CPPUNIT_ASSERT_EQUAL(aFrame2.meBlend, aFrame2Roundtripped.meBlend);
+}
+
+void PngFilterTest::testPngSuite()
+{
+ // Test the PngSuite test files by Willem van Schaik
+ // filename: g04i2c08.png
+ // || ||||
+ // test feature (in this case gamma) ------+| ||||
+ // parameter of test (here gamma-value) ----+ ||||
+ // interlaced or non-interlaced --------------+|||
+ // color-type (numerical) ---------------------+||
+ // color-type (descriptive) --------------------+|
+ // bit-depth ------------------------------------+
+
+ // Some notes about the cases:
+ // - RGB palette PNGs get converted to a bitmap with png_set_palette_to_rgb
+ // - Grayscale PNGs with alpha also do with png_set_gray_to_rgb
+ // - Grayscale PNGs without alpha use BitmapEx palette utilities
+ // - 1, 2, 4 bit grayscale w/o alpha gets converted to 8 bit with png_set_expand_gray_1_2_4_to_8
+ // - 16 bit per channel gets converted to 8 bit per channel with png_set_scale_16
+ // - PNGs that are not size related have size 32x32
+ // - Internally BitmapEx is never 32 bpp, instead it's 24 bpp (rgb) and uses an 8 bpp alpha mask
+ std::pair<std::u16string_view, Case> aCases[] = {
+ // Basic formats, not interlaced
+ { u"basn0g01.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // b&w
+ { u"basn0g02.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // 2 bit grayscale
+ { u"basn0g04.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // 4 bit grayscale
+ { u"basn0g08.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // 8 bit grayscale
+ { u"basn0g16.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // 16 bit grayscale
+ { u"basn2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // 8 bit rgb
+ { u"basn2c16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // 16 bit rgb
+ { u"basn3p01.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // 1 bit palette
+ { u"basn3p02.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // 2 bit palette
+ { u"basn3p04.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // 4 bit palette
+ { u"basn3p08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // 8 bit palette
+ { u"basn4a08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // 8 bit grayscale + 8 bit alpha
+ { u"basn4a16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // 16 bit grayscale + 16 bit alpha
+ { u"basn6a08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // 8 bit rgba
+ { u"basn6a16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // 16 bit rgba
+ // Basic formats, interlaced
+ { u"basi0g01.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // b&w
+ { u"basi0g02.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // 2 bit grayscale
+ { u"basi0g04.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // 4 bit grayscale
+ { u"basi0g08.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // 8 bit grayscale
+ { u"basi0g16.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // 16 bit grayscale
+ { u"basi2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // 8 bit rgb
+ { u"basi2c16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // 16 bit rgb
+ { u"basi3p01.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // 1 bit palette
+ { u"basi3p02.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // 2 bit palette
+ { u"basi3p04.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // 4 bit palette
+ { u"basi3p08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // 8 bit palette
+ { u"basi4a08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // 8 bit grayscale + 8 bit alpha
+ { u"basi4a16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // 16 bit grayscale + 16 bit alpha
+ { u"basi6a08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // 8 bit rgba
+ { u"basi6a16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // 16 bit rgba
+ // // Odd sizes, not interlaced
+ { u"s01n3p01.png",
+ {
+ 1,
+ 1,
+ 24,
+ false,
+ false,
+ } }, // 1x1
+ { u"s02n3p01.png",
+ {
+ 2,
+ 2,
+ 24,
+ false,
+ false,
+ } }, // 2x2
+ { u"s03n3p01.png",
+ {
+ 3,
+ 3,
+ 24,
+ false,
+ false,
+ } }, // 3x3
+ { u"s04n3p01.png",
+ {
+ 4,
+ 4,
+ 24,
+ false,
+ false,
+ } }, // 4x4
+ { u"s05n3p02.png",
+ {
+ 5,
+ 5,
+ 24,
+ false,
+ false,
+ } }, // 5x5
+ { u"s06n3p02.png",
+ {
+ 6,
+ 6,
+ 24,
+ false,
+ false,
+ } }, // 6x6
+ { u"s07n3p02.png",
+ {
+ 7,
+ 7,
+ 24,
+ false,
+ false,
+ } }, // 7x7
+ { u"s08n3p02.png",
+ {
+ 8,
+ 8,
+ 24,
+ false,
+ false,
+ } }, // 8x8
+ { u"s09n3p02.png",
+ {
+ 9,
+ 9,
+ 24,
+ false,
+ false,
+ } }, // 9x9
+ { u"s32n3p04.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // 32x32
+ { u"s33n3p04.png",
+ {
+ 33,
+ 33,
+ 24,
+ false,
+ false,
+ } }, // 33x33
+ { u"s34n3p04.png",
+ {
+ 34,
+ 34,
+ 24,
+ false,
+ false,
+ } }, // 34x34
+ { u"s35n3p04.png",
+ {
+ 35,
+ 35,
+ 24,
+ false,
+ false,
+ } }, // 35x35
+ { u"s36n3p04.png",
+ {
+ 36,
+ 36,
+ 24,
+ false,
+ false,
+ } }, // 36x36
+ { u"s37n3p04.png",
+ {
+ 37,
+ 37,
+ 24,
+ false,
+ false,
+ } }, // 37x37
+ { u"s38n3p04.png",
+ {
+ 38,
+ 38,
+ 24,
+ false,
+ false,
+ } }, // 38x38
+ { u"s39n3p04.png",
+ {
+ 39,
+ 39,
+ 24,
+ false,
+ false,
+ } }, // 39x39
+ { u"s40n3p04.png",
+ {
+ 40,
+ 40,
+ 24,
+ false,
+ false,
+ } }, // 40x40
+ // // Odd sizes, interlaced
+ { u"s01i3p01.png",
+ {
+ 1,
+ 1,
+ 24,
+ false,
+ false,
+ } }, // 1x1
+ { u"s02i3p01.png",
+ {
+ 2,
+ 2,
+ 24,
+ false,
+ false,
+ } }, // 2x2
+ { u"s03i3p01.png",
+ {
+ 3,
+ 3,
+ 24,
+ false,
+ false,
+ } }, // 3x3
+ { u"s04i3p01.png",
+ {
+ 4,
+ 4,
+ 24,
+ false,
+ false,
+ } }, // 4x4
+ { u"s05i3p02.png",
+ {
+ 5,
+ 5,
+ 24,
+ false,
+ false,
+ } }, // 5x5
+ { u"s06i3p02.png",
+ {
+ 6,
+ 6,
+ 24,
+ false,
+ false,
+ } }, // 6x6
+ { u"s07i3p02.png",
+ {
+ 7,
+ 7,
+ 24,
+ false,
+ false,
+ } }, // 7x7
+ { u"s08i3p02.png",
+ {
+ 8,
+ 8,
+ 24,
+ false,
+ false,
+ } }, // 8x8
+ { u"s09i3p02.png",
+ {
+ 9,
+ 9,
+ 24,
+ false,
+ false,
+ } }, // 9x9
+ { u"s32i3p04.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // 32x32
+ { u"s33i3p04.png",
+ {
+ 33,
+ 33,
+ 24,
+ false,
+ false,
+ } }, // 33x33
+ { u"s34i3p04.png",
+ {
+ 34,
+ 34,
+ 24,
+ false,
+ false,
+ } }, // 34x34
+ { u"s35i3p04.png",
+ {
+ 35,
+ 35,
+ 24,
+ false,
+ false,
+ } }, // 35x35
+ { u"s36i3p04.png",
+ {
+ 36,
+ 36,
+ 24,
+ false,
+ false,
+ } }, // 36x36
+ { u"s37i3p04.png",
+ {
+ 37,
+ 37,
+ 24,
+ false,
+ false,
+ } }, // 37x37
+ { u"s38i3p04.png",
+ {
+ 38,
+ 38,
+ 24,
+ false,
+ false,
+ } }, // 38x38
+ { u"s39i3p04.png",
+ {
+ 39,
+ 39,
+ 24,
+ false,
+ false,
+ } }, // 39x39
+ { u"s40i3p04.png",
+ {
+ 40,
+ 40,
+ 24,
+ false,
+ false,
+ } }, // 40x40
+ // Background colors
+ { u"bgai4a08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // 8 bit grayscale alpha no background chunk, interlaced
+ { u"bgai4a16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // 16 bit grayscale alpha no background chunk, interlaced
+ { u"bgan6a08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // 3 * 8 bits rgb color alpha, no background chunk
+ { u"bgan6a16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // 3 * 16 bits rgb color alpha, no background chunk
+ { u"bgbn4a08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // 8 bit grayscale alpha, black background chunk
+ { u"bggn4a16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // 16 bit grayscale alpha, gray background chunk
+ { u"bgwn6a08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // 3 * 8 bits rgb color alpha, white background chunk
+ { u"bgyn6a16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // 3 * 16 bits rgb color alpha, yellow background chunk
+ // Transparency
+ { u"tbbn0g04.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // transparent, black background chunk
+ { u"tbbn2c16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // transparent, blue background chunk
+ { u"tbbn3p08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // transparent, black background chunk
+ { u"tbgn2c16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // transparent, green background chunk
+ { u"tbgn3p08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // transparent, light-gray background chunk
+ { u"tbrn2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // transparent, red background chunk
+ { u"tbwn0g16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // transparent, white background chunk
+ { u"tbwn3p08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // transparent, white background chunk
+ { u"tbyn3p08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // transparent, yellow background chunk
+ { u"tp1n3p08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // transparent, but no background chunk
+ { u"tm3n3p02.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // multiple levels of transparency, 3 entries
+ // Gamma
+ { u"g03n0g16.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale, file-gamma = 0.35
+ { u"g03n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color, file-gamma = 0.35
+ { u"g03n3p04.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // paletted, file-gamma = 0.35
+ { u"g04n0g16.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale, file-gamma = 0.45
+ { u"g04n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color, file-gamma = 0.45
+ { u"g04n3p04.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // paletted, file-gamma = 0.45
+ { u"g05n0g16.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale, file-gamma = 0.55
+ { u"g05n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color, file-gamma = 0.55
+ { u"g05n3p04.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // paletted, file-gamma = 0.55
+ { u"g07n0g16.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale, file-gamma = 0.70
+ { u"g07n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color, file-gamma = 0.70
+ { u"g07n3p04.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // paletted, file-gamma = 0.70
+ { u"g10n0g16.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale, file-gamma = 1.00
+ { u"g10n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color, file-gamma = 1.00
+ { u"g10n3p04.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // paletted, file-gamma = 1.00
+ { u"g25n0g16.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale, file-gamma = 2.50
+ { u"g25n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color, file-gamma = 2.50
+ { u"g25n3p04.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // paletted, file-gamma = 2.50
+ // Image filtering
+ { u"f00n0g08.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale no interlacing, filter-type 0
+ { u"f00n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color no interlacing, filter-type 0
+ { u"f01n0g08.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale no interlacing, filter-type 1
+ { u"f01n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color no interlacing, filter-type 1
+ { u"f02n0g08.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale no interlacing, filter-type 2
+ { u"f02n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color no interlacing, filter-type 2
+ { u"f03n0g08.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale no interlacing, filter-type 3
+ { u"f03n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color no interlacing, filter-type 3
+ { u"f04n0g08.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale no interlacing, filter-type 4
+ { u"f04n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color no interlacing, filter-type 4
+ { u"f99n0g04.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale bit-depth 4, filter changing per scanline
+ // Additional palettes
+ { u"pp0n2c16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // six-cube palette-chunk in true-color image
+ { u"pp0n6a08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ true,
+ } }, // six-cube palette-chunk in true-color+alpha image
+ { u"ps1n0g08.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // six-cube suggested palette (1 byte) in grayscale image
+ { u"ps1n2c16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // six-cube suggested palette (1 byte) in true-color image
+ { u"ps2n0g08.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // six-cube suggested palette (2 bytes) in grayscale image
+ { u"ps2n2c16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // six-cube suggested palette (2 bytes) in true-color image
+ // Ancillary chunks
+ { u"ccwn2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // chroma chunk w:0.31270.329 r:0.640.33 g:0.300.60 b:0.15,0.06
+ { u"ccwn3p08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // chroma chunk w:0.31270.329 r:0.640.33 g:0.300.60 b:0.15,0.06
+ { u"cdfn2c08.png",
+ {
+ 8,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // physical pixel dimensions, 8x32 flat pixels
+ { u"cdhn2c08.png",
+ {
+ 32,
+ 8,
+ 24,
+ false,
+ false,
+ } }, // physical pixel dimensions, 32x8 high pixels
+ { u"cdsn2c08.png",
+ {
+ 8,
+ 8,
+ 24,
+ false,
+ false,
+ } }, // physical pixel dimensions, 8x8 square pixels
+ { u"cdun2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // physical pixel dimensions, 1000 pixels per 1 meter
+ { u"ch1n3p04.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // histogram 15 colors
+ { u"ch2n3p08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // histogram 256 colors
+ { u"cm0n0g04.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale modification time, 01-jan-2000 12:34:56
+ { u"cm7n0g04.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale modification time, 01-jan-1970 00:00:00
+ { u"cm9n0g04.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale modification time, 31-dec-1999 23:59:59
+ { u"cs3n2c16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color, 13 significant bits
+ { u"cs3n3p08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // paletted, 3 significant bits
+ { u"cs5n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color, 5 significant bits
+ { u"cs5n3p08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // paletted, 5 significant bits
+ { u"cs8n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color, 8 significant bits (reference)
+ { u"cs8n3p08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // paletted, 8 significant bits (reference)
+ { u"ct0n0g04.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale no textual data
+ { u"ct1n0g04.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale with textual data
+ { u"ctzn0g04.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale with compressed textual data
+ { u"cten0g04.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale international UTF-8, english
+ { u"ctfn0g04.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale international UTF-8, finnish
+ { u"ctgn0g04.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale international UTF-8, greek
+ { u"cthn0g04.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale international UTF-8, hindi
+ { u"ctjn0g04.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale international UTF-8, japanese
+ { u"exif2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // chunk with jpeg exif data
+ // Chunk ordering
+ { u"oi1n0g16.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale mother image with 1 idat-chunk
+ { u"oi1n2c16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color mother image with 1 idat-chunk
+ { u"oi2n0g16.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale image with 2 idat-chunks
+ { u"oi2n2c16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color image with 2 idat-chunks
+ { u"oi4n0g16.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale image with 4 unequal sized idat-chunks
+ { u"oi4n2c16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color image with 4 unequal sized idat-chunks
+ { u"oi9n0g16.png",
+ {
+ 32,
+ 32,
+ 8,
+ true,
+ false,
+ } }, // grayscale image with all idat-chunks length one
+ { u"oi9n2c16.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color image with all idat-chunks length one
+ // Zlib compression
+ { u"z00n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color no interlacing, compression level 0 (none)
+ { u"z03n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color no interlacing, compression level 3
+ { u"z06n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color no interlacing, compression level 6 (default)
+ { u"z09n2c08.png",
+ {
+ 32,
+ 32,
+ 24,
+ false,
+ false,
+ } }, // color no interlacing, compression level 9 (maximum)
+ };
+
+ for (const auto & [ aCaseName, aCase ] : aCases)
+ {
+ checkImportExportPng(getFullUrl(aCaseName), aCase);
+ }
+
+ OUString aCorruptedFilenames[] = {
+ "xs1n0g01.png", // signature byte 1 MSBit reset to zero
+ "xs2n0g01.png", // signature byte 2 is a 'Q'
+ "xs4n0g01.png", // signature byte 4 lowercase
+ "xs7n0g01.png", // 7th byte a space instead of control-Z
+ "xcrn0g04.png", // added cr bytes
+ "xlfn0g04.png", // added lf bytes
+ "xhdn0g08.png", // incorrect IHDR checksum
+ "xc1n0g08.png", // color type 1
+ "xc9n2c08.png", // color type 9
+ "xd0n2c08.png", // bit-depth 0
+ "xd3n2c08.png", // bit-depth 3
+ "xd9n2c08.png", // bit-depth 99
+ "xdtn0g01.png", // missing IDAT chunk
+ "xcsn0g01.png", // incorrect IDAT checksum
+ };
+
+ for (const auto& aFilename : aCorruptedFilenames)
+ {
+ checkImportCorruptedPng(getFullUrl(aFilename));
+ }
+}
+
+void PngFilterTest::testMsGifInPng()
+{
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ {
+ Graphic aGraphic;
+ const OUString aURL(getFullUrl(u"ms-gif.png"));
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ ErrCode aResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, aResult);
+ CPPUNIT_ASSERT(aGraphic.IsGfxLink());
+ // The image is technically a PNG, but it has an animated Gif as a chunk (Microsoft extension).
+ CPPUNIT_ASSERT_EQUAL(GfxLinkType::NativeGif, aGraphic.GetSharedGfxLink()->GetType());
+ CPPUNIT_ASSERT(aGraphic.IsAnimated());
+ }
+ {
+ // Tests msOG chunk export support
+ const OUString aURL(getFullUrl(u"dummy.gif"));
+ SvFileStream aGIFStream(aURL, StreamMode::READ);
+ sal_uInt32 nGIFSize = aGIFStream.TellEnd();
+ const char* const pHeader = "MSOFFICE9.0";
+ auto nHeaderSize = strlen(pHeader);
+ uno::Sequence<sal_Int8> aGIFSequence(nHeaderSize + nGIFSize);
+ sal_Int8* pSequence = aGIFSequence.getArray();
+ for (size_t i = 0; i < nHeaderSize; i++)
+ *pSequence++ = pHeader[i];
+ aGIFStream.Seek(STREAM_SEEK_TO_BEGIN);
+ aGIFStream.ReadBytes(pSequence, nGIFSize);
+ // Create msOG chunk
+ beans::PropertyValue aChunkProperty, aFilterProperty;
+ aChunkProperty.Name = "msOG";
+ aChunkProperty.Value <<= aGIFSequence;
+ uno::Sequence<beans::PropertyValue> aAdditionalChunkSequence{ aChunkProperty };
+ aFilterProperty.Name = "AdditionalChunks";
+ aFilterProperty.Value <<= aAdditionalChunkSequence;
+ uno::Sequence<beans::PropertyValue> aPNGParameters{ aFilterProperty };
+ // Export the png with the chunk
+ utl::TempFileNamed aTempFile(u"testPngExportMsGif", true, u".png");
+ if (!bKeepTemp)
+ aTempFile.EnableKillingFile();
+ {
+ SvStream& rStream = *aTempFile.GetStream(StreamMode::WRITE);
+ BitmapEx aDummyBitmap(Size(8, 8), vcl::PixelFormat::N24_BPP);
+ vcl::PngImageWriter aPngWriter(rStream);
+ aPngWriter.setParameters(aPNGParameters);
+ bool bWriteSuccess = aPngWriter.write(aDummyBitmap);
+ CPPUNIT_ASSERT_EQUAL(true, bWriteSuccess);
+ aTempFile.CloseStream();
+ }
+ {
+ SvStream& rStream = *aTempFile.GetStream(StreamMode::READ);
+ rStream.Seek(0);
+ // Import the png and check that it is a gif
+ Graphic aGraphic;
+ ErrCode aResult = rFilter.ImportGraphic(aGraphic, aTempFile.GetURL(), rStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, aResult);
+ CPPUNIT_ASSERT(aGraphic.IsGfxLink());
+ CPPUNIT_ASSERT_EQUAL(GfxLinkType::NativeGif, aGraphic.GetSharedGfxLink()->GetType());
+ CPPUNIT_ASSERT(aGraphic.IsAnimated());
+ }
+ }
+}
+
+void PngFilterTest::testPngRoundtrip8BitGrey()
+{
+ utl::TempFileNamed aTempFile(u"testPngRoundtrip8BitGrey");
+ if (!bKeepTemp)
+ aTempFile.EnableKillingFile();
+ {
+ SvStream& rStream = *aTempFile.GetStream(StreamMode::WRITE);
+ Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N8_BPP, &Bitmap::GetGreyPalette(256));
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(COL_BLACK);
+ for (int i = 0; i < 8; ++i)
+ {
+ for (int j = 0; j < 8; ++j)
+ {
+ pWriteAccess->SetPixel(i, j, COL_GRAY);
+ }
+ }
+ for (int i = 8; i < 16; ++i)
+ {
+ for (int j = 8; j < 16; ++j)
+ {
+ pWriteAccess->SetPixel(i, j, COL_LIGHTGRAY);
+ }
+ }
+ }
+ BitmapEx aBitmapEx(aBitmap);
+
+ vcl::PngImageWriter aPngWriter(rStream);
+ CPPUNIT_ASSERT_EQUAL(true, aPngWriter.write(aBitmapEx));
+ aTempFile.CloseStream();
+ }
+ {
+ SvStream& rStream = *aTempFile.GetStream(StreamMode::READ);
+
+ vcl::PngImageReader aPngReader(rStream);
+ BitmapEx aBitmapEx;
+ CPPUNIT_ASSERT_EQUAL(true, aPngReader.read(aBitmapEx));
+
+ CPPUNIT_ASSERT_EQUAL(Size(16, 16), aBitmapEx.GetSizePixel());
+
+ CPPUNIT_ASSERT_EQUAL(COL_GRAY, aBitmapEx.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTGRAY, aBitmapEx.GetPixelColor(15, 15));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, aBitmapEx.GetPixelColor(15, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, aBitmapEx.GetPixelColor(0, 15));
+ }
+}
+
+void PngFilterTest::testPngRoundtrip24()
+{
+ utl::TempFileNamed aTempFile(u"testPngRoundtrip24");
+ if (!bKeepTemp)
+ aTempFile.EnableKillingFile();
+ {
+ SvStream& rStream = *aTempFile.GetStream(StreamMode::WRITE);
+ Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ pWriteAccess->Erase(COL_BLACK);
+ for (int i = 0; i < 8; ++i)
+ {
+ for (int j = 0; j < 8; ++j)
+ {
+ pWriteAccess->SetPixel(i, j, COL_LIGHTRED);
+ }
+ }
+ for (int i = 8; i < 16; ++i)
+ {
+ for (int j = 8; j < 16; ++j)
+ {
+ pWriteAccess->SetPixel(i, j, COL_LIGHTBLUE);
+ }
+ }
+ }
+ BitmapEx aBitmapEx(aBitmap);
+
+ vcl::PngImageWriter aPngWriter(rStream);
+ CPPUNIT_ASSERT_EQUAL(true, aPngWriter.write(aBitmapEx));
+ }
+ {
+ SvStream& rStream = *aTempFile.GetStream(StreamMode::READ);
+ rStream.Seek(0);
+
+ vcl::PngImageReader aPngReader(rStream);
+ BitmapEx aBitmapEx;
+ CPPUNIT_ASSERT_EQUAL(true, aPngReader.read(aBitmapEx));
+
+ CPPUNIT_ASSERT_EQUAL(Size(16, 16), aBitmapEx.GetSizePixel());
+
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aBitmapEx.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, aBitmapEx.GetPixelColor(15, 15));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, aBitmapEx.GetPixelColor(15, 0));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, aBitmapEx.GetPixelColor(0, 15));
+ }
+}
+
+void PngFilterTest::testPngRoundtrip24_8()
+{
+ utl::TempFileNamed aTempFile(u"testPngRoundtrip24_8");
+ if (!bKeepTemp)
+ aTempFile.EnableKillingFile();
+ {
+ SvStream& rStream = *aTempFile.GetStream(StreamMode::WRITE);
+ Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);
+ AlphaMask aAlpha(Size(16, 16));
+ {
+ BitmapScopedWriteAccess pWriteAccessBitmap(aBitmap);
+ BitmapScopedWriteAccess pWriteAccessAlpha(aAlpha);
+ pWriteAccessAlpha->Erase(Color(0xAA, 0xAA, 0xAA));
+ pWriteAccessBitmap->Erase(COL_BLACK);
+ for (int i = 0; i < 8; ++i)
+ {
+ for (int j = 0; j < 8; ++j)
+ {
+ pWriteAccessBitmap->SetPixel(i, j, COL_LIGHTRED);
+ pWriteAccessAlpha->SetPixel(i, j, Color(0xBB, 0xBB, 0xBB));
+ }
+ }
+ for (int i = 8; i < 16; ++i)
+ {
+ for (int j = 8; j < 16; ++j)
+ {
+ pWriteAccessBitmap->SetPixel(i, j, COL_LIGHTBLUE);
+ pWriteAccessAlpha->SetPixel(i, j, Color(0xCC, 0xCC, 0xCC));
+ }
+ }
+ }
+ BitmapEx aBitmapEx(aBitmap, aAlpha);
+ vcl::PngImageWriter aPngWriter(rStream);
+ CPPUNIT_ASSERT_EQUAL(true, aPngWriter.write(aBitmapEx));
+ }
+ {
+ SvStream& rStream = *aTempFile.GetStream(StreamMode::READ);
+ rStream.Seek(0);
+
+ vcl::PngImageReader aPngReader(rStream);
+ BitmapEx aBitmapEx;
+ CPPUNIT_ASSERT_EQUAL(true, aPngReader.read(aBitmapEx));
+
+ CPPUNIT_ASSERT_EQUAL(Size(16, 16), aBitmapEx.GetSizePixel());
+
+ CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xBB, 0xFF, 0x00, 0x00),
+ aBitmapEx.GetPixelColor(0, 0));
+ CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xCC, 0x00, 0x00, 0xFF),
+ aBitmapEx.GetPixelColor(15, 15));
+ CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xAA, 0x00, 0x00, 0x00),
+ aBitmapEx.GetPixelColor(15, 0));
+ CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xAA, 0x00, 0x00, 0x00),
+ aBitmapEx.GetPixelColor(0, 15));
+ }
+}
+
+void PngFilterTest::testPngRoundtrip32() {}
+
+void PngFilterTest::testPngWrite8BitRGBPalette()
+{
+ SvMemoryStream aExportStream;
+ BitmapPalette aRedPalette;
+ aRedPalette.SetEntryCount(256);
+ for (sal_uInt16 i = 0; i < 256; i++)
+ {
+ aRedPalette[i].SetRed(i);
+ aRedPalette[i].SetGreen(0);
+ aRedPalette[i].SetBlue(0);
+ }
+ {
+ Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N8_BPP, &aRedPalette);
+ {
+ BitmapScopedWriteAccess pWriteAccessBitmap(aBitmap);
+ for (int i = 0; i < 16; i++)
+ {
+ for (int j = 0; j < 16; j++)
+ {
+ pWriteAccessBitmap->SetPixelIndex(i, j, i * 16 + j);
+ }
+ }
+ }
+ BitmapEx aBitmapEx(aBitmap);
+ vcl::PngImageWriter aPngWriter(aExportStream);
+ CPPUNIT_ASSERT_EQUAL(true, aPngWriter.write(aBitmapEx));
+ }
+ aExportStream.Seek(0);
+ {
+ vcl::PngImageReader aPngReader(aExportStream);
+ BitmapEx aBitmapEx;
+ CPPUNIT_ASSERT_EQUAL(true, aPngReader.read(aBitmapEx));
+
+ CPPUNIT_ASSERT_EQUAL(Size(16, 16), aBitmapEx.GetSizePixel());
+
+ for (int i = 0; i < 16; i++)
+ {
+ for (int j = 0; j < 16; j++)
+ {
+ CPPUNIT_ASSERT_EQUAL(aRedPalette[i * 16 + j].GetRGBColor(),
+ aBitmapEx.GetPixelColor(j, i));
+ }
+ }
+ }
+}
+
+void PngFilterTest::testTdf153180MonochromeFilterPngExport()
+{
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+
+ Graphic aGraphicOriginal;
+ {
+ // 3 * 16 bits rgb color alpha, no background chunk
+ const OUString aURL(getFullUrl(u"bgan6a16.png"));
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ ErrCode aResult = rFilter.ImportGraphic(aGraphicOriginal, aURL, aFileStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, aResult);
+ CPPUNIT_ASSERT(aGraphicOriginal.IsAlpha());
+ }
+
+ // Apply the monochrome filter to the graphic but keep the alpha.
+ BitmapEx aBitmapEx(aGraphicOriginal.GetBitmapEx());
+ AlphaMask aAlphaMask(aBitmapEx.GetAlphaMask());
+
+ BitmapEx aTmpBmpEx(aBitmapEx.GetBitmap());
+ BitmapFilter::Filter(aTmpBmpEx, BitmapMonochromeFilter{ sal_uInt8{ 127 } });
+
+ Graphic aGraphicAfterFilter{ BitmapEx(aTmpBmpEx.GetBitmap(), aAlphaMask) };
+ CPPUNIT_ASSERT(aGraphicAfterFilter.IsAlpha());
+
+ // export the resulting graphic
+ utl::TempFileNamed aTempFile(u"testPngExportTdf153180", true, u".png");
+ if (!bKeepTemp)
+ aTempFile.EnableKillingFile();
+ {
+ SvStream& rStream = *aTempFile.GetStream(StreamMode::WRITE);
+ vcl::PngImageWriter aPngWriter(rStream);
+ bool bWriteSuccess = aPngWriter.write(aGraphicAfterFilter.GetBitmapEx());
+ CPPUNIT_ASSERT_EQUAL(true, bWriteSuccess);
+ aTempFile.CloseStream();
+ }
+ {
+ SvStream& rStream = *aTempFile.GetStream(StreamMode::READ);
+ rStream.Seek(0);
+ // Import the png and check that it still has alpha
+ Graphic aGraphic;
+ ErrCode aResult = rFilter.ImportGraphic(aGraphic, aTempFile.GetURL(), rStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, aResult);
+
+ // Without the accompanying patch would fail with:
+ // assertion failed
+ // -Expression : aGraphic.IsAlpha()
+ CPPUNIT_ASSERT(aGraphic.IsAlpha());
+ }
+}
+
+void PngFilterTest::testDump()
+{
+ utl::TempFileNamed aTempFile;
+ Bitmap aBitmap(Size(1, 1), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pWriteAccessBitmap(aBitmap);
+ pWriteAccessBitmap->SetPixel(0, 0, BitmapColor());
+ }
+ BitmapEx aBitmapEx(aBitmap);
+ aBitmapEx.DumpAsPng(aTempFile.GetURL().toUtf8().getStr());
+ SvStream* pStream = aTempFile.GetStream(StreamMode::READ);
+ CPPUNIT_ASSERT_GREATER(static_cast<sal_uInt64>(0), pStream->remainingSize());
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(PngFilterTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/png/data/alpha-rect-8bit-RGBA.png b/vcl/qa/cppunit/png/data/alpha-rect-8bit-RGBA.png
new file mode 100644
index 0000000000..1e90e1a6cf
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/alpha-rect-8bit-RGBA.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/apng_simple.apng b/vcl/qa/cppunit/png/data/apng_simple.apng
new file mode 100644
index 0000000000..445b7acaf4
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/apng_simple.apng
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basi0g01.png b/vcl/qa/cppunit/png/data/basi0g01.png
new file mode 100644
index 0000000000..556fa72704
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basi0g01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basi0g02.png b/vcl/qa/cppunit/png/data/basi0g02.png
new file mode 100644
index 0000000000..ce09821ef1
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basi0g02.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basi0g04.png b/vcl/qa/cppunit/png/data/basi0g04.png
new file mode 100644
index 0000000000..3853273f93
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basi0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basi0g08.png b/vcl/qa/cppunit/png/data/basi0g08.png
new file mode 100644
index 0000000000..faed8bec44
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basi0g08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basi0g16.png b/vcl/qa/cppunit/png/data/basi0g16.png
new file mode 100644
index 0000000000..a9f28165ef
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basi0g16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basi2c08.png b/vcl/qa/cppunit/png/data/basi2c08.png
new file mode 100644
index 0000000000..2aab44d42b
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basi2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basi2c16.png b/vcl/qa/cppunit/png/data/basi2c16.png
new file mode 100644
index 0000000000..cd7e50f914
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basi2c16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basi3p01.png b/vcl/qa/cppunit/png/data/basi3p01.png
new file mode 100644
index 0000000000..00a7cea6c2
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basi3p01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basi3p02.png b/vcl/qa/cppunit/png/data/basi3p02.png
new file mode 100644
index 0000000000..bb16b44b30
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basi3p02.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basi3p04.png b/vcl/qa/cppunit/png/data/basi3p04.png
new file mode 100644
index 0000000000..b4e888e247
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basi3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basi3p08.png b/vcl/qa/cppunit/png/data/basi3p08.png
new file mode 100644
index 0000000000..50a6d1cac7
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basi3p08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basi4a08.png b/vcl/qa/cppunit/png/data/basi4a08.png
new file mode 100644
index 0000000000..398132be5f
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basi4a08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basi4a16.png b/vcl/qa/cppunit/png/data/basi4a16.png
new file mode 100644
index 0000000000..51192e7311
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basi4a16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basi6a08.png b/vcl/qa/cppunit/png/data/basi6a08.png
new file mode 100644
index 0000000000..aecb32e0d9
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basi6a08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basi6a16.png b/vcl/qa/cppunit/png/data/basi6a16.png
new file mode 100644
index 0000000000..4181533ad8
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basi6a16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basn0g01.png b/vcl/qa/cppunit/png/data/basn0g01.png
new file mode 100644
index 0000000000..1d722423aa
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basn0g01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basn0g02.png b/vcl/qa/cppunit/png/data/basn0g02.png
new file mode 100644
index 0000000000..508332418f
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basn0g02.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basn0g04.png b/vcl/qa/cppunit/png/data/basn0g04.png
new file mode 100644
index 0000000000..0bf3687863
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basn0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basn0g08.png b/vcl/qa/cppunit/png/data/basn0g08.png
new file mode 100644
index 0000000000..23c82379a2
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basn0g08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basn0g16.png b/vcl/qa/cppunit/png/data/basn0g16.png
new file mode 100644
index 0000000000..e7c82f78eb
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basn0g16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basn2c08.png b/vcl/qa/cppunit/png/data/basn2c08.png
new file mode 100644
index 0000000000..db5ad15865
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basn2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basn2c16.png b/vcl/qa/cppunit/png/data/basn2c16.png
new file mode 100644
index 0000000000..50c1cb91a0
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basn2c16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basn3p01.png b/vcl/qa/cppunit/png/data/basn3p01.png
new file mode 100644
index 0000000000..b145c2b8ef
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basn3p01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basn3p02.png b/vcl/qa/cppunit/png/data/basn3p02.png
new file mode 100644
index 0000000000..8985b3d818
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basn3p02.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basn3p04.png b/vcl/qa/cppunit/png/data/basn3p04.png
new file mode 100644
index 0000000000..0fbf9e827b
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basn3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basn3p08.png b/vcl/qa/cppunit/png/data/basn3p08.png
new file mode 100644
index 0000000000..0ddad07e5f
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basn3p08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basn4a08.png b/vcl/qa/cppunit/png/data/basn4a08.png
new file mode 100644
index 0000000000..3e13052201
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basn4a08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basn4a16.png b/vcl/qa/cppunit/png/data/basn4a16.png
new file mode 100644
index 0000000000..8243644d07
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basn4a16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basn6a08.png b/vcl/qa/cppunit/png/data/basn6a08.png
new file mode 100644
index 0000000000..e608738763
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basn6a08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/basn6a16.png b/vcl/qa/cppunit/png/data/basn6a16.png
new file mode 100644
index 0000000000..984a99525f
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/basn6a16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/bgai4a08.png b/vcl/qa/cppunit/png/data/bgai4a08.png
new file mode 100644
index 0000000000..398132be5f
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/bgai4a08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/bgai4a16.png b/vcl/qa/cppunit/png/data/bgai4a16.png
new file mode 100644
index 0000000000..51192e7311
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/bgai4a16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/bgan6a08.png b/vcl/qa/cppunit/png/data/bgan6a08.png
new file mode 100644
index 0000000000..e608738763
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/bgan6a08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/bgan6a16.png b/vcl/qa/cppunit/png/data/bgan6a16.png
new file mode 100644
index 0000000000..984a99525f
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/bgan6a16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/bgbn4a08.png b/vcl/qa/cppunit/png/data/bgbn4a08.png
new file mode 100644
index 0000000000..7cbefc3bff
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/bgbn4a08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/bggn4a16.png b/vcl/qa/cppunit/png/data/bggn4a16.png
new file mode 100644
index 0000000000..13fd85ba19
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/bggn4a16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/bgwn6a08.png b/vcl/qa/cppunit/png/data/bgwn6a08.png
new file mode 100644
index 0000000000..a67ff205bb
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/bgwn6a08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/bgyn6a16.png b/vcl/qa/cppunit/png/data/bgyn6a16.png
new file mode 100644
index 0000000000..ae3e9be58a
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/bgyn6a16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/ccwn2c08.png b/vcl/qa/cppunit/png/data/ccwn2c08.png
new file mode 100644
index 0000000000..47c24817b7
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/ccwn2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/ccwn3p08.png b/vcl/qa/cppunit/png/data/ccwn3p08.png
new file mode 100644
index 0000000000..8bb2c10981
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/ccwn3p08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/cdfn2c08.png b/vcl/qa/cppunit/png/data/cdfn2c08.png
new file mode 100644
index 0000000000..559e5261e7
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/cdfn2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/cdhn2c08.png b/vcl/qa/cppunit/png/data/cdhn2c08.png
new file mode 100644
index 0000000000..3e07e8ecbd
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/cdhn2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/cdsn2c08.png b/vcl/qa/cppunit/png/data/cdsn2c08.png
new file mode 100644
index 0000000000..076c32cc08
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/cdsn2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/cdun2c08.png b/vcl/qa/cppunit/png/data/cdun2c08.png
new file mode 100644
index 0000000000..846033be6b
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/cdun2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/ch1n3p04.png b/vcl/qa/cppunit/png/data/ch1n3p04.png
new file mode 100644
index 0000000000..17cd12dfc9
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/ch1n3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/ch2n3p08.png b/vcl/qa/cppunit/png/data/ch2n3p08.png
new file mode 100644
index 0000000000..25c17987a7
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/ch2n3p08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/cm0n0g04.png b/vcl/qa/cppunit/png/data/cm0n0g04.png
new file mode 100644
index 0000000000..9fba5db3b8
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/cm0n0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/cm7n0g04.png b/vcl/qa/cppunit/png/data/cm7n0g04.png
new file mode 100644
index 0000000000..f7dc46e685
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/cm7n0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/cm9n0g04.png b/vcl/qa/cppunit/png/data/cm9n0g04.png
new file mode 100644
index 0000000000..dd70911adc
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/cm9n0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/color-rect-4bit-pal.png b/vcl/qa/cppunit/png/data/color-rect-4bit-pal.png
new file mode 100644
index 0000000000..740eede512
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/color-rect-4bit-pal.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/color-rect-8bit-RGB-interlaced.png b/vcl/qa/cppunit/png/data/color-rect-8bit-RGB-interlaced.png
new file mode 100644
index 0000000000..17ca9a350d
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/color-rect-8bit-RGB-interlaced.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/color-rect-8bit-RGB.png b/vcl/qa/cppunit/png/data/color-rect-8bit-RGB.png
new file mode 100644
index 0000000000..727859d8a7
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/color-rect-8bit-RGB.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/cs3n2c16.png b/vcl/qa/cppunit/png/data/cs3n2c16.png
new file mode 100644
index 0000000000..bf5fd20a20
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/cs3n2c16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/cs3n3p08.png b/vcl/qa/cppunit/png/data/cs3n3p08.png
new file mode 100644
index 0000000000..f4a66237bf
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/cs3n3p08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/cs5n2c08.png b/vcl/qa/cppunit/png/data/cs5n2c08.png
new file mode 100644
index 0000000000..40f947c33e
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/cs5n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/cs5n3p08.png b/vcl/qa/cppunit/png/data/cs5n3p08.png
new file mode 100644
index 0000000000..dfd6e6e6ec
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/cs5n3p08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/cs8n2c08.png b/vcl/qa/cppunit/png/data/cs8n2c08.png
new file mode 100644
index 0000000000..8e01d3294f
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/cs8n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/cs8n3p08.png b/vcl/qa/cppunit/png/data/cs8n3p08.png
new file mode 100644
index 0000000000..a44066eb6e
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/cs8n3p08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/ct0n0g04.png b/vcl/qa/cppunit/png/data/ct0n0g04.png
new file mode 100644
index 0000000000..40d1e062f8
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/ct0n0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/ct1n0g04.png b/vcl/qa/cppunit/png/data/ct1n0g04.png
new file mode 100644
index 0000000000..3ba110aa76
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/ct1n0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/cten0g04.png b/vcl/qa/cppunit/png/data/cten0g04.png
new file mode 100644
index 0000000000..a6a56faf2b
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/cten0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/ctfn0g04.png b/vcl/qa/cppunit/png/data/ctfn0g04.png
new file mode 100644
index 0000000000..353873ebbd
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/ctfn0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/ctgn0g04.png b/vcl/qa/cppunit/png/data/ctgn0g04.png
new file mode 100644
index 0000000000..453f2b0a49
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/ctgn0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/cthn0g04.png b/vcl/qa/cppunit/png/data/cthn0g04.png
new file mode 100644
index 0000000000..8fce253e6d
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/cthn0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/ctjn0g04.png b/vcl/qa/cppunit/png/data/ctjn0g04.png
new file mode 100644
index 0000000000..a77b8d2fee
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/ctjn0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/ctzn0g04.png b/vcl/qa/cppunit/png/data/ctzn0g04.png
new file mode 100644
index 0000000000..b4401c9cfc
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/ctzn0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/dummy.gif b/vcl/qa/cppunit/png/data/dummy.gif
new file mode 100644
index 0000000000..fd5c62dcdc
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/dummy.gif
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/exif2c08.png b/vcl/qa/cppunit/png/data/exif2c08.png
new file mode 100644
index 0000000000..56eb734991
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/exif2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/f00n0g08.png b/vcl/qa/cppunit/png/data/f00n0g08.png
new file mode 100644
index 0000000000..45a0075967
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/f00n0g08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/f00n2c08.png b/vcl/qa/cppunit/png/data/f00n2c08.png
new file mode 100644
index 0000000000..d6a1ffff62
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/f00n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/f01n0g08.png b/vcl/qa/cppunit/png/data/f01n0g08.png
new file mode 100644
index 0000000000..4a1107b463
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/f01n0g08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/f01n2c08.png b/vcl/qa/cppunit/png/data/f01n2c08.png
new file mode 100644
index 0000000000..26fee958ce
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/f01n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/f02n0g08.png b/vcl/qa/cppunit/png/data/f02n0g08.png
new file mode 100644
index 0000000000..bfe410c5e7
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/f02n0g08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/f02n2c08.png b/vcl/qa/cppunit/png/data/f02n2c08.png
new file mode 100644
index 0000000000..e590f12348
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/f02n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/f03n0g08.png b/vcl/qa/cppunit/png/data/f03n0g08.png
new file mode 100644
index 0000000000..ed01e2923c
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/f03n0g08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/f03n2c08.png b/vcl/qa/cppunit/png/data/f03n2c08.png
new file mode 100644
index 0000000000..758115059d
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/f03n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/f04n0g08.png b/vcl/qa/cppunit/png/data/f04n0g08.png
new file mode 100644
index 0000000000..663fdae3e7
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/f04n0g08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/f04n2c08.png b/vcl/qa/cppunit/png/data/f04n2c08.png
new file mode 100644
index 0000000000..3c8b5116e7
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/f04n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/f99n0g04.png b/vcl/qa/cppunit/png/data/f99n0g04.png
new file mode 100644
index 0000000000..0b521c1d56
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/f99n0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g03n0g16.png b/vcl/qa/cppunit/png/data/g03n0g16.png
new file mode 100644
index 0000000000..41083ca80f
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g03n0g16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g03n2c08.png b/vcl/qa/cppunit/png/data/g03n2c08.png
new file mode 100644
index 0000000000..a9354dbee6
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g03n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g03n3p04.png b/vcl/qa/cppunit/png/data/g03n3p04.png
new file mode 100644
index 0000000000..60396c95af
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g03n3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g04n0g16.png b/vcl/qa/cppunit/png/data/g04n0g16.png
new file mode 100644
index 0000000000..32395b76c9
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g04n0g16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g04n2c08.png b/vcl/qa/cppunit/png/data/g04n2c08.png
new file mode 100644
index 0000000000..a652b0ce87
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g04n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g04n3p04.png b/vcl/qa/cppunit/png/data/g04n3p04.png
new file mode 100644
index 0000000000..5661cc3131
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g04n3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g05n0g16.png b/vcl/qa/cppunit/png/data/g05n0g16.png
new file mode 100644
index 0000000000..70b37f01e2
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g05n0g16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g05n2c08.png b/vcl/qa/cppunit/png/data/g05n2c08.png
new file mode 100644
index 0000000000..932c136536
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g05n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g05n3p04.png b/vcl/qa/cppunit/png/data/g05n3p04.png
new file mode 100644
index 0000000000..9619930585
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g05n3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g07n0g16.png b/vcl/qa/cppunit/png/data/g07n0g16.png
new file mode 100644
index 0000000000..d6a47c2d57
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g07n0g16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g07n2c08.png b/vcl/qa/cppunit/png/data/g07n2c08.png
new file mode 100644
index 0000000000..597346460f
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g07n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g07n3p04.png b/vcl/qa/cppunit/png/data/g07n3p04.png
new file mode 100644
index 0000000000..c73fb61365
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g07n3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g10n0g16.png b/vcl/qa/cppunit/png/data/g10n0g16.png
new file mode 100644
index 0000000000..85f2c958e9
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g10n0g16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g10n2c08.png b/vcl/qa/cppunit/png/data/g10n2c08.png
new file mode 100644
index 0000000000..b3039970c1
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g10n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g10n3p04.png b/vcl/qa/cppunit/png/data/g10n3p04.png
new file mode 100644
index 0000000000..1b6a6be2ca
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g10n3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g25n0g16.png b/vcl/qa/cppunit/png/data/g25n0g16.png
new file mode 100644
index 0000000000..a9f6787c7a
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g25n0g16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g25n2c08.png b/vcl/qa/cppunit/png/data/g25n2c08.png
new file mode 100644
index 0000000000..03f505a64b
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g25n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/g25n3p04.png b/vcl/qa/cppunit/png/data/g25n3p04.png
new file mode 100644
index 0000000000..4f943c6175
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/g25n3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/ms-gif.png b/vcl/qa/cppunit/png/data/ms-gif.png
new file mode 100644
index 0000000000..1f683241f5
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/ms-gif.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/oi1n0g16.png b/vcl/qa/cppunit/png/data/oi1n0g16.png
new file mode 100644
index 0000000000..e7c82f78eb
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/oi1n0g16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/oi1n2c16.png b/vcl/qa/cppunit/png/data/oi1n2c16.png
new file mode 100644
index 0000000000..50c1cb91a0
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/oi1n2c16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/oi2n0g16.png b/vcl/qa/cppunit/png/data/oi2n0g16.png
new file mode 100644
index 0000000000..14d64c583d
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/oi2n0g16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/oi2n2c16.png b/vcl/qa/cppunit/png/data/oi2n2c16.png
new file mode 100644
index 0000000000..4c2e3e3352
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/oi2n2c16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/oi4n0g16.png b/vcl/qa/cppunit/png/data/oi4n0g16.png
new file mode 100644
index 0000000000..69e73ede31
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/oi4n0g16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/oi4n2c16.png b/vcl/qa/cppunit/png/data/oi4n2c16.png
new file mode 100644
index 0000000000..93691e373a
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/oi4n2c16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/oi9n0g16.png b/vcl/qa/cppunit/png/data/oi9n0g16.png
new file mode 100644
index 0000000000..9248413576
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/oi9n0g16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/oi9n2c16.png b/vcl/qa/cppunit/png/data/oi9n2c16.png
new file mode 100644
index 0000000000..f0512e49f2
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/oi9n2c16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/pp0n2c16.png b/vcl/qa/cppunit/png/data/pp0n2c16.png
new file mode 100644
index 0000000000..8f2aad7335
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/pp0n2c16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/pp0n6a08.png b/vcl/qa/cppunit/png/data/pp0n6a08.png
new file mode 100644
index 0000000000..4ed7a30e4d
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/pp0n6a08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/ps1n0g08.png b/vcl/qa/cppunit/png/data/ps1n0g08.png
new file mode 100644
index 0000000000..99625fa4ba
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/ps1n0g08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/ps1n2c16.png b/vcl/qa/cppunit/png/data/ps1n2c16.png
new file mode 100644
index 0000000000..0c7a6b380e
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/ps1n2c16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/ps2n0g08.png b/vcl/qa/cppunit/png/data/ps2n0g08.png
new file mode 100644
index 0000000000..90b2979685
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/ps2n0g08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/ps2n2c16.png b/vcl/qa/cppunit/png/data/ps2n2c16.png
new file mode 100644
index 0000000000..a4a181e4ec
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/ps2n2c16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/rect-1bit-pal.png b/vcl/qa/cppunit/png/data/rect-1bit-pal.png
new file mode 100644
index 0000000000..cf7ac3e7c3
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/rect-1bit-pal.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s01i3p01.png b/vcl/qa/cppunit/png/data/s01i3p01.png
new file mode 100644
index 0000000000..6c0fad1fc9
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s01i3p01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s01n3p01.png b/vcl/qa/cppunit/png/data/s01n3p01.png
new file mode 100644
index 0000000000..cb2c8c7826
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s01n3p01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s02i3p01.png b/vcl/qa/cppunit/png/data/s02i3p01.png
new file mode 100644
index 0000000000..2defaed911
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s02i3p01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s02n3p01.png b/vcl/qa/cppunit/png/data/s02n3p01.png
new file mode 100644
index 0000000000..2b1b669643
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s02n3p01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s03i3p01.png b/vcl/qa/cppunit/png/data/s03i3p01.png
new file mode 100644
index 0000000000..c23fdc4631
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s03i3p01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s03n3p01.png b/vcl/qa/cppunit/png/data/s03n3p01.png
new file mode 100644
index 0000000000..6d96ee4f87
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s03n3p01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s04i3p01.png b/vcl/qa/cppunit/png/data/s04i3p01.png
new file mode 100644
index 0000000000..0e710c2c39
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s04i3p01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s04n3p01.png b/vcl/qa/cppunit/png/data/s04n3p01.png
new file mode 100644
index 0000000000..956396c45b
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s04n3p01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s05i3p02.png b/vcl/qa/cppunit/png/data/s05i3p02.png
new file mode 100644
index 0000000000..d14cbd351a
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s05i3p02.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s05n3p02.png b/vcl/qa/cppunit/png/data/s05n3p02.png
new file mode 100644
index 0000000000..bf940f0576
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s05n3p02.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s06i3p02.png b/vcl/qa/cppunit/png/data/s06i3p02.png
new file mode 100644
index 0000000000..456ada3200
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s06i3p02.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s06n3p02.png b/vcl/qa/cppunit/png/data/s06n3p02.png
new file mode 100644
index 0000000000..501064dc25
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s06n3p02.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s07i3p02.png b/vcl/qa/cppunit/png/data/s07i3p02.png
new file mode 100644
index 0000000000..44b66bab9e
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s07i3p02.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s07n3p02.png b/vcl/qa/cppunit/png/data/s07n3p02.png
new file mode 100644
index 0000000000..6a582593d6
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s07n3p02.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s08i3p02.png b/vcl/qa/cppunit/png/data/s08i3p02.png
new file mode 100644
index 0000000000..acf74f3fc4
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s08i3p02.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s08n3p02.png b/vcl/qa/cppunit/png/data/s08n3p02.png
new file mode 100644
index 0000000000..b7094e1b4f
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s08n3p02.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s09i3p02.png b/vcl/qa/cppunit/png/data/s09i3p02.png
new file mode 100644
index 0000000000..0bfae8e456
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s09i3p02.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s09n3p02.png b/vcl/qa/cppunit/png/data/s09n3p02.png
new file mode 100644
index 0000000000..711ab82451
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s09n3p02.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s32i3p04.png b/vcl/qa/cppunit/png/data/s32i3p04.png
new file mode 100644
index 0000000000..0841910b72
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s32i3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s32n3p04.png b/vcl/qa/cppunit/png/data/s32n3p04.png
new file mode 100644
index 0000000000..fa58e3e3f6
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s32n3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s33i3p04.png b/vcl/qa/cppunit/png/data/s33i3p04.png
new file mode 100644
index 0000000000..ab0dc14aba
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s33i3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s33n3p04.png b/vcl/qa/cppunit/png/data/s33n3p04.png
new file mode 100644
index 0000000000..764f1a3dc7
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s33n3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s34i3p04.png b/vcl/qa/cppunit/png/data/s34i3p04.png
new file mode 100644
index 0000000000..bd99039be4
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s34i3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s34n3p04.png b/vcl/qa/cppunit/png/data/s34n3p04.png
new file mode 100644
index 0000000000..9cbc68b3b9
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s34n3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s35i3p04.png b/vcl/qa/cppunit/png/data/s35i3p04.png
new file mode 100644
index 0000000000..e2a5e0a659
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s35i3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s35n3p04.png b/vcl/qa/cppunit/png/data/s35n3p04.png
new file mode 100644
index 0000000000..90b892ebaf
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s35n3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s36i3p04.png b/vcl/qa/cppunit/png/data/s36i3p04.png
new file mode 100644
index 0000000000..eb61b6f9a3
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s36i3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s36n3p04.png b/vcl/qa/cppunit/png/data/s36n3p04.png
new file mode 100644
index 0000000000..b38d179774
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s36n3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s37i3p04.png b/vcl/qa/cppunit/png/data/s37i3p04.png
new file mode 100644
index 0000000000..6e2b1e9b79
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s37i3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s37n3p04.png b/vcl/qa/cppunit/png/data/s37n3p04.png
new file mode 100644
index 0000000000..4d3054da51
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s37n3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s38i3p04.png b/vcl/qa/cppunit/png/data/s38i3p04.png
new file mode 100644
index 0000000000..a0a8a140ad
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s38i3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s38n3p04.png b/vcl/qa/cppunit/png/data/s38n3p04.png
new file mode 100644
index 0000000000..1233ed048e
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s38n3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s39i3p04.png b/vcl/qa/cppunit/png/data/s39i3p04.png
new file mode 100644
index 0000000000..04fee93eae
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s39i3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s39n3p04.png b/vcl/qa/cppunit/png/data/s39n3p04.png
new file mode 100644
index 0000000000..c750100d55
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s39n3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s40i3p04.png b/vcl/qa/cppunit/png/data/s40i3p04.png
new file mode 100644
index 0000000000..68f358b822
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s40i3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/s40n3p04.png b/vcl/qa/cppunit/png/data/s40n3p04.png
new file mode 100644
index 0000000000..864b6b9673
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/s40n3p04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/tbbn0g04.png b/vcl/qa/cppunit/png/data/tbbn0g04.png
new file mode 100644
index 0000000000..39a7050d27
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/tbbn0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/tbbn2c16.png b/vcl/qa/cppunit/png/data/tbbn2c16.png
new file mode 100644
index 0000000000..dd3168e5c8
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/tbbn2c16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/tbbn3p08.png b/vcl/qa/cppunit/png/data/tbbn3p08.png
new file mode 100644
index 0000000000..0ede3574db
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/tbbn3p08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/tbgn2c16.png b/vcl/qa/cppunit/png/data/tbgn2c16.png
new file mode 100644
index 0000000000..85cec395c0
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/tbgn2c16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/tbgn3p08.png b/vcl/qa/cppunit/png/data/tbgn3p08.png
new file mode 100644
index 0000000000..8cf2e6fb6a
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/tbgn3p08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/tbrn2c08.png b/vcl/qa/cppunit/png/data/tbrn2c08.png
new file mode 100644
index 0000000000..5cca0d6210
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/tbrn2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/tbwn0g16.png b/vcl/qa/cppunit/png/data/tbwn0g16.png
new file mode 100644
index 0000000000..99bdeed2b3
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/tbwn0g16.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/tbwn3p08.png b/vcl/qa/cppunit/png/data/tbwn3p08.png
new file mode 100644
index 0000000000..eacab7a144
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/tbwn3p08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/tbyn3p08.png b/vcl/qa/cppunit/png/data/tbyn3p08.png
new file mode 100644
index 0000000000..656db0989a
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/tbyn3p08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/tm3n3p02.png b/vcl/qa/cppunit/png/data/tm3n3p02.png
new file mode 100644
index 0000000000..fb3ef1d0c5
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/tm3n3p02.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/tp1n3p08.png b/vcl/qa/cppunit/png/data/tp1n3p08.png
new file mode 100644
index 0000000000..a6c9f35a86
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/tp1n3p08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/xc1n0g08.png b/vcl/qa/cppunit/png/data/xc1n0g08.png
new file mode 100644
index 0000000000..9404227370
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/xc1n0g08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/xc9n2c08.png b/vcl/qa/cppunit/png/data/xc9n2c08.png
new file mode 100644
index 0000000000..b11c2a7b40
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/xc9n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/xcrn0g04.png b/vcl/qa/cppunit/png/data/xcrn0g04.png
new file mode 100644
index 0000000000..48abba193a
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/xcrn0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/xcsn0g01.png b/vcl/qa/cppunit/png/data/xcsn0g01.png
new file mode 100644
index 0000000000..9863a262ca
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/xcsn0g01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/xd0n2c08.png b/vcl/qa/cppunit/png/data/xd0n2c08.png
new file mode 100644
index 0000000000..2f001610a8
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/xd0n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/xd3n2c08.png b/vcl/qa/cppunit/png/data/xd3n2c08.png
new file mode 100644
index 0000000000..9e4a3ff7ac
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/xd3n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/xd9n2c08.png b/vcl/qa/cppunit/png/data/xd9n2c08.png
new file mode 100644
index 0000000000..2c3b91aa4f
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/xd9n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/xdtn0g01.png b/vcl/qa/cppunit/png/data/xdtn0g01.png
new file mode 100644
index 0000000000..1a81abef82
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/xdtn0g01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/xhdn0g08.png b/vcl/qa/cppunit/png/data/xhdn0g08.png
new file mode 100644
index 0000000000..fcb8737fa2
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/xhdn0g08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/xlfn0g04.png b/vcl/qa/cppunit/png/data/xlfn0g04.png
new file mode 100644
index 0000000000..d9ec53ed94
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/xlfn0g04.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/xs1n0g01.png b/vcl/qa/cppunit/png/data/xs1n0g01.png
new file mode 100644
index 0000000000..1817c5144f
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/xs1n0g01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/xs2n0g01.png b/vcl/qa/cppunit/png/data/xs2n0g01.png
new file mode 100644
index 0000000000..b8147f2a84
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/xs2n0g01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/xs4n0g01.png b/vcl/qa/cppunit/png/data/xs4n0g01.png
new file mode 100644
index 0000000000..45237a1d29
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/xs4n0g01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/xs7n0g01.png b/vcl/qa/cppunit/png/data/xs7n0g01.png
new file mode 100644
index 0000000000..3f307f14ea
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/xs7n0g01.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/z00n2c08.png b/vcl/qa/cppunit/png/data/z00n2c08.png
new file mode 100644
index 0000000000..7669eb8385
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/z00n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/z03n2c08.png b/vcl/qa/cppunit/png/data/z03n2c08.png
new file mode 100644
index 0000000000..bfb10de8de
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/z03n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/z06n2c08.png b/vcl/qa/cppunit/png/data/z06n2c08.png
new file mode 100644
index 0000000000..b90ebc10f5
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/z06n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/png/data/z09n2c08.png b/vcl/qa/cppunit/png/data/z09n2c08.png
new file mode 100644
index 0000000000..5f191a78ee
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/z09n2c08.png
Binary files differ
diff --git a/vcl/qa/cppunit/skia/skia.cxx b/vcl/qa/cppunit/skia/skia.cxx
new file mode 100644
index 0000000000..cb4223a4f8
--- /dev/null
+++ b/vcl/qa/cppunit/skia/skia.cxx
@@ -0,0 +1,592 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <test/bootstrapfixture.hxx>
+
+#include <tools/stream.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+#include <vcl/graphicfilter.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/skia/SkiaHelper.hxx>
+
+#include <skia/salbmp.hxx>
+#include <skia/utils.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+using namespace SkiaHelper;
+
+// This tests backends that use Skia (i.e. intentionally not the svp one, which is the default.)
+// Note that you still may need to actually set for Skia to be used (see vcl/README.vars).
+// If Skia is not enabled, all tests will be silently skipped.
+namespace
+{
+class SkiaTest : public test::BootstrapFixture
+{
+public:
+ SkiaTest()
+ : test::BootstrapFixture(true, false)
+ {
+ }
+
+ void testBitmapErase();
+ void testDrawShaders();
+ void testInterpretAs8Bit();
+ void testAlphaBlendWith();
+ void testBitmapCopyOnWrite();
+ void testMatrixQuality();
+ void testDelayedScale();
+ void testDelayedScaleAlphaImage();
+ void testDrawDelayedScaleImage();
+ void testChecksum();
+ void testTdf137329();
+ void testTdf140848();
+ void testTdf132367();
+
+ CPPUNIT_TEST_SUITE(SkiaTest);
+ CPPUNIT_TEST(testBitmapErase);
+ CPPUNIT_TEST(testDrawShaders);
+ CPPUNIT_TEST(testInterpretAs8Bit);
+ CPPUNIT_TEST(testAlphaBlendWith);
+ CPPUNIT_TEST(testBitmapCopyOnWrite);
+ CPPUNIT_TEST(testMatrixQuality);
+ CPPUNIT_TEST(testDelayedScale);
+ CPPUNIT_TEST(testDelayedScaleAlphaImage);
+ CPPUNIT_TEST(testDrawDelayedScaleImage);
+ CPPUNIT_TEST(testChecksum);
+ CPPUNIT_TEST(testTdf137329);
+ CPPUNIT_TEST(testTdf140848);
+ CPPUNIT_TEST(testTdf132367);
+ CPPUNIT_TEST_SUITE_END();
+
+private:
+#if 0
+ template <class BitmapT> // handle both Bitmap and BitmapEx
+ void savePNG(const OUString& sWhere, const BitmapT& rBmp)
+ {
+ SvFileStream aStream(sWhere, StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(BitmapEx(rBmp), aStream);
+ }
+ void savePNG(const OUString& sWhere, const ScopedVclPtr<VirtualDevice>& device)
+ {
+ SvFileStream aStream(sWhere, StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(device->GetBitmapEx(Point(), device->GetOutputSizePixel()), aStream);
+ }
+#endif
+};
+
+void SkiaTest::testBitmapErase()
+{
+ if (!SkiaHelper::isVCLSkiaEnabled())
+ return;
+ Bitmap bitmap(Size(10, 10), vcl::PixelFormat::N24_BPP);
+ SkiaSalBitmap* skiaBitmap = dynamic_cast<SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaBitmap);
+ // Uninitialized bitmap.
+ CPPUNIT_ASSERT(!skiaBitmap->unittestHasBuffer());
+ CPPUNIT_ASSERT(!skiaBitmap->unittestHasImage());
+ CPPUNIT_ASSERT(!skiaBitmap->unittestHasAlphaImage());
+ CPPUNIT_ASSERT(!skiaBitmap->unittestHasEraseColor());
+ // Test that Bitmap.Erase() just sets erase color and doesn't allocate pixels.
+ bitmap.Erase(COL_RED);
+ skiaBitmap = dynamic_cast<SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(!skiaBitmap->unittestHasBuffer());
+ CPPUNIT_ASSERT(!skiaBitmap->unittestHasImage());
+ CPPUNIT_ASSERT(!skiaBitmap->unittestHasAlphaImage());
+ CPPUNIT_ASSERT(skiaBitmap->unittestHasEraseColor());
+ // Reading a pixel will create pixel data.
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_RED), BitmapReadAccess(bitmap).GetColor(0, 0));
+ skiaBitmap = dynamic_cast<SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaBitmap->unittestHasBuffer());
+ CPPUNIT_ASSERT(!skiaBitmap->unittestHasImage());
+ CPPUNIT_ASSERT(!skiaBitmap->unittestHasAlphaImage());
+ CPPUNIT_ASSERT(!skiaBitmap->unittestHasEraseColor());
+}
+
+// Test that draw calls that internally result in SkShader calls work properly.
+void SkiaTest::testDrawShaders()
+{
+ if (!SkiaHelper::isVCLSkiaEnabled())
+ return;
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(20, 20));
+ device->SetBackground(Wallpaper(COL_WHITE));
+ device->Erase();
+ Bitmap bitmap(Size(10, 10), vcl::PixelFormat::N24_BPP);
+ bitmap.Erase(COL_RED);
+ SkiaSalBitmap* skiaBitmap = dynamic_cast<SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaBitmap->PreferSkShader());
+ AlphaMask alpha(Size(10, 10));
+ alpha.Erase(64);
+ SkiaSalBitmap* skiaAlpha
+ = dynamic_cast<SkiaSalBitmap*>(alpha.GetBitmap().ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaAlpha->PreferSkShader());
+
+ device->DrawBitmap(Point(5, 5), bitmap);
+ //savePNG("/tmp/a1.png", device);
+ // Check that the area is painted, but nothing else.
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_RED, device->GetPixel(Point(5, 5)));
+ CPPUNIT_ASSERT_EQUAL(COL_RED, device->GetPixel(Point(14, 14)));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(15, 15)));
+ device->Erase();
+
+ device->DrawBitmapEx(Point(5, 5), BitmapEx(bitmap, alpha));
+ //savePNG("/tmp/a2.png", device);
+ Color resultRed(COL_RED.GetRed() * 3 / 4 + 64, 64, 64); // 3/4 red, 1/4 white
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(resultRed, device->GetPixel(Point(5, 5)));
+ CPPUNIT_ASSERT_EQUAL(resultRed, device->GetPixel(Point(14, 14)));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(15, 15)));
+ device->Erase();
+
+ basegfx::B2DHomMatrix matrix;
+ matrix.scale(10, 10);
+ matrix.rotate(M_PI / 4);
+ device->DrawTransformedBitmapEx(matrix, BitmapEx(bitmap, alpha));
+ //savePNG("/tmp/a3.png", device);
+ CPPUNIT_ASSERT_EQUAL(resultRed, device->GetPixel(Point(0, 1)));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(1, 0)));
+ CPPUNIT_ASSERT_EQUAL(resultRed, device->GetPixel(Point(0, 10)));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(10, 10)));
+ device->Erase();
+
+ // Test with scaling. Use everything 10x larger to reduce the impact of smoothscaling.
+ ScopedVclPtr<VirtualDevice> deviceLarge
+ = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ deviceLarge->SetOutputSizePixel(Size(200, 200));
+ deviceLarge->SetBackground(Wallpaper(COL_WHITE));
+ deviceLarge->Erase();
+ Bitmap bitmapLarge(Size(100, 100), vcl::PixelFormat::N24_BPP);
+ bitmapLarge.Erase(COL_RED);
+ SkiaSalBitmap* skiaBitmapLarge
+ = dynamic_cast<SkiaSalBitmap*>(bitmapLarge.ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaBitmapLarge->PreferSkShader());
+ AlphaMask alphaLarge(Size(100, 100));
+ alphaLarge.Erase(64);
+ {
+ BitmapWriteAccess access(bitmapLarge);
+ access.SetFillColor(COL_BLUE);
+ access.FillRect(tools::Rectangle(Point(20, 40), Size(10, 10)));
+ }
+ // Using alpha will still lead to shaders being used.
+ deviceLarge->DrawBitmapEx(Point(50, 50), Size(60, 60), Point(20, 20), Size(30, 30),
+ BitmapEx(bitmapLarge, alphaLarge));
+ //savePNG("/tmp/a4.png", deviceLarge);
+ Color resultBlue(64, 64, COL_BLUE.GetBlue() * 3 / 4 + 64); // 3/4 blue, 1/4 white
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, deviceLarge->GetPixel(Point(40, 40)));
+ CPPUNIT_ASSERT_EQUAL(resultRed, deviceLarge->GetPixel(Point(50, 50)));
+ CPPUNIT_ASSERT_EQUAL(resultRed, deviceLarge->GetPixel(Point(100, 100)));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, deviceLarge->GetPixel(Point(110, 110)));
+ // Don't test colors near the edge between the colors, smoothscaling affects them.
+ const int diff = 3;
+ CPPUNIT_ASSERT_EQUAL(resultRed, deviceLarge->GetPixel(Point(50 + diff, 89 - diff)));
+ CPPUNIT_ASSERT_EQUAL(resultBlue, deviceLarge->GetPixel(Point(50 + diff, 90 + diff)));
+ CPPUNIT_ASSERT_EQUAL(resultBlue, deviceLarge->GetPixel(Point(69 - diff, 100 - diff)));
+ CPPUNIT_ASSERT_EQUAL(resultRed, deviceLarge->GetPixel(Point(70 + diff, 100 - diff)));
+ CPPUNIT_ASSERT_EQUAL(resultBlue, deviceLarge->GetPixel(Point(50 + diff, 100 - diff)));
+ device->Erase();
+}
+
+void SkiaTest::testInterpretAs8Bit()
+{
+ if (!SkiaHelper::isVCLSkiaEnabled())
+ return;
+ Bitmap bitmap(Size(10, 10), vcl::PixelFormat::N24_BPP);
+ // Test with erase color.
+ bitmap.Erase(Color(33, 33, 33));
+ SkiaSalBitmap* skiaBitmap = dynamic_cast<SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaBitmap->unittestHasEraseColor());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, bitmap.getPixelFormat());
+ bitmap.Convert(BmpConversion::N8BitNoConversion);
+ skiaBitmap = dynamic_cast<SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaBitmap->unittestHasEraseColor());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, bitmap.getPixelFormat());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(33), BitmapReadAccess(bitmap).GetPixelIndex(0, 0));
+
+ // Test with image.
+ bitmap = Bitmap(Size(10, 10), vcl::PixelFormat::N24_BPP);
+ bitmap.Erase(Color(34, 34, 34));
+ BitmapReadAccess(bitmap).GetColor(0, 0); // Create pixel data, reset erase color.
+ skiaBitmap = dynamic_cast<SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get());
+ skiaBitmap->GetSkImage();
+ CPPUNIT_ASSERT(!skiaBitmap->unittestHasEraseColor());
+ CPPUNIT_ASSERT(skiaBitmap->unittestHasImage());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, bitmap.getPixelFormat());
+ bitmap.Convert(BmpConversion::N8BitNoConversion);
+ skiaBitmap = dynamic_cast<SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaBitmap->unittestHasImage());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, bitmap.getPixelFormat());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(34), BitmapReadAccess(bitmap).GetPixelIndex(0, 0));
+}
+
+void SkiaTest::testAlphaBlendWith()
+{
+ if (!SkiaHelper::isVCLSkiaEnabled())
+ return;
+ AlphaMask alpha(Size(10, 10));
+ AlphaMask bitmap(Size(10, 10));
+ // Test with erase colors set.
+ alpha.Erase(64);
+ SkiaSalBitmap* skiaAlpha
+ = dynamic_cast<SkiaSalBitmap*>(alpha.GetBitmap().ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaAlpha->unittestHasEraseColor());
+ bitmap.Erase(64);
+ SkiaSalBitmap* skiaBitmap
+ = dynamic_cast<SkiaSalBitmap*>(bitmap.GetBitmap().ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaBitmap->unittestHasEraseColor());
+ alpha.BlendWith(bitmap);
+ skiaAlpha = dynamic_cast<SkiaSalBitmap*>(alpha.GetBitmap().ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaAlpha->unittestHasEraseColor());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, alpha.getPixelFormat());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(208),
+ BitmapScopedReadAccess(alpha)->GetPixelIndex(0, 0));
+
+ // Test with images set.
+ alpha.Erase(64);
+ BitmapScopedReadAccess(alpha)->GetColor(0, 0); // Reading a pixel will create pixel data.
+ skiaAlpha = dynamic_cast<SkiaSalBitmap*>(alpha.GetBitmap().ImplGetSalBitmap().get());
+ skiaAlpha->GetSkImage();
+ CPPUNIT_ASSERT(!skiaAlpha->unittestHasEraseColor());
+ CPPUNIT_ASSERT(skiaAlpha->unittestHasImage());
+ bitmap.Erase(64);
+ BitmapScopedReadAccess(bitmap)->GetColor(0, 0); // Reading a pixel will create pixel data.
+ skiaBitmap = dynamic_cast<SkiaSalBitmap*>(bitmap.GetBitmap().ImplGetSalBitmap().get());
+ skiaBitmap->GetSkImage();
+ CPPUNIT_ASSERT(!skiaBitmap->unittestHasEraseColor());
+ CPPUNIT_ASSERT(skiaBitmap->unittestHasImage());
+ alpha.BlendWith(bitmap);
+ skiaAlpha = dynamic_cast<SkiaSalBitmap*>(alpha.GetBitmap().ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaAlpha->unittestHasImage());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, alpha.getPixelFormat());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(207),
+ BitmapScopedReadAccess(alpha)->GetPixelIndex(0, 0));
+
+ // Test with erase color for alpha and image for other bitmap.
+ alpha.Erase(64);
+ skiaAlpha = dynamic_cast<SkiaSalBitmap*>(alpha.GetBitmap().ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaAlpha->unittestHasEraseColor());
+ bitmap.Erase(64);
+ BitmapScopedReadAccess(bitmap)->GetColor(0, 0); // Reading a pixel will create pixel data.
+ skiaBitmap = dynamic_cast<SkiaSalBitmap*>(bitmap.GetBitmap().ImplGetSalBitmap().get());
+ skiaBitmap->GetSkImage();
+ CPPUNIT_ASSERT(!skiaBitmap->unittestHasEraseColor());
+ CPPUNIT_ASSERT(skiaBitmap->unittestHasImage());
+ alpha.BlendWith(bitmap);
+ skiaAlpha = dynamic_cast<SkiaSalBitmap*>(alpha.GetBitmap().ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaAlpha->unittestHasImage());
+ CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, alpha.getPixelFormat());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(207),
+ BitmapScopedReadAccess(alpha)->GetPixelIndex(0, 0));
+}
+
+void SkiaTest::testBitmapCopyOnWrite()
+{
+ if (!SkiaHelper::isVCLSkiaEnabled())
+ return;
+ SkiaSalBitmap bitmap;
+ CPPUNIT_ASSERT(bitmap.Create(Size(10, 10), vcl::PixelFormat::N24_BPP, BitmapPalette()));
+ bitmap.GetSkImage();
+ bitmap.GetAlphaSkImage();
+ CPPUNIT_ASSERT(bitmap.unittestHasBuffer());
+ CPPUNIT_ASSERT(bitmap.unittestHasImage());
+ CPPUNIT_ASSERT(bitmap.unittestHasAlphaImage());
+ SkiaSalBitmap bitmap2;
+ CPPUNIT_ASSERT(bitmap2.Create(bitmap));
+ // Data should be shared.
+ CPPUNIT_ASSERT_EQUAL(bitmap.unittestGetBuffer(), bitmap2.unittestGetBuffer());
+ CPPUNIT_ASSERT(bitmap2.unittestHasImage());
+ CPPUNIT_ASSERT(bitmap2.unittestHasAlphaImage());
+ CPPUNIT_ASSERT_EQUAL(bitmap.unittestGetImage(), bitmap2.unittestGetImage());
+ CPPUNIT_ASSERT_EQUAL(bitmap.unittestGetAlphaImage(), bitmap2.unittestGetAlphaImage());
+ // Reading still should keep the data shared.
+ const SkImage* oldImage = bitmap.unittestGetImage();
+ const SkImage* oldAlphaImage = bitmap.unittestGetAlphaImage();
+ BitmapBuffer* buffer = bitmap.AcquireBuffer(BitmapAccessMode::Read);
+ CPPUNIT_ASSERT_EQUAL(bitmap.unittestGetBuffer(), bitmap2.unittestGetBuffer());
+ bitmap.ReleaseBuffer(buffer, BitmapAccessMode::Read);
+ // Images get possibly updated only after releasing the buffer.
+ CPPUNIT_ASSERT_EQUAL(bitmap.unittestGetImage(), bitmap2.unittestGetImage());
+ CPPUNIT_ASSERT_EQUAL(bitmap.unittestGetAlphaImage(), bitmap2.unittestGetAlphaImage());
+ CPPUNIT_ASSERT_EQUAL(bitmap.unittestGetImage(), oldImage);
+ CPPUNIT_ASSERT_EQUAL(bitmap.unittestGetAlphaImage(), oldAlphaImage);
+ // Writing should unshare.
+ buffer = bitmap.AcquireBuffer(BitmapAccessMode::Write);
+ CPPUNIT_ASSERT(bitmap.unittestGetBuffer() != bitmap2.unittestGetBuffer());
+ bitmap.ReleaseBuffer(buffer, BitmapAccessMode::Write);
+ CPPUNIT_ASSERT(bitmap.unittestGetImage() != bitmap2.unittestGetImage());
+ CPPUNIT_ASSERT(bitmap.unittestGetAlphaImage() != bitmap2.unittestGetAlphaImage());
+ CPPUNIT_ASSERT(bitmap.unittestGetImage() != oldImage);
+ CPPUNIT_ASSERT(bitmap.unittestGetAlphaImage() != oldAlphaImage);
+}
+
+void SkiaTest::testMatrixQuality()
+{
+ if (!SkiaHelper::isVCLSkiaEnabled())
+ return;
+ // Not changing the size (but possibly rotated/flipped) does not need high quality transformations.
+ CPPUNIT_ASSERT(!SkiaTests::matrixNeedsHighQuality(SkMatrix()));
+ CPPUNIT_ASSERT(!SkiaTests::matrixNeedsHighQuality(SkMatrix::RotateDeg(90)));
+ CPPUNIT_ASSERT(!SkiaTests::matrixNeedsHighQuality(SkMatrix::RotateDeg(180)));
+ CPPUNIT_ASSERT(!SkiaTests::matrixNeedsHighQuality(SkMatrix::RotateDeg(270)));
+ CPPUNIT_ASSERT(!SkiaTests::matrixNeedsHighQuality(SkMatrix::Scale(1, -1)));
+ CPPUNIT_ASSERT(SkiaTests::matrixNeedsHighQuality(SkMatrix::Scale(0, -1)));
+ CPPUNIT_ASSERT(SkiaTests::matrixNeedsHighQuality(SkMatrix::Scale(2, 1)));
+ CPPUNIT_ASSERT(SkiaTests::matrixNeedsHighQuality(SkMatrix::RotateDeg(89)));
+}
+
+void SkiaTest::testDelayedScale()
+{
+ if (!SkiaHelper::isVCLSkiaEnabled())
+ return;
+ Bitmap bitmap1(Size(10, 10), vcl::PixelFormat::N24_BPP);
+ SkiaSalBitmap* skiaBitmap1 = dynamic_cast<SkiaSalBitmap*>(bitmap1.ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaBitmap1);
+ // Do scaling based on mBuffer.
+ (void)BitmapReadAccess(bitmap1); // allocates mBuffer
+ CPPUNIT_ASSERT(skiaBitmap1->unittestHasBuffer());
+ CPPUNIT_ASSERT(!skiaBitmap1->unittestHasImage());
+ CPPUNIT_ASSERT(bitmap1.Scale(2, 2, BmpScaleFlag::Default));
+ skiaBitmap1 = dynamic_cast<SkiaSalBitmap*>(bitmap1.ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaBitmap1);
+ CPPUNIT_ASSERT(skiaBitmap1->unittestHasBuffer());
+ CPPUNIT_ASSERT(!skiaBitmap1->unittestHasImage());
+ CPPUNIT_ASSERT_EQUAL(Size(20, 20), bitmap1.GetSizePixel());
+ CPPUNIT_ASSERT_EQUAL(Size(20, 20), imageSize(skiaBitmap1->GetSkImage()));
+ BitmapBuffer* buffer1 = skiaBitmap1->AcquireBuffer(BitmapAccessMode::Read);
+ CPPUNIT_ASSERT(buffer1);
+ CPPUNIT_ASSERT_EQUAL(tools::Long(20), buffer1->mnWidth);
+ CPPUNIT_ASSERT_EQUAL(tools::Long(20), buffer1->mnHeight);
+ skiaBitmap1->ReleaseBuffer(buffer1, BitmapAccessMode::Read);
+ // Do scaling based on mImage.
+ SkiaSalBitmap skiaBitmap2(skiaBitmap1->GetSkImage());
+ CPPUNIT_ASSERT(!skiaBitmap2.unittestHasBuffer());
+ CPPUNIT_ASSERT(skiaBitmap2.unittestHasImage());
+ CPPUNIT_ASSERT(skiaBitmap2.Scale(2, 3, BmpScaleFlag::Default));
+ CPPUNIT_ASSERT(!skiaBitmap2.unittestHasBuffer());
+ CPPUNIT_ASSERT(skiaBitmap2.unittestHasImage());
+ CPPUNIT_ASSERT_EQUAL(Size(40, 60), skiaBitmap2.GetSize());
+ CPPUNIT_ASSERT_EQUAL(Size(40, 60), imageSize(skiaBitmap2.GetSkImage()));
+ BitmapBuffer* buffer2 = skiaBitmap2.AcquireBuffer(BitmapAccessMode::Read);
+ CPPUNIT_ASSERT(buffer2);
+ CPPUNIT_ASSERT_EQUAL(tools::Long(40), buffer2->mnWidth);
+ CPPUNIT_ASSERT_EQUAL(tools::Long(60), buffer2->mnHeight);
+ skiaBitmap2.ReleaseBuffer(buffer2, BitmapAccessMode::Read);
+}
+
+void SkiaTest::testDelayedScaleAlphaImage()
+{
+ if (!SkiaHelper::isVCLSkiaEnabled())
+ return;
+ auto bitmapTmp = std::make_unique<SkiaSalBitmap>();
+ CPPUNIT_ASSERT(bitmapTmp->Create(Size(10, 10), vcl::PixelFormat::N24_BPP, BitmapPalette()));
+ bitmapTmp->Erase(COL_RED);
+ // Create a bitmap that has only an image, not a pixel buffer.
+ SkiaSalBitmap bitmap(bitmapTmp->GetSkImage());
+ bitmapTmp.release();
+ CPPUNIT_ASSERT(!bitmap.unittestHasBuffer());
+ CPPUNIT_ASSERT(bitmap.unittestHasImage());
+ CPPUNIT_ASSERT(!bitmap.unittestHasAlphaImage());
+ // Set up pending scale.
+ CPPUNIT_ASSERT(bitmap.Scale(2.0, 2.0, BmpScaleFlag::Fast));
+ CPPUNIT_ASSERT(bitmap.unittestHasPendingScale());
+ CPPUNIT_ASSERT(bitmap.InterpretAs8Bit());
+ // Ask for SkImage and make sure it's scaled up.
+ sk_sp<SkImage> image = bitmap.GetSkImage();
+ CPPUNIT_ASSERT_EQUAL(20, image->width());
+ // Ask again, this time it should be cached.
+ sk_sp<SkImage> image2 = bitmap.GetSkImage();
+ CPPUNIT_ASSERT_EQUAL(image.get(), image2.get());
+ // Add another scale.
+ CPPUNIT_ASSERT(bitmap.Scale(3.0, 3.0, BmpScaleFlag::Fast));
+ // Ask for alpha SkImage and make sure it's scaled up.
+ sk_sp<SkImage> alphaImage = bitmap.GetAlphaSkImage();
+ CPPUNIT_ASSERT_EQUAL(60, alphaImage->width());
+ // Ask again, this time it should be cached.
+ sk_sp<SkImage> alphaImage2 = bitmap.GetAlphaSkImage();
+ CPPUNIT_ASSERT_EQUAL(alphaImage.get(), alphaImage2.get());
+ // Ask again for non-alpha image, it should be scaled again.
+ sk_sp<SkImage> image3 = bitmap.GetSkImage();
+ CPPUNIT_ASSERT_EQUAL(60, image3->width());
+ CPPUNIT_ASSERT(image3.get() != image2.get());
+ CPPUNIT_ASSERT(image3.get() != image.get());
+ // Create pixel buffer from the image (it should convert from alpha image because the bitmap is 8bpp
+ // and the alpha image size matches).
+ SkiaSalBitmap bitmapCopy;
+ bitmapCopy.Create(bitmap);
+ CPPUNIT_ASSERT(!bitmap.unittestHasBuffer());
+ BitmapBuffer* buffer1 = bitmap.AcquireBuffer(BitmapAccessMode::Read);
+ CPPUNIT_ASSERT(bitmap.unittestHasBuffer());
+ bitmap.ReleaseBuffer(buffer1, BitmapAccessMode::Read);
+ CPPUNIT_ASSERT_EQUAL(Size(60, 60), bitmap.GetSize());
+ // Scale the copy before the buffer was created (this time it should convert from non-alpha image
+ // because of the different size).
+ CPPUNIT_ASSERT(!bitmapCopy.unittestHasBuffer());
+ CPPUNIT_ASSERT(bitmapCopy.Scale(4.0, 4.0, BmpScaleFlag::Fast));
+ BitmapBuffer* buffer2 = bitmapCopy.AcquireBuffer(BitmapAccessMode::Read);
+ CPPUNIT_ASSERT(bitmapCopy.unittestHasBuffer());
+ bitmapCopy.ReleaseBuffer(buffer2, BitmapAccessMode::Read);
+ CPPUNIT_ASSERT_EQUAL(Size(240, 240), bitmapCopy.GetSize());
+}
+
+void SkiaTest::testDrawDelayedScaleImage()
+{
+ if (!SkiaHelper::isVCLSkiaEnabled())
+ return;
+ if (SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster)
+ return; // This test tests caching that's done only in raster mode.
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(10, 10));
+ device->SetBackground(Wallpaper(COL_WHITE));
+ device->Erase();
+ Bitmap bitmap(Size(10, 10), vcl::PixelFormat::N24_BPP);
+ bitmap.Erase(COL_RED);
+ // Set a pixel to create pixel data.
+ BitmapWriteAccess(bitmap).SetPixel(0, 0, COL_BLUE);
+ SkiaSalBitmap* skiaBitmap1 = dynamic_cast<SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get());
+ // Force creating of image.
+ sk_sp<SkImage> image1 = skiaBitmap1->GetSkImage();
+ CPPUNIT_ASSERT(skiaBitmap1->unittestHasImage());
+ CPPUNIT_ASSERT(bitmap.Scale(Size(5, 5)));
+ // Make sure delayed scaling has not changed the image.
+ SkiaSalBitmap* skiaBitmap2 = dynamic_cast<SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaBitmap2->unittestHasImage());
+ sk_sp<SkImage> image2 = skiaBitmap2->GetSkImage(SkiaHelper::DirectImage::Yes);
+ CPPUNIT_ASSERT_EQUAL(image1, image2);
+ CPPUNIT_ASSERT_EQUAL(Size(5, 5), bitmap.GetSizePixel());
+ CPPUNIT_ASSERT_EQUAL(Size(10, 10), SkiaHelper::imageSize(image2));
+ // Draw the bitmap scaled to size 10x10 and check that the 10x10 image was used (and kept),
+ // even though technically the bitmap is 5x5.
+ device->DrawBitmap(Point(0, 0), Size(10, 10), bitmap);
+ SkiaSalBitmap* skiaBitmap3 = dynamic_cast<SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get());
+ CPPUNIT_ASSERT(skiaBitmap3->unittestHasImage());
+ sk_sp<SkImage> image3 = skiaBitmap3->GetSkImage(SkiaHelper::DirectImage::Yes);
+ CPPUNIT_ASSERT_EQUAL(image1, image3);
+ CPPUNIT_ASSERT_EQUAL(Size(5, 5), bitmap.GetSizePixel());
+ CPPUNIT_ASSERT_EQUAL(Size(10, 10), SkiaHelper::imageSize(image3));
+}
+
+void SkiaTest::testChecksum()
+{
+ if (!SkiaHelper::isVCLSkiaEnabled())
+ return;
+ Bitmap bitmap(Size(10, 10), vcl::PixelFormat::N24_BPP);
+ bitmap.Erase(COL_RED);
+ BitmapChecksum checksum1 = bitmap.GetChecksum();
+ // Set a pixel to create pixel data, that should change checksum.
+ BitmapWriteAccess(bitmap).SetPixel(0, 0, COL_BLUE);
+ BitmapChecksum checksum2 = bitmap.GetChecksum();
+ CPPUNIT_ASSERT(checksum2 != checksum1);
+ SkiaSalBitmap* skiaBitmap1 = dynamic_cast<SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get());
+ // Creating an image should not change the checksum.
+ sk_sp<SkImage> image1 = skiaBitmap1->GetSkImage();
+ BitmapChecksum checksum3 = bitmap.GetChecksum();
+ CPPUNIT_ASSERT_EQUAL(checksum2, checksum3);
+ // Delayed scaling should change checksum even if the scaling has not taken place.
+ bitmap.Scale(Size(20, 20));
+ BitmapChecksum checksum4 = bitmap.GetChecksum();
+ CPPUNIT_ASSERT(checksum4 != checksum3);
+ // Setting back to the original red content should have the original checksum.
+ // (This also makes sure this next step is not affected by the delayed scaling
+ // above possibly taking place now.)
+ bitmap = Bitmap(Size(10, 10), vcl::PixelFormat::N24_BPP);
+ bitmap.Erase(COL_RED);
+ BitmapChecksum checksum5 = bitmap.GetChecksum();
+ CPPUNIT_ASSERT_EQUAL(checksum1, checksum5);
+ // The optimized changing of images to greyscale should change the checksum.
+ SkiaSalBitmap* skiaBitmap2 = dynamic_cast<SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get());
+ skiaBitmap2->unittestResetToImage();
+ BitmapChecksum checksum6 = skiaBitmap2->GetChecksum();
+ CPPUNIT_ASSERT_EQUAL(checksum5, checksum6);
+ CPPUNIT_ASSERT(skiaBitmap2->ConvertToGreyscale());
+ BitmapChecksum checksum7 = skiaBitmap2->GetChecksum();
+ CPPUNIT_ASSERT(checksum7 != checksum6);
+}
+
+void SkiaTest::testTdf137329()
+{
+ if (!SkiaHelper::isVCLSkiaEnabled())
+ return;
+ // Draw a filled polygon in the entire device, with AA enabled.
+ // All pixels in the device should be black, even those at edges (i.e. not affected by AA).
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(10, 10));
+ device->SetBackground(Wallpaper(COL_WHITE));
+ device->SetAntialiasing(AntialiasingFlags::Enable);
+ device->Erase();
+ device->SetLineColor();
+ device->SetFillColor(COL_BLACK);
+ device->DrawPolyPolygon(
+ basegfx::B2DPolyPolygon(basegfx::B2DPolygon{ { 0, 0 }, { 10, 0 }, { 10, 10 }, { 0, 10 } }));
+ // savePNG("/tmp/tdf137329.png", device);
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, device->GetPixel(Point(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, device->GetPixel(Point(9, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, device->GetPixel(Point(9, 9)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, device->GetPixel(Point(0, 9)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, device->GetPixel(Point(4, 4)));
+}
+
+void SkiaTest::testTdf140848()
+{
+ if (!SkiaHelper::isVCLSkiaEnabled())
+ return;
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(1300, 400));
+ device->SetBackground(Wallpaper(COL_BLACK));
+ device->SetAntialiasing(AntialiasingFlags::Enable);
+ device->Erase();
+ device->SetLineColor();
+ device->SetFillColor(COL_WHITE);
+ basegfx::B2DPolygon p1 = { { 952.73121259842514519, 102.4599685039370911 },
+ { 952.73121259842514519, 66.55445669291347599 },
+ { 1239.9753070866140661, 66.554456692913390725 },
+ { 1239.9753070866140661, 138.36548031496062094 },
+ { 952.73121259842514519, 138.36548031496070621 } };
+ basegfx::B2DPolygon p2 = { { 1168.1642834645670064, 210.17650393700790801 },
+ { 1168.1642834645670064, 66.554456692913404936 },
+ { 1239.9753070866140661, 66.554456692913390725 },
+ { 1239.9753070866142934, 353.79855118110236845 },
+ { 1168.1642834645670064, 353.79855118110236845 } };
+ device->DrawPolyPolygon(basegfx::B2DPolyPolygon(p1));
+ device->DrawPolyPolygon(basegfx::B2DPolyPolygon(p2));
+ //savePNG("/tmp/tdf140848.png", device);
+ // Rounding errors caused the overlapping part not to be drawn.
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(1200, 100)));
+}
+
+void SkiaTest::testTdf132367()
+{
+ if (!SkiaHelper::isVCLSkiaEnabled())
+ return;
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(2, 2));
+ device->SetBackground(Wallpaper(COL_BLACK));
+ device->Erase();
+ device->DrawPixel(Point(1, 1), COL_WHITE);
+ // This will make the bitmap store data in SkImage.
+ Bitmap bitmap = device->GetBitmap(Point(0, 0), Size(2, 2));
+ // Scaling will only set up delayed scaling of the SkImage.
+ bitmap.Scale(Size(4, 4), BmpScaleFlag::NearestNeighbor);
+ // Now it will need to be converted to pixel buffer, check it's converted properly
+ // from the SkImage.
+ BitmapReadAccess access(bitmap);
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), access.Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(4), access.Height());
+ CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_WHITE), access.GetColor(3, 3));
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(SkiaTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/svm/data/arc.svm b/vcl/qa/cppunit/svm/data/arc.svm
new file mode 100644
index 0000000000..5d443a704d
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/arc.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/bitmapexs.svm b/vcl/qa/cppunit/svm/data/bitmapexs.svm
new file mode 100644
index 0000000000..f6860bdff9
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/bitmapexs.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/bitmaps.svm b/vcl/qa/cppunit/svm/data/bitmaps.svm
new file mode 100644
index 0000000000..bf42f1bae8
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/bitmaps.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/chord.svm b/vcl/qa/cppunit/svm/data/chord.svm
new file mode 100644
index 0000000000..7c7136eed0
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/chord.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/clipregion.svm b/vcl/qa/cppunit/svm/data/clipregion.svm
new file mode 100644
index 0000000000..bf8170222c
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/clipregion.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/comment.svm b/vcl/qa/cppunit/svm/data/comment.svm
new file mode 100644
index 0000000000..0e2a0f2295
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/comment.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/ellipse.svm b/vcl/qa/cppunit/svm/data/ellipse.svm
new file mode 100644
index 0000000000..369e4292c8
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/ellipse.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/eps.svm b/vcl/qa/cppunit/svm/data/eps.svm
new file mode 100644
index 0000000000..33b923a14a
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/eps.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/fillcolor.svm b/vcl/qa/cppunit/svm/data/fillcolor.svm
new file mode 100644
index 0000000000..8e1a826c4c
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/fillcolor.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/floattransparent.svm b/vcl/qa/cppunit/svm/data/floattransparent.svm
new file mode 100644
index 0000000000..802fa70782
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/floattransparent.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/font.svm b/vcl/qa/cppunit/svm/data/font.svm
new file mode 100644
index 0000000000..301e9d3c18
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/font.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/gradient.svm b/vcl/qa/cppunit/svm/data/gradient.svm
new file mode 100644
index 0000000000..68811e0654
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/gradient.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/gradientex.svm b/vcl/qa/cppunit/svm/data/gradientex.svm
new file mode 100644
index 0000000000..6a83d078cd
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/gradientex.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/hatch.svm b/vcl/qa/cppunit/svm/data/hatch.svm
new file mode 100644
index 0000000000..4e6bdf638b
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/hatch.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/intersectrectclipregion.svm b/vcl/qa/cppunit/svm/data/intersectrectclipregion.svm
new file mode 100644
index 0000000000..23179cbe8a
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/intersectrectclipregion.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/intersectregionclipregion.svm b/vcl/qa/cppunit/svm/data/intersectregionclipregion.svm
new file mode 100644
index 0000000000..6f85718632
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/intersectregionclipregion.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/layoutmode.svm b/vcl/qa/cppunit/svm/data/layoutmode.svm
new file mode 100644
index 0000000000..d12f09d663
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/layoutmode.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/line.svm b/vcl/qa/cppunit/svm/data/line.svm
new file mode 100644
index 0000000000..67441ec920
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/line.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/linecolor.svm b/vcl/qa/cppunit/svm/data/linecolor.svm
new file mode 100644
index 0000000000..2cba7f734c
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/linecolor.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/mapmode.svm b/vcl/qa/cppunit/svm/data/mapmode.svm
new file mode 100644
index 0000000000..4f74a336e6
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/mapmode.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/masks.svm b/vcl/qa/cppunit/svm/data/masks.svm
new file mode 100644
index 0000000000..b9e777930a
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/masks.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/moveclipregion.svm b/vcl/qa/cppunit/svm/data/moveclipregion.svm
new file mode 100644
index 0000000000..6b53403741
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/moveclipregion.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/overlinecolor.svm b/vcl/qa/cppunit/svm/data/overlinecolor.svm
new file mode 100644
index 0000000000..10cd773fb0
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/overlinecolor.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/pie.svm b/vcl/qa/cppunit/svm/data/pie.svm
new file mode 100644
index 0000000000..1eaf674476
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/pie.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/pixel.svm b/vcl/qa/cppunit/svm/data/pixel.svm
new file mode 100644
index 0000000000..dc15ab8f03
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/pixel.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/point.svm b/vcl/qa/cppunit/svm/data/point.svm
new file mode 100644
index 0000000000..5b5c3731bb
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/point.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/polygon.svm b/vcl/qa/cppunit/svm/data/polygon.svm
new file mode 100644
index 0000000000..162ccfd9cd
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/polygon.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/polyline.svm b/vcl/qa/cppunit/svm/data/polyline.svm
new file mode 100644
index 0000000000..9139da3712
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/polyline.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/polypolygon.svm b/vcl/qa/cppunit/svm/data/polypolygon.svm
new file mode 100644
index 0000000000..ea01bd14a9
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/polypolygon.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/pushpop.svm b/vcl/qa/cppunit/svm/data/pushpop.svm
new file mode 100644
index 0000000000..d5ab6e80a9
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/pushpop.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/rasterop.svm b/vcl/qa/cppunit/svm/data/rasterop.svm
new file mode 100644
index 0000000000..ac9f796c2b
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/rasterop.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/rect.svm b/vcl/qa/cppunit/svm/data/rect.svm
new file mode 100644
index 0000000000..8c398b5729
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/rect.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/refpoint.svm b/vcl/qa/cppunit/svm/data/refpoint.svm
new file mode 100644
index 0000000000..e6412f9083
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/refpoint.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/roundrect.svm b/vcl/qa/cppunit/svm/data/roundrect.svm
new file mode 100644
index 0000000000..aa0ca3e296
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/roundrect.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/strecthtext.svm b/vcl/qa/cppunit/svm/data/strecthtext.svm
new file mode 100644
index 0000000000..f6dcb5fbad
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/strecthtext.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/text.svm b/vcl/qa/cppunit/svm/data/text.svm
new file mode 100644
index 0000000000..37da7f598e
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/text.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/textalign.svm b/vcl/qa/cppunit/svm/data/textalign.svm
new file mode 100644
index 0000000000..091c49876b
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/textalign.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/textarray.svm b/vcl/qa/cppunit/svm/data/textarray.svm
new file mode 100644
index 0000000000..7863b03cef
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/textarray.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/textcolor.svm b/vcl/qa/cppunit/svm/data/textcolor.svm
new file mode 100644
index 0000000000..f55b3ccd7c
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/textcolor.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/textfillecolor.svm b/vcl/qa/cppunit/svm/data/textfillecolor.svm
new file mode 100644
index 0000000000..52a4f29f33
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/textfillecolor.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/textlanguage.svm b/vcl/qa/cppunit/svm/data/textlanguage.svm
new file mode 100644
index 0000000000..2e930d27e5
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/textlanguage.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/textline.svm b/vcl/qa/cppunit/svm/data/textline.svm
new file mode 100644
index 0000000000..1d85178230
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/textline.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/textlinecolor.svm b/vcl/qa/cppunit/svm/data/textlinecolor.svm
new file mode 100644
index 0000000000..786dfc465b
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/textlinecolor.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/textrectangle.svm b/vcl/qa/cppunit/svm/data/textrectangle.svm
new file mode 100644
index 0000000000..62c424d6ae
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/textrectangle.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/transparent.svm b/vcl/qa/cppunit/svm/data/transparent.svm
new file mode 100644
index 0000000000..40864b90a9
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/transparent.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/data/wallpaper.svm b/vcl/qa/cppunit/svm/data/wallpaper.svm
new file mode 100644
index 0000000000..8fca8c31f7
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/wallpaper.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/svmtest.cxx b/vcl/qa/cppunit/svm/svmtest.cxx
new file mode 100644
index 0000000000..d339e80b2f
--- /dev/null
+++ b/vcl/qa/cppunit/svm/svmtest.cxx
@@ -0,0 +1,2352 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <osl/endian.h>
+#include <test/bootstrapfixture.hxx>
+#include <test/xmltesttools.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/hatch.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/filter/PngImageWriter.hxx>
+#include <tools/fract.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/filter/SvmReader.hxx>
+#include <vcl/filter/SvmWriter.hxx>
+#include <salhelper/simplereferenceobject.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+
+#include <config_features.h>
+#include <config_fonts.h>
+#include <vcl/skia/SkiaHelper.hxx>
+
+using namespace css;
+
+class SvmTest : public test::BootstrapFixture, public XmlTestTools
+{
+ OUString maDataUrl;
+
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(maDataUrl) + sFileName;
+ }
+
+ void checkRendering(ScopedVclPtrInstance<VirtualDevice> const & pVirtualDev, const GDIMetaFile& rMetaFile, const char * where);
+
+ // write GDI Metafile to a file in data directory
+ // only use this for new tests to create the svm file
+ void writeToFile(GDIMetaFile& rMetaFile, std::u16string_view rName);
+
+ GDIMetaFile writeAndReadStream(
+ GDIMetaFile& rMetaFile, std::u16string_view rName = std::u16string_view());
+
+ GDIMetaFile readFile(std::u16string_view sName);
+
+ xmlDocUniquePtr dumpMeta(const GDIMetaFile& rMetaFile);
+
+ void checkVirtualDevice(const xmlDocUniquePtr& pDoc);
+ void checkErase(const xmlDocUniquePtr& pDoc);
+
+ void checkPixel(const GDIMetaFile& rMetaFile);
+ void testPixel();
+
+ void checkPoint(const GDIMetaFile& rMetaFile);
+ void testPoint();
+
+ void checkLine(const GDIMetaFile& rMetaFile);
+ void testLine();
+
+ void checkRect(const GDIMetaFile& rMetaFile);
+ void testRect();
+
+ void checkRoundRect(const GDIMetaFile& rMetaFile);
+ void testRoundRect();
+
+ void checkEllipse(const GDIMetaFile& rMetaFile);
+ void testEllipse();
+
+ void checkArc(const GDIMetaFile& rMetaFile);
+ void testArc();
+
+ void checkPie(const GDIMetaFile& rMetaFile);
+ void testPie();
+
+ void checkChord(const GDIMetaFile& rMetaFile);
+ void testChord();
+
+ void checkPolyLine(const GDIMetaFile& rMetaFile);
+ void testPolyLine();
+
+ void checkPolygon(const GDIMetaFile& rMetaFile);
+ void testPolygon();
+
+ void checkPolyPolygon(const GDIMetaFile& rMetaFile);
+ void testPolyPolygon();
+
+ void checkText(const GDIMetaFile& rMetaFile);
+ void testText();
+
+ void checkTextArray(const GDIMetaFile& rMetaFile);
+ void testTextArray();
+
+ void checkstretchText(const GDIMetaFile& rMetaFile);
+ void teststretchText();
+
+ void checkTextRect(const GDIMetaFile& rMetaFile);
+ void testTextRect();
+
+ void checkTextLine(const GDIMetaFile& rMetaFile);
+ void testTextLine();
+
+ void checkBitmaps(const GDIMetaFile& rMetaFile);
+ void testBitmaps();
+
+ void checkBitmapExs(const GDIMetaFile& rMetaFile, bool bIsSvmFile);
+ void testBitmapExs();
+
+ void checkMasks(const GDIMetaFile& rMetaFile);
+ void testMasks();
+
+ void checkGradient(const GDIMetaFile& rMetaFile);
+ void testGradient();
+
+ void checkGradientEx(const GDIMetaFile& rMetaFile);
+ void testGradientEx();
+
+ void checkHatch(const GDIMetaFile& rMetaFile);
+ void testHatch();
+
+ void checkWallpaper(const GDIMetaFile& rMetaFile);
+ void testWallpaper();
+
+ void checkClipRegion(const GDIMetaFile& rMetaFile);
+ void testClipRegion();
+
+ void checkIntersectRectClipRegion(const GDIMetaFile& rMetaFile);
+ void testIntersectRectClipRegion();
+
+ void checkIntersectRegionClipRegion(const GDIMetaFile& rMetaFile);
+ void testIntersectRegionClipRegion();
+
+ void checkMoveClipRegion(const GDIMetaFile& rMetaFile);
+ void testMoveClipRegion();
+
+ void checkLineColor(const GDIMetaFile& rMetaFile);
+ void testLineColor();
+
+ void checkFillColor(const GDIMetaFile& rMetaFile);
+ void testFillColor();
+
+ void checkTextColor(const GDIMetaFile& rMetaFile);
+ void testTextColor();
+
+ void checkTextFillColor(const GDIMetaFile& rMetaFile);
+ void testTextFillColor();
+
+ void checkTextLineColor(const GDIMetaFile& rMetaFile);
+ void testTextLineColor();
+
+ void checkOverLineColor(const GDIMetaFile& rMetaFile);
+ void testOverLineColor();
+
+ void checkTextAlign(const GDIMetaFile& rMetaFile);
+ void testTextAlign();
+
+ void checkMapMode(const GDIMetaFile& rMetaFile);
+ void testMapMode();
+
+#if HAVE_MORE_FONTS && !defined(_WIN32)
+ void checkFont(const GDIMetaFile& rMetaFile);
+#endif
+ void testFont();
+
+ void checkPushPop(const GDIMetaFile& rMetaFile);
+ void testPushPop();
+
+ void checkRasterOp(const GDIMetaFile& rMetaFile);
+ void testRasterOp();
+
+ void checkTransparent(const GDIMetaFile& rMetaFile);
+ void testTransparent();
+
+ void checkFloatTransparent(const GDIMetaFile& rMetaFile);
+ void testFloatTransparent();
+
+ void checkEPS(const GDIMetaFile& rMetaFile);
+ void testEPS();
+
+ void checkRefPoint(const GDIMetaFile& rMetaFile);
+ void testRefPoint();
+
+ void checkComment(const GDIMetaFile& rMetaFile);
+ void testComment();
+
+ void checkLayoutMode(const GDIMetaFile& rMetaFile);
+ void testLayoutMode();
+
+ void checkTextLanguage(const GDIMetaFile& rMetaFile);
+ void testTextLanguage();
+
+public:
+ SvmTest()
+ : BootstrapFixture(true, false)
+ , maDataUrl("/vcl/qa/cppunit/svm/data/")
+ {}
+
+ CPPUNIT_TEST_SUITE(SvmTest);
+ CPPUNIT_TEST(testPixel);
+ CPPUNIT_TEST(testPoint);
+ CPPUNIT_TEST(testLine);
+ CPPUNIT_TEST(testRect);
+ CPPUNIT_TEST(testRoundRect);
+ CPPUNIT_TEST(testEllipse);
+ CPPUNIT_TEST(testArc);
+ CPPUNIT_TEST(testPie);
+ CPPUNIT_TEST(testChord);
+ CPPUNIT_TEST(testPolyLine);
+ CPPUNIT_TEST(testPolygon);
+ CPPUNIT_TEST(testPolyPolygon);
+ CPPUNIT_TEST(testText);
+ CPPUNIT_TEST(testTextArray);
+ CPPUNIT_TEST(teststretchText);
+ CPPUNIT_TEST(testTextRect);
+ CPPUNIT_TEST(testTextLine);
+ CPPUNIT_TEST(testBitmaps); // BMP, BMPSCALE, BMPSCALEPART
+ CPPUNIT_TEST(testBitmapExs); // BMPEX, BMPEXSCALE, BMPEXSCALEPART
+ CPPUNIT_TEST(testMasks); // MASK, MASKSCALE, MASKSCALEPART
+ CPPUNIT_TEST(testGradient);
+ CPPUNIT_TEST(testGradientEx);
+ CPPUNIT_TEST(testHatch);
+ CPPUNIT_TEST(testWallpaper);
+ CPPUNIT_TEST(testClipRegion);
+ CPPUNIT_TEST(testIntersectRectClipRegion);
+ CPPUNIT_TEST(testIntersectRegionClipRegion);
+ CPPUNIT_TEST(testMoveClipRegion);
+ CPPUNIT_TEST(testLineColor);
+ CPPUNIT_TEST(testFillColor);
+ CPPUNIT_TEST(testTextColor);
+ CPPUNIT_TEST(testTextFillColor);
+ CPPUNIT_TEST(testTextLineColor);
+ CPPUNIT_TEST(testOverLineColor);
+ CPPUNIT_TEST(testTextAlign);
+ CPPUNIT_TEST(testMapMode);
+ CPPUNIT_TEST(testFont);
+ CPPUNIT_TEST(testPushPop);
+ CPPUNIT_TEST(testRasterOp);
+ CPPUNIT_TEST(testTransparent);
+ CPPUNIT_TEST(testFloatTransparent);
+ CPPUNIT_TEST(testEPS);
+ CPPUNIT_TEST(testRefPoint);
+ CPPUNIT_TEST(testComment);
+ CPPUNIT_TEST(testLayoutMode);
+ CPPUNIT_TEST(testTextLanguage);
+
+ CPPUNIT_TEST_SUITE_END();
+};
+
+static void setupBaseVirtualDevice(VirtualDevice& rDevice, GDIMetaFile& rMeta)
+{
+ rDevice.SetConnectMetaFile(&rMeta);
+ Size aVDSize(10, 10);
+ rDevice.SetOutputSizePixel(aVDSize);
+ rDevice.SetBackground(Wallpaper(COL_LIGHTRED));
+ rDevice.Erase();
+}
+
+void SvmTest::checkRendering(ScopedVclPtrInstance<VirtualDevice> const & pVirtualDev, const GDIMetaFile& rMetaFile, const char * where)
+{
+ BitmapEx aSourceBitmapEx = pVirtualDev->GetBitmapEx(Point(), Size(10, 10));
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDevResult;
+ pVirtualDevResult->SetOutputSizePixel(Size(10, 10));
+ const_cast<GDIMetaFile&>(rMetaFile).Play(*pVirtualDevResult);
+ BitmapEx aResultBitmapEx = pVirtualDevResult->GetBitmapEx(Point(), Size(10, 10));
+
+ const bool bWriteCompareBitmap = false;
+
+ if (bWriteCompareBitmap)
+ {
+ utl::TempFileNamed aTempFile;
+ aTempFile.EnableKillingFile();
+
+ {
+ SvFileStream aStream(aTempFile.GetURL() + ".source.png", StreamMode::WRITE | StreamMode::TRUNC);
+ vcl::PngImageWriter aPNGWriter(aStream);
+ aPNGWriter.write(aSourceBitmapEx);
+ }
+ {
+ SvFileStream aStream(aTempFile.GetURL() + ".result.png", StreamMode::WRITE | StreamMode::TRUNC);
+ vcl::PngImageWriter aPNGWriter(aStream);
+ aPNGWriter.write(aResultBitmapEx);
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(where, aSourceBitmapEx.GetChecksum(), aResultBitmapEx.GetChecksum());
+}
+
+static GDIMetaFile readMetafile(const OUString& rUrl)
+{
+ GDIMetaFile aResultMetafile;
+ SvFileStream aFileStream(rUrl, StreamMode::READ);
+ aFileStream.Seek(STREAM_SEEK_TO_BEGIN);
+ SvmReader aReader(aFileStream);
+ aReader.Read(aResultMetafile);
+ return aResultMetafile;
+}
+
+static void writeMetaFile(GDIMetaFile& rInputMetafile, const OUString& rUrl)
+{
+ SvFileStream aFileStream(rUrl, StreamMode::WRITE);
+ aFileStream.Seek(STREAM_SEEK_TO_BEGIN);
+ SvmWriter aWriter(aFileStream);
+ aWriter.Write(rInputMetafile);
+ aFileStream.Close();
+}
+
+void SvmTest::writeToFile(GDIMetaFile& rMetaFile, std::u16string_view rName)
+{
+ if (rName.empty())
+ return;
+ OUString sFilePath = getFullUrl(rName);
+ writeMetaFile(rMetaFile, sFilePath);
+}
+
+GDIMetaFile SvmTest::writeAndReadStream(GDIMetaFile& rMetaFile, std::u16string_view rName)
+{
+ if (!rName.empty())
+ writeToFile(rMetaFile, rName);
+
+ SvMemoryStream aStream;
+ SvmWriter aWriter(aStream);
+ aWriter.Write(rMetaFile);
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ GDIMetaFile aResultMetafile;
+ SvmReader aReader(aStream);
+ aResultMetafile.Clear();
+ aReader.Read(aResultMetafile);
+ return aResultMetafile;
+}
+
+GDIMetaFile SvmTest::readFile(std::u16string_view sName)
+{
+ OUString sFilePath = getFullUrl(sName);
+ return readMetafile(sFilePath);
+}
+
+xmlDocUniquePtr SvmTest::dumpMeta(const GDIMetaFile& rMetaFile)
+{
+ MetafileXmlDump dumper;
+ xmlDocUniquePtr pDoc = dumpAndParse(dumper, rMetaFile);
+ CPPUNIT_ASSERT (pDoc);
+
+ checkVirtualDevice(pDoc);
+ checkErase(pDoc);
+
+ return pDoc;
+}
+
+void SvmTest::checkVirtualDevice(const xmlDocUniquePtr& pDoc)
+{
+ assertXPath(pDoc, "/metafile/linecolor[1]"_ostr, "color"_ostr, "#000000");
+ assertXPath(pDoc, "/metafile/fillcolor[1]"_ostr, "color"_ostr, "#ffffff");
+
+ assertXPathAttrs(pDoc, "/metafile/rect[1]"_ostr, {
+ {"left", "0"}, {"top", "0"},
+ {"right", "9"}, {"bottom", "9"}
+ });
+
+ assertXPath(pDoc, "/metafile/linecolor[2]"_ostr, "color"_ostr, "#000000");
+ assertXPath(pDoc, "/metafile/fillcolor[2]"_ostr, "color"_ostr, "#ffffff");
+}
+
+void SvmTest::checkErase(const xmlDocUniquePtr& pDoc)
+{
+ assertXPath(pDoc, "/metafile/linecolor[3]"_ostr, "color"_ostr, "#000000");
+ assertXPath(pDoc, "/metafile/fillcolor[3]"_ostr, "color"_ostr, "#ff0000");
+
+ assertXPathAttrs(pDoc, "/metafile/rect[2]"_ostr, {
+ {"left", "0"}, {"top", "0"},
+ {"right", "9"}, {"bottom", "9"}
+ });
+}
+
+void SvmTest::checkPixel(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/pixel[1]"_ostr, {
+ {"x", "8"}, {"y", "1"}, {"color", "#008000"},
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/pixel[2]"_ostr, {
+ {"x", "1"}, {"y", "8"}, {"color", "#000080"},
+ });
+}
+
+void SvmTest::testPixel()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->DrawPixel(Point(8, 1), COL_GREEN);
+ pVirtualDev->DrawPixel(Point(1, 8), COL_BLUE);
+
+ checkPixel(writeAndReadStream(aGDIMetaFile));
+ checkPixel(readFile(u"pixel.svm"));
+}
+
+void SvmTest::checkPoint(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/point[1]"_ostr, {
+ {"x", "4"}, {"y", "4"}
+ });
+}
+
+void SvmTest::testPoint()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->DrawPixel(Point(4, 4));
+
+ checkPoint(writeAndReadStream(aGDIMetaFile));
+ checkPoint(readFile(u"point.svm"));
+}
+
+void SvmTest::checkLine(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/line[1]"_ostr, {
+ {"startx", "1"}, {"starty", "1"},
+ {"endx", "8"}, {"endy", "8"},
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/line[1]"_ostr, {
+ {"style", "solid"}, {"width", "0"},
+ {"dashlen", "0"}, {"dashcount", "0"},
+ {"dotlen", "0"}, {"dotcount", "0"},
+ {"distance", "0"},
+ {"join", "round"}, {"cap", "butt"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/line[2]"_ostr, {
+ {"startx", "1"}, {"starty", "8"},
+ {"endx", "8"}, {"endy", "1"},
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/line[2]"_ostr, {
+ {"style", "dash"}, {"width", "7"},
+ {"dashlen", "5"}, {"dashcount", "4"},
+ {"dotlen", "3"}, {"dotcount", "2"},
+ {"distance", "1"},
+ {"join", "miter"}, {"cap", "round"}
+ });
+}
+
+void SvmTest::testLine()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->DrawLine(Point(1, 1), Point(8, 8));
+ LineInfo aLineInfo(LineStyle::Dash, 7);
+ aLineInfo.SetDashLen(5);
+ aLineInfo.SetDashCount(4);
+ aLineInfo.SetDotLen(3);
+ aLineInfo.SetDotCount(2);
+ aLineInfo.SetDistance(1);
+ aLineInfo.SetLineJoin(basegfx::B2DLineJoin::Miter);
+ aLineInfo.SetLineCap(css::drawing::LineCap_ROUND);
+ pVirtualDev->DrawLine(Point(1, 8), Point(8, 1), aLineInfo);
+
+ checkLine(writeAndReadStream(aGDIMetaFile));
+ checkLine(readFile(u"line.svm"));
+}
+
+void SvmTest::checkRect(const GDIMetaFile& rMetaFile)
+{
+
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPath(pDoc, "/metafile/linecolor[5]"_ostr, "color"_ostr, "#123456");
+ assertXPath(pDoc, "/metafile/fillcolor[5]"_ostr, "color"_ostr, "#654321");
+
+ assertXPathAttrs(pDoc, "/metafile/rect[3]"_ostr, {
+ {"left", "1"}, {"top", "2"},
+ {"right", "4"}, {"bottom", "5"},
+ });
+}
+
+void SvmTest::testRect()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetLineColor(Color(0x123456));
+ pVirtualDev->SetFillColor(Color(0x654321));
+
+ pVirtualDev->DrawRect(tools::Rectangle(Point(1, 2), Size(4, 4)));
+
+ checkRect(writeAndReadStream(aGDIMetaFile));
+ checkRect(readFile(u"rect.svm"));
+}
+
+void SvmTest::checkRoundRect(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPath(pDoc, "/metafile/linecolor[5]"_ostr, "color"_ostr, "#123456");
+ assertXPath(pDoc, "/metafile/fillcolor[5]"_ostr, "color"_ostr, "#654321");
+
+ assertXPathAttrs(pDoc, "/metafile/roundrect[1]"_ostr, {
+ {"left", "1"}, {"top", "2"},
+ {"right", "4"}, {"bottom", "5"},
+ {"horizontalround", "1"}, {"verticalround", "2"}
+ });
+}
+
+void SvmTest::testRoundRect()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetLineColor(Color(0x123456));
+ pVirtualDev->SetFillColor(Color(0x654321));
+
+ pVirtualDev->DrawRect(tools::Rectangle(Point(1, 2), Size(4, 4)), 1, 2);
+
+ checkRoundRect(writeAndReadStream(aGDIMetaFile));
+ checkRoundRect(readFile(u"roundrect.svm"));
+}
+
+void SvmTest::checkEllipse(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPath(pDoc, "/metafile/linecolor[5]"_ostr, "color"_ostr, "#123456");
+ assertXPath(pDoc, "/metafile/fillcolor[5]"_ostr, "color"_ostr, "#654321");
+
+ assertXPathAttrs(pDoc, "/metafile/ellipse[1]"_ostr, {
+ {"left", "1"}, {"top", "2"},
+ {"right", "4"}, {"bottom", "5"},
+ });
+}
+
+void SvmTest::testEllipse()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetLineColor(Color(0x123456));
+ pVirtualDev->SetFillColor(Color(0x654321));
+
+ pVirtualDev->DrawEllipse(tools::Rectangle(Point(1, 2), Size(4, 4)));
+
+ checkEllipse(writeAndReadStream(aGDIMetaFile));
+ checkEllipse(readFile(u"ellipse.svm"));
+}
+
+void SvmTest::checkArc(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPath(pDoc, "/metafile/linecolor[5]"_ostr, "color"_ostr, "#123456");
+ assertXPath(pDoc, "/metafile/fillcolor[5]"_ostr, "color"_ostr, "#654321");
+
+ assertXPathAttrs(pDoc, "/metafile/arc[1]"_ostr, {
+ {"left", "1"}, {"top", "2"},
+ {"right", "4"}, {"bottom", "5"},
+
+ {"startx", "10"}, {"starty", "11"},
+ {"endx", "12"}, {"endy", "13"},
+ });
+}
+
+void SvmTest::testArc()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetLineColor(Color(0x123456));
+ pVirtualDev->SetFillColor(Color(0x654321));
+
+ pVirtualDev->DrawArc(tools::Rectangle(Point(1, 2), Size(4, 4)), Point(10, 11), Point(12, 13));
+
+ checkArc(writeAndReadStream(aGDIMetaFile));
+ checkArc(readFile(u"arc.svm"));
+}
+
+void SvmTest::checkPie(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPath(pDoc, "/metafile/linecolor[5]"_ostr, "color"_ostr, "#123456");
+ assertXPath(pDoc, "/metafile/fillcolor[5]"_ostr, "color"_ostr, "#654321");
+
+ assertXPathAttrs(pDoc, "/metafile/pie[1]"_ostr, {
+ {"left", "11"}, {"top", "12"},
+ {"right", "14"}, {"bottom", "15"},
+
+ {"startx", "20"}, {"starty", "21"},
+ {"endx", "22"}, {"endy", "23"},
+ });
+}
+
+void SvmTest::testPie()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetLineColor(Color(0x123456));
+ pVirtualDev->SetFillColor(Color(0x654321));
+
+ pVirtualDev->DrawPie(tools::Rectangle(Point(11, 12), Size(4, 4)), Point(20, 21), Point(22, 23));
+
+ checkPie(writeAndReadStream(aGDIMetaFile));
+ checkPie(readFile(u"pie.svm"));
+}
+
+void SvmTest::checkChord(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPath(pDoc, "/metafile/linecolor[5]"_ostr, "color"_ostr, "#123456");
+ assertXPath(pDoc, "/metafile/fillcolor[5]"_ostr, "color"_ostr, "#654321");
+
+ assertXPathAttrs(pDoc, "/metafile/chord[1]"_ostr, {
+ {"left", "21"}, {"top", "22"},
+ {"right", "24"}, {"bottom", "25"},
+
+ {"startx", "30"}, {"starty", "31"},
+ {"endx", "32"}, {"endy", "33"},
+ });
+}
+
+void SvmTest::testChord()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetLineColor(Color(0x123456));
+ pVirtualDev->SetFillColor(Color(0x654321));
+
+ pVirtualDev->DrawChord(tools::Rectangle(Point(21, 22), Size(4, 4)), Point(30, 31), Point(32, 33));
+
+ checkChord(writeAndReadStream(aGDIMetaFile));
+ checkChord(readFile(u"chord.svm"));
+}
+
+void SvmTest::checkPolyLine(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/polyline[1]"_ostr, {
+ {"style", "solid"}, {"width", "0"},
+ {"dashlen", "0"}, {"dashcount", "0"},
+ {"dotlen", "0"}, {"dotcount", "0"},
+ {"distance", "0"},
+ {"join", "round"}, {"cap", "butt"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/polyline[1]/point[1]"_ostr, {{"x", "1"}, {"y", "8"}});
+ assertXPathAttrs(pDoc, "/metafile/polyline[1]/point[2]"_ostr, {{"x", "2"}, {"y", "7"}});
+ assertXPathAttrs(pDoc, "/metafile/polyline[1]/point[3]"_ostr, {{"x", "3"}, {"y", "6"}});
+
+ assertXPathAttrs(pDoc, "/metafile/polyline[2]"_ostr, {
+ {"style", "dash"}, {"width", "7"},
+ {"dashlen", "5"}, {"dashcount", "4"},
+ {"dotlen", "3"}, {"dotcount", "2"},
+ {"distance", "1"},
+ {"join", "miter"}, {"cap", "round"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/polyline[2]/point[1]"_ostr, {{"x", "8"}, {"y", "1"}, {"flags", "normal"}});
+ assertXPathAttrs(pDoc, "/metafile/polyline[2]/point[2]"_ostr, {{"x", "7"}, {"y", "2"}, {"flags", "control"}});
+ assertXPathAttrs(pDoc, "/metafile/polyline[2]/point[3]"_ostr, {{"x", "6"}, {"y", "3"}, {"flags", "smooth"}});
+ assertXPathAttrs(pDoc, "/metafile/polyline[2]/point[4]"_ostr, {{"x", "5"}, {"y", "4"}, {"flags", "symmetric"}});
+}
+
+void SvmTest::testPolyLine()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ tools::Polygon aPolygon(3);
+ aPolygon.SetPoint(Point(1, 8), 0);
+ aPolygon.SetPoint(Point(2, 7), 1);
+ aPolygon.SetPoint(Point(3, 6), 2);
+
+ pVirtualDev->DrawPolyLine(aPolygon);
+
+ tools::Polygon aPolygonWithControl(4);
+ aPolygonWithControl.SetPoint(Point(8, 1), 0);
+ aPolygonWithControl.SetPoint(Point(7, 2), 1);
+ aPolygonWithControl.SetPoint(Point(6, 3), 2);
+ aPolygonWithControl.SetPoint(Point(5, 4), 3);
+
+ aPolygonWithControl.SetFlags(0, PolyFlags::Normal);
+ aPolygonWithControl.SetFlags(1, PolyFlags::Control);
+ aPolygonWithControl.SetFlags(2, PolyFlags::Smooth);
+ aPolygonWithControl.SetFlags(3, PolyFlags::Symmetric);
+
+ LineInfo aLineInfo(LineStyle::Dash, 7);
+ aLineInfo.SetDashLen(5);
+ aLineInfo.SetDashCount(4);
+ aLineInfo.SetDotLen(3);
+ aLineInfo.SetDotCount(2);
+ aLineInfo.SetDistance(1);
+ aLineInfo.SetLineJoin(basegfx::B2DLineJoin::Miter);
+ aLineInfo.SetLineCap(css::drawing::LineCap_ROUND);
+
+ pVirtualDev->DrawPolyLine(aPolygonWithControl, aLineInfo);
+
+ checkPolyLine(writeAndReadStream(aGDIMetaFile));
+ checkPolyLine(readFile(u"polyline.svm"));
+}
+
+void SvmTest::checkPolygon(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/polygon[1]/point[1]"_ostr, {{"x", "1"}, {"y", "8"}});
+ assertXPathAttrs(pDoc, "/metafile/polygon[1]/point[2]"_ostr, {{"x", "2"}, {"y", "7"}});
+ assertXPathAttrs(pDoc, "/metafile/polygon[1]/point[3]"_ostr, {{"x", "3"}, {"y", "6"}});
+
+ assertXPathAttrs(pDoc, "/metafile/polygon[2]/point[1]"_ostr, {{"x", "8"}, {"y", "1"}, {"flags", "normal"}});
+ assertXPathAttrs(pDoc, "/metafile/polygon[2]/point[2]"_ostr, {{"x", "7"}, {"y", "2"}, {"flags", "control"}});
+ assertXPathAttrs(pDoc, "/metafile/polygon[2]/point[3]"_ostr, {{"x", "6"}, {"y", "3"}, {"flags", "smooth"}});
+ assertXPathAttrs(pDoc, "/metafile/polygon[2]/point[4]"_ostr, {{"x", "5"}, {"y", "4"}, {"flags", "symmetric"}});
+}
+
+void SvmTest::testPolygon()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ tools::Polygon aPolygon(3);
+ aPolygon.SetPoint(Point(1, 8), 0);
+ aPolygon.SetPoint(Point(2, 7), 1);
+ aPolygon.SetPoint(Point(3, 6), 2);
+
+ pVirtualDev->DrawPolygon(aPolygon);
+
+ tools::Polygon aPolygonWithControl(4);
+ aPolygonWithControl.SetPoint(Point(8, 1), 0);
+ aPolygonWithControl.SetPoint(Point(7, 2), 1);
+ aPolygonWithControl.SetPoint(Point(6, 3), 2);
+ aPolygonWithControl.SetPoint(Point(5, 4), 3);
+
+ aPolygonWithControl.SetFlags(0, PolyFlags::Normal);
+ aPolygonWithControl.SetFlags(1, PolyFlags::Control);
+ aPolygonWithControl.SetFlags(2, PolyFlags::Smooth);
+ aPolygonWithControl.SetFlags(3, PolyFlags::Symmetric);
+
+ pVirtualDev->DrawPolygon(aPolygonWithControl);
+
+ checkPolygon(writeAndReadStream(aGDIMetaFile));
+ checkPolygon(readFile(u"polygon.svm"));
+}
+
+void SvmTest::checkPolyPolygon(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/polypolygon[1]/polygon[1]/point[1]"_ostr, {{"x", "1"}, {"y", "8"}});
+ assertXPathAttrs(pDoc, "/metafile/polypolygon[1]/polygon[1]/point[2]"_ostr, {{"x", "2"}, {"y", "7"}});
+ assertXPathAttrs(pDoc, "/metafile/polypolygon[1]/polygon[1]/point[3]"_ostr, {{"x", "3"}, {"y", "6"}});
+
+ assertXPathAttrs(pDoc, "/metafile/polypolygon[1]/polygon[2]/point[1]"_ostr, {{"x", "8"}, {"y", "1"}, {"flags", "normal"}});
+ assertXPathAttrs(pDoc, "/metafile/polypolygon[1]/polygon[2]/point[2]"_ostr, {{"x", "7"}, {"y", "2"}, {"flags", "control"}});
+ assertXPathAttrs(pDoc, "/metafile/polypolygon[1]/polygon[2]/point[3]"_ostr, {{"x", "6"}, {"y", "3"}, {"flags", "smooth"}});
+ assertXPathAttrs(pDoc, "/metafile/polypolygon[1]/polygon[2]/point[4]"_ostr, {{"x", "5"}, {"y", "4"}, {"flags", "symmetric"}});
+}
+
+void SvmTest::testPolyPolygon()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ tools::Polygon aPolygon(3);
+ aPolygon.SetPoint(Point(1, 8), 0);
+ aPolygon.SetPoint(Point(2, 7), 1);
+ aPolygon.SetPoint(Point(3, 6), 2);
+
+ tools::Polygon aPolygonWithControl(4);
+ aPolygonWithControl.SetPoint(Point(8, 1), 0);
+ aPolygonWithControl.SetPoint(Point(7, 2), 1);
+ aPolygonWithControl.SetPoint(Point(6, 3), 2);
+ aPolygonWithControl.SetPoint(Point(5, 4), 3);
+
+ aPolygonWithControl.SetFlags(0, PolyFlags::Normal);
+ aPolygonWithControl.SetFlags(1, PolyFlags::Control);
+ aPolygonWithControl.SetFlags(2, PolyFlags::Smooth);
+ aPolygonWithControl.SetFlags(3, PolyFlags::Symmetric);
+
+ tools::PolyPolygon aPolyPolygon(2);
+ aPolyPolygon.Insert(aPolygon);
+ aPolyPolygon.Insert(aPolygonWithControl);
+
+ pVirtualDev->DrawPolyPolygon(aPolyPolygon);
+
+ checkPolyPolygon(writeAndReadStream(aGDIMetaFile));
+ checkPolyPolygon(readFile(u"polypolygon.svm"));
+}
+
+void SvmTest::checkText(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/text[1]"_ostr, {
+ {"x", "4"}, {"y", "6"}, {"index", "1"}, {"length", "2"},
+ });
+
+ assertXPathContent(pDoc, "/metafile/text[1]/textcontent"_ostr, "xABC");
+}
+
+void SvmTest::testText()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->DrawText(Point(4,6), "xABC", 1, 2);
+
+ checkText(writeAndReadStream(aGDIMetaFile));
+ checkText(readFile(u"text.svm"));
+}
+
+void SvmTest::checkTextArray(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/textarray[1]"_ostr, {
+ {"x", "4"}, {"y", "6"}, {"index", "1"}, {"length", "4"},
+ });
+ assertXPathContent(pDoc, "/metafile/textarray[1]/dxarray"_ostr, "15 20 25 ");
+ assertXPathContent(pDoc, "/metafile/textarray[1]/text"_ostr, "123456");
+}
+
+void SvmTest::testTextArray()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+ sal_Int32 const aDX[] = { 10, 15, 20, 25, 30, 35 };
+ pVirtualDev->DrawTextArray(Point(4,6), "123456", KernArraySpan(aDX), {}, 1, 4);
+
+ checkTextArray(writeAndReadStream(aGDIMetaFile));
+ checkTextArray(readFile(u"textarray.svm"));
+}
+
+void SvmTest::checkstretchText(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/stretchtext[1]"_ostr, {
+ {"x", "4"}, {"y", "6"}, {"index", "1"}, {"length", "4"}, {"width", "10"}
+ });
+
+ assertXPathContent(pDoc, "/metafile/stretchtext[1]/textcontent"_ostr, "123456");
+}
+
+void SvmTest::teststretchText()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+ pVirtualDev->DrawStretchText(Point(4,6), 10, "123456", 1, 4);
+
+ checkstretchText(writeAndReadStream(aGDIMetaFile));
+ checkstretchText(readFile(u"strecthtext.svm"));
+}
+
+void SvmTest::checkTextRect(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/textrect[1]"_ostr, {
+ {"left", "0"}, {"top", "0"}, {"right", "4"}, {"bottom", "4"}
+ });
+ assertXPathContent(pDoc, "/metafile/textrect[1]/textcontent"_ostr, "123456");
+ assertXPathContent(pDoc, "/metafile/textrect[1]/style"_ostr, "Center");
+}
+
+void SvmTest::testTextRect()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+ pVirtualDev->DrawText(tools::Rectangle(Point(0,0), Size(5,5)), "123456", DrawTextFlags::Center);
+
+ checkTextRect(writeAndReadStream(aGDIMetaFile));
+ checkTextRect(readFile(u"textrectangle.svm"));
+}
+
+void SvmTest::checkTextLine(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/textline[1]"_ostr, {
+ {"x", "4"}, {"y", "6"}, {"width", "10"},
+ {"strikeout", "single"}, {"underline", "single"}, {"overline", "single"}
+ });
+}
+
+void SvmTest::testTextLine()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+ pVirtualDev->DrawTextLine(Point(4,6), 10, STRIKEOUT_SINGLE, LINESTYLE_SINGLE, LINESTYLE_SINGLE);
+
+ checkTextLine(writeAndReadStream(aGDIMetaFile));
+ checkTextLine(readFile(u"textline.svm"));
+}
+
+void SvmTest::checkBitmaps(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ if (SkiaHelper::isVCLSkiaEnabled())
+ return; // TODO SKIA using CRCs is broken (the idea of it)
+
+ assertXPathAttrs(pDoc, "/metafile/bmp[1]"_ostr, {{"x", "1"}, {"y", "2"}, {"crc",
+#if defined OSL_BIGENDIAN
+ "5e01ddcc"
+#else
+ "469f0820"
+#endif
+ }});
+ assertXPathAttrs(pDoc, "/metafile/bmpscale[1]"_ostr, {
+ {"x", "1"}, {"y", "2"}, {"width", "3"}, {"height", "4"}, {"crc", "4937e32d"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/bmpscalepart[1]"_ostr, {
+ {"destx", "1"}, {"desty", "2"}, {"destwidth", "3"}, {"destheight", "4"},
+ {"srcx", "2"}, {"srcy", "1"}, {"srcwidth", "4"}, {"srcheight", "3"},
+ {"crc",
+#if defined OSL_BIGENDIAN
+ "b8dee5da"
+#else
+ "3789377b"
+#endif
+ }
+ });
+}
+
+void SvmTest::testBitmaps()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ Bitmap aBitmap1(Size(4,4), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap1);
+ pAccess->Erase(COL_RED);
+ }
+ Bitmap aBitmap2(Size(4,4), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap2);
+ pAccess->Erase(COL_GREEN);
+ }
+ Bitmap aBitmap3(Size(4,4), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap3);
+ pAccess->Erase(COL_BLUE);
+ }
+ pVirtualDev->DrawBitmap(Point(1, 2), aBitmap1);
+ pVirtualDev->DrawBitmap(Point(1, 2), Size(3, 4), aBitmap2);
+ pVirtualDev->DrawBitmap(Point(1, 2), Size(3, 4), Point(2, 1), Size(4, 3), aBitmap3);
+
+ {
+ GDIMetaFile aReloadedGDIMetaFile = writeAndReadStream(aGDIMetaFile);
+ checkBitmaps(aReloadedGDIMetaFile);
+ checkRendering(pVirtualDev, aReloadedGDIMetaFile, SAL_WHERE);
+ }
+ {
+ GDIMetaFile aFileGDIMetaFile = readFile(u"bitmaps.svm");
+ checkBitmaps(aFileGDIMetaFile);
+ checkRendering(pVirtualDev, aFileGDIMetaFile, SAL_WHERE);
+ }
+}
+
+void SvmTest::checkBitmapExs(const GDIMetaFile& rMetaFile, bool bIsSvmFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ if (SkiaHelper::isVCLSkiaEnabled())
+ return; // TODO SKIA using CRCs is broken (the idea of it)
+
+ static const std::vector<OUString> aExpectedCRC
+ {
+#if defined OSL_BIGENDIAN
+ "08feb5d3",
+ "281fc589",
+ "b8dee5da",
+ "4df0e464",
+ "186ff868", // 1-bit
+ "33b4a07c", // 4-bit color bitmap - same as 8-bit color bitmap
+ "33b4a07c",
+ "742c3e35",
+#else
+ "ac936607",
+ "4937e32d",
+ "3789377b",
+ "839e8cce",
+ "236aaf55", // 1-bit
+ "2949ccc7", // 4-bit color bitmap - same as 8-bit color bitmap
+ "2949ccc7",
+ "e5df8aad",
+#endif
+ };
+
+ static const std::array<OUString, 8> aExpectedContentChecksum
+ {
+ "26bdebd04e5b18d685cea04982179e273ee3b659",
+ "f4f52df6ef965a2f0fbccbe6aca35ba3457cf9d5",
+ "7c953a06d34bbd38897f950d595df2880dbb0f75",
+ "ca3e5cdde1c395e1ee76d339a5bf6e46fbac3249",
+ "8a1ebc46f890eb0879464c6e293bffd4ce7fadc0", // 1-bit
+ "23611fc9f484c23e45bbd457730adb8ab5355509", // 4-bit color bitmap - same as 8-bit color bitmap
+ "23611fc9f484c23e45bbd457730adb8ab5355509",
+ "97e499b74104debf12f99a774a2c4edc914d8900",
+ };
+
+ assertXPathAttrs(pDoc, "/metafile/bmpex[1]"_ostr, {
+ {"x", "1"}, {"y", "1"}, {"crc", aExpectedCRC[0]}, {"transparenttype", "bitmap"}, {"contentchecksum", aExpectedContentChecksum[0]}, {"pixelformat", "24BPP"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/bmpexscale[1]"_ostr, {
+ {"x", "5"}, {"y", "0"}, {"width", "2"}, {"height", "3"},
+ {"crc", aExpectedCRC[1]}, {"transparenttype", "bitmap"}, {"contentchecksum", aExpectedContentChecksum[1]}, {"pixelformat", "24BPP"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/bmpexscalepart[1]"_ostr, {
+ {"destx", "7"}, {"desty", "1"}, {"destwidth", "2"}, {"destheight", "2"},
+ {"srcx", "0"}, {"srcy", "0"}, {"srcwidth", "3"}, {"srcheight", "4"},
+ {"crc", aExpectedCRC[2]}, {"transparenttype", "bitmap"}, {"contentchecksum", aExpectedContentChecksum[2]}, {"pixelformat", "24BPP"}
+ });
+
+#ifndef MACOSX
+ assertXPathAttrs(pDoc, "/metafile/bmpex[2]"_ostr, {
+ {"x", "6"}, {"y", "6"}, {"crc", aExpectedCRC[3]}, {"transparenttype", "bitmap"}, {"contentchecksum", aExpectedContentChecksum[3]}
+ });
+ assertXPathAttrs(pDoc, "/metafile/bmpex[3]"_ostr, {
+ {"x", "0"}, {"y", "6"}, {"transparenttype", "bitmap"}, {"contentchecksum", aExpectedContentChecksum[4]}, {"pixelformat", "8BPP"}
+ });
+ if (!bIsSvmFile)
+ {
+ assertXPathAttrs(pDoc, "/metafile/bmpex[3]"_ostr, {
+ {"crc", aExpectedCRC[4]}
+ });
+ }
+ assertXPathAttrs(pDoc, "/metafile/bmpex[4]"_ostr, {
+ {"x", "2"}, {"y", "6"}, {"crc", aExpectedCRC[5]}, {"transparenttype", "bitmap"}, {"contentchecksum", aExpectedContentChecksum[5]}, {"pixelformat", "8BPP"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/bmpex[5]"_ostr, {
+ {"x", "0"}, {"y", "8"}, {"crc", aExpectedCRC[6]}, {"transparenttype", "bitmap"}, {"contentchecksum", aExpectedContentChecksum[6]}, {"pixelformat", "8BPP"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/bmpex[6]"_ostr, {
+ {"x", "2"}, {"y", "8"}, {"crc", aExpectedCRC[7]}, {"transparenttype", "bitmap"}, {"contentchecksum", aExpectedContentChecksum[7]}, {"pixelformat", "8BPP"}
+ });
+#else
+ (void)bIsSvmFile;
+#endif
+}
+
+void SvmTest::testBitmapExs()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ // DrawBitmapEx
+ {
+ Bitmap aBitmap(Size(4,4), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap);
+ pAccess->Erase(COL_YELLOW);
+ }
+
+ pVirtualDev->DrawBitmapEx(Point(1, 1), BitmapEx(aBitmap, COL_WHITE));
+ }
+
+ // DrawBitmapEx - Scale
+ {
+ Bitmap aBitmap(Size(4,4), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap);
+ pAccess->Erase(COL_GREEN);
+ }
+ pVirtualDev->DrawBitmapEx(Point(5, 0), Size(2, 3), BitmapEx(aBitmap, COL_WHITE));
+ }
+
+ // DrawBitmapEx - Scale - Part
+ {
+ Bitmap aBitmap(Size(4,4), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap);
+ pAccess->Erase(COL_BLUE);
+ }
+ pVirtualDev->DrawBitmapEx(Point(7, 1), Size(2, 2), Point(0, 0), Size(3, 4), BitmapEx(aBitmap, COL_WHITE));
+ }
+
+ // DrawBitmapEx - 50% transparent
+ {
+ Bitmap aBitmap(Size(4, 4), vcl::PixelFormat::N24_BPP);
+ AlphaMask aAlpha(Size(4, 4));
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap);
+ pAccess->Erase(COL_MAGENTA);
+
+ BitmapScopedWriteAccess pAlphaAccess(aAlpha);
+ pAlphaAccess->Erase(Color(127, 127, 127));
+ }
+ pVirtualDev->DrawBitmapEx(Point(6, 6), BitmapEx(aBitmap, aAlpha));
+ }
+
+ // DrawBitmapEx - 1-bit
+ {
+ Bitmap aBitmap(Size(2, 2), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap);
+ pAccess->Erase(COL_MAGENTA);
+ }
+ aBitmap.Convert(BmpConversion::N1BitThreshold);
+ pVirtualDev->DrawBitmapEx(Point(0, 6), BitmapEx(aBitmap, COL_WHITE));
+ }
+
+ // DrawBitmapEx - used to be 4-bit
+ {
+ Bitmap aBitmap(Size(2, 2), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap);
+ pAccess->Erase(COL_MAGENTA);
+ }
+ aBitmap.Convert(BmpConversion::N8BitColors);
+ pVirtualDev->DrawBitmapEx(Point(2, 6), BitmapEx(aBitmap, COL_WHITE));
+ }
+
+ // DrawBitmapEx - 8-bit Color
+ {
+ Bitmap aBitmap(Size(2, 2), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap);
+ pAccess->Erase(COL_MAGENTA);
+ }
+ aBitmap.Convert(BmpConversion::N8BitColors);
+ pVirtualDev->DrawBitmapEx(Point(0, 8), BitmapEx(aBitmap, COL_WHITE));
+ }
+
+ // DrawBitmapEx - 8-bit Grey
+ {
+ Bitmap aBitmap(Size(2, 2), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap);
+ pAccess->Erase(COL_MAGENTA);
+ }
+ aBitmap.Convert(BmpConversion::N8BitGreys);
+ pVirtualDev->DrawBitmapEx(Point(2, 8), BitmapEx(aBitmap, COL_WHITE));
+ }
+
+ {
+ GDIMetaFile aReloadedGDIMetaFile = writeAndReadStream(aGDIMetaFile);
+ checkBitmapExs(aReloadedGDIMetaFile, /*bIsSvmFile*/false);
+ checkRendering(pVirtualDev, aReloadedGDIMetaFile, SAL_WHERE);
+ }
+ {
+ GDIMetaFile aFileGDIMetaFile = readFile(u"bitmapexs.svm");
+ checkBitmapExs(aFileGDIMetaFile, /*bIsSvmFile*/true);
+ checkRendering(pVirtualDev, aFileGDIMetaFile, SAL_WHERE);
+ }
+}
+
+void SvmTest::checkMasks(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/mask[1]"_ostr, {
+ {"x", "1"}, {"y", "2"},
+ {"color", "#000000"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/maskscale[1]"_ostr, {
+ {"x", "1"}, {"y", "2"}, {"width", "3"}, {"height", "4"},
+ {"color", "#000000"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/maskscalepart[1]"_ostr, {
+ {"destx", "1"}, {"desty", "2"}, {"destwidth", "3"}, {"destheight", "4"},
+ {"srcx", "2"}, {"srcy", "1"}, {"srcwidth", "4"}, {"srcheight", "3"},
+ {"color", "#ff0000"}
+ });
+}
+
+// TODO: Masks are kind-of special - we don't persist the color attribute (it is
+// always #000000) of the meta-action (which is wrong), but rely on alpha to do
+// the right thing.
+void SvmTest::testMasks()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ Bitmap aBitmap1(Size(4,4), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap1);
+ pAccess->Erase(COL_RED);
+ }
+ Bitmap aBitmap2(Size(4,4), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap2);
+ pAccess->Erase(COL_GREEN);
+ }
+ Bitmap aBitmap3(Size(4,4), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap3);
+ pAccess->Erase(COL_BLUE);
+ }
+
+ pVirtualDev->DrawMask(Point(1, 2), aBitmap1, COL_LIGHTRED);
+ pVirtualDev->DrawMask(Point(1, 2), Size(3, 4), aBitmap2, COL_LIGHTRED);
+ pVirtualDev->DrawMask(Point(1, 2), Size(3, 4), Point(2, 1), Size(4, 3), aBitmap3, COL_LIGHTRED, MetaActionType::MASKSCALEPART);
+
+ checkMasks(writeAndReadStream(aGDIMetaFile));
+ checkMasks(readFile(u"masks.svm"));
+}
+
+void SvmTest::checkGradient(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/gradient[1]"_ostr, {
+ {"style", "Linear"},
+ {"startcolor", "#ffffff"},
+ {"endcolor", "#000000"},
+ {"angle", "0"},
+ {"border", "0"},
+ {"offsetx", "50"},
+ {"offsety", "50"},
+ {"startintensity", "100"},
+ {"endintensity", "100"},
+ {"steps", "0"},
+ });
+ assertXPathAttrs(pDoc, "/metafile/gradient[1]/rectangle"_ostr, {
+ {"left", "1"},
+ {"top", "2"},
+ {"right", "4"},
+ {"bottom", "6"},
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/gradient[2]"_ostr, {
+ {"style", "Radial"},
+ {"startcolor", "#ff0000"},
+ {"endcolor", "#00ff00"},
+ {"angle", "55"},
+ {"border", "10"},
+ {"offsetx", "22"},
+ {"offsety", "24"},
+ {"startintensity", "4"},
+ {"endintensity", "14"},
+ {"steps", "64"},
+ });
+ assertXPathAttrs(pDoc, "/metafile/gradient[2]/rectangle"_ostr, {
+ {"left", "3"},
+ {"top", "4"},
+ {"right", "3"},
+ {"bottom", "5"},
+ });
+}
+
+void SvmTest::testGradient()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ tools::Rectangle aRectangle(Point(1, 2), Size(4,5));
+
+ Gradient aGradient(css::awt::GradientStyle_LINEAR, COL_WHITE, COL_BLACK);
+ pVirtualDev->DrawGradient(aRectangle, aGradient);
+
+ tools::Rectangle aRectangle2(Point(3, 4), Size(1,2));
+
+ Gradient aGradient2;
+ aGradient2.SetStyle(css::awt::GradientStyle_RADIAL);
+ aGradient2.SetStartColor(COL_LIGHTRED);
+ aGradient2.SetEndColor(COL_LIGHTGREEN);
+ aGradient2.SetAngle(Degree10(55));
+ aGradient2.SetBorder(10);
+ aGradient2.SetOfsX(22);
+ aGradient2.SetOfsY(24);
+ aGradient2.SetStartIntensity(4);
+ aGradient2.SetEndIntensity(14);
+ aGradient2.SetSteps(64);
+ pVirtualDev->DrawGradient(aRectangle2, aGradient2);
+
+ checkGradient(writeAndReadStream(aGDIMetaFile));
+ checkGradient(readFile(u"gradient.svm"));
+}
+
+void SvmTest::checkGradientEx(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/gradientex[1]"_ostr, {
+ {"style", "Linear"},
+ {"startcolor", "#ffffff"},
+ {"endcolor", "#000000"},
+ {"angle", "0"},
+ {"border", "0"},
+ {"offsetx", "50"},
+ {"offsety", "50"},
+ {"startintensity", "100"},
+ {"endintensity", "100"},
+ {"steps", "0"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/gradientex[1]/polygon/point[1]"_ostr, {
+ {"x", "1"},
+ {"y", "8"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/gradientex[1]/polygon/point[2]"_ostr, {
+ {"x", "2"},
+ {"y", "7"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/gradientex[1]/polygon/point[3]"_ostr, {
+ {"x", "3"},
+ {"y", "6"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/gradientex[2]"_ostr, {
+ {"style", "Axial"},
+ {"startcolor", "#ff00ff"},
+ {"endcolor", "#008080"},
+ {"angle", "55"},
+ {"border", "10"},
+ {"offsetx", "22"},
+ {"offsety", "24"},
+ {"startintensity", "4"},
+ {"endintensity", "14"},
+ {"steps", "64"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/gradientex[2]/polygon[1]/point[1]"_ostr, {
+ {"x", "1"},
+ {"y", "2"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/gradientex[2]/polygon[1]/point[2]"_ostr, {
+ {"x", "3"},
+ {"y", "4"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/gradientex[2]/polygon[2]/point[1]"_ostr, {
+ {"x", "8"},
+ {"y", "9"}
+ });
+ assertXPathAttrs(pDoc, "/metafile/gradientex[2]/polygon[2]/point[2]"_ostr, {
+ {"x", "6"},
+ {"y", "7"}
+ });
+}
+
+void SvmTest::testGradientEx()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ tools::Polygon aPolygon(3);
+ aPolygon.SetPoint(Point(1, 8), 0);
+ aPolygon.SetPoint(Point(2, 7), 1);
+ aPolygon.SetPoint(Point(3, 6), 2);
+
+ tools::PolyPolygon aPolyPolygon(1);
+ aPolyPolygon.Insert(aPolygon);
+
+ Gradient aGradient(css::awt::GradientStyle_LINEAR, COL_WHITE, COL_BLACK);
+ pVirtualDev->DrawGradient(aPolyPolygon, aGradient);
+
+ tools::Polygon aPolygon2(2);
+ aPolygon2.SetPoint(Point(1, 2), 0);
+ aPolygon2.SetPoint(Point(3, 4), 1);
+
+ tools::Polygon aPolygon3(2);
+ aPolygon3.SetPoint(Point(8, 9), 0);
+ aPolygon3.SetPoint(Point(6, 7), 1);
+
+ tools::PolyPolygon aPolyPolygon2(1);
+ aPolyPolygon2.Insert(aPolygon2);
+ aPolyPolygon2.Insert(aPolygon3);
+
+ Gradient aGradient2;
+ aGradient2.SetStyle(css::awt::GradientStyle_AXIAL);
+ aGradient2.SetStartColor(COL_LIGHTMAGENTA);
+ aGradient2.SetEndColor(COL_CYAN);
+ aGradient2.SetAngle(Degree10(55));
+ aGradient2.SetBorder(10);
+ aGradient2.SetOfsX(22);
+ aGradient2.SetOfsY(24);
+ aGradient2.SetStartIntensity(4);
+ aGradient2.SetEndIntensity(14);
+ aGradient2.SetSteps(64);
+ pVirtualDev->DrawGradient(aPolyPolygon2, aGradient2);
+
+ checkGradientEx(writeAndReadStream(aGDIMetaFile));
+ checkGradientEx(readFile(u"gradientex.svm"));
+}
+
+void SvmTest::checkHatch(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/hatch[1]/polygon/point[1]"_ostr, {
+ {"x", "1"}, {"y", "8"},
+ });
+ assertXPathAttrs(pDoc, "/metafile/hatch[1]/polygon/point[2]"_ostr, {
+ {"x", "2"}, {"y", "7"},
+ });
+ assertXPathAttrs(pDoc, "/metafile/hatch[1]/polygon/point[3]"_ostr, {
+ {"x", "3"}, {"y", "6"},
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/hatch[1]/hatch"_ostr, {
+ {"style", "Single"},
+ {"color", "#ffff00"},
+ {"distance", "15"},
+ {"angle", "900"},
+ });
+}
+
+void SvmTest::testHatch()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ tools::Polygon aPolygon(3);
+ aPolygon.SetPoint(Point(1, 8), 0);
+ aPolygon.SetPoint(Point(2, 7), 1);
+ aPolygon.SetPoint(Point(3, 6), 2);
+
+ tools::PolyPolygon aPolyPolygon(1);
+ aPolyPolygon.Insert(aPolygon);
+
+ Hatch aHatch(HatchStyle::Single, COL_YELLOW, 15, 900_deg10);
+
+ pVirtualDev->DrawHatch(aPolyPolygon, aHatch);
+
+ checkHatch(writeAndReadStream(aGDIMetaFile));
+ checkHatch(readFile(u"hatch.svm"));
+}
+
+void SvmTest::checkWallpaper(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ // Funny enough - we don't serialize the rectangle of the wallpaper so it's always EMPTY
+ assertXPathAttrs(pDoc, "/metafile/wallpaper[1]"_ostr,
+ {
+ {"left", "0"},
+ {"top", "0"},
+ {"right", "empty"},
+ {"bottom", "empty"},
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/wallpaper[1]/wallpaper"_ostr,
+ {
+ {"color", "#00ff00"},
+ {"style", "Tile"},
+ {"fixed", "true"},
+ {"scrollable", "true"},
+ });
+}
+
+void SvmTest::testWallpaper()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ Wallpaper aWallpaper(COL_LIGHTGREEN);
+ pVirtualDev->DrawWallpaper(tools::Rectangle(Point(1, 1), Size(3, 3)), aWallpaper);
+
+ checkWallpaper(writeAndReadStream(aGDIMetaFile));
+ checkWallpaper(readFile(u"wallpaper.svm"));
+}
+
+void SvmTest::checkClipRegion(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/clipregion[1]"_ostr, {
+ {"left", "2"},
+ {"top", "2"},
+ {"right", "5"},
+ {"bottom", "5"},
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/clipregion[2]/polygon[1]/point[1]"_ostr, {
+ {"x", "1"},
+ {"y", "8"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/clipregion[2]/polygon[1]/point[2]"_ostr, {
+ {"x", "2"},
+ {"y", "7"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/clipregion[2]/polygon[1]/point[3]"_ostr, {
+ {"x", "3"},
+ {"y", "6"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/clipregion[3]/polygon[1]/point[1]"_ostr, {
+ {"x", "1"},
+ {"y", "8"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/clipregion[3]/polygon[1]/point[2]"_ostr, {
+ {"x", "2"},
+ {"y", "7"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/clipregion[3]/polygon[1]/point[3]"_ostr, {
+ {"x", "3"},
+ {"y", "6"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/clipregion[3]/polygon[2]/point[1]"_ostr, {
+ {"x", "4"},
+ {"y", "9"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/clipregion[3]/polygon[2]/point[2]"_ostr, {
+ {"x", "5"},
+ {"y", "10"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/clipregion[3]/polygon[2]/point[3]"_ostr, {
+ {"x", "6"},
+ {"y", "11"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/clipregion[4]/polygon[1]/point[1]"_ostr, {
+ {"x", "0"},
+ {"y", "1"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/clipregion[4]/polygon[1]/point[2]"_ostr, {
+ {"x", "2"},
+ {"y", "3"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/clipregion[4]/polygon[1]/point[3]"_ostr, {
+ {"x", "4"},
+ {"y", "4"}
+ });
+}
+
+void SvmTest::testClipRegion()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ vcl::Region aRegion(tools::Rectangle(Point(2, 2), Size(4, 4)));
+
+ pVirtualDev->SetClipRegion(aRegion);
+
+ tools::Polygon aPolygon(3);
+ aPolygon.SetPoint(Point(1, 8), 0);
+ aPolygon.SetPoint(Point(2, 7), 1);
+ aPolygon.SetPoint(Point(3, 6), 2);
+
+ vcl::Region aRegion2(aPolygon);
+ pVirtualDev->SetClipRegion(aRegion2);
+
+ tools::Polygon aPolygon1(3);
+ aPolygon1.SetPoint(Point(4, 9), 0);
+ aPolygon1.SetPoint(Point(5, 10), 1);
+ aPolygon1.SetPoint(Point(6, 11), 2);
+
+ tools::PolyPolygon aPolyPolygon(2);
+ aPolyPolygon.Insert(aPolygon);
+ aPolyPolygon.Insert(aPolygon1);
+
+ vcl::Region aRegion3(aPolyPolygon);
+ pVirtualDev->SetClipRegion(aRegion3);
+
+ basegfx::B2DPolygon aB2DPolygon;
+ aB2DPolygon.append(basegfx::B2DPoint(0.0, 1.1));
+ aB2DPolygon.append(basegfx::B2DPoint(2.2, 3.3));
+ aB2DPolygon.append(basegfx::B2DPoint(3.7, 3.8));
+
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(aB2DPolygon);
+
+ vcl::Region aRegion4(aB2DPolyPolygon);
+ pVirtualDev->SetClipRegion(aRegion4);
+
+ checkClipRegion(writeAndReadStream(aGDIMetaFile));
+ checkClipRegion(readFile(u"clipregion.svm"));
+}
+
+void SvmTest::checkIntersectRectClipRegion(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/sectregionclipregion[1]"_ostr, {
+ {"left", "1"},
+ {"top", "2"},
+ {"right", "4"},
+ {"bottom", "9"}
+ });
+}
+
+void SvmTest::testIntersectRectClipRegion()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ tools::Rectangle aRectangle(Point(1, 2), Size(4, 8));
+
+ vcl::Region aRegion(aRectangle);
+
+ pVirtualDev->IntersectClipRegion(aRegion);
+ checkIntersectRectClipRegion(writeAndReadStream(aGDIMetaFile));
+ checkIntersectRectClipRegion(readFile(u"intersectrectclipregion.svm"));
+}
+
+void SvmTest::checkIntersectRegionClipRegion(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/sectregionclipregion[1]"_ostr, {
+ {"left", "1"},
+ {"top", "2"},
+ {"right", "5"},
+ {"bottom", "6"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/sectregionclipregion[2]"_ostr, {
+ {"left", "1"},
+ {"top", "2"},
+ {"right", "7"},
+ {"bottom", "8"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/sectregionclipregion[3]"_ostr, {
+ {"left", "0"},
+ {"top", "3"},
+ {"right", "2"},
+ {"bottom", "6"}
+ });
+}
+
+void SvmTest::testIntersectRegionClipRegion()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ tools::Polygon aPolygon(3);
+ aPolygon.SetPoint(Point(1, 2), 0);
+ aPolygon.SetPoint(Point(3, 4), 1);
+ aPolygon.SetPoint(Point(5, 6), 2);
+
+ vcl::Region aRegion(aPolygon);
+ pVirtualDev->IntersectClipRegion(aRegion);
+
+ tools::Polygon aPolygon1(2);
+ aPolygon1.SetPoint(Point(5, 6), 0);
+ aPolygon1.SetPoint(Point(7, 8), 1);
+
+ tools::PolyPolygon aPolyPolygon(2);
+ aPolyPolygon.Insert(aPolygon);
+ aPolyPolygon.Insert(aPolygon1);
+
+ vcl::Region aRegion1(aPolyPolygon);
+ pVirtualDev->IntersectClipRegion(aRegion1);
+
+ basegfx::B2DPolygon aB2DPolygon;
+ aB2DPolygon.append(basegfx::B2DPoint(0.0, 3.3));
+ aB2DPolygon.append(basegfx::B2DPoint(1.1, 4.4));
+ aB2DPolygon.append(basegfx::B2DPoint(2.2, 5.5));
+
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(aB2DPolygon);
+
+ vcl::Region aRegion2(aB2DPolyPolygon);
+ pVirtualDev->IntersectClipRegion(aRegion2);
+
+ checkIntersectRegionClipRegion(writeAndReadStream(aGDIMetaFile));
+ checkIntersectRegionClipRegion(readFile(u"intersectregionclipregion.svm"));
+}
+
+void SvmTest::checkMoveClipRegion(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/moveclipregion[1]"_ostr, {
+ {"horzmove", "1"},
+ {"vertmove", "2"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/moveclipregion[2]"_ostr, {
+ {"horzmove", "-3"},
+ {"vertmove", "-4"}
+ });
+}
+
+void SvmTest::testMoveClipRegion()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ tools::Rectangle aRectangle(Point(1, 2), Size(4, 8));
+
+ vcl::Region aRegion(aRectangle);
+ aRegion.Move(2, 2);
+ pVirtualDev->SetClipRegion(aRegion);
+
+ pVirtualDev->MoveClipRegion(1, 2);
+ pVirtualDev->MoveClipRegion(-3, -4);
+
+ checkMoveClipRegion(writeAndReadStream(aGDIMetaFile));
+ checkMoveClipRegion(readFile(u"moveclipregion.svm"));
+}
+
+void SvmTest::checkLineColor(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/push/linecolor[1]"_ostr, {
+ {"color", "#654321"},
+ });
+}
+
+void SvmTest::testLineColor()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->Push();
+ pVirtualDev->SetLineColor(Color(0x654321));
+ pVirtualDev->Pop();
+
+ checkLineColor(writeAndReadStream(aGDIMetaFile));
+ checkLineColor(readFile(u"linecolor.svm"));
+}
+
+void SvmTest::checkFillColor(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/push/fillcolor[1]"_ostr, {
+ {"color", "#456789"},
+ });
+}
+
+void SvmTest::testFillColor()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->Push();
+ pVirtualDev->SetFillColor(Color(0x456789));
+ pVirtualDev->Pop();
+
+ checkFillColor(writeAndReadStream(aGDIMetaFile));
+ checkFillColor(readFile(u"fillcolor.svm"));
+}
+
+void SvmTest::checkTextColor(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/textcolor[1]"_ostr, {
+ {"color", "#123456"},
+ });
+}
+
+void SvmTest::testTextColor()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetTextColor(Color(0x123456));
+
+ checkTextColor(writeAndReadStream(aGDIMetaFile));
+ checkTextColor(readFile(u"textcolor.svm"));
+}
+
+void SvmTest::checkTextFillColor(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/textfillcolor[1]"_ostr, {
+ {"color", "#234567"},
+ });
+}
+
+void SvmTest::testTextFillColor()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetTextFillColor(Color(0x234567));
+
+ checkTextFillColor(writeAndReadStream(aGDIMetaFile));
+ checkTextFillColor(readFile(u"textfillecolor.svm"));
+}
+
+void SvmTest::checkTextLineColor(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/textlinecolor[1]"_ostr, {
+ {"color", "#345678"},
+ });
+}
+
+void SvmTest::testTextLineColor()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetTextLineColor(Color(0x345678));
+
+ checkTextLineColor(writeAndReadStream(aGDIMetaFile));
+ checkTextLineColor(readFile(u"textlinecolor.svm"));
+}
+
+void SvmTest::checkOverLineColor(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/push/overlinecolor[1]"_ostr, {
+ {"color", "#345678"},
+ });
+}
+
+void SvmTest::testOverLineColor()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->Push();
+ pVirtualDev->SetOverlineColor(Color(0x345678));
+ pVirtualDev->Pop();
+
+ checkOverLineColor(writeAndReadStream(aGDIMetaFile));
+ checkOverLineColor(readFile(u"overlinecolor.svm"));
+}
+
+void SvmTest::checkTextAlign(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/textalign[1]"_ostr, {
+ {"align", "bottom"},
+ });
+}
+
+void SvmTest::testTextAlign()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetTextAlign(TextAlign::ALIGN_BOTTOM);
+
+ checkTextAlign(writeAndReadStream(aGDIMetaFile));
+ checkTextAlign(readFile(u"textalign.svm"));
+}
+
+void SvmTest::checkMapMode(const GDIMetaFile& rMetafile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetafile);
+
+ assertXPathAttrs(pDoc, "/metafile/mapmode[1]"_ostr, {
+ {"mapunit", "MapPixel"},
+ {"x", "0"},
+ {"y", "0"},
+ {"scalex", "(1/1)"},
+ {"scaley", "(1/1)"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/mapmode[2]"_ostr, {
+ {"mapunit", "Map100thInch"},
+ {"x", "0"},
+ {"y", "1"},
+ {"scalex", "(1/2)"},
+ {"scaley", "(2/3)"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/mapmode[3]"_ostr, {
+ {"mapunit", "MapRelative"},
+ {"x", "0"},
+ {"y", "-1"},
+ {"scalex", "(25/12)"},
+ {"scaley", "(25/16)"}
+ });
+}
+
+void SvmTest::testMapMode()
+{
+ if (!IsDefaultDPI())
+ return;
+ GDIMetaFile aGDIMetafile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetafile);
+
+ MapMode aMapMode;
+
+ pVirtualDev->SetMapMode(aMapMode);
+
+ MapMode aMapMode1(MapUnit::Map100thInch);
+ aMapMode1.SetOrigin(Point(0, 1));
+ aMapMode1.SetScaleX(Fraction(1, 2));
+ aMapMode1.SetScaleY(Fraction(2, 3));
+
+ pVirtualDev->SetMetafileMapMode(aMapMode1, false);
+
+ MapMode aMapMode2;
+ pVirtualDev->SetMetafileMapMode(aMapMode2, true);
+
+ checkMapMode(writeAndReadStream(aGDIMetafile));
+ checkMapMode(readFile(u"mapmode.svm"));
+}
+
+#if HAVE_MORE_FONTS && !defined(_WIN32)
+void SvmTest::checkFont(const GDIMetaFile& rMetafile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetafile);
+ assertXPathAttrs(pDoc, "/metafile/font[1]"_ostr, {
+ {"color", "#ffffff"},
+ {"fillcolor", "#ffffff"},
+ {"name", "Liberation Sans"},
+ {"stylename", "Regular"},
+ {"width", "12"},
+ {"height", "12"},
+ {"orientation", "50"},
+ {"weight", "thin"},
+ {"vertical", "true"},
+ });
+}
+#endif
+
+void SvmTest::testFont()
+{
+#if HAVE_MORE_FONTS
+// Windows interprets Width differently causing build errors
+#if !defined(_WIN32)
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+ vcl::Font aFont(FontFamily::FAMILY_SCRIPT, Size(15, 15));
+ aFont.SetWeight(FontWeight::WEIGHT_THIN);
+ aFont.SetFamilyName("Liberation Sans");
+ aFont.SetStyleName("Regular");
+ aFont.SetFontHeight(12);
+ aFont.SetAverageFontWidth(12);
+ aFont.SetVertical(true);
+ aFont.SetOrientation(Degree10(50));
+ pVirtualDev->SetFont(aFont);
+ checkFont(writeAndReadStream(aGDIMetaFile));
+ checkFont(readFile(u"font.svm"));
+#endif // _WIN32
+#endif
+}
+
+void SvmTest::checkPushPop(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/push[1]"_ostr, {{"flags", "PushAll"}});
+ assertXPathAttrs(pDoc, "/metafile/push[1]/linecolor[1]"_ostr, {{"color", "#800000"}});
+ assertXPathAttrs(pDoc, "/metafile/push[1]/line[1]"_ostr, {
+ {"startx", "4"}, {"starty", "4"},
+ {"endx", "6"}, {"endy", "6"},
+ });
+ assertXPathAttrs(pDoc, "/metafile/push[1]/push[1]"_ostr, {{"flags", "PushLineColor, PushFillColor"}});
+ assertXPathAttrs(pDoc, "/metafile/push[1]/push[1]/line[1]"_ostr, {
+ {"startx", "5"}, {"starty", "5"},
+ {"endx", "7"}, {"endy", "7"},
+ });
+}
+
+void SvmTest::testPushPop()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetLineColor(COL_YELLOW);
+ pVirtualDev->Push();
+ pVirtualDev->SetLineColor(COL_RED);
+ pVirtualDev->DrawLine(Point(4,4), Point(6,6));
+ pVirtualDev->Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR);
+ pVirtualDev->SetLineColor(COL_LIGHTRED);
+ pVirtualDev->DrawLine(Point(5,5), Point(7,7));
+ pVirtualDev->Pop();
+ pVirtualDev->Pop();
+ pVirtualDev->DrawLine(Point(1,1), Point(8,8));
+
+ checkPushPop(writeAndReadStream(aGDIMetaFile));
+ checkPushPop(readFile(u"pushpop.svm"));
+}
+
+void SvmTest::checkRasterOp(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/rasterop[1]"_ostr, {
+ {"operation", "xor"},
+ });
+}
+
+void SvmTest::testRasterOp()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetRasterOp(RasterOp::Xor);
+
+ checkRasterOp(writeAndReadStream(aGDIMetaFile));
+ checkRasterOp(readFile(u"rasterop.svm"));
+}
+
+void SvmTest::checkTransparent(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/transparent[1]"_ostr, {
+ {"transparence", "50"},
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/transparent[1]/polygon/point[1]"_ostr, {
+ {"x", "1"}, {"y", "8"},
+ });
+ assertXPathAttrs(pDoc, "/metafile/transparent[1]/polygon/point[2]"_ostr, {
+ {"x", "2"}, {"y", "7"},
+ });
+ assertXPathAttrs(pDoc, "/metafile/transparent[1]/polygon/point[3]"_ostr, {
+ {"x", "3"}, {"y", "6"},
+ });
+}
+
+void SvmTest::testTransparent()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ tools::Polygon aPolygon(3);
+ aPolygon.SetPoint(Point(1, 8), 0);
+ aPolygon.SetPoint(Point(2, 7), 1);
+ aPolygon.SetPoint(Point(3, 6), 2);
+
+ tools::PolyPolygon aPolyPolygon(1);
+ aPolyPolygon.Insert(aPolygon);
+
+ pVirtualDev->DrawTransparent(tools::PolyPolygon(aPolygon), 50);
+
+ CPPUNIT_ASSERT(aGDIMetaFile.HasTransparentActions());
+ checkTransparent(writeAndReadStream(aGDIMetaFile));
+ checkTransparent(readFile(u"transparent.svm"));
+}
+
+void SvmTest::checkFloatTransparent(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/floattransparent[1]"_ostr, {
+ {"x", "1"},
+ {"y", "2"},
+ {"width", "3"},
+ {"height", "4"},
+ {"transparent", "true"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/floattransparent[1]/gradient[1]"_ostr, {
+ {"style", "Linear"},
+ {"startcolor", "#ffffff"},
+ {"endcolor", "#000000"},
+ {"angle", "0"},
+ {"border", "0"},
+ {"offsetx", "50"},
+ {"offsety", "50"},
+ {"startintensity", "100"},
+ {"endintensity", "100"},
+ {"steps", "0"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/floattransparent[1]/metafile[1]/point[1]"_ostr, {
+ {"x", "1"},
+ {"y", "8"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/floattransparent[1]/metafile[1]/point[2]"_ostr, {
+ {"x", "2"},
+ {"y", "7"}
+ });
+}
+
+void SvmTest::testFloatTransparent()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ GDIMetaFile aGDIMetaFile1;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev1;
+ setupBaseVirtualDevice(*pVirtualDev1, aGDIMetaFile1);
+
+ pVirtualDev1->DrawPixel(Point(1, 8));
+ pVirtualDev1->DrawPixel(Point(2, 7));
+
+ Gradient aGradient(css::awt::GradientStyle_LINEAR, COL_WHITE, COL_BLACK);
+
+ pVirtualDev->DrawTransparent(aGDIMetaFile1, Point(1, 2), Size(3, 4), aGradient);
+
+ checkFloatTransparent(writeAndReadStream(aGDIMetaFile));
+ checkFloatTransparent(readFile(u"floattransparent.svm"));
+}
+
+void SvmTest::checkEPS(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/eps[1]"_ostr, {
+ {"x", "1"},
+ {"y", "8"},
+ {"width", "2"},
+ {"height", "7"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/eps[1]/gfxlink[1]"_ostr, {
+ {"width", "3"},
+ {"height", "6"},
+ {"type", "EpsBuffer"},
+ {"userid", "12345"},
+ {"datasize", "3"},
+ {"data", "616263"},
+ {"native", "false"},
+ {"emf", "false"},
+ {"validmapmode", "true"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/eps[1]/gfxlink[1]/prefmapmode[1]"_ostr, {
+ {"mapunit", "Map100thInch"},
+ {"x", "0"},
+ {"y", "1"},
+ {"scalex", "(1/2)"},
+ {"scaley", "(2/3)"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/eps[1]/metafile[1]/point[1]"_ostr, {
+ {"x", "1"},
+ {"y", "8"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/eps[1]/metafile[1]/point[2]"_ostr, {
+ {"x", "2"},
+ {"y", "7"}
+ });
+}
+
+void SvmTest::testEPS()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ sal_uInt8 aBuffer[] = { 'a','b','c' };
+ SvMemoryStream stream(aBuffer, std::size(aBuffer), StreamMode::READ);
+ BinaryDataContainer aContainer(stream, std::size(aBuffer));
+
+ MapMode aMapMode1(MapUnit::Map100thInch);
+ aMapMode1.SetOrigin(Point(0, 1));
+ aMapMode1.SetScaleX(Fraction(1, 2));
+ aMapMode1.SetScaleY(Fraction(2, 3));
+
+ GDIMetaFile aGDIMetaFile1;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev1;
+ setupBaseVirtualDevice(*pVirtualDev1, aGDIMetaFile1);
+
+ pVirtualDev1->DrawPixel(Point(1, 8));
+ pVirtualDev1->DrawPixel(Point(2, 7));
+
+ GfxLink aGfxLink(aContainer, GfxLinkType::EpsBuffer);
+ aGfxLink.SetPrefMapMode(aMapMode1);
+ aGfxLink.SetUserId(12345);
+ aGfxLink.SetPrefSize(Size(3, 6));
+ pVirtualDev->DrawEPS(Point(1, 8), Size(2, 7), aGfxLink, &aGDIMetaFile1);
+
+ checkEPS(writeAndReadStream(aGDIMetaFile));
+ checkEPS(readFile(u"eps.svm"));
+}
+
+void SvmTest::checkRefPoint(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/refpoint[1]"_ostr, {
+ {"x", "0"},
+ {"y", "0"},
+ {"set", "false"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/refpoint[2]"_ostr, {
+ {"x", "1"},
+ {"y", "2"},
+ {"set", "true"}
+ });
+}
+
+void SvmTest::testRefPoint()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetRefPoint();
+
+ pVirtualDev->SetRefPoint(Point(1,2));
+
+ checkRefPoint(writeAndReadStream(aGDIMetaFile));
+ checkRefPoint(readFile(u"refpoint.svm"));
+}
+
+void SvmTest::checkComment(const GDIMetaFile& rMetafile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetafile);
+
+ assertXPathAttrs(pDoc, "/metafile/comment[1]"_ostr, {
+ {"value", "0"}
+ });
+
+ assertXPathContent(pDoc, "/metafile/comment[1]/comment[1]"_ostr, "Test comment");
+
+ assertXPathAttrs(pDoc, "/metafile/comment[2]"_ostr, {
+ {"datasize", "48"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/comment[2]"_ostr, {
+ {"data", "540068006500730065002000610072006500200073006f006d0065002000740065007300740020006400610074006100"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/comment[2]"_ostr, {
+ {"value", "4"}
+ });
+
+ assertXPathContent(pDoc, "/metafile/comment[2]/comment[1]"_ostr, "This is a test comment");
+}
+
+void SvmTest::testComment()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ aGDIMetaFile.AddAction(new MetaCommentAction("Test comment"_ostr));
+
+ using namespace std::literals::string_view_literals;
+ static constexpr auto aString
+ = "T\0h\0e\0s\0e\0 \0a\0r\0e\0 \0s\0o\0m\0e\0 \0t\0e\0s\0t\0 \0d\0a\0t\0a\0"sv;
+ aGDIMetaFile.AddAction(new MetaCommentAction("This is a test comment"_ostr, \
+ 4, \
+ reinterpret_cast<const sal_uInt8*>(aString.data()), \
+ aString.length() ));
+
+ checkComment(writeAndReadStream(aGDIMetaFile));
+ checkComment(readFile(u"comment.svm"));
+}
+
+void SvmTest::checkLayoutMode(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/layoutmode[1]"_ostr, {
+ {"textlayout", "TextOriginLeft"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/layoutmode[2]"_ostr, {
+ {"textlayout", "BiDiRtl"}
+ });
+}
+
+void SvmTest::testLayoutMode()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::TextOriginLeft);
+ pVirtualDev->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiRtl);
+
+ checkLayoutMode(writeAndReadStream(aGDIMetaFile));
+ checkLayoutMode(readFile(u"layoutmode.svm"));
+}
+
+void SvmTest::checkTextLanguage(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/textlanguage[1]"_ostr, {
+ {"language", "#0408"}
+ });
+
+ assertXPathAttrs(pDoc, "/metafile/textlanguage[2]"_ostr, {
+ {"language", "#00ff"}
+ });
+}
+
+void SvmTest::testTextLanguage()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+
+ pVirtualDev->SetDigitLanguage(LANGUAGE_GREEK);
+ pVirtualDev->SetDigitLanguage(LANGUAGE_NONE);
+
+ checkTextLanguage(writeAndReadStream(aGDIMetaFile));
+ checkTextLanguage(readFile(u"textlanguage.svm"));
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(SvmTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/test_blocklist_evaluate.xml b/vcl/qa/cppunit/test_blocklist_evaluate.xml
new file mode 100644
index 0000000000..bd9985e327
--- /dev/null
+++ b/vcl/qa/cppunit/test_blocklist_evaluate.xml
@@ -0,0 +1,46 @@
+<?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", "windows", "linux", "osx_10_5", "osx_10_6", "osx_10_7", "osx_10_8", "osx"
+ 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>
+
+ <entry os="all" vendor="amd" compare="less" version="10.20.30.40">
+ <device id="all"/>
+ </entry>
+
+ <entry os="all" vendor="microsoft" compare="equal" version="10.20.30.40">
+ <device id="all"/>
+ </entry>
+
+ <entry os="all" vendor="intel" compare="less" version="10.18.14.4264">
+ <device id="all"/>
+ </entry>
+
+ <entry os="osx" vendor="microsoft" compare="equal" version="10.20.30.50">
+ <device id="all"/>
+ </entry>
+
+ <entry os="linux" vendor="microsoft" compare="equal" version="10.20.30.50">
+ <device id="all"/>
+ </entry>
+
+ </denylist>
+</root>
diff --git a/vcl/qa/cppunit/test_blocklist_parse.xml b/vcl/qa/cppunit/test_blocklist_parse.xml
new file mode 100644
index 0000000000..9a0f6acc57
--- /dev/null
+++ b/vcl/qa/cppunit/test_blocklist_parse.xml
@@ -0,0 +1,63 @@
+<?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/.
+-->
+
+<root>
+ <allowlist>
+ <entry os="all" vendor="all" compare="less" version="10.20.30.40">
+ <device id="all"/>
+ </entry>
+ <entry os="7" vendor="nvidia" compare="equal" version="10.20.30.40">
+ <device id="all"/>
+ </entry>
+ <entry os="8" vendor="microsoft" compare="not_equal" version="10.20.30.40">
+ <device id="all"/>
+ </entry>
+ <entry os="8" vendor="0xcafe" compare="not_equal" version="10.20.30.40">
+ <device id="all"/>
+ </entry>
+ <entry os="8_1" compare="between_exclusive" version="10.20.30.40">
+ <device id="all"/>
+ </entry>
+ <entry os="10" compare="between_inclusive" version="10.20.30.40">
+ <device id="all"/>
+ </entry>
+ <entry os="all" compare="between_inclusive_start" version="10.20.30.40">
+ <device id="all"/>
+ </entry>
+ <entry os="all">
+ <device id="all"/>
+ </entry>
+ </allowlist>
+ <denylist>
+ <entry os="all" vendor="all" compare="less" version="10.20.30.40">
+ <device id="all"/>
+ </entry>
+ <entry os="7" vendor="nvidia" compare="equal" version="10.20.30.40">
+ <device id="all"/>
+ </entry>
+ <entry os="8" vendor="microsoft" compare="not_equal" version="10.20.30.40">
+ <device id="all"/>
+ </entry>
+ <entry os="8" vendor="0xcafe" compare="not_equal" version="10.20.30.40">
+ <device id="all"/>
+ </entry>
+ <entry os="8_1" compare="between_exclusive" version="10.20.30.40">
+ <device id="all"/>
+ </entry>
+ <entry os="10" compare="between_inclusive" version="10.20.30.40">
+ <device id="all"/>
+ </entry>
+ <entry os="all" compare="between_inclusive_start" version="10.20.30.40">
+ <device id="all"/>
+ </entry>
+ <entry os="all">
+ <device id="all"/>
+ </entry>
+ </denylist>
+</root>
diff --git a/vcl/qa/cppunit/test_blocklist_vulkan.xml b/vcl/qa/cppunit/test_blocklist_vulkan.xml
new file mode 100644
index 0000000000..9542ee5f22
--- /dev/null
+++ b/vcl/qa/cppunit/test_blocklist_vulkan.xml
@@ -0,0 +1,30 @@
+<?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>
+
+ <entry os="all" vendor="amd" compare="less" version="1.2.3">
+ <device id="all"/>
+ </entry>
+
+ </denylist>
+</root>
diff --git a/vcl/qa/cppunit/text.cxx b/vcl/qa/cppunit/text.cxx
new file mode 100644
index 0000000000..cabe502db4
--- /dev/null
+++ b/vcl/qa/cppunit/text.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/.
+ */
+
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <test/bootstrapfixture.hxx>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+
+#include <vcl/BitmapReadAccess.hxx>
+#include <comphelper/errcode.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+
+#include <ImplLayoutArgs.hxx>
+#include <TextLayoutCache.hxx>
+#include <salgdi.hxx>
+
+class VclTextTest : public test::BootstrapFixture
+{
+ // if enabled - check the result images with:
+ // "xdg-open ./workdir/CppunitTest/vcl_text_test.test.core/"
+ static constexpr const bool mbExportBitmap = false;
+
+public:
+ void exportDevice(const OUString& filename, const VclPtr<VirtualDevice>& device)
+ {
+ if (mbExportBitmap)
+ {
+ BitmapEx aBitmapEx(device->GetBitmapEx(Point(0, 0), device->GetOutputSizePixel()));
+ OUString cwd;
+ CPPUNIT_ASSERT_EQUAL(osl_Process_E_None, osl_getProcessWorkingDir(&cwd.pData));
+ OUString url;
+ CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None,
+ osl::FileBase::getAbsoluteFileURL(cwd, filename, url));
+ SvFileStream aStream(url, StreamMode::WRITE | StreamMode::TRUNC);
+ CPPUNIT_ASSERT_EQUAL(
+ ERRCODE_NONE, GraphicFilter::GetGraphicFilter().compressAsPNG(aBitmapEx, aStream));
+ }
+ }
+
+ VclTextTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+};
+
+// Avoid issues when colorized antialiasing generates slightly tinted rather than truly black
+// pixels:
+static bool isBlack(Color col)
+{
+ return col.GetRed() < 25 && col.GetGreen() < 25 && col.GetBlue() < 25;
+}
+
+// Return pixel width of the base of the given character located above
+// the starting position.
+// In other words, go up in y direction until a black pixel is found,
+// then return the horizontal width of the area of those pixels.
+// For 'L' this gives the width of the base of the character.
+static tools::Long getCharacterBaseWidth(VirtualDevice* device, const Point& start)
+{
+ Bitmap bitmap = device->GetBitmap(Point(), device->GetOutputSizePixel());
+ BitmapScopedReadAccess access(bitmap);
+ tools::Long y = start.Y();
+ while (y >= 0 && !isBlack(access->GetColor(y, start.X())))
+ --y;
+ if (y < 0)
+ return -1;
+ tools::Long xmin = start.X();
+ while (xmin >= 0 && access->GetColor(y, xmin) != COL_WHITE)
+ --xmin;
+ tools::Long xmax = start.X();
+ while (xmax < bitmap.GetSizePixel().Width() && access->GetColor(y, xmax) != COL_WHITE)
+ ++xmax;
+ return xmax - xmin + 1;
+}
+
+// Similar to above but this time from the top, for U+30E8 (it's straight at the top, not at the bottom).
+static tools::Long getCharacterTopWidth(VirtualDevice* device, const Point& start)
+{
+ Bitmap bitmap = device->GetBitmap(Point(), device->GetOutputSizePixel());
+ BitmapScopedReadAccess access(bitmap);
+ tools::Long y = start.Y();
+ while (y < bitmap.GetSizePixel().Height() && !isBlack(access->GetColor(y, start.X())))
+ ++y;
+ if (y >= bitmap.GetSizePixel().Height())
+ return -1;
+ tools::Long xmin = start.X();
+ while (xmin >= 0 && access->GetColor(y, xmin) != COL_WHITE)
+ --xmin;
+ tools::Long xmax = start.X();
+ while (xmax < bitmap.GetSizePixel().Width() && access->GetColor(y, xmax) != COL_WHITE)
+ ++xmax;
+ return xmax - xmin + 1;
+}
+
+// Similar to above, but this time return the pixel height of the left-most
+// line of the character, going right from the starting point.
+// For 'L' this gives the height of the left line.
+static tools::Long getCharacterLeftSideHeight(VirtualDevice* device, const Point& start)
+{
+ Bitmap bitmap = device->GetBitmap(Point(), device->GetOutputSizePixel());
+ BitmapScopedReadAccess access(bitmap);
+ tools::Long x = start.X();
+ while (x < bitmap.GetSizePixel().Width() && !isBlack(access->GetColor(start.Y(), x)))
+ ++x;
+ if (x >= bitmap.GetSizePixel().Width())
+ return -1;
+ tools::Long ymin = start.Y();
+ while (ymin >= 0 && access->GetColor(ymin, x) != COL_WHITE)
+ --ymin;
+ tools::Long ymax = start.Y();
+ while (ymax < bitmap.GetSizePixel().Width() && access->GetColor(ymax, x) != COL_WHITE)
+ ++ymax;
+ return ymax - ymin + 1;
+}
+
+// Test rendering of the 'L' character (chosen because L is a simple shape).
+// Check things like using a double font size doubling the size of the character, correct rotation, etc.
+// IMPORTANT: If you modify this, check also the VclCjkTextTest::testVerticalText().
+CPPUNIT_TEST_FIXTURE(VclTextTest, testSimpleText)
+{
+ OUString text("L");
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(100, 100));
+ device->SetBackground(Wallpaper(COL_WHITE));
+ // Disable AA, to make all pixels be black or white.
+ device->SetAntialiasing(AntialiasingFlags::DisableText);
+
+ // Bail out on all backends that do not work (or I didn't test). Opt-out rather than opt-in
+ // to make sure new backends fail initially.
+ if (device->GetGraphics()->getRenderBackendName() == "qt5"
+ || device->GetGraphics()->getRenderBackendName() == "qt5svp"
+ || device->GetGraphics()->getRenderBackendName() == "gtk3svp"
+ || device->GetGraphics()->getRenderBackendName() == "aqua"
+ || device->GetGraphics()->getRenderBackendName() == "gen"
+ || device->GetGraphics()->getRenderBackendName() == "genpsp")
+ return;
+
+ // Use Dejavu fonts, they are shipped with LO, so they should be ~always available.
+ // Use Sans variant for simpler glyph shapes (no serifs).
+ vcl::Font font("DejaVu Sans", "Book", Size(0, 36));
+ device->Erase();
+ device->SetFont(font);
+ device->DrawText(Point(10, 10), text);
+ exportDevice("simple-text-36.png", device);
+ // Height of 'L' with font 36 size should be roughly 28 pixels.
+ // Use the 'doubles' variant of the test, since that one allows
+ // a delta, and allow several pixels of delta to account
+ // for different rendering methods and whatnot.
+ tools::Long height36 = getCharacterLeftSideHeight(device, Point(0, 30));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(tools::Long(28), height36, 4);
+ tools::Long width36 = getCharacterBaseWidth(device, Point(20, 99));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(tools::Long(19), width36, 4);
+
+ font.SetOrientation(2700_deg10);
+ device->Erase();
+ device->SetFont(font);
+ device->DrawText(Point(90, 10), text);
+ exportDevice("simple-text-36-270deg.png", device);
+ // Width and height here should be swapped, again allowing for some imprecisions.
+ tools::Long height36Rotated = getCharacterLeftSideHeight(device, Point(0, 20));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(width36, height36Rotated, 2);
+ tools::Long width36Rotated = getCharacterTopWidth(device, Point(70, 0));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(height36, width36Rotated, 2);
+
+ font = vcl::Font("DejaVu Sans", "Book", Size(0, 72));
+ device->Erase();
+ device->SetFont(font);
+ device->DrawText(Point(10, 10), text);
+ exportDevice("simple-text-72.png", device);
+ // Font size is doubled, so pixel sizes should also roughly double.
+ tools::Long height72 = getCharacterLeftSideHeight(device, Point(0, 30));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(height36 * 2, height72, 4);
+ tools::Long width72 = getCharacterBaseWidth(device, Point(20, 99));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(width36 * 2, width72, 5);
+
+ font.SetOrientation(2700_deg10);
+ device->Erase();
+ device->SetFont(font);
+ device->DrawText(Point(90, 10), text);
+ exportDevice("simple-text-72-270deg.png", device);
+ tools::Long height72Rotated = getCharacterLeftSideHeight(device, Point(0, 35));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(width72, height72Rotated, 2);
+ tools::Long width72Rotated = getCharacterTopWidth(device, Point(50, 0));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(height72, width72Rotated, 2);
+
+ // Test width scaled to 200%.
+ font = vcl::Font("DejaVu Sans", "Book", Size(72, 36));
+#ifdef _WIN32
+ // TODO: What is the proper way to draw 200%-wide text? This is needed on Windows
+ // but it breaks Linux.
+ font.SetAverageFontWidth(2 * font.GetOrCalculateAverageFontWidth());
+#endif
+ device->Erase();
+ device->SetFont(font);
+ device->DrawText(Point(10, 10), text);
+ exportDevice("simple-text-36-200pct.png", device);
+ tools::Long height36pct200 = getCharacterLeftSideHeight(device, Point(0, 30));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(height36, height36pct200, 2);
+ tools::Long width36pct200 = getCharacterBaseWidth(device, Point(20, 99));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(width36 * 2, width36pct200, 5);
+
+ // Test width scaled to 50%.
+ font = vcl::Font("DejaVu Sans", "Book", Size(18, 36));
+#ifdef _WIN32
+ font.SetAverageFontWidth(0.5 * font.GetOrCalculateAverageFontWidth());
+#endif
+ device->Erase();
+ device->SetFont(font);
+ device->DrawText(Point(10, 10), text);
+ exportDevice("simple-text-36-50pct.png", device);
+ tools::Long height36pct50 = getCharacterLeftSideHeight(device, Point(0, 40));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(height36, height36pct50, 2);
+ tools::Long width36pct50 = getCharacterBaseWidth(device, Point(15, 99));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(width36 / 2, width36pct50, 2);
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testSimpleTextFontSpecificKerning)
+{
+ OUString aAV(u"AV"_ustr);
+
+ vcl::Font aFont("DejaVu Sans", "Book", Size(0, 2048));
+
+ ScopedVclPtrInstance<VirtualDevice> pOutDev;
+ pOutDev->SetFont(aFont);
+
+ // absolute character widths AKA text array.
+ tools::Long nRefTextWidth = 2671;
+ std::vector<sal_Int32> aRefCharWidths = { 1270, 2671 };
+ KernArray aCharWidths;
+ tools::Long nTextWidth = pOutDev->GetTextArray(aAV, &aCharWidths);
+
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths[0], aCharWidths.get_subunit_array()[0]);
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths[1], aCharWidths.get_subunit_array()[1]);
+ // this sporadically returns 75 or 74 on some of the windows tinderboxes eg. tb73
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, nTextWidth);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back());
+
+ // text advance width and line height
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, pOutDev->GetTextWidth(aAV));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(2384), pOutDev->GetTextHeight());
+
+ // exact bounding rectangle, not essentially the same as text width/height
+ tools::Rectangle aBoundRect;
+ pOutDev->GetTextBoundRect(aBoundRect, aAV);
+ CPPUNIT_ASSERT_EQUAL(tools::Long(16), aBoundRect.Left());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(408), aBoundRect.Top());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(2639), aBoundRect.GetWidth());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(1493), aBoundRect.getOpenHeight());
+
+ // normal orientation
+ tools::Rectangle aInput;
+ tools::Rectangle aRect = pOutDev->GetTextRect(aInput, aAV);
+
+ // now rotate 270 degrees
+ vcl::Font aRotated(aFont);
+ aRotated.SetOrientation(2700_deg10);
+ pOutDev->SetFont(aRotated);
+ tools::Rectangle aRectRot = pOutDev->GetTextRect(aInput, aAV);
+
+ // Check that we did do the rotation...
+ CPPUNIT_ASSERT_EQUAL(aRectRot.GetWidth(), aRect.GetHeight());
+ CPPUNIT_ASSERT_EQUAL(aRectRot.GetHeight(), aRect.GetWidth());
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testSimpleTextNoKerning)
+{
+ OUString aAV(u"AV"_ustr);
+
+ vcl::Font aFont("DejaVu Sans", "Book", Size(0, 2048));
+ aFont.SetKerning(FontKerning::NONE);
+
+ ScopedVclPtrInstance<VirtualDevice> pOutDev;
+ pOutDev->SetFont(aFont);
+
+ // absolute character widths AKA text array.
+ tools::Long nRefTextWidth = 2802;
+ std::vector<sal_Int32> aRefCharWidths = { 1401, 2802 };
+ KernArray aCharWidths;
+ tools::Long nTextWidth = pOutDev->GetTextArray(aAV, &aCharWidths);
+
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths[0], aCharWidths.get_subunit_array()[0]);
+ CPPUNIT_ASSERT_EQUAL(aRefCharWidths[1], aCharWidths.get_subunit_array()[1]);
+ // this sporadically returns 75 or 74 on some of the windows tinderboxes eg. tb73
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, nTextWidth);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back());
+
+ // text advance width and line height
+ CPPUNIT_ASSERT_EQUAL(nRefTextWidth, pOutDev->GetTextWidth(aAV));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(2384), pOutDev->GetTextHeight());
+
+ // exact bounding rectangle, not essentially the same as text width/height
+ tools::Rectangle aBoundRect;
+ pOutDev->GetTextBoundRect(aBoundRect, aAV);
+ CPPUNIT_ASSERT_EQUAL(tools::Long(16), aBoundRect.Left());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(408), aBoundRect.Top());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(2770), aBoundRect.GetWidth());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(1493), aBoundRect.getOpenHeight());
+
+ // normal orientation
+ tools::Rectangle aInput;
+ tools::Rectangle aRect = pOutDev->GetTextRect(aInput, aAV);
+
+ // now rotate 270 degrees
+ vcl::Font aRotated(aFont);
+ aRotated.SetOrientation(2700_deg10);
+ pOutDev->SetFont(aRotated);
+ tools::Rectangle aRectRot = pOutDev->GetTextRect(aInput, aAV);
+
+ // Check that we did do the rotation...
+ CPPUNIT_ASSERT_EQUAL(aRectRot.GetWidth(), aRect.GetHeight());
+ CPPUNIT_ASSERT_EQUAL(aRectRot.GetHeight(), aRect.GetWidth());
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testTextLayoutCache)
+{
+ OUString sTestString = u"The quick brown fox\n jumped over the lazy dogالعاشر"_ustr;
+ vcl::text::TextLayoutCache cache(sTestString.getStr(), sTestString.getLength());
+
+ vcl::text::Run run1 = cache.runs[0];
+ vcl::text::Run run2 = cache.runs[1];
+
+ bool bCorrectRuns = (cache.runs.size() == 2);
+ CPPUNIT_ASSERT_MESSAGE("Wrong number of runs", bCorrectRuns);
+ CPPUNIT_ASSERT_EQUAL(USCRIPT_LATIN, run1.nCode);
+ CPPUNIT_ASSERT_EQUAL(0, run1.nStart);
+ CPPUNIT_ASSERT_EQUAL(45, run1.nEnd);
+ CPPUNIT_ASSERT_EQUAL(USCRIPT_ARABIC, run2.nCode);
+ CPPUNIT_ASSERT_EQUAL(45, run2.nStart);
+ CPPUNIT_ASSERT_EQUAL(51, run2.nEnd);
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testImplLayoutRuns_AddPos)
+{
+ ImplLayoutRuns aRuns;
+ aRuns.AddPos(1, false);
+ aRuns.AddPos(2, false);
+ aRuns.AddPos(3, false);
+ aRuns.AddPos(4, true); // add RTL marker glyph
+ aRuns.AddPos(5, false);
+ aRuns.AddPos(6, true); // add RTL marker glyph
+ aRuns.AddPos(7, false);
+
+ int nCharPos(0);
+ bool bRightToLeftMarker(false);
+
+ CPPUNIT_ASSERT(aRuns.GetNextPos(&nCharPos, &bRightToLeftMarker));
+ CPPUNIT_ASSERT_EQUAL(1, nCharPos);
+ CPPUNIT_ASSERT(!bRightToLeftMarker);
+
+ CPPUNIT_ASSERT(aRuns.GetNextPos(&nCharPos, &bRightToLeftMarker));
+ CPPUNIT_ASSERT_EQUAL(2, nCharPos);
+ CPPUNIT_ASSERT(!bRightToLeftMarker);
+
+ CPPUNIT_ASSERT(aRuns.GetNextPos(&nCharPos, &bRightToLeftMarker));
+ CPPUNIT_ASSERT_EQUAL(3, nCharPos);
+ CPPUNIT_ASSERT(!bRightToLeftMarker);
+
+ CPPUNIT_ASSERT(aRuns.GetNextPos(&nCharPos, &bRightToLeftMarker));
+ CPPUNIT_ASSERT_EQUAL(4, nCharPos);
+ CPPUNIT_ASSERT(bRightToLeftMarker);
+
+ CPPUNIT_ASSERT(aRuns.GetNextPos(&nCharPos, &bRightToLeftMarker));
+ CPPUNIT_ASSERT_EQUAL(5, nCharPos);
+ CPPUNIT_ASSERT(!bRightToLeftMarker);
+
+ CPPUNIT_ASSERT(aRuns.GetNextPos(&nCharPos, &bRightToLeftMarker));
+ CPPUNIT_ASSERT_EQUAL(6, nCharPos);
+ CPPUNIT_ASSERT(bRightToLeftMarker);
+
+ CPPUNIT_ASSERT(aRuns.GetNextPos(&nCharPos, &bRightToLeftMarker));
+ CPPUNIT_ASSERT_EQUAL(7, nCharPos);
+ CPPUNIT_ASSERT(!bRightToLeftMarker);
+
+ // no next position, we are running off the end
+ CPPUNIT_ASSERT(!aRuns.GetNextPos(&nCharPos, &bRightToLeftMarker));
+
+ aRuns.ResetPos();
+
+ int nMinRunPos, nEndRunPos;
+ bool bRightToLeft(false);
+
+ CPPUNIT_ASSERT(aRuns.GetRun(&nMinRunPos, &nEndRunPos, &bRightToLeft));
+ CPPUNIT_ASSERT_EQUAL(1, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(4, nEndRunPos);
+ CPPUNIT_ASSERT(!bRightToLeft);
+
+ aRuns.NextRun();
+ CPPUNIT_ASSERT(aRuns.GetRun(&nMinRunPos, &nEndRunPos, &bRightToLeft));
+ CPPUNIT_ASSERT_EQUAL(4, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(5, nEndRunPos);
+ CPPUNIT_ASSERT(bRightToLeft);
+
+ aRuns.NextRun();
+ CPPUNIT_ASSERT(aRuns.GetRun(&nMinRunPos, &nEndRunPos, &bRightToLeft));
+ CPPUNIT_ASSERT_EQUAL(5, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(6, nEndRunPos);
+ CPPUNIT_ASSERT(!bRightToLeft);
+
+ aRuns.NextRun();
+ CPPUNIT_ASSERT(aRuns.GetRun(&nMinRunPos, &nEndRunPos, &bRightToLeft));
+ CPPUNIT_ASSERT_EQUAL(6, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(7, nEndRunPos);
+ CPPUNIT_ASSERT(bRightToLeft);
+
+ // test clear
+ aRuns.Clear();
+ CPPUNIT_ASSERT(aRuns.IsEmpty());
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testImplLayoutRuns_AddRuns)
+{
+ ImplLayoutRuns aRuns;
+ aRuns.AddRun(1, 4, false);
+ aRuns.AddRun(5, 4, true);
+ aRuns.AddRun(5, 6, false);
+ aRuns.AddRun(6, 7, true);
+
+ int nCharPos(0);
+ bool bRightToLeftMarker(false);
+
+ CPPUNIT_ASSERT(aRuns.GetNextPos(&nCharPos, &bRightToLeftMarker));
+ CPPUNIT_ASSERT_EQUAL(1, nCharPos);
+ CPPUNIT_ASSERT(!bRightToLeftMarker);
+
+ CPPUNIT_ASSERT(aRuns.GetNextPos(&nCharPos, &bRightToLeftMarker));
+ CPPUNIT_ASSERT_EQUAL(2, nCharPos);
+ CPPUNIT_ASSERT(!bRightToLeftMarker);
+
+ CPPUNIT_ASSERT(aRuns.GetNextPos(&nCharPos, &bRightToLeftMarker));
+ CPPUNIT_ASSERT_EQUAL(3, nCharPos);
+ CPPUNIT_ASSERT(!bRightToLeftMarker);
+
+ CPPUNIT_ASSERT(aRuns.GetNextPos(&nCharPos, &bRightToLeftMarker));
+ CPPUNIT_ASSERT_EQUAL(4, nCharPos);
+ CPPUNIT_ASSERT(bRightToLeftMarker);
+
+ CPPUNIT_ASSERT(aRuns.GetNextPos(&nCharPos, &bRightToLeftMarker));
+ CPPUNIT_ASSERT_EQUAL(5, nCharPos);
+ CPPUNIT_ASSERT(!bRightToLeftMarker);
+
+ CPPUNIT_ASSERT(aRuns.GetNextPos(&nCharPos, &bRightToLeftMarker));
+ CPPUNIT_ASSERT_EQUAL(6, nCharPos);
+ CPPUNIT_ASSERT(bRightToLeftMarker);
+
+ // no next position, we are running off the end
+ CPPUNIT_ASSERT(!aRuns.GetNextPos(&nCharPos, &bRightToLeftMarker));
+
+ aRuns.ResetPos();
+
+ int nMinRunPos, nEndRunPos;
+ bool bRightToLeft(false);
+
+ CPPUNIT_ASSERT(aRuns.GetRun(&nMinRunPos, &nEndRunPos, &bRightToLeft));
+ CPPUNIT_ASSERT_EQUAL(1, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(4, nEndRunPos);
+ CPPUNIT_ASSERT(!bRightToLeft);
+
+ aRuns.NextRun();
+ CPPUNIT_ASSERT(aRuns.GetRun(&nMinRunPos, &nEndRunPos, &bRightToLeft));
+ CPPUNIT_ASSERT_EQUAL(4, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(5, nEndRunPos);
+ CPPUNIT_ASSERT(bRightToLeft);
+
+ aRuns.NextRun();
+ CPPUNIT_ASSERT(aRuns.GetRun(&nMinRunPos, &nEndRunPos, &bRightToLeft));
+ CPPUNIT_ASSERT_EQUAL(5, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(6, nEndRunPos);
+ CPPUNIT_ASSERT(!bRightToLeft);
+
+ aRuns.NextRun();
+ CPPUNIT_ASSERT(aRuns.GetRun(&nMinRunPos, &nEndRunPos, &bRightToLeft));
+ CPPUNIT_ASSERT_EQUAL(6, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(7, nEndRunPos);
+ CPPUNIT_ASSERT(bRightToLeft);
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testImplLayoutRuns_PosIsInRun)
+{
+ ImplLayoutRuns aRuns;
+ aRuns.AddRun(1, 4, false);
+ aRuns.AddRun(4, 5, true);
+ aRuns.AddRun(5, 6, false);
+ aRuns.AddRun(6, 7, true);
+
+ CPPUNIT_ASSERT(aRuns.PosIsInRun(1));
+ CPPUNIT_ASSERT(aRuns.PosIsInRun(2));
+ CPPUNIT_ASSERT(aRuns.PosIsInRun(3));
+
+ aRuns.NextRun();
+ CPPUNIT_ASSERT(aRuns.PosIsInRun(4));
+
+ aRuns.NextRun();
+ CPPUNIT_ASSERT(aRuns.PosIsInRun(5));
+
+ aRuns.NextRun();
+ CPPUNIT_ASSERT(aRuns.PosIsInRun(6));
+
+ CPPUNIT_ASSERT(!aRuns.PosIsInRun(7));
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testImplLayoutRuns_PosIsInAnyRun)
+{
+ ImplLayoutRuns aRuns;
+ aRuns.AddRun(1, 4, false);
+ aRuns.AddRun(4, 5, true);
+ aRuns.AddRun(5, 6, false);
+ aRuns.AddRun(6, 7, true);
+
+ CPPUNIT_ASSERT(aRuns.PosIsInAnyRun(1));
+ CPPUNIT_ASSERT(!aRuns.PosIsInAnyRun(7));
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testImplLayoutArgsBiDiStrong)
+{
+ OUString sTestString = u"The quick brown fox\n jumped over the lazy dog"
+ "العاشر"_ustr;
+ vcl::text::ImplLayoutArgs aArgs(sTestString, 0, sTestString.getLength(),
+ SalLayoutFlags::BiDiStrong, LanguageTag(LANGUAGE_NONE),
+ nullptr);
+
+ int nMinRunPos(0);
+ int nEndRunPos(0);
+ bool bRTL(false);
+
+ aArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL);
+ CPPUNIT_ASSERT_EQUAL(0, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(19, nEndRunPos);
+ CPPUNIT_ASSERT(!bRTL);
+
+ aArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL);
+ CPPUNIT_ASSERT_EQUAL(20, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(51, nEndRunPos);
+ CPPUNIT_ASSERT(!bRTL);
+
+ aArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL);
+ CPPUNIT_ASSERT_EQUAL(20, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(51, nEndRunPos);
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testImplLayoutArgsBiDiRtl)
+{
+ OUString sTestString = u"The quick brown fox\n jumped over the lazy dog"
+ "العاشر"_ustr;
+ vcl::text::ImplLayoutArgs aArgs(sTestString, 0, sTestString.getLength(),
+ SalLayoutFlags::BiDiRtl, LanguageTag(LANGUAGE_NONE), nullptr);
+
+ int nMinRunPos(0);
+ int nEndRunPos(0);
+ bool bRTL(false);
+
+ aArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL);
+ CPPUNIT_ASSERT_EQUAL(45, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(51, nEndRunPos);
+ CPPUNIT_ASSERT(&bRTL);
+
+ aArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL);
+ CPPUNIT_ASSERT_EQUAL(21, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(45, nEndRunPos);
+ CPPUNIT_ASSERT(!bRTL);
+
+ aArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL);
+ CPPUNIT_ASSERT_EQUAL(20, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(21, nEndRunPos);
+ CPPUNIT_ASSERT(bRTL);
+
+ aArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL);
+ CPPUNIT_ASSERT_EQUAL(0, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(19, nEndRunPos);
+ CPPUNIT_ASSERT(!bRTL);
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testImplLayoutArgsRightAlign)
+{
+ OUString sTestString = u"The quick brown fox\n jumped over the lazy dog"
+ "العاشر"_ustr;
+ vcl::text::ImplLayoutArgs aArgs(sTestString, 0, sTestString.getLength(),
+ SalLayoutFlags::RightAlign, LanguageTag(LANGUAGE_NONE),
+ nullptr);
+
+ int nMinRunPos(0);
+ int nEndRunPos(0);
+ bool bRTL(false);
+
+ aArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL);
+ CPPUNIT_ASSERT_EQUAL(0, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(19, nEndRunPos);
+ CPPUNIT_ASSERT(!bRTL);
+
+ aArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL);
+ CPPUNIT_ASSERT_EQUAL(20, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(45, nEndRunPos);
+ CPPUNIT_ASSERT(!bRTL);
+
+ aArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL);
+ CPPUNIT_ASSERT_EQUAL(45, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(51, nEndRunPos);
+ CPPUNIT_ASSERT(bRTL);
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testImplLayoutArgs_PrepareFallback_precalculatedglyphs)
+{
+ // this font has no Cyrillic characters and thus needs fallback
+ const vcl::Font aFont("Amiri", Size(0, 36));
+
+ ScopedVclPtrInstance<VirtualDevice> pVirDev;
+ pVirDev->SetFont(aFont);
+
+ static constexpr OStringLiteral sUTF8String(u8"Тхе Ñуицк\n ыумпед овер");
+ const OUString sTestString(OUString::fromUtf8(sUTF8String));
+ std::unique_ptr<SalLayout> pLayout
+ = pVirDev->ImplLayout(sTestString, 0, sTestString.getLength(), Point(0, 0), 0, {}, {},
+ SalLayoutFlags::GlyphItemsOnly);
+ SalLayoutGlyphs aGlyphs = pLayout->GetGlyphs();
+ SalLayoutGlyphsImpl* pGlyphsImpl = aGlyphs.Impl(1);
+
+ vcl::text::ImplLayoutArgs aArgs(sTestString, 0, sTestString.getLength(),
+ SalLayoutFlags::BiDiRtl, LanguageTag(LANGUAGE_RUSSIAN),
+ nullptr);
+
+ aArgs.PrepareFallback(pGlyphsImpl);
+
+ int nMinRunPos(0);
+ int nEndRunPos(0);
+ bool bRTL(false);
+
+ aArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL);
+ CPPUNIT_ASSERT_EQUAL(0, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(3, nEndRunPos);
+ CPPUNIT_ASSERT(!bRTL);
+
+ aArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL);
+ CPPUNIT_ASSERT_EQUAL(4, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(9, nEndRunPos);
+ CPPUNIT_ASSERT(!bRTL);
+
+ aArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL);
+ CPPUNIT_ASSERT_EQUAL(11, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(17, nEndRunPos);
+ CPPUNIT_ASSERT(!bRTL);
+
+ aArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL);
+ CPPUNIT_ASSERT_EQUAL(18, nMinRunPos);
+ CPPUNIT_ASSERT_EQUAL(22, nEndRunPos);
+ CPPUNIT_ASSERT(!bRTL);
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testGetStringWithCenterEllpsis)
+{
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(1000, 1000));
+ device->SetFont(vcl::Font("DejaVu Sans", "Book", Size(0, 11)));
+
+ CPPUNIT_ASSERT_EQUAL(
+ u"a b c d ...v w x y z"_ustr,
+ device->GetEllipsisString(u"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"_ustr, 100,
+ DrawTextFlags::CenterEllipsis));
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testGetStringWithEndEllpsis)
+{
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(1000, 1000));
+ device->SetFont(vcl::Font("DejaVu Sans", "Book", Size(0, 11)));
+
+ CPPUNIT_ASSERT_EQUAL(u"a"_ustr, device->GetEllipsisString(u"abcde. f g h i j ..."_ustr, 10,
+ DrawTextFlags::EndEllipsis));
+
+ CPPUNIT_ASSERT_EQUAL(
+ u"a b c d e f g h i j ..."_ustr,
+ device->GetEllipsisString(u"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"_ustr, 100,
+ DrawTextFlags::EndEllipsis));
+
+ CPPUNIT_ASSERT_EQUAL(
+ u"a"_ustr, device->GetEllipsisString(u"abcde. f g h i j ..."_ustr, 1,
+ DrawTextFlags::EndEllipsis | DrawTextFlags::Clip));
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testGetStringWithNewsEllpsis)
+{
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(1000, 1000));
+ device->SetFont(vcl::Font("DejaVu Sans", "Book", Size(0, 11)));
+
+ CPPUNIT_ASSERT_EQUAL(u"a"_ustr, device->GetEllipsisString(u"abcde. f g h i j ..."_ustr, 10,
+ DrawTextFlags::NewsEllipsis));
+
+ CPPUNIT_ASSERT_EQUAL(
+ u"a b .... x y z"_ustr,
+ device->GetEllipsisString(u"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"_ustr,
+ 100, DrawTextFlags::NewsEllipsis));
+
+ CPPUNIT_ASSERT_EQUAL(
+ u"a b .... x y z"_ustr,
+ device->GetEllipsisString(u"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"_ustr,
+ 100, DrawTextFlags::NewsEllipsis));
+
+ CPPUNIT_ASSERT_EQUAL(
+ u"a b c d e f g h i j ..."_ustr,
+ device->GetEllipsisString(u"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"_ustr, 100,
+ DrawTextFlags::NewsEllipsis));
+
+ CPPUNIT_ASSERT_EQUAL(
+ u"a..... x y z"_ustr,
+ device->GetEllipsisString(u"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"_ustr,
+ 100, DrawTextFlags::NewsEllipsis));
+
+ CPPUNIT_ASSERT_EQUAL(
+ u"ab. cde..."_ustr,
+ device->GetEllipsisString(u"ab. cde. x y z"_ustr, 50, DrawTextFlags::NewsEllipsis));
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testGetTextBreak)
+{
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(1000, 1000));
+ device->SetFont(vcl::Font("DejaVu Sans", "Book", Size(0, 11)));
+
+ const OUString sTestStr(u"textline_ text_"_ustr);
+ const auto nLen = sTestStr.getLength();
+ const auto nTextWidth = device->GetTextWidth("text");
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(4),
+ device->GetTextBreak(sTestStr, nTextWidth, 0, nLen));
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(7),
+ device->GetTextBreak(sTestStr, nTextWidth, 3, nLen));
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(9),
+ device->GetTextBreak(sTestStr, nTextWidth, 6, nLen));
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(12),
+ device->GetTextBreak(sTestStr, nTextWidth, 8, nLen));
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(14),
+ device->GetTextBreak(sTestStr, nTextWidth, 11, nLen));
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1),
+ device->GetTextBreak(sTestStr, nTextWidth, 13, nLen));
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testGetSingleLineTextRect)
+{
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(1000, 1000));
+ device->SetFont(vcl::Font("Liberation Sans", Size(0, 11)));
+
+ CPPUNIT_ASSERT_EQUAL(
+ tools::Rectangle(Point(), Size(75, 12)),
+ device->GetTextRect(tools::Rectangle(Point(), Point(100, 100)), "This is test text"));
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testGetSingleLineTextRectWithEndEllipsis)
+{
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(1000, 1000));
+ device->SetFont(vcl::Font("Liberation Sans", Size(0, 11)));
+
+ CPPUNIT_ASSERT_EQUAL(
+ tools::Rectangle(Point(), Size(52, 12)),
+ device->GetTextRect(tools::Rectangle(Point(), Point(50, 50)), "This is test text",
+ DrawTextFlags::WordBreak | DrawTextFlags::EndEllipsis));
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testGetRightBottomAlignedSingleLineTextRect)
+{
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(1000, 1000));
+ device->SetFont(vcl::Font("Liberation Sans", Size(0, 11)));
+
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(Point(926, 989), Size(75, 12)),
+ device->GetTextRect(tools::Rectangle(Point(), Point(1000, 1000)),
+ "This is test text",
+ DrawTextFlags::Right | DrawTextFlags::Bottom));
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testGetRotatedSingleLineTextRect)
+{
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(1000, 1000));
+ device->SetFont(vcl::Font("Liberation Sans", Size(0, 11)));
+
+ vcl::Font aFont(device->GetFont());
+ aFont.SetOrientation(45_deg10);
+ device->SetFont(aFont);
+
+ CPPUNIT_ASSERT_EQUAL(
+ tools::Rectangle(Point(0, -3), Size(75, 18)),
+ device->GetTextRect(tools::Rectangle(Point(), Point(100, 100)), "This is test text"));
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testGetMultiLineTextRect)
+{
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(1000, 1000));
+ device->SetFont(vcl::Font("Liberation Sans", Size(0, 11)));
+
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(Point(), Size(75, 12)),
+ device->GetTextRect(tools::Rectangle(Point(), Point(100, 100)),
+ "This is test text",
+ DrawTextFlags::WordBreak | DrawTextFlags::MultiLine));
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testGetMultiLineTextRectWithEndEllipsis)
+{
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(1000, 1000));
+ device->SetFont(vcl::Font("Liberation Sans", Size(0, 11)));
+
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(Point(), Size(52, 48)),
+ device->GetTextRect(tools::Rectangle(Point(), Point(50, 50)),
+ "This is test text xyzabc123abcdefghijk",
+ DrawTextFlags::WordBreak | DrawTextFlags::EndEllipsis
+ | DrawTextFlags::MultiLine));
+}
+
+CPPUNIT_TEST_FIXTURE(VclTextTest, testGetRightBottomAlignedMultiLineTextRect)
+{
+ ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
+ device->SetOutputSizePixel(Size(1000, 1000));
+ device->SetFont(vcl::Font("Liberation Sans", Size(0, 11)));
+
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(Point(926, 989), Size(75, 12)),
+ device->GetTextRect(tools::Rectangle(Point(), Point(1000, 1000)),
+ "This is test text",
+ DrawTextFlags::Right | DrawTextFlags::Bottom
+ | DrawTextFlags::MultiLine));
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/timer.cxx b/vcl/qa/cppunit/timer.cxx
new file mode 100644
index 0000000000..467d7b1a77
--- /dev/null
+++ b/vcl/qa/cppunit/timer.cxx
@@ -0,0 +1,566 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/*
+ * Timers are evil beasts across platforms...
+ */
+
+#include <test/bootstrapfixture.hxx>
+
+#include <osl/thread.hxx>
+#include <chrono>
+
+#include <vcl/timer.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/scheduler.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+// #define TEST_WATCHDOG
+
+// Enables timer tests that appear to provoke windows under load unduly.
+//#define TEST_TIMERPRECISION
+
+namespace {
+
+/// Avoid our timer tests just wedging the build if they fail.
+class WatchDog : public osl::Thread
+{
+ sal_Int32 mnSeconds;
+public:
+ explicit WatchDog(sal_Int32 nSeconds) :
+ Thread(),
+ mnSeconds( nSeconds )
+ {
+ create();
+ }
+ virtual void SAL_CALL run() override
+ {
+ osl::Thread::wait( std::chrono::seconds(mnSeconds) );
+ fprintf(stderr, "ERROR: WatchDog timer thread expired, failing the test!\n");
+ fflush(stderr);
+ CPPUNIT_ASSERT_MESSAGE("watchdog triggered", false);
+ }
+};
+
+}
+
+static WatchDog * aWatchDog = new WatchDog( 120 ); // random high number in secs
+
+class TimerTest : public test::BootstrapFixture
+{
+public:
+ TimerTest() : BootstrapFixture(true, false) {}
+
+ void testIdle();
+ void testIdleMainloop();
+#ifdef TEST_WATCHDOG
+ void testWatchdog();
+#endif
+ void testDurations();
+#ifdef TEST_TIMERPRECISION
+ void testAutoTimer();
+ void testMultiAutoTimers();
+#endif
+ void testAutoTimerStop();
+ void testNestedTimer();
+ void testSlowTimerCallback();
+ void testTriggerIdleFromIdle();
+ void testInvokedReStart();
+ void testPriority();
+ void testRoundRobin();
+
+ CPPUNIT_TEST_SUITE(TimerTest);
+ CPPUNIT_TEST(testIdle);
+ CPPUNIT_TEST(testIdleMainloop);
+#ifdef TEST_WATCHDOG
+ CPPUNIT_TEST(testWatchdog);
+#endif
+ CPPUNIT_TEST(testDurations);
+#ifdef TEST_TIMERPRECISION
+ CPPUNIT_TEST(testAutoTimer);
+ CPPUNIT_TEST(testMultiAutoTimers);
+#endif
+ CPPUNIT_TEST(testAutoTimerStop);
+ CPPUNIT_TEST(testNestedTimer);
+ CPPUNIT_TEST(testSlowTimerCallback);
+ CPPUNIT_TEST(testTriggerIdleFromIdle);
+ CPPUNIT_TEST(testInvokedReStart);
+ CPPUNIT_TEST(testPriority);
+ CPPUNIT_TEST(testRoundRobin);
+
+ CPPUNIT_TEST_SUITE_END();
+};
+
+#ifdef TEST_WATCHDOG
+void TimerTest::testWatchdog()
+{
+ // out-wait the watchdog.
+ osl::Thread::wait( std::chrono::seconds(12) );
+}
+#endif
+
+namespace {
+
+class IdleBool : public Idle
+{
+ bool &mrBool;
+public:
+ explicit IdleBool( bool &rBool ) :
+ Idle( "IdleBool" ), mrBool( rBool )
+ {
+ SetPriority( TaskPriority::LOWEST );
+ Start();
+ mrBool = false;
+ }
+ virtual void Invoke() override
+ {
+ mrBool = true;
+ Application::EndYield();
+ }
+};
+
+}
+
+void TimerTest::testIdle()
+{
+ bool bTriggered = false;
+ IdleBool aTest( bTriggered );
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_MESSAGE("idle triggered", bTriggered);
+}
+
+void TimerTest::testIdleMainloop()
+{
+ bool bTriggered = false;
+ IdleBool aTest( bTriggered );
+ // coverity[loop_top] - Application::Yield allows the timer to fire and toggle bDone
+ while (!bTriggered)
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // can't test this via Application::Yield since this
+ // also processes all tasks directly via the scheduler.
+ pSVData->maAppData.mnDispatchLevel++;
+ pSVData->mpDefInst->DoYield(true, false);
+ pSVData->maAppData.mnDispatchLevel--;
+ }
+ CPPUNIT_ASSERT_MESSAGE("mainloop idle triggered", bTriggered);
+}
+
+namespace {
+
+class TimerBool : public Timer
+{
+ bool &mrBool;
+public:
+ TimerBool( sal_uInt64 nMS, bool &rBool ) :
+ Timer( "TimerBool" ), mrBool( rBool )
+ {
+ SetTimeout( nMS );
+ Start();
+ mrBool = false;
+ }
+ virtual void Invoke() override
+ {
+ mrBool = true;
+ Application::EndYield();
+ }
+};
+
+}
+
+void TimerTest::testDurations()
+{
+ for (auto const nDuration : { 0, 1, 500, 1000 })
+ {
+ bool bDone = false;
+ TimerBool aTimer( nDuration, bDone );
+ // coverity[loop_top] - Application::Yield allows the timer to fire and toggle bDone
+ while( !bDone )
+ {
+ Application::Yield();
+ }
+ }
+}
+
+namespace {
+
+class AutoTimerCount : public AutoTimer
+{
+ sal_Int32 &mrCount;
+ const sal_Int32 mnMaxCount;
+
+public:
+ AutoTimerCount( sal_uInt64 nMS, sal_Int32 &rCount,
+ const sal_Int32 nMaxCount = -1 )
+ : AutoTimer( "AutoTimerCount" )
+ , mrCount( rCount )
+ , mnMaxCount( nMaxCount )
+ {
+ SetTimeout( nMS );
+ Start();
+ mrCount = 0;
+ }
+
+ virtual void Invoke() override
+ {
+ ++mrCount;
+ CPPUNIT_ASSERT( mnMaxCount < 0 || mrCount <= mnMaxCount );
+ if ( mrCount == mnMaxCount )
+ Stop();
+ }
+};
+
+}
+
+#ifdef TEST_TIMERPRECISION
+
+void TimerTest::testAutoTimer()
+{
+ const sal_Int32 nDurationMs = 30;
+ const sal_Int32 nEventsCount = 5;
+ const double exp = (nDurationMs * nEventsCount);
+
+ sal_Int32 nCount = 0;
+ std::ostringstream msg;
+
+ // Repeat when we have random latencies.
+ // This is expected on non-realtime OSes.
+ for (int i = 0; i < 10; ++i)
+ {
+ const auto start = std::chrono::high_resolution_clock::now();
+ nCount = 0;
+ AutoTimerCount aCount(nDurationMs, nCount);
+ while (nCount < nEventsCount) {
+ Application::Yield();
+ }
+
+ const auto end = std::chrono::high_resolution_clock::now();
+ double dur = std::chrono::duration<double, std::milli>(end - start).count();
+
+ msg << std::setprecision(2) << std::fixed
+ << "periodic multi-timer - dur: "
+ << dur << " (" << exp << ") ms." << std::endl;
+
+ // +/- 20% should be reasonable enough a margin.
+ if (dur >= (exp * 0.8) && dur <= (exp * 1.2))
+ {
+ // Success.
+ return;
+ }
+ }
+
+ CPPUNIT_FAIL(msg.str().c_str());
+}
+
+void TimerTest::testMultiAutoTimers()
+{
+ // The behavior of the timers change drastically
+ // when multiple timers are present.
+ // The worst, in my tests, is when two
+ // timers with 1ms period exist with a
+ // third of much longer period.
+
+ const sal_Int32 nDurationMsX = 5;
+ const sal_Int32 nDurationMsY = 10;
+ const sal_Int32 nDurationMs = 40;
+ const sal_Int32 nEventsCount = 5;
+ const double exp = (nDurationMs * nEventsCount);
+ const double expX = (exp / nDurationMsX);
+ const double expY = (exp / nDurationMsY);
+
+ sal_Int32 nCountX = 0;
+ sal_Int32 nCountY = 0;
+ sal_Int32 nCount = 0;
+ std::ostringstream msg;
+
+ // Repeat when we have random latencies.
+ // This is expected on non-realtime OSes.
+ for (int i = 0; i < 10; ++i)
+ {
+ nCountX = 0;
+ nCountY = 0;
+ nCount = 0;
+
+ const auto start = std::chrono::high_resolution_clock::now();
+ AutoTimerCount aCountX(nDurationMsX, nCountX);
+ AutoTimerCount aCountY(nDurationMsY, nCountY);
+
+ AutoTimerCount aCount(nDurationMs, nCount);
+ // coverity[loop_top] - Application::Yield allows the timer to fire and toggle nCount
+ while (nCount < nEventsCount) {
+ Application::Yield();
+ }
+
+ const auto end = std::chrono::high_resolution_clock::now();
+ double dur = std::chrono::duration<double, std::milli>(end - start).count();
+
+ msg << std::setprecision(2) << std::fixed << "periodic multi-timer - dur: "
+ << dur << " (" << exp << ") ms, nCount: " << nCount
+ << " (" << nEventsCount << "), nCountX: " << nCountX
+ << " (" << expX << "), nCountY: " << nCountY
+ << " (" << expY << ")." << std::endl;
+
+ // +/- 20% should be reasonable enough a margin.
+ if (dur >= (exp * 0.8) && dur <= (exp * 1.2) &&
+ nCountX >= (expX * 0.8) && nCountX <= (expX * 1.2) &&
+ nCountY >= (expY * 0.8) && nCountY <= (expY * 1.2))
+ {
+ // Success.
+ return;
+ }
+ }
+
+ CPPUNIT_FAIL(msg.str().c_str());
+}
+#endif // TEST_TIMERPRECISION
+
+void TimerTest::testAutoTimerStop()
+{
+ sal_Int32 nTimerCount = 0;
+ const sal_Int32 nMaxCount = 5;
+ AutoTimerCount aAutoTimer( 0, nTimerCount, nMaxCount );
+ // coverity[loop_top] - Application::Yield allows the timer to fire and increment TimerCount
+ while (nMaxCount != nTimerCount)
+ Application::Yield();
+ CPPUNIT_ASSERT( !aAutoTimer.IsActive() );
+ CPPUNIT_ASSERT( !Application::Reschedule() );
+}
+
+namespace {
+
+class YieldTimer : public Timer
+{
+public:
+ explicit YieldTimer( sal_uInt64 nMS ) : Timer( "YieldTimer" )
+ {
+ SetTimeout( nMS );
+ Start();
+ }
+ virtual void Invoke() override
+ {
+ for (int i = 0; i < 100; i++)
+ Application::Yield();
+ }
+};
+
+}
+
+void TimerTest::testNestedTimer()
+{
+ sal_Int32 nCount = 0;
+ YieldTimer aCount(5);
+ AutoTimerCount aCountUp( 3, nCount );
+ // coverity[loop_top] - Application::Yield allows the timer to fire and increment nCount
+ while (nCount < 20)
+ Application::Yield();
+}
+
+namespace {
+
+class SlowCallbackTimer : public Timer
+{
+ bool &mbSlow;
+public:
+ SlowCallbackTimer( sal_uInt64 nMS, bool &bBeenSlow ) :
+ Timer( "SlowCallbackTimer" ), mbSlow( bBeenSlow )
+ {
+ SetTimeout( nMS );
+ Start();
+ mbSlow = false;
+ }
+ virtual void Invoke() override
+ {
+ osl::Thread::wait( std::chrono::seconds(1) );
+ mbSlow = true;
+ }
+};
+
+}
+
+void TimerTest::testSlowTimerCallback()
+{
+ bool bBeenSlow = false;
+ sal_Int32 nCount = 0;
+ AutoTimerCount aHighFreq(1, nCount);
+ SlowCallbackTimer aSlow(250, bBeenSlow);
+ // coverity[loop_top] - Application::Yield allows the timer to fire and toggle bBeenSlow
+ while (!bBeenSlow)
+ Application::Yield();
+ // coverity[loop_top] - Application::Yield allows the timer to fire and increment nCount
+ while (nCount < 200)
+ Application::Yield();
+}
+
+namespace {
+
+class TriggerIdleFromIdle : public Idle
+{
+ bool* mpTriggered;
+ TriggerIdleFromIdle* mpOther;
+public:
+ explicit TriggerIdleFromIdle( bool* pTriggered, TriggerIdleFromIdle* pOther ) :
+ Idle( "TriggerIdleFromIdle" ), mpTriggered(pTriggered), mpOther(pOther)
+ {
+ }
+ virtual void Invoke() override
+ {
+ Start();
+ if (mpOther)
+ mpOther->Start();
+ Application::Yield();
+ if (mpTriggered)
+ *mpTriggered = true;
+ }
+};
+
+}
+
+void TimerTest::testTriggerIdleFromIdle()
+{
+ bool bTriggered1 = false;
+ bool bTriggered2 = false;
+ TriggerIdleFromIdle aTest2( &bTriggered2, nullptr );
+ TriggerIdleFromIdle aTest1( &bTriggered1, &aTest2 );
+ aTest1.Start();
+ Application::Yield();
+ CPPUNIT_ASSERT_MESSAGE("idle not triggered", bTriggered1);
+ CPPUNIT_ASSERT_MESSAGE("idle not triggered", bTriggered2);
+}
+
+namespace {
+
+class IdleInvokedReStart : public Idle
+{
+ sal_Int32 &mrCount;
+public:
+ IdleInvokedReStart( sal_Int32 &rCount )
+ : Idle( "IdleInvokedReStart" ), mrCount( rCount )
+ {
+ Start();
+ }
+ virtual void Invoke() override
+ {
+ mrCount++;
+ if ( mrCount < 2 )
+ Start();
+ }
+};
+
+}
+
+void TimerTest::testInvokedReStart()
+{
+ sal_Int32 nCount = 0;
+ IdleInvokedReStart aIdle( nCount );
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL( sal_Int32(2), nCount );
+}
+
+namespace {
+
+class IdleSerializer : public Idle
+{
+ sal_uInt32 mnPosition;
+ sal_uInt32 &mrProcessed;
+public:
+ IdleSerializer(const char *pDebugName, TaskPriority ePrio,
+ sal_uInt32 nPosition, sal_uInt32 &rProcessed)
+ : Idle( pDebugName )
+ , mnPosition( nPosition )
+ , mrProcessed( rProcessed )
+ {
+ SetPriority(ePrio);
+ Start();
+ }
+ virtual void Invoke() override
+ {
+ ++mrProcessed;
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Ignored prio", mnPosition, mrProcessed );
+ }
+};
+
+}
+
+void TimerTest::testPriority()
+{
+ // scope, so tasks are deleted
+ {
+ // Start: 1st Idle low, 2nd high
+ sal_uInt32 nProcessed = 0;
+ IdleSerializer aLowPrioIdle("IdleSerializer LowPrio",
+ TaskPriority::LOWEST, 2, nProcessed);
+ IdleSerializer aHighPrioIdle("IdleSerializer HighPrio",
+ TaskPriority::HIGHEST, 1, nProcessed);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Not all idles processed", sal_uInt32(2), nProcessed );
+ }
+
+ {
+ // Start: 1st Idle high, 2nd low
+ sal_uInt32 nProcessed = 0;
+ IdleSerializer aHighPrioIdle("IdleSerializer HighPrio",
+ TaskPriority::HIGHEST, 1, nProcessed);
+ IdleSerializer aLowPrioIdle("IdleSerializer LowPrio",
+ TaskPriority::LOWEST, 2, nProcessed);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Not all idles processed", sal_uInt32(2), nProcessed );
+ }
+}
+
+namespace {
+
+class TestAutoIdleRR : public AutoIdle
+{
+ sal_uInt32 &mrCount;
+
+ DECL_LINK( IdleRRHdl, Timer *, void );
+
+public:
+ TestAutoIdleRR( sal_uInt32 &rCount,
+ const char *pDebugName )
+ : AutoIdle( pDebugName )
+ , mrCount( rCount )
+ {
+ CPPUNIT_ASSERT_EQUAL( sal_uInt32(0), mrCount );
+ SetInvokeHandler( LINK( this, TestAutoIdleRR, IdleRRHdl ) );
+ Start();
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(TestAutoIdleRR, IdleRRHdl, Timer *, void)
+{
+ ++mrCount;
+ if ( mrCount == 3 )
+ Stop();
+}
+
+void TimerTest::testRoundRobin()
+{
+ sal_uInt32 nCount1 = 0, nCount2 = 0;
+ TestAutoIdleRR aIdle1( nCount1, "TestAutoIdleRR aIdle1" ),
+ aIdle2( nCount2, "TestAutoIdleRR aIdle2" );
+ while ( Application::Reschedule() )
+ {
+ CPPUNIT_ASSERT( nCount1 == nCount2 || nCount1 - 1 == nCount2 );
+ CPPUNIT_ASSERT( nCount1 <= 3 );
+ CPPUNIT_ASSERT( nCount2 <= 3 );
+ }
+ CPPUNIT_ASSERT_EQUAL( sal_uInt32(3), nCount1 );
+ CPPUNIT_ASSERT_EQUAL( sal_uInt32(3), nCount2 );
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TimerTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/widgetdraw/WidgetDefinitionReaderTest.cxx b/vcl/qa/cppunit/widgetdraw/WidgetDefinitionReaderTest.cxx
new file mode 100644
index 0000000000..2665508ee4
--- /dev/null
+++ b/vcl/qa/cppunit/widgetdraw/WidgetDefinitionReaderTest.cxx
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <cppunit/TestAssert.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+#include <unotest/bootstrapfixturebase.hxx>
+
+#include <widgetdraw/WidgetDefinitionReader.hxx>
+
+namespace
+{
+constexpr OUStringLiteral gaDataUrl(u"/vcl/qa/cppunit/widgetdraw/data/");
+
+class WidgetDefinitionReaderTest : public test::BootstrapFixtureBase
+{
+private:
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(gaDataUrl) + sFileName;
+ }
+
+public:
+ void testRead();
+ void testReadSettings();
+
+ CPPUNIT_TEST_SUITE(WidgetDefinitionReaderTest);
+ CPPUNIT_TEST(testRead);
+ CPPUNIT_TEST(testReadSettings);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void WidgetDefinitionReaderTest::testReadSettings()
+{
+ {
+ vcl::WidgetDefinition aDefinition;
+ vcl::WidgetDefinitionReader aReader(getFullUrl(u"definitionSettings1.xml"),
+ getFullUrl(u""));
+ CPPUNIT_ASSERT(aReader.read(aDefinition));
+ CPPUNIT_ASSERT_EQUAL(""_ostr, aDefinition.mpSettings->msCenteredTabs);
+ }
+
+ {
+ vcl::WidgetDefinition aDefinition;
+ vcl::WidgetDefinitionReader aReader(getFullUrl(u"definitionSettings2.xml"),
+ getFullUrl(u""));
+ CPPUNIT_ASSERT(aReader.read(aDefinition));
+ CPPUNIT_ASSERT_EQUAL("true"_ostr, aDefinition.mpSettings->msCenteredTabs);
+ }
+
+ {
+ vcl::WidgetDefinition aDefinition;
+ vcl::WidgetDefinitionReader aReader(getFullUrl(u"definitionSettings3.xml"),
+ getFullUrl(u""));
+ CPPUNIT_ASSERT(aReader.read(aDefinition));
+ CPPUNIT_ASSERT_EQUAL("true"_ostr, aDefinition.mpSettings->msNoActiveTabTextRaise);
+ CPPUNIT_ASSERT_EQUAL("false"_ostr, aDefinition.mpSettings->msCenteredTabs);
+ CPPUNIT_ASSERT_EQUAL("0"_ostr, aDefinition.mpSettings->msListBoxEntryMargin);
+ CPPUNIT_ASSERT_EQUAL("10"_ostr, aDefinition.mpSettings->msDefaultFontSize);
+ CPPUNIT_ASSERT_EQUAL("16"_ostr, aDefinition.mpSettings->msTitleHeight);
+ CPPUNIT_ASSERT_EQUAL("12"_ostr, aDefinition.mpSettings->msFloatTitleHeight);
+ CPPUNIT_ASSERT_EQUAL("15"_ostr, aDefinition.mpSettings->msListBoxPreviewDefaultLogicWidth);
+ CPPUNIT_ASSERT_EQUAL("7"_ostr, aDefinition.mpSettings->msListBoxPreviewDefaultLogicHeight);
+ }
+}
+
+void WidgetDefinitionReaderTest::testRead()
+{
+ vcl::WidgetDefinition aDefinition;
+
+ vcl::WidgetDefinitionReader aReader(getFullUrl(u"definition1.xml"), getFullUrl(u""));
+ CPPUNIT_ASSERT(aReader.read(aDefinition));
+
+ CPPUNIT_ASSERT_EQUAL(OUString("123456"), aDefinition.mpStyle->maFaceColor.AsRGBHexString());
+ CPPUNIT_ASSERT_EQUAL(OUString("234567"), aDefinition.mpStyle->maCheckedColor.AsRGBHexString());
+ CPPUNIT_ASSERT_EQUAL(OUString("345678"), aDefinition.mpStyle->maLightColor.AsRGBHexString());
+
+ CPPUNIT_ASSERT_EQUAL(OUString("ffffff"),
+ aDefinition.mpStyle->maVisitedLinkColor.AsRGBHexString());
+ CPPUNIT_ASSERT_EQUAL(OUString("ffffff"), aDefinition.mpStyle->maToolTextColor.AsRGBHexString());
+ CPPUNIT_ASSERT_EQUAL(OUString("ffffff"),
+ aDefinition.mpStyle->maWindowTextColor.AsRGBHexString());
+
+ // Pushbutton
+ {
+ ControlState eState
+ = ControlState::DEFAULT | ControlState::ENABLED | ControlState::ROLLOVER;
+ std::vector<std::shared_ptr<vcl::WidgetDefinitionState>> aStates
+ = aDefinition.getDefinition(ControlType::Pushbutton, ControlPart::Entire)
+ ->getStates(ControlType::Pushbutton, ControlPart::Entire, eState,
+ PushButtonValue());
+
+ CPPUNIT_ASSERT_EQUAL(size_t(2), aStates.size());
+
+ CPPUNIT_ASSERT_EQUAL(size_t(2), aStates[0]->mpWidgetDrawActions.size());
+ CPPUNIT_ASSERT_EQUAL(vcl::WidgetDrawActionType::RECTANGLE,
+ aStates[0]->mpWidgetDrawActions[0]->maType);
+ CPPUNIT_ASSERT_EQUAL(vcl::WidgetDrawActionType::LINE,
+ aStates[0]->mpWidgetDrawActions[1]->maType);
+ }
+
+ // Radiobutton
+ {
+ std::vector<std::shared_ptr<vcl::WidgetDefinitionState>> aStates
+ = aDefinition.getDefinition(ControlType::Radiobutton, ControlPart::Entire)
+ ->getStates(ControlType::Radiobutton, ControlPart::Entire, ControlState::NONE,
+ ImplControlValue(ButtonValue::On));
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aStates.size());
+ CPPUNIT_ASSERT_EQUAL(size_t(2), aStates[0]->mpWidgetDrawActions.size());
+ }
+
+ {
+ std::vector<std::shared_ptr<vcl::WidgetDefinitionState>> aStates
+ = aDefinition.getDefinition(ControlType::Radiobutton, ControlPart::Entire)
+ ->getStates(ControlType::Radiobutton, ControlPart::Entire, ControlState::NONE,
+ ImplControlValue(ButtonValue::Off));
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aStates.size());
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aStates[0]->mpWidgetDrawActions.size());
+ }
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(WidgetDefinitionReaderTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/widgetdraw/data/definition1.xml b/vcl/qa/cppunit/widgetdraw/data/definition1.xml
new file mode 100644
index 0000000000..34e2196181
--- /dev/null
+++ b/vcl/qa/cppunit/widgetdraw/data/definition1.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widgets>
+ <style>
+ <faceColor value="#123456"/>
+ <checkedColor value="#234567"/>
+ <lightColor value="#345678"/>
+ <lightBorderColor value="#FFFFFF"/>
+ <shadowColor value="#FFFFFF"/>
+ <darkShadowColor value="#FFFFFF"/>
+ <buttonTextColor value="#FFFFFF"/>
+ <buttonRolloverTextColor value="#FFFFFF"/>
+ <radioCheckTextColor value="#FFFFFF"/>
+ <groupTextColor value="#FFFFFF"/>
+ <labelTextColor value="#FFFFFF"/>
+ <windowColor value="#FFFFFF"/>
+ <windowTextColor value="#FFFFFF"/>
+ <dialogColor value="#FFFFFF"/>
+ <dialogTextColor value="#FFFFFF"/>
+ <workspaceColor value="#FFFFFF"/>
+ <monoColor value="#FFFFFF"/>
+ <fieldColor value="#FFFFFF"/>
+ <fieldTextColor value="#FFFFFF"/>
+ <fieldRolloverTextColor value="#FFFFFF"/>
+ <activeColor value="#FFFFFF"/>
+ <activeTextColor value="#FFFFFF"/>
+ <activeBorderColor value="#FFFFFF"/>
+ <deactiveColor value="#FFFFFF"/>
+ <deactiveTextColor value="#FFFFFF"/>
+ <deactiveBorderColor value="#FFFFFF"/>
+ <menuColor value="#FFFFFF"/>
+ <menuBarColor value="#FFFFFF"/>
+ <menuBarRolloverColor value="#FFFFFF"/>
+ <menuBorderColor value="#FFFFFF"/>
+ <menuTextColor value="#FFFFFF"/>
+ <menuBarTextColor value="#FFFFFF"/>
+ <menuBarRolloverTextColor value="#FFFFFF"/>
+ <menuBarHighlightTextColor value="#FFFFFF"/>
+ <menuHighlightColor value="#FFFFFF"/>
+ <menuHighlightTextColor value="#FFFFFF"/>
+ <highlightColor value="#FFFFFF"/>
+ <highlightTextColor value="#FFFFFF"/>
+ <activeTabColor value="#FFFFFF"/>
+ <inactiveTabColor value="#FFFFFF"/>
+ <tabTextColor value="#FFFFFF"/>
+ <tabRolloverTextColor value="#FFFFFF"/>
+ <tabHighlightTextColor value="#FFFFFF"/>
+ <disableColor value="#FFFFFF"/>
+ <helpColor value="#FFFFFF"/>
+ <helpTextColor value="#FFFFFF"/>
+ <linkColor value="#FFFFFF"/>
+ <visitedLinkColor value="#FFFFFF"/>
+ <toolTextColor value="#FFFFFF"/>
+ </style>
+ <pushbutton>
+ <part value="Entire">
+ <state enabled="any" focused="any" pressed="any" rollover="any" default="any" selected="any" button-value="any">
+ <rect stroke="#808080" fill="#FFFFFF" stroke-width="1.0" rx="5" ry="5" margin="1"/>
+ <line stroke="#808080" fill="#808080" stroke-width="1.0"/>
+ </state>
+ <state enabled="true" focused="any" pressed="any" rollover="true" default="true" selected="any" button-value="any">
+ <rect stroke="#808080" fill="#808080" stroke-width="1.0" rx="5" ry="5" margin="1"/>
+ </state>
+ </part>
+ <part value="Focus">
+ <state enabled="any" focused="any" pressed="any" rollover="any" default="any" selected="any" button-value="any">
+ <rect stroke="#808080" fill="#FFFFFF" stroke-width="1.0" rx="5" ry="5" margin="1"/>
+ </state>
+ </part>
+ </pushbutton>
+ <radiobutton>
+ <part value="Entire">
+ <state enabled="any" focused="any" pressed="any" rollover="any" default="any" selected="any" button-value="false">
+ <rect stroke="#007AFF" fill="#FFFFFF" stroke-width="1" margin="0"/>
+ </state>
+ <state enabled="any" focused="any" pressed="any" rollover="any" default="any" selected="any" button-value="true">
+ <rect stroke="#007AFF" fill="#FFFFFF" stroke-width="1" margin="0"/>
+ <rect stroke="#007AFF" fill="#007AFF" stroke-width="1" margin="3"/>
+ </state>
+ </part>
+ </radiobutton>
+</widgets>
diff --git a/vcl/qa/cppunit/widgetdraw/data/definitionSettings1.xml b/vcl/qa/cppunit/widgetdraw/data/definitionSettings1.xml
new file mode 100644
index 0000000000..9ca7f894f2
--- /dev/null
+++ b/vcl/qa/cppunit/widgetdraw/data/definitionSettings1.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widgets>
+ <settings>
+ </settings>
+</widgets>
diff --git a/vcl/qa/cppunit/widgetdraw/data/definitionSettings2.xml b/vcl/qa/cppunit/widgetdraw/data/definitionSettings2.xml
new file mode 100644
index 0000000000..0d6d6e1115
--- /dev/null
+++ b/vcl/qa/cppunit/widgetdraw/data/definitionSettings2.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widgets>
+ <settings>
+ <centeredTabs value="true"/>
+ </settings>
+</widgets>
diff --git a/vcl/qa/cppunit/widgetdraw/data/definitionSettings3.xml b/vcl/qa/cppunit/widgetdraw/data/definitionSettings3.xml
new file mode 100644
index 0000000000..9ad88dd545
--- /dev/null
+++ b/vcl/qa/cppunit/widgetdraw/data/definitionSettings3.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widgets>
+ <settings>
+ <noActiveTabTextRaise value="true"/>
+ <centeredTabs value="false"/>
+ <listBoxEntryMargin value="0"/>
+ <defaultFontSize value="10"/>
+ <titleHeight value="16"/>
+ <floatTitleHeight value="12"/>
+ <listBoxPreviewDefaultLogicWidth value="15"/>
+ <listBoxPreviewDefaultLogicHeight value="7"/>
+ </settings>
+</widgets>
diff --git a/vcl/qa/unit/data/vcl-dialogs-test.txt b/vcl/qa/unit/data/vcl-dialogs-test.txt
new file mode 100644
index 0000000000..0725d52c76
--- /dev/null
+++ b/vcl/qa/unit/data/vcl-dialogs-test.txt
@@ -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/.
+#
+
+# This file contains all dialogs that the unit tests in the module
+# will work on if it is in script mode. It will read one-by-one,
+# try to open it and create a screenshot that will be saved in
+# workdir/screenshots using the pattern of the ui-file name.
+#
+# Syntax:
+# - empty lines are allowed
+# - lines starting with '#' are treated as comment
+# - all other lines should contain a *.ui filename in the same
+# notation as in the dialog constructors (see code)
+
+#
+# The 'known' dialogs which have a hard-coded representation
+# in registerKnownDialogsByID/createDialogByID
+#
+
+# No known dialogs in vcl for now
+
+#
+# Dialogs without a hard-coded representation. These will
+# be visualized using a fallback based on weld::Builder
+#
+
+# currently deactivated, leads to problems and the test to not work
+# This is typically a hint that these should be hard-coded in the
+# test case since they need some document and model data to work
+
+vcl/ui/printerpropertiesdialog.ui
+vcl/ui/printerpaperpage.ui
+vcl/ui/printerdevicepage.ui
+vcl/ui/printdialog.ui
+vcl/ui/querydialog.ui
+vcl/ui/cupspassworddialog.ui
+vcl/ui/errornoprinterdialog.ui
+vcl/ui/errornocontentdialog.ui
+vcl/ui/printprogressdialog.ui
diff --git a/vcl/qa/unit/vcl-dialogs-test.cxx b/vcl/qa/unit/vcl-dialogs-test.cxx
new file mode 100644
index 0000000000..4f5016da29
--- /dev/null
+++ b/vcl/qa/unit/vcl-dialogs-test.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 <sal/config.h>
+#include <test/screenshot_test.hxx>
+#include <vcl/abstdlg.hxx>
+
+using namespace ::com::sun::star;
+
+/// Test opening a dialog in vcl
+class VclDialogsTest : public ScreenshotTest
+{
+private:
+ /// helper method to populate KnownDialogs, called in setUp(). Needs to be
+ /// written and has to add entries to KnownDialogs
+ virtual void registerKnownDialogsByID(mapType& rKnownDialogs) override;
+
+ /// dialog creation for known dialogs by ID. Has to be implemented for
+ /// each registered known dialog
+ virtual VclPtr<VclAbstractDialog> createDialogByID(sal_uInt32 nID) override;
+
+public:
+ VclDialogsTest();
+
+ // try to open a dialog
+ void openAnyDialog();
+
+ CPPUNIT_TEST_SUITE(VclDialogsTest);
+ CPPUNIT_TEST(openAnyDialog);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+VclDialogsTest::VclDialogsTest() {}
+
+void VclDialogsTest::registerKnownDialogsByID(mapType& /*rKnownDialogs*/)
+{
+ // fill map of known dialogs
+}
+
+VclPtr<VclAbstractDialog> VclDialogsTest::createDialogByID(sal_uInt32 /*nID*/) { return nullptr; }
+
+void VclDialogsTest::openAnyDialog()
+{
+ /// process input file containing the UXMLDescriptions of the dialogs to dump
+ processDialogBatchFile(u"vcl/qa/unit/data/vcl-dialogs-test.txt");
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VclDialogsTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtAccessibleEventListener.cxx b/vcl/qt5/QtAccessibleEventListener.cxx
new file mode 100644
index 0000000000..0bf4dcddbf
--- /dev/null
+++ b/vcl/qt5/QtAccessibleEventListener.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 <QtAccessibleEventListener.hxx>
+#include <QtAccessibleRegistry.hxx>
+#include <QtTools.hxx>
+
+#include <sal/log.hxx>
+
+#include <com/sun/star/accessibility/AccessibleEventId.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp>
+#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp>
+#include <com/sun/star/accessibility/TextSegment.hpp>
+
+#include <QtGui/QAccessible>
+#include <QtGui/QAccessibleTextSelectionEvent>
+
+using namespace css;
+using namespace css::accessibility;
+using namespace css::lang;
+using namespace css::uno;
+
+QtAccessibleEventListener::QtAccessibleEventListener(QtAccessibleWidget* pAccessibleWidget)
+ : m_pAccessibleWidget(pAccessibleWidget)
+{
+}
+
+void QtAccessibleEventListener::HandleStateChangedEvent(
+ QAccessibleInterface* pQAccessibleInterface,
+ const css::accessibility::AccessibleEventObject& rEvent)
+{
+ QAccessible::State aState;
+
+ sal_Int64 nState = 0;
+ rEvent.NewValue >>= nState;
+ // States in 'QAccessibleStateChangeEvent' indicate what states have changed, so if e.g.
+ // an object loses focus (not just if it gains it), 'focus' state needs to be set to 'true',
+ // so retrieve the old/previous value from the event if necessary.
+ if (nState == AccessibleStateType::INVALID)
+ rEvent.OldValue >>= nState;
+
+ switch (nState)
+ {
+ case AccessibleStateType::ACTIVE:
+ aState.active = true;
+ break;
+ case AccessibleStateType::BUSY:
+ aState.busy = true;
+ break;
+ case AccessibleStateType::CHECKABLE:
+ aState.checkable = true;
+ break;
+ case AccessibleStateType::CHECKED:
+ aState.checked = true;
+ break;
+ case AccessibleStateType::COLLAPSE:
+ aState.collapsed = true;
+ break;
+ case AccessibleStateType::DEFAULT:
+ aState.defaultButton = true;
+ break;
+ case AccessibleStateType::ENABLED:
+ aState.disabled = true;
+ break;
+ case AccessibleStateType::EDITABLE:
+ aState.editable = true;
+ break;
+ case AccessibleStateType::EXPANDABLE:
+ aState.expandable = true;
+ break;
+ case AccessibleStateType::EXPANDED:
+ aState.expanded = true;
+ break;
+ case AccessibleStateType::FOCUSABLE:
+ aState.focusable = true;
+ break;
+ case AccessibleStateType::FOCUSED:
+ aState.focused = true;
+ break;
+ case AccessibleStateType::INVALID:
+ aState.invalid = true;
+ break;
+ case AccessibleStateType::VISIBLE:
+ aState.invisible = true;
+ break;
+ case AccessibleStateType::MODAL:
+ aState.modal = true;
+ break;
+ case AccessibleStateType::MOVEABLE:
+ aState.movable = true;
+ break;
+ case AccessibleStateType::MULTI_LINE:
+ // comment in Qt's qaccessible.h has this:
+ // "// quint64 singleLine : 1; // we have multi line, this is redundant."
+ case AccessibleStateType::SINGLE_LINE:
+ aState.multiLine = true;
+ break;
+ case AccessibleStateType::MULTI_SELECTABLE:
+ aState.multiSelectable = true;
+ break;
+ case AccessibleStateType::OFFSCREEN:
+ aState.offscreen = true;
+ break;
+ case AccessibleStateType::PRESSED:
+ aState.pressed = true;
+ break;
+ case AccessibleStateType::RESIZABLE:
+ aState.sizeable = true;
+ break;
+ case AccessibleStateType::SELECTABLE:
+ aState.selectable = true;
+ break;
+ case AccessibleStateType::SELECTED:
+ aState.selected = true;
+ break;
+ case AccessibleStateType::SHOWING:
+ {
+ // Qt does not have an equivalent for the SHOWING state,
+ // but has separate event types
+ QAccessible::Event eEventType;
+ sal_Int64 nNewState = 0;
+ if ((rEvent.NewValue >>= nNewState) && nNewState == AccessibleStateType::SHOWING)
+ eEventType = QAccessible::ObjectShow;
+ else
+ eEventType = QAccessible::ObjectHide;
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, eEventType));
+ break;
+ }
+ // These don't seem to have a matching Qt equivalent
+ case AccessibleStateType::ARMED:
+ case AccessibleStateType::DEFUNC:
+ case AccessibleStateType::HORIZONTAL:
+ case AccessibleStateType::ICONIFIED:
+ case AccessibleStateType::INDETERMINATE:
+ case AccessibleStateType::MANAGES_DESCENDANTS:
+ case AccessibleStateType::OPAQUE:
+ case AccessibleStateType::SENSITIVE:
+ case AccessibleStateType::STALE:
+ case AccessibleStateType::TRANSIENT:
+ case AccessibleStateType::VERTICAL:
+ default:
+ return;
+ }
+
+ QAccessible::updateAccessibility(
+ new QAccessibleStateChangeEvent(pQAccessibleInterface, aState));
+}
+
+void QtAccessibleEventListener::notifyEvent(const css::accessibility::AccessibleEventObject& aEvent)
+{
+ QAccessibleInterface* pQAccessibleInterface = m_pAccessibleWidget;
+
+ switch (aEvent.EventId)
+ {
+ case AccessibleEventId::NAME_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::NameChanged));
+ return;
+ case AccessibleEventId::DESCRIPTION_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::DescriptionChanged));
+ return;
+ case AccessibleEventId::ACTION_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::ActionChanged));
+ return;
+ case AccessibleEventId::ACTIVE_DESCENDANT_CHANGED:
+ {
+ // Qt has a QAccessible::ActiveDescendantChanged event type, but events of
+ // that type are currently just ignored on Qt side and not forwarded to AT-SPI.
+ // Send a state change event for the focused state of the newly
+ // active descendant instead
+ uno::Reference<accessibility::XAccessible> xActiveAccessible;
+ aEvent.NewValue >>= xActiveAccessible;
+ if (!xActiveAccessible.is())
+ return;
+
+ QObject* pQtAcc = QtAccessibleRegistry::getQObject(xActiveAccessible);
+ QAccessibleInterface* pInterface = QAccessible::queryAccessibleInterface(pQtAcc);
+ QAccessible::State aState;
+ aState.focused = true;
+ QAccessible::updateAccessibility(new QAccessibleStateChangeEvent(pInterface, aState));
+ return;
+ }
+ case AccessibleEventId::CARET_CHANGED:
+ {
+ sal_Int32 nNewCursorPos = 0;
+ aEvent.NewValue >>= nNewCursorPos;
+ QAccessible::updateAccessibility(
+ new QAccessibleTextCursorEvent(pQAccessibleInterface, nNewCursorPos));
+ return;
+ }
+ case AccessibleEventId::CHILD:
+ {
+ Reference<XAccessible> xChild;
+ if (aEvent.NewValue >>= xChild)
+ {
+ assert(xChild.is()
+ && "AccessibleEventId::CHILD event NewValue without valid child set");
+ // tdf#159213 for now, workaround invalid events being sent and don't crash in release builds
+ if (!xChild.is())
+ return;
+ QAccessible::updateAccessibility(new QAccessibleEvent(
+ QtAccessibleRegistry::getQObject(xChild), QAccessible::ObjectCreated));
+ return;
+ }
+ if (aEvent.OldValue >>= xChild)
+ {
+ assert(xChild.is()
+ && "AccessibleEventId::CHILD event OldValue without valid child set");
+ // tdf#159213 for now, workaround invalid events being sent and don't crash in release builds
+ if (!xChild.is())
+ return;
+ QAccessible::updateAccessibility(new QAccessibleEvent(
+ QtAccessibleRegistry::getQObject(xChild), QAccessible::ObjectDestroyed));
+ return;
+ }
+ SAL_WARN("vcl.qt",
+ "Ignoring invalid AccessibleEventId::CHILD event without any child set.");
+ return;
+ }
+ case AccessibleEventId::HYPERTEXT_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::HypertextChanged));
+ return;
+ case AccessibleEventId::SELECTION_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::Selection));
+ return;
+ case AccessibleEventId::VISIBLE_DATA_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::VisibleDataChanged));
+ return;
+ case AccessibleEventId::TEXT_SELECTION_CHANGED:
+ {
+ QAccessibleTextInterface* pTextInterface = pQAccessibleInterface->textInterface();
+ if (!pTextInterface)
+ {
+ SAL_WARN("vcl.qt", "TEXT_SELECTION_CHANGED event received for object not "
+ "implementing text interface");
+ return;
+ }
+ int nStartOffset = 0;
+ int nEndOffset = 0;
+ pTextInterface->selection(0, &nStartOffset, &nEndOffset);
+ QAccessible::updateAccessibility(
+ new QAccessibleTextSelectionEvent(pQAccessibleInterface, nStartOffset, nEndOffset));
+ return;
+ }
+ case AccessibleEventId::TEXT_ATTRIBUTE_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::AttributeChanged));
+ return;
+ case AccessibleEventId::TEXT_CHANGED:
+ {
+ TextSegment aDeletedText;
+ TextSegment aInsertedText;
+ if (aEvent.OldValue >>= aDeletedText)
+ {
+ QAccessible::updateAccessibility(
+ new QAccessibleTextRemoveEvent(pQAccessibleInterface, aDeletedText.SegmentStart,
+ toQString(aDeletedText.SegmentText)));
+ }
+ if (aEvent.NewValue >>= aInsertedText)
+ {
+ QAccessible::updateAccessibility(new QAccessibleTextInsertEvent(
+ pQAccessibleInterface, aInsertedText.SegmentStart,
+ toQString(aInsertedText.SegmentText)));
+ }
+ return;
+ }
+ case AccessibleEventId::TABLE_CAPTION_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::TableCaptionChanged));
+ return;
+ case AccessibleEventId::TABLE_COLUMN_DESCRIPTION_CHANGED:
+ QAccessible::updateAccessibility(new QAccessibleEvent(
+ pQAccessibleInterface, QAccessible::TableColumnDescriptionChanged));
+ return;
+ case AccessibleEventId::TABLE_COLUMN_HEADER_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::TableColumnHeaderChanged));
+ return;
+ case AccessibleEventId::TABLE_MODEL_CHANGED:
+ {
+ AccessibleTableModelChange aChange;
+ aEvent.NewValue >>= aChange;
+
+ QAccessibleTableModelChangeEvent::ModelChangeType nType;
+ switch (aChange.Type)
+ {
+ case AccessibleTableModelChangeType::COLUMNS_INSERTED:
+ nType = QAccessibleTableModelChangeEvent::ColumnsInserted;
+ break;
+ case AccessibleTableModelChangeType::COLUMNS_REMOVED:
+ nType = QAccessibleTableModelChangeEvent::ColumnsRemoved;
+ break;
+ case AccessibleTableModelChangeType::ROWS_INSERTED:
+ nType = QAccessibleTableModelChangeEvent::RowsInserted;
+ break;
+ case AccessibleTableModelChangeType::ROWS_REMOVED:
+ nType = QAccessibleTableModelChangeEvent::RowsRemoved;
+ break;
+ case AccessibleTableModelChangeType::UPDATE:
+ nType = QAccessibleTableModelChangeEvent::DataChanged;
+ break;
+ default:
+ assert(false && "Unhandled AccessibleTableModelChangeType");
+ return;
+ }
+ QAccessibleTableModelChangeEvent* pTableEvent
+ = new QAccessibleTableModelChangeEvent(pQAccessibleInterface, nType);
+ pTableEvent->setFirstRow(aChange.FirstRow);
+ pTableEvent->setLastRow(aChange.LastRow);
+ pTableEvent->setFirstColumn(aChange.FirstColumn);
+ pTableEvent->setLastColumn(aChange.LastColumn);
+ QAccessible::updateAccessibility(pTableEvent);
+ return;
+ }
+ case AccessibleEventId::TABLE_ROW_DESCRIPTION_CHANGED:
+ QAccessible::updateAccessibility(new QAccessibleEvent(
+ pQAccessibleInterface, QAccessible::TableRowDescriptionChanged));
+ return;
+ case AccessibleEventId::TABLE_ROW_HEADER_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::TableRowHeaderChanged));
+ return;
+ case AccessibleEventId::TABLE_SUMMARY_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::TableSummaryChanged));
+ return;
+ case AccessibleEventId::SELECTION_CHANGED_ADD:
+ case AccessibleEventId::SELECTION_CHANGED_REMOVE:
+ {
+ QAccessible::Event eEventType;
+ if (aEvent.EventId == AccessibleEventId::SELECTION_CHANGED_ADD)
+ eEventType = QAccessible::SelectionAdd;
+ else
+ eEventType = QAccessible::SelectionRemove;
+
+ uno::Reference<accessibility::XAccessible> xChildAcc;
+ aEvent.NewValue >>= xChildAcc;
+ if (!xChildAcc.is())
+ {
+ SAL_WARN("vcl.qt",
+ "Selection add/remove event without the (un)selected accessible set");
+ return;
+ }
+ Reference<XAccessibleContext> xContext = xChildAcc->getAccessibleContext();
+ if (!xContext.is())
+ {
+ SAL_WARN("vcl.qt", "No valid XAccessibleContext for (un)selected accessible.");
+ return;
+ }
+
+ // Qt expects the event to be sent for the (un)selected child
+ QObject* pChildObject = QtAccessibleRegistry::getQObject(xChildAcc);
+ assert(pChildObject);
+ QAccessible::updateAccessibility(new QAccessibleEvent(pChildObject, eEventType));
+ return;
+ }
+ case AccessibleEventId::SELECTION_CHANGED_WITHIN:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::SelectionWithin));
+ return;
+ case AccessibleEventId::PAGE_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::PageChanged));
+ return;
+ case AccessibleEventId::SECTION_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::SectionChanged));
+ return;
+ case AccessibleEventId::COLUMN_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::TextColumnChanged));
+ return;
+ case AccessibleEventId::BOUNDRECT_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::LocationChanged));
+ return;
+ case AccessibleEventId::STATE_CHANGED:
+ HandleStateChangedEvent(pQAccessibleInterface, aEvent);
+ return;
+ case AccessibleEventId::VALUE_CHANGED:
+ {
+ QAccessibleValueInterface* pValueInterface = pQAccessibleInterface->valueInterface();
+ if (pValueInterface)
+ {
+ const QVariant aValue = pValueInterface->currentValue();
+ QAccessible::updateAccessibility(
+ new QAccessibleValueChangeEvent(pQAccessibleInterface, aValue));
+ }
+ return;
+ }
+ case AccessibleEventId::ROLE_CHANGED:
+ case AccessibleEventId::INVALIDATE_ALL_CHILDREN:
+ case AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED:
+ case AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED:
+ case AccessibleEventId::CONTROLLED_BY_RELATION_CHANGED:
+ case AccessibleEventId::CONTROLLER_FOR_RELATION_CHANGED:
+ case AccessibleEventId::LABEL_FOR_RELATION_CHANGED:
+ case AccessibleEventId::LABELED_BY_RELATION_CHANGED:
+ case AccessibleEventId::MEMBER_OF_RELATION_CHANGED:
+ case AccessibleEventId::SUB_WINDOW_OF_RELATION_CHANGED:
+ case AccessibleEventId::LISTBOX_ENTRY_EXPANDED:
+ case AccessibleEventId::LISTBOX_ENTRY_COLLAPSED:
+ case AccessibleEventId::ACTIVE_DESCENDANT_CHANGED_NOFOCUS:
+ default:
+ SAL_WARN("vcl.qt", "Unmapped AccessibleEventId: " << aEvent.EventId);
+ return;
+ }
+}
+
+void QtAccessibleEventListener::disposing(const EventObject& /* Source */)
+{
+ assert(m_pAccessibleWidget);
+ m_pAccessibleWidget->invalidate();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtAccessibleRegistry.cxx b/vcl/qt5/QtAccessibleRegistry.cxx
new file mode 100644
index 0000000000..88f9abcfd1
--- /dev/null
+++ b/vcl/qt5/QtAccessibleRegistry.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 <QtAccessibleRegistry.hxx>
+#include <QtXAccessible.hxx>
+
+#include <cassert>
+
+std::map<XAccessible*, QObject*> QtAccessibleRegistry::m_aMapping = {};
+
+QObject* QtAccessibleRegistry::getQObject(css::uno::Reference<XAccessible> xAcc)
+{
+ if (!xAcc.is())
+ return nullptr;
+
+ // look for existing entry in the map
+ auto entry = m_aMapping.find(xAcc.get());
+ if (entry != m_aMapping.end())
+ return entry->second;
+
+ // create a new object and remember it in the map
+ QtXAccessible* pQtAcc = new QtXAccessible(xAcc);
+ m_aMapping.emplace(xAcc.get(), pQtAcc);
+ return pQtAcc;
+}
+
+void QtAccessibleRegistry::insert(css::uno::Reference<XAccessible> xAcc, QObject* pQObject)
+{
+ assert(pQObject);
+ m_aMapping.emplace(xAcc.get(), pQObject);
+}
+
+void QtAccessibleRegistry::remove(css::uno::Reference<XAccessible> xAcc)
+{
+ assert(xAcc.is());
+ m_aMapping.erase(xAcc.get());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtAccessibleWidget.cxx b/vcl/qt5/QtAccessibleWidget.cxx
new file mode 100644
index 0000000000..7eadc33138
--- /dev/null
+++ b/vcl/qt5/QtAccessibleWidget.cxx
@@ -0,0 +1,1848 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <QtAccessibleWidget.hxx>
+
+#include <QtGui/QAccessibleInterface>
+
+#include <QtAccessibleEventListener.hxx>
+#include <QtAccessibleRegistry.hxx>
+#include <QtFrame.hxx>
+#include <QtTools.hxx>
+#include <QtWidget.hxx>
+#include <QtXAccessible.hxx>
+
+#include <com/sun/star/accessibility/AccessibleRelationType.hpp>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/accessibility/AccessibleScrollType.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/AccessibleTextType.hpp>
+#include <com/sun/star/accessibility/XAccessible.hpp>
+#include <com/sun/star/accessibility/XAccessibleAction.hpp>
+#include <com/sun/star/accessibility/XAccessibleComponent.hpp>
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
+#include <com/sun/star/accessibility/XAccessibleEventListener.hpp>
+#include <com/sun/star/accessibility/XAccessibleKeyBinding.hpp>
+#include <com/sun/star/accessibility/XAccessibleRelationSet.hpp>
+#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
+#include <com/sun/star/accessibility/XAccessibleTable.hpp>
+#include <com/sun/star/accessibility/XAccessibleTableSelection.hpp>
+#include <com/sun/star/accessibility/XAccessibleText.hpp>
+#include <com/sun/star/accessibility/XAccessibleValue.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+
+#include <comphelper/AccessibleImplementationHelper.hxx>
+#include <o3tl/any.hxx>
+#include <sal/log.hxx>
+#include <vcl/accessibility/AccessibleTextAttributeHelper.hxx>
+
+using namespace css;
+using namespace css::accessibility;
+using namespace css::beans;
+using namespace css::uno;
+
+QtAccessibleWidget::QtAccessibleWidget(const Reference<XAccessible> xAccessible, QObject* pObject)
+ : m_xAccessible(xAccessible)
+ , m_pObject(pObject)
+{
+ Reference<XAccessibleContext> xContext = xAccessible->getAccessibleContext();
+ Reference<XAccessibleEventBroadcaster> xBroadcaster(xContext, UNO_QUERY);
+ if (xBroadcaster.is())
+ {
+ Reference<XAccessibleEventListener> xListener(new QtAccessibleEventListener(this));
+ xBroadcaster->addAccessibleEventListener(xListener);
+ }
+}
+
+void QtAccessibleWidget::invalidate()
+{
+ QtAccessibleRegistry::remove(m_xAccessible);
+ m_xAccessible.clear();
+}
+
+Reference<XAccessibleContext> QtAccessibleWidget::getAccessibleContextImpl() const
+{
+ Reference<XAccessibleContext> xAc;
+
+ if (m_xAccessible.is())
+ {
+ try
+ {
+ xAc = m_xAccessible->getAccessibleContext();
+ }
+ catch (css::lang::DisposedException /*ex*/)
+ {
+ SAL_WARN("vcl.qt", "Accessible context disposed already");
+ }
+ // sometimes getAccessibleContext throws also RuntimeException if context is no longer alive
+ catch (css::uno::RuntimeException /*ex*/)
+ {
+ // so let's catch it here, cuz otherwise soffice falls flat on its face
+ // with FatalError and nothing else
+ SAL_WARN("vcl.qt", "Accessible context no longer alive");
+ }
+ }
+
+ return xAc;
+}
+
+css::uno::Reference<css::accessibility::XAccessibleTable>
+QtAccessibleWidget::getAccessibleTableForParent() const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return nullptr;
+
+ Reference<XAccessible> xParent = xAcc->getAccessibleParent();
+ if (!xParent.is())
+ return nullptr;
+
+ Reference<XAccessibleContext> xParentContext = xParent->getAccessibleContext();
+ if (!xParentContext.is())
+ return nullptr;
+
+ return Reference<XAccessibleTable>(xParentContext, UNO_QUERY);
+}
+
+QWindow* QtAccessibleWidget::window() const
+{
+ assert(m_pObject);
+ if (m_pObject->isWidgetType())
+ {
+ QWidget* pWidget = static_cast<QWidget*>(m_pObject);
+ QWidget* pWindow = pWidget->window();
+ if (pWindow)
+ return pWindow->windowHandle();
+ }
+
+ QAccessibleInterface* pParent = parent();
+ if (pParent)
+ return pParent->window();
+
+ return nullptr;
+}
+
+int QtAccessibleWidget::childCount() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return 0;
+
+ sal_Int64 nChildCount = xAc->getAccessibleChildCount();
+ if (nChildCount > std::numeric_limits<int>::max())
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::childCount: Child count exceeds maximum int value, "
+ "returning max int.");
+ nChildCount = std::numeric_limits<int>::max();
+ }
+
+ return nChildCount;
+}
+
+int QtAccessibleWidget::indexOfChild(const QAccessibleInterface* pChild) const
+{
+ const QtAccessibleWidget* pAccessibleWidget = dynamic_cast<const QtAccessibleWidget*>(pChild);
+ if (!pAccessibleWidget)
+ {
+ SAL_WARN(
+ "vcl.qt",
+ "QtAccessibleWidget::indexOfChild called with child that is no QtAccessibleWidget");
+ return -1;
+ }
+
+ Reference<XAccessibleContext> xContext = pAccessibleWidget->getAccessibleContextImpl();
+ if (!xContext.is())
+ return -1;
+
+ sal_Int64 nChildIndex = xContext->getAccessibleIndexInParent();
+ if (nChildIndex > std::numeric_limits<int>::max())
+ {
+ // use -2 when the child index is too large to fit into 32 bit to neither use the
+ // valid index of another child nor -1, which would e.g. make the Orca screen reader
+ // interpret the object as being a zombie
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::indexOfChild: Child index exceeds maximum int value, "
+ "returning -2.");
+ nChildIndex = -2;
+ }
+ return nChildIndex;
+}
+
+namespace
+{
+sal_Int16 lcl_matchQtTextBoundaryType(QAccessible::TextBoundaryType boundaryType)
+{
+ switch (boundaryType)
+ {
+ case QAccessible::CharBoundary:
+ return com::sun::star::accessibility::AccessibleTextType::CHARACTER;
+ case QAccessible::WordBoundary:
+ return com::sun::star::accessibility::AccessibleTextType::WORD;
+ case QAccessible::SentenceBoundary:
+ return com::sun::star::accessibility::AccessibleTextType::SENTENCE;
+ case QAccessible::ParagraphBoundary:
+ return com::sun::star::accessibility::AccessibleTextType::PARAGRAPH;
+ case QAccessible::LineBoundary:
+ return com::sun::star::accessibility::AccessibleTextType::LINE;
+ case QAccessible::NoBoundary:
+ // assert here, better handle it directly at call site
+ assert(false
+ && "No match for QAccessible::NoBoundary, handle it separately at call site.");
+ break;
+ default:
+ break;
+ }
+
+ SAL_WARN("vcl.qt", "Unmatched text boundary type: " << boundaryType);
+ return -1;
+}
+
+QAccessible::Relation lcl_matchUnoRelation(short relationType)
+{
+ // Qt semantics is the other way around
+ switch (relationType)
+ {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
+ case AccessibleRelationType::CONTENT_FLOWS_FROM:
+ return QAccessible::FlowsTo;
+ case AccessibleRelationType::CONTENT_FLOWS_TO:
+ return QAccessible::FlowsFrom;
+#endif
+ case AccessibleRelationType::CONTROLLED_BY:
+ return QAccessible::Controller;
+ case AccessibleRelationType::CONTROLLER_FOR:
+ return QAccessible::Controlled;
+#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
+ case AccessibleRelationType::DESCRIBED_BY:
+ return QAccessible::DescriptionFor;
+#endif
+ case AccessibleRelationType::LABELED_BY:
+ return QAccessible::Label;
+ case AccessibleRelationType::LABEL_FOR:
+ return QAccessible::Labelled;
+ case AccessibleRelationType::INVALID:
+ case AccessibleRelationType::MEMBER_OF:
+ case AccessibleRelationType::SUB_WINDOW_OF:
+ case AccessibleRelationType::NODE_CHILD_OF:
+ default:
+ SAL_WARN("vcl.qt", "Unmatched relation: " << relationType);
+ return {};
+ }
+}
+
+void lcl_appendRelation(QVector<QPair<QAccessibleInterface*, QAccessible::Relation>>* relations,
+ AccessibleRelation aRelation, QAccessible::Relation match)
+{
+ QAccessible::Relation aQRelation = lcl_matchUnoRelation(aRelation.RelationType);
+ // skip in case there's no Qt relation matching the filter
+ if (!(aQRelation & match))
+ return;
+
+ sal_uInt32 nTargetCount = aRelation.TargetSet.getLength();
+
+ for (sal_uInt32 i = 0; i < nTargetCount; i++)
+ {
+ Reference<XAccessible> xAccessible(aRelation.TargetSet[i], uno::UNO_QUERY);
+ relations->append(
+ { QAccessible::queryAccessibleInterface(QtAccessibleRegistry::getQObject(xAccessible)),
+ aQRelation });
+ }
+}
+}
+
+QVector<QPair<QAccessibleInterface*, QAccessible::Relation>>
+QtAccessibleWidget::relations(QAccessible::Relation match) const
+{
+ QVector<QPair<QAccessibleInterface*, QAccessible::Relation>> relations;
+
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return relations;
+
+ Reference<XAccessibleRelationSet> xRelationSet = xAc->getAccessibleRelationSet();
+ if (xRelationSet.is())
+ {
+ int count = xRelationSet->getRelationCount();
+ for (int i = 0; i < count; i++)
+ {
+ AccessibleRelation aRelation = xRelationSet->getRelation(i);
+ lcl_appendRelation(&relations, aRelation, match);
+ }
+ }
+
+ return relations;
+}
+
+QAccessibleInterface* QtAccessibleWidget::focusChild() const
+{
+ /* if (m_pWindow->HasChildPathFocus())
+ return QAccessible::queryAccessibleInterface(
+ new QtXAccessible(m_xAccessible->getAccessibleContext()->getAccessibleChild(index))); */
+ return QAccessible::queryAccessibleInterface(object());
+}
+
+QRect QtAccessibleWidget::rect() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QRect();
+
+ Reference<XAccessibleComponent> xAccessibleComponent(xAc, UNO_QUERY);
+ awt::Point aPoint = xAccessibleComponent->getLocationOnScreen();
+ awt::Size aSize = xAccessibleComponent->getSize();
+
+ return QRect(aPoint.X, aPoint.Y, aSize.Width, aSize.Height);
+}
+
+QAccessibleInterface* QtAccessibleWidget::parent() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ if (xAc->getAccessibleParent().is())
+ return QAccessible::queryAccessibleInterface(
+ QtAccessibleRegistry::getQObject(xAc->getAccessibleParent()));
+
+ // go via the QObject hierarchy; some a11y objects like the application
+ // (at the root of the a11y hierarchy) are handled solely by Qt and have
+ // no LO-internal a11y objects associated with them
+ QObject* pObj = object();
+ if (pObj && pObj->parent())
+ return QAccessible::queryAccessibleInterface(pObj->parent());
+
+ // return app as parent for top-level objects
+ return QAccessible::queryAccessibleInterface(qApp);
+}
+
+QAccessibleInterface* QtAccessibleWidget::child(int index) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ if (index < 0 || index >= xAc->getAccessibleChildCount())
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::child called with invalid index: " << index);
+ return nullptr;
+ }
+
+ return QAccessible::queryAccessibleInterface(
+ QtAccessibleRegistry::getQObject(xAc->getAccessibleChild(index)));
+}
+
+QString QtAccessibleWidget::text(QAccessible::Text text) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QString();
+
+ switch (text)
+ {
+ case QAccessible::Name:
+ return toQString(xAc->getAccessibleName());
+ case QAccessible::Description:
+ case QAccessible::DebugDescription:
+ return toQString(xAc->getAccessibleDescription());
+ case QAccessible::Value:
+ case QAccessible::Help:
+ case QAccessible::Accelerator:
+ case QAccessible::UserText:
+ default:
+ return QString("Unknown");
+ }
+}
+QAccessible::Role QtAccessibleWidget::role() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QAccessible::NoRole;
+
+ switch (xAc->getAccessibleRole())
+ {
+ case AccessibleRole::UNKNOWN:
+ return QAccessible::NoRole;
+ case AccessibleRole::ALERT:
+ return QAccessible::AlertMessage;
+ case AccessibleRole::COLUMN_HEADER:
+ return QAccessible::ColumnHeader;
+ case AccessibleRole::CANVAS:
+ return QAccessible::Canvas;
+ case AccessibleRole::CHECK_BOX:
+ return QAccessible::CheckBox;
+ case AccessibleRole::CHECK_MENU_ITEM:
+ return QAccessible::MenuItem;
+ case AccessibleRole::COLOR_CHOOSER:
+ return QAccessible::ColorChooser;
+ case AccessibleRole::COMBO_BOX:
+ return QAccessible::ComboBox;
+ case AccessibleRole::DATE_EDITOR:
+ return QAccessible::EditableText;
+ case AccessibleRole::DESKTOP_ICON:
+ return QAccessible::Graphic;
+ case AccessibleRole::DESKTOP_PANE:
+ case AccessibleRole::DIRECTORY_PANE:
+ return QAccessible::Pane;
+ case AccessibleRole::DIALOG:
+ return QAccessible::Dialog;
+ case AccessibleRole::DOCUMENT:
+ return QAccessible::Document;
+ case AccessibleRole::EMBEDDED_OBJECT:
+ return QAccessible::UserRole;
+ case AccessibleRole::END_NOTE:
+ return QAccessible::Note;
+ case AccessibleRole::FILE_CHOOSER:
+ return QAccessible::Dialog;
+ case AccessibleRole::FILLER:
+ return QAccessible::Whitespace;
+ case AccessibleRole::FONT_CHOOSER:
+ return QAccessible::UserRole;
+ case AccessibleRole::FOOTER:
+ return QAccessible::Footer;
+ case AccessibleRole::FOOTNOTE:
+ return QAccessible::Note;
+ case AccessibleRole::FRAME: // top-level window with title bar
+ return QAccessible::Window;
+ case AccessibleRole::GLASS_PANE:
+ return QAccessible::UserRole;
+ case AccessibleRole::GRAPHIC:
+ return QAccessible::Graphic;
+ case AccessibleRole::GROUP_BOX:
+ return QAccessible::Grouping;
+ case AccessibleRole::HEADER:
+ return QAccessible::UserRole;
+ case AccessibleRole::HEADING:
+ return QAccessible::Heading;
+ case AccessibleRole::HYPER_LINK:
+ return QAccessible::Link;
+ case AccessibleRole::ICON:
+ return QAccessible::Graphic;
+ case AccessibleRole::INTERNAL_FRAME:
+ return QAccessible::UserRole;
+ case AccessibleRole::LABEL:
+ return QAccessible::StaticText;
+ case AccessibleRole::LAYERED_PANE:
+ return QAccessible::Pane;
+ case AccessibleRole::LIST:
+ return QAccessible::List;
+ case AccessibleRole::LIST_ITEM:
+ return QAccessible::ListItem;
+ case AccessibleRole::MENU:
+ case AccessibleRole::MENU_BAR:
+ return QAccessible::MenuBar;
+ case AccessibleRole::MENU_ITEM:
+ return QAccessible::MenuItem;
+ case AccessibleRole::NOTIFICATION:
+ return QAccessible::Notification;
+ case AccessibleRole::OPTION_PANE:
+ return QAccessible::Pane;
+ case AccessibleRole::PAGE_TAB:
+ return QAccessible::PageTab;
+ case AccessibleRole::PAGE_TAB_LIST:
+ return QAccessible::PageTabList;
+ case AccessibleRole::PANEL:
+ return QAccessible::Pane;
+ case AccessibleRole::PARAGRAPH:
+ case AccessibleRole::BLOCK_QUOTE:
+ return QAccessible::Paragraph;
+ case AccessibleRole::PASSWORD_TEXT:
+ // Qt API doesn't have a separate role to distinguish password edits,
+ // but a 'passwordEdit' state
+ return QAccessible::EditableText;
+ case AccessibleRole::POPUP_MENU:
+ return QAccessible::PopupMenu;
+ case AccessibleRole::PUSH_BUTTON:
+ return QAccessible::Button;
+ case AccessibleRole::PROGRESS_BAR:
+ return QAccessible::ProgressBar;
+ case AccessibleRole::RADIO_BUTTON:
+ return QAccessible::RadioButton;
+ case AccessibleRole::RADIO_MENU_ITEM:
+ return QAccessible::MenuItem;
+ case AccessibleRole::ROW_HEADER:
+ return QAccessible::RowHeader;
+ case AccessibleRole::ROOT_PANE:
+ return QAccessible::Pane;
+ case AccessibleRole::SCROLL_BAR:
+ return QAccessible::ScrollBar;
+ case AccessibleRole::SCROLL_PANE:
+ return QAccessible::Pane;
+ case AccessibleRole::SHAPE:
+ return QAccessible::Graphic;
+ case AccessibleRole::SEPARATOR:
+ return QAccessible::Separator;
+ case AccessibleRole::SLIDER:
+ return QAccessible::Slider;
+ case AccessibleRole::SPIN_BOX:
+ return QAccessible::SpinBox;
+ case AccessibleRole::SPLIT_PANE:
+ return QAccessible::Pane;
+ case AccessibleRole::STATUS_BAR:
+ return QAccessible::StatusBar;
+ case AccessibleRole::TABLE:
+ return QAccessible::Table;
+ case AccessibleRole::TABLE_CELL:
+ return QAccessible::Cell;
+ case AccessibleRole::TEXT:
+ return QAccessible::EditableText;
+ case AccessibleRole::TEXT_FRAME:
+ return QAccessible::UserRole;
+ case AccessibleRole::TOGGLE_BUTTON:
+ return QAccessible::Button;
+ case AccessibleRole::TOOL_BAR:
+ return QAccessible::ToolBar;
+ case AccessibleRole::TOOL_TIP:
+ return QAccessible::ToolTip;
+ case AccessibleRole::TREE:
+ return QAccessible::Tree;
+ case AccessibleRole::VIEW_PORT:
+ return QAccessible::UserRole;
+ case AccessibleRole::BUTTON_DROPDOWN:
+ return QAccessible::ButtonDropDown;
+ case AccessibleRole::BUTTON_MENU:
+ return QAccessible::ButtonMenu;
+ case AccessibleRole::CAPTION:
+ return QAccessible::StaticText;
+ case AccessibleRole::CHART:
+ return QAccessible::Chart;
+ case AccessibleRole::EDIT_BAR:
+ return QAccessible::Equation;
+ case AccessibleRole::FORM:
+ return QAccessible::Form;
+ case AccessibleRole::IMAGE_MAP:
+ return QAccessible::Graphic;
+ case AccessibleRole::NOTE:
+ return QAccessible::Note;
+ case AccessibleRole::RULER:
+ return QAccessible::UserRole;
+ case AccessibleRole::SECTION:
+ return QAccessible::Section;
+ case AccessibleRole::TREE_ITEM:
+ return QAccessible::TreeItem;
+ case AccessibleRole::TREE_TABLE:
+ return QAccessible::Tree;
+ case AccessibleRole::COMMENT:
+ return QAccessible::Note;
+ case AccessibleRole::COMMENT_END:
+ return QAccessible::UserRole;
+ case AccessibleRole::DOCUMENT_PRESENTATION:
+ return QAccessible::Document;
+ case AccessibleRole::DOCUMENT_SPREADSHEET:
+ return QAccessible::Document;
+ case AccessibleRole::DOCUMENT_TEXT:
+ return QAccessible::Document;
+ case AccessibleRole::STATIC:
+ return QAccessible::StaticText;
+ case AccessibleRole::WINDOW: // top-level window without title bar
+ return QAccessible::Window;
+ }
+
+ SAL_WARN("vcl.qt", "Unmapped role: " << getAccessibleContextImpl()->getAccessibleRole());
+ return QAccessible::NoRole;
+}
+
+namespace
+{
+void lcl_addState(QAccessible::State* state, sal_Int64 nState)
+{
+ switch (nState)
+ {
+ case AccessibleStateType::INVALID:
+ state->invalid = true;
+ break;
+ case AccessibleStateType::ACTIVE:
+ state->active = true;
+ break;
+ case AccessibleStateType::ARMED:
+ // No match
+ break;
+ case AccessibleStateType::BUSY:
+ state->busy = true;
+ break;
+ case AccessibleStateType::CHECKABLE:
+ state->checkable = true;
+ break;
+ case AccessibleStateType::CHECKED:
+ state->checked = true;
+ break;
+ case AccessibleStateType::EDITABLE:
+ state->editable = true;
+ break;
+ case AccessibleStateType::ENABLED:
+ state->disabled = false;
+ break;
+ case AccessibleStateType::EXPANDABLE:
+ state->expandable = true;
+ break;
+ case AccessibleStateType::EXPANDED:
+ state->expanded = true;
+ break;
+ case AccessibleStateType::FOCUSABLE:
+ state->focusable = true;
+ break;
+ case AccessibleStateType::FOCUSED:
+ state->focused = true;
+ break;
+ case AccessibleStateType::HORIZONTAL:
+ // No match
+ break;
+ case AccessibleStateType::ICONIFIED:
+ // No match
+ break;
+ case AccessibleStateType::INDETERMINATE:
+ state->checkStateMixed = true;
+ break;
+ case AccessibleStateType::MANAGES_DESCENDANTS:
+ // No match
+ break;
+ case AccessibleStateType::MODAL:
+ state->modal = true;
+ break;
+ case AccessibleStateType::MOVEABLE:
+ state->movable = true;
+ break;
+ case AccessibleStateType::MULTI_LINE:
+ state->multiLine = true;
+ break;
+ case AccessibleStateType::OPAQUE:
+ // No match
+ break;
+ case AccessibleStateType::PRESSED:
+ state->pressed = true;
+ break;
+ case AccessibleStateType::RESIZABLE:
+ state->sizeable = true;
+ break;
+ case AccessibleStateType::SELECTABLE:
+ state->selectable = true;
+ break;
+ case AccessibleStateType::SELECTED:
+ state->selected = true;
+ break;
+ case AccessibleStateType::SENSITIVE:
+ // No match
+ break;
+ case AccessibleStateType::SHOWING:
+ // No match
+ break;
+ case AccessibleStateType::SINGLE_LINE:
+ // No match
+ break;
+ case AccessibleStateType::STALE:
+ // No match
+ break;
+ case AccessibleStateType::TRANSIENT:
+ // No match
+ break;
+ case AccessibleStateType::VERTICAL:
+ // No match
+ break;
+ case AccessibleStateType::VISIBLE:
+ state->invisible = false;
+ break;
+ case AccessibleStateType::DEFAULT:
+ // No match
+ break;
+ case AccessibleStateType::DEFUNC:
+ state->invalid = true;
+ break;
+ case AccessibleStateType::MULTI_SELECTABLE:
+ state->multiSelectable = true;
+ break;
+ default:
+ SAL_WARN("vcl.qt", "Unmapped state: " << nState);
+ break;
+ }
+}
+}
+
+QAccessible::State QtAccessibleWidget::state() const
+{
+ QAccessible::State state;
+
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return state;
+
+ sal_Int64 nStateSet(xAc->getAccessibleStateSet());
+
+ for (int i = 0; i < 63; ++i)
+ {
+ sal_Int64 nState = sal_Int64(1) << i;
+ if (nStateSet & nState)
+ lcl_addState(&state, nState);
+ }
+
+ if (xAc->getAccessibleRole() == AccessibleRole::PASSWORD_TEXT)
+ state.passwordEdit = true;
+
+ return state;
+}
+
+QColor QtAccessibleWidget::foregroundColor() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QColor();
+
+ Reference<XAccessibleComponent> xAccessibleComponent(xAc, UNO_QUERY);
+ return toQColor(Color(ColorTransparency, xAccessibleComponent->getForeground()));
+}
+
+QColor QtAccessibleWidget::backgroundColor() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QColor();
+
+ Reference<XAccessibleComponent> xAccessibleComponent(xAc, UNO_QUERY);
+ return toQColor(Color(ColorTransparency, xAccessibleComponent->getBackground()));
+}
+
+void* QtAccessibleWidget::interface_cast(QAccessible::InterfaceType t)
+{
+ if (t == QAccessible::ActionInterface && accessibleProvidesInterface<XAccessibleAction>())
+ return static_cast<QAccessibleActionInterface*>(this);
+ if (t == QAccessible::TextInterface && accessibleProvidesInterface<XAccessibleText>())
+ return static_cast<QAccessibleTextInterface*>(this);
+ if (t == QAccessible::EditableTextInterface
+ && accessibleProvidesInterface<XAccessibleEditableText>())
+ return static_cast<QAccessibleEditableTextInterface*>(this);
+ if (t == QAccessible::ValueInterface && accessibleProvidesInterface<XAccessibleValue>())
+ return static_cast<QAccessibleValueInterface*>(this);
+ if (t == QAccessible::TableCellInterface)
+ {
+ // parent must be a table
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (xTable.is())
+ return static_cast<QAccessibleTableCellInterface*>(this);
+ }
+ if (t == QAccessible::TableInterface && accessibleProvidesInterface<XAccessibleTable>())
+ return static_cast<QAccessibleTableInterface*>(this);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+ if (t == QAccessible::SelectionInterface && accessibleProvidesInterface<XAccessibleSelection>())
+ return static_cast<QAccessibleSelectionInterface*>(this);
+#endif
+ return nullptr;
+}
+
+bool QtAccessibleWidget::isValid() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ return xAc.is();
+}
+
+QObject* QtAccessibleWidget::object() const { return m_pObject; }
+
+void QtAccessibleWidget::setText(QAccessible::Text /* t */, const QString& /* text */) {}
+
+QAccessibleInterface* QtAccessibleWidget::childAt(int x, int y) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ Reference<XAccessibleComponent> xAccessibleComponent(xAc, UNO_QUERY);
+ // convert from screen to local coordinates
+ QPoint aLocalCoords = QPoint(x, y) - rect().topLeft();
+ return QAccessible::queryAccessibleInterface(
+ QtAccessibleRegistry::getQObject(xAccessibleComponent->getAccessibleAtPoint(
+ awt::Point(aLocalCoords.x(), aLocalCoords.y()))));
+}
+
+QAccessibleInterface* QtAccessibleWidget::customFactory(const QString& classname, QObject* object)
+{
+ if (classname == QLatin1String("QtWidget") && object && object->isWidgetType())
+ {
+ QtWidget* pWidget = static_cast<QtWidget*>(object);
+ vcl::Window* pWindow = pWidget->frame().GetWindow();
+
+ if (pWindow)
+ {
+ css::uno::Reference<XAccessible> xAcc = pWindow->GetAccessible();
+ // insert into registry so the association between the XAccessible and the QtWidget
+ // is remembered rather than creating a different QtXAccessible when a QObject is needed later
+ QtAccessibleRegistry::insert(xAcc, object);
+ return new QtAccessibleWidget(xAcc, object);
+ }
+ }
+ if (classname == QLatin1String("QtXAccessible") && object)
+ {
+ QtXAccessible* pXAccessible = static_cast<QtXAccessible*>(object);
+ if (pXAccessible->m_xAccessible.is())
+ {
+ QtAccessibleWidget* pRet = new QtAccessibleWidget(pXAccessible->m_xAccessible, object);
+ // clear the reference in the QtXAccessible, no longer needed now that the QtAccessibleWidget holds one
+ pXAccessible->m_xAccessible.clear();
+ return pRet;
+ }
+ }
+
+ return nullptr;
+}
+
+// QAccessibleActionInterface
+QStringList QtAccessibleWidget::actionNames() const
+{
+ QStringList actionNames;
+ Reference<XAccessibleAction> xAccessibleAction(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xAccessibleAction.is())
+ return actionNames;
+
+ int count = xAccessibleAction->getAccessibleActionCount();
+ for (int i = 0; i < count; i++)
+ {
+ OUString desc = xAccessibleAction->getAccessibleActionDescription(i);
+ actionNames.append(toQString(desc));
+ }
+ return actionNames;
+}
+
+void QtAccessibleWidget::doAction(const QString& actionName)
+{
+ Reference<XAccessibleAction> xAccessibleAction(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xAccessibleAction.is())
+ return;
+
+ int index = actionNames().indexOf(actionName);
+ if (index == -1)
+ return;
+ xAccessibleAction->doAccessibleAction(index);
+}
+
+QStringList QtAccessibleWidget::keyBindingsForAction(const QString& actionName) const
+{
+ QStringList keyBindings;
+ Reference<XAccessibleAction> xAccessibleAction(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xAccessibleAction.is())
+ return keyBindings;
+
+ int index = actionNames().indexOf(actionName);
+ if (index == -1)
+ return keyBindings;
+
+ Reference<XAccessibleKeyBinding> xKeyBinding
+ = xAccessibleAction->getAccessibleActionKeyBinding(index);
+
+ if (!xKeyBinding.is())
+ return keyBindings;
+
+ int count = xKeyBinding->getAccessibleKeyBindingCount();
+ for (int i = 0; i < count; i++)
+ {
+ Sequence<awt::KeyStroke> keyStroke = xKeyBinding->getAccessibleKeyBinding(i);
+ keyBindings.append(toQString(comphelper::GetkeyBindingStrByXkeyBinding(keyStroke)));
+ }
+ return keyBindings;
+}
+
+// QAccessibleTextInterface
+void QtAccessibleWidget::addSelection(int /* startOffset */, int /* endOffset */)
+{
+ SAL_INFO("vcl.qt", "Unsupported QAccessibleTextInterface::addSelection");
+}
+
+// Text attributes are returned in format specified in IAccessible2 spec, since that
+// is what Qt handles:
+// https://wiki.linuxfoundation.org/accessibility/iaccessible2/textattributes
+QString QtAccessibleWidget::attributes(int offset, int* startOffset, int* endOffset) const
+{
+ if (startOffset == nullptr || endOffset == nullptr)
+ return QString();
+
+ *startOffset = -1;
+ *endOffset = -1;
+
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return QString();
+
+ // handle special values for offset the same way base class's QAccessibleTextWidget::attributes does
+ // (as defined in IAccessible 2: -1 -> length, -2 -> cursor position)
+ if (offset == -2)
+ offset = cursorPosition();
+
+ const int nTextLength = characterCount();
+ if (offset == -1 || offset == nTextLength)
+ offset = nTextLength - 1;
+
+ if (offset < 0 || offset > nTextLength)
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::attributes called with invalid offset: " << offset);
+ return QString();
+ }
+
+ // Qt doesn't have the strict separation into text and object attributes, but also
+ // supports text-specific attributes that are object attributes according to the
+ // IAccessible2 spec.
+ sal_Int32 nStart = 0;
+ sal_Int32 nEnd = 0;
+ const OUString aRet = AccessibleTextAttributeHelper::GetIAccessible2TextAttributes(
+ xText, IA2AttributeType::TextAttributes | IA2AttributeType::ObjectAttributes,
+ static_cast<sal_Int32>(offset), nStart, nEnd);
+ *startOffset = static_cast<int>(nStart);
+ *endOffset = static_cast<int>(nEnd);
+ return toQString(aRet);
+}
+
+int QtAccessibleWidget::characterCount() const
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (xText.is())
+ return xText->getCharacterCount();
+ return 0;
+}
+
+QRect QtAccessibleWidget::characterRect(int nOffset) const
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return QRect();
+
+ if (nOffset < 0 || nOffset > xText->getCharacterCount())
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::characterRect called with invalid offset: " << nOffset);
+ return QRect();
+ }
+
+ const awt::Rectangle aBounds = xText->getCharacterBounds(nOffset);
+ const QRect aRect(aBounds.X, aBounds.Y, aBounds.Width, aBounds.Height);
+ // convert to screen coordinates
+ const QRect aScreenPos = rect();
+ return aRect.translated(aScreenPos.x(), aScreenPos.y());
+}
+
+int QtAccessibleWidget::cursorPosition() const
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (xText.is())
+ return xText->getCaretPosition();
+ return 0;
+}
+
+int QtAccessibleWidget::offsetAtPoint(const QPoint& rPoint) const
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return -1;
+
+ // convert from screen to local coordinates
+ QPoint aLocalCoords = rPoint - rect().topLeft();
+ awt::Point aPoint(aLocalCoords.x(), aLocalCoords.y());
+ return xText->getIndexAtPoint(aPoint);
+}
+
+void QtAccessibleWidget::removeSelection(int /* selectionIndex */)
+{
+ SAL_INFO("vcl.qt", "Unsupported QAccessibleTextInterface::removeSelection");
+}
+
+void QtAccessibleWidget::scrollToSubstring(int startIndex, int endIndex)
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return;
+
+ sal_Int32 nTextLength = xText->getCharacterCount();
+ if (startIndex < 0 || startIndex > nTextLength || endIndex < 0 || endIndex > nTextLength)
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::scrollToSubstring called with invalid offset.");
+ return;
+ }
+
+ xText->scrollSubstringTo(startIndex, endIndex, AccessibleScrollType_SCROLL_ANYWHERE);
+}
+
+void QtAccessibleWidget::selection(int selectionIndex, int* startOffset, int* endOffset) const
+{
+ if (!startOffset && !endOffset)
+ return;
+
+ Reference<XAccessibleText> xText;
+ if (selectionIndex == 0)
+ xText = Reference<XAccessibleText>(getAccessibleContextImpl(), UNO_QUERY);
+
+ if (startOffset)
+ *startOffset = xText.is() ? xText->getSelectionStart() : 0;
+ if (endOffset)
+ *endOffset = xText.is() ? xText->getSelectionEnd() : 0;
+}
+
+int QtAccessibleWidget::selectionCount() const
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (xText.is() && !xText->getSelectedText().isEmpty())
+ return 1; // Only 1 selection supported atm
+ return 0;
+}
+
+void QtAccessibleWidget::setCursorPosition(int position)
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return;
+
+ if (position < 0 || position > xText->getCharacterCount())
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::setCursorPosition called with invalid offset: " << position);
+ return;
+ }
+
+ xText->setCaretPosition(position);
+}
+
+void QtAccessibleWidget::setSelection(int /* selectionIndex */, int startOffset, int endOffset)
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return;
+
+ sal_Int32 nTextLength = xText->getCharacterCount();
+ if (startOffset < 0 || startOffset > nTextLength || endOffset < 0 || endOffset > nTextLength)
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::setSelection called with invalid offset.");
+ return;
+ }
+
+ xText->setSelection(startOffset, endOffset);
+}
+
+QString QtAccessibleWidget::text(int startOffset, int endOffset) const
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return QString();
+
+ sal_Int32 nTextLength = xText->getCharacterCount();
+ if (startOffset < 0 || startOffset > nTextLength || endOffset < 0 || endOffset > nTextLength)
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::text called with invalid offset.");
+ return QString();
+ }
+
+ return toQString(xText->getTextRange(startOffset, endOffset));
+}
+
+QString QtAccessibleWidget::textAfterOffset(int nOffset,
+ QAccessible::TextBoundaryType eBoundaryType,
+ int* pStartOffset, int* pEndOffset) const
+{
+ if (pStartOffset == nullptr || pEndOffset == nullptr)
+ return QString();
+
+ *pStartOffset = -1;
+ *pEndOffset = -1;
+
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return QString();
+
+ const int nCharCount = characterCount();
+ // -1 is special value for text length
+ if (nOffset == -1)
+ nOffset = nCharCount;
+ else if (nOffset < -1 || nOffset > nCharCount)
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::textAfterOffset called with invalid offset: " << nOffset);
+ return QString();
+ }
+
+ if (eBoundaryType == QAccessible::NoBoundary)
+ {
+ if (nOffset == nCharCount)
+ return QString();
+ *pStartOffset = nOffset + 1;
+ *pEndOffset = nCharCount;
+ return text(nOffset + 1, nCharCount);
+ }
+
+ sal_Int16 nUnoBoundaryType = lcl_matchQtTextBoundaryType(eBoundaryType);
+ assert(nUnoBoundaryType > 0);
+ const TextSegment aSegment = xText->getTextBehindIndex(nOffset, nUnoBoundaryType);
+ *pStartOffset = aSegment.SegmentStart;
+ *pEndOffset = aSegment.SegmentEnd;
+ return toQString(aSegment.SegmentText);
+}
+
+QString QtAccessibleWidget::textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType,
+ int* startOffset, int* endOffset) const
+{
+ if (startOffset == nullptr || endOffset == nullptr)
+ return QString();
+
+ const int nCharCount = characterCount();
+ if (boundaryType == QAccessible::NoBoundary)
+ {
+ *startOffset = 0;
+ *endOffset = nCharCount;
+ return text(0, nCharCount);
+ }
+
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return QString();
+
+ sal_Int16 nUnoBoundaryType = lcl_matchQtTextBoundaryType(boundaryType);
+ assert(nUnoBoundaryType > 0);
+
+ // special value of -1 for offset means text length
+ if (offset == -1)
+ offset = nCharCount;
+
+ if (offset < 0 || offset > nCharCount)
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::textAtOffset called with invalid offset: " << offset);
+ return QString();
+ }
+
+ const TextSegment segment = xText->getTextAtIndex(offset, nUnoBoundaryType);
+ *startOffset = segment.SegmentStart;
+ *endOffset = segment.SegmentEnd;
+ return toQString(segment.SegmentText);
+}
+
+QString QtAccessibleWidget::textBeforeOffset(int nOffset,
+ QAccessible::TextBoundaryType eBoundaryType,
+ int* pStartOffset, int* pEndOffset) const
+{
+ if (pStartOffset == nullptr || pEndOffset == nullptr)
+ return QString();
+
+ *pStartOffset = -1;
+ *pEndOffset = -1;
+
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return QString();
+
+ const int nCharCount = characterCount();
+ // -1 is special value for text length
+ if (nOffset == -1)
+ nOffset = nCharCount;
+ else if (nOffset < -1 || nOffset > nCharCount)
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::textBeforeOffset called with invalid offset: " << nOffset);
+ return QString();
+ }
+
+ if (eBoundaryType == QAccessible::NoBoundary)
+ {
+ *pStartOffset = 0;
+ *pEndOffset = nOffset;
+ return text(0, nOffset);
+ }
+
+ sal_Int16 nUnoBoundaryType = lcl_matchQtTextBoundaryType(eBoundaryType);
+ assert(nUnoBoundaryType > 0);
+ const TextSegment aSegment = xText->getTextBeforeIndex(nOffset, nUnoBoundaryType);
+ *pStartOffset = aSegment.SegmentStart;
+ *pEndOffset = aSegment.SegmentEnd;
+ return toQString(aSegment.SegmentText);
+}
+
+// QAccessibleEditableTextInterface
+
+void QtAccessibleWidget::deleteText(int startOffset, int endOffset)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return;
+
+ Reference<XAccessibleEditableText> xEditableText(xAc, UNO_QUERY);
+ if (!xEditableText.is())
+ return;
+
+ sal_Int32 nTextLength = xEditableText->getCharacterCount();
+ if (startOffset < 0 || startOffset > nTextLength || endOffset < 0 || endOffset > nTextLength)
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::deleteText called with invalid offset.");
+ return;
+ }
+
+ xEditableText->deleteText(startOffset, endOffset);
+}
+
+void QtAccessibleWidget::insertText(int offset, const QString& text)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return;
+
+ Reference<XAccessibleEditableText> xEditableText(xAc, UNO_QUERY);
+ if (!xEditableText.is())
+ return;
+
+ if (offset < 0 || offset > xEditableText->getCharacterCount())
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::insertText called with invalid offset: " << offset);
+ return;
+ }
+
+ xEditableText->insertText(toOUString(text), offset);
+}
+
+void QtAccessibleWidget::replaceText(int startOffset, int endOffset, const QString& text)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return;
+
+ Reference<XAccessibleEditableText> xEditableText(xAc, UNO_QUERY);
+ if (!xEditableText.is())
+ return;
+
+ sal_Int32 nTextLength = xEditableText->getCharacterCount();
+ if (startOffset < 0 || startOffset > nTextLength || endOffset < 0 || endOffset > nTextLength)
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::replaceText called with invalid offset.");
+ return;
+ }
+
+ xEditableText->replaceText(startOffset, endOffset, toOUString(text));
+}
+
+// QAccessibleValueInterface
+QVariant QtAccessibleWidget::currentValue() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QVariant();
+
+ Reference<XAccessibleValue> xValue(xAc, UNO_QUERY);
+ if (!xValue.is())
+ return QVariant();
+ double aDouble = 0;
+ xValue->getCurrentValue() >>= aDouble;
+ return QVariant(aDouble);
+}
+
+QVariant QtAccessibleWidget::maximumValue() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QVariant();
+
+ Reference<XAccessibleValue> xValue(xAc, UNO_QUERY);
+ if (!xValue.is())
+ return QVariant();
+ double aDouble = 0;
+ xValue->getMaximumValue() >>= aDouble;
+ return QVariant(aDouble);
+}
+
+QVariant QtAccessibleWidget::minimumStepSize() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QVariant();
+
+ Reference<XAccessibleValue> xValue(xAc, UNO_QUERY);
+ if (!xValue.is())
+ return QVariant();
+ double dMinStep = 0;
+ xValue->getMinimumIncrement() >>= dMinStep;
+ return QVariant(dMinStep);
+}
+
+QVariant QtAccessibleWidget::minimumValue() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QVariant();
+
+ Reference<XAccessibleValue> xValue(xAc, UNO_QUERY);
+ if (!xValue.is())
+ return QVariant();
+ double aDouble = 0;
+ xValue->getMinimumValue() >>= aDouble;
+ return QVariant(aDouble);
+}
+
+void QtAccessibleWidget::setCurrentValue(const QVariant& value)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return;
+
+ Reference<XAccessibleValue> xValue(xAc, UNO_QUERY);
+ if (!xValue.is())
+ return;
+
+ // Different types of numerical values for XAccessibleValue are possible.
+ // If current value has an integer type, also use that for the new value, to make
+ // sure underlying implementations expecting that can handle the value properly.
+ const Any aCurrentValue = xValue->getCurrentValue();
+ if (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_LONG)
+ xValue->setCurrentValue(Any(static_cast<sal_Int32>(value.toInt())));
+ else if (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_HYPER)
+ xValue->setCurrentValue(Any(static_cast<sal_Int64>(value.toLongLong())));
+ else
+ xValue->setCurrentValue(Any(value.toDouble()));
+}
+
+// QAccessibleTableInterface
+QAccessibleInterface* QtAccessibleWidget::caption() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return nullptr;
+ return QAccessible::queryAccessibleInterface(
+ QtAccessibleRegistry::getQObject(xTable->getAccessibleCaption()));
+}
+
+QAccessibleInterface* QtAccessibleWidget::cellAt(int row, int column) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return nullptr;
+
+ if (row < 0 || row >= xTable->getAccessibleRowCount() || column < 0
+ || column >= xTable->getAccessibleColumnCount())
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::cellAt called with invalid row/column index ("
+ << row << ", " << column << ")");
+ return nullptr;
+ }
+
+ return QAccessible::queryAccessibleInterface(
+ QtAccessibleRegistry::getQObject(xTable->getAccessibleCellAt(row, column)));
+}
+
+int QtAccessibleWidget::columnCount() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return 0;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return 0;
+ return xTable->getAccessibleColumnCount();
+}
+
+QString QtAccessibleWidget::columnDescription(int column) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QString();
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return QString();
+ return toQString(xTable->getAccessibleColumnDescription(column));
+}
+
+bool QtAccessibleWidget::isColumnSelected(int nColumn) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return false;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return false;
+
+ if (nColumn < 0 || nColumn >= xTable->getAccessibleColumnCount())
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::isColumnSelected called with invalid column index "
+ << nColumn);
+ return false;
+ }
+
+ return xTable->isAccessibleColumnSelected(nColumn);
+}
+
+bool QtAccessibleWidget::isRowSelected(int nRow) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return false;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return false;
+
+ if (nRow < 0 || nRow >= xTable->getAccessibleRowCount())
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::isRowSelected called with invalid row index " << nRow);
+ return false;
+ }
+
+ return xTable->isAccessibleRowSelected(nRow);
+}
+
+void QtAccessibleWidget::modelChange(QAccessibleTableModelChangeEvent*) {}
+
+int QtAccessibleWidget::rowCount() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return 0;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return 0;
+ return xTable->getAccessibleRowCount();
+}
+
+QString QtAccessibleWidget::rowDescription(int row) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QString();
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return QString();
+ return toQString(xTable->getAccessibleRowDescription(row));
+}
+
+bool QtAccessibleWidget::selectColumn(int column)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return false;
+
+ if (column < 0 || column >= columnCount())
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::selectColumn called with invalid column index " << column);
+ return false;
+ }
+
+ Reference<XAccessibleTableSelection> xTableSelection(xAc, UNO_QUERY);
+ if (!xTableSelection.is())
+ return false;
+ return xTableSelection->selectColumn(column);
+}
+
+bool QtAccessibleWidget::selectRow(int row)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return false;
+
+ if (row < 0 || row >= rowCount())
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::selectRow called with invalid row index " << row);
+ return false;
+ }
+
+ Reference<XAccessibleTableSelection> xTableSelection(xAc, UNO_QUERY);
+ if (!xTableSelection.is())
+ return false;
+ return xTableSelection->selectRow(row);
+}
+
+int QtAccessibleWidget::selectedCellCount() const { return selectedItemCount(); }
+
+QList<QAccessibleInterface*> QtAccessibleWidget::selectedCells() const { return selectedItems(); }
+
+int QtAccessibleWidget::selectedColumnCount() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return 0;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return 0;
+ return xTable->getSelectedAccessibleColumns().getLength();
+}
+
+QList<int> QtAccessibleWidget::selectedColumns() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QList<int>();
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return QList<int>();
+ return toQList(xTable->getSelectedAccessibleColumns());
+}
+
+int QtAccessibleWidget::selectedRowCount() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return 0;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return 0;
+ return xTable->getSelectedAccessibleRows().getLength();
+}
+
+QList<int> QtAccessibleWidget::selectedRows() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QList<int>();
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return QList<int>();
+ return toQList(xTable->getSelectedAccessibleRows());
+}
+
+QAccessibleInterface* QtAccessibleWidget::summary() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return nullptr;
+ return QAccessible::queryAccessibleInterface(
+ QtAccessibleRegistry::getQObject(xTable->getAccessibleSummary()));
+}
+
+bool QtAccessibleWidget::unselectColumn(int column)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return false;
+
+ Reference<XAccessibleTableSelection> xTableSelection(xAc, UNO_QUERY);
+ if (!xTableSelection.is())
+ return false;
+ return xTableSelection->unselectColumn(column);
+}
+
+bool QtAccessibleWidget::unselectRow(int row)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return false;
+
+ Reference<XAccessibleTableSelection> xTableSelection(xAc, UNO_QUERY);
+ if (!xTableSelection.is())
+ return false;
+ return xTableSelection->unselectRow(row);
+}
+
+// QAccessibleTableCellInterface
+QList<QAccessibleInterface*> QtAccessibleWidget::columnHeaderCells() const
+{
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (!xTable.is())
+ return QList<QAccessibleInterface*>();
+
+ Reference<XAccessibleTable> xHeaders = xTable->getAccessibleColumnHeaders();
+ if (!xHeaders.is())
+ return QList<QAccessibleInterface*>();
+
+ const sal_Int32 nCol = columnIndex();
+ QList<QAccessibleInterface*> aHeaderCells;
+ for (sal_Int32 nRow = 0; nRow < xHeaders->getAccessibleRowCount(); nRow++)
+ {
+ Reference<XAccessible> xCell = xHeaders->getAccessibleCellAt(nRow, nCol);
+ QAccessibleInterface* pInterface
+ = QAccessible::queryAccessibleInterface(QtAccessibleRegistry::getQObject(xCell));
+ aHeaderCells.push_back(pInterface);
+ }
+ return aHeaderCells;
+}
+
+int QtAccessibleWidget::columnIndex() const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return -1;
+
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (!xTable.is())
+ return -1;
+
+ const sal_Int64 nIndexInParent = xAcc->getAccessibleIndexInParent();
+ return xTable->getAccessibleColumn(nIndexInParent);
+}
+
+bool QtAccessibleWidget::isSelected() const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return false;
+
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (!xTable.is())
+ return false;
+
+ const sal_Int32 nColumn = columnIndex();
+ const sal_Int32 nRow = rowIndex();
+ return xTable->isAccessibleSelected(nRow, nColumn);
+}
+
+int QtAccessibleWidget::columnExtent() const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return -1;
+
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (!xTable.is())
+ return -1;
+
+ const sal_Int32 nColumn = columnIndex();
+ const sal_Int32 nRow = rowIndex();
+ return xTable->getAccessibleColumnExtentAt(nRow, nColumn);
+}
+
+QList<QAccessibleInterface*> QtAccessibleWidget::rowHeaderCells() const
+{
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (!xTable.is())
+ return QList<QAccessibleInterface*>();
+
+ Reference<XAccessibleTable> xHeaders = xTable->getAccessibleRowHeaders();
+ if (!xHeaders.is())
+ return QList<QAccessibleInterface*>();
+
+ const sal_Int32 nRow = rowIndex();
+ QList<QAccessibleInterface*> aHeaderCells;
+ for (sal_Int32 nCol = 0; nCol < xHeaders->getAccessibleColumnCount(); nCol++)
+ {
+ Reference<XAccessible> xCell = xHeaders->getAccessibleCellAt(nRow, nCol);
+ QAccessibleInterface* pInterface
+ = QAccessible::queryAccessibleInterface(QtAccessibleRegistry::getQObject(xCell));
+ aHeaderCells.push_back(pInterface);
+ }
+ return aHeaderCells;
+}
+
+int QtAccessibleWidget::rowExtent() const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return -1;
+
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (!xTable.is())
+ return -1;
+
+ const sal_Int32 nColumn = columnIndex();
+ const sal_Int32 nRow = rowIndex();
+ return xTable->getAccessibleRowExtentAt(nRow, nColumn);
+}
+
+int QtAccessibleWidget::rowIndex() const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return -1;
+
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (!xTable.is())
+ return -1;
+
+ const sal_Int64 nIndexInParent = xAcc->getAccessibleIndexInParent();
+ return xTable->getAccessibleRow(nIndexInParent);
+}
+
+QAccessibleInterface* QtAccessibleWidget::table() const
+{
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (!xTable.is())
+ return nullptr;
+
+ Reference<XAccessible> xTableAcc(xTable, UNO_QUERY);
+ if (!xTableAcc.is())
+ return nullptr;
+
+ return QAccessible::queryAccessibleInterface(QtAccessibleRegistry::getQObject(xTableAcc));
+}
+
+// QAccessibleSelectionInterface
+int QtAccessibleWidget::selectedItemCount() const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return 0;
+
+ Reference<XAccessibleSelection> xSelection(xAcc, UNO_QUERY);
+ if (!xSelection.is())
+ return 0;
+
+ sal_Int64 nSelected = xSelection->getSelectedAccessibleChildCount();
+ if (nSelected > std::numeric_limits<int>::max())
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::selectedItemCount: Cell count exceeds maximum int value, "
+ "using max int.");
+ nSelected = std::numeric_limits<int>::max();
+ }
+ return nSelected;
+}
+
+QList<QAccessibleInterface*> QtAccessibleWidget::selectedItems() const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return QList<QAccessibleInterface*>();
+
+ Reference<XAccessibleSelection> xSelection(xAcc, UNO_QUERY);
+ if (!xSelection.is())
+ return QList<QAccessibleInterface*>();
+
+ QList<QAccessibleInterface*> aSelectedItems;
+ sal_Int64 nSelected = xSelection->getSelectedAccessibleChildCount();
+ if (nSelected > std::numeric_limits<int>::max())
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::selectedItems: Cell count exceeds maximum int value, "
+ "using max int.");
+ nSelected = std::numeric_limits<int>::max();
+ }
+ for (sal_Int64 i = 0; i < nSelected; i++)
+ {
+ Reference<XAccessible> xChild = xSelection->getSelectedAccessibleChild(i);
+ QAccessibleInterface* pInterface
+ = QAccessible::queryAccessibleInterface(QtAccessibleRegistry::getQObject(xChild));
+ aSelectedItems.push_back(pInterface);
+ }
+ return aSelectedItems;
+}
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+QAccessibleInterface* QtAccessibleWidget::selectedItem(int nSelectionIndex) const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return nullptr;
+
+ Reference<XAccessibleSelection> xSelection(xAcc, UNO_QUERY);
+ if (!xSelection.is())
+ return nullptr;
+
+ if (nSelectionIndex < 0 || nSelectionIndex >= xSelection->getSelectedAccessibleChildCount())
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::selectedItem called with invalid index: " << nSelectionIndex);
+ return nullptr;
+ }
+
+ Reference<XAccessible> xChild = xSelection->getSelectedAccessibleChild(nSelectionIndex);
+ if (!xChild)
+ return nullptr;
+
+ return QAccessible::queryAccessibleInterface(QtAccessibleRegistry::getQObject(xChild));
+}
+
+bool QtAccessibleWidget::isSelected(QAccessibleInterface* pItem) const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return false;
+
+ Reference<XAccessibleSelection> xSelection(xAcc, UNO_QUERY);
+ if (!xSelection.is())
+ return false;
+
+ int nChildIndex = indexOfChild(pItem);
+ if (nChildIndex < 0)
+ return false;
+
+ return xSelection->isAccessibleChildSelected(nChildIndex);
+}
+
+bool QtAccessibleWidget::select(QAccessibleInterface* pItem)
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return false;
+
+ Reference<XAccessibleSelection> xSelection(xAcc, UNO_QUERY);
+ if (!xSelection.is())
+ return false;
+
+ int nChildIndex = indexOfChild(pItem);
+ if (nChildIndex < 0)
+ return false;
+
+ xSelection->selectAccessibleChild(nChildIndex);
+ return true;
+}
+
+bool QtAccessibleWidget::unselect(QAccessibleInterface* pItem)
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return false;
+
+ Reference<XAccessibleSelection> xSelection(xAcc, UNO_QUERY);
+ if (!xSelection.is())
+ return false;
+
+ int nChildIndex = indexOfChild(pItem);
+ if (nChildIndex < 0)
+ return false;
+
+ xSelection->deselectAccessibleChild(nChildIndex);
+ return true;
+}
+
+bool QtAccessibleWidget::selectAll()
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return false;
+
+ Reference<XAccessibleSelection> xSelection(xAcc, UNO_QUERY);
+ if (!xSelection.is())
+ return false;
+
+ xSelection->selectAllAccessibleChildren();
+ return true;
+}
+
+bool QtAccessibleWidget::clear()
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return false;
+
+ Reference<XAccessibleSelection> xSelection(xAcc, UNO_QUERY);
+ if (!xSelection.is())
+ return false;
+
+ xSelection->clearAccessibleSelection();
+ return true;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtBitmap.cxx b/vcl/qt5/QtBitmap.cxx
new file mode 100644
index 0000000000..dd83c57c23
--- /dev/null
+++ b/vcl/qt5/QtBitmap.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 <QtBitmap.hxx>
+#include <QtTools.hxx>
+#include <QtGraphics.hxx>
+
+#include <QtGui/QImage>
+#include <QtCore/QVector>
+#include <QtGui/QColor>
+
+#include <o3tl/safeint.hxx>
+#include <sal/log.hxx>
+#include <tools/helpers.hxx>
+
+QtBitmap::QtBitmap() {}
+
+QtBitmap::QtBitmap(const QImage& rImage) { m_pImage.reset(new QImage(rImage)); }
+
+bool QtBitmap::Create(const Size& rSize, vcl::PixelFormat ePixelFormat, const BitmapPalette& rPal)
+{
+ if (ePixelFormat == vcl::PixelFormat::INVALID)
+ return false;
+
+ if (ePixelFormat == vcl::PixelFormat::N8_BPP)
+ assert(256 >= rPal.GetEntryCount());
+
+ m_pImage.reset(new QImage(toQSize(rSize), getBitFormat(ePixelFormat)));
+ m_pImage->fill(Qt::transparent);
+ m_aPalette = rPal;
+
+ auto count = rPal.GetEntryCount();
+ if (count && m_pImage)
+ {
+ QVector<QRgb> aColorTable(count);
+ for (unsigned i = 0; i < count; ++i)
+ aColorTable[i] = qRgb(rPal[i].GetRed(), rPal[i].GetGreen(), rPal[i].GetBlue());
+ m_pImage->setColorTable(std::move(aColorTable));
+ }
+ return true;
+}
+
+bool QtBitmap::Create(const SalBitmap& rSalBmp)
+{
+ const QtBitmap* pBitmap = static_cast<const QtBitmap*>(&rSalBmp);
+ m_pImage.reset(new QImage(*pBitmap->m_pImage));
+ m_aPalette = pBitmap->m_aPalette;
+ return true;
+}
+
+bool QtBitmap::Create(const SalBitmap& rSalBmp, SalGraphics* pSalGraphics)
+{
+ const QtBitmap* pBitmap = static_cast<const QtBitmap*>(&rSalBmp);
+ QtGraphics* pGraphics = static_cast<QtGraphics*>(pSalGraphics);
+ QImage* pImage = pGraphics->getQImage();
+ m_pImage.reset(new QImage(pBitmap->m_pImage->convertToFormat(pImage->format())));
+ return true;
+}
+
+bool QtBitmap::Create(const SalBitmap& rSalBmp, vcl::PixelFormat eNewPixelFormat)
+{
+ if (eNewPixelFormat == vcl::PixelFormat::INVALID)
+ return false;
+ const QtBitmap* pBitmap = static_cast<const QtBitmap*>(&rSalBmp);
+ m_pImage.reset(new QImage(pBitmap->m_pImage->convertToFormat(getBitFormat(eNewPixelFormat))));
+ return true;
+}
+
+bool QtBitmap::Create(const css::uno::Reference<css::rendering::XBitmapCanvas>& /*rBitmapCanvas*/,
+ Size& /*rSize*/, bool /*bMask*/)
+{
+ return false;
+}
+
+void QtBitmap::Destroy() { m_pImage.reset(); }
+
+Size QtBitmap::GetSize() const
+{
+ if (m_pImage)
+ return toSize(m_pImage->size());
+ return Size();
+}
+
+sal_uInt16 QtBitmap::GetBitCount() const
+{
+ if (m_pImage)
+ return getFormatBits(m_pImage->format());
+ return 0;
+}
+
+BitmapBuffer* QtBitmap::AcquireBuffer(BitmapAccessMode /*nMode*/)
+{
+ static const BitmapPalette aEmptyPalette;
+
+ if (!m_pImage)
+ return nullptr;
+
+ BitmapBuffer* pBuffer = new BitmapBuffer;
+
+ pBuffer->mnWidth = m_pImage->width();
+ pBuffer->mnHeight = m_pImage->height();
+ pBuffer->mnBitCount = getFormatBits(m_pImage->format());
+ pBuffer->mpBits = m_pImage->bits();
+ pBuffer->mnScanlineSize = m_pImage->bytesPerLine();
+
+ switch (pBuffer->mnBitCount)
+ {
+ case 1:
+ pBuffer->mnFormat = ScanlineFormat::N1BitMsbPal | ScanlineFormat::TopDown;
+ pBuffer->maPalette = m_aPalette;
+ break;
+ case 8:
+ pBuffer->mnFormat = ScanlineFormat::N8BitPal | ScanlineFormat::TopDown;
+ pBuffer->maPalette = m_aPalette;
+ break;
+ case 24:
+ pBuffer->mnFormat = ScanlineFormat::N24BitTcRgb | ScanlineFormat::TopDown;
+ pBuffer->maPalette = aEmptyPalette;
+ break;
+ case 32:
+ {
+#ifdef OSL_BIGENDIAN
+ pBuffer->mnFormat = ScanlineFormat::N32BitTcArgb | ScanlineFormat::TopDown;
+#else
+ pBuffer->mnFormat = ScanlineFormat::N32BitTcBgra | ScanlineFormat::TopDown;
+#endif
+ pBuffer->maPalette = aEmptyPalette;
+ break;
+ }
+ default:
+ assert(false);
+ }
+
+ return pBuffer;
+}
+
+void QtBitmap::ReleaseBuffer(BitmapBuffer* pBuffer, BitmapAccessMode nMode)
+{
+ m_aPalette = pBuffer->maPalette;
+ auto count = m_aPalette.GetEntryCount();
+ if (pBuffer->mnBitCount != 4 && count)
+ {
+ QVector<QRgb> aColorTable(count);
+ for (unsigned i = 0; i < count; ++i)
+ aColorTable[i]
+ = qRgb(m_aPalette[i].GetRed(), m_aPalette[i].GetGreen(), m_aPalette[i].GetBlue());
+ m_pImage->setColorTable(std::move(aColorTable));
+ }
+ delete pBuffer;
+ if (nMode == BitmapAccessMode::Write)
+ InvalidateChecksum();
+}
+
+bool QtBitmap::GetSystemData(BitmapSystemData& /*rData*/) { return false; }
+
+bool QtBitmap::ScalingSupported() const { return false; }
+
+bool QtBitmap::Scale(const double& /*rScaleX*/, const double& /*rScaleY*/,
+ BmpScaleFlag /*nScaleFlag*/)
+{
+ return false;
+}
+
+bool QtBitmap::Replace(const Color& /*rSearchColor*/, const Color& /*rReplaceColor*/,
+ sal_uInt8 /*nTol*/)
+{
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtClipboard.cxx b/vcl/qt5/QtClipboard.cxx
new file mode 100644
index 0000000000..e9eb476fb2
--- /dev/null
+++ b/vcl/qt5/QtClipboard.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/.
+ *
+ */
+
+#include <QtClipboard.hxx>
+#include <QtClipboard.moc>
+
+#include <cppuhelper/supportsservice.hxx>
+#include <sal/log.hxx>
+
+#include <QtWidgets/QApplication>
+
+#include <QtInstance.hxx>
+#include <QtTransferable.hxx>
+#include <QtTools.hxx>
+
+#include <cassert>
+#include <map>
+#include <utility>
+
+QtClipboard::QtClipboard(OUString aModeString, const QClipboard::Mode aMode)
+ : cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard,
+ css::datatransfer::clipboard::XFlushableClipboard,
+ XServiceInfo>(m_aMutex)
+ , m_aClipboardName(std::move(aModeString))
+ , m_aClipboardMode(aMode)
+ , m_bOwnClipboardChange(false)
+ , m_bDoClear(false)
+{
+ assert(isSupported(m_aClipboardMode));
+ // DirectConnection guarantees the changed slot runs in the same thread as the QClipboard
+ connect(QApplication::clipboard(), &QClipboard::changed, this, &QtClipboard::handleChanged,
+ Qt::DirectConnection);
+
+ // explicitly queue an event, so we can eventually ignore it
+ connect(this, &QtClipboard::clearClipboard, this, &QtClipboard::handleClearClipboard,
+ Qt::QueuedConnection);
+}
+
+css::uno::Reference<css::uno::XInterface> QtClipboard::create(const OUString& aModeString)
+{
+ static const std::map<OUString, QClipboard::Mode> aNameToClipboardMap
+ = { { "CLIPBOARD", QClipboard::Clipboard }, { "PRIMARY", QClipboard::Selection } };
+
+ assert(QApplication::clipboard()->thread() == qApp->thread());
+
+ auto iter = aNameToClipboardMap.find(aModeString);
+ if (iter != aNameToClipboardMap.end() && isSupported(iter->second))
+ return cppu::getXWeak(new QtClipboard(aModeString, iter->second));
+ SAL_WARN("vcl.qt", "Ignoring unrecognized clipboard type: '" << aModeString << "'");
+ return css::uno::Reference<css::uno::XInterface>();
+}
+
+void QtClipboard::flushClipboard()
+{
+ auto* pSalInst(GetQtInstance());
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([this]() {
+ if (!isOwner(m_aClipboardMode))
+ return;
+
+ QClipboard* pClipboard = QApplication::clipboard();
+ const QtMimeData* pQtMimeData
+ = dynamic_cast<const QtMimeData*>(pClipboard->mimeData(m_aClipboardMode));
+ assert(pQtMimeData);
+
+ QMimeData* pMimeCopy = nullptr;
+ if (pQtMimeData && pQtMimeData->deepCopy(&pMimeCopy))
+ {
+ m_bOwnClipboardChange = true;
+ pClipboard->setMimeData(pMimeCopy, m_aClipboardMode);
+ m_bOwnClipboardChange = false;
+ }
+ });
+}
+
+css::uno::Reference<css::datatransfer::XTransferable> QtClipboard::getContents()
+{
+#if defined(EMSCRIPTEN)
+ static QMimeData aMimeData;
+#endif
+ osl::MutexGuard aGuard(m_aMutex);
+
+ // if we're the owner, we might have the XTransferable from setContents. but
+ // maybe a non-LO clipboard change from within LO, like some C'n'P in the
+ // QFileDialog, might have invalidated m_aContents, so we need to check it too.
+ if (isOwner(m_aClipboardMode) && m_aContents.is())
+ return m_aContents;
+
+ // check if we can still use the shared QtClipboardTransferable
+ const QMimeData* pMimeData = QApplication::clipboard()->mimeData(m_aClipboardMode);
+#if defined(EMSCRIPTEN)
+ if (!pMimeData)
+ pMimeData = &aMimeData;
+#endif
+ if (m_aContents.is())
+ {
+ const auto* pTrans = dynamic_cast<QtClipboardTransferable*>(m_aContents.get());
+ assert(pTrans);
+ if (pTrans && pTrans->mimeData() == pMimeData)
+ return m_aContents;
+ }
+
+ m_aContents = new QtClipboardTransferable(m_aClipboardMode, pMimeData);
+ return m_aContents;
+}
+
+void QtClipboard::handleClearClipboard()
+{
+ if (!m_bDoClear)
+ return;
+ QApplication::clipboard()->clear(m_aClipboardMode);
+}
+
+void QtClipboard::setContents(
+ const css::uno::Reference<css::datatransfer::XTransferable>& xTrans,
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& xClipboardOwner)
+{
+ // it's actually possible to get a non-empty xTrans and an empty xClipboardOwner!
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> xOldOwner(m_aOwner);
+ css::uno::Reference<css::datatransfer::XTransferable> xOldContents(m_aContents);
+ m_aContents = xTrans;
+ m_aOwner = xClipboardOwner;
+
+ m_bDoClear = !m_aContents.is();
+ if (!m_bDoClear)
+ {
+ m_bOwnClipboardChange = true;
+ QApplication::clipboard()->setMimeData(new QtMimeData(m_aContents), m_aClipboardMode);
+ m_bOwnClipboardChange = false;
+ }
+ else
+ {
+ assert(!m_aOwner.is());
+ Q_EMIT clearClipboard();
+ }
+
+ aGuard.clear();
+
+ // we have to notify only an owner change, since handleChanged can't
+ // access the previous owner anymore and can just handle lost ownership.
+ if (xOldOwner.is() && xOldOwner != xClipboardOwner)
+ xOldOwner->lostOwnership(this, xOldContents);
+}
+
+void QtClipboard::handleChanged(QClipboard::Mode aMode)
+{
+ if (aMode != m_aClipboardMode)
+ return;
+
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ // QtWayland will send a second change notification (seemingly without any
+ // trigger). And any C'n'P operation in the Qt file picker emits a signal,
+ // with LO still holding the clipboard ownership, but internally having lost
+ // it. So ignore any signal, which still delivers the internal QtMimeData
+ // as the clipboard content and is no "advertised" change.
+ if (!m_bOwnClipboardChange && isOwner(aMode)
+ && dynamic_cast<const QtMimeData*>(QApplication::clipboard()->mimeData(aMode)))
+ return;
+
+ css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> xOldOwner(m_aOwner);
+ css::uno::Reference<css::datatransfer::XTransferable> xOldContents(m_aContents);
+ // ownership change from LO POV is handled in setContents
+ if (!m_bOwnClipboardChange)
+ {
+ m_aContents.clear();
+ m_aOwner.clear();
+ }
+
+ std::vector<css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>> aListeners(
+ m_aListeners);
+ css::datatransfer::clipboard::ClipboardEvent aEv;
+ aEv.Contents = getContents();
+
+ aGuard.clear();
+
+ if (!m_bOwnClipboardChange && xOldOwner.is())
+ xOldOwner->lostOwnership(this, xOldContents);
+ for (auto const& listener : aListeners)
+ listener->changedContents(aEv);
+}
+
+OUString QtClipboard::getImplementationName() { return "com.sun.star.datatransfer.QtClipboard"; }
+
+css::uno::Sequence<OUString> QtClipboard::getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.clipboard.SystemClipboard" };
+}
+
+sal_Bool QtClipboard::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+OUString QtClipboard::getName() { return m_aClipboardName; }
+
+sal_Int8 QtClipboard::getRenderingCapabilities() { return 0; }
+
+void QtClipboard::addClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+ m_aListeners.push_back(listener);
+}
+
+void QtClipboard::removeClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+ std::erase(m_aListeners, listener);
+}
+
+bool QtClipboard::isSupported(const QClipboard::Mode aMode)
+{
+ const QClipboard* pClipboard = QApplication::clipboard();
+ switch (aMode)
+ {
+ case QClipboard::Selection:
+ return pClipboard->supportsSelection();
+
+ case QClipboard::FindBuffer:
+ return pClipboard->supportsFindBuffer();
+
+ case QClipboard::Clipboard:
+ return true;
+ }
+ return false;
+}
+
+bool QtClipboard::isOwner(const QClipboard::Mode aMode)
+{
+ if (!isSupported(aMode))
+ return false;
+
+ const QClipboard* pClipboard = QApplication::clipboard();
+ switch (aMode)
+ {
+ case QClipboard::Selection:
+ return pClipboard->ownsSelection();
+
+ case QClipboard::FindBuffer:
+ return pClipboard->ownsFindBuffer();
+
+ case QClipboard::Clipboard:
+ return pClipboard->ownsClipboard();
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtData.cxx b/vcl/qt5/QtData.cxx
new file mode 100644
index 0000000000..cc2883ae80
--- /dev/null
+++ b/vcl/qt5/QtData.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 <QtData.hxx>
+
+#include <QtGui/QBitmap>
+#include <QtGui/QCursor>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QStyle>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <vcl/ImageTree.hxx>
+
+#include <bitmaps.hlst>
+#include <cursor_hotspots.hxx>
+#include <unx/glyphcache.hxx>
+
+QtData::QtData()
+ : GenericUnixSalData()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ pSVData->maNWFData.mbDockingAreaSeparateTB = true;
+ pSVData->maNWFData.mbFlatMenu = true;
+ pSVData->maNWFData.mbRolloverMenubar = true;
+ pSVData->maNWFData.mbNoFocusRects = true;
+ pSVData->maNWFData.mbNoFocusRectsForFlatButtons = true;
+
+ QStyle* style = QApplication::style();
+ pSVData->maNWFData.mnMenuFormatBorderX = style->pixelMetric(QStyle::PM_MenuPanelWidth)
+ + style->pixelMetric(QStyle::PM_MenuHMargin);
+ pSVData->maNWFData.mnMenuFormatBorderY = style->pixelMetric(QStyle::PM_MenuPanelWidth)
+ + style->pixelMetric(QStyle::PM_MenuVMargin);
+}
+
+// outline dtor b/c of FreetypeManager incomplete type
+QtData::~QtData() {}
+
+static QCursor* getQCursorFromIconTheme(const OUString& rIconName, int nXHot, int nYHot)
+{
+ const OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ const OUString sUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
+ auto xMemStream = ImageTree::get().getImageStream(rIconName, sIconTheme, sUILang);
+ if (!xMemStream)
+ return nullptr;
+ auto nLength = xMemStream->TellEnd();
+ if (!nLength)
+ {
+ SAL_WARN("vcl.qt", "Cannot load cursor pixmap from empty stream.");
+ return nullptr;
+ }
+
+ const unsigned char* pData = static_cast<const unsigned char*>(xMemStream->GetData());
+ QPixmap aPixmap;
+ aPixmap.loadFromData(pData, nLength);
+ return new QCursor(aPixmap, nXHot, nYHot);
+}
+
+#define MAKE_CURSOR(vcl_name, name, icon_name) \
+ case vcl_name: \
+ pCursor = getQCursorFromIconTheme(icon_name, name##curs_x_hot, name##curs_y_hot); \
+ break
+
+#define MAP_BUILTIN(vcl_name, qt_enum) \
+ case vcl_name: \
+ pCursor = new QCursor(qt_enum); \
+ break
+
+QCursor& QtData::getCursor(PointerStyle ePointerStyle)
+{
+ if (!m_aCursors[ePointerStyle])
+ {
+ QCursor* pCursor = nullptr;
+
+ switch (ePointerStyle)
+ {
+ MAP_BUILTIN(PointerStyle::Arrow, Qt::ArrowCursor);
+ MAP_BUILTIN(PointerStyle::Text, Qt::IBeamCursor);
+ MAP_BUILTIN(PointerStyle::Help, Qt::WhatsThisCursor);
+ MAP_BUILTIN(PointerStyle::Cross, Qt::CrossCursor);
+ MAP_BUILTIN(PointerStyle::Wait, Qt::WaitCursor);
+ MAP_BUILTIN(PointerStyle::NSize, Qt::SizeVerCursor);
+ MAP_BUILTIN(PointerStyle::SSize, Qt::SizeVerCursor);
+ MAP_BUILTIN(PointerStyle::WSize, Qt::SizeHorCursor);
+ MAP_BUILTIN(PointerStyle::ESize, Qt::SizeHorCursor);
+
+ MAP_BUILTIN(PointerStyle::NWSize, Qt::SizeFDiagCursor);
+ MAP_BUILTIN(PointerStyle::NESize, Qt::SizeBDiagCursor);
+ MAP_BUILTIN(PointerStyle::SWSize, Qt::SizeBDiagCursor);
+ MAP_BUILTIN(PointerStyle::SESize, Qt::SizeFDiagCursor);
+ MAP_BUILTIN(PointerStyle::WindowNSize, Qt::SizeVerCursor);
+ MAP_BUILTIN(PointerStyle::WindowSSize, Qt::SizeVerCursor);
+ MAP_BUILTIN(PointerStyle::WindowWSize, Qt::SizeHorCursor);
+ MAP_BUILTIN(PointerStyle::WindowESize, Qt::SizeHorCursor);
+ MAP_BUILTIN(PointerStyle::WindowNWSize, Qt::SizeFDiagCursor);
+ MAP_BUILTIN(PointerStyle::WindowNESize, Qt::SizeBDiagCursor);
+ MAP_BUILTIN(PointerStyle::WindowSWSize, Qt::SizeBDiagCursor);
+ MAP_BUILTIN(PointerStyle::WindowSESize, Qt::SizeFDiagCursor);
+
+ MAP_BUILTIN(PointerStyle::HSizeBar, Qt::SizeHorCursor);
+ MAP_BUILTIN(PointerStyle::VSizeBar, Qt::SizeVerCursor);
+
+ MAP_BUILTIN(PointerStyle::RefHand, Qt::PointingHandCursor);
+ MAP_BUILTIN(PointerStyle::Hand, Qt::OpenHandCursor);
+#if 0
+ MAP_BUILTIN( PointerStyle::Pen, GDK_PENCIL );
+#endif
+ MAP_BUILTIN(PointerStyle::HSplit, Qt::SizeHorCursor);
+ MAP_BUILTIN(PointerStyle::VSplit, Qt::SizeVerCursor);
+
+ MAP_BUILTIN(PointerStyle::Move, Qt::SizeAllCursor);
+
+ MAP_BUILTIN(PointerStyle::Null, Qt::BlankCursor);
+ MAKE_CURSOR(PointerStyle::Magnify, magnify_, RID_CURSOR_MAGNIFY);
+ MAKE_CURSOR(PointerStyle::Fill, fill_, RID_CURSOR_FILL);
+ MAKE_CURSOR(PointerStyle::MoveData, movedata_, RID_CURSOR_MOVE_DATA);
+ MAKE_CURSOR(PointerStyle::CopyData, copydata_, RID_CURSOR_COPY_DATA);
+ MAKE_CURSOR(PointerStyle::MoveFile, movefile_, RID_CURSOR_MOVE_FILE);
+ MAKE_CURSOR(PointerStyle::CopyFile, copyfile_, RID_CURSOR_COPY_FILE);
+ MAKE_CURSOR(PointerStyle::MoveFiles, movefiles_, RID_CURSOR_MOVE_FILES);
+ MAKE_CURSOR(PointerStyle::CopyFiles, copyfiles_, RID_CURSOR_COPY_FILES);
+ MAKE_CURSOR(PointerStyle::NotAllowed, nodrop_, RID_CURSOR_NOT_ALLOWED);
+ MAKE_CURSOR(PointerStyle::Rotate, rotate_, RID_CURSOR_ROTATE);
+ MAKE_CURSOR(PointerStyle::HShear, hshear_, RID_CURSOR_H_SHEAR);
+ MAKE_CURSOR(PointerStyle::VShear, vshear_, RID_CURSOR_V_SHEAR);
+ MAKE_CURSOR(PointerStyle::DrawLine, drawline_, RID_CURSOR_DRAW_LINE);
+ MAKE_CURSOR(PointerStyle::DrawRect, drawrect_, RID_CURSOR_DRAW_RECT);
+ MAKE_CURSOR(PointerStyle::DrawPolygon, drawpolygon_, RID_CURSOR_DRAW_POLYGON);
+ MAKE_CURSOR(PointerStyle::DrawBezier, drawbezier_, RID_CURSOR_DRAW_BEZIER);
+ MAKE_CURSOR(PointerStyle::DrawArc, drawarc_, RID_CURSOR_DRAW_ARC);
+ MAKE_CURSOR(PointerStyle::DrawPie, drawpie_, RID_CURSOR_DRAW_PIE);
+ MAKE_CURSOR(PointerStyle::DrawCircleCut, drawcirclecut_, RID_CURSOR_DRAW_CIRCLE_CUT);
+ MAKE_CURSOR(PointerStyle::DrawEllipse, drawellipse_, RID_CURSOR_DRAW_ELLIPSE);
+ MAKE_CURSOR(PointerStyle::DrawConnect, drawconnect_, RID_CURSOR_DRAW_CONNECT);
+ MAKE_CURSOR(PointerStyle::DrawText, drawtext_, RID_CURSOR_DRAW_TEXT);
+ MAKE_CURSOR(PointerStyle::Mirror, mirror_, RID_CURSOR_MIRROR);
+ MAKE_CURSOR(PointerStyle::Crook, crook_, RID_CURSOR_CROOK);
+ MAKE_CURSOR(PointerStyle::Crop, crop_, RID_CURSOR_CROP);
+ MAKE_CURSOR(PointerStyle::MovePoint, movepoint_, RID_CURSOR_MOVE_POINT);
+ MAKE_CURSOR(PointerStyle::MoveBezierWeight, movebezierweight_,
+ RID_CURSOR_MOVE_BEZIER_WEIGHT);
+ MAKE_CURSOR(PointerStyle::DrawFreehand, drawfreehand_, RID_CURSOR_DRAW_FREEHAND);
+ MAKE_CURSOR(PointerStyle::DrawCaption, drawcaption_, RID_CURSOR_DRAW_CAPTION);
+ MAKE_CURSOR(PointerStyle::LinkData, linkdata_, RID_CURSOR_LINK_DATA);
+ MAKE_CURSOR(PointerStyle::MoveDataLink, movedlnk_, RID_CURSOR_MOVE_DATA_LINK);
+ MAKE_CURSOR(PointerStyle::CopyDataLink, copydlnk_, RID_CURSOR_COPY_DATA_LINK);
+ MAKE_CURSOR(PointerStyle::LinkFile, linkfile_, RID_CURSOR_LINK_FILE);
+ MAKE_CURSOR(PointerStyle::MoveFileLink, moveflnk_, RID_CURSOR_MOVE_FILE_LINK);
+ MAKE_CURSOR(PointerStyle::CopyFileLink, copyflnk_, RID_CURSOR_COPY_FILE_LINK);
+ MAKE_CURSOR(PointerStyle::Chart, chart_, RID_CURSOR_CHART);
+ MAKE_CURSOR(PointerStyle::Detective, detective_, RID_CURSOR_DETECTIVE);
+ MAKE_CURSOR(PointerStyle::PivotCol, pivotcol_, RID_CURSOR_PIVOT_COLUMN);
+ MAKE_CURSOR(PointerStyle::PivotRow, pivotrow_, RID_CURSOR_PIVOT_ROW);
+ MAKE_CURSOR(PointerStyle::PivotField, pivotfld_, RID_CURSOR_PIVOT_FIELD);
+ MAKE_CURSOR(PointerStyle::PivotDelete, pivotdel_, RID_CURSOR_PIVOT_DELETE);
+ MAKE_CURSOR(PointerStyle::Chain, chain_, RID_CURSOR_CHAIN);
+ MAKE_CURSOR(PointerStyle::ChainNotAllowed, chainnot_, RID_CURSOR_CHAIN_NOT_ALLOWED);
+ MAKE_CURSOR(PointerStyle::AutoScrollN, asn_, RID_CURSOR_AUTOSCROLL_N);
+ MAKE_CURSOR(PointerStyle::AutoScrollS, ass_, RID_CURSOR_AUTOSCROLL_S);
+ MAKE_CURSOR(PointerStyle::AutoScrollW, asw_, RID_CURSOR_AUTOSCROLL_W);
+ MAKE_CURSOR(PointerStyle::AutoScrollE, ase_, RID_CURSOR_AUTOSCROLL_E);
+ MAKE_CURSOR(PointerStyle::AutoScrollNW, asnw_, RID_CURSOR_AUTOSCROLL_NW);
+ MAKE_CURSOR(PointerStyle::AutoScrollNE, asne_, RID_CURSOR_AUTOSCROLL_NE);
+ MAKE_CURSOR(PointerStyle::AutoScrollSW, assw_, RID_CURSOR_AUTOSCROLL_SW);
+ MAKE_CURSOR(PointerStyle::AutoScrollSE, asse_, RID_CURSOR_AUTOSCROLL_SE);
+ MAKE_CURSOR(PointerStyle::AutoScrollNS, asns_, RID_CURSOR_AUTOSCROLL_NS);
+ MAKE_CURSOR(PointerStyle::AutoScrollWE, aswe_, RID_CURSOR_AUTOSCROLL_WE);
+ MAKE_CURSOR(PointerStyle::AutoScrollNSWE, asnswe_, RID_CURSOR_AUTOSCROLL_NSWE);
+ MAKE_CURSOR(PointerStyle::TextVertical, vertcurs_, RID_CURSOR_TEXT_VERTICAL);
+
+ MAKE_CURSOR(PointerStyle::TabSelectS, tblsels_, RID_CURSOR_TAB_SELECT_S);
+ MAKE_CURSOR(PointerStyle::TabSelectE, tblsele_, RID_CURSOR_TAB_SELECT_E);
+ MAKE_CURSOR(PointerStyle::TabSelectSE, tblselse_, RID_CURSOR_TAB_SELECT_SE);
+ MAKE_CURSOR(PointerStyle::TabSelectW, tblselw_, RID_CURSOR_TAB_SELECT_W);
+ MAKE_CURSOR(PointerStyle::TabSelectSW, tblselsw_, RID_CURSOR_TAB_SELECT_SW);
+
+ MAKE_CURSOR(PointerStyle::HideWhitespace, hidewhitespace_, RID_CURSOR_HIDE_WHITESPACE);
+ MAKE_CURSOR(PointerStyle::ShowWhitespace, showwhitespace_, RID_CURSOR_SHOW_WHITESPACE);
+
+ MAKE_CURSOR(PointerStyle::FatCross, fatcross_, RID_CURSOR_FATCROSS);
+ default:
+ break;
+ }
+ if (!pCursor)
+ {
+ pCursor = new QCursor(Qt::ArrowCursor);
+ SAL_WARN("vcl.qt", "pointer " << static_cast<int>(ePointerStyle) << " not implemented");
+ }
+
+ m_aCursors[ePointerStyle].reset(pCursor);
+ }
+
+ return *m_aCursors[ePointerStyle];
+}
+
+void QtData::ErrorTrapPush() {}
+
+bool QtData::ErrorTrapPop(bool /*bIgnoreError*/) { return false; }
+
+bool QtData::noNativeControls()
+{
+ static const bool bNoNative
+ = ((nullptr != getenv("SAL_VCL_QT5_NO_NATIVE")) && (nullptr != ImplGetSVData())
+ && ImplGetSVData()->maAppData.mxToolkitName
+ && ImplGetSVData()->maAppData.mxToolkitName->match("qt5"));
+ return bNoNative;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtDragAndDrop.cxx b/vcl/qt5/QtDragAndDrop.cxx
new file mode 100644
index 0000000000..ffabc1bbba
--- /dev/null
+++ b/vcl/qt5/QtDragAndDrop.cxx
@@ -0,0 +1,249 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/awt/MouseButton.hpp>
+#include <com/sun/star/datatransfer/DataFlavor.hpp>
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <sal/log.hxx>
+
+#include <QtDragAndDrop.hxx>
+#include <QtFrame.hxx>
+#include <QtTransferable.hxx>
+#include <QtWidget.hxx>
+
+#include <QtGui/QDrag>
+
+using namespace com::sun::star;
+
+QtDragSource::~QtDragSource() {}
+
+void QtDragSource::deinitialize() { m_pFrame = nullptr; }
+
+sal_Bool QtDragSource::isDragImageSupported() { return true; }
+
+sal_Int32 QtDragSource::getDefaultCursor(sal_Int8) { return 0; }
+
+void QtDragSource::initialize(const css::uno::Sequence<css::uno::Any>& rArguments)
+{
+ if (rArguments.getLength() < 2)
+ {
+ throw uno::RuntimeException("DragSource::initialize: Cannot install window event handler",
+ getXWeak());
+ }
+
+ sal_IntPtr nFrame = 0;
+ rArguments.getConstArray()[1] >>= nFrame;
+
+ if (!nFrame)
+ {
+ throw uno::RuntimeException("DragSource::initialize: missing SalFrame", getXWeak());
+ }
+
+ m_pFrame = reinterpret_cast<QtFrame*>(nFrame);
+ m_pFrame->registerDragSource(this);
+}
+
+void QtDragSource::startDrag(
+ const datatransfer::dnd::DragGestureEvent& /*rEvent*/, sal_Int8 sourceActions,
+ sal_Int32 /*cursor*/, sal_Int32 /*image*/,
+ const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
+ const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener)
+{
+ m_xListener = rListener;
+
+ if (m_pFrame)
+ {
+ QDrag* drag = new QDrag(m_pFrame->GetQWidget());
+ drag->setMimeData(new QtMimeData(rTrans));
+ // just a reminder that exec starts a nested event loop, so everything after
+ // this call is just executed, after D'n'D has finished!
+ drag->exec(toQtDropActions(sourceActions), getPreferredDropAction(sourceActions));
+ }
+
+ // the drop will eventually call fire_dragEnd, which will clear the listener.
+ // if D'n'D ends without success, we just get a leave event without any indicator,
+ // but the event loop will be terminated, so we have to try to inform the source of
+ // a failure in any way.
+ fire_dragEnd(datatransfer::dnd::DNDConstants::ACTION_NONE, false);
+}
+
+void QtDragSource::fire_dragEnd(sal_Int8 nAction, bool bDropSuccessful)
+{
+ if (!m_xListener.is())
+ return;
+
+ datatransfer::dnd::DragSourceDropEvent aEv;
+ aEv.DropAction = nAction;
+ aEv.DropSuccess = bDropSuccessful;
+
+ auto xListener = m_xListener;
+ m_xListener.clear();
+ xListener->dragDropEnd(aEv);
+}
+
+OUString SAL_CALL QtDragSource::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.VclQtDragSource";
+}
+
+sal_Bool SAL_CALL QtDragSource::supportsService(OUString const& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL QtDragSource::getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.dnd.QtDragSource" };
+}
+
+QtDropTarget::QtDropTarget()
+ : WeakComponentImplHelper(m_aMutex)
+ , m_pFrame(nullptr)
+ , m_bActive(false)
+ , m_nDefaultActions(0)
+{
+}
+
+OUString SAL_CALL QtDropTarget::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.VclQtDropTarget";
+}
+
+sal_Bool SAL_CALL QtDropTarget::supportsService(OUString const& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL QtDropTarget::getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.dnd.QtDropTarget" };
+}
+
+QtDropTarget::~QtDropTarget() {}
+
+void QtDropTarget::deinitialize()
+{
+ m_pFrame = nullptr;
+ m_bActive = false;
+}
+
+void QtDropTarget::initialize(const uno::Sequence<uno::Any>& rArguments)
+{
+ if (rArguments.getLength() < 2)
+ {
+ throw uno::RuntimeException("DropTarget::initialize: Cannot install window event handler",
+ getXWeak());
+ }
+
+ sal_IntPtr nFrame = 0;
+ rArguments.getConstArray()[1] >>= nFrame;
+
+ if (!nFrame)
+ {
+ throw uno::RuntimeException("DropTarget::initialize: missing SalFrame", getXWeak());
+ }
+
+ m_nDropAction = datatransfer::dnd::DNDConstants::ACTION_NONE;
+
+ m_pFrame = reinterpret_cast<QtFrame*>(nFrame);
+ m_pFrame->registerDropTarget(this);
+ m_bActive = true;
+}
+
+void QtDropTarget::addDropTargetListener(
+ const uno::Reference<css::datatransfer::dnd::XDropTargetListener>& xListener)
+{
+ ::osl::Guard<::osl::Mutex> aGuard(m_aMutex);
+
+ m_aListeners.push_back(xListener);
+}
+
+void QtDropTarget::removeDropTargetListener(
+ const uno::Reference<css::datatransfer::dnd::XDropTargetListener>& xListener)
+{
+ ::osl::Guard<::osl::Mutex> aGuard(m_aMutex);
+
+ std::erase(m_aListeners, xListener);
+}
+
+sal_Bool QtDropTarget::isActive() { return m_bActive; }
+
+void QtDropTarget::setActive(sal_Bool bActive) { m_bActive = bActive; }
+
+sal_Int8 QtDropTarget::getDefaultActions() { return m_nDefaultActions; }
+
+void QtDropTarget::setDefaultActions(sal_Int8 nDefaultActions)
+{
+ m_nDefaultActions = nDefaultActions;
+}
+
+void QtDropTarget::fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde)
+{
+ osl::ClearableGuard<::osl::Mutex> aGuard(m_aMutex);
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(
+ m_aListeners);
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ {
+ listener->dragEnter(dtde);
+ }
+}
+
+void QtDropTarget::fire_dragOver(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde)
+{
+ osl::ClearableGuard<::osl::Mutex> aGuard(m_aMutex);
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(
+ m_aListeners);
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ listener->dragOver(dtde);
+}
+
+void QtDropTarget::fire_drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde)
+{
+ m_bDropSuccessful = true;
+
+ osl::ClearableGuard<osl::Mutex> aGuard(m_aMutex);
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(
+ m_aListeners);
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ listener->drop(dtde);
+}
+
+void QtDropTarget::fire_dragExit(const css::datatransfer::dnd::DropTargetEvent& dte)
+{
+ osl::ClearableGuard<::osl::Mutex> aGuard(m_aMutex);
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(
+ m_aListeners);
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ listener->dragExit(dte);
+}
+
+void QtDropTarget::acceptDrag(sal_Int8 dragOperation) { m_nDropAction = dragOperation; }
+
+void QtDropTarget::rejectDrag() { m_nDropAction = 0; }
+
+void QtDropTarget::acceptDrop(sal_Int8 dropOperation) { m_nDropAction = dropOperation; }
+
+void QtDropTarget::rejectDrop() { m_nDropAction = 0; }
+
+void QtDropTarget::dropComplete(sal_Bool success)
+{
+ m_bDropSuccessful = (m_bDropSuccessful && success);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtFilePicker.cxx b/vcl/qt5/QtFilePicker.cxx
new file mode 100644
index 0000000000..8a13ec6723
--- /dev/null
+++ b/vcl/qt5/QtFilePicker.cxx
@@ -0,0 +1,983 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <fpicker/fpsofficeResMgr.hxx>
+#include <QtFilePicker.hxx>
+#include <QtFilePicker.moc>
+
+#include <QtFrame.hxx>
+#include <QtTools.hxx>
+#include <QtWidget.hxx>
+#include <QtInstance.hxx>
+
+#include <com/sun/star/awt/SystemDependentXWindow.hpp>
+#include <com/sun/star/awt/XSystemDependentWindowPeer.hpp>
+#include <com/sun/star/awt/XWindow.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/TerminationVetoException.hpp>
+#include <com/sun/star/frame/XDesktop.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/SystemDependent.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp>
+#include <com/sun/star/ui/dialogs/ControlActions.hpp>
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+#include <com/sun/star/uri/ExternalUriReferenceTranslator.hpp>
+#include <cppuhelper/interfacecontainer.h>
+#include <cppuhelper/supportsservice.hxx>
+#include <rtl/process.h>
+#include <sal/log.hxx>
+
+#include <QtCore/QDebug>
+#include <QtCore/QRegularExpression>
+#include <QtCore/QThread>
+#include <QtCore/QUrl>
+#include <QtGui/QClipboard>
+#include <QtGui/QWindow>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QCheckBox>
+#include <QtWidgets/QComboBox>
+#include <QtWidgets/QGridLayout>
+#include <QtWidgets/QHBoxLayout>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QMessageBox>
+#include <QtWidgets/QPushButton>
+#include <QtWidgets/QWidget>
+
+#include <unx/geninst.h>
+#include <fpicker/strings.hrc>
+#include <utility>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::ui::dialogs::TemplateDescription;
+using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds;
+using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::uno;
+
+namespace
+{
+uno::Sequence<OUString> FilePicker_getSupportedServiceNames()
+{
+ return { "com.sun.star.ui.dialogs.FilePicker", "com.sun.star.ui.dialogs.SystemFilePicker",
+ "com.sun.star.ui.dialogs.QtFilePicker" };
+}
+}
+
+QtFilePicker::QtFilePicker(css::uno::Reference<css::uno::XComponentContext> context,
+ QFileDialog::FileMode eMode, bool bUseNative)
+ : QtFilePicker_Base(m_aHelperMutex)
+ , m_context(std::move(context))
+ , m_bIsFolderPicker(eMode == QFileDialog::Directory)
+ , m_pParentWidget(nullptr)
+ , m_pFileDialog(new QFileDialog(nullptr, {}, QDir::homePath()))
+ , m_pExtraControls(new QWidget())
+{
+ m_pFileDialog->setOption(QFileDialog::DontUseNativeDialog, !bUseNative);
+
+ m_pFileDialog->setFileMode(eMode);
+ m_pFileDialog->setWindowModality(Qt::ApplicationModal);
+
+ if (m_bIsFolderPicker)
+ {
+ m_pFileDialog->setOption(QFileDialog::ShowDirsOnly, true);
+ m_pFileDialog->setWindowTitle(toQString(FpsResId(STR_SVT_FOLDERPICKER_DEFAULT_TITLE)));
+ }
+
+ m_pLayout = dynamic_cast<QGridLayout*>(m_pFileDialog->layout());
+
+ setMultiSelectionMode(false);
+
+ // XFilePickerListener notifications
+ connect(m_pFileDialog.get(), SIGNAL(filterSelected(const QString&)), this,
+ SLOT(filterSelected(const QString&)));
+ connect(m_pFileDialog.get(), SIGNAL(currentChanged(const QString&)), this,
+ SLOT(currentChanged(const QString&)));
+
+ // update automatic file extension when filter is changed
+ connect(m_pFileDialog.get(), SIGNAL(filterSelected(const QString&)), this,
+ SLOT(updateAutomaticFileExtension()));
+
+ connect(m_pFileDialog.get(), SIGNAL(finished(int)), this, SLOT(finished(int)));
+}
+
+QtFilePicker::~QtFilePicker()
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this]() {
+ // must delete it in main thread, otherwise
+ // QSocketNotifier::setEnabled() will crash us
+ m_pFileDialog.reset();
+ });
+}
+
+void SAL_CALL
+QtFilePicker::addFilePickerListener(const uno::Reference<XFilePickerListener>& xListener)
+{
+ SolarMutexGuard aGuard;
+ m_xListener = xListener;
+}
+
+void SAL_CALL QtFilePicker::removeFilePickerListener(const uno::Reference<XFilePickerListener>&)
+{
+ SolarMutexGuard aGuard;
+ m_xListener.clear();
+}
+
+void SAL_CALL QtFilePicker::setTitle(const OUString& title)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread(
+ [this, &title]() { m_pFileDialog->setWindowTitle(toQString(title)); });
+}
+
+void QtFilePicker::prepareExecute()
+{
+ QWidget* pTransientParent = m_pParentWidget;
+ if (!pTransientParent)
+ {
+ vcl::Window* pWindow = ::Application::GetActiveTopWindow();
+ if (pWindow)
+ {
+ QtFrame* pFrame = dynamic_cast<QtFrame*>(pWindow->ImplGetFrame());
+ assert(pFrame);
+ if (pFrame)
+ pTransientParent = pFrame->asChild();
+ }
+ }
+
+ if (!m_aNamedFilterList.isEmpty())
+ m_pFileDialog->setNameFilters(m_aNamedFilterList);
+ if (!m_aCurrentFilter.isEmpty())
+ m_pFileDialog->selectNameFilter(m_aCurrentFilter);
+
+ updateAutomaticFileExtension();
+
+ uno::Reference<css::frame::XDesktop> xDesktop(css::frame::Desktop::create(m_context),
+ UNO_QUERY_THROW);
+
+ // will hide the window, so do before show
+ m_pFileDialog->setParent(pTransientParent, m_pFileDialog->windowFlags());
+ m_pFileDialog->show();
+ xDesktop->addTerminateListener(this);
+}
+
+void QtFilePicker::finished(int nResult)
+{
+ SolarMutexGuard g;
+ uno::Reference<css::frame::XDesktop> xDesktop(css::frame::Desktop::create(m_context),
+ UNO_QUERY_THROW);
+ xDesktop->removeTerminateListener(this);
+ m_pFileDialog->setParent(nullptr, m_pFileDialog->windowFlags());
+
+ if (m_xClosedListener.is())
+ {
+ const sal_Int16 nRet = (QFileDialog::Rejected == nResult) ? ExecutableDialogResults::CANCEL
+ : ExecutableDialogResults::OK;
+ css::ui::dialogs::DialogClosedEvent aEvent(*this, nRet);
+ m_xClosedListener->dialogClosed(aEvent);
+ m_xClosedListener.clear();
+ }
+}
+
+sal_Int16 SAL_CALL QtFilePicker::execute()
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ sal_uInt16 ret;
+ pSalInst->RunInMainThread([&ret, this]() { ret = execute(); });
+ return ret;
+ }
+
+ prepareExecute();
+ int result = m_pFileDialog->exec();
+
+ if (QFileDialog::Rejected == result)
+ return ExecutableDialogResults::CANCEL;
+ return ExecutableDialogResults::OK;
+}
+
+// XAsynchronousExecutableDialog functions
+void SAL_CALL QtFilePicker::setDialogTitle(const OUString& _rTitle) { setTitle(_rTitle); }
+
+void SAL_CALL
+QtFilePicker::startExecuteModal(const Reference<css::ui::dialogs::XDialogClosedListener>& xListener)
+{
+ m_xClosedListener = xListener;
+ prepareExecute();
+ m_pFileDialog->show();
+}
+
+void SAL_CALL QtFilePicker::setMultiSelectionMode(sal_Bool multiSelect)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, multiSelect]() {
+ if (m_bIsFolderPicker || m_pFileDialog->acceptMode() == QFileDialog::AcceptSave)
+ return;
+
+ m_pFileDialog->setFileMode(multiSelect ? QFileDialog::ExistingFiles
+ : QFileDialog::ExistingFile);
+ });
+}
+
+void SAL_CALL QtFilePicker::setDefaultName(const OUString& name)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, &name]() { m_pFileDialog->selectFile(toQString(name)); });
+}
+
+void SAL_CALL QtFilePicker::setDisplayDirectory(const OUString& dir)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, &dir]() {
+ QString qDir(toQString(dir));
+ m_pFileDialog->setDirectoryUrl(QUrl(qDir));
+ });
+}
+
+OUString SAL_CALL QtFilePicker::getDisplayDirectory()
+{
+ SolarMutexGuard g;
+ OUString ret;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread(
+ [&ret, this]() { ret = toOUString(m_pFileDialog->directoryUrl().toString()); });
+ return ret;
+}
+
+uno::Sequence<OUString> SAL_CALL QtFilePicker::getFiles()
+{
+ uno::Sequence<OUString> seq = getSelectedFiles();
+ if (seq.getLength() > 1)
+ seq.realloc(1);
+ return seq;
+}
+
+uno::Sequence<OUString> SAL_CALL QtFilePicker::getSelectedFiles()
+{
+ SolarMutexGuard g;
+ QList<QUrl> urls;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([&urls, this]() { urls = m_pFileDialog->selectedUrls(); });
+
+ uno::Sequence<OUString> seq(urls.size());
+ auto seqRange = asNonConstRange(seq);
+
+ auto const trans = css::uri::ExternalUriReferenceTranslator::create(m_context);
+ size_t i = 0;
+ for (const QUrl& aURL : urls)
+ {
+ // Unlike LO, QFileDialog (<https://doc.qt.io/qt-5/qfiledialog.html>) apparently always
+ // treats file-system pathnames as UTF-8--encoded, regardless of LANG/LC_CTYPE locale
+ // setting. And pathnames containing byte sequences that are not valid UTF-8 are apparently
+ // filtered out and not even displayed by QFileDialog, so aURL will always have a "payload"
+ // that matches the pathname's byte sequence. So the pathname's byte sequence (which
+ // happens to also be aURL's payload) in the LANG/LC_CTYPE encoding needs to be converted
+ // into LO's internal UTF-8 file URL encoding via
+ // XExternalUriReferenceTranslator::translateToInternal (which looks somewhat paradoxical as
+ // aURL.toEncoded() nominally already has a UTF-8 payload):
+ auto const extUrl = toOUString(aURL.toEncoded());
+ auto intUrl = trans->translateToInternal(extUrl);
+ if (intUrl.isEmpty())
+ {
+ // If translation failed, fall back to original URL:
+ SAL_WARN("vcl.qt", "cannot convert <" << extUrl << "> from locale encoding to UTF-8");
+ intUrl = extUrl;
+ }
+ seqRange[i++] = intUrl;
+ }
+
+ return seq;
+}
+
+void SAL_CALL QtFilePicker::appendFilter(const OUString& title, const OUString& filter)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ pSalInst->RunInMainThread([this, &title, &filter]() { appendFilter(title, filter); });
+ return;
+ }
+
+ // '/' need to be escaped else they are assumed to be mime types
+ QString sTitle = toQString(title).replace("/", "\\/");
+
+ QString sFilterName = sTitle;
+ // the Qt non-native file picker adds the extensions to the filter title, so strip them
+ if (m_pFileDialog->testOption(QFileDialog::DontUseNativeDialog))
+ {
+ int pos = sFilterName.indexOf(" (");
+ if (pos >= 0)
+ sFilterName.truncate(pos);
+ }
+
+ QString sGlobFilter = toQString(filter);
+
+ // LibreOffice gives us filters separated by ';' qt dialogs just want space separated
+ sGlobFilter.replace(";", " ");
+
+ // make sure "*.*" is not used as "all files"
+ sGlobFilter.replace("*.*", "*");
+
+ m_aNamedFilterList << QStringLiteral("%1 (%2)").arg(sFilterName, sGlobFilter);
+ m_aTitleToFilterMap[sTitle] = m_aNamedFilterList.constLast();
+ m_aNamedFilterToExtensionMap[m_aNamedFilterList.constLast()] = sGlobFilter;
+}
+
+void SAL_CALL QtFilePicker::setCurrentFilter(const OUString& title)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, &title]() {
+ m_aCurrentFilter = m_aTitleToFilterMap.value(toQString(title).replace("/", "\\/"));
+ });
+}
+
+OUString SAL_CALL QtFilePicker::getCurrentFilter()
+{
+ SolarMutexGuard g;
+ QString filter;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([&filter, this]() {
+ filter = m_aTitleToFilterMap.key(m_pFileDialog->selectedNameFilter());
+ });
+
+ if (filter.isEmpty())
+ filter = "ODF Text Document (.odt)";
+ return toOUString(filter);
+}
+
+void SAL_CALL QtFilePicker::appendFilterGroup(const OUString& rGroupTitle,
+ const uno::Sequence<beans::StringPair>& filters)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ pSalInst->RunInMainThread(
+ [this, &rGroupTitle, &filters]() { appendFilterGroup(rGroupTitle, filters); });
+ return;
+ }
+
+ const sal_uInt16 length = filters.getLength();
+ for (sal_uInt16 i = 0; i < length; ++i)
+ {
+ beans::StringPair aPair = filters[i];
+ appendFilter(aPair.First, aPair.Second);
+ }
+}
+
+uno::Any QtFilePicker::handleGetListValue(const QComboBox* pWidget, sal_Int16 nControlAction)
+{
+ uno::Any aAny;
+ switch (nControlAction)
+ {
+ case ControlActions::GET_ITEMS:
+ {
+ Sequence<OUString> aItemList(pWidget->count());
+ auto aItemListRange = asNonConstRange(aItemList);
+ for (sal_Int32 i = 0; i < pWidget->count(); ++i)
+ aItemListRange[i] = toOUString(pWidget->itemText(i));
+ aAny <<= aItemList;
+ break;
+ }
+ case ControlActions::GET_SELECTED_ITEM:
+ {
+ if (!pWidget->currentText().isEmpty())
+ aAny <<= toOUString(pWidget->currentText());
+ break;
+ }
+ case ControlActions::GET_SELECTED_ITEM_INDEX:
+ {
+ if (pWidget->currentIndex() >= 0)
+ aAny <<= static_cast<sal_Int32>(pWidget->currentIndex());
+ break;
+ }
+ default:
+ SAL_WARN("vcl.qt",
+ "undocumented/unimplemented ControlAction for a list " << nControlAction);
+ break;
+ }
+ return aAny;
+}
+
+void QtFilePicker::handleSetListValue(QComboBox* pWidget, sal_Int16 nControlAction,
+ const uno::Any& rValue)
+{
+ switch (nControlAction)
+ {
+ case ControlActions::ADD_ITEM:
+ {
+ OUString sItem;
+ rValue >>= sItem;
+ pWidget->addItem(toQString(sItem));
+ break;
+ }
+ case ControlActions::ADD_ITEMS:
+ {
+ Sequence<OUString> aStringList;
+ rValue >>= aStringList;
+ for (auto const& sItem : std::as_const(aStringList))
+ pWidget->addItem(toQString(sItem));
+ break;
+ }
+ case ControlActions::DELETE_ITEM:
+ {
+ sal_Int32 nPos = 0;
+ rValue >>= nPos;
+ pWidget->removeItem(nPos);
+ break;
+ }
+ case ControlActions::DELETE_ITEMS:
+ {
+ pWidget->clear();
+ break;
+ }
+ case ControlActions::SET_SELECT_ITEM:
+ {
+ sal_Int32 nPos = 0;
+ rValue >>= nPos;
+ pWidget->setCurrentIndex(nPos);
+ break;
+ }
+ default:
+ SAL_WARN("vcl.qt",
+ "undocumented/unimplemented ControlAction for a list " << nControlAction);
+ break;
+ }
+
+ pWidget->setEnabled(pWidget->count() > 0);
+}
+
+void SAL_CALL QtFilePicker::setValue(sal_Int16 controlId, sal_Int16 nControlAction,
+ const uno::Any& value)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ pSalInst->RunInMainThread([this, controlId, nControlAction, &value]() {
+ setValue(controlId, nControlAction, value);
+ });
+ return;
+ }
+
+ if (m_aCustomWidgetsMap.contains(controlId))
+ {
+ QWidget* widget = m_aCustomWidgetsMap.value(controlId);
+ QCheckBox* cb = dynamic_cast<QCheckBox*>(widget);
+ if (cb)
+ cb->setChecked(value.get<bool>());
+ else
+ {
+ QComboBox* combo = dynamic_cast<QComboBox*>(widget);
+ if (combo)
+ handleSetListValue(combo, nControlAction, value);
+ }
+ }
+ else
+ SAL_WARN("vcl.qt", "set value on unknown control " << controlId);
+}
+
+uno::Any SAL_CALL QtFilePicker::getValue(sal_Int16 controlId, sal_Int16 nControlAction)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ uno::Any ret;
+ pSalInst->RunInMainThread([&ret, this, controlId, nControlAction]() {
+ ret = getValue(controlId, nControlAction);
+ });
+ return ret;
+ }
+
+ uno::Any res(false);
+ if (m_aCustomWidgetsMap.contains(controlId))
+ {
+ QWidget* widget = m_aCustomWidgetsMap.value(controlId);
+ QCheckBox* cb = dynamic_cast<QCheckBox*>(widget);
+ if (cb)
+ res <<= cb->isChecked();
+ else
+ {
+ QComboBox* combo = dynamic_cast<QComboBox*>(widget);
+ if (combo)
+ res = handleGetListValue(combo, nControlAction);
+ }
+ }
+ else
+ SAL_WARN("vcl.qt", "get value on unknown control " << controlId);
+
+ return res;
+}
+
+void SAL_CALL QtFilePicker::enableControl(sal_Int16 controlId, sal_Bool enable)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, controlId, enable]() {
+ if (m_aCustomWidgetsMap.contains(controlId))
+ m_aCustomWidgetsMap.value(controlId)->setEnabled(enable);
+ else
+ SAL_WARN("vcl.qt", "enable unknown control " << controlId);
+ });
+}
+
+void SAL_CALL QtFilePicker::setLabel(sal_Int16 controlId, const OUString& label)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ pSalInst->RunInMainThread([this, controlId, label]() { setLabel(controlId, label); });
+ return;
+ }
+
+ if (m_aCustomWidgetsMap.contains(controlId))
+ {
+ QCheckBox* cb = dynamic_cast<QCheckBox*>(m_aCustomWidgetsMap.value(controlId));
+ if (cb)
+ cb->setText(toQString(label));
+ }
+ else
+ SAL_WARN("vcl.qt", "set label on unknown control " << controlId);
+}
+
+OUString SAL_CALL QtFilePicker::getLabel(sal_Int16 controlId)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ OUString ret;
+ pSalInst->RunInMainThread([&ret, this, controlId]() { ret = getLabel(controlId); });
+ return ret;
+ }
+
+ QString label;
+ if (m_aCustomWidgetsMap.contains(controlId))
+ {
+ QCheckBox* cb = dynamic_cast<QCheckBox*>(m_aCustomWidgetsMap.value(controlId));
+ if (cb)
+ label = cb->text();
+ }
+ else
+ SAL_WARN("vcl.qt", "get label on unknown control " << controlId);
+
+ return toOUString(label);
+}
+
+QString QtFilePicker::getResString(TranslateId pResId)
+{
+ QString aResString;
+
+ if (!pResId)
+ return aResString;
+
+ aResString = toQString(FpsResId(pResId));
+
+ return aResString.replace('~', '&');
+}
+
+void QtFilePicker::addCustomControl(sal_Int16 controlId)
+{
+ QWidget* widget = nullptr;
+ QLabel* label = nullptr;
+ TranslateId resId;
+ QCheckBox* pCheckbox = nullptr;
+
+ switch (controlId)
+ {
+ case CHECKBOX_AUTOEXTENSION:
+ resId = STR_SVT_FILEPICKER_AUTO_EXTENSION;
+ break;
+ case CHECKBOX_PASSWORD:
+ resId = STR_SVT_FILEPICKER_PASSWORD;
+ break;
+ case CHECKBOX_FILTEROPTIONS:
+ resId = STR_SVT_FILEPICKER_FILTER_OPTIONS;
+ break;
+ case CHECKBOX_READONLY:
+ resId = STR_SVT_FILEPICKER_READONLY;
+ break;
+ case CHECKBOX_LINK:
+ resId = STR_SVT_FILEPICKER_INSERT_AS_LINK;
+ break;
+ case CHECKBOX_PREVIEW:
+ resId = STR_SVT_FILEPICKER_SHOW_PREVIEW;
+ break;
+ case CHECKBOX_SELECTION:
+ resId = STR_SVT_FILEPICKER_SELECTION;
+ break;
+ case CHECKBOX_GPGENCRYPTION:
+ resId = STR_SVT_FILEPICKER_GPGENCRYPT;
+ break;
+ case PUSHBUTTON_PLAY:
+ resId = STR_SVT_FILEPICKER_PLAY;
+ break;
+ case LISTBOX_VERSION:
+ resId = STR_SVT_FILEPICKER_VERSION;
+ break;
+ case LISTBOX_TEMPLATE:
+ resId = STR_SVT_FILEPICKER_TEMPLATES;
+ break;
+ case LISTBOX_IMAGE_TEMPLATE:
+ resId = STR_SVT_FILEPICKER_IMAGE_TEMPLATE;
+ break;
+ case LISTBOX_IMAGE_ANCHOR:
+ resId = STR_SVT_FILEPICKER_IMAGE_ANCHOR;
+ break;
+ case LISTBOX_VERSION_LABEL:
+ case LISTBOX_TEMPLATE_LABEL:
+ case LISTBOX_IMAGE_TEMPLATE_LABEL:
+ case LISTBOX_IMAGE_ANCHOR_LABEL:
+ case LISTBOX_FILTER_SELECTOR:
+ break;
+ }
+
+ switch (controlId)
+ {
+ case CHECKBOX_AUTOEXTENSION:
+ pCheckbox = new QCheckBox(getResString(resId), m_pExtraControls);
+ // to add/remove automatic file extension based on checkbox
+ connect(pCheckbox, SIGNAL(stateChanged(int)), this,
+ SLOT(updateAutomaticFileExtension()));
+ widget = pCheckbox;
+ break;
+ case CHECKBOX_PASSWORD:
+ case CHECKBOX_FILTEROPTIONS:
+ case CHECKBOX_READONLY:
+ case CHECKBOX_LINK:
+ case CHECKBOX_PREVIEW:
+ case CHECKBOX_SELECTION:
+ case CHECKBOX_GPGENCRYPTION:
+ widget = new QCheckBox(getResString(resId), m_pExtraControls);
+ break;
+ case PUSHBUTTON_PLAY:
+ break;
+ case LISTBOX_VERSION:
+ case LISTBOX_TEMPLATE:
+ case LISTBOX_IMAGE_ANCHOR:
+ case LISTBOX_IMAGE_TEMPLATE:
+ case LISTBOX_FILTER_SELECTOR:
+ label = new QLabel(getResString(resId), m_pExtraControls);
+ widget = new QComboBox(m_pExtraControls);
+ label->setBuddy(widget);
+ break;
+ case LISTBOX_VERSION_LABEL:
+ case LISTBOX_TEMPLATE_LABEL:
+ case LISTBOX_IMAGE_TEMPLATE_LABEL:
+ case LISTBOX_IMAGE_ANCHOR_LABEL:
+ break;
+ }
+
+ if (widget)
+ {
+ const int row = m_pLayout->rowCount();
+ if (label)
+ m_pLayout->addWidget(label, row, 0);
+ m_pLayout->addWidget(widget, row, 1);
+ m_aCustomWidgetsMap.insert(controlId, widget);
+ }
+}
+
+void SAL_CALL QtFilePicker::initialize(const uno::Sequence<uno::Any>& args)
+{
+ // parameter checking
+ uno::Any arg;
+ if (args.getLength() == 0)
+ throw lang::IllegalArgumentException("no arguments", static_cast<XFilePicker2*>(this), 1);
+
+ arg = args[0];
+
+ if ((arg.getValueType() != cppu::UnoType<sal_Int16>::get())
+ && (arg.getValueType() != cppu::UnoType<sal_Int8>::get()))
+ {
+ throw lang::IllegalArgumentException("invalid argument type",
+ static_cast<XFilePicker2*>(this), 1);
+ }
+
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ pSalInst->RunInMainThread([this, args]() { initialize(args); });
+ return;
+ }
+
+ m_aNamedFilterToExtensionMap.clear();
+ m_aNamedFilterList.clear();
+ m_aTitleToFilterMap.clear();
+ m_aCurrentFilter.clear();
+
+ sal_Int16 templateId = -1;
+ arg >>= templateId;
+
+ QFileDialog::AcceptMode acceptMode = QFileDialog::AcceptOpen;
+ switch (templateId)
+ {
+ case FILEOPEN_SIMPLE:
+ break;
+
+ case FILESAVE_SIMPLE:
+ acceptMode = QFileDialog::AcceptSave;
+ break;
+
+ case FILESAVE_AUTOEXTENSION:
+ acceptMode = QFileDialog::AcceptSave;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ break;
+
+ case FILESAVE_AUTOEXTENSION_PASSWORD:
+ acceptMode = QFileDialog::AcceptSave;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ addCustomControl(CHECKBOX_PASSWORD);
+ addCustomControl(CHECKBOX_GPGENCRYPTION);
+ break;
+
+ case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS:
+ acceptMode = QFileDialog::AcceptSave;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ addCustomControl(CHECKBOX_PASSWORD);
+ addCustomControl(CHECKBOX_GPGENCRYPTION);
+ addCustomControl(CHECKBOX_FILTEROPTIONS);
+ break;
+
+ case FILESAVE_AUTOEXTENSION_SELECTION:
+ acceptMode = QFileDialog::AcceptSave;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ addCustomControl(CHECKBOX_SELECTION);
+ break;
+
+ case FILESAVE_AUTOEXTENSION_TEMPLATE:
+ acceptMode = QFileDialog::AcceptSave;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ addCustomControl(LISTBOX_TEMPLATE);
+ break;
+
+ case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE:
+ addCustomControl(CHECKBOX_LINK);
+ addCustomControl(CHECKBOX_PREVIEW);
+ addCustomControl(LISTBOX_IMAGE_TEMPLATE);
+ break;
+
+ case FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR:
+ addCustomControl(CHECKBOX_LINK);
+ addCustomControl(CHECKBOX_PREVIEW);
+ addCustomControl(LISTBOX_IMAGE_ANCHOR);
+ break;
+
+ case FILEOPEN_PLAY:
+ addCustomControl(PUSHBUTTON_PLAY);
+ break;
+
+ case FILEOPEN_LINK_PLAY:
+ addCustomControl(CHECKBOX_LINK);
+ addCustomControl(PUSHBUTTON_PLAY);
+ break;
+
+ case FILEOPEN_READONLY_VERSION:
+ addCustomControl(CHECKBOX_READONLY);
+ addCustomControl(LISTBOX_VERSION);
+ break;
+
+ case FILEOPEN_LINK_PREVIEW:
+ addCustomControl(CHECKBOX_LINK);
+ addCustomControl(CHECKBOX_PREVIEW);
+ break;
+
+ case FILEOPEN_PREVIEW:
+ addCustomControl(CHECKBOX_PREVIEW);
+ break;
+
+ default:
+ throw lang::IllegalArgumentException("Unknown template",
+ static_cast<XFilePicker2*>(this), 1);
+ }
+
+ TranslateId resId;
+ switch (acceptMode)
+ {
+ case QFileDialog::AcceptOpen:
+ resId = STR_FILEDLG_OPEN;
+ break;
+ case QFileDialog::AcceptSave:
+ resId = STR_FILEDLG_SAVE;
+ m_pFileDialog->setFileMode(QFileDialog::AnyFile);
+ break;
+ }
+
+ m_pFileDialog->setAcceptMode(acceptMode);
+ m_pFileDialog->setWindowTitle(getResString(resId));
+
+ css::uno::Reference<css::awt::XWindow> xParentWindow;
+ if (args.getLength() > 1)
+ args[1] >>= xParentWindow;
+ if (!xParentWindow.is())
+ return;
+
+ css::uno::Reference<css::awt::XSystemDependentWindowPeer> xSysWinPeer(xParentWindow,
+ css::uno::UNO_QUERY);
+ if (!xSysWinPeer.is())
+ return;
+
+ // the sal_*Int8 handling is strange, but it's public API - no way around
+ css::uno::Sequence<sal_Int8> aProcessIdent(16);
+ rtl_getGlobalProcessId(reinterpret_cast<sal_uInt8*>(aProcessIdent.getArray()));
+ uno::Any aAny
+ = xSysWinPeer->getWindowHandle(aProcessIdent, css::lang::SystemDependent::SYSTEM_XWINDOW);
+ css::awt::SystemDependentXWindow xSysWin;
+ aAny >>= xSysWin;
+
+ const auto& pFrames = pSalInst->getFrames();
+ const tools::Long aWindowHandle = xSysWin.WindowHandle;
+ const auto it
+ = std::find_if(pFrames.begin(), pFrames.end(), [&aWindowHandle](auto pFrame) -> bool {
+ const SystemEnvData* pData = pFrame->GetSystemData();
+ return pData && tools::Long(pData->GetWindowHandle(pFrame)) == aWindowHandle;
+ });
+ if (it != pFrames.end())
+ m_pParentWidget = static_cast<QtFrame*>(*it)->asChild();
+}
+
+void SAL_CALL QtFilePicker::cancel() { m_pFileDialog->reject(); }
+
+void SAL_CALL QtFilePicker::disposing(const lang::EventObject& rEvent)
+{
+ uno::Reference<XFilePickerListener> xFilePickerListener(rEvent.Source, uno::UNO_QUERY);
+
+ if (xFilePickerListener.is())
+ {
+ removeFilePickerListener(xFilePickerListener);
+ }
+}
+
+void SAL_CALL QtFilePicker::queryTermination(const css::lang::EventObject&)
+{
+ throw css::frame::TerminationVetoException();
+}
+
+void SAL_CALL QtFilePicker::notifyTermination(const css::lang::EventObject&)
+{
+ SolarMutexGuard aGuard;
+ m_pFileDialog->reject();
+}
+
+OUString SAL_CALL QtFilePicker::getImplementationName()
+{
+ return "com.sun.star.ui.dialogs.QtFilePicker";
+}
+
+sal_Bool SAL_CALL QtFilePicker::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+uno::Sequence<OUString> SAL_CALL QtFilePicker::getSupportedServiceNames()
+{
+ return FilePicker_getSupportedServiceNames();
+}
+
+void QtFilePicker::updateAutomaticFileExtension()
+{
+ bool bSetAutoExtension
+ = getValue(CHECKBOX_AUTOEXTENSION, ControlActions::GET_SELECTED_ITEM).get<bool>();
+ if (bSetAutoExtension)
+ {
+ QString sSuffix = m_aNamedFilterToExtensionMap.value(m_pFileDialog->selectedNameFilter());
+ // string is "*.<SUFFIX>" if a specific filter was selected that has exactly one possible file extension
+ if (sSuffix.lastIndexOf("*.") == 0)
+ {
+ sSuffix = sSuffix.remove("*.");
+ m_pFileDialog->setDefaultSuffix(sSuffix);
+ }
+ else
+ {
+ // fall back to setting none otherwise
+ SAL_INFO(
+ "vcl.qt",
+ "Unable to retrieve unambiguous file extension. Will not add any automatically.");
+ bSetAutoExtension = false;
+ }
+ }
+
+ if (!bSetAutoExtension)
+ m_pFileDialog->setDefaultSuffix("");
+}
+
+void QtFilePicker::filterSelected(const QString&)
+{
+ FilePickerEvent aEvent;
+ aEvent.ElementId = LISTBOX_FILTER;
+ SAL_INFO("vcl.qt", "filter changed");
+ if (m_xListener.is())
+ m_xListener->controlStateChanged(aEvent);
+}
+
+void QtFilePicker::currentChanged(const QString&)
+{
+ FilePickerEvent aEvent;
+ SAL_INFO("vcl.qt", "file selection changed");
+ if (m_xListener.is())
+ m_xListener->fileSelectionChanged(aEvent);
+}
+
+OUString QtFilePicker::getDirectory()
+{
+ uno::Sequence<OUString> seq = getSelectedFiles();
+ if (seq.getLength() > 1)
+ seq.realloc(1);
+ return seq[0];
+}
+
+void QtFilePicker::setDescription(const OUString&) {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtFont.cxx b/vcl/qt5/QtFont.cxx
new file mode 100644
index 0000000000..e3a6c0b0a9
--- /dev/null
+++ b/vcl/qt5/QtFont.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <QtFont.hxx>
+#include <QtTools.hxx>
+
+#include <QtGui/QFont>
+#include <QtGui/QRawFont>
+#include <QtGui/QPainterPath>
+
+static inline void applyWeight(QtFont& rFont, FontWeight eWeight)
+{
+ switch (eWeight)
+ {
+ case WEIGHT_THIN:
+ rFont.setWeight(QFont::Thin);
+ break;
+ case WEIGHT_ULTRALIGHT:
+ rFont.setWeight(QFont::ExtraLight);
+ break;
+ case WEIGHT_LIGHT:
+ rFont.setWeight(QFont::Light);
+ break;
+ case WEIGHT_SEMILIGHT:
+ [[fallthrough]];
+ case WEIGHT_NORMAL:
+ rFont.setWeight(QFont::Normal);
+ break;
+ case WEIGHT_MEDIUM:
+ rFont.setWeight(QFont::Medium);
+ break;
+ case WEIGHT_SEMIBOLD:
+ rFont.setWeight(QFont::DemiBold);
+ break;
+ case WEIGHT_BOLD:
+ rFont.setWeight(QFont::Bold);
+ break;
+ case WEIGHT_ULTRABOLD:
+ rFont.setWeight(QFont::ExtraBold);
+ break;
+ case WEIGHT_BLACK:
+ rFont.setWeight(QFont::Black);
+ break;
+ default:
+ break;
+ }
+}
+
+static inline void applyStretch(QtFont& rFont, FontWidth eWidthType)
+{
+ switch (eWidthType)
+ {
+ case WIDTH_DONTKNOW:
+ rFont.setStretch(QFont::AnyStretch);
+ break;
+ case WIDTH_ULTRA_CONDENSED:
+ rFont.setStretch(QFont::UltraCondensed);
+ break;
+ case WIDTH_EXTRA_CONDENSED:
+ rFont.setStretch(QFont::ExtraCondensed);
+ break;
+ case WIDTH_CONDENSED:
+ rFont.setStretch(QFont::Condensed);
+ break;
+ case WIDTH_SEMI_CONDENSED:
+ rFont.setStretch(QFont::SemiCondensed);
+ break;
+ case WIDTH_NORMAL:
+ rFont.setStretch(QFont::Unstretched);
+ break;
+ case WIDTH_SEMI_EXPANDED:
+ rFont.setStretch(QFont::SemiExpanded);
+ break;
+ case WIDTH_EXPANDED:
+ rFont.setStretch(QFont::Expanded);
+ break;
+ case WIDTH_EXTRA_EXPANDED:
+ rFont.setStretch(QFont::ExtraExpanded);
+ break;
+ case WIDTH_ULTRA_EXPANDED:
+ rFont.setStretch(QFont::UltraExpanded);
+ break;
+ default:
+ break;
+ }
+}
+
+static inline void applyStyle(QtFont& rFont, FontItalic eItalic)
+{
+ switch (eItalic)
+ {
+ case ITALIC_NONE:
+ rFont.setStyle(QFont::Style::StyleNormal);
+ break;
+ case ITALIC_OBLIQUE:
+ rFont.setStyle(QFont::Style::StyleOblique);
+ break;
+ case ITALIC_NORMAL:
+ rFont.setStyle(QFont::Style::StyleItalic);
+ break;
+ default:
+ break;
+ }
+}
+
+QtFont::QtFont(const vcl::font::PhysicalFontFace& rPFF, const vcl::font::FontSelectPattern& rFSP)
+ : LogicalFontInstance(rPFF, rFSP)
+{
+ setFamily(toQString(rPFF.GetFamilyName()));
+ applyWeight(*this, rPFF.GetWeight());
+ setPixelSize(rFSP.mnHeight);
+ applyStretch(*this, rPFF.GetWidthType());
+ applyStyle(*this, rFSP.GetItalic());
+}
+
+bool QtFont::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rB2DPolyPoly, bool) const
+{
+ rB2DPolyPoly.clear();
+ basegfx::B2DPolygon aPart;
+ QRawFont aRawFont(QRawFont::fromFont(*this));
+ QPainterPath aQPath = aRawFont.pathForGlyph(nId);
+
+ for (int a(0); a < aQPath.elementCount(); a++)
+ {
+ const QPainterPath::Element aQElement = aQPath.elementAt(a);
+
+ switch (aQElement.type)
+ {
+ case QPainterPath::MoveToElement:
+ {
+ if (aPart.count())
+ {
+ aPart.setClosed(true);
+ rB2DPolyPoly.append(aPart);
+ aPart.clear();
+ }
+
+ aPart.append(basegfx::B2DPoint(aQElement.x, aQElement.y));
+ break;
+ }
+ case QPainterPath::LineToElement:
+ {
+ aPart.append(basegfx::B2DPoint(aQElement.x, aQElement.y));
+ break;
+ }
+ case QPainterPath::CurveToElement:
+ {
+ const QPainterPath::Element aQ2 = aQPath.elementAt(++a);
+ const QPainterPath::Element aQ3 = aQPath.elementAt(++a);
+ aPart.appendBezierSegment(basegfx::B2DPoint(aQElement.x, aQElement.y),
+ basegfx::B2DPoint(aQ2.x, aQ2.y),
+ basegfx::B2DPoint(aQ3.x, aQ3.y));
+ break;
+ }
+ case QPainterPath::CurveToDataElement:
+ {
+ break;
+ }
+ }
+ }
+
+ if (aPart.count())
+ {
+ aPart.setClosed(true);
+ rB2DPolyPoly.append(aPart);
+ aPart.clear();
+ }
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtFontFace.cxx b/vcl/qt5/QtFontFace.cxx
new file mode 100644
index 0000000000..351f597395
--- /dev/null
+++ b/vcl/qt5/QtFontFace.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <unotools/fontdefs.hxx>
+
+#include <QtFontFace.hxx>
+#include <QtFont.hxx>
+#include <QtTools.hxx>
+
+#include <font/LogicalFontInstance.hxx>
+#include <font/FontSelectPattern.hxx>
+#include <font/PhysicalFontCollection.hxx>
+
+#include <QtGui/QFont>
+#include <QtGui/QFontDatabase>
+#include <QtGui/QFontInfo>
+#include <QtGui/QRawFont>
+#include <utility>
+
+using namespace vcl;
+
+QtFontFace::QtFontFace(const QtFontFace& rSrc)
+ : vcl::font::PhysicalFontFace(rSrc)
+ , m_aFontId(rSrc.m_aFontId)
+ , m_eFontIdType(rSrc.m_eFontIdType)
+{
+}
+
+FontWeight QtFontFace::toFontWeight(const int nWeight)
+{
+ if (nWeight <= QFont::Thin)
+ return WEIGHT_THIN;
+ if (nWeight <= QFont::ExtraLight)
+ return WEIGHT_ULTRALIGHT;
+ if (nWeight <= QFont::Light)
+ return WEIGHT_LIGHT;
+ if (nWeight <= QFont::Normal)
+ return WEIGHT_NORMAL;
+ if (nWeight <= QFont::Medium)
+ return WEIGHT_MEDIUM;
+ if (nWeight <= QFont::DemiBold)
+ return WEIGHT_SEMIBOLD;
+ if (nWeight <= QFont::Bold)
+ return WEIGHT_BOLD;
+ if (nWeight <= QFont::ExtraBold)
+ return WEIGHT_ULTRABOLD;
+ return WEIGHT_BLACK;
+}
+
+FontWidth QtFontFace::toFontWidth(const int nStretch)
+{
+ if (nStretch == 0) // QFont::AnyStretch since Qt 5.8
+ return WIDTH_DONTKNOW;
+ if (nStretch <= QFont::UltraCondensed)
+ return WIDTH_ULTRA_CONDENSED;
+ if (nStretch <= QFont::ExtraCondensed)
+ return WIDTH_EXTRA_CONDENSED;
+ if (nStretch <= QFont::Condensed)
+ return WIDTH_CONDENSED;
+ if (nStretch <= QFont::SemiCondensed)
+ return WIDTH_SEMI_CONDENSED;
+ if (nStretch <= QFont::Unstretched)
+ return WIDTH_NORMAL;
+ if (nStretch <= QFont::SemiExpanded)
+ return WIDTH_SEMI_EXPANDED;
+ if (nStretch <= QFont::Expanded)
+ return WIDTH_EXPANDED;
+ if (nStretch <= QFont::ExtraExpanded)
+ return WIDTH_EXTRA_EXPANDED;
+ return WIDTH_ULTRA_EXPANDED;
+}
+
+FontItalic QtFontFace::toFontItalic(const QFont::Style eStyle)
+{
+ switch (eStyle)
+ {
+ case QFont::StyleNormal:
+ return ITALIC_NONE;
+ case QFont::StyleItalic:
+ return ITALIC_NORMAL;
+ case QFont::StyleOblique:
+ return ITALIC_OBLIQUE;
+ }
+
+ return ITALIC_NONE;
+}
+
+void QtFontFace::fillAttributesFromQFont(const QFont& rFont, FontAttributes& rFA)
+{
+ QFontInfo aFontInfo(rFont);
+
+ rFA.SetFamilyName(toOUString(aFontInfo.family()));
+ rFA.SetStyleName(toOUString(aFontInfo.styleName()));
+ rFA.SetPitch(aFontInfo.fixedPitch() ? PITCH_FIXED : PITCH_VARIABLE);
+ rFA.SetWeight(QtFontFace::toFontWeight(aFontInfo.weight()));
+ rFA.SetItalic(QtFontFace::toFontItalic(aFontInfo.style()));
+ rFA.SetWidthType(QtFontFace::toFontWidth(rFont.stretch()));
+}
+
+QtFontFace* QtFontFace::fromQFont(const QFont& rFont)
+{
+ FontAttributes aFA;
+ fillAttributesFromQFont(rFont, aFA);
+ return new QtFontFace(aFA, rFont.toString(), FontIdType::Font);
+}
+
+QtFontFace* QtFontFace::fromQFontDatabase(const QString& aFamily, const QString& aStyle)
+{
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ auto const isFixedPitch = QFontDatabase::isFixedPitch(aFamily, aStyle);
+ auto const weigh = QFontDatabase::weight(aFamily, aStyle);
+ auto const italic = QFontDatabase::italic(aFamily, aStyle);
+ auto const aPointList = QFontDatabase::pointSizes(aFamily, aStyle);
+#else
+ QFontDatabase aFDB;
+ auto const isFixedPitch = aFDB.isFixedPitch(aFamily, aStyle);
+ auto const weigh = aFDB.weight(aFamily, aStyle);
+ auto const italic = aFDB.italic(aFamily, aStyle);
+ auto const aPointList = aFDB.pointSizes(aFamily, aStyle);
+#endif
+
+ FontAttributes aFA;
+
+ aFA.SetFamilyName(toOUString(aFamily));
+ aFA.SetStyleName(toOUString(aStyle));
+ aFA.SetPitch(isFixedPitch ? PITCH_FIXED : PITCH_VARIABLE);
+ aFA.SetWeight(QtFontFace::toFontWeight(weigh));
+ aFA.SetItalic(italic ? ITALIC_NORMAL : ITALIC_NONE);
+
+ int nPointSize = 0;
+ if (!aPointList.empty())
+ nPointSize = aPointList[0];
+
+ return new QtFontFace(aFA, aFamily + "," + aStyle + "," + QString::number(nPointSize),
+ FontIdType::FontDB);
+}
+
+QtFontFace::QtFontFace(const FontAttributes& rFA, QString aFontID, const FontIdType eFontIdType)
+ : PhysicalFontFace(rFA)
+ , m_aFontId(std::move(aFontID))
+ , m_eFontIdType(eFontIdType)
+{
+}
+
+sal_IntPtr QtFontFace::GetFontId() const { return reinterpret_cast<sal_IntPtr>(&m_aFontId); }
+
+QFont QtFontFace::CreateFont() const
+{
+ QFont aFont;
+ switch (m_eFontIdType)
+ {
+ case FontDB:
+ {
+ QStringList aStrList = m_aFontId.split(",");
+ if (3 == aStrList.size())
+ {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ aFont = QFontDatabase::font(aStrList[0], aStrList[1], aStrList[2].toInt());
+#else
+ QFontDatabase aFDB;
+ aFont = aFDB.font(aStrList[0], aStrList[1], aStrList[2].toInt());
+#endif
+ }
+ else
+ SAL_WARN("vcl.qt", "Invalid QFontDatabase font ID " << m_aFontId);
+ break;
+ }
+ case Font:
+ bool bRet = aFont.fromString(m_aFontId);
+ SAL_WARN_IF(!bRet, "vcl.qt", "Failed to create QFont from ID: " << m_aFontId);
+ Q_UNUSED(bRet);
+ break;
+ }
+ return aFont;
+}
+
+rtl::Reference<LogicalFontInstance>
+QtFontFace::CreateFontInstance(const vcl::font::FontSelectPattern& rFSD) const
+{
+ return new QtFont(*this, rFSD);
+}
+
+hb_blob_t* QtFontFace::GetHbTable(hb_tag_t nTag) const
+{
+ char pTagName[5] = { '\0' };
+ hb_tag_to_string(nTag, pTagName);
+
+ QFont aFont = CreateFont();
+ QRawFont aRawFont(QRawFont::fromFont(aFont));
+ QByteArray aTable = aRawFont.fontTable(pTagName);
+ const sal_uInt32 nLength = aTable.size();
+
+ hb_blob_t* pBlob = nullptr;
+ if (nLength > 0)
+ pBlob = hb_blob_create(aTable.data(), nLength, HB_MEMORY_MODE_DUPLICATE, nullptr, nullptr);
+ return pBlob;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtFrame.cxx b/vcl/qt5/QtFrame.cxx
new file mode 100644
index 0000000000..24dcb5ff6f
--- /dev/null
+++ b/vcl/qt5/QtFrame.cxx
@@ -0,0 +1,1520 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <QtFrame.hxx>
+#include <QtFrame.moc>
+
+#include <QtData.hxx>
+#include <QtDragAndDrop.hxx>
+#include <QtFontFace.hxx>
+#include <QtGraphics.hxx>
+#include <QtInstance.hxx>
+#include <QtMainWindow.hxx>
+#include <QtMenu.hxx>
+#include <QtSvpGraphics.hxx>
+#include <QtSystem.hxx>
+#include <QtTools.hxx>
+#include <QtTransferable.hxx>
+#if CHECK_ANY_QT_USING_X11
+#include <QtX11Support.hxx>
+#endif
+
+#include <QtCore/QMimeData>
+#include <QtCore/QPoint>
+#include <QtCore/QSize>
+#include <QtCore/QThread>
+#include <QtGui/QDragMoveEvent>
+#include <QtGui/QDropEvent>
+#include <QtGui/QIcon>
+#include <QtGui/QWindow>
+#include <QtGui/QScreen>
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+#include <QtGui/QStyleHints>
+#endif
+#include <QtWidgets/QStyle>
+#include <QtWidgets/QToolTip>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QMenuBar>
+#include <QtWidgets/QMainWindow>
+#if CHECK_QT5_USING_X11
+#include <QtX11Extras/QX11Info>
+#endif
+
+#include <window.h>
+#include <vcl/syswin.hxx>
+
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+
+#include <cairo.h>
+#include <headless/svpgdi.hxx>
+
+#include <unx/fontmanager.hxx>
+
+static void SvpDamageHandler(void* handle, sal_Int32 nExtentsX, sal_Int32 nExtentsY,
+ sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight)
+{
+ QtFrame* pThis = static_cast<QtFrame*>(handle);
+ pThis->Damage(nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight);
+}
+
+namespace
+{
+sal_Int32 screenNumber(const QScreen* pScreen)
+{
+ const QList<QScreen*> screens = QApplication::screens();
+
+ sal_Int32 nScreen = 0;
+ bool bFound = false;
+ for (const QScreen* pCurScreen : screens)
+ {
+ if (pScreen == pCurScreen)
+ {
+ bFound = true;
+ break;
+ }
+ nScreen++;
+ }
+
+ return bFound ? nScreen : -1;
+}
+}
+
+QtFrame::QtFrame(QtFrame* pParent, SalFrameStyleFlags nStyle, bool bUseCairo)
+ : m_pTopLevel(nullptr)
+ , m_bUseCairo(bUseCairo)
+ , m_bNullRegion(true)
+ , m_bGraphicsInUse(false)
+ , m_ePointerStyle(PointerStyle::Arrow)
+ , m_pDragSource(nullptr)
+ , m_pDropTarget(nullptr)
+ , m_bInDrag(false)
+ , m_bDefaultSize(true)
+ , m_bDefaultPos(true)
+ , m_bFullScreen(false)
+ , m_bFullScreenSpanAll(false)
+#if CHECK_ANY_QT_USING_X11
+ , m_nKeyModifiers(ModKeyFlags::NONE)
+#endif
+ , m_nInputLanguage(LANGUAGE_DONTKNOW)
+{
+ QtInstance* pInst = GetQtInstance();
+ pInst->insertFrame(this);
+
+ m_aDamageHandler.handle = this;
+ m_aDamageHandler.damaged = ::SvpDamageHandler;
+
+ if (nStyle & SalFrameStyleFlags::DEFAULT) // ensure default style
+ {
+ nStyle |= SalFrameStyleFlags::MOVEABLE | SalFrameStyleFlags::SIZEABLE
+ | SalFrameStyleFlags::CLOSEABLE;
+ nStyle &= ~SalFrameStyleFlags::FLOAT;
+ }
+
+ m_nStyle = nStyle;
+ m_pParent = pParent;
+
+ Qt::WindowFlags aWinFlags(Qt::Widget);
+ if (!(nStyle & SalFrameStyleFlags::SYSTEMCHILD))
+ {
+ if (nStyle & SalFrameStyleFlags::INTRO)
+ aWinFlags = Qt::SplashScreen;
+ // floating toolbars are frameless tool windows
+ // + they must be able to receive keyboard focus
+ else if ((nStyle & SalFrameStyleFlags::FLOAT)
+ && (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION))
+ aWinFlags = Qt::Tool | Qt::FramelessWindowHint;
+ else if (nStyle & SalFrameStyleFlags::TOOLTIP)
+ aWinFlags = Qt::ToolTip;
+ // Can't use Qt::Popup, because it grabs the input focus and generates a focus-out event,
+ // instantly auto-closing the LO's editable ComboBox popup.
+ // On X11, the alternative Qt::Window | Qt::FramelessWindowHint | Qt::BypassWindowManagerHint
+ // seems to work well enough, but at least on Wayland and WASM, this results in problems.
+ // So while using Qt::ToolTip, the popups are wrongly advertised via accessibility, at least
+ // the GUI seems to work on all platforms... what a mess.
+ else if (isPopup())
+ aWinFlags = Qt::ToolTip | Qt::FramelessWindowHint;
+ else if (nStyle & SalFrameStyleFlags::TOOLWINDOW)
+ aWinFlags = Qt::Tool;
+ // top level windows can't be transient in Qt, so make them dialogs, if they have a parent. At least
+ // the plasma shell relies on this setting to skip dialogs in the window list. And Qt Xcb will just
+ // set transient for the types Dialog, Sheet, Tool, SplashScreen, ToolTip, Drawer and Popup.
+ else if (nStyle & SalFrameStyleFlags::DIALOG || m_pParent)
+ aWinFlags = Qt::Dialog;
+ else
+ aWinFlags = Qt::Window;
+ }
+
+ if (aWinFlags == Qt::Window)
+ {
+ m_pTopLevel = new QtMainWindow(*this, aWinFlags);
+ m_pQWidget = new QtWidget(*this);
+ m_pTopLevel->setCentralWidget(m_pQWidget);
+ m_pTopLevel->setFocusProxy(m_pQWidget);
+ }
+ else
+ {
+ m_pQWidget = new QtWidget(*this, aWinFlags);
+ // from Qt's POV the popup window doesn't have the input focus, so we must force tooltips...
+ if (isPopup())
+ m_pQWidget->setAttribute(Qt::WA_AlwaysShowToolTips);
+ }
+
+ FillSystemEnvData(m_aSystemData, reinterpret_cast<sal_IntPtr>(this), m_pQWidget);
+
+ QWindow* pChildWindow = windowHandle();
+ connect(pChildWindow, &QWindow::screenChanged, this, &QtFrame::screenChanged);
+
+ if (pParent && !(pParent->m_nStyle & SalFrameStyleFlags::PLUG))
+ {
+ QWindow* pParentWindow = pParent->windowHandle();
+ if (pParentWindow && pChildWindow && (pParentWindow != pChildWindow))
+ pChildWindow->setTransientParent(pParentWindow);
+ }
+
+ SetIcon(SV_ICON_ID_OFFICE);
+}
+
+void QtFrame::screenChanged(QScreen*) { m_pQWidget->fakeResize(); }
+
+void QtFrame::FillSystemEnvData(SystemEnvData& rData, sal_IntPtr pWindow, QWidget* pWidget)
+{
+ assert(rData.platform == SystemEnvData::Platform::Invalid);
+ assert(rData.toolkit == SystemEnvData::Toolkit::Invalid);
+ if (QGuiApplication::platformName() == "wayland")
+ rData.platform = SystemEnvData::Platform::Wayland;
+ else if (QGuiApplication::platformName() == "xcb")
+ rData.platform = SystemEnvData::Platform::Xcb;
+ else if (QGuiApplication::platformName() == "wasm")
+ rData.platform = SystemEnvData::Platform::WASM;
+ else
+ {
+ // maybe add a SystemEnvData::Platform::Unsupported to avoid special cases and not abort?
+ SAL_WARN("vcl.qt",
+ "Unsupported qt VCL platform: " << toOUString(QGuiApplication::platformName()));
+ std::abort();
+ }
+
+ rData.toolkit = SystemEnvData::Toolkit::Qt;
+ rData.aShellWindow = pWindow;
+ rData.pWidget = pWidget;
+}
+
+QtFrame::~QtFrame()
+{
+ QtInstance* pInst = GetQtInstance();
+ pInst->eraseFrame(this);
+ delete asChild();
+ m_aSystemData.aShellWindow = 0;
+}
+
+void QtFrame::Damage(sal_Int32 nExtentsX, sal_Int32 nExtentsY, sal_Int32 nExtentsWidth,
+ sal_Int32 nExtentsHeight) const
+{
+ m_pQWidget->update(scaledQRect(QRect(nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight),
+ 1 / devicePixelRatioF()));
+}
+
+SalGraphics* QtFrame::AcquireGraphics()
+{
+ if (m_bGraphicsInUse)
+ return nullptr;
+
+ m_bGraphicsInUse = true;
+
+ if (m_bUseCairo)
+ {
+ if (!m_pSvpGraphics)
+ {
+ QSize aSize = m_pQWidget->size() * devicePixelRatioF();
+ m_pSvpGraphics.reset(new QtSvpGraphics(this));
+ m_pSurface.reset(
+ cairo_image_surface_create(CAIRO_FORMAT_ARGB32, aSize.width(), aSize.height()));
+ m_pSvpGraphics->setSurface(m_pSurface.get(),
+ basegfx::B2IVector(aSize.width(), aSize.height()));
+ cairo_surface_set_user_data(m_pSurface.get(), QtSvpGraphics::getDamageKey(),
+ &m_aDamageHandler, nullptr);
+ }
+ return m_pSvpGraphics.get();
+ }
+ else
+ {
+ if (!m_pQtGraphics)
+ {
+ m_pQtGraphics.reset(new QtGraphics(this));
+ m_pQImage.reset(
+ new QImage(m_pQWidget->size() * devicePixelRatioF(), Qt_DefaultFormat32));
+ m_pQImage->fill(Qt::transparent);
+ m_pQtGraphics->ChangeQImage(m_pQImage.get());
+ }
+ return m_pQtGraphics.get();
+ }
+}
+
+void QtFrame::ReleaseGraphics(SalGraphics* pSalGraph)
+{
+ (void)pSalGraph;
+ if (m_bUseCairo)
+ assert(pSalGraph == m_pSvpGraphics.get());
+ else
+ assert(pSalGraph == m_pQtGraphics.get());
+ m_bGraphicsInUse = false;
+}
+
+bool QtFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
+{
+ QtInstance* pInst = GetQtInstance();
+ pInst->PostEvent(this, pData.release(), SalEvent::UserEvent);
+ return true;
+}
+
+QWidget* QtFrame::asChild() const
+{
+ if (m_pTopLevel)
+ return m_pTopLevel;
+ return m_pQWidget;
+}
+
+qreal QtFrame::devicePixelRatioF() const { return asChild()->devicePixelRatioF(); }
+
+bool QtFrame::isWindow() const { return asChild()->isWindow(); }
+
+QWindow* QtFrame::windowHandle() const
+{
+ // set attribute 'Qt::WA_NativeWindow' first to make sure a window handle actually exists
+ QWidget* pChild = asChild();
+ assert(pChild->window() == pChild);
+ switch (m_aSystemData.platform)
+ {
+ case SystemEnvData::Platform::Wayland:
+ case SystemEnvData::Platform::Xcb:
+ pChild->setAttribute(Qt::WA_NativeWindow);
+ break;
+ case SystemEnvData::Platform::WASM:
+ // no idea, why Qt::WA_NativeWindow breaks the menubar for EMSCRIPTEN
+ break;
+ case SystemEnvData::Platform::Invalid:
+ std::abort();
+ break;
+ }
+ return pChild->windowHandle();
+}
+
+QScreen* QtFrame::screen() const { return asChild()->screen(); }
+
+bool QtFrame::GetUseDarkMode() const
+{
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+ const QStyleHints* pStyleHints = QApplication::styleHints();
+ return pStyleHints->colorScheme() == Qt::ColorScheme::Dark;
+#else
+ // use same mechanism for determining dark mode preference as xdg-desktop-portal-kde, s.
+ // https://invent.kde.org/plasma/xdg-desktop-portal-kde/-/blob/0a4237549debf9518f8cfbaf531456850c0729bd/src/settings.cpp#L213-227
+ const QPalette aPalette = QApplication::palette();
+ const int nWindowBackGroundGray = qGray(aPalette.window().color().rgb());
+ return nWindowBackGroundGray < 192;
+#endif
+}
+
+bool QtFrame::isMinimized() const { return asChild()->isMinimized(); }
+
+bool QtFrame::isMaximized() const { return asChild()->isMaximized(); }
+
+void QtFrame::SetWindowStateImpl(Qt::WindowStates eState)
+{
+ return asChild()->setWindowState(eState);
+}
+
+void QtFrame::SetTitle(const OUString& rTitle)
+{
+ QtInstance* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread(
+ [this, rTitle]() { m_pQWidget->window()->setWindowTitle(toQString(rTitle)); });
+}
+
+void QtFrame::SetIcon(sal_uInt16 nIcon)
+{
+ if (m_nStyle
+ & (SalFrameStyleFlags::PLUG | SalFrameStyleFlags::SYSTEMCHILD
+ | SalFrameStyleFlags::FLOAT | SalFrameStyleFlags::INTRO
+ | SalFrameStyleFlags::OWNERDRAWDECORATION)
+ || !isWindow())
+ return;
+
+ QString appicon;
+
+ if (nIcon == SV_ICON_ID_TEXT)
+ appicon = "libreoffice-writer";
+ else if (nIcon == SV_ICON_ID_SPREADSHEET)
+ appicon = "libreoffice-calc";
+ else if (nIcon == SV_ICON_ID_DRAWING)
+ appicon = "libreoffice-draw";
+ else if (nIcon == SV_ICON_ID_PRESENTATION)
+ appicon = "libreoffice-impress";
+ else if (nIcon == SV_ICON_ID_DATABASE)
+ appicon = "libreoffice-base";
+ else if (nIcon == SV_ICON_ID_FORMULA)
+ appicon = "libreoffice-math";
+ else
+ appicon = "libreoffice-startcenter";
+
+ QIcon aIcon = QIcon::fromTheme(appicon);
+ m_pQWidget->window()->setWindowIcon(aIcon);
+}
+
+void QtFrame::SetMenu(SalMenu*) {}
+
+void QtFrame::SetExtendedFrameStyle(SalExtStyle /*nExtStyle*/) { /* not needed */}
+
+void QtFrame::Show(bool bVisible, bool bNoActivate)
+{
+ assert(m_pQWidget);
+ if (bVisible == asChild()->isVisible())
+ return;
+
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+
+ if (!bVisible) // hide
+ {
+ pSalInst->RunInMainThread([this]() { asChild()->setVisible(false); });
+ return;
+ }
+
+ // show
+ SetDefaultSize();
+
+ pSalInst->RunInMainThread([this, bNoActivate]() {
+ QWidget* const pChild = asChild();
+ pChild->setVisible(true);
+ pChild->raise();
+ if (!bNoActivate)
+ {
+ pChild->activateWindow();
+ pChild->setFocus();
+ }
+ });
+}
+
+void QtFrame::SetMinClientSize(tools::Long nWidth, tools::Long nHeight)
+{
+ if (!isChild())
+ {
+ const qreal fRatio = devicePixelRatioF();
+ asChild()->setMinimumSize(round(nWidth / fRatio), round(nHeight / fRatio));
+ }
+}
+
+void QtFrame::SetMaxClientSize(tools::Long nWidth, tools::Long nHeight)
+{
+ if (!isChild())
+ {
+ const qreal fRatio = devicePixelRatioF();
+ asChild()->setMaximumSize(round(nWidth / fRatio), round(nHeight / fRatio));
+ }
+}
+
+int QtFrame::menuBarOffset() const
+{
+ QtMainWindow* pTopLevel = m_pParent->GetTopLevelWindow();
+ if (pTopLevel && pTopLevel->menuBar() && pTopLevel->menuBar()->isVisible())
+ return round(pTopLevel->menuBar()->geometry().height() * devicePixelRatioF());
+ return 0;
+}
+
+void QtFrame::SetDefaultPos()
+{
+ if (!m_bDefaultPos)
+ return;
+
+ // center on parent
+ if (m_pParent)
+ {
+ const qreal fRatio = devicePixelRatioF();
+ QWidget* const pParentWin = m_pParent->asChild()->window();
+ QWidget* const pChildWin = asChild()->window();
+ QPoint aPos = (pParentWin->rect().center() - pChildWin->rect().center()) * fRatio;
+ aPos.ry() -= menuBarOffset();
+ SetPosSize(aPos.x(), aPos.y(), 0, 0, SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y);
+ assert(!m_bDefaultPos);
+ }
+ else
+ m_bDefaultPos = false;
+}
+
+Size QtFrame::CalcDefaultSize()
+{
+ assert(isWindow());
+
+ Size aSize;
+ if (!m_bFullScreen)
+ {
+ const QScreen* pScreen = screen();
+ if (!pScreen)
+ pScreen = QGuiApplication::screens().at(0);
+ aSize = bestmaxFrameSizeForScreenSize(toSize(pScreen->size()));
+ }
+ else
+ {
+ if (!m_bFullScreenSpanAll)
+ {
+ aSize = toSize(QGuiApplication::screens().at(maGeometry.screen())->size());
+ }
+ else
+ {
+ QScreen* pScreen = QGuiApplication::screenAt(QPoint(0, 0));
+ aSize = toSize(pScreen->availableVirtualGeometry().size());
+ }
+ }
+
+ return aSize;
+}
+
+void QtFrame::SetDefaultSize()
+{
+ if (!m_bDefaultSize)
+ return;
+
+ Size aDefSize = CalcDefaultSize();
+ SetPosSize(0, 0, aDefSize.Width(), aDefSize.Height(),
+ SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT);
+ assert(!m_bDefaultSize);
+}
+
+void QtFrame::SetPosSize(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ sal_uInt16 nFlags)
+{
+ if (!isWindow() || isChild(true, false))
+ return;
+
+ if (nFlags & (SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT))
+ {
+ if (isChild(false) || !m_pQWidget->isMaximized())
+ {
+ if (!(nFlags & SAL_FRAME_POSSIZE_WIDTH))
+ nWidth = maGeometry.width();
+ else if (!(nFlags & SAL_FRAME_POSSIZE_HEIGHT))
+ nHeight = maGeometry.height();
+
+ if (nWidth > 0 && nHeight > 0)
+ {
+ m_bDefaultSize = false;
+ const int nNewWidth = round(nWidth / devicePixelRatioF());
+ const int nNewHeight = round(nHeight / devicePixelRatioF());
+ if (m_nStyle & SalFrameStyleFlags::SIZEABLE)
+ asChild()->resize(nNewWidth, nNewHeight);
+ else
+ asChild()->setFixedSize(nNewWidth, nNewHeight);
+ }
+
+ // assume the resize happened
+ // needed for calculations and will eventually be corrected by events
+ if (nWidth > 0)
+ maGeometry.setWidth(nWidth);
+ if (nHeight > 0)
+ maGeometry.setHeight(nHeight);
+ }
+ }
+
+ if (!(nFlags & (SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y)))
+ {
+ if (nFlags & (SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT))
+ SetDefaultPos();
+ return;
+ }
+
+ if (m_pParent)
+ {
+ const SalFrameGeometry& aParentGeometry = m_pParent->maGeometry;
+ if (QGuiApplication::isRightToLeft())
+ nX = aParentGeometry.x() + aParentGeometry.width() - nX - maGeometry.width() - 1;
+ else
+ nX += aParentGeometry.x();
+ nY += aParentGeometry.y() + menuBarOffset();
+ }
+
+ if (!(nFlags & SAL_FRAME_POSSIZE_X))
+ nX = maGeometry.x();
+ else if (!(nFlags & SAL_FRAME_POSSIZE_Y))
+ nY = maGeometry.y();
+
+ // assume the reposition happened
+ // needed for calculations and will eventually be corrected by events later
+ maGeometry.setPos({ nX, nY });
+
+ m_bDefaultPos = false;
+ asChild()->move(round(nX / devicePixelRatioF()), round(nY / devicePixelRatioF()));
+}
+
+void QtFrame::GetClientSize(tools::Long& rWidth, tools::Long& rHeight)
+{
+ rWidth = round(m_pQWidget->width() * devicePixelRatioF());
+ rHeight = round(m_pQWidget->height() * devicePixelRatioF());
+}
+
+void QtFrame::GetWorkArea(AbsoluteScreenPixelRectangle& rRect)
+{
+ if (!isWindow())
+ return;
+ QScreen* pScreen = screen();
+ if (!pScreen)
+ return;
+
+ QSize aSize = pScreen->availableVirtualSize() * devicePixelRatioF();
+ rRect = AbsoluteScreenPixelRectangle(0, 0, aSize.width(), aSize.height());
+}
+
+SalFrame* QtFrame::GetParent() const { return m_pParent; }
+
+void QtFrame::SetModal(bool bModal)
+{
+ if (!isWindow() || asChild()->isModal() == bModal)
+ return;
+
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, bModal]() {
+
+ QWidget* const pChild = asChild();
+ const bool bWasVisible = pChild->isVisible();
+
+ // modality change is only effective if the window is hidden
+ if (bWasVisible)
+ {
+ pChild->hide();
+ if (QGuiApplication::platformName() == "xcb")
+ {
+ SAL_WARN("vcl.qt", "SetModal called after Show - apply delay");
+ // tdf#152979 give QXcbConnection some time to avoid
+ // "qt.qpa.xcb: internal error: void QXcbWindow::setNetWmStateOnUnmappedWindow() called on mapped window"
+ QThread::msleep(100);
+ }
+ }
+
+ pChild->setWindowModality(bModal ? Qt::WindowModal : Qt::NonModal);
+
+ if (bWasVisible)
+ pChild->show();
+ });
+}
+
+bool QtFrame::GetModal() const { return isWindow() && windowHandle()->isModal(); }
+
+void QtFrame::SetWindowState(const vcl::WindowData* pState)
+{
+ if (!isWindow() || !pState || isChild(true, false))
+ return;
+
+ const vcl::WindowDataMask nMaxGeometryMask
+ = vcl::WindowDataMask::PosSize | vcl::WindowDataMask::MaximizedX
+ | vcl::WindowDataMask::MaximizedY | vcl::WindowDataMask::MaximizedWidth
+ | vcl::WindowDataMask::MaximizedHeight;
+
+ if ((pState->mask() & vcl::WindowDataMask::State)
+ && (pState->state() & vcl::WindowState::Maximized) && !isMaximized()
+ && (pState->mask() & nMaxGeometryMask) == nMaxGeometryMask)
+ {
+ const qreal fRatio = devicePixelRatioF();
+ QWidget* const pChild = asChild();
+ pChild->resize(ceil(pState->width() / fRatio), ceil(pState->height() / fRatio));
+ pChild->move(ceil(pState->x() / fRatio), ceil(pState->y() / fRatio));
+ SetWindowStateImpl(Qt::WindowMaximized);
+ }
+ else if (pState->mask() & vcl::WindowDataMask::PosSize)
+ {
+ sal_uInt16 nPosSizeFlags = 0;
+ if (pState->mask() & vcl::WindowDataMask::X)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
+ if (pState->mask() & vcl::WindowDataMask::Y)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
+ if (pState->mask() & vcl::WindowDataMask::Width)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
+ if (pState->mask() & vcl::WindowDataMask::Height)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
+ SetPosSize(pState->x(), pState->y(), pState->width(), pState->height(), nPosSizeFlags);
+ }
+ else if (pState->mask() & vcl::WindowDataMask::State && !isChild())
+ {
+ if (pState->state() & vcl::WindowState::Maximized)
+ SetWindowStateImpl(Qt::WindowMaximized);
+ else if (pState->state() & vcl::WindowState::Minimized)
+ SetWindowStateImpl(Qt::WindowMinimized);
+ else
+ SetWindowStateImpl(Qt::WindowNoState);
+ }
+}
+
+bool QtFrame::GetWindowState(vcl::WindowData* pState)
+{
+ pState->setState(vcl::WindowState::Normal);
+ pState->setMask(vcl::WindowDataMask::State);
+ if (isMinimized())
+ pState->rState() |= vcl::WindowState::Minimized;
+ else if (isMaximized())
+ pState->rState() |= vcl::WindowState::Maximized;
+ else
+ {
+ // we want the frame position and the client area size
+ QRect rect = scaledQRect({ asChild()->pos(), asChild()->size() }, devicePixelRatioF());
+ pState->setPosSize(toRectangle(rect));
+ pState->rMask() |= vcl::WindowDataMask::PosSize;
+ }
+
+ return true;
+}
+
+void QtFrame::ShowFullScreen(bool bFullScreen, sal_Int32 nScreen)
+{
+ // only top-level windows can go fullscreen
+ assert(m_pTopLevel);
+
+ if (m_bFullScreen == bFullScreen)
+ return;
+
+ m_bFullScreen = bFullScreen;
+ m_bFullScreenSpanAll = m_bFullScreen && (nScreen < 0);
+
+ // show it if it isn't shown yet
+ if (!isWindow())
+ m_pTopLevel->show();
+
+ if (m_bFullScreen)
+ {
+ m_aRestoreGeometry = m_pTopLevel->geometry();
+ m_nRestoreScreen = maGeometry.screen();
+ SetScreenNumber(m_bFullScreenSpanAll ? m_nRestoreScreen : nScreen);
+ if (!m_bFullScreenSpanAll)
+ windowHandle()->showFullScreen();
+ else
+ windowHandle()->showNormal();
+ }
+ else
+ {
+ SetScreenNumber(m_nRestoreScreen);
+ windowHandle()->showNormal();
+ m_pTopLevel->setGeometry(m_aRestoreGeometry);
+ }
+}
+
+void QtFrame::StartPresentation(bool bStart)
+{
+#if CHECK_ANY_QT_USING_X11
+ // meh - so there's no Qt platform independent solution
+ // https://forum.qt.io/topic/38504/solved-qdialog-in-fullscreen-disable-os-screensaver
+ assert(m_aSystemData.platform != SystemEnvData::Platform::Invalid);
+ unsigned int nRootWindow(0);
+ std::optional<Display*> aDisplay;
+
+#if CHECK_QT5_USING_X11
+ if (QX11Info::isPlatformX11())
+ {
+ nRootWindow = QX11Info::appRootWindow();
+ aDisplay = QX11Info::display();
+ }
+#endif
+
+ m_SessionManagerInhibitor.inhibit(bStart, u"presentation", APPLICATION_INHIBIT_IDLE,
+ nRootWindow, aDisplay);
+#else
+ Q_UNUSED(bStart)
+#endif
+}
+
+void QtFrame::SetAlwaysOnTop(bool bOnTop)
+{
+ QWidget* const pWidget = asChild();
+ const Qt::WindowFlags flags = pWidget->windowFlags();
+ if (bOnTop)
+ pWidget->setWindowFlags(flags | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint);
+ else
+ pWidget->setWindowFlags(flags & ~(Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint));
+}
+
+void QtFrame::ToTop(SalFrameToTop nFlags)
+{
+ QtInstance* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, nFlags]() {
+ QWidget* const pWidget = asChild();
+ if (isWindow() && !(nFlags & SalFrameToTop::GrabFocusOnly))
+ pWidget->raise();
+ if ((nFlags & SalFrameToTop::RestoreWhenMin) || (nFlags & SalFrameToTop::ForegroundTask))
+ {
+ if (nFlags & SalFrameToTop::RestoreWhenMin)
+ pWidget->setWindowState(pWidget->windowState() & ~Qt::WindowMinimized);
+ pWidget->activateWindow();
+ }
+ else if ((nFlags & SalFrameToTop::GrabFocus) || (nFlags & SalFrameToTop::GrabFocusOnly))
+ {
+ if (!(nFlags & SalFrameToTop::GrabFocusOnly))
+ pWidget->activateWindow();
+ pWidget->setFocus(Qt::OtherFocusReason);
+ }
+ });
+}
+
+void QtFrame::SetPointer(PointerStyle ePointerStyle)
+{
+ if (ePointerStyle == m_ePointerStyle)
+ return;
+ m_ePointerStyle = ePointerStyle;
+
+ m_pQWidget->setCursor(GetQtData()->getCursor(ePointerStyle));
+}
+
+void QtFrame::CaptureMouse(bool bMouse)
+{
+ static const char* pEnv = getenv("SAL_NO_MOUSEGRABS");
+ if (pEnv && *pEnv)
+ return;
+
+ if (bMouse)
+ m_pQWidget->grabMouse();
+ else
+ m_pQWidget->releaseMouse();
+}
+
+void QtFrame::SetPointerPos(tools::Long nX, tools::Long nY)
+{
+ // some cursor already exists (and it has m_ePointerStyle shape)
+ // so here we just reposition it
+ QCursor::setPos(m_pQWidget->mapToGlobal(QPoint(nX, nY) / devicePixelRatioF()));
+}
+
+void QtFrame::Flush()
+{
+ // was: QGuiApplication::sync();
+ // but FIXME it causes too many issues, figure out sth better
+
+ // unclear if we need to also flush cairo surface - gtk3 backend
+ // does not do it. QPainter in QtWidget::paintEvent() is
+ // destroyed, so that state should be safely flushed.
+}
+
+bool QtFrame::ShowTooltip(const OUString& rText, const tools::Rectangle& rHelpArea)
+{
+ QRect aHelpArea(toQRect(rHelpArea));
+ if (QGuiApplication::isRightToLeft())
+ aHelpArea.moveLeft(maGeometry.width() - aHelpArea.width() - aHelpArea.left() - 1);
+ m_aTooltipText = rText;
+ m_aTooltipArea = aHelpArea;
+ return true;
+}
+
+void QtFrame::SetInputContext(SalInputContext* pContext)
+{
+ if (!pContext)
+ return;
+
+ if (!(pContext->mnOptions & InputContextFlags::Text))
+ return;
+
+ m_pQWidget->setAttribute(Qt::WA_InputMethodEnabled);
+}
+
+void QtFrame::EndExtTextInput(EndExtTextInputFlags /*nFlags*/)
+{
+ if (m_pQWidget)
+ m_pQWidget->endExtTextInput();
+}
+
+OUString QtFrame::GetKeyName(sal_uInt16 nKeyCode)
+{
+ vcl::KeyCode vclKeyCode(nKeyCode);
+ int nCode = vclKeyCode.GetCode();
+ int nRetCode = 0;
+
+ if (nCode >= KEY_0 && nCode <= KEY_9)
+ nRetCode = (nCode - KEY_0) + Qt::Key_0;
+ else if (nCode >= KEY_A && nCode <= KEY_Z)
+ nRetCode = (nCode - KEY_A) + Qt::Key_A;
+ else if (nCode >= KEY_F1 && nCode <= KEY_F26)
+ nRetCode = (nCode - KEY_F1) + Qt::Key_F1;
+ else
+ {
+ switch (nCode)
+ {
+ case KEY_DOWN:
+ nRetCode = Qt::Key_Down;
+ break;
+ case KEY_UP:
+ nRetCode = Qt::Key_Up;
+ break;
+ case KEY_LEFT:
+ nRetCode = Qt::Key_Left;
+ break;
+ case KEY_RIGHT:
+ nRetCode = Qt::Key_Right;
+ break;
+ case KEY_HOME:
+ nRetCode = Qt::Key_Home;
+ break;
+ case KEY_END:
+ nRetCode = Qt::Key_End;
+ break;
+ case KEY_PAGEUP:
+ nRetCode = Qt::Key_PageUp;
+ break;
+ case KEY_PAGEDOWN:
+ nRetCode = Qt::Key_PageDown;
+ break;
+ case KEY_RETURN:
+ nRetCode = Qt::Key_Return;
+ break;
+ case KEY_ESCAPE:
+ nRetCode = Qt::Key_Escape;
+ break;
+ case KEY_TAB:
+ nRetCode = Qt::Key_Tab;
+ break;
+ case KEY_BACKSPACE:
+ nRetCode = Qt::Key_Backspace;
+ break;
+ case KEY_SPACE:
+ nRetCode = Qt::Key_Space;
+ break;
+ case KEY_INSERT:
+ nRetCode = Qt::Key_Insert;
+ break;
+ case KEY_DELETE:
+ nRetCode = Qt::Key_Delete;
+ break;
+ case KEY_ADD:
+ nRetCode = Qt::Key_Plus;
+ break;
+ case KEY_SUBTRACT:
+ nRetCode = Qt::Key_Minus;
+ break;
+ case KEY_MULTIPLY:
+ nRetCode = Qt::Key_Asterisk;
+ break;
+ case KEY_DIVIDE:
+ nRetCode = Qt::Key_Slash;
+ break;
+ case KEY_POINT:
+ nRetCode = Qt::Key_Period;
+ break;
+ case KEY_COMMA:
+ nRetCode = Qt::Key_Comma;
+ break;
+ case KEY_LESS:
+ nRetCode = Qt::Key_Less;
+ break;
+ case KEY_GREATER:
+ nRetCode = Qt::Key_Greater;
+ break;
+ case KEY_EQUAL:
+ nRetCode = Qt::Key_Equal;
+ break;
+ case KEY_FIND:
+ nRetCode = Qt::Key_Find;
+ break;
+ case KEY_CONTEXTMENU:
+ nRetCode = Qt::Key_Menu;
+ break;
+ case KEY_HELP:
+ nRetCode = Qt::Key_Help;
+ break;
+ case KEY_UNDO:
+ nRetCode = Qt::Key_Undo;
+ break;
+ case KEY_REPEAT:
+ nRetCode = Qt::Key_Redo;
+ break;
+ case KEY_TILDE:
+ nRetCode = Qt::Key_AsciiTilde;
+ break;
+ case KEY_QUOTELEFT:
+ nRetCode = Qt::Key_QuoteLeft;
+ break;
+ case KEY_BRACKETLEFT:
+ nRetCode = Qt::Key_BracketLeft;
+ break;
+ case KEY_BRACKETRIGHT:
+ nRetCode = Qt::Key_BracketRight;
+ break;
+ case KEY_NUMBERSIGN:
+ nRetCode = Qt::Key_NumberSign;
+ break;
+ case KEY_XF86FORWARD:
+ nRetCode = Qt::Key_Forward;
+ break;
+ case KEY_XF86BACK:
+ nRetCode = Qt::Key_Back;
+ break;
+ case KEY_COLON:
+ nRetCode = Qt::Key_Colon;
+ break;
+ case KEY_SEMICOLON:
+ nRetCode = Qt::Key_Semicolon;
+ break;
+
+ // Special cases
+ case KEY_COPY:
+ nRetCode = Qt::Key_Copy;
+ break;
+ case KEY_CUT:
+ nRetCode = Qt::Key_Cut;
+ break;
+ case KEY_PASTE:
+ nRetCode = Qt::Key_Paste;
+ break;
+ case KEY_OPEN:
+ nRetCode = Qt::Key_Open;
+ break;
+ }
+ }
+
+ if (vclKeyCode.IsShift())
+ nRetCode += Qt::SHIFT;
+ if (vclKeyCode.IsMod1())
+ nRetCode += Qt::CTRL;
+ if (vclKeyCode.IsMod2())
+ nRetCode += Qt::ALT;
+
+ QKeySequence keySeq(nRetCode);
+ OUString sKeyName = toOUString(keySeq.toString());
+
+ return sKeyName;
+}
+
+bool QtFrame::MapUnicodeToKeyCode(sal_Unicode /*aUnicode*/, LanguageType /*aLangType*/,
+ vcl::KeyCode& /*rKeyCode*/)
+{
+ // not supported yet
+ return false;
+}
+
+LanguageType QtFrame::GetInputLanguage() { return m_nInputLanguage; }
+
+void QtFrame::setInputLanguage(LanguageType nInputLanguage)
+{
+ if (nInputLanguage == m_nInputLanguage)
+ return;
+ m_nInputLanguage = nInputLanguage;
+ CallCallback(SalEvent::InputLanguageChange, nullptr);
+}
+
+static Color toColor(const QColor& rColor)
+{
+ return Color(rColor.red(), rColor.green(), rColor.blue());
+}
+
+static bool toVclFont(const QFont& rQFont, const css::lang::Locale& rLocale, vcl::Font& rVclFont)
+{
+ FontAttributes aFA;
+ QtFontFace::fillAttributesFromQFont(rQFont, aFA);
+
+ bool bFound = psp::PrintFontManager::get().matchFont(aFA, rLocale);
+ SAL_INFO("vcl.qt", "font match result for '"
+ << rQFont.family() << "': "
+ << (bFound ? OUString::Concat("'") + aFA.GetFamilyName() + "'"
+ : OUString("failed")));
+
+ if (!bFound)
+ return false;
+
+ QFontInfo qFontInfo(rQFont);
+ int nPointHeight = qFontInfo.pointSize();
+ if (nPointHeight <= 0)
+ nPointHeight = rQFont.pointSize();
+
+ vcl::Font aFont(aFA.GetFamilyName(), Size(0, nPointHeight));
+ if (aFA.GetWeight() != WEIGHT_DONTKNOW)
+ aFont.SetWeight(aFA.GetWeight());
+ if (aFA.GetWidthType() != WIDTH_DONTKNOW)
+ aFont.SetWidthType(aFA.GetWidthType());
+ if (aFA.GetItalic() != ITALIC_DONTKNOW)
+ aFont.SetItalic(aFA.GetItalic());
+ if (aFA.GetPitch() != PITCH_DONTKNOW)
+ aFont.SetPitch(aFA.GetPitch());
+
+ rVclFont = aFont;
+ return true;
+}
+
+void QtFrame::UpdateSettings(AllSettings& rSettings)
+{
+ if (QtData::noNativeControls())
+ return;
+
+ StyleSettings style(rSettings.GetStyleSettings());
+ const css::lang::Locale aLocale = rSettings.GetUILanguageTag().getLocale();
+
+ // General settings
+ QPalette pal = QApplication::palette();
+
+ style.SetToolbarIconSize(ToolbarIconSize::Large);
+
+ Color aFore = toColor(pal.color(QPalette::Active, QPalette::WindowText));
+ Color aBack = toColor(pal.color(QPalette::Active, QPalette::Window));
+ Color aText = toColor(pal.color(QPalette::Active, QPalette::Text));
+ Color aBase = toColor(pal.color(QPalette::Active, QPalette::Base));
+ Color aButn = toColor(pal.color(QPalette::Active, QPalette::ButtonText));
+ Color aMid = toColor(pal.color(QPalette::Active, QPalette::Mid));
+ Color aHigh = toColor(pal.color(QPalette::Active, QPalette::Highlight));
+ Color aHighText = toColor(pal.color(QPalette::Active, QPalette::HighlightedText));
+ Color aLink = toColor(pal.color(QPalette::Active, QPalette::Link));
+ Color aVisitedLink = toColor(pal.color(QPalette::Active, QPalette::LinkVisited));
+
+ style.SetSkipDisabledInMenus(true);
+
+ // Foreground
+ style.SetRadioCheckTextColor(aFore);
+ style.SetLabelTextColor(aFore);
+ style.SetDialogTextColor(aFore);
+ style.SetGroupTextColor(aFore);
+
+ // Text
+ style.SetFieldTextColor(aText);
+ style.SetFieldRolloverTextColor(aText);
+ style.SetListBoxWindowTextColor(aText);
+ style.SetWindowTextColor(aText);
+ style.SetToolTextColor(aText);
+
+ // Base
+ style.SetFieldColor(aBase);
+ style.SetWindowColor(aBase);
+ style.SetActiveTabColor(aBase);
+ style.SetListBoxWindowBackgroundColor(aBase);
+ style.SetAlternatingRowColor(toColor(pal.color(QPalette::Active, QPalette::AlternateBase)));
+
+ // Buttons
+ style.SetDefaultButtonTextColor(aButn);
+ style.SetButtonTextColor(aButn);
+ style.SetDefaultActionButtonTextColor(aButn);
+ style.SetActionButtonTextColor(aButn);
+ style.SetFlatButtonTextColor(aButn);
+ style.SetDefaultButtonRolloverTextColor(aButn);
+ style.SetButtonRolloverTextColor(aButn);
+ style.SetDefaultActionButtonRolloverTextColor(aButn);
+ style.SetActionButtonRolloverTextColor(aButn);
+ style.SetFlatButtonRolloverTextColor(aButn);
+ style.SetDefaultButtonPressedRolloverTextColor(aButn);
+ style.SetButtonPressedRolloverTextColor(aButn);
+ style.SetDefaultActionButtonPressedRolloverTextColor(aButn);
+ style.SetActionButtonPressedRolloverTextColor(aButn);
+ style.SetFlatButtonPressedRolloverTextColor(aButn);
+
+ // Tabs
+ style.SetTabTextColor(aButn);
+ style.SetTabRolloverTextColor(aButn);
+ style.SetTabHighlightTextColor(aButn);
+
+ // Disable color
+ style.SetDisableColor(toColor(pal.color(QPalette::Disabled, QPalette::WindowText)));
+
+ // Background
+ style.BatchSetBackgrounds(aBack);
+ style.SetInactiveTabColor(aBack);
+
+ // Workspace
+ style.SetWorkspaceColor(aMid);
+
+ // Selection
+ // https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/305
+ style.SetAccentColor(aHigh);
+ style.SetHighlightColor(aHigh);
+ style.SetHighlightTextColor(aHighText);
+ style.SetListBoxWindowHighlightColor(aHigh);
+ style.SetListBoxWindowHighlightTextColor(aHighText);
+ style.SetActiveColor(aHigh);
+ style.SetActiveTextColor(aHighText);
+
+ // Links
+ style.SetLinkColor(aLink);
+ style.SetVisitedLinkColor(aVisitedLink);
+
+ // Tooltip
+ style.SetHelpColor(toColor(QToolTip::palette().color(QPalette::Active, QPalette::ToolTipBase)));
+ style.SetHelpTextColor(
+ toColor(QToolTip::palette().color(QPalette::Active, QPalette::ToolTipText)));
+
+ // Menu
+ std::unique_ptr<QMenuBar> pMenuBar = std::make_unique<QMenuBar>();
+ QPalette qMenuCG = pMenuBar->palette();
+
+ // Menu text and background color, theme specific
+ Color aMenuFore = toColor(qMenuCG.color(QPalette::WindowText));
+ Color aMenuBack = toColor(qMenuCG.color(QPalette::Window));
+
+ style.SetMenuTextColor(aMenuFore);
+ style.SetMenuBarTextColor(style.GetPersonaMenuBarTextColor().value_or(aMenuFore));
+ style.SetMenuColor(aMenuBack);
+ style.SetMenuBarColor(aMenuBack);
+ style.SetMenuHighlightColor(toColor(qMenuCG.color(QPalette::Highlight)));
+ style.SetMenuHighlightTextColor(toColor(qMenuCG.color(QPalette::HighlightedText)));
+
+ // set special menubar highlight text color
+ if (QApplication::style()->inherits("HighContrastStyle"))
+ ImplGetSVData()->maNWFData.maMenuBarHighlightTextColor
+ = toColor(qMenuCG.color(QPalette::HighlightedText));
+ else
+ ImplGetSVData()->maNWFData.maMenuBarHighlightTextColor = aMenuFore;
+
+ // set menubar rollover color
+ if (pMenuBar->style()->styleHint(QStyle::SH_MenuBar_MouseTracking))
+ {
+ style.SetMenuBarRolloverColor(toColor(qMenuCG.color(QPalette::Highlight)));
+ style.SetMenuBarRolloverTextColor(ImplGetSVData()->maNWFData.maMenuBarHighlightTextColor);
+ }
+ else
+ {
+ style.SetMenuBarRolloverColor(aMenuBack);
+ style.SetMenuBarRolloverTextColor(aMenuFore);
+ }
+ style.SetMenuBarHighlightTextColor(style.GetMenuHighlightTextColor());
+
+ // Default fonts
+ vcl::Font aFont;
+ if (toVclFont(QApplication::font(), aLocale, aFont))
+ {
+ style.BatchSetFonts(aFont, aFont);
+ aFont.SetWeight(WEIGHT_BOLD);
+ style.SetTitleFont(aFont);
+ style.SetFloatTitleFont(aFont);
+ }
+
+ // Tooltip font
+ if (toVclFont(QToolTip::font(), aLocale, aFont))
+ style.SetHelpFont(aFont);
+
+ // Menu bar font
+ if (toVclFont(pMenuBar->font(), aLocale, aFont))
+ style.SetMenuFont(aFont);
+
+ // Icon theme
+ const bool bPreferDarkTheme = GetUseDarkMode();
+ style.SetPreferredIconTheme(toOUString(QIcon::themeName()), bPreferDarkTheme);
+
+ // Scroll bar size
+ style.SetScrollBarSize(QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent));
+ style.SetMinThumbSize(QApplication::style()->pixelMetric(QStyle::PM_ScrollBarSliderMin));
+
+ // These colors are used for the ruler text and marks
+ style.SetShadowColor(toColor(pal.color(QPalette::Disabled, QPalette::WindowText)));
+ style.SetDarkShadowColor(toColor(pal.color(QPalette::Inactive, QPalette::WindowText)));
+
+ // Cursor blink interval
+ int nFlashTime = QApplication::cursorFlashTime();
+ style.SetCursorBlinkTime(nFlashTime != 0 ? nFlashTime / 2 : STYLE_CURSOR_NOBLINKTIME);
+
+ rSettings.SetStyleSettings(style);
+}
+
+void QtFrame::Beep() { QApplication::beep(); }
+
+SalFrame::SalPointerState QtFrame::GetPointerState()
+{
+ SalPointerState aState;
+ aState.maPos = toPoint(QCursor::pos() * devicePixelRatioF());
+ aState.maPos.Move(-maGeometry.x(), -maGeometry.y());
+ aState.mnState = GetMouseModCode(QGuiApplication::mouseButtons())
+ | GetKeyModCode(QGuiApplication::keyboardModifiers());
+ return aState;
+}
+
+KeyIndicatorState QtFrame::GetIndicatorState() { return KeyIndicatorState(); }
+
+void QtFrame::SimulateKeyPress(sal_uInt16 nKeyCode)
+{
+ SAL_WARN("vcl.qt", "missing simulate keypress " << nKeyCode);
+}
+
+// don't set QWidget parents; this breaks popups on Wayland, like the LO ComboBox or ColorPicker!
+void QtFrame::SetParent(SalFrame* pNewParent) { m_pParent = static_cast<QtFrame*>(pNewParent); }
+
+void QtFrame::SetPluginParent(SystemParentData* /*pNewParent*/)
+{
+ //FIXME: no SetPluginParent impl. for qt5
+}
+
+void QtFrame::ResetClipRegion() { m_bNullRegion = true; }
+
+void QtFrame::BeginSetClipRegion(sal_uInt32)
+{
+ m_aRegion = QRegion(QRect(QPoint(0, 0), m_pQWidget->size()));
+}
+
+void QtFrame::UnionClipRegion(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight)
+{
+ m_aRegion
+ = m_aRegion.united(scaledQRect(QRect(nX, nY, nWidth, nHeight), 1 / devicePixelRatioF()));
+}
+
+void QtFrame::EndSetClipRegion() { m_bNullRegion = false; }
+
+void QtFrame::SetScreenNumber(unsigned int nScreen)
+{
+ if (!isWindow())
+ return;
+
+ QWindow* const pWindow = windowHandle();
+ if (!pWindow)
+ return;
+
+ QList<QScreen*> screens = QApplication::screens();
+ if (static_cast<int>(nScreen) < screens.size() || m_bFullScreenSpanAll)
+ {
+ QRect screenGeo;
+
+ if (!m_bFullScreenSpanAll)
+ {
+ screenGeo = QGuiApplication::screens().at(nScreen)->geometry();
+ pWindow->setScreen(QApplication::screens()[nScreen]);
+ }
+ else // special case: fullscreen over all available screens
+ {
+ assert(m_bFullScreen);
+ // left-most screen
+ QScreen* pScreen = QGuiApplication::screenAt(QPoint(0, 0));
+ // entire virtual desktop
+ screenGeo = pScreen->availableVirtualGeometry();
+ pWindow->setScreen(pScreen);
+ pWindow->setGeometry(screenGeo);
+ nScreen = screenNumber(pScreen);
+ }
+
+ // setScreen by itself has no effect, explicitly move the widget to
+ // the new screen
+ asChild()->move(screenGeo.topLeft());
+ }
+ else
+ {
+ // index outta bounds, use primary screen
+ QScreen* primaryScreen = QApplication::primaryScreen();
+ pWindow->setScreen(primaryScreen);
+ nScreen = static_cast<sal_uInt32>(screenNumber(primaryScreen));
+ }
+
+ maGeometry.setScreen(nScreen);
+}
+
+void QtFrame::SetApplicationID(const OUString& rWMClass)
+{
+#if CHECK_QT5_USING_X11
+ assert(m_aSystemData.platform != SystemEnvData::Platform::Invalid);
+ if (m_aSystemData.platform != SystemEnvData::Platform::Xcb || !m_pTopLevel)
+ return;
+
+ QtX11Support::setApplicationID(m_pTopLevel->winId(), rWMClass);
+#else
+ Q_UNUSED(rWMClass);
+#endif
+}
+
+void QtFrame::ResolveWindowHandle(SystemEnvData& rData) const
+{
+ if (!rData.pWidget)
+ return;
+ assert(rData.platform != SystemEnvData::Platform::Invalid);
+ if (rData.platform != SystemEnvData::Platform::Wayland)
+ rData.SetWindowHandle(static_cast<QWidget*>(rData.pWidget)->winId());
+}
+
+bool QtFrame::GetUseReducedAnimation() const { return GetQtInstance()->GetUseReducedAnimation(); }
+
+// Drag'n'drop foo
+
+void QtFrame::registerDragSource(QtDragSource* pDragSource)
+{
+ assert(!m_pDragSource);
+ m_pDragSource = pDragSource;
+}
+
+void QtFrame::deregisterDragSource(QtDragSource const* pDragSource)
+{
+ assert(m_pDragSource == pDragSource);
+ (void)pDragSource;
+ m_pDragSource = nullptr;
+}
+
+void QtFrame::registerDropTarget(QtDropTarget* pDropTarget)
+{
+ assert(!m_pDropTarget);
+ m_pDropTarget = pDropTarget;
+
+ QtInstance* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this]() { m_pQWidget->setAcceptDrops(true); });
+}
+
+void QtFrame::deregisterDropTarget(QtDropTarget const* pDropTarget)
+{
+ assert(m_pDropTarget == pDropTarget);
+ (void)pDropTarget;
+ m_pDropTarget = nullptr;
+}
+
+static css::uno::Reference<css::datatransfer::XTransferable>
+lcl_getXTransferable(const QMimeData* pMimeData)
+{
+ css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
+ const QtMimeData* pQtMimeData = dynamic_cast<const QtMimeData*>(pMimeData);
+ if (!pQtMimeData)
+ xTransferable = new QtDnDTransferable(pMimeData);
+ else
+ xTransferable = pQtMimeData->xTransferable();
+ return xTransferable;
+}
+
+static sal_Int8 lcl_getUserDropAction(const QDropEvent* pEvent, const sal_Int8 nSourceActions,
+ const QMimeData* pMimeData)
+{
+// we completely ignore all proposals by the Qt event, as they don't
+// match at all with the preferred LO DnD actions.
+// check the key modifiers to detect a user-overridden DnD action
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ const Qt::KeyboardModifiers eKeyMod = pEvent->modifiers();
+#else
+ const Qt::KeyboardModifiers eKeyMod = pEvent->keyboardModifiers();
+#endif
+ sal_Int8 nUserDropAction = 0;
+ if ((eKeyMod & Qt::ShiftModifier) && !(eKeyMod & Qt::ControlModifier))
+ nUserDropAction = css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ else if ((eKeyMod & Qt::ControlModifier) && !(eKeyMod & Qt::ShiftModifier))
+ nUserDropAction = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
+ else if ((eKeyMod & Qt::ShiftModifier) && (eKeyMod & Qt::ControlModifier))
+ nUserDropAction = css::datatransfer::dnd::DNDConstants::ACTION_LINK;
+ nUserDropAction &= nSourceActions;
+
+ // select the default DnD action, if there isn't a user preference
+ if (0 == nUserDropAction)
+ {
+ // default LO internal action is move, but default external action is copy
+ nUserDropAction = dynamic_cast<const QtMimeData*>(pMimeData)
+ ? css::datatransfer::dnd::DNDConstants::ACTION_MOVE
+ : css::datatransfer::dnd::DNDConstants::ACTION_COPY;
+ nUserDropAction &= nSourceActions;
+
+ // if the default doesn't match any allowed source action, fall back to the
+ // preferred of all allowed source actions
+ if (0 == nUserDropAction)
+ nUserDropAction = toVclDropAction(getPreferredDropAction(nSourceActions));
+
+ // this is "our" preference, but actually we would even prefer any default,
+ // if there is any
+ nUserDropAction |= css::datatransfer::dnd::DNDConstants::ACTION_DEFAULT;
+ }
+ return nUserDropAction;
+}
+
+void QtFrame::handleDragMove(QDragMoveEvent* pEvent)
+{
+ assert(m_pDropTarget);
+
+ // prepare our suggested drop action for the drop target
+ const sal_Int8 nSourceActions = toVclDropActions(pEvent->possibleActions());
+ const QMimeData* pMimeData = pEvent->mimeData();
+ const sal_Int8 nUserDropAction = lcl_getUserDropAction(pEvent, nSourceActions, pMimeData);
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ const Point aPos = toPoint(pEvent->position().toPoint() * devicePixelRatioF());
+#else
+ const Point aPos = toPoint(pEvent->pos() * devicePixelRatioF());
+#endif
+
+ css::datatransfer::dnd::DropTargetDragEnterEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(m_pDropTarget);
+ aEvent.Context = static_cast<css::datatransfer::dnd::XDropTargetDragContext*>(m_pDropTarget);
+ aEvent.LocationX = aPos.X();
+ aEvent.LocationY = aPos.Y();
+ aEvent.DropAction = nUserDropAction;
+ aEvent.SourceActions = nSourceActions;
+
+ // ask the drop target to accept our drop action
+ if (!m_bInDrag)
+ {
+ aEvent.SupportedDataFlavors = lcl_getXTransferable(pMimeData)->getTransferDataFlavors();
+ m_pDropTarget->fire_dragEnter(aEvent);
+ m_bInDrag = true;
+ }
+ else
+ m_pDropTarget->fire_dragOver(aEvent);
+
+ // the drop target accepted our drop action => inform Qt
+ if (m_pDropTarget->proposedDropAction() != 0)
+ {
+ pEvent->setDropAction(getPreferredDropAction(m_pDropTarget->proposedDropAction()));
+ pEvent->accept();
+ }
+ else // or maybe someone else likes it?
+ pEvent->ignore();
+}
+
+void QtFrame::handleDrop(QDropEvent* pEvent)
+{
+ assert(m_pDropTarget);
+
+ // prepare our suggested drop action for the drop target
+ const sal_Int8 nSourceActions = toVclDropActions(pEvent->possibleActions());
+ const sal_Int8 nUserDropAction
+ = lcl_getUserDropAction(pEvent, nSourceActions, pEvent->mimeData());
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ const Point aPos = toPoint(pEvent->position().toPoint() * devicePixelRatioF());
+#else
+ const Point aPos = toPoint(pEvent->pos() * devicePixelRatioF());
+#endif
+
+ css::datatransfer::dnd::DropTargetDropEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(m_pDropTarget);
+ aEvent.Context = static_cast<css::datatransfer::dnd::XDropTargetDropContext*>(m_pDropTarget);
+ aEvent.LocationX = aPos.X();
+ aEvent.LocationY = aPos.Y();
+ aEvent.SourceActions = nSourceActions;
+ aEvent.DropAction = nUserDropAction;
+ aEvent.Transferable = lcl_getXTransferable(pEvent->mimeData());
+
+ // ask the drop target to accept our drop action
+ m_pDropTarget->fire_drop(aEvent);
+ m_bInDrag = false;
+
+ const bool bDropSuccessful = m_pDropTarget->dropSuccessful();
+ const sal_Int8 nDropAction = m_pDropTarget->proposedDropAction();
+
+ // inform the drag source of the drag-origin frame of the drop result
+ if (pEvent->source())
+ {
+ QtWidget* pWidget = dynamic_cast<QtWidget*>(pEvent->source());
+ assert(pWidget); // AFAIK there shouldn't be any non-Qt5Widget as source in LO itself
+ if (pWidget)
+ pWidget->frame().m_pDragSource->fire_dragEnd(nDropAction, bDropSuccessful);
+ }
+
+ // the drop target accepted our drop action => inform Qt
+ if (bDropSuccessful)
+ {
+ pEvent->setDropAction(getPreferredDropAction(nDropAction));
+ pEvent->accept();
+ }
+ else // or maybe someone else likes it?
+ pEvent->ignore();
+}
+
+void QtFrame::handleDragLeave()
+{
+ css::datatransfer::dnd::DropTargetEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(m_pDropTarget);
+ m_pDropTarget->fire_dragExit(aEvent);
+ m_bInDrag = false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtGraphics.cxx b/vcl/qt5/QtGraphics.cxx
new file mode 100644
index 0000000000..d809556ce2
--- /dev/null
+++ b/vcl/qt5/QtGraphics.cxx
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <QtGraphics.hxx>
+
+#include <QtData.hxx>
+#include <QtFont.hxx>
+#include <QtFrame.hxx>
+#include <QtGraphics_Controls.hxx>
+#include <QtPainter.hxx>
+
+#include <QtGui/QImage>
+#include <QtGui/QPainter>
+#include <QtWidgets/QPushButton>
+#include <QtWidgets/QWidget>
+
+QtGraphics::QtGraphics( QtFrame *pFrame, QImage *pQImage )
+ : m_pFrame( pFrame )
+ , m_pTextStyle{ nullptr, }
+ , m_aTextColor( 0x00, 0x00, 0x00 )
+{
+ m_pBackend = std::make_unique<QtGraphicsBackend>(m_pFrame, pQImage);
+
+ if (!initWidgetDrawBackends(false))
+ {
+ if (!QtData::noNativeControls())
+ m_pWidgetDraw.reset(new QtGraphics_Controls(*this));
+ }
+ if (m_pFrame)
+ setDevicePixelRatioF(m_pFrame->devicePixelRatioF());
+}
+
+QtGraphics::~QtGraphics() { ReleaseFonts(); }
+
+void QtGraphics::ChangeQImage(QImage* pQImage)
+{
+ m_pBackend->setQImage(pQImage);
+ m_pBackend->ResetClipRegion();
+}
+
+SalGraphicsImpl* QtGraphics::GetImpl() const { return m_pBackend.get(); }
+
+SystemGraphicsData QtGraphics::GetGraphicsData() const { return SystemGraphicsData(); }
+
+#if ENABLE_CAIRO_CANVAS
+
+bool QtGraphics::SupportsCairo() const { return false; }
+
+cairo::SurfaceSharedPtr
+QtGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& /*rSurface*/) const
+{
+ return nullptr;
+}
+
+cairo::SurfaceSharedPtr QtGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int /*x*/,
+ int /*y*/, int /*width*/, int /*height*/) const
+{
+ return nullptr;
+}
+
+cairo::SurfaceSharedPtr QtGraphics::CreateBitmapSurface(const OutputDevice& /*rRefDevice*/,
+ const BitmapSystemData& /*rData*/,
+ const Size& /*rSize*/) const
+{
+ return nullptr;
+}
+
+css::uno::Any QtGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& /*rSurface*/,
+ const basegfx::B2ISize& /*rSize*/) const
+{
+ return css::uno::Any();
+}
+
+#endif
+
+void QtGraphics::handleDamage(const tools::Rectangle& rDamagedRegion)
+{
+ assert(m_pWidgetDraw);
+ assert(dynamic_cast<QtGraphics_Controls*>(m_pWidgetDraw.get()));
+ assert(!rDamagedRegion.IsEmpty());
+
+ QImage* pImage = static_cast<QtGraphics_Controls*>(m_pWidgetDraw.get())->getImage();
+ QImage blit(*pImage);
+ blit.setDevicePixelRatio(1);
+ QtPainter aPainter(*m_pBackend);
+ aPainter.drawImage(QPoint(rDamagedRegion.Left(), rDamagedRegion.Top()), blit);
+ aPainter.update(toQRect(rDamagedRegion));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtGraphics_Controls.cxx b/vcl/qt5/QtGraphics_Controls.cxx
new file mode 100644
index 0000000000..81ab7a7edc
--- /dev/null
+++ b/vcl/qt5/QtGraphics_Controls.cxx
@@ -0,0 +1,1168 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <QtGraphics_Controls.hxx>
+
+#include <QtGui/QPainter>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QFrame>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QLineEdit>
+
+#include <QtTools.hxx>
+#include <QtGraphicsBase.hxx>
+#include <vcl/decoview.hxx>
+
+/**
+ Conversion function between VCL ControlState together with
+ ImplControlValue and Qt state flags.
+ @param nControlState State of the widget (default, focused, ...) in Native Widget Framework.
+ @param aValue Value held by the widget (on, off, ...)
+*/
+static QStyle::State vclStateValue2StateFlag(ControlState nControlState,
+ const ImplControlValue& aValue)
+{
+ QStyle::State nState
+ = ((nControlState & ControlState::ENABLED) ? QStyle::State_Enabled : QStyle::State_None)
+ | ((nControlState & ControlState::FOCUSED)
+ ? QStyle::State_HasFocus | QStyle::State_KeyboardFocusChange
+ : QStyle::State_None)
+ | ((nControlState & ControlState::PRESSED) ? QStyle::State_Sunken : QStyle::State_None)
+ | ((nControlState & ControlState::SELECTED) ? QStyle::State_Selected : QStyle::State_None)
+ | ((nControlState & ControlState::ROLLOVER) ? QStyle::State_MouseOver
+ : QStyle::State_None);
+
+ switch (aValue.getTristateVal())
+ {
+ case ButtonValue::On:
+ nState |= QStyle::State_On;
+ break;
+ case ButtonValue::Off:
+ nState |= QStyle::State_Off;
+ break;
+ case ButtonValue::Mixed:
+ nState |= QStyle::State_NoChange;
+ break;
+ default:
+ break;
+ }
+
+ return nState;
+}
+
+static void lcl_ApplyBackgroundColorToStyleOption(QStyleOption& rOption,
+ const Color& rBackgroundColor)
+{
+ if (rBackgroundColor != COL_AUTO)
+ {
+ QColor aColor = toQColor(rBackgroundColor);
+ for (QPalette::ColorRole role : { QPalette::Window, QPalette::Button, QPalette::Base })
+ rOption.palette.setColor(role, aColor);
+ }
+}
+
+QtGraphics_Controls::QtGraphics_Controls(const QtGraphicsBase& rGraphics)
+ : m_rGraphics(rGraphics)
+{
+}
+
+bool QtGraphics_Controls::isNativeControlSupported(ControlType type, ControlPart part)
+{
+ switch (type)
+ {
+ case ControlType::Tooltip:
+ case ControlType::Progress:
+ case ControlType::ListNode:
+ return (part == ControlPart::Entire);
+
+ case ControlType::Pushbutton:
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ return (part == ControlPart::Entire) || (part == ControlPart::Focus);
+
+ case ControlType::ListHeader:
+ return (part == ControlPart::Button);
+
+ case ControlType::Menubar:
+ case ControlType::MenuPopup:
+ case ControlType::Editbox:
+ case ControlType::MultilineEditbox:
+ case ControlType::Combobox:
+ case ControlType::Toolbar:
+ case ControlType::Frame:
+ case ControlType::Scrollbar:
+ case ControlType::WindowBackground:
+ case ControlType::Fixedline:
+ return true;
+
+ case ControlType::Listbox:
+ return (part == ControlPart::Entire || part == ControlPart::HasBackgroundTexture);
+
+ case ControlType::Spinbox:
+ return (part == ControlPart::Entire || part == ControlPart::HasBackgroundTexture);
+
+ case ControlType::Slider:
+ return (part == ControlPart::TrackHorzArea || part == ControlPart::TrackVertArea);
+
+ case ControlType::TabItem:
+ case ControlType::TabPane:
+ return ((part == ControlPart::Entire) || part == ControlPart::TabPaneWithHeader);
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+inline int QtGraphics_Controls::pixelMetric(QStyle::PixelMetric metric, const QStyleOption* option,
+ const QWidget* pWidget)
+{
+ return QApplication::style()->pixelMetric(metric, option, pWidget);
+}
+
+inline QSize QtGraphics_Controls::sizeFromContents(QStyle::ContentsType type,
+ const QStyleOption* option,
+ const QSize& contentsSize)
+{
+ return QApplication::style()->sizeFromContents(type, option, contentsSize);
+}
+
+inline QRect QtGraphics_Controls::subControlRect(QStyle::ComplexControl control,
+ const QStyleOptionComplex* option,
+ QStyle::SubControl subControl)
+{
+ return QApplication::style()->subControlRect(control, option, subControl);
+}
+
+inline QRect QtGraphics_Controls::subElementRect(QStyle::SubElement element,
+ const QStyleOption* option)
+{
+ return QApplication::style()->subElementRect(element, option);
+}
+
+void QtGraphics_Controls::draw(QStyle::ControlElement element, QStyleOption& rOption, QImage* image,
+ const Color& rBackgroundColor, QStyle::State const state, QRect rect)
+{
+ const QRect& targetRect = !rect.isNull() ? rect : image->rect();
+
+ rOption.state |= state;
+ rOption.rect = downscale(targetRect);
+
+ lcl_ApplyBackgroundColorToStyleOption(rOption, rBackgroundColor);
+
+ QPainter painter(image);
+ QApplication::style()->drawControl(element, &rOption, &painter);
+}
+
+void QtGraphics_Controls::draw(QStyle::PrimitiveElement element, QStyleOption& rOption,
+ QImage* image, const Color& rBackgroundColor,
+ QStyle::State const state, QRect rect)
+{
+ const QRect& targetRect = !rect.isNull() ? rect : image->rect();
+
+ rOption.state |= state;
+ rOption.rect = downscale(targetRect);
+
+ lcl_ApplyBackgroundColorToStyleOption(rOption, rBackgroundColor);
+
+ QPainter painter(image);
+ QApplication::style()->drawPrimitive(element, &rOption, &painter);
+}
+
+void QtGraphics_Controls::draw(QStyle::ComplexControl element, QStyleOptionComplex& rOption,
+ QImage* image, const Color& rBackgroundColor,
+ QStyle::State const state)
+{
+ const QRect& targetRect = image->rect();
+
+ rOption.state |= state;
+ rOption.rect = downscale(targetRect);
+
+ lcl_ApplyBackgroundColorToStyleOption(rOption, rBackgroundColor);
+
+ QPainter painter(image);
+ QApplication::style()->drawComplexControl(element, &rOption, &painter);
+}
+
+void QtGraphics_Controls::drawFrame(QStyle::PrimitiveElement element, QImage* image,
+ const Color& rBackgroundColor, QStyle::State const& state,
+ bool bClip, QStyle::PixelMetric eLineMetric)
+{
+ const int fw = pixelMetric(eLineMetric);
+ QStyleOptionFrame option;
+ option.frameShape = QFrame::StyledPanel;
+ option.state = QStyle::State_Sunken | state;
+ option.lineWidth = fw;
+
+ QRect aRect = downscale(image->rect());
+ option.rect = aRect;
+
+ lcl_ApplyBackgroundColorToStyleOption(option, rBackgroundColor);
+
+ QPainter painter(image);
+ if (bClip)
+ painter.setClipRegion(QRegion(aRect).subtracted(aRect.adjusted(fw, fw, -fw, -fw)));
+ QApplication::style()->drawPrimitive(element, &option, &painter);
+}
+
+void QtGraphics_Controls::fillQStyleOptionTab(const ImplControlValue& value, QStyleOptionTab& sot)
+{
+ const TabitemValue& rValue = static_cast<const TabitemValue&>(value);
+ if (rValue.isFirst())
+ sot.position = rValue.isLast() ? QStyleOptionTab::OnlyOneTab : QStyleOptionTab::Beginning;
+ else if (rValue.isLast())
+ sot.position = rValue.isFirst() ? QStyleOptionTab::OnlyOneTab : QStyleOptionTab::End;
+ else
+ sot.position = QStyleOptionTab::Middle;
+}
+
+void QtGraphics_Controls::fullQStyleOptionTabWidgetFrame(QStyleOptionTabWidgetFrame& option,
+ bool bDownscale)
+{
+ option.state = QStyle::State_Enabled;
+ option.rightCornerWidgetSize = QSize(0, 0);
+ option.leftCornerWidgetSize = QSize(0, 0);
+ int nLineWidth = pixelMetric(QStyle::PM_DefaultFrameWidth);
+ option.lineWidth = bDownscale ? std::max(1, downscale(nLineWidth, Round::Ceil)) : nLineWidth;
+ option.midLineWidth = 0;
+ option.shape = QTabBar::RoundedNorth;
+}
+
+bool QtGraphics_Controls::drawNativeControl(ControlType type, ControlPart part,
+ const tools::Rectangle& rControlRegion,
+ ControlState nControlState,
+ const ImplControlValue& value, const OUString&,
+ const Color& rBackgroundColor)
+{
+ bool nativeSupport = isNativeControlSupported(type, part);
+ if (!nativeSupport)
+ {
+ assert(!nativeSupport && "drawNativeControl called without native support!");
+ return false;
+ }
+
+ if (m_lastPopupRect.isValid()
+ && (type != ControlType::MenuPopup || part != ControlPart::MenuItem))
+ m_lastPopupRect = QRect();
+
+ bool returnVal = true;
+
+ QRect widgetRect = toQRect(rControlRegion);
+
+ //if no image, or resized, make a new image
+ if (!m_image || m_image->size() != widgetRect.size())
+ {
+ m_image.reset(new QImage(widgetRect.width(), widgetRect.height(),
+ QImage::Format_ARGB32_Premultiplied));
+ m_image->setDevicePixelRatio(m_rGraphics.devicePixelRatioF());
+ }
+
+ // Default image color - just once
+ switch (type)
+ {
+ case ControlType::MenuPopup:
+ if (part == ControlPart::MenuItemCheckMark || part == ControlPart::MenuItemRadioMark)
+ {
+ // it is necessary to fill the background transparently first, as this
+ // is painted after menuitem highlight, otherwise there would be a grey area
+ m_image->fill(Qt::transparent);
+ break;
+ }
+ [[fallthrough]]; // QPalette::Window
+ case ControlType::Menubar:
+ case ControlType::WindowBackground:
+ m_image->fill(QApplication::palette().color(QPalette::Window).rgb());
+ break;
+ case ControlType::Tooltip:
+ m_image->fill(QApplication::palette().color(QPalette::ToolTipBase).rgb());
+ break;
+ case ControlType::Scrollbar:
+ if ((part == ControlPart::DrawBackgroundVert)
+ || (part == ControlPart::DrawBackgroundHorz))
+ {
+ m_image->fill(QApplication::palette().color(QPalette::Window).rgb());
+ break;
+ }
+ [[fallthrough]]; // Qt::transparent
+ default:
+ m_image->fill(Qt::transparent);
+ break;
+ }
+
+ if (type == ControlType::Pushbutton)
+ {
+ const PushButtonValue& rPBValue = static_cast<const PushButtonValue&>(value);
+ if (part == ControlPart::Focus)
+ // Nothing to do. Drawing focus separately is not needed because that's
+ // already handled by the ControlState::FOCUSED state being set when
+ // drawing the entire control
+ return true;
+ assert(part == ControlPart::Entire);
+ QStyleOptionButton option;
+ if (nControlState & ControlState::DEFAULT)
+ option.features |= QStyleOptionButton::DefaultButton;
+ if (rPBValue.m_bFlatButton)
+ option.features |= QStyleOptionButton::Flat;
+ draw(QStyle::CE_PushButton, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::Menubar)
+ {
+ if (part == ControlPart::MenuItem)
+ {
+ QStyleOptionMenuItem option;
+ option.state = vclStateValue2StateFlag(nControlState, value);
+ if ((nControlState & ControlState::ROLLOVER)
+ && QApplication::style()->styleHint(QStyle::SH_MenuBar_MouseTracking))
+ option.state |= QStyle::State_Selected;
+
+ if (nControlState
+ & ControlState::SELECTED) // Passing State_Sunken is currently not documented.
+ option.state |= QStyle::State_Sunken; // But some kinds of QStyle interpret it.
+
+ draw(QStyle::CE_MenuBarItem, option, m_image.get(), rBackgroundColor);
+ }
+ else if (part == ControlPart::Entire)
+ {
+ QStyleOptionMenuItem option;
+ draw(QStyle::CE_MenuBarEmptyArea, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else
+ {
+ returnVal = false;
+ }
+ }
+ else if (type == ControlType::MenuPopup)
+ {
+ assert(part == ControlPart::MenuItem ? m_lastPopupRect.isValid()
+ : !m_lastPopupRect.isValid());
+ if (part == ControlPart::MenuItem)
+ {
+ QStyleOptionMenuItem option;
+ draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ // HACK: LO core first paints the entire popup and only then it paints menu items,
+ // but QMenu::paintEvent() paints popup frame after all items. That means highlighted
+ // items here would paint the highlight over the frame border. Since calls to ControlPart::MenuItem
+ // are always preceded by calls to ControlPart::Entire, just remember the size for the whole
+ // popup (otherwise not possible to get here) and draw the border afterwards.
+ QRect framerect(m_lastPopupRect.topLeft() - widgetRect.topLeft(),
+ widgetRect.size().expandedTo(m_lastPopupRect.size()));
+ QStyleOptionFrame frame;
+ draw(QStyle::PE_FrameMenu, frame, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value), framerect);
+ }
+ else if (part == ControlPart::Separator)
+ {
+ QStyleOptionMenuItem option;
+ option.menuItemType = QStyleOptionMenuItem::Separator;
+ // Painting the whole menu item area results in different background
+ // with at least Plastique style, so clip only to the separator itself
+ // (QSize( 2, 2 ) is hardcoded in Qt)
+ option.rect = m_image->rect();
+ QSize size = sizeFromContents(QStyle::CT_MenuItem, &option, QSize(2, 2));
+ QRect rect = m_image->rect();
+ QPoint center = rect.center();
+ rect.setHeight(size.height());
+ rect.moveCenter(center);
+ option.state |= vclStateValue2StateFlag(nControlState, value);
+ option.rect = rect;
+
+ QPainter painter(m_image.get());
+ // don't paint over popup frame border (like the hack above, but here it can be simpler)
+ const int fw = pixelMetric(QStyle::PM_MenuPanelWidth);
+ painter.setClipRect(rect.adjusted(fw, 0, -fw, 0));
+ QApplication::style()->drawControl(QStyle::CE_MenuItem, &option, &painter);
+ }
+ else if (part == ControlPart::MenuItemCheckMark || part == ControlPart::MenuItemRadioMark)
+ {
+ QStyleOptionMenuItem option;
+ option.checkType = (part == ControlPart::MenuItemCheckMark)
+ ? QStyleOptionMenuItem::NonExclusive
+ : QStyleOptionMenuItem::Exclusive;
+ option.checked = bool(nControlState & ControlState::PRESSED);
+ // widgetRect is now the rectangle for the checkbox/radiobutton itself, but Qt
+ // paints the whole menu item, so translate position (and it'll be clipped);
+ // it is also necessary to fill the background transparently first, as this
+ // is painted after menuitem highlight, otherwise there would be a grey area
+ assert(value.getType() == ControlType::MenuPopup);
+ const MenupopupValue* menuVal = static_cast<const MenupopupValue*>(&value);
+ QRect menuItemRect(toQRect(menuVal->maItemRect));
+ QRect rect(menuItemRect.topLeft() - widgetRect.topLeft(),
+ widgetRect.size().expandedTo(menuItemRect.size()));
+ // checkboxes are always displayed next to images in menus, so are never centered
+ const int focus_size = pixelMetric(QStyle::PM_FocusFrameHMargin);
+ rect.moveTo(-focus_size, rect.y());
+ draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState & ~ControlState::PRESSED, value), rect);
+ }
+ else if (part == ControlPart::Entire)
+ {
+ QStyleOptionMenuItem option;
+ option.state = vclStateValue2StateFlag(nControlState, value);
+ draw(QStyle::PE_PanelMenu, option, m_image.get(), rBackgroundColor);
+ // Try hard to get any frame!
+ QStyleOptionFrame frame;
+ draw(QStyle::PE_FrameMenu, frame, m_image.get(), rBackgroundColor);
+ draw(QStyle::PE_FrameWindow, frame, m_image.get(), rBackgroundColor);
+ m_lastPopupRect = widgetRect;
+ }
+ else
+ returnVal = false;
+ }
+ else if ((type == ControlType::Toolbar) && (part == ControlPart::Button))
+ {
+ QStyleOptionToolButton option;
+
+ option.arrowType = Qt::NoArrow;
+ option.subControls = QStyle::SC_ToolButton;
+ option.state = vclStateValue2StateFlag(nControlState, value);
+ option.state |= QStyle::State_Raised | QStyle::State_Enabled | QStyle::State_AutoRaise;
+
+ draw(QStyle::CC_ToolButton, option, m_image.get(), rBackgroundColor);
+ }
+ else if ((type == ControlType::Toolbar) && (part == ControlPart::Entire))
+ {
+ QStyleOptionToolBar option;
+ draw(QStyle::CE_ToolBar, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if ((type == ControlType::Toolbar)
+ && (part == ControlPart::ThumbVert || part == ControlPart::ThumbHorz))
+ {
+ // reduce paint area only to the handle area
+ const int handleExtend = pixelMetric(QStyle::PM_ToolBarHandleExtent);
+ QStyleOption option;
+ QRect aRect = m_image->rect();
+ if (part == ControlPart::ThumbVert)
+ {
+ aRect.setWidth(handleExtend);
+ option.state = QStyle::State_Horizontal;
+ }
+ else
+ aRect.setHeight(handleExtend);
+ draw(QStyle::PE_IndicatorToolBarHandle, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value), aRect);
+ }
+ else if (type == ControlType::Editbox || type == ControlType::MultilineEditbox)
+ {
+ drawFrame(QStyle::PE_FrameLineEdit, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value), false);
+ }
+ else if (type == ControlType::Combobox)
+ {
+ QStyleOptionComboBox option;
+ option.editable = true;
+ draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::Listbox)
+ {
+ QStyleOptionComboBox option;
+ option.editable = false;
+ switch (part)
+ {
+ case ControlPart::ListboxWindow:
+ drawFrame(QStyle::PE_Frame, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value), true,
+ QStyle::PM_ComboBoxFrameWidth);
+ break;
+ case ControlPart::SubEdit:
+ draw(QStyle::CE_ComboBoxLabel, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ break;
+ case ControlPart::Entire:
+ draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ break;
+ case ControlPart::ButtonDown:
+ option.subControls = QStyle::SC_ComboBoxArrow;
+ draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ break;
+ default:
+ returnVal = false;
+ break;
+ }
+ }
+ else if (type == ControlType::ListNode)
+ {
+ QStyleOption option;
+ option.state = vclStateValue2StateFlag(nControlState, value);
+ option.state |= QStyle::State_Item | QStyle::State_Children;
+
+ if (value.getTristateVal() == ButtonValue::On)
+ option.state |= QStyle::State_Open;
+
+ draw(QStyle::PE_IndicatorBranch, option, m_image.get(), rBackgroundColor);
+ }
+ else if (type == ControlType::ListHeader)
+ {
+ QStyleOptionHeader option;
+ draw(QStyle::CE_HeaderSection, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::Checkbox)
+ {
+ if (part == ControlPart::Entire)
+ {
+ QStyleOptionButton option;
+ // clear FOCUSED bit, focus is drawn separately
+ nControlState &= ~ControlState::FOCUSED;
+ draw(QStyle::CE_CheckBox, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (part == ControlPart::Focus)
+ {
+ QStyleOptionFocusRect option;
+ draw(QStyle::PE_FrameFocusRect, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ }
+ else if (type == ControlType::Scrollbar)
+ {
+ if ((part == ControlPart::DrawBackgroundVert) || (part == ControlPart::DrawBackgroundHorz))
+ {
+ QStyleOptionSlider option;
+ assert(value.getType() == ControlType::Scrollbar);
+ const ScrollbarValue* sbVal = static_cast<const ScrollbarValue*>(&value);
+
+ //if the scroll bar is active (aka not degenerate... allow for hover events)
+ if (sbVal->mnVisibleSize < sbVal->mnMax)
+ option.state = QStyle::State_MouseOver;
+
+ bool horizontal = (part == ControlPart::DrawBackgroundHorz); //horizontal or vertical
+ option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
+ if (horizontal)
+ option.state |= QStyle::State_Horizontal;
+
+ // If the scrollbar has a mnMin == 0 and mnMax == 0 then mnVisibleSize is set to -1?!
+ // I don't know if a negative mnVisibleSize makes any sense, so just handle this case
+ // without crashing LO with a SIGFPE in the Qt library.
+ const tools::Long nVisibleSize
+ = (sbVal->mnMin == sbVal->mnMax) ? 0 : sbVal->mnVisibleSize;
+
+ option.minimum = sbVal->mnMin;
+ option.maximum = sbVal->mnMax - nVisibleSize;
+ option.maximum = qMax(option.maximum, option.minimum); // bnc#619772
+ option.sliderValue = sbVal->mnCur;
+ option.sliderPosition = sbVal->mnCur;
+ option.pageStep = nVisibleSize;
+ if (part == ControlPart::DrawBackgroundHorz)
+ option.upsideDown
+ = (QGuiApplication::isRightToLeft()
+ && sbVal->maButton1Rect.Left() < sbVal->maButton2Rect.Left())
+ || (QGuiApplication::isLeftToRight()
+ && sbVal->maButton1Rect.Left() > sbVal->maButton2Rect.Left());
+
+ //setup the active control... always the slider
+ if (sbVal->mnThumbState & ControlState::ROLLOVER)
+ option.activeSubControls = QStyle::SC_ScrollBarSlider;
+
+ draw(QStyle::CC_ScrollBar, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else
+ {
+ returnVal = false;
+ }
+ }
+ else if (type == ControlType::Spinbox)
+ {
+ QStyleOptionSpinBox option;
+ option.frame = true;
+
+ // determine active control
+ if (value.getType() == ControlType::SpinButtons)
+ {
+ const SpinbuttonValue* pSpinVal = static_cast<const SpinbuttonValue*>(&value);
+ if (pSpinVal->mnUpperState & ControlState::PRESSED)
+ option.activeSubControls |= QStyle::SC_SpinBoxUp;
+ if (pSpinVal->mnLowerState & ControlState::PRESSED)
+ option.activeSubControls |= QStyle::SC_SpinBoxDown;
+ if (pSpinVal->mnUpperState & ControlState::ENABLED)
+ option.stepEnabled |= QAbstractSpinBox::StepUpEnabled;
+ if (pSpinVal->mnLowerState & ControlState::ENABLED)
+ option.stepEnabled |= QAbstractSpinBox::StepDownEnabled;
+ if (pSpinVal->mnUpperState & ControlState::ROLLOVER)
+ option.state = QStyle::State_MouseOver;
+ if (pSpinVal->mnLowerState & ControlState::ROLLOVER)
+ option.state = QStyle::State_MouseOver;
+ }
+
+ draw(QStyle::CC_SpinBox, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::Radiobutton)
+ {
+ if (part == ControlPart::Entire)
+ {
+ QStyleOptionButton option;
+ // clear FOCUSED bit, focus is drawn separately
+ nControlState &= ~ControlState::FOCUSED;
+ draw(QStyle::CE_RadioButton, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (part == ControlPart::Focus)
+ {
+ QStyleOptionFocusRect option;
+ draw(QStyle::PE_FrameFocusRect, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ }
+ else if (type == ControlType::Tooltip)
+ {
+ QStyleOption option;
+ draw(QStyle::PE_PanelTipLabel, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::Frame)
+ {
+ drawFrame(QStyle::PE_Frame, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::WindowBackground)
+ {
+ // Nothing to do - see "Default image color" switch ^^
+ }
+ else if (type == ControlType::Fixedline)
+ {
+ QStyleOptionMenuItem option;
+ option.menuItemType = QStyleOptionMenuItem::Separator;
+ option.state = vclStateValue2StateFlag(nControlState, value);
+ option.state |= QStyle::State_Item;
+
+ draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor);
+ }
+ else if (type == ControlType::Slider
+ && (part == ControlPart::TrackHorzArea || part == ControlPart::TrackVertArea))
+ {
+ assert(value.getType() == ControlType::Slider);
+ const SliderValue* slVal = static_cast<const SliderValue*>(&value);
+ QStyleOptionSlider option;
+
+ option.state = vclStateValue2StateFlag(nControlState, value);
+ option.maximum = slVal->mnMax;
+ option.minimum = slVal->mnMin;
+ option.sliderPosition = option.sliderValue = slVal->mnCur;
+ bool horizontal = (part == ControlPart::TrackHorzArea); //horizontal or vertical
+ option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
+ if (horizontal)
+ option.state |= QStyle::State_Horizontal;
+
+ draw(QStyle::CC_Slider, option, m_image.get(), rBackgroundColor);
+ }
+ else if (type == ControlType::Progress && part == ControlPart::Entire)
+ {
+ QStyleOptionProgressBar option;
+ option.minimum = 0;
+ option.maximum = widgetRect.width();
+ option.progress = value.getNumericVal();
+
+ draw(QStyle::CE_ProgressBar, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::TabItem && part == ControlPart::Entire)
+ {
+ QStyleOptionTab sot;
+ fillQStyleOptionTab(value, sot);
+ draw(QStyle::CE_TabBarTabShape, sot, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::TabPane && part == ControlPart::Entire)
+ {
+ const TabPaneValue& rValue = static_cast<const TabPaneValue&>(value);
+
+ // get the overlap size for the tabs, so they will overlap the frame
+ QStyleOptionTab tabOverlap;
+ tabOverlap.shape = QTabBar::RoundedNorth;
+ TabPaneValue::m_nOverlap = pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap);
+
+ QStyleOptionTabWidgetFrame option;
+ fullQStyleOptionTabWidgetFrame(option, false);
+ option.tabBarRect = toQRect(rValue.m_aTabHeaderRect);
+ option.selectedTabRect
+ = rValue.m_aSelectedTabRect.IsEmpty() ? QRect() : toQRect(rValue.m_aSelectedTabRect);
+ option.tabBarSize = toQSize(rValue.m_aTabHeaderRect.GetSize());
+ option.rect = m_image->rect();
+ QRect aRect = subElementRect(QStyle::SE_TabWidgetTabPane, &option);
+ draw(QStyle::PE_FrameTabWidget, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value), aRect);
+ }
+ else
+ {
+ returnVal = false;
+ }
+
+ return returnVal;
+}
+
+bool QtGraphics_Controls::getNativeControlRegion(ControlType type, ControlPart part,
+ const tools::Rectangle& controlRegion,
+ ControlState controlState,
+ const ImplControlValue& val, const OUString&,
+ tools::Rectangle& nativeBoundingRegion,
+ tools::Rectangle& nativeContentRegion)
+{
+ bool retVal = false;
+
+ QRect boundingRect = toQRect(controlRegion);
+ QRect contentRect = boundingRect;
+ QStyleOptionComplex styleOption;
+
+ switch (type)
+ {
+ // Metrics of the push button
+ case ControlType::Pushbutton:
+ if (part == ControlPart::Entire)
+ {
+ styleOption.state = vclStateValue2StateFlag(controlState, val);
+
+ if (controlState & ControlState::DEFAULT)
+ {
+ int size = upscale(pixelMetric(QStyle::PM_ButtonDefaultIndicator, &styleOption),
+ Round::Ceil);
+ boundingRect.adjust(-size, -size, size, size);
+ retVal = true;
+ }
+ }
+ else if (part == ControlPart::Focus)
+ retVal = true;
+ break;
+ case ControlType::Editbox:
+ case ControlType::MultilineEditbox:
+ {
+ // we have to get stable borders, otherwise layout loops.
+ // so we simply only scale the detected borders.
+ QStyleOptionFrame fo;
+ fo.frameShape = QFrame::StyledPanel;
+ fo.state = QStyle::State_Sunken;
+ fo.lineWidth = pixelMetric(QStyle::PM_DefaultFrameWidth);
+ fo.rect = downscale(contentRect);
+ fo.rect.setSize(sizeFromContents(QStyle::CT_LineEdit, &fo, fo.rect.size()));
+ QRect aSubRect = subElementRect(QStyle::SE_LineEditContents, &fo);
+
+ // VCL tests borders with small defaults before layout, where Qt returns no sub-rect,
+ // so this gets us at least some frame.
+ int nLine = upscale(fo.lineWidth, Round::Ceil);
+ int nLeft = qMin(-nLine, upscale(fo.rect.left() - aSubRect.left(), Round::Floor));
+ int nTop = qMin(-nLine, upscale(fo.rect.top() - aSubRect.top(), Round::Floor));
+ int nRight = qMax(nLine, upscale(fo.rect.right() - aSubRect.right(), Round::Ceil));
+ int nBottom = qMax(nLine, upscale(fo.rect.bottom() - aSubRect.bottom(), Round::Ceil));
+ boundingRect.adjust(nLeft, nTop, nRight, nBottom);
+
+ // tdf#150451: ensure a minimum size that fits text content + frame at top and bottom.
+ // Themes may use the widget type for determining the actual frame width to use,
+ // so pass a dummy QLineEdit
+ //
+ // NOTE: This is currently only done here for the minimum size calculation and
+ // not above because the handling for edit boxes here and in the calling code
+ // currently does all kinds of "interesting" things like doing extra size adjustments
+ // or passing the content rect where the bounding rect would be expected,...
+ // Ideally this should be cleaned up in the callers and all platform integrations
+ // to adhere to what the doc in vcl/inc/WidgetDrawInterface.hxx says, but this
+ // here keeps it working with existing code for now.
+ // (s.a. discussion in https://gerrit.libreoffice.org/c/core/+/146516 for more details)
+ QLineEdit aDummyEdit;
+ const int nFrameWidth = pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, &aDummyEdit);
+ QFontMetrics aFontMetrics(QApplication::font());
+ const int minHeight = upscale(aFontMetrics.height() + 2 * nFrameWidth, Round::Floor);
+ if (boundingRect.height() < minHeight)
+ {
+ const int nDiff = minHeight - boundingRect.height();
+ boundingRect.setHeight(boundingRect.height() + nDiff);
+ contentRect.setHeight(contentRect.height() + nDiff);
+ }
+
+ retVal = true;
+ break;
+ }
+ case ControlType::Checkbox:
+ if (part == ControlPart::Entire)
+ {
+ styleOption.state = vclStateValue2StateFlag(controlState, val);
+
+ int nWidth = pixelMetric(QStyle::PM_IndicatorWidth, &styleOption);
+ int nHeight = pixelMetric(QStyle::PM_IndicatorHeight, &styleOption);
+ contentRect.setSize(upscale(QSize(nWidth, nHeight), Round::Ceil));
+
+ int nHMargin = pixelMetric(QStyle::PM_FocusFrameHMargin, &styleOption);
+ int nVMargin = pixelMetric(QStyle::PM_FocusFrameVMargin, &styleOption);
+ contentRect.adjust(0, 0, 2 * upscale(nHMargin, Round::Ceil),
+ 2 * upscale(nVMargin, Round::Ceil));
+
+ boundingRect = contentRect;
+ retVal = true;
+ }
+ break;
+ case ControlType::Combobox:
+ case ControlType::Listbox:
+ {
+ QStyleOptionComboBox cbo;
+
+ cbo.rect = downscale(QRect(0, 0, contentRect.width(), contentRect.height()));
+ cbo.state = vclStateValue2StateFlag(controlState, val);
+
+ switch (part)
+ {
+ case ControlPart::Entire:
+ {
+ // find out the minimum size that should be used
+ // assume contents is a text line
+ QSize aContentSize = downscale(contentRect.size(), Round::Ceil);
+ QFontMetrics aFontMetrics(QApplication::font());
+ aContentSize.setHeight(aFontMetrics.height());
+ QSize aMinSize = upscale(
+ sizeFromContents(QStyle::CT_ComboBox, &cbo, aContentSize), Round::Ceil);
+ if (aMinSize.height() > contentRect.height())
+ contentRect.setHeight(aMinSize.height());
+ boundingRect = contentRect;
+ retVal = true;
+ break;
+ }
+ case ControlPart::ButtonDown:
+ {
+ contentRect = upscale(
+ subControlRect(QStyle::CC_ComboBox, &cbo, QStyle::SC_ComboBoxArrow));
+ contentRect.translate(boundingRect.left(), boundingRect.top());
+ retVal = true;
+ break;
+ }
+ case ControlPart::SubEdit:
+ {
+ contentRect = upscale(
+ subControlRect(QStyle::CC_ComboBox, &cbo, QStyle::SC_ComboBoxEditField));
+ contentRect.translate(boundingRect.left(), boundingRect.top());
+ retVal = true;
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ case ControlType::Spinbox:
+ {
+ QStyleOptionSpinBox sbo;
+ sbo.frame = true;
+
+ sbo.rect = downscale(QRect(0, 0, contentRect.width(), contentRect.height()));
+ sbo.state = vclStateValue2StateFlag(controlState, val);
+
+ switch (part)
+ {
+ case ControlPart::Entire:
+ {
+ QSize aContentSize = downscale(contentRect.size(), Round::Ceil);
+ QFontMetrics aFontMetrics(QApplication::font());
+ aContentSize.setHeight(aFontMetrics.height());
+ QSize aMinSize = upscale(
+ sizeFromContents(QStyle::CT_SpinBox, &sbo, aContentSize), Round::Ceil);
+ if (aMinSize.height() > contentRect.height())
+ contentRect.setHeight(aMinSize.height());
+ boundingRect = contentRect;
+ retVal = true;
+ break;
+ }
+ case ControlPart::ButtonUp:
+ contentRect
+ = upscale(subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxUp));
+ contentRect.translate(boundingRect.left(), boundingRect.top());
+ retVal = true;
+ break;
+ case ControlPart::ButtonDown:
+ contentRect
+ = upscale(subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxDown));
+ contentRect.translate(boundingRect.left(), boundingRect.top());
+ retVal = true;
+ break;
+ case ControlPart::SubEdit:
+ contentRect = upscale(
+ subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxEditField));
+ contentRect.translate(boundingRect.left(), boundingRect.top());
+ retVal = true;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ case ControlType::MenuPopup:
+ {
+ int h, w;
+ switch (part)
+ {
+ case ControlPart::MenuItemCheckMark:
+ h = upscale(pixelMetric(QStyle::PM_IndicatorHeight), Round::Floor);
+ w = upscale(pixelMetric(QStyle::PM_IndicatorWidth), Round::Floor);
+ retVal = true;
+ break;
+ case ControlPart::MenuItemRadioMark:
+ h = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorHeight), Round::Floor);
+ w = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorWidth), Round::Floor);
+ retVal = true;
+ break;
+ default:
+ break;
+ }
+ if (retVal)
+ {
+ contentRect = QRect(0, 0, w, h);
+ boundingRect = contentRect;
+ }
+ break;
+ }
+ case ControlType::Frame:
+ {
+ if (part == ControlPart::Border)
+ {
+ int nFrameWidth = upscale(pixelMetric(QStyle::PM_DefaultFrameWidth), Round::Ceil);
+ contentRect.adjust(nFrameWidth, nFrameWidth, -nFrameWidth, -nFrameWidth);
+ retVal = true;
+ }
+ break;
+ }
+ case ControlType::Radiobutton:
+ {
+ const int h = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorHeight), Round::Ceil);
+ const int w = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorWidth), Round::Ceil);
+
+ contentRect = QRect(boundingRect.left(), boundingRect.top(), w, h);
+ int nHMargin = pixelMetric(QStyle::PM_FocusFrameHMargin, &styleOption);
+ int nVMargin = pixelMetric(QStyle::PM_FocusFrameVMargin, &styleOption);
+ contentRect.adjust(0, 0, upscale(2 * nHMargin, Round::Ceil),
+ upscale(2 * nVMargin, Round::Ceil));
+ boundingRect = contentRect;
+
+ retVal = true;
+ break;
+ }
+ case ControlType::Slider:
+ {
+ const int w = upscale(pixelMetric(QStyle::PM_SliderLength), Round::Ceil);
+ if (part == ControlPart::ThumbHorz)
+ {
+ contentRect
+ = QRect(boundingRect.left(), boundingRect.top(), w, boundingRect.height());
+ boundingRect = contentRect;
+ retVal = true;
+ }
+ else if (part == ControlPart::ThumbVert)
+ {
+ contentRect
+ = QRect(boundingRect.left(), boundingRect.top(), boundingRect.width(), w);
+ boundingRect = contentRect;
+ retVal = true;
+ }
+ break;
+ }
+ case ControlType::Toolbar:
+ {
+ const int nWorH = upscale(pixelMetric(QStyle::PM_ToolBarHandleExtent), Round::Ceil);
+ if (part == ControlPart::ThumbHorz)
+ {
+ contentRect
+ = QRect(boundingRect.left(), boundingRect.top(), boundingRect.width(), nWorH);
+ boundingRect = contentRect;
+ retVal = true;
+ }
+ else if (part == ControlPart::ThumbVert)
+ {
+ contentRect
+ = QRect(boundingRect.left(), boundingRect.top(), nWorH, boundingRect.height());
+ boundingRect = contentRect;
+ retVal = true;
+ }
+ else if (part == ControlPart::Button)
+ {
+ QStyleOptionToolButton option;
+ option.arrowType = Qt::NoArrow;
+ option.features = QStyleOptionToolButton::None;
+ option.rect = downscale(QRect({ 0, 0 }, contentRect.size()));
+ contentRect = upscale(
+ subControlRect(QStyle::CC_ToolButton, &option, QStyle::SC_ToolButton));
+ boundingRect = contentRect;
+ retVal = true;
+ }
+ break;
+ }
+ case ControlType::Scrollbar:
+ {
+ // core can't handle 3-button scrollbars well, so we fix that in hitTestNativeControl(),
+ // for the rest also provide the track area (i.e. area not taken by buttons)
+ if (part == ControlPart::TrackVertArea || part == ControlPart::TrackHorzArea)
+ {
+ QStyleOptionSlider option;
+ bool horizontal = (part == ControlPart::TrackHorzArea); //horizontal or vertical
+ option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
+ if (horizontal)
+ option.state |= QStyle::State_Horizontal;
+ // getNativeControlRegion usually gets ImplControlValue as 'val' (i.e. not the proper
+ // subclass), so use random sensible values (doesn't matter anyway, as the wanted
+ // geometry here depends only on button sizes)
+ option.maximum = 10;
+ option.minimum = 0;
+ option.sliderPosition = option.sliderValue = 4;
+ option.pageStep = 2;
+ // Adjust coordinates to make the widget appear to be at (0,0), i.e. make
+ // widget and screen coordinates the same. QStyle functions should use screen
+ // coordinates but at least QPlastiqueStyle::subControlRect() is buggy
+ // and sometimes uses widget coordinates.
+ option.rect = downscale(QRect({ 0, 0 }, contentRect.size()));
+ contentRect = upscale(
+ subControlRect(QStyle::CC_ScrollBar, &option, QStyle::SC_ScrollBarGroove));
+ contentRect.translate(boundingRect.left()
+ - (contentRect.width() - boundingRect.width()),
+ boundingRect.top());
+ boundingRect = contentRect;
+ retVal = true;
+ }
+ break;
+ }
+ case ControlType::TabItem:
+ {
+ QStyleOptionTab sot;
+ fillQStyleOptionTab(val, sot);
+ QSize aMinSize = upscale(sizeFromContents(QStyle::CT_TabBarTab, &sot,
+ downscale(contentRect.size(), Round::Ceil)),
+ Round::Ceil);
+ contentRect.setSize(aMinSize);
+ boundingRect = contentRect;
+ retVal = true;
+ break;
+ }
+ case ControlType::TabPane:
+ {
+ const TabPaneValue& rValue = static_cast<const TabPaneValue&>(val);
+ QStyleOptionTabWidgetFrame sotwf;
+ fullQStyleOptionTabWidgetFrame(sotwf, true);
+ QSize contentSize(
+ std::max(rValue.m_aTabHeaderRect.GetWidth(), controlRegion.GetWidth()),
+ rValue.m_aTabHeaderRect.GetHeight() + controlRegion.GetHeight());
+ QSize aMinSize = upscale(
+ sizeFromContents(QStyle::CT_TabWidget, &sotwf, downscale(contentSize, Round::Ceil)),
+ Round::Ceil);
+ contentRect.setSize(aMinSize);
+ boundingRect = contentRect;
+ retVal = true;
+ break;
+ }
+ default:
+ break;
+ }
+ if (retVal)
+ {
+ nativeBoundingRegion = toRectangle(boundingRect);
+ nativeContentRegion = toRectangle(contentRect);
+ }
+
+ return retVal;
+}
+
+/** Test whether the position is in the native widget.
+ If the return value is true, bIsInside contains information whether
+ aPos was or was not inside the native widget specified by the
+ nType/nPart combination.
+*/
+bool QtGraphics_Controls::hitTestNativeControl(ControlType nType, ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ const Point& rPos, bool& rIsInside)
+{
+ if (nType == ControlType::Scrollbar)
+ {
+ if (nPart != ControlPart::ButtonUp && nPart != ControlPart::ButtonDown
+ && nPart != ControlPart::ButtonLeft && nPart != ControlPart::ButtonRight)
+ { // we adjust only for buttons (because some scrollbars have 3 buttons,
+ // and LO core doesn't handle such scrollbars well)
+ return false;
+ }
+ rIsInside = false;
+ bool bHorizontal = (nPart == ControlPart::ButtonLeft || nPart == ControlPart::ButtonRight);
+ QRect rect = toQRect(rControlRegion);
+ QPoint pos(rPos.X(), rPos.Y());
+ // Adjust coordinates to make the widget appear to be at (0,0), i.e. make
+ // widget and screen coordinates the same. QStyle functions should use screen
+ // coordinates but at least QPlastiqueStyle::subControlRect() is buggy
+ // and sometimes uses widget coordinates.
+ pos -= rect.topLeft();
+ rect.moveTo(0, 0);
+ QStyleOptionSlider options;
+ options.orientation = bHorizontal ? Qt::Horizontal : Qt::Vertical;
+ if (bHorizontal)
+ options.state |= QStyle::State_Horizontal;
+ options.rect = rect;
+ // some random sensible values, since we call this code only for scrollbar buttons,
+ // the slider position does not exactly matter
+ options.maximum = 10;
+ options.minimum = 0;
+ options.sliderPosition = options.sliderValue = 4;
+ options.pageStep = 2;
+ QStyle::SubControl control
+ = QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar, &options, pos);
+ if (nPart == ControlPart::ButtonUp || nPart == ControlPart::ButtonLeft)
+ rIsInside = (control == QStyle::SC_ScrollBarSubLine);
+ else // DOWN, RIGHT
+ rIsInside = (control == QStyle::SC_ScrollBarAddLine);
+ return true;
+ }
+ return false;
+}
+
+inline int QtGraphics_Controls::downscale(int size, Round eRound)
+{
+ return static_cast<int>(eRound == Round::Ceil ? ceil(size / m_rGraphics.devicePixelRatioF())
+ : floor(size / m_rGraphics.devicePixelRatioF()));
+}
+
+inline int QtGraphics_Controls::upscale(int size, Round eRound)
+{
+ return static_cast<int>(eRound == Round::Ceil ? ceil(size * m_rGraphics.devicePixelRatioF())
+ : floor(size * m_rGraphics.devicePixelRatioF()));
+}
+
+inline QRect QtGraphics_Controls::downscale(const QRect& rect)
+{
+ return QRect(downscale(rect.x(), Round::Floor), downscale(rect.y(), Round::Floor),
+ downscale(rect.width(), Round::Ceil), downscale(rect.height(), Round::Ceil));
+}
+
+inline QRect QtGraphics_Controls::upscale(const QRect& rect)
+{
+ return QRect(upscale(rect.x(), Round::Floor), upscale(rect.y(), Round::Floor),
+ upscale(rect.width(), Round::Ceil), upscale(rect.height(), Round::Ceil));
+}
+
+inline QSize QtGraphics_Controls::downscale(const QSize& size, Round eRound)
+{
+ return QSize(downscale(size.width(), eRound), downscale(size.height(), eRound));
+}
+
+inline QSize QtGraphics_Controls::upscale(const QSize& size, Round eRound)
+{
+ return QSize(upscale(size.width(), eRound), upscale(size.height(), eRound));
+}
+
+inline QPoint QtGraphics_Controls::upscale(const QPoint& point, Round eRound)
+{
+ return QPoint(upscale(point.x(), eRound), upscale(point.y(), eRound));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtGraphics_GDI.cxx b/vcl/qt5/QtGraphics_GDI.cxx
new file mode 100644
index 0000000000..2005de80f7
--- /dev/null
+++ b/vcl/qt5/QtGraphics_GDI.cxx
@@ -0,0 +1,710 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <QtGraphics.hxx>
+
+#include <QtBitmap.hxx>
+#include <QtPainter.hxx>
+
+#include <sal/log.hxx>
+
+#include <QtGui/QPainter>
+#include <QtGui/QScreen>
+#include <QtGui/QWindow>
+#include <QtWidgets/QWidget>
+
+#include <numeric>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+
+QtGraphicsBackend::QtGraphicsBackend(QtFrame* pFrame, QImage* pQImage)
+ : m_pFrame(pFrame)
+ , m_pQImage(pQImage)
+ , m_oLineColor(std::in_place, 0x00, 0x00, 0x00)
+ , m_oFillColor(std::in_place, 0xFF, 0xFF, 0XFF)
+ , m_eCompositionMode(QPainter::CompositionMode_SourceOver)
+{
+ ResetClipRegion();
+}
+
+QtGraphicsBackend::~QtGraphicsBackend() {}
+
+const basegfx::B2DPoint aHalfPointOfs(0.5, 0.5);
+
+static void AddPolygonToPath(QPainterPath& rPath, const basegfx::B2DPolygon& rPolygon,
+ bool bClosePath, bool bPixelSnap, bool bLineDraw)
+{
+ const int nPointCount = rPolygon.count();
+ // short circuit if there is nothing to do
+ if (nPointCount == 0)
+ return;
+
+ const bool bHasCurves = rPolygon.areControlPointsUsed();
+ for (int nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++)
+ {
+ int nClosedIdx = nPointIdx;
+ if (nPointIdx >= nPointCount)
+ {
+ // prepare to close last curve segment if needed
+ if (bClosePath && (nPointIdx == nPointCount))
+ nClosedIdx = 0;
+ else
+ break;
+ }
+
+ basegfx::B2DPoint aPoint = rPolygon.getB2DPoint(nClosedIdx);
+
+ if (bPixelSnap)
+ {
+ // snap device coordinates to full pixels
+ aPoint.setX(basegfx::fround(aPoint.getX()));
+ aPoint.setY(basegfx::fround(aPoint.getY()));
+ }
+
+ if (bLineDraw)
+ aPoint += aHalfPointOfs;
+ if (!nPointIdx)
+ {
+ // first point => just move there
+ rPath.moveTo(aPoint.getX(), aPoint.getY());
+ continue;
+ }
+
+ bool bPendingCurve = false;
+ if (bHasCurves)
+ {
+ bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx);
+ bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx);
+ }
+
+ if (!bPendingCurve) // line segment
+ rPath.lineTo(aPoint.getX(), aPoint.getY());
+ else // cubic bezier segment
+ {
+ basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint(nPrevIdx);
+ basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint(nClosedIdx);
+ if (bLineDraw)
+ {
+ aCP1 += aHalfPointOfs;
+ aCP2 += aHalfPointOfs;
+ }
+ rPath.cubicTo(aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), aPoint.getX(),
+ aPoint.getY());
+ }
+ }
+
+ if (bClosePath)
+ rPath.closeSubpath();
+}
+
+static bool AddPolyPolygonToPath(QPainterPath& rPath, const basegfx::B2DPolyPolygon& rPolyPoly,
+ bool bPixelSnap, bool bLineDraw)
+{
+ if (rPolyPoly.count() == 0)
+ return false;
+ for (auto const& rPolygon : rPolyPoly)
+ {
+ AddPolygonToPath(rPath, rPolygon, true, bPixelSnap, bLineDraw);
+ }
+ return true;
+}
+
+void QtGraphicsBackend::setClipRegion(const vcl::Region& rRegion)
+{
+ if (rRegion.IsRectangle())
+ {
+ m_aClipRegion = toQRect(rRegion.GetBoundRect());
+ if (!m_aClipPath.isEmpty())
+ {
+ QPainterPath aPath;
+ m_aClipPath.swap(aPath);
+ }
+ }
+ else if (!rRegion.HasPolyPolygonOrB2DPolyPolygon())
+ {
+ QRegion aQRegion;
+ RectangleVector aRectangles;
+ rRegion.GetRegionRectangles(aRectangles);
+ for (const auto& rRect : aRectangles)
+ aQRegion += toQRect(rRect);
+ m_aClipRegion = aQRegion;
+ if (!m_aClipPath.isEmpty())
+ {
+ QPainterPath aPath;
+ m_aClipPath.swap(aPath);
+ }
+ }
+ else
+ {
+ QPainterPath aPath;
+ const basegfx::B2DPolyPolygon aPolyClip(rRegion.GetAsB2DPolyPolygon());
+ AddPolyPolygonToPath(aPath, aPolyClip, !getAntiAlias(), false);
+ m_aClipPath.swap(aPath);
+ if (!m_aClipRegion.isEmpty())
+ {
+ QRegion aRegion;
+ m_aClipRegion.swap(aRegion);
+ }
+ }
+}
+
+void QtGraphicsBackend::ResetClipRegion()
+{
+ if (m_pQImage)
+ m_aClipRegion = QRegion(m_pQImage->rect());
+ else
+ m_aClipRegion = QRegion();
+ if (!m_aClipPath.isEmpty())
+ {
+ QPainterPath aPath;
+ m_aClipPath.swap(aPath);
+ }
+}
+
+void QtGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY)
+{
+ QtPainter aPainter(*this);
+ aPainter.drawPoint(nX, nY);
+ aPainter.update(nX, nY, 1, 1);
+}
+
+void QtGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY, Color nColor)
+{
+ QtPainter aPainter(*this);
+ aPainter.setPen(toQColor(nColor));
+ aPainter.setPen(Qt::SolidLine);
+ aPainter.drawPoint(nX, nY);
+ aPainter.update(nX, nY, 1, 1);
+}
+
+void QtGraphicsBackend::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2)
+{
+ QtPainter aPainter(*this);
+ aPainter.drawLine(nX1, nY1, nX2, nY2);
+
+ if (nX1 > nX2)
+ std::swap(nX1, nX2);
+ if (nY1 > nY2)
+ std::swap(nY1, nY2);
+ aPainter.update(nX1, nY1, nX2 - nX1 + 1, nY2 - nY1 + 1);
+}
+
+void QtGraphicsBackend::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight)
+{
+ if (!m_oFillColor && !m_oLineColor)
+ return;
+
+ QtPainter aPainter(*this, true);
+ if (m_oFillColor)
+ aPainter.fillRect(nX, nY, nWidth, nHeight, aPainter.brush());
+ if (m_oLineColor)
+ aPainter.drawRect(nX, nY, nWidth - 1, nHeight - 1);
+ aPainter.update(nX, nY, nWidth, nHeight);
+}
+
+void QtGraphicsBackend::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry)
+{
+ if (0 == nPoints)
+ return;
+
+ QtPainter aPainter(*this);
+ QPoint* pPoints = new QPoint[nPoints];
+ QPoint aTopLeft(pPtAry->getX(), pPtAry->getY());
+ QPoint aBottomRight = aTopLeft;
+ for (sal_uInt32 i = 0; i < nPoints; ++i, ++pPtAry)
+ {
+ pPoints[i] = QPoint(pPtAry->getX(), pPtAry->getY());
+ if (pPtAry->getX() < aTopLeft.x())
+ aTopLeft.setX(pPtAry->getX());
+ if (pPtAry->getY() < aTopLeft.y())
+ aTopLeft.setY(pPtAry->getY());
+ if (pPtAry->getX() > aBottomRight.x())
+ aBottomRight.setX(pPtAry->getX());
+ if (pPtAry->getY() > aBottomRight.y())
+ aBottomRight.setY(pPtAry->getY());
+ }
+ aPainter.drawPolyline(pPoints, nPoints);
+ delete[] pPoints;
+ aPainter.update(QRect(aTopLeft, aBottomRight));
+}
+
+void QtGraphicsBackend::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry)
+{
+ QtPainter aPainter(*this, true);
+ QPolygon aPolygon(nPoints);
+ for (sal_uInt32 i = 0; i < nPoints; ++i, ++pPtAry)
+ aPolygon.setPoint(i, pPtAry->getX(), pPtAry->getY());
+ aPainter.drawPolygon(aPolygon);
+ aPainter.update(aPolygon.boundingRect());
+}
+
+void QtGraphicsBackend::drawPolyPolygon(sal_uInt32 nPolyCount, const sal_uInt32* pPoints,
+ const Point** ppPtAry)
+{
+ // ignore invisible polygons
+ if (!m_oFillColor && !m_oLineColor)
+ return;
+
+ QPainterPath aPath;
+ for (sal_uInt32 nPoly = 0; nPoly < nPolyCount; nPoly++)
+ {
+ const sal_uInt32 nPoints = pPoints[nPoly];
+ if (nPoints > 1)
+ {
+ const Point* pPtAry = ppPtAry[nPoly];
+ aPath.moveTo(pPtAry->getX(), pPtAry->getY());
+ pPtAry++;
+ for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++)
+ aPath.lineTo(pPtAry->getX(), pPtAry->getY());
+ aPath.closeSubpath();
+ }
+ }
+
+ QtPainter aPainter(*this, true);
+ aPainter.drawPath(aPath);
+ aPainter.update(aPath.boundingRect());
+}
+
+void QtGraphicsBackend::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+ // ignore invisible polygons
+ if (!m_oFillColor && !m_oLineColor)
+ return;
+ if ((fTransparency >= 1.0) || (fTransparency < 0))
+ return;
+
+ // Fallback: Transform to DeviceCoordinates
+ basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
+ aPolyPolygon.transform(rObjectToDevice);
+
+ QPainterPath aPath;
+ // ignore empty polygons
+ if (!AddPolyPolygonToPath(aPath, aPolyPolygon, !getAntiAlias(), m_oLineColor.has_value()))
+ return;
+
+ QtPainter aPainter(*this, true, 255 * (1.0 - fTransparency));
+ aPainter.drawPath(aPath);
+ aPainter.update(aPath.boundingRect());
+}
+
+bool QtGraphicsBackend::drawPolyLineBezier(sal_uInt32 /*nPoints*/, const Point* /*pPtAry*/,
+ const PolyFlags* /*pFlgAry*/)
+{
+ return false;
+}
+
+bool QtGraphicsBackend::drawPolygonBezier(sal_uInt32 /*nPoints*/, const Point* /*pPtAry*/,
+ const PolyFlags* /*pFlgAry*/)
+{
+ return false;
+}
+
+bool QtGraphicsBackend::drawPolyPolygonBezier(sal_uInt32 /*nPoly*/, const sal_uInt32* /*pPoints*/,
+ const Point* const* /*pPtAry*/,
+ const PolyFlags* const* /*pFlgAry*/)
+{
+ return false;
+}
+
+bool QtGraphicsBackend::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& rPolyLine, double fTransparency,
+ double fLineWidth,
+ const std::vector<double>* pStroke, // MM01
+ basegfx::B2DLineJoin eLineJoin, css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle, bool bPixelSnapHairline)
+{
+ if (!m_oFillColor && !m_oLineColor)
+ {
+ return true;
+ }
+
+ // MM01 check done for simple reasons
+ if (!rPolyLine.count() || fTransparency < 0.0 || fTransparency > 1.0)
+ {
+ return true;
+ }
+
+ // MM01 need to do line dashing as fallback stuff here now
+ const double fDotDashLength(
+ nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0);
+ const bool bStrokeUsed(0.0 != fDotDashLength);
+ assert(!bStrokeUsed || (bStrokeUsed && pStroke));
+ basegfx::B2DPolyPolygon aPolyPolygonLine;
+
+ if (bStrokeUsed)
+ {
+ // apply LineStyle
+ basegfx::utils::applyLineDashing(rPolyLine, // source
+ *pStroke, // pattern
+ &aPolyPolygonLine, // target for lines
+ nullptr, // target for gaps
+ fDotDashLength); // full length if available
+ }
+ else
+ {
+ // no line dashing, just copy
+ aPolyPolygonLine.append(rPolyLine);
+ }
+
+ // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
+ aPolyPolygonLine.transform(rObjectToDevice);
+ if (bPixelSnapHairline)
+ {
+ aPolyPolygonLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygonLine);
+ }
+
+ // tdf#124848 get correct LineWidth in discrete coordinates,
+ if (fLineWidth == 0) // hairline
+ fLineWidth = 1.0;
+ else // Adjust line width for object-to-device scale.
+ fLineWidth = (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
+
+ // setup poly-polygon path
+ QPainterPath aPath;
+
+ // MM01 todo - I assume that this is OKAY to be done in one run for Qt,
+ // but this NEEDS to be checked/verified
+ for (sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
+ {
+ const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
+ AddPolygonToPath(aPath, aPolyLine, aPolyLine.isClosed(), !getAntiAlias(), true);
+ }
+
+ QtPainter aPainter(*this, false, 255 * (1.0 - fTransparency));
+
+ // setup line attributes
+ QPen aPen = aPainter.pen();
+ aPen.setWidth(fLineWidth);
+
+ switch (eLineJoin)
+ {
+ case basegfx::B2DLineJoin::Bevel:
+ aPen.setJoinStyle(Qt::BevelJoin);
+ break;
+ case basegfx::B2DLineJoin::Round:
+ aPen.setJoinStyle(Qt::RoundJoin);
+ break;
+ case basegfx::B2DLineJoin::NONE:
+ case basegfx::B2DLineJoin::Miter:
+ aPen.setMiterLimit(1.0 / sin(fMiterMinimumAngle / 2.0));
+ aPen.setJoinStyle(Qt::MiterJoin);
+ break;
+ }
+
+ switch (eLineCap)
+ {
+ default: // css::drawing::LineCap_BUTT:
+ aPen.setCapStyle(Qt::FlatCap);
+ break;
+ case css::drawing::LineCap_ROUND:
+ aPen.setCapStyle(Qt::RoundCap);
+ break;
+ case css::drawing::LineCap_SQUARE:
+ aPen.setCapStyle(Qt::SquareCap);
+ break;
+ }
+
+ aPainter.setPen(aPen);
+ aPainter.drawPath(aPath);
+ aPainter.update(aPath.boundingRect());
+ return true;
+}
+
+bool QtGraphicsBackend::drawGradient(const tools::PolyPolygon& /*rPolyPolygon*/,
+ const Gradient& /*rGradient*/)
+{
+ return false;
+}
+
+bool QtGraphicsBackend::implDrawGradient(basegfx::B2DPolyPolygon const& /*rPolyPolygon*/,
+ SalGradient const& /*rGradient*/)
+{
+ return false;
+}
+
+void QtGraphicsBackend::drawScaledImage(const SalTwoRect& rPosAry, const QImage& rImage)
+{
+ QtPainter aPainter(*this);
+ QRect aSrcRect(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+ QRect aDestRect(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight);
+ aPainter.drawImage(aDestRect, rImage, aSrcRect);
+ aPainter.update(aDestRect);
+}
+
+void QtGraphicsBackend::copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX,
+ tools::Long nSrcY, tools::Long nSrcWidth, tools::Long nSrcHeight,
+ bool /*bWindowInvalidate*/)
+{
+ if (nDestX == nSrcX && nDestY == nSrcY)
+ return;
+
+ SalTwoRect aTR(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
+
+ QImage* pImage = m_pQImage;
+ QImage aImage = pImage->copy(aTR.mnSrcX, aTR.mnSrcY, aTR.mnSrcWidth, aTR.mnSrcHeight);
+ pImage = &aImage;
+ aTR.mnSrcX = 0;
+ aTR.mnSrcY = 0;
+
+ drawScaledImage(aTR, *pImage);
+}
+
+void QtGraphicsBackend::copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics)
+{
+ if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
+ || rPosAry.mnDestHeight <= 0)
+ return;
+
+ QImage aImage, *pImage;
+ SalTwoRect aPosAry = rPosAry;
+
+ if (!pSrcGraphics)
+ {
+ pImage = m_pQImage;
+ aImage
+ = pImage->copy(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+ pImage = &aImage;
+ aPosAry.mnSrcX = 0;
+ aPosAry.mnSrcY = 0;
+ }
+ else
+ pImage = static_cast<QtGraphics*>(pSrcGraphics)->getQImage();
+
+ drawScaledImage(aPosAry, *pImage);
+}
+
+void QtGraphicsBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
+{
+ if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
+ || rPosAry.mnDestHeight <= 0)
+ return;
+
+ const QImage* pImage = static_cast<const QtBitmap*>(&rSalBitmap)->GetQImage();
+
+ assert(pImage);
+
+ drawScaledImage(rPosAry, *pImage);
+}
+
+void QtGraphicsBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& /*rSalBitmap*/,
+ const SalBitmap& /*rTransparentBitmap*/)
+{
+ if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
+ || rPosAry.mnDestHeight <= 0)
+ return;
+
+ assert(rPosAry.mnSrcWidth == rPosAry.mnDestWidth);
+ assert(rPosAry.mnSrcHeight == rPosAry.mnDestHeight);
+}
+
+void QtGraphicsBackend::drawMask(const SalTwoRect& rPosAry, const SalBitmap& /*rSalBitmap*/,
+ Color /*nMaskColor*/)
+{
+ if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
+ || rPosAry.mnDestHeight <= 0)
+ return;
+
+ assert(rPosAry.mnSrcWidth == rPosAry.mnDestWidth);
+ assert(rPosAry.mnSrcHeight == rPosAry.mnDestHeight);
+}
+
+std::shared_ptr<SalBitmap> QtGraphicsBackend::getBitmap(tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight)
+{
+ return std::make_shared<QtBitmap>(m_pQImage->copy(nX, nY, nWidth, nHeight));
+}
+
+Color QtGraphicsBackend::getPixel(tools::Long nX, tools::Long nY)
+{
+ return Color(ColorTransparency, m_pQImage->pixel(nX, nY));
+}
+
+void QtGraphicsBackend::invert(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, SalInvert nFlags)
+{
+ QtPainter aPainter(*this);
+ if (SalInvert::N50 & nFlags)
+ {
+ aPainter.setCompositionMode(QPainter::RasterOp_SourceXorDestination);
+ QBrush aBrush(Qt::white, Qt::Dense4Pattern);
+ aPainter.fillRect(nX, nY, nWidth, nHeight, aBrush);
+ }
+ else
+ {
+ if (SalInvert::TrackFrame & nFlags)
+ {
+ aPainter.setCompositionMode(QPainter::RasterOp_SourceXorDestination);
+ QPen aPen(Qt::white);
+ aPen.setStyle(Qt::DotLine);
+ aPainter.setPen(aPen);
+ aPainter.drawRect(nX, nY, nWidth, nHeight);
+ }
+ else
+ {
+ aPainter.setCompositionMode(QPainter::RasterOp_SourceXorDestination);
+ aPainter.fillRect(nX, nY, nWidth, nHeight, Qt::white);
+ }
+ }
+ aPainter.update(nX, nY, nWidth, nHeight);
+}
+
+void QtGraphicsBackend::invert(sal_uInt32 /*nPoints*/, const Point* /*pPtAry*/,
+ SalInvert /*nFlags*/)
+{
+}
+
+bool QtGraphicsBackend::drawEPS(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/,
+ tools::Long /*nHeight*/, void* /*pPtr*/, sal_uInt32 /*nSize*/)
+{
+ return false;
+}
+
+bool QtGraphicsBackend::blendBitmap(const SalTwoRect&, const SalBitmap& /*rBitmap*/)
+{
+ return false;
+}
+
+bool QtGraphicsBackend::blendAlphaBitmap(const SalTwoRect&, const SalBitmap& /*rSrcBitmap*/,
+ const SalBitmap& /*rMaskBitmap*/,
+ const SalBitmap& /*rAlphaBitmap*/)
+{
+ return false;
+}
+
+static QImage getAlphaImage(const SalBitmap& rSourceBitmap, const SalBitmap& rAlphaBitmap)
+{
+ assert(rSourceBitmap.GetSize() == rAlphaBitmap.GetSize());
+ assert(rAlphaBitmap.GetBitCount() == 8 || rAlphaBitmap.GetBitCount() == 1);
+
+ QImage aAlphaMask = *static_cast<const QtBitmap*>(&rAlphaBitmap)->GetQImage();
+
+ const QImage* pBitmap = static_cast<const QtBitmap*>(&rSourceBitmap)->GetQImage();
+ QImage aImage = pBitmap->convertToFormat(Qt_DefaultFormat32);
+ aImage.setAlphaChannel(aAlphaMask);
+ return aImage;
+}
+
+bool QtGraphicsBackend::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap)
+{
+ drawScaledImage(rPosAry, getAlphaImage(rSourceBitmap, rAlphaBitmap));
+ return true;
+}
+
+bool QtGraphicsBackend::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap, double fAlpha)
+{
+ QImage aImage;
+ if (!pAlphaBitmap)
+ aImage = *static_cast<const QtBitmap*>(&rSourceBitmap)->GetQImage();
+ else
+ aImage = getAlphaImage(rSourceBitmap, *pAlphaBitmap);
+
+ const basegfx::B2DVector aXRel = rX - rNull;
+ const basegfx::B2DVector aYRel = rY - rNull;
+
+ QtPainter aPainter(*this);
+ aPainter.setOpacity(fAlpha);
+ aPainter.setTransform(QTransform(aXRel.getX() / aImage.width(), aXRel.getY() / aImage.width(),
+ aYRel.getX() / aImage.height(), aYRel.getY() / aImage.height(),
+ rNull.getX(), rNull.getY()));
+ aPainter.drawImage(QPoint(0, 0), aImage);
+ aPainter.update(aImage.rect());
+ return true;
+}
+
+bool QtGraphicsBackend::hasFastDrawTransformedBitmap() const { return false; }
+
+bool QtGraphicsBackend::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, sal_uInt8 nTransparency)
+{
+ if (!m_oFillColor && !m_oLineColor)
+ return true;
+ assert(nTransparency <= 100);
+ if (nTransparency > 100)
+ nTransparency = 100;
+ QtPainter aPainter(*this, true, (100 - nTransparency) * (255.0 / 100));
+ if (m_oFillColor)
+ aPainter.fillRect(nX, nY, nWidth, nHeight, aPainter.brush());
+ if (m_oLineColor)
+ aPainter.drawRect(nX, nY, nWidth - 1, nHeight - 1);
+ aPainter.update(nX, nY, nWidth, nHeight);
+ return true;
+}
+
+sal_uInt16 QtGraphicsBackend::GetBitCount() const { return getFormatBits(m_pQImage->format()); }
+
+tools::Long QtGraphicsBackend::GetGraphicsWidth() const { return m_pQImage->width(); }
+
+void QtGraphicsBackend::SetLineColor() { m_oLineColor = std::nullopt; }
+
+void QtGraphicsBackend::SetLineColor(Color nColor) { m_oLineColor = nColor; }
+
+void QtGraphicsBackend::SetFillColor() { m_oFillColor = std::nullopt; }
+
+void QtGraphicsBackend::SetFillColor(Color nColor) { m_oFillColor = nColor; }
+
+void QtGraphicsBackend::SetXORMode(bool bSet, bool)
+{
+ if (bSet)
+ m_eCompositionMode = QPainter::CompositionMode_Xor;
+ else
+ m_eCompositionMode = QPainter::CompositionMode_SourceOver;
+}
+
+void QtGraphicsBackend::SetROPLineColor(SalROPColor /*nROPColor*/) {}
+
+void QtGraphicsBackend::SetROPFillColor(SalROPColor /*nROPColor*/) {}
+
+bool QtGraphicsBackend::supportsOperation(OutDevSupportType eType) const
+{
+ switch (eType)
+ {
+ case OutDevSupportType::TransparentRect:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void QtGraphics::GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY)
+{
+ char* pForceDpi;
+ if ((pForceDpi = getenv("SAL_FORCEDPI")))
+ {
+ OString sForceDPI(pForceDpi);
+ rDPIX = rDPIY = sForceDPI.toInt32();
+ return;
+ }
+
+ if (!m_pFrame)
+ return;
+
+ QScreen* pScreen = m_pFrame->GetQWidget()->screen();
+ rDPIX = pScreen->logicalDotsPerInchX() * pScreen->devicePixelRatio() + 0.5;
+ rDPIY = pScreen->logicalDotsPerInchY() * pScreen->devicePixelRatio() + 0.5;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtGraphics_Text.cxx b/vcl/qt5/QtGraphics_Text.cxx
new file mode 100644
index 0000000000..19837d510f
--- /dev/null
+++ b/vcl/qt5/QtGraphics_Text.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 <sal/config.h>
+
+#include <QtGraphics.hxx>
+#include <QtFontFace.hxx>
+#include <QtFont.hxx>
+#include <QtPainter.hxx>
+
+#include <vcl/fontcharmap.hxx>
+#include <unx/geninst.h>
+#include <unx/fontmanager.hxx>
+#include <unx/glyphcache.hxx>
+
+#include <sallayout.hxx>
+#include <font/PhysicalFontCollection.hxx>
+
+#include <QtGui/QGlyphRun>
+#include <QtGui/QFontDatabase>
+#include <QtGui/QRawFont>
+#include <QtCore/QStringList>
+
+void QtGraphics::SetTextColor(Color nColor) { m_aTextColor = nColor; }
+
+void QtGraphics::SetFont(LogicalFontInstance* pReqFont, int nFallbackLevel)
+{
+ // release the text styles
+ for (int i = nFallbackLevel; i < MAX_FALLBACK; ++i)
+ {
+ if (!m_pTextStyle[i])
+ break;
+ m_pTextStyle[i].clear();
+ }
+
+ if (!pReqFont)
+ return;
+
+ m_pTextStyle[nFallbackLevel] = static_cast<QtFont*>(pReqFont);
+}
+
+void QtGraphics::GetFontMetric(FontMetricDataRef& rFMD, int nFallbackLevel)
+{
+ QRawFont aRawFont(QRawFont::fromFont(*m_pTextStyle[nFallbackLevel]));
+ QtFontFace::fillAttributesFromQFont(*m_pTextStyle[nFallbackLevel], *rFMD);
+
+ rFMD->ImplCalcLineSpacing(m_pTextStyle[nFallbackLevel].get());
+ rFMD->ImplInitBaselines(m_pTextStyle[nFallbackLevel].get());
+
+ rFMD->SetSlant(0);
+ rFMD->SetWidth(aRawFont.averageCharWidth());
+
+ rFMD->SetMinKashida(m_pTextStyle[nFallbackLevel]->GetKashidaWidth());
+}
+
+FontCharMapRef QtGraphics::GetFontCharMap() const
+{
+ if (!m_pTextStyle[0])
+ return FontCharMapRef(new FontCharMap());
+ return m_pTextStyle[0]->GetFontFace()->GetFontCharMap();
+}
+
+bool QtGraphics::GetFontCapabilities(vcl::FontCapabilities& rFontCapabilities) const
+{
+ if (!m_pTextStyle[0])
+ return false;
+ return m_pTextStyle[0]->GetFontFace()->GetFontCapabilities(rFontCapabilities);
+}
+
+void QtGraphics::GetDevFontList(vcl::font::PhysicalFontCollection* pPFC)
+{
+ static const bool bUseFontconfig = (nullptr == getenv("SAL_VCL_QT5_NO_FONTCONFIG"));
+
+ if (pPFC->Count())
+ return;
+
+ FreetypeManager& rFontManager = FreetypeManager::get();
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ ::std::vector<psp::fontID> aList;
+
+ rMgr.getFontList(aList);
+ for (auto const& nFontId : aList)
+ {
+ auto const* pFont = rMgr.getFont(nFontId);
+ if (!pFont)
+ continue;
+
+ // normalize face number to the FreetypeManager
+ int nFaceNum = rMgr.getFontFaceNumber(nFontId);
+ int nVariantNum = rMgr.getFontFaceVariation(nFontId);
+
+ // inform FreetypeManager about this font provided by the PsPrint subsystem
+ FontAttributes aFA = pFont->m_aFontAttributes;
+ aFA.IncreaseQualityBy(4096);
+ const OString& rFileName = rMgr.getFontFileSysPath(nFontId);
+ rFontManager.AddFontFile(rFileName, nFaceNum, nVariantNum, nFontId, aFA);
+ }
+
+ if (bUseFontconfig)
+ SalGenericInstance::RegisterFontSubstitutors(pPFC);
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ for (auto& family : QFontDatabase::families())
+ for (auto& style : QFontDatabase::styles(family))
+ pPFC->Add(QtFontFace::fromQFontDatabase(family, style));
+#else
+ QFontDatabase aFDB;
+ for (auto& family : aFDB.families())
+ for (auto& style : aFDB.styles(family))
+ pPFC->Add(QtFontFace::fromQFontDatabase(family, style));
+#endif
+}
+
+void QtGraphics::ClearDevFontCache() {}
+
+bool QtGraphics::AddTempDevFont(vcl::font::PhysicalFontCollection*, const OUString& /*rFileURL*/,
+ const OUString& /*rFontName*/)
+{
+ return false;
+}
+
+namespace
+{
+class QtCommonSalLayout : public GenericSalLayout
+{
+public:
+ QtCommonSalLayout(LogicalFontInstance& rLFI)
+ : GenericSalLayout(rLFI)
+ {
+ }
+
+ void SetOrientation(Degree10 nOrientation) { mnOrientation = nOrientation; }
+};
+}
+
+std::unique_ptr<GenericSalLayout> QtGraphics::GetTextLayout(int nFallbackLevel)
+{
+ assert(m_pTextStyle[nFallbackLevel]);
+ if (!m_pTextStyle[nFallbackLevel])
+ return nullptr;
+ return std::make_unique<QtCommonSalLayout>(*m_pTextStyle[nFallbackLevel]);
+}
+
+static QRawFont GetRawFont(const QFont& rFont, bool bWithoutHintingInTextDirection)
+{
+ QFont::HintingPreference eHinting = rFont.hintingPreference();
+ static bool bAllowDefaultHinting = getenv("SAL_ALLOW_DEFAULT_HINTING") != nullptr;
+ bool bAllowedHintStyle
+ = !bWithoutHintingInTextDirection || bAllowDefaultHinting
+ || (eHinting == QFont::PreferNoHinting || eHinting == QFont::PreferVerticalHinting);
+ if (bWithoutHintingInTextDirection && !bAllowedHintStyle)
+ {
+ QFont aFont(rFont);
+ aFont.setHintingPreference(QFont::PreferVerticalHinting);
+ return QRawFont::fromFont(aFont);
+ }
+ return QRawFont::fromFont(rFont);
+}
+
+void QtGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+ const QtFont* pFont = static_cast<const QtFont*>(&rLayout.GetFont());
+ assert(pFont);
+ QRawFont aRawFont(GetRawFont(*pFont, rLayout.GetSubpixelPositioning()));
+
+ QVector<quint32> glyphIndexes;
+ QVector<QPointF> positions;
+
+ // prevent glyph rotation inside the SalLayout
+ // probably better to add a parameter to GetNextGlyphs?
+ QtCommonSalLayout* pQtLayout
+ = static_cast<QtCommonSalLayout*>(const_cast<GenericSalLayout*>(&rLayout));
+ Degree10 nOrientation = rLayout.GetOrientation();
+ if (nOrientation)
+ pQtLayout->SetOrientation(0_deg10);
+
+ basegfx::B2DPoint aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ glyphIndexes.push_back(pGlyph->glyphId());
+ positions.push_back(QPointF(aPos.getX(), aPos.getY()));
+ }
+
+ // seems to be common to try to layout an empty string...
+ if (positions.empty())
+ return;
+
+ if (nOrientation)
+ pQtLayout->SetOrientation(nOrientation);
+
+ QGlyphRun aGlyphRun;
+ aGlyphRun.setPositions(positions);
+ aGlyphRun.setGlyphIndexes(glyphIndexes);
+ aGlyphRun.setRawFont(aRawFont);
+
+ QtPainter aPainter(*m_pBackend);
+ QColor aColor = toQColor(m_aTextColor);
+ aPainter.setPen(aColor);
+
+ if (nOrientation)
+ {
+ // make text position the center of the rotation
+ // then rotate and move back
+ QRect window = aPainter.window();
+ window.moveTo(-positions[0].x(), -positions[0].y());
+ aPainter.setWindow(window);
+
+ QTransform p;
+ p.rotate(-static_cast<qreal>(nOrientation.get()) / 10.0);
+ p.translate(-positions[0].x(), -positions[0].y());
+ aPainter.setTransform(p);
+ }
+
+ aPainter.drawGlyphRun(QPointF(), aGlyphRun);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtInstance.cxx b/vcl/qt5/QtInstance.cxx
new file mode 100644
index 0000000000..4880c1bdec
--- /dev/null
+++ b/vcl/qt5/QtInstance.cxx
@@ -0,0 +1,771 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <QtInstance.hxx>
+#include <QtInstance.moc>
+
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+
+#include <QtBitmap.hxx>
+#include <QtClipboard.hxx>
+#include <QtData.hxx>
+#include <QtDragAndDrop.hxx>
+#include <QtFilePicker.hxx>
+#include <QtFrame.hxx>
+#include <QtMenu.hxx>
+#include <QtObject.hxx>
+#include <QtOpenGLContext.hxx>
+#include "QtSvpVirtualDevice.hxx"
+#include <QtSystem.hxx>
+#include <QtTimer.hxx>
+#include <QtVirtualDevice.hxx>
+
+#include <headless/svpvd.hxx>
+
+#include <QtCore/QAbstractEventDispatcher>
+#include <QtCore/QLibraryInfo>
+#include <QtCore/QThread>
+#include <QtGui/QScreen>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QWidget>
+
+#include <vclpluginapi.h>
+#include <tools/debug.hxx>
+#include <comphelper/flagguard.hxx>
+#include <dndhelper.hxx>
+#include <vcl/sysdata.hxx>
+#include <sal/log.hxx>
+#include <osl/process.h>
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && ENABLE_GSTREAMER_1_0 && QT5_HAVE_GOBJECT
+#include <unx/gstsink.hxx>
+#endif
+#include <headless/svpbmp.hxx>
+
+#include <mutex>
+#include <condition_variable>
+
+#ifdef EMSCRIPTEN
+#include <QtCore/QtPlugin>
+Q_IMPORT_PLUGIN(QWasmIntegrationPlugin)
+#endif
+
+namespace
+{
+/// TODO: not much Qt specific here? could be generalised, esp. for OSX...
+/// this subclass allows for the transfer of a closure for running on the main
+/// thread, to handle all the thread affine stuff in Qt; the SolarMutex is
+/// "loaned" to the main thread for the execution of the closure.
+/// @note it doesn't work to just use "emit" and signals/slots to move calls to
+/// the main thread, because the other thread has the SolarMutex; the other
+/// thread (typically) cannot release SolarMutex, because then the main thread
+/// will handle all sorts of events and whatnot; this design ensures that the
+/// main thread only runs the passed closure (unless the closure releases
+/// SolarMutex itself, which should probably be avoided).
+class QtYieldMutex : public SalYieldMutex
+{
+public:
+ /// flag only accessed on main thread:
+ /// main thread has "borrowed" SolarMutex from another thread
+ bool m_bNoYieldLock = false;
+ /// members for communication from non-main thread to main thread
+ std::mutex m_RunInMainMutex;
+ std::condition_variable m_InMainCondition;
+ bool m_isWakeUpMain = false;
+ std::function<void()> m_Closure; ///< code for main thread to run
+ /// members for communication from main thread to non-main thread
+ std::condition_variable m_ResultCondition;
+ bool m_isResultReady = false;
+
+ virtual bool IsCurrentThread() const override;
+ virtual void doAcquire(sal_uInt32 nLockCount) override;
+ virtual sal_uInt32 doRelease(bool const bUnlockAll) override;
+};
+}
+
+bool QtYieldMutex::IsCurrentThread() const
+{
+ auto const* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (pSalInst->IsMainThread() && m_bNoYieldLock)
+ {
+ return true; // main thread has borrowed SolarMutex
+ }
+ return SalYieldMutex::IsCurrentThread();
+}
+
+void QtYieldMutex::doAcquire(sal_uInt32 nLockCount)
+{
+ auto const* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ SalYieldMutex::doAcquire(nLockCount);
+ return;
+ }
+ if (m_bNoYieldLock)
+ {
+ return; // special case for main thread: borrowed from other thread
+ }
+ do // main thread acquire...
+ {
+ std::function<void()> func; // copy of closure on thread stack
+ {
+ std::unique_lock<std::mutex> g(m_RunInMainMutex);
+ if (m_aMutex.tryToAcquire())
+ {
+ // if there's a closure, the other thread holds m_aMutex
+ assert(!m_Closure);
+ m_isWakeUpMain = false;
+ --nLockCount; // have acquired once!
+ ++m_nCount;
+ break;
+ }
+ m_InMainCondition.wait(g, [this]() { return m_isWakeUpMain; });
+ m_isWakeUpMain = false;
+ std::swap(func, m_Closure);
+ }
+ if (func)
+ {
+ assert(!m_bNoYieldLock);
+ m_bNoYieldLock = true; // execute closure with borrowed SolarMutex
+ func();
+ m_bNoYieldLock = false;
+ std::scoped_lock<std::mutex> g(m_RunInMainMutex);
+ assert(!m_isResultReady);
+ m_isResultReady = true;
+ m_ResultCondition.notify_all(); // unblock other thread
+ }
+ } while (true);
+ SalYieldMutex::doAcquire(nLockCount);
+}
+
+sal_uInt32 QtYieldMutex::doRelease(bool const bUnlockAll)
+{
+ auto const* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (pSalInst->IsMainThread() && m_bNoYieldLock)
+ {
+ return 1; // dummy value
+ }
+
+ std::scoped_lock<std::mutex> g(m_RunInMainMutex);
+ // read m_nCount before doRelease (it's guarded by m_aMutex)
+ bool const isReleased(bUnlockAll || m_nCount == 1);
+ sal_uInt32 nCount = SalYieldMutex::doRelease(bUnlockAll);
+ if (isReleased && !pSalInst->IsMainThread())
+ {
+ m_isWakeUpMain = true;
+ m_InMainCondition.notify_all(); // unblock main thread
+ }
+ return nCount;
+}
+
+// this could be abstracted to be independent of Qt by passing in the
+// event-trigger as another function parameter...
+// it could also be a template of the return type, then it could return the
+// result of func... but then how to handle the result in doAcquire?
+void QtInstance::RunInMainThread(std::function<void()> func)
+{
+ DBG_TESTSOLARMUTEX();
+ if (IsMainThread())
+ {
+ func();
+ return;
+ }
+
+ QtYieldMutex* const pMutex(static_cast<QtYieldMutex*>(GetYieldMutex()));
+ {
+ std::scoped_lock<std::mutex> g(pMutex->m_RunInMainMutex);
+ assert(!pMutex->m_Closure);
+ pMutex->m_Closure = func;
+ // unblock main thread in case it is blocked on condition
+ pMutex->m_isWakeUpMain = true;
+ pMutex->m_InMainCondition.notify_all();
+ }
+
+ TriggerUserEventProcessing();
+ {
+ std::unique_lock<std::mutex> g(pMutex->m_RunInMainMutex);
+ pMutex->m_ResultCondition.wait(g, [pMutex]() { return pMutex->m_isResultReady; });
+ pMutex->m_isResultReady = false;
+ }
+}
+
+OUString QtInstance::constructToolkitID(std::u16string_view sTKname)
+{
+ OUString sID(sTKname + OUString::Concat(u" ("));
+ if (m_bUseCairo)
+ sID += "cairo+";
+ else
+ sID += "qfont+";
+ sID += toOUString(QGuiApplication::platformName()) + OUString::Concat(u")");
+ return sID;
+}
+
+QtInstance::QtInstance(std::unique_ptr<QApplication>& pQApp)
+ : SalGenericInstance(std::make_unique<QtYieldMutex>())
+ , m_bUseCairo(nullptr == getenv("SAL_VCL_QT_USE_QFONT"))
+ , m_pTimer(nullptr)
+ , m_bSleeping(false)
+ , m_pQApplication(std::move(pQApp))
+ , m_aUpdateStyleTimer("vcl::qt5 m_aUpdateStyleTimer")
+ , m_bUpdateFonts(false)
+ , m_pActivePopup(nullptr)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ const OUString sToolkit = "qt" + OUString::number(QT_VERSION_MAJOR);
+ pSVData->maAppData.mxToolkitName = constructToolkitID(sToolkit);
+
+ // this one needs to be blocking, so that the handling in main thread
+ // is processed before the thread emitting the signal continues
+ connect(this, SIGNAL(ImplYieldSignal(bool, bool)), this, SLOT(ImplYield(bool, bool)),
+ Qt::BlockingQueuedConnection);
+
+ // this one needs to be queued non-blocking
+ // in order to have this event arriving to correct event processing loop
+ connect(this, &QtInstance::deleteObjectLaterSignal, this,
+ [](QObject* pObject) { QtInstance::deleteObjectLater(pObject); }, Qt::QueuedConnection);
+
+ m_aUpdateStyleTimer.SetTimeout(50);
+ m_aUpdateStyleTimer.SetInvokeHandler(LINK(this, QtInstance, updateStyleHdl));
+
+ QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance(qApp->thread());
+ connect(dispatcher, &QAbstractEventDispatcher::awake, this, [this]() { m_bSleeping = false; });
+ connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, this,
+ [this]() { m_bSleeping = true; });
+
+ connect(QGuiApplication::inputMethod(), &QInputMethod::localeChanged, this,
+ &QtInstance::localeChanged);
+
+ for (const QScreen* pCurScreen : QApplication::screens())
+ connectQScreenSignals(pCurScreen);
+ connect(qApp, &QGuiApplication::primaryScreenChanged, this, &QtInstance::primaryScreenChanged);
+ connect(qApp, &QGuiApplication::screenAdded, this, &QtInstance::screenAdded);
+ connect(qApp, &QGuiApplication::screenRemoved, this, &QtInstance::screenRemoved);
+
+#ifndef EMSCRIPTEN
+ m_bSupportsOpenGL = true;
+#else
+ ImplGetSVData()->maAppData.m_bUseSystemLoop = true;
+#endif
+}
+
+QtInstance::~QtInstance()
+{
+ // force freeing the QApplication before freeing the arguments,
+ // as it uses references to the provided arguments!
+ m_pQApplication.reset();
+}
+
+void QtInstance::AfterAppInit()
+{
+ // set the default application icon via desktop file just on Wayland,
+ // as this otherwise overrides the individual desktop icons on X11.
+ if (QGuiApplication::platformName() == "wayland")
+ QGuiApplication::setDesktopFileName(QStringLiteral("libreoffice-startcenter.desktop"));
+ QGuiApplication::setLayoutDirection(AllSettings::GetLayoutRTL() ? Qt::RightToLeft
+ : Qt::LeftToRight);
+}
+
+void QtInstance::localeChanged()
+{
+ SolarMutexGuard aGuard;
+ const vcl::Window* pFocusWindow = Application::GetFocusWindow();
+ SalFrame* const pFocusFrame = pFocusWindow ? pFocusWindow->ImplGetFrame() : nullptr;
+ if (!pFocusFrame)
+ return;
+
+ const LanguageTag aTag(
+ toOUString(QGuiApplication::inputMethod()->locale().name().replace("_", "-")));
+ static_cast<QtFrame*>(pFocusFrame)->setInputLanguage(aTag.getLanguageType());
+}
+
+void QtInstance::deleteObjectLater(QObject* pObject) { pObject->deleteLater(); }
+
+SalFrame* QtInstance::CreateChildFrame(SystemParentData* /*pParent*/, SalFrameStyleFlags nStyle)
+{
+ SalFrame* pRet(nullptr);
+ RunInMainThread([&, this]() { pRet = new QtFrame(nullptr, nStyle, useCairo()); });
+ assert(pRet);
+ return pRet;
+}
+
+SalFrame* QtInstance::CreateFrame(SalFrame* pParent, SalFrameStyleFlags nStyle)
+{
+ assert(!pParent || dynamic_cast<QtFrame*>(pParent));
+
+ SalFrame* pRet(nullptr);
+ RunInMainThread(
+ [&, this]() { pRet = new QtFrame(static_cast<QtFrame*>(pParent), nStyle, useCairo()); });
+ assert(pRet);
+ return pRet;
+}
+
+void QtInstance::DestroyFrame(SalFrame* pFrame)
+{
+ if (pFrame)
+ {
+ assert(dynamic_cast<QtFrame*>(pFrame));
+ Q_EMIT deleteObjectLaterSignal(static_cast<QtFrame*>(pFrame));
+ }
+}
+
+SalObject* QtInstance::CreateObject(SalFrame* pParent, SystemWindowData*, bool bShow)
+{
+ assert(!pParent || dynamic_cast<QtFrame*>(pParent));
+
+ SalObject* pRet(nullptr);
+ RunInMainThread([&]() { pRet = new QtObject(static_cast<QtFrame*>(pParent), bShow); });
+ assert(pRet);
+ return pRet;
+}
+
+void QtInstance::DestroyObject(SalObject* pObject)
+{
+ if (pObject)
+ {
+ assert(dynamic_cast<QtObject*>(pObject));
+ Q_EMIT deleteObjectLaterSignal(static_cast<QtObject*>(pObject));
+ }
+}
+
+std::unique_ptr<SalVirtualDevice>
+QtInstance::CreateVirtualDevice(SalGraphics& rGraphics, tools::Long& nDX, tools::Long& nDY,
+ DeviceFormat /*eFormat*/, const SystemGraphicsData* pGd)
+{
+ if (m_bUseCairo)
+ {
+ SvpSalGraphics* pSvpSalGraphics = dynamic_cast<QtSvpGraphics*>(&rGraphics);
+ assert(pSvpSalGraphics);
+ // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget
+ cairo_surface_t* pPreExistingTarget
+ = pGd ? static_cast<cairo_surface_t*>(pGd->pSurface) : nullptr;
+ std::unique_ptr<SalVirtualDevice> pVD(
+ new QtSvpVirtualDevice(pSvpSalGraphics->getSurface(), pPreExistingTarget));
+ pVD->SetSize(nDX, nDY);
+ return pVD;
+ }
+ else
+ {
+ std::unique_ptr<SalVirtualDevice> pVD(new QtVirtualDevice(/*scale*/ 1));
+ pVD->SetSize(nDX, nDY);
+ return pVD;
+ }
+}
+
+std::unique_ptr<SalMenu> QtInstance::CreateMenu(bool bMenuBar, Menu* pVCLMenu)
+{
+ SolarMutexGuard aGuard;
+ std::unique_ptr<SalMenu> pRet;
+ RunInMainThread([&pRet, bMenuBar, pVCLMenu]() {
+ QtMenu* pSalMenu = new QtMenu(bMenuBar);
+ pRet.reset(pSalMenu);
+ pSalMenu->SetMenu(pVCLMenu);
+ });
+ assert(pRet);
+ return pRet;
+}
+
+std::unique_ptr<SalMenuItem> QtInstance::CreateMenuItem(const SalItemParams& rItemData)
+{
+ return std::unique_ptr<SalMenuItem>(new QtMenuItem(&rItemData));
+}
+
+SalTimer* QtInstance::CreateSalTimer()
+{
+ m_pTimer = new QtTimer();
+ return m_pTimer;
+}
+
+SalSystem* QtInstance::CreateSalSystem() { return new QtSystem; }
+
+std::shared_ptr<SalBitmap> QtInstance::CreateSalBitmap()
+{
+ if (m_bUseCairo)
+ return std::make_shared<SvpSalBitmap>();
+ else
+ return std::make_shared<QtBitmap>();
+}
+
+bool QtInstance::ImplYield(bool bWait, bool bHandleAllCurrentEvents)
+{
+ // Re-acquire the guard for user events when called via Q_EMIT ImplYieldSignal
+ SolarMutexGuard aGuard;
+ bool wasEvent = DispatchUserEvents(bHandleAllCurrentEvents);
+ if (!bHandleAllCurrentEvents && wasEvent)
+ return true;
+
+ /**
+ * Quoting the Qt docs: [QAbstractEventDispatcher::processEvents] processes
+ * pending events that match flags until there are no more events to process.
+ */
+ SolarMutexReleaser aReleaser;
+ QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance(qApp->thread());
+ if (bWait && !wasEvent)
+ wasEvent = dispatcher->processEvents(QEventLoop::WaitForMoreEvents);
+ else
+ wasEvent = dispatcher->processEvents(QEventLoop::AllEvents) || wasEvent;
+ return wasEvent;
+}
+
+bool QtInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
+{
+ bool bWasEvent = false;
+ if (qApp->thread() == QThread::currentThread())
+ {
+ bWasEvent = ImplYield(bWait, bHandleAllCurrentEvents);
+ if (bWasEvent)
+ m_aWaitingYieldCond.set();
+ }
+ else
+ {
+ {
+ SolarMutexReleaser aReleaser;
+ bWasEvent = Q_EMIT ImplYieldSignal(false, bHandleAllCurrentEvents);
+ }
+ if (!bWasEvent && bWait)
+ {
+ m_aWaitingYieldCond.reset();
+ SolarMutexReleaser aReleaser;
+ m_aWaitingYieldCond.wait();
+ bWasEvent = true;
+ }
+ }
+ return bWasEvent;
+}
+
+bool QtInstance::AnyInput(VclInputFlags nType)
+{
+ bool bResult = false;
+ if (nType & VclInputFlags::TIMER)
+ bResult |= (m_pTimer && m_pTimer->remainingTime() == 0);
+ if (nType & VclInputFlags::OTHER)
+ bResult |= !m_bSleeping;
+ return bResult;
+}
+
+OUString QtInstance::GetConnectionIdentifier() { return OUString(); }
+
+void QtInstance::AddToRecentDocumentList(const OUString&, const OUString&, const OUString&) {}
+
+#ifndef EMSCRIPTEN
+OpenGLContext* QtInstance::CreateOpenGLContext() { return new QtOpenGLContext; }
+#endif
+
+bool QtInstance::IsMainThread() const
+{
+ return !qApp || (qApp->thread() == QThread::currentThread());
+}
+
+void QtInstance::TriggerUserEventProcessing()
+{
+ QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance(qApp->thread());
+ dispatcher->wakeUp();
+}
+
+void QtInstance::ProcessEvent(SalUserEvent aEvent)
+{
+ aEvent.m_pFrame->CallCallback(aEvent.m_nEvent, aEvent.m_pData);
+}
+
+rtl::Reference<QtFilePicker>
+QtInstance::createPicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode eMode)
+{
+ if (!IsMainThread())
+ {
+ SolarMutexGuard g;
+ rtl::Reference<QtFilePicker> pPicker;
+ RunInMainThread([&, this]() { pPicker = createPicker(context, eMode); });
+ assert(pPicker);
+ return pPicker;
+ }
+
+ return new QtFilePicker(context, eMode);
+}
+
+css::uno::Reference<css::ui::dialogs::XFilePicker2>
+QtInstance::createFilePicker(const css::uno::Reference<css::uno::XComponentContext>& context)
+{
+ return css::uno::Reference<css::ui::dialogs::XFilePicker2>(
+ createPicker(context, QFileDialog::ExistingFile));
+}
+
+css::uno::Reference<css::ui::dialogs::XFolderPicker2>
+QtInstance::createFolderPicker(const css::uno::Reference<css::uno::XComponentContext>& context)
+{
+ return css::uno::Reference<css::ui::dialogs::XFolderPicker2>(
+ createPicker(context, QFileDialog::Directory));
+}
+
+css::uno::Reference<css::uno::XInterface>
+QtInstance::CreateClipboard(const css::uno::Sequence<css::uno::Any>& arguments)
+{
+ OUString sel;
+ if (arguments.getLength() == 0)
+ {
+ sel = "CLIPBOARD";
+ }
+ else if (arguments.getLength() != 1 || !(arguments[0] >>= sel))
+ {
+ throw css::lang::IllegalArgumentException("bad QtInstance::CreateClipboard arguments",
+ css::uno::Reference<css::uno::XInterface>(), -1);
+ }
+
+ // This could also use RunInMain, but SolarMutexGuard is enough
+ // since at this point we're not accessing the clipboard, just get the
+ // accessor to the clipboard.
+ SolarMutexGuard aGuard;
+
+ auto it = m_aClipboards.find(sel);
+ if (it != m_aClipboards.end())
+ return it->second;
+
+ css::uno::Reference<css::uno::XInterface> xClipboard = QtClipboard::create(sel);
+ if (xClipboard.is())
+ m_aClipboards[sel] = xClipboard;
+
+ return xClipboard;
+}
+
+css::uno::Reference<css::uno::XInterface>
+QtInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv)
+{
+ return vcl::X11DnDHelper(new QtDragSource(), pSysEnv->aShellWindow);
+}
+
+css::uno::Reference<css::uno::XInterface>
+QtInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv)
+{
+ return vcl::X11DnDHelper(new QtDropTarget(), pSysEnv->aShellWindow);
+}
+
+IMPL_LINK_NOARG(QtInstance, updateStyleHdl, Timer*, void)
+{
+ SolarMutexGuard aGuard;
+ SalFrame* pFrame = anyFrame();
+ if (pFrame)
+ {
+ pFrame->CallCallback(SalEvent::SettingsChanged, nullptr);
+ if (m_bUpdateFonts)
+ {
+ pFrame->CallCallback(SalEvent::FontChanged, nullptr);
+ m_bUpdateFonts = false;
+ }
+ }
+}
+
+void QtInstance::UpdateStyle(bool bFontsChanged)
+{
+ if (bFontsChanged)
+ m_bUpdateFonts = true;
+ if (!m_aUpdateStyleTimer.IsActive())
+ m_aUpdateStyleTimer.Start();
+}
+
+void* QtInstance::CreateGStreamerSink(const SystemChildWindow* pWindow)
+{
+// As of 2021-09, qt-gstreamer is unmaintained and there is no Qt 6 video sink
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && ENABLE_GSTREAMER_1_0 && QT5_HAVE_GOBJECT
+ auto pSymbol = gstElementFactoryNameSymbol();
+ if (!pSymbol)
+ return nullptr;
+
+ const SystemEnvData* pEnvData = pWindow->GetSystemData();
+ if (!pEnvData)
+ return nullptr;
+
+ if (pEnvData->platform != SystemEnvData::Platform::Wayland)
+ return nullptr;
+
+ GstElement* pVideosink = pSymbol("qwidget5videosink", "qwidget5videosink");
+ if (pVideosink)
+ {
+ QWidget* pQWidget = static_cast<QWidget*>(pEnvData->pWidget);
+ g_object_set(G_OBJECT(pVideosink), "widget", pQWidget, nullptr);
+ }
+ else
+ {
+ SAL_WARN("vcl.qt", "Couldn't initialize qwidget5videosink."
+ " Video playback might not work as expected."
+ " Please install Qt5 packages for QtGStreamer.");
+ // with no videosink explicitly set, GStreamer will open its own (misplaced) window(s) to display video
+ }
+
+ return pVideosink;
+#else
+ Q_UNUSED(pWindow);
+ return nullptr;
+#endif
+}
+
+void QtInstance::connectQScreenSignals(const QScreen* pScreen)
+{
+ connect(pScreen, &QScreen::orientationChanged, this, &QtInstance::orientationChanged);
+ connect(pScreen, &QScreen::virtualGeometryChanged, this, &QtInstance::virtualGeometryChanged);
+}
+
+void QtInstance::notifyDisplayChanged()
+{
+ SolarMutexGuard aGuard;
+ SalFrame* pAnyFrame = anyFrame();
+ if (pAnyFrame)
+ pAnyFrame->CallCallback(SalEvent::DisplayChanged, nullptr);
+}
+
+void QtInstance::orientationChanged(Qt::ScreenOrientation) { notifyDisplayChanged(); }
+
+void QtInstance::primaryScreenChanged(QScreen*) { notifyDisplayChanged(); }
+
+void QtInstance::screenAdded(QScreen* pScreen)
+{
+ connectQScreenSignals(pScreen);
+ if (QApplication::screens().size() == 1)
+ notifyDisplayChanged();
+}
+
+void QtInstance::screenRemoved(QScreen*) { notifyDisplayChanged(); }
+
+void QtInstance::virtualGeometryChanged(const QRect&) { notifyDisplayChanged(); }
+
+void QtInstance::AllocFakeCmdlineArgs(std::unique_ptr<char* []>& rFakeArgv,
+ std::unique_ptr<int>& rFakeArgc,
+ std::vector<FreeableCStr>& rFakeArgvFreeable)
+{
+ OString aVersion(qVersion());
+ SAL_INFO("vcl.qt", "qt version string is " << aVersion);
+
+ const sal_uInt32 nParams = osl_getCommandArgCount();
+ sal_uInt32 nDisplayValueIdx = 0;
+ OUString aParam, aBin;
+
+ for (sal_uInt32 nIdx = 0; nIdx < nParams; ++nIdx)
+ {
+ osl_getCommandArg(nIdx, &aParam.pData);
+ if (aParam != "-display")
+ continue;
+ ++nIdx;
+ nDisplayValueIdx = nIdx;
+ }
+
+ osl_getExecutableFile(&aParam.pData);
+ osl_getSystemPathFromFileURL(aParam.pData, &aBin.pData);
+ OString aExec = OUStringToOString(aBin, osl_getThreadTextEncoding());
+
+ std::vector<FreeableCStr> aFakeArgvFreeable;
+ aFakeArgvFreeable.reserve(4);
+ aFakeArgvFreeable.emplace_back(strdup(aExec.getStr()));
+ aFakeArgvFreeable.emplace_back(strdup("--nocrashhandler"));
+ if (nDisplayValueIdx)
+ {
+ aFakeArgvFreeable.emplace_back(strdup("-display"));
+ osl_getCommandArg(nDisplayValueIdx, &aParam.pData);
+ OString aDisplay = OUStringToOString(aParam, osl_getThreadTextEncoding());
+ aFakeArgvFreeable.emplace_back(strdup(aDisplay.getStr()));
+ }
+ rFakeArgvFreeable.swap(aFakeArgvFreeable);
+
+ const int nFakeArgc = rFakeArgvFreeable.size();
+ rFakeArgv.reset(new char*[nFakeArgc]);
+ for (int i = 0; i < nFakeArgc; i++)
+ rFakeArgv[i] = rFakeArgvFreeable[i].get();
+
+ rFakeArgc.reset(new int);
+ *rFakeArgc = nFakeArgc;
+}
+
+void QtInstance::MoveFakeCmdlineArgs(std::unique_ptr<char* []>& rFakeArgv,
+ std::unique_ptr<int>& rFakeArgc,
+ std::vector<FreeableCStr>& rFakeArgvFreeable)
+{
+ m_pFakeArgv = std::move(rFakeArgv);
+ m_pFakeArgc = std::move(rFakeArgc);
+ m_pFakeArgvFreeable.swap(rFakeArgvFreeable);
+}
+
+std::unique_ptr<QApplication> QtInstance::CreateQApplication(int& nArgc, char** pArgv)
+{
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ // for Qt 6, setting Qt::AA_EnableHighDpiScaling and Qt::AA_UseHighDpiPixmaps
+ // is deprecated, they're always enabled
+ QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ // for scaled icons in the native menus
+ QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
+#endif
+
+ FreeableCStr session_manager;
+ if (getenv("SESSION_MANAGER") != nullptr)
+ {
+ session_manager.reset(strdup(getenv("SESSION_MANAGER")));
+ unsetenv("SESSION_MANAGER");
+ }
+
+ std::unique_ptr<QApplication> pQApp = std::make_unique<QApplication>(nArgc, pArgv);
+
+ if (session_manager != nullptr)
+ {
+ // coverity[tainted_string] - trusted source for setenv
+ setenv("SESSION_MANAGER", session_manager.get(), 1);
+ }
+
+ QApplication::setQuitOnLastWindowClosed(false);
+ return pQApp;
+}
+
+bool QtInstance::DoExecute(int& nExitCode)
+{
+ const bool bIsOnSystemEventLoop = Application::IsOnSystemEventLoop();
+ if (bIsOnSystemEventLoop)
+ nExitCode = QApplication::exec();
+ return bIsOnSystemEventLoop;
+}
+
+void QtInstance::DoQuit()
+{
+ if (Application::IsOnSystemEventLoop())
+ QApplication::quit();
+}
+
+void QtInstance::setActivePopup(QtFrame* pFrame)
+{
+ assert(!pFrame || pFrame->isPopup());
+ m_pActivePopup = pFrame;
+}
+
+extern "C" {
+VCLPLUG_QT_PUBLIC SalInstance* create_SalInstance()
+{
+ std::unique_ptr<char* []> pFakeArgv;
+ std::unique_ptr<int> pFakeArgc;
+ std::vector<FreeableCStr> aFakeArgvFreeable;
+ QtInstance::AllocFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable);
+
+ std::unique_ptr<QApplication> pQApp
+ = QtInstance::CreateQApplication(*pFakeArgc, pFakeArgv.get());
+
+ QtInstance* pInstance = new QtInstance(pQApp);
+ pInstance->MoveFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable);
+
+ new QtData();
+
+ return pInstance;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtInstance_Print.cxx b/vcl/qt5/QtInstance_Print.cxx
new file mode 100644
index 0000000000..e6396099db
--- /dev/null
+++ b/vcl/qt5/QtInstance_Print.cxx
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <QtInstance.hxx>
+#include <QtPrinter.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/QueueInfo.hxx>
+#include <printerinfomanager.hxx>
+
+#include <jobset.h>
+#include <print.h>
+#include <salptype.hxx>
+
+#include <unx/genpspgraphics.h>
+
+using namespace psp;
+
+/*
+ * static helpers
+ */
+
+static OUString getPdfDir(const PrinterInfo& rInfo)
+{
+ OUString aDir;
+ sal_Int32 nIndex = 0;
+ while (nIndex != -1)
+ {
+ OUString aToken(rInfo.m_aFeatures.getToken(0, ',', nIndex));
+ if (aToken.startsWith("pdf="))
+ {
+ sal_Int32 nPos = 0;
+ aDir = aToken.getToken(1, '=', nPos);
+ if (aDir.isEmpty())
+ if (auto const env = getenv("HOME"))
+ {
+ aDir = OStringToOUString(std::string_view(env), osl_getThreadTextEncoding());
+ }
+ break;
+ }
+ }
+ return aDir;
+}
+
+SalInfoPrinter* QtInstance::CreateInfoPrinter(SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pJobSetup)
+{
+ // create and initialize SalInfoPrinter
+ PspSalInfoPrinter* pPrinter = new PspSalInfoPrinter;
+ configurePspInfoPrinter(pPrinter, pQueueInfo, pJobSetup);
+
+ return pPrinter;
+}
+
+void QtInstance::DestroyInfoPrinter(SalInfoPrinter* pPrinter) { delete pPrinter; }
+
+std::unique_ptr<SalPrinter> QtInstance::CreatePrinter(SalInfoPrinter* pInfoPrinter)
+{
+ // create and initialize SalPrinter
+ QtPrinter* pPrinter = new QtPrinter(pInfoPrinter);
+ pPrinter->m_aJobData = static_cast<PspSalInfoPrinter*>(pInfoPrinter)->m_aJobData;
+
+ return std::unique_ptr<SalPrinter>(pPrinter);
+}
+
+void QtInstance::GetPrinterQueueInfo(ImplPrnQueueList* pList)
+{
+ PrinterInfoManager& rManager(PrinterInfoManager::get());
+ static const char* pNoSyncDetection = getenv("SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION");
+ if (!pNoSyncDetection || !*pNoSyncDetection)
+ {
+ // #i62663# synchronize possible asynchronouse printer detection now
+ rManager.checkPrintersChanged(true);
+ }
+ ::std::vector<OUString> aPrinters;
+ rManager.listPrinters(aPrinters);
+
+ for (const auto& rPrinter : aPrinters)
+ {
+ const PrinterInfo& rInfo(rManager.getPrinterInfo(rPrinter));
+ // create new entry
+ std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo);
+ pInfo->maPrinterName = rPrinter;
+ pInfo->maDriver = rInfo.m_aDriverName;
+ pInfo->maLocation = rInfo.m_aLocation;
+ pInfo->maComment = rInfo.m_aComment;
+
+ sal_Int32 nIndex = 0;
+ while (nIndex != -1)
+ {
+ OUString aToken(rInfo.m_aFeatures.getToken(0, ',', nIndex));
+ if (aToken.startsWith("pdf="))
+ {
+ pInfo->maLocation = getPdfDir(rInfo);
+ break;
+ }
+ }
+
+ pList->Add(std::move(pInfo));
+ }
+}
+
+void QtInstance::GetPrinterQueueState(SalPrinterQueueInfo*) {}
+
+OUString QtInstance::GetDefaultPrinter()
+{
+ PrinterInfoManager& rManager(PrinterInfoManager::get());
+ return rManager.getDefaultPrinter();
+}
+
+void QtInstance::PostPrintersChanged() {}
+
+std::unique_ptr<GenPspGraphics> QtInstance::CreatePrintGraphics()
+{
+ return std::make_unique<GenPspGraphics>();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtMainWindow.cxx b/vcl/qt5/QtMainWindow.cxx
new file mode 100644
index 0000000000..5ff9ac9a81
--- /dev/null
+++ b/vcl/qt5/QtMainWindow.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/.
+ *
+ */
+
+#include <QtMainWindow.hxx>
+#include <QtMainWindow.moc>
+#include <QtAccessibleWidget.hxx>
+
+#include <QtGui/QAccessible>
+#include <QtGui/QCloseEvent>
+
+QtMainWindow::QtMainWindow(QtFrame& rFrame, Qt::WindowFlags f)
+ : QMainWindow(nullptr, f)
+ , m_rFrame(rFrame)
+{
+#ifndef EMSCRIPTEN
+ QAccessible::installFactory(QtAccessibleWidget::customFactory);
+#endif
+}
+
+void QtMainWindow::closeEvent(QCloseEvent* pEvent)
+{
+ bool bRet = false;
+ bRet = m_rFrame.CallCallback(SalEvent::Close, nullptr);
+
+ if (bRet)
+ pEvent->accept();
+ // SalEvent::Close returning false may mean that user has vetoed
+ // closing the frame ("you have unsaved changes" dialog for example)
+ // We shouldn't process the event in such case
+ else
+ pEvent->ignore();
+}
+
+void QtMainWindow::moveEvent(QMoveEvent* pEvent)
+{
+ const qreal fRatio = m_rFrame.devicePixelRatioF();
+ m_rFrame.maGeometry.setPos(toPoint(pEvent->pos() * fRatio));
+ m_rFrame.CallCallback(SalEvent::Move, nullptr);
+}
diff --git a/vcl/qt5/QtMenu.cxx b/vcl/qt5/QtMenu.cxx
new file mode 100644
index 0000000000..93f3d6f5a3
--- /dev/null
+++ b/vcl/qt5/QtMenu.cxx
@@ -0,0 +1,920 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <QtMenu.hxx>
+#include <QtMenu.moc>
+
+#include <QtFrame.hxx>
+#include <QtInstance.hxx>
+#include <QtMainWindow.hxx>
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+#include <QtWidgets/QActionGroup>
+#else
+#include <QtGui/QActionGroup>
+#endif
+
+#include <QtWidgets/QButtonGroup>
+#include <QtWidgets/QHBoxLayout>
+#include <QtWidgets/QMenuBar>
+#include <QtWidgets/QPushButton>
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+#include <QtGui/QShortcut>
+#else
+#include <QtWidgets/QShortcut>
+#endif
+#include <QtWidgets/QStyle>
+
+#include <o3tl/safeint.hxx>
+#include <vcl/svapp.hxx>
+#include <sal/log.hxx>
+
+#include <strings.hrc>
+#include <bitmaps.hlst>
+
+#include <vcl/toolkit/floatwin.hxx>
+#include <window.h>
+
+// LO SalMenuButtonItem::mnId is sal_uInt16, so we go with -2, as -1 has a special meaning as automatic id
+constexpr int CLOSE_BUTTON_ID = -2;
+const QString gButtonGroupKey("QtMenu::ButtonGroup");
+
+static inline void lcl_force_menubar_layout_update(QMenuBar& rMenuBar)
+{
+ // just exists as a function to not comment it everywhere: forces reposition of the
+ // corner widget after its layout changes, which will otherwise just happen on resize.
+ // it unfortunatly has additional side effects; see QtMenu::GetMenuBarButtonRectPixel.
+ rMenuBar.adjustSize();
+}
+
+OUString QtMenu::m_sCurrentHelpId = u""_ustr;
+
+QtMenu::QtMenu(bool bMenuBar)
+ : mpVCLMenu(nullptr)
+ , mpParentSalMenu(nullptr)
+ , mpFrame(nullptr)
+ , mbMenuBar(bMenuBar)
+ , mpQMenuBar(nullptr)
+ , mpQMenu(nullptr)
+ , m_pButtonGroup(nullptr)
+{
+}
+
+bool QtMenu::VisibleMenuBar() { return true; }
+
+void QtMenu::InsertMenuItem(QtMenuItem* pSalMenuItem, unsigned nPos)
+{
+ sal_uInt16 nId = pSalMenuItem->mnId;
+ OUString aText = mpVCLMenu->GetItemText(nId);
+ NativeItemText(aText);
+ vcl::KeyCode nAccelKey = mpVCLMenu->GetAccelKey(nId);
+
+ pSalMenuItem->mpAction.reset();
+ pSalMenuItem->mpMenu.reset();
+
+ if (mbMenuBar)
+ {
+ // top-level menu
+ if (validateQMenuBar())
+ {
+ QMenu* pQMenu = new QMenu(toQString(aText), nullptr);
+ connectHelpSignalSlots(pQMenu, pSalMenuItem);
+ pSalMenuItem->mpMenu.reset(pQMenu);
+
+ if ((nPos != MENU_APPEND)
+ && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenuBar->actions().size())))
+ {
+ mpQMenuBar->insertMenu(mpQMenuBar->actions()[nPos], pQMenu);
+ }
+ else
+ {
+ mpQMenuBar->addMenu(pQMenu);
+ }
+
+ // correct parent menu for generated menu
+ if (pSalMenuItem->mpSubMenu)
+ {
+ pSalMenuItem->mpSubMenu->mpQMenu = pQMenu;
+ }
+
+ connect(pQMenu, &QMenu::aboutToShow, this,
+ [pSalMenuItem] { slotMenuAboutToShow(pSalMenuItem); });
+ connect(pQMenu, &QMenu::aboutToHide, this,
+ [pSalMenuItem] { slotMenuAboutToHide(pSalMenuItem); });
+ }
+ }
+ else
+ {
+ if (!mpQMenu)
+ {
+ // no QMenu set, instantiate own one
+ mpOwnedQMenu.reset(new QMenu);
+ mpQMenu = mpOwnedQMenu.get();
+ connectHelpSignalSlots(mpQMenu, pSalMenuItem);
+ }
+
+ if (pSalMenuItem->mpSubMenu)
+ {
+ // submenu
+ QMenu* pQMenu = new QMenu(toQString(aText), nullptr);
+ connectHelpSignalSlots(pQMenu, pSalMenuItem);
+ pSalMenuItem->mpMenu.reset(pQMenu);
+
+ if ((nPos != MENU_APPEND)
+ && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenu->actions().size())))
+ {
+ mpQMenu->insertMenu(mpQMenu->actions()[nPos], pQMenu);
+ }
+ else
+ {
+ mpQMenu->addMenu(pQMenu);
+ }
+
+ // correct parent menu for generated menu
+ pSalMenuItem->mpSubMenu->mpQMenu = pQMenu;
+
+ ReinitializeActionGroup(nPos);
+
+ // clear all action groups since menu is recreated
+ pSalMenuItem->mpSubMenu->ResetAllActionGroups();
+
+ connect(pQMenu, &QMenu::aboutToShow, this,
+ [pSalMenuItem] { slotMenuAboutToShow(pSalMenuItem); });
+ connect(pQMenu, &QMenu::aboutToHide, this,
+ [pSalMenuItem] { slotMenuAboutToHide(pSalMenuItem); });
+ }
+ else
+ {
+ if (pSalMenuItem->mnType == MenuItemType::SEPARATOR)
+ {
+ QAction* pAction = new QAction(nullptr);
+ pSalMenuItem->mpAction.reset(pAction);
+ pAction->setSeparator(true);
+
+ if ((nPos != MENU_APPEND)
+ && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenu->actions().size())))
+ {
+ mpQMenu->insertAction(mpQMenu->actions()[nPos], pAction);
+ }
+ else
+ {
+ mpQMenu->addAction(pAction);
+ }
+
+ ReinitializeActionGroup(nPos);
+ }
+ else
+ {
+ // leaf menu
+ QAction* pAction = new QAction(toQString(aText), nullptr);
+ pSalMenuItem->mpAction.reset(pAction);
+
+ if ((nPos != MENU_APPEND)
+ && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenu->actions().size())))
+ {
+ mpQMenu->insertAction(mpQMenu->actions()[nPos], pAction);
+ }
+ else
+ {
+ mpQMenu->addAction(pAction);
+ }
+
+ ReinitializeActionGroup(nPos);
+
+ UpdateActionGroupItem(pSalMenuItem);
+
+ pAction->setShortcut(toQString(nAccelKey.GetName()));
+
+ connect(pAction, &QAction::triggered, this,
+ [pSalMenuItem] { slotMenuTriggered(pSalMenuItem); });
+ connect(pAction, &QAction::hovered, this,
+ [pSalMenuItem] { slotMenuHovered(pSalMenuItem); });
+ }
+ }
+ }
+
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ {
+ pAction->setEnabled(pSalMenuItem->mbEnabled);
+ pAction->setVisible(pSalMenuItem->mbVisible);
+ }
+}
+
+void QtMenu::ReinitializeActionGroup(unsigned nPos)
+{
+ const unsigned nCount = GetItemCount();
+
+ if (nCount == 0)
+ {
+ return;
+ }
+
+ if (nPos == MENU_APPEND)
+ {
+ nPos = nCount - 1;
+ }
+ else if (nPos >= nCount)
+ {
+ return;
+ }
+
+ QtMenuItem* pPrevItem = (nPos > 0) ? GetItemAtPos(nPos - 1) : nullptr;
+ QtMenuItem* pCurrentItem = GetItemAtPos(nPos);
+ QtMenuItem* pNextItem = (nPos < nCount - 1) ? GetItemAtPos(nPos + 1) : nullptr;
+
+ if (pCurrentItem->mnType == MenuItemType::SEPARATOR)
+ {
+ pCurrentItem->mpActionGroup.reset();
+
+ // if it's inserted into middle of existing group, split it into two groups:
+ // first goes original group, after separator goes new group
+ if (pPrevItem && pPrevItem->mpActionGroup && pNextItem && pNextItem->mpActionGroup
+ && (pPrevItem->mpActionGroup == pNextItem->mpActionGroup))
+ {
+ std::shared_ptr<QActionGroup> pFirstActionGroup = pPrevItem->mpActionGroup;
+ auto pSecondActionGroup = std::make_shared<QActionGroup>(nullptr);
+ pSecondActionGroup->setExclusive(true);
+
+ auto actions = pFirstActionGroup->actions();
+
+ for (unsigned idx = nPos + 1; idx < nCount; ++idx)
+ {
+ QtMenuItem* pModifiedItem = GetItemAtPos(idx);
+
+ if ((!pModifiedItem) || (!pModifiedItem->mpActionGroup))
+ {
+ break;
+ }
+
+ pModifiedItem->mpActionGroup = pSecondActionGroup;
+ auto action = pModifiedItem->getAction();
+
+ if (actions.contains(action))
+ {
+ pFirstActionGroup->removeAction(action);
+ pSecondActionGroup->addAction(action);
+ }
+ }
+ }
+ }
+ else
+ {
+ if (!pCurrentItem->mpActionGroup)
+ {
+ // unless element is inserted between two separators, or a separator and an end of vector, use neighbouring group since it's shared
+ if (pPrevItem && pPrevItem->mpActionGroup)
+ {
+ pCurrentItem->mpActionGroup = pPrevItem->mpActionGroup;
+ }
+ else if (pNextItem && pNextItem->mpActionGroup)
+ {
+ pCurrentItem->mpActionGroup = pNextItem->mpActionGroup;
+ }
+ else
+ {
+ pCurrentItem->mpActionGroup = std::make_shared<QActionGroup>(nullptr);
+ pCurrentItem->mpActionGroup->setExclusive(true);
+ }
+ }
+
+ // if there's also a different group after this element, merge it
+ if (pNextItem && pNextItem->mpActionGroup
+ && (pCurrentItem->mpActionGroup != pNextItem->mpActionGroup))
+ {
+ auto pFirstCheckedAction = pCurrentItem->mpActionGroup->checkedAction();
+ auto pSecondCheckedAction = pNextItem->mpActionGroup->checkedAction();
+ auto actions = pNextItem->mpActionGroup->actions();
+
+ // first move all actions from second group to first one, and if first group already has checked action,
+ // and second group also has a checked action, uncheck action from second group
+ for (auto action : actions)
+ {
+ pNextItem->mpActionGroup->removeAction(action);
+
+ if (pFirstCheckedAction && pSecondCheckedAction && (action == pSecondCheckedAction))
+ {
+ action->setChecked(false);
+ }
+
+ pCurrentItem->mpActionGroup->addAction(action);
+ }
+
+ // now replace all pointers to second group with pointers to first group
+ for (unsigned idx = nPos + 1; idx < nCount; ++idx)
+ {
+ QtMenuItem* pModifiedItem = GetItemAtPos(idx);
+
+ if ((!pModifiedItem) || (!pModifiedItem->mpActionGroup))
+ {
+ break;
+ }
+
+ pModifiedItem->mpActionGroup = pCurrentItem->mpActionGroup;
+ }
+ }
+ }
+}
+
+void QtMenu::ResetAllActionGroups()
+{
+ for (unsigned nItem = 0; nItem < GetItemCount(); ++nItem)
+ {
+ QtMenuItem* pSalMenuItem = GetItemAtPos(nItem);
+ pSalMenuItem->mpActionGroup.reset();
+ }
+}
+
+void QtMenu::UpdateActionGroupItem(const QtMenuItem* pSalMenuItem)
+{
+ QAction* pAction = pSalMenuItem->getAction();
+ if (!pAction)
+ return;
+
+ bool bChecked = mpVCLMenu->IsItemChecked(pSalMenuItem->mnId);
+ MenuItemBits itemBits = mpVCLMenu->GetItemBits(pSalMenuItem->mnId);
+
+ if (itemBits & MenuItemBits::RADIOCHECK)
+ {
+ pAction->setCheckable(true);
+
+ if (pSalMenuItem->mpActionGroup)
+ {
+ pSalMenuItem->mpActionGroup->addAction(pAction);
+ }
+
+ pAction->setChecked(bChecked);
+ }
+ else
+ {
+ pAction->setActionGroup(nullptr);
+
+ if (itemBits & MenuItemBits::CHECKABLE)
+ {
+ pAction->setCheckable(true);
+ pAction->setChecked(bChecked);
+ }
+ else
+ {
+ pAction->setChecked(false);
+ pAction->setCheckable(false);
+ }
+ }
+}
+
+void QtMenu::InsertItem(SalMenuItem* pSalMenuItem, unsigned nPos)
+{
+ SolarMutexGuard aGuard;
+ QtMenuItem* pItem = static_cast<QtMenuItem*>(pSalMenuItem);
+
+ if (nPos == MENU_APPEND)
+ maItems.push_back(pItem);
+ else
+ maItems.insert(maItems.begin() + nPos, pItem);
+
+ pItem->mpParentMenu = this;
+
+ InsertMenuItem(pItem, nPos);
+}
+
+void QtMenu::RemoveItem(unsigned nPos)
+{
+ SolarMutexGuard aGuard;
+
+ if (nPos >= maItems.size())
+ return;
+
+ QtMenuItem* pItem = maItems[nPos];
+ pItem->mpAction.reset();
+ pItem->mpMenu.reset();
+
+ maItems.erase(maItems.begin() + nPos);
+
+ // Recalculate action groups if necessary:
+ // if separator between two QActionGroups was removed,
+ // it may be needed to merge them
+ if (nPos > 0)
+ {
+ ReinitializeActionGroup(nPos - 1);
+ }
+}
+
+void QtMenu::SetSubMenu(SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned nPos)
+{
+ SolarMutexGuard aGuard;
+ QtMenuItem* pItem = static_cast<QtMenuItem*>(pSalMenuItem);
+ QtMenu* pQSubMenu = static_cast<QtMenu*>(pSubMenu);
+
+ pItem->mpSubMenu = pQSubMenu;
+ // at this point the pointer to parent menu may be outdated, update it too
+ pItem->mpParentMenu = this;
+
+ if (pQSubMenu != nullptr)
+ {
+ pQSubMenu->mpParentSalMenu = this;
+ pQSubMenu->mpQMenu = pItem->mpMenu.get();
+ }
+
+ // if it's not a menu bar item, then convert it to corresponding item if type if necessary.
+ // If submenu is present and it's an action, convert it to menu.
+ // If submenu is not present and it's a menu, convert it to action.
+ // It may be fine to proceed in any case, but by skipping other cases
+ // amount of unneeded actions taken should be reduced.
+ if (pItem->mpParentMenu->mbMenuBar || (pQSubMenu && pItem->mpMenu)
+ || ((!pQSubMenu) && pItem->mpAction))
+ {
+ return;
+ }
+
+ InsertMenuItem(pItem, nPos);
+}
+
+void QtMenu::SetFrame(const SalFrame* pFrame)
+{
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ pSalInst->RunInMainThread([this, pFrame]() { SetFrame(pFrame); });
+ return;
+ }
+
+ SolarMutexGuard aGuard;
+ assert(mbMenuBar);
+ mpFrame = const_cast<QtFrame*>(static_cast<const QtFrame*>(pFrame));
+
+ mpFrame->SetMenu(this);
+
+ QtMainWindow* pMainWindow = mpFrame->GetTopLevelWindow();
+ if (!pMainWindow)
+ return;
+
+ mpQMenuBar = new QMenuBar();
+ pMainWindow->setMenuBar(mpQMenuBar);
+
+ QWidget* pWidget = mpQMenuBar->cornerWidget(Qt::TopRightCorner);
+ if (pWidget)
+ {
+ m_pButtonGroup = pWidget->findChild<QButtonGroup*>(gButtonGroupKey);
+ assert(m_pButtonGroup);
+ connect(m_pButtonGroup, QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked), this,
+ &QtMenu::slotMenuBarButtonClicked);
+ QPushButton* pButton = static_cast<QPushButton*>(m_pButtonGroup->button(CLOSE_BUTTON_ID));
+ if (pButton)
+ connect(pButton, &QPushButton::clicked, this, &QtMenu::slotCloseDocument);
+ }
+ else
+ m_pButtonGroup = nullptr;
+ mpQMenu = nullptr;
+
+ DoFullMenuUpdate(mpVCLMenu);
+}
+
+void QtMenu::DoFullMenuUpdate(Menu* pMenuBar)
+{
+ // clear action groups since menu is rebuilt
+ ResetAllActionGroups();
+ ShowCloseButton(false);
+
+ for (sal_Int32 nItem = 0; nItem < static_cast<sal_Int32>(GetItemCount()); nItem++)
+ {
+ QtMenuItem* pSalMenuItem = GetItemAtPos(nItem);
+ InsertMenuItem(pSalMenuItem, nItem);
+ SetItemImage(nItem, pSalMenuItem, pSalMenuItem->maImage);
+ const bool bShowDisabled
+ = bool(pMenuBar->GetMenuFlags() & MenuFlags::AlwaysShowDisabledEntries)
+ || !bool(pMenuBar->GetMenuFlags() & MenuFlags::HideDisabledEntries);
+ const bool bVisible = pSalMenuItem->mbVisible
+ && (bShowDisabled || mpVCLMenu->IsItemEnabled(pSalMenuItem->mnId));
+ pSalMenuItem->getAction()->setVisible(bVisible);
+
+ if (pSalMenuItem->mpSubMenu != nullptr)
+ {
+ pMenuBar->HandleMenuActivateEvent(pSalMenuItem->mpSubMenu->GetMenu());
+ pSalMenuItem->mpSubMenu->DoFullMenuUpdate(pMenuBar);
+ pMenuBar->HandleMenuDeActivateEvent(pSalMenuItem->mpSubMenu->GetMenu());
+ }
+ }
+}
+
+void QtMenu::ShowItem(unsigned nPos, bool bShow)
+{
+ if (nPos < maItems.size())
+ {
+ QtMenuItem* pSalMenuItem = GetItemAtPos(nPos);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ pAction->setVisible(bShow);
+ pSalMenuItem->mbVisible = bShow;
+ }
+}
+
+void QtMenu::SetItemBits(unsigned nPos, MenuItemBits)
+{
+ if (nPos < maItems.size())
+ {
+ QtMenuItem* pSalMenuItem = GetItemAtPos(nPos);
+ UpdateActionGroupItem(pSalMenuItem);
+ }
+}
+
+void QtMenu::CheckItem(unsigned nPos, bool bChecked)
+{
+ if (nPos < maItems.size())
+ {
+ QtMenuItem* pSalMenuItem = GetItemAtPos(nPos);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ {
+ pAction->setCheckable(true);
+ pAction->setChecked(bChecked);
+ }
+ }
+}
+
+void QtMenu::EnableItem(unsigned nPos, bool bEnable)
+{
+ if (nPos < maItems.size())
+ {
+ QtMenuItem* pSalMenuItem = GetItemAtPos(nPos);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ pAction->setEnabled(bEnable);
+ pSalMenuItem->mbEnabled = bEnable;
+ }
+}
+
+void QtMenu::SetItemText(unsigned, SalMenuItem* pItem, const OUString& rText)
+{
+ QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ {
+ OUString aText(rText);
+ NativeItemText(aText);
+ pAction->setText(toQString(aText));
+ }
+}
+
+void QtMenu::SetItemImage(unsigned, SalMenuItem* pItem, const Image& rImage)
+{
+ QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem);
+
+ // Save new image to use it in DoFullMenuUpdate
+ pSalMenuItem->maImage = rImage;
+
+ QAction* pAction = pSalMenuItem->getAction();
+ if (!pAction)
+ return;
+
+ pAction->setIcon(QPixmap::fromImage(toQImage(rImage)));
+}
+
+void QtMenu::SetAccelerator(unsigned, SalMenuItem* pItem, const vcl::KeyCode&,
+ const OUString& rText)
+{
+ QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ pAction->setShortcut(QKeySequence(toQString(rText), QKeySequence::PortableText));
+}
+
+void QtMenu::GetSystemMenuData(SystemMenuData*) {}
+
+QtMenu* QtMenu::GetTopLevel()
+{
+ QtMenu* pMenu = this;
+ while (pMenu->mpParentSalMenu)
+ pMenu = pMenu->mpParentSalMenu;
+ return pMenu;
+}
+
+bool QtMenu::validateQMenuBar() const
+{
+ if (!mpQMenuBar)
+ return false;
+ assert(mpFrame);
+ QtMainWindow* pMainWindow = mpFrame->GetTopLevelWindow();
+ assert(pMainWindow);
+ const bool bValid = mpQMenuBar == pMainWindow->menuBar();
+ if (!bValid)
+ {
+ QtMenu* thisPtr = const_cast<QtMenu*>(this);
+ thisPtr->mpQMenuBar = nullptr;
+ }
+ return bValid;
+}
+
+void QtMenu::ShowMenuBar(bool bVisible)
+{
+ if (!validateQMenuBar())
+ return;
+
+ mpQMenuBar->setVisible(bVisible);
+ if (bVisible)
+ lcl_force_menubar_layout_update(*mpQMenuBar);
+}
+
+void QtMenu::slotMenuHovered(QtMenuItem* pItem)
+{
+ const OUString sHelpId = pItem->mpParentMenu->GetMenu()->GetHelpId(pItem->mnId);
+ m_sCurrentHelpId = sHelpId;
+}
+
+void QtMenu::slotShowHelp()
+{
+ SolarMutexGuard aGuard;
+ Help* pHelp = Application::GetHelp();
+ if (pHelp && !m_sCurrentHelpId.isEmpty())
+ {
+ pHelp->Start(m_sCurrentHelpId);
+ }
+}
+
+void QtMenu::slotMenuTriggered(QtMenuItem* pQItem)
+{
+ if (!pQItem)
+ return;
+
+ QtMenu* pSalMenu = pQItem->mpParentMenu;
+ QtMenu* pTopLevel = pSalMenu->GetTopLevel();
+
+ Menu* pMenu = pSalMenu->GetMenu();
+ auto mnId = pQItem->mnId;
+
+ // HACK to allow HandleMenuCommandEvent to "not-set" the checked button
+ // LO expects a signal before an item state change, so reset the check item
+ if (pQItem->mpAction->isCheckable()
+ && (!pQItem->mpActionGroup || pQItem->mpActionGroup->actions().size() <= 1))
+ pQItem->mpAction->setChecked(!pQItem->mpAction->isChecked());
+ pTopLevel->GetMenu()->HandleMenuCommandEvent(pMenu, mnId);
+}
+
+void QtMenu::slotMenuAboutToShow(QtMenuItem* pQItem)
+{
+ if (pQItem)
+ {
+ QtMenu* pSalMenu = pQItem->mpSubMenu;
+ QtMenu* pTopLevel = pSalMenu->GetTopLevel();
+
+ Menu* pMenu = pSalMenu->GetMenu();
+
+ // following function may update the menu
+ pTopLevel->GetMenu()->HandleMenuActivateEvent(pMenu);
+ }
+}
+
+void QtMenu::slotMenuAboutToHide(QtMenuItem* pQItem)
+{
+ if (pQItem)
+ {
+ QtMenu* pSalMenu = pQItem->mpSubMenu;
+ QtMenu* pTopLevel = pSalMenu->GetTopLevel();
+
+ Menu* pMenu = pSalMenu->GetMenu();
+
+ pTopLevel->GetMenu()->HandleMenuDeActivateEvent(pMenu);
+ }
+}
+
+void QtMenu::NativeItemText(OUString& rItemText)
+{
+ // preserve literal '&'s in menu texts
+ rItemText = rItemText.replaceAll("&", "&&");
+
+ rItemText = rItemText.replace('~', '&');
+}
+
+void QtMenu::slotCloseDocument()
+{
+ MenuBar* pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
+ if (pVclMenuBar)
+ Application::PostUserEvent(pVclMenuBar->GetCloseButtonClickHdl());
+}
+
+void QtMenu::slotMenuBarButtonClicked(QAbstractButton* pButton)
+{
+ MenuBar* pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
+ if (pVclMenuBar)
+ {
+ SolarMutexGuard aGuard;
+ pVclMenuBar->HandleMenuButtonEvent(m_pButtonGroup->id(pButton));
+ }
+}
+
+QPushButton* QtMenu::ImplAddMenuBarButton(const QIcon& rIcon, const QString& rToolTip, int nId)
+{
+ if (!validateQMenuBar())
+ return nullptr;
+
+ QWidget* pWidget = mpQMenuBar->cornerWidget(Qt::TopRightCorner);
+ QHBoxLayout* pLayout;
+ if (!pWidget)
+ {
+ assert(!m_pButtonGroup);
+ pWidget = new QWidget(mpQMenuBar);
+ assert(!pWidget->layout());
+ pLayout = new QHBoxLayout();
+ pLayout->setContentsMargins(QMargins());
+ pLayout->setSpacing(0);
+ pWidget->setLayout(pLayout);
+ m_pButtonGroup = new QButtonGroup(pLayout);
+ m_pButtonGroup->setObjectName(gButtonGroupKey);
+ m_pButtonGroup->setExclusive(false);
+ connect(m_pButtonGroup, QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked), this,
+ &QtMenu::slotMenuBarButtonClicked);
+ pWidget->show();
+ mpQMenuBar->setCornerWidget(pWidget, Qt::TopRightCorner);
+ }
+ else
+ pLayout = static_cast<QHBoxLayout*>(pWidget->layout());
+ assert(m_pButtonGroup);
+ assert(pLayout);
+
+ QPushButton* pButton = static_cast<QPushButton*>(m_pButtonGroup->button(nId));
+ if (pButton)
+ ImplRemoveMenuBarButton(nId);
+
+ pButton = new QPushButton();
+ // we don't want the button to increase the QMenuBar height, so a fixed size square it is
+ const int nFixedLength
+ = mpQMenuBar->height() - 2 * mpQMenuBar->style()->pixelMetric(QStyle::PM_MenuBarVMargin);
+ pButton->setFixedSize(nFixedLength, nFixedLength);
+ pButton->setIcon(rIcon);
+ pButton->setFlat(true);
+ pButton->setFocusPolicy(Qt::NoFocus);
+ pButton->setToolTip(rToolTip);
+
+ m_pButtonGroup->addButton(pButton, nId);
+ int nPos = pLayout->count();
+ if (m_pButtonGroup->button(CLOSE_BUTTON_ID))
+ nPos--;
+ pLayout->insertWidget(nPos, pButton, 0, Qt::AlignCenter);
+ // show must happen after adding the button to the layout, otherwise the button is
+ // shown, but not correct in the layout, if at all! Some times the layout ignores it.
+ pButton->show();
+
+ lcl_force_menubar_layout_update(*mpQMenuBar);
+
+ return pButton;
+}
+
+bool QtMenu::AddMenuBarButton(const SalMenuButtonItem& rItem)
+{
+ if (!validateQMenuBar())
+ return false;
+ return !!ImplAddMenuBarButton(QIcon(QPixmap::fromImage(toQImage(rItem.maImage))),
+ toQString(rItem.maToolTipText), rItem.mnId);
+}
+
+void QtMenu::ImplRemoveMenuBarButton(int nId)
+{
+ if (!validateQMenuBar())
+ return;
+
+ assert(m_pButtonGroup);
+ auto* pButton = m_pButtonGroup->button(nId);
+ assert(pButton);
+ QWidget* pWidget = mpQMenuBar->cornerWidget(Qt::TopRightCorner);
+ assert(pWidget);
+ QLayout* pLayout = pWidget->layout();
+ m_pButtonGroup->removeButton(pButton);
+ pLayout->removeWidget(pButton);
+ delete pButton;
+
+ lcl_force_menubar_layout_update(*mpQMenuBar);
+}
+
+void QtMenu::connectHelpShortcut(QMenu* pMenu)
+{
+ assert(pMenu);
+ QKeySequence sequence(QKeySequence::HelpContents);
+ QShortcut* pQShortcut = new QShortcut(sequence, pMenu);
+ connect(pQShortcut, &QShortcut::activated, this, QtMenu::slotShowHelp);
+ connect(pQShortcut, &QShortcut::activatedAmbiguously, this, QtMenu::slotShowHelp);
+}
+
+void QtMenu::connectHelpSignalSlots(QMenu* pMenu, QtMenuItem* pSalMenuItem)
+{
+ // connect hovered signal of the menu's own action
+ QAction* pAction = pMenu->menuAction();
+ assert(pAction);
+ connect(pAction, &QAction::hovered, this, [pSalMenuItem] { slotMenuHovered(pSalMenuItem); });
+
+ // connect slot to handle Help key (F1)
+ connectHelpShortcut(pMenu);
+}
+
+void QtMenu::RemoveMenuBarButton(sal_uInt16 nId) { ImplRemoveMenuBarButton(nId); }
+
+tools::Rectangle QtMenu::GetMenuBarButtonRectPixel(sal_uInt16 nId, SalFrame* pFrame)
+{
+#ifdef NDEBUG
+ Q_UNUSED(pFrame);
+#endif
+ if (!validateQMenuBar())
+ return tools::Rectangle();
+
+ assert(mpFrame == static_cast<QtFrame*>(pFrame));
+ assert(m_pButtonGroup);
+ auto* pButton = static_cast<QPushButton*>(m_pButtonGroup->button(nId));
+ assert(pButton);
+
+ // unfortunatly, calling lcl_force_menubar_layout_update results in a temporary wrong menubar size,
+ // but it's the correct minimal size AFAIK and the layout seems correct, so just adjust the width.
+ QPoint aPos = pButton->mapTo(mpFrame->asChild(), QPoint());
+ aPos.rx() += (mpFrame->asChild()->width() - mpQMenuBar->width());
+ return tools::Rectangle(toPoint(aPos), toSize(pButton->size()));
+}
+
+void QtMenu::ShowCloseButton(bool bShow)
+{
+ if (!validateQMenuBar())
+ return;
+
+ if (!bShow && !m_pButtonGroup)
+ return;
+
+ QPushButton* pButton = nullptr;
+ if (m_pButtonGroup)
+ pButton = static_cast<QPushButton*>(m_pButtonGroup->button(CLOSE_BUTTON_ID));
+ if (!bShow && !pButton)
+ return;
+
+ if (!pButton)
+ {
+ QIcon aIcon;
+ if (QIcon::hasThemeIcon("window-close-symbolic"))
+ aIcon = QIcon::fromTheme("window-close-symbolic");
+ else
+ aIcon = QIcon(
+ QPixmap::fromImage(toQImage(Image(StockImage::Yes, SV_RESID_BITMAP_CLOSEDOC))));
+ pButton = ImplAddMenuBarButton(aIcon, toQString(VclResId(SV_HELPTEXT_CLOSEDOCUMENT)),
+ CLOSE_BUTTON_ID);
+ connect(pButton, &QPushButton::clicked, this, &QtMenu::slotCloseDocument);
+ }
+
+ if (bShow)
+ pButton->show();
+ else
+ pButton->hide();
+
+ lcl_force_menubar_layout_update(*mpQMenuBar);
+}
+
+bool QtMenu::ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangle& rRect,
+ FloatWinPopupFlags nFlags)
+{
+ assert(mpQMenu);
+ DoFullMenuUpdate(mpVCLMenu);
+ mpQMenu->setTearOffEnabled(bool(nFlags & FloatWinPopupFlags::AllowTearOff));
+
+ const VclPtr<vcl::Window> xParent = pWin->ImplGetWindowImpl()->mpRealParent;
+ AbsoluteScreenPixelRectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect);
+
+ // tdf#154447 Menu bar height has to be added
+ QtFrame* pFrame = static_cast<QtFrame*>(pWin->ImplGetFrame());
+ assert(pFrame);
+ aFloatRect.SetPosY(aFloatRect.getY() + pFrame->menuBarOffset());
+
+ const QRect aRect = toQRect(aFloatRect, 1 / pFrame->devicePixelRatioF());
+ mpQMenu->exec(aRect.bottomLeft());
+
+ return true;
+}
+
+int QtMenu::GetMenuBarHeight() const
+{
+ if (!validateQMenuBar() || mpQMenuBar->isHidden())
+ return 0;
+
+ return mpQMenuBar->height();
+}
+
+QtMenuItem::QtMenuItem(const SalItemParams* pItemData)
+ : mpParentMenu(nullptr)
+ , mpSubMenu(nullptr)
+ , mnId(pItemData->nId)
+ , mnType(pItemData->eType)
+ , mbVisible(true)
+ , mbEnabled(true)
+ , maImage(pItemData->aImage)
+{
+}
+
+QAction* QtMenuItem::getAction() const
+{
+ if (mpMenu)
+ return mpMenu->menuAction();
+ if (mpAction)
+ return mpAction.get();
+ return nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtObject.cxx b/vcl/qt5/QtObject.cxx
new file mode 100644
index 0000000000..fbdc8e9b62
--- /dev/null
+++ b/vcl/qt5/QtObject.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 <QtObject.hxx>
+#include <QtObject.moc>
+
+#include <QtFrame.hxx>
+#include <QtWidget.hxx>
+
+#include <QtGui/QGuiApplication>
+#include <QtGui/QKeyEvent>
+#include <QtGui/QMouseEvent>
+
+QtObject::QtObject(QtFrame* pParent, bool bShow)
+ : m_pParent(pParent)
+ , m_pQWidget(nullptr)
+ , m_bForwardKey(false)
+{
+ if (!m_pParent || !pParent->GetQWidget())
+ return;
+
+ m_pQWidget = new QtObjectWidget(*this);
+ if (bShow)
+ m_pQWidget->show();
+
+ QtFrame::FillSystemEnvData(m_aSystemData, reinterpret_cast<sal_IntPtr>(this), m_pQWidget);
+}
+
+QtObject::~QtObject()
+{
+ if (m_pQWidget)
+ {
+ m_pQWidget->setParent(nullptr);
+ delete m_pQWidget;
+ }
+}
+
+QWindow* QtObject::windowHandle() const
+{
+ return m_pQWidget ? m_pQWidget->windowHandle() : nullptr;
+}
+
+void QtObject::ResetClipRegion()
+{
+ if (m_pQWidget)
+ m_pRegion = QRegion(m_pQWidget->geometry());
+ else
+ m_pRegion = QRegion();
+}
+
+void QtObject::BeginSetClipRegion(sal_uInt32) { m_pRegion = QRegion(); }
+
+void QtObject::UnionClipRegion(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight)
+{
+ m_pRegion += QRect(nX, nY, nWidth, nHeight);
+}
+
+void QtObject::EndSetClipRegion()
+{
+ if (m_pQWidget)
+ m_pRegion = m_pRegion.intersected(m_pQWidget->geometry());
+}
+
+void QtObject::SetPosSize(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight)
+{
+ if (m_pQWidget)
+ {
+ m_pQWidget->move(nX, nY);
+ m_pQWidget->setFixedSize(nWidth, nHeight);
+ }
+}
+
+void QtObject::Show(bool bVisible)
+{
+ if (m_pQWidget)
+ m_pQWidget->setVisible(bVisible);
+}
+
+void QtObject::SetForwardKey(bool bEnable) { m_bForwardKey = bEnable; }
+
+void QtObject::Reparent(SalFrame* pFrame)
+{
+ QtFrame* pNewParent = static_cast<QtFrame*>(pFrame);
+ if (m_pParent == pNewParent)
+ return;
+ m_pParent = pNewParent;
+ m_pQWidget->setParent(m_pParent->GetQWidget());
+}
+
+QtObjectWidget::QtObjectWidget(QtObject& rParent)
+ : QWidget(rParent.frame()->GetQWidget())
+ , m_rParent(rParent)
+{
+ assert(m_rParent.frame() && m_rParent.frame()->GetQWidget());
+ setAttribute(Qt::WA_NoSystemBackground);
+ setAttribute(Qt::WA_OpaquePaintEvent);
+}
+
+void QtObjectWidget::focusInEvent(QFocusEvent*)
+{
+ SolarMutexGuard aGuard;
+ m_rParent.CallCallback(SalObjEvent::GetFocus);
+}
+
+void QtObjectWidget::focusOutEvent(QFocusEvent*)
+{
+ SolarMutexGuard aGuard;
+ m_rParent.CallCallback(SalObjEvent::LoseFocus);
+}
+
+void QtObjectWidget::mousePressEvent(QMouseEvent* pEvent)
+{
+ SolarMutexGuard aGuard;
+ m_rParent.CallCallback(SalObjEvent::ToTop);
+
+ if (m_rParent.forwardKey())
+ pEvent->ignore();
+}
+
+void QtObjectWidget::mouseReleaseEvent(QMouseEvent* pEvent)
+{
+ if (m_rParent.forwardKey())
+ pEvent->ignore();
+}
+
+void QtObjectWidget::keyReleaseEvent(QKeyEvent* pEvent)
+{
+ if (m_rParent.forwardKey())
+ pEvent->ignore();
+}
+
+void QtObjectWidget::keyPressEvent(QKeyEvent* pEvent)
+{
+ if (m_rParent.forwardKey())
+ pEvent->ignore();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtOpenGLContext.cxx b/vcl/qt5/QtOpenGLContext.cxx
new file mode 100644
index 0000000000..9dd75b69a1
--- /dev/null
+++ b/vcl/qt5/QtOpenGLContext.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 <QtOpenGLContext.hxx>
+
+#include <epoxy/gl.h>
+
+#include <vcl/sysdata.hxx>
+#include <opengl/zone.hxx>
+#include <sal/log.hxx>
+
+#include <window.h>
+
+#include <QtObject.hxx>
+
+#include <QtGui/QOpenGLContext>
+#include <QtGui/QWindow>
+
+bool QtOpenGLContext::g_bAnyCurrent = false;
+
+void QtOpenGLContext::swapBuffers()
+{
+ OpenGLZone aZone;
+
+ if (m_pContext && m_pWindow && m_pWindow->isExposed())
+ {
+ m_pContext->swapBuffers(m_pWindow);
+ }
+
+ BuffersSwapped();
+}
+
+void QtOpenGLContext::resetCurrent()
+{
+ clearCurrent();
+
+ OpenGLZone aZone;
+
+ if (m_pContext)
+ {
+ m_pContext->doneCurrent();
+ g_bAnyCurrent = false;
+ }
+}
+
+bool QtOpenGLContext::isCurrent()
+{
+ OpenGLZone aZone;
+ return g_bAnyCurrent && (QOpenGLContext::currentContext() == m_pContext);
+}
+
+bool QtOpenGLContext::isAnyCurrent()
+{
+ OpenGLZone aZone;
+ return g_bAnyCurrent && (QOpenGLContext::currentContext() != nullptr);
+}
+
+bool QtOpenGLContext::ImplInit()
+{
+ if (!m_pWindow)
+ {
+ SAL_WARN("vcl.opengl.qt", "failed to create window");
+ return false;
+ }
+
+ m_pWindow->setSurfaceType(QSurface::OpenGLSurface);
+ m_pWindow->create();
+
+ m_pContext = new QOpenGLContext(m_pWindow);
+ if (!m_pContext->create())
+ {
+ SAL_WARN("vcl.opengl.qt", "failed to create context");
+ return false;
+ }
+
+ m_pContext->makeCurrent(m_pWindow);
+ g_bAnyCurrent = true;
+
+ bool bRet = InitGL();
+ InitGLDebugging();
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+ registerAsCurrent();
+
+ return bRet;
+}
+
+void QtOpenGLContext::makeCurrent()
+{
+ if (isCurrent())
+ return;
+
+ OpenGLZone aZone;
+
+ clearCurrent();
+
+ if (m_pContext && m_pWindow)
+ {
+ m_pContext->makeCurrent(m_pWindow);
+ g_bAnyCurrent = true;
+ }
+
+ registerAsCurrent();
+}
+
+void QtOpenGLContext::destroyCurrentContext()
+{
+ OpenGLZone aZone;
+
+ if (m_pContext)
+ {
+ m_pContext->doneCurrent();
+ g_bAnyCurrent = false;
+ }
+
+ if (glGetError() != GL_NO_ERROR)
+ {
+ SAL_WARN("vcl.opengl.qt", "glError: " << glGetError());
+ }
+}
+
+void QtOpenGLContext::initWindow()
+{
+ if (!m_pChildWindow)
+ {
+ SystemWindowData winData = generateWinData(mpWindow, mbRequestLegacyContext);
+ m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
+ }
+
+ if (m_pChildWindow)
+ {
+ InitChildWindow(m_pChildWindow.get());
+ }
+
+ m_pWindow
+ = static_cast<QtObject*>(m_pChildWindow->ImplGetWindowImpl()->mpSysObj)->windowHandle();
+}
diff --git a/vcl/qt5/QtPainter.cxx b/vcl/qt5/QtPainter.cxx
new file mode 100644
index 0000000000..115b4a82c6
--- /dev/null
+++ b/vcl/qt5/QtPainter.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 <QtPainter.hxx>
+
+#include <QtGui/QColor>
+
+QtPainter::QtPainter(QtGraphicsBackend& rGraphics, bool bPrepareBrush, sal_uInt8 nTransparency)
+ : m_rGraphics(rGraphics)
+{
+ if (rGraphics.m_pQImage)
+ {
+ if (!begin(rGraphics.m_pQImage))
+ std::abort();
+ }
+ else
+ {
+ assert(rGraphics.m_pFrame);
+ if (!begin(rGraphics.m_pFrame->GetQWidget()))
+ std::abort();
+ }
+ if (!rGraphics.m_aClipPath.isEmpty())
+ setClipPath(rGraphics.m_aClipPath);
+ else
+ setClipRegion(rGraphics.m_aClipRegion);
+ if (rGraphics.m_oLineColor)
+ {
+ QColor aColor = toQColor(*rGraphics.m_oLineColor);
+ aColor.setAlpha(nTransparency);
+ setPen(aColor);
+ }
+ else
+ setPen(Qt::NoPen);
+ if (bPrepareBrush && rGraphics.m_oFillColor)
+ {
+ QColor aColor = toQColor(*rGraphics.m_oFillColor);
+ aColor.setAlpha(nTransparency);
+ setBrush(aColor);
+ }
+ setCompositionMode(rGraphics.m_eCompositionMode);
+ setRenderHint(QPainter::Antialiasing, m_rGraphics.getAntiAlias());
+}
diff --git a/vcl/qt5/QtPrinter.cxx b/vcl/qt5/QtPrinter.cxx
new file mode 100644
index 0000000000..346dd3f26e
--- /dev/null
+++ b/vcl/qt5/QtPrinter.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 <QtPrinter.hxx>
+
+QtPrinter::QtPrinter(SalInfoPrinter* pInfoPrinter)
+ : PspSalPrinter(pInfoPrinter)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtSvpGraphics.cxx b/vcl/qt5/QtSvpGraphics.cxx
new file mode 100644
index 0000000000..903fee1f56
--- /dev/null
+++ b/vcl/qt5/QtSvpGraphics.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 <sal/config.h>
+#include <sal/log.hxx>
+#include <salbmp.hxx>
+
+#include <config_cairo_canvas.h>
+
+#include <QtData.hxx>
+#include <QtFrame.hxx>
+#include <QtGraphics_Controls.hxx>
+#include <QtSvpGraphics.hxx>
+#include <QtSvpSurface.hxx>
+#include <QtTools.hxx>
+
+#include <QtGui/QScreen>
+#include <QtGui/QWindow>
+#include <QtWidgets/QWidget>
+
+QtSvpGraphics::QtSvpGraphics(QtFrame* pFrame)
+ : m_pFrame(pFrame)
+{
+ if (!QtData::noNativeControls())
+ m_pWidgetDraw.reset(new QtGraphics_Controls(*this));
+ if (m_pFrame)
+ setDevicePixelRatioF(m_pFrame->devicePixelRatioF());
+}
+
+QtSvpGraphics::~QtSvpGraphics() {}
+
+void QtSvpGraphics::updateQWidget() const
+{
+ if (!m_pFrame)
+ return;
+ QWidget* pQWidget = m_pFrame->GetQWidget();
+ if (pQWidget)
+ pQWidget->update(pQWidget->rect());
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+bool QtSvpGraphics::SupportsCairo() const { return true; }
+
+cairo::SurfaceSharedPtr
+QtSvpGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const
+{
+ return std::make_shared<cairo::QtSvpSurface>(rSurface);
+}
+
+cairo::SurfaceSharedPtr QtSvpGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int x,
+ int y, int width, int height) const
+{
+ return std::make_shared<cairo::QtSvpSurface>(this, x, y, width, height);
+}
+
+#endif
+
+static void QImage2BitmapBuffer(QImage& rImg, BitmapBuffer& rBuf)
+{
+ assert(rImg.width());
+ assert(rImg.height());
+
+ rBuf.mnWidth = rImg.width();
+ rBuf.mnHeight = rImg.height();
+ rBuf.mnBitCount = getFormatBits(rImg.format());
+ rBuf.mpBits = rImg.bits();
+ rBuf.mnScanlineSize = rImg.bytesPerLine();
+}
+
+void QtSvpGraphics::handleDamage(const tools::Rectangle& rDamagedRegion)
+{
+ assert(m_pWidgetDraw);
+ assert(dynamic_cast<QtGraphics_Controls*>(m_pWidgetDraw.get()));
+ assert(!rDamagedRegion.IsEmpty());
+
+ QImage* pImage = static_cast<QtGraphics_Controls*>(m_pWidgetDraw.get())->getImage();
+ assert(pImage);
+ if (pImage->width() == 0 || pImage->height() == 0)
+ return;
+
+ BitmapBuffer aBuffer;
+ QImage2BitmapBuffer(*pImage, aBuffer);
+ SalTwoRect aTR(0, 0, pImage->width(), pImage->height(), rDamagedRegion.Left(),
+ rDamagedRegion.Top(), rDamagedRegion.GetWidth(), rDamagedRegion.GetHeight());
+
+ getSvpBackend()->drawBitmapBuffer(aTR, &aBuffer, CAIRO_OPERATOR_OVER);
+}
+
+void QtSvpGraphics::GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY)
+{
+ char* pForceDpi;
+ if ((pForceDpi = getenv("SAL_FORCEDPI")))
+ {
+ OString sForceDPI(pForceDpi);
+ rDPIX = rDPIY = sForceDPI.toInt32();
+ return;
+ }
+
+ if (!m_pFrame)
+ return;
+
+ QScreen* pScreen = m_pFrame->GetQWidget()->screen();
+ rDPIX = pScreen->logicalDotsPerInchX() * pScreen->devicePixelRatio() + 0.5;
+ rDPIY = pScreen->logicalDotsPerInchY() * pScreen->devicePixelRatio() + 0.5;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtSvpSurface.cxx b/vcl/qt5/QtSvpSurface.cxx
new file mode 100644
index 0000000000..760419bd7f
--- /dev/null
+++ b/vcl/qt5/QtSvpSurface.cxx
@@ -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/.
+ */
+
+#include <utility>
+
+#include <QtSvpSurface.hxx>
+
+#include <QtSvpGraphics.hxx>
+
+#include <vcl/sysdata.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+#include <basegfx/vector/b2isize.hxx>
+
+namespace
+{
+Size get_surface_size(cairo_surface_t* surface)
+{
+ cairo_t* cr = cairo_create(surface);
+ double x1, x2, y1, y2;
+ cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
+ cairo_destroy(cr);
+ return Size(x2 - x1, y2 - y1);
+}
+}
+
+namespace cairo
+{
+QtSvpSurface::QtSvpSurface(CairoSurfaceSharedPtr pSurface)
+ : m_pGraphics(nullptr)
+ , m_pCairoContext(nullptr)
+ , m_pSurface(std::move(pSurface))
+{
+}
+
+QtSvpSurface::QtSvpSurface(const QtSvpGraphics* pGraphics, int x, int y, int width, int height)
+ : m_pGraphics(pGraphics)
+ , m_pCairoContext(pGraphics->getCairoContext())
+{
+ cairo_surface_t* surface = cairo_get_target(m_pCairoContext);
+ m_pSurface.reset(cairo_surface_create_for_rectangle(surface, x, y, width, height),
+ &cairo_surface_destroy);
+}
+
+QtSvpSurface::~QtSvpSurface()
+{
+ if (m_pCairoContext)
+ cairo_destroy(m_pCairoContext);
+}
+
+CairoSharedPtr QtSvpSurface::getCairo() const
+{
+ return CairoSharedPtr(cairo_create(m_pSurface.get()), &cairo_destroy);
+}
+
+SurfaceSharedPtr QtSvpSurface::getSimilar(int cairo_content_type, int width, int height) const
+{
+ return std::make_shared<QtSvpSurface>(CairoSurfaceSharedPtr(
+ cairo_surface_create_similar(
+ m_pSurface.get(), static_cast<cairo_content_t>(cairo_content_type), width, height),
+ &cairo_surface_destroy));
+}
+
+void QtSvpSurface::flush() const
+{
+ cairo_surface_flush(m_pSurface.get());
+ if (m_pGraphics)
+ m_pGraphics->updateQWidget();
+}
+
+VclPtr<VirtualDevice> QtSvpSurface::createVirtualDevice() const
+{
+ SystemGraphicsData aSystemGraphicsData;
+
+ aSystemGraphicsData.nSize = sizeof(SystemGraphicsData);
+ aSystemGraphicsData.pSurface = m_pSurface.get();
+
+ return VclPtr<VirtualDevice>::Create(aSystemGraphicsData, get_surface_size(m_pSurface.get()),
+ DeviceFormat::WITHOUT_ALPHA);
+}
+
+} // namespace cairo
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtSvpVirtualDevice.hxx b/vcl/qt5/QtSvpVirtualDevice.hxx
new file mode 100644
index 0000000000..26247a6e1d
--- /dev/null
+++ b/vcl/qt5/QtSvpVirtualDevice.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <headless/svpvd.hxx>
+#include <QtSvpGraphics.hxx>
+
+class VCL_DLLPUBLIC QtSvpVirtualDevice : public SvpSalVirtualDevice
+{
+public:
+ QtSvpVirtualDevice(cairo_surface_t* pRefSurface, cairo_surface_t* pPreExistingTarget)
+ : SvpSalVirtualDevice(pRefSurface, pPreExistingTarget)
+ {
+ }
+
+ SalGraphics* AcquireGraphics() override { return AddGraphics(new QtSvpGraphics(nullptr)); }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtSystem.cxx b/vcl/qt5/QtSystem.cxx
new file mode 100644
index 0000000000..d43e47832f
--- /dev/null
+++ b/vcl/qt5/QtSystem.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/.
+ */
+
+#include <QtGui/QGuiApplication>
+#include <QtGui/QScreen>
+
+#include <tools/gen.hxx>
+#include <QtSystem.hxx>
+#include <QtTools.hxx>
+
+unsigned int QtSystem::GetDisplayScreenCount() { return QGuiApplication::screens().size(); }
+
+AbsoluteScreenPixelRectangle QtSystem::GetDisplayScreenPosSizePixel(unsigned int nScreen)
+{
+ QRect qRect = QGuiApplication::screens().at(nScreen)->geometry();
+ return AbsoluteScreenPixelRectangle(toRectangle(scaledQRect(qRect, qApp->devicePixelRatio())));
+}
+
+int QtSystem::ShowNativeDialog(const OUString&, const OUString&, const std::vector<OUString>&)
+{
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtTimer.cxx b/vcl/qt5/QtTimer.cxx
new file mode 100644
index 0000000000..1a34213977
--- /dev/null
+++ b/vcl/qt5/QtTimer.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 <QtTimer.hxx>
+#include <QtTimer.moc>
+
+#include <QtInstance.hxx>
+
+#include <QtWidgets/QApplication>
+#include <QtCore/QThread>
+
+#include <vcl/svapp.hxx>
+#include <sal/log.hxx>
+
+#include <svdata.hxx>
+
+QtTimer::QtTimer()
+{
+ m_aTimer.setSingleShot(true);
+ m_aTimer.setTimerType(Qt::PreciseTimer);
+ connect(&m_aTimer, SIGNAL(timeout()), this, SLOT(timeoutActivated()));
+ connect(this, SIGNAL(startTimerSignal(int)), this, SLOT(startTimer(int)));
+ connect(this, SIGNAL(stopTimerSignal()), this, SLOT(stopTimer()));
+}
+
+void QtTimer::timeoutActivated()
+{
+ SolarMutexGuard aGuard;
+ if (Application::IsOnSystemEventLoop())
+ {
+ const ImplSVData* pSVData = ImplGetSVData();
+ assert(pSVData && pSVData->mpDefInst);
+ static_cast<QtInstance*>(pSVData->mpDefInst)->DispatchUserEvents(true);
+ }
+ CallCallback();
+}
+
+void QtTimer::startTimer(int nMS) { m_aTimer.start(nMS); }
+
+void QtTimer::Start(sal_uInt64 nMS) { Q_EMIT startTimerSignal(nMS); }
+
+void QtTimer::stopTimer() { m_aTimer.stop(); }
+
+void QtTimer::Stop() { Q_EMIT stopTimerSignal(); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtTools.cxx b/vcl/qt5/QtTools.cxx
new file mode 100644
index 0000000000..030b3af2b5
--- /dev/null
+++ b/vcl/qt5/QtTools.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 <QtTools.hxx>
+
+#include <cairo.h>
+
+#include <tools/stream.hxx>
+#include <vcl/event.hxx>
+#include <vcl/image.hxx>
+#include <vcl/filter/PngImageWriter.hxx>
+
+#include <QtGui/QImage>
+
+void CairoDeleter::operator()(cairo_surface_t* pSurface) const { cairo_surface_destroy(pSurface); }
+
+sal_uInt16 GetKeyModCode(Qt::KeyboardModifiers eKeyModifiers)
+{
+ sal_uInt16 nCode = 0;
+ if (eKeyModifiers & Qt::ShiftModifier)
+ nCode |= KEY_SHIFT;
+ if (eKeyModifiers & Qt::ControlModifier)
+ nCode |= KEY_MOD1;
+ if (eKeyModifiers & Qt::AltModifier)
+ nCode |= KEY_MOD2;
+ if (eKeyModifiers & Qt::MetaModifier)
+ nCode |= KEY_MOD3;
+ return nCode;
+}
+
+sal_uInt16 GetMouseModCode(Qt::MouseButtons eButtons)
+{
+ sal_uInt16 nCode = 0;
+ if (eButtons & Qt::LeftButton)
+ nCode |= MOUSE_LEFT;
+ if (eButtons & Qt::MiddleButton)
+ nCode |= MOUSE_MIDDLE;
+ if (eButtons & Qt::RightButton)
+ nCode |= MOUSE_RIGHT;
+ return nCode;
+}
+
+Qt::DropActions toQtDropActions(sal_Int8 dragOperation)
+{
+ Qt::DropActions eRet = Qt::IgnoreAction;
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
+ eRet |= Qt::CopyAction;
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
+ eRet |= Qt::MoveAction;
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
+ eRet |= Qt::LinkAction;
+ return eRet;
+}
+
+sal_Int8 toVclDropActions(Qt::DropActions dragOperation)
+{
+ sal_Int8 nRet(0);
+ if (dragOperation & Qt::CopyAction)
+ nRet |= css::datatransfer::dnd::DNDConstants::ACTION_COPY;
+ if (dragOperation & Qt::MoveAction)
+ nRet |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ if (dragOperation & Qt::LinkAction)
+ nRet |= css::datatransfer::dnd::DNDConstants::ACTION_LINK;
+ return nRet;
+}
+
+sal_Int8 toVclDropAction(Qt::DropAction dragOperation)
+{
+ sal_Int8 nRet(0);
+ if (dragOperation == Qt::CopyAction)
+ nRet = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
+ else if (dragOperation == Qt::MoveAction)
+ nRet = css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ else if (dragOperation == Qt::LinkAction)
+ nRet = css::datatransfer::dnd::DNDConstants::ACTION_LINK;
+ return nRet;
+}
+
+Qt::DropAction getPreferredDropAction(sal_Int8 dragOperation)
+{
+ Qt::DropAction eAct = Qt::IgnoreAction;
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
+ eAct = Qt::MoveAction;
+ else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
+ eAct = Qt::CopyAction;
+ else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
+ eAct = Qt::LinkAction;
+ return eAct;
+}
+
+QImage toQImage(const Image& rImage)
+{
+ QImage aImage;
+
+ if (!!rImage)
+ {
+ SvMemoryStream aMemStm;
+ auto rBitmapEx = rImage.GetBitmapEx();
+ vcl::PngImageWriter aWriter(aMemStm);
+ aWriter.write(rBitmapEx);
+ aImage.loadFromData(static_cast<const uchar*>(aMemStm.GetData()), aMemStm.TellEnd());
+ }
+
+ return aImage;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtTransferable.cxx b/vcl/qt5/QtTransferable.cxx
new file mode 100644
index 0000000000..d9e0beaa71
--- /dev/null
+++ b/vcl/qt5/QtTransferable.cxx
@@ -0,0 +1,361 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <QtTransferable.hxx>
+
+#include <comphelper/sequence.hxx>
+#include <sal/log.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <QtWidgets/QApplication>
+
+#include <QtInstance.hxx>
+#include <QtTools.hxx>
+
+#include <cassert>
+
+static bool lcl_textMimeInfo(std::u16string_view rMimeString, bool& bHaveNoCharset,
+ bool& bHaveUTF16, bool& bHaveUTF8)
+{
+ sal_Int32 nIndex = 0;
+ if (o3tl::getToken(rMimeString, 0, ';', nIndex) == u"text/plain")
+ {
+ std::u16string_view aToken(o3tl::getToken(rMimeString, 0, ';', nIndex));
+ if (aToken == u"charset=utf-16")
+ bHaveUTF16 = true;
+ else if (aToken == u"charset=utf-8")
+ bHaveUTF8 = true;
+ else if (aToken.empty())
+ bHaveNoCharset = true;
+ else // we just handle UTF-16 and UTF-8, everything else is "bytes"
+ return false;
+ return true;
+ }
+ return false;
+}
+
+QtTransferable::QtTransferable(const QMimeData* pMimeData)
+ : m_pMimeData(pMimeData)
+ , m_bProvideUTF16FromOtherEncoding(false)
+{
+ assert(pMimeData);
+}
+
+css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL QtTransferable::getTransferDataFlavors()
+{
+ // it's just filled once, ever, so just try to get it without locking first
+ if (m_aMimeTypeSeq.hasElements())
+ return m_aMimeTypeSeq;
+
+ // better safe then sorry; preventing broken usage
+ // DnD should not be shared and Clipboard access runs in the GUI thread
+ osl::MutexGuard aGuard(m_aMutex);
+ if (m_aMimeTypeSeq.hasElements())
+ return m_aMimeTypeSeq;
+
+ QStringList aFormatList(m_pMimeData->formats());
+ // we might add the UTF-16 mime text variant later
+ const int nMimeTypeSeqSize = aFormatList.size() + 1;
+ bool bHaveNoCharset = false, bHaveUTF16 = false, bHaveUTF8 = false;
+ css::uno::Sequence<css::datatransfer::DataFlavor> aMimeTypeSeq(nMimeTypeSeqSize);
+ auto pMimeTypeSeq = aMimeTypeSeq.getArray();
+
+ css::datatransfer::DataFlavor aFlavor;
+ int nMimeTypeCount = 0;
+
+ for (const QString& rMimeType : aFormatList)
+ {
+ // filter out non-MIME types such as TARGETS, MULTIPLE, TIMESTAMP
+ if (rMimeType.indexOf('/') == -1)
+ continue;
+
+ // gtk3 thinks it is not well defined - skip too
+ if (rMimeType == QStringLiteral("text/plain;charset=unicode"))
+ continue;
+
+ // LO doesn't like 'text/plain', so we have to provide UTF-16
+ bool bIsNoCharset = false, bIsUTF16 = false, bIsUTF8 = false;
+ if (lcl_textMimeInfo(toOUString(rMimeType), bIsNoCharset, bIsUTF16, bIsUTF8))
+ {
+ bHaveNoCharset |= bIsNoCharset;
+ bHaveUTF16 |= bIsUTF16;
+ bHaveUTF8 |= bIsUTF8;
+ if (bIsUTF16)
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ else
+ aFlavor.DataType = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get();
+ }
+ else
+ aFlavor.DataType = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get();
+
+ aFlavor.MimeType = toOUString(rMimeType);
+ assert(nMimeTypeCount < nMimeTypeSeqSize);
+ pMimeTypeSeq[nMimeTypeCount] = aFlavor;
+ nMimeTypeCount++;
+ }
+
+ m_bProvideUTF16FromOtherEncoding = (bHaveNoCharset || bHaveUTF8) && !bHaveUTF16;
+ if (m_bProvideUTF16FromOtherEncoding)
+ {
+ aFlavor.MimeType = "text/plain;charset=utf-16";
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ assert(nMimeTypeCount < nMimeTypeSeqSize);
+ pMimeTypeSeq[nMimeTypeCount] = aFlavor;
+ nMimeTypeCount++;
+ }
+
+ aMimeTypeSeq.realloc(nMimeTypeCount);
+
+ m_aMimeTypeSeq = aMimeTypeSeq;
+ return m_aMimeTypeSeq;
+}
+
+sal_Bool SAL_CALL
+QtTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
+{
+ const auto aSeq = getTransferDataFlavors();
+ return std::any_of(aSeq.begin(), aSeq.end(), [&](const css::datatransfer::DataFlavor& aFlavor) {
+ return rFlavor.MimeType == aFlavor.MimeType;
+ });
+}
+
+css::uno::Any SAL_CALL QtTransferable::getTransferData(const css::datatransfer::DataFlavor& rFlavor)
+{
+ css::uno::Any aAny;
+ if (!isDataFlavorSupported(rFlavor))
+ return aAny;
+
+ if (rFlavor.MimeType == "text/plain;charset=utf-16")
+ {
+ OUString aString;
+ if (m_bProvideUTF16FromOtherEncoding)
+ {
+ if (m_pMimeData->hasFormat("text/plain;charset=utf-8"))
+ {
+ QByteArray aByteData(m_pMimeData->data(QStringLiteral("text/plain;charset=utf-8")));
+ aString = OUString::fromUtf8(reinterpret_cast<const char*>(aByteData.data()));
+ }
+ else
+ {
+ QByteArray aByteData(m_pMimeData->data(QStringLiteral("text/plain")));
+ aString = OUString(reinterpret_cast<const char*>(aByteData.data()),
+ aByteData.size(), osl_getThreadTextEncoding());
+ }
+ }
+ else
+ {
+ QByteArray aByteData(m_pMimeData->data(toQString(rFlavor.MimeType)));
+ aString = OUString(reinterpret_cast<const sal_Unicode*>(aByteData.data()),
+ aByteData.size() / 2);
+ }
+ aAny <<= aString;
+ }
+ else
+ {
+ QByteArray aByteData(m_pMimeData->data(toQString(rFlavor.MimeType)));
+ css::uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(aByteData.data()),
+ aByteData.size());
+ aAny <<= aSeq;
+ }
+
+ return aAny;
+}
+
+QtClipboardTransferable::QtClipboardTransferable(const QClipboard::Mode aMode,
+ const QMimeData* pMimeData)
+ : QtTransferable(pMimeData)
+ , m_aMode(aMode)
+{
+}
+
+bool QtClipboardTransferable::hasInFlightChanged() const
+{
+ const bool bChanged(mimeData() != QApplication::clipboard()->mimeData(m_aMode));
+ SAL_WARN_IF(bChanged, "vcl.qt", "In flight clipboard change detected - broken clipboard read!");
+ return bChanged;
+}
+
+css::uno::Any SAL_CALL
+QtClipboardTransferable::getTransferData(const css::datatransfer::DataFlavor& rFlavor)
+{
+ css::uno::Any aAny;
+ auto* pSalInst(GetQtInstance());
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([&, this]() {
+ if (!hasInFlightChanged())
+ aAny = QtTransferable::getTransferData(rFlavor);
+ });
+ return aAny;
+}
+
+css::uno::Sequence<css::datatransfer::DataFlavor>
+ SAL_CALL QtClipboardTransferable::getTransferDataFlavors()
+{
+ css::uno::Sequence<css::datatransfer::DataFlavor> aSeq;
+ auto* pSalInst(GetQtInstance());
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([&, this]() {
+ if (!hasInFlightChanged())
+ aSeq = QtTransferable::getTransferDataFlavors();
+ });
+ return aSeq;
+}
+
+sal_Bool SAL_CALL
+QtClipboardTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
+{
+ bool bIsSupported = false;
+ auto* pSalInst(GetQtInstance());
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([&, this]() {
+ if (!hasInFlightChanged())
+ bIsSupported = QtTransferable::isDataFlavorSupported(rFlavor);
+ });
+ return bIsSupported;
+}
+
+QtMimeData::QtMimeData(const css::uno::Reference<css::datatransfer::XTransferable>& xTrans)
+ : m_aContents(xTrans)
+ , m_bHaveNoCharset(false)
+ , m_bHaveUTF8(false)
+{
+ assert(xTrans.is());
+}
+
+bool QtMimeData::deepCopy(QMimeData** const pMimeCopy) const
+{
+ if (!pMimeCopy)
+ return false;
+
+ QMimeData* pMimeData = new QMimeData();
+ for (QString& format : formats())
+ {
+ QByteArray aData = data(format);
+ // Checking for custom MIME types
+ if (format.startsWith("application/x-qt"))
+ {
+ // Retrieving true format name
+ int indexBegin = format.indexOf('"') + 1;
+ int indexEnd = format.indexOf('"', indexBegin);
+ format = format.mid(indexBegin, indexEnd - indexBegin);
+ }
+ pMimeData->setData(format, aData);
+ }
+
+ *pMimeCopy = pMimeData;
+ return true;
+}
+
+QStringList QtMimeData::formats() const
+{
+ if (!m_aMimeTypeList.isEmpty())
+ return m_aMimeTypeList;
+
+ const css::uno::Sequence<css::datatransfer::DataFlavor> aFormats
+ = m_aContents->getTransferDataFlavors();
+ QStringList aList;
+ bool bHaveUTF16 = false;
+
+ for (const auto& rFlavor : aFormats)
+ {
+ aList << toQString(rFlavor.MimeType);
+ lcl_textMimeInfo(rFlavor.MimeType, m_bHaveNoCharset, bHaveUTF16, m_bHaveUTF8);
+ }
+
+ // we provide a locale encoded and a UTF-8 variant, if missing
+ if (m_bHaveNoCharset || bHaveUTF16 || m_bHaveUTF8)
+ {
+ // if there is a text representation from LO point of view, it'll be UTF-16
+ assert(bHaveUTF16);
+ if (!m_bHaveUTF8)
+ aList << QStringLiteral("text/plain;charset=utf-8");
+ if (!m_bHaveNoCharset)
+ aList << QStringLiteral("text/plain");
+ }
+
+ m_aMimeTypeList = aList;
+ return m_aMimeTypeList;
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+QVariant QtMimeData::retrieveData(const QString& mimeType, QVariant::Type) const
+#else
+QVariant QtMimeData::retrieveData(const QString& mimeType, QMetaType) const
+#endif
+{
+ if (!hasFormat(mimeType))
+ return QVariant();
+
+ css::datatransfer::DataFlavor aFlavor;
+ aFlavor.MimeType = toOUString(mimeType);
+ aFlavor.DataType = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get();
+
+ bool bWantNoCharset = false, bWantUTF16 = false, bWantUTF8 = false;
+ if (lcl_textMimeInfo(aFlavor.MimeType, bWantNoCharset, bWantUTF16, bWantUTF8))
+ {
+ if ((bWantNoCharset && !m_bHaveNoCharset) || (bWantUTF8 && !m_bHaveUTF8))
+ {
+ aFlavor.MimeType = "text/plain;charset=utf-16";
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ }
+ else if (bWantUTF16)
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ }
+
+ css::uno::Any aValue;
+
+ try
+ {
+ // tdf#129809 take a reference in case m_aContents is replaced during this call
+ css::uno::Reference<com::sun::star::datatransfer::XTransferable> xCurrentContents(
+ m_aContents);
+ aValue = xCurrentContents->getTransferData(aFlavor);
+ }
+ catch (...)
+ {
+ }
+
+ QByteArray aByteArray;
+ if (aValue.getValueTypeClass() == css::uno::TypeClass_STRING)
+ {
+ OUString aString;
+ aValue >>= aString;
+
+ if (bWantUTF8)
+ {
+ OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
+ aByteArray = QByteArray(aUTF8String.getStr(), aUTF8String.getLength());
+ }
+ else if (bWantNoCharset)
+ {
+ OString aLocaleString(OUStringToOString(aString, osl_getThreadTextEncoding()));
+ aByteArray = QByteArray(aLocaleString.getStr(), aLocaleString.getLength());
+ }
+ else if (bWantUTF16)
+ {
+ aByteArray = QByteArray(reinterpret_cast<const char*>(aString.getStr()),
+ aString.getLength() * 2);
+ }
+ else
+ return QVariant(toQString(aString));
+ }
+ else
+ {
+ css::uno::Sequence<sal_Int8> aData;
+ aValue >>= aData;
+ aByteArray
+ = QByteArray(reinterpret_cast<const char*>(aData.getConstArray()), aData.getLength());
+ }
+ return QVariant::fromValue(aByteArray);
+}
+
+bool QtMimeData::hasFormat(const QString& mimeType) const { return formats().contains(mimeType); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtVirtualDevice.cxx b/vcl/qt5/QtVirtualDevice.cxx
new file mode 100644
index 0000000000..22844f1df6
--- /dev/null
+++ b/vcl/qt5/QtVirtualDevice.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 <QtVirtualDevice.hxx>
+
+#include <QtGraphics.hxx>
+#include <QtTools.hxx>
+
+#include <QtGui/QImage>
+
+QtVirtualDevice::QtVirtualDevice(double fScale)
+ : m_fScale(fScale)
+{
+}
+
+SalGraphics* QtVirtualDevice::AcquireGraphics()
+{
+ assert(m_pImage);
+ QtGraphics* pGraphics = new QtGraphics(m_pImage.get());
+ m_aGraphics.push_back(pGraphics);
+ return pGraphics;
+}
+
+void QtVirtualDevice::ReleaseGraphics(SalGraphics* pGraphics)
+{
+ std::erase(m_aGraphics, dynamic_cast<QtGraphics*>(pGraphics));
+ delete pGraphics;
+}
+
+bool QtVirtualDevice::SetSize(tools::Long nNewDX, tools::Long nNewDY)
+{
+ return SetSizeUsingBuffer(nNewDX, nNewDY, nullptr);
+}
+
+bool QtVirtualDevice::SetSizeUsingBuffer(tools::Long nNewDX, tools::Long nNewDY, sal_uInt8* pBuffer)
+{
+ if (nNewDX == 0)
+ nNewDX = 1;
+ if (nNewDY == 0)
+ nNewDY = 1;
+
+ if (m_pImage && m_aFrameSize.width() == nNewDX && m_aFrameSize.height() == nNewDY)
+ return true;
+
+ m_aFrameSize = QSize(nNewDX, nNewDY);
+
+ nNewDX *= m_fScale;
+ nNewDY *= m_fScale;
+
+ if (pBuffer)
+ m_pImage.reset(new QImage(pBuffer, nNewDX, nNewDY, Qt_DefaultFormat32));
+ else
+ m_pImage.reset(new QImage(nNewDX, nNewDY, Qt_DefaultFormat32));
+
+ m_pImage->fill(Qt::transparent);
+ m_pImage->setDevicePixelRatio(m_fScale);
+
+ // update device in existing graphics
+ for (auto pQtGraph : m_aGraphics)
+ pQtGraph->ChangeQImage(m_pImage.get());
+
+ return true;
+}
+
+tools::Long QtVirtualDevice::GetWidth() const { return m_pImage ? m_aFrameSize.width() : 0; }
+
+tools::Long QtVirtualDevice::GetHeight() const { return m_pImage ? m_aFrameSize.height() : 0; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtWidget.cxx b/vcl/qt5/QtWidget.cxx
new file mode 100644
index 0000000000..a7c4f32e92
--- /dev/null
+++ b/vcl/qt5/QtWidget.cxx
@@ -0,0 +1,1024 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <QtWidget.hxx>
+#include <QtWidget.moc>
+
+#include <QtFrame.hxx>
+#include <QtGraphics.hxx>
+#include <QtInstance.hxx>
+#include <QtMainWindow.hxx>
+#include <QtSvpGraphics.hxx>
+#include <QtTransferable.hxx>
+#include <QtTools.hxx>
+
+#include <QtCore/QMimeData>
+#include <QtGui/QDrag>
+#include <QtGui/QFocusEvent>
+#include <QtGui/QGuiApplication>
+#include <QtGui/QImage>
+#include <QtGui/QKeyEvent>
+#include <QtGui/QMouseEvent>
+#include <QtGui/QPainter>
+#include <QtGui/QPaintEvent>
+#include <QtGui/QResizeEvent>
+#include <QtGui/QShowEvent>
+#include <QtGui/QTextCharFormat>
+#include <QtGui/QWheelEvent>
+#include <QtWidgets/QMainWindow>
+#include <QtWidgets/QToolTip>
+#include <QtWidgets/QWidget>
+
+#include <cairo.h>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <window.h>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+
+#if CHECK_ANY_QT_USING_X11
+#define XK_MISCELLANY
+#include <X11/keysymdef.h>
+#endif
+
+using namespace com::sun::star;
+
+void QtWidget::paintEvent(QPaintEvent* pEvent)
+{
+ QPainter p(this);
+ if (!m_rFrame.m_bNullRegion)
+ p.setClipRegion(m_rFrame.m_aRegion);
+
+ QImage aImage;
+ if (m_rFrame.m_bUseCairo)
+ {
+ cairo_surface_t* pSurface = m_rFrame.m_pSurface.get();
+ cairo_surface_flush(pSurface);
+
+ aImage = QImage(cairo_image_surface_get_data(pSurface),
+ cairo_image_surface_get_width(pSurface),
+ cairo_image_surface_get_height(pSurface), Qt_DefaultFormat32);
+ }
+ else
+ aImage = *m_rFrame.m_pQImage;
+
+ const qreal fRatio = m_rFrame.devicePixelRatioF();
+ aImage.setDevicePixelRatio(fRatio);
+ QRectF source(pEvent->rect().topLeft() * fRatio, pEvent->rect().size() * fRatio);
+ p.drawImage(pEvent->rect(), aImage, source);
+}
+
+void QtWidget::resizeEvent(QResizeEvent* pEvent)
+{
+ const qreal fRatio = m_rFrame.devicePixelRatioF();
+ const int nWidth = ceil(pEvent->size().width() * fRatio);
+ const int nHeight = ceil(pEvent->size().height() * fRatio);
+
+ m_rFrame.maGeometry.setSize({ nWidth, nHeight });
+
+ if (m_rFrame.m_bUseCairo)
+ {
+ if (m_rFrame.m_pSurface)
+ {
+ const int nOldWidth = cairo_image_surface_get_width(m_rFrame.m_pSurface.get());
+ const int nOldHeight = cairo_image_surface_get_height(m_rFrame.m_pSurface.get());
+ if (nOldWidth != nWidth || nOldHeight != nHeight)
+ {
+ cairo_surface_t* pSurface
+ = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nWidth, nHeight);
+ cairo_surface_set_user_data(pSurface, SvpSalGraphics::getDamageKey(),
+ &m_rFrame.m_aDamageHandler, nullptr);
+ m_rFrame.m_pSvpGraphics->setSurface(pSurface, basegfx::B2IVector(nWidth, nHeight));
+ UniqueCairoSurface old_surface(m_rFrame.m_pSurface.release());
+ m_rFrame.m_pSurface.reset(pSurface);
+
+ const int nMinWidth = qMin(nOldWidth, nWidth);
+ const int nMinHeight = qMin(nOldHeight, nHeight);
+ SalTwoRect rect(0, 0, nMinWidth, nMinHeight, 0, 0, nMinWidth, nMinHeight);
+ m_rFrame.m_pSvpGraphics->copySource(rect, old_surface.get());
+ }
+ }
+ }
+ else
+ {
+ if (m_rFrame.m_pQImage && m_rFrame.m_pQImage->size() != QSize(nWidth, nHeight))
+ {
+ QImage* pImage = new QImage(m_rFrame.m_pQImage->copy(0, 0, nWidth, nHeight));
+ m_rFrame.m_pQtGraphics->ChangeQImage(pImage);
+ m_rFrame.m_pQImage.reset(pImage);
+ }
+ }
+
+ m_rFrame.CallCallback(SalEvent::Resize, nullptr);
+}
+
+void QtWidget::fakeResize()
+{
+ QResizeEvent aEvent(size(), QSize());
+ resizeEvent(&aEvent);
+}
+
+void QtWidget::fillSalAbstractMouseEvent(const QtFrame& rFrame, const QInputEvent* pQEvent,
+ const QPoint& rPos, Qt::MouseButtons eButtons, int nWidth,
+ SalAbstractMouseEvent& aSalEvent)
+{
+ const qreal fRatio = rFrame.devicePixelRatioF();
+ const Point aPos = toPoint(rPos * fRatio);
+
+ aSalEvent.mnX = QGuiApplication::isLeftToRight() ? aPos.X() : round(nWidth * fRatio) - aPos.X();
+ aSalEvent.mnY = aPos.Y();
+ aSalEvent.mnTime = pQEvent->timestamp();
+ aSalEvent.mnCode = GetKeyModCode(pQEvent->modifiers()) | GetMouseModCode(eButtons);
+}
+
+#define FILL_SAME(rFrame, nWidth) \
+ fillSalAbstractMouseEvent(rFrame, pEvent, pEvent->pos(), pEvent->buttons(), nWidth, aEvent)
+
+void QtWidget::handleMouseButtonEvent(const QtFrame& rFrame, const QMouseEvent* pEvent)
+{
+ SalMouseEvent aEvent;
+ FILL_SAME(rFrame, rFrame.GetQWidget()->width());
+
+ switch (pEvent->button())
+ {
+ case Qt::LeftButton:
+ aEvent.mnButton = MOUSE_LEFT;
+ break;
+ case Qt::MiddleButton:
+ aEvent.mnButton = MOUSE_MIDDLE;
+ break;
+ case Qt::RightButton:
+ aEvent.mnButton = MOUSE_RIGHT;
+ break;
+ default:
+ return;
+ }
+
+ SalEvent nEventType;
+ if (pEvent->type() == QEvent::MouseButtonPress || pEvent->type() == QEvent::MouseButtonDblClick)
+ nEventType = SalEvent::MouseButtonDown;
+ else
+ nEventType = SalEvent::MouseButtonUp;
+ rFrame.CallCallback(nEventType, &aEvent);
+}
+
+void QtWidget::mousePressEvent(QMouseEvent* pEvent)
+{
+ handleMouseButtonEvent(m_rFrame, pEvent);
+ if (m_rFrame.isPopup()
+ && !geometry().translated(geometry().topLeft() * -1).contains(pEvent->pos()))
+ closePopup();
+}
+
+void QtWidget::mouseReleaseEvent(QMouseEvent* pEvent) { handleMouseButtonEvent(m_rFrame, pEvent); }
+
+void QtWidget::mouseMoveEvent(QMouseEvent* pEvent)
+{
+ SalMouseEvent aEvent;
+ FILL_SAME(m_rFrame, width());
+
+ aEvent.mnButton = 0;
+
+ m_rFrame.CallCallback(SalEvent::MouseMove, &aEvent);
+ pEvent->accept();
+}
+
+void QtWidget::handleMouseEnterLeaveEvents(const QtFrame& rFrame, QEvent* pQEvent)
+{
+ const qreal fRatio = rFrame.devicePixelRatioF();
+ const QWidget* pWidget = rFrame.GetQWidget();
+ const Point aPos = toPoint(pWidget->mapFromGlobal(QCursor::pos()) * fRatio);
+
+ SalMouseEvent aEvent;
+ aEvent.mnX
+ = QGuiApplication::isLeftToRight() ? aPos.X() : round(pWidget->width() * fRatio) - aPos.X();
+ aEvent.mnY = aPos.Y();
+ aEvent.mnTime = 0;
+ aEvent.mnButton = 0;
+ aEvent.mnCode = GetKeyModCode(QGuiApplication::keyboardModifiers())
+ | GetMouseModCode(QGuiApplication::mouseButtons());
+
+ SalEvent nEventType;
+ if (pQEvent->type() == QEvent::Enter)
+ nEventType = SalEvent::MouseMove;
+ else
+ nEventType = SalEvent::MouseLeave;
+ rFrame.CallCallback(nEventType, &aEvent);
+ pQEvent->accept();
+}
+
+void QtWidget::leaveEvent(QEvent* pEvent) { handleMouseEnterLeaveEvents(m_rFrame, pEvent); }
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+void QtWidget::enterEvent(QEnterEvent* pEvent)
+#else
+void QtWidget::enterEvent(QEvent* pEvent)
+#endif
+{
+ handleMouseEnterLeaveEvents(m_rFrame, pEvent);
+}
+
+void QtWidget::wheelEvent(QWheelEvent* pEvent)
+{
+ SalWheelMouseEvent aEvent;
+ fillSalAbstractMouseEvent(m_rFrame, pEvent, pEvent->position().toPoint(), pEvent->buttons(),
+ width(), aEvent);
+
+ // mouse wheel ticks are 120, which we map to 3 lines.
+ // we have to accumulate for touch scroll to keep track of the absolute delta.
+
+ int nDelta = pEvent->angleDelta().y(), lines;
+ aEvent.mbHorz = nDelta == 0;
+ if (aEvent.mbHorz)
+ {
+ nDelta = (QGuiApplication::isLeftToRight() ? 1 : -1) * pEvent->angleDelta().x();
+ if (!nDelta)
+ return;
+
+ m_nDeltaX += nDelta;
+ lines = m_nDeltaX / 40;
+ m_nDeltaX = m_nDeltaX % 40;
+ }
+ else
+ {
+ m_nDeltaY += nDelta;
+ lines = m_nDeltaY / 40;
+ m_nDeltaY = m_nDeltaY % 40;
+ }
+
+ aEvent.mnDelta = nDelta;
+ aEvent.mnNotchDelta = nDelta < 0 ? -1 : 1;
+ aEvent.mnScrollLines = std::abs(lines);
+
+ m_rFrame.CallCallback(SalEvent::WheelMouse, &aEvent);
+ pEvent->accept();
+}
+
+void QtWidget::dragEnterEvent(QDragEnterEvent* event)
+{
+ if (dynamic_cast<const QtMimeData*>(event->mimeData()))
+ event->accept();
+ else
+ event->acceptProposedAction();
+}
+
+// also called when a drop is rejected
+void QtWidget::dragLeaveEvent(QDragLeaveEvent*) { m_rFrame.handleDragLeave(); }
+
+void QtWidget::dragMoveEvent(QDragMoveEvent* pEvent) { m_rFrame.handleDragMove(pEvent); }
+
+void QtWidget::dropEvent(QDropEvent* pEvent) { m_rFrame.handleDrop(pEvent); }
+
+void QtWidget::moveEvent(QMoveEvent* pEvent)
+{
+ // already handled by QtMainWindow::moveEvent
+ if (m_rFrame.m_pTopLevel)
+ return;
+
+ m_rFrame.maGeometry.setPos(toPoint(pEvent->pos() * m_rFrame.devicePixelRatioF()));
+ m_rFrame.CallCallback(SalEvent::Move, nullptr);
+}
+
+void QtWidget::showEvent(QShowEvent*)
+{
+ QSize aSize(m_rFrame.GetQWidget()->size() * m_rFrame.devicePixelRatioF());
+ // forcing an immediate update somehow interferes with the hide + show
+ // sequence from QtFrame::SetModal, if the frame was already set visible,
+ // resulting in a hidden / unmapped window
+ SalPaintEvent aPaintEvt(0, 0, aSize.width(), aSize.height());
+ if (m_rFrame.isPopup())
+ GetQtInstance()->setActivePopup(&m_rFrame);
+ m_rFrame.CallCallback(SalEvent::Paint, &aPaintEvt);
+}
+
+void QtWidget::hideEvent(QHideEvent*)
+{
+ if (m_rFrame.isPopup() && GetQtInstance()->activePopup() == &m_rFrame)
+ GetQtInstance()->setActivePopup(nullptr);
+}
+
+void QtWidget::closeEvent(QCloseEvent* /*pEvent*/)
+{
+ m_rFrame.CallCallback(SalEvent::Close, nullptr);
+}
+
+static sal_uInt16 GetKeyCode(int keyval, Qt::KeyboardModifiers modifiers)
+{
+ sal_uInt16 nCode = 0;
+ if (keyval >= Qt::Key_0 && keyval <= Qt::Key_9)
+ nCode = KEY_0 + (keyval - Qt::Key_0);
+ else if (keyval >= Qt::Key_A && keyval <= Qt::Key_Z)
+ nCode = KEY_A + (keyval - Qt::Key_A);
+ else if (keyval >= Qt::Key_F1 && keyval <= Qt::Key_F26)
+ nCode = KEY_F1 + (keyval - Qt::Key_F1);
+ else if (modifiers.testFlag(Qt::KeypadModifier)
+ && (keyval == Qt::Key_Period || keyval == Qt::Key_Comma))
+ // Qt doesn't use a special keyval for decimal separator ("," or ".")
+ // on numerical keypad, but sets Qt::KeypadModifier in addition
+ nCode = KEY_DECIMAL;
+ else
+ {
+ switch (keyval)
+ {
+ case Qt::Key_Down:
+ nCode = KEY_DOWN;
+ break;
+ case Qt::Key_Up:
+ nCode = KEY_UP;
+ break;
+ case Qt::Key_Left:
+ nCode = KEY_LEFT;
+ break;
+ case Qt::Key_Right:
+ nCode = KEY_RIGHT;
+ break;
+ case Qt::Key_Home:
+ nCode = KEY_HOME;
+ break;
+ case Qt::Key_End:
+ nCode = KEY_END;
+ break;
+ case Qt::Key_PageUp:
+ nCode = KEY_PAGEUP;
+ break;
+ case Qt::Key_PageDown:
+ nCode = KEY_PAGEDOWN;
+ break;
+ case Qt::Key_Return:
+ case Qt::Key_Enter:
+ nCode = KEY_RETURN;
+ break;
+ case Qt::Key_Escape:
+ nCode = KEY_ESCAPE;
+ break;
+ case Qt::Key_Tab:
+ // oddly enough, Qt doesn't send Shift-Tab event as 'Tab key pressed with Shift
+ // modifier' but as 'Backtab key pressed' (while its modifier bits are still
+ // set to Shift) -- so let's map both Key_Tab and Key_Backtab to VCL's KEY_TAB
+ case Qt::Key_Backtab:
+ nCode = KEY_TAB;
+ break;
+ case Qt::Key_Backspace:
+ nCode = KEY_BACKSPACE;
+ break;
+ case Qt::Key_Space:
+ nCode = KEY_SPACE;
+ break;
+ case Qt::Key_Insert:
+ nCode = KEY_INSERT;
+ break;
+ case Qt::Key_Delete:
+ nCode = KEY_DELETE;
+ break;
+ case Qt::Key_Plus:
+ nCode = KEY_ADD;
+ break;
+ case Qt::Key_Minus:
+ nCode = KEY_SUBTRACT;
+ break;
+ case Qt::Key_Asterisk:
+ nCode = KEY_MULTIPLY;
+ break;
+ case Qt::Key_Slash:
+ nCode = KEY_DIVIDE;
+ break;
+ case Qt::Key_Period:
+ nCode = KEY_POINT;
+ break;
+ case Qt::Key_Comma:
+ nCode = KEY_COMMA;
+ break;
+ case Qt::Key_Less:
+ nCode = KEY_LESS;
+ break;
+ case Qt::Key_Greater:
+ nCode = KEY_GREATER;
+ break;
+ case Qt::Key_Equal:
+ nCode = KEY_EQUAL;
+ break;
+ case Qt::Key_Find:
+ nCode = KEY_FIND;
+ break;
+ case Qt::Key_Menu:
+ nCode = KEY_CONTEXTMENU;
+ break;
+ case Qt::Key_Help:
+ nCode = KEY_HELP;
+ break;
+ case Qt::Key_Undo:
+ nCode = KEY_UNDO;
+ break;
+ case Qt::Key_Redo:
+ nCode = KEY_REPEAT;
+ break;
+ case Qt::Key_Cancel:
+ nCode = KEY_F11;
+ break;
+ case Qt::Key_AsciiTilde:
+ nCode = KEY_TILDE;
+ break;
+ case Qt::Key_QuoteLeft:
+ nCode = KEY_QUOTELEFT;
+ break;
+ case Qt::Key_BracketLeft:
+ nCode = KEY_BRACKETLEFT;
+ break;
+ case Qt::Key_BracketRight:
+ nCode = KEY_BRACKETRIGHT;
+ break;
+ case Qt::Key_NumberSign:
+ nCode = KEY_NUMBERSIGN;
+ break;
+ case Qt::Key_Forward:
+ nCode = KEY_XF86FORWARD;
+ break;
+ case Qt::Key_Back:
+ nCode = KEY_XF86BACK;
+ break;
+ case Qt::Key_Colon:
+ nCode = KEY_COLON;
+ break;
+ case Qt::Key_Semicolon:
+ nCode = KEY_SEMICOLON;
+ break;
+ case Qt::Key_Copy:
+ nCode = KEY_COPY;
+ break;
+ case Qt::Key_Cut:
+ nCode = KEY_CUT;
+ break;
+ case Qt::Key_Open:
+ nCode = KEY_OPEN;
+ break;
+ case Qt::Key_Paste:
+ nCode = KEY_PASTE;
+ break;
+ }
+ }
+
+ return nCode;
+}
+
+void QtWidget::commitText(QtFrame& rFrame, const QString& aText)
+{
+ SalExtTextInputEvent aInputEvent;
+ aInputEvent.mpTextAttr = nullptr;
+ aInputEvent.mnCursorFlags = 0;
+ aInputEvent.maText = toOUString(aText);
+ aInputEvent.mnCursorPos = aInputEvent.maText.getLength();
+
+ SolarMutexGuard aGuard;
+ vcl::DeletionListener aDel(&rFrame);
+ rFrame.CallCallback(SalEvent::ExtTextInput, &aInputEvent);
+ if (!aDel.isDeleted())
+ rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
+}
+
+void QtWidget::deleteReplacementText(QtFrame& rFrame, int nReplacementStart, int nReplacementLength)
+{
+ // get the surrounding text
+ SolarMutexGuard aGuard;
+ SalSurroundingTextRequestEvent aSurroundingTextEvt;
+ aSurroundingTextEvt.maText.clear();
+ aSurroundingTextEvt.mnStart = aSurroundingTextEvt.mnEnd = 0;
+ rFrame.CallCallback(SalEvent::SurroundingTextRequest, &aSurroundingTextEvt);
+
+ // Turn nReplacementStart, nReplacementLength into a UTF-16 selection
+ const Selection aSelection = SalFrame::CalcDeleteSurroundingSelection(
+ aSurroundingTextEvt.maText, aSurroundingTextEvt.mnStart, nReplacementStart,
+ nReplacementLength);
+
+ const Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32);
+ if (aSelection == aInvalid)
+ {
+ SAL_WARN("vcl.qt", "Invalid selection when deleting IM replacement text");
+ return;
+ }
+
+ SalSurroundingTextSelectionChangeEvent aEvt;
+ aEvt.mnStart = aSelection.Min();
+ aEvt.mnEnd = aSelection.Max();
+ rFrame.CallCallback(SalEvent::DeleteSurroundingTextRequest, &aEvt);
+}
+
+bool QtWidget::handleGestureEvent(QtFrame& rFrame, QGestureEvent* pGestureEvent)
+{
+ if (QGesture* pGesture = pGestureEvent->gesture(Qt::PinchGesture))
+ {
+ if (!pGesture->hasHotSpot())
+ {
+ pGestureEvent->ignore();
+ return false;
+ }
+
+ GestureEventZoomType eType = GestureEventZoomType::Begin;
+ switch (pGesture->state())
+ {
+ case Qt::GestureStarted:
+ eType = GestureEventZoomType::Begin;
+ break;
+ case Qt::GestureUpdated:
+ eType = GestureEventZoomType::Update;
+ break;
+ case Qt::GestureFinished:
+ eType = GestureEventZoomType::End;
+ break;
+ case Qt::NoGesture:
+ case Qt::GestureCanceled:
+ default:
+ SAL_WARN("vcl.qt", "Unhandled pinch gesture state: " << pGesture->state());
+ pGestureEvent->ignore();
+ return false;
+ }
+
+ QPinchGesture* pPinchGesture = static_cast<QPinchGesture*>(pGesture);
+ const QPointF aHotspot = pGesture->hotSpot();
+ SalGestureZoomEvent aEvent;
+ aEvent.meEventType = eType;
+ aEvent.mnX = aHotspot.x();
+ aEvent.mnY = aHotspot.y();
+ aEvent.mfScaleDelta = 1 + pPinchGesture->totalScaleFactor();
+ rFrame.CallCallback(SalEvent::GestureZoom, &aEvent);
+ pGestureEvent->accept();
+ return true;
+ }
+
+ pGestureEvent->ignore();
+ return false;
+}
+
+bool QtWidget::handleKeyEvent(QtFrame& rFrame, const QWidget& rWidget, QKeyEvent* pEvent)
+{
+ const bool bIsKeyPressed
+ = pEvent->type() == QEvent::KeyPress || pEvent->type() == QEvent::ShortcutOverride;
+ sal_uInt16 nCode = GetKeyCode(pEvent->key(), pEvent->modifiers());
+ if (bIsKeyPressed && nCode == 0 && pEvent->text().length() > 1
+ && rWidget.testAttribute(Qt::WA_InputMethodEnabled))
+ {
+ commitText(rFrame, pEvent->text());
+ pEvent->accept();
+ return true;
+ }
+
+ if (nCode == 0 && pEvent->text().isEmpty())
+ {
+ sal_uInt16 nModCode = GetKeyModCode(pEvent->modifiers());
+ SalKeyModEvent aModEvt;
+ aModEvt.mbDown = bIsKeyPressed;
+ aModEvt.mnModKeyCode = ModKeyFlags::NONE;
+
+#if CHECK_ANY_QT_USING_X11
+ if (QGuiApplication::platformName() == "xcb")
+ {
+ // pressing just the ctrl key leads to a keysym of XK_Control but
+ // the event state does not contain ControlMask. In the release
+ // event it's the other way round: it does contain the Control mask.
+ // The modifier mode therefore has to be adapted manually.
+ ModKeyFlags nExtModMask = ModKeyFlags::NONE;
+ sal_uInt16 nModMask = 0;
+ switch (pEvent->nativeVirtualKey())
+ {
+ case XK_Control_L:
+ nExtModMask = ModKeyFlags::LeftMod1;
+ nModMask = KEY_MOD1;
+ break;
+ case XK_Control_R:
+ nExtModMask = ModKeyFlags::RightMod1;
+ nModMask = KEY_MOD1;
+ break;
+ case XK_Alt_L:
+ nExtModMask = ModKeyFlags::LeftMod2;
+ nModMask = KEY_MOD2;
+ break;
+ case XK_Alt_R:
+ nExtModMask = ModKeyFlags::RightMod2;
+ nModMask = KEY_MOD2;
+ break;
+ case XK_Shift_L:
+ nExtModMask = ModKeyFlags::LeftShift;
+ nModMask = KEY_SHIFT;
+ break;
+ case XK_Shift_R:
+ nExtModMask = ModKeyFlags::RightShift;
+ nModMask = KEY_SHIFT;
+ break;
+ // Map Meta/Super keys to MOD3 modifier on all Unix systems
+ // except macOS
+ case XK_Meta_L:
+ case XK_Super_L:
+ nExtModMask = ModKeyFlags::LeftMod3;
+ nModMask = KEY_MOD3;
+ break;
+ case XK_Meta_R:
+ case XK_Super_R:
+ nExtModMask = ModKeyFlags::RightMod3;
+ nModMask = KEY_MOD3;
+ break;
+ }
+
+ if (!bIsKeyPressed)
+ {
+ // sending the old mnModKeyCode mask on release is needed to
+ // implement the writing direction switch with Ctrl + L/R-Shift
+ aModEvt.mnModKeyCode = rFrame.m_nKeyModifiers;
+ nModCode &= ~nModMask;
+ rFrame.m_nKeyModifiers &= ~nExtModMask;
+ }
+ else
+ {
+ nModCode |= nModMask;
+ rFrame.m_nKeyModifiers |= nExtModMask;
+ aModEvt.mnModKeyCode = rFrame.m_nKeyModifiers;
+ }
+ }
+#endif
+ aModEvt.mnCode = nModCode;
+
+ rFrame.CallCallback(SalEvent::KeyModChange, &aModEvt);
+ return false;
+ }
+
+#if CHECK_ANY_QT_USING_X11
+ // prevent interference of writing direction switch (Ctrl + L/R-Shift) with "normal" shortcuts
+ rFrame.m_nKeyModifiers = ModKeyFlags::NONE;
+#endif
+
+ SalKeyEvent aEvent;
+ aEvent.mnCharCode = (pEvent->text().isEmpty() ? 0 : pEvent->text().at(0).unicode());
+ aEvent.mnRepeat = 0;
+ aEvent.mnCode = nCode;
+ aEvent.mnCode |= GetKeyModCode(pEvent->modifiers());
+
+ QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle);
+
+ bool bStopProcessingKey;
+ if (bIsKeyPressed)
+ bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyInput, &aEvent);
+ else
+ bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyUp, &aEvent);
+ if (bStopProcessingKey)
+ pEvent->accept();
+ return bStopProcessingKey;
+}
+
+bool QtWidget::handleEvent(QtFrame& rFrame, QWidget& rWidget, QEvent* pEvent)
+{
+ if (pEvent->type() == QEvent::Gesture)
+ {
+ QGestureEvent* pGestureEvent = static_cast<QGestureEvent*>(pEvent);
+ return handleGestureEvent(rFrame, pGestureEvent);
+ }
+ else if (pEvent->type() == QEvent::ShortcutOverride)
+ {
+ // ignore non-spontaneous QEvent::ShortcutOverride events,
+ // since such an extra event is sent e.g. with Orca screen reader enabled,
+ // so that two events of that kind (the "real one" and a non-spontaneous one)
+ // would otherwise be processed, resulting in duplicate input as 'handleKeyEvent'
+ // is called below (s. tdf#122053)
+ if (!pEvent->spontaneous())
+ {
+ return false;
+ }
+
+ // Accepted event disables shortcut activation,
+ // but enables keypress event.
+ // If event is not accepted and shortcut is successfully activated,
+ // KeyPress event is omitted.
+ //
+ // Instead of processing keyPressEvent, handle ShortcutOverride event,
+ // and if it's handled - disable the shortcut, it should have been activated.
+ // Don't process keyPressEvent generated after disabling shortcut since it was handled here.
+ // If event is not handled, don't accept it and let Qt activate related shortcut.
+ if (handleKeyEvent(rFrame, rWidget, static_cast<QKeyEvent*>(pEvent)))
+ return true;
+ }
+ else if (pEvent->type() == QEvent::ToolTip)
+ {
+ // Qt's POV on the active popup is wrong due to our fake popup, so check LO's state.
+ // Otherwise Qt will continue handling ToolTip events from the "parent" window.
+ const QtFrame* pPopupFrame = GetQtInstance()->activePopup();
+ if (!rFrame.m_aTooltipText.isEmpty() && (!pPopupFrame || pPopupFrame == &rFrame))
+ QToolTip::showText(QCursor::pos(), toQString(rFrame.m_aTooltipText), &rWidget,
+ rFrame.m_aTooltipArea);
+ else
+ {
+ QToolTip::hideText();
+ pEvent->ignore();
+ }
+ return true;
+ }
+ return false;
+}
+
+bool QtWidget::event(QEvent* pEvent)
+{
+ return handleEvent(m_rFrame, *this, pEvent) || QWidget::event(pEvent);
+}
+
+void QtWidget::keyReleaseEvent(QKeyEvent* pEvent)
+{
+ if (!handleKeyReleaseEvent(m_rFrame, *this, pEvent))
+ QWidget::keyReleaseEvent(pEvent);
+}
+
+void QtWidget::focusInEvent(QFocusEvent*) { m_rFrame.CallCallback(SalEvent::GetFocus, nullptr); }
+
+void QtWidget::closePopup()
+{
+ VclPtr<FloatingWindow> pFirstFloat = ImplGetSVData()->mpWinData->mpFirstFloat;
+ if (pFirstFloat && !(pFirstFloat->GetPopupModeFlags() & FloatWinPopupFlags::NoAppFocusClose))
+ {
+ SolarMutexGuard aGuard;
+ pFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll);
+ }
+}
+
+void QtWidget::focusOutEvent(QFocusEvent*)
+{
+#if CHECK_ANY_QT_USING_X11
+ m_rFrame.m_nKeyModifiers = ModKeyFlags::NONE;
+#endif
+ endExtTextInput();
+ m_rFrame.CallCallback(SalEvent::LoseFocus, nullptr);
+ closePopup();
+}
+
+QtWidget::QtWidget(QtFrame& rFrame, Qt::WindowFlags f)
+ // if you try to set the QWidget parent via the QtFrame, instead of using the Q_NULLPTR, at
+ // least test Wayland popups; these horribly broke last time doing this (read commits)!
+ : QWidget(Q_NULLPTR, f)
+ , m_rFrame(rFrame)
+ , m_bNonEmptyIMPreeditSeen(false)
+ , m_bInInputMethodQueryCursorRectangle(false)
+ , m_nDeltaX(0)
+ , m_nDeltaY(0)
+{
+ setAttribute(Qt::WA_TranslucentBackground);
+ setAttribute(Qt::WA_OpaquePaintEvent);
+ setAttribute(Qt::WA_NoSystemBackground);
+ setMouseTracking(true);
+ if (!rFrame.isPopup())
+ setFocusPolicy(Qt::StrongFocus);
+ else
+ setFocusPolicy(Qt::ClickFocus);
+
+ grabGesture(Qt::PinchGesture);
+}
+
+static ExtTextInputAttr lcl_MapUnderlineStyle(QTextCharFormat::UnderlineStyle us)
+{
+ switch (us)
+ {
+ case QTextCharFormat::NoUnderline:
+ return ExtTextInputAttr::NONE;
+ case QTextCharFormat::DotLine:
+ return ExtTextInputAttr::DottedUnderline;
+ case QTextCharFormat::DashDotDotLine:
+ case QTextCharFormat::DashDotLine:
+ return ExtTextInputAttr::DashDotUnderline;
+ case QTextCharFormat::WaveUnderline:
+ return ExtTextInputAttr::GrayWaveline;
+ default:
+ return ExtTextInputAttr::Underline;
+ }
+}
+
+void QtWidget::inputMethodEvent(QInputMethodEvent* pEvent)
+{
+ const bool bHasCommitText = !pEvent->commitString().isEmpty();
+ const int nReplacementLength = pEvent->replacementLength();
+
+ if (nReplacementLength > 0 || bHasCommitText)
+ {
+ if (nReplacementLength > 0)
+ deleteReplacementText(m_rFrame, pEvent->replacementStart(), nReplacementLength);
+ if (bHasCommitText)
+ commitText(m_rFrame, pEvent->commitString());
+ }
+ else
+ {
+ SalExtTextInputEvent aInputEvent;
+ aInputEvent.mpTextAttr = nullptr;
+ aInputEvent.mnCursorFlags = 0;
+ aInputEvent.maText = toOUString(pEvent->preeditString());
+ aInputEvent.mnCursorPos = 0;
+
+ const sal_Int32 nLength = aInputEvent.maText.getLength();
+ const QList<QInputMethodEvent::Attribute>& rAttrList = pEvent->attributes();
+ std::vector<ExtTextInputAttr> aTextAttrs(std::max(sal_Int32(1), nLength),
+ ExtTextInputAttr::NONE);
+ aInputEvent.mpTextAttr = aTextAttrs.data();
+
+ for (const QInputMethodEvent::Attribute& rAttr : rAttrList)
+ {
+ switch (rAttr.type)
+ {
+ case QInputMethodEvent::TextFormat:
+ {
+ QTextCharFormat aCharFormat
+ = qvariant_cast<QTextFormat>(rAttr.value).toCharFormat();
+ if (aCharFormat.isValid())
+ {
+ ExtTextInputAttr aETIP
+ = lcl_MapUnderlineStyle(aCharFormat.underlineStyle());
+ if (aCharFormat.hasProperty(QTextFormat::BackgroundBrush))
+ aETIP |= ExtTextInputAttr::Highlight;
+ if (aCharFormat.fontStrikeOut())
+ aETIP |= ExtTextInputAttr::RedText;
+ for (int j = rAttr.start; j < rAttr.start + rAttr.length; j++)
+ {
+ SAL_WARN_IF(j >= static_cast<int>(aTextAttrs.size()), "vcl.qt",
+ "QInputMethodEvent::Attribute out of range. Broken range: "
+ << rAttr.start << "," << rAttr.start + rAttr.length
+ << " Legal range: 0," << aTextAttrs.size());
+ if (j >= static_cast<int>(aTextAttrs.size()))
+ break;
+ aTextAttrs[j] = aETIP;
+ }
+ }
+ break;
+ }
+ case QInputMethodEvent::Cursor:
+ {
+ aInputEvent.mnCursorPos = rAttr.start;
+ if (rAttr.length == 0)
+ aInputEvent.mnCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
+ break;
+ }
+ default:
+ SAL_WARN("vcl.qt", "Unhandled QInputMethodEvent attribute: "
+ << static_cast<int>(rAttr.type));
+ break;
+ }
+ }
+
+ const bool bIsEmpty = aInputEvent.maText.isEmpty();
+ if (m_bNonEmptyIMPreeditSeen || !bIsEmpty)
+ {
+ SolarMutexGuard aGuard;
+ vcl::DeletionListener aDel(&m_rFrame);
+ m_rFrame.CallCallback(SalEvent::ExtTextInput, &aInputEvent);
+ if (!aDel.isDeleted() && bIsEmpty)
+ m_rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
+ m_bNonEmptyIMPreeditSeen = !bIsEmpty;
+ }
+ }
+
+ pEvent->accept();
+}
+
+static bool lcl_retrieveSurrounding(sal_Int32& rPosition, sal_Int32& rAnchor, QString* pText,
+ QString* pSelection)
+{
+ SolarMutexGuard aGuard;
+ vcl::Window* pFocusWin = Application::GetFocusWindow();
+ if (!pFocusWin)
+ return false;
+
+ 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.qt", "Exception in getting input method surrounding text");
+ }
+
+ if (xText.is())
+ {
+ rPosition = xText->getCaretPosition();
+ if (rPosition != -1)
+ {
+ if (pText)
+ *pText = toQString(xText->getText());
+
+ sal_Int32 nSelStart = xText->getSelectionStart();
+ sal_Int32 nSelEnd = xText->getSelectionEnd();
+ if (nSelStart == nSelEnd)
+ {
+ rAnchor = rPosition;
+ }
+ else
+ {
+ if (rPosition == nSelStart)
+ rAnchor = nSelEnd;
+ else
+ rAnchor = nSelStart;
+ if (pSelection)
+ *pSelection = toQString(xText->getSelectedText());
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+QVariant QtWidget::inputMethodQuery(Qt::InputMethodQuery property) const
+{
+ switch (property)
+ {
+ case Qt::ImSurroundingText:
+ {
+ QString aText;
+ sal_Int32 nCursorPos, nAnchor;
+ if (lcl_retrieveSurrounding(nCursorPos, nAnchor, &aText, nullptr))
+ return QVariant(aText);
+ return QVariant();
+ }
+ case Qt::ImCursorPosition:
+ {
+ sal_Int32 nCursorPos, nAnchor;
+ if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr))
+ return QVariant(static_cast<int>(nCursorPos));
+ return QVariant();
+ }
+ case Qt::ImCursorRectangle:
+ {
+ if (!m_bInInputMethodQueryCursorRectangle)
+ {
+ m_bInInputMethodQueryCursorRectangle = true;
+ SalExtTextInputPosEvent aPosEvent;
+ m_rFrame.CallCallback(SalEvent::ExtTextInputPos, &aPosEvent);
+ const qreal fRatio = m_rFrame.devicePixelRatioF();
+ m_aImCursorRectangle.setRect(aPosEvent.mnX / fRatio, aPosEvent.mnY / fRatio,
+ aPosEvent.mnWidth / fRatio,
+ aPosEvent.mnHeight / fRatio);
+ m_bInInputMethodQueryCursorRectangle = false;
+ }
+ return QVariant(m_aImCursorRectangle);
+ }
+ case Qt::ImAnchorPosition:
+ {
+ sal_Int32 nCursorPos, nAnchor;
+ if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr))
+ return QVariant(static_cast<int>(nAnchor));
+ return QVariant();
+ }
+ case Qt::ImCurrentSelection:
+ {
+ QString aSelection;
+ sal_Int32 nCursorPos, nAnchor;
+ if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, &aSelection))
+ return QVariant(aSelection);
+ return QVariant();
+ }
+ default:
+ return QWidget::inputMethodQuery(property);
+ }
+}
+
+void QtWidget::endExtTextInput()
+{
+ if (m_bNonEmptyIMPreeditSeen)
+ {
+ m_rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
+ m_bNonEmptyIMPreeditSeen = false;
+ }
+}
+
+void QtWidget::changeEvent(QEvent* pEvent)
+{
+ switch (pEvent->type())
+ {
+ case QEvent::FontChange:
+ [[fallthrough]];
+ case QEvent::PaletteChange:
+ [[fallthrough]];
+ case QEvent::StyleChange:
+ {
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->UpdateStyle(QEvent::FontChange == pEvent->type());
+ break;
+ }
+ default:
+ break;
+ }
+ QWidget::changeEvent(pEvent);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtX11Support.cxx b/vcl/qt5/QtX11Support.cxx
new file mode 100644
index 0000000000..84036fb8a5
--- /dev/null
+++ b/vcl/qt5/QtX11Support.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 <QtX11Support.hxx>
+
+#include <config_vclplug.h>
+
+#include <QtCore/QVersionNumber>
+
+#include <QtInstance.hxx>
+#include <QtTools.hxx>
+
+#if CHECK_QT5_USING_X11
+#include <QtX11Extras/QX11Info>
+#endif
+
+#include <unx/gensys.h>
+
+void QtX11Support::setApplicationID(const xcb_window_t nWinId, std::u16string_view rWMClass)
+{
+#if CHECK_QT5_USING_X11
+ OString aResClass = OUStringToOString(rWMClass, RTL_TEXTENCODING_ASCII_US);
+ const char* pResClass
+ = !aResClass.isEmpty() ? aResClass.getStr() : SalGenericSystem::getFrameClassName();
+ OString aResName = SalGenericSystem::getFrameResName();
+
+ // the WM_CLASS data consists of two concatenated cstrings, including the terminating '\0' chars
+ const uint32_t data_len = aResName.getLength() + 1 + strlen(pResClass) + 1;
+ char* data = new char[data_len];
+ memcpy(data, aResName.getStr(), aResName.getLength() + 1);
+ memcpy(data + aResName.getLength() + 1, pResClass, strlen(pResClass) + 1);
+
+ xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, nWinId, XCB_ATOM_WM_CLASS,
+ XCB_ATOM_STRING, 8, data_len, data);
+ delete[] data;
+#else
+ Q_UNUSED(nWinId);
+ Q_UNUSED(rWMClass);
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtXAccessible.cxx b/vcl/qt5/QtXAccessible.cxx
new file mode 100644
index 0000000000..25c0c5e9e1
--- /dev/null
+++ b/vcl/qt5/QtXAccessible.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/.
+ */
+
+#include <QtXAccessible.hxx>
+#include <QtXAccessible.moc>
+
+#include <QtFrame.hxx>
+#include <QtTools.hxx>
+#include <QtWidget.hxx>
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+
+#include <sal/log.hxx>
+#include <utility>
+
+using namespace css::accessibility;
+using namespace css::uno;
+
+QtXAccessible::QtXAccessible(Reference<XAccessible> xAccessible)
+ : m_xAccessible(std::move(xAccessible))
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtAccessibleEventListener.cxx b/vcl/qt6/QtAccessibleEventListener.cxx
new file mode 100644
index 0000000000..9a0c401af6
--- /dev/null
+++ b/vcl/qt6/QtAccessibleEventListener.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtAccessibleEventListener.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtAccessibleRegistry.cxx b/vcl/qt6/QtAccessibleRegistry.cxx
new file mode 100644
index 0000000000..782393e713
--- /dev/null
+++ b/vcl/qt6/QtAccessibleRegistry.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtAccessibleRegistry.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtAccessibleWidget.cxx b/vcl/qt6/QtAccessibleWidget.cxx
new file mode 100644
index 0000000000..c850d63118
--- /dev/null
+++ b/vcl/qt6/QtAccessibleWidget.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtAccessibleWidget.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtBitmap.cxx b/vcl/qt6/QtBitmap.cxx
new file mode 100644
index 0000000000..fe82d077d8
--- /dev/null
+++ b/vcl/qt6/QtBitmap.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtBitmap.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtClipboard.cxx b/vcl/qt6/QtClipboard.cxx
new file mode 100644
index 0000000000..b5ed788e71
--- /dev/null
+++ b/vcl/qt6/QtClipboard.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtClipboard.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtData.cxx b/vcl/qt6/QtData.cxx
new file mode 100644
index 0000000000..73035751aa
--- /dev/null
+++ b/vcl/qt6/QtData.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtData.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtDragAndDrop.cxx b/vcl/qt6/QtDragAndDrop.cxx
new file mode 100644
index 0000000000..7c30624019
--- /dev/null
+++ b/vcl/qt6/QtDragAndDrop.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtDragAndDrop.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtFilePicker.cxx b/vcl/qt6/QtFilePicker.cxx
new file mode 100644
index 0000000000..5dda50afdf
--- /dev/null
+++ b/vcl/qt6/QtFilePicker.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtFilePicker.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtFont.cxx b/vcl/qt6/QtFont.cxx
new file mode 100644
index 0000000000..0af6c3addd
--- /dev/null
+++ b/vcl/qt6/QtFont.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtFont.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtFontFace.cxx b/vcl/qt6/QtFontFace.cxx
new file mode 100644
index 0000000000..1dda71dbd4
--- /dev/null
+++ b/vcl/qt6/QtFontFace.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtFontFace.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtFrame.cxx b/vcl/qt6/QtFrame.cxx
new file mode 100644
index 0000000000..fc15d6b53f
--- /dev/null
+++ b/vcl/qt6/QtFrame.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtFrame.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtGraphics.cxx b/vcl/qt6/QtGraphics.cxx
new file mode 100644
index 0000000000..a092d4a590
--- /dev/null
+++ b/vcl/qt6/QtGraphics.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtGraphics.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtGraphics_Controls.cxx b/vcl/qt6/QtGraphics_Controls.cxx
new file mode 100644
index 0000000000..1ae93c236a
--- /dev/null
+++ b/vcl/qt6/QtGraphics_Controls.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtGraphics_Controls.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtGraphics_GDI.cxx b/vcl/qt6/QtGraphics_GDI.cxx
new file mode 100644
index 0000000000..a7d1bb5027
--- /dev/null
+++ b/vcl/qt6/QtGraphics_GDI.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtGraphics_GDI.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtGraphics_Text.cxx b/vcl/qt6/QtGraphics_Text.cxx
new file mode 100644
index 0000000000..d7326b0f20
--- /dev/null
+++ b/vcl/qt6/QtGraphics_Text.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtGraphics_Text.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtInstance.cxx b/vcl/qt6/QtInstance.cxx
new file mode 100644
index 0000000000..7b0910e6d3
--- /dev/null
+++ b/vcl/qt6/QtInstance.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtInstance.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtInstance_Print.cxx b/vcl/qt6/QtInstance_Print.cxx
new file mode 100644
index 0000000000..f08e6e17ed
--- /dev/null
+++ b/vcl/qt6/QtInstance_Print.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtInstance_Print.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtMainWindow.cxx b/vcl/qt6/QtMainWindow.cxx
new file mode 100644
index 0000000000..e230ccaac6
--- /dev/null
+++ b/vcl/qt6/QtMainWindow.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtMainWindow.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtMenu.cxx b/vcl/qt6/QtMenu.cxx
new file mode 100644
index 0000000000..18c2d4c4e5
--- /dev/null
+++ b/vcl/qt6/QtMenu.cxx
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <QtWidgets/QMenu>
+
+#include "../qt5/QtMenu.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtObject.cxx b/vcl/qt6/QtObject.cxx
new file mode 100644
index 0000000000..e804679c54
--- /dev/null
+++ b/vcl/qt6/QtObject.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtObject.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtOpenGLContext.cxx b/vcl/qt6/QtOpenGLContext.cxx
new file mode 100644
index 0000000000..80af012c90
--- /dev/null
+++ b/vcl/qt6/QtOpenGLContext.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtOpenGLContext.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtPainter.cxx b/vcl/qt6/QtPainter.cxx
new file mode 100644
index 0000000000..8a8de83ee2
--- /dev/null
+++ b/vcl/qt6/QtPainter.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtPainter.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtPrinter.cxx b/vcl/qt6/QtPrinter.cxx
new file mode 100644
index 0000000000..8c5b93b237
--- /dev/null
+++ b/vcl/qt6/QtPrinter.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtPrinter.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtSvpGraphics.cxx b/vcl/qt6/QtSvpGraphics.cxx
new file mode 100644
index 0000000000..6182d816a1
--- /dev/null
+++ b/vcl/qt6/QtSvpGraphics.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtSvpGraphics.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtSvpSurface.cxx b/vcl/qt6/QtSvpSurface.cxx
new file mode 100644
index 0000000000..7d773a8798
--- /dev/null
+++ b/vcl/qt6/QtSvpSurface.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtSvpSurface.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtSvpVirtualDevice.hxx b/vcl/qt6/QtSvpVirtualDevice.hxx
new file mode 100644
index 0000000000..1639ab1a22
--- /dev/null
+++ b/vcl/qt6/QtSvpVirtualDevice.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtSvpVirtualDevice.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtSystem.cxx b/vcl/qt6/QtSystem.cxx
new file mode 100644
index 0000000000..f7ae8d1c8f
--- /dev/null
+++ b/vcl/qt6/QtSystem.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtSystem.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtTimer.cxx b/vcl/qt6/QtTimer.cxx
new file mode 100644
index 0000000000..94ba353a5a
--- /dev/null
+++ b/vcl/qt6/QtTimer.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtTimer.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtTools.cxx b/vcl/qt6/QtTools.cxx
new file mode 100644
index 0000000000..0a003b566e
--- /dev/null
+++ b/vcl/qt6/QtTools.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtTools.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtTransferable.cxx b/vcl/qt6/QtTransferable.cxx
new file mode 100644
index 0000000000..d6384d5d5b
--- /dev/null
+++ b/vcl/qt6/QtTransferable.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtTransferable.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtVirtualDevice.cxx b/vcl/qt6/QtVirtualDevice.cxx
new file mode 100644
index 0000000000..5d40d2af1e
--- /dev/null
+++ b/vcl/qt6/QtVirtualDevice.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtVirtualDevice.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtWidget.cxx b/vcl/qt6/QtWidget.cxx
new file mode 100644
index 0000000000..0e1e3203c3
--- /dev/null
+++ b/vcl/qt6/QtWidget.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtWidget.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtX11Support.cxx b/vcl/qt6/QtX11Support.cxx
new file mode 100644
index 0000000000..28f67978e3
--- /dev/null
+++ b/vcl/qt6/QtX11Support.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtX11Support.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt6/QtXAccessible.cxx b/vcl/qt6/QtXAccessible.cxx
new file mode 100644
index 0000000000..d1a262007b
--- /dev/null
+++ b/vcl/qt6/QtXAccessible.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../qt5/QtXAccessible.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/AquaGraphicsBackend.cxx b/vcl/quartz/AquaGraphicsBackend.cxx
new file mode 100644
index 0000000000..4badefacf4
--- /dev/null
+++ b/vcl/quartz/AquaGraphicsBackend.cxx
@@ -0,0 +1,1346 @@
+/* -*- 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 <sal/config.h>
+#include <sal/log.hxx>
+
+#include <cassert>
+#include <cstring>
+#include <numeric>
+#include <utility>
+
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <osl/endian.h>
+#include <osl/file.hxx>
+#include <sal/types.h>
+#include <tools/long.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <fontsubset.hxx>
+#include <quartz/salbmp.h>
+#ifdef MACOSX
+#include <quartz/salgdi.h>
+#endif
+#include <quartz/utils.h>
+#ifdef IOS
+#include <ios/iosinst.hxx>
+#endif
+
+using namespace vcl;
+
+namespace
+{
+const basegfx::B2DPoint aHalfPointOfs(0.5, 0.5);
+
+void AddPolygonToPath(CGMutablePathRef xPath, const basegfx::B2DPolygon& rPolygon, bool bClosePath,
+ bool bPixelSnap, bool bLineDraw)
+{
+ // short circuit if there is nothing to do
+ const int nPointCount = rPolygon.count();
+ if (nPointCount <= 0)
+ {
+ return;
+ }
+
+ const bool bHasCurves = rPolygon.areControlPointsUsed();
+ for (int nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++)
+ {
+ int nClosedIdx = nPointIdx;
+ if (nPointIdx >= nPointCount)
+ {
+ // prepare to close last curve segment if needed
+ if (bClosePath && (nPointIdx == nPointCount))
+ {
+ nClosedIdx = 0;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ basegfx::B2DPoint aPoint = rPolygon.getB2DPoint(nClosedIdx);
+
+ if (bPixelSnap)
+ {
+ // snap device coordinates to full pixels
+ aPoint.setX(basegfx::fround(aPoint.getX()));
+ aPoint.setY(basegfx::fround(aPoint.getY()));
+ }
+
+ if (bLineDraw)
+ {
+ aPoint += aHalfPointOfs;
+ }
+ if (!nPointIdx)
+ {
+ // first point => just move there
+ CGPathMoveToPoint(xPath, nullptr, aPoint.getX(), aPoint.getY());
+ continue;
+ }
+
+ bool bPendingCurve = false;
+ if (bHasCurves)
+ {
+ bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx);
+ bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx);
+ }
+
+ if (!bPendingCurve) // line segment
+ {
+ CGPathAddLineToPoint(xPath, nullptr, aPoint.getX(), aPoint.getY());
+ }
+ else // cubic bezier segment
+ {
+ basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint(nPrevIdx);
+ basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint(nClosedIdx);
+ if (bLineDraw)
+ {
+ aCP1 += aHalfPointOfs;
+ aCP2 += aHalfPointOfs;
+ }
+ CGPathAddCurveToPoint(xPath, nullptr, aCP1.getX(), aCP1.getY(), aCP2.getX(),
+ aCP2.getY(), aPoint.getX(), aPoint.getY());
+ }
+ }
+
+ if (bClosePath)
+ {
+ CGPathCloseSubpath(xPath);
+ }
+}
+
+void alignLinePoint(const Point* i_pIn, float& o_fX, float& o_fY)
+{
+ o_fX = static_cast<float>(i_pIn->getX()) + 0.5;
+ o_fY = static_cast<float>(i_pIn->getY()) + 0.5;
+}
+
+void getBoundRect(sal_uInt32 nPoints, const Point* pPtAry, tools::Long& rX, tools::Long& rY,
+ tools::Long& rWidth, tools::Long& rHeight)
+{
+ tools::Long nX1 = pPtAry->getX();
+ tools::Long nX2 = nX1;
+ tools::Long nY1 = pPtAry->getY();
+ tools::Long nY2 = nY1;
+
+ for (sal_uInt32 n = 1; n < nPoints; n++)
+ {
+ if (pPtAry[n].getX() < nX1)
+ {
+ nX1 = pPtAry[n].getX();
+ }
+ else if (pPtAry[n].getX() > nX2)
+ {
+ nX2 = pPtAry[n].getX();
+ }
+ if (pPtAry[n].getY() < nY1)
+ {
+ nY1 = pPtAry[n].getY();
+ }
+ else if (pPtAry[n].getY() > nY2)
+ {
+ nY2 = pPtAry[n].getY();
+ }
+ }
+ rX = nX1;
+ rY = nY1;
+ rWidth = nX2 - nX1 + 1;
+ rHeight = nY2 - nY1 + 1;
+}
+
+Color ImplGetROPColor(SalROPColor nROPColor)
+{
+ Color nColor;
+ if (nROPColor == SalROPColor::N0)
+ {
+ nColor = Color(0, 0, 0);
+ }
+ else
+ {
+ nColor = Color(255, 255, 255);
+ }
+ return nColor;
+}
+
+void drawPattern50(void*, CGContextRef rContext)
+{
+ static const CGRect aRects[2] = { { { 0, 0 }, { 2, 2 } }, { { 2, 2 }, { 2, 2 } } };
+ CGContextAddRects(rContext, aRects, 2);
+ CGContextFillPath(rContext);
+}
+}
+
+AquaGraphicsBackend::AquaGraphicsBackend(AquaSharedAttributes& rShared)
+ : AquaGraphicsBackendBase(rShared, this)
+{
+}
+
+AquaGraphicsBackend::~AquaGraphicsBackend() {}
+
+void AquaGraphicsBackend::Init() {}
+void AquaGraphicsBackend::freeResources() {}
+
+void AquaGraphicsBackend::setClipRegion(vcl::Region const& rRegion)
+{
+ // release old clip path
+ mrShared.unsetClipPath();
+ mrShared.mxClipPath = CGPathCreateMutable();
+
+ // set current path, either as polypolgon or sequence of rectangles
+ RectangleVector aRectangles;
+ rRegion.GetRegionRectangles(aRectangles);
+
+ for (const auto& rRect : aRectangles)
+ {
+ const tools::Long nW(rRect.Right() - rRect.Left() + 1); // uses +1 logic in original
+
+ if (nW)
+ {
+ const tools::Long nH(rRect.Bottom() - rRect.Top() + 1); // uses +1 logic in original
+
+ if (nH)
+ {
+ const CGRect aRect = CGRectMake(rRect.Left(), rRect.Top(), nW, nH);
+ CGPathAddRect(mrShared.mxClipPath, nullptr, aRect);
+ }
+ }
+ }
+ // set the current path as clip region
+ if (mrShared.checkContext())
+ mrShared.setState();
+}
+
+void AquaGraphicsBackend::ResetClipRegion()
+{
+ // release old path and indicate no clipping
+ mrShared.unsetClipPath();
+
+ if (mrShared.checkContext())
+ {
+ mrShared.setState();
+ }
+}
+
+sal_uInt16 AquaGraphicsBackend::GetBitCount() const
+{
+ sal_uInt16 nBits = mrShared.mnBitmapDepth ? mrShared.mnBitmapDepth : 32; //24;
+ return nBits;
+}
+
+tools::Long AquaGraphicsBackend::GetGraphicsWidth() const
+{
+ tools::Long width = 0;
+ if (mrShared.maContextHolder.isSet()
+ && (
+#ifndef IOS
+ mrShared.mbWindow ||
+#endif
+ mrShared.mbVirDev))
+ {
+ width = mrShared.mnWidth;
+ }
+
+#ifndef IOS
+ if (width == 0)
+ {
+ if (mrShared.mbWindow && mrShared.mpFrame)
+ {
+ width = mrShared.mpFrame->maGeometry.width();
+ }
+ }
+#endif
+ return width;
+}
+
+void AquaGraphicsBackend::SetLineColor()
+{
+ mrShared.maLineColor.SetAlpha(0.0); // transparent
+ if (mrShared.checkContext())
+ {
+ CGContextSetRGBStrokeColor(mrShared.maContextHolder.get(), mrShared.maLineColor.GetRed(),
+ mrShared.maLineColor.GetGreen(), mrShared.maLineColor.GetBlue(),
+ mrShared.maLineColor.GetAlpha());
+ }
+}
+
+void AquaGraphicsBackend::SetLineColor(Color nColor)
+{
+ mrShared.maLineColor = RGBAColor(nColor);
+ if (mrShared.checkContext())
+ {
+ CGContextSetRGBStrokeColor(mrShared.maContextHolder.get(), mrShared.maLineColor.GetRed(),
+ mrShared.maLineColor.GetGreen(), mrShared.maLineColor.GetBlue(),
+ mrShared.maLineColor.GetAlpha());
+ }
+}
+
+void AquaGraphicsBackend::SetFillColor()
+{
+ mrShared.maFillColor.SetAlpha(0.0); // transparent
+ if (mrShared.checkContext())
+ {
+ CGContextSetRGBFillColor(mrShared.maContextHolder.get(), mrShared.maFillColor.GetRed(),
+ mrShared.maFillColor.GetGreen(), mrShared.maFillColor.GetBlue(),
+ mrShared.maFillColor.GetAlpha());
+ }
+}
+
+void AquaGraphicsBackend::SetFillColor(Color nColor)
+{
+ mrShared.maFillColor = RGBAColor(nColor);
+ if (mrShared.checkContext())
+ {
+ CGContextSetRGBFillColor(mrShared.maContextHolder.get(), mrShared.maFillColor.GetRed(),
+ mrShared.maFillColor.GetGreen(), mrShared.maFillColor.GetBlue(),
+ mrShared.maFillColor.GetAlpha());
+ }
+}
+
+void AquaGraphicsBackend::SetXORMode(bool bSet, bool bInvertOnly)
+{
+ // return early if XOR mode remains unchanged
+ if (mrShared.mbPrinter)
+ {
+ return;
+ }
+ if (!bSet && mrShared.mnXorMode == 2)
+ {
+ CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeNormal);
+ mrShared.mnXorMode = 0;
+ return;
+ }
+ else if (bSet && bInvertOnly && mrShared.mnXorMode == 0)
+ {
+ CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference);
+ mrShared.mnXorMode = 2;
+ return;
+ }
+
+ if (!mrShared.mpXorEmulation && !bSet)
+ {
+ return;
+ }
+ if (mrShared.mpXorEmulation && bSet == mrShared.mpXorEmulation->IsEnabled())
+ {
+ return;
+ }
+ if (!mrShared.checkContext())
+ {
+ return;
+ }
+ // prepare XOR emulation
+ if (!mrShared.mpXorEmulation)
+ {
+ mrShared.mpXorEmulation = std::make_unique<XorEmulation>();
+ mrShared.mpXorEmulation->SetTarget(mrShared.mnWidth, mrShared.mnHeight,
+ mrShared.mnBitmapDepth, mrShared.maContextHolder.get(),
+ mrShared.maLayer.get());
+ }
+
+ // change the XOR mode
+ if (bSet)
+ {
+ mrShared.mpXorEmulation->Enable();
+ mrShared.maContextHolder.set(mrShared.mpXorEmulation->GetMaskContext());
+ mrShared.mnXorMode = 1;
+ }
+ else
+ {
+ mrShared.mpXorEmulation->UpdateTarget();
+ mrShared.mpXorEmulation->Disable();
+ mrShared.maContextHolder.set(mrShared.mpXorEmulation->GetTargetContext());
+ mrShared.mnXorMode = 0;
+ }
+}
+
+void AquaGraphicsBackend::SetROPFillColor(SalROPColor nROPColor)
+{
+ if (!mrShared.mbPrinter)
+ {
+ SetFillColor(ImplGetROPColor(nROPColor));
+ }
+}
+
+void AquaGraphicsBackend::SetROPLineColor(SalROPColor nROPColor)
+{
+ if (!mrShared.mbPrinter)
+ {
+ SetLineColor(ImplGetROPColor(nROPColor));
+ }
+}
+
+void AquaGraphicsBackend::drawPixelImpl(tools::Long nX, tools::Long nY, const RGBAColor& rColor)
+{
+ if (!mrShared.checkContext())
+ return;
+
+ // overwrite the fill color
+ CGContextSetFillColor(mrShared.maContextHolder.get(), rColor.AsArray());
+ // draw 1x1 rect, there is no pixel drawing in Quartz
+ const CGRect aDstRect = CGRectMake(nX, nY, 1, 1);
+ CGContextFillRect(mrShared.maContextHolder.get(), aDstRect);
+
+ refreshRect(aDstRect);
+
+ // reset the fill color
+ CGContextSetFillColor(mrShared.maContextHolder.get(), mrShared.maFillColor.AsArray());
+}
+
+void AquaGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY)
+{
+ // draw pixel with current line color
+ drawPixelImpl(nX, nY, mrShared.maLineColor);
+}
+
+void AquaGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY, Color nColor)
+{
+ const RGBAColor aPixelColor(nColor);
+ drawPixelImpl(nX, nY, aPixelColor);
+}
+
+void AquaGraphicsBackend::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2,
+ tools::Long nY2)
+{
+ if (nX1 == nX2 && nY1 == nY2)
+ {
+ // #i109453# platform independent code expects at least one pixel to be drawn
+ drawPixel(nX1, nY1);
+ return;
+ }
+
+ if (!mrShared.checkContext())
+ return;
+
+ CGContextBeginPath(mrShared.maContextHolder.get());
+ CGContextMoveToPoint(mrShared.maContextHolder.get(), float(nX1) + 0.5, float(nY1) + 0.5);
+ CGContextAddLineToPoint(mrShared.maContextHolder.get(), float(nX2) + 0.5, float(nY2) + 0.5);
+ CGContextDrawPath(mrShared.maContextHolder.get(), kCGPathStroke);
+
+ tools::Rectangle aRefreshRect(nX1, nY1, nX2, nY2);
+ (void)aRefreshRect;
+ // Is a call to RefreshRect( aRefreshRect ) missing here?
+}
+
+void AquaGraphicsBackend::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight)
+{
+ if (!mrShared.checkContext())
+ return;
+
+ CGRect aRect = CGRectMake(nX, nY, nWidth, nHeight);
+ if (mrShared.isPenVisible())
+ {
+ aRect.origin.x += 0.5;
+ aRect.origin.y += 0.5;
+ aRect.size.width -= 1;
+ aRect.size.height -= 1;
+ }
+
+ if (mrShared.isBrushVisible())
+ {
+ CGContextFillRect(mrShared.maContextHolder.get(), aRect);
+ }
+ if (mrShared.isPenVisible())
+ {
+ CGContextStrokeRect(mrShared.maContextHolder.get(), aRect);
+ }
+ mrShared.refreshRect(nX, nY, nWidth, nHeight);
+}
+
+void AquaGraphicsBackend::drawPolyLine(sal_uInt32 nPoints, const Point* pPointArray)
+{
+ if (nPoints < 1)
+ return;
+
+ if (!mrShared.checkContext())
+ return;
+
+ tools::Long nX = 0, nY = 0, nWidth = 0, nHeight = 0;
+ getBoundRect(nPoints, pPointArray, nX, nY, nWidth, nHeight);
+
+ float fX, fY;
+ CGContextBeginPath(mrShared.maContextHolder.get());
+ alignLinePoint(pPointArray, fX, fY);
+ CGContextMoveToPoint(mrShared.maContextHolder.get(), fX, fY);
+ pPointArray++;
+
+ for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPointArray++)
+ {
+ alignLinePoint(pPointArray, fX, fY);
+ CGContextAddLineToPoint(mrShared.maContextHolder.get(), fX, fY);
+ }
+ CGContextStrokePath(mrShared.maContextHolder.get());
+
+ mrShared.refreshRect(nX, nY, nWidth, nHeight);
+}
+
+void AquaGraphicsBackend::drawPolygon(sal_uInt32 nPoints, const Point* pPointArray)
+{
+ if (nPoints <= 1)
+ return;
+
+ if (!mrShared.checkContext())
+ return;
+
+ tools::Long nX = 0, nY = 0, nWidth = 0, nHeight = 0;
+ getBoundRect(nPoints, pPointArray, nX, nY, nWidth, nHeight);
+
+ CGPathDrawingMode eMode;
+ if (mrShared.isBrushVisible() && mrShared.isPenVisible())
+ {
+ eMode = kCGPathEOFillStroke;
+ }
+ else if (mrShared.isPenVisible())
+ {
+ eMode = kCGPathStroke;
+ }
+ else if (mrShared.isBrushVisible())
+ {
+ eMode = kCGPathEOFill;
+ }
+ else
+ {
+ SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
+ return;
+ }
+
+ CGContextBeginPath(mrShared.maContextHolder.get());
+
+ if (mrShared.isPenVisible())
+ {
+ float fX, fY;
+ alignLinePoint(pPointArray, fX, fY);
+ CGContextMoveToPoint(mrShared.maContextHolder.get(), fX, fY);
+ pPointArray++;
+ for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPointArray++)
+ {
+ alignLinePoint(pPointArray, fX, fY);
+ CGContextAddLineToPoint(mrShared.maContextHolder.get(), fX, fY);
+ }
+ }
+ else
+ {
+ CGContextMoveToPoint(mrShared.maContextHolder.get(), pPointArray->getX(),
+ pPointArray->getY());
+ pPointArray++;
+ for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPointArray++)
+ {
+ CGContextAddLineToPoint(mrShared.maContextHolder.get(), pPointArray->getX(),
+ pPointArray->getY());
+ }
+ }
+
+ CGContextClosePath(mrShared.maContextHolder.get());
+ CGContextDrawPath(mrShared.maContextHolder.get(), eMode);
+
+ mrShared.refreshRect(nX, nY, nWidth, nHeight);
+}
+
+void AquaGraphicsBackend::drawPolyPolygon(sal_uInt32 nPolyCount, const sal_uInt32* pPoints,
+ const Point** ppPtAry)
+{
+ if (nPolyCount <= 0)
+ return;
+
+ if (!mrShared.checkContext())
+ return;
+
+ // find bound rect
+ tools::Long leftX = 0, topY = 0, maxWidth = 0, maxHeight = 0;
+
+ getBoundRect(pPoints[0], ppPtAry[0], leftX, topY, maxWidth, maxHeight);
+
+ for (sal_uInt32 n = 1; n < nPolyCount; n++)
+ {
+ tools::Long nX = leftX, nY = topY, nW = maxWidth, nH = maxHeight;
+ getBoundRect(pPoints[n], ppPtAry[n], nX, nY, nW, nH);
+ if (nX < leftX)
+ {
+ maxWidth += leftX - nX;
+ leftX = nX;
+ }
+ if (nY < topY)
+ {
+ maxHeight += topY - nY;
+ topY = nY;
+ }
+ if (nX + nW > leftX + maxWidth)
+ {
+ maxWidth = nX + nW - leftX;
+ }
+ if (nY + nH > topY + maxHeight)
+ {
+ maxHeight = nY + nH - topY;
+ }
+ }
+
+ // prepare drawing mode
+ CGPathDrawingMode eMode;
+ if (mrShared.isBrushVisible() && mrShared.isPenVisible())
+ {
+ eMode = kCGPathEOFillStroke;
+ }
+ else if (mrShared.isPenVisible())
+ {
+ eMode = kCGPathStroke;
+ }
+ else if (mrShared.isBrushVisible())
+ {
+ eMode = kCGPathEOFill;
+ }
+ else
+ {
+ SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
+ return;
+ }
+
+ // convert to CGPath
+ CGContextBeginPath(mrShared.maContextHolder.get());
+ if (mrShared.isPenVisible())
+ {
+ for (sal_uInt32 nPoly = 0; nPoly < nPolyCount; nPoly++)
+ {
+ const sal_uInt32 nPoints = pPoints[nPoly];
+ if (nPoints > 1)
+ {
+ const Point* pPtAry = ppPtAry[nPoly];
+ float fX, fY;
+
+ alignLinePoint(pPtAry, fX, fY);
+ CGContextMoveToPoint(mrShared.maContextHolder.get(), fX, fY);
+ pPtAry++;
+
+ for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++)
+ {
+ alignLinePoint(pPtAry, fX, fY);
+ CGContextAddLineToPoint(mrShared.maContextHolder.get(), fX, fY);
+ }
+ CGContextClosePath(mrShared.maContextHolder.get());
+ }
+ }
+ }
+ else
+ {
+ for (sal_uInt32 nPoly = 0; nPoly < nPolyCount; nPoly++)
+ {
+ const sal_uInt32 nPoints = pPoints[nPoly];
+ if (nPoints > 1)
+ {
+ const Point* pPtAry = ppPtAry[nPoly];
+ CGContextMoveToPoint(mrShared.maContextHolder.get(), pPtAry->getX(),
+ pPtAry->getY());
+ pPtAry++;
+ for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++)
+ {
+ CGContextAddLineToPoint(mrShared.maContextHolder.get(), pPtAry->getX(),
+ pPtAry->getY());
+ }
+ CGContextClosePath(mrShared.maContextHolder.get());
+ }
+ }
+ }
+
+ CGContextDrawPath(mrShared.maContextHolder.get(), eMode);
+
+ mrShared.refreshRect(leftX, topY, maxWidth, maxHeight);
+}
+
+void AquaGraphicsBackend::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+#ifdef IOS
+ if (!mrShared.maContextHolder.isSet())
+ return;
+#endif
+
+ // short circuit if there is nothing to do
+ if (rPolyPolygon.count() == 0)
+ return;
+
+ // ignore invisible polygons
+ if ((fTransparency >= 1.0) || (fTransparency < 0))
+ return;
+
+ // Fallback: Transform to DeviceCoordinates
+ basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
+ aPolyPolygon.transform(rObjectToDevice);
+
+ // setup poly-polygon path
+ CGMutablePathRef xPath = CGPathCreateMutable();
+ // tdf#120252 Use the correct, already transformed PolyPolygon (as long as
+ // the transformation is not used here...)
+ for (auto const& rPolygon : std::as_const(aPolyPolygon))
+ {
+ AddPolygonToPath(xPath, rPolygon, true, !getAntiAlias(), mrShared.isPenVisible());
+ }
+
+ const CGRect aRefreshRect = CGPathGetBoundingBox(xPath);
+ // #i97317# workaround for Quartz having problems with drawing small polygons
+ if (aRefreshRect.size.width > 0.125 || aRefreshRect.size.height > 0.125)
+ {
+ // prepare drawing mode
+ CGPathDrawingMode eMode;
+ if (mrShared.isBrushVisible() && mrShared.isPenVisible())
+ {
+ eMode = kCGPathEOFillStroke;
+ }
+ else if (mrShared.isPenVisible())
+ {
+ eMode = kCGPathStroke;
+ }
+ else if (mrShared.isBrushVisible())
+ {
+ eMode = kCGPathEOFill;
+ }
+ else
+ {
+ SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
+ CGPathRelease(xPath);
+ return;
+ }
+
+ // use the path to prepare the graphics context
+ mrShared.maContextHolder.saveState();
+ CGContextBeginPath(mrShared.maContextHolder.get());
+ CGContextAddPath(mrShared.maContextHolder.get(), xPath);
+
+ // draw path with antialiased polygon
+ CGContextSetShouldAntialias(mrShared.maContextHolder.get(), getAntiAlias());
+ CGContextSetAlpha(mrShared.maContextHolder.get(), 1.0 - fTransparency);
+ CGContextDrawPath(mrShared.maContextHolder.get(), eMode);
+ mrShared.maContextHolder.restoreState();
+
+ // mark modified rectangle as updated
+ refreshRect(aRefreshRect);
+ }
+
+ CGPathRelease(xPath);
+}
+
+bool AquaGraphicsBackend::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& rPolyLine, double fTransparency,
+ double fLineWidth,
+ const std::vector<double>* pStroke, // MM01
+ basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
+ bool bPixelSnapHairline)
+{
+ // MM01 check done for simple reasons
+ if (!rPolyLine.count() || fTransparency < 0.0 || fTransparency > 1.0)
+ {
+ return true;
+ }
+
+#ifdef IOS
+ if (!mrShared.checkContext())
+ return false;
+#endif
+
+ // tdf#124848 get correct LineWidth in discrete coordinates,
+ if (fLineWidth == 0) // hairline
+ fLineWidth = 1.0;
+ else // Adjust line width for object-to-device scale.
+ fLineWidth = (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
+
+ // #i101491# Aqua does not support B2DLineJoin::NONE; return false to use
+ // the fallback (own geometry preparation)
+ // #i104886# linejoin-mode and thus the above only applies to "fat" lines
+ if ((basegfx::B2DLineJoin::NONE == eLineJoin) && (fLineWidth > 1.3))
+ return false;
+
+ // MM01 need to do line dashing as fallback stuff here now
+ const double fDotDashLength(
+ nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0);
+ const bool bStrokeUsed(0.0 != fDotDashLength);
+ assert(!bStrokeUsed || (bStrokeUsed && pStroke));
+ basegfx::B2DPolyPolygon aPolyPolygonLine;
+
+ if (bStrokeUsed)
+ {
+ // apply LineStyle
+ basegfx::utils::applyLineDashing(rPolyLine, // source
+ *pStroke, // pattern
+ &aPolyPolygonLine, // target for lines
+ nullptr, // target for gaps
+ fDotDashLength); // full length if available
+ }
+ else
+ {
+ // no line dashing, just copy
+ aPolyPolygonLine.append(rPolyLine);
+ }
+
+ // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
+ aPolyPolygonLine.transform(rObjectToDevice);
+ if (bPixelSnapHairline)
+ {
+ aPolyPolygonLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygonLine);
+ }
+
+ // setup line attributes
+ CGLineJoin aCGLineJoin = kCGLineJoinMiter;
+ switch (eLineJoin)
+ {
+ case basegfx::B2DLineJoin::NONE:
+ aCGLineJoin = /*TODO?*/ kCGLineJoinMiter;
+ break;
+ case basegfx::B2DLineJoin::Bevel:
+ aCGLineJoin = kCGLineJoinBevel;
+ break;
+ case basegfx::B2DLineJoin::Miter:
+ aCGLineJoin = kCGLineJoinMiter;
+ break;
+ case basegfx::B2DLineJoin::Round:
+ aCGLineJoin = kCGLineJoinRound;
+ break;
+ }
+ // convert miter minimum angle to miter limit
+ CGFloat fCGMiterLimit = 1.0 / sin(std::max(fMiterMinimumAngle, 0.01 * M_PI) / 2.0);
+ // setup cap attribute
+ CGLineCap aCGLineCap(kCGLineCapButt);
+
+ switch (eLineCap)
+ {
+ default: // css::drawing::LineCap_BUTT:
+ {
+ aCGLineCap = kCGLineCapButt;
+ break;
+ }
+ case css::drawing::LineCap_ROUND:
+ {
+ aCGLineCap = kCGLineCapRound;
+ break;
+ }
+ case css::drawing::LineCap_SQUARE:
+ {
+ aCGLineCap = kCGLineCapSquare;
+ break;
+ }
+ }
+
+ // setup poly-polygon path
+ CGMutablePathRef xPath = CGPathCreateMutable();
+
+ // MM01 todo - I assume that this is OKAY to be done in one run for quartz
+ // but this NEEDS to be checked/verified
+ for (sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
+ {
+ const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
+ AddPolygonToPath(xPath, aPolyLine, aPolyLine.isClosed(), !getAntiAlias(), true);
+ }
+
+ const CGRect aRefreshRect = CGPathGetBoundingBox(xPath);
+ // #i97317# workaround for Quartz having problems with drawing small polygons
+ if ((aRefreshRect.size.width > 0.125) || (aRefreshRect.size.height > 0.125))
+ {
+ // use the path to prepare the graphics context
+ mrShared.maContextHolder.saveState();
+ CGContextBeginPath(mrShared.maContextHolder.get());
+ CGContextAddPath(mrShared.maContextHolder.get(), xPath);
+ // draw path with antialiased line
+ CGContextSetShouldAntialias(mrShared.maContextHolder.get(), getAntiAlias());
+ CGContextSetAlpha(mrShared.maContextHolder.get(), 1.0 - fTransparency);
+ CGContextSetLineJoin(mrShared.maContextHolder.get(), aCGLineJoin);
+ CGContextSetLineCap(mrShared.maContextHolder.get(), aCGLineCap);
+ CGContextSetLineWidth(mrShared.maContextHolder.get(), fLineWidth);
+ CGContextSetMiterLimit(mrShared.maContextHolder.get(), fCGMiterLimit);
+ CGContextDrawPath(mrShared.maContextHolder.get(), kCGPathStroke);
+ mrShared.maContextHolder.restoreState();
+
+ // mark modified rectangle as updated
+ refreshRect(aRefreshRect);
+ }
+
+ CGPathRelease(xPath);
+
+ return true;
+}
+
+bool AquaGraphicsBackend::drawPolyLineBezier(sal_uInt32 /*nPoints*/, const Point* /*pPointArray*/,
+ const PolyFlags* /*pFlagArray*/)
+{
+ return false;
+}
+
+bool AquaGraphicsBackend::drawPolygonBezier(sal_uInt32 /*nPoints*/, const Point* /*pPointArray*/,
+ const PolyFlags* /*pFlagArray*/)
+{
+ return false;
+}
+
+bool AquaGraphicsBackend::drawPolyPolygonBezier(sal_uInt32 /*nPoly*/, const sal_uInt32* /*pPoints*/,
+ const Point* const* /*pPointArray*/,
+ const PolyFlags* const* /*pFlagArray*/)
+{
+ return false;
+}
+
+void AquaGraphicsBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
+{
+ if (!mrShared.checkContext())
+ return;
+
+ CGImageRef xImage = rSalBitmap.CreateCroppedImage(
+ static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
+ static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight));
+ if (!xImage)
+ return;
+
+ const CGRect aDstRect
+ = CGRectMake(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight);
+ CGContextDrawImage(mrShared.maContextHolder.get(), aDstRect, xImage);
+
+ CGImageRelease(xImage);
+ refreshRect(aDstRect);
+}
+
+void AquaGraphicsBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransparentBitmap)
+{
+ if (!mrShared.checkContext())
+ return;
+
+ CGImageRef xMaskedImage(rSalBitmap.CreateWithMask(rTransparentBitmap, rPosAry.mnSrcX,
+ rPosAry.mnSrcY, rPosAry.mnSrcWidth,
+ rPosAry.mnSrcHeight));
+ if (!xMaskedImage)
+ return;
+
+ const CGRect aDstRect
+ = CGRectMake(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight);
+ CGContextDrawImage(mrShared.maContextHolder.get(), aDstRect, xMaskedImage);
+ CGImageRelease(xMaskedImage);
+ refreshRect(aDstRect);
+}
+
+void AquaGraphicsBackend::drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ Color nMaskColor)
+{
+ if (!mrShared.checkContext())
+ return;
+
+ CGImageRef xImage = rSalBitmap.CreateColorMask(
+ rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, nMaskColor);
+ if (!xImage)
+ return;
+
+ const CGRect aDstRect
+ = CGRectMake(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight);
+ CGContextDrawImage(mrShared.maContextHolder.get(), aDstRect, xImage);
+ CGImageRelease(xImage);
+ refreshRect(aDstRect);
+}
+
+std::shared_ptr<SalBitmap> AquaGraphicsBackend::getBitmap(tools::Long nX, tools::Long nY,
+ tools::Long nDX, tools::Long nDY)
+{
+ SAL_WARN_IF(!mrShared.maLayer.isSet(), "vcl.quartz",
+ "AquaSalGraphics::getBitmap() with no layer this=" << this);
+
+ mrShared.applyXorContext();
+
+ std::shared_ptr<QuartzSalBitmap> pBitmap = std::make_shared<QuartzSalBitmap>();
+ if (!pBitmap->Create(mrShared.maLayer, mrShared.mnBitmapDepth, nX, nY, nDX, nDY,
+ mrShared.isFlipped()))
+ {
+ pBitmap = nullptr;
+ }
+ return pBitmap;
+}
+
+Color AquaGraphicsBackend::getPixel(tools::Long nX, tools::Long nY)
+{
+ // return default value on printers or when out of bounds
+ if (!mrShared.maLayer.isSet() || (nX < 0) || (nX >= mrShared.mnWidth) || (nY < 0)
+ || (nY >= mrShared.mnHeight))
+ {
+ return COL_BLACK;
+ }
+
+ // prepare creation of matching a CGBitmapContext
+#if defined OSL_BIGENDIAN
+ struct
+ {
+ unsigned char b, g, r, a;
+ } aPixel;
+#else
+ struct
+ {
+ unsigned char a, r, g, b;
+ } aPixel;
+#endif
+
+ // create a one-pixel bitmap context
+ // TODO: is it worth to cache it?
+ CGContextRef xOnePixelContext = CGBitmapContextCreate(
+ &aPixel, 1, 1, 8, 32, GetSalData()->mxRGBSpace,
+ uint32_t(kCGImageAlphaNoneSkipFirst) | uint32_t(kCGBitmapByteOrder32Big));
+
+ // update this graphics layer
+ mrShared.applyXorContext();
+
+ // copy the requested pixel into the bitmap context
+ if (mrShared.isFlipped())
+ {
+ nY = mrShared.mnHeight - nY;
+ }
+ const CGPoint aCGPoint = CGPointMake(-nX, -nY);
+ CGContextDrawLayerAtPoint(xOnePixelContext, aCGPoint, mrShared.maLayer.get());
+
+ CGContextRelease(xOnePixelContext);
+
+ Color nColor(aPixel.r, aPixel.g, aPixel.b);
+ return nColor;
+}
+
+void AquaSalGraphics::GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY)
+{
+#ifndef IOS
+ if (!mnRealDPIY)
+ {
+ initResolution((maShared.mbWindow && maShared.mpFrame) ? maShared.mpFrame->getNSWindow()
+ : nil);
+ }
+
+ rDPIX = mnRealDPIX;
+ rDPIY = mnRealDPIY;
+#else
+ // This *must* be 96 or else the iOS app will behave very badly (tiles are scaled wrongly and
+ // don't match each others at their boundaries, and other issues). But *why* it must be 96 I
+ // have no idea. The commit that changed it to 96 from (the arbitrary) 200 did not say. If you
+ // know where else 96 is explicitly or implicitly hard-coded, please modify this comment.
+
+ // Follow-up: It might be this: in 'online', loleaflet/src/map/Map.js:
+ // 15 = 1440 twips-per-inch / 96 dpi.
+ // Chosen to match previous hardcoded value of 3840 for
+ // the current tile pixel size of 256.
+ rDPIX = rDPIY = 96;
+#endif
+}
+
+void AquaGraphicsBackend::pattern50Fill()
+{
+ static const CGFloat aFillCol[4] = { 1, 1, 1, 1 };
+ static const CGPatternCallbacks aCallback = { 0, &drawPattern50, nullptr };
+ static const CGColorSpaceRef mxP50Space = CGColorSpaceCreatePattern(GetSalData()->mxRGBSpace);
+ static const CGPatternRef mxP50Pattern
+ = CGPatternCreate(nullptr, CGRectMake(0, 0, 4, 4), CGAffineTransformIdentity, 4, 4,
+ kCGPatternTilingConstantSpacing, false, &aCallback);
+ SAL_WARN_IF(!mrShared.maContextHolder.get(), "vcl.quartz", "maContextHolder.get() is NULL");
+ CGContextSetFillColorSpace(mrShared.maContextHolder.get(), mxP50Space);
+ CGContextSetFillPattern(mrShared.maContextHolder.get(), mxP50Pattern, aFillCol);
+ CGContextFillPath(mrShared.maContextHolder.get());
+}
+
+void AquaGraphicsBackend::invert(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, SalInvert nFlags)
+{
+ if (mrShared.checkContext())
+ {
+ CGRect aCGRect = CGRectMake(nX, nY, nWidth, nHeight);
+ mrShared.maContextHolder.saveState();
+ if (nFlags & SalInvert::TrackFrame)
+ {
+ const CGFloat dashLengths[2] = { 4.0, 4.0 }; // for drawing dashed line
+ CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference);
+ CGContextSetRGBStrokeColor(mrShared.maContextHolder.get(), 1.0, 1.0, 1.0, 1.0);
+ CGContextSetLineDash(mrShared.maContextHolder.get(), 0, dashLengths, 2);
+ CGContextSetLineWidth(mrShared.maContextHolder.get(), 2.0);
+ CGContextStrokeRect(mrShared.maContextHolder.get(), aCGRect);
+ }
+ else if (nFlags & SalInvert::N50)
+ {
+ //CGContextSetAllowsAntialiasing( maContextHolder.get(), false );
+ CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference);
+ CGContextAddRect(mrShared.maContextHolder.get(), aCGRect);
+ pattern50Fill();
+ }
+ else // just invert
+ {
+ CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference);
+ CGContextSetRGBFillColor(mrShared.maContextHolder.get(), 1.0, 1.0, 1.0, 1.0);
+ CGContextFillRect(mrShared.maContextHolder.get(), aCGRect);
+ }
+ mrShared.maContextHolder.restoreState();
+ refreshRect(aCGRect);
+ }
+}
+
+namespace
+{
+CGPoint* makeCGptArray(sal_uInt32 nPoints, const Point* pPtAry)
+{
+ CGPoint* CGpoints = new CGPoint[nPoints];
+ for (sal_uLong i = 0; i < nPoints; i++)
+ {
+ CGpoints[i].x = pPtAry[i].getX();
+ CGpoints[i].y = pPtAry[i].getY();
+ }
+ return CGpoints;
+}
+
+} // end anonymous ns
+
+void AquaGraphicsBackend::invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nSalFlags)
+{
+ if (mrShared.checkContext())
+ {
+ mrShared.maContextHolder.saveState();
+ CGPoint* CGpoints = makeCGptArray(nPoints, pPtAry);
+ CGContextAddLines(mrShared.maContextHolder.get(), CGpoints, nPoints);
+ if (nSalFlags & SalInvert::TrackFrame)
+ {
+ const CGFloat dashLengths[2] = { 4.0, 4.0 }; // for drawing dashed line
+ CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference);
+ CGContextSetRGBStrokeColor(mrShared.maContextHolder.get(), 1.0, 1.0, 1.0, 1.0);
+ CGContextSetLineDash(mrShared.maContextHolder.get(), 0, dashLengths, 2);
+ CGContextSetLineWidth(mrShared.maContextHolder.get(), 2.0);
+ CGContextStrokePath(mrShared.maContextHolder.get());
+ }
+ else if (nSalFlags & SalInvert::N50)
+ {
+ CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference);
+ pattern50Fill();
+ }
+ else // just invert
+ {
+ CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference);
+ CGContextSetRGBFillColor(mrShared.maContextHolder.get(), 1.0, 1.0, 1.0, 1.0);
+ CGContextFillPath(mrShared.maContextHolder.get());
+ }
+ const CGRect aRefreshRect = CGContextGetClipBoundingBox(mrShared.maContextHolder.get());
+ mrShared.maContextHolder.restoreState();
+ delete[] CGpoints;
+ refreshRect(aRefreshRect);
+ }
+}
+
+#ifndef IOS
+bool AquaGraphicsBackend::drawEPS(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, void* pEpsData, sal_uInt32 nByteCount)
+{
+ // convert the raw data to an NSImageRef
+ NSData* xNSData = [NSData dataWithBytes:pEpsData length:static_cast<int>(nByteCount)];
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // 'NSEPSImageRep' is deprecated: first deprecated in macOS 14.0 - `NSEPSImageRep` instances
+ // cannot be created on macOS 14.0 and later
+ NSImageRep* xEpsImage = [NSEPSImageRep imageRepWithData:xNSData];
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ if (!xEpsImage)
+ {
+ return false;
+ }
+ // get the target context
+ if (!mrShared.checkContext())
+ {
+ return false;
+ }
+ // NOTE: flip drawing, else the nsimage would be drawn upside down
+ mrShared.maContextHolder.saveState();
+ // CGContextTranslateCTM( maContextHolder.get(), 0, +mnHeight );
+ CGContextScaleCTM(mrShared.maContextHolder.get(), +1, -1);
+ nY = /*mnHeight*/ -(nY + nHeight);
+
+ // prepare the target context
+ NSGraphicsContext* pOrigNSCtx = [NSGraphicsContext currentContext];
+ [pOrigNSCtx retain];
+
+ // create new context
+ NSGraphicsContext* pDrawNSCtx =
+ [NSGraphicsContext graphicsContextWithCGContext:mrShared.maContextHolder.get()
+ flipped:mrShared.isFlipped()];
+ // set it, setCurrentContext also releases the previously set one
+ [NSGraphicsContext setCurrentContext:pDrawNSCtx];
+
+ // draw the EPS
+ const NSRect aDstRect = NSMakeRect(nX, nY, nWidth, nHeight);
+ const bool bOK = [xEpsImage drawInRect:aDstRect];
+
+ // restore the NSGraphicsContext
+ [NSGraphicsContext setCurrentContext:pOrigNSCtx];
+ [pOrigNSCtx release]; // restore the original retain count
+
+ mrShared.maContextHolder.restoreState();
+ // mark the destination rectangle as updated
+ refreshRect(aDstRect);
+
+ return bOK;
+}
+#else
+bool AquaGraphicsBackend::drawEPS(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/,
+ tools::Long /*nHeight*/, void* /*pEpsData*/,
+ sal_uInt32 /*nByteCount*/)
+{
+ return false;
+}
+#endif
+
+bool AquaGraphicsBackend::blendBitmap(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rBitmap*/)
+{
+ return false;
+}
+
+bool AquaGraphicsBackend::blendAlphaBitmap(const SalTwoRect& /*rPosAry*/,
+ const SalBitmap& /*rSrcBitmap*/,
+ const SalBitmap& /*rMaskBitmap*/,
+ const SalBitmap& /*rAlphaBitmap*/)
+{
+ return false;
+}
+
+bool AquaGraphicsBackend::drawAlphaBitmap(const SalTwoRect& rTR, const SalBitmap& rSrcBitmap,
+ const SalBitmap& rAlphaBmp)
+{
+ // An image mask can't have a depth > 8 bits (should be 1 to 8 bits)
+ if (rAlphaBmp.GetBitCount() > 8)
+ return false;
+
+ // are these two tests really necessary? (see vcl/unx/source/gdi/salgdi2.cxx)
+ // horizontal/vertical mirroring not implemented yet
+ if (rTR.mnDestWidth < 0 || rTR.mnDestHeight < 0)
+ return false;
+
+ CGImageRef xMaskedImage = rSrcBitmap.CreateWithMask(rAlphaBmp, rTR.mnSrcX, rTR.mnSrcY,
+ rTR.mnSrcWidth, rTR.mnSrcHeight);
+ if (!xMaskedImage)
+ return false;
+
+ if (mrShared.checkContext())
+ {
+ const CGRect aDstRect
+ = CGRectMake(rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
+ CGContextDrawImage(mrShared.maContextHolder.get(), aDstRect, xMaskedImage);
+ refreshRect(aDstRect);
+ }
+
+ CGImageRelease(xMaskedImage);
+
+ return true;
+}
+
+bool AquaGraphicsBackend::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSrcBitmap,
+ const SalBitmap* pAlphaBmp, double fAlpha)
+{
+ if (!mrShared.checkContext())
+ return true;
+
+ if (fAlpha != 1.0)
+ return false;
+
+ // get the Quartz image
+ CGImageRef xImage = nullptr;
+ const Size aSize = rSrcBitmap.GetSize();
+
+ if (!pAlphaBmp)
+ xImage = rSrcBitmap.CreateCroppedImage(0, 0, int(aSize.Width()), int(aSize.Height()));
+ else
+ xImage
+ = rSrcBitmap.CreateWithMask(*pAlphaBmp, 0, 0, int(aSize.Width()), int(aSize.Height()));
+
+ if (!xImage)
+ return false;
+
+ // setup the image transformation
+ // using the rNull,rX,rY points as destinations for the (0,0),(0,Width),(Height,0) source points
+ mrShared.maContextHolder.saveState();
+ const basegfx::B2DVector aXRel = rX - rNull;
+ const basegfx::B2DVector aYRel = rY - rNull;
+ const CGAffineTransform aCGMat = CGAffineTransformMake(
+ aXRel.getX() / aSize.Width(), aXRel.getY() / aSize.Width(), aYRel.getX() / aSize.Height(),
+ aYRel.getY() / aSize.Height(), rNull.getX(), rNull.getY());
+
+ CGContextConcatCTM(mrShared.maContextHolder.get(), aCGMat);
+
+ // draw the transformed image
+ const CGRect aSrcRect = CGRectMake(0, 0, aSize.Width(), aSize.Height());
+ CGContextDrawImage(mrShared.maContextHolder.get(), aSrcRect, xImage);
+
+ CGImageRelease(xImage);
+
+ // restore the Quartz graphics state
+ mrShared.maContextHolder.restoreState();
+
+ // mark the destination as painted
+ const CGRect aDstRect = CGRectApplyAffineTransform(aSrcRect, aCGMat);
+ refreshRect(aDstRect);
+
+ return true;
+}
+
+bool AquaGraphicsBackend::hasFastDrawTransformedBitmap() const { return false; }
+
+bool AquaGraphicsBackend::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, sal_uInt8 nTransparency)
+{
+ if (!mrShared.checkContext())
+ return true;
+
+ // save the current state
+ mrShared.maContextHolder.saveState();
+ CGContextSetAlpha(mrShared.maContextHolder.get(), (100 - nTransparency) * (1.0 / 100));
+
+ CGRect aRect = CGRectMake(nX, nY, nWidth - 1, nHeight - 1);
+ if (mrShared.isPenVisible())
+ {
+ aRect.origin.x += 0.5;
+ aRect.origin.y += 0.5;
+ }
+
+ CGContextBeginPath(mrShared.maContextHolder.get());
+ CGContextAddRect(mrShared.maContextHolder.get(), aRect);
+ CGContextDrawPath(mrShared.maContextHolder.get(), kCGPathFill);
+
+ mrShared.maContextHolder.restoreState();
+ refreshRect(aRect);
+
+ return true;
+}
+
+bool AquaGraphicsBackend::drawGradient(const tools::PolyPolygon& /*rPolygon*/,
+ const Gradient& /*rGradient*/)
+{
+ return false;
+}
+
+bool AquaGraphicsBackend::implDrawGradient(basegfx::B2DPolyPolygon const& /*rPolyPolygon*/,
+ SalGradient const& /*rGradient*/)
+{
+ return false;
+}
+
+bool AquaGraphicsBackend::supportsOperation(OutDevSupportType eType) const
+{
+ switch (eType)
+ {
+ case OutDevSupportType::TransparentRect:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/CoreTextFont.cxx b/vcl/quartz/CoreTextFont.cxx
new file mode 100644
index 0000000000..6248a255c6
--- /dev/null
+++ b/vcl/quartz/CoreTextFont.cxx
@@ -0,0 +1,235 @@
+/* -*- 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 <sal/config.h>
+#include <sal/log.hxx>
+
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+#ifdef MACOSX
+#include <osx/saldata.hxx>
+#include <osx/salinst.h>
+#endif
+#include <font/LogicalFontInstance.hxx>
+#include <impglyphitem.hxx>
+#include <quartz/CoreTextFont.hxx>
+#include <quartz/CoreTextFontFace.hxx>
+#include <quartz/salgdi.h>
+#include <quartz/utils.h>
+#include <hb.h>
+
+CoreTextFont::CoreTextFont(const CoreTextFontFace& rPFF, const vcl::font::FontSelectPattern& rFSP)
+ : LogicalFontInstance(rPFF, rFSP)
+ , mfFontStretch(1.0)
+ , mfFontRotation(0.0)
+ , mpCTFont(nullptr)
+{
+ double fScaledFontHeight = rFSP.mfExactHeight;
+
+ // convert font rotation to radian
+ mfFontRotation = toRadians(rFSP.mnOrientation);
+
+ // dummy matrix so we can use CGAffineTransformConcat() below
+ CGAffineTransform aMatrix = CGAffineTransformMakeTranslation(0, 0);
+
+ // handle font stretching if any
+ if ((rFSP.mnWidth != 0) && (rFSP.mnWidth != rFSP.mnHeight))
+ {
+ mfFontStretch = float(rFSP.mnWidth) / rFSP.mnHeight;
+ aMatrix = CGAffineTransformConcat(aMatrix, CGAffineTransformMakeScale(mfFontStretch, 1.0F));
+ }
+
+ // artificial italic
+ if (NeedsArtificialItalic())
+ aMatrix = CGAffineTransformConcat(
+ aMatrix, CGAffineTransformMake(1, 0, ARTIFICIAL_ITALIC_SKEW, 1, 0, 0));
+
+ CTFontDescriptorRef pFontDesc = rPFF.GetFontDescriptorRef();
+ mpCTFont = CTFontCreateWithFontDescriptor(pFontDesc, fScaledFontHeight, &aMatrix);
+}
+
+CoreTextFont::~CoreTextFont()
+{
+ if (mpCTFont)
+ CFRelease(mpCTFont);
+}
+
+void CoreTextFont::GetFontMetric(FontMetricDataRef const& rxFontMetric)
+{
+ rxFontMetric->ImplCalcLineSpacing(this);
+ rxFontMetric->ImplInitBaselines(this);
+
+ // since FontMetricData::mnWidth is only used for stretching/squeezing fonts
+ // setting this width to the pixel height of the fontsize is good enough
+ // it also makes the calculation of the stretch factor simple
+ rxFontMetric->SetWidth(lrint(CTFontGetSize(mpCTFont) * mfFontStretch));
+
+ rxFontMetric->SetMinKashida(GetKashidaWidth());
+}
+
+namespace
+{
+// callbacks from CTFontCreatePathForGlyph+CGPathApply for GetGlyphOutline()
+struct GgoData
+{
+ basegfx::B2DPolygon maPolygon;
+ basegfx::B2DPolyPolygon* mpPolyPoly;
+};
+}
+
+static void MyCGPathApplierFunc(void* pData, const CGPathElement* pElement)
+{
+ basegfx::B2DPolygon& rPolygon = static_cast<GgoData*>(pData)->maPolygon;
+ const int nPointCount = rPolygon.count();
+
+ switch (pElement->type)
+ {
+ case kCGPathElementCloseSubpath:
+ case kCGPathElementMoveToPoint:
+ if (nPointCount > 0)
+ {
+ static_cast<GgoData*>(pData)->mpPolyPoly->append(rPolygon);
+ rPolygon.clear();
+ }
+ // fall through for kCGPathElementMoveToPoint:
+ if (pElement->type != kCGPathElementMoveToPoint)
+ {
+ break;
+ }
+ [[fallthrough]];
+ case kCGPathElementAddLineToPoint:
+ rPolygon.append(basegfx::B2DPoint(+pElement->points[0].x, -pElement->points[0].y));
+ break;
+
+ case kCGPathElementAddCurveToPoint:
+ rPolygon.append(basegfx::B2DPoint(+pElement->points[2].x, -pElement->points[2].y));
+ rPolygon.setNextControlPoint(
+ nPointCount - 1, basegfx::B2DPoint(pElement->points[0].x, -pElement->points[0].y));
+ rPolygon.setPrevControlPoint(
+ nPointCount + 0, basegfx::B2DPoint(pElement->points[1].x, -pElement->points[1].y));
+ break;
+
+ case kCGPathElementAddQuadCurveToPoint:
+ {
+ const basegfx::B2DPoint aStartPt = rPolygon.getB2DPoint(nPointCount - 1);
+ const basegfx::B2DPoint aCtrPt1((aStartPt.getX() + 2 * pElement->points[0].x) / 3.0,
+ (aStartPt.getY() - 2 * pElement->points[0].y) / 3.0);
+ const basegfx::B2DPoint aCtrPt2(
+ (+2 * pElement->points[0].x + pElement->points[1].x) / 3.0,
+ (-2 * pElement->points[0].y - pElement->points[1].y) / 3.0);
+ rPolygon.append(basegfx::B2DPoint(+pElement->points[1].x, -pElement->points[1].y));
+ rPolygon.setNextControlPoint(nPointCount - 1, aCtrPt1);
+ rPolygon.setPrevControlPoint(nPointCount + 0, aCtrPt2);
+ }
+ break;
+ }
+}
+
+bool CoreTextFont::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rResult, bool) const
+{
+ rResult.clear();
+
+ CGGlyph nCGGlyph = nId;
+
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ const CTFontOrientation aFontOrientation = kCTFontDefaultOrientation;
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ CGRect aCGRect
+ = CTFontGetBoundingRectsForGlyphs(mpCTFont, aFontOrientation, &nCGGlyph, nullptr, 1);
+
+ if (!CGRectIsNull(aCGRect) && CGRectIsEmpty(aCGRect))
+ {
+ // CTFontCreatePathForGlyph returns NULL for blank glyphs, but we want
+ // to return true for them.
+ return true;
+ }
+
+ CGPathRef xPath = CTFontCreatePathForGlyph(mpCTFont, nCGGlyph, nullptr);
+ if (!xPath)
+ {
+ return false;
+ }
+
+ GgoData aGgoData;
+ aGgoData.mpPolyPoly = &rResult;
+ CGPathApply(xPath, static_cast<void*>(&aGgoData), MyCGPathApplierFunc);
+#if 0 // TODO: does OSX ensure that the last polygon is always closed?
+ const CGPathElement aClosingElement = { kCGPathElementCloseSubpath, NULL };
+ MyCGPathApplierFunc( (void*)&aGgoData, &aClosingElement );
+#endif
+ CFRelease(xPath);
+
+ return true;
+}
+
+hb_blob_t* CoreTextFontFace::GetHbTable(hb_tag_t nTag) const
+{
+ hb_blob_t* pBlob = nullptr;
+ CTFontRef pFont = CTFontCreateWithFontDescriptor(mxFontDescriptor, 0.0, nullptr);
+
+ if (!nTag)
+ {
+ // If nTag is 0, the whole font data is requested. CoreText does not
+ // give us that, so we will construct an HarfBuzz face from CoreText
+ // table data and return the blob of that face.
+
+ auto pTags = CTFontCopyAvailableTables(pFont, kCTFontTableOptionNoOptions);
+ CFIndex nTags = pTags ? CFArrayGetCount(pTags) : 0;
+ if (nTags > 0)
+ {
+ hb_face_t* pHbFace = hb_face_builder_create();
+ for (CFIndex i = 0; i < nTags; i++)
+ {
+ auto nTable = reinterpret_cast<intptr_t>(CFArrayGetValueAtIndex(pTags, i));
+ assert(nTable);
+ auto pTable = GetHbTable(nTable);
+ assert(pTable);
+ hb_face_builder_add_table(pHbFace, nTable, pTable);
+ }
+ pBlob = hb_face_reference_blob(pHbFace);
+
+ hb_face_destroy(pHbFace);
+ }
+ if (pTags)
+ CFRelease(pTags);
+ }
+ else
+ {
+ CFDataRef pData = CTFontCopyTable(pFont, nTag, kCTFontTableOptionNoOptions);
+ const CFIndex nLength = pData ? CFDataGetLength(pData) : 0;
+ if (nLength > 0)
+ {
+ auto pBuffer = new UInt8[nLength];
+ const CFRange aRange = CFRangeMake(0, nLength);
+ CFDataGetBytes(pData, aRange, pBuffer);
+
+ pBlob = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength,
+ HB_MEMORY_MODE_READONLY, pBuffer,
+ [](void* data) { delete[] static_cast<UInt8*>(data); });
+ }
+ if (pData)
+ CFRelease(pData);
+ }
+
+ CFRelease(pFont);
+ return pBlob;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/CoreTextFontFace.cxx b/vcl/quartz/CoreTextFontFace.cxx
new file mode 100644
index 0000000000..662b439a9e
--- /dev/null
+++ b/vcl/quartz/CoreTextFontFace.cxx
@@ -0,0 +1,98 @@
+/* -*- 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 <sal/config.h>
+#include <sal/log.hxx>
+
+#ifdef MACOSX
+#include <osx/saldata.hxx>
+#include <osx/salinst.h>
+#endif
+#include <font/LogicalFontInstance.hxx>
+#include <quartz/CoreTextFont.hxx>
+#include <quartz/CoreTextFontFace.hxx>
+#include <quartz/salgdi.h>
+#include <quartz/utils.h>
+
+CoreTextFontFace::CoreTextFontFace(const FontAttributes& rDFA, CTFontDescriptorRef xFontDescriptor)
+ : vcl::font::PhysicalFontFace(rDFA)
+ , mxFontDescriptor(xFontDescriptor)
+{
+ CFRetain(mxFontDescriptor);
+}
+
+CoreTextFontFace::~CoreTextFontFace() { CFRelease(mxFontDescriptor); }
+
+sal_IntPtr CoreTextFontFace::GetFontId() const
+{
+ return reinterpret_cast<sal_IntPtr>(mxFontDescriptor);
+}
+
+const std::vector<hb_variation_t>& CoreTextFontFace::GetVariations(const LogicalFontInstance&) const
+{
+ CTFontRef pFont = CTFontCreateWithFontDescriptor(mxFontDescriptor, 0.0, nullptr);
+
+ if (!mxVariations)
+ {
+ mxVariations.emplace();
+ CFArrayRef pAxes = CTFontCopyVariationAxes(pFont);
+ if (pAxes)
+ {
+ CFDictionaryRef pVariations = CTFontCopyVariation(pFont);
+ if (pVariations)
+ {
+ CFIndex nAxes = CFArrayGetCount(pAxes);
+ for (CFIndex i = 0; i < nAxes; ++i)
+ {
+ auto pAxis = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(pAxes, i));
+ if (pAxis)
+ {
+ hb_tag_t nTag;
+ auto pTag = static_cast<CFNumberRef>(
+ CFDictionaryGetValue(pAxis, kCTFontVariationAxisIdentifierKey));
+ if (!pTag)
+ continue;
+ CFNumberGetValue(pTag, kCFNumberIntType, &nTag);
+
+ float fValue;
+ auto pValue
+ = static_cast<CFNumberRef>(CFDictionaryGetValue(pVariations, pTag));
+ if (!pValue)
+ continue;
+ CFNumberGetValue(pValue, kCFNumberFloatType, &fValue);
+
+ mxVariations->push_back({ nTag, fValue });
+ }
+ }
+ CFRelease(pVariations);
+ }
+ CFRelease(pAxes);
+ }
+ }
+
+ return *mxVariations;
+}
+
+rtl::Reference<LogicalFontInstance>
+CoreTextFontFace::CreateFontInstance(const vcl::font::FontSelectPattern& rFSD) const
+{
+ return new CoreTextFont(*this, rFSD);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/SystemFontList.cxx b/vcl/quartz/SystemFontList.cxx
new file mode 100644
index 0000000000..e068caf80f
--- /dev/null
+++ b/vcl/quartz/SystemFontList.cxx
@@ -0,0 +1,301 @@
+/* -*- 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 <sal/config.h>
+#include <sal/log.hxx>
+
+#include <tools/long.hxx>
+
+
+#include <quartz/SystemFontList.hxx>
+#include <impfont.hxx>
+#ifdef MACOSX
+#include <osx/saldata.hxx>
+#include <osx/salinst.h>
+#endif
+#include <fontattributes.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <quartz/CoreTextFontFace.hxx>
+#include <quartz/salgdi.h>
+#include <quartz/utils.h>
+#include <sallayout.hxx>
+
+
+FontAttributes DevFontFromCTFontDescriptor( CTFontDescriptorRef pFD, bool* bFontEnabled )
+{
+ // all CoreText fonts are device fonts that can rotate just fine
+ FontAttributes rDFA;
+ rDFA.SetQuality( 0 );
+
+ // reset the font attributes
+ rDFA.SetFamilyType( FAMILY_DONTKNOW );
+ rDFA.SetPitch( PITCH_VARIABLE );
+ rDFA.SetWidthType( WIDTH_NORMAL );
+ rDFA.SetWeight( WEIGHT_NORMAL );
+ rDFA.SetItalic( ITALIC_NONE );
+ rDFA.SetMicrosoftSymbolEncoded( false );
+
+ // get font name
+#ifdef MACOSX
+ CFStringRef pLang = nullptr;
+ CFStringRef pFamilyName = static_cast<CFStringRef>(
+ CTFontDescriptorCopyLocalizedAttribute( pFD, kCTFontFamilyNameAttribute, &pLang ));
+
+ if ( !pLang )
+ {
+ if( pFamilyName )
+ {
+ CFRelease( pFamilyName );
+ }
+ pFamilyName = static_cast<CFStringRef>(CTFontDescriptorCopyAttribute( pFD, kCTFontFamilyNameAttribute ));
+ }
+#else
+ // No "Application" on iOS. And it is unclear whether this code
+ // snippet will actually ever get invoked on iOS anyway. So just
+ // use the old code that uses a non-localized font name.
+ CFStringRef pFamilyName = (CFStringRef)CTFontDescriptorCopyAttribute( pFD, kCTFontFamilyNameAttribute );
+#endif
+
+ rDFA.SetFamilyName( GetOUString( pFamilyName ) );
+
+ // get font style
+ CFStringRef pStyleName = static_cast<CFStringRef>(CTFontDescriptorCopyAttribute( pFD, kCTFontStyleNameAttribute ));
+ rDFA.SetStyleName( GetOUString( pStyleName ) );
+
+ // get font-enabled status
+ if( bFontEnabled )
+ {
+ int bEnabled = TRUE; // by default (and when we're on macOS < 10.6) it's "enabled"
+ CFNumberRef pEnabled = static_cast<CFNumberRef>(CTFontDescriptorCopyAttribute( pFD, kCTFontEnabledAttribute ));
+ CFNumberGetValue( pEnabled, kCFNumberIntType, &bEnabled );
+ *bFontEnabled = bEnabled;
+ }
+
+ // get font attributes
+ CFDictionaryRef pAttrDict = static_cast<CFDictionaryRef>(CTFontDescriptorCopyAttribute( pFD, kCTFontTraitsAttribute ));
+
+ if (bFontEnabled && *bFontEnabled)
+ {
+ // Ignore font formats not supported.
+ int nFormat;
+ CFNumberRef pFormat = static_cast<CFNumberRef>(CTFontDescriptorCopyAttribute(pFD, kCTFontFormatAttribute));
+ CFNumberGetValue(pFormat, kCFNumberIntType, &nFormat);
+ if (nFormat == kCTFontFormatUnrecognized || nFormat == kCTFontFormatPostScript || nFormat == kCTFontFormatBitmap)
+ {
+ SAL_INFO("vcl.fonts", "Ignoring font with unsupported format: " << rDFA.GetFamilyName());
+ *bFontEnabled = false;
+ }
+ CFRelease(pFormat);
+ }
+
+ // get symbolic trait
+ // TODO: use other traits such as MonoSpace/Condensed/Expanded or Vertical too
+ SInt64 nSymbolTrait = 0;
+ CFNumberRef pSymbolNum = nullptr;
+ if( CFDictionaryGetValueIfPresent( pAttrDict, kCTFontSymbolicTrait, reinterpret_cast<const void**>(&pSymbolNum) ) )
+ {
+ CFNumberGetValue( pSymbolNum, kCFNumberSInt64Type, &nSymbolTrait );
+ if (nSymbolTrait & kCTFontMonoSpaceTrait)
+ rDFA.SetPitch(PITCH_FIXED);
+ }
+
+ // get the font weight
+ double fWeight = 0;
+ CFNumberRef pWeightNum = static_cast<CFNumberRef>(CFDictionaryGetValue( pAttrDict, kCTFontWeightTrait ));
+ // tdf#140401 check if attribute is a nullptr
+ if( pWeightNum )
+ CFNumberGetValue( pWeightNum, kCFNumberDoubleType, &fWeight );
+ int nInt = WEIGHT_NORMAL;
+
+ // Special case fixes
+
+ // tdf#67744: Courier Std Medium is always bold. We get a kCTFontWeightTrait of 0.23 which
+ // surely must be wrong.
+ if (rDFA.GetFamilyName() == "Courier Std" &&
+ (rDFA.GetStyleName() == "Medium" || rDFA.GetStyleName() == "Medium Oblique") &&
+ fWeight > 0.2)
+ {
+ fWeight = 0;
+ }
+
+ // tdf#68889: Ditto for Gill Sans MT Pro. Here I can kinda understand it, maybe the
+ // kCTFontWeightTrait is intended to give a subjective "optical" impression of how the font
+ // looks, and Gill Sans MT Pro Medium is kinda heavy. But with the way LibreOffice uses fonts,
+ // we still should think of it as being "medium" weight.
+ if (rDFA.GetFamilyName() == "Gill Sans MT Pro" &&
+ (rDFA.GetStyleName() == "Medium" || rDFA.GetStyleName() == "Medium Italic") &&
+ fWeight > 0.2)
+ {
+ fWeight = 0;
+ }
+
+ if( fWeight > 0 )
+ {
+ nInt = rint(int(WEIGHT_NORMAL) + fWeight * ((WEIGHT_BLACK - WEIGHT_NORMAL)/0.68));
+ if( nInt > WEIGHT_BLACK )
+ {
+ nInt = WEIGHT_BLACK;
+ }
+ }
+ else if( fWeight < 0 )
+ {
+ nInt = rint(int(WEIGHT_NORMAL) + fWeight * ((WEIGHT_NORMAL - WEIGHT_THIN)/0.8));
+ if( nInt < WEIGHT_THIN )
+ {
+ nInt = WEIGHT_THIN;
+ }
+ }
+ rDFA.SetWeight( static_cast<FontWeight>(nInt) );
+
+ // get the font slant
+ double fSlant = 0;
+ CFNumberRef pSlantNum = static_cast<CFNumberRef>(CFDictionaryGetValue( pAttrDict, kCTFontSlantTrait ));
+ // tdf#140401 check if attribute is a nullptr
+ if( pSlantNum )
+ CFNumberGetValue( pSlantNum, kCFNumberDoubleType, &fSlant );
+ if( fSlant >= 0.035 )
+ {
+ rDFA.SetItalic( ITALIC_NORMAL );
+ }
+ // get width trait
+ double fWidth = 0;
+ CFNumberRef pWidthNum = static_cast<CFNumberRef>(CFDictionaryGetValue( pAttrDict, kCTFontWidthTrait ));
+ // tdf#140401 check if attribute is a nullptr
+ if( pWidthNum )
+ CFNumberGetValue( pWidthNum, kCFNumberDoubleType, &fWidth );
+ nInt = WIDTH_NORMAL;
+
+ if( fWidth > 0 )
+ {
+ nInt = rint( int(WIDTH_NORMAL) + fWidth * ((WIDTH_ULTRA_EXPANDED - WIDTH_NORMAL)/0.4));
+ if( nInt > WIDTH_ULTRA_EXPANDED )
+ {
+ nInt = WIDTH_ULTRA_EXPANDED;
+ }
+ }
+ else if( fWidth < 0 )
+ {
+ nInt = rint( int(WIDTH_NORMAL) + fWidth * ((WIDTH_NORMAL - WIDTH_ULTRA_CONDENSED)/0.5));
+ if( nInt < WIDTH_ULTRA_CONDENSED )
+ {
+ nInt = WIDTH_ULTRA_CONDENSED;
+ }
+ }
+ rDFA.SetWidthType( static_cast<FontWidth>(nInt) );
+
+ // release the attribute dict that we had copied
+ CFRelease( pAttrDict );
+
+ // TODO? also use the HEAD table if available to get more attributes
+// CFDataRef CTFontCopyTable( CTFontRef, kCTFontTableHead, /*kCTFontTableOptionNoOptions*/kCTFontTableOptionExcludeSynthetic );
+
+ return rDFA;
+}
+
+static void fontEnumCallBack( const void* pValue, void* pContext )
+{
+ CTFontDescriptorRef pFD = static_cast<CTFontDescriptorRef>(pValue);
+
+ bool bFontEnabled;
+ FontAttributes rDFA = DevFontFromCTFontDescriptor( pFD, &bFontEnabled );
+
+ if( bFontEnabled)
+ {
+ rtl::Reference<CoreTextFontFace> pFontData = new CoreTextFontFace( rDFA, pFD );
+ SystemFontList* pFontList = static_cast<SystemFontList*>(pContext);
+ pFontList->AddFont( pFontData.get() );
+ }
+}
+
+SystemFontList::SystemFontList()
+ : mpCTFontCollection( nullptr )
+ , mpCTFontArray( nullptr )
+{}
+
+SystemFontList::~SystemFontList()
+{
+ maFontContainer.clear();
+
+ if( mpCTFontArray )
+ {
+ CFRelease( mpCTFontArray );
+ }
+ if( mpCTFontCollection )
+ {
+ CFRelease( mpCTFontCollection );
+ }
+}
+
+void SystemFontList::AddFont( CoreTextFontFace* pFontData )
+{
+ sal_IntPtr nFontId = pFontData->GetFontId();
+ maFontContainer[ nFontId ] = pFontData;
+}
+
+void SystemFontList::AnnounceFonts( vcl::font::PhysicalFontCollection& rFontCollection ) const
+{
+ for(const auto& rEntry : maFontContainer )
+ {
+ rFontCollection.Add( rEntry.second.get() );
+ }
+}
+
+CoreTextFontFace* SystemFontList::GetFontDataFromId( sal_IntPtr nFontId ) const
+{
+ auto it = maFontContainer.find( nFontId );
+ if( it == maFontContainer.end() )
+ {
+ return nullptr;
+ }
+ return (*it).second.get();
+}
+
+bool SystemFontList::Init()
+{
+ // enumerate available system fonts
+ static const int nMaxDictEntries = 8;
+ CFMutableDictionaryRef pCFDict = CFDictionaryCreateMutable( nullptr,
+ nMaxDictEntries,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks );
+
+ CFDictionaryAddValue( pCFDict, kCTFontCollectionRemoveDuplicatesOption, kCFBooleanTrue );
+ mpCTFontCollection = CTFontCollectionCreateFromAvailableFonts( pCFDict );
+ CFRelease( pCFDict );
+ mpCTFontArray = CTFontCollectionCreateMatchingFontDescriptors( mpCTFontCollection );
+
+ const int nFontCount = CFArrayGetCount( mpCTFontArray );
+ const CFRange aFullRange = CFRangeMake( 0, nFontCount );
+ CFArrayApplyFunction( mpCTFontArray, aFullRange, fontEnumCallBack, this );
+
+ return true;
+}
+
+std::unique_ptr<SystemFontList> GetCoretextFontList()
+{
+ std::unique_ptr<SystemFontList> pList(new SystemFontList());
+ if( !pList->Init() )
+ {
+ return nullptr;
+ }
+
+ return pList;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/cgutils.mm b/vcl/quartz/cgutils.mm
new file mode 100644
index 0000000000..0d611bec11
--- /dev/null
+++ b/vcl/quartz/cgutils.mm
@@ -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 .
+ */
+
+#include <quartz/cgutils.h>
+
+#include <salbmp.hxx>
+#ifdef MACOSX
+#include <osx/saldata.hxx>
+#else
+#include <ios/iosinst.hxx>
+#endif
+
+#ifdef MACOSX
+#include <premac.h>
+#include <Metal/Metal.h>
+#include <postmac.h>
+#endif
+
+static void CFRTLFree(void* /*info*/, const void* data, size_t /*size*/)
+{
+ std::free( const_cast<void*>(data) );
+}
+
+CGImageRef CreateWithSalBitmapAndMask( const SalBitmap& rBitmap, const SalBitmap& rMask, int nX, int nY, int nWidth, int nHeight )
+{
+ CGImageRef xImage( rBitmap.CreateCroppedImage( nX, nY, nWidth, nHeight ) );
+ if( !xImage )
+ return nullptr;
+
+ CGImageRef xMask = rMask.CreateCroppedImage( nX, nY, nWidth, nHeight );
+ if( !xMask )
+ return xImage;
+
+ // If xMask is an image (i.e. not a mask), it must be greyscale - a requirement of the
+ // CGImageCreateWithMask() function.
+ if( !CGImageIsMask(xMask) && CGImageGetColorSpace(xMask) != GetSalData()->mxGraySpace )
+ {
+ CGImageRef xGrayMask = CGImageCreateCopyWithColorSpace(xMask, GetSalData()->mxGraySpace);
+ if (xGrayMask)
+ {
+ CFRelease(xMask);
+ xMask = xGrayMask;
+ }
+ else
+ {
+ // Many gallery images will fail to be converted to a grayscale
+ // colorspace so fall back to old mask creation code
+ const CGRect xImageRect=CGRectMake( 0, 0, nWidth, nHeight );//the rect has no offset
+
+ // create the alpha mask image fitting our image
+ // TODO: is caching the full mask or the subimage mask worth it?
+ int nMaskBytesPerRow = ((nWidth + 3) & ~3);
+ void* pMaskMem = std::malloc( nMaskBytesPerRow * nHeight );
+ CGContextRef xMaskContext = CGBitmapContextCreate( pMaskMem,
+ nWidth, nHeight, 8, nMaskBytesPerRow, GetSalData()->mxGraySpace, kCGImageAlphaNone );
+ CGContextDrawImage( xMaskContext, xImageRect, xMask );
+ CFRelease( xMask );
+ CGDataProviderRef xDataProvider( CGDataProviderCreateWithData( nullptr,
+ pMaskMem, nHeight * nMaskBytesPerRow, &CFRTLFree ) );
+
+ static const CGFloat* pDecode = nullptr;
+ xMask = CGImageMaskCreate( nWidth, nHeight, 8, 8, nMaskBytesPerRow, xDataProvider, pDecode, false );
+ CFRelease( xDataProvider );
+ CFRelease( xMaskContext );
+ }
+ }
+
+ if( !xMask )
+ return xImage;
+
+ // combine image and alpha mask
+ CGImageRef xMaskedImage = CGImageCreateWithMask( xImage, xMask );
+ CFRelease( xMask );
+ CFRelease( xImage );
+ return xMaskedImage;
+}
+
+#ifdef MACOSX
+
+bool DefaultMTLDeviceIsSupported()
+{
+ id<MTLDevice> pMetalDevice = MTLCreateSystemDefaultDevice();
+ if (!pMetalDevice || !pMetalDevice.name)
+ {
+ SAL_WARN("vcl.skia", "MTLCreateSystemDefaultDevice() returned nil");
+ return false;
+ }
+
+ SAL_WARN("vcl.skia", "Default MTLDevice is \"" << [pMetalDevice.name UTF8String] << "\"");
+
+ bool bRet = true;
+
+ // tdf#156881 Disable Metal with AMD Radeon Pro 5XXX GPUs on macOS Catalina
+ // When running macOS Catalina on a 2019 MacBook Pro, unexpected drawing
+ // artifacts are drawn so disable Metal for the AMD Radeon Pro GPUs listed
+ // for that model in https://support.apple.com/kb/SP809.
+ if (@available(macOS 11, *))
+ {
+ // No known problems with macOS Big Sur or later
+ }
+ else
+ {
+ static NSString* pAMDRadeonPro5300Prefix = @"AMD Radeon Pro 5300M";
+ static NSString* pAMDRadeonPro5500Prefix = @"AMD Radeon Pro 5500M";
+ if ([pMetalDevice.name hasPrefix:pAMDRadeonPro5300Prefix] || [pMetalDevice.name hasPrefix:pAMDRadeonPro5500Prefix])
+ bRet = false;
+ }
+
+ [pMetalDevice release];
+ return bRet;
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/salbmp.cxx b/vcl/quartz/salbmp.cxx
new file mode 100644
index 0000000000..ab435c0acd
--- /dev/null
+++ b/vcl/quartz/salbmp.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 <sal/config.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <cstddef>
+#include <limits>
+
+#include <o3tl/make_shared.hxx>
+#include <tools/color.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/BitmapAccessMode.hxx>
+#include <vcl/BitmapBuffer.hxx>
+#include <vcl/BitmapColor.hxx>
+#include <vcl/BitmapPalette.hxx>
+#include <vcl/ColorMask.hxx>
+#include <vcl/Scanline.hxx>
+
+#include <bitmap/bmpfast.hxx>
+#include <quartz/cgutils.h>
+#include <quartz/salbmp.h>
+#include <quartz/utils.h>
+#include <bitmap/ScanlineTools.hxx>
+
+#ifdef MACOSX
+#include <osx/saldata.hxx>
+#else
+#include <ios/iosinst.hxx>
+#endif
+
+const unsigned long k32BitRedColorMask = 0x00ff0000;
+const unsigned long k32BitGreenColorMask = 0x0000ff00;
+const unsigned long k32BitBlueColorMask = 0x000000ff;
+
+QuartzSalBitmap::QuartzSalBitmap()
+ : mxCachedImage( nullptr )
+ , mnBits(0)
+ , mnWidth(0)
+ , mnHeight(0)
+ , mnBytesPerRow(0)
+{
+}
+
+QuartzSalBitmap::~QuartzSalBitmap()
+{
+ doDestroy();
+}
+
+bool QuartzSalBitmap::Create( const Size& rSize, vcl::PixelFormat ePixelFormat, const BitmapPalette& rBitmapPalette )
+{
+ if (ePixelFormat == vcl::PixelFormat::INVALID)
+ return false;
+
+ maPalette = rBitmapPalette;
+ mnBits = vcl::pixelFormatBitCount(ePixelFormat);
+ mnWidth = rSize.Width();
+ mnHeight = rSize.Height();
+ return AllocateUserData();
+}
+
+bool QuartzSalBitmap::Create( const SalBitmap& rSalBmp )
+{
+ vcl::PixelFormat ePixelFormat = vcl::bitDepthToPixelFormat(rSalBmp.GetBitCount());
+ return Create( rSalBmp, ePixelFormat);
+}
+
+bool QuartzSalBitmap::Create( const SalBitmap& rSalBmp, SalGraphics* pGraphics )
+{
+ vcl::PixelFormat ePixelFormat = vcl::PixelFormat::INVALID;
+ if (pGraphics)
+ ePixelFormat = vcl::bitDepthToPixelFormat(pGraphics->GetBitCount());
+ else
+ ePixelFormat = vcl::bitDepthToPixelFormat(rSalBmp.GetBitCount());
+
+ return Create( rSalBmp, ePixelFormat);
+}
+
+bool QuartzSalBitmap::Create( const SalBitmap& rSalBmp, vcl::PixelFormat eNewPixelFormat )
+{
+ const QuartzSalBitmap& rSourceBitmap = static_cast<const QuartzSalBitmap&>(rSalBmp);
+
+ if (eNewPixelFormat != vcl::PixelFormat::INVALID && rSourceBitmap.m_pUserBuffer)
+ {
+ mnBits = vcl::pixelFormatBitCount(eNewPixelFormat);
+ mnWidth = rSourceBitmap.mnWidth;
+ mnHeight = rSourceBitmap.mnHeight;
+ maPalette = rSourceBitmap.maPalette;
+
+ if( AllocateUserData() )
+ {
+ ConvertBitmapData( mnWidth, mnHeight, mnBits, mnBytesPerRow, maPalette,
+ m_pUserBuffer.get(), rSourceBitmap.mnBits,
+ rSourceBitmap.mnBytesPerRow, rSourceBitmap.maPalette,
+ rSourceBitmap.m_pUserBuffer.get() );
+ return true;
+ }
+ }
+ return false;
+}
+
+bool QuartzSalBitmap::Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& /*xBitmapCanvas*/,
+ Size& /*rSize*/, bool /*bMask*/ )
+{
+ return false;
+}
+
+void QuartzSalBitmap::Destroy()
+{
+ doDestroy();
+}
+
+void QuartzSalBitmap::doDestroy()
+{
+ DestroyContext();
+ m_pUserBuffer.reset();
+}
+
+void QuartzSalBitmap::DestroyContext()
+{
+ if( mxCachedImage )
+ {
+ CGImageRelease( mxCachedImage );
+ mxCachedImage = nullptr;
+ }
+
+ if (maGraphicContext.isSet())
+ {
+ CGContextRelease(maGraphicContext.get());
+ maGraphicContext.set(nullptr);
+ m_pContextBuffer.reset();
+ }
+}
+
+bool QuartzSalBitmap::CreateContext()
+{
+ DestroyContext();
+
+ // prepare graphics context
+ // convert image from user input if available
+ const bool bSkipConversion = !m_pUserBuffer;
+ if( bSkipConversion )
+ AllocateUserData();
+
+ // default to RGBA color space
+ CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
+ CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst;
+
+ // convert data into something accepted by CGBitmapContextCreate()
+ size_t bitsPerComponent = 8;
+ sal_uInt32 nContextBytesPerRow = mnBytesPerRow;
+ if( mnBits == 32 )
+ {
+ // no conversion needed for truecolor
+ m_pContextBuffer = m_pUserBuffer;
+ }
+ else if( mnBits == 8 && maPalette.IsGreyPalette8Bit() )
+ {
+ // no conversion needed for grayscale
+ m_pContextBuffer = m_pUserBuffer;
+ aCGColorSpace = GetSalData()->mxGraySpace;
+ aCGBmpInfo = kCGImageAlphaNone;
+ bitsPerComponent = mnBits;
+ }
+ // TODO: is special handling for 1bit input buffers worth it?
+ else
+ {
+ // convert user data to 32 bit
+ nContextBytesPerRow = mnWidth << 2;
+ try
+ {
+ m_pContextBuffer = o3tl::make_shared_array<sal_uInt8>(mnHeight * nContextBytesPerRow);
+
+ if( !bSkipConversion )
+ {
+ ConvertBitmapData( mnWidth, mnHeight,
+ 32, nContextBytesPerRow, maPalette, m_pContextBuffer.get(),
+ mnBits, mnBytesPerRow, maPalette, m_pUserBuffer.get() );
+ }
+ }
+ catch( const std::bad_alloc& )
+ {
+ maGraphicContext.set(nullptr);
+ }
+ }
+
+ if (m_pContextBuffer)
+ {
+ maGraphicContext.set(CGBitmapContextCreate(m_pContextBuffer.get(), mnWidth, mnHeight,
+ bitsPerComponent, nContextBytesPerRow,
+ aCGColorSpace, aCGBmpInfo));
+ }
+
+ if (!maGraphicContext.isSet())
+ m_pContextBuffer.reset();
+
+ return maGraphicContext.isSet();
+}
+
+bool QuartzSalBitmap::AllocateUserData()
+{
+ Destroy();
+
+ if( mnWidth && mnHeight )
+ {
+ mnBytesPerRow = 0;
+
+ switch( mnBits )
+ {
+ case 1: mnBytesPerRow = (mnWidth + 7) >> 3; break;
+ case 8: mnBytesPerRow = mnWidth; break;
+ case 24: mnBytesPerRow = (mnWidth << 1) + mnWidth; break;
+ case 32: mnBytesPerRow = mnWidth << 2; break;
+ default:
+ assert(false && "vcl::QuartzSalBitmap::AllocateUserData(), illegal bitcount!");
+ }
+ }
+
+ bool alloc = false;
+ if (mnBytesPerRow != 0 &&
+ mnBytesPerRow <= std::numeric_limits<sal_uInt32>::max() / mnHeight)
+ {
+ try
+ {
+ m_pUserBuffer = o3tl::make_shared_array<sal_uInt8>(mnBytesPerRow * mnHeight);
+ alloc = true;
+ }
+ catch (std::bad_alloc &) {}
+ }
+ if (!alloc)
+ {
+ SAL_WARN( "vcl.quartz", "bad_alloc: " << mnWidth << "x" << mnHeight << " (" << mnBytesPerRow * mnHeight << " bytes)");
+ m_pUserBuffer.reset();
+ mnBytesPerRow = 0;
+ }
+
+ return bool(m_pUserBuffer);
+}
+
+void QuartzSalBitmap::ConvertBitmapData( sal_uInt32 nWidth, sal_uInt32 nHeight,
+ sal_uInt16 nDestBits, sal_uInt32 nDestBytesPerRow,
+ const BitmapPalette& rDestPalette, sal_uInt8* pDestData,
+ sal_uInt16 nSrcBits, sal_uInt32 nSrcBytesPerRow,
+ const BitmapPalette& rSrcPalette, sal_uInt8* pSrcData )
+
+{
+ if( (nDestBytesPerRow == nSrcBytesPerRow) &&
+ (nDestBits == nSrcBits) && ((nSrcBits != 8) || (rDestPalette.operator==( rSrcPalette ))) )
+ {
+ // simple case, same format, so just copy
+ memcpy( pDestData, pSrcData, nHeight * nDestBytesPerRow );
+ return;
+ }
+
+ // try accelerated conversion if possible
+ // TODO: are other truecolor conversions except BGR->ARGB worth it?
+ bool bConverted = false;
+ if( (nSrcBits == 24) && (nDestBits == 32) )
+ {
+ // TODO: extend bmpfast.cxx with a method that can be directly used here
+ BitmapBuffer aSrcBuf;
+ aSrcBuf.mnFormat = ScanlineFormat::N24BitTcBgr;
+ aSrcBuf.mpBits = pSrcData;
+ aSrcBuf.mnBitCount = nSrcBits;
+ aSrcBuf.mnScanlineSize = nSrcBytesPerRow;
+ BitmapBuffer aDstBuf;
+ aDstBuf.mnFormat = ScanlineFormat::N32BitTcArgb;
+ aDstBuf.mpBits = pDestData;
+ aDstBuf.mnBitCount = nDestBits;
+ aDstBuf.mnScanlineSize = nDestBytesPerRow;
+
+ aSrcBuf.mnWidth = aDstBuf.mnWidth = nWidth;
+ aSrcBuf.mnHeight = aDstBuf.mnHeight = nHeight;
+
+ SalTwoRect aTwoRects(0, 0, mnWidth, mnHeight, 0, 0, mnWidth, mnHeight);
+ bConverted = ::ImplFastBitmapConversion( aDstBuf, aSrcBuf, aTwoRects );
+ }
+
+ if( !bConverted )
+ {
+ // TODO: this implementation is for clarity, not for speed
+
+ auto pTarget = vcl::bitmap::getScanlineTransformer(nDestBits, rDestPalette);
+ auto pSource = vcl::bitmap::getScanlineTransformer(nSrcBits, rSrcPalette);
+
+ if (pTarget && pSource)
+ {
+ sal_uInt32 nY = nHeight;
+ while( nY-- )
+ {
+ pTarget->startLine(pDestData);
+ pSource->startLine(pSrcData);
+
+ sal_uInt32 nX = nWidth;
+ while( nX-- )
+ {
+ pTarget->writePixel(pSource->readPixel());
+ }
+ pSrcData += nSrcBytesPerRow;
+ pDestData += nDestBytesPerRow;
+ }
+ }
+ }
+}
+
+Size QuartzSalBitmap::GetSize() const
+{
+ return Size( mnWidth, mnHeight );
+}
+
+sal_uInt16 QuartzSalBitmap::GetBitCount() const
+{
+ return mnBits;
+}
+
+namespace {
+
+struct pal_entry
+{
+ sal_uInt8 mnRed;
+ sal_uInt8 mnGreen;
+ sal_uInt8 mnBlue;
+};
+
+}
+
+pal_entry const aImplSalSysPalEntryAry[ 16 ] =
+{
+{ 0, 0, 0 },
+{ 0, 0, 0x80 },
+{ 0, 0x80, 0 },
+{ 0, 0x80, 0x80 },
+{ 0x80, 0, 0 },
+{ 0x80, 0, 0x80 },
+{ 0x80, 0x80, 0 },
+{ 0x80, 0x80, 0x80 },
+{ 0xC0, 0xC0, 0xC0 },
+{ 0, 0, 0xFF },
+{ 0, 0xFF, 0 },
+{ 0, 0xFF, 0xFF },
+{ 0xFF, 0, 0 },
+{ 0xFF, 0, 0xFF },
+{ 0xFF, 0xFF, 0 },
+{ 0xFF, 0xFF, 0xFF }
+};
+
+static const BitmapPalette& GetDefaultPalette( int mnBits, bool bMonochrome )
+{
+ if( bMonochrome )
+ return Bitmap::GetGreyPalette( 1U << mnBits );
+
+ // at this point we should provide some kind of default palette
+ // since all other platforms do so, too.
+ static bool bDefPalInit = false;
+ static BitmapPalette aDefPalette256;
+ static BitmapPalette aDefPalette2;
+ if( ! bDefPalInit )
+ {
+ bDefPalInit = true;
+ aDefPalette256.SetEntryCount( 256 );
+ aDefPalette2.SetEntryCount( 2 );
+
+ // Standard colors
+ unsigned int i;
+ for( i = 0; i < 16; i++ )
+ {
+ aDefPalette256[i] = BitmapColor( aImplSalSysPalEntryAry[i].mnRed,
+ aImplSalSysPalEntryAry[i].mnGreen,
+ aImplSalSysPalEntryAry[i].mnBlue );
+ }
+
+ aDefPalette2[0] = BitmapColor( 0, 0, 0 );
+ aDefPalette2[1] = BitmapColor( 0xff, 0xff, 0xff );
+
+ // own palette (6/6/6)
+ const int DITHER_PAL_STEPS = 6;
+ const sal_uInt8 DITHER_PAL_DELTA = 51;
+ int nB, nG, nR;
+ sal_uInt8 nRed, nGreen, nBlue;
+ for( nB=0, nBlue=0; nB < DITHER_PAL_STEPS; nB++, nBlue += DITHER_PAL_DELTA )
+ {
+ for( nG=0, nGreen=0; nG < DITHER_PAL_STEPS; nG++, nGreen += DITHER_PAL_DELTA )
+ {
+ for( nR=0, nRed=0; nR < DITHER_PAL_STEPS; nR++, nRed += DITHER_PAL_DELTA )
+ {
+ aDefPalette256[ i ] = BitmapColor( nRed, nGreen, nBlue );
+ i++;
+ }
+ }
+ }
+ }
+
+ // now fill in appropriate palette
+ switch( mnBits )
+ {
+ case 1: return aDefPalette2;
+ case 8: return aDefPalette256;
+ default: break;
+ }
+
+ const static BitmapPalette aEmptyPalette;
+ return aEmptyPalette;
+}
+
+BitmapBuffer* QuartzSalBitmap::AcquireBuffer( BitmapAccessMode /*nMode*/ )
+{
+ // TODO: AllocateUserData();
+ if (!m_pUserBuffer)
+ return nullptr;
+
+ BitmapBuffer* pBuffer = new BitmapBuffer;
+ pBuffer->mnWidth = mnWidth;
+ pBuffer->mnHeight = mnHeight;
+ pBuffer->maPalette = maPalette;
+ pBuffer->mnScanlineSize = mnBytesPerRow;
+ pBuffer->mpBits = m_pUserBuffer.get();
+ pBuffer->mnBitCount = mnBits;
+ switch( mnBits )
+ {
+ case 1:
+ pBuffer->mnFormat = ScanlineFormat::N1BitMsbPal;
+ break;
+ case 8:
+ pBuffer->mnFormat = ScanlineFormat::N8BitPal;
+ break;
+ case 24:
+ pBuffer->mnFormat = ScanlineFormat::N24BitTcBgr;
+ break;
+ case 32:
+ {
+ pBuffer->mnFormat = ScanlineFormat::N32BitTcArgb;
+ ColorMaskElement aRedMask(k32BitRedColorMask);
+ aRedMask.CalcMaskShift();
+ ColorMaskElement aGreenMask(k32BitGreenColorMask);
+ aGreenMask.CalcMaskShift();
+ ColorMaskElement aBlueMask(k32BitBlueColorMask);
+ aBlueMask.CalcMaskShift();
+ pBuffer->maColorMask = ColorMask(aRedMask, aGreenMask, aBlueMask);
+ break;
+ }
+ default: assert(false);
+ }
+
+ // some BitmapBuffer users depend on a complete palette
+ if( (mnBits <= 8) && !maPalette )
+ pBuffer->maPalette = GetDefaultPalette( mnBits, true );
+
+ return pBuffer;
+}
+
+void QuartzSalBitmap::ReleaseBuffer( BitmapBuffer* pBuffer, BitmapAccessMode nMode )
+{
+ // invalidate graphic context if we have different data
+ if( nMode == BitmapAccessMode::Write )
+ {
+ maPalette = pBuffer->maPalette;
+ if (maGraphicContext.isSet())
+ {
+ DestroyContext();
+ }
+ InvalidateChecksum();
+ }
+
+ delete pBuffer;
+}
+
+CGImageRef QuartzSalBitmap::CreateCroppedImage( int nX, int nY, int nNewWidth, int nNewHeight ) const
+{
+ if( !mxCachedImage )
+ {
+ if (!maGraphicContext.isSet())
+ {
+ if( !const_cast<QuartzSalBitmap*>(this)->CreateContext() )
+ {
+ return nullptr;
+ }
+ }
+ mxCachedImage = CGBitmapContextCreateImage(maGraphicContext.get());
+ }
+
+ CGImageRef xCroppedImage = nullptr;
+ // short circuit if there is nothing to crop
+ if( !nX && !nY && (mnWidth == nNewWidth) && (mnHeight == nNewHeight) )
+ {
+ xCroppedImage = mxCachedImage;
+ CFRetain( xCroppedImage );
+ }
+ else
+ {
+ nY = mnHeight - (nY + nNewHeight); // adjust for y-mirrored context
+ const CGRect aCropRect = { { static_cast<CGFloat>(nX), static_cast<CGFloat>(nY) }, { static_cast<CGFloat>(nNewWidth), static_cast<CGFloat>(nNewHeight) } };
+ xCroppedImage = CGImageCreateWithImageInRect( mxCachedImage, aCropRect );
+ }
+
+ return xCroppedImage;
+}
+
+static void CFRTLFree(void* /*info*/, const void* data, size_t /*size*/)
+{
+ std::free( const_cast<void*>(data) );
+}
+
+CGImageRef QuartzSalBitmap::CreateWithMask( const SalBitmap& rMask,
+ int nX, int nY, int nWidth, int nHeight ) const
+{
+ return CreateWithSalBitmapAndMask( *this, rMask, nX, nY, nWidth, nHeight );
+}
+
+/** creates an image from the given rectangle, replacing all black pixels
+ with nMaskColor and make all other full transparent */
+CGImageRef QuartzSalBitmap::CreateColorMask( int nX, int nY, int nWidth,
+ int nHeight, Color nMaskColor ) const
+{
+ CGImageRef xMask = nullptr;
+ if (m_pUserBuffer && (nX + nWidth <= mnWidth) && (nY + nHeight <= mnHeight))
+ {
+ auto pSourcePixels = vcl::bitmap::getScanlineTransformer(mnBits, maPalette);
+ // Don't allocate destination buffer if there is no scanline transformer
+ if( !pSourcePixels )
+ return xMask;
+
+ const sal_uInt32 nDestBytesPerRow = nWidth << 2;
+ std::unique_ptr<sal_uInt32[]> pMaskBuffer(new (std::nothrow) sal_uInt32[ nHeight * nDestBytesPerRow / 4] );
+ if( pMaskBuffer )
+ {
+ sal_uInt32 nColor;
+ reinterpret_cast<sal_uInt8*>(&nColor)[0] = 0xff;
+ reinterpret_cast<sal_uInt8*>(&nColor)[1] = nMaskColor.GetRed();
+ reinterpret_cast<sal_uInt8*>(&nColor)[2] = nMaskColor.GetGreen();
+ reinterpret_cast<sal_uInt8*>(&nColor)[3] = nMaskColor.GetBlue();
+
+ sal_uInt8* pSource = m_pUserBuffer.get();
+ sal_uInt32* pDest = pMaskBuffer.get();
+ // First to nY on y-axis, as that is our starting point (sub-image)
+ if( nY )
+ pSource += nY * mnBytesPerRow;
+
+ int y = nHeight;
+ while( y-- )
+ {
+ pSourcePixels->startLine( pSource );
+ pSourcePixels->skipPixel(nX); // Skip on x axis to nX
+ sal_uInt32 x = nWidth;
+ while( x-- )
+ {
+ // Fix failure to generate the correct color mask
+ // OutputDevice::ImplDrawRotateText() draws black text but
+ // that will generate gray pixels due to antialiasing so
+ // count dark gray the same as black, light gray the same
+ // as white, and the rest as medium gray.
+ // The results are not smooth since LibreOffice appears to
+ // redraw these semi-transparent masks repeatedly without
+ // clearing the background so the semi-transparent pixels
+ // will grow darker with repeatedly redraws due to
+ // cumulative blending. But it is now better than before.
+ sal_uInt8 nAlpha = 255 - pSourcePixels->readPixel().GetRed();
+ sal_uInt32 nPremultColor = nColor;
+ if ( nAlpha < 192 )
+ {
+ if ( nAlpha < 64 )
+ {
+ nPremultColor = 0;
+ }
+ else
+ {
+ reinterpret_cast<sal_uInt8*>(&nPremultColor)[0] /= 2;
+ reinterpret_cast<sal_uInt8*>(&nPremultColor)[1] /= 2;
+ reinterpret_cast<sal_uInt8*>(&nPremultColor)[2] /= 2;
+ reinterpret_cast<sal_uInt8*>(&nPremultColor)[3] /= 2;
+ }
+ }
+ *pDest++ = nPremultColor;
+ }
+ pSource += mnBytesPerRow;
+ }
+
+ CGDataProviderRef xDataProvider( CGDataProviderCreateWithData(nullptr, pMaskBuffer.release(), nHeight * nDestBytesPerRow, &CFRTLFree) );
+ xMask = CGImageCreate(nWidth, nHeight, 8, 32, nDestBytesPerRow, GetSalData()->mxRGBSpace, kCGImageAlphaPremultipliedFirst, xDataProvider, nullptr, true, kCGRenderingIntentDefault);
+ CFRelease(xDataProvider);
+ }
+ }
+ return xMask;
+}
+
+/** QuartzSalBitmap::GetSystemData Get platform native image data from existing image
+ *
+ * @param rData struct BitmapSystemData, defined in vcl/inc/bitmap.hxx
+ * @return true if successful
+**/
+bool QuartzSalBitmap::GetSystemData( BitmapSystemData& rData )
+{
+ bool bRet = false;
+
+ if (!maGraphicContext.isSet())
+ CreateContext();
+
+ if (maGraphicContext.isSet())
+ {
+ bRet = true;
+
+ if ((CGBitmapContextGetBitsPerPixel(maGraphicContext.get()) == 32) &&
+ (CGBitmapContextGetBitmapInfo(maGraphicContext.get()) & kCGBitmapByteOrderMask) != kCGBitmapByteOrder32Host)
+ {
+ /**
+ * We need to hack things because VCL does not use kCGBitmapByteOrder32Host, while Cairo requires it.
+ *
+ * Not sure what the above comment means. We don't use Cairo on macOS or iOS.
+ *
+ * This whole if statement was originally (before 2011) inside #ifdef CAIRO. Did we use Cairo on Mac back then?
+ * Anyway, nowadays (since many years, I think) we don't, so should this if statement be dropped? Fun.
+ */
+
+ CGImageRef xImage = CGBitmapContextCreateImage(maGraphicContext.get());
+
+ // re-create the context with single change: include kCGBitmapByteOrder32Host flag.
+ CGContextHolder aGraphicContextNew(CGBitmapContextCreate(CGBitmapContextGetData(maGraphicContext.get()),
+ CGBitmapContextGetWidth(maGraphicContext.get()),
+ CGBitmapContextGetHeight(maGraphicContext.get()),
+ CGBitmapContextGetBitsPerComponent(maGraphicContext.get()),
+ CGBitmapContextGetBytesPerRow(maGraphicContext.get()),
+ CGBitmapContextGetColorSpace(maGraphicContext.get()),
+ CGBitmapContextGetBitmapInfo(maGraphicContext.get()) | kCGBitmapByteOrder32Host));
+ CFRelease(maGraphicContext.get());
+
+ // Needs to be flipped
+ aGraphicContextNew.saveState();
+ CGContextTranslateCTM (aGraphicContextNew.get(), 0, CGBitmapContextGetHeight(aGraphicContextNew.get()));
+ CGContextScaleCTM (aGraphicContextNew.get(), 1.0, -1.0);
+
+ CGContextDrawImage(aGraphicContextNew.get(), CGRectMake( 0, 0, CGImageGetWidth(xImage), CGImageGetHeight(xImage)), xImage);
+
+ // Flip back
+ CGContextRestoreGState( aGraphicContextNew.get() );
+ CGImageRelease( xImage );
+ maGraphicContext = aGraphicContextNew;
+ }
+
+ rData.mnWidth = mnWidth;
+ rData.mnHeight = mnHeight;
+ }
+
+ return bRet;
+}
+
+bool QuartzSalBitmap::ScalingSupported() const
+{
+ return false;
+}
+
+bool QuartzSalBitmap::Scale( const double& /*rScaleX*/, const double& /*rScaleY*/, BmpScaleFlag /*nScaleFlag*/ )
+{
+ return false;
+}
+
+bool QuartzSalBitmap::Replace( const Color& /*rSearchColor*/, const Color& /*rReplaceColor*/, sal_uInt8 /*nTol*/ )
+{
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/salgdi.cxx b/vcl/quartz/salgdi.cxx
new file mode 100644
index 0000000000..0522ff8d58
--- /dev/null
+++ b/vcl/quartz/salgdi.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <sal/log.hxx>
+#include <config_folders.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/range/b2drectangle.hxx>
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <rtl/bootstrap.h>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <tools/long.hxx>
+#include <comphelper/lok.hxx>
+
+#include <vcl/metric.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <fontsubset.hxx>
+#include <impfont.hxx>
+#include <font/FontMetricData.hxx>
+#include <font/fontsubstitution.hxx>
+#include <font/PhysicalFontCollection.hxx>
+
+#ifdef MACOSX
+#include <osx/salframe.h>
+#endif
+#include <quartz/utils.h>
+#ifdef IOS
+#include <ios/iosinst.hxx>
+#endif
+#include <sallayout.hxx>
+
+#include <config_features.h>
+#include <vcl/skia/SkiaHelper.hxx>
+#if HAVE_FEATURE_SKIA
+#include <skia/osx/gdiimpl.hxx>
+#endif
+
+#include <quartz/SystemFontList.hxx>
+#include <quartz/CoreTextFont.hxx>
+#include <quartz/CoreTextFontFace.hxx>
+
+using namespace vcl;
+
+namespace {
+
+class CoreTextGlyphFallbackSubstititution
+: public vcl::font::GlyphFallbackFontSubstitution
+{
+public:
+ bool FindFontSubstitute(vcl::font::FontSelectPattern&, LogicalFontInstance* pLogicalFont, OUString&) const override;
+};
+
+bool FontHasCharacter(CTFontRef pFont, const OUString& rString, sal_Int32 nIndex, sal_Int32 nLen)
+{
+ auto const glyphs = std::make_unique<CGGlyph[]>(nLen);
+ return CTFontGetGlyphsForCharacters(pFont, reinterpret_cast<const UniChar*>(rString.getStr() + nIndex), glyphs.get(), nLen);
+}
+
+}
+
+bool CoreTextGlyphFallbackSubstititution::FindFontSubstitute(vcl::font::FontSelectPattern& rPattern, LogicalFontInstance* pLogicalFont,
+ OUString& rMissingChars) const
+{
+ bool bFound = false;
+ CoreTextFont* pFont = static_cast<CoreTextFont*>(pLogicalFont);
+ CFStringRef pStr = CreateCFString(rMissingChars);
+ if (pStr)
+ {
+ CTFontRef pFallback = CTFontCreateForString(pFont->GetCTFont(), pStr, CFRangeMake(0, CFStringGetLength(pStr)));
+ if (pFallback)
+ {
+ bFound = true;
+
+ // tdf#148470 remove the resolved chars from rMissing to flag which ones are still missing
+ // for an attempt with another font
+ OUStringBuffer aStillMissingChars;
+ for (sal_Int32 nStrIndex = 0; nStrIndex < rMissingChars.getLength();)
+ {
+ sal_Int32 nOldStrIndex = nStrIndex;
+ rMissingChars.iterateCodePoints(&nStrIndex);
+ sal_Int32 nCharLength = nStrIndex - nOldStrIndex;
+ if (!FontHasCharacter(pFallback, rMissingChars, nOldStrIndex, nCharLength))
+ aStillMissingChars.append(rMissingChars.getStr() + nOldStrIndex, nCharLength);
+ }
+ rMissingChars = aStillMissingChars.toString();
+
+ CTFontDescriptorRef pDesc = CTFontCopyFontDescriptor(pFallback);
+ FontAttributes rAttr = DevFontFromCTFontDescriptor(pDesc, nullptr);
+
+ rPattern.maSearchName = rAttr.GetFamilyName();
+
+ CFRelease(pFallback);
+ CFRelease(pDesc);
+ }
+ CFRelease(pStr);
+ }
+
+ return bFound;
+}
+
+AquaSalGraphics::AquaSalGraphics(bool bPrinter)
+ : mnRealDPIX( 0 )
+ , mnRealDPIY( 0 )
+{
+ SAL_INFO( "vcl.quartz", "AquaSalGraphics::AquaSalGraphics() this=" << this );
+
+#if HAVE_FEATURE_SKIA
+ // tdf#146842 Do not use Skia for printing
+ // Skia does not work with a native print graphics contexts. I am not sure
+ // why but from what I can see, the Skia implementation drawing to a bitmap
+ // buffer. However, in an NSPrintOperation, the print view's backing buffer
+ // is CGPDFContext so even if this bug could be solved by blitting the
+ // Skia bitmap buffer, the printed PDF would not have selectable text so
+ // always disable Skia for print graphics contexts.
+ if(!bPrinter && SkiaHelper::isVCLSkiaEnabled())
+ mpBackend.reset(new AquaSkiaSalGraphicsImpl(*this, maShared));
+#else
+ (void)bPrinter;
+ if(false)
+ ;
+#endif
+ else
+ mpBackend.reset(new AquaGraphicsBackend(maShared));
+
+ for (int i = 0; i < MAX_FALLBACK; ++i)
+ mpFont[i] = nullptr;
+
+ if (comphelper::LibreOfficeKit::isActive())
+ initWidgetDrawBackends(true);
+}
+
+AquaSalGraphics::~AquaSalGraphics()
+{
+ SAL_INFO( "vcl.quartz", "AquaSalGraphics::~AquaSalGraphics() this=" << this );
+
+ maShared.unsetClipPath();
+
+ ReleaseFonts();
+
+ maShared.mpXorEmulation.reset();
+
+#ifdef IOS
+ if (maShared.mbForeignContext)
+ return;
+#endif
+ if (maShared.maLayer.isSet())
+ {
+ CGLayerRelease(maShared.maLayer.get());
+ }
+ else if (maShared.maContextHolder.isSet()
+#ifdef MACOSX
+ && maShared.mbWindow
+#endif
+ )
+ {
+ // destroy backbuffer bitmap context that we created ourself
+ CGContextRelease(maShared.maContextHolder.get());
+ maShared.maContextHolder.set(nullptr);
+ }
+}
+
+SalGraphicsImpl* AquaSalGraphics::GetImpl() const
+{
+ return mpBackend->GetImpl();
+}
+
+void AquaSalGraphics::SetTextColor( Color nColor )
+{
+ maShared.maTextColor = nColor;
+}
+
+void AquaSalGraphics::GetFontMetric(FontMetricDataRef& rxFontMetric, int nFallbackLevel)
+{
+ if (nFallbackLevel < MAX_FALLBACK && mpFont[nFallbackLevel])
+ {
+ mpFont[nFallbackLevel]->GetFontMetric(rxFontMetric);
+ }
+}
+
+static bool AddTempDevFont(const OUString& rFontFileURL)
+{
+ OUString aUSystemPath;
+ OSL_VERIFY( !osl::FileBase::getSystemPathFromFileURL( rFontFileURL, aUSystemPath ) );
+ OString aCFileName = OUStringToOString( aUSystemPath, RTL_TEXTENCODING_UTF8 );
+
+ CFStringRef rFontPath = CFStringCreateWithCString(nullptr, aCFileName.getStr(), kCFStringEncodingUTF8);
+ CFURLRef rFontURL = CFURLCreateWithFileSystemPath(nullptr, rFontPath, kCFURLPOSIXPathStyle, true);
+
+ CFErrorRef error;
+ bool success = CTFontManagerRegisterFontsForURL(rFontURL, kCTFontManagerScopeProcess, &error);
+ if (!success)
+ {
+ CFRelease(error);
+ }
+ CFRelease(rFontPath);
+ CFRelease(rFontURL);
+
+ return success;
+}
+
+static void AddTempFontDir( const OUString &rFontDirUrl )
+{
+ osl::Directory aFontDir( rFontDirUrl );
+ osl::FileBase::RC rcOSL = aFontDir.open();
+ if( rcOSL == osl::FileBase::E_None )
+ {
+ osl::DirectoryItem aDirItem;
+
+ while( aFontDir.getNextItem( aDirItem, 10 ) == osl::FileBase::E_None )
+ {
+ osl::FileStatus aFileStatus( osl_FileStatus_Mask_FileURL );
+ rcOSL = aDirItem.getFileStatus( aFileStatus );
+ if ( rcOSL == osl::FileBase::E_None )
+ {
+ AddTempDevFont(aFileStatus.getFileURL());
+ }
+ }
+ }
+}
+
+static void AddLocalTempFontDirs()
+{
+ static bool bFirst = true;
+ if( !bFirst )
+ return;
+
+ bFirst = false;
+
+ // add private font files
+
+ OUString aBrandStr( "$BRAND_BASE_DIR" );
+ rtl_bootstrap_expandMacros( &aBrandStr.pData );
+
+ // internal font resources, required for normal operation, like OpenSymbol
+ AddTempFontDir( aBrandStr + "/" LIBO_SHARE_RESOURCE_FOLDER "/common/fonts/" );
+
+ AddTempFontDir( aBrandStr + "/" LIBO_SHARE_FOLDER "/fonts/truetype/" );
+}
+
+void AquaSalGraphics::GetDevFontList(vcl::font::PhysicalFontCollection* pFontCollection)
+{
+ SAL_WARN_IF( !pFontCollection, "vcl", "AquaSalGraphics::GetDevFontList(NULL) !");
+
+ AddLocalTempFontDirs();
+
+ SalData* pSalData = GetSalData();
+ pSalData->mpFontList = GetCoretextFontList();
+
+ // Copy all PhysicalFontFace objects contained in the SystemFontList
+ pSalData->mpFontList->AnnounceFonts( *pFontCollection );
+
+ static CoreTextGlyphFallbackSubstititution aSubstFallback;
+ pFontCollection->SetFallbackHook(&aSubstFallback);
+}
+
+void AquaSalGraphics::ClearDevFontCache()
+{
+ SalData* pSalData = GetSalData();
+ pSalData->mpFontList.reset();
+}
+
+bool AquaSalGraphics::AddTempDevFont(vcl::font::PhysicalFontCollection*,
+ const OUString& rFontFileURL, const OUString& /*rFontName*/)
+{
+ return ::AddTempDevFont(rFontFileURL);
+}
+
+void AquaSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+ mpBackend->drawTextLayout(rLayout);
+}
+
+void AquaGraphicsBackend::drawTextLayout(const GenericSalLayout& rLayout)
+{
+#ifdef IOS
+ if (!mrShared.checkContext())
+ {
+ SAL_WARN("vcl.quartz", "AquaSalGraphics::DrawTextLayout() without context");
+ return;
+ }
+#endif
+
+ const CoreTextFont& rFont = *static_cast<const CoreTextFont*>(&rLayout.GetFont());
+ const vcl::font::FontSelectPattern& rFontSelect = rFont.GetFontSelectPattern();
+ if (rFontSelect.mnHeight == 0)
+ {
+ SAL_WARN("vcl.quartz", "AquaSalGraphics::DrawTextLayout(): rFontSelect.mnHeight is zero!?");
+ return;
+ }
+
+ CTFontRef pCTFont = rFont.GetCTFont();
+ CGAffineTransform aRotMatrix = CGAffineTransformMakeRotation(-rFont.mfFontRotation);
+
+ basegfx::B2DPoint aPos;
+ const GlyphItem* pGlyph;
+ std::vector<CGGlyph> aGlyphIds;
+ std::vector<CGPoint> aGlyphPos;
+ std::vector<bool> aGlyphOrientation;
+ int nStart = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ CGPoint aGCPos = CGPointMake(aPos.getX(), -aPos.getY());
+
+ // Whether the glyph should be upright in vertical mode or not
+ bool bUprightGlyph = false;
+
+ if (rFont.mfFontRotation)
+ {
+ if (pGlyph->IsVertical())
+ bUprightGlyph = true;
+ else
+ // Transform the position of rotated glyphs.
+ aGCPos = CGPointApplyAffineTransform(aGCPos, aRotMatrix);
+ }
+
+ aGlyphIds.push_back(pGlyph->glyphId());
+ aGlyphPos.push_back(aGCPos);
+ aGlyphOrientation.push_back(bUprightGlyph);
+ }
+
+ if (aGlyphIds.empty())
+ return;
+
+ assert(aGlyphIds.size() == aGlyphPos.size());
+#if 0
+ std::cerr << "aGlyphIds:[";
+ for (unsigned i = 0; i < aGlyphIds.size(); i++)
+ {
+ if (i > 0)
+ std::cerr << ",";
+ std::cerr << aGlyphIds[i];
+ }
+ std::cerr << "]\n";
+ std::cerr << "aGlyphPos:[";
+ for (unsigned i = 0; i < aGlyphPos.size(); i++)
+ {
+ if (i > 0)
+ std::cerr << ",";
+ std::cerr << aGlyphPos[i];
+ }
+ std::cerr << "]\n";
+#endif
+
+ mrShared.maContextHolder.saveState();
+ RGBAColor textColor( mrShared.maTextColor );
+
+ // The view is vertically flipped (no idea why), flip it back.
+ CGContextScaleCTM(mrShared.maContextHolder.get(), 1.0, -1.0);
+ CGContextSetShouldAntialias(mrShared.maContextHolder.get(), !mrShared.mbNonAntialiasedText);
+ CGContextSetFillColor(mrShared.maContextHolder.get(), textColor.AsArray());
+
+ if (rFont.NeedsArtificialBold())
+ {
+
+ float fSize = rFontSelect.mnHeight / 23.0f;
+ CGContextSetStrokeColor(mrShared.maContextHolder.get(), textColor.AsArray());
+ CGContextSetLineWidth(mrShared.maContextHolder.get(), fSize);
+ CGContextSetTextDrawingMode(mrShared.maContextHolder.get(), kCGTextFillStroke);
+ }
+
+ if (rLayout.GetSubpixelPositioning())
+ {
+ CGContextSetAllowsFontSubpixelQuantization(mrShared.maContextHolder.get(), false);
+ CGContextSetShouldSubpixelQuantizeFonts(mrShared.maContextHolder.get(), false);
+ CGContextSetAllowsFontSubpixelPositioning(mrShared.maContextHolder.get(), true);
+ CGContextSetShouldSubpixelPositionFonts(mrShared.maContextHolder.get(), true);
+ }
+
+ auto aIt = aGlyphOrientation.cbegin();
+ while (aIt != aGlyphOrientation.cend())
+ {
+ bool bUprightGlyph = *aIt;
+ // Find the boundary of the run of glyphs with the same rotation, to be
+ // drawn together.
+ auto aNext = std::find(aIt, aGlyphOrientation.cend(), !bUprightGlyph);
+ size_t nStartIndex = std::distance(aGlyphOrientation.cbegin(), aIt);
+ size_t nLen = std::distance(aIt, aNext);
+
+ mrShared.maContextHolder.saveState();
+ if (rFont.mfFontRotation && !bUprightGlyph)
+ {
+ CGContextRotateCTM(mrShared.maContextHolder.get(), rFont.mfFontRotation);
+ }
+ CTFontDrawGlyphs(pCTFont, &aGlyphIds[nStartIndex], &aGlyphPos[nStartIndex], nLen, mrShared.maContextHolder.get());
+ mrShared.maContextHolder.restoreState();
+
+ aIt = aNext;
+ }
+
+ mrShared.maContextHolder.restoreState();
+}
+
+void AquaSalGraphics::SetFont(LogicalFontInstance* pReqFont, int nFallbackLevel)
+{
+ // release the font
+ for (int i = nFallbackLevel; i < MAX_FALLBACK; ++i)
+ {
+ if (!mpFont[i])
+ break;
+ mpFont[i].clear();
+ }
+
+ if (!pReqFont)
+ return;
+
+ // update the font
+ mpFont[nFallbackLevel] = static_cast<CoreTextFont*>(pReqFont);
+}
+
+std::unique_ptr<GenericSalLayout> AquaSalGraphics::GetTextLayout(int nFallbackLevel)
+{
+ assert(mpFont[nFallbackLevel]);
+ if (!mpFont[nFallbackLevel])
+ return nullptr;
+ return std::make_unique<GenericSalLayout>(*mpFont[nFallbackLevel]);
+}
+
+FontCharMapRef AquaSalGraphics::GetFontCharMap() const
+{
+ if (!mpFont[0])
+ {
+ return FontCharMapRef( new FontCharMap() );
+ }
+
+ return mpFont[0]->GetFontFace()->GetFontCharMap();
+}
+
+bool AquaSalGraphics::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ if (!mpFont[0])
+ return false;
+
+ return mpFont[0]->GetFontFace()->GetFontCapabilities(rFontCapabilities);
+}
+
+void AquaSalGraphics::Flush()
+{
+ mpBackend->Flush();
+}
+
+void AquaSalGraphics::Flush( const tools::Rectangle& rRect )
+{
+ mpBackend->Flush( rRect );
+}
+
+void AquaSalGraphics::WindowBackingPropertiesChanged()
+{
+ mpBackend->WindowBackingPropertiesChanged();
+}
+
+#ifdef IOS
+
+bool AquaSharedAttributes::checkContext()
+{
+ if (mbForeignContext)
+ {
+ SAL_INFO("vcl.ios", "CheckContext() this=" << this << ", mbForeignContext, return true");
+ return true;
+ }
+
+ SAL_INFO( "vcl.ios", "CheckContext() this=" << this << ", not foreign, return false");
+ return false;
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/salgdicommon.cxx b/vcl/quartz/salgdicommon.cxx
new file mode 100644
index 0000000000..98ff40a7dc
--- /dev/null
+++ b/vcl/quartz/salgdicommon.cxx
@@ -0,0 +1,234 @@
+/* -*- 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 <sal/config.h>
+#include <sal/log.hxx>
+
+#include <cassert>
+#include <cstring>
+#include <numeric>
+
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <osl/endian.h>
+#include <osl/file.hxx>
+#include <sal/types.h>
+#include <tools/long.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <quartz/salbmp.h>
+#ifdef MACOSX
+#include <quartz/salgdi.h>
+#endif
+#include <quartz/utils.h>
+#ifdef IOS
+#include <svdata.hxx>
+#endif
+
+using namespace vcl;
+
+#ifndef IOS
+
+void AquaSalGraphics::copyResolution( AquaSalGraphics& rGraphics )
+{
+ if (!rGraphics.mnRealDPIY && rGraphics.maShared.mbWindow && rGraphics.maShared.mpFrame)
+ {
+ rGraphics.initResolution(rGraphics.maShared.mpFrame->getNSWindow());
+ }
+ mnRealDPIX = rGraphics.mnRealDPIX;
+ mnRealDPIY = rGraphics.mnRealDPIY;
+}
+
+#endif
+
+SystemGraphicsData AquaSalGraphics::GetGraphicsData() const
+{
+ SystemGraphicsData aRes;
+ aRes.nSize = sizeof(aRes);
+ aRes.rCGContext = maShared.maContextHolder.get();
+ return aRes;
+}
+
+#ifndef IOS
+
+void AquaSalGraphics::initResolution(NSWindow* nsWindow)
+{
+ if (!nsWindow)
+ {
+ if (Application::IsBitmapRendering())
+ mnRealDPIX = mnRealDPIY = 96;
+ return;
+ }
+
+ // #i100617# read DPI only once; there is some kind of weird caching going on
+ // if the main screen changes
+ // FIXME: this is really unfortunate and needs to be investigated
+
+ SalData* pSalData = GetSalData();
+ if( pSalData->mnDPIX == 0 || pSalData->mnDPIY == 0 )
+ {
+ NSScreen* pScreen = nil;
+
+ /* #i91301#
+ many woes went into the try to have different resolutions
+ on different screens. The result of these trials is that OOo is not ready
+ for that yet, vcl and applications would need to be adapted.
+
+ Unfortunately this is not possible in the 3.0 timeframe.
+ So let's stay with one resolution for all Windows and VirtualDevices
+ which is the resolution of the main screen
+
+ This of course also means that measurements are exact only on the main screen.
+ For activating different resolutions again just comment out the two lines below.
+
+ if( pWin )
+ pScreen = [pWin screen];
+ */
+ if( pScreen == nil )
+ {
+ NSArray* pScreens = [NSScreen screens];
+ if( pScreens && [pScreens count] > 0)
+ {
+ pScreen = [pScreens objectAtIndex: 0];
+ }
+ }
+
+ mnRealDPIX = mnRealDPIY = 96;
+ if( pScreen )
+ {
+ NSDictionary* pDev = [pScreen deviceDescription];
+ if( pDev )
+ {
+ NSNumber* pVal = [pDev objectForKey: @"NSScreenNumber"];
+ if( pVal )
+ {
+ // FIXME: casting a long to CGDirectDisplayID is evil, but
+ // Apple suggest to do it this way
+ const CGDirectDisplayID nDisplayID = static_cast<CGDirectDisplayID>([pVal longValue]);
+ const CGSize aSize = CGDisplayScreenSize( nDisplayID ); // => result is in millimeters
+ mnRealDPIX = static_cast<sal_Int32>((CGDisplayPixelsWide( nDisplayID ) * 25.4) / aSize.width);
+ mnRealDPIY = static_cast<sal_Int32>((CGDisplayPixelsHigh( nDisplayID ) * 25.4) / aSize.height);
+ }
+ else
+ {
+ OSL_FAIL( "no resolution found in device description" );
+ }
+ }
+ else
+ {
+ OSL_FAIL( "no device description" );
+ }
+ }
+ else
+ {
+ OSL_FAIL( "no screen found" );
+ }
+
+ // #i107076# maintaining size-WYSIWYG-ness causes many problems for
+ // low-DPI, high-DPI or for mis-reporting devices
+ // => it is better to limit the calculation result then
+ static const int nMinDPI = 72;
+ if( (mnRealDPIX < nMinDPI) || (mnRealDPIY < nMinDPI) )
+ {
+ mnRealDPIX = mnRealDPIY = nMinDPI;
+ }
+ // Note that on a Retina display, the "mnRealDPIX" as
+ // calculated above is not the true resolution of the display,
+ // but the "logical" one, or whatever the correct terminology
+ // is. (For instance on a 5K 27in iMac, it's 108.) So at
+ // least currently, it won't be over 200. I don't know whether
+ // this test is a "sanity check", or whether there is some
+ // real reason to limit this to 200.
+ static const int nMaxDPI = 200;
+ if( (mnRealDPIX > nMaxDPI) || (mnRealDPIY > nMaxDPI) )
+ {
+ mnRealDPIX = mnRealDPIY = nMaxDPI;
+ }
+ // for OSX any anisotropy reported for the display resolution is best ignored (e.g. TripleHead2Go)
+ mnRealDPIX = mnRealDPIY = (mnRealDPIX + mnRealDPIY + 1) / 2;
+
+ pSalData->mnDPIX = mnRealDPIX;
+ pSalData->mnDPIY = mnRealDPIY;
+ }
+ else
+ {
+ mnRealDPIX = pSalData->mnDPIX;
+ mnRealDPIY = pSalData->mnDPIY;
+ }
+}
+
+#endif
+
+void AquaSharedAttributes::setState()
+{
+ maContextHolder.restoreState();
+ maContextHolder.saveState();
+
+ // setup clipping
+ if (mxClipPath)
+ {
+ CGContextBeginPath(maContextHolder.get()); // discard any existing path
+ CGContextAddPath(maContextHolder.get(), mxClipPath); // set the current path to the clipping path
+ CGContextClip(maContextHolder.get()); // use it for clipping
+ }
+
+ // set RGB colorspace and line and fill colors
+ CGContextSetFillColor(maContextHolder.get(), maFillColor.AsArray() );
+
+ CGContextSetStrokeColor(maContextHolder.get(), maLineColor.AsArray() );
+ CGContextSetShouldAntialias(maContextHolder.get(), false );
+ if (mnXorMode == 2)
+ {
+ CGContextSetBlendMode(maContextHolder.get(), kCGBlendModeDifference );
+ }
+}
+
+#ifndef IOS
+
+void AquaSalGraphics::updateResolution()
+{
+ SAL_WARN_IF(!maShared.mbWindow, "vcl", "updateResolution on inappropriate graphics");
+
+ initResolution((maShared.mbWindow && maShared.mpFrame) ? maShared.mpFrame->getNSWindow() : nil);
+}
+
+#endif
+
+XorEmulation::XorEmulation()
+ : m_xTargetLayer( nullptr )
+ , m_xTargetContext( nullptr )
+ , m_xMaskContext( nullptr )
+ , m_xTempContext( nullptr )
+ , m_pMaskBuffer( nullptr )
+ , m_pTempBuffer( nullptr )
+ , m_nBufferLongs( 0 )
+ , m_bIsEnabled( false )
+{
+ SAL_INFO( "vcl.quartz", "XorEmulation::XorEmulation() this=" << this );
+}
+
+XorEmulation::~XorEmulation()
+{
+ SAL_INFO( "vcl.quartz", "XorEmulation::~XorEmulation() this=" << this );
+ Disable();
+ SetTarget( 0, 0, 0, nullptr, nullptr );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/salvd.cxx b/vcl/quartz/salvd.cxx
new file mode 100644
index 0000000000..4e0c295a17
--- /dev/null
+++ b/vcl/quartz/salvd.cxx
@@ -0,0 +1,177 @@
+/* -*- 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 <sal/config.h>
+#include <sal/log.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+
+#ifdef MACOSX
+#include <osx/salinst.h>
+#include <osx/saldata.hxx>
+#include <osx/salframe.h>
+#else
+#include <ios/iosinst.hxx>
+#include "headless/svpframe.hxx"
+#include "headless/svpinst.hxx"
+#include "headless/svpvd.hxx"
+#endif
+#include <quartz/salgdi.h>
+#include <quartz/salvd.h>
+#include <quartz/utils.h>
+
+std::unique_ptr<SalVirtualDevice> AquaSalInstance::CreateVirtualDevice( SalGraphics& rGraphics,
+ tools::Long &nDX, tools::Long &nDY,
+ DeviceFormat eFormat,
+ const SystemGraphicsData *pData )
+{
+ // #i92075# can be called first in a thread
+ SalData::ensureThreadAutoreleasePool();
+
+#ifdef IOS
+ if( pData )
+ {
+ return std::unique_ptr<SalVirtualDevice>(new AquaSalVirtualDevice( static_cast< AquaSalGraphics* >(&rGraphics),
+ nDX, nDY, eFormat, pData ));
+ }
+ else
+ {
+ std::unique_ptr<SalVirtualDevice> pNew(new AquaSalVirtualDevice( NULL, nDX, nDY, eFormat, NULL ));
+ pNew->SetSize( nDX, nDY );
+ return pNew;
+ }
+#else
+ return std::unique_ptr<SalVirtualDevice>(new AquaSalVirtualDevice( static_cast< AquaSalGraphics* >(&rGraphics),
+ nDX, nDY, eFormat, pData ));
+#endif
+}
+
+AquaSalVirtualDevice::AquaSalVirtualDevice(
+ AquaSalGraphics* pGraphic, tools::Long &nDX, tools::Long &nDY,
+ DeviceFormat eFormat, const SystemGraphicsData *pData )
+ : mbGraphicsUsed( false )
+ , mnBitmapDepth( 0 )
+ , mnWidth(0)
+ , mnHeight(0)
+{
+ SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::AquaSalVirtualDevice() this=" << this
+ << " size=(" << nDX << "x" << nDY << ") bitcount=" << static_cast<int>(eFormat) <<
+ " pData=" << pData << " context=" << (pData ? pData->rCGContext : nullptr) );
+
+ if( pGraphic && pData && pData->rCGContext )
+ {
+ // Create virtual device based on existing SystemGraphicsData
+ // We ignore nDx and nDY, as the desired size comes from the SystemGraphicsData.
+ // the mxContext is from pData (what "mxContext"? there is no such field anywhere in vcl;)
+ mbForeignContext = true;
+ mpGraphics = new AquaSalGraphics( /*pGraphic*/ );
+ if (nDX == 0)
+ {
+ nDX = 1;
+ }
+ if (nDY == 0)
+ {
+ nDY = 1;
+ }
+ maLayer.set(CGLayerCreateWithContext(pData->rCGContext, CGSizeMake(nDX, nDY), nullptr));
+ // Interrogate the context as to its real size
+ if (maLayer.isSet())
+ {
+ const CGSize aSize = CGLayerGetSize(maLayer.get());
+ nDX = static_cast<tools::Long>(aSize.width);
+ nDY = static_cast<tools::Long>(aSize.height);
+ }
+ else
+ {
+ nDX = 0;
+ nDY = 0;
+ }
+
+ mpGraphics->SetVirDevGraphics(this, maLayer, pData->rCGContext);
+
+ SAL_INFO("vcl.virdev", "AquaSalVirtualDevice::AquaSalVirtualDevice() this=" << this <<
+ " (" << nDX << "x" << nDY << ") mbForeignContext=" << (mbForeignContext ? "YES" : "NO"));
+
+ }
+ else
+ {
+ // create empty new virtual device
+ mbForeignContext = false; // the mxContext is created within VCL
+ mpGraphics = new AquaSalGraphics(); // never fails
+ switch (eFormat)
+ {
+#ifdef IOS
+ case DeviceFormat::GRAYSCALE:
+ mnBitmapDepth = 8;
+ break;
+#endif
+ default:
+ mnBitmapDepth = 0;
+ break;
+ }
+#ifdef MACOSX
+ // inherit resolution from reference device
+ if( pGraphic )
+ {
+ AquaSalFrame* pFrame = pGraphic->getGraphicsFrame();
+ if( pFrame && AquaSalFrame::isAlive( pFrame ) )
+ {
+ mpGraphics->setGraphicsFrame( pFrame );
+ mpGraphics->copyResolution( *pGraphic );
+ }
+ }
+#endif
+ if( nDX && nDY )
+ {
+ SetSize( nDX, nDY );
+ }
+ // NOTE: if SetSize does not succeed, we just ignore the nDX and nDY
+ }
+}
+
+AquaSalVirtualDevice::~AquaSalVirtualDevice()
+{
+ SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::~AquaSalVirtualDevice() this=" << this );
+
+ if( mpGraphics )
+ {
+ mpGraphics->SetVirDevGraphics( this, nullptr, nullptr );
+ delete mpGraphics;
+ mpGraphics = nullptr;
+ }
+ Destroy();
+}
+
+SalGraphics* AquaSalVirtualDevice::AcquireGraphics()
+{
+ if( mbGraphicsUsed || !mpGraphics )
+ {
+ return nullptr;
+ }
+ mbGraphicsUsed = true;
+ return mpGraphics;
+}
+
+void AquaSalVirtualDevice::ReleaseGraphics( SalGraphics* )
+{
+ mbGraphicsUsed = false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/utils.cxx b/vcl/quartz/utils.cxx
new file mode 100644
index 0000000000..b07e68f746
--- /dev/null
+++ b/vcl/quartz/utils.cxx
@@ -0,0 +1,242 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <rtl/alloc.h>
+#include <rtl/ustrbuf.hxx>
+
+#include <quartz/utils.h>
+
+OUString GetOUString( CFStringRef rStr )
+{
+ if( rStr == nullptr )
+ {
+ return OUString();
+ }
+
+ CFIndex nLength = CFStringGetLength( rStr );
+ if( nLength == 0 )
+ {
+ return OUString();
+ }
+
+ const UniChar* pConstStr = CFStringGetCharactersPtr( rStr );
+ if( pConstStr )
+ {
+ return OUString( reinterpret_cast<sal_Unicode const *>(pConstStr), nLength );
+ }
+
+ std::unique_ptr<UniChar[]> pStr(new UniChar[nLength]);
+ CFRange aRange = { 0, nLength };
+ CFStringGetCharacters( rStr, aRange, pStr.get() );
+
+ OUString aRet( reinterpret_cast<sal_Unicode *>(pStr.get()), nLength );
+ return aRet;
+}
+
+OUString GetOUString( const NSString* pStr )
+{
+ if( ! pStr )
+ {
+ return OUString();
+ }
+
+ int nLen = [pStr length];
+ if( nLen == 0 )
+ {
+ return OUString();
+ }
+
+ OUStringBuffer aBuf( nLen+1 );
+ aBuf.setLength( nLen );
+ [pStr getCharacters:
+ reinterpret_cast<unichar *>(const_cast<sal_Unicode*>(aBuf.getStr()))];
+
+ return aBuf.makeStringAndClear();
+}
+
+CFStringRef CreateCFString( const OUString& rStr )
+{
+ return CFStringCreateWithCharacters(kCFAllocatorDefault, reinterpret_cast<UniChar const *>(rStr.getStr()), rStr.getLength() );
+}
+
+NSString* CreateNSString( const OUString& rStr )
+{
+ return [[NSString alloc] initWithCharacters: reinterpret_cast<unichar const *>(rStr.getStr()) length: rStr.getLength()];
+}
+
+OUString NSStringArrayToOUString(NSArray* array)
+{
+ OUString result = "[";
+ OUString sep;
+ for (NSUInteger i = 0; i < [array count]; i++)
+ {
+ result = result + sep + OUString::fromUtf8([[array objectAtIndex:i] UTF8String]);
+ sep = ",";
+ }
+ result = result + "]";
+ return result;
+}
+
+OUString NSDictionaryKeysToOUString(NSDictionary* dict)
+{
+ OUString result = "{";
+ OUString sep;
+ for (NSString *key in dict)
+ {
+ result = result + sep + OUString::fromUtf8([key UTF8String]);
+ sep = ",";
+ }
+ result = result + "}";
+ return result;
+}
+
+std::ostream &operator <<(std::ostream& s, const CGRect &rRect)
+{
+#ifndef SAL_LOG_INFO
+ (void) rRect;
+#else
+ if (CGRectIsNull(rRect))
+ {
+ s << "NULL";
+ }
+ else
+ {
+ s << rRect.size << "@" << rRect.origin;
+ }
+#endif
+ return s;
+}
+
+std::ostream &operator <<(std::ostream& s, const CGPoint &rPoint)
+{
+#ifndef SAL_LOG_INFO
+ (void) rPoint;
+#else
+ s << "(" << rPoint.x << "," << rPoint.y << ")";
+#endif
+ return s;
+}
+
+std::ostream &operator <<(std::ostream& s, const CGSize &rSize)
+{
+#ifndef SAL_LOG_INFO
+ (void) rSize;
+#else
+ s << rSize.width << "x" << rSize.height;
+#endif
+ return s;
+}
+
+std::ostream &operator <<(std::ostream& s, CGColorRef pColor)
+{
+#ifndef SAL_LOG_INFO
+ (void) pColor;
+#else
+ CFStringRef colorString = CFCopyDescription(pColor);
+ if (colorString)
+ {
+ s << GetOUString(colorString);
+ CFRelease(colorString);
+ }
+ else
+ {
+ s << "NULL";
+ }
+#endif
+ return s;
+}
+
+std::ostream &operator <<(std::ostream& s, const CGAffineTransform &aXform)
+{
+#ifndef SAL_LOG_INFO
+ (void) aXform;
+#else
+ if (CGAffineTransformIsIdentity(aXform))
+ {
+ s << "IDENT";
+ }
+ else
+ {
+ s << "[" << aXform.a << "," << aXform.b << "," << aXform.c << "," << aXform.d << "," << aXform.tx << "," << aXform.ty << "]";
+ }
+#endif
+ return s;
+}
+
+std::ostream &operator <<(std::ostream& s, CGColorSpaceRef cs)
+{
+#ifndef SAL_LOG_INFO
+ (void) cs;
+#else
+ if (cs == nullptr)
+ {
+ s << "null";
+ return s;
+ }
+
+ CGColorSpaceModel model = CGColorSpaceGetModel(cs);
+ switch (model)
+ {
+ case kCGColorSpaceModelUnknown:
+ s << "Unknown";
+ break;
+ case kCGColorSpaceModelMonochrome:
+ s << "Monochrome";
+ break;
+ case kCGColorSpaceModelRGB:
+ s << "RGB";
+ if (CGColorSpaceIsWideGamutRGB(cs))
+ s << " (wide gamut)";
+ break;
+ case kCGColorSpaceModelCMYK:
+ s << "CMYK";
+ break;
+ case kCGColorSpaceModelLab:
+ s << "Lab";
+ break;
+ case kCGColorSpaceModelDeviceN:
+ s << "DeviceN";
+ break;
+ case kCGColorSpaceModelIndexed:
+ s << "Indexed (" << CGColorSpaceGetColorTableCount(cs) << ")";
+ break;
+ case kCGColorSpaceModelPattern:
+ s << "Pattern";
+ break;
+ case kCGColorSpaceModelXYZ:
+ s << "XYZ";
+ break;
+ default:
+ s << "?(" << model << ")";
+ break;
+ }
+
+ CFStringRef name = CGColorSpaceCopyName(cs);
+ if (name != nullptr)
+ s << " (" << [static_cast<NSString *>(name) UTF8String] << ")";
+#endif
+ return s;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/skia/README b/vcl/skia/README
new file mode 100644
index 0000000000..63f6073bac
--- /dev/null
+++ b/vcl/skia/README
@@ -0,0 +1,87 @@
+This is code for using the Skia library as a drawing library in VCL backends.
+See external/skia for info on the library itself.
+
+Environment variables:
+======================
+
+See README.vars in the toplevel vcl/ directory. Note that many backends do not
+use Skia. E.g. on Linux it is necessary to also use SAL_USE_VCLPLUGIN=gen .
+
+There are also GUI options for controlling whether Skia is enabled.
+
+Skia drawing methods:
+=====================
+
+Skia supports several methods to draw:
+- Raster - CPU-based drawing (here primarily used for fallback when Vulkan isn't available or for debugging)
+- Vulkan - Vulkan-based GPU drawing, this is the default
+- Metal - MACOSX GPU drawing, this is the Mac default
+
+There are more (OpenGL, Metal on Mac, etc.), but (as of now) they are not supported by VCL.
+
+Logging:
+========
+
+Run LO with 'SAL_LOG=+INFO.vcl.skia' to get log information about Skia including
+tracing each drawing operation. If you want log information without drawing operations,
+use 'SAL_LOG=+INFO.vcl.skia-INFO.vcl.skia.trace'.
+
+Debugging:
+==========
+
+Both SkiaSalBitmap and SkiaSalGraphicsImpl have a dump() method that writes a PNG
+with the current contents. There is also SkiaHelper::dump() for dumping contents
+of SkBitmap, SkImage and SkSurface. You can use these in a debugger too, for example
+'p SkiaHelper::dump(image, "/tmp/a.png")'.
+
+If there is a drawing problem, you can use something like the following piece of code
+to dump an image after each relevant operation (or do it in postDraw() if you don't
+know which operation is responsible). You can then find the relevant image
+and match it with the responsible operation (run LO with 'SAL_LOG=+INFO.vcl.skia').
+
+ static int cnt = 0;
+ ++cnt;
+ char buf[100];
+ sprintf(buf,"/tmp/a%05d.png", cnt);
+ SAL_DEBUG("CNT:" << cnt);
+ if(cnt > 4000) // skip some initial drawing operations
+ dump(buf);
+
+
+Testing:
+========
+
+Currently unittests always use the 'headless' VCL backend. Use something like the following
+to run VCL unittests with Skia (and possibly skip slowcheck):
+
+SAL_SKIA=raster SAL_ENABLESKIA=1 SAL_USE_VCLPLUGIN=gen make vcl.build vcl.unitcheck vcl.slowcheck
+
+You can also use 'visualbackendtest' to visually check some operations. Use something like:
+
+SAL_SKIA=raster SAL_ENABLESKIA=1 SAL_USE_VCLPLUGIN=gen [srcdir]/bin/run visualbackendtest
+
+
+Thread safety:
+==============
+
+SolarMutex must be held for most operations (asserted in SkiaSalGraphicsImpl::preDraw() and
+in SkiaZone constructor). The reason for this is that this restriction does not appear to be
+a problem, so there's no need to verify thread safety of the code (including the Skia library).
+There's probably no fundamental reason why the code couldn't be made thread-safe.
+
+
+GrDirectContext sharing:
+========================
+
+We use Skia's sk_app::WindowContext class for creating surfaces for windows, that class
+takes care of the internals. But of offscreen drawing, we need an instance of class
+GrDirectContext. There is sk_app::WindowContext::getGrDirectContext(), but each instance creates
+its own GrDirectContext, and apparently it does not work to mix them. Which means that
+for offscreen drawing we would need to know which window (and only that window)
+the contents will be eventually painted to, which is not possible (it may not even
+be known at the time).
+
+To solve this problem we patch sk_app::WindowContext to create just one GrDirectContext object
+and share it between instances. Additionally, using sk_app::WindowContext::SharedGrDirectContext
+it is possible to share it also for offscreen drawing, including keeping proper reference
+count.
diff --git a/vcl/skia/SkiaHelper.cxx b/vcl/skia/SkiaHelper.cxx
new file mode 100644
index 0000000000..712ba39d75
--- /dev/null
+++ b/vcl/skia/SkiaHelper.cxx
@@ -0,0 +1,914 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/skia/SkiaHelper.hxx>
+
+#if !HAVE_FEATURE_SKIA
+
+namespace SkiaHelper
+{
+bool isVCLSkiaEnabled() { return false; }
+bool isAlphaMaskBlendingEnabled() { return false; }
+
+} // namespace
+
+#else
+
+#include <rtl/bootstrap.hxx>
+#include <vcl/svapp.hxx>
+#include <desktop/crashreport.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <watchdog.hxx>
+#include <skia/zone.hxx>
+#include <sal/log.hxx>
+#include <driverblocklist.hxx>
+#include <skia/utils.hxx>
+#include <config_folders.h>
+#include <osl/file.hxx>
+#include <tools/stream.hxx>
+#include <list>
+#include <o3tl/lru_map.hxx>
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkEncodedImageFormat.h>
+#include <SkPaint.h>
+#include <SkSurface.h>
+#include <SkGraphics.h>
+#include <GrDirectContext.h>
+#include <SkRuntimeEffect.h>
+#include <SkStream.h>
+#include <SkTileMode.h>
+#include <skia_compiler.hxx>
+#include <skia_opts.hxx>
+#if defined(MACOSX)
+#include <premac.h>
+#endif
+#include <tools/sk_app/VulkanWindowContext.h>
+#include <tools/sk_app/MetalWindowContext.h>
+#if defined(MACOSX)
+#include <postmac.h>
+#endif
+#include <src/core/SkOpts.h>
+#include <src/core/SkChecksum.h>
+#include <include/encode/SkPngEncoder.h>
+#include <ganesh/SkSurfaceGanesh.h>
+#if defined _MSC_VER
+#pragma warning(disable : 4100) // "unreferenced formal parameter"
+#pragma warning(disable : 4324) // "structure was padded due to alignment specifier"
+#endif
+#if defined __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-parameter"
+#endif
+#if defined __GNUC__ && !defined __clang__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#endif
+#include <src/image/SkImage_Base.h>
+#if defined __GNUC__ && !defined __clang__
+#pragma GCC diagnostic pop
+#endif
+#if defined __clang__
+#pragma clang diagnostic pop
+#endif
+
+#include <fstream>
+
+#ifdef SK_METAL
+#ifdef MACOSX
+#include <quartz/cgutils.h>
+#endif
+#endif
+
+namespace SkiaHelper
+{
+static 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;
+}
+
+static void writeToLog(SvStream& stream, const char* key, const char* value)
+{
+ stream.WriteOString(key);
+ stream.WriteOString(": ");
+ stream.WriteOString(value);
+ stream.WriteChar('\n');
+}
+
+OUString readLog()
+{
+ SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::READ);
+
+ OUString sResult;
+ OString sLine;
+ while (logFile.ReadLine(sLine))
+ sResult += OStringToOUString(sLine, RTL_TEXTENCODING_UTF8) + "\n";
+
+ return sResult;
+}
+
+uint32_t vendorId = 0;
+
+#ifdef SK_VULKAN
+static void writeToLog(SvStream& stream, const char* key, std::u16string_view value)
+{
+ writeToLog(stream, key, OUStringToOString(value, RTL_TEXTENCODING_UTF8).getStr());
+}
+
+static OUString getDenylistFile()
+{
+ OUString url("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER);
+ rtl::Bootstrap::expandMacros(url);
+
+ return url + "/skia/skia_denylist_vulkan.xml";
+}
+
+static uint32_t driverVersion = 0;
+
+static OUString versionAsString(uint32_t version)
+{
+ return OUString::number(version >> 22) + "." + OUString::number((version >> 12) & 0x3ff) + "."
+ + OUString::number(version & 0xfff);
+}
+
+static std::string_view vendorAsString(uint32_t vendor)
+{
+ return DriverBlocklist::GetVendorNameFromId(vendor);
+}
+
+// Note that this function also logs system information about Vulkan.
+static bool isVulkanDenylisted(const VkPhysicalDeviceProperties& props)
+{
+ static const char* const types[]
+ = { "other", "integrated", "discrete", "virtual", "cpu", "??" }; // VkPhysicalDeviceType
+ driverVersion = props.driverVersion;
+ vendorId = props.vendorID;
+ OUString vendorIdStr = "0x" + OUString::number(props.vendorID, 16);
+ OUString deviceIdStr = "0x" + OUString::number(props.deviceID, 16);
+ OUString driverVersionString = versionAsString(driverVersion);
+ OUString apiVersion = versionAsString(props.apiVersion);
+ const char* deviceType = types[std::min<unsigned>(props.deviceType, SAL_N_ELEMENTS(types) - 1)];
+
+ CrashReporter::addKeyValue("VulkanVendor", vendorIdStr, CrashReporter::AddItem);
+ CrashReporter::addKeyValue("VulkanDevice", deviceIdStr, CrashReporter::AddItem);
+ CrashReporter::addKeyValue("VulkanAPI", apiVersion, CrashReporter::AddItem);
+ CrashReporter::addKeyValue("VulkanDriver", driverVersionString, CrashReporter::AddItem);
+ CrashReporter::addKeyValue("VulkanDeviceType", OUString::createFromAscii(deviceType),
+ CrashReporter::AddItem);
+ CrashReporter::addKeyValue("VulkanDeviceName", OUString::createFromAscii(props.deviceName),
+ CrashReporter::Write);
+
+ SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::WRITE | StreamMode::TRUNC);
+ writeToLog(logFile, "RenderMethod", "vulkan");
+ writeToLog(logFile, "Vendor", vendorIdStr);
+ writeToLog(logFile, "Device", deviceIdStr);
+ writeToLog(logFile, "API", apiVersion);
+ writeToLog(logFile, "Driver", driverVersionString);
+ writeToLog(logFile, "DeviceType", deviceType);
+ writeToLog(logFile, "DeviceName", props.deviceName);
+
+ SAL_INFO("vcl.skia",
+ "Vulkan API version: " << apiVersion << ", driver version: " << driverVersionString
+ << ", vendor: " << vendorIdStr << " ("
+ << vendorAsString(vendorId) << "), device: " << deviceIdStr
+ << ", type: " << deviceType << ", name: " << props.deviceName);
+ bool denylisted
+ = DriverBlocklist::IsDeviceBlocked(getDenylistFile(), DriverBlocklist::VersionType::Vulkan,
+ driverVersionString, vendorIdStr, deviceIdStr);
+ writeToLog(logFile, "Denylisted", denylisted ? "yes" : "no");
+ return denylisted;
+}
+#endif
+
+#ifdef SK_METAL
+static void writeSkiaMetalInfo()
+{
+ SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::WRITE | StreamMode::TRUNC);
+ writeToLog(logFile, "RenderMethod", "metal");
+}
+#endif
+
+static void writeSkiaRasterInfo()
+{
+ SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::WRITE | StreamMode::TRUNC);
+ writeToLog(logFile, "RenderMethod", "raster");
+ // Log compiler, Skia works best when compiled with Clang.
+ writeToLog(logFile, "Compiler", skia_compiler_name());
+}
+
+#if defined(SK_VULKAN) || defined(SK_METAL)
+static std::unique_ptr<sk_app::WindowContext> getTemporaryWindowContext();
+#endif
+
+static void checkDeviceDenylisted(bool blockDisable = false)
+{
+ static bool done = false;
+ if (done)
+ return;
+
+ SkiaZone zone;
+
+ bool useRaster = false;
+ switch (renderMethodToUse())
+ {
+ case RenderVulkan:
+ {
+#ifdef SK_VULKAN
+ // First try if a GrDirectContext already exists.
+ std::unique_ptr<sk_app::WindowContext> temporaryWindowContext;
+ GrDirectContext* grDirectContext
+ = sk_app::VulkanWindowContext::getSharedGrDirectContext();
+ if (!grDirectContext)
+ {
+ // This function is called from isVclSkiaEnabled(), which
+ // may be called when deciding which X11 visual to use,
+ // and that visual is normally needed when creating
+ // Skia's VulkanWindowContext, which is needed for the GrDirectContext.
+ // Avoid the loop by creating a temporary WindowContext
+ // that will use the default X11 visual (that shouldn't matter
+ // for just finding out information about Vulkan) and destroying
+ // the temporary context will clean up again.
+ temporaryWindowContext = getTemporaryWindowContext();
+ grDirectContext = sk_app::VulkanWindowContext::getSharedGrDirectContext();
+ }
+ bool denylisted = true; // assume the worst
+ if (grDirectContext) // Vulkan was initialized properly
+ {
+ denylisted
+ = isVulkanDenylisted(sk_app::VulkanWindowContext::getPhysDeviceProperties());
+ SAL_INFO("vcl.skia", "Vulkan denylisted: " << denylisted);
+ }
+ else
+ SAL_INFO("vcl.skia", "Vulkan could not be initialized");
+ if (denylisted && !blockDisable)
+ {
+ disableRenderMethod(RenderVulkan);
+ useRaster = true;
+ }
+#else
+ SAL_WARN("vcl.skia", "Vulkan support not built in");
+ (void)blockDisable;
+ useRaster = true;
+#endif
+ break;
+ }
+ case RenderMetal:
+ {
+#ifdef SK_METAL
+ // First try if a GrDirectContext already exists.
+ std::unique_ptr<sk_app::WindowContext> temporaryWindowContext;
+ GrDirectContext* grDirectContext = sk_app::getMetalSharedGrDirectContext();
+ if (!grDirectContext)
+ {
+ // Create a temporary window context just to get the GrDirectContext,
+ // as an initial test of Metal functionality.
+ temporaryWindowContext = getTemporaryWindowContext();
+ grDirectContext = sk_app::getMetalSharedGrDirectContext();
+ }
+ if (grDirectContext) // Metal was initialized properly
+ {
+#ifdef MACOSX
+ if (!blockDisable && !DefaultMTLDeviceIsSupported())
+ {
+ SAL_INFO("vcl.skia", "Metal default device not supported");
+ disableRenderMethod(RenderMetal);
+ useRaster = true;
+ }
+ else
+#endif
+ {
+ SAL_INFO("vcl.skia", "Using Skia Metal mode");
+ writeSkiaMetalInfo();
+ }
+ }
+ else
+ {
+ SAL_INFO("vcl.skia", "Metal could not be initialized");
+ disableRenderMethod(RenderMetal);
+ useRaster = true;
+ }
+#else
+ SAL_WARN("vcl.skia", "Metal support not built in");
+ useRaster = true;
+#endif
+ break;
+ }
+ case RenderRaster:
+ useRaster = true;
+ break;
+ }
+ if (useRaster)
+ {
+ SAL_INFO("vcl.skia", "Using Skia raster mode");
+ // software, never denylisted
+ writeSkiaRasterInfo();
+ }
+ done = true;
+}
+
+static bool skiaSupportedByBackend = false;
+static bool supportsVCLSkia()
+{
+ if (!skiaSupportedByBackend)
+ {
+ SAL_INFO("vcl.skia", "Skia not supported by VCL backend, disabling");
+ return false;
+ }
+ return getenv("SAL_DISABLESKIA") == nullptr;
+}
+
+static void initInternal();
+
+bool isVCLSkiaEnabled()
+{
+ /**
+ * The !bSet part should only be called once! Changing the results in the same
+ * run will mix Skia and normal rendering.
+ */
+
+ static bool bSet = false;
+ static bool bEnable = false;
+ static bool bForceSkia = false;
+
+ // allow global disable when testing SystemPrimitiveRenderer since current Skia on Win does not
+ // harmonize with using Direct2D and D2DPixelProcessor2D
+ static const bool bTestSystemPrimitiveRenderer(
+ nullptr != std::getenv("TEST_SYSTEM_PRIMITIVE_RENDERER"));
+ if (bTestSystemPrimitiveRenderer)
+ return false;
+
+ // No hardware rendering, so no Skia
+ // TODO SKIA
+ if (Application::IsBitmapRendering())
+ return false;
+
+ if (bSet)
+ {
+ return bForceSkia || bEnable;
+ }
+
+ /*
+ * There are a number of cases that these environment variables cover:
+ * * SAL_FORCESKIA forces Skia if disabled by UI options or denylisted
+ * * SAL_DISABLESKIA avoids the use of Skia regardless of any option
+ */
+
+ bSet = true;
+ bForceSkia = !!getenv("SAL_FORCESKIA") || officecfg::Office::Common::VCL::ForceSkia::get();
+
+ bool bRet = false;
+ bool bSupportsVCLSkia = supportsVCLSkia();
+ if (bForceSkia && bSupportsVCLSkia)
+ {
+ bRet = true;
+ initInternal();
+ // don't actually block if denylisted, but log it if enabled, and also get the vendor id
+ checkDeviceDenylisted(true);
+ }
+ else if (getenv("SAL_FORCEGL"))
+ {
+ // Skia usage is checked before GL usage, so if GL is forced (and Skia is not), do not
+ // enable Skia in order to allow GL.
+ bRet = false;
+ }
+ else if (bSupportsVCLSkia)
+ {
+ static bool bEnableSkiaEnv = !!getenv("SAL_ENABLESKIA");
+
+ bEnable = bEnableSkiaEnv;
+
+ if (officecfg::Office::Common::VCL::UseSkia::get())
+ bEnable = true;
+
+ // Force disable in safe mode
+ if (Application::IsSafeModeEnabled())
+ bEnable = false;
+
+ if (bEnable)
+ {
+ initInternal();
+ checkDeviceDenylisted(); // switch to raster if driver is denylisted
+ }
+
+ bRet = bEnable;
+ }
+
+ if (bRet)
+ WatchdogThread::start();
+
+ CrashReporter::addKeyValue("UseSkia", OUString::boolean(bRet), CrashReporter::Write);
+
+ return bRet;
+}
+
+bool isAlphaMaskBlendingEnabled() { return false; }
+
+static RenderMethod methodToUse = RenderRaster;
+
+static bool initRenderMethodToUse()
+{
+ if (const char* env = getenv("SAL_SKIA"))
+ {
+ if (strcmp(env, "raster") == 0)
+ {
+ methodToUse = RenderRaster;
+ return true;
+ }
+#ifdef MACOSX
+ if (strcmp(env, "metal") == 0)
+ {
+ methodToUse = RenderMetal;
+ return true;
+ }
+#else
+ if (strcmp(env, "vulkan") == 0)
+ {
+ methodToUse = RenderVulkan;
+ return true;
+ }
+#endif
+ SAL_WARN("vcl.skia", "Unrecognized value of SAL_SKIA");
+ abort();
+ }
+ methodToUse = RenderRaster;
+ if (officecfg::Office::Common::VCL::ForceSkiaRaster::get())
+ return true;
+#ifdef SK_METAL
+ methodToUse = RenderMetal;
+#endif
+#ifdef SK_VULKAN
+ methodToUse = RenderVulkan;
+#endif
+ return true;
+}
+
+RenderMethod renderMethodToUse()
+{
+ static bool methodToUseInited = initRenderMethodToUse();
+ if (methodToUseInited) // Used just to ensure thread-safe one-time init.
+ return methodToUse;
+ abort();
+}
+
+void disableRenderMethod(RenderMethod method)
+{
+ if (renderMethodToUse() != method)
+ return;
+ // Choose a fallback, right now always raster.
+ methodToUse = RenderRaster;
+}
+
+// If needed, we'll allocate one extra window context so that we have a valid GrDirectContext
+// from Vulkan/MetalWindowContext.
+static std::unique_ptr<sk_app::WindowContext> sharedWindowContext;
+
+static std::unique_ptr<sk_app::WindowContext> (*createGpuWindowContextFunction)(bool) = nullptr;
+static void setCreateGpuWindowContext(std::unique_ptr<sk_app::WindowContext> (*function)(bool))
+{
+ createGpuWindowContextFunction = function;
+}
+
+GrDirectContext* getSharedGrDirectContext()
+{
+ SkiaZone zone;
+ assert(renderMethodToUse() != RenderRaster);
+ if (sharedWindowContext)
+ return sharedWindowContext->directContext();
+ // TODO mutex?
+ // Set up the shared GrDirectContext from Skia's (patched) Vulkan/MetalWindowContext, if it's been
+ // already set up.
+ switch (renderMethodToUse())
+ {
+ case RenderVulkan:
+#ifdef SK_VULKAN
+ if (GrDirectContext* context = sk_app::VulkanWindowContext::getSharedGrDirectContext())
+ return context;
+#endif
+ break;
+ case RenderMetal:
+#ifdef SK_METAL
+ if (GrDirectContext* context = sk_app::getMetalSharedGrDirectContext())
+ return context;
+#endif
+ break;
+ case RenderRaster:
+ abort();
+ }
+ static bool done = false;
+ if (done)
+ return nullptr;
+ done = true;
+ if (createGpuWindowContextFunction == nullptr)
+ return nullptr; // not initialized properly (e.g. used from a VCL backend with no Skia support)
+ sharedWindowContext = createGpuWindowContextFunction(false);
+ GrDirectContext* grDirectContext
+ = sharedWindowContext ? sharedWindowContext->directContext() : nullptr;
+ if (grDirectContext)
+ return grDirectContext;
+ SAL_WARN_IF(renderMethodToUse() == RenderVulkan, "vcl.skia",
+ "Cannot create Vulkan GPU context, falling back to Raster");
+ SAL_WARN_IF(renderMethodToUse() == RenderMetal, "vcl.skia",
+ "Cannot create Metal GPU context, falling back to Raster");
+ disableRenderMethod(renderMethodToUse());
+ return nullptr;
+}
+
+#if defined(SK_VULKAN) || defined(SK_METAL)
+static std::unique_ptr<sk_app::WindowContext> getTemporaryWindowContext()
+{
+ if (createGpuWindowContextFunction == nullptr)
+ return nullptr;
+ return createGpuWindowContextFunction(true);
+}
+#endif
+
+static RenderMethod renderMethodToUseForSize(const SkISize& size)
+{
+ // Do not use GPU for small surfaces. The problem is that due to the separate alpha hack
+ // we quite often may call GetBitmap() on VirtualDevice, which is relatively slow
+ // when the pixels need to be fetched from the GPU. And there are documents that use
+ // many tiny surfaces (bsc#1183308 for example), where this slowness adds up too much.
+ // This should be re-evaluated once the separate alpha hack is removed (SKIA_USE_BITMAP32)
+ // and we no longer (hopefully) fetch pixels that often.
+ if (size.width() <= 32 && size.height() <= 32)
+ return RenderRaster;
+ return renderMethodToUse();
+}
+
+sk_sp<SkSurface> createSkSurface(int width, int height, SkColorType type, SkAlphaType alpha)
+{
+ SkiaZone zone;
+ assert(type == kN32_SkColorType || type == kAlpha_8_SkColorType);
+ sk_sp<SkSurface> surface;
+ switch (renderMethodToUseForSize({ width, height }))
+ {
+ case RenderVulkan:
+ case RenderMetal:
+ {
+ if (GrDirectContext* grDirectContext = getSharedGrDirectContext())
+ {
+ surface = SkSurfaces::RenderTarget(grDirectContext, skgpu::Budgeted::kNo,
+ SkImageInfo::Make(width, height, type, alpha), 0,
+ surfaceProps());
+ if (surface)
+ {
+#ifdef DBG_UTIL
+ prefillSurface(surface);
+#endif
+ return surface;
+ }
+ SAL_WARN_IF(renderMethodToUse() == RenderVulkan, "vcl.skia",
+ "Cannot create Vulkan GPU offscreen surface, falling back to Raster");
+ SAL_WARN_IF(renderMethodToUse() == RenderMetal, "vcl.skia",
+ "Cannot create Metal GPU offscreen surface, falling back to Raster");
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ // Create raster surface as a fallback.
+ surface = SkSurfaces::Raster(SkImageInfo::Make(width, height, type, alpha), surfaceProps());
+ assert(surface);
+ if (surface)
+ {
+#ifdef DBG_UTIL
+ prefillSurface(surface);
+#endif
+ return surface;
+ }
+ // In non-debug builds we could return SkSurface::MakeNull() and try to cope with the situation,
+ // but that can lead to unnoticed data loss, so better fail clearly.
+ abort();
+}
+
+sk_sp<SkImage> createSkImage(const SkBitmap& bitmap)
+{
+ SkiaZone zone;
+ assert(bitmap.colorType() == kN32_SkColorType || bitmap.colorType() == kAlpha_8_SkColorType);
+ switch (renderMethodToUseForSize(bitmap.dimensions()))
+ {
+ case RenderVulkan:
+ case RenderMetal:
+ {
+ if (GrDirectContext* grDirectContext = getSharedGrDirectContext())
+ {
+ sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(
+ grDirectContext, skgpu::Budgeted::kNo,
+ bitmap.info().makeAlphaType(kPremul_SkAlphaType), 0, surfaceProps());
+ if (surface)
+ {
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
+ surface->getCanvas()->drawImage(bitmap.asImage(), 0, 0, SkSamplingOptions(),
+ &paint);
+ return makeCheckedImageSnapshot(surface);
+ }
+ // Try to fall back in non-debug builds.
+ SAL_WARN_IF(renderMethodToUse() == RenderVulkan, "vcl.skia",
+ "Cannot create Vulkan GPU offscreen surface, falling back to Raster");
+ SAL_WARN_IF(renderMethodToUse() == RenderMetal, "vcl.skia",
+ "Cannot create Metal GPU offscreen surface, falling back to Raster");
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ // Create raster image as a fallback.
+ sk_sp<SkImage> image = SkImages::RasterFromBitmap(bitmap);
+ assert(image);
+ return image;
+}
+
+sk_sp<SkImage> makeCheckedImageSnapshot(sk_sp<SkSurface> surface)
+{
+ sk_sp<SkImage> ret = surface->makeImageSnapshot();
+ assert(ret);
+ if (ret)
+ return ret;
+ abort();
+}
+
+sk_sp<SkImage> makeCheckedImageSnapshot(sk_sp<SkSurface> surface, const SkIRect& bounds)
+{
+ sk_sp<SkImage> ret = surface->makeImageSnapshot(bounds);
+ assert(ret);
+ if (ret)
+ return ret;
+ abort();
+}
+
+namespace
+{
+// Image cache, for saving results of complex operations such as drawTransformedBitmap().
+struct ImageCacheItem
+{
+ OString key;
+ sk_sp<SkImage> image;
+ tools::Long size; // cost of the item
+};
+} //namespace
+
+// LRU cache, last item is the least recently used. Hopefully there won't be that many items
+// to require a hash/map. Using o3tl::lru_map would be simpler, but it doesn't support
+// calculating cost of each item.
+static std::list<ImageCacheItem> imageCache;
+static tools::Long imageCacheSize = 0; // sum of all ImageCacheItem.size
+
+void addCachedImage(const OString& key, sk_sp<SkImage> image)
+{
+ static bool disabled = getenv("SAL_DISABLE_SKIA_CACHE") != nullptr;
+ if (disabled)
+ return;
+ tools::Long size = static_cast<tools::Long>(image->width()) * image->height()
+ * SkColorTypeBytesPerPixel(image->imageInfo().colorType());
+ imageCache.push_front({ key, image, size });
+ imageCacheSize += size;
+ SAL_INFO("vcl.skia.trace", "addcachedimage " << image << " :" << size << "/" << imageCacheSize);
+ const tools::Long maxSize = maxImageCacheSize();
+ while (imageCacheSize > maxSize)
+ {
+ assert(!imageCache.empty());
+ imageCacheSize -= imageCache.back().size;
+ SAL_INFO("vcl.skia.trace",
+ "least used removal " << imageCache.back().image << ":" << imageCache.back().size);
+ imageCache.pop_back();
+ }
+}
+
+sk_sp<SkImage> findCachedImage(const OString& key)
+{
+ for (auto it = imageCache.begin(); it != imageCache.end(); ++it)
+ {
+ if (it->key == key)
+ {
+ sk_sp<SkImage> ret = it->image;
+ SAL_INFO("vcl.skia.trace", "findcachedimage " << key << " : " << it->image << " found");
+ imageCache.splice(imageCache.begin(), imageCache, it);
+ return ret;
+ }
+ }
+ SAL_INFO("vcl.skia.trace", "findcachedimage " << key << " not found");
+ return nullptr;
+}
+
+void removeCachedImage(sk_sp<SkImage> image)
+{
+ for (auto it = imageCache.begin(); it != imageCache.end();)
+ {
+ if (it->image == image)
+ {
+ imageCacheSize -= it->size;
+ assert(imageCacheSize >= 0);
+ it = imageCache.erase(it);
+ }
+ else
+ ++it;
+ }
+}
+
+tools::Long maxImageCacheSize()
+{
+ // Defaults to 4x 2000px 32bpp images, 64MiB.
+ return officecfg::Office::Common::Cache::Skia::ImageCacheSize::get();
+}
+
+static o3tl::lru_map<uint32_t, uint32_t> checksumCache(256);
+
+static uint32_t computeSkPixmapChecksum(const SkPixmap& pixmap)
+{
+ // Use uint32_t because that's what SkChecksum::Hash32() returns.
+ static_assert(std::is_same_v<uint32_t, decltype(SkChecksum::Hash32(nullptr, 0, 0))>);
+ const size_t dataRowBytes = pixmap.width() << pixmap.shiftPerPixel();
+ if (dataRowBytes == pixmap.rowBytes())
+ return SkChecksum::Hash32(pixmap.addr(), pixmap.height() * dataRowBytes, 0);
+ uint32_t sum = 0;
+ for (int row = 0; row < pixmap.height(); ++row)
+ sum = SkChecksum::Hash32(pixmap.addr(0, row), dataRowBytes, sum);
+ return sum;
+}
+
+uint32_t getSkImageChecksum(sk_sp<SkImage> image)
+{
+ // Cache the checksums based on the uniqueID() (which should stay the same
+ // for the same image), because it may be still somewhat expensive.
+ uint32_t id = image->uniqueID();
+ auto it = checksumCache.find(id);
+ if (it != checksumCache.end())
+ return it->second;
+ SkPixmap pixmap;
+ if (!image->peekPixels(&pixmap))
+ abort(); // Fetching of GPU-based pixels is expensive, and shouldn't(?) be needed anyway.
+ uint32_t checksum = computeSkPixmapChecksum(pixmap);
+ checksumCache.insert({ id, checksum });
+ return checksum;
+}
+
+static sk_sp<SkBlender> invertBlender;
+static sk_sp<SkBlender> xorBlender;
+
+// This does the invert operation, i.e. result = color(255-R,255-G,255-B,A).
+void setBlenderInvert(SkPaint* paint)
+{
+ if (!invertBlender)
+ {
+ // Note that the colors are premultiplied, so '1 - dst.r' must be
+ // written as 'dst.a - dst.r', since premultiplied R is in the range (0-A).
+ const char* const diff = R"(
+ vec4 main( vec4 src, vec4 dst )
+ {
+ return vec4( dst.a - dst.r, dst.a - dst.g, dst.a - dst.b, dst.a );
+ }
+ )";
+ auto effect = SkRuntimeEffect::MakeForBlender(SkString(diff));
+ if (!effect.effect)
+ {
+ SAL_WARN("vcl.skia",
+ "SKRuntimeEffect::MakeForBlender failed: " << effect.errorText.c_str());
+ abort();
+ }
+ invertBlender = effect.effect->makeBlender(nullptr);
+ }
+ paint->setBlender(invertBlender);
+}
+
+// This does the xor operation, i.e. bitwise xor of RGB values of both colors.
+void setBlenderXor(SkPaint* paint)
+{
+ if (!xorBlender)
+ {
+ // Note that the colors are premultiplied, converting to 0-255 range
+ // must also unpremultiply.
+ const char* const diff = R"(
+ vec4 main( vec4 src, vec4 dst )
+ {
+ return vec4(
+ float(int(src.r * src.a * 255.0) ^ int(dst.r * dst.a * 255.0)) / 255.0 / dst.a,
+ float(int(src.g * src.a * 255.0) ^ int(dst.g * dst.a * 255.0)) / 255.0 / dst.a,
+ float(int(src.b * src.a * 255.0) ^ int(dst.b * dst.a * 255.0)) / 255.0 / dst.a,
+ dst.a );
+ }
+ )";
+ SkRuntimeEffect::Options opts;
+ // Skia does not allow binary operators in the default ES2Strict mode, but that's only
+ // because of OpenGL support. We don't use OpenGL, and it's safe for all modes that we do use.
+ // https://groups.google.com/g/skia-discuss/c/EPLuQbg64Kc/m/2uDXFIGhAwAJ
+ opts.maxVersionAllowed = SkSL::Version::k300;
+ auto effect = SkRuntimeEffect::MakeForBlender(SkString(diff), opts);
+ if (!effect.effect)
+ {
+ SAL_WARN("vcl.skia",
+ "SKRuntimeEffect::MakeForBlender failed: " << effect.errorText.c_str());
+ abort();
+ }
+ xorBlender = effect.effect->makeBlender(nullptr);
+ }
+ paint->setBlender(xorBlender);
+}
+
+static void initInternal()
+{
+ // Set up all things needed for using Skia.
+ SkGraphics::Init();
+ SkLoOpts::Init();
+}
+
+void cleanup()
+{
+ sharedWindowContext.reset();
+ imageCache.clear();
+ imageCacheSize = 0;
+ invertBlender.reset();
+ xorBlender.reset();
+}
+
+static SkSurfaceProps commonSurfaceProps;
+const SkSurfaceProps* surfaceProps() { return &commonSurfaceProps; }
+
+void setPixelGeometry(SkPixelGeometry pixelGeometry)
+{
+ commonSurfaceProps = SkSurfaceProps(commonSurfaceProps.flags(), pixelGeometry);
+}
+
+// Skia should not be used from VCL backends that do not actually support it, as there will be setup missing.
+// The code here (that is in the vcl lib) needs a function for creating Vulkan/Metal context that is
+// usually available only in the backend libs.
+void prepareSkia(std::unique_ptr<sk_app::WindowContext> (*createGpuWindowContext)(bool))
+{
+ setCreateGpuWindowContext(createGpuWindowContext);
+ skiaSupportedByBackend = true;
+}
+
+void dump(const SkBitmap& bitmap, const char* file)
+{
+ dump(SkImages::RasterFromBitmap(bitmap), file);
+}
+
+void dump(const sk_sp<SkSurface>& surface, const char* file)
+{
+ surface->getCanvas()->flush();
+ dump(makeCheckedImageSnapshot(surface), file);
+}
+
+void dump(const sk_sp<SkImage>& image, const char* file)
+{
+ SkBitmap bm;
+ if (!as_IB(image)->getROPixels(getSharedGrDirectContext(), &bm))
+ return;
+ SkPixmap pixmap;
+ if (!bm.peekPixels(&pixmap))
+ return;
+ SkPngEncoder::Options opts;
+ opts.fFilterFlags = SkPngEncoder::FilterFlag::kNone;
+ opts.fZLibLevel = 1;
+ SkDynamicMemoryWStream stream;
+ if (!SkPngEncoder::Encode(&stream, pixmap, opts))
+ return;
+ sk_sp<SkData> data = stream.detachAsData();
+ std::ofstream ostream(file, std::ios::binary);
+ ostream.write(static_cast<const char*>(data->data()), data->size());
+}
+
+#ifdef DBG_UTIL
+void prefillSurface(const sk_sp<SkSurface>& surface)
+{
+ // Pre-fill the surface with deterministic garbage.
+ SkBitmap bitmap;
+ bitmap.allocN32Pixels(2, 2);
+ SkPMColor* scanline;
+ scanline = bitmap.getAddr32(0, 0);
+ *scanline++ = SkPreMultiplyARGB(0xFF, 0xBF, 0x80, 0x40);
+ *scanline++ = SkPreMultiplyARGB(0xFF, 0x40, 0x80, 0xBF);
+ scanline = bitmap.getAddr32(0, 1);
+ *scanline++ = SkPreMultiplyARGB(0xFF, 0xE3, 0x5C, 0x13);
+ *scanline++ = SkPreMultiplyARGB(0xFF, 0x13, 0x5C, 0xE3);
+ bitmap.setImmutable();
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
+ paint.setShader(
+ bitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, SkSamplingOptions()));
+ surface->getCanvas()->drawPaint(paint);
+}
+#endif
+
+} // namespace
+
+#endif // HAVE_FEATURE_SKIA
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/skia/gdiimpl.cxx b/vcl/skia/gdiimpl.cxx
new file mode 100644
index 0000000000..d7028b9598
--- /dev/null
+++ b/vcl/skia/gdiimpl.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 .
+ */
+
+#include <skia/gdiimpl.hxx>
+
+#include <salgdi.hxx>
+#include <skia/salbmp.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/lazydelete.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/skia/SkiaHelper.hxx>
+#include <skia/utils.hxx>
+#include <skia/zone.hxx>
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkGradientShader.h>
+#include <SkPath.h>
+#include <SkRegion.h>
+#include <SkPathEffect.h>
+#include <SkDashPathEffect.h>
+#include <GrBackendSurface.h>
+#include <SkTextBlob.h>
+#include <SkRSXform.h>
+
+#include <numeric>
+#include <sstream>
+
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <o3tl/sorted_vector.hxx>
+#include <rtl/math.hxx>
+
+using namespace SkiaHelper;
+
+namespace
+{
+// Create Skia Path from B2DPolygon
+// Note that polygons generally have the complication that when used
+// for area (fill) operations they usually miss the right-most and
+// bottom-most line of pixels of the bounding rectangle (see
+// https://lists.freedesktop.org/archives/libreoffice/2019-November/083709.html).
+// So be careful with rectangle->polygon conversions (generally avoid them).
+void addPolygonToPath(const basegfx::B2DPolygon& rPolygon, SkPath& rPath, sal_uInt32 nFirstIndex,
+ sal_uInt32 nLastIndex, const sal_uInt32 nPointCount, const bool bClosePath,
+ const bool bHasCurves, bool* hasOnlyOrthogonal = nullptr)
+{
+ assert(nFirstIndex < nPointCount);
+ assert(nLastIndex <= nPointCount);
+
+ if (nPointCount <= 1)
+ return;
+
+ bool bFirst = true;
+ sal_uInt32 nPreviousIndex = nFirstIndex == 0 ? nPointCount - 1 : nFirstIndex - 1;
+ basegfx::B2DPoint aPreviousPoint = rPolygon.getB2DPoint(nPreviousIndex);
+
+ for (sal_uInt32 nIndex = nFirstIndex; nIndex <= nLastIndex; nIndex++)
+ {
+ if (nIndex == nPointCount && !bClosePath)
+ continue;
+
+ // Make sure we loop the last point to first point
+ sal_uInt32 nCurrentIndex = nIndex % nPointCount;
+ basegfx::B2DPoint aCurrentPoint = rPolygon.getB2DPoint(nCurrentIndex);
+
+ if (bFirst)
+ {
+ rPath.moveTo(aCurrentPoint.getX(), aCurrentPoint.getY());
+ bFirst = false;
+ }
+ else if (!bHasCurves)
+ {
+ rPath.lineTo(aCurrentPoint.getX(), aCurrentPoint.getY());
+ // If asked for, check whether the polygon has a line that is not
+ // strictly horizontal or vertical.
+ if (hasOnlyOrthogonal != nullptr && aCurrentPoint.getX() != aPreviousPoint.getX()
+ && aCurrentPoint.getY() != aPreviousPoint.getY())
+ *hasOnlyOrthogonal = false;
+ }
+ else
+ {
+ basegfx::B2DPoint aPreviousControlPoint = rPolygon.getNextControlPoint(nPreviousIndex);
+ basegfx::B2DPoint aCurrentControlPoint = rPolygon.getPrevControlPoint(nCurrentIndex);
+
+ if (aPreviousControlPoint.equal(aPreviousPoint)
+ && aCurrentControlPoint.equal(aCurrentPoint))
+ {
+ rPath.lineTo(aCurrentPoint.getX(), aCurrentPoint.getY()); // a straight line
+ if (hasOnlyOrthogonal != nullptr && aCurrentPoint.getX() != aPreviousPoint.getX()
+ && aCurrentPoint.getY() != aPreviousPoint.getY())
+ *hasOnlyOrthogonal = false;
+ }
+ else
+ {
+ if (aPreviousControlPoint.equal(aPreviousPoint))
+ {
+ aPreviousControlPoint
+ = aPreviousPoint + ((aPreviousControlPoint - aCurrentPoint) * 0.0005);
+ }
+ if (aCurrentControlPoint.equal(aCurrentPoint))
+ {
+ aCurrentControlPoint
+ = aCurrentPoint + ((aCurrentControlPoint - aPreviousPoint) * 0.0005);
+ }
+ rPath.cubicTo(aPreviousControlPoint.getX(), aPreviousControlPoint.getY(),
+ aCurrentControlPoint.getX(), aCurrentControlPoint.getY(),
+ aCurrentPoint.getX(), aCurrentPoint.getY());
+ if (hasOnlyOrthogonal != nullptr)
+ *hasOnlyOrthogonal = false;
+ }
+ }
+ aPreviousPoint = aCurrentPoint;
+ nPreviousIndex = nCurrentIndex;
+ }
+ if (bClosePath && nFirstIndex == 0 && nLastIndex == nPointCount)
+ {
+ rPath.close();
+ }
+}
+
+void addPolygonToPath(const basegfx::B2DPolygon& rPolygon, SkPath& rPath,
+ bool* hasOnlyOrthogonal = nullptr)
+{
+ addPolygonToPath(rPolygon, rPath, 0, rPolygon.count(), rPolygon.count(), rPolygon.isClosed(),
+ rPolygon.areControlPointsUsed(), hasOnlyOrthogonal);
+}
+
+void addPolyPolygonToPath(const basegfx::B2DPolyPolygon& rPolyPolygon, SkPath& rPath,
+ bool* hasOnlyOrthogonal = nullptr)
+{
+ const sal_uInt32 nPolygonCount(rPolyPolygon.count());
+
+ if (nPolygonCount == 0)
+ return;
+
+ sal_uInt32 nPointCount = 0;
+ for (const auto& rPolygon : rPolyPolygon)
+ nPointCount += rPolygon.count() * 3; // because cubicTo is 3 elements
+ rPath.incReserve(nPointCount);
+
+ for (const auto& rPolygon : rPolyPolygon)
+ {
+ addPolygonToPath(rPolygon, rPath, hasOnlyOrthogonal);
+ }
+}
+
+// Check if the given polygon contains a straight line. If not, it consists
+// solely of curves.
+bool polygonContainsLine(const basegfx::B2DPolyPolygon& rPolyPolygon)
+{
+ if (!rPolyPolygon.areControlPointsUsed())
+ return true; // no curves at all
+ for (const auto& rPolygon : rPolyPolygon)
+ {
+ const sal_uInt32 nPointCount(rPolygon.count());
+ bool bFirst = true;
+
+ const bool bClosePath(rPolygon.isClosed());
+
+ sal_uInt32 nCurrentIndex = 0;
+ sal_uInt32 nPreviousIndex = nPointCount - 1;
+
+ basegfx::B2DPoint aCurrentPoint;
+ basegfx::B2DPoint aPreviousPoint;
+
+ for (sal_uInt32 nIndex = 0; nIndex <= nPointCount; nIndex++)
+ {
+ if (nIndex == nPointCount && !bClosePath)
+ continue;
+
+ // Make sure we loop the last point to first point
+ nCurrentIndex = nIndex % nPointCount;
+ if (bFirst)
+ bFirst = false;
+ else
+ {
+ basegfx::B2DPoint aPreviousControlPoint
+ = rPolygon.getNextControlPoint(nPreviousIndex);
+ basegfx::B2DPoint aCurrentControlPoint
+ = rPolygon.getPrevControlPoint(nCurrentIndex);
+
+ if (aPreviousControlPoint.equal(aPreviousPoint)
+ && aCurrentControlPoint.equal(aCurrentPoint))
+ {
+ return true; // found a straight line
+ }
+ }
+ aPreviousPoint = aCurrentPoint;
+ nPreviousIndex = nCurrentIndex;
+ }
+ }
+ return false; // no straight line found
+}
+
+// returns true if the source or destination rectangles are invalid
+bool checkInvalidSourceOrDestination(SalTwoRect const& rPosAry)
+{
+ return rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
+ || rPosAry.mnDestHeight <= 0;
+}
+
+std::string dumpOptionalColor(const std::optional<Color>& c)
+{
+ std::ostringstream oss;
+ if (c)
+ oss << *c;
+ else
+ oss << "no color";
+
+ return std::move(oss).str(); // optimized in C++20
+}
+
+} // end anonymous namespace
+
+// Class that triggers flushing the backing buffer when idle.
+class SkiaFlushIdle : public Idle
+{
+ SkiaSalGraphicsImpl* mpGraphics;
+#ifndef NDEBUG
+ char* debugname;
+#endif
+
+public:
+ explicit SkiaFlushIdle(SkiaSalGraphicsImpl* pGraphics)
+ : Idle(get_debug_name(pGraphics))
+ , mpGraphics(pGraphics)
+ {
+ // We don't want to be swapping before we've painted.
+ SetPriority(TaskPriority::POST_PAINT);
+ }
+#ifndef NDEBUG
+ virtual ~SkiaFlushIdle() { free(debugname); }
+#endif
+ const char* get_debug_name(SkiaSalGraphicsImpl* pGraphics)
+ {
+#ifndef NDEBUG
+ // Idle keeps just a pointer, so we need to store the string
+ debugname = strdup(
+ OString("skia idle 0x" + OString::number(reinterpret_cast<sal_uIntPtr>(pGraphics), 16))
+ .getStr());
+ return debugname;
+#else
+ (void)pGraphics;
+ return "skia idle";
+#endif
+ }
+
+ virtual void Invoke() override
+ {
+ mpGraphics->performFlush();
+ Stop();
+ // tdf#157312 Don't change priority
+ // Instances of this class are constructed with
+ // TaskPriority::POST_PAINT, but then it was set to
+ // TaskPriority::HIGHEST when reused. Flushing
+ // seems to be expensive (at least with Skia/Metal) so keep the
+ // existing priority when reused.
+ SetPriority(TaskPriority::POST_PAINT);
+ }
+};
+
+SkiaSalGraphicsImpl::SkiaSalGraphicsImpl(SalGraphics& rParent, SalGeometryProvider* pProvider)
+ : mParent(rParent)
+ , mProvider(pProvider)
+ , mIsGPU(false)
+ , moLineColor(std::nullopt)
+ , moFillColor(std::nullopt)
+ , mXorMode(XorMode::None)
+ , mFlush(new SkiaFlushIdle(this))
+ , mScaling(1)
+ , mInWindowBackingPropertiesChanged(false)
+{
+}
+
+SkiaSalGraphicsImpl::~SkiaSalGraphicsImpl()
+{
+ assert(!mSurface);
+ assert(!mWindowContext);
+}
+
+void SkiaSalGraphicsImpl::Init() {}
+
+void SkiaSalGraphicsImpl::createSurface()
+{
+ SkiaZone zone;
+ if (isOffscreen())
+ createOffscreenSurface();
+ else
+ createWindowSurface();
+ mClipRegion = vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight()));
+ mDirtyRect = SkIRect::MakeWH(GetWidth(), GetHeight());
+ setCanvasScalingAndClipping();
+
+ // We don't want to be swapping before we've painted.
+ mFlush->Stop();
+ mFlush->SetPriority(TaskPriority::POST_PAINT);
+}
+
+void SkiaSalGraphicsImpl::createWindowSurface(bool forceRaster)
+{
+ SkiaZone zone;
+ assert(!isOffscreen());
+ assert(!mSurface);
+ createWindowSurfaceInternal(forceRaster);
+ if (!mSurface)
+ {
+ switch (forceRaster ? RenderRaster : renderMethodToUse())
+ {
+ case RenderVulkan:
+ SAL_WARN("vcl.skia",
+ "cannot create Vulkan GPU window surface, falling back to Raster");
+ destroySurface(); // destroys also WindowContext
+ return createWindowSurface(true); // try again
+ case RenderMetal:
+ SAL_WARN("vcl.skia",
+ "cannot create Metal GPU window surface, falling back to Raster");
+ destroySurface(); // destroys also WindowContext
+ return createWindowSurface(true); // try again
+ case RenderRaster:
+ abort(); // This should not really happen, do not even try to cope with it.
+ }
+ }
+ mIsGPU = mSurface->getCanvas()->recordingContext() != nullptr;
+#ifdef DBG_UTIL
+ prefillSurface(mSurface);
+#endif
+}
+
+bool SkiaSalGraphicsImpl::isOffscreen() const
+{
+ if (mProvider == nullptr || mProvider->IsOffScreen())
+ return true;
+ // HACK: Sometimes (tdf#131939, tdf#138022, tdf#140288) VCL passes us a zero-sized window,
+ // and zero size is invalid for Skia, so force offscreen surface, where we handle this.
+ if (GetWidth() <= 0 || GetHeight() <= 0)
+ return true;
+ return false;
+}
+
+void SkiaSalGraphicsImpl::createOffscreenSurface()
+{
+ SkiaZone zone;
+ assert(isOffscreen());
+ assert(!mSurface);
+ // HACK: See isOffscreen().
+ int width = std::max(1, GetWidth());
+ int height = std::max(1, GetHeight());
+ // We need to use window scaling even for offscreen surfaces, because the common usage is rendering something
+ // into an offscreen surface and then copy it to a window, so without scaling here the result would be originally
+ // drawn without scaling and only upscaled when drawing to a window.
+ mScaling = getWindowScaling();
+ mSurface = createSkSurface(width * mScaling, height * mScaling);
+ assert(mSurface);
+ mIsGPU = mSurface->getCanvas()->recordingContext() != nullptr;
+}
+
+void SkiaSalGraphicsImpl::destroySurface()
+{
+ SkiaZone zone;
+ if (mSurface)
+ {
+ // check setClipRegion() invariant
+ assert(mSurface->getCanvas()->getSaveCount() == 3);
+ // if this fails, something forgot to use SkAutoCanvasRestore
+ assert(mSurface->getCanvas()->getTotalMatrix() == SkMatrix::Scale(mScaling, mScaling));
+ }
+ mSurface.reset();
+ mWindowContext.reset();
+ mIsGPU = false;
+ mScaling = 1;
+}
+
+void SkiaSalGraphicsImpl::performFlush()
+{
+ SkiaZone zone;
+ flushDrawing();
+ if (mSurface)
+ {
+ // Related: tdf#152703 Eliminate flickering during live resizing of a window
+ // When in live resize, the SkiaSalGraphicsImpl class does not detect that
+ // the window size has changed until after the flush has been called so
+ // call checkSurface() to recreate the SkSurface if needed before flushing.
+ checkSurface();
+ if (mDirtyRect.intersect(SkIRect::MakeWH(GetWidth(), GetHeight())))
+ flushSurfaceToWindowContext();
+ mDirtyRect.setEmpty();
+ }
+}
+
+void SkiaSalGraphicsImpl::flushSurfaceToWindowContext()
+{
+ sk_sp<SkSurface> screenSurface = mWindowContext->getBackbufferSurface();
+ if (screenSurface != mSurface)
+ {
+ // GPU-based window contexts require calling getBackbufferSurface()
+ // for every swapBuffers(), for this reason mSurface is an offscreen surface
+ // where we keep the contents (LO does not do full redraws).
+ // So here blit the surface to the window context surface and then swap it.
+
+ // Raster should always draw directly to backbuffer to save copying
+ // except for small sizes - see renderMethodToUseForSize
+ assert(isGPU() || (mSurface->width() <= 32 && mSurface->height() <= 32));
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // copy as is
+ // We ignore mDirtyRect here, and mSurface already is in screenSurface coordinates,
+ // so no transformation needed.
+ screenSurface->getCanvas()->drawImage(makeCheckedImageSnapshot(mSurface), 0, 0,
+ SkSamplingOptions(), &paint);
+ screenSurface->flushAndSubmit(); // Otherwise the window is not drawn sometimes.
+ mWindowContext->swapBuffers(nullptr); // Must swap the entire surface.
+ }
+ else
+ {
+ // For raster mode use directly the backbuffer surface, it's just a bitmap
+ // surface anyway, and for those there's no real requirement to call
+ // getBackbufferSurface() repeatedly. Using our own surface would duplicate
+ // memory and cost time copying pixels around.
+ assert(!isGPU());
+ SkIRect dirtyRect = mDirtyRect;
+ if (mScaling != 1) // Adjust to mSurface coordinates if needed.
+ dirtyRect = scaleRect(dirtyRect, mScaling);
+ mWindowContext->swapBuffers(&dirtyRect);
+ }
+}
+
+void SkiaSalGraphicsImpl::DeInit() { destroySurface(); }
+
+void SkiaSalGraphicsImpl::preDraw()
+{
+ assert(comphelper::SolarMutex::get()->IsCurrentThread());
+ SkiaZone::enter(); // matched in postDraw()
+ checkSurface();
+ checkPendingDrawing();
+}
+
+void SkiaSalGraphicsImpl::postDraw()
+{
+ scheduleFlush();
+ // Skia (at least when using Vulkan) queues drawing commands and executes them only later.
+ // But tdf#136369 leads to creating and queueing many tiny bitmaps, which makes
+ // Skia slow, and may make it even run out of memory. So force a flush if such
+ // a problematic operation has been performed too many times without a flush.
+ // Note that the counter is a static variable, as all drawing shares the same Skia drawing
+ // context (and so the flush here will also flush all drawing).
+ if (pendingOperationsToFlush > 1000)
+ {
+ mSurface->flushAndSubmit();
+ pendingOperationsToFlush = 0;
+ }
+ SkiaZone::leave(); // matched in preDraw()
+ // If there's a problem with the GPU context, abort.
+ if (GrDirectContext* context = GrAsDirectContext(mSurface->getCanvas()->recordingContext()))
+ {
+ // Running out of memory on the GPU technically could be possibly recoverable,
+ // but we don't know the exact status of the surface (and what has or has not been drawn to it),
+ // so in practice this is unrecoverable without possible data loss.
+ if (context->oomed())
+ {
+ SAL_WARN("vcl.skia", "GPU context has run out of memory, aborting.");
+ abort();
+ }
+ // Unrecoverable problem.
+ if (context->abandoned())
+ {
+ SAL_WARN("vcl.skia", "GPU context has been abandoned, aborting.");
+ abort();
+ }
+ }
+}
+
+void SkiaSalGraphicsImpl::scheduleFlush()
+{
+ if (!isOffscreen())
+ {
+ if (!Application::IsInExecute())
+ performFlush(); // otherwise nothing would trigger idle rendering
+ else if (!mFlush->IsActive())
+ mFlush->Start();
+ }
+}
+
+// VCL can sometimes resize us without telling us, update the surface if needed.
+// Also create the surface on demand if it has not been created yet (it is a waste
+// to create it in Init() if it gets recreated later anyway).
+void SkiaSalGraphicsImpl::checkSurface()
+{
+ if (!mSurface)
+ {
+ createSurface();
+ SAL_INFO("vcl.skia.trace",
+ "create(" << this << "): " << Size(mSurface->width(), mSurface->height()));
+ }
+ else if (mInWindowBackingPropertiesChanged || GetWidth() * mScaling != mSurface->width()
+ || GetHeight() * mScaling != mSurface->height())
+ {
+ if (!avoidRecreateByResize())
+ {
+ Size oldSize(mSurface->width(), mSurface->height());
+ // Recreating a surface means that the old SkSurface contents will be lost.
+ // But if a window has been resized the windowing system may send repaint events
+ // only for changed parts and VCL would not repaint the whole area, assuming
+ // that some parts have not changed (this is what seems to cause tdf#131952).
+ // So carry over the old contents for windows, even though generally everything
+ // will be usually repainted anyway.
+ sk_sp<SkImage> snapshot;
+ if (!isOffscreen())
+ {
+ flushDrawing();
+ snapshot = makeCheckedImageSnapshot(mSurface);
+ }
+
+ destroySurface();
+ createSurface();
+
+ if (snapshot)
+ {
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // copy as is
+ // Scaling by current mScaling is active, undo that. We assume that the scaling
+ // does not change.
+ resetCanvasScalingAndClipping();
+ mSurface->getCanvas()->drawImage(snapshot, 0, 0, SkSamplingOptions(), &paint);
+ setCanvasScalingAndClipping();
+ }
+ SAL_INFO("vcl.skia.trace", "recreate(" << this << "): old " << oldSize << " new "
+ << Size(mSurface->width(), mSurface->height())
+ << " requested "
+ << Size(GetWidth(), GetHeight()));
+ }
+ }
+}
+
+bool SkiaSalGraphicsImpl::avoidRecreateByResize() const
+{
+ // Keep the old surface if VCL sends us a broken size (see isOffscreen()).
+ if (GetWidth() == 0 || GetHeight() == 0)
+ return true;
+ return false;
+}
+
+void SkiaSalGraphicsImpl::flushDrawing()
+{
+ if (!mSurface)
+ return;
+ checkPendingDrawing();
+ ++pendingOperationsToFlush;
+}
+
+void SkiaSalGraphicsImpl::setCanvasScalingAndClipping()
+{
+ SkCanvas* canvas = mSurface->getCanvas();
+ assert(canvas->getSaveCount() == 1);
+ // If HiDPI scaling is active, simply set a scaling matrix for the canvas. This means
+ // that all painting can use VCL coordinates and they'll be automatically translated to mSurface
+ // scaled coordinates. If that is not wanted, the scale() state needs to be temporarily unset.
+ // State such as mDirtyRect is not scaled, the scaling matrix applies to clipping too,
+ // and the rest needs to be handled explicitly.
+ // When reading mSurface contents there's no automatic scaling and it needs to be handled explicitly.
+ canvas->save(); // keep the original state without any scaling
+ canvas->scale(mScaling, mScaling);
+
+ // SkCanvas::clipRegion() can only further reduce the clip region,
+ // but we need to set the given region, which may extend it.
+ // So handle that by always having the full clip region saved on the stack
+ // and always go back to that. SkCanvas::restore() only affects the clip
+ // and the matrix.
+ canvas->save(); // keep scaled state without clipping
+ setCanvasClipRegion(canvas, mClipRegion);
+}
+
+void SkiaSalGraphicsImpl::resetCanvasScalingAndClipping()
+{
+ SkCanvas* canvas = mSurface->getCanvas();
+ assert(canvas->getSaveCount() == 3);
+ canvas->restore(); // undo clipping
+ canvas->restore(); // undo scaling
+}
+
+void SkiaSalGraphicsImpl::setClipRegion(const vcl::Region& region)
+{
+ if (mClipRegion == region)
+ return;
+ SkiaZone zone;
+ checkPendingDrawing();
+ checkSurface();
+ mClipRegion = region;
+ SAL_INFO("vcl.skia.trace", "setclipregion(" << this << "): " << region);
+ SkCanvas* canvas = mSurface->getCanvas();
+ assert(canvas->getSaveCount() == 3);
+ canvas->restore(); // undo previous clip state, see setCanvasScalingAndClipping()
+ canvas->save();
+ setCanvasClipRegion(canvas, region);
+}
+
+void SkiaSalGraphicsImpl::setCanvasClipRegion(SkCanvas* canvas, const vcl::Region& region)
+{
+ SkiaZone zone;
+ SkPath path;
+ // Always use region rectangles, regardless of what the region uses internally.
+ // That's what other VCL backends do, and trying to use addPolyPolygonToPath()
+ // in case a polygon is used leads to off-by-one errors such as tdf#133208.
+ RectangleVector rectangles;
+ region.GetRegionRectangles(rectangles);
+ path.incReserve(rectangles.size() + 1);
+ for (const tools::Rectangle& rectangle : rectangles)
+ path.addRect(SkRect::MakeXYWH(rectangle.getX(), rectangle.getY(), rectangle.GetWidth(),
+ rectangle.GetHeight()));
+ path.setFillType(SkPathFillType::kEvenOdd);
+ canvas->clipPath(path);
+}
+
+void SkiaSalGraphicsImpl::ResetClipRegion()
+{
+ setClipRegion(vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight())));
+}
+
+const vcl::Region& SkiaSalGraphicsImpl::getClipRegion() const { return mClipRegion; }
+
+sal_uInt16 SkiaSalGraphicsImpl::GetBitCount() const { return 32; }
+
+tools::Long SkiaSalGraphicsImpl::GetGraphicsWidth() const { return GetWidth(); }
+
+void SkiaSalGraphicsImpl::SetLineColor()
+{
+ checkPendingDrawing();
+ moLineColor = std::nullopt;
+}
+
+void SkiaSalGraphicsImpl::SetLineColor(Color nColor)
+{
+ checkPendingDrawing();
+ moLineColor = nColor;
+}
+
+void SkiaSalGraphicsImpl::SetFillColor()
+{
+ checkPendingDrawing();
+ moFillColor = std::nullopt;
+}
+
+void SkiaSalGraphicsImpl::SetFillColor(Color nColor)
+{
+ checkPendingDrawing();
+ moFillColor = nColor;
+}
+
+void SkiaSalGraphicsImpl::SetXORMode(bool set, bool invert)
+{
+ XorMode newMode = set ? (invert ? XorMode::Invert : XorMode::Xor) : XorMode::None;
+ if (newMode == mXorMode)
+ return;
+ checkPendingDrawing();
+ SAL_INFO("vcl.skia.trace", "setxormode(" << this << "): " << set << "/" << invert);
+ mXorMode = newMode;
+}
+
+void SkiaSalGraphicsImpl::SetROPLineColor(SalROPColor nROPColor)
+{
+ checkPendingDrawing();
+ switch (nROPColor)
+ {
+ case SalROPColor::N0:
+ moLineColor = Color(0, 0, 0);
+ break;
+ case SalROPColor::N1:
+ moLineColor = Color(0xff, 0xff, 0xff);
+ break;
+ case SalROPColor::Invert:
+ moLineColor = Color(0xff, 0xff, 0xff);
+ break;
+ }
+}
+
+void SkiaSalGraphicsImpl::SetROPFillColor(SalROPColor nROPColor)
+{
+ checkPendingDrawing();
+ switch (nROPColor)
+ {
+ case SalROPColor::N0:
+ moFillColor = Color(0, 0, 0);
+ break;
+ case SalROPColor::N1:
+ moFillColor = Color(0xff, 0xff, 0xff);
+ break;
+ case SalROPColor::Invert:
+ moFillColor = Color(0xff, 0xff, 0xff);
+ break;
+ }
+}
+
+void SkiaSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY)
+{
+ drawPixel(nX, nY, *moLineColor);
+}
+
+void SkiaSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY, Color nColor)
+{
+ preDraw();
+ SAL_INFO("vcl.skia.trace", "drawpixel(" << this << "): " << Point(nX, nY) << ":" << nColor);
+ addUpdateRegion(SkRect::MakeXYWH(nX, nY, 1, 1));
+ SkPaint paint = makePixelPaint(nColor);
+ // Apparently drawPixel() is actually expected to set the pixel and not draw it.
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
+ if (mScaling != 1 && isUnitTestRunning())
+ {
+ // On HiDPI displays, draw a square on the entire non-hidpi "pixel" when running unittests,
+ // since tests often require precise pixel drawing.
+ paint.setStrokeWidth(1); // this will be scaled by mScaling
+ paint.setStrokeCap(SkPaint::kSquare_Cap);
+ }
+ getDrawCanvas()->drawPoint(toSkX(nX), toSkY(nY), paint);
+ postDraw();
+}
+
+void SkiaSalGraphicsImpl::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2,
+ tools::Long nY2)
+{
+ if (!moLineColor)
+ return;
+ preDraw();
+ SAL_INFO("vcl.skia.trace", "drawline(" << this << "): " << Point(nX1, nY1) << "->"
+ << Point(nX2, nY2) << ":" << *moLineColor);
+ addUpdateRegion(SkRect::MakeLTRB(nX1, nY1, nX2, nY2).makeSorted());
+ SkPaint paint = makeLinePaint();
+ paint.setAntiAlias(mParent.getAntiAlias());
+ if (mScaling != 1 && isUnitTestRunning())
+ {
+ // On HiDPI displays, do not draw hairlines, draw 1-pixel wide lines in order to avoid
+ // smoothing that would confuse unittests.
+ paint.setStrokeWidth(1); // this will be scaled by mScaling
+ paint.setStrokeCap(SkPaint::kSquare_Cap);
+ }
+ getDrawCanvas()->drawLine(toSkX(nX1), toSkY(nY1), toSkX(nX2), toSkY(nY2), paint);
+ postDraw();
+}
+
+void SkiaSalGraphicsImpl::privateDrawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, double fTransparency,
+ bool blockAA)
+{
+ preDraw();
+ SAL_INFO("vcl.skia.trace", "privatedrawrect("
+ << this << "): " << SkIRect::MakeXYWH(nX, nY, nWidth, nHeight)
+ << ":" << dumpOptionalColor(moLineColor) << ":"
+ << dumpOptionalColor(moFillColor) << ":" << fTransparency);
+ addUpdateRegion(SkRect::MakeXYWH(nX, nY, nWidth, nHeight));
+ SkCanvas* canvas = getDrawCanvas();
+ if (moFillColor)
+ {
+ SkPaint paint = makeFillPaint(fTransparency);
+ paint.setAntiAlias(!blockAA && mParent.getAntiAlias());
+ // HACK: If the polygon is just a line, it still should be drawn. But when filling
+ // Skia doesn't draw empty polygons, so in that case ensure the line is drawn.
+ if (!moLineColor && SkSize::Make(nWidth, nHeight).isEmpty())
+ paint.setStyle(SkPaint::kStroke_Style);
+ canvas->drawIRect(SkIRect::MakeXYWH(nX, nY, nWidth, nHeight), paint);
+ }
+ if (moLineColor && moLineColor != moFillColor) // otherwise handled by fill
+ {
+ SkPaint paint = makeLinePaint(fTransparency);
+ paint.setAntiAlias(!blockAA && mParent.getAntiAlias());
+ if (mScaling != 1 && isUnitTestRunning())
+ {
+ // On HiDPI displays, do not draw just a hairline but instead a full-width "pixel" when running unittests,
+ // since tests often require precise pixel drawing.
+ paint.setStrokeWidth(1); // this will be scaled by mScaling
+ paint.setStrokeCap(SkPaint::kSquare_Cap);
+ }
+ // The obnoxious "-1 DrawRect()" hack that I don't understand the purpose of (and I'm not sure
+ // if anybody does), but without it some cases do not work. The max() is needed because Skia
+ // will not draw anything if width or height is 0.
+ canvas->drawRect(SkRect::MakeXYWH(toSkX(nX), toSkY(nY),
+ std::max(tools::Long(1), nWidth - 1),
+ std::max(tools::Long(1), nHeight - 1)),
+ paint);
+ }
+ postDraw();
+}
+
+void SkiaSalGraphicsImpl::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight)
+{
+ privateDrawAlphaRect(nX, nY, nWidth, nHeight, 0.0, true);
+}
+
+void SkiaSalGraphicsImpl::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry)
+{
+ basegfx::B2DPolygon aPolygon;
+ aPolygon.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
+ aPolygon.setClosed(false);
+
+ drawPolyLine(basegfx::B2DHomMatrix(), aPolygon, 0.0, 1.0, nullptr, basegfx::B2DLineJoin::Miter,
+ css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0) /*default*/, false);
+}
+
+void SkiaSalGraphicsImpl::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry)
+{
+ basegfx::B2DPolygon aPolygon;
+ aPolygon.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
+
+ drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aPolygon), 0.0);
+}
+
+void SkiaSalGraphicsImpl::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point** pPtAry)
+{
+ basegfx::B2DPolyPolygon aPolyPolygon;
+ for (sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
+ {
+ sal_uInt32 nPoints = pPoints[nPolygon];
+ if (nPoints)
+ {
+ const Point* pSubPoints = pPtAry[nPolygon];
+ basegfx::B2DPolygon aPolygon;
+ aPolygon.append(basegfx::B2DPoint(pSubPoints->getX(), pSubPoints->getY()), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ aPolygon.setB2DPoint(i,
+ basegfx::B2DPoint(pSubPoints[i].getX(), pSubPoints[i].getY()));
+
+ aPolyPolygon.append(aPolygon);
+ }
+ }
+
+ drawPolyPolygon(basegfx::B2DHomMatrix(), aPolyPolygon, 0.0);
+}
+
+void SkiaSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+ const bool bHasFill(moFillColor.has_value());
+ const bool bHasLine(moLineColor.has_value());
+
+ if (rPolyPolygon.count() == 0 || !(bHasFill || bHasLine) || fTransparency < 0.0
+ || fTransparency >= 1.0)
+ return;
+
+ basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
+ aPolyPolygon.transform(rObjectToDevice);
+
+ SAL_INFO("vcl.skia.trace", "drawpolypolygon(" << this << "): " << aPolyPolygon << ":"
+ << dumpOptionalColor(moLineColor) << ":"
+ << dumpOptionalColor(moFillColor));
+
+ if (delayDrawPolyPolygon(aPolyPolygon, fTransparency))
+ {
+ scheduleFlush();
+ return;
+ }
+
+ performDrawPolyPolygon(aPolyPolygon, fTransparency, mParent.getAntiAlias());
+}
+
+void SkiaSalGraphicsImpl::performDrawPolyPolygon(const basegfx::B2DPolyPolygon& aPolyPolygon,
+ double fTransparency, bool useAA)
+{
+ preDraw();
+
+ SkPath polygonPath;
+ bool hasOnlyOrthogonal = true;
+ addPolyPolygonToPath(aPolyPolygon, polygonPath, &hasOnlyOrthogonal);
+ polygonPath.setFillType(SkPathFillType::kEvenOdd);
+ addUpdateRegion(polygonPath.getBounds());
+
+ // For lines we use toSkX()/toSkY() in order to pass centers of pixels to Skia,
+ // as that leads to better results with floating-point coordinates
+ // (e.g. https://bugs.chromium.org/p/skia/issues/detail?id=9611).
+ // But that means that we generally need to use it also for areas, so that they
+ // line up properly if used together (tdf#134346).
+ // On the other hand, with AA enabled and rectangular areas, this leads to fuzzy
+ // edges (tdf#137329). But since rectangular areas line up perfectly to pixels
+ // everywhere, it shouldn't be necessary to do this for them.
+ // So if AA is enabled, avoid this fixup for rectangular areas.
+ if (!useAA || !hasOnlyOrthogonal)
+ {
+ // We normally use pixel at their center positions, but slightly off (see toSkX/Y()).
+ // With AA lines that "slightly off" causes tiny changes of color, making some tests
+ // fail. Since moving AA-ed line slightly to a side doesn't cause any real visual
+ // difference, just place exactly at the center. tdf#134346
+ const SkScalar posFix = useAA ? toSkXYFix : 0;
+ polygonPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
+ }
+ if (moFillColor)
+ {
+ SkPaint aPaint = makeFillPaint(fTransparency);
+ aPaint.setAntiAlias(useAA);
+ // HACK: If the polygon is just a line, it still should be drawn. But when filling
+ // Skia doesn't draw empty polygons, so in that case ensure the line is drawn.
+ if (!moLineColor && polygonPath.getBounds().isEmpty())
+ aPaint.setStyle(SkPaint::kStroke_Style);
+ getDrawCanvas()->drawPath(polygonPath, aPaint);
+ }
+ if (moLineColor && moLineColor != moFillColor) // otherwise handled by fill
+ {
+ SkPaint aPaint = makeLinePaint(fTransparency);
+ aPaint.setAntiAlias(useAA);
+ getDrawCanvas()->drawPath(polygonPath, aPaint);
+ }
+ postDraw();
+}
+
+namespace
+{
+struct LessThan
+{
+ bool operator()(const basegfx::B2DPoint& point1, const basegfx::B2DPoint& point2) const
+ {
+ if (basegfx::fTools::equal(point1.getX(), point2.getX()))
+ return basegfx::fTools::less(point1.getY(), point2.getY());
+ return basegfx::fTools::less(point1.getX(), point2.getX());
+ }
+};
+} // namespace
+
+bool SkiaSalGraphicsImpl::delayDrawPolyPolygon(const basegfx::B2DPolyPolygon& aPolyPolygon,
+ double fTransparency)
+{
+ // There is some code that needlessly subdivides areas into adjacent rectangles,
+ // but Skia doesn't line them up perfectly if AA is enabled (e.g. Cairo, Qt5 do,
+ // but Skia devs claim it's working as intended
+ // https://groups.google.com/d/msg/skia-discuss/NlKpD2X_5uc/Vuwd-kyYBwAJ).
+ // An example is tdf#133016, which triggers SvgStyleAttributes::add_stroke()
+ // implementing a line stroke as a bunch of polygons instead of just one, and
+ // SvgLinearAtomPrimitive2D::create2DDecomposition() creates a gradient
+ // as a series of polygons of gradually changing color. Those places should be
+ // changed, but try to merge those split polygons back into the original one,
+ // where the needlessly created edges causing problems will not exist.
+ // This means drawing of such polygons needs to be delayed, so that they can
+ // be possibly merged with the next one.
+ // Merge only polygons of the same properties (color, etc.), so the gradient problem
+ // actually isn't handled here.
+
+ // Only AA polygons need merging, because they do not line up well because of the AA of the edges.
+ if (!mParent.getAntiAlias())
+ return false;
+ // Only filled polygons without an outline are problematic.
+ if (!moFillColor || moLineColor)
+ return false;
+ // Merge only simple polygons, real polypolygons most likely aren't needlessly split,
+ // so they do not need joining.
+ if (aPolyPolygon.count() != 1)
+ return false;
+ // If the polygon is not closed, it doesn't mark an area to be filled.
+ if (!aPolyPolygon.isClosed())
+ return false;
+ // If a polygon does not contain a straight line, i.e. it's all curves, then do not merge.
+ // First of all that's even more expensive, and second it's very unlikely that it's a polygon
+ // split into more polygons.
+ if (!polygonContainsLine(aPolyPolygon))
+ return false;
+
+ if (mLastPolyPolygonInfo.polygons.size() != 0
+ && (mLastPolyPolygonInfo.transparency != fTransparency
+ || !mLastPolyPolygonInfo.bounds.overlaps(aPolyPolygon.getB2DRange())))
+ {
+ checkPendingDrawing(); // Cannot be parts of the same larger polygon, draw the last and reset.
+ }
+ if (!mLastPolyPolygonInfo.polygons.empty())
+ {
+ assert(aPolyPolygon.count() == 1);
+ assert(mLastPolyPolygonInfo.polygons.back().count() == 1);
+ // Check if the new and the previous polygon share at least one point. If not, then they
+ // cannot be adjacent polygons, so there's no point in trying to merge them.
+ bool sharePoint = false;
+ const basegfx::B2DPolygon& poly1 = aPolyPolygon.getB2DPolygon(0);
+ const basegfx::B2DPolygon& poly2 = mLastPolyPolygonInfo.polygons.back().getB2DPolygon(0);
+ o3tl::sorted_vector<basegfx::B2DPoint, LessThan> poly1Points; // for O(n log n)
+ poly1Points.reserve(poly1.count());
+ for (sal_uInt32 i = 0; i < poly1.count(); ++i)
+ poly1Points.insert(poly1.getB2DPoint(i));
+ for (sal_uInt32 i = 0; i < poly2.count(); ++i)
+ if (poly1Points.find(poly2.getB2DPoint(i)) != poly1Points.end())
+ {
+ sharePoint = true;
+ break;
+ }
+ if (!sharePoint)
+ checkPendingDrawing(); // Draw the previous one and reset.
+ }
+ // Collect the polygons that can be possibly merged. Do the merging only once at the end,
+ // because it's not a cheap operation.
+ mLastPolyPolygonInfo.polygons.push_back(aPolyPolygon);
+ mLastPolyPolygonInfo.bounds.expand(aPolyPolygon.getB2DRange());
+ mLastPolyPolygonInfo.transparency = fTransparency;
+ return true;
+}
+
+// Tdf#140848 - basegfx::utils::mergeToSinglePolyPolygon() seems to have rounding
+// errors that sometimes cause it to merge incorrectly.
+static void roundPolygonPoints(basegfx::B2DPolyPolygon& polyPolygon)
+{
+ for (basegfx::B2DPolygon& polygon : polyPolygon)
+ {
+ polygon.makeUnique();
+ for (sal_uInt32 i = 0; i < polygon.count(); ++i)
+ polygon.setB2DPoint(i, basegfx::B2DPoint(basegfx::fround(polygon.getB2DPoint(i))));
+ // Control points are saved as vectors relative to points, so hopefully
+ // there's no need to round those.
+ }
+}
+
+void SkiaSalGraphicsImpl::checkPendingDrawing()
+{
+ if (mLastPolyPolygonInfo.polygons.size() != 0)
+ { // Flush any pending polygon drawing.
+ basegfx::B2DPolyPolygonVector polygons;
+ std::swap(polygons, mLastPolyPolygonInfo.polygons);
+ double transparency = mLastPolyPolygonInfo.transparency;
+ mLastPolyPolygonInfo.bounds.reset();
+ if (polygons.size() == 1)
+ performDrawPolyPolygon(polygons.front(), transparency, true);
+ else
+ {
+ for (basegfx::B2DPolyPolygon& p : polygons)
+ roundPolygonPoints(p);
+ performDrawPolyPolygon(basegfx::utils::mergeToSinglePolyPolygon(polygons), transparency,
+ true);
+ }
+ }
+}
+
+bool SkiaSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& rPolyLine, double fTransparency,
+ double fLineWidth, const std::vector<double>* pStroke,
+ basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
+ bool bPixelSnapHairline)
+{
+ if (!rPolyLine.count() || fTransparency < 0.0 || fTransparency > 1.0 || !moLineColor)
+ {
+ return true;
+ }
+
+ preDraw();
+ SAL_INFO("vcl.skia.trace",
+ "drawpolyline(" << this << "): " << rPolyLine << ":" << *moLineColor);
+
+ // Adjust line width for object-to-device scale.
+ fLineWidth = (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
+ // On HiDPI displays, do not draw hairlines, draw 1-pixel wide lines in order to avoid
+ // smoothing that would confuse unittests.
+ if (fLineWidth == 0 && mScaling != 1 && isUnitTestRunning())
+ fLineWidth = 1; // this will be scaled by mScaling
+
+ // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
+ basegfx::B2DPolygon aPolyLine(rPolyLine);
+ aPolyLine.transform(rObjectToDevice);
+ if (bPixelSnapHairline)
+ {
+ aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine);
+ }
+
+ SkPaint aPaint = makeLinePaint(fTransparency);
+
+ switch (eLineJoin)
+ {
+ case basegfx::B2DLineJoin::Bevel:
+ aPaint.setStrokeJoin(SkPaint::kBevel_Join);
+ break;
+ case basegfx::B2DLineJoin::Round:
+ aPaint.setStrokeJoin(SkPaint::kRound_Join);
+ break;
+ case basegfx::B2DLineJoin::NONE:
+ break;
+ case basegfx::B2DLineJoin::Miter:
+ aPaint.setStrokeJoin(SkPaint::kMiter_Join);
+ // convert miter minimum angle to miter limit
+ aPaint.setStrokeMiter(1.0 / std::sin(fMiterMinimumAngle / 2.0));
+ break;
+ }
+
+ switch (eLineCap)
+ {
+ case css::drawing::LineCap_ROUND:
+ aPaint.setStrokeCap(SkPaint::kRound_Cap);
+ break;
+ case css::drawing::LineCap_SQUARE:
+ aPaint.setStrokeCap(SkPaint::kSquare_Cap);
+ break;
+ default: // css::drawing::LineCap_BUTT:
+ aPaint.setStrokeCap(SkPaint::kButt_Cap);
+ break;
+ }
+
+ aPaint.setStrokeWidth(fLineWidth);
+ aPaint.setAntiAlias(mParent.getAntiAlias());
+ // See the tdf#134346 comment above.
+ const SkScalar posFix = mParent.getAntiAlias() ? toSkXYFix : 0;
+
+ if (pStroke && std::accumulate(pStroke->begin(), pStroke->end(), 0.0) != 0)
+ {
+ std::vector<SkScalar> intervals;
+ // Transform size by the matrix.
+ for (double stroke : *pStroke)
+ intervals.push_back((rObjectToDevice * basegfx::B2DVector(stroke, 0)).getLength());
+ aPaint.setPathEffect(SkDashPathEffect::Make(intervals.data(), intervals.size(), 0));
+ }
+
+ // Skia does not support basegfx::B2DLineJoin::NONE, so in that case batch only if lines
+ // are not wider than a pixel.
+ if (eLineJoin != basegfx::B2DLineJoin::NONE || fLineWidth <= 1.0)
+ {
+ SkPath aPath;
+ aPath.incReserve(aPolyLine.count() * 3); // because cubicTo is 3 elements
+ addPolygonToPath(aPolyLine, aPath);
+ aPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
+ addUpdateRegion(aPath.getBounds());
+ getDrawCanvas()->drawPath(aPath, aPaint);
+ }
+ else
+ {
+ sal_uInt32 nPoints = aPolyLine.count();
+ bool bClosed = aPolyLine.isClosed();
+ bool bHasCurves = aPolyLine.areControlPointsUsed();
+ for (sal_uInt32 j = 0; j < nPoints; ++j)
+ {
+ SkPath aPath;
+ aPath.incReserve(2 * 3); // because cubicTo is 3 elements
+ addPolygonToPath(aPolyLine, aPath, j, j + 1, nPoints, bClosed, bHasCurves);
+ aPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
+ addUpdateRegion(aPath.getBounds());
+ getDrawCanvas()->drawPath(aPath, aPaint);
+ }
+ }
+
+ postDraw();
+
+ return true;
+}
+
+bool SkiaSalGraphicsImpl::drawPolyLineBezier(sal_uInt32, const Point*, const PolyFlags*)
+{
+ return false;
+}
+
+bool SkiaSalGraphicsImpl::drawPolygonBezier(sal_uInt32, const Point*, const PolyFlags*)
+{
+ return false;
+}
+
+bool SkiaSalGraphicsImpl::drawPolyPolygonBezier(sal_uInt32, const sal_uInt32*, const Point* const*,
+ const PolyFlags* const*)
+{
+ return false;
+}
+
+void SkiaSalGraphicsImpl::copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX,
+ tools::Long nSrcY, tools::Long nSrcWidth, tools::Long nSrcHeight,
+ bool /*bWindowInvalidate*/)
+{
+ if (nDestX == nSrcX && nDestY == nSrcY)
+ return;
+ preDraw();
+ SAL_INFO("vcl.skia.trace", "copyarea("
+ << this << "): " << Point(nSrcX, nSrcY) << "->"
+ << SkIRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight));
+ // Using SkSurface::draw() should be more efficient, but it's too buggy.
+ SalTwoRect rPosAry(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
+ privateCopyBits(rPosAry, this);
+ postDraw();
+}
+
+void SkiaSalGraphicsImpl::copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics)
+{
+ preDraw();
+ SkiaSalGraphicsImpl* src;
+ if (pSrcGraphics)
+ {
+ assert(dynamic_cast<SkiaSalGraphicsImpl*>(pSrcGraphics->GetImpl()));
+ src = static_cast<SkiaSalGraphicsImpl*>(pSrcGraphics->GetImpl());
+ src->checkSurface();
+ src->flushDrawing();
+ }
+ else
+ {
+ src = this;
+ assert(mXorMode == XorMode::None);
+ }
+ auto srcDebug = [&]() -> std::string {
+ if (src == this)
+ return "(self)";
+ else
+ {
+ std::ostringstream stream;
+ stream << "(" << src << ")";
+ return stream.str();
+ }
+ };
+ SAL_INFO("vcl.skia.trace", "copybits(" << this << "): " << srcDebug() << ": " << rPosAry);
+ privateCopyBits(rPosAry, src);
+ postDraw();
+}
+
+void SkiaSalGraphicsImpl::privateCopyBits(const SalTwoRect& rPosAry, SkiaSalGraphicsImpl* src)
+{
+ assert(mXorMode == XorMode::None);
+ addUpdateRegion(SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
+ rPosAry.mnDestHeight));
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
+ SkIRect srcRect = SkIRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth,
+ rPosAry.mnSrcHeight);
+ SkRect destRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
+ rPosAry.mnDestHeight);
+
+ if (!SkIRect::Intersects(srcRect, SkIRect::MakeWH(src->GetWidth(), src->GetHeight()))
+ || !SkRect::Intersects(destRect, SkRect::MakeWH(GetWidth(), GetHeight())))
+ return;
+
+ if (src == this)
+ {
+ // Copy-to-self means that we'd take a snapshot, which would refcount the data,
+ // and then drawing would result in copy in write, copying the entire surface.
+ // Try to copy less by making a snapshot of only what is needed.
+ // A complication here is that drawImageRect() can handle coordinates outside
+ // of surface fine, but makeImageSnapshot() will crop to the surface area,
+ // so do that manually here in order to adjust also destination rectangle.
+ if (srcRect.x() < 0 || srcRect.y() < 0)
+ {
+ destRect.fLeft += -srcRect.x();
+ destRect.fTop += -srcRect.y();
+ srcRect.adjust(-srcRect.x(), -srcRect.y(), 0, 0);
+ }
+ // Note that right() and bottom() are not inclusive (are outside of the rect).
+ if (srcRect.right() - 1 > GetWidth() || srcRect.bottom() - 1 > GetHeight())
+ {
+ destRect.fRight += GetWidth() - srcRect.right();
+ destRect.fBottom += GetHeight() - srcRect.bottom();
+ srcRect.adjust(0, 0, GetWidth() - srcRect.right(), GetHeight() - srcRect.bottom());
+ }
+ // Scaling for source coordinates must be done manually.
+ if (src->mScaling != 1)
+ srcRect = scaleRect(srcRect, src->mScaling);
+ sk_sp<SkImage> image = makeCheckedImageSnapshot(src->mSurface, srcRect);
+ srcRect.offset(-srcRect.x(), -srcRect.y());
+ getDrawCanvas()->drawImageRect(image, SkRect::Make(srcRect), destRect,
+ makeSamplingOptions(rPosAry, mScaling, src->mScaling),
+ &paint, SkCanvas::kFast_SrcRectConstraint);
+ }
+ else
+ {
+ // Scaling for source coordinates must be done manually.
+ if (src->mScaling != 1)
+ srcRect = scaleRect(srcRect, src->mScaling);
+ // Do not use makeImageSnapshot(rect), as that one may make a needless data copy.
+ getDrawCanvas()->drawImageRect(makeCheckedImageSnapshot(src->mSurface),
+ SkRect::Make(srcRect), destRect,
+ makeSamplingOptions(rPosAry, mScaling, src->mScaling),
+ &paint, SkCanvas::kFast_SrcRectConstraint);
+ }
+}
+
+bool SkiaSalGraphicsImpl::blendBitmap(const SalTwoRect& rPosAry, const SalBitmap& rBitmap)
+{
+ if (checkInvalidSourceOrDestination(rPosAry))
+ return false;
+
+ assert(dynamic_cast<const SkiaSalBitmap*>(&rBitmap));
+ const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rBitmap);
+ // This is used by VirtualDevice in the alpha mode for the "alpha" layer
+ // So the result is transparent only if both the inputs
+ // are transparent. Which seems to be what SkBlendMode::kModulate does,
+ // so use that.
+ // See also blendAlphaBitmap().
+ if (rSkiaBitmap.IsFullyOpaqueAsAlpha())
+ {
+ // Optimization. If the bitmap means fully opaque, it's all one's. In CPU
+ // mode it should be faster to just copy instead of SkBlendMode::kMultiply.
+ drawBitmap(rPosAry, rSkiaBitmap);
+ }
+ else
+ drawBitmap(rPosAry, rSkiaBitmap, SkBlendMode::kModulate);
+ return true;
+}
+
+bool SkiaSalGraphicsImpl::blendAlphaBitmap(const SalTwoRect& rPosAry,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap& rMaskBitmap,
+ const SalBitmap& rAlphaBitmap)
+{
+ // tdf#156361 use slow blending path if alpha mask blending is disabled
+ // SkiaSalGraphicsImpl::blendBitmap() fails unexpectedly in the following
+ // cases so return false and use the non-Skia alpha mask blending code:
+ // - Unexpected white areas when running a slideshow or printing:
+ // https://bugs.documentfoundation.org/attachment.cgi?id=188447
+ // - Unexpected scaling of bitmap and/or alpha mask when exporting to PDF:
+ // https://bugs.documentfoundation.org/attachment.cgi?id=188498
+ if (!SkiaHelper::isAlphaMaskBlendingEnabled())
+ return false;
+
+ if (checkInvalidSourceOrDestination(rPosAry))
+ return false;
+
+ assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
+ assert(dynamic_cast<const SkiaSalBitmap*>(&rMaskBitmap));
+ assert(dynamic_cast<const SkiaSalBitmap*>(&rAlphaBitmap));
+ const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
+ const SkiaSalBitmap& rSkiaMaskBitmap = static_cast<const SkiaSalBitmap&>(rMaskBitmap);
+ const SkiaSalBitmap& rSkiaAlphaBitmap = static_cast<const SkiaSalBitmap&>(rAlphaBitmap);
+
+ if (rSkiaMaskBitmap.IsFullyOpaqueAsAlpha())
+ {
+ // Optimization. If the mask of the bitmap to be blended means it's actually opaque,
+ // just draw the bitmap directly (that's what the math below will result in).
+ drawBitmap(rPosAry, rSkiaSourceBitmap);
+ return true;
+ }
+ // This was originally implemented for the OpenGL drawing method and it is poorly documented.
+ // The source and mask bitmaps are the usual data and alpha bitmaps, and 'alpha'
+ // is the "alpha" layer of the VirtualDevice (the alpha in VirtualDevice is also stored
+ // as a separate bitmap). Now if I understand it correctly these two alpha masks first need
+ // to be combined into the actual alpha mask to be used. The formula for TYPE_BLEND
+ // in opengl's combinedTextureFragmentShader.glsl is
+ // "result_alpha = 1.0 - (1.0 - floor(alpha)) * mask".
+ // See also blendBitmap().
+
+ SkSamplingOptions samplingOptions = makeSamplingOptions(rPosAry, mScaling);
+ // First do the "( 1 - alpha ) * mask"
+ // (no idea how to do "floor", but hopefully not needed in practice).
+ sk_sp<SkShader> shaderAlpha
+ = SkShaders::Blend(SkBlendMode::kDstIn, rSkiaMaskBitmap.GetAlphaSkShader(samplingOptions),
+ rSkiaAlphaBitmap.GetAlphaSkShader(samplingOptions));
+ // And now draw the bitmap with "1 - x", where x is the "( 1 - alpha ) * mask".
+ sk_sp<SkShader> shader = SkShaders::Blend(SkBlendMode::kSrcIn, shaderAlpha,
+ rSkiaSourceBitmap.GetSkShader(samplingOptions));
+ drawShader(rPosAry, shader);
+ return true;
+}
+
+void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
+{
+ if (checkInvalidSourceOrDestination(rPosAry))
+ return;
+
+ assert(dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap));
+ const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap);
+
+ drawBitmap(rPosAry, rSkiaSourceBitmap);
+}
+
+void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ const SalBitmap& rMaskBitmap)
+{
+ drawAlphaBitmap(rPosAry, rSalBitmap, rMaskBitmap);
+}
+
+void SkiaSalGraphicsImpl::drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ Color nMaskColor)
+{
+ assert(dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap));
+ const SkiaSalBitmap& skiaBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap);
+ // SkBlendMode::kDstOut must be used instead of SkBlendMode::kDstIn because
+ // the alpha channel of what is drawn appears to get inverted at some point
+ // after it is drawn
+ drawShader(
+ rPosAry,
+ SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is alpha.
+ SkShaders::Color(toSkColor(nMaskColor)),
+ skiaBitmap.GetAlphaSkShader(makeSamplingOptions(rPosAry, mScaling))));
+}
+
+std::shared_ptr<SalBitmap> SkiaSalGraphicsImpl::getBitmap(tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight)
+{
+ SkiaZone zone;
+ checkSurface();
+ SAL_INFO("vcl.skia.trace",
+ "getbitmap(" << this << "): " << SkIRect::MakeXYWH(nX, nY, nWidth, nHeight));
+ flushDrawing();
+ // TODO makeImageSnapshot(rect) may copy the data, which may be a waste if this is used
+ // e.g. for VirtualDevice's lame alpha blending, in which case the image will eventually end up
+ // in blendAlphaBitmap(), where we could simply use the proper rect of the image.
+ sk_sp<SkImage> image = makeCheckedImageSnapshot(
+ mSurface, scaleRect(SkIRect::MakeXYWH(nX, nY, nWidth, nHeight), mScaling));
+ std::shared_ptr<SkiaSalBitmap> bitmap = std::make_shared<SkiaSalBitmap>(image);
+ // If the surface is scaled for HiDPI, the bitmap needs to be scaled down, otherwise
+ // it would have incorrect size from the API point of view. The DirectImage::Yes handling
+ // in mergeCacheBitmaps() should access the original unscaled bitmap data to avoid
+ // pointless scaling back and forth.
+ if (mScaling != 1)
+ {
+ if (!isUnitTestRunning())
+ bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, goodScalingQuality());
+ else
+ {
+ // Some tests require exact pixel values and would be confused by smooth-scaling.
+ // And some draw something smooth and not smooth-scaling there would break the checks.
+ if (isUnitTestRunning("BackendTest__testDrawHaflEllipseAAWithPolyLineB2D_")
+ || isUnitTestRunning("BackendTest__testDrawRectAAWithLine_")
+ || isUnitTestRunning("GraphicsRenderTest__testDrawRectAAWithLine"))
+ {
+ bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, goodScalingQuality());
+ }
+ else
+ bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, BmpScaleFlag::NearestNeighbor);
+ }
+ }
+ return bitmap;
+}
+
+Color SkiaSalGraphicsImpl::getPixel(tools::Long nX, tools::Long nY)
+{
+ SkiaZone zone;
+ checkSurface();
+ SAL_INFO("vcl.skia.trace", "getpixel(" << this << "): " << Point(nX, nY));
+ flushDrawing();
+ // This is presumably slow, but getPixel() should be generally used only by unit tests.
+ SkBitmap bitmap;
+ if (!bitmap.tryAllocN32Pixels(mSurface->width(), mSurface->height()))
+ abort();
+ if (!mSurface->readPixels(bitmap, 0, 0))
+ abort();
+ return fromSkColor(bitmap.getColor(nX * mScaling, nY * mScaling));
+}
+
+void SkiaSalGraphicsImpl::invert(basegfx::B2DPolygon const& rPoly, SalInvert eFlags)
+{
+ preDraw();
+ SAL_INFO("vcl.skia.trace", "invert(" << this << "): " << rPoly << ":" << int(eFlags));
+ assert(mXorMode == XorMode::None);
+ SkPath aPath;
+ aPath.incReserve(rPoly.count());
+ addPolygonToPath(rPoly, aPath);
+ aPath.setFillType(SkPathFillType::kEvenOdd);
+ addUpdateRegion(aPath.getBounds());
+ SkAutoCanvasRestore autoRestore(getDrawCanvas(), true);
+ SkPaint aPaint;
+ // There's no blend mode for inverting as such, but kExclusion is 's + d - 2*s*d',
+ // so with d = 1.0 (all channels) it becomes effectively '1 - s', i.e. inverted color.
+ aPaint.setBlendMode(SkBlendMode::kExclusion);
+ aPaint.setColor(SkColorSetARGB(255, 255, 255, 255));
+ // TrackFrame just inverts a dashed path around the polygon
+ if (eFlags == SalInvert::TrackFrame)
+ {
+ // TrackFrame is not supposed to paint outside of the polygon (usually rectangle),
+ // but wider stroke width usually results in that, so ensure the requirement
+ // by clipping.
+ getDrawCanvas()->clipRect(aPath.getBounds(), SkClipOp::kIntersect, false);
+ aPaint.setStrokeWidth(2);
+ constexpr float intervals[] = { 4.0f, 4.0f };
+ aPaint.setStyle(SkPaint::kStroke_Style);
+ aPaint.setPathEffect(SkDashPathEffect::Make(intervals, std::size(intervals), 0));
+ }
+ else
+ {
+ aPaint.setStyle(SkPaint::kFill_Style);
+
+ // N50 inverts in checker pattern
+ if (eFlags == SalInvert::N50)
+ {
+ // This creates 2x2 checker pattern bitmap
+ // TODO Use createSkSurface() and cache the image
+ SkBitmap aBitmap;
+ aBitmap.allocN32Pixels(2, 2);
+ const SkPMColor white = SkPreMultiplyARGB(0xFF, 0xFF, 0xFF, 0xFF);
+ const SkPMColor black = SkPreMultiplyARGB(0xFF, 0x00, 0x00, 0x00);
+ SkPMColor* scanline;
+ scanline = aBitmap.getAddr32(0, 0);
+ *scanline++ = white;
+ *scanline++ = black;
+ scanline = aBitmap.getAddr32(0, 1);
+ *scanline++ = black;
+ *scanline++ = white;
+ aBitmap.setImmutable();
+ // The bitmap is repeated in both directions the checker pattern is as big
+ // as the polygon (usually rectangle)
+ aPaint.setShader(
+ aBitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, SkSamplingOptions()));
+ }
+ }
+ getDrawCanvas()->drawPath(aPath, aPaint);
+ postDraw();
+}
+
+void SkiaSalGraphicsImpl::invert(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, SalInvert eFlags)
+{
+ basegfx::B2DRectangle aRectangle(nX, nY, nX + nWidth, nY + nHeight);
+ auto aRect = basegfx::utils::createPolygonFromRect(aRectangle);
+ invert(aRect, eFlags);
+}
+
+void SkiaSalGraphicsImpl::invert(sal_uInt32 nPoints, const Point* pPointArray, SalInvert eFlags)
+{
+ basegfx::B2DPolygon aPolygon;
+ aPolygon.append(basegfx::B2DPoint(pPointArray[0].getX(), pPointArray[0].getY()), nPoints);
+ for (sal_uInt32 i = 1; i < nPoints; ++i)
+ {
+ aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPointArray[i].getX(), pPointArray[i].getY()));
+ }
+ aPolygon.setClosed(true);
+
+ invert(aPolygon, eFlags);
+}
+
+bool SkiaSalGraphicsImpl::drawEPS(tools::Long, tools::Long, tools::Long, tools::Long, void*,
+ sal_uInt32)
+{
+ return false;
+}
+
+// Create SkImage from a bitmap and possibly an alpha mask (the usual VCL one-minus-alpha),
+// with the given target size. Result will be possibly cached, unless disabled.
+// Especially in raster mode scaling and alpha blending may be expensive if done repeatedly.
+sk_sp<SkImage> SkiaSalGraphicsImpl::mergeCacheBitmaps(const SkiaSalBitmap& bitmap,
+ const SkiaSalBitmap* alphaBitmap,
+ const Size& targetSize)
+{
+ if (alphaBitmap)
+ assert(bitmap.GetSize() == alphaBitmap->GetSize());
+
+ if (targetSize.IsEmpty())
+ return {};
+ if (alphaBitmap && alphaBitmap->IsFullyOpaqueAsAlpha())
+ alphaBitmap = nullptr; // the alpha can be ignored
+ if (bitmap.PreferSkShader() && (!alphaBitmap || alphaBitmap->PreferSkShader()))
+ return {};
+
+ // If the bitmap has SkImage that matches the required size, try to use it, even
+ // if it doesn't match bitmap.GetSize(). This can happen with delayed scaling.
+ // This will catch cases such as some code pre-scaling the bitmap, which would make GetSkImage()
+ // scale, changing GetImageKey() in the process so we'd have to re-cache, and then we'd need
+ // to scale again in this function.
+ bool bitmapReady = false;
+ bool alphaBitmapReady = false;
+ if (const sk_sp<SkImage>& image = bitmap.GetSkImage(DirectImage::Yes))
+ {
+ assert(!bitmap.PreferSkShader());
+ if (imageSize(image) == targetSize)
+ bitmapReady = true;
+ }
+ // If the image usable and there's no alpha, then it matches exactly what's wanted.
+ if (bitmapReady && !alphaBitmap)
+ return bitmap.GetSkImage(DirectImage::Yes);
+ if (alphaBitmap)
+ {
+ if (!alphaBitmap->GetAlphaSkImage(DirectImage::Yes)
+ && alphaBitmap->GetSkImage(DirectImage::Yes)
+ && imageSize(alphaBitmap->GetSkImage(DirectImage::Yes)) == targetSize)
+ {
+ // There's a usable non-alpha image, try to convert it to alpha.
+ assert(!alphaBitmap->PreferSkShader());
+ const_cast<SkiaSalBitmap*>(alphaBitmap)->TryDirectConvertToAlphaNoScaling();
+ }
+ if (const sk_sp<SkImage>& image = alphaBitmap->GetAlphaSkImage(DirectImage::Yes))
+ {
+ assert(!alphaBitmap->PreferSkShader());
+ if (imageSize(image) == targetSize)
+ alphaBitmapReady = true;
+ }
+ }
+
+ if (bitmapReady && (!alphaBitmap || alphaBitmapReady))
+ {
+ // Try to find a cached image based on the already existing images.
+ OString key = makeCachedImageKey(bitmap, alphaBitmap, targetSize, DirectImage::Yes,
+ DirectImage::Yes);
+ if (sk_sp<SkImage> image = findCachedImage(key))
+ {
+ assert(imageSize(image) == targetSize);
+ return image;
+ }
+ }
+
+ // Probably not much point in caching of just doing a copy.
+ if (alphaBitmap == nullptr && targetSize == bitmap.GetSize())
+ return {};
+ // Image too small to be worth caching if not scaling.
+ if (targetSize == bitmap.GetSize() && targetSize.Width() < 100 && targetSize.Height() < 100)
+ return {};
+ // GPU-accelerated drawing with SkShader should be fast enough to not need caching.
+ if (isGPU())
+ {
+ // tdf#140925: But if this is such an extensive downscaling that caching the result
+ // would noticeably reduce amount of data processed by the GPU on repeated usage, do it.
+ int reduceRatio = bitmap.GetSize().Width() * bitmap.GetSize().Height() / targetSize.Width()
+ / targetSize.Height();
+ if (reduceRatio < 10)
+ return {};
+ }
+ // Do not cache the result if it would take most of the cache and thus get evicted soon.
+ if (targetSize.Width() * targetSize.Height() * 4 > maxImageCacheSize() * 0.7)
+ return {};
+
+ // Use ready direct image if they are both available, now even the size doesn't matter
+ // (we'll scale as necessary and it's better to scale from the original). Require only
+ // that they are the same size, or that one prefers a shader or doesn't exist
+ // (i.e. avoid two images of different size).
+ bitmapReady = bitmap.GetSkImage(DirectImage::Yes) != nullptr;
+ alphaBitmapReady = alphaBitmap && alphaBitmap->GetAlphaSkImage(DirectImage::Yes) != nullptr;
+ if (bitmapReady && alphaBitmap && !alphaBitmapReady && !alphaBitmap->PreferSkShader())
+ bitmapReady = false;
+ if (alphaBitmapReady && !bitmapReady && bitmap.PreferSkShader())
+ alphaBitmapReady = false;
+
+ DirectImage bitmapType = bitmapReady ? DirectImage::Yes : DirectImage::No;
+ DirectImage alphaBitmapType = alphaBitmapReady ? DirectImage::Yes : DirectImage::No;
+
+ // Try to find a cached result, this time after possible delayed scaling.
+ OString key = makeCachedImageKey(bitmap, alphaBitmap, targetSize, bitmapType, alphaBitmapType);
+ if (sk_sp<SkImage> image = findCachedImage(key))
+ {
+ assert(imageSize(image) == targetSize);
+ return image;
+ }
+
+ // In some cases (tdf#134237) the target size may be very large. In that case it's
+ // better to rely on Skia to clip and draw only the necessary, rather than prepare
+ // a very large image only to not use most of it. Do this only after checking whether
+ // the image is already cached, since it might have been already cached in a previous
+ // call that had the draw area large enough to be seen as worth caching.
+ const Size drawAreaSize = mClipRegion.GetBoundRect().GetSize() * mScaling;
+ if (targetSize.Width() > drawAreaSize.Width() || targetSize.Height() > drawAreaSize.Height())
+ {
+ // This is a bit tricky. The condition above just checks that at least a part of the resulting
+ // image will not be used (it's larger then our drawing area). But this may often happen
+ // when just scrolling a document with a large image, where the caching may very well be worth it.
+ // Since the problem is mainly the cost of upscaling and then the size of the resulting bitmap,
+ // compute a ratio of how much this is going to be scaled up, how much this is larger than
+ // the drawing area, and then refuse to cache if it's too much.
+ const double upscaleRatio
+ = std::max(1.0, 1.0 * targetSize.Width() / bitmap.GetSize().Width()
+ * targetSize.Height() / bitmap.GetSize().Height());
+ const double oversizeRatio = 1.0 * targetSize.Width() / drawAreaSize.Width()
+ * targetSize.Height() / drawAreaSize.Height();
+ const double ratio = upscaleRatio * oversizeRatio;
+ if (ratio > 4)
+ {
+ SAL_INFO("vcl.skia.trace", "mergecachebitmaps("
+ << this << "): not caching, ratio:" << ratio << ", "
+ << bitmap.GetSize() << "->" << targetSize << " in "
+ << drawAreaSize);
+ return {};
+ }
+ }
+
+ Size sourceSize;
+ if (bitmapReady)
+ sourceSize = imageSize(bitmap.GetSkImage(DirectImage::Yes));
+ else if (alphaBitmapReady)
+ sourceSize = imageSize(alphaBitmap->GetAlphaSkImage(DirectImage::Yes));
+ else
+ sourceSize = bitmap.GetSize();
+
+ // Generate a new result and cache it.
+ sk_sp<SkSurface> tmpSurface
+ = createSkSurface(targetSize, alphaBitmap ? kPremul_SkAlphaType : bitmap.alphaType());
+ if (!tmpSurface)
+ return nullptr;
+ SkCanvas* canvas = tmpSurface->getCanvas();
+ SkAutoCanvasRestore autoRestore(canvas, true);
+ SkPaint paint;
+ SkSamplingOptions samplingOptions;
+ if (targetSize != sourceSize)
+ {
+ SkMatrix matrix;
+ matrix.set(SkMatrix::kMScaleX, 1.0 * targetSize.Width() / sourceSize.Width());
+ matrix.set(SkMatrix::kMScaleY, 1.0 * targetSize.Height() / sourceSize.Height());
+ canvas->concat(matrix);
+ if (!isUnitTestRunning()) // unittests want exact pixel values
+ samplingOptions = makeSamplingOptions(matrix, 1);
+ }
+ if (alphaBitmap != nullptr)
+ {
+ canvas->clear(SK_ColorTRANSPARENT);
+ paint.setShader(
+ SkShaders::Blend(SkBlendMode::kDstIn, bitmap.GetSkShader(samplingOptions, bitmapType),
+ alphaBitmap->GetAlphaSkShader(samplingOptions, alphaBitmapType)));
+ canvas->drawPaint(paint);
+ }
+ else if (bitmap.PreferSkShader())
+ {
+ paint.setShader(bitmap.GetSkShader(samplingOptions, bitmapType));
+ canvas->drawPaint(paint);
+ }
+ else
+ canvas->drawImage(bitmap.GetSkImage(bitmapType), 0, 0, samplingOptions, &paint);
+ if (isGPU())
+ SAL_INFO("vcl.skia.trace", "mergecachebitmaps(" << this << "): caching GPU downscaling:"
+ << bitmap.GetSize() << "->" << targetSize);
+ sk_sp<SkImage> image = makeCheckedImageSnapshot(tmpSurface);
+ addCachedImage(key, image);
+ return image;
+}
+
+OString SkiaSalGraphicsImpl::makeCachedImageKey(const SkiaSalBitmap& bitmap,
+ const SkiaSalBitmap* alphaBitmap,
+ const Size& targetSize, DirectImage bitmapType,
+ DirectImage alphaBitmapType)
+{
+ OString key = OString::number(targetSize.Width()) + "x" + OString::number(targetSize.Height())
+ + "_" + bitmap.GetImageKey(bitmapType);
+ if (alphaBitmap)
+ key += "_" + alphaBitmap->GetAlphaImageKey(alphaBitmapType);
+ return key;
+}
+
+bool SkiaSalGraphicsImpl::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap)
+{
+ assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
+ assert(dynamic_cast<const SkiaSalBitmap*>(&rAlphaBitmap));
+ const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
+ const SkiaSalBitmap& rSkiaAlphaBitmap = static_cast<const SkiaSalBitmap&>(rAlphaBitmap);
+ // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
+ // alpha blending or scaling.
+ SalTwoRect imagePosAry(rPosAry);
+ Size imageSize = rSourceBitmap.GetSize();
+ // If the bitmap will be scaled, prefer to do it in mergeCacheBitmaps(), if possible.
+ if ((rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
+ && rPosAry.mnSrcX == 0 && rPosAry.mnSrcY == 0
+ && rPosAry.mnSrcWidth == rSourceBitmap.GetSize().Width()
+ && rPosAry.mnSrcHeight == rSourceBitmap.GetSize().Height())
+ {
+ imagePosAry.mnSrcWidth = imagePosAry.mnDestWidth;
+ imagePosAry.mnSrcHeight = imagePosAry.mnDestHeight;
+ imageSize = Size(imagePosAry.mnSrcWidth, imagePosAry.mnSrcHeight);
+ }
+ sk_sp<SkImage> image
+ = mergeCacheBitmaps(rSkiaSourceBitmap, &rSkiaAlphaBitmap, imageSize * mScaling);
+ if (image)
+ drawImage(imagePosAry, image, mScaling);
+ else if (rSkiaAlphaBitmap.IsFullyOpaqueAsAlpha()
+ && !rSkiaSourceBitmap.PreferSkShader()) // alpha can be ignored
+ drawBitmap(rPosAry, rSkiaSourceBitmap);
+ else
+ drawShader(rPosAry,
+ SkShaders::Blend(
+ SkBlendMode::kDstIn,
+ rSkiaSourceBitmap.GetSkShader(makeSamplingOptions(rPosAry, mScaling)),
+ rSkiaAlphaBitmap.GetAlphaSkShader(makeSamplingOptions(rPosAry, mScaling))));
+ return true;
+}
+
+void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SkiaSalBitmap& bitmap,
+ SkBlendMode blendMode)
+{
+ // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
+ // scaling.
+ SalTwoRect imagePosAry(rPosAry);
+ Size imageSize = bitmap.GetSize();
+ // If the bitmap will be scaled, prefer to do it in mergeCacheBitmaps(), if possible.
+ if ((rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
+ && rPosAry.mnSrcX == 0 && rPosAry.mnSrcY == 0
+ && rPosAry.mnSrcWidth == bitmap.GetSize().Width()
+ && rPosAry.mnSrcHeight == bitmap.GetSize().Height())
+ {
+ imagePosAry.mnSrcWidth = imagePosAry.mnDestWidth;
+ imagePosAry.mnSrcHeight = imagePosAry.mnDestHeight;
+ imageSize = Size(imagePosAry.mnSrcWidth, imagePosAry.mnSrcHeight);
+ }
+ sk_sp<SkImage> image = mergeCacheBitmaps(bitmap, nullptr, imageSize * mScaling);
+ if (image)
+ drawImage(imagePosAry, image, mScaling, blendMode);
+ else if (bitmap.PreferSkShader())
+ drawShader(rPosAry, bitmap.GetSkShader(makeSamplingOptions(rPosAry, mScaling)), blendMode);
+ else
+ drawImage(rPosAry, bitmap.GetSkImage(), 1, blendMode);
+}
+
+void SkiaSalGraphicsImpl::drawImage(const SalTwoRect& rPosAry, const sk_sp<SkImage>& aImage,
+ int srcScaling, SkBlendMode eBlendMode)
+{
+ SkRect aSourceRect
+ = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+ if (srcScaling != 1)
+ aSourceRect = scaleRect(aSourceRect, srcScaling);
+ SkRect aDestinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY,
+ rPosAry.mnDestWidth, rPosAry.mnDestHeight);
+
+ SkPaint aPaint = makeBitmapPaint();
+ aPaint.setBlendMode(eBlendMode);
+
+ preDraw();
+ SAL_INFO("vcl.skia.trace",
+ "drawimage(" << this << "): " << rPosAry << ":" << SkBlendMode_Name(eBlendMode));
+ addUpdateRegion(aDestinationRect);
+ getDrawCanvas()->drawImageRect(aImage, aSourceRect, aDestinationRect,
+ makeSamplingOptions(rPosAry, mScaling, srcScaling), &aPaint,
+ SkCanvas::kFast_SrcRectConstraint);
+ ++pendingOperationsToFlush; // tdf#136369
+ postDraw();
+}
+
+// SkShader can be used to merge multiple bitmaps with appropriate blend modes (e.g. when
+// merging a bitmap with its alpha mask).
+void SkiaSalGraphicsImpl::drawShader(const SalTwoRect& rPosAry, const sk_sp<SkShader>& shader,
+ SkBlendMode blendMode)
+{
+ preDraw();
+ SAL_INFO("vcl.skia.trace", "drawshader(" << this << "): " << rPosAry);
+ SkRect destinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
+ rPosAry.mnDestHeight);
+ addUpdateRegion(destinationRect);
+ SkPaint paint = makeBitmapPaint();
+ paint.setBlendMode(blendMode);
+ paint.setShader(shader);
+ SkCanvas* canvas = getDrawCanvas();
+ // Scaling needs to be done explicitly using a matrix.
+ SkAutoCanvasRestore autoRestore(canvas, true);
+ SkMatrix matrix = SkMatrix::Translate(rPosAry.mnDestX, rPosAry.mnDestY)
+ * SkMatrix::Scale(1.0 * rPosAry.mnDestWidth / rPosAry.mnSrcWidth,
+ 1.0 * rPosAry.mnDestHeight / rPosAry.mnSrcHeight)
+ * SkMatrix::Translate(-rPosAry.mnSrcX, -rPosAry.mnSrcY);
+#ifndef NDEBUG
+ // Handle floating point imprecisions, round p1 to 2 decimal places.
+ auto compareRounded = [](const SkPoint& p1, const SkPoint& p2) {
+ return rtl::math::round(p1.x(), 2) == p2.x() && rtl::math::round(p1.y(), 2) == p2.y();
+ };
+#endif
+ assert(compareRounded(matrix.mapXY(rPosAry.mnSrcX, rPosAry.mnSrcY),
+ SkPoint::Make(rPosAry.mnDestX, rPosAry.mnDestY)));
+ assert(compareRounded(
+ matrix.mapXY(rPosAry.mnSrcX + rPosAry.mnSrcWidth, rPosAry.mnSrcY + rPosAry.mnSrcHeight),
+ SkPoint::Make(rPosAry.mnDestX + rPosAry.mnDestWidth,
+ rPosAry.mnDestY + rPosAry.mnDestHeight)));
+ canvas->concat(matrix);
+ SkRect sourceRect
+ = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+ canvas->drawRect(sourceRect, paint);
+ postDraw();
+}
+
+bool SkiaSalGraphicsImpl::hasFastDrawTransformedBitmap() const
+{
+ // Return true even in raster mode, even that way Skia is faster than e.g. GraphicObject
+ // trying to handle stuff manually.
+ return true;
+}
+
+// Whether applying matrix needs image smoothing for the transformation.
+static bool matrixNeedsHighQuality(const SkMatrix& matrix)
+{
+ if (matrix.isIdentity())
+ return false;
+ if (matrix.isScaleTranslate())
+ {
+ if (abs(matrix.getScaleX()) == 1 && abs(matrix.getScaleY()) == 1)
+ return false; // Only at most flipping and keeping the size.
+ return true;
+ }
+ assert(!matrix.hasPerspective()); // we do not use this
+ if (matrix.getScaleX() == 0 && matrix.getScaleY() == 0)
+ {
+ // Rotating 90 or 270 degrees while keeping the size.
+ if ((matrix.getSkewX() == 1 && matrix.getSkewY() == -1)
+ || (matrix.getSkewX() == -1 && matrix.getSkewY() == 1))
+ return false;
+ }
+ return true;
+}
+
+namespace SkiaTests
+{
+bool matrixNeedsHighQuality(const SkMatrix& matrix) { return ::matrixNeedsHighQuality(matrix); }
+}
+
+bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap, double fAlpha)
+{
+ assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
+ assert(!pAlphaBitmap || dynamic_cast<const SkiaSalBitmap*>(pAlphaBitmap));
+
+ const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
+ const SkiaSalBitmap* pSkiaAlphaBitmap = static_cast<const SkiaSalBitmap*>(pAlphaBitmap);
+
+ if (pSkiaAlphaBitmap && pSkiaAlphaBitmap->IsFullyOpaqueAsAlpha())
+ pSkiaAlphaBitmap = nullptr; // the alpha can be ignored
+
+ // Setup the image transformation,
+ // using the rNull, rX, rY points as destinations for the (0,0), (Width,0), (0,Height) source points.
+ const basegfx::B2DVector aXRel = rX - rNull;
+ const basegfx::B2DVector aYRel = rY - rNull;
+
+ preDraw();
+ SAL_INFO("vcl.skia.trace", "drawtransformedbitmap(" << this << "): " << rSourceBitmap.GetSize()
+ << " " << rNull << ":" << rX << ":" << rY);
+
+ addUpdateRegion(SkRect::MakeWH(GetWidth(), GetHeight())); // can't tell, use whole area
+ // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
+ // alpha blending or scaling.
+ // The extra fAlpha blending is not cached, with the assumption that it usually gradually changes
+ // for each invocation.
+ // Pass size * mScaling to mergeCacheBitmaps() so that it prepares the size that will be needed
+ // after the mScaling-scaling matrix, but otherwise calculate everything else using the VCL coordinates.
+ Size imageSize(round(aXRel.getLength()), round(aYRel.getLength()));
+ sk_sp<SkImage> imageToDraw
+ = mergeCacheBitmaps(rSkiaBitmap, pSkiaAlphaBitmap, imageSize * mScaling);
+ if (imageToDraw)
+ {
+ SkMatrix matrix;
+ // Round sizes for scaling, so that sub-pixel differences don't
+ // trigger unnecessary scaling. Image has already been scaled
+ // by mergeCacheBitmaps() and we shouldn't scale here again
+ // unless the drawing is also skewed.
+ matrix.set(SkMatrix::kMScaleX, round(aXRel.getX()) / imageSize.Width());
+ matrix.set(SkMatrix::kMScaleY, round(aYRel.getY()) / imageSize.Height());
+ matrix.set(SkMatrix::kMSkewY, aXRel.getY() / imageSize.Width());
+ matrix.set(SkMatrix::kMSkewX, aYRel.getX() / imageSize.Height());
+ matrix.set(SkMatrix::kMTransX, rNull.getX());
+ matrix.set(SkMatrix::kMTransY, rNull.getY());
+ SkCanvas* canvas = getDrawCanvas();
+ SkAutoCanvasRestore autoRestore(canvas, true);
+ canvas->concat(matrix);
+ SkSamplingOptions samplingOptions;
+ // If the matrix changes geometry, we need to smooth-scale. If there's mScaling,
+ // that's already been handled by mergeCacheBitmaps().
+ if (matrixNeedsHighQuality(matrix))
+ samplingOptions = makeSamplingOptions(matrix, 1);
+ if (fAlpha == 1.0)
+ {
+ // Specify sizes to scale the image size back if needed (because of mScaling).
+ SkRect dstRect = SkRect::MakeWH(imageSize.Width(), imageSize.Height());
+ SkRect srcRect = SkRect::MakeWH(imageToDraw->width(), imageToDraw->height());
+ SkPaint paint = makeBitmapPaint();
+ canvas->drawImageRect(imageToDraw, srcRect, dstRect, samplingOptions, &paint,
+ SkCanvas::kFast_SrcRectConstraint);
+ }
+ else
+ {
+ SkPaint paint = makeBitmapPaint();
+ // Scale the image size back if needed.
+ SkMatrix scale = SkMatrix::Scale(1.0 / mScaling, 1.0 / mScaling);
+ paint.setShader(SkShaders::Blend(
+ SkBlendMode::kDstIn, imageToDraw->makeShader(samplingOptions, &scale),
+ SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
+ canvas->drawRect(SkRect::MakeWH(imageSize.Width(), imageSize.Height()), paint);
+ }
+ }
+ else
+ {
+ SkMatrix matrix;
+ const Size aSize = rSourceBitmap.GetSize();
+ matrix.set(SkMatrix::kMScaleX, aXRel.getX() / aSize.Width());
+ matrix.set(SkMatrix::kMScaleY, aYRel.getY() / aSize.Height());
+ matrix.set(SkMatrix::kMSkewY, aXRel.getY() / aSize.Width());
+ matrix.set(SkMatrix::kMSkewX, aYRel.getX() / aSize.Height());
+ matrix.set(SkMatrix::kMTransX, rNull.getX());
+ matrix.set(SkMatrix::kMTransY, rNull.getY());
+ SkCanvas* canvas = getDrawCanvas();
+ SkAutoCanvasRestore autoRestore(canvas, true);
+ canvas->concat(matrix);
+ SkSamplingOptions samplingOptions;
+ if (matrixNeedsHighQuality(matrix) || (mScaling != 1 && !isUnitTestRunning()))
+ samplingOptions = makeSamplingOptions(matrix, mScaling);
+ if (pSkiaAlphaBitmap)
+ {
+ SkPaint paint = makeBitmapPaint();
+ paint.setShader(SkShaders::Blend(SkBlendMode::kDstIn,
+ rSkiaBitmap.GetSkShader(samplingOptions),
+ pSkiaAlphaBitmap->GetAlphaSkShader(samplingOptions)));
+ if (fAlpha != 1.0)
+ paint.setShader(
+ SkShaders::Blend(SkBlendMode::kDstIn, paint.refShader(),
+ SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
+ canvas->drawRect(SkRect::MakeWH(aSize.Width(), aSize.Height()), paint);
+ }
+ else if (rSkiaBitmap.PreferSkShader() || fAlpha != 1.0)
+ {
+ SkPaint paint = makeBitmapPaint();
+ paint.setShader(rSkiaBitmap.GetSkShader(samplingOptions));
+ if (fAlpha != 1.0)
+ paint.setShader(
+ SkShaders::Blend(SkBlendMode::kDstIn, paint.refShader(),
+ SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
+ canvas->drawRect(SkRect::MakeWH(aSize.Width(), aSize.Height()), paint);
+ }
+ else
+ {
+ SkPaint paint = makeBitmapPaint();
+ canvas->drawImage(rSkiaBitmap.GetSkImage(), 0, 0, samplingOptions, &paint);
+ }
+ }
+ postDraw();
+ return true;
+}
+
+bool SkiaSalGraphicsImpl::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, sal_uInt8 nTransparency)
+{
+ privateDrawAlphaRect(nX, nY, nWidth, nHeight, nTransparency / 100.0);
+ return true;
+}
+
+bool SkiaSalGraphicsImpl::drawGradient(const tools::PolyPolygon& rPolyPolygon,
+ const Gradient& rGradient)
+{
+ if (rGradient.GetStyle() != css::awt::GradientStyle_LINEAR
+ && rGradient.GetStyle() != css::awt::GradientStyle_AXIAL
+ && rGradient.GetStyle() != css::awt::GradientStyle_RADIAL)
+ return false; // unsupported
+ if (rGradient.GetSteps() != 0)
+ return false; // We can't tell Skia how many colors to use in the gradient.
+ preDraw();
+ SAL_INFO("vcl.skia.trace", "drawgradient(" << this << "): " << rPolyPolygon.getB2DPolyPolygon()
+ << ":" << static_cast<int>(rGradient.GetStyle()));
+ tools::Rectangle boundRect(rPolyPolygon.GetBoundRect());
+ if (boundRect.IsEmpty())
+ return true;
+ SkPath path;
+ if (rPolyPolygon.IsRect())
+ {
+ // Rect->Polygon conversion loses the right and bottom edge, fix that.
+ path.addRect(SkRect::MakeXYWH(boundRect.getX(), boundRect.getY(), boundRect.GetWidth(),
+ boundRect.GetHeight()));
+ boundRect.AdjustRight(1);
+ boundRect.AdjustBottom(1);
+ }
+ else
+ addPolyPolygonToPath(rPolyPolygon.getB2DPolyPolygon(), path);
+ path.setFillType(SkPathFillType::kEvenOdd);
+ addUpdateRegion(path.getBounds());
+
+ Gradient aGradient(rGradient);
+ tools::Rectangle aBoundRect;
+ Point aCenter;
+ aGradient.SetAngle(aGradient.GetAngle() + 2700_deg10);
+ aGradient.GetBoundRect(boundRect, aBoundRect, aCenter);
+
+ SkColor startColor
+ = toSkColorWithIntensity(rGradient.GetStartColor(), rGradient.GetStartIntensity());
+ SkColor endColor = toSkColorWithIntensity(rGradient.GetEndColor(), rGradient.GetEndIntensity());
+
+ sk_sp<SkShader> shader;
+ if (rGradient.GetStyle() == css::awt::GradientStyle_LINEAR)
+ {
+ tools::Polygon aPoly(aBoundRect);
+ aPoly.Rotate(aCenter, aGradient.GetAngle() % 3600_deg10);
+ SkPoint points[2] = { SkPoint::Make(toSkX(aPoly[0].X()), toSkY(aPoly[0].Y())),
+ SkPoint::Make(toSkX(aPoly[1].X()), toSkY(aPoly[1].Y())) };
+ SkColor colors[2] = { startColor, endColor };
+ SkScalar pos[2] = { SkDoubleToScalar(aGradient.GetBorder() / 100.0), 1.0 };
+ shader = SkGradientShader::MakeLinear(points, colors, pos, 2, SkTileMode::kClamp);
+ }
+ else if (rGradient.GetStyle() == css::awt::GradientStyle_AXIAL)
+ {
+ tools::Polygon aPoly(aBoundRect);
+ aPoly.Rotate(aCenter, aGradient.GetAngle() % 3600_deg10);
+ SkPoint points[2] = { SkPoint::Make(toSkX(aPoly[0].X()), toSkY(aPoly[0].Y())),
+ SkPoint::Make(toSkX(aPoly[1].X()), toSkY(aPoly[1].Y())) };
+ SkColor colors[3] = { endColor, startColor, endColor };
+ SkScalar border = SkDoubleToScalar(aGradient.GetBorder() / 100.0);
+ SkScalar pos[3] = { std::min<SkScalar>(border * 0.5f, 0.5f), 0.5f,
+ std::max<SkScalar>(1 - border * 0.5f, 0.5f) };
+ shader = SkGradientShader::MakeLinear(points, colors, pos, 3, SkTileMode::kClamp);
+ }
+ else
+ {
+ // Move the center by (-1,-1) (the default VCL algorithm is a bit off-center that way,
+ // Skia is the opposite way).
+ SkPoint center = SkPoint::Make(toSkX(aCenter.X()) - 1, toSkY(aCenter.Y()) - 1);
+ SkScalar radius = std::max(aBoundRect.GetWidth() / 2.0, aBoundRect.GetHeight() / 2.0);
+ SkColor colors[2] = { endColor, startColor };
+ SkScalar pos[2] = { SkDoubleToScalar(aGradient.GetBorder() / 100.0), 1.0 };
+ shader = SkGradientShader::MakeRadial(center, radius, colors, pos, 2, SkTileMode::kClamp);
+ }
+
+ SkPaint paint = makeGradientPaint();
+ paint.setAntiAlias(mParent.getAntiAlias());
+ paint.setShader(shader);
+ getDrawCanvas()->drawPath(path, paint);
+ postDraw();
+ return true;
+}
+
+bool SkiaSalGraphicsImpl::implDrawGradient(const basegfx::B2DPolyPolygon& rPolyPolygon,
+ const SalGradient& rGradient)
+{
+ preDraw();
+ SAL_INFO("vcl.skia.trace",
+ "impldrawgradient(" << this << "): " << rPolyPolygon << ":" << rGradient.maPoint1
+ << "->" << rGradient.maPoint2 << ":" << rGradient.maStops.size());
+
+ SkPath path;
+ addPolyPolygonToPath(rPolyPolygon, path);
+ path.setFillType(SkPathFillType::kEvenOdd);
+ addUpdateRegion(path.getBounds());
+
+ SkPoint points[2]
+ = { SkPoint::Make(toSkX(rGradient.maPoint1.getX()), toSkY(rGradient.maPoint1.getY())),
+ SkPoint::Make(toSkX(rGradient.maPoint2.getX()), toSkY(rGradient.maPoint2.getY())) };
+ std::vector<SkColor> colors;
+ std::vector<SkScalar> pos;
+ for (const SalGradientStop& stop : rGradient.maStops)
+ {
+ colors.emplace_back(toSkColor(stop.maColor));
+ pos.emplace_back(stop.mfOffset);
+ }
+ sk_sp<SkShader> shader = SkGradientShader::MakeLinear(points, colors.data(), pos.data(),
+ colors.size(), SkTileMode::kDecal);
+ SkPaint paint = makeGradientPaint();
+ paint.setAntiAlias(mParent.getAntiAlias());
+ paint.setShader(shader);
+ getDrawCanvas()->drawPath(path, paint);
+ postDraw();
+ return true;
+}
+
+static double toRadian(Degree10 degree10th) { return toRadians(3600_deg10 - degree10th); }
+static double toCos(Degree10 degree10th) { return SkScalarCos(toRadian(degree10th)); }
+static double toSin(Degree10 degree10th) { return SkScalarSin(toRadian(degree10th)); }
+
+void SkiaSalGraphicsImpl::drawGenericLayout(const GenericSalLayout& layout, Color textColor,
+ const SkFont& font, const SkFont& verticalFont)
+{
+ SkiaZone zone;
+ std::vector<SkGlyphID> glyphIds;
+ std::vector<SkRSXform> glyphForms;
+ std::vector<bool> verticals;
+ glyphIds.reserve(256);
+ glyphForms.reserve(256);
+ verticals.reserve(256);
+ basegfx::B2DPoint aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (layout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ glyphIds.push_back(pGlyph->glyphId());
+ Degree10 angle = layout.GetOrientation();
+ if (pGlyph->IsVertical())
+ angle += 900_deg10;
+ SkRSXform form = SkRSXform::Make(toCos(angle), toSin(angle), aPos.getX(), aPos.getY());
+ glyphForms.emplace_back(std::move(form));
+ verticals.emplace_back(pGlyph->IsVertical());
+ }
+ if (glyphIds.empty())
+ return;
+
+ preDraw();
+ auto getBoundRect = [&layout]() {
+ tools::Rectangle rect;
+ layout.GetBoundRect(rect);
+ return rect;
+ };
+ SAL_INFO("vcl.skia.trace", "drawtextblob(" << this << "): " << getBoundRect() << ", "
+ << glyphIds.size() << " glyphs, " << textColor);
+
+ // Vertical glyphs need a different font, so split drawing into runs that each
+ // draw only consecutive horizontal or vertical glyphs.
+ std::vector<bool>::const_iterator pos = verticals.cbegin();
+ std::vector<bool>::const_iterator end = verticals.cend();
+ while (pos != end)
+ {
+ bool verticalRun = *pos;
+ std::vector<bool>::const_iterator rangeEnd = std::find(pos + 1, end, !verticalRun);
+ size_t index = pos - verticals.cbegin();
+ size_t count = rangeEnd - pos;
+ sk_sp<SkTextBlob> textBlob = SkTextBlob::MakeFromRSXform(
+ glyphIds.data() + index, count * sizeof(SkGlyphID), glyphForms.data() + index,
+ verticalRun ? verticalFont : font, SkTextEncoding::kGlyphID);
+ addUpdateRegion(textBlob->bounds());
+ SkPaint paint = makeTextPaint(textColor);
+ getDrawCanvas()->drawTextBlob(textBlob, 0, 0, paint);
+ pos = rangeEnd;
+ }
+ postDraw();
+}
+
+bool SkiaSalGraphicsImpl::supportsOperation(OutDevSupportType eType) const
+{
+ switch (eType)
+ {
+ case OutDevSupportType::TransparentRect:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int getScaling()
+{
+ // It makes sense to support the debugging flag on all platforms
+ // for unittests purpose, even if the actual windows cannot do it.
+ if (const char* env = getenv("SAL_FORCE_HIDPI_SCALING"))
+ return atoi(env);
+ return 1;
+}
+
+int SkiaSalGraphicsImpl::getWindowScaling() const
+{
+ static const int scaling = getScaling();
+ return scaling;
+}
+
+void SkiaSalGraphicsImpl::dump(const char* file) const
+{
+ assert(mSurface.get());
+ SkiaHelper::dump(mSurface, file);
+}
+
+void SkiaSalGraphicsImpl::windowBackingPropertiesChanged()
+{
+ if (mInWindowBackingPropertiesChanged || !isGPU())
+ return;
+
+ mInWindowBackingPropertiesChanged = true;
+ performFlush();
+ mInWindowBackingPropertiesChanged = false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/skia/osx/bitmap.cxx b/vcl/skia/osx/bitmap.cxx
new file mode 100644
index 0000000000..edb339ce52
--- /dev/null
+++ b/vcl/skia/osx/bitmap.cxx
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * Some of this code is based on Skia source code, covered by the following
+ * license notice (see readlicense_oo for the full license):
+ *
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ */
+
+#include <skia/osx/bitmap.hxx>
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/image.hxx>
+
+#include <skia/salbmp.hxx>
+#include <osx/saldata.hxx>
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkShader.h>
+
+using namespace SkiaHelper;
+
+namespace SkiaHelper
+{
+CGImageRef createCGImage(const Image& rImage)
+{
+ BitmapEx bitmapEx(rImage.GetBitmapEx());
+ Bitmap bitmap(bitmapEx.GetBitmap());
+
+ if (bitmap.IsEmpty() || !bitmap.ImplGetSalBitmap())
+ return nullptr;
+
+ assert(dynamic_cast<SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get()) != nullptr);
+ SkiaSalBitmap* skiaBitmap = static_cast<SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get());
+
+ SkBitmap targetBitmap;
+ if (!targetBitmap.tryAllocPixels(
+ SkImageInfo::Make(bitmap.GetSizePixel().getWidth(), bitmap.GetSizePixel().getHeight(),
+ kRGBA_8888_SkColorType, kPremul_SkAlphaType)))
+ return nullptr;
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is
+ SkMatrix matrix; // The image is needed upside-down.
+ matrix.preTranslate(0, targetBitmap.height());
+ matrix.setConcat(matrix, SkMatrix::Scale(1, -1));
+
+ if (!bitmapEx.IsAlpha())
+ {
+ SkCanvas canvas(targetBitmap);
+ canvas.concat(matrix);
+ canvas.drawImage(skiaBitmap->GetSkImage(), 0, 0, SkSamplingOptions(), &paint);
+ }
+ else
+ {
+ AlphaMask alpha(bitmapEx.GetAlphaMask());
+ // tdf#156854 invert alpha mask for macOS native menu item images
+ // At the time of this change, only the AquaSalMenu class calls this
+ // function so it should be safe to invert the alpha mask here.
+ alpha.Invert();
+ Bitmap alphaBitmap(alpha.GetBitmap());
+ assert(dynamic_cast<SkiaSalBitmap*>(alphaBitmap.ImplGetSalBitmap().get()) != nullptr);
+ SkiaSalBitmap* skiaAlpha
+ = static_cast<SkiaSalBitmap*>(alphaBitmap.ImplGetSalBitmap().get());
+#if 0
+ // Drawing to a bitmap using a shader from a GPU-backed image fails silently.
+ // https://bugs.chromium.org/p/skia/issues/detail?id=12685
+ paint.setShader(SkShaders::Blend(SkBlendMode::kDstOut,
+ skiaBitmap->GetSkShader(SkSamplingOptions()),
+ skiaAlpha->GetAlphaSkShader(SkSamplingOptions())));
+#else
+ sk_sp<SkImage> imB = skiaBitmap->GetSkImage()->makeNonTextureImage();
+ sk_sp<SkImage> imA = skiaAlpha->GetAlphaSkImage()->makeNonTextureImage();
+ paint.setShader(SkShaders::Blend(SkBlendMode::kDstOut, imB->makeShader(SkSamplingOptions()),
+ imA->makeShader(SkSamplingOptions())));
+#endif
+ SkCanvas canvas(targetBitmap);
+ canvas.concat(matrix);
+ canvas.drawPaint(paint);
+ }
+
+ CGContextRef context = CGBitmapContextCreate(
+ targetBitmap.getAddr32(0, 0), targetBitmap.width(), targetBitmap.height(), 8,
+ targetBitmap.rowBytes(), GetSalData()->mxRGBSpace, kCGImageAlphaPremultipliedLast);
+ if (!context)
+ return nullptr;
+ CGImageRef screenImage = CGBitmapContextCreateImage(context);
+ CFRelease(context);
+ return screenImage;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/skia/osx/gdiimpl.cxx b/vcl/skia/osx/gdiimpl.cxx
new file mode 100644
index 0000000000..c4bd751842
--- /dev/null
+++ b/vcl/skia/osx/gdiimpl.cxx
@@ -0,0 +1,384 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * Some of this code is based on Skia source code, covered by the following
+ * license notice (see readlicense_oo for the full license):
+ *
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <skia/osx/gdiimpl.hxx>
+
+#include <skia/utils.hxx>
+#include <skia/zone.hxx>
+
+#include <tools/sk_app/mac/WindowContextFactory_mac.h>
+
+#include <quartz/CoreTextFont.hxx>
+#include <quartz/SystemFontList.hxx>
+#include <skia/quartz/cgutils.h>
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkFont.h>
+#include <SkFontMgr_mac_ct.h>
+#include <SkTypeface_mac.h>
+
+using namespace SkiaHelper;
+
+static void releaseInstalledPixels(void* pAddr, void*)
+{
+ if (pAddr)
+ delete[] static_cast<sal_uInt8*>(pAddr);
+}
+
+AquaSkiaSalGraphicsImpl::AquaSkiaSalGraphicsImpl(AquaSalGraphics& rParent,
+ AquaSharedAttributes& rShared)
+ : SkiaSalGraphicsImpl(rParent, rShared.mpFrame)
+ , AquaGraphicsBackendBase(rShared, this)
+{
+ Init(); // mac code doesn't call Init()
+}
+
+AquaSkiaSalGraphicsImpl::~AquaSkiaSalGraphicsImpl()
+{
+ DeInit(); // mac code doesn't call DeInit()
+}
+
+void AquaSkiaSalGraphicsImpl::freeResources() {}
+
+void AquaSkiaSalGraphicsImpl::createWindowSurfaceInternal(bool forceRaster)
+{
+ assert(!mWindowContext);
+ assert(!mSurface);
+ SkiaZone zone;
+ sk_app::DisplayParams displayParams;
+ displayParams.fColorType = kN32_SkColorType;
+ sk_app::window_context_factory::MacWindowInfo macWindow;
+ macWindow.fMainView = mrShared.mpFrame->mpNSView;
+ mScaling = getWindowScaling();
+ RenderMethod renderMethod = forceRaster ? RenderRaster : renderMethodToUse();
+ switch (renderMethod)
+ {
+ case RenderRaster:
+ // RasterWindowContext_mac uses OpenGL internally, which we don't want,
+ // so use our own surface and do blitting to the screen ourselves.
+ mSurface = createSkSurface(GetWidth() * mScaling, GetHeight() * mScaling);
+ break;
+ case RenderMetal:
+ mWindowContext
+ = sk_app::window_context_factory::MakeMetalForMac(macWindow, displayParams);
+ // Like with other GPU contexts, create a proxy offscreen surface (see
+ // flushSurfaceToWindowContext()). Here it's additionally needed because
+ // it appears that Metal surfaces cannot be read from, which would break things
+ // like copyArea().
+ if (mWindowContext)
+ mSurface = createSkSurface(GetWidth() * mScaling, GetHeight() * mScaling);
+ break;
+ case RenderVulkan:
+ abort();
+ break;
+ }
+}
+
+int AquaSkiaSalGraphicsImpl::getWindowScaling() const
+{
+ // The system function returns float, but only integer multiples realistically make sense.
+ return sal::aqua::getWindowScaling();
+}
+
+void AquaSkiaSalGraphicsImpl::Flush() { performFlush(); }
+
+void AquaSkiaSalGraphicsImpl::Flush(const tools::Rectangle&) { performFlush(); }
+
+void AquaSkiaSalGraphicsImpl::WindowBackingPropertiesChanged() { windowBackingPropertiesChanged(); }
+
+void AquaSkiaSalGraphicsImpl::flushSurfaceToWindowContext()
+{
+ if (!isGPU())
+ flushSurfaceToScreenCG();
+ else
+ SkiaSalGraphicsImpl::flushSurfaceToWindowContext();
+}
+
+// For Raster we use our own screen blitting (see above).
+void AquaSkiaSalGraphicsImpl::flushSurfaceToScreenCG()
+{
+ // Based on AquaGraphicsBackend::drawBitmap().
+ if (!mrShared.checkContext())
+ return;
+
+ assert(mSurface.get());
+ // Do not use sub-rect, it creates copies of the data.
+ sk_sp<SkImage> image = makeCheckedImageSnapshot(mSurface);
+ SkPixmap pixmap;
+ if (!image->peekPixels(&pixmap))
+ abort();
+ // If window scaling, then mDirtyRect is in VCL coordinates, mSurface has screen size (=points,HiDPI),
+ // maContextHolder has screen size but a scale matrix set so its inputs are in VCL coordinates (see
+ // its setup in AquaSharedAttributes::checkContext()).
+ // This creates the bitmap context from the cropped part, writable_addr32() will get
+ // the first pixel of mDirtyRect.topLeft(), and using pixmap.rowBytes() ensures the following
+ // pixel lines will be read from correct positions.
+ if (pixmap.bounds() != mDirtyRect && pixmap.bounds().bottom() == mDirtyRect.bottom())
+ {
+ // HACK for tdf#145843: If mDirtyRect includes the last line but not the first pixel of it,
+ // then the rowBytes() trick would lead to the CG* functions thinking that even pixels after
+ // the pixmap data belong to the area (since the shifted x()+rowBytes() points there) and
+ // at least on Intel Mac they would actually read those data, even though I see no good reason
+ // to do that, as that's beyond the x()+width() for the last line. That could be handled
+ // by creating a subset SkImage (which as is said above copies data), or set the x coordinate
+ // to 0, which will then make rowBytes() match the actual data.
+ mDirtyRect.fLeft = 0;
+ // Related tdf#156630 pixmaps can be wider than the dirty rectangle
+ // This seems to most commonly occur when SAL_FORCE_HIDPI_SCALING=1
+ // and the native window scale is 2.
+ assert(mDirtyRect.width() <= pixmap.bounds().width());
+ }
+
+ // tdf#145843 Do not use CGBitmapContextCreate() to create a bitmap context
+ // As described in the comment in the above code, CGBitmapContextCreate()
+ // and CGBitmapContextCreateWithData() will try to access pixels up to
+ // mDirtyRect.x() + pixmap.bounds.width() for each row. When reading the
+ // last line in the SkPixmap, the buffer allocated for the SkPixmap ends at
+ // mDirtyRect.x() + mDirtyRect.width() and mDirtyRect.width() is clamped to
+ // pixmap.bounds.width() - mDirtyRect.x().
+ // This behavior looks like an optimization within CGBitmapContextCreate()
+ // to draw with a single memcpy() so fix this bug by chaining the
+ // CGDataProvider(), CGImageCreate(), and CGImageCreateWithImageInRect()
+ // functions to create the screen image.
+ CGDataProviderRef dataProvider = CGDataProviderCreateWithData(
+ nullptr, pixmap.writable_addr32(0, 0), pixmap.computeByteSize(), nullptr);
+ if (!dataProvider)
+ {
+ SAL_WARN("vcl.skia", "flushSurfaceToScreenGC(): Failed to allocate data provider");
+ return;
+ }
+
+ CGImageRef fullImage = CGImageCreate(pixmap.bounds().width(), pixmap.bounds().height(), 8,
+ 8 * image->imageInfo().bytesPerPixel(), pixmap.rowBytes(),
+ GetSalData()->mxRGBSpace,
+ SkiaToCGBitmapType(image->colorType(), image->alphaType()),
+ dataProvider, nullptr, false, kCGRenderingIntentDefault);
+ if (!fullImage)
+ {
+ CGDataProviderRelease(dataProvider);
+ SAL_WARN("vcl.skia", "flushSurfaceToScreenGC(): Failed to allocate full image");
+ return;
+ }
+
+ CGImageRef screenImage = CGImageCreateWithImageInRect(
+ fullImage, CGRectMake(mDirtyRect.x() * mScaling, mDirtyRect.y() * mScaling,
+ mDirtyRect.width() * mScaling, mDirtyRect.height() * mScaling));
+ if (!screenImage)
+ {
+ CGImageRelease(fullImage);
+ CGDataProviderRelease(dataProvider);
+ SAL_WARN("vcl.skia", "flushSurfaceToScreenGC(): Failed to allocate screen image");
+ return;
+ }
+
+ mrShared.maContextHolder.saveState();
+ // Drawing to the actual window has scaling active, so use unscaled coordinates, the scaling matrix will scale them
+ // to the proper screen coordinates. Unless the scaling is fake for debugging, in which case scale them to draw
+ // at the scaled size.
+ int windowScaling = 1;
+ static const char* env = getenv("SAL_FORCE_HIDPI_SCALING");
+ if (env != nullptr)
+ windowScaling = atoi(env);
+ CGRect drawRect
+ = CGRectMake(mDirtyRect.x() * windowScaling, mDirtyRect.y() * windowScaling,
+ mDirtyRect.width() * windowScaling, mDirtyRect.height() * windowScaling);
+ if (mrShared.isFlipped())
+ {
+ // I don't understand why, but apparently it's needed to explicitly to flip the drawing, even though maContextHelper
+ // has this set up, so this unsets the flipping.
+ CGFloat invertedY = drawRect.origin.y + drawRect.size.height;
+ CGContextTranslateCTM(mrShared.maContextHolder.get(), 0, invertedY);
+ CGContextScaleCTM(mrShared.maContextHolder.get(), 1, -1);
+ drawRect.origin.y = 0;
+ }
+ CGContextDrawImage(mrShared.maContextHolder.get(), drawRect, screenImage);
+ mrShared.maContextHolder.restoreState();
+
+ CGImageRelease(screenImage);
+ CGImageRelease(fullImage);
+ CGDataProviderRelease(dataProvider);
+
+ // This is also in VCL coordinates.
+ mrShared.refreshRect(mDirtyRect.x(), mDirtyRect.y(), mDirtyRect.width(), mDirtyRect.height());
+}
+
+bool AquaSkiaSalGraphicsImpl::drawNativeControl(ControlType nType, ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ ControlState nState, const ImplControlValue& aValue)
+{
+ // tdf#157613 make sure surface is not a nullptr
+ checkSurface();
+ if (!mSurface)
+ return false;
+
+ // rControlRegion is not the whole area that the control should be painted to (e.g. highlight
+ // around focused lineedit is outside of it). Since we draw to a temporary bitmap, we need tofind out
+ // the real size. Using getNativeControlRegion() might seem like the function to call, but we need
+ // the other direction - what is called rControlRegion here is rNativeContentRegion in that function
+ // what's called rControlRegion there is what we need here. Moreover getNativeControlRegion()
+ // in some cases returns a fixed size that does not depend on its input, so we have no way to
+ // actually find out what the original size was (or maybe the function is kind of broken, I don't know).
+ // So, add a generous margin and hope it's enough.
+ tools::Rectangle boundingRegion(rControlRegion);
+ boundingRegion.expand(50 * mScaling);
+ // Do a scaled bitmap in HiDPI in order not to lose precision.
+ const tools::Long width = boundingRegion.GetWidth() * mScaling;
+ const tools::Long height = boundingRegion.GetHeight() * mScaling;
+ const size_t bytes = width * height * 4;
+ sal_uInt8* data = new sal_uInt8[bytes];
+ memset(data, 0, bytes);
+ CGContextRef context = CGBitmapContextCreate(
+ data, width, height, 8, width * 4, GetSalData()->mxRGBSpace,
+ SkiaToCGBitmapType(mSurface->imageInfo().colorType(), kPremul_SkAlphaType));
+ if (!context)
+ {
+ SAL_WARN("vcl.skia", "drawNativeControl(): Failed to allocate bitmap context");
+ return false;
+ }
+ // Setup context state for drawing (performDrawNativeControl() e.g. fills background in some cases).
+ CGContextSetFillColorSpace(context, GetSalData()->mxRGBSpace);
+ CGContextSetStrokeColorSpace(context, GetSalData()->mxRGBSpace);
+ if (moLineColor)
+ {
+ RGBAColor lineColor(*moLineColor);
+ CGContextSetRGBStrokeColor(context, lineColor.GetRed(), lineColor.GetGreen(),
+ lineColor.GetBlue(), lineColor.GetAlpha());
+ }
+ if (moFillColor)
+ {
+ RGBAColor fillColor(*moFillColor);
+ CGContextSetRGBFillColor(context, fillColor.GetRed(), fillColor.GetGreen(),
+ fillColor.GetBlue(), fillColor.GetAlpha());
+ }
+ // Adjust for our drawn-to coordinates in the bitmap.
+ tools::Rectangle movedRegion(Point(rControlRegion.getX() - boundingRegion.getX(),
+ rControlRegion.getY() - boundingRegion.getY()),
+ rControlRegion.GetSize());
+ // Flip drawing upside down.
+ CGContextTranslateCTM(context, 0, height);
+ CGContextScaleCTM(context, 1, -1);
+ // And possibly scale the native drawing.
+ CGContextScaleCTM(context, mScaling, mScaling);
+ bool bOK = performDrawNativeControl(nType, nPart, movedRegion, nState, aValue, context,
+ mrShared.mpFrame);
+ CGContextRelease(context);
+ if (bOK)
+ {
+ // Let SkBitmap determine when it is safe to delete the pixel buffer
+ SkBitmap bitmap;
+ if (!bitmap.installPixels(SkImageInfo::Make(width, height,
+ mSurface->imageInfo().colorType(),
+ kPremul_SkAlphaType),
+ data, width * 4, releaseInstalledPixels, nullptr))
+ abort();
+
+ // Make bitmap immutable to avoid making a copy in bitmap.asImage()
+ bitmap.setImmutable();
+
+ preDraw();
+ SAL_INFO("vcl.skia.trace", "drawnativecontrol(" << this << "): " << rControlRegion << ":"
+ << int(nType) << "/" << int(nPart));
+ tools::Rectangle updateRect = boundingRegion;
+ // For background update only part that is not clipped, the same
+ // as in AquaGraphicsBackend::drawNativeControl().
+ if (nType == ControlType::WindowBackground)
+ updateRect.Intersection(mClipRegion.GetBoundRect());
+ addUpdateRegion(SkRect::MakeXYWH(updateRect.getX(), updateRect.getY(),
+ updateRect.GetWidth(), updateRect.GetHeight()));
+ SkRect drawRect = SkRect::MakeXYWH(boundingRegion.getX(), boundingRegion.getY(),
+ boundingRegion.GetWidth(), boundingRegion.GetHeight());
+ assert(drawRect.width() * mScaling == bitmap.width()); // no scaling should be needed
+ getDrawCanvas()->drawImageRect(bitmap.asImage(), drawRect, SkSamplingOptions());
+ // Related: tdf#156881 flush the canvas after drawing the pixel buffer
+ getDrawCanvas()->flush();
+ ++pendingOperationsToFlush; // tdf#136369
+ postDraw();
+ }
+ return bOK;
+}
+
+void AquaSkiaSalGraphicsImpl::drawTextLayout(const GenericSalLayout& rLayout)
+{
+ const bool bSubpixelPositioning = rLayout.GetSubpixelPositioning();
+ const CoreTextFont& rFont = *static_cast<const CoreTextFont*>(&rLayout.GetFont());
+ const vcl::font::FontSelectPattern& rFontSelect = rFont.GetFontSelectPattern();
+ int nHeight = rFontSelect.mnHeight;
+ int nWidth = rFontSelect.mnWidth ? rFontSelect.mnWidth : nHeight;
+ if (nWidth == 0 || nHeight == 0)
+ {
+ SAL_WARN("vcl.skia", "DrawTextLayout(): rFontSelect.mnHeight is zero!?");
+ return;
+ }
+
+ if (!fontManager)
+ {
+ std::unique_ptr<SystemFontList> fontList = GetCoretextFontList();
+ if (fontList == nullptr)
+ {
+ SAL_WARN("vcl.skia", "DrawTextLayout(): No coretext font list");
+ fontManager = SkFontMgr_New_CoreText(nullptr);
+ }
+ else
+ {
+ fontManager = SkFontMgr_New_CoreText(fontList->fontCollection());
+ }
+ }
+
+ sk_sp<SkTypeface> typeface = SkMakeTypefaceFromCTFont(rFont.GetCTFont());
+ SkFont font(typeface);
+ font.setSize(nHeight);
+ // font.setScaleX(rFont.mfFontStretch); TODO
+ if (rFont.NeedsArtificialBold())
+ font.setEmbolden(true);
+
+ SkFont::Edging ePreferredAliasing
+ = bSubpixelPositioning ? SkFont::Edging::kSubpixelAntiAlias : SkFont::Edging::kAntiAlias;
+ if (bSubpixelPositioning)
+ {
+ // note that SkFont defaults to a BaselineSnap of true, so I think really only
+ // subpixel in text direction
+ font.setSubpixel(true);
+ }
+ font.setEdging(mrShared.mbNonAntialiasedText ? SkFont::Edging::kAlias : ePreferredAliasing);
+
+ // Vertical font, use width as "height".
+ SkFont verticalFont(font);
+ verticalFont.setSize(nHeight);
+ // verticalFont.setSize(nWidth); TODO
+ // verticalFont.setScaleX(1.0 * nHeight / nWidth);
+
+ drawGenericLayout(rLayout, mrShared.maTextColor, font, verticalFont);
+}
+
+namespace
+{
+std::unique_ptr<sk_app::WindowContext> createMetalWindowContext(bool /*temporary*/)
+{
+ sk_app::DisplayParams displayParams;
+ sk_app::window_context_factory::MacWindowInfo macWindow;
+ macWindow.fMainView = nullptr;
+ return sk_app::window_context_factory::MakeMetalForMac(macWindow, displayParams);
+}
+}
+
+void AquaSkiaSalGraphicsImpl::prepareSkia() { SkiaHelper::prepareSkia(createMetalWindowContext); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/skia/quartz/salbmp.mm b/vcl/skia/quartz/salbmp.mm
new file mode 100644
index 0000000000..4e9782daf1
--- /dev/null
+++ b/vcl/skia/quartz/salbmp.mm
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <skia/salbmp.hxx>
+#include <skia/zone.hxx>
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkPaint.h>
+
+#include <quartz/cgutils.h>
+#ifdef MACOSX
+#include <osx/saldata.hxx>
+#else
+#include <svdata.hxx>
+#endif
+#include <skia/quartz/cgutils.h>
+
+CGImageRef SkiaSalBitmap::CreateWithMask(const SalBitmap& rMask, int nX, int nY, int nWidth,
+ int nHeight) const
+{
+ return CreateWithSalBitmapAndMask( *this, rMask, nX, nY, nWidth, nHeight );
+}
+
+/** creates an image from the given rectangle, replacing all black pixels
+ with nMaskColor and make all other full transparent */
+CGImageRef SkiaSalBitmap::CreateColorMask(int nX, int nY, int nWidth, int nHeight,
+ Color nMaskColor) const
+{
+ SkiaZone zone;
+ SkBitmap targetBitmap;
+ if (!targetBitmap.tryAllocPixels(SkImageInfo::Make(nWidth, nHeight, kN32_SkColorType, kPremul_SkAlphaType)))
+ return nullptr;
+
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is
+ SkMatrix matrix; // The image is needed upside-down.
+ matrix.preTranslate(0, targetBitmap.height());
+ matrix.setConcat(matrix, SkMatrix::Scale(1, -1));
+
+ SkCanvas canvas(targetBitmap);
+ canvas.concat(matrix);
+ canvas.drawImageRect(GetSkImage(), SkRect::MakeXYWH(nX, nY, nWidth, nHeight), SkRect::MakeXYWH(0, 0, nWidth, nHeight), SkSamplingOptions(), &paint, SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint);
+ canvas.flush();
+
+ const SkColor maskColor = SkiaHelper::toSkColor(nMaskColor);
+ for (int y = 0; y < targetBitmap.height(); y++)
+ {
+ SkColor *pPixels = targetBitmap.getAddr32(0, y);
+ for (int x = 0; x < targetBitmap.width(); x++)
+ *pPixels++ = (*pPixels == SK_ColorBLACK ? maskColor : SK_ColorTRANSPARENT);
+ }
+
+ targetBitmap.setImmutable();
+
+ CGContextRef xContext = CGBitmapContextCreate(targetBitmap.getAddr32(0, 0), targetBitmap.width(), targetBitmap.height(), 8, targetBitmap.rowBytes(), GetSalData()->mxRGBSpace, SkiaToCGBitmapType(targetBitmap.colorType(), targetBitmap.alphaType()));
+ if (!xContext)
+ return nullptr;
+
+ CGImageRef xCroppedImage = CGBitmapContextCreateImage(xContext);
+ CGContextRelease(xContext);
+ return xCroppedImage;
+}
+
+
+CGImageRef SkiaSalBitmap::CreateCroppedImage(int nX, int nY, int nWidth, int nHeight) const
+{
+ SkiaZone zone;
+ SkBitmap targetBitmap;
+ if (!targetBitmap.tryAllocPixels(SkImageInfo::Make(nWidth, nHeight, kN32_SkColorType, kPremul_SkAlphaType)))
+ return nullptr;
+
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is
+ SkMatrix matrix; // The image is needed upside-down.
+ matrix.preTranslate(0, targetBitmap.height());
+ matrix.setConcat(matrix, SkMatrix::Scale(1, -1));
+
+ SkCanvas canvas(targetBitmap);
+ canvas.concat(matrix);
+ canvas.drawImageRect(GetSkImage(), SkRect::MakeXYWH(nX, nY, nWidth, nHeight), SkRect::MakeXYWH(0, 0, nWidth, nHeight), SkSamplingOptions(), &paint, SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint);
+ canvas.flush();
+ targetBitmap.setImmutable();
+
+ CGContextRef xContext = CGBitmapContextCreate(targetBitmap.getAddr32(0, 0), targetBitmap.width(), targetBitmap.height(), 8, targetBitmap.rowBytes(), GetSalData()->mxRGBSpace, SkiaToCGBitmapType(targetBitmap.colorType(), targetBitmap.alphaType()));
+ if (!xContext)
+ return nullptr;
+
+ CGImageRef xCroppedImage = CGBitmapContextCreateImage(xContext);
+ CGContextRelease(xContext);
+ return xCroppedImage;
+}
diff --git a/vcl/skia/salbmp.cxx b/vcl/skia/salbmp.cxx
new file mode 100644
index 0000000000..f828338814
--- /dev/null
+++ b/vcl/skia/salbmp.cxx
@@ -0,0 +1,1500 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <skia/salbmp.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <tools/helpers.hxx>
+#include <boost/smart_ptr/make_shared.hpp>
+
+#include <salgdi.hxx>
+#include <salinst.hxx>
+#include <scanlinewriter.hxx>
+#include <svdata.hxx>
+#include <bitmap/bmpfast.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+
+#include <skia/utils.hxx>
+#include <skia/zone.hxx>
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkImage.h>
+#include <SkPixelRef.h>
+#include <SkShader.h>
+#include <SkSurface.h>
+#include <SkSwizzle.h>
+#include <SkColorFilter.h>
+#include <SkColorMatrix.h>
+#include <skia_opts.hxx>
+
+#ifdef DBG_UTIL
+#include <fstream>
+#define CANARY "skia-canary"
+#endif
+
+using namespace SkiaHelper;
+
+// As constexpr here, evaluating it directly in code makes Clang warn about unreachable code.
+constexpr bool kN32_SkColorTypeIsBGRA = (kN32_SkColorType == kBGRA_8888_SkColorType);
+
+SkiaSalBitmap::SkiaSalBitmap() {}
+
+SkiaSalBitmap::~SkiaSalBitmap() {}
+
+SkiaSalBitmap::SkiaSalBitmap(const sk_sp<SkImage>& image)
+{
+ ResetAllData();
+ mImage = image;
+ mPalette = BitmapPalette();
+#if SKIA_USE_BITMAP32
+ mBitCount = 32;
+#else
+ mBitCount = 24;
+#endif
+ mSize = mPixelsSize = Size(image->width(), image->height());
+ ComputeScanlineSize();
+ mReadAccessCount = 0;
+#ifdef DBG_UTIL
+ mWriteAccessCount = 0;
+#endif
+ SAL_INFO("vcl.skia.trace", "bitmapfromimage(" << this << ")");
+}
+
+bool SkiaSalBitmap::Create(const Size& rSize, vcl::PixelFormat ePixelFormat,
+ const BitmapPalette& rPal)
+{
+ assert(mReadAccessCount == 0);
+ ResetAllData();
+ if (ePixelFormat == vcl::PixelFormat::INVALID)
+ return false;
+ mPalette = rPal;
+ mBitCount = vcl::pixelFormatBitCount(ePixelFormat);
+ mSize = rSize;
+ ResetPendingScaling();
+ if (!ComputeScanlineSize())
+ {
+ mBitCount = 0;
+ mSize = mPixelsSize = Size();
+ mScanlineSize = 0;
+ mPalette = BitmapPalette();
+ return false;
+ }
+ SAL_INFO("vcl.skia.trace", "create(" << this << ")");
+ return true;
+}
+
+bool SkiaSalBitmap::ComputeScanlineSize()
+{
+ int bitScanlineWidth;
+ if (o3tl::checked_multiply<int>(mPixelsSize.Width(), mBitCount, bitScanlineWidth))
+ {
+ SAL_WARN("vcl.skia", "checked multiply failed");
+ return false;
+ }
+ mScanlineSize = AlignedWidth4Bytes(bitScanlineWidth);
+ return true;
+}
+
+void SkiaSalBitmap::CreateBitmapData()
+{
+ assert(!mBuffer);
+ // Make sure code has not missed calling ComputeScanlineSize().
+ assert(mScanlineSize == int(AlignedWidth4Bytes(mPixelsSize.Width() * mBitCount)));
+ // The pixels could be stored in SkBitmap, but Skia only supports 8bit gray, 16bit and 32bit formats
+ // (e.g. 24bpp is actually stored as 32bpp). But some of our code accessing the bitmap assumes that
+ // when it asked for 24bpp, the format really will be 24bpp (e.g. the png loader), so we cannot use
+ // SkBitmap to store the data. And even 8bpp is problematic, since Skia does not support palettes
+ // and a VCL bitmap can change its grayscale status simply by changing the palette.
+ // Moreover creating SkImage from SkBitmap does a data copy unless the bitmap is immutable.
+ // So just always store pixels in our buffer and convert as necessary.
+ if (mScanlineSize == 0 || mPixelsSize.Height() == 0)
+ return;
+
+ size_t allocate = mScanlineSize * mPixelsSize.Height();
+#ifdef DBG_UTIL
+ allocate += sizeof(CANARY);
+#endif
+ mBuffer = boost::make_shared_noinit<sal_uInt8[]>(allocate);
+#ifdef DBG_UTIL
+ // fill with random garbage
+ sal_uInt8* buffer = mBuffer.get();
+ for (size_t i = 0; i < allocate; i++)
+ buffer[i] = (i & 0xFF);
+ memcpy(buffer + allocate - sizeof(CANARY), CANARY, sizeof(CANARY));
+#endif
+}
+
+bool SkiaSalBitmap::Create(const SalBitmap& rSalBmp)
+{
+ return Create(rSalBmp, vcl::bitDepthToPixelFormat(rSalBmp.GetBitCount()));
+}
+
+bool SkiaSalBitmap::Create(const SalBitmap& rSalBmp, SalGraphics* pGraphics)
+{
+ auto ePixelFormat = vcl::PixelFormat::INVALID;
+ if (pGraphics)
+ ePixelFormat = vcl::bitDepthToPixelFormat(pGraphics->GetBitCount());
+ else
+ ePixelFormat = vcl::bitDepthToPixelFormat(rSalBmp.GetBitCount());
+
+ return Create(rSalBmp, ePixelFormat);
+}
+
+bool SkiaSalBitmap::Create(const SalBitmap& rSalBmp, vcl::PixelFormat eNewPixelFormat)
+{
+ assert(mReadAccessCount == 0);
+ assert(&rSalBmp != this);
+ ResetAllData();
+ const SkiaSalBitmap& src = static_cast<const SkiaSalBitmap&>(rSalBmp);
+ mImage = src.mImage;
+ mImageImmutable = src.mImageImmutable;
+ mAlphaImage = src.mAlphaImage;
+ mBuffer = src.mBuffer;
+ mPalette = src.mPalette;
+ mBitCount = src.mBitCount;
+ mSize = src.mSize;
+ mPixelsSize = src.mPixelsSize;
+ mScanlineSize = src.mScanlineSize;
+ mScaleQuality = src.mScaleQuality;
+ mEraseColorSet = src.mEraseColorSet;
+ mEraseColor = src.mEraseColor;
+ if (vcl::pixelFormatBitCount(eNewPixelFormat) != src.GetBitCount())
+ {
+ // This appears to be unused(?). Implement this just in case, but be lazy
+ // about it and rely on EnsureBitmapData() doing the conversion from mImage
+ // if needed, even if that may need unnecessary to- and from- SkImage
+ // conversion.
+ ResetToSkImage(GetSkImage());
+ }
+ SAL_INFO("vcl.skia.trace", "create(" << this << "): (" << &src << ")");
+ return true;
+}
+
+bool SkiaSalBitmap::Create(const css::uno::Reference<css::rendering::XBitmapCanvas>&, Size&, bool)
+{
+ return false;
+}
+
+void SkiaSalBitmap::Destroy()
+{
+ SAL_INFO("vcl.skia.trace", "destroy(" << this << ")");
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ assert(mReadAccessCount == 0);
+ ResetAllData();
+}
+
+Size SkiaSalBitmap::GetSize() const { return mSize; }
+
+sal_uInt16 SkiaSalBitmap::GetBitCount() const { return mBitCount; }
+
+BitmapBuffer* SkiaSalBitmap::AcquireBuffer(BitmapAccessMode nMode)
+{
+ switch (nMode)
+ {
+ case BitmapAccessMode::Write:
+ EnsureBitmapUniqueData();
+ if (!mBuffer)
+ return nullptr;
+ assert(mPixelsSize == mSize);
+ assert(!mEraseColorSet);
+ break;
+ case BitmapAccessMode::Read:
+ EnsureBitmapData();
+ if (!mBuffer)
+ return nullptr;
+ assert(mPixelsSize == mSize);
+ assert(!mEraseColorSet);
+ break;
+ case BitmapAccessMode::Info:
+ // Related tdf#156629 and tdf#156630 force snapshot of alpha mask
+ // On macOS, with Skia/Metal or Skia/Raster with a Retina display
+ // (i.e. 2.0 window scale), the alpha mask gets upscaled in certain
+ // cases.
+ // This bug appears to be caused by pending scaling of an existing
+ // SkImage in the bitmap parameter. So, force the SkiaSalBitmap to
+ // handle its pending scaling.
+ // Note: also handle pending scaling if SAL_FORCE_HIDPI_SCALING is
+ // set otherwise exporting the following animated .png image will
+ // fail:
+ // https://bugs.documentfoundation.org/attachment.cgi?id=188792
+ static const bool bForceHiDPIScaling = getenv("SAL_FORCE_HIDPI_SCALING") != nullptr;
+ if (mImage && !mImageImmutable && mBitCount == 8 && mPalette.IsGreyPalette8Bit()
+ && (mPixelsSize != mSize || bForceHiDPIScaling))
+ {
+ ResetToSkImage(GetSkImage());
+ ResetPendingScaling();
+ assert(mPixelsSize == mSize);
+
+ // When many of the images affected by tdf#156629 and
+ // tdf#156630 are exported to PDF the first time after the
+ // image has been opened and before it has been printed or run
+ // in a slideshow, the alpha mask will unexpectedly be
+ // inverted. Fix that by marking this alpha mask as immutable
+ // so that when Invert() is called on this alpha mask, it will
+ // be a noop. Invert() is a noop after EnsureBitmapData() is
+ // called but we don't want to call that due to performance
+ // so set a flag instead.
+ mImageImmutable = true;
+ }
+ break;
+ }
+#ifdef DBG_UTIL
+ // BitmapWriteAccess stores also a copy of the palette and it can
+ // be modified, so concurrent reading of it might result in inconsistencies.
+ assert(mWriteAccessCount == 0 || nMode == BitmapAccessMode::Write);
+#endif
+ BitmapBuffer* buffer = new BitmapBuffer;
+ buffer->mnWidth = mSize.Width();
+ buffer->mnHeight = mSize.Height();
+ buffer->mnBitCount = mBitCount;
+ buffer->maPalette = mPalette;
+ if (nMode != BitmapAccessMode::Info)
+ buffer->mpBits = mBuffer.get();
+ else
+ buffer->mpBits = nullptr;
+ if (mPixelsSize == mSize)
+ buffer->mnScanlineSize = mScanlineSize;
+ else
+ {
+ // The value of mScanlineSize is based on internal mPixelsSize, but the outside
+ // world cares about mSize, the size that the report as the size of the bitmap,
+ // regardless of any internal state. So report scanline size for that size.
+ Size savedPixelsSize = mPixelsSize;
+ mPixelsSize = mSize;
+ ComputeScanlineSize();
+ buffer->mnScanlineSize = mScanlineSize;
+ mPixelsSize = savedPixelsSize;
+ ComputeScanlineSize();
+ }
+ switch (mBitCount)
+ {
+ case 1:
+ buffer->mnFormat = ScanlineFormat::N1BitMsbPal;
+ break;
+ case 8:
+ buffer->mnFormat = ScanlineFormat::N8BitPal;
+ break;
+ case 24:
+ // Make the RGB/BGR format match the default Skia 32bpp format, to allow
+ // easy conversion later.
+ buffer->mnFormat = kN32_SkColorTypeIsBGRA ? ScanlineFormat::N24BitTcBgr
+ : ScanlineFormat::N24BitTcRgb;
+ break;
+ case 32:
+ buffer->mnFormat = kN32_SkColorTypeIsBGRA ? ScanlineFormat::N32BitTcBgra
+ : ScanlineFormat::N32BitTcRgba;
+ break;
+ default:
+ abort();
+ }
+ buffer->mnFormat |= ScanlineFormat::TopDown;
+ // Refcount all read/write accesses, to catch problems with existing accesses while
+ // a bitmap changes, and also to detect when we can free mBuffer if wanted.
+ // Write mode implies also reading. It would be probably a good idea to count even
+ // Info accesses, but VclCanvasBitmap keeps one around pointlessly, causing tdf#150817.
+ if (nMode == BitmapAccessMode::Read || nMode == BitmapAccessMode::Write)
+ ++mReadAccessCount;
+#ifdef DBG_UTIL
+ if (nMode == BitmapAccessMode::Write)
+ ++mWriteAccessCount;
+#endif
+ return buffer;
+}
+
+void SkiaSalBitmap::ReleaseBuffer(BitmapBuffer* pBuffer, BitmapAccessMode nMode)
+{
+ ReleaseBuffer(pBuffer, nMode, false);
+}
+
+void SkiaSalBitmap::ReleaseBuffer(BitmapBuffer* pBuffer, BitmapAccessMode nMode,
+ bool dontChangeToErase)
+{
+ if (nMode == BitmapAccessMode::Write)
+ {
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount > 0);
+ --mWriteAccessCount;
+#endif
+ mPalette = pBuffer->maPalette;
+ ResetToBuffer();
+ DataChanged();
+ }
+ if (nMode == BitmapAccessMode::Read || nMode == BitmapAccessMode::Write)
+ {
+ assert(mReadAccessCount > 0);
+ --mReadAccessCount;
+ }
+ // Are there any more ground movements underneath us ?
+ assert(pBuffer->mnWidth == mSize.Width());
+ assert(pBuffer->mnHeight == mSize.Height());
+ assert(pBuffer->mnBitCount == mBitCount);
+ assert(pBuffer->mpBits == mBuffer.get() || nMode == BitmapAccessMode::Info);
+ verify();
+ delete pBuffer;
+ if (nMode == BitmapAccessMode::Write && !dontChangeToErase)
+ {
+ // This saves memory and is also used by IsFullyOpaqueAsAlpha() to avoid unnecessary
+ // alpha blending.
+ if (IsAllBlack())
+ {
+ SAL_INFO("vcl.skia.trace", "releasebuffer(" << this << "): erasing to black");
+ EraseInternal(COL_BLACK);
+ }
+ }
+}
+
+static bool isAllZero(const sal_uInt8* data, size_t size)
+{ // For performance, check in larger data chunks.
+#ifdef UINT64_MAX
+ const int64_t* d = reinterpret_cast<const int64_t*>(data);
+#else
+ const int32_t* d = reinterpret_cast<const int32_t*>(data);
+#endif
+ constexpr size_t step = sizeof(*d) * 8;
+ for (size_t i = 0; i < size / step; ++i)
+ { // Unrolled loop.
+ if (d[0] != 0)
+ return false;
+ if (d[1] != 0)
+ return false;
+ if (d[2] != 0)
+ return false;
+ if (d[3] != 0)
+ return false;
+ if (d[4] != 0)
+ return false;
+ if (d[5] != 0)
+ return false;
+ if (d[6] != 0)
+ return false;
+ if (d[7] != 0)
+ return false;
+ d += 8;
+ }
+ for (size_t i = size / step * step; i < size; ++i)
+ if (data[i] != 0)
+ return false;
+ return true;
+}
+
+bool SkiaSalBitmap::IsAllBlack() const
+{
+ if (mBitCount % 8 != 0 || (!!mPalette && mPalette[0] != COL_BLACK))
+ return false; // Don't bother.
+ if (mSize.Width() * mBitCount / 8 == mScanlineSize)
+ return isAllZero(mBuffer.get(), mScanlineSize * mSize.Height());
+ for (tools::Long y = 0; y < mSize.Height(); ++y)
+ if (!isAllZero(mBuffer.get() + mScanlineSize * y, mSize.Width() * mBitCount / 8))
+ return false;
+ return true;
+}
+
+bool SkiaSalBitmap::GetSystemData(BitmapSystemData&)
+{
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ return false;
+}
+
+bool SkiaSalBitmap::ScalingSupported() const { return true; }
+
+bool SkiaSalBitmap::Scale(const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag)
+{
+ SkiaZone zone;
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ Size newSize(FRound(mSize.Width() * rScaleX), FRound(mSize.Height() * rScaleY));
+ if (mSize == newSize)
+ return true;
+
+ SAL_INFO("vcl.skia.trace", "scale(" << this << "): " << mSize << "/" << mBitCount << "->"
+ << newSize << ":" << static_cast<int>(nScaleFlag));
+
+ if (mEraseColorSet)
+ { // Simple.
+ mSize = newSize;
+ ResetPendingScaling();
+ EraseInternal(mEraseColor);
+ return true;
+ }
+
+ if (mBitCount < 24 && !mPalette.IsGreyPalette8Bit())
+ {
+ // Scaling can introduce additional colors not present in the original
+ // bitmap (e.g. when smoothing). If the bitmap is indexed (has non-trivial palette),
+ // this would break the bitmap, because the actual scaling is done only somewhen later.
+ // Linear 8bit palette (grey) is ok, since there we use directly the values as colors.
+ SAL_INFO("vcl.skia.trace", "scale(" << this << "): indexed bitmap");
+ return false;
+ }
+ // The idea here is that the actual scaling will be delayed until the result
+ // is actually needed. Usually the scaled bitmap will be drawn somewhere,
+ // so delaying will mean the scaling can be done as a part of GetSkImage().
+ // That means it can be GPU-accelerated, while done here directly it would need
+ // to be either done by CPU, or with the CPU->GPU->CPU roundtrip required
+ // by GPU-accelerated scaling.
+ // Pending scaling is detected by 'mSize != mPixelsSize' for mBuffer,
+ // and 'imageSize(mImage) != mSize' for mImage. It is not intended to have 3 different
+ // sizes though, code below keeps only mBuffer or mImage. Note that imageSize(mImage)
+ // may or may not be equal to mPixelsSize, depending on whether mImage is set here
+ // (sizes will be equal) or whether it's set in GetSkImage() (will not be equal).
+ // Pending scaling is considered "done" by the time mBuffer is resized (or created).
+ // Resizing of mImage is somewhat independent of this, since mImage is primarily
+ // considered to be a cached object (although sometimes it's the only data available).
+
+ // If there is already one scale() pending, use the lowest quality of all requested.
+ switch (nScaleFlag)
+ {
+ case BmpScaleFlag::Fast:
+ mScaleQuality = nScaleFlag;
+ break;
+ case BmpScaleFlag::NearestNeighbor:
+ // We handle this the same way as Fast by mapping to Skia's nearest-neighbor,
+ // and it's needed for unittests (mScaling and testTdf132367()).
+ mScaleQuality = nScaleFlag;
+ break;
+ case BmpScaleFlag::Default:
+ if (mScaleQuality == BmpScaleFlag::BestQuality)
+ mScaleQuality = nScaleFlag;
+ break;
+ case BmpScaleFlag::BestQuality:
+ // Best is the maximum, set by default.
+ break;
+ default:
+ SAL_INFO("vcl.skia.trace", "scale(" << this << "): unsupported scale algorithm");
+ return false;
+ }
+ mSize = newSize;
+ // If we have both mBuffer and mImage, prefer mImage, since it likely will be drawn later.
+ // We could possibly try to keep the buffer as well, but that would complicate things
+ // with two different data structures to be scaled on-demand, and it's a question
+ // if that'd realistically help with anything.
+ if (mImage)
+ ResetToSkImage(mImage);
+ else
+ ResetToBuffer();
+ DataChanged();
+ // The rest will be handled when the scaled bitmap is actually needed,
+ // such as in EnsureBitmapData() or GetSkImage().
+ return true;
+}
+
+bool SkiaSalBitmap::Replace(const Color&, const Color&, sal_uInt8)
+{
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ return false;
+}
+
+bool SkiaSalBitmap::ConvertToGreyscale()
+{
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ // Normally this would need to convert contents of mBuffer for all possible formats,
+ // so just let the VCL algorithm do it.
+ // Avoid the costly SkImage->buffer->SkImage conversion.
+ if (!mBuffer && mImage && !mEraseColorSet)
+ {
+ if (mBitCount == 8 && mPalette.IsGreyPalette8Bit())
+ return true;
+ sk_sp<SkSurface> surface
+ = createSkSurface(imageSize(mImage), mImage->imageInfo().alphaType());
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
+ // VCL uses different coefficients for conversion to gray than Skia, so use the VCL
+ // values from Bitmap::ImplMakeGreyscales(). Do not use kGray_8_SkColorType,
+ // Skia would use its gray conversion formula.
+ // NOTE: The matrix is 4x5 organized as columns (i.e. each line is a column, not a row).
+ constexpr SkColorMatrix toGray(77 / 256.0, 151 / 256.0, 28 / 256.0, 0, 0, // R column
+ 77 / 256.0, 151 / 256.0, 28 / 256.0, 0, 0, // G column
+ 77 / 256.0, 151 / 256.0, 28 / 256.0, 0, 0, // B column
+ 0, 0, 0, 1, 0); // don't modify alpha
+ paint.setColorFilter(SkColorFilters::Matrix(toGray));
+ surface->getCanvas()->drawImage(mImage, 0, 0, SkSamplingOptions(), &paint);
+ mBitCount = 8;
+ ComputeScanlineSize();
+ mPalette = Bitmap::GetGreyPalette(256);
+ ResetToSkImage(makeCheckedImageSnapshot(surface));
+ DataChanged();
+ SAL_INFO("vcl.skia.trace", "converttogreyscale(" << this << ")");
+ return true;
+ }
+ return false;
+}
+
+bool SkiaSalBitmap::InterpretAs8Bit()
+{
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ if (mBitCount == 8 && mPalette.IsGreyPalette8Bit())
+ return true;
+ if (mEraseColorSet)
+ {
+ mBitCount = 8;
+ ComputeScanlineSize();
+ mPalette = Bitmap::GetGreyPalette(256);
+ EraseInternal(mEraseColor);
+ SAL_INFO("vcl.skia.trace", "interpretas8bit(" << this << ") with erase color");
+ return true;
+ }
+ // This is usually used by AlphaMask, the point is just to treat
+ // the content as an alpha channel. This is often used
+ // by the horrible separate-alpha-outdev hack, where the bitmap comes
+ // from SkiaSalGraphicsImpl::GetBitmap(), so only mImage is set,
+ // and that is followed by a later call to GetAlphaSkImage().
+ // Avoid the costly SkImage->buffer->SkImage conversion and simply
+ // just treat the SkImage as being for 8bit bitmap. EnsureBitmapData()
+ // will do the conversion if needed, but the normal case will be
+ // GetAlphaSkImage() creating kAlpha_8_SkColorType SkImage from it.
+ if (mImage)
+ {
+ mBitCount = 8;
+ ComputeScanlineSize();
+ mPalette = Bitmap::GetGreyPalette(256);
+ ResetToSkImage(mImage); // keep mImage, it will be interpreted as 8bit if needed
+ DataChanged();
+ SAL_INFO("vcl.skia.trace", "interpretas8bit(" << this << ") with image");
+ return true;
+ }
+ SAL_INFO("vcl.skia.trace", "interpretas8bit(" << this << ") with pixel data, ignoring");
+ return false;
+}
+
+bool SkiaSalBitmap::Erase(const Color& color)
+{
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ // Optimized variant, just remember the color and apply it when needed,
+ // which may save having to do format conversions (e.g. GetSkImage()
+ // may directly erase the SkImage).
+ EraseInternal(color);
+ SAL_INFO("vcl.skia.trace", "erase(" << this << ")");
+ return true;
+}
+
+void SkiaSalBitmap::EraseInternal(const Color& color)
+{
+ ResetAllData();
+ mEraseColorSet = true;
+ mEraseColor = color;
+}
+
+bool SkiaSalBitmap::AlphaBlendWith(const SalBitmap& rSalBmp)
+{
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ const SkiaSalBitmap* otherBitmap = dynamic_cast<const SkiaSalBitmap*>(&rSalBmp);
+ if (!otherBitmap)
+ return false;
+ if (mSize != otherBitmap->mSize)
+ return false;
+ // We're called from AlphaMask, which should ensure 8bit.
+ assert(GetBitCount() == 8 && mPalette.IsGreyPalette8Bit());
+ // If neither bitmap have Skia images, then AlphaMask::BlendWith() will be faster,
+ // as it will operate on mBuffer pixel buffers, while for Skia we'd need to convert it.
+ // If one has and one doesn't, do it using Skia, under the assumption that after this
+ // the resulting Skia image will be needed for drawing.
+ if (!(mImage || mEraseColorSet) && !(otherBitmap->mImage || otherBitmap->mEraseColorSet))
+ return false;
+ // This is for AlphaMask, which actually stores the alpha as the pixel values.
+ // I.e. take value of the color channel (one of them, if >8bit, they should be the same).
+ if (mEraseColorSet && otherBitmap->mEraseColorSet)
+ {
+ const sal_uInt16 nGrey1 = mEraseColor.GetRed();
+ const sal_uInt16 nGrey2 = otherBitmap->mEraseColor.GetRed();
+ // See comment in AlphaMask::BlendWith for how this calculation was derived
+ const sal_uInt8 nGrey = static_cast<sal_uInt8>(nGrey1 * nGrey2 / 255);
+ mEraseColor = Color(nGrey, nGrey, nGrey);
+ DataChanged();
+ SAL_INFO("vcl.skia.trace",
+ "alphablendwith(" << this << ") : with erase color " << otherBitmap);
+ return true;
+ }
+ std::unique_ptr<SkiaSalBitmap> otherBitmapAllocated;
+ if (otherBitmap->GetBitCount() != 8 || !otherBitmap->mPalette.IsGreyPalette8Bit())
+ { // Convert/interpret as 8bit if needed.
+ otherBitmapAllocated = std::make_unique<SkiaSalBitmap>();
+ if (!otherBitmapAllocated->Create(*otherBitmap) || !otherBitmapAllocated->InterpretAs8Bit())
+ return false;
+ otherBitmap = otherBitmapAllocated.get();
+ }
+ // This is 8-bit bitmap serving as mask, so the image itself needs no alpha.
+ sk_sp<SkSurface> surface = createSkSurface(mSize, kOpaque_SkAlphaType);
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is
+ surface->getCanvas()->drawImage(GetSkImage(), 0, 0, SkSamplingOptions(), &paint);
+ // in the 0..1 range that skia uses, the equation we want is:
+ // r = 1 - ((1 - src) + (1 - dest) - (1 - src) * (1 - dest))
+ // which simplifies to:
+ // r = src * dest
+ // which is SkBlendMode::kModulate
+ paint.setBlendMode(SkBlendMode::kModulate);
+ surface->getCanvas()->drawImage(otherBitmap->GetSkImage(), 0, 0, SkSamplingOptions(), &paint);
+ ResetToSkImage(makeCheckedImageSnapshot(surface));
+ DataChanged();
+ SAL_INFO("vcl.skia.trace", "alphablendwith(" << this << ") : with image " << otherBitmap);
+ return true;
+}
+
+bool SkiaSalBitmap::Invert()
+{
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ // Normally this would need to convert contents of mBuffer for all possible formats,
+ // so just let the VCL algorithm do it.
+ // Avoid the costly SkImage->buffer->SkImage conversion.
+ if (!mBuffer && mImage && !mImageImmutable && !mEraseColorSet)
+ {
+ // This is 8-bit bitmap serving as alpha/transparency/mask, so the image itself needs no alpha.
+ // tdf#156866 use mSize instead of mPixelSize for inverted surface
+ // Commit 5baac4e53128d3c0fc73b9918dc9a9c2777ace08 switched to setting
+ // the surface size to mPixelsSize in an attempt to avoid downscaling
+ // mImage but since it causes tdf#156866, revert back to setting the
+ // surface size to mSize.
+ sk_sp<SkSurface> surface = createSkSurface(mSize, kOpaque_SkAlphaType);
+ surface->getCanvas()->clear(SK_ColorWHITE);
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kDifference);
+ // Drawing the image does not work so create a shader from the image
+ paint.setShader(GetSkShader(SkSamplingOptions()));
+ surface->getCanvas()->drawRect(SkRect::MakeXYWH(0, 0, mSize.Width(), mSize.Height()),
+ paint);
+ ResetToSkImage(makeCheckedImageSnapshot(surface));
+ DataChanged();
+
+#ifdef MACOSX
+ // tdf#158014 make image immutable after using Skia to invert
+ // I can't explain why inverting using Skia causes this bug on
+ // macOS but not other platforms. My guess is that Skia on macOS
+ // is sharing some data when different SkiaSalBitmap instances
+ // are created from the same OutputDevice. So, mark this
+ // SkiaSalBitmap instance's image as immutable so that successive
+ // inversions are done with buffered bitmap data instead of Skia.
+ mImageImmutable = true;
+#endif
+
+ SAL_INFO("vcl.skia.trace", "invert(" << this << ")");
+ return true;
+ }
+ return false;
+}
+
+SkBitmap SkiaSalBitmap::GetAsSkBitmap() const
+{
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ EnsureBitmapData();
+ assert(mSize == mPixelsSize); // data has already been scaled if needed
+ SkiaZone zone;
+ SkBitmap bitmap;
+ if (mBuffer)
+ {
+ if (mBitCount == 32)
+ {
+ // Make a copy, the bitmap should be immutable (otherwise converting it
+ // to SkImage will make a copy anyway).
+ const size_t bytes = mPixelsSize.Height() * mScanlineSize;
+ std::unique_ptr<sal_uInt8[]> data(new sal_uInt8[bytes]);
+ memcpy(data.get(), mBuffer.get(), bytes);
+ if (!bitmap.installPixels(
+ SkImageInfo::MakeS32(mPixelsSize.Width(), mPixelsSize.Height(), alphaType()),
+ data.release(), mScanlineSize,
+ [](void* addr, void*) { delete[] static_cast<sal_uInt8*>(addr); }, nullptr))
+ abort();
+ }
+ else if (mBitCount == 24)
+ {
+ // Convert 24bpp RGB/BGR to 32bpp RGBA/BGRA.
+ std::unique_ptr<uint32_t[]> data(
+ new uint32_t[mPixelsSize.Height() * mPixelsSize.Width()]);
+ uint32_t* dest = data.get();
+ // SkConvertRGBToRGBA() also works as BGR to BGRA (the function extends 3 bytes to 4
+ // by adding 0xFF alpha, so position of B and R doesn't matter).
+ if (mPixelsSize.Width() * 3 == mScanlineSize)
+ SkConvertRGBToRGBA(dest, mBuffer.get(), mPixelsSize.Height() * mPixelsSize.Width());
+ else
+ {
+ for (tools::Long y = 0; y < mPixelsSize.Height(); ++y)
+ {
+ const sal_uInt8* src = mBuffer.get() + mScanlineSize * y;
+ SkConvertRGBToRGBA(dest, src, mPixelsSize.Width());
+ dest += mPixelsSize.Width();
+ }
+ }
+ if (!bitmap.installPixels(
+ SkImageInfo::MakeS32(mPixelsSize.Width(), mPixelsSize.Height(),
+ kOpaque_SkAlphaType),
+ data.release(), mPixelsSize.Width() * 4,
+ [](void* addr, void*) { delete[] static_cast<sal_uInt8*>(addr); }, nullptr))
+ abort();
+ }
+ else if (mBitCount == 8 && mPalette.IsGreyPalette8Bit())
+ {
+ // Convert 8bpp gray to 32bpp RGBA/BGRA.
+ // There's also kGray_8_SkColorType, but it's probably simpler to make
+ // GetAsSkBitmap() always return 32bpp SkBitmap and then assume mImage
+ // is always 32bpp too.
+ std::unique_ptr<uint32_t[]> data(
+ new uint32_t[mPixelsSize.Height() * mPixelsSize.Width()]);
+ uint32_t* dest = data.get();
+ if (mPixelsSize.Width() * 1 == mScanlineSize)
+ SkConvertGrayToRGBA(dest, mBuffer.get(),
+ mPixelsSize.Height() * mPixelsSize.Width());
+ else
+ {
+ for (tools::Long y = 0; y < mPixelsSize.Height(); ++y)
+ {
+ const sal_uInt8* src = mBuffer.get() + mScanlineSize * y;
+ SkConvertGrayToRGBA(dest, src, mPixelsSize.Width());
+ dest += mPixelsSize.Width();
+ }
+ }
+ if (!bitmap.installPixels(
+ SkImageInfo::MakeS32(mPixelsSize.Width(), mPixelsSize.Height(),
+ kOpaque_SkAlphaType),
+ data.release(), mPixelsSize.Width() * 4,
+ [](void* addr, void*) { delete[] static_cast<sal_uInt8*>(addr); }, nullptr))
+ abort();
+ }
+ else
+ {
+ std::unique_ptr<sal_uInt8[]> data = convertDataBitCount(
+ mBuffer.get(), mPixelsSize.Width(), mPixelsSize.Height(), mBitCount, mScanlineSize,
+ mPalette, kN32_SkColorTypeIsBGRA ? BitConvert::BGRA : BitConvert::RGBA);
+ if (!bitmap.installPixels(
+ SkImageInfo::MakeS32(mPixelsSize.Width(), mPixelsSize.Height(),
+ kOpaque_SkAlphaType),
+ data.release(), mPixelsSize.Width() * 4,
+ [](void* addr, void*) { delete[] static_cast<sal_uInt8*>(addr); }, nullptr))
+ abort();
+ }
+ }
+ bitmap.setImmutable();
+ return bitmap;
+}
+
+// If mEraseColor is set, this is the color to use when the bitmap is used as alpha bitmap.
+// E.g. COL_BLACK actually means fully transparent and COL_WHITE means fully opaque.
+// This is because the alpha value is set as the color itself, not the alpha of the color.
+static SkColor fromEraseColorToAlphaImageColor(Color color)
+{
+ return SkColorSetARGB(color.GetBlue(), 0, 0, 0);
+}
+
+// SkiaSalBitmap can store data in both the SkImage and our mBuffer, which with large
+// images can waste quite a lot of memory. Ideally we should store the data in Skia's
+// SkBitmap, but LO wants us to support data formats that Skia doesn't support.
+// So try to conserve memory by keeping the data only once in that was the most
+// recently wanted storage, and drop the other one. Usually the other one won't be needed
+// for a long time, and especially with raster the conversion is usually fast.
+// Do this only with raster, to avoid GPU->CPU transfer in GPU mode (exception is 32bit
+// builds, where memory is more important). Also don't do this with paletted bitmaps,
+// where EnsureBitmapData() would be expensive.
+// Ideally SalBitmap should be able to say which bitmap formats it supports
+// and VCL code should oblige, which would allow reusing the same data.
+bool SkiaSalBitmap::ConserveMemory() const
+{
+ static bool keepBitmapBuffer = getenv("SAL_SKIA_KEEP_BITMAP_BUFFER") != nullptr;
+ constexpr bool is32Bit = sizeof(void*) == 4;
+ // 16MiB bitmap data at least (set to 0 for easy testing).
+ constexpr tools::Long maxBufferSize = 2000 * 2000 * 4;
+ return !keepBitmapBuffer && (renderMethodToUse() == RenderRaster || is32Bit)
+ && mPixelsSize.Height() * mScanlineSize > maxBufferSize
+ && (mBitCount > 8 || (mBitCount == 8 && mPalette.IsGreyPalette8Bit()));
+}
+
+const sk_sp<SkImage>& SkiaSalBitmap::GetSkImage(DirectImage direct) const
+{
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ if (direct == DirectImage::Yes)
+ return mImage;
+ if (mEraseColorSet)
+ {
+ if (mImage)
+ {
+ assert(imageSize(mImage) == mSize);
+ return mImage;
+ }
+ SkiaZone zone;
+ sk_sp<SkSurface> surface = createSkSurface(
+ mSize, mEraseColor.IsTransparent() ? kPremul_SkAlphaType : kOpaque_SkAlphaType);
+ assert(surface);
+ surface->getCanvas()->clear(toSkColor(mEraseColor));
+ SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this);
+ thisPtr->mImage = makeCheckedImageSnapshot(surface);
+ SAL_INFO("vcl.skia.trace", "getskimage(" << this << ") from erase color " << mEraseColor);
+ return mImage;
+ }
+ if (mPixelsSize != mSize && !mImage && renderMethodToUse() != RenderRaster)
+ {
+ // The bitmap has a pending scaling, but no image. This function would below call GetAsSkBitmap(),
+ // which would do CPU-based pixel scaling, and then it would get converted to an image.
+ // Be more efficient, first convert to an image and then the block below will scale on the GPU.
+ SAL_INFO("vcl.skia.trace", "getskimage(" << this << "): shortcut image scaling "
+ << mPixelsSize << "->" << mSize);
+ SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this);
+ Size savedSize = mSize;
+ thisPtr->mSize = mPixelsSize; // block scaling
+ SkiaZone zone;
+ sk_sp<SkImage> image = createSkImage(GetAsSkBitmap());
+ assert(image);
+ thisPtr->mSize = savedSize;
+ thisPtr->ResetToSkImage(image);
+ }
+ if (mImage)
+ {
+ if (imageSize(mImage) != mSize)
+ {
+ assert(!mBuffer); // This code should be only called if only mImage holds data.
+ SkiaZone zone;
+ sk_sp<SkSurface> surface = createSkSurface(mSize, mImage->imageInfo().alphaType());
+ assert(surface);
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
+ surface->getCanvas()->drawImageRect(
+ mImage, SkRect::MakeWH(mSize.Width(), mSize.Height()),
+ makeSamplingOptions(mScaleQuality, imageSize(mImage), mSize, 1), &paint);
+ SAL_INFO("vcl.skia.trace", "getskimage(" << this << "): image scaled "
+ << Size(mImage->width(), mImage->height())
+ << "->" << mSize << ":"
+ << static_cast<int>(mScaleQuality));
+ SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this);
+ thisPtr->mImage = makeCheckedImageSnapshot(surface);
+ }
+ return mImage;
+ }
+ SkiaZone zone;
+ sk_sp<SkImage> image = createSkImage(GetAsSkBitmap());
+ assert(image);
+ SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this);
+ thisPtr->mImage = image;
+ // The data is now stored both in the SkImage and in our mBuffer, so drop the buffer
+ // if conserving memory. It'll be converted back by EnsureBitmapData() if needed.
+ if (ConserveMemory() && mReadAccessCount == 0)
+ {
+ SAL_INFO("vcl.skia.trace", "getskimage(" << this << "): dropping buffer");
+ thisPtr->ResetToSkImage(mImage);
+ }
+ SAL_INFO("vcl.skia.trace", "getskimage(" << this << ")");
+ return mImage;
+}
+
+const sk_sp<SkImage>& SkiaSalBitmap::GetAlphaSkImage(DirectImage direct) const
+{
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ if (direct == DirectImage::Yes)
+ return mAlphaImage;
+ if (mEraseColorSet)
+ {
+ if (mAlphaImage)
+ {
+ assert(imageSize(mAlphaImage) == mSize);
+ return mAlphaImage;
+ }
+ SkiaZone zone;
+ sk_sp<SkSurface> surface = createSkSurface(mSize, kAlpha_8_SkColorType);
+ assert(surface);
+ surface->getCanvas()->clear(fromEraseColorToAlphaImageColor(mEraseColor));
+ SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this);
+ thisPtr->mAlphaImage = makeCheckedImageSnapshot(surface);
+ SAL_INFO("vcl.skia.trace",
+ "getalphaskimage(" << this << ") from erase color " << mEraseColor);
+ return mAlphaImage;
+ }
+ if (mAlphaImage)
+ {
+ if (imageSize(mAlphaImage) == mSize)
+ return mAlphaImage;
+ }
+ if (mImage)
+ {
+ SkiaZone zone;
+ const bool scaling = imageSize(mImage) != mSize;
+ SkPixmap pixmap;
+ if (mImage->peekPixels(&pixmap))
+ {
+ assert(pixmap.colorType() == kN32_SkColorType);
+ // In non-GPU mode, convert 32bit data to 8bit alpha, this is faster than
+ // the SkColorFilter below. Since this is the VCL alpha-vdev alpha, where
+ // all R,G,B are the same and in fact mean alpha, this means we simply take one
+ // 8bit channel from the input, and that's the output.
+ SkBitmap bitmap;
+ if (!bitmap.installPixels(pixmap))
+ abort();
+ SkBitmap alphaBitmap;
+ if (!alphaBitmap.tryAllocPixels(SkImageInfo::MakeA8(bitmap.width(), bitmap.height())))
+ abort();
+ if (int(bitmap.rowBytes()) == bitmap.width() * 4)
+ {
+ SkConvertRGBAToR(alphaBitmap.getAddr8(0, 0), bitmap.getAddr32(0, 0),
+ bitmap.width() * bitmap.height());
+ }
+ else
+ {
+ for (tools::Long y = 0; y < bitmap.height(); ++y)
+ SkConvertRGBAToR(alphaBitmap.getAddr8(0, y), bitmap.getAddr32(0, y),
+ bitmap.width());
+ }
+ alphaBitmap.setImmutable();
+ sk_sp<SkImage> alphaImage = createSkImage(alphaBitmap);
+ assert(alphaImage);
+ SAL_INFO("vcl.skia.trace", "getalphaskimage(" << this << ") from raster image");
+ // Don't bother here with ConserveMemory(), mImage -> mAlphaImage conversions should
+ // generally only happen with the separate-alpha-outdev hack, and those bitmaps should
+ // be temporary.
+ SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this);
+ thisPtr->mAlphaImage = alphaImage;
+ // Fix testDelayedScaleAlphaImage unit test
+ // Do not return the alpha mask if it is awaiting pending scaling.
+ // Pending scaling has not yet been done at this point since the
+ // scaling is done in the code following this block.
+ if (!scaling)
+ return mAlphaImage;
+ }
+ // Move the R channel value to the alpha channel. This seems to be the only
+ // way to reinterpret data in SkImage as an alpha SkImage without accessing the pixels.
+ // NOTE: The matrix is 4x5 organized as columns (i.e. each line is a column, not a row).
+ constexpr SkColorMatrix redToAlpha(0, 0, 0, 0, 0, // R column
+ 0, 0, 0, 0, 0, // G column
+ 0, 0, 0, 0, 0, // B column
+ 1, 0, 0, 0, 0); // A column
+ SkPaint paint;
+ paint.setColorFilter(SkColorFilters::Matrix(redToAlpha));
+ if (scaling)
+ assert(!mBuffer); // This code should be only called if only mImage holds data.
+ sk_sp<SkSurface> surface = createSkSurface(mSize, kAlpha_8_SkColorType);
+ assert(surface);
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
+ surface->getCanvas()->drawImageRect(
+ mImage, SkRect::MakeWH(mSize.Width(), mSize.Height()),
+ scaling ? makeSamplingOptions(mScaleQuality, imageSize(mImage), mSize, 1)
+ : SkSamplingOptions(),
+ &paint);
+ if (scaling)
+ SAL_INFO("vcl.skia.trace", "getalphaskimage(" << this << "): image scaled "
+ << Size(mImage->width(), mImage->height())
+ << "->" << mSize << ":"
+ << static_cast<int>(mScaleQuality));
+ else
+ SAL_INFO("vcl.skia.trace", "getalphaskimage(" << this << ") from image");
+ // Don't bother here with ConserveMemory(), mImage -> mAlphaImage conversions should
+ // generally only happen with the separate-alpha-outdev hack, and those bitmaps should
+ // be temporary.
+ SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this);
+ thisPtr->mAlphaImage = makeCheckedImageSnapshot(surface);
+ return mAlphaImage;
+ }
+ SkiaZone zone;
+ EnsureBitmapData();
+ assert(mSize == mPixelsSize); // data has already been scaled if needed
+ SkBitmap alphaBitmap;
+ if (mBuffer && mBitCount <= 8)
+ {
+ assert(mBuffer.get());
+ verify();
+ std::unique_ptr<sal_uInt8[]> data
+ = convertDataBitCount(mBuffer.get(), mSize.Width(), mSize.Height(), mBitCount,
+ mScanlineSize, mPalette, BitConvert::A8);
+ if (!alphaBitmap.installPixels(
+ SkImageInfo::MakeA8(mSize.Width(), mSize.Height()), data.release(), mSize.Width(),
+ [](void* addr, void*) { delete[] static_cast<sal_uInt8*>(addr); }, nullptr))
+ abort();
+ alphaBitmap.setImmutable();
+ sk_sp<SkImage> image = createSkImage(alphaBitmap);
+ assert(image);
+ const_cast<sk_sp<SkImage>&>(mAlphaImage) = image;
+ }
+ else
+ {
+ sk_sp<SkSurface> surface = createSkSurface(mSize, kAlpha_8_SkColorType);
+ assert(surface);
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
+ // Move the R channel value to the alpha channel. This seems to be the only
+ // way to reinterpret data in SkImage as an alpha SkImage without accessing the pixels.
+ // NOTE: The matrix is 4x5 organized as columns (i.e. each line is a column, not a row).
+ constexpr SkColorMatrix redToAlpha(0, 0, 0, 0, 0, // R column
+ 0, 0, 0, 0, 0, // G column
+ 0, 0, 0, 0, 0, // B column
+ 1, 0, 0, 0, 0); // A column
+ paint.setColorFilter(SkColorFilters::Matrix(redToAlpha));
+ surface->getCanvas()->drawImage(GetAsSkBitmap().asImage(), 0, 0, SkSamplingOptions(),
+ &paint);
+ SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this);
+ thisPtr->mAlphaImage = makeCheckedImageSnapshot(surface);
+ }
+ // The data is now stored both in the SkImage and in our mBuffer, so drop the buffer
+ // if conserving memory and the conversion back would be simple (it'll be converted back
+ // by EnsureBitmapData() if needed).
+ if (ConserveMemory() && mBitCount == 8 && mPalette.IsGreyPalette8Bit() && mReadAccessCount == 0)
+ {
+ SAL_INFO("vcl.skia.trace", "getalphaskimage(" << this << "): dropping buffer");
+ SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this);
+ thisPtr->mBuffer.reset();
+ }
+ SAL_INFO("vcl.skia.trace", "getalphaskimage(" << this << ")");
+ return mAlphaImage;
+}
+
+void SkiaSalBitmap::TryDirectConvertToAlphaNoScaling()
+{
+ // This is a bit of a hack. Because of the VCL alpha hack where alpha is stored
+ // separately, we often convert mImage to mAlphaImage to represent the alpha
+ // channel. If code finds out that there is mImage but no mAlphaImage,
+ // this will create it from it, without checking for delayed scaling (i.e.
+ // it is "direct").
+ assert(mImage);
+ assert(!mAlphaImage);
+ // Set wanted size, trigger conversion.
+ Size savedSize = mSize;
+ mSize = imageSize(mImage);
+ GetAlphaSkImage();
+ assert(mAlphaImage);
+ mSize = savedSize;
+}
+
+// If the bitmap is to be erased, SkShader with the color set is more efficient
+// than creating an image filled with the color.
+bool SkiaSalBitmap::PreferSkShader() const { return mEraseColorSet; }
+
+sk_sp<SkShader> SkiaSalBitmap::GetSkShader(const SkSamplingOptions& samplingOptions,
+ DirectImage direct) const
+{
+ if (mEraseColorSet)
+ return SkShaders::Color(toSkColor(mEraseColor));
+ return GetSkImage(direct)->makeShader(samplingOptions);
+}
+
+sk_sp<SkShader> SkiaSalBitmap::GetAlphaSkShader(const SkSamplingOptions& samplingOptions,
+ DirectImage direct) const
+{
+ if (mEraseColorSet)
+ return SkShaders::Color(fromEraseColorToAlphaImageColor(mEraseColor));
+ return GetAlphaSkImage(direct)->makeShader(samplingOptions);
+}
+
+bool SkiaSalBitmap::IsFullyOpaqueAsAlpha() const
+{
+ if (!mEraseColorSet) // Set from Erase() or ReleaseBuffer().
+ return false;
+ // If the erase color is set so that this bitmap used as alpha would
+ // mean a fully opaque alpha mask (= noop), we can skip using it.
+ return SkColorGetA(fromEraseColorToAlphaImageColor(mEraseColor)) == 255;
+}
+
+SkAlphaType SkiaSalBitmap::alphaType() const
+{
+ if (mEraseColorSet)
+ return mEraseColor.IsTransparent() ? kPremul_SkAlphaType : kOpaque_SkAlphaType;
+#if SKIA_USE_BITMAP32
+ // The bitmap's alpha matters only if SKIA_USE_BITMAP32 is set, otherwise
+ // the alpha is in a separate bitmap.
+ if (mBitCount == 32)
+ return kPremul_SkAlphaType;
+#endif
+ return kOpaque_SkAlphaType;
+}
+
+void SkiaSalBitmap::PerformErase()
+{
+ if (mPixelsSize.IsEmpty())
+ return;
+ BitmapBuffer* bitmapBuffer = AcquireBuffer(BitmapAccessMode::Write);
+ if (bitmapBuffer == nullptr)
+ abort();
+ Color fastColor = mEraseColor;
+ if (!!mPalette)
+ fastColor = Color(ColorAlpha, mPalette.GetBestIndex(fastColor));
+ if (!ImplFastEraseBitmap(*bitmapBuffer, fastColor))
+ {
+ FncSetPixel setPixel = BitmapReadAccess::SetPixelFunction(bitmapBuffer->mnFormat);
+ assert(bitmapBuffer->mnFormat & ScanlineFormat::TopDown);
+ // Set first scanline, copy to others.
+ Scanline scanline = bitmapBuffer->mpBits;
+ for (tools::Long x = 0; x < bitmapBuffer->mnWidth; ++x)
+ setPixel(scanline, x, mEraseColor, bitmapBuffer->maColorMask);
+ for (tools::Long y = 1; y < bitmapBuffer->mnHeight; ++y)
+ memcpy(scanline + y * bitmapBuffer->mnScanlineSize, scanline,
+ bitmapBuffer->mnScanlineSize);
+ }
+ ReleaseBuffer(bitmapBuffer, BitmapAccessMode::Write, true);
+}
+
+void SkiaSalBitmap::EnsureBitmapData()
+{
+ if (mEraseColorSet)
+ {
+ SkiaZone zone;
+ assert(mPixelsSize == mSize);
+ assert(!mBuffer);
+ CreateBitmapData();
+ // Unset now, so that repeated call will return mBuffer.
+ mEraseColorSet = false;
+ PerformErase();
+ verify();
+ SAL_INFO("vcl.skia.trace",
+ "ensurebitmapdata(" << this << ") from erase color " << mEraseColor);
+ return;
+ }
+
+ if (mBuffer)
+ {
+ if (mSize == mPixelsSize)
+ return;
+ // Pending scaling. Create raster SkImage from the bitmap data
+ // at the pixel size and then the code below will scale at the correct
+ // bpp from the image.
+ SAL_INFO("vcl.skia.trace", "ensurebitmapdata(" << this << "): pixels to be scaled "
+ << mPixelsSize << "->" << mSize << ":"
+ << static_cast<int>(mScaleQuality));
+ Size savedSize = mSize;
+ mSize = mPixelsSize;
+ ResetToSkImage(SkImages::RasterFromBitmap(GetAsSkBitmap()));
+ mSize = savedSize;
+ }
+
+ // Convert from alpha image, if the conversion is simple.
+ if (mAlphaImage && imageSize(mAlphaImage) == mSize && mBitCount == 8
+ && mPalette.IsGreyPalette8Bit())
+ {
+ assert(mAlphaImage->colorType() == kAlpha_8_SkColorType);
+ SkiaZone zone;
+ SkBitmap bitmap;
+ SkPixmap pixmap;
+ if (mAlphaImage->peekPixels(&pixmap))
+ bitmap.installPixels(pixmap);
+ else
+ {
+ if (!bitmap.tryAllocPixels(SkImageInfo::MakeA8(mSize.Width(), mSize.Height())))
+ abort();
+ SkCanvas canvas(bitmap);
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
+ canvas.drawImage(mAlphaImage, 0, 0, SkSamplingOptions(), &paint);
+ canvas.flush();
+ }
+ bitmap.setImmutable();
+ ResetPendingScaling();
+ CreateBitmapData();
+ assert(mBuffer != nullptr);
+ assert(mPixelsSize == mSize);
+ if (int(bitmap.rowBytes()) == mScanlineSize)
+ memcpy(mBuffer.get(), bitmap.getPixels(), mSize.Height() * mScanlineSize);
+ else
+ {
+ for (tools::Long y = 0; y < mSize.Height(); ++y)
+ {
+ const uint8_t* src = static_cast<uint8_t*>(bitmap.getAddr(0, y));
+ sal_uInt8* dest = mBuffer.get() + mScanlineSize * y;
+ memcpy(dest, src, mScanlineSize);
+ }
+ }
+ verify();
+ // We've created the bitmap data from mAlphaImage, drop the image if conserving memory,
+ // it'll be converted back if needed.
+ if (ConserveMemory())
+ {
+ SAL_INFO("vcl.skia.trace", "ensurebitmapdata(" << this << "): dropping images");
+ ResetToBuffer();
+ }
+ SAL_INFO("vcl.skia.trace", "ensurebitmapdata(" << this << "): from alpha image");
+ return;
+ }
+
+ if (!mImage)
+ {
+ // No data at all, create uninitialized data.
+ CreateBitmapData();
+ SAL_INFO("vcl.skia.trace", "ensurebitmapdata(" << this << "): uninitialized");
+ return;
+ }
+ // Try to fill mBuffer from mImage.
+ assert(mImage->colorType() == kN32_SkColorType);
+ SkiaZone zone;
+ // If the source image has no alpha, then use no alpha (faster to convert), otherwise
+ // use kUnpremul_SkAlphaType to make Skia convert from premultiplied alpha when reading
+ // from the SkImage (the alpha will be ignored if converting to bpp<32 formats, but
+ // the color channels must be unpremultiplied. Unless bpp==32 and SKIA_USE_BITMAP32,
+ // in which case use kPremul_SkAlphaType, since SKIA_USE_BITMAP32 implies premultiplied alpha.
+ SkAlphaType alphaType = kUnpremul_SkAlphaType;
+ if (mImage->imageInfo().alphaType() == kOpaque_SkAlphaType)
+ alphaType = kOpaque_SkAlphaType;
+#if SKIA_USE_BITMAP32
+ if (mBitCount == 32)
+ alphaType = kPremul_SkAlphaType;
+#endif
+ SkBitmap bitmap;
+ SkPixmap pixmap;
+ if (imageSize(mImage) == mSize && mImage->imageInfo().alphaType() == alphaType
+ && mImage->peekPixels(&pixmap))
+ {
+ bitmap.installPixels(pixmap);
+ }
+ else
+ {
+ if (!bitmap.tryAllocPixels(SkImageInfo::MakeS32(mSize.Width(), mSize.Height(), alphaType)))
+ abort();
+ SkCanvas canvas(bitmap);
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
+ if (imageSize(mImage) != mSize) // pending scaling?
+ {
+ canvas.drawImageRect(mImage, SkRect::MakeWH(mSize.getWidth(), mSize.getHeight()),
+ makeSamplingOptions(mScaleQuality, imageSize(mImage), mSize, 1),
+ &paint);
+ SAL_INFO("vcl.skia.trace",
+ "ensurebitmapdata(" << this << "): image scaled " << imageSize(mImage) << "->"
+ << mSize << ":" << static_cast<int>(mScaleQuality));
+ }
+ else
+ canvas.drawImage(mImage, 0, 0, SkSamplingOptions(), &paint);
+ canvas.flush();
+ }
+ bitmap.setImmutable();
+ ResetPendingScaling();
+ CreateBitmapData();
+ assert(mBuffer != nullptr);
+ assert(mPixelsSize == mSize);
+ if (mBitCount == 32)
+ {
+ if (int(bitmap.rowBytes()) == mScanlineSize)
+ memcpy(mBuffer.get(), bitmap.getPixels(), mSize.Height() * mScanlineSize);
+ else
+ {
+ for (tools::Long y = 0; y < mSize.Height(); ++y)
+ {
+ const uint8_t* src = static_cast<uint8_t*>(bitmap.getAddr(0, y));
+ sal_uInt8* dest = mBuffer.get() + mScanlineSize * y;
+ memcpy(dest, src, mScanlineSize);
+ }
+ }
+ }
+ else if (mBitCount == 24) // non-paletted
+ {
+ if (int(bitmap.rowBytes()) == mSize.Width() * 4 && mSize.Width() * 3 == mScanlineSize)
+ {
+ SkConvertRGBAToRGB(mBuffer.get(), bitmap.getAddr32(0, 0),
+ mSize.Height() * mSize.Width());
+ }
+ else
+ {
+ for (tools::Long y = 0; y < mSize.Height(); ++y)
+ {
+ const uint32_t* src = bitmap.getAddr32(0, y);
+ sal_uInt8* dest = mBuffer.get() + mScanlineSize * y;
+ SkConvertRGBAToRGB(dest, src, mSize.Width());
+ }
+ }
+ }
+ else if (mBitCount == 8 && mPalette.IsGreyPalette8Bit())
+ { // no actual data conversion, use one color channel as the gray value
+ if (int(bitmap.rowBytes()) == mSize.Width() * 4 && mSize.Width() * 1 == mScanlineSize)
+ {
+ SkConvertRGBAToR(mBuffer.get(), bitmap.getAddr32(0, 0), mSize.Height() * mSize.Width());
+ }
+ else
+ {
+ for (tools::Long y = 0; y < mSize.Height(); ++y)
+ {
+ const uint32_t* src = bitmap.getAddr32(0, y);
+ sal_uInt8* dest = mBuffer.get() + mScanlineSize * y;
+ SkConvertRGBAToR(dest, src, mSize.Width());
+ }
+ }
+ }
+ else
+ {
+ std::unique_ptr<vcl::ScanlineWriter> pWriter
+ = vcl::ScanlineWriter::Create(mBitCount, mPalette);
+ for (tools::Long y = 0; y < mSize.Height(); ++y)
+ {
+ const uint8_t* src = static_cast<uint8_t*>(bitmap.getAddr(0, y));
+ sal_uInt8* dest = mBuffer.get() + mScanlineSize * y;
+ pWriter->nextLine(dest);
+ for (tools::Long x = 0; x < mSize.Width(); ++x)
+ {
+ sal_uInt8 r = *src++;
+ sal_uInt8 g = *src++;
+ sal_uInt8 b = *src++;
+ ++src; // skip alpha
+ pWriter->writeRGB(r, g, b);
+ }
+ }
+ }
+ verify();
+ // We've created the bitmap data from mImage, drop the image if conserving memory,
+ // it'll be converted back if needed.
+ if (ConserveMemory())
+ {
+ SAL_INFO("vcl.skia.trace", "ensurebitmapdata(" << this << "): dropping images");
+ ResetToBuffer();
+ }
+ SAL_INFO("vcl.skia.trace", "ensurebitmapdata(" << this << ")");
+}
+
+void SkiaSalBitmap::EnsureBitmapUniqueData()
+{
+#ifdef DBG_UTIL
+ assert(mWriteAccessCount == 0);
+#endif
+ EnsureBitmapData();
+ assert(mPixelsSize == mSize);
+ if (mBuffer.use_count() > 1)
+ {
+ sal_uInt32 allocate = mScanlineSize * mSize.Height();
+#ifdef DBG_UTIL
+ assert(memcmp(mBuffer.get() + allocate, CANARY, sizeof(CANARY)) == 0);
+ allocate += sizeof(CANARY);
+#endif
+ boost::shared_ptr<sal_uInt8[]> newBuffer = boost::make_shared_noinit<sal_uInt8[]>(allocate);
+ memcpy(newBuffer.get(), mBuffer.get(), allocate);
+ mBuffer = newBuffer;
+ }
+}
+
+void SkiaSalBitmap::ResetToBuffer()
+{
+ SkiaZone zone;
+ // This should never be called to drop mImage if that's the only data we have.
+ assert(mBuffer || !mImage);
+ mImage.reset();
+ mImageImmutable = false;
+ mAlphaImage.reset();
+ mEraseColorSet = false;
+}
+
+void SkiaSalBitmap::ResetToSkImage(sk_sp<SkImage> image)
+{
+ assert(mReadAccessCount == 0); // can't reset mBuffer if there's a read access pointing to it
+ SkiaZone zone;
+ mBuffer.reset();
+ // Just to be safe, assume mutability of the image does not change
+ mImage = image;
+ mAlphaImage.reset();
+ mEraseColorSet = false;
+}
+
+void SkiaSalBitmap::ResetAllData()
+{
+ assert(mReadAccessCount == 0);
+ SkiaZone zone;
+ mBuffer.reset();
+ mImage.reset();
+ mImageImmutable = false;
+ mAlphaImage.reset();
+ mEraseColorSet = false;
+ mPixelsSize = mSize;
+ ComputeScanlineSize();
+ DataChanged();
+}
+
+void SkiaSalBitmap::DataChanged() { InvalidateChecksum(); }
+
+void SkiaSalBitmap::ResetPendingScaling()
+{
+ if (mPixelsSize == mSize)
+ return;
+ SkiaZone zone;
+ mScaleQuality = BmpScaleFlag::BestQuality;
+ mPixelsSize = mSize;
+ ComputeScanlineSize();
+ // Information about the pending scaling has been discarded, so make sure we do not
+ // keep around any cached images that would still need scaling.
+ if (mImage && imageSize(mImage) != mSize)
+ {
+ mImage.reset();
+ mImageImmutable = false;
+ }
+ if (mAlphaImage && imageSize(mAlphaImage) != mSize)
+ mAlphaImage.reset();
+}
+
+OString SkiaSalBitmap::GetImageKey(DirectImage direct) const
+{
+ if (mEraseColorSet)
+ {
+ std::stringstream ss;
+ ss << std::hex << std::setfill('0') << std::setw(6)
+ << static_cast<sal_uInt32>(mEraseColor.GetRGBColor()) << std::setw(2)
+ << static_cast<int>(mEraseColor.GetAlpha());
+ return OString::Concat("E") + ss.str().c_str();
+ }
+ assert(direct == DirectImage::No || mImage);
+ sk_sp<SkImage> image = GetSkImage(direct);
+ // In some cases drawing code may try to draw the same content but using
+ // different bitmaps (even underlying bitmaps), for example canvas apparently
+ // copies the same things around in tdf#146095. For pixel-based images
+ // it should be still cheaper to compute a checksum and avoid re-caching.
+ if (!image->isTextureBacked())
+ return OString::Concat("C") + OString::number(getSkImageChecksum(image));
+ return OString::Concat("I") + OString::number(image->uniqueID());
+}
+
+OString SkiaSalBitmap::GetAlphaImageKey(DirectImage direct) const
+{
+ if (mEraseColorSet)
+ {
+ std::stringstream ss;
+ ss << std::hex << std::setfill('0') << std::setw(2)
+ << static_cast<int>(SkColorGetA(fromEraseColorToAlphaImageColor(mEraseColor)));
+ return OString::Concat("E") + ss.str().c_str();
+ }
+ assert(direct == DirectImage::No || mAlphaImage);
+ sk_sp<SkImage> image = GetAlphaSkImage(direct);
+ if (!image->isTextureBacked())
+ return OString::Concat("C") + OString::number(getSkImageChecksum(image));
+ return OString::Concat("I") + OString::number(image->uniqueID());
+}
+
+void SkiaSalBitmap::dump(const char* file) const
+{
+ // Use a copy, so that debugging doesn't affect this instance.
+ SkiaSalBitmap copy;
+ copy.Create(*this);
+ SkiaHelper::dump(copy.GetSkImage(), file);
+}
+
+#ifdef DBG_UTIL
+void SkiaSalBitmap::verify() const
+{
+ if (!mBuffer)
+ return;
+ // Use mPixelsSize, that describes the size of the actual data.
+ assert(memcmp(mBuffer.get() + mScanlineSize * mPixelsSize.Height(), CANARY, sizeof(CANARY))
+ == 0);
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/skia/skia_denylist_vulkan.xml b/vcl/skia/skia_denylist_vulkan.xml
new file mode 100644
index 0000000000..8da86069fc
--- /dev/null
+++ b/vcl/skia/skia_denylist_vulkan.xml
@@ -0,0 +1,73 @@
+<?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", "windows", "linux", "osx_10_5", "osx_10_6", "osx_10_7", "osx_10_8", "osx"
+ 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>
+ <entry os="all" vendor="intel" compare="less_equal" version="0.16.2">
+ <device id="all"/>
+ </entry>
+ <entry os="all" vendor="intel" compare="equal" version="0.402.743"> <!-- tdf#138729 -->
+ <device id="all"/>
+ </entry>
+ <entry os="windows" vendor="intel"> <!-- tdf#144923 off-topic comment 14 + tdf#150232 -->
+ <device id="0x9a49"/>
+ </entry>
+ <entry os="all" vendor="amd" compare="less" version="2.0.198"> <!-- tdf#146402 -->
+ <device id="all"/>
+ </entry>
+ <entry os="windows" vendor="nvidia" compare="less" version="457.36.0">
+ <device id="all"/>
+ </entry>
+ <entry os="windows" vendor="nvidia"> <!-- tdf#151929 Crash with GTX 670 -->
+ <device id="0x1189"/>
+ </entry>
+ <entry os="windows" vendor="nvidia"> <!-- tdf#150232 -->
+ <device id="0x1401"/>
+ </entry>
+ <entry os="windows" vendor="nvidia"> <!-- tdf#151627 + tdf#152559 -->
+ <device id="0x1402"/>
+ </entry>
+ <entry os="windows" vendor="nvidia"> <!-- tdf#153538: GeForce GTX 1070 Ti -->
+ <device id="0x1b82"/>
+ </entry>
+ <entry os="windows" vendor="nvidia"> <!-- tdf#156986: GeForce RTX 4070 (536.396.0) -->
+ <device id="0x2786"/>
+ </entry>
+ <entry os="windows" vendor="nvidia"> <!-- tdf#154378: GeForce RTX 3080 (531.164.0) -->
+ <device id="0x2206"/>
+ </entry>
+ <entry os="windows" vendor="nvidia"> <!-- tdf#155143: GeForce GTX 1660 SUPER (516.376.0) -->
+ <device id="0x21c4"/>
+ </entry>
+ <entry os="windows" vendor="nvidia"> <!-- tdf#155143: GTX 960M (512.60.0) -->
+ <device id="0x139d"/>
+ </entry>
+ <entry os="windows" vendor="nvidia"> <!-- tdf#155143: GeForce GTX 970 (531.272.0) -->
+ <device id="0x13c2"/>
+ </entry>
+ <entry os="windows" vendor="nvidia"> <!-- tdf#155143: Quadro P400 (516.376.0) -->
+ <device id="0x1cb3"/>
+ </entry>
+ <entry os="7" vendor="all">
+ <device id="all"/>
+ </entry>
+ </denylist>
+</root>
diff --git a/vcl/skia/win/gdiimpl.cxx b/vcl/skia/win/gdiimpl.cxx
new file mode 100644
index 0000000000..39294bc2b3
--- /dev/null
+++ b/vcl/skia/win/gdiimpl.cxx
@@ -0,0 +1,428 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <skia/win/gdiimpl.hxx>
+
+#include <win/saldata.hxx>
+#include <vcl/skia/SkiaHelper.hxx>
+#include <skia/utils.hxx>
+#include <skia/zone.hxx>
+#include <skia/win/font.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <comphelper/windowserrorstring.hxx>
+#include <sal/log.hxx>
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkPaint.h>
+#include <SkPixelRef.h>
+#include <SkTypeface_win.h>
+#include <SkFont.h>
+#include <SkFontMgr.h>
+#include <tools/sk_app/win/WindowContextFactory_win.h>
+#include <tools/sk_app/WindowContext.h>
+
+#include <windows.h>
+
+using namespace SkiaHelper;
+
+WinSkiaSalGraphicsImpl::WinSkiaSalGraphicsImpl(WinSalGraphics& rGraphics,
+ SalGeometryProvider* mpProvider)
+ : SkiaSalGraphicsImpl(rGraphics, mpProvider)
+ , mWinParent(rGraphics)
+{
+}
+
+void WinSkiaSalGraphicsImpl::createWindowSurfaceInternal(bool forceRaster)
+{
+ assert(!mWindowContext);
+ assert(!mSurface);
+ SkiaZone zone;
+ sk_app::DisplayParams displayParams;
+ assert(GetWidth() > 0 && GetHeight() > 0);
+ displayParams.fSurfaceProps = *surfaceProps();
+ switch (forceRaster ? RenderRaster : renderMethodToUse())
+ {
+ case RenderRaster:
+ mWindowContext = sk_app::window_context_factory::MakeRasterForWin(mWinParent.gethWnd(),
+ displayParams);
+ if (mWindowContext)
+ mSurface = mWindowContext->getBackbufferSurface();
+ break;
+ case RenderVulkan:
+ mWindowContext = sk_app::window_context_factory::MakeVulkanForWin(mWinParent.gethWnd(),
+ displayParams);
+ // See flushSurfaceToWindowContext().
+ if (mWindowContext)
+ mSurface = createSkSurface(GetWidth(), GetHeight());
+ break;
+ case RenderMetal:
+ abort();
+ break;
+ }
+}
+
+void WinSkiaSalGraphicsImpl::freeResources() {}
+
+void WinSkiaSalGraphicsImpl::Flush() { performFlush(); }
+
+bool WinSkiaSalGraphicsImpl::TryRenderCachedNativeControl(ControlCacheKey const& rControlCacheKey,
+ int nX, int nY)
+{
+ static bool gbCacheEnabled = !getenv("SAL_WITHOUT_WIDGET_CACHE");
+ if (!gbCacheEnabled)
+ return false;
+
+ auto& controlsCache = SkiaControlsCache::get();
+ SkiaControlCacheType::const_iterator iterator = controlsCache.find(rControlCacheKey);
+ if (iterator == controlsCache.end())
+ return false;
+
+ preDraw();
+ SAL_INFO("vcl.skia.trace", "tryrendercachednativecontrol("
+ << this << "): "
+ << SkIRect::MakeXYWH(nX, nY, iterator->second->width(),
+ iterator->second->height()));
+ addUpdateRegion(
+ SkRect::MakeXYWH(nX, nY, iterator->second->width(), iterator->second->height()));
+ mSurface->getCanvas()->drawImage(iterator->second, nX, nY);
+ postDraw();
+ return true;
+}
+
+bool WinSkiaSalGraphicsImpl::RenderAndCacheNativeControl(CompatibleDC& rWhite, CompatibleDC& rBlack,
+ int nX, int nY,
+ ControlCacheKey& aControlCacheKey)
+{
+ assert(dynamic_cast<SkiaCompatibleDC*>(&rWhite));
+ assert(dynamic_cast<SkiaCompatibleDC*>(&rBlack));
+
+ sk_sp<SkImage> image = static_cast<SkiaCompatibleDC&>(rBlack).getAsImageDiff(
+ static_cast<SkiaCompatibleDC&>(rWhite));
+ preDraw();
+ SAL_INFO("vcl.skia.trace",
+ "renderandcachednativecontrol("
+ << this << "): " << SkIRect::MakeXYWH(nX, nY, image->width(), image->height()));
+ addUpdateRegion(SkRect::MakeXYWH(nX, nY, image->width(), image->height()));
+ mSurface->getCanvas()->drawImage(image, nX, nY);
+ postDraw();
+
+ if (!aControlCacheKey.canCacheControl())
+ return true;
+ SkiaControlCachePair pair(aControlCacheKey, std::move(image));
+ SkiaControlsCache::get().insert(std::move(pair));
+ return true;
+}
+
+sk_sp<SkTypeface>
+WinSkiaSalGraphicsImpl::createDirectWriteTypeface(const WinFontInstance* pWinFont) try
+{
+ using sal::systools::ThrowIfFailed;
+ IDWriteFactory* dwriteFactory;
+ WinSalGraphics::getDWriteFactory(&dwriteFactory);
+ if (!dwriteDone)
+ {
+ dwriteFontMgr = SkFontMgr_New_DirectWrite(dwriteFactory);
+ dwriteDone = true;
+ }
+ if (!dwriteFontMgr)
+ return nullptr;
+
+ IDWriteFontFace* fontFace = pWinFont->GetDWFontFace();
+ if (!fontFace)
+ return nullptr;
+
+ sal::systools::COMReference<IDWriteFontCollection> collection;
+ ThrowIfFailed(dwriteFactory->GetSystemFontCollection(&collection), SAL_WHERE);
+ sal::systools::COMReference<IDWriteFont> font;
+ // As said above, this fails for our fonts.
+ if (FAILED(collection->GetFontFromFontFace(fontFace, &font)))
+ {
+ // If not found in system collection, try our private font collection.
+ // If that's not possible we'll fall back to Skia's GDI-based font rendering.
+ if (!dwritePrivateCollection
+ || FAILED(dwritePrivateCollection->GetFontFromFontFace(fontFace, &font)))
+ {
+ // Our private fonts are installed using AddFontResourceExW( FR_PRIVATE )
+ // and that does not make them available to the DWrite system font
+ // collection. For such cases attempt to update a collection of
+ // private fonts with this newly used font.
+
+ sal::systools::COMReference<IDWriteFactory3> dwriteFactory3;
+ ThrowIfFailed(dwriteFactory->QueryInterface(&dwriteFactory3), SAL_WHERE);
+
+ if (!dwriteFontSetBuilder)
+ ThrowIfFailed(dwriteFactory3->CreateFontSetBuilder(&dwriteFontSetBuilder),
+ SAL_WHERE);
+
+ UINT32 numberOfFiles;
+ ThrowIfFailed(fontFace->GetFiles(&numberOfFiles, nullptr), SAL_WHERE);
+ if (numberOfFiles != 1)
+ return nullptr;
+
+ sal::systools::COMReference<IDWriteFontFile> fontFile;
+ ThrowIfFailed(fontFace->GetFiles(&numberOfFiles, &fontFile), SAL_WHERE);
+
+ BOOL isSupported;
+ DWRITE_FONT_FILE_TYPE fileType;
+ UINT32 numberOfFonts;
+ ThrowIfFailed(fontFile->Analyze(&isSupported, &fileType, nullptr, &numberOfFonts),
+ SAL_WHERE);
+ if (!isSupported)
+ return nullptr;
+
+ // For each font within the font file, get a font face reference and add to the builder.
+ for (UINT32 fontIndex = 0; fontIndex < numberOfFonts; ++fontIndex)
+ {
+ sal::systools::COMReference<IDWriteFontFaceReference> fontFaceReference;
+ if (FAILED(dwriteFactory3->CreateFontFaceReference(fontFile.get(), fontIndex,
+ DWRITE_FONT_SIMULATIONS_NONE,
+ &fontFaceReference)))
+ continue;
+
+ // Leave it to DirectWrite to read properties directly out of the font files
+ dwriteFontSetBuilder->AddFontFaceReference(fontFaceReference.get());
+ }
+
+ sal::systools::COMReference<IDWriteFontSet> fontSet;
+ ThrowIfFailed(dwriteFontSetBuilder->CreateFontSet(&fontSet), SAL_WHERE);
+ ThrowIfFailed(dwriteFactory3->CreateFontCollectionFromFontSet(fontSet.get(),
+ &dwritePrivateCollection),
+ SAL_WHERE);
+ ThrowIfFailed(dwritePrivateCollection->GetFontFromFontFace(fontFace, &font), SAL_WHERE);
+ }
+ }
+ sal::systools::COMReference<IDWriteFontFamily> fontFamily;
+ ThrowIfFailed(font->GetFontFamily(&fontFamily), SAL_WHERE);
+ return sk_sp<SkTypeface>(
+ SkCreateTypefaceDirectWrite(dwriteFontMgr, fontFace, font.get(), fontFamily.get()));
+}
+catch (const sal::systools::ComError& e)
+{
+ SAL_DETAIL_LOG_STREAM(SAL_DETAIL_ENABLE_LOG_INFO, ::SAL_DETAIL_LOG_LEVEL_INFO, "vcl.skia",
+ e.what(),
+ "HRESULT 0x" << OUString::number(e.GetHresult(), 16) << ": "
+ << WindowsErrorStringFromHRESULT(e.GetHresult()));
+ return nullptr;
+}
+
+bool WinSkiaSalGraphicsImpl::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+ assert(dynamic_cast<const SkiaWinFontInstance*>(&rLayout.GetFont()));
+ const SkiaWinFontInstance* pWinFont
+ = static_cast<const SkiaWinFontInstance*>(&rLayout.GetFont());
+ const HFONT hLayoutFont = pWinFont->GetHFONT();
+ double hScale = pWinFont->getHScale();
+ LOGFONTW logFont;
+ if (GetObjectW(hLayoutFont, sizeof(logFont), &logFont) == 0)
+ {
+ assert(false);
+ return false;
+ }
+ sk_sp<SkTypeface> typeface = pWinFont->GetSkiaTypeface();
+ if (!typeface)
+ {
+ typeface = createDirectWriteTypeface(pWinFont);
+ bool dwrite = true;
+ if (!typeface) // fall back to GDI text rendering
+ {
+ // If lfWidth is kept, then with hScale != 1 characters get too wide, presumably
+ // because the horizontal scaling gets applied twice if GDI is used for drawing (tdf#141715).
+ // Using lfWidth /= hScale gives slightly incorrect sizes, for a reason I don't understand.
+ // LOGFONT docs say that 0 means GDI will find out the right value on its own somehow,
+ // and it apparently works.
+ logFont.lfWidth = 0;
+ // Reset LOGFONT orientation, the proper orientation is applied by drawGenericLayout(),
+ // and keeping this would make it get applied once more when doing the actual GDI drawing.
+ // Resetting it here does not seem to cause any problem.
+ logFont.lfOrientation = 0;
+ logFont.lfEscapement = 0;
+ typeface = SkCreateTypefaceFromLOGFONT(logFont);
+ dwrite = false;
+ if (!typeface)
+ return false;
+ }
+ // Cache the typeface.
+ const_cast<SkiaWinFontInstance*>(pWinFont)->SetSkiaTypeface(typeface, dwrite);
+ }
+
+ SkFont font(typeface);
+
+ bool bSubpixelPositioning = rLayout.GetSubpixelPositioning();
+ SkFont::Edging ePreferredAliasing
+ = bSubpixelPositioning ? SkFont::Edging::kSubpixelAntiAlias : fontEdging;
+ if (bSubpixelPositioning)
+ {
+ // note that SkFont defaults to a BaselineSnap of true, so I think really only
+ // subpixel in text direction
+ font.setSubpixel(true);
+ }
+ font.setEdging(logFont.lfQuality == NONANTIALIASED_QUALITY ? SkFont::Edging::kAlias
+ : ePreferredAliasing);
+
+ const vcl::font::FontSelectPattern& rFSD = pWinFont->GetFontSelectPattern();
+ int nHeight = rFSD.mnHeight;
+ int nWidth = rFSD.mnWidth ? rFSD.mnWidth : nHeight;
+ if (nWidth == 0 || nHeight == 0)
+ return false;
+ font.setSize(nHeight);
+ font.setScaleX(hScale);
+
+ // Unlike with Freetype-based font handling, use height even in vertical mode,
+ // additionally multiply it by horizontal scale to get the proper
+ // size and then scale the width back, otherwise the height would
+ // not be correct. I don't know why this is inconsistent.
+ SkFont verticalFont(font);
+ verticalFont.setSize(nHeight * hScale);
+ verticalFont.setScaleX(1.0 / hScale);
+
+ assert(dynamic_cast<SkiaSalGraphicsImpl*>(mWinParent.GetImpl()));
+ SkiaSalGraphicsImpl* impl = static_cast<SkiaSalGraphicsImpl*>(mWinParent.GetImpl());
+ COLORREF color = ::GetTextColor(mWinParent.getHDC());
+ Color salColor(GetRValue(color), GetGValue(color), GetBValue(color));
+ impl->drawGenericLayout(rLayout, salColor, font, verticalFont);
+ return true;
+}
+
+SkFont::Edging WinSkiaSalGraphicsImpl::fontEdging;
+
+void WinSkiaSalGraphicsImpl::initFontInfo()
+{
+ // Skia needs to be explicitly told what kind of antialiasing should be used,
+ // get it from system settings. This does not actually matter for the text
+ // rendering itself, since Skia has been patched to simply use the setting
+ // from the LOGFONT, which gets set by VCL's ImplGetLogFontFromFontSelect()
+ // and that one normally uses DEFAULT_QUALITY, so Windows will select
+ // the appropriate AA setting. But Skia internally chooses the format to which
+ // the glyphs will be rendered based on this setting (subpixel AA requires colors,
+ // others do not).
+ fontEdging = SkFont::Edging::kAlias;
+ SkPixelGeometry pixelGeometry = kUnknown_SkPixelGeometry;
+ BOOL set;
+ if (SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &set, 0) && set)
+ {
+ UINT set2;
+ if (SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &set2, 0)
+ && set2 == FE_FONTSMOOTHINGCLEARTYPE)
+ {
+ fontEdging = SkFont::Edging::kSubpixelAntiAlias;
+ if (SystemParametersInfo(SPI_GETFONTSMOOTHINGORIENTATION, 0, &set2, 0)
+ && set2 == FE_FONTSMOOTHINGORIENTATIONBGR)
+ // No idea how to tell if it's horizontal or vertical.
+ pixelGeometry = kBGR_H_SkPixelGeometry;
+ else
+ pixelGeometry = kRGB_H_SkPixelGeometry; // default
+ }
+ else
+ fontEdging = SkFont::Edging::kAntiAlias;
+ }
+ setPixelGeometry(pixelGeometry);
+}
+
+void WinSkiaSalGraphicsImpl::ClearDevFontCache()
+{
+ dwriteFontMgr.reset();
+ dwriteFontSetBuilder.clear();
+ dwritePrivateCollection.clear();
+ dwriteDone = false;
+ initFontInfo(); // get font info again, just in case
+}
+
+SkiaCompatibleDC::SkiaCompatibleDC(SalGraphics& rGraphics, int x, int y, int width, int height)
+ : CompatibleDC(rGraphics, x, y, width, height, false)
+{
+}
+
+sk_sp<SkImage> SkiaCompatibleDC::getAsImageDiff(const SkiaCompatibleDC& white) const
+{
+ SkiaZone zone;
+ assert(maRects.mnSrcWidth == white.maRects.mnSrcWidth
+ || maRects.mnSrcHeight == white.maRects.mnSrcHeight);
+ SkBitmap tmpBitmap;
+ if (!tmpBitmap.tryAllocPixels(SkImageInfo::Make(maRects.mnSrcWidth, maRects.mnSrcHeight,
+ kBGRA_8888_SkColorType, kPremul_SkAlphaType),
+ maRects.mnSrcWidth * 4))
+ abort();
+ // Native widgets are drawn twice on black/white background to synthetize alpha
+ // (commit c6b66646870cb2bffaa73565affcf80bf74e0b5c). The problem is that
+ // most widgets when drawn on transparent background are drawn properly (and the result
+ // is in premultiplied alpha format), some such as "Edit" (used by ControlType::Editbox)
+ // keep the alpha channel as transparent. Therefore the alpha is actually computed
+ // from the difference in the premultiplied red channels when drawn one black and on white.
+ // Alpha is computed as "alpha = 1.0 - abs(black.red - white.red)".
+ // I doubt this can be done using Skia, so do it manually here. Fortunately
+ // the bitmaps should be fairly small and are cached.
+ uint32_t* dest = tmpBitmap.getAddr32(0, 0);
+ assert(dest == tmpBitmap.getPixels());
+ const sal_uInt32* src = mpData;
+ const sal_uInt32* whiteSrc = white.mpData;
+ uint32_t* end = dest + tmpBitmap.width() * tmpBitmap.height();
+ while (dest < end)
+ {
+ uint32_t alpha = 255 - abs(int(*src & 0xff) - int(*whiteSrc & 0xff));
+ *dest = (*src & 0x00ffffff) | (alpha << 24);
+ ++dest;
+ ++src;
+ ++whiteSrc;
+ }
+ tmpBitmap.notifyPixelsChanged();
+ tmpBitmap.setImmutable();
+ sk_sp<SkSurface> surface = createSkSurface(tmpBitmap.width(), tmpBitmap.height());
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
+ SkCanvas* canvas = surface->getCanvas();
+ canvas->save();
+ // The data we got is upside-down.
+ SkMatrix matrix;
+ matrix.preTranslate(0, tmpBitmap.height());
+ matrix.setConcat(matrix, SkMatrix::Scale(1, -1));
+ canvas->concat(matrix);
+ canvas->drawImage(tmpBitmap.asImage(), 0, 0, SkSamplingOptions(), &paint);
+ canvas->restore();
+ return makeCheckedImageSnapshot(surface);
+}
+
+SkiaControlsCache::SkiaControlsCache()
+ : cache(200)
+{
+}
+
+SkiaControlCacheType& SkiaControlsCache::get()
+{
+ SalData* data = GetSalData();
+ if (!data->m_pSkiaControlsCache)
+ data->m_pSkiaControlsCache.reset(new SkiaControlsCache);
+ return data->m_pSkiaControlsCache->cache;
+}
+
+namespace
+{
+std::unique_ptr<sk_app::WindowContext> createVulkanWindowContext(bool /*temporary*/)
+{
+ SkiaZone zone;
+ sk_app::DisplayParams displayParams;
+ return sk_app::window_context_factory::MakeVulkanForWin(nullptr, displayParams);
+}
+}
+
+void WinSkiaSalGraphicsImpl::prepareSkia()
+{
+ initFontInfo();
+ SkiaHelper::prepareSkia(createVulkanWindowContext);
+}
+
+void WinSkiaSalGraphicsImpl::ClearNativeControlCache()
+{
+ SalData* data = GetSalData();
+ data->m_pSkiaControlsCache.reset();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/skia/x11/gdiimpl.cxx b/vcl/skia/x11/gdiimpl.cxx
new file mode 100644
index 0000000000..6a7cce14dd
--- /dev/null
+++ b/vcl/skia/x11/gdiimpl.cxx
@@ -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/.
+ *
+ * Some of this code is based on Skia source code, covered by the following
+ * license notice (see readlicense_oo for the full license):
+ *
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ */
+
+#include <skia/x11/gdiimpl.hxx>
+
+#include <tools/sk_app/unix/WindowContextFactory_unix.h>
+
+#include <skia/utils.hxx>
+#include <skia/zone.hxx>
+
+#include <X11/Xutil.h>
+
+using namespace SkiaHelper;
+
+X11SkiaSalGraphicsImpl::X11SkiaSalGraphicsImpl(X11SalGraphics& rParent)
+ : SkiaSalGraphicsImpl(rParent, rParent.GetGeometryProvider())
+ , mX11Parent(rParent)
+{
+}
+
+void X11SkiaSalGraphicsImpl::Init()
+{
+ // The m_pFrame and m_pVDev pointers are updated late in X11
+ setProvider(mX11Parent.GetGeometryProvider());
+ SkiaSalGraphicsImpl::Init();
+}
+
+void X11SkiaSalGraphicsImpl::createWindowSurfaceInternal(bool forceRaster)
+{
+ assert(!mWindowContext);
+ assert(!mSurface);
+ assert(mX11Parent.GetDrawable() != None);
+ RenderMethod renderMethod = forceRaster ? RenderRaster : renderMethodToUse();
+ mScaling = getWindowScaling();
+ mWindowContext = createWindowContext(mX11Parent.GetXDisplay(), mX11Parent.GetDrawable(),
+ &mX11Parent.GetVisual(), GetWidth() * mScaling,
+ GetHeight() * mScaling, renderMethod, false);
+ if (mWindowContext)
+ {
+ // See flushSurfaceToWindowContext().
+ if (renderMethod == RenderRaster)
+ mSurface = mWindowContext->getBackbufferSurface();
+ else
+ mSurface = createSkSurface(GetWidth(), GetHeight());
+ }
+}
+
+std::unique_ptr<sk_app::WindowContext>
+X11SkiaSalGraphicsImpl::createWindowContext(Display* display, Drawable drawable,
+ const XVisualInfo* visual, int width, int height,
+ RenderMethod renderMethod, bool temporary)
+{
+ SkiaZone zone;
+ sk_app::DisplayParams displayParams;
+ displayParams.fColorType = kN32_SkColorType;
+#if defined LINUX
+ // WORKAROUND: VSync causes freezes that can even temporarily freeze the entire desktop.
+ // This happens even with the latest 450.66 drivers despite them claiming a fix for vsync.
+ // https://forums.developer.nvidia.com/t/hangs-freezes-when-vulkan-v-sync-vk-present-mode-fifo-khr-is-enabled/67751
+ if (getVendor() == DriverBlocklist::VendorNVIDIA)
+ displayParams.fDisableVsync = true;
+#endif
+ sk_app::window_context_factory::XlibWindowInfo winInfo;
+ assert(display);
+ winInfo.fDisplay = display;
+ winInfo.fWindow = drawable;
+ winInfo.fFBConfig = nullptr; // not used
+ winInfo.fVisualInfo = const_cast<XVisualInfo*>(visual);
+ assert(winInfo.fVisualInfo->visual != nullptr); // make sure it's not an uninitialized SalVisual
+ winInfo.fWidth = width;
+ winInfo.fHeight = height;
+#if defined DBG_UTIL && !defined NDEBUG
+ // Our patched Skia has VulkanWindowContext that shares grDirectContext, which requires
+ // that the X11 visual is always the same. Ensure it is so.
+ static VisualID checkVisualID = -1U;
+ // Exception is for the temporary case during startup, when SkiaHelper's
+ // checkDeviceDenylisted() needs a WindowContext and may be called before SalVisual
+ // is ready.
+ if (!temporary)
+ {
+ assert(checkVisualID == -1U || winInfo.fVisualInfo->visualid == checkVisualID);
+ checkVisualID = winInfo.fVisualInfo->visualid;
+ }
+#else
+ (void)temporary;
+#endif
+ switch (renderMethod)
+ {
+ case RenderRaster:
+ // Make sure we ask for color type that matches the X11 visual. If red mask
+ // is larger value than blue mask, then on little endian this means blue is first.
+ // This should also preferably match SK_R32_SHIFT set in config_skia.h, as that
+ // improves performance, the common setup seems to be BGRA (possibly because of
+ // choosing OpenGL-capable visual).
+ displayParams.fColorType
+ = (visual->red_mask > visual->blue_mask ? kBGRA_8888_SkColorType
+ : kRGBA_8888_SkColorType);
+ return sk_app::window_context_factory::MakeRasterForXlib(winInfo, displayParams);
+ case RenderVulkan:
+ return sk_app::window_context_factory::MakeVulkanForXlib(winInfo, displayParams);
+ case RenderMetal:
+ abort();
+ break;
+ }
+ abort();
+}
+
+bool X11SkiaSalGraphicsImpl::avoidRecreateByResize() const
+{
+ if (SkiaSalGraphicsImpl::avoidRecreateByResize())
+ return true;
+ if (!mSurface || isOffscreen())
+ return false;
+ // Skia's WindowContext uses actual dimensions of the X window, which due to X11 being
+ // asynchronous may be temporarily different from what VCL thinks are the dimensions.
+ // That can lead to us repeatedly calling recreateSurface() because of "incorrect"
+ // size, and we otherwise need to check for size changes, because VCL does not inform us.
+ // Avoid the problem here by checking the size of the X window and bail out if Skia
+ // would just return the same size as it is now.
+ Window r;
+ int x, y;
+ unsigned int w, h, border, depth;
+ XGetGeometry(mX11Parent.GetXDisplay(), mX11Parent.GetDrawable(), &r, &x, &y, &w, &h, &border,
+ &depth);
+ return mSurface->width() == int(w) && mSurface->height() == int(h);
+}
+
+void X11SkiaSalGraphicsImpl::freeResources() {}
+
+void X11SkiaSalGraphicsImpl::Flush() { performFlush(); }
+
+std::unique_ptr<sk_app::WindowContext> createVulkanWindowContext(bool temporary)
+{
+ SalDisplay* salDisplay = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ const XVisualInfo* visual;
+ XVisualInfo* visuals = nullptr;
+ if (!temporary)
+ visual = &salDisplay->GetVisual(salDisplay->GetDefaultXScreen());
+ else
+ {
+ // SalVisual from salDisplay may not be setup yet at this point, get
+ // info for the default visual.
+ XVisualInfo search;
+ search.visualid = XVisualIDFromVisual(
+ DefaultVisual(salDisplay->GetDisplay(), salDisplay->GetDefaultXScreen().getXScreen()));
+ int count;
+ visuals = XGetVisualInfo(salDisplay->GetDisplay(), VisualIDMask, &search, &count);
+ assert(count == 1);
+ visual = visuals;
+ }
+ std::unique_ptr<sk_app::WindowContext> ret = X11SkiaSalGraphicsImpl::createWindowContext(
+ salDisplay->GetDisplay(), None, visual, 1, 1, RenderVulkan, temporary);
+ if (temporary)
+ XFree(visuals);
+ return ret;
+}
+
+void X11SkiaSalGraphicsImpl::prepareSkia() { SkiaHelper::prepareSkia(createVulkanWindowContext); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/skia/x11/salvd.cxx b/vcl/skia/x11/salvd.cxx
new file mode 100644
index 0000000000..4647933058
--- /dev/null
+++ b/vcl/skia/x11/salvd.cxx
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/sysdata.hxx>
+
+#include <unx/salunx.h>
+#include <unx/saldisp.hxx>
+#include <unx/salgdi.h>
+#include <unx/salvd.h>
+
+#include <skia/x11/salvd.hxx>
+
+void X11SalGraphics::Init(X11SkiaSalVirtualDevice* pDevice)
+{
+ SalDisplay* pDisplay = pDevice->GetDisplay();
+
+ m_nXScreen = pDevice->GetXScreenNumber();
+ maX11Common.m_pColormap = &pDisplay->GetColormap(m_nXScreen);
+
+ m_pVDev = pDevice;
+ m_pFrame = nullptr;
+
+ mxImpl->Init();
+}
+
+X11SkiaSalVirtualDevice::X11SkiaSalVirtualDevice(const SalGraphics& rGraphics, tools::Long nDX,
+ tools::Long nDY, const SystemGraphicsData* pData,
+ std::unique_ptr<X11SalGraphics> pNewGraphics)
+ : mpGraphics(std::move(pNewGraphics))
+ , mbGraphics(false)
+ , mnXScreen(0)
+{
+ assert(mpGraphics);
+
+ // TODO Check where a VirtualDevice is created from SystemGraphicsData
+ assert(pData == nullptr);
+ (void)pData;
+
+ mpDisplay = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ mnXScreen = static_cast<const X11SalGraphics&>(rGraphics).GetScreenNumber();
+ mnWidth = nDX;
+ mnHeight = nDY;
+ mpGraphics->Init(this);
+}
+
+X11SkiaSalVirtualDevice::~X11SkiaSalVirtualDevice() {}
+
+SalGraphics* X11SkiaSalVirtualDevice::AcquireGraphics()
+{
+ if (mbGraphics)
+ return nullptr;
+
+ if (mpGraphics)
+ mbGraphics = true;
+
+ return mpGraphics.get();
+}
+
+void X11SkiaSalVirtualDevice::ReleaseGraphics(SalGraphics*) { mbGraphics = false; }
+
+bool X11SkiaSalVirtualDevice::SetSize(tools::Long nDX, tools::Long nDY)
+{
+ if (!nDX)
+ nDX = 1;
+ if (!nDY)
+ nDY = 1;
+
+ mnWidth = nDX;
+ mnHeight = nDY;
+ if (mpGraphics)
+ mpGraphics->Init(this);
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/skia/x11/textrender.cxx b/vcl/skia/x11/textrender.cxx
new file mode 100644
index 0000000000..acc930e070
--- /dev/null
+++ b/vcl/skia/x11/textrender.cxx
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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>
+
+#if defined __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-value"
+#endif
+#if defined __GNUC__ && !defined __clang__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-value"
+#endif
+
+#include <skia/x11/textrender.hxx>
+
+#include <unx/fc_fontoptions.hxx>
+#include <unx/freetype_glyphcache.hxx>
+#include <vcl/svapp.hxx>
+#include <sallayout.hxx>
+#include <skia/gdiimpl.hxx>
+
+#include <SkFont.h>
+#include <SkFontMgr_fontconfig.h>
+
+#if defined __GNUC__ && !defined __clang__
+#pragma GCC diagnostic pop
+#endif
+#if defined __clang__
+#pragma clang diagnostic pop
+#endif
+
+void SkiaTextRender::DrawTextLayout(const GenericSalLayout& rLayout, const SalGraphics& rGraphics)
+{
+ const FreetypeFontInstance& rInstance = static_cast<FreetypeFontInstance&>(rLayout.GetFont());
+ const FreetypeFont& rFont = rInstance.GetFreetypeFont();
+ const vcl::font::FontSelectPattern& rFSD = rInstance.GetFontSelectPattern();
+ int nHeight = rFSD.mnHeight;
+ int nWidth = rFSD.mnWidth ? rFSD.mnWidth : nHeight;
+ if (nWidth == 0 || nHeight == 0)
+ return;
+
+ if (!fontManager)
+ {
+ // Get the global FcConfig that our VCL fontconfig code uses, and refcount it.
+ fontManager = SkFontMgr_New_FontConfig(FcConfigReference(nullptr));
+ }
+ sk_sp<SkTypeface> typeface
+ = SkFontMgr_createTypefaceFromFcPattern(fontManager, rFont.GetFontOptions()->GetPattern());
+ SkFont font(typeface);
+ font.setSize(nHeight);
+ font.setScaleX(1.0 * nWidth / nHeight);
+ if (rInstance.NeedsArtificialItalic())
+ font.setSkewX(-1.0 * ARTIFICIAL_ITALIC_SKEW);
+ if (rInstance.NeedsArtificialBold())
+ font.setEmbolden(true);
+
+ bool bSubpixelPositioning = rLayout.GetSubpixelPositioning();
+ SkFont::Edging ePreferredAliasing
+ = bSubpixelPositioning ? SkFont::Edging::kSubpixelAntiAlias : SkFont::Edging::kAntiAlias;
+ if (bSubpixelPositioning)
+ {
+ // note that SkFont defaults to a BaselineSnap of true, so I think really only
+ // subpixel in text direction
+ font.setSubpixel(true);
+
+ SkFontHinting eHinting = font.getHinting();
+ static bool bAllowDefaultHinting = getenv("SAL_ALLOW_DEFAULT_HINTING") != nullptr;
+ bool bAllowedHintStyle
+ = bAllowDefaultHinting
+ || (eHinting == SkFontHinting::kNone || eHinting == SkFontHinting::kSlight);
+ if (!bAllowedHintStyle)
+ font.setHinting(SkFontHinting::kSlight);
+ }
+
+ font.setEdging(rFont.GetAntialiasAdvice() ? ePreferredAliasing : SkFont::Edging::kAlias);
+
+ // Vertical font, use width as "height".
+ SkFont verticalFont(font);
+ verticalFont.setSize(nWidth);
+ verticalFont.setScaleX(1.0 * nHeight / nWidth);
+
+ assert(dynamic_cast<SkiaSalGraphicsImpl*>(rGraphics.GetImpl()));
+ SkiaSalGraphicsImpl* impl = static_cast<SkiaSalGraphicsImpl*>(rGraphics.GetImpl());
+ impl->drawGenericLayout(rLayout, mnTextColor, font, verticalFont);
+}
+
+void SkiaTextRender::ClearDevFontCache() { fontManager.reset(); }
+
+#if 0
+// SKIA TODO
+void FontConfigFontOptions::cairo_font_options_substitute(FcPattern* pPattern)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ const cairo_font_options_t* pFontOptions = pSVData->mpDefInst->GetCairoFontOptions();
+ if( !pFontOptions )
+ return;
+ cairo_ft_font_options_substitute(pFontOptions, pPattern);
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/skia/zone.cxx b/vcl/skia/zone.cxx
new file mode 100644
index 0000000000..f954173662
--- /dev/null
+++ b/vcl/skia/zone.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 <skia/zone.hxx>
+
+#include <officecfg/Office/Common.hxx>
+#include <com/sun/star/util/XFlushable.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+
+#include <sal/log.hxx>
+#include <o3tl/unreachable.hxx>
+
+#include <vcl/skia/SkiaHelper.hxx>
+
+#include <config_skia.h>
+
+using namespace SkiaHelper;
+
+/**
+ * Called from a signal handler or watchdog thread if we get
+ * a crash or hang in some driver.
+ */
+void SkiaZone::hardDisable()
+{
+ // protect ourselves from double calling etc.
+ static bool bDisabled = false;
+ if (bDisabled)
+ return;
+
+ bDisabled = true;
+
+ // Instead of disabling Skia as a whole, only force the CPU-based
+ // raster mode, which should be safe as it does not use drivers.
+ std::shared_ptr<comphelper::ConfigurationChanges> xChanges(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::VCL::ForceSkiaRaster::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 SkiaZone::checkDebug(int nUnchanged, const CrashWatchdogTimingsValues& aTimingValues)
+{
+ SAL_INFO("vcl.watchdog", "Skia watchdog - unchanged "
+ << nUnchanged << " enter count " << enterCount()
+ << " breakpoints mid: " << aTimingValues.mnDisableEntries
+ << " max " << aTimingValues.mnAbortAfter);
+}
+
+const CrashWatchdogTimingsValues& SkiaZone::getCrashWatchdogTimingsValues()
+{
+ switch (renderMethodToUse())
+ {
+ case RenderVulkan:
+ case RenderMetal:
+ {
+#if defined(SK_RELEASE)
+ static const CrashWatchdogTimingsValues gpuValues = { 6, 20 }; /* 1.5s, 5s */
+#elif defined(SK_DEBUG)
+ static const CrashWatchdogTimingsValues gpuValues = { 60, 200 }; /* 15s, 50s */
+#else
+#error Unknown Skia debug/release setting.
+#endif
+ return gpuValues;
+ }
+ case RenderRaster:
+ {
+ // CPU-based operations with large images may take a noticeably long time,
+ // so use large values. CPU-based rendering shouldn't use any unstable drivers anyway.
+ static const CrashWatchdogTimingsValues rasterValues = { 600, 2000 }; /* 150s, 500s */
+ return rasterValues;
+ }
+ }
+ O3TL_UNREACHABLE;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/accessibility/AccessibleTextAttributeHelper.cxx b/vcl/source/accessibility/AccessibleTextAttributeHelper.cxx
new file mode 100644
index 0000000000..5647dc972b
--- /dev/null
+++ b/vcl/source/accessibility/AccessibleTextAttributeHelper.cxx
@@ -0,0 +1,396 @@
+/* -*- 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/accessibility/AccessibleTextAttributeHelper.hxx>
+
+#include <com/sun/star/accessibility/AccessibleTextType.hpp>
+#include <com/sun/star/accessibility/XAccessibleTextMarkup.hpp>
+#include <com/sun/star/awt/FontSlant.hpp>
+#include <com/sun/star/awt/FontStrikeout.hpp>
+#include <com/sun/star/awt/FontUnderline.hpp>
+#include <com/sun/star/awt/FontWeight.hpp>
+#include <com/sun/star/style/ParagraphAdjust.hpp>
+#include <com/sun/star/text/TextMarkupType.hpp>
+#include <o3tl/any.hxx>
+#include <tools/color.hxx>
+
+namespace
+{
+OUString lcl_ConvertCharEscapement(sal_Int16 nEscapement)
+{
+ if (nEscapement > 0)
+ return "super";
+ if (nEscapement < 0)
+ return "sub";
+
+ return "baseline";
+}
+
+OUString lcl_ConverCharStrikeout(sal_Int16 nStrikeout)
+{
+ OUString sTextLineThroughStyle;
+ OUString sTextLineThroughText;
+ OUString sTextLineThroughType;
+ OUString sTextLineThroughWidth;
+
+ switch (nStrikeout)
+ {
+ case css::awt::FontStrikeout::BOLD:
+ sTextLineThroughType = "single";
+ sTextLineThroughWidth = "bold";
+ break;
+ case css::awt::FontStrikeout::DONTKNOW:
+ break;
+ case css::awt::FontStrikeout::DOUBLE:
+ sTextLineThroughType = "double";
+ break;
+ case css::awt::FontStrikeout::NONE:
+ sTextLineThroughStyle = "none";
+ break;
+ case css::awt::FontStrikeout::SINGLE:
+ sTextLineThroughType = "single";
+ break;
+ case css::awt::FontStrikeout::SLASH:
+ sTextLineThroughText = u"/"_ustr;
+ break;
+ case css::awt::FontStrikeout::X:
+ sTextLineThroughText = u"X"_ustr;
+ break;
+ default:
+ assert(false && "Unhandled strikeout type");
+ }
+
+ OUString sResult;
+ if (!sTextLineThroughStyle.isEmpty())
+ sResult += u"text-line-through-style:"_ustr + sTextLineThroughStyle + ";";
+ if (!sTextLineThroughText.isEmpty())
+ sResult += u"text-line-through-text:"_ustr + sTextLineThroughText + ";";
+ if (!sTextLineThroughType.isEmpty())
+ sResult += u"text-line-through-type:"_ustr + sTextLineThroughType + ";";
+ if (!sTextLineThroughWidth.isEmpty())
+ sResult += u"text-line-through-width:"_ustr + sTextLineThroughWidth + ";";
+
+ return sResult;
+}
+
+OUString lcl_convertFontWeight(double fontWeight)
+{
+ if (fontWeight == css::awt::FontWeight::THIN || fontWeight == css::awt::FontWeight::ULTRALIGHT)
+ return "100";
+ if (fontWeight == css::awt::FontWeight::LIGHT)
+ return "200";
+ if (fontWeight == css::awt::FontWeight::SEMILIGHT)
+ return "300";
+ if (fontWeight == css::awt::FontWeight::NORMAL)
+ return "normal";
+ if (fontWeight == css::awt::FontWeight::SEMIBOLD)
+ return "500";
+ if (fontWeight == css::awt::FontWeight::BOLD)
+ return "bold";
+ if (fontWeight == css::awt::FontWeight::ULTRABOLD)
+ return "800";
+ if (fontWeight == css::awt::FontWeight::BLACK)
+ return "900";
+
+ // awt::FontWeight::DONTKNOW || fontWeight == awt::FontWeight::NORMAL
+ return "normal";
+}
+
+OUString lcl_ConvertFontSlant(css::awt::FontSlant eFontSlant)
+{
+ switch (eFontSlant)
+ {
+ case css::awt::FontSlant::FontSlant_NONE:
+ return "normal";
+ case css::awt::FontSlant::FontSlant_OBLIQUE:
+ case css::awt::FontSlant::FontSlant_REVERSE_OBLIQUE:
+ return "oblique";
+ case css::awt::FontSlant::FontSlant_ITALIC:
+ case css::awt::FontSlant::FontSlant_REVERSE_ITALIC:
+ return "italic";
+ case css::awt::FontSlant::FontSlant_DONTKNOW:
+ case css::awt::FontSlant::FontSlant_MAKE_FIXED_SIZE:
+ default:
+ return "";
+ }
+}
+
+// s. https://wiki.linuxfoundation.org/accessibility/iaccessible2/textattributes
+// for values
+void lcl_ConvertFontUnderline(sal_Int16 nFontUnderline, OUString& rUnderlineStyle,
+ OUString& rUnderlineType, OUString& rUnderlineWidth)
+{
+ rUnderlineStyle = u""_ustr;
+ rUnderlineType = u"single"_ustr;
+ rUnderlineWidth = u"auto"_ustr;
+
+ switch (nFontUnderline)
+ {
+ case css::awt::FontUnderline::BOLD:
+ rUnderlineWidth = u"bold"_ustr;
+ return;
+ case css::awt::FontUnderline::BOLDDASH:
+ rUnderlineWidth = u"bold"_ustr;
+ rUnderlineStyle = u"dash"_ustr;
+ return;
+ case css::awt::FontUnderline::BOLDDASHDOT:
+ rUnderlineWidth = u"bold"_ustr;
+ rUnderlineStyle = u"dot-dash"_ustr;
+ return;
+ case css::awt::FontUnderline::BOLDDASHDOTDOT:
+ rUnderlineWidth = u"bold"_ustr;
+ rUnderlineStyle = u"dot-dot-dash"_ustr;
+ return;
+ case css::awt::FontUnderline::BOLDDOTTED:
+ rUnderlineWidth = u"bold"_ustr;
+ rUnderlineStyle = u"dotted"_ustr;
+ return;
+ case css::awt::FontUnderline::BOLDLONGDASH:
+ rUnderlineWidth = u"bold"_ustr;
+ rUnderlineStyle = u"long-dash"_ustr;
+ return;
+ case css::awt::FontUnderline::BOLDWAVE:
+ rUnderlineWidth = u"bold"_ustr;
+ rUnderlineStyle = u"wave"_ustr;
+ return;
+ case css::awt::FontUnderline::DASH:
+ rUnderlineStyle = u"dash"_ustr;
+ return;
+ case css::awt::FontUnderline::DASHDOT:
+ rUnderlineStyle = u"dot-dash"_ustr;
+ return;
+ case css::awt::FontUnderline::DASHDOTDOT:
+ rUnderlineStyle = u"dot-dot-dash"_ustr;
+ return;
+ case css::awt::FontUnderline::DONTKNOW:
+ rUnderlineWidth = u""_ustr;
+ rUnderlineStyle = u""_ustr;
+ rUnderlineType = u""_ustr;
+ return;
+ case css::awt::FontUnderline::DOTTED:
+ rUnderlineStyle = u"dotted"_ustr;
+ return;
+ case css::awt::FontUnderline::DOUBLE:
+ rUnderlineType = u"double"_ustr;
+ return;
+ case css::awt::FontUnderline::DOUBLEWAVE:
+ rUnderlineStyle = u"wave"_ustr;
+ rUnderlineType = u"double"_ustr;
+ return;
+ case css::awt::FontUnderline::LONGDASH:
+ rUnderlineStyle = u"long-dash"_ustr;
+ return;
+ case css::awt::FontUnderline::NONE:
+ rUnderlineWidth = u"none"_ustr;
+ rUnderlineStyle = u"none"_ustr;
+ rUnderlineType = u"none"_ustr;
+ return;
+ case css::awt::FontUnderline::SINGLE:
+ rUnderlineType = u"single"_ustr;
+ return;
+ case css::awt::FontUnderline::SMALLWAVE:
+ case css::awt::FontUnderline::WAVE:
+ rUnderlineStyle = u"wave"_ustr;
+ return;
+ default:
+ assert(false && "Unhandled font underline type");
+ }
+}
+
+/** Converts Color to "rgb(r,g,b)" as specified in https://wiki.linuxfoundation.org/accessibility/iaccessible2/textattributes. */
+OUString lcl_ConvertColor(Color aColor)
+{
+ return u"rgb(" + OUString::number(aColor.GetRed()) + u"\\,"
+ + OUString::number(aColor.GetGreen()) + u"\\," + OUString::number(aColor.GetBlue())
+ + u")";
+}
+
+OUString lcl_ConvertParagraphAdjust(css::style::ParagraphAdjust eParaAdjust)
+{
+ switch (eParaAdjust)
+ {
+ case css::style::ParagraphAdjust_LEFT:
+ return u"left"_ustr;
+ case css::style::ParagraphAdjust_RIGHT:
+ return u"right"_ustr;
+ case css::style::ParagraphAdjust_BLOCK:
+ case css::style::ParagraphAdjust_STRETCH:
+ return u"justify"_ustr;
+ case css::style::ParagraphAdjust_CENTER:
+ return u"center"_ustr;
+ default:
+ assert(false && "Unhandled ParagraphAdjust value");
+ return u""_ustr;
+ }
+}
+}
+
+OUString AccessibleTextAttributeHelper::ConvertUnoToIAccessible2TextAttributes(
+ const css::uno::Sequence<css::beans::PropertyValue>& rUnoAttributes,
+ IA2AttributeType eAttributeType)
+{
+ OUString aRet;
+ for (css::beans::PropertyValue const& prop : rUnoAttributes)
+ {
+ OUString sAttribute;
+ OUString sValue;
+
+ if (eAttributeType & IA2AttributeType::TextAttributes)
+ {
+ if (prop.Name == "CharBackColor")
+ {
+ sAttribute = "background-color";
+ sValue = lcl_ConvertColor(
+ Color(ColorTransparency, *o3tl::doAccess<sal_Int32>(prop.Value)));
+ }
+ else if (prop.Name == "CharColor")
+ {
+ sAttribute = "color";
+ sValue = lcl_ConvertColor(
+ Color(ColorTransparency, *o3tl::doAccess<sal_Int32>(prop.Value)));
+ }
+ else if (prop.Name == "CharEscapement")
+ {
+ sAttribute = "text-position";
+ const sal_Int16 nEscapement = *o3tl::doAccess<sal_Int16>(prop.Value);
+ sValue = lcl_ConvertCharEscapement(nEscapement);
+ }
+ else if (prop.Name == "CharFontName")
+ {
+ sAttribute = "font-family";
+ sValue = *o3tl::doAccess<OUString>(prop.Value);
+ }
+ else if (prop.Name == "CharHeight")
+ {
+ sAttribute = "font-size";
+ sValue = OUString::number(*o3tl::doAccess<double>(prop.Value)) + "pt";
+ }
+ else if (prop.Name == "CharPosture")
+ {
+ sAttribute = "font-style";
+ const css::awt::FontSlant eFontSlant
+ = *o3tl::doAccess<css::awt::FontSlant>(prop.Value);
+ sValue = lcl_ConvertFontSlant(eFontSlant);
+ }
+ else if (prop.Name == "CharStrikeout")
+ {
+ const sal_Int16 nStrikeout = *o3tl::doAccess<sal_Int16>(prop.Value);
+ aRet += lcl_ConverCharStrikeout(nStrikeout);
+ }
+ else if (prop.Name == "CharUnderline")
+ {
+ OUString sUnderlineStyle;
+ OUString sUnderlineType;
+ OUString sUnderlineWidth;
+ const sal_Int16 nUnderline = *o3tl::doAccess<sal_Int16>(prop.Value);
+ lcl_ConvertFontUnderline(nUnderline, sUnderlineStyle, sUnderlineType,
+ sUnderlineWidth);
+
+ // leave 'sAttribute' and 'sName' empty, set all attributes here
+ if (!sUnderlineStyle.isEmpty())
+ aRet += u"text-underline-style:" + sUnderlineStyle + ";";
+ if (!sUnderlineType.isEmpty())
+ aRet += u"text-underline-type:" + sUnderlineType + ";";
+ if (!sUnderlineWidth.isEmpty())
+ aRet += u"text-underline-width:" + sUnderlineWidth + ";";
+ }
+ else if (prop.Name == "CharWeight")
+ {
+ sAttribute = "font-weight";
+ sValue = lcl_convertFontWeight(*o3tl::doAccess<double>(prop.Value));
+ }
+ }
+
+ // so far, "ParaAdjust" is the only UNO text attribute that
+ // maps to an object attribute for IAccessible2 ("text-align")
+ if (sAttribute.isEmpty() && (eAttributeType & IA2AttributeType::ObjectAttributes)
+ && prop.Name == "ParaAdjust")
+ {
+ sAttribute = "text-align";
+ const css::style::ParagraphAdjust eParaAdjust
+ = static_cast<css::style::ParagraphAdjust>(*o3tl::doAccess<sal_Int16>(prop.Value));
+ sValue = lcl_ConvertParagraphAdjust(eParaAdjust);
+ }
+
+ if (!sAttribute.isEmpty() && !sValue.isEmpty())
+ aRet += sAttribute + ":" + sValue + ";";
+ }
+
+ return aRet;
+}
+
+OUString AccessibleTextAttributeHelper::GetIAccessible2TextAttributes(
+ css::uno::Reference<css::accessibility::XAccessibleText> xText, IA2AttributeType eAttributeType,
+ sal_Int32 nOffset, sal_Int32& rStartOffset, sal_Int32& rEndOffset)
+{
+ assert(xText.is());
+
+ const css::uno::Sequence<css::beans::PropertyValue> attribs
+ = xText->getCharacterAttributes(nOffset, css::uno::Sequence<OUString>());
+ OUString sAttributes = ConvertUnoToIAccessible2TextAttributes(attribs, eAttributeType);
+
+ css::accessibility::TextSegment aAttributeRun
+ = xText->getTextAtIndex(nOffset, css::accessibility::AccessibleTextType::ATTRIBUTE_RUN);
+ rStartOffset = aAttributeRun.SegmentStart;
+ rEndOffset = aAttributeRun.SegmentEnd;
+
+ // report spelling error as "invalid:spelling;" IA2 text attribute and
+ // adapt start/end index as necessary
+ css::uno::Reference<css::accessibility::XAccessibleTextMarkup> xTextMarkup(xText,
+ css::uno::UNO_QUERY);
+ if ((eAttributeType & IA2AttributeType::TextAttributes) && xTextMarkup.is())
+ {
+ bool bInvalidSpelling = false;
+ const sal_Int32 nMarkupCount(
+ xTextMarkup->getTextMarkupCount(css::text::TextMarkupType::SPELLCHECK));
+ for (sal_Int32 nMarkupIndex = 0; nMarkupIndex < nMarkupCount; ++nMarkupIndex)
+ {
+ const css::accessibility::TextSegment aTextSegment
+ = xTextMarkup->getTextMarkup(nMarkupIndex, css::text::TextMarkupType::SPELLCHECK);
+ const sal_Int32 nStartOffsetTextMarkup = aTextSegment.SegmentStart;
+ const sal_Int32 nEndOffsetTextMarkup = aTextSegment.SegmentEnd;
+ if (nStartOffsetTextMarkup <= nOffset)
+ {
+ if (nOffset < nEndOffsetTextMarkup)
+ {
+ // offset is inside invalid spelling
+ rStartOffset = ::std::max(rStartOffset, nStartOffsetTextMarkup);
+ rEndOffset = ::std::min(rEndOffset, nEndOffsetTextMarkup);
+ bInvalidSpelling = true;
+ break;
+ }
+ else
+ {
+ rStartOffset = ::std::max(rStartOffset, nEndOffsetTextMarkup);
+ }
+ }
+ else
+ {
+ rEndOffset = ::std::min(rEndOffset, nStartOffsetTextMarkup);
+ }
+ }
+
+ if (bInvalidSpelling)
+ sAttributes += u"invalid:spelling;"_ustr;
+ }
+
+ return sAttributes;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/animate/Animation.cxx b/vcl/source/animate/Animation.cxx
new file mode 100644
index 0000000000..daa9e1f1be
--- /dev/null
+++ b/vcl/source/animate/Animation.cxx
@@ -0,0 +1,706 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <rtl/crc.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 <animate/AnimationRenderer.hxx>
+
+sal_uLong Animation::gAnimationRendererCount = 0;
+
+Animation::Animation()
+ : maTimer("vcl::Animation")
+ , mnLoopCount(0)
+ , mnLoops(0)
+ , mnFrameIndex(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)
+ , mnFrameIndex(rAnimation.mnFrameIndex)
+ , mbIsInAnimation(false)
+ , mbLoopTerminated(rAnimation.mbLoopTerminated)
+{
+ for (auto const& rFrame : rAnimation.maFrames)
+ maFrames.emplace_back(new AnimationFrame(*rFrame));
+
+ 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.maFrames)
+ maFrames.emplace_back(new AnimationFrame(*i));
+
+ maGlobalSize = rAnimation.maGlobalSize;
+ maBitmapEx = rAnimation.maBitmapEx;
+ mnLoopCount = rAnimation.mnLoopCount;
+ mnFrameIndex = rAnimation.mnFrameIndex;
+ mbLoopTerminated = rAnimation.mbLoopTerminated;
+ mnLoops = mbLoopTerminated ? 0 : mnLoopCount;
+ }
+ return *this;
+}
+
+bool Animation::operator==(const Animation& rAnimation) const
+{
+ return maFrames.size() == rAnimation.maFrames.size() && maBitmapEx == rAnimation.maBitmapEx
+ && maGlobalSize == rAnimation.maGlobalSize
+ && std::equal(maFrames.begin(), maFrames.end(), rAnimation.maFrames.begin(),
+ [](const std::unique_ptr<AnimationFrame>& pAnim1,
+ const std::unique_ptr<AnimationFrame>& pAnim2) -> bool {
+ return *pAnim1 == *pAnim2;
+ });
+}
+
+void Animation::Clear()
+{
+ maTimer.Stop();
+ mbIsInAnimation = false;
+ maGlobalSize = Size();
+ maBitmapEx.SetEmpty();
+ maFrames.clear();
+ maRenderers.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(maFrames.begin(), maFrames.end(),
+ [&aRect](const std::unique_ptr<AnimationFrame>& 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& pAnimationFrame : maFrames)
+ {
+ nSizeBytes += pAnimationFrame->maBitmapEx.GetSizeBytes();
+ }
+
+ return nSizeBytes;
+}
+
+BitmapChecksum Animation::GetChecksum() const
+{
+ SVBT32 aBT32;
+ BitmapChecksumOctetArray aBCOA;
+ BitmapChecksum nCrc = GetBitmapEx().GetChecksum();
+
+ UInt32ToSVBT32(maFrames.size(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(maGlobalSize.Width(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(maGlobalSize.Height(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ for (auto const& i : maFrames)
+ {
+ BCToBCOA(i->GetChecksum(), aBCOA);
+ nCrc = rtl_crc32(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+ }
+
+ return nCrc;
+}
+
+bool Animation::Start(OutputDevice& rOut, const Point& rDestPt, const Size& rDestSz,
+ tools::Long nRendererId, OutputDevice* pFirstFrameOutDev)
+{
+ bool bRet = false;
+
+ if (!maFrames.empty())
+ {
+ if ((rOut.GetOutDevType() == OUTDEV_WINDOW) && !mbLoopTerminated
+ && (ANIMATION_TIMEOUT_ON_CLICK != maFrames[mnFrameIndex]->mnWait))
+ {
+ bool differs = true;
+
+ auto itAnimView = std::find_if(
+ maRenderers.begin(), maRenderers.end(),
+ [&rOut, nRendererId](const std::unique_ptr<AnimationRenderer>& pRenderer) -> bool {
+ return pRenderer->matches(&rOut, nRendererId);
+ });
+
+ if (itAnimView != maRenderers.end())
+ {
+ if ((*itAnimView)->getOriginPosition() == rDestPt
+ && (*itAnimView)->getOutSizePix() == rOut.LogicToPixel(rDestSz))
+ {
+ (*itAnimView)->repaint();
+ differs = false;
+ }
+ else
+ {
+ maRenderers.erase(itAnimView);
+ }
+ }
+
+ if (maRenderers.empty())
+ {
+ maTimer.Stop();
+ mbIsInAnimation = false;
+ mnFrameIndex = 0;
+ }
+
+ if (differs)
+ maRenderers.emplace_back(new AnimationRenderer(this, &rOut, rDestPt, rDestSz,
+ nRendererId, pFirstFrameOutDev));
+
+ if (!mbIsInAnimation)
+ {
+ ImplRestartTimer(maFrames[mnFrameIndex]->mnWait);
+ mbIsInAnimation = true;
+ }
+ }
+ else
+ Draw(rOut, rDestPt, rDestSz);
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+void Animation::Stop(const OutputDevice* pOut, tools::Long nRendererId)
+{
+ std::erase_if(maRenderers, [=](const std::unique_ptr<AnimationRenderer>& pRenderer) -> bool {
+ return pRenderer->matches(pOut, nRendererId);
+ });
+
+ if (maRenderers.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 = maFrames.size();
+
+ if (!nCount)
+ return;
+
+ AnimationFrame* pObj = maFrames[std::min(mnFrameIndex, nCount - 1)].get();
+
+ if (rOut.GetConnectMetaFile() || (rOut.GetOutDevType() == OUTDEV_PRINTER))
+ {
+ maFrames[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 = mnFrameIndex;
+ if (mbLoopTerminated)
+ const_cast<Animation*>(this)->mnFrameIndex = nCount - 1;
+
+ {
+ AnimationRenderer{ const_cast<Animation*>(this), &rOut, rDestPt, rDestSz, 0 };
+ }
+
+ const_cast<Animation*>(this)->mnFrameIndex = nOldPos;
+ }
+}
+
+namespace
+{
+constexpr sal_uLong constMinTimeout = 2;
+}
+
+void Animation::ImplRestartTimer(sal_uLong nTimeout)
+{
+ maTimer.SetTimeout(std::max(nTimeout, constMinTimeout) * 10);
+ maTimer.Start();
+}
+
+std::vector<std::unique_ptr<AnimationData>> Animation::CreateAnimationDataItems()
+{
+ std::vector<std::unique_ptr<AnimationData>> aDataItems;
+
+ for (auto const& rItem : maRenderers)
+ {
+ aDataItems.emplace_back(rItem->createAnimationData());
+ }
+
+ return aDataItems;
+}
+
+void Animation::PopulateRenderers()
+{
+ for (auto& pDataItem : CreateAnimationDataItems())
+ {
+ AnimationRenderer* pRenderer = nullptr;
+ if (!pDataItem->mpRendererData)
+ {
+ pRenderer = new AnimationRenderer(this, pDataItem->mpRenderContext,
+ pDataItem->maOriginStartPt, pDataItem->maStartSize,
+ pDataItem->mnRendererId);
+
+ maRenderers.push_back(std::unique_ptr<AnimationRenderer>(pRenderer));
+ }
+ else
+ {
+ pRenderer = pDataItem->mpRendererData;
+ }
+
+ pRenderer->pause(pDataItem->mbIsPaused);
+ pRenderer->setMarked(true);
+ }
+}
+
+void Animation::RenderNextFrameInAllRenderers()
+{
+ AnimationFrame* pCurrentFrameBmp
+ = (++mnFrameIndex < maFrames.size()) ? maFrames[mnFrameIndex].get() : nullptr;
+
+ if (!pCurrentFrameBmp)
+ {
+ if (mnLoops == 1)
+ {
+ Stop();
+ mbLoopTerminated = true;
+ mnFrameIndex = maFrames.size() - 1;
+ maBitmapEx = maFrames[mnFrameIndex]->maBitmapEx;
+ return;
+ }
+ else
+ {
+ if (mnLoops)
+ mnLoops--;
+
+ mnFrameIndex = 0;
+ pCurrentFrameBmp = maFrames[mnFrameIndex].get();
+ }
+ }
+
+ // Paint all views.
+ std::for_each(maRenderers.cbegin(), maRenderers.cend(),
+ [this](const auto& pRenderer) { pRenderer->draw(mnFrameIndex); });
+ /*
+ * 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.
+ */
+ std::erase_if(maRenderers, [](const auto& pRenderer) { return pRenderer->isMarked(); });
+
+ // stop or restart timer
+ if (maRenderers.empty())
+ Stop();
+ else
+ ImplRestartTimer(pCurrentFrameBmp->mnWait);
+}
+
+void Animation::PruneMarkedRenderers()
+{
+ // delete all unmarked views
+ std::erase_if(maRenderers, [](const auto& pRenderer) { return !pRenderer->isMarked(); });
+
+ // reset marked state
+ std::for_each(maRenderers.cbegin(), maRenderers.cend(),
+ [](const auto& pRenderer) { pRenderer->setMarked(false); });
+}
+
+bool Animation::IsAnyRendererActive()
+{
+ return std::any_of(maRenderers.cbegin(), maRenderers.cend(),
+ [](const auto& pRenderer) { return !pRenderer->isPaused(); });
+}
+
+IMPL_LINK_NOARG(Animation, ImplTimeoutHdl, Timer*, void)
+{
+ const size_t nAnimCount = maFrames.size();
+
+ if (nAnimCount)
+ {
+ bool bIsAnyRendererActive = true;
+
+ if (maNotifyLink.IsSet())
+ {
+ maNotifyLink.Call(this);
+ PopulateRenderers();
+ PruneMarkedRenderers();
+ bIsAnyRendererActive = IsAnyRendererActive();
+ }
+
+ if (maRenderers.empty())
+ Stop();
+ else if (!bIsAnyRendererActive)
+ ImplRestartTimer(10);
+ else
+ RenderNextFrameInAllRenderers();
+ }
+ else
+ {
+ Stop();
+ }
+}
+
+bool Animation::Insert(const AnimationFrame& rStepBmp)
+{
+ bool bRet = false;
+
+ if (!IsInAnimation())
+ {
+ tools::Rectangle aGlobalRect(Point(), maGlobalSize);
+
+ maGlobalSize
+ = aGlobalRect.Union(tools::Rectangle(rStepBmp.maPositionPixel, rStepBmp.maSizePixel))
+ .GetSize();
+ maFrames.emplace_back(new AnimationFrame(rStepBmp));
+
+ // As a start, we make the first BitmapEx the replacement BitmapEx
+ if (maFrames.size() == 1)
+ maBitmapEx = rStepBmp.maBitmapEx;
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+const AnimationFrame& Animation::Get(sal_uInt16 nAnimation) const
+{
+ SAL_WARN_IF((nAnimation >= maFrames.size()), "vcl", "No object at this position");
+ return *maFrames[nAnimation];
+}
+
+void Animation::Replace(const AnimationFrame& rNewAnimationFrame, sal_uInt16 nAnimation)
+{
+ SAL_WARN_IF((nAnimation >= maFrames.size()), "vcl", "No object at this position");
+
+ maFrames[nAnimation].reset(new AnimationFrame(rNewAnimationFrame));
+
+ // If we insert at first position we also need to
+ // update the replacement BitmapEx
+ if ((!nAnimation && (!mbLoopTerminated || (maFrames.size() == 1)))
+ || ((nAnimation == maFrames.size() - 1) && mbLoopTerminated))
+ {
+ maBitmapEx = rNewAnimationFrame.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() && !maFrames.empty())
+ {
+ bRet = true;
+
+ for (size_t i = 0, n = maFrames.size(); (i < n) && bRet; ++i)
+ bRet = maFrames[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() && !maFrames.empty())
+ {
+ bRet = true;
+
+ for (size_t i = 0, n = maFrames.size(); (i < n) && bRet; ++i)
+ {
+ bRet = BitmapFilter::Filter(maFrames[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() && !maFrames.empty())
+ {
+ bRet = true;
+
+ for (size_t i = 0, n = maFrames.size(); (i < n) && bRet; ++i)
+ bRet = maFrames[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() || maFrames.empty())
+ return;
+
+ bRet = true;
+
+ if (nMirrorFlags == BmpMirrorFlags::NONE)
+ return;
+
+ for (size_t i = 0, n = maFrames.size(); (i < n) && bRet; ++i)
+ {
+ AnimationFrame* pCurrentFrameBmp = maFrames[i].get();
+ bRet = pCurrentFrameBmp->maBitmapEx.Mirror(nMirrorFlags);
+ if (bRet)
+ {
+ if (nMirrorFlags & BmpMirrorFlags::Horizontal)
+ pCurrentFrameBmp->maPositionPixel.setX(maGlobalSize.Width()
+ - pCurrentFrameBmp->maPositionPixel.X()
+ - pCurrentFrameBmp->maSizePixel.Width());
+
+ if (nMirrorFlags & BmpMirrorFlags::Vertical)
+ pCurrentFrameBmp->maPositionPixel.setY(maGlobalSize.Height()
+ - pCurrentFrameBmp->maPositionPixel.Y()
+ - pCurrentFrameBmp->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() || maFrames.empty())
+ return;
+
+ bRet = true;
+
+ for (size_t i = 0, n = maFrames.size(); (i < n) && bRet; ++i)
+ {
+ bRet = maFrames[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 AnimationFrame& rAnimationFrame = rAnimation.Get(i);
+ const sal_uInt16 nRest = nCount - i - 1;
+
+ // Write AnimationFrame
+ WriteDIBBitmapEx(rAnimationFrame.maBitmapEx, rOStm);
+ tools::GenericTypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(rAnimationFrame.maPositionPixel);
+ aSerializer.writeSize(rAnimationFrame.maSizePixel);
+ aSerializer.writeSize(rAnimation.maGlobalSize);
+ rOStm.WriteUInt16((ANIMATION_TIMEOUT_ON_CLICK == rAnimationFrame.mnWait)
+ ? 65535
+ : rAnimationFrame.mnWait);
+ rOStm.WriteUInt16(static_cast<sal_uInt16>(rAnimationFrame.meDisposal));
+ rOStm.WriteBool(rAnimationFrame.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 AnimationFrames 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 AnimationFrames
+ if (bReadAnimations)
+ {
+ AnimationFrame aAnimationFrame;
+ sal_uInt32 nTmp32;
+ sal_uInt16 nTmp16;
+ bool cTmp;
+
+ do
+ {
+ ReadDIBBitmapEx(aAnimationFrame.maBitmapEx, rIStm);
+ tools::GenericTypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(aAnimationFrame.maPositionPixel);
+ aSerializer.readSize(aAnimationFrame.maSizePixel);
+ aSerializer.readSize(rAnimation.maGlobalSize);
+ rIStm.ReadUInt16(nTmp16);
+ aAnimationFrame.mnWait = ((65535 == nTmp16) ? ANIMATION_TIMEOUT_ON_CLICK : nTmp16);
+ rIStm.ReadUInt16(nTmp16);
+ aAnimationFrame.meDisposal = static_cast<Disposal>(nTmp16);
+ rIStm.ReadCharAsBool(cTmp);
+ aAnimationFrame.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(aAnimationFrame);
+ } while (nTmp16 && !rIStm.GetError());
+
+ rAnimation.ResetLoopCount();
+ }
+
+ rIStm.SetEndian(nOldFormat);
+
+ return rIStm;
+}
+
+AnimationData::AnimationData()
+ : mpRenderContext(nullptr)
+ , mpRendererData(nullptr)
+ , mnRendererId(0)
+ , mbIsPaused(false)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/animate/AnimationFrame.cxx b/vcl/source/animate/AnimationFrame.cxx
new file mode 100644
index 0000000000..697167ebdc
--- /dev/null
+++ b/vcl/source/animate/AnimationFrame.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 <sal/config.h>
+#include <o3tl/underlyingenumvalue.hxx>
+#include <tools/solar.h>
+#include <vcl/animate/AnimationFrame.hxx>
+#include <rtl/crc.h>
+
+BitmapChecksum AnimationFrame::GetChecksum() const
+{
+ BitmapChecksum nCrc = maBitmapEx.GetChecksum();
+ SVBT32 aBT32;
+
+ Int32ToSVBT32(maPositionPixel.X(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(maPositionPixel.Y(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(maSizePixel.Width(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(maSizePixel.Height(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(mnWait, aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ UInt32ToSVBT32(o3tl::to_underlying(meDisposal), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ UInt32ToSVBT32(o3tl::to_underlying(meBlend), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ UInt32ToSVBT32(sal_uInt32(mbUserInput), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ return nCrc;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/animate/AnimationRenderer.cxx b/vcl/source/animate/AnimationRenderer.cxx
new file mode 100644
index 0000000000..29f386e0d3
--- /dev/null
+++ b/vcl/source/animate/AnimationRenderer.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 <memory>
+#include <animate/AnimationRenderer.hxx>
+
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+#include <tools/helpers.hxx>
+
+#include <window.h>
+
+AnimationRenderer::AnimationRenderer( Animation* pParent, OutputDevice* pOut,
+ const Point& rPt, const Size& rSz,
+ sal_uLong nRendererId,
+ OutputDevice* pFirstFrameOutDev ) :
+ mpParent ( pParent ),
+ mpRenderContext ( pFirstFrameOutDev ? pFirstFrameOutDev : pOut ),
+ mnRendererId ( nRendererId ),
+ maOriginPt ( rPt ),
+ maLogicalSize ( rSz ),
+ maSizePx ( mpRenderContext->LogicToPixel( maLogicalSize ) ),
+ maClip ( mpRenderContext->GetClipRegion() ),
+ mpBackground ( VclPtr<VirtualDevice>::Create() ),
+ mpRestore ( VclPtr<VirtualDevice>::Create() ),
+ mnActIndex ( 0 ),
+ meLastDisposal ( Disposal::Back ),
+ mbIsPaused ( false ),
+ mbIsMarked ( false ),
+ mbIsMirroredHorizontally( maLogicalSize.Width() < 0 ),
+ mbIsMirroredVertically( maLogicalSize.Height() < 0 )
+{
+ Animation::ImplIncAnimCount();
+
+ // Mirrored horizontally?
+ if( mbIsMirroredHorizontally )
+ {
+ maDispPt.setX( maOriginPt.X() + maLogicalSize.Width() + 1 );
+ maDispSz.setWidth( -maLogicalSize.Width() );
+ maSizePx.setWidth( -maSizePx.Width() );
+ }
+ else
+ {
+ maDispPt.setX( maOriginPt.X() );
+ maDispSz.setWidth( maLogicalSize.Width() );
+ }
+
+ // Mirrored vertically?
+ if( mbIsMirroredVertically )
+ {
+ maDispPt.setY( maOriginPt.Y() + maLogicalSize.Height() + 1 );
+ maDispSz.setHeight( -maLogicalSize.Height() );
+ maSizePx.setHeight( -maSizePx.Height() );
+ }
+ else
+ {
+ maDispPt.setY( maOriginPt.Y() );
+ maDispSz.setHeight( maLogicalSize.Height() );
+ }
+
+ // save background
+ mpBackground->SetOutputSizePixel( maSizePx );
+ mpRenderContext->SaveBackground(*mpBackground, maDispPt, maDispSz, maSizePx);
+
+ // Initialize drawing to actual position
+ drawToIndex( mpParent->ImplGetCurPos() );
+
+ // If first frame OutputDevice is set, update variables now for real OutputDevice
+ if( pFirstFrameOutDev )
+ {
+ mpRenderContext = pOut;
+ maClip = mpRenderContext->GetClipRegion();
+ }
+}
+
+AnimationRenderer::~AnimationRenderer()
+{
+ mpBackground.disposeAndClear();
+ mpRestore.disposeAndClear();
+
+ Animation::ImplDecAnimCount();
+}
+
+bool AnimationRenderer::matches(const OutputDevice* pOut, tools::Long nRendererId) const
+{
+ return (!pOut || pOut == mpRenderContext) && (nRendererId == 0 || nRendererId == mnRendererId);
+}
+
+void AnimationRenderer::getPosSize( const AnimationFrame& rAnimationFrame, Point& rPosPix, Size& rSizePix )
+{
+ const Size& rAnmSize = mpParent->GetDisplaySizePixel();
+ Point aPt2( rAnimationFrame.maPositionPixel.X() + rAnimationFrame.maSizePixel.Width() - 1,
+ rAnimationFrame.maPositionPixel.Y() + rAnimationFrame.maSizePixel.Height() - 1 );
+ double fFactX, fFactY;
+
+ // calculate x scaling
+ if( rAnmSize.Width() > 1 )
+ fFactX = static_cast<double>( maSizePx.Width() - 1 ) / ( rAnmSize.Width() - 1 );
+ else
+ fFactX = 1.0;
+
+ // calculate y scaling
+ if( rAnmSize.Height() > 1 )
+ fFactY = static_cast<double>( maSizePx.Height() - 1 ) / ( rAnmSize.Height() - 1 );
+ else
+ fFactY = 1.0;
+
+ rPosPix.setX( FRound( rAnimationFrame.maPositionPixel.X() * fFactX ) );
+ rPosPix.setY( FRound( rAnimationFrame.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( maSizePx.Width() - 1 - aPt2.X() );
+
+ // Mirrored vertically?
+ if( mbIsMirroredVertically )
+ rPosPix.setY( maSizePx.Height() - 1 - aPt2.Y() );
+}
+
+void AnimationRenderer::drawToIndex( sal_uLong nIndex )
+{
+ 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( maSizePx, false );
+ nIndex = std::min( nIndex, static_cast<sal_uLong>(mpParent->Count()) - 1 );
+
+ for( sal_uLong i = 0; i <= nIndex; i++ )
+ draw( i, aVDev.get() );
+
+ if (xOldClip)
+ pRenderContext->SetClipRegion( maClip );
+
+ pRenderContext->DrawOutDev( maDispPt, maDispSz, Point(), maSizePx, *aVDev );
+ if (pGuard)
+ pGuard->SetPaintRect(tools::Rectangle(maDispPt, maDispSz));
+
+ if (xOldClip)
+ pRenderContext->SetClipRegion(*xOldClip);
+}
+
+void AnimationRenderer::draw( sal_uLong nIndex, 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;
+ mnActIndex = std::min( nIndex, nLastPos );
+ const AnimationFrame& rAnimationFrame = mpParent->Get( static_cast<sal_uInt16>( mnActIndex ) );
+
+ getPosSize( rAnimationFrame, 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( maSizePx, false );
+ pDev->DrawOutDev( Point(), maSizePx, maDispPt, maDispSz, *pRenderContext );
+ }
+ else
+ pDev = pVDev;
+
+ // restore background after each run
+ if( !nIndex )
+ {
+ meLastDisposal = Disposal::Back;
+ maRestPt = Point();
+ maRestSz = maSizePx;
+ }
+
+ // 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 = rAnimationFrame.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, rAnimationFrame.maBitmapEx );
+
+ if( !pVDev )
+ {
+ std::optional<vcl::Region> xOldClip;
+ if (!maClip.IsNull())
+ xOldClip = pRenderContext->GetClipRegion();
+
+ if (xOldClip)
+ pRenderContext->SetClipRegion( maClip );
+
+ pRenderContext->DrawOutDev( maDispPt, maDispSz, Point(), maSizePx, *pDev );
+ if (pGuard)
+ pGuard->SetPaintRect(tools::Rectangle(maDispPt, maDispSz));
+
+ if( xOldClip)
+ {
+ pRenderContext->SetClipRegion(*xOldClip);
+ xOldClip.reset();
+ }
+
+ pDev.disposeAndClear();
+ pRenderContext->Flush();
+ }
+ }
+}
+
+void AnimationRenderer::repaint()
+{
+ const bool bOldPause = mbIsPaused;
+
+ mpRenderContext->SaveBackground(*mpBackground, maDispPt, maDispSz, maSizePx);
+
+ mbIsPaused = false;
+ drawToIndex( mnActIndex );
+ mbIsPaused = bOldPause;
+}
+
+AnimationData* AnimationRenderer::createAnimationData() const
+{
+ AnimationData* pDataItem = new AnimationData;
+
+ pDataItem->maOriginStartPt = maOriginPt;
+ pDataItem->maStartSize = maLogicalSize;
+ pDataItem->mpRenderContext = mpRenderContext;
+ pDataItem->mpRendererData = const_cast<AnimationRenderer *>(this);
+ pDataItem->mnRendererId = mnRendererId;
+ pDataItem->mbIsPaused = mbIsPaused;
+
+ return pDataItem;
+}
+
+/* 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 0000000000..52a4ab0f50
--- /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 0000000000..4166ae0845
--- /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 0000000000..c8f6a1ac7e
--- /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 0000000000..eb79752beb
--- /dev/null
+++ b/vcl/source/app/IconThemeSelector.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/.
+ */
+
+#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, bool bPreferDarkIconTheme)
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ if (!bPreferDarkIconTheme)
+ return "colibre";
+ else
+ return "colibre_dark";
+ }
+
+#ifdef _WIN32
+ (void)desktopEnvironment;
+ if (!bPreferDarkIconTheme)
+ return "colibre";
+ else
+ return "colibre_dark";
+#else
+ OUString r;
+ if ( desktopEnvironment.equalsIgnoreAsciiCase("plasma5") ||
+ desktopEnvironment.equalsIgnoreAsciiCase("plasma6") ||
+ desktopEnvironment.equalsIgnoreAsciiCase("lxqt") ) {
+ if (!bPreferDarkIconTheme)
+ r = "breeze";
+ else
+ r = "breeze_dark";
+ }
+ else if ( desktopEnvironment.equalsIgnoreAsciiCase("macosx") ) {
+ if (!bPreferDarkIconTheme)
+ r = "sukapura";
+ else
+ r = "sukapura_dark";
+ }
+ else if ( desktopEnvironment.equalsIgnoreAsciiCase("gnome") ||
+ desktopEnvironment.equalsIgnoreAsciiCase("mate") ||
+ desktopEnvironment.equalsIgnoreAsciiCase("unity") ) {
+ if (!bPreferDarkIconTheme)
+ r = "elementary";
+ else
+ r = "sifr_dark";
+ } else
+ {
+ if (!bPreferDarkIconTheme)
+ r = FALLBACK_LIGHT_ICON_THEME_ID;
+ else
+ r = FALLBACK_DARK_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;
+ }
+ }
+
+ OUString themeForDesktop = GetIconThemeForDesktopEnvironment(desktopEnvironment, mPreferDarkIconTheme);
+ 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() ? IconThemeInfo::HIGH_CONTRAST_ID_DARK
+ : 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_LIGHT_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 0000000000..4ab1608d53
--- /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::Concat2View(OUString::Concat(rBaseDir) + "/" LIBO_ETC_FOLDER + rName), rBitmap);
+ }
+}
+
+bool Application::LoadBrandBitmap (std::u16string_view 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(OUStringChar('/') + 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, Concat2View(aBaseName + "-" + aFallback + aPng), rBitmap))
+ return true;
+ }
+
+ return tryLoadPng( aBaseDir, Concat2View(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 0000000000..675373aa5f
--- /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 OUString& 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 0000000000..cfeeeaf5d9
--- /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 0000000000..bf0e897dfc
--- /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 0000000000..bbdb0ea3c6
--- /dev/null
+++ b/vcl/source/app/help.cxx
@@ -0,0 +1,686 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <comphelper/diagnose_ex.hxx>
+#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;
+ Point aScreenPos = nStyle & QuickHelpFlags::NoAutoPos
+ ? Point()
+ : pParent->OutputToScreenPixel(pParent->GetPointerPosPixel());
+ ImplShowHelpWindow( pParent, nHelpWinStyle, nStyle,
+ rHelpText, aScreenPos, 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 );
+ if (IsVisible())
+ PaintImmediately();
+}
+
+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 )
+ {
+ ResetHideTimer();
+ 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 HelpTextWindow::ResetHideTimer()
+{
+ if (mnHelpWinStyle == HELPWINSTYLE_QUICK)
+ {
+ // start auto-hide-timer for non-ShowTip windows
+ if (this == ImplGetSVHelpData().mpHelpWin)
+ maHideTimer.Start();
+ }
+}
+
+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;
+
+ bool bNoDelay = false;
+ if (VclPtr<HelpTextWindow> pHelpWin = aHelpData.mpHelpWin)
+ {
+ 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();
+ }
+ pHelpWin->ResetHideTimer(); // It is shown anew, so prolongate the hide timeout
+ return;
+ }
+
+ // 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
+ ImplDestroyHelpWindow( bWasVisible );
+ }
+
+ if (rHelpText.isEmpty())
+ return;
+
+ VclPtr<HelpTextWindow> 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 (!bNoDelay)
+ {
+ if ( !aHelpData.mbRequestingHelp )
+ {
+ bNoDelay = true;
+ }
+ else
+ {
+ sal_uInt64 nCurTime = tools::Time::GetSystemTicks();
+ if ( ( nCurTime - aHelpData.mnLastHelpHideTime ) < o3tl::make_unsigned(HelpSettings::GetTipDelay()) )
+ 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 )
+{
+ AbsoluteScreenPixelPoint aPos;
+ AbsoluteScreenPixelSize aSz( pHelpWin->GetSizePixel() );
+ AbsoluteScreenPixelRectangle aScreenRect = pHelpWin->ImplGetFrameWindow()->GetDesktopRectPixel();
+ vcl::Window* pWindow = pHelpWin->GetParent()->ImplGetFrameWindow();
+ // get mouse screen coords
+ AbsoluteScreenPixelPoint aMousePos(pWindow->OutputToAbsoluteScreenPixel(pWindow->GetPointerPosPixel()));
+
+ if ( nStyle & QuickHelpFlags::NoAutoPos )
+ {
+ // convert help area to screen coords
+ AbsoluteScreenPixelRectangle devHelpArea(
+ pWindow->OutputToAbsoluteScreenPixel( rHelpArea.TopLeft() ),
+ pWindow->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) );
+ }
+ else
+ {
+ aPos = pWindow->OutputToAbsoluteScreenPixel(rPos);
+ if ( nHelpWinStyle == HELPWINSTYLE_QUICK )
+ {
+ 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 ( 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...
+ */
+ AbsoluteScreenPixelRectangle aHelpRect( aPos, aSz );
+ if( aHelpRect.Contains( aMousePos ) )
+ {
+ AbsoluteScreenPixelPoint delta(2,2);
+ AbsoluteScreenPixelPoint aSize( aSz.Width(), aSz.Height() );
+ AbsoluteScreenPixelPoint aTest( aMousePos - aSize - delta );
+ if( aTest.X() > aScreenRect.Left() && aTest.Y() > aScreenRect.Top() )
+ aPos = aTest;
+ else
+ aPos = aMousePos + delta;
+ }
+ }
+
+ Point aPosOut = pWindow->AbsoluteScreenToOutputPixel( aPos );
+ pHelpWin->SetPosPixel( aPosOut );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/htmltransferable.cxx b/vcl/source/app/htmltransferable.cxx
new file mode 100644
index 0000000000..24f65fe929
--- /dev/null
+++ b/vcl/source/app/htmltransferable.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 <vcl/htmltransferable.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>
+
+using namespace ::com::sun::star;
+
+namespace vcl::unohelper
+{
+HtmlTransferable::HtmlTransferable(OString sData)
+ : data(sData)
+{
+}
+
+HtmlTransferable::~HtmlTransferable() {}
+
+// css::uno::XInterface
+uno::Any HtmlTransferable::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 HtmlTransferable::getTransferData(const datatransfer::DataFlavor& rFlavor)
+{
+ SotClipboardFormatId nT = SotExchange::GetFormat(rFlavor);
+ if (nT != SotClipboardFormatId::HTML)
+ {
+ throw datatransfer::UnsupportedFlavorException();
+ }
+ size_t size = data.getLength();
+ uno::Sequence<sal_Int8> sData(size);
+ std::memcpy(sData.getArray(), data.getStr(), size);
+ return uno::Any(sData);
+}
+
+uno::Sequence<datatransfer::DataFlavor> HtmlTransferable::getTransferDataFlavors()
+{
+ uno::Sequence<datatransfer::DataFlavor> aDataFlavors(1);
+ auto ref = aDataFlavors.getArray()[0];
+ ref.MimeType = "text/html";
+ ref.DataType = cppu::UnoType<uno::Sequence<sal_Int8>>::get();
+ SotExchange::GetFormatDataFlavor(SotClipboardFormatId::HTML, aDataFlavors.getArray()[0]);
+ return aDataFlavors;
+}
+
+sal_Bool HtmlTransferable::isDataFlavorSupported(const datatransfer::DataFlavor& rFlavor)
+{
+ SotClipboardFormatId nT = SotExchange::GetFormat(rFlavor);
+ return (nT == SotClipboardFormatId::HTML);
+}
+
+} // namespace vcl::unohelper
diff --git a/vcl/source/app/i18nhelp.cxx b/vcl/source/app/i18nhelp.cxx
new file mode 100644
index 0000000000..d497cdba4f
--- /dev/null
+++ b/vcl/source/app/i18nhelp.cxx
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <utility>
+#include <vcl/i18nhelp.hxx>
+
+using namespace ::com::sun::star;
+
+vcl::I18nHelper::I18nHelper( const css::uno::Reference< css::uno::XComponentContext >& rxContext, LanguageTag aLanguageTag )
+ :
+ maLanguageTag(std::move( aLanguageTag))
+{
+ 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 0000000000..7e57565371
--- /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 0000000000..026867e99b
--- /dev/null
+++ b/vcl/source/app/salplug.cxx
@@ -0,0 +1,478 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 HAVE_FEATURE_UI
+#if USING_X11
+#define UNIX_DESKTOP_DETECT 1
+#include <unx/desktops.hxx>
+#else
+#define UNIX_DESKTOP_DETECT 0
+#endif
+#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 == "kf6" ||
+ 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 pPlasma6FallbackList[] =
+ {
+#if ENABLE_KF6
+ "kf6",
+#endif
+#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;
+ else if (desktop == DESKTOP_PLASMA6)
+ pList = pPlasma6FallbackList;
+
+ 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_KF6
+ "kf6",
+#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", "PLASMA6", "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 0000000000..c677ff56a4
--- /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 <comphelper/diagnose_ex.hxx>
+#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 0000000000..95beb907f6
--- /dev/null
+++ b/vcl/source/app/salvtables.cxx
@@ -0,0 +1,7618 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/XVclWindowPeer.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/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 <listbox.hxx>
+#include <menutogglebutton.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
+}
+
+OUString SalFrame::DumpSetPosSize(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, sal_uInt16 nFlags)
+{
+ // assuming the 4 integers normally don't have more than 4 digits, but might be negative
+ OUStringBuffer aBuffer(4 * 5 + 5);
+ if (nFlags & SAL_FRAME_POSSIZE_WIDTH)
+ aBuffer << nWidth << "x";
+ else
+ aBuffer << "?x";
+ if (nFlags & SAL_FRAME_POSSIZE_HEIGHT)
+ aBuffer << nHeight << "@(";
+ else
+ aBuffer << "?@(";
+ if (nFlags & SAL_FRAME_POSSIZE_X)
+ aBuffer << nX << ",";
+ else
+ aBuffer << "?,";
+ if (nFlags & SAL_FRAME_POSSIZE_Y)
+ aBuffer << nY << ")";
+ else
+ aBuffer << "?)";
+ return aBuffer.makeStringAndClear();
+}
+
+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() {}
+
+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(std::move(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::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()); }
+
+OUString SalInstanceWidget::get_buildable_name() const { return m_xWidget->get_id(); }
+
+void SalInstanceWidget::set_buildable_name(const OUString& rId) { return m_xWidget->set_id(rId); }
+
+void SalInstanceWidget::set_help_id(const OUString& rId) { return m_xWidget->SetHelpId(rId); }
+
+OUString 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::set_cursor_data(void* pData)
+{
+ vcl::Cursor* pCursor = static_cast<vcl::Cursor*>(pData);
+ if (!pCursor)
+ return;
+
+ m_xWidget->SetCursor(pCursor);
+}
+
+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);
+}
+
+IMPL_LINK(SalInstanceWidget, SettingsChangedHdl, VclWindowEvent&, rEvent, void)
+{
+ if (rEvent.GetId() != VclEventId::WindowDataChanged)
+ return;
+
+ DataChangedEvent* pData = static_cast<DataChangedEvent*>(rEvent.GetData());
+ if (pData->GetType() == DataChangedEventType::SETTINGS)
+ m_aStyleUpdatedHdl.Call(*this);
+}
+
+void SalInstanceWidget::connect_style_updated(const Link<Widget&, void>& rLink)
+{
+ if (m_aStyleUpdatedHdl.IsSet())
+ m_xWidget->RemoveEventListener(LINK(this, SalInstanceWidget, SettingsChangedHdl));
+ weld::Widget::connect_style_updated(rLink);
+ if (m_aStyleUpdatedHdl.IsSet())
+ m_xWidget->AddEventListener(LINK(this, SalInstanceWidget, SettingsChangedHdl));
+}
+
+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_aStyleUpdatedHdl.IsSet())
+ m_xWidget->RemoveEventListener(LINK(this, SalInstanceWidget, SettingsChangedHdl));
+ 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::WITH_ALPHA);
+}
+
+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, const OUString& 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, rId, 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));
+}
+OUString 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 OUString& rIdent, bool bSensitive)
+{
+ m_xMenu->EnableItem(rIdent, bSensitive);
+}
+bool SalInstanceMenu::get_sensitive(const OUString& rIdent) const
+{
+ return m_xMenu->IsItemEnabled(m_xMenu->GetItemId(rIdent));
+}
+void SalInstanceMenu::set_active(const OUString& rIdent, bool bActive)
+{
+ m_xMenu->CheckItem(rIdent, bActive);
+}
+bool SalInstanceMenu::get_active(const OUString& rIdent) const
+{
+ return m_xMenu->IsItemChecked(m_xMenu->GetItemId(rIdent));
+}
+void SalInstanceMenu::set_label(const OUString& rIdent, const OUString& rLabel)
+{
+ m_xMenu->SetItemText(m_xMenu->GetItemId(rIdent), rLabel);
+}
+OUString SalInstanceMenu::get_label(const OUString& rIdent) const
+{
+ return m_xMenu->GetItemText(m_xMenu->GetItemId(rIdent));
+}
+void SalInstanceMenu::set_visible(const OUString& 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, nInsertPos);
+}
+
+// Defines the help id of the item in a given position
+void SalInstanceMenu::set_item_help_id(const OUString& rIdent, const OUString& rHelpId)
+{
+ m_xMenu->SetHelpId(m_xMenu->GetItemId(rIdent), rHelpId);
+}
+
+void SalInstanceMenu::remove(const OUString& rId)
+{
+ m_xMenu->RemoveItem(m_xMenu->GetItemPos(m_xMenu->GetItemId(rId)));
+}
+int SalInstanceMenu::n_children() const { return m_xMenu->GetItemCount(); }
+OUString 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 OUString& rIdent, bool bSensitive)
+{
+ m_xToolBox->EnableItem(m_xToolBox->GetItemId(rIdent), bSensitive);
+}
+
+bool SalInstanceToolbar::get_item_sensitive(const OUString& rIdent) const
+{
+ return m_xToolBox->IsItemEnabled(m_xToolBox->GetItemId(rIdent));
+}
+
+void SalInstanceToolbar::set_item_visible(const OUString& rIdent, bool bVisible)
+{
+ m_xToolBox->ShowItem(m_xToolBox->GetItemId(rIdent), bVisible);
+}
+
+void SalInstanceToolbar::set_item_help_id(const OUString& rIdent, const OUString& rHelpId)
+{
+ m_xToolBox->SetHelpId(m_xToolBox->GetItemId(rIdent), rHelpId);
+}
+
+bool SalInstanceToolbar::get_item_visible(const OUString& rIdent) const
+{
+ return m_xToolBox->IsItemVisible(m_xToolBox->GetItemId(rIdent));
+}
+
+void SalInstanceToolbar::set_item_active(const OUString& rIdent, bool bActive)
+{
+ ToolBoxItemId nItemId = m_xToolBox->GetItemId(rIdent);
+ m_xToolBox->CheckItem(nItemId, bActive);
+}
+
+bool SalInstanceToolbar::get_item_active(const OUString& rIdent) const
+{
+ return m_xToolBox->IsItemChecked(m_xToolBox->GetItemId(rIdent));
+}
+
+void SalInstanceToolbar::set_menu_item_active(const OUString& rIdent, bool bActive)
+{
+ ToolBoxItemId nItemId = m_xToolBox->GetItemId(rIdent);
+ assert(m_xToolBox->GetItemBits(nItemId) & ToolBoxItemBits::DROPDOWN);
+
+ if (bActive)
+ {
+ m_sStartShowIdent = m_xToolBox->GetItemCommand(nItemId);
+ 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 OUString& rIdent) const
+{
+ ToolBoxItemId nItemId = m_xToolBox->GetItemId(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 OUString& 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(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 OUString& rIdent, weld::Menu* pMenu)
+{
+ SalInstanceMenu* pInstanceMenu = dynamic_cast<SalInstanceMenu*>(pMenu);
+
+ PopupMenu* pPopup = pInstanceMenu ? pInstanceMenu->getMenu() : nullptr;
+
+ ToolBoxItemId nId = m_xToolBox->GetItemId(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, OUString(), rId, ToolBoxItemBits::ICON_ONLY);
+}
+
+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(); }
+
+OUString SalInstanceToolbar::get_item_ident(int nIndex) const
+{
+ return m_xToolBox->GetItemCommand(m_xToolBox->GetItemId(nIndex));
+}
+
+void SalInstanceToolbar::set_item_ident(int nIndex, const OUString& rIdent)
+{
+ return m_xToolBox->SetItemCommand(m_xToolBox->GetItemId(nIndex), 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 OUString& rIdent) const
+{
+ return m_xToolBox->GetItemText(m_xToolBox->GetItemId(rIdent));
+}
+
+void SalInstanceToolbar::set_item_label(const OUString& rIdent, const OUString& rLabel)
+{
+ m_xToolBox->SetItemText(m_xToolBox->GetItemId(rIdent), rLabel);
+}
+
+void SalInstanceToolbar::set_item_icon_name(const OUString& rIdent, const OUString& rIconName)
+{
+ m_xToolBox->SetItemImage(m_xToolBox->GetItemId(rIdent), Image(StockImage::Yes, rIconName));
+}
+
+void SalInstanceToolbar::set_item_image_mirrored(const OUString& rIdent, bool bMirrored)
+{
+ m_xToolBox->SetItemImageMirrorMode(m_xToolBox->GetItemId(rIdent), bMirrored);
+}
+
+void SalInstanceToolbar::set_item_image(const OUString& rIdent,
+ const css::uno::Reference<css::graphic::XGraphic>& rIcon)
+{
+ m_xToolBox->SetItemImage(m_xToolBox->GetItemId(rIdent), Image(rIcon));
+}
+
+void SalInstanceToolbar::set_item_image(const OUString& rIdent, VirtualDevice* pDevice)
+{
+ if (pDevice)
+ m_xToolBox->SetItemImage(m_xToolBox->GetItemId(rIdent), createImage(*pDevice));
+ else
+ m_xToolBox->SetItemImage(m_xToolBox->GetItemId(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 OUString& rIdent, const OUString& rTip)
+{
+ m_xToolBox->SetQuickHelpText(m_xToolBox->GetItemId(rIdent), rTip);
+}
+
+OUString SalInstanceToolbar::get_item_tooltip_text(const OUString& rIdent) const
+{
+ return m_xToolBox->GetQuickHelpText(m_xToolBox->GetItemId(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));
+}
+
+IMPL_LINK_NOARG(SalInstanceToolbar, DropdownClick, ToolBox*, void)
+{
+ ToolBoxItemId nItemId = m_xToolBox->GetCurItemId();
+ set_menu_item_active(m_xToolBox->GetItemCommand(nItemId), 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));
+ 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::WITHOUT_ALPHA));
+ 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(); }
+
+AbsoluteScreenPixelRectangle 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 OUString& rStr)
+{
+ SystemWindow* pSysWin = dynamic_cast<SystemWindow*>(m_xWindow.get());
+ assert(pSysWin);
+ pSysWin->SetWindowState(rStr);
+}
+
+OUString SalInstanceWindow::get_window_state(vcl::WindowDataMask 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;
+}
+
+const vcl::ILibreOfficeKitNotifier* SalInstanceWindow::GetLOKNotifier()
+{
+ return m_xWindow ? m_xWindow->GetLOKNotifier() : nullptr;
+}
+
+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 OUString& 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);
+}
+
+int SalInstanceAssistant::find_page(std::u16string_view rIdent) const
+{
+ for (size_t i = 0; i < m_aAddedPages.size(); ++i)
+ {
+ if (m_aAddedPages[i]->get_id() == rIdent)
+ return i;
+ }
+ return -1;
+}
+
+int SalInstanceAssistant::find_id(int nId) const
+{
+ for (size_t i = 0; i < m_aIds.size(); ++i)
+ {
+ if (nId == m_aIds[i])
+ return i;
+ }
+ return -1;
+}
+
+SalInstanceAssistant::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);
+}
+
+int SalInstanceAssistant::get_current_page() const { return find_id(m_xWizard->GetCurLevel()); }
+
+int SalInstanceAssistant::get_n_pages() const { return m_aAddedPages.size(); }
+
+OUString SalInstanceAssistant::get_page_ident(int nPage) const
+{
+ return m_aAddedPages[nPage]->get_id();
+}
+
+OUString SalInstanceAssistant::get_current_page_ident() const
+{
+ return get_page_ident(get_current_page());
+}
+
+void SalInstanceAssistant::set_current_page(int nPage)
+{
+ 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();
+}
+
+void SalInstanceAssistant::set_current_page(const OUString& rIdent)
+{
+ int nIndex = find_page(rIdent);
+ if (nIndex == -1)
+ return;
+ set_current_page(nIndex);
+}
+
+void SalInstanceAssistant::set_page_index(const OUString& rIdent, int nNewIndex)
+{
+ 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();
+}
+
+weld::Container* SalInstanceAssistant::append_page(const OUString& rIdent)
+{
+ VclPtrInstance<TabPage> xPage(m_xWizard);
+ VclPtrInstance<VclGrid> xGrid(xPage);
+ xPage->set_id(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();
+}
+
+OUString SalInstanceAssistant::get_page_title(const OUString& rIdent) const
+{
+ int nIndex = find_page(rIdent);
+ if (nIndex == -1)
+ return OUString();
+ return m_aAddedPages[nIndex]->GetText();
+}
+
+void SalInstanceAssistant::set_page_title(const OUString& rIdent, const OUString& rTitle)
+{
+ 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();
+ }
+}
+
+void SalInstanceAssistant::set_page_sensitive(const OUString& rIdent, bool bSensitive)
+{
+ 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();
+ }
+}
+
+void SalInstanceAssistant::set_page_side_help_id(const OUString& rHelpId)
+{
+ m_xWizard->SetRoadmapHelpId(rHelpId);
+}
+
+void SalInstanceAssistant::set_page_side_image(const OUString& rImage)
+{
+ m_xWizard->SetRoadmapBitmap(createImage(rImage).GetBitmapEx());
+}
+
+SalInstanceAssistant::~SalInstanceAssistant()
+{
+ 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(); }
+};
+}
+
+void SalInstanceScrolledWindow::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);
+}
+
+SalInstanceScrolledWindow::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);
+}
+
+void SalInstanceScrolledWindow::hadjustment_configure(int value, int lower, int upper,
+ int step_increment, int page_increment,
+ int page_size)
+{
+ 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);
+}
+
+int SalInstanceScrolledWindow::hadjustment_get_value() const
+{
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.GetThumbPos();
+}
+
+void SalInstanceScrolledWindow::hadjustment_set_value(int value)
+{
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ rHorzScrollBar.SetThumbPos(value);
+ if (!m_bUserManagedScrolling)
+ m_aOrigHScrollHdl.Call(&rHorzScrollBar);
+}
+
+int SalInstanceScrolledWindow::hadjustment_get_upper() const
+{
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.GetRangeMax();
+}
+
+void SalInstanceScrolledWindow::hadjustment_set_upper(int upper)
+{
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ rHorzScrollBar.SetRangeMax(upper);
+}
+
+int SalInstanceScrolledWindow::hadjustment_get_page_size() const
+{
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.GetVisibleSize();
+}
+
+void SalInstanceScrolledWindow::hadjustment_set_page_size(int size)
+{
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.SetVisibleSize(size);
+}
+
+void SalInstanceScrolledWindow::hadjustment_set_page_increment(int size)
+{
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.SetPageSize(size);
+}
+
+void SalInstanceScrolledWindow::hadjustment_set_step_increment(int size)
+{
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.SetLineSize(size);
+}
+
+void SalInstanceScrolledWindow::set_hpolicy(VclPolicyType eHPolicy)
+{
+ 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();
+}
+
+VclPolicyType SalInstanceScrolledWindow::get_hpolicy() const
+{
+ WinBits nWinBits = m_xScrolledWindow->GetStyle();
+ if (nWinBits & WB_AUTOHSCROLL)
+ return VclPolicyType::AUTOMATIC;
+ else if (nWinBits & WB_HSCROLL)
+ return VclPolicyType::ALWAYS;
+ return VclPolicyType::NEVER;
+}
+
+void SalInstanceScrolledWindow::vadjustment_configure(int value, int lower, int upper,
+ int step_increment, int page_increment,
+ int page_size)
+{
+ 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);
+}
+
+int SalInstanceScrolledWindow::vadjustment_get_value() const
+{
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.GetThumbPos();
+}
+
+void SalInstanceScrolledWindow::vadjustment_set_value(int value)
+{
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ rVertScrollBar.SetThumbPos(value);
+ if (!m_bUserManagedScrolling)
+ m_aOrigVScrollHdl.Call(&rVertScrollBar);
+}
+
+int SalInstanceScrolledWindow::vadjustment_get_upper() const
+{
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.GetRangeMax();
+}
+
+void SalInstanceScrolledWindow::vadjustment_set_upper(int upper)
+{
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ rVertScrollBar.SetRangeMax(upper);
+}
+
+int SalInstanceScrolledWindow::vadjustment_get_lower() const
+{
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.GetRangeMin();
+}
+
+void SalInstanceScrolledWindow::vadjustment_set_lower(int lower)
+{
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ rVertScrollBar.SetRangeMin(lower);
+}
+
+int SalInstanceScrolledWindow::vadjustment_get_page_size() const
+{
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.GetVisibleSize();
+}
+
+void SalInstanceScrolledWindow::vadjustment_set_page_size(int size)
+{
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.SetVisibleSize(size);
+}
+
+void SalInstanceScrolledWindow::vadjustment_set_page_increment(int size)
+{
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.SetPageSize(size);
+}
+
+void SalInstanceScrolledWindow::vadjustment_set_step_increment(int size)
+{
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.SetLineSize(size);
+}
+
+void SalInstanceScrolledWindow::set_vpolicy(VclPolicyType eVPolicy)
+{
+ 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();
+}
+
+VclPolicyType SalInstanceScrolledWindow::get_vpolicy() const
+{
+ WinBits nWinBits = m_xScrolledWindow->GetStyle();
+ if (nWinBits & WB_AUTOVSCROLL)
+ return VclPolicyType::AUTOMATIC;
+ else if (nWinBits & WB_VSCROLL)
+ return VclPolicyType::ALWAYS;
+ return VclPolicyType::NEVER;
+}
+
+int SalInstanceScrolledWindow::get_scroll_thickness() const
+{
+ return m_xScrolledWindow->getVertScrollBar().get_preferred_size().Width();
+}
+
+void SalInstanceScrolledWindow::set_scroll_thickness(int nThickness)
+{
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ rHorzScrollBar.set_height_request(nThickness);
+ rVertScrollBar.set_width_request(nThickness);
+}
+
+void SalInstanceScrolledWindow::customize_scrollbars(const Color& rBackgroundColor,
+ const Color& rShadowColor,
+ const Color& rFaceColor)
+{
+ 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);
+}
+
+SalInstanceScrolledWindow::~SalInstanceScrolledWindow()
+{
+ 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());
+}
+
+namespace
+{
+class SalInstanceScrollbar : public SalInstanceWidget, public virtual weld::Scrollbar
+{
+private:
+ VclPtr<ScrollBar> m_xScrollBar;
+
+ DECL_LINK(ScrollHdl, ScrollBar*, void);
+
+public:
+ SalInstanceScrollbar(ScrollBar* pScrollbar, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceWidget(pScrollbar, pBuilder, bTakeOwnership)
+ , m_xScrollBar(pScrollbar)
+ {
+ m_xScrollBar->SetScrollHdl(LINK(this, SalInstanceScrollbar, ScrollHdl));
+ m_xScrollBar->EnableDrag();
+ }
+
+ virtual void adjustment_configure(int value, int lower, int upper, int step_increment,
+ int page_increment, int page_size) override
+ {
+ m_xScrollBar->SetRangeMin(lower);
+ m_xScrollBar->SetRangeMax(upper);
+ m_xScrollBar->SetLineSize(step_increment);
+ m_xScrollBar->SetPageSize(page_increment);
+ m_xScrollBar->SetThumbPos(value);
+ m_xScrollBar->SetVisibleSize(page_size);
+ }
+
+ virtual int adjustment_get_value() const override { return m_xScrollBar->GetThumbPos(); }
+
+ virtual void adjustment_set_value(int value) override { m_xScrollBar->SetThumbPos(value); }
+
+ virtual int adjustment_get_upper() const override { return m_xScrollBar->GetRangeMax(); }
+
+ virtual void adjustment_set_upper(int upper) override { m_xScrollBar->SetRangeMax(upper); }
+
+ virtual int adjustment_get_lower() const override { return m_xScrollBar->GetRangeMin(); }
+
+ virtual void adjustment_set_lower(int lower) override { m_xScrollBar->SetRangeMin(lower); }
+
+ virtual int adjustment_get_page_size() const override { return m_xScrollBar->GetVisibleSize(); }
+
+ virtual void adjustment_set_page_size(int size) override { m_xScrollBar->SetVisibleSize(size); }
+
+ virtual int adjustment_get_page_increment() const override
+ {
+ return m_xScrollBar->GetPageSize();
+ }
+
+ virtual void adjustment_set_page_increment(int size) override
+ {
+ m_xScrollBar->SetPageSize(size);
+ }
+
+ virtual int adjustment_get_step_increment() const override
+ {
+ return m_xScrollBar->GetLineSize();
+ }
+
+ virtual void adjustment_set_step_increment(int size) override
+ {
+ m_xScrollBar->SetLineSize(size);
+ }
+
+ virtual ScrollType get_scroll_type() const override { return m_xScrollBar->GetType(); }
+
+ virtual int get_scroll_thickness() const override
+ {
+ if (m_xScrollBar->GetStyle() & WB_HORZ)
+ return m_xScrollBar->get_preferred_size().Height();
+ return m_xScrollBar->get_preferred_size().Width();
+ }
+
+ virtual void set_scroll_thickness(int nThickness) override
+ {
+ if (m_xScrollBar->GetStyle() & WB_HORZ)
+ m_xScrollBar->set_height_request(nThickness);
+ else
+ m_xScrollBar->set_width_request(nThickness);
+ }
+};
+}
+
+IMPL_LINK_NOARG(SalInstanceScrollbar, ScrollHdl, ScrollBar*, void) { signal_adjustment_changed(); }
+
+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());
+}
+
+OUString SalInstanceNotebook::get_page_ident(int nPage) const
+{
+ return m_xNotebook->GetPageName(m_xNotebook->GetPageId(nPage));
+}
+
+OUString SalInstanceNotebook::get_current_page_ident() const
+{
+ return m_xNotebook->GetPageName(m_xNotebook->GetCurPageId());
+}
+
+int SalInstanceNotebook::get_page_index(const OUString& 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 OUString& 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 OUString& rIdent)
+{
+ m_xNotebook->SetCurPageId(m_xNotebook->GetPageId(rIdent));
+}
+
+void SalInstanceNotebook::remove_page(const OUString& 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 OUString& 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 OUString& rIdent) const
+{
+ return m_xNotebook->GetPageText(m_xNotebook->GetPageId(rIdent));
+}
+
+void SalInstanceNotebook::set_tab_label_text(const OUString& 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());
+}
+
+SalInstanceVerticalNotebook::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));
+}
+
+int SalInstanceVerticalNotebook::get_current_page() const
+{
+ return m_xNotebook->GetPagePos(m_xNotebook->GetCurPageId());
+}
+
+OUString SalInstanceVerticalNotebook::get_page_ident(int nPage) const
+{
+ return m_xNotebook->GetPageId(nPage);
+}
+
+OUString SalInstanceVerticalNotebook::get_current_page_ident() const
+{
+ return m_xNotebook->GetCurPageId();
+}
+
+int SalInstanceVerticalNotebook::get_page_index(const OUString& rIdent) const
+{
+ sal_uInt16 nPageIndex = m_xNotebook->GetPagePos(rIdent);
+ if (nPageIndex == TAB_PAGE_NOTFOUND)
+ return -1;
+ return nPageIndex;
+}
+
+weld::Container* SalInstanceVerticalNotebook::get_page(const OUString& rIdent) const
+{
+ 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();
+}
+
+void SalInstanceVerticalNotebook::set_current_page(int nPage)
+{
+ m_xNotebook->SetCurPageId(m_xNotebook->GetPageId(nPage));
+}
+
+void SalInstanceVerticalNotebook::set_current_page(const OUString& rIdent)
+{
+ m_xNotebook->SetCurPageId(rIdent);
+}
+
+void SalInstanceVerticalNotebook::remove_page(const OUString& rIdent)
+{
+ 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);
+}
+
+void SalInstanceVerticalNotebook::insert_page(const OUString& rIdent, const OUString& rLabel,
+ int nPos)
+{
+ 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);
+ }
+}
+
+int SalInstanceVerticalNotebook::get_n_pages() const { return m_xNotebook->GetPageCount(); }
+
+void SalInstanceVerticalNotebook::set_tab_label_text(const OUString& rIdent, const OUString& rText)
+{
+ return m_xNotebook->SetPageText(rIdent, rText);
+}
+
+OUString SalInstanceVerticalNotebook::get_tab_label_text(const OUString& rIdent) const
+{
+ return m_xNotebook->GetPageText(rIdent);
+}
+
+void SalInstanceVerticalNotebook::set_show_tabs(bool /*bShow*/)
+{
+ // if someone needs this they will have to implement it in VerticalTabControl
+ assert(false && "not implemented");
+}
+
+SalInstanceVerticalNotebook::~SalInstanceVerticalNotebook()
+{
+ 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));
+}
+
+static void set_label_wrap(Control& rWidget, bool wrap)
+{
+ WinBits nBits = rWidget.GetStyle();
+ nBits &= ~WB_WORDBREAK;
+ if (wrap)
+ nBits |= WB_WORDBREAK;
+ rWidget.SetStyle(nBits);
+ rWidget.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, nInsertPos);
+}
+
+void SalInstanceMenuButton::set_item_sensitive(const OUString& rIdent, bool bSensitive)
+{
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ pMenu->EnableItem(rIdent, bSensitive);
+}
+
+void SalInstanceMenuButton::remove_item(const OUString& 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 OUString& rIdent, bool bActive)
+{
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ pMenu->CheckItem(rIdent, bActive);
+}
+
+void SalInstanceMenuButton::set_item_label(const OUString& rIdent, const OUString& rText)
+{
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ pMenu->SetItemText(pMenu->GetItemId(rIdent), rText);
+}
+
+OUString SalInstanceMenuButton::get_item_label(const OUString& rIdent) const
+{
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ return pMenu->GetItemText(pMenu->GetItemId(rIdent));
+}
+
+void SalInstanceMenuButton::set_item_visible(const OUString& 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(); }
+};
+}
+
+IMPL_LINK(SalInstanceLinkButton, ClickHdl, FixedHyperlink&, rButton, void)
+{
+ bool bConsumed = signal_activate_link();
+ if (!bConsumed)
+ m_aOrigClickHdl.Call(rButton);
+}
+
+void SalInstanceLinkButton::set_label_wrap(bool bWrap) { ::set_label_wrap(*m_xButton, bWrap); }
+
+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; }
+
+void SalInstanceRadioButton::set_label_wrap(bool bWrap)
+{
+ ::set_label_wrap(*m_xRadioButton, bWrap);
+}
+
+SalInstanceRadioButton::~SalInstanceRadioButton()
+{
+ m_xRadioButton->SetToggleHdl(Link<::RadioButton&, void>());
+}
+
+IMPL_LINK_NOARG(SalInstanceRadioButton, ToggleHdl, ::RadioButton&, void)
+{
+ if (notify_events_disabled())
+ return;
+ signal_toggled();
+}
+
+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;
+}
+
+void SalInstanceCheckButton::set_label_wrap(bool bWrap)
+{
+ ::set_label_wrap(*m_xCheckButton, bWrap);
+}
+
+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 SalInstanceLevelBar : public SalInstanceWidget, public virtual weld::LevelBar
+{
+private:
+ VclPtr<::ProgressBar> m_xLevelBar;
+
+public:
+ SalInstanceLevelBar(::ProgressBar* pLevelBar, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceWidget(pLevelBar, pBuilder, bTakeOwnership)
+ , m_xLevelBar(pLevelBar)
+ {
+ }
+
+ virtual void set_percentage(double fPercentage) override
+ {
+ m_xLevelBar->SetValue(static_cast<sal_uInt16>(fPercentage));
+ }
+};
+}
+
+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(0xffff38); // "light yellow 1"
+ 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_BLACK); // contrast of 5.87 to the red background
+ pEntry->SetControlBackground(0xff3838); // "light red 1"
+ 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, pEntry->ItemCount());
+ 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.GetType() == SvLBoxItemType::Button
+ || rItem.GetType() == SvLBoxItemType::ContextBmp)
+ {
+ rItem.Enable(bSensitive);
+ InvalidateModelEntry(pEntry);
+ }
+ }
+ 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);
+}
+
+bool SalInstanceTreeView::do_get_sensitive(SvTreeListEntry* pEntry, int col)
+{
+ if (static_cast<size_t>(col) == pEntry->ItemCount())
+ return false;
+
+ assert(col >= 0 && o3tl::make_unsigned(col) < pEntry->ItemCount());
+ SvLBoxItem& rItem = pEntry->GetItem(col);
+ return rItem.isEnable();
+}
+
+bool SalInstanceTreeView::get_sensitive(SvTreeListEntry* pEntry, int col) const
+{
+ col = to_internal_model(col);
+ return do_get_sensitive(pEntry, col);
+}
+
+void SalInstanceTreeView::set_sensitive(int pos, bool bSensitive, int col)
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ set_sensitive(pEntry, bSensitive, col);
+}
+
+bool SalInstanceTreeView::get_sensitive(int pos, int col) const
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ return get_sensitive(pEntry, 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);
+}
+
+bool SalInstanceTreeView::get_sensitive(const weld::TreeIter& rIter, int col) const
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return get_sensitive(rVclIter.iter, 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)
+{
+ if (col == -1)
+ {
+ for (size_t nCur = 0; nCur < pEntry->ItemCount(); ++nCur)
+ {
+ SvLBoxItem& rItem = pEntry->GetItem(nCur);
+ if (rItem.GetType() == SvLBoxItemType::String)
+ {
+ static_cast<SvLBoxString&>(rItem).Emphasize(bOn);
+ InvalidateModelEntry(pEntry);
+ }
+ }
+ return;
+ }
+
+ 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);
+ 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, bool bAutoScroll)
+{
+ LclTabListBox* pTreeView
+ = !bDnDMode ? dynamic_cast<LclTabListBox*>(m_xTreeView.get()) : nullptr;
+ SvTreeListEntry* pTarget = pTreeView ? pTreeView->GetTargetAtPoint(rPos, false, bAutoScroll)
+ : 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>());
+ }
+ if (g_DragSource == this)
+ g_DragSource = nullptr;
+ 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({});
+ m_xTreeView->SetCustomRenderHdl(Link<svtree_render_args, void>());
+ m_xTreeView->SetCustomMeasureHdl(Link<svtree_measure_args, Size>());
+}
+
+IMPL_LINK(SalInstanceTreeView, TooltipHdl, SvTreeListEntry*, pEntry, OUString)
+{
+ if (pEntry && !notify_events_disabled())
+ return signal_query_tooltip(SalInstanceTreeIter(pEntry));
+
+ return {};
+}
+
+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, SvTreeListEntry*, pEntry, OUString)
+{
+ if (pEntry && !notify_events_disabled())
+ return signal_query_tooltip(SalInstanceTreeIter(pEntry));
+
+ return {};
+}
+
+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));
+}
+
+IMPL_LINK(SalInstanceIconView, DumpElemToPropertyTreeHdl, const ::IconView::json_prop_query&,
+ rQuery, bool)
+{
+ SvTreeListEntry* pEntry = std::get<1>(rQuery);
+ return m_aGetPropertyTreeElemHdl.Call(weld::json_prop_query(
+ std::get<0>(rQuery), SalInstanceTreeIter(pEntry), std::get<2>(rQuery)));
+}
+
+void SalInstanceIconView::connect_get_property_tree_elem(
+ const Link<const weld::json_prop_query&, bool>& rLink)
+{
+ weld::IconView::connect_get_property_tree_elem(rLink);
+ m_xIconView->SetDumpElemToPropertyTreeHdl(
+ LINK(this, SalInstanceIconView, DumpElemToPropertyTreeHdl));
+}
+
+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();
+}
+
+OUString SalInstanceIconView::get_text(const weld::TreeIter& rIter) const
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return SvTabListBox::GetEntryText(rVclIter.iter, 0);
+}
+
+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;
+}
+
+SalInstanceFormattedSpinButton::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));
+}
+
+void SalInstanceFormattedSpinButton::set_text(const OUString& rText)
+{
+ disable_notify_events();
+ m_xButton->SpinField::SetText(rText);
+ enable_notify_events();
+}
+
+void SalInstanceFormattedSpinButton::connect_changed(const Link<weld::Entry&, void>& rLink)
+{
+ if (!m_pFormatter) // once a formatter is set, it takes over "changed"
+ {
+ SalInstanceEntry::connect_changed(rLink);
+ return;
+ }
+ m_pFormatter->connect_changed(rLink);
+}
+
+void SalInstanceFormattedSpinButton::connect_focus_out(const Link<weld::Widget&, void>& rLink)
+{
+ if (!m_pFormatter) // once a formatter is set, it takes over "focus-out"
+ {
+ m_aLoseFocusHdl = rLink;
+ return;
+ }
+ m_pFormatter->connect_focus_out(rLink);
+}
+
+void SalInstanceFormattedSpinButton::SetFormatter(weld::EntryFormatter* pFormatter)
+{
+ m_pFormatter = pFormatter;
+ m_xButton->SetFormatter(pFormatter);
+}
+
+Formatter& SalInstanceFormattedSpinButton::GetFormatter() { return m_xButton->GetFormatter(); }
+
+SalInstanceFormattedSpinButton::~SalInstanceFormattedSpinButton()
+{
+ 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::connect_style_updated(const Link<Widget&, void>& rLink)
+{
+ weld::Widget::connect_style_updated(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;
+}
+
+AbsoluteScreenPixelPoint 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>());
+
+ // tdf#159089 dispose custom accessible here and unset for `m_xDrawingArea`
+ // rather than waiting for `m_xDrawingArea` to get disposed, to prevent
+ // unsafe use of the now potentially non-functional accessible until it
+ // gets disposed with the VclDrawingArea
+ css::uno::Reference<css::accessibility::XAccessible> xAccessible
+ = m_xDrawingArea->GetAccessible();
+ css::uno::Reference<css::lang::XComponent> xComp(xAccessible, css::uno::UNO_QUERY);
+ if (xComp.is())
+ {
+ xComp->dispose();
+ m_xDrawingArea->SetAccessible(nullptr);
+ }
+}
+
+OutputDevice& SalInstanceDrawingArea::get_ref_device() { return *m_xDrawingArea->GetOutDev(); }
+
+void SalInstanceDrawingArea::click(const Point& rPos)
+{
+ MouseEvent aEvent(rPos, 1, MouseEventModifiers::NONE, MOUSE_LEFT, 0);
+ VclPtr<VclDrawingArea> xDrawingArea(m_xDrawingArea);
+ xDrawingArea->MouseButtonDown(aEvent);
+ xDrawingArea->MouseButtonUp(aEvent);
+}
+
+void SalInstanceDrawingArea::dblclick(const Point& rPos)
+{
+ MouseEvent aEvent(rPos, 2, MouseEventModifiers::NONE, MOUSE_LEFT, 0);
+ VclPtr<VclDrawingArea> xDrawingArea(m_xDrawingArea);
+ xDrawingArea->MouseButtonDown(aEvent);
+ xDrawingArea->MouseButtonUp(aEvent);
+}
+
+void SalInstanceDrawingArea::mouse_up(const Point& rPos)
+{
+ MouseEvent aEvent(rPos, 0, MouseEventModifiers::NONE, MOUSE_LEFT, 0);
+ m_xDrawingArea->MouseButtonUp(aEvent);
+}
+
+void SalInstanceDrawingArea::mouse_down(const Point& rPos)
+{
+ MouseEvent aEvent(rPos, 0, MouseEventModifiers::NONE, MOUSE_LEFT, 0);
+ m_xDrawingArea->MouseButtonDown(aEvent);
+}
+
+void SalInstanceDrawingArea::mouse_move(const Point& rPos)
+{
+ MouseEvent aEvent(rPos, 0, MouseEventModifiers::NONE, MOUSE_LEFT, 0);
+ m_xDrawingArea->MouseMove(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 OUString&, 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, {}, rFrame, false))
+{
+}
+
+std::unique_ptr<weld::MessageDialog> SalInstanceBuilder::weld_message_dialog(const OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& id)
+{
+ ::ProgressBar* pProgress = m_xBuilder->get<::ProgressBar>(id);
+ return pProgress ? std::make_unique<SalInstanceProgressBar>(pProgress, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::LevelBar> SalInstanceBuilder::weld_level_bar(const OUString& id)
+{
+ ::ProgressBar* pLevel = m_xBuilder->get<::ProgressBar>(id);
+ return pLevel ? std::make_unique<SalInstanceLevelBar>(pLevel, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::Spinner> SalInstanceBuilder::weld_spinner(const OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& containerid, const OUString& entryid,
+ const OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& 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 OUString& id)
+{
+ ToolBox* pToolBox = m_xBuilder->get<ToolBox>(id);
+ return pToolBox ? std::make_unique<SalInstanceToolbar>(pToolBox, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::Scrollbar> SalInstanceBuilder::weld_scrollbar(const OUString& id)
+{
+ ScrollBar* pScrollbar = m_xBuilder->get<ScrollBar>(id);
+ return pScrollbar ? std::make_unique<SalInstanceScrollbar>(pScrollbar, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::SizeGroup> SalInstanceBuilder::create_size_group()
+{
+ return std::make_unique<SalInstanceSizeGroup>();
+}
+
+OUString SalInstanceBuilder::get_current_page_help_id() const
+{
+ vcl::Window* pCtrl = m_xBuilder->get("tabcontrol");
+ if (!pCtrl)
+ return {};
+ 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 {};
+}
+
+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();
+ OUString 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"))
+ {
+ OUString 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(sHelpId, 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 OUString&)>& 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 0000000000..e6893055f6
--- /dev/null
+++ b/vcl/source/app/scheduler.cxx
@@ -0,0 +1,667 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <comphelper/diagnose_ex.hxx>
+#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 || (!rSchedCtx.mpSchedulerStack->mpTask && !rSchedCtx.mpSchedulerStack->mpNext) );
+
+ 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(), "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)
+ {
+ // Related: tdf#152703 Eliminate potential blocking during live resize
+ // Only higher priority tasks need to be fired to redraw the window
+ // so skip firing potentially long-running tasks, such as the Writer
+ // idle layout timer, when a window is in live resize
+ if ( ImplGetSVData()->mpWinData->mbIsLiveResize && nTaskPriority == static_cast<int>(TaskPriority::LOWEST) )
+ continue;
+
+ 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;
+ }
+
+ 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() );
+
+ 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;
+
+ bool bIsHighPriorityIdle = pMostUrgent->mePriority >= TaskPriority::HIGH_IDLE;
+
+ // invoke the task
+ Unlock();
+
+ // 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.
+ bool bDelayInvoking = bIsHighPriorityIdle &&
+ Application::AnyInput( VclInputFlags::MOUSE | VclInputFlags::KEYBOARD | VclInputFlags::PAINT );
+
+ /*
+ * 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
+ {
+ if (bDelayInvoking)
+ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks()
+ << " idle priority task " << pTask->GetDebugName()
+ << " delayed, system events pending" );
+ else
+ {
+ // prepare Scheduler object for deletion after handling
+ pTask->SetDeletionFlags();
+ 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 0000000000..d61902fcf6
--- /dev/null
+++ b/vcl/source/app/session.cxx
@@ -0,0 +1,416 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <cppuhelper/supportsservice.hxx>
+
+#include <comphelper/diagnose_ex.hxx>
+#include <utility>
+#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/lang/XServiceInfo.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, css::lang::XServiceInfo >
+{
+ struct Listener
+ {
+ css::uno::Reference< XSessionManagerListener > m_xListener;
+ bool m_bInteractionRequested;
+ bool m_bInteractionDone;
+ bool m_bSaveDone;
+
+ explicit Listener( css::uno::Reference< XSessionManagerListener > xListener )
+ : m_xListener(std::move( 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;
+
+ OUString SAL_CALL getImplementationName() override {
+ return "com.sun.star.frame.VCLSessionManagerClient";
+ }
+
+ sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+ css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override {
+ return {"com.sun.star.frame.SessionManagerClient"};
+ }
+
+ void SAL_CALL disposing() override;
+
+ void callSaveRequested( bool bShutdown );
+ void callShutdownCancelled();
+ void callInteractionGranted( bool bGranted );
+ void callQuit();
+
+public:
+ VCLSession();
+};
+
+}
+
+VCLSession::VCLSession()
+ : WeakComponentImplHelper( 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() );
+
+ std::erase_if(m_aListeners, [&](Listener& listener) {return xListener == listener.m_xListener;});
+}
+
+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 )
+ {
+ if (m_xSession)
+ 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(getXWeak());
+ 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 0000000000..a1138de24c
--- /dev/null
+++ b/vcl/source/app/settings.cxx
@@ -0,0 +1,3378 @@
+/* -*- 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>
+
+#include <salframe.hxx>
+#include <svdata.hxx>
+
+using namespace ::com::sun::star;
+
+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::FocusOnly;
+};
+
+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 maGroupTextColor;
+ Color maHelpColor;
+ Color maHelpTextColor;
+ Color maAccentColor;
+ 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 maListBoxWindowBackgroundColor;
+ Color maListBoxWindowTextColor;
+ Color maListBoxWindowHighlightColor;
+ Color maListBoxWindowHighlightTextColor;
+ 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 ),
+ maGroupTextColor( rData.maGroupTextColor ),
+ maHelpColor( rData.maHelpColor ),
+ maHelpTextColor( rData.maHelpTextColor ),
+ maAccentColor( rData.maAccentColor ),
+ 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 ),
+ maListBoxWindowBackgroundColor( rData.maListBoxWindowBackgroundColor ),
+ maListBoxWindowTextColor( rData.maListBoxWindowTextColor ),
+ maListBoxWindowHighlightColor( rData.maListBoxWindowHighlightColor ),
+ maListBoxWindowHighlightTextColor( rData.maListBoxWindowHighlightTextColor ),
+ 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;
+ maListBoxWindowBackgroundColor = COL_WHITE;
+ maListBoxWindowTextColor = COL_BLACK;
+ maListBoxWindowHighlightColor = COL_BLUE;
+ maListBoxWindowHighlightTextColor = COL_WHITE;
+ maMenuBarTextColor = COL_BLACK;
+ maMenuBarRolloverTextColor = COL_WHITE;
+ maMenuBarHighlightTextColor = COL_WHITE;
+ maMenuHighlightColor = COL_BLUE;
+ maMenuHighlightTextColor = COL_WHITE;
+ maAccentColor = COL_RED;
+ 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;
+ 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::SetAccentColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maAccentColor = rColor;
+}
+
+const Color&
+StyleSettings::GetAccentColor() const
+{
+ return mxData->maAccentColor;
+}
+
+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::SetListBoxWindowBackgroundColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maListBoxWindowBackgroundColor = rColor;
+}
+
+const Color&
+StyleSettings::GetListBoxWindowBackgroundColor() const
+{
+ return mxData->maListBoxWindowBackgroundColor;
+}
+
+void
+StyleSettings::SetListBoxWindowTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maListBoxWindowTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetListBoxWindowTextColor() const
+{
+ return mxData->maListBoxWindowTextColor;
+}
+
+void
+StyleSettings::SetListBoxWindowHighlightColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maListBoxWindowHighlightColor = rColor;
+}
+
+const Color&
+StyleSettings::GetListBoxWindowHighlightColor() const
+{
+ return mxData->maListBoxWindowHighlightColor;
+}
+
+void
+StyleSettings::SetListBoxWindowHighlightTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maListBoxWindowHighlightTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetListBoxWindowHighlightTextColor() const
+{
+ return mxData->maListBoxWindowHighlightTextColor;
+}
+
+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::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;
+
+ if (!rColor.IsDark())
+ {
+ mxData->maLightColor.IncreaseLuminance(64);
+ mxData->maShadowColor.DecreaseLuminance(64);
+ mxData->maDarkShadowColor.DecreaseLuminance(100);
+ }
+ else
+ {
+ mxData->maLightColor.DecreaseLuminance(64);
+ mxData->maShadowColor.IncreaseLuminance(64);
+ mxData->maDarkShadowColor.IncreaseLuminance(100);
+ }
+
+ sal_uInt8 nRed = (mxData->maLightColor.GetRed() + mxData->maShadowColor.GetRed()) / 2;
+ sal_uInt8 nGreen = (mxData->maLightColor.GetGreen() + mxData->maShadowColor.GetGreen()) / 2;
+ sal_uInt8 nBlue = (mxData->maLightColor.GetBlue() + mxData->maShadowColor.GetBlue()) / 2;
+ mxData->maCheckedColor = Color(nRed, nGreen, nBlue);
+ }
+ 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() );
+
+ // 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->maListBoxWindowBackgroundColor == rSet.mxData->maListBoxWindowBackgroundColor) &&
+ (mxData->maListBoxWindowTextColor == rSet.mxData->maListBoxWindowTextColor) &&
+ (mxData->maListBoxWindowHighlightColor == rSet.mxData->maListBoxWindowHighlightColor) &&
+ (mxData->maListBoxWindowHighlightTextColor == rSet.mxData->maListBoxWindowHighlightTextColor) &&
+ (mxData->maMenuBarTextColor == rSet.mxData->maMenuBarTextColor) &&
+ (mxData->maMenuBarRolloverTextColor == rSet.mxData->maMenuBarRolloverTextColor) &&
+ (mxData->maMenuHighlightColor == rSet.mxData->maMenuHighlightColor) &&
+ (mxData->maMenuHighlightTextColor == rSet.mxData->maMenuHighlightTextColor) &&
+ (mxData->maAccentColor == rSet.mxData->maAccentColor) &&
+ (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->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;
+
+ mxData->mnEnableATT = bEnable ? TRISTATE_TRUE : TRISTATE_FALSE;
+
+ if (getenv("LO_TESTNAME") != nullptr)
+ return; // No registry changing; no SettingsConfigItem modification
+
+ 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" ) );
+ }
+}
+#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;
+}
+
+int MiscSettings::GetDarkMode()
+{
+ return officecfg::Office::Common::Misc::Appearance::get();
+}
+
+void MiscSettings::SetDarkMode(int nMode)
+{
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Misc::Appearance::set(nMode, batch);
+ batch->commit();
+
+ vcl::Window *pWin = Application::GetFirstTopLevelWindow();
+ while (pWin)
+ {
+ pWin->ImplGetFrame()->UpdateDarkMode();
+ pWin = Application::GetNextTopLevelWindow(pWin);
+ }
+}
+
+bool MiscSettings::GetUseDarkMode()
+{
+ vcl::Window* pDefWindow = ImplGetDefaultWindow();
+ if (pDefWindow == nullptr)
+ return false;
+ return pDefWindow->ImplGetFrame()->GetUseDarkMode();
+}
+
+int MiscSettings::GetAppColorMode()
+{
+ if (utl::ConfigManager::IsFuzzing())
+ return 0;
+ return officecfg::Office::Common::Misc::ApplicationAppearance::get();
+}
+
+void MiscSettings::SetAppColorMode(int nMode)
+{
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Misc::ApplicationAppearance::set(nMode, batch);
+ batch->commit();
+}
+
+bool MiscSettings::GetUseReducedAnimation()
+{
+ vcl::Window* pDefWindow = ImplGetDefaultWindow();
+ if (pDefWindow == nullptr)
+ return false;
+ return pDefWindow->ImplGetFrame()->GetUseReducedAnimation();
+}
+
+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 0000000000..024ba9ebcd
--- /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 0000000000..bef130dc7d
--- /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 0000000000..e1e12dbc3e
--- /dev/null
+++ b/vcl/source/app/svapp.cxx
@@ -0,0 +1,1786 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <config_version.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 <tools/json_writer.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 <graphic/Manager.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/threadpool.hxx>
+#include <comphelper/solarmutex.hxx>
+#include <osl/process.h>
+
+#ifdef DBG_UTIL
+#include <svl/poolitem.hxx>
+#include <svl/itemset.hxx>
+#include <svl/itempool.hxx>
+#endif
+
+#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;
+ GestureEventPan 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 GestureEventPan& 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;
+#ifdef DBG_UTIL
+ // Due to
+ // svx/source/dialog/framelinkarray.cxx
+ // class Cell final : public SfxPoolItem
+ // const Cell OBJ_CELL_NONE;
+ // being a static held SfxPoolItem which is not yet de-initialized here
+ // number often is (1), even higher with other modules loaded (like 5).
+ // These get de-allocated reliably in module-deinitializations, so this
+ // is no memory loss. These counters are more to be able to have an eye
+ // on amounts of SfxPoolItems used during office usage and to be able to
+ // detect if an error in future changes may lead to memory losses - these
+ // would show in dramatically higher numbers then immediately
+ SAL_INFO("vcl.items", "ITEM: " << getAllocatedSfxPoolItemCount() << " SfxPoolItems still allocated at shutdown");
+ SAL_INFO("vcl.items", "ITEM: " << getUsedSfxPoolItemCount() << " SfxPoolItems were incarnated during runtime");
+
+ // Same mechanism for SfxItemSet(s)
+ SAL_INFO("vcl.items", "ITEM: " << getAllocatedSfxItemSetCount() << " SfxItemSets still allocated at shutdown");
+ SAL_INFO("vcl.items", "ITEM: " << getUsedSfxItemSetCount() << " SfxItemSets were incarnated during runtime");
+
+ // Same mechanism for PoolItemHolder(s)
+ SAL_INFO("vcl.items", "ITEM: " << getAllocatedSfxPoolItemHolderCount() << " SfxPoolItemHolders still allocated at shutdown");
+ SAL_INFO("vcl.items", "ITEM: " << getUsedSfxPoolItemHolderCount() << " SfxPoolItemHolders were incarnated during runtime");
+
+ // Same mechanism for SfxPoolItem(s)directly put to a Pool
+ SAL_INFO("vcl.items", "ITEM: " << getRemainingDirectlyPooledSfxPoolItemCount() << " SfxPoolItems still directly put in Pool at shutdown (deleted @Pool destruction)");
+ SAL_INFO("vcl.items", "ITEM: " << getAllDirectlyPooledSfxPoolItemCount() << " SfxPoolItems directly put in Pool");
+
+ // Additional call to list still incarnated SfxPoolItems (under 'svl.items')
+ listAllocatedSfxPoolItems();
+
+#endif
+}
+
+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];
+}
+
+void Application::notifyWindow(vcl::LOKWindowId /*nLOKWindowId*/,
+ const OUString& /*rAction*/,
+ const std::vector<vcl::LOKPayloadItem>& /*rPayload = std::vector<LOKPayloadItem>()*/) const
+{
+ SAL_WARN("vcl", "Invoked not implemented method: Application::notifyWindow");
+}
+
+void Application::libreOfficeKitViewCallback(int nType, const OString& pPayload) const
+{
+ if (!comphelper::LibreOfficeKit::isActive())
+ return;
+
+ if (m_pCallback)
+ {
+ m_pCallback(nType, pPayload.getStr(), m_pCallbackData);
+ }
+}
+
+void Application::notifyInvalidation(tools::Rectangle const* /*pRect*/) const
+{
+}
+
+void Application::Execute()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mbInAppExecute = true;
+ pSVData->maAppData.mbAppQuit = false;
+
+ 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;
+ std::erase(rVec, rKeyListener);
+}
+
+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,
+ GestureEventPan 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 GestureEventPan 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, NotifyEventType::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, NotifyEventType::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, NotifyEventType::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()
+{
+ vcl::Window* pWindow = ImplGetDefaultWindow();
+ if (pWindow != nullptr)
+ {
+ return pWindow->GetOutDev();
+ }
+ else
+ {
+ return nullptr;
+ }
+}
+
+basegfx::SystemDependentDataManager& Application::GetSystemDependentDataManager()
+{
+ return ImplGetSystemDependentDataManager();
+}
+
+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;
+}
+
+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;
+}
+
+AbsoluteScreenPixelRectangle Application::GetScreenPosSizePixel( unsigned int nScreen )
+{
+ SalSystem* pSys = ImplGetSalSystem();
+ if (!pSys)
+ {
+ SAL_WARN("vcl", "Requesting screen size/pos for screen #" << nScreen << " failed");
+ assert(false);
+ return AbsoluteScreenPixelRectangle();
+ }
+ AbsoluteScreenPixelRectangle 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 AbsoluteScreenPixelPoint& i_rPoint, const AbsoluteScreenPixelRectangle& i_rRect )
+{
+ const AbsoluteScreenPixelPoint 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 AbsoluteScreenPixelRectangle& i_rRect )
+{
+ const unsigned int nScreens = GetScreenCount();
+ unsigned int nBestMatchScreen = 0;
+ unsigned long nOverlap = 0;
+ for( unsigned int i = 0; i < nScreens; i++ )
+ {
+ const AbsoluteScreenPixelRectangle 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
+ AbsoluteScreenPixelRectangle 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 AbsoluteScreenPixelPoint 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 AbsoluteScreenPixelRectangle 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 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 constexpr OUString aNone(u"none"_ustr);
+ 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)
+{
+ if (count == 0)
+ return;
+ 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);
+}
+
+void dumpState(rtl::OStringBuffer &rState)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if (!pSVData)
+ return;
+
+ rState.append("\nWindows:\t");
+ rState.append(static_cast<sal_Int32>(Application::GetTopWindowCount()));
+
+ vcl::Window *pWin = Application::GetFirstTopLevelWindow();
+ while (pWin)
+ {
+ tools::JsonWriter props;
+ pWin->DumpAsPropertyTree(props);
+
+ rState.append("\n\tWindow: ");
+ rState.append(props.finishAndGetAsOString());
+
+ pWin = Application::GetNextTopLevelWindow( pWin );
+ }
+
+ vcl::graphic::Manager::get().dumpState(rState);
+
+ pSVData->dumpState(rState);
+}
+
+void trimMemory(int nTarget)
+{
+ if (nTarget >= 1000)
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+ if (!pSVData) // shutting down
+ return;
+ pSVData->dropCaches();
+ vcl::graphic::Manager::get().dropCache();
+ // TODO: ideally - free up any deeper dirtied thread stacks.
+ // comphelper::ThreadPool::getSharedOptimalPool().shutdown();
+ }
+ // else for now caches re-fill themselves as/when used.
+}
+
+} // 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 0000000000..e919bfda7c
--- /dev/null
+++ b/vcl/source/app/svdata.cxx
@@ -0,0 +1,534 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <comphelper/diagnose_ex.hxx>
+#include <o3tl/string_view.hxx>
+#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 = std::size(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::u16string_view rEnglishMetricString)
+ {
+ sal_uInt32 nUnits = std::size(SV_FUNIT_STRINGS);
+ for (sal_uInt32 i = 0; i < nUnits; ++i)
+ {
+ if (o3tl::equalsAscii(rEnglishMetricString, SV_FUNIT_STRINGS[i].first.getId()))
+ 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();
+}
+
+void ImplSVData::dropCaches()
+{
+ // we are iterating over a map and doing erase while inside a loop which is doing erase
+ // hence we can't use clear() here
+ maGDIData.maScaleCache.remove_if([](const lru_scale_cache::key_value_pair_t&)
+ { return true; });
+
+ maGDIData.maThemeDrawCommandsCache.clear();
+ maGDIData.maThemeImageCache.clear();
+}
+
+void ImplSVData::dumpState(rtl::OStringBuffer &rState)
+{
+ rState.append("\nScaleCache:\t");
+ rState.append(static_cast<sal_Int32>(maGDIData.maScaleCache.size()));
+ rState.append("\t items:");
+
+ for (auto it = maGDIData.maScaleCache.begin();
+ it != maGDIData.maScaleCache.begin(); ++it)
+ {
+ rState.append("\n\t");
+ rState.append(static_cast<sal_Int32>(it->first.maDestSize.Width()));
+ rState.append("x");
+ rState.append(static_cast<sal_Int32>(it->first.maDestSize.Height()));
+ }
+}
+
+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 0000000000..3aa2cecf4e
--- /dev/null
+++ b/vcl/source/app/svmain.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 <sal/config.h>
+#include <sal/log.hxx>
+
+#include <cassert>
+
+#include <osl/file.hxx>
+#include <osl/signal.h>
+
+#include <desktop/exithelper.h>
+
+#include <comphelper/accessibleeventnotifier.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/asyncnotification.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <unotools/syslocale.hxx>
+#include <unotools/syslocaleoptions.hxx>
+#include <utility>
+#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 <systools/opensslinit.hxx>
+
+#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 <comphelper/diagnose_ex.hxx>
+
+#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();
+
+#if defined(LINUX) && !defined(SYSTEM_OPENSSL)
+ if (!bWasInitVCL)
+ {
+ OUString constexpr name(u"SSL_CERT_FILE"_ustr);
+ OUString temp;
+ if (osl_getEnvironment(name.pData, &temp.pData) == osl_Process_E_NotFound)
+ {
+ try // to point bundled OpenSSL to some system certificate file
+ { // ... this only works if the client actually calls
+ // SSL_CTX_set_default_verify_paths() or similar; e.g. python ssl.
+ char const*const path = GetCABundleFile();
+ OUString const filepath(::rtl::OStringToOUString(
+ ::std::string_view(path), osl_getThreadTextEncoding()));
+ osl_setEnvironment(name.pData, filepath.pData);
+ }
+ catch (uno::RuntimeException const& e)
+ {
+ SAL_WARN("vcl", e.Message);
+ }
+ }
+ }
+#endif
+
+ 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( css::uno::Reference< css::uno::XCurrentContext > ctx)
+ : m_xNextContext(std::move( 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 = \""
+ + OUStringToOString( pWin->GetText(), osl_getThreadTextEncoding() )
+ + "\" type = \""
+ + typeid(*pWin).name()
+ + "\", ptr = 0x"
+ + OString::number(reinterpret_cast<sal_Int64>( pWin ), 16 )
+ + "\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();
+
+#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
+
+ Scheduler::ImplDeInitScheduler();
+
+ pSVData->mpWinData->maMsgBoxImgList.clear();
+ pSVData->maCtrlData.maCheckImgList.clear();
+ pSVData->maCtrlData.maRadioImgList.clear();
+ pSVData->maCtrlData.moDisclosurePlus.reset();
+ pSVData->maCtrlData.moDisclosureMinus.reset();
+ pSVData->mpDefaultWin.disposeAndClear();
+
+#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();
+ pSVData->dropCaches();
+
+ comphelper::AccessibleEventNotifier::shutdown();
+
+ // 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 unsigned __stdcall threadmain(void* 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
+
+ hThreadID = reinterpret_cast<HANDLE>(_beginthreadex(
+ 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
+ nullptr )); // 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 0000000000..183cd41a4e
--- /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 0000000000..89b526c91f
--- /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 0000000000..575b9c7441
--- /dev/null
+++ b/vcl/source/app/unohelp2.cxx
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <utility>
+#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( OUString aText ) : maText(std::move( aText ))
+ {
+ }
+
+ 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, OString(aStream.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 0000000000..9e8bdb7d1e
--- /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 )
+{
+ std::erase(m_aListeners, rListener);
+ 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 0000000000..dc2153e639
--- /dev/null
+++ b/vcl/source/app/watchdog.cxx
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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
+
+#include <atomic>
+
+namespace
+{
+std::atomic<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 0000000000..235e3140ad
--- /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 OUString& 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 OUString& 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 OUString& rDialogId,
+ const OUString& 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 OUString& 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()));
+ AbsoluteScreenPixelRectangle aRectAbs = FloatingWindow::ImplConvertToAbsPos(&rOutWin, rRect);
+
+ vcl::Window* pWin = rOutWin.GetFrameWindow();
+
+ rRect = FloatingWindow::ImplConvertToRelPos(pWin, aRectAbs);
+ rRect.SetPos(pWin->ScreenToOutputPixel(rRect.TopLeft()));
+
+ return rOutWin.GetFrameWeld();
+}
+
+void SetPointFont(OutputDevice& rDevice, const vcl::Font& rFont, bool bUseDeviceDPI)
+{
+ auto pDefaultDevice = Application::GetDefaultDevice();
+ if (pDefaultDevice)
+ if (vcl::Window* pDefaultWindow = pDefaultDevice->GetOwnerWindow())
+ pDefaultWindow->SetPointFont(rDevice, rFont, bUseDeviceDPI);
+}
+
+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 0000000000..0398faaa49
--- /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 0000000000..d36261b2eb
--- /dev/null
+++ b/vcl/source/bitmap/BitmapAlphaClampFilter.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/bitmapex.hxx>
+#include <vcl/BitmapAlphaClampFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapAlphaClampFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ if (!rBitmapEx.IsAlpha())
+ return rBitmapEx;
+
+ AlphaMask aBitmapAlpha(rBitmapEx.GetAlphaMask());
+ {
+ BitmapScopedWriteAccess 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 ((255 - aBitmapAlphaValue.GetIndex()) > mcThreshold)
+ {
+ aBitmapAlphaValue.SetIndex(0);
+ 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 0000000000..e4285cb53a
--- /dev/null
+++ b/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx
@@ -0,0 +1,370 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/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
+{
+ BitmapScopedReadAccess& mpReadAccess;
+ BitmapScopedWriteAccess& mpWriteAccess;
+ sal_Int32 mnRadius;
+ sal_uInt8 mnOutsideVal;
+ Color maOutsideColor;
+
+ FilterSharedData(BitmapScopedReadAccess& rReadAccess, BitmapScopedWriteAccess& rWriteAccess,
+ sal_Int32 nRadius, sal_uInt8 nOutsideVal)
+ : mpReadAccess(rReadAccess)
+ , mpWriteAccess(rWriteAccess)
+ , 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(BitmapScopedReadAccess& 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(BitmapScopedWriteAccess& 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 BitmapScopedReadAccess& 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(BitmapScopedWriteAccess& 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)
+ {
+ BitmapScopedReadAccess& pReadAccess = rShared.mpReadAccess;
+ BitmapScopedWriteAccess& 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)
+ {
+ BitmapScopedReadAccess& pReadAccess = rShared.mpReadAccess;
+ BitmapScopedWriteAccess& 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();
+
+ {
+ BitmapScopedReadAccess 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);
+ }
+ {
+ BitmapScopedReadAccess 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
+ {
+ {
+ BitmapScopedReadAccess 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);
+ }
+ {
+ BitmapScopedReadAccess 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.GetAlphaMask());
+}
+
+Bitmap BitmapBasicMorphologyFilter::filter(Bitmap const& rBitmap) const
+{
+ Bitmap bitmapCopy(rBitmap);
+ ScanlineFormat nScanlineFormat;
+ {
+ BitmapScopedReadAccess 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 0000000000..9014bfe5dd
--- /dev/null
+++ b/vcl/source/bitmap/BitmapColorQuantizationFilter.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 <sal/config.h>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapColorQuantizationFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <algorithm>
+#include <cstdlib>
+
+BitmapEx BitmapColorQuantizationFilter::execute(BitmapEx const& aBitmapEx) const
+{
+ Bitmap aBitmap = aBitmapEx.GetBitmap();
+
+ if (vcl::numberOfColors(aBitmap.getPixelFormat()) <= sal_Int64(mnNewColorCount))
+ return BitmapEx(aBitmap);
+
+ BitmapScopedReadAccess pRAcc(aBitmap);
+ if (!pRAcc)
+ return BitmapEx();
+
+ auto const cappedNewColorCount = std::min(mnNewColorCount, sal_uInt16(256));
+
+ 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)
+ return BitmapEx();
+
+ 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();
+ pCountTable.reset();
+ pRAcc.reset();
+
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aSize);
+
+ return BitmapEx(aBitmap);
+}
+
+/* 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 0000000000..21e996193e
--- /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 <vcl/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] = std::clamp((nX + cR) / 2, sal_Int32(0), sal_Int32(255));
+ aMapG[nX] = std::clamp((nX + cG) / 2, sal_Int32(0), sal_Int32(255));
+ aMapB[nX] = std::clamp((nX + cB) / 2, sal_Int32(0), sal_Int32(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 0000000000..4a0d3d89a8
--- /dev/null
+++ b/vcl/source/bitmap/BitmapConvolutionMatrixFilter.cxx
@@ -0,0 +1,199 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/BitmapWriteAccess.hxx>
+
+#include <array>
+
+BitmapEx BitmapConvolutionMatrixFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const sal_Int32 nDivisor = 8;
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+ if (!pReadAcc)
+ return BitmapEx();
+
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N24_BPP);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return BitmapEx();
+
+ 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>(
+ std::clamp(nSumR / nDivisor, sal_Int32(0), sal_Int32(255))),
+ static_cast<sal_uInt8>(
+ std::clamp(nSumG / nDivisor, sal_Int32(0), sal_Int32(255))),
+ static_cast<sal_uInt8>(
+ std::clamp(nSumB / nDivisor, sal_Int32(0), sal_Int32(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();
+ pReadAcc.reset();
+
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+
+ return BitmapEx(aBitmap);
+}
+
+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 0000000000..e38c450d79
--- /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 <vcl/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());
+ BitmapScopedReadAccess 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.GetAlphaMask());
+ }
+ 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 0000000000..c9cc10b1d1
--- /dev/null
+++ b/vcl/source/bitmap/BitmapDuoToneFilter.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/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapDuoToneFilter.hxx>
+#include <vcl/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);
+ BitmapScopedReadAccess 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 0000000000..6fa5dd5c12
--- /dev/null
+++ b/vcl/source/bitmap/BitmapEmbossGreyFilter.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/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <tools/helpers.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapEmbossGreyFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <algorithm>
+
+BitmapEx BitmapEmbossGreyFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ if (!aBitmap.ImplMakeGreyscales())
+ return BitmapEx();
+
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+ if (!pReadAcc)
+ return BitmapEx();
+
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N8_BPP, &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return BitmapEx();
+
+ BitmapColor aGrey(sal_uInt8(0));
+ const sal_Int32 nWidth = pWriteAcc->Width();
+ const sal_Int32 nHeight = pWriteAcc->Height();
+ const double fAzim = toRadians(mnAzimuthAngle);
+ const double fElev = toRadians(mnElevationAngle);
+ std::vector<sal_Int32> pHMap(nWidth + 2);
+ std::vector<sal_Int32> pVMap(nHeight + 2);
+ const double nLx = cos(fAzim) * cos(fElev) * 255.0;
+ const double nLy = sin(fAzim) * cos(fElev) * 255.0;
+ const double nLz = sin(fElev) * 255.0;
+ const double nNz = 6 * 255.0 / 4;
+ const double nNzLz = nNz * nLz;
+ const sal_uInt8 cLz = FRound(std::clamp(nLz, 0.0, 255.0));
+
+ // fill mapping tables
+ pHMap[0] = 0;
+
+ for (sal_Int32 nX = 1; nX <= nWidth; nX++)
+ {
+ pHMap[nX] = nX - 1;
+ }
+
+ pHMap[nWidth + 1] = nWidth - 1;
+
+ pVMap[0] = 0;
+
+ for (sal_Int32 nY = 1; nY <= nHeight; nY++)
+ {
+ pVMap[nY] = nY - 1;
+ }
+
+ pVMap[nHeight + 1] = nHeight - 1;
+
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ sal_Int32 nGrey11 = pReadAcc->GetPixel(pVMap[nY], pHMap[0]).GetIndex();
+ sal_Int32 nGrey12 = pReadAcc->GetPixel(pVMap[nY], pHMap[1]).GetIndex();
+ sal_Int32 nGrey13 = pReadAcc->GetPixel(pVMap[nY], pHMap[2]).GetIndex();
+ sal_Int32 nGrey21 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[0]).GetIndex();
+ sal_Int32 nGrey22 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[1]).GetIndex();
+ sal_Int32 nGrey23 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[2]).GetIndex();
+ sal_Int32 nGrey31 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[0]).GetIndex();
+ sal_Int32 nGrey32 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[1]).GetIndex();
+ sal_Int32 nGrey33 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[2]).GetIndex();
+
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ const sal_Int32 nNx = nGrey11 + nGrey21 + nGrey31 - nGrey13 - nGrey23 - nGrey33;
+ const sal_Int32 nNy = nGrey31 + nGrey32 + nGrey33 - nGrey11 - nGrey12 - nGrey13;
+
+ if (!nNx && !nNy)
+ {
+ aGrey.SetIndex(cLz);
+ }
+ else if (double nDotL = nNx * nLx + nNy * nLy + nNzLz; nDotL < 0)
+ {
+ aGrey.SetIndex(0);
+ }
+ else
+ {
+ const double fGrey = nDotL / std::hypot(nNx, nNy, nNz);
+ aGrey.SetIndex(FRound(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();
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+
+ return BitmapEx(aBitmap);
+}
+
+/* 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 0000000000..40feacbf66
--- /dev/null
+++ b/vcl/source/bitmap/BitmapEx.cxx
@@ -0,0 +1,1523 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/crc.h>
+#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 <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapMaskToAlphaFilter.hxx>
+
+#include <o3tl/any.hxx>
+#include <tools/stream.hxx>
+#include <vcl/filter/PngImageWriter.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 );
+
+ 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;
+
+ assert(typeid(rMask) != typeid(AlphaMask)
+ && "If this mask is actually an AlphaMask, then it will be inverted unnecessarily "
+ "and the alpha channel will be wrong");
+
+ if( rMask.getPixelFormat() == vcl::PixelFormat::N8_BPP && rMask.HasGreyPalette8Bit() )
+ {
+ maAlphaMask = rMask;
+ maAlphaMask.Invert();
+ }
+ else if( rMask.getPixelFormat() == vcl::PixelFormat::N8_BPP )
+ {
+ BitmapEx aMaskEx(rMask);
+ BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(255));
+ aMaskEx.Invert();
+ maAlphaMask = aMaskEx.GetBitmap();
+ }
+ else
+ {
+ // convert to alpha bitmap
+ SAL_WARN( "vcl", "BitmapEx: forced mask to monochrome");
+ BitmapEx aMaskEx(rMask);
+ BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(255));
+ aMaskEx.Invert();
+ 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 ),
+ 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.CreateAlphaMask( 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();
+}
+
+void BitmapEx::ClearAlpha()
+{
+ maAlphaMask.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;
+}
+
+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 = rtl_crc32( 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( 0 );
+ }
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ maAlphaMask.Rotate( nAngle10, COL_ALPHA_TRANSPARENT );
+ }
+ else
+ {
+ bRet = maBitmap.Rotate( nAngle10, rFillColor );
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ maAlphaMask.Rotate( nAngle10, COL_ALPHA_TRANSPARENT );
+ }
+
+ 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_ALPHA_TRANSPARENT : COL_ALPHA_OPAQUE );
+ 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 )
+{
+ if( maBitmap.IsEmpty() )
+ return false;
+
+ bool bRet = maBitmap.CopyPixel( rRectDst, rRectSrc );
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ maAlphaMask.CopyPixel( rRectDst, rRectSrc );
+
+ return bRet;
+}
+
+bool BitmapEx::CopyPixel( const tools::Rectangle& rRectDst, const tools::Rectangle& rRectSrc,
+ const BitmapEx& rBmpExSrc )
+{
+ if( maBitmap.IsEmpty() )
+ return false;
+
+ if (!maBitmap.CopyPixel( rRectDst, rRectSrc, rBmpExSrc.maBitmap ))
+ return false;
+
+ if( rBmpExSrc.IsAlpha() )
+ {
+ if( IsAlpha() )
+ // cast to use the optimized AlphaMask::CopyPixel
+ maAlphaMask.CopyPixel_AlphaOptimized( rRectDst, rRectSrc, rBmpExSrc.maAlphaMask );
+ else
+ {
+ sal_uInt8 nTransparencyOpaque = 0;
+ maAlphaMask = AlphaMask(GetSizePixel(), &nTransparencyOpaque);
+ maAlphaMask.CopyPixel( rRectDst, rRectSrc, rBmpExSrc.maAlphaMask );
+ }
+ }
+ else if (IsAlpha())
+ {
+ sal_uInt8 nTransparencyOpaque = 0;
+ const AlphaMask aAlphaSrc(rBmpExSrc.GetSizePixel(), &nTransparencyOpaque);
+
+ maAlphaMask.CopyPixel( rRectDst, rRectSrc, aAlphaSrc );
+ }
+
+ return true;
+}
+
+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() )
+ maAlphaMask.Erase( 255 - rFillColor.GetAlpha() );
+ else
+ maAlphaMask.Erase( 0 );
+ }
+ }
+
+ 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
+ {
+ BitmapScopedReadAccess pRead(maAlphaMask);
+ if(pRead)
+ {
+ const BitmapColor aBitmapColor(pRead->GetPixel(nY, nX));
+ nAlpha = aBitmapColor.GetIndex();
+ }
+ }
+ return nAlpha;
+}
+
+
+Color BitmapEx::GetPixelColor(sal_Int32 nX, sal_Int32 nY) const
+{
+ BitmapScopedReadAccess pReadAccess( maBitmap );
+ assert(pReadAccess);
+
+ BitmapColor aColor = pReadAccess->GetColor(nY, nX);
+
+ if (IsAlpha())
+ {
+ AlphaMask aAlpha = GetAlphaMask();
+ BitmapScopedReadAccess pAlphaReadAccess(aAlpha);
+ aColor.SetAlpha(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)
+ {
+ BitmapScopedReadAccess xRead(rSource);
+
+ if (xRead)
+ {
+ const Size aDestinationSizePixel(aDestination.GetSizePixel());
+
+ // tdf#157795 set color to black outside of bitmap bounds
+ // Due to commit 81994cb2b8b32453a92bcb011830fcb884f22ff3,
+ // transparent areas are now black instead of white.
+ const BitmapColor aOutside(0x0, 0x0, 0x0);
+
+ 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(GetAlphaMask().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.
+ BitmapScopedReadAccess 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, GetAlphaMask());
+ }
+ 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)
+{
+ // FIXME the call sites are actually passing in transparency
+ nAlpha = 255 - nAlpha;
+ 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);
+ BitmapScopedWriteAccess 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( GetAlphaMask(), rColor );
+ maAlphaMask = Bitmap();
+ maBitmapSize = maBitmap.GetSizePixel();
+ }
+}
+
+static Bitmap DetectEdges( const Bitmap& rBmp )
+{
+ constexpr sal_uInt8 cEdgeDetectThreshold = 128;
+ const Size aSize( rBmp.GetSizePixel() );
+
+ if( ( aSize.Width() <= 2 ) || ( aSize.Height() <= 2 ) )
+ return rBmp;
+
+ Bitmap aWorkBmp( rBmp );
+
+ if( !aWorkBmp.Convert( BmpConversion::N8BitGreys ) )
+ return rBmp;
+
+ ScopedVclPtr<VirtualDevice> pVirDev(VclPtr<VirtualDevice>::Create());
+ pVirDev->SetOutputSizePixel(aSize);
+ BitmapScopedReadAccess pReadAcc(aWorkBmp);
+ if( !pReadAcc )
+ return rBmp;
+
+ 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 );
+ }
+ }
+
+ pReadAcc.reset();
+
+ Bitmap 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.Normalize();
+
+ 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;
+
+ BitmapScopedReadAccess pAcc(aWorkBmp);
+
+ 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 );
+ }
+ }
+
+ return aRetPoly;
+}
+
+void BitmapEx::ChangeColorAlpha( sal_uInt8 cIndexFrom, sal_Int8 nAlphaTo )
+{
+ AlphaMask aAlphaMask(GetAlphaMask());
+ BitmapScopedWriteAccess pAlphaWriteAccess(aAlphaMask);
+ BitmapScopedReadAccess pReadAccess(maBitmap);
+ assert( pReadAccess.get() && pAlphaWriteAccess.get() );
+ if ( !(pReadAccess.get() && pAlphaWriteAccess.get()) )
+ return;
+
+ for ( tools::Long nY = 0; nY < pReadAccess->Height(); nY++ )
+ {
+ Scanline pScanline = pAlphaWriteAccess->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 )
+ pAlphaWriteAccess->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 = GetAlphaMask();
+ BitmapScopedWriteAccess pA(aAlpha);
+ assert(pA);
+
+ if( !pA )
+ return;
+
+ sal_uLong nTrans = cTrans;
+ 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++ )
+ {
+ sal_uLong nNewTrans = nTrans + (255 - *pAScan);
+ // clamp to 255
+ nNewTrans = ( nNewTrans & 0xffffff00 ) ? 255 : nNewTrans;
+ *pAScan++ = static_cast<sal_uInt8>( 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++ )
+ {
+ sal_uLong nNewTrans = nTrans + (255 - pA->GetIndexFromData( pScanline, nX ));
+ // clamp to 255
+ nNewTrans = ( nNewTrans & 0xffffff00 ) ? 255 : nNewTrans;
+ // convert back to alpha
+ aAlphaValue.SetIndex( static_cast<sal_uInt8>(255 - nNewTrans) );
+ pA->SetPixelOnData( pScanline, nX, aAlphaValue );
+ }
+ }
+ }
+ }
+ *this = BitmapEx( GetBitmap(), aAlpha );
+}
+
+void BitmapEx::CombineMaskOr(Color maskColor, sal_uInt8 nTol)
+{
+ AlphaMask aNewMask = maBitmap.CreateAlphaMask( maskColor, nTol );
+ if ( IsAlpha() )
+ aNewMask.AlphaCombineOr( 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)
+{
+ BitmapScopedReadAccess 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();
+}
+
+void BitmapEx::DumpAsPng(const char* pFileName) const
+{
+ OUString sPath;
+ if (pFileName)
+ {
+ sPath = OUString::fromUtf8(pFileName);
+ }
+ else if (const char* pEnv = std::getenv("VCL_DUMP_BMP_PATH"))
+ {
+ sPath = OUString::fromUtf8(pEnv);
+ }
+ else
+ {
+ sPath = "file:///tmp/bitmap.png";
+ }
+ SvFileStream aStream(sPath, StreamMode::STD_READWRITE | StreamMode::TRUNC);
+ assert(aStream.good());
+ vcl::PngImageWriter aWriter(aStream);
+ aWriter.write(*this);
+}
+
+/* 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 0000000000..431211369f
--- /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 <vcl/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)
+ {
+ BitmapScopedReadAccess 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.GetAlphaMask());
+
+ 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 0000000000..6e3b1ef555
--- /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 <vcl/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();
+
+ {
+ BitmapScopedReadAccess 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);
+ }
+ {
+ BitmapScopedReadAccess 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
+ {
+ {
+ BitmapScopedReadAccess 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);
+ }
+ {
+ BitmapScopedReadAccess 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.GetAlphaMask());
+}
+
+Bitmap BitmapFilterStackBlur::filter(Bitmap const& rBitmap) const
+{
+ Bitmap bitmapCopy(rBitmap);
+ ScanlineFormat nScanlineFormat;
+ {
+ BitmapScopedReadAccess 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 0000000000..d1bc188c95
--- /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 <vcl/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);
+
+ BitmapScopedReadAccess 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 = 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>(std::clamp(aValueRed / aSum, 0.0, 255.0)),
+ static_cast<sal_uInt8>(std::clamp(aValueGreen / aSum, 0.0, 255.0)),
+ static_cast<sal_uInt8>(std::clamp(aValueBlue / aSum, 0.0, 255.0)));
+
+ 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 0000000000..817d5187c3
--- /dev/null
+++ b/vcl/source/bitmap/BitmapInfoAccess.cxx
@@ -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 .
+ */
+
+#include <vcl/BitmapInfoAccess.hxx>
+
+#include <salbmp.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+BitmapInfoAccess::BitmapInfoAccess(const AlphaMask& rBitmap, BitmapAccessMode nMode)
+ : BitmapInfoAccess(rBitmap.GetBitmap(), nMode)
+{
+}
+
+BitmapInfoAccess::BitmapInfoAccess(const 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();
+ const_cast<Bitmap&>(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;
+ const_cast<Bitmap&>(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);
+}
+
+sal_uInt16 BitmapInfoAccess::GetMatchingPaletteIndex(const BitmapColor& rBitmapColor) const
+{
+ assert(HasPalette());
+ return mpBuffer->maPalette.GetMatchingIndex(rBitmapColor);
+}
+
+/* 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 0000000000..c0c866b53d
--- /dev/null
+++ b/vcl/source/bitmap/BitmapInterpolateScaleFilter.cxx
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/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))
+ {
+ BitmapScopedReadAccess 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]
+ = std::clamp(static_cast<sal_Int32>(fTemp), sal_Int32(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 = aBitmap;
+ pWriteAcc = 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]
+ = std::clamp(static_cast<sal_Int32>(fTemp), sal_Int32(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 0000000000..a5c4f71e63
--- /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 <vcl/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);
+
+ BitmapScopedReadAccess 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.GetAlphaMask());
+}
+
+/* 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 0000000000..acae8f516e
--- /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 <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapMaskToAlphaFilter.hxx>
+
+/**
+ * Convert a 1-bit mask to an alpha bitmap
+ */
+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));
+
+ BitmapScopedReadAccess 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_ALPHA_OPAQUE;
+ else if (aBmpColor == COL_WHITE)
+ aBmpColor = COL_ALPHA_TRANSPARENT;
+ else if (aBmpColor == Color(0, 0, 1))
+ aBmpColor = COL_ALPHA_TRANSPARENT;
+ 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 0000000000..222451d0a9
--- /dev/null
+++ b/vcl/source/bitmap/BitmapMedianFilter.cxx
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/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());
+
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+ if (!pReadAcc)
+ return BitmapEx();
+
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N24_BPP);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return BitmapEx();
+
+ 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();
+ pReadAcc.reset();
+
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+
+ return BitmapEx(aBitmap);
+}
+
+/* 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 0000000000..00f7b99ef1
--- /dev/null
+++ b/vcl/source/bitmap/BitmapMonochromeFilter.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 <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapMonochromeFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapMonochromeFilter::execute(BitmapEx const& aBitmapEx) const
+{
+ Bitmap aBitmap = aBitmapEx.GetBitmap();
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+ if (!pReadAcc)
+ return BitmapEx();
+
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N8_BPP, &Bitmap::GetGreyPalette(256));
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return BitmapEx();
+
+ 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();
+ pReadAcc.reset();
+
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aSize);
+
+ return BitmapEx(aBitmap);
+}
+
+/* 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 0000000000..0f89b23b9a
--- /dev/null
+++ b/vcl/source/bitmap/BitmapMosaicFilter.cxx
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapMosaicFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ bool bRet = false;
+
+ if (mnTileWidth > 1 || mnTileHeight > 1)
+ {
+ std::optional<Bitmap> pNewBmp;
+ BitmapScopedReadAccess pReadAcc;
+ BitmapScopedWriteAccess pWriteAcc;
+
+ if (!isPalettePixelFormat(aBitmap.getPixelFormat()))
+ {
+ pReadAcc = aBitmap;
+ pWriteAcc = aBitmap;
+ }
+ else
+ {
+ pNewBmp.emplace(aBitmap.GetSizePixel(), vcl::PixelFormat::N24_BPP);
+ pReadAcc = aBitmap;
+ pWriteAcc = *pNewBmp;
+ }
+
+ 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;
+ }
+
+ pReadAcc.reset();
+ pWriteAcc.reset();
+
+ if (pNewBmp)
+ {
+ 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 0000000000..ee2cf716c9
--- /dev/null
+++ b/vcl/source/bitmap/BitmapPopArtFilter.cxx
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/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 sal_uInt16 nEntryCount = 1 << pWriteAcc->GetBitCount();
+ sal_uInt16 n = 0;
+ std::vector<PopArtEntry> aPopArtTable(nEntryCount);
+
+ for (n = 0; n < nEntryCount; n++)
+ {
+ PopArtEntry& rEntry = aPopArtTable[n];
+ rEntry.mnIndex = 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++;
+ assert(aPopArtTable[pWriteAcc->GetIndexFromData(pScanline, nX)].mnCount != 0);
+ }
+ }
+
+ // sort table
+ std::sort(aPopArtTable.begin(), aPopArtTable.end(),
+ [](const PopArtEntry& lhs, const PopArtEntry& rhs) {
+ return lhs.mnCount > rhs.mnCount;
+ });
+
+ // get last used entry
+ sal_uInt16 nFirstEntry;
+ sal_uInt16 nLastEntry = 0;
+
+ for (n = 0; n < nEntryCount; n++)
+ {
+ if (aPopArtTable[n].mnCount)
+ nLastEntry = n;
+ }
+
+ // rotate palette (one entry)
+ const BitmapColor aFirstCol(pWriteAcc->GetPaletteColor(aPopArtTable[0].mnIndex));
+
+ for (nFirstEntry = 0; nFirstEntry < nLastEntry; nFirstEntry++)
+ {
+ pWriteAcc->SetPaletteColor(
+ aPopArtTable[nFirstEntry].mnIndex,
+ pWriteAcc->GetPaletteColor(aPopArtTable[nFirstEntry + 1].mnIndex));
+ }
+
+ pWriteAcc->SetPaletteColor(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 0000000000..5dc3c944d9
--- /dev/null
+++ b/vcl/source/bitmap/BitmapReadAccess.cxx
@@ -0,0 +1,534 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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(const AlphaMask& rBitmap, BitmapAccessMode nMode)
+ : BitmapReadAccess(rBitmap.GetBitmap(), nMode)
+{
+}
+
+BitmapReadAccess::BitmapReadAccess(const 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::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::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::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 0000000000..27df45f7ba
--- /dev/null
+++ b/vcl/source/bitmap/BitmapScaleConvolutionFilter.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 <osl/diagnose.h>
+#include <tools/helpers.hxx>
+
+#include <vcl/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(std::clamp(j, sal_Int32(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;
+ }
+
+ BitmapScopedReadAccess 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 >(std::clamp< sal_Int32 >(aValueRed / aSum, 0, 255)),
+ static_cast< sal_uInt8 >(std::clamp< sal_Int32 >(aValueGreen / aSum, 0, 255)),
+ static_cast< sal_uInt8 >(std::clamp< 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;
+ }
+
+ BitmapScopedReadAccess pReadAcc(rSource);
+ if(!pReadAcc)
+ return false;
+
+ 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);
+ if(!pWriteAcc)
+ return false;
+
+ 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 >(std::clamp< sal_Int32 >(aValueRed / aSum, 0, 255)),
+ static_cast< sal_uInt8 >(std::clamp< sal_Int32 >(aValueGreen / aSum, 0, 255)),
+ static_cast< sal_uInt8 >(std::clamp< 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();
+
+ return true;
+}
+
+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 0000000000..3c844c690e
--- /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 <vcl/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] = std::clamp(sal_Int32(fTemp), sal_Int32(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");
+ }
+
+ {
+ BitmapScopedReadAccess 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 0000000000..a65cd6ee56
--- /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 <vcl/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);
+
+ BitmapScopedReadAccess pReadAccBlur(aBlur);
+ BitmapScopedReadAccess 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>(
+ std::clamp(aColor.GetRed() + (aColor.GetRed() - aColorBlur.GetRed()) * aAmount,
+ 0.0, 255.0)),
+ static_cast<sal_uInt8>(std::clamp(
+ aColor.GetGreen() + (aColor.GetGreen() - aColorBlur.GetGreen()) * aAmount, 0.0,
+ 255.0)),
+ static_cast<sal_uInt8>(std::clamp(
+ aColor.GetBlue() + (aColor.GetBlue() - aColorBlur.GetBlue()) * aAmount, 0.0,
+ 255.0)));
+
+ 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 0000000000..fdc5a8a722
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSepiaFilter.cxx
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/BitmapWriteAccess.hxx>
+
+#include <algorithm>
+
+BitmapEx BitmapSepiaFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+ if (!pReadAcc)
+ return BitmapEx();
+
+ 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)
+ return BitmapEx();
+
+ 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();
+ pReadAcc.reset();
+
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+
+ return BitmapEx(aBitmap);
+}
+
+/* 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 0000000000..76da85b3bf
--- /dev/null
+++ b/vcl/source/bitmap/BitmapShadowFilter.cxx
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/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 0000000000..4dc045be2d
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSimpleColorQuantizationFilter.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/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapSimpleColorQuantizationFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/Octree.hxx>
+
+BitmapEx BitmapSimpleColorQuantizationFilter::execute(BitmapEx const& aBitmapEx) const
+{
+ Bitmap aBitmap = aBitmapEx.GetBitmap();
+
+ if (vcl::numberOfColors(aBitmap.getPixelFormat()) <= sal_Int64(mnNewColorCount))
+ return BitmapEx(aBitmap);
+
+ Bitmap aNewBmp;
+ BitmapScopedReadAccess pRAcc(aBitmap);
+ if (!pRAcc)
+ return BitmapEx();
+
+ const sal_uInt16 nColorCount = std::min(mnNewColorCount, sal_uInt16(256));
+ auto ePixelFormat = vcl::PixelFormat::N8_BPP;
+
+ Octree aOct(*pRAcc, nColorCount);
+ const BitmapPalette& rPal = aOct.GetPalette();
+
+ aNewBmp = Bitmap(aBitmap.GetSizePixel(), ePixelFormat, &rPal);
+ BitmapScopedWriteAccess pWAcc(aNewBmp);
+ if (!pWAcc)
+ return BitmapEx();
+
+ 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();
+ pRAcc.reset();
+
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aSize);
+
+ return BitmapEx(aBitmap);
+}
+
+/* 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 0000000000..e9c135f8ec
--- /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 0000000000..c1c6822757
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSobelGreyFilter.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/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapSobelGreyFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <algorithm>
+
+BitmapEx BitmapSobelGreyFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ if (!aBitmap.ImplMakeGreyscales())
+ return BitmapEx();
+
+ BitmapScopedReadAccess pReadAcc(aBitmap);
+ if (!pReadAcc)
+ return BitmapEx();
+
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N8_BPP, &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return BitmapEx();
+
+ 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>(std::hypot(nSum1, 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();
+ pReadAcc.reset();
+
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+
+ return BitmapEx(aBitmap);
+}
+
+/* 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 0000000000..ca2485a7f4
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSolarizeFilter.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/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapSolarizeFilter.hxx>
+#include <vcl/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 0000000000..26e035e8da
--- /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)
+{
+ BitmapScopedReadAccess 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 0000000000..ee0134a900
--- /dev/null
+++ b/vcl/source/bitmap/BitmapTools.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/.
+ *
+ */
+
+#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 <comphelper/diagnose_ex.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <vcl/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, sal_Int8 nBitCount,
+ bool bReversColors, bool bReverseAlpha)
+{
+ assert(nStride >= (nWidth * nBitCount / 8));
+ assert(nBitCount == 1 || nBitCount == 8 || nBitCount == 24 || nBitCount == 32);
+
+ PixelFormat ePixelFormat;
+ if (nBitCount == 1)
+ ePixelFormat = PixelFormat::N8_BPP; // we convert 1-bit input data to 8-bit format
+ else if (nBitCount == 8)
+ ePixelFormat = PixelFormat::N8_BPP;
+ else if (nBitCount == 24)
+ ePixelFormat = PixelFormat::N24_BPP;
+ else if (nBitCount == 32)
+ ePixelFormat = PixelFormat::N32_BPP;
+ else
+ std::abort();
+ Bitmap aBmp;
+ if (nBitCount == 1)
+ {
+ BitmapPalette aBiLevelPalette { COL_BLACK, COL_WHITE };
+ aBmp = Bitmap(Size(nWidth, nHeight), PixelFormat::N8_BPP, &aBiLevelPalette);
+ }
+ else
+ aBmp = Bitmap(Size(nWidth, nHeight), ePixelFormat);
+
+ BitmapScopedWriteAccess pWrite(aBmp);
+ assert(pWrite.get());
+ if( !pWrite )
+ return BitmapEx();
+ std::optional<AlphaMask> pAlphaMask;
+ BitmapScopedWriteAccess xMaskAcc;
+ if (nBitCount == 32)
+ {
+ pAlphaMask.emplace( Size(nWidth, nHeight) );
+ xMaskAcc = *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 (nBitCount == 8)
+ col = BitmapColor( *p );
+ else 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)
+ {
+ // FIXME this parameter is badly named
+ const sal_uInt8 nValue = bReverseAlpha ? *p : 0xff - *p;
+ xMaskAcc->SetPixelOnData(pMaskScanLine, x, BitmapColor(nValue));
+ p += 4;
+ }
+ }
+ }
+ }
+ // Avoid further bitmap use with unfinished write access
+ pWrite.reset();
+ xMaskAcc.reset();
+ 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::optional<AlphaMask> pAlphaMask;
+ BitmapScopedWriteAccess xMaskAcc;
+ if (nBitCount == 32)
+ {
+ pAlphaMask.emplace( rawBitmap.maSize );
+ xMaskAcc = *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(*p));
+ p += 4;
+ }
+ }
+ }
+
+ xMaskAcc.reset();
+ pWrite.reset();
+
+ 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.
+
+ cairo_surface_t *pPixels = cairo_surface_create_similar_image(pSurface,
+ 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;
+
+ BitmapScopedWriteAccess 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, 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.GetAlphaMask().GetBitmap();
+ }
+
+ BitmapScopedReadAccess pReadAccess( aSrcBitmap );
+ BitmapScopedReadAccess pAlphaReadAccess;
+ if (rBitmap.IsAlpha())
+ pAlphaReadAccess = aSrcAlpha;
+
+ if( !pReadAccess || (!pAlphaReadAccess && 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(0) );
+ }
+ 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(0) );
+ }
+ else
+ {
+ pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(255) );
+ 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.GetAlphaMask();
+ }
+
+ {
+
+ BitmapScopedWriteAccess 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(pOld->GetIndexFromData(pScanline, x) * fFactor);
+ const sal_uInt8 aCol(basegfx::fround((fOpOld * fOpNew) * 255.0));
+
+ pOld->SetPixelOnData(pScanline, x, BitmapColor(aCol));
+ }
+ }
+ }
+ else
+ {
+ BitmapScopedReadAccess 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(pOld->GetIndexFromData(pScanline, x) * fFactor);
+ const double fOpNew(pNew->GetIndexFromData(pScanline, x) * fFactor);
+ const sal_uInt8 aCol(basegfx::fround((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.GetAlphaMask());
+ BitmapScopedReadAccess pR(fromVDev);
+ BitmapScopedWriteAccess 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 alpha (255 == no, 0 == fully transparent),
+ // so to blend these we have to multiply
+ const sal_uInt8 nCombined((nIndR * 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.GetAlphaMask().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 ] = 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 ] = 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.GetAlphaMask();
+
+ BitmapScopedReadAccess pBitmapReadAcc( aBitmap );
+ BitmapScopedReadAccess pAlphaReadAcc;
+ 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;
+
+ 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.get(), 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.get(), 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.get(), 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.get(), 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.get(), 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.get(), 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
+ }
+ }
+ }
+
+ bHasAlpha = bIsAlpha;
+
+}
+
+ uno::Sequence< sal_Int8 > CanvasExtractBitmapData(BitmapEx const & rBitmapEx, const geometry::IntegerRectangle2D& rect)
+ {
+ Bitmap aBitmap( rBitmapEx.GetBitmap() );
+ Bitmap aAlpha( rBitmapEx.GetAlphaMask().GetBitmap() );
+
+ BitmapScopedReadAccess pReadAccess( aBitmap );
+ BitmapScopedReadAccess pAlphaReadAccess;
+ if (!aAlpha.IsEmpty())
+ pAlphaReadAccess = 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++ ] = 255 - 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::N8_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())
+ {
+ // 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.
+ BitmapScopedReadAccess 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);
+ BitmapScopedWriteAccess pResultAlphaAccess(aResultAlpha);
+
+ BitmapScopedReadAccess 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(aColor.GetAlpha(), aColor.GetAlpha(), aColor.GetAlpha());
+
+ pResultBitmapAccess->SetPixelOnData(aResultScan, nX, aResultColor);
+ pResultAlphaAccess->SetPixelOnData(aResultScanAlpha, nX, aResultColorAlpha);
+ }
+ }
+ }
+ if (rInput.IsAlpha())
+ rResult = BitmapEx(aResultBitmap, rInput.GetAlphaMask());
+ 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 0000000000..610cb2cc26
--- /dev/null
+++ b/vcl/source/bitmap/BitmapWriteAccess.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 <sal/config.h>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/BitmapWriteAccess.hxx>
+#include <bitmap/bmpfast.hxx>
+
+BitmapWriteAccess::BitmapWriteAccess(Bitmap& rBitmap)
+ : BitmapReadAccess(rBitmap, BitmapAccessMode::Write)
+{
+}
+
+BitmapWriteAccess::BitmapWriteAccess(AlphaMask& 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::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 0000000000..98ad8c9fcf
--- /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 0000000000..634fc3cdd6
--- /dev/null
+++ b/vcl/source/bitmap/alpha.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/BitmapWriteAccess.hxx>
+#include <salinst.hxx>
+#include <svdata.hxx>
+#include <salbmp.hxx>
+#include <sal/log.hxx>
+#if HAVE_FEATURE_SKIA
+#include <vcl/skia/SkiaHelper.hxx>
+#endif
+
+
+AlphaMask::AlphaMask() = default;
+
+AlphaMask::AlphaMask( const Bitmap& rBitmap ) :
+ maBitmap( rBitmap )
+{
+ if ( !rBitmap.IsEmpty() )
+ maBitmap.Convert( BmpConversion::N8BitNoConversion );
+#if HAVE_FEATURE_SKIA
+ // Related tdf#156866 force snapshot of alpha mask when using Skia
+ // In release builds, tdf#156629 and tdf#156630 reappear in many
+ // cases because a BitmapInfoAccess is in a debug block. So, instead
+ // of relying on other code to a create a BitmapInfoAccess instance,
+ // create one here to force the alpha mask to handle any pending
+ // scaling and make the alpha mask immutable.
+ else if ( SkiaHelper::isVCLSkiaEnabled() )
+ BitmapInfoAccess aInfoAccess( maBitmap );
+#endif
+ assert( (IsEmpty() || maBitmap.getPixelFormat() == vcl::PixelFormat::N8_BPP) && "alpha bitmap should be 8bpp" );
+ assert( (IsEmpty() || maBitmap.HasGreyPalette8Bit()) && "alpha bitmap should have greyscale palette" );
+}
+
+AlphaMask::AlphaMask( const AlphaMask& ) = default;
+
+AlphaMask::AlphaMask( AlphaMask&& ) = default;
+
+AlphaMask::AlphaMask( const Size& rSizePixel, const sal_uInt8* pEraseTransparency )
+ : maBitmap(rSizePixel, vcl::PixelFormat::N8_BPP, &Bitmap::GetGreyPalette(256))
+{
+ if( pEraseTransparency )
+ {
+ sal_uInt8 nAlpha = 255 - *pEraseTransparency;
+ maBitmap.Erase( Color( nAlpha, nAlpha, nAlpha ) );
+ }
+ else
+ maBitmap.Erase( COL_ALPHA_OPAQUE );
+}
+
+AlphaMask::~AlphaMask() = default;
+
+AlphaMask& AlphaMask::operator=( const Bitmap& rBitmap )
+{
+ maBitmap = rBitmap;
+
+ if( !rBitmap.IsEmpty() )
+ maBitmap.Convert( BmpConversion::N8BitNoConversion );
+
+ assert( maBitmap.getPixelFormat() == vcl::PixelFormat::N8_BPP && "alpha bitmap should be 8bpp" );
+ assert( maBitmap.HasGreyPalette8Bit() && "alpha bitmap should have greyscale palette" );
+
+ return *this;
+}
+
+void AlphaMask::Erase( sal_uInt8 cTransparency )
+{
+ sal_uInt8 nAlpha = 255 - cTransparency;
+ maBitmap.Erase( Color( nAlpha, nAlpha, nAlpha ) );
+}
+
+void AlphaMask::BlendWith(const AlphaMask& rOther)
+{
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpBmp->Create(*maBitmap.ImplGetSalBitmap()) && xImpBmp->AlphaBlendWith(*rOther.maBitmap.ImplGetSalBitmap()))
+ {
+ maBitmap.ImplSetSalBitmap(xImpBmp);
+ assert( maBitmap.getPixelFormat() == vcl::PixelFormat::N8_BPP && "alpha bitmap should be 8bpp" );
+ assert( maBitmap.HasGreyPalette8Bit() && "alpha bitmap should have greyscale palette" );
+ return;
+ }
+ BitmapScopedReadAccess pOtherAcc(rOther);
+ BitmapScopedWriteAccess pAcc(*this);
+ assert (pOtherAcc && pAcc && pOtherAcc->GetBitCount() == 8 && pAcc->GetBitCount() == 8 && "cannot BlendWith this combination");
+ if (!(pOtherAcc && pAcc && pOtherAcc->GetBitCount() == 8 && pAcc->GetBitCount() == 8))
+ {
+ SAL_WARN("vcl", "cannot BlendWith this combination");
+ 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;
+ // Awkward calculation because the original used transparency, and to replicate
+ // the logic we need to translate into transparency, perform the original logic,
+ // then translate back to alpha.
+ // The original looked like:
+ // auto tmp = nGrey1 + nGrey2 - (nGrey1 * nGrey2 / 255)
+ // which, when converted to using alpha looks like
+ // auto tmp = 255 - ((255 - nGrey1) + (255 - nGrey2) - (255 - nGrey1) * (255 - nGrey2) / 255);
+ // which then simplifies to:
+ auto tmp = nGrey1 * nGrey2 / 255;
+ *scanline = static_cast<sal_uInt8>(tmp);
+ ++scanline;
+ ++otherScanline;
+ }
+ }
+ pAcc.reset();
+ assert( maBitmap.getPixelFormat() == vcl::PixelFormat::N8_BPP && "alpha bitmap should be 8bpp" );
+ assert( maBitmap.HasGreyPalette8Bit() && "alpha bitmap should have greyscale palette" );
+}
+
+bool AlphaMask::hasAlpha() const
+{
+ // no content, no alpha
+ if(IsEmpty())
+ return false;
+
+ BitmapScopedReadAccess pAcc(*this);
+ const tools::Long nHeight(pAcc->Height());
+ const tools::Long nWidth(pAcc->Width());
+
+ // no content, no alpha
+ if(0 == nHeight || 0 == nWidth)
+ return false;
+
+ for (tools::Long y = 0; y < nHeight; ++y)
+ {
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ if (255 != pAcc->GetColor(y, x).GetRed())
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool AlphaMask::AlphaCombineOr(const AlphaMask& rMask)
+{
+ BitmapScopedReadAccess pMaskAcc(rMask);
+ BitmapScopedWriteAccess pAcc(*this);
+
+ if (!pMaskAcc || !pAcc)
+ return false;
+
+ assert (pMaskAcc->GetBitCount() == 8 && pAcc->GetBitCount() == 8);
+
+ const tools::Long nWidth = std::min(pMaskAcc->Width(), pAcc->Width());
+ const tools::Long nHeight = std::min(pMaskAcc->Height(), pAcc->Height());
+
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ ConstScanline pScanlineMask = pMaskAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ if (*pScanlineMask != 255 || *pScanline != 255)
+ *pScanline = 0;
+ else
+ *pScanline = 255;
+ ++pScanline;
+ ++pScanlineMask;
+ }
+ }
+
+ return true;
+}
+
+bool AlphaMask::Invert()
+{
+ if (IsEmpty())
+ return false;
+ bool b = maBitmap.Invert();
+ assert( maBitmap.getPixelFormat() == vcl::PixelFormat::N8_BPP && "alpha bitmap should be 8bpp" );
+ assert( maBitmap.HasGreyPalette8Bit() && "alpha bitmap should have greyscale palette" );
+ return b;
+}
+
+/* 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 0000000000..53b30d0b31
--- /dev/null
+++ b/vcl/source/bitmap/bitmap.cxx
@@ -0,0 +1,1681 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <utility>
+#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 <vcl/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> pSalBitmap)
+ : mxSalBmp(std::move(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;
+
+ switch (ePixelFormat)
+ {
+ case vcl::PixelFormat::N8_BPP:
+ {
+ static const BitmapPalette aPalN8_BPP = [] {
+ BitmapPalette aPal(1 << sal_uInt16(vcl::PixelFormat::N8_BPP));
+ 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
+ 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 );
+ return aPal;
+ }();
+ if (!pPal)
+ pPal = &aPalN8_BPP;
+ break;
+ }
+ default:
+ {
+ static const BitmapPalette aPalEmpty;
+ if (!pPal || !vcl::isPalettePixelFormat(ePixelFormat))
+ pPal = &aPalEmpty;
+ break;
+ }
+ }
+
+ mxSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap();
+ mxSalBmp->Create(rSizePixel, ePixelFormat, *pPal);
+}
+
+#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:/path/ or ~/path/
+ 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 + "BitmapDump.png", *this);
+ }
+#endif
+}
+
+namespace
+{
+template <size_t N>
+constexpr std::enable_if_t<255 % (N - 1) == 0, std::array<BitmapColor, N>> getGreyscalePalette()
+{
+ const int step = 255 / (N - 1);
+ std::array<BitmapColor, N> a;
+ for (size_t i = 0; i < N; ++i)
+ a[i] = BitmapColor(i * step, i * step, i * step);
+ return a;
+}
+}
+
+const BitmapPalette& Bitmap::GetGreyPalette( int nEntries )
+{
+ // Create greyscale palette with 2, 4, 16 or 256 entries
+ switch (nEntries)
+ {
+ case 2:
+ {
+ static const BitmapPalette aGreyPalette2 = getGreyscalePalette<2>();
+ return aGreyPalette2;
+ }
+ case 4:
+ {
+ static const BitmapPalette aGreyPalette4 = getGreyscalePalette<4>();
+ return aGreyPalette4;
+ }
+ case 16:
+ {
+ static const BitmapPalette aGreyPalette16 = getGreyscalePalette<16>();
+ return aGreyPalette16;
+ }
+ case 256:
+ {
+ static const BitmapPalette aGreyPalette256 = getGreyscalePalette<256>();
+ 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 <= 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 = false;
+
+ BitmapScopedInfoAccess pIAcc(*this);
+
+ if( pIAcc )
+ {
+ bRet = pIAcc->HasPalette() && pIAcc->GetPalette().IsGreyPaletteAny();
+ }
+
+ return bRet;
+}
+
+bool Bitmap::HasGreyPalette8Bit() const
+{
+ bool bRet = false;
+ BitmapScopedInfoAccess pIAcc(*this);
+
+ if( pIAcc )
+ {
+ bRet = pIAcc->HasPalette() && pIAcc->GetPalette().IsGreyPalette8Bit();
+ }
+
+ return bRet;
+}
+
+BitmapChecksum Bitmap::GetChecksum() const
+{
+ if( !mxSalBmp )
+ return 0;
+
+ BitmapChecksum 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;
+}
+
+bool Bitmap::Crop( const tools::Rectangle& rRectPixel )
+{
+ const Size aSizePix( GetSizePixel() );
+ tools::Rectangle aRect( rRectPixel );
+
+ aRect.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( aRect.IsEmpty() || aSizePix == aRect.GetSize())
+ return false;
+
+ BitmapScopedReadAccess pReadAcc(*this);
+ if( !pReadAcc )
+ return false;
+
+ const tools::Rectangle aNewRect( Point(), aRect.GetSize() );
+ Bitmap aNewBmp(aNewRect.GetSize(), getPixelFormat(), &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if( !pWriteAcc )
+ return false;
+
+ 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();
+ pReadAcc.reset();
+
+ ReassignWithSize( aNewBmp );
+
+ return true;
+};
+
+bool Bitmap::CopyPixel( const tools::Rectangle& rRectDst,
+ const tools::Rectangle& rRectSrc )
+{
+ const Size aSizePix( GetSizePixel() );
+ tools::Rectangle aRectDst( rRectDst );
+
+ aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( aRectDst.IsEmpty() )
+ return false;
+
+ tools::Rectangle aRectSrc( rRectSrc );
+
+ aRectSrc.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( aRectSrc.IsEmpty() || ( aRectSrc == aRectDst ) )
+ return false;
+
+ BitmapScopedWriteAccess pWriteAcc(*this);
+ if( !pWriteAcc )
+ return false;
+
+ 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 ) );
+ }
+ }
+
+ return true;
+}
+
+bool Bitmap::CopyPixel( const tools::Rectangle& rRectDst,
+ const tools::Rectangle& rRectSrc, const Bitmap& rBmpSrc )
+{
+ const Size aSizePix( GetSizePixel() );
+ tools::Rectangle aRectDst( rRectDst );
+
+ aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( aRectDst.IsEmpty() )
+ return false;
+
+ if( rBmpSrc.mxSalBmp == mxSalBmp ) // if self-copy
+ return CopyPixel(rRectDst, rRectSrc);
+
+ Bitmap* pSrc = &const_cast<Bitmap&>(rBmpSrc);
+ const Size aCopySizePix( pSrc->GetSizePixel() );
+ tools::Rectangle aRectSrc( rRectSrc );
+ const sal_uInt16 nSrcBitCount = vcl::pixelFormatBitCount(rBmpSrc.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 )
+ {
+ BitmapScopedReadAccess 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() )
+ return false;
+
+ BitmapScopedReadAccess pReadAcc(*pSrc);
+ if( !pReadAcc )
+ return false;
+
+ BitmapScopedWriteAccess pWriteAcc(*this);
+ if( !pWriteAcc )
+ return false;
+
+ 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 ) );
+ }
+
+ bool bRet = ( nWidth > 0 ) && ( nHeight > 0 );
+
+ return bRet;
+}
+
+bool Bitmap::CopyPixel_AlphaOptimized( const tools::Rectangle& rRectDst, const tools::Rectangle& rRectSrc )
+{
+ assert(HasGreyPalette8Bit());
+ // 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 );
+
+ aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( aRectDst.IsEmpty() )
+ return false;
+
+ tools::Rectangle aRectSrc( rRectSrc );
+ aRectSrc.Intersection( tools::Rectangle( Point(), aSizePix ) );
+ if( aRectSrc.IsEmpty() || ( aRectSrc == aRectDst ) )
+ return false;
+
+ BitmapScopedWriteAccess pWriteAcc(*this);
+ if( !pWriteAcc )
+ return false;
+
+ 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 ) );
+ }
+ }
+
+ return true;
+}
+
+bool Bitmap::CopyPixel_AlphaOptimized( const tools::Rectangle& rRectDst, const tools::Rectangle& rRectSrc,
+ const AlphaMask& rBmpSrc )
+{
+ assert(HasGreyPalette8Bit());
+ assert(rBmpSrc.GetBitmap().HasGreyPalette8Bit());
+ // 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 );
+
+ aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( aRectDst.IsEmpty() )
+ return false;
+
+ if( rBmpSrc.GetBitmap().mxSalBmp == mxSalBmp ) // self-copy
+ return CopyPixel_AlphaOptimized(rRectDst, rRectSrc);
+
+ Bitmap* pSrc = &const_cast<Bitmap&>(rBmpSrc.GetBitmap());
+ const Size aCopySizePix( pSrc->GetSizePixel() );
+ tools::Rectangle aRectSrc( rRectSrc );
+
+ aRectSrc.Intersection( tools::Rectangle( Point(), aCopySizePix ) );
+ if( aRectSrc.IsEmpty() )
+ return false;
+
+ BitmapScopedReadAccess pReadAcc(*pSrc);
+ if( !pReadAcc )
+ return false;
+
+ BitmapScopedWriteAccess pWriteAcc(*this);
+ if( !pWriteAcc )
+ return false;
+
+ 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 ) );
+ }
+
+ bool bRet = ( nWidth > 0 ) && ( nHeight > 0 );
+
+ return bRet;
+}
+
+bool Bitmap::Expand( sal_Int32 nDX, sal_Int32 nDY, const Color* pInitColor )
+{
+ if( !nDX && !nDY )
+ return false;
+
+ const Size aSizePixel( GetSizePixel() );
+ const tools::Long nWidth = aSizePixel.Width();
+ const tools::Long nHeight = aSizePixel.Height();
+ const Size aNewSize( nWidth + nDX, nHeight + nDY );
+ BitmapScopedReadAccess pReadAcc(*this);
+ if( !pReadAcc )
+ return false;
+
+ BitmapPalette aBmpPal( pReadAcc->GetPalette() );
+ Bitmap aNewBmp(aNewSize, getPixelFormat(), &aBmpPal);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if( !pWriteAcc )
+ return false;
+
+ 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();
+ pReadAcc.reset();
+
+ ReassignWithSize(aNewBmp);
+
+ return true;
+}
+
+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()
+{
+ BitmapScopedReadAccess pReadAcc(*this);
+ if( !pReadAcc )
+ return false;
+
+ 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 )
+ return true;
+
+ const auto ePixelFormat = vcl::PixelFormat::N8_BPP;
+ Bitmap aNewBmp(GetSizePixel(), ePixelFormat, &rPal );
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if( !pWriteAcc )
+ return false;
+
+ 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();
+ pReadAcc.reset();
+
+ const MapMode aMap( maPrefMapMode );
+ const Size aSize( maPrefSize );
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+
+ return true;
+}
+
+bool Bitmap::ImplConvertUp(vcl::PixelFormat ePixelFormat, Color const * pExtColor)
+{
+ SAL_WARN_IF(ePixelFormat <= getPixelFormat(), "vcl", "New pixel format must be greater!" );
+
+ BitmapScopedReadAccess pReadAcc(*this);
+ if (!pReadAcc)
+ return false;
+
+ BitmapPalette aPalette;
+ Bitmap aNewBmp(GetSizePixel(), ePixelFormat, pReadAcc->HasPalette() ? &pReadAcc->GetPalette() : &aPalette);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return false;
+
+ 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));
+ }
+ }
+ }
+ }
+
+ const MapMode aMap(maPrefMapMode);
+ const Size aSize(maPrefSize);
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+
+ return true;
+}
+
+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 )!");
+
+ BitmapScopedReadAccess pReadAcc(*this);
+ if (!pReadAcc)
+ return false;
+
+ BitmapPalette aPalette;
+ Bitmap aNewBmp(GetSizePixel(), vcl::PixelFormat::N8_BPP, &aPalette);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return false;
+
+ 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);
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+
+ const MapMode aMap(maPrefMapMode);
+ const Size aSize(maPrefSize);
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+
+ return true;
+}
+
+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;
+ }
+ }
+
+ 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::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 void shiftColors(sal_Int32* pColorArray, const BitmapScopedReadAccess& pReadAcc)
+{
+ Scanline pScanlineRead = pReadAcc->GetScanline(0); // Why always 0?
+ for (tools::Long n = 0; n < pReadAcc->Width(); ++n)
+ {
+ const BitmapColor& rColor = pReadAcc->GetColorFromData(pScanlineRead, n);
+ *pColorArray++ = static_cast<sal_Int32>(rColor.GetBlue()) << 12;
+ *pColorArray++ = static_cast<sal_Int32>(rColor.GetGreen()) << 12;
+ *pColorArray++ = static_cast<sal_Int32>(rColor.GetRed()) << 12;
+ }
+}
+
+bool Bitmap::Dither()
+{
+ const Size aSize( GetSizePixel() );
+ if( aSize.Width() == 1 || aSize.Height() == 1 )
+ return true;
+ if( ( aSize.Width() <= 3 ) || ( aSize.Height() <= 2 ) )
+ return false;
+
+ BitmapScopedReadAccess pReadAcc(*this);
+ Bitmap aNewBmp(GetSizePixel(), vcl::PixelFormat::N8_BPP);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if( !pReadAcc || !pWriteAcc )
+ return false;
+
+ 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();
+ shiftColors(p2T, pReadAcc);
+ for( tools::Long nYAcc = 0; nYAcc < nHeight; nYAcc++ )
+ {
+ std::swap(p1T, p2T);
+ if (nYAcc < nHeight - 1)
+ shiftColors(p2T, pReadAcc);
+
+ auto CalcError = [](tools::Long n)
+ {
+ n = std::clamp<tools::Long>(n >> 12, 0, 255);
+ return std::pair(FloydErrMap[n], FloydMap[n]);
+ };
+
+ auto CalcErrors = [&](tools::Long n)
+ { return std::tuple_cat(CalcError(p1T[n]), CalcError(p1T[n + 1]), CalcError(p1T[n + 2])); };
+
+ auto CalcT = [](sal_Int32* dst, const int* src, int b, int g, int r)
+ {
+ dst[0] += src[b];
+ dst[1] += src[g];
+ dst[2] += src[r];
+ };
+
+ auto Calc1 = [&](int x, int b, int g, int r) { CalcT(p2T + x + 3, FloydError1, b, g, r); };
+ auto Calc3 = [&](int x, int b, int g, int r) { CalcT(p2T + x - 3, FloydError3, b, g, r); };
+ auto Calc5 = [&](int x, int b, int g, int r) { CalcT(p2T + x, FloydError5, b, g, r); };
+ auto Calc7 = [&](int x, int b, int g, int r) { CalcT(p1T + x + 3, FloydError7, b, g, r); };
+
+ Scanline pScanline = pWriteAcc->GetScanline(nYAcc);
+ // Examine first Pixel separately
+ {
+ auto [nBErr, nBC, nGErr, nGC, nRErr, nRC] = CalcErrors(0);
+ Calc1(0, nBErr, nGErr, nRErr);
+ Calc5(0, nBErr, nGErr, nRErr);
+ Calc7(0, nBErr, nGErr, nRErr);
+ pWriteAcc->SetPixelOnData( pScanline, 0, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
+ }
+ // Get middle Pixels using a loop
+ for ( tools::Long nX = 3, nXAcc = 1; nX < nW2; nX += 3, nXAcc++ )
+ {
+ auto [nBErr, nBC, nGErr, nGC, nRErr, nRC] = CalcErrors(nX);
+ Calc1(nX, nBErr, nGErr, nRErr);
+ Calc3(nX, nBErr, nGErr, nRErr);
+ Calc5(nX, nBErr, nGErr, nRErr);
+ Calc7(nX, nBErr, nGErr, nRErr);
+ pWriteAcc->SetPixelOnData( pScanline, nXAcc, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
+ }
+ // Treat last Pixel separately
+ {
+ auto [nBErr, nBC, nGErr, nGC, nRErr, nRC] = CalcErrors(nW2);
+ Calc3(nW2, nBErr, nGErr, nRErr);
+ Calc5(nW2, nBErr, nGErr, nRErr);
+ 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;
+}
+
+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 )
+{
+ // nothing to do => return quickly
+ if( !nLuminancePercent && !nContrastPercent &&
+ !nChannelRPercent && !nChannelGPercent && !nChannelBPercent &&
+ ( fGamma == 1.0 ) && !bInvert )
+ {
+ return true;
+ }
+
+ BitmapScopedWriteAccess pAcc(*this);
+ if( !pAcc )
+ return false;
+
+ 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 * std::clamp( nContrastPercent, short(0), short(100) ) );
+ else
+ fM = ( 128.0 + 1.27 * std::clamp( nContrastPercent, short(-100), short(0) ) ) / 128.0;
+
+ if(!msoBrightness)
+ // total offset = luminance offset + contrast offset
+ fOff = std::clamp( nLuminancePercent, short(-100), short(100) ) * 2.55 + 128.0 - fM * 128.0;
+ else
+ fOff = std::clamp( nLuminancePercent, short(-100), short(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 ] = FRound( std::clamp( nX * fM + fROff, 0.0, 255.0 ) );
+ cMapG[ nX ] = FRound( std::clamp( nX * fM + fGOff, 0.0, 255.0 ) );
+ cMapB[ nX ] = FRound( std::clamp( nX * fM + fBOff, 0.0, 255.0 ) );
+ }
+ 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 ] = FRound( std::clamp( (nX+fROff/2-128) * fM + 128 + fROff/2, 0.0, 255.0 ) );
+ cMapG[ nX ] = FRound( std::clamp( (nX+fGOff/2-128) * fM + 128 + fGOff/2, 0.0, 255.0 ) );
+ cMapB[ nX ] = FRound( std::clamp( (nX+fBOff/2-128) * fM + 128 + fBOff/2, 0.0, 255.0 ) );
+ }
+ 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();
+
+ return true;
+}
+
+namespace
+{
+inline sal_uInt8 backBlendAlpha(sal_uInt16 alpha, sal_uInt16 srcCol, sal_uInt16 startCol)
+{
+ const sal_uInt16 nAlpha((alpha * startCol) / 255);
+ if(srcCol > nAlpha)
+ {
+ return static_cast<sal_uInt8>(((srcCol - nAlpha) * 255) / (255 - nAlpha));
+ }
+
+ return 0;
+}
+}
+
+void Bitmap::RemoveBlendedStartColor(
+ const Color& rStartColor,
+ const AlphaMask& rAlphaMask)
+{
+ // no content, done
+ if(IsEmpty())
+ return;
+
+ BitmapScopedWriteAccess pAcc(*this);
+ const tools::Long nHeight(pAcc->Height());
+ const tools::Long nWidth(pAcc->Width());
+
+ // no content, done
+ if(0 == nHeight || 0 == nWidth)
+ return;
+
+ BitmapScopedReadAccess pAlphaAcc(rAlphaMask);
+
+ // inequal sizes of content and alpha, avoid change (maybe assert?)
+ if(pAlphaAcc->Height() != nHeight || pAlphaAcc->Width() != nWidth)
+ return;
+
+ // prepare local values as sal_uInt16 to avoid multiple conversions
+ const sal_uInt16 nStartColRed(rStartColor.GetRed());
+ const sal_uInt16 nStartColGreen(rStartColor.GetGreen());
+ const sal_uInt16 nStartColBlue(rStartColor.GetBlue());
+
+ for (tools::Long y = 0; y < nHeight; ++y)
+ {
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ // get alpha value
+ const sal_uInt8 nAlpha8(pAlphaAcc->GetColor(y, x).GetRed());
+
+ // not or completely transparent, no adaptation needed
+ if(0 == nAlpha8 || 255 == nAlpha8)
+ continue;
+
+ // prepare local value as sal_uInt16 to avoid multiple conversions
+ const sal_uInt16 nAlpha16(static_cast<sal_uInt16>(nAlpha8));
+
+ // get source color
+ BitmapColor aColor(pAcc->GetColor(y, x));
+
+ // modify/blend back source color
+ aColor.SetRed(backBlendAlpha(nAlpha16, static_cast<sal_uInt16>(aColor.GetRed()), nStartColRed));
+ aColor.SetGreen(backBlendAlpha(nAlpha16, static_cast<sal_uInt16>(aColor.GetGreen()), nStartColGreen));
+ aColor.SetBlue(backBlendAlpha(nAlpha16, static_cast<sal_uInt16>(aColor.GetBlue()), nStartColBlue));
+
+ // write result back
+ pAcc->SetPixel(y, x, aColor);
+ }
+ }
+}
+
+const basegfx::SystemDependentDataHolder* Bitmap::accessSystemDependentDataHolder() const
+{
+ if(!mxSalBmp)
+ return nullptr;
+ return mxSalBmp->accessSystemDependentDataHolder();
+}
+
+/* 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 0000000000..63ccd2b130
--- /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<AnimationFrame>>& 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 0000000000..cc3674ad31
--- /dev/null
+++ b/vcl/source/bitmap/bitmappaint.cxx
@@ -0,0 +1,1226 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/BitmapWriteAccess.hxx>
+#include <salbmp.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+#include <algorithm>
+#include <memory>
+
+static BitmapColor UpdatePaletteForNewColor(BitmapScopedWriteAccess& pAcc,
+ const sal_uInt16 nActColors,
+ const sal_uInt16 nMaxColors, const tools::Long nHeight,
+ const tools::Long nWidth,
+ const BitmapColor& rWantedColor);
+
+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()
+{
+ if (!mxSalBmp)
+ return false;
+
+ // try optimised call, much faster on Skia
+ if (mxSalBmp->Invert())
+ {
+ mxSalBmp->InvalidateChecksum();
+ return true;
+ }
+
+ BitmapScopedWriteAccess pWriteAcc(*this);
+ const tools::Long nWidth = pWriteAcc->Width();
+ const tools::Long nHeight = pWriteAcc->Height();
+
+ if (pWriteAcc->HasPalette())
+ {
+ const sal_uInt16 nActColors = pWriteAcc->GetPaletteEntryCount();
+ const sal_uInt16 nMaxColors = 1 << pWriteAcc->GetBitCount();
+
+ if (pWriteAcc->GetPalette().IsGreyPalette8Bit())
+ {
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ BitmapColor aBmpColor = pWriteAcc->GetPixelFromData(pScanline, nX);
+ aBmpColor.SetIndex(0xff - aBmpColor.GetIndex());
+ pWriteAcc->SetPixelOnData(pScanline, nX, aBmpColor);
+ }
+ }
+ }
+ else
+ {
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ BitmapColor aBmpColor = pWriteAcc->GetPixelFromData(pScanline, nX);
+ aBmpColor = pWriteAcc->GetPaletteColor(aBmpColor.GetIndex());
+ aBmpColor.Invert();
+ BitmapColor aReplace = UpdatePaletteForNewColor(
+ pWriteAcc, nActColors, nMaxColors, nHeight, nWidth, aBmpColor);
+ pWriteAcc->SetPixelOnData(pScanline, nX, aReplace);
+ }
+ }
+ }
+ }
+ else
+ {
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ BitmapColor aBmpColor = pWriteAcc->GetPixelFromData(pScanline, nX);
+ aBmpColor.Invert();
+ pWriteAcc->SetPixelOnData(pScanline, nX, aBmpColor);
+ }
+ }
+ }
+ mxSalBmp->InvalidateChecksum();
+
+ return true;
+}
+
+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)
+{
+ nAngle10 %= 3600_deg10;
+ nAngle10 = (nAngle10 < 0_deg10) ? (Degree10(3599) + nAngle10) : nAngle10;
+
+ if (!nAngle10)
+ return true;
+ if (nAngle10 == 1800_deg10)
+ return Mirror(BmpMirrorFlags::Horizontal | BmpMirrorFlags::Vertical);
+
+ BitmapScopedReadAccess pReadAcc(*this);
+ if (!pReadAcc)
+ return false;
+
+ Bitmap aRotatedBmp;
+ bool bRet = false;
+ 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) const
+{
+ BitmapScopedReadAccess pReadAcc(*this);
+ if (!pReadAcc)
+ return Bitmap();
+
+ // 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.
+
+ if ((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 = vcl::PixelFormat::N8_BPP;
+ Bitmap aNewBmp(GetSizePixel(), ePixelFormat, &Bitmap::GetGreyPalette(256));
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return Bitmap();
+
+ 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));
+
+ const BitmapColor aTest(pReadAcc->GetBestMatchingColor(rTransColor));
+
+ if (pWriteAcc->GetScanlineFormat() == pReadAcc->GetScanlineFormat() && aWhite.GetIndex() == 1
+ && (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 (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);
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ aNewBmp.maPrefSize = maPrefSize;
+ aNewBmp.maPrefMapMode = maPrefMapMode;
+
+ return aNewBmp;
+}
+
+Bitmap Bitmap::CreateMask(const Color& rTransColor, sal_uInt8 nTol) const
+{
+ if (nTol == 0)
+ return CreateMask(rTransColor);
+
+ BitmapScopedReadAccess pReadAcc(*this);
+ if (!pReadAcc)
+ return Bitmap();
+
+ // 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.
+
+ auto ePixelFormat = vcl::PixelFormat::N8_BPP;
+ Bitmap aNewBmp(GetSizePixel(), ePixelFormat, &Bitmap::GetGreyPalette(256));
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return Bitmap();
+
+ 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));
+
+ BitmapColor aCol;
+ tools::Long nR, nG, nB;
+ const tools::Long nMinR = std::clamp<tools::Long>(rTransColor.GetRed() - nTol, 0, 255);
+ const tools::Long nMaxR = std::clamp<tools::Long>(rTransColor.GetRed() + nTol, 0, 255);
+ const tools::Long nMinG = std::clamp<tools::Long>(rTransColor.GetGreen() - nTol, 0, 255);
+ const tools::Long nMaxG = std::clamp<tools::Long>(rTransColor.GetGreen() + nTol, 0, 255);
+ const tools::Long nMinB = std::clamp<tools::Long>(rTransColor.GetBlue() - nTol, 0, 255);
+ const tools::Long nMaxB = std::clamp<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);
+ }
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ aNewBmp.maPrefSize = maPrefSize;
+ aNewBmp.maPrefMapMode = maPrefMapMode;
+
+ return aNewBmp;
+}
+
+AlphaMask Bitmap::CreateAlphaMask(const Color& rTransColor) const
+{
+ BitmapScopedReadAccess pReadAcc(*this);
+ if (!pReadAcc)
+ return AlphaMask();
+
+ // Historically LO used 1bpp masks, but 8bpp masks are much faster,
+ // better supported by hardware, and the memory savings are not worth
+ // it anymore.
+
+ if ((pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal)
+ && pReadAcc->GetBestMatchingColor(COL_ALPHA_TRANSPARENT)
+ == 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 AlphaMask(*this);
+ }
+
+ AlphaMask aNewBmp(GetSizePixel());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return AlphaMask();
+
+ const tools::Long nWidth = pReadAcc->Width();
+ const tools::Long nHeight = pReadAcc->Height();
+ const BitmapColor aOpaqueColor(pWriteAcc->GetBestMatchingColor(COL_ALPHA_OPAQUE));
+ const BitmapColor aTransparentColor(pWriteAcc->GetBestMatchingColor(COL_ALPHA_TRANSPARENT));
+
+ const BitmapColor aTest(pReadAcc->GetBestMatchingColor(rTransColor));
+
+ if (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] = aTransparentColor.GetIndex();
+ else
+ pDst[nX] = aOpaqueColor.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, aTransparentColor);
+ else
+ pWriteAcc->SetPixelOnData(pScanline, nX, aOpaqueColor);
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ aNewBmp.SetPrefSize(maPrefSize);
+ aNewBmp.SetPrefMapMode(maPrefMapMode);
+
+ return aNewBmp;
+}
+
+AlphaMask Bitmap::CreateAlphaMask(const Color& rTransColor, sal_uInt8 nTol) const
+{
+ if (nTol == 0)
+ return CreateAlphaMask(rTransColor);
+
+ BitmapScopedReadAccess pReadAcc(*this);
+ if (!pReadAcc)
+ return AlphaMask();
+
+ // 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.
+
+ AlphaMask aNewBmp(GetSizePixel());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if (!pWriteAcc)
+ return AlphaMask();
+
+ const tools::Long nWidth = pReadAcc->Width();
+ const tools::Long nHeight = pReadAcc->Height();
+ const BitmapColor aOpaqueColor(pWriteAcc->GetBestMatchingColor(COL_ALPHA_OPAQUE));
+ const BitmapColor aTransparentColor(pWriteAcc->GetBestMatchingColor(COL_ALPHA_TRANSPARENT));
+
+ BitmapColor aCol;
+ tools::Long nR, nG, nB;
+ const tools::Long nMinR = std::clamp<tools::Long>(rTransColor.GetRed() - nTol, 0, 255);
+ const tools::Long nMaxR = std::clamp<tools::Long>(rTransColor.GetRed() + nTol, 0, 255);
+ const tools::Long nMinG = std::clamp<tools::Long>(rTransColor.GetGreen() - nTol, 0, 255);
+ const tools::Long nMaxG = std::clamp<tools::Long>(rTransColor.GetGreen() + nTol, 0, 255);
+ const tools::Long nMinB = std::clamp<tools::Long>(rTransColor.GetBlue() - nTol, 0, 255);
+ const tools::Long nMaxB = std::clamp<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, aTransparentColor);
+ }
+ else
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aOpaqueColor);
+ }
+ }
+ }
+ }
+ 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, aTransparentColor);
+ }
+ else
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aOpaqueColor);
+ }
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ aNewBmp.SetPrefSize(maPrefSize);
+ aNewBmp.SetPrefMapMode(maPrefMapMode);
+
+ return aNewBmp;
+}
+
+vcl::Region Bitmap::CreateRegion(const Color& rColor, const tools::Rectangle& rRect) const
+{
+ tools::Rectangle aRect(rRect);
+ BitmapScopedReadAccess pReadAcc(*this);
+
+ aRect.Intersection(tools::Rectangle(Point(), GetSizePixel()));
+ aRect.Normalize();
+
+ if (!pReadAcc)
+ return vcl::Region(aRect);
+
+ vcl::Region aRegion;
+ 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();
+
+ return aRegion;
+}
+
+bool Bitmap::ReplaceMask(const AlphaMask& rMask, const Color& rReplaceColor)
+{
+ BitmapScopedReadAccess pMaskAcc(rMask);
+ BitmapScopedWriteAccess pAcc(*this);
+
+ if (!pMaskAcc || !pAcc)
+ return false;
+
+ 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();
+
+ aReplace = UpdatePaletteForNewColor(pAcc, nActColors, nMaxColors, nHeight, nWidth,
+ BitmapColor(rReplaceColor));
+ }
+ 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);
+ }
+ }
+
+ return true;
+}
+
+bool Bitmap::Replace(const AlphaMask& rAlpha, const Color& rMergeColor)
+{
+ Bitmap aNewBmp(GetSizePixel(), vcl::PixelFormat::N24_BPP);
+ BitmapScopedReadAccess pAcc(*this);
+ BitmapScopedReadAccess pAlphaAcc(rAlpha);
+ BitmapScopedWriteAccess pNewAcc(aNewBmp);
+
+ if (!pAcc || !pAlphaAcc || !pNewAcc)
+ return false;
+
+ 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, pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX));
+ pNewAcc->SetPixelOnData(pScanline, nX, aCol);
+ }
+ }
+
+ pAcc.reset();
+ pAlphaAcc.reset();
+ pNewAcc.reset();
+
+ const MapMode aMap(maPrefMapMode);
+ const Size aSize(maPrefSize);
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+
+ return true;
+}
+
+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;
+ }
+ }
+
+ BitmapScopedWriteAccess pAcc(*this);
+ if (!pAcc)
+ return false;
+
+ const tools::Long nMinR = std::clamp<tools::Long>(rSearchColor.GetRed() - nTol, 0, 255);
+ const tools::Long nMaxR = std::clamp<tools::Long>(rSearchColor.GetRed() + nTol, 0, 255);
+ const tools::Long nMinG = std::clamp<tools::Long>(rSearchColor.GetGreen() - nTol, 0, 255);
+ const tools::Long nMaxG = std::clamp<tools::Long>(rSearchColor.GetGreen() + nTol, 0, 255);
+ const tools::Long nMinB = std::clamp<tools::Long>(rSearchColor.GetBlue() - nTol, 0, 255);
+ const tools::Long nMaxB = std::clamp<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();
+
+ return true;
+}
+
+bool Bitmap::Replace(const Color* pSearchColors, const Color* pReplaceColors, size_t nColorCount,
+ sal_uInt8 const* pTols)
+{
+ BitmapScopedWriteAccess pAcc(*this);
+ if (!pAcc)
+ return false;
+
+ 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();
+
+ return true;
+}
+
+// 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);
+
+ BitmapScopedReadAccess pAlphaAcc(rAlpha);
+
+ BitmapScopedWriteAccess pAcc(*this);
+
+ if (!pAlphaAcc || !pAcc)
+ return false;
+
+ 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, pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX));
+ pAcc->SetPixelOnData(pScanline, nX, aBmpColor);
+ }
+ }
+
+ return true;
+}
+
+static BitmapColor UpdatePaletteForNewColor(BitmapScopedWriteAccess& pAcc,
+ const sal_uInt16 nActColors,
+ const sal_uInt16 nMaxColors, const tools::Long nHeight,
+ const tools::Long nWidth,
+ const BitmapColor& rWantedColor)
+{
+ // default to the nearest color
+ sal_uInt16 aReplacePalIndex = pAcc->GetMatchingPaletteIndex(rWantedColor);
+ if (aReplacePalIndex != SAL_MAX_UINT16)
+ return BitmapColor(static_cast<sal_uInt8>(aReplacePalIndex));
+
+ // for paletted images without a matching palette entry
+
+ // if the palette has empty entries use the last one
+ if (nActColors < nMaxColors)
+ {
+ pAcc->SetPaletteEntryCount(nActColors + 1);
+ pAcc->SetPaletteColor(nActColors, rWantedColor);
+ return BitmapColor(static_cast<sal_uInt8>(nActColors));
+ }
+
+ // look for an unused palette entry (NOTE: expensive!)
+ 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, rWantedColor);
+ return BitmapColor(static_cast<sal_uInt8>(i));
+ }
+ }
+ assert(false && "found nothing");
+ return BitmapColor(0);
+}
+
+/* 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 0000000000..43eae34754
--- /dev/null
+++ b/vcl/source/bitmap/bitmappalette.cxx
@@ -0,0 +1,242 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <rtl/crc.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(const BitmapColor* first, const BitmapColor* last)
+ : maBitmapColor(first, last)
+ {
+ }
+ 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(const BitmapColor* first, const BitmapColor* last)
+ : mpImpl({ first, last })
+{
+}
+
+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 rtl_crc32(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];
+}
+
+/// Returns the BitmapColor (i.e. palette index) that is either an exact match
+/// of the required color, or failing that, the entry that is the closest i.e. least error
+/// as measured by Color::GetColorError.
+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;
+}
+
+/// Returns the BitmapColor (i.e. palette index) that is an exact match
+/// of the required color. Returns SAL_MAX_UINT16 if nothing found.
+sal_uInt16 BitmapPalette::GetMatchingIndex(const BitmapColor& rCol) const
+{
+ auto const& rBitmapColor = mpImpl->GetBitmapData();
+
+ for (size_t j = 0; j < rBitmapColor.size(); ++j)
+ {
+ if (rCol == rBitmapColor[j])
+ {
+ return j;
+ }
+ }
+
+ return SAL_MAX_UINT16;
+}
+
+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 0000000000..53de074adc
--- /dev/null
+++ b/vcl/source/bitmap/bmpfast.cxx
@@ -0,0 +1,823 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/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::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:
+ 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::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:
+ 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:
+ 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/dibtools.cxx b/vcl/source/bitmap/dibtools.cxx
new file mode 100644
index 0000000000..4eb15ac284
--- /dev/null
+++ b/vcl/source/bitmap/dibtools.cxx
@@ -0,0 +1,1769 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/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 <= 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)
+{
+ 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;
+ }
+ return BitmapColor(nIndex);
+}
+
+bool ImplDecodeRLE(sal_uInt8* pBuffer, DIBV5Header const & rHeader, BitmapWriteAccess& rAcc, BitmapPalette& rPalette, 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));
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp & 0x0f, rPalette));
+ }
+
+ if( nRunByte & 1 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(*pRLE >> 4, rPalette));
+
+ 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));
+
+ 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));
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp & 0x0f, rPalette));
+ }
+
+ if( ( nCountByte & 1 ) && ( nX < nWidth ) )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp >> 4, rPalette));
+ }
+ else
+ {
+ for (sal_uLong i = 0; i < nCountByte && nX < nWidth; ++i)
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp, rPalette));
+ }
+ }
+ }
+ 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)
+{
+ 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, 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));
+ }
+ }
+ }
+ 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));
+ }
+ }
+ }
+ 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));
+ }
+ }
+ }
+ 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, aColor);
+ }
+ }
+ }
+ 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, aPixelColor);
+ }
+ }
+ }
+ 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, aColor);
+ 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, aColor);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return rIStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplReadDIBBody(SvStream& rIStm, Bitmap& rBmp, AlphaMask* pBmpAlpha, sal_uInt64 nOffset, 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_Int64 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;
+ BitmapScopedWriteAccess 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 = 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
+
+ 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);
+
+ 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, sal_uLong nCompression, sal_uInt32& rImageSize)
+{
+ if(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((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(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(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::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;
+
+ for( tools::Long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+
+ 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();
+ }
+
+ rOStm.WriteBytes(aBuf.data(), nAlignedWidth);
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ rImageSize = rOStm.Tell() - rImageSize;
+
+ return (!rOStm.GetError());
+}
+
+bool ImplWriteDIBBody(const Bitmap& rBitmap, SvStream& rOStm, BitmapReadAccess const & rAcc, 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 = DIBINFOHEADERSIZE; // size dependent on CF_DIB type to use
+ aHeader.nWidth = rAcc.Width();
+ aHeader.nHeight = rAcc.Height();
+ aHeader.nPlanes = 1;
+
+ if(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(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 = ((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(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, 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, 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 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, bMSOFormat);
+ }
+ }
+ else
+ {
+ bRet = ImplReadDIBBody(rIStm, rTarget, nullptr, nOffset, 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())
+ return false;
+
+ BitmapScopedReadAccess pAcc(rSource);
+ 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, bCompressed);
+ }
+ }
+ else
+ {
+ bRet = ImplWriteDIBBody(rSource, rOStm, *pAcc, bCompressed);
+ }
+
+ pAcc.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, bMSOFormat);
+}
+
+bool ReadDIBBitmapEx(
+ BitmapEx& rTarget,
+ SvStream& rIStm,
+ bool bFileHeader,
+ bool bMSOFormat)
+{
+ Bitmap aBmp;
+ bool bRetval(ImplReadDIB(aBmp, nullptr, rIStm, bFileHeader, 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);
+
+ if(bRetval && !aMask.IsEmpty())
+ 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)
+{
+ bool rv = ImplReadDIB(rTarget, &rTargetAlpha, rIStm, true);
+ // convert transparency->alpha
+ if (rv)
+ rTargetAlpha.Invert();
+ return rv;
+}
+
+bool ReadRawDIB(
+ BitmapEx& rTarget,
+ const unsigned char* pBuf,
+ const ScanlineFormat nFormat,
+ const int nHeight,
+ const int nStride)
+{
+ BitmapScopedWriteAccess pWriteAccess(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())
+ {
+ // invert the alpha because the other routines actually want transparency
+ AlphaMask tmpAlpha = rSource.maAlphaMask;
+ tmpAlpha.Invert();
+ return ImplWriteDIB(tmpAlpha.GetBitmap(), 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 0000000000..b5f26fb7a6
--- /dev/null
+++ b/vcl/source/bitmap/floyd.hxx
@@ -0,0 +1,177 @@
+/* -*- 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
+
+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 int 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
+};
+
+constexpr int FloydErrMap[256]
+ = { 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
+ 52, 53, 54, 55, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
+ 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
+ 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 5, 6, 7, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
+ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
+ 53, 54, 55, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
+ 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 };
+
+constexpr int 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
+};
+
+constexpr int 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
+};
+
+constexpr int 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
+};
+
+constexpr int 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
+};
+
+/* 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 0000000000..3f8f8471a6
--- /dev/null
+++ b/vcl/source/bitmap/impvect.cxx
@@ -0,0 +1,996 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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::optional<Bitmap> xBmp(std::in_place, rColorBmp );
+ BitmapScopedReadAccess pRAcc(*xBmp);
+
+ if( pRAcc )
+ {
+ 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 )
+ {
+ tools::PolyPolygon aPolyPoly;
+ 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( std::move(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 0000000000..257d1b5e5a
--- /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 0000000000..67912262e6
--- /dev/null
+++ b/vcl/source/bitmap/salbmp.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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>
+#include <rtl/crc.h>
+
+static BitmapChecksum scanlineChecksum(BitmapChecksum nCrc, const sal_uInt8* bits, int lineBitsCount, sal_uInt8 extraBitsMask)
+{
+ if( lineBitsCount / 8 > 0 )
+ nCrc = rtl_crc32( nCrc, bits, lineBitsCount / 8 );
+ if( extraBitsMask != 0 )
+ {
+ sal_uInt8 extraByte = bits[ lineBitsCount / 8 ] & extraBitsMask;
+ nCrc = rtl_crc32( 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;
+ }
+ default:
+ break;
+ }
+ }
+ if( pBuf->mnFormat & ScanlineFormat::TopDown )
+ {
+ if( pBuf->mnScanlineSize == lineBitsCount / 8 )
+ nCrc = rtl_crc32(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;
+}
+
+const basegfx::SystemDependentDataHolder* SalBitmap::accessSystemDependentDataHolder() const
+{
+ // default has no support, returns nullptr
+ return nullptr;
+}
+
+/* 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 0000000000..4a3e6fbb4d
--- /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 0000000000..c9bd3518cd
--- /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 0000000000..cc5facf177
--- /dev/null
+++ b/vcl/source/cnttype/mcnttype.cxx
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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( );
+
+ const auto iter = m_ParameterMap.find(lower);
+ assert(iter != m_ParameterMap.end());
+ return iter->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 0000000000..c4053066b7
--- /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 0000000000..92554d3fb7
--- /dev/null
+++ b/vcl/source/components/dtranscomp.cxx
@@ -0,0 +1,467 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <comphelper/processfactory.hxx>
+#include <osl/mutex.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/LokClipboard.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);
+
+ std::erase(m_aListeners, listener);
+}
+
+
+
+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 getXWeak(new vcl::GenericClipboard());
+#else
+ if (comphelper::LibreOfficeKit::isActive()) {
+ // In LOK, each document view shall have its own clipboard instance (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"):
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> xClipboard =
+ css::datatransfer::clipboard::LokClipboard::create(
+ comphelper::getProcessComponentContext());
+ return xClipboard;
+ }
+#endif
+ DBG_TESTSOLARMUTEX();
+ if (!m_clipboard.is()) {
+ m_clipboard = getXWeak(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 getXWeak(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 getXWeak(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 0000000000..0a5b7e8f6f
--- /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 0000000000..104981ad43
--- /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/implbase.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::WeakImplHelper< 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 0000000000..94090d5cd6
--- /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 0000000000..6aaf2e553a
--- /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 0000000000..0769245eb4
--- /dev/null
+++ b/vcl/source/control/InterimItemWindow.cxx
@@ -0,0 +1,208 @@
+/* -*- 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 OUString& 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(NotifyEventType::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(NotifyEventType::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::SetPriority(TaskPriority nPriority)
+{
+ // Eliminate warning when changing timer's priority
+ // Task::SetPriority() expects the timer to be stopped while
+ // changing the timer's priority.
+ bool bActive = m_aLayoutIdle.IsActive();
+ if (bActive)
+ m_aLayoutIdle.Stop();
+ m_aLayoutIdle.SetPriority(nPriority);
+ if (bActive)
+ m_aLayoutIdle.Start();
+}
+
+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 0000000000..3cc21815ab
--- /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 0000000000..c893cc8332
--- /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 0000000000..fd5aa5814d
--- /dev/null
+++ b/vcl/source/control/PriorityMergedHBox.cxx
@@ -0,0 +1,199 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+* This file is part of the LibreOffice project.
+*
+* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*
+* This file incorporates work covered by the following license notice:
+*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed
+* with this work for additional information regarding copyright
+* ownership. The ASF licenses this file to you under the Apache
+* License, Version 2.0 (the "License"); you may not use this file
+* 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())
+ {
+ tools::Long nWindowWidth = pWindow->GetOutputSizePixel().Width();
+ if (!nWindowWidth)
+ nWindowWidth = getLayoutRequisition(*pWindow).Width() + get_spacing();
+
+ if (nWindowWidth)
+ nCurrentWidth -= nWindowWidth;
+ 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;
+ // find max height and total width
+ for (vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ Size aChildSize = getLayoutRequisition(*pChild);
+ if (!pChild->IsVisible())
+ setPrimaryDimension(aChildSize, 0);
+ else
+ {
+ ++nVisibleChildren;
+ tools::Long nPrimaryDimension = getPrimaryDimension(aChildSize);
+ nPrimaryDimension += pChild->get_padding() * 2;
+ setPrimaryDimension(aChildSize, nPrimaryDimension);
+ }
+ accumulateMaxes(aChildSize, aSize);
+ }
+
+ 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 0000000000..1a3311de9f
--- /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 0000000000..ac06f445e0
--- /dev/null
+++ b/vcl/source/control/button.cxx
@@ -0,0 +1,3842 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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, const css::uno::Reference<css::frame::XFrame>& rFrame)
+{
+ maCommand = aCommand;
+ SetClickHdl( LINK( this, Button, dispatchCommandHandler) );
+
+ mpButtonData->mpStatusListener = new VclStatusListener<Button>(this, rFrame, 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.getOpenWidth() > 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 OUString &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
+{
+
+std::string_view 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());
+ OStringBuffer aBuffer("data:image/png;base64,");
+ ::comphelper::Base64::encode(aBuffer, aSeq);
+ rJsonWriter.put("image", aBuffer);
+ }
+ }
+
+ 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()));
+ if (isToggleButton())
+ rJsonWriter.put("isToggle", true);
+}
+
+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();
+
+#if defined(MACOSX) || defined(IOS)
+ // tdf#152486 These are the buttons in infobars where the infobar has a custom
+ // background color and on these platforms the buttons blend with
+ // their background.
+ vcl::Window* pParent = GetParent();
+ if (pParent->get_id() == "ExtraButton")
+ {
+ while (pParent && !pParent->IsControlBackground())
+ pParent = pParent->GetParent();
+ if (pParent)
+ {
+ if (aColor.IsBright() && !pParent->GetControlBackground().IsDark())
+ aColor = COL_BLACK;
+ }
+ }
+#endif
+
+ 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;
+
+ if (GetStyle() & WB_FLATBUTTON)
+ nButtonStyle |= DrawButtonFlags::Flat;
+
+ // 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;
+
+ // 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 (!aControlValue.m_bFlatButton || (nState & ControlState::ROLLOVER) || (nState & ControlState::PRESSED)
+ || (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);
+ 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() == NotifyEventType::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 OUString &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)
+ {
+ std::erase(*m_xGroup, VclPtr<RadioButton>(this));
+ 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 );
+ 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, rPos, 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() == NotifyEventType::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 OUString &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());
+ OStringBuffer aBuffer("data:image/png;base64,");
+ ::comphelper::Base64::encode(aBuffer, aSeq);
+ rJsonWriter.put("image", aBuffer);
+ }
+ }
+}
+
+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 );
+ 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, rPos, 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() == NotifyEventType::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 OUString &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 0000000000..cce0bce63a
--- /dev/null
+++ b/vcl/source/control/calendar.cxx
@@ -0,0 +1,1747 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/kernarray.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 <tools/json_writer.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;
+ }
+
+ // header position for the last day of week
+ mnDayOfWeekAry[7] = mnMonthWidth;
+
+ 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) % std::size(maDayTexts)];
+ 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));
+ KernArray aTmp;
+ for (int k=0; k<7; ++k)
+ aTmp.push_back(mnDayOfWeekAry[k+1]);
+ rRenderContext.DrawTextArray(Point(nDayX + mnDayOfWeekAry[0], nDayY), maDayOfWeekText, aTmp, {}, 0, aTmp.size());
+
+ // 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::ImplScrollCalendar( 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) )
+ {
+ ImplScrollCalendar( 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() )
+ ImplScrollCalendar( true );
+ else if ( GetLastMonth() < aFirstSelDate )
+ ImplScrollCalendar( 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;
+ ImplScrollCalendar( 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 )
+ {
+ ImplScrollCalendar( true );
+ nNotchDelta++;
+ }
+ }
+ else
+ {
+ while ( nNotchDelta > 0 )
+ {
+ ImplScrollCalendar( 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();
+}
+
+void Calendar::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Control::DumpAsPropertyTree(rJsonWriter);
+
+ auto aDate = GetFirstSelectedDate();
+
+ rJsonWriter.put("type", "calendar");
+ rJsonWriter.put("day", aDate.GetDay());
+ rJsonWriter.put("month", aDate.GetMonth());
+ rJsonWriter.put("year", aDate.GetYear());
+}
+
+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 );
+ }
+}
+
+// tdf#142783 consider the Edit and its DropDown as one compound control for the purpose of
+// notification of loss of focus from the control
+bool CalendarField::FocusWindowBelongsToControl(const vcl::Window* pFocusWin) const
+{
+ return DateField::FocusWindowBelongsToControl(pFocusWin) || (mpFloatWin && mpFloatWin->ImplIsWindowOrChild(pFocusWin));
+}
+
+/* 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 0000000000..2a979b70b0
--- /dev/null
+++ b/vcl/source/control/combobox.cxx
@@ -0,0 +1,1608 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::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.getOpenWidth();
+ }
+
+ 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);
+
+ 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( rPos, 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, rPos, 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( rPos, 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( rPos, 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 );
+ }
+}
+
+void ComboBox::SetHighlightColor( const Color& rColor )
+{
+ AllSettings aSettings(GetSettings());
+ StyleSettings aStyle(aSettings.GetStyleSettings());
+ aStyle.SetHighlightColor(rColor);
+ aSettings.SetStyleSettings(aStyle);
+ SetSettings(aSettings);
+
+ AllSettings aSettingsSubEdit(m_pImpl->m_pSubEdit->GetSettings());
+ StyleSettings aStyleSubEdit(aSettingsSubEdit.GetStyleSettings());
+ aStyleSubEdit.SetHighlightColor(rColor);
+ aSettingsSubEdit.SetStyleSettings(aStyleSubEdit);
+ m_pImpl->m_pSubEdit->SetSettings(aSettings);
+
+ m_pImpl->m_pImplLB->SetHighlightColor(rColor);
+}
+
+void ComboBox::SetHighlightTextColor( const Color& rColor )
+{
+ AllSettings aSettings(GetSettings());
+ StyleSettings aStyle(aSettings.GetStyleSettings());
+ aStyle.SetHighlightTextColor(rColor);
+ aSettings.SetStyleSettings(aStyle);
+ SetSettings(aSettings);
+
+ AllSettings aSettingsSubEdit(m_pImpl->m_pSubEdit->GetSettings());
+ StyleSettings aStyleSubEdit(aSettingsSubEdit.GetStyleSettings());
+ aStyleSubEdit.SetHighlightTextColor(rColor);
+ aSettingsSubEdit.SetStyleSettings(aStyleSubEdit);
+ m_pImpl->m_pSubEdit->SetSettings(aSettings);
+
+ m_pImpl->m_pImplLB->SetHighlightTextColor(rColor);
+}
+
+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 );
+ AbsoluteScreenPixelPoint aConvPointAbs = OutputToAbsoluteScreenPixel( aConvPoint );
+ aConvPoint = rMain->AbsoluteScreenToOutputPixel( aConvPointAbs );
+ 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.getOpenWidth(), (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.getOpenWidth(), 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 OUString &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());
+
+ if (IsUserDrawEnabled())
+ rJsonWriter.put("customEntryRenderer", true);
+}
+
+/* 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 0000000000..4f7a42badf
--- /dev/null
+++ b/vcl/source/control/ctrl.cxx
@@ -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 .
+ */
+
+#include <comphelper/lok.hxx>
+#include <o3tl/safeint.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/event.hxx>
+#include <vcl/ctrl.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <vcl/DocWindow.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;
+}
+
+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::FocusWindowBelongsToControl(const vcl::Window* pFocusWin) const
+{
+ return ImplIsWindowOrChild(pFocusWin);
+}
+
+bool Control::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
+ {
+ if ( !mbHasControlFocus )
+ {
+ mbHasControlFocus = true;
+ CompatStateChanged( StateChangedType::ControlFocus );
+ if ( ImplCallEventListenersAndHandler( VclEventId::ControlGetFocus, {} ) )
+ // been destroyed within the handler
+ return true;
+ }
+ }
+ else
+ {
+ if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
+ {
+ vcl::Window* pFocusWin = Application::GetFocusWindow();
+ if ( !pFocusWin || !FocusWindowBelongsToControl(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 = removeMnemonicFromString( _rStr );
+
+ 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 = removeMnemonicFromString( _rStr );
+
+ 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::LogicInvalidate(const tools::Rectangle* pRectangle)
+{
+ VclPtr<vcl::Window> pParent = GetParentWithLOKNotifier();
+ if (!pParent || !dynamic_cast<vcl::DocWindow*>(GetParent()))
+ {
+ // if control doesn't belong to a DocWindow, the overridden base class
+ // method has to be invoked
+ Window::LogicInvalidate(pRectangle);
+ return;
+ }
+
+ // avoid endless paint/invalidate loop in Impress
+ if (comphelper::LibreOfficeKit::isTiledPainting())
+ return;
+
+ tools::Rectangle aResultRectangle;
+ if (!pRectangle)
+ {
+ // we have to invalidate the whole control area not the whole document
+ aResultRectangle = PixelToLogic(tools::Rectangle(GetPosPixel(), GetSizePixel()), MapMode(MapUnit::MapTwip));
+ }
+ else
+ {
+ aResultRectangle = OutputDevice::LogicToLogic(*pRectangle, GetMapMode(), MapMode(MapUnit::MapTwip));
+ }
+
+ pParent->GetLOKNotifier()->notifyInvalidation(&aResultRectangle);
+}
+
+/* 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 0000000000..0e75dc36b9
--- /dev/null
+++ b/vcl/source/control/edit.cxx
@@ -0,0 +1,2937 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <utility>
+#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, OUString aOldTextAfterStartPos);
+
+ void CopyAttribs(const ExtTextInputAttr* pA, sal_Int32 nL);
+ void DestroyAttribs();
+};
+
+Impl_IMEInfos::Impl_IMEInfos(sal_Int32 nP, OUString _aOldTextAfterStartPos)
+ : aOldTextAfterStartPos(std::move(_aOldTextAfterStartPos)),
+ 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 OUString &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();
+
+ KernArray aDX;
+ if (nLen)
+ GetOutDev()->GetCaretPositions(aText, aDX, 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.Normalize();
+ // selection is highlighted
+ for(sal_Int32 i = 0; i < nLen; ++i)
+ {
+ tools::Rectangle aRect(aPos, Size(10, nTH));
+ aRect.SetLeft( aDX[2 * i] + mnXOffset + ImplGetExtraXOffset() );
+ aRect.SetRight( aDX[2 * i + 1] + mnXOffset + ImplGetExtraXOffset() );
+ aRect.Normalize();
+ 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( aDX[2 * (nIndex + mpIMEInfos->nPos)] + mnXOffset + ImplGetExtraXOffset() );
+ aRect.SetRight( aDX[2 * (nIndex + mpIMEInfos->nPos) + 1] + mnXOffset + ImplGetExtraXOffset() );
+ aRect.Normalize();
+ 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.Normalize();
+
+ 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.Normalize();
+
+ 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.subView(0, 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)
+ && (!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;
+
+ if( !aText.isEmpty() )
+ {
+ KernArray aDX;
+ GetOutDev()->GetCaretPositions(aText, aDX, 0, aText.getLength());
+
+ if( maSelection.Max() < aText.getLength() )
+ nTextPos = aDX[ 2*maSelection.Max() ];
+ else
+ nTextPos = aDX[ 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();
+
+ if (aText.isEmpty())
+ return nIndex;
+
+ KernArray aDX;
+ GetOutDev()->GetCaretPositions(aText, aDX, 0, aText.getLength());
+ tools::Long nX = rWindowPos.X() - mnXOffset - ImplGetExtraXOffset();
+ for (sal_Int32 i = 0; i < aText.getLength(); aText.iterateCodePoints(&i))
+ {
+ if( (aDX[2*i] >= nX && aDX[2*i+1] <= nX) ||
+ (aDX[2*i+1] >= nX && aDX[2*i] <= nX))
+ {
+ nIndex = i;
+ if( aDX[2*i] < aDX[2*i+1] )
+ {
+ if( nX > (aDX[2*i]+aDX[2*i+1])/2 )
+ aText.iterateCodePoints(&nIndex);
+ }
+ else
+ {
+ if( nX < (aDX[2*i]+aDX[2*i+1])/2 )
+ aText.iterateCodePoints(&nIndex);
+ }
+ break;
+ }
+ }
+ if( nIndex == EDIT_NOLIMIT )
+ {
+ nIndex = 0;
+ sal_Int32 nFinalIndex = 0;
+ tools::Long nDiff = std::abs( aDX[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( aDX[2*i]-nX );
+
+ if( nNewDiff < nDiff )
+ {
+ nIndex = i;
+ nDiff = nNewDiff;
+ }
+
+ nFinalIndex = i;
+
+ aText.iterateCodePoints(&i);
+ }
+ if (nIndex == nFinalIndex && std::abs( aDX[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;
+
+ // tdf#127588 - extend selection to the entire field or paste the text
+ // from the clipboard to the current position if there is no selection
+ if (mnMaxTextLen < EDIT_NOLIMIT && maSelection.Len() == 0)
+ {
+ const sal_Int32 aTextLen = aText.getLength();
+ if (aTextLen == mnMaxTextLen)
+ {
+ maSelection.Min() = 0;
+ maSelection.Max() = mnMaxTextLen;
+ } else
+ maSelection.Max() = std::min<sal_Int32>(maSelection.Min() + aTextLen, mnMaxTextLen);
+ }
+
+ Selection aSelection(maSelection);
+ aSelection.Normalize();
+ 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.Normalize();
+
+ 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()
+{
+ Control::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();
+
+ if (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 ) );
+ }
+}
+
+void Edit::LoseFocus()
+{
+ if ( !mpSubEdit )
+ {
+ if (IsNativeWidgetEnabled() &&
+ IsNativeControlSupported(ControlType::Editbox, ControlPart::Entire))
+ {
+ ImplInvalidateOutermostBorder( mbIsSubEdit ? GetParent() : this );
+ }
+
+ if ( !mbActivePopup && !( GetStyle() & WB_NOHIDESELECTION ) && maSelection.Len() )
+ ImplInvalidateOrRepaint(); // paint the selection
+ }
+
+ Control::LoseFocus();
+}
+
+bool Edit::PreNotify(NotifyEvent& rNEvt)
+{
+ if (rNEvt.GetType() == NotifyEventType::MOUSEMOVE)
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if (pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged())
+ {
+ // trigger redraw if mouse over state has changed
+ if (pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow())
+ {
+ if (IsNativeWidgetEnabled() &&
+ IsNativeControlSupported(ControlType::Editbox, ControlPart::Entire))
+ {
+ ImplInvalidateOutermostBorder(this);
+ }
+ }
+ }
+ }
+
+ return Control::PreNotify(rNEvt);
+}
+
+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(u"cut"), bEnableCut);
+ pPopup->EnableItem(pPopup->GetItemId(u"copy"), bEnableCopy);
+ pPopup->EnableItem(pPopup->GetItemId(u"delete"), bEnableDelete);
+ pPopup->EnableItem(pPopup->GetItemId(u"paste"), bEnablePaste);
+ pPopup->SetItemText(pPopup->GetItemId(u"specialchar"),
+ BuilderUtils::convertMnemonicMarkup(VclResId(STR_SPECIAL_CHARACTER_MENU_ENTRY)));
+ pPopup->EnableItem(pPopup->GetItemId(u"specialchar"), bEnableSpecialChar);
+ pPopup->EnableItem(
+ pPopup->GetItemId(u"undo"),
+ std::u16string_view(maUndoText) != std::u16string_view(maText));
+ bool bAllSelected = maSelection.Min() == 0 && maSelection.Max() == maText.getLength();
+ pPopup->EnableItem(pPopup->GetItemId(u"selectall"), !bAllSelected);
+ pPopup->ShowItem(pPopup->GetItemId(u"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 );
+ OUString 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();
+ KernArray aDX;
+ GetOutDev()->GetCaretPositions(aText, aDX, 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(OUString _aForbiddenChars)
+ : sForbiddenChars(std::move(_aForbiddenChars))
+{
+}
+
+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.Normalize();
+ 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(u"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(u"undo"), vcl::KeyCode( KeyFuncType::UNDO));
+ pPopup->SetAccelKey(pPopup->GetItemId(u"cut"), vcl::KeyCode( KeyFuncType::CUT));
+ pPopup->SetAccelKey(pPopup->GetItemId(u"copy"), vcl::KeyCode( KeyFuncType::COPY));
+ pPopup->SetAccelKey(pPopup->GetItemId(u"paste"), vcl::KeyCode( KeyFuncType::PASTE));
+ pPopup->SetAccelKey(pPopup->GetItemId(u"delete"), vcl::KeyCode( KeyFuncType::DELETE));
+ pPopup->SetAccelKey(pPopup->GetItemId(u"selectall"), vcl::KeyCode( KEY_A, false, true, false, false));
+ pPopup->SetAccelKey(pPopup->GetItemId(u"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.Normalize();
+
+ // 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.Normalize();
+
+ 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.Normalize();
+
+ // 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);
+
+ if (IsPassword())
+ rJsonWriter.put("password", true);
+}
+
+/* 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 0000000000..d85b235b0e
--- /dev/null
+++ b/vcl/source/control/field.cxx
@@ -0,0 +1,1882 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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(OUString::number(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,
+ std::u16string_view rOldDecSep, std::u16string_view rNewDecSep,
+ std::u16string_view 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( OUString::Concat(std::u16string_view(pBuffer + nIndex, nIndexTh - nIndex )) + rNewThSep );
+ nIndex = nIndexTh + rOldThSep.size();
+ }
+ else if( nIndexDec != -1 )
+ {
+ aBuf.append( OUString::Concat(std::u16string_view(pBuffer + nIndex, nIndexDec - nIndex )) + rNewDecSep );
+ nIndex = nIndexDec + rOldDecSep.size();
+ }
+ else
+ {
+ aBuf.append( pBuffer + nIndex );
+ nIndex = -1;
+ }
+ }
+
+ io_rText = aBuf.makeStringAndClear();
+}
+
+void ImplUpdateSeparators( std::u16string_view rOldDecSep, std::u16string_view rNewDecSep,
+ std::u16string_view 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.Normalize();
+ 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 = std::u16string_view(OUString::number(rFormatter.GetMin())).size();
+ string::padToLength(aBuf, nTextLen, '9');
+ Size aMinTextSize = rSpinField.CalcMinimumSizeForText(
+ rFormatter.CreateFieldText(OUString::unacquired(aBuf).toInt64()));
+ aBuf.setLength(0);
+
+ nTextLen = std::u16string_view(OUString::number(rFormatter.GetMax())).size();
+ 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() == NotifyEventType::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() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::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(std::u16string_view rStr)
+{
+ // fetch unit text
+ OUStringBuffer aStr;
+ for (sal_Int32 i = static_cast<sal_Int32>(rStr.size())-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(std::u16string_view 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"_ustr;
+ if (aSuffix != "\"" && aSuffix != sDoublePrime)
+ aStr += " ";
+ else
+ aSuffix = sDoublePrime;
+ }
+ else if (meUnit == FieldUnit::FOOT)
+ {
+ OUString sPrime = u"\u2032"_ustr;
+ 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 OUString &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() == NotifyEventType::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() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::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 0000000000..8552d2510d
--- /dev/null
+++ b/vcl/source/control/field2.cxx
@@ -0,0 +1,3188 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <comphelper/diagnose_ex.hxx>
+#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>
+#include <tools/duration.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()->getCharacterType( aCharStr, 0,
+ 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( std::u16string_view 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.size();
+
+ // 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( std::u16string_view 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.Normalize();
+ 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.Normalize();
+ 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.Normalize();
+ 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, 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.Normalize();
+ 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() == NotifyEventType::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() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::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.Normalize();
+ 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.Normalize();
+ 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() == NotifyEventType::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() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::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.subView(0, _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());
+ }
+ else if ( mbDuration )
+ {
+ tools::Duration aDuration( 0, aTempTime);
+ rOutStr = ImplGetLocaleDataWrapper().getDuration( aDuration, 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.Normalize();
+ 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());
+ }
+ else if ( bDuration )
+ {
+ tools::Duration aDuration( 0, rNewTime);
+ aStr = rLocaleData.getDuration( aDuration, 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() == NotifyEventType::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() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::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 0000000000..068332e5b6
--- /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 OUString &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 OUString &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());
+ OStringBuffer aBuffer("data:image/png;base64,");
+ ::comphelper::Base64::encode(aBuffer, aSeq);
+ rJsonWriter.put("image", aBuffer);
+ }
+ }
+}
+
+
+/* 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 0000000000..5b4960d92f
--- /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.getOpenWidth(), 0);
+ else if (GetStyle() & WB_CENTER)
+ aFocusRect.Move((aSize.Width() - aFocusRect.getOpenWidth()) / 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 OUString &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 0000000000..fc7bdfee6f
--- /dev/null
+++ b/vcl/source/control/fmtfield.cxx
@@ -0,0 +1,1366 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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_bDefaultValueSet(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.Normalize();
+ 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.Normalize();
+
+ 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;
+
+ // tdf#155241 default to m_dDefaultValue only if explicitly set
+ // otherwise default to m_dCurrentValue
+ if (m_bDefaultValueSet)
+ 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("#" + aLocaleInfo.getNumThousandSep() + "##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, ' ');
+
+ OUString sTemp =
+ "[$" + sSymbol + "] "
+ + 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
+ + ";[$"
+ + sSymbol
+ + "] -"
+ + sNewFormat;
+
+ sNewFormat = sTemp;
+ }
+ else
+ {
+ OUString sTemp = getCurrencySymbol();
+ sTemp = comphelper::string::strip(sTemp, ' ');
+
+ sNewFormat.append(" [$" + sTemp + "]");
+ }
+
+ // 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 OUString &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() == NotifyEventType::KEYINPUT)
+ GetFormatter().SetLastSelection(GetSelection());
+ return SpinField::PreNotify(rNEvt);
+}
+
+bool FormattedField::EventNotify(NotifyEvent& rNEvt)
+{
+ if ((rNEvt.GetType() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::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 0000000000..34f10750ae
--- /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 0000000000..414824b29d
--- /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 0000000000..5a052c083e
--- /dev/null
+++ b/vcl/source/control/imivctl.hxx
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/config.h>
+
+#include <o3tl/safeint.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/vclptr.hxx>
+#include <tools/debug.hxx>
+#include <vcl/svtaccessiblefactory.hxx>
+#include <vcl/toolkit/ivctrl.hxx>
+#include <vcl/toolkit/scrbar.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();
+};
+
+/* 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 0000000000..1364dc4f44
--- /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() )
+ {
+ double 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.Normalize();
+ 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.Normalize();
+ 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.Normalize();
+ 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 0000000000..5b0d2d8adb
--- /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 0000000000..bb4da51859
--- /dev/null
+++ b/vcl/source/control/imp_listbox.cxx
@@ -0,0 +1,3029 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/i18nhelp.hxx>
+#include <vcl/naturalsort.hxx>
+#include <vcl/vcllayout.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+#include <vcl/toolkit/scrbar.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.GetListBoxWindowTextColor());
+
+ if (IsControlBackground())
+ rRenderContext.SetBackground(GetControlBackground());
+ else
+ rRenderContext.SetBackground(rStyleSettings.GetListBoxWindowBackgroundColor());
+}
+
+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.GetListBoxWindowHighlightTextColor());
+ rRenderContext.SetFillColor(rStyleSettings.GetListBoxWindowHighlightColor());
+ 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() == NotifyEventType::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::GesturePan)
+ {
+ 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());
+ }
+}
+
+void ImplListBox::SetHighlightColor(const Color& rColor)
+{
+ AllSettings aSettings(GetSettings());
+ StyleSettings aStyle(aSettings.GetStyleSettings());
+ aStyle.SetHighlightColor(rColor);
+ aSettings.SetStyleSettings(aStyle);
+ SetSettings(aSettings);
+
+ AllSettings aSettingsLB(maLBWindow->GetSettings());
+ StyleSettings aStyleLB(aSettingsLB.GetStyleSettings());
+ aStyleLB.SetListBoxWindowHighlightColor(rColor);
+ aSettingsLB.SetStyleSettings(aStyleLB);
+ maLBWindow->SetSettings(aSettingsLB);
+}
+
+void ImplListBox::SetHighlightTextColor(const Color& rColor)
+{
+ AllSettings aSettings(GetSettings());
+ StyleSettings aStyle(aSettings.GetStyleSettings());
+ aStyle.SetHighlightTextColor(rColor);
+ aSettings.SetStyleSettings(aStyle);
+ SetSettings(aSettings);
+
+ AllSettings aSettingsLB(maLBWindow->GetSettings());
+ StyleSettings aStyleLB(aSettingsLB.GetStyleSettings());
+ aStyleLB.SetListBoxWindowHighlightTextColor(rColor);
+ aSettingsLB.SetStyleSettings(aStyleLB);
+ maLBWindow->SetSettings(aSettingsLB);
+}
+
+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);
+}
+
+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 = pWin->IsMouseOver();
+ if (!bMouseOver)
+ {
+ 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;
+
+ Color aBackgroundColor = COL_AUTO;
+ if (IsControlBackground())
+ aBackgroundColor = GetControlBackground();
+
+ // 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(), aBackgroundColor);
+ }
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Listbox, ControlPart::Entire, aCtrlRegion,
+ nState, aControlValue, OUString(), aBackgroundColor);
+ }
+
+ 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 (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 (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() == NotifyEventType::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().getOpenWidth();
+ 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, LISTBOX_FLOATWINPOPUPFLAGS );
+
+ 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 0000000000..a2f502ff81
--- /dev/null
+++ b/vcl/source/control/ivctrl.cxx
@@ -0,0 +1,679 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <utility>
+#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( OUString _aText,
+ Image _aImage )
+ : aImage(std::move(_aImage))
+ , aText(std::move(_aText))
+ , 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::XVclWindowPeer > xHoldAlive(GetComponentInterface());
+ xAccessible = _pImpl->GetAccessibleFactory().createAccessibleIconChoiceCtrl( *this, xAccParent );
+ }
+ }
+ return xAccessible;
+}
+
+struct VerticalTabPageData
+{
+ OUString 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::u16string_view rId) const
+{
+ VerticalTabPageData* pRet = nullptr;
+ for (auto & pData : maPageList)
+ {
+ if (pData->sId == rId)
+ {
+ pRet = pData.get();
+ break;
+ }
+ }
+ return pRet;
+}
+
+void VerticalTabControl::SetCurPageId(const OUString& rId)
+{
+ OUString 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(),m_sCurrentPageId);
+}
+
+const OUString & VerticalTabControl::GetPageId(sal_uInt16 nIndex) const
+{
+ return maPageList[nIndex]->sId;
+}
+
+void VerticalTabControl::InsertPage(const rtl::OUString &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::u16string_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::u16string_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::u16string_view rPageId) const
+{
+ VerticalTabPageData* pData = GetPageData(rPageId);
+ if (!pData)
+ return nullptr;
+ return pData->xPage;
+}
+
+OUString VerticalTabControl::GetPageText(std::u16string_view rPageId) const
+{
+ VerticalTabPageData* pData = GetPageData(rPageId);
+ if (!pData)
+ return OUString();
+ return pData->pEntry->GetText();
+}
+
+void VerticalTabControl::SetPageText(std::u16string_view rPageId, const OUString& rText)
+{
+ VerticalTabPageData* pData = GetPageData(rPageId);
+ if (!pData)
+ return;
+ pData->pEntry->SetText(rText);
+}
+
+void VerticalTabControl::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ rJsonWriter.put("id", get_id());
+ rJsonWriter.put("type", "tabcontrol");
+ rJsonWriter.put("vertical", true);
+ rJsonWriter.put("selected", GetCurPageId());
+
+ {
+ auto childrenNode = rJsonWriter.startArray("children");
+ for (int i = 0; i < GetPageCount(); i++)
+ {
+ VclPtr<vcl::Window> pChild = GetPage(GetPageId(i));
+
+ if (pChild)
+ {
+ if (!pChild->GetChildCount())
+ continue;
+
+ auto aChildNode = rJsonWriter.startStruct();
+ pChild->DumpAsPropertyTree(rJsonWriter);
+ }
+ }
+ }
+ {
+ auto tabsNode = rJsonWriter.startArray("tabs");
+ for(int i = 0; i < GetPageCount(); i++)
+ {
+ VclPtr<vcl::Window> pChild = GetPage(GetPageId(i));
+
+ if (pChild)
+ {
+ if (!pChild->GetChildCount())
+ continue;
+
+ auto aTabNode = rJsonWriter.startStruct();
+ auto sId = GetPageId(i);
+ rJsonWriter.put("text", GetPageText(sId));
+ rJsonWriter.put("id", sId);
+ rJsonWriter.put("name", GetPageText(sId));
+ }
+ }
+ }
+}
+
+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 0000000000..e189c8480f
--- /dev/null
+++ b/vcl/source/control/listbox.cxx
@@ -0,0 +1,1457 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 );
+ AbsoluteScreenPixelPoint aConvPointAbs = OutputToAbsoluteScreenPixel( aConvPoint );
+ aConvPoint = rMain->AbsoluteScreenToOutputPixel( aConvPointAbs );
+ 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 );
+ aConvPointAbs = OutputToAbsoluteScreenPixel( aConvPoint );
+ aConvPoint = mpImplWin->AbsoluteScreenToOutputPixel( aConvPointAbs );
+
+ // 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 )
+ {
+
+ mpImplWin->SetBackground( GetControlBackground() );
+ mpImplWin->SetControlBackground( 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() == NotifyEventType::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() == NotifyEventType::LOSEFOCUS )
+ {
+ if ( IsInDropDown() && !HasChildPathFocus( true ) )
+ mpFloatWin->EndPopupMode();
+ }
+ else if ( (rNEvt.GetType() == NotifyEventType::COMMAND) &&
+ (rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) &&
+ (rNEvt.GetWindow() == mpImplWin) )
+ {
+ const Point& rMousePos = rNEvt.GetCommandEvent()->GetMousePosPixel();
+ const tools::Rectangle aWinRect(mpImplWin->GetPosPixel(), mpImplWin->GetSizePixel());
+ const bool bMousePositionedOverWin = aWinRect.Contains(rMousePos);
+
+ MouseWheelBehaviour nWheelBehavior( GetSettings().GetMouseSettings().GetWheelBehavior() );
+ if (bMousePositionedOverWin
+ && ((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)
+ }
+ }
+ }
+
+ if (rNEvt.GetType() == NotifyEventType::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))
+ {
+ GetWindow(GetWindowType::Border)->Invalidate(InvalidateFlags::NoErase);
+ }
+ }
+ }
+
+ 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();
+}
+
+void ListBox::SetHighlightColor(const Color& rColor)
+{
+ AllSettings aSettings(GetSettings());
+ StyleSettings aStyle(aSettings.GetStyleSettings());
+ aStyle.SetHighlightColor(rColor);
+ aSettings.SetStyleSettings(aStyle);
+ SetSettings(aSettings);
+
+ mpImplLB->SetHighlightColor(rColor);
+}
+
+void ListBox::SetHighlightTextColor(const Color& rColor)
+{
+ AllSettings aSettings(GetSettings());
+ StyleSettings aStyle(aSettings.GetStyleSettings());
+ aStyle.SetHighlightTextColor(rColor);
+ aSettings.SetStyleSettings(aStyle);
+ SetSettings(aSettings);
+
+ mpImplLB->SetHighlightTextColor(rColor);
+}
+
+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 OUString &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 0000000000..1d942d1e56
--- /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() == NotifyEventType::GETFOCUS )
+ {
+ MarkToBeReformatted( false );
+ }
+ else if( rNEvt.GetType() == NotifyEventType::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 0000000000..7545dba9b3
--- /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::PrepareExecute()
+{
+ if (!GetPopupMenu())
+ SetPopupMenu(VclPtr<PopupMenu>::Create());
+
+ MenuButton::PrepareExecute();
+
+ 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 0000000000..186340d8e0
--- /dev/null
+++ b/vcl/source/control/menubtn.cxx
@@ -0,0 +1,309 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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;
+
+ PrepareExecute();
+
+ 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);
+ }
+ }
+
+ Activate();
+
+ 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();
+}
+
+void MenuButton::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ PushButton::DumpAsPropertyTree(rJsonWriter);
+
+ if (mpMenu)
+ {
+ auto aMenuNode = rJsonWriter.startArray("menu");
+ for (int i = 0; i < mpMenu->GetItemCount(); i++)
+ {
+ auto aEntryNode = rJsonWriter.startStruct();
+ auto sId = mpMenu->GetItemId(i);
+ rJsonWriter.put("id", mpMenu->GetItemIdent(sId));
+ rJsonWriter.put("text", mpMenu->GetItemText(sId));
+ }
+ }
+}
+
+//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 0000000000..db336b1fe8
--- /dev/null
+++ b/vcl/source/control/notebookbar.cxx
@@ -0,0 +1,375 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <utility>
+
+#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/frame/FrameAction.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, css::frame::XFrameActionListener>
+{
+ bool mbActive;
+ VclPtr<NotebookBar> mpParent;
+ css::uno::Reference<css::frame::XFrame> mxFrame;
+public:
+ NotebookBarContextChangeEventListener(NotebookBar *p, css::uno::Reference<css::frame::XFrame> xFrame) :
+ mbActive(false),
+ mpParent(p),
+ mxFrame(std::move(xFrame))
+ {}
+
+ void setupFrameListener(bool bListen);
+ void setupListener(bool bListen);
+
+ // XContextChangeEventListener
+ virtual void SAL_CALL notifyContextChangeEvent(const css::ui::ContextChangeEventObject& rEvent) override;
+
+ // XFrameActionListener
+ virtual void SAL_CALL frameAction(const css::frame::FrameActionEvent& rEvent) override;
+
+ virtual void SAL_CALL disposing(const ::css::lang::EventObject&) override;
+};
+
+NotebookBar::NotebookBar(Window* pParent, const OUString& rID, const OUString& rUIXMLDescription,
+ const css::uno::Reference<css::frame::XFrame>& rFrame,
+ const NotebookBarAddonsItem& aNotebookBarAddonsItem)
+ : Control(pParent)
+ , m_pEventListener(new NotebookBarContextChangeEventListener(this, rFrame))
+ , m_pViewShell(nullptr)
+ , m_bIsWelded(false)
+ , m_sUIXMLDescription(rUIXMLDescription)
+{
+ m_pEventListener->setupFrameListener(true);
+
+ 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>(aName));
+ 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();
+
+ m_pEventListener->setupFrameListener(false);
+ m_pEventListener->setupListener(false);
+ m_pEventListener.clear();
+
+ Control::dispose();
+}
+
+bool NotebookBar::PreNotify(NotifyEvent& rNEvt)
+{
+ // capture KeyEvents for taskpane cycling
+ if (rNEvt.GetType() == NotifyEventType::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 NotebookBarContextChangeEventListener::setupListener(bool bListen)
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ return;
+
+ auto xMultiplexer(css::ui::ContextChangeEventMultiplexer::get(::comphelper::getProcessComponentContext()));
+
+ if (bListen)
+ {
+ try
+ {
+ xMultiplexer->addContextChangeEventListener(this, mxFrame->getController());
+ }
+ catch (const css::uno::Exception&)
+ {
+ }
+ }
+ else
+ xMultiplexer->removeAllContextChangeEventListeners(this);
+
+ mbActive = bListen;
+}
+
+void NotebookBarContextChangeEventListener::setupFrameListener(bool bListen)
+{
+ if (bListen)
+ mxFrame->addFrameActionListener(this);
+ else
+ mxFrame->removeFrameActionListener(this);
+}
+
+void SAL_CALL NotebookBarContextChangeEventListener::frameAction(const css::frame::FrameActionEvent& rEvent)
+{
+ if (!mbActive)
+ return;
+
+ if (rEvent.Action == css::frame::FrameAction_COMPONENT_REATTACHED)
+ {
+ setupListener(true);
+ }
+ else if (rEvent.Action == css::frame::FrameAction_COMPONENT_DETACHING)
+ {
+ setupListener(false);
+ // We don't want to give up on listening; just wait for
+ // another controller to be attached to the frame.
+ mbActive = true;
+ }
+}
+
+void NotebookBar::SetupListener(bool bListen)
+{
+ m_pEventListener->setupListener(bListen);
+}
+
+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 0000000000..e15c7c055d
--- /dev/null
+++ b/vcl/source/control/prgsbar.cxx
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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, ProgressBar::BarStyle eBarStyle )
+{
+ WinBits nOutStyle = nOrgStyle;
+ if( pParent && (nOrgStyle & WB_BORDER) != 0 )
+ {
+ if (pParent->IsNativeControlSupported(eBarStyle == ProgressBar::BarStyle::Progress
+ ? ControlType::Progress
+ : ControlType::LevelBar,
+ ControlPart::Entire))
+ nOutStyle &= WB_BORDER;
+ }
+ return nOutStyle;
+}
+
+Size ProgressBar::GetOptimalSize() const
+{
+ return meBarStyle == BarStyle::Progress ? Size(150, 20) : Size(150,10);
+}
+
+ProgressBar::ProgressBar( vcl::Window* pParent, WinBits nWinStyle, BarStyle eBarStyle ) :
+ Window( pParent, clearProgressBarBorder( pParent, nWinStyle, eBarStyle ) ),
+ meBarStyle(eBarStyle)
+{
+ 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(meBarStyle == BarStyle::Progress ? ControlType::Progress
+ : ControlType::LevelBar,
+ 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()),
+ meBarStyle == BarStyle::Progress ? ControlType::Progress : ControlType::LevelBar);
+}
+
+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 0000000000..777e00e0bc
--- /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 0000000000..a3d3251cea
--- /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 = OutputDevice::LogicToLogic(Point(ROADMAP_INDENT_X, 8), GetMapMode(), MapMode(MapUnit::MapAppFont));
+
+ Size aOutputSize(rRenderContext.GetOutputSize());
+
+ // 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() == NotifyEventType::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 0000000000..1b3c9e96b4
--- /dev/null
+++ b/vcl/source/control/roadmapwizard.cxx
@@ -0,0 +1,837 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <tools/json_writer.hxx>
+#include <osl/diagnose.h>
+
+#include <strings.hrc>
+#include <svdata.hxx>
+#include <wizdlg.hxx>
+
+#include <vector>
+
+#include "wizimpldata.hxx"
+#include <uiobject-internal.hxx>
+
+namespace vcl
+{
+ 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 OUString& _rId )
+ {
+ m_xRoadmapImpl->pRoadmap->SetHelpId( _rId );
+ }
+
+ void RoadmapWizard::SetRoadmapBitmap(const BitmapEx& rBmp)
+ {
+ m_xRoadmapImpl->pRoadmap->SetRoadmapBitmap(rBmp);
+ }
+
+ void RoadmapWizardMachine::SetRoadmapHelpId(const OUString& 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(OUString::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);
+ }
+
+ OUString sIdent(getPageIdentForState(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 OUString&, rCurItemId, bool)
+ {
+ WizardTypes::WizardState nSelectedState = getStateFromPageIdent(rCurItemId);
+
+ if (nSelectedState == 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( nSelectedState, 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(nSelectedState);
+ WizardTypes::WizardState nTemp = nSelectedState;
+ while( nTemp )
+ {
+ if( m_pImpl->aDisabledStates.find( --nTemp ) != m_pImpl->aDisabledStates.end() )
+ removePageFromHistory( nTemp );
+ }
+ }
+ else
+ bResult = skipBackwardUntil(nSelectedState);
+
+ 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(getPageIdentForState(_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
+ {
+ bool isButton(WindowType eType)
+ {
+ return eType == WindowType::PUSHBUTTON || eType == WindowType::OKBUTTON
+ || eType == WindowType::CANCELBUTTON || eType == WindowType::HELPBUTTON;
+ }
+ }
+
+ void RoadmapWizard::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+ {
+ rJsonWriter.put("id", get_id());
+ rJsonWriter.put("type", "dialog");
+ rJsonWriter.put("title", GetText());
+
+ OUString sDialogId = GetHelpId();
+ 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 : m_xRoadmapImpl->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());
+
+ {
+ auto childrenNode = rJsonWriter.startArray("children");
+
+ auto containerNode = rJsonWriter.startStruct();
+ rJsonWriter.put("id", "container");
+ rJsonWriter.put("type", "container");
+ rJsonWriter.put("vertical", true);
+
+ {
+ auto containerChildrenNode = rJsonWriter.startArray("children");
+
+ // tabpages
+ for (int i = 0; i < GetChildCount(); i++)
+ {
+ vcl::Window* pChild = GetChild(i);
+
+ if (!isButton(pChild->GetType()) && pChild != mpViewWindow)
+ {
+ auto childNode = rJsonWriter.startStruct();
+ pChild->DumpAsPropertyTree(rJsonWriter);
+ }
+ }
+
+ // buttons
+ {
+ auto buttonsNode = rJsonWriter.startStruct();
+ rJsonWriter.put("id", "buttons");
+ rJsonWriter.put("type", "buttonbox");
+ rJsonWriter.put("layoutstyle", "end");
+ {
+ auto buttonsChildrenNode = rJsonWriter.startArray("children");
+ for (int i = 0; i < GetChildCount(); i++)
+ {
+ vcl::Window* pChild = GetChild(i);
+
+ if (isButton(pChild->GetType()))
+ {
+ auto childNode = rJsonWriter.startStruct();
+ pChild->DumpAsPropertyTree(rJsonWriter);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+} // 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 0000000000..b652360139
--- /dev/null
+++ b/vcl/source/control/scrbar.cxx
@@ -0,0 +1,1473 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/timer.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/toolkit/scrbar.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::Normalize( 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::Normalize( 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() > 5 )
+ {
+ aRect.AdjustLeft(2 );
+ aRect.AdjustRight( -2 );
+ }
+ if( aRect.GetHeight() > 5 )
+ {
+ 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() );
+ if (mpData->maTimer.GetTimeout() != STYLE_CURSOR_NOBLINKTIME)
+ 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() == NotifyEventType::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.Normalize();
+ 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();
+}
+
+bool ScrollBar::Inactive() const
+{
+ return !IsEnabled() || !IsInputEnabled() || IsInModalMode();
+}
+
+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 0000000000..3a119ea4f5
--- /dev/null
+++ b/vcl/source/control/slider.cxx
@@ -0,0 +1,906 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 ||
+ nType == StateChangedType::ControlFocus )
+ {
+ 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.Normalize();
+ 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 0000000000..d56138c6cd
--- /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.Normalize();
+ 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() == NotifyEventType::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 0000000000..973825a3a9
--- /dev/null
+++ b/vcl/source/control/spinfld.cxx
@@ -0,0 +1,1028 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::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()) )
+ {
+ if (!IsNativeWidgetEnabled() ||
+ !IsNativeControlSupported(ControlType::Editbox, ControlPart::Entire))
+ {
+ // 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 0000000000..cafbae9862
--- /dev/null
+++ b/vcl/source/control/tabctrl.cxx
@@ -0,0 +1,2402 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/mnemonic.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>
+
+#define TAB_OFFSET 3
+/// Space to the left and right of the tabitem
+#define TAB_ITEM_OFFSET_X 10
+/// Space to the top and bottom of the tabitem
+#define TAB_ITEM_OFFSET_Y 3
+#define TAB_EXTRASPACE_X 6
+#define TAB_BORDER_LEFT 1
+#define TAB_BORDER_TOP 1
+#define TAB_BORDER_RIGHT 2
+#define TAB_BORDER_BOTTOM 2
+
+class ImplTabItem final
+{
+ sal_uInt16 m_nId;
+
+public:
+ VclPtr<TabPage> mpTabPage;
+ OUString maText;
+ OUString maFormatText;
+ OUString maHelpText;
+ OUString maAccessibleName;
+ OUString maAccessibleDescription;
+ OUString 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::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() );
+ }
+}
+
+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 ) );
+
+ // 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_ITEM_OFFSET_X*2 );
+ aSize.AdjustHeight(TAB_ITEM_OFFSET_Y*2 );
+
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), aSize );
+ tools::Rectangle aBoundingRgn, aContentRgn;
+ const TabitemValue aControlValue(tools::Rectangle(TAB_ITEM_OFFSET_X, TAB_ITEM_OFFSET_Y,
+ aSize.Width() - TAB_ITEM_OFFSET_X * 2,
+ aSize.Height() - TAB_ITEM_OFFSET_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_ITEM_OFFSET_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.GetWidth();
+ }
+ 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 )
+{
+ 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({});
+ }
+
+ 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_ITEM_OFFSET_X,
+ pItem->maRect.Top() + TAB_ITEM_OFFSET_Y,
+ pItem->maRect.Right() - TAB_ITEM_OFFSET_X,
+ pItem->maRect.Bottom() - TAB_ITEM_OFFSET_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 (mpTabCtrlData->maItemList.size())
+ {
+ tools::Long nLeft = LONG_MAX;
+ tools::Long nRight = 0;
+ for (const auto &item : mpTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+ nRight = std::max(nRight, item.maRect.Right());
+ nLeft = std::min(nLeft, item.maRect.Left());
+ }
+ aHeaderRect.SetLeft(nLeft);
+ aHeaderRect.SetRight(nRight);
+ }
+
+ if (bPaneWithHeader)
+ aRect.SetTop(0);
+
+ 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)
+{
+ 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(), {});
+ }
+
+ 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() == NotifyEventType::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() == NotifyEventType::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 )
+{
+ 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();
+
+ 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();
+
+ 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 OUString& 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;
+
+ 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();
+ 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 removeMnemonicFromString(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 OUString& rName ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ if ( pItem )
+ pItem->maTabName = rName;
+}
+
+OUString TabControl::GetPageName( sal_uInt16 nPageId ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ if (pItem)
+ return pItem->maTabName;
+
+ return {};
+}
+
+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::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 OUString &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 (auto id : GetPageIDs())
+ {
+ TabPage* pChild = GetTabPage(id);
+
+ 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;
+
+ TabPage* pPage = GetTabPage(mnCurPageId);
+ // Try to stay on the current tab (unless the new context has a special tab)
+ if (pPage && eLastContext != vcl::EnumContext::Context::Any
+ && pPage->HasContext(vcl::EnumContext::Context::Any) && pPage->IsEnabled())
+ {
+ bHandled = true;
+ }
+
+ for (int nChild = 0; nChild < GetPageCount(); ++nChild)
+ {
+ sal_uInt16 nPageId = TabControl::GetPageId(nChild);
+ pPage = GetTabPage(nPageId);
+
+ if (!pPage)
+ continue;
+
+ SetPageVisible(nPageId, pPage->HasContext(eContext) || pPage->HasContext(vcl::EnumContext::Context::Any));
+
+ if (eContext != vcl::EnumContext::Context::Any
+ && (!bHandled || !pPage->HasContext(vcl::EnumContext::Context::Any))
+ && pPage->HasContext(eContext))
+ {
+ SetCurPageId(nPageId);
+ bHandled = true;
+ bLastContextWasSupported = true;
+ }
+
+ if (!bHandled && bLastContextWasSupported
+ && pPage->HasContext(vcl::EnumContext::Context::Default))
+ {
+ SetCurPageId(nPageId);
+ }
+ }
+
+ 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 0000000000..075e40d2de
--- /dev/null
+++ b/vcl/source/control/throbber.cxx
@@ -0,0 +1,234 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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.SetTimeout(mnStepTime);
+ 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;
+
+ sal_Unicode const* const pResolutions[] = { u"16", u"32", u"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( OUString::Concat("private:graphicrepository/vcl/res/spinner-")
+ + pResolutions[index]
+ + "-" );
+ if ( i < 9 )
+ aURL.append( "0" );
+ aURL.append( OUString::number( sal_Int32( i + 1 ) ) + ".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 0000000000..33d3d55f31
--- /dev/null
+++ b/vcl/source/control/thumbpos.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
+
+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);
+}
+
+/* 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 0000000000..68dd66004a
--- /dev/null
+++ b/vcl/source/control/wizardmachine.cxx
@@ -0,0 +1,1436 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <comphelper/diagnose_ex.hxx>
+#include <strings.hrc>
+#include <svdata.hxx>
+#include <wizdlg.hxx>
+#include <stack>
+#include "wizimpldata.hxx"
+
+constexpr OUString HID_WIZARD_NEXT = u"SVT_HID_WIZARD_NEXT"_ustr;
+constexpr OUString HID_WIZARD_PREVIOUS = u"SVT_HID_WIZARD_PREVIOUS"_ustr;
+
+#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 OUString& 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::AddButtonResponse( Button* pButton, int response)
+ {
+ m_xRoadmapImpl->maResponses[pButton] = response;
+ }
+
+ 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();
+ m_pHelp->set_id("help");
+ AddButtonResponse(m_pHelp, RET_HELP);
+ 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() == NotifyEventType::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
+ {
+ pPage->Hide();
+ 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();
+ }
+
+ OUString WizardMachine::getPageIdentForState(WizardTypes::WizardState nState) const
+ {
+ return OUString::number(nState);
+ }
+
+ WizardTypes::WizardState WizardMachine::getStateFromPageIdent(const OUString& rIdent) const
+ {
+ return rIdent.toInt32();
+ }
+
+ 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(getPageIdentForState(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 0000000000..9502e8ba4e
--- /dev/null
+++ b/vcl/source/control/wizimpldata.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 .
+ */
+
+#pragma once
+
+#include <stack>
+#include <map>
+#include <set>
+#include <vcl/toolkit/roadmap.hxx>
+
+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 )
+ {
+ }
+ };
+
+ 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;
+ std::map<VclPtr<vcl::Window>, short> maResponses;
+ 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 );
+ };
+} // namespace svt
+
+/* 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 0000000000..e0174848f7
--- /dev/null
+++ b/vcl/source/edit/textdat2.hxx
@@ -0,0 +1,281 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/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 );
+ }
+};
+
+/* 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 0000000000..bf29c1443d
--- /dev/null
+++ b/vcl/source/edit/textdata.cxx
@@ -0,0 +1,345 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <utility>
+#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, OUString _aOldTextAfterStartPos )
+ : aOldTextAfterStartPos(std::move(_aOldTextAfterStartPos))
+ , 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 0000000000..a6d7bd8c24
--- /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>
+#include <utility>
+
+// 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()
+{
+ std::erase_if(
+ maAttribs,
+ [] (const std::unique_ptr<TextCharAttrib>& rAttrib) { return rAttrib->IsEmpty(); } );
+ mbHasEmptyAttribs = false;
+}
+
+TextNode::TextNode( OUString aText ) :
+ maText(std::move( aText ))
+{
+}
+
+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, std::u16string_view rText )
+{
+ maText = maText.replaceAt( nPos, 0, rText );
+ ExpandAttribs( nPos, rText.size() );
+}
+
+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, std::u16string_view rStr )
+{
+ SAL_WARN_IF( rStr.find( 0x0A ) != std::u16string_view::npos, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
+ SAL_WARN_IF( rStr.find( 0x0D ) != std::u16string_view::npos, "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.size() );
+ 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 0000000000..3dd0dce8d2
--- /dev/null
+++ b/vcl/source/edit/textdoc.hxx
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <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( OUString aText );
+
+ 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, std::u16string_view 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, std::u16string_view 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 );
+};
+
+/* 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 0000000000..6bf7eddc15
--- /dev/null
+++ b/vcl/source/edit/texteng.cxx
@@ -0,0 +1,2894 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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
+{
+ // get the index that really is first
+ const sal_Int32 nFirstPos = std::min(rCurSel.GetStart().GetIndex(), rCurSel.GetEnd().GetIndex());
+
+ bool bIsSequenceChecking =
+ SvtCTLOptions::IsCTLFontEnabled() &&
+ SvtCTLOptions::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();
+
+ if (xISC.is())
+ {
+ sal_Int32 nTmpPos = aPaM.GetIndex();
+ sal_Int16 nCheckMode = SvtCTLOptions::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 (SvtCTLOptions::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;
+ }
+ }
+
+ assert(pTextPortion && "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 )
+ {
+ assert(pPortion);
+
+ 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 0000000000..7aea9af6b8
--- /dev/null
+++ b/vcl/source/edit/textund2.hxx
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 "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, OUString aStr );
+
+ 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, OUString aStr );
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual OUString GetComment () const override;
+};
+
+/* 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 0000000000..e53e60e5d5
--- /dev/null
+++ b/vcl/source/edit/textundo.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 "textundo.hxx"
+#include "textund2.hxx"
+#include <strings.hrc>
+
+#include <sal/log.hxx>
+#include <utility>
+#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());
+ // coverity[leaked_storage : FALSE] - ownership transferred to this with mbDelObject
+ 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, OUString aStr )
+ : TextUndo( pTextEngine ),
+ maTextPaM( rTextPaM ), maText(std::move( aStr ))
+{
+}
+
+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, OUString aStr )
+ : TextUndo( pTextEngine ),
+ maTextPaM( rTextPaM ), maText(std::move( aStr ))
+{
+}
+
+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 0000000000..571a16c2ce
--- /dev/null
+++ b/vcl/source/edit/textundo.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <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;
+};
+
+/* 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 0000000000..ad1d28d199
--- /dev/null
+++ b/vcl/source/edit/textview.cxx
@@ -0,0 +1,2238 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <utility>
+#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( OUString aText ) : maText(std::move( aText ))
+{
+}
+
+// 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( u"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( u"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( OUStringChar(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( std::u16string_view rNewText ) const
+{
+ bool bOK = true;
+ if ( mpImpl->mpTextEngine->GetMaxTextLen() )
+ {
+ sal_Int32 n = mpImpl->mpTextEngine->GetTextLen() + rNewText.size();
+ 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::SearchOptions2& 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::SearchOptions2& 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 0000000000..8e979c1e3f
--- /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 0000000000..d519735859
--- /dev/null
+++ b/vcl/source/edit/vclmedit.cxx
@@ -0,0 +1,1509 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/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/settings.hxx>
+#include <vcl/toolkit/scrbar.hxx>
+#include <vcl/toolkit/vclmedit.hxx>
+#include <vcl/weld.hxx>
+#include <osl/diagnose.h>
+#include <tools/json_writer.hxx>
+#include <strings.hrc>
+#include <svdata.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::GesturePan)
+ {
+ 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)
+ {
+ if (!rKEvent.GetKeyCode().IsMod1())
+ bDone = mpExtTextView->KeyInput(rKEvent);
+ else
+ {
+ // tdf#107625 make ctrl+tab act like tab when MultiLine Edit normally accepts tab as an input char
+ vcl::KeyCode aKeyCode(rKEvent.GetKeyCode().GetCode(), rKEvent.GetKeyCode().GetModifier() & ~KEY_MOD1);
+ KeyEvent aKEventWithoutMod1(rKEvent.GetCharCode(), aKeyCode, rKEvent.GetRepeat());
+ Window::KeyInput(aKEventWithoutMod1);
+ bDone = true;
+ }
+ }
+ }
+ 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(u"cut"), bEnableCut);
+ pPopup->EnableItem(pPopup->GetItemId(u"copy"), bEnableCopy);
+ pPopup->EnableItem(pPopup->GetItemId(u"delete"), bEnableDelete);
+ pPopup->EnableItem(pPopup->GetItemId(u"paste"), bEnablePaste);
+ pPopup->SetItemText(pPopup->GetItemId(u"specialchar"),
+ BuilderUtils::convertMnemonicMarkup(VclResId(STR_SPECIAL_CHARACTER_MENU_ENTRY)));
+ pPopup->EnableItem(pPopup->GetItemId(u"specialchar"), bEnableSpecialChar);
+ pPopup->EnableItem(pPopup->GetItemId(u"undo"), bEnableUndo);
+ pPopup->ShowItem(pPopup->GetItemId(u"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 );
+ OUString 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() == NotifyEventType::COMMAND )
+ {
+ bDone = pImpVclMEdit->HandleCommand( *rNEvt.GetCommandEvent() );
+ }
+ return bDone || Edit::EventNotify( rNEvt );
+}
+
+bool VclMultiLineEdit::PreNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+
+ if( ( rNEvt.GetType() == NotifyEventType::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 OUString &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 0000000000..87097a5be2
--- /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::SearchOptions2& 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::SearchOptions2 aOptions( rSearchOptions );
+ aOptions.Locale = Application::GetSettings().GetLanguageTag().getLocale();
+ utl::TextSearch aSearcher(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 0000000000..fd3ce0cea8
--- /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()
+{
+ static constexpr OUStringLiteral STYPE ( u"Type" );
+ static constexpr OUStringLiteral SUINAME ( u"UIName" );
+ static constexpr OUStringLiteral SFLAGS ( u"Flags" );
+ static constexpr OUStringLiteral SMEDIATYPE ( u"MediaType" );
+ static constexpr OUStringLiteral SEXTENSIONS ( u"Extensions" );
+ static constexpr OUStringLiteral SFORMATNAME ( u"FormatName" );
+ static constexpr OUStringLiteral SREALFILTERNAME ( u"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 0000000000..95e52d80ec
--- /dev/null
+++ b/vcl/source/filter/FilterConfigCache.hxx
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <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();
+};
+
+/* 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 0000000000..41eaac04e1
--- /dev/null
+++ b/vcl/source/filter/FilterConfigItem.cxx
@@ -0,0 +1,404 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#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, std::u16string_view rTree )
+{
+ bool bAvailable = !rTree.empty();
+ if ( bAvailable )
+ {
+ sal_Int32 nIdx{0};
+ if ( rTree[0] == '/' )
+ ++nIdx;
+
+ // creation arguments: nodepath
+ PropertyValue aPathArgument = comphelper::makePropertyValue("nodepath",
+ OUString(o3tl::getToken(rTree, 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.size();
+ while (bAvailable && nIdx>=0 && nIdx<nEnd)
+ {
+ Reference< XHierarchicalNameAccess > xHierarchicalNameAccess
+ ( xReadAccess, UNO_QUERY );
+
+ if ( !xHierarchicalNameAccess.is() )
+ bAvailable = false;
+ else
+ {
+ const OUString aNode( o3tl::getToken(rTree, 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::IsReadOnly(const OUString& rName)
+{
+ if (!xPropSet.is())
+ return false;
+
+ const Reference<XPropertySetInfo> xInfo(xPropSet->getPropertySetInfo());
+ if (!xInfo.is() || !xInfo->hasPropertyByName(rName))
+ return false;
+
+ const css::beans::Property aProp(xInfo->getPropertyByName(rName));
+ return (aProp.Attributes & PropertyAttribute::READONLY);
+}
+
+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 0000000000..a878a8ca11
--- /dev/null
+++ b/vcl/source/filter/GraphicFormatDetector.cxx
@@ -0,0 +1,1428 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/filter/PngImageReader.hxx>
+#include <graphic/GraphicFormatDetector.hxx>
+#include <graphic/DetectorTools.hxx>
+#include <tools/solar.h>
+#include <tools/zcodec.hxx>
+#include <tools/fract.hxx>
+#include <filter/WebpReader.hxx>
+#include <vcl/TypeSerializer.hxx>
+#include <vcl/outdev.hxx>
+#include <utility>
+
+constexpr sal_uInt32 SVG_CHECK_SIZE = 8192;
+constexpr sal_uInt32 WMF_EMF_CHECK_SIZE = 44;
+
+namespace
+{
+class SeekGuard
+{
+public:
+ SeekGuard(SvStream& rStream, sal_uInt64 nStartPosition)
+ : mrStream(rStream)
+ , mnStartPosition(nStartPosition)
+ {
+ }
+ ~SeekGuard() { mrStream.Seek(mnStartPosition); }
+
+private:
+ SvStream& mrStream;
+ sal_uInt64 mnStartPosition;
+};
+}
+
+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 = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("BMP"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkBMP())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("WMF") || rFormatExtension.startsWith("WMZ")
+ || rFormatExtension.startsWith("EMF") || rFormatExtension.startsWith("EMZ"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkWMF() || aDetector.checkEMF())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PCX"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPCX())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("TIF"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkTIF())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("GIF"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkGIF())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("APNG"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkAPNG())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PNG"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPNG())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("JPG"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkJPG())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("SVM"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkSVM())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PCD"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPCD())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PSD"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPSD())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("EPS"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkEPS())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("DXF"))
+ {
+ if (aDetector.checkDXF())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PCT"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPCT())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PBM") || rFormatExtension.startsWith("PGM")
+ || rFormatExtension.startsWith("PPM"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPBM() || aDetector.checkPGM() || aDetector.checkPPM())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("RAS"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkRAS())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest)
+ {
+ bSomethingTested = true;
+ if (aDetector.checkXPM())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+ else if (rFormatExtension.startsWith("XPM"))
+ {
+ return true;
+ }
+
+ if (!bTest)
+ {
+ if (aDetector.checkXBM())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+ else if (rFormatExtension.startsWith("XBM"))
+ {
+ return true;
+ }
+
+ if (!bTest)
+ {
+ if (aDetector.checkSVG())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+ else if (rFormatExtension.startsWith("SVG"))
+ {
+ return true;
+ }
+
+ if (!bTest || rFormatExtension.startsWith("TGA"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkTGA())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("MOV"))
+ {
+ if (aDetector.checkMOV())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PDF"))
+ {
+ if (aDetector.checkPDF())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("WEBP"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkWEBP())
+ {
+ rFormatExtension = getImportFormatShortName(aDetector.getMetadata().mnFormat);
+ 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 aFormatExtension,
+ bool bExtendedInfo)
+ : mrStream(rStream)
+ , maExtension(std::move(aFormatExtension))
+ , mnFirstLong(0)
+ , mnSecondLong(0)
+ , mnStreamPosition(0)
+ , mnStreamLength(0)
+ , mbExtendedInfo(bExtendedInfo)
+ , mbWasCompressed(false)
+ , maMetadata()
+{
+}
+
+bool GraphicFormatDetector::detect()
+{
+ maFirstBytes.clear();
+ maFirstBytes.resize(256, 0);
+
+ mnFirstLong = 0;
+ mnSecondLong = 0;
+
+ mnStreamPosition = mrStream.Tell();
+ mnStreamLength = mrStream.remainingSize();
+ SeekGuard aGuard(mrStream, mnStreamPosition);
+
+ 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;
+ SeekGuard aGuard(mrStream, mnStreamPosition);
+ 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;
+
+ maMetadata.mnFormat = GraphicFileFormat::MET;
+ return true;
+}
+
+bool GraphicFormatDetector::checkBMP()
+{
+ SeekGuard aGuard(mrStream, mnStreamPosition);
+ bool bRet = false;
+ 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)
+ {
+ maMetadata.mnFormat = GraphicFileFormat::BMP;
+ bRet = true;
+ if (mbExtendedInfo)
+ {
+ sal_uInt32 nTemp32;
+ sal_uInt16 nTemp16;
+ sal_uInt32 nCompression;
+
+ mrStream.SetEndian(SvStreamEndian::LITTLE);
+ mrStream.Seek(mnStreamPosition + nOffset + 2);
+
+ // up to first info
+ mrStream.SeekRel(0x10);
+
+ // Pixel width
+ mrStream.ReadUInt32(nTemp32);
+ maMetadata.maPixSize.setWidth(nTemp32);
+
+ // Pixel height
+ mrStream.ReadUInt32(nTemp32);
+ maMetadata.maPixSize.setHeight(nTemp32);
+
+ // Planes
+ mrStream.ReadUInt16(nTemp16);
+ maMetadata.mnPlanes = nTemp16;
+
+ // BitCount
+ mrStream.ReadUInt16(nTemp16);
+ maMetadata.mnBitsPerPixel = nTemp16;
+
+ // Compression
+ mrStream.ReadUInt32(nTemp32);
+ nCompression = nTemp32;
+
+ // logical width
+ mrStream.SeekRel(4);
+ mrStream.ReadUInt32(nTemp32);
+ sal_uInt32 nXPelsPerMeter = 0;
+ if (nTemp32)
+ {
+ maMetadata.maLogSize.setWidth((maMetadata.maPixSize.Width() * 100000)
+ / nTemp32);
+ nXPelsPerMeter = nTemp32;
+ }
+
+ // logical height
+ mrStream.ReadUInt32(nTemp32);
+ sal_uInt32 nYPelsPerMeter = 0;
+ if (nTemp32)
+ {
+ maMetadata.maLogSize.setHeight((maMetadata.maPixSize.Height() * 100000)
+ / nTemp32);
+ nYPelsPerMeter = nTemp32;
+ }
+
+ // further validation, check for rational values
+ if ((maMetadata.mnBitsPerPixel > 24) || (nCompression > 3))
+ {
+ maMetadata.mnFormat = GraphicFileFormat::NOT;
+ bRet = false;
+ }
+
+ if (bRet && nXPelsPerMeter && nYPelsPerMeter)
+ {
+ maMetadata.maPreferredMapMode
+ = MapMode(MapUnit::MapMM, Point(), Fraction(1000, nXPelsPerMeter),
+ Fraction(1000, nYPelsPerMeter));
+
+ maMetadata.maPreferredLogSize
+ = Size(maMetadata.maPixSize.getWidth(), maMetadata.maPixSize.getHeight());
+ }
+ }
+ }
+ }
+ return bRet;
+}
+
+bool GraphicFormatDetector::checkWMF()
+{
+ 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
+ checkAndUncompressBuffer(sExtendedOrDecompressedFirstBytes, WMF_EMF_CHECK_SIZE,
+ nDecompressedSize);
+ if (mnFirstLong == 0xd7cdc69a || mnFirstLong == 0x01000900)
+ {
+ if (mbWasCompressed)
+ maMetadata.mnFormat = GraphicFileFormat::WMZ;
+ else
+ maMetadata.mnFormat = GraphicFileFormat::WMF;
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkEMF()
+{
+ 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 -> emz
+ sal_uInt8* pCheckArray = checkAndUncompressBuffer(sExtendedOrDecompressedFirstBytes,
+ WMF_EMF_CHECK_SIZE, nDecompressedSize);
+ if (mnFirstLong == 0x01000000 && pCheckArray[40] == 0x20 && pCheckArray[41] == 0x45
+ && pCheckArray[42] == 0x4d && pCheckArray[43] == 0x46)
+ {
+ if (mbWasCompressed)
+ maMetadata.mnFormat = GraphicFileFormat::EMZ;
+ else
+ maMetadata.mnFormat = GraphicFileFormat::EMF;
+ if (mbExtendedInfo)
+ {
+ sal_Int32 nBoundLeft = 0, nBoundTop = 0, nBoundRight = 0, nBoundBottom = 0;
+ sal_Int32 nFrameLeft = 0, nFrameTop = 0, nFrameRight = 0, nFrameBottom = 0;
+ nBoundLeft = pCheckArray[8] | (pCheckArray[9] << 8) | (pCheckArray[10] << 16)
+ | (pCheckArray[11] << 24);
+ nBoundTop = pCheckArray[12] | (pCheckArray[13] << 8) | (pCheckArray[14] << 16)
+ | (pCheckArray[15] << 24);
+ nBoundRight = pCheckArray[16] | (pCheckArray[17] << 8) | (pCheckArray[18] << 16)
+ | (pCheckArray[19] << 24);
+ nBoundBottom = pCheckArray[20] | (pCheckArray[21] << 8) | (pCheckArray[22] << 16)
+ | (pCheckArray[23] << 24);
+ nFrameLeft = pCheckArray[24] | (pCheckArray[25] << 8) | (pCheckArray[26] << 16)
+ | (pCheckArray[27] << 24);
+ nFrameTop = pCheckArray[28] | (pCheckArray[29] << 8) | (pCheckArray[30] << 16)
+ | (pCheckArray[31] << 24);
+ nFrameRight = pCheckArray[32] | (pCheckArray[33] << 8) | (pCheckArray[34] << 16)
+ | (pCheckArray[35] << 24);
+ nFrameBottom = pCheckArray[36] | (pCheckArray[37] << 8) | (pCheckArray[38] << 16)
+ | (pCheckArray[39] << 24);
+ // size in pixels
+ maMetadata.maPixSize.setWidth(nBoundRight - nBoundLeft + 1);
+ maMetadata.maPixSize.setHeight(nBoundBottom - nBoundTop + 1);
+ // size in 0.01mm units
+ maMetadata.maLogSize.setWidth(nFrameRight - nFrameLeft + 1);
+ maMetadata.maLogSize.setHeight(nFrameBottom - nFrameTop + 1);
+ }
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPCX()
+{
+ // ! 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;
+ SeekGuard aGuard(mrStream, mrStream.Tell());
+ mrStream.SetEndian(SvStreamEndian::LITTLE);
+ mrStream.ReadUChar(cByte);
+ if (cByte == 0x0a)
+ {
+ maMetadata.mnFormat = GraphicFileFormat::PCX;
+ mrStream.SeekRel(1);
+ // compression
+ mrStream.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
+ mrStream.ReadUChar(cByte);
+ maMetadata.mnBitsPerPixel = cByte;
+
+ // image dimensions
+ mrStream.ReadUInt16(nTemp16);
+ nXmin = nTemp16;
+ mrStream.ReadUInt16(nTemp16);
+ nYmin = nTemp16;
+ mrStream.ReadUInt16(nTemp16);
+ nXmax = nTemp16;
+ mrStream.ReadUInt16(nTemp16);
+ nYmax = nTemp16;
+
+ maMetadata.maPixSize.setWidth(nXmax - nXmin + 1);
+ maMetadata.maPixSize.setHeight(nYmax - nYmin + 1);
+
+ // resolution
+ mrStream.ReadUInt16(nTemp16);
+ nDPIx = nTemp16;
+ mrStream.ReadUInt16(nTemp16);
+ nDPIy = nTemp16;
+
+ // set logical size
+ MapMode aMap(MapUnit::MapInch, Point(), Fraction(1, nDPIx), Fraction(1, nDPIy));
+ maMetadata.maLogSize = OutputDevice::LogicToLogic(maMetadata.maPixSize, aMap,
+ MapMode(MapUnit::Map100thMM));
+
+ // number of color planes
+ cByte = 5; // Illegal value in case of EOF.
+ mrStream.SeekRel(49);
+ mrStream.ReadUChar(cByte);
+ maMetadata.mnPlanes = cByte;
+
+ bRet = (maMetadata.mnPlanes <= 4);
+ }
+ }
+ return bRet;
+}
+
+bool GraphicFormatDetector::checkTIF()
+{
+ SeekGuard aGuard(mrStream, mrStream.Tell());
+ mrStream.Seek(mnStreamPosition);
+ bool bRet = false;
+ sal_uInt8 cByte1 = 0;
+ sal_uInt8 cByte2 = 1;
+
+ mrStream.ReadUChar(cByte1);
+ mrStream.ReadUChar(cByte2);
+ if (cByte1 == cByte2)
+ {
+ bool bDetectOk = false;
+
+ if (cByte1 == 0x49)
+ {
+ mrStream.SetEndian(SvStreamEndian::LITTLE);
+ bDetectOk = true;
+ }
+ else if (cByte1 == 0x4d)
+ {
+ mrStream.SetEndian(SvStreamEndian::BIG);
+ bDetectOk = true;
+ }
+
+ if (bDetectOk)
+ {
+ sal_uInt16 nTemp16 = 0;
+ sal_uInt32 nTemp32 = 0;
+
+ mrStream.ReadUInt16(nTemp16);
+ if (nTemp16 == 0x2a)
+ {
+ maMetadata.mnFormat = GraphicFileFormat::TIF;
+ bRet = true;
+
+ if (mbExtendedInfo)
+ {
+ sal_uInt32 nIfdOffset = 0;
+
+ // Offset of the first IFD
+ mrStream.ReadUInt32(nIfdOffset);
+ mrStream.SeekRel(nIfdOffset - 8); // read 6 bytes until here
+
+ sal_uInt16 nNumberOfTags = 0;
+ mrStream.ReadUInt16(nNumberOfTags);
+
+ bool bOk = true;
+ sal_Int32 nCount = 0;
+
+ // read tags till we find Tag256(Width)
+ mrStream.ReadUInt16(nTemp16);
+ while (nTemp16 != 256 && bOk)
+ {
+ mrStream.SeekRel(10);
+ mrStream.ReadUInt16(nTemp16);
+ nCount++;
+ if (nCount > nNumberOfTags)
+ bOk = false;
+ }
+
+ if (bOk)
+ {
+ // width
+ mrStream.ReadUInt16(nTemp16);
+ mrStream.SeekRel(4);
+ if (nTemp16 == 3)
+ {
+ mrStream.ReadUInt16(nTemp16);
+ maMetadata.maPixSize.setWidth(nTemp16);
+ mrStream.SeekRel(2);
+ }
+ else
+ {
+ mrStream.ReadUInt32(nTemp32);
+ maMetadata.maPixSize.setWidth(nTemp32);
+ }
+
+ // height
+ mrStream.SeekRel(2);
+ mrStream.ReadUInt16(nTemp16);
+ mrStream.SeekRel(4);
+ if (nTemp16 == 3)
+ {
+ mrStream.ReadUInt16(nTemp16);
+ maMetadata.maPixSize.setHeight(nTemp16);
+ mrStream.SeekRel(2);
+ }
+ else
+ {
+ mrStream.ReadUInt32(nTemp32);
+ maMetadata.maPixSize.setHeight(nTemp32);
+ }
+
+ // Bits/Pixel
+ mrStream.ReadUInt16(nTemp16);
+ if (nTemp16 == 258)
+ {
+ mrStream.SeekRel(6);
+ mrStream.ReadUInt16(nTemp16);
+ maMetadata.mnBitsPerPixel = nTemp16;
+ mrStream.SeekRel(2);
+ }
+ else
+ mrStream.SeekRel(-2);
+
+ // compression
+ mrStream.ReadUInt16(nTemp16);
+ if (nTemp16 == 259)
+ {
+ mrStream.SeekRel(6);
+ mrStream.ReadUInt16(nTemp16); // compression
+ mrStream.SeekRel(2);
+ }
+ else
+ mrStream.SeekRel(-2);
+ }
+ }
+ }
+ }
+ }
+ return bRet;
+}
+
+bool GraphicFormatDetector::checkGIF()
+{
+ if (mnFirstLong == 0x47494638 && (maFirstBytes[4] == 0x37 || maFirstBytes[4] == 0x39)
+ && maFirstBytes[5] == 0x61)
+ {
+ maMetadata.mnFormat = GraphicFileFormat::GIF;
+ if (mbExtendedInfo)
+ {
+ sal_uInt16 nWidth = maFirstBytes[6] | (maFirstBytes[7] << 8);
+ sal_uInt16 nHeight = maFirstBytes[8] | (maFirstBytes[9] << 8);
+ maMetadata.maPixSize = Size(nWidth, nHeight);
+ maMetadata.mnBitsPerPixel = ((maFirstBytes[10] & 112) >> 4) + 1;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPNG()
+{
+ SeekGuard aGuard(mrStream, mnStreamPosition);
+ uint64_t nSignature = (static_cast<uint64_t>(mnFirstLong) << 32) | mnSecondLong;
+ if (nSignature == PNG_SIGNATURE)
+ {
+ maMetadata.mnFormat = GraphicFileFormat::PNG;
+ if (mbExtendedInfo)
+ {
+ sal_uInt32 nTemp32;
+ mrStream.Seek(mnStreamPosition + PNG_SIGNATURE_SIZE);
+ do
+ {
+ sal_uInt8 cByte = 0;
+
+ // IHDR-Chunk
+ mrStream.SeekRel(8);
+
+ // width
+ mrStream.ReadUInt32(nTemp32);
+ if (!mrStream.good())
+ break;
+ maMetadata.maPixSize.setWidth(nTemp32);
+
+ // height
+ mrStream.ReadUInt32(nTemp32);
+ if (!mrStream.good())
+ break;
+ maMetadata.maPixSize.setHeight(nTemp32);
+
+ // Bits/Pixel
+ mrStream.ReadUChar(cByte);
+ if (!mrStream.good())
+ break;
+ maMetadata.mnBitsPerPixel = cByte;
+
+ // Colour type - check whether it supports alpha values
+ sal_uInt8 cColType = 0;
+ mrStream.ReadUChar(cColType);
+ if (!mrStream.good())
+ break;
+ maMetadata.mbIsAlpha = maMetadata.mbIsTransparent
+ = (cColType == 4 || cColType == 6);
+
+ // Planes always 1;
+ // compression always
+ maMetadata.mnPlanes = 1;
+
+ sal_uInt32 nLen32 = 0;
+ nTemp32 = 0;
+
+ mrStream.SeekRel(7);
+
+ // read up to the start of the image
+ mrStream.ReadUInt32(nLen32);
+ mrStream.ReadUInt32(nTemp32);
+ while (mrStream.good() && nTemp32 != PNG_IDAT_SIGNATURE)
+ {
+ if (nTemp32 == PNG_PHYS_SIGNATURE) // physical pixel dimensions
+ {
+ sal_uLong nXRes;
+ sal_uLong nYRes;
+
+ // horizontal resolution
+ nTemp32 = 0;
+ mrStream.ReadUInt32(nTemp32);
+ nXRes = nTemp32;
+
+ // vertical resolution
+ nTemp32 = 0;
+ mrStream.ReadUInt32(nTemp32);
+ nYRes = nTemp32;
+
+ // unit
+ cByte = 0;
+ mrStream.ReadUChar(cByte);
+
+ if (cByte)
+ {
+ if (nXRes)
+ maMetadata.maLogSize.setWidth(
+ (maMetadata.maPixSize.Width() * 100000) / nXRes);
+
+ if (nYRes)
+ maMetadata.maLogSize.setHeight(
+ (maMetadata.maPixSize.Height() * 100000) / nYRes);
+ }
+
+ nLen32 -= 9;
+ }
+ else if (nTemp32 == PNG_TRNS_SIGNATURE) // transparency
+ {
+ maMetadata.mbIsTransparent = true;
+ maMetadata.mbIsAlpha = (cColType != 0 && cColType != 2);
+ }
+
+ // skip forward to next chunk
+ mrStream.SeekRel(4 + nLen32);
+ mrStream.ReadUInt32(nLen32);
+ mrStream.ReadUInt32(nTemp32);
+ }
+ } while (false);
+ }
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkAPNG()
+{
+ mrStream.Seek(mnStreamPosition);
+ if (PngImageReader::isAPng(mrStream))
+ {
+ maMetadata.mnFormat = GraphicFileFormat::APNG;
+ 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)))
+ {
+ maMetadata.mnFormat = GraphicFileFormat::JPG;
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkSVM()
+{
+ sal_uInt32 n32 = 0;
+ bool bRet = false;
+ SeekGuard aGuard(mrStream, mrStream.Tell());
+ mrStream.SetEndian(SvStreamEndian::LITTLE);
+ mrStream.ReadUInt32(n32);
+ if (n32 == 0x44475653)
+ {
+ sal_uInt8 cByte = 0;
+ mrStream.ReadUChar(cByte);
+ if (cByte == 0x49)
+ {
+ maMetadata.mnFormat = GraphicFileFormat::SVM;
+ bRet = true;
+
+ if (mbExtendedInfo)
+ {
+ sal_uInt32 nTemp32;
+ sal_uInt16 nTemp16;
+
+ mrStream.SeekRel(0x04);
+
+ // width
+ nTemp32 = 0;
+ mrStream.ReadUInt32(nTemp32);
+ maMetadata.maLogSize.setWidth(nTemp32);
+
+ // height
+ nTemp32 = 0;
+ mrStream.ReadUInt32(nTemp32);
+ maMetadata.maLogSize.setHeight(nTemp32);
+
+ // read MapUnit and determine PrefSize
+ nTemp16 = 0;
+ mrStream.ReadUInt16(nTemp16);
+ maMetadata.maLogSize = OutputDevice::LogicToLogic(
+ maMetadata.maLogSize, MapMode(static_cast<MapUnit>(nTemp16)),
+ MapMode(MapUnit::Map100thMM));
+ }
+ }
+ }
+ else
+ {
+ mrStream.SeekRel(-4);
+ n32 = 0;
+ mrStream.ReadUInt32(n32);
+
+ if (n32 == 0x4D4C4356)
+ {
+ sal_uInt16 nTmp16 = 0;
+
+ mrStream.ReadUInt16(nTmp16);
+
+ if (nTmp16 == 0x4654)
+ {
+ maMetadata.mnFormat = GraphicFileFormat::SVM;
+ bRet = true;
+
+ if (mbExtendedInfo)
+ {
+ MapMode aMapMode;
+ mrStream.SeekRel(0x06);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.readMapMode(aMapMode);
+ aSerializer.readSize(maMetadata.maLogSize);
+ maMetadata.maLogSize = OutputDevice::LogicToLogic(
+ maMetadata.maLogSize, aMapMode, MapMode(MapUnit::Map100thMM));
+ }
+ }
+ }
+ }
+ return bRet;
+}
+
+bool GraphicFormatDetector::checkPCD()
+{
+ if (mnStreamLength < 2055)
+ return false;
+ char sBuffer[8];
+ SeekGuard aGuard(mrStream, mnStreamPosition);
+ mrStream.Seek(mnStreamPosition + 2048);
+ sBuffer[mrStream.ReadBytes(sBuffer, 7)] = 0;
+
+ if (strncmp(sBuffer, "PCD_IPI", 7) == 0)
+ {
+ maMetadata.mnFormat = GraphicFileFormat::PCD;
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPSD()
+{
+ SeekGuard aGuard(mrStream, mnStreamPosition);
+ bool bRet = false;
+ if ((mnFirstLong == 0x38425053) && ((mnSecondLong >> 16) == 1))
+ {
+ maMetadata.mnFormat = GraphicFileFormat::PSD;
+ bRet = true;
+ if (mbExtendedInfo)
+ {
+ sal_uInt16 nChannels = 0;
+ sal_uInt32 nRows = 0;
+ sal_uInt32 nColumns = 0;
+ sal_uInt16 nDepth = 0;
+ sal_uInt16 nMode = 0;
+ mrStream.Seek(mnStreamPosition + 6);
+ mrStream.SeekRel(6); // Pad
+ mrStream.ReadUInt16(nChannels)
+ .ReadUInt32(nRows)
+ .ReadUInt32(nColumns)
+ .ReadUInt16(nDepth)
+ .ReadUInt16(nMode);
+ if ((nDepth == 1) || (nDepth == 8) || (nDepth == 16))
+ {
+ maMetadata.mnBitsPerPixel = (nDepth == 16) ? 8 : nDepth;
+ switch (nChannels)
+ {
+ case 4:
+ case 3:
+ maMetadata.mnBitsPerPixel = 24;
+ [[fallthrough]];
+ case 2:
+ case 1:
+ maMetadata.maPixSize.setWidth(nColumns);
+ maMetadata.maPixSize.setHeight(nRows);
+ break;
+ default:
+ bRet = false;
+ }
+ }
+ else
+ bRet = false;
+ }
+ }
+ return bRet;
+}
+
+bool GraphicFormatDetector::checkEPS()
+{
+ const char* pFirstBytesAsCharArray = reinterpret_cast<char*>(maFirstBytes.data());
+
+ if (mnFirstLong == 0xC5D0D3C6)
+ {
+ maMetadata.mnFormat = GraphicFileFormat::EPS;
+ return true;
+ }
+ else if (checkArrayForMatchingStrings(pFirstBytesAsCharArray, 30,
+ { "%!PS-Adobe"_ostr, " EPS"_ostr }))
+ {
+ maMetadata.mnFormat = GraphicFileFormat::EPS;
+ return true;
+ }
+
+ return false;
+}
+
+bool GraphicFormatDetector::checkDXF()
+{
+ if (strncmp(reinterpret_cast<char*>(maFirstBytes.data()), "AutoCAD Binary DXF", 18) == 0)
+ {
+ maMetadata.mnFormat = GraphicFileFormat::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))
+ {
+ maMetadata.mnFormat = GraphicFileFormat::DXF;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPCT()
+{
+ SeekGuard aGuard(mrStream, mnStreamPosition);
+ if (isPCT(mrStream, mnStreamPosition, mnStreamLength))
+ {
+ maMetadata.mnFormat = GraphicFileFormat::PCT;
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPBM()
+{
+ SeekGuard aGuard(mrStream, mrStream.Tell());
+ sal_uInt8 nFirst = 0, nSecond = 0, nThird = 0;
+ mrStream.ReadUChar(nFirst).ReadUChar(nSecond).ReadUChar(nThird);
+ if (nFirst == 'P' && ((nSecond == '1') || (nSecond == '4')) && isspace(nThird))
+ {
+ maMetadata.mnFormat = GraphicFileFormat::PBM;
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPGM()
+{
+ sal_uInt8 nFirst = 0, nSecond = 0, nThird = 0;
+ SeekGuard aGuard(mrStream, mrStream.Tell());
+ mrStream.ReadUChar(nFirst).ReadUChar(nSecond).ReadUChar(nThird);
+ if (nFirst == 'P' && ((nSecond == '2') || (nSecond == '5')) && isspace(nThird))
+ {
+ maMetadata.mnFormat = GraphicFileFormat::PGM;
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPPM()
+{
+ sal_uInt8 nFirst = 0, nSecond = 0, nThird = 0;
+ SeekGuard aGuard(mrStream, mrStream.Tell());
+ mrStream.ReadUChar(nFirst).ReadUChar(nSecond).ReadUChar(nThird);
+ if (nFirst == 'P' && ((nSecond == '3') || (nSecond == '6')) && isspace(nThird))
+ {
+ maMetadata.mnFormat = GraphicFileFormat::PPM;
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkRAS()
+{
+ if (mnFirstLong == 0x59a66a95)
+ {
+ maMetadata.mnFormat = GraphicFileFormat::RAS;
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkXPM()
+{
+ const char* pFirstBytesAsCharArray = reinterpret_cast<char*>(maFirstBytes.data());
+ if (matchArrayWithString(pFirstBytesAsCharArray, 256, "/* XPM */"_ostr))
+ {
+ maMetadata.mnFormat = GraphicFileFormat::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]);
+
+ SeekGuard aGuard(mrStream, mnStreamPosition);
+ mrStream.Seek(mnStreamPosition);
+ nSize = mrStream.ReadBytes(pBuffer.get(), nSize);
+
+ const char* pBufferAsCharArray = reinterpret_cast<char*>(pBuffer.get());
+
+ if (checkArrayForMatchingStrings(pBufferAsCharArray, nSize, { "#define"_ostr, "_width"_ostr }))
+ {
+ maMetadata.mnFormat = GraphicFileFormat::XBM;
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkSVG()
+{
+ SeekGuard aGuard(mrStream, mnStreamPosition);
+ 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 = mbWasCompressed;
+ 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"_ostr, "version"_ostr, "DOCTYPE"_ostr, "svg"_ostr }))
+ {
+ bIsSvg = true;
+ }
+
+ // check for svg element in 1st 256 bytes
+ // search for '<svg'
+ if (!bIsSvg
+ && checkArrayForMatchingStrings(pCheckArrayAsCharArray, nCheckSize, { "<svg"_ostr }))
+ {
+ 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, SVG_CHECK_SIZE);
+ }
+ else
+ {
+ nCheckSize = std::min<sal_uInt64>(mnStreamLength, SVG_CHECK_SIZE);
+ mrStream.Seek(mnStreamPosition);
+ nCheckSize = mrStream.ReadBytes(sExtendedOrDecompressedFirstBytes, nCheckSize);
+ }
+
+ // search for '<svg'
+ if (checkArrayForMatchingStrings(pCheckArrayAsCharArray, nCheckSize, { "<svg"_ostr }))
+ {
+ bIsSvg = true;
+ }
+ }
+
+ if (bIsSvg)
+ {
+ if (mbWasCompressed)
+ maMetadata.mnFormat = GraphicFileFormat::SVGZ;
+ else
+ maMetadata.mnFormat = GraphicFileFormat::SVG;
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkTGA()
+{
+ SeekGuard aGuard(mrStream, mnStreamPosition);
+ // 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)
+ {
+ maMetadata.mnFormat = GraphicFileFormat::TGA;
+ return true;
+ }
+ }
+
+ // Fallback to file extension check
+ if (maExtension.startsWith("TGA"))
+ {
+ maMetadata.mnFormat = GraphicFileFormat::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'))
+ {
+ maMetadata.mnFormat = GraphicFileFormat::MOV;
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPDF()
+{
+ if (maFirstBytes[0] == '%' && maFirstBytes[1] == 'P' && maFirstBytes[2] == 'D'
+ && maFirstBytes[3] == 'F' && maFirstBytes[4] == '-')
+ {
+ maMetadata.mnFormat = GraphicFileFormat::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')
+ {
+ maMetadata.mnFormat = GraphicFileFormat::WEBP;
+ if (mbExtendedInfo)
+ {
+ mrStream.Seek(mnStreamPosition);
+ ReadWebpInfo(mrStream, maMetadata.maPixSize, maMetadata.mnBitsPerPixel,
+ maMetadata.mbIsAlpha);
+ maMetadata.mbIsTransparent = maMetadata.mbIsAlpha;
+ }
+ return true;
+ }
+ return false;
+}
+
+const GraphicMetadata& GraphicFormatDetector::getMetadata() { return maMetadata; }
+
+sal_uInt8* GraphicFormatDetector::checkAndUncompressBuffer(sal_uInt8* aUncompressedBuffer,
+ sal_uInt32 nSize, sal_uInt64& nRetSize)
+{
+ SeekGuard aGuard(mrStream, mnStreamPosition);
+ 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]);
+ }
+ mbWasCompressed = true;
+ return aUncompressedBuffer;
+ }
+ mbWasCompressed = false;
+ 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 0000000000..5b11fcc782
--- /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 0000000000..792dd6a93a
--- /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 0000000000..3d2b6a463c
--- /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 0000000000..a5fcedec23
--- /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 0000000000..59ba24cf5c
--- /dev/null
+++ b/vcl/source/filter/egif/egif.cxx
@@ -0,0 +1,551 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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;
+ BitmapScopedReadAccess 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)
+ , 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.reset();
+
+ 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 AnimationFrame& rAnimationFrame = rAnimation.Get( i );
+
+ WriteBitmapEx(rAnimationFrame.maBitmapEx, rAnimationFrame.maPositionPixel, true,
+ rAnimationFrame.mnWait, rAnimationFrame.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 )
+ {
+ AlphaMask aMask( rBmpEx.GetAlphaMask() );
+
+ aAccBmp = rBmpEx.GetBitmap();
+ bTransparent = false;
+
+ if( !aMask.IsEmpty() )
+ {
+ if( aAccBmp.Convert( BmpConversion::N8BitTrans ) )
+ {
+ aMask.Convert( BmpConversion::N1BitThreshold );
+ aMask.Invert();
+ aAccBmp.ReplaceMask( aMask, BMP_COL_TRANS );
+ bTransparent = true;
+ }
+ else
+ aAccBmp.Convert( BmpConversion::N8BitColors );
+ }
+ else
+ aAccBmp.Convert( BmpConversion::N8BitColors );
+
+ m_pAcc = aAccBmp;
+
+ if( !m_pAcc )
+ bStatus = false;
+ }
+
+ return bStatus;
+}
+
+
+void GIFWriter::DestroyAccess()
+{
+ m_pAcc.reset();
+}
+
+
+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( static_cast<sal_uInt8>(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 ]);
+
+ assert(bStatus && "should not calling here if status is bad");
+ assert( 8 == m_pAcc->GetBitCount() && m_pAcc->HasPalette()
+ && "by the time we get here, the image should be in palette format");
+ 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 0000000000..41c65d2da4
--- /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 0000000000..13ef3f4ba5
--- /dev/null
+++ b/vcl/source/filter/egif/giflzwc.hxx
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/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();
+};
+
+/* 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 0000000000..88a9df9b8e
--- /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 *, AlphaMask const *, const Point &, double nWidth, double nHeight );
+ void ImplText( const OUString& rUniString, const Point& rPos, KernArraySpan pDXArry, std::span<const sal_Bool> pKashidaArry, sal_Int32 nWidth, VirtualDevice const & rVDev );
+ void ImplSetAttrForText( const Point & rPoint );
+ void ImplWriteCharacter( char );
+ void ImplWriteString( const OString&, VirtualDevice const & rVDev, KernArraySpan 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->WriteOString( "%%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->WriteOString( "%%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 );
+ BitmapScopedReadAccess pAcc(aTmpBitmap);
+ if ( pAcc )
+ {
+ mpPS->WriteOString( "%%BeginPreview: " ); // BoundingBox
+ ImplWriteLong( aSizeBitmap.Width() );
+ ImplWriteLong( aSizeBitmap.Height() );
+ mpPS->WriteOString( "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->WriteOString( "%" );
+ 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--;
+ }
+ }
+ pAcc.reset();
+ 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(), pA->GetKashidaArray(), 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 );
+ AlphaMask aMask( aBitmapEx.GetAlphaMask() );
+ 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 );
+ AlphaMask aMask( aBitmapEx.GetAlphaMask() );
+ 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 );
+ AlphaMask aMask( aBitmapEx.GetAlphaMask() );
+ 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
+
+ }
+ AlphaMask aMask( aBitmapEx.GetAlphaMask() );
+ 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"_ostr;
+ 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"_ostr;
+ 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->WriteOString( "p" );
+ mnCursorPos += 2;
+ ImplExecMode( PS_RET );
+ }
+ }
+ mpPS->WriteOString( "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->WriteOString( "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->WriteOString( "0 rl 0 " );
+ ImplWriteDouble( nHeight );
+ mpPS->WriteOString( "rl " );
+ ImplWriteDouble( nWidth );
+ mpPS->WriteOString( "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->WriteOString( "0 rl 0 " );
+ ImplWriteDouble( nHeight );
+ mpPS->WriteOString( "rl " );
+ ImplWriteDouble( nWidth );
+ mpPS->WriteOString( "neg 0 rl ef " );
+ mpPS->WriteOString( "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->WriteOString( "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->WriteOString( "p" );
+ mnCursorPos += 2;
+ ImplExecMode( PS_RET );
+ }
+ }
+ mpPS->WriteOString( "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, AlphaMask const * pAlphaMaskBitmap, 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 ( pAlphaMaskBitmap )
+ {
+ bDoTrans = true;
+ while (true)
+ {
+ if ( mnLevel == 1 && nHeight > 10 )
+ nHeight = 8;
+ aRect = tools::Rectangle( Point( 0, nHeightOrg - nHeightLeft ), Size( nWidth, nHeight ) );
+ aRegion = pAlphaMaskBitmap->CreateRegion( COL_ALPHA_OPAQUE, 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" );
+ }
+ BitmapScopedReadAccess pAcc(aTileBitmap);
+
+ 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->WriteOString( "8 [" );
+ ImplWriteLong( nWidth );
+ mpPS->WriteOString( "0 0 " );
+ ImplWriteLong( -nHeight );
+ ImplWriteLong( 0 );
+ ImplWriteLong( nHeight );
+ ImplWriteLine( "]" );
+ mpPS->WriteOString( "{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->WriteOString( "/Width " );
+ ImplWriteLong( nWidth, PS_RET );
+ mpPS->WriteOString( "/Height " );
+ ImplWriteLong( nHeight, PS_RET );
+ ImplWriteLine( "/BitsPerComponent 8" );
+ ImplWriteLine( "/Decode[0 1]" );
+ mpPS->WriteOString( "/ImageMatrix[" );
+ ImplWriteLong( nWidth );
+ mpPS->WriteOString( "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->WriteOString( "/Width " );
+ ImplWriteLong( nWidth, PS_RET );
+ mpPS->WriteOString( "/Height " );
+ ImplWriteLong( nHeight, PS_RET );
+ ImplWriteLine( "/BitsPerComponent 8" );
+ ImplWriteLine( "/Decode[0 255]" );
+ mpPS->WriteOString( "/ImageMatrix[" );
+ ImplWriteLong( nWidth );
+ mpPS->WriteOString( "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->WriteOString( "/Width " );
+ ImplWriteLong( nWidth, PS_RET );
+ mpPS->WriteOString( "/Height " );
+ ImplWriteLong( nHeight, PS_RET );
+ ImplWriteLine( "/BitsPerComponent 8" );
+ ImplWriteLine( "/Decode[0 1 0 1 0 1]" );
+ mpPS->WriteOString( "/ImageMatrix[" );
+ ImplWriteLong( nWidth );
+ mpPS->WriteOString( "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" );
+
+ pAcc.reset();
+ 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, KernArraySpan 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, KernArraySpan pDXArry, std::span<const sal_Bool> pKashidaArry, 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::WITHOUT_ALPHA);
+ 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, pKashidaArry ) )
+ {
+ // 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->WriteOString( "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->WriteOString( "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->WriteOString( "gs " );
+ ImplWriteF( nRotation.get(), 1 );
+ mpPS->WriteOString( "r " );
+ }
+}
+
+void PSWriter::ImplDefineFont( const char* pOriginalName, const char* pItalic )
+{
+ mpPS->WriteUChar( '/' ); //convert the font pOriginalName using ISOLatin1Encoding
+ mpPS->WriteOString( pOriginalName );
+ switch ( maFont.GetWeight() )
+ {
+ case WEIGHT_SEMIBOLD :
+ case WEIGHT_BOLD :
+ case WEIGHT_ULTRABOLD :
+ case WEIGHT_BLACK :
+ mpPS->WriteOString( "-Bold" );
+ if ( maFont.GetItalic() != ITALIC_NONE )
+ mpPS->WriteOString( pItalic );
+ break;
+ default:
+ if ( maFont.GetItalic() != ITALIC_NONE )
+ mpPS->WriteOString( pItalic );
+ break;
+ }
+ ImplWriteLine( " f" );
+}
+
+void PSWriter::ImplClosePathDraw()
+{
+ mpPS->WriteOString( "pc" );
+ mnCursorPos += 2;
+ ImplExecMode( PS_RET );
+}
+
+void PSWriter::ImplPathDraw()
+{
+ mpPS->WriteOString( "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->WriteOString( "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 0000000000..a6e07c49b3
--- /dev/null
+++ b/vcl/source/filter/etiff/etiff.cxx
@@ -0,0 +1,585 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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_uInt64 mnStreamOfs;
+
+ bool mbStatus;
+ BitmapScopedReadAccess 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_uInt64 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)
+ , 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(AnimationFrame(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 AnimationFrame& rAnimationFrame = aAnimation.Get( i );
+ Bitmap aBmp = rAnimationFrame.maBitmapEx.GetBitmap();
+ mpAcc = aBmp;
+ 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_uInt64 nCurPos = m_rOStm.Tell();
+ m_rOStm.Seek( mnCurrentTagCountPos );
+ m_rOStm.WriteUInt16( mnTagCount );
+ m_rOStm.Seek( nCurPos );
+
+ mpAcc.reset();
+ }
+ 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_uInt64 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 0000000000..25bcdd201c
--- /dev/null
+++ b/vcl/source/filter/graphicfilter.cxx
@@ -0,0 +1,1961 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <rtl/crc.h>
+#include <fltcall.hxx>
+#include <vcl/salctype.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+#include <vcl/filter/SvmWriter.hxx>
+#include <vcl/filter/PngImageWriter.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 <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()
+{
+ // Enable support only for unittests
+ const char* const testname = getenv("LO_TESTNAME");
+ if(testname)
+ 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
+
+static OUString ImpGetExtension( std::u16string_view rPath )
+{
+ OUString aExt;
+ INetURLObject aURL( rPath );
+ aExt = aURL.GetFileExtension().toAsciiUpperCase();
+ return aExt;
+}
+
+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<BitmapScopedWriteAccess> 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.GetAlphaMask();
+ rContext.m_pAlphaAccess = std::make_unique<BitmapScopedWriteAccess>(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)
+ {
+ rContext.m_pAccess.reset();
+ rContext.m_pAlphaAccess.reset();
+ if (!rContext.mAlphaMask.IsEmpty()) // Need to move the AlphaMask back to the BitmapEx.
+ *rContext.m_pGraphic = BitmapEx( rContext.m_pGraphic->GetBitmapExRef().GetBitmap(), rContext.mAlphaMask );
+
+ if (rContext.m_nStatus == ERRCODE_NONE && (rContext.m_eLinkType != GfxLinkType::NONE) && !rContext.m_pGraphic->GetReaderContext())
+ {
+ BinaryDataContainer aGraphicContent;
+
+ const sal_uInt64 nStreamEnd = rContext.m_pStream->Tell();
+ sal_Int32 nGraphicContentSize = nStreamEnd - rContext.m_nStreamBegin;
+
+ if (nGraphicContentSize > 0)
+ {
+ try
+ {
+ rContext.m_pStream->Seek(rContext.m_nStreamBegin);
+ aGraphicContent = BinaryDataContainer(*rContext.m_pStream, nGraphicContentSize);
+ }
+ catch (const std::bad_alloc&)
+ {
+ rContext.m_nStatus = ERRCODE_GRFILTER_TOOBIG;
+ }
+ }
+
+ if (rContext.m_nStatus == ERRCODE_NONE)
+ rContext.m_pGraphic->SetGfxLink(std::make_shared<GfxLink>(aGraphicContent, 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::none_of(toLoad.begin(), toLoad.end(), predicate ))
+ toLoad.push_back( graphic );
+ }
+ }
+ if( toLoad.empty())
+ return;
+ std::vector< std::unique_ptr<SvStream>> streams;
+ streams.reserve(toLoad.size());
+ 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;
+ loadedGraphics.reserve(streams.size());
+ 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);
+
+ BinaryDataContainer aGraphicContent;
+
+ // read graphic
+ {
+ if (aFilterName.equalsIgnoreAsciiCase(IMP_GIF))
+ {
+ eLinkType = GfxLinkType::NativeGif;
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_PNG))
+ {
+ // check if this PNG contains a GIF chunk!
+ aGraphicContent = vcl::PngImageReader::getMicrosoftGifChunk(rIStream);
+ if (!aGraphicContent.isEmpty())
+ 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)
+ {
+ aMemStream.Seek(STREAM_SEEK_TO_BEGIN);
+ aGraphicContent = BinaryDataContainer(aMemStream, nMemoryLength);
+
+ bOkay = true;
+ }
+ }
+ else
+ {
+ aGraphicContent = BinaryDataContainer(rIStream, 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) ||
+ aFilterName.equalsIgnoreAsciiCase(IMP_WMZ) ||
+ aFilterName.equalsIgnoreAsciiCase(IMP_EMZ))
+ {
+ 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)
+ {
+ aMemStream.Seek(STREAM_SEEK_TO_BEGIN);
+ aGraphicContent = BinaryDataContainer(aMemStream, nMemoryLength);
+ }
+ }
+ else
+ {
+ aGraphicContent = BinaryDataContainer(rIStream, 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 (aGraphicContent.isEmpty())
+ {
+ if (nStreamLength > 0)
+ {
+ try
+ {
+ rIStream.Seek(nStreamBegin);
+ aGraphicContent = BinaryDataContainer(rIStream, nStreamLength);
+ }
+ catch (const std::bad_alloc&)
+ {
+ nStatus = ERRCODE_GRFILTER_TOOBIG;
+ }
+ }
+ }
+
+ if( nStatus == ERRCODE_NONE )
+ {
+ bool bAnimated = false;
+ Size aLogicSize;
+ if (eLinkType == GfxLinkType::NativeGif && !aGraphicContent.isEmpty())
+ {
+ std::shared_ptr<SvStream> pMemoryStream = aGraphicContent.getAsStream();
+ bAnimated = IsGIFAnimated(*pMemoryStream, aLogicSize);
+ if (!pSizeHint && aLogicSize.getWidth() && aLogicSize.getHeight())
+ {
+ pSizeHint = &aLogicSize;
+ }
+ }
+ aGraphic.SetGfxLink(std::make_shared<GfxLink>(aGraphicContent, 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, BinaryDataContainer& rpGraphicContent)
+{
+ ErrCode aReturnCode = ERRCODE_NONE;
+
+ // check if this PNG contains a GIF chunk!
+ if (auto aMSGifChunk = vcl::PngImageReader::getMicrosoftGifChunk(rStream);
+ !aMSGifChunk.isEmpty())
+ {
+ std::shared_ptr<SvStream> pIStrm(aMSGifChunk.getAsStream());
+ ImportGIF(*pIStrm, rGraphic);
+ rLinkType = GfxLinkType::NativeGif;
+ rpGraphicContent = aMSGifChunk;
+ return aReturnCode;
+ }
+
+ // PNG has no GIF chunk
+ Graphic aGraphic;
+ vcl::PngImageReader aPNGReader(rStream);
+ aPNGReader.read(aGraphic);
+ if (!aGraphic.GetBitmapEx().IsEmpty())
+ {
+ rGraphic = aGraphic;
+ 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, BinaryDataContainer& rpGraphicContent)
+{
+ 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)
+ {
+ aMemStream.Seek(STREAM_SEEK_TO_BEGIN);
+ rpGraphicContent = BinaryDataContainer(aMemStream, nMemoryLength);
+
+ // Make a uncompressed copy for GfxLink
+ if (!aMemStream.GetError())
+ {
+ auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(rpGraphicContent, VectorGraphicDataType::Svg);
+ rGraphic = Graphic(aVectorGraphicDataPtr);
+ bOkay = true;
+ }
+ }
+ }
+ else
+ {
+ BinaryDataContainer aNewData(rStream, nStreamLength);
+
+ if (!rStream.GetError())
+ {
+ auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aNewData, 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;
+ }
+ }
+ BinaryDataContainer aNewData(*aNewStream, nStreamLength);
+ if (!aNewStream->GetError())
+ {
+ auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aNewData, eType);
+
+ 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();
+
+ BinaryDataContainer aGraphicContent;
+
+ 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, aGraphicContent);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_JPEG))
+ {
+ nStatus = readJPEG(rIStream, rGraphic, eLinkType, nImportFlags);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_SVG) || aFilterName.equalsIgnoreAsciiCase(IMP_SVGZ))
+ {
+ nStatus = readSVG(rIStream, rGraphic, eLinkType, aGraphicContent);
+ }
+ 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) || aFilterName.equalsIgnoreAsciiCase(IMP_WMZ))
+ {
+ nStatus = readWMF(rIStream, rGraphic, eLinkType);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_EMF) || aFilterName.equalsIgnoreAsciiCase(IMP_EMZ))
+ {
+ 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 (aGraphicContent.isEmpty())
+ {
+ const sal_uInt64 nStreamEnd = rIStream.Tell();
+ const sal_uInt64 nGraphicContentSize = nStreamEnd - nStreamBegin;
+
+ if (nGraphicContentSize > 0)
+ {
+ try
+ {
+ rIStream.Seek(nStreamBegin);
+ aGraphicContent = BinaryDataContainer(rIStream, nGraphicContentSize);
+ }
+ catch (const std::bad_alloc&)
+ {
+ nStatus = ERRCODE_GRFILTER_TOOBIG;
+ }
+ }
+ }
+ if( nStatus == ERRCODE_NONE )
+ {
+ rGraphic.SetGfxLink(std::make_shared<GfxLink>(aGraphicContent, 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();
+ bool bShouldCompress = false;
+ SvMemoryStream rCompressableStm;
+
+ if( nFormat == GRFILTER_FORMAT_DONTKNOW )
+ {
+ OUString aExt = ImpGetExtension( rPath );
+ 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 ) || aFilterName.equalsIgnoreAsciiCase( EXP_WMZ ) )
+ {
+ bool bDone(false);
+ SvStream* rTempStm = &rOStm;
+ if (aFilterName.equalsIgnoreAsciiCase(EXP_WMZ))
+ {
+ // Write to a different stream so that we can compress to rOStm later
+ rCompressableStm.SetBufferSize( rOStm.GetBufferSize() );
+ rTempStm = &rCompressableStm;
+ bShouldCompress = true;
+ }
+ // 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)
+ {
+ rVectorGraphicDataPtr->getBinaryDataContainer().writeToStream(*rTempStm);
+ if (rTempStm->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, *rTempStm, &aConfigItem))
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+
+ if (rTempStm->GetError())
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ }
+ else if ( aFilterName.equalsIgnoreAsciiCase( EXP_EMF ) || aFilterName.equalsIgnoreAsciiCase( EXP_EMZ ) )
+ {
+ bool bDone(false);
+ SvStream* rTempStm = &rOStm;
+ if (aFilterName.equalsIgnoreAsciiCase(EXP_EMZ))
+ {
+ // Write to a different stream so that we can compress to rOStm later
+ rCompressableStm.SetBufferSize( rOStm.GetBufferSize() );
+ rTempStm = &rCompressableStm;
+ bShouldCompress = true;
+ }
+ // 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())
+ {
+ rVectorGraphicDataPtr->getBinaryDataContainer().writeToStream(*rTempStm);
+ if (rTempStm->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(), *rTempStm))
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+
+ if (rTempStm->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 ) )
+ {
+ auto aBitmapEx = aGraphic.GetBitmapEx();
+ vcl::PngImageWriter aPNGWriter( rOStm );
+ if ( pFilterData )
+ aPNGWriter.setParameters( *pFilterData );
+ aPNGWriter.write( aBitmapEx );
+
+ if( rOStm.GetError() )
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else if ( aFilterName.equalsIgnoreAsciiCase( EXP_APNG ) )
+ {
+ vcl::PngImageWriter aPNGWriter( rOStm );
+ if ( pFilterData )
+ aPNGWriter.setParameters( *pFilterData );
+ aPNGWriter.write( aGraphic );
+
+ if( rOStm.GetError() )
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase( EXP_SVG ) || aFilterName.equalsIgnoreAsciiCase( EXP_SVGZ ) )
+ {
+ bool bDone(false);
+ SvStream* rTempStm = &rOStm;
+ if (aFilterName.equalsIgnoreAsciiCase(EXP_SVGZ))
+ {
+ // Write to a different stream so that we can compress to rOStm later
+ rCompressableStm.SetBufferSize(rOStm.GetBufferSize());
+ rTempStm = &rCompressableStm;
+ bShouldCompress = true;
+ }
+
+ // 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())
+ {
+ rVectorGraphicDataPtr->getBinaryDataContainer().writeToStream(*rTempStm);
+ if( rTempStm->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(
+ getXWeak( new ImpFilterOutputStream( *rTempStm ) ) );
+
+ 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 );
+ }
+ else if ( bShouldCompress )
+ {
+ sal_uInt32 nUncompressedCRC32
+ = rtl_crc32( 0, rCompressableStm.GetData(), rCompressableStm.GetSize() );
+ ZCodec aCodec;
+ rCompressableStm.Seek( 0 );
+ aCodec.BeginCompression( ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true );
+ // the inner modify time/filename doesn't really matter in this context because
+ // compressed graphic formats are meant to be opened as is - not to be extracted
+ aCodec.SetCompressionMetadata( "inner"_ostr, 0, nUncompressedCRC32 );
+ aCodec.Compress( rCompressableStm, rOStm );
+ tools::Long nCompressedLength = aCodec.EndCompression();
+ if ( rOStm.GetError() || nCompressedLength <= 0 )
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ 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 0000000000..d7161b2808
--- /dev/null
+++ b/vcl/source/filter/graphicfilter2.cxx
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/outdev.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <graphic/GraphicFormatDetector.hxx>
+
+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()
+{
+ aMetadata.mnFormat = GraphicFileFormat::NOT;
+ aMetadata.mnBitsPerPixel = 0;
+ aMetadata.mnPlanes = 0;
+ aMetadata.mnNumberOfImageComponents = 0;
+ aMetadata.mbIsTransparent = false;
+ aMetadata.mbIsAlpha = false;
+}
+
+bool GraphicDescriptor::ImpDetectBMP( SvStream& rStm, bool bExtendedInfo )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, bExtendedInfo );
+ bool bRet = aDetector.detect() && aDetector.checkBMP();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectGIF( SvStream& rStm, bool bExtendedInfo )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, bExtendedInfo );
+ bool bRet = aDetector.detect() && aDetector.checkGIF();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ 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_uInt64 nStmPos = rStm.Tell();
+
+ rStm.SetEndian( SvStreamEndian::BIG );
+ rStm.ReadUInt32( nTemp32 );
+
+ // compare upper 24 bits
+ if( 0xffd8ff00 == ( nTemp32 & 0xffffff00 ) )
+ {
+ aMetadata.mnFormat = 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_uInt64 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 ) );
+ aMetadata.maLogSize = OutputDevice::LogicToLogic( aMetadata.maPixSize, 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 );
+ aMetadata.mnNumberOfImageComponents = nNumberOfImageComponents;
+
+ // nSamplingFactor (lower nibble: vertical,
+ // upper nibble: horizontal) is unused
+
+ aMetadata.maPixSize.setHeight( nNumberOfLines );
+ aMetadata.maPixSize.setWidth( nSamplesPerLine );
+ aMetadata.mnBitsPerPixel = ( nNumberOfImageComponents == 3 ? 24 : nNumberOfImageComponents == 1 ? 8 : 0 );
+ aMetadata.mnPlanes = 1;
+
+ if (aMap.GetMapUnit() != MapUnit::MapPixel)
+ // We already know the DPI, but the
+ // pixel size arrived later, so do the
+ // conversion again.
+ aMetadata.maLogSize = OutputDevice::LogicToLogic(
+ aMetadata.maPixSize, 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 )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, false /*bExtendedInfo*/ );
+ bool bRet = aDetector.detect() && aDetector.checkPCD();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPCX( SvStream& rStm )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, true /*bExtendedInfo*/ );
+ bool bRet = aDetector.detect() && aDetector.checkPCX();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPNG( SvStream& rStm, bool bExtendedInfo )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, bExtendedInfo );
+ bool bRet = aDetector.detect() && aDetector.checkPNG();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectTIF( SvStream& rStm, bool bExtendedInfo )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, bExtendedInfo );
+ bool bRet = aDetector.detect() && aDetector.checkTIF();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectXBM( SvStream& rStm, bool )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, false /* bExtendedInfo */ );
+ bool bRet = aDetector.detect() && aDetector.checkXBM();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectXPM( SvStream& rStm, bool )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, false /* bExtendedInfo */ );
+ bool bRet = aDetector.detect() && aDetector.checkXPM();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPBM( SvStream& rStm, bool )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, false /* bExtendedInfo */ );
+ bool bRet = aDetector.detect() && aDetector.checkPBM();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPGM( SvStream& rStm, bool )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, false /* bExtendedInfo */ );
+ bool bRet = aDetector.detect() && aDetector.checkPGM();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPPM( SvStream& rStm, bool )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, false /* bExtendedInfo */ );
+ bool bRet = aDetector.detect() && aDetector.checkPPM();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectRAS( SvStream& rStm, bool )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, false /* bExtendedInfo */ );
+ bool bRet = aDetector.detect() && aDetector.checkRAS();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectTGA( SvStream& rStm, bool )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, false /* bExtendedInfo */ );
+ bool bRet = aDetector.detect() && aDetector.checkTGA();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPSD( SvStream& rStm, bool bExtendedInfo )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, bExtendedInfo );
+ bool bRet = aDetector.detect() && aDetector.checkPSD();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectEPS( SvStream& rStm, bool )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, false /*bExtendedInfo*/ );
+ bool bRet = aDetector.detect() && aDetector.checkEPS();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectDXF( SvStream& rStm, bool )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, false /*bExtendedInfo*/ );
+ bool bRet = aDetector.detect() && aDetector.checkDXF();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectMET( SvStream& rStm, bool )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, false /*bExtendedInfo*/ );
+ bool bRet = aDetector.detect() && aDetector.checkMET();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPCT( SvStream& rStm, bool )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, false /*bExtendedInfo*/ );
+ bool bRet = aDetector.detect() && aDetector.checkPCT();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectSVM( SvStream& rStm, bool bExtendedInfo )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, bExtendedInfo );
+ bool bRet = aDetector.detect() && aDetector.checkSVM();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectWMF(SvStream& rStm, bool /*bExtendedInfo*/)
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, false /*bExtendedInfo*/ );
+ bool bRet = aDetector.detect() && aDetector.checkWMF();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectEMF(SvStream& rStm, bool bExtendedInfo)
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, bExtendedInfo );
+ bool bRet = aDetector.detect() && aDetector.checkEMF();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectSVG( SvStream& rStm, bool /*bExtendedInfo*/ )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, false /*bExtendedInfo*/ );
+ bool bRet = aDetector.detect() && aDetector.checkSVG();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectWEBP( SvStream& rStm, bool bExtendedInfo )
+{
+ vcl::GraphicFormatDetector aDetector( rStm, aPathExt, bExtendedInfo );
+ bool bRet = aDetector.detect() && aDetector.checkWEBP();
+ if ( bRet )
+ aMetadata = aDetector.getMetadata();
+ return bRet;
+}
+
+OUString GraphicDescriptor::GetImportFormatShortName( GraphicFileFormat nFormat )
+{
+ return vcl::getImportFormatShortName( nFormat );
+}
+
+/* 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 0000000000..2b26abffd3
--- /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 0000000000..94aef49882
--- /dev/null
+++ b/vcl/source/filter/idxf/dxf2mtf.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 .
+ */
+
+#pragma once
+
+#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);
+
+};
+
+/* 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 0000000000..3c1e119238
--- /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 = ""_ostr;
+ m_sAlsoName = ""_ostr;
+ aBasePoint.fx=0.0;
+ aBasePoint.fy=0.0;
+ aBasePoint.fz=0.0;
+ nFlags=0;
+ m_sXRef = ""_ostr;
+
+ 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 0000000000..25565f0c2a
--- /dev/null
+++ b/vcl/source/filter/idxf/dxfblkrd.hxx
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/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
+
+};
+
+/* 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 0000000000..9f9e075514
--- /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"_ostr)
+ , m_sLineType("BYLAYER"_ostr)
+{
+ 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"_ostr)
+{
+ 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"_ostr)
+{
+ 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"_ostr)
+{
+ 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 0000000000..f1a267727d
--- /dev/null
+++ b/vcl/source/filter/idxf/dxfentrd.hxx
@@ -0,0 +1,535 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 "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;
+};
+
+class DXFEdgeTypeCircularArc : public DXFEdgeType
+{
+ DXFVector aCenter; // 10,20
+ double fRadius; // 40
+ double fStartAngle; // 50
+ double fEndAngle; // 51
+ sal_Int32 nIsCounterClockwiseFlag; // 73
+public:
+ DXFEdgeTypeCircularArc();
+ virtual bool EvaluateGroup( DXFGroupReader & rDGR ) override;
+};
+
+class DXFEdgeTypeEllipticalArc : public DXFEdgeType
+{
+ DXFVector aCenter; // 10,20
+ DXFVector aEndPoint; // 11,21
+ double fLength; // 40
+ double fStartAngle; // 50
+ double fEndAngle; // 51
+ sal_Int32 nIsCounterClockwiseFlag; // 73
+public:
+ DXFEdgeTypeEllipticalArc();
+ virtual bool EvaluateGroup( DXFGroupReader & rDGR ) override;
+};
+
+class DXFEdgeTypeSpline : public DXFEdgeType
+{
+ sal_Int32 nDegree; // 94
+ sal_Int32 nRational; // 73
+ sal_Int32 nPeriodic; // 74
+ sal_Int32 nKnotCount; // 75
+ sal_Int32 nControlCount; // 76
+public:
+ 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
+};
+
+/* 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 0000000000..3319882e32
--- /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"_ostr;
+ }
+ 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 0000000000..6bf3d38a09
--- /dev/null
+++ b/vcl/source/filter/idxf/dxfgrprd.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#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;
+}
+
+/* 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 0000000000..7124e296e3
--- /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 0000000000..734193fcec
--- /dev/null
+++ b/vcl/source/filter/idxf/dxfreprd.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#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]; }
+
+/* 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 0000000000..7e20e6c692
--- /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 0000000000..9e0f83f3ed
--- /dev/null
+++ b/vcl/source/filter/idxf/dxftblrd.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/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;
+
+};
+
+/* 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 0000000000..a11358b510
--- /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=std::hypot(aMX.fx, aMX.fy);
+ fey=std::hypot(aMY.fx, 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 0000000000..2aed6ef0dd
--- /dev/null
+++ b/vcl/source/filter/idxf/dxfvec.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#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;
+}
+
+/* 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 0000000000..26d42b10cb
--- /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 0000000000..bb4ea06b2a
--- /dev/null
+++ b/vcl/source/filter/ieps/ieps.cxx
@@ -0,0 +1,817 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 const sal_uInt8* ImplSearchEntry( const 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(const 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(const 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::TempFileNamed aTempOutput;
+ utl::TempFileNamed 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"_ostr);
+ 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(const 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;
+ const 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)
+ {
+ std::string_view chunk(reinterpret_cast<const char*>(pDest), nLen);
+ if (chunk != "none")
+ {
+ aString += " Title:" + OStringToOUString(chunk, RTL_TEXTENCODING_ASCII_US) + "\n";
+ }
+ }
+ }
+ 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)
+ {
+ std::string_view chunk(reinterpret_cast<const char*>(pDest), nLen);
+ aString += " Creator:" + OStringToOUString(chunk, RTL_TEXTENCODING_ASCII_US) + "\n";
+ }
+ }
+ 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)
+ {
+ std::string_view chunk(reinterpret_cast<const char*>(pDest), nLen);
+ if (chunk != "none")
+ {
+ aString += " CreationDate:" + OStringToOUString(chunk, RTL_TEXTENCODING_ASCII_US) + "\n";
+ }
+ }
+ }
+ 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)
+ {
+ sal_uInt64 nBufStartPos = rStream.Tell();
+ BinaryDataContainer aBuf(rStream, nPSSize);
+ if (!aBuf.isEmpty())
+ {
+ sal_uInt32 nBytesRead = aBuf.getSize();
+ 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)
+ {
+ const sal_uInt8* pDest = ImplSearchEntry( aBuf.getData(), reinterpret_cast<sal_uInt8 const *>("%%BeginPreview:"), nBytesRead - nSecurityCount, 15 );
+ sal_uInt32 nRemainingBytes = pDest ? (nBytesRead - (pDest - aBuf.getData())) : 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 - aBuf.getData() ) );
+
+ 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;
+ }
+ }
+ }
+ }
+ }
+
+ const sal_uInt8* pDest = ImplSearchEntry( aBuf.getData(), reinterpret_cast<sal_uInt8 const *>("%%BoundingBox:"), nBytesRead, 14 );
+ sal_uInt32 nRemainingBytes = pDest ? (nBytesRead - (pDest - aBuf.getData())) : 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(aBuf.getData(), nBytesRead, aGraphic);
+ if (!bHasPreview)
+ bHasPreview = RenderAsBMP(aBuf.getData(), nBytesRead, aGraphic);
+ }
+
+ // if there is no preview -> make a red box
+ if( !bHasPreview )
+ {
+ MakePreview(aBuf.getData(), nBytesRead, nWidth, nHeight,
+ aGraphic);
+ }
+
+ GfxLink aGfxLink( aBuf, GfxLinkType::EpsBuffer ) ;
+ aMtf.AddAction( static_cast<MetaAction*>( new MetaEPSAction( Point(), Size( nWidth, nHeight ),
+ std::move(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 0000000000..b062593a9e
--- /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 0000000000..3f60c28f8e
--- /dev/null
+++ b/vcl/source/filter/igif/decode.hxx
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/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 );
+};
+
+/* 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 0000000000..8f56edaee7
--- /dev/null
+++ b/vcl/source/filter/igif/gifread.cxx
@@ -0,0 +1,1022 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/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;
+ bool bEnhance;
+
+ 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 )
+ , bEnhance( false )
+{
+ 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::N8_BPP, &Bitmap::GetGreyPalette(256));
+
+ if (!aAnimation.Count())
+ aBmp1.Erase(aWhite);
+
+ pAcc1 = 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 = 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;
+ }
+
+ // tdf#157793 limit tdf#157635 fix to only larger palettes
+ // I don't know why, but the fix for tdf#157635 causes
+ // images with a palette of 16 entries to be inverted.
+ // Also, fix tdf#158047 by allowing the tdf#157635 fix for
+ // palettes with 64 entries.
+ bEnhance = (nCount > 16);
+}
+
+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()
+{
+ AnimationFrame aAnimationFrame;
+
+ pAcc8.reset();
+
+ if( bGCTransparent )
+ {
+ pAcc1.reset();
+ AlphaMask aAlphaMask(aBmp1);
+ aAlphaMask.Invert(); // convert from transparency to alpha
+ aAnimationFrame.maBitmapEx = BitmapEx( aBmp8, aAlphaMask );
+ }
+ else
+ {
+ // tdf#157576 and tdf#157635 mask out black pixels
+ // Due to the switch from transparency to alpha in commit
+ // 81994cb2b8b32453a92bcb011830fcb884f22ff3, mask out black
+ // pixels in bitmap.
+ if (bEnhance)
+ aAnimationFrame.maBitmapEx = BitmapEx( aBmp8, aBmp8 );
+ else
+ aAnimationFrame.maBitmapEx = BitmapEx( aBmp8 );
+ }
+
+ aAnimationFrame.maPositionPixel = Point( nImagePosX, nImagePosY );
+ aAnimationFrame.maSizePixel = Size( nImageWidth, nImageHeight );
+ aAnimationFrame.mnWait = ( nTimer != 65535 ) ? nTimer : ANIMATION_TIMEOUT_ON_CLICK;
+ aAnimationFrame.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 (aAnimationFrame.mnWait < 2) // 20ms, specified in 100's of a second
+ aAnimationFrame.mnWait = 10;
+
+ if( nGCDisposalMethod == 2 )
+ aAnimationFrame.meDisposal = Disposal::Back;
+ else if( nGCDisposalMethod == 3 )
+ aAnimationFrame.meDisposal = Disposal::Previous;
+ else
+ aAnimationFrame.meDisposal = Disposal::Not;
+
+ nAnimationByteSize += aAnimationFrame.maBitmapEx.GetSizeBytes();
+ nAnimationMinFileData += static_cast<sal_uInt64>(nImageWidth) * nImageHeight / 2560;
+ aAnimation.Insert(aAnimationFrame);
+
+ 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 = aBmp1;
+ bStatus = bStatus && pAcc1;
+ }
+ else
+ aImGraphic = BitmapEx(aBmp8);
+
+ pAcc8 = 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 0000000000..642921fd0a
--- /dev/null
+++ b/vcl/source/filter/igif/gifread.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <vcl/graph.hxx>
+
+VCL_DLLPUBLIC bool ImportGIF(SvStream& rStream, Graphic& rGraphic);
+bool IsGIFAnimated(SvStream& rStream, Size& rLogicSize);
+
+/* 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 0000000000..e055ea8382
--- /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=hypot(q*(x1-cx), p*(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 0000000000..37736a48f3
--- /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 0000000000..220ac61110
--- /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 0000000000..b1162d5ec4
--- /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 0000000000..2710b97a78
--- /dev/null
+++ b/vcl/source/filter/ipdf/pdfcompat.cxx
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <tools/solar.h>
+#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_uInt64 nStreamLength = aMemoryStream.TellEnd();
+
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ BinaryDataContainer aPdfData(aMemoryStream, nStreamLength);
+ if (aMemoryStream.GetError())
+ return {};
+
+ return 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 0000000000..159db9a38b
--- /dev/null
+++ b/vcl/source/filter/ipdf/pdfdocument.cxx
@@ -0,0 +1,3384 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <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::writeBufferBytes(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"_ostr));
+ if (!pT)
+ continue;
+
+ const OString& rValue = pT->GetValue();
+ static constexpr std::string_view aPrefix = "Signature";
+ if (!rValue.startsWith(aPrefix))
+ continue;
+
+ nRet = std::max(nRet, o3tl::toUInt32(rValue.subView(aPrefix.size())));
+ }
+
+ 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(OString::number(nSignatureId)
+ + " 0 obj\n"
+ "<</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 + ">\n/Type/Sig/SubFilter");
+ if (bAdES)
+ aSigBuffer.append("/ETSI.CAdES.detached");
+ else
+ aSigBuffer.append("/adbe.pkcs7.detached");
+
+ // Time of signing.
+ aSigBuffer.append(" /M (" + vcl::PDFWriter::GetDateTime()
+ + ")"
+
+ // Byte range: we can write offset1-length1 and offset2 right now, will
+ // write length2 later.
+ " /ByteRange [ 0 "
+ // -1 and +1 is the leading "<" and the trailing ">" around the hex string.
+ + OString::number(rContentOffset - 1) + " "
+ + OString::number(rContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) + " ");
+ 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.
+ + " /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"_ostr);
+ 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"_ostr))
+ {
+ 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.WriteNumberAsString(nAppearanceId);
+ aEditBuffer.WriteOString(" 0 obj\n");
+ aEditBuffer.WriteOString("<</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.WriteOString("/BBox[0 0 ");
+ aEditBuffer.WriteNumberAsString(rSignatureRectangle.getOpenWidth());
+ aEditBuffer.WriteOString(" ");
+ aEditBuffer.WriteNumberAsString(rSignatureRectangle.getOpenHeight());
+ aEditBuffer.WriteOString("]\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.WriteNumberAsString(nLength);
+ if (bCompressed)
+ {
+ aEditBuffer.WriteOString(" /Filter/FlateDecode");
+ }
+
+ aEditBuffer.WriteOString("\n>>\n");
+
+ aEditBuffer.WriteOString("stream\n");
+
+ // Copy the original page streams to the form XObject stream.
+ aStream.Seek(0);
+ aEditBuffer.WriteStream(aStream);
+
+ aEditBuffer.WriteOString("\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.WriteNumberAsString(nAnnotId);
+ m_aEditBuffer.WriteOString(" 0 obj\n");
+ m_aEditBuffer.WriteOString("<</Type/Annot/Subtype/Widget/F 132\n");
+ m_aEditBuffer.WriteOString("/Rect[0 0 ");
+ m_aEditBuffer.WriteNumberAsString(rSignatureRectangle.getOpenWidth());
+ m_aEditBuffer.WriteOString(" ");
+ m_aEditBuffer.WriteNumberAsString(rSignatureRectangle.getOpenHeight());
+ m_aEditBuffer.WriteOString("]\n");
+ m_aEditBuffer.WriteOString("/FT/Sig\n");
+ m_aEditBuffer.WriteOString("/P ");
+ m_aEditBuffer.WriteNumberAsString(rFirstPage.GetObjectValue());
+ m_aEditBuffer.WriteOString(" 0 R\n");
+ m_aEditBuffer.WriteOString("/T(Signature");
+ m_aEditBuffer.WriteNumberAsString(nNextSignature);
+ m_aEditBuffer.WriteOString(")\n");
+ m_aEditBuffer.WriteOString("/V ");
+ m_aEditBuffer.WriteNumberAsString(nSignatureId);
+ m_aEditBuffer.WriteOString(" 0 R\n");
+ m_aEditBuffer.WriteOString("/DV ");
+ m_aEditBuffer.WriteNumberAsString(nSignatureId);
+ m_aEditBuffer.WriteOString(" 0 R\n");
+ m_aEditBuffer.WriteOString("/AP<<\n/N ");
+ m_aEditBuffer.WriteNumberAsString(nAppearanceId);
+ m_aEditBuffer.WriteOString(" 0 R\n>>\n");
+ m_aEditBuffer.WriteOString(">>\nendobj\n\n");
+
+ return nAnnotId;
+}
+
+bool PDFDocument::WritePageObject(PDFObjectElement& rFirstPage, sal_Int32 nAnnotId)
+{
+ PDFElement* pAnnots = rFirstPage.Lookup("Annots"_ostr);
+ 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.WriteNumberAsString(nAnnotsId);
+ m_aEditBuffer.WriteOString(" 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.WriteOString(" ");
+ m_aEditBuffer.WriteNumberAsString(pReference->GetObjectValue());
+ m_aEditBuffer.WriteOString(" 0 R");
+ }
+ // Write our reference.
+ m_aEditBuffer.WriteOString(" ");
+ m_aEditBuffer.WriteNumberAsString(nAnnotId);
+ m_aEditBuffer.WriteOString(" 0 R");
+
+ m_aEditBuffer.WriteOString("]\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.WriteNumberAsString(nFirstPageId);
+ m_aEditBuffer.WriteOString(" 0 obj\n");
+ m_aEditBuffer.WriteOString("<<");
+ 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.WriteOString("/Annots[");
+ m_aEditBuffer.WriteNumberAsString(nAnnotId);
+ m_aEditBuffer.WriteOString(" 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"_ostr)
+ + pDictionary->GetKeyValueLength("Annots"_ostr) - 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.WriteOString(" ");
+ m_aEditBuffer.WriteNumberAsString(nAnnotId);
+ m_aEditBuffer.WriteOString(" 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.WriteOString(">>");
+ m_aEditBuffer.WriteOString("\nendobj\n\n");
+ }
+
+ return true;
+}
+
+bool PDFDocument::WriteCatalogObject(sal_Int32 nAnnotId, PDFReferenceElement*& pRoot)
+{
+ if (m_pXRefStream)
+ pRoot = dynamic_cast<PDFReferenceElement*>(m_pXRefStream->Lookup("Root"_ostr));
+ else
+ {
+ if (!m_pTrailer)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: found no trailer");
+ return false;
+ }
+ pRoot = dynamic_cast<PDFReferenceElement*>(m_pTrailer->Lookup("Root"_ostr));
+ }
+ 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"_ostr);
+ 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.WriteNumberAsString(nAcroFormId);
+ m_aEditBuffer.WriteOString(" 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"_ostr))
+ {
+ 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"_ostr)
+ + pAcroFormDictionary->GetKeyValueLength("Fields"_ostr)
+ - 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.WriteOString("<<");
+ 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.WriteOString(" ");
+ m_aEditBuffer.WriteNumberAsString(nAnnotId);
+ m_aEditBuffer.WriteOString(" 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.WriteOString(">>");
+ }
+
+ m_aEditBuffer.WriteOString("\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.WriteNumberAsString(nCatalogId);
+ m_aEditBuffer.WriteOString(" 0 obj\n");
+ m_aEditBuffer.WriteOString("<<");
+ if (!pAcroFormDictionary)
+ {
+ // No AcroForm key, assume no signatures.
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + pCatalog->GetDictionaryOffset(),
+ pCatalog->GetDictionaryLength());
+ m_aEditBuffer.WriteOString("/AcroForm<</Fields[\n");
+ m_aEditBuffer.WriteNumberAsString(nAnnotId);
+ m_aEditBuffer.WriteOString(" 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"_ostr);
+ 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"_ostr)
+ + pAcroFormDictionary->GetKeyValueLength("Fields"_ostr)
+ - 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.WriteOString(" ");
+ m_aEditBuffer.WriteNumberAsString(nAnnotId);
+ m_aEditBuffer.WriteOString(" 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.WriteOString(">>\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.WriteNumberAsString(nXRefStreamId);
+ m_aEditBuffer.WriteOString(
+ " 0 obj\n<</DecodeParms<</Columns 5/Predictor 12>>/Filter/FlateDecode");
+
+ // ID.
+ auto pID = dynamic_cast<PDFArrayElement*>(m_pXRefStream->Lookup("ID"_ostr));
+ if (pID)
+ {
+ const std::vector<PDFElement*>& rElements = pID->GetElements();
+ m_aEditBuffer.WriteOString("/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.WriteOString("> <");
+ }
+ m_aEditBuffer.WriteOString("> ] ");
+ }
+
+ // Index.
+ m_aEditBuffer.WriteOString("/Index [ ");
+ for (const auto& rXRef : m_aXRef)
+ {
+ if (!rXRef.second.GetDirty())
+ continue;
+
+ m_aEditBuffer.WriteNumberAsString(rXRef.first);
+ m_aEditBuffer.WriteOString(" 1 ");
+ }
+ m_aEditBuffer.WriteOString("] ");
+
+ // Info.
+ auto pInfo = dynamic_cast<PDFReferenceElement*>(m_pXRefStream->Lookup("Info"_ostr));
+ if (pInfo)
+ {
+ m_aEditBuffer.WriteOString("/Info ");
+ m_aEditBuffer.WriteNumberAsString(pInfo->GetObjectValue());
+ m_aEditBuffer.WriteOString(" ");
+ m_aEditBuffer.WriteNumberAsString(pInfo->GetGenerationValue());
+ m_aEditBuffer.WriteOString(" R ");
+ }
+
+ // Length.
+ m_aEditBuffer.WriteOString("/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.WriteNumberAsString(aXRefStream.GetSize());
+
+ if (!m_aStartXRefs.empty())
+ {
+ // Write location of the previous cross-reference section.
+ m_aEditBuffer.WriteOString("/Prev ");
+ m_aEditBuffer.WriteNumberAsString(m_aStartXRefs.back());
+ }
+
+ // Root.
+ m_aEditBuffer.WriteOString("/Root ");
+ m_aEditBuffer.WriteNumberAsString(pRoot->GetObjectValue());
+ m_aEditBuffer.WriteOString(" ");
+ m_aEditBuffer.WriteNumberAsString(pRoot->GetGenerationValue());
+ m_aEditBuffer.WriteOString(" R ");
+
+ // Size.
+ m_aEditBuffer.WriteOString("/Size ");
+ m_aEditBuffer.WriteNumberAsString(m_aXRef.size());
+
+ m_aEditBuffer.WriteOString("/Type/XRef/W[1 3 1]>>\nstream\n");
+ aXRefStream.Seek(0);
+ m_aEditBuffer.WriteStream(aXRefStream);
+ m_aEditBuffer.WriteOString("\nendstream\nendobj\n\n");
+ }
+ else
+ {
+ // Write the xref table.
+ m_aEditBuffer.WriteOString("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.WriteNumberAsString(nObject);
+ m_aEditBuffer.WriteOString(" 1\n");
+ OStringBuffer aBuffer = OString::number(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.WriteOString("trailer\n<</Size ");
+ m_aEditBuffer.WriteNumberAsString(m_aXRef.size());
+ m_aEditBuffer.WriteOString("/Root ");
+ m_aEditBuffer.WriteNumberAsString(pRoot->GetObjectValue());
+ m_aEditBuffer.WriteOString(" ");
+ m_aEditBuffer.WriteNumberAsString(pRoot->GetGenerationValue());
+ m_aEditBuffer.WriteOString(" R\n");
+ auto pInfo = dynamic_cast<PDFReferenceElement*>(m_pTrailer->Lookup("Info"_ostr));
+ if (pInfo)
+ {
+ m_aEditBuffer.WriteOString("/Info ");
+ m_aEditBuffer.WriteNumberAsString(pInfo->GetObjectValue());
+ m_aEditBuffer.WriteOString(" ");
+ m_aEditBuffer.WriteNumberAsString(pInfo->GetGenerationValue());
+ m_aEditBuffer.WriteOString(" R\n");
+ }
+ auto pID = dynamic_cast<PDFArrayElement*>(m_pTrailer->Lookup("ID"_ostr));
+ if (pID)
+ {
+ const std::vector<PDFElement*>& rElements = pID->GetElements();
+ m_aEditBuffer.WriteOString("/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.WriteOString(">\n<");
+ }
+ m_aEditBuffer.WriteOString("> ]\n");
+ }
+
+ if (!m_aStartXRefs.empty())
+ {
+ // Write location of the previous cross-reference section.
+ m_aEditBuffer.WriteOString("/Prev ");
+ m_aEditBuffer.WriteNumberAsString(m_aStartXRefs.back());
+ }
+
+ m_aEditBuffer.WriteOString(">>\n");
+ }
+}
+
+bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& xCertificate,
+ const OUString& rDescription, bool bAdES)
+{
+ m_aEditBuffer.Seek(STREAM_SEEK_TO_END);
+ m_aEditBuffer.WriteOString("\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.WriteOString("startxref\n");
+ m_aEditBuffer.WriteNumberAsString(nXRefOffset);
+ m_aEditBuffer.WriteOString("\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"_ostr);
+ 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"_ostr));
+
+ // 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"_ostr));
+ 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"_ostr);
+ // 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"_ostr);
+ 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"_ostr));
+ 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"_ostr)))
+ {
+ const std::map<OString, PDFElement*>& rItems = pDecodeParams->GetItems();
+ auto it = rItems.find("Columns"_ostr);
+ if (it != rItems.end())
+ if (auto pColumns = dynamic_cast<PDFNumberElement*>(it->second))
+ nColumns = pColumns->GetValue();
+ it = rItems.find("Predictor"_ostr);
+ 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"_ostr));
+ std::vector<size_t> aFirstObjects;
+ std::vector<size_t> aNumberOfObjects;
+ if (!pIndex)
+ {
+ auto pSize = dynamic_cast<PDFNumberElement*>(pObject->Lookup("Size"_ostr));
+ 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"_ostr));
+ 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 pKidsRef = pPages->Lookup("Kids"_ostr);
+ auto pKids = dynamic_cast<PDFArrayElement*>(pKidsRef);
+ if (!pKids)
+ {
+ auto pRefKids = dynamic_cast<PDFReferenceElement*>(pKidsRef);
+ if (!pRefKids)
+ {
+ SAL_WARN("vcl.filter", "visitPages: pages has no kids");
+ return;
+ }
+ auto pObjWithKids = pRefKids->LookupObject();
+ if (!pObjWithKids)
+ {
+ SAL_WARN("vcl.filter", "visitPages: pages has no kids");
+ return;
+ }
+
+ pKids = pObjWithKids->GetArray();
+ }
+
+ 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"_ostr));
+ 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"_ostr));
+ else if (m_pXRefStream)
+ pRoot = dynamic_cast<PDFReferenceElement*>(m_pXRefStream->Lookup("Root"_ostr));
+
+ 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"_ostr);
+ 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"_ostr);
+ 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"_ostr));
+ 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"_ostr));
+ 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"_ostr));
+ 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"_ostr));
+ if (!pFirst)
+ {
+ SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: no First");
+ return;
+ }
+
+ auto pN = dynamic_cast<PDFNumberElement*>(Lookup("N"_ostr));
+ if (!pN)
+ {
+ SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: no N");
+ return;
+ }
+ size_t nN = pN->GetValue();
+
+ auto pLength = dynamic_cast<PDFNumberElement*>(Lookup("Length"_ostr));
+ 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))
+ {
+ // Handle previously stored number
+ if (aNumbers.size() > 2)
+ {
+ aNumbers.resize(aNumbers.size() - 2);
+ if (pParsingArray)
+ {
+ for (auto& pNumber : aNumbers)
+ pParsingArray->PushBack(pNumber);
+ }
+ aNumbers.clear();
+ }
+
+ 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))
+ {
+ // Handle previously stored number
+ if (!aNumbers.empty())
+ {
+ if (pParsingArray)
+ {
+ for (auto& pNumber : aNumbers)
+ pParsingArray->PushBack(pNumber);
+ }
+ aNumbers.clear();
+ }
+
+ 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))
+ {
+ // Handle previously stored number
+ if (!aNumbers.empty())
+ {
+ if (pParsingArray)
+ {
+ for (auto& pNumber : aNumbers)
+ pParsingArray->PushBack(pNumber);
+ }
+ aNumbers.clear();
+ }
+
+ 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))
+ {
+ // Handle previously stored number
+ if (!aNumbers.empty())
+ {
+ if (pParsingArray)
+ {
+ for (auto& pNumber : aNumbers)
+ pParsingArray->PushBack(pNumber);
+ }
+ aNumbers.clear();
+ }
+
+ 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 0000000000..ac4bceaf45
--- /dev/null
+++ b/vcl/source/filter/ipdf/pdfread.cxx
@@ -0,0 +1,395 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/BitmapWriteAccess.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <unotools/datetime.hxx>
+#include <tools/UnitConversion.hxx>
+
+#include <vcl/filter/PDFiumLibrary.hxx>
+#include <sal/log.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.
+
+ int nPageWidth = std::round(vcl::pdf::pointToPixel(nPageWidthPoints, fResolutionDPI)
+ * PDF_INSERT_MAGIC_SCALE_FACTOR);
+ int 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);
+ BitmapScopedWriteAccess pMaskAccess(aMask);
+ ConstScanline pPdfBuffer = pPdfBitmap->getBuffer();
+ const int nStride = pPdfBitmap->getStride();
+ std::vector<sal_uInt8> aScanlineAlpha(nPageWidth);
+ for (int nRow = 0; nRow < nPageHeight; ++nRow)
+ {
+ ConstScanline pPdfLine = pPdfBuffer + (nStride * nRow);
+ // pdfium byte order is BGRA.
+ pWriteAccess->CopyScanline(nRow, pPdfLine, ScanlineFormat::N32BitTcBgra, nStride);
+ for (int nCol = 0; nCol < nPageWidth; ++nCol)
+ {
+ 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::B2DPoint const& rInputPoint,
+ basegfx::B2DSize const& rPageSize)
+{
+ double x = convertPointToMm100(rInputPoint.getX());
+ double y = convertPointToMm100(rPageSize.getHeight() - 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::FreeText
+ || 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.getHeight() - rRectangle.getMinY()),
+ convertPointToMm100(rRectangle.getMaxX()),
+ convertPointToMm100(aPageSize.getHeight() - 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.getWidth() <= 0.0 || aPageSize.getHeight() <= 0.0)
+ continue;
+
+ // Returned unit is points
+
+ tools::Long nPageWidth = std::round(convertPointToMm100(aPageSize.getWidth()));
+ tools::Long nPageHeight = std::round(convertPointToMm100(aPageSize.getHeight()));
+
+ // 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 0000000000..2d9be6f8cd
--- /dev/null
+++ b/vcl/source/filter/ipict/ipict.cxx
@@ -0,0 +1,2040 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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;
+ }
+ double fAng1 = basegfx::deg2rad(nstartAngle);
+ double fAng2 = basegfx::deg2rad(nstartAngle + narcAngle);
+ 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 = 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.getOpenWidth()+1);
+ aRect.setHeight(aRect.getOpenHeight()+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 0000000000..88a62cfd2f
--- /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 0000000000..446d564e95
--- /dev/null
+++ b/vcl/source/filter/ipict/shape.hxx
@@ -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 .
+ */
+#pragma once
+
+#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);
+}
+
+/* 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 0000000000..887dccce1b
--- /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_uInt64 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_uInt64 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>(std::clamp( aBitmapColor.GetRed() - nDAT, sal_Int32(0), sal_Int32(255) ));
+ sal_uInt8 cG = static_cast<sal_uInt8>(std::clamp( aBitmapColor.GetGreen() - nDAT, sal_Int32(0), sal_Int32(255) ));
+ sal_uInt8 cB = static_cast<sal_uInt8>(std::clamp( aBitmapColor.GetBlue() - nDAT, sal_Int32(0), sal_Int32(255) ));
+ mpBitmap->SetPixel( nY, nX, Color( cR, cG, cB ) );
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ if (mbTransparent && m_rPSD.good())
+ {
+ // the psd is 24 or 8 bit grafix + alpha channel
+
+ 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 ? 255 : 0);
+ 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 ? 255 : 0);
+ 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 0000000000..49cfe2bef2
--- /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 0000000000..6b3b3037fe
--- /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 0000000000..741be4c5c1
--- /dev/null
+++ b/vcl/source/filter/itiff/itiff.cxx
@@ -0,0 +1,359 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/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 nStart;
+ tsize_t nSize;
+ bool bAllowOneShortRead;
+ Context(SvStream& rInStream)
+ : rStream(rInStream)
+ , nStart(rInStream.Tell())
+ , nSize(rInStream.remainingSize())
+ , bAllowOneShortRead(false)
+ {
+ }
+ };
+}
+
+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->bAllowOneShortRead)
+ {
+ memset(static_cast<char*>(buf) + nRead, 0, size - nRead);
+ pContext->bAllowOneShortRead = false;
+ 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:
+ offset = pContext->nStart + offset;
+ break;
+ case SEEK_CUR:
+ offset = pContext->rStream.Tell() + offset;
+ break;
+ case SEEK_END:
+ offset = pContext->rStream.TellEnd() + offset;
+ break;
+ default:
+ assert(false && "unknown seek type");
+ break;
+ }
+
+ pContext->rStream.Seek(offset);
+
+ return offset - pContext->nStart;
+}
+
+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);
+ 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;
+
+ const bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ uint64_t nTotalPixelsRequired = 0;
+
+ 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;
+ }
+
+ uint32_t nPixelsRequired;
+ // use the same max size that libtiff defaults to for its own utilities
+ constexpr size_t nMaxPixelsAllowed = (256 * 1024 * 1024) / 4;
+ // two buffers currently required, so limit further
+ bool bOk = !o3tl::checked_multiply(w, h, nPixelsRequired) && nPixelsRequired <= nMaxPixelsAllowed / 2;
+ SAL_WARN_IF(!bOk, "filter.tiff", "skipping oversized tiff image " << w << " x " << h);
+
+ if (!TIFFIsTiled(tif))
+ {
+ size_t nStripSize = TIFFStripSize(tif);
+ if (nStripSize > SAL_MAX_INT32)
+ {
+ SAL_WARN("filter.tiff", "skipping oversized tiff strip size " << nStripSize);
+ bOk = false;
+ }
+ }
+
+ uint16_t PhotometricInterpretation(0);
+ uint16_t Compression(COMPRESSION_NONE);
+ if (bOk)
+ {
+ TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &PhotometricInterpretation);
+ TIFFGetField(tif, TIFFTAG_COMPRESSION, &Compression);
+ }
+
+ if (bOk && bFuzzing)
+ {
+ const uint64_t MAX_PIXEL_SIZE = 120000000;
+ const uint64_t MAX_TILE_SIZE = 100000000;
+ nTotalPixelsRequired += nPixelsRequired;
+ if (TIFFTileSize64(tif) > MAX_TILE_SIZE || nTotalPixelsRequired > MAX_PIXEL_SIZE)
+ {
+ SAL_WARN("filter.tiff", "skipping large tiffs");
+ break;
+ }
+
+ if (TIFFIsTiled(tif))
+ {
+ uint32_t tw, th;
+ TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tw);
+ TIFFGetField(tif, TIFFTAG_TILELENGTH, &th);
+
+ if (tw > w || th > h)
+ {
+ bOk = th < 1000 * tw && tw < 1000 * th;
+ SAL_WARN_IF(!bOk, "filter.tiff", "skipping slow bizarre ratio tile of " << tw << " x " << th << " for image of " << w << " x " << h);
+ }
+
+ if (PhotometricInterpretation == PHOTOMETRIC_LOGL)
+ {
+ uint32_t nLogLBufferRequired;
+ bOk &= !o3tl::checked_multiply(tw, th, nLogLBufferRequired) && nLogLBufferRequired < MAX_PIXEL_SIZE;
+ SAL_WARN_IF(!bOk, "filter.tiff", "skipping oversized tiff tile " << tw << " x " << th);
+ }
+
+ if (Compression == COMPRESSION_CCITTFAX4)
+ {
+ uint32_t DspRuns;
+ bOk &= !o3tl::checked_multiply(tw, static_cast<uint32_t>(4), DspRuns) && DspRuns < MAX_PIXEL_SIZE;
+ SAL_WARN_IF(!bOk, "filter.tiff", "skipping oversized tiff tile width: " << tw);
+ }
+ }
+ }
+
+ if (!bOk)
+ break;
+
+ std::vector<uint32_t> raster(nPixelsRequired);
+
+ const bool bNewCodec = Compression >= COMPRESSION_ZSTD; // >= 50000 at time of writing
+ // For tdf#149417 we generally allow one short read for fidelity with the old
+ // parser that this replaced. But don't allow that for:
+ // a) new compression variations that the old parser didn't handle
+ // b) complicated pixel layout variations that the old parser didn't handle
+ // so we don't take libtiff into uncharted territory.
+ aContext.bAllowOneShortRead = !bNewCodec && PhotometricInterpretation != PHOTOMETRIC_YCBCR;
+
+ 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));
+ BitmapScopedWriteAccess 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, a);
+ ++src;
+ }
+ }
+
+ raster.clear();
+
+ access.reset();
+ accessAlpha.reset();
+
+ BitmapEx aBitmapEx(bitmap, bitmapAlpha);
+
+ if (!bFuzzing)
+ {
+ 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));
+
+ AnimationFrame aAnimationFrame(aBitmapEx, Point(0, 0), aBitmapEx.GetSizePixel(),
+ ANIMATION_TIMEOUT_ON_CLICK, Disposal::Back);
+ aAnimation.Insert(aAnimationFrame);
+ }
+ 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 0000000000..ceb942d203
--- /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 <vcl/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::N8_BPP, &Bitmap::GetGreyPalette(256));
+ pAcc1 = 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::N8_BPP, &Bitmap::GetGreyPalette(256));
+
+ 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 0000000000..ce8d3d159d
--- /dev/null
+++ b/vcl/source/filter/ixbm/xbmread.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+
+VCL_DLLPUBLIC bool ImportXBM(SvStream& rStream, Graphic& rGraphic);
+
+/* 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 0000000000..f8b36ed98a
--- /dev/null
+++ b/vcl/source/filter/ixpm/rgbtable.hxx
@@ -0,0 +1,693 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+
+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}
+};
+
+/* 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 0000000000..918a75d5a7
--- /dev/null
+++ b/vcl/source/filter/ixpm/xpmread.cxx
@@ -0,0 +1,695 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/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
+ ePixelFormat = vcl::PixelFormat::N8_BPP;
+
+ maBmp = Bitmap(Size(mnWidth, mnHeight), ePixelFormat);
+ mpAcc = maBmp;
+
+ // mbTransparent is TRUE if at least one colour is transparent
+ if ( mbTransparent )
+ {
+ maMaskBmp = Bitmap(Size(mnWidth, mnHeight), vcl::PixelFormat::N8_BPP, &Bitmap::GetGreyPalette(256));
+ mpMaskAcc = 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 0000000000..469281bdcc
--- /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_uInt64 nStreamPosition = rStream.Tell();
+ bool result = processJpeg(rStream, false);
+ rStream.Seek( nStreamPosition );
+
+ return result;
+}
+
+void Exif::write(SvStream& rStream)
+{
+ sal_uInt64 nStreamPosition = rStream.Tell();
+ processJpeg(rStream, true);
+ rStream.Seek( nStreamPosition );
+}
+
+bool Exif::processJpeg(SvStream& rStream, bool bSetValue)
+{
+ sal_uInt16 aMagic16;
+ sal_uInt16 aLength;
+
+ sal_uInt64 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_uInt64 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_uInt64 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 0000000000..287b7ae434
--- /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 0000000000..1861056039
--- /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.emplace();
+ mpIncompleteAlpha.emplace();
+ }
+}
+
+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.emplace();
+
+ 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.emplace(aSize, vcl::PixelFormat::N8_BPP, &aGrayPal);
+ }
+ else
+ {
+ mpBitmap.emplace(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.emplace(aSizePixel);
+ mpIncompleteAlpha->Erase(255);
+ }
+
+ if (nLines && (nLines < aSizePixel.Height()))
+ {
+ const tools::Long nNewLines = nLines - mnLastLines;
+
+ if (nNewLines > 0)
+ {
+ {
+ BitmapScopedWriteAccess pAccess(*mpIncompleteAlpha);
+ pAccess->SetFillColor(COL_ALPHA_OPAQUE);
+ 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 0000000000..6282cb4ed0
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegReader.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <vcl/graph.hxx>
+#include <vcl/bitmap.hxx>
+
+#include <vcl/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::optional<Bitmap> mpBitmap;
+ std::optional<AlphaMask> 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; }
+};
+
+/* 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 0000000000..16c0c060bd
--- /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 0000000000..09dfe773d1
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegTransform.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#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();
+};
+
+/* 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 0000000000..ae644884c1
--- /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 = 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().getWidth() << "\nJPEG Export - DPI Y: " << rGraphic.GetPPI().getHeight());
+
+ 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 0000000000..a48d06bd26
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegWriter.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#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;
+ BitmapScopedReadAccess 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 );
+
+};
+
+/* 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 0000000000..b863b11c43
--- /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 0000000000..e7158b858b
--- /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 0000000000..0ab27fb2fb
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpeg.h
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <basegfx/vector/b2dsize.hxx>
+#include <vcl/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;
+};
+
+/* 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 0000000000..f3991dd999
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpeg.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <vcl/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);
+
+/* 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 0000000000..16c9800383
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpegc.cxx
@@ -0,0 +1,551 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <jerror.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);
+}
+
+}
+
+extern "C" {
+
+// see also external/libtiff/0001-ofz-54685-Timeout.patch
+static void emitMessage (j_common_ptr cinfo, int msg_level)
+{
+ if (msg_level < 0)
+ {
+ // https://libjpeg-turbo.org/pmwiki/uploads/About/TwoIssueswiththeJPEGStandard.pdf
+ // try to retain some degree of recoverability up to some reasonable
+ // limit (initially using ImageMagick's current limit of 1000), then
+ // bail.
+ constexpr int WarningLimit = 1000;
+ static bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ // ofz#50452 due to Timeouts, just abandon fuzzing on any
+ // JWRN_NOT_SEQUENTIAL
+ if (bFuzzing && cinfo->err->msg_code == JWRN_NOT_SEQUENTIAL)
+ {
+ cinfo->err->error_exit(cinfo);
+ return;
+ }
+ if (++cinfo->err->num_warnings > WarningLimit)
+ 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;
+ jpeg_progress_mgr progress;
+ ErrorManagerStruct jerr;
+ JpegDecompressOwner aOwner;
+ std::unique_ptr<BitmapScopedWriteAccess> pScopedAccess;
+ std::vector<sal_uInt8> pScanLineBuffer;
+ std::vector<sal_uInt8> pCYMKBuffer;
+};
+
+// https://github.com/libjpeg-turbo/libjpeg-turbo/issues/284
+// https://libjpeg-turbo.org/pmwiki/uploads/About/TwoIssueswiththeJPEGStandard.pdf
+#define LIMITSCANS 100
+
+void progress_monitor(j_common_ptr cinfo)
+{
+ if (cinfo->is_decompressor)
+ {
+ jpeg_decompress_struct* decompressor = reinterpret_cast<j_decompress_ptr>(cinfo);
+ if (decompressor->input_scan_number >= LIMITSCANS)
+ {
+ SAL_WARN("vcl.filter", "too many progressive scans, cancelling import after: " << decompressor->input_scan_number << " scans");
+ errorExit(cinfo);
+ }
+ }
+}
+
+}
+
+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);
+ rContext.cinfo.progress = &rContext.progress;
+ rContext.progress.progress_monitor = progress_monitor;
+ 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;
+
+ if (utl::ConfigManager::IsFuzzing())
+ {
+ tools::Long nResult = 0;
+ if (o3tl::checked_multiply(nWidth, nHeight, nResult) || nResult > 4000000)
+ return;
+ if (rContext.cinfo.err->num_warnings && (nWidth > 8192 || nHeight > 8192))
+ 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.getWidth(), 65535) && o3tl::convertsToAtMost(rPPI.getHeight(), 65535))
+ {
+ cinfo.density_unit = 1;
+ cinfo.X_density = rPPI.getWidth();
+ cinfo.Y_density = rPPI.getHeight();
+ }
+ else
+ {
+ SAL_WARN("vcl.filter", "ignoring too large PPI (" << rPPI.getWidth() << ", " << rPPI.getHeight() << ")");
+ }
+
+ 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 0000000000..9b3e367752
--- /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 0000000000..d26cb95100
--- /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 0000000000..a5d403335c
--- /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 0000000000..d6a5a219c5
--- /dev/null
+++ b/vcl/source/filter/png/PngImageReader.cxx
@@ -0,0 +1,915 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <comphelper/scopeguard.hxx>
+#include <osl/endian.h>
+#include <vcl/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");
+ }
+ }
+}
+
+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;
+};
+
+/// Animation Control chunk for APNG files
+struct acTLChunk
+{
+ sal_uInt32 num_frames;
+ sal_uInt32 num_plays;
+};
+
+/// Base class for fcTL and fdAT chunks since both of these chunks use a sequence number for ordering
+struct FrameDataChunk
+{
+ sal_uInt32 sequence_number;
+ virtual ~FrameDataChunk() = default;
+};
+
+/// fcTL (Frame Control) chunk for APNG files
+struct fcTLChunk : public FrameDataChunk
+{
+ sal_uInt32 width;
+ sal_uInt32 height;
+ sal_uInt32 x_offset;
+ sal_uInt32 y_offset;
+ sal_uInt16 delay_num;
+ sal_uInt16 delay_den;
+ sal_uInt8 dispose_op;
+ sal_uInt8 blend_op;
+};
+
+/// fdAT (Frame Data) chunk for APNG files
+struct fdATChunk : public FrameDataChunk
+{
+ std::vector<sal_uInt8> frame_data;
+};
+
+/// APNG chunk holder class, used for the user pointer in the libpng callback function
+struct APNGInfo
+{
+ bool mbIsApng = false;
+ acTLChunk maACTLChunk;
+ std::vector<std::unique_ptr<FrameDataChunk>> maFrameData;
+};
+
+int handle_unknown_chunk(png_structp png, png_unknown_chunkp chunk)
+{
+ std::string sName(chunk->name, chunk->name + 4);
+ APNGInfo* aAPNGInfo = static_cast<APNGInfo*>(png_get_user_chunk_ptr(png));
+ if (sName == "acTL")
+ {
+ if (chunk->size < sizeof(acTLChunk))
+ return -1;
+ aAPNGInfo->maACTLChunk = *reinterpret_cast<acTLChunk*>(chunk->data);
+ aAPNGInfo->maACTLChunk.num_frames = OSL_SWAPDWORD(aAPNGInfo->maACTLChunk.num_frames);
+ aAPNGInfo->maACTLChunk.num_plays = OSL_SWAPDWORD(aAPNGInfo->maACTLChunk.num_plays);
+ aAPNGInfo->mbIsApng = true;
+ }
+ else
+ {
+ std::unique_ptr<FrameDataChunk> pBaseChunk;
+
+ if (sName == "fcTL")
+ {
+ // Can't check with sizeof(fcTLChunk) because it may not be packed
+ if (chunk->size != 26)
+ {
+ return -1;
+ }
+
+ // byte
+ // 0 sequence_number (unsigned int) Sequence number of the animation chunk, starting from 0
+ // 4 width (unsigned int) Width of the following frame
+ // 8 height (unsigned int) Height of the following frame
+ // 12 x_offset (unsigned int) X position at which to render the following frame
+ // 16 y_offset (unsigned int) Y position at which to render the following frame
+ // 20 delay_num (unsigned short) Frame delay fraction numerator
+ // 22 delay_den (unsigned short) Frame delay fraction denominator
+ // 24 dispose_op (byte) Type of frame area disposal to be done after rendering this frame
+ // 25 blend_op (byte) Type of frame area rendering for this frame
+
+ // memcpy each member instead of reinterpret_cast because struct may not be packed
+ std::unique_ptr<fcTLChunk> aChunk = std::make_unique<fcTLChunk>();
+ std::memcpy(&aChunk->width, chunk->data + 4, 4);
+ std::memcpy(&aChunk->height, chunk->data + 8, 4);
+ std::memcpy(&aChunk->x_offset, chunk->data + 12, 4);
+ std::memcpy(&aChunk->y_offset, chunk->data + 16, 4);
+ std::memcpy(&aChunk->delay_num, chunk->data + 20, 2);
+ std::memcpy(&aChunk->delay_den, chunk->data + 22, 2);
+ std::memcpy(&aChunk->dispose_op, chunk->data + 24, 1);
+ std::memcpy(&aChunk->blend_op, chunk->data + 25, 1);
+ aChunk->width = OSL_SWAPDWORD(aChunk->width);
+ aChunk->height = OSL_SWAPDWORD(aChunk->height);
+ aChunk->x_offset = OSL_SWAPDWORD(aChunk->x_offset);
+ aChunk->y_offset = OSL_SWAPDWORD(aChunk->y_offset);
+ aChunk->delay_num = OSL_SWAPWORD(aChunk->delay_num);
+ aChunk->delay_den = OSL_SWAPWORD(aChunk->delay_den);
+ pBaseChunk = std::move(aChunk);
+ }
+ else if (sName == "fdAT")
+ {
+ size_t nDataSize = chunk->size;
+ if (nDataSize < 4)
+ return -1;
+
+ std::unique_ptr<fdATChunk> aChunk = std::make_unique<fdATChunk>();
+ aChunk->frame_data.resize(nDataSize);
+ // Replace sequence number with the IDAT signature
+ sal_uInt32 nIDATSwapped = OSL_SWAPDWORD(PNG_IDAT_SIGNATURE);
+ std::memcpy(aChunk->frame_data.data(), &nIDATSwapped, 4);
+ // Skip sequence number when copying
+ std::memcpy(aChunk->frame_data.data() + 4, chunk->data + 4, nDataSize - 4);
+ pBaseChunk = std::move(aChunk);
+ }
+ else
+ {
+ // Unknown ancillary chunk
+ return 0;
+ }
+
+ sal_uInt32 nSequenceNumber = 0;
+ std::memcpy(&nSequenceNumber, chunk->data, 4);
+ nSequenceNumber = OSL_SWAPDWORD(nSequenceNumber);
+
+ pBaseChunk->sequence_number = nSequenceNumber;
+ if (pBaseChunk->sequence_number < aAPNGInfo->maFrameData.size())
+ {
+ // Make sure chunks are ordered based on their sequence number because the
+ // png specification does not impose ordering restrictions on ancillary chunks
+ aAPNGInfo->maFrameData.insert(aAPNGInfo->maFrameData.begin()
+ + pBaseChunk->sequence_number,
+ std::move(pBaseChunk));
+ }
+ else
+ {
+ aAPNGInfo->maFrameData.push_back(std::move(pBaseChunk));
+ }
+ }
+ return 1;
+}
+
+/// Gets the important chunks (IHDR, PLTE etc.) to a stream so that a stream can be constructed for each APNG frame
+void getImportantChunks(SvStream& rInStream, SvStream& rOutStream, sal_uInt32 nWidth,
+ sal_uInt32 nHeight)
+{
+ sal_uInt64 nPos = rInStream.Tell();
+ rInStream.SetEndian(SvStreamEndian::BIG);
+ rOutStream.SetEndian(SvStreamEndian::BIG);
+ rOutStream.WriteUInt64(PNG_SIGNATURE);
+ rOutStream.WriteUInt32(PNG_IHDR_SIZE);
+ rOutStream.WriteUInt32(PNG_IHDR_SIGNATURE);
+ rOutStream.WriteUInt32(nWidth);
+ rOutStream.WriteUInt32(nHeight);
+ rInStream.Seek(rOutStream.Tell());
+ sal_uInt32 nIHDRData1;
+ sal_uInt8 nIHDRData2;
+ rInStream.ReadUInt32(nIHDRData1);
+ rInStream.ReadUChar(nIHDRData2);
+ rOutStream.WriteUInt32(nIHDRData1);
+ rOutStream.WriteUChar(nIHDRData2);
+ rOutStream.SeekRel(-PNG_IHDR_SIZE - PNG_CRC_SIZE);
+ std::vector<uint8_t> aIHDRData(PNG_IHDR_SIZE + PNG_CRC_SIZE);
+ rOutStream.ReadBytes(aIHDRData.data(), aIHDRData.size());
+ rOutStream.WriteUInt32(rtl_crc32(0, aIHDRData.data(), aIHDRData.size()));
+ rInStream.Seek(PNG_SIGNATURE_SIZE + PNG_TYPE_SIZE + PNG_SIZE_SIZE + PNG_IHDR_SIZE
+ + PNG_CRC_SIZE);
+ while (rInStream.good())
+ {
+ sal_uInt32 nChunkSize(0), nChunkType(0);
+ rInStream.ReadUInt32(nChunkSize);
+ rInStream.ReadUInt32(nChunkType);
+ bool bBreakOuter = false;
+ switch (nChunkType)
+ {
+ case PNG_ACTL_SIGNATURE:
+ case PNG_FCTL_SIGNATURE:
+ case PNG_FDAT_SIGNATURE:
+ {
+ // skip apng chunks
+ rInStream.SeekRel(nChunkSize + PNG_CRC_SIZE);
+ continue;
+ }
+ case PNG_IDAT_SIGNATURE:
+ {
+ // IDAT chunk hit, no more important png chunks
+ bBreakOuter = true;
+ break;
+ }
+ default:
+ {
+ // Seek back to start of chunk
+ rInStream.SeekRel(-PNG_TYPE_SIZE - PNG_SIZE_SIZE);
+ const size_t nDataSize = PNG_SIZE_SIZE + PNG_TYPE_SIZE
+ + static_cast<size_t>(nChunkSize) + PNG_CRC_SIZE;
+ if (nDataSize > rInStream.remainingSize())
+ {
+ SAL_WARN("vcl.filter", "png claims record of size: "
+ << nDataSize << ", but only "
+ << rInStream.remainingSize() << " available.");
+ bBreakOuter = true;
+ break;
+ }
+ // Copy chunk to rOutStream
+ std::vector<uint8_t> aData(nDataSize);
+ rInStream.ReadBytes(aData.data(), nDataSize);
+ rOutStream.WriteBytes(aData.data(), nDataSize);
+ break;
+ }
+ }
+ if (bBreakOuter)
+ {
+ break;
+ }
+ }
+ rInStream.Seek(nPos);
+}
+
+sal_uInt32 NumDenToTime(sal_uInt16 nNumerator, sal_uInt16 nDenominator)
+{
+ if (nDenominator == 0)
+ nDenominator = 100;
+ return (static_cast<double>(nNumerator) / nDenominator) * 100;
+}
+
+bool fcTLbeforeIDAT(SvStream& rStream)
+{
+ sal_uInt64 nPos = rStream.Tell();
+ SvStreamEndian originalEndian = rStream.GetEndian();
+ comphelper::ScopeGuard aGuard([&rStream, nPos, originalEndian] {
+ rStream.Seek(nPos);
+ rStream.SetEndian(originalEndian);
+ });
+ // Skip PNG header and IHDR
+ rStream.SetEndian(SvStreamEndian::BIG);
+ if (!checkSeek(rStream, PNG_SIGNATURE_SIZE + PNG_TYPE_SIZE + PNG_SIZE_SIZE + PNG_IHDR_SIZE
+ + PNG_CRC_SIZE))
+ return false;
+ do
+ {
+ sal_uInt32 nChunkSize(0), nChunkType(0);
+ rStream.ReadUInt32(nChunkSize);
+ rStream.ReadUInt32(nChunkType);
+ switch (nChunkType)
+ {
+ case PNG_FCTL_SIGNATURE:
+ return true;
+ case PNG_IDAT_SIGNATURE:
+ return false;
+ default:
+ {
+ if (!checkSeek(rStream, rStream.Tell() + nChunkSize + PNG_CRC_SIZE))
+ return false;
+ break;
+ }
+ }
+ } while (rStream.good());
+ return false;
+}
+
+bool reader(SvStream& rStream, Graphic& rGraphic,
+ GraphicFilterImportFlags nImportFlags = GraphicFilterImportFlags::NONE,
+ BitmapScopedWriteAccess* pAccess = nullptr,
+ BitmapScopedWriteAccess* 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;
+
+ APNGInfo aAPNGInfo;
+ png_set_read_user_chunk_fn(pPng, &aAPNGInfo, &handle_unknown_chunk);
+ // don't complain about vpAg and exIf chunks
+ png_set_keep_unknown_chunks(pPng, 2, nullptr, 0);
+
+ 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.
+ BitmapEx aBitmapEx;
+ Bitmap aBitmap;
+ AlphaMask aBitmapAlpha;
+ Size prefSize;
+ BitmapScopedWriteAccess pWriteAccessInstance;
+ BitmapScopedWriteAccess 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())
+ aBitmapEx = BitmapEx(aBitmap, aBitmapAlpha);
+ else if (!aBitmap.IsEmpty())
+ aBitmapEx = BitmapEx(aBitmap);
+ if (!aBitmapEx.IsEmpty() && !prefSize.IsEmpty())
+ {
+ aBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ aBitmapEx.SetPrefSize(prefSize);
+ }
+ rGraphic = aBitmapEx;
+ }
+ 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);
+ aBitmapAlpha.Erase(0); // opaque
+ }
+ 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())
+ aBitmapEx = BitmapEx(aBitmap, aBitmapAlpha);
+ else
+ aBitmapEx = BitmapEx(aBitmap);
+ if (!prefSize.IsEmpty())
+ {
+ aBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ aBitmapEx.SetPrefSize(prefSize);
+ }
+ rGraphic = aBitmapEx;
+ return true;
+ }
+
+ pWriteAccessInstance = aBitmap;
+ if (!pWriteAccessInstance)
+ return false;
+ if (!aBitmapAlpha.IsEmpty())
+ {
+ pWriteAccessAlphaInstance = aBitmapAlpha;
+ if (!pWriteAccessAlphaInstance)
+ return false;
+ }
+ }
+ BitmapScopedWriteAccess& pWriteAccess = pAccess ? *pAccess : pWriteAccessInstance;
+ BitmapScopedWriteAccess& 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++] = 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++] = 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())
+ aBitmapEx = BitmapEx(aBitmap, aBitmapAlpha);
+ else
+ aBitmapEx = BitmapEx(aBitmap);
+ if (!prefSize.IsEmpty())
+ {
+ aBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ aBitmapEx.SetPrefSize(prefSize);
+ }
+ }
+
+ if (aAPNGInfo.mbIsApng)
+ {
+ Animation aAnimation;
+ // We create new pngs for each frame and use the PngImageReader to create
+ // the BitmapExs for each frame
+ bool bFctlBeforeIDAT = fcTLbeforeIDAT(rStream);
+ size_t nSequenceIndex = static_cast<size_t>(bFctlBeforeIDAT);
+ sal_uInt32 nFrames
+ = aAPNGInfo.maACTLChunk.num_frames - static_cast<sal_uInt32>(bFctlBeforeIDAT);
+ {
+ if (aAPNGInfo.maFrameData.empty())
+ return false;
+ fcTLChunk* aFctlChunk = dynamic_cast<fcTLChunk*>(aAPNGInfo.maFrameData[0].get());
+ if (!aFctlChunk)
+ return false;
+ Size aCanvasSize(aFctlChunk->width, aFctlChunk->height);
+ aAnimation.SetDisplaySizePixel(aCanvasSize);
+ aAnimation.SetLoopCount(aAPNGInfo.maACTLChunk.num_plays);
+ if (bFctlBeforeIDAT)
+ {
+ Point aFirstPoint(0, 0);
+ auto aDisposal = static_cast<Disposal>(aFctlChunk->dispose_op);
+ auto aBlend = static_cast<Blend>(aFctlChunk->blend_op);
+ if (aDisposal == Disposal::Previous)
+ aDisposal = Disposal::Back;
+ AnimationFrame aAnimationFrame(
+ aBitmapEx, aFirstPoint, aCanvasSize,
+ NumDenToTime(aFctlChunk->delay_num, aFctlChunk->delay_den), aDisposal, aBlend);
+ aAnimation.Insert(aAnimationFrame);
+ }
+ }
+ for (sal_uInt32 i = 0; i < nFrames; i++)
+ {
+ fcTLChunk* aFctlChunk
+ = nSequenceIndex < aAPNGInfo.maFrameData.size()
+ ? dynamic_cast<fcTLChunk*>(aAPNGInfo.maFrameData[nSequenceIndex++].get())
+ : nullptr;
+ if (!aFctlChunk)
+ return false;
+ Disposal aDisposal = static_cast<Disposal>(aFctlChunk->dispose_op);
+ Blend aBlend = static_cast<Blend>(aFctlChunk->blend_op);
+ if (i == 0 && aDisposal == Disposal::Back)
+ aDisposal = Disposal::Previous;
+ SvMemoryStream aFrameStream;
+ getImportantChunks(rStream, aFrameStream, aFctlChunk->width, aFctlChunk->height);
+ // A single frame can have multiple fdAT chunks
+ while (fdATChunk* pFdatChunk
+ = nSequenceIndex < aAPNGInfo.maFrameData.size()
+ ? dynamic_cast<fdATChunk*>(aAPNGInfo.maFrameData[nSequenceIndex].get())
+ : nullptr)
+ {
+ // Write fdAT chunks as IDAT chunks
+ auto nDataSize = pFdatChunk->frame_data.size();
+ aFrameStream.WriteUInt32(nDataSize - PNG_TYPE_SIZE);
+ aFrameStream.WriteBytes(pFdatChunk->frame_data.data(), nDataSize);
+ sal_uInt32 nCrc = rtl_crc32(0, pFdatChunk->frame_data.data(), nDataSize);
+ aFrameStream.WriteUInt32(nCrc);
+ nSequenceIndex++;
+ }
+ aFrameStream.WriteUInt32(PNG_IEND_SIZE);
+ aFrameStream.WriteUInt32(PNG_IEND_SIGNATURE);
+ aFrameStream.WriteUInt32(PNG_IEND_CRC);
+ Graphic aFrameGraphic;
+ aFrameStream.Seek(0);
+ bool bSuccess = reader(aFrameStream, aFrameGraphic);
+ if (!bSuccess)
+ return false;
+ BitmapEx aFrameBitmapEx = aFrameGraphic.GetBitmapEx();
+ Point aStartPoint(aFctlChunk->x_offset, aFctlChunk->y_offset);
+ Size aSize(aFctlChunk->width, aFctlChunk->height);
+ AnimationFrame aAnimationFrame(
+ aFrameBitmapEx, aStartPoint, aSize,
+ NumDenToTime(aFctlChunk->delay_num, aFctlChunk->delay_den), aDisposal, aBlend);
+ aAnimation.Insert(aAnimationFrame);
+ }
+ rGraphic = aAnimation;
+ return true;
+ }
+ else
+ {
+ rGraphic = aBitmapEx;
+ }
+
+ return true;
+}
+
+BinaryDataContainer getMsGifChunk(SvStream& rStream)
+{
+ if (!isPng(rStream))
+ return {};
+ // 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 {};
+ 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 {}; // broken PNG
+
+ char msHeader[MSGifHeaderSize];
+ if (rStream.ReadBytes(msHeader, MSGifHeaderSize) != MSGifHeaderSize)
+ return {};
+ computedCrc = rtl_crc32(computedCrc, msHeader, MSGifHeaderSize);
+ length -= MSGifHeaderSize;
+
+ BinaryDataContainer chunk(rStream, length);
+ if (chunk.isEmpty())
+ return {};
+ computedCrc = rtl_crc32(computedCrc, chunk.getData(), chunk.getSize());
+ rStream.ReadUInt32(crc);
+ if (!ignoreCrc && crc != computedCrc)
+ continue; // invalid chunk, ignore
+ return chunk;
+ }
+ if (rStream.remainingSize() < length)
+ return {};
+ rStream.SeekRel(length);
+ rStream.ReadUInt32(crc);
+ if (type == PNG_IEND_SIGNATURE)
+ return {};
+ }
+}
+
+} // anonymous namespace
+
+namespace vcl
+{
+PngImageReader::PngImageReader(SvStream& rStream)
+ : mrStream(rStream)
+{
+}
+
+bool PngImageReader::read(BitmapEx& rBitmapEx)
+{
+ Graphic aGraphic;
+ bool bRet = reader(mrStream, aGraphic);
+ rBitmapEx = aGraphic.GetBitmapEx();
+ return bRet;
+}
+
+bool PngImageReader::read(Graphic& rGraphic) { return reader(mrStream, rGraphic); }
+
+BitmapEx PngImageReader::read()
+{
+ Graphic aGraphic;
+ read(aGraphic);
+ return aGraphic.GetBitmapEx();
+}
+
+BinaryDataContainer PngImageReader::getMicrosoftGifChunk(SvStream& rStream)
+{
+ sal_uInt64 originalPosition = rStream.Tell();
+ SvStreamEndian originalEndian = rStream.GetEndian();
+ rStream.SetEndian(SvStreamEndian::BIG);
+ auto chunk = getMsGifChunk(rStream);
+ rStream.SetEndian(originalEndian);
+ rStream.Seek(originalPosition);
+ return chunk;
+}
+
+bool ImportPNG(SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags,
+ BitmapScopedWriteAccess* pAccess, BitmapScopedWriteAccess* pAlphaAccess)
+{
+ // Creating empty bitmaps should be practically a no-op, and thus thread-safe.
+ Graphic aGraphic;
+ if (reader(rInputStream, aGraphic, nImportFlags, pAccess, pAlphaAccess))
+ {
+ if (!(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap))
+ rGraphic = aGraphic;
+ return true;
+ }
+ return false;
+}
+
+bool PngImageReader::isAPng(SvStream& rStream)
+{
+ auto nStmPos = rStream.Tell();
+ SvStreamEndian originalEndian = rStream.GetEndian();
+ comphelper::ScopeGuard aGuard([&rStream, nStmPos, originalEndian] {
+ rStream.Seek(nStmPos);
+ rStream.SetEndian(originalEndian);
+ });
+ if (!isPng(rStream))
+ return false;
+ rStream.SetEndian(SvStreamEndian::BIG);
+ sal_uInt32 nChunkSize, nChunkType;
+ rStream.ReadUInt32(nChunkSize);
+ rStream.ReadUInt32(nChunkType);
+ if (!rStream.good() || nChunkType != PNG_IHDR_SIGNATURE)
+ return false;
+ rStream.SeekRel(nChunkSize);
+ // Skip IHDR CRC
+ rStream.SeekRel(PNG_CRC_SIZE);
+ // Look for acTL chunk that exists before the first IDAT chunk
+ while (true)
+ {
+ rStream.ReadUInt32(nChunkSize);
+ if (!rStream.good())
+ return false;
+ rStream.ReadUInt32(nChunkType);
+ if (!rStream.good())
+ return false;
+ // Check if it's an IDAT chunk -> regular PNG
+ if (nChunkType == PNG_IDAT_SIGNATURE)
+ return false;
+ else if (nChunkType == PNG_ACTL_SIGNATURE)
+ return true;
+ else
+ {
+ if (!checkSeek(rStream, rStream.Tell() + nChunkSize + PNG_CRC_SIZE))
+ return false;
+ }
+ }
+}
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/png/PngImageWriter.cxx b/vcl/source/filter/png/PngImageWriter.cxx
new file mode 100644
index 0000000000..584487fabd
--- /dev/null
+++ b/vcl/source/filter/png/PngImageWriter.cxx
@@ -0,0 +1,511 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/PngImageWriter.hxx>
+#include <png.h>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <sal/log.hxx>
+#include <rtl/crc.h>
+
+namespace
+{
+void combineScanlineChannels(Scanline pColorScanline, Scanline pAlphaScanline,
+ std::vector<std::remove_pointer_t<Scanline>>& pResult,
+ sal_uInt32 nBitmapWidth, int colorType)
+{
+ if (colorType == PNG_COLOR_TYPE_GRAY_ALPHA)
+ {
+ for (sal_uInt32 i = 0; i < nBitmapWidth; ++i)
+ {
+ pResult[i * 2] = *pColorScanline++; // Gray
+ pResult[i * 2 + 1] = *pAlphaScanline++; // A
+ }
+ return;
+ }
+
+ for (sal_uInt32 i = 0; i < nBitmapWidth; ++i)
+ {
+ pResult[i * 4] = *pColorScanline++; // R
+ pResult[i * 4 + 1] = *pColorScanline++; // G
+ pResult[i * 4 + 2] = *pColorScanline++; // B
+ pResult[i * 4 + 3] = *pAlphaScanline++; // A
+ }
+}
+}
+
+namespace vcl
+{
+static void lclWriteStream(png_structp pPng, png_bytep pData, png_size_t pDataSize)
+{
+ png_voidp pIO = png_get_io_ptr(pPng);
+
+ if (pIO == nullptr)
+ return;
+
+ SvStream* pStream = static_cast<SvStream*>(pIO);
+
+ sal_Size nBytesWritten = pStream->WriteBytes(pData, pDataSize);
+
+ if (nBytesWritten != pDataSize)
+ png_error(pPng, "Write Error");
+}
+
+static void writeFctlChunk(std::vector<uint8_t>& aFctlChunk, sal_uInt32 nSequenceNumber, Size aSize,
+ Point aOffset, sal_uInt16 nDelayNum, sal_uInt16 nDelayDen,
+ Disposal nDisposeOp, Blend nBlendOp)
+{
+ if (aFctlChunk.size() != 26)
+ aFctlChunk.resize(26);
+
+ sal_uInt32 nWidth = aSize.Width();
+ sal_uInt32 nHeight = aSize.Height();
+ sal_uInt32 nXOffset = aOffset.X();
+ sal_uInt32 nYOffset = aOffset.Y();
+
+ // Writing each byte separately instead of using memcpy here for clarity
+ // about PNG chunks using big endian
+
+ // Write sequence number
+ aFctlChunk[0] = (nSequenceNumber >> 24) & 0xFF;
+ aFctlChunk[1] = (nSequenceNumber >> 16) & 0xFF;
+ aFctlChunk[2] = (nSequenceNumber >> 8) & 0xFF;
+ aFctlChunk[3] = nSequenceNumber & 0xFF;
+
+ // Write width
+ aFctlChunk[4] = (nWidth >> 24) & 0xFF;
+ aFctlChunk[5] = (nWidth >> 16) & 0xFF;
+ aFctlChunk[6] = (nWidth >> 8) & 0xFF;
+ aFctlChunk[7] = nWidth & 0xFF;
+
+ // Write height
+ aFctlChunk[8] = (nHeight >> 24) & 0xFF;
+ aFctlChunk[9] = (nHeight >> 16) & 0xFF;
+ aFctlChunk[10] = (nHeight >> 8) & 0xFF;
+ aFctlChunk[11] = nHeight & 0xFF;
+
+ // Write x offset
+ aFctlChunk[12] = (nXOffset >> 24) & 0xFF;
+ aFctlChunk[13] = (nXOffset >> 16) & 0xFF;
+ aFctlChunk[14] = (nXOffset >> 8) & 0xFF;
+ aFctlChunk[15] = nXOffset & 0xFF;
+
+ // Write y offset
+ aFctlChunk[16] = (nYOffset >> 24) & 0xFF;
+ aFctlChunk[17] = (nYOffset >> 16) & 0xFF;
+ aFctlChunk[18] = (nYOffset >> 8) & 0xFF;
+ aFctlChunk[19] = nYOffset & 0xFF;
+
+ // Write delay numerator
+ aFctlChunk[20] = (nDelayNum >> 8) & 0xFF;
+ aFctlChunk[21] = nDelayNum & 0xFF;
+
+ // Write delay denominator
+ aFctlChunk[22] = (nDelayDen >> 8) & 0xFF;
+ aFctlChunk[23] = nDelayDen & 0xFF;
+
+ // Write disposal method
+ aFctlChunk[24] = static_cast<uint8_t>(nDisposeOp);
+
+ // Write blend operation
+ aFctlChunk[25] = static_cast<uint8_t>(nBlendOp);
+}
+
+static bool pngWrite(SvStream& rStream, const Graphic& rGraphic, int nCompressionLevel,
+ bool bInterlaced, bool bTranslucent,
+ const std::vector<PngChunk>& aAdditionalChunks)
+{
+ if (rGraphic.IsNone())
+ return false;
+
+ sal_uInt32 nSequenceNumber = 0;
+ const bool bIsApng = rGraphic.IsAnimated();
+ Animation aAnimation = bIsApng ? rGraphic.GetAnimation() : Animation();
+
+ png_structp pPng = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+
+ if (!pPng)
+ return false;
+
+ png_infop pInfo = png_create_info_struct(pPng);
+ if (!pInfo)
+ {
+ png_destroy_write_struct(&pPng, nullptr);
+ return false;
+ }
+
+ BitmapEx aBitmapEx;
+ if (rGraphic.GetBitmapEx().getPixelFormat() == vcl::PixelFormat::N32_BPP)
+ {
+ if (!vcl::bitmap::convertBitmap32To24Plus8(rGraphic.GetBitmapExRef(), aBitmapEx))
+ return false;
+ }
+ else
+ {
+ aBitmapEx = rGraphic.GetBitmapExRef();
+ }
+
+ if (!bTranslucent)
+ {
+ // Clear alpha channel
+ aBitmapEx.ClearAlpha();
+ }
+
+ Bitmap aBitmap;
+ AlphaMask aAlphaMask;
+ BitmapScopedReadAccess pAccess;
+ BitmapScopedReadAccess pAlphaAccess;
+
+ if (setjmp(png_jmpbuf(pPng)))
+ {
+ pAccess.reset();
+ pAlphaAccess.reset();
+ png_destroy_read_struct(&pPng, &pInfo, nullptr);
+ return false;
+ }
+
+ // Set our custom stream writer
+ png_set_write_fn(pPng, &rStream, lclWriteStream, nullptr);
+
+ aBitmap = aBitmapEx.GetBitmap();
+ if (bTranslucent)
+ aAlphaMask = aBitmapEx.GetAlphaMask();
+
+ {
+ bool bCombineChannels = false;
+ pAccess = aBitmap;
+ if (bTranslucent)
+ pAlphaAccess = aAlphaMask;
+ Size aSize = aBitmapEx.GetSizePixel();
+
+ int bitDepth = -1;
+ int colorType = -1;
+
+ /* PNG_COLOR_TYPE_GRAY (1, 2, 4, 8, 16)
+ PNG_COLOR_TYPE_GRAY_ALPHA (8, 16)
+ PNG_COLOR_TYPE_PALETTE (bit depths 1, 2, 4, 8)
+ PNG_COLOR_TYPE_RGB (bit_depths 8, 16)
+ PNG_COLOR_TYPE_RGB_ALPHA (bit_depths 8, 16)
+ PNG_COLOR_MASK_PALETTE
+ PNG_COLOR_MASK_COLOR
+ PNG_COLOR_MASK_ALPHA
+ */
+ auto eScanlineFormat = pAccess->GetScanlineFormat();
+ switch (eScanlineFormat)
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ {
+ colorType = PNG_COLOR_TYPE_PALETTE;
+ bitDepth = 1;
+ break;
+ }
+ case ScanlineFormat::N8BitPal:
+ {
+ if (!aBitmap.HasGreyPalette8Bit())
+ colorType = PNG_COLOR_TYPE_PALETTE;
+ else
+ {
+ colorType = PNG_COLOR_TYPE_GRAY;
+ if (pAlphaAccess)
+ {
+ colorType = PNG_COLOR_TYPE_GRAY_ALPHA;
+ bCombineChannels = true;
+ }
+ }
+ bitDepth = 8;
+ break;
+ }
+ case ScanlineFormat::N24BitTcBgr:
+ {
+ png_set_bgr(pPng);
+ [[fallthrough]];
+ }
+ case ScanlineFormat::N24BitTcRgb:
+ {
+ colorType = PNG_COLOR_TYPE_RGB;
+ bitDepth = 8;
+ if (pAlphaAccess)
+ {
+ colorType = PNG_COLOR_TYPE_RGBA;
+ bCombineChannels = true;
+ }
+ break;
+ }
+ case ScanlineFormat::N32BitTcBgra:
+ {
+ png_set_bgr(pPng);
+ [[fallthrough]];
+ }
+ case ScanlineFormat::N32BitTcRgba:
+ {
+ colorType = PNG_COLOR_TYPE_RGBA;
+ bitDepth = 8;
+ break;
+ }
+ default:
+ {
+ return false;
+ }
+ }
+
+ if (aBitmapEx.GetPrefMapMode().GetMapUnit() == MapUnit::Map100thMM)
+ {
+ Size aPrefSize(aBitmapEx.GetPrefSize());
+ if (aPrefSize.Width() && aPrefSize.Height())
+ {
+ sal_uInt32 nPrefSizeX = o3tl::convert(aSize.Width(), 100000, aPrefSize.Width());
+ sal_uInt32 nPrefSizeY = o3tl::convert(aSize.Height(), 100000, aPrefSize.Height());
+ png_set_pHYs(pPng, pInfo, nPrefSizeX, nPrefSizeY, 1);
+ }
+ }
+
+ png_set_compression_level(pPng, nCompressionLevel);
+
+ int interlaceType = bInterlaced ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE;
+ int compressionType = PNG_COMPRESSION_TYPE_DEFAULT;
+ int filterMethod = PNG_FILTER_TYPE_DEFAULT;
+
+ // Convert BitmapPalette to png_color*
+ if (colorType == PNG_COLOR_TYPE_PALETTE)
+ {
+ // Reserve enough space for 3 channels for each palette entry
+ auto aBitmapPalette = pAccess->GetPalette();
+ auto nEntryCount = aBitmapPalette.GetEntryCount();
+ std::unique_ptr<png_color[]> aPngPaletteArray(new png_color[nEntryCount * 3]);
+ for (sal_uInt16 i = 0; i < nEntryCount; i++)
+ {
+ aPngPaletteArray[i].red = aBitmapPalette[i].GetRed();
+ aPngPaletteArray[i].green = aBitmapPalette[i].GetGreen();
+ aPngPaletteArray[i].blue = aBitmapPalette[i].GetBlue();
+ }
+ // Palette is copied over so it can be safely discarded
+ png_set_PLTE(pPng, pInfo, aPngPaletteArray.get(), nEntryCount);
+ }
+
+ png_set_IHDR(pPng, pInfo, aSize.Width(), aSize.Height(), bitDepth, colorType, interlaceType,
+ compressionType, filterMethod);
+
+ png_write_info(pPng, pInfo);
+
+ if (bIsApng)
+ {
+ // Write acTL chunk
+ sal_uInt32 nNumFrames = aAnimation.Count();
+ sal_uInt32 nNumPlays = aAnimation.GetLoopCount();
+
+ std::vector<uint8_t> aActlChunk;
+ aActlChunk.resize(8);
+
+ // Write number of frames
+ aActlChunk[0] = (nNumFrames >> 24) & 0xFF;
+ aActlChunk[1] = (nNumFrames >> 16) & 0xFF;
+ aActlChunk[2] = (nNumFrames >> 8) & 0xFF;
+ aActlChunk[3] = nNumFrames & 0xFF;
+
+ // Write number of plays
+ aActlChunk[4] = (nNumPlays >> 24) & 0xFF;
+ aActlChunk[5] = (nNumPlays >> 16) & 0xFF;
+ aActlChunk[6] = (nNumPlays >> 8) & 0xFF;
+ aActlChunk[7] = nNumPlays & 0xFF;
+
+ png_write_chunk(pPng, reinterpret_cast<png_const_bytep>("acTL"),
+ reinterpret_cast<png_const_bytep>(aActlChunk.data()),
+ aActlChunk.size());
+
+ // Write first frame fcTL chunk which is corresponding to the IDAT chunk
+ std::vector<uint8_t> aFctlChunk;
+ const AnimationFrame& rFirstFrame = *aAnimation.GetAnimationFrames()[0];
+ writeFctlChunk(aFctlChunk, nSequenceNumber++, rFirstFrame.maSizePixel,
+ rFirstFrame.maPositionPixel, rFirstFrame.mnWait, 100,
+ rFirstFrame.meDisposal, rFirstFrame.meBlend);
+
+ png_write_chunk(pPng, reinterpret_cast<png_const_bytep>("fcTL"),
+ reinterpret_cast<png_const_bytep>(aFctlChunk.data()),
+ aFctlChunk.size());
+ }
+
+ int nNumberOfPasses = 1;
+
+ Scanline pSourcePointer;
+
+ tools::Long nHeight = pAccess->Height();
+
+ for (int nPass = 0; nPass < nNumberOfPasses; nPass++)
+ {
+ for (tools::Long y = 0; y < nHeight; y++)
+ {
+ pSourcePointer = pAccess->GetScanline(y);
+ Scanline pFinalPointer = pSourcePointer;
+ std::vector<std::remove_pointer_t<Scanline>> aCombinedChannels;
+ if (bCombineChannels)
+ {
+ auto nBitmapWidth = pAccess->Width();
+ // Allocate enough size to fit all channels
+ aCombinedChannels.resize(nBitmapWidth * png_get_channels(pPng, pInfo));
+ Scanline pAlphaPointer = pAlphaAccess->GetScanline(y);
+ if (!pSourcePointer || !pAlphaPointer)
+ return false;
+ // Combine color and alpha channels
+ combineScanlineChannels(pSourcePointer, pAlphaPointer, aCombinedChannels,
+ nBitmapWidth, colorType);
+ pFinalPointer = aCombinedChannels.data();
+ }
+ png_write_row(pPng, pFinalPointer);
+ }
+ }
+ }
+
+ if (bIsApng)
+ {
+ // Already wrote first frame as an IDAT chunk
+ // Need to write the rest of the frames as fcTL & fdAT chunks
+ const auto& rFrames = aAnimation.GetAnimationFrames();
+
+ for (uint32_t i = 0; i < rFrames.size() - 1; i++)
+ {
+ const AnimationFrame& rCurrentFrame = *rFrames[1 + i];
+ SvMemoryStream aStream;
+
+ if (!pngWrite(aStream, rCurrentFrame.maBitmapEx, nCompressionLevel, bInterlaced,
+ bTranslucent, {}))
+ return false;
+
+ std::vector<uint8_t> aFdatChunk;
+
+ aStream.SetEndian(SvStreamEndian::BIG);
+
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+ aStream.Seek(8); // Skip PNG signature
+
+ while (aStream.good())
+ {
+ sal_uInt32 nChunkSize;
+ char sChunkName[4] = { 0 };
+ aStream.ReadUInt32(nChunkSize);
+ aStream.ReadBytes(sChunkName, 4);
+
+ if (std::string(sChunkName, 4) == "IDAT")
+ {
+ // 4 extra bytes for the sequence number
+ aFdatChunk.resize(nChunkSize + 4);
+ aStream.ReadBytes(aFdatChunk.data() + 4, nChunkSize);
+ break;
+ }
+ else
+ {
+ aStream.SeekRel(nChunkSize + 4);
+ }
+ }
+
+ std::vector<uint8_t> aFctlChunk;
+ writeFctlChunk(aFctlChunk, nSequenceNumber++, rCurrentFrame.maSizePixel,
+ rCurrentFrame.maPositionPixel, rCurrentFrame.mnWait, 100,
+ rCurrentFrame.meDisposal, rCurrentFrame.meBlend);
+
+ // Write sequence number
+ aFdatChunk[0] = nSequenceNumber >> 24;
+ aFdatChunk[1] = nSequenceNumber >> 16;
+ aFdatChunk[2] = nSequenceNumber >> 8;
+ aFdatChunk[3] = nSequenceNumber;
+ nSequenceNumber++;
+
+ png_write_chunk(pPng, reinterpret_cast<png_const_bytep>("fcTL"),
+ reinterpret_cast<png_const_bytep>(aFctlChunk.data()),
+ aFctlChunk.size());
+ png_write_chunk(pPng, reinterpret_cast<png_const_bytep>("fdAT"),
+ reinterpret_cast<png_const_bytep>(aFdatChunk.data()),
+ aFdatChunk.size());
+ }
+ }
+
+ if (!aAdditionalChunks.empty())
+ {
+ for (const auto& aChunk : aAdditionalChunks)
+ {
+ png_write_chunk(pPng, aChunk.name.data(), aChunk.data.data(), aChunk.size);
+ }
+ }
+
+ png_write_end(pPng, pInfo);
+
+ png_destroy_write_struct(&pPng, &pInfo);
+
+ return true;
+}
+
+void PngImageWriter::setParameters(css::uno::Sequence<css::beans::PropertyValue> const& rParameters)
+{
+ for (auto const& rValue : rParameters)
+ {
+ if (rValue.Name == "Compression")
+ rValue.Value >>= mnCompressionLevel;
+ else if (rValue.Name == "Interlaced")
+ rValue.Value >>= mbInterlaced;
+ else if (rValue.Name == "Translucent")
+ {
+ tools::Long nTmp = 0;
+ rValue.Value >>= nTmp;
+ if (!nTmp)
+ mbTranslucent = false;
+ }
+ else if (rValue.Name == "AdditionalChunks")
+ {
+ css::uno::Sequence<css::beans::PropertyValue> aAdditionalChunkSequence;
+ if (rValue.Value >>= aAdditionalChunkSequence)
+ {
+ for (const auto& rAdditionalChunk : std::as_const(aAdditionalChunkSequence))
+ {
+ if (rAdditionalChunk.Name.getLength() == 4)
+ {
+ vcl::PngChunk aChunk;
+ for (sal_Int32 k = 0; k < 4; k++)
+ {
+ aChunk.name[k] = static_cast<sal_uInt8>(rAdditionalChunk.Name[k]);
+ }
+ aChunk.name[4] = '\0';
+
+ css::uno::Sequence<sal_Int8> aByteSeq;
+ if (rAdditionalChunk.Value >>= aByteSeq)
+ {
+ sal_uInt32 nChunkSize = aByteSeq.getLength();
+ aChunk.size = nChunkSize;
+ if (nChunkSize)
+ {
+ const sal_Int8* pSource = aByteSeq.getConstArray();
+ std::vector<sal_uInt8> aData(pSource, pSource + nChunkSize);
+ aChunk.data = std::move(aData);
+ maAdditionalChunks.push_back(aChunk);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+PngImageWriter::PngImageWriter(SvStream& rStream)
+ : mrStream(rStream)
+ , mnCompressionLevel(6)
+ , mbInterlaced(false)
+ , mbTranslucent(true)
+{
+}
+
+bool PngImageWriter::write(const Graphic& rGraphic)
+{
+ return pngWrite(mrStream, rGraphic, mnCompressionLevel, mbInterlaced, mbTranslucent,
+ maAdditionalChunks);
+}
+
+} // 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 0000000000..01d5cf9f4a
--- /dev/null
+++ b/vcl/source/filter/png/png.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/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+namespace vcl
+{
+bool ImportPNG(SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags,
+ BitmapScopedWriteAccess* pAccess, BitmapScopedWriteAccess* pAlphaAccess);
+}
+
+/* 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 0000000000..0e9277dc92
--- /dev/null
+++ b/vcl/source/filter/svm/SvmConverter.cxx
@@ -0,0 +1,1289 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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(std::u16string_view rStr, sal_Int32& rIndex, sal_Int32& rLength,
+ KernArray* pDXAry = nullptr)
+ {
+ const sal_Int32 nStrLength = rStr.size();
+
+ 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_uInt64 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( tools::Polygon(aRect), aLineInfo ) );
+ }
+ }
+ break;
+
+ case GDI_ELLIPSE_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+
+ if( bFatLine )
+ {
+ 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( std::move(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));
+
+ KernArray 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.set(j, nTmp);
+ }
+
+ // #106172# Add last DX array elem, if missing
+ if( nAryLen != nStrLen )
+ {
+ if (nAryLen+1 == nStrLen && nIndex >= 0)
+ {
+ KernArray 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.set(nStrLen-1, aDXAry[ nStrLen-2 ] + aTmpAry[ nStrLen-1 ] - aTmpAry[ nStrLen-2 ]);
+ else
+ aDXAry.set(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, std::move(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( std::move(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<css::awt::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, std::move(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( std::move(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, std::move(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( std::move(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( std::move(aPolyPoly), std::move(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;
+ 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 0000000000..cde32b31fe
--- /dev/null
+++ b/vcl/source/filter/svm/SvmConverter.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#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);
+
+/* 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 0000000000..56f2d933bb
--- /dev/null
+++ b/vcl/source/filter/svm/SvmReader.cxx
@@ -0,0 +1,1480 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <unotools/configmgr.hxx>
+
+#include <vcl/filter/SvmReader.hxx>
+#include <vcl/rendercontext/DrawTextFlags.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);
+
+ KernArray 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
+ {
+ sal_Int32 i;
+ sal_Int32 val(0);
+ for (i = 0; i < nAryLen; i++)
+ {
+ mrStream.ReadInt32(val);
+ aArray.push_back(val);
+ }
+ // #106172# setup remainder
+ for (; i < nTmpLen; i++)
+ aArray.push_back(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));
+
+ if (aCompat.GetVersion() >= 3) // Version 3
+ {
+ sal_uInt32 nKashidaAryLen(0);
+ mrStream.ReadUInt32(nKashidaAryLen);
+ nTmpLen = std::min(nKashidaAryLen, static_cast<sal_uInt32>(pAction->GetDXArray().size()));
+ if (nTmpLen)
+ {
+ // aKashidaArray, if not empty, must be the same size as aArray
+ std::vector<sal_Bool> aKashidaArray(pAction->GetDXArray().size(), 0);
+
+ // [-loplugin:fakebool] false positive:
+ sal_Bool val(sal_False);
+ for (sal_uInt32 i = 0; i < nTmpLen; i++)
+ {
+ mrStream.ReadUChar(val);
+ aKashidaArray[i] = val;
+ }
+ pAction->SetKashidaArray(std::move(aKashidaArray));
+ }
+ }
+
+ 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);
+
+ DrawTextFlags nFlags(static_cast<DrawTextFlags>(nTmp));
+ const static bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ if (bFuzzing)
+ nFlags = nFlags & ~DrawTextFlags::MultiLine;
+
+ pAction->SetStyle(nFlags);
+
+ 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);
+ if (nTempWidth < 0)
+ {
+ SAL_WARN("vcl.gdi", "negative width");
+ nTempWidth = 0;
+ }
+ 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;
+}
+
+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);
+
+ 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);
+
+ 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);
+
+ Gradient aGradient;
+ aSerializer.readGradient(aGradient);
+
+ pAction->SetGDIMetaFile(aMtf);
+ pAction->SetPoint(aPoint);
+ pAction->SetSize(aSize);
+ pAction->SetGradient(aGradient);
+
+ // tdf#155479 add support for MCGR and SVG export
+ if (aCompat.GetVersion() > 1)
+ {
+ basegfx::BColorStops aColorStops;
+ sal_uInt16 nTmp(0);
+ double fOff, fR, fG, fB;
+ mrStream.ReadUInt16(nTmp);
+
+ const size_t nMaxPossibleEntries = mrStream.remainingSize() / 4 * sizeof(double);
+ if (nTmp > nMaxPossibleEntries)
+ {
+ SAL_WARN("vcl.gdi", "gradient record claims to have: " << nTmp << " entries, but only "
+ << nMaxPossibleEntries
+ << " possible, clamping");
+ nTmp = nMaxPossibleEntries;
+ }
+
+ for (sal_uInt16 a(0); a < nTmp; a++)
+ {
+ mrStream.ReadDouble(fOff);
+ mrStream.ReadDouble(fR);
+ mrStream.ReadDouble(fG);
+ mrStream.ReadDouble(fB);
+
+ aColorStops.emplace_back(fOff, basegfx::BColor(fR, fG, fB));
+ }
+
+ pAction->addSVGTransparencyColorStops(aColorStops);
+ }
+
+ 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 0000000000..09c7cce21e
--- /dev/null
+++ b/vcl/source/filter/svm/SvmWriter.cxx
@@ -0,0 +1,1447 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <rtl/crc.h>
+#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 = rtl_crc32(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA);
+ nCrc = rtl_crc32(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ Int32ToSVBT32(pAct->GetPoint().X(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetPoint().Y(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = rtl_crc32(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA);
+ nCrc = rtl_crc32(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ Int32ToSVBT32(pAct->GetPoint().X(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetPoint().Y(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSize().Width(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSize().Height(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = rtl_crc32(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA);
+ nCrc = rtl_crc32(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ Int32ToSVBT32(pAct->GetDestPoint().X(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestPoint().Y(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestSize().Width(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestSize().Height(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcPoint().X(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcPoint().Y(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcSize().Width(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcSize().Height(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ MetaBmpExAction* pAct = static_cast<MetaBmpExAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = rtl_crc32(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmapEx().GetChecksum(), aBCOA);
+ nCrc = rtl_crc32(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ Int32ToSVBT32(pAct->GetPoint().X(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetPoint().Y(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = rtl_crc32(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmapEx().GetChecksum(), aBCOA);
+ nCrc = rtl_crc32(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ Int32ToSVBT32(pAct->GetPoint().X(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetPoint().Y(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSize().Width(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSize().Height(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = rtl_crc32(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmapEx().GetChecksum(), aBCOA);
+ nCrc = rtl_crc32(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ Int32ToSVBT32(pAct->GetDestPoint().X(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestPoint().Y(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestSize().Width(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestSize().Height(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcPoint().X(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcPoint().Y(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcSize().Width(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcSize().Height(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::MASK:
+ {
+ MetaMaskAction* pAct = static_cast<MetaMaskAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = rtl_crc32(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA);
+ nCrc = rtl_crc32(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ UInt32ToSVBT32(sal_uInt32(pAct->GetColor()), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetPoint().X(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetPoint().Y(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::MASKSCALE:
+ {
+ MetaMaskScaleAction* pAct = static_cast<MetaMaskScaleAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = rtl_crc32(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA);
+ nCrc = rtl_crc32(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ UInt32ToSVBT32(sal_uInt32(pAct->GetColor()), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetPoint().X(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetPoint().Y(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSize().Width(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSize().Height(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ {
+ MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = rtl_crc32(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA);
+ nCrc = rtl_crc32(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ UInt32ToSVBT32(sal_uInt32(pAct->GetColor()), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestPoint().X(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestPoint().Y(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestSize().Width(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestSize().Height(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcPoint().X(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcPoint().Y(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcSize().Width(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcSize().Height(), aBT32);
+ nCrc = rtl_crc32(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction);
+ nCrc = rtl_crc32(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 = rtl_crc32(nCrc, aSVBT64, 8);
+ DoubleToSVBT64(aPoint.getY(), aSVBT64);
+ nCrc = rtl_crc32(nCrc, aSVBT64, 8);
+
+ if (bControl)
+ {
+ if (rPolygon.isPrevControlPointUsed(b))
+ {
+ const basegfx::B2DPoint aCtrl(rPolygon.getPrevControlPoint(b));
+
+ DoubleToSVBT64(aCtrl.getX(), aSVBT64);
+ nCrc = rtl_crc32(nCrc, aSVBT64, 8);
+ DoubleToSVBT64(aCtrl.getY(), aSVBT64);
+ nCrc = rtl_crc32(nCrc, aSVBT64, 8);
+ }
+
+ if (rPolygon.isNextControlPointUsed(b))
+ {
+ const basegfx::B2DPoint aCtrl(rPolygon.getNextControlPoint(b));
+
+ DoubleToSVBT64(aCtrl.getX(), aSVBT64);
+ nCrc = rtl_crc32(nCrc, aSVBT64, 8);
+ DoubleToSVBT64(aCtrl.getY(), aSVBT64);
+ nCrc = rtl_crc32(nCrc, aSVBT64, 8);
+ }
+ }
+ }
+ }
+
+ sal_uInt8 tmp = static_cast<sal_uInt8>(rAct.IsClipping());
+ nCrc = rtl_crc32(nCrc, &tmp, 1);
+ }
+ else
+ {
+ SvmWriter aWriter(aMemStm);
+ aWriter.MetaActionHandler(pAction, &aWriteData);
+ nCrc = rtl_crc32(nCrc, aMemStm.GetData(), aMemStm.Tell());
+ aMemStm.Seek(0);
+ }
+ }
+ break;
+
+ default:
+ {
+ SvmWriter aWriter(aMemStm);
+ aWriter.MetaActionHandler(pAction, &aWriteData);
+ nCrc = rtl_crc32(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 KernArray& rDXArray = pAction->GetDXArray();
+
+ const sal_Int32 nAryLen = !rDXArray.empty() ? pAction->GetLen() : 0;
+
+ VersionCompatWrite aCompat(mrStream, 3);
+ 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
+
+ // Version 3
+ const auto& rKashidaArray = pAction->GetKashidaArray();
+ mrStream.WriteUInt32(rKashidaArray.size());
+ for (const auto& val : rKashidaArray)
+ mrStream.WriteUChar(val);
+}
+
+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()));
+
+ // tdf#155479 prep vars for MCGR
+ const basegfx::BColorStops* pSVGTransparencyColorStops(pAction->getSVGTransparencyColorStops());
+ const bool bSVG(nullptr != pSVGTransparencyColorStops);
+
+ VersionCompatWrite aCompat(mrStream, bSVG ? 2 : 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());
+
+ // tdf#155479 add support for MCGR and SVG export
+ if (bSVG)
+ {
+ sal_uInt16 nTmp(sal::static_int_cast<sal_uInt16>(pSVGTransparencyColorStops->size()));
+ mrStream.WriteUInt16(nTmp);
+
+ for (auto const& rCand : *pSVGTransparencyColorStops)
+ {
+ mrStream.WriteDouble(rCand.getStopOffset());
+ const basegfx::BColor& rColor(rCand.getStopColor());
+ mrStream.WriteDouble(rColor.getRed());
+ mrStream.WriteDouble(rColor.getGreen());
+ mrStream.WriteDouble(rColor.getBlue());
+ }
+ }
+}
+
+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 0000000000..1d75ff79e8
--- /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 <vcl/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.
+ BitmapScopedWriteAccess 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())
+ {
+ BitmapScopedWriteAccess 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 0000000000..c5249b5adf
--- /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.GetAlphaMask();
+ BitmapScopedReadAccess access(bitmap);
+ BitmapScopedReadAccess 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 0000000000..0277affb82
--- /dev/null
+++ b/vcl/source/filter/wmf/emfwr.cxx
@@ -0,0 +1,1510 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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_uInt64 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_uInt64 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;
+ sal_uInt64 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( tools::PolyPolygon(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_uInt64 nDIBSize = aMemStm.Tell();
+ sal_uInt32 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, KernArraySpan pDXArray, sal_uInt32 nWidth )
+{
+ sal_Int32 nLen = rText.getLength(), i;
+
+ if( !nLen )
+ return;
+
+ sal_uInt32 nNormWidth;
+ KernArray aOwnArray;
+ KernArraySpan 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.assign(pDXArray);
+ pDX = aOwnArray;
+ }
+ const double fFactor = static_cast<double>(nWidth) / nNormWidth;
+
+ for( i = 0; i < ( nLen - 1 ); i++ )
+ aOwnArray.set(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() );
+ AlphaMask aMsk( pA->GetBitmapEx().GetAlphaMask() );
+
+ if( !aMsk.IsEmpty() )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ ImplWriteBmpRecord( aMsk.GetBitmap(), 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() );
+ AlphaMask aMsk( pA->GetBitmapEx().GetAlphaMask() );
+
+ if( !aMsk.IsEmpty() )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ ImplWriteBmpRecord( aMsk.GetBitmap(), 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() );
+ AlphaMask aMsk( aBmpEx.GetAlphaMask() );
+
+ if( !aMsk.IsEmpty() )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ ImplWriteBmpRecord( aMsk.GetBitmap(), 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 0000000000..75f7f38196
--- /dev/null
+++ b/vcl/source/filter/wmf/emfwr.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#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_uInt64 mnRecordPos;
+ sal_uInt64 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, KernArraySpan 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);
+};
+
+/* 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 0000000000..db615410c1
--- /dev/null
+++ b/vcl/source/filter/wmf/wmf.cxx
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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_uInt64 nStreamStart(rStream.Tell());
+ const sal_uInt64 nStreamEnd(rStream.TellEnd());
+
+ if (nStreamStart >= nStreamEnd)
+ {
+ return false;
+ }
+
+ // Read binary data to mem array
+ const sal_uInt64 nStreamLength(nStreamEnd - nStreamStart);
+ BinaryDataContainer aDataContainer(rStream, nStreamLength);
+ 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 0000000000..4d183bd304
--- /dev/null
+++ b/vcl/source/filter/wmf/wmfexternal.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 <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)
+{
+}
+
+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 0000000000..5f1089c777
--- /dev/null
+++ b/vcl/source/filter/wmf/wmfwr.cxx
@@ -0,0 +1,1901 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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, KernArraySpan 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 ) || IsOpenSymbol( 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,
+ std::u16string_view rString,
+ KernArraySpan pDXAry )
+{
+ sal_Int32 nOriginalTextLen = rString.size();
+
+ 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, std::u16string_view rString,
+ const OString& rByteString, KernArraySpan 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.size();
+ 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();
+ KernArray 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.set(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() );
+ AlphaMask aMsk( pA->GetBitmapEx().GetAlphaMask() );
+
+ if( !aMsk.IsEmpty() )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ 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() );
+ AlphaMask aMsk( pA->GetBitmapEx().GetAlphaMask() );
+
+ if( !aMsk.IsEmpty() )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ WMFRecord_StretchDIB( pA->GetPoint(), pA->GetSize(), aMsk.GetBitmap(), 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() );
+ AlphaMask aMsk( aBmpEx.GetAlphaMask() );
+
+ if( !aMsk.IsEmpty() )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ WMFRecord_StretchDIB( pA->GetDestPoint(), pA->GetDestSize(), aMsk.GetBitmap(), 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 0000000000..b22d3d295c
--- /dev/null
+++ b/vcl/source/filter/wmf/wmfwr.hxx
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+#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, KernArraySpan pDXAry );
+ void WMFRecord_ExtTextOut(const Point& rPoint, std::u16string_view rString, KernArraySpan pDXAry);
+
+ void TrueExtTextOut(const Point& rPoint, std::u16string_view rString,
+ const OString& rByteString, KernArraySpan 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);
+};
+
+/* 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 0000000000..e494fb7ce7
--- /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/EmphasisMark.cxx b/vcl/source/font/EmphasisMark.cxx
new file mode 100644
index 0000000000..be1a20223e
--- /dev/null
+++ b/vcl/source/font/EmphasisMark.cxx
@@ -0,0 +1,174 @@
+/* -*- 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 <sal/config.h>
+
+#include <font/EmphasisMark.hxx>
+
+namespace vcl::font
+{
+EmphasisMark::EmphasisMark(FontEmphasisMark eEmphasis, tools::Long nHeight, sal_Int32 nDPIY)
+{
+ 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 }
+ };
+
+ mnWidth = 0;
+ mnYOff = 0;
+ mbIsPolyLine = 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 )
+ maRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) );
+ else
+ {
+ tools::Long nRad = nDotSize/2;
+ tools::Polygon aPoly( Point( nRad, nRad ), nRad, nRad );
+ maPolyPoly.Insert( aPoly );
+ }
+ mnYOff = ((nHeight*250)/1000)/2; // Center to the another EmphasisMarks
+ mnWidth = nDotSize;
+ break;
+
+ case FontEmphasisMark::Circle:
+ // Dot has 80% of the height
+ nDotSize = (nHeight*800)/1000;
+ if ( !nDotSize )
+ nDotSize = 1;
+ if ( nDotSize <= 2 )
+ maRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) );
+ else
+ {
+ tools::Long nRad = nDotSize/2;
+ tools::Polygon aPoly( Point( nRad, nRad ), nRad, nRad );
+ maPolyPoly.Insert( aPoly );
+ // Border mnWidth is 15%
+ tools::Long nBorder = (nDotSize*150)/1000;
+ if ( nBorder <= 1 )
+ mbIsPolyLine = true;
+ else
+ {
+ tools::Polygon aPoly2( Point( nRad, nRad ),
+ nRad-nBorder, nRad-nBorder );
+ maPolyPoly.Insert( aPoly2 );
+ }
+ }
+ mnWidth = nDotSize;
+ break;
+
+ case FontEmphasisMark::Disc:
+ // Dot has 80% of the height
+ nDotSize = (nHeight*800)/1000;
+ if ( !nDotSize )
+ nDotSize = 1;
+ if ( nDotSize <= 2 )
+ maRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) );
+ else
+ {
+ tools::Long nRad = nDotSize/2;
+ tools::Polygon aPoly( Point( nRad, nRad ), nRad, nRad );
+ maPolyPoly.Insert( aPoly );
+ }
+ mnWidth = 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 )
+ {
+ maRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) );
+ mnWidth = nDotSize;
+ }
+ else
+ {
+ maRect1 = tools::Rectangle( Point(), Size( 1, 1 ) );
+ maRect2 = 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();
+ mnWidth = aBoundRect.GetWidth();
+ nDotSize = aBoundRect.GetHeight();
+ maPolyPoly.Insert( aTemp );
+ }
+ break;
+ default: break;
+ }
+
+ // calculate position
+ tools::Long nOffY = 1+(nDPIY/300); // one visible pixel space
+ tools::Long nSpaceY = nHeight-nDotSize;
+ if ( nSpaceY >= nOffY*2 )
+ mnYOff += nOffY;
+
+ if ( !(eEmphasis & FontEmphasisMark::PosBelow) )
+ mnYOff += nDotSize;
+}
+}
+
+/* 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 0000000000..ece71df8ed
--- /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_nCode(0)
+ , m_eType(FeatureType::OpenType)
+{
+}
+
+Feature::Feature(uint32_t nCode, FeatureType eType)
+ : m_nCode(nCode)
+ , 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(-1)
+ , m_eType(FeatureParameterType::BOOL)
+{
+}
+
+FeatureDefinition::FeatureDefinition(uint32_t nCode, OUString aDescription,
+ FeatureParameterType eType,
+ std::vector<FeatureParameter>&& rEnumParameters,
+ int32_t nDefault)
+ : m_sDescription(std::move(aDescription))
+ , m_nCode(nCode)
+ , m_nDefault(nDefault)
+ , m_eType(eType)
+ , m_aEnumParameters(std::move(rEnumParameters))
+{
+}
+
+FeatureDefinition::FeatureDefinition(uint32_t nCode, TranslateId pDescriptionID,
+ OUString aNumericPart)
+ : m_pDescriptionID(pDescriptionID)
+ , m_sNumericPart(std::move(aNumericPart))
+ , m_nCode(nCode)
+ , m_nDefault(-1)
+ , m_eType(FeatureParameterType::BOOL)
+{
+}
+
+FeatureDefinition::FeatureDefinition(uint32_t nCode, TranslateId pDescriptionID,
+ std::vector<FeatureParameter> aEnumParameters)
+ : m_pDescriptionID(pDescriptionID)
+ , m_nCode(nCode)
+ , m_nDefault(-1)
+ , 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; }
+
+int32_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 0000000000..fd175eade0
--- /dev/null
+++ b/vcl/source/font/FeatureCollector.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/.
+ */
+
+#include <font/FeatureCollector.hxx>
+#include <font/OpenTypeFeatureDefinitionList.hxx>
+#include <i18nlangtag/languagetag.hxx>
+
+#include <font/OpenTypeFeatureStrings.hrc>
+#include <svdata.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_rLanguageTag.getLanguageType());
+
+ 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(nFeatureCode, vcl::font::FeatureType::Graphite);
+ vcl::font::Feature& rFeature = m_rFontFeatures.back();
+ rFeature.m_aDefinition
+ = vcl::font::FeatureDefinition(nFeatureCode, sLabel, eFeatureParameterType,
+ std::move(aParameters), int32_t(nValue));
+ }
+ }
+ gr_featureval_destroy(pfeatureValues);
+ return true;
+}
+
+void FeatureCollector::collectForTable(hb_tag_t aTableTag)
+{
+ unsigned int nFeatureCount
+ = hb_ot_layout_table_get_feature_tags(m_pHbFace, aTableTag, 0, nullptr, nullptr);
+ std::vector<hb_tag_t> aFeatureTags(nFeatureCount);
+ hb_ot_layout_table_get_feature_tags(m_pHbFace, aTableTag, 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_nCode = aFeatureTag;
+
+ FeatureDefinition aDefinition = OpenTypeFeatureDefinitionList().getDefinition(rFeature);
+ std::vector<vcl::font::FeatureParameter> aParameters{
+ { 0, VclResId(STR_FONT_FEATURE_PARAM_NONE) }
+ };
+
+ unsigned int nFeatureIdx;
+ if (hb_ot_layout_language_find_feature(m_pHbFace, aTableTag, 0,
+ HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX, aFeatureTag,
+ &nFeatureIdx))
+ {
+ // ssXX and cvXX can have name ID defined for them, check for
+ // them and use as appropriate.
+ hb_ot_name_id_t aLabelID;
+ hb_ot_name_id_t aFirstParameterID;
+ unsigned nNamedParameters;
+ if (hb_ot_layout_feature_get_name_ids(m_pHbFace, aTableTag, nFeatureIdx, &aLabelID,
+ nullptr, nullptr, &nNamedParameters,
+ &aFirstParameterID))
+ {
+ OUString sLabel = m_pFace->GetName(NameID(aLabelID), m_rLanguageTag);
+ if (!sLabel.isEmpty())
+ aDefinition = vcl::font::FeatureDefinition(aFeatureTag, sLabel);
+
+ // cvXX features can have parameters name IDs, check for
+ // them and populate feature parameters as appropriate.
+ for (unsigned i = 0; i < nNamedParameters; i++)
+ {
+ hb_ot_name_id_t aNameID = aFirstParameterID + i;
+ OUString sName = m_pFace->GetName(NameID(aNameID), m_rLanguageTag);
+ if (!sName.isEmpty())
+ aParameters.emplace_back(uint32_t(i + 1), sName);
+ else
+ aParameters.emplace_back(uint32_t(i + 1), OUString::number(i + 1));
+ }
+ }
+
+ unsigned int nAlternates = 0;
+ if (aTableTag == HB_OT_TAG_GSUB)
+ {
+ // Collect lookups in this feature, and input glyphs for each
+ // lookup, and calculate the max number of alternates they have.
+ unsigned int nLookups = hb_ot_layout_feature_get_lookups(
+ m_pHbFace, aTableTag, nFeatureIdx, 0, nullptr, nullptr);
+ std::vector<unsigned int> aLookups(nLookups);
+ hb_ot_layout_feature_get_lookups(m_pHbFace, aTableTag, nFeatureIdx, 0, &nLookups,
+ aLookups.data());
+
+ hb_set_t* pGlyphs = hb_set_create();
+ for (unsigned int nLookupIdx : aLookups)
+ {
+ hb_set_clear(pGlyphs);
+ hb_ot_layout_lookup_collect_glyphs(m_pHbFace, aTableTag, nLookupIdx, nullptr,
+ pGlyphs, nullptr, nullptr);
+ hb_codepoint_t nGlyphIdx = HB_SET_VALUE_INVALID;
+ while (hb_set_next(pGlyphs, &nGlyphIdx))
+ {
+ nAlternates
+ = std::max(nAlternates,
+ hb_ot_layout_lookup_get_glyph_alternates(
+ m_pHbFace, nLookupIdx, nGlyphIdx, 0, nullptr, nullptr));
+ }
+ }
+ hb_set_destroy(pGlyphs);
+ }
+
+ // Append the alternates to the feature parameters, keeping any
+ // existing ones calculated from cvXX features above.
+ for (unsigned int i = aParameters.size() - 1; i < nAlternates; i++)
+ aParameters.emplace_back(uint32_t(i + 1), OUString::number(i + 1));
+
+ if (aParameters.size() > 1)
+ {
+ aDefinition = vcl::font::FeatureDefinition(
+ aFeatureTag, aDefinition.getDescription(),
+ vcl::font::FeatureParameterType::ENUM, std::move(aParameters), 0);
+ }
+ }
+
+ if (aDefinition)
+ rFeature.m_aDefinition = aDefinition;
+ }
+}
+
+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 0000000000..697662032f
--- /dev/null
+++ b/vcl/source/font/FeatureParser.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/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, int32_t> FeatureParser::getFeaturesMap() const
+{
+ std::unordered_map<uint32_t, int32_t> aResultMap;
+ for (auto const& rFeat : m_aFeatures)
+ 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 0000000000..30c2c9aaa5
--- /dev/null
+++ b/vcl/source/font/FontSelectPattern.cxx
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <utility>
+#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,
+ OUString aSearchName, const Size& rSize, float fExactHeight, bool bNonAntialias)
+ : maSearchName(std::move( aSearchName ))
+ , 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/LogicalFontInstance.cxx b/vcl/source/font/LogicalFontInstance.cxx
new file mode 100644
index 0000000000..0c21cba475
--- /dev/null
+++ b/vcl/source/font/LogicalFontInstance.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 <sal/config.h>
+
+#include <hb-ot.h>
+#include <hb-graphite2.h>
+
+#include <font/PhysicalFontFace.hxx>
+#include <font/LogicalFontInstance.hxx>
+#include <impfontcache.hxx>
+
+LogicalFontInstance::LogicalFontInstance(const vcl::font::PhysicalFontFace& rFontFace,
+ const vcl::font::FontSelectPattern& rFontSelData)
+ : mxFontMetric(new FontMetricData(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);
+
+ if (m_pHbFontUntransformed)
+ hb_font_destroy(m_pHbFontUntransformed);
+
+ if (m_pHbDrawFuncs)
+ hb_draw_funcs_destroy(m_pHbDrawFuncs);
+}
+
+hb_font_t* LogicalFontInstance::InitHbFont()
+{
+ auto pFace = GetFontFace();
+ hb_face_t* pHbFace = pFace->GetHbFace();
+ assert(pHbFace);
+ auto nUPEM = pFace->UnitsPerEm();
+
+ hb_font_t* pHbFont = hb_font_create(pHbFace);
+ hb_font_set_scale(pHbFont, nUPEM, nUPEM);
+ hb_ot_font_set_funcs(pHbFont);
+
+ auto aVariations = pFace->GetVariations(*this);
+ if (!aVariations.empty())
+ hb_font_set_variations(pHbFont, aVariations.data(), aVariations.size());
+
+ // If we are applying artificial italic, instruct HarfBuzz to do the same
+ // so that mark positioning is also transformed.
+ if (NeedsArtificialItalic())
+ hb_font_set_synthetic_slant(pHbFont, ARTIFICIAL_ITALIC_SKEW);
+
+ ImplInitHbFont(pHbFont);
+
+ return pHbFont;
+}
+
+hb_font_t* LogicalFontInstance::GetHbFontUntransformed() const
+{
+ auto* pHbFont = const_cast<LogicalFontInstance*>(this)->GetHbFont();
+
+ if (NeedsArtificialItalic()) // || NeedsArtificialBold()
+ {
+ if (!m_pHbFontUntransformed)
+ {
+ m_pHbFontUntransformed = hb_font_create_sub_font(pHbFont);
+ // Unset slant set on parent font.
+ // Does not actually work: https://github.com/harfbuzz/harfbuzz/issues/3890
+ hb_font_set_synthetic_slant(m_pHbFontUntransformed, 0);
+ }
+ return m_pHbFontUntransformed;
+ }
+
+ return pHbFont;
+}
+
+double LogicalFontInstance::GetKashidaWidth() const
+{
+ sal_GlyphId nGlyph = GetGlyphIndex(0x0640);
+ if (nGlyph)
+ return GetGlyphWidth(nGlyph);
+ return 0;
+}
+
+void LogicalFontInstance::GetScale(double* nXScale, double* nYScale) const
+{
+ double nUPEM = GetFontFace()->UnitsPerEm();
+ 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;
+
+ auto* pHbFont = const_cast<LogicalFontInstance*>(this)->GetHbFont();
+ hb_glyph_extents_t aExtents;
+ bool res = hb_font_get_glyph_extents(pHbFont, nID, &aExtents);
+
+ if (res)
+ {
+ double nXScale = 0, nYScale = 0;
+ GetScale(&nXScale, &nYScale);
+
+ double fMinX = aExtents.x_bearing;
+ double fMinY = aExtents.y_bearing;
+ double fMaxX = aExtents.x_bearing + aExtents.width;
+ double fMaxY = aExtents.y_bearing + aExtents.height;
+
+ tools::Rectangle aRect(std::floor(fMinX * nXScale), -std::ceil(fMinY * nYScale),
+ std::ceil(fMaxX * nXScale), -std::floor(fMaxY * nYScale));
+ if (mnOrientation && !bVertical)
+ {
+ // Apply font rotation.
+ const double fRad = toRadians(mnOrientation);
+ const double fCos = cos(fRad);
+ const double fSin = sin(fRad);
+
+ rRect.SetLeft(fCos * aRect.Left() + fSin * aRect.Top());
+ rRect.SetTop(-fSin * aRect.Left() - fCos * aRect.Top());
+ rRect.SetRight(fCos * aRect.Right() + fSin * aRect.Bottom());
+ rRect.SetBottom(-fSin * aRect.Right() - fCos * aRect.Bottom());
+ }
+ else
+ rRect = aRect;
+ }
+
+ if (mpFontCache && res)
+ mpFontCache->CacheGlyphBoundRect(this, nID, rRect);
+ return res;
+}
+
+sal_GlyphId LogicalFontInstance::GetGlyphIndex(uint32_t nUnicode, uint32_t nVariationSelector) const
+{
+ auto* pHbFont = const_cast<LogicalFontInstance*>(this)->GetHbFont();
+ sal_GlyphId nGlyph = 0;
+ if (hb_font_get_glyph(pHbFont, nUnicode, nVariationSelector, &nGlyph))
+ return nGlyph;
+ return 0;
+}
+
+double LogicalFontInstance::GetGlyphWidth(sal_GlyphId nGlyph, bool bVertical, bool bScale) const
+{
+ auto* pHbFont = const_cast<LogicalFontInstance*>(this)->GetHbFont();
+ int nWidth;
+ if (bVertical)
+ nWidth = hb_font_get_glyph_v_advance(pHbFont, nGlyph);
+ else
+ nWidth = hb_font_get_glyph_h_advance(pHbFont, nGlyph);
+
+ if (!bScale)
+ return nWidth;
+
+ double nScale = 0;
+ GetScale(&nScale, nullptr);
+ return double(nWidth * nScale);
+}
+
+bool LogicalFontInstance::IsGraphiteFont()
+{
+ if (!m_xbIsGraphiteFont.has_value())
+ {
+ 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)
+ {
+ m_xeFontFamilyEnum = FontFamilyEnum::Unclassified;
+
+ // 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 (GetFontFace()->GetName(vcl::font::NAME_ID_FONT_FAMILY) == "DFKai-SB")
+ 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;
+}
+
+bool LogicalFontInstance::NeedsArtificialBold() const
+{
+ return m_aFontSelData.GetWeight() > WEIGHT_MEDIUM && m_pFontFace->GetWeight() <= WEIGHT_MEDIUM;
+}
+
+bool LogicalFontInstance::NeedsArtificialItalic() const
+{
+ return m_aFontSelData.GetItalic() != ITALIC_NONE && m_pFontFace->GetItalic() == ITALIC_NONE;
+}
+
+namespace
+{
+void move_to_func(hb_draw_funcs_t*, void* /*pDrawData*/, hb_draw_state_t*, float to_x, float to_y,
+ void* pUserData)
+{
+ auto pPoly = static_cast<basegfx::B2DPolygon*>(pUserData);
+ pPoly->append(basegfx::B2DPoint(to_x, -to_y));
+}
+
+void line_to_func(hb_draw_funcs_t*, void* /*pDrawData*/, hb_draw_state_t*, float to_x, float to_y,
+ void* pUserData)
+{
+ auto pPoly = static_cast<basegfx::B2DPolygon*>(pUserData);
+ pPoly->append(basegfx::B2DPoint(to_x, -to_y));
+}
+
+void cubic_to_func(hb_draw_funcs_t*, void* /*pDrawData*/, hb_draw_state_t*, float control1_x,
+ float control1_y, float control2_x, float control2_y, float to_x, float to_y,
+ void* pUserData)
+{
+ auto pPoly = static_cast<basegfx::B2DPolygon*>(pUserData);
+ pPoly->appendBezierSegment(basegfx::B2DPoint(control1_x, -control1_y),
+ basegfx::B2DPoint(control2_x, -control2_y),
+ basegfx::B2DPoint(to_x, -to_y));
+}
+
+void close_path_func(hb_draw_funcs_t*, void* pDrawData, hb_draw_state_t*, void* pUserData)
+{
+ auto pPolyPoly = static_cast<basegfx::B2DPolyPolygon*>(pDrawData);
+ auto pPoly = static_cast<basegfx::B2DPolygon*>(pUserData);
+ pPolyPoly->append(*pPoly);
+ pPoly->clear();
+}
+}
+
+basegfx::B2DPolyPolygon LogicalFontInstance::GetGlyphOutlineUntransformed(sal_GlyphId nGlyph) const
+{
+ if (!m_pHbDrawFuncs)
+ {
+ m_pHbDrawFuncs = hb_draw_funcs_create();
+ auto pUserData = const_cast<basegfx::B2DPolygon*>(&m_aDrawPolygon);
+ hb_draw_funcs_set_move_to_func(m_pHbDrawFuncs, move_to_func, pUserData, nullptr);
+ hb_draw_funcs_set_line_to_func(m_pHbDrawFuncs, line_to_func, pUserData, nullptr);
+ hb_draw_funcs_set_cubic_to_func(m_pHbDrawFuncs, cubic_to_func, pUserData, nullptr);
+ // B2DPolyPolygon does not support quadratic curves, HarfBuzz will
+ // convert them to cubic curves for us if we don’t set a callback
+ // function.
+ //hb_draw_funcs_set_quadratic_to_func(m_pHbDrawFuncs, quadratic_to_func, pUserData, nullptr);
+ hb_draw_funcs_set_close_path_func(m_pHbDrawFuncs, close_path_func, pUserData, nullptr);
+ }
+
+ basegfx::B2DPolyPolygon aPolyPoly;
+#if HB_VERSION_ATLEAST(7, 0, 0)
+ hb_font_draw_glyph(GetHbFontUntransformed(), nGlyph, m_pHbDrawFuncs, &aPolyPoly);
+#else
+ hb_font_get_glyph_shape(GetHbFontUntransformed(), nGlyph, m_pHbDrawFuncs, &aPolyPoly);
+#endif
+ return aPolyPoly;
+}
+
+/* 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 0000000000..0400833937
--- /dev/null
+++ b/vcl/source/font/OpenTypeFeatureDefinitionList.cxx
@@ -0,0 +1,159 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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"),
+ });
+}
+
+FeatureDefinition OpenTypeFeatureDefinitionListPrivate::getDefinition(vcl::font::Feature& rFeature)
+{
+ if (rFeature.isCharacterVariant() || rFeature.isStylisticSet())
+ {
+ FeatureDefinition aFeatureDefinition;
+ OUString sNumericPart = OUStringChar(char((rFeature.m_nCode >> 8) & 0xFF))
+ + OUStringChar(char((rFeature.m_nCode >> 0) & 0xFF));
+ if (rFeature.isCharacterVariant())
+ aFeatureDefinition = { rFeature.m_nCode, STR_FONT_FEATURE_ID_CVXX, sNumericPart };
+ else if (rFeature.isStylisticSet())
+ aFeatureDefinition = { rFeature.m_nCode, STR_FONT_FEATURE_ID_SSXX, sNumericPart };
+ return aFeatureDefinition;
+ }
+
+ auto nFeatureCode = rFeature.m_nCode;
+ 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 0000000000..04b031ad6b
--- /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( std::u16string_view rFontName )
+{
+ // Test, if Fontname includes CJK characters --> In this case we
+ // mention that it is a CJK font
+ for(size_t i = 0; i < rFontName.size(); 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, u"" );
+
+ 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,
+ std::u16string_view 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.empty() && (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.size() >= 4) &&
+ (pData->GetMatchFamilyName().getLength() >= 4) &&
+ ((rSearchFamilyName.find( pData->GetMatchFamilyName() ) != std::u16string_view::npos) ||
+ (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.IsMicrosoftSymbolEncoded() )
+ {
+ 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.IsMicrosoftSymbolEncoded() )
+ 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 0000000000..aa9a9327f7
--- /dev/null
+++ b/vcl/source/font/PhysicalFontFace.cxx
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <osl/file.hxx>
+#include <osl/thread.h>
+
+#include <fontattributes.hxx>
+#include <impfontcharmap.hxx>
+#include <sft.hxx>
+#include <salgdi.hxx>
+
+#include <font/FontSelectPattern.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <string_view>
+
+#include <hb-ot.h>
+
+namespace vcl::font
+{
+PhysicalFontFace::PhysicalFontFace(const FontAttributes& rDFA)
+ : FontAttributes(rDFA)
+ , mpHbFace(nullptr)
+ , mpHbUnscaledFont(nullptr)
+{
+}
+
+PhysicalFontFace::~PhysicalFontFace()
+{
+ if (mpHbFace)
+ hb_face_destroy(mpHbFace);
+ if (mpHbUnscaledFont)
+ hb_font_destroy(mpHbUnscaledFont);
+}
+
+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;
+}
+
+RawFontData PhysicalFontFace::GetRawFontData(uint32_t nTag) const
+{
+ auto pHbFace = GetHbFace();
+ // If nTag is 0, reference the whole font.
+ if (!nTag)
+ return RawFontData(hb_face_reference_blob(pHbFace));
+ return RawFontData(hb_face_reference_table(pHbFace, nTag));
+}
+
+static hb_blob_t* getTable(hb_face_t*, hb_tag_t nTag, void* pUserData)
+{
+ return static_cast<const PhysicalFontFace*>(pUserData)->GetHbTable(nTag);
+}
+
+hb_face_t* PhysicalFontFace::GetHbFace() const
+{
+ if (mpHbFace == nullptr)
+ mpHbFace
+ = hb_face_create_for_tables(getTable, const_cast<PhysicalFontFace*>(this), nullptr);
+ return mpHbFace;
+}
+
+hb_font_t* PhysicalFontFace::GetHbUnscaledFont() const
+{
+ if (mpHbUnscaledFont == nullptr)
+ mpHbUnscaledFont = hb_font_create(GetHbFace());
+ return mpHbUnscaledFont;
+}
+
+FontCharMapRef PhysicalFontFace::GetFontCharMap() const
+{
+ if (mxCharMap.is())
+ return mxCharMap;
+
+ // Check if this font is using symbol cmap subtable, most likely redundant
+ // since HarfBuzz handles mapping symbol fonts for us.
+ RawFontData aData(GetRawFontData(HB_TAG('c', 'm', 'a', 'p')));
+ bool bSymbol = HasMicrosoftSymbolCmap(aData.data(), aData.size());
+
+ hb_face_t* pHbFace = GetHbFace();
+ hb_set_t* pUnicodes = hb_set_create();
+ hb_face_collect_unicodes(pHbFace, pUnicodes);
+
+ if (hb_set_get_population(pUnicodes))
+ {
+ // Convert HarfBuzz set to code ranges.
+ std::vector<sal_UCS4> aRangeCodes;
+ hb_codepoint_t nFirst, nLast = HB_SET_VALUE_INVALID;
+ while (hb_set_next_range(pUnicodes, &nFirst, &nLast))
+ {
+ aRangeCodes.push_back(nFirst);
+ aRangeCodes.push_back(nLast + 1);
+ }
+
+ mxCharMap = new FontCharMap(bSymbol, std::move(aRangeCodes));
+ }
+
+ hb_set_destroy(pUnicodes);
+
+ if (!mxCharMap.is())
+ mxCharMap = FontCharMap::GetDefaultMap(IsMicrosoftSymbolEncoded());
+
+ return mxCharMap;
+}
+
+bool PhysicalFontFace::GetFontCapabilities(vcl::FontCapabilities& rFontCapabilities) const
+{
+ if (!mxFontCapabilities)
+ {
+ mxFontCapabilities.emplace();
+ RawFontData aData(GetRawFontData(HB_TAG('O', 'S', '/', '2')));
+ getTTCoverage(mxFontCapabilities->oUnicodeRange, mxFontCapabilities->oCodePageRange,
+ aData.data(), aData.size());
+ }
+
+ rFontCapabilities = *mxFontCapabilities;
+ return rFontCapabilities.oUnicodeRange || rFontCapabilities.oCodePageRange;
+}
+
+namespace
+{
+class RawFace
+{
+public:
+ RawFace(hb_face_t* pFace)
+ : mpFace(hb_face_reference(pFace))
+ {
+ }
+
+ RawFace(const RawFace& rOther)
+ : mpFace(hb_face_reference(rOther.mpFace))
+ {
+ }
+
+ ~RawFace() { hb_face_destroy(mpFace); }
+
+ RawFontData GetTable(uint32_t nTag) const
+ {
+ return RawFontData(hb_face_reference_table(mpFace, nTag));
+ }
+
+private:
+ hb_face_t* mpFace;
+};
+
+class TrueTypeFace final : public AbstractTrueTypeFont
+{
+ const RawFace m_aFace;
+ mutable std::array<RawFontData, NUM_TAGS> m_aTableList;
+
+ const RawFontData& table(sal_uInt32 nIdx) const
+ {
+ assert(nIdx < NUM_TAGS);
+ static const uint32_t aTags[NUM_TAGS] = {
+ T_maxp, T_glyf, T_head, T_loca, T_name, T_hhea, T_hmtx, T_cmap,
+ T_vhea, T_vmtx, T_OS2, T_post, T_cvt, T_prep, T_fpgm, T_CFF,
+ };
+ if (m_aTableList[nIdx].empty())
+ m_aTableList[nIdx] = std::move(m_aFace.GetTable(aTags[nIdx]));
+ return m_aTableList[nIdx];
+ }
+
+public:
+ TrueTypeFace(RawFace aFace, const FontCharMapRef rCharMap)
+ : AbstractTrueTypeFont(nullptr, rCharMap)
+ , m_aFace(std::move(aFace))
+ {
+ }
+
+ bool hasTable(sal_uInt32 nIdx) const override { return !table(nIdx).empty(); }
+ const sal_uInt8* table(sal_uInt32 nIdx, sal_uInt32& nSize) const override
+ {
+ auto& rTable = table(nIdx);
+ nSize = rTable.size();
+ return rTable.data();
+ }
+};
+}
+
+bool PhysicalFontFace::CreateFontSubset(std::vector<sal_uInt8>& rOutBuffer,
+ const sal_GlyphId* pGlyphIds, const sal_uInt8* pEncoding,
+ const int nGlyphCount, FontSubsetInfo& rInfo) const
+{
+ // Prepare data for font subsetter.
+ TrueTypeFace aSftFont(RawFace(GetHbFace()), GetFontCharMap());
+ if (aSftFont.initialize() != SFErrCodes::Ok)
+ return false;
+
+ // write subset into destination file
+ return CreateTTFfontSubset(aSftFont, rOutBuffer, pGlyphIds, pEncoding, nGlyphCount, rInfo);
+}
+
+bool PhysicalFontFace::HasColorLayers() const
+{
+ const auto pHbFace = GetHbFace();
+ return hb_ot_color_has_layers(pHbFace) && hb_ot_color_has_palettes(pHbFace);
+}
+
+const std::vector<ColorPalette>& PhysicalFontFace::GetColorPalettes() const
+{
+ if (!mxColorPalettes)
+ {
+ mxColorPalettes.emplace();
+ const auto pHbFace = GetHbFace();
+ auto nPalettes = hb_ot_color_palette_get_count(pHbFace);
+ mxColorPalettes->reserve(nPalettes);
+ for (auto nPalette = 0u; nPalette < nPalettes; nPalette++)
+ {
+ auto nColors = hb_ot_color_palette_get_colors(pHbFace, nPalette, 0, nullptr, nullptr);
+ ColorPalette aPalette(nColors);
+ for (auto nColor = 0u; nColor < nColors; nColor++)
+ {
+ uint32_t nCount = 1;
+ hb_color_t aColor;
+ hb_ot_color_palette_get_colors(pHbFace, nPalette, nColor, &nCount, &aColor);
+ auto a = hb_color_get_alpha(aColor);
+ auto r = hb_color_get_red(aColor);
+ auto g = hb_color_get_green(aColor);
+ auto b = hb_color_get_blue(aColor);
+ aPalette[nColor] = Color(ColorAlphaTag::ColorAlpha, a, r, g, b);
+ }
+ mxColorPalettes->push_back(aPalette);
+ }
+ }
+
+ return *mxColorPalettes;
+}
+
+std::vector<ColorLayer> PhysicalFontFace::GetGlyphColorLayers(sal_GlyphId nGlyphIndex) const
+{
+ if (!HasColorLayers())
+ return {};
+
+ const auto pHbFace = GetHbFace();
+
+ auto nLayers = hb_ot_color_glyph_get_layers(pHbFace, nGlyphIndex, 0, nullptr, nullptr);
+ std::vector<ColorLayer> aLayers(nLayers);
+ for (auto nLayer = 0u; nLayer < nLayers; nLayer++)
+ {
+ hb_ot_color_layer_t aLayer;
+ uint32_t nCount = 1;
+ hb_ot_color_glyph_get_layers(pHbFace, nGlyphIndex, nLayer, &nCount, &aLayer);
+ aLayers[nLayer] = { aLayer.glyph, aLayer.color_index };
+ }
+
+ return aLayers;
+}
+
+bool PhysicalFontFace::HasColorBitmaps() const { return hb_ot_color_has_png(GetHbFace()); }
+
+RawFontData PhysicalFontFace::GetGlyphColorBitmap(sal_GlyphId nGlyphIndex,
+ tools::Rectangle& rRect) const
+{
+ if (!HasColorBitmaps())
+ return {};
+
+ hb_font_t* pHbFont = GetHbUnscaledFont();
+ auto aData = RawFontData(hb_ot_color_glyph_reference_png(pHbFont, nGlyphIndex));
+ if (!aData.empty())
+ {
+ hb_glyph_extents_t aExtents;
+ if (hb_font_get_glyph_extents(pHbFont, nGlyphIndex, &aExtents))
+ {
+ auto aPoint = Point(aExtents.x_bearing, aExtents.y_bearing + aExtents.height);
+ auto aSize = Size(aExtents.width, -aExtents.height);
+ rRect = tools::Rectangle(aPoint, aSize);
+ }
+ }
+
+ return aData;
+}
+
+OString PhysicalFontFace::GetGlyphName(sal_GlyphId nGlyphIndex, bool bValidate) const
+{
+ char aBuffer[256];
+ hb_font_glyph_to_string(GetHbUnscaledFont(), nGlyphIndex, aBuffer, 256);
+ if (bValidate)
+ {
+ // https://learn.microsoft.com/en-us/typography/opentype/spec/post#version-20
+ // Valid characters are limited to A–Z, a–z, 0–9, “.†(FULL STOP), and “_†(LOW LINE).
+ const char* p = aBuffer;
+ while ((*p >= '0' && *p <= '9') || (*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z')
+ || *p == '.' || *p == '_')
+ ++p;
+ if (*p != '\0')
+ return "g" + OString::number(nGlyphIndex);
+ }
+
+ return OString(aBuffer);
+}
+
+OUString PhysicalFontFace::GetName(NameID aNameID, const LanguageTag& rLanguageTag) const
+{
+ auto pHbFace = GetHbFace();
+
+ auto aHbLang = HB_LANGUAGE_INVALID;
+ if (rLanguageTag.getLanguageType() != LANGUAGE_NONE)
+ {
+ auto aLanguage(rLanguageTag.getBcp47().toUtf8());
+ aHbLang = hb_language_from_string(aLanguage.getStr(), aLanguage.getLength());
+ }
+
+ auto nName = hb_ot_name_get_utf16(pHbFace, aNameID, aHbLang, nullptr, nullptr);
+ if (!nName && aHbLang == HB_LANGUAGE_INVALID)
+ {
+ // Fallback to English if localized name is missing.
+ aHbLang = hb_language_from_string("en", 2);
+ nName = hb_ot_name_get_utf16(pHbFace, aNameID, aHbLang, nullptr, nullptr);
+ }
+
+ OUString sName;
+ if (nName)
+ {
+ std::vector<uint16_t> aBuf(++nName); // make space for terminating NUL.
+ hb_ot_name_get_utf16(pHbFace, aNameID, aHbLang, &nName, aBuf.data());
+ sName = OUString(reinterpret_cast<sal_Unicode*>(aBuf.data()), nName);
+ }
+
+ return sName;
+}
+
+const std::vector<hb_variation_t>& PhysicalFontFace::GetVariations(const LogicalFontInstance&) const
+{
+ if (!mxVariations)
+ {
+ SAL_WARN("vcl.fonts", "Getting font variations is not supported.");
+ mxVariations.emplace();
+ }
+ return *mxVariations;
+}
+}
+
+/* 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 0000000000..6956394a59
--- /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>
+#include <utility>
+
+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( std::u16string_view rFontName )
+{
+ // Test, if Fontname includes CJK characters --> In this case we
+ // mention that it is a CJK font
+ for(size_t i = 0; i < rFontName.size(); 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( OUString aSearchName )
+: maSearchName(std::move( aSearchName )),
+ 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();
+ 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->IsMicrosoftSymbolEncoded() )
+ 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 0000000000..6dfee423a9
--- /dev/null
+++ b/vcl/source/font/font.cxx
@@ -0,0 +1,1185 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <i18nlangtag/mslangid.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 (GetFamilyName() != rFamilyName
+ || GetAverageFontSize() != rSize)
+ {
+ mpImplFont->SetFamilyName( rFamilyName );
+ mpImplFont->SetFontSize( rSize );
+ }
+}
+
+Font::Font( const OUString& rFamilyName, const OUString& rStyleName, const Size& rSize )
+{
+ if (GetFamilyName() != rFamilyName
+ || GetStyleName() != rStyleName
+ || GetAverageFontSize() != rSize)
+ {
+ mpImplFont->SetFamilyName( rFamilyName );
+ mpImplFont->SetStyleName( rStyleName );
+ mpImplFont->SetFontSize( rSize );
+ }
+}
+
+Font::Font( FontFamily eFamily, const Size& rSize )
+{
+ if (GetFontFamily() != eFamily
+ || GetAverageFontSize() != rSize)
+ {
+ mpImplFont->SetFamilyType( eFamily );
+ mpImplFont->SetFontSize( rSize );
+ }
+}
+
+Font::~Font()
+{
+}
+
+void Font::SetColor( const Color& rColor )
+{
+ if (GetColor() != rColor)
+ {
+ mpImplFont->maColor = rColor;
+ }
+}
+
+void Font::SetFillColor( const Color& rColor )
+{
+ if (GetFillColor() != rColor)
+ {
+ mpImplFont->maFillColor = rColor;
+ if ( rColor.IsTransparent() )
+ mpImplFont->mbTransparent = true;
+ }
+}
+
+void Font::SetTransparent( bool bTransparent )
+{
+ if (IsTransparent() != 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 (GetFamilyName() != rFamilyName)
+ mpImplFont->SetFamilyName( rFamilyName );
+}
+
+void Font::SetStyleName( const OUString& rStyleName )
+{
+ if (GetStyleName() != 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 );
+}
+
+void Font::SetLanguageTag( const LanguageTag& rLanguageTag )
+{
+ if (GetLanguageTag() != rLanguageTag)
+ mpImplFont->maLanguageTag = rLanguageTag;
+}
+
+void Font::SetCJKContextLanguageTag( const LanguageTag& rLanguageTag )
+{
+ if (GetCJKContextLanguageTag() != rLanguageTag)
+ mpImplFont->maCJKLanguageTag = rLanguageTag;
+}
+
+void Font::SetLanguage( LanguageType eLanguage )
+{
+ if (GetLanguage() != eLanguage)
+ mpImplFont->maLanguageTag.reset( eLanguage);
+}
+
+void Font::SetCJKContextLanguage( LanguageType eLanguage )
+{
+ if (GetCJKContextLanguage() != 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::SetFixKerning( short nSpacing )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->mnSpacing != nSpacing)
+ mpImplFont->mnSpacing = nSpacing;
+}
+
+short Font::GetFixKerning() const
+{
+ return mpImplFont->mnSpacing;
+}
+
+bool Font::IsFixKerning() const
+{
+ return mpImplFont->mnSpacing != 0;
+}
+
+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;
+}
+
+FontEmphasisMark Font::GetEmphasisMarkStyle() const
+{
+ FontEmphasisMark nEmphasisMark = GetEmphasisMark();
+
+ // If no Position is set, then calculate the default position, which
+ // depends on the language
+ if (!(nEmphasisMark & (FontEmphasisMark::PosAbove | FontEmphasisMark::PosBelow)))
+ {
+ LanguageType eLang = GetLanguage();
+ // In Chinese Simplified the EmphasisMarks are below/left
+ if (MsLangId::isSimplifiedChinese(eLang))
+ {
+ nEmphasisMark |= FontEmphasisMark::PosBelow;
+ }
+ else
+ {
+ eLang = GetCJKContextLanguage();
+
+ // In Chinese Simplified the EmphasisMarks are below/left
+ if (MsLangId::isSimplifiedChinese(eLang))
+ nEmphasisMark |= FontEmphasisMark::PosBelow;
+ else
+ nEmphasisMark |= FontEmphasisMark::PosAbove;
+ }
+ }
+
+ return nEmphasisMark;
+}
+
+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::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.SetMicrosoftSymbolEncoded( 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 OUString 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"_ustr;
+
+ 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);
+ }
+ if (rImplFont.maAverageFontSize.Height() > 8192)
+ {
+ SAL_WARN("vcl.gdi", "suspicious average height of: " << rImplFont.maAverageFontSize.Height());
+ rImplFont.maAverageFontSize.setHeight(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 & o3tl::typed_flags<FontEmphasisMark>::mask);
+ }
+
+ 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;
+ }
+
+ if( aCompat.GetVersion() >= 5 )
+ {
+ rIStm.ReadInt16( nTmps16 );
+ rImplFont.mnSpacing = nTmps16;
+ }
+
+ // Relief
+ // CJKContextLanguage
+
+ return rIStm;
+}
+
+SvStream& WriteImplFont( SvStream& rOStm, const ImplFont& rImplFont, tools::Long nNormedFontScaling )
+{
+ // tdf#127471 increase to version 4
+ // tdf#66819 increase to version 5
+ VersionCompatWrite aCompat( rOStm, 5 );
+
+ 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);
+
+ // new in version 5
+ rOStm.WriteInt16( rImplFont.mnSpacing );
+ 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.isEmpty() )
+ o_rResult.SetFamilyName( aInfo.ufamily );
+ else if( !aInfo.family.isEmpty() )
+ 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.isEmpty() )
+ o_rResult.SetStyleName( aInfo.usubfamily );
+ else if( !aInfo.subfamily.isEmpty() )
+ 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 FontFamily& Font::GetFontFamily() const { return mpImplFont->meFamily; }
+
+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(); }
+const Size& Font::GetAverageFontSize() const { return mpImplFont->maAverageFontSize; }
+
+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 ),
+ mnSpacing( 0 ),
+ meCharSet( RTL_TEXTENCODING_DONTKNOW ),
+ maLanguageTag( LANGUAGE_DONTKNOW ),
+ maCJKLanguageTag( LANGUAGE_DONTKNOW ),
+ 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 ),
+ mnSpacing( rImplFont.mnSpacing ),
+ maAverageFontSize( rImplFont.maAverageFontSize ),
+ meCharSet( rImplFont.meCharSet ),
+ maLanguageTag( rImplFont.maLanguageTag ),
+ maCJKLanguageTag( rImplFont.maCJKLanguageTag ),
+ 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)
+ || (mnSpacing != rOther.mnSpacing)
+ || (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, mnSpacing );
+ 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 0000000000..5c020d0724
--- /dev/null
+++ b/vcl/source/font/fontattributes.cxx
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 ),
+ mbMicrosoftSymbolEncoded( 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 (mbMicrosoftSymbolEncoded != rOther.mbMicrosoftSymbolEncoded)
+ 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 0000000000..c0dba15350
--- /dev/null
+++ b/vcl/source/font/fontcache.cxx
@@ -0,0 +1,283 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <font/LogicalFontInstance.hxx>
+#include <tools/debug.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.IsMicrosoftSymbolEncoded() || rB.IsMicrosoftSymbolEncoded())
+ {
+ 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()
+{
+ DBG_TESTSOLARMUTEX();
+ 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 )
+{
+ DBG_TESTSOLARMUTEX();
+ 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->IsMicrosoftSymbolEncoded() || aFontSelData.IsMicrosoftSymbolEncoded() || IsOpenSymbol(aFontSelData.maSearchName) )
+ {
+ 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()
+{
+ DBG_TESTSOLARMUTEX();
+ // #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 0000000000..1a149bcc7d
--- /dev/null
+++ b/vcl/source/font/fontcharmap.cxx
@@ -0,0 +1,251 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#include <utility>
+#include <vcl/fontcharmap.hxx>
+#include <impfontcharmap.hxx>
+#include <sal/log.hxx>
+
+#include <algorithm>
+#include <vector>
+
+static ImplFontCharMapRef g_pDefaultImplFontCharMap;
+const std::vector<sal_uInt32> aDefaultUnicodeRanges = { 0x0020, 0xD800, 0xE000, 0xFFF0 };
+const std::vector<sal_uInt32> aDefaultSymbolRanges = { 0x0020, 0x0100, 0xF020, 0xF100 };
+
+ImplFontCharMap::~ImplFontCharMap()
+{
+}
+
+ImplFontCharMap::ImplFontCharMap(bool bMicrosoftSymbolMap, std::vector<sal_uInt32> aRangeCodes)
+: maRangeCodes(std::move(aRangeCodes))
+, mnCharCount( 0 )
+, m_bMicrosoftSymbolMap(bMicrosoftSymbolMap)
+{
+ for (size_t i = 0; i < maRangeCodes.size(); i += 2)
+ {
+ sal_UCS4 cFirst = maRangeCodes[i];
+ sal_UCS4 cLast = maRangeCodes[i + 1];
+ mnCharCount += cLast - cFirst;
+ }
+}
+
+ImplFontCharMapRef const & ImplFontCharMap::getDefaultMap(bool bMicrosoftSymbolMap)
+{
+ const auto& rRanges = bMicrosoftSymbolMap ? aDefaultSymbolRanges : aDefaultUnicodeRanges;
+ g_pDefaultImplFontCharMap = ImplFontCharMapRef(new ImplFontCharMap(bMicrosoftSymbolMap, rRanges));
+ return g_pDefaultImplFontCharMap;
+}
+
+bool ImplFontCharMap::isDefaultMap() const
+{
+ const bool bIsDefault = (maRangeCodes == aDefaultUnicodeRanges) || (maRangeCodes == aDefaultSymbolRanges);
+ return bIsDefault;
+}
+
+static unsigned GetUShort(const unsigned char* p) { return((p[0]<<8) | p[1]);}
+
+bool HasMicrosoftSymbolCmap(const unsigned char* pCmap, int nLength)
+{
+ // 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;
+
+ for (const unsigned char* p = pCmap + 4; --nSubTables >= 0; p += 8)
+ {
+ int nPlatform = GetUShort(p);
+ int nEncoding = GetUShort(p + 2);
+ // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cmap.html
+ // When the platformID is 3 (Windows), an encoding of 0 is Symbol
+ if (nPlatform == 3 && nEncoding == 0)
+ return true;
+ }
+
+ return false;
+}
+
+FontCharMap::FontCharMap()
+ : mpImplFontCharMap( ImplFontCharMap::getDefaultMap() )
+{
+}
+
+FontCharMap::FontCharMap( ImplFontCharMapRef pIFCMap )
+ : mpImplFontCharMap(std::move( pIFCMap ))
+{
+}
+
+FontCharMap::FontCharMap(bool bMicrosoftSymbolMap, std::vector<sal_uInt32> aRangeCodes)
+ : mpImplFontCharMap(new ImplFontCharMap(bMicrosoftSymbolMap, std::move(aRangeCodes)))
+{
+}
+
+FontCharMap::~FontCharMap()
+{
+ mpImplFontCharMap = nullptr;
+}
+
+FontCharMapRef FontCharMap::GetDefaultMap(bool bMicrosoftSymbolMap)
+{
+ FontCharMapRef xFontCharMap( new FontCharMap( ImplFontCharMap::getDefaultMap(bMicrosoftSymbolMap) ) );
+ return xFontCharMap;
+}
+
+bool FontCharMap::IsDefaultMap() const
+{
+ return mpImplFontCharMap->isDefaultMap();
+}
+
+bool FontCharMap::isMicrosoftSymbolMap() const { return mpImplFontCharMap->m_bMicrosoftSymbolMap; }
+
+int FontCharMap::GetCharCount() const
+{
+ return mpImplFontCharMap->mnCharCount;
+}
+
+int FontCharMap::CountCharsInRange( sal_UCS4 cMin, sal_UCS4 cMax ) const
+{
+ const auto& rRanges = mpImplFontCharMap->maRangeCodes;
+ int nCount = 0;
+
+ // find and adjust range and char count for cMin
+ int nRangeMin = findRangeIndex( cMin );
+ if( nRangeMin & 1 )
+ ++nRangeMin;
+ else if (cMin > rRanges[nRangeMin])
+ nCount -= cMin - rRanges[nRangeMin];
+
+ // find and adjust range and char count for cMax
+ int nRangeMax = findRangeIndex( cMax );
+ if( nRangeMax & 1 )
+ --nRangeMax;
+ else
+ nCount -= rRanges[nRangeMax + 1] - cMax - 1;
+
+ // count chars in complete ranges between cMin and cMax
+ for( int i = nRangeMin; i <= nRangeMax; i+=2 )
+ nCount += rRanges[i + 1] - rRanges[i];
+
+ return nCount;
+}
+
+bool FontCharMap::HasChar( sal_UCS4 cChar ) const
+{
+ const int nRange = findRangeIndex( cChar );
+ if (nRange==0 && cChar < mpImplFontCharMap->maRangeCodes[0])
+ return false;
+ return ((nRange & 1) == 0); // inside a range
+}
+
+sal_UCS4 FontCharMap::GetFirstChar() const
+{
+ return mpImplFontCharMap->maRangeCodes.front();
+}
+
+sal_UCS4 FontCharMap::GetLastChar() const
+{
+ return mpImplFontCharMap->maRangeCodes.back() - 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->maRangeCodes[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->maRangeCodes[nRange] - 1; // => last in prev range
+ return (cChar - 1);
+}
+
+int FontCharMap::GetIndexFromChar( sal_UCS4 cChar ) const
+{
+ // TODO: improve linear walk?
+ int nCharIndex = 0;
+ const auto& rRanges = mpImplFontCharMap->maRangeCodes;
+ for (size_t i = 0; i < rRanges.size(); i += 2)
+ {
+ sal_UCS4 cFirst = rRanges[i];
+ sal_UCS4 cLast = rRanges[i + 1];
+ 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 auto& rRanges = mpImplFontCharMap->maRangeCodes;
+ for (size_t i = 0; i < rRanges.size(); i += 2)
+ {
+ sal_UCS4 cFirst = rRanges[i];
+ sal_UCS4 cLast = rRanges[i + 1];
+ nIndex -= cLast - cFirst;
+ if( nIndex < 0 )
+ return (cLast + nIndex);
+ }
+
+ // we can only get here with an out-of-bounds charindex
+ return mpImplFontCharMap->maRangeCodes.front();
+}
+
+int FontCharMap::findRangeIndex( sal_UCS4 cChar ) const
+{
+ const auto& rRanges = mpImplFontCharMap->maRangeCodes;
+ int nLower = 0;
+ int nMid = rRanges.size() / 2;
+ int nUpper = rRanges.size() - 1;
+ while( nLower < nUpper )
+ {
+ if (cChar >= rRanges[nMid])
+ nLower = nMid;
+ else
+ nUpper = nMid - 1;
+ nMid = (nLower + nUpper + 1) / 2;
+ }
+
+ return nMid;
+}
+
+/* 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 0000000000..115b3da6fb
--- /dev/null
+++ b/vcl/source/font/fontmetric.cxx
@@ -0,0 +1,563 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <unotools/configmgr.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/outdev.hxx>
+
+#include <font/FontSelectPattern.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <font/LogicalFontInstance.hxx>
+#include <font/FontMetricData.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.IsMicrosoftSymbolEncoded() ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE);
+ 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;
+}
+
+FontMetricData::FontMetricData( 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() );
+}
+
+bool FontMetricData::ShouldNotUseUnderlineMetrics() const
+{
+ if (utl::ConfigManager::IsFuzzing())
+ return false;
+
+ css::uno::Sequence<OUString> rNoUnderlineMetricsList(
+ officecfg::Office::Common::Misc::FontsDontUseUnderlineMetrics::get());
+ if (comphelper::findValue(rNoUnderlineMetricsList, GetFamilyName()) != -1)
+ {
+ SAL_INFO("vcl.gdi.fontmetric", "Not using underline metrics for: " << GetFamilyName());
+ return true;
+ }
+ return false;
+}
+
+bool FontMetricData::ImplInitTextLineSizeHarfBuzz(LogicalFontInstance* pFont)
+{
+ if (ShouldNotUseUnderlineMetrics())
+ return false;
+
+ auto* pHbFont = pFont->GetHbFont();
+
+ hb_position_t nUnderlineSize;
+ if (!hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_UNDERLINE_SIZE, &nUnderlineSize))
+ return false;
+ hb_position_t nUnderlineOffset;
+ if (!hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_UNDERLINE_OFFSET, &nUnderlineOffset))
+ return false;
+ hb_position_t nStrikeoutSize;
+ if (!hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_STRIKEOUT_SIZE, &nStrikeoutSize))
+ return false;
+ hb_position_t nStrikeoutOffset;
+ if (!hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_STRIKEOUT_OFFSET, &nStrikeoutOffset))
+ return false;
+
+ double fScale = 0;
+ pFont->GetScale(nullptr, &fScale);
+
+ double nOffset = -nUnderlineOffset * fScale;
+ double nSize = nUnderlineSize * fScale;
+ double nSize2 = nSize / 2.;
+ double nBSize = nSize * 2.;
+ double n2Size = nBSize / 3.;
+
+ mnUnderlineSize = std::ceil(nSize);
+ mnUnderlineOffset = std::ceil(nOffset);
+
+ mnBUnderlineSize = std::ceil(nBSize);
+ mnBUnderlineOffset = std::ceil(nOffset - nSize2);
+
+ mnDUnderlineSize = std::ceil(n2Size);
+ mnDUnderlineOffset1 = mnBUnderlineOffset;
+ mnDUnderlineOffset2 = mnBUnderlineOffset + mnDUnderlineSize * 2;
+
+ mnWUnderlineSize = mnBUnderlineSize;
+ mnWUnderlineOffset = std::ceil(nOffset + nSize);
+
+ nOffset = -nStrikeoutOffset * fScale;
+ nSize = nStrikeoutSize * fScale;
+ nSize2 = nSize / 2.;
+ nBSize = nSize * 2.;
+ n2Size = nBSize / 3.;
+
+ mnStrikeoutSize = std::ceil(nSize);
+ mnStrikeoutOffset = std::ceil(nOffset);
+
+ mnBStrikeoutSize = std::ceil(nBSize);
+ mnBStrikeoutOffset = std::round(nOffset - nSize2);
+
+ mnDStrikeoutSize = std::ceil(n2Size);
+ mnDStrikeoutOffset1 = mnBStrikeoutOffset;
+ mnDStrikeoutOffset2 = mnBStrikeoutOffset + mnDStrikeoutSize * 2;
+
+ return true;
+}
+
+void FontMetricData::ImplInitTextLineSize( const OutputDevice* pDev )
+{
+ mnBulletOffset = ( pDev->GetTextWidth( OUString( u' ' ) ) - pDev->GetTextWidth( OUString( u'\x00b7' ) ) ) >> 1 ;
+
+ if (ImplInitTextLineSizeHarfBuzz(const_cast<LogicalFontInstance*>(pDev->GetFontInstance())))
+ return;
+
+ 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;
+
+}
+
+
+void FontMetricData::ImplInitAboveTextLineSize( const OutputDevice* pDev )
+{
+ ImplInitTextLineSize(pDev);
+
+ 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 nCeiling = -mnAscent;
+
+ mnAboveUnderlineSize = mnUnderlineSize;
+ mnAboveUnderlineOffset = nCeiling + (nIntLeading - mnUnderlineSize + 1) / 2;
+
+ mnAboveBUnderlineSize = mnBUnderlineSize;
+ mnAboveBUnderlineOffset = nCeiling + (nIntLeading - mnBUnderlineSize + 1) / 2;
+
+ mnAboveDUnderlineSize = mnDUnderlineSize;
+ mnAboveDUnderlineOffset1 = nCeiling + (nIntLeading - 3*mnDUnderlineSize + 1) / 2;
+ mnAboveDUnderlineOffset2 = nCeiling + (nIntLeading + mnDUnderlineSize + 1) / 2;
+
+ mnAboveWUnderlineSize = mnWUnderlineSize;
+ mnAboveWUnderlineOffset = nCeiling + (nIntLeading + 1) / 2;
+}
+
+void FontMetricData::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"_ustr ); // 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 FontMetricData::ShouldUseWinMetrics(int nAscent, int nDescent, int nTypoAscent,
+ int nTypoDescent, int nWinAscent,
+ int nWinDescent) const
+{
+ if (utl::ConfigManager::IsFuzzing())
+ return false;
+
+ OUString aFontIdentifier(
+ GetFamilyName() + ","
+ + OUString::number(nAscent) + "," + OUString::number(nDescent) + ","
+ + OUString::number(nTypoAscent) + "," + OUString::number(nTypoDescent) + ","
+ + OUString::number(nWinAscent) + "," + OUString::number(nWinDescent));
+
+ 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;
+}
+
+// These are “private†HarfBuzz metrics tags, they are supported by not exposed
+// in the public header. They are safe to use, HarfBuzz just does not want to
+// advertise them.
+constexpr auto ASCENT_OS2 = static_cast<hb_ot_metrics_tag_t>(HB_TAG('O', 'a', 's', 'c'));
+constexpr auto DESCENT_OS2 = static_cast<hb_ot_metrics_tag_t>(HB_TAG('O', 'd', 's', 'c'));
+constexpr auto LINEGAP_OS2 = static_cast<hb_ot_metrics_tag_t>(HB_TAG('O', 'l', 'g', 'p'));
+constexpr auto ASCENT_HHEA = static_cast<hb_ot_metrics_tag_t>(HB_TAG('H', 'a', 's', 'c'));
+constexpr auto DESCENT_HHEA = static_cast<hb_ot_metrics_tag_t>(HB_TAG('H', 'd', 's', 'c'));
+constexpr auto LINEGAP_HHEA = static_cast<hb_ot_metrics_tag_t>(HB_TAG('H', 'l', 'g', 'p'));
+
+void FontMetricData::ImplCalcLineSpacing(LogicalFontInstance* pFontInstance)
+{
+ mnAscent = mnDescent = mnExtLeading = mnIntLeading = 0;
+ auto* pFace = pFontInstance->GetFontFace();
+ auto* pHbFont = pFontInstance->GetHbFont();
+
+ double fScale = 0;
+ pFontInstance->GetScale(nullptr, &fScale);
+ double fAscent = 0, fDescent = 0, fExtLeading = 0;
+
+ auto aFvar(pFace->GetRawFontData(HB_TAG('f', 'v', 'a', 'r')));
+ if (!aFvar.empty())
+ {
+ // This is a variable font, trust HarfBuzz to give us the right metrics
+ // and apply variations to them.
+ hb_position_t nAscent, nDescent, nLineGap;
+ if (hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER, &nAscent)
+ && hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER,
+ &nDescent)
+ && hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP,
+ &nLineGap))
+ {
+ fAscent = nAscent * fScale;
+ fDescent = -nDescent * fScale;
+ fExtLeading = nLineGap * fScale;
+ }
+ }
+ else
+ {
+ // This is not a variable font, we try to choose the best metrics
+ // ourselves for backward comparability:
+ //
+ // - 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.
+
+ // Try hhea table first.
+ hb_position_t nAscent = 0, nDescent = 0, nLineGap = 0;
+ if (hb_ot_metrics_get_position(pHbFont, ASCENT_HHEA, &nAscent)
+ && hb_ot_metrics_get_position(pHbFont, DESCENT_HHEA, &nDescent)
+ && hb_ot_metrics_get_position(pHbFont, LINEGAP_HHEA, &nLineGap))
+ {
+ // tdf#107605: Some fonts have weird values here, so check that
+ // ascender is +ve and descender is -ve as they normally should.
+ if (nAscent >= 0 && nDescent <= 0)
+ {
+ fAscent = nAscent * fScale;
+ fDescent = -nDescent * fScale;
+ fExtLeading = nLineGap * fScale;
+ }
+ }
+
+ // But if OS/2 is present, prefer it.
+ hb_position_t nTypoAscent, nTypoDescent, nTypoLineGap, nWinAscent, nWinDescent;
+ if (hb_ot_metrics_get_position(pHbFont, ASCENT_OS2, &nTypoAscent)
+ && hb_ot_metrics_get_position(pHbFont, DESCENT_OS2, &nTypoDescent)
+ && hb_ot_metrics_get_position(pHbFont, LINEGAP_OS2, &nTypoLineGap)
+ && hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_HORIZONTAL_CLIPPING_ASCENT,
+ &nWinAscent)
+ && hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_HORIZONTAL_CLIPPING_DESCENT,
+ &nWinDescent))
+ {
+ if ((fAscent == 0.0 && fDescent == 0.0)
+ || ShouldUseWinMetrics(nAscent, nDescent, nTypoAscent, nTypoDescent, nWinAscent,
+ nWinDescent))
+ {
+ fAscent = nWinAscent * fScale;
+ fDescent = nWinDescent * fScale;
+ fExtLeading = 0;
+ }
+
+ bool bUseTypoMetrics = false;
+ {
+ // TODO: Use HarfBuzz API instead of raw access
+ // https://github.com/harfbuzz/harfbuzz/issues/1920
+ sal_uInt16 fsSelection = 0;
+ auto aOS2(pFace->GetRawFontData(HB_TAG('O', 'S', '/', '2')));
+ SvMemoryStream aStream(const_cast<uint8_t*>(aOS2.data()), aOS2.size(),
+ StreamMode::READ);
+ // Font data are big endian.
+ aStream.SetEndian(SvStreamEndian::BIG);
+ if (aStream.Seek(vcl::OS2_fsSelection_offset) == vcl::OS2_fsSelection_offset)
+ aStream.ReadUInt16(fsSelection);
+ bUseTypoMetrics = fsSelection & (1 << 7);
+ }
+ if (bUseTypoMetrics && nTypoAscent >= 0 && nTypoDescent <= 0)
+ {
+ fAscent = nTypoAscent * fScale;
+ fDescent = -nTypoDescent * fScale;
+ fExtLeading = nTypoLineGap * fScale;
+ }
+ }
+ }
+
+ mnAscent = round(fAscent);
+ mnDescent = round(fDescent);
+ mnExtLeading = round(fExtLeading);
+
+ if (mnAscent || mnDescent)
+ mnIntLeading = mnAscent + mnDescent - mnHeight;
+}
+
+void FontMetricData::ImplInitBaselines(LogicalFontInstance *pFontInstance)
+{
+ hb_font_t* pHbFont = pFontInstance->GetHbFont();
+ double fScale = 0;
+ pFontInstance->GetScale(nullptr, &fScale);
+ 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 0000000000..2021a6a44d
--- /dev/null
+++ b/vcl/source/fontsubset/cff.cxx
@@ -0,0 +1,2625 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <comphelper/flagguard.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/sprintf.hxx>
+#include <rtl/math.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/string.hxx>
+#include <strhelper.hxx>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+
+typedef sal_uInt8 U8;
+typedef sal_uInt16 U16;
+typedef sal_Int64 S64;
+
+typedef double RealType;
+typedef RealType ValType;
+
+constexpr OString tok_notdef = ".notdef"_ostr;
+constexpr OString tok_space = "space"_ostr;
+constexpr OString tok_exclam = "exclam"_ostr;
+constexpr OString tok_quotedbl = "quotedbl"_ostr;
+constexpr OString tok_numbersign = "numbersign"_ostr;
+constexpr OString tok_dollar = "dollar"_ostr;
+constexpr OString tok_percent = "percent"_ostr;
+constexpr OString tok_ampersand = "ampersand"_ostr;
+constexpr OString tok_quoteright = "quoteright"_ostr;
+constexpr OString tok_parenleft = "parenleft"_ostr;
+constexpr OString tok_parenright = "parenright"_ostr;
+constexpr OString tok_asterisk = "asterisk"_ostr;
+constexpr OString tok_plus = "plus"_ostr;
+constexpr OString tok_comma = "comma"_ostr;
+constexpr OString tok_hyphen = "hyphen"_ostr;
+constexpr OString tok_period = "period"_ostr;
+constexpr OString tok_slash = "slash"_ostr;
+constexpr OString tok_zero = "zero"_ostr;
+constexpr OString tok_one = "one"_ostr;
+constexpr OString tok_two = "two"_ostr;
+constexpr OString tok_three = "three"_ostr;
+constexpr OString tok_four = "four"_ostr;
+constexpr OString tok_five = "five"_ostr;
+constexpr OString tok_six = "six"_ostr;
+constexpr OString tok_seven = "seven"_ostr;
+constexpr OString tok_eight = "eight"_ostr;
+constexpr OString tok_nine = "nine"_ostr;
+constexpr OString tok_colon = "colon"_ostr;
+constexpr OString tok_semicolon = "semicolon"_ostr;
+constexpr OString tok_less = "less"_ostr;
+constexpr OString tok_equal = "equal"_ostr;
+constexpr OString tok_greater = "greater"_ostr;
+constexpr OString tok_question = "question"_ostr;
+constexpr OString tok_at = "at"_ostr;
+constexpr OString tok_A = "A"_ostr;
+constexpr OString tok_B = "B"_ostr;
+constexpr OString tok_C = "C"_ostr;
+constexpr OString tok_D = "D"_ostr;
+constexpr OString tok_E = "E"_ostr;
+constexpr OString tok_F = "F"_ostr;
+constexpr OString tok_G = "G"_ostr;
+constexpr OString tok_H = "H"_ostr;
+constexpr OString tok_I = "I"_ostr;
+constexpr OString tok_J = "J"_ostr;
+constexpr OString tok_K = "K"_ostr;
+constexpr OString tok_L = "L"_ostr;
+constexpr OString tok_M = "M"_ostr;
+constexpr OString tok_N = "N"_ostr;
+constexpr OString tok_O = "O"_ostr;
+constexpr OString tok_P = "P"_ostr;
+constexpr OString tok_Q = "Q"_ostr;
+constexpr OString tok_R = "R"_ostr;
+constexpr OString tok_S = "S"_ostr;
+constexpr OString tok_T = "T"_ostr;
+constexpr OString tok_U = "U"_ostr;
+constexpr OString tok_V = "V"_ostr;
+constexpr OString tok_W = "W"_ostr;
+constexpr OString tok_X = "X"_ostr;
+constexpr OString tok_Y = "Y"_ostr;
+constexpr OString tok_Z = "Z"_ostr;
+constexpr OString tok_bracketleft = "bracketleft"_ostr;
+constexpr OString tok_backslash = "backslash"_ostr;
+constexpr OString tok_bracketright = "bracketright"_ostr;
+constexpr OString tok_asciicircum = "asciicircum"_ostr;
+constexpr OString tok_underscore = "underscore"_ostr;
+constexpr OString tok_quoteleft = "quoteleft"_ostr;
+constexpr OString tok_a = "a"_ostr;
+constexpr OString tok_b = "b"_ostr;
+constexpr OString tok_c = "c"_ostr;
+constexpr OString tok_d = "d"_ostr;
+constexpr OString tok_e = "e"_ostr;
+constexpr OString tok_f = "f"_ostr;
+constexpr OString tok_g = "g"_ostr;
+constexpr OString tok_h = "h"_ostr;
+constexpr OString tok_i = "i"_ostr;
+constexpr OString tok_j = "j"_ostr;
+constexpr OString tok_k = "k"_ostr;
+constexpr OString tok_l = "l"_ostr;
+constexpr OString tok_m = "m"_ostr;
+constexpr OString tok_n = "n"_ostr;
+constexpr OString tok_o = "o"_ostr;
+constexpr OString tok_p = "p"_ostr;
+constexpr OString tok_q = "q"_ostr;
+constexpr OString tok_r = "r"_ostr;
+constexpr OString tok_s = "s"_ostr;
+constexpr OString tok_t = "t"_ostr;
+constexpr OString tok_u = "u"_ostr;
+constexpr OString tok_v = "v"_ostr;
+constexpr OString tok_w = "w"_ostr;
+constexpr OString tok_x = "x"_ostr;
+constexpr OString tok_y = "y"_ostr;
+constexpr OString tok_z = "z"_ostr;
+constexpr OString tok_braceleft = "braceleft"_ostr;
+constexpr OString tok_bar = "bar"_ostr;
+constexpr OString tok_braceright = "braceright"_ostr;
+constexpr OString tok_asciitilde = "asciitilde"_ostr;
+constexpr OString tok_exclamdown = "exclamdown"_ostr;
+constexpr OString tok_cent = "cent"_ostr;
+constexpr OString tok_sterlin = "sterlin"_ostr;
+constexpr OString tok_fraction = "fraction"_ostr;
+constexpr OString tok_yen = "yen"_ostr;
+constexpr OString tok_florin = "florin"_ostr;
+constexpr OString tok_section = "section"_ostr;
+constexpr OString tok_currency = "currency"_ostr;
+constexpr OString tok_quotesingle = "quotesingle"_ostr;
+constexpr OString tok_quotedblleft = "quotedblleft"_ostr;
+constexpr OString tok_guillemotleft = "guillemotleft"_ostr;
+constexpr OString tok_guilsinglleft = "guilsinglleft"_ostr;
+constexpr OString tok_guilsinglright = "guilsinglright"_ostr;
+constexpr OString tok_fi = "fi"_ostr;
+constexpr OString tok_fl = "fl"_ostr;
+constexpr OString tok_endash = "endash"_ostr;
+constexpr OString tok_dagger = "dagger"_ostr;
+constexpr OString tok_daggerdbl = "daggerdbl"_ostr;
+constexpr OString tok_periodcentered = "periodcentered"_ostr;
+constexpr OString tok_paragraph = "paragraph"_ostr;
+constexpr OString tok_bullet = "bullet"_ostr;
+constexpr OString tok_quotesinglbase = "quotesinglbase"_ostr;
+constexpr OString tok_quotedblbase = "quotedblbase"_ostr;
+constexpr OString tok_quotedblright = "quotedblright"_ostr;
+constexpr OString tok_guillemotright = "guillemotright"_ostr;
+constexpr OString tok_ellipsis = "ellipsis"_ostr;
+constexpr OString tok_perthousand = "perthousand"_ostr;
+constexpr OString tok_questiondown = "questiondown"_ostr;
+constexpr OString tok_grave = "grave"_ostr;
+constexpr OString tok_acute = "acute"_ostr;
+constexpr OString tok_circumflex = "circumflex"_ostr;
+constexpr OString tok_tilde = "tilde"_ostr;
+constexpr OString tok_macron = "macron"_ostr;
+constexpr OString tok_breve = "breve"_ostr;
+constexpr OString tok_dotaccent = "dotaccent"_ostr;
+constexpr OString tok_dieresis = "dieresis"_ostr;
+constexpr OString tok_ring = "ring"_ostr;
+constexpr OString tok_cedilla = "cedilla"_ostr;
+constexpr OString tok_hungarumlaut = "hungarumlaut"_ostr;
+constexpr OString tok_ogonek = "ogonek"_ostr;
+constexpr OString tok_caron = "caron"_ostr;
+constexpr OString tok_emdash = "emdash"_ostr;
+constexpr OString tok_AE = "AE"_ostr;
+constexpr OString tok_ordfeminine = "ordfeminine"_ostr;
+constexpr OString tok_Lslash = "Lslash"_ostr;
+constexpr OString tok_Oslash = "Oslash"_ostr;
+constexpr OString tok_OE = "OE"_ostr;
+constexpr OString tok_ordmasculine = "ordmasculine"_ostr;
+constexpr OString tok_ae = "ae"_ostr;
+constexpr OString tok_dotlessi = "dotlessi"_ostr;
+constexpr OString tok_lslash = "lslash"_ostr;
+constexpr OString tok_oslash = "oslash"_ostr;
+constexpr OString tok_oe = "oe"_ostr;
+constexpr OString tok_germandbls = "germandbls"_ostr;
+constexpr OString tok_onesuperior = "onesuperior"_ostr;
+constexpr OString tok_logicalnot = "logicalnot"_ostr;
+constexpr OString tok_mu = "mu"_ostr;
+constexpr OString tok_trademark = "trademark"_ostr;
+constexpr OString tok_Eth = "Eth"_ostr;
+constexpr OString tok_onehalf = "onehalf"_ostr;
+constexpr OString tok_plusminus = "plusminus"_ostr;
+constexpr OString tok_Thorn = "Thorn"_ostr;
+constexpr OString tok_onequarter = "onequarter"_ostr;
+constexpr OString tok_divide = "divide"_ostr;
+constexpr OString tok_brokenbar = "brokenbar"_ostr;
+constexpr OString tok_degree = "degree"_ostr;
+constexpr OString tok_thorn = "thorn"_ostr;
+constexpr OString tok_threequarters = "threequarters"_ostr;
+constexpr OString tok_twosuperior = "twosuperior"_ostr;
+constexpr OString tok_registered = "registered"_ostr;
+constexpr OString tok_minus = "minus"_ostr;
+constexpr OString tok_eth = "eth"_ostr;
+constexpr OString tok_multiply = "multiply"_ostr;
+constexpr OString tok_threesuperior = "threesuperior"_ostr;
+constexpr OString tok_copyright = "copyright"_ostr;
+constexpr OString tok_Aacute = "Aacute"_ostr;
+constexpr OString tok_Acircumflex = "Acircumflex"_ostr;
+constexpr OString tok_Adieresis = "Adieresis"_ostr;
+constexpr OString tok_Agrave = "Agrave"_ostr;
+constexpr OString tok_Aring = "Aring"_ostr;
+constexpr OString tok_Atilde = "Atilde"_ostr;
+constexpr OString tok_Ccedilla = "Ccedilla"_ostr;
+constexpr OString tok_Eacute = "Eacute"_ostr;
+constexpr OString tok_Ecircumflex = "Ecircumflex"_ostr;
+constexpr OString tok_Edieresis = "Edieresis"_ostr;
+constexpr OString tok_Egrave = "Egrave"_ostr;
+constexpr OString tok_Iacute = "Iacute"_ostr;
+constexpr OString tok_Icircumflex = "Icircumflex"_ostr;
+constexpr OString tok_Idieresis = "Idieresis"_ostr;
+constexpr OString tok_Igrave = "Igrave"_ostr;
+constexpr OString tok_Ntilde = "Ntilde"_ostr;
+constexpr OString tok_Oacute = "Oacute"_ostr;
+constexpr OString tok_Ocircumflex = "Ocircumflex"_ostr;
+constexpr OString tok_Odieresis = "Odieresis"_ostr;
+constexpr OString tok_Ograve = "Ograve"_ostr;
+constexpr OString tok_Otilde = "Otilde"_ostr;
+constexpr OString tok_Scaron = "Scaron"_ostr;
+constexpr OString tok_Uacute = "Uacute"_ostr;
+constexpr OString tok_Ucircumflex = "Ucircumflex"_ostr;
+constexpr OString tok_Udieresis = "Udieresis"_ostr;
+constexpr OString tok_Ugrave = "Ugrave"_ostr;
+constexpr OString tok_Yacute = "Yacute"_ostr;
+constexpr OString tok_Ydieresis = "Ydieresis"_ostr;
+constexpr OString tok_Zcaron = "Zcaron"_ostr;
+constexpr OString tok_aacute = "aacute"_ostr;
+constexpr OString tok_acircumflex = "acircumflex"_ostr;
+constexpr OString tok_adieresis = "adieresis"_ostr;
+constexpr OString tok_agrave = "agrave"_ostr;
+constexpr OString tok_aring = "aring"_ostr;
+constexpr OString tok_atilde = "atilde"_ostr;
+constexpr OString tok_ccedilla = "ccedilla"_ostr;
+constexpr OString tok_eacute = "eacute"_ostr;
+constexpr OString tok_ecircumflex = "ecircumflex"_ostr;
+constexpr OString tok_edieresis = "edieresis"_ostr;
+constexpr OString tok_egrave = "egrave"_ostr;
+constexpr OString tok_iacute = "iacute"_ostr;
+constexpr OString tok_icircumflex = "icircumflex"_ostr;
+constexpr OString tok_idieresis = "idieresis"_ostr;
+constexpr OString tok_igrave = "igrave"_ostr;
+constexpr OString tok_ntilde = "ntilde"_ostr;
+constexpr OString tok_oacute = "oacute"_ostr;
+constexpr OString tok_ocircumflex = "ocircumflex"_ostr;
+constexpr OString tok_odieresis = "odieresis"_ostr;
+constexpr OString tok_ograve = "ograve"_ostr;
+constexpr OString tok_otilde = "otilde"_ostr;
+constexpr OString tok_scaron = "scaron"_ostr;
+constexpr OString tok_uacute = "uacute"_ostr;
+constexpr OString tok_ucircumflex = "ucircumflex"_ostr;
+constexpr OString tok_udieresis = "udieresis"_ostr;
+constexpr OString tok_ugrave = "ugrave"_ostr;
+constexpr OString tok_yacute = "yacute"_ostr;
+constexpr OString tok_ydieresis = "ydieresis"_ostr;
+constexpr OString tok_zcaron = "zcaron"_ostr;
+constexpr OString tok_exclamsmall = "exclamsmall"_ostr;
+constexpr OString tok_Hungarumlautsmall = "Hungarumlautsmall"_ostr;
+constexpr OString tok_dollaroldstyle = "dollaroldstyle"_ostr;
+constexpr OString tok_dollarsuperior = "dollarsuperior"_ostr;
+constexpr OString tok_ampersandsmall = "ampersandsmall"_ostr;
+constexpr OString tok_Acutesmall = "Acutesmall"_ostr;
+constexpr OString tok_parenleftsuperior = "parenleftsuperior"_ostr;
+constexpr OString tok_parenrightsuperior = "parenrightsuperior"_ostr;
+constexpr OString tok_twodotenleader = "twodotenleader"_ostr;
+constexpr OString tok_onedotenleader = "onedotenleader"_ostr;
+constexpr OString tok_zerooldstyle = "zerooldstyle"_ostr;
+constexpr OString tok_oneoldstyle = "oneoldstyle"_ostr;
+constexpr OString tok_twooldstyle = "twooldstyle"_ostr;
+constexpr OString tok_threeoldstyle = "threeoldstyle"_ostr;
+constexpr OString tok_fouroldstyle = "fouroldstyle"_ostr;
+constexpr OString tok_fiveoldstyle = "fiveoldstyle"_ostr;
+constexpr OString tok_sixoldstyle = "sixoldstyle"_ostr;
+constexpr OString tok_sevenoldstyle = "sevenoldstyle"_ostr;
+constexpr OString tok_eightoldstyle = "eightoldstyle"_ostr;
+constexpr OString tok_nineoldstile = "nineoldstile"_ostr;
+constexpr OString tok_commasuperior = "commasuperior"_ostr;
+constexpr OString tok_threequartersemdash = "threequartersemdash"_ostr;
+constexpr OString tok_periodsuperior = "periodsuperior"_ostr;
+constexpr OString tok_questionsmall = "questionsmall"_ostr;
+constexpr OString tok_asuperior = "asuperior"_ostr;
+constexpr OString tok_bsuperior = "bsuperior"_ostr;
+constexpr OString tok_centsuperior = "centsuperior"_ostr;
+constexpr OString tok_dsuperior = "dsuperior"_ostr;
+constexpr OString tok_esuperior = "esuperior"_ostr;
+constexpr OString tok_isuperior = "isuperior"_ostr;
+constexpr OString tok_lsuperior = "lsuperior"_ostr;
+constexpr OString tok_msuperior = "msuperior"_ostr;
+constexpr OString tok_nsuperior = "nsuperior"_ostr;
+constexpr OString tok_osuperior = "osuperior"_ostr;
+constexpr OString tok_rsuperior = "rsuperior"_ostr;
+constexpr OString tok_ssuperior = "ssuperior"_ostr;
+constexpr OString tok_tsuperior = "tsuperior"_ostr;
+constexpr OString tok_ff = "ff"_ostr;
+constexpr OString tok_ffi = "ffi"_ostr;
+constexpr OString tok_ffl = "ffl"_ostr;
+constexpr OString tok_parenleftinferior = "parenleftinferior"_ostr;
+constexpr OString tok_parenrightinferior = "parenrightinferior"_ostr;
+constexpr OString tok_Circumflexsmall = "Circumflexsmall"_ostr;
+constexpr OString tok_hyphensuperior = "hyphensuperior"_ostr;
+constexpr OString tok_Gravesmall = "Gravesmall"_ostr;
+constexpr OString tok_Asmall = "Asmall"_ostr;
+constexpr OString tok_Bsmall = "Bsmall"_ostr;
+constexpr OString tok_Csmall = "Csmall"_ostr;
+constexpr OString tok_Dsmall = "Dsmall"_ostr;
+constexpr OString tok_Esmall = "Esmall"_ostr;
+constexpr OString tok_Fsmall = "Fsmall"_ostr;
+constexpr OString tok_Gsmall = "Gsmall"_ostr;
+constexpr OString tok_Hsmall = "Hsmall"_ostr;
+constexpr OString tok_Ismall = "Ismall"_ostr;
+constexpr OString tok_Jsmall = "Jsmall"_ostr;
+constexpr OString tok_Ksmall = "Ksmall"_ostr;
+constexpr OString tok_Lsmall = "Lsmall"_ostr;
+constexpr OString tok_Msmall = "Msmall"_ostr;
+constexpr OString tok_Nsmall = "Nsmall"_ostr;
+constexpr OString tok_Osmall = "Osmall"_ostr;
+constexpr OString tok_Psmall = "Psmall"_ostr;
+constexpr OString tok_Qsmall = "Qsmall"_ostr;
+constexpr OString tok_Rsmall = "Rsmall"_ostr;
+constexpr OString tok_Ssmall = "Ssmall"_ostr;
+constexpr OString tok_Tsmall = "Tsmall"_ostr;
+constexpr OString tok_Usmall = "Usmall"_ostr;
+constexpr OString tok_Vsmall = "Vsmall"_ostr;
+constexpr OString tok_Wsmall = "Wsmall"_ostr;
+constexpr OString tok_Xsmall = "Xsmall"_ostr;
+constexpr OString tok_Ysmall = "Ysmall"_ostr;
+constexpr OString tok_Zsmall = "Zsmall"_ostr;
+constexpr OString tok_colonmonetary = "colonmonetary"_ostr;
+constexpr OString tok_onefitted = "onefitted"_ostr;
+constexpr OString tok_rupia = "rupia"_ostr;
+constexpr OString tok_Tildesmall = "Tildesmall"_ostr;
+constexpr OString tok_exclamdownsmall = "exclamdownsmall"_ostr;
+constexpr OString tok_centoldstyle = "centoldstyle"_ostr;
+constexpr OString tok_Lslashsmall = "Lslashsmall"_ostr;
+constexpr OString tok_Scaronsmall = "Scaronsmall"_ostr;
+constexpr OString tok_Zcaronsmall = "Zcaronsmall"_ostr;
+constexpr OString tok_Dieresissmall = "Dieresissmall"_ostr;
+constexpr OString tok_Brevesmall = "Brevesmall"_ostr;
+constexpr OString tok_Caronsmall = "Caronsmall"_ostr;
+constexpr OString tok_Dotaccentsmall = "Dotaccentsmall"_ostr;
+constexpr OString tok_Macronsmall = "Macronsmall"_ostr;
+constexpr OString tok_figuredash = "figuredash"_ostr;
+constexpr OString tok_hypheninferior = "hypheninferior"_ostr;
+constexpr OString tok_Ogoneksmall = "Ogoneksmall"_ostr;
+constexpr OString tok_Ringsmall = "Ringsmall"_ostr;
+constexpr OString tok_Cedillasmall = "Cedillasmall"_ostr;
+constexpr OString tok_questiondownsmall = "questiondownsmall"_ostr;
+constexpr OString tok_oneeight = "oneeight"_ostr;
+constexpr OString tok_threeeights = "threeeights"_ostr;
+constexpr OString tok_fiveeights = "fiveeights"_ostr;
+constexpr OString tok_seveneights = "seveneights"_ostr;
+constexpr OString tok_onethird = "onethird"_ostr;
+constexpr OString tok_twothirds = "twothirds"_ostr;
+constexpr OString tok_zerosuperior = "zerosuperior"_ostr;
+constexpr OString tok_foursuperior = "foursuperior"_ostr;
+constexpr OString tok_fivesuperior = "fivesuperior"_ostr;
+constexpr OString tok_sixsuperior = "sixsuperior"_ostr;
+constexpr OString tok_sevensuperior = "sevensuperior"_ostr;
+constexpr OString tok_eightsuperior = "eightsuperior"_ostr;
+constexpr OString tok_ninesuperior = "ninesuperior"_ostr;
+constexpr OString tok_zeroinferior = "zeroinferior"_ostr;
+constexpr OString tok_oneinferior = "oneinferior"_ostr;
+constexpr OString tok_twoinferior = "twoinferior"_ostr;
+constexpr OString tok_threeinferior = "threeinferior"_ostr;
+constexpr OString tok_fourinferior = "fourinferior"_ostr;
+constexpr OString tok_fiveinferior = "fiveinferior"_ostr;
+constexpr OString tok_sixinferior = "sixinferior"_ostr;
+constexpr OString tok_seveninferior = "seveninferior"_ostr;
+constexpr OString tok_eightinferior = "eightinferior"_ostr;
+constexpr OString tok_nineinferior = "nineinferior"_ostr;
+constexpr OString tok_centinferior = "centinferior"_ostr;
+constexpr OString tok_dollarinferior = "dollarinferior"_ostr;
+constexpr OString tok_periodinferior = "periodinferior"_ostr;
+constexpr OString tok_commainferior = "commainferior"_ostr;
+constexpr OString tok_Agravesmall = "Agravesmall"_ostr;
+constexpr OString tok_Aacutesmall = "Aacutesmall"_ostr;
+constexpr OString tok_Acircumflexsmall = "Acircumflexsmall"_ostr;
+constexpr OString tok_Atildesmall = "Atildesmall"_ostr;
+constexpr OString tok_Adieresissmall = "Adieresissmall"_ostr;
+constexpr OString tok_Aringsmall = "Aringsmall"_ostr;
+constexpr OString tok_AEsmall = "AEsmall"_ostr;
+constexpr OString tok_Ccedillasmall = "Ccedillasmall"_ostr;
+constexpr OString tok_Egravesmall = "Egravesmall"_ostr;
+constexpr OString tok_Eacutesmall = "Eacutesmall"_ostr;
+constexpr OString tok_Ecircumflexsmall = "Ecircumflexsmall"_ostr;
+constexpr OString tok_Edieresissmall = "Edieresissmall"_ostr;
+constexpr OString tok_Igravesmall = "Igravesmall"_ostr;
+constexpr OString tok_Iacutesmall = "Iacutesmall"_ostr;
+constexpr OString tok_Icircumflexsmall = "Icircumflexsmall"_ostr;
+constexpr OString tok_Idieresissmall = "Idieresissmall"_ostr;
+constexpr OString tok_Ethsmall = "Ethsmall"_ostr;
+constexpr OString tok_Ntildesmall = "Ntildesmall"_ostr;
+constexpr OString tok_Ogravesmall = "Ogravesmall"_ostr;
+constexpr OString tok_Oacutesmall = "Oacutesmall"_ostr;
+constexpr OString tok_Ocircumflexsmall = "Ocircumflexsmall"_ostr;
+constexpr OString tok_Otildesmall = "Otildesmall"_ostr;
+constexpr OString tok_Odieressissmall = "Odieressissmall"_ostr;
+constexpr OString tok_OEsmall = "OEsmall"_ostr;
+constexpr OString tok_Oslashsmall = "Oslashsmall"_ostr;
+constexpr OString tok_Ugravesmall = "Ugravesmall"_ostr;
+constexpr OString tok_Uacutesmall = "Uacutesmall"_ostr;
+constexpr OString tok_Ucircumflexsmall = "Ucircumflexsmall"_ostr;
+constexpr OString tok_Udieresissmall = "Udieresissmall"_ostr;
+constexpr OString tok_Yacutesmall = "Yacutesmall"_ostr;
+constexpr OString tok_Thornsmall = "Thornsmall"_ostr;
+constexpr OString tok_Ydieresissmall = "Ydieresissmall"_ostr;
+constexpr OString tok_001_000 = "001.000"_ostr;
+constexpr OString tok_001_001 = "001.001"_ostr;
+constexpr OString tok_001_002 = "001.002"_ostr;
+constexpr OString tok_001_003 = "001.003"_ostr;
+constexpr OString tok_Black = "Black"_ostr;
+constexpr OString tok_Bold = "Bold"_ostr;
+constexpr OString tok_Book = "Book"_ostr;
+constexpr OString tok_Light = "Light"_ostr;
+constexpr OString tok_Medium = "Medium"_ostr;
+constexpr OString tok_Regular = "Regular"_ostr;
+constexpr OString tok_Roman = "Roman"_ostr;
+constexpr OString tok_Semibold = "Semibold"_ostr;
+
+constexpr OString pStringIds[] = {
+/*0*/ tok_notdef, tok_space, tok_exclam, tok_quotedbl,
+ tok_numbersign, tok_dollar, tok_percent, tok_ampersand,
+ tok_quoteright, tok_parenleft, tok_parenright, tok_asterisk,
+ tok_plus, tok_comma, tok_hyphen, tok_period,
+/*16*/ tok_slash, tok_zero, tok_one, tok_two,
+ tok_three, tok_four, tok_five, tok_six,
+ tok_seven, tok_eight, tok_nine, tok_colon,
+ tok_semicolon, tok_less, tok_equal, tok_greater,
+/*32*/ tok_question, tok_at, tok_A, tok_B,
+ tok_C, tok_D, tok_E, tok_F,
+ tok_G, tok_H, tok_I, tok_J,
+ tok_K, tok_L, tok_M, tok_N,
+/*48*/ tok_O, tok_P, tok_Q, tok_R,
+ tok_S, tok_T, tok_U, tok_V,
+ tok_W, tok_X, tok_Y, tok_Z,
+ tok_bracketleft, tok_backslash, tok_bracketright, tok_asciicircum,
+/*64*/ tok_underscore, tok_quoteleft, tok_a, tok_b,
+ tok_c, tok_d, tok_e, tok_f,
+ tok_g, tok_h, tok_i, tok_j,
+ tok_k, tok_l, tok_m, tok_n,
+/*80*/ tok_o, tok_p, tok_q, tok_r,
+ tok_s, tok_t, tok_u, tok_v,
+ tok_w, tok_x, tok_y, tok_z,
+ tok_braceleft, tok_bar, tok_braceright, tok_asciitilde,
+/*96*/ tok_exclamdown, tok_cent, tok_sterlin, tok_fraction,
+ tok_yen, tok_florin, tok_section, tok_currency,
+ tok_quotesingle, tok_quotedblleft, tok_guillemotleft, tok_guilsinglleft,
+ tok_guilsinglright, tok_fi, tok_fl, tok_endash,
+/*112*/ tok_dagger, tok_daggerdbl, tok_periodcentered, tok_paragraph,
+ tok_bullet, tok_quotesinglbase, tok_quotedblbase, tok_quotedblright,
+ tok_guillemotright, tok_ellipsis, tok_perthousand, tok_questiondown,
+ tok_grave, tok_acute, tok_circumflex, tok_tilde,
+/*128*/ tok_macron, tok_breve, tok_dotaccent, tok_dieresis,
+ tok_ring, tok_cedilla, tok_hungarumlaut, tok_ogonek,
+ tok_caron, tok_emdash, tok_AE, tok_ordfeminine,
+ tok_Lslash, tok_Oslash, tok_OE, tok_ordmasculine,
+/*144*/ tok_ae, tok_dotlessi, tok_lslash, tok_oslash,
+ tok_oe, tok_germandbls, tok_onesuperior, tok_logicalnot,
+ tok_mu, tok_trademark, tok_Eth, tok_onehalf,
+ tok_plusminus, tok_Thorn, tok_onequarter, tok_divide,
+/*160*/ tok_brokenbar, tok_degree, tok_thorn, tok_threequarters,
+ tok_twosuperior, tok_registered, tok_minus, tok_eth,
+ tok_multiply, tok_threesuperior, tok_copyright, tok_Aacute,
+ tok_Acircumflex, tok_Adieresis, tok_Agrave, tok_Aring,
+/*176*/ tok_Atilde, tok_Ccedilla, tok_Eacute, tok_Ecircumflex,
+ tok_Edieresis, tok_Egrave, tok_Iacute, tok_Icircumflex,
+ tok_Idieresis, tok_Igrave, tok_Ntilde, tok_Oacute,
+ tok_Ocircumflex, tok_Odieresis, tok_Ograve, tok_Otilde,
+/*192*/ tok_Scaron, tok_Uacute, tok_Ucircumflex, tok_Udieresis,
+ tok_Ugrave, tok_Yacute, tok_Ydieresis, tok_Zcaron,
+ tok_aacute, tok_acircumflex, tok_adieresis, tok_agrave,
+ tok_aring, tok_atilde, tok_ccedilla, tok_eacute,
+/*208*/ tok_ecircumflex, tok_edieresis, tok_egrave, tok_iacute,
+ tok_icircumflex, tok_idieresis, tok_igrave, tok_ntilde,
+ tok_oacute, tok_ocircumflex, tok_odieresis, tok_ograve,
+ tok_otilde, tok_scaron, tok_uacute, tok_ucircumflex,
+/*224*/ tok_udieresis, tok_ugrave, tok_yacute, tok_ydieresis,
+ tok_zcaron, tok_exclamsmall, tok_Hungarumlautsmall,tok_dollaroldstyle,
+ tok_dollarsuperior, tok_ampersandsmall, tok_Acutesmall, tok_parenleftsuperior,
+ tok_parenrightsuperior,tok_twodotenleader, tok_onedotenleader, tok_zerooldstyle,
+/*240*/ tok_oneoldstyle, tok_twooldstyle, tok_threeoldstyle, tok_fouroldstyle,
+ tok_fiveoldstyle, tok_sixoldstyle, tok_sevenoldstyle, tok_eightoldstyle,
+ tok_nineoldstile, tok_commasuperior, tok_threequartersemdash,tok_periodsuperior,
+ tok_questionsmall, tok_asuperior, tok_bsuperior, tok_centsuperior,
+/*256*/ tok_dsuperior, tok_esuperior, tok_isuperior, tok_lsuperior,
+ tok_msuperior, tok_nsuperior, tok_osuperior, tok_rsuperior,
+ tok_ssuperior, tok_tsuperior, tok_ff, tok_ffi,
+ tok_ffl, tok_parenleftinferior,tok_parenrightinferior,tok_Circumflexsmall,
+/*272*/ tok_hyphensuperior,tok_Gravesmall, tok_Asmall, tok_Bsmall,
+ tok_Csmall, tok_Dsmall, tok_Esmall, tok_Fsmall,
+ tok_Gsmall, tok_Hsmall, tok_Ismall, tok_Jsmall,
+ tok_Ksmall, tok_Lsmall, tok_Msmall, tok_Nsmall,
+/*288*/ tok_Osmall, tok_Psmall, tok_Qsmall, tok_Rsmall,
+ tok_Ssmall, tok_Tsmall, tok_Usmall, tok_Vsmall,
+ tok_Wsmall, tok_Xsmall, tok_Ysmall, tok_Zsmall,
+ tok_colonmonetary, tok_onefitted, tok_rupia, tok_Tildesmall,
+/*304*/ tok_exclamdownsmall,tok_centoldstyle, tok_Lslashsmall, tok_Scaronsmall,
+ tok_Zcaronsmall, tok_Dieresissmall, tok_Brevesmall, tok_Caronsmall,
+ tok_Dotaccentsmall, tok_Macronsmall, tok_figuredash, tok_hypheninferior,
+ tok_Ogoneksmall, tok_Ringsmall, tok_Cedillasmall, tok_questiondownsmall,
+/*320*/ tok_oneeight, tok_threeeights, tok_fiveeights, tok_seveneights,
+ tok_onethird, tok_twothirds, tok_zerosuperior, tok_foursuperior,
+ tok_fivesuperior, tok_sixsuperior, tok_sevensuperior, tok_eightsuperior,
+ tok_ninesuperior, tok_zeroinferior, tok_oneinferior, tok_twoinferior,
+/*336*/ tok_threeinferior,tok_fourinferior, tok_fiveinferior, tok_sixinferior,
+ tok_seveninferior, tok_eightinferior, tok_nineinferior, tok_centinferior,
+ tok_dollarinferior, tok_periodinferior, tok_commainferior, tok_Agravesmall,
+ tok_Aacutesmall, tok_Acircumflexsmall, tok_Atildesmall, tok_Adieresissmall,
+/*352*/ tok_Aringsmall, tok_AEsmall, tok_Ccedillasmall, tok_Egravesmall,
+ tok_Eacutesmall, tok_Ecircumflexsmall, tok_Edieresissmall, tok_Igravesmall,
+ tok_Iacutesmall, tok_Icircumflexsmall, tok_Idieresissmall, tok_Ethsmall,
+ tok_Ntildesmall, tok_Ogravesmall, tok_Oacutesmall, tok_Ocircumflexsmall,
+/*368*/ tok_Otildesmall, tok_Odieressissmall, tok_OEsmall, tok_Oslashsmall,
+ tok_Ugravesmall, tok_Uacutesmall, tok_Ucircumflexsmall, tok_Udieresissmall,
+ tok_Yacutesmall, tok_Thornsmall, tok_Ydieresissmall, tok_001_000,
+ tok_001_001, tok_001_002, tok_001_003, tok_Black,
+/*384*/ tok_Bold, tok_Book, tok_Light, tok_Medium,
+ tok_Regular, tok_Roman, tok_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;
+};
+
+
+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,
+ 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();
+ OString getString( int nStringID);
+ int getFDSelect( int nGlyphIndex) const;
+ int getGlyphSID( int nGlyphIndex) const;
+ OString 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
+OString 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
+ comphelper::ValueRestorationGuard pReadPtr(mpReadPtr);
+ comphelper::ValueRestorationGuard pReadEnd(mpReadEnd);
+ nStringID -= nStdStrings;
+ int nLen = seekIndexData( mnStringIdxBase, nStringID);
+ // assert( nLen >= 0);
+ // TODO: just return the undecorated name
+ if( nLen < 0) {
+ return "name[" + OString::number(nStringID) + "].notfound!";
+ } else {
+ const int nMaxLen = 2560 - 1;
+ if( nLen >= nMaxLen)
+ nLen = nMaxLen; // TODO: still needed?
+ return OString(reinterpret_cast<char const *>(mpReadPtr), nLen);
+ }
+}
+
+// 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
+OString CffSubsetterContext::getGlyphName( int nGlyphIndex)
+{
+ // the first glyph is always the .notdef glyph
+ if( nGlyphIndex == 0)
+ return tok_notdef;
+
+ // get the glyph specific name
+ const int nSID = getGlyphSID( nGlyphIndex);
+ if( nSID < 0) // default glyph name
+ {
+ char aDefaultGlyphName[64];
+ o3tl::sprintf( aDefaultGlyphName, "gly%03d", nGlyphIndex);
+ return aDefaultGlyphName;
+ }
+ else if( mbCIDFont) // default glyph name in CIDs
+ {
+ char aDefaultGlyphName[64];
+ o3tl::sprintf( aDefaultGlyphName, "cid%03d", nSID);
+ return aDefaultGlyphName;
+ }
+ else { // glyph name from string table
+ auto const pSidName = getString( nSID);
+ // check validity of glyph name
+ const char* p = pSidName.getStr();
+ while( (*p >= '0') && (*p <= 'z')) ++p;
+ if( (p >= pSidName.getStr()+1) && (*p == '\0'))
+ return pSidName;
+ // if needed invent a fallback name
+ char aDefaultGlyphName[64];
+ o3tl::sprintf( aDefaultGlyphName, "bad%03d", nSID);
+ return aDefaultGlyphName;
+ }
+}
+
+bool CffSubsetterContext::getBaseAccent(ValType aBase, ValType aAccent, int* nBase, int* nAccent)
+{
+ bool bBase = false, bAccent = false;
+ for (int i = 0; i < mnCharStrCount; i++)
+ {
+ OString 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( SvStream* 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:
+ SvStream* mpFileOut;
+ unsigned mnEECryptR;
+public:
+ OStringBuffer maBuffer;
+
+ char maSubsetName[256];
+ bool mbPfbSubset;
+ int mnHexLineCol;
+};
+
+}
+
+Type1Emitter::Type1Emitter( SvStream* pOutFile, bool bPfbSubset)
+: mpFileOut( pOutFile)
+, mnEECryptR( 55665) // default eexec seed, TODO: mnEECryptSeed
+, 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 = mpFileOut->Tell();
+ 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 = mpFileOut->Tell();
+ if (nCurrPos < 0)
+ return;
+ if (mpFileOut->Seek(nTellPos) != static_cast<sal_uInt64>(nTellPos))
+ return;
+ mpFileOut->WriteBytes(cData, sizeof(cData));
+ mpFileOut->Seek(nCurrPos);
+}
+
+inline size_t Type1Emitter::emitRawData(const char* pData, size_t nLength) const
+{
+ return mpFileOut->WriteBytes( pData, nLength );
+}
+
+inline void Type1Emitter::emitAllRaw()
+{
+ // writeout raw data
+ emitRawData( maBuffer.getStr(), maBuffer.getLength());
+ // reset the raw buffer
+ maBuffer.setLength(0);
+}
+
+inline void Type1Emitter::emitAllHex()
+{
+ auto const end = maBuffer.getStr() + maBuffer.getLength();
+ for( const char* p = maBuffer.getStr(); p < end;) {
+ // convert binary chunk to hex
+ char aHexBuf[0x4000];
+ char* pOut = aHexBuf;
+ while( (p < end) && (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
+ maBuffer.setLength(0);
+}
+
+void Type1Emitter::emitAllCrypted()
+{
+ // apply t1crypt
+ for( sal_Int32 i = 0; i < maBuffer.getLength(); ++i) {
+ maBuffer[i] ^= (mnEECryptR >> 8);
+ mnEECryptR = (static_cast<U8>(maBuffer[i]) + mnEECryptR) * 52845 + 22719;
+ }
+
+ // emit the t1crypt result
+ if( mbPfbSubset)
+ emitAllRaw();
+ else
+ emitAllHex();
+}
+
+// #i110387# quick-and-dirty double->ascii conversion
+// also strip off trailing zeros in fraction while we are at it
+static OString dbl2str( double fVal)
+{
+ return rtl::math::doubleToString(fVal, rtl_math_StringFormat_G, 6, '.', true);
+}
+
+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
+ maBuffer.append( 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;
+ maBuffer.append(dbl2str( aVal));
+ maBuffer.append(' ');
+ }
+ // emit the last value
+ maBuffer.append(dbl2str( aVal));
+ // emit the line tail
+ maBuffer.append( 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;
+
+ rCharStrings.push_back(aCharString);
+ }
+}
+
+void CffSubsetterContext::emitAsType1( Type1Emitter& rEmitter,
+ const sal_GlyphId* pReqGlyphIds, const U8* pReqEncoding,
+ 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).getStr(), sizeof(rEmitter.maSubsetName) - 1);
+ pFontName[sizeof(rEmitter.maSubsetName) - 1] = 0;
+ } else if( mnFullNameSID) {
+ // approximate fontname as fullname-whitespace
+ auto const str = getString( mnFullNameSID);
+ const char* pI = str.getStr();
+ 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;
+
+ // create a PFB+Type1 header
+ if( rEmitter.mbPfbSubset ) {
+ static const char aPfbHeader[] = "\x80\x01\x00\x00\x00\x00";
+ rEmitter.emitRawData( aPfbHeader, sizeof(aPfbHeader)-1);
+ }
+
+ rEmitter.maBuffer.append(
+ "%!FontType1-1.0: " + OString::Concat(rEmitter.maSubsetName) + " 001.003\n");
+ // emit TOPDICT
+ rEmitter.maBuffer.append(
+ "11 dict begin\n" // TODO: dynamic entry count for TOPDICT
+ "/FontType 1 def\n"
+ "/PaintType 0 def\n");
+ rEmitter.maBuffer.append( "/FontName /" + OString::Concat(rEmitter.maSubsetName) + " def\n");
+ rEmitter.maBuffer.append( "/UniqueID " + OString::number(nUniqueId) + " def\n");
+ // emit FontMatrix
+ if( maFontMatrix.size() == 6)
+ rEmitter.emitValVector( "/FontMatrix [", "]readonly def\n", maFontMatrix);
+ else // emit default FontMatrix if needed
+ rEmitter.maBuffer.append( "/FontMatrix [0.001 0 0 0.001 0 0]readonly def\n");
+
+ // emit FontBBox
+ ValType fXFactor = 1.0;
+ ValType fYFactor = 1.0;
+ if( maFontMatrix.size() >= 4) {
+ fXFactor = 1000.0F * maFontMatrix[0];
+ fYFactor = 1000.0F * maFontMatrix[3];
+ }
+
+ auto aFontBBox = maFontBBox;
+ if (rFSInfo.m_bFilled)
+ aFontBBox = {
+ rFSInfo.m_aFontBBox.Left() / fXFactor, rFSInfo.m_aFontBBox.Top() / fYFactor,
+ rFSInfo.m_aFontBBox.Right() / fXFactor, (rFSInfo.m_aFontBBox.Bottom() + 1) / fYFactor
+ };
+ else 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
+ rEmitter.maBuffer.append(
+ "/FontInfo 2 dict dup begin\n" // TODO: check fontinfo entry count
+ " /FullName (" + OString::Concat(pFullName) + ") readonly def\n"
+ " /FamilyName (" + pFamilyName + ") readonly def\n"
+ "end readonly def\n");
+
+ rEmitter.maBuffer.append(
+ "/Encoding 256 array\n"
+ "0 1 255 {1 index exch /.notdef put} for\n");
+ for( int i = 1; (i < nGlyphCount) && (i < 256); ++i) {
+ OString pGlyphName = getGlyphName( pReqGlyphIds[i]);
+ rEmitter.maBuffer.append(
+ "dup " + OString::number(pReqEncoding[i]) + " /" + pGlyphName + " put\n");
+ }
+ rEmitter.maBuffer.append( "readonly def\n");
+ rEmitter.maBuffer.append(
+ // 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
+ rEmitter.maBuffer.append(
+ "\110\104\125 "
+ "dup\n/Private " + OString::number(nPrivEntryCount) + " 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?
+
+ // emit blue hint related privdict entries
+ if( !mpCffLocal->maBlueValues.empty())
+ rEmitter.emitValVector( "/BlueValues [", "]ND\n", mpCffLocal->maBlueValues);
+ else
+ rEmitter.maBuffer.append( "/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) {
+ rEmitter.maBuffer.append( "/BlueScale ");
+ rEmitter.maBuffer.append(dbl2str( mpCffLocal->mfBlueScale));
+ rEmitter.maBuffer.append( " def\n");
+ }
+ if( mpCffLocal->mfBlueShift) { // default BlueShift==7
+ rEmitter.maBuffer.append( "/BlueShift ");
+ rEmitter.maBuffer.append(dbl2str( mpCffLocal->mfBlueShift));
+ rEmitter.maBuffer.append( " def\n");
+ }
+ if( mpCffLocal->mfBlueFuzz) { // default BlueFuzz==1
+ rEmitter.maBuffer.append( "/BlueFuzz ");
+ rEmitter.maBuffer.append(dbl2str( mpCffLocal->mfBlueFuzz));
+ rEmitter.maBuffer.append( " def\n");
+ }
+
+ // emit stem hint related privdict entries
+ if( mpCffLocal->maStemStdHW) {
+ rEmitter.maBuffer.append( "/StdHW [");
+ rEmitter.maBuffer.append(dbl2str( mpCffLocal->maStemStdHW));
+ rEmitter.maBuffer.append( "] def\n");
+ }
+ if( mpCffLocal->maStemStdVW) {
+ rEmitter.maBuffer.append( "/StdVW [");
+ rEmitter.maBuffer.append(dbl2str( mpCffLocal->maStemStdVW));
+ rEmitter.maBuffer.append( "] def\n");
+ }
+ rEmitter.emitValVector( "/StemSnapH [", "]ND\n", mpCffLocal->maStemSnapH);
+ rEmitter.emitValVector( "/StemSnapV [", "]ND\n", mpCffLocal->maStemSnapV);
+
+ // emit other hints
+ if( mpCffLocal->mbForceBold)
+ rEmitter.maBuffer.append( "/ForceBold true def\n");
+ if( mpCffLocal->mnLangGroup != 0)
+ rEmitter.maBuffer.append(
+ "/LanguageGroup " + OString::number(mpCffLocal->mnLangGroup) + " def\n");
+ if( mpCffLocal->mnLangGroup == 1) // compatibility with ancient printers
+ rEmitter.maBuffer.append( "/RndStemUp false def\n");
+ if( mpCffLocal->mfExpFactor) {
+ rEmitter.maBuffer.append( "/ExpansionFactor ");
+ rEmitter.maBuffer.append(dbl2str( mpCffLocal->mfExpFactor));
+ rEmitter.maBuffer.append( " def\n");
+ }
+
+ // emit remaining privdict entries
+ rEmitter.maBuffer.append( "/UniqueID " + OString::number(nUniqueId) + " def\n");
+ // TODO?: more privdict entries?
+
+ rEmitter.maBuffer.append(
+ "/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");
+
+ // emit used GlobalSubr charstrings
+ // these are the just the default subrs
+ // TODO: do we need them as the flex hints are resolved differently?
+ rEmitter.maBuffer.append(
+ "/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");
+
+ // 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);
+ }
+ rEmitter.maBuffer.append(
+ "2 index /CharStrings " + OString::number(aCharStrings.size()) + " dict dup begin\n");
+ rEmitter.emitAllCrypted();
+ for (const auto& rCharString : aCharStrings)
+ {
+ // get the glyph name
+ OString pGlyphName = getGlyphName(rCharString.nCffGlyphId);
+ // emit the encrypted Type1op charstring
+ rEmitter.maBuffer.append(
+ "/" + pGlyphName + " " + OString::number(rCharString.nLen) + " RD ");
+ rEmitter.maBuffer.append(
+ reinterpret_cast<char const *>(rCharString.aOps), rCharString.nLen);
+ rEmitter.maBuffer.append( " ND\n");
+ rEmitter.emitAllCrypted();
+ // provide individual glyphwidths if requested
+ }
+ rEmitter.maBuffer.append( "end end\nreadonly put\nput\n");
+ rEmitter.maBuffer.append( "dup/FontName get exch definefont pop\n");
+ rEmitter.maBuffer.append( "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
+
+ rFSInfo.m_nFontType = rEmitter.mbPfbSubset ? FontType::TYPE1_PFB : FontType::TYPE1_PFA;
+
+ if (rFSInfo.m_bFilled)
+ return;
+
+ 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_aPSName = OUString( rEmitter.maSubsetName, strlen(rEmitter.maSubsetName), RTL_TEXTENCODING_UTF8 );
+}
+
+bool FontSubsetInfo::CreateFontSubsetFromCff()
+{
+ 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,
+ 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 0000000000..2a45a1194e
--- /dev/null
+++ b/vcl/source/fontsubset/fontsubset.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 <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)
+ , m_bFilled(false)
+ , mpInFontBytes( nullptr)
+ , mnInByteLength( 0)
+ , meInFontType( FontType::NO_FONT)
+ , 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)
+{
+ meInFontType = eInFontType;
+ mpInFontBytes = pInFontBytes;
+ mnInByteLength = nInByteLength;
+}
+
+bool FontSubsetInfo::CreateFontSubset(
+ FontType nReqFontTypeMask,
+ SvStream* pOutFile, const char* pReqFontName,
+ const sal_GlyphId* pReqGlyphIds, const sal_uInt8* pReqEncodedIds, int nReqGlyphCount)
+{
+ // prepare request details needed by all underlying subsetters
+ mnReqFontTypeMask = nReqFontTypeMask;
+ mpOutFile = pOutFile;
+ mpReqFontName = pReqFontName;
+ mpReqGlyphIds = pReqGlyphIds;
+ mpReqEncodedIds = pReqEncodedIds;
+ mnReqGlyphCount = nReqGlyphCount;
+
+ OString aPSName = m_aPSName.toUtf8();
+ if (!mpReqFontName)
+ mpReqFontName = aPSName.getStr();
+
+ // 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::CFF_FONT:
+ bOK = CreateFontSubsetFromCff();
+ break;
+ default:
+ OSL_FAIL( "unhandled type in CreateFontSubset()");
+ break;
+ }
+
+ return bOK;
+}
+
+/* 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 0000000000..1c8c2f6b5f
--- /dev/null
+++ b/vcl/source/fontsubset/sft.cxx
@@ -0,0 +1,1847 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/endian.h>
+#include <osl/thread.h>
+#include <unotools/tempfile.hxx>
+#include <fontsubset.hxx>
+#include <algorithm>
+#include <memory>
+
+namespace vcl
+{
+
+namespace {
+
+/*- 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) */
+};
+
+}
+
+/*- 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;
+}
+
+/* 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 , std::vector<ControlPoint>&, TTGlyphMetrics *, std::vector< sal_uInt32 >* );
+
+/* returns the number of control points, allocates the pointArray */
+static int GetSimpleTTOutline(AbstractTrueTypeFont const *ttf, sal_uInt32 glyphID,
+ std::vector<ControlPoint>& pointArray, TTGlyphMetrics *metrics)
+{
+ sal_uInt32 nTableSize;
+ const sal_uInt8* table = ttf->table(O_glyf, nTableSize);
+ sal_uInt8 n;
+ int i, j, z;
+
+ pointArray.clear();
+
+ 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;
+ }
+
+ std::vector<ControlPoint> pa(palen);
+
+ 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 */
+ 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 = std::move(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, std::vector<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;
+ std::vector<ControlPoint> nextComponent;
+ int i, np;
+ F16Dot16 a = 0x10000, b = 0, c = 0, d = 0x10000, m, n, abs1, abs2, abs3;
+
+ pointArray.clear();
+
+ 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");
+ 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");
+ 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");
+ 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");
+ 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");
+ 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 );
+ }
+ }
+
+ if (myPoints.size() > SAL_MAX_UINT16) {
+ SAL_WARN("vcl.fonts", "number of points has to be limited to max value GlyphData::npoints can contain, abandon effort");
+ myPoints.clear();
+ break;
+ }
+
+ } 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();
+
+ pointArray = std::move(myPoints);
+
+ 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, std::vector<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.clear();
+
+ 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;
+}
+
+/*- Extracts a string from the name table and allocates memory for it -*/
+
+static OString nameExtract( const sal_uInt8* name, int nTableSize, int n, int dbFlag, OUString* ucs2result )
+{
+ OStringBuffer 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->clear();
+ return OString();
+ }
+
+ if( ucs2result )
+ ucs2result->clear();
+ if (dbFlag) {
+ res.setLength(len/2);
+ for (int i = 0; i < len/2; i++)
+ {
+ res[i] = *(ptr + i * 2 + 1);
+ SAL_WARN_IF(res[i] == 0, "vcl.fonts", "font name is bogus");
+ }
+ if( ucs2result )
+ {
+ OUStringBuffer buf(len/2);
+ buf.setLength(len/2);
+ for (int i = 0; i < len/2; i++ )
+ {
+ buf[i] = GetUInt16( ptr, 2*i );
+ SAL_WARN_IF(buf[i] == 0, "vcl.fonts", "font name is bogus");
+ }
+ *ucs2result = buf.makeStringAndClear();
+ }
+ } else {
+ res.setLength(len);
+ memcpy(static_cast<void*>(const_cast<char*>(res.getStr())), ptr, len);
+ }
+
+ return res.makeStringAndClear();
+}
+
+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(AbstractTrueTypeFont *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.clear();
+ if ((r = findname(table, n, 3, 1, 0x0409, 6)) != -1)
+ t->psname = nameExtract(table, nTableSize, r, 1, nullptr);
+ if ( t->psname.isEmpty() && (r = findname(table, n, 1, 0, 0, 6)) != -1)
+ t->psname = nameExtract(table, nTableSize, r, 0, nullptr);
+ if ( t->psname.isEmpty() && (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.isEmpty() && (r = findname(table, n, 2, 2, 0, 6)) != -1)
+ {
+ t->psname = nameExtract(table, nTableSize, r, 0, nullptr);
+ }
+ if ( t->psname.isEmpty() )
+ {
+ if (!t->fileName().empty())
+ {
+ const char* pReverse = t->fileName().data() + t->fileName().length();
+ /* take only last token of filename */
+ while (pReverse != t->fileName().data() && *pReverse != '/') pReverse--;
+ if(*pReverse == '/') pReverse++;
+ int nReverseLen = strlen(pReverse);
+ for (i=nReverseLen - 1; i > 0; i--)
+ {
+ /*- Remove the suffix -*/
+ if (*(pReverse + i) == '.' ) {
+ nReverseLen = i;
+ break;
+ }
+ }
+ t->psname = OString(std::string_view(pReverse, nReverseLen));
+ }
+ else
+ t->psname = "Unknown"_ostr;
+ }
+
+ /* Font family and subfamily names: preferred Apple */
+ t->family.clear();
+ if ((r = findname(table, n, 0, 0, 0, 1)) != -1)
+ t->family = nameExtract(table, nTableSize, r, 1, &t->ufamily);
+ if ( t->family.isEmpty() && (r = findname(table, n, 3, 1, 0x0409, 1)) != -1)
+ t->family = nameExtract(table, nTableSize, r, 1, &t->ufamily);
+ if ( t->family.isEmpty() && (r = findname(table, n, 1, 0, 0, 1)) != -1)
+ t->family = nameExtract(table, nTableSize, r, 0, nullptr);
+ if ( t->family.isEmpty() && (r = findname(table, n, 3, 1, 0x0411, 1)) != -1)
+ t->family = nameExtract(table, nTableSize, r, 1, &t->ufamily);
+ if ( t->family.isEmpty() && (r = findname(table, n, 3, 0, 0x0409, 1)) != -1)
+ t->family = nameExtract(table, nTableSize, r, 1, &t->ufamily);
+ if ( t->family.isEmpty() )
+ t->family = t->psname;
+
+ t->subfamily.clear();
+ t->usubfamily.clear();
+ if ((r = findname(table, n, 1, 0, 0, 2)) != -1)
+ t->subfamily = nameExtract(table, nTableSize, r, 0, &t->usubfamily);
+ if ( t->subfamily.isEmpty() && (r = findname(table, n, 3, 1, 0x0409, 2)) != -1)
+ t->subfamily = nameExtract(table, nTableSize, r, 1, &t->usubfamily);
+
+ /* #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; i < t->psname.getLength() && 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.isEmpty() && t->family.isEmpty() )
+ return;
+
+ bool bReplace = true;
+
+ for( i = 0; i < t->ufamily.getLength() && bReplace; i++ )
+ if( t->ufamily[ i ] < 33 || t->ufamily[ i ] > 127 )
+ bReplace = false;
+ if( bReplace )
+ {
+ t->psname = t->family;
+ }
+}
+
+/*- Public functions */
+
+int CountTTCFonts(const char* fname)
+{
+ FILE* fd;
+#ifdef LINUX
+ int nFD;
+ int n;
+ if (sscanf(fname, "/:FD:/%d%n", &nFD, &n) == 1 && fname[n] == '\0')
+ {
+ lseek(nFD, 0, SEEK_SET);
+ int nDupFd = dup(nFD);
+ fd = nDupFd != -1 ? fdopen(nDupFd, "rb") : nullptr;
+ }
+ else
+#endif
+ fd = fopen(fname, "rb");
+
+ if (!fd)
+ return 0;
+
+ int nFonts = 0;
+ sal_uInt8 buffer[12];
+ if (fread(buffer, 1, 12, fd) == 12) {
+ if(GetUInt32(buffer, 0) == T_ttcf )
+ nFonts = GetUInt32(buffer, 8);
+ }
+
+ if (nFonts > 0)
+ {
+ fseek(fd, 0, SEEK_END);
+ sal_uInt64 fileSize = ftell(fd);
+
+ //Feel free to calc the exact max possible number of fonts a file
+ //could contain given its physical size. But this will clamp it to
+ //a sane starting point
+ //http://processingjs.nihongoresources.com/the_smallest_font/
+ //https://github.com/grzegorzrolek/null-ttf
+ const int nMaxFontsPossible = fileSize / 528;
+ if (nFonts > nMaxFontsPossible)
+ {
+ SAL_WARN("vcl.fonts", "font file " << fname <<" claims to have "
+ << nFonts << " fonts, but only "
+ << nMaxFontsPossible << " are possible");
+ nFonts = nMaxFontsPossible;
+ }
+ }
+
+ 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;
+ }
+
+ int nFD;
+ int n;
+ if (sscanf(fname, "/:FD:/%d%n", &nFD, &n) == 1 && fname[n] == '\0')
+ {
+ lseek(nFD, 0, SEEK_SET);
+ fd = dup(nFD);
+ }
+ else
+ 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)
+ , m_bMicrosoftSymbolEncoded(false)
+{
+ if (pFileName)
+ m_sFileName = pFileName;
+}
+
+AbstractTrueTypeFont::~AbstractTrueTypeFont()
+{
+}
+
+TrueTypeFont::TrueTypeFont(const char* pFileName, const FontCharMapRef xCharMap)
+ : AbstractTrueTypeFont(pFileName, xCharMap)
+ , fsize(-1)
+ , ptr(nullptr)
+ , ntables(0)
+{
+}
+
+TrueTypeFont::~TrueTypeFont()
+{
+#if !defined(_WIN32)
+ if (!fileName().empty())
+ munmap(ptr, fsize);
+#endif
+}
+
+void CloseTTFont(TrueTypeFont* ttf) { delete ttf; }
+
+SFErrCodes AbstractTrueTypeFont::initialize()
+{
+ SFErrCodes ret = indexGlyphData();
+ if (ret != SFErrCodes::Ok)
+ return ret;
+
+ GetNames(this);
+
+ return SFErrCodes::Ok;
+}
+
+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.
+ // TODO: We only need this for fonts with CBDT table since they usually
+ // lack glyf or CFF table, the check should be more specific, or better
+ // non-subsetting code should not be calling this.
+ m_aGlyphOffsets.clear();
+ }
+
+ 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())
+ {
+ table = this->table(O_cmap, table_size);
+ m_bMicrosoftSymbolEncoded = HasMicrosoftSymbolCmap(table, table_size);
+ }
+ else
+ m_bMicrosoftSymbolEncoded = m_xCharMap->isMicrosoftSymbolMap();
+
+ 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_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 */
+
+ return AbstractTrueTypeFont::initialize();
+}
+
+int GetTTGlyphPoints(AbstractTrueTypeFont *ttf, sal_uInt32 glyphID, std::vector<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;
+
+ 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 (nOffset == nNextOffset)
+ return n;
+
+ const auto* ptr = glyf + nOffset;
+ 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 CreateTTFromTTGlyphs(AbstractTrueTypeFont *ttf,
+ std::vector<sal_uInt8>& rOutBuffer,
+ sal_uInt16 const *glyphArray,
+ sal_uInt8 const *encoding,
+ int nGlyphs)
+{
+ std::unique_ptr<TrueTypeTableGeneric> cvt, prep, fpgm, os2;
+ std::unique_ptr<TrueTypeTableName> name;
+ std::unique_ptr<TrueTypeTableMaxp> maxp;
+ std::unique_ptr<TrueTypeTableHhea> hhea;
+ std::unique_ptr<TrueTypeTableHead> head;
+ std::unique_ptr<TrueTypeTableGlyf> glyf;
+ std::unique_ptr<TrueTypeTableCmap> cmap;
+ std::unique_ptr<TrueTypeTablePost> post;
+ int i;
+ SFErrCodes res;
+
+ TrueTypeCreator ttcr(T_true);
+
+ /** name **/
+
+ std::vector<NameRecord> names;
+ GetTTNameRecords(ttf, names);
+ name.reset(new TrueTypeTableName(std::move(names)));
+
+ /** maxp **/
+ sal_uInt32 nTableSize;
+ const sal_uInt8* p = ttf->table(O_maxp, nTableSize);
+ maxp.reset(new TrueTypeTableMaxp(p, nTableSize));
+
+ /** hhea **/
+ p = ttf->table(O_hhea, nTableSize);
+ if (p && nTableSize >= HHEA_caretSlopeRun_offset + 2)
+ hhea.reset(new TrueTypeTableHhea(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.reset(new TrueTypeTableHhea(0, 0, 0, 0, 0));
+
+ /** head **/
+
+ p = ttf->table(O_head, nTableSize);
+ assert(p != nullptr);
+ head.reset(new TrueTypeTableHead(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.reset(new TrueTypeTableGlyf());
+ std::unique_ptr<sal_uInt32[]> gID(new sal_uInt32[nGlyphs]);
+
+ for (i = 0; i < nGlyphs; i++) {
+ gID[i] = glyf->glyfAdd(GetTTRawGlyphData(ttf, glyphArray[i]), ttf);
+ }
+
+ /** cmap **/
+ cmap.reset(new TrueTypeTableCmap());
+
+ for (i=0; i < nGlyphs; i++) {
+ cmap->cmapAdd(0x010000, encoding[i], gID[i]);
+ }
+
+ /** cvt **/
+ if ((p = ttf->table(O_cvt, nTableSize)) != nullptr)
+ cvt.reset(new TrueTypeTableGeneric(T_cvt, nTableSize, p));
+
+ /** prep **/
+ if ((p = ttf->table(O_prep, nTableSize)) != nullptr)
+ prep.reset(new TrueTypeTableGeneric(T_prep, nTableSize, p));
+
+ /** fpgm **/
+ if ((p = ttf->table(O_fpgm, nTableSize)) != nullptr)
+ fpgm.reset(new TrueTypeTableGeneric(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.reset(new TrueTypeTablePost(0x00030000,
+ nItalic, nPosition,
+ nThickness, nFixedPitch));
+ }
+ else
+ post.reset(new TrueTypeTablePost(0x00030000, 0, 0, 0, 0));
+
+ ttcr.AddTable(std::move(name)); ttcr.AddTable(std::move(maxp)); ttcr.AddTable(std::move(hhea));
+ ttcr.AddTable(std::move(head)); ttcr.AddTable(std::move(glyf)); ttcr.AddTable(std::move(cmap));
+ ttcr.AddTable(std::move(cvt)); ttcr.AddTable(std::move(prep)); ttcr.AddTable(std::move(fpgm));
+ ttcr.AddTable(std::move(post)); ttcr.AddTable(std::move(os2));
+
+ res = ttcr.StreamToMemory(rOutBuffer);
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN_IF(res != SFErrCodes::Ok, "vcl.fonts", "StreamToMemory: error code: "
+ << (int) res << ".");
+#endif
+
+ return res;
+}
+
+namespace
+{
+void FillFontSubsetInfo(AbstractTrueTypeFont* ttf, FontSubsetInfo& rInfo)
+{
+ TTGlobalFontInfo aTTInfo;
+ GetTTGlobalFontInfo(ttf, &aTTInfo);
+
+ rInfo.m_aPSName = OUString::fromUtf8(aTTInfo.psname);
+ rInfo.m_nFontType = FontType::SFNT_TTF;
+ rInfo.m_aFontBBox
+ = tools::Rectangle(Point(aTTInfo.xMin, aTTInfo.yMin), Point(aTTInfo.xMax, aTTInfo.yMax));
+ rInfo.m_nCapHeight = aTTInfo.yMax; // Well ...
+ rInfo.m_nAscent = aTTInfo.winAscent;
+ rInfo.m_nDescent = aTTInfo.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 = +aTTInfo.typoAscender;
+ if (!rInfo.m_nAscent)
+ rInfo.m_nAscent = +aTTInfo.ascender;
+ if (!rInfo.m_nDescent)
+ rInfo.m_nDescent = +aTTInfo.typoDescender;
+ if (!rInfo.m_nDescent)
+ rInfo.m_nDescent = -aTTInfo.descender;
+
+ rInfo.m_bFilled = true;
+}
+
+bool CreateCFFfontSubset(const unsigned char* pFontBytes, int nByteLength,
+ std::vector<sal_uInt8>& rOutBuffer, const sal_GlyphId* pGlyphIds,
+ const sal_uInt8* pEncoding, int nGlyphCount, FontSubsetInfo& rInfo)
+{
+ utl::TempFileFast aTempFile;
+ SvStream* pStream = aTempFile.GetStream(StreamMode::READWRITE);
+
+ rInfo.LoadFont(FontType::CFF_FONT, pFontBytes, nByteLength);
+ bool bRet = rInfo.CreateFontSubset(FontType::TYPE1_PFB, pStream, nullptr, pGlyphIds, pEncoding,
+ nGlyphCount);
+
+ if (bRet)
+ {
+ rOutBuffer.resize(pStream->TellEnd());
+ pStream->Seek(0);
+ auto nRead = pStream->ReadBytes(rOutBuffer.data(), rOutBuffer.size());
+ if (nRead != rOutBuffer.size())
+ {
+ rOutBuffer.clear();
+ return false;
+ }
+ }
+
+ return bRet;
+}
+}
+
+bool CreateTTFfontSubset(vcl::AbstractTrueTypeFont& rTTF, std::vector<sal_uInt8>& rOutBuffer,
+ const sal_GlyphId* pGlyphIds, const sal_uInt8* pEncoding,
+ const int nOrigGlyphCount, FontSubsetInfo& rInfo)
+{
+ // Get details about the subset font.
+ FillFontSubsetInfo(&rTTF, rInfo);
+
+ // Shortcut for CFF-subsetting.
+ sal_uInt32 nCFF;
+ const sal_uInt8* pCFF = rTTF.table(O_CFF, nCFF);
+ if (nCFF)
+ return CreateCFFfontSubset(pCFF, nCFF, rOutBuffer, pGlyphIds, pEncoding,
+ nOrigGlyphCount, rInfo);
+
+ // 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;
+ }
+
+ // write subset into destination file
+ return (CreateTTFromTTGlyphs(&rTTF, rOutBuffer, aShortIDs, aTempEncs, nGlyphCount)
+ == vcl::SFErrCodes::Ok);
+}
+
+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(AbstractTrueTypeFont *ttf, TTGlobalFontInfo *info)
+{
+ int UPEm = ttf->unitsPerEm();
+
+ info->family = ttf->family;
+ info->ufamily = ttf->ufamily;
+ info->subfamily = ttf->subfamily;
+ info->usubfamily = ttf->usubfamily;
+ info->psname = ttf->psname;
+ info->microsoftSymbolEncoded = ttf->IsMicrosoftSymbolEncoded();
+
+ 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));
+ }
+}
+
+std::unique_ptr<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;
+
+ std::unique_ptr<GlyphData> d(new GlyphData);
+
+ if (length > 0) {
+ const sal_uInt8* srcptr = glyf + ttf->glyphOffset(glyphID);
+ const size_t nChunkLen = ((length + 1) & ~1);
+ d->ptr.reset(new sal_uInt8[nChunkLen]);
+ memcpy(d->ptr.get(), srcptr, length);
+ memset(d->ptr.get() + 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 */
+ std::vector<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);
+ } 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;
+}
+
+void GetTTNameRecords(AbstractTrueTypeFont const *ttf, std::vector<NameRecord>& nr)
+{
+ sal_uInt32 nTableSize;
+ const sal_uInt8* table = ttf->table(O_name, nTableSize);
+
+ nr.clear();
+
+ 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);
+ sal_uInt32 nStrBase = GetUInt16(table, 4);
+ int i;
+
+ if (n == 0) return;
+
+ 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;
+ }
+
+ nr.resize(n);
+
+ 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);
+ break;
+ }
+
+ nr[i].platformID = GetUInt16(table, 6 + 0 + 12 * i);
+ nr[i].encodingID = GetUInt16(table, 6 + 2 + 12 * i);
+ nr[i].languageID = LanguageType(GetUInt16(table, 6 + 4 + 12 * i));
+ nr[i].nameID = GetUInt16(table, 6 + 6 + 12 * i);
+ sal_uInt16 slen = GetUInt16(table, 6 + 8 + 12 * i);
+ sal_uInt32 nStrOffset = GetUInt16(table, nLargestFixedOffsetPos);
+ if (slen) {
+ if (nStrBase + nStrOffset + slen >= nTableSize)
+ continue;
+
+ const sal_uInt32 rec_string = nStrBase + nStrOffset;
+ const size_t available_space = rec_string > nTableSize ? 0 : (nTableSize - rec_string);
+ if (slen <= available_space)
+ {
+ nr[i].sptr.resize(slen);
+ memcpy(nr[i].sptr.data(), table + rec_string, slen);
+ }
+ }
+ // some fonts have 3.0 names => fix them to 3.1
+ if( (nr[i].platformID == 3) && (nr[i].encodingID == 0) )
+ nr[i].encodingID = 1;
+ }
+}
+
+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;
+ }
+
+ std::vector<sal_uInt8> aBuffer;
+ CreateTTFromTTGlyphs(pTTF, aBuffer, 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 0000000000..84a0375c80
--- /dev/null
+++ b/vcl/source/fontsubset/ttcr.cxx
@@ -0,0 +1,1136 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <string.h>
+
+namespace vcl
+{
+
+/*
+ * Private Data Types
+ */
+
+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;
+}
+
+namespace {
+struct NameRecordCompareF
+{
+ bool operator()(const NameRecord& l, const NameRecord& r) const
+ {
+ if (l.platformID != r.platformID) {
+ return l.platformID < r.platformID;
+ } else if (l.encodingID != r.encodingID) {
+ return l.encodingID < r.encodingID;
+ } else if (l.languageID != r.languageID) {
+ return l.languageID < r.languageID;
+ } else if (l.nameID != r.nameID) {
+ return l.nameID < r.nameID;
+ }
+ return false;
+ }
+};
+}
+
+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;
+}
+
+/*
+ * Public functions
+ */
+
+TrueTypeCreator::TrueTypeCreator(sal_uInt32 _tag)
+{
+ this->m_tag = _tag;
+}
+
+void TrueTypeCreator::AddTable(std::unique_ptr<TrueTypeTable> table)
+{
+ if (table != nullptr) {
+ this->m_tables.push_back(std::move(table));
+ }
+}
+
+void TrueTypeCreator::RemoveTable(sal_uInt32 tableTag)
+{
+ for (auto it = this->m_tables.begin(); it != this->m_tables.end(); )
+ {
+ if ((*it)->m_tag == tableTag)
+ {
+ it = this->m_tables.erase(it);
+ }
+ else
+ ++it;
+ }
+}
+
+SFErrCodes TrueTypeCreator::StreamToMemory(std::vector<sal_uInt8>& rOutBuffer)
+{
+ 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 (this->m_tables.empty())
+ return SFErrCodes::TtFormat;
+
+ ProcessTables();
+
+ /* ProcessTables() adds 'loca' and 'hmtx' */
+
+ sal_uInt16 numTables = this->m_tables.size();
+
+ std::unique_ptr<TableEntry[]> te(new TableEntry[numTables]);
+
+ int teIdx = 0;
+ for (auto const & e : this->m_tables)
+ {
+ e->GetRawData(&te[teIdx]);
+ ++teIdx;
+ }
+
+ qsort(te.get(), 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; */
+ }
+
+ rOutBuffer.resize(s);
+ sal_uInt8* ttf = rOutBuffer.data();
+
+ /* Offset Table */
+ PutUInt32(this->m_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; */
+ }
+
+ te.reset();
+
+ 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);
+
+ return SFErrCodes::Ok;
+}
+
+/*
+ * 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
+
+namespace {
+
+struct CmapSubTable {
+ sal_uInt32 id; /* subtable ID (platform/encoding ID) */
+ std::vector<std::pair<sal_uInt32, sal_uInt32>> mappings; /* character to glyph mapping array */
+};
+
+}
+
+struct table_cmap {
+ sal_uInt32 n; /* number of used CMAP sub-tables */
+ sal_uInt32 m; /* number of allocated CMAP sub-tables */
+ std::unique_ptr<CmapSubTable[]> s; /* sorted array of sub-tables */
+};
+
+struct tdata_loca {
+ sal_uInt32 nbytes; /* number of bytes in loca table */
+ std::unique_ptr<sal_uInt8[]> ptr; /* pointer to the data */
+};
+
+/* allocate memory for a TT table */
+static std::unique_ptr<sal_uInt8[]> ttmalloc(sal_uInt32 nbytes)
+{
+ sal_uInt32 n = (nbytes + 3) & sal_uInt32(~3);
+ return std::make_unique<sal_uInt8[]>(n);
+}
+
+TrueTypeTable::~TrueTypeTable() {}
+
+TrueTypeTableGeneric::~TrueTypeTableGeneric()
+{
+}
+
+TrueTypeTableHead::~TrueTypeTableHead()
+{
+}
+
+TrueTypeTableHhea::~TrueTypeTableHhea()
+{
+}
+
+TrueTypeTableLoca::~TrueTypeTableLoca()
+{
+}
+
+TrueTypeTableMaxp::~TrueTypeTableMaxp()
+{
+}
+
+TrueTypeTableGlyf::~TrueTypeTableGlyf()
+{
+}
+
+TrueTypeTableCmap::~TrueTypeTableCmap()
+{
+}
+
+TrueTypeTableName::~TrueTypeTableName()
+{
+}
+
+TrueTypeTablePost::~TrueTypeTablePost()
+{
+ if (m_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>(m_format) << ".");
+ }
+}
+
+
+int TrueTypeTableGeneric::GetRawData(TableEntry* te)
+{
+ te->data = this->m_ptr.get();
+ te->length = this->m_nbytes;
+ te->tag = this->m_tag;
+
+ return TTCR_OK;
+}
+
+int TrueTypeTableHead::GetRawData(TableEntry* te)
+{
+ te->length = HEAD_Length;
+ te->data = this->m_head.get();
+ te->tag = T_head;
+
+ return TTCR_OK;
+}
+
+int TrueTypeTableHhea::GetRawData(TableEntry* te)
+{
+ te->length = HHEA_Length;
+ te->data = this->m_hhea.get();
+ te->tag = T_hhea;
+
+ return TTCR_OK;
+}
+
+int TrueTypeTableLoca::GetRawData(TableEntry* te)
+{
+ assert(this->m_loca != nullptr);
+
+ if (m_loca->nbytes == 0) return TTCR_ZEROGLYPHS;
+
+ te->data = m_loca->ptr.get();
+ te->length = m_loca->nbytes;
+ te->tag = T_loca;
+
+ return TTCR_OK;
+}
+
+int TrueTypeTableMaxp::GetRawData(TableEntry* te)
+{
+ te->length = MAXP_Version1Length;
+ te->data = this->m_maxp.get();
+ te->tag = T_maxp;
+
+ return TTCR_OK;
+}
+
+int TrueTypeTableGlyf::GetRawData(TableEntry* te)
+{
+ sal_uInt32 n, nbytes = 0;
+ /* sal_uInt16 curID = 0; */ /* to check if glyph IDs are sequential and start from zero */
+
+ te->data = nullptr;
+ te->length = 0;
+ te->tag = 0;
+
+ if (m_list.size() == 0) return TTCR_ZEROGLYPHS;
+
+ for (const std::unique_ptr<GlyphData>& pGlyph : m_list)
+ {
+ /* if (((GlyphData *) listCurrent(l))->glyphID != curID++) return TTCR_GLYPHSEQ; */
+ nbytes += pGlyph->nbytes;
+ }
+
+ m_rawdata = ttmalloc(nbytes);
+
+ auto p = m_rawdata.get();
+ for (const std::unique_ptr<GlyphData>& pGlyph : m_list)
+ {
+ n = pGlyph->nbytes;
+ if (n != 0) {
+ memcpy(p, pGlyph->ptr.get(), n);
+ p += n;
+ }
+ }
+
+ te->length = nbytes;
+ te->data = m_rawdata.get();
+ te->tag = T_glyf;
+
+ return TTCR_OK;
+}
+
+/* cmap packers */
+static std::unique_ptr<sal_uInt8[]> PackCmapType0(CmapSubTable const *s, sal_uInt32 *length)
+{
+ std::unique_ptr<sal_uInt8[]> ptr(new sal_uInt8[262]);
+ sal_uInt8 *p = ptr.get() + 6;
+
+ PutUInt16(0, ptr.get(), 0);
+ PutUInt16(262, ptr.get(), 2);
+ PutUInt16(0, ptr.get(), 4);
+
+ for (sal_uInt32 i = 0; i < 256; i++) {
+ sal_uInt16 g = 0;
+ for (const auto& [ch, glyph] : s->mappings) {
+ if (ch == i) {
+ g = static_cast<sal_uInt16>(glyph);
+ }
+ }
+ p[i] = static_cast<sal_uInt8>(g);
+ }
+ *length = 262;
+ return ptr;
+}
+
+static std::unique_ptr<sal_uInt8[]> PackCmapType6(CmapSubTable const *s, sal_uInt32 *length)
+{
+ std::unique_ptr<sal_uInt8[]> ptr(new sal_uInt8[s->mappings.size()*2 + 10]);
+ sal_uInt8 *p = ptr.get() + 10;
+
+ PutUInt16(6, ptr.get(), 0);
+ PutUInt16(static_cast<sal_uInt16>(s->mappings.size()*2+10), ptr.get(), 2);
+ PutUInt16(0, ptr.get(), 4);
+ PutUInt16(0, ptr.get(), 6);
+ PutUInt16(static_cast<sal_uInt16>(s->mappings.size()), ptr.get(), 8 );
+
+ for (size_t i = 0; i < s->mappings.size(); i++) {
+ sal_uInt16 g = 0;
+ for (const auto& [ch, glyph] : s->mappings) {
+ if (ch == i) {
+ g = static_cast<sal_uInt16>(glyph);
+ }
+ }
+ PutUInt16( g, p, 2*i );
+ }
+ *length = s->mappings.size()*2+10;
+ return ptr;
+}
+
+/* XXX it only handles Format 0 encoding tables */
+static std::unique_ptr<sal_uInt8[]> PackCmap(CmapSubTable const *s, sal_uInt32 *length)
+{
+ if (s->mappings.back().second > 0xff)
+ return PackCmapType6(s, length);
+ else
+ return PackCmapType0(s, length);
+}
+
+int TrueTypeTableCmap::GetRawData(TableEntry* te)
+{
+ sal_uInt32 i;
+ sal_uInt32 tlen = 0;
+ sal_uInt32 l;
+ sal_uInt32 cmapsize;
+ sal_uInt8 *cmap;
+ sal_uInt32 coffset;
+
+ assert(m_cmap);
+ assert(m_cmap->n != 0);
+
+ std::unique_ptr<std::unique_ptr<sal_uInt8[]>[]> subtables(new std::unique_ptr<sal_uInt8[]>[m_cmap->n]);
+ std::unique_ptr<sal_uInt32[]> sizes(new sal_uInt32[m_cmap->n]);
+
+ for (i = 0; i < m_cmap->n; i++) {
+ subtables[i] = PackCmap(m_cmap->s.get()+i, &l);
+ sizes[i] = l;
+ tlen += l;
+ }
+
+ cmapsize = tlen + 4 + 8 * m_cmap->n;
+ this->m_rawdata = ttmalloc(cmapsize);
+ cmap = this->m_rawdata.get();
+
+ PutUInt16(0, cmap, 0);
+ PutUInt16(static_cast<sal_uInt16>(m_cmap->n), cmap, 2);
+ coffset = 4 + m_cmap->n * 8;
+
+ for (i = 0; i < m_cmap->n; i++) {
+ PutUInt16(static_cast<sal_uInt16>(m_cmap->s[i].id >> 16), cmap + 4, i * 8);
+ PutUInt16(static_cast<sal_uInt16>(m_cmap->s[i].id & 0xFF), cmap + 4, 2 + i * 8);
+ PutUInt32(coffset, cmap + 4, 4 + i * 8);
+ memcpy(cmap + coffset, subtables[i].get(), sizes[i]);
+ subtables[i].reset();
+ coffset += sizes[i];
+ }
+
+ subtables.reset();
+ sizes.reset();
+
+ te->data = cmap;
+ te->length = cmapsize;
+ te->tag = T_cmap;
+
+ return TTCR_OK;
+}
+
+int TrueTypeTableName::GetRawData(TableEntry* te)
+{
+ sal_Int16 i=0, n; /* number of Name Records */
+ int stringLen = 0;
+ sal_uInt8 *p1, *p2;
+
+ te->data = nullptr;
+ te->length = 0;
+ te->tag = 0;
+
+ if ((n = static_cast<sal_Int16>(m_list.size())) == 0) return TTCR_NONAMES;
+
+ std::vector<NameRecord> nr = m_list;
+
+ for (const NameRecord & rName : m_list)
+ stringLen += rName.sptr.size();
+
+ if (stringLen > 65535) {
+ return TTCR_NAMETOOLONG;
+ }
+
+ std::sort(nr.begin(), nr.end(), NameRecordCompareF());
+
+ int nameLen = stringLen + 12 * n + 6;
+ std::unique_ptr<sal_uInt8[]> name = ttmalloc(nameLen);
+
+ PutUInt16(0, name.get(), 0);
+ PutUInt16(n, name.get(), 2);
+ PutUInt16(static_cast<sal_uInt16>(6 + 12 * n), name.get(), 4);
+
+ p1 = name.get() + 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].sptr.size(), p1, 8);
+ PutUInt16(static_cast<sal_uInt16>(p2 - (name.get() + 6 + 12 * n)), p1, 10);
+ if (nr[i].sptr.size()) {
+ memcpy(p2, nr[i].sptr.data(), nr[i].sptr.size());
+ }
+ /* {int j; for(j=0; j<nr[i].slen; j++) printf("%c", nr[i].sptr[j]); printf("\n"); }; */
+ p2 += nr[i].sptr.size();
+ p1 += 12;
+ }
+
+ nr.clear();
+ this->m_rawdata = std::move(name);
+
+ te->data = this->m_rawdata.get();
+ te->length = static_cast<sal_uInt16>(nameLen);
+ te->tag = T_name;
+
+ /*{int j; for(j=0; j<nameLen; j++) printf("%c", name[j]); }; */
+
+ return TTCR_OK;
+}
+
+int TrueTypeTablePost::GetRawData(TableEntry* te)
+{
+ std::unique_ptr<sal_uInt8[]> post;
+ sal_uInt32 postLen = 0;
+ int ret;
+
+ this->m_rawdata.reset();
+
+ if (m_format == 0x00030000) {
+ postLen = 32;
+ post = ttmalloc(postLen);
+ PutUInt32(0x00030000, post.get(), 0);
+ PutUInt32(m_italicAngle, post.get(), 4);
+ PutUInt16(m_underlinePosition, post.get(), 8);
+ PutUInt16(m_underlineThickness, post.get(), 10);
+ PutUInt16(static_cast<sal_uInt16>(m_isFixedPitch), post.get(), 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>(m_format) << ".");
+ ret = TTCR_POSTFORMAT;
+ }
+
+ this->m_rawdata = std::move(post);
+ te->data = this->m_rawdata.get();
+ te->length = postLen;
+ te->tag = T_post;
+
+ return ret;
+}
+
+/*
+ * 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
+ *
+ */
+
+TrueTypeTableGeneric::TrueTypeTableGeneric(sal_uInt32 tag,
+ sal_uInt32 nbytes,
+ const sal_uInt8* ptr)
+ : TrueTypeTable(tag),
+ m_nbytes(nbytes)
+{
+ if (nbytes) {
+ m_ptr = ttmalloc(nbytes);
+ memcpy(m_ptr.get(), ptr, nbytes);
+ }
+}
+
+TrueTypeTableGeneric::TrueTypeTableGeneric(sal_uInt32 tag,
+ sal_uInt32 nbytes,
+ std::unique_ptr<sal_uInt8[]> ptr)
+ : TrueTypeTable(tag),
+ m_nbytes(nbytes)
+{
+ if (nbytes) {
+ m_ptr = std::move(ptr);
+ }
+}
+
+TrueTypeTableHead::TrueTypeTableHead(sal_uInt32 fontRevision,
+ sal_uInt16 flags,
+ sal_uInt16 unitsPerEm,
+ const sal_uInt8* created,
+ sal_uInt16 macStyle,
+ sal_uInt16 lowestRecPPEM,
+ sal_Int16 fontDirectionHint)
+ : TrueTypeTable(T_head)
+ , m_head(ttmalloc(HEAD_Length))
+{
+ assert(created != nullptr);
+
+ sal_uInt8* ptr = m_head.get();
+
+ 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 */
+}
+
+TrueTypeTableHhea::TrueTypeTableHhea(sal_Int16 ascender,
+ sal_Int16 descender,
+ sal_Int16 linegap,
+ sal_Int16 caretSlopeRise,
+ sal_Int16 caretSlopeRun)
+ : TrueTypeTable(T_hhea),
+ m_hhea(ttmalloc(HHEA_Length))
+{
+ sal_uInt8* ptr = m_hhea.get();
+
+ 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 */
+}
+
+TrueTypeTableLoca::TrueTypeTableLoca()
+ : TrueTypeTable(T_loca),
+ m_loca(new tdata_loca)
+{
+ this->m_loca->nbytes = 0;
+ this->m_loca->ptr = nullptr;
+}
+
+TrueTypeTableMaxp::TrueTypeTableMaxp( const sal_uInt8* maxp, int size)
+ : TrueTypeTable(T_maxp)
+{
+ this->m_maxp = ttmalloc(MAXP_Version1Length);
+
+ if (maxp && size == MAXP_Version1Length) {
+ memcpy(this->m_maxp.get(), maxp, MAXP_Version1Length);
+ }
+}
+
+TrueTypeTableGlyf::TrueTypeTableGlyf()
+ : TrueTypeTable(T_glyf)
+{
+}
+
+TrueTypeTableCmap::TrueTypeTableCmap()
+ : TrueTypeTable(T_cmap)
+ , m_cmap(new table_cmap)
+{
+ m_cmap->n = 0;
+ m_cmap->m = CMAP_SUBTABLE_INIT;
+ m_cmap->s.reset(new CmapSubTable[CMAP_SUBTABLE_INIT]);
+}
+
+TrueTypeTableName::TrueTypeTableName(std::vector<NameRecord> nr)
+ : TrueTypeTable(T_name)
+ , m_list(std::move(nr))
+{
+}
+
+TrueTypeTablePost::TrueTypeTablePost(sal_Int32 format,
+ sal_Int32 italicAngle,
+ sal_Int16 underlinePosition,
+ sal_Int16 underlineThickness,
+ sal_uInt32 isFixedPitch)
+ : TrueTypeTable(T_post)
+{
+ assert(format == 0x00030000); /* Only format 3.0 is supported at this time */
+
+ m_format = format;
+ m_italicAngle = italicAngle;
+ m_underlinePosition = underlinePosition;
+ m_underlineThickness = underlineThickness;
+ m_isFixedPitch = isFixedPitch;
+}
+
+void TrueTypeTableCmap::cmapAdd(sal_uInt32 id, sal_uInt32 c, sal_uInt32 g)
+{
+ sal_uInt32 i, found;
+ CmapSubTable *s;
+
+ assert(m_cmap);
+ s = m_cmap->s.get(); assert(s != nullptr);
+
+ found = 0;
+
+ for (i = 0; i < m_cmap->n; i++) {
+ if (s[i].id == id) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ if (m_cmap->n == m_cmap->m) {
+ std::unique_ptr<CmapSubTable[]> tmp(new CmapSubTable[m_cmap->m + CMAP_SUBTABLE_INCR]);
+ for (sal_uInt32 j = 0; j != m_cmap->m; ++j) {
+ tmp[j] = std::move(s[j]);
+ }
+ m_cmap->m += CMAP_SUBTABLE_INCR;
+ s = tmp.get();
+ m_cmap->s = std::move(tmp);
+ }
+
+ for (i = 0; i < m_cmap->n; i++) {
+ if (s[i].id > id) break;
+ }
+
+ if (i < m_cmap->n) {
+ for (sal_uInt32 j = m_cmap->n; j != i; --j) {
+ s[j + 1] = std::move(s[j]);
+ }
+ }
+
+ m_cmap->n++;
+
+ s[i].id = id;
+ }
+
+ s[i].mappings.emplace_back(c, g);
+}
+
+sal_uInt32 TrueTypeTableGlyf::glyfAdd(std::unique_ptr<GlyphData> glyphdata, AbstractTrueTypeFont *fnt)
+{
+ sal_uInt32 currentID;
+ int ret, n, ncomponents;
+
+ if (!glyphdata) return sal_uInt32(~0);
+
+ std::vector< sal_uInt32 > glyphlist;
+
+ ncomponents = GetTTGlyphComponents(fnt, glyphdata->glyphID, glyphlist);
+
+ if (m_list.size() > 0) {
+ ret = n = m_list.back()->newID + 1;
+ } else {
+ ret = n = 0;
+ }
+ glyphdata->newID = n++;
+ m_list.push_back(std::move(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! */
+ for (const std::unique_ptr<GlyphData>& pGlyph : m_list)
+ {
+ if (pGlyph->glyphID == currentID) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ std::unique_ptr<GlyphData> gd = GetTTRawGlyphData(fnt, currentID);
+ gd->newID = n++;
+ m_list.push_back(std::move(gd));
+ }
+ } while( ++it != glyphlist.end() );
+ }
+
+ return ret;
+}
+
+TrueTypeTable *TrueTypeCreator::FindTable(sal_uInt32 tableTag)
+{
+ for (const std::unique_ptr<TrueTypeTable>& p : this->m_tables)
+ if (p->m_tag == tableTag) {
+ return p.get();
+ }
+
+ 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
+ *
+ */
+void TrueTypeCreator::ProcessTables()
+{
+ TrueTypeTableHhea *hhea = nullptr;
+ TrueTypeTableMaxp *maxp = nullptr;
+ TrueTypeTableHead *head = nullptr;
+ std::unique_ptr<TrueTypeTableLoca> loca;
+ TrueTypeTableGlyf *glyf = nullptr;
+ sal_uInt32 nGlyphs, locaLen = 0, glyfLen = 0;
+ sal_Int16 xMin = 0, yMin = 0, xMax = 0, yMax = 0;
+ sal_uInt32 i = 0;
+ sal_Int16 indexToLocFormat;
+ std::unique_ptr<sal_uInt8[]> hmtxPtr;
+ sal_uInt8 *hheaPtr;
+ sal_uInt32 hmtxSize;
+ sal_uInt8 *p1, *p2;
+ sal_uInt16 maxPoints = 0, maxContours = 0, maxCompositePoints = 0, maxCompositeContours = 0;
+ int nlsb = 0;
+ std::unique_ptr<sal_uInt32[]> gid; /* array of old glyphIDs */
+
+ glyf = static_cast<TrueTypeTableGlyf*>(FindTable(T_glyf));
+ std::vector<std::unique_ptr<GlyphData>>& glyphlist = glyf->m_list;
+ nGlyphs = glyphlist.size();
+ if (!nGlyphs)
+ {
+ SAL_WARN("vcl.fonts", "no glyphs found in ProcessTables");
+ return;
+ }
+ gid.reset(new sal_uInt32[nGlyphs]);
+
+ RemoveTable(T_loca);
+ RemoveTable(T_hmtx);
+
+ /* XXX Need to make sure that composite glyphs do not break during glyph renumbering */
+
+ for (const std::unique_ptr<GlyphData>& gd : 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.get(), 2);
+ if (z < xMin) xMin = z;
+
+ z = GetInt16(gd->ptr.get(), 4);
+ if (z < yMin) yMin = z;
+
+ z = GetInt16(gd->ptr.get(), 6);
+ if (z > xMax) xMax = z;
+
+ z = GetInt16(gd->ptr.get(), 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;
+ }
+
+ }
+
+ indexToLocFormat = (glyfLen / 2 > 0xFFFF) ? 1 : 0;
+ locaLen = indexToLocFormat ? (nGlyphs + 1) << 2 : (nGlyphs + 1) << 1;
+
+ std::unique_ptr<sal_uInt8[]> glyfPtr = ttmalloc(glyfLen);
+ std::unique_ptr<sal_uInt8[]> locaPtr = ttmalloc(locaLen);
+ std::unique_ptr<TTSimpleGlyphMetrics[]> met(new TTSimpleGlyphMetrics[nGlyphs]);
+ i = 0;
+
+ p1 = glyfPtr.get();
+ p2 = locaPtr.get();
+ for (const std::unique_ptr<GlyphData>& gd : glyphlist)
+ {
+ if (gd->compflag && gd->nbytes > 10) { /* re-number all components */
+ sal_uInt16 flags, index;
+ sal_uInt8 *ptr = gd->ptr.get() + 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.get(), gd->nbytes);
+ }
+ if (indexToLocFormat == 1) {
+ PutUInt32(p1 - glyfPtr.get(), p2, 0);
+ p2 += 4;
+ } else {
+ PutUInt16(static_cast<sal_uInt16>((p1 - glyfPtr.get()) >> 1), p2, 0);
+ p2 += 2;
+ }
+ p1 += gd->nbytes;
+
+ /* fill the array of metrics */
+ met[i].adv = gd->aw;
+ met[i].sb = gd->lsb;
+ i++;
+ }
+
+ gid.reset();
+
+ if (indexToLocFormat == 1) {
+ PutUInt32(p1 - glyfPtr.get(), p2, 0);
+ } else {
+ PutUInt16(static_cast<sal_uInt16>((p1 - glyfPtr.get()) >> 1), p2, 0);
+ }
+
+ glyf->m_rawdata = std::move(glyfPtr);
+
+ loca.reset(new TrueTypeTableLoca());
+ loca->m_loca->ptr = std::move(locaPtr);
+ loca->m_loca->nbytes = locaLen;
+
+ AddTable(std::move(loca));
+
+ head = static_cast<TrueTypeTableHead*>(FindTable(T_head));
+ sal_uInt8* const pHeadData = head->m_head.get();
+ 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 = static_cast<TrueTypeTableMaxp*>(FindTable(T_maxp));
+
+ sal_uInt8* const pMaxpData = maxp->m_maxp.get();
+ 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 = static_cast<TrueTypeTableHhea*>(FindTable(T_hhea)); assert(hhea != nullptr);
+ hheaPtr = hhea->m_hhea.get();
+ 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.get();
+
+ 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(std::make_unique<TrueTypeTableGeneric>(T_hmtx, hmtxSize, std::move(hmtxPtr)));
+ PutUInt16(static_cast<sal_uInt16>(nGlyphs - nlsb), hheaPtr, 34);
+}
+
+/**
+ * TrueTypeCreator destructor. It calls destructors for all TrueTypeTables added to it.
+ */
+TrueTypeCreator::~TrueTypeCreator()
+{
+}
+
+} // namespace vcl
+
+#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 0000000000..4dd78b0544
--- /dev/null
+++ b/vcl/source/fontsubset/ttcr.hxx
@@ -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 .
+ */
+
+/**
+ * @file ttcr.hxx
+ * @brief TrueType font creator
+ */
+
+#pragma once
+
+#include <sft.hxx>
+#include <vector>
+
+namespace vcl
+{
+class TrueTypeTable;
+struct tdata_loca;
+struct table_cmap;
+struct TableEntry;
+
+
+/* TrueType data types */
+ typedef struct {
+ sal_uInt16 aw;
+ sal_Int16 lsb;
+ } longHorMetrics;
+
+/** 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 */
+ };
+
+ class TrueTypeCreator {
+ public:
+ /**
+ * TrueTypeCreator constructor.
+ * Allocates all internal structures.
+ */
+ TrueTypeCreator(sal_uInt32 tag);
+ ~TrueTypeCreator();
+ /**
+ * Adds a TrueType table to the TrueType creator.
+ */
+ void AddTable(std::unique_ptr<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(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(std::vector<sal_uInt8>& rOutBuffer);
+
+ private:
+ TrueTypeTable *FindTable(sal_uInt32 tag);
+ void ProcessTables();
+
+ sal_uInt32 m_tag; /**< TrueType file tag */
+ std::vector<std::unique_ptr<TrueTypeTable>> m_tables; /**< List of table tags and pointers */
+ };
+
+ /* A generic base class for all TrueType tables */
+ class TrueTypeTable {
+ protected:
+ TrueTypeTable(sal_uInt32 tag_) : m_tag(tag_) {}
+
+ public:
+ virtual ~TrueTypeTable();
+
+ /**
+ * 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
+ *
+ */
+ virtual int GetRawData(TableEntry*) = 0;
+
+ sal_uInt32 m_tag = 0; /* table tag */
+ std::unique_ptr<sal_uInt8[]> m_rawdata; /* raw data allocated by GetRawData_*() */
+ };
+
+ class TrueTypeTableGeneric : public TrueTypeTable
+ {
+ public:
+ /**
+ *
+ * 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.
+ */
+ TrueTypeTableGeneric(sal_uInt32 tag,
+ sal_uInt32 nbytes,
+ const sal_uInt8* ptr);
+ TrueTypeTableGeneric(sal_uInt32 tag,
+ sal_uInt32 nbytes,
+ std::unique_ptr<sal_uInt8[]> ptr);
+ virtual ~TrueTypeTableGeneric() override;
+ virtual int GetRawData(TableEntry*) override;
+ private:
+ sal_uInt32 m_nbytes;
+ std::unique_ptr<sal_uInt8[]> m_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.
+ */
+ class TrueTypeTableHead : public TrueTypeTable
+ {
+ public:
+ TrueTypeTableHead(sal_uInt32 fontRevision,
+ sal_uInt16 flags,
+ sal_uInt16 unitsPerEm,
+ const sal_uInt8 *created,
+ sal_uInt16 macStyle,
+ sal_uInt16 lowestRecPPEM,
+ sal_Int16 fontDirectionHint);
+ virtual ~TrueTypeTableHead() override;
+ virtual int GetRawData(TableEntry*) override;
+
+ std::unique_ptr<sal_uInt8[]> m_head;
+ };
+
+/**
+ * Creates a new 'hhea' table for a TrueType font.
+ * Allocates memory for it and stores it in the hhea pointer.
+ */
+ class TrueTypeTableHhea : public TrueTypeTable
+ {
+ public:
+ TrueTypeTableHhea(sal_Int16 ascender,
+ sal_Int16 descender,
+ sal_Int16 linegap,
+ sal_Int16 caretSlopeRise,
+ sal_Int16 caretSlopeRun);
+ virtual ~TrueTypeTableHhea() override;
+ virtual int GetRawData(TableEntry*) override;
+
+ std::unique_ptr<sal_uInt8[]> m_hhea;
+ };
+
+/**
+ * Creates a new empty 'loca' table for a TrueType font.
+ *
+ * INTERNAL: gets called only from ProcessTables();
+ */
+ class TrueTypeTableLoca : public TrueTypeTable
+ {
+ public:
+ TrueTypeTableLoca();
+ virtual ~TrueTypeTableLoca() override;
+ virtual int GetRawData(TableEntry*) override;
+
+ std::unique_ptr<tdata_loca> m_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
+ */
+ class TrueTypeTableMaxp : public TrueTypeTable
+ {
+ public:
+ TrueTypeTableMaxp(const sal_uInt8* maxp, int size);
+ virtual ~TrueTypeTableMaxp() override;
+ virtual int GetRawData(TableEntry*) override;
+
+ std::unique_ptr<sal_uInt8[]> m_maxp;
+ };
+
+/**
+ * Creates a new empty 'glyf' table.
+ */
+ class TrueTypeTableGlyf : public TrueTypeTable
+ {
+ public:
+ TrueTypeTableGlyf();
+ virtual ~TrueTypeTableGlyf() override;
+ virtual int GetRawData(TableEntry*) override;
+
+ /**
+ * 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(std::unique_ptr<GlyphData> glyphdata, AbstractTrueTypeFont *fnt);
+
+ std::vector<std::unique_ptr<GlyphData>> m_list;
+ };
+
+/**
+ * Creates a new empty 'cmap' table.
+ */
+ class TrueTypeTableCmap : public TrueTypeTable
+ {
+ public:
+ TrueTypeTableCmap();
+ virtual ~TrueTypeTableCmap() override;
+ virtual int GetRawData(TableEntry*) override;
+
+ /**
+ * Add a character/glyph pair to a cmap table
+ */
+ void cmapAdd(sal_uInt32 id, sal_uInt32 c, sal_uInt32 g);
+
+ private:
+ std::unique_ptr<table_cmap> m_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.
+ */
+ class TrueTypeTableName : public TrueTypeTable
+ {
+ public:
+ TrueTypeTableName(std::vector<NameRecord> nr);
+ virtual ~TrueTypeTableName() override;
+ virtual int GetRawData(TableEntry*) override;
+ private:
+ std::vector<NameRecord> m_list;
+ };
+
+/**
+ * Creates a new 'post' table of one of the supported formats
+ */
+ class TrueTypeTablePost : public TrueTypeTable
+ {
+ public:
+ TrueTypeTablePost(sal_Int32 format,
+ sal_Int32 italicAngle,
+ sal_Int16 underlinePosition,
+ sal_Int16 underlineThickness,
+ sal_uInt32 isFixedPitch);
+ virtual ~TrueTypeTablePost() override;
+ virtual int GetRawData(TableEntry*) override;
+ private:
+ sal_uInt32 m_format;
+ sal_uInt32 m_italicAngle;
+ sal_Int16 m_underlinePosition;
+ sal_Int16 m_underlineThickness;
+ sal_uInt32 m_isFixedPitch;
+ };
+
+} // namespace
+
+/* 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 0000000000..b7966f2798
--- /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 0000000000..89eba1b7f4
--- /dev/null
+++ b/vcl/source/fontsubset/xlat.hxx
@@ -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 .
+ */
+
+/*| Author: Alexander Gelfenbain |*/
+
+#pragma once
+
+#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);
+}
+
+/* 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 0000000000..bcf6f54639
--- /dev/null
+++ b/vcl/source/gdi/CommonSalLayout.cxx
@@ -0,0 +1,850 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/svapp.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 <hb-icu.h>
+
+#include <map>
+#include <memory>
+
+GenericSalLayout::GenericSalLayout(LogicalFontInstance &rFont)
+ : m_GlyphItems(rFont)
+ , mpVertGlyphs(nullptr)
+ , mbFuzzing(utl::ConfigManager::IsFuzzing())
+{
+}
+
+GenericSalLayout::~GenericSalLayout()
+{
+ if (mpVertGlyphs)
+ hb_set_destroy(mpVertGlyphs);
+}
+
+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 {
+ int32_t 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 U_VO_TRANSFORMED_UPRIGHT;
+
+ return u_getIntPropertyValue(cCh, UCHAR_VERTICAL_ORIENTATION);
+ }
+} // 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);
+
+ // tdf#107612
+ // If the start of the fallback run is Mongolian character and the previous
+ // character is NNBSP, we want to include the NNBSP in the fallback since
+ // it has special uses in Mongolian and have to be in the same text run to
+ // work.
+ sal_Int32 nTempPos = nGraphemeStartPos;
+ if (nGraphemeStartPos > 0)
+ {
+ auto nCurrChar = rArgs.mrStr.iterateCodePoints(&nTempPos, 0);
+ auto nPrevChar = rArgs.mrStr.iterateCodePoints(&nTempPos, -1);
+ if (nPrevChar == 0x202F
+ && u_getIntPropertyValue(nCurrChar, UCHAR_SCRIPT) == USCRIPT_MONGOLIAN)
+ nGraphemeStartPos = nTempPos;
+ }
+
+ //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.mpDXArray)
+ ApplyDXArray(rArgs.mpDXArray, rArgs.mpKashidaArray);
+ 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)
+{
+ sal_GlyphId nGlyphIndex = GetFont().GetGlyphIndex(aChar, aVariationSelector);
+ if (!nGlyphIndex)
+ return false;
+
+ if (!mpVertGlyphs)
+ {
+ hb_face_t* pHbFace = hb_font_get_face(GetFont().GetHbFont());
+ 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 input glyphs in each lookup (i.e. the glyphs that
+ // this lookup applies to).
+ 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);
+ }
+ }
+ hb_set_destroy(pLookups);
+ }
+
+ 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.
+ double 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.0;
+ }
+
+ hb_buffer_t* pHbBuffer = hb_buffer_create();
+ hb_buffer_pre_allocate(pHbBuffer, nGlyphCapacity);
+
+ 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 (rArgs.mnFlags & SalLayoutFlags::DisableLigatures)
+ {
+ SAL_INFO("vcl.harfbuzz", "Disabling ligatures for font: " << rFontSelData.maTargetName);
+
+ // Both of these are optional ligatures, enabled by default but not for
+ // orthographically-required ligatures.
+ maFeatures.push_back({ HB_TAG('l','i','g','a'), 0, 0, static_cast<unsigned int>(-1) });
+ maFeatures.push_back({ HB_TAG('c','l','i','g'), 0, 0, static_cast<unsigned int>(-1) });
+ }
+
+ ParseFeatures(rFontSelData.maTargetName);
+
+ double nXScale = 0;
+ double nYScale = 0;
+ GetFont().GetScale(&nXScale, &nYScale);
+
+ basegfx::B2DPoint 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);
+ int32_t 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 == U_VO_UPRIGHT || aVo == U_VO_TRANSFORMED_UPRIGHT ||
+ (aVo == U_VO_TRANSFORMED_ROTATED &&
+ HasVerticalAlternate(aChar, aVariationSelector)))
+ {
+ aDirection = HB_DIRECTION_TTB;
+ }
+ else
+ {
+ aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
+ }
+
+ if (aSubRuns.empty() || aSubRuns.back().maDirection != aDirection || aSubRuns.back().maScript != aScript)
+ 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;
+
+ // Produce HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL that we use below.
+ nHbFlags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL;
+
+ 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);
+
+ // The shapers that we want HarfBuzz to use, in the order of
+ // preference.
+ const char*const pHbShapers[] = { "graphite2", "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_isUWhiteSpace(aChar))
+ nGlyphFlags |= GlyphItemFlags::IS_SPACING;
+
+ if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]) & HB_GLYPH_FLAG_UNSAFE_TO_BREAK)
+ nGlyphFlags |= GlyphItemFlags::IS_UNSAFE_TO_BREAK;
+
+ if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]) & HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL)
+ nGlyphFlags |= GlyphItemFlags::IS_SAFE_TO_INSERT_KASHIDA;
+
+ double 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.0 );
+ }
+
+ }
+ else
+ {
+ nAdvance = pHbPositions[i].x_advance;
+ nXOffset = pHbPositions[i].x_offset;
+ nYOffset = -pHbPositions[i].y_offset;
+ }
+
+ nAdvance = nAdvance * nXScale;
+ nXOffset = nXOffset * nXScale;
+ nYOffset = nYOffset * nYScale;
+ if (!GetSubpixelPositioning())
+ {
+ nAdvance = std::lround(nAdvance);
+ nXOffset = std::lround(nXOffset);
+ nYOffset = std::lround(nYOffset);
+ }
+
+ basegfx::B2DPoint 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<double>& rCharWidths, const OUString& rStr) const
+{
+ const int nCharCount = mnEndCharPos - mnMinCharPos;
+
+ rCharWidths.clear();
+ rCharWidths.resize(nCharCount, 0);
+
+ css::uno::Reference<css::i18n::XBreakIterator> xBreak;
+ auto aLocale(maLanguageTag.getLocale());
+
+ for (auto const& aGlyphItem : m_GlyphItems)
+ {
+ if (aGlyphItem.charPos() >= mnEndCharPos)
+ continue;
+
+ unsigned int nGraphemeCount = 0;
+ if (aGlyphItem.charCount() > 1 && aGlyphItem.newWidth() != 0 && !rStr.isEmpty())
+ {
+ // We are calculating DX array for cursor positions and this is a
+ // ligature, find out how many grapheme clusters are in it.
+ if (!xBreak.is())
+ xBreak = mxBreak.is() ? mxBreak : vcl::unohelper::CreateBreakIterator();
+
+ // Count grapheme clusters in the ligature.
+ sal_Int32 nDone;
+ sal_Int32 nPos = aGlyphItem.charPos();
+ while (nPos < aGlyphItem.charPos() + aGlyphItem.charCount())
+ {
+ nPos = xBreak->nextCharacters(rStr, nPos, aLocale,
+ css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
+ nGraphemeCount++;
+ }
+ }
+
+ if (nGraphemeCount > 1)
+ {
+ // More than one grapheme cluster, we want to distribute the glyph
+ // width over them.
+ std::vector<double> aWidths(nGraphemeCount);
+
+ // Check if the glyph has ligature caret positions.
+ unsigned int nCarets = nGraphemeCount;
+ std::vector<hb_position_t> aCarets(nGraphemeCount);
+ hb_ot_layout_get_ligature_carets(GetFont().GetHbFont(),
+ aGlyphItem.IsRTLGlyph() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR,
+ aGlyphItem.glyphId(), 0, &nCarets, aCarets.data());
+
+ // Carets are 1-less than the grapheme count (since the last
+ // position is defined by glyph width), if the count does not
+ // match, ignore it.
+ if (nCarets == nGraphemeCount - 1)
+ {
+ // Scale the carets and apply glyph offset to them since they
+ // are based on the default glyph metrics.
+ double fScale = 0;
+ GetFont().GetScale(&fScale, nullptr);
+ for (size_t i = 0; i < nCarets; i++)
+ aCarets[i] = (aCarets[i] * fScale) + aGlyphItem.xOffset();
+
+ // Use the glyph width for the last caret.
+ aCarets[nCarets] = aGlyphItem.newWidth();
+
+ // Carets are absolute from the X origin of the glyph, turn
+ // them to relative widths that we need below.
+ for (size_t i = 0; i < nGraphemeCount; i++)
+ aWidths[i] = aCarets[i] - (i == 0 ? 0 : aCarets[i - 1]);
+
+ // Carets are in visual order, but we want widths in logical
+ // order.
+ if (aGlyphItem.IsRTLGlyph())
+ std::reverse(aWidths.begin(), aWidths.end());
+ }
+ else
+ {
+ // The glyph has no carets, distribute the width evenly.
+ auto nWidth = aGlyphItem.newWidth() / nGraphemeCount;
+ std::fill(aWidths.begin(), aWidths.end(), nWidth);
+
+ // Add rounding difference to the last component to maintain
+ // ligature width.
+ aWidths[nGraphemeCount - 1] += aGlyphItem.newWidth() - (nWidth * nGraphemeCount);
+ }
+
+ // Set the width of each grapheme cluster.
+ sal_Int32 nDone;
+ sal_Int32 nPos = aGlyphItem.charPos();
+ for (auto nWidth : aWidths)
+ {
+ rCharWidths[nPos - mnMinCharPos] += nWidth;
+ nPos = xBreak->nextCharacters(rStr, nPos, aLocale,
+ css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
+ }
+ }
+ else
+ rCharWidths[aGlyphItem.charPos() - mnMinCharPos] += aGlyphItem.newWidth();
+ }
+}
+
+// - pDXArray: is the adjustments to glyph advances (usually due to
+// justification).
+// - pKashidaArray: is the places where kashidas are inserted (for Arabic
+// justification). The number of kashidas is calculated from the pDXArray.
+void GenericSalLayout::ApplyDXArray(const double* pDXArray, const sal_Bool* pKashidaArray)
+{
+ int nCharCount = mnEndCharPos - mnMinCharPos;
+ std::vector<double> aOldCharWidths;
+ std::unique_ptr<double[]> const pNewCharWidths(new double[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];
+ }
+
+ // Map of Kashida insertion points (in the glyph items vector) and the
+ // requested width.
+ std::map<size_t, std::pair<double, double>> pKashidas;
+
+ // The accumulated difference in X position.
+ double 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;
+ double 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 // RTL
+ {
+ // Adjust the width and position of the first (rightmost) glyph in
+ // the cluster. This is RTL, so 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 the rest of the glyphs in the cluster.
+ // We iterate backwards since this is an RTL glyph.
+ for (int j = i - 1; j >= 0 && m_GlyphItems[j].IsInCluster(); j--)
+ m_GlyphItems[j].adjustLinearPosX(nDelta + nDiff);
+
+ // This is a Kashida insertion position, mark it. Kashida glyphs
+ // will be inserted below.
+ if (pKashidaArray && pKashidaArray[nCharPos])
+ pKashidas[i] = { nDiff, pNewCharWidths[nCharPos] };
+
+ 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 glyph belonging to the same
+ // character which is wrong as DX adjustments are character based).
+ nDelta += nDiff;
+ }
+
+ // Insert Kashida glyphs.
+ if (pKashidas.empty())
+ return;
+
+ // Find Kashida glyph width and index.
+ sal_GlyphId nKashidaIndex = GetFont().GetGlyphIndex(0x0640);
+ double nKashidaWidth = GetFont().GetKashidaWidth();
+ if (!GetSubpixelPositioning())
+ nKashidaWidth = std::ceil(nKashidaWidth);
+
+ if (nKashidaWidth <= 0)
+ {
+ SAL_WARN("vcl.gdi", "Asked to insert Kashidas in a font with bogus Kashida width");
+ return;
+ }
+
+ size_t nInserted = 0;
+ for (auto const& pKashida : pKashidas)
+ {
+ auto pGlyphIter = m_GlyphItems.begin() + nInserted + pKashida.first;
+
+ // The total Kashida width.
+ auto const& [nTotalWidth, nClusterWidth] = 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.
+ double nOverlap = 0;
+ double nShortfall = nTotalWidth - nKashidaWidth * nCopies;
+ if (nShortfall > 0)
+ {
+ ++nCopies;
+ double nExcess = nCopies * nKashidaWidth - nTotalWidth;
+ if (nExcess > 0)
+ nOverlap = nExcess / (nCopies - 1);
+ }
+
+ basegfx::B2DPoint aPos = pGlyphIter->linearPos();
+ int nCharPos = pGlyphIter->charPos();
+ GlyphItemFlags const nFlags = GlyphItemFlags::IS_IN_CLUSTER | GlyphItemFlags::IS_RTL_GLYPH;
+ // Move to the left side of the adjusted width and start inserting
+ // glyphs there.
+ aPos.adjustX(-nClusterWidth + pGlyphIter->origWidth());
+ while (nCopies--)
+ {
+ GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, 0, 0, 0);
+ pGlyphIter = m_GlyphItems.insert(pGlyphIter, aKashida);
+ aPos.adjustX(nKashidaWidth - nOverlap);
+ ++pGlyphIter;
+ ++nInserted;
+ }
+ }
+}
+
+// Kashida will be inserted between nCharPos and nNextCharPos.
+bool GenericSalLayout::IsKashidaPosValid(int nCharPos, int nNextCharPos) const
+{
+ // Search for glyph items corresponding to nCharPos and nNextCharPos.
+ auto const& rGlyph = std::find_if(m_GlyphItems.begin(), m_GlyphItems.end(),
+ [&](const GlyphItem& g) { return g.charPos() == nCharPos; });
+ auto const& rNextGlyph = std::find_if(m_GlyphItems.begin(), m_GlyphItems.end(),
+ [&](const GlyphItem& g) { return g.charPos() == nNextCharPos; });
+
+ // If either is not found then a ligature is created at this position, we
+ // can’t insert Kashida here.
+ if (rGlyph == m_GlyphItems.end() || rNextGlyph == m_GlyphItems.end())
+ return false;
+
+ // If the either character is not supported by this layout, return false so
+ // that fallback layouts would be checked for it.
+ if (rGlyph->glyphId() == 0 || rNextGlyph->glyphId() == 0)
+ return false;
+
+ // Lastly check if this position is kashida-safe.
+ return rNextGlyph->IsSafeToInsertKashida();
+}
+
+/* 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 0000000000..0be4c829dd
--- /dev/null
+++ b/vcl/source/gdi/FileDefinitionWidgetDraw.cxx
@@ -0,0 +1,1130 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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:
+ case ControlType::LevelBar:
+ 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.GetAlphaMask().GetBitmap().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:
+ case ControlType::LevelBar:
+ {
+ 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);
+
+ 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 0000000000..49890fc38e
--- /dev/null
+++ b/vcl/source/gdi/TypeSerializer.cxx
@@ -0,0 +1,483 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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<css::awt::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;
+ }
+
+ rGfxLink = GfxLink(BinaryDataContainer(mrStream, 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)
+ {
+ BinaryDataContainer aDataContainer(mrStream, nLength);
+
+ 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);
+ pVectorGraphicData->getBinaryDataContainer().writeToStream(mrStream);
+
+ // 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;
+ // ofz#62439 negative numbers are multiplied by -1, MIN_INT32 * -1
+ // cannot be expressed as an int
+ if (rScale.GetNumerator() == std::numeric_limits<sal_Int32>::min())
+ 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/WidgetDefinition.cxx b/vcl/source/gdi/WidgetDefinition.cxx
new file mode 100644
index 0000000000..1c8fb1321e
--- /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 <utility>
+#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"_ostr;
+
+ 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"_ostr;
+ else if (rTabItemValue.isLeftAligned() || rTabItemValue.isFirst())
+ sExtra = "first"_ostr;
+ else if (rTabItemValue.isRightAligned() || rTabItemValue.isLast())
+ sExtra = "last"_ostr;
+ else
+ sExtra = "middle"_ostr;
+ }
+ break;
+ case ControlType::ListHeader:
+ {
+ if (ePart == ControlPart::Arrow)
+ {
+ if (rValue.getNumericVal() == 1)
+ sExtra = "down"_ostr;
+ else
+ sExtra = "up"_ostr;
+ }
+ }
+ break;
+ case ControlType::Pushbutton:
+ {
+ auto const& rPushButtonValue = static_cast<PushButtonValue const&>(rValue);
+ if (rPushButtonValue.mbIsAction)
+ sExtra = "action"_ostr;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (state->msExtra != "any" && state->msExtra != sExtra)
+ {
+ bAdd = false;
+ }
+
+ if (bAdd)
+ aStatesToAdd.push_back(state);
+ }
+
+ return aStatesToAdd;
+}
+
+WidgetDefinitionState::WidgetDefinitionState(OString sEnabled, OString sFocused, OString sPressed,
+ OString sRollover, OString sDefault, OString sSelected,
+ OString sButtonValue, OString sExtra)
+ : msEnabled(std::move(sEnabled))
+ , msFocused(std::move(sFocused))
+ , msPressed(std::move(sPressed))
+ , msRollover(std::move(sRollover))
+ , msDefault(std::move(sDefault))
+ , msSelected(std::move(sSelected))
+ , msButtonValue(std::move(sButtonValue))
+ , msExtra(std::move(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 0000000000..f5373b035c
--- /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 <utility>
+#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"_ostr;
+ 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 },
+ { "levelbar", ControlType::LevelBar },
+ { "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 aDefinitionFile, OUString aResourcePath)
+ : m_rDefinitionFile(std::move(aDefinitionFile))
+ , m_rResourcePath(std::move(aResourcePath))
+{
+}
+
+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"_ostr), aStrokeColor);
+ Color aFillColor;
+ readColor(rWalker.attribute("fill"_ostr), aFillColor);
+ OString sStrokeWidth = rWalker.attribute("stroke-width"_ostr);
+ sal_Int32 nStrokeWidth = -1;
+ if (!sStrokeWidth.isEmpty())
+ nStrokeWidth = sStrokeWidth.toInt32();
+
+ sal_Int32 nRx = -1;
+ OString sRx = rWalker.attribute("rx"_ostr);
+ if (!sRx.isEmpty())
+ nRx = sRx.toInt32();
+
+ sal_Int32 nRy = -1;
+ OString sRy = rWalker.attribute("ry"_ostr);
+ if (!sRy.isEmpty())
+ nRy = sRy.toInt32();
+
+ OString sX1 = rWalker.attribute("x1"_ostr);
+ float fX1 = sX1.isEmpty() ? 0.0 : sX1.toFloat();
+
+ OString sY1 = rWalker.attribute("y1"_ostr);
+ float fY1 = sY1.isEmpty() ? 0.0 : sY1.toFloat();
+
+ OString sX2 = rWalker.attribute("x2"_ostr);
+ float fX2 = sX2.isEmpty() ? 1.0 : sX2.toFloat();
+
+ OString sY2 = rWalker.attribute("y2"_ostr);
+ 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"_ostr), aStrokeColor);
+
+ OString sStrokeWidth = rWalker.attribute("stroke-width"_ostr);
+ sal_Int32 nStrokeWidth = -1;
+ if (!sStrokeWidth.isEmpty())
+ nStrokeWidth = sStrokeWidth.toInt32();
+
+ OString sX1 = rWalker.attribute("x1"_ostr);
+ float fX1 = sX1.isEmpty() ? -1.0 : sX1.toFloat();
+
+ OString sY1 = rWalker.attribute("y1"_ostr);
+ float fY1 = sY1.isEmpty() ? -1.0 : sY1.toFloat();
+
+ OString sX2 = rWalker.attribute("x2"_ostr);
+ float fX2 = sX2.isEmpty() ? -1.0 : sX2.toFloat();
+
+ OString sY2 = rWalker.attribute("y2"_ostr);
+ 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"_ostr);
+ rpState->addDrawImage(m_rResourcePath
+ + OStringToOUString(sSource, RTL_TEXTENCODING_UTF8));
+ }
+ else if (rWalker.name() == "external")
+ {
+ OString sSource = rWalker.attribute("source"_ostr);
+ 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"_ostr);
+ ControlPart ePart = xmlStringToControlPart(sPart);
+
+ std::shared_ptr<WidgetDefinitionPart> pPart = std::make_shared<WidgetDefinitionPart>();
+
+ OString sWidth = rWalker.attribute("width"_ostr);
+ if (!sWidth.isEmpty())
+ {
+ sal_Int32 nWidth = sWidth.isEmpty() ? 0 : sWidth.toInt32();
+ pPart->mnWidth = nWidth;
+ }
+
+ OString sHeight = rWalker.attribute("height"_ostr);
+ if (!sHeight.isEmpty())
+ {
+ sal_Int32 nHeight = sHeight.isEmpty() ? 0 : sHeight.toInt32();
+ pPart->mnHeight = nHeight;
+ }
+
+ OString sMarginHeight = rWalker.attribute("margin-height"_ostr);
+ if (!sMarginHeight.isEmpty())
+ {
+ sal_Int32 nMarginHeight = sMarginHeight.isEmpty() ? 0 : sMarginHeight.toInt32();
+ pPart->mnMarginHeight = nMarginHeight;
+ }
+
+ OString sMarginWidth = rWalker.attribute("margin-width"_ostr);
+ if (!sMarginWidth.isEmpty())
+ {
+ sal_Int32 nMarginWidth = sMarginWidth.isEmpty() ? 0 : sMarginWidth.toInt32();
+ pPart->mnMarginWidth = nMarginWidth;
+ }
+
+ OString sOrientation = rWalker.attribute("orientation"_ostr);
+ 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"_ostr));
+ OString sFocused = getValueOrAny(rWalker.attribute("focused"_ostr));
+ OString sPressed = getValueOrAny(rWalker.attribute("pressed"_ostr));
+ OString sRollover = getValueOrAny(rWalker.attribute("rollover"_ostr));
+ OString sDefault = getValueOrAny(rWalker.attribute("default"_ostr));
+ OString sSelected = getValueOrAny(rWalker.attribute("selected"_ostr));
+ OString sButtonValue = getValueOrAny(rWalker.attribute("button-value"_ostr));
+ OString sExtra = getValueOrAny(rWalker.attribute("extra"_ostr));
+
+ 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 },
+ };
+
+ 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"_ostr), *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"_ostr), *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 0000000000..5586f67a61
--- /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 0000000000..51fb6df10e
--- /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 0000000000..a8a4f7e983
--- /dev/null
+++ b/vcl/source/gdi/embeddedfontshelper.cxx
@@ -0,0 +1,363 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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,
+ std::u16string_view 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, std::u16string_view 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( extra == u"?" )
+ filename += OUString::number( uniqueCounter++ );
+ else
+ filename += extra;
+ 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;
+
+ // Maybe we don't find the perfect match for the font. E.G. we have fonts with the same family name
+ // but not same bold or italic etc...
+ // In this case we add all the fonts having the family name of the used font:
+ // - we store all these fonts in familyNameFonts during loop
+ // - if we haven't found the perfect match we store all fonts in familyNameFonts
+ typedef std::vector<vcl::font::PhysicalFontFace*> FontList;
+ FontList familyNameFonts;
+
+ 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;
+ }
+ // adding "not perfect match" to familyNameFonts vector
+ familyNameFonts.push_back(f);
+
+ }
+ }
+
+ // if we have found a perfect match we will add only "selected", otherwise all familyNameFonts
+ FontList fontsToAdd = (selected ? FontList(1, selected) : std::move(familyNameFonts));
+
+ for (vcl::font::PhysicalFontFace* f : fontsToAdd)
+ {
+ if (!selected) { // recalculate file not for "not perfect match"
+ filename = OUString::Concat(familyName) + "_" + OUString::number(f->GetFamilyType()) + "_" +
+ OUString::number(f->GetItalic()) + "_" + OUString::number(f->GetWeight()) + "_" +
+ OUString::number(f->GetPitch()) + ".ttf"; // TODO is it always ttf?
+ 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.
+ continue;
+ }
+ }
+ auto aFontData(f->GetRawFontData(0));
+ if (!aFontData.empty())
+ {
+ auto data = aFontData.data();
+ auto size = aFontData.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( 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;
+ }
+ }
+ }
+ }
+ 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 0000000000..1bb84a6694
--- /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/formpdfexport.cxx b/vcl/source/gdi/formpdfexport.cxx
new file mode 100644
index 0000000000..ac952491a2
--- /dev/null
+++ b/vcl/source/gdi/formpdfexport.cxx
@@ -0,0 +1,824 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/formpdfexport.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/lineend.hxx>
+#include <unordered_map>
+#include <sal/log.hxx>
+
+#include <com/sun/star/container/XIndexAccess.hpp>
+#include <com/sun/star/form/XForm.hpp>
+#include <com/sun/star/container/XChild.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/form/FormComponentType.hpp>
+#include <com/sun/star/awt/TextAlign.hpp>
+#include <com/sun/star/awt/XControl.hpp>
+#include <com/sun/star/style/VerticalAlignment.hpp>
+#include <com/sun/star/form/FormButtonType.hpp>
+#include <com/sun/star/form/FormSubmitMethod.hpp>
+
+#include <toolkit/helper/vclunohelper.hxx>
+#include <vcl/pdfextoutdevdata.hxx>
+#include <vcl/unohelp.hxx>
+
+#include <algorithm>
+#include <iterator>
+
+
+static vcl::Font CreateFont( const css::awt::FontDescriptor& rDescr )
+{
+ vcl::Font aFont;
+ if ( !rDescr.Name.isEmpty() )
+ aFont.SetFamilyName( rDescr.Name );
+ if ( !rDescr.StyleName.isEmpty() )
+ aFont.SetStyleName( rDescr.StyleName );
+ if ( rDescr.Height )
+ aFont.SetFontSize( Size( rDescr.Width, rDescr.Height ) );
+ if ( static_cast<FontFamily>(rDescr.Family) != FAMILY_DONTKNOW )
+ aFont.SetFamily( static_cast<FontFamily>(rDescr.Family) );
+ if ( static_cast<rtl_TextEncoding>(rDescr.CharSet) != RTL_TEXTENCODING_DONTKNOW )
+ aFont.SetCharSet( static_cast<rtl_TextEncoding>(rDescr.CharSet) );
+ if ( static_cast<FontPitch>(rDescr.Pitch) != PITCH_DONTKNOW )
+ aFont.SetPitch( static_cast<FontPitch>(rDescr.Pitch) );
+ if ( rDescr.CharacterWidth )
+ aFont.SetWidthType(vcl::unohelper::ConvertFontWidth(rDescr.CharacterWidth));
+ if ( rDescr.Weight )
+ aFont.SetWeight(vcl::unohelper::ConvertFontWeight(rDescr.Weight));
+ if ( rDescr.Slant != css::awt::FontSlant_DONTKNOW )
+ aFont.SetItalic(vcl::unohelper::ConvertFontSlant(rDescr.Slant));
+ if ( static_cast<FontLineStyle>(rDescr.Underline) != LINESTYLE_DONTKNOW )
+ aFont.SetUnderline( static_cast<FontLineStyle>(rDescr.Underline) );
+ if ( static_cast<FontStrikeout>(rDescr.Strikeout) != STRIKEOUT_DONTKNOW )
+ aFont.SetStrikeout( static_cast<FontStrikeout>(rDescr.Strikeout) );
+
+ // Not DONTKNOW
+ aFont.SetOrientation( Degree10(static_cast<sal_Int16>(rDescr.Orientation * 10)) );
+ aFont.SetKerning( static_cast<FontKerning>(rDescr.Kerning) );
+ aFont.SetWordLineMode( rDescr.WordLineMode );
+
+ return aFont;
+}
+
+namespace toolkitform
+{
+
+
+ using namespace ::com::sun::star;
+ using namespace ::com::sun::star::uno;
+ using namespace ::com::sun::star::awt;
+ using namespace ::com::sun::star::style;
+ using namespace ::com::sun::star::beans;
+ using namespace ::com::sun::star::form;
+ using namespace ::com::sun::star::lang;
+ using namespace ::com::sun::star::container;
+
+ constexpr OUString FM_PROP_NAME = u"Name"_ustr;
+
+ namespace
+ {
+
+ /** determines the FormComponentType of a form control
+ */
+ sal_Int16 classifyFormControl( const Reference< XPropertySet >& _rxModel )
+ {
+ static constexpr OUString FM_PROP_CLASSID = u"ClassId"_ustr;
+ sal_Int16 nControlType = FormComponentType::CONTROL;
+
+ Reference< XPropertySetInfo > xPSI;
+ if ( _rxModel.is() )
+ xPSI = _rxModel->getPropertySetInfo();
+ if ( xPSI.is() && xPSI->hasPropertyByName( FM_PROP_CLASSID ) )
+ {
+ if( ! (_rxModel->getPropertyValue( FM_PROP_CLASSID ) >>= nControlType) ) {
+ SAL_WARN("toolkit.helper", "classifyFormControl: unable to get property " << FM_PROP_CLASSID);
+ }
+ }
+
+ return nControlType;
+ }
+
+
+ /** (default-)creates a PDF widget according to a given FormComponentType
+ */
+ std::unique_ptr<vcl::PDFWriter::AnyWidget> createDefaultWidget( sal_Int16 _nFormComponentType )
+ {
+ switch ( _nFormComponentType )
+ {
+ case FormComponentType::COMMANDBUTTON:
+ return std::make_unique<vcl::PDFWriter::PushButtonWidget>();
+ case FormComponentType::CHECKBOX:
+ return std::make_unique<vcl::PDFWriter::CheckBoxWidget>();
+ case FormComponentType::RADIOBUTTON:
+ return std::make_unique<vcl::PDFWriter::RadioButtonWidget>();
+ case FormComponentType::LISTBOX:
+ return std::make_unique<vcl::PDFWriter::ListBoxWidget>();
+ case FormComponentType::COMBOBOX:
+ return std::make_unique<vcl::PDFWriter::ComboBoxWidget>();
+
+ case FormComponentType::TEXTFIELD:
+ case FormComponentType::FILECONTROL:
+ case FormComponentType::DATEFIELD:
+ case FormComponentType::TIMEFIELD:
+ case FormComponentType::NUMERICFIELD:
+ case FormComponentType::CURRENCYFIELD:
+ case FormComponentType::PATTERNFIELD:
+ return std::make_unique<vcl::PDFWriter::EditWidget>();
+ }
+ return nullptr;
+ }
+
+
+ /** determines a unique number for the radio group which the given radio
+ button model belongs to
+
+ The number is guaranteed to be
+ <ul><li>unique within the document in which the button lives</li>
+ <li>the same for subsequent calls with other radio button models,
+ which live in the same document, and belong to the same group</li>
+ </ul>
+
+ @precond
+ the model must be part of the form component hierarchy in a document
+ */
+ sal_Int32 determineRadioGroupId( const Reference< XPropertySet >& _rxRadioModel )
+ {
+ OSL_ENSURE( classifyFormControl( _rxRadioModel ) == FormComponentType::RADIOBUTTON,
+ "determineRadioGroupId: this *is* no radio button model!" );
+ // The fact that radio button groups need to be unique within the complete
+ // host document makes it somewhat difficult ...
+ // Problem is that two form radio buttons belong to the same group if
+ // - they have the same parent
+ // - AND they have the same name or group name
+ // This implies that we need some knowledge about (potentially) *all* radio button
+ // groups in the document.
+
+ // get the root-level container
+ Reference< XChild > xChild( _rxRadioModel, UNO_QUERY );
+ Reference< XForm > xParentForm( xChild.is() ? xChild->getParent() : Reference< XInterface >(), UNO_QUERY );
+ OSL_ENSURE( xParentForm.is(), "determineRadioGroupId: no parent form -> group id!" );
+ if ( !xParentForm.is() )
+ return -1;
+
+ while ( xParentForm.is() )
+ {
+ xChild = xParentForm.get();
+ xParentForm.set(xChild->getParent(), css::uno::UNO_QUERY);
+ }
+ Reference< XIndexAccess > xRoot( xChild->getParent(), UNO_QUERY );
+ OSL_ENSURE( xRoot.is(), "determineRadioGroupId: unable to determine the root of the form component hierarchy!" );
+ if ( !xRoot.is() )
+ return -1;
+
+ // count the leafs in the hierarchy, until we encounter radio button
+ ::std::vector< Reference< XIndexAccess > > aAncestors;
+ ::std::vector< sal_Int32 > aPath;
+
+ Reference< XInterface > xNormalizedLookup( _rxRadioModel, UNO_QUERY );
+ Reference< XIndexAccess > xCurrentContainer( xRoot );
+ sal_Int32 nStartWithChild = 0;
+ sal_Int32 nGroupsEncountered = 0;
+ do
+ {
+ std::unordered_map<OUString,sal_Int32> GroupNameMap;
+ std::unordered_map<OUString,sal_Int32> SharedNameMap;
+ sal_Int32 nCount = xCurrentContainer->getCount();
+ sal_Int32 i;
+ for ( i = nStartWithChild; i < nCount; ++i )
+ {
+ Reference< XInterface > xElement( xCurrentContainer->getByIndex( i ), UNO_QUERY );
+ if ( !xElement.is() )
+ {
+ OSL_FAIL( "determineRadioGroupId: very suspicious!" );
+ continue;
+ }
+
+ Reference< XIndexAccess > xNewContainer( xElement, UNO_QUERY );
+ if ( xNewContainer.is() )
+ {
+ // step down the hierarchy
+ aAncestors.push_back( xCurrentContainer );
+ xCurrentContainer = xNewContainer;
+ aPath.push_back( i );
+ nStartWithChild = 0;
+ break;
+ // out of the inner loop, but continue with the outer loop
+ }
+
+ if ( xElement.get() == xNormalizedLookup.get() )
+ {
+ // Our radio button is in this container.
+ // Now take the time to ID this container's groups and return the button's groupId
+ for ( i = 0; i < nCount; ++i )
+ {
+ try
+ {
+ xElement.set( xCurrentContainer->getByIndex( i ), UNO_QUERY_THROW );
+ Reference< XServiceInfo > xModelSI( xElement, UNO_QUERY_THROW );
+ if ( xModelSI->supportsService("com.sun.star.awt.UnoControlRadioButtonModel") )
+ {
+ Reference< XPropertySet > aProps( xElement, UNO_QUERY_THROW );
+
+ OUString sGroupName;
+ aProps->getPropertyValue("GroupName") >>= sGroupName;
+ if ( !sGroupName.isEmpty() )
+ {
+ // map: unique key is the group name, so attempts to add a different ID value
+ // for an existing group are ignored - keeping the first ID - perfect for this scenario.
+ GroupNameMap.emplace( sGroupName, nGroupsEncountered + i );
+
+ if ( xElement.get() == xNormalizedLookup.get() )
+ return GroupNameMap[sGroupName];
+ }
+ else
+ {
+ // Old implementation didn't have a GroupName, just identical Control names.
+ aProps->getPropertyValue( FM_PROP_NAME ) >>= sGroupName;
+ SharedNameMap.emplace( sGroupName, nGroupsEncountered + i );
+
+ if ( xElement.get() == xNormalizedLookup.get() )
+ return SharedNameMap[sGroupName];
+ }
+
+ }
+ }
+ catch( uno::Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("toolkit");
+ }
+ }
+ SAL_WARN("toolkit.helper","determineRadioGroupId: did not find the radios element's group!" );
+ }
+ }
+
+ // we encounter this container the first time. In particular, we did not just step up
+ if ( nStartWithChild == 0 )
+ {
+ // Our control wasn't in this container, so consider every item to be a possible unique group.
+ // This is way too much: Not all of the elements in the current container will form groups.
+ // But anyway, this number is sufficient for our purpose, since sequential group ids are not required.
+ // Ultimately, the container contains *at most* this many groups.
+ nGroupsEncountered += nCount;
+ }
+
+ if ( i >= nCount )
+ {
+ // the loop terminated because there were no more elements
+ // -> step up, if possible
+ if ( aAncestors.empty() )
+ break;
+
+ xCurrentContainer = aAncestors.back(); aAncestors.pop_back();
+ nStartWithChild = aPath.back() + 1; aPath.pop_back();
+ }
+ }
+ while ( true );
+ return -1;
+ }
+
+
+ /** copies a StringItemList to a PDF widget's list
+ */
+ void getStringItemVector( const Reference< XPropertySet >& _rxModel, ::std::vector< OUString >& _rVector )
+ {
+ Sequence< OUString > aListEntries;
+ if( ! (_rxModel->getPropertyValue( "StringItemList" ) >>= aListEntries) ) {
+ SAL_WARN("toolkit.helper", "getStringItemVector: unable to get property StringItemList");
+ }
+ _rVector.insert( _rVector.end(), std::cbegin(aListEntries), std::cend(aListEntries) );
+ }
+ }
+
+
+ /** creates a PDF compatible control descriptor for the given control
+ */
+ std::unique_ptr<vcl::PDFWriter::AnyWidget> describePDFControl( const Reference< XControl >& _rxControl,
+ vcl::PDFExtOutDevData& i_pdfExportData )
+ {
+ std::unique_ptr<vcl::PDFWriter::AnyWidget> Descriptor;
+ OSL_ENSURE( _rxControl.is(), "describePDFControl: invalid (NULL) control!" );
+ if ( !_rxControl.is() )
+ return Descriptor;
+
+ try
+ {
+ Reference< XPropertySet > xModelProps( _rxControl->getModel(), UNO_QUERY );
+ sal_Int16 nControlType = classifyFormControl( xModelProps );
+ Descriptor = createDefaultWidget( nControlType );
+ if (!Descriptor)
+ // no PDF widget available for this
+ return Descriptor;
+
+ Reference< XPropertySetInfo > xPSI( xModelProps->getPropertySetInfo() );
+ Reference< XServiceInfo > xSI( xModelProps, UNO_QUERY );
+ OSL_ENSURE( xSI.is(), "describePDFControl: no service info!" );
+ // if we survived classifyFormControl, then it's a real form control, and they all have
+ // service infos
+
+
+ // set the common widget properties
+
+
+ // Name, Description, Text
+ if( ! (xModelProps->getPropertyValue( FM_PROP_NAME ) >>= Descriptor->Name) ) {
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_NAME);
+ }
+ if( ! (xModelProps->getPropertyValue( "HelpText" ) >>= Descriptor->Description) ) {
+ SAL_INFO("toolkit.helper", "describePDFControl: unable to get property HelpText");
+ }
+ Any aText;
+ static constexpr OUString FM_PROP_TEXT = u"Text"_ustr;
+ static constexpr OUString FM_PROP_LABEL = u"Label"_ustr;
+ static constexpr OUString FM_PROP_VALUE = u"Value"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_TEXT ) )
+ aText = xModelProps->getPropertyValue( FM_PROP_TEXT );
+ else if ( xPSI->hasPropertyByName( FM_PROP_LABEL ) )
+ aText = xModelProps->getPropertyValue( FM_PROP_LABEL );
+ else if ( xPSI->hasPropertyByName( FM_PROP_VALUE ) )
+ {
+ double aValue;
+ if (xModelProps->getPropertyValue( FM_PROP_VALUE ) >>= aValue)
+ aText <<= OUString::number(aValue);
+ }
+
+ if ( aText.hasValue() ) {
+ if( ! (aText >>= Descriptor->Text) ) {
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to assign aText to Descriptor->Text");
+ }
+ }
+
+
+ // readonly
+ static constexpr OUString FM_PROP_READONLY = u"ReadOnly"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_READONLY ) )
+ if( ! (xModelProps->getPropertyValue( FM_PROP_READONLY ) >>= Descriptor->ReadOnly) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_READONLY);
+
+
+ // border
+ {
+ static constexpr OUString FM_PROP_BORDER = u"Border"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_BORDER ) )
+ {
+ sal_Int16 nBorderType = 0;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_BORDER ) >>= nBorderType) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_BORDER);
+ Descriptor->Border = ( nBorderType != 0 );
+
+ OUString sBorderColorPropertyName( "BorderColor" );
+ if ( xPSI->hasPropertyByName( sBorderColorPropertyName ) )
+ {
+ Color nBorderColor = COL_TRANSPARENT;
+ if ( xModelProps->getPropertyValue( sBorderColorPropertyName ) >>= nBorderColor )
+ Descriptor->BorderColor = nBorderColor;
+ else
+ Descriptor->BorderColor = COL_BLACK;
+ }
+ }
+ }
+
+
+ // background color
+ static constexpr OUString FM_PROP_BACKGROUNDCOLOR = u"BackgroundColor"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_BACKGROUNDCOLOR ) )
+ {
+ Color nBackColor = COL_TRANSPARENT;
+ xModelProps->getPropertyValue( FM_PROP_BACKGROUNDCOLOR ) >>= nBackColor;
+ Descriptor->Background = true;
+ Descriptor->BackgroundColor = nBackColor;
+ }
+
+
+ // text color
+ static constexpr OUString FM_PROP_TEXTCOLOR = u"TextColor"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_TEXTCOLOR ) )
+ {
+ Color nTextColor = COL_TRANSPARENT;
+ xModelProps->getPropertyValue( FM_PROP_TEXTCOLOR ) >>= nTextColor;
+ Descriptor->TextColor = nTextColor;
+ }
+
+
+ // text style
+ Descriptor->TextStyle = DrawTextFlags::NONE;
+
+ // multi line and word break
+ // The MultiLine property of the control is mapped to both the "MULTILINE" and
+ // "WORDBREAK" style flags
+ static constexpr OUString FM_PROP_MULTILINE = u"MultiLine"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_MULTILINE ) )
+ {
+ bool bMultiLine = false;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_MULTILINE ) >>= bMultiLine) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_MULTILINE);
+ if ( bMultiLine )
+ Descriptor->TextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
+ }
+
+ // horizontal alignment
+ static constexpr OUString FM_PROP_ALIGN = u"Align"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_ALIGN ) )
+ {
+ sal_Int16 nAlign = awt::TextAlign::LEFT;
+ xModelProps->getPropertyValue( FM_PROP_ALIGN ) >>= nAlign;
+ // TODO: when the property is VOID - are there situations/UIs where this
+ // means something else than LEFT?
+ switch ( nAlign )
+ {
+ case awt::TextAlign::LEFT: Descriptor->TextStyle |= DrawTextFlags::Left; break;
+ case awt::TextAlign::CENTER: Descriptor->TextStyle |= DrawTextFlags::Center; break;
+ case awt::TextAlign::RIGHT: Descriptor->TextStyle |= DrawTextFlags::Right; break;
+ default:
+ OSL_FAIL( "describePDFControl: invalid text align!" );
+ }
+ }
+
+ // vertical alignment
+ {
+ OUString sVertAlignPropertyName( "VerticalAlign" );
+ if ( xPSI->hasPropertyByName( sVertAlignPropertyName ) )
+ {
+ VerticalAlignment nAlign = VerticalAlignment_MIDDLE;
+ xModelProps->getPropertyValue( sVertAlignPropertyName ) >>= nAlign;
+ switch ( nAlign )
+ {
+ case VerticalAlignment_TOP: Descriptor->TextStyle |= DrawTextFlags::Top; break;
+ case VerticalAlignment_MIDDLE: Descriptor->TextStyle |= DrawTextFlags::VCenter; break;
+ case VerticalAlignment_BOTTOM: Descriptor->TextStyle |= DrawTextFlags::Bottom; break;
+ default:
+ OSL_FAIL( "describePDFControl: invalid vertical text align!" );
+ }
+ }
+ }
+
+ // font
+ static constexpr OUString FM_PROP_FONT = u"FontDescriptor"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_FONT ) )
+ {
+ FontDescriptor aUNOFont;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_FONT ) >>= aUNOFont) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_FONT);
+ Descriptor->TextFont = CreateFont( aUNOFont );
+ }
+
+ // tab order
+ OUString aTabIndexString( "TabIndex" );
+ if ( xPSI->hasPropertyByName( aTabIndexString ) )
+ {
+ sal_Int16 nIndex = -1;
+ if( ! (xModelProps->getPropertyValue( aTabIndexString ) >>= nIndex) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << aTabIndexString);
+ Descriptor->TabOrder = nIndex;
+ }
+
+
+ // special widget properties
+
+ // edits
+ if ( Descriptor->getType() == vcl::PDFWriter::Edit )
+ {
+ vcl::PDFWriter::EditWidget* pEditWidget = static_cast< vcl::PDFWriter::EditWidget* >( Descriptor.get() );
+
+ // multiline (already flagged in the TextStyle)
+ pEditWidget->MultiLine = bool( Descriptor->TextStyle & DrawTextFlags::MultiLine );
+
+ // password input
+ OUString sEchoCharPropName( "EchoChar" );
+ if ( xPSI->hasPropertyByName( sEchoCharPropName ) )
+ {
+ sal_Int16 nEchoChar = 0;
+ if ( ( xModelProps->getPropertyValue( sEchoCharPropName ) >>= nEchoChar ) && ( nEchoChar != 0 ) )
+ pEditWidget->Password = true;
+ }
+
+ // file select
+ if ( xSI->supportsService( "com.sun.star.form.component.FileControl" ) )
+ pEditWidget->FileSelect = true;
+
+ // maximum text length
+ static constexpr OUString FM_PROP_MAXTEXTLEN = u"MaxTextLen"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_MAXTEXTLEN ) )
+ {
+ sal_Int16 nMaxTextLength = 0;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_MAXTEXTLEN ) >>= nMaxTextLength) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_MAXTEXTLEN);
+ if ( nMaxTextLength <= 0 )
+ // "-1" has a special meaning for database-bound controls
+ nMaxTextLength = 0;
+ pEditWidget->MaxLen = nMaxTextLength;
+ }
+
+ switch ( nControlType )
+ {
+ case FormComponentType::CURRENCYFIELD:
+ case FormComponentType::NUMERICFIELD:
+ {
+
+ pEditWidget->Format = vcl::PDFWriter::Number;
+
+ static constexpr OUString FM_PROP_CURRENCYSYMBOL = u"CurrencySymbol"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_CURRENCYSYMBOL ) )
+ {
+ OUString sCurrencySymbol;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_CURRENCYSYMBOL ) >>= sCurrencySymbol) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_CURRENCYSYMBOL);
+ pEditWidget->CurrencySymbol = sCurrencySymbol;
+ }
+
+ static constexpr OUString FM_PROP_DECIMALACCURACY = u"DecimalAccuracy"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_DECIMALACCURACY ) )
+ {
+ sal_Int32 nDecimalAccuracy = 0;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_DECIMALACCURACY ) >>= nDecimalAccuracy) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_DECIMALACCURACY);
+ pEditWidget->DecimalAccuracy = nDecimalAccuracy;
+ }
+
+ static constexpr OUString FM_PROP_PREPENDCURRENCYSYMBOL = u"PrependCurrencySymbol"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_PREPENDCURRENCYSYMBOL ) )
+ {
+ bool bPrependCurrencySymbol = true;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_PREPENDCURRENCYSYMBOL ) >>= bPrependCurrencySymbol) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_PREPENDCURRENCYSYMBOL);
+ pEditWidget->PrependCurrencySymbol = bPrependCurrencySymbol;
+ }
+ } break;
+ case FormComponentType::TIMEFIELD:
+ {
+ pEditWidget->Format = vcl::PDFWriter::Time;
+
+ static constexpr OUString FM_PROP_TIMEFORMAT = u"TimeFormat"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_TIMEFORMAT ) )
+ {
+ sal_Int32 nTimeFormat = 0;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_TIMEFORMAT ) >>= nTimeFormat) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_TIMEFORMAT);
+
+ switch ( nTimeFormat )
+ {
+ case 0:
+ pEditWidget->TimeFormat = "HH:MM"; //13:45
+ break;
+ case 1:
+ pEditWidget->TimeFormat = "HH:MM:ss"; //13:45:00
+ break;
+ case 2:
+ pEditWidget->TimeFormat = "h:MMtt"; //01:45 PM
+ break;
+ case 3:
+ pEditWidget->TimeFormat = "h:MM:sstt"; //01:45:00 PM
+ break;
+ }
+ }
+ } break;
+ case FormComponentType::DATEFIELD:
+ {
+ pEditWidget->Format = vcl::PDFWriter::Date;
+
+ static constexpr OUString FM_PROP_DATEFORMAT = u"DateFormat"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_DATEFORMAT ) )
+ {
+ sal_Int32 nDateFormat = 0;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_DATEFORMAT ) >>= nDateFormat) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_DATEFORMAT);
+
+ switch ( nDateFormat )
+ {
+ case 0:
+ case 1:
+ pEditWidget->DateFormat = "mm/dd/yy"; // Standard (short)
+ break;
+ case 2:
+ case 3:
+ pEditWidget->DateFormat = "mm/dd/yyyy"; // Standard (long)
+ break;
+ case 4:
+ pEditWidget->DateFormat = "dd/mm/yy"; // DD/MM/YY
+ break;
+ case 5:
+ pEditWidget->DateFormat = "mm/dd/yy"; // MM/DD/YY
+ break;
+ case 6:
+ pEditWidget->DateFormat = "yy/mm/dd"; // YY/MM/DD
+ break;
+ case 7:
+ pEditWidget->DateFormat = "dd/mm/yyyy"; // DD/MM/YYYY
+ break;
+ case 8:
+ pEditWidget->DateFormat = "mm/dd/yyyy"; // MM/DD/YYYY
+ break;
+ case 9:
+ pEditWidget->DateFormat = "yyyy/mm/dd"; // YYYY/MM/DD
+ break;
+ case 10:
+ pEditWidget->DateFormat = "yy-mm-dd"; // YY-MM-DD
+ break;
+ case 11:
+ pEditWidget->DateFormat = "yyyy-mm-dd"; // YYYY-MM-DD
+ break;
+ }
+ }
+ } break;
+ }
+ }
+
+ // buttons
+ if ( Descriptor->getType() == vcl::PDFWriter::PushButton )
+ {
+ vcl::PDFWriter::PushButtonWidget* pButtonWidget = static_cast< vcl::PDFWriter::PushButtonWidget* >( Descriptor.get() );
+ FormButtonType eButtonType = FormButtonType_PUSH;
+ if( ! (xModelProps->getPropertyValue("ButtonType") >>= eButtonType) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property ButtonType");
+ static constexpr OUString FM_PROP_TARGET_URL = u"TargetURL"_ustr;
+ if ( eButtonType == FormButtonType_SUBMIT )
+ {
+ // if a button is a submit button, then it uses the URL at its parent form
+ Reference< XChild > xChild( xModelProps, UNO_QUERY );
+ Reference < XPropertySet > xParentProps;
+ if ( xChild.is() )
+ xParentProps.set(xChild->getParent(), css::uno::UNO_QUERY);
+ if ( xParentProps.is() )
+ {
+ Reference< XServiceInfo > xParentSI( xParentProps, UNO_QUERY );
+ if ( xParentSI.is() && xParentSI->supportsService("com.sun.star.form.component.HTMLForm") )
+ {
+ if( ! (xParentProps->getPropertyValue( FM_PROP_TARGET_URL ) >>= pButtonWidget->URL) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_TARGET_URL);
+ pButtonWidget->Submit = true;
+ FormSubmitMethod eMethod = FormSubmitMethod_POST;
+ if( ! (xParentProps->getPropertyValue("SubmitMethod") >>= eMethod) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_TARGET_URL);
+ pButtonWidget->SubmitGet = (eMethod == FormSubmitMethod_GET);
+ }
+ }
+ }
+ else if ( eButtonType == FormButtonType_URL )
+ {
+ OUString sURL;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_TARGET_URL ) >>= sURL) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_TARGET_URL);
+ const bool bDocumentLocalTarget = sURL.startsWith("#");
+ if ( bDocumentLocalTarget )
+ {
+ // Register the destination for future handling ...
+ pButtonWidget->Dest = i_pdfExportData.RegisterDest();
+
+ // and put it into the bookmarks, to ensure the future handling really happens
+ ::std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks( i_pdfExportData.GetBookmarks() );
+ vcl::PDFExtOutDevBookmarkEntry aBookmark;
+ aBookmark.nDestId = pButtonWidget->Dest;
+ aBookmark.aBookmark = sURL;
+ rBookmarks.push_back( aBookmark );
+ }
+ else
+ pButtonWidget->URL = sURL;
+
+ pButtonWidget->Submit = false;
+ }
+
+ // TODO:
+ // In PDF files, buttons are either reset, url or submit buttons. So if we have a simple PUSH button
+ // in a document, then this means that we do not export a SubmitToURL, which means that in PDF,
+ // the button is used as reset button.
+ // Is this desired? If no, we would have to reset Descriptor to NULL here, in case eButtonType
+ // != FormButtonType_SUBMIT && != FormButtonType_RESET
+
+ // the PDF exporter defaults the text style, if 0. To prevent this, we have to transfer the UNO
+ // defaults to the PDF widget
+ if ( pButtonWidget->TextStyle == DrawTextFlags::NONE )
+ pButtonWidget->TextStyle = DrawTextFlags::Center | DrawTextFlags::VCenter;
+ }
+
+
+ // check boxes
+ static constexpr OUString FM_PROP_STATE = u"State"_ustr;
+ if ( Descriptor->getType() == vcl::PDFWriter::CheckBox )
+ {
+ vcl::PDFWriter::CheckBoxWidget* pCheckBoxWidget = static_cast< vcl::PDFWriter::CheckBoxWidget* >( Descriptor.get() );
+ sal_Int16 nState = 0;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_STATE ) >>= nState) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_STATE);
+ pCheckBoxWidget->Checked = ( nState != 0 );
+
+ try
+ {
+ xModelProps->getPropertyValue( "RefValue" ) >>= pCheckBoxWidget->OnValue;
+ }
+ catch(...)
+ {
+ }
+
+ try
+ {
+ xModelProps->getPropertyValue( "SecondaryRefValue" ) >>= pCheckBoxWidget->OffValue;
+ }
+ catch(...)
+ {
+ }
+ }
+
+
+ // radio buttons
+ if ( Descriptor->getType() == vcl::PDFWriter::RadioButton )
+ {
+ vcl::PDFWriter::RadioButtonWidget* pRadioWidget = static_cast< vcl::PDFWriter::RadioButtonWidget* >( Descriptor.get() );
+ sal_Int16 nState = 0;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_STATE ) >>= nState) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_STATE);
+ pRadioWidget->Selected = ( nState != 0 );
+ pRadioWidget->RadioGroup = determineRadioGroupId( xModelProps );
+ try
+ {
+ xModelProps->getPropertyValue( "RefValue" ) >>= pRadioWidget->OnValue;
+ }
+ catch(...)
+ {
+ }
+
+ try
+ {
+ xModelProps->getPropertyValue( "SecondaryRefValue" ) >>= pRadioWidget->OffValue;
+ }
+ catch(...)
+ {
+ }
+ }
+
+
+ // list boxes
+ if ( Descriptor->getType() == vcl::PDFWriter::ListBox )
+ {
+ vcl::PDFWriter::ListBoxWidget* pListWidget = static_cast< vcl::PDFWriter::ListBoxWidget* >( Descriptor.get() );
+
+ // drop down
+ if( ! (xModelProps->getPropertyValue( "Dropdown" ) >>= pListWidget->DropDown) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property Dropdown");
+
+ // multi selection
+ if( ! (xModelProps->getPropertyValue("MultiSelection") >>= pListWidget->MultiSelect) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property MultiSelection");
+
+ // entries
+ getStringItemVector( xModelProps, pListWidget->Entries );
+
+ // get selected items
+ Sequence< sal_Int16 > aSelectIndices;
+ if( ! (xModelProps->getPropertyValue("SelectedItems") >>= aSelectIndices) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property SelectedItems");
+ if( aSelectIndices.hasElements() )
+ {
+ pListWidget->SelectedEntries.resize( 0 );
+ auto nEntriesSize = static_cast<sal_Int16>(pListWidget->Entries.size());
+ std::copy_if(std::cbegin(aSelectIndices), std::cend(aSelectIndices), std::back_inserter(pListWidget->SelectedEntries),
+ [&nEntriesSize](const sal_Int16 nIndex) { return nIndex >= 0 && nIndex < nEntriesSize; });
+ }
+ }
+
+
+ // combo boxes
+ if ( Descriptor->getType() == vcl::PDFWriter::ComboBox )
+ {
+ vcl::PDFWriter::ComboBoxWidget* pComboWidget = static_cast< vcl::PDFWriter::ComboBoxWidget* >( Descriptor.get() );
+
+ // entries
+ getStringItemVector( xModelProps, pComboWidget->Entries );
+ }
+
+
+ // some post-processing
+
+ // text line ends
+ // some controls may (always or dependent on other settings) return UNIX line ends
+ Descriptor->Text = convertLineEnd(Descriptor->Text, LINEEND_CRLF);
+ }
+ catch( const Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "toolkit", "describePDFControl" );
+ }
+ return Descriptor;
+ }
+
+
+} // namespace toolkitform
+
+
+/* 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 0000000000..95c42c5df7
--- /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.GetAlphaMask().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"_ostr,
+ 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"_ostr,
+ 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 0000000000..fb4ff37dfa
--- /dev/null
+++ b/vcl/source/gdi/gdimtf.cxx
@@ -0,0 +1,2357 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <comphelper/diagnose_ex.hxx>
+#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 ),
+ m_bSVG ( 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 ),
+ m_bSVG ( rMtf.m_bSVG )
+{
+ 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;
+ m_bSVG = rMtf.m_bSVG;
+
+ 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 );
+ rMtf.setSVG( rMtf.getSVG() || m_bSVG );
+
+ 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::ScaleActions(double const fScaleX, double const 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 );
+ }
+}
+
+void GDIMetaFile::Scale( double fScaleX, double fScaleY )
+{
+ ScaleActions(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( std::move(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::WITHOUT_ALPHA);
+ 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( tools::Polygon(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->GetKashidaArray(), 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::Polygon(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::Polygon(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::Polygon(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::Polygon(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,
+ tools::PolyPolygon(ImplGetRotatedPolygon( tools::Polygon(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"_ostr, 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"_ostr, 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::Polygon(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::Polygon(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( tools::Polygon(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( std::move(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.Normalize();
+
+ 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(), pAct->GetKashidaArray() );
+ 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( ColorAlpha, 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.GetAlphaMask() );
+ 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;
+ aMtf.m_bSVG = m_bSVG;
+
+ 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( std::move(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, std::move(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(), std::move(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(), std::move(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 * std::clamp( nContrastPercent, short(0), short(100) ) );
+ else
+ fM = ( 128.0 + 1.27 * std::clamp( nContrastPercent, short(-100), short(0) ) ) / 128.0;
+
+ if(!msoBrightness)
+ // total offset = luminance offset + contrast offset
+ fOff = std::clamp( nLuminancePercent, short(-100), short(100) ) * 2.55 + 128.0 - fM * 128.0;
+ else
+ fOff = std::clamp( nLuminancePercent, short(-100), short(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 ] = FRound(std::clamp( nX * fM + fROff, 0.0, 255.0 ));
+ aColParam.pMapG[ nX ] = FRound(std::clamp( nX * fM + fGOff, 0.0, 255.0 ));
+ aColParam.pMapB[ nX ] = FRound(std::clamp( nX * fM + fBOff, 0.0, 255.0 ));
+ }
+ else
+ {
+ aColParam.pMapR[ nX ] = FRound(std::clamp( (nX+fROff/2-128) * fM + 128 + fROff/2, 0.0, 255.0 ));
+ aColParam.pMapG[ nX ] = FRound(std::clamp( (nX+fGOff/2-128) * fM + 128 + fGOff/2, 0.0, 255.0 ));
+ aColParam.pMapB[ nX ] = FRound(std::clamp( (nX+fBOff/2-128) * fM + 128 + fBOff/2, 0.0, 255.0 ));
+ }
+ 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/gfxlink.cxx b/vcl/source/gdi/gfxlink.cxx
new file mode 100644
index 0000000000..c6ca4678b0
--- /dev/null
+++ b/vcl/source/gdi/gfxlink.cxx
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <utility>
+#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(BinaryDataContainer aDataConainer, GfxLinkType nType)
+ : meType(nType)
+ , mnUserId(0)
+ , maDataContainer(std::move(aDataConainer))
+ , 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 0000000000..75a53a2a93
--- /dev/null
+++ b/vcl/source/gdi/gradient.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 <tools/gen.hxx>
+
+#include <vcl/gradient.hxx>
+#include <vcl/metaact.hxx>
+#include <cmath>
+
+class Gradient::Impl
+{
+public:
+ css::awt::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 (css::awt::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( css::awt::GradientStyle eStyle,
+ const Color& rStartColor, const Color& rEndColor )
+{
+ mpImplGradient->meStyle = eStyle;
+ mpImplGradient->maStartColor = rStartColor;
+ mpImplGradient->maEndColor = rEndColor;
+}
+
+Gradient::~Gradient() = default;
+
+
+css::awt::GradientStyle Gradient::GetStyle() const
+{
+ return mpImplGradient->meStyle;
+}
+
+void Gradient::SetStyle( css::awt::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() == css::awt::GradientStyle_LINEAR || GetStyle() == css::awt::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() == css::awt::GradientStyle_SQUARE || GetStyle() == css::awt::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() == css::awt::GradientStyle_RADIAL )
+ {
+ // Calculation of radii for circle
+ aSize.setWidth( static_cast<tools::Long>(0.5 + std::hypot(aSize.Width(), aSize.Height())) );
+ aSize.setHeight( aSize.Width() );
+ }
+ else if( GetStyle() == css::awt::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.Normalize();
+
+ // 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() == css::awt::GradientStyle_LINEAR || GetStyle() == css::awt::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() == css::awt::GradientStyle_LINEAR || GetStyle() == css::awt::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() == css::awt::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)
+ {
+ std::swap( nStartRed, nEndRed );
+ std::swap( nStartGreen, nEndGreen );
+ std::swap( nStartBlue, nEndBlue );
+ }
+
+ 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( std::move(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() != css::awt::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 = tools::Polygon(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() == css::awt::GradientStyle_RADIAL || GetStyle() == css::awt::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 0000000000..4c0efa56be
--- /dev/null
+++ b/vcl/source/gdi/graph.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 <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 ::unographic::Graphic* pUnoGraphic = dynamic_cast<::unographic::Graphic*>(rxGraphic.get());
+ const ::Graphic* pGraphic = pUnoGraphic ? &pUnoGraphic->GetGraphic() : nullptr;
+
+ 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 nRendererId,
+ OutputDevice* pFirstFrameOutDev)
+{
+ ImplTestRefCount();
+ mxImpGraphic->startAnimation(rOutDev, rDestPt, rDestSz, nRendererId, pFirstFrameOutDev);
+}
+
+void Graphic::StopAnimation( const OutputDevice* pOutDev, tools::Long nRendererId )
+{
+ ImplTestRefCount();
+ mxImpGraphic->stopAnimation( pOutDev, nRendererId );
+}
+
+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;
+}
+
+/* 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 0000000000..8308da0415
--- /dev/null
+++ b/vcl/source/gdi/graphictools.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 <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <utility>
+#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( tools::Polygon aPath,
+ tools::PolyPolygon aStartArrow,
+ tools::PolyPolygon aEndArrow,
+ double fTransparency,
+ double fStrokeWidth,
+ CapType aCap,
+ JoinType aJoin,
+ double fMiterLimit,
+ DashArray&& rDashArray ) :
+ maPath(std::move( aPath )),
+ maStartArrow(std::move( aStartArrow )),
+ maEndArrow(std::move( aEndArrow )),
+ 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( tools::PolyPolygon aPath,
+ 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,
+ Graphic aFillGraphic ) :
+ maPath(std::move( aPath )),
+ 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(std::move( 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 0000000000..e097f2f36d
--- /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/impglyphitem.cxx b/vcl/source/gdi/impglyphitem.cxx
new file mode 100644
index 0000000000..ca8016a192
--- /dev/null
+++ b/vcl/source/gdi/impglyphitem.cxx
@@ -0,0 +1,555 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <utility>
+#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 <o3tl/string_view.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,
+ // or in the middle of a cluster
+ // (for non-RTL charPos is always the start of a multi-character glyph).
+ if (rtl && (pos->charPos() + pos->charCount() > beginPos + 1 || pos->IsInCluster()))
+ 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().
+ basegfx::B2DPoint zeroPoint
+ = pos->linearPos() - basegfx::B2DPoint(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::isLayoutEquivalent(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].isLayoutEquivalent((*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(std::u16string_view 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(o3tl::iterateCodePoints(text, &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, std::u16string_view 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));
+ 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->isLayoutEquivalent(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_NON_APPLICATION_FONT_USE=abort 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 const bool bAbortOnFontSubstitute = [] {
+ const char* pEnv = getenv("SAL_NON_APPLICATION_FONT_USE");
+ return pEnv && strcmp(pEnv, "abort") == 0;
+ }();
+ 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 = 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 (!mbCacheGlyphsWhenDoingFallbackFonts && glyphs.Impl(1) != nullptr)
+ {
+ mLastTemporaryGlyphs = std::move(glyphs);
+ mLastTemporaryKey.reset();
+ return &mLastTemporaryGlyphs;
+ }
+ mCachedGlyphs.insert(std::make_pair(key, std::move(glyphs)));
+ 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;
+}
+
+void SalLayoutGlyphsCache::SetCacheGlyphsWhenDoingFallbackFonts(bool bOK)
+{
+ mbCacheGlyphsWhenDoingFallbackFonts = bOK;
+ if (!bOK)
+ clear();
+}
+
+SalLayoutGlyphsCache::CachedGlyphsKey::CachedGlyphsKey(
+ const VclPtr<const OutputDevice>& outputDevice, OUString t, sal_Int32 i, sal_Int32 l,
+ tools::Long w)
+ : text(std::move(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())
+ , digitLanguage(outputDevice->GetDigitLanguage())
+ , layoutMode(outputDevice->GetLayoutMode())
+ , rtl(outputDevice->IsRTLEnabled())
+{
+ const LogicalFontInstance* fi = outputDevice->GetFontInstance();
+ fi->GetScale(&fontScaleX, &fontScaleY);
+
+ const vcl::font::FontSelectPattern& rFSD = fi->GetFontSelectPattern();
+ disabledLigatures = rFSD.GetPitch() == PITCH_FIXED;
+ artificialItalic = fi->NeedsArtificialItalic();
+ artificialBold = fi->NeedsArtificialBold();
+
+ 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, disabledLigatures);
+ 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
+ && disabledLigatures == other.disabledLigatures
+ && 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 0000000000..ac36d2c72b
--- /dev/null
+++ b/vcl/source/gdi/impgraph.cxx
@@ -0,0 +1,1775 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/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 <utility>
+#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
+{
+private:
+ utl::TempFileFast maTempFile;
+ OUString maOriginURL;
+
+public:
+ ImpSwapFile(OUString aOriginURL)
+ : maOriginURL(std::move(aOriginURL))
+ {
+ }
+
+ SvStream* getStream() { return maTempFile.GetStream(StreamMode::READWRITE); }
+ OUString const & getOriginURL() const { return maOriginURL; }
+};
+
+SvStream* ImpGraphic::getSwapFileStream() const
+{
+ if (mpSwapFile)
+ return mpSwapFile->getStream();
+ return nullptr;
+}
+
+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> xGfxLink, sal_Int32 nPageIndex)
+ : mpGfxLink(std::move(xGfxLink))
+ , 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 aGraphicExternalLink) :
+ meType ( GraphicType::Default ),
+ mnSizeBytes ( 0 ),
+ mbSwapOut ( false ),
+ mbDummyContext ( false ),
+ maGraphicExternalLink(std::move(aGraphicExternalLink)),
+ 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& rOther ) const
+{
+ if( this == &rOther )
+ return true;
+
+ if (mbPrepared && rOther.mbPrepared)
+ return (*mpGfxLink == *rOther.mpGfxLink);
+
+ if (!isAvailable() || !rOther.isAvailable())
+ return false;
+
+ if ( meType != rOther.meType )
+ return false;
+
+ bool bRet = false;
+ switch( meType )
+ {
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ return true;
+
+ case GraphicType::GdiMetafile:
+ return ( rOther.maMetaFile == maMetaFile );
+
+ case GraphicType::Bitmap:
+ {
+ if(maVectorGraphicData)
+ {
+ if(maVectorGraphicData == rOther.maVectorGraphicData)
+ {
+ // equal instances
+ bRet = true;
+ }
+ else if(rOther.maVectorGraphicData)
+ {
+ // equal content
+ bRet = (*maVectorGraphicData) == (*rOther.maVectorGraphicData);
+ }
+ }
+ else if( mpAnimation )
+ {
+ if( rOther.mpAnimation && ( *rOther.mpAnimation == *mpAnimation ) )
+ bRet = true;
+ }
+ else if( !rOther.mpAnimation && ( rOther.maBitmapEx == maBitmapEx ) )
+ {
+ bRet = true;
+ }
+ }
+ 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();
+
+#ifdef MACOSX
+ // tdf#157680 scale down estimated size of embedded PDF
+ // For some unknown reason, the embedded PDF sizes
+ // are 20x larger than expected. This only occurs on
+ // macOS so possibly there is some special conversion
+ // from MapUnit::MapPoint to MapUnit::MapTwip elsewhere
+ // in the code.
+ if (maVectorGraphicData->getType() == VectorGraphicDataType::Pdf)
+ aSize = Size(basegfx::fround(rRange.getWidth() / 20.0f), basegfx::fround(rRange.getHeight() / 20.0f));
+ else
+#endif
+ 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 (mpAnimation)
+ {
+ 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 (mpAnimation)
+ {
+ 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 nRendererId,
+ OutputDevice* pFirstFrameOutDev )
+{
+ ensureAvailable();
+
+ if( isSupportedGraphic() && !isSwappedOut() && mpAnimation )
+ mpAnimation->Start(rOutDev, rDestPt, rDestSize, nRendererId, pFirstFrameOutDev);
+}
+
+void ImpGraphic::stopAnimation( const OutputDevice* pOutDev, tools::Long nRendererId )
+{
+ ensureAvailable();
+
+ if( isSupportedGraphic() && !isSwappedOut() && mpAnimation )
+ mpAnimation->Stop( pOutDev, nRendererId );
+}
+
+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());
+ maVectorGraphicData->getBinaryDataContainer().writeToStream(rStream);
+ }
+ else if (mpAnimation)
+ {
+ 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;
+ }
+
+ if (mpGfxLink)
+ mpGfxLink->getDataContainer().swapOut();
+
+ 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();
+
+ mpGfxLink->getDataContainer().swapOut();
+
+ // mark as swapped out
+ mbSwapOut = true;
+
+ bResult = true;
+ }
+ else
+ {
+ // Create a swap file
+ auto pSwapFile = o3tl::make_shared<ImpSwapFile>(getOriginURL());
+
+ // Open a stream to write the swap file to
+ {
+ SvStream* pOutputStream = pSwapFile->getStream();
+
+ if (!pOutputStream)
+ return false;
+
+ // Write to stream
+ pOutputStream->SetVersion(SOFFICE_FILEFORMAT_50);
+ pOutputStream->SetCompressMode(SvStreamCompressFlags::NATIVE);
+ pOutputStream->SetBufferSize(GRAPHIC_STREAMBUFSIZE);
+
+ if (!pOutputStream->GetError() && swapOutContent(*pOutputStream))
+ {
+ pOutputStream->FlushBuffer();
+ bResult = !pOutputStream->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::dumpState(rtl::OStringBuffer &rState)
+{
+ if (meType == GraphicType::NONE && mnSizeBytes == 0)
+ return; // uninteresting.
+
+ rState.append("\n\t");
+
+ if (mbSwapOut)
+ rState.append("swapped\t");
+ else
+ rState.append("loaded\t");
+
+ rState.append(static_cast<sal_Int32>(meType));
+ rState.append("\tsize:\t");
+ rState.append(static_cast<sal_Int64>(mnSizeBytes));
+ rState.append("\tgfxl:\t");
+ rState.append(static_cast<sal_Int64>(mpGfxLink ? mpGfxLink->getSizeBytes() : -1));
+ rState.append("\t");
+ rState.append(static_cast<sal_Int32>(maSwapInfo.maSizePixel.Width()));
+ rState.append("x");
+ rState.append(static_cast<sal_Int32>(maSwapInfo.maSizePixel.Height()));
+ rState.append("\t");
+ rState.append(static_cast<sal_Int32>(maExPrefSize.Width()));
+ rState.append("x");
+ rState.append(static_cast<sal_Int32>(maExPrefSize.Height()));
+}
+
+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
+ {
+ SvStream* pStream = nullptr;
+
+ if (mpSwapFile)
+ pStream = mpSwapFile->getStream();
+
+ if (pStream)
+ {
+ pStream->SetVersion(SOFFICE_FILEFORMAT_50);
+ pStream->SetCompressMode(SvStreamCompressFlags::NATIVE);
+ pStream->SetBufferSize(GRAPHIC_STREAMBUFSIZE);
+ pStream->Seek(STREAM_SEEK_TO_BEGIN);
+
+ bReturn = swapInFromStream(*pStream);
+
+ restoreFromSwapInfo();
+
+ 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)
+ {
+ BinaryDataContainer aDataContainer(rStream, nVectorGraphicDataSize);
+
+ 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 0000000000..19fa712ea6
--- /dev/null
+++ b/vcl/source/gdi/jobset.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 <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 {
+
+// used only for compatibility with older versions,
+// printer/driver name are truncated if too long,
+// device name and port name are completely unused
+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;
+ 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() ),
+ mpDriverData(),
+ mbPapersizeFromSetup( rJobSetup.GetPapersizeFromSetup() ),
+ meSetupMode( rJobSetup.GetPrinterSetupMode() ),
+ maValueMap( rJobSetup.GetValueMap() )
+ {
+ if ( rJobSetup.GetDriverData() )
+ {
+ mpDriverData.reset( new sal_uInt8[mnDriverDataLen] );
+ memcpy( mpDriverData.get(), rJobSetup.GetDriverData(), mnDriverDataLen );
+ }
+ else
+ mpDriverData = nullptr;
+}
+
+ImplJobSetup::~ImplJobSetup()
+{
+}
+
+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::SetDriverData(std::unique_ptr<sal_uInt8[]> pDriverData, sal_uInt32 nDriverDataLen)
+{
+ mpDriverData = std::move(pDriverData);
+ mnDriverDataLen = nDriverDataLen;
+}
+
+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.get(),
+ rImplJobSetup.mpDriverData.get(),
+ std::min(mnDriverDataLen, rImplJobSetup.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();
+
+ // use (potentially truncated) printer/driver name from ImplOldJobSetupData as fallback,
+ // gets overwritten below if PRINTER_NAME/DRIVER_NAME keys are set
+ pData->cPrinterName[std::size(pData->cPrinterName) - 1] = 0;
+ rJobData.SetPrinterName( OStringToOUString(pData->cPrinterName, aStreamEncoding) );
+ pData->cDriverName[std::size(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 ) );
+ const sal_uInt32 nDriverDataLen = 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 ( nDriverDataLen )
+ {
+ const char* pDriverData = reinterpret_cast<const char*>(pOldJobData) + nOldJobDataSize;
+ const char* pDriverDataEnd = pDriverData + nDriverDataLen;
+ if (pDriverDataEnd > pTempBuf.get() + nRead)
+ {
+ SAL_WARN("vcl", "corrupted job setup");
+ }
+ else
+ {
+ auto pNewDriverData = std::make_unique<sal_uInt8[]>( nDriverDataLen );
+ memcpy( pNewDriverData.get(), pDriverData, nDriverDataLen );
+ rJobData.SetDriverData( std::move(pNewDriverData), nDriverDataLen );
+ }
+ }
+ 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 if (aKey == u"PRINTER_NAME")
+ rJobData.SetPrinterName(aValue);
+ else if (aKey == u"DRIVER_NAME")
+ rJobData.SetDriver(aValue);
+ 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(), std::size(aOldData.cPrinterName) - 1);
+ OString aDriverByteName(OUStringToOString(rJobData.GetDriver(), RTL_TEXTENCODING_UTF8));
+ strncpy(aOldData.cDriverName, aDriverByteName.getStr(), std::size(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;
+ }
+ // write printer, driver name in full, the ones in aOldData may be truncated
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, u"PRINTER_NAME", RTL_TEXTENCODING_UTF8);
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, rJobData.GetPrinterName(), RTL_TEXTENCODING_UTF8);
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, u"DRIVER_NAME", RTL_TEXTENCODING_UTF8);
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, rJobData.GetDriver(), RTL_TEXTENCODING_UTF8);
+
+ 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 0000000000..490b1d920d
--- /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 0000000000..45c7a29a81
--- /dev/null
+++ b/vcl/source/gdi/mapmod.cxx
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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
+{
+ MapUnit meUnit;
+ bool mbSimple;
+ Point maOrigin;
+ // 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;
+
+ ImplMapMode();
+ ImplMapMode(MapUnit eMapUnit);
+ 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(MapUnit eMapUnit) :
+ maOrigin( 0, 0 ),
+ maScaleX( 1, 1 ),
+ maScaleY( 1, 1 )
+{
+ meUnit = eMapUnit;
+ 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::ImplType GetUnitDefault(MapUnit mapUnit)
+ {
+ static MapMode::ImplType defaults[]
+ {
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::Map100thMM) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::Map10thMM) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapMM) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapCM) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::Map1000thInch) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::Map100thInch) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::Map10thInch) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapInch) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapPoint) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapTwip) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapPixel) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapSysFont) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapAppFont) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapRelative) ),
+ };
+ if (mapUnit <= MapUnit::MapRelative) {
+ return MapMode::ImplType(defaults[static_cast<int>(mapUnit)]); // [-loplugin:redundantfcast] false positive
+ }
+ // sometimes the SvmReader stuff will generate a bogus mapunit value
+ return MapMode::ImplType(MapMode::ImplMapMode(mapUnit));
+ }
+}
+
+MapMode::MapMode() : mpImplMapMode(GetGlobalDefault())
+{
+}
+
+MapMode::MapMode( const MapMode& ) = default;
+
+MapMode::MapMode( MapUnit eUnit ) : mpImplMapMode(GetUnitDefault(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->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->mbSimple = false;
+}
+
+void MapMode::SetScaleY( const Fraction& rScaleY )
+{
+ mpImplMapMode->maScaleY = rScaleY;
+ 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 0000000000..2e1f3e4d84
--- /dev/null
+++ b/vcl/source/gdi/metaact.cxx
@@ -0,0 +1,2208 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <utility>
+#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.Normalize();
+}
+
+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,
+ LineInfo aLineInfo ) :
+ MetaAction ( MetaActionType::LINE ),
+ maLineInfo (std::move( aLineInfo )),
+ 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( tools::Polygon aPoly ) :
+ MetaAction ( MetaActionType::POLYLINE ),
+ maPoly (std::move( aPoly ))
+{}
+
+MetaPolyLineAction::MetaPolyLineAction( tools::Polygon aPoly, LineInfo aLineInfo ) :
+ MetaAction ( MetaActionType::POLYLINE ),
+ maLineInfo (std::move( aLineInfo )),
+ maPoly (std::move( aPoly ))
+{}
+
+static bool AllowDim(tools::Long nDim)
+{
+ static bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ if (bFuzzing)
+ {
+ if (nDim > 0x20000000 || nDim < -0x20000000)
+ {
+ SAL_WARN("vcl", "skipping huge dimension: " << nDim);
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool AllowPoint(const Point& rPoint)
+{
+ return AllowDim(rPoint.X()) && AllowDim(rPoint.Y());
+}
+
+static bool AllowRect(const tools::Rectangle& rRect)
+{
+ return AllowPoint(rRect.TopLeft()) && AllowPoint(rRect.BottomRight());
+}
+
+void MetaPolyLineAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowRect(pOut->LogicToPixel(maPoly.GetBoundRect())))
+ return;
+
+ 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( tools::Polygon aPoly ) :
+ MetaAction ( MetaActionType::POLYGON ),
+ maPoly (std::move( aPoly ))
+{}
+
+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( tools::PolyPolygon aPolyPoly ) :
+ MetaAction ( MetaActionType::POLYPOLYGON ),
+ maPolyPoly (std::move( aPolyPoly ))
+{}
+
+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, OUString aStr,
+ sal_Int32 nIndex, sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::TEXT ),
+ maPt ( rPt ),
+ maStr (std::move( aStr )),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{}
+
+void MetaTextAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowDim(pOut->LogicToPixel(maPt).Y()))
+ return;
+
+ 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 ),
+ maKashidaAry( rAction.maKashidaAry ),
+ mnIndex ( rAction.mnIndex ),
+ mnLen ( rAction.mnLen )
+{
+}
+
+MetaTextArrayAction::MetaTextArrayAction( const Point& rStartPt,
+ OUString aStr,
+ KernArray aDXAry,
+ std::vector<sal_Bool> aKashidaAry,
+ sal_Int32 nIndex,
+ sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::TEXTARRAY ),
+ maStartPt ( rStartPt ),
+ maStr (std::move( aStr )),
+ maDXAry (std::move( aDXAry )),
+ maKashidaAry(std::move( aKashidaAry )),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{
+}
+
+MetaTextArrayAction::MetaTextArrayAction( const Point& rStartPt,
+ OUString aStr,
+ KernArraySpan pDXAry,
+ std::span<const sal_Bool> pKashidaAry,
+ sal_Int32 nIndex,
+ sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::TEXTARRAY ),
+ maStartPt ( rStartPt ),
+ maStr (std::move( aStr )),
+ maKashidaAry( pKashidaAry.begin(), pKashidaAry.end() ),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{
+ maDXAry.assign(pDXAry);
+}
+
+MetaTextArrayAction::~MetaTextArrayAction()
+{
+}
+
+void MetaTextArrayAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawTextArray( maStartPt, maStr, maDXAry, maKashidaAry, 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.set(i, FRound(maDXAry[i] * fabs(fScaleX)));
+ }
+}
+
+void MetaTextArrayAction::SetDXArray(KernArray aArray)
+{
+ maDXAry = std::move(aArray);
+}
+
+void MetaTextArrayAction::SetKashidaArray(std::vector<sal_Bool> aArray)
+{
+ maKashidaAry = std::move(aArray);
+}
+
+MetaStretchTextAction::MetaStretchTextAction() :
+ MetaAction ( MetaActionType::STRETCHTEXT ),
+ mnWidth ( 0 ),
+ mnIndex ( 0 ),
+ mnLen ( 0 )
+{}
+
+MetaStretchTextAction::~MetaStretchTextAction()
+{}
+
+MetaStretchTextAction::MetaStretchTextAction( const Point& rPt, sal_uInt32 nWidth,
+ OUString aStr,
+ sal_Int32 nIndex, sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::STRETCHTEXT ),
+ maPt ( rPt ),
+ maStr (std::move( aStr )),
+ mnWidth ( nWidth ),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{}
+
+void MetaStretchTextAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowDim(pOut->LogicToPixel(maPt).Y()))
+ return;
+
+ 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,
+ OUString aStr, DrawTextFlags nStyle ) :
+ MetaAction ( MetaActionType::TEXTRECT ),
+ maRect ( rRect ),
+ maStr (std::move( aStr )),
+ mnStyle ( nStyle )
+{}
+
+void MetaTextRectAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowRect(pOut->LogicToPixel(maRect)))
+ return;
+
+ 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 )
+{
+ if (mnWidth < 0)
+ {
+ SAL_WARN("vcl", "skipping line with negative width: " << mnWidth);
+ return;
+ }
+ 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)
+{
+ static bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ if (bFuzzing)
+ {
+ constexpr int nMaxScaleWhenFuzzing = 128;
+
+ auto nSourceHeight = rSource.Height();
+ auto nDestHeight = rDest.Height();
+ if (nSourceHeight && std::abs(nDestHeight / nSourceHeight) > nMaxScaleWhenFuzzing)
+ {
+ SAL_WARN("vcl", "skipping large vertical scaling: " << nSourceHeight << " to " << nDestHeight);
+ return false;
+ }
+
+ if (nDestHeight && std::abs(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 && std::abs(nDestWidth / nSourceWidth) > nMaxScaleWhenFuzzing)
+ {
+ SAL_WARN("vcl", "skipping large horizontal scaling: " << nSourceWidth << " to " << nDestWidth);
+ return false;
+ }
+
+ if (nDestWidth && std::abs(nSourceWidth / nDestWidth) > nMaxScaleWhenFuzzing)
+ {
+ SAL_WARN("vcl", "skipping large horizontal scaling: " << nSourceWidth << " to " << nDestWidth);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void MetaBmpScaleAction::Execute( OutputDevice* pOut )
+{
+ Size aPixelSize(pOut->LogicToPixel(maSz));
+ if (!AllowRect(tools::Rectangle(pOut->LogicToPixel(maPt), aPixelSize)) ||
+ !AllowScale(maBmp.GetSizePixel(), aPixelSize))
+ {
+ 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 )
+{
+ if (!AllowRect(pOut->LogicToPixel(tools::Rectangle(maDstPt, maDstSz))))
+ return;
+
+ 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(), pOut->LogicToPixel(maSz)))
+ return;
+ if (!AllowRect(pOut->LogicToPixel(tools::Rectangle(maPt, 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 )
+{
+ if (!AllowRect(pOut->LogicToPixel(tools::Rectangle(maDstPt, maDstSz))))
+ return;
+
+ 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 )
+{
+ if (!AllowRect(pOut->LogicToPixel(tools::Rectangle(maPt, maSz))))
+ return;
+ 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 )
+{
+ if (!AllowRect(pOut->LogicToPixel(tools::Rectangle(maDstPt, maDstSz))))
+ return;
+
+ 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, Gradient aGradient ) :
+ MetaAction ( MetaActionType::GRADIENT ),
+ maRect ( rRect ),
+ maGradient (std::move( aGradient ))
+{}
+
+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( tools::PolyPolygon aPolyPoly, Gradient aGradient ) :
+ MetaAction ( MetaActionType::GRADIENTEX ),
+ maPolyPoly (std::move( aPolyPoly )),
+ maGradient (std::move( aGradient ))
+{}
+
+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( tools::PolyPolygon aPolyPoly, const Hatch& rHatch ) :
+ MetaAction ( MetaActionType::HATCH ),
+ maPolyPoly (std::move( aPolyPoly )),
+ maHatch ( rHatch )
+{}
+
+void MetaHatchAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowRect(pOut->LogicToPixel(maPolyPoly.GetBoundRect())))
+ return;
+ if (!AllowDim(pOut->LogicToPixel(Point(maHatch.GetDistance(), 0)).X()))
+ return;
+
+ 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( vcl::Region aRegion, bool bClip ) :
+ MetaAction ( MetaActionType::CLIPREGION ),
+ maRegion (std::move( aRegion )),
+ 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( vcl::Region aRegion ) :
+ MetaAction ( MetaActionType::ISECTREGIONCLIPREGION ),
+ maRegion (std::move( aRegion ))
+{
+}
+
+void MetaISectRegionClipRegionAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowRect(pOut->LogicToPixel(maRegion.GetBoundRect())))
+ return;
+
+ 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 )
+{
+ if (!AllowPoint(pOut->LogicToPixel(Point(mnHorzMove, mnVertMove))))
+ return;
+ 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( vcl::Font aFont ) :
+ MetaAction ( MetaActionType::FONT ),
+ maFont (std::move( aFont ))
+{
+ // #96876: because RTL_TEXTENCODING_SYMBOL is often set at the OpenSymbol 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 ( IsOpenSymbol( maFont.GetFamilyName() )
+ && ( maFont.GetCharSet() != RTL_TEXTENCODING_UNICODE ) )
+ {
+ SAL_WARN_IF(maFont.GetCharSet() == RTL_TEXTENCODING_SYMBOL, "vcl", "OpenSymbol should not have charset of RTL_TEXTENCODING_SYMBOL in new documents");
+ 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( tools::PolyPolygon aPolyPoly, sal_uInt16 nTransPercent ) :
+ MetaAction ( MetaActionType::Transparent ),
+ maPolyPoly (std::move( aPolyPoly )),
+ 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, Gradient aGradient ) :
+ MetaAction ( MetaActionType::FLOATTRANSPARENT ),
+ maMtf ( rMtf ),
+ maPoint ( rPos ),
+ maSize ( rSize ),
+ maGradient (std::move( aGradient ))
+{}
+
+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();
+}
+
+void MetaFloatTransparentAction::addSVGTransparencyColorStops(const basegfx::BColorStops& rSVGTransparencyColorStops)
+{
+ maSVGTransparencyColorStops = rSVGTransparencyColorStops;
+}
+
+MetaEPSAction::MetaEPSAction() :
+ MetaAction(MetaActionType::EPS)
+{}
+
+MetaEPSAction::~MetaEPSAction()
+{}
+
+MetaEPSAction::MetaEPSAction( const Point& rPoint, const Size& rSize,
+ GfxLink aGfxLink, const GDIMetaFile& rSubst ) :
+ MetaAction ( MetaActionType::EPS ),
+ maGfxLink (std::move( aGfxLink )),
+ 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( OString aComment, sal_Int32 nValue, const sal_uInt8* pData, sal_uInt32 nDataSize ) :
+ MetaAction ( MetaActionType::COMMENT ),
+ maComment (std::move( aComment )),
+ 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 0000000000..e6b1adc80e
--- /dev/null
+++ b/vcl/source/gdi/mtfxmldump.cxx
@@ -0,0 +1,1568 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/bitmap.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+
+#include <rtl/string.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <comphelper/hash.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");
+ if (nFlags & vcl::PushFlags::RTLENABLED)
+ aStrings.emplace_back("PushRTLEnabled");
+
+ 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");
+
+ 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"_ostr;
+ case MetaActionType::PIXEL: return "pixel"_ostr;
+ case MetaActionType::POINT: return "point"_ostr;
+ case MetaActionType::LINE: return "line"_ostr;
+ case MetaActionType::RECT: return "rect"_ostr;
+ case MetaActionType::ROUNDRECT: return "roundrect"_ostr;
+ case MetaActionType::ELLIPSE: return "ellipse"_ostr;
+ case MetaActionType::ARC: return "arc"_ostr;
+ case MetaActionType::PIE: return "pie"_ostr;
+ case MetaActionType::CHORD: return "chord"_ostr;
+ case MetaActionType::POLYLINE: return "polyline"_ostr;
+ case MetaActionType::POLYGON: return "polygon"_ostr;
+ case MetaActionType::POLYPOLYGON: return "polypolygon"_ostr;
+ case MetaActionType::TEXT: return "text"_ostr;
+ case MetaActionType::TEXTARRAY: return "textarray"_ostr;
+ case MetaActionType::STRETCHTEXT: return "stretchtext"_ostr;
+ case MetaActionType::TEXTRECT: return "textrect"_ostr;
+ case MetaActionType::TEXTLINE: return "textline"_ostr;
+ case MetaActionType::BMP: return "bmp"_ostr;
+ case MetaActionType::BMPSCALE: return "bmpscale"_ostr;
+ case MetaActionType::BMPSCALEPART: return "bmpscalepart"_ostr;
+ case MetaActionType::BMPEX: return "bmpex"_ostr;
+ case MetaActionType::BMPEXSCALE: return "bmpexscale"_ostr;
+ case MetaActionType::BMPEXSCALEPART: return "bmpexscalepart"_ostr;
+ case MetaActionType::MASK: return "mask"_ostr;
+ case MetaActionType::MASKSCALE: return "maskscale"_ostr;
+ case MetaActionType::MASKSCALEPART: return "maskscalepart"_ostr;
+ case MetaActionType::GRADIENT: return "gradient"_ostr;
+ case MetaActionType::GRADIENTEX: return "gradientex"_ostr;
+ case MetaActionType::HATCH: return "hatch"_ostr;
+ case MetaActionType::WALLPAPER: return "wallpaper"_ostr;
+ case MetaActionType::CLIPREGION: return "clipregion"_ostr;
+ case MetaActionType::ISECTRECTCLIPREGION: return "sectrectclipregion"_ostr;
+ case MetaActionType::ISECTREGIONCLIPREGION: return "sectregionclipregion"_ostr;
+ case MetaActionType::MOVECLIPREGION: return "moveclipregion"_ostr;
+ case MetaActionType::LINECOLOR: return "linecolor"_ostr;
+ case MetaActionType::FILLCOLOR: return "fillcolor"_ostr;
+ case MetaActionType::TEXTCOLOR: return "textcolor"_ostr;
+ case MetaActionType::TEXTFILLCOLOR: return "textfillcolor"_ostr;
+ case MetaActionType::TEXTLINECOLOR: return "textlinecolor"_ostr;
+ case MetaActionType::OVERLINECOLOR: return "overlinecolor"_ostr;
+ case MetaActionType::TEXTALIGN: return "textalign"_ostr;
+ case MetaActionType::MAPMODE: return "mapmode"_ostr;
+ case MetaActionType::FONT: return "font"_ostr;
+ case MetaActionType::PUSH: return "push"_ostr;
+ case MetaActionType::POP: return "pop"_ostr;
+ case MetaActionType::RASTEROP: return "rasterop"_ostr;
+ case MetaActionType::Transparent: return "transparent"_ostr;
+ case MetaActionType::FLOATTRANSPARENT: return "floattransparent"_ostr;
+ case MetaActionType::EPS: return "eps"_ostr;
+ case MetaActionType::REFPOINT: return "refpoint"_ostr;
+ case MetaActionType::COMMENT: return "comment"_ostr;
+ case MetaActionType::LAYOUTMODE: return "layoutmode"_ostr;
+ case MetaActionType::TEXTLANGUAGE: return "textlanguage"_ostr;
+ }
+ return ""_ostr;
+}
+
+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());
+}
+
+OUString convertGradientStyleToOUString(css::awt::GradientStyle eStyle)
+{
+ switch (eStyle)
+ {
+ case css::awt::GradientStyle_LINEAR: return "Linear";
+ case css::awt::GradientStyle_AXIAL: return "Axial";
+ case css::awt::GradientStyle_RADIAL: return "Radial";
+ case css::awt::GradientStyle_ELLIPTICAL: return "Elliptical";
+ case css::awt::GradientStyle_SQUARE: return "Square";
+ case css::awt::GradientStyle_RECT: return "Rect";
+ case css::awt::GradientStyle::GradientStyle_MAKE_FIXED_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());
+}
+
+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::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 nFlags)
+{
+ if (nFlags == vcl::text::ComplexTextLayoutFlags::Default)
+ return "Default";
+
+ std::vector<OUString> aStrings;
+
+ if (nFlags & vcl::text::ComplexTextLayoutFlags::BiDiRtl)
+ aStrings.emplace_back("BiDiRtl");
+ if (nFlags & vcl::text::ComplexTextLayoutFlags::BiDiStrong)
+ aStrings.emplace_back("BiDiStrong");
+ if (nFlags & vcl::text::ComplexTextLayoutFlags::TextOriginLeft)
+ aStrings.emplace_back("TextOriginLeft");
+ if (nFlags & vcl::text::ComplexTextLayoutFlags::TextOriginRight)
+ aStrings.emplace_back("TextOriginRight");
+
+ 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 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());
+}
+
+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());
+}
+
+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", "empty"_ostr);
+ else
+ rWriter.attribute("right", rRectangle.Right());
+ if (rRectangle.IsHeightEmpty())
+ rWriter.attribute("bottom", "empty"_ostr);
+ 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", convertGradientStyleToOUString(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());
+}
+
+OString toHexString(const std::vector<unsigned char>& a)
+{
+ std::stringstream aStrm;
+ for (auto& i : a)
+ {
+ aStrm << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(i);
+ }
+
+ return OString(aStrm.str());
+}
+
+void writeBitmapContentChecksum(tools::XmlWriter& rWriter, Bitmap const& rBitmap)
+{
+ Bitmap aBitmap(rBitmap);
+
+ comphelper::Hash aHashEngine(comphelper::HashType::SHA1);
+ BitmapScopedReadAccess pReadAccess(aBitmap);
+ assert(pReadAccess);
+
+ for (tools::Long y = 0 ; y < pReadAccess->Height() ; ++y)
+ {
+ for (tools::Long x = 0 ; x < pReadAccess->Width() ; ++x)
+ {
+ BitmapColor aColor = pReadAccess->GetColor(y, x);
+ sal_uInt8 r = aColor.GetRed();
+ sal_uInt8 g = aColor.GetGreen();
+ sal_uInt8 b = aColor.GetBlue();
+ sal_uInt8 a = aColor.GetAlpha();
+ aHashEngine.update(&r, 1);
+ aHashEngine.update(&g, 1);
+ aHashEngine.update(&b, 1);
+ aHashEngine.update(&a, 1);
+ }
+ }
+ std::vector<unsigned char> aVector = aHashEngine.finalize();
+ rWriter.attribute("contentchecksum", toHexString(aVector));
+}
+
+void writeBitmap(tools::XmlWriter& rWriter, Bitmap const& rBitmap)
+{
+ writeBitmapContentChecksum(rWriter, rBitmap);
+ rWriter.attribute("bitmapwidth", rBitmap.GetSizePixel().Width());
+ rWriter.attribute("bitmapheight", rBitmap.GetSizePixel().Height());
+ rWriter.attribute("pixelformat", convertPixelFormatToString(rBitmap.getPixelFormat()));
+ rWriter.attribute("crc", hex32(rBitmap.GetChecksum()));
+}
+
+} // 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())
+ {
+ auto & rArray = pMetaTextArrayAction->GetDXArray();
+ rWriter.startElement("dxarray");
+ if (aIndex < o3tl::narrowing<sal_Int32>(rArray.size()))
+ rWriter.attribute("first", rArray[aIndex]);
+ if (aIndex + aLength - 1 < o3tl::narrowing<sal_Int32>(rArray.size()))
+ rWriter.attribute("last", rArray[aIndex + aLength - 1]);
+ OUStringBuffer sDxLengthString(std::max((aLength - aIndex) * 4, sal_Int32(0)));
+ for (sal_Int32 i = 0; i < aLength - aIndex; ++i)
+ {
+ sDxLengthString.append(OUString::number(rArray[aIndex + i]) + " ");
+ }
+ 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);
+ Bitmap aBitmap = pMeta->GetBitmap();
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ writeBitmap(rWriter, aBitmap);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ auto pMeta = static_cast<MetaBmpScaleAction*>(pAction);
+ Bitmap aBitmap = pMeta->GetBitmap();
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ writeSize(rWriter, pMeta->GetSize());
+ writeBitmap(rWriter, aBitmap);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ auto pMeta = static_cast<MetaBmpScalePartAction*>(pAction);
+ Bitmap aBitmap = pMeta->GetBitmap();
+ 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());
+ writeBitmap(rWriter, aBitmap);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ auto pMeta = static_cast<MetaBmpExAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ Bitmap aBitmap = pMeta->GetBitmapEx().GetBitmap();
+ rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx()));
+ writeBitmap(rWriter, aBitmap);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ auto pMeta = static_cast<MetaBmpExScaleAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ writeSize(rWriter, pMeta->GetSize());
+ Bitmap aBitmap = pMeta->GetBitmapEx().GetBitmap();
+ rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx()));
+ writeBitmap(rWriter, aBitmap);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ auto pMeta = static_cast<MetaBmpExScalePartAction*>(pAction);
+ Bitmap aBitmap = pMeta->GetBitmapEx().GetBitmap();
+ 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("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx()));
+ writeBitmap(rWriter, aBitmap);
+ 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", "not implemented in xml dump"_ostr);
+ 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 0000000000..599e1a543f
--- /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 0000000000..7f80bfdd03
--- /dev/null
+++ b/vcl/source/gdi/pdfbuildin_fonts.cxx
@@ -0,0 +1,760 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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);
+ // dubious, see BuildinFont::GetFontCharMap
+ aDFA.SetMicrosoftSymbolEncoded(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::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 0000000000..c2c838d5db
--- /dev/null
+++ b/vcl/source/gdi/pdfextoutdevdata.cxx
@@ -0,0 +1,919 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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>
+#include <variant>
+
+namespace vcl
+{
+namespace {
+
+struct CreateNamedDest {
+ OUString maDestName;
+ MapMode maParaMapMode;
+ PDFWriter::DestAreaType mnParaDestAreaType;
+ tools::Rectangle maParaRect;
+ sal_Int32 mnPage;
+};
+struct CreateDest {
+ MapMode maParaMapMode;
+ PDFWriter::DestAreaType mnParaDestAreaType;
+ tools::Rectangle maParaRect;
+ sal_Int32 mnPage;
+};
+struct CreateControlLink { sal_Int32 mnControlId; };
+struct CreateLink {
+ OUString maAltText;
+ MapMode maParaMapMode;
+ tools::Rectangle maParaRect;
+ sal_Int32 mnPage;
+};
+struct CreateScreen {
+ OUString maAltText;
+ OUString maMimeType;
+ MapMode maParaMapMode;
+ tools::Rectangle maParaRect;
+ sal_Int32 mnPage;
+};
+struct SetLinkDest {
+ sal_Int32 mnLinkId;
+ sal_Int32 mnDestId;
+};
+struct SetLinkURL {
+ OUString maLinkURL;
+ sal_Int32 mnLinkId;
+};
+struct SetScreenURL {
+ OUString maScreenURL;
+ sal_Int32 mnScreenId;
+};
+struct SetScreenStream {
+ OUString maScreenStream;
+ sal_Int32 mnScreenId;
+};
+struct RegisterDest { sal_Int32 mnDestId; };
+struct CreateOutlineItem {
+ OUString maText;
+ sal_Int32 mnParent;
+ sal_Int32 mnDestID;
+};
+struct CreateNote {
+ MapMode maParaMapMode;
+ PDFNote maParaPDFNote;
+ tools::Rectangle maParaRect;
+ sal_Int32 mnPage;
+};
+struct SetPageTransition {
+ PDFWriter::PageTransition maParaPageTransition;
+ sal_uInt32 mnMilliSec;
+ sal_Int32 mnPage;
+};
+struct EnsureStructureElement { sal_Int32 mnId; };
+struct InitStructureElement {
+ PDFWriter::StructElement mParaStructElement;
+ OUString maAlias;
+ sal_Int32 mnId;
+};
+struct BeginStructureElement { sal_Int32 mnId; };
+struct EndStructureElement{};
+struct SetCurrentStructureElement { sal_Int32 mnStructId; };
+struct SetStructureAttribute {
+ PDFWriter::StructAttribute mParaStructAttribute;
+ PDFWriter::StructAttributeValue mParaStructAttributeValue;
+};
+struct SetStructureAttributeNumerical { PDFWriter::StructAttribute mParaStructAttribute; sal_Int32 mnId; };
+struct SetStructureBoundingBox { tools::Rectangle mRect; };
+struct SetStructureAnnotIds {
+ ::std::vector<sal_Int32> annotIds;
+};
+struct SetActualText { OUString maText; };
+struct SetAlternateText { OUString maText; };
+struct CreateControl {
+ std::shared_ptr< PDFWriter::AnyWidget > mxControl;
+};
+struct BeginGroup {};
+struct EndGroupGfxLink {
+ Graphic maGraphic;
+ tools::Rectangle maOutputRect, maVisibleOutputRect;
+ sal_Int32 mnTransparency;
+};
+
+typedef std::variant<CreateNamedDest,
+ CreateDest,
+ CreateControlLink,
+ CreateLink,
+ CreateScreen,
+ SetLinkDest,
+ SetLinkURL,
+ SetScreenURL,
+ SetScreenStream,
+ RegisterDest,
+ CreateOutlineItem,
+ CreateNote,
+ SetPageTransition> GlobalActionData;
+
+typedef std::variant<EnsureStructureElement,
+ InitStructureElement,
+ BeginStructureElement,
+ EndStructureElement,
+ SetCurrentStructureElement,
+ SetStructureAttribute,
+ SetStructureAttributeNumerical,
+ SetStructureBoundingBox,
+ SetStructureAnnotIds,
+ SetActualText,
+ SetAlternateText,
+ CreateControl,
+ BeginGroup,
+ EndGroupGfxLink> PageActionData;
+
+struct PDFExtOutDevDataSyncPage
+{
+ sal_uInt32 nIdx;
+ PageActionData eAct;
+};
+
+struct PDFLinkDestination
+{
+ tools::Rectangle mRect;
+ MapMode mMapMode;
+ sal_Int32 mPageNr;
+ PDFWriter::DestAreaType mAreaType;
+};
+}
+
+struct GlobalSyncData
+{
+ std::deque< GlobalActionData > mActions;
+ ::std::map< sal_Int32, PDFLinkDestination > mFutureDestinations;
+
+ sal_Int32 GetMappedId(sal_Int32 nLinkId);
+
+ /** the way this appears to work: (only) everything that increments mCurId
+ at recording time must put an item into mParaIds at playback time,
+ so that the mCurId becomes the eventual index into mParaIds.
+ */
+ sal_Int32 mCurId;
+ std::vector< sal_Int32 > mParaIds;
+ std::map<void const*, sal_Int32> mSEMap;
+
+ sal_Int32 mCurrentStructElement;
+ std::vector< sal_Int32 > mStructParents;
+ GlobalSyncData() :
+ mCurId ( 0 ),
+ mCurrentStructElement( 0 )
+ {
+ mStructParents.push_back(0); // because PDFWriterImpl has a dummy root
+ }
+ void PlayGlobalActions( PDFWriter& rWriter );
+};
+
+sal_Int32 GlobalSyncData::GetMappedId(sal_Int32 nLinkId)
+{
+ /* 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;
+}
+
+void GlobalSyncData::PlayGlobalActions( PDFWriter& rWriter )
+{
+ for (auto const& action : mActions)
+ {
+ if (std::holds_alternative<CreateNamedDest>(action)) { //i56629
+ const vcl::CreateNamedDest& rCreateNamedDest = std::get<CreateNamedDest>(action);
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( rCreateNamedDest.maParaMapMode );
+ mParaIds.push_back( rWriter.CreateNamedDest( rCreateNamedDest.maDestName, rCreateNamedDest.maParaRect, rCreateNamedDest.mnPage, rCreateNamedDest.mnParaDestAreaType ) );
+ rWriter.Pop();
+ }
+ else if (std::holds_alternative<CreateDest>(action)) {
+ const vcl::CreateDest& rCreateDest = std::get<CreateDest>(action);
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( rCreateDest.maParaMapMode );
+ mParaIds.push_back( rWriter.CreateDest( rCreateDest.maParaRect, rCreateDest.mnPage, rCreateDest.mnParaDestAreaType ) );
+ rWriter.Pop();
+ }
+ else if (std::holds_alternative<CreateControlLink>(action)) {
+ const vcl::CreateControlLink& rCreateControlLink = std::get<CreateControlLink>(action);
+ // tdf#157397: this must be called *in order* with CreateLink etc.
+ rWriter.SetLinkPropertyID(rCreateControlLink.mnControlId, sal_Int32(mParaIds.size()));
+ mParaIds.push_back(rCreateControlLink.mnControlId);
+ }
+ else if (std::holds_alternative<CreateLink>(action)) {
+ const vcl::CreateLink& rCreateLink = std::get<CreateLink>(action);
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( rCreateLink.maParaMapMode );
+ mParaIds.push_back( rWriter.CreateLink(rCreateLink.maParaRect, rCreateLink.mnPage, rCreateLink.maAltText) );
+ // resolve LinkAnnotation structural attribute
+ rWriter.SetLinkPropertyID( mParaIds.back(), sal_Int32(mParaIds.size()-1) );
+ rWriter.Pop();
+ }
+ else if (std::holds_alternative<CreateScreen>(action)) {
+ const vcl::CreateScreen& rCreateScreen = std::get<CreateScreen>(action);
+ rWriter.Push(PushFlags::MAPMODE);
+ rWriter.SetMapMode(rCreateScreen.maParaMapMode);
+ mParaIds.push_back(rWriter.CreateScreen(rCreateScreen.maParaRect, rCreateScreen.mnPage, rCreateScreen.maAltText, rCreateScreen.maMimeType));
+ // resolve AnnotIds structural attribute
+ rWriter.SetLinkPropertyID(mParaIds.back(), sal_Int32(mParaIds.size()-1));
+ rWriter.Pop();
+ }
+ else if (std::holds_alternative<SetLinkDest>(action)) {
+ const vcl::SetLinkDest& rSetLinkDest = std::get<SetLinkDest>(action);
+ sal_Int32 nLinkId = GetMappedId(rSetLinkDest.mnLinkId);
+ sal_Int32 nDestId = GetMappedId(rSetLinkDest.mnDestId);
+ rWriter.SetLinkDest( nLinkId, nDestId );
+ }
+ else if (std::holds_alternative<SetLinkURL>(action)) {
+ const vcl::SetLinkURL& rSetLinkURL = std::get<SetLinkURL>(action);
+ sal_Int32 nLinkId = GetMappedId(rSetLinkURL.mnLinkId);
+ rWriter.SetLinkURL( nLinkId, rSetLinkURL.maLinkURL );
+ }
+ else if (std::holds_alternative<SetScreenURL>(action)) {
+ const vcl::SetScreenURL& rSetScreenURL = std::get<SetScreenURL>(action);
+ sal_Int32 nScreenId = GetMappedId(rSetScreenURL.mnScreenId);
+ rWriter.SetScreenURL(nScreenId, rSetScreenURL.maScreenURL);
+ }
+ else if (std::holds_alternative<SetScreenStream>(action)) {
+ const vcl::SetScreenStream& rSetScreenStream = std::get<SetScreenStream>(action);
+ sal_Int32 nScreenId = GetMappedId(rSetScreenStream.mnScreenId);
+ rWriter.SetScreenStream(nScreenId, rSetScreenStream.maScreenStream);
+ }
+ else if (std::holds_alternative<RegisterDest>(action)) {
+ const vcl::RegisterDest& rRegisterDest = std::get<RegisterDest>(action);
+ const sal_Int32 nDestId = rRegisterDest.mnDestId;
+ 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();
+ }
+ else if (std::holds_alternative<CreateOutlineItem>(action)) {
+ const vcl::CreateOutlineItem& rCreateOutlineItem = std::get<CreateOutlineItem>(action);
+ sal_Int32 nParent = GetMappedId(rCreateOutlineItem.mnParent);
+ sal_Int32 nLinkId = GetMappedId(rCreateOutlineItem.mnDestID);
+ mParaIds.push_back( rWriter.CreateOutlineItem( nParent, rCreateOutlineItem.maText, nLinkId ) );
+ }
+ else if (std::holds_alternative<CreateNote>(action)) {
+ const vcl::CreateNote& rCreateNote = std::get<CreateNote>(action);
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( rCreateNote.maParaMapMode );
+ rWriter.CreateNote( rCreateNote.maParaRect, rCreateNote.maParaPDFNote, rCreateNote.mnPage );
+ }
+ else if (std::holds_alternative<SetPageTransition>(action)) {
+ const vcl::SetPageTransition& rSetPageTransition = std::get<SetPageTransition>(action);
+ rWriter.SetPageTransition( rSetPageTransition.maParaPageTransition, rSetPageTransition.mnMilliSec, rSetPageTransition.mnPage );
+ }
+ }
+}
+
+struct PageSyncData
+{
+ std::deque< PDFExtOutDevDataSyncPage > mActions;
+ Graphic mCurrentGraphic;
+ GlobalSyncData* mpGlobalData;
+
+ bool mbGroupIgnoreGDIMtfActions;
+
+
+ explicit PageSyncData( GlobalSyncData* pGlobal )
+ : mbGroupIgnoreGDIMtfActions ( false )
+ { mpGlobalData = pGlobal; }
+
+ void PushAction( const OutputDevice& rOutDev, PageActionData eAct );
+ bool PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAction, const GDIMetaFile& rMtf, const PDFExtOutDevData& rOutDevData );
+};
+
+void PageSyncData::PushAction( const OutputDevice& rOutDev, PageActionData eAct )
+{
+ GDIMetaFile* pMtf = rOutDev.GetConnectMetaFile();
+ SAL_WARN_IF( !pMtf, "vcl", "PageSyncData::PushAction -> no ConnectMetaFile !!!" );
+
+ PDFExtOutDevDataSyncPage aSync;
+ aSync.eAct = std::move(eAct);
+ if ( pMtf )
+ aSync.nIdx = pMtf->GetActionSize();
+ else
+ aSync.nIdx = 0x7fffffff; // sync not possible
+ mActions.emplace_back( std::move(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;
+ PDFExtOutDevDataSyncPage aDataSync = std::move(mActions.front());
+ mActions.pop_front();
+ if (std::holds_alternative<EnsureStructureElement>(aDataSync.eAct)) {
+#ifndef NDEBUG
+ const vcl::EnsureStructureElement& rEnsureStructureElement = std::get<EnsureStructureElement>(aDataSync.eAct);
+ sal_Int32 const id =
+#endif
+ rWriter.EnsureStructureElement();
+ assert(id == -1 || id == rEnsureStructureElement.mnId); // identity mapping
+ }
+ else if (std::holds_alternative<InitStructureElement>(aDataSync.eAct)) {
+ const vcl::InitStructureElement& rInitStructureElement = std::get<InitStructureElement>(aDataSync.eAct);
+ rWriter.InitStructureElement(rInitStructureElement.mnId, rInitStructureElement.mParaStructElement, rInitStructureElement.maAlias);
+ }
+ else if (std::holds_alternative<BeginStructureElement>(aDataSync.eAct)) {
+ const vcl::BeginStructureElement& rBeginStructureElement = std::get<BeginStructureElement>(aDataSync.eAct);
+ rWriter.BeginStructureElement(rBeginStructureElement.mnId);
+ }
+ else if (std::holds_alternative<EndStructureElement>(aDataSync.eAct)) {
+ rWriter.EndStructureElement();
+ }
+ else if (std::holds_alternative<SetCurrentStructureElement>(aDataSync.eAct)) {
+ const vcl::SetCurrentStructureElement& rSetCurrentStructureElement = std::get<SetCurrentStructureElement>(aDataSync.eAct);
+ rWriter.SetCurrentStructureElement(rSetCurrentStructureElement.mnStructId);
+ }
+ else if (std::holds_alternative<SetStructureAttribute>(aDataSync.eAct)) {
+ const vcl::SetStructureAttribute& rSetStructureAttribute = std::get<SetStructureAttribute>(aDataSync.eAct);
+ rWriter.SetStructureAttribute( rSetStructureAttribute.mParaStructAttribute, rSetStructureAttribute.mParaStructAttributeValue );
+ }
+ else if (std::holds_alternative<SetStructureAttributeNumerical>(aDataSync.eAct)) {
+ const vcl::SetStructureAttributeNumerical& rSetStructureAttributeNumerical = std::get<SetStructureAttributeNumerical>(aDataSync.eAct);
+ rWriter.SetStructureAttributeNumerical( rSetStructureAttributeNumerical.mParaStructAttribute, rSetStructureAttributeNumerical.mnId );
+ }
+ else if (std::holds_alternative<SetStructureBoundingBox>(aDataSync.eAct)) {
+ const vcl::SetStructureBoundingBox& rSetStructureBoundingBox = std::get<SetStructureBoundingBox>(aDataSync.eAct);
+ rWriter.SetStructureBoundingBox( rSetStructureBoundingBox.mRect );
+ }
+ else if (std::holds_alternative<SetStructureAnnotIds>(aDataSync.eAct)) {
+ const vcl::SetStructureAnnotIds& rSetStructureAnnotIds = std::get<SetStructureAnnotIds>(aDataSync.eAct);
+ rWriter.SetStructureAnnotIds(rSetStructureAnnotIds.annotIds);
+ }
+ else if (std::holds_alternative<SetActualText>(aDataSync.eAct)) {
+ const vcl::SetActualText& rSetActualText = std::get<SetActualText>(aDataSync.eAct);
+ rWriter.SetActualText( rSetActualText.maText );
+ }
+ else if (std::holds_alternative<SetAlternateText>(aDataSync.eAct)) {
+ const vcl::SetAlternateText& rSetAlternateText = std::get<SetAlternateText>(aDataSync.eAct);
+ rWriter.SetAlternateText( rSetAlternateText.maText );
+ }
+ else if (std::holds_alternative<CreateControl>(aDataSync.eAct)) {
+ const vcl::CreateControl& rCreateControl = std::get<CreateControl>(aDataSync.eAct);
+ std::shared_ptr< PDFWriter::AnyWidget > pControl( rCreateControl.mxControl );
+ SAL_WARN_IF( !pControl, "vcl", "PageSyncData::PlaySyncPageAct: invalid widget!" );
+ if ( pControl )
+ {
+ sal_Int32 const n = rWriter.CreateControl(*pControl);
+ // resolve AnnotIds structural attribute
+ ::std::vector<sal_Int32> const annotIds{ sal_Int32(mpGlobalData->mCurId) };
+ rWriter.SetStructureAnnotIds(annotIds);
+ // tdf#157397: this must be called *in order* with CreateLink etc.
+ mpGlobalData->mActions.push_back(CreateControlLink{n});
+ mpGlobalData->mCurId++;
+ }
+ }
+ else if (std::holds_alternative<BeginGroup>(aDataSync.eAct)) {
+ /* first determining if this BeginGroup is starting a GfxLink,
+ by searching for an EndGroup or an EndGroupGfxLink */
+ mbGroupIgnoreGDIMtfActions = false;
+ auto itStartingGfxLink = std::find_if(mActions.begin(), mActions.end(),
+ [](const PDFExtOutDevDataSyncPage& rAction) { return std::holds_alternative<EndGroupGfxLink>(rAction.eAct); });
+ if ( itStartingGfxLink != mActions.end() )
+ {
+ EndGroupGfxLink& rEndGroup = std::get<EndGroupGfxLink>(itStartingGfxLink->eAct);
+ Graphic& rGraphic = rEndGroup.maGraphic;
+ if ( rGraphic.IsGfxLink() )
+ {
+ GfxLinkType eType = rGraphic.GetGfxLink().GetType();
+ if ( eType == GfxLinkType::NativeJpg )
+ {
+ mbGroupIgnoreGDIMtfActions = rOutDevData.HasAdequateCompression(rGraphic, rEndGroup.maOutputRect, rEndGroup.maVisibleOutputRect);
+ if ( !mbGroupIgnoreGDIMtfActions )
+ mCurrentGraphic = rGraphic;
+ }
+ else if ( eType == GfxLinkType::NativePng || eType == GfxLinkType::NativePdf )
+ {
+ if ( eType == GfxLinkType::NativePdf || rOutDevData.HasAdequateCompression(rGraphic, rEndGroup.maOutputRect, rEndGroup.maVisibleOutputRect) )
+ mCurrentGraphic = rGraphic;
+ }
+ }
+ }
+ }
+ else if (std::holds_alternative<EndGroupGfxLink>(aDataSync.eAct)) {
+ EndGroupGfxLink& rEndGroup = std::get<EndGroupGfxLink>(aDataSync.eAct);
+ tools::Rectangle aOutputRect, aVisibleOutputRect;
+ Graphic aGraphic( rEndGroup.maGraphic );
+
+ sal_Int32 nTransparency = rEndGroup.mnTransparency;
+ aOutputRect = rEndGroup.maOutputRect;
+ aVisibleOutputRect = rEndGroup.maVisibleOutputRect;
+
+ 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();
+ }
+ }
+ else if ( mbGroupIgnoreGDIMtfActions )
+ {
+ rCurGDIMtfAction++;
+ bRet = true;
+ }
+ return bRet;
+}
+
+PDFExtOutDevData::PDFExtOutDevData( const OutputDevice& rOutDev ) :
+ mrOutDev ( rOutDev ),
+ mbTaggedPDF ( false ),
+ mbExportNotes ( true ),
+ mbExportNotesInMargin ( false ),
+ 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::SetIsExportNotesInMargin( const bool bExportNotesInMargin )
+{
+ mbExportNotesInMargin = bExportNotesInMargin;
+}
+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(PDFWriter *const pWriter)
+{
+ if (pWriter != nullptr)
+ {
+ // tdf#157182 HACK: all PDF actions on this page will be deleted; to have
+ // matching SE IDs on the *next* page, replay EnsureStructureElement actions
+ for (PDFExtOutDevDataSyncPage const& rAction : mpPageSyncData->mActions)
+ {
+ if (std::holds_alternative<struct EnsureStructureElement>(rAction.eAct))
+ {
+ pWriter->EnsureStructureElement();
+ }
+ }
+ }
+ *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(
+ vcl::CreateNamedDest{ sDestName, mrOutDev.GetMapMode(), PDFWriter::DestAreaType::XYZ, rRect, nPageNr == -1 ? mnPage : nPageNr } );
+
+ return mpGlobalSyncData->mCurId++;
+}
+//<---i56629
+sal_Int32 PDFExtOutDevData::RegisterDest()
+{
+ const sal_Int32 nLinkDestID = mpGlobalSyncData->mCurId++;
+ mpGlobalSyncData->mActions.push_back( vcl::RegisterDest{ 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(
+ vcl::CreateDest{ mrOutDev.GetMapMode(), eType, rRect, nPageNr == -1 ? mnPage : nPageNr } );
+ return mpGlobalSyncData->mCurId++;
+}
+sal_Int32 PDFExtOutDevData::CreateLink(const tools::Rectangle& rRect, OUString const& rAltText, sal_Int32 nPageNr)
+{
+ mpGlobalSyncData->mActions.push_back(
+ vcl::CreateLink{rAltText, mrOutDev.GetMapMode(), rRect, nPageNr == -1 ? mnPage : nPageNr } );
+ return mpGlobalSyncData->mCurId++;
+}
+
+sal_Int32 PDFExtOutDevData::CreateScreen(const tools::Rectangle& rRect,
+ OUString const& rAltText, OUString const& rMimeType,
+ sal_Int32 nPageNr, SdrObject const*const pObj)
+{
+ mpGlobalSyncData->mActions.push_back(vcl::CreateScreen{ rAltText, rMimeType, mrOutDev.GetMapMode(), rRect, nPageNr });
+ auto const ret(mpGlobalSyncData->mCurId++);
+ m_ScreenAnnotations[pObj].push_back(ret);
+ return ret;
+}
+
+::std::vector<sal_Int32> const& PDFExtOutDevData::GetScreenAnnotIds(SdrObject const*const pObj) const
+{
+ auto const it(m_ScreenAnnotations.find(pObj));
+ if (it == m_ScreenAnnotations.end())
+ {
+ assert(false); // expected?
+ }
+ return it->second;
+}
+
+void PDFExtOutDevData::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
+{
+ mpGlobalSyncData->mActions.push_back( vcl::SetLinkDest{ nLinkId, nDestId } );
+}
+void PDFExtOutDevData::SetLinkURL( sal_Int32 nLinkId, const OUString& rURL )
+{
+ mpGlobalSyncData->mActions.push_back( vcl::SetLinkURL{ rURL, nLinkId } );
+}
+
+void PDFExtOutDevData::SetScreenURL(sal_Int32 nScreenId, const OUString& rURL)
+{
+ mpGlobalSyncData->mActions.push_back(vcl::SetScreenURL{ rURL, nScreenId });
+}
+
+void PDFExtOutDevData::SetScreenStream(sal_Int32 nScreenId, const OUString& rURL)
+{
+ mpGlobalSyncData->mActions.push_back(vcl::SetScreenStream{ rURL, nScreenId });
+}
+
+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( vcl::CreateOutlineItem{ rText, nParent, nDestID } );
+ return mpGlobalSyncData->mCurId++;
+}
+void PDFExtOutDevData::CreateNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
+{
+ mpGlobalSyncData->mActions.push_back(
+ vcl::CreateNote{ mrOutDev.GetMapMode(), rNote, rRect, nPageNr == -1 ? mnPage : nPageNr } );
+}
+void PDFExtOutDevData::SetPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec )
+{
+ mpGlobalSyncData->mActions.push_back( vcl::SetPageTransition{ eType, nMilliSec, 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::EnsureStructureElement(void const*const key)
+{
+ sal_Int32 id(-1);
+ if (key != nullptr)
+ {
+ auto const it(mpGlobalSyncData->mSEMap.find(key));
+ if (it != mpGlobalSyncData->mSEMap.end())
+ {
+ id = it->second;
+ }
+ }
+ if (id == -1)
+ {
+ id = mpGlobalSyncData->mStructParents.size();
+ mpPageSyncData->PushAction(mrOutDev, vcl::EnsureStructureElement{ id });
+ mpGlobalSyncData->mStructParents.push_back(mpGlobalSyncData->mCurrentStructElement);
+ if (key != nullptr)
+ {
+ mpGlobalSyncData->mSEMap.emplace(key, id);
+ }
+ }
+ return id;
+}
+
+void PDFExtOutDevData::InitStructureElement(sal_Int32 const id,
+ PDFWriter::StructElement const eType, const OUString& rAlias)
+{
+ mpPageSyncData->PushAction(mrOutDev, vcl::InitStructureElement{ eType, rAlias, id });
+ // update parent: required for hell fly anchor frames in sw, so that on the actual
+ // anchor frame EndStructureElement() resets mCurrentStructElement properly.
+ mpGlobalSyncData->mStructParents[id] = mpGlobalSyncData->mCurrentStructElement;
+}
+
+void PDFExtOutDevData::BeginStructureElement(sal_Int32 const id)
+{
+ mpPageSyncData->PushAction( mrOutDev, vcl::BeginStructureElement{ id } );
+ mpGlobalSyncData->mCurrentStructElement = id;
+}
+
+sal_Int32 PDFExtOutDevData::WrapBeginStructureElement(
+ PDFWriter::StructElement const eType, const OUString& rAlias)
+{
+ sal_Int32 const id = EnsureStructureElement(nullptr);
+ InitStructureElement(id, eType, rAlias);
+ BeginStructureElement(id);
+ return id;
+}
+
+void PDFExtOutDevData::EndStructureElement()
+{
+ assert(mpGlobalSyncData->mCurrentStructElement != 0); // underflow?
+ mpPageSyncData->PushAction( mrOutDev, vcl::EndStructureElement{} );
+ mpGlobalSyncData->mCurrentStructElement = mpGlobalSyncData->mStructParents[ mpGlobalSyncData->mCurrentStructElement ];
+}
+
+void PDFExtOutDevData::SetCurrentStructureElement(sal_Int32 const nStructId)
+{
+ assert(o3tl::make_unsigned(nStructId) < mpGlobalSyncData->mStructParents.size());
+ mpGlobalSyncData->mCurrentStructElement = nStructId;
+ mpPageSyncData->PushAction( mrOutDev, vcl::SetCurrentStructureElement{ nStructId } );
+}
+
+sal_Int32 PDFExtOutDevData::GetCurrentStructureElement() const
+{
+ return mpGlobalSyncData->mCurrentStructElement;
+}
+
+void PDFExtOutDevData::SetStructureAttribute( PDFWriter::StructAttribute eAttr, PDFWriter::StructAttributeValue eVal )
+{
+ mpPageSyncData->PushAction( mrOutDev, vcl::SetStructureAttribute{ eAttr, eVal } );
+}
+void PDFExtOutDevData::SetStructureAttributeNumerical( PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
+{
+ mpPageSyncData->PushAction( mrOutDev, vcl::SetStructureAttributeNumerical { eAttr, nValue } );
+}
+void PDFExtOutDevData::SetStructureBoundingBox( const tools::Rectangle& rRect )
+{
+ mpPageSyncData->PushAction( mrOutDev, vcl::SetStructureBoundingBox{ rRect } );
+}
+
+void PDFExtOutDevData::SetStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds)
+{
+ mpPageSyncData->PushAction(mrOutDev, vcl::SetStructureAnnotIds{ rAnnotIds });
+}
+
+void PDFExtOutDevData::SetActualText( const OUString& rText )
+{
+ mpPageSyncData->PushAction( mrOutDev, vcl::SetActualText{ rText } );
+}
+void PDFExtOutDevData::SetAlternateText( const OUString& rText )
+{
+ mpPageSyncData->PushAction( mrOutDev, vcl::SetAlternateText{ rText } );
+}
+
+void PDFExtOutDevData::CreateControl( const PDFWriter::AnyWidget& rControlType )
+{
+ std::shared_ptr< PDFWriter::AnyWidget > pClone( rControlType.Clone() );
+ mpPageSyncData->PushAction( mrOutDev, vcl::CreateControl{ pClone } );
+}
+
+void PDFExtOutDevData::BeginGroup()
+{
+ mpPageSyncData->PushAction( mrOutDev, vcl::BeginGroup{} );
+}
+
+void PDFExtOutDevData::EndGroup( const Graphic& rGraphic,
+ sal_uInt8 nTransparency,
+ const tools::Rectangle& rOutputRect,
+ const tools::Rectangle& rVisibleOutputRect )
+{
+ mpPageSyncData->PushAction( mrOutDev,
+ vcl::EndGroupGfxLink{rGraphic, rOutputRect, rVisibleOutputRect, nTransparency} );
+}
+
+// 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/pdfobjectcopier.cxx b/vcl/source/gdi/pdfobjectcopier.cxx
new file mode 100644
index 0000000000..0d07c0df7f
--- /dev/null
+++ b/vcl/source/gdi/pdfobjectcopier.cxx
@@ -0,0 +1,346 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = OString::number(nObject) + " 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("/" + rPair.first + " ");
+ 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))
+ 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))
+ return -1;
+ aLine.setLength(0);
+ m_rContainer.disableStreamEncryption();
+
+ aLine.append("\nendstream\n");
+ if (!m_rContainer.writeBuffer(aLine))
+ return -1;
+ aLine.setLength(0);
+ }
+
+ aLine.append("endobj\n\n");
+ if (!m_rContainer.writeBuffer(aLine))
+ 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;
+ filter::PDFObjectElement* pKindObject = nullptr;
+ if (auto pResources
+ = dynamic_cast<filter::PDFDictionaryElement*>(rPage.Lookup("Resources"_ostr)))
+ {
+ // 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 {};
+ }
+
+ pKindObject = pReferenced;
+ aItems = pReferenced->GetDictionaryItems();
+ }
+ }
+ else if (filter::PDFObjectElement* pPageResources = rPage.LookupObject("Resources"_ostr))
+ {
+ // 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();
+ pKindObject = pObject;
+ }
+ }
+ if (aItems.empty())
+ return {};
+
+ SvMemoryStream& rDocBuffer = rPage.GetDocument().GetEditBuffer();
+ bool bHasDictValue = false;
+
+ 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)
+ {
+ if (pKindObject && dynamic_cast<filter::PDFDictionaryElement*>(rItem.second))
+ {
+ bHasDictValue = true;
+ break;
+ }
+
+ 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;
+ }
+
+ if (bHasDictValue && pKindObject)
+ {
+ sal_Int32 nObject = copyExternalResource(rDocBuffer, *pKindObject, rCopiedResources);
+ return "/" + rKind + " " + OString::number(nObject) + " 0 R";
+ }
+
+ // 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"_ostr, "ExtGState"_ostr, "Font"_ostr,
+ "XObject"_ostr, "Shading"_ostr, "Pattern"_ostr };
+ 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"_ostr));
+ auto pFilterArray = dynamic_cast<filter::PDFArrayElement*>(pContent->Lookup("Filter"_ostr));
+ if (!pFilter && pFilterArray)
+ {
+ auto& aElements = pFilterArray->GetElements();
+ if (!aElements.empty())
+ pFilter = dynamic_cast<filter::PDFNameElement*>(aElements[0]);
+ }
+
+ 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 0000000000..60437f55fe
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter.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 <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,
+ KernArraySpan pDXAry,
+ std::span<const sal_Bool> pKashidaAry,
+ sal_Int32 nIndex,
+ sal_Int32 nLen )
+{
+ xImplementation->drawTextArray( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen );
+}
+
+void PDFWriter::DrawStretchText(
+ const Point& rStartPt,
+ sal_Int32 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_uInt32 nHorzRound, sal_uInt32 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, OUString const& rAltText, OUString const& rMimeType)
+{
+ return xImplementation->createScreen(rRect, nPageNr, rAltText, rMimeType);
+}
+
+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::EnsureStructureElement()
+{
+ return xImplementation->ensureStructureElement();
+}
+
+void PDFWriter::InitStructureElement(sal_Int32 const id,
+ PDFWriter::StructElement const eType, std::u16string_view const rAlias)
+{
+ return xImplementation->initStructureElement(id, eType, rAlias);
+}
+
+void PDFWriter::BeginStructureElement(sal_Int32 const id)
+{
+ return xImplementation->beginStructureElement(id);
+}
+
+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::SetStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds)
+{
+ xImplementation->setStructureAnnotIds(rAnnotIds);
+}
+
+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::AddAttachedFile(OUString const& rFileName, OUString const& rMimeType, OUString const& rDescription, std::unique_ptr<PDFOutputStream> pStream)
+{
+ xImplementation->addDocumentAttachedFile(rFileName, rMimeType, rDescription, std::move(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 0000000000..c7ef07ff49
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -0,0 +1,11934 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <comphelper/xmlencode.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/crc.h>
+#include <rtl/digest.h>
+#include <rtl/uri.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <svl/cryptosign.hxx>
+#include <sal/log.hxx>
+#include <svl/urihelper.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <tools/helpers.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/UnitConversion.hxx>
+#include <tools/zcodec.hxx>
+#include <unotools/configmgr.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <vcl/glyphitemcache.hxx>
+#include <vcl/kernarray.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/pdfread.hxx>
+#include <vcl/settings.hxx>
+#include <strhelper.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/filter/pdfdocument.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+#include <comphelper/hash.hxx>
+
+#include <svdata.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <fontsubset.hxx>
+#include <font/EmphasisMark.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 <frozen/bits/defines.h>
+#include <frozen/bits/elsa_std.h>
+#include <frozen/map.h>
+
+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 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 ");
+}
+
+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;
+}
+
+void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle);
+
+} // 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 = i_rControl.Name;
+ 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 = "Widget"_ostr;
+ }
+
+ 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
+ {
+ aTry = aFullName + "_" + OString::number(nTry++);
+ 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)));
+ }
+}
+
+const char* getPDFVersionStr(PDFWriter::PDFVersion ePDFVersion)
+{
+ switch (ePDFVersion)
+ {
+ case PDFWriter::PDFVersion::PDF_A_1:
+ case PDFWriter::PDFVersion::PDF_1_4:
+ return "1.4";
+ case PDFWriter::PDFVersion::PDF_1_5:
+ return "1.5";
+ case PDFWriter::PDFVersion::PDF_1_6:
+ return "1.6";
+ default:
+ case PDFWriter::PDFVersion::PDF_A_2:
+ case PDFWriter::PDFVersion::PDF_A_3:
+ case PDFWriter::PDFVersion::PDF_1_7:
+ return "1.7";
+ }
+}
+
+} // 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)
+ {
+ // 1.6 or later
+ default:
+ m_nUserUnit = std::ceil(std::max(nPageWidth, nPageHeight) / 14400.0);
+ break;
+ case PDFWriter::PDFVersion::PDF_1_4:
+ case PDFWriter::PDFVersion::PDF_1_5:
+ case PDFWriter::PDFVersion::PDF_A_1:
+ 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(
+ OString::number(m_aStreamObjects.back())
+ + " 0 obj\n<</Length "
+ + OString::number( m_nStreamLengthObject )
+ + " 0 R" );
+ if (!g_bDebugDisableCompression)
+ aLine.append( "/Filter/FlateDecode" );
+ aLine.append( ">>\nstream\n" );
+ if( ! m_pWriter->writeBuffer( aLine ) )
+ 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" ) )
+ 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 );
+}
+
+bool PDFPage::emit(sal_Int32 nParentObject )
+{
+ m_pWriter->MARK("PDFPage::emit");
+ // emit page object
+ if( ! m_pWriter->updateObject( m_nPageObject ) )
+ return false;
+ OStringBuffer aLine(
+ OString::number(m_nPageObject)
+ + " 0 obj\n"
+ "<</Type/Page/Parent "
+ + OString::number(nParentObject)
+ + " 0 R"
+ "/Resources "
+ + OString::number(m_pWriter->getResourceDictObj())
+ + " 0 R" );
+ if( m_nPageWidth && m_nPageHeight )
+ {
+ aLine.append( "/MediaBox[0 0 "
+ + OString::number(m_nPageWidth / m_nUserUnit)
+ + " "
+ + OString::number(m_nPageHeight / m_nUserUnit)
+ + "]" );
+ if (m_nUserUnit > 1)
+ {
+ aLine.append("\n/UserUnit " + OString::number(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( OString::number(m_aAnnotations[i])
+ + " 0 R" );
+ aLine.append( ((i+1)%15) ? " " : "\n" );
+ }
+ aLine.append( "]\n" );
+ if (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( OString::number(m_aMCIDParents[i])
+ + " 0 R" );
+ aStructParents.append( ((i%10) == 9) ? "\n" : " " );
+ }
+ aStructParents.append( "]" );
+ m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() );
+
+ aLine.append( "/StructParents "
+ + OString::number( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) )
+ + "\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( OString::Concat("/S/") + pStyle + "\n" );
+ }
+ if( pDm )
+ {
+ aLine.append( OString::Concat("/Dm/") + pDm + "\n" );
+ }
+ if( pM )
+ {
+ aLine.append( OString::Concat("/M/") + pM + "\n" );
+ }
+ if( pDi )
+ {
+ aLine.append( OString::Concat("/Di ") + pDi + "\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( " " + OString::number( i ) + " 0 R" );
+ }
+ if( nStreamObjects > 1 )
+ aLine.append( ']' );
+ aLine.append( ">>\nendobj\n\n" );
+ return m_pWriter->writeBuffer( aLine );
+}
+
+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 : 842; // default A4 height in inch/72, OK to use hardcoded value here?
+
+ 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::WITHOUT_ALPHA, 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(utl::ConfigManager::IsFuzzing() ? 15 :
+ 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;
+ //m_StructElementStack.push(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-" );
+ aBuffer.append(getPDFVersionStr(m_aContext.Version));
+ // append something binary as comment (suggested in PDF Reference)
+ aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" );
+ if( !writeBuffer( aBuffer ) )
+ {
+ 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_7;
+
+ m_bIsPDF_A3 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_3);
+ if( m_bIsPDF_A3 )
+ m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7;
+
+ 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_aContext.DocumentInfo.ModificationDate, 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,
+ const css::util::DateTime& rCreationMetaDate,
+ 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;
+ aDT.NanoSeconds = rCreationMetaDate.NanoSeconds;
+ aDT.Seconds = rCreationMetaDate.Seconds;
+ aDT.Minutes = rCreationMetaDate.Minutes;
+ aDT.Hours = rCreationMetaDate.Hours;
+ aDT.Day = rCreationMetaDate.Day;
+ aDT.Month = rCreationMetaDate.Month;
+ aDT.Year = rCreationMetaDate.Year;
+
+ osl_getSystemTime( &aGMT );
+ osl_getLocalTimeFromSystemTime( &aGMT, &aTVal );
+ 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(
+ OStringChar(static_cast<char>('0' + ((aDT.Year/1000)%10)) )
+ + OStringChar(static_cast<char>('0' + ((aDT.Year/100)%10)) )
+ + OStringChar(static_cast<char>('0' + ((aDT.Year/10)%10)) )
+ + OStringChar(static_cast<char>('0' + ((aDT.Year)%10)) )
+ + "-"
+ + OStringChar(static_cast<char>('0' + ((aDT.Month/10)%10)) )
+ + OStringChar(static_cast<char>('0' + ((aDT.Month)%10)) )
+ + "-"
+ + OStringChar(static_cast<char>('0' + ((aDT.Day/10)%10)) )
+ + OStringChar(static_cast<char>('0' + ((aDT.Day)%10)) )
+ + "T"
+ + OStringChar(static_cast<char>('0' + ((aDT.Hours/10)%10)) )
+ + OStringChar(static_cast<char>('0' + ((aDT.Hours)%10)) )
+ + ":"
+ + OStringChar(static_cast<char>('0' + ((aDT.Minutes/10)%10)) )
+ + OStringChar(static_cast<char>('0' + ((aDT.Minutes)%10)) )
+ + ":"
+ + OStringChar(static_cast<char>('0' + ((aDT.Seconds/10)%10)) )
+ + OStringChar(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(
+ OStringChar(static_cast<char>('0' + ((nDelta/36000)%10)) )
+ + OStringChar(static_cast<char>('0' + ((nDelta/3600)%10)) )
+ + ":"
+ + OStringChar(static_cast<char>('0' + ((nDelta/600)%6)) )
+ + OStringChar(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 );
+}
+
+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 );
+ writeBufferBytes( m_pMemStream->GetData(), nLen );
+ m_pMemStream.reset();
+ }
+}
+
+bool PDFWriterImpl::writeBufferBytes( 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 );
+
+ const Fraction frac(m_aPages.back().m_nUserUnit, pointToPixel(1));
+ m_aMapMode = MapMode(MapUnit::MapPoint, Point(), frac, frac);
+
+ 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 );
+}
+
+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( OString::number(nObject)
+ + " 0 obj\n"
+ "<</Nums[\n" );
+ sal_Int32 nTreeItems = m_aStructParentTree.size();
+ for( sal_Int32 n = 0; n < nTreeItems; n++ )
+ {
+ aLine.append( OString::number(n) + " "
+ + m_aStructParentTree[n]
+ + "\n" );
+ }
+ aLine.append( "]>>\nendobj\n\n" );
+ CHECK_RETURN( updateObject( nObject ) );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ }
+ return nObject;
+}
+
+// every structure element already has a unique object id - just use it for ID
+static OString GenerateID(sal_Int32 const nObjectId)
+{
+ return "id" + OString::number(nObjectId);
+}
+
+sal_Int32 PDFWriterImpl::emitStructIDTree(sal_Int32 const nObject)
+{
+ // loosely following PDF 1.7, 10.6.5 Example of Logical Structure, Example 10.15
+ if (nObject < 0)
+ {
+ return nObject;
+ }
+ // the name tree entries must be sorted lexicographically.
+ std::map<OString, sal_Int32> ids;
+ for (auto n : m_StructElemObjsWithID)
+ {
+ ids.emplace(GenerateID(n), n);
+ }
+ OStringBuffer buf;
+ appendObjectID(nObject, buf);
+ buf.append("<</Names [\n");
+ for (auto const& it : ids)
+ {
+ appendLiteralStringEncrypt(it.first, nObject, buf);
+ buf.append(" ");
+ appendObjectReference(it.second, buf);
+ buf.append("\n");
+ }
+ buf.append("] >>\nendobj\n\n");
+
+ CHECK_RETURN( updateObject(nObject) );
+ CHECK_RETURN( writeBuffer(buf) );
+
+ return nObject;
+}
+
+const char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
+{
+ static constexpr auto aAttributeStrings = frozen::make_map<PDFWriter::StructAttribute, const char*>({
+ { PDFWriter::Placement, "Placement" },
+ { PDFWriter::WritingMode, "WritingMode" },
+ { PDFWriter::SpaceBefore, "SpaceBefore" },
+ { PDFWriter::SpaceAfter, "SpaceAfter" },
+ { PDFWriter::StartIndent, "StartIndent" },
+ { PDFWriter::EndIndent, "EndIndent" },
+ { PDFWriter::TextIndent, "TextIndent" },
+ { PDFWriter::TextAlign, "TextAlign" },
+ { PDFWriter::Width, "Width" },
+ { PDFWriter::Height, "Height" },
+ { PDFWriter::BlockAlign, "BlockAlign" },
+ { PDFWriter::InlineAlign, "InlineAlign" },
+ { PDFWriter::LineHeight, "LineHeight" },
+ { PDFWriter::BaselineShift, "BaselineShift" },
+ { PDFWriter::TextDecorationType,"TextDecorationType" },
+ { PDFWriter::ListNumbering, "ListNumbering" },
+ { PDFWriter::RowSpan, "RowSpan" },
+ { PDFWriter::ColSpan, "ColSpan" },
+ { PDFWriter::Scope, "Scope" },
+ { PDFWriter::Role, "Role" },
+ { PDFWriter::RubyAlign, "RubyAlign" },
+ { PDFWriter::RubyPosition, "RubyPosition" },
+ { PDFWriter::Type, "Type" },
+ { PDFWriter::Subtype, "Subtype" },
+ { PDFWriter::LinkAnnotation, "LinkAnnotation" }
+ });
+
+ auto 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 constexpr auto aValueStrings = frozen::make_map<PDFWriter::StructAttributeValue, const char*>({
+ { PDFWriter::NONE, "None" },
+ { PDFWriter::Block, "Block" },
+ { PDFWriter::Inline, "Inline" },
+ { PDFWriter::Before, "Before" },
+ { PDFWriter::After, "After" },
+ { PDFWriter::Start, "Start" },
+ { PDFWriter::End, "End" },
+ { PDFWriter::LrTb, "LrTb" },
+ { PDFWriter::RlTb, "RlTb" },
+ { PDFWriter::TbRl, "TbRl" },
+ { PDFWriter::Center, "Center" },
+ { PDFWriter::Justify, "Justify" },
+ { PDFWriter::Auto, "Auto" },
+ { PDFWriter::Middle, "Middle" },
+ { PDFWriter::Normal, "Normal" },
+ { PDFWriter::Underline, "Underline" },
+ { PDFWriter::Overline, "Overline" },
+ { PDFWriter::LineThrough,"LineThrough" },
+ { PDFWriter::Row, "Row" },
+ { PDFWriter::Column, "Column" },
+ { PDFWriter::Both, "Both" },
+ { PDFWriter::Pagination, "Pagination" },
+ { PDFWriter::Layout, "Layout" },
+ { PDFWriter::Page, "Page" },
+ { PDFWriter::Background, "Background" },
+ { PDFWriter::Header, "Header" },
+ { PDFWriter::Footer, "Footer" },
+ { PDFWriter::Watermark, "Watermark" },
+ { PDFWriter::Rb, "rb" },
+ { PDFWriter::Cb, "cb" },
+ { PDFWriter::Pb, "pb" },
+ { PDFWriter::Tv, "tv" },
+ { PDFWriter::RStart, "Start" },
+ { PDFWriter::RCenter, "Center" },
+ { PDFWriter::REnd, "End" },
+ { PDFWriter::RJustify, "Justify" },
+ { PDFWriter::RDistribute,"Distribute" },
+ { PDFWriter::RBefore, "Before" },
+ { PDFWriter::RAfter, "After" },
+ { PDFWriter::RWarichu, "Warichu" },
+ { PDFWriter::RInline, "Inline" },
+ { PDFWriter::Disc, "Disc" },
+ { PDFWriter::Circle, "Circle" },
+ { PDFWriter::Square, "Square" },
+ { PDFWriter::Decimal, "Decimal" },
+ { PDFWriter::UpperRoman, "UpperRoman" },
+ { PDFWriter::LowerRoman, "LowerRoman" },
+ { PDFWriter::UpperAlpha, "UpperAlpha" },
+ { PDFWriter::LowerAlpha, "LowerAlpha" }
+ });
+
+ auto 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" );
+}
+
+template<typename T>
+void PDFWriterImpl::AppendAnnotKid(PDFStructureElement& i_rEle, T & rAnnot)
+{
+ // update struct parent of link
+ OString const aStructParentEntry(OString::number(i_rEle.m_nObject) + " 0 R");
+ m_aStructParentTree.push_back( aStructParentEntry );
+ rAnnot.m_nStructParent = m_aStructParentTree.size()-1;
+ sal_Int32 const nAnnotObj(rAnnot.m_nObject);
+ i_rEle.m_aKids.emplace_back(ObjReferenceObj{nAnnotObj});
+}
+
+OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
+{
+ // create layout, list and table attribute sets
+ OStringBuffer aLayout(256), aList(64), aTable(64);
+ OStringBuffer aPrintField;
+ 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::Role)
+ {
+ appendStructureAttributeLine(attribute.first, attribute.second, aPrintField, 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() )
+ {
+ AppendAnnotKid(i_rEle, m_aLinks[nLink]);
+ }
+ 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" );
+ }
+
+ OStringBuffer aRet(256);
+ bool isArray(false);
+ if (1 < (aLayout.isEmpty() ? 0 : 1) + (aList.isEmpty() ? 0 : 1)
+ + (aPrintField.isEmpty() ? 0 : 1) + (aTable.isEmpty() ? 0 : 1))
+ {
+ isArray = true;
+ aRet.append(" [");
+ }
+ auto const WriteAttrs = [&](char const*const pName, OStringBuffer & rBuf)
+ {
+ aRet.append(" <</O");
+ aRet.append(pName);
+ aRet.append(rBuf);
+ aRet.append(">>");
+ };
+ if( !aLayout.isEmpty() )
+ {
+ WriteAttrs("/Layout", aLayout);
+ }
+ if( !aList.isEmpty() )
+ {
+ WriteAttrs("/List", aList);
+ }
+ if (!aPrintField.isEmpty())
+ {
+ WriteAttrs("/PrintField", aPrintField);
+ }
+ if( !aTable.isEmpty() )
+ {
+ WriteAttrs("/Table", aTable);
+ }
+
+ if (isArray)
+ {
+ aRet.append( " ]" );
+ }
+ return aRet.makeStringAndClear();
+}
+
+sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
+{
+ assert(rEle.m_nOwnElement == 0 || rEle.m_oType);
+ if (rEle.m_nOwnElement != rEle.m_nParentElement // emit the struct tree root
+ // do not emit NonStruct and its children
+ && *rEle.m_oType == PDFWriter::NonStructElement)
+ {
+ 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_oType != 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(
+ OString::number(rEle.m_nObject)
+ + " 0 obj\n"
+ "<</Type" );
+ sal_Int32 nParentTree = -1;
+ sal_Int32 nIDTree = -1;
+ if( rEle.m_nOwnElement == rEle.m_nParentElement )
+ {
+ nParentTree = createObject();
+ CHECK_RETURN( nParentTree );
+ aLine.append( "/StructTreeRoot\n"
+ "/ParentTree "
+ + OString::number(nParentTree)
+ + " 0 R\n" );
+ if( ! m_aRoleMap.empty() )
+ {
+ aLine.append( "/RoleMap<<" );
+ for (auto const& role : m_aRoleMap)
+ {
+ aLine.append( "/" + role.first + "/" + role.second + "\n" );
+ }
+ aLine.append( ">>\n" );
+ }
+ if (!m_StructElemObjsWithID.empty())
+ {
+ nIDTree = createObject();
+ aLine.append("/IDTree ");
+ appendObjectReference(nIDTree, aLine);
+ 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_oType) );
+ if (m_StructElemObjsWithID.find(rEle.m_nObject) != m_StructElemObjsWithID.end())
+ {
+ aLine.append("\n/ID ");
+ appendLiteralStringEncrypt(GenerateID(rEle.m_nObject), rEle.m_nObject, aLine);
+ }
+ aLine.append(
+ "\n"
+ "/P "
+ + OString::number(m_aStructure[ rEle.m_nParentElement ].m_nObject)
+ + " 0 R\n"
+ "/Pg "
+ + OString::number(rEle.m_nFirstPageObject)
+ + " 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" + aAttribs + "\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( "-" + aCountry );
+ }
+ aLine.append( "/Lang" );
+ appendLiteralStringEncrypt( aLocBuf, rEle.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+ }
+ if (!rEle.m_AnnotIds.empty())
+ {
+ for (auto const id : rEle.m_AnnotIds)
+ {
+ auto const it(m_aLinkPropertyMap.find(id));
+ assert(it != m_aLinkPropertyMap.end());
+
+ if (*rEle.m_oType == PDFWriter::Form)
+ {
+ assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aWidgets.size());
+ AppendAnnotKid(rEle, m_aWidgets[it->second]);
+ }
+ else
+ {
+ assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aScreens.size());
+ AppendAnnotKid(rEle, m_aScreens[it->second]);
+ }
+ }
+ }
+ if( ! rEle.m_aKids.empty() )
+ {
+ unsigned int i = 0;
+ aLine.append( "/K[" );
+ for (auto const& rKid : rEle.m_aKids)
+ {
+ if (std::holds_alternative<ObjReference>(rKid))
+ {
+ ObjReference const& rObj(std::get<ObjReference>(rKid));
+ appendObjectReference(rObj.nObject, aLine);
+ aLine.append( ( (i & 15) == 15 ) ? "\n" : " " );
+ }
+ else if (std::holds_alternative<ObjReferenceObj>(rKid))
+ {
+ ObjReferenceObj const& rObj(std::get<ObjReferenceObj>(rKid));
+ aLine.append("<</Type/OBJR/Obj ");
+ appendObjectReference(rObj.nObject, aLine);
+ aLine.append(">>\n");
+ }
+ else
+ {
+ assert(std::holds_alternative<MCIDReference>(rKid));
+ MCIDReference const& rMCID(std::get<MCIDReference>(rKid));
+ if (rMCID.nPageObj == rEle.m_nFirstPageObject)
+ {
+ aLine.append(OString::number(rMCID.nMCID) + " ");
+ }
+ else
+ {
+ aLine.append("<</Type/MCR/Pg ");
+ appendObjectReference(rMCID.nPageObj, aLine);
+ aLine.append(" /MCID " + OString::number(rMCID.nMCID) + ">>\n");
+ }
+ }
+ ++i;
+ }
+ aLine.append( "]\n" );
+ }
+ aLine.append( ">>\nendobj\n\n" );
+
+ CHECK_RETURN( updateObject( rEle.m_nObject ) );
+ CHECK_RETURN( writeBuffer( aLine ) );
+
+ CHECK_RETURN( emitStructParentTree( nParentTree ) );
+ CHECK_RETURN( emitStructIDTree(nIDTree) );
+
+ 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(
+ OString::number(tiling.m_nObject)
+ + " 0 obj\n"
+ "<</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 "
+ + OString::number(static_cast<sal_Int32>(nTilingStreamSize))
+ + ">>\nstream\n" );
+ if ( !updateObject( tiling.m_nObject ) ) return false;
+ if ( !writeBuffer( aTilingObj ) ) return false;
+ checkAndEnableStreamEncryption( tiling.m_nObject );
+ bool written = writeBufferBytes( 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 ) ) 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(
+ OString::number(nFontObject)
+ + " 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 ) );
+ return nFontObject;
+}
+
+namespace
+{
+// Translate units from TT to PS (standard 1/1000)
+int XUnits(int nUPEM, int n) { return (n * 1000) / nUPEM; }
+}
+
+std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const vcl::font::PhysicalFontFace* pFace, EmbedFont const & rEmbed )
+{
+ std::map< sal_Int32, sal_Int32 > aRet;
+
+ if (g_bDebugDisableCompression)
+ emitComment("PDFWriterImpl::emitSystemFont");
+
+ 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 = pFace->GetFamilyName();
+
+ sal_Int32 pWidths[256] = { 0 };
+ const LogicalFontInstance* pFontInstance = rEmbed.m_pFontInstance;
+ auto nUPEM = pFace->UnitsPerEm();
+ for( sal_Ucs c = 32; c < 256; c++ )
+ {
+ sal_GlyphId nGlyph = pFontInstance->GetGlyphIndex(c);
+ pWidths[c] = XUnits(nUPEM, pFontInstance->GetGlyphWidth(nGlyph, false, false));
+ }
+
+ // We are interested only in filling aInfo
+ sal_GlyphId aGlyphIds[] = { 0 };
+ sal_uInt8 pEncoding[] = { 0 };
+ std::vector<sal_uInt8> aBuffer;
+ pFace->CreateFontSubset(aBuffer, aGlyphIds, pEncoding, 1, aInfo);
+
+ // write font descriptor
+ sal_Int32 nFontDescriptor = emitFontDescriptor( pFace, aInfo, 0, 0 );
+ if( nFontDescriptor )
+ {
+ // write font object
+ sal_Int32 nObject = createObject();
+ if( updateObject( nObject ) )
+ {
+ OStringBuffer aLine( 1024 );
+ aLine.append(
+ OString::number(nObject)
+ + " 0 obj\n"
+ "<</Type/Font/Subtype/TrueType"
+ "/BaseFont/" );
+ appendName( aInfo.m_aPSName, aLine );
+ aLine.append( "\n" );
+ if (!pFace->IsMicrosoftSymbolEncoded())
+ 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 "
+ + OString::number( nFontDescriptor )
+ + " 0 R>>\n"
+ "endobj\n\n" );
+ writeBuffer( aLine );
+
+ aRet[ rEmbed.m_nNormalFontID ] = nObject;
+ }
+ }
+
+ return aRet;
+}
+
+namespace
+{
+uint32_t fillSubsetArrays(const FontEmit& rSubset, sal_GlyphId* pGlyphIds, sal_Int32* pWidths,
+ sal_uInt8* pEncoding, sal_Int32* pEncToUnicodeIndex,
+ sal_Int32* pCodeUnitsPerGlyph, std::vector<sal_Ucs>& rCodeUnits,
+ sal_Int32& nToUnicodeStream)
+{
+ rCodeUnits.reserve(256);
+
+ // if it gets used then it will appear in s_subset.m_aMapping, otherwise 0 is fine
+ pWidths[0] = 0;
+
+ uint32_t nGlyphs = 1;
+ for (auto const& item : rSubset.m_aMapping)
+ {
+ sal_uInt8 nEnc = item.second.getGlyphId();
+
+ SAL_WARN_IF(pGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter",
+ "duplicate glyph");
+ SAL_WARN_IF(nEnc > rSubset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding");
+
+ pGlyphIds[nEnc] = item.first;
+ pEncoding[nEnc] = nEnc;
+ pEncToUnicodeIndex[nEnc] = static_cast<sal_Int32>(rCodeUnits.size());
+ pCodeUnitsPerGlyph[nEnc] = item.second.countCodes();
+ pWidths[nEnc] = item.second.getGlyphWidth();
+ for (sal_Int32 n = 0; n < pCodeUnitsPerGlyph[nEnc]; n++)
+ rCodeUnits.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");
+ }
+
+ return nGlyphs;
+}
+}
+
+bool PDFWriterImpl::emitType3Font(const vcl::font::PhysicalFontFace* pFace,
+ const FontSubset& rType3Font,
+ std::map<sal_Int32, sal_Int32>& rFontIDToObject)
+{
+ if (g_bDebugDisableCompression)
+ emitComment("PDFWriterImpl::emitType3Font");
+
+ const auto& rColorPalettes = pFace->GetColorPalettes();
+
+ FontSubsetInfo aSubsetInfo;
+ sal_GlyphId pTempGlyphIds[] = { 0 };
+ sal_uInt8 pTempEncoding[] = { 0 };
+ std::vector<sal_uInt8> aBuffer;
+ pFace->CreateFontSubset(aBuffer, pTempGlyphIds, pTempEncoding, 1, aSubsetInfo);
+
+ for (auto& rSubset : rType3Font.m_aSubsets)
+ {
+ sal_GlyphId pGlyphIds[256] = {};
+ sal_Int32 pWidths[256];
+ sal_uInt8 pEncoding[256] = {};
+ sal_Int32 pEncToUnicodeIndex[256] = {};
+ sal_Int32 pCodeUnitsPerGlyph[256] = {};
+ std::vector<sal_Ucs> aCodeUnits;
+ sal_Int32 nToUnicodeStream = 0;
+
+ // fill arrays and prepare encoding index map
+ auto nGlyphs = fillSubsetArrays(rSubset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex,
+ pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream);
+
+ // write font descriptor
+ sal_Int32 nFontDescriptor = 0;
+ if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4)
+ nFontDescriptor = emitFontDescriptor(pFace, aSubsetInfo, rSubset.m_nFontID, 0);
+
+ if (nToUnicodeStream)
+ nToUnicodeStream = createToUnicodeCMap(pEncoding, aCodeUnits, pCodeUnitsPerGlyph,
+ pEncToUnicodeIndex, nGlyphs);
+
+ // write font object
+ sal_Int32 nFontObject = createObject();
+ if (!updateObject(nFontObject))
+ return false;
+
+ OStringBuffer aLine(1024);
+ aLine.append(
+ OString::number(nFontObject)
+ + " 0 obj\n"
+ "<</Type/Font/Subtype/Type3/Name/");
+ appendName(aSubsetInfo.m_aPSName, aLine);
+
+ aLine.append(
+ "\n/FontBBox["
+ // note: Top and Bottom are reversed in VCL and PDF rectangles
+ + OString::number(aSubsetInfo.m_aFontBBox.Left())
+ + " "
+ + OString::number(aSubsetInfo.m_aFontBBox.Top())
+ + " "
+ + OString::number(aSubsetInfo.m_aFontBBox.Right())
+ + " "
+ + OString::number(aSubsetInfo.m_aFontBBox.Bottom() + 1)
+ + "]\n");
+
+ // tdf#155610
+ // Adobe Acrobat does not seem to like certain UPEMs, so instead of
+ // setting the FontMatrix scale relative to the UPEM, we always set to
+ // 0.001 (1000 UPEM) and scale everything if the font’s UPEM is
+ // different.
+ double fScale = 1000. / pFace->UnitsPerEm();
+
+ aLine.append("/FontMatrix[0.001 0 0 0.001 0 0]\n");
+
+ sal_Int32 pGlyphStreams[256] = {};
+ aLine.append("/CharProcs<<\n");
+ for (auto i = 1u; i < nGlyphs; i++)
+ {
+ auto nStream = createObject();
+ aLine.append("/"
+ + pFace->GetGlyphName(pGlyphIds[i], true)
+ + " "
+ + OString::number(nStream)
+ + " 0 R\n");
+ pGlyphStreams[i] = nStream;
+ }
+ aLine.append(">>\n"
+
+ "/Encoding<</Type/Encoding/Differences[1");
+ for (auto i = 1u; i < nGlyphs; i++)
+ aLine.append(" /" + pFace->GetGlyphName(pGlyphIds[i], true));
+ aLine.append("]>>\n"
+
+ "/FirstChar 0\n"
+ "/LastChar "
+ + OString::number(nGlyphs - 1)
+ + "\n"
+
+ "/Widths[");
+ for (auto i = 0u; i < nGlyphs; i++)
+ {
+ appendDouble(pWidths[i] * fScale, aLine);
+ aLine.append(" ");
+ }
+ aLine.append("]\n");
+
+ if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4)
+ {
+ aLine.append("/FontDescriptor " + OString::number(nFontDescriptor) + " 0 R\n");
+ }
+
+ auto nResources = createObject();
+ aLine.append("/Resources " + OString::number(nResources) + " 0 R\n");
+
+ if (nToUnicodeStream)
+ {
+ aLine.append("/ToUnicode " + OString::number(nToUnicodeStream) + " 0 R\n");
+ }
+
+ aLine.append(">>\n"
+ "endobj\n\n");
+
+ if (!writeBuffer(aLine))
+ return false;
+
+ std::set<sal_Int32> aUsedFonts;
+ std::list<BitmapEmit> aUsedBitmaps;
+ std::map<sal_uInt8, sal_Int32> aUsedAlpha;
+ ResourceDict aResourceDict;
+ std::list<StreamRedirect> aOutputStreams;
+
+ // Scale for glyph outlines.
+ double fScaleX = (GetDPIX() / 72.) * fScale;
+ double fScaleY = (GetDPIY() / 72.) * fScale;
+
+ for (auto i = 1u; i < nGlyphs; i++)
+ {
+ auto nStream = pGlyphStreams[i];
+ if (!updateObject(nStream))
+ return false;
+ OStringBuffer aContents(1024);
+ appendDouble(pWidths[i] * fScale, aContents);
+ aContents.append(" 0 d0\n");
+
+ const auto& rGlyph = rSubset.m_aMapping.find(pGlyphIds[i])->second;
+ const auto& rLayers = rGlyph.getColorLayers();
+ for (const auto& rLayer : rLayers)
+ {
+ aUsedFonts.insert(rLayer.m_nFontID);
+
+ aContents.append("q ");
+ // 0xFFFF is a special value means foreground color.
+ if (rLayer.m_nColorIndex != 0xFFFF)
+ {
+ auto& rPalette = rColorPalettes[0];
+ auto aColor(rPalette[rLayer.m_nColorIndex]);
+ appendNonStrokingColor(aColor, aContents);
+ aContents.append(" ");
+ if (aColor.GetAlpha() != 0xFF
+ && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4)
+ {
+ auto nAlpha = aColor.GetAlpha();
+ OStringBuffer aName(16);
+ aName.append("GS");
+ appendHex(nAlpha, aName);
+
+ aContents.append("/" + aName + " gs ");
+
+ if (aUsedAlpha.find(nAlpha) == aUsedAlpha.end())
+ {
+ auto nObject = createObject();
+ aUsedAlpha[nAlpha] = nObject;
+ pushResource(ResourceKind::ExtGState, aName.makeStringAndClear(),
+ nObject, aResourceDict, aOutputStreams);
+ }
+ }
+ }
+ aContents.append(
+ "BT "
+ "/F" + OString::number(rLayer.m_nFontID) + " ");
+ appendDouble(pFace->UnitsPerEm() * fScale, aContents);
+ aContents.append(
+ " Tf "
+ "<");
+ appendHex(rLayer.m_nSubsetGlyphID, aContents);
+ aContents.append(
+ ">Tj "
+ "ET "
+ "Q\n");
+ }
+
+ tools::Rectangle aRect;
+ const auto& rBitmapData = rGlyph.getColorBitmap(aRect);
+ if (!rBitmapData.empty())
+ {
+ SvMemoryStream aStream(const_cast<uint8_t*>(rBitmapData.data()), rBitmapData.size(),
+ StreamMode::READ);
+ vcl::PngImageReader aReader(aStream);
+
+ // When rendering an image with an alpha mask during PDF
+ // export, the alpha mask needs to be inverted
+ BitmapEx aBitmapEx = aReader.read();
+ if ( aBitmapEx.IsAlpha())
+ {
+ AlphaMask aAlpha = aBitmapEx.GetAlphaMask();
+ aAlpha.Invert();
+ aBitmapEx = BitmapEx(aBitmapEx.GetBitmap(), aAlpha);
+ }
+
+ auto aBitmapEmit = createBitmapEmit(aBitmapEx, Graphic(),
+ aUsedBitmaps, aResourceDict, aOutputStreams);
+
+ auto nObject = aBitmapEmit.m_aReferenceXObject.getObject();
+ aContents.append("q ");
+ appendDouble(aRect.GetWidth() * fScale, aContents);
+ aContents.append(" 0 0 ");
+ appendDouble(aRect.GetHeight() * fScale, aContents);
+ aContents.append(
+ + " "
+ + OString::number(aRect.getX())
+ + " "
+ + OString::number(aRect.getY())
+ + " cm "
+ "/Im"
+ + OString::number(nObject)
+ + " Do Q\n");
+ }
+
+ const auto& rOutline = rGlyph.getOutline();
+ if (rOutline.count())
+ {
+ aContents.append("q ");
+ appendDouble(fScaleX, aContents);
+ aContents.append(" 0 0 ");
+ appendDouble(fScaleY, aContents);
+ aContents.append(" 0 ");
+ appendDouble(m_aPages.back().getHeight() * -fScaleY, aContents, 3);
+ aContents.append(" cm\n");
+ m_aPages.back().appendPolyPolygon(rOutline, aContents);
+ aContents.append("f\n"
+ "Q\n");
+ }
+
+ aLine.setLength(0);
+ aLine.append(OString::number(nStream)
+ + " 0 obj\n<</Length "
+ + OString::number(aContents.getLength())
+ + ">>\nstream\n");
+ if (!writeBuffer(aLine))
+ return false;
+ if (!writeBuffer(aContents))
+ return false;
+ aLine.setLength(0);
+ aLine.append("endstream\nendobj\n\n");
+ if (!writeBuffer(aLine))
+ return false;
+ }
+
+ // write font dict
+ sal_Int32 nFontDict = 0;
+ if (!aUsedFonts.empty())
+ {
+ nFontDict = createObject();
+ aLine.setLength(0);
+ aLine.append(OString::number(nFontDict) + " 0 obj\n<<");
+ for (auto nFontID : aUsedFonts)
+ {
+ aLine.append("/F"
+ + OString::number(nFontID)
+ + " "
+ + OString::number(rFontIDToObject[nFontID])
+ + " 0 R");
+ }
+ aLine.append(">>\nendobj\n\n");
+ if (!updateObject(nFontDict))
+ return false;
+ if (!writeBuffer(aLine))
+ return false;
+ }
+
+ // write ExtGState objects
+ if (!aUsedAlpha.empty())
+ {
+ for (const auto & [ nAlpha, nObject ] : aUsedAlpha)
+ {
+ aLine.setLength(0);
+ aLine.append(OString::number(nObject) + " 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(nAlpha / 255., aLine);
+ aLine.append("/ca ");
+ appendDouble(nAlpha / 255., aLine);
+ }
+ aLine.append(">>\nendobj\n\n");
+ if (!updateObject(nObject))
+ return false;
+ if (!writeBuffer(aLine))
+ return false;
+ }
+ }
+
+ // write bitmap objects
+ for (auto& aBitmap : aUsedBitmaps)
+ writeBitmapObject(aBitmap);
+
+ // write resources dict
+ aLine.setLength(0);
+ aLine.append(OString::number(nResources) + " 0 obj\n");
+ aResourceDict.append(aLine, nFontDict);
+ aLine.append("endobj\n\n");
+ if (!updateObject(nResources))
+ return false;
+ if (!writeBuffer(aLine))
+ return false;
+
+ rFontIDToObject[rSubset.m_nFontID] = nFontObject;
+ }
+
+ return true;
+}
+
+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 std::vector<sal_Ucs>& rCodeUnits,
+ const sal_Int32* pCodeUnitsPerGlyph,
+ const sal_Int32* pEncToUnicodeIndex,
+ uint32_t nGlyphs )
+{
+ int nMapped = 0;
+ for (auto n = 0u; n < nGlyphs; ++n)
+ if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[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 (auto n = 0u; n < nGlyphs; ++n)
+ {
+ if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]])
+ {
+ if( (nCount % 100) == 0 )
+ {
+ if( nCount )
+ aContents.append( "endbfchar\n" );
+ aContents.append( OString::number(static_cast<sal_Int32>(std::min(nMapped-nCount, 100)) )
+ + " 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>(rCodeUnits[nIndex + j] / 256), aContents );
+ appendHex( static_cast<sal_Int8>(rCodeUnits[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( OString::number(nStream ) + " 0 obj\n<</Length " );
+ sal_uInt64 nLen = 0;
+ if (!g_bDebugDisableCompression)
+ {
+ nLen = aStream.Tell();
+ aStream.Seek( 0 );
+ aLine.append( OString::number(nLen) + "/Filter/FlateDecode" );
+ }
+ else
+ aLine.append( aContents.getLength() );
+ aLine.append( ">>\nstream\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ checkAndEnableStreamEncryption( nStream );
+ if (!g_bDebugDisableCompression)
+ {
+ CHECK_RETURN( writeBufferBytes( aStream.GetData(), nLen ) );
+ }
+ else
+ {
+ CHECK_RETURN( writeBuffer( aContents ) );
+ }
+ disableStreamEncryption();
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\n"
+ "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ return nStream;
+}
+
+sal_Int32 PDFWriterImpl::emitFontDescriptor( const vcl::font::PhysicalFontFace* pFace, 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( pFace->GetItalic() == ITALIC_NORMAL || pFace->GetItalic() == ITALIC_OBLIQUE )
+ nFontFlags |= (1 << 6);
+ if( pFace->GetPitch() == PITCH_FIXED )
+ nFontFlags |= 1;
+ if( pFace->GetFamilyType() == FAMILY_SCRIPT )
+ nFontFlags |= (1 << 3);
+ else if( pFace->GetFamilyType() == FAMILY_ROMAN )
+ nFontFlags |= (1 << 1);
+
+ sal_Int32 nFontDescriptor = createObject();
+ CHECK_RETURN( updateObject( nFontDescriptor ) );
+ aLine.setLength( 0 );
+ aLine.append(
+ OString::number(nFontDescriptor)
+ + " 0 obj\n"
+ "<</Type/FontDescriptor/FontName/" );
+ appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
+ aLine.append( "\n"
+ "/Flags "
+ + OString::number( nFontFlags )
+ + "\n"
+ "/FontBBox["
+ // note: Top and Bottom are reversed in VCL and PDF rectangles
+ + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Left()) )
+ + " "
+ + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Top()) )
+ + " "
+ + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Right()) )
+ + " "
+ + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Bottom()+1) )
+ + "]/ItalicAngle " );
+ if( pFace->GetItalic() == ITALIC_OBLIQUE || pFace->GetItalic() == ITALIC_NORMAL )
+ aLine.append( "-30" );
+ else
+ aLine.append( "0" );
+ aLine.append( "\n"
+ "/Ascent "
+ + OString::number( static_cast<sal_Int32>(rInfo.m_nAscent) )
+ + "\n"
+ "/Descent "
+ + OString::number( static_cast<sal_Int32>(-rInfo.m_nDescent) )
+ + "\n"
+ "/CapHeight "
+ + OString::number( static_cast<sal_Int32>(rInfo.m_nCapHeight) )
+ // According to PDF reference 1.4 StemV is required
+ // seems a tad strange to me, but well ...
+ + "\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( " " + OString::number(nFontStream) + " 0 R\n" );
+ }
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+
+ 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()
+{
+ OStringBuffer aLine( 1024 );
+
+ std::map< sal_Int32, sal_Int32 > aFontIDToObject;
+
+ for (const auto & subset : m_aSubsets)
+ {
+ for (auto & s_subset :subset.second.m_aSubsets)
+ {
+ sal_GlyphId pGlyphIds[ 256 ] = {};
+ sal_Int32 pWidths[ 256 ];
+ sal_uInt8 pEncoding[ 256 ] = {};
+ sal_Int32 pEncToUnicodeIndex[ 256 ] = {};
+ sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {};
+ std::vector<sal_Ucs> aCodeUnits;
+ sal_Int32 nToUnicodeStream = 0;
+
+ // fill arrays and prepare encoding index map
+ auto nGlyphs = fillSubsetArrays(s_subset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex,
+ pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream);
+
+ std::vector<sal_uInt8> aBuffer;
+ FontSubsetInfo aSubsetInfo;
+ const auto* pFace = subset.first;
+ if (pFace->CreateFontSubset(aBuffer, pGlyphIds, pEncoding, nGlyphs, aSubsetInfo))
+ {
+ // create font stream
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::emitFonts" );
+ }
+ sal_Int32 nFontStream = createObject();
+ sal_Int32 nStreamLengthObject = createObject();
+ if ( !updateObject( nFontStream ) ) return false;
+ aLine.setLength( 0 );
+ aLine.append( OString::number(nFontStream)
+ + " 0 obj\n"
+ "<</Length "
+ + OString::number( 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( OString::number(static_cast<sal_Int32>(aBuffer.size()))
+ + ">>\n"
+ "stream\n" );
+ if ( !writeBuffer( aLine ) ) return false;
+ if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
+
+ // copy font file
+ beginCompression();
+ checkAndEnableStreamEncryption( nFontStream );
+ if (!writeBufferBytes(aBuffer.data(), aBuffer.size()))
+ return false;
+ }
+ 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?
+ {
+ // get the PFB-segment lengths
+ ThreeInts aSegmentLengths = {0,0,0};
+ getPfbSegmentLengths(aBuffer.data(), static_cast<int>(aBuffer.size()), aSegmentLengths);
+ // the lengths below are mandatory for PDF-exported Type1 fonts
+ // because the PFB segment headers get stripped! WhyOhWhy.
+ aLine.append( OString::number(static_cast<sal_Int32>(aSegmentLengths[0]) )
+ + "/Length2 "
+ + OString::number( static_cast<sal_Int32>(aSegmentLengths[1]) )
+ + "/Length3 "
+ + OString::number( static_cast<sal_Int32>(aSegmentLengths[2]) )
+ + ">>\n"
+ "stream\n" );
+ if ( !writeBuffer( aLine ) ) return false;
+ if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
+
+ // emit PFB-sections without section headers
+ beginCompression();
+ checkAndEnableStreamEncryption( nFontStream );
+ if ( !writeBufferBytes( &aBuffer[6], aSegmentLengths[0] ) ) return false;
+ if ( !writeBufferBytes( &aBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ) return false;
+ if ( !writeBufferBytes( &aBuffer[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();
+
+ 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 ) ) return false;
+
+ // emit stream length object
+ if ( !updateObject( nStreamLengthObject ) ) return false;
+ aLine.setLength( 0 );
+ aLine.append( OString::number(nStreamLengthObject)
+ + " 0 obj\n"
+ + OString::number( static_cast<sal_Int64>(nEndPos-nStartPos) )
+ + "\nendobj\n\n" );
+ if ( !writeBuffer( aLine ) ) return false;
+
+ // write font descriptor
+ sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first, aSubsetInfo, s_subset.m_nFontID, nFontStream );
+
+ if( nToUnicodeStream )
+ nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits, pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs );
+
+ sal_Int32 nFontObject = createObject();
+ if ( !updateObject( nFontObject ) ) return false;
+ aLine.setLength( 0 );
+ aLine.append( OString::number(nFontObject) + " 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 "
+ + OString::number( static_cast<sal_Int32>(nGlyphs-1) )
+ + "\n"
+ "/Widths[" );
+ for (auto i = 0u; i < nGlyphs; i++)
+ {
+ aLine.append( pWidths[ i ] );
+ aLine.append( ((i & 15) == 15) ? "\n" : " " );
+ }
+ aLine.append( "]\n"
+ "/FontDescriptor "
+ + OString::number( nFontDescriptor )
+ + " 0 R\n" );
+ if( nToUnicodeStream )
+ {
+ aLine.append( "/ToUnicode "
+ + OString::number( nToUnicodeStream )
+ + " 0 R\n" );
+ }
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ if ( !writeBuffer( aLine ) ) return false;
+
+ aFontIDToObject[ s_subset.m_nFontID ] = nFontObject;
+ }
+ else
+ {
+ OStringBuffer aErrorComment( 256 );
+ aErrorComment.append( "CreateFontSubset failed for font \""
+ + OUStringToOString( pFace->GetFamilyName(), RTL_TEXTENCODING_UTF8 )
+ + "\"" );
+ if( pFace->GetItalic() == ITALIC_NORMAL )
+ aErrorComment.append( " italic" );
+ else if( pFace->GetItalic() == ITALIC_OBLIQUE )
+ aErrorComment.append( " oblique" );
+ aErrorComment.append( " weight=" + OString::number( sal_Int32(pFace->GetWeight()) ) );
+ emitComment( aErrorComment.getStr() );
+ }
+ }
+ }
+
+ // 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;
+ }
+ }
+
+ // emit Type3 fonts
+ for (auto const& it : m_aType3Fonts)
+ {
+ if (!emitType3Font(it.first, it.second, aFontIDToObject))
+ return false;
+ }
+
+ OStringBuffer aFontDict( 1024 );
+ aFontDict.append( OString::number(getFontDictObject())
+ + " 0 obj\n"
+ "<<" );
+ int ni = 0;
+ for (auto const& itemMap : aFontIDToObject)
+ {
+ aFontDict.append( "/F"
+ + OString::number( itemMap.first )
+ + " "
+ + OString::number( itemMap.second )
+ + " 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 ) ) 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( OString::number(nResourceDict)
+ + " 0 obj\n" );
+ m_aGlobalResourceDict.append( aLine, getFontDictObject() );
+ aLine.append( "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ 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( OString::number(rItem.m_nObject)
+ + " 0 obj\n"
+ "<<" );
+ // number of visible children (all levels)
+ if( i > 0 || aCounts[0] > 0 )
+ {
+ aLine.append( "/Count " + OString::number( aCounts[i] ) );
+ }
+ if( ! rItem.m_aChildren.empty() )
+ {
+ // children list: First, Last
+ aLine.append( "/First "
+ + OString::number( m_aOutline[rItem.m_aChildren.front()].m_nObject )
+ + " 0 R/Last "
+ + OString::number( m_aOutline[rItem.m_aChildren.back()].m_nObject )
+ + " 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 "
+ + OString::number( rItem.m_nParentObject )
+ + " 0 R" );
+ if( rItem.m_nPrevObject )
+ {
+ aLine.append( "/Prev "
+ + OString::number( rItem.m_nPrevObject )
+ + " 0 R" );
+ }
+ if( rItem.m_nNextObject )
+ {
+ aLine.append( "/Next "
+ + OString::number( rItem.m_nNextObject )
+ + " 0 R" );
+ }
+ }
+ aLine.append( ">>\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ }
+
+ 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;
+}
+
+void PDFWriterImpl::addDocumentAttachedFile(OUString const& rFileName, OUString const& rMimeType, OUString const& rDescription, std::unique_ptr<PDFOutputStream> rStream)
+{
+ sal_Int32 nObjectID = addEmbeddedFile(std::move(rStream), rMimeType);
+ auto& rAttachedFile = m_aDocumentAttachedFiles.emplace_back();
+ rAttachedFile.maFilename = rFileName;
+ rAttachedFile.maMimeType = rMimeType;
+ rAttachedFile.maDescription = rDescription;
+ rAttachedFile.mnEmbeddedFileObjectId = nObjectID;
+ rAttachedFile.mnObjectId = createObject();
+}
+
+sal_Int32 PDFWriterImpl::addEmbeddedFile(std::unique_ptr<PDFOutputStream> rStream, OUString const& rMimeType)
+{
+ sal_Int32 aObjectID = createObject();
+ auto& rEmbedded = m_aEmbeddedFiles.emplace_back();
+ rEmbedded.m_nObject = aObjectID;
+ rEmbedded.m_aSubType = rMimeType;
+ rEmbedded.m_pStream = std::move(rStream);
+ return aObjectID;
+}
+
+sal_Int32 PDFWriterImpl::addEmbeddedFile(BinaryDataContainer const & rDataContainer)
+{
+ sal_Int32 aObjectID = createObject();
+ m_aEmbeddedFiles.emplace_back();
+ m_aEmbeddedFiles.back().m_nObject = aObjectID;
+ m_aEmbeddedFiles.back().m_aDataContainer = rDataContainer;
+ return aObjectID;
+}
+
+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));
+ aLine.setLength(0);
+
+ CHECK_RETURN(writeBufferBytes(aMemoryStream.GetData(), aMemoryStream.GetSize()));
+
+ aLine.append("\nendstream\nendobj\n\n");
+ CHECK_RETURN(writeBuffer(aLine));
+ aLine.setLength(0);
+ }
+
+ if (!updateObject(rScreen.m_nObject))
+ continue;
+
+ // Annot dictionary.
+ aLine.append(OString::number(rScreen.m_nObject)
+ + " 0 obj\n"
+ "<</Type/Annot"
+ "/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 "
+ + OString::number(rScreen.m_nObject)
+ + " 0 R ");
+
+ // Rendition dictionary.
+ aLine.append("/R<</Type/Rendition /S/MR ");
+
+ // MediaClip dictionary.
+ aLine.append("/C<</Type/MediaClip /S/MCD ");
+ if (bEmbed)
+ {
+ aLine.append("\n/D << /Type /Filespec /F (<embedded file>) ");
+ if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
+ { // ISO 14289-1:2014, Clause: 7.11
+ aLine.append("/UF (<embedded file>) ");
+ }
+ aLine.append("/EF << /F ");
+ aLine.append(rScreen.m_nTempFileObject);
+ aLine.append(" 0 R >>");
+ }
+ else
+ {
+ // Linked.
+ aLine.append("\n/D << /Type /Filespec /FS /URL /F ");
+ appendLiteralStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine, osl_getThreadTextEncoding());
+ if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
+ { // ISO 14289-1:2014, Clause: 7.11
+ aLine.append("/UF ");
+ appendUnicodeTextStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine);
+ }
+ }
+ if (PDFWriter::PDFVersion::PDF_1_6 <= m_aContext.Version
+ && !rScreen.m_AltText.isEmpty())
+ { // ISO 14289-1:2014, Clause: 7.11
+ aLine.append("/Desc ");
+ appendUnicodeTextStringEncrypt(rScreen.m_AltText, rScreen.m_nObject, aLine);
+ }
+ aLine.append(" >>\n"); // end of /D
+ // Allow playing the video via a tempfile.
+ aLine.append("/P <</TF (TEMPACCESS)>>");
+ // ISO 14289-1:2014, Clause: 7.18.6.2
+ aLine.append("/CT ");
+ appendLiteralStringEncrypt(rScreen.m_MimeType, rScreen.m_nObject, aLine);
+ // ISO 14289-1:2014, Clause: 7.18.6.2
+ // Alt text is a "Multi-language Text Array"
+ aLine.append(" /Alt [ () ");
+ appendUnicodeTextStringEncrypt(rScreen.m_AltText, rScreen.m_nObject, aLine);
+ aLine.append(" ] "
+ ">>");
+
+ // End Rendition dictionary by requesting play/pause/stop controls.
+ aLine.append("/P<</BE<</C true >>>>"
+ ">>");
+
+ // End Action dictionary.
+ aLine.append("/OP 0 >>");
+
+ if (-1 != rScreen.m_nStructParent)
+ {
+ aLine.append("\n/StructParent "
+ + OString::number(rScreen.m_nStructParent)
+ + "\n");
+ }
+
+ // End Annot dictionary.
+ aLine.append("/P "
+ + OString::number(m_aPages[rScreen.m_nPage].m_nPageObject)
+ + " 0 R\n>>\nendobj\n\n");
+ CHECK_RETURN(writeBuffer(aLine));
+ }
+
+ 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.getLength() > 0 ?
+ m_aContext.BaseURL :
+ //use dummy location if empty
+ u"http://ahost.ax"_ustr),
+ 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 != -1)
+ {
+ aLine.append( "/StructParent " );
+ aLine.append( rLink.m_nStructParent );
+ }
+ aLine.append( ">>\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ }
+
+ 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("] ");
+}
+
+} // end anonymous namespace
+
+void PDFWriterImpl::emitTextAnnotationLine(OStringBuffer & aLine, PDFNoteEntry const & rNote)
+{
+ appendObjectID(rNote.m_nObject, aLine);
+
+ aLine.append("<</Type /Annot /Subtype ");
+ if (rNote.m_aContents.maPolygons.size() == 1)
+ {
+ auto const& rPolygon = rNote.m_aContents.maPolygons[0];
+ aLine.append(rPolygon.isClosed() ? "/Polygon " : "/Polyline ");
+ aLine.append("/Vertices [");
+ for (sal_uInt32 i = 0; i < rPolygon.count(); ++i)
+ {
+ appendDouble(convertMm100ToPoint(rPolygon.getB2DPoint(i).getX()), aLine, nLog10Divisor);
+ aLine.append(" ");
+ appendDouble(m_aPages[rNote.m_nPage].getHeight()
+ - convertMm100ToPoint(rPolygon.getB2DPoint(i).getY()),
+ aLine, nLog10Divisor);
+ aLine.append(" ");
+ }
+ aLine.append("] ");
+ aLine.append("/C [");
+ appendColor(rNote.m_aContents.annotColor, aLine, false);
+ aLine.append("] ");
+ if (rPolygon.isClosed())
+ {
+ aLine.append("/IC [");
+ appendColor(rNote.m_aContents.interiorColor, aLine, false);
+ aLine.append("] ");
+ }
+ }
+ else if (rNote.m_aContents.maPolygons.size() > 1)
+ {
+ aLine.append("/Ink /InkList [");
+ for (auto const& rPolygon : rNote.m_aContents.maPolygons)
+ {
+ aLine.append("[");
+ for (sal_uInt32 i = 0; i < rPolygon.count(); ++i)
+ {
+ appendDouble(convertMm100ToPoint(rPolygon.getB2DPoint(i).getX()), aLine,
+ nLog10Divisor);
+ aLine.append(" ");
+ appendDouble(m_aPages[rNote.m_nPage].getHeight()
+ - convertMm100ToPoint(rPolygon.getB2DPoint(i).getY()),
+ aLine, nLog10Divisor);
+ aLine.append(" ");
+ }
+ aLine.append("]");
+ aLine.append("/C [");
+ appendColor(rNote.m_aContents.annotColor, aLine, false);
+ aLine.append("] ");
+ }
+ aLine.append("] ");
+ }
+ else if (rNote.m_aContents.isFreeText)
+ aLine.append("/FreeText ");
+ else
+ aLine.append("/Text ");
+
+ aLine.append("/BS<</W 0>>");
+
+ // 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))
+ return false;
+ }
+
+ {
+
+ if (!updateObject(rPopUp.m_nObject))
+ return false;
+
+ OStringBuffer aLine(1024);
+
+ emitPopupAnnotationLine(aLine, rPopUp);
+
+ if (!writeBuffer(aLine))
+ 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"_ostr ][ "Standard"_ostr ] = 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"_ostr;
+ rButton.m_aMKDictCAString = ""_ostr;
+}
+
+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 );
+ writeBuffer( "/Tx BMC\nEMC\n" );
+
+ endRedirect();
+ pop();
+
+ rEdit.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = 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
+ writeBuffer( "/Tx BMC\nEMC\n" );
+
+ endRedirect();
+ pop();
+
+ rBox.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = 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 );
+ drawRectangle( aCheckRect );
+ writeBuffer( " Q\n" );
+ 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() ) );
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace();
+ Pop();
+
+ // make sure OpenSymbol is embedded, and includes our checkmark
+ const sal_Unicode cMark=0x2713;
+ const auto nGlyphId = pFontInstance->GetGlyphIndex(cMark);
+ const auto nGlyphWidth = pFontInstance->GetGlyphWidth(nGlyphId, false, false);
+
+ sal_uInt8 nMappedGlyph;
+ sal_Int32 nMappedFontObject;
+ registerGlyph(nGlyphId, pFace, pFontInstance, { cMark }, 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"_ostr;
+ rBox.m_aMKDictCAString = "8"_ostr;
+ 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 );
+ endRedirect();
+ rBox.m_aAppearances[ "N"_ostr ][ "Yes"_ostr ] = pCheckStream;
+
+ // write 'unchecked' appearance stream
+ SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
+ beginRedirect( pUncheckStream, aCheckRect );
+ writeBuffer( "/Tx BMC\nEMC\n" );
+ endRedirect();
+ rBox.m_aAppearances[ "N"_ostr ][ "Off"_ostr ] = 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 );
+ drawEllipse( aCheckRect );
+ writeBuffer( " Q\n" );
+ setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
+ drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
+
+ pop();
+
+ //to encrypt this (el)
+ rBox.m_aMKDict = "/CA"_ostr;
+ //after this assignment, to m_aMKDic cannot be added anything
+ rBox.m_aMKDictCAString = "l"_ostr;
+
+ 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 );
+ 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" );
+ endRedirect();
+
+ pop();
+ rBox.m_aAppearances[ "N"_ostr ][ "Yes"_ostr ] = pCheckStream;
+
+ SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
+ beginRedirect( pUncheckStream, aCheckRect );
+ writeBuffer( "/Tx BMC\nEMC\n" );
+ endRedirect();
+ rBox.m_aAppearances[ "N"_ostr ][ "Off"_ostr ] = 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 ) );
+ checkAndEnableStreamEncryption( nObject );
+ CHECK_RETURN( writeBufferBytes( pAppearanceStream->GetData(), nStreamLen ) );
+ disableStreamEncryption();
+ CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n" ) );
+
+ 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"_ostr );
+ if( app_it != rWidget.m_aAppearances.end() )
+ {
+ auto stream_it = app_it->second.find( "Yes"_ostr );
+ 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"_ostr );
+ if( app_it != rWidget.m_aAppearances.end() )
+ {
+ auto stream_it = app_it->second.find( "Off"_ostr );
+ 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;
+ }
+
+ if (-1 != rWidget.m_nStructParent)
+ {
+ aLine.append("/StructParent ");
+ aLine.append(rWidget.m_nStructParent);
+ aLine.append("\n");
+ }
+
+ 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 (!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:
+ nFlags |= 32;
+ break;
+ case PDFWriter::PDF:
+ 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 ) );
+ }
+ 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;
+}
+
+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
+ {
+ if( m_bWrite && aData.hasElements() )
+ {
+ sal_Int32 nBytes = aData.getLength();
+ m_pWriter->writeBufferBytes( aData.getConstArray(), nBytes );
+ }
+ }
+ virtual void SAL_CALL flush() override {}
+ virtual void SAL_CALL closeOutput() override
+ {
+ m_bWrite = false;
+ }
+};
+
+bool PDFWriterImpl::emitEmbeddedFiles()
+{
+ for (auto& rEmbeddedFile : m_aEmbeddedFiles)
+ {
+ if (!updateObject(rEmbeddedFile.m_nObject))
+ continue;
+
+ sal_Int32 nSizeObject = createObject();
+ sal_Int32 nParamsObject = createObject();
+
+ OStringBuffer aLine;
+ aLine.append(rEmbeddedFile.m_nObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<< /Type /EmbeddedFile");
+ if (!rEmbeddedFile.m_aSubType.isEmpty())
+ {
+ aLine.append("/Subtype /");
+ appendName(rEmbeddedFile.m_aSubType, aLine);
+ }
+ aLine.append(" /Length ");
+ appendObjectReference(nSizeObject, aLine);
+ aLine.append(" /Params ");
+ appendObjectReference(nParamsObject, aLine);
+ aLine.append(">>\nstream\n");
+ checkAndEnableStreamEncryption(rEmbeddedFile.m_nObject);
+ CHECK_RETURN(writeBuffer(aLine));
+ disableStreamEncryption();
+ aLine.setLength(0);
+
+ sal_Int64 nSize{};
+ if (!rEmbeddedFile.m_aDataContainer.isEmpty())
+ {
+ nSize = rEmbeddedFile.m_aDataContainer.getSize();
+ CHECK_RETURN(writeBufferBytes(rEmbeddedFile.m_aDataContainer.getData(), rEmbeddedFile.m_aDataContainer.getSize()));
+ }
+ else if (rEmbeddedFile.m_pStream)
+ {
+ sal_uInt64 nBegin = getCurrentFilePosition();
+ css::uno::Reference<css::io::XOutputStream> xStream(new PDFStreamIf(this));
+ rEmbeddedFile.m_pStream->write(xStream);
+ rEmbeddedFile.m_pStream.reset();
+ xStream.clear();
+ nSize = sal_Int64(getCurrentFilePosition() - nBegin);
+ }
+ aLine.append("\nendstream\nendobj\n\n");
+ CHECK_RETURN(writeBuffer(aLine));
+ aLine.setLength(0);
+
+ if (!updateObject(nSizeObject))
+ return false;
+ aLine.append(nSizeObject);
+ aLine.append(" 0 obj\n");
+ aLine.append(nSize);
+ aLine.append("\nendobj\n\n");
+ if (!writeBuffer(aLine))
+ return false;
+ aLine.setLength(0);
+
+ if (!updateObject(nParamsObject))
+ return false;
+ aLine.append(nParamsObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<<");
+ aLine.append("/Size ");
+ aLine.append(nSize);
+ aLine.append(">>");
+ aLine.append("\nendobj\n\n");
+ if (!writeBuffer(aLine))
+ return false;
+ }
+ 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)
+ {
+ removePlaceholderSE(m_aStructure, m_aStructure[0]);
+ // 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" );
+
+ if( m_aPages.empty() ) // sanity check, this should not happen
+ aLine.append( "/MediaBox[0 0 595 842]\n" ); // default A4 size in pt
+
+ 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 ) );
+
+ // emit annotation objects
+ CHECK_RETURN( emitAnnotations() );
+ CHECK_RETURN( emitEmbeddedFiles() );
+
+ // emit attached files
+ for (auto & rAttachedFile : m_aDocumentAttachedFiles)
+ {
+ if (!updateObject(rAttachedFile.mnObjectId))
+ return false;
+ aLine.setLength( 0 );
+
+ appendObjectID(rAttachedFile.mnObjectId, aLine);
+ aLine.append("<</Type /Filespec");
+ aLine.append("/F<");
+ PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine);
+ aLine.append("> ");
+ if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
+ {
+ aLine.append("/UF<");
+ PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine);
+ aLine.append("> ");
+ }
+ if (!rAttachedFile.maDescription.isEmpty())
+ {
+ aLine.append("/Desc <");
+ PDFWriter::AppendUnicodeTextString(rAttachedFile.maDescription, aLine);
+ aLine.append("> ");
+ }
+ aLine.append("/EF <</F ");
+ appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine);
+ aLine.append(">>");
+ aLine.append(">>\nendobj\n\n");
+ CHECK_RETURN( writeBuffer( aLine ) );
+ }
+
+ // 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_aDocumentAttachedFiles.empty())
+ {
+ aLine.append("/Names ");
+ aLine.append("<</EmbeddedFiles <</Names [");
+ for (auto & rAttachedFile : m_aDocumentAttachedFiles)
+ {
+ aLine.append('<');
+ PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine);
+ aLine.append('>');
+ aLine.append(' ');
+ appendObjectReference(rAttachedFile.mnObjectId, aLine);
+ }
+ aLine.append("]>>>>");
+ aLine.append("\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 842]\n" ); //Open fit width, default A4 height in pt, OK to use hardcoded value here?
+ break;
+ case PDFWriter::FitVisible :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef );
+ aLine.append( " /FitBH 842]\n" ); //Open fit visible, , default A4 height in pt, OK to use hardcoded value here?
+ 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.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.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)
+ {
+ 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 );
+}
+
+#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 );
+}
+
+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 ) )
+ 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 ) )
+ 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 ) ) 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 = writeBufferBytes( 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" ) )
+ 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 ) ) 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 ) ) return 0;
+
+ return nOIObject;
+}
+
+static void lcl_assignMeta(std::u16string_view aValue, OString& aMeta)
+{
+ if (!aValue.empty())
+ {
+ aMeta = OUStringToOString(comphelper::string::encodeForXml(aValue), RTL_TEXTENCODING_UTF8);
+ }
+}
+
+static void lcl_assignMeta(const css::uno::Sequence<OUString>& rValues, std::vector<OString>& rMeta)
+{
+ if (!rValues.hasElements())
+ return;
+
+ std::vector<OString> aNewMetaVector;
+ aNewMetaVector.reserve(rValues.getLength());
+
+ for (const OUString& rValue : rValues)
+ {
+ aNewMetaVector.emplace_back(
+ OUStringToOString(comphelper::string::encodeForXml(rValue), RTL_TEXTENCODING_UTF8));
+ }
+
+ rMeta = std::move(aNewMetaVector);
+}
+
+// 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);
+ aMetadata.msPDFVersion = getPDFVersionStr(m_aContext.Version);
+ lcl_assignMeta(m_aContext.DocumentInfo.Keywords, aMetadata.msKeywords);
+ lcl_assignMeta(m_aContext.DocumentInfo.Contributor, aMetadata.maContributor);
+ lcl_assignMeta(m_aContext.DocumentInfo.Coverage, aMetadata.msCoverage);
+ lcl_assignMeta(m_aContext.DocumentInfo.Identifier, aMetadata.msIdentifier);
+ lcl_assignMeta(m_aContext.DocumentInfo.Publisher, aMetadata.maPublisher);
+ lcl_assignMeta(m_aContext.DocumentInfo.Relation, aMetadata.maRelation);
+ lcl_assignMeta(m_aContext.DocumentInfo.Rights, aMetadata.msRights);
+ lcl_assignMeta(m_aContext.DocumentInfo.Source, aMetadata.msSource);
+ lcl_assignMeta(m_aContext.DocumentInfo.Type, aMetadata.msType);
+ 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 ) )
+ return 0;
+ //emit the stream
+ if ( !writeBufferBytes( aMetadata.getData(), aMetadata.getSize() ) )
+ return 0;
+
+ if( ! writeBuffer( "\nendstream\nendobj\n\n" ) )
+ 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 ) )
+ 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" ) );
+
+ 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 ) );
+
+ 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 ) );
+ }
+
+ // 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_aDocumentAttachedFiles.empty())
+ {
+ aLine.append( "/AdditionalStreams [" );
+ for (auto const& rAttachedFile : m_aDocumentAttachedFiles)
+ {
+ aLine.append( "/" );
+ appendName(rAttachedFile.maMimeType, aLine);
+ aLine.append(" ");
+ appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine);
+ aLine.append("\n");
+ }
+ aLine.append( "]\n" );
+ }
+
+ aLine.append( ">>\n"
+ "startxref\n" );
+ aLine.append( static_cast<sal_Int64>(nXRefOffset) );
+ aLine.append( "\n"
+ "%%EOF\n" );
+ return writeBuffer( aLine );
+}
+
+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
+}
+
+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 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 LogicalFontInstance* pFontInstance = GetFontInstance();
+ const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace();
+ sal_Int32 nFontID = 0;
+ auto it = m_aSystemFonts.find( pFace );
+ if( it != m_aSystemFonts.end() )
+ nFontID = it->second.m_nNormalFontID;
+ else
+ {
+ nFontID = m_nNextFID++;
+ m_aSystemFonts[ pFace ] = EmbedFont();
+ m_aSystemFonts[ pFace ].m_pFontInstance = const_cast<LogicalFontInstance*>(pFontInstance);
+ m_aSystemFonts[ pFace ].m_nNormalFontID = nFontID;
+ }
+
+ Pop();
+ return nFontID;
+}
+
+void PDFWriterImpl::registerSimpleGlyph(const sal_GlyphId nFontGlyphId,
+ const vcl::font::PhysicalFontFace* pFace,
+ const std::vector<sal_Ucs>& rCodeUnits,
+ sal_Int32 nGlyphWidth,
+ sal_uInt8& nMappedGlyph,
+ sal_Int32& nMappedFontObject)
+{
+ FontSubset& rSubset = m_aSubsets[ pFace ];
+ // 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(XUnits(pFace->UnitsPerEm(), 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::registerGlyph(const sal_GlyphId nFontGlyphId,
+ const vcl::font::PhysicalFontFace* pFace,
+ const LogicalFontInstance* pFont,
+ const std::vector<sal_Ucs>& rCodeUnits, sal_Int32 nGlyphWidth,
+ sal_uInt8& nMappedGlyph, sal_Int32& nMappedFontObject)
+{
+ auto bVariations = !pFace->GetVariations(*pFont).empty();
+ // tdf#155161
+ // PDF doesn’t support CFF2 table and we currently don’t convert them to
+ // Type 1 (like we do with CFF table), so treat it like fonts with
+ // variations and embed as Type 3 fonts.
+ if (!pFace->GetRawFontData(HB_TAG('C', 'F', 'F', '2')).empty())
+ bVariations = true;
+
+ if (pFace->IsColorFont() || bVariations)
+ {
+ // Font has colors, check if this glyph has color layers or bitmap.
+ tools::Rectangle aRect;
+ auto aLayers = pFace->GetGlyphColorLayers(nFontGlyphId);
+ auto aBitmap = pFace->GetGlyphColorBitmap(nFontGlyphId, aRect);
+ if (!aLayers.empty() || !aBitmap.empty() || bVariations)
+ {
+ auto& rSubset = m_aType3Fonts[pFace];
+ 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
+ auto& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[nFontGlyphId];
+ rNewGlyphEmit.setGlyphId(nNewId);
+ rNewGlyphEmit.setGlyphWidth(nGlyphWidth);
+ for (const auto nCode : rCodeUnits)
+ rNewGlyphEmit.addCode(nCode);
+
+ // add color layers to the glyphs
+ if (!aLayers.empty())
+ {
+ for (const auto& aLayer : aLayers)
+ {
+ sal_uInt8 nLayerGlyph;
+ sal_Int32 nLayerFontID;
+ registerSimpleGlyph(aLayer.nGlyphIndex, pFace, rCodeUnits, nGlyphWidth,
+ nLayerGlyph, nLayerFontID);
+
+ rNewGlyphEmit.addColorLayer(
+ { nLayerFontID, nLayerGlyph, aLayer.nColorIndex });
+ }
+ }
+ else if (!aBitmap.empty())
+ rNewGlyphEmit.setColorBitmap(aBitmap, aRect);
+ else if (bVariations)
+ rNewGlyphEmit.setOutline(pFont->GetGlyphOutlineUntransformed(nFontGlyphId));
+
+ // add new glyph to font mapping
+ Glyph& rNewGlyph = rSubset.m_aMapping[nFontGlyphId];
+ rNewGlyph.m_nFontID = nMappedFontObject;
+ rNewGlyph.m_nSubsetGlyphID = nNewId;
+ }
+ return;
+ }
+ }
+
+ // If we reach here then the glyph has no color layers.
+ registerSimpleGlyph(nFontGlyphId, pFace, rCodeUnits, nGlyphWidth, nMappedGlyph,
+ nMappedFontObject);
+}
+
+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() += basegfx::B2DPoint(nOff, nOff);
+ drawLayout( rLayout, rText, bTextLines );
+ rLayout.DrawBase() -= basegfx::B2DPoint(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,
+ sal_Int32 nFontHeight)
+{
+ double nXOffset = 0;
+ Point aCurPos(SubPixelToLogic(rGlyphs[0].m_aPos));
+ 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;
+
+ // perform artificial italics if necessary
+ double fSkew = 0.0;
+ if (rGlyphs[i].m_pFont->NeedsArtificialItalic())
+ fSkew = ARTIFICIAL_ITALIC_SKEW;
+
+ 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 += SubPixelToLogic(basegfx::B2DPoint(nXOffset / fXScale, 0)) - SubPixelToLogic(basegfx::B2DPoint());
+ if( i < rGlyphs.size()-1 )
+ // #i120627# the text on the Y axis is reversed when export ppt file to PDF format
+ {
+ double nOffsetX = rGlyphs[i+1].m_aPos.getX() - rGlyphs[i].m_aPos.getX();
+ double nOffsetY = rGlyphs[i+1].m_aPos.getY() - rGlyphs[i].m_aPos.getY();
+ nXOffset += std::hypot(nOffsetX, 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,
+ 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_pFont != rGlyphs[i-1].m_pFont ||
+ rGlyphs[i].m_aPos.getY() != rGlyphs[i-1].m_aPos.getY() )
+ {
+ 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 back transformed to current coordinate system
+ Point aCurPos(SubPixelToLogic(rGlyphs[nBeginRun].m_aPos));
+ aCurPos += rAlignOffset;
+
+ // perform artificial italics if necessary
+ double fSkew = 0.0;
+ if (rGlyphs[nBeginRun].m_pFont->NeedsArtificialItalic())
+ fSkew = ARTIFICIAL_ITALIC_SKEW;
+
+ // 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 basegfx::B2DPoint aThisPos = aMat.transform( rGlyphs[nPos].m_aPos );
+ const basegfx::B2DPoint aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos );
+ double fAdvance = aThisPos.getX() - aPrevPos.getX();
+ 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;
+ 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());
+ }
+ }
+
+ // 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()->NeedsArtificialBold())
+ {
+ 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 GlyphItem* pGlyph = nullptr;
+ const LogicalFontInstance* pGlyphFont = 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
+ basegfx::B2DPoint aPos;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont))
+ {
+ const auto* pFace = pGlyphFont->GetFontFace();
+
+ 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;
+
+ const auto nGlyphId = pGlyph->glyphId();
+
+ // A glyph can't have more than one ToUnicode entry, use ActualText
+ // instead.
+ if (!aCodeUnits.empty() && !bUseActualText)
+ {
+ for (const auto& rSubset : m_aSubsets[pFace].m_aSubsets)
+ {
+ const auto& it = rSubset.m_aMapping.find(nGlyphId);
+ if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits)
+ {
+ bUseActualText = true;
+ aCodeUnits.clear();
+ }
+ }
+ }
+
+ auto nGlyphWidth = pGlyphFont->GetGlyphWidth(nGlyphId, pGlyph->IsVertical(), false);
+
+ sal_uInt8 nMappedGlyph;
+ sal_Int32 nMappedFontObject;
+ registerGlyph(nGlyphId, pFace, pGlyphFont, aCodeUnits, nGlyphWidth, nMappedGlyph, nMappedFontObject);
+
+ int nCharPos = -1;
+ if (bUseActualText || pGlyph->IsInCluster())
+ nCharPos = pGlyph->charPos();
+
+ aGlyphs.emplace_back(aPos,
+ pGlyph,
+ pGlyphFont,
+ XUnits(pFace->UnitsPerEm(), 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.
+ basegfx::B2DPoint aDrawPosition(rLayout.GetDrawPosition());
+ tools::Rectangle aRectangle(SubPixelToLogic(aDrawPosition),
+ 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(SubPixelToLogic(aDrawPosition), 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, nFontHeight);
+ else
+ drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, 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 );
+
+ // 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() )
+ {
+ basegfx::B2DPoint aStartPt;
+ double nWidth = 0;
+ nIndex = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
+ {
+ if (!pGlyph->IsSpacing())
+ {
+ if( !nWidth )
+ aStartPt = aPos;
+
+ nWidth += pGlyph->newWidth();
+ }
+ else if( nWidth > 0 )
+ {
+ drawTextLine( SubPixelToLogic(aStartPt),
+ ImplDevicePixelToLogicWidth( nWidth ),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ nWidth = 0;
+ }
+ }
+
+ if( nWidth > 0 )
+ {
+ drawTextLine( SubPixelToLogic(aStartPt),
+ ImplDevicePixelToLogicWidth( nWidth ),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+ }
+ else
+ {
+ basegfx::B2DPoint aStartPt = rLayout.GetDrawPosition();
+ int nWidth = rLayout.GetTextWidth();
+ drawTextLine( SubPixelToLogic(aStartPt),
+ ImplDevicePixelToLogicWidth( nWidth ),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+ }
+
+ // write eventual emphasis marks
+ if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) )
+ return;
+
+ push( PushFlags::ALL );
+
+ aLine.setLength( 0 );
+ aLine.append( "q\n" );
+
+ FontEmphasisMark nEmphMark = m_aCurrentPDFState.m_aFont.GetEmphasisMarkStyle();
+
+ tools::Long nEmphHeight;
+ if ( nEmphMark & FontEmphasisMark::PosBelow )
+ nEmphHeight = GetEmphasisDescent();
+ else
+ nEmphHeight = GetEmphasisAscent();
+
+ vcl::font::EmphasisMark aEmphasisMark(nEmphMark, ImplDevicePixelToLogicWidth(nEmphHeight), GetDPIY());
+ if ( aEmphasisMark.IsShapePolyLine() )
+ {
+ setLineColor( m_aCurrentPDFState.m_aFont.GetColor() );
+ setFillColor( COL_TRANSPARENT );
+ }
+ else
+ {
+ setFillColor( m_aCurrentPDFState.m_aFont.GetColor() );
+ setLineColor( COL_TRANSPARENT );
+ }
+
+ writeBuffer( aLine );
+
+ Point aOffset(0,0);
+ Point aOffsetVert(0,0);
+
+ if ( nEmphMark & FontEmphasisMark::PosBelow )
+ {
+ aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset() );
+ aOffsetVert = aOffset;
+ }
+ else
+ {
+ aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetAscent() + aEmphasisMark.GetYOffset()) );
+ // Todo: use ideographic em-box or ideographic character face information.
+ aOffsetVert.AdjustY(-(GetFontInstance()->mxFontMetric->GetAscent() +
+ GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset()));
+ }
+
+ tools::Long nEmphWidth2 = aEmphasisMark.GetWidth() / 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() );
+
+ tools::Rectangle aRectangle;
+ nIndex = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont))
+ {
+ if (!pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle))
+ continue;
+
+ if (!pGlyph->IsSpacing())
+ {
+ basegfx::B2DPoint aAdjOffset;
+ if (pGlyph->IsVertical())
+ {
+ aAdjOffset = basegfx::B2DPoint(aOffsetVert.X(), aOffsetVert.Y());
+ aAdjOffset.adjustX((-pGlyph->origWidth() + aEmphasisMark.GetWidth()) / 2);
+ }
+ else
+ {
+ aAdjOffset = basegfx::B2DPoint(aOffset.X(), aOffset.Y());
+ aAdjOffset.adjustX(aRectangle.Left() + (aRectangle.GetWidth() - aEmphasisMark.GetWidth()) / 2 );
+ }
+
+ aAdjOffset = aRotScale.transform( aAdjOffset );
+
+ aAdjOffset -= basegfx::B2DPoint(nEmphWidth2, nEmphHeight2);
+
+ basegfx::B2DPoint aMarkDevPos(aPos);
+ aMarkDevPos += aAdjOffset;
+ Point aMarkPos = SubPixelToLogic(aMarkDevPos);
+ drawEmphasisMark( aMarkPos.X(), aMarkPos.Y(),
+ aEmphasisMark.GetShape(), aEmphasisMark.IsShapePolyLine(),
+ aEmphasisMark.GetRect1(), aEmphasisMark.GetRect2() );
+ }
+ }
+
+ writeBuffer( "Q\n" );
+ 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, KernArraySpan pDXArray, std::span<const sal_Bool> pKashidaArray, 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, pKashidaArray,
+ SalLayoutFlags::NONE, nullptr, layoutGlyphs );
+ if( pLayout )
+ {
+ drawLayout( *pLayout, rText, true );
+ }
+}
+
+void PDFWriterImpl::drawStretchText( const Point& rPos, sal_Int32 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 );
+
+ // 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 = removeMnemonicFromString( aStr, nMnemonicPos );
+
+ // multiline text
+ if ( nStyle & DrawTextFlags::MultiLine )
+ {
+ ImplMultiTextLineInfo aMultiLineInfo;
+ sal_Int32 i;
+ sal_Int32 nFormatLines;
+
+ if ( nTextHeight )
+ {
+ vcl::DefaultTextLayout aLayout( *this );
+ OUString aLastLine;
+ aLayout.GetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle );
+ 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 );
+}
+
+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 );
+}
+
+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 );
+ }
+ 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 = pFontInstance->mxFontMetric->GetAboveUnderlineSize();
+ nLinePos = pFontInstance->mxFontMetric->GetAboveUnderlineOffset();
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = pFontInstance->mxFontMetric->GetUnderlineSize();
+ nLinePos = 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 = pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize();
+ nLinePos = pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset();
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = pFontInstance->mxFontMetric->GetBoldUnderlineSize();
+ nLinePos = pFontInstance->mxFontMetric->GetBoldUnderlineOffset();
+ }
+ break;
+ case LINESTYLE_DOUBLE:
+ if ( bIsAbove )
+ {
+ if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() )
+ ImplInitAboveTextLineSize();
+ nLineHeight = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize();
+ nLinePos = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1();
+ nLinePos2 = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2();
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = pFontInstance->mxFontMetric->GetDoubleUnderlineSize();
+ nLinePos = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1();
+ nLinePos2 = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2();
+ }
+ break;
+ default:
+ break;
+ }
+
+ if ( !nLineHeight )
+ return;
+
+ // tdf#154235
+ // nLinePos/nLinePos2 is the distance from baseline to the top of the line,
+ // while in PDF we stroke the line so the position is to the middle of the
+ // line, we add half of nLineHeight to account for that.
+ auto nOffset = nLineHeight / 2;
+ if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
+ {
+ // Except when outlining, as now we are drawing a rectangle, so
+ // nLinePos is the bottom of the rectangle, so need to add nLineHeight
+ // to it.
+ nOffset = nLineHeight;
+ }
+
+ nLineHeight = HCONV(nLineHeight);
+ nLinePos = HCONV(nLinePos + nOffset);
+ nLinePos2 = HCONV(nLinePos2 + nOffset);
+
+ // 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), 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 = pFontInstance->mxFontMetric->GetStrikeoutSize();
+ nLinePos = pFontInstance->mxFontMetric->GetStrikeoutOffset();
+ break;
+ case STRIKEOUT_BOLD:
+ if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() )
+ ImplInitTextLineSize();
+ nLineHeight = pFontInstance->mxFontMetric->GetBoldStrikeoutSize();
+ nLinePos = pFontInstance->mxFontMetric->GetBoldStrikeoutOffset();
+ break;
+ case STRIKEOUT_DOUBLE:
+ if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() )
+ ImplInitTextLineSize();
+ nLineHeight = pFontInstance->mxFontMetric->GetDoubleStrikeoutSize();
+ nLinePos = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1();
+ nLinePos2 = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2();
+ break;
+ default:
+ break;
+ }
+
+ if ( !nLineHeight )
+ return;
+
+ // tdf#154235
+ // nLinePos/nLinePos2 is the distance from baseline to the bottom of the line,
+ // while in PDF we stroke the line so the position is to the middle of the
+ // line, we add half of nLineHeight to account for that.
+ nLinePos = HCONV(nLinePos + nLineHeight / 2);
+ nLinePos2 = HCONV(nLinePos2 + nLineHeight / 2);
+ nLineHeight = HCONV(nLineHeight);
+
+ 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.copy(1);
+ 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 ( aOverlineColor.IsTransparent() )
+ aOverlineColor = 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 );
+}
+
+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 );
+}
+
+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 );
+}
+
+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 );
+
+ 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, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams)
+{
+ if( nObject < 0 )
+ return;
+
+ switch( eKind )
+ {
+ case ResourceKind::XObject:
+ rResourceDict.m_aXObjects[rResource] = nObject;
+ if (!rOutputStreams.empty())
+ rOutputStreams.front().m_aResourceDict.m_aXObjects[rResource] = nObject;
+ break;
+ case ResourceKind::ExtGState:
+ rResourceDict.m_aExtGStates[rResource] = nObject;
+ if (!rOutputStreams.empty())
+ rOutputStreams.front().m_aResourceDict.m_aExtGStates[rResource] = nObject;
+ break;
+ case ResourceKind::Shading:
+ rResourceDict.m_aShadings[rResource] = nObject;
+ if (!rOutputStreams.empty())
+ rOutputStreams.front().m_aResourceDict.m_aShadings[rResource] = nObject;
+ break;
+ case ResourceKind::Pattern:
+ rResourceDict.m_aPatterns[rResource] = nObject;
+ if (!rOutputStreams.empty())
+ rOutputStreams.front().m_aResourceDict.m_aPatterns[rResource] = nObject;
+ break;
+ }
+}
+
+void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject )
+{
+ pushResource(eKind, rResource, nObject, m_aGlobalResourceDict, m_aOutputStreams);
+}
+
+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 );
+
+ 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 );
+}
+
+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 );
+}
+
+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 );
+}
+
+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 );
+}
+
+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 );
+}
+
+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 );
+ drawPolyLine( rPoly );
+ writeBuffer( "Q\n" );
+ }
+ 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 );
+ 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 );
+ }
+ writeBuffer( "Q\n" );
+
+ 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 );
+
+ 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 ) );
+ checkAndEnableStreamEncryption( rObject.m_nObject );
+ CHECK_RETURN2( writeBufferBytes( rObject.m_pContentStream->GetData(), nSize ) );
+ disableStreamEncryption();
+ aLine.setLength( 0 );
+ aLine.append( "\n"
+ "endstream\n"
+ "endobj\n\n" );
+ CHECK_RETURN2( writeBuffer( aLine ) );
+
+ // 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 ) );
+}
+
+bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject )
+{
+ // LO internal gradient -> PDF shading type:
+ // * css::awt::GradientStyle_LINEAR: axial shading, using sampled-function with 2 samples
+ // [t=0:colorStart, t=1:colorEnd]
+ // * css::awt::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 );
+ BitmapScopedReadAccess 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 css::awt::GradientStyle_LINEAR:
+ case css::awt::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 css::awt::GradientStyle_LINEAR:
+ aLine.append('2');
+ break;
+ case css::awt::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 ) );
+
+ 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 css::awt::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( writeBufferBytes( aCol, 3 ) );
+ [[fallthrough]];
+ case css::awt::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( writeBufferBytes( 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( writeBufferBytes( 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( writeBufferBytes( 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 ) );
+
+ // 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 ) );
+
+ 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 css::awt::GradientStyle_LINEAR:
+ case css::awt::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() == css::awt::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 css::awt::GradientStyle_LINEAR:
+ case css::awt::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 );
+}
+
+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_uInt64 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( static_cast<sal_Int64>(nLength) );
+ if( nMaskObject )
+ {
+ aLine.append(" /SMask ");
+ aLine.append( nMaskObject );
+ aLine.append( " 0 R " );
+ }
+ aLine.append( ">>\nstream\n" );
+ CHECK_RETURN2( writeBuffer( aLine ) );
+
+ checkAndEnableStreamEncryption( rObject.m_nObject );
+ CHECK_RETURN2( writeBufferBytes( rObject.m_pStream->GetData(), nLength ) );
+ disableStreamEncryption();
+
+ aLine.setLength( 0 );
+ CHECK_RETURN2( writeBuffer( "\nendstream\nendobj\n\n" ) );
+
+ if( nMaskObject )
+ {
+ BitmapEmit aEmit;
+ aEmit.m_nObject = nMaskObject;
+ aEmit.m_aBitmap = BitmapEx( rObject.m_aAlphaMask.GetBitmap(), 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();
+ static const double fMagicScaleFactor = PDF_INSERT_MAGIC_SCALE_FACTOR;
+
+ 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)
+ {
+ // tdf#156842 increase scale for external PDF data
+ // Multiply PDF_INSERT_MAGIC_SCALE_FACTOR for platforms like macOS
+ // that scale all images by this number.
+ // This fix also allows the CppunitTest_vcl_pdfexport to run
+ // successfully on macOS.
+ fScaleX = fMagicScaleFactor / aSize.Width();
+ fScaleY = fMagicScaleFactor / aSize.Height();
+
+ // 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;
+ }
+
+ double aOrigin[2] = { 0.0, 0.0 };
+ if (auto* pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("MediaBox"_ostr)))
+ {
+ const auto& rElements = pArray->GetElements();
+ if (rElements.size() >= 4)
+ {
+ // get x1, y1 of the rectangle.
+ for (sal_Int32 nIdx = 0; nIdx < 2; ++nIdx)
+ {
+ if (const auto* pNumElement = dynamic_cast<filter::PDFNumberElement*>(rElements[nIdx]))
+ aOrigin[nIdx] = pNumElement->GetValue();
+ }
+ }
+ }
+
+ std::vector<filter::PDFObjectElement*> aContentStreams;
+ if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"_ostr))
+ aContentStreams.push_back(pContentStream);
+ else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents"_ostr)))
+ {
+ 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"_ostr)))
+ {
+ 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"_ostr));
+ if (!pType || pType->GetValue() != "Annot")
+ {
+ continue;
+ }
+
+ auto pSubtype = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Subtype"_ostr));
+ 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"_ostr)))
+ {
+ // 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() - aOrigin[0], -0.5 * aBBox.getHeight() - aOrigin[1]);
+ 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 [ ");
+ aLine.append(aOrigin[0]);
+ aLine.append(' ');
+ aLine.append(aOrigin[1]);
+ aLine.append(' ');
+ aLine.append(aBBox.getWidth() + aOrigin[0]);
+ aLine.append(' ');
+ aLine.append(aBBox.getHeight() + aOrigin[1]);
+ 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))
+ 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))
+ return;
+ aLine.setLength(0);
+ disableStreamEncryption();
+
+ aLine.append("\nendstream\nendobj\n\n");
+ if (!writeBuffer(aLine))
+ 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 ");
+ // tdf#157680 reduce size by magic scale factor in /BBox
+ aLine.append(aSize.Width() / fMagicScaleFactor);
+ aLine.append(" ");
+ // tdf#157680 reduce size by magic scale factor in /BBox
+ aLine.append(aSize.Height() / fMagicScaleFactor);
+ aLine.append(" ]\n");
+
+ if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0)
+ {
+ // Write the reference dictionary.
+ aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) ");
+ if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
+ { // ISO 14289-1:2014, Clause: 7.11
+ aLine.append("/UF (<embedded file>) ");
+ }
+ aLine.append("/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");
+
+ // Reset non-stroking color in case the XObject uses the default
+ aStream.append("0 0 0 rg\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))
+ return;
+ aLine.setLength(0);
+
+ checkAndEnableStreamEncryption(rEmit.m_nFormObject);
+ aLine.append(aStream.getStr());
+ if (!writeBuffer(aLine))
+ return;
+ aLine.setLength(0);
+ disableStreamEncryption();
+
+ aLine.append("\nendstream\nendobj\n\n");
+ CHECK_RETURN2(writeBuffer(aLine));
+}
+
+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 = 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() )
+ {
+ if( rObject.m_aBitmap.IsAlpha() )
+ {
+ aBitmap = rObject.m_aBitmap.GetAlphaMask().GetBitmap();
+ aBitmap.Convert( BmpConversion::N1BitThreshold );
+ SAL_WARN_IF(aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP, "vcl.pdfwriter", "mask conversion failed" );
+ }
+ }
+ else if (aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP)
+ {
+ aBitmap = rObject.m_aBitmap.GetAlphaMask().GetBitmap();
+ aBitmap.Convert( BmpConversion::N8BitGreys );
+ SAL_WARN_IF(aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP, "vcl.pdfwriter", "alpha mask conversion failed" );
+ }
+ }
+
+ BitmapScopedReadAccess pAccess(aBitmap);
+
+ bool bTrueColor = true;
+ sal_Int32 nBitsPerComponent = 0;
+ auto const ePixelFormat = aBitmap.getPixelFormat();
+ switch (ePixelFormat)
+ {
+ 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::N8_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
+ {
+ aLine.append( "/ColorSpace/DeviceGray\n"
+ "/Decode [ 1 0 ]\n" );
+ }
+
+ if (!bMask && !m_bIsPDF_A1)
+ {
+ if( bWriteMask )
+ {
+ nMaskObject = createObject();
+ if (rObject.m_aBitmap.IsAlpha())
+ 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 ) );
+ 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( writeBufferBytes( 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(writeBufferBytes(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 ) );
+ 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 ) );
+
+ 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.
+ auto nObjectID = addEmbeddedFile(rDataContainer);
+ rEmit.m_nEmbeddedObject = nObjectID;
+ }
+ 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 = rtl_crc32( 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 );
+
+ 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 );
+}
+
+const BitmapEmit& PDFWriterImpl::createBitmapEmit(const BitmapEx& i_rBitmap, const Graphic& rGraphic, std::list<BitmapEmit>& rBitmaps, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams)
+{
+ BitmapEx aBitmap( i_rBitmap );
+ auto ePixelFormat = aBitmap.GetBitmap().getPixelFormat();
+ if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
+ 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.GetAlphaMask().GetChecksum();
+ std::list<BitmapEmit>::const_iterator it = std::find_if(rBitmaps.begin(), rBitmaps.end(),
+ [&](const BitmapEmit& arg) { return aID == arg.m_aID; });
+ if (it == rBitmaps.end())
+ {
+ rBitmaps.push_front(BitmapEmit());
+ rBitmaps.front().m_aID = aID;
+ rBitmaps.front().m_aBitmap = aBitmap;
+ if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
+ rBitmaps.front().m_nObject = createObject();
+ createEmbeddedFile(rGraphic, rBitmaps.front().m_aReferenceXObject, rBitmaps.front().m_nObject);
+ it = rBitmaps.begin();
+ }
+
+ sal_Int32 nObject = it->m_aReferenceXObject.getObject();
+ OString aObjName = "Im" + OString::number(nObject);
+ pushResource(ResourceKind::XObject, aObjName, nObject, rResourceDict, rOutputStreams);
+
+ return *it;
+}
+
+const BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx& i_rBitmap, const Graphic& rGraphic )
+{
+ return createBitmapEmit(i_rBitmap, rGraphic, m_aBitmaps, m_aGlobalResourceDict, m_aOutputStreams);
+}
+
+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)" );
+
+ 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 );
+}
+
+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 );
+ }
+ }
+ 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 );
+ drawBitmap( aBmpPos, aBmpSize, aBitmap );
+ writeBuffer( "Q\n" );
+ }
+}
+
+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 );
+}
+
+/* #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;
+ rNoteEntry.m_nPage = nPageNr;
+ // 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, OUString const& rAltText, OUString const& rMimeType)
+{
+ 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(rAltText, rMimeType);
+ 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 constexpr auto aTagStrings = frozen::make_map<PDFWriter::StructElement, const char*>({
+ { PDFWriter::NonStructElement, "NonStruct" },
+ { PDFWriter::Document, "Document" },
+ { PDFWriter::Part, "Part" },
+ { PDFWriter::Article, "Art" },
+ { PDFWriter::Section, "Sect" },
+ { PDFWriter::Division, "Div" },
+ { PDFWriter::BlockQuote, "BlockQuote" },
+ { PDFWriter::Caption, "Caption" },
+ { PDFWriter::TOC, "TOC" },
+ { PDFWriter::TOCI, "TOCI" },
+ { PDFWriter::Index, "Index" },
+ { PDFWriter::Paragraph, "P" },
+ { PDFWriter::Heading, "H" },
+ { PDFWriter::H1, "H1" },
+ { PDFWriter::H2, "H2" },
+ { PDFWriter::H3, "H3" },
+ { PDFWriter::H4, "H4" },
+ { PDFWriter::H5, "H5" },
+ { PDFWriter::H6, "H6" },
+ { PDFWriter::List, "L" },
+ { PDFWriter::ListItem, "LI" },
+ { PDFWriter::LILabel, "Lbl" },
+ { PDFWriter::LIBody, "LBody" },
+ { PDFWriter::Table, "Table" },
+ { PDFWriter::TableRow, "TR" },
+ { PDFWriter::TableHeader, "TH" },
+ { PDFWriter::TableData, "TD" },
+ { PDFWriter::Span, "Span" },
+ { PDFWriter::Quote, "Quote" },
+ { PDFWriter::Note, "Note" },
+ { PDFWriter::Reference, "Reference" },
+ { PDFWriter::BibEntry, "BibEntry" },
+ { PDFWriter::Code, "Code" },
+ { PDFWriter::Link, "Link" },
+ { PDFWriter::Annot, "Annot" },
+ { PDFWriter::Ruby, "Ruby" },
+ { PDFWriter::RB, "RB" },
+ { PDFWriter::RT, "RT" },
+ { PDFWriter::RP, "RP" },
+ { PDFWriter::Warichu, "Warichu" },
+ { PDFWriter::WT, "WT" },
+ { PDFWriter::WP, "WP" },
+ { PDFWriter::Figure, "Figure" },
+ { PDFWriter::Formula, "Formula"},
+ { PDFWriter::Form, "Form" }
+ });
+
+ if (eType == PDFWriter::Annot
+ && m_aContext.Version < PDFWriter::PDFVersion::PDF_1_5)
+ {
+ return "Figure"; // fallback
+ }
+
+ auto 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 aware of a reason for doing it in any case, so just don't do it.
+ if (aAlias != aTag)
+ m_aRoleMap[aAlias] = aTag;
+}
+
+void PDFWriterImpl::beginStructureElementMCSeq()
+{
+ assert(m_nCurrentStructElement == 0 || m_aStructure[m_nCurrentStructElement].m_oType);
+ if( m_bEmitStructure &&
+ m_nCurrentStructElement > 0 && // StructTreeRoot
+ // Document = SwPageFrame => this is not *inside* the page content
+ // stream so do not emit MCID!
+ m_aStructure[m_nCurrentStructElement].m_oType &&
+ *m_aStructure[m_nCurrentStructElement].m_oType != PDFWriter::Document &&
+ ! 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_oType) );
+ aLine.append( "<</MCID " );
+ aLine.append( nMCID );
+ aLine.append( ">>BDC\n" );
+ writeBuffer( aLine );
+
+ // 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(MCIDReference{m_aPages[m_nCurrentPage].m_nPageObject, nMCID});
+ // 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_oType &&
+ *m_aStructure[m_nCurrentStructElement].m_oType == PDFWriter::NonStructElement &&
+ ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
+ )
+ {
+ OString aLine = "/Artifact "_ostr;
+ writeBuffer( aLine );
+ // emit property list if requested
+ OStringBuffer buf;
+ for (auto const& rAttr : m_aStructure[m_nCurrentStructElement].m_aAttributes)
+ {
+ appendStructureAttributeLine(rAttr.first, rAttr.second, buf, false);
+ }
+ if (buf.isEmpty())
+ {
+ writeBuffer("BMC\n");
+ }
+ else
+ {
+ writeBuffer("<<");
+ writeBuffer(buf);
+ writeBuffer(">> BDC\n");
+ }
+ // mark element MC sequence as open
+ m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true;
+ }
+}
+
+void PDFWriterImpl::endStructureElementMCSeq(EndMode const endMode)
+{
+ if (m_nCurrentStructElement > 0 // not StructTreeRoot
+ && m_aStructure[m_nCurrentStructElement].m_oType
+ && (m_bEmitStructure
+ || (endMode != EndMode::OnlyStruct
+ && m_aStructure[m_nCurrentStructElement].m_oType == PDFWriter::NonStructElement))
+ && m_aStructure[m_nCurrentStructElement].m_bOpenMCSeq)
+ {
+ writeBuffer( "EMC\n" );
+ 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_oType
+ && *m_aStructure[nEle].m_oType == PDFWriter::NonStructElement)
+ {
+ bEmit = false;
+ break;
+ }
+ nEle = m_aStructure[ nEle ].m_nParentElement;
+ }
+ }
+ return bEmit;
+}
+
+sal_Int32 PDFWriterImpl::ensureStructureElement()
+{
+ if( ! m_aContext.Tagged )
+ return -1;
+
+ sal_Int32 nNewId = sal_Int32(m_aStructure.size());
+ m_aStructure.emplace_back();
+ PDFStructureElement& rEle = m_aStructure.back();
+ // leave rEle.m_oType uninitialised
+ rEle.m_nOwnElement = nNewId;
+ // temporary parent
+ rEle.m_nParentElement = m_nCurrentStructElement;
+ rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
+ m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId );
+ return nNewId;
+}
+
+void PDFWriterImpl::initStructureElement(sal_Int32 const id,
+ PDFWriter::StructElement const eType, std::u16string_view const rAlias)
+{
+ if( ! m_aContext.Tagged )
+ return;
+
+ 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::vector< 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_oType
+ && *m_aStructure[nElement].m_oType == 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" );
+ }
+ }
+
+ PDFStructureElement& rEle = m_aStructure[id];
+ assert(!rEle.m_oType);
+ rEle.m_oType.emplace(eType);
+ // remove it from its possibly placeholder parent; append to real parent
+ auto const it(std::find(m_aStructure[rEle.m_nParentElement].m_aChildren.begin(),
+ m_aStructure[rEle.m_nParentElement].m_aChildren.end(), id));
+ assert(it != m_aStructure[rEle.m_nParentElement].m_aChildren.end());
+ m_aStructure[rEle.m_nParentElement].m_aChildren.erase(it);
+ rEle.m_nParentElement = m_nCurrentStructElement;
+ rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
+ m_aStructure[m_nCurrentStructElement].m_aChildren.push_back(id);
+
+ // handle alias names
+ if( !rAlias.empty() && eType != PDFWriter::NonStructElement )
+ {
+ OStringBuffer aNameBuf( rAlias.size() );
+ appendName( rAlias, aNameBuf );
+ OString aAliasName( aNameBuf.makeStringAndClear() );
+ rEle.m_aAlias = aAliasName;
+ addRoleMap(aAliasName, eType);
+ }
+
+ if (m_bEmitStructure && eType != PDFWriter::NonStructElement) // don't create nonexistent objects
+ {
+ rEle.m_nObject = createObject();
+ // update parent's kids list
+ m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(ObjReference{rEle.m_nObject});
+ // ISO 14289-1:2014, Clause: 7.9
+ if (*rEle.m_oType == PDFWriter::Note)
+ {
+ m_StructElemObjsWithID.insert(rEle.m_nObject);
+ }
+ }
+}
+
+void PDFWriterImpl::beginStructureElement(sal_Int32 const id)
+{
+ if( m_nCurrentPage < 0 )
+ return;
+
+ if( ! m_aContext.Tagged )
+ return;
+
+ assert(id != -1 && "cid#1538888 doesn't consider above m_aContext.Tagged");
+
+ // close eventual current MC sequence
+ endStructureElementMCSeq(EndMode::OnlyStruct);
+
+ PDFStructureElement& rEle = m_aStructure[id];
+ m_StructElementStack.push(m_nCurrentStructElement);
+ m_nCurrentStructElement = id;
+
+ if (g_bDebugDisableCompression)
+ {
+ OStringBuffer aLine( "beginStructureElement " );
+ aLine.append( m_nCurrentStructElement );
+ aLine.append( ": " );
+ aLine.append( rEle.m_oType
+ ? getStructureTag(*rEle.m_oType)
+ : "<placeholder>" );
+ 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();
+}
+
+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( m_aStructure[m_nCurrentStructElement].m_oType
+ ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
+ : "<placeholder>" );
+ 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_StructElementStack.top();
+ m_StructElementStack.pop();
+
+ // check whether to emit structure henceforth
+ m_bEmitStructure = checkEmitStructure();
+
+ if (g_bDebugDisableCompression && m_bEmitStructure)
+ {
+ emitComment( aLine.getStr() );
+ }
+}
+
+namespace {
+
+void removePlaceholderSEImpl(std::vector<PDFStructureElement> & rStructure,
+ std::vector<sal_Int32>::iterator & rParentIt)
+{
+ PDFStructureElement& rEle(rStructure[*rParentIt]);
+ removePlaceholderSE(rStructure, rEle);
+
+ if (!rEle.m_oType)
+ {
+ // Placeholder was not initialised - should not happen when printing
+ // a full page, but might if a selection is printed, which can be only
+ // a shape without its anchor.
+ // Handle this by moving the children to the parent SE.
+ PDFStructureElement & rParent(rStructure[rEle.m_nParentElement]);
+ rParentIt = rParent.m_aChildren.erase(rParentIt);
+ std::vector<sal_Int32> children;
+ for (auto const child : rEle.m_aChildren)
+ {
+ PDFStructureElement& rChild = rStructure[child];
+ rChild.m_nParentElement = rEle.m_nParentElement;
+ children.push_back(rChild.m_nOwnElement);
+ }
+ rParentIt = rParent.m_aChildren.insert(rParentIt, children.begin(), children.end())
+ + children.size();
+ }
+ else
+ {
+ ++rParentIt;
+ }
+
+}
+
+void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle)
+{
+ for (auto it = rEle.m_aChildren.begin(); it != rEle.m_aChildren.end(); )
+ {
+ removePlaceholderSEImpl(rStructure, it);
+ }
+}
+
+} // end anonymous namespace
+
+/*
+ * 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_nOwnElement != rEle.m_nParentElement
+ && *rEle.m_oType == PDFWriter::NonStructElement)
+ {
+ return;
+ }
+
+ for (auto const& child : rEle.m_aChildren)
+ {
+ assert(child > 0 && o3tl::make_unsigned(child) < m_aStructure.size());
+ if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() )
+ {
+ PDFStructureElement& rChild = m_aStructure[ child ];
+ if (*rChild.m_oType != PDFWriter::NonStructElement)
+ {
+ //triggered when a child of the rEle element is found
+ assert(rChild.m_nParentElement == rEle.m_nOwnElement);
+ 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::vector< sal_Int32 > aNewChildren;
+
+ // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?)
+ OString aAliasName("Div"_ostr);
+ 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_oType.emplace(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(ObjReference{rEleNew.m_nObject});
+ aNewChildren.push_back( nNewId );
+
+ std::vector< 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.insert( rEleNew.m_aChildren.begin(),
+ rEle.m_aChildren.begin(),
+ aChildEndIt );
+ rEle.m_aChildren.erase( 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( m_aStructure[m_nCurrentStructElement].m_oType
+ ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
+ : "<placeholder>" );
+ 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;
+
+ assert(m_aStructure[m_nCurrentStructElement].m_oType);
+ bool bInsert = false;
+ if (m_nCurrentStructElement > 0
+ && (m_bEmitStructure
+ // allow it for topmost non-structured element
+ || (m_aContext.Tagged
+ && (0 == m_aStructure[m_nCurrentStructElement].m_nParentElement
+ || !m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType
+ || *m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType != PDFWriter::NonStructElement))))
+ {
+ PDFWriter::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
+ 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
+ && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::Type:
+ if (eVal == PDFWriter::Pagination || eVal == PDFWriter::Layout || eVal == PDFWriter::Page)
+ // + Background for PDF >= 1.7
+ {
+ if (eType == PDFWriter::NonStructElement)
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::Subtype:
+ if (eVal == PDFWriter::Header || eVal == PDFWriter::Footer || eVal == PDFWriter::Watermark)
+ {
+ if (eType == PDFWriter::NonStructElement
+ && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::Role:
+ if (eVal == PDFWriter::Rb || eVal == PDFWriter::Cb || eVal == PDFWriter::Pb || eVal == PDFWriter::Tv)
+ {
+ if (eType == PDFWriter::Form
+ && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::RubyAlign:
+ if (eVal == PDFWriter::RStart || eVal == PDFWriter::RCenter || eVal == PDFWriter::REnd || eVal == PDFWriter::RJustify || eVal == PDFWriter::RDistribute)
+ {
+ if (eType == PDFWriter::RT
+ && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::RubyPosition:
+ if (eVal == PDFWriter::RBefore || eVal == PDFWriter::RAfter || eVal == PDFWriter::RWarichu || eVal == PDFWriter::RInline)
+ {
+ if (eType == PDFWriter::RT
+ && 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_oType)
+ << " (" << 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;
+
+ assert(m_aStructure[m_nCurrentStructElement].m_oType);
+ 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 const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
+ 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_oType)
+ << " (" << 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;
+
+ assert(m_aStructure[m_nCurrentStructElement].m_oType);
+ PDFWriter::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
+ 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::setStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds)
+{
+ assert(!(m_nCurrentPage < 0 || m_aPages.size() <= o3tl::make_unsigned(m_nCurrentPage)));
+
+ if (!m_aContext.Tagged || m_nCurrentStructElement <= 0 || !m_bEmitStructure)
+ {
+ return;
+ }
+
+ m_aStructure[m_nCurrentStructElement].m_AnnotIds = rAnnotIds;
+}
+
+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"_ostr );
+ if( app_it != rKid.m_aAppearances.end() )
+ {
+ auto stream_it = app_it->second.find( "Yes"_ostr );
+ 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"_ostr );
+ if( app_it != rKid.m_aAppearances.end() )
+ {
+ auto stream_it = app_it->second.find( "Off"_ostr );
+ 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)
+ 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)
+ 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"_ostr ][ "Standard"_ostr ] = 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::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 0000000000..58fc31aafd
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter_impl2.cxx
@@ -0,0 +1,2047 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/skia/SkiaHelper.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 )
+ 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.GetAlphaMask();
+ 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(DeviceFormat::WITH_ALPHA);
+ if( xVDev->SetOutputSizePixel( aDstSizePixel, true, true ) )
+ {
+ Point aPoint;
+
+ MapMode aMapMode( pDummyVDev->GetMapMode() );
+ aMapMode.SetOrigin( aPoint );
+ xVDev->SetMapMode( aMapMode );
+ const bool bVDevOldMap = xVDev->IsMapModeEnabled();
+ 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 );
+ BitmapEx aPaint = xVDev->GetBitmapEx(aPoint, 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( aPoint, aDstSize ), rTransparenceGradient );
+ xVDev->SetDrawMode( DrawModeFlags::Default );
+ xVDev->EnableMapMode( false );
+
+ AlphaMask aAlpha(xVDev->GetBitmap(Point(), xVDev->GetOutputSizePixel()));
+ AlphaMask aPaintAlpha(aPaint.GetAlphaMask());
+ // The alpha mask is inverted from what is
+ // expected so invert it again. To test this
+ // code, export to PDF the transparent shapes,
+ // gradients, and images in the documents
+ // attached to the following bug reports:
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=155912
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=156630
+ aAlpha.Invert(); // convert to alpha
+ aAlpha.BlendWith(aPaintAlpha);
+#if HAVE_FEATURE_SKIA
+#if OSL_DEBUG_LEVEL > 0
+ // In release builds, we always invert
+ // regardless of whether Skia is enabled or not.
+ // But in debug builds, we can't invert when
+ // Skia is enabled.
+ if ( !SkiaHelper::isVCLSkiaEnabled() )
+#endif
+#endif
+ {
+ // When Skia is disabled, the alpha mask
+ // must be inverted a second time. To test
+ // this code, export the following
+ // document to PDF:
+ // https://bugs.documentfoundation.org/attachment.cgi?id=188084
+ aAlpha.Invert(); // convert to alpha
+ }
+
+ xVDev.disposeAndClear();
+
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( rPos, rSize, BitmapEx( aPaint.GetBitmap(), 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 = "XPATHSTROKE_SEQ_END"_ostr;
+ 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 = "XPATHFILL_SEQ_END"_ostr;
+ 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);
+
+ // The alpha mask is inverted from what is
+ // expected so invert it again. To test this
+ // code, export to PDF the transparent shapes,
+ // gradients, and images in the documents
+ // attached to the following bug reports:
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=155912
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=156630
+ BitmapEx aBitmapEx( pA->GetBitmapEx() );
+ if ( aBitmapEx.IsAlpha())
+ {
+ AlphaMask aAlpha = aBitmapEx.GetAlphaMask();
+ aAlpha.Invert();
+ aBitmapEx = BitmapEx(aBitmapEx.GetBitmap(), aAlpha);
+ }
+
+ 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);
+
+ // The alpha mask is inverted from what is
+ // expected so invert it again. To test this
+ // code, export to PDF the transparent shapes,
+ // gradients, and images in the documents
+ // attached to the following bug reports:
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=155912
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=156630
+ BitmapEx aBitmapEx( pA->GetBitmapEx() );
+ if ( aBitmapEx.IsAlpha())
+ {
+ AlphaMask aAlpha = aBitmapEx.GetAlphaMask();
+ aAlpha.Invert();
+ aBitmapEx = BitmapEx(aBitmapEx.GetBitmap(), aAlpha);
+ }
+
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetPoint(), pA->GetSize(), aBitmapEx, aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pAction);
+
+ // The alpha mask is inverted from what is
+ // expected so invert it again. To test this
+ // code, export to PDF the transparent shapes,
+ // gradients, and images in the documents
+ // attached to the following bug reports:
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=155912
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=156630
+ BitmapEx aBitmapEx( pA->GetBitmapEx() );
+ if ( aBitmapEx.IsAlpha())
+ {
+ AlphaMask aAlpha = aBitmapEx.GetAlphaMask();
+ aAlpha.Invert();
+ aBitmapEx = BitmapEx(aBitmapEx.GetBitmap(), aAlpha);
+ }
+
+ 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->GetKashidaArray(), 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;
+ writeBufferBytes( &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 )
+ {
+ writeBufferBytes( &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 )
+ {
+ writeBufferBytes( &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 css::awt::GradientStyle_LINEAR:
+ case css::awt::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 0000000000..eff94a9211
--- /dev/null
+++ b/vcl/source/gdi/print.cxx
@@ -0,0 +1,1676 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 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.Normalize();
+
+ if( rBmp.IsEmpty() || !aSrcRect.GetWidth() || !aSrcRect.GetHeight() || !aDestSz.Width() || !aDestSz.Height() )
+ return;
+
+ Bitmap aPaint( rBmp );
+ BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE;
+
+ // 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 );
+ }
+
+ // destination mirrored
+ if( nMirrFlags != BmpMirrorFlags::NONE )
+ {
+ aPaint.Mirror( nMirrFlags );
+ }
+
+ // we always want to have a mask
+ AlphaMask aAlphaMask(aSrcRect.GetSize());
+ aAlphaMask.Erase( 0 );
+
+ // 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 );
+
+ tools::Rectangle rectangle { Point(0,0), aSrcRect.GetSize() };
+ 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);
+
+ 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.GetAlphaMask(), COL_WHITE );
+ DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmp );
+ }
+ else
+ {
+ Bitmap aBmp( rBmpEx.GetBitmap() );
+ ImplPrintTransparent( aBmp, 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 )
+ {
+ rData.SetDriverData(nullptr, 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.Normalize();
+
+ if( !(!rMask.IsEmpty() && aSrcRect.GetWidth() && aSrcRect.GetHeight() && aDestSz.Width() && aDestSz.Height()) )
+ return;
+
+ Bitmap aMask( rMask );
+ BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE;
+
+ if (aMask.getPixelFormat() >= vcl::PixelFormat::N8_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() );
+ rData.SetOrientation( Orientation::Portrait );
+
+ 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 0000000000..85b39c683d
--- /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 0000000000..1ebab3b481
--- /dev/null
+++ b/vcl/source/gdi/print3.cxx
@@ -0,0 +1,2156 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <comphelper/diagnose_ex.hxx>
+#include <tools/debug.hxx>
+#include <tools/urlobj.hxx>
+
+#include <utility>
+#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(std::shared_ptr<PrinterController> i_xController,
+ const JobSetup& i_rInitSetup)
+ : mxController(std::move( 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( "-" + OUString::number( 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&)
+ {
+ }
+ }
+#ifdef MACOSX
+ else
+ {
+ // The PrintDialog updates the printer list in its constructor so do
+ // the same for printers that bring up their own dialog since. Not
+ // sure if this is needed or not on Windows or X11, so limit only to
+ // macOS for now.
+ Printer::updatePrinters();
+ }
+#endif
+
+ 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 0000000000..91d292519f
--- /dev/null
+++ b/vcl/source/gdi/regband.cxx
@@ -0,0 +1,886 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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
+{
+ SAL_WARN_IF(mpFirstSep == nullptr, "vcl", "ImplRegionBand::XLeftBoundary -> no separation in band!");
+
+ return mpFirstSep ? mpFirstSep->mnXLeft : 0;
+}
+
+tools::Long ImplRegionBand::GetXRightBoundary() const
+{
+ SAL_WARN_IF( mpFirstSep == nullptr, "vcl", "ImplRegionBand::XRightBoundary -> no separation in band!" );
+ if (!mpFirstSep)
+ return 0;
+ // 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 0000000000..6665b5c48d
--- /dev/null
+++ b/vcl/source/gdi/region.cxx
@@ -0,0 +1,1793 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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)
+{
+ if (!rRect.IsEmpty())
+ mpRegionBand = std::make_shared<RegionBand>(rRect);
+}
+
+Region::Region(const tools::Polygon& rPolygon)
+: mbIsNull(false)
+{
+
+ if(rPolygon.GetSize())
+ {
+ ImplCreatePolyPolyRegion(tools::PolyPolygon(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())
+ {
+ std::shared_ptr<RegionBand> pNew = std::make_shared<RegionBand>(*getRegionBand());
+
+ pNew->Move(nHorzMove, nVertMove);
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand = std::move(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())
+ {
+ std::shared_ptr<RegionBand> pNew = std::make_shared<RegionBand>(*getRegionBand());
+
+ pNew->Scale(fScaleX, fScaleY);
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand = std::move(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
+ if(!mpRegionBand)
+ {
+ // empty? -> done!
+ return;
+ }
+
+ std::shared_ptr<RegionBand>& pNew = mpRegionBand;
+ // only make a copy if someone else is also using it
+ if (pNew.use_count() > 1)
+ pNew = std::make_shared<RegionBand>(*pNew);
+
+ // 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();
+}
+
+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();
+ if (!rRect.IsEmpty())
+ mpRegionBand = std::make_shared<RegionBand>(rRect);
+ else
+ mpRegionBand.reset();
+ 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 0000000000..7b48df7a09
--- /dev/null
+++ b/vcl/source/gdi/regionband.cxx
@@ -0,0 +1,1365 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <o3tl/safeint.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 = o3tl::saturating_add(pBand->mnYTop, nVertMove);
+ pBand->mnYBottom = o3tl::saturating_add(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
+{
+ if (!mpFirstBand)
+ return tools::Rectangle();
+
+ // 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 0000000000..622c86c395
--- /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 0000000000..0a5eb5140a
--- /dev/null
+++ b/vcl/source/gdi/salgdilayout.cxx
@@ -0,0 +1,940 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <salgdi.hxx>
+#include <salframe.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;
+ const int nParentX = aGeom.x() - pParent->maGeometry.x();
+ aGeom.setX(pParent->maGeometry.x() + pParent->maGeometry.width() - maGeometry.width() - nParentX);
+ return aGeom;
+ }
+ else
+ return maGeometry;
+}
+
+SalGraphics::SalGraphics()
+: m_nLayout( SalLayoutFlags::NONE ),
+ m_eLastMirrorMode(MirrorMode::NONE),
+ m_nLastMirrorTranslation(0),
+ m_bAntiAlias(false)
+{
+ // read global RTL settings
+ if( AllSettings::GetLayoutRTL() )
+ m_nLayout = SalLayoutFlags::BiDiRtl;
+}
+
+bool SalGraphics::initWidgetDrawBackends(bool bForce)
+{
+ static 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;
+}
+
+void 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 );
+ setClipRegion( aMirror );
+ }
+ else
+ {
+ 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);
+ }
+}
+
+void 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())
+ {
+ 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();
+}
+
+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 0000000000..af281127ba
--- /dev/null
+++ b/vcl/source/gdi/sallayout.cxx
@@ -0,0 +1,1182 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <algorithm>
+#include <memory>
+
+#include <impglyphitem.hxx>
+
+// Glyph Flags
+#define GF_FONTMASK 0xF0000000
+#define GF_FONTSHIFT 28
+
+
+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 ),
+ maLanguageTag( LANGUAGE_DONTKNOW ),
+ mnOrientation( 0 ),
+ maDrawOffset( 0, 0 ),
+ mbSubpixelPositioning(false)
+{}
+
+SalLayout::~SalLayout()
+{}
+
+void SalLayout::AdjustLayout( vcl::text::ImplLayoutArgs& rArgs )
+{
+ mnMinCharPos = rArgs.mnMinCharPos;
+ mnEndCharPos = rArgs.mnEndCharPos;
+ mnOrientation = rArgs.mnOrientation;
+ maLanguageTag = rArgs.maLanguageTag;
+}
+
+basegfx::B2DPoint SalLayout::GetDrawPosition(const basegfx::B2DPoint& rRelative) const
+{
+ basegfx::B2DPoint aPos(maDrawBase);
+ basegfx::B2DPoint 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 (mbSubpixelPositioning)
+ {
+ double nX = +fCos * fX + fSin * fY;
+ double nY = +fCos * fY - fSin * fX;
+ aPos += basegfx::B2DPoint(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 += basegfx::B2DPoint(nX, nY);
+ }
+ }
+
+ return aPos;
+}
+
+bool SalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rVector) const
+{
+ bool bAllOk = true;
+ bool bOneOk = false;
+
+ basegfx::B2DPolyPolygon aGlyphOutline;
+
+ basegfx::B2DPoint 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;
+
+ basegfx::B2DPoint 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
+}
+
+double GenericSalLayout::FillDXArray( std::vector<double>* pCharWidths, const OUString& rStr ) const
+{
+ if (pCharWidths)
+ GetCharWidths(*pCharWidths, rStr);
+
+ return GetTextWidth();
+}
+
+// the text width is the maximum logical extent of all glyphs
+double GenericSalLayout::GetTextWidth() const
+{
+ if (!m_GlyphItems.IsValid())
+ return 0;
+
+ double nWidth = 0;
+ for (auto const& aGlyphItem : m_GlyphItems)
+ nWidth += aGlyphItem.newWidth();
+
+ return nWidth;
+}
+
+void GenericSalLayout::Justify(double nNewWidth)
+{
+ double 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;
+ double nMaxGlyphWidth = 0;
+ for(pGlyphIter = m_GlyphItems.begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter)
+ {
+ if( !pGlyphIter->IsInCluster() )
+ ++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
+ double 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->IsInCluster() || (nStretchable <= 0) )
+ continue;
+
+ // distribute extra space equally to stretchable glyphs
+ double nDeltaWidth = nDiffWidth / nStretchable--;
+ nDiffWidth -= nDeltaWidth;
+ pGlyphIter->addNewWidth(nDeltaWidth);
+ nDeltaSum += nDeltaWidth;
+ }
+ }
+ else // condensed case
+ {
+ // squeeze width by moving glyphs proportionally
+ double fSqueeze = nNewWidth / nOldWidth;
+ if(m_GlyphItems.size() > 1)
+ {
+ for( pGlyphIter = m_GlyphItems.begin(); ++pGlyphIter != pGlyphIterRight;)
+ {
+ double nX = pGlyphIter->linearPos().getX();
+ nX = 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(std::u16string_view rStr)
+{
+ const int nLength = rStr.size();
+ 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
+ double 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(std::vector<double>& rCaretPositions,
+ const OUString& rStr) const
+{
+ const int nCaretPositions = (mnEndCharPos - mnMinCharPos) * 2;
+
+ rCaretPositions.clear();
+ rCaretPositions.resize(nCaretPositions, -1);
+
+ if (m_GlyphItems.empty())
+ return;
+
+ std::vector<double> aCharWidths;
+ GetCharWidths(aCharWidths, rStr);
+
+ // calculate caret positions using glyph array
+ for (auto const& aGlyphItem : m_GlyphItems)
+ {
+ auto nCurrX = aGlyphItem.linearPos().getX() - aGlyphItem.xOffset();
+ auto nCharStart = aGlyphItem.charPos();
+ auto nCharEnd = nCharStart + aGlyphItem.charCount() - 1;
+ if (!aGlyphItem.IsRTLGlyph())
+ {
+ // unchanged positions for LTR case
+ for (int i = nCharStart; i <= nCharEnd; i++)
+ {
+ int n = i - mnMinCharPos;
+ int nCurrIdx = 2 * n;
+
+ auto nLeft = nCurrX;
+ nCurrX += aCharWidths[n];
+ auto nRight = nCurrX;
+
+ rCaretPositions[nCurrIdx] = nLeft;
+ rCaretPositions[nCurrIdx + 1] = nRight;
+ }
+ }
+ else
+ {
+ // reverse positions for RTL case
+ for (int i = nCharEnd; i >= nCharStart; i--)
+ {
+ int n = i - mnMinCharPos;
+ int nCurrIdx = 2 * n;
+
+ auto nRight = nCurrX;
+ nCurrX += aCharWidths[n];
+ auto nLeft = nCurrX;
+
+ rCaretPositions[nCurrIdx] = nLeft;
+ rCaretPositions[nCurrIdx + 1] = nRight;
+ }
+ }
+ }
+}
+
+sal_Int32 GenericSalLayout::GetTextBreak(double nMaxWidth, double nCharExtra, int nFactor) const
+{
+ std::vector<double> aCharWidths;
+ GetCharWidths(aCharWidths, {});
+
+ double nWidth = 0;
+ for( int i = mnMinCharPos; i < mnEndCharPos; ++i )
+ {
+ double 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,
+ basegfx::B2DPoint& rPos, int& nStart,
+ const LogicalFontInstance** ppGlyphFont) 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
+ basegfx::B2DPoint 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<double> 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
+ double 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
+ double 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) )
+ {
+ double nDiffWidth = nTargetWidth - nOrigWidth;
+ double nWidthSum = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ double nJustWidth = aJustificationArray[i];
+ if( (nJustWidth > 0) && (nStretchable > 0) )
+ {
+ double nDeltaWidth = nDiffWidth / nStretchable;
+ nJustWidth += nDeltaWidth;
+ nDiffWidth -= nDeltaWidth;
+ --nStretchable;
+ }
+ nWidthSum += nJustWidth;
+ aJustificationArray[i] = nWidthSum;
+ }
+ if( nWidthSum != nTargetWidth )
+ aJustificationArray[ nCharCount-1 ] = nTargetWidth;
+
+ // change the DXArray temporarily (just for the justification)
+ aMultiArgs.mpDXArray = aJustificationArray.data();
+ }
+ }
+
+ ImplAdjustMultiLayout(rArgs, aMultiArgs, aMultiArgs.mpDXArray);
+}
+
+void MultiSalLayout::ImplAdjustMultiLayout(vcl::text::ImplLayoutArgs& rArgs,
+ vcl::text::ImplLayoutArgs& rMultiArgs,
+ const double* 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 };
+
+ basegfx::B2DPoint 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
+ double 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].PosIsInAnyRun(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
+ double 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(double nMaxWidth, double 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<double> aCharWidths;
+ std::vector<double> 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];
+ }
+
+ double nWidth = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ nWidth += aCharWidths[ i ] * nFactor;
+ if( nWidth > nMaxWidth )
+ return (i + mnMinCharPos);
+ nWidth += nCharExtra;
+ }
+
+ return -1;
+}
+
+double MultiSalLayout::GetTextWidth() const
+{
+ // Measure text width. There might be holes in each SalLayout due to
+ // missing chars, so we use GetNextGlyph() to get the glyphs across all
+ // layouts.
+ int nStart = 0;
+ basegfx::B2DPoint aPos;
+ const GlyphItem* pGlyphItem;
+
+ double nWidth = 0;
+ while (GetNextGlyph(&pGlyphItem, aPos, nStart))
+ nWidth += pGlyphItem->newWidth();
+
+ return nWidth;
+}
+
+double MultiSalLayout::FillDXArray( std::vector<double>* pCharWidths, const OUString& rStr ) const
+{
+ if (pCharWidths)
+ {
+ // prepare merging of fallback levels
+ std::vector<double> aTempWidths;
+ const int nCharCount = mnEndCharPos - mnMinCharPos;
+ pCharWidths->clear();
+ pCharWidths->resize(nCharCount, 0);
+
+ for (int n = mnLevel; --n >= 0;)
+ {
+ // query every fallback level
+ mpLayouts[n]->FillDXArray(&aTempWidths, rStr);
+
+ // 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;
+ double nCharWidth = aTempWidths[i];
+ if (!nCharWidth)
+ continue;
+ (*pCharWidths)[i] = nCharWidth;
+ }
+ }
+ }
+
+ return GetTextWidth();
+}
+
+void MultiSalLayout::GetCaretPositions(std::vector<double>& rCaretPositions,
+ const OUString& rStr) const
+{
+ // prepare merging of fallback levels
+ std::vector<double> aTempPos;
+ const int nCaretPositions = (mnEndCharPos - mnMinCharPos) * 2;
+ rCaretPositions.clear();
+ rCaretPositions.resize(nCaretPositions, -1);
+
+ for (int n = mnLevel; --n >= 0;)
+ {
+ // query every fallback level
+ mpLayouts[n]->GetCaretPositions(aTempPos, rStr);
+
+ // calculate virtual char widths using most probable fallback layout
+ for (int i = 0; i < nCaretPositions; ++i)
+ {
+ // one char cannot be resolved from different fallbacks
+ if (rCaretPositions[i] != -1)
+ continue;
+ if (aTempPos[i] >= 0)
+ rCaretPositions[i] = aTempPos[i];
+ }
+ }
+}
+
+bool MultiSalLayout::GetNextGlyph(const GlyphItem** pGlyph,
+ basegfx::B2DPoint& rPos, int& nStart,
+ const LogicalFontInstance** ppGlyphFont) 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();
+ if (rLayout.GetNextGlyph(pGlyph, rPos, nStart, ppGlyphFont))
+ {
+ int nFontTag = nLevel << GF_FONTSHIFT;
+ nStart |= nFontTag;
+ 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, int nNextCharPos) const
+{
+ // Check the base layout
+ bool bValid = mpLayouts[0]->IsKashidaPosValid(nCharPos, nNextCharPos);
+
+ // 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) &&
+ maFallbackRuns[i - 1].PosIsInAnyRun(nNextCharPos))
+ {
+ bValid = mpLayouts[i]->IsKashidaPosValid(nCharPos, nNextCharPos);
+ 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 0000000000..e8c09ab1d8
--- /dev/null
+++ b/vcl/source/gdi/salmisc.cxx
@@ -0,0 +1,433 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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::optional<BitmapBuffer> StretchAndConvert(
+ const BitmapBuffer& rSrcBuffer, const SalTwoRect& rTwoRect,
+ ScanlineFormat nDstBitmapFormat, std::optional<BitmapPalette> pDstPal, const ColorMask* pDstMask )
+{
+ FncGetPixel pFncGetPixel;
+ FncSetPixel pFncSetPixel;
+ std::optional<BitmapBuffer> pDstBuffer(std::in_place);
+
+ // 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( 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 std::nullopt;
+ }
+ pDstBuffer->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase);
+ if (pDstBuffer->mnScanlineSize < nScanlineBase/8)
+ {
+ SAL_WARN("vcl.gdi", "scanline calculation wraparound");
+ pDstBuffer->mpBits = nullptr;
+ return std::nullopt;
+ }
+ try
+ {
+ pDstBuffer->mpBits = new sal_uInt8[ pDstBuffer->mnScanlineSize * pDstBuffer->mnHeight ];
+ }
+ catch( const std::bad_alloc& )
+ {
+ // memory exception, clean up
+ pDstBuffer->mpBits = nullptr;
+ return std::nullopt;
+ }
+
+ // do we need a destination palette or color mask?
+ if( ( nDstScanlineFormat == ScanlineFormat::N1BitMsbPal ) ||
+ ( nDstScanlineFormat == ScanlineFormat::N8BitPal ) )
+ {
+ assert(pDstPal && "destination buffer requires palette");
+ if (!pDstPal)
+ {
+ return std::nullopt;
+ }
+ pDstBuffer->maPalette = *pDstPal;
+ }
+ else if(nDstScanlineFormat == ScanlineFormat::N32BitTcMask )
+ {
+ assert(pDstMask && "destination buffer requires color mask");
+ if (!pDstMask)
+ {
+ return std::nullopt;
+ }
+ 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 std::nullopt;
+ }
+
+ // 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 0000000000..19cb54772b
--- /dev/null
+++ b/vcl/source/gdi/scrptrun.cxx
@@ -0,0 +1,259 @@
+/*
+ *******************************************************************************
+ *
+ * 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/uchar.h>
+#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;
+ }
+
+};
+
+UScriptCode getScript(UChar32 ch, UErrorCode* status)
+{
+ // tdf#154549
+ // Make combining marks inherit the script of their bases, regardless of
+ // their own script.
+ if (u_getIntPropertyValue(ch, UCHAR_GENERAL_CATEGORY) == U_NON_SPACING_MARK)
+ return USCRIPT_INHERITED;
+
+ UScriptCode script = uscript_getScript(ch, status);
+ if (U_FAILURE(*status))
+ return script;
+
+ // 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.
+ 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/vectorgraphicdata.cxx b/vcl/source/gdi/vectorgraphicdata.cxx
new file mode 100644
index 0000000000..9d94b171a4
--- /dev/null
+++ b/vcl/source/gdi/vectorgraphicdata.cxx
@@ -0,0 +1,357 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/diagnose_ex.hxx>
+#include <tools/stream.hxx>
+#include <sal/log.hxx>
+#include <utility>
+#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 <rtl/crc.h>
+#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:
+ {
+ const uno::Reference<io::XInputStream> xInputStream = maDataContainer.getAsXInputStream();
+
+ 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);
+
+ const uno::Reference<io::XInputStream> xInputStream = maDataContainer.getAsXInputStream();
+
+ 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(
+ BinaryDataContainer aDataContainer,
+ VectorGraphicDataType eVectorDataType,
+ sal_Int32 nPageIndex)
+: maDataContainer(std::move(aDataContainer)),
+ 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)
+ {
+ BinaryDataContainer aData(rIStm, nStmLen);
+
+ if (!rIStm.GetError())
+ {
+ maDataContainer = aData;
+ }
+ }
+}
+
+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 rtl_crc32(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 0000000000..b86c4ae496
--- /dev/null
+++ b/vcl/source/gdi/virdev.cxx
@@ -0,0 +1,521 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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, meFormatAndAlpha, 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 eFormatAndAlpha,
+ OutDevType eOutDevType)
+ : OutputDevice(eOutDevType)
+ , meFormatAndAlpha(eFormatAndAlpha)
+{
+ SAL_INFO( "vcl.virdev", "VirtualDevice::VirtualDevice( " << static_cast<int>(eFormatAndAlpha)
+ << ", " << static_cast<int>(eOutDevType) << " )" );
+
+ ImplInitVirDev(pCompDev ? pCompDev : Application::GetDefaultDevice(), 0, 0);
+}
+
+VirtualDevice::VirtualDevice(const SystemGraphicsData& rData, const Size &rSize,
+ DeviceFormat eFormat)
+ : OutputDevice(OUTDEV_VIRDEV)
+ , meFormatAndAlpha(eFormat)
+{
+ 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, meFormatAndAlpha);
+ 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 opaque,
+ // fill rect with that (linecolor, too, because of
+ // those pesky missing pixel problems)
+ Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
+ SetLineColor( COL_ALPHA_OPAQUE );
+ SetFillColor( COL_ALPHA_OPAQUE );
+ DrawRect( rRect );
+ Pop();
+}
+
+bool VirtualDevice::ImplSetOutputSizePixel( const Size& rNewSize, bool bErase,
+ sal_uInt8 *const pBuffer, bool bAlphaMaskTransparent )
+{
+ if( InnerImplSetOutputSizePixel(rNewSize, bErase, pBuffer) )
+ {
+ if (meFormatAndAlpha != DeviceFormat::WITHOUT_ALPHA)
+ {
+ // #110958# Setup alpha bitmap
+ if(mpAlphaVDev && mpAlphaVDev->GetOutputSizePixel() != rNewSize)
+ {
+ mpAlphaVDev.disposeAndClear();
+ }
+
+ if( !mpAlphaVDev )
+ {
+ mpAlphaVDev = VclPtr<VirtualDevice>::Create(*this, meFormatAndAlpha);
+ mpAlphaVDev->InnerImplSetOutputSizePixel(rNewSize, bErase, nullptr);
+ mpAlphaVDev->SetBackground( Wallpaper(bAlphaMaskTransparent ? COL_ALPHA_TRANSPARENT : COL_ALPHA_OPAQUE) );
+ mpAlphaVDev->Erase();
+ }
+
+ // TODO: copy full outdev state to new one, here. Also needed in outdev2.cxx:DrawOutDev
+ if( GetLineColor() != COL_TRANSPARENT )
+ mpAlphaVDev->SetLineColor( COL_ALPHA_OPAQUE );
+
+ if( GetFillColor() != COL_TRANSPARENT )
+ mpAlphaVDev->SetFillColor( COL_ALPHA_OPAQUE );
+
+ mpAlphaVDev->SetMapMode( GetMapMode() );
+
+ mpAlphaVDev->SetAntialiasing( GetAntialiasing() );
+ }
+
+ 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, bool bAlphaMaskTransparent )
+{
+ return ImplSetOutputSizePixel(rNewSize, bErase, nullptr, bAlphaMaskTransparent);
+}
+
+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 0000000000..2c7d571e90
--- /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( css::awt::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 0000000000..89ae5eb8da
--- /dev/null
+++ b/vcl/source/graphic/BinaryDataContainer.cxx
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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>
+#include <unotools/tempfile.hxx>
+#include <comphelper/lok.hxx>
+#include <comphelper/seqstream.hxx>
+#include <sal/log.hxx>
+
+struct BinaryDataContainer::Impl
+{
+ // temp file to store the data out of RAM if necessary
+ std::unique_ptr<utl::TempFileFast> mpFile;
+ // the binary data
+ std::shared_ptr<std::vector<sal_uInt8>> mpData;
+
+ Impl(SvStream& stream, size_t size) { readData(stream, size); }
+
+ /// Populate mpData from the stream
+ void readData(SvStream& stream, size_t size)
+ {
+ auto pData = std::make_shared<std::vector<sal_uInt8>>(size);
+ if (stream.ReadBytes(pData->data(), pData->size()) == size)
+ mpData = std::move(pData);
+ }
+
+ /// ensure the data is in-RAM
+ void ensureSwappedIn()
+ {
+ if (mpData || !mpFile)
+ return;
+
+ auto pStream = mpFile->GetStream(StreamMode::READ);
+ pStream->Seek(0);
+ readData(*pStream, pStream->remainingSize());
+
+ // Horrifying data loss ...
+ SAL_WARN_IF(pStream->GetError(), "vcl",
+ "Inconsistent system - failed to swap image back in");
+ }
+
+ void swapOut()
+ {
+ if (mpFile)
+ {
+ // we already have it swapped out.
+ mpData.reset();
+ return;
+ }
+
+ if (!mpData || mpData->empty())
+ return;
+
+ mpFile.reset(new utl::TempFileFast());
+ auto pStream = mpFile->GetStream(StreamMode::READWRITE);
+
+ pStream->WriteBytes(mpData->data(), mpData->size());
+
+ mpData.reset();
+ }
+};
+
+BinaryDataContainer::BinaryDataContainer(SvStream& stream, size_t size)
+ : mpImpl(new Impl(stream, size))
+{
+}
+
+size_t BinaryDataContainer::calculateHash() const
+{
+ size_t nSeed = 0;
+ if (mpImpl && mpImpl->mpData && !mpImpl->mpData->empty())
+ {
+ o3tl::hash_combine(nSeed, getSize());
+ for (sal_uInt8 const& rByte : *mpImpl->mpData)
+ o3tl::hash_combine(nSeed, rByte);
+ }
+ return nSeed;
+}
+
+css::uno::Sequence<sal_Int8> BinaryDataContainer::getCopyAsByteSequence() const
+{
+ if (isEmpty())
+ return css::uno::Sequence<sal_Int8>();
+ assert(mpImpl);
+
+ css::uno::Sequence<sal_Int8> aData(getSize());
+
+ std::copy(mpImpl->mpData->cbegin(), mpImpl->mpData->cend(), aData.getArray());
+
+ return aData;
+}
+
+namespace
+{
+/*
+ * Hold a reference on the internal state in case we swap out
+ * and free the vector while someone holds an SvStream pointer.
+ */
+class ReferencedMemoryStream : public SvMemoryStream
+{
+ std::shared_ptr<std::vector<sal_uInt8>> mpData;
+
+public:
+ ReferencedMemoryStream(const std::shared_ptr<std::vector<sal_uInt8>>& pData)
+ : SvMemoryStream(pData->data(), pData->size(), StreamMode::READ)
+ , mpData(pData)
+ {
+ }
+};
+
+class ReferencedXInputStream : public comphelper::MemoryInputStream
+{
+ std::shared_ptr<std::vector<sal_uInt8>> mpData;
+
+public:
+ ReferencedXInputStream(const std::shared_ptr<std::vector<sal_uInt8>>& pData)
+ : comphelper::MemoryInputStream(reinterpret_cast<const sal_Int8*>(pData->data()),
+ pData->size())
+ , mpData(pData)
+ {
+ }
+};
+}
+
+std::shared_ptr<SvStream> BinaryDataContainer::getAsStream()
+{
+ ensureSwappedIn(); // TODO: transfer in streamed chunks
+ return std::make_shared<ReferencedMemoryStream>(mpImpl->mpData);
+}
+
+css::uno::Reference<css::io::XInputStream> BinaryDataContainer::getAsXInputStream()
+{
+ ensureSwappedIn(); // TODO: transfer in streamed chunks
+ return new ReferencedXInputStream(mpImpl->mpData);
+}
+
+std::size_t BinaryDataContainer::writeToStream(SvStream& rStream) const
+{
+ ensureSwappedIn(); // TODO: transfer in streamed chunks
+ return rStream.WriteBytes(getData(), getSize());
+}
+
+size_t BinaryDataContainer::getSize() const
+{
+ ensureSwappedIn();
+ return mpImpl && mpImpl->mpData ? mpImpl->mpData->size() : 0;
+}
+
+size_t BinaryDataContainer::getSizeBytes() const
+{
+ return mpImpl && mpImpl->mpData ? mpImpl->mpData->size() : 0;
+}
+
+bool BinaryDataContainer::isEmpty() const
+{
+ ensureSwappedIn();
+ return !mpImpl || !mpImpl->mpData || mpImpl->mpData->empty();
+}
+
+const sal_uInt8* BinaryDataContainer::getData() const
+{
+ ensureSwappedIn();
+ return mpImpl && mpImpl->mpData ? mpImpl->mpData->data() : nullptr;
+}
+
+void BinaryDataContainer::ensureSwappedIn() const
+{
+ if (mpImpl)
+ mpImpl->ensureSwappedIn();
+}
+
+void BinaryDataContainer::swapOut() const
+{
+ // Only bother reducing memory footprint in kit mode - for mobile/online etc.
+ if (!mpImpl || !comphelper::LibreOfficeKit::isActive())
+ return;
+
+ mpImpl->swapOut();
+}
+
+/* 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 0000000000..c643803313
--- /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
+ = dynamic_cast<UnoBinaryDataContainer*>(rxBinaryDataContainer.get());
+ 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 0000000000..d78acea530
--- /dev/null
+++ b/vcl/source/graphic/GraphicID.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 <graphic/GraphicID.hxx>
+
+#include <impgraph.hxx>
+#include <rtl/crc.h>
+#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 = rtl_crc32(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 0000000000..63d35efb68
--- /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 0000000000..7abf3b3d3b
--- /dev/null
+++ b/vcl/source/graphic/GraphicObject.cxx
@@ -0,0 +1,935 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <utility>
+#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( Graphic aGraphic, const GraphicAttr& rAttr ) :
+ maGraphic(std::move( aGraphic )), maAttr( rAttr ) {}
+};
+
+GraphicObject::GraphicObject()
+{
+}
+
+GraphicObject::GraphicObject(Graphic aGraphic)
+ : maGraphic(std::move(aGraphic))
+{
+}
+
+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 = tools::PolyPolygon(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 nRendererId,
+ 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, nRendererId, pFirstFrameOutDev);
+
+ if( bCropped )
+ rOut.Pop();
+
+ bRet = true;
+ }
+ else
+ bRet = Draw(rOut, rPt, rSz, &aAttr);
+
+ return bRet;
+}
+
+void GraphicObject::StopAnimation( const OutputDevice* pOut, tools::Long nRendererId )
+{
+ if (mxSimpleCache)
+ mxSimpleCache->maGraphic.StopAnimation(pOut, nRendererId);
+}
+
+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 )
+ {
+ AnimationFrame aAnimationFrame( aAnim.Get( nFrame ) );
+
+ if( !aCropRect.Contains( tools::Rectangle(aAnimationFrame.maPositionPixel, aAnimationFrame.maSizePixel) ) )
+ {
+ // setup actual cropping (relative to frame position)
+ tools::Rectangle aCropRectRel( aCropRect );
+ aCropRectRel.Move( -aAnimationFrame.maPositionPixel.X(),
+ -aAnimationFrame.maPositionPixel.Y() );
+
+ // cropping affects this frame, apply it then
+ // do _not_ apply enlargement, this is done below
+ ImplTransformBitmap( aAnimationFrame.maBitmapEx, rAttr, Size(), Size(),
+ aCropRectRel, rDestSize, false );
+
+ aAnim.Replace( aAnimationFrame, 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 )
+ {
+ AnimationFrame aAnimationFrame( aAnim.Get( nFrame ) );
+
+ aAnimationFrame.maPositionPixel += aPosOffset;
+
+ aAnim.Replace( aAnimationFrame, 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 0000000000..a8dd186a3d
--- /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(std::move(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().GetAlphaMask().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() ).CreateAlphaMask( 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 = rBmpEx;
+ }
+ else
+ {
+ // #104115# Generate mask bitmap and init to zero
+ AlphaMask aMask(aBmpSize);
+ aMask.Erase(255);
+
+ // #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 0000000000..9137ebd8a2
--- /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 0000000000..f77e3e3c40
--- /dev/null
+++ b/vcl/source/graphic/Manager.cxx
@@ -0,0 +1,317 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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, bool bDropAll)
+{
+ // 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) && !bDropAll)
+ return;
+
+ if (pEachImpGraphic->isSwappedOut())
+ continue;
+
+ sal_Int64 nCurrentGraphicSize = getGraphicSizeBytes(pEachImpGraphic);
+ if (nCurrentGraphicSize > 100000 || bDropAll)
+ {
+ 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, bool bDropAll)
+{
+ // maMutex is locked in callers
+
+ if (!mbSwapEnabled)
+ return;
+
+ if (mnUsedSize < mnMemoryLimit && !bDropAll)
+ return;
+
+ // avoid recursive reduceGraphicMemory on reexport of tdf118346-1.odg to odg
+ if (mbReducingGraphicMemory)
+ return;
+
+ mbReducingGraphicMemory = true;
+
+ loopGraphicsAndSwapOut(rGuard, bDropAll);
+
+ 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;
+}
+
+void Manager::dropCache()
+{
+ std::unique_lock aGuard(maMutex);
+
+ reduceGraphicMemory(aGuard, true);
+}
+
+void Manager::dumpState(rtl::OStringBuffer& rState)
+{
+ std::unique_lock aGuard(maMutex);
+
+ rState.append("\nImage Manager items:\t");
+ rState.append(static_cast<sal_Int32>(m_pImpGraphicList.size()));
+ rState.append("\tsize:\t");
+ rState.append(static_cast<sal_Int64>(mnUsedSize / 1024));
+ rState.append("\tkb");
+
+ for (ImpGraphic* pEachImpGraphic : m_pImpGraphicList)
+ {
+ pEachImpGraphic->dumpState(rState);
+ }
+}
+
+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 0000000000..3fe277024a
--- /dev/null
+++ b/vcl/source/graphic/UnoBinaryDataContainer.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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::uno::Sequence<sal_Int8> SAL_CALL UnoBinaryDataContainer::getCopyAsByteSequence()
+{
+ return maBinaryDataContainer.getCopyAsByteSequence();
+}
+
+/* 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 0000000000..9036f54452
--- /dev/null
+++ b/vcl/source/graphic/UnoGraphic.cxx
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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::queryInterface( 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<graphic::XGraphicTransformer>::get())
+ aAny <<= uno::Reference< graphic::XGraphicTransformer >(this);
+ else
+ aAny = ::unographic::GraphicDescriptor::queryInterface( rType );
+
+ return aAny;
+}
+
+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().GetAlphaMask().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>();
+ }
+}
+
+// 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.ChangeColorAlpha( cIndexFrom, 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());
+ AlphaMask aMask(aBitmap.CreateAlphaMask(aColorFrom, nTolerance));
+ aBitmap.Replace(aColorFrom, aColorTo, nTolerance);
+ aReturnGraphic = ::Graphic(BitmapEx(aBitmap, aMask));
+ }
+ else
+ {
+ 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.GetAlphaMask() );
+
+ 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 0000000000..e83ac98959
--- /dev/null
+++ b/vcl/source/graphic/UnoGraphicDescriptor.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 <graphic/UnoGraphicDescriptor.hxx>
+
+#include <cppuhelper/weakagg.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;
+
+ OUString aMimeType;
+ sal_uInt8 cType = graphic::GraphicType::EMPTY;
+
+ switch( aDescriptor.GetFileFormat() )
+ {
+ case GraphicFileFormat::BMP: aMimeType = MIMETYPE_BMP; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::GIF: aMimeType = MIMETYPE_GIF; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::JPG: aMimeType = MIMETYPE_JPG; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PCD: aMimeType = MIMETYPE_PCD; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PCX: aMimeType = MIMETYPE_PCX; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PNG: aMimeType = MIMETYPE_PNG; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::TIF: aMimeType = MIMETYPE_TIF; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::XBM: aMimeType = MIMETYPE_XBM; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::XPM: aMimeType = MIMETYPE_XPM; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PBM: aMimeType = MIMETYPE_PBM; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PGM: aMimeType = MIMETYPE_PGM; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PPM: aMimeType = MIMETYPE_PPM; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::RAS: aMimeType = MIMETYPE_RAS; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::TGA: aMimeType = MIMETYPE_TGA; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PSD: aMimeType = MIMETYPE_PSD; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::WEBP: aMimeType = MIMETYPE_WEBP; cType = graphic::GraphicType::PIXEL; break;
+
+ case GraphicFileFormat::EPS: aMimeType = MIMETYPE_EPS; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::DXF: aMimeType = MIMETYPE_DXF; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::MET: aMimeType = MIMETYPE_MET; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::PCT: aMimeType = MIMETYPE_PCT; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::SVM: aMimeType = MIMETYPE_SVM; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::WMF: aMimeType = MIMETYPE_WMF; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::WMZ: aMimeType = MIMETYPE_WMF; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::EMF: aMimeType = MIMETYPE_EMF; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::EMZ: aMimeType = MIMETYPE_EMF; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::SVG: aMimeType = MIMETYPE_SVG; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::SVGZ: aMimeType = MIMETYPE_SVG; cType = graphic::GraphicType::VECTOR; break;
+
+ default:
+ break;
+ }
+
+ if( graphic::GraphicType::EMPTY != cType )
+ {
+ meType = ( ( graphic::GraphicType::PIXEL == cType ) ? GraphicType::Bitmap : GraphicType::GdiMetafile );
+ maMimeType = aMimeType;
+ maSizePixel = aDescriptor.GetSizePixel();
+ maSize100thMM = aDescriptor.GetSize_100TH_MM();
+ mnBitsPerPixel = aDescriptor.GetBitsPerPixel();
+ mbTransparent = ( graphic::GraphicType::VECTOR == cType );
+ }
+}
+
+
+uno::Any SAL_CALL GraphicDescriptor::queryInterface( 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 = OWeakObject::queryInterface( rType );
+
+ return aAny;
+}
+
+
+void SAL_CALL GraphicDescriptor::acquire()
+ noexcept
+{
+ OWeakObject::acquire();
+}
+
+
+void SAL_CALL GraphicDescriptor::release()
+ noexcept
+{
+ OWeakObject::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() )
+ {
+ GfxLink aLink = mpGraphic->GetGfxLink();
+ switch (aLink.GetType())
+ {
+ case GfxLinkType::NativeGif: aMimeType = MIMETYPE_GIF; break;
+
+ // #i15508# added BMP type for better exports (checked, works)
+ case GfxLinkType::NativeBmp: aMimeType = MIMETYPE_BMP; break;
+
+ case GfxLinkType::NativeJpg: aMimeType = MIMETYPE_JPG; break;
+ case GfxLinkType::NativePng: aMimeType = MIMETYPE_PNG; break;
+ case GfxLinkType::NativeTif: aMimeType = MIMETYPE_TIF; break;
+ case GfxLinkType::NativeWmf: aMimeType = aLink.IsEMF() ? MIMETYPE_EMF : MIMETYPE_WMF; break;
+ case GfxLinkType::NativeMet: aMimeType = MIMETYPE_MET; break;
+ case GfxLinkType::NativePct: aMimeType = MIMETYPE_PCT; break;
+ case GfxLinkType::NativeWebp: aMimeType = MIMETYPE_WEBP; break;
+
+ // added Svg mimetype support
+ case GfxLinkType::NativeSvg: aMimeType = MIMETYPE_SVG; break;
+ case GfxLinkType::NativePdf: aMimeType = MIMETYPE_PDF; break;
+
+ default:
+ break;
+ }
+ }
+
+ 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 0000000000..6bde780976
--- /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 0000000000..73bbcef3ec
--- /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 0000000000..bceb9fbabe
--- /dev/null
+++ b/vcl/source/graphic/UnoGraphicProvider.cxx
@@ -0,0 +1,837 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 ::unographic::Graphic* pUnoGraphic = dynamic_cast<::unographic::Graphic*>(xIFace.get());
+ const ::Graphic* pGraphic = pUnoGraphic ? &pUnoGraphic->GetGraphic() : nullptr;
+
+ 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 0000000000..9976810923
--- /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 0000000000..c2f6244175
--- /dev/null
+++ b/vcl/source/graphic/VectorGraphicSearch.cxx
@@ -0,0 +1,307 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/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.getWidth()),
+ convertPointToMm100(aPDFSize.getHeight()));
+ 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().getHeight();
+
+ 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 aGraphic)
+ : mpImplementation(std::make_unique<VectorGraphicSearch::Implementation>())
+ , maGraphic(std::move(aGraphic))
+{
+}
+
+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 0000000000..f2e0f7889c
--- /dev/null
+++ b/vcl/source/helper/canvasbitmap.cxx
@@ -0,0 +1,1346 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <comphelper/diagnose_ex.hxx>
+#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);
+}
+
+BitmapScopedReadAccess& 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;
+}
+
+BitmapScopedReadAccess& 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.GetAlphaMask().GetBitmap();
+ m_pAlphaAcc = 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::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.getOpenWidth()*aRequestedBytes.getOpenHeight());
+ sal_Int8* pOutBuf = aRet.getArray();
+
+ bitmapLayout.ScanLines = aRequestedBytes.getOpenHeight();
+ bitmapLayout.ScanLineBytes =
+ bitmapLayout.ScanLineStride= aRequestedBytes.getOpenWidth();
+
+ sal_Int32 nScanlineStride=bitmapLayout.ScanLineStride;
+ if( !(m_pBmpAcc->GetScanlineFormat() & ScanlineFormat::TopDown) )
+ {
+ pOutBuf += bitmapLayout.ScanLineStride*(aRequestedBytes.getOpenHeight()-1);
+ nScanlineStride *= -1;
+ }
+
+ if( !m_aBmpEx.IsAlpha() )
+ {
+ BitmapScopedReadAccess& 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.getOpenWidth());
+ pOutBuf += nScanlineStride;
+ }
+ }
+ else
+ {
+ BitmapScopedReadAccess& pBmpAcc = getBitmapReadAccess();
+ BitmapScopedReadAccess& 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);
+ // vcl used to store transparency. Now it stores alpha. But we need the UNO
+ // interface to still preserve the old interface.
+ *pOutScan++ = 255 - 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++;
+ // vcl used to store transparency. Now it stores alpha. But we need the UNO
+ // interface to still preserve the old interface.
+ *pOutScan++ = 255 - 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() )
+ {
+ BitmapScopedReadAccess& 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
+ {
+ BitmapScopedReadAccess& pBmpAcc = getBitmapReadAccess();
+ BitmapScopedReadAccess& 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);
+ // vcl used to store transparency. Now it stores alpha. But we need the UNO
+ // interface to still preserve the old interface.
+ *pOutBuf = 255 - 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;
+ // vcl used to store transparency. Now it stores alpha. But we need the UNO
+ // interface to still preserve the old interface.
+ *pOutBuf++ = 255 - 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( )
+{
+ return m_nBitsPerOutputPixel;
+}
+
+uno::Sequence< ::sal_Int32 > SAL_CALL VclCanvasBitmap::getComponentBitCounts( )
+{
+ 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() );
+
+ BitmapScopedReadAccess& 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() );
+
+ BitmapScopedReadAccess& 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);
+ 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(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() );
+
+ BitmapScopedReadAccess& 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);
+ 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(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());
+ BitmapScopedReadAccess& 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());
+ BitmapScopedReadAccess& 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());
+ BitmapScopedReadAccess& 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 0000000000..1e1b2bdd2d
--- /dev/null
+++ b/vcl/source/helper/canvastools.cxx
@@ -0,0 +1,627 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/vector/b2dsize.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 <comphelper/diagnose_ex.hxx>
+
+#include <vcl/bitmapex.hxx>
+
+#include <canvasbitmap.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/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( 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( 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 <= 8 ) ? vcl::PixelFormat::N8_BPP :
+ vcl::PixelFormat::N24_BPP;
+ auto eAlphaPixelFormat = 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;
+ if (nAlphaDepth)
+ pAlphaWriteAccess = 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::B2DSize b2DSizeFromSize(const Size& rSize)
+ {
+ return basegfx::B2DSize(rSize.Width(), rSize.Height());
+ }
+
+ basegfx::B2DVector b2DVectorFromSize(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 0000000000..eb964b34b8
--- /dev/null
+++ b/vcl/source/helper/commandinfoprovider.cxx
@@ -0,0 +1,477 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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<OUString> aImageCmdSeq { rsCommandName };
+
+ Sequence<Reference<graphic::XGraphic>> 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)));
+ Reference<ui::XImageManager> xModuleImageManager(xUICfgMgr->getImageManager(), UNO_QUERY);
+
+ Sequence<OUString> aImageCmdSeq { rsCommandName };
+
+ Sequence<Reference<graphic::XGraphic>> 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 0000000000..b06067b9d7
--- /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 );
+
+ std::erase(m_aHandlers, handler);
+}
+
+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 0000000000..e17b45e7a9
--- /dev/null
+++ b/vcl/source/helper/driverblocklist.cxx
@@ -0,0 +1,769 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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>
+#include <utility>
+
+#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(OUString aURL, std::vector<DriverInfo>& rDriverList, VersionType versionType)
+ : meBlockType(BlockType::UNKNOWN)
+ , mrDriverList(rDriverList)
+ , maURL(std::move(aURL))
+ , 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(OUString aString)
+ : maString(std::move(aString))
+ {
+ }
+
+ 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, OUString vendor, VersionComparisonOp op,
+ uint64_t driverVersion, bool bAllowlisted,
+ const char* suggestedVersion /* = nullptr */)
+ : meOperatingSystem(os)
+ , maAdapterVendor(std::move(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/evntpost.cxx b/vcl/source/helper/evntpost.cxx
new file mode 100644
index 0000000000..9e2106f492
--- /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/idletask.cxx b/vcl/source/helper/idletask.cxx
new file mode 100644
index 0000000000..7df0514912
--- /dev/null
+++ b/vcl/source/helper/idletask.cxx
@@ -0,0 +1,51 @@
+/* -*- 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/idletask.hxx>
+#include <vcl/svapp.hxx>
+
+//constructor of IdleTask Class
+IdleTask::IdleTask()
+ : flag(false)
+{
+ //setting the Priority of Idle task to LOW, LOWEST
+ maIdle.SetPriority(TaskPriority::LOWEST);
+ //set idle for callback
+ maIdle.SetInvokeHandler(LINK(this, IdleTask, FlipFlag));
+ //starting the idle
+ maIdle.Start();
+}
+
+//GetFlag() of IdleTask Class
+bool IdleTask::GetFlag() const
+{
+ //returning the status of current flag
+ return flag;
+}
+
+//Callback function of IdleTask Class
+IMPL_LINK(IdleTask, FlipFlag, Timer*, , void)
+{
+ //setting the flag to make sure that low priority idle task has been dispatched
+ flag = true;
+}
+
+void IdleTask::waitUntilIdleDispatched()
+{
+ //creating instance of IdleTask Class
+ IdleTask idleTask;
+ while (!idleTask.GetFlag())
+ {
+ //dispatching all the events via VCL main-loop
+ SolarMutexGuard aGuard;
+ Application::Yield();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/helper/lazydelete.cxx b/vcl/source/helper/lazydelete.cxx
new file mode 100644
index 0000000000..af1f9fcb33
--- /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;
+ std::erase(rList, this);
+}
+
+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 0000000000..ebe48d1200
--- /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 || __GNUC__ == 13)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
+ if( *pLeap == ' ' )
+ *pLeap = 0;
+#if defined(__GNUC__) && (__GNUC__ == 12 || __GNUC__ == 13)
+#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 0000000000..fa9e151fcc
--- /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 <vcl/accessibletable.hxx>
+#include <vcl/accessibletableprovider.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 rtl::Reference<vcl::IAccessibleBrowseBox>
+ createAccessibleBrowseBox(
+ const css::uno::Reference< css::accessibility::XAccessible >& /*_rxParent*/,
+ vcl::IAccessibleTableProvider& /*_rBrowseBox*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual rtl::Reference<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
+ s_hAccessibleImplementationModule = osl_loadModuleRelative( &thisModule, u"" SVLIBRARY( "acc" ) ""_ustr.pData, 0 );
+ if ( s_hAccessibleImplementationModule != nullptr )
+ {
+ s_pAccessibleFactoryFunc = reinterpret_cast<GetSvtAccessibilityComponentFactory>(
+ osl_getFunctionSymbol( s_hAccessibleImplementationModule, u"getSvtAccessibilityComponentFactory"_ustr.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 0000000000..cbd342bd28
--- /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 0000000000..f8c0d56f88
--- /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.GetAlphaMask().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 0000000000..6bf57e6994
--- /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 0000000000..fdc47cbfe7
--- /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 0000000000..cf70052a9f
--- /dev/null
+++ b/vcl/source/image/ImplImage.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 <sal/log.hxx>
+#include <utility>
+#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 <vcl/skia/SkiaHelper.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(OUString aStockName)
+ : maBitmapChecksum(0)
+ , maStockName(std::move(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::WITH_ALPHA);
+
+ // Fix white background in font color and font background color
+ // in the Breeze icons by setting the alpha mask to transparent
+ bool bAlphaMaskTransparent = true;
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled() && SkiaHelper::isAlphaMaskBlendingEnabled())
+ bAlphaMaskTransparent = false;
+#endif
+ aVDev->SetOutputSizePixel(aTarget, true, bAlphaMaskTransparent);
+ 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 0000000000..94ce3b94cf
--- /dev/null
+++ b/vcl/source/image/ImplImageTree.cxx
@@ -0,0 +1,716 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <comphelper/diagnose_ex.hxx>
+#include <tools/stream.hxx>
+#include <tools/urlobj.hxx>
+#include <implimagetree.hxx>
+
+#include <utility>
+#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/filter/PngImageWriter.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;
+ OutputDevice* pDevice = Application::GetDefaultDevice();
+ if (!(meFlags & ImageLoadFlags::IgnoreScalingFactor) && pDevice != nullptr)
+ aScalePercentage = pDevice->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));
+ try
+ {
+ SvFileStream aStream(sUrl, StreamMode::WRITE);
+ vcl::PngImageWriter aWriter(aStream);
+ aWriter.write(rParameters.mrBitmap);
+ 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(Concat2View("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 constexpr OUString aLinkFilename(u"links.txt"_ustr);
+
+ 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 > context, OUString url)
+ : mxContext(std::move(context)), maURL(std::move(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/OpenGLContext.cxx b/vcl/source/opengl/OpenGLContext.cxx
new file mode 100644
index 0000000000..e68e66564d
--- /dev/null
+++ b/vcl/source/opengl/OpenGLContext.cxx
@@ -0,0 +1,496 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <thread>
+#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.
+ std::this_thread::sleep_for(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 0000000000..c944426b75
--- /dev/null
+++ b/vcl/source/opengl/OpenGLHelper.cxx
@@ -0,0 +1,911 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/filter/PngImageWriter.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 <driverblocklist.hxx>
+#include <opengl/zone.hxx>
+#include <vcl/opengl/OpenGLWrapper.hxx>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <desktop/crashreport.hxx>
+#include <vcl/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
+
+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(
+ OStringChar(pHexData[ val & 0xf ]) + OStringChar(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 {
+ SvFileStream sOutput( rFileName, StreamMode::WRITE );
+ vcl::PngImageWriter aWriter( sOutput );
+ aWriter.write( aBitmap );
+ 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 );
+ BitmapScopedWriteAccess 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++ = 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;
+}
+
+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 0000000000..eb033a0fde
--- /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 0000000000..70f4e01926
--- /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 0000000000..bc8a051002
--- /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.WriteOString(pKey);
+ rStrm.WriteOString(": ");
+ 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 0000000000..d9e7dc11b1
--- /dev/null
+++ b/vcl/source/opengl/win/context.cxx
@@ -0,0 +1,667 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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);
+ }
+
+ InitChildWindow(m_pChildWindow.get());
+ const SystemEnvData* sysData(m_pChildWindow->GetSystemData());
+
+ // Hack: the performance of OpenGL increases with non-child window.
+ // Also, this keeps the backing window opaque on GL context destruction,
+ // which doesn't prevent the flicker (tdf#122737) completely, but at
+ // least doesn't show user desktop during the flicker.
+ LONG_PTR style = GetWindowLongPtrW(sysData->hWnd, GWL_STYLE);
+ style &= ~WS_CHILD;
+ SetWindowLongPtrW(sysData->hWnd, GWL_STYLE, style);
+
+ m_aGLWin.hWnd = sysData->hWnd;
+
+ m_aGLWin.hDC = GetDC(m_aGLWin.hWnd);
+}
+
+void WinOpenGLContext::destroyCurrentContext()
+{
+ if (m_aGLWin.hRC)
+ {
+ std::erase(g_vShareList, m_aGLWin.hRC);
+
+ 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 0000000000..93125f9108
--- /dev/null
+++ b/vcl/source/opengl/x11/context.cxx
@@ -0,0 +1,517 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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::erase(g_vShareList, m_aGLWin.ctx);
+
+ 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 0000000000..7c07367a82
--- /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.GetAlphaMask().GetBitmap() ) ) );
+ else
+ mpAlphaVDev->SetBackground( Wallpaper( COL_ALPHA_OPAQUE ));
+ }
+ else if( rBackground.IsGradient())
+ {
+ mpAlphaVDev->SetBackground( Wallpaper( COL_ALPHA_OPAQUE ));
+ }
+ else
+ {
+ // Color background.
+ int alpha = rBackground.GetColor().GetAlpha();
+ mpAlphaVDev->SetBackground( Wallpaper( Color( alpha, alpha, alpha )));
+ }
+}
+
+
+/* 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 0000000000..86ac231375
--- /dev/null
+++ b/vcl/source/outdev/bitmap.cxx
@@ -0,0 +1,993 @@
+/* -*- 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 <vcl/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
+{
+ if ( !mpGraphics && !AcquireGraphics() )
+ return Bitmap();
+
+ assert(mpGraphics);
+
+ 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 ( nWidth <= 0 || nHeight <= 0 || nX > (mnOutWidth + mnOutOffX) || nY > (mnOutHeight + mnOutOffY))
+ return Bitmap();
+
+ Bitmap aBmp;
+ 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.GetBitmap().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)
+ {
+ if (ImplLogicToDevicePixel(aOutSz).IsEmpty()) // nothing to draw
+ return;
+ Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aRelPt, aOutSz ) );
+ if (SalBitmap* pSalAlphaBmp2 = aAlphaBitmap.ImplGetSalBitmap().get())
+ {
+ if (mpGraphics->BlendAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, *pSalAlphaBmp2, *this))
+ {
+ mpAlphaVDev->BlendBitmap(aTR, rAlpha.GetBitmap());
+ return;
+ }
+ }
+ }
+ else
+ {
+ if (mpGraphics->DrawAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, *this))
+ return;
+ }
+
+ // we need to make sure Skia never reaches this slow code path
+ // (but do not fail in no-op cases)
+ assert(!SkiaHelper::isVCLSkiaEnabled() || !SkiaHelper::isAlphaMaskBlendingEnabled()
+ || tools::Rectangle(Point(), rBmp.GetSizePixel())
+ .Intersection(tools::Rectangle(rSrcPtPixel, rSrcSizePixel)).IsEmpty()
+ || mpAlphaVDev->LogicToPixel(mpAlphaVDev->GetOutputSizePixel()).IsEmpty());
+ }
+
+ 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] = std::clamp(nDstLocation + tools::Long(fTemp), tools::Long(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)
+ return false;
+
+ 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);
+
+ BitmapScopedReadAccess pBitmapReadAccess(rBitmap);
+ BitmapScopedReadAccess pAlphaReadAccess(rAlpha);
+
+ SAL_WARN_IF(pAlphaReadAccess->GetScanlineFormat() != ScanlineFormat::N8BitPal, "vcl.gdi", "non-8bit alpha no longer supported!");
+ assert(pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal);
+
+ // #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 );
+
+ const sal_uInt8 nSrcAlpha = pA->GetPixelIndex( nMapY, nMapX );
+ const sal_uInt8 nDstAlpha = 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 );
+ BitmapScopedReadAccess 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[ nResAlpha ] + nD ) >> 16 ] +
+ nVCLGLut[ ( nVCLLut[ nResAlpha ] + nD ) >> 16 ] +
+ nVCLBLut[ ( nVCLLut[ 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(nResAlpha, nResAlpha, 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 )
+{
+ if( !pP || !pA )
+ return aBmp;
+
+ if( GetBitCount() <= 8 )
+ {
+ Bitmap aDither(aBmp.GetSizePixel(), vcl::PixelFormat::N8_BPP);
+ BitmapColor aIndex( 0 );
+ BitmapScopedReadAccess pB(aBmp);
+ BitmapScopedWriteAccess pW(aDither);
+
+ for( int 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;
+
+ Scanline pScanline = pW->GetScanline(nY);
+ Scanline pScanlineAlpha = pA->GetScanline(nMapY);
+ for( int 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 ) ];
+
+ BitmapColor aDstCol = pB->GetColor( nY, nX );
+ aDstCol.Merge( pP->GetColor( nMapY, nMapX ), 255 - 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();
+ return aDither;
+ }
+
+ BitmapScopedWriteAccess pB(aBmp);
+
+ bool bFastBlend = false;
+ if (!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 (!bFastBlend)
+ {
+ for (int 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(int nX = 0; nX < nDstWidth; nX++)
+ {
+ tools::Long nMapX = pMapX[nX];
+
+ if (bHMirr)
+ nMapX = aBmpRect.Right() - nMapX;
+
+ BitmapColor aDstCol = pB->GetPixelFromData(pBScan, nX);
+ aDstCol.Merge(pP->GetColor(nMapY, nMapX), pAScan[nMapX]);
+ pB->SetPixelOnData(pBScan, nX, aDstCol);
+ }
+ }
+ }
+
+ pB.reset();
+ return aBmp;
+}
+
+/* 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 0000000000..04a50d5545
--- /dev/null
+++ b/vcl/source/outdev/bitmapex.cxx
@@ -0,0 +1,680 @@
+/* -*- 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 ) );
+ }
+
+ 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.GetAlphaMask(), 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.GetBitmap().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.GetAlphaMask().GetBitmap(),
+ rBitmapEx.GetAlphaMask()));
+ }
+ 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();
+ AlphaMask aAlphaBitmap;
+
+ if(rBitmapEx.IsAlpha())
+ {
+ aAlphaBitmap = rBitmapEx.GetAlphaMask();
+ }
+ else if (mpAlphaVDev)
+ {
+ aAlphaBitmap = AlphaMask(rBitmapEx.GetSizePixel());
+ aAlphaBitmap.Erase(0); // opaque
+ }
+
+ SalBitmap* pSalAlphaBmp = aAlphaBitmap.GetBitmap().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.GetBitmap(), 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 nTransparency( static_cast<sal_uInt8>( ::basegfx::fround( 255.0*(1.0 - fAlpha) + .5) ) );
+ AlphaMask aAlpha( bitmapEx.GetSizePixel(), &nTransparency );
+ if( bitmapEx.IsAlpha())
+ aAlpha.BlendWith( bitmapEx.GetAlphaMask());
+ bitmapEx = BitmapEx( bitmapEx.GetBitmap(), aAlpha );
+ }
+
+ // 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 0000000000..6efc9df9d0
--- /dev/null
+++ b/vcl/source/outdev/clipping.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <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;
+ }
+
+ pGraphics->SetClipRegion( rRegion, *this );
+ return true;
+}
+
+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 0000000000..b5a13fb721
--- /dev/null
+++ b/vcl/source/outdev/curvedshapes.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 <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+#include <cassert>
+
+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 0000000000..3f436ee327
--- /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.Normalize();
+ 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 0000000000..c684595fb2
--- /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_ALPHA_OPAQUE );
+}
+
+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 0000000000..2086db7f63
--- /dev/null
+++ b/vcl/source/outdev/font.cxx
@@ -0,0 +1,1293 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <font/EmphasisMark.hxx>
+
+#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 <unicode/uchar.h>
+
+#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_ALPHA_OPAQUE );
+ 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;
+
+ const LanguageTag& rOfficeLanguage = Application::GetSettings().GetUILanguageTag();
+
+ vcl::font::FeatureCollector aFeatureCollector(pFontInstance->GetFontFace(), rFontFeatures, rOfficeLanguage);
+ aFeatureCollector.collect();
+
+ return true;
+}
+
+FontMetric OutputDevice::GetFontMetric() const
+{
+ FontMetric aMetric;
+ if (!ImplNewFont())
+ return aMetric;
+
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ FontMetricDataRef 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->IsMicrosoftSymbolEncoded() ? 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);
+}
+
+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 )
+{
+ static bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ static bool bAbortOnFontSubstitute = [] {
+ const char* pEnv = getenv("SAL_NON_APPLICATION_FONT_USE");
+ return pEnv && strcmp(pEnv, "abort") == 0;
+ }();
+
+ if (!pOutDev && !bFuzzing) // default is NULL
+ pOutDev = Application::GetDefaultDevice();
+
+ OUString aSearch;
+ if (!bFuzzing)
+ {
+ 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
+
+ // during cppunit tests with SAL_NON_APPLICATION_FONT_USE set we don't have any bundled fonts
+ // that support the default CTL and CJK languages of Hindi and Chinese, so just pick something
+ // (unsuitable) that does exist, if they get used with SAL_NON_APPLICATION_FONT_USE=abort then
+ // glyph fallback will trigger std::abort
+ if (bAbortOnFontSubstitute)
+ {
+ if (eLang == LANGUAGE_HINDI || eLang == LANGUAGE_CHINESE_SIMPLIFIED)
+ aSearch = "DejaVu Sans";
+ }
+ }
+ 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 = ImplLogicHeightToDeviceSubPixel(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( this );
+ 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 = maFont.GetEmphasisMarkStyle();
+ 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 = maFont.GetEmphasisMarkStyle();
+ tools::Long nEmphasisHeight;
+
+ if ( nEmphasisMark & FontEmphasisMark::PosBelow )
+ nEmphasisHeight = mnEmphasisDescent;
+ else
+ nEmphasisHeight = mnEmphasisAscent;
+
+ vcl::font::EmphasisMark aEmphasisMark(nEmphasisMark, nEmphasisHeight, GetDPIY());
+
+ if (aEmphasisMark.IsShapePolyLine())
+ {
+ SetLineColor( GetTextColor() );
+ SetFillColor();
+ }
+ else
+ {
+ SetLineColor();
+ SetFillColor( GetTextColor() );
+ }
+
+ Point aOffset(0,0);
+ Point aOffsetVert(0,0);
+
+ if ( nEmphasisMark & FontEmphasisMark::PosBelow )
+ {
+ aOffset.AdjustY(mpFontInstance->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset());
+ aOffsetVert = aOffset;
+ }
+ else
+ {
+ aOffset.AdjustY(-(mpFontInstance->mxFontMetric->GetAscent() + aEmphasisMark.GetYOffset()));
+ // Todo: use ideographic em-box or ideographic character face information.
+ aOffsetVert.AdjustY(-(mpFontInstance->mxFontMetric->GetAscent() +
+ mpFontInstance->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset()));
+ }
+
+ tools::Long nEmphasisWidth2 = aEmphasisMark.GetWidth() / 2;
+ tools::Long nEmphasisHeight2 = nEmphasisHeight / 2;
+ aOffset += Point( nEmphasisWidth2, nEmphasisHeight2 );
+
+ basegfx::B2DPoint 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;
+ if (pGlyph->IsVertical())
+ {
+ aAdjPoint = aOffsetVert;
+ aAdjPoint.AdjustX((-pGlyph->origWidth() + aEmphasisMark.GetWidth()) / 2);
+ }
+ else
+ {
+ aAdjPoint = aOffset;
+ aAdjPoint.AdjustX(aRectangle.Left() + (aRectangle.GetWidth() - aEmphasisMark.GetWidth()) / 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(),
+ aEmphasisMark.GetShape(), aEmphasisMark.IsShapePolyLine(),
+ aEmphasisMark.GetRect1(), aEmphasisMark.GetRect2() );
+ }
+ }
+
+ 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;
+}
+
+bool OutputDevice::ForceFallbackFont(vcl::Font const& rFallbackFont)
+{
+ vcl::Font aOldFont = GetFont();
+ SetFont(rFallbackFont);
+ if (!InitFont())
+ return false;
+
+ mpForcedFallbackInstance = mpFontInstance;
+ SetFont(aOldFont);
+ if (!InitFont())
+ return false;
+
+ if (mpForcedFallbackInstance)
+ return true;
+
+ return false;
+}
+
+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
+ bool bRTL;
+ int nMinRunPos, nEndRunPos;
+ OUStringBuffer aMissingCodeBuf(512);
+ while (rLayoutArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL))
+ aMissingCodeBuf.append(rLayoutArgs.mrStr.subView(nMinRunPos, nEndRunPos - nMinRunPos));
+ rLayoutArgs.ResetPos();
+ OUString aMissingCodes = aMissingCodeBuf.makeStringAndClear();
+
+ vcl::font::FontSelectPattern aFontSelData(mpFontInstance->GetFontSelectPattern());
+ SalLayoutGlyphsImpl* pGlyphsImpl = pGlyphs ? pGlyphs->Impl(1) : nullptr;
+
+ bool bHasUsedFallback = false;
+
+ // try if fallback fonts support the missing code units
+ for( int nFallbackLevel = 1; nFallbackLevel < MAX_FALLBACK; ++nFallbackLevel )
+ {
+ rtl::Reference<LogicalFontInstance> pFallbackFont;
+ if (!bHasUsedFallback && mpForcedFallbackInstance)
+ {
+ pFallbackFont = mpForcedFallbackInstance;
+ bHasUsedFallback = true;
+ }
+ else 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;
+
+ SAL_INFO("vcl", "Fallback font (level " << nFallbackLevel << "): family: " << pFallbackFont->GetFontFace()->GetFamilyName()
+ << ", style: " << pFallbackFont->GetFontFace()->GetStyleName());
+
+ 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_INFO("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;
+
+ auto nKashidaWidth = mpFontInstance->mxFontMetric->GetMinKashida();
+ if (!mbMap)
+ nKashidaWidth = std::ceil(nKashidaWidth);
+
+ return ImplDevicePixelToLogicWidth(nKashidaWidth);
+}
+
+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;
+
+ auto nEnd = nIdx + nLen - 1;
+ sal_Int32 nDropped = 0;
+ for( int i = 0; i < nKashCount; ++i )
+ {
+ auto nPos = pKashidaPos[i];
+ auto nNextPos = nPos + 1;
+
+ // Skip combining marks to find the next character after this position.
+ while (nNextPos <= nEnd &&
+ u_getIntPropertyValue(rTxt[nNextPos], UCHAR_JOINING_TYPE) == U_JT_TRANSPARENT)
+ nNextPos++;
+
+ // The next position is past end of the layout, it would happen if we
+ // changed the text styling in the middle of a word. Since we don’t do
+ // apply OpenType features across different layouts, this can’t be an
+ // invalid place to insert Kashida.
+ if (nNextPos > nEnd)
+ continue;
+
+ if (!pSalLayout->IsKashidaPosValid(nPos, nNextPos))
+ pKashidaPosDropped[nDropped++] = nPos;
+ }
+ 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, std::u16string_view rStr,
+ sal_Int32 nIndex, sal_Int32 nLen ) const
+{
+ if( nIndex >= static_cast<sal_Int32>(rStr.size()) )
+ return nIndex;
+ sal_Int32 nEnd;
+ if( nLen == -1 )
+ nEnd = rStr.size();
+ else
+ nEnd = std::min<sal_Int32>( rStr.size(), nIndex + nLen );
+
+ SAL_WARN_IF( nIndex >= nEnd, "vcl.gdi", "StartPos >= EndPos?" );
+ SAL_WARN_IF( nEnd > static_cast<sal_Int32>(rStr.size()), "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();
+ mpForcedFallbackInstance.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 0000000000..e3803d660d
--- /dev/null
+++ b/vcl/source/outdev/gradient.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 <tools/poly.hxx>
+
+#include <vcl/gradient.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+
+#include <salgdi.hxx>
+
+#include <cassert>
+#include <memory>
+
+#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.Normalize();
+
+ // 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() == css::awt::GradientStyle_LINEAR || rGradient.GetStyle() == css::awt::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_ALPHA_OPAQUE );
+ mpAlphaVDev->DrawPolyPolygon( rPolyPoly );
+ mpAlphaVDev->SetFillColor( aFillCol );
+ }
+}
+
+void OutputDevice::ClipAndDrawGradientMetafile ( const Gradient &rGradient, const tools::PolyPolygon &rPolyPoly )
+{
+ const bool bOldOutput = IsOutputEnabled();
+ EnableOutput( false );
+
+ Push( vcl::PushFlags::CLIPREGION );
+ SetClipRegion( vcl::Region( rPolyPoly ) );
+ DrawGradient( rPolyPoly.GetBoundRect(), 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, std::move(aGradient) ) );
+ }
+ else
+ {
+ mpMetaFile->AddAction( new MetaCommentAction( "XGRAD_SEQ_BEGIN"_ostr ) );
+ mpMetaFile->AddAction( new MetaGradientExAction( rPolyPoly, rGradient ) );
+
+ ClipAndDrawGradientMetafile ( rGradient, rPolyPoly );
+
+ mpMetaFile->AddAction( new MetaCommentAction( "XGRAD_SEQ_END"_ostr ) );
+ }
+}
+
+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() == css::awt::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)
+ {
+ std::swap( nStartRed, nEndRed );
+ std::swap( nStartGreen, nEndGreen );
+ std::swap( nStartBlue, nEndBlue );
+ }
+
+ 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() != css::awt::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 = tools::Polygon(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 = tools::Polygon(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() == css::awt::GradientStyle_RADIAL || rGradient.GetStyle() == css::awt::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() == css::awt::GradientStyle_LINEAR || rGradient.GetStyle() == css::awt::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 )
+ {
+ if (mnDrawMode & DrawModeFlags::SettingsForSelection)
+ aColor = GetSettings().GetStyleSettings().GetHighlightColor();
+ else
+ 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 0000000000..0fc755864a
--- /dev/null
+++ b/vcl/source/outdev/hatch.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 <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/virdev.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+
+#include <cassert>
+#include <cstdlib>
+#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( (static_cast<double>(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() - ( (static_cast<double>(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() + ( (static_cast<double>(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 0000000000..6bf59455da
--- /dev/null
+++ b/vcl/source/outdev/line.cxx
@@ -0,0 +1,370 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <unotools/configmgr.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_ALPHA_OPAQUE );
+}
+
+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 (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 )
+{
+ static const bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ const bool bTryB2d(RasterOp::OverPaint == GetRasterOp() && IsLineColor());
+ basegfx::B2DPolyPolygon aFillPolyPolygon;
+ const bool bDashUsed(LineStyle::Dash == rInfo.GetStyle());
+ const bool bLineWidthUsed(rInfo.GetWidth() > 1);
+
+ if (!bFuzzing && 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.
+ static int nRecurseLimit = utl::ConfigManager::IsFuzzing() ? 10 : 30;
+ aLinePolyPolygon = basegfx::utils::adaptiveSubdivideByDistance(aLinePolyPolygon, 1.0, nRecurseLimit);
+ }
+
+ 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 (bFuzzing)
+ {
+ const basegfx::B2DRange aRange(basegfx::utils::getRange(aFillPolyPolygon));
+ if (aRange.getMaxX() - aRange.getMinX() > 0x10000000
+ || aRange.getMaxY() - aRange.getMinY() > 0x10000000)
+ {
+ SAL_WARN("vcl.gdi", "drawLine, skipping suspicious range of: "
+ << aRange << " for fuzzing performance");
+ bDone = true;
+ }
+ }
+
+ if (bTryB2d && !bDone)
+ {
+ mpGraphics->DrawPolyPolygon(
+ basegfx::B2DHomMatrix(),
+ aFillPolyPolygon,
+ 0.0,
+ *this);
+ bDone = true;
+ }
+
+ 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 0000000000..23c68a2385
--- /dev/null
+++ b/vcl/source/outdev/map.cxx
@@ -0,0 +1,1868 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <tools/bigint.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/cursor.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/wrkwin.hxx>
+
+#include <ImplOutDevData.hxx>
+#include <svdata.hxx>
+#include <window.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <tools/UnitConversion.hxx>
+
+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 funcCalcOffset = [](const Fraction& rScale, tools::Long& rnMapOffset, tools::Long nOrigin)
+ {
+ auto nNumerator = rScale.GetNumerator();
+ assert(nNumerator != 0);
+
+ BigInt aX( rnMapOffset );
+ aX *= BigInt( rScale.GetDenominator() );
+ if ( rnMapOffset >= 0 )
+ {
+ if (nNumerator >= 0)
+ aX += BigInt(nNumerator / 2);
+ else
+ aX -= BigInt((nNumerator + 1) / 2);
+ }
+ else
+ {
+ if (nNumerator >= 0 )
+ aX -= BigInt((nNumerator - 1) / 2);
+ else
+ aX += BigInt(nNumerator / 2);
+ }
+ aX /= BigInt(nNumerator);
+ rnMapOffset = static_cast<tools::Long>(aX) + nOrigin;
+ };
+
+ funcCalcOffset(aScaleX, rMapRes.mnMapOfsX, aOrigin.X());
+ funcCalcOffset(aScaleY, rMapRes.mnMapOfsY, aOrigin.Y());
+ }
+
+ // calculate scaling factor according to MapMode
+ // aTemp? = rMapRes.mnMapSc? * aScale?
+ Fraction aTempX = Fraction::MakeFraction( rMapRes.mnMapScNumX,
+ aScaleX.GetNumerator(),
+ rMapRes.mnMapScDenomX,
+ aScaleX.GetDenominator() );
+ Fraction aTempY = Fraction::MakeFraction( 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 ImplLogicToSubPixel(tools::Long n, tools::Long nDPI, tools::Long nMapNum,
+ tools::Long nMapDenom)
+{
+ assert(nDPI > 0);
+ assert(nMapDenom != 0);
+ return static_cast<double>(n) * nMapNum * nDPI / nMapDenom;
+}
+
+static tools::Long ImplSubPixelToLogic(double n, tools::Long nDPI, tools::Long nMapNum,
+ tools::Long nMapDenom)
+{
+ assert(nDPI > 0);
+ assert(nMapNum != 0);
+
+ return std::round(n * nMapDenom / nMapNum / nDPI);
+}
+
+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);
+}
+
+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)
+ {
+ maMapMode.SetScaleX(Fraction::MakeFraction(
+ maMapMode.GetScaleX().GetNumerator(), rNewMapMode.GetScaleX().GetNumerator(),
+ maMapMode.GetScaleX().GetDenominator(), rNewMapMode.GetScaleX().GetDenominator()));
+
+ maMapMode.SetScaleY(Fraction::MakeFraction(
+ maMapMode.GetScaleY().GetNumerator(), rNewMapMode.GetScaleY().GetNumerator(),
+ maMapMode.GetScaleY().GetDenominator(), rNewMapMode.GetScaleY().GetDenominator()));
+
+ maMapMode.SetOrigin(Point(maMapRes.mnMapOfsX, maMapRes.mnMapOfsY));
+ }
+ 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 = Fraction::MakeFraction( rNewMapMode.GetScaleX().GetNumerator(),
+ maMapMode.GetScaleX().GetDenominator(),
+ rNewMapMode.GetScaleX().GetDenominator(),
+ maMapMode.GetScaleX().GetNumerator() );
+ Fraction aYF = Fraction::MakeFraction( 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 = Fraction::MakeFraction( aXF.GetNumerator(), aF.GetNumerator(),
+ aXF.GetDenominator(), aF.GetDenominator() );
+ aYF = Fraction::MakeFraction( 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 );
+}
+
+Point OutputDevice::SubPixelToLogic(const basegfx::B2DPoint& rDevicePt) const
+{
+ if (!mbMap)
+ {
+ assert(floor(rDevicePt.getX() == rDevicePt.getX()) && floor(rDevicePt.getY() == rDevicePt.getY()));
+ return Point(rDevicePt.getX(), rDevicePt.getY());
+ }
+
+ return Point(ImplSubPixelToLogic(rDevicePt.getX(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX) - maMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplSubPixelToLogic(rDevicePt.getY(), 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);
+}
+
+std::pair<ImplMapRes, ImplMapRes> ENTER4(const MapMode& rMMSource, const MapMode& rMMDest)
+{
+ std::pair<ImplMapRes, ImplMapRes> result;
+ ImplCalcMapResolution(rMMSource, 72, 72, result.first);
+ ImplCalcMapResolution(rMMDest, 72, 72, result.second);
+ return result;
+}
+}
+
+// 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
+ {
+ const auto& [aMapResSource, aMapResDest] = 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
+ {
+ const auto& [aMapResSource, aMapResDest] = 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
+ {
+ const auto& [aMapResSource, aMapResDest] = 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
+ {
+ const auto& [aMapResSource, aMapResDest] = 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 );
+}
+
+
+double OutputDevice::ImplLogicWidthToDeviceSubPixel(tools::Long nWidth) const
+{
+ if (!mbMap)
+ return nWidth;
+
+ return ImplLogicToSubPixel(nWidth, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX);
+}
+
+double OutputDevice::ImplLogicHeightToDeviceSubPixel(tools::Long nHeight) const
+{
+ if (!mbMap)
+ return nHeight;
+
+ return ImplLogicToSubPixel(nHeight, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY);
+}
+
+basegfx::B2DPoint OutputDevice::ImplLogicToDeviceSubPixel(const Point& rPoint) const
+{
+ if (!mbMap)
+ return basegfx::B2DPoint(rPoint.X() + mnOutOffX, rPoint.Y() + mnOutOffY);
+
+ return basegfx::B2DPoint(ImplLogicToSubPixel(rPoint.X() + maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX)
+ + mnOutOffX + mnOutOffOrigX,
+ ImplLogicToSubPixel(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 0000000000..004b248785
--- /dev/null
+++ b/vcl/source/outdev/mask.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <salgdi.hxx>
+#include <salbmp.hxx>
+
+#include <cassert>
+
+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 0000000000..1b035c72bd
--- /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 <sal/log.hxx>
+
+#include <vcl/pdfextoutdevdata.hxx>
+#include <vcl/salnativewidgets.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+
+#include <salgdi.hxx>
+#include <toolbarvalue.hxx>
+#include <menubarvalue.hxx>
+
+#include <cassert>
+
+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 0000000000..828a121cca
--- /dev/null
+++ b/vcl/source/outdev/outdev.cxx
@@ -0,0 +1,822 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/processfactory.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/graph.hxx>
+#include <vcl/lazydelete.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/toolkit/unowrap.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/virdev.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 ),
+ moSettings( Application::GetSettings() )
+{
+ mpGraphics = nullptr;
+ mpUnoGraphicsList = nullptr;
+ mpPrevGraphics = nullptr;
+ mpNextGraphics = nullptr;
+ mpMetaFile = nullptr;
+ mpFontInstance = nullptr;
+ mpForcedFallbackInstance = 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?
+ 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();
+ mpForcedFallbackInstance.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 )
+{
+ *moSettings = 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::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::MapMM));
+ aInfo.PixelPerMeterX = aTmpSz.Width();
+ aInfo.PixelPerMeterY = aTmpSz.Height();
+ 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 0000000000..8c97aeb432
--- /dev/null
+++ b/vcl/source/outdev/pixel.cxx
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <drawmode.hxx>
+#include <salgdi.hxx>
+
+#include <cassert>
+
+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(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(rColor.GetAlpha(), rColor.GetAlpha(), 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 0000000000..e764e6b66d
--- /dev/null
+++ b/vcl/source/outdev/polygon.cxx
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <basegfx/matrix/b2dhommatrix.hxx>
+#include <tools/poly.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+#include <cassert>
+#include <memory>
+
+#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 (RasterOp::OverPaint == GetRasterOp() && (IsLineColor() || IsFillColor()))
+ {
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPoly.getB2DPolyPolygon());
+
+ // ensure closed - may be asserted, will prevent buffering
+ if(!aB2DPolyPolygon.isClosed())
+ {
+ aB2DPolyPolygon.setClosed(true);
+ }
+
+ if (IsFillColor())
+ {
+ mpGraphics->DrawPolyPolygon(
+ aTransform,
+ aB2DPolyPolygon,
+ 0.0,
+ *this);
+ }
+
+ bool bSuccess(true);
+ if (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 (RasterOp::OverPaint == GetRasterOp() && (IsLineColor() || IsFillColor()))
+ {
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ basegfx::B2DPolygon aB2DPolygon(rPoly.getB2DPolygon());
+
+ // ensure closed - maybe assert, hinders buffering
+ if(!aB2DPolygon.isClosed())
+ {
+ aB2DPolygon.setClosed(true);
+ }
+
+ if (IsFillColor())
+ {
+ mpGraphics->DrawPolyPolygon(
+ aTransform,
+ basegfx::B2DPolyPolygon(aB2DPolygon),
+ 0.0,
+ *this);
+ }
+
+ bool bSuccess(true);
+ if (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 (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())
+ {
+ mpGraphics->DrawPolyPolygon(
+ aTransform,
+ aB2DPolyPolygon,
+ 0.0,
+ *this);
+ }
+
+ if (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( tools::PolyPolygon(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 0000000000..75e8252f4a
--- /dev/null
+++ b/vcl/source/outdev/polyline.cxx
@@ -0,0 +1,404 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+#include <cassert>
+
+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);
+
+ tools::Polygon aToolsPolygon( rB2DPolygon );
+ mpMetaFile->AddAction( new MetaPolyLineAction( std::move(aToolsPolygon), std::move(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
+ tools::Polygon aToolsPolygon( rB2DPolygon );
+ mpMetaFile->AddAction( new MetaPolyLineAction( std::move(aToolsPolygon), std::move(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(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 0000000000..63ec16c62c
--- /dev/null
+++ b/vcl/source/outdev/rect.cxx
@@ -0,0 +1,438 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <tools/poly.hxx>
+#include <tools/helpers.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+#include <cassert>
+
+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.Normalize();
+
+ 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.Normalize();
+
+ // 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 0000000000..129348051e
--- /dev/null
+++ b/vcl/source/outdev/stack.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 <sal/config.h>
+#include <sal/log.hxx>
+#include <tools/debug.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 (nFlags & vcl::PushFlags::RTLENABLED)
+ rState.mbRTLEnabled = IsRTLEnabled();
+
+ 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();
+ }
+
+ if ( rState.mnFlags & vcl::PushFlags::RTLENABLED )
+ EnableRTL( rState.mbRTLEnabled );
+
+ 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 0000000000..1b40a1b2de
--- /dev/null
+++ b/vcl/source/outdev/text.cxx
@@ -0,0 +1,2106 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <basegfx/matrix/b2dhommatrix.hxx>
+#include <tools/lineend.hxx>
+#include <tools/debug.hxx>
+#include <unotools/configmgr.hxx>
+
+#include <vcl/ctrl.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/textrectinfo.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/sysdata.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 <TextLayoutCache.hxx>
+#include <font/PhysicalFontFace.hxx>
+
+#include <memory>
+#include <optional>
+
+#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 );
+}
+
+void OutputDevice::ImplInitTextColor()
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( mbInitTextColor )
+ {
+ mpGraphics->SetTextColor( GetTextColor() );
+ mbInitTextColor = false;
+ }
+}
+
+OUString OutputDevice::GetEllipsisString( const OUString& rOrigStr, tools::Long nMaxWidth,
+ DrawTextFlags nStyle ) const
+{
+ vcl::DefaultTextLayout aTextLayout(*const_cast< OutputDevice* >(this));
+ return aTextLayout.GetEllipsisString(rOrigStr, nMaxWidth, nStyle);
+}
+
+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 double nWidth = rSalLayout.GetTextWidth();
+ const basegfx::B2DPoint 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
+{
+ basegfx::B2DPoint aPoint = rSalLayout.GetDrawPosition();
+ tools::Long nX = aPoint.getX();
+ tools::Long nY = aPoint.getY();
+
+ double 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() = basegfx::B2DPoint( 0, 0 );
+ rSalLayout.DrawOffset() = Point( 0, 0 );
+ if (!rSalLayout.GetBoundRect(aBoundRect))
+ {
+ // guess vertical text extents if GetBoundRect failed
+ double 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();
+
+ basegfx::B2DPoint 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() += basegfx::B2DPoint( nOff, nOff );
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() -= basegfx::B2DPoint( nOff, nOff );
+ SetTextColor( aOldColor );
+ SetTextLineColor( aOldTextLineColor );
+ SetOverlineColor( aOldOverlineColor );
+ ImplInitTextColor();
+
+ if ( !maFont.IsOutline() )
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ }
+
+ if ( maFont.IsOutline() )
+ {
+ rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(-1,-1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+1,+1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(-1,+0);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(-1,+1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+0,+1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+0,-1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+1,-1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+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() += basegfx::B2DPoint(mnTextOffX, mnTextOffY);
+
+ if( IsTextFillColor() )
+ ImplDrawTextBackground( rSalLayout );
+
+ if( mbTextSpecial )
+ ImplDrawSpecialText( rSalLayout );
+ else
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+}
+
+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_ALPHA_OPAQUE );
+}
+
+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_ALPHA_OPAQUE );
+}
+
+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, false, 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,
+ KernArraySpan pDXAry,
+ std::span<const sal_Bool> pKashidaAry,
+ 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, pKashidaAry, 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, pKashidaAry, flags, nullptr, pSalLayoutCache);
+ if( pSalLayout )
+ {
+ ImplDrawText( *pSalLayout );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawTextArray( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen, flags );
+}
+
+tools::Long OutputDevice::GetTextArray( const OUString& rStr, KernArray* pKernArray,
+ sal_Int32 nIndex, sal_Int32 nLen, bool bCaret,
+ 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;
+ }
+
+ std::vector<sal_Int32>* pDXAry = pKernArray ? &pKernArray->get_subunit_array() : nullptr;
+
+ // 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)
+ {
+ pDXAry->resize(nLen);
+ std::fill(pDXAry->begin(), pDXAry->end(), 0);
+ }
+ return 0;
+ }
+
+ std::unique_ptr<std::vector<double>> xDXPixelArray;
+ if(pDXAry)
+ {
+ xDXPixelArray.reset(new std::vector<double>(nLen));
+ }
+ std::vector<double>* pDXPixelArray = xDXPixelArray.get();
+ double nWidth = pSalLayout->FillDXArray(pDXPixelArray, bCaret ? rStr : OUString());
+
+ // convert virtual char widths to virtual absolute positions
+ if( pDXPixelArray )
+ {
+ for( int i = 1; i < nLen; ++i )
+ {
+ (*pDXPixelArray)[i] += (*pDXPixelArray)[i - 1];
+ }
+ }
+
+ // convert from font units to logical units
+ if (pDXPixelArray)
+ {
+ int nSubPixelFactor = pKernArray->get_factor();
+ if (mbMap)
+ {
+ for (int i = 0; i < nLen; ++i)
+ (*pDXPixelArray)[i] = ImplDevicePixelToLogicWidth((*pDXPixelArray)[i] * nSubPixelFactor);
+ }
+ else if (nSubPixelFactor)
+ {
+ for (int i = 0; i < nLen; ++i)
+ (*pDXPixelArray)[i] *= nSubPixelFactor;
+ }
+ }
+
+ if (pDXAry)
+ {
+ pDXAry->resize(nLen);
+ for (int i = 0; i < nLen; ++i)
+ (*pDXAry)[i] = basegfx::fround((*pDXPixelArray)[i]);
+ }
+
+ if (mbMap)
+ nWidth = ImplDevicePixelToLogicWidth( nWidth );
+
+ return basegfx::fround(nWidth);
+}
+
+void OutputDevice::GetCaretPositions( const OUString& rStr, KernArray& rCaretXArray,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ const SalLayoutGlyphs* pGlyphs ) const
+{
+
+ if( nIndex >= rStr.getLength() )
+ return;
+ if( nIndex+nLen >= rStr.getLength() )
+ nLen = rStr.getLength() - nIndex;
+
+ sal_Int32 nCaretPos = nLen * 2;
+ std::vector<sal_Int32>& rCaretPos = rCaretXArray.get_subunit_array();
+ rCaretPos.resize(nCaretPos);
+
+ // do layout
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, Point(0, 0), 0, {}, {},
+ eDefaultLayout, nullptr, pGlyphs);
+ if( !pSalLayout )
+ {
+ std::fill(rCaretPos.begin(), rCaretPos.end(), -1);
+ return;
+ }
+
+ std::vector<double> aCaretPixelPos;
+ pSalLayout->GetCaretPositions(aCaretPixelPos, rStr);
+
+ // fixup unknown caret positions
+ int i;
+ for (i = 0; i < nCaretPos; ++i)
+ if (aCaretPixelPos[i] >= 0)
+ break;
+ tools::Long nXPos = (i < nCaretPos) ? aCaretPixelPos[i] : -1;
+ for (i = 0; i < nCaretPos; ++i)
+ {
+ if (aCaretPixelPos[i] >= 0)
+ nXPos = aCaretPixelPos[i];
+ else
+ aCaretPixelPos[i] = nXPos;
+ }
+
+ // handle window mirroring
+ if( IsRTLEnabled() )
+ {
+ double nWidth = pSalLayout->GetTextWidth();
+ for (i = 0; i < nCaretPos; ++i)
+ aCaretPixelPos[i] = nWidth - aCaretPixelPos[i] - 1;
+ }
+
+ int nSubPixelFactor = rCaretXArray.get_factor();
+ // convert from font units to logical units
+ if( mbMap )
+ {
+ for (i = 0; i < nCaretPos; ++i)
+ aCaretPixelPos[i] = ImplDevicePixelToLogicWidth(aCaretPixelPos[i] * nSubPixelFactor);
+ }
+ else if (nSubPixelFactor)
+ {
+ for (i = 0; i < nCaretPos; ++i)
+ aCaretPixelPos[i] *= nSubPixelFactor;
+ }
+
+ for (i = 0; i < nCaretPos; ++i)
+ rCaretPos[i] = basegfx::fround(aCaretPixelPos[i]);
+}
+
+void OutputDevice::DrawStretchText( const Point& rStartPt, sal_Int32 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,
+ double 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( maFont.IsFixKerning() ||
+ ( mpFontInstance && mpFontInstance->GetFontSelectPattern().GetPitch() == PITCH_FIXED ) )
+ nLayoutFlags |= SalLayoutFlags::DisableLigatures;
+
+ 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,
+ KernArraySpan pDXArray,
+ std::span<const sal_Bool> pKashidaArray,
+ 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;
+
+ // 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;
+ }
+
+ double nPixelWidth = nLogicalWidth;
+ if( nLogicalWidth && mbMap )
+ {
+ // convert from logical units to physical units
+ nPixelWidth = ImplLogicWidthToDeviceSubPixel(nLogicalWidth);
+ }
+
+ vcl::text::ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nMinIndex, nLen,
+ nPixelWidth, flags, pLayoutCache);
+
+ double nEndGlyphCoord(0);
+ std::unique_ptr<double[]> xDXPixelArray;
+ if( !pDXArray.empty() )
+ {
+ xDXPixelArray.reset(new double[nLen]);
+
+ if (mbMap)
+ {
+ // convert from logical units to font units without rounding,
+ // keeping accuracy for lower levels
+ int nSubPixels = pDXArray.get_factor();
+ for (int i = 0; i < nLen; ++i)
+ xDXPixelArray[i] = ImplLogicWidthToDeviceSubPixel(pDXArray.get_subunit(i)) / nSubPixels;
+ nEndGlyphCoord = xDXPixelArray[nLen - 1];
+ }
+ else
+ {
+ for(int i = 0; i < nLen; ++i)
+ xDXPixelArray[i] = pDXArray.get(i);
+ nEndGlyphCoord = std::lround(xDXPixelArray[nLen - 1]);
+ }
+
+ aLayoutArgs.SetDXArray(xDXPixelArray.get());
+ }
+
+ if (!pKashidaArray.empty())
+ aLayoutArgs.SetKashidaArray(pKashidaArray.data());
+
+ // get matching layout object for base font
+ std::unique_ptr<SalLayout> pSalLayout = mpGraphics->GetTextLayout(0);
+
+ if (pSalLayout)
+ pSalLayout->SetSubpixelPositioning(mbMap);
+
+ // layout text
+ if( pSalLayout && !pSalLayout->LayoutText( aLayoutArgs, pGlyphs ? pGlyphs->Impl(0) : nullptr ) )
+ {
+ pSalLayout.reset();
+ }
+
+ if( !pSalLayout )
+ return nullptr;
+
+ // 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 );
+
+ // default to on for pdf export, which uses SubPixelToLogic to convert back to
+ // the logical coord space, of if we are scaling/mapping
+ if (mbMap || meOutDevType == OUTDEV_PDF)
+ pSalLayout->DrawBase() = ImplLogicToDeviceSubPixel(rLogicalPos);
+ else
+ {
+ Point aDevicePos = ImplLogicToDevicePixel(rLogicalPos);
+ pSalLayout->DrawBase() = basegfx::B2DPoint(aDevicePos.X(), aDevicePos.Y());
+ }
+
+ // adjust to right alignment if necessary
+ if( aLayoutArgs.mnFlags & SalLayoutFlags::RightAlign )
+ {
+ double 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 = 1;
+ if (!mbMap)
+ nSubPixelFactor = 64;
+ double nTextPixelWidth = ImplLogicWidthToDeviceSubPixel(nTextWidth * nSubPixelFactor);
+ double nExtraPixelWidth = 0;
+ if( nCharExtra != 0 )
+ nExtraPixelWidth = ImplLogicWidthToDeviceSubPixel(nCharExtra * nSubPixelFactor);
+ 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 = 1;
+ if (!mbMap)
+ nSubPixelFactor = 64;
+
+ double nTextPixelWidth = ImplLogicWidthToDeviceSubPixel(nTextWidth * nSubPixelFactor);
+ double nExtraPixelWidth = 0;
+ if( nCharExtra != 0 )
+ nExtraPixelWidth = ImplLogicWidthToDeviceSubPixel(nCharExtra * nSubPixelFactor);
+
+ // 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
+ double 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::TextLayoutCommon& _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)
+ {
+ if (nStyle & DrawTextFlags::Clip)
+ return;
+ static bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ SAL_WARN_IF(bFuzzing, "vcl", "skipping negative rectangle of: " << nWidth << " x " << nHeight);
+ if (bFuzzing)
+ 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 = removeMnemonicFromString( 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 = _rLayout.GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle);
+ 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 = _rLayout.GetEllipsisString(aLastLine, nWidth, nStyle);
+ 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;
+
+ KernArray aDXArray;
+ _rLayout.GetTextArray(aStr, &aDXArray, nIndex, nLineLen, true);
+ sal_Int32 nPos = nMnemonicPos - nIndex;
+ sal_Int32 lc_x1 = nPos ? aDXArray[nPos - 1] : 0;
+ sal_Int32 lc_x2 = aDXArray[nPos];
+ double nMnemonicWidth = rTargetDevice.ImplLogicWidthToDeviceSubPixel(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 = _rLayout.GetEllipsisString(aStr, nWidth, nStyle);
+ 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;
+ double nMnemonicWidth = 0;
+ if (nMnemonicPos != -1 && nMnemonicPos < aStr.getLength())
+ {
+ KernArray aDXArray;
+ _rLayout.GetTextArray(aStr, &aDXArray, 0, aStr.getLength(), true);
+ tools::Long lc_x1 = nMnemonicPos? aDXArray[nMnemonicPos - 1] : 0;
+ tools::Long lc_x2 = aDXArray[nMnemonicPos];
+ nMnemonicWidth = rTargetDevice.ImplLogicWidthToDeviceSubPixel(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::TextLayoutCommon* _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::TextLayoutCommon* _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 = removeMnemonicFromString( aStr );
+
+ if ( nStyle & DrawTextFlags::MultiLine )
+ {
+ ImplMultiTextLineInfo aMultiLineInfo;
+ sal_Int32 nFormatLines;
+ sal_Int32 i;
+
+ nMaxWidth = 0;
+ vcl::DefaultTextLayout aDefaultLayout( *const_cast< OutputDevice* >( this ) );
+
+ if (_pTextLayout)
+ const_cast<vcl::TextLayoutCommon*>(_pTextLayout)->GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle);
+ else
+ aDefaultLayout.GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle);
+
+ 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 );
+
+ if (maFont.GetOrientation() != 0_deg10)
+ {
+ tools::Polygon aRotatedPolygon(aRect);
+ aRotatedPolygon.Rotate(Point(aRect.GetWidth() / 2, aRect.GetHeight() / 2), maFont.GetOrientation());
+ return aRotatedPolygon.GetBoundRect();
+ }
+
+ return aRect;
+}
+
+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 = removeMnemonicFromString( 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;
+ }
+
+ KernArray aDXArray;
+ GetTextArray(aStr, &aDXArray, nIndex, nLen, true, nullptr, pGlyphs);
+ sal_Int32 nPos = nMnemonicPos - nIndex;
+ sal_Int32 lc_x1 = nPos ? aDXArray[nPos - 1] : 0;
+ sal_Int32 lc_x2 = aDXArray[nPos];
+ nMnemonicWidth = std::abs(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();
+ }
+ }
+
+ 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))
+ {
+ 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 )
+ {
+ 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 = removeMnemonicFromString( 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 );
+}
+
+bool OutputDevice::GetTextBoundRect( tools::Rectangle& rRect,
+ const OUString& rStr, sal_Int32 nBase,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ sal_uLong nLayoutWidth, KernArraySpan pDXAry,
+ std::span<const sal_Bool> pKashidaAry,
+ const SalLayoutGlyphs* pGlyphs ) const
+{
+ bool bRet = false;
+ rRect.SetEmpty();
+
+ std::unique_ptr<SalLayout> pSalLayout;
+ const Point aPoint;
+ // calculate offset when nBase!=nIndex
+ double 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, pKashidaAry );
+ 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, pKashidaAry, eDefaultLayout,
+ nullptr, pGlyphs);
+ if( pSalLayout )
+ {
+ tools::Rectangle aPixelRect;
+ bRet = pSalLayout->GetBoundRect(aPixelRect);
+
+ if( bRet )
+ {
+ Point aRotatedOfs( mnTextOffX, mnTextOffY );
+ basegfx::B2DPoint aPos = pSalLayout->GetDrawPosition(basegfx::B2DPoint(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,
+ KernArraySpan pDXArray,
+ std::span<const sal_Bool> pKashidaArray ) 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
+ double 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, pKashidaArray);
+ 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, pKashidaArray );
+ if( pSalLayout )
+ {
+ bRet = pSalLayout->GetOutline(rVector);
+ if( bRet )
+ {
+ // transform polygon to pixel units
+ basegfx::B2DHomMatrix aMatrix;
+
+ if (nXOffset || mnTextOffX || mnTextOffY)
+ {
+ basegfx::B2DPoint aRotatedOfs(mnTextOffX, mnTextOffY);
+ aRotatedOfs -= pSalLayout->GetDrawPosition(basegfx::B2DPoint(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, KernArraySpan pDXArray,
+ std::span<const sal_Bool> pKashidaArray ) const
+{
+ rResultVector.clear();
+
+ // get the basegfx polypolygon vector
+ basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
+ if( !GetTextOutlines( aB2DPolyPolyVector, rStr, nBase, nIndex, nLen,
+ nLayoutWidth, pDXArray, pKashidaArray ) )
+ 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 0000000000..84965e87b5
--- /dev/null
+++ b/vcl/source/outdev/textline.cxx
@@ -0,0 +1,1126 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/WaveLine.hxx>
+#include <tools/helpers.hxx>
+#include <o3tl/hash_combine.hxx>
+#include <o3tl/lru_map.hxx>
+#include <unotools/configmgr.hxx>
+#include <vcl/lazydelete.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+#include <impglyphitem.hxx>
+
+#include <cassert>
+
+#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( this );
+}
+
+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 )
+{
+ static bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ if (bFuzzing && nWidth > 10000)
+ {
+ SAL_WARN("vcl.gdi", "drawLine, skipping suspicious WaveTextLine of length: "
+ << nWidth << " for fuzzing performance");
+ return;
+ }
+
+ 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 )
+{
+ static bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ if (bFuzzing && nWidth > 100000)
+ {
+ SAL_WARN("vcl.gdi", "drawLine, skipping suspicious TextLine of length: "
+ << nWidth << " for fuzzing performance");
+ return;
+ }
+
+ 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() = basegfx::B2DPoint(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, double 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 basegfx::B2DPoint aStartPt = rSalLayout.DrawBase();
+
+ // calculate distance of each word from the base point
+ basegfx::B2DPoint aPos;
+ double nDist = 0;
+ double 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 double 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
+ {
+ basegfx::B2DPoint 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() )
+ {
+ // 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_ALPHA_OPAQUE );
+}
+
+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_ALPHA_OPAQUE );
+}
+
+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 );
+ double fWidth = ImplLogicWidthToDeviceSubPixel(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::WITH_ALPHA );
+ 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.GetAlphaMask());
+
+ 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 0000000000..7588dec556
--- /dev/null
+++ b/vcl/source/outdev/transparent.cxx
@@ -0,0 +1,1935 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/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 (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;
+
+ if (IsFillColor())
+ {
+ mpGraphics->DrawPolyPolygon(
+ aFullTransform,
+ aB2DPolyPolygon,
+ fAdjustedTransparency,
+ *this);
+ }
+
+ if (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( 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 (true
+#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 true;
+
+ 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)
+ mpGraphics->DrawPolyPolygon(
+ aTransform,
+ aB2DPolyPolygon,
+ fTransparency,
+ *this);
+ bDrawn = true;
+ }
+
+ 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.getOpenWidth(), aPixelRect.getOpenHeight(),
+ 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 = FRound( std::clamp( nTransparencePercent * 2.55, 0.0, 255.0 ) );
+
+ 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);
+ BitmapScopedReadAccess 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() );
+ sal_uInt8 nAlpha = 255 - sal::static_int_cast<sal_uInt8>(255*nTransparencePercent/100);
+ mpAlphaVDev->SetFillColor( Color(nAlpha, nAlpha, nAlpha) );
+
+ mpAlphaVDev->DrawTransparent( rPolyPoly, nTransparencePercent );
+
+ mpAlphaVDev->SetFillColor( aFillCol );
+ }
+}
+
+void OutputDevice::DrawTransparent( const GDIMetaFile& rMtf, const Point& rPos,
+ const Size& rSize, const Gradient& rTransparenceGradient )
+{
+ DrawTransparent( rMtf, rPos, rSize, rPos, rSize, rTransparenceGradient );
+}
+
+void OutputDevice::DrawTransparent( const GDIMetaFile& rMtf, const Point& rPos, const Size& rSize,
+ const Point& rMtfPos, const Size& rMtfSize,
+ 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, rMtfPos, rMtfSize);
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ }
+ else
+ {
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ tools::Rectangle aOutRect( LogicToPixel( tools::Rectangle(rPos, 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::WITH_ALPHA);
+
+ xVDev->mnDPIX = mnDPIX;
+ xVDev->mnDPIY = mnDPIY;
+
+ if( xVDev->SetOutputSizePixel( aDstRect.GetSize(), true, true ) )
+ {
+ // tdf#150610 fix broken rendering of text meta actions
+ // Even when drawing to a VirtualDevice that has antialiasing
+ // disabled, text will still be drawn with some antialiased
+ // pixels on HiDPI displays. So, use the antialiasing enabled
+ // code to render if there are any text meta actions in the
+ // metafile.
+ if(GetAntialiasing() != AntialiasingFlags::NONE || rPos != rMtfPos || rSize != rMtfSize)
+ {
+ // #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, rMtfPos, rMtfSize);
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+
+ // get content bitmap from buffer
+ xVDev->EnableMapMode(false);
+
+ const BitmapEx aPaint(xVDev->GetBitmapEx(aPoint, xVDev->GetOutputSizePixel()));
+
+ // create alpha mask from gradient and get as Bitmap
+ xVDev->EnableMapMode(bBufferMapModeEnabled);
+ xVDev->SetDrawMode(DrawModeFlags::GrayGradient);
+ // Related tdf#150610 draw gradient to VirtualDevice bounds
+ // If we are here and the metafile bounds differs from the
+ // VirtualDevice bounds so that we apply the transparency
+ // gradient to any pixels drawn outside of the metafile
+ // bounds.
+ xVDev->DrawGradient(tools::Rectangle(rPos, rSize), rTransparenceGradient);
+ xVDev->SetDrawMode(DrawModeFlags::Default);
+ xVDev->EnableMapMode(false);
+
+ AlphaMask aAlpha(xVDev->GetBitmap(aPoint, xVDev->GetOutputSizePixel()));
+ AlphaMask aPaintAlpha(aPaint.GetAlphaMask());
+ // The alpha mask is inverted from what
+ // is expected so invert it again
+ aAlpha.Invert(); // convert to alpha
+ aAlpha.BlendWith(aPaintAlpha);
+
+ xVDev.disposeAndClear();
+
+ // draw masked content to target and restore MapMode
+ DrawBitmapEx(aDstRect.TopLeft(), BitmapEx(aPaint.GetBitmap(), 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, rMtfPos, rMtfSize);
+ 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( rMtfPos, rMtfSize ), rTransparenceGradient );
+ xVDev->SetDrawMode( DrawModeFlags::Default );
+ xVDev->EnableMapMode( false );
+
+ AlphaMask aAlpha(xVDev->GetBitmap(Point(), xVDev->GetOutputSizePixel()));
+ AlphaMask aPaintAlpha(aPaint.GetAlphaMask());
+ // The alpha mask is inverted from what
+ // is expected so invert it again
+ aAlpha.Invert(); // convert to alpha
+ aAlpha.BlendWith(aPaintAlpha);
+
+ 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.GetAlphaMask(), 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.Normalize();
+ 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(), rTextAct.GetKashidaArray() );
+ 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"_ostr ) );
+ rOutMtf.AddAction( new MetaBmpScaleAction( aDstPtPix, aDstSzPix, aBandBmp ) );
+ rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_END"_ostr ) );
+
+ 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 0000000000..62dd0e67f1
--- /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 0000000000..2ae7cb2e5a
--- /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.Normalize();
+
+ 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.GetAlphaMask() );
+ }
+ 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.Normalize();
+ 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.Normalize();
+ 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.Normalize();
+ 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.Normalize();
+ 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 0000000000..7c42084ebe
--- /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 0000000000..e3716e1e0a
--- /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 0000000000..0f7aead024
--- /dev/null
+++ b/vcl/source/pdf/Matrix3.cxx
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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]);
+}
+
+basegfx::B2DPoint Matrix3::transform(const basegfx::B2DPoint& rOrig) const
+{
+ double x = rOrig.getX(), y = rOrig.getY();
+ return basegfx::B2DPoint(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 0000000000..553be738c8
--- /dev/null
+++ b/vcl/source/pdf/PDFiumLibrary.cxx
@@ -0,0 +1,1531 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <sal/log.hxx>
+#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 <vcl/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");
+
+static_assert(static_cast<int>(vcl::pdf::PDFFormFieldType::Unknown) == FPDF_FORMFIELD_UNKNOWN,
+ "PDFFormFieldType::Unknown value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFFormFieldType::PushButton) == FPDF_FORMFIELD_PUSHBUTTON,
+ "PDFFormFieldType::PushButton value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFFormFieldType::CheckBox) == FPDF_FORMFIELD_CHECKBOX,
+ "PDFFormFieldType::CheckBox value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFFormFieldType::RadioButton)
+ == FPDF_FORMFIELD_RADIOBUTTON,
+ "PDFFormFieldType::RadioButton value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFFormFieldType::ComboBox) == FPDF_FORMFIELD_COMBOBOX,
+ "PDFFormFieldType::ComboBox value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFFormFieldType::ListBox) == FPDF_FORMFIELD_LISTBOX,
+ "PDFFormFieldType::ListBox value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFFormFieldType::TextField) == FPDF_FORMFIELD_TEXTFIELD,
+ "PDFFormFieldType::TextField value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFFormFieldType::Signature) == FPDF_FORMFIELD_SIGNATURE,
+ "PDFFormFieldType::Signature value mismatch");
+
+static_assert(static_cast<int>(vcl::pdf::PDFAnnotAActionType::KeyStroke)
+ == FPDF_ANNOT_AACTION_KEY_STROKE,
+ "PDFAnnotAActionType::KeyStroke) value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFAnnotAActionType::Format) == FPDF_ANNOT_AACTION_FORMAT,
+ "PDFAnnotAActionType::Format) value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFAnnotAActionType::Validate)
+ == FPDF_ANNOT_AACTION_VALIDATE,
+ "PDFAnnotAActionType::Validate) value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFAnnotAActionType::Calculate)
+ == FPDF_ANNOT_AACTION_CALCULATE,
+ "PDFAnnotAActionType::Calculate) 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;
+ PDFFormFieldType getFormFieldType(PDFiumDocument* pDoc) override;
+ float getFontSize(PDFiumDocument* pDoc) override;
+ OUString getFormFieldAlternateName(PDFiumDocument* pDoc) override;
+ int getFormFieldFlags(PDFiumDocument* pDoc) override;
+ OUString getFormAdditionalActionJavaScript(PDFiumDocument* pDoc,
+ PDFAnnotAActionType eEvent) override;
+ OUString getFormFieldValue(PDFiumDocument* pDoc) 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;
+
+ void onAfterLoadPage(PDFiumDocument* pDoc) 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;
+ /// @brief creates bitmap, can reduce size if needed, check nWidth and nHeight
+ 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)
+ {
+ int nOriginal = nHeight;
+ // PDFium cannot create big bitmaps, max 2^14 x 2^14 x 4 bytes per pixel
+ if (nHeight > 16384)
+ nHeight = 16384;
+
+ if (nWidth > 16384)
+ {
+ nWidth = 16384.0 / nOriginal * nWidth;
+ }
+
+ if (nWidth * nHeight > 16384 * 16384)
+ {
+ nOriginal = nWidth;
+ nHeight = 16384.0 / nOriginal * nHeight;
+ }
+
+ pPdfBitmap = FPDFBitmap_Create(nWidth, nHeight, nAlpha);
+ }
+
+ if (!pPdfBitmap)
+ {
+ maLastError = "Failed to create bitmap";
+ SAL_WARN("vcl.filter", "PDFiumImpl: " << getLastError());
+ }
+ 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);
+}
+
+void PDFiumPageImpl::onAfterLoadPage(PDFiumDocument* pDoc)
+{
+ auto pDocImpl = static_cast<PDFiumDocumentImpl*>(pDoc);
+ FORM_OnAfterLoadPage(mpPage, pDocImpl->getFormHandlePointer());
+}
+
+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)
+{
+ int nPageWidth = getWidth();
+ int nPageHeight = getHeight();
+ std::unique_ptr<PDFiumBitmap> pPdfBitmap
+ = PDFiumLibrary::get()->createBitmap(nPageWidth, nPageHeight, /*nAlpha=*/1);
+ if (!pPdfBitmap)
+ return 0;
+
+ PDFiumBitmapImpl* pBitmapImpl = static_cast<PDFiumBitmapImpl*>(pPdfBitmap.get());
+
+ int nFlags = 0;
+ if (nMDPPerm != 3)
+ {
+ // Annotations/commenting should affect the checksum, signature verification wants this.
+ nFlags = FPDF_ANNOT;
+ }
+ FPDF_RenderPageBitmap(pBitmapImpl->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(pBitmapImpl->getPointer()));
+ const int nStride = FPDFBitmap_GetStride(pBitmapImpl->getPointer());
+ for (int 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()
+{
+ unsigned int nR, nG, nB, nA;
+ if (FPDFAnnot_GetColor(mpAnnotation, FPDFANNOT_COLORTYPE_Color, &nR, &nG, &nB, &nA))
+ {
+ return Color(ColorAlpha, nA, nR, nG, nB);
+ }
+ // FPDFAnnot_GetColor can return false if there is an appearance stream
+ // So we search for a color with getStrokeColor
+ for (int i = 0; i < getObjectCount(); ++i)
+ {
+ if (getObject(i)->getType() == PDFPageObjectType::Path)
+ return getObject(i)->getStrokeColor();
+ }
+ return COL_TRANSPARENT;
+}
+
+Color PDFiumAnnotationImpl::getInteriorColor()
+{
+ unsigned int nR, nG, nB, nA;
+ if (FPDFAnnot_GetColor(mpAnnotation, FPDFANNOT_COLORTYPE_InteriorColor, &nR, &nG, &nB, &nA))
+ {
+ return Color(ColorAlpha, nA, nR, nG, nB);
+ }
+ // FPDFAnnot_GetColor can return false if there is an appearance stream
+ // So we search for a color with getFillColor
+ for (int i = 0; i < getObjectCount(); ++i)
+ {
+ if (getObject(i)->getType() == PDFPageObjectType::Path)
+ return getObject(i)->getFillColor();
+ }
+ return COL_TRANSPARENT;
+}
+
+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;
+}
+
+PDFFormFieldType PDFiumAnnotationImpl::getFormFieldType(PDFiumDocument* pDoc)
+{
+ auto pDocImpl = static_cast<PDFiumDocumentImpl*>(pDoc);
+ return PDFFormFieldType(
+ FPDFAnnot_GetFormFieldType(pDocImpl->getFormHandlePointer(), mpAnnotation));
+}
+
+int PDFiumAnnotationImpl::getFormFieldFlags(PDFiumDocument* pDoc)
+{
+ auto pDocImpl = static_cast<PDFiumDocumentImpl*>(pDoc);
+ return FPDFAnnot_GetFormFieldFlags(pDocImpl->getFormHandlePointer(), mpAnnotation);
+}
+
+float PDFiumAnnotationImpl::getFontSize(PDFiumDocument* pDoc)
+{
+ auto pDocImpl = static_cast<PDFiumDocumentImpl*>(pDoc);
+ float fRet{};
+ if (!FPDFAnnot_GetFontSize(pDocImpl->getFormHandlePointer(), mpAnnotation, &fRet))
+ {
+ return 0.0f;
+ }
+
+ return fRet;
+}
+
+OUString PDFiumAnnotationImpl::getFormFieldAlternateName(PDFiumDocument* pDoc)
+{
+ auto pDocImpl = static_cast<PDFiumDocumentImpl*>(pDoc);
+ OUString aString;
+ unsigned long nSize = FPDFAnnot_GetFormFieldAlternateName(pDocImpl->getFormHandlePointer(),
+ mpAnnotation, 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_GetFormFieldAlternateName(
+ pDocImpl->getFormHandlePointer(), mpAnnotation,
+ reinterpret_cast<FPDF_WCHAR*>(pText.get()), nSize * 2);
+ assert(nStringSize % 2 == 0);
+ nStringSize /= 2;
+ if (nStringSize > 0)
+ {
+#if defined OSL_BIGENDIAN
+ for (unsigned long i = 0; i != nStringSize; ++i)
+ {
+ pText[i] = OSL_SWAPWORD(pText[i]);
+ }
+#endif
+ aString = OUString(pText.get());
+ }
+ }
+ return aString;
+}
+
+OUString PDFiumAnnotationImpl::getFormFieldValue(PDFiumDocument* pDoc)
+{
+ auto pDocImpl = static_cast<PDFiumDocumentImpl*>(pDoc);
+ OUString aString;
+ unsigned long nSize
+ = FPDFAnnot_GetFormFieldValue(pDocImpl->getFormHandlePointer(), mpAnnotation, 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_GetFormFieldValue(pDocImpl->getFormHandlePointer(), mpAnnotation,
+ reinterpret_cast<FPDF_WCHAR*>(pText.get()), nSize * 2);
+ assert(nStringSize % 2 == 0);
+ nStringSize /= 2;
+ if (nStringSize > 0)
+ {
+#if defined OSL_BIGENDIAN
+ for (unsigned long i = 0; i != nStringSize; ++i)
+ {
+ pText[i] = OSL_SWAPWORD(pText[i]);
+ }
+#endif
+ aString = OUString(pText.get());
+ }
+ }
+ return aString;
+}
+
+OUString PDFiumAnnotationImpl::getFormAdditionalActionJavaScript(PDFiumDocument* pDoc,
+ PDFAnnotAActionType eEvent)
+{
+ auto pDocImpl = static_cast<PDFiumDocumentImpl*>(pDoc);
+ OUString aString;
+ unsigned long nSize = FPDFAnnot_GetFormAdditionalActionJavaScript(
+ pDocImpl->getFormHandlePointer(), mpAnnotation, static_cast<int>(eEvent), 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_GetFormAdditionalActionJavaScript(
+ pDocImpl->getFormHandlePointer(), mpAnnotation, static_cast<int>(eEvent),
+ reinterpret_cast<FPDF_WCHAR*>(pText.get()), nSize * 2);
+ assert(nStringSize % 2 == 0);
+ nStringSize /= 2;
+ if (nStringSize > 0)
+ {
+#if defined OSL_BIGENDIAN
+ for (unsigned long i = 0; i != nStringSize; ++i)
+ {
+ pText[i] = OSL_SWAPWORD(pText[i]);
+ }
+#endif
+ aString = OUString(pText.get());
+ }
+ }
+ return aString;
+}
+
+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 0000000000..cb392a8cb8
--- /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(std::u16string_view rInput)
+{
+ if (rInput.size() < 6)
+ return {};
+
+ std::u16string_view prefix = rInput.substr(0, 2);
+ if (prefix != u"D:")
+ return {};
+
+ std::u16string_view sYear = rInput.substr(2, 4);
+
+ std::u16string_view sMonth(u"01");
+ if (rInput.size() >= 8)
+ sMonth = rInput.substr(6, 2);
+
+ std::u16string_view sDay(u"01");
+ if (rInput.size() >= 10)
+ sDay = rInput.substr(8, 2);
+
+ std::u16string_view sHours(u"00");
+ if (rInput.size() >= 12)
+ sHours = rInput.substr(10, 2);
+
+ std::u16string_view sMinutes(u"00");
+ if (rInput.size() >= 14)
+ sMinutes = rInput.substr(12, 2);
+
+ std::u16string_view sSeconds(u"00");
+ if (rInput.size() >= 16)
+ sSeconds = rInput.substr(14, 2);
+
+ OUString sTimeZoneMark("Z");
+ if (rInput.size() >= 17)
+ sTimeZoneMark = rInput.substr(16, 1);
+
+ std::u16string_view sTimeZoneHours(u"00");
+ std::u16string_view sTimeZoneMinutes(u"00");
+ if ((sTimeZoneMark == "+" || sTimeZoneMark == "-") && rInput.size() >= 22)
+ {
+ std::u16string_view sTimeZoneSeparator = rInput.substr(19, 1);
+ if (sTimeZoneSeparator == u"'")
+ {
+ sTimeZoneHours = rInput.substr(17, 2);
+ sTimeZoneMinutes = rInput.substr(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 0000000000..0e6a043603
--- /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 0000000000..f4647cb383
--- /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("<<\n");
+ if (nFontDictObject)
+ rBuf.append("/Font " + OString::number(nFontDictObject) + " 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 0000000000..53bf3902ab
--- /dev/null
+++ b/vcl/source/pdf/XmpMetadata.cxx
@@ -0,0 +1,367 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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"_ostr, "xmpmeta"_ostr, "adobe:ns:meta/"_ostr);
+ aXmlWriter.startElement("rdf"_ostr, "RDF"_ostr,
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#"_ostr);
+
+ // PDF/A part ( ISO 19005-1:2005 - 6.7.11 )
+ if (mnPDF_A > 0)
+ {
+ OString sPdfVersion = OString::number(mnPDF_A);
+
+ aXmlWriter.startElement("rdf:Description");
+ aXmlWriter.attribute("rdf:about", ""_ostr);
+ aXmlWriter.attribute("xmlns:pdfaid", "http://www.aiim.org/pdfa/ns/id/"_ostr);
+
+ aXmlWriter.startElement("pdfaid:part");
+ aXmlWriter.content(sPdfVersion);
+ aXmlWriter.endElement();
+
+ aXmlWriter.startElement("pdfaid:conformance");
+ aXmlWriter.content("B"_ostr);
+ aXmlWriter.endElement();
+
+ aXmlWriter.endElement();
+ }
+
+ // Dublin Core properties
+ if (!msTitle.isEmpty() || !msAuthor.isEmpty() || !msSubject.isEmpty()
+ || !maContributor.empty() || !msCoverage.isEmpty() || !msIdentifier.isEmpty()
+ || !maPublisher.empty() || !maRelation.empty() || !msRights.isEmpty()
+ || !msSource.isEmpty() || !msType.isEmpty())
+ {
+ aXmlWriter.startElement("rdf:Description");
+ aXmlWriter.attribute("rdf:about", ""_ostr);
+ aXmlWriter.attribute("xmlns:dc", "http://purl.org/dc/elements/1.1/"_ostr);
+
+ aXmlWriter.startElement("dc:format");
+ aXmlWriter.content("application/pdf"_ostr);
+ aXmlWriter.endElement();
+
+ 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", "x-default"_ostr);
+ 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", "x-default"_ostr);
+ aXmlWriter.content(msSubject);
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ }
+ if (!maContributor.empty())
+ {
+ aXmlWriter.startElement("dc:contributor");
+ aXmlWriter.startElement("rdf:Bag");
+ for (const OString& rContributor : maContributor)
+ {
+ aXmlWriter.startElement("rdf:li");
+ aXmlWriter.content(rContributor);
+ aXmlWriter.endElement();
+ }
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ }
+ if (!msCoverage.isEmpty())
+ {
+ aXmlWriter.startElement("dc:coverage");
+ aXmlWriter.content(msCoverage);
+ aXmlWriter.endElement();
+ }
+ if (!msIdentifier.isEmpty())
+ {
+ aXmlWriter.startElement("dc:identifier");
+ aXmlWriter.content(msIdentifier);
+ aXmlWriter.endElement();
+ }
+ if (!maPublisher.empty())
+ {
+ aXmlWriter.startElement("dc:publisher");
+ aXmlWriter.startElement("rdf:Bag");
+ for (const OString& rPublisher : maPublisher)
+ {
+ aXmlWriter.startElement("rdf:li");
+ aXmlWriter.content(rPublisher);
+ aXmlWriter.endElement();
+ }
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ }
+ if (!maRelation.empty())
+ {
+ aXmlWriter.startElement("dc:relation");
+ aXmlWriter.startElement("rdf:Bag");
+ for (const OString& rRelation : maRelation)
+ {
+ aXmlWriter.startElement("rdf:li");
+ aXmlWriter.content(rRelation);
+ aXmlWriter.endElement();
+ }
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ }
+ if (!msRights.isEmpty())
+ {
+ aXmlWriter.startElement("dc:rights");
+ aXmlWriter.startElement("rdf:Alt");
+ aXmlWriter.startElement("rdf:li");
+ aXmlWriter.attribute("xml:lang", "x-default"_ostr);
+ aXmlWriter.content(msRights);
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ }
+ if (!msSource.isEmpty())
+ {
+ aXmlWriter.startElement("dc:source");
+ aXmlWriter.content(msSource);
+ aXmlWriter.endElement();
+ }
+ if (!msType.isEmpty())
+ {
+ aXmlWriter.startElement("dc:type");
+ aXmlWriter.content(msType);
+ aXmlWriter.endElement();
+ }
+ aXmlWriter.endElement();
+ }
+
+ // PDF/UA
+ if (mbPDF_UA)
+ {
+ if (mnPDF_A != 0)
+ { // tdf#157517 PDF/A extension schema is required
+ aXmlWriter.startElement("rdf:Description");
+ aXmlWriter.attribute("rdf:about", ""_ostr);
+ aXmlWriter.attribute("xmlns:pdfaExtension",
+ "http://www.aiim.org/pdfa/ns/extension/"_ostr);
+ aXmlWriter.attribute("xmlns:pdfaSchema",
+ "http://www.aiim.org/pdfa/ns/schema#"_ostr);
+ aXmlWriter.attribute("xmlns:pdfaProperty",
+ "http://www.aiim.org/pdfa/ns/property#"_ostr);
+ aXmlWriter.startElement("pdfaExtension:schemas");
+ aXmlWriter.startElement("rdf:Bag");
+ aXmlWriter.startElement("rdf:li");
+ aXmlWriter.attribute("rdf:parseType", "Resource"_ostr);
+ aXmlWriter.startElement("pdfaSchema:namespaceURI");
+ aXmlWriter.content("http://www.aiim.org/pdfua/ns/id/"_ostr);
+ aXmlWriter.endElement();
+ aXmlWriter.startElement("pdfaSchema:prefix");
+ aXmlWriter.content("pdfuaid"_ostr);
+ aXmlWriter.endElement();
+ aXmlWriter.startElement("pdfaSchema:schema");
+ aXmlWriter.content("PDF/UA identification schema"_ostr);
+ aXmlWriter.endElement();
+ aXmlWriter.startElement("pdfaSchema:property");
+ aXmlWriter.startElement("rdf:Seq");
+
+ aXmlWriter.startElement("rdf:li");
+ aXmlWriter.attribute("rdf:parseType", "Resource"_ostr);
+ aXmlWriter.startElement("pdfaProperty:category");
+ aXmlWriter.content("internal"_ostr);
+ aXmlWriter.endElement();
+ aXmlWriter.startElement("pdfaProperty:description");
+ aXmlWriter.content("PDF/UA version identifier"_ostr);
+ aXmlWriter.endElement();
+ aXmlWriter.startElement("pdfaProperty:name");
+ aXmlWriter.content("part"_ostr);
+ aXmlWriter.endElement();
+ aXmlWriter.startElement("pdfaProperty:valueType");
+ aXmlWriter.content("Integer"_ostr);
+ aXmlWriter.endElement();
+ aXmlWriter.endElement(); // rdf:li
+
+ aXmlWriter.startElement("rdf:li");
+ aXmlWriter.attribute("rdf:parseType", "Resource"_ostr);
+ aXmlWriter.startElement("pdfaProperty:category");
+ aXmlWriter.content("internal"_ostr);
+ aXmlWriter.endElement();
+ aXmlWriter.startElement("pdfaProperty:description");
+ aXmlWriter.content("PDF/UA amendment identifier"_ostr);
+ aXmlWriter.endElement();
+ aXmlWriter.startElement("pdfaProperty:name");
+ aXmlWriter.content("amd"_ostr);
+ aXmlWriter.endElement();
+ aXmlWriter.startElement("pdfaProperty:valueType");
+ aXmlWriter.content("Text"_ostr);
+ aXmlWriter.endElement();
+ aXmlWriter.endElement(); // rdf:li
+
+ aXmlWriter.startElement("rdf:li");
+ aXmlWriter.attribute("rdf:parseType", "Resource"_ostr);
+ aXmlWriter.startElement("pdfaProperty:category");
+ aXmlWriter.content("internal"_ostr);
+ aXmlWriter.endElement();
+ aXmlWriter.startElement("pdfaProperty:description");
+ aXmlWriter.content("PDF/UA corrigenda identifier"_ostr);
+ aXmlWriter.endElement();
+ aXmlWriter.startElement("pdfaProperty:name");
+ aXmlWriter.content("corr"_ostr);
+ aXmlWriter.endElement();
+ aXmlWriter.startElement("pdfaProperty:valueType");
+ aXmlWriter.content("Text"_ostr);
+ aXmlWriter.endElement();
+ aXmlWriter.endElement(); // rdf:li
+
+ aXmlWriter.endElement(); // rdf:Seq
+ aXmlWriter.endElement(); // pdfaSchema:property
+ aXmlWriter.endElement(); // rdf:li
+ aXmlWriter.endElement(); // rdf:Bag
+ aXmlWriter.endElement(); // pdfaExtension:schemas
+ aXmlWriter.endElement(); // rdf:Description
+ }
+ OString sPdfUaVersion = OString::number(1);
+ aXmlWriter.startElement("rdf:Description");
+ aXmlWriter.attribute("rdf:about", ""_ostr);
+ aXmlWriter.attribute("xmlns:pdfuaid", "http://www.aiim.org/pdfua/ns/id/"_ostr);
+
+ aXmlWriter.startElement("pdfuaid:part");
+ aXmlWriter.content(sPdfUaVersion);
+ aXmlWriter.endElement();
+
+ aXmlWriter.endElement();
+ }
+
+ // PDF properties
+ if (!msProducer.isEmpty() || !msKeywords.isEmpty() || !msPDFVersion.isEmpty())
+ {
+ aXmlWriter.startElement("rdf:Description");
+ aXmlWriter.attribute("rdf:about", ""_ostr);
+ aXmlWriter.attribute("xmlns:pdf", "http://ns.adobe.com/pdf/1.3/"_ostr);
+ if (!msProducer.isEmpty())
+ {
+ aXmlWriter.startElement("pdf:Producer");
+ aXmlWriter.content(msProducer);
+ aXmlWriter.endElement();
+ }
+ if (!msKeywords.isEmpty())
+ {
+ aXmlWriter.startElement("pdf:Keywords");
+ aXmlWriter.content(msKeywords);
+ aXmlWriter.endElement();
+ }
+ if (!msPDFVersion.isEmpty())
+ {
+ aXmlWriter.startElement("pdf:PDFVersion");
+ aXmlWriter.content(msPDFVersion);
+ aXmlWriter.endElement();
+ }
+ aXmlWriter.endElement();
+ }
+
+ // XMP Basic schema
+ aXmlWriter.startElement("rdf:Description");
+ aXmlWriter.attribute("rdf:about", ""_ostr);
+ aXmlWriter.attribute("xmlns:xmp", "http://ns.adobe.com/xap/1.0/"_ostr);
+ 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.startElement("xmp:ModifyDate");
+ aXmlWriter.content(m_sCreateDate);
+ aXmlWriter.endElement();
+
+ aXmlWriter.startElement("xmp:MetadataDate");
+ 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 0000000000..776b14d47c
--- /dev/null
+++ b/vcl/source/printer/Options.cxx
@@ -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 .
+ */
+
+#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);
+
+ 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 0000000000..8ec787cbd1
--- /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 0000000000..fd3f29ac27
--- /dev/null
+++ b/vcl/source/rendercontext/drawmode.cxx
@@ -0,0 +1,283 @@
+/* -*- 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)
+ {
+ if (nDrawMode & DrawModeFlags::SettingsForSelection)
+ aColor = rStyleSettings.GetHighlightColor();
+ else
+ aColor = rStyleSettings.GetWindowTextColor();
+ }
+ }
+ }
+
+ 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)
+ {
+ if (nDrawMode & DrawModeFlags::SettingsForSelection)
+ aColor = rStyleSettings.GetHighlightColor();
+ else
+ 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)
+ {
+ if (nDrawMode & DrawModeFlags::SettingsForSelection)
+ aColor = rStyleSettings.GetHighlightColor();
+ else
+ aColor = rStyleSettings.GetWindowTextColor();
+ }
+
+ 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)
+ {
+ if (nDrawMode & DrawModeFlags::SettingsForSelection)
+ aColor = rStyleSettings.GetHighlightTextColor();
+ else
+ aColor = rStyleSettings.GetWindowTextColor();
+ }
+ }
+
+ 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)
+ {
+ if (nDrawMode & DrawModeFlags::SettingsForSelection)
+ aTextColor = rStyleSettings.GetHighlightTextColor();
+ else
+ aTextColor = rStyleSettings.GetWindowTextColor();
+ }
+
+ 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)
+ {
+ if (nDrawMode & DrawModeFlags::SettingsForSelection)
+ aTextFillColor = rStyleSettings.GetHighlightColor();
+ else
+ 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::N8_BPP,
+ &Bitmap::GetGreyPalette(256));
+ 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.GetAlphaMask().GetBitmap());
+ aMaskEx.Invert(); // convert to transparency
+ BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(129));
+ aMaskEx.Invert(); // convert to alpha
+ aBmpEx = BitmapEx(aColorBmp, aMaskEx.GetBitmap());
+ }
+ else
+ {
+ aBmpEx = BitmapEx(aColorBmp);
+ }
+ }
+
+ 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 0000000000..721ae910f1
--- /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 0000000000..826d288bba
--- /dev/null
+++ b/vcl/source/text/ImplLayoutArgs.cxx
@@ -0,0 +1,363 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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>
+#include <utility>
+
+namespace vcl::text
+{
+ImplLayoutArgs::ImplLayoutArgs(const OUString& rStr, int nMinCharPos, int nEndCharPos,
+ SalLayoutFlags nFlags, LanguageTag aLanguageTag,
+ vcl::text::TextLayoutCache const* const pLayoutCache)
+ : maLanguageTag(std::move(aLanguageTag))
+ , mnFlags(nFlags)
+ , mrStr(rStr)
+ , mnMinCharPos(nMinCharPos)
+ , mnEndCharPos(nEndCharPos)
+ , m_pTextLayoutCache(pLayoutCache)
+ , mpDXArray(nullptr)
+ , mpKashidaArray(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(double nWidth) { mnLayoutWidth = nWidth; }
+
+void ImplLayoutArgs::SetDXArray(double const* pDXArray) { mpDXArray = pDXArray; }
+
+void ImplLayoutArgs::SetKashidaArray(sal_Bool const* pKashidaArray)
+{
+ mpKashidaArray = pKashidaArray;
+}
+
+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(DisableLigatures);
+ 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)
+ {
+ s << "[";
+ int count = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
+ lim = count;
+ if (lim > 10)
+ lim = 7;
+ for (int i = 0; i < lim; i++)
+ {
+ s << rArgs.mpDXArray[i];
+ if (i < lim - 1)
+ s << ",";
+ }
+ if (count > lim)
+ {
+ if (count > lim + 1)
+ s << "...";
+ s << rArgs.mpDXArray[count - 1];
+ }
+ s << "]";
+ }
+ else
+ s << "NULL";
+
+ s << ",KashidaArray=";
+ if (rArgs.mpKashidaArray)
+ {
+ s << "[";
+ int count = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
+ lim = count;
+ if (lim > 10)
+ lim = 7;
+ for (int i = 0; i < lim; i++)
+ {
+ s << rArgs.mpKashidaArray[i];
+ if (i < lim - 1)
+ s << ",";
+ }
+ if (count > lim)
+ {
+ if (count > lim + 1)
+ s << "...";
+ s << rArgs.mpKashidaArray[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 0000000000..3e054f5c40
--- /dev/null
+++ b/vcl/source/text/ImplLayoutRuns.cxx
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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>
+#include <algorithm>
+
+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) )
+ std::swap( nCharPos0, nCharPos1 );
+
+ 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
+ std::swap( nMinCharPos, nEndCharPos );
+
+ 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 0000000000..1d3e8e5045
--- /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/text/mnemonic.cxx b/vcl/source/text/mnemonic.cxx
new file mode 100644
index 0000000000..7336032b8d
--- /dev/null
+++ b/vcl/source/text/mnemonic.cxx
@@ -0,0 +1,53 @@
+/* -*- 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/mnemonic.hxx>
+
+OUString removeMnemonicFromString(OUString const& rStr)
+{
+ sal_Int32 nDummy;
+ return removeMnemonicFromString(rStr, nDummy);
+}
+
+OUString removeMnemonicFromString(OUString const& 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;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/text/textlayout.cxx b/vcl/source/text/textlayout.cxx
new file mode 100644
index 0000000000..33232b6f09
--- /dev/null
+++ b/vcl/source/text/textlayout.cxx
@@ -0,0 +1,746 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/file.h>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <comphelper/processfactory.hxx>
+#include <i18nlangtag/languagetag.hxx>
+
+#include <vcl/ctrl.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/unohelp.hxx>
+
+#include <textlayout.hxx>
+#include <textlineinfo.hxx>
+
+static bool lcl_IsCharIn(sal_Unicode c, const char* pStr)
+{
+ while ( *pStr )
+ {
+ if ( *pStr == c )
+ return true;
+ pStr++;
+ }
+
+ return false;
+}
+
+ImplMultiTextLineInfo::ImplMultiTextLineInfo()
+{
+}
+
+ImplMultiTextLineInfo::~ImplMultiTextLineInfo()
+{
+}
+
+void ImplMultiTextLineInfo::AddLine( const ImplTextLineInfo& rLine )
+{
+ mvLines.push_back(rLine);
+}
+
+void ImplMultiTextLineInfo::Clear()
+{
+ mvLines.clear();
+}
+
+namespace vcl
+{
+ OUString TextLayoutCommon::GetCenterEllipsisString(OUString const& rOrigStr, sal_Int32 nIndex, tools::Long nMaxWidth)
+ {
+ OUStringBuffer aTmpStr(rOrigStr);
+
+ // speed it up by removing all but 1.33x as many as the break pos.
+ sal_Int32 nEraseChars = std::max<sal_Int32>(4, rOrigStr.getLength() - (nIndex*4)/3);
+ while(nEraseChars < rOrigStr.getLength() && GetTextWidth(aTmpStr.toString(), 0, aTmpStr.getLength()) > nMaxWidth)
+ {
+ aTmpStr = rOrigStr;
+ sal_Int32 i = (aTmpStr.getLength() - nEraseChars)/2;
+ aTmpStr.remove(i, nEraseChars++);
+ aTmpStr.insert(i, "...");
+ }
+
+ return aTmpStr.makeStringAndClear();
+ }
+
+ OUString TextLayoutCommon::GetEndEllipsisString(OUString const& rOrigStr, sal_Int32 nIndex, tools::Long nMaxWidth, bool bClipText)
+ {
+ OUString aStr = rOrigStr;
+ aStr = aStr.copy(0, nIndex);
+
+ if (nIndex > 1)
+ {
+ aStr += "...";
+ while (!aStr.isEmpty() && (GetTextWidth(aStr, 0, aStr.getLength()) > nMaxWidth))
+ {
+ if ((nIndex > 1) || (nIndex == aStr.getLength()))
+ nIndex--;
+
+ aStr = aStr.replaceAt(nIndex, 1, u"");
+ }
+ }
+
+ if (aStr.isEmpty() && bClipText)
+ aStr += OUStringChar(rOrigStr[0]);
+
+ return aStr;
+ }
+
+ namespace
+ {
+ OUString lcl_GetPathEllipsisString(OUString const& rOrigStr, sal_Int32 nIndex)
+ {
+ OUString aPath(rOrigStr);
+ OUString aAbbreviatedPath;
+ osl_abbreviateSystemPath(aPath.pData, &aAbbreviatedPath.pData, nIndex, nullptr);
+ return aAbbreviatedPath;
+ }
+ }
+
+ OUString TextLayoutCommon::GetNewsEllipsisString(OUString const& rOrigStr, tools::Long nMaxWidth, DrawTextFlags nStyle)
+ {
+ OUString aStr = rOrigStr;
+ static char const pSepChars[] = ".";
+
+ // Determine last section
+ sal_Int32 nLastContent = aStr.getLength();
+ while (nLastContent)
+ {
+ nLastContent--;
+
+ if (lcl_IsCharIn(aStr[nLastContent], pSepChars))
+ break;
+ }
+
+ while (nLastContent && lcl_IsCharIn(aStr[nLastContent-1], pSepChars))
+ {
+ nLastContent--;
+ }
+
+ OUString aLastStr = aStr.copy(nLastContent);
+ OUString aTempLastStr1 = "..." + aLastStr;
+
+ if (GetTextWidth(aTempLastStr1, 0, aTempLastStr1.getLength()) > nMaxWidth)
+ return GetEllipsisString(aStr, nMaxWidth, DrawTextFlags::EndEllipsis);
+
+ sal_Int32 nFirstContent = 0;
+ while (nFirstContent < nLastContent)
+ {
+ nFirstContent++;
+ if (lcl_IsCharIn(aStr[nFirstContent], pSepChars))
+ break;
+ }
+
+ while ((nFirstContent < nLastContent) && lcl_IsCharIn(aStr[nFirstContent], pSepChars))
+ {
+ nFirstContent++;
+ }
+
+ if (nFirstContent >= nLastContent)
+ return GetEllipsisString(aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis);
+
+ if (nFirstContent > 4)
+ nFirstContent = 4;
+
+ OUString aFirstStr = OUString::Concat(aStr.subView(0, nFirstContent)) + "...";
+ OUString aTempStr = aFirstStr + aLastStr;
+
+ if (GetTextWidth(aTempStr, 0, aTempStr.getLength() ) > nMaxWidth)
+ return GetEllipsisString(aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis);
+
+ do
+ {
+ aStr = aTempStr;
+
+ if (nLastContent > aStr.getLength())
+ nLastContent = aStr.getLength();
+
+ while (nFirstContent < nLastContent)
+ {
+ nLastContent--;
+ if (lcl_IsCharIn(aStr[nLastContent], pSepChars))
+ break;
+
+ }
+
+ while ((nFirstContent < nLastContent) && lcl_IsCharIn(aStr[nLastContent-1], pSepChars))
+ {
+ nLastContent--;
+ }
+
+ if (nFirstContent < nLastContent)
+ {
+ std::u16string_view aTempLastStr = aStr.subView(nLastContent);
+ aTempStr = aFirstStr + aTempLastStr;
+
+ if (GetTextWidth(aTempStr, 0, aTempStr.getLength()) > nMaxWidth)
+ break;
+ }
+ }
+ while (nFirstContent < nLastContent);
+
+ return aStr;
+ }
+
+ OUString TextLayoutCommon::GetEllipsisString(OUString const& rOrigStr, tools::Long nMaxWidth, DrawTextFlags nStyle)
+ {
+ OUString aStr = rOrigStr;
+ sal_Int32 nIndex = GetTextBreak( aStr, nMaxWidth, 0, aStr.getLength() );
+
+ if (nIndex == -1)
+ return aStr;
+
+ if ((nStyle & DrawTextFlags::CenterEllipsis) == DrawTextFlags::CenterEllipsis)
+ aStr = GetCenterEllipsisString(rOrigStr, nIndex, nMaxWidth);
+ else if (nStyle & DrawTextFlags::EndEllipsis)
+ aStr = GetEndEllipsisString(rOrigStr, nIndex, nMaxWidth, (nStyle & DrawTextFlags::Clip) == DrawTextFlags::Clip);
+ else if (nStyle & DrawTextFlags::PathEllipsis)
+ aStr = lcl_GetPathEllipsisString(rOrigStr, nIndex);
+ else if ( nStyle & DrawTextFlags::NewsEllipsis )
+ aStr = GetNewsEllipsisString(rOrigStr, nMaxWidth, nStyle);
+
+ return aStr;
+ }
+
+ sal_Int32 TextLayoutCommon::BreakLinesWithIterator(const tools::Long nWidth, OUString const& rStr,
+ css::uno::Reference< css::linguistic2::XHyphenator > const& xHyph,
+ css::uno::Reference<css::i18n::XBreakIterator> const& xBI,
+ const bool bHyphenate,
+ const sal_Int32 nPos, sal_Int32 nBreakPos)
+ {
+ const css::lang::Locale& rDefLocale(Application::GetSettings().GetUILanguageTag().getLocale());
+ sal_Int32 nSoftBreak = 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", "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_INFO_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 TextLayoutCommon::BreakLinesSimple(const tools::Long nWidth, OUString const& rStr,
+ 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 = GetTextWidth( rStr, nPos, nSpacePos-nPos );
+ }
+ } while( nW > nWidth );
+
+ if( nSpacePos != -1 )
+ {
+ nBreakPos = nSpacePos;
+ nLineWidth = GetTextWidth( rStr, nPos, nBreakPos-nPos );
+ if( nBreakPos < rStr.getLength()-1 )
+ nBreakPos++;
+ }
+ return nBreakPos;
+ }
+
+ tools::Long TextLayoutCommon::GetTextLines(tools::Rectangle const& rRect, const tools::Long nTextHeight,
+ ImplMultiTextLineInfo& rLineInfo,
+ tools::Long nWidth, OUString const& rStr,
+ DrawTextFlags nStyle)
+ {
+ 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 = GetTextWidth( rStr, nPos, nBreakPos-nPos );
+ if ( ( nLineWidth > nWidth ) && ( nStyle & DrawTextFlags::WordBreak ) )
+ {
+ if ( !xBI.is() )
+ xBI = vcl::unohelper::CreateBreakIterator();
+
+ if ( xBI.is() )
+ {
+ nBreakPos = BreakLinesWithIterator(nWidth, rStr, xHyph, xBI, bHyphenate, nPos, nBreakPos);
+ nLineWidth = GetTextWidth(rStr, nPos, nBreakPos - nPos);
+ }
+ else
+ // fallback to something really simple
+ nBreakPos = BreakLinesSimple(nWidth, rStr, 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;
+ }
+
+ 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 );
+ }
+
+ tools::Long DefaultTextLayout::GetTextArray( const OUString& _rText, KernArray* _pDXArray,
+ sal_Int32 _nStartIndex, sal_Int32 _nLength, bool bCaret ) const
+ {
+ return m_rTargetDevice.GetTextArray( _rText, _pDXArray, _nStartIndex, _nLength, bCaret );
+ }
+
+ 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 TextLayoutCommon
+ {
+ 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 tools::Long GetTextArray( const OUString& _rText, KernArray* _pDXAry, sal_Int32 _nStartIndex, sal_Int32 _nLength, bool bCaret = false ) 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:
+
+ 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() );
+ SAL_WARN_IF(aTargetMapMode.GetOrigin() != Point(), "vcl", "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.
+ SAL_WARN_IF(aTargetMapMode.GetMapUnit() != MapUnit::MapPixel, "vcl", "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 );
+ SAL_WARN_IF(aTargetMapMode.GetMapUnit() == MapUnit::MapPixel, "vcl", "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( std::u16string_view _rText, const sal_Int32 _nStartIndex, sal_Int32& _io_nLength )
+ {
+ sal_Int32 nTextLength = _rText.size();
+ if ( _nStartIndex > nTextLength )
+ return false;
+ if ( _nStartIndex + _io_nLength > nTextLength )
+ _io_nLength = nTextLength - _nStartIndex;
+ return true;
+ }
+ }
+
+ tools::Long ReferenceDeviceTextLayout::GetTextArray( const OUString& _rText, KernArray* _pDXAry, sal_Int32 _nStartIndex, sal_Int32 _nLength, bool bCaret ) 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, bCaret );
+#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;
+ }
+
+ KernArray 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() ) ) );
+ }
+
+ 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/toolkit/README b/vcl/source/toolkit/README
new file mode 100644
index 0000000000..78863390f1
--- /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 0000000000..7b23e1006e
--- /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 0000000000..9c0f6c505a
--- /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 0000000000..fa17e69ff2
--- /dev/null
+++ b/vcl/source/treelist/headbar.cxx
@@ -0,0 +1,1301 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 + "...");
+ 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 0000000000..e80a29b0bc
--- /dev/null
+++ b/vcl/source/treelist/iconview.cxx
@@ -0,0 +1,330 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/PngImageWriter.hxx>
+#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>
+#include <comphelper/propertyvalue.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);
+ }
+
+ 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::XVclWindowPeer> 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 OString extractPngString(const SvLBoxContextBmp* pBmpItem)
+{
+ BitmapEx aImage = pBmpItem->GetBitmap1().GetBitmapEx();
+ SvMemoryStream aOStm(65535, 65535);
+ // Use fastest compression "1"
+ css::uno::Sequence<css::beans::PropertyValue> aFilterData{
+ comphelper::makePropertyValue("Compression", sal_Int32(1)),
+ };
+ vcl::PngImageWriter aPNGWriter(aOStm);
+ aPNGWriter.setParameters(aFilterData);
+ if (aPNGWriter.write(aImage))
+ {
+ css::uno::Sequence<sal_Int8> aSeq(static_cast<sal_Int8 const*>(aOStm.GetData()),
+ aOStm.Tell());
+ OStringBuffer aBuffer("data:image/png;base64,");
+ ::comphelper::Base64::encode(aBuffer, aSeq);
+ return aBuffer.makeStringAndClear();
+ }
+
+ return ""_ostr;
+}
+
+void IconView::DumpEntryAndSiblings(tools::JsonWriter& rJsonWriter, SvTreeListEntry* pEntry)
+{
+ 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());
+
+ const bool bHandled
+ = maDumpElemToPropertyTreeHdl.IsSet()
+ && maDumpElemToPropertyTreeHdl.Call(json_prop_query(rJsonWriter, pEntry, "image"));
+ if (!bHandled)
+ {
+ pIt = pEntry->GetFirstItem(SvLBoxItemType::ContextBmp);
+ if (pIt)
+ {
+ const SvLBoxContextBmp* pBmpItem = static_cast<const SvLBoxContextBmp*>(pIt);
+ if (pBmpItem)
+ rJsonWriter.put("image", extractPngString(pBmpItem));
+ }
+ }
+
+ if (const OUString tooltip = GetEntryTooltip(pEntry); !tooltip.isEmpty())
+ rJsonWriter.put("tooltip", tooltip);
+
+ if (IsSelected(pEntry))
+ rJsonWriter.put("selected", true);
+
+ if (pEntry->GetFlags() & SvTLEntryFlags::IS_SEPARATOR)
+ rJsonWriter.put("separator", true);
+
+ rJsonWriter.put("row", GetModel()->GetAbsPos(pEntry));
+
+ pEntry = pEntry->NextSibling();
+ }
+}
+
+void IconView::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ SvTreeListBox::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("type", "iconview");
+ rJsonWriter.put("singleclickactivate", GetActivateOnSingleClick());
+ auto aNode = rJsonWriter.startArray("entries");
+ DumpEntryAndSiblings(rJsonWriter, First());
+}
+
+/* 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 0000000000..048e193d4f
--- /dev/null
+++ b/vcl/source/treelist/iconviewimpl.cxx
@@ -0,0 +1,738 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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()
+{
+ FindMostRight();
+ SyncVerThumb();
+ FillView();
+ ShowVerSBar();
+ if( m_bSimpleTravel && m_pCursor && m_pView->HasFocus() )
+ m_pView->Select( m_pCursor );
+ ShowCursor( true );
+ 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 0000000000..d566e338bb
--- /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() 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 0000000000..ba29af0202
--- /dev/null
+++ b/vcl/source/treelist/imap.cxx
@@ -0,0 +1,988 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <utility>
+#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( OUString _aURL, OUString _aAltText, OUString _aDesc,
+ OUString _aTarget, OUString _aName, bool bURLActive )
+: aURL(std::move( _aURL ))
+, aAltText(std::move( _aAltText ))
+, aDesc(std::move( _aDesc ))
+, aTarget(std::move( _aTarget ))
+, aName(std::move( _aName ))
+, 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 );
+
+ return static_cast<sal_Int32>( std::hypot( aPoint.X(), aPoint.Y() ) ) <= nRadius;
+}
+
+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( OUString _aName )
+: aName(std::move( _aName ))
+{
+}
+
+
+/******************************************************************************
+|*
+|* 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 ) const
+{
+ 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.WriteOString( 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 0000000000..1543801392
--- /dev/null
+++ b/vcl/source/treelist/imap2.cxx
@@ -0,0 +1,528 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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(OString::number(nRadius) + " ");
+ 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(";"_ostr, ""_ostr);
+ 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(";"_ostr, ""_ostr);
+ 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>(std::hypot( aDX.X(), 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 0000000000..073725f34b
--- /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 0000000000..35c795cb08
--- /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 0000000000..0deea8f698
--- /dev/null
+++ b/vcl/source/treelist/svimpbox.cxx
@@ -0,0 +1,3160 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vcl/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()
+{
+ 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 );
+ 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();
+
+ // Set color to draw the vertical and horizontal lines
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+
+ 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();
+ }
+ // 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_pActiveButton->isEnable())
+ {
+ 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)
+ {
+ const bool bChecked = m_pActiveButton->IsStateChecked();
+ m_pActiveButton->ClickHdl(m_pActiveEntry);
+ if (m_pActiveButton->IsStateChecked() != bChecked)
+ CallEventListeners(VclEventId::CheckboxToggle, 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))
+ {
+ // trigger button
+ SvLBoxItem* pButtonItem = m_pCursor->GetFirstItem(SvLBoxItemType::Button);
+ if (pButtonItem)
+ {
+ SvLBoxButton* pButton = static_cast<SvLBoxButton*>(pButtonItem);
+ const bool bChecked = pButton->IsStateChecked();
+ pButton->ClickHdl(m_pCursor);
+ InvalidateEntry(m_pCursor);
+ if (pButton->IsStateChecked() != bChecked)
+ CallEventListeners(VclEventId::CheckboxToggle, m_pActiveEntry);
+ }
+ else
+ bKeyUsed = false;
+ }
+ else
+ {
+ SelAllDestrAnch( false );
+ m_pView->Select( m_pCursor );
+ }
+ }
+ 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))
+ {
+ if (bMod1)
+ CollapseTo(m_pCursor);
+ else
+ m_pView->Collapse(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::GesturePan)
+ {
+ 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();
+ }
+}
+
+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 0000000000..26b3da4547
--- /dev/null
+++ b/vcl/source/treelist/svlbitm.cxx
@@ -0,0 +1,551 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <utility>
+#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::HIGHLIGHTED))
+ nIdx = SvBmp::HIUNCHECKED;
+ else if (nItemState == (SvItemStateFlags::CHECKED | SvItemStateFlags::HIGHLIGHTED))
+ nIdx = SvBmp::HICHECKED;
+ else if (nItemState == (SvItemStateFlags::TRISTATE | SvItemStateFlags::HIGHLIGHTED))
+ 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(OUString aStr)
+ : mbEmphasized(false)
+ , mbCustom(false)
+ , mfAlign(0.0)
+ , maText(std::move(aStr))
+{
+}
+
+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).getOpenWidth());
+ }
+ else if (mfAlign > 0.5)
+ {
+ nStyle |= DrawTextFlags::Right;
+ aSize.setWidth(rDev.GetBoundingRect(&rEntry).getOpenWidth());
+ }
+ }
+ 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() && !mbDisabled) ? 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() && !mbDisabled) ? 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 0000000000..81f0f4e8b0
--- /dev/null
+++ b/vcl/source/treelist/svtabbx.cxx
@@ -0,0 +1,1139 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/types.hxx>
+#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 <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/XAccessible.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());
+ }
+ }
+ else if (rItem.GetType() == SvLBoxItemType::ContextBmp)
+ {
+ const SvLBoxContextBmp* pBmpItem = dynamic_cast<const SvLBoxContextBmp*>(&rItem);
+ if (pBmpItem)
+ {
+ const OUString& rCollapsed = pBmpItem->GetBitmap1().GetStock();
+ const OUString& rExpanded = pBmpItem->GetBitmap2().GetStock();
+ if (!o3tl::trim(rCollapsed).empty() || !o3tl::trim(rExpanded).empty())
+ {
+ auto aColumn = rJsonWriter.startStruct();
+ if (!o3tl::trim(rCollapsed).empty())
+ rJsonWriter.put("collapsed", rCollapsed);
+ if (!o3tl::trim(rExpanded).empty())
+ rJsonWriter.put("expanded", rExpanded);
+ }
+ }
+ }
+ }
+ }
+
+ // 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 (rChildren.size() > 0 && !pTabListBox->IsExpanded(pEntry))
+ {
+ rJsonWriter.put("collapsed", 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", pTabListBox->GetModel()->GetAbsPos(pEntry));
+
+ 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;
+ }
+ */
+
+ // the 1st column (index 1 or 2 depending on button flags) is always set
+ // editable by SvTreeListBox::SetTabs(),
+ // which prevents setting a different column to editable as the first
+ // one with the flag is picked in SvTreeListBox::ImplEditEntry()
+ assert(aTabs.back()->nFlags & SvLBoxTabFlags::EDITABLE);
+ if (!(mvTabList[0].nFlags & SvLBoxTabFlags::EDITABLE))
+ {
+ aTabs.back()->nFlags &= ~SvLBoxTabFlags::EDITABLE;
+ }
+
+ // 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)
+{
+ assert(0 < nTabs);
+ 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;
+ }
+ // by default, 1st one is editable, others not; override with set_column_editables
+ mvTabList[0].nFlags |= SvLBoxTabFlags::EDITABLE;
+ 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 = GetEntryOnPos( 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()
+{
+ for (css::uno::Reference<css::accessibility::XAccessible>& rxChild : m_aAccessibleChildren)
+ comphelper::disposeComponent(rxChild);
+ m_aAccessibleChildren.clear();
+
+ 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 = GetEntryOnPos( _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 == GetEntryOnPos(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( GetEntryOnPos( _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 = GetEntryOnPos( _nRow );
+ return ( pEntry && IsSelected( pEntry ) );
+}
+
+bool SvHeaderTabListBox::IsColumnSelected( sal_Int32 ) const
+{
+ return false;
+}
+
+void SvHeaderTabListBox::GetAllSelectedRows(css::uno::Sequence<sal_Int32 >& rRowIndices) const
+{
+ const sal_Int32 nCount = GetSelectedRowCount();
+ rRowIndices.realloc(nCount);
+ auto pRows = rRowIndices.getArray();
+ SvTreeListEntry* pEntry = FirstSelected();
+ sal_Int32 nIndex = 0;
+ while (nIndex < nCount && pEntry)
+ {
+ pRows[nIndex] = GetEntryPos(pEntry);
+ pEntry = NextSelected( pEntry );
+ ++nIndex;
+ }
+ assert(nIndex == nCount && "Mismatch between GetSelectedRowCount() and count of selected rows when iterating.");
+}
+
+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)
+ aRect = tools::Rectangle(m_pImpl->m_pHeaderBar->GetWindowExtentsAbsolute());
+ else
+ {
+ pParent = m_pImpl->m_pHeaderBar->GetAccessibleParentWindow();
+ assert(pParent);
+ aRect = m_pImpl->m_pHeaderBar->GetWindowExtentsRelative(*pParent );
+ }
+ }
+ return aRect;
+}
+
+tools::Rectangle SvHeaderTabListBox::calcTableRect( bool _bOnScreen )
+{
+ if ( _bOnScreen )
+ return tools::Rectangle(GetWindowExtentsAbsolute());
+ else
+ return GetWindowExtentsRelative( *GetAccessibleParentWindow() );
+}
+
+tools::Rectangle SvHeaderTabListBox::GetFieldRectPixel( sal_Int32 _nRow, sal_uInt16 _nColumn, bool _bIsHeader, bool _bOnScreen )
+{
+ DBG_ASSERT( !_bIsHeader || 0 == _nRow, "invalid parameters" );
+ tools::Rectangle aRect;
+ SvTreeListEntry* pEntry = GetEntryOnPos(_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 );
+ aTopLeft = aRect.TopLeft();
+ if (_bOnScreen)
+ aTopLeft += Point(GetWindowExtentsAbsolute().TopLeft());
+ else
+ aTopLeft += GetWindowExtentsRelative( *GetAccessibleParentWindow() ).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->getTable(), *this, nullptr, _nRow, _nColumnPos, eState, false );
+ else
+ xChild = m_pImpl->m_aFactoryAccess.getFactory().createAccessibleBrowseBoxTableCell(
+ m_pAccessible->getTable(), *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( sal_Int64& _rStateSet, AccessibleBrowseBoxObjType _eType ) const
+{
+ switch( _eType )
+ {
+ case AccessibleBrowseBoxObjType::BrowseBox:
+ case AccessibleBrowseBoxObjType::Table:
+ {
+ _rStateSet |= AccessibleStateType::FOCUSABLE;
+ if ( HasFocus() )
+ _rStateSet |= AccessibleStateType::FOCUSED;
+ if ( IsActive() )
+ _rStateSet |= AccessibleStateType::ACTIVE;
+ if ( IsEnabled() )
+ {
+ _rStateSet |= AccessibleStateType::ENABLED;
+ _rStateSet |= AccessibleStateType::SENSITIVE;
+ }
+ if ( IsReallyVisible() )
+ _rStateSet |= AccessibleStateType::VISIBLE;
+ if ( _eType == AccessibleBrowseBoxObjType::Table )
+ {
+
+ _rStateSet |= AccessibleStateType::MANAGES_DESCENDANTS;
+ _rStateSet |= AccessibleStateType::MULTI_SELECTABLE;
+ }
+ break;
+ }
+
+ case AccessibleBrowseBoxObjType::ColumnHeaderBar:
+ {
+ sal_Int32 nCurRow = GetCurrRow();
+ sal_uInt16 nCurColumn = GetCurrColumn();
+ if ( IsCellVisible( nCurRow, nCurColumn ) )
+ _rStateSet |= AccessibleStateType::VISIBLE;
+ if ( IsEnabled() )
+ _rStateSet |= AccessibleStateType::ENABLED;
+ _rStateSet |= AccessibleStateType::TRANSIENT;
+ break;
+ }
+
+ case AccessibleBrowseBoxObjType::RowHeaderCell:
+ case AccessibleBrowseBoxObjType::ColumnHeaderCell:
+ {
+ _rStateSet |= AccessibleStateType::VISIBLE;
+ _rStateSet |= AccessibleStateType::FOCUSABLE;
+ _rStateSet |= AccessibleStateType::TRANSIENT;
+ if ( IsEnabled() )
+ _rStateSet |= AccessibleStateType::ENABLED;
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void SvHeaderTabListBox::FillAccessibleStateSetForCell( sal_Int64& _rStateSet, sal_Int32 _nRow, sal_uInt16 _nColumn ) const
+{
+ _rStateSet |= AccessibleStateType::FOCUSABLE;
+ _rStateSet |= AccessibleStateType::SELECTABLE;
+ _rStateSet |= AccessibleStateType::TRANSIENT;
+
+ if ( IsCellVisible( _nRow, _nColumn ) )
+ {
+ _rStateSet |= AccessibleStateType::VISIBLE;
+ _rStateSet |= AccessibleStateType::ENABLED;
+ }
+
+ if ( IsRowSelected( _nRow ) )
+ {
+ _rStateSet |= AccessibleStateType::ACTIVE;
+ if (HasChildPathFocus())
+ _rStateSet |= AccessibleStateType::FOCUSED;
+ _rStateSet |= AccessibleStateType::SELECTED;
+ }
+ if ( IsEnabled() )
+ _rStateSet |= 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 );
+}
+
+AbsoluteScreenPixelRectangle SvHeaderTabListBox::GetWindowExtentsAbsolute() const
+{
+ return Control::GetWindowExtentsAbsolute();
+}
+
+tools::Rectangle SvHeaderTabListBox::GetWindowExtentsRelative(const vcl::Window& rRelativeWindow) const
+{
+ return Control::GetWindowExtentsRelative( rRelativeWindow );
+}
+
+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 0000000000..7e6009de77
--- /dev/null
+++ b/vcl/source/treelist/transfer.cxx
@@ -0,0 +1,2243 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <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/scopeguard.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/filter/PngImageWriter.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_uInt64 nFirstPos = rOStm.Tell();
+ const sal_uInt32 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_uInt64 nLastPos = rOStm.Tell();
+
+ rOStm.Seek( nFirstPos );
+ rOStm.WriteUInt32( nLastPos - nFirstPos );
+ rOStm.Seek( nLastPos );
+
+ return rOStm;
+}
+
+static void TryReadTransferableObjectDescriptor(SvStream& rIStm,
+ TransferableObjectDescriptor& rObjDesc)
+{
+ auto nStartPos = rIStm.Tell();
+ comphelper::ScopeGuard streamPosRestore([nStartPos, &rIStm] { rIStm.Seek(nStartPos); });
+
+ sal_uInt32 size;
+ rIStm.ReadUInt32(size);
+
+ SvGlobalName className;
+ rIStm >> className;
+
+ sal_uInt32 viewAspect;
+ rIStm.ReadUInt32(viewAspect);
+
+ sal_Int32 width, height;
+ rIStm.ReadInt32(width).ReadInt32(height);
+
+ sal_Int32 dragStartPosX, dragStartPosY;
+ rIStm.ReadInt32(dragStartPosX).ReadInt32(dragStartPosY);
+
+ const OUString typeName = rIStm.ReadUniOrByteString(osl_getThreadTextEncoding());
+ const OUString displayName = rIStm.ReadUniOrByteString(osl_getThreadTextEncoding());
+
+ sal_uInt32 nSig1, nSig2;
+ rIStm.ReadUInt32(nSig1).ReadUInt32(nSig2);
+
+ if (!rIStm.good() || rIStm.Tell() - nStartPos != size || nSig1 != TOD_SIG1 || nSig2 != TOD_SIG2)
+ return;
+
+ rObjDesc.maClassName = className;
+ rObjDesc.mnViewAspect = viewAspect;
+ rObjDesc.maSize = Size(width, height);
+ rObjDesc.maDragStartPos = Point(dragStartPosX, dragStartPosY);
+ rObjDesc.maTypeName = typeName;
+ rObjDesc.maDisplayName = displayName;
+}
+
+// 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 constexpr OUString aClassNameString( u"classname"_ustr );
+ static constexpr OUString aTypeNameString( u"typename"_ustr );
+ static constexpr OUString aDisplayNameString( u"displayname"_ustr );
+ static constexpr OUString aViewAspectString( u"viewaspect"_ustr );
+ static constexpr OUString aWidthString( u"width"_ustr );
+ static constexpr OUString aHeightString( u"height"_ustr );
+ static constexpr OUString aPosXString( u"posx"_ustr );
+ static constexpr OUString aPosYString( u"posy"_ustr );
+
+ 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& )
+{
+}
+
+
+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::PngImageWriter aPNGWriter(aMemStm);
+ aPNGWriter.setParameters(aFilterData);
+ aPNGWriter.write(rBitmapEx);
+ }
+ 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));
+ OString sOut =
+ OString::number(sURL.getLength())
+ + "@" + sURL
+ + OString::number(sDesc.getLength())
+ + "@" + 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 );
+}
+
+namespace {
+
+class TransferableClipboardNotifier : public ::cppu::WeakImplHelper< XClipboardListener >
+{
+private:
+ 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 );
+
+ /// 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 )
+ :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;
+ 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()
+{
+ SolarMutexGuard g;
+
+ Reference< XClipboardListener > xKeepMeAlive( this );
+
+ if ( mxNotifier.is() )
+ mxNotifier->removeClipboardListener( this );
+ mxNotifier.clear();
+
+ mpListener = nullptr;
+}
+
+struct TransferableDataHelper_Impl
+{
+ 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 )
+ {
+ SolarMutexGuard g;
+
+ 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)
+{
+ SolarMutexGuard g;
+
+ 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( );
+ {
+ SolarMutexGuard g;
+ 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 constexpr OUString aCharsetStr( u"charset"_ustr );
+
+
+ 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;
+
+ 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);
+ auto data = GetSequence(format, {});
+ SvMemoryStream aSrcStm(data.getArray(), data.getLength(), StreamMode::READ);
+ TryReadTransferableObjectDescriptor(aSrcStm, *mxObjDesc);
+ break;
+ }
+ }
+}
+
+
+bool TransferableDataHelper::HasFormat( SotClipboardFormatId nFormat ) const
+{
+ SolarMutexGuard g;
+ return std::any_of(maFormats.begin(), maFormats.end(),
+ [&](const DataFlavorEx& data) { return data.mnSotId == nFormat; });
+}
+
+bool TransferableDataHelper::HasFormat( const DataFlavor& rFlavor ) const
+{
+ SolarMutexGuard g;
+ for (auto const& format : maFormats)
+ {
+ if( TransferableDataHelper::IsEqual( rFlavor, format ) )
+ return true;
+ }
+
+ return false;
+}
+
+sal_uInt32 TransferableDataHelper::GetFormatCount() const
+{
+ SolarMutexGuard g;
+ return maFormats.size();
+}
+
+SotClipboardFormatId TransferableDataHelper::GetFormat( sal_uInt32 nFormat ) const
+{
+ SolarMutexGuard g;
+ 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
+{
+ SolarMutexGuard g;
+ 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
+{
+ Any aRet;
+
+ try
+ {
+ SolarMutexGuard g;
+ 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
+ // 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
+ // 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 ) const
+{
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetTransferableObjectDescriptor( rDesc ) );
+}
+
+
+bool TransferableDataHelper::GetTransferableObjectDescriptor( TransferableObjectDescriptor& rDesc ) const
+{
+ 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( )
+{
+ SolarMutexGuard g;
+
+ StopClipboardListening( );
+
+ mxImpl->mxClipboardListener = new TransferableClipboardNotifier(mxClipboard, *this);
+
+ return mxImpl->mxClipboardListener->isListening();
+}
+
+void TransferableDataHelper::StopClipboardListening( )
+{
+ SolarMutexGuard g;
+
+ 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 constexpr OUString aCharsetString( u"charset"_ustr );
+
+ 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 constexpr OUString aFormatString( u"windows_formatname"_ustr );
+
+ 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 0000000000..1c53be66f1
--- /dev/null
+++ b/vcl/source/treelist/transfer2.cxx
@@ -0,0 +1,528 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/datatransfer/clipboard/LokClipboard.hpp>
+#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::optional<INetBookmark> moBookmk;
+
+ 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->moBookmk )
+ bFnd = SetINetBookmark( *pImpl->moBookmk, rFlavor );
+ break;
+
+ default: break;
+ }
+
+ return bFnd;
+}
+
+
+void TransferDataContainer::CopyINetBookmark( const INetBookmark& rBkmk )
+{
+ pImpl->moBookmk = 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() ||
+ pImpl->moBookmk.has_value();
+}
+
+
+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
+ {
+#ifdef IOS
+ if (false)
+ ;
+#else
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ xClipboard = css::datatransfer::clipboard::LokClipboard::create(
+ comphelper::getProcessComponentContext());
+ }
+#endif
+ else
+ {
+ 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 0000000000..9d1aa62bd2
--- /dev/null
+++ b/vcl/source/treelist/treelist.cxx
@@ -0,0 +1,1509 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <cassert>
+#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
+{
+ 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 );
+}
+
+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)
+ {
+ const auto iter = m_DataTable.find(pParent);
+ assert(iter != m_DataTable.end());
+ SvViewDataEntry* pViewData = iter->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 )
+{
+ assert(pEntry && "Remove:No Entry");
+ const auto iter = m_DataTable.find(pEntry);
+ assert(iter != m_DataTable.end());
+ SvViewDataEntry* pViewData = iter->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)
+ {
+ SvDataTable::iterator itr = m_DataTable.find(pCurEntry);
+ assert(itr != m_DataTable.end() && "Entry not in Table");
+ pViewData = itr->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);
+ 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 );
+ 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
+{
+ const SvTreeListEntry* pParent = pEntry->pParent;
+ if (pParent == pRootItem.get())
+ pParent = nullptr;
+ return pParent;
+}
+
+SvTreeListEntry* SvTreeList::GetParent( SvTreeListEntry* pEntry )
+{
+ 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 0000000000..6dd915e239
--- /dev/null
+++ b/vcl/source/treelist/treelistbox.cxx
@@ -0,0 +1,3589 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/help.hxx>
+#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 <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();
+ const VclPtr<Edit> & GetEditWidget() const { return pEdit; };
+};
+
+// ***************************************************************
+
+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),
+ 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 );
+ return nInsPos;
+}
+
+sal_uInt32 SvTreeListBox::Insert( SvTreeListEntry* pEntry,sal_uInt32 nRootPos )
+{
+ sal_uInt32 nInsPos = pModel->Insert( pEntry, nRootPos );
+ 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
+// ******************************************************************
+
+VclPtr<Edit> SvTreeListBox::GetEditWidget() const
+{
+ return pEdCtrl ? pEdCtrl->GetEditWidget() : nullptr;
+}
+
+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 );
+ // add an indent if the context bitmap can't be centered without touching the expander
+ if (nContextBmpWidthMax > nIndent + (nNodeWidthPixel / 2))
+ nStartPos += nIndent;
+ 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();
+ }
+ 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();
+ }
+
+ // #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 && pItemCheckBox->isEnable() && 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 );
+}
+
+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 ) )
+ {
+ 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 auto nMaxContextBmpWidthBeforeIndentIsNeeded =
+ nIndent + GetExpandedNodeBmp().GetSizePixel().Width() / 2;
+ const bool bHasButtonsAtRoot = nWindowStyle & WB_HASBUTTONSATROOT;
+
+ 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);
+
+ // add an indent if the context bitmap can't be centered without touching the expander
+ if (nCurTab == 0 && !(nTreeFlags & SvTreeFlags::CHKBTN) && bHasButtonsAtRoot &&
+ pTab->nFlags & SvLBoxTabFlags::ADJUST_CENTER &&
+ !(pTab->nFlags & SvLBoxTabFlags::FORCE) &&
+ aSize.Width() > nMaxContextBmpWidthBeforeIndentIsNeeded)
+ nX += nIndent;
+
+ 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);
+ }
+ }
+
+ // 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::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::ImplInvalidate( const vcl::Region* pRegion, 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::ImplInvalidate( pRegion, nInvalidateFlags );
+ pImpl->Invalidate();
+}
+
+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())
+ {
+ const Point pos(ScreenToOutputPixel(rHEvt.GetMousePosPixel()));
+ if (SvTreeListEntry* entry = GetEntry(pos))
+ {
+ const OUString tooltip = aTooltipHdl.Call(entry);
+ if (!tooltip.isEmpty())
+ {
+ const Size size(GetOutputSizePixel().Width(), GetEntryHeight());
+ tools::Rectangle screenRect(OutputToScreenPixel(GetEntryPosition(entry)), size);
+ Help::ShowQuickHelp(this, screenRect, tooltip);
+ 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::XVclWindowPeer > xHoldAlive(GetComponentInterface());
+ xAccessible = pImpl->m_aFactoryAccess.getFactory().createAccessibleTreeListBox( *this, xAccParent );
+ }
+ }
+ return xAccessible;
+}
+
+void SvTreeListBox::FillAccessibleEntryStateSet( SvTreeListEntry* pEntry, sal_Int64& rStateSet ) const
+{
+ assert(pEntry && "SvTreeListBox::FillAccessibleEntryStateSet: invalid entry");
+
+ if ( pEntry->HasChildrenOnDemand() || pEntry->HasChildren() )
+ {
+ rStateSet |= AccessibleStateType::EXPANDABLE;
+ if ( IsExpanded( pEntry ) )
+ rStateSet |= AccessibleStateType::EXPANDED;
+ }
+
+ if (nTreeFlags & SvTreeFlags::CHKBTN)
+ rStateSet |= AccessibleStateType::CHECKABLE;
+ if ( GetCheckButtonState( pEntry ) == SvButtonState::Checked )
+ rStateSet |= AccessibleStateType::CHECKED;
+ if ( IsEntryVisible( pEntry ) )
+ rStateSet |= AccessibleStateType::VISIBLE;
+ if ( IsSelected( pEntry ) )
+ rStateSet |= AccessibleStateType::SELECTED;
+ if ( IsEnabled() )
+ {
+ rStateSet |= AccessibleStateType::ENABLED;
+ rStateSet |= AccessibleStateType::FOCUSABLE;
+ rStateSet |= AccessibleStateType::SELECTABLE;
+ SvViewDataEntry* pViewDataNewCur = GetViewDataEntry(pEntry);
+ if (pViewDataNewCur && pViewDataNewCur->HasFocus())
+ rStateSet |= 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 OUString &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 0000000000..59f9680d00
--- /dev/null
+++ b/vcl/source/treelist/treelistentry.cxx
@@ -0,0 +1,234 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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)
+{
+}
+
+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 0000000000..ca45d76fa9
--- /dev/null
+++ b/vcl/source/treelist/uiobject.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/.
+ */
+
+#include <memory>
+
+#include <vcl/toolkit/edit.hxx>
+#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 if (auto const pEdit = mxTreeList->GetEditWidget())
+ {
+ std::unique_ptr<UIObject>(new EditUIObject(pEdit))->execute(rAction, rParameters);
+ }
+ 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, {nID}));
+ }
+ else if (nID == -1) // FIXME hack?
+ {
+ if (auto const pEdit = mxTreeList->GetEditWidget())
+ {
+ return std::unique_ptr<UIObject>(new EditUIObject(pEdit));
+ }
+ }
+
+ 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, std::vector<sal_Int32> nTreePath):
+ mxTreeList(xTreeList),
+ maTreePath(std::move(nTreePath))
+{
+}
+
+SvTreeListEntry* TreeListEntryUIObject::getEntry() const
+{
+ SvTreeListEntry* pEntry = nullptr;
+ for (sal_Int32 nID : maTreePath)
+ {
+ pEntry = mxTreeList->GetEntry(pEntry, nID);
+ if (!pEntry)
+ throw css::uno::RuntimeException("Could not find child with id: " + OUString::number(nID));
+ }
+ return pEntry;
+}
+
+StringMap TreeListEntryUIObject::get_state()
+{
+ SvTreeListEntry* pEntry = getEntry();
+
+ StringMap aMap;
+
+ aMap["Text"] = mxTreeList->GetEntryText(pEntry);
+ aMap["Children"] = OUString::number(mxTreeList->GetLevelChildCount(pEntry));
+ aMap["VisibleChildCount"] = OUString::number(mxTreeList->GetVisibleChildCount(pEntry));
+ aMap["IsSelected"] = OUString::boolean(mxTreeList->IsSelected(pEntry));
+
+ aMap["IsSemiTransparent"] = OUString::boolean(bool(pEntry->GetFlags() & SvTLEntryFlags::SEMITRANSPARENT));
+
+ SvLBoxButton* pItem = static_cast<SvLBoxButton*>(pEntry->GetFirstItem(SvLBoxItemType::Button));
+ if (pItem)
+ aMap["IsChecked"] = OUString::boolean(pItem->IsStateChecked());
+
+ return aMap;
+}
+
+void TreeListEntryUIObject::execute(const OUString& rAction, const StringMap& /*rParameters*/)
+{
+ SvTreeListEntry* pEntry = getEntry();
+
+ if (rAction == "COLLAPSE")
+ {
+ mxTreeList->Collapse(pEntry);
+ }
+ else if (rAction == "EXPAND")
+ {
+ mxTreeList->Expand(pEntry);
+ }
+ else if (rAction == "SELECT")
+ {
+ mxTreeList->Select(pEntry);
+ }
+ else if (rAction == "DESELECT")
+ {
+ mxTreeList->Select(pEntry, false);
+ }
+ else if (rAction == "CLICK")
+ {
+ SvLBoxButton* pItem = static_cast<SvLBoxButton*>(pEntry->GetFirstItem(SvLBoxItemType::Button));
+ if (!pItem)
+ return;
+ pItem->ClickHdl(pEntry);
+ }
+ else if (rAction == "DOUBLECLICK")
+ {
+ mxTreeList->SetCurEntry(pEntry);
+ mxTreeList->DoubleClickHdl();
+ }
+}
+
+std::unique_ptr<UIObject> TreeListEntryUIObject::get_child(const OUString& rID)
+{
+ SvTreeListEntry* pParentEntry = getEntry();
+
+ sal_Int32 nID = rID.toInt32();
+ if (nID >= 0)
+ {
+ SvTreeListEntry* pEntry = mxTreeList->GetEntry(pParentEntry, nID);
+ if (!pEntry)
+ return nullptr;
+
+ std::vector<sal_Int32> aChildTreePath(maTreePath);
+ aChildTreePath.push_back(nID);
+ return std::unique_ptr<UIObject>(new TreeListEntryUIObject(mxTreeList, std::move(aChildTreePath)));
+ }
+
+ return nullptr;
+}
+
+std::set<OUString> TreeListEntryUIObject::get_children() const
+{
+ SvTreeListEntry* pEntry = getEntry();
+
+ std::set<OUString> aChildren;
+
+ size_t nChildren = mxTreeList->GetLevelChildCount(pEntry);
+ 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 0000000000..b14d479310
--- /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 0000000000..29520fd613
--- /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 0000000000..d5e1b6f3eb
--- /dev/null
+++ b/vcl/source/uitest/uiobject.cxx
@@ -0,0 +1,1864 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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(std::u16string_view rStr)
+{
+ std::vector<KeyEvent> aEvents;
+ vcl::KeyCode aCode;
+ for (size_t i = 0, n = rStr.size(); 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, OUStringBuffer* debug = nullptr)
+{
+ 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;
+
+ if (debug)
+ debug->append(pChild->get_id() + " ");
+
+ vcl::Window* pResult = findChild(pChild, rID, bRequireVisible, debug);
+ 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
+ OUStringBuffer debug;
+ vcl::Window* pWindow = findChild(mxWindow.get(), rID, false, &debug);
+ if (!pWindow)
+ {
+ vcl::Window* pDialogParent = get_top_parent(mxWindow.get());
+ pWindow = findChild(pDialogParent, rID, false, &debug);
+ }
+
+ if (!pWindow)
+ throw css::uno::RuntimeException("Could not find child with id: " + rID + " children were " + std::u16string_view(debug));
+
+ 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());
+ aMap["Enabled"] = OUString::boolean(mxRadioButton->IsEnabled());
+
+ 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();
+ aMap["EntryCount"] = OUString::number(mxComboBox->GetEntryCount());
+ 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();
+ OUString 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());
+
+ OUString 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"] = 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");
+ assert(itr != rParameters.end());
+ 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 0000000000..f52e636f50
--- /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 0000000000..1a59520f19
--- /dev/null
+++ b/vcl/source/uitest/uno/uiobject_uno.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 <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/scheduler.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):
+ mpObj(std::move(pObj))
+{
+ assert(mpObj);
+}
+
+UIObjectUnoObj::~UIObjectUnoObj()
+{
+ SolarMutexGuard aGuard;
+ mpObj.reset();
+}
+
+css::uno::Reference<css::ui::test::XUIObject> SAL_CALL UIObjectUnoObj::getChild(const OUString& rID)
+{
+ SolarMutexGuard aGuard;
+ std::unique_ptr<UIObject> pObj = mpObj->get_child(rID);
+ SAL_WARN_IF(!pObj, "vcl", "child " << rID << " of parent " << mpObj->dumpState() << " does not exist");
+ 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)
+{
+ 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();
+ Scheduler::ProcessEventsToIdle();
+}
+
+css::uno::Sequence<css::beans::PropertyValue> UIObjectUnoObj::getState()
+{
+ 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()
+{
+ 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()
+{
+ 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()
+{
+ 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 0000000000..e86ce1bfd2
--- /dev/null
+++ b/vcl/source/uitest/uno/uiobject_uno.hxx
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <comphelper/compbase.hxx>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/ui/test/XUIObject.hpp>
+
+#include <memory>
+
+#include <vcl/uitest/uiobject.hxx>
+
+typedef ::comphelper::WeakComponentImplHelper <
+ css::ui::test::XUIObject, css::lang::XServiceInfo
+ > UIObjectBase;
+
+class UIObjectUnoObj : 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 0000000000..9886b049a4
--- /dev/null
+++ b/vcl/source/uitest/uno/uitest_uno.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 <comphelper/compbase.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 ::comphelper::WeakComponentImplHelper <
+ css::ui::test::XUITest, css::lang::XServiceInfo
+ > UITestBase;
+
+class UITestUnoObj : public UITestBase
+{
+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()
+{
+}
+
+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/DocWindow.cxx b/vcl/source/window/DocWindow.cxx
new file mode 100644
index 0000000000..fe2ce61dfb
--- /dev/null
+++ b/vcl/source/window/DocWindow.cxx
@@ -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/.
+ */
+
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+
+#include <sal/log.hxx>
+#include <vcl/DocWindow.hxx>
+#include <vcl/ITiledRenderable.hxx>
+
+namespace vcl
+{
+void DocWindow::SetPointer(PointerStyle nPointer)
+{
+ Window::SetPointer(nPointer);
+
+ 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"_ostr;
+ auto aIt = vcl::gaLOKPointerMap.find(aPointer);
+ if (aIt != vcl::gaLOKPointerMap.end())
+ {
+ aPointerString = aIt->second;
+ }
+
+ pWin->GetLOKNotifier()->libreOfficeKitViewCallback(LOK_CALLBACK_MOUSE_POINTER, aPointerString);
+}
+} // namespace vcl
+
+/* 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 0000000000..ffe0655682
--- /dev/null
+++ b/vcl/source/window/EnumContext.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/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() const
+{
+ return meApplication;
+}
+
+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 0000000000..d8cdfebf85
--- /dev/null
+++ b/vcl/source/window/NotebookBarAddonsMerger.cxx
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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->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 OUString& 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 0000000000..28055f7e21
--- /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 0000000000..5bacc91b37
--- /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<OUString> VclAbstractDialog::getAllPageUIXMLDescriptions() const
+{
+ // default has no pages
+ return {};
+}
+
+bool VclAbstractDialog::selectPageByUIXMLDescription(const OUString& /*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 0000000000..a20c289f20
--- /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 0000000000..3c6103ac31
--- /dev/null
+++ b/vcl/source/window/accessibility.cxx
@@ -0,0 +1,594 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/mnemonic.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/XVclWindowPeer.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;
+
+ return IsNativeFrame();
+}
+
+vcl::Window* Window::GetAccessibleParentWindow() const
+{
+ if (!mpWindowImpl || IsNativeFrame())
+ 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 (IsNativeFrame() )
+ 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 removeMnemonicFromString( 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>();
+
+ sal_Int64 nState = xContext->getAccessibleStateSet();
+ if (nState & accessibility::AccessibleStateType::FOCUSED)
+ {
+ uno::Reference<accessibility::XAccessibleEditableText> xText(xContext, uno::UNO_QUERY);
+ if (xText.is())
+ return xText;
+ if (nState & accessibility::AccessibleStateType::MANAGES_DESCENDANTS)
+ return uno::Reference<accessibility::XAccessibleEditableText>();
+ }
+
+ bool bSafeToIterate = true;
+ sal_Int64 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_Int64 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 0000000000..26ea9846e8
--- /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 0000000000..e7b5693640
--- /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;
+ vcl::Window *pSubEdit = 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;
+ pSubEdit = static_cast<Edit*>(pCtrl)->GetSubEdit();
+ }
+ 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;
+ pSubEdit = static_cast<Edit*>(pCtrl)->GetSubEdit();
+ }
+ 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;
+ pSubEdit = static_cast<Edit*>(pCtrl)->GetSubEdit();
+ 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);
+ if (pSubEdit)
+ pSubEdit->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() || pCtrl->HasFocus() || pCtrl->HasChildPathFocus())
+ nState |= ControlState::FOCUSED;
+
+ bool bMouseOver = pCtrl->IsMouseOver();
+ if (!bMouseOver)
+ {
+ 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
+ const bool bShowJunctionToLauncher = !(pData->mnTitleType & (BorderWindowTitleType::Normal | BorderWindowTitleType::Small));
+ if (bShowJunctionToLauncher && !ImplGetSVData()->maNWFData.mbNoFrameJunctionForPopups)
+ {
+ FloatingWindow* pWin = dynamic_cast<FloatingWindow*>(pData->mpBorderWindow->GetWindow(GetWindowType::Client));
+ if (pWin)
+ {
+ vcl::Region aClipRgn(aInRect);
+ AbsoluteScreenPixelRectangle aItemClipRect(pWin->ImplGetItemEdgeClipRect());
+ if (!aItemClipRect.IsEmpty())
+ {
+ tools::Rectangle aTmp(pData->mpBorderWindow->AbsoluteScreenToOutputPixel(aItemClipRect.TopLeft()), aItemClipRect.GetSize());
+ aClipRgn.Exclude(aTmp);
+ 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 )
+ {
+ mpWindowImpl->mbOverlapWin = true;
+ mpWindowImpl->mbFrame = true;
+
+ if( nStyle & WB_SYSTEMCHILDWINDOW )
+ {
+ mbFrameBorder = false;
+ }
+ else if( nStyle & (WB_OWNERDRAWDECORATION | WB_POPUP) )
+ {
+ mbFrameBorder = (nOrgStyle & WB_NOBORDER) == 0;
+ }
+ else
+ {
+ 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 0000000000..aa9e51dfa3
--- /dev/null
+++ b/vcl/source/window/bubblewindow.cxx
@@ -0,0 +1,591 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <utility>
+#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, OUString aTitle,
+ OUString aText, Image aImage )
+ : FloatingWindow( pParent, WB_SYSTEMWINDOW
+ | WB_OWNERDRAWDECORATION
+ | WB_NOBORDER
+ )
+ , maBubbleTitle(std::move( aTitle ))
+ , maBubbleText(std::move( aText ))
+ , maBubbleImage(std::move( aImage ))
+ , 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() );
+ AbsoluteScreenPixelPoint 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")
+ , 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);
+}
+
+sal_uInt16 MenuBarUpdateIconManager::GetIconID(MenuBar* pMenuBar) const
+{
+ auto aI = std::find(maIconMBars.begin(), maIconMBars.end(), pMenuBar);
+ if (aI == maIconMBars.end())
+ return 0;
+ return maIconIDs[std::distance(maIconMBars.begin(), aI)];
+}
+
+VclPtr<BubbleWindow> MenuBarUpdateIconManager::GetBubbleWindow()
+{
+ if (!mpActiveSysWin)
+ return nullptr;
+
+ tools::Rectangle aIconRect = mpActiveMBar->GetMenuBarButtonRectPixel(GetIconID(mpActiveMBar));
+ if( aIconRect.IsEmpty() )
+ return nullptr;
+
+ auto pBubbleWin = mpBubbleWin;
+
+ if ( !pBubbleWin ) {
+ pBubbleWin = VclPtr<BubbleWindow>::Create( mpActiveSysWin, 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();
+}
+
+IMPL_LINK(MenuBarUpdateIconManager, WindowEventHdl, VclWindowEvent&, rEvent, void)
+{
+ VclEventId nEventID = rEvent.GetId();
+
+ if ( VclEventId::ObjectDying == nEventID )
+ {
+ if (mpActiveSysWin == rEvent.GetWindow())
+ {
+ RemoveBubbleWindow();
+ mpActiveSysWin = nullptr;
+ mpActiveMBar = nullptr;
+ }
+ }
+ 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)
+ {
+ if (pMBar == mpActiveMBar)
+ {
+ RemoveBubbleWindow();
+ mpActiveMBar = nullptr;
+ }
+ RemoveMenuBarIcon(pMBar);
+ }
+ }
+ else if ( ( nEventID == VclEventId::WindowMove ) ||
+ ( nEventID == VclEventId::WindowResize ) )
+ {
+ if ( mpActiveSysWin == rEvent.GetWindow() &&
+ mpBubbleWin && mpActiveMBar )
+ {
+ tools::Rectangle aIconRect = mpActiveMBar->GetMenuBarButtonRectPixel(GetIconID(mpActiveMBar));
+ 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();
+
+ return false;
+}
+
+IMPL_LINK_NOARG(MenuBarUpdateIconManager, WaitTimeOutHdl, Timer *, void)
+{
+ mpBubbleWin = GetBubbleWindow();
+
+ if ( mpBubbleWin )
+ {
+ mpBubbleWin->Show();
+ }
+}
+
+MenuBarUpdateIconManager::~MenuBarUpdateIconManager()
+{
+ Application::RemoveEventListener( maApplicationEventHdl );
+
+ RemoveBubbleWindow();
+ RemoveMenuBarIcons();
+}
+
+void MenuBarUpdateIconManager::RemoveMenuBarIcons()
+{
+ while (!maIconMBars.empty())
+ RemoveMenuBarIcon(maIconMBars[0]);
+}
+
+void MenuBarUpdateIconManager::SetShowMenuIcon(bool bShowMenuIcon)
+{
+ if ( bShowMenuIcon != mbShowMenuIcon )
+ {
+ mbShowMenuIcon = bShowMenuIcon;
+ if ( bShowMenuIcon )
+ Application::PostUserEvent(LINK(this, MenuBarUpdateIconManager, UserEventHdl));
+ else
+ {
+ RemoveBubbleWindow();
+ RemoveMenuBarIcons();
+ }
+ }
+}
+
+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& rSysWin, bool bAddEventHdl)
+{
+ if (!mbShowMenuIcon)
+ return;
+
+ MenuBar *pActiveMBar = rSysWin.GetMenuBar();
+ if (&rSysWin != mpActiveSysWin || pActiveMBar != mpActiveMBar)
+ RemoveBubbleWindow();
+
+ auto aI = std::find(maIconMBars.begin(), maIconMBars.end(), pActiveMBar);
+ if (aI == maIconMBars.end())
+ {
+ 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 );
+ sal_uInt16 nIconID = pActiveMBar->AddMenuBarButton( aImage,
+ LINK( this, MenuBarUpdateIconManager, ClickHdl ),
+ aBuf.makeStringAndClear() );
+ maIconMBars.push_back(pActiveMBar);
+ maIconIDs.push_back(nIconID);
+ }
+
+ if (bAddEventHdl)
+ rSysWin.AddEventListener( maWindowEventHdl );
+ }
+
+ if (mpActiveMBar != pActiveMBar)
+ {
+ if (mpActiveMBar)
+ {
+ mpActiveMBar->SetMenuBarButtonHighlightHdl(GetIconID(mpActiveMBar),
+ Link<MenuBarButtonCallbackArg&,bool>());
+ }
+ mpActiveMBar = pActiveMBar;
+ if (mpActiveMBar)
+ {
+ mpActiveMBar->SetMenuBarButtonHighlightHdl(GetIconID(mpActiveMBar),
+ LINK(this, MenuBarUpdateIconManager, HighlightHdl));
+ }
+ }
+
+ mpActiveSysWin = &rSysWin;
+
+ if (mbShowBubble && pActiveMBar)
+ {
+ mpBubbleWin = GetBubbleWindow();
+ if ( mpBubbleWin )
+ {
+ mpBubbleWin->Show();
+ maTimeoutTimer.Start();
+ }
+ mbShowBubble = false;
+ }
+}
+
+void MenuBarUpdateIconManager::RemoveMenuBarIcon(MenuBar* pMenuBar)
+{
+ auto aI = std::find(maIconMBars.begin(), maIconMBars.end(), pMenuBar);
+ if (aI == maIconMBars.end())
+ return;
+
+ auto aIconI = maIconIDs.begin() + std::distance(maIconMBars.begin(), aI);
+
+ try
+ {
+ pMenuBar->RemoveMenuBarButton(*aIconI);
+ }
+ catch (...)
+ {
+ }
+
+ maIconMBars.erase(aI);
+ maIconIDs.erase(aIconI);
+}
+
+void MenuBarUpdateIconManager::RemoveBubbleWindow()
+{
+ maWaitIdle.Stop();
+ maTimeoutTimer.Stop();
+ mpBubbleWin.disposeAndClear();
+}
+
+/* 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 0000000000..188fbb1acc
--- /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 0000000000..eafc829e59
--- /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 0000000000..bc307ba855
--- /dev/null
+++ b/vcl/source/window/builder.cxx
@@ -0,0 +1,4399 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <config_vclplug.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 <utility>
+#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/toolkit/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 <comphelper/diagnose_ex.hxx>
+#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,
+ const ILibreOfficeKitNotifier* pNotifier)
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ return JSInstanceBuilder::CreateMessageDialog(pParent, eMessageType, eButtonType, rPrimaryMessage, pNotifier);
+ 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"_ustr;
+ if (aSuffix != "\"" && aSuffix != sDoublePrime)
+ aStr += " ";
+ else
+ aSuffix = sDoublePrime;
+ }
+ else if (m_eSrcUnit == FieldUnit::FOOT)
+ {
+ OUString sPrime = u"\u2032"_ustr;
+ 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,
+ OUString sID, css::uno::Reference<css::frame::XFrame> xFrame,
+ bool bLegacy, const NotebookBarAddonsItem* pNotebookBarAddonsItem)
+ : m_pNotebookBarAddonsItem(pNotebookBarAddonsItem
+ ? new NotebookBarAddonsItem(*pNotebookBarAddonsItem)
+ : new NotebookBarAddonsItem{})
+ , m_sID(std::move(sID))
+ , m_sHelpRoot(sUIFile)
+ , m_pStringReplace(Translate::GetReadStringHook())
+ , m_pParent(pParent)
+ , m_bToplevelParentFound(false)
+ , m_bLegacy(bLegacy)
+ , m_pParserState(new ParserState)
+ , m_xFrame(std::move(xFrame))
+{
+ 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 += "/";
+
+ try
+ {
+ xmlreader::XmlReader reader(sUIDir + sUIFile);
+
+ 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);
+ 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& [ rType, rParam ] : rMap)
+ {
+ if (rType == "role")
+ {
+ sal_Int16 role = BuilderUtils::getRoleFromName(rParam);
+ if (role != com::sun::star::accessibility::AccessibleRole::UNKNOWN)
+ pSource->SetAccessibleRole(role);
+ }
+ else
+ {
+ vcl::Window *pTarget = get(rParam);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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& [ rKey, rValue ] : sizeGroup.m_aProperties)
+ xGroup->set_property(rKey, rValue);
+
+ for (auto const& elem : sizeGroup.m_aWidgets)
+ {
+ vcl::Window* pWindow = get(elem);
+ 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);
+ 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);
+ 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);
+ }
+
+ //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);
+ 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("pixbuf");
+ if (aFind != rMap.end())
+ {
+ sIconName = aFind->second;
+ rMap.erase(aFind);
+ }
+ }
+ {
+ VclBuilder::stringmap::iterator aFind = rMap.find("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("relief");
+ if (aFind != rMap.end())
+ {
+ assert(aFind->second != "half" && "relief of 'half' unsupported");
+ 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("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("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("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("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("tooltip-text");
+ if (aFind == rMap.end())
+ aFind = rMap.find("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("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("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("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("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("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, rFrame);
+ }
+
+ 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 OUString &id, stringmap &rMap)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find("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);
+ rMap.erase(aFind);
+ }
+}
+
+void VclBuilder::connectNumericFormatterAdjustment(const OUString &id, const OUString &rAdjustment)
+{
+ if (!rAdjustment.isEmpty())
+ m_pParserState->m_aNumericFormatterAdjustmentMaps.emplace_back(id, rAdjustment);
+}
+
+void VclBuilder::connectFormattedFormatterAdjustment(const OUString &id, const OUString &rAdjustment)
+{
+ if (!rAdjustment.isEmpty())
+ m_pParserState->m_aFormattedFormatterAdjustmentMaps.emplace_back(id, rAdjustment);
+}
+
+bool VclBuilder::extractAdjustmentToMap(const OUString& id, VclBuilder::stringmap& rMap, std::vector<WidgetAdjustmentMap>& rAdjustmentMap)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find("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("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("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("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("draw-indicator");
+ if (aFind != rMap.end())
+ {
+ bDrawIndicator = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bDrawIndicator;
+ }
+}
+
+void VclBuilder::extractModel(const OUString &id, stringmap &rMap)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find("model");
+ if (aFind != rMap.end())
+ {
+ m_pParserState->m_aModelMaps.emplace_back(id, aFind->second,
+ extractActive(rMap));
+ rMap.erase(aFind);
+ }
+}
+
+void VclBuilder::extractBuffer(const OUString &id, stringmap &rMap)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find("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("icon-size");
+ if (aFind != rMap.end())
+ nSize = aFind->second.toInt32();
+ return nSize;
+}
+
+void VclBuilder::extractButtonImage(const OUString &id, stringmap &rMap, bool bRadio)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find("image");
+ if (aFind != rMap.end())
+ {
+ m_pParserState->m_aButtonImageWidgetMaps.emplace_back(id, aFind->second, bRadio);
+ rMap.erase(aFind);
+ }
+}
+
+void VclBuilder::extractMnemonicWidget(const OUString &rLabelID, stringmap &rMap)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find("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["width-request"] = OUString::number(nWidthReq);
+ sal_Int32 nHeightReq = pScrollParent->get_height_request();
+ rMap["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
+
+// This ifdef branch is mainly for building for the Collabora Online
+// -based mobile apps for Android and iOS.
+
+extern "C" VclBuilder::customMakeWidget lo_get_custom_widget_func(const char* name);
+
+#elif defined EMSCRIPTEN && !ENABLE_QT5
+
+// This branch is mainly for building for WASM, and especially for
+// Collabora Online in the browser, where code from core and Collabora
+// Online is compiled to WASM and linked into a single WASM binary.
+// (Not for Allotropia's Qt-based LibreOffice in the browser.)
+
+// When building core for WASM it doesn't use the same
+// solenv/bin/native-code.py thing as the mobile apps, even if in both
+// cases everything is linked statically. So there is no generated
+// native-code.h, and we can't use lo_get_custom_widget_func() from
+// that. So cheat and duplicate the code from an existing generated
+// native-code.h. It's just a handful of lines anyway.
+
+extern "C" void makeNotebookbarTabControl(VclPtr<vcl::Window> &rRet, const VclPtr<vcl::Window> &pParent, VclBuilder::stringmap &rVec);
+extern "C" void makeNotebookbarToolBox(VclPtr<vcl::Window> &rRet, const VclPtr<vcl::Window> &pParent, VclBuilder::stringmap &rVec);
+
+static struct { const char *name; VclBuilder::customMakeWidget func; } custom_widgets[] = {
+ { "makeNotebookbarTabControl", makeNotebookbarTabControl },
+ { "makeNotebookbarToolBox", makeNotebookbarToolBox },
+};
+
+static VclBuilder::customMakeWidget lo_get_custom_widget_func(const char* name)
+{
+ for (size_t i = 0; i < sizeof(custom_widgets) / sizeof(custom_widgets[0]); i++)
+ if (strcmp(name, custom_widgets[i].name) == 0)
+ return custom_widgets[i].func;
+ return nullptr;
+}
+
+#endif
+
+namespace
+{
+// Takes a string like "sfxlo-NotebookbarToolBox"
+VclBuilder::customMakeWidget GetCustomMakeWidget(const OUString& rName)
+{
+ const OUString name = rName == "sfxlo-SidebarToolBox" ? "sfxlo-NotebookbarToolBox" : rName;
+ VclBuilder::customMakeWidget pFunction = nullptr;
+ if (sal_Int32 nDelim = name.indexOf('-'); nDelim != -1)
+ {
+ const OUString sFunction(OUString::Concat("make") + name.subView(nDelim + 1));
+
+#ifndef DISABLE_DYNLOADING
+ const OUString sModule = OUString::Concat(SAL_DLLPREFIX)
+ + name.subView(0, nDelim)
+ + 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, OUStringToOString(sFunction, RTL_TEXTENCODING_UTF8).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 || (defined EMSCRIPTEN && !ENABLE_QT5)
+ // This ifdef branch is mainly for building for either the
+ // Android or iOS apps, or the Collabora Online as WASM thing.
+ 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 OUString &name, const OUString &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
+ OUString sTabPageId = get_by_window(pParent) +
+ "-page" +
+ OUString::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);
+
+ if (name == "GtkStatusbar")
+ xWindow->SetAccessibleRole(css::accessibility::AccessibleRole::STATUS_BAR);
+ }
+ 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("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("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);
+ OUString 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
+ OUString 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, ProgressBar::BarStyle::Progress);
+ }
+ else if (name == "GtkLevelBar")
+ {
+ extractAdjustmentToMap(id, rMap, m_pParserState->m_aScrollAdjustmentMaps);
+ bVertical = extractOrientation(rMap);
+ xWindow = VclPtr<ProgressBar>::Create(pParent, bVertical ? WB_VERT : WB_HORZ, ProgressBar::BarStyle::Level);
+ }
+ 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!
+ if (aCommand.isEmpty() && !m_bLegacy)
+ aCommand = id;
+ pToolBox->InsertItem(nItemId, extractLabel(rMap), aCommand, nBits);
+ }
+
+ 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& [rKey, rValue] : rProps)
+ 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("customproperty");
+ if (aFind != rMap.end())
+ {
+ sCustomProperty = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sCustomProperty;
+ }
+
+ void ensureDefaultWidthChars(VclBuilder::stringmap &rMap)
+ {
+ OUString 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("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 OUString& roleName)
+ {
+ using namespace com::sun::star::accessibility;
+
+ static const std::unordered_map<OUString, 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::NOTIFICATION },
+ { "info bar", AccessibleRole::UNKNOWN },
+ { "level bar", AccessibleRole::UNKNOWN },
+ { "title bar", AccessibleRole::UNKNOWN },
+ { "block quote", AccessibleRole::BLOCK_QUOTE },
+ { "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 OUString &rClass,
+ const OUString &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(rID);
+ 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("size");
+ if (aSize != rPango.end())
+ {
+ pCurrentChild->set_font_attribute(aSize->first, aSize->second);
+ rPango.erase(aSize);
+ }
+ for (auto const& [ rKey, rValue ] : rPango)
+ 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<OUString> 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);
+ OUString sID(name.begin, name.length, RTL_TEXTENCODING_UTF8);
+ sal_Int32 nDelim = sID.indexOf(':');
+ if (nDelim != -1)
+ {
+ aProperties["customproperty"] = sID.copy(nDelim + 1);
+ 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("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& [ rKey, rValue ] : aAtkProperties)
+ {
+ 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;
+
+ OUString sProperty;
+ OUString sValue;
+
+ while (reader.nextAttribute(&nsId, &span))
+ {
+ if (span == "name")
+ {
+ span = reader.getAttributeValue(false);
+ sProperty = OUString(span.begin, span.length, RTL_TEXTENCODING_UTF8);
+ }
+ else if (span == "value")
+ {
+ span = reader.getAttributeValue(false);
+ sValue = OUString(span.begin, span.length, RTL_TEXTENCODING_UTF8);
+ }
+ }
+
+ if (!sProperty.isEmpty())
+ rMap[sProperty] = sValue;
+}
+
+void VclBuilder::collectAtkRelationAttribute(xmlreader::XmlReader &reader, stringmap &rMap)
+{
+ xmlreader::Span span;
+ int nsId;
+
+ OUString sProperty;
+ OUString sValue;
+
+ while (reader.nextAttribute(&nsId, &span))
+ {
+ if (span == "type")
+ {
+ span = reader.getAttributeValue(false);
+ sProperty = OUString(span.begin, span.length, RTL_TEXTENCODING_UTF8);
+ }
+ else if (span == "target")
+ {
+ span = reader.getAttributeValue(false);
+ sValue = OUString(span.begin, span.length, RTL_TEXTENCODING_UTF8);
+ sal_Int32 nDelim = sValue.indexOf(':');
+ if (nDelim != -1)
+ sValue = sValue.copy(0, nDelim);
+ }
+ }
+
+ if (!sProperty.isEmpty())
+ rMap[sProperty] = sValue;
+}
+
+void VclBuilder::collectAtkRoleAttribute(xmlreader::XmlReader &reader, stringmap &rMap)
+{
+ xmlreader::Span span;
+ int nsId;
+
+ OUString sProperty;
+
+ while (reader.nextAttribute(&nsId, &span))
+ {
+ if (span == "type")
+ {
+ span = reader.getAttributeValue(false);
+ sProperty = OUString(span.begin, span.length, RTL_TEXTENCODING_UTF8);
+ }
+ }
+
+ if (!sProperty.isEmpty())
+ rMap["role"] = sProperty;
+}
+
+void VclBuilder::handleRow(xmlreader::XmlReader &reader, const OUString &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 OUString &rID, std::u16string_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 != u"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& [ rKey, rValue ] : rProperties)
+ {
+ 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;
+ OUString 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 = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
+ }
+ }
+
+ (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 OUString &rID, bool bMenuBar)
+{
+ VclPtr<Menu> pCurrentMenu;
+ if (bMenuBar)
+ pCurrentMenu = VclPtr<MenuBar>::Create();
+ else
+ pCurrentMenu = VclPtr<PopupMenu>::Create();
+
+ pCurrentMenu->set_id(rID);
+
+ 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)
+{
+ OUString sClass;
+ OUString sID;
+ OUString sCustomProperty;
+ PopupMenu *pSubMenu = nullptr;
+
+ xmlreader::Span name;
+ int nsId;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "class")
+ {
+ name = reader.getAttributeValue(false);
+ sClass = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
+ }
+ else if (name == "id")
+ {
+ name = reader.getAttributeValue(false);
+ sID = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
+ if (m_bLegacy)
+ {
+ sal_Int32 nDelim = sID.indexOf(':');
+ if (nDelim != -1)
+ {
+ sCustomProperty = sID.subView(nDelim+1);
+ sID = sID.copy(0, nDelim);
+ }
+ }
+ }
+ }
+
+ int nLevel = 1;
+
+ stringmap aProperties;
+ stringmap aAtkProperties;
+ accelmap aAccelerators;
+
+ if (!sCustomProperty.isEmpty())
+ aProperties["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);
+ OUString sWidget(name.begin, name.length, RTL_TEXTENCODING_UTF8);
+ 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<OUString,OUString> &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);
+ sal_Unicode 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 OUString &rClass, const OUString &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& [ rKey, rValue ] : rProps)
+ {
+ 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& [ rKey, rValue ] : rAtkProps)
+ {
+ 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& [ rSignal, rValue ] : rAccels)
+ {
+ 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>(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)
+{
+ OUString sClass;
+ OUString sID;
+ OUString sCustomProperty;
+
+ xmlreader::Span name;
+ int nsId;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "class")
+ {
+ name = reader.getAttributeValue(false);
+ sClass = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
+ }
+ else if (name == "id")
+ {
+ name = reader.getAttributeValue(false);
+ sID = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
+ if (m_bLegacy)
+ {
+ sal_Int32 nDelim = sID.indexOf(':');
+ if (nDelim != -1)
+ {
+ sCustomProperty = 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["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")
+ {
+ OUString classStyle = getStyleClass(reader);
+ OUString rest;
+
+ if (classStyle.startsWith("context-", &rest))
+ {
+ aContext.push_back(vcl::EnumContext::GetContextEnum(rest));
+ }
+ else if (classStyle.startsWith("priority-", &rest))
+ {
+ nPriority = rest.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;
+}
+
+OUString VclBuilder::getStyleClass(xmlreader::XmlReader &reader)
+{
+ xmlreader::Span name;
+ int nsId;
+ OUString aRet;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "name")
+ {
+ name = reader.getAttributeValue(false);
+ aRet = OUString (name.begin, name.length, RTL_TEXTENCODING_UTF8);
+ }
+ }
+
+ return aRet;
+}
+
+void VclBuilder::collectProperty(xmlreader::XmlReader &reader, stringmap &rMap) const
+{
+ xmlreader::Span name;
+ int nsId;
+
+ OUString sProperty;
+ OString sContext;
+
+ bool bTranslated = false;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "name")
+ {
+ name = reader.getAttributeValue(false);
+ sProperty = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
+ }
+ 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);
+ OUString sID(name.begin, name.length, RTL_TEXTENCODING_UTF8);
+ 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;
+
+ OUString sProperty;
+ OUString sValue;
+ OUString sModifiers;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "key")
+ {
+ name = reader.getAttributeValue(false);
+ sValue = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
+ }
+ else if (name == "signal")
+ {
+ name = reader.getAttributeValue(false);
+ sProperty = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
+ }
+ else if (name == "modifiers")
+ {
+ name = reader.getAttributeValue(false);
+ sModifiers = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
+ }
+ }
+
+ 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::u16string_view sID)
+{
+ for (auto const& child : m_aChildren)
+ {
+ if (child.m_sID == sID)
+ return child.m_pWindow;
+ }
+
+ return nullptr;
+}
+
+PopupMenu *VclBuilder::get_menu(std::u16string_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::u16string_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 OUString& 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);
+}
+
+OUString 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 {};
+}
+
+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 OUString& sID) const
+{
+ const auto 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 OUString& sID) const
+{
+ const auto 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 OUString& sID) const
+{
+ const auto 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& [ rKey, rValue ] : rAdjustment)
+ {
+ 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& [ rKey, rValue ] : rAdjustment)
+ {
+ 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& [ rKey, rValue ] : rAdjustment)
+ {
+ 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& [ rKey, rValue ] : rAdjustment)
+ {
+ 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& [ rKey, rValue ] : rTextBuffer)
+ {
+ 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(OUString aId, Menu *pMenu)
+ : m_sID(std::move(aId))
+ , 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 0000000000..f55283cff8
--- /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 0000000000..83c21826e7
--- /dev/null
+++ b/vcl/source/window/commandevent.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 <string.h>
+
+#include <utility>
+#include <vcl/commandevent.hxx>
+
+CommandExtTextInputData::CommandExtTextInputData( OUString aText,
+ const ExtTextInputAttr* pTextAttr, sal_Int32 nCursorPos, sal_uInt16 nCursorFlags,
+ bool bOnlyCursor)
+ : maText(std::move(aText))
+{
+ 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 CommandGestureSwipeData* CommandEvent::GetGestureSwipeData() const
+{
+ if( mnCommand == CommandEventId::GestureSwipe )
+ return static_cast<const CommandGestureSwipeData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandGestureLongPressData* CommandEvent::GetLongPressData() const
+{
+ if( mnCommand == CommandEventId::GestureLongPress )
+ return static_cast<const CommandGestureLongPressData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandGesturePanData* CommandEvent::GetGesturePanData() const
+{
+ if (mnCommand == CommandEventId::GesturePan)
+ return static_cast<const CommandGesturePanData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandGestureZoomData* CommandEvent::GetGestureZoomData() const
+{
+ if (mnCommand == CommandEventId::GestureZoom)
+ return static_cast<const CommandGestureZoomData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandGestureRotateData* CommandEvent::GetGestureRotateData() const
+{
+ if (mnCommand == CommandEventId::GestureRotate)
+ return static_cast<const CommandGestureRotateData*>(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 0000000000..d160e8aa73
--- /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.getOpenHeight() * 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 0000000000..3464569238
--- /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 0000000000..f9f6978f0e
--- /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 0000000000..2067a9e4ca
--- /dev/null
+++ b/vcl/source/window/decoview.cxx
@@ -0,0 +1,1020 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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>
+
+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
+ {
+ const bool bFlat(nStyle & DrawButtonFlags::Flat);
+ const bool bDepressed(nStyle & (DrawButtonFlags::Pressed | DrawButtonFlags::Checked));
+
+ 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 );
+ }
+
+ bool bNoFace = false;
+ Color aColor1;
+ Color aColor2;
+ if (!bFlat)
+ {
+ if (bDepressed)
+ {
+ aColor1 = rStyleSettings.GetDarkShadowColor();
+ aColor2 = rStyleSettings.GetLightColor();
+ }
+ else
+ {
+ if ( nStyle & DrawButtonFlags::NoLightBorder )
+ aColor1 = rStyleSettings.GetLightBorderColor();
+ else
+ aColor1 = rStyleSettings.GetLightColor();
+ aColor2 = rStyleSettings.GetDarkShadowColor();
+ }
+
+ ImplDraw2ColorFrame( pDev, aFillRect, aColor1, aColor2 );
+
+ if (bDepressed)
+ {
+ 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 );
+ }
+ else // flat buttons
+ {
+ // draw a border if the flat button is highlighted
+ if (nStyle & DrawButtonFlags::Highlight)
+ {
+ aColor1 = rStyleSettings.GetShadowColor();
+ ImplDraw2ColorFrame(pDev, aFillRect, aColor1, aColor1);
+ }
+ // fill in the button if it is pressed in
+ bNoFace = !bDepressed;
+ }
+
+ pDev->SetLineColor();
+ if ( nStyle & (DrawButtonFlags::Checked | DrawButtonFlags::DontKnow) )
+ pDev->SetFillColor( rStyleSettings.GetCheckedColor() );
+ else if (!bNoFace)
+ 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 )
+ std::swap( aLightColor, aShadowColor );
+
+ DrawFrame( rRect, aLightColor, aShadowColor );
+}
+
+tools::Rectangle DecorationView::DrawFrame( const tools::Rectangle& rRect, DrawFrameStyle nStyle, DrawFrameFlags nFlags )
+{
+ tools::Rectangle aRect = mpOutDev->LogicToPixel( rRect );
+ bool bOldMap = mpOutDev->IsMapModeEnabled();
+ 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 );
+ }
+ }
+
+ 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 = mpOutDev->LogicToPixel( rRect );
+ const bool bOldMap = mpOutDev->IsMapModeEnabled();
+ 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 );
+ }
+
+ 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.GetSeparatorColor() );
+
+ 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 0000000000..83a8e8baba
--- /dev/null
+++ b/vcl/source/window/dialog.cxx
@@ -0,0 +1,1736 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <salinst.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, const OUString& rID, const OUString& rUIXMLDescription)
+ : SystemWindow(WindowType::DIALOG, "vcl::Dialog maLayoutIdle")
+ , mnInitFlag(InitFlag::Default)
+{
+ ImplLOKNotifier(pParent);
+ ImplInitDialogData();
+ loadUI(pParent, rID, 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() == NotifyEventType::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() == NotifyEventType::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());
+ aItems.emplace_back("unique_id", this->get_id().toUtf8());
+ 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())
+ {
+#ifdef IOS
+ // gh#5908 handle pasting disallowed clipboard contents on iOS
+ // When another app owns the current clipboard contents, pasting
+ // will display a "allow or disallow" dialog. If the disallow
+ // option is selected, the data from the UIPasteboard will be
+ // garbage and we will find ourselves here. Since calling
+ // SetLOKNotifier() with a nullptr aborts in an assert(), fix
+ // the crash by failing gracefully.
+ return false;
+#else
+ SetLOKNotifier(mpDialogImpl->m_aInstallLOKNotifierHdl.Call(nullptr));
+#endif
+ }
+
+ 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.");
+ }
+
+ if (SalInstance::IsRunningUnitTest())
+ { // helps starbasic unit tests show their errors
+ std::cerr << "Dialog \"" << ImplGetDialogText(this)
+ << "\"cancelled in silent mode";
+ }
+
+ 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(Concat2View("Open Modal " + get_id()));
+ else
+ UITestLogger::getInstance().log(Concat2View("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());
+ aItems.emplace_back("unique_id", this->get_id().toUtf8());
+ 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
+ std::erase_if(rExecuteDialogs, [this](VclPtr<Dialog>& dialog){ return dialog.get() == this; });
+}
+
+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())
+ {
+ if (mpDialogImpl->m_bLOKTunneling)
+ 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();
+
+ // coverity[check_after_deref] - ImplEndExecuteModal might trigger destruction of mpDialogImpl
+ 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());
+ aItems.emplace_back("unique_id", this->get_id().toUtf8());
+ pNotifier->notifyWindow(GetLOKWindowId(), "size_changed", aItems);
+ }
+}
+
+bool Dialog::set_property(const OUString &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 = GetHelpId();
+ 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 0000000000..ac75333d90
--- /dev/null
+++ b/vcl/source/window/dlgctrl.cxx
@@ -0,0 +1,1168 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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( NotifyEventType::LOSEFOCUS, pSWindow );
+ if ( !ImplCallPreNotify( aNEvt1 ) )
+ pSWindow->CompatLoseFocus();
+ pSWindow->mpWindowImpl->mnGetFocusFlags = nGetFocusFlags | GetFocusFlags::Around;
+ NotifyEvent aNEvt2( NotifyEventType::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 )
+ {
+ mpWindowImpl->mpDlgCtrlDownWindow->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( NotifyEventType::LOSEFOCUS, pSWindow );
+ if ( !ImplCallPreNotify( aNEvt1 ) )
+ pSWindow->CompatLoseFocus();
+ pSWindow->mpWindowImpl->mnGetFocusFlags = nGetFocusFlags | GetFocusFlags::Around;
+ NotifyEvent aNEvt2( NotifyEventType::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
+ {
+ 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;
+#ifdef _WIN32
+ // tdf#157649 Allow omitting the Alt key when focus is in the dialog action area:
+ bool bIsButtonBox = dynamic_cast<VclButtonBox*>(pSWindow->GetParent()) != nullptr;
+ if ((bIsButtonBox && pSWindow->GetParent()->HasChildPathFocus(true)) || aKeyCode.IsMod2())
+#else
+ if (aKeyCode.IsMod2())
+#endif
+ {
+ pSWindow->ImplControlFocus( nGetFocusFlags );
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ if (isSuitableDestination(pButtonWindow))
+ {
+ if ( bKeyInput )
+ {
+ if ( mpWindowImpl->mpDlgCtrlDownWindow && (mpWindowImpl->mpDlgCtrlDownWindow.get() != pButtonWindow) )
+ {
+ mpWindowImpl->mpDlgCtrlDownWindow->SetPressed( false );
+ mpWindowImpl->mpDlgCtrlDownWindow = nullptr;
+ }
+
+ static_cast<PushButton*>(pButtonWindow)->SetPressed( true );
+ mpWindowImpl->mpDlgCtrlDownWindow = static_cast<PushButton*>(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 )
+ {
+ mpWindowImpl->mpDlgCtrlDownWindow->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( std::u16string_view rStr )
+{
+ sal_Unicode nChar = 0;
+ size_t nPos = 0;
+ do
+ {
+ nPos = rStr.find( '~', nPos );
+ if( nPos != std::u16string_view::npos && nPos < rStr.size() )
+ 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 0000000000..94af911c78
--- /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( std::u16string_view 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 0000000000..c57841c1fd
--- /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->ScreenToOutputPixel( 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->ScreenToOutputPixel( 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->ScreenToOutputPixel( 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->ScreenToOutputPixel( 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->ScreenToOutputPixel( 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 0000000000..9ff128c808
--- /dev/null
+++ b/vcl/source/window/dndlistenercontainer.cxx
@@ -0,0 +1,455 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 )
+{
+ m_bActive = true;
+ m_nDefaultActions = nDefaultActions;
+}
+
+DNDListenerContainer::~DNDListenerContainer()
+{
+}
+
+void SAL_CALL DNDListenerContainer::addDragGestureListener( const Reference< XDragGestureListener >& dgl )
+{
+ std::unique_lock g(m_aMutex);
+ maDragGestureListeners.addInterface( g, dgl );
+}
+
+void SAL_CALL DNDListenerContainer::removeDragGestureListener( const Reference< XDragGestureListener >& dgl )
+{
+ std::unique_lock g(m_aMutex);
+ maDragGestureListeners.removeInterface( g, dgl );
+}
+
+void SAL_CALL DNDListenerContainer::resetRecognizer( )
+{
+}
+
+void SAL_CALL DNDListenerContainer::addDropTargetListener( const Reference< XDropTargetListener >& dtl )
+{
+ std::unique_lock g(m_aMutex);
+ maDropTargetListeners.addInterface( g, dtl );
+}
+
+void SAL_CALL DNDListenerContainer::removeDropTargetListener( const Reference< XDropTargetListener >& dtl )
+{
+ std::unique_lock g(m_aMutex);
+ maDropTargetListeners.removeInterface( g, 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 )
+{
+ std::unique_lock g(m_aMutex);
+ if (!m_bActive || maDropTargetListeners.getLength(g) == 0)
+ return 0;
+
+ sal_uInt32 nRet = 0;
+
+ comphelper::OInterfaceIteratorHelper4 aIterator( g, maDropTargetListeners );
+
+ // 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())
+ {
+ Reference< XDropTargetListener > xListener( aIterator.next() );
+ try
+ {
+ // fire drop until the first one has accepted
+ if( m_xDropTargetDropContext.is() )
+ {
+ g.unlock();
+ xListener->drop( aEvent );
+ }
+ else
+ {
+ g.unlock();
+ DropTargetEvent aDTEvent( static_cast < XDropTarget * > (this), 0 );
+ xListener->dragExit( aDTEvent );
+ }
+
+ g.lock();
+ nRet++;
+ }
+ catch (const RuntimeException&)
+ {
+ aIterator.remove( g );
+ }
+ }
+
+ // 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()
+{
+ std::unique_lock g(m_aMutex);
+ if (!m_bActive || maDropTargetListeners.getLength(g) == 0)
+ return 0;
+
+ sal_uInt32 nRet = 0;
+
+ // do not construct the event before you are sure at least one listener is registered
+ DropTargetEvent aEvent( static_cast < XDropTarget * > (this), 0 );
+
+ comphelper::OInterfaceIteratorHelper4 aIterator( g, maDropTargetListeners );
+ g.unlock();
+ while (aIterator.hasMoreElements())
+ {
+ Reference< XDropTargetListener > xListener( aIterator.next() );
+ try
+ {
+ xListener->dragExit( aEvent );
+ nRet++;
+ }
+ catch (const RuntimeException&)
+ {
+ g.lock();
+ aIterator.remove( g );
+ g.unlock();
+ }
+ }
+
+ return nRet;
+}
+
+sal_uInt32 DNDListenerContainer::fireDragOverEvent( const Reference< XDropTargetDragContext >& context,
+ sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions )
+{
+ std::unique_lock g(m_aMutex);
+ if (!m_bActive || maDropTargetListeners.getLength(g) == 0)
+ return 0;
+
+ sal_uInt32 nRet = 0;
+
+ // fire DropTargetDropEvent on all XDropTargetListeners
+
+ comphelper::OInterfaceIteratorHelper4 aIterator( g, maDropTargetListeners );
+
+ // 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())
+ {
+ Reference< XDropTargetListener > xListener( aIterator.next() );
+ try
+ {
+ if( m_xDropTargetDragContext.is() )
+ {
+ g.unlock();
+ xListener->dragOver( aEvent );
+ g.lock();
+ }
+ nRet++;
+ }
+ catch (const RuntimeException&)
+ {
+ aIterator.remove(g);
+ }
+ }
+
+ // 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 )
+{
+ std::unique_lock g(m_aMutex);
+ if (!m_bActive || maDropTargetListeners.getLength(g) == 0)
+ return 0;
+
+ sal_uInt32 nRet = 0;
+
+ comphelper::OInterfaceIteratorHelper4 aIterator( g, maDropTargetListeners );
+
+ // 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())
+ {
+ Reference< XDropTargetListener > xListener( aIterator.next() );
+ try
+ {
+ if( m_xDropTargetDragContext.is() )
+ {
+ g.unlock();
+ xListener->dragEnter( aEvent );
+ g.lock();
+ }
+ nRet++;
+ }
+ catch (const RuntimeException&)
+ {
+ aIterator.remove( g );
+ }
+ }
+
+ // 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 )
+{
+ std::unique_lock g(m_aMutex);
+ if (!m_bActive || maDropTargetListeners.getLength(g) == 0)
+ return 0;
+
+ sal_uInt32 nRet = 0;
+
+ // fire DropTargetDropEvent on all XDropTargetListeners
+
+ comphelper::OInterfaceIteratorHelper4 aIterator( g, maDropTargetListeners );
+
+ // 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())
+ {
+ Reference< XDropTargetListener > xListener( aIterator.next() );
+ try
+ {
+ if( m_xDropTargetDragContext.is() )
+ {
+ g.unlock();
+ xListener->dropActionChanged( aEvent );
+ g.lock();
+ }
+ nRet++;
+ }
+ catch (const RuntimeException&)
+ {
+ aIterator.remove( g );
+ }
+ }
+
+ // 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 )
+{
+ std::unique_lock g(m_aMutex);
+ if (maDragGestureListeners.getLength(g) == 0)
+ return 0;
+
+ sal_uInt32 nRet = 0;
+
+ // 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 );
+
+ comphelper::OInterfaceIteratorHelper4 aIterator( g, maDragGestureListeners );
+ g.unlock();
+ while( aIterator.hasMoreElements() )
+ {
+ Reference< XDragGestureListener > xListener( aIterator.next() );
+ try
+ {
+ xListener->dragGestureRecognized( aEvent );
+ nRet++;
+ }
+ catch (const RuntimeException&)
+ {
+ g.lock();
+ aIterator.remove( g );
+ g.unlock();
+ }
+ }
+
+ 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 0000000000..be3f6ef99e
--- /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 0000000000..7cde1910fe
--- /dev/null
+++ b/vcl/source/window/dockmgr.cxx
@@ -0,0 +1,1083 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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()->OutputToScreenPixel( 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()->OutputToScreenPixel( 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()->ScreenToOutputPixel( aFrameMousePos );
+ aMousePos.AdjustX( -(maMouseOff.X()) );
+ aMousePos.AdjustY( -(maMouseOff.Y()) );
+ Point aPos = GetWindow()->OutputToScreenPixel( 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()->ScreenToOutputPixel( 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();
+ // grab focus (again) after showing docking window, as e.g. a11y focus
+ // events require window to be visible first
+ if (nFlags & FloatWinPopupFlags::GrabFocus)
+ mpFloatWin->GrabFocus();
+
+ 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();
+ // grab focus (again) after showing docking window, as e.g. a11y focus
+ // events require window to be visible first
+ if (nFlags & FloatWinPopupFlags::GrabFocus)
+ mpFloatWin->GrabFocus();
+}
+
+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 0000000000..3f8853877b
--- /dev/null
+++ b/vcl/source/window/dockwin.cxx
@@ -0,0 +1,1148 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 = OutputToScreenPixel( 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 OUString& 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 OUString& 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 = OutputToScreenPixel( 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 = ScreenToOutputPixel( aFrameMousePos );
+ aMousePos.AdjustX( -(maMouseOff.X()) );
+ aMousePos.AdjustY( -(maMouseOff.Y()) );
+ Point aFramePos = OutputToScreenPixel( 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( ScreenToOutputPixel( 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() == NotifyEventType::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() == NotifyEventType::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())
+ {
+ if ((nFlags & PosSizeFlags::Size) == PosSizeFlags::Size)
+ mpFloatWin->SetOutputSizePixel({ nWidth, nHeight });
+ if ((nFlags & PosSizeFlags::Pos) == PosSizeFlags::Pos)
+ mpFloatWin->SetPosPixel({ 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 )
+ {
+ vcl::WindowData aData;
+ aData.setMask(vcl::WindowDataMask::Pos);
+ pWrapper->mpFloatWin->GetWindowState( aData );
+ AbsoluteScreenPixelPoint aPos(aData.x(), aData.y());
+ // LOK needs logic coordinates not absolute screen position for autofilter menu
+ if (!comphelper::LibreOfficeKit::isActive() || get_id() != "check_list_menu")
+ return pWrapper->mpFloatWin->GetParent()->ImplGetFrameWindow()->AbsoluteScreenToOutputPixel( aPos );
+ return Point(aPos);
+ }
+ else
+ return maFloatPos;
+ }
+
+ if ( mpFloatWin )
+ {
+ vcl::WindowData aData;
+ aData.setMask(vcl::WindowDataMask::Pos);
+ mpFloatWin->GetWindowState( aData );
+ AbsoluteScreenPixelPoint aPos(aData.x(), aData.y());
+ return mpFloatWin->GetParent()->ImplGetFrameWindow()->AbsoluteScreenToOutputPixel( 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(Size(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 ? OUString("InterimDockParent") : OUString("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 0000000000..409f54eb1f
--- /dev/null
+++ b/vcl/source/window/errinf.cxx
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <utility>
+#include <vcl/errinf.hxx>
+
+#include <algorithm>
+#include <vector>
+
+class ErrorHandler;
+
+namespace {
+
+ ErrorRegistry& GetErrorRegistry()
+ {
+ static ErrorRegistry gErrorRegistry;
+ return gErrorRegistry;
+ }
+
+}
+
+bool ErrorStringFactory::CreateString(const ErrCodeMsg& nInfo, OUString& rStr)
+{
+ for(const ErrorHandler *pHdlr : GetErrorRegistry().errorHandlers)
+ {
+ if(pHdlr->CreateString(nInfo, rStr))
+ return true;
+ }
+ return false;
+}
+
+ErrorRegistry::ErrorRegistry()
+ : pDsp(nullptr)
+ , bIsWindowDsp(false)
+ , m_bLock(false)
+{
+}
+
+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;
+ std::erase(rErrorHandlers, this);
+}
+
+bool ErrorHandler::GetErrorString(const ErrCodeMsg& nErrCode, OUString& rErrStr)
+{
+ OUString aErr;
+
+ if(!nErrCode || nErrCode == ERRCODE_ABORT)
+ return false;
+
+ if (ErrorStringFactory::CreateString(nErrCode, aErr))
+ {
+ rErrStr = aErr;
+ return true;
+ }
+
+ return false;
+}
+
+DialogMask ErrorHandler::HandleError(const ErrCodeMsg& nErr, weld::Window *pParent, DialogMask nFlags)
+{
+ if (nErr == ERRCODE_NONE || nErr == ERRCODE_ABORT)
+ return DialogMask::NONE;
+
+ ErrorRegistry &rData = GetErrorRegistry();
+ OUString aAction;
+
+ if (!rData.contexts.empty())
+ {
+ rData.contexts.front()->GetString(nErr, aAction);
+
+ for(ErrorContext *pCtx : rData.contexts)
+ {
+ if(pCtx->GetParent())
+ {
+ pParent = pCtx->GetParent();
+ break;
+ }
+ }
+ }
+
+ bool bWarning = nErr.IsWarning();
+ DialogMask nErrFlags = DialogMask::ButtonDefaultsOk | DialogMask::ButtonsOk;
+ if (bWarning)
+ nErrFlags |= DialogMask::MessageWarning;
+ else
+ nErrFlags |= DialogMask::MessageError;
+
+ if( nErr.GetDialogMask() != DialogMask::NONE )
+ nErrFlags = nErr.GetDialogMask();
+
+ OUString aErr;
+ if (ErrorStringFactory::CreateString(nErr, 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 " << nErr);
+ // Error 1 (ERRCODE_ABORT) is classified as a General Error in sfx
+ if (nErr.GetCode() != 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;
+ std::erase(rContexts, this);
+}
+
+ErrorContext *ErrorContext::GetContext()
+{
+ return GetErrorRegistry().contexts.empty() ? nullptr : GetErrorRegistry().contexts.front();
+}
+
+weld::Window* ErrorContext::GetParent()
+{
+ return pImpl ? pImpl->pWin : nullptr;
+}
+
+/* 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 0000000000..23d910112a
--- /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() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::KEYINPUT) || (rNEvt.GetType() == NotifyEventType::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() == NotifyEventType::KEYINPUT );
+ }
+ }
+ else if ( (rNEvt.GetType() == NotifyEventType::GETFOCUS) || (rNEvt.GetType() == NotifyEventType::LOSEFOCUS) )
+ {
+ ImplDlgCtrlFocusChanged( rNEvt.GetWindow(), rNEvt.GetType() == NotifyEventType::GETFOCUS );
+ if ( (rNEvt.GetWindow() == this) && (rNEvt.GetType() == NotifyEventType::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;
+ std::erase(rListeners, rEventListener);
+ 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;
+ std::erase(rListeners, rEventListener);
+ 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() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::KEYINPUT )
+ {
+ if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) )
+ CallEventListeners( VclEventId::WindowKeyInput, const_cast<KeyEvent *>(rNEvt.GetKeyEvent()) );
+ }
+ else if( rNEvt.GetType() == NotifyEventType::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.x(), g.y());
+ if( pParentFrame )
+ {
+ g = pParentFrame->GetGeometry();
+ mpWindowImpl->maPos -= Point(g.x(), g.y());
+ }
+ // 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( NotifyEventType 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 0000000000..b2faacadb5
--- /dev/null
+++ b/vcl/source/window/floatwin.cxx
@@ -0,0 +1,974 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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;
+ AbsoluteScreenPixelRectangle 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;
+}
+
+AbsoluteScreenPixelRectangle 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 OUString& 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
+ AbsoluteScreenPixelPoint aPos;
+ Size aSize = ::isLayoutEnabled(pWindow) ? pWindow->get_preferred_size() : pWindow->GetSizePixel();
+ AbsoluteScreenPixelRectangle 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();
+
+ AbsoluteScreenPixelRectangle devRect( pW->OutputToAbsoluteScreenPixel( normRect.TopLeft() ),
+ pW->OutputToAbsoluteScreenPixel( normRect.BottomRight() ) );
+
+ AbsoluteScreenPixelRectangle devRectRTL( devRect );
+ if( bRTL )
+ // create a rect that can be compared to desktop coordinates
+ devRectRTL = pW->ImplOutputToUnmirroredAbsoluteScreenPixel( normRect );
+ if( Application::GetScreenCount() > 1 )
+ aScreenRect = Application::GetScreenPosSizePixel(
+ Application::GetBestScreen( bRTL ? devRectRTL : devRect ) );
+
+ FloatWinPopupFlags nArrangeAry[5];
+ sal_uInt16 nArrangeAttempts = 5;
+ AbsoluteScreenPixelPoint 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;
+
+ Point aPosOut = 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 =
+ AbsoluteScreenPixelRectangle( 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(aPosOut, MapMode(MapUnit::MapTwip));
+ }
+ else
+ {
+ *pLOKTwipsPos = OutputDevice::LogicToLogic(aPosOut, pW->GetMapMode(), MapMode(MapUnit::MapTwip));
+ }
+ }
+
+ // caller expects coordinates relative to top-level win
+ return pW->OutputToScreenPixel( aPosOut );
+}
+
+AbsoluteScreenPixelPoint FloatingWindow::ImplConvertToAbsPos(vcl::Window* pReference, const Point& rPos)
+{
+ const OutputDevice *pWindowOutDev = pReference->GetOutDev();
+
+ // compare coordinates in absolute screen coordinates
+ if ( pWindowOutDev->HasMirroredGraphics() && !comphelper::LibreOfficeKit::isActive() )
+ {
+ Point aTmp(rPos);
+ if(!pReference->IsRTLEnabled() )
+ pWindowOutDev->ReMirror( aTmp );
+
+ tools::Rectangle aRect( pReference->ScreenToOutputPixel(aTmp), Size(1,1) ) ;
+ aRect = tools::Rectangle(pReference->ImplOutputToUnmirroredAbsoluteScreenPixel( aRect ));
+ return AbsoluteScreenPixelPoint(aRect.TopLeft());
+ }
+ else
+ return pReference->OutputToAbsoluteScreenPixel(
+ pReference->ScreenToOutputPixel(rPos) );
+}
+
+AbsoluteScreenPixelRectangle FloatingWindow::ImplConvertToAbsPos(vcl::Window* pReference, const tools::Rectangle& rRect)
+{
+ AbsoluteScreenPixelRectangle aFloatRect;
+
+ 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() )
+ {
+ tools::Rectangle aScreenRect(rRect);
+ if(!pReference->IsRTLEnabled() )
+ pParentWinOutDev->ReMirror(aScreenRect);
+
+ tools::Rectangle aOutRect(pReference->ScreenToOutputPixel(aScreenRect.TopLeft()), aScreenRect.GetSize());
+ aFloatRect = pReference->ImplOutputToUnmirroredAbsoluteScreenPixel(aOutRect);
+ }
+ else
+ aFloatRect = AbsoluteScreenPixelRectangle(
+ pReference->OutputToAbsoluteScreenPixel(pReference->ScreenToOutputPixel(rRect.TopLeft())),
+ rRect.GetSize());
+
+ return aFloatRect;
+}
+
+tools::Rectangle FloatingWindow::ImplConvertToRelPos(vcl::Window* pReference, const AbsoluteScreenPixelRectangle& rRect)
+{
+ tools::Rectangle aFloatRect;
+
+ 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(rRect);
+ aFloatRect.SetPos(pReference->OutputToScreenPixel(aFloatRect.TopLeft()));
+
+ if(!pReference->IsRTLEnabled() )
+ pParentWinOutDev->ReMirror(aFloatRect);
+ }
+ else
+ aFloatRect = tools::Rectangle(pReference->OutputToScreenPixel(pReference->AbsoluteScreenToOutputPixel(rRect.TopLeft())),
+ rRect.GetSize());
+
+ return aFloatRect;
+}
+
+FloatingWindow* FloatingWindow::ImplFloatHitTest( vcl::Window* pReference, const Point& rPos, bool& rbHitTestInsideRect )
+{
+ FloatingWindow* pWin = this;
+ rbHitTestInsideRect = false;
+
+ AbsoluteScreenPixelPoint 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)
+ AbsoluteScreenPixelRectangle 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() == NotifyEventType::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("rectangle"_ostr, 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 0000000000..9521829785
--- /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 0000000000..671510e3c5
--- /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 0000000000..3ac75eab0d
--- /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 0000000000..cc3fca5802
--- /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 0000000000..eca00d4114
--- /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 0000000000..5639d8e62d
--- /dev/null
+++ b/vcl/source/window/layout.cxx
@@ -0,0 +1,3128 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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>
+
+// Needed since LLVM 15 libc++ (hence the ignored -Wunused-macros for older libc++) when
+// #include <boost/multi_array.hpp> below includes Boost 1.79.0
+// workdir/UnpackedTarball/boost/boost/functional.hpp using std::unary_function, but must
+// come very early here in case <functional> is already (indirectly) included earlier:
+#include <config_libcxx.h>
+#if HAVE_LIBCPP
+#if defined __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-macros"
+#endif
+// [-loplugin:reservedid]:
+#define _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION
+#if defined __clang__
+#pragma clang diagnostic pop
+#endif
+#endif
+
+#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 <utility>
+#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/toolkit/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();
+ tools::Long nPrimaryDimension = getPrimaryDimension(rAllocation);
+ nExtraSpace = (nPrimaryDimension - getPrimaryDimension(aRequisition)) / nExpandChildren;
+ }
+
+ //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 OUString &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
+{
+ // fdo#74284 call Boxes Panels, keep them as "Filler" under
+ // at least Linux seeing as that's what Gtk3 did for GtkBoxes.
+ // Though now with Gtk4 that uses GTK_ACCESSIBLE_ROLE_GROUP
+ // which maps to ATSPI_ROLE_PANEL
+#if defined(_WIN32)
+ return css::accessibility::AccessibleRole::PANEL;
+#else
+ static sal_uInt16 eRole = Application::GetToolkitName() == "gtk4" ?
+ css::accessibility::AccessibleRole::PANEL :
+ css::accessibility::AccessibleRole::FILLER;
+ return eRole;
+#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 OUString &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)
+ {
+#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 13
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdangling-reference"
+#endif
+ const GridEntry &rEntry = A[x][y];
+#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 13
+#pragma GCC diagnostic pop
+#endif
+ 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)
+ {
+#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 13
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdangling-reference"
+#endif
+ const GridEntry &rEntry = A[x][y];
+#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 13
+#pragma GCC diagnostic pop
+#endif
+ 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)
+ {
+#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 13
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdangling-reference"
+#endif
+ const GridEntry &rEntry = A[x][y];
+#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 13
+#pragma GCC diagnostic pop
+#endif
+ 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)
+ {
+#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 13
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdangling-reference"
+#endif
+ GridEntry &rEntry = A[x][y];
+#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 13
+#pragma GCC diagnostic pop
+#endif
+ 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 OUString &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.moDisclosurePlus)
+ rCtrlData.moDisclosurePlus.emplace(StockImage::Yes, SV_DISCLOSURE_PLUS);
+ if (!rCtrlData.moDisclosureMinus)
+ rCtrlData.moDisclosureMinus.emplace(StockImage::Yes, SV_DISCLOSURE_MINUS);
+
+ Image* pImg
+ = IsChecked() ? &*rCtrlData.moDisclosureMinus : &*rCtrlData.moDisclosurePlus;
+
+ 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 OUString &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 OUString &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() == NotifyEventType::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);
+}
+
+namespace {
+void lcl_dumpScrollbar(::tools::JsonWriter& rJsonWriter, ScrollBar& rScrollBar)
+{
+ rJsonWriter.put("lower", rScrollBar.GetRangeMin());
+ rJsonWriter.put("upper", rScrollBar.GetRangeMax());
+ rJsonWriter.put("step_increment", rScrollBar.GetLineSize());
+ rJsonWriter.put("page_increment", rScrollBar.GetPageSize());
+ rJsonWriter.put("value", rScrollBar.GetThumbPos());
+ rJsonWriter.put("page_size", rScrollBar.GetVisibleSize());
+}
+};
+
+void VclScrolledWindow::DumpAsPropertyTree(::tools::JsonWriter& rJsonWriter)
+{
+ VclBin::DumpAsPropertyTree(rJsonWriter);
+
+ rJsonWriter.put("user_managed_scrolling", m_bUserManagedScrolling);
+
+ {
+ auto aVertical = rJsonWriter.startNode("vertical");
+
+ ScrollBar& rScrollBar = getVertScrollBar();
+ lcl_dumpScrollbar(rJsonWriter, rScrollBar);
+
+ WinBits nWinBits = GetStyle();
+ if (nWinBits & WB_VSCROLL)
+ rJsonWriter.put("policy", "always");
+ else if (nWinBits & WB_AUTOVSCROLL)
+ rJsonWriter.put("policy", "auto");
+ else
+ rJsonWriter.put("policy", "never");
+ }
+
+ {
+ auto aHorizontal = rJsonWriter.startNode("horizontal");
+
+ ScrollBar& rScrollBar = getHorzScrollBar();
+ lcl_dumpScrollbar(rJsonWriter, rScrollBar);
+
+ WinBits nWinBits = GetStyle();
+ if (nWinBits & WB_HSCROLL)
+ rJsonWriter.put("policy", "always");
+ else if (nWinBits & WB_AUTOHSCROLL)
+ rJsonWriter.put("policy", "auto");
+ else
+ rJsonWriter.put("policy", "never");
+ }
+}
+
+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 OUString &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,
+ OUString aMessage,
+ 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(std::move(aMessage))
+{
+ 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 OUString &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;
+ OutputDevice* pRefDevice = GetOutDev();
+ Size aRenderSize(pRefDevice->PixelToLogic(GetOutputSizePixel()));
+ Size aOutputSize = GetSizePixel();
+ pDevice->SetOutputSize(aRenderSize);
+ tools::Rectangle aRect(Point(0,0), aRenderSize);
+
+ // Dark mode support
+ pDevice->DrawWallpaper(aRect, pRefDevice->GetBackground());
+
+ Paint(*pDevice, aRect);
+
+ BitmapEx aImage = pDevice->GetBitmapEx(Point(0,0), aRenderSize);
+ aImage.Scale(aOutputSize);
+ rJsonWriter.put("imagewidth", aRenderSize.Width());
+ rJsonWriter.put("imageheight", aRenderSize.Height());
+
+ 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());
+ OStringBuffer aBuffer("data:image/png;base64,");
+ ::comphelper::Base64::encode(aBuffer, aSeq);
+ rJsonWriter.put("image", aBuffer);
+ }
+ 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 0000000000..346e1fdc8f
--- /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 0000000000..82d630742a
--- /dev/null
+++ b/vcl/source/window/menu.cxx
@@ -0,0 +1,3143 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/diagnose_ex.hxx>
+#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>
+
+#include <officecfg/Office/Common.hxx>
+
+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.getOpenWidth() - width)/2 + 1;
+ tools::Long y = rRect.Top() + (rRect.getOpenHeight() - 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 OUString &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 OUString &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 OUString &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 OUString &rIdent, sal_uInt16 nPos)
+{
+ InsertItem(nItemId, rStr, nItemBits, rIdent, nPos);
+ SetItemImage( nItemId, rImage );
+}
+
+void Menu::InsertSeparator(const OUString &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( {}, 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());
+}
+
+bool Menu::HasValidEntries(bool bCheckPopups) const
+{
+ bool bValidEntries = false;
+ sal_uInt16 nCount = GetItemCount();
+ for (sal_uInt16 n = 0; !bValidEntries && (n < nCount); n++)
+ {
+ MenuItemData* pItem = pItemList->GetDataFromPos(n);
+ if (pItem->bEnabled && (pItem->eType != MenuItemType::SEPARATOR))
+ {
+ if (bCheckPopups && pItem->pSubMenu)
+ bValidEntries = pItem->pSubMenu->HasValidEntries(true);
+ else
+ bValidEntries = true;
+ }
+ }
+ return bValidEntries;
+}
+
+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::u16string_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;
+}
+
+OUString Menu::GetItemIdent(sal_uInt16 nId) const
+{
+ const MenuItemData* pData = pItemList->GetData(nId);
+ return pData ? pData->sIdent : OUString();
+}
+
+void Menu::SetItemBits( sal_uInt16 nItemId, MenuItemBits nBits )
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData(nItemId, nPos);
+
+ if (pData && (pData->nBits != nBits))
+ {
+ // these two menu item bits are relevant for (accessible) role
+ const MenuItemBits nRoleMask = MenuItemBits::CHECKABLE | MenuItemBits::RADIOCHECK;
+ const bool bRoleBitsChanged = (pData->nBits & nRoleMask) != (nBits & nRoleMask);
+
+ pData->nBits = nBits;
+
+ // update native menu
+ if (ImplGetSalMenu())
+ ImplGetSalMenu()->SetItemBits(nPos, nBits);
+
+ if (bRoleBitsChanged)
+ ImplCallEventListeners(VclEventId::MenuItemRoleChanged, nPos);
+ }
+}
+
+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 ( 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 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::u16string_view rIdent , bool bCheck )
+{
+ CheckItem( GetItemId( rIdent ), bCheck );
+}
+
+bool Menu::IsItemCheckable(sal_uInt16 nItemId) const
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData(nItemId, nPos);
+
+ if (!pData)
+ return false;
+
+ return pData->HasCheck();
+}
+
+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( pData->aHelpId, static_cast<weld::Widget*>(nullptr) );
+ }
+ }
+
+ //Fallback to Menu::GetAccessibleDescription without reentry to GetHelpText()
+ if (pData->aHelpText.isEmpty())
+ return pData->aAccessibleDescription;
+ 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 OUString& rHelpId )
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ pData->aHelpId = rHelpId;
+}
+
+OUString Menu::GetHelpId( sal_uInt16 nItemId ) const
+{
+ OUString aRet;
+
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ {
+ if ( !pData->aHelpId.isEmpty() )
+ aRet = pData->aHelpId;
+ else
+ aRet = pData->aCommandStr;
+ }
+
+ 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.screen();
+ 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(removeMnemonicFromString(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.getOpenWidth() - 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.getOpenWidth() - 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 );
+}
+
+static FloatWinPopupFlags lcl_TranslateFlags(PopupMenuFlags nFlags)
+{
+ 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 nPopupModeFlags;
+}
+
+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 );
+ return ImplExecute( pExecWindow, rRect, lcl_TranslateFlags(nFlags), 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();
+ }
+}
+
+bool PopupMenu::PrepareRun(const VclPtr<vcl::Window>& pParentWin, tools::Rectangle& rRect,
+ FloatWinPopupFlags& nPopupModeFlags, Menu* pSFrom,
+ bool& bRealExecute, VclPtr<MenuFloatingWindow>& pWin)
+{
+ bRealExecute = false;
+ const sal_uInt16 nItemCount = GetItemCount();
+ if (!pSFrom && (vcl::IsInPopupMenuExecute() || !nItemCount))
+ return false;
+
+ mpLayoutData.reset();
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ pStartedFrom = pSFrom;
+ nSelectedId = 0;
+ sSelectedIdent.clear();
+ bCanceled = false;
+
+ VclPtr<vcl::Window> xFocusId;
+ 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?!" );
+ rRect.SetPos(pParentWin->OutputToScreenPixel(rRect.TopLeft()));
+
+ nPopupModeFlags |= FloatWinPopupFlags::NoKeyClose | FloatWinPopupFlags::AllMouseButtonClose | FloatWinPopupFlags::GrabFocus;
+ if (bRealExecute)
+ nPopupModeFlags |= FloatWinPopupFlags::NewLevel;
+
+ bInCallback = true; // set it here, if Activate overridden
+ Activate();
+ bInCallback = false;
+
+ if (pParentWin->isDisposed())
+ return false;
+
+ if ( bCanceled || bKilled )
+ return false;
+
+ if (!nItemCount)
+ return false;
+
+ // The flag MenuFlags::HideDisabledEntries is inherited.
+ if ( pSFrom )
+ {
+ if ( pSFrom->nMenuFlags & MenuFlags::HideDisabledEntries )
+ nMenuFlags |= MenuFlags::HideDisabledEntries;
+ else
+ nMenuFlags &= ~MenuFlags::HideDisabledEntries;
+ }
+ else
+ {
+ if (officecfg::Office::Common::View::Menu::DontHideDisabledEntry::get())
+ nMenuFlags &= ~MenuFlags::HideDisabledEntries;
+ else
+ 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, {});
+ size_t nPos = 0;
+ pData = pItemList->GetData( pData->nId, nPos );
+ assert(pData);
+ if (pData)
+ {
+ pData->bIsTemporary = true;
+ }
+ ImplCallEventListeners(VclEventId::MenuSubmenuChanged, nPos);
+ }
+
+ pWin = VclPtrInstance<MenuFloatingWindow>(this, pParentWin, WB_BORDER | WB_SYSTEMWINDOW);
+ if (comphelper::LibreOfficeKit::isActive() && get_id() == "editviewspellmenu")
+ {
+ VclPtr<vcl::Window> xNotifierParent = pParentWin->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 );
+
+ AbsoluteScreenPixelRectangle aDesktopRect(pWin->GetDesktopRectPixel());
+ if( Application::GetScreenCount() > 1 )
+ {
+ vcl::Window* pDeskW = pWindow->GetWindow( GetWindowType::RealParent );
+ if( ! pDeskW )
+ pDeskW = pWindow;
+ AbsoluteScreenPixelPoint aDesktopTL(pDeskW->OutputToAbsoluteScreenPixel(rRect.TopLeft()));
+ aDesktopRect = Application::GetScreenPosSizePixel(
+ Application::GetBestScreen(AbsoluteScreenPixelRectangle(aDesktopTL, rRect.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();
+
+ AbsoluteScreenPixelRectangle devRect(pRef->OutputToAbsoluteScreenPixel(rRect.TopLeft()),
+ pRef->OutputToAbsoluteScreenPixel(rRect.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 -= pParentWin->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 ) );
+ }
+
+ pWin->SetFocusId( xFocusId );
+ pWin->SetOutputSizePixel( aSz );
+ return true;
+}
+
+bool PopupMenu::Run(const VclPtr<MenuFloatingWindow>& pWin, const bool bRealExecute, const bool bPreSelectFirst,
+ const FloatWinPopupFlags nPopupModeFlags, Menu* pSFrom, const tools::Rectangle& rRect)
+{
+ SalMenu* pMenu = ImplGetSalMenu();
+ if (pMenu && bRealExecute && pMenu->ShowNativePopupMenu(pWin, rRect, nPopupModeFlags))
+ return true;
+
+ pWin->StartPopupMode(rRect, nPopupModeFlags);
+ 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 )
+ {
+ for (size_t n = 0; n < static_cast<size_t>(GetItemCount()); 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();
+
+ return false;
+}
+
+void PopupMenu::FinishRun(const VclPtr<MenuFloatingWindow>& pWin, const VclPtr<vcl::Window>& pParentWin, const bool bRealExecute, const bool bIsNativeMenu)
+{
+ if (!bRealExecute || pWin->isDisposed())
+ return;
+
+ if (!bIsNativeMenu)
+ {
+ VclPtr<vcl::Window> 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();
+ }
+ }
+ }
+ else
+ pWin->StopExecute();
+
+ pWin->doShutdown();
+ pWindow.disposeAndClear();
+ ImplClosePopupToolBox(pParentWin);
+ ImplFlushPendingSelect();
+}
+
+sal_uInt16 PopupMenu::ImplExecute(const VclPtr<vcl::Window>& pParentWin, const tools::Rectangle& rRect,
+ FloatWinPopupFlags nPopupModeFlags, Menu* pSFrom, bool bPreSelectFirst)
+{
+ // tdf#126054 hold this until after function completes
+ VclPtr<PopupMenu> xThis(this);
+ bool bRealExecute = false;
+ tools::Rectangle aRect(rRect);
+ VclPtr<MenuFloatingWindow> pWin;
+ if (!PrepareRun(pParentWin, aRect, nPopupModeFlags, pSFrom, bRealExecute, pWin))
+ return 0;
+ const bool bNative = Run(pWin, bRealExecute, bPreSelectFirst, nPopupModeFlags, pSFrom, aRect);
+ FinishRun(pWin, pParentWin, bRealExecute, bNative);
+ return nSelectedId;
+}
+
+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 0000000000..8e2bc8d7eb
--- /dev/null
+++ b/vcl/source/window/menubarwindow.cxx
@@ -0,0 +1,1220 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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"
+#include <menubarvalue.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(m_pMenu->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 = 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())
+ 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 (!m_pMenu->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 0000000000..b5f026254d
--- /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<MenuBar> 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 0000000000..95a0d3f4d0
--- /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 = 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 0000000000..f26fb50373
--- /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 0000000000..d6849e3e71
--- /dev/null
+++ b/vcl/source/window/menuitemlist.cxx
@@ -0,0 +1,316 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/mnemonic.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 = removeMnemonicFromString(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 OUString &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 OUString &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 0000000000..fc25a40292
--- /dev/null
+++ b/vcl/source/window/menuitemlist.hxx
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <utility>
+#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<PopupMenu> 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)
+ OUString sIdent;
+ OUString 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( OUString aStr )
+ : nId(0)
+ , eType(MenuItemType::DONTKNOW)
+ , nBits(MenuItemBits::NONE)
+ , pSubMenu(nullptr)
+ , aText(std::move(aStr))
+ , 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 OUString &rIdent
+ );
+ void InsertSeparator(const OUString &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 0000000000..802c62e285
--- /dev/null
+++ b/vcl/source/window/menuwindow.cxx
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 )
+ {
+ // Check if there is a Help ID available, or else use
+ // the command URL
+ OUString aCommand = pMenu->GetItemCommand( nId );
+ OUString aHelpId;
+
+ // If no entry is selected, use the general menu Help ID
+ if (nId <= 0)
+ aHelpId = pMenu->GetHelpId();
+ else
+ aHelpId = pMenu->GetHelpId(nId);
+
+ if( aHelpId.isEmpty() )
+ aHelpId = OOO_HELP_INDEX;
+
+ if ( !aHelpId.isEmpty() )
+ pHelp->Start(aHelpId);
+ else
+ pHelp->Start(aCommand);
+ }
+ 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 0000000000..df0606b888
--- /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 0000000000..e4f4cf8cf7
--- /dev/null
+++ b/vcl/source/window/mnemonic.cxx
@@ -0,0 +1,351 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 ) );
+}
+
+MnemonicGenerator& MnemonicGenerator::operator=(MnemonicGenerator const &) = default; //MSVC2022 workaround
+MnemonicGenerator::MnemonicGenerator(MnemonicGenerator const&) = default; //MSVC2022 workaround
+
+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 0000000000..e5c8e130f7
--- /dev/null
+++ b/vcl/source/window/mouse.cxx
@@ -0,0 +1,744 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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, NotifyEventType::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( NotifyEventType::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( NotifyEventType::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( NotifyEventType::MOUSEMOVE, this, &rMEvt );
+ EventNotify(aNEvt);
+}
+
+void Window::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ NotifyEvent aNEvt( NotifyEventType::MOUSEBUTTONDOWN, this, &rMEvt );
+ if (!EventNotify(aNEvt) && mpWindowImpl)
+ mpWindowImpl->mbMouseButtonDown = true;
+}
+
+void Window::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ NotifyEvent aNEvt( NotifyEventType::MOUSEBUTTONUP, this, &rMEvt );
+ if (!EventNotify(aNEvt) && mpWindowImpl)
+ 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() );
+}
+
+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 = OutputToScreenPixel( 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
+ // ScreenToOutputPixel(), we get back the original position.
+ Point aPos = OutputToScreenPixel(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 ScreenToOutputPixel( aPos );
+}
+
+Point Window::GetLastPointerPosPixel()
+{
+
+ Point aPos( mpWindowImpl->mpFrameData->mnBeforeLastMouseX, mpWindowImpl->mpFrameData->mnBeforeLastMouseY );
+ if( GetOutDev()->ImplIsAntiparallel() )
+ {
+ const OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReMirror( aPos );
+ }
+ return ScreenToOutputPixel( 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 = ScreenToOutputPixel( 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 0000000000..a98703ca25
--- /dev/null
+++ b/vcl/source/window/paint.cxx
@@ -0,0 +1,1808 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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::WITH_ALPHA);
+
+ 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 0000000000..9c5f519f7c
--- /dev/null
+++ b/vcl/source/window/printdlg.cxx
@@ -0,0 +1,2254 @@
+/* -*- 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 <utility>
+#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( aNumText + " " );
+ aBuf.appendAscii( eUnit == o3tl::Length::mm ? "mm" : "in" );
+ if( !i_rPaperName.empty() )
+ {
+ aBuf.append( OUString::Concat(" (") + i_rPaperName + ")" );
+ }
+ 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, std::shared_ptr<PrinterController> i_xController)
+ : GenericDialogController(i_pWindow, "vcl/ui/printdialog.ui", "PrintDialog")
+ , maPController(std::move( i_xController ))
+ , 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"))
+ , 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));
+ 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",
+ m_xDialog->get_window_state(vcl::WindowDataMask::All) );
+
+ 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)
+ {
+ OUString 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(aValue);
+
+ // 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() ? SV_PRINT_COLLATE_BMP : 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( i_rHelpIds.getConstArray()[i_nIndex] );
+ }
+
+ 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->set_help_id("vcl/ui/printdialog/PrintDialog");
+ xWindow->show();
+ continue;
+ }
+
+ Sequence< beans::PropertyValue > aOptProp;
+ rOption.Value >>= aOptProp;
+
+ // extract ui element
+ OUString aCtrlType;
+ OUString 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 = aIDs[0];
+ }
+ 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(aHelpIds[0]);
+
+ // set help text
+ if (aHelpTexts.hasElements())
+ pPage->set_tooltip_text(aHelpTexts[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 = aIDs[m];
+ 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 == 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 0000000000..9efc020a6a
--- /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->ScreenToOutputPixel( aMousePos ) );
+ CommandScrollData aScrollData( mnActDeltaX, mnActDeltaY );
+ CommandEvent aCEvt( aCmdMousePos, CommandEventId::AutoScroll, true, &aScrollData );
+ NotifyEvent aNCmdEvt( NotifyEventType::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 0000000000..a22ecaa7c5
--- /dev/null
+++ b/vcl/source/window/seleng.cxx
@@ -0,0 +1,422 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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;
+ pFunctionSet->SetCursorAtPoint(aPos);
+ 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_uInt64 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 0000000000..329b63038f
--- /dev/null
+++ b/vcl/source/window/settings.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 <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/settings.hxx>
+
+#include <officecfg/Office/Common.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(*moSettings);
+ 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)
+ {
+ ImplBorderWindow* pImpl = static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get());
+ if (pImpl->mpMenuBarWindow)
+ pImpl->mpMenuBarWindow->UpdateSettings(rSettings, true);
+ if (pImpl->mpNotebookBar)
+ pImpl->mpNotebookBar->UpdateSettings(rSettings, true);
+ }
+ }
+
+ AllSettings aOldSettings(*mpWindowImpl->mxOutDev->moSettings);
+ AllSettingsFlags nChangeFlags = mpWindowImpl->mxOutDev->moSettings->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->moSettings->GetMouseSettings() );
+ aSet.SetWheelBehavior( aOldSettings.GetMouseSettings().GetWheelBehavior() );
+ mpWindowImpl->mxOutDev->moSettings->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 );
+
+ static const bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ if (!bFuzzing)
+ {
+ static const char* pEnvHC = getenv( "SAL_FORCE_HC" );
+ const bool bForceHCMode = pEnvHC && *pEnvHC;
+ if (bForceHCMode)
+ aStyleSettings.SetHighContrastMode( true );
+ else
+ {
+ sal_Int32 nHighContrastMode = officecfg::Office::Common::Accessibility::HighContrast::get();
+ if (nHighContrastMode != 0) // 0 Automatic, 1 Disable, 2 Enable
+ {
+ const bool bEnable = nHighContrastMode == 2;
+ aStyleSettings.SetHighContrastMode(bEnable);
+ }
+ }
+ }
+
+ 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 0000000000..df631b270b
--- /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 0000000000..f2bba6b2ce
--- /dev/null
+++ b/vcl/source/window/splitwin.cxx
@@ -0,0 +1,2717 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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.getOpenWidth();
+ tools::Long nWidthHalf = nWidth / 2;
+ tools::Long nHeight = rRect.getOpenHeight();
+ 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() == NotifyEventType::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;
+ std::swap( bPropSmall, bPropGreat );
+ }
+
+ 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 0000000000..c144eaa9bb
--- /dev/null
+++ b/vcl/source/window/stacking.cxx
@@ -0,0 +1,1152 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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>
+#include <com/sun/star/awt/XVclWindowPeer.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 0000000000..fc14762bef
--- /dev/null
+++ b/vcl/source/window/status.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 <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
+
+#define STATUSBAR_MIN_HEIGHT 16 // icons height, tdf#153344
+
+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;
+ OUString 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, ControlType eControlType)
+{
+ if (rRenderContext.IsNativeControlSupported(eControlType, 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
+ AbsoluteScreenPixelPoint aTL1(pWindow->OutputToAbsoluteScreenPixel(rFramePosSize.TopLeft()));
+ Point aTL = pEraseWindow->AbsoluteScreenToOutputPixel(aTL1);
+ 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(eControlType, ControlPart::Entire, aControlRegion,
+ ControlState::ENABLED, aValue, OUString());
+ if (bNeedErase)
+ rRenderContext.Pop();
+ if (bNativeOK)
+ return;
+ }
+
+ if (eControlType == ControlType::LevelBar)
+ {
+ if (nPercent2 < 2500)
+ rRenderContext.SetFillColor({ 0xFF0000 });
+ else if (nPercent2 < 5000)
+ rRenderContext.SetFillColor({ 0xFFFF00 });
+ else if (nPercent2 < 7500)
+ rRenderContext.SetFillColor({ 0x0000FF });
+ else
+ rRenderContext.SetFillColor({ 0x00FF00 });
+ }
+
+ // 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 && eControlType == ControlType::Progress)
+ {
+ // 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, ControlType::Progress);
+}
+
+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::PaintSelfAndChildrenImmediately()
+{
+ WindowImpl* pWindowImpl = ImplGetWindowImpl();
+ const bool bOrigOverlapWin = pWindowImpl->mbOverlapWin;
+ // Temporarily set mbOverlapWin so that any parent windows of StatusBar
+ // that happen to have accumulated Invalidates are not taken as the root
+ // paint candidate from which to paint the paint hierarchy. So we limit the
+ // paint here to this statusbar and its children, disabling the
+ // optimization to bundle pending paints together and suppressing any
+ // unexpected side effects of entering parent window paint handlers if this
+ // call is not from the primordial thread.
+ pWindowImpl->mbOverlapWin = true;
+ PaintImmediately();
+ pWindowImpl->mbOverlapWin = bOrigOverlapWin;
+}
+
+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);
+ PaintSelfAndChildrenImmediately();
+ }
+}
+
+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);
+ PaintSelfAndChildrenImmediately();
+ }
+}
+
+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);
+ PaintSelfAndChildrenImmediately();
+ }
+}
+
+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( pItem->maHelpId, 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 OUString& 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();
+ PaintSelfAndChildrenImmediately();
+ }
+}
+
+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);
+ PaintSelfAndChildrenImmediately();
+ mnLastProgressPaint_ms = nTime_ms;
+ }
+ }
+}
+
+void StatusBar::EndProgressMode()
+{
+ SAL_WARN_IF( !mbProgressMode, "vcl", "StatusBar::EndProgressMode(): no progress mode" );
+
+ mbProgressMode = false;
+ maPrgsTxt.clear();
+
+ if ( IsReallyVisible() )
+ {
+ Invalidate();
+ PaintSelfAndChildrenImmediately();
+ }
+}
+
+void StatusBar::SetText(const OUString& rText)
+{
+ if ((GetStyle() & WB_RIGHT) && !mbProgressMode && IsReallyVisible() && IsUpdateMode())
+ {
+ if (mbFormat)
+ {
+ Invalidate();
+ Window::SetText(rText);
+ }
+ else
+ {
+ Invalidate();
+ Window::SetText(rText);
+ PaintSelfAndChildrenImmediately();
+ }
+ }
+ else if (mbProgressMode)
+ {
+ maPrgsTxt = rText;
+ if (IsReallyVisible())
+ {
+ Invalidate();
+ PaintSelfAndChildrenImmediately();
+ }
+ }
+ 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 = std::max( static_cast<int>(GetTextHeight()), STATUSBAR_MIN_HEIGHT);
+ 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 0000000000..644aa1f095
--- /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 0000000000..6584c1e3b0
--- /dev/null
+++ b/vcl/source/window/syswin.cxx
@@ -0,0 +1,1171 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/ustrbuf.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 OUString& 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() == NotifyEventType::COMMAND)
+ ToggleMnemonicsOnHierarchy(*rNEvt.GetCommandEvent(), this);
+
+ // capture KeyEvents for menu handling
+ if (rNEvt.GetType() == NotifyEventType::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() == NotifyEventType::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;
+}
+
+vcl::WindowData::WindowData(std::u16string_view rStr)
+{
+ vcl::WindowData& rData = *this;
+ vcl::WindowDataMask nValidMask = vcl::WindowDataMask::NONE;
+ sal_Int32 nIndex = 0;
+
+ std::u16string_view aTokenStr = o3tl::getToken(rStr, 0, ',', nIndex);
+ if (!aTokenStr.empty())
+ {
+ rData.setX(o3tl::toInt32(aTokenStr));
+ if (rData.x() > -16384 && rData.x() < 16384)
+ nValidMask |= vcl::WindowDataMask::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.y() > -16384 && rData.y() < 16384)
+ nValidMask |= vcl::WindowDataMask::Y;
+ else
+ rData.setY(0);
+ }
+ else
+ rData.setY(0);
+ aTokenStr = o3tl::getToken(rStr, 0, ',', nIndex);
+ if (!aTokenStr.empty())
+ {
+ sal_Int32 nWidth = o3tl::toInt32(aTokenStr);
+ if (nWidth >= 0)
+ {
+ rData.setWidth(nWidth);
+ }
+ if (rData.width() > 0 && rData.width() < 16384)
+ nValidMask |= vcl::WindowDataMask::Width;
+ else
+ rData.setWidth(0);
+ }
+ else
+ rData.setWidth(0);
+ aTokenStr = o3tl::getToken(rStr, 0, ';', nIndex);
+ if (!aTokenStr.empty())
+ {
+ sal_Int32 nHeight = o3tl::toInt32(aTokenStr);
+ if (nHeight >= 0)
+ {
+ rData.setHeight(nHeight);
+ }
+ if (rData.height() > 0 && rData.height() < 16384)
+ nValidMask |= vcl::WindowDataMask::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
+ vcl::WindowState nState = static_cast<vcl::WindowState>(o3tl::toInt32(aTokenStr));
+ //nState &= ~vcl::WindowState::Minimized;
+ rData.setState(nState);
+ nValidMask |= vcl::WindowDataMask::State;
+ }
+ else
+ rData.setState(vcl::WindowState::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 |= vcl::WindowDataMask::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 |= vcl::WindowDataMask::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 |= vcl::WindowDataMask::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 |= vcl::WindowDataMask::MaximizedHeight;
+ else
+ rData.SetMaximizedHeight(0);
+ }
+ else
+ rData.SetMaximizedHeight(0);
+
+ // mark valid fields
+ rData.setMask(nValidMask);
+}
+
+OUString vcl::WindowData::toStr() const
+{
+ const vcl::WindowDataMask nValidMask = mask();
+ if ( nValidMask == vcl::WindowDataMask::NONE )
+ return {};
+
+ OUStringBuffer rStrBuf(64);
+
+ tools::Rectangle aRect = posSize();
+
+ if (nValidMask & vcl::WindowDataMask::X)
+ rStrBuf.append(static_cast<sal_Int32>(aRect.Left()));
+ rStrBuf.append(',');
+ if (nValidMask & vcl::WindowDataMask::Y)
+ rStrBuf.append(static_cast<sal_Int32>(aRect.Top()));
+ rStrBuf.append(',');
+ if (nValidMask & vcl::WindowDataMask::Width)
+ rStrBuf.append(static_cast<sal_Int32>(aRect.GetWidth()));
+ rStrBuf.append(',');
+ if (nValidMask & vcl::WindowDataMask::Height)
+ rStrBuf.append(static_cast<sal_Int32>(aRect.GetHeight()));
+ rStrBuf.append( ';' );
+ if (nValidMask & vcl::WindowDataMask::State)
+ {
+ // #94144# allow Minimize again, should be masked out when read from configuration
+ // 91625 - ignore Minimize
+ rStrBuf.append(static_cast<sal_Int32>(state()));
+ }
+ rStrBuf.append(';');
+ if (nValidMask & vcl::WindowDataMask::MaximizedX)
+ rStrBuf.append(static_cast<sal_Int32>(GetMaximizedX()));
+ rStrBuf.append(',');
+ if (nValidMask & vcl::WindowDataMask::MaximizedY)
+ rStrBuf.append(static_cast<sal_Int32>(GetMaximizedY()));
+ rStrBuf.append( ',' );
+ if (nValidMask & vcl::WindowDataMask::MaximizedWidth)
+ rStrBuf.append(static_cast<sal_Int32>(GetMaximizedWidth()));
+ rStrBuf.append(',');
+ if (nValidMask & vcl::WindowDataMask::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 )
+{
+ AbsoluteScreenPixelRectangle 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 WindowData
+ 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::SetWindowState(const vcl::WindowData& rData)
+{
+ const vcl::WindowDataMask nValidMask = rData.mask();
+ if ( nValidMask == vcl::WindowDataMask::NONE )
+ return;
+
+ if ( mbSysChild )
+ return;
+
+ vcl::Window* pWindow = this;
+ while ( pWindow->mpWindowImpl->mpBorderWindow )
+ pWindow = pWindow->mpWindowImpl->mpBorderWindow;
+
+ if ( pWindow->mpWindowImpl->mbFrame )
+ {
+ const vcl::WindowState nState = rData.state();
+ vcl::WindowData aState = rData;
+
+ if (rData.mask() & vcl::WindowDataMask::Size)
+ {
+ // #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() > static_cast<tools::Long>(aState.width()))
+ aState.setWidth(maMinOutSize.Width());
+ if (maMinOutSize.Height() > static_cast<tools::Long>(aState.width()))
+ aState.setHeight(maMinOutSize.Height());
+ }
+
+ // #94144# allow Minimize again, should be masked out when read from configuration
+ // 91625 - ignore Minimize
+ //nState &= ~(WindowState::Minimized);
+ aState.rState() &= vcl::WindowState::SystemMask;
+
+ // normalize window positions onto screen
+ tools::Long nX = aState.x(), nY = aState.y();
+ ImplMoveToScreen(nX, nY, aState.width(), aState.height(), pWindow);
+ aState.setPos({ nX, nY });
+ nX = aState.GetMaximizedX();
+ nY = aState.GetMaximizedY();
+ ImplMoveToScreen(nX, nY, aState.GetMaximizedWidth(), aState.GetMaximizedHeight(), pWindow);
+ aState.SetMaximizedX(nX);
+ aState.SetMaximizedY(nY);
+
+ // #96568# avoid having multiple frames at the same screen location
+ // do the check only if not maximized
+ if( !((rData.mask() & vcl::WindowDataMask::State) && (nState & vcl::WindowState::Maximized)) )
+ if (rData.mask() & vcl::WindowDataMask::PosSize)
+ {
+ AbsoluteScreenPixelRectangle 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.x()-aState.x()) < 2 && std::abs(g.y()-aState.y()) < 5 )
+ {
+ tools::Long displacement = g.topDecoration() ? g.topDecoration() : 20;
+ if( static_cast<tools::Long>(aState.x() + displacement + aState.width() + g.rightDecoration()) > aDesktop.Right() ||
+ static_cast<tools::Long>(aState.y() + displacement + aState.height() + g.bottomDecoration()) > aDesktop.Bottom() )
+ {
+ // displacing would leave screen
+ aState.setX(g.leftDecoration() ? g.leftDecoration() : 10); // should result in (0,0)
+ aState.setY(displacement);
+ if( bWrapped ||
+ static_cast<tools::Long>(aState.x() + displacement + aState.width() + g.rightDecoration()) > aDesktop.Right() ||
+ static_cast<tools::Long>(aState.y() + displacement + aState.height() + g.bottomDecoration()) > aDesktop.Bottom() )
+ break; // further displacement not possible -> break
+ // avoid endless testing
+ bWrapped = true;
+ }
+ else
+ aState.move(displacement, 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.mask() & vcl::WindowDataMask::State) && (nState & vcl::WindowState::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.width() != rData.width() || aGeometry.height() != rData.height())
+ ImplHandleResize(pWindow, aGeometry.width(), aGeometry.height());
+ }
+ else
+ if (rData.mask() & vcl::WindowDataMask::Size)
+ ImplHandleResize(pWindow, aState.width(), aState.height()); // #i43799# use aState and not rData, see above
+ }
+ else
+ {
+ PosSizeFlags nPosSize = PosSizeFlags::NONE;
+ if ( nValidMask & vcl::WindowDataMask::X )
+ nPosSize |= PosSizeFlags::X;
+ if ( nValidMask & vcl::WindowDataMask::Y )
+ nPosSize |= PosSizeFlags::Y;
+ if ( nValidMask & vcl::WindowDataMask::Width )
+ nPosSize |= PosSizeFlags::Width;
+ if ( nValidMask & vcl::WindowDataMask::Height )
+ nPosSize |= PosSizeFlags::Height;
+
+ tools::Long nX = rData.x();
+ tools::Long nY = rData.y();
+ tools::Long nWidth = rData.width();
+ tools::Long nHeight = rData.height();
+ const SalFrameGeometry& rGeom = pWindow->mpWindowImpl->mpFrame->GetGeometry();
+ if( nX < 0 )
+ nX = 0;
+ if( nX + nWidth > static_cast<tools::Long>(rGeom.width()) )
+ nX = rGeom.width() - nWidth;
+ if( nY < 0 )
+ nY = 0;
+ if( nY + nHeight > static_cast<tools::Long>(rGeom.height()) )
+ nY = rGeom.height() - 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 & vcl::WindowDataMask::Size)
+ mbInitialLayoutSizeCalculated = true;
+}
+
+void SystemWindow::GetWindowState(vcl::WindowData& rData) const
+{
+ vcl::WindowDataMask nValidMask = rData.mask();
+ if ( nValidMask == vcl::WindowDataMask::NONE )
+ return;
+
+ if ( mbSysChild )
+ {
+ rData.setMask( vcl::WindowDataMask::NONE );
+ return;
+ }
+
+ const vcl::Window* pWindow = this;
+ while ( pWindow->mpWindowImpl->mpBorderWindow )
+ pWindow = pWindow->mpWindowImpl->mpBorderWindow;
+
+ if ( pWindow->mpWindowImpl->mbFrame )
+ {
+ vcl::WindowData aState;
+ if ( mpWindowImpl->mpFrame->GetWindowState( &aState ) )
+ {
+ // Limit mask only to what we've received, the rest is not set.
+ nValidMask &= aState.mask();
+ rData.setMask( nValidMask );
+ if ( nValidMask & vcl::WindowDataMask::X )
+ rData.setX( aState.x() );
+ if ( nValidMask & vcl::WindowDataMask::Y )
+ rData.setY( aState.y() );
+ if ( nValidMask & vcl::WindowDataMask::Width )
+ rData.setWidth( aState.width() );
+ if ( nValidMask & vcl::WindowDataMask::Height )
+ rData.setHeight( aState.height() );
+ if ( nValidMask & vcl::WindowDataMask::MaximizedX )
+ rData.SetMaximizedX( aState.GetMaximizedX() );
+ if ( nValidMask & vcl::WindowDataMask::MaximizedY )
+ rData.SetMaximizedY( aState.GetMaximizedY() );
+ if ( nValidMask & vcl::WindowDataMask::MaximizedWidth )
+ rData.SetMaximizedWidth( aState.GetMaximizedWidth() );
+ if ( nValidMask & vcl::WindowDataMask::MaximizedHeight )
+ rData.SetMaximizedHeight( aState.GetMaximizedHeight() );
+ if ( nValidMask & vcl::WindowDataMask::State )
+ {
+ // #94144# allow Minimize again, should be masked out when read from configuration
+ // 91625 - ignore Minimize
+ if (!(nValidMask & vcl::WindowDataMask::Minimized))
+ aState.rState() &= ~vcl::WindowState::Minimized;
+ rData.setState(aState.state());
+ }
+ rData.setMask( nValidMask );
+ }
+ else
+ rData.setMask(vcl::WindowDataMask::NONE);
+ }
+ else
+ {
+ Point aPos = GetPosPixel();
+ Size aSize = GetSizePixel();
+ vcl::WindowState nState = vcl::WindowState::NONE;
+
+ nValidMask &= vcl::WindowDataMask::PosSizeState;
+ rData.setMask( nValidMask );
+ if (nValidMask & vcl::WindowDataMask::X)
+ rData.setX(aPos.X());
+ if (nValidMask & vcl::WindowDataMask::Y)
+ rData.setY(aPos.Y());
+ if (nValidMask & vcl::WindowDataMask::Width)
+ rData.setWidth(aSize.Width());
+ if (nValidMask & vcl::WindowDataMask::Height)
+ rData.setHeight(aSize.Height());
+ if (nValidMask & vcl::WindowDataMask::State)
+ rData.setState(nState);
+ }
+}
+
+void SystemWindow::SetWindowState(std::u16string_view rStr)
+{
+ if (rStr.empty())
+ return;
+ SetWindowState(vcl::WindowData(rStr));
+}
+
+OUString SystemWindow::GetWindowState(vcl::WindowDataMask nMask) const
+{
+ vcl::WindowData aData;
+ aData.setMask(nMask);
+ GetWindowState(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.screen();
+}
+
+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(Size(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::WITHOUT_ALPHA));
+ 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 0000000000..f132300a07
--- /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 0000000000..612b17d272
--- /dev/null
+++ b/vcl/source/window/tabpage.cxx
@@ -0,0 +1,309 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/toolkit/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 );
+
+ bool bHasHoriBar = false;
+ bool bHasVertBar = false;
+
+ Link<ScrollBar*,void> aLink( LINK( this, TabPage, ScrollBarHdl ) );
+
+ if ( nStyle & ( WB_AUTOHSCROLL | WB_AUTOVSCROLL ) )
+ {
+ if ( nStyle & WB_AUTOHSCROLL )
+ {
+ bHasHoriBar = true;
+ m_pHScroll.set(VclPtr<ScrollBar>::Create(this, (WB_HSCROLL | WB_DRAG)));
+ m_pHScroll->Show();
+ m_pHScroll->SetScrollHdl(aLink);
+ }
+ if ( nStyle & WB_AUTOVSCROLL )
+ {
+ bHasVertBar = true;
+ m_pVScroll.set(VclPtr<ScrollBar>::Create(this, (WB_VSCROLL | WB_DRAG)));
+ m_pVScroll->Show();
+ m_pVScroll->SetScrollHdl(aLink);
+ }
+ }
+
+ if ( bHasHoriBar || bHasVertBar )
+ {
+ 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 )
+{
+ Size aSize = GetSizePixel();
+
+ Wallpaper aWallpaper = GetBackground();
+ if ( !aWallpaper.IsBitmap() )
+ ImplInitSettings();
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetLineColor();
+
+ if ( aWallpaper.IsBitmap() )
+ pDev->DrawBitmapEx( rPos, aSize, aWallpaper.GetBitmap() );
+ else
+ {
+ if( aWallpaper.GetColor() == COL_AUTO )
+ pDev->SetFillColor( GetSettings().GetStyleSettings().GetDialogColor() );
+ else
+ pDev->SetFillColor( aWallpaper.GetColor() );
+ pDev->DrawRect( tools::Rectangle( rPos, 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 0000000000..16747f0c08
--- /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 {
+
+AbsoluteScreenPixelPoint ImplTaskPaneListGetPos( const vcl::Window *w )
+{
+ AbsoluteScreenPixelPoint pos;
+ if( w->IsDockingWindow() )
+ {
+ Point pos1 = static_cast<const DockingWindow*>(w)->GetPosPixel();
+ vcl::Window *pF = static_cast<const DockingWindow*>(w)->GetFloatingWindow();
+ if( pF )
+ pos = pF->OutputToAbsoluteScreenPixel( pF->ScreenToOutputPixel( pos1 ) );
+ else
+ pos = w->OutputToAbsoluteScreenPixel( pos1 );
+ }
+ 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 0000000000..fc9effe580
--- /dev/null
+++ b/vcl/source/window/toolbox.cxx
@@ -0,0 +1,4810 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/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 <vcl/ptrstyle.hxx>
+#include <bitmaps.hlst>
+#include <toolbarvalue.hxx>
+
+#include <tools/poly.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(css::awt::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().GetFaceColor());
+ else
+ rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetWindowColor());
+
+ 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;
+ 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;
+
+ 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 OUString& 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;
+
+ 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.getOpenWidth() - aImageSize.Width())/2;
+ tools::Long y = rRect.Top() + (rRect.getOpenHeight() - 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.getOpenWidth() - 2 * nMargin;
+ const tools::Long nHalfSize = (nSize + 1) / 2;
+ const tools::Long x = rDropDownRect.Left() + nMargin + (bRotate ? (rDropDownRect.getOpenWidth() - nHalfSize) / 2 : 0);
+ const tools::Long y = rDropDownRect.Top() + nMargin + (rDropDownRect.getOpenHeight() - (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.getOpenWidth() : 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.getOpenWidth() : 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;
+
+ 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( pItem->maHelpId, 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() == NotifyEventType::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() == NotifyEventType::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() == NotifyEventType::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::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 0000000000..c799495b9b
--- /dev/null
+++ b/vcl/source/window/toolbox2.cxx
@@ -0,0 +1,1757 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <utility>
+#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, Image aImage,
+ ToolBoxItemBits nItemBits ) :
+ maImage(std::move( aImage ))
+{
+ init(nItemId, nItemBits, false);
+}
+
+ImplToolItem::ImplToolItem( ToolBoxItemId nItemId, OUString aText,
+ OUString aCommand, ToolBoxItemBits nItemBits ) :
+ maText(std::move( aText )),
+ maCommandStr(std::move( aCommand ))
+{
+ init(nItemId, nItemBits, false);
+}
+
+ImplToolItem::ImplToolItem( ToolBoxItemId nItemId, Image aImage,
+ OUString aText, ToolBoxItemBits nItemBits ) :
+ maImage(std::move( aImage )),
+ maText(std::move( aText ))
+{
+ 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, const OUString& rCommand, 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), rCommand, 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, aLabel, rCommand, nBits, nPos);
+ SetItemImage(nItemId, aImage);
+ 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() )
+ {
+ AbsoluteScreenPixelRectangle 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
+
+ AbsoluteScreenPixelPoint 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;
+}
+
+static Image ImplMirrorImage( const Image& rImage )
+{
+ BitmapEx aMirrBitmapEx( rImage.GetBitmapEx() );
+
+ aMirrBitmapEx.Mirror( BmpMirrorFlags::Horizontal );
+
+ return Image( aMirrBitmapEx );
+}
+
+static Image ImplRotImage( const Image& rImage, Degree10 nAngle10 )
+{
+ BitmapEx aRotBitmapEx( rImage.GetBitmapEx() );
+
+ aRotBitmapEx.Rotate( nAngle10, COL_WHITE );
+
+ return Image( aRotBitmapEx );
+}
+
+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 = pItem->mbMirrorMode ? ImplMirrorImage(rImage) : rImage;
+ if (pItem->mnImageAngle != 0_deg10)
+ pItem->maImage = ImplRotImage(pItem->maImage, pItem->mnImageAngle);
+
+ // only once all is calculated, do extra work
+ if (!mbCalc)
+ {
+ if (aOldSize != pItem->maImage.GetSizePixel())
+ ImplInvalidate( true );
+ else
+ ImplUpdateItem( nPos );
+ }
+}
+
+void ToolBox::SetItemImageAngle( ToolBoxItemId nItemId, Degree10 nAngle10 )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos == ITEM_NOTFOUND )
+ return;
+
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+ pItem->mnImageAngle = nAngle10;
+}
+
+void ToolBox::SetItemImageMirrorMode( ToolBoxItemId nItemId, bool bMirror )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos == ITEM_NOTFOUND )
+ return;
+
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+ pItem->mbMirrorMode = bMirror;
+}
+
+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 OUString& 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());
+ OStringBuffer aBuffer("data:image/png;base64,");
+ ::comphelper::Base64::encode(aBuffer, aSeq);
+ rJsonWriter.put("image", aBuffer);
+ }
+ }
+ }
+ }
+}
+
+/* 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 0000000000..c3fa7fb3d7
--- /dev/null
+++ b/vcl/source/window/window.cxx
@@ -0,0 +1,3979 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <comphelper/diagnose_ex.hxx>
+#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/toolkit/button.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/XVclWindowPeer.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( !IsNativeFrame() && 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 ? mpWindowImpl->mxOutDev.get() : nullptr;
+}
+
+::OutputDevice* Window::GetOutDev()
+{
+ return mpWindowImpl ? mpWindowImpl->mxOutDev.get() : nullptr;
+}
+
+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;
+ 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->moSettings->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()->moSettings->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,
+ bool bUseRenderContextDPI) const
+{
+ Size aSize = rFont.GetFontSize();
+
+ if (aSize.Width())
+ {
+ aSize.setWidth( aSize.Width() *
+ ( bUseRenderContextDPI ? rRenderContext.GetDPIX() : mpWindowImpl->mpFrameData->mnDPIX) );
+ aSize.AdjustWidth(72 / 2 );
+ aSize.setWidth( aSize.Width() / 72 );
+ }
+ aSize.setHeight( aSize.Height()
+ * ( bUseRenderContextDPI ? rRenderContext.GetDPIY() : mpWindowImpl->mpFrameData->mnDPIY) );
+ aSize.AdjustHeight(72/2 );
+ aSize.setHeight( aSize.Height() / 72 );
+
+ aSize = rRenderContext.PixelToLogic(aSize);
+
+ rFont.SetFontSize(aSize);
+}
+
+void Window::ImplLogicToPoint(vcl::RenderContext const & rRenderContext, vcl::Font& rFont) const
+{
+ Size aSize = rFont.GetFontSize();
+ 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( 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( NotifyEventType::KEYINPUT, this, &rKEvt );
+ if ( !CompatNotify( aNEvt ) )
+ mpWindowImpl->mbKeyInput = true;
+}
+
+void Window::KeyUp( const KeyEvent& rKEvt )
+{
+ NotifyEvent aNEvt( NotifyEventType::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( NotifyEventType::GETFOCUS, this );
+ CompatNotify( aNEvt );
+}
+
+void Window::LoseFocus()
+{
+ NotifyEvent aNEvt( NotifyEventType::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( GetHelpId() );
+ 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( NotifyEventType::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,
+ bool bUseRenderContextDPI)
+{
+ vcl::Font aFont = rFont;
+ ImplPointToLogic(rRenderContext, aFont, bUseRenderContextDPI);
+ 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 );
+}
+
+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.width() )
+ {
+ // 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.width();
+ if( !myWidth )
+ myWidth = nWidth;
+ nFlags |= PosSizeFlags::X;
+ nSysFlags |= SAL_FRAME_POSSIZE_X;
+ nX = aParentSysGeometry.x() - aSysGeometry.leftDecoration() + aParentSysGeometry.width()
+ - myWidth - 1 - aSysGeometry.x();
+ }
+ }
+ }
+ 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;
+}
+
+AbsoluteScreenPixelRectangle Window::GetDesktopRectPixel() const
+{
+ AbsoluteScreenPixelRectangle 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() const
+{
+ // revert mnOutOffX changes that were potentially made in ImplPosSizeWindow
+ tools::Long offx = GetOutDev()->mnOutOffX;
+ const 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 = 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 = ImplGetUnmirroredOutOffX();
+ return Point( rPos.X()-offx, rPos.Y() - GetOutDev()->mnOutOffY );
+}
+
+AbsoluteScreenPixelPoint Window::OutputToAbsoluteScreenPixel( const Point& rPos ) const
+{
+ // relative to the screen
+ Point p = OutputToScreenPixel( rPos );
+ SalFrameGeometry g = mpWindowImpl->mpFrame->GetGeometry();
+ p.AdjustX(g.x() );
+ p.AdjustY(g.y() );
+ return AbsoluteScreenPixelPoint(p);
+}
+
+Point Window::AbsoluteScreenToOutputPixel( const AbsoluteScreenPixelPoint& rPos ) const
+{
+ // relative to the screen
+ Point p = ScreenToOutputPixel( Point(rPos) );
+ SalFrameGeometry g = mpWindowImpl->mpFrame->GetGeometry();
+ p.AdjustX( -(g.x()) );
+ p.AdjustY( -(g.y()) );
+ return p;
+}
+
+AbsoluteScreenPixelRectangle 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.x()+g.width()-p1.X() );
+ p1.AdjustY(g.y() );
+
+ Point p2 = rRect.BottomLeft();
+ p2 = OutputToScreenPixel(p2);
+ p2.setX( g.x()+g.width()-p2.X() );
+ p2.AdjustY(g.y() );
+
+ return AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint(p1), AbsoluteScreenPixelPoint(p2) );
+}
+
+tools::Rectangle Window::ImplUnmirroredAbsoluteScreenToOutputPixel( const AbsoluteScreenPixelRectangle &rRect ) const
+{
+ // undo ImplOutputToUnmirroredAbsoluteScreenPixel
+ SalFrameGeometry g = mpWindowImpl->mpFrame->GetUnmirroredGeometry();
+
+ Point p1( rRect.TopRight() );
+ p1.AdjustY(-g.y() );
+ p1.setX( g.x()+g.width()-p1.X() );
+ p1 = ScreenToOutputPixel(p1);
+
+ Point p2( rRect.BottomLeft() );
+ p2.AdjustY(-g.y());
+ p2.setX( g.x()+g.width()-p2.X() );
+ p2 = ScreenToOutputPixel(p2);
+
+ return tools::Rectangle( p1, p2 );
+}
+
+
+// with decoration
+tools::Rectangle Window::GetWindowExtentsRelative(const vcl::Window & rRelativeWindow) const
+{
+ AbsoluteScreenPixelRectangle aRect = GetWindowExtentsAbsolute();
+ // #106399# express coordinates relative to borderwindow
+ const vcl::Window *pRelWin = rRelativeWindow.mpWindowImpl->mpBorderWindow ? rRelativeWindow.mpWindowImpl->mpBorderWindow.get() : &rRelativeWindow;
+ return tools::Rectangle(
+ pRelWin->AbsoluteScreenToOutputPixel( aRect.GetPos() ),
+ aRect.GetSize() );
+}
+
+// with decoration
+AbsoluteScreenPixelRectangle Window::GetWindowExtentsAbsolute() const
+{
+ // 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;
+
+ AbsoluteScreenPixelPoint aPos( pWin->OutputToAbsoluteScreenPixel( Point(0,0) ) );
+ 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) )
+ {
+ SalFrameGeometry g = mpWindowImpl->mpFrame->GetGeometry();
+ aPos.AdjustX( -sal_Int32(g.leftDecoration()) );
+ aPos.AdjustY( -sal_Int32(g.topDecoration()) );
+ aSize.AdjustWidth(g.leftDecoration() + g.rightDecoration() );
+ aSize.AdjustHeight(g.topDecoration() + g.bottomDecoration() );
+ }
+ return AbsoluteScreenPixelRectangle( 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( GetHelpId() );
+ 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 )
+ {
+ mpWindowImpl->maHelpText = mpWindowImpl->maHelpText + "\n------------------\n" + aStrHelpId;
+ }
+ 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::XVclWindowPeer > 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::XVclWindowPeer > 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::XVclWindowPeer > 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);
+ }
+
+ 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
+{
+
+std::string_view 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;
+ }
+ }
+
+ vcl::Window* pAccLabelFor = getAccessibleRelationLabelFor();
+ if (pAccLabelFor)
+ rJsonWriter.put("labelFor", pAccLabelFor->get_id());
+
+ vcl::Window* pAccLabelledBy = GetAccessibleRelationLabeledBy();
+ if (pAccLabelledBy)
+ rJsonWriter.put("labelledBy", pAccLabelledBy->get_id());
+
+ 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.width()-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()->moSettings, 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 0000000000..ceaebf52be
--- /dev/null
+++ b/vcl/source/window/window2.cxx
@@ -0,0 +1,2059 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/scrollable.hxx>
+#include <vcl/toolkit/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.Normalize();
+
+ 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 )
+{
+ if (!mpWindowImpl)
+ {
+ SAL_WARN("vcl", "ImplTrackTimerHdl has outlived dispose");
+ return;
+ }
+
+ 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( ScreenToOutputPixel( 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 );
+ }
+
+ SAL_WARN_IF(pSVData->mpWinData->mpTrackTimer, "vcl", "StartTracking called while TrackerTimer still running");
+
+ if ( !mpWindowImpl->mbUseFrameData &&
+ (nFlags & (StartTrackingFlags::ScrollRepeat | StartTrackingFlags::ButtonRepeat)) )
+ {
+ pSVData->mpWinData->mpTrackTimer.reset(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 )
+ pSVData->mpWinData->mpTrackTimer.reset();
+
+ 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( ScreenToOutputPixel( 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
+{
+ if (!mpWindowImpl)
+ return false;
+ if (mpWindowImpl->mbUseFrameData && mpWindowImpl->mpFrameData)
+ {
+ return mpWindowImpl->mpFrameData->mpTrackWin == this;
+ }
+ if (!mpWindowImpl->mbUseFrameData && ImplGetSVData()->mpWinData)
+ {
+ return ImplGetSVData()->mpWinData->mpTrackWin == this;
+ }
+ return false;
+}
+
+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( Scrollable* pScrl, double nN, bool isMultiplyByLineSize )
+{
+ if (!pScrl || !nN || pScrl->Inactive())
+ 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,
+ Scrollable* pHScrl, Scrollable* 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->Inactive() )
+ nFlags |= StartAutoScrollFlags::Horz;
+ }
+ if ( pVScrl )
+ {
+ if ( (pVScrl->GetVisibleSize() < pVScrl->GetRangeMax()) &&
+ !pVScrl->Inactive() )
+ 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 )
+ {
+ Scrollable* 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::GesturePan:
+ {
+ if (pVScrl)
+ {
+ const CommandGesturePanData* pData = rCmd.GetGesturePanData();
+ if (pData->meEventType == GestureEventPanType::Begin)
+ {
+ mpWindowImpl->mpFrameData->mnTouchPanPosition = pVScrl->GetThumbPos();
+ }
+ else if(pData->meEventType == GestureEventPanType::Update)
+ {
+ tools::Long nOriginalPosition = mpWindowImpl->mpFrameData->mnTouchPanPosition;
+ pVScrl->DoScroll(nOriginalPosition + (pData->mfOffset / pVScrl->GetVisibleSize()));
+ }
+ if (pData->meEventType == GestureEventPanType::End)
+ {
+ 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( Scrollable* pHScrl, double nX,
+ Scrollable* 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 OUString& rHelpId )
+{
+ mpWindowImpl->maHelpId = rHelpId;
+}
+
+const OUString& 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;
+}
+
+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;
+}
+
+bool Window::IsNativeFrame() 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;
+}
+
+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 OUString &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 OUString &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);
+ 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 0000000000..30c41f5e20
--- /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::TextLayoutCommon* _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 0000000000..e2e47160e9
--- /dev/null
+++ b/vcl/source/window/winproc.cxx
@@ -0,0 +1,2948 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/GestureEventPan.hxx>
+#include <vcl/GestureEventZoom.hxx>
+#include <vcl/GestureEventRotate.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, NotifyEventType 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 == NotifyEventType::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 == NotifyEventType::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 == NotifyEventType::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( NotifyEventType::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, NotifyEventType nSVEvent, bool bMouseLeave,
+ tools::Long nX, tools::Long nY, sal_uInt64 nMsgTime,
+ sal_uInt16 nCode, MouseEventModifiers nMode )
+{
+ SAL_INFO( "vcl.debugevent",
+ "mouse event "
+ "(NotifyEventType " << 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 == NotifyEventType::MOUSEBUTTONDOWN) || (nSVEvent == NotifyEventType::MOUSEBUTTONUP) )
+ {
+ if ( (nSVEvent == NotifyEventType::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, NotifyEventType::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 == NotifyEventType::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 == NotifyEventType::MOUSEMOVE )
+ {
+ ImplHandleMouseHelpRequest( pChild, aMousePos );
+ if( pWinFrameData->mpMouseMoveWin.get() != pChild )
+ nMode |= MouseEventModifiers::ENTERWINDOW;
+ }
+
+ // Call the hook also, if Window is disabled
+
+ if ( nSVEvent == NotifyEventType::MOUSEBUTTONDOWN )
+ return true;
+ else
+ {
+ // Set normal MousePointer for disabled windows
+ if ( nSVEvent == NotifyEventType::MOUSEMOVE )
+ ImplSetMousePointer( pChild );
+
+ return false;
+ }
+ }
+
+ // End ExtTextInput-Mode, if the user click in the same TopLevel Window
+ if (pSVData->mpWinData->mpExtTextInputWin
+ && ((nSVEvent == NotifyEventType::MOUSEBUTTONDOWN)
+ || (nSVEvent == NotifyEventType::MOUSEBUTTONUP)))
+ pSVData->mpWinData->mpExtTextInputWin->EndExtTextInput();
+ }
+
+ // determine mouse event data
+ if ( nSVEvent == NotifyEventType::MOUSEMOVE )
+ {
+ // check if MouseMove belongs to same window and if the
+ // status did not change
+ if ( pChild )
+ {
+ Point aChildMousePos = pChild->ScreenToOutputPixel( 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->ScreenToOutputPixel( 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->ScreenToOutputPixel( aMousePos );
+ MouseEvent aMLeaveEvt( aLeaveMousePos, nClicks, nMode | MouseEventModifiers::LEAVEWINDOW, nCode, nCode );
+ NotifyEvent aNLeaveEvt( NotifyEventType::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 == NotifyEventType::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->ScreenToOutputPixel( 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 == NotifyEventType::MOUSEMOVE )
+ pChild->ImplGetFrameData()->mbInMouseMove = true;
+
+ // bring window into foreground on mouseclick
+ if ( nSVEvent == NotifyEventType::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 == NotifyEventType::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 == NotifyEventType::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 == NotifyEventType::MOUSEMOVE )
+ pChild->ImplGetWindowImpl()->mpFrameData->mbInMouseMove = false;
+
+ if ( nSVEvent == NotifyEventType::MOUSEMOVE )
+ {
+ if ( bCallHelpRequest && !ImplGetSVHelpData().mbKeyboardHelp )
+ ImplHandleMouseHelpRequest( pChild, pChild->OutputToScreenPixel( aMEvt.GetPosPixel() ) );
+ bRet = true;
+ }
+ else if ( !bRet )
+ {
+ if ( nSVEvent == NotifyEventType::MOUSEBUTTONDOWN )
+ {
+ if ( !pChild->ImplGetWindowImpl()->mbMouseButtonDown )
+ bRet = true;
+ }
+ else
+ {
+ if ( !pChild->ImplGetWindowImpl()->mbMouseButtonUp )
+ bRet = true;
+ }
+ }
+
+ if ( nSVEvent == NotifyEventType::MOUSEMOVE )
+ {
+ // set new mouse pointer
+ if ( !bMouseLeave )
+ ImplSetMousePointer( pChild );
+ }
+ else if ( (nSVEvent == NotifyEventType::MOUSEBUTTONDOWN) || (nSVEvent == NotifyEventType::MOUSEBUTTONUP) )
+ {
+ // Command-Events
+ if ( /*!bRet &&*/ (nClicks == 1) && (nSVEvent == NotifyEventType::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 == NotifyEventType::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, NotifyEventType 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->ScreenToOutputPixel(aMousePos);
+
+ pFrameData->mnLastMouseX = nX;
+ pFrameData->mnLastMouseY = nY;
+ pFrameData->mnClickCount = nClicks;
+ pFrameData->mnMouseCode = nCode;
+ pFrameData->mbMouseIn = false;
+
+ vcl::Window* pDragWin = pFrameData->mpMouseDownWin;
+ if (pDragWin &&
+ nEvent == NotifyEventType::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 == NotifyEventType::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->ScreenToOutputPixel(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 == NotifyEventType::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 == NotifyEventType::MOUSEMOVE)
+ {
+ if (pFrameData->mpTrackWin)
+ {
+ TrackingEvent aTrackingEvent(aMouseEvent);
+ pFrameData->mpTrackWin->Tracking(aTrackingEvent);
+ }
+ else
+ xWindow->MouseMove(aMouseEvent);
+ }
+ else if (nEvent == NotifyEventType::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 == NotifyEventType::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, NotifyEventType 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 NotifyEventType::KEYINPUT:
+ nVCLEvent = VclEventId::WindowKeyInput;
+ break;
+ case NotifyEventType::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 == NotifyEventType::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 == NotifyEventType::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 == NotifyEventType::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 == NotifyEventType::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->ScreenToOutputPixel( rPos );
+ CommandEvent aCEvt( aCmdMousePos, CommandEventId::Wheel, true, pWheelData );
+ NotifyEvent aNCmdEvt( NotifyEventType::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 HandleGestureSwipeEvent : public HandleGestureEvent
+{
+private:
+ CommandGestureSwipeData m_aSwipeData;
+public:
+ HandleGestureSwipeEvent(vcl::Window *pWindow, const SalGestureSwipeEvent& 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::GestureSwipe, &m_aSwipeData);
+ }
+};
+
+}
+
+static bool ImplHandleSwipe(vcl::Window *pWindow, const SalGestureSwipeEvent& rEvt)
+{
+ HandleGestureSwipeEvent aHandler(pWindow, rEvt);
+ return aHandler.HandleEvent();
+}
+
+namespace {
+
+class HandleGestureLongPressEvent : public HandleGestureEvent
+{
+private:
+ CommandGestureLongPressData m_aLongPressData;
+public:
+ HandleGestureLongPressEvent(vcl::Window *pWindow, const SalGestureLongPressEvent& 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::GestureLongPress, &m_aLongPressData);
+ }
+};
+
+}
+
+static bool ImplHandleLongPress(vcl::Window *pWindow, const SalGestureLongPressEvent& rEvt)
+{
+ HandleGestureLongPressEvent aHandler(pWindow, rEvt);
+ return aHandler.HandleEvent();
+}
+
+namespace {
+
+class HandleGesturePanEvent : public HandleGestureEvent
+{
+private:
+ CommandGesturePanData m_aGestureData;
+
+public:
+ HandleGesturePanEvent(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::GesturePan, &m_aGestureData);
+ }
+};
+
+}
+
+static bool ImplHandleGestureEvent(vcl::Window* pWindow, const SalGestureEvent& rEvent)
+{
+ HandleGesturePanEvent aHandler(pWindow, rEvent);
+ return aHandler.HandleEvent();
+}
+
+namespace {
+
+class HandleGestureZoomEvent : public HandleGestureEvent
+{
+private:
+ CommandGestureZoomData m_aGestureData;
+
+public:
+ HandleGestureZoomEvent(vcl::Window* pWindow, const SalGestureZoomEvent& rEvent)
+ : HandleGestureEvent(pWindow, Point(rEvent.mnX, rEvent.mnY))
+ , m_aGestureData(rEvent.mnX, rEvent.mnY, rEvent.meEventType, rEvent.mfScaleDelta)
+ {
+ }
+
+ virtual bool CallCommand(vcl::Window* pWindow, const Point& /*rMousePos*/) override
+ {
+ return ImplCallCommand(pWindow, CommandEventId::GestureZoom, &m_aGestureData);
+ }
+};
+
+}
+
+static bool ImplHandleGestureZoomEvent(vcl::Window* pWindow, const SalGestureZoomEvent& rEvent)
+{
+ HandleGestureZoomEvent aHandler(pWindow, rEvent);
+ return aHandler.HandleEvent();
+}
+
+namespace {
+
+class HandleGestureRotateEvent : public HandleGestureEvent
+{
+private:
+ CommandGestureRotateData m_aGestureData;
+
+public:
+ HandleGestureRotateEvent(vcl::Window* pWindow, const SalGestureRotateEvent& rEvent)
+ : HandleGestureEvent(pWindow, Point(rEvent.mnX, rEvent.mnY))
+ , m_aGestureData(rEvent.mnX, rEvent.mnY, rEvent.meEventType, rEvent.mfAngleDelta)
+ {
+ }
+
+ virtual bool CallCommand(vcl::Window* pWindow, const Point& /*rMousePos*/) override
+ {
+ return ImplCallCommand(pWindow, CommandEventId::GestureRotate, &m_aGestureData);
+ }
+};
+
+}
+
+static bool ImplHandleGestureRotateEvent(vcl::Window* pWindow, const SalGestureRotateEvent& rEvent)
+{
+ HandleGestureRotateEvent 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(NotifyEventType::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, NotifyEventType::MOUSEMOVE, true,
+ pEvent->mnX, pEvent->mnY,
+ pEvent->mnTime, pEvent->mnCode,
+ ImplGetMouseMoveMode( pEvent ) );
+}
+
+static bool ImplHandleSalMouseMove( vcl::Window* pWindow, SalMouseEvent const * pEvent )
+{
+ return ImplHandleMouseEvent( pWindow, NotifyEventType::MOUSEMOVE, false,
+ pEvent->mnX, pEvent->mnY,
+ pEvent->mnTime, pEvent->mnCode,
+ ImplGetMouseMoveMode( pEvent ) );
+}
+
+static bool ImplHandleSalMouseButtonDown( vcl::Window* pWindow, SalMouseEvent const * pEvent )
+{
+ return ImplHandleMouseEvent( pWindow, NotifyEventType::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, NotifyEventType::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.Normalize();
+
+ 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->maCursorBound = AbsoluteScreenPixelRectangle();
+
+ 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 );
+ AbsoluteScreenPixelPoint aAbsScreenPos = pChild->OutputToAbsoluteScreenPixel( pChild->ScreenToOutputPixel(aDeviceRect.TopLeft()) );
+ pEvt->maCursorBound = AbsoluteScreenPixelRectangle(aAbsScreenPos, aDeviceRect.GetSize());
+ 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, NotifyEventType::KEYINPUT,
+ pKeyEvt->mnCode, pKeyEvt->mnCharCode, pKeyEvt->mnRepeat, true );
+ }
+ break;
+ case SalEvent::ExternalKeyInput:
+ {
+ KeyEvent const * pKeyEvt = static_cast<KeyEvent const *>(pEvent);
+ bRet = ImplHandleKey( pWindow, NotifyEventType::KEYINPUT,
+ pKeyEvt->GetKeyCode().GetFullCode(), pKeyEvt->GetCharCode(), pKeyEvt->GetRepeat(), false );
+ }
+ break;
+ case SalEvent::KeyUp:
+ {
+ SalKeyEvent const * pKeyEvt = static_cast<SalKeyEvent const *>(pEvent);
+ bRet = ImplHandleKey( pWindow, NotifyEventType::KEYUP,
+ pKeyEvt->mnCode, pKeyEvt->mnCharCode, pKeyEvt->mnRepeat, true );
+ }
+ break;
+ case SalEvent::ExternalKeyUp:
+ {
+ KeyEvent const * pKeyEvt = static_cast<KeyEvent const *>(pEvent);
+ bRet = ImplHandleKey( pWindow, NotifyEventType::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.width() - 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.width(), g.height());
+ }
+ 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::GestureSwipe:
+ bRet = ImplHandleSwipe(pWindow, *static_cast<const SalGestureSwipeEvent*>(pEvent));
+ break;
+
+ case SalEvent::GestureLongPress:
+ bRet = ImplHandleLongPress(pWindow, *static_cast<const SalGestureLongPressEvent*>(pEvent));
+ break;
+
+ case SalEvent::ExternalGesture:
+ {
+ auto const * pGestureEvent = static_cast<GestureEventPan 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::GesturePan:
+ {
+ auto const * aSalGestureEvent = static_cast<SalGestureEvent const *>(pEvent);
+ bRet = ImplHandleGestureEvent(pWindow, *aSalGestureEvent);
+ }
+ break;
+ case SalEvent::GestureZoom:
+ {
+ const auto * pGestureEvent = static_cast<SalGestureZoomEvent const *>(pEvent);
+ bRet = ImplHandleGestureZoomEvent(pWindow, *pGestureEvent);
+ }
+ break;
+ case SalEvent::GestureRotate:
+ {
+ const auto * pGestureEvent = static_cast<SalGestureRotateEvent const *>(pEvent);
+ bRet = ImplHandleGestureRotateEvent(pWindow, *pGestureEvent);
+ }
+ 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 0000000000..a04c46ae29
--- /dev/null
+++ b/vcl/source/window/wrkwin.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 <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
+{
+ vcl::WindowData aData;
+ if (mpWindowImpl->mpFrame->GetWindowState(&aData))
+ return bool(aData.state() & vcl::WindowState::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(vcl::WindowState aFrameState )
+{
+ vcl::WindowData aState;
+ aState.setMask(vcl::WindowDataMask::State);
+ aState.setState(aFrameState);
+ mpWindowImpl->mpFrame->SetWindowState(&aState);
+}
+
+void WorkWindow::Minimize()
+{
+ ImplSetFrameState( vcl::WindowState::Minimized );
+}
+
+void WorkWindow::Restore()
+{
+ ImplSetFrameState( vcl::WindowState::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 ? vcl::WindowState::Maximized : vcl::WindowState::Normal );
+}
+
+bool WorkWindow::IsMaximized() const
+{
+ bool bRet = false;
+
+ vcl::WindowData aState;
+ if( mpWindowImpl->mpFrame->GetWindowState( &aState ) )
+ {
+ if( aState.state() & (vcl::WindowState::Maximized |
+ vcl::WindowState::MaximizedHorz |
+ vcl::WindowState::MaximizedVert ) )
+ bRet = true;
+ }
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/uiconfig/theme_definitions/ios/arrow-down.svg b/vcl/uiconfig/theme_definitions/ios/arrow-down.svg
new file mode 100644
index 0000000000..032df3df64
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/arrow-down.svg
@@ -0,0 +1,5 @@
+<svg version="1.1" viewBox="0 0 11 20" xmlns="http://www.w3.org/2000/svg">
+ <g transform="translate(245.46 49.566)">
+ <path d="m-242.75-44.074h5.5903l-2.7951 8z" fill="#007aff" stroke="#007aff"/>
+ </g>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/arrow-up.svg b/vcl/uiconfig/theme_definitions/ios/arrow-up.svg
new file mode 100644
index 0000000000..524906f310
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/arrow-up.svg
@@ -0,0 +1,5 @@
+<svg version="1.1" viewBox="0 0 11 20" xmlns="http://www.w3.org/2000/svg">
+ <g transform="translate(245.46 49.566)">
+ <path d="m-242.75-35.066h5.5903l-2.7951-7.9855z" fill="#007aff" stroke="#007aff"/>
+ </g>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/combobox-button-disabled.svg b/vcl/uiconfig/theme_definitions/ios/combobox-button-disabled.svg
new file mode 100644
index 0000000000..ccb892d77f
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/combobox-button-disabled.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" viewBox="0 0 35 36" xmlns="http://www.w3.org/2000/svg">
+ <path d="m6.8098e-4 -4e-7v1.2272l-6.8092e-4 33.546v1.2272h33.75c0.68524 0 1.2499-0.55442 1.2499-1.2272v-33.546c0-0.67283-0.56468-1.2272-1.2499-1.2272z" fill="#8e8e93"/>
+ <path d="m17.5 19.091-4.6667-5.0907-1.3333 1.4546 6 6.5454 6-6.5454-1.3333-1.4546z" fill="#f5f5f5"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/combobox-button.svg b/vcl/uiconfig/theme_definitions/ios/combobox-button.svg
new file mode 100644
index 0000000000..b4a1627f38
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/combobox-button.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" viewBox="0 0 35 36" xmlns="http://www.w3.org/2000/svg">
+ <path d="m6.8098e-4 -4e-7v1.2272l-6.8092e-4 33.546v1.2272h33.75c0.68524 0 1.2499-0.55442 1.2499-1.2272v-33.546c0-0.67283-0.56468-1.2272-1.2499-1.2272z" fill="#0273f8"/>
+ <path d="m17.5 19.091-4.6667-5.0907-1.3333 1.4546 6 6.5454 6-6.5454-1.3333-1.4546z" fill="#f5f5f5"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/combobox-disabled.svg b/vcl/uiconfig/theme_definitions/ios/combobox-disabled.svg
new file mode 100644
index 0000000000..e4c1f63599
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/combobox-disabled.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <rect x=".5" y=".5" width="43" height="25" rx="2" ry="2" fill="#fff" stroke="#8e8e93"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/combobox.svg b/vcl/uiconfig/theme_definitions/ios/combobox.svg
new file mode 100644
index 0000000000..24a3b12c2d
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/combobox.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <rect x=".5" y=".5" width="43" height="25" rx="2" ry="2" fill="#fff" stroke="#007aff"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/common-rect-disabled.svg b/vcl/uiconfig/theme_definitions/ios/common-rect-disabled.svg
new file mode 100644
index 0000000000..ee3300ef26
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/common-rect-disabled.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <rect x=".5" y=".5" width="43" height="25" rx="4" ry="4" fill="#fff" stroke="#8e8e93"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/common-rect-focus-slim.svg b/vcl/uiconfig/theme_definitions/ios/common-rect-focus-slim.svg
new file mode 100644
index 0000000000..e641444596
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/common-rect-focus-slim.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <rect x=".5" y=".5" width="43" height="25" rx="4" ry="4" fill="none" stroke="#79b9ff"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/common-rect-focus.svg b/vcl/uiconfig/theme_definitions/ios/common-rect-focus.svg
new file mode 100644
index 0000000000..ca27ee8557
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/common-rect-focus.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <rect x="1.5" y="1.5" width="41" height="23" rx="4" ry="4" fill="none" stroke="#79b9ff" stroke-width="3"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/common-rect.svg b/vcl/uiconfig/theme_definitions/ios/common-rect.svg
new file mode 100644
index 0000000000..667b0960f3
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/common-rect.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <rect x=".5" y=".5" width="43" height="25" rx="4" ry="4" fill="#fff" stroke="#007aff"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/definition.xml b/vcl/uiconfig/theme_definitions/ios/definition.xml
new file mode 100644
index 0000000000..958c85600e
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/definition.xml
@@ -0,0 +1,530 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<widgets>
+ <style>
+ <faceColor value="#F7F7F7"/>
+ <checkedColor value="#C0C0C0"/>
+ <lightColor value="#FFFFFF"/>
+ <lightBorderColor value="#F7F7F7"/>
+ <shadowColor value="#808080"/>
+ <darkShadowColor value="#000000"/>
+ <buttonTextColor value="#007AFF"/>
+ <defaultActionButtonTextColor value="#007AFF"/>
+ <actionButtonTextColor value="#007AFF"/>
+ <actionButtonRolloverTextColor value="#007AFF"/>
+ <buttonRolloverTextColor value="#FFFFFF"/>
+ <radioCheckTextColor value="#000000"/>
+ <groupTextColor value="#000000"/>
+ <labelTextColor value="#000000"/>
+ <windowColor value="#FFFFFF"/>
+ <windowTextColor value="#000000"/>
+ <dialogColor value="#FFFFFF"/>
+ <dialogTextColor value="#000000"/>
+ <workspaceColor value="#F7F7F7"/>
+ <monoColor value="#000000"/>
+ <fieldColor value="#FFFFFF"/>
+ <fieldTextColor value="#000000"/>
+ <fieldRolloverTextColor value="#000000"/>
+ <activeColor value="#007AFF"/>
+ <activeTextColor value="#FFFFFF"/>
+ <activeBorderColor value="#C0C0C0"/>
+ <deactiveColor value="#808080"/>
+ <deactiveTextColor value="#C0C0C0"/>
+ <deactiveBorderColor value="#C0C0C0"/>
+ <menuColor value="#FFFFFF"/>
+ <menuBarColor value="#FFFFFF"/>
+ <menuBarRolloverColor value="#007AFF"/>
+ <menuBorderColor value="#C0C0C0"/>
+ <menuTextColor value="#000000"/>
+ <menuBarTextColor value="#000000"/>
+ <menuBarRolloverTextColor value="#000000"/>
+ <menuBarHighlightTextColor value="#000000"/>
+ <menuHighlightColor value="#007AFF"/>
+ <menuHighlightTextColor value="#FFFFFF"/>
+ <highlightColor value="#007AFF"/>
+ <highlightTextColor value="#FFFFFF"/>
+ <activeTabColor value="#FFFFFF"/>
+ <inactiveTabColor value="#C0C0C0"/>
+ <tabTextColor value="#007AFF"/>
+ <tabRolloverTextColor value="#007AFF"/>
+ <tabHighlightTextColor value="#FFFFFF"/>
+ <disableColor value="#808080"/>
+ <helpColor value="#FFFFE0"/>
+ <helpTextColor value="#000000"/>
+ <linkColor value="#007AFF"/>
+ <visitedLinkColor value="#0464AA"/>
+ <toolTextColor value="#000000"/>
+ <fontColor value="#000000"/>
+ </style>
+
+ <!--
+ Various setting for controls that aren't style colors.
+ Empty "value" attribute or if setting is not present means default will be used.
+ -->
+
+ <settings>
+ <noActiveTabTextRaise value="true"/>
+ <centeredTabs value="true"/>
+ <listBoxEntryMargin value="20"/>
+ <defaultFontSize value="10"/>
+ <titleHeight value="16"/>
+ <floatTitleHeight value="12"/>
+ <listBoxPreviewDefaultLogicWidth value="16"/>
+ <listBoxPreviewDefaultLogicHeight value="16"/>
+ </settings>
+
+ <!--
+ Follows the definitions od various controls.
+ The definition is always in form:
+
+ <{ControlType} attributes...>
+ <{ControlPart} attributes...>
+ <state attributes...>
+ {draw commands}
+ ...
+ </state>
+ </{ControlPart}>
+ </{ControlType}>
+
+ Supported <state> attributes are:
+ enabled="true|false|any"
+ focused="true|false|any"
+ pressed="true|false|any"
+ rollover="true|false|any"
+ default="true|false|any"
+ selected="true|false|any"
+ button-value="true|false|any"
+ extra="{various}"
+
+ control specific:
+ <spinbox> attributes:
+ - orientation: stacked (default), edit-decrease-increase, decrease-edit-increase
+
+ -->
+
+ <pushbutton>
+ <part value="Entire">
+ <state enabled="true">
+ <external source="pushbutton-default.svg" />
+ </state>
+ <state enabled="true" rollover="true">
+ <external source="pushbutton-rollover.svg" />
+ </state>
+ <state enabled="false">
+ <external source="pushbutton-disabled.svg" />
+ </state>
+ </part>
+ <part value="Focus">
+ <state>
+ <external source="common-rect-focus.svg" />
+ </state>
+ </part>
+ </pushbutton>
+
+ <radiobutton>
+ <part value="Entire" width="26" height="26">
+ <state enabled="true" pressed="false" button-value="true">
+ <image source="tick-on.svg" />
+ </state>
+ <state enabled="true" pressed="true" button-value="true">
+ <image source="tick-on-pressed.svg" />
+ </state>
+ <state enabled="false" button-value="true">
+ <image source="tick-on-disabled.svg" />
+ </state>
+ <state enabled="true" pressed="false" button-value="false">
+ <image source="tick-off.svg" />
+ </state>
+ <state enabled="true" pressed="true" button-value="false">
+ <image source="tick-off-pressed.svg" />
+ </state>
+ <state enabled="false" button-value="false">
+ <image source="tick-off-disabled.svg" />
+ </state>
+ </part>
+ <part value="Focus">
+ <state>
+ <external source="common-rect-focus-slim.svg" />
+ </state>
+ </part>
+ </radiobutton>
+
+ <checkbox>
+ <part value="Entire" width="46" height="32">
+ <state enabled="true" pressed="false" button-value="true">
+ <image source="switch-on.svg" />
+ </state>
+ <state enabled="true" pressed="true" button-value="true">
+ <image source="switch-on-pressed.svg" />
+ </state>
+ <state enabled="false" button-value="true">
+ <image source="switch-on-disabled.svg" />
+ </state>
+ <state enabled="true" pressed="false" button-value="false">
+ <image source="switch-off.svg" />
+ </state>
+ <state enabled="true" pressed="true" button-value="false">
+ <image source="switch-off-pressed.svg" />
+ </state>
+ <state enabled="false" button-value="false">
+ <image source="switch-off-disabled.svg" />
+ </state>
+ </part>
+ <part value="Focus">
+ <state>
+ <external source="common-rect-focus-slim.svg" />
+ </state>
+ </part>
+ </checkbox>
+
+ <combobox>
+ <part value="Entire">
+ <state enabled="true">
+ <external source="combobox.svg" />
+ </state>
+ <state enabled="false">
+ <external source="combobox-disabled.svg" />
+ </state>
+ </part>
+ <part value="SubEdit">
+ <state>
+ </state>
+ </part>
+ <part value="ButtonDown" width="35" height="36">
+ <state enabled="true">
+ <image source="combobox-button.svg" />
+ </state>
+ <state enabled="false">
+ <image source="combobox-button-disabled.svg" />
+ </state>
+ </part>
+ <part value="Focus">
+ <state>
+ <external source="common-rect-focus.svg" />
+ </state>
+ </part>
+ </combobox>
+
+ <editbox>
+ <part value="Entire" height="32">
+ <state enabled="true">
+ <external source="common-rect.svg" />
+ </state>
+ <state enabled="false">
+ <external source="common-rect-disabled.svg" />
+ </state>
+ <state focused="true">
+ <external source="common-rect-focus.svg" />
+ </state>
+ </part>
+ </editbox>
+
+ <listbox>
+ <part value="Entire">
+ <state enabled="true">
+ <external source="combobox.svg" />
+ </state>
+ <state enabled="false">
+ <external source="combobox-disabled.svg" />
+ </state>
+ </part>
+ <part value="SubEdit">
+ <state/> <!-- Intentional empty - don't draw anything -->
+ </part>
+ <part value="ButtonDown" width="35" height="36">
+ <state enabled="true">
+ <image source="combobox-button.svg" />
+ </state>
+ <state enabled="false">
+ <image source="combobox-button-disabled.svg" />
+ </state>
+ </part>
+ <part value="Focus">
+ <state>
+ <external source="common-rect-focus.svg" />
+ </state>
+ </part>
+ </listbox>
+
+ <spinbox>
+ <part value="Entire" orientation="decrease-edit-increase">
+ <state>
+ <rect stroke="#ffffff" fill="#ffffff" stroke-width="0" />
+ </state>
+ </part>
+ <part value="SubEdit">
+ <state/> <!-- Intentional empty - don't draw anything -->
+ </part>
+ <part value="ButtonDown" width="44" height="26">
+ <state enabled="true">
+ <external source="spinbox-left.svg" />
+ </state>
+ <state enabled="true" pressed="true">
+ <external source="spinbox-left-pressed.svg" />
+ </state>
+ <state enabled="false">
+ <external source="spinbox-left-disabled.svg" />
+ </state>
+ </part>
+ <part value="ButtonUp" width="44" height="26">
+ <state enabled="true">
+ <external source="spinbox-right.svg" />
+ </state>
+ <state enabled="true" pressed="true">
+ <external source="spinbox-right-pressed.svg" />
+ </state>
+ <state enabled="false">
+ <external source="spinbox-right-disabled.svg" />
+ </state>
+ </part>
+ <part value="Focus">
+ <state>
+ <external source="common-rect-focus-slim.svg" />
+ </state>
+ </part>
+ </spinbox>
+
+ <scrollbar>
+ <part value="ThumbHorz">
+ <state>
+ <external source="scrollbar-horizontal.svg" />
+ </state>
+ </part>
+ <part value="ThumbVert">
+ <state>
+ <external source="scrollbar-vertical.svg" />
+ </state>
+ </part>
+ <part value="ButtonUp">
+ </part>
+ <part value="ButtonDown">
+ </part>
+ <part value="ButtonLeft">
+ </part>
+ <part value="ButtonRight">
+ </part>
+ <part value="TrackHorzLeft">
+ <state>
+ <rect stroke="#8e8e93" fill="#ffffff" stroke-width="0" />
+ </state>
+ </part>
+ <part value="TrackHorzRight">
+ <state>
+ <rect stroke="#8e8e93" fill="#ffffff" stroke-width="0" />
+ </state>
+ </part>
+ <part value="TrackVertUpper">
+ <state>
+ <rect stroke="#8e8e93" fill="#ffffff" stroke-width="0" />
+ </state>
+ </part>
+ <part value="TrackVertLower">
+ <state>
+ <rect stroke="#8e8e93" fill="#ffffff" stroke-width="0" />
+ </state>
+ </part>
+ </scrollbar>
+
+ <slider>
+ <part value="Button">
+ <state enabled="true">
+ <image source="slider-button.svg" />
+ </state>
+ <state enabled="false">
+ <image source="slider-button-disabled.svg" />
+ </state>
+ </part>
+ <part value="TrackHorzLeft">
+ <state enabled="true">
+ <line stroke="#007AFF" stroke-width="6" x1="0.0" y1="0.5" x2="1.0" y2="0.5"/>
+ </state>
+ <state enabled="false">
+ <line stroke="#8e8e93" stroke-width="6" x1="0.0" y1="0.5" x2="1.0" y2="0.5"/>
+ </state>
+ </part>
+ <part value="TrackHorzRight">
+ <state>
+ <line stroke="#8e8e93" stroke-width="6" x1="0.0" y1="0.5" x2="1.0" y2="0.5"/>
+ </state>
+ </part>
+ <part value="TrackVertUpper">
+ <state enabled="true">
+ <line stroke="#007AFF" stroke-width="6" x1="0.5" y1="0.0" x2="0.5" y2="1.0"/>
+ </state>
+ <state enabled="false">
+ <line stroke="#8e8e93" stroke-width="6" x1="0.5" y1="0.0" x2="0.5" y2="1.0"/>
+ </state>
+ </part>
+ <part value="TrackVertLower">
+ <state>
+ <line stroke="#8e8e93" stroke-width="6" x1="0.5" y1="0.0" x2="0.5" y2="1.0"/>
+ </state>
+ </part>
+ </slider>
+
+ <fixedline>
+ <part value="SeparatorHorz">
+ <state>
+ <line stroke="#007AFF" fill="#007AFF" stroke-width="2" x1="0.0" y1="0.5" x2="1.0" y2="0.5"/>
+ </state>
+ </part>
+ <part value="SeparatorVert">
+ <state>
+ <line stroke="#007AFF" fill="#007AFF" stroke-width="2" x1="0.5" y1="0.0" x2="0.5" y2="1.0"/>
+ </state>
+ </part>
+ </fixedline>
+
+ <progress>
+ <part value="Entire">
+ <state>
+ <rect stroke="#007AFF" fill="#007AFF" stroke-width="1" rx="7" ry="7"/>
+ </state>
+ </part>
+ </progress>
+
+ <tabitem>
+ <part value="Entire" margin-width="8" height="32">
+ <state selected="false" extra="first">
+ <external source="tabitem-first.svg" />
+ </state>
+ <state selected="false" extra="middle">
+ <external source="tabitem-middle.svg" />
+ </state>
+ <state selected="false" extra="last">
+ <external source="tabitem-last.svg" />
+ </state>
+ <state selected="true" extra="first">
+ <external source="tabitem-first-selected.svg" />
+ </state>
+ <state selected="true" extra="middle">
+ <external source="tabitem-middle-selected.svg" />
+ </state>
+ <state selected="true" extra="last">
+ <external source="tabitem-last-selected.svg" />
+ </state>
+ </part>
+ </tabitem>
+
+ <tabheader>
+ <part value="Entire">
+ <state>
+ <rect stroke="#FFFFFF" fill="#FFFFFF" stroke-width="1" rx="1" ry="1"/>
+ </state>
+ </part>
+ </tabheader>
+
+ <tabpane>
+ <part value="Entire">
+ <state>
+ <rect stroke="#FFFFFF" fill="#FFFFFF" stroke-width="1" rx="1" ry="1"/>
+ </state>
+ </part>
+ </tabpane>
+
+ <tabbody>
+ <part value="Entire">
+ <state>
+ <rect stroke="#f4f5f5" fill="#FFFFFF" stroke-width="1" rx="1" ry="1"/>
+ </state>
+ </part>
+ </tabbody>
+
+ <windowbackground>
+ <part value="BackgroundWindow">
+ <state>
+ <rect stroke="#f4f5f5" fill="#FFFFFF" stroke-width="1" rx="1" ry="1"/>
+ </state>
+ </part>
+ <part value="BackgroundDialog">
+ <state>
+ <rect stroke="#f4f5f5" fill="#FFFFFF" stroke-width="1" rx="1" ry="1"/>
+ </state>
+ </part>
+ </windowbackground>
+
+ <frame>
+ <part value="Border">
+ <state>
+ <rect stroke="#FFFFFF" fill="#FFFFFF" stroke-width="1" rx="1" ry="1"/>
+ </state>
+ </part>
+ </frame>
+
+ <toolbar>
+ <part value="DrawBackgroundHorz">
+ <state>
+ <rect stroke="#FFFFFF" fill="#FFFFFF" stroke-width="1" rx="1" ry="1"/>
+ </state>
+ </part>
+
+ <part value="DrawBackgroundVert">
+ <state>
+ <rect stroke="#FFFFFF" fill="#FFFFFF" stroke-width="1" rx="1" ry="1"/>
+ </state>
+ </part>
+
+ <part value="ThumbHorz">
+ <state/> <!-- Intentional empty - don't draw anything -->
+ </part>
+
+ <part value="ThumbVert">
+ <state/> <!-- Intentional empty - don't draw anything -->
+ </part>
+
+ <part value="SeparatorVert">
+ <state>
+ <line stroke="#007AFF" fill="#007AFF" stroke-width="2" x1="0.5" y1="0.0" x2="0.5" y2="1.0"/>
+ </state>
+ </part>
+
+ <part value="SeparatorHorz">
+ <state>
+ <line stroke="#007AFF" fill="#007AFF" stroke-width="2" x1="0.0" y1="0.5" x2="1.0" y2="0.5"/>
+ </state>
+ </part>
+
+ <part value="Button">
+ <state enabled="true" button-value="true">
+ <rect stroke="#c0c0c0" fill="#c0c0c0" stroke-width="1" rx="4" ry="4"/>
+ </state>
+ <state enabled="true" button-value="false">
+ <rect stroke="#f4f5f5" fill="#f4f5f5" stroke-width="1" rx="4" ry="4"/>
+ </state>
+ <state enabled="false">
+ <rect stroke="#007AFF" fill="#00FF00" stroke-width="1" rx="4" ry="4"/>
+ </state>
+ </part>
+ </toolbar>
+
+ <listnode>
+ </listnode>
+
+ <listnet>
+ </listnet>
+
+ <listheader>
+ <part value="Button">
+ <state>
+ <rect stroke="#8e8e93" fill="#f4f5f5" stroke-width="1" rx="1" ry="1"/>
+ </state>
+ </part>
+ <part value="Arrow">
+ <state extra="up">
+ <image source="arrow-up.svg"/>
+ </state>
+ <state extra="down">
+ <image source="arrow-down.svg"/>
+ </state>
+ </part>
+ </listheader>
+
+ <menubar>
+ </menubar>
+
+ <menupopup>
+ </menupopup>
+
+ <tooltip>
+ </tooltip>
+</widgets>
diff --git a/vcl/uiconfig/theme_definitions/ios/pushbutton-default.svg b/vcl/uiconfig/theme_definitions/ios/pushbutton-default.svg
new file mode 100644
index 0000000000..667b0960f3
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/pushbutton-default.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <rect x=".5" y=".5" width="43" height="25" rx="4" ry="4" fill="#fff" stroke="#007aff"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/pushbutton-disabled.svg b/vcl/uiconfig/theme_definitions/ios/pushbutton-disabled.svg
new file mode 100644
index 0000000000..ee3300ef26
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/pushbutton-disabled.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <rect x=".5" y=".5" width="43" height="25" rx="4" ry="4" fill="#fff" stroke="#8e8e93"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/pushbutton-rollover.svg b/vcl/uiconfig/theme_definitions/ios/pushbutton-rollover.svg
new file mode 100644
index 0000000000..dad0069ef7
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/pushbutton-rollover.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <rect x=".5" y=".5" width="43" height="25" rx="4" ry="4" fill="#007aff" stroke="#007aff"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/scrollbar-horizontal.svg b/vcl/uiconfig/theme_definitions/ios/scrollbar-horizontal.svg
new file mode 100644
index 0000000000..4bdcd409dc
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/scrollbar-horizontal.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" viewBox="0 0 50 16" xmlns="http://www.w3.org/2000/svg">
+ <rect width="50" height="16" fill="#fff"/>
+ <path d="m43.943 8h-37.886" fill="none" stroke="#8e8e93" stroke-linecap="round" stroke-width="8"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/scrollbar-vertical.svg b/vcl/uiconfig/theme_definitions/ios/scrollbar-vertical.svg
new file mode 100644
index 0000000000..b4a885d913
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/scrollbar-vertical.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" viewBox="0 0 16 50" xmlns="http://www.w3.org/2000/svg">
+ <rect y="1e-7" width="16" height="50" fill="#fff"/>
+ <path d="m8 43.943v-37.886" fill="none" stroke="#8e8e93" stroke-linecap="round" stroke-width="8"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/slider-button-disabled.svg b/vcl/uiconfig/theme_definitions/ios/slider-button-disabled.svg
new file mode 100644
index 0000000000..3c3b9fc56c
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/slider-button-disabled.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg">
+ <rect x="2" y="2" width="24" height="24" rx="12" ry="12" fill="#fff" stroke="#ccc" stroke-width=".92308"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/slider-button.svg b/vcl/uiconfig/theme_definitions/ios/slider-button.svg
new file mode 100644
index 0000000000..66772c7521
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/slider-button.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg">
+ <rect x="2" y="2" width="24" height="24" rx="12" ry="12" fill="#fff" stroke="#8e8e93" stroke-width=".92308"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/spinbox-left-disabled.svg b/vcl/uiconfig/theme_definitions/ios/spinbox-left-disabled.svg
new file mode 100644
index 0000000000..01cac0c3e8
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/spinbox-left-disabled.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <path d="m4.9103 0.5h38.59v25h-38.59c-2.4433 0-4.4103-1.784-4.4103-4v-17c0-2.216 1.967-4 4.4103-4z" fill="#fff" stroke="#8e8e93" stroke-linecap="round"/>
+ <rect x="15" y="12" width="14" height="2" fill="#8e8e93"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/spinbox-left-pressed.svg b/vcl/uiconfig/theme_definitions/ios/spinbox-left-pressed.svg
new file mode 100644
index 0000000000..df8c90cccb
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/spinbox-left-pressed.svg
@@ -0,0 +1,5 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <path d="m4.9103 0.5h38.59v25h-38.59c-2.4433 0-4.4103-1.784-4.4103-4v-17c0-2.216 1.967-4 4.4103-4z" fill="none" stroke="#007aff" stroke-linecap="round"/>
+ <rect x="15" y="12" width="14" height="2" fill="#007aff"/>
+ <path d="m4.9103 0.5h38.59v25h-38.59c-2.4433 0-4.4103-1.784-4.4103-4v-17c0-2.216 1.967-4 4.4103-4z" fill="#007aff" opacity=".15" stroke="#007aff" stroke-linecap="round"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/spinbox-left.svg b/vcl/uiconfig/theme_definitions/ios/spinbox-left.svg
new file mode 100644
index 0000000000..a300f6f3af
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/spinbox-left.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <path d="m4.9103 0.5h38.59v25h-38.59c-2.4433 0-4.4103-1.784-4.4103-4v-17c0-2.216 1.967-4 4.4103-4z" fill="#fff" stroke="#007aff" stroke-linecap="round"/>
+ <rect x="15" y="12" width="14" height="2" fill="#007aff"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/spinbox-right-disabled.svg b/vcl/uiconfig/theme_definitions/ios/spinbox-right-disabled.svg
new file mode 100644
index 0000000000..5eb82b2f8f
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/spinbox-right-disabled.svg
@@ -0,0 +1,5 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <path d="m0.5 0.5h38.59c2.4433 0 4.4103 1.784 4.4103 4v17c0 2.216-1.967 4-4.4103 4h-38.59z" fill="#fff" stroke="#8e8e93" stroke-linecap="round"/>
+ <rect x="15" y="12" width="14" height="2" fill="#8e8e93"/>
+ <rect x="21" y="6" width="2" height="14" fill="#8e8e93"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/spinbox-right-pressed.svg b/vcl/uiconfig/theme_definitions/ios/spinbox-right-pressed.svg
new file mode 100644
index 0000000000..41482a4bc9
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/spinbox-right-pressed.svg
@@ -0,0 +1,6 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <path d="m0.5 0.5h38.59c2.4433 0 4.4103 1.784 4.4103 4v17c0 2.216-1.967 4-4.4103 4h-38.59z" fill="none" stroke="#007aff" stroke-linecap="round"/>
+ <rect x="15" y="12" width="14" height="2" fill="#007aff" />
+ <rect x="21" y="6" width="2" height="14" fill="#007aff" />
+ <path d="m0.5 0.5h38.59c2.4433 0 4.4103 1.784 4.4103 4v17c0 2.216-1.967 4-4.4103 4h-38.59z" fill="#007aff" opacity=".15" stroke="#007aff" stroke-linecap="round"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/spinbox-right.svg b/vcl/uiconfig/theme_definitions/ios/spinbox-right.svg
new file mode 100644
index 0000000000..6e6676052b
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/spinbox-right.svg
@@ -0,0 +1,5 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <path d="m0.5 0.5h38.59c2.4433 0 4.4103 1.784 4.4103 4v17c0 2.216-1.967 4-4.4103 4h-38.59z" fill="#fff" stroke="#007aff" stroke-linecap="round"/>
+ <rect x="15" y="12" width="14" height="2" fill="#007aff"/>
+ <rect x="21" y="6" width="2" height="14" fill="#007aff"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/switch-off-disabled.svg b/vcl/uiconfig/theme_definitions/ios/switch-off-disabled.svg
new file mode 100644
index 0000000000..a24166a767
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/switch-off-disabled.svg
@@ -0,0 +1,14 @@
+<svg version="1.1" viewBox="0 0 46 32" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <filter id="filter864" x="-.072" y="-.072" width="1.144" height="1.144" color-interpolation-filters="sRGB">
+ <feGaussianBlur stdDeviation="0.1905"/>
+ </filter>
+ </defs>
+ <g transform="matrix(3.7783 0 0 3.8564 927.42 199.77)">
+ <g transform="translate(.5911 .076237)" opacity=".5">
+ <rect transform="scale(-1,1)" x="234.59" y="-50.912" width="10.74" height="6.3951" rx="3.175" ry="6.35" fill="none" opacity="1" stroke="#e4e4e4" stroke-width=".3788"/>
+ <circle transform="matrix(-1 0 0 1 -.39821 .23624)" cx="241.75" cy="-47.45" r="3.175" fill="#c6c6c6" filter="url(#filter864)" opacity=".75"/>
+ <circle transform="scale(-1,1)" cx="242.15" cy="-47.714" r="2.9829" fill="#fefeff" opacity="1" stroke="#ebebeb" stroke-opacity=".78431" stroke-width=".13229"/>
+ </g>
+ </g>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/switch-off-pressed.svg b/vcl/uiconfig/theme_definitions/ios/switch-off-pressed.svg
new file mode 100644
index 0000000000..f1f8980962
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/switch-off-pressed.svg
@@ -0,0 +1,15 @@
+<svg version="1.1" viewBox="0 0 46 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <radialGradient id="radialGradient840" cx="241.59" cy="-47.248" r="4.2368" gradientTransform="matrix(.98186 -.0040331 .003525 .85818 4.3485 -5.6203)" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#121212" offset="0"/>
+ <stop stop-color="#c6c6c6" stop-opacity="0" offset="1"/>
+ </radialGradient>
+ </defs>
+ <g transform="matrix(3.7405 0 0 3.8201 918.41 198.09)">
+ <g transform="translate(.6348 .032528)">
+ <rect transform="scale(-1,1)" x="234.59" y="-50.912" width="10.74" height="6.3951" rx="3.175" ry="6.35" fill="#e4e4e4" stroke="#e4e4e4" stroke-width=".3788"/>
+ <rect transform="scale(-1,1)" x="237.15" y="-50.774" width="8.4824" height="7.2644" rx="3.175" ry="6.35" fill="url(#radialGradient840)"/>
+ <rect transform="scale(-1,1)" x="237.91" y="-50.799" width="7.2232" height="6.2779" rx="3.175" ry="6.35" fill="#fff" stroke="#ebebeb" stroke-opacity=".78431" stroke-width=".10751"/>
+ </g>
+ </g>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/switch-off.svg b/vcl/uiconfig/theme_definitions/ios/switch-off.svg
new file mode 100644
index 0000000000..80fda60b95
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/switch-off.svg
@@ -0,0 +1,15 @@
+<svg version="1.1" viewBox="0 0 46 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <radialGradient id="radialGradient858" cx="241.75" cy="-47.45" r="3.6322" gradientTransform="matrix(.95314 .0010684 -.0010429 .93044 11.678 -3.1355)" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#121212" offset="0"/>
+ <stop stop-color="#c6c6c6" stop-opacity="0" offset="1"/>
+ </radialGradient>
+ </defs>
+ <g transform="matrix(3.7783 0 0 3.7628 927.41 195)">
+ <g transform="translate(.59099 .076237)">
+ <rect transform="scale(-1,1)" x="234.59" y="-50.912" width="10.74" height="6.3951" rx="3.175" ry="6.35" fill="#fff" stroke="#e4e4e4" stroke-width=".3788"/>
+ <circle transform="scale(-1,1)" cx="242.15" cy="-47.026" r="3.6322" fill="url(#radialGradient858)"/>
+ <circle transform="scale(-1,1)" cx="242.15" cy="-47.714" r="2.9829" fill="#fff" stroke="#ebebeb" stroke-opacity=".78431" stroke-width=".13229"/>
+ </g>
+ </g>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/switch-on-disabled.svg b/vcl/uiconfig/theme_definitions/ios/switch-on-disabled.svg
new file mode 100644
index 0000000000..931417d621
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/switch-on-disabled.svg
@@ -0,0 +1,15 @@
+<svg version="1.1" viewBox="0 0 46 32" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="linearGradient850" x1="234.4" x2="239.24" y1="-47.714" y2="-47.785" gradientTransform="matrix(3.7774 0 0 3.7628 -883.43 195.28)" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#4cd964" offset="0"/>
+ <stop stop-color="#4ad462" offset="1"/>
+ </linearGradient>
+ <radialGradient id="radialGradient858-0" cx="241.75" cy="-47.45" r="3.6322" gradientTransform="matrix(3.6004 .0040202 -.0039394 3.5011 -840.31 183.48)" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#121212" offset="0"/>
+ <stop stop-color="#c6c6c6" stop-opacity="0" offset="1"/>
+ </radialGradient>
+ </defs>
+ <rect x="2.7091" y="3.7083" width="40.569" height="24.063" rx="11.993" ry="23.894" fill="url(#linearGradient850)" opacity=".5" stroke="#52d667" stroke-width="1.4281"/>
+ <ellipse cx="30.284" cy="18.331" rx="13.72" ry="13.667" fill="url(#radialGradient858-0)" opacity=".5" stroke-width="3.7701"/>
+ <ellipse cx="31.266" cy="15.742" rx="11.268" ry="11.224" fill="#fff" stroke="#ebebeb" stroke-opacity=".78431" stroke-width=".49875"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/switch-on-pressed.svg b/vcl/uiconfig/theme_definitions/ios/switch-on-pressed.svg
new file mode 100644
index 0000000000..b6f7169ba6
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/switch-on-pressed.svg
@@ -0,0 +1,11 @@
+<svg version="1.1" viewBox="0 0 46 32" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <radialGradient id="radialGradient840" cx="241.59" cy="-47.248" r="4.2368" gradientTransform="matrix(3.6941 -.015341 .013262 3.2643 -863.77 176.13)" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#121212" offset="0"/>
+ <stop stop-color="#c6c6c6" stop-opacity="0" offset="1"/>
+ </radialGradient>
+ </defs>
+ <rect x="2.7365" y="3.7209" width="40.407" height="24.326" rx="11.945" ry="24.154" fill="#4bd663" stroke="#4bd763" stroke-width="1.433"/>
+ <rect x="12.105" y="4.3676" width="31.913" height="27.632" rx="11.945" ry="24.154" fill="url(#radialGradient840)" stroke-width="3.783"/>
+ <rect x="15.227" y="4.1508" width="27.176" height="23.88" rx="11.945" ry="24.154" fill="#fff" stroke="#ebebeb" stroke-opacity=".78431" stroke-width=".40671"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/switch-on.svg b/vcl/uiconfig/theme_definitions/ios/switch-on.svg
new file mode 100644
index 0000000000..3c13bd4519
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/switch-on.svg
@@ -0,0 +1,15 @@
+<svg version="1.1" viewBox="0 0 46 32" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="linearGradient850" x1="234.4" x2="239.24" y1="-47.714" y2="-47.785" gradientTransform="matrix(3.7773 0 0 3.7628 -883.38 195.28)" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#4cd964" offset="0"/>
+ <stop stop-color="#4ad462" offset="1"/>
+ </linearGradient>
+ <radialGradient id="radialGradient858-0-7" cx="241.75" cy="-47.45" r="3.6322" gradientTransform="matrix(3.6003 .0040202 -.0039394 3.5011 -840.26 183.48)" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#121212" offset="0"/>
+ <stop stop-color="#c6c6c6" stop-opacity="0" offset="1"/>
+ </radialGradient>
+ </defs>
+ <rect x="2.7336" y="3.7083" width="40.568" height="24.063" rx="11.993" ry="23.894" fill="url(#linearGradient850)" stroke="#52d667" stroke-width="1.4281"/>
+ <ellipse cx="30.308" cy="18.331" rx="13.72" ry="13.667" fill="url(#radialGradient858-0-7)" stroke-width="3.77"/>
+ <ellipse cx="31.29" cy="15.742" rx="11.267" ry="11.224" fill="#fff" stroke="#ebebeb" stroke-opacity=".78431" stroke-width=".49874"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/tabitem-first-selected.svg b/vcl/uiconfig/theme_definitions/ios/tabitem-first-selected.svg
new file mode 100644
index 0000000000..bb4d98cf1c
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/tabitem-first-selected.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <path d="m4.9103 0.5h38.59v25h-38.59c-2.4433 0-4.4103-1.784-4.4103-4v-17c0-2.216 1.967-4 4.4103-4z" fill="#007aff" stroke="#007aff" stroke-linecap="round"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/tabitem-first.svg b/vcl/uiconfig/theme_definitions/ios/tabitem-first.svg
new file mode 100644
index 0000000000..17997e4a59
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/tabitem-first.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <path d="m4.9103 0.5h38.59v25h-38.59c-2.4433 0-4.4103-1.784-4.4103-4v-17c0-2.216 1.967-4 4.4103-4z" fill="#fff" stroke="#007aff" stroke-linecap="round"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/tabitem-last-selected.svg b/vcl/uiconfig/theme_definitions/ios/tabitem-last-selected.svg
new file mode 100644
index 0000000000..6027d35a43
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/tabitem-last-selected.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <path d="m0.5 0.5h38.59c2.4433 0 4.4103 1.784 4.4103 4v17c0 2.216-1.967 4-4.4103 4h-38.59z" fill="#007aff" stroke="#007aff" stroke-linecap="round"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/tabitem-last.svg b/vcl/uiconfig/theme_definitions/ios/tabitem-last.svg
new file mode 100644
index 0000000000..398e1cb2b2
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/tabitem-last.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <path d="m0.5 0.5h38.59c2.4433 0 4.4103 1.784 4.4103 4v17c0 2.216-1.967 4-4.4103 4h-38.59z" fill="#fff" stroke="#007aff" stroke-linecap="round"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/tabitem-middle-selected.svg b/vcl/uiconfig/theme_definitions/ios/tabitem-middle-selected.svg
new file mode 100644
index 0000000000..b1877520eb
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/tabitem-middle-selected.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <rect x=".5" y=".5" width="43" height="25" fill="#007aff" stroke="#007aff"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/tabitem-middle.svg b/vcl/uiconfig/theme_definitions/ios/tabitem-middle.svg
new file mode 100644
index 0000000000..309d850eb8
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/tabitem-middle.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 44 26" xmlns="http://www.w3.org/2000/svg">
+ <rect x=".5" y=".5" width="43" height="25" fill="#fff" stroke="#007aff"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/tick-off-disabled.svg b/vcl/uiconfig/theme_definitions/ios/tick-off-disabled.svg
new file mode 100644
index 0000000000..909397fb55
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/tick-off-disabled.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg">
+ <rect x="-.0060954" y=".0034595" width="26" height="26" rx="13" ry="13" fill="#8ab4e4" stroke-width="3.7795"/>
+ <circle cx="12.995" cy="13.005" r="12" fill="#fafafa" stroke-width="3.7795"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/tick-off-pressed.svg b/vcl/uiconfig/theme_definitions/ios/tick-off-pressed.svg
new file mode 100644
index 0000000000..eeb5475b14
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/tick-off-pressed.svg
@@ -0,0 +1,11 @@
+<svg version="1.1" viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <radialGradient id="radialGradient90581" cx="53.446" cy="74.485" r="3.4396" gradientTransform="matrix(5.9155 0 0 5.5461 -303.16 -400.11)" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#007aff" stop-opacity="0" offset="0"/>
+ <stop stop-color="#646464" stop-opacity=".039216" offset="1"/>
+ </radialGradient>
+ </defs>
+ <rect x="-6.7139e-6" y="6.7139e-7" width="26" height="26" rx="13" ry="13" fill="#0071f0"/>
+ <circle cx="13" cy="13" r="12" fill="#eaf0f6"/>
+ <rect x=".98" y=".981" width="24.038" height="24.038" rx="12.019" ry="12.019" fill="none" stroke="url(#radialGradient90581)" stroke-width=".96154"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/tick-off.svg b/vcl/uiconfig/theme_definitions/ios/tick-off.svg
new file mode 100644
index 0000000000..16d2a51096
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/tick-off.svg
@@ -0,0 +1,13 @@
+<svg version="1.1" viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <radialGradient id="radialGradient88258" cx="45.178" cy="74.485" r="3.175" gradientTransform="matrix(4.4356 -9.663e-7 9.663e-7 4.4356 -187.39 -317.38)" gradientUnits="userSpaceOnUse">
+ <stop offset="0"/>
+ <stop stop-color="#7b7b7b" stop-opacity="0" offset="1"/>
+ </radialGradient>
+ </defs>
+ <g stroke-width="3.7795">
+ <rect x="-.0060954" y=".0034595" width="26" height="26" rx="13" ry="13" fill="#007aff"/>
+ <circle cx="12.995" cy="13.005" r="13" fill="url(#radialGradient88258)"/>
+ <circle cx="12.995" cy="13.005" r="12" fill="#fff"/>
+ </g>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/tick-on-disabled.svg b/vcl/uiconfig/theme_definitions/ios/tick-on-disabled.svg
new file mode 100644
index 0000000000..0db53e3d4c
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/tick-on-disabled.svg
@@ -0,0 +1,11 @@
+<svg version="1.1" viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <radialGradient id="radialGradient91656" cx="53.446" cy="74.485" r="3.4396" gradientTransform="matrix(7.9952 3.0076e-6 -2.4334e-6 6.4688 -414.31 -468.83)" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#007aff" stop-opacity="0" offset="0"/>
+ <stop stop-color="#646464" stop-opacity=".19608" offset="1"/>
+ </radialGradient>
+ </defs>
+ <rect x="-1.3449e-5" y="7.8907e-6" width="26" height="26" rx="13" ry="13" fill="#8ab4e4"/>
+ <path d="m5.25 12.813 1-1 4.625 4.625 8.875-8.875 0.99999 1-9.875 9.875z" fill="#fff"/>
+ <rect x=".49999" y=".5" width="25" height="25" rx="12.5" ry="12.5" fill="none" stroke="url(#radialGradient91656)"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/tick-on-pressed.svg b/vcl/uiconfig/theme_definitions/ios/tick-on-pressed.svg
new file mode 100644
index 0000000000..cf02114dd1
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/tick-on-pressed.svg
@@ -0,0 +1,11 @@
+<svg version="1.1" viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <radialGradient id="radialGradient90581" cx="53.446" cy="74.485" r="3.4396" gradientTransform="matrix(6.1521 0 0 5.7679 -315.8 -416.63)" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#007aff" stop-opacity="0" offset="0"/>
+ <stop stop-color="#646464" stop-opacity=".19608" offset="1"/>
+ </radialGradient>
+ </defs>
+ <rect x="-1.3449e-5" y="-6.9939e-6" width="26" height="26" rx="13" ry="13" fill="#0071f0"/>
+ <path d="m5.25 12.812 1-1 4.625 4.625 8.875-8.875 0.99999 1-9.875 9.875z" fill="#dfeaf7"/>
+ <rect x=".49999" y=".5" width="25" height="25" rx="12.5" ry="12.5" fill="none" stroke="url(#radialGradient90581)"/>
+</svg>
diff --git a/vcl/uiconfig/theme_definitions/ios/tick-on.svg b/vcl/uiconfig/theme_definitions/ios/tick-on.svg
new file mode 100644
index 0000000000..f812bcba77
--- /dev/null
+++ b/vcl/uiconfig/theme_definitions/ios/tick-on.svg
@@ -0,0 +1,13 @@
+<svg version="1.1" viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <radialGradient id="radialGradient90581" cx="53.446" cy="74.485" r="3.4396" gradientTransform="matrix(7.9952 3.0076e-6 -2.4334e-6 6.4688 -659.77 -519.98)" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#007aff" stop-opacity="0" offset="0"/>
+ <stop stop-color="#646464" stop-opacity=".19608" offset="1"/>
+ </radialGradient>
+ </defs>
+ <g transform="translate(245.46 51.154)">
+ <rect x="-245.46" y="-51.154" width="26" height="26" rx="13" ry="13" fill="#007aff"/>
+ <path d="m-240.21-38.341 1-0.99999 4.625 4.625 8.875-8.8749 0.99999 0.99999-9.875 9.8749z" fill="#fff"/>
+ <rect x="-244.96" y="-50.654" width="25" height="25" rx="12.5" ry="12.5" fill="none" stroke="url(#radialGradient90581)"/>
+ </g>
+</svg>
diff --git a/vcl/uiconfig/ui/aboutbox.ui b/vcl/uiconfig/ui/aboutbox.ui
new file mode 100644
index 0000000000..c97ed6b970
--- /dev/null
+++ b/vcl/uiconfig/ui/aboutbox.ui
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkBox" id="about">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkImage" id="logo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon-name">missing-image</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="logoreplacement">
+ <property name="can_focus">False</property>
+ <property name="no_show_all">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <!-- n-columns=1 n-rows=1 -->
+ <object class="GtkGrid" id="grid1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <child>
+ <object class="GtkTextView" id="version">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="editable">False</property>
+ <property name="wrap_mode">word</property>
+ <property name="justification">center</property>
+ <property name="cursor_visible">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLinkButton" id="buildIdLink">
+ <property name="label" translatable="yes" context="aboutdialog|buildIdLink">See Log: $GITHASH</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="relief">none</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="description">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="justify">center</property>
+ <property name="wrap">True</property>
+ <property name="max_width_chars">62</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <!-- n-columns=1 n-rows=1 -->
+ <object class="GtkGrid" id="grid2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <child>
+ <object class="GtkLabel" id="copyright">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="hexpand">True</property>
+ <property name="justify">center</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/combobox.ui b/vcl/uiconfig/ui/combobox.ui
new file mode 100644
index 0000000000..23cfe7ed9e
--- /dev/null
+++ b/vcl/uiconfig/ui/combobox.ui
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.2 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkBox" id="box">
+ <property name="name">combobox</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="no_show_all">True</property>
+ <child>
+ <object class="GtkEntry" id="entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="no_show_all">True</property>
+ <property name="activates_default">True</property>
+ <property name="truncate-multiline">True</property>
+ <style>
+ <class name="combo"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="no_show_all">True</property>
+ <property name="always_show_image">True</property>
+ <property name="draw_indicator">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImage" id="arrow">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">pan-down-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="combo"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <object class="GtkMenuButton" id="overlaybutton">
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="no_show_all">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="use_popover">False</property>
+ <child>
+ <object class="GtkImage" id="overlayarrow">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">pan-down-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <object class="GtkWindow" id="popup">
+ <property name="name">gtk-combobox-popup-window</property>
+ <property name="can_focus">False</property>
+ <property name="type">popup</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="type_hint">combo</property>
+ <child type="titlebar">
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkOverlay" id="overlay">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="shadow_type">in</property>
+ <property name="overlay_scrolling">False</property>
+ <child>
+ <object class="GtkTreeView" id="treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="enable_search">False</property>
+ <property name="search_column">0</property>
+ <property name="show_expanders">False</property>
+ <property name="activate_on_single_click">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="index">-1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/cupspassworddialog.ui b/vcl/uiconfig/ui/cupspassworddialog.ui
new file mode 100644
index 0000000000..f01691ba74
--- /dev/null
+++ b/vcl/uiconfig/ui/cupspassworddialog.ui
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkDialog" id="CUPSPasswordDialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="title" translatable="yes" context="cupspassworddialog|CUPSPasswordDialog">Authentication Request</property>
+ <property name="modal">True</property>
+ <property name="default_width">0</property>
+ <property name="default_height">0</property>
+ <property name="type_hint">normal</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="ok">
+ <property name="label" translatable="yes" context="stock">_OK</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="cancel">
+ <property name="label" translatable="yes" context="stock">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <!-- n-columns=1 n-rows=1 -->
+ <object class="GtkGrid" id="grid1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="cupspassworddialog|label1">_User:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">user</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="cupspassworddialog|label2">_Password:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">pass</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="text">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="cupspassworddialog|text">Please enter your authentication data for server “%sâ€</property>
+ <property name="use_underline">True</property>
+ <property name="wrap">True</property>
+ <property name="max_width_chars">56</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="user">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="truncate-multiline">True</property>
+ <property name="activates_default">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="pass">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="visibility">False</property>
+ <property name="activates_default">True</property>
+ <property name="truncate-multiline">True</property>
+ <property name="input_purpose">password</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="can_focus">False</property>
+ <property name="no_show_all">True</property>
+ <property name="label" translatable="yes" context="cupspassworddialog|label1">_Domain:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">domain</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="domain">
+ <property name="can_focus">True</property>
+ <property name="no_show_all">True</property>
+ <property name="hexpand">True</property>
+ <property name="truncate-multiline">True</property>
+ <property name="activates_default">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-5">ok</action-widget>
+ <action-widget response="-6">cancel</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/dockingwindow.ui b/vcl/uiconfig/ui/dockingwindow.ui
new file mode 100644
index 0000000000..270946f7ea
--- /dev/null
+++ b/vcl/uiconfig/ui/dockingwindow.ui
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkWindow" id="DockingWindow">
+ <property name="can-focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="border-width">6</property>
+ <property name="type-hint">dock</property>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/editmenu.ui b/vcl/uiconfig/ui/editmenu.ui
new file mode 100644
index 0000000000..e0d55fb8cc
--- /dev/null
+++ b/vcl/uiconfig/ui/editmenu.ui
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkMenu" id="menu">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuItem" id="undo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="editmenu|undo">_Undo</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="menuitem6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="cut">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="editmenu|cut">Cu_t</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="copy">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="editmenu|copy">_Copy</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="paste">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="editmenu|paste">_Paste</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="delete">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="editmenu|delete">_Delete</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="menuitem1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="selectall">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="editmenu|selectall">Select _All</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="specialchar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/errornocontentdialog.ui b/vcl/uiconfig/ui/errornocontentdialog.ui
new file mode 100644
index 0000000000..2cd22a4108
--- /dev/null
+++ b/vcl/uiconfig/ui/errornocontentdialog.ui
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.2 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkMessageDialog" id="ErrorNoContentDialog">
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes" context="errornocontentdialog|ErrorNoContentDialog">%PRODUCTNAME</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="message_type">error</property>
+ <property name="buttons">ok</property>
+ <property name="text" translatable="yes" context="errornocontentdialog|ErrorNoContentDialog">There are no pages to be printed.</property>
+ <property name="secondary_text" translatable="yes" context="errornocontentdialog|ErrorNoContentDialog">Please check your document for ranges relevant to printing.</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="messagedialog-vbox">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="messagedialog-action_area">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/errornoprinterdialog.ui b/vcl/uiconfig/ui/errornoprinterdialog.ui
new file mode 100644
index 0000000000..313aa9d171
--- /dev/null
+++ b/vcl/uiconfig/ui/errornoprinterdialog.ui
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.2 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkMessageDialog" id="ErrorNoPrinterDialog">
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes" context="errornoprinterdialog|ErrorNoPrinterDialog">%PRODUCTNAME</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="message_type">error</property>
+ <property name="buttons">ok</property>
+ <property name="text" translatable="yes" context="errornoprinterdialog|ErrorNoPrinterDialog">No default printer found.</property>
+ <property name="secondary_text" translatable="yes" context="errornoprinterdialog|ErrorNoPrinterDialog">Please choose a printer and try again.</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="messagedialog-vbox">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="messagedialog-action_area">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/interimdockparent.ui b/vcl/uiconfig/ui/interimdockparent.ui
new file mode 100644
index 0000000000..146973b6c1
--- /dev/null
+++ b/vcl/uiconfig/ui/interimdockparent.ui
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkWindow" id="InterimDockParent">
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="border_width">4</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">popup-menu</property>
+ <property name="deletable">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/interimtearableparent.ui b/vcl/uiconfig/ui/interimtearableparent.ui
new file mode 100644
index 0000000000..4ab25d2e9b
--- /dev/null
+++ b/vcl/uiconfig/ui/interimtearableparent.ui
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.36.0 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkWindow" id="InterimTearableParent">
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="border_width">4</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">True</property>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ <child type="titlebar">
+ <placeholder/>
+ </child>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/menutogglebutton3.ui b/vcl/uiconfig/ui/menutogglebutton3.ui
new file mode 100644
index 0000000000..158c5afe9b
--- /dev/null
+++ b/vcl/uiconfig/ui/menutogglebutton3.ui
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.36.0 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkBox" id="box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="no_show_all">True</property>
+ <child>
+ <object class="GtkToggleButton" id="togglebutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <property name="always_show_image">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="menubutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="always_show_image">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/menutogglebutton4.ui b/vcl/uiconfig/ui/menutogglebutton4.ui
new file mode 100644
index 0000000000..cfecfda02c
--- /dev/null
+++ b/vcl/uiconfig/ui/menutogglebutton4.ui
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkBox" id="box">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="hexpand">False</property>
+ <child>
+ <object class="GtkToggleButton" id="togglebutton">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="receives-default">True</property>
+ <property name="hexpand">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="separator">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="menubutton">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="icon-name">pan-down-symbolic</property>
+ <property name="receives-default">True</property>
+ </object>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/openlockedquerybox.ui b/vcl/uiconfig/ui/openlockedquerybox.ui
new file mode 100644
index 0000000000..680698d2ec
--- /dev/null
+++ b/vcl/uiconfig/ui/openlockedquerybox.ui
@@ -0,0 +1,266 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.40.0 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkDialog" id="OpenLockedQueryBox">
+ <property name="can-focus">False</property>
+ <property name="border-width">6</property>
+ <property name="title" translatable="yes" context="openlockedquerybox|OpenLockedQueryBox">Document in Use</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="destroy-with-parent">True</property>
+ <property name="type-hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="VerticalBox">
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="ButtonContainer">
+ <property name="can-focus">False</property>
+ <property name="layout-style">end</property>
+ <child>
+ <object class="GtkButton" id="open">
+ <property name="label" translatable="yes" context="openlockedquerybox|open">_Open</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="cancel">
+ <property name="label" translatable="yes" context="openlockedquerybox|cancel">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack-type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="MainPanel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="spacing">24</property>
+ <child>
+ <object class="GtkImage" id="questionmark">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <property name="margin-start">24</property>
+ <property name="margin-top">70</property>
+ <property name="icon-name">vcl/res/help.png</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="questionmark-atkobject">
+ <property name="AtkObject::accessible-name" translatable="yes" context="openlockedquerybox|questionmark">Question mark image</property>
+ <property name="AtkObject::accessible-description" translatable="yes" context="openlockedquerybox|questionmark">Question mark icon for dialog box.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <!-- n-columns=2 n-rows=4 -->
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-end">6</property>
+ <property name="margin-top">6</property>
+ <property name="row-spacing">18</property>
+ <property name="column-spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="messagetext">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="openlockedquerybox|messagetext">This file has been locked by another user.</property>
+ <property name="width-chars">0</property>
+ <property name="max-width-chars">0</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="messagetext-atkobject">
+ <property name="AtkObject::accessible-role">static</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ReadOnlyMessage">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="valign">start</property>
+ <property name="label" translatable="yes" context="openlockedquerybox|ReadOnlyMessage">You can open it read only and
+receive a notification if ready.</property>
+ <property name="xalign">0</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="ReadOnlyMessage-atkobject">
+ <property name="AtkObject::accessible-role">static</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="OpenCopyMessage">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="openlockedquerybox|OpenCopyMessage">You can open a copy on your
+local system.</property>
+ <property name="xalign">0</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="OpenCopyMessage-atkobject">
+ <property name="AtkObject::accessible-role">static</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkExpander" id="moredetailsexpander">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="resize-toplevel">True</property>
+ <child>
+ <object class="GtkLabel" id="hiddentext">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-top">10</property>
+ <property name="xalign">0</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="hiddentext-atkobject">
+ <property name="AtkObject::accessible-role">static</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="expandertext">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="openlockedquerybox|expandertext">_More Details</property>
+ <property name="use-underline">True</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="opencopy">
+ <property name="label" translatable="yes" context="openlockedquerybox|opencopy">Open Co_py</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkButton" id="readonly">
+ <property name="label" translatable="yes" context="openlockedquerybox|readonly">Open _R/O</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="notify">
+ <property name="label" translatable="yes" context="openlockedquerybox|notify">_Notify</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="OpenLockedQueryBox-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="openlockedquerybox|OpenLockedQueryBox">Displays information about locked files and further steps to take when opening a locked file.</property>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/printdialog.ui b/vcl/uiconfig/ui/printdialog.ui
new file mode 100644
index 0000000000..6f0aa80d05
--- /dev/null
+++ b/vcl/uiconfig/ui/printdialog.ui
@@ -0,0 +1,1481 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.40.0 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="lower">10</property>
+ <property name="upper">200</property>
+ <property name="value">100</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment2">
+ <property name="lower">1</property>
+ <property name="upper">16384</property>
+ <property name="value">1</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment3">
+ <property name="lower">1</property>
+ <property name="upper">1000</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment4">
+ <property name="lower">1</property>
+ <property name="upper">1000</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment5">
+ <property name="upper">1000</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment6">
+ <property name="upper">1000</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ </object>
+ <object class="GtkImage" id="imgBack">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="icon-name">go-previous</property>
+ </object>
+ <object class="GtkImage" id="imgFirst">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="icon-name">go-first</property>
+ </object>
+ <object class="GtkImage" id="imgForward">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="icon-name">go-next</property>
+ </object>
+ <object class="GtkImage" id="imgLast">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="icon-name">go-last</property>
+ </object>
+ <object class="GtkDialog" id="PrintDialog">
+ <property name="can-focus">False</property>
+ <property name="border-width">6</property>
+ <property name="title" translatable="yes" context="printdialog|PrintDialog">Print</property>
+ <property name="modal">True</property>
+ <property name="default-width">0</property>
+ <property name="default-height">0</property>
+ <property name="type-hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="PrintDialogBox">
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="internalarea">
+ <property name="can-focus">False</property>
+ <property name="layout-style">end</property>
+ <child>
+ <object class="GtkButton" id="help">
+ <property name="label" translatable="yes" context="stock">_Help</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ <property name="secondary">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="ok">
+ <property name="label" translatable="yes" context="printdialog|print">_Print</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="can-default">True</property>
+ <property name="has-default">True</property>
+ <property name="receives-default">True</property>
+ <property name="use-underline">True</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="cancel">
+ <property name="label" translatable="yes" context="stock">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkBox" id="box2">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-end">6</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkBox" id="box13">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkDrawingArea" id="preview">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="events">GDK_STRUCTURE_MASK | GDK_SCROLL_MASK</property>
+ <property name="tooltip-text" translatable="yes" context="printdialog|printpreview">Print preview</property>
+ <property name="margin-end">6</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="preview-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|preview">The preview shows how each sheet of paper will look. You can browse through all sheets of paper with the buttons below the preview.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box3">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="hexpand">True</property>
+ <child>
+ <object class="GtkBox" id="box10">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkCheckButton" id="previewbox">
+ <property name="label" translatable="yes" context="printdialog|previewbox">Pre_view</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="active">True</property>
+ <property name="draw-indicator">True</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="previewbox-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|previewbox">Turn on or off display of the print preview.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack-type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box14">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">end</property>
+ <property name="hexpand">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="btnFirst">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="tooltip-text" translatable="yes" context="printdialog|firstpage">First page</property>
+ <property name="image">imgFirst</property>
+ <property name="always-show-image">True</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="btnFirst-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|btnFirst">Shows preview of the first page.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="backward">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="tooltip-text" translatable="yes" context="printdialog|backward">Previous page</property>
+ <property name="image">imgBack</property>
+ <property name="always-show-image">True</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="backward-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|backward">Shows preview of the previous page.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="pageedit">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="width-chars">3</property>
+ <property name="text">1</property>
+ <property name="truncate-multiline">True</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="pageedit-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|pageedit">Enter the number of page to be shown in the preview.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="totalnumpages">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printdialog|totalnumpages">/ %n</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="forward">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="tooltip-text" translatable="yes" context="printdialog|forward">Next page</property>
+ <property name="image">imgForward</property>
+ <property name="always-show-image">True</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="forward-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|forward">Shows preview of the next page.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="btnLast">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="tooltip-text" translatable="yes" context="printdialog|lastpage">Last page</property>
+ <property name="image">imgLast</property>
+ <property name="always-show-image">True</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="btnLast-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|btnLast">Shows preview of the last page.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack-type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack-type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="tabcontrol">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="margin-start">6</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="hscrollbar-policy">never</property>
+ <property name="shadow-type">in</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkBox" id="box11">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="border-width">6</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkFrame" id="frPrinterName">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label-xalign">0</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkBox" id="box5">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="margin-top">6</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkComboBoxText" id="printersbox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="printersbox-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|printersbox">The list box shows the installed printers. Click the printer to use for the current print job. Click the Properties button to change some of the printer properties.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box6">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="labelstatus">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printdialog|labelstatus">Status:</property>
+ <accessibility>
+ <relation type="label-for" target="status"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="status">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printdialog|status">Default Printer</property>
+ <accessibility>
+ <relation type="labelled-by" target="labelstatus"/>
+ </accessibility>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="status-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|status">Shows the availability of the selected printer.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="setup">
+ <property name="label" translatable="yes" context="printdialog|setup">Properties...</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="halign">end</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="setup-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|setup">Opens the Printer Properties dialog. The printer properties vary according to the printer that you select.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack-type">end</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="labelprinter">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printdialog|labelprinter">Printer</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">printersbox</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frPrintRange">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label-xalign">0</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <!-- n-columns=1 n-rows=2 -->
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="margin-top">6</property>
+ <property name="row-spacing">6</property>
+ <child>
+ <!-- n-columns=2 n-rows=3 -->
+ <object class="GtkGrid" id="rangegrid">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="row-spacing">3</property>
+ <property name="column-spacing">6</property>
+ <child>
+ <object class="GtkRadioButton" id="rbAllPages">
+ <property name="label" translatable="yes" context="printdialog|rbAllPages">_All pages</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="halign">start</property>
+ <property name="margin-bottom">2</property>
+ <property name="use-underline">True</property>
+ <property name="active">True</property>
+ <property name="draw-indicator">True</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="rbAllPages-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|rbAllPages">Prints the entire document.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="rbRangePages">
+ <property name="label" translatable="yes" context="printdialog|rbPageRange">_Pages:</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="halign">start</property>
+ <property name="margin-top">2</property>
+ <property name="margin-bottom">2</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <property name="group">rbAllPages</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="rbRangePages-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|rbRangePages">Prints only the pages or slides that you specify in the Pages box.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="pagerange">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="activates-default">True</property>
+ <property name="truncate-multiline">True</property>
+ <property name="placeholder-text" translatable="yes" context="printdialog|pagerange">e.g.: 1, 3-5, 7, 9</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="pagerange-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|pagerange">To print a range of pages, use a format like 3-6. To print single pages, use a format like 7;9;11. You can print a combination of page ranges and single pages, by using a format like 3-6;8;10;12.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="rbRangeSelection">
+ <property name="label" translatable="yes" context="printdialog|rbRangeSelection">_Selection</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="halign">start</property>
+ <property name="margin-bottom">2</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <property name="group">rbAllPages</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="rbRangeSelection-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|rbRangeSelection">Prints only the selected area(s) or object(s) in the current document.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="labelcopies">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes" context="printdialog|labelcopies">_Number of copies:</property>
+ <property name="use-underline">True</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="copycount">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <property name="activates-default">True</property>
+ <property name="text">1</property>
+ <property name="truncate-multiline">True</property>
+ <property name="adjustment">adjustment2</property>
+ <property name="value">1</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="copycount-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|copycount">Enter the number of copies that you want to print.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkExpander" id="exRangeExpander">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="hexpand">True</property>
+ <child>
+ <!-- n-columns=2 n-rows=6 -->
+ <object class="GtkGrid" id="gdCopiesExtra">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-top">6</property>
+ <property name="row-spacing">3</property>
+ <property name="column-spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="fromwhich">
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes" context="printdialog|fromwhich">_From which print:</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">printextrabox</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="printextrabox">
+ <property name="can-focus">False</property>
+ <property name="hexpand">True</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="labelpapersides">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes" context="printdialog|labelpapersides">Paper _sides:</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">sidesbox</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="sidesbox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="hexpand">True</property>
+ <items>
+ <item translatable="yes" context="printdialog|liststore4">Print on one side (simplex)</item>
+ <item translatable="yes" context="printdialog|liststore4">Print on both sides (duplex long edge)</item>
+ <item translatable="yes" context="printdialog|liststore4">Print on both sides (duplex short edge)</item>
+ </items>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="sidesbox-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|sidesbox">If the printer is capable of duplex printing it's possible to choose between using only one side of the paper or both.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="cbPrintOrder">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes" context="printdialog|cbPrintOrder">Order:</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">collate</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="reverseorder">
+ <property name="label" translatable="yes" context="printdialog|reverseorder">Print in _reverse order</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="halign">start</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="reverseorder-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|reverseorder">Check to print pages in reverse order.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box9">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkCheckButton" id="collate">
+ <property name="label" translatable="yes" context="printdialog|collate">_Collate</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="collate-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|collate">Preserves the page order of the original document.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="collateimage">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="icon-name">missing-image</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="singlejobs">
+ <property name="label" translatable="yes" context="printdialog|singlejobs">Create separate print jobs for collated output</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="singlejobs-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|collate">Check to not rely on the printer to create collated copies but create a print job for each copy instead.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="includeevenodd">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes" context="printdialog|includeevenodd">Include:</property>
+ <property name="use-underline">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="evenoddbox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="active">0</property>
+ <items>
+ <item translatable="yes" context="printdialog|liststore3">Odd and Even Pages</item>
+ <item translatable="yes" context="printdialog|liststore3">Odd Pages</item>
+ <item translatable="yes" context="printdialog|liststore3">Even Pages</item>
+ </items>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="evenoddbox-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|evenoddbox">Select the subset of pages to print.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="lbRangeExpander">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printdialog|rangeexpander">Collation and Paper Sides</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printdialog|label2">Range and Copies</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="layoutframe">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label-xalign">0</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <!-- n-columns=1 n-rows=2 -->
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="margin-top">6</property>
+ <property name="row-spacing">6</property>
+ <child>
+ <!-- n-columns=2 n-rows=2 -->
+ <object class="GtkGrid" id="pagegrid">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="labelorientation">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes" context="printdialog|labelorientation">Orientation:</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">pageorientationbox</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="labelsize">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes" context="printdialog|labelsize">Paper size:</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">papersizebox</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="pageorientationbox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="active">0</property>
+ <items>
+ <item translatable="yes" context="printdialog|liststore3">Automatic</item>
+ <item translatable="yes" context="printdialog|liststore3">Portrait</item>
+ <item translatable="yes" context="printdialog|liststore3">Landscape</item>
+ </items>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="pageorientationbox-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|pageorientationbox">Select the orientation of the paper.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="papersizebox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="hexpand">True</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="papersizebox-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|papersizebox">Set the paper size you would like to use. The preview will show how the document would look on a paper of the given size.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkExpander" id="exLayoutExpander">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="hexpand">True</property>
+ <child>
+ <!-- n-columns=5 n-rows=7 -->
+ <object class="GtkGrid" id="expandpagegrid">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-top">6</property>
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">6</property>
+ <child>
+ <object class="GtkBox" id="box12">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="valign">center</property>
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkRadioButton" id="pagespersheetbtn">
+ <property name="label" translatable="yes" context="printdialog|pagespersheetbtn">Pages per sheet:</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <property name="use-underline">True</property>
+ <property name="active">True</property>
+ <property name="draw-indicator">True</property>
+ <accessibility>
+ <relation type="label-for" target="pagespersheetbox"/>
+ </accessibility>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="pagespersheetbtn-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|pagespersheetbtn">Print multiple pages per sheet of paper.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="pagespersheettxt">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="pagespersheetbox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="valign">center</property>
+ <property name="hexpand">True</property>
+ <property name="active">0</property>
+ <items>
+ <item id="1" context="printdialog|liststore1">1</item>
+ <item id="2" context="printdialog|liststore1">2</item>
+ <item id="4" context="printdialog|liststore1">4</item>
+ <item id="6" context="printdialog|liststore1">6</item>
+ <item id="9" context="printdialog|liststore1">9</item>
+ <item id="16" context="printdialog|liststore1">16</item>
+ <item id="65535" translatable="yes" context="printdialog|liststore1">Custom</item>
+ </items>
+ <accessibility>
+ <relation type="labelled-by" target="pagespersheetbtn"/>
+ </accessibility>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="pagespersheetbox-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|pagespersheetbox">Select how many pages to print per sheet of paper.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="pagestxt">
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printdialog|pagespersheettxt">Pages:</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">pagerows</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="pagerows">
+ <property name="can-focus">True</property>
+ <property name="activates-default">True</property>
+ <property name="text">1</property>
+ <property name="truncate-multiline">True</property>
+ <property name="adjustment">adjustment3</property>
+ <property name="value">1</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="pagerows-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|pagerows">Select number of rows.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="by">
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printdialog|by">by</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">pagecols</property>
+ </object>
+ <packing>
+ <property name="left-attach">2</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="pagecols">
+ <property name="can-focus">True</property>
+ <property name="activates-default">True</property>
+ <property name="text">1</property>
+ <property name="truncate-multiline">True</property>
+ <property name="adjustment">adjustment4</property>
+ <property name="value">1</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="pagecols-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|pagecols">Select number of columns.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">3</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="pagemargintxt1">
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printdialog|pagemargintxt1">Margin:</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">pagemarginsb</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="pagemarginsb">
+ <property name="can-focus">True</property>
+ <property name="activates-default">True</property>
+ <property name="text">0</property>
+ <property name="truncate-multiline">True</property>
+ <property name="adjustment">adjustment5</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="pagemarginsb-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|pagemarginsb">Select margin between individual pages on each sheet of paper.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="pagemargintxt2">
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes" context="printdialog|pagemargintxt2">between pages</property>
+ </object>
+ <packing>
+ <property name="left-attach">2</property>
+ <property name="top-attach">2</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="sheetmargintxt1">
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printdialog|sheetmargintxt1">Distance:</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">sheetmarginsb</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="sheetmarginsb">
+ <property name="can-focus">True</property>
+ <property name="activates-default">True</property>
+ <property name="text">0</property>
+ <property name="truncate-multiline">True</property>
+ <property name="adjustment">adjustment6</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="sheetmarginsb-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|sheetmarginsb">Select margin between the printed pages and paper edge.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="sheetmargintxt2">
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes" context="printdialog|sheetmargintxt2">to sheet border</property>
+ </object>
+ <packing>
+ <property name="left-attach">2</property>
+ <property name="top-attach">3</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="labelorder">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="valign">center</property>
+ <property name="label" translatable="yes" context="printdialog|labelorder">Order:</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">orderbox</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="orderbox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="valign">center</property>
+ <property name="hexpand">True</property>
+ <items>
+ <item translatable="yes" context="printdialog|liststore2">Left to right, then down</item>
+ <item translatable="yes" context="printdialog|liststore2">Top to bottom, then right</item>
+ <item translatable="yes" context="printdialog|liststore2">Top to bottom, then left</item>
+ <item translatable="yes" context="printdialog|liststore2">Right to left, then down</item>
+ </items>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="orderbox-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|orderbox">Select order in which pages are to be printed.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">4</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="bordercb">
+ <property name="label" translatable="yes" context="printdialog|bordercb">Draw a border around each page</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="halign">end</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="bordercb-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|bordercb">Check to draw a border around each page.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">5</property>
+ <property name="width">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="brochure">
+ <property name="label" translatable="yes" context="printdialog|brochure">Brochure</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="halign">start</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ <property name="group">pagespersheetbtn</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="brochure-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|brochure">Select to print the document in brochure format.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="scriptdirection">
+ <property name="can-focus">False</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">6</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkDrawingArea" id="orderpreview">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="tooltip-text" translatable="yes" context="printdialog|collationpreview">Collation preview</property>
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="orderpreview-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|orderpreview">Change the arrangement of pages to be printed on every sheet of paper. The preview shows how every final sheet of paper will look.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">4</property>
+ <property name="top-attach">0</property>
+ <property name="height">5</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="lbLayoutExpander">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printdialog|layoutexpander">Pages per Sheet</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printdialog|label3">Page Layout</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="generallabel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printdialog|generallabel">General</property>
+ </object>
+ <packing>
+ <property name="tab-fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="customcontents">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="border-width">6</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="custom">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label">custom</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab-fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-11">help</action-widget>
+ <action-widget response="-5">ok</action-widget>
+ <action-widget response="-6">cancel</action-widget>
+ </action-widgets>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="PrintDialog-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes" context="printdialog|extended_tip|PrintDialog">Prints the current document, selection, or the pages that you specify. You can also set the print options for the current document.</property>
+ </object>
+ </child>
+ </object>
+ <object class="GtkSizeGroup" id="sizegroup1">
+ <widgets>
+ <widget name="rangegrid"/>
+ <widget name="gdCopiesExtra"/>
+ </widgets>
+ </object>
+ <object class="GtkSizeGroup" id="sizegroup2">
+ <widgets>
+ <widget name="pagegrid"/>
+ <widget name="expandpagegrid"/>
+ </widgets>
+ </object>
+ <object class="GtkTreeStore" id="liststore1">
+ <columns>
+ <!-- column-name text -->
+ <column type="gchararray"/>
+ <!-- column-name id -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkSizeGroup" id="sizegroupLabel"/>
+ <object class="GtkSizeGroup" id="sizegroupLabel2"/>
+</interface>
diff --git a/vcl/uiconfig/ui/printerdevicepage.ui b/vcl/uiconfig/ui/printerdevicepage.ui
new file mode 100644
index 0000000000..249562950e
--- /dev/null
+++ b/vcl/uiconfig/ui/printerdevicepage.ui
@@ -0,0 +1,235 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkTreeStore" id="liststore1">
+ <columns>
+ <!-- column-name text -->
+ <column type="gchararray"/>
+ <!-- column-name id -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkTreeStore" id="liststore2">
+ <columns>
+ <!-- column-name text -->
+ <column type="gchararray"/>
+ <!-- column-name id -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <!-- n-columns=2 n-rows=3 -->
+ <object class="GtkGrid" id="PrinterDevicePage">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="border-width">6</property>
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">12</property>
+ <property name="column-homogeneous">True</property>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printerdevicepage|label7">_Option:</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">options</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printerdevicepage|label8">Current _value:</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">values</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="shadow-type">in</property>
+ <child>
+ <object class="GtkTreeView" id="options">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="model">liststore1</property>
+ <property name="headers-visible">False</property>
+ <property name="headers-clickable">False</property>
+ <property name="search-column">0</property>
+ <property name="show-expanders">False</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection1"/>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="treeviewcolumn1">
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <!-- n-columns=1 n-rows=2 -->
+ <object class="GtkGrid" id="valuegrid">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="row-spacing">6</property>
+ <child>
+ <object class="GtkEntry" id="custom">
+ <property name="can-focus">True</property>
+ <property name="no-show-all">True</property>
+ <property name="activates-default">True</property>
+ <property name="truncate-multiline">True</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="shadow-type">in</property>
+ <child>
+ <object class="GtkTreeView" id="values">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="model">liststore2</property>
+ <property name="headers-visible">False</property>
+ <property name="headers-clickable">False</property>
+ <property name="search-column">0</property>
+ <property name="show-expanders">False</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection2"/>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="treeviewcolumn2">
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext2"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <!-- n-columns=2 n-rows=2 -->
+ <object class="GtkGrid" id="grid1">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printerdevicepage|label11">Color _depth:</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">colordepth</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes" context="printerdevicepage|label10">Co_lor:</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">colorspace</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="colorspace">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <items>
+ <item translatable="yes" context="printerdevicepage|colorspace">From driver</item>
+ <item translatable="yes" context="printerdevicepage|colorspace">Color</item>
+ <item translatable="yes" context="printerdevicepage|colorspace">Grayscale</item>
+ </items>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="colordepth">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <items>
+ <item translatable="yes" context="printerdevicepage|colordepth">8 Bit</item>
+ <item translatable="yes" context="printerdevicepage|colordepth">24 Bit</item>
+ </items>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/printerpaperpage.ui b/vcl/uiconfig/ui/printerpaperpage.ui
new file mode 100644
index 0000000000..9b2ef1e04d
--- /dev/null
+++ b/vcl/uiconfig/ui/printerpaperpage.ui
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.4 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <!-- n-columns=1 n-rows=1 -->
+ <object class="GtkGrid" id="PrinterPaperPage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="border_width">6</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+ <property name="column_homogeneous">True</property>
+ <child>
+ <object class="GtkLabel" id="paperft">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="printerpaperpage|paperft">_Paper size:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">paperlb</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="orientft">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="printerpaperpage|orientft">_Orientation:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">orientlb</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="duplexft">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="printerpaperpage|duplexft">_Duplex:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">duplexlb</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="slotft">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="printerpaperpage|slotft">Paper tray:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">slotlb</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="paperlb">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="orientlb">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <items>
+ <item translatable="yes" context="printerpaperpage|orientlb">Portrait</item>
+ <item translatable="yes" context="printerpaperpage|orientlb">Landscape</item>
+ </items>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="duplexlb">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="slotlb">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="papersizefromsetup">
+ <property name="label" translatable="yes" context="printerpaperpage|papersizefromsetup">Use only paper size from printer preferences</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/printerpropertiesdialog.ui b/vcl/uiconfig/ui/printerpropertiesdialog.ui
new file mode 100644
index 0000000000..d305fee358
--- /dev/null
+++ b/vcl/uiconfig/ui/printerpropertiesdialog.ui
@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.4 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkDialog" id="PrinterPropertiesDialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="title" translatable="yes" context="printerpropertiesdialog|PrinterPropertiesDialog">Properties of %s</property>
+ <property name="modal">True</property>
+ <property name="default_width">0</property>
+ <property name="default_height">0</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="ok">
+ <property name="label" translatable="yes" context="stock">_OK</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="cancel">
+ <property name="label" translatable="yes" context="stock">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="tabcontrol">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="scrollable">True</property>
+ <property name="enable_popup">True</property>
+ <child>
+ <!-- n-columns=1 n-rows=1 -->
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="paper">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="printerpropertiesdialog|paper">Paper</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <!-- n-columns=1 n-rows=1 -->
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="device">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="printerpropertiesdialog|device">Device</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-5">ok</action-widget>
+ <action-widget response="-6">cancel</action-widget>
+ </action-widgets>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/printprogressdialog.ui b/vcl/uiconfig/ui/printprogressdialog.ui
new file mode 100644
index 0000000000..fe17fbd3f1
--- /dev/null
+++ b/vcl/uiconfig/ui/printprogressdialog.ui
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkDialog" id="PrintProgressDialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="title" translatable="yes" context="printprogressdialog|PrintProgressDialog">Printing</property>
+ <property name="resizable">False</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="cancel">
+ <property name="label" translatable="yes" context="stock">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <!-- n-columns=1 n-rows=1 -->
+ <object class="GtkGrid" id="grid1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" context="printprogressdialog|label">Page %p of %n</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">progressbar</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkProgressBar" id="progressbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">cancel</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/querydialog.ui b/vcl/uiconfig/ui/querydialog.ui
new file mode 100644
index 0000000000..d472022bdb
--- /dev/null
+++ b/vcl/uiconfig/ui/querydialog.ui
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.4 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkDialog" id="QueryDialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="title" translatable="yes" context="querydialog|QueryDialog">New Data Type</property>
+ <property name="modal">True</property>
+ <property name="default_width">0</property>
+ <property name="default_height">0</property>
+ <property name="type_hint">normal</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="ok">
+ <property name="label" translatable="yes" context="stock">_OK</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="cancel">
+ <property name="label" translatable="yes" context="stock">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <!-- n-columns=1 n-rows=1 -->
+ <object class="GtkGrid" id="grid2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="truncate-multiline">True</property>
+ <property name="activates_default">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-5">ok</action-widget>
+ <action-widget response="-6">cancel</action-widget>
+ </action-widgets>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/screenshotparent.ui b/vcl/uiconfig/ui/screenshotparent.ui
new file mode 100644
index 0000000000..629b1741b5
--- /dev/null
+++ b/vcl/uiconfig/ui/screenshotparent.ui
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkDialog" id="ScreenShot">
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="modal">True</property>
+ <property name="default_width">0</property>
+ <property name="default_height">0</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="ScreenShotBox">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="internalarea">
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="cancel">
+ <property name="label" translatable="yes" context="stock">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">cancel</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/vcl/uiconfig/ui/wizard.ui b/vcl/uiconfig/ui/wizard.ui
new file mode 100644
index 0000000000..e1f68da9e6
--- /dev/null
+++ b/vcl/uiconfig/ui/wizard.ui
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkAssistant" id="Wizard">
+ <property name="can_focus">True</property>
+ <property name="border_width">6</property>
+ <property name="modal">True</property>
+ <property name="default_width">0</property>
+ <property name="default_height">0</property>
+ <property name="type_hint">dialog</property>
+ <property name="use_header_bar">0</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+</interface>
diff --git a/vcl/unx/generic/app/gendata.cxx b/vcl/unx/generic/app/gendata.cxx
new file mode 100644
index 0000000000..79175217c8
--- /dev/null
+++ b/vcl/unx/generic/app/gendata.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 .
+ */
+
+#ifdef IOS
+#include <ios/iosinst.hxx>
+#endif
+
+#include <unx/gendata.hxx>
+
+#include <unx/fontmanager.hxx>
+
+#ifndef IOS
+
+#include <unx/glyphcache.hxx>
+#include <printerinfomanager.hxx>
+
+SalData::SalData() { SetSalData(this); }
+
+SalData::~SalData() {}
+
+#endif
+
+GenericUnixSalData::GenericUnixSalData()
+ : m_pDisplay(nullptr)
+{
+}
+
+GenericUnixSalData::~GenericUnixSalData()
+{
+#ifndef IOS
+ // at least for InitPrintFontManager the sequence is important
+ m_pPrintFontManager.reset();
+ m_pFreetypeManager.reset();
+ m_pPrinterInfoManager.reset();
+#endif
+}
+
+void GenericUnixSalData::Dispose() {}
+
+#ifndef IOS
+void GenericUnixSalData::InitFreetypeManager() { m_pFreetypeManager.reset(new FreetypeManager); }
+#endif
+
+void GenericUnixSalData::InitPrintFontManager()
+{
+#ifndef IOS
+ GetFreetypeManager();
+ m_pPrintFontManager.reset(new psp::PrintFontManager);
+ m_pPrintFontManager->initialize();
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/gendisp.cxx b/vcl/unx/generic/app/gendisp.cxx
new file mode 100644
index 0000000000..b1dbef3f5f
--- /dev/null
+++ b/vcl/unx/generic/app/gendisp.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 <salframe.hxx>
+#include <unx/gendisp.hxx>
+
+SalGenericDisplay::SalGenericDisplay()
+{
+ m_pCapture = nullptr;
+}
+
+SalGenericDisplay::~SalGenericDisplay()
+{
+}
+
+void SalGenericDisplay::registerFrame( SalFrame* pFrame )
+{
+ insertFrame( pFrame );
+}
+
+void SalGenericDisplay::deregisterFrame( SalFrame* pFrame )
+{
+ eraseFrame( pFrame );
+}
+
+void SalGenericDisplay::emitDisplayChanged()
+{
+ SalFrame *pAnyFrame = anyFrame();
+ if( pAnyFrame )
+ pAnyFrame->CallCallback( SalEvent::DisplayChanged, nullptr );
+}
+
+bool SalGenericDisplay::DispatchInternalEvent( bool bHandleAllCurrentEvent )
+{
+ return DispatchUserEvents( bHandleAllCurrentEvent );
+}
+
+void SalGenericDisplay::SendInternalEvent( SalFrame* pFrame, void* pData, SalEvent nEvent )
+{
+ PostEvent( pFrame, pData, nEvent );
+}
+
+void SalGenericDisplay::CancelInternalEvent( SalFrame* pFrame, void* pData, SalEvent nEvent )
+{
+ RemoveEvent( pFrame, pData, nEvent );
+}
+
+void SalGenericDisplay::ProcessEvent( SalUserEvent aEvent )
+{
+ aEvent.m_pFrame->CallCallback( aEvent.m_nEvent, aEvent.m_pData );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/geninst.cxx b/vcl/unx/generic/app/geninst.cxx
new file mode 100644
index 0000000000..191d87ca76
--- /dev/null
+++ b/vcl/unx/generic/app/geninst.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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>
+
+#if defined(LINUX)
+# include <stdio.h>
+#endif
+#if defined(__FreeBSD__)
+# include <sys/utsname.h>
+#endif
+
+#include <config_features.h>
+#if HAVE_FEATURE_OPENGL
+#include <vcl/opengl/OpenGLContext.hxx>
+#endif
+#include <unx/geninst.h>
+#include <o3tl/string_view.hxx>
+
+// SalYieldMutex
+
+SalYieldMutex::SalYieldMutex()
+{
+#if HAVE_FEATURE_OPENGL
+ SetBeforeReleaseHandler( &OpenGLContext::prepareForYield );
+#endif
+}
+
+SalYieldMutex::~SalYieldMutex()
+{
+}
+
+SalGenericInstance::~SalGenericInstance()
+{
+}
+
+OUString SalGenericInstance::getOSVersion()
+{
+ OUString aKernelVer = "unknown";
+#if defined(LINUX)
+ FILE* pVersion = fopen( "/proc/version", "r" );
+ if ( pVersion )
+ {
+ char aVerBuffer[512];
+ if ( fgets ( aVerBuffer, 511, pVersion ) )
+ {
+ aKernelVer = OUString::createFromAscii( aVerBuffer );
+ // "Linux version 3.16.7-29-desktop ..."
+ std::u16string_view aVers = o3tl::getToken(aKernelVer, 2, ' ' );
+ // "3.16.7-29-desktop ..."
+ size_t nTooDetailed = aVers.find( '.', 2);
+ if (nTooDetailed < 1 || nTooDetailed > 8)
+ aKernelVer = "Linux (misparsed version)";
+ else // "3.16.7-29-desktop ..."
+ aKernelVer = OUString::Concat("Linux ") + aVers.substr(0, nTooDetailed);
+ }
+ fclose( pVersion );
+ }
+#elif defined(__FreeBSD__)
+ struct utsname stName;
+ if ( uname( &stName ) != 0 )
+ return aKernelVer;
+
+ sal_Int32 nDots = 0;
+ sal_Int32 nIndex = 0;
+ aKernelVer = OUString::createFromAscii( stName.release );
+ while ( nIndex++ < aKernelVer.getLength() )
+ {
+ const char c = stName.release[ nIndex ];
+ if ( c == ' ' || c == '-' || ( c == '.' && nDots++ > 0 ) )
+ break;
+ }
+ aKernelVer = OUString::createFromAscii(stName.sysname) + " " + aKernelVer.copy(0, nIndex);
+#elif defined(EMSCRIPTEN)
+#define str(s) #s
+#define xstr(s) str(s)
+ aKernelVer = "Emscripten " xstr(__EMSCRIPTEN_major__)
+ "." xstr(__EMSCRIPTEN_minor__) "." xstr(__EMSCRIPTEN_tiny__);
+#endif
+ return aKernelVer;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/gensys.cxx b/vcl/unx/generic/app/gensys.cxx
new file mode 100644
index 0000000000..98371c5484
--- /dev/null
+++ b/vcl/unx/generic/app/gensys.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <unx/gensys.h>
+
+#include <svdata.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <rtl/bootstrap.hxx>
+#include <osl/process.h>
+#include <osl/thread.h>
+#include <unotools/configmgr.hxx>
+
+using namespace com::sun::star;
+
+SalGenericSystem::SalGenericSystem()
+{
+}
+
+SalGenericSystem::~SalGenericSystem()
+{
+}
+
+int SalGenericSystem::ShowNativeMessageBox( const OUString& rTitle, const OUString& rMessage )
+{
+ std::vector< OUString > aButtons;
+ int nButtonIds[5] = {0}, nBut = 0;
+
+ ImplHideSplash();
+
+ aButtons.push_back( "OK" );
+ nButtonIds[nBut++] = SALSYSTEM_SHOWNATIVEMSGBOX_BTN_OK;
+ int nResult = ShowNativeDialog( rTitle, rMessage, aButtons );
+
+ return nResult != -1 ? nButtonIds[ nResult ] : 0;
+}
+
+#if !defined(ANDROID) && !defined(IOS)
+
+// X11-specific
+
+const char* SalGenericSystem::getFrameResName()
+{
+ /* according to ICCCM:
+ * first search command line for -name parameter
+ * then try RESOURCE_NAME environment variable
+ * then use argv[0] stripped by directories
+ */
+ static OStringBuffer aResName;
+ if( aResName.isEmpty() )
+ {
+ int nArgs = osl_getCommandArgCount();
+ for( int n = 0; n < nArgs-1; n++ )
+ {
+ OUString aArg;
+ osl_getCommandArg( n, &aArg.pData );
+ if( aArg.equalsIgnoreAsciiCase("-name") )
+ {
+ osl_getCommandArg( n+1, &aArg.pData );
+ aResName.append( OUStringToOString( aArg, osl_getThreadTextEncoding() ) );
+ break;
+ }
+ }
+ if( aResName.isEmpty() )
+ {
+ const char* pEnv = getenv( "RESOURCE_NAME" );
+ if( pEnv && *pEnv )
+ aResName.append( pEnv );
+ }
+ if( aResName.isEmpty() )
+ aResName.append( OUStringToOString( utl::ConfigManager::getProductName().toAsciiLowerCase(),
+ osl_getThreadTextEncoding()));
+ }
+ return aResName.getStr();
+}
+
+const char* SalGenericSystem::getFrameClassName()
+{
+ static OStringBuffer aClassName;
+ if( aClassName.isEmpty() )
+ {
+ OUString aIni, aProduct;
+ rtl::Bootstrap::get( "BRAND_BASE_DIR", aIni );
+ aIni += "/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap" );
+ rtl::Bootstrap aBootstrap( aIni );
+ aBootstrap.getFrom( "ProductKey", aProduct );
+
+ if( !aProduct.isEmpty() )
+ aClassName.append( OUStringToOString( aProduct, osl_getThreadTextEncoding() ) );
+ else
+ aClassName.append( OUStringToOString( utl::ConfigManager::getProductName(), osl_getThreadTextEncoding()));
+ }
+ return aClassName.getStr();
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/i18n_cb.cxx b/vcl/unx/generic/app/i18n_cb.cxx
new file mode 100644
index 0000000000..c17c01a4d2
--- /dev/null
+++ b/vcl/unx/generic/app/i18n_cb.cxx
@@ -0,0 +1,494 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <o3tl/safeint.hxx>
+#include <osl/thread.h>
+#include <sal/log.hxx>
+
+#include <X11/Xlib.h>
+
+#include <vcl/commandevent.hxx>
+#include <unx/i18n_cb.hxx>
+#include <unx/i18n_ic.hxx>
+#include <unx/i18n_im.hxx>
+#include <salframe.hxx>
+
+// i. preedit start callback
+
+int
+PreeditStartCallback ( XIC, XPointer client_data, XPointer )
+{
+ preedit_data_t* pPreeditData = reinterpret_cast<preedit_data_t*>(client_data);
+ if ( pPreeditData->eState == PreeditStatus::ActivationRequired )
+ {
+ pPreeditData->eState = PreeditStatus::Active;
+ pPreeditData->aText.nLength = 0;
+ }
+
+ return -1;
+}
+
+// ii. preedit done callback
+
+void
+PreeditDoneCallback ( XIC, XPointer client_data, XPointer )
+{
+ preedit_data_t* pPreeditData = reinterpret_cast<preedit_data_t*>(client_data);
+ if (pPreeditData->eState == PreeditStatus::Active )
+ {
+ if( pPreeditData->pFrame )
+ pPreeditData->pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+ }
+ pPreeditData->eState = PreeditStatus::StartPending;
+}
+
+// iii. preedit draw callback
+
+// Handle deletion of text in a preedit_draw_callback
+// from and howmuch are guaranteed to be nonnegative
+
+static void
+Preedit_DeleteText(preedit_text_t *ptext, int from, int howmuch)
+{
+ // If we've been asked to delete no text then just set
+ // nLength correctly and return
+ if (ptext->nLength == 0)
+ {
+ ptext->nLength = from;
+ return;
+ }
+
+ int to = from + howmuch;
+
+ if (to == static_cast<int>(ptext->nLength))
+ {
+ // delete from the end of the text
+ ptext->nLength = from;
+ }
+ else if (to < static_cast<int>(ptext->nLength))
+ {
+ // cut out of the middle of the text
+ memmove( static_cast<void*>(ptext->pUnicodeBuffer + from),
+ static_cast<void*>(ptext->pUnicodeBuffer + to),
+ (ptext->nLength - to) * sizeof(sal_Unicode));
+ memmove( static_cast<void*>(ptext->pCharStyle + from),
+ static_cast<void*>(ptext->pCharStyle + to),
+ (ptext->nLength - to) * sizeof(XIMFeedback));
+ ptext->nLength -= howmuch;
+ }
+ else
+ {
+ // XXX this indicates an error, are we out of sync ?
+ SAL_INFO("vcl.app", "Preedit_DeleteText( from=" << from
+ << " to=" << to
+ << " length=" << ptext->nLength
+ << " ).");
+ fprintf (stderr, "\t XXX internal error, out of sync XXX\n");
+
+ ptext->nLength = from;
+ }
+
+ // NULL-terminate the string
+ ptext->pUnicodeBuffer[ptext->nLength] = u'\0';
+}
+
+// reallocate the textbuffer with sufficiently large size 2^x
+// nnewlimit is presupposed to be larger than ptext->size
+static void
+enlarge_buffer ( preedit_text_t *ptext, int nnewlimit )
+{
+ size_t nnewsize = ptext->nSize;
+
+ while ( nnewsize <= o3tl::make_unsigned(nnewlimit) )
+ nnewsize *= 2;
+
+ ptext->nSize = nnewsize;
+ ptext->pUnicodeBuffer = static_cast<sal_Unicode*>(realloc(static_cast<void*>(ptext->pUnicodeBuffer),
+ nnewsize * sizeof(sal_Unicode)));
+ ptext->pCharStyle = static_cast<XIMFeedback*>(realloc(static_cast<void*>(ptext->pCharStyle),
+ nnewsize * sizeof(XIMFeedback)));
+}
+
+// Handle insertion of text in a preedit_draw_callback
+// string field of XIMText struct is guaranteed to be != NULL
+
+static void
+Preedit_InsertText(preedit_text_t *pText, XIMText *pInsertText, int where)
+{
+ sal_Unicode *pInsertTextString;
+ int nInsertTextLength = 0;
+ XIMFeedback *pInsertTextCharStyle = pInsertText->feedback;
+
+ nInsertTextLength = pInsertText->length;
+
+ // can't handle wchar_t strings, so convert to multibyte chars first
+ char *pMBString;
+ size_t nMBLength;
+ if (pInsertText->encoding_is_wchar)
+ {
+ wchar_t *pWCString = pInsertText->string.wide_char;
+ size_t nBytes = wcstombs ( nullptr, pWCString, 0 /* don't care */);
+ pMBString = static_cast<char*>(alloca( nBytes + 1 ));
+ nMBLength = wcstombs ( pMBString, pWCString, nBytes + 1);
+ }
+ else
+ {
+ pMBString = pInsertText->string.multi_byte;
+ nMBLength = strlen(pMBString); // xxx
+ }
+
+ // convert multibyte chars to unicode
+ rtl_TextEncoding nEncoding = osl_getThreadTextEncoding();
+
+ if (nEncoding != RTL_TEXTENCODING_UNICODE)
+ {
+ rtl_TextToUnicodeConverter aConverter =
+ rtl_createTextToUnicodeConverter( nEncoding );
+ rtl_TextToUnicodeContext aContext =
+ rtl_createTextToUnicodeContext(aConverter);
+
+ sal_Size nBufferSize = nInsertTextLength * 2;
+
+ pInsertTextString = static_cast<sal_Unicode*>(alloca(nBufferSize));
+
+ sal_uInt32 nConversionInfo;
+ sal_Size nConvertedChars;
+
+ rtl_convertTextToUnicode( aConverter, aContext,
+ pMBString, nMBLength,
+ pInsertTextString, nBufferSize,
+ RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_IGNORE
+ | RTL_TEXTTOUNICODE_FLAGS_INVALID_IGNORE,
+ &nConversionInfo, &nConvertedChars );
+
+ rtl_destroyTextToUnicodeContext(aConverter, aContext);
+ rtl_destroyTextToUnicodeConverter(aConverter);
+
+ }
+ else
+ {
+ pInsertTextString = reinterpret_cast<sal_Unicode*>(pMBString);
+ }
+
+ // enlarge target text-buffer if necessary
+ if (pText->nSize <= (pText->nLength + nInsertTextLength))
+ enlarge_buffer(pText, pText->nLength + nInsertTextLength);
+
+ // insert text: displace old mem and put new bytes in
+ int from = where;
+ int to = where + nInsertTextLength;
+ int howmany = pText->nLength - where;
+
+ memmove(static_cast<void*>(pText->pUnicodeBuffer + to),
+ static_cast<void*>(pText->pUnicodeBuffer + from),
+ howmany * sizeof(sal_Unicode));
+ memmove(static_cast<void*>(pText->pCharStyle + to),
+ static_cast<void*>(pText->pCharStyle + from),
+ howmany * sizeof(XIMFeedback));
+
+ to = from;
+ howmany = nInsertTextLength;
+
+ memcpy(static_cast<void*>(pText->pUnicodeBuffer + to), static_cast<void*>(pInsertTextString),
+ howmany * sizeof(sal_Unicode));
+ memcpy(static_cast<void*>(pText->pCharStyle + to), static_cast<void*>(pInsertTextCharStyle),
+ howmany * sizeof(XIMFeedback));
+
+ pText->nLength += howmany;
+
+ // NULL-terminate the string
+ pText->pUnicodeBuffer[pText->nLength] = u'\0';
+}
+
+// Handle the change of attributes in a preedit_draw_callback
+
+static void
+Preedit_UpdateAttributes ( preedit_text_t* ptext, XIMFeedback const * feedback,
+ int from, int amount )
+{
+ if ( (from + amount) > static_cast<int>(ptext->nLength) )
+ {
+ // XXX this indicates an error, are we out of sync ?
+ SAL_INFO("vcl.app", "Preedit_UpdateAttributes( "
+ << from << " + " << amount << " > " << ptext->nLength
+ << " ).");
+ fprintf (stderr, "\t XXX internal error, out of sync XXX\n");
+
+ return;
+ }
+
+ memcpy ( ptext->pCharStyle + from,
+ feedback, amount * sizeof(XIMFeedback) );
+}
+
+// Convert the XIM feedback values into appropriate VCL
+// EXTTEXTINPUT_ATTR values
+// returns an allocate list of attributes, which must be freed by caller
+static ExtTextInputAttr*
+Preedit_FeedbackToSAL ( const XIMFeedback* pfeedback, int nlength, std::vector<ExtTextInputAttr>& rSalAttr )
+{
+ ExtTextInputAttr *psalattr;
+ ExtTextInputAttr nval;
+ ExtTextInputAttr noldval = ExtTextInputAttr::NONE;
+ XIMFeedback nfeedback;
+
+ // only work with reasonable length
+ if (nlength > 0 && nlength > sal::static_int_cast<int>(rSalAttr.size()) )
+ {
+ rSalAttr.reserve( nlength );
+ psalattr = rSalAttr.data();
+ }
+ else
+ return nullptr;
+
+ for (int npos = 0; npos < nlength; npos++)
+ {
+ nval = ExtTextInputAttr::NONE;
+ nfeedback = pfeedback[npos];
+
+ // means to use the feedback of the previous char
+ if (nfeedback == 0)
+ {
+ nval = noldval;
+ }
+ // convert feedback to attributes
+ else
+ {
+ if (nfeedback & XIMReverse)
+ nval |= ExtTextInputAttr::Highlight;
+ if (nfeedback & XIMUnderline)
+ nval |= ExtTextInputAttr::Underline;
+ if (nfeedback & XIMHighlight)
+ nval |= ExtTextInputAttr::Highlight;
+ if (nfeedback & XIMPrimary)
+ nval |= ExtTextInputAttr::DottedUnderline;
+ if (nfeedback & XIMSecondary)
+ nval |= ExtTextInputAttr::DashDotUnderline;
+ if (nfeedback & XIMTertiary) // same as 2ery
+ nval |= ExtTextInputAttr::DashDotUnderline;
+
+ }
+ // copy in list
+ psalattr[npos] = nval;
+ noldval = nval;
+ }
+ // return list of sal attributes
+ return psalattr;
+}
+
+void
+PreeditDrawCallback(XIC ic, XPointer client_data,
+ XIMPreeditDrawCallbackStruct *call_data)
+{
+ preedit_data_t* pPreeditData = reinterpret_cast<preedit_data_t*>(client_data);
+
+ // if there's nothing to change then change nothing
+ if ( ( (call_data->text == nullptr) && (call_data->chg_length == 0) )
+ || pPreeditData->pFrame == nullptr )
+ return;
+
+ // Solaris 7 deletes the preedit buffer after commit
+ // since the next call to preeditstart will have the same effect just skip this.
+ // if (pPreeditData->eState == ePreeditStatusStartPending && call_data->text == NULL)
+ // return;
+
+ if ( pPreeditData->eState == PreeditStatus::StartPending )
+ pPreeditData->eState = PreeditStatus::ActivationRequired;
+ PreeditStartCallback( ic, client_data, nullptr );
+
+ // Edit the internal textbuffer as indicated by the call_data,
+ // chg_first and chg_length are guaranteed to be nonnegative
+
+ // handle text deletion
+ if (call_data->text == nullptr)
+ {
+ Preedit_DeleteText(&(pPreeditData->aText),
+ call_data->chg_first, call_data->chg_length );
+ }
+ else
+ {
+ // handle text insertion
+ if ( (call_data->chg_length == 0)
+ && (call_data->text->string.wide_char != nullptr))
+ {
+ Preedit_InsertText(&(pPreeditData->aText), call_data->text,
+ call_data->chg_first);
+ }
+ else if ( (call_data->chg_length != 0)
+ && (call_data->text->string.wide_char != nullptr))
+ {
+ // handle text replacement by deletion and insertion of text,
+ // not smart, just good enough
+
+ Preedit_DeleteText(&(pPreeditData->aText),
+ call_data->chg_first, call_data->chg_length);
+ Preedit_InsertText(&(pPreeditData->aText), call_data->text,
+ call_data->chg_first);
+ }
+ else if ( (call_data->chg_length != 0)
+ && (call_data->text->string.wide_char == nullptr))
+ {
+ // not really a text update, only attributes are concerned
+ Preedit_UpdateAttributes(&(pPreeditData->aText),
+ call_data->text->feedback,
+ call_data->chg_first, call_data->chg_length);
+ }
+ }
+
+ // build the SalExtTextInputEvent and send it up
+
+ pPreeditData->aInputEv.mpTextAttr = Preedit_FeedbackToSAL(
+ pPreeditData->aText.pCharStyle, pPreeditData->aText.nLength, pPreeditData->aInputFlags);
+ pPreeditData->aInputEv.mnCursorPos = call_data->caret;
+ pPreeditData->aInputEv.maText = OUString(pPreeditData->aText.pUnicodeBuffer,
+ pPreeditData->aText.nLength);
+ pPreeditData->aInputEv.mnCursorFlags = 0; // default: make cursor visible
+
+ if ( pPreeditData->eState == PreeditStatus::Active && pPreeditData->pFrame )
+ pPreeditData->pFrame->CallCallback(SalEvent::ExtTextInput, static_cast<void*>(&pPreeditData->aInputEv));
+ if (pPreeditData->aText.nLength == 0 && pPreeditData->pFrame )
+ pPreeditData->pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+
+ if (pPreeditData->aText.nLength == 0)
+ pPreeditData->eState = PreeditStatus::StartPending;
+
+ GetPreeditSpotLocation(ic, reinterpret_cast<XPointer>(pPreeditData));
+}
+
+void
+GetPreeditSpotLocation(XIC ic, XPointer client_data)
+{
+
+ // Send SalEventExtTextInputPos event to get spotlocation
+
+ SalExtTextInputPosEvent aPosEvent;
+ preedit_data_t* pPreeditData = reinterpret_cast<preedit_data_t*>(client_data);
+
+ if( pPreeditData->pFrame )
+ pPreeditData->pFrame->CallCallback(SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent));
+
+ XPoint point;
+ point.x = aPosEvent.mnX + aPosEvent.mnWidth;
+ point.y = aPosEvent.mnY + aPosEvent.mnHeight;
+
+ XVaNestedList preedit_attr;
+ preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &point, nullptr);
+ XSetICValues(ic, XNPreeditAttributes, preedit_attr, nullptr);
+ XFree(preedit_attr);
+}
+
+// iv. preedit caret callback
+
+#if OSL_DEBUG_LEVEL > 1
+void
+PreeditCaretCallback ( XIC ic, XPointer client_data,
+ XIMPreeditCaretCallbackStruct *call_data )
+{
+ // XXX PreeditCaretCallback is pure debug code for now
+ const char *direction = "?";
+ const char *style = "?";
+
+ switch ( call_data->style )
+ {
+ case XIMIsInvisible: style = "Invisible"; break;
+ case XIMIsPrimary: style = "Primary"; break;
+ case XIMIsSecondary: style = "Secondary"; break;
+ }
+ switch ( call_data->direction )
+ {
+ case XIMForwardChar: direction = "Forward char"; break;
+ case XIMBackwardChar: direction = "Backward char"; break;
+ case XIMForwardWord: direction = "Forward word"; break;
+ case XIMBackwardWord: direction = "Backward word"; break;
+ case XIMCaretUp: direction = "Caret up"; break;
+ case XIMCaretDown: direction = "Caret down"; break;
+ case XIMNextLine: direction = "Next line"; break;
+ case XIMPreviousLine: direction = "Previous line"; break;
+ case XIMLineStart: direction = "Line start"; break;
+ case XIMLineEnd: direction = "Line end"; break;
+ case XIMAbsolutePosition: direction = "Absolute"; break;
+ case XIMDontChange: direction = "Don't change"; break;
+ }
+
+ SAL_INFO("vcl.app", "PreeditCaretCallback( ic=" << ic
+ << ", client=" << client_data
+ << ",");
+ SAL_INFO("vcl.app", "\t position=" << call_data->position
+ << ", direction=\"" << direction
+ << "\", style=\"" << style
+ << "\" ).");
+}
+#else
+void
+PreeditCaretCallback ( XIC, XPointer, XIMPreeditCaretCallbackStruct* )
+{
+}
+#endif
+
+// v. commit string callback: convert an extended text input (iiimp ... )
+// into an ordinary key-event
+
+Bool
+IsControlCode(sal_Unicode nChar)
+{
+ if ( nChar <= 0x1F /* C0 controls */ )
+ return True;
+ else
+ return False;
+}
+
+// vi. status callbacks: for now these are empty, they are just needed for turbo linux
+
+void
+StatusStartCallback (XIC, XPointer, XPointer)
+{
+}
+
+void
+StatusDoneCallback (XIC, XPointer, XPointer)
+{
+}
+
+void
+StatusDrawCallback (XIC, XPointer, XIMStatusDrawCallbackStruct *)
+{
+}
+
+// vii. destroy callbacks: internally disable all IC/IM calls
+
+void
+IC_IMDestroyCallback (XIM, XPointer client_data, XPointer)
+{
+ SalI18N_InputContext *pContext = reinterpret_cast<SalI18N_InputContext*>(client_data);
+ if (pContext != nullptr)
+ pContext->HandleDestroyIM();
+}
+
+void
+IM_IMDestroyCallback (XIM, XPointer client_data, XPointer)
+{
+ SalI18N_InputMethod *pMethod = reinterpret_cast<SalI18N_InputMethod*>(client_data);
+ if (pMethod != nullptr)
+ pMethod->HandleDestroyIM();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/i18n_ic.cxx b/vcl/unx/generic/app/i18n_ic.cxx
new file mode 100644
index 0000000000..32390a8888
--- /dev/null
+++ b/vcl/unx/generic/app/i18n_ic.cxx
@@ -0,0 +1,604 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <X11/Xlib.h>
+
+#include <unx/i18n_ic.hxx>
+#include <unx/i18n_im.hxx>
+
+#include <unx/salframe.h>
+#include <unx/saldisp.hxx>
+
+#include <sal/log.hxx>
+
+using namespace vcl;
+
+static void sendEmptyCommit( SalFrame* pFrame )
+{
+ vcl::DeletionListener aDel( pFrame );
+
+ SalExtTextInputEvent aEmptyEv;
+ aEmptyEv.mpTextAttr = nullptr;
+ aEmptyEv.maText.clear();
+ aEmptyEv.mnCursorPos = 0;
+ aEmptyEv.mnCursorFlags = 0;
+ pFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void*>(&aEmptyEv) );
+ if( ! aDel.isDeleted() )
+ pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+}
+
+// Constructor / Destructor, the InputContext is bound to the SalFrame, as it
+// needs the shell window as a focus window
+
+SalI18N_InputContext::~SalI18N_InputContext()
+{
+ if ( maContext != nullptr )
+ XDestroyIC( maContext );
+ if ( mpAttributes != nullptr )
+ XFree( mpAttributes );
+ if ( mpStatusAttributes != nullptr )
+ XFree( mpStatusAttributes );
+ if ( mpPreeditAttributes != nullptr )
+ XFree( mpPreeditAttributes );
+
+ if (maClientData.aText.pUnicodeBuffer != nullptr)
+ free(maClientData.aText.pUnicodeBuffer);
+ if (maClientData.aText.pCharStyle != nullptr)
+ free(maClientData.aText.pCharStyle);
+}
+
+// convenience routine to add items to a XVaNestedList
+
+static XVaNestedList
+XVaAddToNestedList( XVaNestedList a_srclist, char* name, XPointer value )
+{
+ XVaNestedList a_dstlist;
+
+ // if ( value == NULL )
+ // return a_srclist;
+
+ if ( a_srclist == nullptr )
+ {
+ a_dstlist = XVaCreateNestedList(
+ 0,
+ name, value,
+ nullptr );
+ }
+ else
+ {
+ a_dstlist = XVaCreateNestedList(
+ 0,
+ XNVaNestedList, a_srclist,
+ name, value,
+ nullptr );
+ }
+
+ return a_dstlist != nullptr ? a_dstlist : a_srclist ;
+}
+
+// convenience routine to create a fontset
+
+static XFontSet
+get_font_set( Display *p_display )
+{
+ static XFontSet p_font_set = nullptr;
+
+ if (p_font_set == nullptr)
+ {
+ char **pp_missing_list;
+ int n_missing_count;
+ char *p_default_string;
+
+ p_font_set = XCreateFontSet(p_display, "-*",
+ &pp_missing_list, &n_missing_count, &p_default_string);
+ }
+
+ return p_font_set;
+}
+
+const XIMStyle g_nSupportedStatusStyle(
+ XIMStatusCallbacks |
+ XIMStatusNothing |
+ XIMStatusNone
+ );
+
+// Constructor for an InputContext (IC)
+
+SalI18N_InputContext::SalI18N_InputContext ( SalFrame *pFrame ) :
+ mbUseable( True ),
+ maContext( nullptr ),
+ mnSupportedPreeditStyle(
+ XIMPreeditCallbacks |
+ XIMPreeditNothing |
+ XIMPreeditNone
+ ),
+ mnStatusStyle( 0 ),
+ mnPreeditStyle( 0 ),
+ mpAttributes( nullptr ),
+ mpStatusAttributes( nullptr ),
+ mpPreeditAttributes( nullptr )
+{
+#ifdef __sun
+ static const char* pIIIMPEnable = getenv( "SAL_DISABLE_OWN_IM_STATUS" );
+ if( pIIIMPEnable && *pIIIMPEnable )
+ mnSupportedStatusStyle &= ~XIMStatusCallbacks;
+#endif
+
+ memset(&maPreeditStartCallback, 0, sizeof(maPreeditStartCallback));
+ memset(&maPreeditDoneCallback, 0, sizeof(maPreeditDoneCallback));
+ memset(&maPreeditDrawCallback, 0, sizeof(maPreeditDrawCallback));
+ memset(&maPreeditCaretCallback, 0, sizeof(maPreeditCaretCallback));
+ memset(&maCommitStringCallback, 0, sizeof(maCommitStringCallback));
+ memset(&maSwitchIMCallback, 0, sizeof(maSwitchIMCallback));
+ memset(&maDestroyCallback, 0, sizeof(maDestroyCallback));
+
+ maClientData.aText.pUnicodeBuffer = nullptr;
+ maClientData.aText.pCharStyle = nullptr;
+ maClientData.aInputEv.mpTextAttr = nullptr;
+ maClientData.aInputEv.mnCursorPos = 0;
+ maClientData.aInputEv.mnCursorFlags = 0;
+
+ SalI18N_InputMethod *pInputMethod;
+ pInputMethod = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetInputMethod();
+
+ mnSupportedPreeditStyle = XIMPreeditCallbacks | XIMPreeditPosition
+ | XIMPreeditNothing | XIMPreeditNone;
+ if (pInputMethod->UseMethod()
+ && SupportInputMethodStyle( pInputMethod->GetSupportedStyles() ) )
+ {
+ const SystemEnvData* pEnv = pFrame->GetSystemData();
+ ::Window aClientWindow = pEnv->aShellWindow;
+ ::Window aFocusWindow = pEnv->GetWindowHandle(pFrame);
+
+ // for status callbacks and commit string callbacks
+#define PREEDIT_BUFSZ 16
+ maClientData.eState = PreeditStatus::StartPending;
+ maClientData.pFrame = pFrame;
+ maClientData.aText.pUnicodeBuffer =
+ static_cast<sal_Unicode*>(malloc(PREEDIT_BUFSZ * sizeof(sal_Unicode)));
+ maClientData.aText.pCharStyle =
+ static_cast<XIMFeedback*>(malloc(PREEDIT_BUFSZ * sizeof(XIMFeedback)));
+ maClientData.aText.nSize = PREEDIT_BUFSZ;
+ maClientData.aText.nLength = 0;
+
+ // Status attributes
+
+ switch ( mnStatusStyle )
+ {
+ case XIMStatusCallbacks:
+ {
+ static XIMCallback aStatusStartCallback;
+ static XIMCallback aStatusDoneCallback;
+ static XIMCallback aStatusDrawCallback;
+
+ aStatusStartCallback.callback = reinterpret_cast<XIMProc>(StatusStartCallback);
+ aStatusStartCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
+ aStatusDoneCallback.callback = reinterpret_cast<XIMProc>(StatusDoneCallback);
+ aStatusDoneCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
+ aStatusDrawCallback.callback = reinterpret_cast<XIMProc>(StatusDrawCallback);
+ aStatusDrawCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
+
+ mpStatusAttributes = XVaCreateNestedList (
+ 0,
+ XNStatusStartCallback, &aStatusStartCallback,
+ XNStatusDoneCallback, &aStatusDoneCallback,
+ XNStatusDrawCallback, &aStatusDrawCallback,
+ nullptr );
+
+ break;
+ }
+
+ case XIMStatusArea:
+ /* not supported */
+ break;
+
+ case XIMStatusNone:
+ case XIMStatusNothing:
+ default:
+ /* no arguments needed */
+ break;
+ }
+
+ // set preedit attributes
+
+ switch ( mnPreeditStyle )
+ {
+ case XIMPreeditCallbacks:
+
+ maPreeditCaretCallback.callback = reinterpret_cast<XIMProc>(PreeditCaretCallback);
+ maPreeditStartCallback.callback = reinterpret_cast<XIMProc>(PreeditStartCallback);
+ maPreeditDoneCallback.callback = reinterpret_cast<XIMProc>(PreeditDoneCallback);
+ maPreeditDrawCallback.callback = reinterpret_cast<XIMProc>(PreeditDrawCallback);
+ maPreeditCaretCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
+ maPreeditStartCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
+ maPreeditDoneCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
+ maPreeditDrawCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
+
+ mpPreeditAttributes = XVaCreateNestedList (
+ 0,
+ XNPreeditStartCallback, &maPreeditStartCallback,
+ XNPreeditDoneCallback, &maPreeditDoneCallback,
+ XNPreeditDrawCallback, &maPreeditDrawCallback,
+ XNPreeditCaretCallback, &maPreeditCaretCallback,
+ nullptr );
+
+ break;
+
+ case XIMPreeditArea:
+ /* not supported */
+ break;
+
+ case XIMPreeditPosition:
+ {
+ // spot location
+ SalExtTextInputPosEvent aPosEvent;
+ pFrame->CallCallback(SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent));
+
+ static XPoint aSpot;
+ aSpot.x = aPosEvent.mnX + aPosEvent.mnWidth;
+ aSpot.y = aPosEvent.mnY + aPosEvent.mnHeight;
+
+ // create attributes for preedit position style
+ mpPreeditAttributes = XVaCreateNestedList (
+ 0,
+ XNSpotLocation, &aSpot,
+ nullptr );
+
+ // XCreateIC() fails on Redflag Linux 2.0 if there is no
+ // fontset though the data itself is not evaluated nor is
+ // it required according to the X specs.
+ Display* pDisplay = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay();
+ XFontSet pFontSet = get_font_set(pDisplay);
+
+ if (pFontSet != nullptr)
+ {
+ mpPreeditAttributes = XVaAddToNestedList( mpPreeditAttributes,
+ const_cast<char*>(XNFontSet), reinterpret_cast<XPointer>(pFontSet));
+ }
+
+ break;
+ }
+
+ case XIMPreeditNone:
+ case XIMPreeditNothing:
+ default:
+ /* no arguments needed */
+ break;
+ }
+
+ // Create the InputContext by giving it exactly the information it
+ // deserves, because inappropriate attributes
+ // let XCreateIC fail on Solaris (eg. for C locale)
+
+ mpAttributes = XVaCreateNestedList(
+ 0,
+ XNFocusWindow, aFocusWindow,
+ XNClientWindow, aClientWindow,
+ XNInputStyle, mnPreeditStyle | mnStatusStyle,
+ nullptr );
+
+ if ( mnPreeditStyle != XIMPreeditNone )
+ {
+#if defined LINUX || defined FREEBSD || defined NETBSD || defined OPENBSD || defined DRAGONFLY
+ if ( mpPreeditAttributes != nullptr )
+#endif
+ mpAttributes = XVaAddToNestedList( mpAttributes,
+ const_cast<char*>(XNPreeditAttributes), static_cast<XPointer>(mpPreeditAttributes) );
+ }
+ if ( mnStatusStyle != XIMStatusNone )
+ {
+#if defined LINUX || defined FREEBSD || defined NETBSD || defined OPENBSD || defined DRAGONFLY
+ if ( mpStatusAttributes != nullptr )
+#endif
+ mpAttributes = XVaAddToNestedList( mpAttributes,
+ const_cast<char*>(XNStatusAttributes), static_cast<XPointer>(mpStatusAttributes) );
+ }
+ maContext = XCreateIC( pInputMethod->GetMethod(),
+ XNVaNestedList, mpAttributes,
+ nullptr );
+ }
+
+ if ( maContext == nullptr )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.app", "input context creation failed.");
+#endif
+
+ mbUseable = False;
+
+ if ( mpAttributes != nullptr )
+ XFree( mpAttributes );
+ if ( mpStatusAttributes != nullptr )
+ XFree( mpStatusAttributes );
+ if ( mpPreeditAttributes != nullptr )
+ XFree( mpPreeditAttributes );
+ if ( maClientData.aText.pUnicodeBuffer != nullptr )
+ free ( maClientData.aText.pUnicodeBuffer );
+ if ( maClientData.aText.pCharStyle != nullptr )
+ free ( maClientData.aText.pCharStyle );
+
+ mpAttributes = nullptr;
+ mpStatusAttributes = nullptr;
+ mpPreeditAttributes = nullptr;
+ maClientData.aText.pUnicodeBuffer = nullptr;
+ maClientData.aText.pCharStyle = nullptr;
+ }
+
+ if ( maContext != nullptr)
+ {
+ maDestroyCallback.callback = IC_IMDestroyCallback;
+ maDestroyCallback.client_data = reinterpret_cast<XPointer>(this);
+ XSetICValues( maContext,
+ XNDestroyCallback, &maDestroyCallback,
+ nullptr );
+ }
+}
+
+// In Solaris 8 the status window does not unmap if the frame unmapps, so
+// unmap it the hard way
+
+void
+SalI18N_InputContext::Unmap()
+{
+ UnsetICFocus();
+ maClientData.pFrame = nullptr;
+}
+
+void
+SalI18N_InputContext::Map( SalFrame *pFrame )
+{
+ if( !mbUseable )
+ return;
+
+ if( !pFrame )
+ return;
+
+ if ( maContext == nullptr )
+ {
+ SalI18N_InputMethod *pInputMethod;
+ pInputMethod = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetInputMethod();
+
+ maContext = XCreateIC( pInputMethod->GetMethod(),
+ XNVaNestedList, mpAttributes,
+ nullptr );
+ }
+ if( maClientData.pFrame != pFrame )
+ SetICFocus( pFrame );
+}
+
+// Handle DestroyCallbacks
+// in fact this is a callback called from the XNDestroyCallback
+
+void
+SalI18N_InputContext::HandleDestroyIM()
+{
+ maContext = nullptr; // don't change
+ mbUseable = False;
+}
+
+// make sure, the input method gets all the X-Events it needs, this is only
+// called once on each frame, it relies on a valid maContext
+
+void
+SalI18N_InputContext::ExtendEventMask( ::Window aFocusWindow )
+{
+ unsigned long nIMEventMask;
+ XWindowAttributes aWindowAttributes;
+
+ if ( mbUseable )
+ {
+ Display *pDisplay = XDisplayOfIM( XIMOfIC(maContext) );
+
+ XGetWindowAttributes( pDisplay, aFocusWindow,
+ &aWindowAttributes );
+ XGetICValues ( maContext,
+ XNFilterEvents, &nIMEventMask,
+ nullptr);
+ nIMEventMask |= aWindowAttributes.your_event_mask;
+ XSelectInput ( pDisplay, aFocusWindow, nIMEventMask );
+ }
+}
+
+// tune the styles provided by the input method with the supported one
+
+unsigned int
+SalI18N_InputContext::GetWeightingOfIMStyle( XIMStyle nStyle )
+{
+ struct StyleWeightingT {
+ const XIMStyle nStyle;
+ const unsigned int nWeight;
+ };
+
+ StyleWeightingT const *pWeightPtr;
+ static const StyleWeightingT pWeight[] = {
+ { XIMPreeditCallbacks, 0x10000000 },
+ { XIMPreeditPosition, 0x02000000 },
+ { XIMPreeditArea, 0x01000000 },
+ { XIMPreeditNothing, 0x00100000 },
+ { XIMPreeditNone, 0x00010000 },
+ { XIMStatusCallbacks, 0x1000 },
+ { XIMStatusArea, 0x0100 },
+ { XIMStatusNothing, 0x0010 },
+ { XIMStatusNone, 0x0001 },
+ { 0, 0x0 }
+ };
+
+ int nWeight = 0;
+ for ( pWeightPtr = pWeight; pWeightPtr->nStyle != 0; pWeightPtr++ )
+ {
+ if ( (pWeightPtr->nStyle & nStyle) != 0 )
+ nWeight += pWeightPtr->nWeight;
+ }
+ return nWeight;
+}
+
+bool
+SalI18N_InputContext::IsSupportedIMStyle( XIMStyle nStyle ) const
+{
+ return (nStyle & mnSupportedPreeditStyle)
+ && (nStyle & g_nSupportedStatusStyle);
+}
+
+bool
+SalI18N_InputContext::SupportInputMethodStyle( XIMStyles const *pIMStyles )
+{
+ mnPreeditStyle = 0;
+ mnStatusStyle = 0;
+
+ if ( pIMStyles != nullptr )
+ {
+ int nBestScore = 0;
+ int nActualScore = 0;
+
+ // check whether the XIM supports one of the desired styles
+ // only a single preedit and a single status style must occur
+ // in an input method style. Hideki said so, so i trust him
+ for ( int nStyle = 0; nStyle < pIMStyles->count_styles; nStyle++ )
+ {
+ XIMStyle nProvidedStyle = pIMStyles->supported_styles[ nStyle ];
+ if ( IsSupportedIMStyle(nProvidedStyle) )
+ {
+ nActualScore = GetWeightingOfIMStyle( nProvidedStyle );
+ if ( nActualScore >= nBestScore )
+ {
+ nBestScore = nActualScore;
+ mnPreeditStyle = nProvidedStyle & mnSupportedPreeditStyle;
+ mnStatusStyle = nProvidedStyle & g_nSupportedStatusStyle;
+ }
+ }
+ }
+ }
+
+ return (mnPreeditStyle != 0) && (mnStatusStyle != 0) ;
+}
+
+// handle extended and normal key input
+
+void
+SalI18N_InputContext::CommitKeyEvent(sal_Unicode const * pText, std::size_t nLength)
+{
+ if (nLength == 1 && IsControlCode(pText[0]))
+ return;
+
+ if( maClientData.pFrame )
+ {
+ SalExtTextInputEvent aTextEvent;
+
+ aTextEvent.mpTextAttr = nullptr;
+ aTextEvent.mnCursorPos = nLength;
+ aTextEvent.maText = OUString(pText, nLength);
+ aTextEvent.mnCursorFlags = 0;
+
+ maClientData.pFrame->CallCallback(SalEvent::ExtTextInput, static_cast<void*>(&aTextEvent));
+ maClientData.pFrame->CallCallback(SalEvent::EndExtTextInput, nullptr);
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ SAL_WARN("vcl.app", "CommitKeyEvent without frame.");
+#endif
+}
+
+int
+SalI18N_InputContext::UpdateSpotLocation()
+{
+ if (maContext == nullptr || maClientData.pFrame == nullptr)
+ return -1;
+
+ SalExtTextInputPosEvent aPosEvent;
+ maClientData.pFrame->CallCallback(SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent));
+
+ XPoint aSpot;
+ aSpot.x = aPosEvent.mnX + aPosEvent.mnWidth;
+ aSpot.y = aPosEvent.mnY + aPosEvent.mnHeight;
+
+ XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &aSpot, nullptr);
+ XSetICValues(maContext, XNPreeditAttributes, preedit_attr, nullptr);
+ XFree(preedit_attr);
+
+ return 0;
+}
+
+// set and unset the focus for the Input Context
+// the context may be NULL despite it is usable if the framewindow is
+// in unmapped state
+
+void
+SalI18N_InputContext::SetICFocus( SalFrame* pFocusFrame )
+{
+ if ( !(mbUseable && (maContext != nullptr)) )
+ return;
+
+ maClientData.pFrame = pFocusFrame;
+
+ const SystemEnvData* pEnv = pFocusFrame->GetSystemData();
+ ::Window aClientWindow = pEnv->aShellWindow;
+ ::Window aFocusWindow = pEnv->GetWindowHandle(pFocusFrame);
+
+ XSetICValues( maContext,
+ XNFocusWindow, aFocusWindow,
+ XNClientWindow, aClientWindow,
+ nullptr );
+
+ if( maClientData.aInputEv.mpTextAttr )
+ {
+ sendEmptyCommit(pFocusFrame);
+ // begin preedit again
+ vcl_sal::getSalDisplay(GetGenericUnixSalData())->SendInternalEvent( pFocusFrame, &maClientData.aInputEv, SalEvent::ExtTextInput );
+ }
+
+ XSetICFocus( maContext );
+}
+
+void
+SalI18N_InputContext::UnsetICFocus()
+{
+
+ if ( mbUseable && (maContext != nullptr) )
+ {
+ // cancel an eventual event posted to begin preedit again
+ vcl_sal::getSalDisplay(GetGenericUnixSalData())->CancelInternalEvent( maClientData.pFrame, &maClientData.aInputEv, SalEvent::ExtTextInput );
+ maClientData.pFrame = nullptr;
+ XUnsetICFocus( maContext );
+ }
+}
+
+// multi byte input method only
+
+void
+SalI18N_InputContext::EndExtTextInput()
+{
+ if ( !mbUseable || (maContext == nullptr) || !maClientData.pFrame )
+ return;
+
+ vcl::DeletionListener aDel( maClientData.pFrame );
+ // delete preedit in sal (commit an empty string)
+ sendEmptyCommit( maClientData.pFrame );
+ if( ! aDel.isDeleted() )
+ {
+ // mark previous preedit state again (will e.g. be sent at focus gain)
+ maClientData.aInputEv.mpTextAttr = maClientData.aInputFlags.data();
+ if( static_cast<X11SalFrame*>(maClientData.pFrame)->hasFocus() )
+ {
+ // begin preedit again
+ vcl_sal::getSalDisplay(GetGenericUnixSalData())->SendInternalEvent( maClientData.pFrame, &maClientData.aInputEv, SalEvent::ExtTextInput );
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/i18n_im.cxx b/vcl/unx/generic/app/i18n_im.cxx
new file mode 100644
index 0000000000..6a655ca39e
--- /dev/null
+++ b/vcl/unx/generic/app/i18n_im.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 <stdio.h>
+#include <string.h>
+#include <iostream>
+
+#ifdef LINUX
+# ifndef __USE_XOPEN
+# define __USE_XOPEN
+# endif
+#endif
+
+#include <X11/Xlib.h>
+
+#include <unx/i18n_im.hxx>
+
+#include <osl/thread.h>
+#include <osl/process.h>
+#include <sal/log.hxx>
+
+#include <unx/i18n_cb.hxx>
+
+using namespace vcl;
+
+// kinput2 IME needs special key handling since key release events are filtered in
+// preeditmode and XmbResetIC does not work
+
+namespace {
+
+class XKeyEventOp : public XKeyEvent
+{
+ private:
+ void init();
+
+ public:
+ XKeyEventOp();
+
+ XKeyEventOp& operator= (const XKeyEvent &rEvent);
+ void erase ();
+ bool match (const XKeyEvent &rEvent) const;
+};
+
+}
+
+void
+XKeyEventOp::init()
+{
+ type = 0; /* serial = 0; */
+ send_event = 0; display = nullptr;
+ window = 0; root = 0;
+ subwindow = 0; /* time = 0; */
+ /* x = 0; y = 0; */
+ /* x_root = 0; y_root = 0; */
+ state = 0; keycode = 0;
+ same_screen = 0;
+}
+
+XKeyEventOp::XKeyEventOp()
+{
+ init();
+}
+
+XKeyEventOp&
+XKeyEventOp::operator= (const XKeyEvent &rEvent)
+{
+ type = rEvent.type; /* serial = rEvent.serial; */
+ send_event = rEvent.send_event; display = rEvent.display;
+ window = rEvent.window; root = rEvent.root;
+ subwindow = rEvent.subwindow;/* time = rEvent.time; */
+ /* x = rEvent.x, y = rEvent.y; */
+ /* x_root = rEvent.x_root, y_root = rEvent.y_root; */
+ state = rEvent.state; keycode = rEvent.keycode;
+ same_screen = rEvent.same_screen;
+
+ return *this;
+}
+
+void
+XKeyEventOp::erase ()
+{
+ init();
+}
+
+bool
+XKeyEventOp::match (const XKeyEvent &rEvent) const
+{
+ return ( (type == KeyPress && rEvent.type == KeyRelease)
+ || (type == KeyRelease && rEvent.type == KeyPress ))
+ /* && serial == rEvent.serial */
+ && send_event == rEvent.send_event
+ && display == rEvent.display
+ && window == rEvent.window
+ && root == rEvent.root
+ && subwindow == rEvent.subwindow
+ /* && time == rEvent.time
+ && x == rEvent.x
+ && y == rEvent.y
+ && x_root == rEvent.x_root
+ && y_root == rEvent.y_root */
+ && state == rEvent.state
+ && keycode == rEvent.keycode
+ && same_screen == rEvent.same_screen;
+}
+
+// locale handling
+
+// Locale handling of the operating system layer
+
+static char*
+SetSystemLocale( const char* p_inlocale )
+{
+ char *p_outlocale = setlocale(LC_ALL, p_inlocale);
+
+ SAL_WARN_IF(p_outlocale == nullptr, "vcl.app",
+ "I18N: Operating system doesn't support locale \""
+ << p_inlocale << "\".");
+
+ return p_outlocale;
+}
+
+#ifdef __sun
+static void
+SetSystemEnvironment( const OUString& rLocale )
+{
+ OUString LC_ALL_Var("LC_ALL");
+ osl_setEnvironment(LC_ALL_Var.pData, rLocale.pData);
+
+ OUString LANG_Var("LANG");
+ osl_setEnvironment(LANG_Var.pData, rLocale.pData);
+}
+#endif
+
+static Bool
+IsPosixLocale( const char* p_locale )
+{
+ if ( p_locale == nullptr )
+ return False;
+ if ( (p_locale[ 0 ] == 'C') && (p_locale[ 1 ] == '\0') )
+ return True;
+ if ( strncmp(p_locale, "POSIX", sizeof("POSIX")) == 0 )
+ return True;
+
+ return False;
+}
+
+// Locale handling of the X Window System layer
+
+static Bool
+IsXWindowCompatibleLocale( const char* p_locale )
+{
+ if ( p_locale == nullptr )
+ return False;
+
+ if ( !XSupportsLocale() )
+ {
+ SAL_WARN("vcl.app",
+ "I18N: X Window System doesn't support locale \""
+ << p_locale << "\".");
+ return False;
+ }
+ return True;
+}
+
+// Set the operating system locale prior to trying to open an
+// XIM InputMethod.
+// Handle the cases where the current locale is either not supported by the
+// operating system (LANG=gaga) or by the XWindow system (LANG=aa_ER@saaho)
+// by providing a fallback.
+// Upgrade "C" or "POSIX" to "en_US" locale to allow umlauts and accents
+// see i8988, i9188, i8930, i16318
+// on Solaris the environment needs to be set equivalent to the locale (#i37047#)
+
+void
+SalI18N_InputMethod::SetLocale()
+{
+ // check whether we want an Input Method engine, if we don't we
+ // do not need to set the locale
+ if ( !mbUseable )
+ return;
+
+ char *locale = SetSystemLocale( "" );
+ if ( (!IsXWindowCompatibleLocale(locale)) || IsPosixLocale(locale) )
+ {
+ osl_setThreadTextEncoding (RTL_TEXTENCODING_ISO_8859_1);
+ locale = SetSystemLocale( "en_US" );
+#ifdef __sun
+ SetSystemEnvironment( "en_US" );
+#endif
+ if (! IsXWindowCompatibleLocale(locale))
+ {
+ locale = SetSystemLocale( "C" );
+#ifdef __sun
+ SetSystemEnvironment( "C" );
+#endif
+ if (! IsXWindowCompatibleLocale(locale))
+ mbUseable = False;
+ }
+ }
+
+ // must not fail if mbUseable since XSupportsLocale() asserts success
+ if ( mbUseable && XSetLocaleModifiers("") == nullptr )
+ {
+ SAL_WARN("vcl.app",
+ "I18N: Can't set X modifiers for locale \""
+ << locale << "\".");
+ mbUseable = False;
+ }
+}
+
+Bool
+SalI18N_InputMethod::PosixLocale()
+{
+ if (maMethod)
+ return IsPosixLocale (XLocaleOfIM (maMethod));
+ return False;
+}
+
+// Constructor / Destructor / Initialisation
+
+SalI18N_InputMethod::SalI18N_InputMethod( )
+ : mbUseable( bUseInputMethodDefault )
+ , maMethod( nullptr )
+ , mpStyles( nullptr )
+{
+ maDestroyCallback.callback = nullptr;
+ maDestroyCallback.client_data = nullptr;
+ const char *pUseInputMethod = getenv( "SAL_USEINPUTMETHOD" );
+ if ( pUseInputMethod != nullptr )
+ mbUseable = pUseInputMethod[0] != '\0' ;
+}
+
+SalI18N_InputMethod::~SalI18N_InputMethod()
+{
+ if ( mpStyles != nullptr )
+ XFree( mpStyles );
+ if ( maMethod != nullptr )
+ XCloseIM ( maMethod );
+}
+
+// XXX
+// debug routine: lets have a look at the provided method styles
+
+#if OSL_DEBUG_LEVEL > 1
+
+extern "C" char*
+GetMethodName( XIMStyle nStyle, char *pBuf, int nBufSize)
+{
+ struct StyleName {
+ const XIMStyle nStyle;
+ const char *pName;
+ const int nNameLen;
+ };
+
+ StyleName *pDescPtr;
+ static const StyleName pDescription[] = {
+ { XIMPreeditArea, "PreeditArea ", sizeof("PreeditArea ") },
+ { XIMPreeditCallbacks, "PreeditCallbacks ",sizeof("PreeditCallbacks ")},
+ { XIMPreeditPosition, "PreeditPosition ", sizeof("PreeditPosition ") },
+ { XIMPreeditNothing, "PreeditNothing ", sizeof("PreeditNothing ") },
+ { XIMPreeditNone, "PreeditNone ", sizeof("PreeditNone ") },
+ { XIMStatusArea, "StatusArea ", sizeof("StatusArea ") },
+ { XIMStatusCallbacks, "StatusCallbacks ", sizeof("StatusCallbacks ") },
+ { XIMStatusNothing, "StatusNothing ", sizeof("StatusNothing ") },
+ { XIMStatusNone, "StatusNone ", sizeof("StatusNone ") },
+ { 0, "NULL", 0 }
+ };
+
+ if ( nBufSize > 0 )
+ pBuf[0] = '\0';
+
+ char *pBufPtr = pBuf;
+ for ( pDescPtr = const_cast<StyleName*>(pDescription); pDescPtr->nStyle != 0; pDescPtr++ )
+ {
+ int nSize = pDescPtr->nNameLen - 1;
+ if ( (nStyle & pDescPtr->nStyle) && (nBufSize > nSize) )
+ {
+ strncpy( pBufPtr, pDescPtr->pName, nSize + 1);
+ pBufPtr += nSize;
+ nBufSize -= nSize;
+ }
+ }
+
+ return pBuf;
+}
+
+extern "C" void
+PrintInputStyle( XIMStyles *pStyle )
+{
+ char pBuf[ 128 ];
+ int nBuf = sizeof( pBuf );
+
+ if ( pStyle == NULL )
+ SAL_INFO("vcl.app", "no input method styles.");
+ else
+ for ( int nStyle = 0; nStyle < pStyle->count_styles; nStyle++ )
+ {
+ SAL_INFO("vcl.app", "style #"
+ << nStyle
+ << " = "
+ << GetMethodName(pStyle->supported_styles[nStyle], pBuf, nBuf));
+ }
+}
+
+#endif
+
+// this is the real constructing routine, since locale setting has to be done
+// prior to xopendisplay, the xopenim call has to be delayed
+
+void
+SalI18N_InputMethod::CreateMethod ( Display *pDisplay )
+{
+ if ( mbUseable )
+ {
+ maMethod = XOpenIM(pDisplay, nullptr, nullptr, nullptr);
+
+ if ((maMethod == nullptr) && (getenv("XMODIFIERS") != nullptr))
+ {
+ OUString envVar("XMODIFIERS");
+ osl_clearEnvironment(envVar.pData);
+ XSetLocaleModifiers("");
+ maMethod = XOpenIM(pDisplay, nullptr, nullptr, nullptr);
+ }
+
+ if ( maMethod != nullptr )
+ {
+ if ( XGetIMValues(maMethod, XNQueryInputStyle, &mpStyles, nullptr)
+ != nullptr)
+ mbUseable = False;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "Creating Mono-Lingual InputMethod.");
+ PrintInputStyle( mpStyles );
+#endif
+ }
+ else
+ {
+ mbUseable = False;
+ }
+ }
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN_IF(!mbUseable, "vcl.app", "input method creation failed.");
+#endif
+
+ maDestroyCallback.callback = IM_IMDestroyCallback;
+ maDestroyCallback.client_data = reinterpret_cast<XPointer>(this);
+ if (mbUseable && maMethod != nullptr)
+ XSetIMValues(maMethod, XNDestroyCallback, &maDestroyCallback, nullptr);
+}
+
+// give IM the opportunity to look at the event, and possibly hide it
+
+bool
+SalI18N_InputMethod::FilterEvent( XEvent *pEvent, ::Window window )
+{
+ if (!mbUseable)
+ return False;
+
+ bool bFilterEvent = XFilterEvent (pEvent, window);
+
+ if (pEvent->type != KeyPress && pEvent->type != KeyRelease)
+ return bFilterEvent;
+
+ /*
+ * fix broken key release handling of some IMs
+ */
+ XKeyEvent* pKeyEvent = &(pEvent->xkey);
+ static XKeyEventOp s_aLastKeyPress;
+
+ if (bFilterEvent)
+ {
+ if (pKeyEvent->type == KeyRelease)
+ bFilterEvent = !s_aLastKeyPress.match (*pKeyEvent);
+ s_aLastKeyPress.erase();
+ }
+ else /* (!bFilterEvent) */
+ {
+ if (pKeyEvent->type == KeyPress)
+ s_aLastKeyPress = *pKeyEvent;
+ else
+ s_aLastKeyPress.erase();
+ }
+
+ return bFilterEvent;
+}
+
+void
+SalI18N_InputMethod::HandleDestroyIM()
+{
+ mbUseable = False;
+ maMethod = nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/i18n_keysym.cxx b/vcl/unx/generic/app/i18n_keysym.cxx
new file mode 100644
index 0000000000..a77632a3e7
--- /dev/null
+++ b/vcl/unx/generic/app/i18n_keysym.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 <X11/X.h>
+#include <sal/types.h>
+
+#include <unx/i18n_keysym.hxx>
+
+// convert keysyms to unicode
+// for all keysyms with byte1 and byte2 equal zero, and of course only for
+// keysyms that have a unicode counterpart
+
+namespace {
+
+struct keymap_t {
+ const int first; const int last;
+ const sal_Unicode *map;
+};
+
+}
+
+// Latin-1 Byte 3 = 0x00
+const sal_Unicode keymap00_map[] = {
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+ 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+ 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
+ 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
+ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
+ 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
+ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7,
+ 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
+ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7,
+ 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
+ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
+ 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
+ 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff };
+const keymap_t keymap00 = { 32, 255, keymap00_map };
+
+// Latin-2 Byte 3 = 0x01
+const sal_Unicode keymap01_map[] = {
+ 0x0104, 0x02d8, 0x0141, 0x0000, 0x013d, 0x015a, 0x0000, 0x0000,
+ 0x0160, 0x015e, 0x0164, 0x0179, 0x0000, 0x017d, 0x017b, 0x0000,
+ 0x0105, 0x02db, 0x0142, 0x0000, 0x013e, 0x015b, 0x02c7, 0x0000,
+ 0x0161, 0x015f, 0x0165, 0x017a, 0x02dd, 0x017e, 0x017c, 0x0154,
+ 0x0000, 0x0000, 0x0102, 0x0000, 0x0139, 0x0106, 0x0000, 0x010c,
+ 0x0000, 0x0118, 0x0000, 0x011a, 0x0000, 0x0000, 0x010e, 0x0110,
+ 0x0143, 0x0147, 0x0000, 0x0000, 0x0150, 0x0000, 0x0000, 0x0158,
+ 0x016e, 0x0000, 0x0170, 0x0000, 0x0000, 0x0162, 0x0000, 0x0155,
+ 0x0000, 0x0000, 0x0103, 0x0000, 0x013a, 0x0107, 0x0000, 0x010d,
+ 0x0000, 0x0119, 0x0000, 0x011b, 0x0000, 0x0000, 0x010f, 0x0111,
+ 0x0144, 0x0148, 0x0000, 0x0000, 0x0151, 0x0000, 0x0000, 0x0159,
+ 0x016f, 0x0000, 0x0171, 0x0000, 0x0000, 0x0163, 0x02d9 };
+const keymap_t keymap01 = { 161, 255, keymap01_map };
+
+// Latin-3 Byte 3 = 0x02
+const sal_Unicode keymap02_map[] = {
+ 0x0126, 0x0000, 0x0000, 0x0000, 0x0000, 0x0124, 0x0000, 0x0000,
+ 0x0130, 0x0000, 0x011e, 0x0134, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0127, 0x0000, 0x0000, 0x0000, 0x0000, 0x0125, 0x0000, 0x0000,
+ 0x0131, 0x0000, 0x011f, 0x0135, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x010a, 0x0108, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0120, 0x0000, 0x0000, 0x011c,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x016c, 0x015c, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x010b, 0x0109, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0121, 0x0000, 0x0000, 0x011d,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x016d, 0x015d };
+const keymap_t keymap02 = { 161, 254, keymap02_map };
+
+// Latin-4 Byte 3 = 0x03
+const sal_Unicode keymap03_map[] = {
+ 0x0138, 0x0156, 0x0000, 0x0128, 0x013b, 0x0000, 0x0000, 0x0000,
+ 0x0112, 0x0122, 0x0166, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0157, 0x0000, 0x0129, 0x013c, 0x0000, 0x0000, 0x0000,
+ 0x0113, 0x0123, 0x0167, 0x014a, 0x0000, 0x014b, 0x0100, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x012e, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0116, 0x0000, 0x0000, 0x012a, 0x0000, 0x0145,
+ 0x014c, 0x0136, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0172,
+ 0x0000, 0x0000, 0x0000, 0x0168, 0x016a, 0x0000, 0x0101, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x012f, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0117, 0x0000, 0x0000, 0x012b, 0x0000, 0x0146,
+ 0x014d, 0x0137, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0173,
+ 0x0000, 0x0000, 0x0000, 0x0169, 0x016b };
+const keymap_t keymap03 = { 162, 254, keymap03_map };
+
+// Kana Byte 3 = 0x04
+const sal_Unicode keymap04_map[] = {
+ 0x203e, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x3002, 0x300c, 0x300d, 0x3001, 0x30fb,
+ 0x30f2, 0x30a1, 0x30a3, 0x30a5, 0x30a7, 0x30a9, 0x30e3, 0x30e5,
+ 0x30e7, 0x30c3, 0x30fc, 0x30a2, 0x30a4, 0x30a6, 0x30a8, 0x30aa,
+ 0x30ab, 0x30ad, 0x30af, 0x30b1, 0x30b3, 0x30b5, 0x30b7, 0x30b9,
+ 0x30bb, 0x30bd, 0x30bf, 0x30c1, 0x30c4, 0x30c6, 0x30c8, 0x30ca,
+ 0x30cb, 0x30cc, 0x30cd, 0x30ce, 0x30cf, 0x30d2, 0x30d5, 0x30d8,
+ 0x30db, 0x30de, 0x30df, 0x30e0, 0x30e1, 0x30e2, 0x30e4, 0x30e6,
+ 0x30e8, 0x30e9, 0x30ea, 0x30eb, 0x30ec, 0x30ed, 0x30ef, 0x30f3,
+ 0x309b, 0x309c };
+const keymap_t keymap04 = { 126, 223, keymap04_map };
+
+// Arabic Byte 3 = 0x05
+const sal_Unicode keymap05_map[] = {
+ 0x060c, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x061b,
+ 0x0000, 0x0000, 0x0000, 0x061f, 0x0000, 0x0621, 0x0622, 0x0623,
+ 0x0624, 0x0625, 0x0626, 0x0627, 0x0628, 0x0629, 0x062a, 0x062b,
+ 0x062c, 0x062d, 0x062e, 0x062f, 0x0630, 0x0631, 0x0632, 0x0633,
+ 0x0634, 0x0635, 0x0636, 0x0637, 0x0638, 0x0639, 0x063a, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0640, 0x0641, 0x0642, 0x0643,
+ 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064a, 0x064b,
+ 0x064c, 0x064d, 0x064e, 0x064f, 0x0650, 0x0651, 0x0652 };
+const keymap_t keymap05 = { 172, 242, keymap05_map };
+
+// Cyrillic Byte 3 = 0x06
+const sal_Unicode keymap06_map[] = {
+ 0x0452, 0x0453, 0x0451, 0x0454, 0x0455, 0x0456, 0x0457, 0x0458,
+ 0x0459, 0x045a, 0x045b, 0x045c, 0x0000, 0x045e, 0x045f, 0x2116,
+ 0x0402, 0x0403, 0x0401, 0x0404, 0x0405, 0x0406, 0x0407, 0x0408,
+ 0x0409, 0x040a, 0x040b, 0x040c, 0x0000, 0x040e, 0x040f, 0x044e,
+ 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, 0x0445,
+ 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f,
+ 0x044f, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, 0x044c,
+ 0x044b, 0x0437, 0x0448, 0x044d, 0x0449, 0x0447, 0x044a, 0x042e,
+ 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, 0x0425,
+ 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f,
+ 0x042f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, 0x042c,
+ 0x042b, 0x0417, 0x0428, 0x042d, 0x0429, 0x0427, 0x042a };
+const keymap_t keymap06 = { 161, 255, keymap06_map };
+
+// Greek Byte 3 = 0x07
+const sal_Unicode keymap07_map[] = {
+ 0x0386, 0x0388, 0x0389, 0x038a, 0x03aa, 0x0000, 0x038c, 0x038e,
+ 0x03ab, 0x0000, 0x038f, 0x0000, 0x0000, 0x0385, 0x2015, 0x0000,
+ 0x03ac, 0x03ad, 0x03ae, 0x03af, 0x03ca, 0x0390, 0x03cc, 0x03cd,
+ 0x03cb, 0x03b0, 0x03ce, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398,
+ 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, 0x03a0,
+ 0x03a1, 0x03a3, 0x0000, 0x03a4, 0x03a5, 0x03a6, 0x03a7, 0x03a8,
+ 0x03a9, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, 0x03b8,
+ 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, 0x03c0,
+ 0x03c1, 0x03c3, 0x03c2, 0x03c4, 0x03c5, 0x03c6, 0x03c7, 0x03c8,
+ 0x03c9 };
+const keymap_t keymap07 = { 161, 249, keymap07_map };
+
+// Technical Byte 3 = 0x08
+const sal_Unicode keymap08_map[] = {
+ 0x23b7, 0x250c, 0x2500, 0x2320, 0x2321, 0x2502, 0x23a1, 0x23a3,
+ 0x23a4, 0x23a6, 0x239b, 0x239d, 0x239e, 0x23a0, 0x23a8, 0x23ac,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x2264, 0x2260, 0x2265, 0x222b, 0x2234,
+ 0x221d, 0x221e, 0x0000, 0x0000, 0x2207, 0x0000, 0x0000, 0x223c,
+ 0x2243, 0x0000, 0x0000, 0x0000, 0x21d4, 0x21d2, 0x2261, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x221a, 0x0000, 0x0000,
+ 0x0000, 0x2282, 0x2283, 0x2229, 0x222a, 0x2227, 0x2228, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2202, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0192, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x2190, 0x2191, 0x2192, 0x2193 };
+const keymap_t keymap08 = { 161, 254, keymap08_map };
+
+// Special Byte 3 = 0x09
+const sal_Unicode keymap09_map[] = {
+ 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x0000, 0x0000,
+ 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba,
+ 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c,
+ 0x2502 };
+const keymap_t keymap09 = { 224, 248, keymap09_map };
+
+// Publishing Byte 3 = 0x0a = 10
+const sal_Unicode keymap10_map[] = {
+ 0x2003, 0x2002, 0x2004, 0x2005, 0x2007, 0x2008, 0x2009, 0x200a,
+ 0x2014, 0x2013, 0x0000, 0x0000, 0x0000, 0x2026, 0x2025, 0x2153,
+ 0x2154, 0x2155, 0x2156, 0x2157, 0x2158, 0x2159, 0x215a, 0x2105,
+ 0x0000, 0x0000, 0x2012, 0x2329, 0x0000, 0x232a, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x215b, 0x215c, 0x215d, 0x215e, 0x0000, 0x0000,
+ 0x2122, 0x2613, 0x0000, 0x25c1, 0x25b7, 0x25cb, 0x25af, 0x2018,
+ 0x2019, 0x201c, 0x201d, 0x211e, 0x0000, 0x2032, 0x2033, 0x0000,
+ 0x271d, 0x0000, 0x25ac, 0x25c0, 0x25b6, 0x25cf, 0x25ae, 0x25e6,
+ 0x25ab, 0x25ad, 0x25b3, 0x25bd, 0x2606, 0x2022, 0x25aa, 0x25b2,
+ 0x25bc, 0x261c, 0x261e, 0x2663, 0x2666, 0x2665, 0x0000, 0x2720,
+ 0x2020, 0x2021, 0x2713, 0x2717, 0x266f, 0x266d, 0x2642, 0x2640,
+ 0x260e, 0x2315, 0x2117, 0x2038, 0x201a, 0x201e };
+const keymap_t keymap10 = { 161, 254, keymap10_map };
+
+// APL Byte 3 = 0x0b = 11
+const sal_Unicode keymap11_map[] = {
+ 0x003c, 0x0000, 0x0000, 0x003e, 0x0000, 0x2228, 0x2227, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x00af, 0x0000, 0x22a5,
+ 0x2229, 0x230a, 0x0000, 0x005f, 0x0000, 0x0000, 0x0000, 0x2218,
+ 0x0000, 0x2395, 0x0000, 0x22a4, 0x25cb, 0x0000, 0x0000, 0x0000,
+ 0x2308, 0x0000, 0x0000, 0x222a, 0x0000, 0x2283, 0x0000, 0x2282,
+ 0x0000, 0x22a2, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x22a3 };
+const keymap_t keymap11 = { 163, 252, keymap11_map };
+
+// Hebrew Byte 3 = 0x0c = 12
+const sal_Unicode keymap12_map[] = {
+ 0x2017, 0x05d0, 0x05d1, 0x05d2, 0x05d3, 0x05d4, 0x05d5, 0x05d6,
+ 0x05d7, 0x05d8, 0x05d9, 0x05da, 0x05db, 0x05dc, 0x05dd, 0x05de,
+ 0x05df, 0x05e0, 0x05e1, 0x05e2, 0x05e3, 0x05e4, 0x05e5, 0x05e6,
+ 0x05e7, 0x05e8, 0x05e9, 0x05ea };
+const keymap_t keymap12 = { 223, 250, keymap12_map };
+
+// Thai Byte 3 = 0x0d = 13
+const sal_Unicode keymap13_map[] = {
+ 0x0e01, 0x0e02, 0x0e03, 0x0e04, 0x0e05, 0x0e06, 0x0e07, 0x0e08,
+ 0x0e09, 0x0e0a, 0x0e0b, 0x0e0c, 0x0e0d, 0x0e0e, 0x0e0f, 0x0e10,
+ 0x0e11, 0x0e12, 0x0e13, 0x0e14, 0x0e15, 0x0e16, 0x0e17, 0x0e18,
+ 0x0e19, 0x0e1a, 0x0e1b, 0x0e1c, 0x0e1d, 0x0e1e, 0x0e1f, 0x0e20,
+ 0x0e21, 0x0e22, 0x0e23, 0x0e24, 0x0e25, 0x0e26, 0x0e27, 0x0e28,
+ 0x0e29, 0x0e2a, 0x0e2b, 0x0e2c, 0x0e2d, 0x0e2e, 0x0e2f, 0x0e30,
+ 0x0e31, 0x0e32, 0x0e33, 0x0e34, 0x0e35, 0x0e36, 0x0e37, 0x0e38,
+ 0x0e39, 0x0e3a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0e3f, 0x0e40,
+ 0x0e41, 0x0e42, 0x0e43, 0x0e44, 0x0e45, 0x0e46, 0x0e47, 0x0e48,
+ 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x0e4d, 0x0000, 0x0000, 0x0e50,
+ 0x0e51, 0x0e52, 0x0e53, 0x0e54, 0x0e55, 0x0e56, 0x0e57, 0x0e58,
+ 0x0e59 };
+const keymap_t keymap13 = { 161, 249, keymap13_map };
+
+// Korean Byte 3 = 0x0e = 14
+const sal_Unicode keymap14_map[] = {
+ 0x3131, 0x3132, 0x3133, 0x3134, 0x3135, 0x3136, 0x3137, 0x3138,
+ 0x3139, 0x313a, 0x313b, 0x313c, 0x313d, 0x313e, 0x313f, 0x3140,
+ 0x3141, 0x3142, 0x3143, 0x3144, 0x3145, 0x3146, 0x3147, 0x3148,
+ 0x3149, 0x314a, 0x314b, 0x314c, 0x314d, 0x314e, 0x314f, 0x3150,
+ 0x3151, 0x3152, 0x3153, 0x3154, 0x3155, 0x3156, 0x3157, 0x3158,
+ 0x3159, 0x315a, 0x315b, 0x315c, 0x315d, 0x315e, 0x315f, 0x3160,
+ 0x3161, 0x3162, 0x3163, 0x11a8, 0x11a9, 0x11aa, 0x11ab, 0x11ac,
+ 0x11ad, 0x11ae, 0x11af, 0x11b0, 0x11b1, 0x11b2, 0x11b3, 0x11b4,
+ 0x11b5, 0x11b6, 0x11b7, 0x11b8, 0x11b9, 0x11ba, 0x11bb, 0x11bc,
+ 0x11bd, 0x11be, 0x11bf, 0x11c0, 0x11c1, 0x11c2, 0x316d, 0x3171,
+ 0x3178, 0x317f, 0x3181, 0x3184, 0x3186, 0x318d, 0x318e, 0x11eb,
+ 0x11f0, 0x11f9, 0x0000, 0x0000, 0x0000, 0x0000, 0x20a9 };
+const keymap_t keymap14 = { 161, 255, keymap14_map };
+
+// missing:
+// Latin-8 Byte 3 = 0x12 = 18
+
+// Latin-9 Byte 3 = 0x13 = 19
+const sal_Unicode keymap19_map[] = {
+ 0x0152, 0x0153, 0x0178 };
+const keymap_t keymap19 = { 188, 190, keymap19_map };
+
+// missing:
+// Armenian Byte 3 = 0x14 = 20
+// Georgian Byte 3 = 0x15 = 21
+// Azeri Byte 3 = 0x16 = 22
+// Vietnamese Byte 3 = 0x1e = 30
+
+// Currency Byte 3 = 0x20 = 32
+const sal_Unicode keymap32_map[] = {
+ 0x20a0, 0x20a1, 0x20a2, 0x20a3, 0x20a4, 0x20a5, 0x20a6, 0x20a7,
+ 0x20a8, 0x0000, 0x20aa, 0x20ab, 0x20ac };
+const keymap_t keymap32 = { 160, 172, keymap32_map };
+
+// Keyboard (Keypad mappings) Byte 3 = 0xff = 255
+const sal_Unicode keymap255_map[] = {
+ 0x0020, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x0000, 0x0000, 0x0000, 0x003d };
+const keymap_t keymap255 = { 128, 189, keymap255_map };
+
+#define INITIAL_KEYMAPS 33
+const keymap_t* const p_keymap[INITIAL_KEYMAPS] = {
+ &keymap00, &keymap01, &keymap02, &keymap03, /* 00 -- 03 */
+ &keymap04, &keymap05, &keymap06, &keymap07, /* 04 -- 07 */
+ &keymap08, &keymap09, &keymap10, &keymap11, /* 08 -- 11 */
+ &keymap12, &keymap13, &keymap14, nullptr, /* 12 -- 15 */
+ nullptr, nullptr, nullptr, &keymap19, /* 16 -- 19 */
+ nullptr, nullptr, nullptr, nullptr, /* 20 -- 23 */
+ nullptr, nullptr, nullptr, nullptr, /* 24 -- 27 */
+ nullptr, nullptr, nullptr, nullptr, /* 28 -- 31 */
+ &keymap32 /* 32 */
+};
+
+sal_Unicode
+KeysymToUnicode (KeySym nKeySym)
+{
+ // keysym is already unicode
+ if ((nKeySym & 0xff000000) == 0x01000000)
+ {
+ // strip off group indicator and iso10646 plane
+ // FIXME can't handle chars from surrogate area.
+ if (! (nKeySym & 0x00ff0000) )
+ return static_cast<sal_Unicode>(nKeySym & 0x0000ffff);
+ }
+ // legacy keysyms, switch to appropriate codeset
+ else
+ {
+ unsigned char n_byte1 = (nKeySym & 0xff000000) >> 24;
+ unsigned char n_byte2 = (nKeySym & 0x00ff0000) >> 16;
+ unsigned char n_byte3 = (nKeySym & 0x0000ff00) >> 8;
+ unsigned char n_byte4 = (nKeySym & 0x000000ff);
+
+ if (n_byte1 != 0)
+ return 0;
+ if (n_byte2 != 0)
+ return 0;
+
+ keymap_t const* p_map = nullptr;
+ if (n_byte3 < INITIAL_KEYMAPS)
+ p_map = p_keymap[n_byte3];
+ else if (n_byte3 == 255)
+ p_map = &keymap255;
+
+ if ((p_map != nullptr) && (n_byte4 >= p_map->first) && (n_byte4 <= p_map->last) )
+ return p_map->map[n_byte4 - p_map->first];
+ }
+
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/i18n_xkb.cxx b/vcl/unx/generic/app/i18n_xkb.cxx
new file mode 100644
index 0000000000..0fc4d7933f
--- /dev/null
+++ b/vcl/unx/generic/app/i18n_xkb.cxx
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <stdio.h>
+#include <iostream>
+
+#include <sal/log.hxx>
+
+#include <X11/Xlib.h>
+#include <X11/XKBlib.h>
+
+#include <unx/i18n_xkb.hxx>
+
+SalI18N_KeyboardExtension::SalI18N_KeyboardExtension( Display* pDisplay )
+ : mbUseExtension(true)
+ , mnEventBase(0)
+{
+ // allow user to set the default keyboard group idx or to disable the usage
+ // of x keyboard extension at all:
+ // setenv SAL_XKEYBOARDGROUP disables keyboard extension
+ // setenv SAL_XKEYBOARDGROUP 2 sets the keyboard group index to 2
+ // keyboard group index must be in [1,4], may be specified in hex or decimal
+ static char *pUseKeyboardExtension = getenv( "SAL_XKEYBOARDGROUP" );
+ if ( pUseKeyboardExtension != nullptr )
+ {
+ mbUseExtension = pUseKeyboardExtension[0] != '\0' ;
+ }
+
+ // query XServer support for XKB Extension,
+ // do not call XQueryExtension() / XInitExtension() due to possible version
+ // clashes !
+ if ( mbUseExtension )
+ {
+ int nMajorExtOpcode;
+ int nExtMajorVersion = XkbMajorVersion;
+ int nExtMinorVersion = XkbMinorVersion;
+ int nErrorBase = 0;
+
+ mbUseExtension = XkbQueryExtension( pDisplay,
+ &nMajorExtOpcode, &mnEventBase, &nErrorBase,
+ &nExtMajorVersion, &nExtMinorVersion ) != 0;
+ }
+
+ // query notification for changes of the keyboard group
+ if ( mbUseExtension )
+ {
+ constexpr auto XkbGroupMask = XkbGroupStateMask | XkbGroupBaseMask
+ | XkbGroupLatchMask | XkbGroupLockMask;
+
+ mbUseExtension = XkbSelectEventDetails( pDisplay,
+ XkbUseCoreKbd, XkbStateNotify, XkbGroupMask, XkbGroupMask );
+ }
+
+ // query initial keyboard group
+ if ( mbUseExtension )
+ {
+ XkbStateRec aStateRecord;
+ XkbGetState( pDisplay, XkbUseCoreKbd, &aStateRecord );
+ }
+}
+
+void
+SalI18N_KeyboardExtension::Dispatch( XEvent* pEvent )
+{
+ // must the event be handled?
+ if ( !mbUseExtension
+ || (pEvent->type != mnEventBase) )
+ return;
+
+ // only handle state notify events for now, and only interested
+ // in group details
+ sal_uInt32 nXKBType = reinterpret_cast<XkbAnyEvent*>(pEvent)->xkb_type;
+ switch ( nXKBType )
+ {
+ case XkbStateNotify:
+ break;
+
+ default:
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.app", "Got unrequested XkbAnyEvent "
+ << std::hex << std::showbase
+ << static_cast<unsigned int>(nXKBType)
+ << "/" << std::dec
+ << static_cast<int>(nXKBType));
+#endif
+ break;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/keysymnames.cxx b/vcl/unx/generic/app/keysymnames.cxx
new file mode 100644
index 0000000000..d4842df955
--- /dev/null
+++ b/vcl/unx/generic/app/keysymnames.cxx
@@ -0,0 +1,509 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <unx/saldisp.hxx>
+#include <X11/keysym.h>
+#include <sal/macros.h>
+
+#if !defined (SunXK_Undo)
+#define SunXK_Undo 0x0000FF65 // XK_Undo
+#define SunXK_Again 0x0000FF66 // XK_Redo
+#define SunXK_Find 0x0000FF68 // XK_Find
+#define SunXK_Stop 0x0000FF69 // XK_Cancel
+#define SunXK_Props 0x1005FF70
+#define SunXK_Front 0x1005FF71
+#define SunXK_Copy 0x1005FF72
+#define SunXK_Open 0x1005FF73
+#define SunXK_Paste 0x1005FF74
+#define SunXK_Cut 0x1005FF75
+#endif
+
+#include <string.h>
+#include <rtl/ustring.hxx>
+
+namespace vcl_sal {
+
+ namespace {
+
+ struct KeysymNameReplacement
+ {
+ KeySym aSymbol;
+ const char* pName;
+ };
+
+ struct KeyboardReplacements
+ {
+ const char* pLangName;
+ const KeysymNameReplacement* pReplacements;
+ int nReplacements;
+ };
+
+ }
+
+ // CAUTION CAUTION CAUTION
+ // every string value in the replacements tables must be in UTF8
+ // be careful with your editor !
+
+ const struct KeysymNameReplacement aImplReplacements_English[] =
+ {
+ { XK_Control_L, "Ctrl" },
+ { XK_Control_R, "Ctrl" },
+ { XK_Escape, "Esc" },
+ { XK_space, "Space" },
+ { XK_Page_Up, "PgUp"},
+ { XK_Page_Down, "PgDn"},
+ { XK_grave, "`"}
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Turkish[] =
+ {
+ { XK_Control_L, "Ctrl" },
+ { XK_Control_R, "Ctrl" },
+ { XK_Right, "Sa\304\237" },
+ { XK_Left, "Sol" },
+ { XK_Up, "Yukar\304\261" },
+ { XK_Down, "A\305\237a\304\237\304\261" },
+ { XK_space, "Bo\305\237luk" }
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Russian[] =
+ {
+ { XK_Right, "\320\222\320\277\321\200\320\260\320\262\320\276" },
+ { XK_Left, "\320\222\320\273\320\265\320\262\320\276" },
+ { XK_Up, "\320\222\320\262\320\265\321\200\321\205" },
+ { XK_Down, "\320\222\320\275\320\270\320\267" },
+ { XK_space, "\320\237\321\200\320\276\320\261\320\265\320\273" }
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_German[] =
+ {
+ { XK_Control_L, "Strg" },
+ { XK_Control_R, "Strg" },
+ { XK_Shift_L, "Umschalt" },
+ { XK_Shift_R, "Umschalt" },
+ { XK_Alt_L, "Alt" },
+ { XK_Alt_R, "Alt Gr" },
+ { XK_Page_Up, "Bild auf" },
+ { XK_Page_Down, "Bild ab" },
+ { XK_End, "Ende" },
+ { XK_Home, "Pos 1" },
+ { XK_Insert, "Einfg" },
+ { XK_Delete, "Entf" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "Rechts" },
+ { XK_Left, "Links" },
+ { XK_Up, "Oben" },
+ { XK_Down, "Unten" },
+ { XK_BackSpace, "R\303\274ckschritt" },
+ { XK_Return, "Eingabe" },
+ { XK_slash, "Schr\303\244gstrich" },
+ { XK_space, "Leertaste" },
+ { SunXK_Stop, "Stop" },
+ { SunXK_Again, "Wiederholen" },
+ { SunXK_Props, "Eigenschaften" },
+ { SunXK_Undo, "Zur\303\274cknehmen" },
+ { SunXK_Front, "Vordergrund" },
+ { SunXK_Copy, "Kopieren" },
+ { SunXK_Open, "\303\226ffnen" },
+ { SunXK_Paste, "Einsetzen" },
+ { SunXK_Find, "Suchen" },
+ { SunXK_Cut, "Ausschneiden" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_French[] =
+ {
+ { XK_Shift_L, "Maj" },
+ { XK_Shift_R, "Maj" },
+ { XK_Page_Up, "Pg. Pr\303\251c" },
+ { XK_Page_Down, "Pg. Suiv" },
+ { XK_End, "Fin" },
+ { XK_Home, "Origine" },
+ { XK_Insert, "Ins\303\251rer" },
+ { XK_Delete, "Suppr" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "Droite" },
+ { XK_Left, "Gauche" },
+ { XK_Up, "Haut" },
+ { XK_Down, "Bas" },
+ { XK_BackSpace, "Ret. Arr" },
+ { XK_Return, "Retour" },
+ { XK_space, "Espace" },
+ { XK_KP_Enter, "Entr\303\251e" },
+ { SunXK_Stop, "Stop" },
+ { SunXK_Again, "Encore" },
+ { SunXK_Props, "Props" },
+ { SunXK_Undo, "Annuler" },
+ { SunXK_Front, "Devant" },
+ { SunXK_Copy, "Copy" },
+ { SunXK_Open, "Ouvrir" },
+ { SunXK_Paste, "Coller" },
+ { SunXK_Find, "Cher." },
+ { SunXK_Cut, "Couper" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Italian[] =
+ {
+ { XK_Shift_L, "Maiusc" },
+ { XK_Shift_R, "Maiusc" },
+ { XK_Page_Up, "PgSu" },
+ { XK_Page_Down, "PgGiu" },
+ { XK_End, "Fine" },
+ { XK_Insert, "Ins" },
+ { XK_Delete, "Canc" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "A destra" },
+ { XK_Left, "A sinistra" },
+ { XK_Up, "Sposta verso l'alto" },
+ { XK_Down, "Sposta verso il basso" },
+ { XK_BackSpace, "Backspace" },
+ { XK_Return, "Invio" },
+ { XK_space, "Spazio" },
+ { SunXK_Stop, "Stop" },
+ { SunXK_Again, "Ancora" },
+ { SunXK_Props, "Propriet\303\240" },
+ { SunXK_Undo, "Annulla" },
+ { SunXK_Front, "Davanti" },
+ { SunXK_Copy, "Copia" },
+ { SunXK_Open, "Apri" },
+ { SunXK_Paste, "Incolla" },
+ { SunXK_Find, "Trova" },
+ { SunXK_Cut, "Taglia" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Dutch[] =
+ {
+ { XK_Page_Up, "PageUp" },
+ { XK_Page_Down, "PageDown" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "Rechts" },
+ { XK_Left, "Links" },
+ { XK_Up, "Boven" },
+ { XK_Down, "Onder" },
+ { XK_BackSpace, "Backspace" },
+ { XK_Return, "Return" },
+ { XK_space, "Spatiebalk" },
+ { SunXK_Stop, "Stop" },
+ { SunXK_Again, "Again" },
+ { SunXK_Props, "Props" },
+ { SunXK_Undo, "Undo" },
+ { SunXK_Front, "Front" },
+ { SunXK_Copy, "Copy" },
+ { SunXK_Open, "Open" },
+ { SunXK_Paste, "Paste" },
+ { SunXK_Find, "Find" },
+ { SunXK_Cut, "Cut" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Norwegian[] =
+ {
+ { XK_Shift_L, "Skift" },
+ { XK_Shift_R, "Skift" },
+ { XK_Page_Up, "PageUp" },
+ { XK_Page_Down, "PageDown" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "H\303\270yre" },
+ { XK_Left, "Venstre" },
+ { XK_Up, "Opp" },
+ { XK_Down, "Ned" },
+ { XK_BackSpace, "Tilbake" },
+ { XK_Return, "Enter" },
+ { SunXK_Stop, "Avbryt" },
+ { SunXK_Again, "Gjenta" },
+ { SunXK_Props, "Egenskaper" },
+ { SunXK_Undo, "Angre" },
+ { SunXK_Front, "Front" },
+ { SunXK_Copy, "Kopi" },
+ { SunXK_Open, "\303\205pne" },
+ { SunXK_Paste, "Lim" },
+ { SunXK_Find, "S\303\270k" },
+ { SunXK_Cut, "Klipp" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Swedish[] =
+ {
+ { XK_Shift_L, "Skift" },
+ { XK_Shift_R, "Skift" },
+ { XK_Page_Up, "PageUp" },
+ { XK_Page_Down, "PageDown" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "H\303\266ger" },
+ { XK_Left, "V\303\244nster" },
+ { XK_Up, "Up" },
+ { XK_Down, "Ned" },
+ { XK_BackSpace, "Backsteg" },
+ { XK_Return, "Retur" },
+ { XK_space, "Blank" },
+ { SunXK_Stop, "Avbryt" },
+ { SunXK_Again, "Upprepa" },
+ { SunXK_Props, "Egenskaper" },
+ { SunXK_Undo, "\303\205ngra" },
+ { SunXK_Front, "Fram" },
+ { SunXK_Copy, "Kopiera" },
+ { SunXK_Open, "\303\226ppna" },
+ { SunXK_Paste, "Klistra in" },
+ { SunXK_Find, "S\303\266k" },
+ { SunXK_Cut, "Klipp ut" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Portuguese[] =
+ {
+ { XK_Page_Up, "PageUp" },
+ { XK_Page_Down, "PageDown" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "Direita" },
+ { XK_Left, "Esquerda" },
+ { XK_Up, "Acima" },
+ { XK_Down, "Abaixo" },
+ { XK_BackSpace, "Backspace" },
+ { XK_Return, "Enter" },
+ { XK_slash, "Barra" },
+ { SunXK_Stop, "Stop" },
+ { SunXK_Again, "Again" },
+ { SunXK_Props, "Props" },
+ { SunXK_Undo, "Undo" },
+ { SunXK_Front, "Front" },
+ { SunXK_Copy, "Copy" },
+ { SunXK_Open, "Open" },
+ { SunXK_Paste, "Paste" },
+ { SunXK_Find, "Find" },
+ { SunXK_Cut, "Cut" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Slovenian[] =
+ {
+ { XK_Control_L, "Krmilka" },
+ { XK_Control_R, "Krmilka" },
+ { XK_Shift_L, "Dvigalka" },
+ { XK_Shift_R, "Dvigalka" },
+ { XK_Alt_L, "Izmenjalka" },
+ { XK_Alt_R, "Desna izmenjalka" },
+ { XK_Page_Up, "Prej\305\241nja stranf" },
+ { XK_Page_Down, "Naslednja stran" },
+ { XK_End, "Konec" },
+ { XK_Home, "Za\304\215etek" },
+ { XK_Insert, "Vstavljalka" },
+ { XK_Delete, "Brisalka" },
+ { XK_Escape, "Ube\305\276nica" },
+ { XK_Right, "Desno" },
+ { XK_Left, "Levo" },
+ { XK_Up, "Navzgor" },
+ { XK_Down, "Navzdol" },
+ { XK_BackSpace, "Vra\304\215alka" },
+ { XK_Return, "Vna\305\241alka" },
+ { XK_slash, "Po\305\241evnica" },
+ { XK_space, "Preslednica" },
+ { SunXK_Stop, "Ustavi" },
+ { SunXK_Again, "Ponovi" },
+ { SunXK_Props, "Lastnosti" },
+ { SunXK_Undo, "Razveljavi" },
+ { SunXK_Front, "Ospredje" },
+ { SunXK_Copy, "Kopiraj" },
+ { SunXK_Open, "Odpri" },
+ { SunXK_Paste, "Prilepi" },
+ { SunXK_Find, "Najdi" },
+ { SunXK_Cut, "Izre\305\276i" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Spanish[] =
+ {
+ { XK_Shift_L, "May\303\272s" },
+ { XK_Shift_R, "May\303\272s" },
+ { XK_Page_Up, "ReP\303\241g" },
+ { XK_Page_Down, "AvP\303\241g" },
+ { XK_End, "Fin" },
+ { XK_Home, "Inicio" },
+ { XK_Delete, "Supr" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "Derecha" },
+ { XK_Left, "Izquierda" },
+ { XK_Up, "Arriba" },
+ { XK_Down, "Abajo" },
+ { XK_BackSpace, "Ret" },
+ { XK_Return, "Entrada" },
+ { XK_space, "Espacio" },
+ { XK_KP_Enter, "Intro" },
+ { SunXK_Stop, "Detener" },
+ { SunXK_Again, "Repetir" },
+ { SunXK_Props, "Props" },
+ { SunXK_Undo, "Anular" },
+ { SunXK_Front, "Delante" },
+ { SunXK_Copy, "Copiar" },
+ { SunXK_Open, "Abrir" },
+ { SunXK_Paste, "Pegar" },
+ { SunXK_Find, "Buscar" },
+ { SunXK_Cut, "Cortar" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Estonian[] =
+ {
+ { XK_Page_Up, "PgUp" },
+ { XK_Page_Down, "PgDown" },
+ { XK_End, "End" },
+ { XK_Home, "Home" },
+ { XK_Insert, "Ins" },
+ { XK_Delete, "Del" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "Nool paremale" },
+ { XK_Left, "Nool vasakule" },
+ { XK_Up, "Nool \303\274les" },
+ { XK_Down, "Nool alla" },
+ { XK_BackSpace, "Tagasil\303\274ke" },
+ { XK_Return, "Enter" },
+ { XK_slash, "Kaldkriips" },
+ { XK_space, "T\303\274hik" },
+ { XK_asterisk, "T\303\244rn" },
+ { SunXK_Stop, "Peata" },
+ { SunXK_Again, "Korda" },
+ { SunXK_Props, "Omadused" },
+ { SunXK_Undo, "V\303\265ta tagasi" },
+ { SunXK_Front, "Esiplaanile" },
+ { SunXK_Copy, "Kopeeri" },
+ { SunXK_Open, "Ava" },
+ { SunXK_Paste, "Aseta" },
+ { SunXK_Find, "Otsi" },
+ { SunXK_Cut, "L\303\265ika" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Catalan[] =
+ {
+ { XK_Shift_L, "Maj" },
+ { XK_Shift_R, "Maj" },
+ { XK_Page_Up, "Re P\303\240g" },
+ { XK_Page_Down, "Av P\303\240g" },
+ { XK_End, "Fi" },
+ { XK_Home, "Inici" },
+ { XK_Delete, "Supr" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "Dreta" },
+ { XK_Left, "Esquerra" },
+ { XK_Up, "Amunt" },
+ { XK_Down, "Avall" },
+ { XK_BackSpace, "Retroc\303\251s" },
+ { XK_Return, "Retorn" },
+ { XK_space, "Espai" },
+ { XK_KP_Enter, "Retorn" },
+ { SunXK_Stop, "Atura" },
+ { SunXK_Again, "Repeteix" },
+ { SunXK_Props, "Props" },
+ { SunXK_Undo, "Desf\303\251s" },
+ { SunXK_Front, "Davant" },
+ { SunXK_Copy, "C\303\262pia" },
+ { SunXK_Open, "Obre" },
+ { SunXK_Paste, "Enganxa" },
+ { SunXK_Find, "Cerca" },
+ { SunXK_Cut, "Retalla" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Lithuanian[] =
+ {
+ { XK_Control_L, "Vald" },
+ { XK_Control_R, "Vald" },
+ { XK_Shift_L, "Lyg2" },
+ { XK_Shift_R, "Lyg2" },
+ { XK_Alt_L, "Alt" },
+ { XK_Alt_R, "Lyg3" },
+ { XK_Page_Up, "Psl\342\206\221" },
+ { XK_Page_Down, "Psl\342\206\223" },
+ { XK_End, "Pab" },
+ { XK_Home, "Prad" },
+ { XK_Insert, "\304\256terpti" },
+ { XK_Delete, "\305\240al" },
+ { XK_Escape, "Gr" },
+ { XK_Right, "De\305\241in\304\227n" },
+ { XK_Left, "Kair\304\227n" },
+ { XK_Up, "Auk\305\241tyn" },
+ { XK_Down, "\305\275emyn" },
+ { XK_BackSpace, "Naikinti" },
+ { XK_Return, "\304\256vesti" },
+ { XK_asterisk, "\305\275vaig\305\276dut\304\227" },
+ { XK_slash, "De\305\241ininis br\305\253k\305\241nys" },
+ { XK_space, "Tarpas" },
+ { SunXK_Stop, "Stabdyti" },
+ { SunXK_Again, "Kartoti" },
+ { SunXK_Props, "Savyb\304\227s" },
+ { SunXK_Undo, "At\305\241aukti" },
+ { SunXK_Front, "Priekinis planas" },
+ { SunXK_Copy, "Kopijuoti" },
+ { SunXK_Open, "Atverti" },
+ { SunXK_Paste, "\304\256d\304\227ti" },
+ { SunXK_Find, "Ie\305\241koti" },
+ { SunXK_Cut, "I\305\241kirpti" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Hungarian[] =
+ {
+ { XK_Right, "Jobbra" },
+ { XK_Left, "Balra" },
+ { XK_Up, "Fel" },
+ { XK_Down, "Le" },
+ { XK_Return, "Enter" },
+ { XK_space, "Sz\303\263k\303\266z" },
+ { XK_asterisk, "Csillag" },
+ { XK_slash, "Oszt\303\241sjel" },
+ };
+
+ const struct KeyboardReplacements aKeyboards[] =
+ {
+ { "ca", aImplReplacements_Catalan, std::size(aImplReplacements_Catalan) },
+ { "de", aImplReplacements_German, std::size(aImplReplacements_German) },
+ { "sl", aImplReplacements_Slovenian, std::size(aImplReplacements_Slovenian) },
+ { "es", aImplReplacements_Spanish, std::size(aImplReplacements_Spanish) },
+ { "et", aImplReplacements_Estonian, std::size(aImplReplacements_Estonian) },
+ { "fr", aImplReplacements_French, std::size(aImplReplacements_French) },
+ { "hu", aImplReplacements_Hungarian, std::size(aImplReplacements_Hungarian) },
+ { "it", aImplReplacements_Italian, std::size(aImplReplacements_Italian) },
+ { "lt", aImplReplacements_Lithuanian, std::size(aImplReplacements_Lithuanian) },
+ { "nl", aImplReplacements_Dutch, std::size(aImplReplacements_Dutch) },
+ { "no", aImplReplacements_Norwegian, std::size(aImplReplacements_Norwegian) },
+ { "pt", aImplReplacements_Portuguese, std::size(aImplReplacements_Portuguese) },
+ { "ru", aImplReplacements_Russian, std::size(aImplReplacements_Russian) },
+ { "sv", aImplReplacements_Swedish, std::size(aImplReplacements_Swedish) },
+ { "tr", aImplReplacements_Turkish, std::size(aImplReplacements_Turkish) },
+ };
+
+ // translate keycodes, used within the displayed menu shortcuts
+ OUString getKeysymReplacementName( std::u16string_view pLang, KeySym nSymbol )
+ {
+ for(const auto & rKeyboard : aKeyboards)
+ {
+ if( o3tl::equalsAscii( pLang, rKeyboard.pLangName ) )
+ {
+ const struct KeysymNameReplacement* pRepl = rKeyboard.pReplacements;
+ for( int m = rKeyboard.nReplacements ; m ; )
+ {
+ if( nSymbol == pRepl[--m].aSymbol )
+ return OUString( pRepl[m].pName, strlen(pRepl[m].pName), RTL_TEXTENCODING_UTF8 );
+ }
+ }
+ }
+
+ // try english fallbacks
+ const struct KeysymNameReplacement* pRepl = aImplReplacements_English;
+ for( int m = SAL_N_ELEMENTS(aImplReplacements_English); m ; )
+ {
+ if( nSymbol == pRepl[--m].aSymbol )
+ return OUString( pRepl[m].pName, strlen(pRepl[m].pName), RTL_TEXTENCODING_UTF8 );
+ }
+
+ return OUString();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/randrwrapper.cxx b/vcl/unx/generic/app/randrwrapper.cxx
new file mode 100644
index 0000000000..1ef474c347
--- /dev/null
+++ b/vcl/unx/generic/app/randrwrapper.cxx
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 USE_RANDR
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrandr.h>
+
+#include <sal/log.hxx>
+
+namespace
+{
+
+class RandRWrapper
+{
+ bool m_bValid;
+
+ explicit RandRWrapper(Display*);
+public:
+ static RandRWrapper& get(Display*);
+ static void releaseWrapper();
+
+ Bool XRRQueryExtension(Display* i_pDisp, int* o_event_base, int* o_error_base )
+ {
+ Bool bRet = False;
+ if( m_bValid )
+ bRet = ::XRRQueryExtension( i_pDisp, o_event_base, o_error_base );
+ return bRet;
+ }
+ XRRScreenConfiguration* XRRGetScreenInfo( Display* i_pDisp, Drawable i_aDrawable )
+ {
+ return m_bValid ? ::XRRGetScreenInfo( i_pDisp, i_aDrawable ) : nullptr;
+ }
+ void XRRFreeScreenConfigInfo( XRRScreenConfiguration* i_pConfig )
+ {
+ if( m_bValid )
+ ::XRRFreeScreenConfigInfo( i_pConfig );
+ }
+ void XRRSelectInput( Display* i_pDisp, ::Window i_window, int i_nMask )
+ {
+ if( m_bValid )
+ ::XRRSelectInput( i_pDisp, i_window, i_nMask );
+ }
+ int XRRUpdateConfiguration( XEvent* i_pEvent )
+ {
+ return m_bValid ? ::XRRUpdateConfiguration( i_pEvent ) : 0;
+ }
+ XRRScreenSize* XRRConfigSizes( XRRScreenConfiguration* i_pConfig, int* o_nSizes )
+ {
+ return m_bValid ? ::XRRConfigSizes( i_pConfig, o_nSizes ) : nullptr;
+ }
+ SizeID XRRConfigCurrentConfiguration( XRRScreenConfiguration* i_pConfig, Rotation* o_pRot )
+ {
+ return m_bValid ? ::XRRConfigCurrentConfiguration( i_pConfig, o_pRot ) : 0;
+ }
+ int XRRRootToScreen( Display *dpy, ::Window root )
+ {
+ return m_bValid ? ::XRRRootToScreen( dpy, root ) : -1;
+ }
+};
+
+RandRWrapper::RandRWrapper( Display* pDisplay ) :
+ m_bValid( true )
+{
+ int nEventBase = 0, nErrorBase = 0;
+ if( !XRRQueryExtension( pDisplay, &nEventBase, &nErrorBase ) )
+ m_bValid = false;
+}
+
+RandRWrapper* pWrapper = nullptr;
+
+RandRWrapper& RandRWrapper::get( Display* i_pDisplay )
+{
+ if( ! pWrapper )
+ pWrapper = new RandRWrapper( i_pDisplay );
+ return *pWrapper;
+}
+
+void RandRWrapper::releaseWrapper()
+{
+ delete pWrapper;
+ pWrapper = nullptr;
+}
+
+} // namespace
+
+#endif
+
+#include <unx/saldisp.hxx>
+#if OSL_DEBUG_LEVEL > 1
+#include <cstdio>
+#endif
+
+void SalDisplay::InitRandR( ::Window aRoot ) const
+{
+ #ifdef USE_RANDR
+ RandRWrapper::get( GetDisplay() ).XRRSelectInput( GetDisplay(), aRoot, RRScreenChangeNotifyMask );
+ #else
+ (void)this;
+ (void)aRoot;
+ #endif
+}
+
+void SalDisplay::DeInitRandR()
+{
+ #ifdef USE_RANDR
+ RandRWrapper::releaseWrapper();
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "SalDisplay::DeInitRandR().");
+#endif
+ #endif
+}
+
+void SalDisplay::processRandREvent( XEvent* pEvent )
+{
+#ifdef USE_RANDR
+ XConfigureEvent* pCnfEvent=reinterpret_cast<XConfigureEvent*>(pEvent);
+ if( !pWrapper || pWrapper->XRRRootToScreen(GetDisplay(),pCnfEvent->window) == -1 )
+ return;
+
+ int nRet = pWrapper->XRRUpdateConfiguration( pEvent );
+ if( nRet != 1 || pEvent->type == ConfigureNotify) // this should then be a XRRScreenChangeNotifyEvent
+ return;
+
+ // update screens
+ bool bNotify = false;
+ for(ScreenData & rScreen : m_aScreens)
+ {
+ if( rScreen.m_bInit )
+ {
+ XRRScreenConfiguration *pConfig = nullptr;
+ XRRScreenSize *pSizes = nullptr;
+ int nSizes = 0;
+ Rotation nRot = 0;
+ SizeID nId = 0;
+
+ pConfig = pWrapper->XRRGetScreenInfo( GetDisplay(), rScreen.m_aRoot );
+ nId = pWrapper->XRRConfigCurrentConfiguration( pConfig, &nRot );
+ pSizes = pWrapper->XRRConfigSizes( pConfig, &nSizes );
+ XRRScreenSize *pTargetSize = pSizes + nId;
+
+ bNotify = bNotify ||
+ rScreen.m_aSize.Width() != pTargetSize->width ||
+ rScreen.m_aSize.Height() != pTargetSize->height;
+
+ rScreen.m_aSize = AbsoluteScreenPixelSize( pTargetSize->width, pTargetSize->height );
+
+ pWrapper->XRRFreeScreenConfigInfo( pConfig );
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "screen " << nId
+ << " changed to size " << (int)pTargetSize->width
+ << "x" << (int)pTargetSize->height);
+#endif
+ }
+ }
+ if( bNotify )
+ emitDisplayChanged();
+#else
+ (void)this;
+ (void)pEvent;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/saldata.cxx b/vcl/unx/generic/app/saldata.cxx
new file mode 100644
index 0000000000..34c7c08789
--- /dev/null
+++ b/vcl/unx/generic/app/saldata.cxx
@@ -0,0 +1,775 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <errno.h>
+#ifdef SUN
+#include <sys/systeminfo.h>
+#endif
+#ifdef FREEBSD
+#include <sys/types.h>
+#include <sys/time.h>
+#endif
+
+#include <osl/process.h>
+
+#include <unx/saldisp.hxx>
+#include <unx/saldata.hxx>
+#include <unx/salunxtime.h>
+#include <unx/sm.hxx>
+#include <unx/i18n_im.hxx>
+
+#include <X11/Xlib.h>
+#include <X11/Xproto.h>
+
+#include <salinst.hxx>
+#include <saltimer.hxx>
+
+#include <osl/diagnose.h>
+#include <osl/signal.h>
+#include <osl/thread.h>
+#include <sal/log.hxx>
+
+#include <vcl/svapp.hxx>
+
+X11SalData* GetX11SalData()
+{
+ return static_cast<X11SalData*>(ImplGetSVData()->mpSalData);
+}
+
+extern "C" {
+
+static int XErrorHdl( Display *pDisplay, XErrorEvent *pEvent )
+{
+ GetX11SalData()->XError( pDisplay, pEvent );
+ return 0;
+}
+
+static int XIOErrorHdl( Display * )
+{
+ if ( Application::IsMainThread() )
+ {
+ /* #106197# hack: until a real shutdown procedure exists
+ * _exit ASAP
+ */
+ if( ImplGetSVData()->maAppData.mbAppQuit )
+ _exit(1);
+
+ // really bad hack
+ if( ! SessionManagerClient::checkDocumentsSaved() )
+ /* oslSignalAction eToDo = */ osl_raiseSignal (OSL_SIGNAL_USER_X11SUBSYSTEMERROR, nullptr);
+ }
+
+ std::fprintf( stderr, "X IO Error\n" );
+ std::fflush( stdout );
+ std::fflush( stderr );
+
+ /* #106197# the same reasons to use _exit instead of exit in salmain
+ * do apply here. Since there is nothing to be done after an XIO
+ * error we have to _exit immediately.
+ */
+ _exit(1);
+ return 0;
+}
+
+}
+
+const struct timeval noyield_ = { 0, 0 };
+const struct timeval yield_ = { 0, 10000 };
+
+static const char* XRequest[] = {
+ // see /usr/lib/X11/XErrorDB, /usr/openwin/lib/XErrorDB ...
+ nullptr,
+ "X_CreateWindow",
+ "X_ChangeWindowAttributes",
+ "X_GetWindowAttributes",
+ "X_DestroyWindow",
+ "X_DestroySubwindows",
+ "X_ChangeSaveSet",
+ "X_ReparentWindow",
+ "X_MapWindow",
+ "X_MapSubwindows",
+ "X_UnmapWindow",
+ "X_UnmapSubwindows",
+ "X_ConfigureWindow",
+ "X_CirculateWindow",
+ "X_GetGeometry",
+ "X_QueryTree",
+ "X_InternAtom",
+ "X_GetAtomName",
+ "X_ChangeProperty",
+ "X_DeleteProperty",
+ "X_GetProperty",
+ "X_ListProperties",
+ "X_SetSelectionOwner",
+ "X_GetSelectionOwner",
+ "X_ConvertSelection",
+ "X_SendEvent",
+ "X_GrabPointer",
+ "X_UngrabPointer",
+ "X_GrabButton",
+ "X_UngrabButton",
+ "X_ChangeActivePointerGrab",
+ "X_GrabKeyboard",
+ "X_UngrabKeyboard",
+ "X_GrabKey",
+ "X_UngrabKey",
+ "X_AllowEvents",
+ "X_GrabServer",
+ "X_UngrabServer",
+ "X_QueryPointer",
+ "X_GetMotionEvents",
+ "X_TranslateCoords",
+ "X_WarpPointer",
+ "X_SetInputFocus",
+ "X_GetInputFocus",
+ "X_QueryKeymap",
+ "X_OpenFont",
+ "X_CloseFont",
+ "X_QueryFont",
+ "X_QueryTextExtents",
+ "X_ListFonts",
+ "X_ListFontsWithInfo",
+ "X_SetFontPath",
+ "X_GetFontPath",
+ "X_CreatePixmap",
+ "X_FreePixmap",
+ "X_CreateGC",
+ "X_ChangeGC",
+ "X_CopyGC",
+ "X_SetDashes",
+ "X_SetClipRectangles",
+ "X_FreeGC",
+ "X_ClearArea",
+ "X_CopyArea",
+ "X_CopyPlane",
+ "X_PolyPoint",
+ "X_PolyLine",
+ "X_PolySegment",
+ "X_PolyRectangle",
+ "X_PolyArc",
+ "X_FillPoly",
+ "X_PolyFillRectangle",
+ "X_PolyFillArc",
+ "X_PutImage",
+ "X_GetImage",
+ "X_PolyText8",
+ "X_PolyText16",
+ "X_ImageText8",
+ "X_ImageText16",
+ "X_CreateColormap",
+ "X_FreeColormap",
+ "X_CopyColormapAndFree",
+ "X_InstallColormap",
+ "X_UninstallColormap",
+ "X_ListInstalledColormaps",
+ "X_AllocColor",
+ "X_AllocNamedColor",
+ "X_AllocColorCells",
+ "X_AllocColorPlanes",
+ "X_FreeColors",
+ "X_StoreColors",
+ "X_StoreNamedColor",
+ "X_QueryColors",
+ "X_LookupColor",
+ "X_CreateCursor",
+ "X_CreateGlyphCursor",
+ "X_FreeCursor",
+ "X_RecolorCursor",
+ "X_QueryBestSize",
+ "X_QueryExtension",
+ "X_ListExtensions",
+ "X_ChangeKeyboardMapping",
+ "X_GetKeyboardMapping",
+ "X_ChangeKeyboardControl",
+ "X_GetKeyboardControl",
+ "X_Bell",
+ "X_ChangePointerControl",
+ "X_GetPointerControl",
+ "X_SetScreenSaver",
+ "X_GetScreenSaver",
+ "X_ChangeHosts",
+ "X_ListHosts",
+ "X_SetAccessControl",
+ "X_SetCloseDownMode",
+ "X_KillClient",
+ "X_RotateProperties",
+ "X_ForceScreenSaver",
+ "X_SetPointerMapping",
+ "X_GetPointerMapping",
+ "X_SetModifierMapping",
+ "X_GetModifierMapping",
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ "X_NoOperation"
+};
+
+X11SalData::X11SalData()
+ : GenericUnixSalData()
+{
+ pXLib_ = nullptr;
+
+ m_aOrigXIOErrorHandler = XSetIOErrorHandler ( XIOErrorHdl );
+ PushXErrorLevel( !!getenv( "SAL_IGNOREXERRORS" ) );
+}
+
+X11SalData::~X11SalData()
+{
+ DeleteDisplay();
+ PopXErrorLevel();
+ XSetIOErrorHandler (m_aOrigXIOErrorHandler);
+}
+
+void X11SalData::Dispose()
+{
+ delete GetDisplay();
+ SetSalData( nullptr );
+}
+
+void X11SalData::DeleteDisplay()
+{
+ delete GetDisplay();
+ SetDisplay( nullptr );
+ pXLib_.reset();
+}
+
+void X11SalData::Init()
+{
+ pXLib_.reset(new SalXLib());
+ pXLib_->Init();
+}
+
+void X11SalData::ErrorTrapPush()
+{
+ PushXErrorLevel( true );
+}
+
+bool X11SalData::ErrorTrapPop( bool bIgnoreError )
+{
+ bool err = false;
+ if( !bIgnoreError )
+ err = HasXErrorOccurred();
+ ResetXErrorOccurred();
+ PopXErrorLevel();
+ return err;
+}
+
+void X11SalData::PushXErrorLevel( bool bIgnore )
+{
+ m_aXErrorHandlerStack.emplace_back( );
+ XErrorStackEntry& rEnt = m_aXErrorHandlerStack.back();
+ rEnt.m_bWas = false;
+ rEnt.m_bIgnore = bIgnore;
+ rEnt.m_aHandler = XSetErrorHandler( XErrorHdl );
+}
+
+void X11SalData::PopXErrorLevel()
+{
+ if( !m_aXErrorHandlerStack.empty() )
+ {
+ XSetErrorHandler( m_aXErrorHandlerStack.back().m_aHandler );
+ m_aXErrorHandlerStack.pop_back();
+ }
+}
+
+SalXLib::SalXLib()
+{
+ m_aTimeout.tv_sec = 0;
+ m_aTimeout.tv_usec = 0;
+ m_nTimeoutMS = 0;
+
+ nFDs_ = 0;
+ FD_ZERO( &aReadFDS_ );
+ FD_ZERO( &aExceptionFDS_ );
+
+ m_pInputMethod = nullptr;
+ m_pDisplay = nullptr;
+
+ m_pTimeoutFDS[0] = m_pTimeoutFDS[1] = -1;
+ if (pipe (m_pTimeoutFDS) == -1)
+ return;
+
+ // initialize 'wakeup' pipe.
+ int flags;
+
+ // set close-on-exec descriptor flag.
+ if ((flags = fcntl (m_pTimeoutFDS[0], F_GETFD)) != -1)
+ {
+ flags |= FD_CLOEXEC;
+ (void)fcntl(m_pTimeoutFDS[0], F_SETFD, flags);
+ }
+ if ((flags = fcntl (m_pTimeoutFDS[1], F_GETFD)) != -1)
+ {
+ flags |= FD_CLOEXEC;
+ (void)fcntl(m_pTimeoutFDS[1], F_SETFD, flags);
+ }
+
+ // set non-blocking I/O flag.
+ if ((flags = fcntl (m_pTimeoutFDS[0], F_GETFL)) != -1)
+ {
+ flags |= O_NONBLOCK;
+ (void)fcntl(m_pTimeoutFDS[0], F_SETFL, flags);
+ }
+ if ((flags = fcntl (m_pTimeoutFDS[1], F_GETFL)) != -1)
+ {
+ flags |= O_NONBLOCK;
+ (void)fcntl(m_pTimeoutFDS[1], F_SETFL, flags);
+ }
+
+ // insert [0] into read descriptor set.
+ FD_SET( m_pTimeoutFDS[0], &aReadFDS_ );
+ nFDs_ = m_pTimeoutFDS[0] + 1;
+}
+
+SalXLib::~SalXLib()
+{
+ // close 'wakeup' pipe.
+ close (m_pTimeoutFDS[0]);
+ close (m_pTimeoutFDS[1]);
+
+ m_pInputMethod.reset();
+}
+
+static Display *OpenX11Display(OString& rDisplay)
+{
+ /*
+ * open connection to X11 Display
+ * try in this order:
+ * o -display command line parameter,
+ * o $DISPLAY environment variable
+ * o default display
+ */
+
+ Display *pDisp = nullptr;
+
+ // is there a -display command line parameter?
+
+ sal_uInt32 nParams = osl_getCommandArgCount();
+ OUString aParam;
+ for (sal_uInt32 i=0; i<nParams; i++)
+ {
+ osl_getCommandArg(i, &aParam.pData);
+ if ( aParam == "-display" )
+ {
+ osl_getCommandArg(i+1, &aParam.pData);
+ rDisplay = OUStringToOString(
+ aParam, osl_getThreadTextEncoding());
+
+ if ((pDisp = XOpenDisplay(rDisplay.getStr()))!=nullptr)
+ {
+ /*
+ * if a -display switch was used, we need
+ * to set the environment accordingly since
+ * the clipboard build another connection
+ * to the xserver using $DISPLAY
+ */
+ OUString envVar("DISPLAY");
+ osl_setEnvironment(envVar.pData, aParam.pData);
+ }
+ break;
+ }
+ }
+
+ if (!pDisp && rDisplay.isEmpty())
+ {
+ // Open $DISPLAY or default...
+ char *pDisplay = getenv("DISPLAY");
+ if (pDisplay != nullptr)
+ rDisplay = OString(pDisplay);
+ pDisp = XOpenDisplay(pDisplay);
+ }
+
+ return pDisp;
+}
+
+void SalXLib::Init()
+{
+ m_pInputMethod.reset( new SalI18N_InputMethod );
+ m_pInputMethod->SetLocale();
+ XrmInitialize();
+
+ OString aDisplay;
+ m_pDisplay = OpenX11Display(aDisplay);
+
+ if ( m_pDisplay )
+ return;
+
+ OUString aProgramFileURL;
+ osl_getExecutableFile( &aProgramFileURL.pData );
+ OUString aProgramSystemPath;
+ osl_getSystemPathFromFileURL (aProgramFileURL.pData, &aProgramSystemPath.pData);
+ OString aProgramName = OUStringToOString(
+ aProgramSystemPath,
+ osl_getThreadTextEncoding() );
+ std::fprintf( stderr, "%s X11 error: Can't open display: %s\n",
+ aProgramName.getStr(), aDisplay.getStr());
+ std::fprintf( stderr, " Set DISPLAY environment variable, use -display option\n");
+ std::fprintf( stderr, " or check permissions of your X-Server\n");
+ std::fprintf( stderr, " (See \"man X\" resp. \"man xhost\" for details)\n");
+ std::fflush( stderr );
+ exit(0);
+
+}
+
+extern "C" {
+static void EmitFontpathWarning()
+{
+ static Bool bOnce = False;
+ if ( !bOnce )
+ {
+ bOnce = True;
+ std::fprintf( stderr, "Please verify your fontpath settings\n"
+ "\t(See \"man xset\" for details"
+ " or ask your system administrator)\n" );
+ }
+}
+
+} /* extern "C" */
+
+static void PrintXError( Display *pDisplay, XErrorEvent *pEvent )
+{
+ char msg[ 120 ] = "";
+ XGetErrorText( pDisplay, pEvent->error_code, msg, sizeof( msg ) );
+ std::fprintf( stderr, "X-Error: %s\n", msg );
+ if( pEvent->request_code < SAL_N_ELEMENTS( XRequest ) )
+ {
+ const char* pName = XRequest[pEvent->request_code];
+ if( !pName )
+ pName = "BadRequest?";
+ std::fprintf( stderr, "\tMajor opcode: %d (%s)\n", pEvent->request_code, pName );
+ }
+ else
+ {
+ std::fprintf( stderr, "\tMajor opcode: %d\n", pEvent->request_code );
+ // TODO: also display extension name?
+ std::fprintf( stderr, "\tMinor opcode: %d\n", pEvent->minor_code );
+ }
+
+ std::fprintf( stderr, "\tResource ID: 0x%lx\n",
+ pEvent->resourceid );
+ std::fprintf( stderr, "\tSerial No: %ld (%ld)\n",
+ pEvent->serial, LastKnownRequestProcessed(pDisplay) );
+
+ if( !getenv( "SAL_SYNCHRONIZE" ) )
+ {
+ std::fprintf( stderr, "These errors are reported asynchronously,\n");
+ std::fprintf( stderr, "set environment variable SAL_SYNCHRONIZE to 1 to help debugging\n");
+ }
+
+ std::fflush( stdout );
+ std::fflush( stderr );
+}
+
+void X11SalData::XError( Display *pDisplay, XErrorEvent *pEvent )
+{
+ if( ! m_aXErrorHandlerStack.back().m_bIgnore )
+ {
+ if ( (pEvent->error_code == BadAlloc)
+ && (pEvent->request_code == X_OpenFont) )
+ {
+ static Bool bOnce = False;
+ if ( !bOnce )
+ {
+ std::fprintf(stderr, "X-Error occurred in a request for X_OpenFont\n");
+ EmitFontpathWarning();
+
+ bOnce = True ;
+ }
+ return;
+ }
+ /* ignore
+ * X_SetInputFocus: it's a hint only anyway
+ * X_GetProperty: this is part of the XGetWindowProperty call and will
+ * be handled by the return value of that function
+ */
+ else if( pEvent->request_code == X_SetInputFocus ||
+ pEvent->request_code == X_GetProperty
+ )
+ return;
+
+ if( pDisplay != vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay() )
+ return;
+
+ PrintXError( pDisplay, pEvent );
+
+ oslSignalAction eToDo = osl_raiseSignal (OSL_SIGNAL_USER_X11SUBSYSTEMERROR, nullptr);
+ switch (eToDo)
+ {
+ case osl_Signal_ActIgnore :
+ return;
+ case osl_Signal_ActAbortApp :
+ abort();
+ case osl_Signal_ActKillApp :
+ exit(0);
+ case osl_Signal_ActCallNextHdl :
+ break;
+ default :
+ break;
+ }
+
+ }
+
+ m_aXErrorHandlerStack.back().m_bWas = true;
+}
+
+void X11SalData::Timeout()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->maSchedCtx.mpSalTimer )
+ pSVData->maSchedCtx.mpSalTimer->CallCallback();
+}
+
+namespace {
+
+struct YieldEntry
+{
+ int fd; // file descriptor for reading
+ void* data; // data for predicate and callback
+ YieldFunc pending; // predicate (determines pending events)
+ YieldFunc queued; // read and queue up events
+ YieldFunc handle; // handle pending events
+
+ int HasPendingEvent() const { return pending( fd, data ); }
+ int IsEventQueued() const { return queued( fd, data ); }
+ void HandleNextEvent() const { handle( fd, data ); }
+};
+
+}
+
+#define MAX_NUM_DESCRIPTORS 128
+
+static YieldEntry yieldTable[ MAX_NUM_DESCRIPTORS ];
+
+void SalXLib::Insert( int nFD, void* data,
+ YieldFunc pending,
+ YieldFunc queued,
+ YieldFunc handle )
+{
+ SAL_WARN_IF( !nFD, "vcl", "can not insert stdin descriptor" );
+ SAL_WARN_IF( yieldTable[nFD].fd, "vcl", "SalXLib::Insert fd twice" );
+
+ yieldTable[nFD].fd = nFD;
+ yieldTable[nFD].data = data;
+ yieldTable[nFD].pending = pending;
+ yieldTable[nFD].queued = queued;
+ yieldTable[nFD].handle = handle;
+
+ FD_SET( nFD, &aReadFDS_ );
+ FD_SET( nFD, &aExceptionFDS_ );
+
+ if( nFD >= nFDs_ )
+ nFDs_ = nFD + 1;
+}
+
+void SalXLib::Remove( int nFD )
+{
+ FD_CLR( nFD, &aReadFDS_ );
+ FD_CLR( nFD, &aExceptionFDS_ );
+
+ yieldTable[nFD].fd = 0;
+
+ if ( nFD == nFDs_ )
+ {
+ for ( nFD = nFDs_ - 1;
+ nFD >= 0 && !yieldTable[nFD].fd;
+ nFD-- ) ;
+
+ nFDs_ = nFD + 1;
+ }
+}
+
+bool SalXLib::CheckTimeout( bool bExecuteTimers )
+{
+ bool bRet = false;
+ if( m_aTimeout.tv_sec ) // timer is started
+ {
+ timeval aTimeOfDay;
+ gettimeofday( &aTimeOfDay, nullptr );
+ if( aTimeOfDay >= m_aTimeout )
+ {
+ bRet = true;
+ if( bExecuteTimers )
+ {
+ // timed out, update timeout
+ m_aTimeout = aTimeOfDay;
+ /*
+ * #107827# autorestart immediately, will be stopped (or set
+ * to different value in notify hdl if necessary;
+ * CheckTimeout should return false while
+ * timers are being dispatched.
+ */
+ m_aTimeout += m_nTimeoutMS;
+ // notify
+ X11SalData::Timeout();
+ }
+ }
+ }
+ return bRet;
+}
+
+bool
+SalXLib::Yield( bool bWait, bool bHandleAllCurrentEvents )
+{
+ // check for timeouts here if you want to make screenshots
+ static char* p_prioritize_timer = getenv ("SAL_HIGHPRIORITY_REPAINT");
+ bool bHandledEvent = false;
+ if (p_prioritize_timer != nullptr)
+ bHandledEvent = CheckTimeout();
+
+ const int nMaxEvents = bHandleAllCurrentEvents ? 100 : 1;
+
+ // first, check for already queued events.
+ for ( int nFD = 0; nFD < nFDs_; nFD++ )
+ {
+ YieldEntry* pEntry = &(yieldTable[nFD]);
+ if ( pEntry->fd )
+ {
+ SAL_WARN_IF( nFD != pEntry->fd, "vcl", "wrong fd in Yield()" );
+ for( int i = 0; i < nMaxEvents && pEntry->HasPendingEvent(); i++ )
+ {
+ pEntry->HandleNextEvent();
+ if( ! bHandleAllCurrentEvents )
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ // next, select with or without timeout according to bWait.
+ int nFDs = nFDs_;
+ fd_set ReadFDS = aReadFDS_;
+ fd_set ExceptionFDS = aExceptionFDS_;
+ int nFound = 0;
+
+ timeval Timeout = noyield_;
+ timeval *pTimeout = &Timeout;
+
+
+ if (bWait)
+ {
+ pTimeout = nullptr;
+ if (m_aTimeout.tv_sec) // Timer is started.
+ {
+ // determine remaining timeout.
+ gettimeofday (&Timeout, nullptr);
+ Timeout = m_aTimeout - Timeout;
+ if (yield_ >= Timeout)
+ {
+ // guard against micro timeout.
+ Timeout = yield_;
+ }
+ pTimeout = &Timeout;
+ }
+ }
+
+ {
+ // release YieldMutex (and re-acquire at block end)
+ SolarMutexReleaser aReleaser;
+ nFound = select( nFDs, &ReadFDS, nullptr, &ExceptionFDS, pTimeout );
+ }
+ if( nFound < 0 ) // error
+ {
+#ifdef DBG_UTIL
+ SAL_INFO("vcl.app", "SalXLib::Yield e=" << errno << " f=" << nFound);
+#endif
+ if( EINTR == errno )
+ {
+ errno = 0;
+ }
+ }
+
+ // usually handle timeouts here (as in 5.2)
+ if (p_prioritize_timer == nullptr)
+ bHandledEvent = CheckTimeout() || bHandledEvent;
+
+ // handle wakeup events.
+ if ((nFound > 0) && FD_ISSET(m_pTimeoutFDS[0], &ReadFDS))
+ {
+ int buffer;
+ while (read (m_pTimeoutFDS[0], &buffer, sizeof(buffer)) > 0)
+ continue;
+ nFound -= 1;
+ }
+
+ // handle other events.
+ if( nFound > 0 )
+ {
+ // now we are in the protected section !
+ // recall select if we have acquired fd's, ready for reading,
+
+ struct timeval noTimeout = { 0, 0 };
+ nFound = select( nFDs_, &ReadFDS, nullptr,
+ &ExceptionFDS, &noTimeout );
+
+ // someone-else has done the job for us
+ if (nFound == 0)
+ {
+ return false;
+ }
+
+ for ( int nFD = 0; nFD < nFDs_; nFD++ )
+ {
+ YieldEntry* pEntry = &(yieldTable[nFD]);
+ if ( pEntry->fd )
+ {
+ if ( FD_ISSET( nFD, &ExceptionFDS ) ) {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.app", "SalXLib::Yield exception.");
+#endif
+ nFound--;
+ }
+ if ( FD_ISSET( nFD, &ReadFDS ) )
+ {
+ for( int i = 0; pEntry->IsEventQueued() && i < nMaxEvents; i++ )
+ {
+ pEntry->HandleNextEvent();
+ bHandledEvent = true;
+ // if a recursive call has done the job
+ // so abort here
+ }
+ nFound--;
+ }
+ }
+ }
+ }
+
+ return bHandledEvent;
+}
+
+void SalXLib::Wakeup()
+{
+ OSL_VERIFY(write (m_pTimeoutFDS[1], "", 1) == 1);
+}
+
+void SalXLib::TriggerUserEventProcessing()
+{
+ Wakeup();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/saldisp.cxx b/vcl/unx/generic/app/saldisp.cxx
new file mode 100644
index 0000000000..6733e48323
--- /dev/null
+++ b/vcl/unx/generic/app/saldisp.cxx
@@ -0,0 +1,2489 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <unistd.h>
+
+#if defined(__sun)
+#include <osl/module.h>
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/XKBlib.h>
+
+#include <X11/cursorfont.h>
+#include <unx/x11_cursors/salcursors.h>
+#include <unx/x11_cursors/invert50.h>
+#ifdef __sun
+#define XK_KOREAN
+#endif
+#include <X11/keysym.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/Xinerama.h>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <tools/debug.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+
+#include <sal/log.hxx>
+#include <sal/types.h>
+#include <unx/i18n_im.hxx>
+#include <unx/i18n_xkb.hxx>
+#include <unx/saldisp.hxx>
+#include <unx/saldata.hxx>
+#include <salinst.hxx>
+#include <unx/salframe.h>
+#include <vcl/keycodes.hxx>
+#include <osl/diagnose.h>
+#include <unx/salobj.h>
+#include <unx/sm.hxx>
+#include <unx/wmadaptor.hxx>
+#include <unx/glyphcache.hxx>
+
+#include <poll.h>
+#include <memory>
+#include <vector>
+
+/* From <X11/Intrinsic.h> */
+typedef unsigned long Pixel;
+
+using namespace vcl_sal;
+
+#ifdef DBG_UTIL
+static const char *Null( const char *p ) { return p ? p : ""; }
+static const char *GetEnv( const char *p ) { return Null( getenv( p ) ); }
+static const char *KeyStr( KeySym n ) { return Null( XKeysymToString( n ) ); }
+
+static const char *GetAtomName( Display *d, Atom a )
+{ return Null( XGetAtomName( d, a ) ); }
+#endif
+
+// check if the resolution is sane
+static bool sal_ValidDPI(tools::Long nDPI)
+{
+ return (nDPI >= 50) && (nDPI <= 500);
+}
+
+static bool sal_GetVisualInfo( Display *pDisplay, XID nVID, XVisualInfo &rVI )
+{
+ int nInfos;
+ XVisualInfo aTemplate;
+ XVisualInfo*pInfo;
+
+ aTemplate.visualid = nVID;
+
+ pInfo = XGetVisualInfo( pDisplay, VisualIDMask, &aTemplate, &nInfos );
+ if( !pInfo )
+ return false;
+
+ rVI = *pInfo;
+ XFree( pInfo );
+
+ SAL_WARN_IF( rVI.visualid != nVID, "vcl",
+ "sal_GetVisualInfo: could not get correct visual by visualId" );
+ return true;
+}
+
+extern "C" srv_vendor_t
+sal_GetServerVendor( Display *p_display )
+{
+ struct vendor_t {
+ srv_vendor_t e_vendor; // vendor as enum
+ const char* p_name; // vendor name as returned by VendorString()
+ unsigned int n_len; // number of chars to compare
+ };
+
+ static const vendor_t vendorlist[] = {
+ { vendor_sun, "Sun Microsystems, Inc.", 10 },
+ };
+
+ // handle regular server vendors
+ char *p_name = ServerVendor( p_display );
+ for (auto const & vendor : vendorlist)
+ {
+ if ( strncmp (p_name, vendor.p_name, vendor.n_len) == 0 )
+ return vendor.e_vendor;
+ }
+
+ // vendor not found in list
+ return vendor_unknown;
+}
+
+bool SalDisplay::BestVisual( Display *pDisplay,
+ int nScreen,
+ XVisualInfo &rVI )
+{
+ VisualID nDefVID = XVisualIDFromVisual( DefaultVisual( pDisplay, nScreen ) );
+ VisualID nVID = 0;
+ char *pVID = getenv( "SAL_VISUAL" );
+ if( pVID )
+ sscanf( pVID, "%li", &nVID );
+
+ if( nVID && sal_GetVisualInfo( pDisplay, nVID, rVI ) )
+ return rVI.visualid == nDefVID;
+
+ XVisualInfo aVI;
+ aVI.screen = nScreen;
+ // get all visuals
+ int nVisuals;
+ XVisualInfo* pVInfos = XGetVisualInfo( pDisplay, VisualScreenMask,
+ &aVI, &nVisuals );
+ // pVInfos should contain at least one visual, otherwise
+ // we're in trouble
+ std::vector<int> aWeights(nVisuals);
+ int i;
+ for( i = 0; i < nVisuals; i++ )
+ {
+ bool bUsable = false;
+ int nTrueColor = 1;
+
+ if ( pVInfos[i].screen != nScreen )
+ {
+ bUsable = false;
+ }
+ else if( pVInfos[i].c_class == TrueColor )
+ {
+ nTrueColor = 2048;
+ if( pVInfos[i].depth == 24 )
+ bUsable = true;
+ }
+ else if( pVInfos[i].c_class == PseudoColor )
+ {
+ bUsable = true;
+ }
+ aWeights[i] = bUsable ? nTrueColor*pVInfos[i].depth : -1024;
+ aWeights[i] -= pVInfos[ i ].visualid;
+ }
+
+ int nBestVisual = 0;
+ int nBestWeight = -1024;
+ for( i = 0; i < nVisuals; i++ )
+ {
+ if (aWeights[i] > nBestWeight)
+ {
+ nBestWeight = aWeights[i];
+ nBestVisual = i;
+ }
+ }
+
+ rVI = pVInfos[ nBestVisual ];
+
+ XFree( pVInfos );
+ return rVI.visualid == nDefVID;
+}
+
+SalDisplay::SalDisplay( Display *display ) :
+ pXLib_( nullptr ),
+ mpKbdExtension( nullptr ),
+ pDisp_( display ),
+ m_nXDefaultScreen( 0 ),
+ nMaxRequestSize_( 0 ),
+ meServerVendor( vendor_unknown ),
+ bNumLockFromXS_( false ),
+ nNumLockIndex_( 0 ),
+ nShiftKeySym_( 0 ),
+ nCtrlKeySym_( 0 ),
+ nMod1KeySym_( 0 ),
+ m_bXinerama( false ),
+ m_nLastUserEventTime( CurrentTime )
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "SalDisplay::SalDisplay().");
+#endif
+ GenericUnixSalData *pData = GetGenericUnixSalData();
+
+ SAL_WARN_IF( pData->GetDisplay(), "vcl", "Second SalDisplay created !!!" );
+ pData->SetDisplay( this );
+
+ m_nXDefaultScreen = SalX11Screen( DefaultScreen( pDisp_ ) );
+}
+
+SalDisplay::~SalDisplay()
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "SalDisplay::~SalDisplay().");
+#endif
+ if( pDisp_ )
+ {
+ doDestruct();
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "display " << pDisp_ << " closed.");
+#endif
+ pDisp_ = nullptr;
+ }
+ // don't do this in doDestruct since RandR extension adds hooks into Display
+ // that is XCloseDisplay still needs the RandR library if it was used
+ DeInitRandR();
+}
+
+void SalDisplay::doDestruct()
+{
+ GenericUnixSalData *pData = GetGenericUnixSalData();
+
+ m_pWMAdaptor.reset();
+
+ FreetypeManager::get().ClearFontCache();
+
+ if( IsDisplay() )
+ {
+ delete mpKbdExtension;
+ mpKbdExtension = nullptr;
+
+ for( size_t i = 0; i < m_aScreens.size(); i++ )
+ {
+ ScreenData& rData = m_aScreens[i];
+ if( rData.m_bInit )
+ {
+ if( rData.m_aMonoGC != rData.m_aCopyGC )
+ XFreeGC( pDisp_, rData.m_aMonoGC );
+ XFreeGC( pDisp_, rData.m_aCopyGC );
+ XFreeGC( pDisp_, rData.m_aAndInvertedGC );
+ XFreeGC( pDisp_, rData.m_aAndGC );
+ XFreeGC( pDisp_, rData.m_aOrGC );
+ XFreeGC( pDisp_, rData.m_aStippleGC );
+ XFreePixmap( pDisp_, rData.m_hInvert50 );
+ XDestroyWindow( pDisp_, rData.m_aRefWindow );
+ Colormap aColMap = rData.m_aColormap.GetXColormap();
+ if( aColMap != None && aColMap != DefaultColormap( pDisp_, i ) )
+ XFreeColormap( pDisp_, aColMap );
+ }
+ }
+
+ for( const Cursor & aCsr : aPointerCache_ )
+ {
+ if( aCsr )
+ XFreeCursor( pDisp_, aCsr );
+ }
+
+ if( pXLib_ )
+ pXLib_->Remove( ConnectionNumber( pDisp_ ) );
+ }
+
+ if( pData->GetDisplay() == static_cast<const SalGenericDisplay *>( this ) )
+ pData->SetDisplay( nullptr );
+}
+
+static int DisplayHasEvent( int fd, void * data )
+{
+ auto pDisplay = static_cast<SalX11Display *>(data);
+ SAL_WARN_IF( ConnectionNumber( pDisplay->GetDisplay() ) != fd, "vcl",
+ "wrong fd in DisplayHasEvent" );
+ if( ! pDisplay->IsDisplay() )
+ return 0;
+
+ bool result;
+
+ SolarMutexGuard aGuard;
+ result = pDisplay->IsEvent();
+ return int(result);
+}
+static int DisplayQueue( int fd, void * data )
+{
+ auto pDisplay = static_cast<SalX11Display *>(data);
+ SAL_WARN_IF( ConnectionNumber( pDisplay->GetDisplay() ) != fd, "vcl",
+ "wrong fd in DisplayHasEvent" );
+ int result;
+
+ SolarMutexGuard aGuard;
+ result = XEventsQueued( pDisplay->GetDisplay(),
+ QueuedAfterReading );
+ return result;
+}
+static int DisplayYield( int fd, void * data )
+{
+ auto pDisplay = static_cast<SalX11Display *>(data);
+ SAL_WARN_IF( ConnectionNumber( pDisplay->GetDisplay() ) != fd, "vcl",
+ "wrong fd in DisplayHasEvent" );
+
+ SolarMutexGuard aGuard;
+ pDisplay->Yield();
+ return 1;
+}
+
+SalX11Display::SalX11Display( Display *display )
+ : SalDisplay( display )
+{
+ Init();
+
+ pXLib_ = GetX11SalData()->GetLib();
+ pXLib_->Insert( ConnectionNumber( pDisp_ ),
+ this,
+ reinterpret_cast<YieldFunc>(DisplayHasEvent),
+ reinterpret_cast<YieldFunc>(DisplayQueue),
+ reinterpret_cast<YieldFunc>(DisplayYield) );
+}
+
+SalX11Display::~SalX11Display()
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "SalX11Display::~SalX11Display().");
+#endif
+ if( pDisp_ )
+ {
+ doDestruct();
+ XCloseDisplay( pDisp_ );
+ pDisp_ = nullptr;
+ }
+}
+
+void SalX11Display::TriggerUserEventProcessing()
+{
+ if( pXLib_ )
+ pXLib_->TriggerUserEventProcessing();
+}
+
+SalDisplay::ScreenData *
+SalDisplay::initScreen( SalX11Screen nXScreen ) const
+{
+ if( nXScreen.getXScreen() >= m_aScreens.size() )
+ nXScreen = m_nXDefaultScreen;
+ ScreenData* pSD = const_cast<ScreenData *>(&m_aScreens[nXScreen.getXScreen()]);
+ if( pSD->m_bInit )
+ return nullptr;
+ pSD->m_bInit = true;
+
+ XVisualInfo aVI;
+ Colormap aColMap;
+
+ if( SalDisplay::BestVisual( pDisp_, nXScreen.getXScreen(), aVI ) ) // DefaultVisual
+ aColMap = DefaultColormap( pDisp_, nXScreen.getXScreen() );
+ else
+ aColMap = XCreateColormap( pDisp_,
+ RootWindow( pDisp_, nXScreen.getXScreen() ),
+ aVI.visual,
+ AllocNone );
+
+ Screen* pScreen = ScreenOfDisplay( pDisp_, nXScreen.getXScreen() );
+
+ pSD->m_aSize = AbsoluteScreenPixelSize( WidthOfScreen( pScreen ), HeightOfScreen( pScreen ) );
+ pSD->m_aRoot = RootWindow( pDisp_, nXScreen.getXScreen() );
+ pSD->m_aVisual = SalVisual( &aVI );
+ pSD->m_aColormap = SalColormap( this, aColMap, nXScreen );
+
+ // we're interested in configure notification of root windows
+ InitRandR( pSD->m_aRoot );
+
+ // - - - - - - - - - - Reference Window/Default Drawable - -
+ XSetWindowAttributes aXWAttributes;
+ aXWAttributes.border_pixel = 0;
+ aXWAttributes.background_pixel = 0;
+ aXWAttributes.colormap = aColMap;
+ pSD->m_aRefWindow = XCreateWindow( pDisp_,
+ pSD->m_aRoot,
+ 0,0, 16,16, 0,
+ pSD->m_aVisual.GetDepth(),
+ InputOutput,
+ pSD->m_aVisual.GetVisual(),
+ CWBorderPixel|CWBackPixel|CWColormap,
+ &aXWAttributes );
+
+ // set client leader (session id gets set when session is started)
+ if( pSD->m_aRefWindow )
+ {
+ // client leader must have WM_CLIENT_LEADER pointing to itself
+ XChangeProperty( pDisp_,
+ pSD->m_aRefWindow,
+ XInternAtom( pDisp_, "WM_CLIENT_LEADER", False ),
+ XA_WINDOW,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(&pSD->m_aRefWindow),
+ 1
+ );
+
+ OString aExec(OUStringToOString(SessionManagerClient::getExecName(), osl_getThreadTextEncoding()));
+ const char* argv[1];
+ argv[0] = aExec.getStr();
+ XSetCommand( pDisp_, pSD->m_aRefWindow, const_cast<char**>(argv), 1 );
+ XSelectInput( pDisp_, pSD->m_aRefWindow, PropertyChangeMask );
+
+ // - - - - - - - - - - GCs - - - - - - - - - - - - - - - - -
+ XGCValues values;
+ values.graphics_exposures = False;
+ values.fill_style = FillOpaqueStippled;
+ values.background = (1<<pSD->m_aVisual.GetDepth())-1;
+ values.foreground = 0;
+
+ pSD->m_aCopyGC = XCreateGC( pDisp_,
+ pSD->m_aRefWindow,
+ GCGraphicsExposures
+ | GCForeground
+ | GCBackground,
+ &values );
+ pSD->m_aAndInvertedGC= XCreateGC( pDisp_,
+ pSD->m_aRefWindow,
+ GCGraphicsExposures
+ | GCForeground
+ | GCBackground,
+ &values );
+ pSD->m_aAndGC = XCreateGC( pDisp_,
+ pSD->m_aRefWindow,
+ GCGraphicsExposures
+ | GCForeground
+ | GCBackground,
+ &values );
+ pSD->m_aOrGC = XCreateGC( pDisp_,
+ pSD->m_aRefWindow,
+ GCGraphicsExposures
+ | GCForeground
+ | GCBackground,
+ &values );
+ pSD->m_aStippleGC = XCreateGC( pDisp_,
+ pSD->m_aRefWindow,
+ GCGraphicsExposures
+ | GCFillStyle
+ | GCForeground
+ | GCBackground,
+ &values );
+
+ XSetFunction( pDisp_, pSD->m_aAndInvertedGC, GXandInverted );
+ XSetFunction( pDisp_, pSD->m_aAndGC, GXand );
+ // PowerPC Solaris 2.5 (XSun 3500) Bug: GXor = GXnop
+ XSetFunction( pDisp_, pSD->m_aOrGC, GXxor );
+
+ if( 1 == pSD->m_aVisual.GetDepth() )
+ {
+ XSetFunction( pDisp_, pSD->m_aCopyGC, GXcopyInverted );
+ pSD->m_aMonoGC = pSD->m_aCopyGC;
+ }
+ else
+ {
+ Pixmap hPixmap = XCreatePixmap( pDisp_, pSD->m_aRefWindow, 1, 1, 1 );
+ pSD->m_aMonoGC = XCreateGC( pDisp_,
+ hPixmap,
+ GCGraphicsExposures,
+ &values );
+ XFreePixmap( pDisp_, hPixmap );
+ }
+ pSD->m_hInvert50 = XCreateBitmapFromData( pDisp_,
+ pSD->m_aRefWindow,
+ reinterpret_cast<const char*>(invert50_bits),
+ invert50_width,
+ invert50_height );
+ }
+ return pSD;
+}
+
+void SalDisplay::Init()
+{
+ for( Cursor & aCsr : aPointerCache_ )
+ aCsr = None;
+
+ m_bXinerama = false;
+
+ int nDisplayScreens = ScreenCount( pDisp_ );
+ m_aScreens = std::vector<ScreenData>(nDisplayScreens);
+
+ bool bExactResolution = false;
+ /* #i15507#
+ * Xft resolution should take precedence since
+ * it is what modern desktops use.
+ */
+ const char* pValStr = XGetDefault( pDisp_, "Xft", "dpi" );
+ if( pValStr != nullptr )
+ {
+ const OString aValStr( pValStr );
+ const tools::Long nDPI = static_cast<tools::Long>(aValStr.toDouble());
+ // guard against insane resolution
+ if( sal_ValidDPI(nDPI) )
+ {
+ aResolution_ = Pair( nDPI, nDPI );
+ bExactResolution = true;
+ }
+ }
+ if( !bExactResolution )
+ {
+ /* if Xft.dpi is not set, try and find the DPI from the
+ * reported screen sizes and resolution. If there are multiple
+ * screens, just fall back to the default 96x96
+ */
+ tools::Long xDPI = 96;
+ tools::Long yDPI = 96;
+ if (m_aScreens.size() == 1) {
+ xDPI = static_cast<tools::Long>(round(DisplayWidth(pDisp_, 0)*25.4/DisplayWidthMM(pDisp_, 0)));
+ yDPI = static_cast<tools::Long>(round(DisplayHeight(pDisp_, 0)*25.4/DisplayHeightMM(pDisp_, 0)));
+ // if either is invalid set it equal to the other
+ if (!sal_ValidDPI(xDPI) && sal_ValidDPI(yDPI))
+ xDPI = yDPI;
+ if (!sal_ValidDPI(yDPI) && sal_ValidDPI(xDPI))
+ yDPI = xDPI;
+ // if both are invalid, reset them to the default
+ if (!sal_ValidDPI(xDPI) && !sal_ValidDPI(yDPI))
+ xDPI = yDPI = 96;
+ }
+ aResolution_ = Pair( xDPI, yDPI );
+ }
+
+ nMaxRequestSize_ = XExtendedMaxRequestSize( pDisp_ ) * 4;
+ if( !nMaxRequestSize_ )
+ nMaxRequestSize_ = XMaxRequestSize( pDisp_ ) * 4;
+
+ meServerVendor = sal_GetServerVendor(pDisp_);
+
+ // - - - - - - - - - - Synchronize - - - - - - - - - - - - -
+ if( getenv( "SAL_SYNCHRONIZE" ) )
+ XSynchronize( pDisp_, True );
+
+ // - - - - - - - - - - Keyboardmapping - - - - - - - - - - -
+ ModifierMapping();
+
+ // - - - - - - - - - - Window Manager - - - - - - - - - - -
+ m_pWMAdaptor = ::vcl_sal::WMAdaptor::createWMAdaptor( this );
+
+ InitXinerama();
+
+#ifdef DBG_UTIL
+ PrintInfo();
+#endif
+}
+
+void SalX11Display::SetupInput()
+{
+ GetGenericUnixSalData()->ErrorTrapPush();
+ SalI18N_KeyboardExtension *pKbdExtension = new SalI18N_KeyboardExtension( pDisp_ );
+ XSync( pDisp_, False );
+
+ bool bError = GetGenericUnixSalData()->ErrorTrapPop( false );
+ GetGenericUnixSalData()->ErrorTrapPush();
+ pKbdExtension->UseExtension( ! bError );
+ GetGenericUnixSalData()->ErrorTrapPop();
+
+ SetKbdExtension( pKbdExtension );
+}
+
+// Sound
+void SalDisplay::Beep() const
+{
+ XBell( pDisp_, 100 );
+}
+
+// Keyboard
+
+namespace {
+
+bool InitXkb(Display* dpy)
+{
+ int nOpcode, nEvent, nError;
+ int nXkbMajor = XkbMajorVersion;
+ int nXkbMinor = XkbMinorVersion;
+
+ if (!XkbLibraryVersion(&nXkbMajor, &nXkbMinor))
+ return false;
+
+ return XkbQueryExtension(
+ dpy, &nOpcode, &nEvent, &nError, &nXkbMajor, &nXkbMinor);
+}
+
+unsigned int GetKeySymMask(Display* dpy, KeySym nKeySym)
+{
+ int nMask = 0;
+ XModifierKeymap* pXmkMap = XGetModifierMapping(dpy);
+ KeyCode nKeyCode = XKeysymToKeycode(dpy, nKeySym);
+ if (nKeyCode == NoSymbol)
+ return 0;
+
+ for (int i = 0; i < 8; ++i)
+ {
+ KeyCode nThisKeyCode = pXmkMap->modifiermap[pXmkMap->max_keypermod*i];
+ if (nThisKeyCode == nKeyCode)
+ nMask = 1 << i;
+ }
+ XFreeModifiermap(pXmkMap);
+ return nMask;
+}
+
+}
+
+void SalDisplay::SimulateKeyPress( sal_uInt16 nKeyCode )
+{
+ if (nKeyCode != KEY_CAPSLOCK)
+ return;
+
+ Display* dpy = GetDisplay();
+ if (!InitXkb(dpy))
+ return;
+
+ unsigned int nMask = GetKeySymMask(dpy, XK_Caps_Lock);
+ XkbStateRec xkbState;
+ XkbGetState(dpy, XkbUseCoreKbd, &xkbState);
+ unsigned int nCapsLockState = xkbState.locked_mods & nMask;
+ if (nCapsLockState)
+ XkbLockModifiers (dpy, XkbUseCoreKbd, nMask, 0);
+ else
+ XkbLockModifiers (dpy, XkbUseCoreKbd, nMask, nMask);
+}
+
+KeyIndicatorState SalDisplay::GetIndicatorState() const
+{
+ unsigned int _state = 0;
+ KeyIndicatorState nState = KeyIndicatorState::NONE;
+ XkbGetIndicatorState(pDisp_, XkbUseCoreKbd, &_state);
+
+ if (_state & 0x00000001)
+ nState |= KeyIndicatorState::CAPSLOCK;
+ if (_state & 0x00000002)
+ nState |= KeyIndicatorState::NUMLOCK;
+ if (_state & 0x00000004)
+ nState |= KeyIndicatorState::SCROLLLOCK;
+
+ return nState;
+}
+
+OUString SalDisplay::GetKeyNameFromKeySym( KeySym nKeySym ) const
+{
+ OUString aLang = Application::GetSettings().GetUILanguageTag().getLanguage();
+ OUString aRet;
+
+ // return an empty string for keysyms that are not bound to
+ // any key code
+ KeyCode aKeyCode = XKeysymToKeycode( GetDisplay(), nKeySym );
+ static_assert(NoSymbol == 0, "X11 inconsistency");
+ if( aKeyCode != NoSymbol )
+ {
+ if( !nKeySym )
+ aRet = "???";
+ else
+ {
+ aRet = ::vcl_sal::getKeysymReplacementName( aLang, nKeySym );
+ if( aRet.isEmpty() )
+ {
+ const char *pString = XKeysymToString( nKeySym );
+ if (pString)
+ {
+ int n = strlen( pString );
+ if( n > 2 && pString[n-2] == '_' )
+ aRet = OUString( pString, n-2, RTL_TEXTENCODING_ISO_8859_1 );
+ else
+ aRet = OUString( pString, n, RTL_TEXTENCODING_ISO_8859_1 );
+ }
+ else
+ aRet = "???";
+ }
+ }
+ }
+ return aRet;
+}
+
+static KeySym sal_XModifier2Keysym( Display *pDisplay,
+ XModifierKeymap const *pXModMap,
+ int n )
+{
+ return XkbKeycodeToKeysym( pDisplay,
+ pXModMap->modifiermap[n*pXModMap->max_keypermod],
+ 0,0 );
+}
+
+void SalDisplay::ModifierMapping()
+{
+ XModifierKeymap *pXModMap = XGetModifierMapping( pDisp_ );
+
+ bNumLockFromXS_ = True;
+ nShiftKeySym_ = sal_XModifier2Keysym( pDisp_, pXModMap, ShiftMapIndex );
+ nCtrlKeySym_ = sal_XModifier2Keysym( pDisp_, pXModMap, ControlMapIndex );
+ nMod1KeySym_ = sal_XModifier2Keysym( pDisp_, pXModMap, Mod1MapIndex );
+ // on Sun and SCO servers XLookupString does not account for NumLock
+ if( GetServerVendor() == vendor_sun )
+ {
+ KeyCode aNumLock = XKeysymToKeycode( pDisp_, XK_Num_Lock );
+
+ if( aNumLock )
+ for( int i = ShiftMapIndex; i <= Mod5MapIndex; i++ )
+ {
+ if( pXModMap->modifiermap[i*pXModMap->max_keypermod] == aNumLock )
+ {
+ bNumLockFromXS_ = False;
+ nNumLockIndex_ = i;
+ break;
+ }
+ }
+ }
+
+ XFreeModifiermap( pXModMap );
+}
+
+OUString SalDisplay::GetKeyName( sal_uInt16 nKeyCode ) const
+{
+ OUString aStrMap;
+ OUString aCustomKeyName;
+
+ if( nKeyCode & KEY_MOD1 )
+ aStrMap += GetKeyNameFromKeySym( nCtrlKeySym_ );
+
+ if( nKeyCode & KEY_MOD2 )
+ {
+ if( !aStrMap.isEmpty() )
+ aStrMap += "+";
+ aStrMap += GetKeyNameFromKeySym( nMod1KeySym_ );
+ }
+
+ if( nKeyCode & KEY_SHIFT )
+ {
+ if( !aStrMap.isEmpty() )
+ aStrMap += "+";
+ aStrMap += GetKeyNameFromKeySym( nShiftKeySym_ );
+ }
+ nKeyCode &= 0x0FFF;
+
+ KeySym nKeySym = 0;
+
+ if( KEY_0 <= nKeyCode && nKeyCode <= KEY_9 )
+ nKeySym = XK_0 + (nKeyCode - KEY_0);
+ else if( KEY_A <= nKeyCode && nKeyCode <= KEY_Z )
+ nKeySym = XK_A + (nKeyCode - KEY_A);
+ else if( KEY_F1 <= nKeyCode && nKeyCode <= KEY_F26 ) // does this key exist?
+ nKeySym = XK_F1 + (nKeyCode - KEY_F1);
+ else switch( nKeyCode )
+ {
+ case KEY_DOWN:
+ nKeySym = XK_Down;
+ break;
+ case KEY_UP:
+ nKeySym = XK_Up;
+ break;
+ case KEY_LEFT:
+ nKeySym = XK_Left;
+ break;
+ case KEY_RIGHT:
+ nKeySym = XK_Right;
+ break;
+ case KEY_HOME:
+ nKeySym = XK_Home;
+ break;
+ case KEY_END:
+ nKeySym = XK_End;
+ break;
+ case KEY_PAGEUP:
+ nKeySym = XK_Page_Up;
+ break;
+ case KEY_PAGEDOWN:
+ nKeySym = XK_Page_Down;
+ break;
+ case KEY_RETURN:
+ nKeySym = XK_Return;
+ break;
+ case KEY_ESCAPE:
+ nKeySym = XK_Escape;
+ break;
+ case KEY_TAB:
+ nKeySym = XK_Tab;
+ break;
+ case KEY_BACKSPACE:
+ nKeySym = XK_BackSpace;
+ break;
+ case KEY_SPACE:
+ nKeySym = XK_space;
+ break;
+ case KEY_INSERT:
+ nKeySym = XK_Insert;
+ break;
+ case KEY_DELETE:
+ nKeySym = XK_Delete;
+ break;
+
+ #if !defined (SunXK_Undo)
+ // we don't intend to use SunXK_Undo, but if it has not been
+ // defined already, then we _do_ need the following:
+ #define SunXK_Props 0x1005FF70
+ #define SunXK_Front 0x1005FF71
+ #define SunXK_Copy 0x1005FF72
+ #define SunXK_Open 0x1005FF73
+ #define SunXK_Paste 0x1005FF74
+ #define SunXK_Cut 0x1005FF75
+ #endif
+ // the following are for XF86 systems
+ #define XF86XK_Copy 0x1008FF57
+ #define XF86XK_Cut 0x1008FF58
+ #define XF86XK_Open 0x1008FF6B
+ #define XF86XK_Paste 0x1008FF6D
+ // which leaves Apollo and OSF systems in the lurch
+
+ case KEY_REPEAT:
+ nKeySym = XK_Redo;
+ break;
+ case KEY_PROPERTIES:
+ nKeySym = SunXK_Props;
+ break;
+ case KEY_UNDO:
+ nKeySym = XK_Undo;
+ break;
+ case KEY_FRONT:
+ nKeySym = SunXK_Front;
+ break;
+ case KEY_COPY:
+ nKeySym = GetServerVendor() == vendor_sun ? SunXK_Copy : XF86XK_Copy;
+ break;
+ case KEY_OPEN:
+ nKeySym = GetServerVendor() == vendor_sun ? SunXK_Open : XF86XK_Open;
+ break;
+ case KEY_PASTE:
+ nKeySym = GetServerVendor() == vendor_sun ? SunXK_Paste : XF86XK_Paste;
+ break;
+ case KEY_FIND:
+ nKeySym = XK_Find;
+ break;
+ case KEY_CUT:
+ nKeySym = GetServerVendor() == vendor_sun ? SunXK_Cut : XF86XK_Cut;
+ /* The original code here had:
+ nKeySym = GetServerVendor() == vendor_sun ? SunXK_Cut : XK_L10;
+ if anyone can remember which non-vendor_sun system used this
+ XK_L10 keysym, and why this hack only applied to KEY_CUT,
+ then please re-hack this code to put it back
+ */
+ break;
+ case KEY_ADD:
+ aCustomKeyName = "+";
+ break;
+ case KEY_SUBTRACT:
+ aCustomKeyName = "-";
+ break;
+ case KEY_MULTIPLY:
+ nKeySym = XK_asterisk;
+ break;
+ case KEY_DIVIDE:
+ nKeySym = XK_slash;
+ break;
+ case KEY_POINT:
+ aCustomKeyName = ".";
+ break;
+ case KEY_COMMA:
+ nKeySym = XK_comma;
+ break;
+ case KEY_LESS:
+ nKeySym = XK_less;
+ break;
+ case KEY_GREATER:
+ nKeySym = XK_greater;
+ break;
+ case KEY_EQUAL:
+ nKeySym = XK_equal;
+ break;
+ case KEY_HELP:
+ nKeySym = XK_Help;
+ break;
+ case KEY_HANGUL_HANJA:
+ nKeySym = XK_Hangul_Hanja;
+ break;
+ case KEY_TILDE:
+ nKeySym = XK_asciitilde;
+ break;
+ case KEY_QUOTELEFT:
+ nKeySym = XK_grave;
+ break;
+ case KEY_BRACKETLEFT:
+ aCustomKeyName = "[";
+ break;
+ case KEY_BRACKETRIGHT:
+ aCustomKeyName = "]";
+ break;
+ case KEY_SEMICOLON:
+ aCustomKeyName = ";";
+ break;
+ case KEY_QUOTERIGHT:
+ aCustomKeyName = "'";
+ break;
+ case KEY_RIGHTCURLYBRACKET:
+ aCustomKeyName = "}";
+ break;
+ case KEY_NUMBERSIGN:
+ aCustomKeyName = "#";
+ break;
+ case KEY_XF86FORWARD:
+ aCustomKeyName = "XF86Forward";
+ break;
+ case KEY_XF86BACK:
+ aCustomKeyName = "XF86Back";
+ break;
+ case KEY_COLON:
+ aCustomKeyName = ":";
+ break;
+ default:
+ nKeySym = 0;
+ break;
+ }
+
+ if( nKeySym )
+ {
+ OUString aKeyName = GetKeyNameFromKeySym( nKeySym );
+ if( !aKeyName.isEmpty() )
+ {
+ if( !aStrMap.isEmpty() )
+ aStrMap += "+";
+ aStrMap += aKeyName;
+ }
+ else
+ aStrMap.clear();
+ }
+ else if (!aCustomKeyName.isEmpty())
+ {
+ // For semicolon, bracket left and bracket right, it's better to use
+ // their keys than their names. (fdo#32891)
+ if (!aStrMap.isEmpty())
+ aStrMap += "+";
+ aStrMap += aCustomKeyName;
+ }
+ else
+ aStrMap.clear();
+
+ return aStrMap;
+}
+
+#ifndef IsISOKey
+#define IsISOKey( n ) (0x0000FE00==((n)&0xFFFFFF00))
+#endif
+
+sal_uInt16 SalDisplay::GetKeyCode( KeySym keysym, char*pcPrintable ) const
+{
+ sal_uInt16 nKey = 0;
+
+ if( XK_a <= keysym && XK_z >= keysym )
+ nKey = static_cast<sal_uInt16>(KEY_A + (keysym - XK_a));
+ else if( XK_A <= keysym && XK_Z >= keysym )
+ nKey = static_cast<sal_uInt16>(KEY_A + (keysym - XK_A));
+ else if( XK_0 <= keysym && XK_9 >= keysym )
+ nKey = static_cast<sal_uInt16>(KEY_0 + (keysym - XK_0));
+ else if( IsModifierKey( keysym ) )
+ ;
+ else if( IsKeypadKey( keysym ) )
+ {
+ if( (keysym >= XK_KP_0) && (keysym <= XK_KP_9) )
+ {
+ nKey = static_cast<sal_uInt16>(KEY_0 + (keysym - XK_KP_0));
+ *pcPrintable = '0' + nKey - KEY_0;
+ }
+ else if( IsPFKey( keysym ) )
+ nKey = static_cast<sal_uInt16>(KEY_F1 + (keysym - XK_KP_F1));
+ else switch( keysym )
+ {
+ case XK_KP_Space:
+ nKey = KEY_SPACE;
+ *pcPrintable = ' ';
+ break;
+ case XK_KP_Tab:
+ nKey = KEY_TAB;
+ break;
+ case XK_KP_Enter:
+ nKey = KEY_RETURN;
+ break;
+ case XK_KP_Begin:
+ case XK_KP_Home:
+ nKey = KEY_HOME;
+ break;
+ case XK_KP_Left:
+ nKey = KEY_LEFT;
+ break;
+ case XK_KP_Up:
+ nKey = KEY_UP;
+ break;
+ case XK_KP_Right:
+ nKey = KEY_RIGHT;
+ break;
+ case XK_KP_Down:
+ nKey = KEY_DOWN;
+ break;
+ case XK_KP_Page_Up: // XK_KP_Page_Up
+ nKey = KEY_PAGEUP;
+ break;
+ case XK_KP_Page_Down: // XK_KP_Page_Down
+ nKey = KEY_PAGEDOWN;
+ break;
+ case XK_KP_End:
+ nKey = KEY_END;
+ break;
+ case XK_KP_Insert:
+ nKey = KEY_INSERT;
+ break;
+ case XK_KP_Delete:
+ nKey = KEY_DELETE;
+ break;
+ case XK_KP_Equal:
+ nKey = KEY_EQUAL;
+ *pcPrintable = '=';
+ break;
+ case XK_KP_Multiply:
+ nKey = KEY_MULTIPLY;
+ *pcPrintable = '*';
+ break;
+ case XK_KP_Add:
+ nKey = KEY_ADD;
+ *pcPrintable = '+';
+ break;
+ case XK_KP_Separator:
+ nKey = KEY_DECIMAL;
+ *pcPrintable = ',';
+ break;
+ case XK_KP_Subtract:
+ nKey = KEY_SUBTRACT;
+ *pcPrintable = '-';
+ break;
+ case XK_KP_Decimal:
+ nKey = KEY_DECIMAL;
+ *pcPrintable = '.';
+ break;
+ case XK_KP_Divide:
+ nKey = KEY_DIVIDE;
+ *pcPrintable = '/';
+ break;
+ }
+ }
+ else if( IsFunctionKey( keysym ) )
+ {
+ if( bNumLockFromXS_ )
+ {
+ if( keysym >= XK_F1 && keysym <= XK_F26 )
+ nKey = static_cast<sal_uInt16>(KEY_F1 + keysym - XK_F1);
+ }
+ else switch( keysym )
+ {
+ // - - - - - Sun X-Server keyboard without Cursorblock ??? - - -
+ case XK_R7: // XK_F27:
+ nKey = KEY_HOME;
+ break;
+ case XK_R8: // XK_F28:
+ nKey = KEY_UP;
+ break;
+ case XK_R9: // XK_F29:
+ nKey = KEY_PAGEUP;
+ break;
+ case XK_R10: // XK_F30:
+ nKey = KEY_LEFT;
+ break;
+ case XK_R11: // XK_F31:
+ nKey = 0; // KEY_F31
+ break;
+ case XK_R12: // XK_F32:
+ nKey = KEY_RIGHT;
+ break;
+ case XK_R13: // XK_F33:
+ nKey = KEY_END;
+ break;
+ case XK_R14: // XK_F34:
+ nKey = KEY_DOWN;
+ break;
+ case XK_R15: // XK_F35:
+ nKey = KEY_PAGEDOWN;
+ break;
+ // - - - - - Sun X-Server keyboard ??? - - - - - - - - - - - -
+ case XK_L1: // XK_F11:
+ nKey = KEY_F11; // on a sun keyboard this actually is usually SunXK_Stop = 0x0000FF69 (XK_Cancel),
+ // but VCL doesn't have a key definition for that
+ break;
+ case XK_L2: // XK_F12:
+ if ( GetServerVendor() == vendor_sun )
+ nKey = KEY_REPEAT;
+ else
+ nKey = KEY_F12;
+ break;
+ case XK_L3: // XK_F13:
+ nKey = KEY_PROPERTIES; // KEY_F13
+ break;
+ case XK_L4: // XK_F14:
+ nKey = KEY_UNDO; // KEY_F14
+ break;
+ case XK_L5: // XK_F15:
+ nKey = KEY_F15; // KEY_FRONT
+ break;
+ case XK_L6: // XK_F16:
+ nKey = KEY_COPY; // KEY_F16
+ break;
+ case XK_L7: // XK_F17:
+ nKey = KEY_F17; // KEY_OPEN
+ break;
+ case XK_L8: // XK_F18:
+ nKey = KEY_PASTE; // KEY_F18
+ break;
+ case XK_L9: // XK_F19:
+ nKey = KEY_F19; // KEY_FIND
+ break;
+ case XK_L10: // XK_F20:
+ nKey = KEY_CUT; // KEY_F20
+ break;
+ default:
+ if( keysym >= XK_F1 && keysym <= XK_F26 )
+ nKey = static_cast<sal_uInt16>(KEY_F1 + keysym - XK_F1);
+ break;
+ }
+ }
+ else if( IsCursorKey( keysym ) )
+ {
+ switch( keysym )
+ {
+ case XK_Begin:
+ case XK_Home:
+ nKey = KEY_HOME;
+ break;
+ case XK_Left:
+ nKey = KEY_LEFT;
+ break;
+ case XK_Up:
+ nKey = KEY_UP;
+ break;
+ case XK_Right:
+ nKey = KEY_RIGHT;
+ break;
+ case XK_Down:
+ nKey = KEY_DOWN;
+ break;
+ case XK_Page_Up: // XK_Page_Up
+ nKey = KEY_PAGEUP;
+ break;
+ case XK_Page_Down: // XK_Page_Down
+ nKey = KEY_PAGEDOWN;
+ break;
+ case XK_End:
+ nKey = KEY_END;
+ break;
+ }
+ }
+ else if( IsMiscFunctionKey( keysym ) )
+ {
+ switch( keysym )
+ {
+ case XK_Insert:
+ nKey = KEY_INSERT;
+ break;
+ case XK_Redo:
+ nKey = KEY_REPEAT;
+ break;
+ case XK_Undo:
+ nKey = KEY_UNDO;
+ break;
+ case XK_Find:
+ nKey = KEY_FIND;
+ break;
+ case XK_Help:
+ nKey = KEY_HELP;
+ break;
+ case XK_Menu:
+ nKey = KEY_CONTEXTMENU;
+ break;
+ }
+ }
+ else if( IsISOKey( keysym ) ) // XK_ISO_
+ {
+ switch( keysym )
+ {
+ case 0xFE20: // XK_ISO_Left_Tab:
+ nKey = KEY_TAB;
+ break;
+ }
+ }
+ else switch( keysym )
+ {
+ case XK_Return:
+ nKey = KEY_RETURN;
+ break;
+ case XK_BackSpace:
+ nKey = KEY_BACKSPACE;
+ break;
+ case XK_Delete:
+ nKey = KEY_DELETE;
+ break;
+ case XK_space:
+ nKey = KEY_SPACE;
+ break;
+ case XK_Tab:
+ nKey = KEY_TAB;
+ break;
+ case XK_Escape:
+ nKey = KEY_ESCAPE;
+ break;
+ case XK_plus:
+ nKey = KEY_ADD;
+ break;
+ case XK_minus:
+ nKey = KEY_SUBTRACT;
+ break;
+ case XK_asterisk:
+ nKey = KEY_MULTIPLY;
+ break;
+ case XK_slash:
+ nKey = KEY_DIVIDE;
+ break;
+ case XK_period:
+ nKey = KEY_POINT;
+ *pcPrintable = '.';
+ break;
+ case XK_comma:
+ nKey = KEY_COMMA;
+ break;
+ case XK_less:
+ nKey = KEY_LESS;
+ break;
+ case XK_greater:
+ nKey = KEY_GREATER;
+ break;
+ case XK_equal:
+ nKey = KEY_EQUAL;
+ break;
+ case XK_Hangul_Hanja:
+ nKey = KEY_HANGUL_HANJA;
+ break;
+ case XK_asciitilde:
+ nKey = KEY_TILDE;
+ *pcPrintable = '~';
+ break;
+ case XK_grave:
+ nKey = KEY_QUOTELEFT;
+ *pcPrintable = '`';
+ break;
+ case XK_bracketleft:
+ nKey = KEY_BRACKETLEFT;
+ *pcPrintable = '[';
+ break;
+ case XK_bracketright:
+ nKey = KEY_BRACKETRIGHT;
+ *pcPrintable = ']';
+ break;
+ case XK_semicolon:
+ nKey = KEY_SEMICOLON;
+ *pcPrintable = ';';
+ break;
+ case XK_quoteright:
+ nKey = KEY_QUOTERIGHT;
+ *pcPrintable = '\'';
+ break;
+ case XK_braceright:
+ nKey = KEY_RIGHTCURLYBRACKET;
+ *pcPrintable = '\'';
+ break;
+ case XK_numbersign:
+ nKey = KEY_NUMBERSIGN;
+ *pcPrintable = '#';
+ break;
+ case XK_colon:
+ nKey = KEY_COLON;
+ *pcPrintable = ':';
+ break;
+ case 0x1008ff27: // tdf#148986: XF86Forward
+ nKey = KEY_XF86FORWARD;
+ break;
+ case 0x1008ff26: // tdf#148986: XF86Back
+ nKey = KEY_XF86BACK;
+ break;
+ // - - - - - - - - - - - - - Apollo - - - - - - - - - - - - - 0x1000
+ case 0x1000FF02: // apXK_Copy
+ nKey = KEY_COPY;
+ break;
+ case 0x1000FF03: // apXK_Cut
+ nKey = KEY_CUT;
+ break;
+ case 0x1000FF04: // apXK_Paste
+ nKey = KEY_PASTE;
+ break;
+ case 0x1000FF14: // apXK_Repeat
+ nKey = KEY_REPEAT;
+ break;
+ // Exit, Save
+ // - - - - - - - - - - - - - - D E C - - - - - - - - - - - - - 0x1000
+ case 0x1000FF00:
+ nKey = KEY_DELETE;
+ break;
+ // - - - - - - - - - - - - - - H P - - - - - - - - - - - - - 0x1000
+ case 0x1000FF73: // hpXK_DeleteChar
+ nKey = KEY_DELETE;
+ break;
+ case 0x1000FF74: // hpXK_BackTab
+ case 0x1000FF75: // hpXK_KP_BackTab
+ nKey = KEY_TAB;
+ break;
+ // - - - - - - - - - - - - - - I B M - - - - - - - - - - - - -
+ // - - - - - - - - - - - - - - O S F - - - - - - - - - - - - - 0x1004
+ case 0x1004FF02: // osfXK_Copy
+ nKey = KEY_COPY;
+ break;
+ case 0x1004FF03: // osfXK_Cut
+ nKey = KEY_CUT;
+ break;
+ case 0x1004FF04: // osfXK_Paste
+ nKey = KEY_PASTE;
+ break;
+ case 0x1004FF07: // osfXK_BackTab
+ nKey = KEY_TAB;
+ break;
+ case 0x1004FF08: // osfXK_BackSpace
+ nKey = KEY_BACKSPACE;
+ break;
+ case 0x1004FF1B: // osfXK_Escape
+ nKey = KEY_ESCAPE;
+ break;
+ // Up, Down, Left, Right, PageUp, PageDown
+ // - - - - - - - - - - - - - - S C O - - - - - - - - - - - - -
+ // - - - - - - - - - - - - - - S G I - - - - - - - - - - - - - 0x1007
+ // - - - - - - - - - - - - - - S N I - - - - - - - - - - - - -
+ // - - - - - - - - - - - - - - S U N - - - - - - - - - - - - - 0x1005
+ case 0x1005FF10: // SunXK_F36
+ nKey = KEY_F11;
+ break;
+ case 0x1005FF11: // SunXK_F37
+ nKey = KEY_F12;
+ break;
+ case 0x1005FF70: // SunXK_Props
+ nKey = KEY_PROPERTIES;
+ break;
+ case 0x1005FF71: // SunXK_Front
+ nKey = KEY_FRONT;
+ break;
+ case 0x1005FF72: // SunXK_Copy
+ nKey = KEY_COPY;
+ break;
+ case 0x1005FF73: // SunXK_Open
+ nKey = KEY_OPEN;
+ break;
+ case 0x1005FF74: // SunXK_Paste
+ nKey = KEY_PASTE;
+ break;
+ case 0x1005FF75: // SunXK_Cut
+ nKey = KEY_CUT;
+ break;
+ }
+ return nKey;
+}
+
+KeySym SalDisplay::GetKeySym( XKeyEvent *pEvent,
+ char *pPrintable,
+ int *pLen,
+ KeySym *pUnmodifiedKeySym,
+ Status *pStatusReturn,
+ XIC aInputContext ) const
+{
+ KeySym nKeySym = 0;
+ memset( pPrintable, 0, *pLen );
+ *pStatusReturn = 0;
+
+ SalI18N_InputMethod* const pInputMethod =
+ pXLib_ ? pXLib_->GetInputMethod() : nullptr;
+
+ // first get the printable of the possibly modified KeySym
+ if ( (aInputContext == nullptr)
+ || (pEvent->type == KeyRelease)
+ || (pInputMethod != nullptr && pInputMethod->PosixLocale()) )
+ {
+ // XmbLookupString must not be called for KeyRelease events
+ // Cannot enter space in c locale problem #89616# #88978# btraq #4478197
+ *pLen = XLookupString( pEvent, pPrintable, 1, &nKeySym, nullptr );
+ }
+ else
+ {
+ *pLen = XmbLookupString( aInputContext,
+ pEvent, pPrintable, *pLen - 1, &nKeySym, pStatusReturn );
+
+ // Lookup the string again, now with appropriate size
+ if ( *pStatusReturn == XBufferOverflow )
+ {
+ pPrintable[ 0 ] = '\0';
+ return 0;
+ }
+
+ switch ( *pStatusReturn )
+ {
+ case XBufferOverflow:
+ /* unhandled error */
+ break;
+ case XLookupNone:
+ /* unhandled error */
+ break;
+ case XLookupKeySym:
+ /* this is a strange one: on exceed sometimes
+ * no printable is returned for the first char entered,
+ * just to retry lookup solves the problem. The problem
+ * is not yet fully understood, so restrict 2nd lookup
+ * to 7bit ascii chars */
+ if ( (XK_space <= nKeySym) && (XK_asciitilde >= nKeySym) )
+ {
+ *pLen = 1;
+ pPrintable[ 0 ] = static_cast<char>(nKeySym);
+ }
+ break;
+ case XLookupBoth:
+ case XLookupChars:
+
+ /* nothing to, char already in pPrintable */
+ break;
+ }
+ }
+
+ if( !bNumLockFromXS_
+ && (IsCursorKey(nKeySym)
+ || IsFunctionKey(nKeySym)
+ || IsKeypadKey(nKeySym)
+ || XK_Delete == nKeySym ) )
+ {
+ // For some X-servers special care is needed for Keypad keys.
+ // For example Solaris XServer:
+ // 2, 4, 6, 8 are classified as Cursorkeys (Up, Down, Left, Right)
+ // 1, 3, 5, 9 are classified as Functionkeys (F27,F29,F33,F35)
+ // 0 as Keypadkey, and the decimal point key not at all (KP_Insert)
+ KeySym nNewKeySym = XLookupKeysym( pEvent, nNumLockIndex_ );
+ if( nNewKeySym != NoSymbol )
+ nKeySym = nNewKeySym;
+ }
+
+ // Now get the unmodified KeySym for KeyCode retrieval
+ // try to strip off modifiers, e.g. Ctrl-$ becomes Ctrl-Shift-4
+ *pUnmodifiedKeySym = XkbKeycodeToKeysym( GetDisplay(), pEvent->keycode, 0, 0);
+
+ return nKeySym;
+}
+
+// Pointer
+static unsigned char nullmask_bits[] = { 0x00, 0x00, 0x00, 0x00 };
+static unsigned char nullcurs_bits[] = { 0x00, 0x00, 0x00, 0x00 };
+
+#define MAKE_BITMAP( name ) \
+ XCreateBitmapFromData( pDisp_, \
+ DefaultRootWindow( pDisp_ ), \
+ reinterpret_cast<const char*>(name##_bits), \
+ name##_width, \
+ name##_height )
+
+#define MAKE_CURSOR( name ) \
+ aCursBitmap = MAKE_BITMAP( name##curs ); \
+ aMaskBitmap = MAKE_BITMAP( name##mask ); \
+ nXHot = name##curs_x_hot; \
+ nYHot = name##curs_y_hot
+
+Cursor SalDisplay::GetPointer( PointerStyle ePointerStyle )
+{
+ Cursor &aCur = aPointerCache_[ePointerStyle];
+
+ if( aCur != None )
+ return aCur;
+
+ Pixmap aCursBitmap = None, aMaskBitmap = None;
+ unsigned int nXHot = 0, nYHot = 0;
+
+ switch( ePointerStyle )
+ {
+ case PointerStyle::Null:
+ MAKE_CURSOR( null );
+ break;
+ case PointerStyle::Arrow:
+ aCur = XCreateFontCursor( pDisp_, XC_left_ptr );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::Wait:
+ aCur = XCreateFontCursor( pDisp_, XC_watch );
+ break;
+ case PointerStyle::Text: // Mouse Pointer is a "I" Beam
+ aCur = XCreateFontCursor( pDisp_, XC_xterm );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::Help:
+ aCur = XCreateFontCursor( pDisp_, XC_question_arrow );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::Cross: // Mouse Pointer is a cross
+ aCur = XCreateFontCursor( pDisp_, XC_crosshair );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::NSize:
+ aCur = XCreateFontCursor( pDisp_, XC_sb_v_double_arrow );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::SSize:
+ aCur = XCreateFontCursor( pDisp_, XC_sb_v_double_arrow );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::WSize:
+ aCur = XCreateFontCursor( pDisp_, XC_sb_h_double_arrow );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::ESize:
+ aCur = XCreateFontCursor( pDisp_, XC_sb_h_double_arrow );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::WindowNSize:
+ aCur = XCreateFontCursor( pDisp_, XC_top_side );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::WindowSSize:
+ aCur = XCreateFontCursor( pDisp_, XC_bottom_side );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::WindowWSize:
+ aCur = XCreateFontCursor( pDisp_, XC_left_side );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::WindowESize:
+ aCur = XCreateFontCursor( pDisp_, XC_right_side );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::NWSize:
+ aCur = XCreateFontCursor( pDisp_, XC_top_left_corner );
+ break;
+ case PointerStyle::NESize:
+ aCur = XCreateFontCursor( pDisp_, XC_top_right_corner );
+ break;
+ case PointerStyle::SWSize:
+ aCur = XCreateFontCursor( pDisp_, XC_bottom_left_corner );
+ break;
+ case PointerStyle::SESize:
+ aCur = XCreateFontCursor( pDisp_, XC_bottom_right_corner );
+ break;
+ case PointerStyle::WindowNWSize:
+ aCur = XCreateFontCursor( pDisp_, XC_top_left_corner );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::WindowNESize:
+ aCur = XCreateFontCursor( pDisp_, XC_top_right_corner );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::WindowSWSize:
+ aCur = XCreateFontCursor( pDisp_, XC_bottom_left_corner );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::WindowSESize:
+ aCur = XCreateFontCursor( pDisp_, XC_bottom_right_corner );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::HSplit:
+ aCur = XCreateFontCursor( pDisp_, XC_sb_h_double_arrow );
+ break;
+ case PointerStyle::VSplit:
+ aCur = XCreateFontCursor( pDisp_, XC_sb_v_double_arrow );
+ break;
+ case PointerStyle::HSizeBar:
+ aCur = XCreateFontCursor( pDisp_, XC_sb_h_double_arrow ); // ???
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::VSizeBar:
+ aCur = XCreateFontCursor( pDisp_, XC_sb_v_double_arrow ); // ???
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::RefHand:
+ aCur = XCreateFontCursor( pDisp_, XC_hand1 );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::Hand:
+ aCur = XCreateFontCursor( pDisp_, XC_hand2 );
+ break;
+ case PointerStyle::Magnify:
+ MAKE_CURSOR( magnify_ );
+ break;
+ case PointerStyle::Fill:
+ MAKE_CURSOR( fill_ );
+ break;
+ case PointerStyle::Move:
+ aCur = XCreateFontCursor( pDisp_, XC_fleur );
+ break;
+ case PointerStyle::MoveData:
+ MAKE_CURSOR( movedata_ );
+ break;
+ case PointerStyle::CopyData:
+ MAKE_CURSOR( copydata_ );
+ break;
+ case PointerStyle::MoveFile:
+ MAKE_CURSOR( movefile_ );
+ break;
+ case PointerStyle::CopyFile:
+ MAKE_CURSOR( copyfile_ );
+ break;
+ case PointerStyle::MoveFiles:
+ MAKE_CURSOR( movefiles_ );
+ break;
+ case PointerStyle::CopyFiles:
+ MAKE_CURSOR( copyfiles_ );
+ break;
+ case PointerStyle::NotAllowed:
+ MAKE_CURSOR( nodrop_ );
+ break;
+ case PointerStyle::Rotate:
+ MAKE_CURSOR( rotate_ );
+ break;
+ case PointerStyle::HShear:
+ MAKE_CURSOR( hshear_ );
+ break;
+ case PointerStyle::VShear:
+ MAKE_CURSOR( vshear_ );
+ break;
+ case PointerStyle::DrawLine:
+ MAKE_CURSOR( drawline_ );
+ break;
+ case PointerStyle::DrawRect:
+ MAKE_CURSOR( drawrect_ );
+ break;
+ case PointerStyle::DrawPolygon:
+ MAKE_CURSOR( drawpolygon_ );
+ break;
+ case PointerStyle::DrawBezier:
+ MAKE_CURSOR( drawbezier_ );
+ break;
+ case PointerStyle::DrawArc:
+ MAKE_CURSOR( drawarc_ );
+ break;
+ case PointerStyle::DrawPie:
+ MAKE_CURSOR( drawpie_ );
+ break;
+ case PointerStyle::DrawCircleCut:
+ MAKE_CURSOR( drawcirclecut_ );
+ break;
+ case PointerStyle::DrawEllipse:
+ MAKE_CURSOR( drawellipse_ );
+ break;
+ case PointerStyle::DrawConnect:
+ MAKE_CURSOR( drawconnect_ );
+ break;
+ case PointerStyle::DrawText:
+ MAKE_CURSOR( drawtext_ );
+ break;
+ case PointerStyle::Mirror:
+ MAKE_CURSOR( mirror_ );
+ break;
+ case PointerStyle::Crook:
+ MAKE_CURSOR( crook_ );
+ break;
+ case PointerStyle::Crop:
+ MAKE_CURSOR( crop_ );
+ break;
+ case PointerStyle::MovePoint:
+ MAKE_CURSOR( movepoint_ );
+ break;
+ case PointerStyle::MoveBezierWeight:
+ MAKE_CURSOR( movebezierweight_ );
+ break;
+ case PointerStyle::DrawFreehand:
+ MAKE_CURSOR( drawfreehand_ );
+ break;
+ case PointerStyle::DrawCaption:
+ MAKE_CURSOR( drawcaption_ );
+ break;
+ case PointerStyle::Pen: // Mouse Pointer is a pencil
+ aCur = XCreateFontCursor( pDisp_, XC_pencil );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::LinkData:
+ MAKE_CURSOR( linkdata_ );
+ break;
+ case PointerStyle::MoveDataLink:
+ MAKE_CURSOR( movedlnk_ );
+ break;
+ case PointerStyle::CopyDataLink:
+ MAKE_CURSOR( copydlnk_ );
+ break;
+ case PointerStyle::LinkFile:
+ MAKE_CURSOR( linkfile_ );
+ break;
+ case PointerStyle::MoveFileLink:
+ MAKE_CURSOR( moveflnk_ );
+ break;
+ case PointerStyle::CopyFileLink:
+ MAKE_CURSOR( copyflnk_ );
+ break;
+ case PointerStyle::Chart:
+ MAKE_CURSOR( chart_ );
+ break;
+ case PointerStyle::Detective:
+ MAKE_CURSOR( detective_ );
+ break;
+ case PointerStyle::PivotCol:
+ MAKE_CURSOR( pivotcol_ );
+ break;
+ case PointerStyle::PivotRow:
+ MAKE_CURSOR( pivotrow_ );
+ break;
+ case PointerStyle::PivotField:
+ MAKE_CURSOR( pivotfld_ );
+ break;
+ case PointerStyle::PivotDelete:
+ MAKE_CURSOR( pivotdel_ );
+ break;
+ case PointerStyle::Chain:
+ MAKE_CURSOR( chain_ );
+ break;
+ case PointerStyle::ChainNotAllowed:
+ MAKE_CURSOR( chainnot_ );
+ break;
+ case PointerStyle::AutoScrollN:
+ MAKE_CURSOR(asn_ );
+ break;
+ case PointerStyle::AutoScrollS:
+ MAKE_CURSOR( ass_ );
+ break;
+ case PointerStyle::AutoScrollW:
+ MAKE_CURSOR( asw_ );
+ break;
+ case PointerStyle::AutoScrollE:
+ MAKE_CURSOR( ase_ );
+ break;
+ case PointerStyle::AutoScrollNW:
+ MAKE_CURSOR( asnw_ );
+ break;
+ case PointerStyle::AutoScrollNE:
+ MAKE_CURSOR( asne_ );
+ break;
+ case PointerStyle::AutoScrollSW:
+ MAKE_CURSOR( assw_ );
+ break;
+ case PointerStyle::AutoScrollSE:
+ MAKE_CURSOR( asse_ );
+ break;
+ case PointerStyle::AutoScrollNS:
+ MAKE_CURSOR( asns_ );
+ break;
+ case PointerStyle::AutoScrollWE:
+ MAKE_CURSOR( aswe_ );
+ break;
+ case PointerStyle::AutoScrollNSWE:
+ MAKE_CURSOR( asnswe_ );
+ break;
+ case PointerStyle::TextVertical:
+ MAKE_CURSOR( vertcurs_ );
+ break;
+
+ // #i32329# Enhanced table selection
+ case PointerStyle::TabSelectS:
+ MAKE_CURSOR( tblsels_ );
+ break;
+ case PointerStyle::TabSelectE:
+ MAKE_CURSOR( tblsele_ );
+ break;
+ case PointerStyle::TabSelectSE:
+ MAKE_CURSOR( tblselse_ );
+ break;
+ case PointerStyle::TabSelectW:
+ MAKE_CURSOR( tblselw_ );
+ break;
+ case PointerStyle::TabSelectSW:
+ MAKE_CURSOR( tblselsw_ );
+ break;
+
+ case PointerStyle::HideWhitespace:
+ MAKE_CURSOR( hidewhitespace_ );
+ break;
+ case PointerStyle::ShowWhitespace:
+ MAKE_CURSOR( showwhitespace_ );
+ break;
+ case PointerStyle::FatCross:
+ MAKE_CURSOR( fatcross_ );
+ break;
+
+ default:
+ OSL_FAIL("pointer not implemented");
+ aCur = XCreateFontCursor( pDisp_, XC_arrow );
+ break;
+ }
+
+ if( None == aCur )
+ {
+ XColor aBlack, aWhite, aDummy;
+ Colormap hColormap = GetColormap(m_nXDefaultScreen).GetXColormap();
+
+ XAllocNamedColor( pDisp_, hColormap, "black", &aBlack, &aDummy );
+ XAllocNamedColor( pDisp_, hColormap, "white", &aWhite, &aDummy );
+
+ aCur = XCreatePixmapCursor( pDisp_,
+ aCursBitmap, aMaskBitmap,
+ &aBlack, &aWhite,
+ nXHot, nYHot );
+
+ XFreePixmap( pDisp_, aCursBitmap );
+ XFreePixmap( pDisp_, aMaskBitmap );
+ }
+
+ return aCur;
+}
+
+int SalDisplay::CaptureMouse( SalFrame *pCapture )
+{
+ static const char* pEnv = getenv( "SAL_NO_MOUSEGRABS" );
+
+ if( !pCapture )
+ {
+ m_pCapture = nullptr;
+ if( !pEnv || !*pEnv )
+ XUngrabPointer( GetDisplay(), CurrentTime );
+ XFlush( GetDisplay() );
+ return 0;
+ }
+
+ m_pCapture = nullptr;
+
+ // FIXME: get rid of X11SalFrame
+ const SystemEnvData* pEnvData = pCapture->GetSystemData();
+ if( !pEnv || !*pEnv )
+ {
+ int ret = XGrabPointer( GetDisplay(),
+ static_cast<::Window>(pEnvData->GetWindowHandle(pCapture)),
+ False,
+ PointerMotionMask| ButtonPressMask|ButtonReleaseMask,
+ GrabModeAsync,
+ GrabModeAsync,
+ None,
+ static_cast<X11SalFrame*>(pCapture)->GetCursor(),
+ CurrentTime );
+
+ if( ret != GrabSuccess )
+ {
+ SAL_WARN("vcl", "SalDisplay::CaptureMouse could not grab pointer: " << ret);
+ return -1;
+ }
+ }
+
+ m_pCapture = pCapture;
+ return 1;
+}
+
+// Events
+
+bool SalX11Display::IsEvent()
+{
+ if( HasUserEvents() || XEventsQueued( pDisp_, QueuedAlready ) )
+ return true;
+
+ XFlush( pDisp_ );
+ return false;
+}
+
+void SalX11Display::Yield()
+{
+ if( DispatchInternalEvent() )
+ return;
+
+ XEvent aEvent;
+ DBG_ASSERT(GetSalInstance()->GetYieldMutex()->IsCurrentThread(),
+ "will crash soon since solar mutex not locked in SalDisplay::Yield" );
+
+ XNextEvent( pDisp_, &aEvent );
+
+ // coverity[overrun-buffer-val : FALSE] - coverity has problems with uno::Sequence
+ Dispatch( &aEvent );
+
+#ifdef DBG_UTIL
+ if( GetX11SalData()->HasXErrorOccurred() )
+ {
+ XFlush( pDisp_ );
+ DbgPrintDisplayEvent("SalDisplay::Yield (WasXError)", &aEvent);
+ }
+#endif
+ GetX11SalData()->ResetXErrorOccurred();
+}
+
+void SalX11Display::Dispatch( XEvent *pEvent )
+{
+ SalI18N_InputMethod* const pInputMethod =
+ pXLib_ ? pXLib_->GetInputMethod() : nullptr;
+
+ if( pInputMethod )
+ {
+ ::Window aFrameWindow = None;
+ if( pEvent->type == KeyPress || pEvent->type == KeyRelease )
+ {
+ const ::Window aWindow = pEvent->xkey.window;
+ for( auto pSalFrame : m_aFrames )
+ {
+ const X11SalFrame* pFrame = static_cast< const X11SalFrame* >( pSalFrame );
+ const ::Window aCurFrameWindow = pFrame->GetWindow();
+ if( aCurFrameWindow == aWindow || pFrame->GetShellWindow() == aWindow )
+ {
+ aFrameWindow = aCurFrameWindow;
+ break;
+ }
+ }
+ }
+ if( pInputMethod->FilterEvent( pEvent, aFrameWindow ) )
+ return;
+ }
+
+ SalInstance* pInstance = GetSalInstance();
+ pInstance->CallEventCallback( pEvent, sizeof( XEvent ) );
+
+ switch( pEvent->type )
+ {
+ case MotionNotify:
+ while( XCheckWindowEvent( pEvent->xany.display,
+ pEvent->xany.window,
+ ButtonMotionMask,
+ pEvent ) )
+ ;
+ m_nLastUserEventTime = pEvent->xmotion.time;
+ break;
+ case PropertyNotify:
+ if( pEvent->xproperty.atom == getWMAdaptor()->getAtom( WMAdaptor::VCL_SYSTEM_SETTINGS ) )
+ {
+ for(const ScreenData & rScreen : m_aScreens)
+ {
+ if( pEvent->xproperty.window == rScreen.m_aRefWindow )
+ {
+ for (auto pSalFrame : m_aFrames )
+ pSalFrame->CallCallback( SalEvent::SettingsChanged, nullptr );
+ return;
+ }
+ }
+ }
+ break;
+ case MappingNotify:
+ if( MappingModifier == pEvent->xmapping.request )
+ {
+ XRefreshKeyboardMapping( &pEvent->xmapping );
+ ModifierMapping();
+ }
+ break;
+ case ButtonPress:
+ case ButtonRelease:
+ m_nLastUserEventTime = pEvent->xbutton.time;
+ break;
+ case KeyPress:
+ case KeyRelease:
+ m_nLastUserEventTime = pEvent->xkey.time;
+ break;
+ default:
+
+ if ( GetKbdExtension()->UseExtension()
+ && GetKbdExtension()->GetEventBase() == pEvent->type )
+ {
+ GetKbdExtension()->Dispatch( pEvent );
+ return;
+ }
+ break;
+ }
+
+ for (auto pSalFrame : m_aFrames )
+ {
+ X11SalFrame* pFrame = static_cast<X11SalFrame*>( pSalFrame );
+
+ ::Window aDispatchWindow = pEvent->xany.window;
+ if( pFrame->GetWindow() == aDispatchWindow
+ || pFrame->GetShellWindow() == aDispatchWindow
+ || pFrame->GetForeignParent() == aDispatchWindow
+ )
+ {
+ pFrame->Dispatch( pEvent );
+ return;
+ }
+ if( pEvent->type == ConfigureNotify && pEvent->xconfigure.window == pFrame->GetStackingWindow() )
+ {
+ pFrame->Dispatch( pEvent );
+ return;
+ }
+ }
+
+ // dispatch to salobjects
+ X11SalObject::Dispatch( pEvent );
+
+ // is this perhaps a root window that changed size ?
+ processRandREvent( pEvent );
+}
+
+#ifdef DBG_UTIL
+void SalDisplay::DbgPrintDisplayEvent(const char *pComment, const XEvent *pEvent) const
+{
+ static const char* const EventNames[] =
+ {
+ nullptr,
+ nullptr,
+ "KeyPress",
+ "KeyRelease",
+ "ButtonPress",
+ "ButtonRelease",
+ "MotionNotify",
+ "EnterNotify",
+ "LeaveNotify",
+ "FocusIn",
+ "FocusOut",
+ "KeymapNotify",
+ "Expose",
+ "GraphicsExpose",
+ "NoExpose",
+ "VisibilityNotify",
+ "CreateNotify",
+ "DestroyNotify",
+ "UnmapNotify",
+ "MapNotify",
+ "MapRequest",
+ "ReparentNotify",
+ "ConfigureNotify",
+ "ConfigureRequest",
+ "GravityNotify",
+ "ResizeRequest",
+ "CirculateNotify",
+ "CirculateRequest",
+ "PropertyNotify",
+ "SelectionClear",
+ "SelectionRequest",
+ "SelectionNotify",
+ "ColormapNotify",
+ "ClientMessage",
+ "MappingNotify"
+ };
+
+ if( pEvent->type <= MappingNotify )
+ {
+ SAL_INFO("vcl.app", "[" << pComment << "] "
+ << EventNames[pEvent->type]
+ << " s=" << pEvent->xany.send_event
+ << " w=" << pEvent->xany.window);
+
+ switch( pEvent->type )
+ {
+ case KeyPress:
+ case KeyRelease:
+ SAL_INFO("vcl.app", "\t\ts=" << pEvent->xkey.state
+ << " c=" << pEvent->xkey.keycode);
+ break;
+
+ case ButtonPress:
+ case ButtonRelease:
+ SAL_INFO("vcl.app", "\t\ts=" << pEvent->xbutton.state
+ << " b=" << pEvent->xbutton.button
+ << " x=" << pEvent->xbutton.x
+ << " y=" << pEvent->xbutton.y
+ << " rx=" << pEvent->xbutton.x_root
+ << " ry=" << pEvent->xbutton.y_root);
+ break;
+
+ case MotionNotify:
+ SAL_INFO("vcl.app", "\t\ts=" << pEvent->xmotion.state
+ << " x=" << pEvent->xmotion.x
+ << " y=" << pEvent->xmotion.y);
+ break;
+
+ case EnterNotify:
+ case LeaveNotify:
+ SAL_INFO("vcl.app", "\t\tm=" << pEvent->xcrossing.mode
+ << " f=" << pEvent->xcrossing.focus
+ << " x=" << pEvent->xcrossing.x
+ << " y=" << pEvent->xcrossing.y);
+ break;
+
+ case FocusIn:
+ case FocusOut:
+ SAL_INFO("vcl.app", "\t\tm=" << pEvent->xfocus.mode
+ << " d=" << pEvent->xfocus.detail);
+ break;
+
+ case Expose:
+ case GraphicsExpose:
+ SAL_INFO("vcl.app", "\t\tc=" << pEvent->xexpose.count
+ << " " << pEvent->xexpose.width
+ << "*" << pEvent->xexpose.height
+ << " " << pEvent->xexpose.x
+ << "+" << pEvent->xexpose.y );
+ break;
+
+ case VisibilityNotify:
+ SAL_INFO("vcl.app", "\t\ts=" << pEvent->xvisibility.state);
+ break;
+
+ case CreateNotify:
+ case DestroyNotify:
+ break;
+
+ case MapNotify:
+ case UnmapNotify:
+ break;
+
+ case ReparentNotify:
+ SAL_INFO("vcl.app", "\t\tp=" << sal::static_int_cast< int >(
+ pEvent->xreparent.parent)
+ << " x=" << pEvent->xreparent.x
+ << " y=" << pEvent->xreparent.y );
+ break;
+
+ case ConfigureNotify:
+ SAL_INFO("vcl.app", "\t\tb=" << pEvent->xconfigure.border_width
+ << " " << pEvent->xconfigure.width
+ << "*" << pEvent->xconfigure.height
+ << " " << pEvent->xconfigure.x
+ << "+" << pEvent->xconfigure.y);
+ break;
+
+ case PropertyNotify:
+ SAL_INFO("vcl.app", "\t\ta=" << GetAtomName(
+ pDisp_, pEvent->xproperty.atom)
+ << std::showbase << std::hex << std::uppercase
+ << " (" << sal::static_int_cast< unsigned int >(
+ pEvent->xproperty.atom) << ").");
+ break;
+
+ case ColormapNotify:
+ SAL_INFO("vcl.app", "\t\tc=" << pEvent->xcolormap.colormap
+ << " n=" << pEvent->xcolormap.c_new
+ << " s=" << pEvent->xcolormap.state);
+ break;
+
+ case ClientMessage:
+ SAL_INFO("vcl.app", "\t\ta=" << GetAtomName(
+ pDisp_, pEvent->xclient.message_type)
+ << std::showbase << std::hex << std::uppercase
+ << " (" << sal::static_int_cast< unsigned int >(
+ pEvent->xclient.message_type) << ")"
+ << std::dec
+ << " f=" << pEvent->xclient.format
+ << std::hex
+ << " [" << pEvent->xclient.data.l[0]
+ << "," << pEvent->xclient.data.l[1]
+ << "," << pEvent->xclient.data.l[2]
+ << "," << pEvent->xclient.data.l[3]
+ << "," << pEvent->xclient.data.l[4]
+ << "]");
+ break;
+
+ case MappingNotify:
+ SAL_INFO("vcl.app", "\t\tr="
+ << (MappingModifier == pEvent->xmapping.request ?
+ "MappingModifier" :
+ (MappingKeyboard == pEvent->xmapping.request ?
+ "MappingKeyboard" : "MappingPointer"))
+ << "d");
+
+ break;
+ }
+ }
+ else
+ SAL_INFO("vcl.app", "[" << pComment << "] "
+ << pEvent->type
+ << " s=" << pEvent->xany.send_event
+ << " w=" << pEvent->xany.window);
+}
+
+void SalDisplay::PrintInfo() const
+{
+ if( IsDisplay() )
+ {
+ SAL_INFO( "vcl", "Environment" );
+ SAL_INFO( "vcl", "\t$DISPLAY \t\"" << GetEnv( "DISPLAY" ) << "\"");
+ SAL_INFO( "vcl", "\t$SAL_VISUAL \t\"" << GetEnv( "SAL_VISUAL" ) << "\"");
+ SAL_INFO( "vcl", "\t$SAL_IGNOREXERRORS\t\"" << GetEnv( "SAL_IGNOREXERRORS" ) << "\"");
+ SAL_INFO( "vcl", "\t$SAL_PROPERTIES \t\"" << GetEnv( "SAL_PROPERTIES" ) << "\"");
+ SAL_INFO( "vcl", "\t$SAL_SYNCHRONIZE \t\"" << GetEnv( "SAL_SYNCHRONIZE" ) << "\"");
+
+ char sHostname[ 120 ];
+ gethostname (sHostname, 120 );
+ SAL_INFO( "vcl", "Client" );
+ SAL_INFO( "vcl", "\tHost \t\"" << sHostname << "\"");
+
+ SAL_INFO( "vcl", "Display" );
+ SAL_INFO( "vcl", "\tHost \t\"" << DisplayString(pDisp_) << "\"");
+ SAL_INFO( "vcl", "\tVendor (Release) \t\"" << ServerVendor(pDisp_) << " (" << VendorRelease(pDisp_) << ")\"");
+ SAL_INFO( "vcl", "\tProtocol \t" << ProtocolVersion(pDisp_) << "." << ProtocolRevision(pDisp_) );
+ SAL_INFO( "vcl", "\tScreen (count,def)\t" << m_nXDefaultScreen.getXScreen() << " (" << ScreenCount(pDisp_) << "," << DefaultScreen(pDisp_) << ")");
+ SAL_INFO( "vcl", "\tshift ctrl alt \t" << KeyStr( nShiftKeySym_ ) << " (0x" << std::hex << sal::static_int_cast< unsigned int >(nShiftKeySym_) << ") "
+ << KeyStr( nCtrlKeySym_ ) << " (0x" << sal::static_int_cast< unsigned int >(nCtrlKeySym_) << ") "
+ << KeyStr( nMod1KeySym_ ) << " (0x" << sal::static_int_cast< unsigned int >(nMod1KeySym_) << ")");
+ if( XExtendedMaxRequestSize(pDisp_) != 0 )
+ SAL_INFO( "vcl", "\tXMaxRequestSize \t" << XMaxRequestSize(pDisp_) * 4 << " " << XExtendedMaxRequestSize(pDisp_) * 4 << " [bytes]");
+ SAL_INFO( "vcl", "\tWMName \t" << getWMAdaptor()->getWindowManagerName() );
+ }
+ SAL_INFO( "vcl", "Screen" );
+ SAL_INFO( "vcl", "\tResolution/Size \t" << aResolution_.A() << "*" << aResolution_.B()
+ << " " << m_aScreens[m_nXDefaultScreen.getXScreen()].m_aSize.Width() << "*" << m_aScreens[m_nXDefaultScreen.getXScreen()].m_aSize.Height()
+ << " " << (std::hypot( DisplayWidthMM ( pDisp_, m_nXDefaultScreen.getXScreen() ),
+ DisplayHeightMM( pDisp_, m_nXDefaultScreen.getXScreen() ) ) / 25.4 ) << "\"" );
+ SAL_INFO( "vcl", "\tBlack&White \t" << GetColormap(m_nXDefaultScreen).GetBlackPixel() << " "
+ << GetColormap(m_nXDefaultScreen).GetWhitePixel() );
+ SAL_INFO( "vcl", "\tRGB \t0x" << std::hex << GetVisual(m_nXDefaultScreen).red_mask
+ << " 0x" << GetVisual(m_nXDefaultScreen).green_mask
+ << " 0x" << GetVisual(m_nXDefaultScreen).blue_mask);
+}
+#endif
+
+void SalDisplay::addXineramaScreenUnique( int i, tools::Long i_nX, tools::Long i_nY, tools::Long i_nWidth, tools::Long i_nHeight )
+{
+ // see if any frame buffers are at the same coordinates
+ // this can happen with weird configuration e.g. on
+ // XFree86 and Clone displays
+ const size_t nScreens = m_aXineramaScreens.size();
+ for( size_t n = 0; n < nScreens; n++ )
+ {
+ if( m_aXineramaScreens[n].Left() == i_nX &&
+ m_aXineramaScreens[n].Top() == i_nY )
+ {
+ if( m_aXineramaScreens[n].GetWidth() < i_nWidth ||
+ m_aXineramaScreens[n].GetHeight() < i_nHeight )
+ {
+ m_aXineramaScreenIndexMap[i] = n;
+ m_aXineramaScreens[n].SetSize( AbsoluteScreenPixelSize( i_nWidth, i_nHeight ) );
+ }
+ return;
+ }
+ }
+ m_aXineramaScreenIndexMap[i] = m_aXineramaScreens.size();
+ m_aXineramaScreens.emplace_back( AbsoluteScreenPixelPoint( i_nX, i_nY ), AbsoluteScreenPixelSize( i_nWidth, i_nHeight ) );
+}
+
+void SalDisplay::InitXinerama()
+{
+ if( m_aScreens.size() > 1 )
+ {
+ m_bXinerama = false;
+ return; // multiple screens mean no xinerama
+ }
+ if( !XineramaIsActive( pDisp_ ) )
+ return;
+
+ int nFramebuffers = 1;
+ XineramaScreenInfo* pScreens = XineramaQueryScreens( pDisp_, &nFramebuffers );
+ if( !pScreens )
+ return;
+
+ if( nFramebuffers > 1 )
+ {
+ m_aXineramaScreens = std::vector<AbsoluteScreenPixelRectangle>();
+ m_aXineramaScreenIndexMap = std::vector<int>(nFramebuffers);
+ for( int i = 0; i < nFramebuffers; i++ )
+ {
+ addXineramaScreenUnique( i, pScreens[i].x_org,
+ pScreens[i].y_org,
+ pScreens[i].width,
+ pScreens[i].height );
+ }
+ m_bXinerama = m_aXineramaScreens.size() > 1;
+ }
+ XFree( pScreens );
+#if OSL_DEBUG_LEVEL > 1
+ if( m_bXinerama )
+ {
+ for (auto const& screen : m_aXineramaScreens)
+ SAL_INFO("vcl.app", "Xinerama screen: "
+ << screen.GetWidth()
+ << "x" << screen.GetHeight()
+ << "+" << screen.Left()
+ << "+" << screen.Top());
+ }
+#endif
+}
+
+extern "C"
+{
+ static Bool timestamp_predicate( Display*, XEvent* i_pEvent, XPointer i_pArg )
+ {
+ SalDisplay* pSalDisplay = reinterpret_cast<SalDisplay*>(i_pArg);
+ if( i_pEvent->type == PropertyNotify &&
+ i_pEvent->xproperty.window == pSalDisplay->GetDrawable( pSalDisplay->GetDefaultXScreen() ) &&
+ i_pEvent->xproperty.atom == pSalDisplay->getWMAdaptor()->getAtom( WMAdaptor::SAL_GETTIMEEVENT )
+ )
+ return True;
+
+ return False;
+ }
+}
+
+Time SalDisplay::GetEventTimeImpl( bool i_bAlwaysReget ) const
+{
+ if( m_nLastUserEventTime == CurrentTime || i_bAlwaysReget )
+ {
+ // get current server time
+ unsigned char c = 0;
+ XEvent aEvent;
+ Atom nAtom = getWMAdaptor()->getAtom( WMAdaptor::SAL_GETTIMEEVENT );
+ XChangeProperty( GetDisplay(), GetDrawable( GetDefaultXScreen() ),
+ nAtom, nAtom, 8, PropModeReplace, &c, 1 );
+ XIfEvent( GetDisplay(), &aEvent, timestamp_predicate, reinterpret_cast<XPointer>(const_cast<SalDisplay *>(this)));
+ m_nLastUserEventTime = aEvent.xproperty.time;
+ }
+ return m_nLastUserEventTime;
+}
+
+SalVisual::SalVisual()
+{
+ visual = nullptr;
+}
+
+SalVisual::SalVisual( const XVisualInfo* pXVI )
+{
+ *static_cast<XVisualInfo*>(this) = *pXVI;
+}
+
+// Converts the order of bytes of a Pixel into bytes of a Color
+// This is not reversible for the 6 XXXA
+
+// Color is RGB (ABGR) a=0xFF000000, r=0xFF0000, g=0xFF00, b=0xFF
+
+SalColormap::SalColormap( const SalDisplay *pDisplay, Colormap hColormap,
+ SalX11Screen nXScreen )
+ : m_pDisplay( pDisplay ),
+ m_hColormap( hColormap )
+{
+ m_aVisual = m_pDisplay->GetVisual( nXScreen );
+
+ XColor aColor;
+
+ GetXPixel( aColor, 0x00, 0x00, 0x00 );
+ m_nBlackPixel = aColor.pixel;
+
+ GetXPixel( aColor, 0xFF, 0xFF, 0xFF );
+ m_nWhitePixel = aColor.pixel;
+
+ m_nUsed = 1 << m_aVisual.GetDepth();
+
+ if( m_aVisual.GetClass() != PseudoColor )
+ return;
+
+ int r, g, b;
+
+ // black, white, gray, ~gray = 4
+ GetXPixels( aColor, 0xC0, 0xC0, 0xC0 );
+
+ // light colors: 3 * 2 = 6
+
+ GetXPixels( aColor, 0x00, 0x00, 0xFF );
+ GetXPixels( aColor, 0x00, 0xFF, 0x00 );
+ GetXPixels( aColor, 0x00, 0xFF, 0xFF );
+
+ // standard colors: 7 * 2 = 14
+ GetXPixels( aColor, 0x00, 0x00, 0x80 );
+ GetXPixels( aColor, 0x00, 0x80, 0x00 );
+ GetXPixels( aColor, 0x00, 0x80, 0x80 );
+ GetXPixels( aColor, 0x80, 0x00, 0x00 );
+ GetXPixels( aColor, 0x80, 0x00, 0x80 );
+ GetXPixels( aColor, 0x80, 0x80, 0x00 );
+ GetXPixels( aColor, 0x80, 0x80, 0x80 );
+ GetXPixels( aColor, 0x00, 0xB8, 0xFF ); // Blue 7
+
+ // cube: 6*6*6 - 8 = 208
+ for( r = 0; r < 0x100; r += 0x33 ) // 0x33, 0x66, 0x99, 0xCC, 0xFF
+ for( g = 0; g < 0x100; g += 0x33 )
+ for( b = 0; b < 0x100; b += 0x33 )
+ GetXPixels( aColor, r, g, b );
+
+ // gray: 16 - 6 = 10
+ for( g = 0x11; g < 0xFF; g += 0x11 )
+ GetXPixels( aColor, g, g, g );
+
+ // green: 16 - 6 = 10
+ for( g = 0x11; g < 0xFF; g += 0x11 )
+ GetXPixels( aColor, 0, g, 0 );
+
+ // red: 16 - 6 = 10
+ for( r = 0x11; r < 0xFF; r += 0x11 )
+ GetXPixels( aColor, r, 0, 0 );
+
+ // blue: 16 - 6 = 10
+ for( b = 0x11; b < 0xFF; b += 0x11 )
+ GetXPixels( aColor, 0, 0, b );
+
+}
+
+// MonoChrome
+SalColormap::SalColormap()
+ : m_pDisplay( vcl_sal::getSalDisplay(GetGenericUnixSalData()) ),
+ m_hColormap( None ),
+ m_nWhitePixel( 1 ),
+ m_nBlackPixel( 0 ),
+ m_nUsed( 2 )
+{
+ m_aPalette = std::vector<Color>(m_nUsed);
+
+ m_aPalette[m_nBlackPixel] = COL_BLACK;
+ m_aPalette[m_nWhitePixel] = COL_WHITE;
+}
+
+// TrueColor
+SalColormap::SalColormap( sal_uInt16 nDepth )
+ : m_pDisplay( vcl_sal::getSalDisplay(GetGenericUnixSalData()) ),
+ m_hColormap( None ),
+ m_nWhitePixel( (1 << nDepth) - 1 ),
+ m_nBlackPixel( 0x00000000 ),
+ m_nUsed( 1 << nDepth )
+{
+ SalX11Screen nXScreen( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDefaultXScreen() );
+ const SalVisual *pVisual = &m_pDisplay->GetVisual( nXScreen );
+
+ if( pVisual->GetClass() == TrueColor && pVisual->GetDepth() == nDepth )
+ m_aVisual = *pVisual;
+ else
+ {
+ XVisualInfo aVI;
+
+ if( !XMatchVisualInfo( m_pDisplay->GetDisplay(),
+ m_pDisplay->GetDefaultXScreen().getXScreen(),
+ nDepth,
+ TrueColor,
+ &aVI ) )
+ {
+ aVI.visual = new Visual;
+ aVI.visualid = VisualID(-1);
+ aVI.screen = -1;
+ aVI.depth = nDepth;
+ aVI.c_class = TrueColor;
+ if( 24 == nDepth ) // 888
+ {
+ aVI.red_mask = 0xFF0000;
+ aVI.green_mask = 0x00FF00;
+ aVI.blue_mask = 0x0000FF;
+ }
+ else if( 8 == nDepth ) // 332
+ {
+ aVI.red_mask = 0x0000E0;
+ aVI.green_mask = 0x00001C;
+ aVI.blue_mask = 0x000003;
+ }
+ else
+ {
+ aVI.red_mask = 0x000000;
+ aVI.green_mask = 0x000000;
+ aVI.blue_mask = 0x000000;
+ }
+ aVI.colormap_size = 0;
+ aVI.bits_per_rgb = 8;
+
+ aVI.visual->ext_data = nullptr;
+ aVI.visual->visualid = aVI.visualid;
+ aVI.visual->c_class = aVI.c_class;
+ aVI.visual->red_mask = aVI.red_mask;
+ aVI.visual->green_mask = aVI.green_mask;
+ aVI.visual->blue_mask = aVI.blue_mask;
+ aVI.visual->bits_per_rgb = aVI.bits_per_rgb;
+ aVI.visual->map_entries = aVI.colormap_size;
+
+ m_aVisual = SalVisual( &aVI );
+ m_aVisualOwnership.owner = true;
+ }
+ else
+ m_aVisual = SalVisual( &aVI );
+ }
+}
+
+SalColormap::~SalColormap()
+{
+ if (m_aVisualOwnership.owner)
+ {
+ delete m_aVisual.visual;
+ }
+}
+
+inline bool SalColormap::GetXPixel( XColor &rColor,
+ int r,
+ int g,
+ int b ) const
+{
+ rColor.red = r * 257;
+ rColor.green = g * 257;
+ rColor.blue = b * 257;
+ return XAllocColor( GetXDisplay(), m_hColormap, &rColor );
+}
+
+bool SalColormap::GetXPixels( XColor &rColor,
+ int r,
+ int g,
+ int b ) const
+{
+ if( !GetXPixel( rColor, r, g, b ) )
+ return false;
+ if( rColor.pixel & 1 )
+ return true;
+ return GetXPixel( rColor, r^0xFF, g^0xFF, b^0xFF );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/salinst.cxx b/vcl/unx/generic/app/salinst.cxx
new file mode 100644
index 0000000000..a77aca2648
--- /dev/null
+++ b/vcl/unx/generic/app/salinst.cxx
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <config_features.h>
+#include <vcl/skia/SkiaHelper.hxx>
+#include <config_skia.h>
+#if HAVE_FEATURE_SKIA
+#include <skia/x11/gdiimpl.hxx>
+#include <skia/salbmp.hxx>
+#endif
+
+#include <headless/svpbmp.hxx>
+#include <unx/saldata.hxx>
+#include <unx/saldisp.hxx>
+#include <unx/salinst.h>
+#include <unx/geninst.h>
+#include <unx/genpspgraphics.h>
+#include <unx/salframe.h>
+#include <unx/sm.hxx>
+#include <unx/i18n_im.hxx>
+
+#include <vcl/inputtypes.hxx>
+
+#include <salwtype.hxx>
+
+// plugin factory function
+extern "C"
+{
+ VCLPLUG_GEN_PUBLIC SalInstance* create_SalInstance()
+ {
+ /* #i92121# workaround deadlocks in the X11 implementation
+ */
+ static const char* pNoXInitThreads = getenv( "SAL_NO_XINITTHREADS" );
+ /* #i90094#
+ from now on we know that an X connection will be
+ established, so protect X against itself
+ */
+ if( ! ( pNoXInitThreads && *pNoXInitThreads ) )
+ XInitThreads();
+
+ X11SalInstance* pInstance = new X11SalInstance( std::make_unique<SalYieldMutex>() );
+
+ // initialize SalData
+ X11SalData *pSalData = new X11SalData();
+
+ pSalData->Init();
+ pInstance->SetLib( pSalData->GetLib() );
+
+ return pInstance;
+ }
+}
+
+X11SalInstance::X11SalInstance(std::unique_ptr<SalYieldMutex> pMutex)
+ : SalGenericInstance(std::move(pMutex))
+ , mpXLib(nullptr)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mxToolkitName = OUString("x11");
+ m_bSupportsOpenGL = true;
+#if HAVE_FEATURE_SKIA
+ X11SkiaSalGraphicsImpl::prepareSkia();
+#if SKIA_USE_BITMAP32
+ if (SkiaHelper::isVCLSkiaEnabled())
+ m_bSupportsBitmap32 = true;
+#endif
+#endif
+}
+
+X11SalInstance::~X11SalInstance()
+{
+ // close session management
+ SessionManagerClient::close();
+
+ // dispose SalDisplay list from SalData
+ // would be done in a static destructor else which is
+ // a little late
+ GetGenericUnixSalData()->Dispose();
+
+#if HAVE_FEATURE_SKIA
+ SkiaHelper::cleanup();
+#endif
+}
+
+SalX11Display* X11SalInstance::CreateDisplay() const
+{
+ return new SalX11Display( mpXLib->GetDisplay() );
+}
+
+// AnyInput from sv/mow/source/app/svapp.cxx
+
+namespace {
+
+struct PredicateReturn
+{
+ VclInputFlags nType;
+ bool bRet;
+};
+
+}
+
+extern "C" {
+static Bool ImplPredicateEvent( Display *, XEvent *pEvent, char *pData )
+{
+ PredicateReturn *pPre = reinterpret_cast<PredicateReturn *>(pData);
+
+ if ( pPre->bRet )
+ return False;
+
+ VclInputFlags nType;
+
+ switch( pEvent->type )
+ {
+ case ButtonPress:
+ case ButtonRelease:
+ case MotionNotify:
+ case EnterNotify:
+ case LeaveNotify:
+ nType = VclInputFlags::MOUSE;
+ break;
+
+ case KeyPress:
+ //case KeyRelease:
+ nType = VclInputFlags::KEYBOARD;
+ break;
+ case Expose:
+ case GraphicsExpose:
+ case NoExpose:
+ nType = VclInputFlags::PAINT;
+ break;
+ default:
+ nType = VclInputFlags::NONE;
+ }
+
+ if ( (nType & pPre->nType) || ( nType == VclInputFlags::NONE && (pPre->nType & VclInputFlags::OTHER) ) )
+ pPre->bRet = true;
+
+ return False;
+}
+}
+
+bool X11SalInstance::AnyInput(VclInputFlags nType)
+{
+ GenericUnixSalData *pData = GetGenericUnixSalData();
+ Display *pDisplay = vcl_sal::getSalDisplay(pData)->GetDisplay();
+ bool bRet = false;
+
+ if( (nType & VclInputFlags::TIMER) && (mpXLib && mpXLib->CheckTimeout(false)) )
+ bRet = true;
+
+ if( !bRet && XPending(pDisplay) )
+ {
+ PredicateReturn aInput;
+ XEvent aEvent;
+
+ aInput.bRet = false;
+ aInput.nType = nType;
+
+ XCheckIfEvent(pDisplay, &aEvent, ImplPredicateEvent,
+ reinterpret_cast<char *>(&aInput) );
+
+ bRet = aInput.bRet;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "AnyInput "
+ << std::showbase << std::hex
+ << static_cast<unsigned int>(nType)
+ << " = " << (bRet ? "true" : "false"));
+#endif
+ return bRet;
+}
+
+bool X11SalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
+{
+ return mpXLib->Yield( bWait, bHandleAllCurrentEvents );
+}
+
+OUString X11SalInstance::GetConnectionIdentifier()
+{
+ static const char* pDisplay = getenv( "DISPLAY" );
+ return pDisplay ? OUString::createFromAscii(pDisplay) : OUString();
+}
+
+SalFrame *X11SalInstance::CreateFrame( SalFrame *pParent, SalFrameStyleFlags nSalFrameStyle )
+{
+ SalFrame *pFrame = new X11SalFrame( pParent, nSalFrameStyle );
+
+ return pFrame;
+}
+
+SalFrame* X11SalInstance::CreateChildFrame( SystemParentData* pParentData, SalFrameStyleFlags nStyle )
+{
+ SalFrame* pFrame = new X11SalFrame( nullptr, nStyle, pParentData );
+
+ return pFrame;
+}
+
+void X11SalInstance::DestroyFrame( SalFrame* pFrame )
+{
+ delete pFrame;
+}
+
+void X11SalInstance::AfterAppInit()
+{
+ assert( mpXLib->GetDisplay() );
+ assert( mpXLib->GetInputMethod() );
+
+ SalX11Display *pSalDisplay = CreateDisplay();
+ mpXLib->GetInputMethod()->CreateMethod( mpXLib->GetDisplay() );
+ pSalDisplay->SetupInput();
+}
+
+void X11SalInstance::AddToRecentDocumentList(const OUString&, const OUString&, const OUString&) {}
+
+void X11SalInstance::PostPrintersChanged()
+{
+ SalDisplay* pDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ for (auto pSalFrame : pDisp->getFrames() )
+ pDisp->PostEvent( pSalFrame, nullptr, SalEvent::PrinterChanged );
+}
+
+std::unique_ptr<GenPspGraphics> X11SalInstance::CreatePrintGraphics()
+{
+ return std::make_unique<GenPspGraphics>();
+}
+
+std::shared_ptr<SalBitmap> X11SalInstance::CreateSalBitmap()
+{
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled())
+ return std::make_shared<SkiaSalBitmap>();
+#endif
+ return std::make_shared<SvpSalBitmap>();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/saltimer.cxx b/vcl/unx/generic/app/saltimer.cxx
new file mode 100644
index 0000000000..dc7a61dfe0
--- /dev/null
+++ b/vcl/unx/generic/app/saltimer.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 <sys/time.h>
+
+#include <unx/salunxtime.h>
+#include <unx/saldisp.hxx>
+#include <unx/saltimer.h>
+#include <unx/salinst.h>
+
+void SalXLib::StopTimer()
+{
+ m_aTimeout.tv_sec = 0;
+ m_aTimeout.tv_usec = 0;
+ m_nTimeoutMS = 0;
+}
+
+void SalXLib::StartTimer( sal_uInt64 nMS )
+{
+ timeval Timeout (m_aTimeout); // previous timeout.
+ gettimeofday (&m_aTimeout, nullptr);
+
+ m_nTimeoutMS = nMS;
+ m_aTimeout += m_nTimeoutMS;
+
+ if ((Timeout > m_aTimeout) || (Timeout.tv_sec == 0))
+ {
+ // Wakeup from previous timeout (or stopped timer).
+ Wakeup();
+ }
+}
+
+SalTimer* X11SalInstance::CreateSalTimer()
+{
+ return new X11SalTimer( mpXLib );
+}
+
+X11SalTimer::~X11SalTimer()
+{
+}
+
+void X11SalTimer::Stop()
+{
+ mpXLib->StopTimer();
+}
+
+void X11SalTimer::Start( sal_uInt64 nMS )
+{
+ mpXLib->StartTimer( nMS );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/sm.cxx b/vcl/unx/generic/app/sm.cxx
new file mode 100644
index 0000000000..071ac32fdb
--- /dev/null
+++ b/vcl/unx/generic/app/sm.cxx
@@ -0,0 +1,857 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <cassert>
+
+#include <string.h>
+#include <unistd.h>
+#include <poll.h>
+#include <fcntl.h>
+
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+
+#include <rtl/process.h>
+#include <osl/security.h>
+#include <osl/diagnose.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include <unx/sm.hxx>
+#include <unx/saldisp.hxx>
+#include <unx/salinst.h>
+
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+
+#include <salframe.hxx>
+#include <salsession.hxx>
+
+namespace {
+
+class IceSalSession : public SalSession
+{
+public:
+ IceSalSession() {}
+
+private:
+ virtual ~IceSalSession() override {}
+
+ virtual void queryInteraction() override;
+ virtual void interactionDone() override;
+ virtual void saveDone() override;
+ virtual bool cancelShutdown() override;
+};
+
+}
+
+std::unique_ptr<SalSession> X11SalInstance::CreateSalSession()
+{
+ SAL_INFO("vcl.sm", "X11SalInstance::CreateSalSession");
+
+ std::unique_ptr<SalSession> p(new IceSalSession);
+ SessionManagerClient::open(p.get());
+ return p;
+}
+
+void IceSalSession::queryInteraction()
+{
+ SAL_INFO("vcl.sm", "IceSalSession::queryInteraction");
+
+ if( ! SessionManagerClient::queryInteraction() )
+ {
+ SAL_INFO("vcl.sm.debug", " call SalSessionInteractionEvent");
+ SalSessionInteractionEvent aEvent( false );
+ CallCallback( &aEvent );
+ }
+}
+
+void IceSalSession::interactionDone()
+{
+ SAL_INFO("vcl.sm", "IceSalSession::interactionDone");
+
+ SessionManagerClient::interactionDone( false );
+}
+
+void IceSalSession::saveDone()
+{
+ SAL_INFO("vcl.sm", "IceSalSession::saveDone");
+
+ SessionManagerClient::saveDone();
+}
+
+bool IceSalSession::cancelShutdown()
+{
+ SAL_INFO("vcl.sm", "IceSalSession::cancelShutdown");
+
+ SessionManagerClient::interactionDone( true );
+ return false;
+}
+
+extern "C" {
+
+static void ICEWatchProc(
+ IceConn ice_conn, IcePointer client_data, Bool opening,
+ IcePointer * watch_data);
+
+static void ICEConnectionWorker(void * data);
+
+}
+
+class ICEConnectionObserver
+{
+ friend void ICEWatchProc(IceConn, IcePointer, Bool, IcePointer *);
+
+ friend void ICEConnectionWorker(void *);
+
+ struct pollfd* m_pFilehandles;
+ int m_nConnections;
+ IceConn* m_pConnections;
+ int m_nWakeupFiles[2];
+ oslThread m_ICEThread;
+ IceIOErrorHandler m_origIOErrorHandler;
+ IceErrorHandler m_origErrorHandler;
+
+ void wakeup();
+
+public:
+ osl::Mutex m_ICEMutex;
+
+ ICEConnectionObserver()
+ : m_pFilehandles(nullptr)
+ , m_nConnections(0)
+ , m_pConnections(nullptr)
+ , m_ICEThread(nullptr)
+ , m_origIOErrorHandler(nullptr)
+ , m_origErrorHandler(nullptr)
+ {
+ SAL_INFO("vcl.sm", "ICEConnectionObserver::ICEConnectionObserver");
+
+ m_nWakeupFiles[0] = m_nWakeupFiles[1] = 0;
+ }
+
+ void activate();
+ void deactivate();
+ void terminate(oslThread iceThread);
+};
+
+SalSession * SessionManagerClient::m_pSession = nullptr;
+std::unique_ptr< ICEConnectionObserver >
+SessionManagerClient::m_xICEConnectionObserver;
+SmcConn SessionManagerClient::m_pSmcConnection = nullptr;
+OString SessionManagerClient::m_aClientID = ""_ostr;
+OString SessionManagerClient::m_aTimeID = ""_ostr;
+OString SessionManagerClient::m_aClientTimeID = ""_ostr;
+bool SessionManagerClient::m_bDocSaveDone = false; // HACK
+
+extern "C" {
+
+static void IgnoreIceErrors(
+ SAL_UNUSED_PARAMETER IceConn, SAL_UNUSED_PARAMETER Bool,
+ SAL_UNUSED_PARAMETER int, SAL_UNUSED_PARAMETER unsigned long,
+ SAL_UNUSED_PARAMETER int, SAL_UNUSED_PARAMETER int,
+ SAL_UNUSED_PARAMETER IcePointer)
+{}
+
+static void IgnoreIceIOErrors(SAL_UNUSED_PARAMETER IceConn) {}
+
+}
+
+static SmProp* pSmProps = nullptr;
+static SmProp** ppSmProps = nullptr;
+static char ** ppSmDel = nullptr;
+
+static int nSmProps = 0;
+static int nSmDel = 0;
+static unsigned char *pSmRestartHint = nullptr;
+
+
+enum { eCloneCommand, eProgram, eRestartCommand, eUserId, eRestartStyleHint };
+enum { eDiscardCommand };
+
+
+static void BuildSmPropertyList()
+{
+ SAL_INFO("vcl.sm", "BuildSmPropertyList");
+
+ if( ! pSmProps )
+ {
+ nSmProps = 5;
+ nSmDel = 1;
+ pSmProps = new SmProp[ nSmProps ];
+ ppSmProps = new SmProp*[ nSmProps ];
+ ppSmDel = new char*[ nSmDel ];
+ }
+
+ OString aExec(OUStringToOString(SessionManagerClient::getExecName(), osl_getThreadTextEncoding()));
+
+ pSmProps[ eCloneCommand ].name = const_cast<char*>(SmCloneCommand);
+ pSmProps[ eCloneCommand ].type = const_cast<char*>(SmLISTofARRAY8);
+ pSmProps[ eCloneCommand ].num_vals = 1;
+ pSmProps[ eCloneCommand ].vals = new SmPropValue;
+ pSmProps[ eCloneCommand ].vals->length = aExec.getLength()+1;
+ pSmProps[ eCloneCommand ].vals->value = strdup( aExec.getStr() );
+
+ pSmProps[ eProgram ].name = const_cast<char*>(SmProgram);
+ pSmProps[ eProgram ].type = const_cast<char*>(SmARRAY8);
+ pSmProps[ eProgram ].num_vals = 1;
+ pSmProps[ eProgram ].vals = new SmPropValue;
+ pSmProps[ eProgram ].vals->length = aExec.getLength()+1;
+ pSmProps[ eProgram ].vals->value = strdup( aExec.getStr() );
+
+ pSmProps[ eRestartCommand ].name = const_cast<char*>(SmRestartCommand);
+ pSmProps[ eRestartCommand ].type = const_cast<char*>(SmLISTofARRAY8);
+ pSmProps[ eRestartCommand ].num_vals = 3;
+ pSmProps[ eRestartCommand ].vals = new SmPropValue[3];
+ pSmProps[ eRestartCommand ].vals[0].length = aExec.getLength()+1;
+ pSmProps[ eRestartCommand ].vals[0].value = strdup( aExec.getStr() );
+ OString aRestartOption = "--session=" + SessionManagerClient::getSessionID();
+ pSmProps[ eRestartCommand ].vals[1].length = aRestartOption.getLength()+1;
+ pSmProps[ eRestartCommand ].vals[1].value = strdup(aRestartOption.getStr());
+ OString aRestartOptionNoLogo("--nologo"_ostr);
+ pSmProps[ eRestartCommand ].vals[2].length = aRestartOptionNoLogo.getLength()+1;
+ pSmProps[ eRestartCommand ].vals[2].value = strdup(aRestartOptionNoLogo.getStr());
+
+ OUString aUserName;
+ OString aUser;
+ oslSecurity aSec = osl_getCurrentSecurity();
+ if( aSec )
+ {
+ osl_getUserName( aSec, &aUserName.pData );
+ aUser = OUStringToOString( aUserName, osl_getThreadTextEncoding() );
+ osl_freeSecurityHandle( aSec );
+ }
+
+ pSmProps[ eUserId ].name = const_cast<char*>(SmUserID);
+ pSmProps[ eUserId ].type = const_cast<char*>(SmARRAY8);
+ pSmProps[ eUserId ].num_vals = 1;
+ pSmProps[ eUserId ].vals = new SmPropValue;
+ pSmProps[ eUserId ].vals->value = strdup( aUser.getStr() );
+ pSmProps[ eUserId ].vals->length = rtl_str_getLength( static_cast<char *>(pSmProps[ 3 ].vals->value) )+1;
+
+ pSmProps[ eRestartStyleHint ].name = const_cast<char*>(SmRestartStyleHint);
+ pSmProps[ eRestartStyleHint ].type = const_cast<char*>(SmCARD8);
+ pSmProps[ eRestartStyleHint ].num_vals = 1;
+ pSmProps[ eRestartStyleHint ].vals = new SmPropValue;
+ pSmProps[ eRestartStyleHint ].vals->value = malloc(1);
+ pSmRestartHint = static_cast<unsigned char *>(pSmProps[ 4 ].vals->value);
+ *pSmRestartHint = SmRestartIfRunning;
+ pSmProps[ eRestartStyleHint ].vals->length = 1;
+
+ for( int i = 0; i < nSmProps; i++ )
+ ppSmProps[ i ] = &pSmProps[i];
+
+ ppSmDel[eDiscardCommand] = const_cast<char*>(SmDiscardCommand);
+}
+
+bool SessionManagerClient::checkDocumentsSaved()
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::checkDocumentsSaved");
+
+ SAL_INFO("vcl.sm.debug", " m_bcheckDocumentsSaved = " << (m_bDocSaveDone ? "true" : "false" ));
+ return m_bDocSaveDone;
+}
+
+IMPL_STATIC_LINK( SessionManagerClient, SaveYourselfHdl, void*, pStateVal, void )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient, SaveYourselfHdl");
+
+ // Decode argument smuggled in as void*:
+ sal_uIntPtr nStateVal = reinterpret_cast< sal_uIntPtr >(pStateVal);
+ bool shutdown = nStateVal != 0;
+
+ static bool bFirstShutdown=true;
+
+ SAL_INFO("vcl.sm.debug", " shutdown = " << (shutdown ? "true" : "false" ) <<
+ ", bFirstShutdown = " << (bFirstShutdown ? "true" : "false" ));
+ if (shutdown && bFirstShutdown) //first shutdown request
+ {
+ bFirstShutdown = false;
+ /*
+ If we have no actual frames open, e.g. we launched a quickstarter,
+ and then shutdown all our frames leaving just a quickstarter running,
+ then we don't want to launch an empty toplevel frame on the next
+ start. (The job of scheduling the restart of the quick-starter is a
+ task of the quick-starter)
+ */
+ *pSmRestartHint = SmRestartNever;
+ for (auto pSalFrame : vcl_sal::getSalDisplay(GetGenericUnixSalData())->getFrames() )
+ {
+ vcl::Window *pWindow = pSalFrame->GetWindow();
+ if (pWindow && pWindow->IsVisible())
+ {
+ *pSmRestartHint = SmRestartIfRunning;
+ SAL_INFO("vcl.sm.debug", " pSmRestartHint = SmRestartIfRunning");
+ break;
+ }
+ }
+ }
+
+ if( m_pSession )
+ {
+ SalSessionSaveRequestEvent aEvent( shutdown );
+ m_pSession->CallCallback( &aEvent );
+ }
+ else
+ saveDone();
+}
+
+IMPL_STATIC_LINK_NOARG( SessionManagerClient, InteractionHdl, void*, void )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient, InteractionHdl");
+
+ if( m_pSession )
+ {
+ SalSessionInteractionEvent aEvent( true );
+ m_pSession->CallCallback( &aEvent );
+ }
+}
+
+IMPL_STATIC_LINK_NOARG( SessionManagerClient, ShutDownCancelHdl, void*, void )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient, ShutDownCancelHdl");
+
+ if( m_pSession )
+ {
+ SalSessionShutdownCancelEvent aEvent;
+ m_pSession->CallCallback( &aEvent );
+ }
+}
+
+void SessionManagerClient::SaveYourselfProc(
+ SmcConn,
+ SmPointer,
+ int save_type,
+ Bool shutdown,
+ int interact_style,
+ Bool
+ )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::SaveYourselfProc");
+
+ TimeValue now;
+ osl_getSystemTime(&now);
+
+ SAL_INFO("vcl.sm", " save_type = " << ((save_type == SmSaveLocal ) ? "local" :
+ (save_type == SmSaveGlobal) ? "global" : "both") <<
+ ", shutdown = " << (shutdown ? "true" : "false" ) <<
+ ", interact_style = " << ((interact_style == SmInteractStyleNone) ? "SmInteractStyleNone" :
+ (interact_style == SmInteractStyleErrors) ? "SmInteractStyleErrors" :
+ "SmInteractStyleAny"));
+ char num[100];
+ snprintf(num, sizeof(num), "_%" SAL_PRIuUINT32 "_%" SAL_PRIuUINT32, now.Seconds, (now.Nanosec / 1001));
+ m_aTimeID = OString(num);
+
+ BuildSmPropertyList();
+
+ SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eProgram ] );
+ SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eUserId ] );
+
+
+ m_bDocSaveDone = false;
+ /* #i49875# some session managers send a "die" message if the
+ * saveDone does not come early enough for their convenience
+ * this can occasionally happen on startup, especially the first
+ * startup. So shortcut the "not shutting down" case since the
+ * upper layers are currently not interested in that event anyway.
+ */
+ if( ! shutdown )
+ {
+ SessionManagerClient::saveDone();
+ return;
+ }
+ // Smuggle argument in as void*:
+ sal_uIntPtr nStateVal = shutdown;
+ Application::PostUserEvent( LINK( nullptr, SessionManagerClient, SaveYourselfHdl ), reinterpret_cast< void * >(nStateVal) );
+}
+
+IMPL_STATIC_LINK_NOARG( SessionManagerClient, ShutDownHdl, void*, void )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient, ShutDownHdl");
+
+ if( m_pSession )
+ {
+ SalSessionQuitEvent aEvent;
+ m_pSession->CallCallback( &aEvent );
+ }
+
+ SalFrame *pAnyFrame = vcl_sal::getSalDisplay(GetGenericUnixSalData())->anyFrame();
+ SAL_INFO("vcl.sm.debug", " rFrames.empty() = " << (pAnyFrame ? "true" : "false"));
+ if( pAnyFrame )
+ pAnyFrame->CallCallback( SalEvent::Shutdown, nullptr );
+}
+
+void SessionManagerClient::DieProc(
+ SmcConn connection,
+ SmPointer
+ )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::DieProc");
+
+ if( connection == m_pSmcConnection )
+ {
+ SAL_INFO("vcl.sm.debug", " connection == m_pSmcConnection" );
+ Application::PostUserEvent( LINK( nullptr, SessionManagerClient, ShutDownHdl ) );
+ }
+}
+
+void SessionManagerClient::SaveCompleteProc(
+ SmcConn,
+ SmPointer
+ )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::SaveCompleteProc");
+}
+
+void SessionManagerClient::ShutdownCanceledProc(
+ SmcConn connection,
+ SmPointer )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::ShutdownCanceledProc" );
+
+ SAL_INFO("vcl.sm.debug", " connection == m_pSmcConnection = " << (( connection == m_pSmcConnection ) ? "true" : "false"));
+ if( connection == m_pSmcConnection )
+ Application::PostUserEvent( LINK( nullptr, SessionManagerClient, ShutDownCancelHdl ) );
+}
+
+void SessionManagerClient::InteractProc(
+ SmcConn connection,
+ SmPointer )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::InteractProc" );
+
+ SAL_INFO("vcl.sm.debug", " connection == m_pSmcConnection = " << (( connection == m_pSmcConnection ) ? "true" : "false"));
+ if( connection == m_pSmcConnection )
+ Application::PostUserEvent( LINK( nullptr, SessionManagerClient, InteractionHdl ) );
+}
+
+void SessionManagerClient::saveDone()
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::saveDone");
+
+ if( !m_pSmcConnection )
+ return;
+
+ assert(m_xICEConnectionObserver);
+ osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex);
+ //SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eCloneCommand ] );
+ // this message-handling is now equal to kate and plasma desktop
+ SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eRestartCommand ] );
+ SmcDeleteProperties( m_pSmcConnection, 1, &ppSmDel[ eDiscardCommand ] );
+ SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eRestartStyleHint ] );
+
+ SmcSaveYourselfDone( m_pSmcConnection, True );
+ SAL_INFO("vcl.sm.debug", " sent SmRestartHint = " << (*pSmRestartHint) );
+ m_bDocSaveDone = true;
+}
+
+void SessionManagerClient::open(SalSession * pSession)
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::open");
+
+ assert(!m_pSession && !m_xICEConnectionObserver && !m_pSmcConnection);
+ // must only be called once
+ m_pSession = pSession;
+ // This is the way Xt does it, so we can too:
+ if( getenv( "SESSION_MANAGER" ) )
+ {
+ SAL_INFO("vcl.sm.debug", " getenv( SESSION_MANAGER ) = true");
+ m_xICEConnectionObserver.reset(new ICEConnectionObserver);
+ m_xICEConnectionObserver->activate();
+
+ {
+ osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex);
+
+ static SmcCallbacks aCallbacks; // does this need to be static?
+ aCallbacks.save_yourself.callback = SaveYourselfProc;
+ aCallbacks.save_yourself.client_data = nullptr;
+ aCallbacks.die.callback = DieProc;
+ aCallbacks.die.client_data = nullptr;
+ aCallbacks.save_complete.callback = SaveCompleteProc;
+ aCallbacks.save_complete.client_data = nullptr;
+ aCallbacks.shutdown_cancelled.callback = ShutdownCanceledProc;
+ aCallbacks.shutdown_cancelled.client_data = nullptr;
+ OString aPrevId(getPreviousSessionID());
+ char* pClientID = nullptr;
+ char aErrBuf[1024];
+ m_pSmcConnection = SmcOpenConnection( nullptr,
+ nullptr,
+ SmProtoMajor,
+ SmProtoMinor,
+ SmcSaveYourselfProcMask |
+ SmcDieProcMask |
+ SmcSaveCompleteProcMask |
+ SmcShutdownCancelledProcMask ,
+ &aCallbacks,
+ aPrevId.isEmpty() ? nullptr : const_cast<char*>(aPrevId.getStr()),
+ &pClientID,
+ sizeof( aErrBuf ),
+ aErrBuf );
+ if( !m_pSmcConnection )
+ SAL_INFO("vcl.sm.debug", " SmcOpenConnection failed: " << aErrBuf);
+ else
+ SAL_INFO("vcl.sm.debug", " SmcOpenConnection succeeded, client ID is " << pClientID );
+ m_aClientID = OString(pClientID);
+ free( pClientID );
+ pClientID = nullptr;
+ }
+
+ SalDisplay* pDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ if( pDisp->GetDrawable(pDisp->GetDefaultXScreen()) && !m_aClientID.isEmpty() )
+ {
+ SAL_INFO("vcl.sm.debug", " SmcOpenConnection open: pDisp->GetDrawable = true");
+ XChangeProperty( pDisp->GetDisplay(),
+ pDisp->GetDrawable( pDisp->GetDefaultXScreen() ),
+ XInternAtom( pDisp->GetDisplay(), "SM_CLIENT_ID", False ),
+ XA_STRING,
+ 8,
+ PropModeReplace,
+ reinterpret_cast<unsigned char const *>(m_aClientID.getStr()),
+ m_aClientID.getLength()
+ );
+ }
+ }
+ else
+ {
+ SAL_INFO("vcl.sm.debug", " getenv( SESSION_MANAGER ) = false");
+ }
+}
+
+const OString& SessionManagerClient::getSessionID()
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::getSessionID");
+
+ m_aClientTimeID = m_aClientID + m_aTimeID;
+
+ SAL_INFO("vcl.sm", " SessionID = " << m_aClientTimeID);
+
+ return m_aClientTimeID;
+}
+
+void SessionManagerClient::close()
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::close");
+
+ if( !m_pSmcConnection )
+ return;
+
+ SAL_INFO("vcl.sm.debug", " attempting SmcCloseConnection");
+ assert(m_xICEConnectionObserver);
+ {
+ osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex);
+ SmcCloseConnection( m_pSmcConnection, 0, nullptr );
+ SAL_INFO("vcl.sm", " SmcCloseConnection closed");
+ }
+ m_xICEConnectionObserver->deactivate();
+ m_xICEConnectionObserver.reset();
+ m_pSmcConnection = nullptr;
+}
+
+bool SessionManagerClient::queryInteraction()
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::queryInteraction");
+
+ bool bRet = false;
+ if( m_pSmcConnection )
+ {
+ assert(m_xICEConnectionObserver);
+ osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex);
+ SAL_INFO("vcl.sm.debug", " SmcInteractRequest" );
+ if( SmcInteractRequest( m_pSmcConnection, SmDialogNormal, InteractProc, nullptr ) )
+ bRet = true;
+ }
+ return bRet;
+}
+
+void SessionManagerClient::interactionDone( bool bCancelShutdown )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::interactionDone");
+
+ if( m_pSmcConnection )
+ {
+ assert(m_xICEConnectionObserver);
+ osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex);
+ SAL_INFO("vcl.sm.debug", " SmcInteractDone = " << (bCancelShutdown ? "true" : "false") );
+ SmcInteractDone( m_pSmcConnection, bCancelShutdown ? True : False );
+ }
+}
+
+OUString SessionManagerClient::getExecName()
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::getExecName");
+
+ OUString aExec, aSysExec;
+ osl_getExecutableFile( &aExec.pData );
+ osl_getSystemPathFromFileURL( aExec.pData, &aSysExec.pData );
+
+ if( aSysExec.endsWith(".bin") )
+ aSysExec = aSysExec.copy( 0, aSysExec.getLength() - RTL_CONSTASCII_LENGTH(".bin") );
+
+ SAL_INFO("vcl.sm.debug", " aSysExec = " << aSysExec);
+ return aSysExec;
+}
+
+OString SessionManagerClient::getPreviousSessionID()
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::getPreviousSessionID");
+
+ OString aPrevId;
+
+ sal_uInt32 n = rtl_getAppCommandArgCount();
+ for (sal_uInt32 i = 0; i != n; ++i)
+ {
+ OUString aArg;
+ rtl_getAppCommandArg( i, &aArg.pData );
+ if(aArg.match("--session="))
+ {
+ aPrevId = OUStringToOString(
+ aArg.subView(RTL_CONSTASCII_LENGTH("--session=")),
+ osl_getThreadTextEncoding());
+ break;
+ }
+ }
+
+ SAL_INFO("vcl.sm.debug", " previous ID = " << aPrevId);
+ return aPrevId;
+}
+
+void ICEConnectionObserver::activate()
+{
+ SAL_INFO("vcl.sm", "ICEConnectionObserver::activate");
+
+ /*
+ * Default handlers call exit, we don't care that strongly if something
+ * happens to fail
+ */
+ m_origIOErrorHandler = IceSetIOErrorHandler( IgnoreIceIOErrors );
+ m_origErrorHandler = IceSetErrorHandler( IgnoreIceErrors );
+ IceAddConnectionWatch( ICEWatchProc, this );
+}
+
+void ICEConnectionObserver::deactivate()
+{
+ SAL_INFO("vcl.sm", "ICEConnectionObserver::deactivate");
+
+ oslThread t;
+ {
+ osl::MutexGuard g(m_ICEMutex);
+ IceRemoveConnectionWatch( ICEWatchProc, this );
+ IceSetErrorHandler( m_origErrorHandler );
+ IceSetIOErrorHandler( m_origIOErrorHandler );
+ m_nConnections = 0;
+ t = m_ICEThread;
+ m_ICEThread = nullptr;
+ }
+ if (t)
+ {
+ SAL_INFO("vcl.sm.debug", " terminate");
+ terminate(t);
+ }
+}
+
+void ICEConnectionObserver::wakeup()
+{
+ SAL_INFO("vcl.sm", "ICEConnectionObserver::wakeup");
+
+ char cChar = 'w';
+ OSL_VERIFY(write(m_nWakeupFiles[1], &cChar, 1) == 1);
+}
+
+void ICEConnectionObserver::terminate(oslThread iceThread)
+{
+ SAL_INFO("vcl.sm", "ICEConnectionObserver::terminate");
+
+ osl_terminateThread(iceThread);
+ wakeup();
+ osl_joinWithThread(iceThread);
+ osl_destroyThread(iceThread);
+ close(m_nWakeupFiles[1]);
+ close(m_nWakeupFiles[0]);
+}
+
+void ICEConnectionWorker(void * data)
+{
+ SAL_INFO("vcl.sm", "ICEConnectionWorker");
+
+ osl::Thread::setName("ICEConnectionWorker");
+ ICEConnectionObserver * pThis = static_cast< ICEConnectionObserver * >(
+ data);
+ for (;;)
+ {
+ oslThread t;
+ {
+ osl::MutexGuard g(pThis->m_ICEMutex);
+ if (pThis->m_ICEThread == nullptr || pThis->m_nConnections == 0)
+ {
+ break;
+ }
+ t = pThis->m_ICEThread;
+ }
+ if (!osl_scheduleThread(t))
+ {
+ break;
+ }
+
+ int nConnectionsBefore;
+ struct pollfd* pLocalFD;
+ {
+ osl::MutexGuard g(pThis->m_ICEMutex);
+ nConnectionsBefore = pThis->m_nConnections;
+ int nBytes = sizeof( struct pollfd )*(nConnectionsBefore+1);
+ pLocalFD = static_cast<struct pollfd*>(std::malloc( nBytes ));
+ memcpy( pLocalFD, pThis->m_pFilehandles, nBytes );
+ }
+
+ int nRet = poll( pLocalFD,nConnectionsBefore+1,-1 );
+ bool bWakeup = (pLocalFD[0].revents & POLLIN);
+ std::free( pLocalFD );
+
+ if( nRet < 1 )
+ continue;
+
+ // clear wakeup pipe
+ if( bWakeup )
+ {
+ char buf[4];
+ while( read( pThis->m_nWakeupFiles[0], buf, sizeof( buf ) ) > 0 )
+ ;
+ SAL_INFO("vcl.sm.debug", " file handles active in wakeup: " << nRet);
+ if( nRet == 1 )
+ continue;
+ }
+
+ // check fd's after we obtained the lock
+ osl::MutexGuard g(pThis->m_ICEMutex);
+ if( pThis->m_nConnections > 0 && pThis->m_nConnections == nConnectionsBefore )
+ {
+ nRet = poll( pThis->m_pFilehandles+1, pThis->m_nConnections, 0 );
+ if( nRet > 0 )
+ {
+ SAL_INFO("vcl.sm.debug", " IceProcessMessages");
+ Bool bReply;
+ for( int i = 0; i < pThis->m_nConnections; i++ )
+ if( pThis->m_pFilehandles[i+1].revents & POLLIN )
+ IceProcessMessages( pThis->m_pConnections[i], nullptr, &bReply );
+ }
+ }
+ }
+
+ SAL_INFO("vcl.sm.debug", " shutting down ICE dispatch thread");
+}
+
+void ICEWatchProc(
+ IceConn ice_conn, IcePointer client_data, Bool opening,
+ SAL_UNUSED_PARAMETER IcePointer *)
+{
+ SAL_INFO("vcl.sm", "ICEWatchProc");
+
+ // Note: This is a callback function for ICE; this implicitly means that a
+ // call into ICE lib is calling this, so the m_ICEMutex MUST already be
+ // locked by the caller.
+ ICEConnectionObserver * pThis = static_cast< ICEConnectionObserver * >(
+ client_data);
+ if( opening )
+ {
+ SAL_INFO("vcl.sm.debug", " opening");
+ int fd = IceConnectionNumber( ice_conn );
+ pThis->m_nConnections++;
+ pThis->m_pConnections = static_cast<IceConn*>(std::realloc( pThis->m_pConnections, sizeof( IceConn )*pThis->m_nConnections ));
+ pThis->m_pFilehandles = static_cast<struct pollfd*>(std::realloc( pThis->m_pFilehandles, sizeof( struct pollfd )*(pThis->m_nConnections+1) ));
+ pThis->m_pConnections[ pThis->m_nConnections-1 ] = ice_conn;
+ pThis->m_pFilehandles[ pThis->m_nConnections ].fd = fd;
+ pThis->m_pFilehandles[ pThis->m_nConnections ].events = POLLIN;
+ if( pThis->m_nConnections == 1 )
+ {
+ SAL_INFO("vcl.sm.debug", " First connection");
+ if (!pipe(pThis->m_nWakeupFiles))
+ {
+ int flags;
+ pThis->m_pFilehandles[0].fd = pThis->m_nWakeupFiles[0];
+ pThis->m_pFilehandles[0].events = POLLIN;
+ // set close-on-exec and nonblock descriptor flag.
+ if ((flags = fcntl(pThis->m_nWakeupFiles[0], F_GETFD)) != -1)
+ {
+ flags |= FD_CLOEXEC;
+ (void)fcntl(pThis->m_nWakeupFiles[0], F_SETFD, flags);
+ }
+ if ((flags = fcntl(pThis->m_nWakeupFiles[0], F_GETFL)) != -1)
+ {
+ flags |= O_NONBLOCK;
+ (void)fcntl(pThis->m_nWakeupFiles[0], F_SETFL, flags);
+ }
+ // set close-on-exec and nonblock descriptor flag.
+ if ((flags = fcntl(pThis->m_nWakeupFiles[1], F_GETFD)) != -1)
+ {
+ flags |= FD_CLOEXEC;
+ (void)fcntl(pThis->m_nWakeupFiles[1], F_SETFD, flags);
+ }
+ if ((flags = fcntl(pThis->m_nWakeupFiles[1], F_GETFL)) != -1)
+ {
+ flags |= O_NONBLOCK;
+ (void)fcntl(pThis->m_nWakeupFiles[1], F_SETFL, flags);
+ }
+ pThis->m_ICEThread = osl_createThread(
+ ICEConnectionWorker, pThis);
+ }
+ }
+ }
+ else // closing
+ {
+ SAL_INFO("vcl.sm.debug", " closing");
+ for( int i = 0; i < pThis->m_nConnections; i++ )
+ {
+ if( pThis->m_pConnections[i] == ice_conn )
+ {
+ if( i < pThis->m_nConnections-1 )
+ {
+ memmove( pThis->m_pConnections+i, pThis->m_pConnections+i+1, sizeof( IceConn )*(pThis->m_nConnections-i-1) );
+ memmove( pThis->m_pFilehandles+i+1, pThis->m_pFilehandles+i+2, sizeof( struct pollfd )*(pThis->m_nConnections-i-1) );
+ }
+ pThis->m_nConnections--;
+ pThis->m_pConnections = static_cast<IceConn*>(std::realloc( pThis->m_pConnections, sizeof( IceConn )*pThis->m_nConnections ));
+ pThis->m_pFilehandles = static_cast<struct pollfd*>(std::realloc( pThis->m_pFilehandles, sizeof( struct pollfd )*(pThis->m_nConnections+1) ));
+ break;
+ }
+ }
+ if( pThis->m_nConnections == 0 && pThis->m_ICEThread )
+ {
+ SAL_INFO("vcl.sm.debug", " terminating ICEThread");
+ oslThread t = pThis->m_ICEThread;
+ pThis->m_ICEThread = nullptr;
+
+ // must release the mutex here
+ pThis->m_ICEMutex.release();
+
+ pThis->terminate(t);
+
+ // acquire the mutex again, because the caller does not expect
+ // it to be released when calling into SM
+ pThis->m_ICEMutex.acquire();
+ }
+ }
+
+ SAL_INFO( "vcl.sm.debug", " ICE connection on " << IceConnectionNumber( ice_conn ) );
+ SAL_INFO( "vcl.sm.debug", " Display connection is " << ConnectionNumber( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay() ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/wmadaptor.cxx b/vcl/unx/generic/app/wmadaptor.cxx
new file mode 100644
index 0000000000..240517d7aa
--- /dev/null
+++ b/vcl/unx/generic/app/wmadaptor.cxx
@@ -0,0 +1,2196 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <stdlib.h>
+#include <unistd.h>
+#include <string_view>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <rtl/locale.h>
+
+#include <osl/thread.h>
+#include <osl/process.h>
+#include <sal/macros.h>
+#include <sal/log.hxx>
+#include <comphelper/string.hxx>
+#include <configsettings.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <unx/wmadaptor.hxx>
+#include <unx/saldisp.hxx>
+#include <unx/salframe.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+namespace vcl_sal {
+
+class NetWMAdaptor : public WMAdaptor
+{
+ void setNetWMState( X11SalFrame* pFrame ) const;
+ void initAtoms();
+ virtual bool isValid() const override;
+public:
+ explicit NetWMAdaptor( SalDisplay* );
+
+ virtual void setWMName( X11SalFrame* pFrame, const OUString& rWMName ) const override;
+ virtual void maximizeFrame( X11SalFrame* pFrame, bool bHorizontal = true, bool bVertical = true ) const override;
+ virtual void setFrameTypeAndDecoration( X11SalFrame* pFrame, WMWindowType eType, int nDecorationFlags, X11SalFrame* pTransientFrame ) const override;
+ virtual void enableAlwaysOnTop( X11SalFrame* pFrame, bool bEnable ) const override;
+ virtual int handlePropertyNotify( X11SalFrame* pFrame, XPropertyEvent* pEvent ) const override;
+ virtual void showFullScreen( X11SalFrame* pFrame, bool bFullScreen ) const override;
+ virtual void frameIsMapping( X11SalFrame* pFrame ) const override;
+ virtual void setUserTime( X11SalFrame* i_pFrame, tools::Long i_nUserTime ) const override;
+};
+
+class GnomeWMAdaptor : public WMAdaptor
+{
+ bool m_bValid;
+
+ void setGnomeWMState( X11SalFrame* pFrame ) const;
+ void initAtoms();
+ virtual bool isValid() const override;
+public:
+ explicit GnomeWMAdaptor( SalDisplay * );
+
+ virtual void maximizeFrame( X11SalFrame* pFrame, bool bHorizontal = true, bool bVertical = true ) const override;
+ virtual void enableAlwaysOnTop( X11SalFrame* pFrame, bool bEnable ) const override;
+ virtual int handlePropertyNotify( X11SalFrame* pFrame, XPropertyEvent* pEvent ) const override;
+};
+
+}
+
+using namespace vcl_sal;
+
+namespace {
+
+struct WMAdaptorProtocol
+{
+ const char* pProtocol;
+ int nProtocol;
+};
+
+}
+
+/*
+ * table must be sorted ascending in strings
+ * since it is use with bsearch
+ */
+const WMAdaptorProtocol aProtocolTab[] =
+{
+ { "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE", WMAdaptor::KDE_NET_WM_WINDOW_TYPE_OVERRIDE },
+ { "_NET_ACTIVE_WINDOW", WMAdaptor::NET_ACTIVE_WINDOW },
+ { "_NET_CURRENT_DESKTOP", WMAdaptor::NET_CURRENT_DESKTOP },
+ { "_NET_NUMBER_OF_DESKTOPS", WMAdaptor::NET_NUMBER_OF_DESKTOPS },
+ { "_NET_WM_DESKTOP", WMAdaptor::NET_WM_DESKTOP },
+ { "_NET_WM_ICON", WMAdaptor::NET_WM_ICON },
+ { "_NET_WM_ICON_NAME", WMAdaptor::NET_WM_ICON_NAME },
+ { "_NET_WM_PING", WMAdaptor::NET_WM_PING },
+ { "_NET_WM_STATE", WMAdaptor::NET_WM_STATE },
+ { "_NET_WM_STATE_ABOVE", WMAdaptor::NET_WM_STATE_STAYS_ON_TOP },
+ { "_NET_WM_STATE_FULLSCREEN", WMAdaptor::NET_WM_STATE_FULLSCREEN },
+ { "_NET_WM_STATE_MAXIMIZED_HORIZ", WMAdaptor::NET_WM_STATE_MAXIMIZED_HORZ }, // common bug in e.g. older kwin and sawfish implementations
+ { "_NET_WM_STATE_MAXIMIZED_HORZ", WMAdaptor::NET_WM_STATE_MAXIMIZED_HORZ },
+ { "_NET_WM_STATE_MAXIMIZED_VERT", WMAdaptor::NET_WM_STATE_MAXIMIZED_VERT },
+ { "_NET_WM_STATE_MODAL", WMAdaptor::NET_WM_STATE_MODAL },
+ { "_NET_WM_STATE_SKIP_PAGER", WMAdaptor::NET_WM_STATE_SKIP_PAGER },
+ { "_NET_WM_STATE_SKIP_TASKBAR", WMAdaptor::NET_WM_STATE_SKIP_TASKBAR },
+ { "_NET_WM_STATE_STAYS_ON_TOP", WMAdaptor::NET_WM_STATE_STAYS_ON_TOP },
+ { "_NET_WM_STATE_STICKY", WMAdaptor::NET_WM_STATE_STICKY },
+ { "_NET_WM_STRUT", WMAdaptor::NET_WM_STRUT },
+ { "_NET_WM_STRUT_PARTIAL", WMAdaptor::NET_WM_STRUT_PARTIAL },
+ { "_NET_WM_WINDOW_TYPE", WMAdaptor::NET_WM_WINDOW_TYPE },
+ { "_NET_WM_WINDOW_TYPE_DESKTOP", WMAdaptor::NET_WM_WINDOW_TYPE_DESKTOP },
+ { "_NET_WM_WINDOW_TYPE_DIALOG", WMAdaptor::NET_WM_WINDOW_TYPE_DIALOG },
+ { "_NET_WM_WINDOW_TYPE_DOCK", WMAdaptor::NET_WM_WINDOW_TYPE_DOCK },
+ { "_NET_WM_WINDOW_TYPE_MENU", WMAdaptor::NET_WM_WINDOW_TYPE_MENU },
+ { "_NET_WM_WINDOW_TYPE_NORMAL", WMAdaptor::NET_WM_WINDOW_TYPE_NORMAL },
+ { "_NET_WM_WINDOW_TYPE_SPLASH", WMAdaptor::NET_WM_WINDOW_TYPE_SPLASH },
+ { "_NET_WM_WINDOW_TYPE_SPLASHSCREEN", WMAdaptor::NET_WM_WINDOW_TYPE_SPLASH }, // bug in Metacity 2.4.1
+ { "_NET_WM_WINDOW_TYPE_TOOLBAR", WMAdaptor::NET_WM_WINDOW_TYPE_TOOLBAR },
+ { "_NET_WM_WINDOW_TYPE_UTILITY", WMAdaptor::NET_WM_WINDOW_TYPE_UTILITY },
+ { "_NET_WORKAREA", WMAdaptor::NET_WORKAREA },
+ { "_WIN_APP_STATE", WMAdaptor::WIN_APP_STATE },
+ { "_WIN_CLIENT_LIST", WMAdaptor::WIN_CLIENT_LIST },
+ { "_WIN_EXPANDED_SIZE", WMAdaptor::WIN_EXPANDED_SIZE },
+ { "_WIN_HINTS", WMAdaptor::WIN_HINTS },
+ { "_WIN_ICONS", WMAdaptor::WIN_ICONS },
+ { "_WIN_LAYER", WMAdaptor::WIN_LAYER },
+ { "_WIN_STATE", WMAdaptor::WIN_STATE },
+ { "_WIN_WORKSPACE", WMAdaptor::WIN_WORKSPACE },
+ { "_WIN_WORKSPACE_COUNT", WMAdaptor::WIN_WORKSPACE_COUNT }
+};
+
+/*
+ * table containing atoms to get anyway
+ */
+
+const WMAdaptorProtocol aAtomTab[] =
+{
+ { "WM_STATE", WMAdaptor::WM_STATE },
+ { "_MOTIF_WM_HINTS", WMAdaptor::MOTIF_WM_HINTS },
+ { "WM_PROTOCOLS", WMAdaptor::WM_PROTOCOLS },
+ { "WM_DELETE_WINDOW", WMAdaptor::WM_DELETE_WINDOW },
+ { "WM_TAKE_FOCUS", WMAdaptor::WM_TAKE_FOCUS },
+ { "WM_COMMAND", WMAdaptor::WM_COMMAND },
+ { "WM_CLIENT_LEADER", WMAdaptor::WM_CLIENT_LEADER },
+ { "WM_LOCALE_NAME", WMAdaptor::WM_LOCALE_NAME },
+ { "WM_TRANSIENT_FOR", WMAdaptor::WM_TRANSIENT_FOR },
+ { "SAL_QUITEVENT", WMAdaptor::SAL_QUITEVENT },
+ { "SAL_USEREVENT", WMAdaptor::SAL_USEREVENT },
+ { "SAL_EXTTEXTEVENT", WMAdaptor::SAL_EXTTEXTEVENT },
+ { "SAL_GETTIMEEVENT", WMAdaptor::SAL_GETTIMEEVENT },
+ { "VCL_SYSTEM_SETTINGS", WMAdaptor::VCL_SYSTEM_SETTINGS },
+ { "_XSETTINGS_SETTINGS", WMAdaptor::XSETTINGS },
+ { "_XEMBED", WMAdaptor::XEMBED },
+ { "_XEMBED_INFO", WMAdaptor::XEMBED_INFO },
+ { "_NET_WM_USER_TIME", WMAdaptor::NET_WM_USER_TIME },
+ { "_NET_WM_PID", WMAdaptor::NET_WM_PID }
+};
+
+extern "C" {
+static int compareProtocol( const void* pLeft, const void* pRight )
+{
+ return strcmp( static_cast<const WMAdaptorProtocol*>(pLeft)->pProtocol, static_cast<const WMAdaptorProtocol*>(pRight)->pProtocol );
+}
+}
+
+std::unique_ptr<WMAdaptor> WMAdaptor::createWMAdaptor( SalDisplay* pSalDisplay )
+{
+ std::unique_ptr<WMAdaptor> pAdaptor;
+
+ // try a NetWM
+ pAdaptor.reset(new NetWMAdaptor( pSalDisplay ));
+ if( ! pAdaptor->isValid() )
+ {
+ pAdaptor.reset();
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ SAL_INFO("vcl.app", "WM supports extended WM hints.");
+#endif
+
+ // try a GnomeWM
+ if( ! pAdaptor )
+ {
+ pAdaptor.reset(new GnomeWMAdaptor( pSalDisplay ));
+ if( ! pAdaptor->isValid() )
+ {
+ pAdaptor.reset();
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ SAL_INFO("vcl.app", "WM supports GNOME WM hints.");
+#endif
+ }
+
+ if( ! pAdaptor )
+ pAdaptor.reset(new WMAdaptor( pSalDisplay ));
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "Window Manager's name is \""
+ << pAdaptor->getWindowManagerName()
+ << "\".");
+#endif
+ return pAdaptor;
+}
+
+/*
+ * WMAdaptor constructor
+ */
+
+WMAdaptor::WMAdaptor( SalDisplay* pDisplay ) :
+ m_pSalDisplay( pDisplay ),
+ m_bEnableAlwaysOnTopWorks( false ),
+ m_bLegacyPartialFullscreen( false ),
+ m_nWinGravity( StaticGravity ),
+ m_nInitWinGravity( StaticGravity ),
+ m_bWMshouldSwitchWorkspace( true ),
+ m_bWMshouldSwitchWorkspaceInit( false )
+{
+ Atom aRealType = None;
+ int nFormat = 8;
+ unsigned long nItems = 0;
+ unsigned long nBytesLeft = 0;
+ unsigned char* pProperty = nullptr;
+
+ // default desktops
+ m_nDesktops = 1;
+ m_aWMWorkAreas = ::std::vector< AbsoluteScreenPixelRectangle >
+ ( 1, AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint(), m_pSalDisplay->GetScreenSize( m_pSalDisplay->GetDefaultXScreen() ) ) );
+ m_bEqualWorkAreas = true;
+
+ memset( m_aWMAtoms, 0, sizeof( m_aWMAtoms ) );
+ m_pDisplay = m_pSalDisplay->GetDisplay();
+
+ initAtoms();
+ getNetWmName(); // try to discover e.g. Sawfish
+
+ if( m_aWMName.isEmpty() )
+ {
+ // check for ReflectionX wm (as it needs a workaround in Windows mode
+ Atom aRwmRunning = XInternAtom( m_pDisplay, "RWM_RUNNING", True );
+ if( aRwmRunning != None &&
+ XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ aRwmRunning,
+ 0, 32,
+ False,
+ aRwmRunning,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0 )
+ {
+ if( aRealType == aRwmRunning )
+ m_aWMName = "ReflectionX";
+ XFree( pProperty );
+ }
+ else
+ {
+ aRwmRunning = XInternAtom( m_pDisplay, "_WRQ_WM_RUNNING", True );
+ if( aRwmRunning != None &&
+ XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ aRwmRunning,
+ 0, 32,
+ False,
+ XA_STRING,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0 )
+ {
+ if( aRealType == XA_STRING )
+ m_aWMName = "ReflectionX Windows";
+ XFree( pProperty );
+ }
+ }
+ }
+ if( !m_aWMName.isEmpty() )
+ return;
+
+ Atom aTTAPlatform = XInternAtom( m_pDisplay, "TTA_CLIENT_PLATFORM", True );
+ if( aTTAPlatform == None ||
+ XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ aTTAPlatform,
+ 0, 32,
+ False,
+ XA_STRING,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) != 0 )
+ return;
+
+ if( aRealType == XA_STRING )
+ {
+ m_aWMName = "Tarantella";
+ // #i62319# pretend that AlwaysOnTop works since
+ // the alwaysontop workaround in salframe.cxx results
+ // in a raise/lower loop on a Windows tarantella client
+ // FIXME: this property contains an identification string that
+ // in theory should be good enough to recognize running on a
+ // Windows client; however this string does not seem to be
+ // documented as well as the property itself.
+ m_bEnableAlwaysOnTopWorks = true;
+ }
+ XFree( pProperty );
+}
+
+/*
+ * WMAdaptor destructor
+ */
+
+WMAdaptor::~WMAdaptor()
+{
+}
+
+/*
+ * NetWMAdaptor constructor
+ */
+
+NetWMAdaptor::NetWMAdaptor( SalDisplay* pSalDisplay ) :
+ WMAdaptor( pSalDisplay )
+{
+ // currently all _NET WMs do transient like expected
+
+ Atom aRealType = None;
+ int nFormat = 8;
+ unsigned long nItems = 0;
+ unsigned long nBytesLeft = 0;
+ unsigned char* pProperty = nullptr;
+
+ initAtoms();
+
+ // check for NetWM
+ bool bNetWM = getNetWmName();
+ if( bNetWM
+ && XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ NET_SUPPORTED ],
+ 0, 0,
+ False,
+ XA_ATOM,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && aRealType == XA_ATOM
+ && nFormat == 32
+ )
+ {
+ if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ // collect supported protocols
+ if( XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ NET_SUPPORTED ],
+ 0, nBytesLeft/4,
+ False,
+ XA_ATOM,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && nItems
+ )
+ {
+ Atom* pAtoms = reinterpret_cast<Atom*>(pProperty);
+ char** pAtomNames = static_cast<char**>(alloca( sizeof(char*)*nItems ));
+ if( XGetAtomNames( m_pDisplay, pAtoms, nItems, pAtomNames ) )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "supported protocols:");
+#endif
+ for( unsigned long i = 0; i < nItems; i++ )
+ {
+ // #i80971# protect against invalid atoms
+ if( pAtomNames[i] == nullptr )
+ continue;
+
+ WMAdaptorProtocol aSearch;
+ aSearch.pProtocol = pAtomNames[i];
+ WMAdaptorProtocol* pMatch = static_cast<WMAdaptorProtocol*>(
+ bsearch( &aSearch,
+ aProtocolTab,
+ SAL_N_ELEMENTS( aProtocolTab ),
+ sizeof( struct WMAdaptorProtocol ),
+ compareProtocol ));
+ if( pMatch )
+ {
+ m_aWMAtoms[ pMatch->nProtocol ] = pAtoms[ i ];
+ if( pMatch->nProtocol == NET_WM_STATE_STAYS_ON_TOP )
+ m_bEnableAlwaysOnTopWorks = true;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", " "
+ << pAtomNames[i]
+ << (((pMatch)&&(pMatch->nProtocol != -1)) ?
+ "" : " (unsupported)"));
+#endif
+ XFree( pAtomNames[i] );
+ }
+ }
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+
+ // get number of desktops
+ if( m_aWMAtoms[ NET_NUMBER_OF_DESKTOPS ]
+ && XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ NET_NUMBER_OF_DESKTOPS ],
+ 0, 1,
+ False,
+ XA_CARDINAL,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && pProperty
+ )
+ {
+ m_nDesktops = *reinterpret_cast<long*>(pProperty);
+ XFree( pProperty );
+ pProperty = nullptr;
+ // get work areas
+ if( m_aWMAtoms[ NET_WORKAREA ]
+ && XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ NET_WORKAREA ],
+ 0, 4*m_nDesktops,
+ False,
+ XA_CARDINAL,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty
+ ) == 0
+ && nItems == 4*static_cast<unsigned>(m_nDesktops)
+ )
+ {
+ m_aWMWorkAreas = ::std::vector< AbsoluteScreenPixelRectangle > ( m_nDesktops );
+ tools::Long* pValues = reinterpret_cast<long*>(pProperty);
+ for( int i = 0; i < m_nDesktops; i++ )
+ {
+ AbsoluteScreenPixelPoint aPoint( pValues[4*i],
+ pValues[4*i+1] );
+ AbsoluteScreenPixelSize aSize( pValues[4*i+2],
+ pValues[4*i+3] );
+ AbsoluteScreenPixelRectangle aWorkArea( aPoint, aSize );
+ m_aWMWorkAreas[i] = aWorkArea;
+ if( aWorkArea != m_aWMWorkAreas[0] )
+ m_bEqualWorkAreas = false;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "workarea " << i
+ << ": " << m_aWMWorkAreas[i].GetWidth()
+ << "x" << m_aWMWorkAreas[i].GetHeight()
+ << "+" << m_aWMWorkAreas[i].Left()
+ << "+" << m_aWMWorkAreas[i].Top());
+#endif
+ }
+ XFree( pProperty );
+ }
+ else
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", nItems/4 << " workareas for "
+ << m_nDesktops << " desktops !");
+#endif
+ if( pProperty )
+ {
+ XFree(pProperty);
+ pProperty = nullptr;
+ }
+ }
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+}
+
+/*
+ * GnomeWMAdaptor constructor
+ */
+
+GnomeWMAdaptor::GnomeWMAdaptor( SalDisplay* pSalDisplay ) :
+ WMAdaptor( pSalDisplay ),
+ m_bValid( false )
+{
+ // currently all Gnome WMs do transient like expected
+
+ Atom aRealType = None;
+ int nFormat = 8;
+ unsigned long nItems = 0;
+ unsigned long nBytesLeft = 0;
+ unsigned char* pProperty = nullptr;
+
+ initAtoms();
+
+ // check for GnomeWM
+ if( m_aWMAtoms[ WIN_SUPPORTING_WM_CHECK ] && m_aWMAtoms[ WIN_PROTOCOLS ] )
+ {
+ if( XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ WIN_SUPPORTING_WM_CHECK ],
+ 0, 1,
+ False,
+ XA_CARDINAL,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && aRealType == XA_CARDINAL
+ && nFormat == 32
+ && nItems != 0
+ )
+ {
+ ::Window aWMChild = *reinterpret_cast< ::Window* >(pProperty);
+ XFree( pProperty );
+ pProperty = nullptr;
+ GetGenericUnixSalData()->ErrorTrapPush();
+ if( XGetWindowProperty( m_pDisplay,
+ aWMChild,
+ m_aWMAtoms[ WIN_SUPPORTING_WM_CHECK ],
+ 0, 1,
+ False,
+ XA_CARDINAL,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && aRealType == XA_CARDINAL
+ && nFormat == 32
+ && nItems != 0 )
+ {
+ if (! GetGenericUnixSalData()->ErrorTrapPop( false ) )
+ {
+ GetGenericUnixSalData()->ErrorTrapPush();
+
+ ::Window aCheckWindow = *reinterpret_cast< ::Window* >(pProperty);
+ XFree( pProperty );
+ pProperty = nullptr;
+ if( aCheckWindow == aWMChild )
+ {
+ m_bValid = true;
+ /*
+ * get name of WM
+ * this is NOT part of the GNOME WM hints, but e.g. Sawfish
+ * already supports this part of the extended WM hints
+ */
+ m_aWMAtoms[ UTF8_STRING ] = XInternAtom( m_pDisplay, "UTF8_STRING", False );
+ getNetWmName();
+ }
+ }
+ else
+ GetGenericUnixSalData()->ErrorTrapPush();
+ }
+ GetGenericUnixSalData()->ErrorTrapPop();
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ }
+ if( m_bValid
+ && XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ WIN_PROTOCOLS ],
+ 0, 0,
+ False,
+ XA_ATOM,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && aRealType == XA_ATOM
+ && nFormat == 32
+ )
+ {
+ if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ // collect supported protocols
+ if( XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ WIN_PROTOCOLS ],
+ 0, nBytesLeft/4,
+ False,
+ XA_ATOM,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && pProperty
+ )
+ {
+ Atom* pAtoms = reinterpret_cast<Atom*>(pProperty);
+ char** pAtomNames = static_cast<char**>(alloca( sizeof(char*)*nItems ));
+ if( XGetAtomNames( m_pDisplay, pAtoms, nItems, pAtomNames ) )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "supported protocols:");
+#endif
+ for( unsigned long i = 0; i < nItems; i++ )
+ {
+ // #i80971# protect against invalid atoms
+ if( pAtomNames[i] == nullptr )
+ continue;
+
+ WMAdaptorProtocol aSearch;
+ aSearch.pProtocol = pAtomNames[i];
+ WMAdaptorProtocol* pMatch = static_cast<WMAdaptorProtocol*>(
+ bsearch( &aSearch,
+ aProtocolTab,
+ SAL_N_ELEMENTS( aProtocolTab ),
+ sizeof( struct WMAdaptorProtocol ),
+ compareProtocol ));
+ if( pMatch )
+ {
+ m_aWMAtoms[ pMatch->nProtocol ] = pAtoms[ i ];
+ if( pMatch->nProtocol == WIN_LAYER )
+ m_bEnableAlwaysOnTopWorks = true;
+ }
+ if( strncmp( "_ICEWM_TRAY", pAtomNames[i], 11 ) == 0 )
+ {
+ m_aWMName = "IceWM";
+ m_nWinGravity = NorthWestGravity;
+ m_nInitWinGravity = NorthWestGravity;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", " "
+ << pAtomNames[i]
+ << (((pMatch) && (pMatch->nProtocol != -1)) ?
+ "" : " (unsupported)"));
+#endif
+ XFree( pAtomNames[i] );
+ }
+ }
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+
+ // get number of desktops
+ if( m_aWMAtoms[ WIN_WORKSPACE_COUNT ]
+ && XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ WIN_WORKSPACE_COUNT ],
+ 0, 1,
+ False,
+ XA_CARDINAL,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && pProperty
+ )
+ {
+ m_nDesktops = *reinterpret_cast<long*>(pProperty);
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+}
+
+/*
+ * getNetWmName()
+ */
+bool WMAdaptor::getNetWmName()
+{
+ Atom aRealType = None;
+ int nFormat = 8;
+ unsigned long nItems = 0;
+ unsigned long nBytesLeft = 0;
+ unsigned char* pProperty = nullptr;
+ bool bNetWM = false;
+
+ if( m_aWMAtoms[ NET_SUPPORTING_WM_CHECK ] && m_aWMAtoms[ NET_WM_NAME ] )
+ {
+ if( XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ NET_SUPPORTING_WM_CHECK ],
+ 0, 1,
+ False,
+ XA_WINDOW,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && aRealType == XA_WINDOW
+ && nFormat == 32
+ && nItems != 0
+ )
+ {
+ ::Window aWMChild = *reinterpret_cast< ::Window* >(pProperty);
+ XFree( pProperty );
+ pProperty = nullptr;
+ GetGenericUnixSalData()->ErrorTrapPush();
+ if( XGetWindowProperty( m_pDisplay,
+ aWMChild,
+ m_aWMAtoms[ NET_SUPPORTING_WM_CHECK ],
+ 0, 1,
+ False,
+ XA_WINDOW,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && aRealType == XA_WINDOW
+ && nFormat == 32
+ && nItems != 0 )
+ {
+ if ( ! GetGenericUnixSalData()->ErrorTrapPop( false ) )
+ {
+ GetGenericUnixSalData()->ErrorTrapPush();
+ ::Window aCheckWindow = *reinterpret_cast< ::Window* >(pProperty);
+ XFree( pProperty );
+ pProperty = nullptr;
+ if( aCheckWindow == aWMChild )
+ {
+ bNetWM = true;
+ // get name of WM
+ m_aWMAtoms[ UTF8_STRING ] = XInternAtom( m_pDisplay, "UTF8_STRING", False );
+ if( XGetWindowProperty( m_pDisplay,
+ aWMChild,
+ m_aWMAtoms[ NET_WM_NAME ],
+ 0, 256,
+ False,
+ AnyPropertyType, /* m_aWMAtoms[ UTF8_STRING ],*/
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && nItems != 0
+ )
+ {
+ if (aRealType == m_aWMAtoms[ UTF8_STRING ])
+ m_aWMName = OUString( reinterpret_cast<char*>(pProperty), nItems, RTL_TEXTENCODING_UTF8 );
+ else if (aRealType == XA_STRING)
+ m_aWMName = OUString( reinterpret_cast<char*>(pProperty), nItems, RTL_TEXTENCODING_ISO_8859_1 );
+
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+
+ // if this is metacity, check for version to enable a legacy workaround
+ if( m_aWMName == "Metacity" )
+ {
+ int nVersionMajor = 0, nVersionMinor = 0;
+ Atom nVersionAtom = XInternAtom( m_pDisplay, "_METACITY_VERSION", True );
+ if( nVersionAtom )
+ {
+ if( XGetWindowProperty( m_pDisplay,
+ aWMChild,
+ nVersionAtom,
+ 0, 256,
+ False,
+ m_aWMAtoms[ UTF8_STRING ],
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && nItems != 0
+ )
+ {
+ OUString aMetaVersion( reinterpret_cast<char*>(pProperty), nItems, RTL_TEXTENCODING_UTF8 );
+ sal_Int32 nIdx {0};
+ nVersionMajor = o3tl::toInt32(o3tl::getToken(aMetaVersion, 0, '.', nIdx));
+ nVersionMinor = o3tl::toInt32(o3tl::getToken(aMetaVersion, 0, '.', nIdx));
+ }
+ if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ }
+ if( nVersionMajor < 2 || (nVersionMajor == 2 && nVersionMinor < 12) )
+ m_bLegacyPartialFullscreen = true;
+ }
+ }
+ }
+ else
+ {
+ if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ GetGenericUnixSalData()->ErrorTrapPush();
+ }
+ }
+
+ GetGenericUnixSalData()->ErrorTrapPop();
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ }
+ return bNetWM;
+}
+
+bool WMAdaptor::getWMshouldSwitchWorkspace() const
+{
+ if( ! m_bWMshouldSwitchWorkspaceInit )
+ {
+ WMAdaptor * pWMA = const_cast<WMAdaptor*>(this);
+
+ pWMA->m_bWMshouldSwitchWorkspace = true;
+ vcl::SettingsConfigItem* pItem = vcl::SettingsConfigItem::get();
+ OUString aSetting( pItem->getValue( "WM",
+ "ShouldSwitchWorkspace" ) );
+ if( aSetting.isEmpty() )
+ {
+ if( m_aWMName == "awesome" )
+ {
+ pWMA->m_bWMshouldSwitchWorkspace = false;
+ }
+ }
+ else
+ pWMA->m_bWMshouldSwitchWorkspace = aSetting.toBoolean();
+ pWMA->m_bWMshouldSwitchWorkspaceInit = true;
+ }
+ return m_bWMshouldSwitchWorkspace;
+}
+
+/*
+ * WMAdaptor::isValid()
+ */
+bool WMAdaptor::isValid() const
+{
+ return true;
+}
+
+/*
+ * NetWMAdaptor::isValid()
+ */
+bool NetWMAdaptor::isValid() const
+{
+ // some necessary sanity checks; there are WMs out there
+ // which implement some of the WM hints spec without
+ // real functionality
+ return
+ m_aWMAtoms[ NET_SUPPORTED ]
+ && m_aWMAtoms[ NET_SUPPORTING_WM_CHECK ]
+ && m_aWMAtoms[ NET_WM_NAME ]
+ && m_aWMAtoms[ NET_WM_WINDOW_TYPE_NORMAL ]
+ && m_aWMAtoms[ NET_WM_WINDOW_TYPE_DIALOG ]
+ ;
+}
+
+/*
+ * GnomeWMAdaptor::isValid()
+ */
+bool GnomeWMAdaptor::isValid() const
+{
+ return m_bValid;
+}
+
+/*
+ * WMAdaptor::initAtoms
+ */
+
+void WMAdaptor::initAtoms()
+{
+ // get basic atoms
+ for(const WMAdaptorProtocol & i : aAtomTab)
+ m_aWMAtoms[ i.nProtocol ] = XInternAtom( m_pDisplay, i.pProtocol, False );
+ m_aWMAtoms[ NET_SUPPORTING_WM_CHECK ] = XInternAtom( m_pDisplay, "_NET_SUPPORTING_WM_CHECK", True );
+ m_aWMAtoms[ NET_WM_NAME ] = XInternAtom( m_pDisplay, "_NET_WM_NAME", True );
+}
+
+/*
+ * NetWMAdaptor::initAtoms
+ */
+
+void NetWMAdaptor::initAtoms()
+{
+ WMAdaptor::initAtoms();
+
+ m_aWMAtoms[ NET_SUPPORTED ] = XInternAtom( m_pDisplay, "_NET_SUPPORTED", True );
+}
+
+/*
+ * GnomeWMAdaptor::initAtoms
+ */
+
+void GnomeWMAdaptor::initAtoms()
+{
+ WMAdaptor::initAtoms();
+
+ m_aWMAtoms[ WIN_PROTOCOLS ] = XInternAtom( m_pDisplay, "_WIN_PROTOCOLS", True );
+ m_aWMAtoms[ WIN_SUPPORTING_WM_CHECK ] = XInternAtom( m_pDisplay, "_WIN_SUPPORTING_WM_CHECK", True );
+}
+
+/*
+ * WMAdaptor::setWMName
+ * sets WM_NAME
+ * WM_ICON_NAME
+ */
+
+void WMAdaptor::setWMName( X11SalFrame* pFrame, const OUString& rWMName ) const
+{
+ OString aTitle(OUStringToOString(rWMName,
+ osl_getThreadTextEncoding()));
+
+ OString aWMLocale;
+ rtl_Locale* pLocale = nullptr;
+ osl_getProcessLocale( &pLocale );
+ if( pLocale )
+ {
+ OUString aLocaleString(
+ LanguageTag( *pLocale).getGlibcLocaleString( std::u16string_view()));
+ aWMLocale = OUStringToOString( aLocaleString, RTL_TEXTENCODING_ISO_8859_1 );
+ }
+ else
+ {
+ static const char* pLang = getenv( "LANG" );
+ aWMLocale = pLang ? pLang : "C";
+ }
+
+ char* pT = const_cast<char*>(aTitle.getStr());
+ XTextProperty aProp = { nullptr, None, 0, 0 };
+ XmbTextListToTextProperty( m_pDisplay,
+ &pT,
+ 1,
+ XStdICCTextStyle,
+ &aProp );
+
+ unsigned char const * pData = aProp.nitems ? aProp.value : reinterpret_cast<unsigned char const *>(aTitle.getStr());
+ Atom nType = aProp.nitems ? aProp.encoding : XA_STRING;
+ int nFormat = aProp.nitems ? aProp.format : 8;
+ int nBytes = aProp.nitems ? aProp.nitems : aTitle.getLength();
+ const SystemEnvData* pEnv = pFrame->GetSystemData();
+ XChangeProperty( m_pDisplay,
+ static_cast<::Window>(pEnv->aShellWindow),
+ XA_WM_NAME,
+ nType,
+ nFormat,
+ PropModeReplace,
+ pData,
+ nBytes );
+ XChangeProperty( m_pDisplay,
+ static_cast<::Window>(pEnv->aShellWindow),
+ XA_WM_ICON_NAME,
+ nType,
+ nFormat,
+ PropModeReplace,
+ pData,
+ nBytes );
+ XChangeProperty( m_pDisplay,
+ static_cast<::Window>(pEnv->aShellWindow),
+ m_aWMAtoms[ WM_LOCALE_NAME ],
+ XA_STRING,
+ 8,
+ PropModeReplace,
+ reinterpret_cast<unsigned char const *>(aWMLocale.getStr()),
+ aWMLocale.getLength() );
+ if (aProp.value != nullptr)
+ XFree( aProp.value );
+}
+
+/*
+ * NetWMAdaptor::setWMName
+ * sets WM_NAME
+ * _NET_WM_NAME
+ * WM_ICON_NAME
+ * _NET_WM_ICON_NAME
+ */
+void NetWMAdaptor::setWMName( X11SalFrame* pFrame, const OUString& rWMName ) const
+{
+ WMAdaptor::setWMName( pFrame, rWMName );
+
+ OString aTitle(OUStringToOString(rWMName, RTL_TEXTENCODING_UTF8));
+ const SystemEnvData* pEnv = pFrame->GetSystemData();
+ if( m_aWMAtoms[ NET_WM_NAME ] )
+ XChangeProperty( m_pDisplay,
+ static_cast<::Window>(pEnv->aShellWindow),
+ m_aWMAtoms[ NET_WM_NAME ],
+ m_aWMAtoms[ UTF8_STRING ],
+ 8,
+ PropModeReplace,
+ reinterpret_cast<unsigned char const *>(aTitle.getStr()),
+ aTitle.getLength() );
+ if( m_aWMAtoms[ NET_WM_ICON_NAME ] )
+ XChangeProperty( m_pDisplay,
+ static_cast<::Window>(pEnv->aShellWindow),
+ m_aWMAtoms[ NET_WM_ICON_NAME ],
+ m_aWMAtoms[ UTF8_STRING ],
+ 8,
+ PropModeReplace,
+ reinterpret_cast<unsigned char const *>(aTitle.getStr()),
+ aTitle.getLength() );
+}
+
+/*
+ * NetWMAdaptor::setNetWMState
+ * sets _NET_WM_STATE
+ */
+void NetWMAdaptor::setNetWMState( X11SalFrame* pFrame ) const
+{
+ if( !(m_aWMAtoms[ NET_WM_STATE ]) )
+ return;
+
+ Atom aStateAtoms[ 10 ];
+ int nStateAtoms = 0;
+
+ // set NET_WM_STATE_MODAL
+ if( pFrame->mbMaximizedVert
+ && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ] )
+ aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ];
+ if( pFrame->mbMaximizedHorz
+ && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ] )
+ aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ];
+ if( pFrame->bAlwaysOnTop_ && m_aWMAtoms[ NET_WM_STATE_STAYS_ON_TOP ] )
+ aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_STAYS_ON_TOP ];
+ if( pFrame->mbFullScreen && m_aWMAtoms[ NET_WM_STATE_FULLSCREEN ] )
+ aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_FULLSCREEN ];
+ if( pFrame->meWindowType == WMWindowType::Utility && m_aWMAtoms[ NET_WM_STATE_SKIP_TASKBAR ] )
+ aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_SKIP_TASKBAR ];
+
+ if( nStateAtoms )
+ {
+ XChangeProperty( m_pDisplay,
+ pFrame->GetShellWindow(),
+ m_aWMAtoms[ NET_WM_STATE ],
+ XA_ATOM,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(aStateAtoms),
+ nStateAtoms
+ );
+ }
+ else
+ XDeleteProperty( m_pDisplay,
+ pFrame->GetShellWindow(),
+ m_aWMAtoms[ NET_WM_STATE ] );
+ if( !pFrame->mbMaximizedHorz
+ || !pFrame->mbMaximizedVert
+ || ( pFrame->nStyle_ & SalFrameStyleFlags::SIZEABLE ) )
+ return;
+
+ /*
+ * for maximizing use NorthWestGravity (including decoration)
+ */
+ XSizeHints hints;
+ tools::Long supplied;
+ bool bHint = false;
+ if( XGetWMNormalHints( m_pDisplay,
+ pFrame->GetShellWindow(),
+ &hints,
+ &supplied ) )
+ {
+ bHint = true;
+ hints.flags |= PWinGravity;
+ hints.win_gravity = NorthWestGravity;
+ XSetWMNormalHints( m_pDisplay,
+ pFrame->GetShellWindow(),
+ &hints );
+ XSync( m_pDisplay, False );
+ }
+
+ // SetPosSize necessary to set width/height, min/max w/h
+ sal_Int32 nCurrent = 0;
+ /*
+ * get current desktop here if work areas have different size
+ * (does this happen on any platform ?)
+ */
+ if( ! m_bEqualWorkAreas )
+ {
+ nCurrent = getCurrentWorkArea();
+ if( nCurrent < 0 )
+ nCurrent = 0;
+ }
+ AbsoluteScreenPixelRectangle aPosSize = m_aWMWorkAreas[nCurrent];
+ const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() );
+ aPosSize = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( aPosSize.Left() + rGeom.leftDecoration(),
+ aPosSize.Top() + rGeom.topDecoration() ),
+ AbsoluteScreenPixelSize( aPosSize.GetWidth()
+ - rGeom.leftDecoration()
+ - rGeom.rightDecoration(),
+ aPosSize.GetHeight()
+ - rGeom.topDecoration()
+ - rGeom.bottomDecoration() )
+ );
+ pFrame->SetPosSize( aPosSize );
+
+ /*
+ * reset gravity hint to static gravity
+ * (this should not move window according to ICCCM)
+ */
+ if( bHint && pFrame->nShowState_ != X11ShowState::Unknown )
+ {
+ hints.win_gravity = StaticGravity;
+ XSetWMNormalHints( m_pDisplay,
+ pFrame->GetShellWindow(),
+ &hints );
+ }
+}
+
+/*
+ * GnomeWMAdaptor::setNetWMState
+ * sets _WIN_STATE
+ */
+void GnomeWMAdaptor::setGnomeWMState( X11SalFrame* pFrame ) const
+{
+ if( !(m_aWMAtoms[ WIN_STATE ]) )
+ return;
+
+ sal_uInt32 nWinWMState = 0;
+
+ if( pFrame->mbMaximizedVert )
+ nWinWMState |= 1 << 2;
+ if( pFrame->mbMaximizedHorz )
+ nWinWMState |= 1 << 3;
+
+ XChangeProperty( m_pDisplay,
+ pFrame->GetShellWindow(),
+ m_aWMAtoms[ WIN_STATE ],
+ XA_CARDINAL,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(&nWinWMState),
+ 1
+ );
+ if( !pFrame->mbMaximizedHorz
+ || !pFrame->mbMaximizedVert
+ || ( pFrame->nStyle_ & SalFrameStyleFlags::SIZEABLE ) )
+ return;
+
+ /*
+ * for maximizing use NorthWestGravity (including decoration)
+ */
+ XSizeHints hints;
+ tools::Long supplied;
+ bool bHint = false;
+ if( XGetWMNormalHints( m_pDisplay,
+ pFrame->GetShellWindow(),
+ &hints,
+ &supplied ) )
+ {
+ bHint = true;
+ hints.flags |= PWinGravity;
+ hints.win_gravity = NorthWestGravity;
+ XSetWMNormalHints( m_pDisplay,
+ pFrame->GetShellWindow(),
+ &hints );
+ XSync( m_pDisplay, False );
+ }
+
+ // SetPosSize necessary to set width/height, min/max w/h
+ sal_Int32 nCurrent = 0;
+ /*
+ * get current desktop here if work areas have different size
+ * (does this happen on any platform ?)
+ */
+ if( ! m_bEqualWorkAreas )
+ {
+ nCurrent = getCurrentWorkArea();
+ if( nCurrent < 0 )
+ nCurrent = 0;
+ }
+ AbsoluteScreenPixelRectangle aPosSize = m_aWMWorkAreas[nCurrent];
+ const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() );
+ aPosSize = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( aPosSize.Left() + rGeom.leftDecoration(),
+ aPosSize.Top() + rGeom.topDecoration() ),
+ AbsoluteScreenPixelSize( aPosSize.GetWidth()
+ - rGeom.leftDecoration()
+ - rGeom.rightDecoration(),
+ aPosSize.GetHeight()
+ - rGeom.topDecoration()
+ - rGeom.bottomDecoration() )
+ );
+ pFrame->SetPosSize( aPosSize );
+
+ /*
+ * reset gravity hint to static gravity
+ * (this should not move window according to ICCCM)
+ */
+ if( bHint && pFrame->nShowState_ != X11ShowState::Unknown )
+ {
+ hints.win_gravity = StaticGravity;
+ XSetWMNormalHints( m_pDisplay,
+ pFrame->GetShellWindow(),
+ &hints );
+ }
+}
+
+/*
+ * WMAdaptor::setFrameDecoration
+ * sets _MOTIF_WM_HINTS
+ * WM_TRANSIENT_FOR
+ */
+
+void WMAdaptor::setFrameTypeAndDecoration( X11SalFrame* pFrame, WMWindowType eType, int nDecorationFlags, X11SalFrame* pReferenceFrame ) const
+{
+ pFrame->meWindowType = eType;
+
+ if( ! pFrame->mbFullScreen )
+ {
+ // set mwm hints
+ struct _mwmhints {
+ unsigned long flags, func, deco;
+ tools::Long input_mode;
+ unsigned long status;
+ } aHint;
+
+ aHint.flags = 15; /* flags for functions, decoration, input mode and status */
+ aHint.deco = 0;
+ aHint.func = 1 << 2;
+ aHint.status = 0;
+ aHint.input_mode = 0;
+
+ // evaluate decoration flags
+ if( nDecorationFlags & decoration_All )
+ {
+ aHint.deco = 1;
+ aHint.func = 1;
+ }
+ else
+ {
+ if( nDecorationFlags & decoration_Title )
+ aHint.deco |= 1 << 3;
+ if( nDecorationFlags & decoration_Border )
+ aHint.deco |= 1 << 1;
+ if( nDecorationFlags & decoration_Resize )
+ {
+ aHint.deco |= 1 << 2;
+ aHint.func |= 1 << 1;
+ }
+ if( nDecorationFlags & decoration_MinimizeBtn )
+ {
+ aHint.deco |= 1 << 5;
+ aHint.func |= 1 << 3;
+ }
+ if( nDecorationFlags & decoration_MaximizeBtn )
+ {
+ aHint.deco |= 1 << 6;
+ aHint.func |= 1 << 4;
+ }
+ if( nDecorationFlags & decoration_CloseBtn )
+ {
+ aHint.deco |= 1 << 4;
+ aHint.func |= 1 << 5;
+ }
+ }
+
+ // set the hint
+ XChangeProperty( m_pDisplay,
+ pFrame->GetShellWindow(),
+ m_aWMAtoms[ MOTIF_WM_HINTS ],
+ m_aWMAtoms[ MOTIF_WM_HINTS ],
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(&aHint),
+ 5 );
+ }
+
+ // set transientFor hint
+ /* #91030# dtwm will not map a dialogue if the transient
+ * window is iconified. This is deemed undesirable because
+ * message boxes do not get mapped, so use the root as transient
+ * instead.
+ */
+ if( pReferenceFrame )
+ {
+ XSetTransientForHint( m_pDisplay,
+ pFrame->GetShellWindow(),
+ pReferenceFrame->bMapped_ ?
+ pReferenceFrame->GetShellWindow() :
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() )
+ );
+ if( ! pReferenceFrame->bMapped_ )
+ pFrame->mbTransientForRoot = true;
+ }
+}
+
+/*
+ * NetWMAdaptor::setFrameDecoration
+ * sets _MOTIF_WM_HINTS
+ * _NET_WM_WINDOW_TYPE
+ * _NET_WM_STATE
+ * WM_TRANSIENT_FOR
+ */
+
+void NetWMAdaptor::setFrameTypeAndDecoration( X11SalFrame* pFrame, WMWindowType eType, int nDecorationFlags, X11SalFrame* pReferenceFrame ) const
+{
+ WMAdaptor::setFrameTypeAndDecoration( pFrame, eType, nDecorationFlags, pReferenceFrame );
+
+ setNetWMState( pFrame );
+
+ // set NET_WM_WINDOW_TYPE
+ if( m_aWMAtoms[ NET_WM_WINDOW_TYPE ] )
+ {
+ Atom aWindowTypes[4];
+ int nWindowTypes = 0;
+ switch( eType )
+ {
+ case WMWindowType::Utility:
+ aWindowTypes[nWindowTypes++] =
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_UTILITY ] ?
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_UTILITY ] :
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_DIALOG ];
+ break;
+ case WMWindowType::ModelessDialogue:
+ aWindowTypes[nWindowTypes++] =
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_DIALOG ];
+ break;
+ case WMWindowType::Splash:
+ aWindowTypes[nWindowTypes++] =
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_SPLASH ] ?
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_SPLASH ] :
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_NORMAL ];
+ break;
+ case WMWindowType::Toolbar:
+ if( m_aWMAtoms[ KDE_NET_WM_WINDOW_TYPE_OVERRIDE ] )
+ aWindowTypes[nWindowTypes++] = m_aWMAtoms[ KDE_NET_WM_WINDOW_TYPE_OVERRIDE ];
+ aWindowTypes[nWindowTypes++] =
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_TOOLBAR ] ?
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_TOOLBAR ] :
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_NORMAL];
+ break;
+ case WMWindowType::Dock:
+ aWindowTypes[nWindowTypes++] =
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_DOCK ] ?
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_DOCK ] :
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_NORMAL];
+ break;
+ default:
+ aWindowTypes[nWindowTypes++] = m_aWMAtoms[ NET_WM_WINDOW_TYPE_NORMAL ];
+ break;
+ }
+ XChangeProperty( m_pDisplay,
+ pFrame->GetShellWindow(),
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE ],
+ XA_ATOM,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(aWindowTypes),
+ nWindowTypes );
+ }
+ if( ( eType == WMWindowType::ModelessDialogue )
+ && ! pReferenceFrame )
+ {
+ XSetTransientForHint( m_pDisplay,
+ pFrame->GetShellWindow(),
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ) );
+ pFrame->mbTransientForRoot = true;
+ }
+}
+
+/*
+ * WMAdaptor::maximizeFrame
+ */
+
+void WMAdaptor::maximizeFrame( X11SalFrame* pFrame, bool bHorizontal, bool bVertical ) const
+{
+ pFrame->mbMaximizedVert = bVertical;
+ pFrame->mbMaximizedHorz = bHorizontal;
+
+ const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() );
+
+ // discard pending configure notifies for this frame
+ XSync( m_pDisplay, False );
+ XEvent aDiscard;
+ while( XCheckTypedWindowEvent( m_pDisplay,
+ pFrame->GetShellWindow(),
+ ConfigureNotify,
+ &aDiscard ) )
+ ;
+ while( XCheckTypedWindowEvent( m_pDisplay,
+ pFrame->GetWindow(),
+ ConfigureNotify,
+ &aDiscard ) )
+ ;
+
+ if( bHorizontal || bVertical )
+ {
+ AbsoluteScreenPixelSize aScreenSize( m_pSalDisplay->GetScreenSize( pFrame->GetScreenNumber() ) );
+ AbsoluteScreenPixelPoint aTL( rGeom.leftDecoration(), rGeom.topDecoration() );
+ if( m_pSalDisplay->IsXinerama() )
+ {
+ AbsoluteScreenPixelPoint aMed( aTL.X() + rGeom.width()/2, aTL.Y() + rGeom.height()/2 );
+ const std::vector< AbsoluteScreenPixelRectangle >& rScreens = m_pSalDisplay->GetXineramaScreens();
+ for(const auto & rScreen : rScreens)
+ if( rScreen.Contains( aMed ) )
+ {
+ aTL += rScreen.TopLeft();
+ aScreenSize = rScreen.GetSize();
+ break;
+ }
+ }
+ AbsoluteScreenPixelRectangle aTarget( aTL,
+ AbsoluteScreenPixelSize( aScreenSize.Width() - rGeom.leftDecoration() - rGeom.topDecoration(),
+ aScreenSize.Height() - rGeom.topDecoration() - rGeom.bottomDecoration() )
+ );
+
+ const AbsoluteScreenPixelRectangle aReferenceGeometry = !pFrame->maRestorePosSize.IsEmpty() ?
+ pFrame->maRestorePosSize : AbsoluteScreenPixelRectangle(rGeom.posSize());
+ if( ! bHorizontal )
+ {
+ aTarget.SetSize({ aReferenceGeometry.GetWidth(), aTarget.GetHeight() });
+ aTarget.SetLeft(aReferenceGeometry.Left());
+ }
+ else if( ! bVertical )
+ {
+ aTarget.SetSize({ aTarget.GetWidth(), aReferenceGeometry.GetHeight() });
+ aTarget.SetTop(aReferenceGeometry.Top());
+ }
+
+ AbsoluteScreenPixelRectangle aRestore(rGeom.posSize());
+ if( pFrame->bMapped_ )
+ {
+ XSetInputFocus( m_pDisplay,
+ pFrame->GetShellWindow(),
+ RevertToNone,
+ CurrentTime
+ );
+ }
+
+ if( pFrame->maRestorePosSize.IsEmpty() )
+ pFrame->maRestorePosSize = aRestore;
+
+ pFrame->SetPosSize( aTarget );
+ pFrame->nWidth_ = aTarget.GetWidth();
+ pFrame->nHeight_ = aTarget.GetHeight();
+ XRaiseWindow( m_pDisplay,
+ pFrame->GetShellWindow()
+ );
+ if( pFrame->GetStackingWindow() )
+ XRaiseWindow( m_pDisplay,
+ pFrame->GetStackingWindow()
+ );
+
+ }
+ else
+ {
+ pFrame->SetPosSize( pFrame->maRestorePosSize );
+ pFrame->maRestorePosSize = AbsoluteScreenPixelRectangle();
+ pFrame->nWidth_ = rGeom.width();
+ pFrame->nHeight_ = rGeom.height();
+ }
+}
+
+/*
+ * NetWMAdaptor::maximizeFrame
+ * changes _NET_WM_STATE by sending a client message
+ */
+
+void NetWMAdaptor::maximizeFrame( X11SalFrame* pFrame, bool bHorizontal, bool bVertical ) const
+{
+ pFrame->mbMaximizedVert = bVertical;
+ pFrame->mbMaximizedHorz = bHorizontal;
+
+ if( m_aWMAtoms[ NET_WM_STATE ]
+ && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ]
+ && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ]
+ && ( pFrame->nStyle_ & ~SalFrameStyleFlags::DEFAULT )
+ )
+ {
+ if( pFrame->bMapped_ )
+ {
+ // window already mapped, send WM a message
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.window = pFrame->GetShellWindow();
+ aEvent.xclient.message_type = m_aWMAtoms[ NET_WM_STATE ];
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = bHorizontal ? 1 : 0;
+ aEvent.xclient.data.l[1] = m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ];
+ aEvent.xclient.data.l[2] = bHorizontal == bVertical ? m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ] : 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &aEvent
+ );
+ if( bHorizontal != bVertical )
+ {
+ aEvent.xclient.data.l[0]= bVertical ? 1 : 0;
+ aEvent.xclient.data.l[1]= m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ];
+ aEvent.xclient.data.l[2]= 0;
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &aEvent
+ );
+ }
+ }
+ else
+ {
+ // window not mapped yet, set _NET_WM_STATE directly
+ setNetWMState( pFrame );
+ }
+ if( !bHorizontal && !bVertical )
+ pFrame->maRestorePosSize = AbsoluteScreenPixelRectangle();
+ else if( pFrame->maRestorePosSize.IsEmpty() )
+ {
+ const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() );
+ pFrame->maRestorePosSize =
+ AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( rGeom.x(), rGeom.y() ), AbsoluteScreenPixelSize( rGeom.width(), rGeom.height() ) );
+ }
+ }
+ else
+ WMAdaptor::maximizeFrame( pFrame, bHorizontal, bVertical );
+}
+
+/*
+ * GnomeWMAdaptor::maximizeFrame
+ * changes _WIN_STATE by sending a client message
+ */
+
+void GnomeWMAdaptor::maximizeFrame( X11SalFrame* pFrame, bool bHorizontal, bool bVertical ) const
+{
+ pFrame->mbMaximizedVert = bVertical;
+ pFrame->mbMaximizedHorz = bHorizontal;
+
+ if( m_aWMAtoms[ WIN_STATE ]
+ && ( pFrame->nStyle_ & ~SalFrameStyleFlags::DEFAULT )
+ )
+ {
+ if( pFrame->bMapped_ )
+ {
+ // window already mapped, send WM a message
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.window = pFrame->GetShellWindow();
+ aEvent.xclient.message_type = m_aWMAtoms[ WIN_STATE ];
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = (1<<2)|(1<<3);
+ aEvent.xclient.data.l[1] =
+ (bVertical ? (1<<2) : 0)
+ | (bHorizontal ? (1<<3) : 0);
+ aEvent.xclient.data.l[2] = 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ),
+ False,
+ SubstructureNotifyMask,
+ &aEvent
+ );
+ }
+ else
+ // window not mapped yet, set _WIN_STATE directly
+ setGnomeWMState( pFrame );
+
+ if( !bHorizontal && !bVertical )
+ pFrame->maRestorePosSize = AbsoluteScreenPixelRectangle();
+ else if( pFrame->maRestorePosSize.IsEmpty() )
+ {
+ const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() );
+ pFrame->maRestorePosSize =
+ AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( rGeom.x(), rGeom.y() ), AbsoluteScreenPixelSize( rGeom.width(), rGeom.height() ) );
+ }
+ }
+ else
+ WMAdaptor::maximizeFrame( pFrame, bHorizontal, bVertical );
+}
+
+/*
+ * WMAdaptor::enableAlwaysOnTop
+ */
+void WMAdaptor::enableAlwaysOnTop( X11SalFrame*, bool /*bEnable*/ ) const
+{
+}
+
+/*
+ * NetWMAdaptor::enableAlwaysOnTop
+ */
+void NetWMAdaptor::enableAlwaysOnTop( X11SalFrame* pFrame, bool bEnable ) const
+{
+ pFrame->bAlwaysOnTop_ = bEnable;
+ if( !(m_aWMAtoms[ NET_WM_STATE_STAYS_ON_TOP ]) )
+ return;
+
+ if( pFrame->bMapped_ )
+ {
+ // window already mapped, send WM a message
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.window = pFrame->GetShellWindow();
+ aEvent.xclient.message_type = m_aWMAtoms[ NET_WM_STATE ];
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = bEnable ? 1 : 0;
+ aEvent.xclient.data.l[1] = m_aWMAtoms[ NET_WM_STATE_STAYS_ON_TOP ];
+ aEvent.xclient.data.l[2] = 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &aEvent
+ );
+ }
+ else
+ setNetWMState( pFrame );
+}
+
+/*
+ * GnomeWMAdaptor::enableAlwaysOnTop
+ */
+void GnomeWMAdaptor::enableAlwaysOnTop( X11SalFrame* pFrame, bool bEnable ) const
+{
+ pFrame->bAlwaysOnTop_ = bEnable;
+ if( !(m_aWMAtoms[ WIN_LAYER ]) )
+ return;
+
+ if( pFrame->bMapped_ )
+ {
+ // window already mapped, send WM a message
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.window = pFrame->GetShellWindow();
+ aEvent.xclient.message_type = m_aWMAtoms[ WIN_LAYER ];
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = bEnable ? 6 : 4;
+ aEvent.xclient.data.l[1] = 0;
+ aEvent.xclient.data.l[2] = 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &aEvent
+ );
+ }
+ else
+ {
+ sal_uInt32 nNewLayer = bEnable ? 6 : 4;
+ XChangeProperty( m_pDisplay,
+ pFrame->GetShellWindow(),
+ m_aWMAtoms[ WIN_LAYER ],
+ XA_CARDINAL,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(&nNewLayer),
+ 1
+ );
+ }
+}
+
+/*
+ * WMAdaptor::changeReferenceFrame
+ */
+void WMAdaptor::changeReferenceFrame( X11SalFrame* pFrame, X11SalFrame const * pReferenceFrame ) const
+{
+ if( ( pFrame->nStyle_ & SalFrameStyleFlags::PLUG )
+ || pFrame->IsOverrideRedirect()
+ || pFrame->IsFloatGrabWindow()
+ )
+ return;
+
+ ::Window aTransient = pFrame->pDisplay_->GetRootWindow( pFrame->GetScreenNumber() );
+ pFrame->mbTransientForRoot = true;
+ if( pReferenceFrame )
+ {
+ aTransient = pReferenceFrame->GetShellWindow();
+ pFrame->mbTransientForRoot = false;
+ }
+ XSetTransientForHint( m_pDisplay,
+ pFrame->GetShellWindow(),
+ aTransient );
+}
+
+/*
+ * WMAdaptor::handlePropertyNotify
+ */
+int WMAdaptor::handlePropertyNotify( X11SalFrame*, XPropertyEvent* ) const
+{
+ return 0;
+}
+
+/*
+ * NetWMAdaptor::handlePropertyNotify
+ */
+int NetWMAdaptor::handlePropertyNotify( X11SalFrame* pFrame, XPropertyEvent* pEvent ) const
+{
+ int nHandled = 1;
+ if( pEvent->atom == m_aWMAtoms[ NET_WM_STATE ] )
+ {
+ pFrame->mbMaximizedHorz = pFrame->mbMaximizedVert = false;
+
+ if( pEvent->state == PropertyNewValue )
+ {
+ Atom nType, *pStates;
+ int nFormat;
+ unsigned long nItems, nBytesLeft;
+ unsigned char* pData = nullptr;
+ tools::Long nOffset = 0;
+ do
+ {
+ XGetWindowProperty( m_pDisplay,
+ pEvent->window,
+ m_aWMAtoms[ NET_WM_STATE ],
+ nOffset, 64,
+ False,
+ XA_ATOM,
+ &nType,
+ &nFormat,
+ &nItems, &nBytesLeft,
+ &pData );
+ if( pData )
+ {
+ if( nType == XA_ATOM && nFormat == 32 && nItems > 0 )
+ {
+ pStates = reinterpret_cast<Atom*>(pData);
+ for( unsigned long i = 0; i < nItems; i++ )
+ {
+ if( pStates[i] == m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ] && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ] )
+ pFrame->mbMaximizedVert = true;
+ else if( pStates[i] == m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ] && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ] )
+ pFrame->mbMaximizedHorz = true;
+ }
+ }
+ XFree( pData );
+ pData = nullptr;
+ nOffset += nItems * nFormat / 32;
+ }
+ else
+ break;
+ } while( nBytesLeft > 0 );
+ }
+
+ if( ! (pFrame->mbMaximizedHorz || pFrame->mbMaximizedVert ) )
+ pFrame->maRestorePosSize = AbsoluteScreenPixelRectangle();
+ else
+ {
+ const SalFrameGeometry& rGeom = pFrame->GetUnmirroredGeometry();
+ // the current geometry may already be changed by the corresponding
+ // ConfigureNotify, but this cannot be helped
+ pFrame->maRestorePosSize =
+ AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( rGeom.x(), rGeom.y() ),
+ AbsoluteScreenPixelSize( rGeom.width(), rGeom.height() ) );
+ }
+ }
+ else if( pEvent->atom == m_aWMAtoms[ NET_WM_DESKTOP ] )
+ {
+ pFrame->m_nWorkArea = getWindowWorkArea( pFrame->GetShellWindow() );
+ }
+ else
+ nHandled = 0;
+
+ return nHandled;
+}
+
+/*
+ * GnomeWMAdaptor::handlePropertyNotify
+ */
+int GnomeWMAdaptor::handlePropertyNotify( X11SalFrame* pFrame, XPropertyEvent* pEvent ) const
+{
+ int nHandled = 1;
+ if( pEvent->atom == m_aWMAtoms[ WIN_STATE ] )
+ {
+ pFrame->mbMaximizedHorz = pFrame->mbMaximizedVert = false;
+
+ if( pEvent->state == PropertyNewValue )
+ {
+ Atom nType;
+ int nFormat = 0;
+ unsigned long nItems = 0;
+ unsigned long nBytesLeft = 0;
+ unsigned char* pData = nullptr;
+ XGetWindowProperty( m_pDisplay,
+ pEvent->window,
+ m_aWMAtoms[ WIN_STATE ],
+ 0, 1,
+ False,
+ XA_CARDINAL,
+ &nType,
+ &nFormat,
+ &nItems, &nBytesLeft,
+ &pData );
+ if( pData )
+ {
+ if( nType == XA_CARDINAL && nFormat == 32 && nItems == 1 )
+ {
+ sal_uInt32 nWinState = *reinterpret_cast<sal_uInt32*>(pData);
+ if( nWinState & (1<<2) )
+ pFrame->mbMaximizedVert = true;
+ if( nWinState & (1<<3) )
+ pFrame->mbMaximizedHorz = true;
+ }
+ XFree( pData );
+ }
+ }
+
+ if( ! (pFrame->mbMaximizedHorz || pFrame->mbMaximizedVert ) )
+ pFrame->maRestorePosSize = AbsoluteScreenPixelRectangle();
+ else
+ {
+ const SalFrameGeometry& rGeom = pFrame->GetUnmirroredGeometry();
+ // the current geometry may already be changed by the corresponding
+ // ConfigureNotify, but this cannot be helped
+ pFrame->maRestorePosSize =
+ AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( rGeom.x(), rGeom.y() ),
+ AbsoluteScreenPixelSize( rGeom.width(), rGeom.height() ) );
+ }
+ }
+ else if( pEvent->atom == m_aWMAtoms[ NET_WM_DESKTOP ] )
+ {
+ pFrame->m_nWorkArea = getWindowWorkArea( pFrame->GetShellWindow() );
+ }
+ else
+ nHandled = 0;
+
+ return nHandled;
+}
+
+/*
+ * WMAdaptor::showFullScreen
+ */
+void WMAdaptor::showFullScreen( X11SalFrame* pFrame, bool bFullScreen ) const
+{
+ pFrame->mbFullScreen = bFullScreen;
+ maximizeFrame( pFrame, bFullScreen, bFullScreen );
+}
+
+/*
+ * NetWMAdaptor::showFullScreen
+ */
+void NetWMAdaptor::showFullScreen( X11SalFrame* pFrame, bool bFullScreen ) const
+{
+ if( m_aWMAtoms[ NET_WM_STATE_FULLSCREEN ] )
+ {
+ pFrame->mbFullScreen = bFullScreen;
+ if( bFullScreen )
+ {
+ if( m_aWMAtoms[ MOTIF_WM_HINTS ] )
+ {
+ XDeleteProperty( m_pDisplay,
+ pFrame->GetShellWindow(),
+ m_aWMAtoms[ MOTIF_WM_HINTS ] );
+ }
+ }
+ if( pFrame->bMapped_ )
+ {
+ // window already mapped, send WM a message
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.window = pFrame->GetShellWindow();
+ aEvent.xclient.message_type = m_aWMAtoms[ NET_WM_STATE ];
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = bFullScreen ? 1 : 0;
+ aEvent.xclient.data.l[1] = m_aWMAtoms[ NET_WM_STATE_FULLSCREEN ];
+ aEvent.xclient.data.l[2] = 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &aEvent
+ );
+ }
+ else
+ {
+ // window not mapped yet, set _NET_WM_STATE directly
+ setNetWMState( pFrame );
+ }
+ // #i42750# guess size before resize event shows up
+ if( bFullScreen )
+ {
+ if( m_pSalDisplay->IsXinerama() )
+ {
+ ::Window aRoot, aChild;
+ int root_x = 0, root_y = 0, lx, ly;
+ unsigned int mask;
+ XQueryPointer( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ),
+ &aRoot, &aChild,
+ &root_x, &root_y, &lx, &ly, &mask );
+ const std::vector< AbsoluteScreenPixelRectangle >& rScreens = m_pSalDisplay->GetXineramaScreens();
+ AbsoluteScreenPixelPoint aMousePoint( root_x, root_y );
+ for(const auto & rScreen : rScreens)
+ {
+ if( rScreen.Contains( aMousePoint ) )
+ {
+ pFrame->maGeometry.setPosSize(tools::Rectangle(rScreen));
+ break;
+ }
+ }
+ }
+ else
+ {
+ AbsoluteScreenPixelSize aSize = m_pSalDisplay->GetScreenSize( pFrame->GetScreenNumber() );
+ pFrame->maGeometry.setPosSize({ { 0, 0 }, aSize });
+ }
+ pFrame->CallCallback( SalEvent::MoveResize, nullptr );
+ }
+ }
+ else WMAdaptor::showFullScreen( pFrame, bFullScreen );
+}
+
+/*
+ * WMAdaptor::getCurrentWorkArea
+ */
+// FIXME: multiscreen case
+int WMAdaptor::getCurrentWorkArea() const
+{
+ int nCurrent = -1;
+ if( m_aWMAtoms[ NET_CURRENT_DESKTOP ] )
+ {
+ Atom aRealType = None;
+ int nFormat = 8;
+ unsigned long nItems = 0;
+ unsigned long nBytesLeft = 0;
+ unsigned char* pProperty = nullptr;
+ if( XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ NET_CURRENT_DESKTOP ],
+ 0, 1,
+ False,
+ XA_CARDINAL,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && pProperty
+ )
+ {
+ nCurrent = int(*reinterpret_cast<sal_Int32*>(pProperty));
+ XFree( pProperty );
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ }
+ return nCurrent;
+}
+
+/*
+ * WMAdaptor::getWindowWorkArea
+ */
+int WMAdaptor::getWindowWorkArea( ::Window aWindow ) const
+{
+ int nCurrent = -1;
+ if( m_aWMAtoms[ NET_WM_DESKTOP ] )
+ {
+ Atom aRealType = None;
+ int nFormat = 8;
+ unsigned long nItems = 0;
+ unsigned long nBytesLeft = 0;
+ unsigned char* pProperty = nullptr;
+ if( XGetWindowProperty( m_pDisplay,
+ aWindow,
+ m_aWMAtoms[ NET_WM_DESKTOP ],
+ 0, 1,
+ False,
+ XA_CARDINAL,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && pProperty
+ )
+ {
+ nCurrent = int(*reinterpret_cast<sal_Int32*>(pProperty));
+ XFree( pProperty );
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ }
+ return nCurrent;
+}
+
+/*
+ * WMAdaptor::getCurrentWorkArea
+ */
+// fixme: multi screen case
+void WMAdaptor::switchToWorkArea( int nWorkArea ) const
+{
+ if( ! getWMshouldSwitchWorkspace() )
+ return;
+
+ if( !m_aWMAtoms[ NET_CURRENT_DESKTOP ] )
+ return;
+
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.window = m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() );
+ aEvent.xclient.message_type = m_aWMAtoms[ NET_CURRENT_DESKTOP ];
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = nWorkArea;
+ aEvent.xclient.data.l[1] = 0;
+ aEvent.xclient.data.l[2] = 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &aEvent
+ );
+
+}
+
+/*
+ * WMAdaptor::frameIsMapping
+ */
+void WMAdaptor::frameIsMapping( X11SalFrame* ) const
+{
+}
+
+/*
+ * NetWMAdaptor::frameIsMapping
+ */
+void NetWMAdaptor::frameIsMapping( X11SalFrame* pFrame ) const
+{
+ setNetWMState( pFrame );
+}
+
+/*
+ * WMAdaptor::setUserTime
+ */
+void WMAdaptor::setUserTime( X11SalFrame*, tools::Long ) const
+{
+}
+
+/*
+ * NetWMAdaptor::setUserTime
+ */
+void NetWMAdaptor::setUserTime( X11SalFrame* i_pFrame, tools::Long i_nUserTime ) const
+{
+ if( m_aWMAtoms[NET_WM_USER_TIME] )
+ {
+ XChangeProperty( m_pDisplay,
+ i_pFrame->GetShellWindow(),
+ m_aWMAtoms[NET_WM_USER_TIME],
+ XA_CARDINAL,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(&i_nUserTime),
+ 1
+ );
+ }
+}
+
+/*
+ * WMAdaptor::setPID
+ */
+void WMAdaptor::setPID( X11SalFrame const * i_pFrame ) const
+{
+ if( !(m_aWMAtoms[NET_WM_PID]) )
+ return;
+
+ tools::Long nPID = static_cast<tools::Long>(getpid());
+ XChangeProperty( m_pDisplay,
+ i_pFrame->GetShellWindow(),
+ m_aWMAtoms[NET_WM_PID],
+ XA_CARDINAL,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(&nPID),
+ 1
+ );
+}
+
+/*
+* WMAdaptor::setClientMachine
+*/
+void WMAdaptor::setClientMachine( X11SalFrame const * i_pFrame ) const
+{
+ OString aWmClient( OUStringToOString( GetGenericUnixSalData()->GetHostname(), RTL_TEXTENCODING_ASCII_US ) );
+ XTextProperty aClientProp = { reinterpret_cast<unsigned char *>(const_cast<char *>(aWmClient.getStr())), XA_STRING, 8, sal::static_int_cast<unsigned long>( aWmClient.getLength() ) };
+ XSetWMClientMachine( m_pDisplay, i_pFrame->GetShellWindow(), &aClientProp );
+}
+
+void WMAdaptor::answerPing( X11SalFrame const * i_pFrame, XClientMessageEvent const * i_pEvent ) const
+{
+ if( !m_aWMAtoms[NET_WM_PING] ||
+ i_pEvent->message_type != m_aWMAtoms[ WM_PROTOCOLS ] ||
+ static_cast<Atom>(i_pEvent->data.l[0]) != m_aWMAtoms[ NET_WM_PING ] )
+ return;
+
+ XEvent aEvent;
+ aEvent.xclient = *i_pEvent;
+ aEvent.xclient.window = m_pSalDisplay->GetRootWindow( i_pFrame->GetScreenNumber() );
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( i_pFrame->GetScreenNumber() ),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &aEvent
+ );
+ XFlush( m_pDisplay );
+}
+
+void WMAdaptor::activateWindow( X11SalFrame const *pFrame, Time nTimestamp )
+{
+ if (!pFrame->bMapped_)
+ return;
+
+ XEvent aEvent;
+
+ aEvent.xclient.type = ClientMessage;
+ aEvent.xclient.window = pFrame->GetShellWindow();
+ aEvent.xclient.message_type = m_aWMAtoms[ NET_ACTIVE_WINDOW ];
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = 1;
+ aEvent.xclient.data.l[1] = nTimestamp;
+ aEvent.xclient.data.l[2] = None;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &aEvent );
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/desktopdetect/desktopdetector.cxx b/vcl/unx/generic/desktopdetect/desktopdetector.cxx
new file mode 100644
index 0000000000..8baeb303fe
--- /dev/null
+++ b/vcl/unx/generic/desktopdetect/desktopdetector.cxx
@@ -0,0 +1,264 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <X11/Xlib.h>
+
+#include <unx/desktops.hxx>
+
+#include <rtl/bootstrap.hxx>
+#include <rtl/process.h>
+#include <osl/thread.h>
+
+#include <vclpluginapi.h>
+
+#include <string.h>
+#include <comphelper/string.hxx>
+
+static bool is_gnome_desktop( Display* pDisplay )
+{
+
+ // warning: these checks are coincidental, GNOME does not
+ // explicitly advertise itself
+ if ( getenv( "GNOME_DESKTOP_SESSION_ID" ) )
+ return true;
+
+ bool ret = false;
+
+ Atom nAtom1 = XInternAtom( pDisplay, "GNOME_SM_PROXY", True );
+ Atom nAtom2 = XInternAtom( pDisplay, "NAUTILUS_DESKTOP_WINDOW_ID", True );
+ if( nAtom1 || nAtom2 )
+ {
+ int nProperties = 0;
+ Atom* pProperties = XListProperties( pDisplay, DefaultRootWindow( pDisplay ), &nProperties );
+ if( pProperties && nProperties )
+ {
+ for( int i = 0; i < nProperties; i++ )
+ if( pProperties[ i ] == nAtom1 ||
+ pProperties[ i ] == nAtom2 )
+ {
+ ret = true;
+ break;
+ }
+ XFree( pProperties );
+ }
+ }
+ if (ret)
+ return true;
+
+ Atom nUTFAtom = XInternAtom( pDisplay, "UTF8_STRING", True );
+ Atom nNetWMNameAtom = XInternAtom( pDisplay, "_NET_WM_NAME", True );
+ if( nUTFAtom && nNetWMNameAtom )
+ {
+ // another, more expensive check: search for a gnome-panel
+ ::Window aRoot, aParent, *pChildren = nullptr;
+ unsigned int nChildren = 0;
+ XQueryTree( pDisplay, DefaultRootWindow( pDisplay ),
+ &aRoot, &aParent, &pChildren, &nChildren );
+ if( pChildren && nChildren )
+ {
+ for( unsigned int i = 0; i < nChildren && ! ret; i++ )
+ {
+ Atom nType = None;
+ int nFormat = 0;
+ unsigned long nItems = 0, nBytes = 0;
+ unsigned char* pProp = nullptr;
+ XGetWindowProperty( pDisplay,
+ pChildren[i],
+ nNetWMNameAtom,
+ 0, 8,
+ False,
+ nUTFAtom,
+ &nType,
+ &nFormat,
+ &nItems,
+ &nBytes,
+ &pProp );
+ if( pProp && nType == nUTFAtom )
+ {
+ OString aWMName( reinterpret_cast<char*>(pProp) );
+ if (
+ (aWMName.equalsIgnoreAsciiCase("gnome-shell")) ||
+ (aWMName.equalsIgnoreAsciiCase("gnome-panel"))
+ )
+ {
+ ret = true;
+ }
+ }
+ if( pProp )
+ XFree( pProp );
+ }
+ XFree( pChildren );
+ }
+ }
+
+ return ret;
+}
+
+static bool is_plasma5_desktop()
+{
+ static const char* pFullVersion = getenv("KDE_FULL_SESSION");
+ static const char* pSessionVersion = getenv("KDE_SESSION_VERSION");
+ return pFullVersion && pSessionVersion && (0 == strcmp(pSessionVersion, "5"));
+}
+
+static bool is_plasma6_desktop()
+{
+ static const char* pFullVersion = getenv("KDE_FULL_SESSION");
+ static const char* pSessionVersion = getenv("KDE_SESSION_VERSION");
+ return pFullVersion && pSessionVersion && (0 == strcmp(pSessionVersion, "6"));
+}
+
+extern "C"
+{
+
+DESKTOP_DETECTOR_PUBLIC DesktopType get_desktop_environment()
+{
+ static const char *pOverride = getenv( "OOO_FORCE_DESKTOP" );
+
+ if ( pOverride && *pOverride )
+ {
+ OString aOver( pOverride );
+
+ if ( aOver.equalsIgnoreAsciiCase( "lxqt" ) )
+ return DESKTOP_LXQT;
+ if (aOver.equalsIgnoreAsciiCase("plasma5") || aOver.equalsIgnoreAsciiCase("plasma"))
+ return DESKTOP_PLASMA5;
+ if (aOver.equalsIgnoreAsciiCase("plasma6"))
+ return DESKTOP_PLASMA6;
+ if ( aOver.equalsIgnoreAsciiCase( "gnome" ) )
+ return DESKTOP_GNOME;
+ if ( aOver.equalsIgnoreAsciiCase( "gnome-wayland" ) )
+ return DESKTOP_GNOME;
+ if ( aOver.equalsIgnoreAsciiCase( "unity" ) )
+ return DESKTOP_UNITY;
+ if ( aOver.equalsIgnoreAsciiCase( "xfce" ) )
+ return DESKTOP_XFCE;
+ if ( aOver.equalsIgnoreAsciiCase( "mate" ) )
+ return DESKTOP_MATE;
+ if ( aOver.equalsIgnoreAsciiCase( "none" ) )
+ return DESKTOP_UNKNOWN;
+ }
+
+ OUString plugin;
+ rtl::Bootstrap::get("SAL_USE_VCLPLUGIN", plugin);
+
+ if (plugin == "svp")
+ return DESKTOP_NONE;
+
+ const char *pDesktop = getenv( "XDG_CURRENT_DESKTOP" );
+ if ( pDesktop )
+ {
+ OString aCurrentDesktop( pDesktop, strlen( pDesktop ) );
+
+ //it may be separated by colon ( e.g. unity:unity7:ubuntu )
+ std::vector<OUString> aSplitCurrentDesktop = comphelper::string::split(
+ OStringToOUString( aCurrentDesktop, RTL_TEXTENCODING_UTF8), ':');
+ for (const auto& rCurrentDesktopStr : aSplitCurrentDesktop)
+ {
+ if ( rCurrentDesktopStr.equalsIgnoreAsciiCase( "unity" ) )
+ return DESKTOP_UNITY;
+ else if ( rCurrentDesktopStr.equalsIgnoreAsciiCase( "gnome" ) )
+ return DESKTOP_GNOME;
+ else if ( rCurrentDesktopStr.equalsIgnoreAsciiCase( "lxqt" ) )
+ return DESKTOP_LXQT;
+ }
+ }
+
+ const char *pSession = getenv( "DESKTOP_SESSION" );
+ OString aDesktopSession;
+ if ( pSession )
+ aDesktopSession = OString( pSession, strlen( pSession ) );
+
+ // fast environment variable checks
+ if ( aDesktopSession.equalsIgnoreAsciiCase( "gnome" ) )
+ return DESKTOP_GNOME;
+ else if ( aDesktopSession.equalsIgnoreAsciiCase( "gnome-wayland" ) )
+ return DESKTOP_GNOME;
+ else if ( aDesktopSession.equalsIgnoreAsciiCase( "mate" ) )
+ return DESKTOP_MATE;
+ else if ( aDesktopSession.equalsIgnoreAsciiCase( "xfce" ) )
+ return DESKTOP_XFCE;
+ else if ( aDesktopSession.equalsIgnoreAsciiCase( "lxqt" ) )
+ return DESKTOP_LXQT;
+
+ if (is_plasma5_desktop())
+ return DESKTOP_PLASMA5;
+ if (is_plasma6_desktop())
+ return DESKTOP_PLASMA6;
+
+ // tdf#121275 if we still can't tell, and WAYLAND_DISPLAY
+ // is set, default to gtk3
+ const char* pWaylandStr = getenv("WAYLAND_DISPLAY");
+ if (pWaylandStr && *pWaylandStr)
+ return DESKTOP_GNOME;
+
+ // these guys can be slower, with X property fetches,
+ // round-trips etc. and so are done later.
+
+ // get display to connect to
+ const char* pDisplayStr = getenv( "DISPLAY" );
+
+ int nParams = rtl_getAppCommandArgCount();
+ OUString aParam;
+ OString aBParm;
+ for( int i = 0; i < nParams; i++ )
+ {
+ rtl_getAppCommandArg( i, &aParam.pData );
+ if( i < nParams-1 && (aParam == "-display" || aParam == "--display" ) )
+ {
+ rtl_getAppCommandArg( i+1, &aParam.pData );
+ aBParm = OUStringToOString( aParam, osl_getThreadTextEncoding() );
+ pDisplayStr = aBParm.getStr();
+ break;
+ }
+ }
+
+ // no server at all
+ if( ! pDisplayStr || !*pDisplayStr )
+ return DESKTOP_NONE;
+
+
+ /* #i92121# workaround deadlocks in the X11 implementation
+ */
+ static const char* pNoXInitThreads = getenv( "SAL_NO_XINITTHREADS" );
+ /* #i90094#
+ from now on we know that an X connection will be
+ established, so protect X against itself
+ */
+ if( ! ( pNoXInitThreads && *pNoXInitThreads ) )
+ XInitThreads();
+
+ Display* pDisplay = XOpenDisplay( pDisplayStr );
+ if( pDisplay == nullptr )
+ return DESKTOP_NONE;
+
+ DesktopType ret;
+ if ( is_gnome_desktop( pDisplay ) )
+ ret = DESKTOP_GNOME;
+ else
+ ret = DESKTOP_UNKNOWN;
+
+ XCloseDisplay( pDisplay );
+
+ return ret;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_clipboard.cxx b/vcl/unx/generic/dtrans/X11_clipboard.cxx
new file mode 100644
index 0000000000..f595de0d57
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_clipboard.cxx
@@ -0,0 +1,231 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <X11/Xatom.h>
+#include "X11_clipboard.hxx"
+#include "X11_transferable.hxx"
+#include <com/sun/star/datatransfer/clipboard/RenderingCapabilities.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <rtl/ref.hxx>
+#include <sal/log.hxx>
+
+#if OSL_DEBUG_LEVEL > 1
+#include <stdio.h>
+#endif
+
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::datatransfer::clipboard;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::awt;
+using namespace cppu;
+using namespace osl;
+using namespace x11;
+
+X11Clipboard::X11Clipboard( SelectionManager& rManager, Atom aSelection ) :
+ ::cppu::WeakComponentImplHelper<
+ css::datatransfer::clipboard::XSystemClipboard,
+ css::lang::XServiceInfo
+ >( rManager.getMutex() ),
+
+ m_xSelectionManager( &rManager ),
+ m_aSelection( aSelection )
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "creating instance of X11Clipboard (this="
+ << this << ").");
+#endif
+}
+
+css::uno::Reference<css::datatransfer::clipboard::XClipboard>
+X11Clipboard::create( SelectionManager& rManager, Atom aSelection )
+{
+ rtl::Reference<X11Clipboard> cb(new X11Clipboard(rManager, aSelection));
+ if( aSelection != None )
+ {
+ rManager.registerHandler(aSelection, *cb);
+ }
+ else
+ {
+ rManager.registerHandler(XA_PRIMARY, *cb);
+ rManager.registerHandler(rManager.getAtom("CLIPBOARD"), *cb);
+ }
+ return cb;
+}
+
+X11Clipboard::~X11Clipboard()
+{
+ MutexGuard aGuard( *Mutex::getGlobalMutex() );
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "shutting down instance of X11Clipboard (this="
+ << this
+ << ", Selection=\""
+ << m_xSelectionManager->getString( m_aSelection )
+ << "\").");
+#endif
+
+ if( m_aSelection != None )
+ m_xSelectionManager->deregisterHandler( m_aSelection );
+ else
+ {
+ m_xSelectionManager->deregisterHandler( XA_PRIMARY );
+ m_xSelectionManager->deregisterHandler( m_xSelectionManager->getAtom( "CLIPBOARD" ) );
+ }
+}
+
+void X11Clipboard::fireChangedContentsEvent()
+{
+ ClearableMutexGuard aGuard( m_xSelectionManager->getMutex() );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "X11Clipboard::fireChangedContentsEvent for "
+ << m_xSelectionManager->getString( m_aSelection )
+ << " (" << m_aListeners.size() << " listeners).");
+#endif
+ ::std::vector< Reference< XClipboardListener > > listeners( m_aListeners );
+ aGuard.clear();
+
+ ClipboardEvent aEvent(getXWeak(), m_aContents);
+ for (auto const& listener : listeners)
+ {
+ if( listener.is() )
+ listener->changedContents(aEvent);
+ }
+}
+
+void X11Clipboard::clearContents()
+{
+ ClearableMutexGuard aGuard(m_xSelectionManager->getMutex());
+ // protect against deletion during outside call
+ Reference< XClipboard > xThis( static_cast<XClipboard*>(this));
+ // copy member references on stack so they can be called
+ // without having the mutex
+ Reference< XClipboardOwner > xOwner( m_aOwner );
+ Reference< XTransferable > xKeepAlive( m_aContents );
+ // clear members
+ m_aOwner.clear();
+ m_aContents.clear();
+
+ // release the mutex
+ aGuard.clear();
+
+ // inform previous owner of lost ownership
+ if ( xOwner.is() )
+ xOwner->lostOwnership(xThis, m_aContents);
+}
+
+Reference< XTransferable > SAL_CALL X11Clipboard::getContents()
+{
+ MutexGuard aGuard(m_xSelectionManager->getMutex());
+
+ if( ! m_aContents.is() )
+ m_aContents = new X11Transferable( SelectionManager::get(), m_aSelection );
+ return m_aContents;
+}
+
+void SAL_CALL X11Clipboard::setContents(
+ const Reference< XTransferable >& xTrans,
+ const Reference< XClipboardOwner >& xClipboardOwner )
+{
+ // remember old values for callbacks before setting the new ones.
+ ClearableMutexGuard aGuard(m_xSelectionManager->getMutex());
+
+ Reference< XClipboardOwner > oldOwner( m_aOwner );
+ m_aOwner = xClipboardOwner;
+
+ Reference< XTransferable > oldContents( m_aContents );
+ m_aContents = xTrans;
+
+ aGuard.clear();
+
+ // for now request ownership for both selections
+ if( m_aSelection != None )
+ m_xSelectionManager->requestOwnership( m_aSelection );
+ else
+ {
+ m_xSelectionManager->requestOwnership( XA_PRIMARY );
+ m_xSelectionManager->requestOwnership( m_xSelectionManager->getAtom( "CLIPBOARD" ) );
+ }
+
+ // notify old owner on loss of ownership
+ if( oldOwner.is() )
+ oldOwner->lostOwnership(static_cast < XClipboard * > (this), oldContents);
+
+ // notify all listeners on content changes
+ fireChangedContentsEvent();
+}
+
+OUString SAL_CALL X11Clipboard::getName()
+{
+ return m_xSelectionManager->getString( m_aSelection );
+}
+
+sal_Int8 SAL_CALL X11Clipboard::getRenderingCapabilities()
+{
+ return RenderingCapabilities::Delayed;
+}
+
+void SAL_CALL X11Clipboard::addClipboardListener( const Reference< XClipboardListener >& listener )
+{
+ MutexGuard aGuard( m_xSelectionManager->getMutex() );
+ m_aListeners.push_back( listener );
+}
+
+void SAL_CALL X11Clipboard::removeClipboardListener( const Reference< XClipboardListener >& listener )
+{
+ MutexGuard aGuard( m_xSelectionManager->getMutex() );
+ std::erase(m_aListeners, listener);
+}
+
+Reference< XTransferable > X11Clipboard::getTransferable()
+{
+ return getContents();
+}
+
+void X11Clipboard::clearTransferable()
+{
+ clearContents();
+}
+
+void X11Clipboard::fireContentsChanged()
+{
+ fireChangedContentsEvent();
+}
+
+Reference< XInterface > X11Clipboard::getReference() noexcept
+{
+ return getXWeak();
+}
+
+OUString SAL_CALL X11Clipboard::getImplementationName( )
+{
+ return X11_CLIPBOARD_IMPLEMENTATION_NAME;
+}
+
+sal_Bool SAL_CALL X11Clipboard::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SAL_CALL X11Clipboard::getSupportedServiceNames( )
+{
+ return X11Clipboard_getSupportedServiceNames();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_clipboard.hxx b/vcl/unx/generic/dtrans/X11_clipboard.hxx
new file mode 100644
index 0000000000..4fd492154c
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_clipboard.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include "X11_selection.hxx"
+
+#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
+#include <cppuhelper/compbase.hxx>
+
+inline constexpr OUString X11_CLIPBOARD_IMPLEMENTATION_NAME = u"com.sun.star.datatransfer.X11ClipboardSupport"_ustr;
+
+namespace x11 {
+
+ class X11Clipboard :
+ public ::cppu::WeakComponentImplHelper <
+ css::datatransfer::clipboard::XSystemClipboard,
+ css::lang::XServiceInfo
+ >,
+ public SelectionAdaptor
+ {
+ css::uno::Reference< css::datatransfer::XTransferable > m_aContents;
+ css::uno::Reference< css::datatransfer::clipboard::XClipboardOwner > m_aOwner;
+
+ rtl::Reference<SelectionManager> m_xSelectionManager;
+ ::std::vector< css::uno::Reference< css::datatransfer::clipboard::XClipboardListener > > m_aListeners;
+ Atom m_aSelection;
+
+ X11Clipboard( SelectionManager& rManager, Atom aSelection );
+
+ friend class SelectionManager;
+
+ void fireChangedContentsEvent();
+ void clearContents();
+
+ public:
+
+ static css::uno::Reference<css::datatransfer::clipboard::XClipboard>
+ create( SelectionManager& rManager, Atom aSelection );
+
+ virtual ~X11Clipboard() 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;
+
+ /*
+ * XClipboard
+ */
+
+ virtual css::uno::Reference< css::datatransfer::XTransferable > SAL_CALL getContents() override;
+
+ virtual void SAL_CALL setContents(
+ const css::uno::Reference< css::datatransfer::XTransferable >& xTrans,
+ const css::uno::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 css::uno::Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
+
+ virtual void SAL_CALL removeClipboardListener(
+ const css::uno::Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
+
+ /*
+ * SelectionAdaptor
+ */
+ virtual css::uno::Reference< css::datatransfer::XTransferable > getTransferable() override;
+ virtual void clearTransferable() override;
+ virtual void fireContentsChanged() override;
+ virtual css::uno::Reference< css::uno::XInterface > getReference() noexcept override;
+ };
+
+ css::uno::Sequence< OUString > X11Clipboard_getSupportedServiceNames();
+ css::uno::Reference< css::uno::XInterface > SAL_CALL X11Clipboard_createInstance(
+ const css::uno::Reference< css::lang::XMultiServiceFactory > & xMultiServiceFactory);
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_dndcontext.cxx b/vcl/unx/generic/dtrans/X11_dndcontext.cxx
new file mode 100644
index 0000000000..638c47387d
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_dndcontext.cxx
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "X11_dndcontext.hxx"
+#include "X11_selection.hxx"
+
+using namespace cppu;
+using namespace x11;
+
+/*
+ * DropTargetDropContext
+ */
+
+DropTargetDropContext::DropTargetDropContext(
+ ::Window aDropWindow,
+ SelectionManager& rManager ) :
+ m_aDropWindow( aDropWindow ),
+ m_xManager( &rManager )
+{
+}
+
+DropTargetDropContext::~DropTargetDropContext()
+{
+}
+
+void DropTargetDropContext::acceptDrop( sal_Int8 dragOperation )
+{
+ m_xManager->accept( dragOperation, m_aDropWindow );
+}
+
+void DropTargetDropContext::rejectDrop()
+{
+ m_xManager->reject( m_aDropWindow );
+}
+
+void DropTargetDropContext::dropComplete( sal_Bool success )
+{
+ m_xManager->dropComplete( success, m_aDropWindow );
+}
+
+/*
+ * DropTargetDragContext
+ */
+
+DropTargetDragContext::DropTargetDragContext(
+ ::Window aDropWindow,
+ SelectionManager& rManager ) :
+ m_aDropWindow( aDropWindow ),
+ m_xManager( &rManager )
+{
+}
+
+DropTargetDragContext::~DropTargetDragContext()
+{
+}
+
+void DropTargetDragContext::acceptDrag( sal_Int8 dragOperation )
+{
+ m_xManager->accept( dragOperation, m_aDropWindow );
+}
+
+void DropTargetDragContext::rejectDrag()
+{
+ m_xManager->reject( m_aDropWindow );
+}
+
+/*
+ * DragSourceContext
+ */
+
+DragSourceContext::DragSourceContext(
+ ::Window aDropWindow,
+ SelectionManager& rManager ) :
+ m_aDropWindow( aDropWindow ),
+ m_xManager( &rManager )
+{
+}
+
+DragSourceContext::~DragSourceContext()
+{
+}
+
+sal_Int32 DragSourceContext::getCurrentCursor()
+{
+ return m_xManager->getCurrentCursor();
+}
+
+void DragSourceContext::setCursor( sal_Int32 cursorId )
+{
+ m_xManager->setCursor( cursorId, m_aDropWindow );
+}
+
+void DragSourceContext::setImage( sal_Int32 )
+{
+}
+
+void DragSourceContext::transferablesFlavorsChanged()
+{
+ m_xManager->transferablesFlavorsChanged();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_dndcontext.hxx b/vcl/unx/generic/dtrans/X11_dndcontext.hxx
new file mode 100644
index 0000000000..71283ea641
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_dndcontext.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 .
+ */
+
+#pragma once
+
+#include <com/sun/star/datatransfer/dnd/XDragSourceContext.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTargetDropContext.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTargetDragContext.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <rtl/ref.hxx>
+
+#include <X11/X.h>
+
+namespace x11 {
+
+ class SelectionManager;
+
+ class DropTargetDropContext :
+ public ::cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDropContext>
+ {
+ ::Window m_aDropWindow;
+ rtl::Reference<SelectionManager> m_xManager;
+ public:
+ DropTargetDropContext( ::Window, SelectionManager& );
+ virtual ~DropTargetDropContext() override;
+
+ // XDropTargetDropContext
+ virtual void SAL_CALL acceptDrop( sal_Int8 dragOperation ) override;
+ virtual void SAL_CALL rejectDrop() override;
+ virtual void SAL_CALL dropComplete( sal_Bool success ) override;
+ };
+
+ class DropTargetDragContext :
+ public ::cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDragContext>
+ {
+ ::Window m_aDropWindow;
+ rtl::Reference<SelectionManager> m_xManager;
+ public:
+ DropTargetDragContext( ::Window, SelectionManager& );
+ virtual ~DropTargetDragContext() override;
+
+ // XDropTargetDragContext
+ virtual void SAL_CALL acceptDrag( sal_Int8 dragOperation ) override;
+ virtual void SAL_CALL rejectDrag() override;
+ };
+
+ class DragSourceContext :
+ public ::cppu::WeakImplHelper<css::datatransfer::dnd::XDragSourceContext>
+ {
+ ::Window m_aDropWindow;
+ rtl::Reference<SelectionManager> m_xManager;
+ public:
+ DragSourceContext( ::Window, SelectionManager& );
+ virtual ~DragSourceContext() override;
+
+ // XDragSourceContext
+ virtual sal_Int32 SAL_CALL getCurrentCursor() override;
+ virtual void SAL_CALL setCursor( sal_Int32 cursorId ) override;
+ virtual void SAL_CALL setImage( sal_Int32 imageId ) override;
+ virtual void SAL_CALL transferablesFlavorsChanged() override;
+ };
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_droptarget.cxx b/vcl/unx/generic/dtrans/X11_droptarget.cxx
new file mode 100644
index 0000000000..e026ad1ab1
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_droptarget.cxx
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 "X11_selection.hxx"
+
+using namespace x11;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::awt;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::datatransfer::dnd;
+
+DropTarget::DropTarget() :
+ ::cppu::WeakComponentImplHelper<
+ XDropTarget,
+ XInitialization,
+ XServiceInfo
+ >( m_aMutex ),
+ m_bActive( false ),
+ m_nDefaultActions( 0 ),
+ m_aTargetWindow( None )
+{
+}
+
+DropTarget::~DropTarget()
+{
+ if( m_xSelectionManager.is() )
+ m_xSelectionManager->deregisterDropTarget( m_aTargetWindow );
+}
+
+void DropTarget::initialize( const Sequence< Any >& arguments )
+{
+ if( arguments.getLength() <= 1 )
+ return;
+
+ OUString aDisplayName;
+ Reference< XDisplayConnection > xConn;
+ arguments.getConstArray()[0] >>= xConn;
+ if( xConn.is() )
+ {
+ Any aIdentifier;
+ aIdentifier >>= aDisplayName;
+ }
+
+ m_xSelectionManager = &SelectionManager::get( aDisplayName );
+ m_xSelectionManager->initialize( arguments );
+
+ if( m_xSelectionManager->getDisplay() ) // #136582# sanity check
+ {
+ sal_IntPtr aWindow = None;
+ arguments.getConstArray()[1] >>= aWindow;
+ m_xSelectionManager->registerDropTarget( aWindow, this );
+ m_aTargetWindow = aWindow;
+ m_bActive = true;
+ }
+}
+
+void DropTarget::addDropTargetListener( const Reference< XDropTargetListener >& xListener )
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
+
+ m_aListeners.push_back( xListener );
+}
+
+void DropTarget::removeDropTargetListener( const Reference< XDropTargetListener >& xListener )
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
+
+ std::erase(m_aListeners, xListener);
+}
+
+sal_Bool DropTarget::isActive()
+{
+ return m_bActive;
+}
+
+void DropTarget::setActive( sal_Bool active )
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
+
+ m_bActive = active;
+}
+
+sal_Int8 DropTarget::getDefaultActions()
+{
+ return m_nDefaultActions;
+}
+
+void DropTarget::setDefaultActions( sal_Int8 actions )
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
+
+ m_nDefaultActions = actions;
+}
+
+void DropTarget::drop( const DropTargetDropEvent& dtde ) noexcept
+{
+ osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
+ std::vector< Reference< XDropTargetListener > > aListeners( m_aListeners );
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ {
+ listener->drop(dtde);
+ }
+}
+
+void DropTarget::dragEnter( const DropTargetDragEnterEvent& dtde ) noexcept
+{
+ osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
+ std::vector< Reference< XDropTargetListener > > aListeners( m_aListeners );
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ {
+ listener->dragEnter(dtde);
+ }
+}
+
+void DropTarget::dragExit( const DropTargetEvent& dte ) noexcept
+{
+ osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
+ std::vector< Reference< XDropTargetListener > > aListeners( m_aListeners );
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ {
+ listener->dragExit(dte);
+ }
+}
+
+void DropTarget::dragOver( const DropTargetDragEvent& dtde ) noexcept
+{
+ osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
+ std::vector< Reference< XDropTargetListener > > aListeners( m_aListeners );
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ {
+ listener->dragOver(dtde);
+ }
+}
+
+// XServiceInfo
+OUString DropTarget::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.XdndDropTarget";
+}
+
+sal_Bool DropTarget::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > DropTarget::getSupportedServiceNames()
+{
+ return Xdnd_dropTarget_getSupportedServiceNames();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_selection.cxx b/vcl/unx/generic/dtrans/X11_selection.cxx
new file mode 100644
index 0000000000..823b77982f
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_selection.cxx
@@ -0,0 +1,4157 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <cstdlib>
+#include <thread>
+
+#include <unx/saldisp.hxx>
+
+#include <unistd.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/XKBlib.h>
+#include <X11/Xatom.h>
+#include <X11/keysym.h>
+
+#include <poll.h>
+
+#include <sal/macros.h>
+
+#include "X11_selection.hxx"
+#include "X11_clipboard.hxx"
+#include "X11_transferable.hxx"
+#include "X11_dndcontext.hxx"
+#include "bmp.hxx"
+
+#include <vcl/svapp.hxx>
+#include <o3tl/string_view.hxx>
+
+// pointer bitmaps
+#include "copydata_curs.h"
+#include "copydata_mask.h"
+#include "movedata_curs.h"
+#include "movedata_mask.h"
+#include "linkdata_curs.h"
+#include "linkdata_mask.h"
+#include "nodrop_curs.h"
+#include "nodrop_mask.h"
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/awt/MouseEvent.hpp>
+#include <com/sun/star/awt/MouseButton.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <rtl/tencinfo.h>
+#include <rtl/ustrbuf.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/solarmutex.hxx>
+
+#include <cppuhelper/supportsservice.hxx>
+#include <algorithm>
+
+constexpr auto DRAG_EVENT_MASK = ButtonPressMask |
+ ButtonReleaseMask |
+ PointerMotionMask |
+ EnterWindowMask |
+ LeaveWindowMask;
+
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::datatransfer::dnd;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::awt;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::frame;
+using namespace cppu;
+
+using namespace x11;
+
+// stubs to satisfy solaris compiler's rather rigid linking warning
+extern "C"
+{
+ static void call_SelectionManager_run( void * pMgr )
+ {
+ SelectionManager::run( pMgr );
+ }
+
+ static void call_SelectionManager_runDragExecute( void * pMgr )
+ {
+ osl_setThreadName("SelectionManager::runDragExecute()");
+ SelectionManager::runDragExecute( pMgr );
+ }
+}
+
+const tools::Long nXdndProtocolRevision = 5;
+
+namespace {
+
+// mapping between mime types (or what the office thinks of mime types)
+// and X convention types
+struct NativeTypeEntry
+{
+ Atom nAtom;
+ const char* pType; // Mime encoding on our side
+ const char* pNativeType; // string corresponding to nAtom for the case of nAtom being uninitialized
+ int nFormat; // the corresponding format
+};
+
+}
+
+// the convention for Xdnd is mime types as specified by the corresponding
+// RFC's with the addition that text/plain without charset tag contains iso8859-1
+// sadly some applications (e.g. gtk) do not honor the mimetype only rule,
+// so for compatibility add UTF8_STRING
+static NativeTypeEntry aXdndConversionTab[] =
+{
+ { 0, "text/plain;charset=iso8859-1", "text/plain", 8 },
+ { 0, "text/plain;charset=utf-8", "UTF8_STRING", 8 }
+};
+
+// for clipboard and primary selections there is only a convention for text
+// that the encoding name of the text is taken as type in all capitalized letters
+static NativeTypeEntry aNativeConversionTab[] =
+{
+ { 0, "text/plain;charset=utf-16", "ISO10646-1", 16 },
+ { 0, "text/plain;charset=utf-8", "UTF8_STRING", 8 },
+ { 0, "text/plain;charset=utf-8", "UTF-8", 8 },
+ { 0, "text/plain;charset=utf-8", "text/plain;charset=UTF-8", 8 },
+ // ISO encodings
+ { 0, "text/plain;charset=iso8859-2", "ISO8859-2", 8 },
+ { 0, "text/plain;charset=iso8859-3", "ISO8859-3", 8 },
+ { 0, "text/plain;charset=iso8859-4", "ISO8859-4", 8 },
+ { 0, "text/plain;charset=iso8859-5", "ISO8859-5", 8 },
+ { 0, "text/plain;charset=iso8859-6", "ISO8859-6", 8 },
+ { 0, "text/plain;charset=iso8859-7", "ISO8859-7", 8 },
+ { 0, "text/plain;charset=iso8859-8", "ISO8859-8", 8 },
+ { 0, "text/plain;charset=iso8859-9", "ISO8859-9", 8 },
+ { 0, "text/plain;charset=iso8859-10", "ISO8859-10", 8 },
+ { 0, "text/plain;charset=iso8859-13", "ISO8859-13", 8 },
+ { 0, "text/plain;charset=iso8859-14", "ISO8859-14", 8 },
+ { 0, "text/plain;charset=iso8859-15", "ISO8859-15", 8 },
+ // asian encodings
+ { 0, "text/plain;charset=jisx0201.1976-0", "JISX0201.1976-0", 8 },
+ { 0, "text/plain;charset=jisx0208.1983-0", "JISX0208.1983-0", 8 },
+ { 0, "text/plain;charset=jisx0208.1990-0", "JISX0208.1990-0", 8 },
+ { 0, "text/plain;charset=jisx0212.1990-0", "JISX0212.1990-0", 8 },
+ { 0, "text/plain;charset=gb2312.1980-0", "GB2312.1980-0", 8 },
+ { 0, "text/plain;charset=ksc5601.1992-0", "KSC5601.1992-0", 8 },
+ // eastern european encodings
+ { 0, "text/plain;charset=koi8-r", "KOI8-R", 8 },
+ { 0, "text/plain;charset=koi8-u", "KOI8-U", 8 },
+ // String (== iso8859-1)
+ { XA_STRING, "text/plain;charset=iso8859-1", "STRING", 8 },
+ // special for compound text
+ { 0, "text/plain;charset=compound_text", "COMPOUND_TEXT", 8 },
+
+ // PIXMAP
+ { XA_PIXMAP, "image/bmp", "PIXMAP", 32 }
+};
+
+rtl_TextEncoding x11::getTextPlainEncoding( const OUString& rMimeType )
+{
+ rtl_TextEncoding aEncoding = RTL_TEXTENCODING_DONTKNOW;
+ OUString aMimeType( rMimeType.toAsciiLowerCase() );
+ sal_Int32 nIndex = 0;
+ if( o3tl::getToken(aMimeType, 0, ';', nIndex ) == u"text/plain" )
+ {
+ if( aMimeType.getLength() == 10 ) // only "text/plain"
+ aEncoding = RTL_TEXTENCODING_ISO_8859_1;
+ else
+ {
+ while( nIndex != -1 )
+ {
+ OUString aToken = aMimeType.getToken( 0, ';', nIndex );
+ sal_Int32 nPos = 0;
+ if( o3tl::getToken(aToken, 0, '=', nPos ) == u"charset" )
+ {
+ OString aEncToken = OUStringToOString( o3tl::getToken(aToken, 0, '=', nPos ), RTL_TEXTENCODING_ISO_8859_1 );
+ aEncoding = rtl_getTextEncodingFromUnixCharset( aEncToken.getStr() );
+ if( aEncoding == RTL_TEXTENCODING_DONTKNOW )
+ {
+ if( aEncToken.equalsIgnoreAsciiCase( "utf-8" ) )
+ aEncoding = RTL_TEXTENCODING_UTF8;
+ }
+ if( aEncoding != RTL_TEXTENCODING_DONTKNOW )
+ break;
+ }
+ }
+ }
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN_IF(aEncoding == RTL_TEXTENCODING_DONTKNOW,
+ "vcl.unx.dtrans", "getTextPlainEncoding( "
+ << rMimeType << " ) failed.");
+#endif
+ return aEncoding;
+}
+
+std::unordered_map< OUString, SelectionManager* >& SelectionManager::getInstances()
+{
+ static std::unordered_map< OUString, SelectionManager* > aInstances;
+ return aInstances;
+}
+
+SelectionManager::SelectionManager() :
+ m_nIncrementalThreshold( 15*1024 ),
+ m_pDisplay( nullptr ),
+ m_aThread( nullptr ),
+ m_aDragExecuteThread( nullptr ),
+ m_aWindow( None ),
+ m_nSelectionTimeout( 0 ),
+ m_nSelectionTimestamp( CurrentTime ),
+ m_bDropEnterSent( true ),
+ m_aCurrentDropWindow( None ),
+ m_nDropTime( None ),
+ m_nLastDropAction( 0 ),
+ m_nLastX( 0 ),
+ m_nLastY( 0 ),
+ m_bDropWaitingForCompletion( false ),
+ m_aDropWindow( None ),
+ m_aDropProxy( None ),
+ m_aDragSourceWindow( None ),
+ m_nLastDragX( 0 ),
+ m_nLastDragY( 0 ),
+ m_nNoPosX( 0 ),
+ m_nNoPosY( 0 ),
+ m_nNoPosWidth( 0 ),
+ m_nNoPosHeight( 0 ),
+ m_nDragButton( 0 ),
+ m_nUserDragAction( 0 ),
+ m_nTargetAcceptAction( 0 ),
+ m_nSourceActions( 0 ),
+ m_bLastDropAccepted( false ),
+ m_bDropSuccess( false ),
+ m_bDropSent( false ),
+ m_nDropTimeout( 0 ),
+ m_bWaitingForPrimaryConversion( false ),
+ m_aMoveCursor( None ),
+ m_aCopyCursor( None ),
+ m_aLinkCursor( None ),
+ m_aNoneCursor( None ),
+ m_aCurrentCursor( None ),
+ m_nCurrentProtocolVersion( nXdndProtocolRevision ),
+ m_nTARGETSAtom( None ),
+ m_nTIMESTAMPAtom( None ),
+ m_nTEXTAtom( None ),
+ m_nINCRAtom( None ),
+ m_nCOMPOUNDAtom( None ),
+ m_nMULTIPLEAtom( None ),
+ m_nImageBmpAtom( None ),
+ m_nXdndAware( None ),
+ m_nXdndEnter( None ),
+ m_nXdndLeave( None ),
+ m_nXdndPosition( None ),
+ m_nXdndStatus( None ),
+ m_nXdndDrop( None ),
+ m_nXdndFinished( None ),
+ m_nXdndSelection( None ),
+ m_nXdndTypeList( None ),
+ m_nXdndProxy( None ),
+ m_nXdndActionCopy( None ),
+ m_nXdndActionMove( None ),
+ m_nXdndActionLink( None ),
+ m_nXdndActionAsk( None ),
+ m_bShutDown( false )
+{
+ memset(&m_aDropEnterEvent, 0, sizeof(m_aDropEnterEvent));
+ m_EndThreadPipe[0] = 0;
+ m_EndThreadPipe[1] = 0;
+ m_aDragRunning.reset();
+}
+
+Cursor SelectionManager::createCursor( const unsigned char* pPointerData, const unsigned char* pMaskData, int width, int height, int hotX, int hotY )
+{
+ Pixmap aPointer;
+ Pixmap aMask;
+ XColor aBlack, aWhite;
+
+ aBlack.pixel = BlackPixel( m_pDisplay, 0 );
+ aBlack.red = aBlack.green = aBlack.blue = 0;
+ aBlack.flags = DoRed | DoGreen | DoBlue;
+
+ aWhite.pixel = WhitePixel( m_pDisplay, 0 );
+ aWhite.red = aWhite.green = aWhite.blue = 0xffff;
+ aWhite.flags = DoRed | DoGreen | DoBlue;
+
+ aPointer =
+ XCreateBitmapFromData( m_pDisplay,
+ m_aWindow,
+ reinterpret_cast<const char*>(pPointerData),
+ width,
+ height );
+ aMask
+ = XCreateBitmapFromData( m_pDisplay,
+ m_aWindow,
+ reinterpret_cast<const char*>(pMaskData),
+ width,
+ height );
+ Cursor aCursor =
+ XCreatePixmapCursor( m_pDisplay, aPointer, aMask,
+ &aBlack, &aWhite,
+ hotX,
+ hotY );
+ XFreePixmap( m_pDisplay, aPointer );
+ XFreePixmap( m_pDisplay, aMask );
+
+ return aCursor;
+}
+
+void SelectionManager::initialize( const Sequence< Any >& arguments )
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if( ! m_xDisplayConnection.is() )
+ {
+ /*
+ * first argument must be a css::awt::XDisplayConnection
+ * from this we will get the XEvents of the vcl event loop by
+ * registering us as XEventHandler on it.
+ *
+ * implementor's note:
+ * FIXME:
+ * finally the clipboard and XDND service is back in the module it belongs
+ * now cleanup and sharing of resources with the normal vcl event loop
+ * needs to be added. The display used would be that of the normal event loop
+ * and synchronization should be done via the SolarMutex.
+ */
+ if( arguments.hasElements() )
+ arguments.getConstArray()[0] >>= m_xDisplayConnection;
+ if( ! m_xDisplayConnection.is() )
+ {
+ }
+ else
+ m_xDisplayConnection->addEventHandler( Any(), this, ~0 );
+ }
+
+ if( m_pDisplay )
+ return;
+
+ OUString aUDisplay;
+ if( m_xDisplayConnection.is() )
+ {
+ Any aIdentifier = m_xDisplayConnection->getIdentifier();
+ aIdentifier >>= aUDisplay;
+ }
+
+ OString aDisplayName( OUStringToOString( aUDisplay, RTL_TEXTENCODING_ISO_8859_1 ) );
+
+ m_pDisplay = XOpenDisplay( aDisplayName.isEmpty() ? nullptr : aDisplayName.getStr());
+
+ if( !m_pDisplay )
+ return;
+
+#ifdef SYNCHRONIZE
+ XSynchronize( m_pDisplay, True );
+#endif
+ // special targets
+ m_nTARGETSAtom = getAtom( "TARGETS" );
+ m_nTIMESTAMPAtom = getAtom( "TIMESTAMP" );
+ m_nTEXTAtom = getAtom( "TEXT" );
+ m_nINCRAtom = getAtom( "INCR" );
+ m_nCOMPOUNDAtom = getAtom( "COMPOUND_TEXT" );
+ m_nMULTIPLEAtom = getAtom( "MULTIPLE" );
+ m_nImageBmpAtom = getAtom( "image/bmp" );
+
+ // Atoms for Xdnd protocol
+ m_nXdndAware = getAtom( "XdndAware" );
+ m_nXdndEnter = getAtom( "XdndEnter" );
+ m_nXdndLeave = getAtom( "XdndLeave" );
+ m_nXdndPosition = getAtom( "XdndPosition" );
+ m_nXdndStatus = getAtom( "XdndStatus" );
+ m_nXdndDrop = getAtom( "XdndDrop" );
+ m_nXdndFinished = getAtom( "XdndFinished" );
+ m_nXdndSelection = getAtom( "XdndSelection" );
+ m_nXdndTypeList = getAtom( "XdndTypeList" );
+ m_nXdndProxy = getAtom( "XdndProxy" );
+ m_nXdndActionCopy = getAtom( "XdndActionCopy" );
+ m_nXdndActionMove = getAtom( "XdndActionMove" );
+ m_nXdndActionLink = getAtom( "XdndActionLink" );
+ m_nXdndActionAsk = getAtom( "XdndActionAsk" );
+
+ // initialize map with member none
+ m_aAtomToString[ 0 ]= "None";
+ m_aAtomToString[ XA_PRIMARY ] = "PRIMARY";
+
+ // create a (invisible) message window
+ m_aWindow = XCreateSimpleWindow( m_pDisplay, DefaultRootWindow( m_pDisplay ),
+ 10, 10, 10, 10, 0, 0, 1 );
+
+ // initialize threshold for incremental transfers
+ // ICCCM says it should be smaller that the max request size
+ // which in turn is guaranteed to be at least 16k bytes
+ m_nIncrementalThreshold = XMaxRequestSize( m_pDisplay ) - 1024;
+
+ if( !m_aWindow )
+ return;
+
+ // initialize default cursors
+ m_aMoveCursor = createCursor( movedata_curs_bits,
+ movedata_mask_bits,
+ movedata_curs_width,
+ movedata_curs_height,
+ movedata_curs_x_hot,
+ movedata_curs_y_hot );
+ m_aCopyCursor = createCursor( copydata_curs_bits,
+ copydata_mask_bits,
+ copydata_curs_width,
+ copydata_curs_height,
+ copydata_curs_x_hot,
+ copydata_curs_y_hot );
+ m_aLinkCursor = createCursor( linkdata_curs_bits,
+ linkdata_mask_bits,
+ linkdata_curs_width,
+ linkdata_curs_height,
+ linkdata_curs_x_hot,
+ linkdata_curs_y_hot );
+ m_aNoneCursor = createCursor( nodrop_curs_bits,
+ nodrop_mask_bits,
+ nodrop_curs_width,
+ nodrop_curs_height,
+ nodrop_curs_x_hot,
+ nodrop_curs_y_hot );
+
+ // just interested in SelectionClear/Notify/Request and PropertyChange
+ XSelectInput( m_pDisplay, m_aWindow, PropertyChangeMask );
+ // create the transferable for Drag operations
+ m_xDropTransferable = new X11Transferable( *this, m_nXdndSelection );
+ registerHandler( m_nXdndSelection, *this );
+
+ m_aThread = osl_createSuspendedThread( call_SelectionManager_run, this );
+ if( m_aThread )
+ osl_resumeThread( m_aThread );
+#if OSL_DEBUG_LEVEL > 1
+ else
+ SAL_WARN("vcl.unx.dtrans", "SelectionManager::initialize: "
+ << "creation of dispatch thread failed !.");
+#endif
+
+ if (pipe(m_EndThreadPipe) != 0) {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.unx.dtrans", "Failed to create endThreadPipe.");
+#endif
+ m_EndThreadPipe[0] = m_EndThreadPipe[1] = 0;
+ }
+}
+
+SelectionManager::~SelectionManager()
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "SelectionManager::~SelectionManager ("
+ << (m_pDisplay ? DisplayString(m_pDisplay) : "no display")
+ << ").");
+#endif
+ {
+ osl::MutexGuard aGuard( *osl::Mutex::getGlobalMutex() );
+
+ auto it = std::find_if(getInstances().begin(), getInstances().end(),
+ [&](const std::pair< OUString, SelectionManager* >& rInstance) { return rInstance.second == this; });
+ if( it != getInstances().end() )
+ getInstances().erase( it );
+ }
+
+ if( m_aThread )
+ {
+ osl_terminateThread( m_aThread );
+ osl_joinWithThread( m_aThread );
+ osl_destroyThread( m_aThread );
+ }
+
+ if( m_aDragExecuteThread )
+ {
+ osl_terminateThread( m_aDragExecuteThread );
+ osl_joinWithThread( m_aDragExecuteThread );
+ m_aDragExecuteThread = nullptr;
+ // thread handle is freed in dragDoDispatch()
+ }
+
+ osl::MutexGuard aGuard(m_aMutex);
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "shutting down SelectionManager.");
+#endif
+
+ if( !m_pDisplay )
+ return;
+
+ deregisterHandler( m_nXdndSelection );
+ // destroy message window
+ if( m_aWindow )
+ XDestroyWindow( m_pDisplay, m_aWindow );
+ // release cursors
+ if (m_aMoveCursor != None)
+ XFreeCursor(m_pDisplay, m_aMoveCursor);
+ if (m_aCopyCursor != None)
+ XFreeCursor(m_pDisplay, m_aCopyCursor);
+ if (m_aLinkCursor != None)
+ XFreeCursor(m_pDisplay, m_aLinkCursor);
+ if (m_aNoneCursor != None)
+ XFreeCursor(m_pDisplay, m_aNoneCursor);
+
+ // paranoia setting, the drag thread should have
+ // done that already
+ XUngrabPointer( m_pDisplay, CurrentTime );
+ XUngrabKeyboard( m_pDisplay, CurrentTime );
+
+ XCloseDisplay( m_pDisplay );
+}
+
+SelectionAdaptor* SelectionManager::getAdaptor( Atom selection )
+{
+ std::unordered_map< Atom, Selection* >::iterator it =
+ m_aSelections.find( selection );
+ return it != m_aSelections.end() ? it->second->m_pAdaptor : nullptr;
+}
+
+OUString SelectionManager::convertFromCompound( const char* pText, int nLen )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+ OUStringBuffer aRet;
+ if( nLen < 0 )
+ nLen = strlen( pText );
+
+ char** pTextList = nullptr;
+ int nTexts = 0;
+
+ XTextProperty aProp;
+ aProp.value = reinterpret_cast<unsigned char *>(const_cast<char *>(pText));
+ aProp.encoding = m_nCOMPOUNDAtom;
+ aProp.format = 8;
+ aProp.nitems = nLen;
+ XmbTextPropertyToTextList( m_pDisplay,
+ &aProp,
+ &pTextList,
+ &nTexts );
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ for( int i = 0; i < nTexts; i++ )
+ aRet.append(OStringToOUString( pTextList[i], aEncoding ));
+
+ if( pTextList )
+ XFreeStringList( pTextList );
+
+ return aRet.makeStringAndClear();
+}
+
+OString SelectionManager::convertToCompound( const OUString& rText )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+ XTextProperty aProp;
+ aProp.value = nullptr;
+ aProp.encoding = XA_STRING;
+ aProp.format = 8;
+ aProp.nitems = 0;
+
+ OString aRet( rText.getStr(), rText.getLength(), osl_getThreadTextEncoding() );
+ char* pT = const_cast<char*>(aRet.getStr());
+
+ XmbTextListToTextProperty( m_pDisplay,
+ &pT,
+ 1,
+ XCompoundTextStyle,
+ &aProp );
+ if( aProp.value )
+ {
+ aRet = reinterpret_cast<char*>(aProp.value);
+ XFree( aProp.value );
+#ifdef __sun
+ /*
+ * for currently unknown reasons XmbTextListToTextProperty on Solaris returns
+ * no data in ISO8859-n encodings (at least for n = 1, 15)
+ * in these encodings the directly converted text does the
+ * trick, also.
+ */
+ if( aRet.isEmpty() && !rText.isEmpty() )
+ aRet = OUStringToOString( rText, osl_getThreadTextEncoding() );
+#endif
+ }
+ else
+ aRet.clear();
+
+ return aRet;
+}
+
+bool SelectionManager::convertData(
+ const css::uno::Reference< XTransferable >& xTransferable,
+ Atom nType,
+ Atom nSelection,
+ int& rFormat,
+ Sequence< sal_Int8 >& rData )
+{
+ bool bSuccess = false;
+
+ if( ! xTransferable.is() )
+ return bSuccess;
+
+ try
+ {
+
+ DataFlavor aFlavor;
+ aFlavor.MimeType = convertTypeFromNative( nType, nSelection, rFormat );
+
+ sal_Int32 nIndex = 0;
+ if( o3tl::getToken(aFlavor.MimeType, 0, ';', nIndex ) == u"text/plain" )
+ {
+ if( o3tl::getToken(aFlavor.MimeType, 0, ';', nIndex ) == u"charset=utf-16" )
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ else
+ aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+ }
+ else
+ aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ if( xTransferable->isDataFlavorSupported( aFlavor ) )
+ {
+ Any aValue( xTransferable->getTransferData( aFlavor ) );
+ if( aValue.getValueTypeClass() == TypeClass_STRING )
+ {
+ OUString aString;
+ aValue >>= aString;
+ rData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aString.getStr()), aString.getLength() * sizeof( sal_Unicode ) );
+ bSuccess = true;
+ }
+ else if( aValue.getValueType() == cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ {
+ aValue >>= rData;
+ bSuccess = true;
+ }
+ }
+ else if( aFlavor.MimeType.startsWith("text/plain") )
+ {
+ rtl_TextEncoding aEncoding = RTL_TEXTENCODING_DONTKNOW;
+ bool bCompoundText = false;
+ if( nType == m_nCOMPOUNDAtom )
+ bCompoundText = true;
+ else
+ aEncoding = getTextPlainEncoding( aFlavor.MimeType );
+ if( aEncoding != RTL_TEXTENCODING_DONTKNOW || bCompoundText )
+ {
+ aFlavor.MimeType = "text/plain;charset=utf-16";
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ if( xTransferable->isDataFlavorSupported( aFlavor ) )
+ {
+ Any aValue( xTransferable->getTransferData( aFlavor ) );
+ OUString aString;
+ aValue >>= aString;
+ OString aByteString( bCompoundText ? convertToCompound( aString ) : OUStringToOString( aString, aEncoding ) );
+ rData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aByteString.getStr()), aByteString.getLength() * sizeof( char ) );
+ bSuccess = true;
+ }
+ }
+ }
+ }
+ // various exceptions possible ... which all lead to a failed conversion
+ // so simplify here to a catch all
+ catch(...)
+ {
+ }
+
+ return bSuccess;
+}
+
+SelectionManager& SelectionManager::get( const OUString& rDisplayName )
+{
+ osl::MutexGuard aGuard( *osl::Mutex::getGlobalMutex() );
+
+ OUString aDisplayName( rDisplayName );
+ if( aDisplayName.isEmpty() )
+ if (auto const env = getenv( "DISPLAY" )) {
+ aDisplayName = OStringToOUString( env, RTL_TEXTENCODING_ISO_8859_1 );
+ }
+ SelectionManager* pInstance = nullptr;
+
+ std::unordered_map< OUString, SelectionManager* >::iterator it = getInstances().find( aDisplayName );
+ if( it != getInstances().end() )
+ pInstance = it->second;
+ else pInstance = getInstances()[ aDisplayName ] = new SelectionManager();
+
+ return *pInstance;
+}
+
+OUString SelectionManager::getString( Atom aAtom )
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if( m_aAtomToString.find( aAtom ) == m_aAtomToString.end() )
+ {
+ char* pAtom = m_pDisplay ? XGetAtomName( m_pDisplay, aAtom ) : nullptr;
+ if( ! pAtom )
+ return OUString();
+ OUString aString( OStringToOUString( pAtom, RTL_TEXTENCODING_ISO_8859_1 ) );
+ XFree( pAtom );
+ m_aStringToAtom[ aString ] = aAtom;
+ m_aAtomToString[ aAtom ] = aString;
+ }
+ return m_aAtomToString[ aAtom ];
+}
+
+Atom SelectionManager::getAtom( const OUString& rString )
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if( m_aStringToAtom.find( rString ) == m_aStringToAtom.end() )
+ {
+ static Atom nNoDisplayAtoms = 1;
+ Atom aAtom = m_pDisplay ? XInternAtom( m_pDisplay, OUStringToOString( rString, RTL_TEXTENCODING_ISO_8859_1 ).getStr(), False ) : nNoDisplayAtoms++;
+ m_aStringToAtom[ rString ] = aAtom;
+ m_aAtomToString[ aAtom ] = rString;
+ }
+ return m_aStringToAtom[ rString ];
+}
+
+bool SelectionManager::requestOwnership( Atom selection )
+{
+ bool bSuccess = false;
+ if( m_pDisplay && m_aWindow )
+ {
+ osl::MutexGuard aGuard(m_aMutex);
+
+ SelectionAdaptor* pAdaptor = getAdaptor( selection );
+ if( pAdaptor )
+ {
+ XSetSelectionOwner( m_pDisplay, selection, m_aWindow, CurrentTime );
+ if( XGetSelectionOwner( m_pDisplay, selection ) == m_aWindow )
+ bSuccess = true;
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans",
+ (bSuccess ? "acquired" : "failed to acquire")
+ << " ownership for selection "
+ << getString( selection ));
+#endif
+
+ Selection* pSel = m_aSelections[ selection ];
+ pSel->m_bOwner = bSuccess;
+ delete pSel->m_pPixmap;
+ pSel->m_pPixmap = nullptr;
+ pSel->m_nOrigTimestamp = m_nSelectionTimestamp;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ SAL_WARN("vcl.unx.dtrans", "no adaptor for selection "
+ << getString( selection ));
+
+ if( pAdaptor->getTransferable().is() )
+ {
+ Sequence< DataFlavor > aTypes = pAdaptor->getTransferable()->getTransferDataFlavors();
+ for( int i = 0; i < aTypes.getLength(); i++ )
+ {
+ SAL_INFO("vcl.unx.dtrans", " " << aTypes.getConstArray()[i].MimeType);
+ }
+ }
+#endif
+ }
+ return bSuccess;
+}
+
+void SelectionManager::convertTypeToNative( const OUString& rType, Atom selection, int& rFormat, ::std::list< Atom >& rConversions, bool bPushFront )
+{
+ NativeTypeEntry* pTab = selection == m_nXdndSelection ? aXdndConversionTab : aNativeConversionTab;
+ int nTabEntries = selection == m_nXdndSelection ? SAL_N_ELEMENTS(aXdndConversionTab) : SAL_N_ELEMENTS(aNativeConversionTab);
+
+ OString aType( OUStringToOString( rType, RTL_TEXTENCODING_ISO_8859_1 ) );
+ SAL_INFO( "vcl.unx.dtrans", "convertTypeToNative " << aType );
+ rFormat = 0;
+ for( int i = 0; i < nTabEntries; i++ )
+ {
+ if( aType.equalsIgnoreAsciiCase( pTab[i].pType ) )
+ {
+ if( ! pTab[i].nAtom )
+ pTab[i].nAtom = getAtom( OStringToOUString( pTab[i].pNativeType, RTL_TEXTENCODING_ISO_8859_1 ) );
+ rFormat = pTab[i].nFormat;
+ if( bPushFront )
+ rConversions.push_front( pTab[i].nAtom );
+ else
+ rConversions.push_back( pTab[i].nAtom );
+ if( pTab[i].nFormat == XA_PIXMAP )
+ {
+ if( bPushFront )
+ {
+ rConversions.push_front( XA_VISUALID );
+ rConversions.push_front( XA_COLORMAP );
+ }
+ else
+ {
+ rConversions.push_back( XA_VISUALID );
+ rConversions.push_back( XA_COLORMAP );
+ }
+ }
+ }
+ }
+ if( ! rFormat )
+ rFormat = 8; // byte buffer
+ if( bPushFront )
+ rConversions.push_front( getAtom( rType ) );
+ else
+ rConversions.push_back( getAtom( rType ) );
+};
+
+void SelectionManager::getNativeTypeList( const Sequence< DataFlavor >& rTypes, std::list< Atom >& rOutTypeList, Atom targetselection )
+{
+ rOutTypeList.clear();
+
+ int nFormat;
+ bool bHaveText = false;
+ for( const auto& rFlavor : rTypes )
+ {
+ if( rFlavor.MimeType.startsWith("text/plain"))
+ bHaveText = true;
+ else
+ convertTypeToNative( rFlavor.MimeType, targetselection, nFormat, rOutTypeList );
+ }
+ if( bHaveText )
+ {
+ if( targetselection != m_nXdndSelection )
+ {
+ // only mimetypes should go into Xdnd type list
+ rOutTypeList.push_front( XA_STRING );
+ rOutTypeList.push_front( m_nCOMPOUNDAtom );
+ }
+ convertTypeToNative( "text/plain;charset=utf-8", targetselection, nFormat, rOutTypeList, true );
+ }
+ if( targetselection != m_nXdndSelection )
+ rOutTypeList.push_back( m_nMULTIPLEAtom );
+}
+
+OUString SelectionManager::convertTypeFromNative( Atom nType, Atom selection, int& rFormat )
+{
+ NativeTypeEntry* pTab = (selection == m_nXdndSelection) ? aXdndConversionTab : aNativeConversionTab;
+ int nTabEntries = (selection == m_nXdndSelection) ? SAL_N_ELEMENTS(aXdndConversionTab) : SAL_N_ELEMENTS(aNativeConversionTab);
+
+ for( int i = 0; i < nTabEntries; i++ )
+ {
+ if( ! pTab[i].nAtom )
+ pTab[i].nAtom = getAtom( OStringToOUString( pTab[i].pNativeType, RTL_TEXTENCODING_ISO_8859_1 ) );
+ if( nType == pTab[i].nAtom )
+ {
+ rFormat = pTab[i].nFormat;
+ return OStringToOUString( pTab[i].pType, RTL_TEXTENCODING_ISO_8859_1 );
+ }
+ }
+ rFormat = 8;
+ return getString( nType );
+}
+
+bool SelectionManager::getPasteData( Atom selection, Atom type, Sequence< sal_Int8 >& rData )
+{
+ osl::ResettableMutexGuard aGuard(m_aMutex);
+ std::unordered_map< Atom, Selection* >::iterator it;
+ bool bSuccess = false;
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "getPasteData( " << getString( selection )
+ << ", native: " << getString( type ) << " ).");
+#endif
+
+ if( ! m_pDisplay )
+ return false;
+
+ it = m_aSelections.find( selection );
+ if( it == m_aSelections.end() )
+ return false;
+
+ ::Window aSelectionOwner = XGetSelectionOwner( m_pDisplay, selection );
+ if( aSelectionOwner == None )
+ return false;
+ if( aSelectionOwner == m_aWindow )
+ {
+ // probably bad timing led us here
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.unx.dtrans", "Innere Nabelschau.");
+#endif
+ return false;
+ }
+
+ // ICCCM recommends to destroy property before convert request unless
+ // parameters are transported; we do only in case of MULTIPLE,
+ // so destroy property unless target is MULTIPLE
+ if( type != m_nMULTIPLEAtom )
+ XDeleteProperty( m_pDisplay, m_aWindow, selection );
+
+ XConvertSelection( m_pDisplay, selection, type, selection, m_aWindow, selection == m_nXdndSelection ? m_nDropTime : CurrentTime );
+ it->second->m_eState = Selection::WaitingForResponse;
+ it->second->m_aRequestedType = type;
+ it->second->m_aData = Sequence< sal_Int8 >();
+ it->second->m_aDataArrived.reset();
+ // really start the request; if we don't flush the
+ // queue the request won't leave it because there are no more
+ // X calls after this until the data arrived or timeout
+ XFlush( m_pDisplay );
+
+ // do a reschedule
+ struct timeval tv_last, tv_current;
+ gettimeofday( &tv_last, nullptr );
+ tv_current = tv_last;
+
+ XEvent aEvent;
+ do
+ {
+ bool bAdjustTime = false;
+ {
+ bool bHandle = false;
+
+ if( XCheckTypedEvent( m_pDisplay,
+ PropertyNotify,
+ &aEvent
+ ) )
+ {
+ bHandle = true;
+ if( aEvent.xproperty.window == m_aWindow
+ && aEvent.xproperty.atom == selection )
+ bAdjustTime = true;
+ }
+ else if( XCheckTypedEvent( m_pDisplay,
+ SelectionClear,
+ &aEvent
+ ) )
+ {
+ bHandle = true;
+ }
+ else if( XCheckTypedEvent( m_pDisplay,
+ SelectionRequest,
+ &aEvent
+ ) )
+ {
+ bHandle = true;
+ }
+ else if( XCheckTypedEvent( m_pDisplay,
+ SelectionNotify,
+ &aEvent
+ ) )
+ {
+ bHandle = true;
+ if( aEvent.xselection.selection == selection
+ && ( aEvent.xselection.requestor == m_aWindow ||
+ aEvent.xselection.requestor == m_aCurrentDropWindow )
+ )
+ bAdjustTime = true;
+ }
+ else
+ {
+ aGuard.clear();
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ aGuard.reset();
+ }
+ if( bHandle )
+ {
+ aGuard.clear();
+ handleXEvent( aEvent );
+ aGuard.reset();
+ }
+ }
+ gettimeofday( &tv_current, nullptr );
+ if( bAdjustTime )
+ tv_last = tv_current;
+ } while( ! it->second->m_aDataArrived.check() && (tv_current.tv_sec - tv_last.tv_sec) < getSelectionTimeout() );
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN_IF((tv_current.tv_sec - tv_last.tv_sec) > getSelectionTimeout(),
+ "vcl.unx.dtrans", "timed out.");
+#endif
+
+ if( it->second->m_aDataArrived.check() &&
+ it->second->m_aData.getLength() )
+ {
+ rData = it->second->m_aData;
+ bSuccess = true;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ SAL_WARN("vcl.unx.dtrans", "conversion unsuccessful.");
+#endif
+ return bSuccess;
+}
+
+bool SelectionManager::getPasteData( Atom selection, const OUString& rType, Sequence< sal_Int8 >& rData )
+{
+ bool bSuccess = false;
+
+ std::unordered_map< Atom, Selection* >::iterator it;
+ {
+ osl::MutexGuard aGuard(m_aMutex);
+
+ it = m_aSelections.find( selection );
+ if( it == m_aSelections.end() )
+ return false;
+ }
+
+ if( it->second->m_aTypes.getLength() == 0 )
+ {
+ Sequence< DataFlavor > aFlavors;
+ getPasteDataTypes( selection, aFlavors );
+ if( it->second->m_aTypes.getLength() == 0 )
+ return false;
+ }
+
+ const Sequence< DataFlavor >& rTypes( it->second->m_aTypes );
+ const std::vector< Atom >& rNativeTypes( it->second->m_aNativeTypes );
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "getPasteData( \""
+ << getString( selection )
+ << "\", \""
+ << rType << "\" ).");
+#endif
+
+ if( rType == "text/plain;charset=utf-16" )
+ {
+ // lets see if we have UTF16 else try to find something convertible
+ if( it->second->m_aTypes.getLength() && ! it->second->m_bHaveUTF16 )
+ {
+ Sequence< sal_Int8 > aData;
+ if( it->second->m_aUTF8Type != None &&
+ getPasteData( selection,
+ it->second->m_aUTF8Type,
+ aData )
+ )
+ {
+ OUString aRet( reinterpret_cast<const char*>(aData.getConstArray()), aData.getLength(), RTL_TEXTENCODING_UTF8 );
+ rData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aRet.getStr()), (aRet.getLength()+1)*sizeof( sal_Unicode ) );
+ bSuccess = true;
+ }
+ else if( it->second->m_bHaveCompound &&
+ getPasteData( selection,
+ m_nCOMPOUNDAtom,
+ aData )
+ )
+ {
+ OUString aRet( convertFromCompound( reinterpret_cast<const char*>(aData.getConstArray()), aData.getLength() ) );
+ rData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aRet.getStr()), (aRet.getLength()+1)*sizeof( sal_Unicode ) );
+ bSuccess = true;
+ }
+ else
+ {
+ for( int i = 0; i < rTypes.getLength(); i++ )
+ {
+ rtl_TextEncoding aEncoding = getTextPlainEncoding( rTypes.getConstArray()[i].MimeType );
+ if( aEncoding != RTL_TEXTENCODING_DONTKNOW &&
+ aEncoding != RTL_TEXTENCODING_UNICODE &&
+ getPasteData( selection,
+ rNativeTypes[i],
+ aData )
+ )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "using \""
+ << rTypes.getConstArray()[i].MimeType
+ << "\" instead of \""
+ << rType
+ << "\".");
+#endif
+
+ OString aConvert( reinterpret_cast<char const *>(aData.getConstArray()), aData.getLength() );
+ OUString aUTF( OStringToOUString( aConvert, aEncoding ) );
+ rData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aUTF.getStr()), (aUTF.getLength()+1)*sizeof( sal_Unicode ) );
+ bSuccess = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ else if( rType == "image/bmp" )
+ {
+ // #i83376# try if someone has the data in image/bmp already before
+ // doing the PIXMAP stuff (e.g. the Gimp has this)
+ bSuccess = getPasteData( selection, m_nImageBmpAtom, rData );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO_IF(bSuccess, "vcl.unx.dtrans",
+ "got " << (int) rData.getLength() << " bytes of image/bmp.");
+#endif
+ if( ! bSuccess )
+ {
+ Pixmap aPixmap = None;
+ Colormap aColormap = None;
+
+ // prepare property for MULTIPLE request
+ Sequence< sal_Int8 > aData;
+ Atom const pTypes[4] = { XA_PIXMAP, XA_PIXMAP, XA_COLORMAP, XA_COLORMAP };
+ {
+ osl::MutexGuard aGuard(m_aMutex);
+
+ XChangeProperty( m_pDisplay,
+ m_aWindow,
+ selection,
+ XA_ATOM,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char const *>(pTypes),
+ 4 );
+ }
+
+ // try MULTIPLE request
+ if( getPasteData( selection, m_nMULTIPLEAtom, aData ) )
+ {
+ Atom* pReturnedTypes = reinterpret_cast<Atom*>(aData.getArray());
+ if( pReturnedTypes[0] == XA_PIXMAP && pReturnedTypes[1] == XA_PIXMAP )
+ {
+ osl::MutexGuard aGuard(m_aMutex);
+
+ Atom type = None;
+ int format = 0;
+ unsigned long nItems = 0;
+ unsigned long nBytes = 0;
+ unsigned char* pReturn = nullptr;
+ XGetWindowProperty( m_pDisplay, m_aWindow, XA_PIXMAP, 0, 1, True, XA_PIXMAP, &type, &format, &nItems, &nBytes, &pReturn );
+ if( pReturn )
+ {
+ if( type == XA_PIXMAP )
+ aPixmap = *reinterpret_cast<Pixmap*>(pReturn);
+ XFree( pReturn );
+ pReturn = nullptr;
+ if( pReturnedTypes[2] == XA_COLORMAP && pReturnedTypes[3] == XA_COLORMAP )
+ {
+ XGetWindowProperty( m_pDisplay, m_aWindow, XA_COLORMAP, 0, 1, True, XA_COLORMAP, &type, &format, &nItems, &nBytes, &pReturn );
+ if( pReturn )
+ {
+ if( type == XA_COLORMAP )
+ aColormap = *reinterpret_cast<Colormap*>(pReturn);
+ XFree( pReturn );
+ }
+ }
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ {
+ SAL_WARN("vcl.unx.dtrans", "could not get PIXMAP property: type="
+ << getString( type )
+ << ", format=" << format
+ << ", items=" << nItems
+ << ", bytes=" << nBytes
+ << ", ret=0x" << pReturn);
+ }
+#endif
+ }
+ }
+
+ if( aPixmap == None )
+ {
+ // perhaps two normal requests will work
+ if( getPasteData( selection, XA_PIXMAP, aData ) )
+ {
+ aPixmap = *reinterpret_cast<Pixmap*>(aData.getArray());
+ if( aColormap == None && getPasteData( selection, XA_COLORMAP, aData ) )
+ aColormap = *reinterpret_cast<Colormap*>(aData.getArray());
+ }
+ }
+
+ // convert data if possible
+ if( aPixmap != None )
+ {
+ osl::MutexGuard aGuard(m_aMutex);
+
+ sal_Int32 nOutSize = 0;
+ sal_uInt8* pBytes = X11_getBmpFromPixmap( m_pDisplay, aPixmap, aColormap, nOutSize );
+ if( pBytes )
+ {
+ if( nOutSize )
+ {
+ rData = Sequence< sal_Int8 >( nOutSize );
+ memcpy( rData.getArray(), pBytes, nOutSize );
+ bSuccess = true;
+ }
+ std::free( pBytes );
+ }
+ }
+ }
+ }
+
+ if( ! bSuccess )
+ {
+ int nFormat;
+ ::std::list< Atom > aTypes;
+ convertTypeToNative( rType, selection, nFormat, aTypes );
+ Atom nSelectedType = None;
+ for (auto const& type : aTypes)
+ {
+ for( auto const & nativeType: rNativeTypes )
+ if(nativeType == type)
+ {
+ nSelectedType = type;
+ if (nSelectedType != None)
+ break;
+ }
+ }
+ if( nSelectedType != None )
+ bSuccess = getPasteData( selection, nSelectedType, rData );
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "getPasteData for selection "
+ << getString( selection )
+ << " and data type "
+ << rType
+ << " returns "
+ << ( bSuccess ? "true" : "false")
+ << ", returned sequence has length "
+ << rData.getLength() << ".");
+#endif
+ return bSuccess;
+}
+
+bool SelectionManager::getPasteDataTypes( Atom selection, Sequence< DataFlavor >& rTypes )
+{
+ std::unordered_map< Atom, Selection* >::iterator it;
+ {
+ osl::MutexGuard aGuard(m_aMutex);
+
+ it = m_aSelections.find( selection );
+ if( it != m_aSelections.end() &&
+ it->second->m_aTypes.getLength() &&
+ std::abs( it->second->m_nLastTimestamp - time( nullptr ) ) < 2
+ )
+ {
+ rTypes = it->second->m_aTypes;
+ return true;
+ }
+ }
+
+ bool bSuccess = false;
+ bool bHaveUTF16 = false;
+ Atom aUTF8Type = None;
+ bool bHaveCompound = false;
+ Sequence< sal_Int8 > aAtoms;
+
+ if( selection == m_nXdndSelection )
+ {
+ // xdnd sends first three types with XdndEnter
+ // if more than three types are supported then the XDndTypeList
+ // property on the source window is used
+ if( m_aDropEnterEvent.data.l[0] && m_aCurrentDropWindow )
+ {
+ if( m_aDropEnterEvent.data.l[1] & 1 )
+ {
+ const unsigned int atomcount = 256;
+ // more than three types; look in property
+ osl::MutexGuard aGuard(m_aMutex);
+
+ Atom nType;
+ int nFormat;
+ unsigned long nItems, nBytes;
+ unsigned char* pBytes = nullptr;
+
+ XGetWindowProperty( m_pDisplay, m_aDropEnterEvent.data.l[0],
+ m_nXdndTypeList, 0, atomcount, False,
+ XA_ATOM,
+ &nType, &nFormat, &nItems, &nBytes, &pBytes );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "have "
+ << nItems
+ << " data types in XdndTypeList.");
+#endif
+ if( nItems == atomcount && nBytes > 0 )
+ {
+ // wow ... more than 256 types !
+ aAtoms.realloc( sizeof( Atom )*atomcount+nBytes );
+ memcpy( aAtoms.getArray(), pBytes, sizeof( Atom )*atomcount );
+ XFree( pBytes );
+ pBytes = nullptr;
+ XGetWindowProperty( m_pDisplay, m_aDropEnterEvent.data.l[0],
+ m_nXdndTypeList, atomcount, nBytes/sizeof(Atom),
+ False, XA_ATOM,
+ &nType, &nFormat, &nItems, &nBytes, &pBytes );
+ {
+ memcpy( aAtoms.getArray()+atomcount*sizeof(Atom), pBytes, nItems*sizeof(Atom) );
+ XFree( pBytes );
+ }
+ }
+ else
+ {
+ aAtoms.realloc( sizeof(Atom)*nItems );
+ memcpy( aAtoms.getArray(), pBytes, nItems*sizeof(Atom) );
+ XFree( pBytes );
+ }
+ }
+ else
+ {
+ // one to three types
+ int n = 0, i;
+ for( i = 0; i < 3; i++ )
+ if( m_aDropEnterEvent.data.l[2+i] )
+ n++;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "have "
+ << n
+ << " data types in XdndEnter.");
+#endif
+ aAtoms.realloc( sizeof(Atom)*n );
+ for( i = 0, n = 0; i < 3; i++ )
+ if( m_aDropEnterEvent.data.l[2+i] )
+ reinterpret_cast<Atom*>(aAtoms.getArray())[n++] = m_aDropEnterEvent.data.l[2+i];
+ }
+ }
+ }
+ // get data of type TARGETS
+ else if( ! getPasteData( selection, m_nTARGETSAtom, aAtoms ) )
+ aAtoms = Sequence< sal_Int8 >();
+
+ std::vector< Atom > aNativeTypes;
+ if( aAtoms.hasElements() )
+ {
+ sal_Int32 nAtoms = aAtoms.getLength() / sizeof(Atom);
+ Atom* pAtoms = reinterpret_cast<Atom*>(aAtoms.getArray());
+ rTypes.realloc( nAtoms );
+ aNativeTypes.resize( nAtoms );
+ DataFlavor* pFlavors = rTypes.getArray();
+ sal_Int32 nNativeTypesIndex = 0;
+ bool bHaveText = false;
+ while( nAtoms-- )
+ {
+ SAL_INFO_IF(*pAtoms && *pAtoms < 0x01000000, "vcl.unx.dtrans",
+ "getPasteDataTypes: available: \"" << getString(*pAtoms) << "\"");
+ if( *pAtoms == m_nCOMPOUNDAtom )
+ bHaveText = bHaveCompound = true;
+ else if( *pAtoms && *pAtoms < 0x01000000 )
+ {
+ int nFormat;
+ pFlavors->MimeType = convertTypeFromNative( *pAtoms, selection, nFormat );
+ pFlavors->DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+ sal_Int32 nIndex = 0;
+ if( o3tl::getToken(pFlavors->MimeType, 0, ';', nIndex ) == u"text/plain" )
+ {
+ std::u16string_view aToken(o3tl::getToken(pFlavors->MimeType, 0, ';', nIndex ));
+ // omit text/plain;charset=unicode since it is not well defined
+ if( aToken == u"charset=unicode" )
+ {
+ pAtoms++;
+ continue;
+ }
+ bHaveText = true;
+ if( aToken == u"charset=utf-16" )
+ {
+ bHaveUTF16 = true;
+ pFlavors->DataType = cppu::UnoType<OUString>::get();
+ }
+ else if( aToken == u"charset=utf-8" )
+ {
+ aUTF8Type = *pAtoms;
+ }
+ }
+ pFlavors++;
+ aNativeTypes[ nNativeTypesIndex ] = *pAtoms;
+ nNativeTypesIndex++;
+ }
+ pAtoms++;
+ }
+ if( (pFlavors - rTypes.getArray()) < rTypes.getLength() )
+ rTypes.realloc(pFlavors - rTypes.getArray());
+ bSuccess = rTypes.hasElements();
+ if( bHaveText && ! bHaveUTF16 )
+ {
+ int i = 0;
+
+ int nNewFlavors = rTypes.getLength()+1;
+ Sequence< DataFlavor > aTemp( nNewFlavors );
+ for( i = 0; i < nNewFlavors-1; i++ )
+ aTemp.getArray()[i+1] = rTypes.getConstArray()[i];
+ aTemp.getArray()[0].MimeType = "text/plain;charset=utf-16";
+ aTemp.getArray()[0].DataType = cppu::UnoType<OUString>::get();
+ rTypes = aTemp;
+
+ std::vector< Atom > aNativeTemp( nNewFlavors );
+ for( i = 0; i < nNewFlavors-1; i++ )
+ aNativeTemp[ i + 1 ] = aNativeTypes[ i ];
+ aNativeTemp[0] = None;
+ aNativeTypes = aNativeTemp;
+ }
+ }
+
+ {
+ osl::MutexGuard aGuard(m_aMutex);
+
+ it = m_aSelections.find( selection );
+ if( it != m_aSelections.end() )
+ {
+ if( bSuccess )
+ {
+ it->second->m_aTypes = rTypes;
+ it->second->m_aNativeTypes = aNativeTypes;
+ it->second->m_nLastTimestamp = time( nullptr );
+ it->second->m_bHaveUTF16 = bHaveUTF16;
+ it->second->m_aUTF8Type = aUTF8Type;
+ it->second->m_bHaveCompound = bHaveCompound;
+ }
+ else
+ {
+ it->second->m_aTypes = Sequence< DataFlavor >();
+ it->second->m_aNativeTypes = std::vector< Atom >();
+ it->second->m_nLastTimestamp = 0;
+ it->second->m_bHaveUTF16 = false;
+ it->second->m_aUTF8Type = None;
+ it->second->m_bHaveCompound = false;
+ }
+ }
+ }
+
+#if OSL_DEBUG_LEVEL > 1
+ {
+ SAL_INFO("vcl.unx.dtrans", "SelectionManager::getPasteDataTypes( "
+ << getString( selection )
+ << " ) = "
+ << (bSuccess ? "true" : "false"));
+ for( int i = 0; i < rTypes.getLength(); i++ )
+ SAL_INFO("vcl.unx.dtrans", "type: " << rTypes.getConstArray()[i].MimeType);
+ }
+#endif
+
+ return bSuccess;
+}
+
+PixmapHolder* SelectionManager::getPixmapHolder( Atom selection )
+{
+ std::unordered_map< Atom, Selection* >::const_iterator it = m_aSelections.find( selection );
+ if( it == m_aSelections.end() )
+ return nullptr;
+ if( ! it->second->m_pPixmap )
+ it->second->m_pPixmap = new PixmapHolder( m_pDisplay );
+ return it->second->m_pPixmap;
+}
+
+static std::size_t GetTrueFormatSize(int nFormat)
+{
+ // http://mail.gnome.org/archives/wm-spec-list/2003-March/msg00067.html
+ return nFormat == 32 ? sizeof(long) : nFormat/8;
+}
+
+bool SelectionManager::sendData( SelectionAdaptor* pAdaptor,
+ ::Window requestor,
+ Atom target,
+ Atom property,
+ Atom selection )
+{
+ osl::ResettableMutexGuard aGuard( m_aMutex );
+
+ // handle targets related to image/bmp
+ if( target == XA_COLORMAP || target == XA_PIXMAP || target == XA_BITMAP || target == XA_VISUALID )
+ {
+ PixmapHolder* pPixmap = getPixmapHolder( selection );
+ if( ! pPixmap ) return false;
+ XID nValue = None;
+
+ // handle colormap request
+ if( target == XA_COLORMAP )
+ nValue = static_cast<XID>(pPixmap->getColormap());
+ else if( target == XA_VISUALID )
+ nValue = static_cast<XID>(pPixmap->getVisualID());
+ else if( target == XA_PIXMAP || target == XA_BITMAP )
+ {
+ nValue = static_cast<XID>(pPixmap->getPixmap());
+ if( nValue == None )
+ {
+ // first conversion
+ Sequence< sal_Int8 > aData;
+ int nFormat;
+ aGuard.clear();
+ bool bConverted = convertData( pAdaptor->getTransferable(), target, selection, nFormat, aData );
+ aGuard.reset();
+ if( bConverted )
+ {
+ // get pixmap again since clearing the guard could have invalidated
+ // the pixmap in another thread
+ pPixmap = getPixmapHolder( selection );
+ // conversion succeeded, so aData contains image/bmp now
+ if( pPixmap->needsConversion( reinterpret_cast<const sal_uInt8*>(aData.getConstArray()) ) )
+ {
+ SAL_INFO( "vcl.unx.dtrans", "trying bitmap conversion" );
+ int depth = pPixmap->getDepth();
+ aGuard.clear();
+ aData = convertBitmapDepth(aData, depth);
+ aGuard.reset();
+ }
+ // get pixmap again since clearing the guard could have invalidated
+ // the pixmap in another thread
+ pPixmap = getPixmapHolder( selection );
+ nValue = static_cast<XID>(pPixmap->setBitmapData( reinterpret_cast<const sal_uInt8*>(aData.getConstArray()) ));
+ }
+ if( nValue == None )
+ return false;
+ }
+ if( target == XA_BITMAP )
+ nValue = static_cast<XID>(pPixmap->getBitmap());
+ }
+
+ XChangeProperty( m_pDisplay,
+ requestor,
+ property,
+ target,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<const unsigned char*>(&nValue),
+ 1);
+ return true;
+ }
+
+ /*
+ * special target TEXT allows us to transfer
+ * the data in an encoding of our choice
+ * COMPOUND_TEXT will work with most applications
+ */
+ if( target == m_nTEXTAtom )
+ target = m_nCOMPOUNDAtom;
+
+ Sequence< sal_Int8 > aData;
+ int nFormat;
+ aGuard.clear();
+ bool bConverted = convertData( pAdaptor->getTransferable(), target, selection, nFormat, aData );
+ aGuard.reset();
+ if( bConverted )
+ {
+ // conversion succeeded
+ if( aData.getLength() > m_nIncrementalThreshold )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "using INCR protocol.");
+ std::unordered_map< ::Window, std::unordered_map< Atom, IncrementalTransfer > >::const_iterator win_it = m_aIncrementals.find( requestor );
+ if( win_it != m_aIncrementals.end() )
+ {
+ std::unordered_map< Atom, IncrementalTransfer >::const_iterator inc_it = win_it->second.find( property );
+ if( inc_it != win_it->second.end() )
+ {
+ const IncrementalTransfer& rInc = inc_it->second;
+ SAL_INFO("vcl.unx.dtrans", "premature end and new start for INCR transfer for window "
+ << std::showbase << std::hex
+ << rInc.m_aRequestor
+ << ", property "
+ << getString( rInc.m_aProperty )
+ << ", type "
+ << getString( rInc.m_aTarget ));
+ }
+ }
+#endif
+
+ // insert IncrementalTransfer
+ IncrementalTransfer& rInc = m_aIncrementals[ requestor ][ property ];
+ rInc.m_aData = aData;
+ rInc.m_nBufferPos = 0;
+ rInc.m_aRequestor = requestor;
+ rInc.m_aProperty = property;
+ rInc.m_aTarget = target;
+ rInc.m_nFormat = nFormat;
+ rInc.m_nTransferStartTime = time( nullptr );
+
+ // use incr protocol, signal start to requestor
+ tools::Long nMinSize = m_nIncrementalThreshold;
+ XSelectInput( m_pDisplay, requestor, PropertyChangeMask );
+ XChangeProperty( m_pDisplay, requestor, property,
+ m_nINCRAtom, 32, PropModeReplace, reinterpret_cast<unsigned char*>(&nMinSize), 1 );
+ XFlush( m_pDisplay );
+ }
+ else
+ {
+ std::size_t nUnitSize = GetTrueFormatSize(nFormat);
+ XChangeProperty( m_pDisplay,
+ requestor,
+ property,
+ target,
+ nFormat,
+ PropModeReplace,
+ reinterpret_cast<const unsigned char*>(aData.getConstArray()),
+ aData.getLength()/nUnitSize );
+ }
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ SAL_WARN("vcl.unx.dtrans", "convertData failed for type: "
+ << convertTypeFromNative( target, selection, nFormat ));
+#endif
+ return bConverted;
+}
+
+bool SelectionManager::handleSelectionRequest( XSelectionRequestEvent& rRequest )
+{
+ osl::ResettableMutexGuard aGuard( m_aMutex );
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "handleSelectionRequest for selection "
+ << getString( rRequest.selection )
+ << " and target "
+ << getString( rRequest.target ));
+#endif
+
+ XEvent aNotify;
+
+ aNotify.type = SelectionNotify;
+ aNotify.xselection.display = rRequest.display;
+ aNotify.xselection.send_event = True;
+ aNotify.xselection.requestor = rRequest.requestor;
+ aNotify.xselection.selection = rRequest.selection;
+ aNotify.xselection.time = rRequest.time;
+ aNotify.xselection.target = rRequest.target;
+ aNotify.xselection.property = None;
+
+ SelectionAdaptor* pAdaptor = getAdaptor( rRequest.selection );
+ // ensure that we still own that selection
+ if( pAdaptor &&
+ XGetSelectionOwner( m_pDisplay, rRequest.selection ) == m_aWindow )
+ {
+ css::uno::Reference< XTransferable > xTrans( pAdaptor->getTransferable() );
+ if( rRequest.target == m_nTARGETSAtom )
+ {
+ // someone requests our types
+ if( xTrans.is() )
+ {
+ aGuard.clear();
+ Sequence< DataFlavor > aFlavors = xTrans->getTransferDataFlavors();
+ aGuard.reset();
+
+ ::std::list< Atom > aConversions;
+ getNativeTypeList( aFlavors, aConversions, rRequest.selection );
+
+ int i, nTypes = aConversions.size();
+ Atom* pTypes = static_cast<Atom*>(alloca( nTypes * sizeof( Atom ) ));
+ std::list< Atom >::const_iterator it;
+ for( i = 0, it = aConversions.begin(); i < nTypes; i++, ++it )
+ pTypes[i] = *it;
+ XChangeProperty( m_pDisplay, rRequest.requestor, rRequest.property,
+ XA_ATOM, 32, PropModeReplace, reinterpret_cast<unsigned char*>(pTypes), nTypes );
+ aNotify.xselection.property = rRequest.property;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "sending type list:");
+ for( int k = 0; k < nTypes; k++ )
+ SAL_INFO("vcl.unx.dtrans", " "
+ << (pTypes[k] ? XGetAtomName( m_pDisplay, pTypes[k] ) :
+ "<None>"));
+#endif
+ }
+ }
+ else if( rRequest.target == m_nTIMESTAMPAtom )
+ {
+ tools::Long nTimeStamp = static_cast<tools::Long>(m_aSelections[rRequest.selection]->m_nOrigTimestamp);
+ XChangeProperty( m_pDisplay, rRequest.requestor, rRequest.property,
+ XA_INTEGER, 32, PropModeReplace, reinterpret_cast<unsigned char*>(&nTimeStamp), 1 );
+ aNotify.xselection.property = rRequest.property;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "sending timestamp: " << (int)nTimeStamp);
+#endif
+ }
+ else
+ {
+ bool bEventSuccess = false;
+ if( rRequest.target == m_nMULTIPLEAtom )
+ {
+ // get all targets
+ Atom nType = None;
+ int nFormat = 0;
+ unsigned long nItems = 0, nBytes = 0;
+ unsigned char* pData = nullptr;
+
+ // get number of atoms
+ XGetWindowProperty( m_pDisplay,
+ rRequest.requestor,
+ rRequest.property,
+ 0, 0,
+ False,
+ AnyPropertyType,
+ &nType, &nFormat,
+ &nItems, &nBytes,
+ &pData );
+ if( nFormat == 32 && nBytes/4 )
+ {
+ if( pData ) // ?? should not happen
+ {
+ XFree( pData );
+ pData = nullptr;
+ }
+ XGetWindowProperty( m_pDisplay,
+ rRequest.requestor,
+ rRequest.property,
+ 0, nBytes/4,
+ False,
+ nType,
+ &nType, &nFormat,
+ &nItems, &nBytes,
+ &pData );
+ if( pData && nItems )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "found "
+ << nItems
+ << " atoms in MULTIPLE request.");
+#endif
+ bEventSuccess = true;
+ bool bResetAtoms = false;
+ Atom* pAtoms = reinterpret_cast<Atom*>(pData);
+ aGuard.clear();
+ for( unsigned long i = 0; i < nItems; i += 2 )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ std::ostringstream oss;
+ oss << " "
+ << getString( pAtoms[i] )
+ << " => "
+ << getString( pAtoms[i+1] )
+ << ": ";
+#endif
+
+ bool bSuccess = sendData( pAdaptor, rRequest.requestor, pAtoms[i], pAtoms[i+1], rRequest.selection );
+#if OSL_DEBUG_LEVEL > 1
+ oss << (bSuccess ? "succeeded" : "failed");
+ SAL_INFO("vcl.unx.dtrans", oss.str());
+#endif
+ if( ! bSuccess )
+ {
+ pAtoms[i] = None;
+ bResetAtoms = true;
+ }
+ }
+ aGuard.reset();
+ if( bResetAtoms )
+ XChangeProperty( m_pDisplay,
+ rRequest.requestor,
+ rRequest.property,
+ XA_ATOM,
+ 32,
+ PropModeReplace,
+ pData,
+ nBytes/4 );
+ }
+ if( pData )
+ XFree( pData );
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ {
+ std::ostringstream oss;
+ oss << "could not get type list from \""
+ << getString( rRequest.property )
+ << "\" of type \""
+ << getString( nType )
+ << "\" on requestor "
+ << std::showbase << std::hex
+ << rRequest.requestor
+ << ", requestor has properties:";
+
+ int nProps = 0;
+ Atom* pProps = XListProperties( m_pDisplay, rRequest.requestor, &nProps );
+ if( pProps )
+ {
+ for( int i = 0; i < nProps; i++ )
+ oss << " \"" << getString( pProps[i]) << "\"";
+ XFree( pProps );
+ }
+ SAL_INFO("vcl.unx.dtrans", oss.str());
+ }
+#endif
+ }
+ else
+ {
+ aGuard.clear();
+ bEventSuccess = sendData( pAdaptor, rRequest.requestor, rRequest.target, rRequest.property, rRequest.selection );
+ aGuard.reset();
+ }
+ if( bEventSuccess )
+ {
+ aNotify.xselection.target = rRequest.target;
+ aNotify.xselection.property = rRequest.property;
+ }
+ }
+ aGuard.clear();
+ xTrans.clear();
+ aGuard.reset();
+ }
+ XSendEvent( m_pDisplay, rRequest.requestor, False, 0, &aNotify );
+
+ if( rRequest.selection == XA_PRIMARY &&
+ m_bWaitingForPrimaryConversion &&
+ m_xDragSourceListener.is() )
+ {
+ DragSourceDropEvent dsde;
+ dsde.Source = getXWeak();
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ if( aNotify.xselection.property != None )
+ {
+ dsde.DropAction = DNDConstants::ACTION_COPY;
+ dsde.DropSuccess = true;
+ }
+ else
+ {
+ dsde.DropAction = DNDConstants::ACTION_NONE;
+ dsde.DropSuccess = false;
+ }
+ css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener );
+ m_xDragSourceListener.clear();
+ aGuard.clear();
+ if( xListener.is() )
+ xListener->dragDropEnd( dsde );
+ }
+
+ // we handled the event in any case by answering
+ return true;
+}
+
+bool SelectionManager::handleReceivePropertyNotify( XPropertyEvent const & rNotify )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+ // data we requested arrived
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "handleReceivePropertyNotify for property "
+ << getString( rNotify.atom ));
+#endif
+ bool bHandled = false;
+
+ std::unordered_map< Atom, Selection* >::iterator it =
+ m_aSelections.find( rNotify.atom );
+ if( it != m_aSelections.end() &&
+ rNotify.state == PropertyNewValue &&
+ ( it->second->m_eState == Selection::WaitingForResponse ||
+ it->second->m_eState == Selection::WaitingForData ||
+ it->second->m_eState == Selection::IncrementalTransfer
+ )
+ )
+ {
+ // MULTIPLE requests are only complete after selection notify
+ if( it->second->m_aRequestedType == m_nMULTIPLEAtom &&
+ ( it->second->m_eState == Selection::WaitingForResponse ||
+ it->second->m_eState == Selection::WaitingForData ) )
+ return false;
+
+ bHandled = true;
+
+ Atom nType = None;
+ int nFormat = 0;
+ unsigned long nItems = 0, nBytes = 0;
+ unsigned char* pData = nullptr;
+
+ // get type and length
+ XGetWindowProperty( m_pDisplay,
+ rNotify.window,
+ rNotify.atom,
+ 0, 0,
+ False,
+ AnyPropertyType,
+ &nType, &nFormat,
+ &nItems, &nBytes,
+ &pData );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "found "
+ << nBytes
+ << " bytes data of type "
+ << getString( nType )
+ << " and format "
+ << nFormat
+ << ", items = "
+ << nItems);
+#endif
+ if( pData )
+ {
+ XFree( pData );
+ pData = nullptr;
+ }
+
+ if( nType == m_nINCRAtom )
+ {
+ // start data transfer
+ XDeleteProperty( m_pDisplay, rNotify.window, rNotify.atom );
+ it->second->m_eState = Selection::IncrementalTransfer;
+ }
+ else if( nType != None )
+ {
+ XGetWindowProperty( m_pDisplay,
+ rNotify.window,
+ rNotify.atom,
+ 0, nBytes/4 +1,
+ True,
+ nType,
+ &nType, &nFormat,
+ &nItems, &nBytes,
+ &pData );
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "read "
+ << nItems
+ << " items data of type "
+ << getString( nType )
+ << " and format "
+ << nFormat
+ << ", "
+ << nBytes
+ << " bytes left in property.");
+#endif
+
+ std::size_t nUnitSize = GetTrueFormatSize(nFormat);
+
+ if( it->second->m_eState == Selection::WaitingForData ||
+ it->second->m_eState == Selection::WaitingForResponse )
+ {
+ // copy data
+ it->second->m_aData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8*>(pData), nItems*nUnitSize );
+ it->second->m_eState = Selection::Inactive;
+ it->second->m_aDataArrived.set();
+ }
+ else if( it->second->m_eState == Selection::IncrementalTransfer )
+ {
+ if( nItems )
+ {
+ // append data
+ Sequence< sal_Int8 > aData( it->second->m_aData.getLength() + nItems*nUnitSize );
+ memcpy( aData.getArray(), it->second->m_aData.getArray(), it->second->m_aData.getLength() );
+ memcpy( aData.getArray() + it->second->m_aData.getLength(), pData, nItems*nUnitSize );
+ it->second->m_aData = aData;
+ }
+ else
+ {
+ it->second->m_eState = Selection::Inactive;
+ it->second->m_aDataArrived.set();
+ }
+ }
+ if( pData )
+ XFree( pData );
+ }
+ else if( it->second->m_eState == Selection::IncrementalTransfer )
+ {
+ it->second->m_eState = Selection::Inactive;
+ it->second->m_aDataArrived.set();
+ }
+ }
+ return bHandled;
+}
+
+bool SelectionManager::handleSendPropertyNotify( XPropertyEvent const & rNotify )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+
+ // ready for next part of an IncrementalTransfer
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "handleSendPropertyNotify for property "
+ << getString( rNotify.atom )
+ << " ("
+ << (rNotify.state == PropertyNewValue ?
+ "new value" :
+ (rNotify.state == PropertyDelete ?
+ "deleted" :
+ "unknown"))
+ << ").");
+#endif
+
+ bool bHandled = false;
+ // feed incrementals
+ if( rNotify.state == PropertyDelete )
+ {
+ auto it = m_aIncrementals.find( rNotify.window );
+ if( it != m_aIncrementals.end() )
+ {
+ bHandled = true;
+ time_t nCurrentTime = time( nullptr );
+ // throw out aborted transfers
+ std::vector< Atom > aTimeouts;
+ for (auto const& incrementalTransfer : it->second)
+ {
+ if( (nCurrentTime - incrementalTransfer.second.m_nTransferStartTime) > (getSelectionTimeout()+2) )
+ {
+ aTimeouts.push_back( incrementalTransfer.first );
+#if OSL_DEBUG_LEVEL > 1
+ const IncrementalTransfer& rInc = incrementalTransfer.second;
+ SAL_INFO("vcl.unx.dtrans",
+ "timeout on INCR transfer for window "
+ << std::showbase << std::hex
+ << rInc.m_aRequestor
+ << ", property "
+ << getString( rInc.m_aProperty )
+ << ", type "
+ << getString( rInc.m_aTarget ));
+#endif
+ }
+ }
+
+ for (auto const& timeout : aTimeouts)
+ {
+ // transfer broken, might even be a new client with the
+ // same window id
+ it->second.erase( timeout );
+ }
+ aTimeouts.clear();
+
+ auto inc_it = it->second.find( rNotify.atom );
+ if( inc_it != it->second.end() )
+ {
+ IncrementalTransfer& rInc = inc_it->second;
+
+ int nBytes = rInc.m_aData.getLength() - rInc.m_nBufferPos;
+ nBytes = std::min(nBytes, m_nIncrementalThreshold);
+ if( nBytes < 0 ) // sanity check
+ nBytes = 0;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "pushing "
+ << nBytes
+ << " bytes: \""
+ << std::setw(std::min(nBytes, 32))
+ << ((const unsigned char*)
+ rInc.m_aData.getConstArray()+rInc.m_nBufferPos)
+ << "\"...");
+#endif
+ std::size_t nUnitSize = GetTrueFormatSize(rInc.m_nFormat);
+
+ XChangeProperty( m_pDisplay,
+ rInc.m_aRequestor,
+ rInc.m_aProperty,
+ rInc.m_aTarget,
+ rInc.m_nFormat,
+ PropModeReplace,
+ reinterpret_cast<const unsigned char*>(rInc.m_aData.getConstArray())+rInc.m_nBufferPos,
+ nBytes/nUnitSize );
+ rInc.m_nBufferPos += nBytes;
+ rInc.m_nTransferStartTime = nCurrentTime;
+
+ if( nBytes == 0 ) // transfer finished
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "finished INCR transfer for "
+ << "window "
+ << std::showbase << std::hex
+ << rInc.m_aRequestor
+ << ", property "
+ << getString( rInc.m_aProperty )
+ << ", type "
+ << getString( rInc.m_aTarget ));
+#endif
+ it->second.erase( inc_it );
+ }
+
+ }
+ // eventually clean up the hash map
+ if( it->second.empty() )
+ m_aIncrementals.erase( it );
+ }
+ }
+ return bHandled;
+}
+
+bool SelectionManager::handleSelectionNotify( XSelectionEvent const & rNotify )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+
+ bool bHandled = false;
+
+ // notification about success/failure of one of our conversion requests
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "handleSelectionNotify for selection "
+ << getString( rNotify.selection )
+ << " and property "
+ << (rNotify.property ? getString( rNotify.property ) : "None")
+ << " ("
+ << std::showbase << std::hex
+ << rNotify.property
+ << ").");
+ SAL_WARN_IF(rNotify.requestor != m_aWindow &&
+ rNotify.requestor != m_aCurrentDropWindow,
+ "vcl.unx.dtrans", "selection notify for unknown window "
+ << std::showbase << std::hex
+ << rNotify.requestor);
+#endif
+ std::unordered_map< Atom, Selection* >::iterator it =
+ m_aSelections.find( rNotify.selection );
+ if (
+ (rNotify.requestor == m_aWindow || rNotify.requestor == m_aCurrentDropWindow) &&
+ it != m_aSelections.end() &&
+ (
+ (it->second->m_eState == Selection::WaitingForResponse) ||
+ (it->second->m_eState == Selection::WaitingForData)
+ )
+ )
+ {
+ bHandled = true;
+ if( it->second->m_aRequestedType == m_nMULTIPLEAtom )
+ {
+ Atom nType = None;
+ int nFormat = 0;
+ unsigned long nItems = 0, nBytes = 0;
+ unsigned char* pData = nullptr;
+
+ // get type and length
+ XGetWindowProperty( m_pDisplay,
+ rNotify.requestor,
+ rNotify.property,
+ 0, 256,
+ False,
+ AnyPropertyType,
+ &nType, &nFormat,
+ &nItems, &nBytes,
+ &pData );
+ if( nBytes ) // HUGE request !!!
+ {
+ if( pData )
+ XFree( pData );
+ XGetWindowProperty( m_pDisplay,
+ rNotify.requestor,
+ rNotify.property,
+ 0, 256+(nBytes+3)/4,
+ False,
+ AnyPropertyType,
+ &nType, &nFormat,
+ &nItems, &nBytes,
+ &pData );
+ }
+ it->second->m_eState = Selection::Inactive;
+ std::size_t nUnitSize = GetTrueFormatSize(nFormat);
+ it->second->m_aData = Sequence< sal_Int8 >(reinterpret_cast<sal_Int8*>(pData), nItems * nUnitSize);
+ it->second->m_aDataArrived.set();
+ if( pData )
+ XFree( pData );
+ }
+ // WaitingForData can actually happen; some
+ // applications (e.g. cmdtool on Solaris) first send
+ // a success and then cancel it. Weird !
+ else if( rNotify.property == None )
+ {
+ // conversion failed, stop transfer
+ it->second->m_eState = Selection::Inactive;
+ it->second->m_aData = Sequence< sal_Int8 >();
+ it->second->m_aDataArrived.set();
+ }
+ // get the bytes, by INCR if necessary
+ else
+ it->second->m_eState = Selection::WaitingForData;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else if( it != m_aSelections.end() )
+ SAL_WARN("vcl.unx.dtrans", "selection in state " << it->second->m_eState);
+#endif
+ return bHandled;
+}
+
+bool SelectionManager::handleDropEvent( XClientMessageEvent const & rMessage )
+{
+ osl::ResettableMutexGuard aGuard(m_aMutex);
+
+ // handle drop related events
+ ::Window aSource = rMessage.data.l[0];
+ ::Window aTarget = rMessage.window;
+
+ bool bHandled = false;
+
+ std::unordered_map< ::Window, DropTargetEntry >::iterator it =
+ m_aDropTargets.find( aTarget );
+
+#if OSL_DEBUG_LEVEL > 1
+ if( rMessage.message_type == m_nXdndEnter ||
+ rMessage.message_type == m_nXdndLeave ||
+ rMessage.message_type == m_nXdndDrop ||
+ rMessage.message_type == m_nXdndPosition )
+ {
+ std::ostringstream oss;
+ oss << "got drop event "
+ << getString( rMessage.message_type )
+ << ", ";
+
+ if( it == m_aDropTargets.end() )
+ oss << "but no target found.";
+ else if( ! it->second.m_pTarget->m_bActive )
+ oss << "but target is inactive.";
+ else if( m_aDropEnterEvent.data.l[0] != None && (::Window)m_aDropEnterEvent.data.l[0] != aSource )
+ oss << "but source "
+ << std::showbase << std::hex
+ << aSource
+ << " is unknown (expected "
+ << m_aDropEnterEvent.data.l[0]
+ << " or 0).";
+ else
+ oss << "processing.";
+ SAL_INFO("vcl.unx.dtrans", oss.str());
+ }
+#endif
+
+ if( it != m_aDropTargets.end() && it->second.m_pTarget->m_bActive &&
+ m_bDropWaitingForCompletion && m_aDropEnterEvent.data.l[0] )
+ {
+ bHandled = true;
+ OSL_FAIL( "someone forgot to call dropComplete ?" );
+ // some listener forgot to call dropComplete in the last operation
+ // let us end it now and accept the new enter event
+ aGuard.clear();
+ dropComplete( false, m_aCurrentDropWindow );
+ aGuard.reset();
+ }
+
+ if( it != m_aDropTargets.end() &&
+ it->second.m_pTarget->m_bActive &&
+ ( m_aDropEnterEvent.data.l[0] == None || ::Window(m_aDropEnterEvent.data.l[0]) == aSource )
+ )
+ {
+ if( rMessage.message_type == m_nXdndEnter )
+ {
+ bHandled = true;
+ m_aDropEnterEvent = rMessage;
+ m_bDropEnterSent = false;
+ m_aCurrentDropWindow = aTarget;
+ m_nCurrentProtocolVersion = m_aDropEnterEvent.data.l[1] >> 24;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "received XdndEnter on "
+ << std::showbase << std::hex
+ << aTarget);
+#endif
+ }
+ else if(
+ rMessage.message_type == m_nXdndPosition &&
+ aSource == ::Window(m_aDropEnterEvent.data.l[0])
+ )
+ {
+ bHandled = true;
+ m_nDropTime = m_nCurrentProtocolVersion > 0 ? rMessage.data.l[3] : CurrentTime;
+
+ ::Window aChild;
+ XTranslateCoordinates( m_pDisplay,
+ it->second.m_aRootWindow,
+ it->first,
+ rMessage.data.l[2] >> 16,
+ rMessage.data.l[2] & 0xffff,
+ &m_nLastX, &m_nLastY,
+ &aChild );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "received XdndPosition on "
+ << std::showbase << std::hex
+ << aTarget
+ << " ("
+ << std::dec
+ << m_nLastX
+ << ", "
+ << m_nLastY
+ << ").");
+#endif
+ DropTargetDragEnterEvent aEvent;
+ aEvent.Source = static_cast< XDropTarget* >(it->second.m_pTarget);
+ aEvent.Context = new DropTargetDragContext( m_aCurrentDropWindow, *this );
+ aEvent.LocationX = m_nLastX;
+ aEvent.LocationY = m_nLastY;
+ aEvent.SourceActions = m_nSourceActions;
+ if( m_nCurrentProtocolVersion < 2 )
+ aEvent.DropAction = DNDConstants::ACTION_COPY;
+ else if( Atom(rMessage.data.l[4]) == m_nXdndActionCopy )
+ aEvent.DropAction = DNDConstants::ACTION_COPY;
+ else if( Atom(rMessage.data.l[4]) == m_nXdndActionMove )
+ aEvent.DropAction = DNDConstants::ACTION_MOVE;
+ else if( Atom(rMessage.data.l[4]) == m_nXdndActionLink )
+ aEvent.DropAction = DNDConstants::ACTION_LINK;
+ else if( Atom(rMessage.data.l[4]) == m_nXdndActionAsk )
+ // currently no interface to implement ask
+ aEvent.DropAction = ~0;
+ else
+ aEvent.DropAction = DNDConstants::ACTION_NONE;
+
+ m_nLastDropAction = aEvent.DropAction;
+ if( ! m_bDropEnterSent )
+ {
+ m_bDropEnterSent = true;
+ aEvent.SupportedDataFlavors = m_xDropTransferable->getTransferDataFlavors();
+ aGuard.clear();
+ it->second->dragEnter( aEvent );
+ }
+ else
+ {
+ aGuard.clear();
+ it->second->dragOver( aEvent );
+ }
+ }
+ else if(
+ rMessage.message_type == m_nXdndLeave &&
+ aSource == ::Window(m_aDropEnterEvent.data.l[0])
+ )
+ {
+ bHandled = true;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "received XdndLeave on "
+ << std::showbase << std::hex
+ << aTarget);
+#endif
+ DropTargetEvent aEvent;
+ aEvent.Source = static_cast< XDropTarget* >(it->second.m_pTarget);
+ m_aDropEnterEvent.data.l[0] = None;
+ if( m_aCurrentDropWindow == aTarget )
+ m_aCurrentDropWindow = None;
+ m_nCurrentProtocolVersion = nXdndProtocolRevision;
+ aGuard.clear();
+ it->second->dragExit( aEvent );
+ }
+ else if(
+ rMessage.message_type == m_nXdndDrop &&
+ aSource == ::Window(m_aDropEnterEvent.data.l[0])
+ )
+ {
+ bHandled = true;
+ m_nDropTime = m_nCurrentProtocolVersion > 0 ? rMessage.data.l[2] : CurrentTime;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "received XdndDrop on "
+ << std::showbase << std::hex
+ << aTarget
+ << " ("
+ << m_nLastX
+ << ", "
+ << m_nLastY
+ << ").");
+#endif
+ if( m_bLastDropAccepted )
+ {
+ DropTargetDropEvent aEvent;
+ aEvent.Source = static_cast< XDropTarget* >(it->second.m_pTarget);
+ aEvent.Context = new DropTargetDropContext( m_aCurrentDropWindow, *this );
+ aEvent.LocationX = m_nLastX;
+ aEvent.LocationY = m_nLastY;
+ aEvent.DropAction = m_nLastDropAction;
+ // there is nothing corresponding to source supported actions
+ // every source can do link, copy and move
+ aEvent.SourceActions= m_nLastDropAction;
+ aEvent.Transferable = m_xDropTransferable;
+
+ m_bDropWaitingForCompletion = true;
+ aGuard.clear();
+ it->second->drop( aEvent );
+ }
+ else
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "XdndDrop canceled due to "
+ << "m_bLastDropAccepted = false." );
+#endif
+ DropTargetEvent aEvent;
+ aEvent.Source = static_cast< XDropTarget* >(it->second.m_pTarget);
+ aGuard.clear();
+ it->second->dragExit( aEvent );
+ // reset the drop status, notify source
+ dropComplete( false, m_aCurrentDropWindow );
+ }
+ }
+ }
+ return bHandled;
+}
+
+/*
+ * methods for XDropTargetDropContext
+ */
+
+void SelectionManager::dropComplete( bool bSuccess, ::Window aDropWindow )
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ if( aDropWindow == m_aCurrentDropWindow )
+ {
+ if( m_xDragSourceListener.is() )
+ {
+ DragSourceDropEvent dsde;
+ dsde.Source = getXWeak();
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = getUserDragAction();
+ dsde.DropSuccess = bSuccess;
+ css::uno::Reference< XDragSourceListener > xListener = m_xDragSourceListener;
+ m_xDragSourceListener.clear();
+
+ aGuard.clear();
+ xListener->dragDropEnd( dsde );
+ }
+ else if( m_aDropEnterEvent.data.l[0] && m_aCurrentDropWindow )
+ {
+ XEvent aEvent;
+ aEvent.xclient.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.window = m_aDropEnterEvent.data.l[0];
+ aEvent.xclient.message_type = m_nXdndFinished;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = m_aCurrentDropWindow;
+ aEvent.xclient.data.l[1] = bSuccess ? 1 : 0;
+ aEvent.xclient.data.l[2] = 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+ if( bSuccess )
+ {
+ if( m_nLastDropAction & DNDConstants::ACTION_MOVE )
+ aEvent.xclient.data.l[2] = m_nXdndActionMove;
+ else if( m_nLastDropAction & DNDConstants::ACTION_COPY )
+ aEvent.xclient.data.l[2] = m_nXdndActionCopy;
+ else if( m_nLastDropAction & DNDConstants::ACTION_LINK )
+ aEvent.xclient.data.l[2] = m_nXdndActionLink;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "Sending XdndFinished to "
+ << std::showbase << std::hex
+ << m_aDropEnterEvent.data.l[0]);
+#endif
+ XSendEvent( m_pDisplay, m_aDropEnterEvent.data.l[0],
+ False, NoEventMask, & aEvent );
+
+ m_aDropEnterEvent.data.l[0] = None;
+ m_aCurrentDropWindow = None;
+ m_nCurrentProtocolVersion = nXdndProtocolRevision;
+ }
+ m_bDropWaitingForCompletion = false;
+ }
+ else
+ OSL_FAIL( "dropComplete from invalid DropTargetDropContext" );
+}
+
+/*
+ * methods for XDropTargetDragContext
+ */
+
+void SelectionManager::sendDragStatus( Atom nDropAction )
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ if( m_xDragSourceListener.is() )
+ {
+ sal_Int8 nNewDragAction;
+ if( nDropAction == m_nXdndActionMove )
+ nNewDragAction = DNDConstants::ACTION_MOVE;
+ else if( nDropAction == m_nXdndActionCopy )
+ nNewDragAction = DNDConstants::ACTION_COPY;
+ else if( nDropAction == m_nXdndActionLink )
+ nNewDragAction = DNDConstants::ACTION_LINK;
+ else
+ nNewDragAction = DNDConstants::ACTION_NONE;
+ nNewDragAction &= m_nSourceActions;
+
+ if( nNewDragAction != m_nTargetAcceptAction )
+ {
+ setCursor( getDefaultCursor( nNewDragAction ), m_aDropWindow );
+ m_nTargetAcceptAction = nNewDragAction;
+ }
+
+ DragSourceDragEvent dsde;
+ dsde.Source = getXWeak();
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = m_nSourceActions;
+ dsde.UserAction = getUserDragAction();
+
+ css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener );
+ // caution: do not change anything after this
+ aGuard.clear();
+ if( xListener.is() )
+ xListener->dragOver( dsde );
+ }
+ else if( m_aDropEnterEvent.data.l[0] && m_aCurrentDropWindow )
+ {
+ XEvent aEvent;
+ aEvent.xclient.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.window = m_aDropEnterEvent.data.l[0];
+ aEvent.xclient.message_type = m_nXdndStatus;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = m_aCurrentDropWindow;
+ aEvent.xclient.data.l[1] = 2;
+ if( nDropAction == m_nXdndActionMove ||
+ nDropAction == m_nXdndActionLink ||
+ nDropAction == m_nXdndActionCopy )
+ aEvent.xclient.data.l[1] |= 1;
+ aEvent.xclient.data.l[2] = 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = m_nCurrentProtocolVersion > 1 ? nDropAction : 0;
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "Sending XdndStatus to "
+ << std::showbase << std::hex
+ << m_aDropEnterEvent.data.l[0]
+ << " with action "
+ << getString( nDropAction ));
+#endif
+
+ XSendEvent( m_pDisplay, m_aDropEnterEvent.data.l[0],
+ False, NoEventMask, & aEvent );
+ XFlush( m_pDisplay );
+ }
+}
+
+sal_Int8 SelectionManager::getUserDragAction() const
+{
+ return (m_nTargetAcceptAction != DNDConstants::ACTION_DEFAULT) ? m_nTargetAcceptAction : m_nUserDragAction;
+}
+
+bool SelectionManager::updateDragAction( int modifierState )
+{
+ bool bRet = false;
+
+ sal_Int8 nNewDropAction = DNDConstants::ACTION_MOVE;
+ if( ( modifierState & ShiftMask ) && ! ( modifierState & ControlMask ) )
+ nNewDropAction = DNDConstants::ACTION_MOVE;
+ else if( ( modifierState & ControlMask ) && ! ( modifierState & ShiftMask ) )
+ nNewDropAction = DNDConstants::ACTION_COPY;
+ else if( ( modifierState & ShiftMask ) && ( modifierState & ControlMask ) )
+ nNewDropAction = DNDConstants::ACTION_LINK;
+ if( m_nCurrentProtocolVersion < 0 && m_aDropWindow != None )
+ nNewDropAction = DNDConstants::ACTION_COPY;
+ nNewDropAction &= m_nSourceActions;
+
+ if( ! ( modifierState & ( ControlMask | ShiftMask ) ) )
+ {
+ if( ! nNewDropAction )
+ {
+ // default to an action so the user does not have to press
+ // keys explicitly
+ if( m_nSourceActions & DNDConstants::ACTION_MOVE )
+ nNewDropAction = DNDConstants::ACTION_MOVE;
+ else if( m_nSourceActions & DNDConstants::ACTION_COPY )
+ nNewDropAction = DNDConstants::ACTION_COPY;
+ else if( m_nSourceActions & DNDConstants::ACTION_LINK )
+ nNewDropAction = DNDConstants::ACTION_LINK;
+ }
+ nNewDropAction |= DNDConstants::ACTION_DEFAULT;
+ }
+
+ if( nNewDropAction != m_nUserDragAction || m_nTargetAcceptAction != DNDConstants::ACTION_DEFAULT )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "updateDragAction: "
+ << std::hex
+ << (int)m_nUserDragAction
+ << " -> "
+ << (int)nNewDropAction);
+#endif
+ bRet = true;
+ m_nUserDragAction = nNewDropAction;
+
+ DragSourceDragEvent dsde;
+ dsde.Source = getXWeak();
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = m_nUserDragAction;
+ dsde.UserAction = m_nUserDragAction;
+ m_nTargetAcceptAction = DNDConstants::ACTION_DEFAULT; // invalidate last accept
+ m_xDragSourceListener->dropActionChanged( dsde );
+ }
+ return bRet;
+}
+
+void SelectionManager::sendDropPosition( bool bForce, Time eventTime )
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ if( m_bDropSent )
+ return;
+
+ std::unordered_map< ::Window, DropTargetEntry >::const_iterator it =
+ m_aDropTargets.find( m_aDropWindow );
+ if( it != m_aDropTargets.end() )
+ {
+ if( it->second.m_pTarget->m_bActive )
+ {
+ int x, y;
+ ::Window aChild;
+ XTranslateCoordinates( m_pDisplay, it->second.m_aRootWindow, m_aDropWindow, m_nLastDragX, m_nLastDragY, &x, &y, &aChild );
+ DropTargetDragEvent dtde;
+ dtde.Source = it->second.m_pTarget->getXWeak();
+ dtde.Context = new DropTargetDragContext( m_aCurrentDropWindow, *this );
+ dtde.LocationX = x;
+ dtde.LocationY = y;
+ dtde.DropAction = getUserDragAction();
+ dtde.SourceActions = m_nSourceActions;
+ aGuard.clear();
+ it->second->dragOver( dtde );
+ }
+ }
+ else if( bForce ||
+
+ m_nLastDragX < m_nNoPosX || m_nLastDragX >= m_nNoPosX+m_nNoPosWidth ||
+ m_nLastDragY < m_nNoPosY || m_nLastDragY >= m_nNoPosY+m_nNoPosHeight
+ )
+ {
+ // send XdndPosition
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.message_type = m_nXdndPosition;
+ aEvent.xclient.window = m_aDropWindow;
+ aEvent.xclient.data.l[0] = m_aWindow;
+ aEvent.xclient.data.l[1] = 0;
+ aEvent.xclient.data.l[2] = m_nLastDragX << 16 | (m_nLastDragY&0xffff);
+ aEvent.xclient.data.l[3] = eventTime;
+
+ if( m_nUserDragAction & DNDConstants::ACTION_COPY )
+ aEvent.xclient.data.l[4]=m_nXdndActionCopy;
+ else if( m_nUserDragAction & DNDConstants::ACTION_MOVE )
+ aEvent.xclient.data.l[4]=m_nXdndActionMove;
+ else if( m_nUserDragAction & DNDConstants::ACTION_LINK )
+ aEvent.xclient.data.l[4]=m_nXdndActionLink;
+ else
+ aEvent.xclient.data.l[4]=m_nXdndActionCopy;
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+ m_nNoPosX = m_nNoPosY = m_nNoPosWidth = m_nNoPosHeight = 0;
+ }
+}
+
+bool SelectionManager::handleDragEvent( XEvent const & rMessage )
+{
+ if( ! m_xDragSourceListener.is() )
+ return false;
+
+ osl::ResettableMutexGuard aGuard(m_aMutex);
+
+ bool bHandled = false;
+
+ // for shortcut
+ std::unordered_map< ::Window, DropTargetEntry >::const_iterator it =
+ m_aDropTargets.find( m_aDropWindow );
+
+#if OSL_DEBUG_LEVEL > 1
+ switch( rMessage.type )
+ {
+ case ClientMessage:
+ SAL_INFO("vcl.unx.dtrans", "handleDragEvent: "
+ << getString( rMessage.xclient.message_type ));
+ break;
+ case MotionNotify:
+ break;
+ case EnterNotify:
+ SAL_INFO("vcl.unx.dtrans", "handleDragEvent: EnterNotify.");
+ break;
+ case LeaveNotify:
+ SAL_INFO("vcl.unx.dtrans", "handleDragEvent: LeaveNotify.");
+ break;
+ case ButtonPress:
+ SAL_INFO("vcl.unx.dtrans", "handleDragEvent: ButtonPress "
+ << rMessage.xbutton.button
+ << " (m_nDragButton = "
+ << m_nDragButton
+ << ").");
+ break;
+ case ButtonRelease:
+ SAL_INFO("vcl.unx.dtrans", "handleDragEvent: ButtonRelease "
+ << rMessage.xbutton.button
+ << " (m_nDragButton = "
+ << m_nDragButton
+ << ").");
+ break;
+ case KeyPress:
+ SAL_INFO("vcl.unx.dtrans", "handleDragEvent: KeyPress.");
+ break;
+ case KeyRelease:
+ SAL_INFO("vcl.unx.dtrans", "handleDragEvent: KeyRelease.");
+ break;
+ default:
+ SAL_INFO("vcl.unx.dtrans", "handleDragEvent: <unknown type "
+ << rMessage.type
+ << ">.");
+ break;
+ }
+#endif
+
+ // handle drag related events
+ if( rMessage.type == ClientMessage )
+ {
+ if( rMessage.xclient.message_type == m_nXdndStatus && Atom(rMessage.xclient.data.l[0]) == m_aDropWindow )
+ {
+ bHandled = true;
+ DragSourceDragEvent dsde;
+ dsde.Source = getXWeak();
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >( this );
+ dsde.UserAction = getUserDragAction();
+ dsde.DropAction = DNDConstants::ACTION_NONE;
+ m_bDropSuccess = (rMessage.xclient.data.l[1] & 1) != 0;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "status drop action: accept = "
+ << (m_bDropSuccess ? "true" : "false")
+ << ", "
+ << getString( rMessage.xclient.data.l[4] ));
+#endif
+ if( rMessage.xclient.data.l[1] & 1 )
+ {
+ if( m_nCurrentProtocolVersion > 1 )
+ {
+ if( Atom(rMessage.xclient.data.l[4]) == m_nXdndActionCopy )
+ dsde.DropAction = DNDConstants::ACTION_COPY;
+ else if( Atom(rMessage.xclient.data.l[4]) == m_nXdndActionMove )
+ dsde.DropAction = DNDConstants::ACTION_MOVE;
+ else if( Atom(rMessage.xclient.data.l[4]) == m_nXdndActionLink )
+ dsde.DropAction = DNDConstants::ACTION_LINK;
+ }
+ else
+ dsde.DropAction = DNDConstants::ACTION_COPY;
+ }
+ m_nTargetAcceptAction = dsde.DropAction;
+
+ if( ! ( rMessage.xclient.data.l[1] & 2 ) )
+ {
+ m_nNoPosX = rMessage.xclient.data.l[2] >> 16;
+ m_nNoPosY = rMessage.xclient.data.l[2] & 0xffff;
+ m_nNoPosWidth = rMessage.xclient.data.l[3] >> 16;
+ m_nNoPosHeight = rMessage.xclient.data.l[3] & 0xffff;
+ }
+ else
+ m_nNoPosX = m_nNoPosY = m_nNoPosWidth = m_nNoPosHeight = 0;
+
+ setCursor( getDefaultCursor( dsde.DropAction ), m_aDropWindow );
+ aGuard.clear();
+ m_xDragSourceListener->dragOver( dsde );
+ }
+ else if( rMessage.xclient.message_type == m_nXdndFinished && m_aDropWindow == Atom(rMessage.xclient.data.l[0]) )
+ {
+ bHandled = true;
+ // notify the listener
+ DragSourceDropEvent dsde;
+ dsde.Source = getXWeak();
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = m_nTargetAcceptAction;
+ dsde.DropSuccess = m_bDropSuccess;
+ css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener );
+ m_xDragSourceListener.clear();
+ aGuard.clear();
+ xListener->dragDropEnd( dsde );
+ }
+ }
+ else if( rMessage.type == MotionNotify ||
+ rMessage.type == EnterNotify || rMessage.type == LeaveNotify
+ )
+ {
+ bHandled = true;
+ bool bForce = false;
+ int root_x = rMessage.type == MotionNotify ? rMessage.xmotion.x_root : rMessage.xcrossing.x_root;
+ int root_y = rMessage.type == MotionNotify ? rMessage.xmotion.y_root : rMessage.xcrossing.y_root;
+ ::Window root = rMessage.type == MotionNotify ? rMessage.xmotion.root : rMessage.xcrossing.root;
+
+ aGuard.clear();
+ if( rMessage.type == MotionNotify )
+ {
+ bForce = updateDragAction( rMessage.xmotion.state );
+ }
+ updateDragWindow( root_x, root_y, root );
+ aGuard.reset();
+
+ if( m_nCurrentProtocolVersion >= 0 && m_aDropProxy != None )
+ {
+ aGuard.clear();
+ sendDropPosition( bForce, rMessage.type == MotionNotify ? rMessage.xmotion.time : rMessage.xcrossing.time );
+ }
+ }
+ else if( rMessage.type == KeyPress || rMessage.type == KeyRelease )
+ {
+ bHandled = true;
+ KeySym aKey = XkbKeycodeToKeysym( m_pDisplay, rMessage.xkey.keycode, 0, 0 );
+ if( aKey == XK_Escape )
+ {
+ // abort drag
+ if( it != m_aDropTargets.end() )
+ {
+ DropTargetEvent dte;
+ dte.Source = it->second.m_pTarget->getXWeak();
+ aGuard.clear();
+ it->second.m_pTarget->dragExit( dte );
+ aGuard.reset();
+ }
+ else if( m_aDropProxy != None && m_nCurrentProtocolVersion >= 0 )
+ {
+ // send XdndLeave
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.message_type = m_nXdndLeave;
+ aEvent.xclient.window = m_aDropWindow;
+ aEvent.xclient.data.l[0] = m_aWindow;
+ memset( aEvent.xclient.data.l+1, 0, sizeof(long)*4);
+ m_aDropWindow = m_aDropProxy = None;
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+ }
+ // notify the listener
+ DragSourceDropEvent dsde;
+ dsde.Source = getXWeak();
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = DNDConstants::ACTION_NONE;
+ dsde.DropSuccess = false;
+ css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener );
+ m_xDragSourceListener.clear();
+ aGuard.clear();
+ xListener->dragDropEnd( dsde );
+ }
+ else
+ {
+ /*
+ * man page says: state is state immediate PRIOR to the
+ * event. It would seem that this is a somewhat arguable
+ * design decision.
+ */
+ int nState = rMessage.xkey.state;
+ int nNewState = 0;
+ switch( aKey )
+ {
+ case XK_Shift_R:
+ case XK_Shift_L: nNewState = ShiftMask;break;
+ case XK_Control_R:
+ case XK_Control_L: nNewState = ControlMask;break;
+ // just interested in shift and ctrl for dnd
+ }
+ if( rMessage.type == KeyPress )
+ nState |= nNewState;
+ else
+ nState &= ~nNewState;
+ aGuard.clear();
+ if( updateDragAction( nState ) )
+ sendDropPosition( true, rMessage.xkey.time );
+ }
+ }
+ else if(
+ ( rMessage.type == ButtonPress || rMessage.type == ButtonRelease ) &&
+ rMessage.xbutton.button == m_nDragButton )
+ {
+ bool bCancel = true;
+ if( m_aDropWindow != None )
+ {
+ if( it != m_aDropTargets.end() )
+ {
+ if( it->second.m_pTarget->m_bActive && m_nUserDragAction != DNDConstants::ACTION_NONE && m_bLastDropAccepted )
+ {
+ bHandled = true;
+ int x, y;
+ ::Window aChild;
+ XTranslateCoordinates( m_pDisplay, rMessage.xbutton.root, m_aDropWindow, rMessage.xbutton.x_root, rMessage.xbutton.y_root, &x, &y, &aChild );
+ DropTargetDropEvent dtde;
+ dtde.Source = it->second.m_pTarget->getXWeak();
+ dtde.Context = new DropTargetDropContext( m_aCurrentDropWindow, *this );
+ dtde.LocationX = x;
+ dtde.LocationY = y;
+ dtde.DropAction = m_nUserDragAction;
+ dtde.SourceActions = m_nSourceActions;
+ dtde.Transferable = m_xDragSourceTransferable;
+ m_bDropSent = true;
+ m_nDropTimeout = time( nullptr );
+ m_bDropWaitingForCompletion = true;
+ aGuard.clear();
+ it->second->drop( dtde );
+ bCancel = false;
+ }
+ else bCancel = true;
+ }
+ else if( m_nCurrentProtocolVersion >= 0 )
+ {
+ bHandled = true;
+
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.message_type = m_nXdndDrop;
+ aEvent.xclient.window = m_aDropWindow;
+ aEvent.xclient.data.l[0] = m_aWindow;
+ aEvent.xclient.data.l[1] = 0;
+ aEvent.xclient.data.l[2] = rMessage.xbutton.time;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+
+ m_bDropSent = true;
+ m_nDropTimeout = time( nullptr );
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+ bCancel = false;
+ }
+ else
+ {
+ // dropping on non XdndWindows: acquire ownership of
+ // PRIMARY and send a middle mouse button click down/up to
+ // target window
+ SelectionAdaptor* pAdaptor = getAdaptor( XA_PRIMARY );
+ if( pAdaptor )
+ {
+ bHandled = true;
+
+ ::Window aDummy;
+ XEvent aEvent;
+ aEvent.type = ButtonPress;
+ aEvent.xbutton.display = m_pDisplay;
+ aEvent.xbutton.window = m_aDropWindow;
+ aEvent.xbutton.root = rMessage.xbutton.root;
+ aEvent.xbutton.subwindow = m_aDropWindow;
+ aEvent.xbutton.time = rMessage.xbutton.time+1;
+ aEvent.xbutton.x_root = rMessage.xbutton.x_root;
+ aEvent.xbutton.y_root = rMessage.xbutton.y_root;
+ aEvent.xbutton.state = rMessage.xbutton.state;
+ aEvent.xbutton.button = Button2;
+ aEvent.xbutton.same_screen = True;
+ XTranslateCoordinates( m_pDisplay,
+ rMessage.xbutton.root, m_aDropWindow,
+ rMessage.xbutton.x_root, rMessage.xbutton.y_root,
+ &aEvent.xbutton.x, &aEvent.xbutton.y,
+ &aDummy );
+ XSendEvent( m_pDisplay, m_aDropWindow, False, ButtonPressMask, &aEvent );
+ aEvent.xbutton.type = ButtonRelease;
+ aEvent.xbutton.time++;
+ aEvent.xbutton.state |= Button2Mask;
+ XSendEvent( m_pDisplay, m_aDropWindow, False, ButtonReleaseMask, &aEvent );
+
+ m_bDropSent = true;
+ m_nDropTimeout = time( nullptr );
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+ m_bWaitingForPrimaryConversion = true;
+ m_bDropSent = true;
+ m_nDropTimeout = time( nullptr );
+ // HACK :-)
+ aGuard.clear();
+ static_cast< X11Clipboard* >( pAdaptor )->setContents( m_xDragSourceTransferable, css::uno::Reference< css::datatransfer::clipboard::XClipboardOwner >() );
+ aGuard.reset();
+ bCancel = false;
+ }
+ }
+ }
+ if( bCancel )
+ {
+ // cancel drag
+ DragSourceDropEvent dsde;
+ dsde.Source = getXWeak();
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = DNDConstants::ACTION_NONE;
+ dsde.DropSuccess = false;
+ css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener );
+ m_xDragSourceListener.clear();
+ aGuard.clear();
+ xListener->dragDropEnd( dsde );
+ bHandled = true;
+ }
+ }
+ return bHandled;
+}
+
+void SelectionManager::accept( sal_Int8 dragOperation, ::Window aDropWindow )
+{
+ if( aDropWindow != m_aCurrentDropWindow )
+ return;
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "accept: " << std::hex << dragOperation);
+#endif
+ Atom nAction = None;
+ dragOperation &= (DNDConstants::ACTION_MOVE | DNDConstants::ACTION_COPY | DNDConstants::ACTION_LINK);
+ if( dragOperation & DNDConstants::ACTION_MOVE )
+ nAction = m_nXdndActionMove;
+ else if( dragOperation & DNDConstants::ACTION_COPY )
+ nAction = m_nXdndActionCopy;
+ else if( dragOperation & DNDConstants::ACTION_LINK )
+ nAction = m_nXdndActionLink;
+ m_bLastDropAccepted = true;
+ sendDragStatus( nAction );
+}
+
+void SelectionManager::reject( ::Window aDropWindow )
+{
+ if( aDropWindow != m_aCurrentDropWindow )
+ return;
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "reject.");
+#endif
+ m_bLastDropAccepted = false;
+ sendDragStatus( None );
+ if( m_bDropSent && m_xDragSourceListener.is() )
+ {
+ DragSourceDropEvent dsde;
+ dsde.Source = getXWeak();
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = DNDConstants::ACTION_NONE;
+ dsde.DropSuccess = false;
+ m_xDragSourceListener->dragDropEnd( dsde );
+ m_xDragSourceListener.clear();
+ }
+}
+
+/*
+ * XDragSource
+ */
+
+sal_Bool SelectionManager::isDragImageSupported()
+{
+ return false;
+}
+
+sal_Int32 SelectionManager::getDefaultCursor( sal_Int8 dragAction )
+{
+ Cursor aCursor = m_aNoneCursor;
+ if( dragAction & DNDConstants::ACTION_MOVE )
+ aCursor = m_aMoveCursor;
+ else if( dragAction & DNDConstants::ACTION_COPY )
+ aCursor = m_aCopyCursor;
+ else if( dragAction & DNDConstants::ACTION_LINK )
+ aCursor = m_aLinkCursor;
+ return aCursor;
+}
+
+int SelectionManager::getXdndVersion( ::Window aWindow, ::Window& rProxy )
+{
+ Atom* pProperties = nullptr;
+ int nProperties = 0;
+ Atom nType;
+ int nFormat;
+ unsigned long nItems, nBytes;
+ unsigned char* pBytes = nullptr;
+
+ int nVersion = -1;
+ rProxy = None;
+
+ /*
+ * XListProperties is used here to avoid unnecessary XGetWindowProperty calls
+ * and therefore reducing latency penalty
+ */
+ pProperties = XListProperties( m_pDisplay, aWindow, &nProperties );
+ // first look for proxy
+ int i;
+ for( i = 0; i < nProperties; i++ )
+ {
+ if( pProperties[i] == m_nXdndProxy )
+ {
+ XGetWindowProperty( m_pDisplay, aWindow, m_nXdndProxy, 0, 1, False, XA_WINDOW,
+ &nType, &nFormat, &nItems, &nBytes, &pBytes );
+ if( pBytes )
+ {
+ if( nType == XA_WINDOW )
+ rProxy = *reinterpret_cast< ::Window* >(pBytes);
+ XFree( pBytes );
+ pBytes = nullptr;
+ if( rProxy != None )
+ {
+ // now check proxy whether it points to itself
+ XGetWindowProperty( m_pDisplay, rProxy, m_nXdndProxy, 0, 1, False, XA_WINDOW,
+ &nType, &nFormat, &nItems, &nBytes, &pBytes );
+ if( pBytes )
+ {
+ if( nType == XA_WINDOW && *reinterpret_cast< ::Window* >(pBytes) != rProxy )
+ rProxy = None;
+ XFree( pBytes );
+ pBytes = nullptr;
+ }
+ else
+ rProxy = None;
+ }
+ }
+ break;
+ }
+ }
+ if ( pProperties )
+ XFree (pProperties);
+
+ ::Window aAwareWindow = rProxy != None ? rProxy : aWindow;
+
+ XGetWindowProperty( m_pDisplay, aAwareWindow, m_nXdndAware, 0, 1, False, XA_ATOM,
+ &nType, &nFormat, &nItems, &nBytes, &pBytes );
+ if( pBytes )
+ {
+ if( nType == XA_ATOM )
+ nVersion = *reinterpret_cast<Atom*>(pBytes);
+ XFree( pBytes );
+ }
+
+ nVersion = std::min<int>(nVersion, nXdndProtocolRevision);
+
+ return nVersion;
+}
+
+void SelectionManager::updateDragWindow( int nX, int nY, ::Window aRoot )
+{
+ osl::ResettableMutexGuard aGuard( m_aMutex );
+
+ css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener );
+
+ m_nLastDragX = nX;
+ m_nLastDragY = nY;
+
+ ::Window aParent = aRoot;
+ ::Window aChild;
+ ::Window aNewProxy = None, aNewCurrentWindow = None;
+ int nNewProtocolVersion = -1;
+ int nWinX, nWinY;
+
+ // find the first XdndAware window or check if root window is
+ // XdndAware or has XdndProxy
+ do
+ {
+ XTranslateCoordinates( m_pDisplay, aRoot, aParent, nX, nY, &nWinX, &nWinY, &aChild );
+ if( aChild != None )
+ {
+ if( aChild == m_aCurrentDropWindow && aChild != aRoot && m_nCurrentProtocolVersion >= 0 )
+ {
+ aParent = aChild;
+ break;
+ }
+ nNewProtocolVersion = getXdndVersion( aChild, aNewProxy );
+ aParent = aChild;
+ }
+ } while( aChild != None && nNewProtocolVersion < 0 );
+
+ aNewCurrentWindow = aParent;
+ if( aNewCurrentWindow == aRoot )
+ {
+ // no children, try root drop
+ nNewProtocolVersion = getXdndVersion( aNewCurrentWindow, aNewProxy );
+ if( nNewProtocolVersion < 3 )
+ {
+ aNewCurrentWindow = aNewProxy = None;
+ nNewProtocolVersion = nXdndProtocolRevision;
+ }
+ }
+
+ DragSourceDragEvent dsde;
+ dsde.Source = getXWeak();
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = nNewProtocolVersion >= 0 ? m_nUserDragAction : DNDConstants::ACTION_COPY;
+ dsde.UserAction = nNewProtocolVersion >= 0 ? m_nUserDragAction : DNDConstants::ACTION_COPY;
+
+ std::unordered_map< ::Window, DropTargetEntry >::const_iterator it;
+ if( aNewCurrentWindow != m_aDropWindow )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "drag left window "
+ << std::showbase << std::hex
+ << m_aDropWindow
+ << std::dec
+ << " (rev. "
+ << m_nCurrentProtocolVersion
+ << "), entered window "
+ << std::showbase << std::hex
+ << aNewCurrentWindow
+ << " (rev "
+ << std::dec
+ << nNewProtocolVersion
+ << ").");
+#endif
+ if( m_aDropWindow != None )
+ {
+ it = m_aDropTargets.find( m_aDropWindow );
+ if( it != m_aDropTargets.end() )
+ // shortcut for own drop targets
+ {
+ DropTargetEvent dte;
+ dte.Source = it->second.m_pTarget->getXWeak();
+ aGuard.clear();
+ it->second.m_pTarget->dragExit( dte );
+ aGuard.reset();
+ }
+ else
+ {
+ // send old drop target a XdndLeave
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.message_type = m_nXdndLeave;
+ aEvent.xclient.window = m_aDropWindow;
+ aEvent.xclient.data.l[0] = m_aWindow;
+ aEvent.xclient.data.l[1] = 0;
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+ }
+ if( xListener.is() )
+ {
+ aGuard.clear();
+ xListener->dragExit( dsde );
+ aGuard.reset();
+ }
+ }
+
+ m_nCurrentProtocolVersion = nNewProtocolVersion;
+ m_aDropWindow = aNewCurrentWindow;
+ m_aDropProxy = aNewProxy != None ? aNewProxy : m_aDropWindow;
+
+ it = m_aDropTargets.find( m_aDropWindow );
+ if( it != m_aDropTargets.end() && ! it->second.m_pTarget->m_bActive )
+ m_aDropProxy = None;
+
+ if( m_aDropProxy != None && xListener.is() )
+ {
+ aGuard.clear();
+ xListener->dragEnter( dsde );
+ aGuard.reset();
+ }
+ // send XdndEnter
+ if( m_aDropProxy != None && m_nCurrentProtocolVersion >= 0 )
+ {
+ it = m_aDropTargets.find( m_aDropWindow );
+ if( it != m_aDropTargets.end() )
+ {
+ XTranslateCoordinates( m_pDisplay, aRoot, m_aDropWindow, nX, nY, &nWinX, &nWinY, &aChild );
+ DropTargetDragEnterEvent dtde;
+ dtde.Source = it->second.m_pTarget->getXWeak();
+ dtde.Context = new DropTargetDragContext( m_aCurrentDropWindow, *this );
+ dtde.LocationX = nWinX;
+ dtde.LocationY = nWinY;
+ dtde.DropAction = m_nUserDragAction;
+ dtde.SourceActions = m_nSourceActions;
+ dtde.SupportedDataFlavors = m_xDragSourceTransferable->getTransferDataFlavors();
+ aGuard.clear();
+ it->second.m_pTarget->dragEnter( dtde );
+ aGuard.reset();
+ }
+ else
+ {
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.message_type = m_nXdndEnter;
+ aEvent.xclient.window = m_aDropWindow;
+ aEvent.xclient.data.l[0] = m_aWindow;
+ aEvent.xclient.data.l[1] = m_nCurrentProtocolVersion << 24;
+ memset( aEvent.xclient.data.l + 2, 0, sizeof( long )*3 );
+ // fill in data types
+ ::std::list< Atom > aConversions;
+ getNativeTypeList( m_aDragFlavors, aConversions, m_nXdndSelection );
+ if( aConversions.size() > 3 )
+ aEvent.xclient.data.l[1] |= 1;
+ ::std::list< Atom >::const_iterator type_it = aConversions.begin();
+ for( int i = 0; type_it != aConversions.end() && i < 3; i++, ++type_it )
+ aEvent.xclient.data.l[i+2] = *type_it;
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+ }
+ }
+ m_nNoPosX = m_nNoPosY = m_nNoPosWidth = m_nNoPosHeight = 0;
+ }
+ else if( m_aDropProxy != None && xListener.is() )
+ {
+ aGuard.clear();
+ // drag over for XdndAware windows comes when receiving XdndStatus
+ xListener->dragOver( dsde );
+ }
+}
+
+void SelectionManager::startDrag(
+ const DragGestureEvent& trigger,
+ sal_Int8 sourceActions,
+ sal_Int32,
+ sal_Int32,
+ const css::uno::Reference< XTransferable >& transferable,
+ const css::uno::Reference< XDragSourceListener >& listener
+ )
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "startDrag( sourceActions = "
+ << std::hex
+ << (int)sourceActions
+ << " ).");
+#endif
+ DragSourceDropEvent aDragFailedEvent;
+ aDragFailedEvent.Source = getXWeak();
+ aDragFailedEvent.DragSource = static_cast< XDragSource* >(this);
+ aDragFailedEvent.DragSourceContext = new DragSourceContext( None, *this );
+ aDragFailedEvent.DropAction = DNDConstants::ACTION_NONE;
+ aDragFailedEvent.DropSuccess = false;
+
+ if( m_aDragRunning.check() )
+ {
+ if( listener.is() )
+ listener->dragDropEnd( aDragFailedEvent );
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.unx.dtrans",
+ "*** ERROR *** second drag and drop started.");
+ if( m_xDragSourceListener.is() )
+ SAL_WARN("vcl.unx.dtrans",
+ "*** ERROR *** drag source listener already set.");
+ else
+ SAL_WARN("vcl.unx.dtrans",
+ "*** ERROR *** drag thread already running.");
+#endif
+ return;
+ }
+
+ SalFrame* pCaptureFrame = nullptr;
+
+ {
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ // first get the current pointer position and the window that
+ // the pointer is located in. since said window should be one
+ // of our DropTargets at the time of executeDrag we can use
+ // them for a start
+ ::Window aRoot, aParent, aChild;
+ int root_x(0), root_y(0), win_x(0), win_y(0);
+ unsigned int mask(0);
+
+ bool bPointerFound = false;
+ for (auto const& dropTarget : m_aDropTargets)
+ {
+ if( XQueryPointer( m_pDisplay, dropTarget.second.m_aRootWindow,
+ &aRoot, &aParent,
+ &root_x, &root_y,
+ &win_x, &win_y,
+ &mask ) )
+ {
+ aParent = dropTarget.second.m_aRootWindow;
+ aRoot = aParent;
+ bPointerFound = true;
+ break;
+ }
+ }
+
+ // don't start DnD if there is none of our windows on the same screen as
+ // the pointer or if no mouse button is pressed
+ if( !bPointerFound || (mask & (Button1Mask|Button2Mask|Button3Mask)) == 0 )
+ {
+ aGuard.clear();
+ if( listener.is() )
+ listener->dragDropEnd( aDragFailedEvent );
+ return;
+ }
+
+ // try to find which of our drop targets is the drag source
+ // if that drop target is deregistered we should stop executing
+ // the drag (actually this is a poor substitute for an "endDrag"
+ // method ).
+ m_aDragSourceWindow = None;
+ do
+ {
+ XTranslateCoordinates( m_pDisplay, aRoot, aParent, root_x, root_y, &win_x, &win_y, &aChild );
+ if( aChild != None && m_aDropTargets.find( aChild ) != m_aDropTargets.end() )
+ {
+ m_aDragSourceWindow = aChild;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "found drag source window "
+ << std::showbase << std::hex
+ << m_aDragSourceWindow);
+#endif
+ break;
+ }
+ aParent = aChild;
+ } while( aChild != None );
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "try to grab pointer ...");
+#endif
+ int nPointerGrabSuccess =
+ XGrabPointer( m_pDisplay, aRoot, True,
+ DRAG_EVENT_MASK,
+ GrabModeAsync, GrabModeAsync,
+ None,
+ None,
+ CurrentTime );
+ /* if we could not grab the pointer here, there is a chance
+ that the pointer is grabbed by the other vcl display (the main loop)
+ so let's break that grab and reset it later
+
+ remark: this whole code should really be molten into normal vcl so only
+ one display is used...
+ */
+ if( nPointerGrabSuccess != GrabSuccess )
+ {
+ comphelper::SolarMutex& rSolarMutex( Application::GetSolarMutex() );
+ if( rSolarMutex.tryToAcquire() )
+ {
+ pCaptureFrame = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetCaptureFrame();
+ if( pCaptureFrame )
+ {
+ vcl_sal::getSalDisplay(GetGenericUnixSalData())->CaptureMouse( nullptr );
+ nPointerGrabSuccess =
+ XGrabPointer( m_pDisplay, aRoot, True,
+ DRAG_EVENT_MASK,
+ GrabModeAsync, GrabModeAsync,
+ None,
+ None,
+ CurrentTime );
+ }
+ }
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "... grabbed pointer: "
+ << nPointerGrabSuccess);
+ SAL_INFO("vcl.unx.dtrans", "try to grab keyboard ...");
+#endif
+ int nKeyboardGrabSuccess =
+ XGrabKeyboard( m_pDisplay, aRoot, True,
+ GrabModeAsync, GrabModeAsync, CurrentTime );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "... grabbed keyboard: "
+ << nKeyboardGrabSuccess);
+#endif
+ if( nPointerGrabSuccess != GrabSuccess || nKeyboardGrabSuccess != GrabSuccess )
+ {
+ if( nPointerGrabSuccess == GrabSuccess )
+ XUngrabPointer( m_pDisplay, CurrentTime );
+ if( nKeyboardGrabSuccess == GrabSuccess )
+ XUngrabKeyboard( m_pDisplay, CurrentTime );
+ XFlush( m_pDisplay );
+ aGuard.clear();
+ if( listener.is() )
+ listener->dragDropEnd( aDragFailedEvent );
+ if( pCaptureFrame )
+ {
+ comphelper::SolarMutex& rSolarMutex( Application::GetSolarMutex() );
+ if( rSolarMutex.tryToAcquire() )
+ vcl_sal::getSalDisplay(GetGenericUnixSalData())->CaptureMouse( pCaptureFrame );
+#if OSL_DEBUG_LEVEL > 0
+ else
+ OSL_FAIL( "failed to acquire SolarMutex to reset capture frame" );
+#endif
+ }
+ return;
+ }
+
+ m_xDragSourceTransferable = transferable;
+ m_xDragSourceListener = listener;
+ m_aDragFlavors = transferable->getTransferDataFlavors();
+ m_aCurrentCursor = None;
+
+ requestOwnership( m_nXdndSelection );
+
+ ::std::list< Atom > aConversions;
+ getNativeTypeList( m_aDragFlavors, aConversions, m_nXdndSelection );
+
+ Atom* pTypes = static_cast<Atom*>(alloca( sizeof(Atom)*aConversions.size() ));
+ int nTypes = 0;
+ for (auto const& conversion : aConversions)
+ pTypes[nTypes++] = conversion;
+
+ XChangeProperty( m_pDisplay, m_aWindow, m_nXdndTypeList, XA_ATOM, 32, PropModeReplace, reinterpret_cast<unsigned char*>(pTypes), nTypes );
+
+ m_nSourceActions = sourceActions | DNDConstants::ACTION_DEFAULT;
+ m_nUserDragAction = DNDConstants::ACTION_MOVE & m_nSourceActions;
+ if( ! m_nUserDragAction )
+ m_nUserDragAction = DNDConstants::ACTION_COPY & m_nSourceActions;
+ if( ! m_nUserDragAction )
+ m_nUserDragAction = DNDConstants::ACTION_LINK & m_nSourceActions;
+ m_nTargetAcceptAction = DNDConstants::ACTION_DEFAULT;
+ m_bDropSent = false;
+ m_bDropSuccess = false;
+ m_bWaitingForPrimaryConversion = false;
+ m_nDragButton = Button1; // default to left button
+ css::awt::MouseEvent aEvent;
+ if( trigger.Event >>= aEvent )
+ {
+ if( aEvent.Buttons & MouseButton::LEFT )
+ m_nDragButton = Button1;
+ else if( aEvent.Buttons & MouseButton::RIGHT )
+ m_nDragButton = Button3;
+ else if( aEvent.Buttons & MouseButton::MIDDLE )
+ m_nDragButton = Button2;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "m_nUserDragAction = "
+ << std::hex
+ << (int)m_nUserDragAction);
+#endif
+ updateDragWindow( root_x, root_y, aRoot );
+ m_nUserDragAction = ~0;
+ updateDragAction( mask );
+ }
+
+ m_aDragRunning.set();
+ m_aDragExecuteThread = osl_createSuspendedThread( call_SelectionManager_runDragExecute, this );
+ if( m_aDragExecuteThread )
+ osl_resumeThread( m_aDragExecuteThread );
+ else
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "osl_createSuspendedThread failed "
+ << "for drag execute.");
+#endif
+ m_xDragSourceListener.clear();
+ m_xDragSourceTransferable.clear();
+
+ m_bDropSent = false;
+ m_bDropSuccess = false;
+ m_bWaitingForPrimaryConversion = false;
+ m_aDropWindow = None;
+ m_aDropProxy = None;
+ m_nCurrentProtocolVersion = nXdndProtocolRevision;
+ m_nNoPosX = 0;
+ m_nNoPosY = 0;
+ m_nNoPosWidth = 0;
+ m_nNoPosHeight = 0;
+ m_aCurrentCursor = None;
+
+ XUngrabPointer( m_pDisplay, CurrentTime );
+ XUngrabKeyboard( m_pDisplay, CurrentTime );
+ XFlush( m_pDisplay );
+
+ if( pCaptureFrame )
+ {
+ comphelper::SolarMutex& rSolarMutex( Application::GetSolarMutex() );
+ if( rSolarMutex.tryToAcquire() )
+ vcl_sal::getSalDisplay(GetGenericUnixSalData())->CaptureMouse( pCaptureFrame );
+#if OSL_DEBUG_LEVEL > 0
+ else
+ OSL_FAIL( "failed to acquire SolarMutex to reset capture frame" );
+#endif
+ }
+
+ m_aDragRunning.reset();
+
+ if( listener.is() )
+ listener->dragDropEnd( aDragFailedEvent );
+ }
+}
+
+void SelectionManager::runDragExecute( void* pThis )
+{
+ SelectionManager* This = static_cast<SelectionManager*>(pThis);
+ This->dragDoDispatch();
+}
+
+void SelectionManager::dragDoDispatch()
+{
+
+ // do drag
+ // m_xDragSourceListener will be cleared on finished drop
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "begin executeDrag dispatching.");
+#endif
+ oslThread aThread = m_aDragExecuteThread;
+ while( m_xDragSourceListener.is() && ( ! m_bDropSent || time(nullptr)-m_nDropTimeout < 5 ) && osl_scheduleThread( aThread ) )
+ {
+ // let the thread in the run method do the dispatching
+ // just look occasionally here whether drop timed out or is completed
+ std::this_thread::sleep_for(std::chrono::milliseconds(200));
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "end executeDrag dispatching.");
+#endif
+ {
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener );
+ css::uno::Reference< XTransferable > xTransferable( m_xDragSourceTransferable );
+ m_xDragSourceListener.clear();
+ m_xDragSourceTransferable.clear();
+
+ DragSourceDropEvent dsde;
+ dsde.Source = getXWeak();
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = DNDConstants::ACTION_NONE;
+ dsde.DropSuccess = false;
+
+ // cleanup after drag
+ if( m_bWaitingForPrimaryConversion )
+ {
+ SelectionAdaptor* pAdaptor = getAdaptor( XA_PRIMARY );
+ if (pAdaptor)
+ pAdaptor->clearTransferable();
+ }
+
+ m_bDropSent = false;
+ m_bDropSuccess = false;
+ m_bWaitingForPrimaryConversion = false;
+ m_aDropWindow = None;
+ m_aDropProxy = None;
+ m_nCurrentProtocolVersion = nXdndProtocolRevision;
+ m_nNoPosX = 0;
+ m_nNoPosY = 0;
+ m_nNoPosWidth = 0;
+ m_nNoPosHeight = 0;
+ m_aCurrentCursor = None;
+
+ XUngrabPointer( m_pDisplay, CurrentTime );
+ XUngrabKeyboard( m_pDisplay, CurrentTime );
+ XFlush( m_pDisplay );
+
+ m_aDragExecuteThread = nullptr;
+ m_aDragRunning.reset();
+
+ aGuard.clear();
+ if( xListener.is() )
+ {
+ xTransferable.clear();
+ xListener->dragDropEnd( dsde );
+ }
+ }
+ osl_destroyThread( aThread );
+}
+
+/*
+ * XDragSourceContext
+ */
+
+
+void SelectionManager::setCursor( sal_Int32 cursor, ::Window aDropWindow )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+ if( aDropWindow == m_aDropWindow && Cursor(cursor) != m_aCurrentCursor )
+ {
+ if( m_xDragSourceListener.is() && ! m_bDropSent )
+ {
+ m_aCurrentCursor = cursor;
+ XChangeActivePointerGrab( m_pDisplay, DRAG_EVENT_MASK, cursor, CurrentTime );
+ XFlush( m_pDisplay );
+ }
+ }
+}
+
+void SelectionManager::transferablesFlavorsChanged()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ m_aDragFlavors = m_xDragSourceTransferable->getTransferDataFlavors();
+
+ std::list< Atom > aConversions;
+
+ getNativeTypeList( m_aDragFlavors, aConversions, m_nXdndSelection );
+
+ Atom* pTypes = static_cast<Atom*>(alloca( sizeof(Atom)*aConversions.size() ));
+ int nTypes = 0;
+ for (auto const& conversion : aConversions)
+ pTypes[nTypes++] = conversion;
+ XChangeProperty( m_pDisplay, m_aWindow, m_nXdndTypeList, XA_ATOM, 32, PropModeReplace, reinterpret_cast<unsigned char*>(pTypes), nTypes );
+
+ if( m_aCurrentDropWindow == None || m_nCurrentProtocolVersion < 0 )
+ return;
+
+ // send synthetic leave and enter events
+
+ XEvent aEvent;
+
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.window = m_aDropWindow;
+ aEvent.xclient.data.l[0] = m_aWindow;
+
+ aEvent.xclient.message_type = m_nXdndLeave;
+ aEvent.xclient.data.l[1] = 0;
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+
+ aEvent.xclient.message_type = m_nXdndEnter;
+ aEvent.xclient.data.l[1] = m_nCurrentProtocolVersion << 24;
+ memset( aEvent.xclient.data.l + 2, 0, sizeof( long )*3 );
+ // fill in data types
+ if( nTypes > 3 )
+ aEvent.xclient.data.l[1] |= 1;
+ for( int j = 0; j < nTypes && j < 3; j++ )
+ aEvent.xclient.data.l[j+2] = pTypes[j];
+
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+
+}
+
+/*
+ * dispatch loop
+ */
+
+bool SelectionManager::handleXEvent( XEvent& rEvent )
+{
+ /*
+ * since we are XConnectionListener to a second X display
+ * to get client messages it is essential not to dispatch
+ * events twice that we get on both connections
+ *
+ * between dispatching ButtonPress and startDrag
+ * the user can already have released the mouse. The ButtonRelease
+ * will then be dispatched in VCLs queue and never turn up here.
+ * Which is not so good, since startDrag will XGrabPointer and
+ * XGrabKeyboard -> solid lock.
+ */
+ if( rEvent.xany.display != m_pDisplay
+ && rEvent.type != ClientMessage
+ && rEvent.type != ButtonPress
+ && rEvent.type != ButtonRelease
+ )
+ return false;
+
+ bool bHandled = false;
+ switch (rEvent.type)
+ {
+ case SelectionClear:
+ {
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "SelectionClear for selection "
+ << getString( rEvent.xselectionclear.selection ));
+#endif
+ SelectionAdaptor* pAdaptor = getAdaptor( rEvent.xselectionclear.selection );
+ std::unordered_map< Atom, Selection* >::iterator it( m_aSelections.find( rEvent.xselectionclear.selection ) );
+ if( it != m_aSelections.end() )
+ it->second->m_bOwner = false;
+ aGuard.clear();
+ if ( pAdaptor )
+ pAdaptor->clearTransferable();
+ }
+ break;
+
+ case SelectionRequest:
+ bHandled = handleSelectionRequest( rEvent.xselectionrequest );
+ break;
+ case PropertyNotify:
+ if( rEvent.xproperty.window == m_aWindow ||
+ rEvent.xproperty.window == m_aCurrentDropWindow
+ )
+ bHandled = handleReceivePropertyNotify( rEvent.xproperty );
+ else
+ bHandled = handleSendPropertyNotify( rEvent.xproperty );
+ break;
+ case SelectionNotify:
+ bHandled = handleSelectionNotify( rEvent.xselection );
+ break;
+ case ClientMessage:
+ // messages from drag target
+ if( rEvent.xclient.message_type == m_nXdndStatus ||
+ rEvent.xclient.message_type == m_nXdndFinished )
+ bHandled = handleDragEvent( rEvent );
+ // messages from drag source
+ else if(
+ rEvent.xclient.message_type == m_nXdndEnter ||
+ rEvent.xclient.message_type == m_nXdndLeave ||
+ rEvent.xclient.message_type == m_nXdndPosition ||
+ rEvent.xclient.message_type == m_nXdndDrop
+ )
+ bHandled = handleDropEvent( rEvent.xclient );
+ break;
+ case EnterNotify:
+ case LeaveNotify:
+ case MotionNotify:
+ case ButtonPress:
+ case ButtonRelease:
+ case KeyPress:
+ case KeyRelease:
+ bHandled = handleDragEvent( rEvent );
+ break;
+ default:
+ ;
+ }
+ return bHandled;
+}
+
+void SelectionManager::dispatchEvent( int millisec )
+{
+ // acquire the mutex to prevent other threads
+ // from using the same X connection
+ osl::ResettableMutexGuard aGuard(m_aMutex);
+
+ if( !XPending( m_pDisplay ))
+ {
+ int nfds = 1;
+ // wait for any events if none are already queued
+ pollfd aPollFD[2];
+ aPollFD[0].fd = XConnectionNumber( m_pDisplay );
+ aPollFD[0].events = POLLIN;
+ aPollFD[0].revents = 0;
+
+ // on infinite timeout we need endthreadpipe monitoring too
+ if (millisec < 0)
+ {
+ aPollFD[1].fd = m_EndThreadPipe[0];
+ aPollFD[1].events = POLLIN | POLLERR;
+ aPollFD[1].revents = 0;
+ nfds = 2;
+ }
+
+ // release mutex for the time of waiting for possible data
+ aGuard.clear();
+ if( poll( aPollFD, nfds, millisec ) <= 0 )
+ return;
+ aGuard.reset();
+ }
+ while( XPending( m_pDisplay ))
+ {
+ XEvent event;
+ XNextEvent( m_pDisplay, &event );
+ aGuard.clear();
+ handleXEvent( event );
+ aGuard.reset();
+ }
+}
+
+void SelectionManager::run( void* pThis )
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "SelectionManager::run.");
+#endif
+ osl::Thread::setName("SelectionManager");
+ // dispatch until the cows come home
+
+ SelectionManager* This = static_cast<SelectionManager*>(pThis);
+
+ timeval aLast;
+ gettimeofday( &aLast, nullptr );
+
+ css::uno::Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
+ This->m_xDesktop.set( Desktop::create(xContext) );
+ This->m_xDesktop->addTerminateListener(This);
+
+ // if end thread pipe properly initialized, allow infinite wait in poll
+ // otherwise, fallback on 1 sec timeout
+ const int timeout = (This->m_EndThreadPipe[0] != This->m_EndThreadPipe[1]) ? -1 : 1000;
+
+ while( osl_scheduleThread(This->m_aThread) )
+ {
+ This->dispatchEvent( timeout );
+
+ timeval aNow;
+ gettimeofday( &aNow, nullptr );
+
+ if( (aNow.tv_sec - aLast.tv_sec) > 0 )
+ {
+ osl::ClearableMutexGuard aGuard(This->m_aMutex);
+ std::vector< std::pair< SelectionAdaptor*, css::uno::Reference< XInterface > > > aChangeVector;
+
+ for (auto const& selection : This->m_aSelections)
+ {
+ if( selection.first != This->m_nXdndSelection && ! selection.second->m_bOwner )
+ {
+ ::Window aOwner = XGetSelectionOwner( This->m_pDisplay, selection.first );
+ if( aOwner != selection.second->m_aLastOwner )
+ {
+ selection.second->m_aLastOwner = aOwner;
+ std::pair< SelectionAdaptor*, css::uno::Reference< XInterface > >
+ aKeep( selection.second->m_pAdaptor, selection.second->m_pAdaptor->getReference() );
+ aChangeVector.push_back( aKeep );
+ }
+ }
+ }
+ aGuard.clear();
+ for (auto const& change : aChangeVector)
+ {
+ change.first->fireContentsChanged();
+ }
+ aLast = aNow;
+ }
+ }
+ // close write end on exit so write() fails and other thread does not block
+ // forever
+ close(This->m_EndThreadPipe[1]);
+ close(This->m_EndThreadPipe[0]);
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "SelectionManager::run end.");
+#endif
+}
+
+void SelectionManager::shutdown() noexcept
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "SelectionManager got app termination event.");
+#endif
+
+ osl::ResettableMutexGuard aGuard(m_aMutex);
+
+ if( m_bShutDown )
+ return;
+ m_bShutDown = true;
+
+ if ( m_xDesktop.is() )
+ m_xDesktop->removeTerminateListener(this);
+
+ if( m_xDisplayConnection.is() )
+ m_xDisplayConnection->removeEventHandler(Any(), this);
+
+ // stop dispatching
+ if( m_aThread )
+ {
+ osl_terminateThread( m_aThread );
+ /*
+ * Allow thread to finish before app exits to avoid pulling the carpet
+ * out from under it if pasting is occurring during shutdown
+ *
+ * a) allow it to have the Mutex and
+ * b) reschedule to allow it to complete callbacks to any
+ * Application::GetSolarMutex protected regions, etc. e.g.
+ * TransferableHelper::getTransferDataFlavors (via
+ * SelectionManager::handleSelectionRequest) which it might
+ * currently be trying to enter.
+ *
+ * Otherwise the thread may be left still waiting on a GlobalMutex
+ * when that gets destroyed, letting the thread blow up and die
+ * when enters the section in a now dead OOo instance.
+ */
+ aGuard.clear();
+ while (osl_isThreadRunning(m_aThread))
+ {
+ { // drop mutex before write - otherwise may deadlock
+ SolarMutexGuard guard2;
+ Application::Reschedule();
+ }
+ // trigger poll()'s wait end by writing a dummy value
+ char dummy=0;
+ dummy = write(m_EndThreadPipe[1], &dummy, 1);
+ }
+ osl_joinWithThread( m_aThread );
+ osl_destroyThread( m_aThread );
+ m_aThread = nullptr;
+ aGuard.reset();
+ }
+ m_xDesktop.clear();
+ m_xDisplayConnection.clear();
+ m_xDropTransferable.clear();
+}
+
+sal_Bool SelectionManager::handleEvent(const Any& event)
+{
+ Sequence< sal_Int8 > aSeq;
+ if( event >>= aSeq )
+ {
+ XEvent* pEvent = reinterpret_cast<XEvent*>(aSeq.getArray());
+ Time nTimestamp = CurrentTime;
+ if( pEvent->type == ButtonPress || pEvent->type == ButtonRelease )
+ nTimestamp = pEvent->xbutton.time;
+ else if( pEvent->type == KeyPress || pEvent->type == KeyRelease )
+ nTimestamp = pEvent->xkey.time;
+ else if( pEvent->type == MotionNotify )
+ nTimestamp = pEvent->xmotion.time;
+ else if( pEvent->type == PropertyNotify )
+ nTimestamp = pEvent->xproperty.time;
+
+ if( nTimestamp != CurrentTime )
+ {
+ osl::MutexGuard aGuard(m_aMutex);
+
+ m_nSelectionTimestamp = nTimestamp;
+ }
+
+ return handleXEvent( *pEvent );
+ }
+ else
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "SelectionManager got downing event.");
+#endif
+ shutdown();
+ }
+ return true;
+}
+
+void SAL_CALL SelectionManager::disposing( const css::lang::EventObject& rEvt )
+{
+ if (rEvt.Source == m_xDesktop || rEvt.Source == m_xDisplayConnection)
+ shutdown();
+}
+
+void SAL_CALL SelectionManager::queryTermination( const css::lang::EventObject& )
+{
+}
+
+/*
+ * To be safe, shutdown needs to be called before the ~SfxApplication is called, waiting until
+ * the downing event can be too late if paste are requested during shutdown and ~SfxApplication
+ * has been called before vcl is shutdown
+ */
+void SAL_CALL SelectionManager::notifyTermination( const css::lang::EventObject& rEvent )
+{
+ disposing(rEvent);
+}
+
+void SelectionManager::registerHandler( Atom selection, SelectionAdaptor& rAdaptor )
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ Selection* pNewSelection = new Selection();
+ pNewSelection->m_pAdaptor = &rAdaptor;
+ m_aSelections[ selection ] = pNewSelection;
+}
+
+void SelectionManager::deregisterHandler( Atom selection )
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ std::unordered_map< Atom, Selection* >::iterator it =
+ m_aSelections.find( selection );
+ if( it != m_aSelections.end() )
+ {
+ delete it->second->m_pPixmap;
+ delete it->second;
+ m_aSelections.erase( it );
+ }
+}
+
+static bool bWasError = false;
+
+extern "C"
+{
+ static int local_xerror_handler(Display* , XErrorEvent*)
+ {
+ bWasError = true;
+ return 0;
+ }
+ typedef int(*xerror_hdl_t)(Display*,XErrorEvent*);
+}
+
+void SelectionManager::registerDropTarget( ::Window aWindow, DropTarget* pTarget )
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ // sanity check
+ std::unordered_map< ::Window, DropTargetEntry >::const_iterator it =
+ m_aDropTargets.find( aWindow );
+ if( it != m_aDropTargets.end() )
+ OSL_FAIL( "attempt to register window as drop target twice" );
+ else if( aWindow && m_pDisplay )
+ {
+ DropTargetEntry aEntry( pTarget );
+ bWasError=false;
+ /* #i100000# ugly workaround: gtk sets its own XErrorHandler which is not suitable for us
+ unfortunately XErrorHandler is not per display, so this is just and ugly hack
+ Need to remove separate display and integrate clipboard/dnd into vcl's unx code ASAP
+ */
+ xerror_hdl_t pOldHandler = XSetErrorHandler( local_xerror_handler );
+ XSelectInput( m_pDisplay, aWindow, PropertyChangeMask );
+ if( ! bWasError )
+ {
+ // set XdndAware
+ XChangeProperty( m_pDisplay, aWindow, m_nXdndAware, XA_ATOM, 32, PropModeReplace, reinterpret_cast<unsigned char const *>(&nXdndProtocolRevision), 1 );
+ if( ! bWasError )
+ {
+ // get root window of window (in 99.999% of all cases this will be
+ // DefaultRootWindow( m_pDisplay )
+ int x, y;
+ unsigned int w, h, bw, d;
+ XGetGeometry( m_pDisplay, aWindow, &aEntry.m_aRootWindow,
+ &x, &y, &w, &h, &bw, &d );
+ }
+ }
+ XSetErrorHandler( pOldHandler );
+ if(bWasError)
+ return;
+ m_aDropTargets[ aWindow ] = aEntry;
+ }
+ else
+ OSL_FAIL( "attempt to register None as drop target" );
+}
+
+void SelectionManager::deregisterDropTarget( ::Window aWindow )
+{
+ osl::ResettableGuard aGuard(m_aMutex);
+
+ m_aDropTargets.erase( aWindow );
+ if( aWindow != m_aDragSourceWindow || !m_aDragRunning.check() )
+ return;
+
+ // abort drag
+ std::unordered_map< ::Window, DropTargetEntry >::const_iterator it =
+ m_aDropTargets.find( m_aDropWindow );
+ if( it != m_aDropTargets.end() )
+ {
+ DropTargetEvent dte;
+ dte.Source = it->second.m_pTarget->getXWeak();
+ aGuard.clear();
+ it->second.m_pTarget->dragExit( dte );
+ aGuard.reset();
+ }
+ else if( m_aDropProxy != None && m_nCurrentProtocolVersion >= 0 )
+ {
+ // send XdndLeave
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.message_type = m_nXdndLeave;
+ aEvent.xclient.window = m_aDropWindow;
+ aEvent.xclient.data.l[0] = m_aWindow;
+ memset( aEvent.xclient.data.l+1, 0, sizeof(long)*4);
+ m_aDropWindow = m_aDropProxy = None;
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+ }
+ // notify the listener
+ DragSourceDropEvent dsde;
+ dsde.Source = getXWeak();
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = DNDConstants::ACTION_NONE;
+ dsde.DropSuccess = false;
+ css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener );
+ m_xDragSourceListener.clear();
+ aGuard.clear();
+ xListener->dragDropEnd( dsde );
+}
+
+/*
+ * SelectionAdaptor
+ */
+
+css::uno::Reference< XTransferable > SelectionManager::getTransferable() noexcept
+{
+ return m_xDragSourceTransferable;
+}
+
+void SelectionManager::clearTransferable() noexcept
+{
+ m_xDragSourceTransferable.clear();
+}
+
+void SelectionManager::fireContentsChanged() noexcept
+{
+}
+
+css::uno::Reference< XInterface > SelectionManager::getReference() noexcept
+{
+ return getXWeak();
+}
+
+/*
+ * SelectionManagerHolder
+ */
+
+SelectionManagerHolder::SelectionManagerHolder() :
+ ::cppu::WeakComponentImplHelper<
+ XDragSource,
+ XInitialization,
+ XServiceInfo > (m_aMutex)
+{
+}
+
+SelectionManagerHolder::~SelectionManagerHolder()
+{
+}
+
+void SelectionManagerHolder::initialize( const Sequence< Any >& arguments )
+{
+ OUString aDisplayName;
+
+ if( arguments.hasElements() )
+ {
+ css::uno::Reference< XDisplayConnection > xConn;
+ arguments.getConstArray()[0] >>= xConn;
+ if( xConn.is() )
+ {
+ Any aIdentifier;
+ aIdentifier >>= aDisplayName;
+ }
+ }
+
+ SelectionManager& rManager = SelectionManager::get( aDisplayName );
+ rManager.initialize( arguments );
+ m_xRealDragSource = static_cast< XDragSource* >(&rManager);
+}
+
+/*
+ * XDragSource
+ */
+
+sal_Bool SelectionManagerHolder::isDragImageSupported()
+{
+ return m_xRealDragSource.is() && m_xRealDragSource->isDragImageSupported();
+}
+
+sal_Int32 SelectionManagerHolder::getDefaultCursor( sal_Int8 dragAction )
+{
+ return m_xRealDragSource.is() ? m_xRealDragSource->getDefaultCursor( dragAction ) : 0;
+}
+
+void SelectionManagerHolder::startDrag(
+ const css::datatransfer::dnd::DragGestureEvent& trigger,
+ sal_Int8 sourceActions, sal_Int32 cursor, sal_Int32 image,
+ const css::uno::Reference< css::datatransfer::XTransferable >& transferable,
+ const css::uno::Reference< css::datatransfer::dnd::XDragSourceListener >& listener
+ )
+{
+ if( m_xRealDragSource.is() )
+ m_xRealDragSource->startDrag( trigger, sourceActions, cursor, image, transferable, listener );
+}
+
+/*
+ * XServiceInfo
+ */
+
+OUString SelectionManagerHolder::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.XdndSupport";
+}
+
+sal_Bool SelectionManagerHolder::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SelectionManagerHolder::getSupportedServiceNames()
+{
+ return Xdnd_getSupportedServiceNames();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_selection.hxx b/vcl/unx/generic/dtrans/X11_selection.hxx
new file mode 100644
index 0000000000..bbfe07e5f6
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_selection.hxx
@@ -0,0 +1,496 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/compbase.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragSource.hpp>
+#include <com/sun/star/awt/XDisplayConnection.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/frame/XDesktop2.hpp>
+#include <osl/thread.h>
+#include <osl/conditn.hxx>
+#include <rtl/ref.hxx>
+
+#include <list>
+#include <unordered_map>
+#include <vector>
+
+#include <X11/Xlib.h>
+
+
+namespace x11 {
+
+ class PixmapHolder; // in bmp.hxx
+ class SelectionManager;
+
+ rtl_TextEncoding getTextPlainEncoding( const OUString& rMimeType );
+
+ class SelectionAdaptor
+ {
+ public:
+ virtual css::uno::Reference< css::datatransfer::XTransferable > getTransferable() = 0;
+ virtual void clearTransferable() = 0;
+ virtual void fireContentsChanged() = 0;
+ virtual css::uno::Reference< css::uno::XInterface > getReference() = 0;
+ // returns a reference that will keep the SelectionAdaptor alive until the
+ // reference is released
+
+ protected:
+ ~SelectionAdaptor() {}
+ };
+
+ class DropTarget :
+ public ::cppu::WeakComponentImplHelper<
+ css::datatransfer::dnd::XDropTarget,
+ css::lang::XInitialization,
+ css::lang::XServiceInfo
+ >
+ {
+ public:
+ ::osl::Mutex m_aMutex;
+ bool m_bActive;
+ sal_Int8 m_nDefaultActions;
+ ::Window m_aTargetWindow;
+ rtl::Reference<SelectionManager>
+ m_xSelectionManager;
+ ::std::vector< css::uno::Reference< css::datatransfer::dnd::XDropTargetListener > >
+ m_aListeners;
+
+ DropTarget();
+ virtual ~DropTarget() override;
+
+ // convenience functions that loop over listeners
+ void dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde ) noexcept;
+ void dragExit( const css::datatransfer::dnd::DropTargetEvent& dte ) noexcept;
+ void dragOver( const css::datatransfer::dnd::DropTargetDragEvent& dtde ) noexcept;
+ void drop( const css::datatransfer::dnd::DropTargetDropEvent& dtde ) noexcept;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& args ) override;
+
+ // XDropTarget
+ virtual void SAL_CALL addDropTargetListener( const css::uno::Reference< css::datatransfer::dnd::XDropTargetListener >& ) override;
+ virtual void SAL_CALL removeDropTargetListener( const css::uno::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;
+
+ // 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;
+ };
+
+ class SelectionManagerHolder :
+ public ::cppu::WeakComponentImplHelper<
+ css::datatransfer::dnd::XDragSource,
+ css::lang::XInitialization,
+ css::lang::XServiceInfo
+ >
+ {
+ ::osl::Mutex m_aMutex;
+ css::uno::Reference< css::datatransfer::dnd::XDragSource >
+ m_xRealDragSource;
+ public:
+ SelectionManagerHolder();
+ virtual ~SelectionManagerHolder() 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;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& arguments ) override;
+
+ // XDragSource
+ virtual sal_Bool SAL_CALL isDragImageSupported() override;
+ virtual sal_Int32 SAL_CALL getDefaultCursor( sal_Int8 dragAction ) override;
+ virtual void SAL_CALL startDrag(
+ const css::datatransfer::dnd::DragGestureEvent& trigger,
+ sal_Int8 sourceActions, sal_Int32 cursor, sal_Int32 image,
+ const css::uno::Reference< css::datatransfer::XTransferable >& transferable,
+ const css::uno::Reference< css::datatransfer::dnd::XDragSourceListener >& listener
+ ) override;
+
+ };
+
+ class SelectionManager :
+ public ::cppu::WeakImplHelper<
+ css::datatransfer::dnd::XDragSource,
+ css::lang::XInitialization,
+ css::awt::XEventHandler,
+ css::frame::XTerminateListener
+ >,
+ public SelectionAdaptor
+ {
+ static std::unordered_map< OUString, SelectionManager* >& getInstances();
+
+ // for INCR type selection transfer
+ // INCR protocol is used if the data cannot
+ // be transported at once but in parts
+ // IncrementalTransfer holds the bytes to be transmitted
+ // as well as the current position
+ // INCR triggers the delivery of the next part by deleting the
+ // property used to transfer the data
+ struct IncrementalTransfer
+ {
+ css::uno::Sequence< sal_Int8 > m_aData;
+ int m_nBufferPos;
+ ::Window m_aRequestor;
+ Atom m_aProperty;
+ Atom m_aTarget;
+ int m_nFormat;
+ time_t m_nTransferStartTime;
+ };
+ int m_nIncrementalThreshold;
+
+ // a struct to hold the data associated with a selection
+ struct Selection
+ {
+ enum State
+ {
+ Inactive, WaitingForResponse, WaitingForData, IncrementalTransfer
+ };
+
+ State m_eState;
+ SelectionAdaptor* m_pAdaptor;
+ ::osl::Condition m_aDataArrived;
+ css::uno::Sequence< sal_Int8 > m_aData;
+ css::uno::Sequence< css::datatransfer::DataFlavor >
+ m_aTypes;
+ std::vector< Atom > m_aNativeTypes;
+ // this is used for caching
+ // m_aTypes is invalid after 2 seconds
+ // m_aNativeTypes contains the corresponding original atom
+ Atom m_aRequestedType;
+ // m_aRequestedType is only valid while WaitingForResponse and WaitingFotData
+ time_t m_nLastTimestamp;
+ bool m_bHaveUTF16;
+ Atom m_aUTF8Type;
+ bool m_bHaveCompound;
+ bool m_bOwner;
+ ::Window m_aLastOwner;
+ PixmapHolder* m_pPixmap;
+ // m_nOrigTimestamp contains the Timestamp at which the selection
+ // was acquired; needed for TimeSTAMP target
+ Time m_nOrigTimestamp;
+
+ Selection() : m_eState( Inactive ),
+ m_pAdaptor( nullptr ),
+ m_aRequestedType( None ),
+ m_nLastTimestamp( 0 ),
+ m_bHaveUTF16( false ),
+ m_aUTF8Type( None ),
+ m_bHaveCompound( false ),
+ m_bOwner( false ),
+ m_aLastOwner( None ),
+ m_pPixmap( nullptr ),
+ m_nOrigTimestamp( CurrentTime )
+ {}
+ };
+
+ // a struct to hold data associated with a XDropTarget
+ struct DropTargetEntry
+ {
+ DropTarget* m_pTarget;
+ ::Window m_aRootWindow;
+
+ DropTargetEntry() : m_pTarget( nullptr ), m_aRootWindow( None ) {}
+ explicit DropTargetEntry( DropTarget* pTarget ) :
+ m_pTarget( pTarget ),
+ m_aRootWindow( None )
+ {}
+ DropTargetEntry( const DropTargetEntry& rEntry ) :
+ m_pTarget( rEntry.m_pTarget ),
+ m_aRootWindow( rEntry.m_aRootWindow )
+ {}
+
+ DropTarget* operator->() const { return m_pTarget; }
+ DropTargetEntry& operator=(const DropTargetEntry& rEntry)
+ { m_pTarget = rEntry.m_pTarget; m_aRootWindow = rEntry.m_aRootWindow; return *this; }
+ };
+
+ // internal data
+ Display* m_pDisplay;
+ oslThread m_aThread;
+ int m_EndThreadPipe[2];
+ oslThread m_aDragExecuteThread;
+ ::osl::Condition m_aDragRunning;
+ ::Window m_aWindow;
+ css::uno::Reference< css::frame::XDesktop2 > m_xDesktop;
+ css::uno::Reference< css::awt::XDisplayConnection >
+ m_xDisplayConnection;
+ sal_Int32 m_nSelectionTimeout;
+ Time m_nSelectionTimestamp;
+
+ // members used for Xdnd
+
+ // drop only
+
+ // contains the XdndEnterEvent of a drop action running
+ // with one of our targets. The data.l[0] member
+ // (containing the drag source ::Window) is set
+ // to None while that is not the case
+ XClientMessageEvent m_aDropEnterEvent;
+ // set to false on XdndEnter
+ // set to true on first XdndPosition or XdndLeave
+ bool m_bDropEnterSent;
+ ::Window m_aCurrentDropWindow;
+ // Time code of XdndDrop
+ Time m_nDropTime;
+ sal_Int8 m_nLastDropAction;
+ // XTransferable for Xdnd with foreign drag source
+ css::uno::Reference< css::datatransfer::XTransferable >
+ m_xDropTransferable;
+ int m_nLastX, m_nLastY;
+ // set to true when calling drop()
+ // if another XdndEnter is received this shows that
+ // someone forgot to call dropComplete - we should reset
+ // and react to the new drop
+ bool m_bDropWaitingForCompletion;
+
+ // drag only
+
+ // None if no Dnd action is running with us as source
+ ::Window m_aDropWindow;
+ // either m_aDropWindow or its XdndProxy
+ ::Window m_aDropProxy;
+ ::Window m_aDragSourceWindow;
+ // XTransferable for Xdnd when we are drag source
+ css::uno::Reference< css::datatransfer::XTransferable >
+ m_xDragSourceTransferable;
+ css::uno::Reference< css::datatransfer::dnd::XDragSourceListener >
+ m_xDragSourceListener;
+ // root coordinates
+ int m_nLastDragX, m_nLastDragY;
+ css::uno::Sequence< css::datatransfer::DataFlavor >
+ m_aDragFlavors;
+ // the rectangle the pointer must leave until a new XdndPosition should
+ // be sent. empty unless the drop target told to fill
+ int m_nNoPosX, m_nNoPosY, m_nNoPosWidth, m_nNoPosHeight;
+ unsigned int m_nDragButton;
+ sal_Int8 m_nUserDragAction;
+ sal_Int8 m_nTargetAcceptAction;
+ sal_Int8 m_nSourceActions;
+ bool m_bLastDropAccepted;
+ bool m_bDropSuccess;
+ bool m_bDropSent;
+ time_t m_nDropTimeout;
+ bool m_bWaitingForPrimaryConversion;
+
+ // drag cursors
+ Cursor m_aMoveCursor;
+ Cursor m_aCopyCursor;
+ Cursor m_aLinkCursor;
+ Cursor m_aNoneCursor;
+ Cursor m_aCurrentCursor;
+
+ // drag and drop
+
+ int m_nCurrentProtocolVersion;
+ std::unordered_map< ::Window, DropTargetEntry >
+ m_aDropTargets;
+
+ // some special atoms that are needed often
+ Atom m_nTARGETSAtom;
+ Atom m_nTIMESTAMPAtom;
+ Atom m_nTEXTAtom;
+ Atom m_nINCRAtom;
+ Atom m_nCOMPOUNDAtom;
+ Atom m_nMULTIPLEAtom;
+ Atom m_nImageBmpAtom;
+ Atom m_nXdndAware;
+ Atom m_nXdndEnter;
+ Atom m_nXdndLeave;
+ Atom m_nXdndPosition;
+ Atom m_nXdndStatus;
+ Atom m_nXdndDrop;
+ Atom m_nXdndFinished;
+ Atom m_nXdndSelection;
+ Atom m_nXdndTypeList;
+ Atom m_nXdndProxy;
+ Atom m_nXdndActionCopy;
+ Atom m_nXdndActionMove;
+ Atom m_nXdndActionLink;
+ Atom m_nXdndActionAsk;
+
+ // caching for atoms
+ std::unordered_map< Atom, OUString >
+ m_aAtomToString;
+ std::unordered_map< OUString, Atom >
+ m_aStringToAtom;
+
+ // the registered selections
+ std::unordered_map< Atom, Selection* >
+ m_aSelections;
+ // IncrementalTransfers in progress
+ std::unordered_map< ::Window, std::unordered_map< Atom, IncrementalTransfer > >
+ m_aIncrementals;
+
+ // do not use X11 multithreading capabilities
+ // since this leads to deadlocks in different Xlib implementations
+ // (XFree as well as Xsun) use an own mutex instead
+ ::osl::Mutex m_aMutex;
+ bool m_bShutDown;
+
+ SelectionManager();
+ virtual ~SelectionManager() override;
+
+ SelectionAdaptor* getAdaptor( Atom selection );
+ PixmapHolder* getPixmapHolder( Atom selection );
+
+ // handle various events
+ bool handleSelectionRequest( XSelectionRequestEvent& rRequest );
+ bool handleSendPropertyNotify( XPropertyEvent const & rNotify );
+ bool handleReceivePropertyNotify( XPropertyEvent const & rNotify );
+ bool handleSelectionNotify( XSelectionEvent const & rNotify );
+ bool handleDragEvent( XEvent const & rMessage );
+ bool handleDropEvent( XClientMessageEvent const & rMessage );
+
+ // dnd helpers
+ void sendDragStatus( Atom nDropAction );
+ void sendDropPosition( bool bForce, Time eventTime );
+ bool updateDragAction( int modifierState );
+ int getXdndVersion( ::Window aXLIB_Window, ::Window& rProxy );
+ Cursor createCursor( const unsigned char* pPointerData, const unsigned char* pMaskData, int width, int height, int hotX, int hotY );
+ // coordinates on root ::Window
+ void updateDragWindow( int nX, int nY, ::Window aRoot );
+
+ bool getPasteData( Atom selection, Atom type, css::uno::Sequence< sal_Int8 >& rData );
+ // returns true if conversion was successful
+ bool convertData( const css::uno::Reference< css::datatransfer::XTransferable >& xTransferable,
+ Atom nType,
+ Atom nSelection,
+ int & rFormat,
+ css::uno::Sequence< sal_Int8 >& rData );
+ bool sendData( SelectionAdaptor* pAdaptor, ::Window requestor, Atom target, Atom property, Atom selection );
+
+ // thread dispatch loop
+ public:
+ // public for extern "C" stub
+ static void run( void* );
+ private:
+ void dispatchEvent( int millisec );
+ // drag thread dispatch
+ public:
+ // public for extern "C" stub
+ static void runDragExecute( void* );
+ private:
+ void dragDoDispatch();
+ bool handleXEvent( XEvent& rEvent );
+
+ // compound text conversion
+ OString convertToCompound( const OUString& rText );
+ OUString convertFromCompound( const char* pText, int nLen );
+
+ sal_Int8 getUserDragAction() const;
+ sal_Int32 getSelectionTimeout();
+ public:
+ static SelectionManager& get( const OUString& rDisplayName = OUString() );
+
+ Display * getDisplay() { return m_pDisplay; };
+
+ void registerHandler( Atom selection, SelectionAdaptor& rAdaptor );
+ void deregisterHandler( Atom selection );
+ bool requestOwnership( Atom selection );
+
+ // allow for synchronization over one mutex for XClipboard
+ osl::Mutex& getMutex() { return m_aMutex; }
+
+ Atom getAtom( const OUString& rString );
+ OUString getString( Atom nAtom );
+
+ // type conversion
+ // note: convertTypeToNative does NOT clear the list, so you can append
+ // multiple types to the same list
+ void convertTypeToNative( const OUString& rType, Atom selection, int& rFormat, ::std::list< Atom >& rConversions, bool bPushFront = false );
+ OUString convertTypeFromNative( Atom nType, Atom selection, int& rFormat );
+ void getNativeTypeList( const css::uno::Sequence< css::datatransfer::DataFlavor >& rTypes, std::list< Atom >& rOutTypeList, Atom targetselection );
+
+ // methods for transferable
+ bool getPasteDataTypes( Atom selection, css::uno::Sequence< css::datatransfer::DataFlavor >& rTypes );
+ bool getPasteData( Atom selection, const OUString& rType, css::uno::Sequence< sal_Int8 >& rData );
+
+ // for XDropTarget to register/deregister itself
+ void registerDropTarget( ::Window aXLIB_Window, DropTarget* pTarget );
+ void deregisterDropTarget( ::Window aXLIB_Window );
+
+ // for XDropTarget{Drag|Drop}Context
+ void accept( sal_Int8 dragOperation, ::Window aDropXLIB_Window );
+ void reject( ::Window aDropXLIB_Window );
+ void dropComplete( bool success, ::Window aDropXLIB_Window );
+
+ // for XDragSourceContext
+ sal_Int32 getCurrentCursor() const { return m_aCurrentCursor;}
+ void setCursor( sal_Int32 cursor, ::Window aDropXLIB_Window );
+ void transferablesFlavorsChanged();
+
+ void shutdown() noexcept;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& arguments ) override;
+
+ // XEventHandler
+ virtual sal_Bool SAL_CALL handleEvent(const css::uno::Any& event) override;
+
+ // XDragSource
+ virtual sal_Bool SAL_CALL isDragImageSupported() override;
+ virtual sal_Int32 SAL_CALL getDefaultCursor( sal_Int8 dragAction ) override;
+ virtual void SAL_CALL startDrag(
+ const css::datatransfer::dnd::DragGestureEvent& trigger,
+ sal_Int8 sourceActions, sal_Int32 cursor, sal_Int32 image,
+ const css::uno::Reference< css::datatransfer::XTransferable >& transferable,
+ const css::uno::Reference< css::datatransfer::dnd::XDragSourceListener >& listener
+ ) override;
+
+ // SelectionAdaptor for XdndSelection Drag (we are drag source)
+ virtual css::uno::Reference< css::datatransfer::XTransferable > getTransferable() noexcept override;
+ virtual void clearTransferable() noexcept override;
+ virtual void fireContentsChanged() noexcept override;
+ virtual css::uno::Reference< css::uno::XInterface > getReference() noexcept override;
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+
+ // XTerminateListener
+ virtual void SAL_CALL queryTermination( const css::lang::EventObject& aEvent ) override;
+ virtual void SAL_CALL notifyTermination( const css::lang::EventObject& aEvent ) override;
+ };
+
+ css::uno::Sequence< OUString > Xdnd_getSupportedServiceNames();
+ css::uno::Reference< css::uno::XInterface > SAL_CALL Xdnd_createInstance(
+ const css::uno::Reference< css::lang::XMultiServiceFactory > & xMultiServiceFactory);
+
+ css::uno::Sequence< OUString > Xdnd_dropTarget_getSupportedServiceNames();
+ css::uno::Reference< css::uno::XInterface > SAL_CALL Xdnd_dropTarget_createInstance(
+ const css::uno::Reference< css::lang::XMultiServiceFactory > & xMultiServiceFactory);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_service.cxx b/vcl/unx/generic/dtrans/X11_service.cxx
new file mode 100644
index 0000000000..e633020b6d
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_service.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 <unx/salinst.h>
+#include <dndhelper.hxx>
+#include <vcl/sysdata.hxx>
+
+#include "X11_clipboard.hxx"
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+
+using namespace cppu;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::datatransfer::clipboard;
+using namespace com::sun::star::awt;
+using namespace x11;
+
+Sequence< OUString > x11::X11Clipboard_getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.clipboard.SystemClipboard" };
+}
+
+Sequence< OUString > x11::Xdnd_getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.dnd.X11DragSource" };
+}
+
+Sequence< OUString > x11::Xdnd_dropTarget_getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.dnd.X11DropTarget" };
+}
+
+css::uno::Reference< XInterface > X11SalInstance::CreateClipboard( const Sequence< Any >& arguments )
+{
+ if ( IsRunningUnitTest() )
+ return SalInstance::CreateClipboard( arguments );
+
+ SelectionManager& rManager = SelectionManager::get();
+ css::uno::Sequence<css::uno::Any> mgrArgs{ css::uno::Any(Application::GetDisplayConnection()) };
+ rManager.initialize(mgrArgs);
+
+ OUString sel;
+ if (!arguments.hasElements()) {
+ sel = "CLIPBOARD";
+ } else if (arguments.getLength() != 1 || !(arguments[0] >>= sel)) {
+ throw css::lang::IllegalArgumentException(
+ "bad X11SalInstance::CreateClipboard arguments",
+ css::uno::Reference<css::uno::XInterface>(), -1);
+ }
+ Atom nSelection = rManager.getAtom(sel);
+
+ std::unordered_map< Atom, css::uno::Reference< XClipboard > >::iterator it = m_aInstances.find( nSelection );
+ if( it != m_aInstances.end() )
+ return it->second;
+
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> pClipboard = X11Clipboard::create( rManager, nSelection );
+ m_aInstances[ nSelection ] = pClipboard;
+
+ return pClipboard;
+}
+
+css::uno::Reference<XInterface> X11SalInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv)
+{
+ return vcl::X11DnDHelper(new SelectionManagerHolder(), pSysEnv->aShellWindow);
+}
+
+css::uno::Reference<XInterface> X11SalInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv)
+{
+ return vcl::X11DnDHelper(new DropTarget(), pSysEnv->aShellWindow);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_transferable.cxx b/vcl/unx/generic/dtrans/X11_transferable.cxx
new file mode 100644
index 0000000000..a6ad1b4a15
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_transferable.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 "X11_transferable.hxx"
+#include <X11/Xatom.h>
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <sal/log.hxx>
+
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::io;
+using namespace com::sun::star::uno;
+using namespace cppu;
+using namespace osl;
+
+using namespace x11;
+
+X11Transferable::X11Transferable(
+ SelectionManager& rManager,
+ Atom selection
+ ) :
+ m_rManager( rManager ),
+ m_aSelection( selection )
+{
+}
+
+X11Transferable::~X11Transferable()
+{
+}
+
+Any SAL_CALL X11Transferable::getTransferData( const DataFlavor& rFlavor )
+{
+ Any aRet;
+ Sequence< sal_Int8 > aData;
+ bool bSuccess = m_rManager.getPasteData( m_aSelection ? m_aSelection : XA_PRIMARY, rFlavor.MimeType, aData );
+ if( ! bSuccess && m_aSelection == 0 )
+ bSuccess = m_rManager.getPasteData( m_rManager.getAtom( "CLIPBOARD" ), rFlavor.MimeType, aData );
+
+ if( ! bSuccess )
+ {
+ throw UnsupportedFlavorException( rFlavor.MimeType, static_cast < XTransferable * > ( this ) );
+ }
+ if( rFlavor.MimeType.equalsIgnoreAsciiCase( "text/plain;charset=utf-16" ) )
+ {
+ int nLen = aData.getLength()/2;
+ if( reinterpret_cast<sal_Unicode const *>(aData.getConstArray())[nLen-1] == 0 )
+ nLen--;
+ OUString aString( reinterpret_cast<sal_Unicode const *>(aData.getConstArray()), nLen );
+ SAL_INFO( "vcl.unx.dtrans", "X11Transferable::getTransferData( \"" << rFlavor.MimeType << "\" )\n -> \"" << aString << "\"");
+ aRet <<= aString.replaceAll("\r\n", "\n");
+ }
+ else
+ aRet <<= aData;
+ return aRet;
+}
+
+Sequence< DataFlavor > SAL_CALL X11Transferable::getTransferDataFlavors()
+{
+ Sequence< DataFlavor > aFlavorList;
+ bool bSuccess = m_rManager.getPasteDataTypes( m_aSelection ? m_aSelection : XA_PRIMARY, aFlavorList );
+ if( ! bSuccess && m_aSelection == 0 )
+ m_rManager.getPasteDataTypes( m_rManager.getAtom( "CLIPBOARD" ), aFlavorList );
+
+ return aFlavorList;
+}
+
+sal_Bool SAL_CALL X11Transferable::isDataFlavorSupported( const DataFlavor& aFlavor )
+{
+ if( aFlavor.DataType != cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ {
+ if( ! aFlavor.MimeType.equalsIgnoreAsciiCase( "text/plain;charset=utf-16" ) &&
+ aFlavor.DataType == cppu::UnoType<OUString>::get() )
+ return false;
+ }
+
+ const Sequence< DataFlavor > aFlavors( getTransferDataFlavors() );
+ return std::any_of(aFlavors.begin(), aFlavors.end(),
+ [&aFlavor](const DataFlavor& rFlavor) {
+ return aFlavor.MimeType.equalsIgnoreAsciiCase( rFlavor.MimeType )
+ && aFlavor.DataType == rFlavor.DataType;
+ });
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_transferable.hxx b/vcl/unx/generic/dtrans/X11_transferable.hxx
new file mode 100644
index 0000000000..23cb9dd373
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_transferable.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include "X11_selection.hxx"
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+
+#include <cppuhelper/implbase.hxx>
+
+namespace x11 {
+
+ class X11Transferable : public ::cppu::WeakImplHelper< css::datatransfer::XTransferable >
+ {
+ SelectionManager& m_rManager;
+ Atom m_aSelection;
+ public:
+ X11Transferable( SelectionManager& rManager, Atom selection );
+ virtual ~X11Transferable() override;
+
+ /*
+ * XTransferable
+ */
+
+ virtual css::uno::Any SAL_CALL getTransferData( const css::datatransfer::DataFlavor& aFlavor ) override;
+
+ virtual css::uno::Sequence< css::datatransfer::DataFlavor > SAL_CALL getTransferDataFlavors( ) override;
+
+ virtual sal_Bool SAL_CALL isDataFlavorSupported( const css::datatransfer::DataFlavor& aFlavor ) override;
+ };
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/bmp.cxx b/vcl/unx/generic/dtrans/bmp.cxx
new file mode 100644
index 0000000000..ac8e50cc2e
--- /dev/null
+++ b/vcl/unx/generic/dtrans/bmp.cxx
@@ -0,0 +1,786 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/dibtools.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapSimpleColorQuantizationFilter.hxx>
+
+#include <sal/log.hxx>
+#include <unx/x11/xlimits.hxx>
+
+#include "bmp.hxx"
+
+using namespace x11;
+
+/*
+ * helper functions
+ */
+
+static void writeLE( sal_uInt16 nNumber, sal_uInt8* pBuffer )
+{
+ pBuffer[ 0 ] = (nNumber & 0xff);
+ pBuffer[ 1 ] = ((nNumber>>8)&0xff);
+}
+
+static void writeLE( sal_uInt32 nNumber, sal_uInt8* pBuffer )
+{
+ pBuffer[ 0 ] = (nNumber & 0xff);
+ pBuffer[ 1 ] = ((nNumber>>8)&0xff);
+ pBuffer[ 2 ] = ((nNumber>>16)&0xff);
+ pBuffer[ 3 ] = ((nNumber>>24)&0xff);
+}
+
+static sal_uInt16 readLE16( const sal_uInt8* pBuffer )
+{
+ //This is untainted data which comes from a controlled source
+ //so, using a byte-swapping pattern which coverity doesn't
+ //detect as such
+ //http://security.coverity.com/blog/2014/Apr/on-detecting-heartbleed-with-static-analysis.html
+ sal_uInt16 v = pBuffer[1]; v <<= 8;
+ v |= pBuffer[0];
+ return v;
+}
+
+static sal_uInt32 readLE32( const sal_uInt8* pBuffer )
+{
+ //This is untainted data which comes from a controlled source
+ //so, using a byte-swapping pattern which coverity doesn't
+ //detect as such
+ //http://security.coverity.com/blog/2014/Apr/on-detecting-heartbleed-with-static-analysis.html
+ sal_uInt32 v = pBuffer[3]; v <<= 8;
+ v |= pBuffer[2]; v <<= 8;
+ v |= pBuffer[1]; v <<= 8;
+ v |= pBuffer[0];
+ return v;
+}
+
+/*
+ * scanline helpers
+ */
+
+static void X11_writeScanlinePixel( unsigned long nColor, sal_uInt8* pScanline, int depth, int x )
+{
+ switch( depth )
+ {
+ case 1:
+ pScanline[ x/8 ] &= ~(1 << (x&7));
+ pScanline[ x/8 ] |= ((nColor & 1) << (x&7));
+ break;
+ case 4:
+ pScanline[ x/2 ] &= ((x&1) ? 0x0f : 0xf0);
+ pScanline[ x/2 ] |= ((x&1) ? (nColor & 0x0f) : ((nColor & 0x0f) << 4));
+ break;
+ default:
+ case 8:
+ pScanline[ x ] = (nColor & 0xff);
+ break;
+ }
+}
+
+static sal_uInt8* X11_getPaletteBmpFromImage(
+ Display* pDisplay,
+ XImage* pImage,
+ Colormap aColormap,
+ sal_Int32& rOutSize
+ )
+{
+ sal_uInt32 nColors = 0;
+
+ rOutSize = 0;
+
+ sal_uInt8* pBuffer = nullptr;
+ sal_uInt32 nHeaderSize, nScanlineSize;
+ sal_uInt16 nBitCount;
+ // determine header and scanline size
+ switch( pImage->depth )
+ {
+ case 1:
+ nHeaderSize = 64;
+ nScanlineSize = (pImage->width+31)/32;
+ nBitCount = 1;
+ break;
+ case 4:
+ nHeaderSize = 72;
+ nScanlineSize = (pImage->width+1)/2;
+ nBitCount = 4;
+ break;
+ default:
+ case 8:
+ nHeaderSize = 1084;
+ nScanlineSize = pImage->width;
+ nBitCount = 8;
+ break;
+ }
+ // adjust scan lines to begin on %4 boundaries
+ if( nScanlineSize & 3 )
+ {
+ nScanlineSize &= 0xfffffffc;
+ nScanlineSize += 4;
+ }
+
+ // allocate buffer to hold header and scanlines, initialize to zero
+ rOutSize = nHeaderSize + nScanlineSize*pImage->height;
+ pBuffer = static_cast<sal_uInt8*>(rtl_allocateZeroMemory( rOutSize ));
+ for( int y = 0; y < pImage->height; y++ )
+ {
+ sal_uInt8* pScanline = pBuffer + nHeaderSize + (pImage->height-1-y)*nScanlineSize;
+ for( int x = 0; x < pImage->width; x++ )
+ {
+ unsigned long nPixel = XGetPixel( pImage, x, y );
+ if( nPixel >= nColors )
+ nColors = nPixel+1;
+ X11_writeScanlinePixel( nPixel, pScanline, pImage->depth, x );
+ }
+ }
+
+ // fill in header fields
+ pBuffer[ 0 ] = 'B';
+ pBuffer[ 1 ] = 'M';
+
+ writeLE( nHeaderSize, pBuffer+10 );
+ writeLE( sal_uInt32(40), pBuffer+14 );
+ writeLE( static_cast<sal_uInt32>(pImage->width), pBuffer+18 );
+ writeLE( static_cast<sal_uInt32>(pImage->height), pBuffer+22 );
+ writeLE( sal_uInt16(1), pBuffer+26 );
+ writeLE( nBitCount, pBuffer+28 );
+ writeLE( static_cast<sal_uInt32>(DisplayWidth(pDisplay,DefaultScreen(pDisplay))*1000/DisplayWidthMM(pDisplay,DefaultScreen(pDisplay))), pBuffer+38);
+ writeLE( static_cast<sal_uInt32>(DisplayHeight(pDisplay,DefaultScreen(pDisplay))*1000/DisplayHeightMM(pDisplay,DefaultScreen(pDisplay))), pBuffer+42);
+ writeLE( nColors, pBuffer+46 );
+ writeLE( nColors, pBuffer+50 );
+
+ XColor aColors[256];
+ if( nColors > (1U << nBitCount) ) // paranoia
+ nColors = (1U << nBitCount);
+ for( unsigned long nPixel = 0; nPixel < nColors; nPixel++ )
+ {
+ aColors[nPixel].flags = DoRed | DoGreen | DoBlue;
+ aColors[nPixel].pixel = nPixel;
+ }
+ XQueryColors( pDisplay, aColormap, aColors, nColors );
+ for( sal_uInt32 i = 0; i < nColors; i++ )
+ {
+ pBuffer[ 54 + i*4 ] = static_cast<sal_uInt8>(aColors[i].blue >> 8);
+ pBuffer[ 55 + i*4 ] = static_cast<sal_uInt8>(aColors[i].green >> 8);
+ pBuffer[ 56 + i*4 ] = static_cast<sal_uInt8>(aColors[i].red >> 8);
+ }
+
+ // done
+
+ return pBuffer;
+}
+
+static unsigned long doRightShift( unsigned long nValue, int nShift )
+{
+ return (nShift > 0) ? (nValue >> nShift) : (nValue << (-nShift));
+}
+
+static unsigned long doLeftShift( unsigned long nValue, int nShift )
+{
+ return (nShift > 0) ? (nValue << nShift) : (nValue >> (-nShift));
+}
+
+static void getShift( unsigned long nMask, int& rShift, int& rSigBits, int& rShift2 )
+{
+ unsigned long nUseMask = nMask;
+ rShift = 0;
+ while( nMask & 0xffffff00 )
+ {
+ rShift++;
+ nMask >>= 1;
+ }
+ if( rShift == 0 )
+ while( ! (nMask & 0x00000080) )
+ {
+ rShift--;
+ nMask <<= 1;
+ }
+
+ int nRotate = sizeof(unsigned long)*8 - rShift;
+ rSigBits = 0;
+ nMask = doRightShift( nUseMask, rShift) ;
+ while( nRotate-- )
+ {
+ if( nMask & 1 )
+ rSigBits++;
+ nMask >>= 1;
+ }
+
+ rShift2 = 0;
+ if( rSigBits < 8 )
+ rShift2 = 8-rSigBits;
+}
+
+static sal_uInt8* X11_getTCBmpFromImage(
+ Display* pDisplay,
+ XImage* pImage,
+ sal_Int32& rOutSize,
+ int nScreenNo
+ )
+{
+ // get masks from visual info (guesswork)
+ XVisualInfo aVInfo;
+ if( ! XMatchVisualInfo( pDisplay, nScreenNo, pImage->depth, TrueColor, &aVInfo ) )
+ return nullptr;
+
+ rOutSize = 0;
+
+ sal_uInt8* pBuffer = nullptr;
+ sal_uInt32 nHeaderSize = 60;
+ sal_uInt32 nScanlineSize = pImage->width*3;
+
+ // adjust scan lines to begin on %4 boundaries
+ if( nScanlineSize & 3 )
+ {
+ nScanlineSize &= 0xfffffffc;
+ nScanlineSize += 4;
+ }
+ int nRedShift, nRedSig, nRedShift2 = 0;
+ getShift( aVInfo.red_mask, nRedShift, nRedSig, nRedShift2 );
+ int nGreenShift, nGreenSig, nGreenShift2 = 0;
+ getShift( aVInfo.green_mask, nGreenShift, nGreenSig, nGreenShift2 );
+ int nBlueShift, nBlueSig, nBlueShift2 = 0;
+ getShift( aVInfo.blue_mask, nBlueShift, nBlueSig, nBlueShift2 );
+
+ // allocate buffer to hold header and scanlines, initialize to zero
+ rOutSize = nHeaderSize + nScanlineSize*pImage->height;
+ pBuffer = static_cast<sal_uInt8*>(rtl_allocateZeroMemory( rOutSize ));
+ for( int y = 0; y < pImage->height; y++ )
+ {
+ sal_uInt8* pScanline = pBuffer + nHeaderSize + (pImage->height-1-y)*nScanlineSize;
+ for( int x = 0; x < pImage->width; x++ )
+ {
+ unsigned long nPixel = XGetPixel( pImage, x, y );
+
+ sal_uInt8 nValue = static_cast<sal_uInt8>(doRightShift( nPixel&aVInfo.blue_mask, nBlueShift));
+ if( nBlueShift2 )
+ nValue |= (nValue >> nBlueShift2 );
+ *pScanline++ = nValue;
+
+ nValue = static_cast<sal_uInt8>(doRightShift( nPixel&aVInfo.green_mask, nGreenShift));
+ if( nGreenShift2 )
+ nValue |= (nValue >> nGreenShift2 );
+ *pScanline++ = nValue;
+
+ nValue = static_cast<sal_uInt8>(doRightShift( nPixel&aVInfo.red_mask, nRedShift));
+ if( nRedShift2 )
+ nValue |= (nValue >> nRedShift2 );
+ *pScanline++ = nValue;
+ }
+ }
+
+ // fill in header fields
+ pBuffer[ 0 ] = 'B';
+ pBuffer[ 1 ] = 'M';
+
+ writeLE( nHeaderSize, pBuffer+10 );
+ writeLE( sal_uInt32(40), pBuffer+14 );
+ writeLE( static_cast<sal_uInt32>(pImage->width), pBuffer+18 );
+ writeLE( static_cast<sal_uInt32>(pImage->height), pBuffer+22 );
+ writeLE( sal_uInt16(1), pBuffer+26 );
+ writeLE( sal_uInt16(24), pBuffer+28 );
+ writeLE( static_cast<sal_uInt32>(DisplayWidth(pDisplay,DefaultScreen(pDisplay))*1000/DisplayWidthMM(pDisplay,DefaultScreen(pDisplay))), pBuffer+38);
+ writeLE( static_cast<sal_uInt32>(DisplayHeight(pDisplay,DefaultScreen(pDisplay))*1000/DisplayHeightMM(pDisplay,DefaultScreen(pDisplay))), pBuffer+42);
+
+ // done
+
+ return pBuffer;
+}
+
+sal_uInt8* x11::X11_getBmpFromPixmap(
+ Display* pDisplay,
+ Drawable aDrawable,
+ Colormap aColormap,
+ sal_Int32& rOutSize
+ )
+{
+ // get geometry of drawable
+ ::Window aRoot;
+ int x,y;
+ unsigned int w, h, bw, d;
+ XGetGeometry( pDisplay, aDrawable, &aRoot, &x, &y, &w, &h, &bw, &d );
+
+ // find which screen we are on
+ int nScreenNo = ScreenCount( pDisplay );
+ while( nScreenNo-- )
+ {
+ if( RootWindow( pDisplay, nScreenNo ) == aRoot )
+ break;
+ }
+ if( nScreenNo < 0 )
+ return nullptr;
+
+ if( aColormap == None )
+ aColormap = DefaultColormap( pDisplay, nScreenNo );
+
+ // get the image
+ XImage* pImage = XGetImage( pDisplay, aDrawable, 0, 0, w, h, AllPlanes, ZPixmap );
+ if( ! pImage )
+ return nullptr;
+
+ sal_uInt8* pBmp = d <= 8 ?
+ X11_getPaletteBmpFromImage( pDisplay, pImage, aColormap, rOutSize ) :
+ X11_getTCBmpFromImage( pDisplay, pImage, rOutSize, nScreenNo );
+ XDestroyImage( pImage );
+
+ return pBmp;
+}
+
+/*
+ * PixmapHolder
+ */
+
+PixmapHolder::PixmapHolder( Display* pDisplay )
+ : m_pDisplay(pDisplay)
+ , m_aColormap(None)
+ , m_aPixmap(None)
+ , m_aBitmap(None)
+ , m_nRedShift(0)
+ , m_nGreenShift(0)
+ , m_nBlueShift(0)
+ , m_nBlueShift2Mask(0)
+ , m_nRedShift2Mask(0)
+ , m_nGreenShift2Mask(0)
+{
+ /* try to get a 24 bit true color visual, if that fails,
+ * revert to default visual
+ */
+ if( ! XMatchVisualInfo( m_pDisplay, DefaultScreen( m_pDisplay ), 24, TrueColor, &m_aInfo ) )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "PixmapHolder reverting to default visual.");
+#endif
+ Visual* pVisual = DefaultVisual( m_pDisplay, DefaultScreen( m_pDisplay ) );
+ m_aInfo.screen = DefaultScreen( m_pDisplay );
+ m_aInfo.visual = pVisual;
+ m_aInfo.visualid = pVisual->visualid;
+ m_aInfo.c_class = pVisual->c_class;
+ m_aInfo.red_mask = pVisual->red_mask;
+ m_aInfo.green_mask = pVisual->green_mask;
+ m_aInfo.blue_mask = pVisual->blue_mask;
+ m_aInfo.depth = DefaultDepth( m_pDisplay, m_aInfo.screen );
+ }
+ m_aColormap = DefaultColormap( m_pDisplay, m_aInfo.screen );
+#if OSL_DEBUG_LEVEL > 1
+ static const char* pClasses[] =
+ { "StaticGray", "GrayScale", "StaticColor", "PseudoColor", "TrueColor", "DirectColor" };
+ SAL_INFO("vcl.unx.dtrans", "PixmapHolder visual: id = "
+ << std::showbase << std::hex
+ << m_aInfo.visualid
+ << ", class = "
+ << ((m_aInfo.c_class >= 0 &&
+ unsigned(m_aInfo.c_class) <
+ SAL_N_ELEMENTS(pClasses)) ?
+ pClasses[m_aInfo.c_class] :
+ "<unknown>")
+ << " ("
+ << std::dec
+ << m_aInfo.c_class
+ << "), depth="
+ << m_aInfo.depth
+ << "; color map = "
+ << std::showbase << std::hex
+ << m_aColormap);
+#endif
+ if( m_aInfo.c_class != TrueColor )
+ return;
+
+ int nRedShift2(0);
+ int nGreenShift2(0);
+ int nBlueShift2(0);
+ int nRedSig, nGreenSig, nBlueSig;
+ getShift( m_aInfo.red_mask, m_nRedShift, nRedSig, nRedShift2 );
+ getShift( m_aInfo.green_mask, m_nGreenShift, nGreenSig, nGreenShift2 );
+ getShift( m_aInfo.blue_mask, m_nBlueShift, nBlueSig, nBlueShift2 );
+
+ m_nBlueShift2Mask = nBlueShift2 ? ~static_cast<unsigned long>((1<<nBlueShift2)-1) : ~0L;
+ m_nGreenShift2Mask = nGreenShift2 ? ~static_cast<unsigned long>((1<<nGreenShift2)-1) : ~0L;
+ m_nRedShift2Mask = nRedShift2 ? ~static_cast<unsigned long>((1<<nRedShift2)-1) : ~0L;
+}
+
+PixmapHolder::~PixmapHolder()
+{
+ if( m_aPixmap != None )
+ XFreePixmap( m_pDisplay, m_aPixmap );
+ if( m_aBitmap != None )
+ XFreePixmap( m_pDisplay, m_aBitmap );
+}
+
+unsigned long PixmapHolder::getTCPixel( sal_uInt8 r, sal_uInt8 g, sal_uInt8 b ) const
+{
+ unsigned long nPixel = 0;
+ unsigned long nValue = static_cast<unsigned long>(b);
+ nValue &= m_nBlueShift2Mask;
+ nPixel |= doLeftShift( nValue, m_nBlueShift );
+
+ nValue = static_cast<unsigned long>(g);
+ nValue &= m_nGreenShift2Mask;
+ nPixel |= doLeftShift( nValue, m_nGreenShift );
+
+ nValue = static_cast<unsigned long>(r);
+ nValue &= m_nRedShift2Mask;
+ nPixel |= doLeftShift( nValue, m_nRedShift );
+
+ return nPixel;
+}
+
+void PixmapHolder::setBitmapDataPalette( const sal_uInt8* pData, XImage* pImage )
+{
+ // setup palette
+ XColor aPalette[256];
+
+ sal_uInt32 nColors = readLE32( pData+32 );
+ sal_uInt32 nWidth = readLE32( pData+4 );
+ sal_uInt32 nHeight = readLE32( pData+8 );
+ sal_uInt16 nDepth = readLE16( pData+14 );
+
+ for( sal_uInt32 i = 0 ; i < nColors; i++ )
+ {
+ if( m_aInfo.c_class != TrueColor )
+ {
+ //This is untainted data which comes from a controlled source
+ //so, using a byte-swapping pattern which coverity doesn't
+ //detect as such
+ //http://security.coverity.com/blog/2014/Apr/on-detecting-heartbleed-with-static-analysis.html
+ aPalette[i].red = static_cast<unsigned short>(pData[42 + i*4]);
+ aPalette[i].red <<= 8;
+ aPalette[i].red |= static_cast<unsigned short>(pData[42 + i*4]);
+
+ aPalette[i].green = static_cast<unsigned short>(pData[41 + i*4]);
+ aPalette[i].green <<= 8;
+ aPalette[i].green |= static_cast<unsigned short>(pData[41 + i*4]);
+
+ aPalette[i].blue = static_cast<unsigned short>(pData[40 + i*4]);
+ aPalette[i].blue <<= 8;
+ aPalette[i].blue |= static_cast<unsigned short>(pData[40 + i*4]);
+ XAllocColor( m_pDisplay, m_aColormap, aPalette+i );
+ }
+ else
+ aPalette[i].pixel = getTCPixel( pData[42+i*4], pData[41+i*4], pData[40+i*4] );
+ }
+ const sal_uInt8* pBMData = pData + readLE32( pData ) + 4*nColors;
+
+ sal_uInt32 nScanlineSize = 0;
+ switch( nDepth )
+ {
+ case 1:
+ nScanlineSize = (nWidth+31)/32;
+ break;
+ case 4:
+ nScanlineSize = (nWidth+1)/2;
+ break;
+ case 8:
+ nScanlineSize = nWidth;
+ break;
+ }
+ // adjust scan lines to begin on %4 boundaries
+ if( nScanlineSize & 3 )
+ {
+ nScanlineSize &= 0xfffffffc;
+ nScanlineSize += 4;
+ }
+
+ // allocate buffer to hold header and scanlines, initialize to zero
+ for( unsigned int y = 0; y < nHeight; y++ )
+ {
+ const sal_uInt8* pScanline = pBMData + (nHeight-1-y)*nScanlineSize;
+ for( unsigned int x = 0; x < nWidth; x++ )
+ {
+ int nCol = 0;
+ switch( nDepth )
+ {
+ case 1: nCol = (pScanline[ x/8 ] & (0x80 >> (x&7))) != 0 ? 0 : 1; break;
+ case 4:
+ if( x & 1 )
+ nCol = static_cast<int>(pScanline[ x/2 ] >> 4);
+ else
+ nCol = static_cast<int>(pScanline[ x/2 ] & 0x0f);
+ break;
+ case 8: nCol = static_cast<int>(pScanline[x]);
+ }
+ XPutPixel( pImage, x, y, aPalette[nCol].pixel );
+ }
+ }
+}
+
+void PixmapHolder::setBitmapDataTCDither( const sal_uInt8* pData, XImage* pImage )
+{
+ XColor aPalette[216];
+
+ int nNonAllocs = 0;
+
+ for( int r = 0; r < 6; r++ )
+ {
+ for( int g = 0; g < 6; g++ )
+ {
+ for( int b = 0; b < 6; b++ )
+ {
+ int i = r*36+g*6+b;
+ aPalette[i].red = r == 5 ? 0xffff : r*10922;
+ aPalette[i].green = g == 5 ? 0xffff : g*10922;
+ aPalette[i].blue = b == 5 ? 0xffff : b*10922;
+ aPalette[i].pixel = 0;
+ if( ! XAllocColor( m_pDisplay, m_aColormap, aPalette+i ) )
+ nNonAllocs++;
+ }
+ }
+ }
+
+ if( nNonAllocs )
+ {
+ XColor aRealPalette[256];
+ int nColors = 1 << m_aInfo.depth;
+ int i;
+ for( i = 0; i < nColors; i++ )
+ aRealPalette[i].pixel = static_cast<unsigned long>(i);
+ XQueryColors( m_pDisplay, m_aColormap, aRealPalette, nColors );
+ for( i = 0; i < nColors; i++ )
+ {
+ sal_uInt8 nIndex =
+ 36*static_cast<sal_uInt8>(aRealPalette[i].red/10923) +
+ 6*static_cast<sal_uInt8>(aRealPalette[i].green/10923) +
+ static_cast<sal_uInt8>(aRealPalette[i].blue/10923);
+ if( aPalette[nIndex].pixel == 0 )
+ aPalette[nIndex] = aRealPalette[i];
+ }
+ }
+
+ sal_uInt32 nWidth = readLE32( pData+4 );
+ sal_uInt32 nHeight = readLE32( pData+8 );
+
+ const sal_uInt8* pBMData = pData + readLE32( pData );
+ sal_uInt32 nScanlineSize = nWidth*3;
+ // adjust scan lines to begin on %4 boundaries
+ if( nScanlineSize & 3 )
+ {
+ nScanlineSize &= 0xfffffffc;
+ nScanlineSize += 4;
+ }
+
+ for( int y = 0; y < static_cast<int>(nHeight); y++ )
+ {
+ const sal_uInt8* pScanline = pBMData + (nHeight-1-static_cast<sal_uInt32>(y))*nScanlineSize;
+ for( int x = 0; x < static_cast<int>(nWidth); x++ )
+ {
+ sal_uInt8 b = pScanline[3*x];
+ sal_uInt8 g = pScanline[3*x+1];
+ sal_uInt8 r = pScanline[3*x+2];
+ sal_uInt8 i = 36*(r/43) + 6*(g/43) + (b/43);
+
+ XPutPixel( pImage, x, y, aPalette[ i ].pixel );
+ }
+ }
+}
+
+void PixmapHolder::setBitmapDataTC( const sal_uInt8* pData, XImage* pImage )
+{
+ sal_uInt32 nWidth = readLE32( pData+4 );
+ sal_uInt32 nHeight = readLE32( pData+8 );
+
+ if (!nWidth || !nHeight)
+ return;
+
+ const sal_uInt8* pBMData = pData + readLE32( pData );
+ sal_uInt32 nScanlineSize = nWidth*3;
+ // adjust scan lines to begin on %4 boundaries
+ if( nScanlineSize & 3 )
+ {
+ nScanlineSize &= 0xfffffffc;
+ nScanlineSize += 4;
+ }
+
+ for( int y = 0; y < static_cast<int>(nHeight); y++ )
+ {
+ const sal_uInt8* pScanline = pBMData + (nHeight-1-static_cast<sal_uInt32>(y))*nScanlineSize;
+ for( int x = 0; x < static_cast<int>(nWidth); x++ )
+ {
+ unsigned long nPixel = getTCPixel( pScanline[3*x+2], pScanline[3*x+1], pScanline[3*x] );
+ XPutPixel( pImage, x, y, nPixel );
+ }
+ }
+}
+
+bool PixmapHolder::needsConversion( const sal_uInt8* pData ) const
+{
+ if( pData[0] != 'B' || pData[1] != 'M' )
+ return true;
+
+ pData = pData+14;
+ sal_uInt32 nDepth = readLE32( pData+14 );
+ if( nDepth == 24 )
+ {
+ if( m_aInfo.c_class != TrueColor )
+ return true;
+ }
+ else if( nDepth != static_cast<sal_uInt32>(m_aInfo.depth) )
+ {
+ if( m_aInfo.c_class != TrueColor )
+ return true;
+ }
+
+ return false;
+}
+
+Pixmap PixmapHolder::setBitmapData( const sal_uInt8* pData )
+{
+ if( pData[0] != 'B' || pData[1] != 'M' )
+ return None;
+
+ pData = pData+14;
+
+ // reject compressed data
+ if( readLE32( pData + 16 ) != 0 )
+ return None;
+
+ sal_uInt32 nWidth = readLE32( pData+4 );
+ sal_uInt32 nHeight = readLE32( pData+8 );
+
+ if( m_aPixmap != None )
+ {
+ XFreePixmap( m_pDisplay, m_aPixmap );
+ m_aPixmap = None;
+ }
+ if( m_aBitmap != None )
+ {
+ XFreePixmap( m_pDisplay, m_aBitmap );
+ m_aBitmap = None;
+ }
+
+ m_aPixmap = limitXCreatePixmap( m_pDisplay,
+ RootWindow( m_pDisplay, m_aInfo.screen ),
+ nWidth, nHeight, m_aInfo.depth );
+
+ if( m_aPixmap != None )
+ {
+ XImage aImage;
+ aImage.width = static_cast<int>(nWidth);
+ aImage.height = static_cast<int>(nHeight);
+ aImage.xoffset = 0;
+ aImage.format = ZPixmap;
+ aImage.data = nullptr;
+ aImage.byte_order = ImageByteOrder( m_pDisplay );
+ aImage.bitmap_unit = BitmapUnit( m_pDisplay );
+ aImage.bitmap_bit_order = BitmapBitOrder( m_pDisplay );
+ aImage.bitmap_pad = BitmapPad( m_pDisplay );
+ aImage.depth = m_aInfo.depth;
+ aImage.red_mask = m_aInfo.red_mask;
+ aImage.green_mask = m_aInfo.green_mask;
+ aImage.blue_mask = m_aInfo.blue_mask;
+ aImage.bytes_per_line = 0; // filled in by XInitImage
+ if( m_aInfo.depth <= 8 )
+ aImage.bits_per_pixel = m_aInfo.depth;
+ else
+ aImage.bits_per_pixel = 8*((m_aInfo.depth+7)/8);
+ aImage.obdata = nullptr;
+
+ XInitImage( &aImage );
+ aImage.data = static_cast<char*>(std::malloc( nHeight*aImage.bytes_per_line ));
+
+ if( readLE32( pData+14 ) == 24 )
+ {
+ if( m_aInfo.c_class == TrueColor )
+ setBitmapDataTC( pData, &aImage );
+ else
+ setBitmapDataTCDither( pData, &aImage );
+ }
+ else
+ setBitmapDataPalette( pData, &aImage );
+
+ // put the image
+ XPutImage( m_pDisplay,
+ m_aPixmap,
+ DefaultGC( m_pDisplay, m_aInfo.screen ),
+ &aImage,
+ 0, 0,
+ 0, 0,
+ nWidth, nHeight );
+
+ // clean up
+ std::free( aImage.data );
+
+ // prepare bitmap (mask)
+ m_aBitmap = limitXCreatePixmap( m_pDisplay,
+ RootWindow( m_pDisplay, m_aInfo.screen ),
+ nWidth, nHeight, 1 );
+ XGCValues aVal;
+ aVal.function = GXcopy;
+ aVal.foreground = 0xffffffff;
+ GC aGC = XCreateGC( m_pDisplay, m_aBitmap, GCFunction | GCForeground, &aVal );
+ XFillRectangle( m_pDisplay, m_aBitmap, aGC, 0, 0, nWidth, nHeight );
+ XFreeGC( m_pDisplay, aGC );
+ }
+
+ return m_aPixmap;
+}
+
+css::uno::Sequence<sal_Int8> x11::convertBitmapDepth(
+ css::uno::Sequence<sal_Int8> const & data, int depth)
+{
+ if (depth < 4) {
+ depth = 1;
+ } else if (depth < 8) {
+ depth = 4;
+ } else if (depth > 8 && depth < 24) {
+ depth = 24;
+ }
+ SolarMutexGuard g;
+ SvMemoryStream in(
+ const_cast<sal_Int8 *>(data.getConstArray()), data.getLength(),
+ StreamMode::READ);
+ Bitmap bm;
+ ReadDIB(bm, in, true);
+ if (bm.getPixelFormat() == vcl::PixelFormat::N24_BPP && depth <= 8) {
+ bm.Dither();
+ }
+ if (vcl::pixelFormatBitCount(bm.getPixelFormat()) != depth) {
+ switch (depth) {
+ case 1:
+ bm.Convert(BmpConversion::N1BitThreshold);
+ break;
+ case 4:
+ {
+ BitmapEx aBmpEx(bm);
+ BitmapFilter::Filter(aBmpEx, BitmapSimpleColorQuantizationFilter(1<<4));
+ bm = aBmpEx.GetBitmap();
+ }
+ break;
+
+ case 8:
+ {
+ BitmapEx aBmpEx(bm);
+ BitmapFilter::Filter(aBmpEx, BitmapSimpleColorQuantizationFilter(1<<8));
+ bm = aBmpEx.GetBitmap();
+ }
+ break;
+
+ case 24:
+ bm.Convert(BmpConversion::N24Bit);
+ break;
+ }
+ }
+ SvMemoryStream out;
+ WriteDIB(bm, out, false, true);
+ return css::uno::Sequence<sal_Int8>(
+ static_cast<sal_Int8 const *>(out.GetData()), out.GetEndOfData());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/bmp.hxx b/vcl/unx/generic/dtrans/bmp.hxx
new file mode 100644
index 0000000000..3a37158db3
--- /dev/null
+++ b/vcl/unx/generic/dtrans/bmp.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <com/sun/star/uno/Sequence.hxx>
+#include <sal/types.h>
+
+namespace x11 {
+
+// helper methods
+sal_uInt8* X11_getBmpFromPixmap( Display* pDisplay,
+ Drawable aDrawable,
+ Colormap aColormap,
+ sal_Int32& rOutSize );
+
+class PixmapHolder
+{
+ Display* m_pDisplay;
+ Colormap m_aColormap;
+ Pixmap m_aPixmap;
+ Pixmap m_aBitmap;
+ XVisualInfo m_aInfo;
+
+ int m_nRedShift;
+ int m_nGreenShift;
+ int m_nBlueShift;
+ tools::ULong m_nBlueShift2Mask, m_nRedShift2Mask, m_nGreenShift2Mask;
+
+ // these expect data pointers to bitmapinfo header
+ void setBitmapDataTC( const sal_uInt8* pData, XImage* pImage );
+ void setBitmapDataTCDither( const sal_uInt8* pData, XImage* pImage );
+ void setBitmapDataPalette( const sal_uInt8* pData, XImage* pImage );
+
+ tools::ULong getTCPixel( sal_uInt8 r, sal_uInt8 g, sal_uInt8 b ) const;
+public:
+ PixmapHolder( Display* pDisplay );
+ ~PixmapHolder();
+
+ // accepts bitmap file (including bitmap file header)
+ Pixmap setBitmapData( const sal_uInt8* pData );
+ bool needsConversion( const sal_uInt8* pData ) const;
+
+ Colormap getColormap() const { return m_aColormap; }
+ Pixmap getPixmap() const { return m_aPixmap; }
+ Pixmap getBitmap() const { return m_aBitmap; }
+ VisualID getVisualID() const { return m_aInfo.visualid; }
+ int getDepth() const { return m_aInfo.depth; }
+};
+
+css::uno::Sequence<sal_Int8> convertBitmapDepth(
+ css::uno::Sequence<sal_Int8> const & data, int depth);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/config.cxx b/vcl/unx/generic/dtrans/config.cxx
new file mode 100644
index 0000000000..c7292628e3
--- /dev/null
+++ b/vcl/unx/generic/dtrans/config.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 <o3tl/any.hxx>
+#include <sal/log.hxx>
+#include <unotools/configitem.hxx>
+
+#include "X11_selection.hxx"
+
+constexpr OUStringLiteral SETTINGS_CONFIGNODE = u"VCL/Settings/Transfer";
+constexpr OUString SELECTION_PROPERTY = u"SelectionTimeout"_ustr;
+
+namespace x11
+{
+
+namespace {
+
+class DtransX11ConfigItem : public ::utl::ConfigItem
+{
+ sal_Int32 m_nSelectionTimeout;
+
+ virtual void Notify( const css::uno::Sequence< OUString >& rPropertyNames ) override;
+ virtual void ImplCommit() override;
+
+public:
+ DtransX11ConfigItem();
+
+ sal_Int32 getSelectionTimeout() const { return m_nSelectionTimeout; }
+};
+
+}
+
+}
+
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace x11;
+
+sal_Int32 SelectionManager::getSelectionTimeout()
+{
+ if( m_nSelectionTimeout < 1 )
+ {
+ DtransX11ConfigItem aCfg;
+ m_nSelectionTimeout = aCfg.getSelectionTimeout();
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "initialized selection timeout to "
+ << m_nSelectionTimeout
+ << " seconds.");
+#endif
+ }
+ return m_nSelectionTimeout;
+}
+
+/*
+ * DtransX11ConfigItem constructor
+ */
+
+DtransX11ConfigItem::DtransX11ConfigItem() :
+ ConfigItem( SETTINGS_CONFIGNODE,
+ ConfigItemMode::NONE ),
+ m_nSelectionTimeout( 3 )
+{
+ Sequence<OUString> aKeys { SELECTION_PROPERTY };
+ const Sequence< Any > aValues = GetProperties( aKeys );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "found "
+ << aValues.getLength()
+ << " properties for "
+ << SELECTION_PROPERTY);
+#endif
+ for( Any const & value : aValues )
+ {
+ if( auto pLine = o3tl::tryAccess<OUString>(value) )
+ {
+ if( !pLine->isEmpty() )
+ {
+ m_nSelectionTimeout = pLine->toInt32();
+ if( m_nSelectionTimeout < 1 )
+ m_nSelectionTimeout = 1;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "found SelectionTimeout \"" << *pLine << "\".");
+#endif
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ SAL_INFO("vcl.unx.dtrans", "found SelectionTimeout of type \""
+ << value.getValueType().getTypeName() << "\".");
+#endif
+ }
+}
+
+void DtransX11ConfigItem::ImplCommit()
+{
+ // for the clipboard service this is readonly, so
+ // there is nothing to commit
+}
+
+/*
+ * DtransX11ConfigItem::Notify
+ */
+
+void DtransX11ConfigItem::Notify( const Sequence< OUString >& /*rPropertyNames*/ )
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/copydata_curs.h b/vcl/unx/generic/dtrans/copydata_curs.h
new file mode 100644
index 0000000000..4cc36ebdeb
--- /dev/null
+++ b/vcl/unx/generic/dtrans/copydata_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define copydata_curs_width 32
+#define copydata_curs_height 32
+#define copydata_curs_x_hot 1
+#define copydata_curs_y_hot 1
+static unsigned char copydata_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
+ 0x7e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00,
+ 0xfe, 0x03, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00,
+ 0x66, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
+ 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x10, 0x53, 0x00, 0x00,
+ 0x28, 0xa3, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x10, 0xf0, 0x1f, 0x00, 0x08, 0xf0, 0x1f, 0x00, 0x10, 0xf0, 0x1e, 0x00,
+ 0xa8, 0xf2, 0x1e, 0x00, 0x50, 0x35, 0x18, 0x00, 0x00, 0xf0, 0x1e, 0x00,
+ 0x00, 0xf0, 0x1e, 0x00, 0x00, 0xf0, 0x1f, 0x00, 0x00, 0xf0, 0x1f, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/copydata_mask.h b/vcl/unx/generic/dtrans/copydata_mask.h
new file mode 100644
index 0000000000..a3538c9522
--- /dev/null
+++ b/vcl/unx/generic/dtrans/copydata_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define copydata_mask_width 32
+#define copydata_mask_height 32
+#define copydata_mask_x_hot 1
+#define copydata_mask_y_hot 1
+static unsigned char copydata_mask_bits[] = {
+ 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+ 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00,
+ 0xe0, 0x03, 0x00, 0x00, 0xf8, 0xff, 0x00, 0x00, 0xfc, 0xff, 0x01, 0x00,
+ 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0x3c, 0xf8, 0x3f, 0x00,
+ 0x3c, 0xf8, 0x3f, 0x00, 0x3c, 0xf8, 0x3f, 0x00, 0xfc, 0xff, 0x3f, 0x00,
+ 0xfc, 0xff, 0x3f, 0x00, 0xfc, 0xff, 0x3f, 0x00, 0xf8, 0xff, 0x3f, 0x00,
+ 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00,
+ 0x00, 0xf8, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/linkdata_curs.h b/vcl/unx/generic/dtrans/linkdata_curs.h
new file mode 100644
index 0000000000..8a4e6db387
--- /dev/null
+++ b/vcl/unx/generic/dtrans/linkdata_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define linkdata_curs_width 32
+#define linkdata_curs_height 32
+#define linkdata_curs_x_hot 1
+#define linkdata_curs_y_hot 1
+static unsigned char linkdata_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
+ 0x7e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00,
+ 0xfe, 0x03, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00,
+ 0x66, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
+ 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x10, 0x53, 0x00, 0x00,
+ 0x28, 0xa3, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x10, 0xf0, 0x1f, 0x00, 0x08, 0x70, 0x18, 0x00, 0x10, 0xf0, 0x18, 0x00,
+ 0xa8, 0x72, 0x18, 0x00, 0x50, 0x35, 0x1a, 0x00, 0x00, 0x30, 0x1f, 0x00,
+ 0x00, 0xb0, 0x1f, 0x00, 0x00, 0x70, 0x1f, 0x00, 0x00, 0xf0, 0x1f, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/linkdata_mask.h b/vcl/unx/generic/dtrans/linkdata_mask.h
new file mode 100644
index 0000000000..a1875a8e0a
--- /dev/null
+++ b/vcl/unx/generic/dtrans/linkdata_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define linkdata_mask_width 32
+#define linkdata_mask_height 32
+#define linkdata_mask_x_hot 1
+#define linkdata_mask_y_hot 1
+static unsigned char linkdata_mask_bits[] = {
+ 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+ 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00,
+ 0xe0, 0x03, 0x00, 0x00, 0xf8, 0xff, 0x00, 0x00, 0xfc, 0xff, 0x01, 0x00,
+ 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0x3c, 0xf8, 0x3f, 0x00,
+ 0x3c, 0xf8, 0x3f, 0x00, 0x3c, 0xf8, 0x3f, 0x00, 0xfc, 0xff, 0x3f, 0x00,
+ 0xfc, 0xff, 0x3f, 0x00, 0xfc, 0xff, 0x3f, 0x00, 0xf8, 0xff, 0x3f, 0x00,
+ 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00,
+ 0x00, 0xf8, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/movedata_curs.h b/vcl/unx/generic/dtrans/movedata_curs.h
new file mode 100644
index 0000000000..b253ce70ca
--- /dev/null
+++ b/vcl/unx/generic/dtrans/movedata_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define movedata_curs_width 32
+#define movedata_curs_height 32
+#define movedata_curs_x_hot 1
+#define movedata_curs_y_hot 1
+static unsigned char movedata_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
+ 0x7e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00,
+ 0xfe, 0x03, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00,
+ 0x66, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
+ 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x10, 0x53, 0x00, 0x00,
+ 0x28, 0xa3, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00,
+ 0x10, 0x40, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00,
+ 0xa8, 0xaa, 0x00, 0x00, 0x50, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/movedata_mask.h b/vcl/unx/generic/dtrans/movedata_mask.h
new file mode 100644
index 0000000000..d317b1556e
--- /dev/null
+++ b/vcl/unx/generic/dtrans/movedata_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define movedata_mask_width 32
+#define movedata_mask_height 32
+#define movedata_mask_x_hot 1
+#define movedata_mask_y_hot 1
+static unsigned char movedata_mask_bits[] = {
+ 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+ 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00,
+ 0xe0, 0x03, 0x00, 0x00, 0xf8, 0xff, 0x00, 0x00, 0xfc, 0xff, 0x01, 0x00,
+ 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0x3c, 0xe0, 0x01, 0x00,
+ 0x3c, 0xe0, 0x01, 0x00, 0x3c, 0xe0, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00,
+ 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0xf8, 0xff, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/nodrop_curs.h b/vcl/unx/generic/dtrans/nodrop_curs.h
new file mode 100644
index 0000000000..9582575180
--- /dev/null
+++ b/vcl/unx/generic/dtrans/nodrop_curs.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define nodrop_curs_width 32
+#define nodrop_curs_height 32
+#define nodrop_curs_x_hot 9
+#define nodrop_curs_y_hot 9
+static unsigned char nodrop_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00,
+ 0xf8, 0x7f, 0x00, 0x00, 0x7c, 0xf8, 0x00, 0x00, 0x1c, 0xfc, 0x00, 0x00,
+ 0x1e, 0xfe, 0x01, 0x00, 0x0e, 0xdf, 0x01, 0x00, 0x8e, 0xcf, 0x01, 0x00,
+ 0xce, 0xc7, 0x01, 0x00, 0xee, 0xc3, 0x01, 0x00, 0xfe, 0xe1, 0x01, 0x00,
+ 0xfc, 0xe0, 0x00, 0x00, 0x7c, 0xf8, 0x00, 0x00, 0xf8, 0x7f, 0x00, 0x00,
+ 0xf0, 0x3f, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/nodrop_mask.h b/vcl/unx/generic/dtrans/nodrop_mask.h
new file mode 100644
index 0000000000..662a300645
--- /dev/null
+++ b/vcl/unx/generic/dtrans/nodrop_mask.h
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define nodrop_mask_width 32
+#define nodrop_mask_height 32
+#define nodrop_mask_x_hot 9
+#define nodrop_mask_y_hot 9
+static unsigned char nodrop_mask_bits[] = {
+ 0xc0, 0x0f, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, 0xf8, 0x7f, 0x00, 0x00,
+ 0xfc, 0xff, 0x00, 0x00, 0xfe, 0xff, 0x01, 0x00, 0x7e, 0xfe, 0x01, 0x00,
+ 0x3f, 0xff, 0x03, 0x00, 0x9f, 0xff, 0x03, 0x00, 0xdf, 0xff, 0x03, 0x00,
+ 0xff, 0xef, 0x03, 0x00, 0xff, 0xe7, 0x03, 0x00, 0xff, 0xf3, 0x03, 0x00,
+ 0xfe, 0xf9, 0x01, 0x00, 0xfe, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x00, 0x00,
+ 0xf8, 0x7f, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/fontmanager/fontconfig.cxx b/vcl/unx/generic/fontmanager/fontconfig.cxx
new file mode 100644
index 0000000000..4eb186a269
--- /dev/null
+++ b/vcl/unx/generic/fontmanager/fontconfig.cxx
@@ -0,0 +1,1310 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <memory>
+#include <string_view>
+
+#include <o3tl/lru_map.hxx>
+#include <unx/fontmanager.hxx>
+#include <unx/helper.hxx>
+#include <comphelper/sequence.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/vclenum.hxx>
+#include <font/FontSelectPattern.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nutil/unicode.hxx>
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <unicode/uchar.h>
+#include <unicode/uscript.h>
+#include <officecfg/Office/Common.hxx>
+#include <org/freedesktop/PackageKit/SyncDbusSessionHelper.hpp>
+#include <config_fonts.h>
+
+#include <fontconfig/fontconfig.h>
+
+#include <cstdio>
+
+#include <unotools/configmgr.hxx>
+#include <unotools/syslocaleoptions.hxx>
+
+#include <osl/process.h>
+
+#include <o3tl/hash_combine.hxx>
+#include <utility>
+#include <algorithm>
+
+using namespace psp;
+using namespace osl;
+
+namespace
+{
+
+struct FontOptionsKey
+{
+ OUString m_sFamilyName;
+ int m_nFontSize;
+ FontItalic m_eItalic;
+ FontWeight m_eWeight;
+ FontWidth m_eWidth;
+ FontPitch m_ePitch;
+
+ bool operator==(const FontOptionsKey& rOther) const
+ {
+ return m_sFamilyName == rOther.m_sFamilyName &&
+ m_nFontSize == rOther.m_nFontSize &&
+ m_eItalic == rOther.m_eItalic &&
+ m_eWeight == rOther.m_eWeight &&
+ m_eWidth == rOther.m_eWidth &&
+ m_ePitch == rOther.m_ePitch;
+ }
+};
+
+}
+
+namespace std
+{
+
+template <> struct hash<FontOptionsKey>
+{
+ std::size_t operator()(const FontOptionsKey& k) const noexcept
+ {
+ std::size_t seed = k.m_sFamilyName.hashCode();
+ o3tl::hash_combine(seed, k.m_nFontSize);
+ o3tl::hash_combine(seed, k.m_eItalic);
+ o3tl::hash_combine(seed, k.m_eWeight);
+ o3tl::hash_combine(seed, k.m_eWidth);
+ o3tl::hash_combine(seed, k.m_ePitch);
+ return seed;
+ }
+};
+
+} // end std namespace
+
+namespace
+{
+
+struct FcPatternDeleter
+{
+ void operator()(FcPattern* pPattern) const
+ {
+ FcPatternDestroy(pPattern);
+ }
+};
+
+typedef std::unique_ptr<FcPattern, FcPatternDeleter> FcPatternUniquePtr;
+
+class CachedFontConfigFontOptions
+{
+private:
+ o3tl::lru_map<FontOptionsKey, FcPatternUniquePtr> lru_options_cache;
+
+public:
+ CachedFontConfigFontOptions()
+ : lru_options_cache(10) // arbitrary cache size of 10
+ {
+ }
+
+ std::unique_ptr<FontConfigFontOptions> lookup(const FontOptionsKey& rKey)
+ {
+ auto it = lru_options_cache.find(rKey);
+ if (it != lru_options_cache.end())
+ return std::make_unique<FontConfigFontOptions>(FcPatternDuplicate(it->second.get()));
+ return nullptr;
+ }
+
+ void cache(const FontOptionsKey& rKey, const FcPattern* pPattern)
+ {
+ lru_options_cache.insert(std::make_pair(rKey, FcPatternUniquePtr(FcPatternDuplicate(pPattern))));
+ }
+
+};
+
+typedef std::pair<FcChar8*, FcChar8*> lang_and_element;
+
+class FontCfgWrapper
+{
+ FcFontSet* m_pFontSet;
+
+ FontCfgWrapper();
+ ~FontCfgWrapper();
+
+public:
+ static FontCfgWrapper& get();
+ static void release();
+
+ void addFontSet( FcSetName );
+
+ FcFontSet* getFontSet();
+ void replaceFontSet(FcFontSet* pFilteredFontSet);
+
+ void clear();
+
+public:
+ FcResult LocalizedElementFromPattern(FcPattern const * pPattern, FcChar8 **family,
+ const char *elementtype, const char *elementlangtype);
+//to-do, make private and add some cleaner accessor methods
+ std::unordered_map< OString, OString > m_aFontNameToLocalized;
+ std::unordered_map< OString, OString > m_aLocalizedToCanonical;
+ CachedFontConfigFontOptions m_aCachedFontOptions;
+private:
+ void cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname, const std::vector< lang_and_element > &lang_and_elements);
+
+ std::unique_ptr<LanguageTag> m_pLanguageTag;
+};
+
+}
+
+FontCfgWrapper::FontCfgWrapper()
+ : m_pFontSet( nullptr )
+{
+ FcInit();
+}
+
+#ifndef FC_FONT_WRAPPER
+#define FC_FONT_WRAPPER "fontwrapper"
+#endif
+
+void FontCfgWrapper::addFontSet( FcSetName eSetName )
+{
+ // Add only acceptable fonts to our config, for future fontconfig use.
+ FcFontSet* pOrig = FcConfigGetFonts( FcConfigGetCurrent(), eSetName );
+ if( !pOrig )
+ return;
+
+ // filter the font sets to remove obsolete faces
+ for( int i = 0; i < pOrig->nfont; ++i )
+ {
+ FcPattern* pPattern = pOrig->fonts[i];
+ // #i115131# ignore non-scalable fonts
+ // Scalable fonts are usually outline fonts, but some bitmaps fonts
+ // (like Noto Color Emoji) are also scalable.
+ FcBool bScalable = FcFalse;
+ FcResult eScalableRes = FcPatternGetBool(pPattern, FC_SCALABLE, 0, &bScalable);
+ if ((eScalableRes != FcResultMatch) || (bScalable == FcFalse))
+ continue;
+
+ // Ignore Type 1 fonts, too.
+ FcChar8* pFormat = nullptr;
+ FcResult eFormatRes = FcPatternGetString(pPattern, FC_FONTFORMAT, 0, &pFormat);
+ if ((eFormatRes == FcResultMatch) && (strcmp(reinterpret_cast<char*>(pFormat), "Type 1") == 0))
+ continue;
+
+ // Ignore any other non-SFNT wrapper format, including WOFF and WOFF2, too.
+ FcChar8* pWrapper = nullptr;
+ FcResult eWrapperRes = FcPatternGetString(pPattern, FC_FONT_WRAPPER, 0, &pWrapper);
+ if ((eWrapperRes == FcResultMatch) && (strcmp(reinterpret_cast<char*>(pWrapper), "SFNT") != 0))
+ continue;
+
+ FcPatternReference( pPattern );
+ FcFontSetAdd( m_pFontSet, pPattern );
+ }
+
+ // TODO?: FcFontSetDestroy( pOrig );
+}
+
+namespace
+{
+ int compareFontNames(const FcPattern *a, const FcPattern *b)
+ {
+ FcChar8 *pNameA=nullptr, *pNameB=nullptr;
+
+ bool bHaveA = FcPatternGetString(a, FC_FAMILY, 0, &pNameA) == FcResultMatch;
+ bool bHaveB = FcPatternGetString(b, FC_FAMILY, 0, &pNameB) == FcResultMatch;
+
+ if (bHaveA && bHaveB)
+ return strcmp(reinterpret_cast<const char*>(pNameA), reinterpret_cast<const char*>(pNameB));
+
+ return int(bHaveA) - int(bHaveB);
+ }
+
+ //Sort fonts so that fonts with the same family name are side-by-side, with
+ //those with higher version numbers first
+ class SortFont
+ {
+ public:
+ bool operator()(const FcPattern *a, const FcPattern *b)
+ {
+ int comp = compareFontNames(a, b);
+ if (comp != 0)
+ return comp < 0;
+
+ int nVersionA=0, nVersionB=0;
+
+ bool bHaveA = FcPatternGetInteger(a, FC_FONTVERSION, 0, &nVersionA) == FcResultMatch;
+ bool bHaveB = FcPatternGetInteger(b, FC_FONTVERSION, 0, &nVersionB) == FcResultMatch;
+
+ if (bHaveA && bHaveB)
+ return nVersionA > nVersionB;
+
+ return bHaveA > bHaveB;
+ }
+ };
+
+ //See fdo#30729 for where an old opensymbol installed system-wide can
+ //clobber the new opensymbol installed locally
+
+ //See if this font is a duplicate with equal attributes which has already been
+ //inserted, or if it an older version of an inserted fonts. Depends on FcFontSet
+ //on being sorted with SortFont
+ bool isPreviouslyDuplicateOrObsoleted(FcFontSet const *pFSet, int i)
+ {
+ const FcPattern *a = pFSet->fonts[i];
+
+ FcPattern* pTestPatternA = FcPatternDuplicate(a);
+ FcPatternDel(pTestPatternA, FC_FILE);
+ FcPatternDel(pTestPatternA, FC_CHARSET);
+ FcPatternDel(pTestPatternA, FC_CAPABILITY);
+ FcPatternDel(pTestPatternA, FC_FONTVERSION);
+ FcPatternDel(pTestPatternA, FC_LANG);
+
+ bool bIsDup(false);
+
+ // fdo#66715: loop for case of several font files for same font
+ for (int j = i - 1; 0 <= j && !bIsDup; --j)
+ {
+ const FcPattern *b = pFSet->fonts[j];
+
+ if (compareFontNames(a, b) != 0)
+ break;
+
+ FcPattern* pTestPatternB = FcPatternDuplicate(b);
+ FcPatternDel(pTestPatternB, FC_FILE);
+ FcPatternDel(pTestPatternB, FC_CHARSET);
+ FcPatternDel(pTestPatternB, FC_CAPABILITY);
+ FcPatternDel(pTestPatternB, FC_FONTVERSION);
+ FcPatternDel(pTestPatternB, FC_LANG);
+
+ bIsDup = FcPatternEqual(pTestPatternA, pTestPatternB);
+
+ FcPatternDestroy(pTestPatternB);
+ }
+
+ FcPatternDestroy(pTestPatternA);
+
+ return bIsDup;
+ }
+}
+
+FcFontSet* FontCfgWrapper::getFontSet()
+{
+ if( !m_pFontSet )
+ {
+ m_pFontSet = FcFontSetCreate();
+ bool bRestrictFontSetToApplicationFonts = false;
+#if HAVE_MORE_FONTS
+ bRestrictFontSetToApplicationFonts = [] {
+ return getenv("SAL_NON_APPLICATION_FONT_USE") != nullptr;
+ }();
+#endif
+ // Add the application fonts before the system fonts.
+ // tdf#157939 We will remove duplicate fonts, where the duplicate is
+ // the one with a smaller version number. If the same version font is
+ // available system-wide or bundled with our application, then we
+ // prefer via stable-sort the first one we see. Load application fonts
+ // first to prefer the one we bundle in the application in that case.
+ addFontSet( FcSetApplication );
+ if (!bRestrictFontSetToApplicationFonts)
+ addFontSet( FcSetSystem );
+
+ std::stable_sort(m_pFontSet->fonts,m_pFontSet->fonts+m_pFontSet->nfont,SortFont());
+ }
+
+ return m_pFontSet;
+}
+
+void FontCfgWrapper::replaceFontSet(FcFontSet* pFilteredFontSet)
+{
+ if (m_pFontSet)
+ FcFontSetDestroy(m_pFontSet);
+ m_pFontSet = pFilteredFontSet;
+}
+
+FontCfgWrapper::~FontCfgWrapper()
+{
+ clear();
+ //To-Do: get gtk vclplug smoketest to pass
+ //FcFini();
+}
+
+static FontCfgWrapper* pOneInstance = nullptr;
+
+FontCfgWrapper& FontCfgWrapper::get()
+{
+ if( ! pOneInstance )
+ pOneInstance = new FontCfgWrapper();
+ return *pOneInstance;
+}
+
+void FontCfgWrapper::release()
+{
+ if( pOneInstance )
+ {
+ delete pOneInstance;
+ pOneInstance = nullptr;
+ }
+}
+
+namespace
+{
+ FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag);
+
+ FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag)
+ {
+ FcChar8* candidate = elements.begin()->second;
+ /* FIXME-BCP47: once fontconfig supports language tags this
+ * language-territory stuff needs to be changed! */
+ SAL_INFO_IF( !rLangTag.isIsoLocale(), "vcl.fonts", "localizedsorter::bestname - not an ISO locale");
+ OString sLangMatch(OUStringToOString(rLangTag.getLanguage().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8));
+ OString sFullMatch = sLangMatch +
+ "-" +
+ OUStringToOString(rLangTag.getCountry().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8);
+
+ bool alreadyclosematch = false;
+ bool found_fallback_englishname = false;
+ for (auto const& element : elements)
+ {
+ const char *pLang = reinterpret_cast<const char*>(element.first);
+ if( sFullMatch == pLang)
+ {
+ // both language and country match
+ candidate = element.second;
+ break;
+ }
+ else if( alreadyclosematch )
+ {
+ // current candidate matches lang of lang-TERRITORY
+ // override candidate only if there is a full match
+ continue;
+ }
+ else if( sLangMatch == pLang)
+ {
+ // just the language matches
+ candidate = element.second;
+ alreadyclosematch = true;
+ }
+ else if( found_fallback_englishname )
+ {
+ // already found an english fallback, don't override candidate
+ // unless there is a better language match
+ continue;
+ }
+ else if( rtl_str_compare( pLang, "en") == 0)
+ {
+ // select a fallback candidate of the first english element
+ // name
+ candidate = element.second;
+ found_fallback_englishname = true;
+ }
+ }
+ return candidate;
+ }
+}
+
+//Set up maps to quickly map between a fonts best UI name and all the rest of its names, and vice versa
+void FontCfgWrapper::cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname,
+ const std::vector< lang_and_element > &lang_and_elements)
+{
+ for (auto const& element : lang_and_elements)
+ {
+ const char *candidate = reinterpret_cast<const char*>(element.second);
+ if (rtl_str_compare(candidate, reinterpret_cast<const char*>(bestfontname)) != 0)
+ m_aFontNameToLocalized[OString(candidate)] = OString(reinterpret_cast<const char*>(bestfontname));
+ }
+ if (rtl_str_compare(reinterpret_cast<const char*>(origfontname), reinterpret_cast<const char*>(bestfontname)) != 0)
+ m_aLocalizedToCanonical[OString(reinterpret_cast<const char*>(bestfontname))] = OString(reinterpret_cast<const char*>(origfontname));
+}
+
+FcResult FontCfgWrapper::LocalizedElementFromPattern(FcPattern const * pPattern, FcChar8 **element,
+ const char *elementtype, const char *elementlangtype)
+{ /* e. g.: ^ FC_FAMILY ^ FC_FAMILYLANG */
+ FcChar8 *origelement;
+ FcResult eElementRes = FcPatternGetString( pPattern, elementtype, 0, &origelement );
+ *element = origelement;
+
+ if( eElementRes == FcResultMatch)
+ {
+ FcChar8* elementlang = nullptr;
+ if (FcPatternGetString( pPattern, elementlangtype, 0, &elementlang ) == FcResultMatch)
+ {
+ std::vector< lang_and_element > lang_and_elements;
+ lang_and_elements.emplace_back(elementlang, *element);
+ int k = 1;
+ while (true)
+ {
+ if (FcPatternGetString( pPattern, elementlangtype, k, &elementlang ) != FcResultMatch)
+ break;
+ if (FcPatternGetString( pPattern, elementtype, k, element ) != FcResultMatch)
+ break;
+ lang_and_elements.emplace_back(elementlang, *element);
+ ++k;
+ }
+
+ if (!m_pLanguageTag)
+ m_pLanguageTag.reset(new LanguageTag(SvtSysLocaleOptions().GetRealUILanguageTag()));
+
+ // FontConfig orders Typographic Family/Subfamily before old
+ // R/B/I/BI-compatible ones, but we want the later, so reverse the
+ // names to match them first.
+ std::reverse(lang_and_elements.begin(), lang_and_elements.end());
+
+ *element = bestname(lang_and_elements, *m_pLanguageTag);
+
+ //if this element is a fontname, map the other names to this best-name
+ if (rtl_str_compare(elementtype, FC_FAMILY) == 0)
+ cacheLocalizedFontNames(origelement, *element, lang_and_elements);
+ }
+ }
+
+ return eElementRes;
+}
+
+void FontCfgWrapper::clear()
+{
+ m_aFontNameToLocalized.clear();
+ m_aLocalizedToCanonical.clear();
+ if( m_pFontSet )
+ {
+ FcFontSetDestroy( m_pFontSet );
+ m_pFontSet = nullptr;
+ }
+ m_pLanguageTag.reset();
+}
+
+/*
+ * PrintFontManager::initFontconfig
+ */
+void PrintFontManager::initFontconfig()
+{
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+ rWrapper.clear();
+}
+
+namespace
+{
+ FontWeight convertWeight(int weight)
+ {
+ // set weight
+ if( weight <= FC_WEIGHT_THIN )
+ return WEIGHT_THIN;
+ else if( weight <= FC_WEIGHT_ULTRALIGHT )
+ return WEIGHT_ULTRALIGHT;
+ else if( weight <= FC_WEIGHT_LIGHT )
+ return WEIGHT_LIGHT;
+ else if( weight <= FC_WEIGHT_BOOK )
+ return WEIGHT_SEMILIGHT;
+ else if( weight <= FC_WEIGHT_NORMAL )
+ return WEIGHT_NORMAL;
+ else if( weight <= FC_WEIGHT_MEDIUM )
+ return WEIGHT_MEDIUM;
+ else if( weight <= FC_WEIGHT_SEMIBOLD )
+ return WEIGHT_SEMIBOLD;
+ else if( weight <= FC_WEIGHT_BOLD )
+ return WEIGHT_BOLD;
+ else if( weight <= FC_WEIGHT_ULTRABOLD )
+ return WEIGHT_ULTRABOLD;
+ return WEIGHT_BLACK;
+ }
+
+ FontItalic convertSlant(int slant)
+ {
+ // set italic
+ if( slant == FC_SLANT_ITALIC )
+ return ITALIC_NORMAL;
+ else if( slant == FC_SLANT_OBLIQUE )
+ return ITALIC_OBLIQUE;
+ return ITALIC_NONE;
+ }
+
+ FontPitch convertSpacing(int spacing)
+ {
+ // set pitch
+ if( spacing == FC_MONO || spacing == FC_CHARCELL )
+ return PITCH_FIXED;
+ return PITCH_VARIABLE;
+ }
+
+ // translation: fontconfig enum -> vcl enum
+ FontWidth convertWidth(int width)
+ {
+ if (width == FC_WIDTH_ULTRACONDENSED)
+ return WIDTH_ULTRA_CONDENSED;
+ else if (width == FC_WIDTH_EXTRACONDENSED)
+ return WIDTH_EXTRA_CONDENSED;
+ else if (width == FC_WIDTH_CONDENSED)
+ return WIDTH_CONDENSED;
+ else if (width == FC_WIDTH_SEMICONDENSED)
+ return WIDTH_SEMI_CONDENSED;
+ else if (width == FC_WIDTH_SEMIEXPANDED)
+ return WIDTH_SEMI_EXPANDED;
+ else if (width == FC_WIDTH_EXPANDED)
+ return WIDTH_EXPANDED;
+ else if (width == FC_WIDTH_EXTRAEXPANDED)
+ return WIDTH_EXTRA_EXPANDED;
+ else if (width == FC_WIDTH_ULTRAEXPANDED)
+ return WIDTH_ULTRA_EXPANDED;
+ return WIDTH_NORMAL;
+ }
+}
+
+namespace
+{
+ // for variable fonts, FC_INDEX has been changed such that the lower half is now the
+ // index of the font within the collection, and the upper half has been repurposed
+ // as the index within the variations
+ unsigned int GetCollectionIndex(unsigned int nEntryId)
+ {
+ return nEntryId & 0xFFFF;
+ }
+
+ unsigned int GetVariationIndex(unsigned int nEntryId)
+ {
+ return nEntryId >> 16;
+ }
+}
+
+void PrintFontManager::countFontconfigFonts()
+{
+ int nFonts = 0;
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+
+ FcFontSet* pFSet = rWrapper.getFontSet();
+ const bool bMinimalFontset = utl::ConfigManager::IsFuzzing();
+ if( pFSet )
+ {
+ SAL_INFO("vcl.fonts", "found " << pFSet->nfont << " entries in fontconfig fontset");
+
+ FcFontSet* pFilteredSet = FcFontSetCreate();
+
+ for( int i = 0; i < pFSet->nfont; i++ )
+ {
+ FcChar8* file = nullptr;
+ FcChar8* family = nullptr;
+ FcChar8* style = nullptr;
+ FcChar8* format = nullptr;
+ int slant = 0;
+ int weight = 0;
+ int width = 0;
+ int spacing = 0;
+ int symbol = 0;
+ int nEntryId = -1;
+ FcBool scalable = false;
+
+ FcResult eFileRes = FcPatternGetString(pFSet->fonts[i], FC_FILE, 0, &file);
+ FcResult eFamilyRes = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &family, FC_FAMILY, FC_FAMILYLANG );
+ if (bMinimalFontset && strncmp(reinterpret_cast<char*>(family), "Liberation", strlen("Liberation")))
+ continue;
+ FcResult eStyleRes = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &style, FC_STYLE, FC_STYLELANG );
+ FcResult eSlantRes = FcPatternGetInteger(pFSet->fonts[i], FC_SLANT, 0, &slant);
+ FcResult eWeightRes = FcPatternGetInteger(pFSet->fonts[i], FC_WEIGHT, 0, &weight);
+ FcResult eWidthRes = FcPatternGetInteger(pFSet->fonts[i], FC_WIDTH, 0, &width);
+ FcResult eSpacRes = FcPatternGetInteger(pFSet->fonts[i], FC_SPACING, 0, &spacing);
+ FcResult eScalableRes = FcPatternGetBool(pFSet->fonts[i], FC_SCALABLE, 0, &scalable);
+ FcResult eSymbolRes = FcPatternGetBool(pFSet->fonts[i], FC_SYMBOL, 0, &symbol);
+ FcResult eIndexRes = FcPatternGetInteger(pFSet->fonts[i], FC_INDEX, 0, &nEntryId);
+ FcResult eFormatRes = FcPatternGetString(pFSet->fonts[i], FC_FONTFORMAT, 0, &format);
+
+ if( eFileRes != FcResultMatch || eFamilyRes != FcResultMatch || eScalableRes != FcResultMatch || eStyleRes != FcResultMatch )
+ continue;
+
+ SAL_INFO(
+ "vcl.fonts.detail",
+ "found font \"" << family << "\" in file " << file << ", weight = "
+ << (eWeightRes == FcResultMatch ? weight : -1) << ", slant = "
+ << (eSpacRes == FcResultMatch ? slant : -1) << ", style = \""
+ << (eStyleRes == FcResultMatch ? reinterpret_cast<const char*>(style) : "<nil>")
+ << "\", width = " << (eWeightRes == FcResultMatch ? width : -1) << ", spacing = "
+ << (eSpacRes == FcResultMatch ? spacing : -1) << ", scalable = "
+ << (eScalableRes == FcResultMatch ? scalable : -1) << ", format "
+ << (eFormatRes == FcResultMatch
+ ? reinterpret_cast<const char*>(format) : "<unknown>")
+ << " symbol = " << (eSymbolRes == FcResultMatch ? symbol : -1));
+
+// OSL_ASSERT(eScalableRes != FcResultMatch || scalable);
+
+ // We support only scalable fonts
+ if( eScalableRes == FcResultMatch && ! scalable )
+ continue;
+
+ if (isPreviouslyDuplicateOrObsoleted(pFSet, i))
+ {
+ SAL_INFO("vcl.fonts.detail", "Ditching " << file << " as duplicate/obsolete");
+ continue;
+ }
+
+ OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
+ splitPath( aOrgPath, aDir, aBase );
+ int nDirID = getDirectoryAtom( aDir );
+
+ PrintFont aFont;
+ aFont.m_nDirectory = nDirID;
+ aFont.m_aFontFile = aBase;
+ if (eIndexRes == FcResultMatch)
+ {
+ aFont.m_nCollectionEntry = GetCollectionIndex(nEntryId);
+ aFont.m_nVariationEntry = GetVariationIndex(nEntryId);
+ }
+
+ auto& rFA = aFont.m_aFontAttributes;
+ rFA.SetWeight(WEIGHT_NORMAL);
+ rFA.SetWidthType(WIDTH_NORMAL);
+ rFA.SetPitch(PITCH_VARIABLE);
+ rFA.SetQuality(512);
+
+ rFA.SetFamilyName(OStringToOUString(std::string_view(reinterpret_cast<char*>(family)), RTL_TEXTENCODING_UTF8));
+ if (eStyleRes == FcResultMatch)
+ rFA.SetStyleName(OStringToOUString(std::string_view(reinterpret_cast<char*>(style)), RTL_TEXTENCODING_UTF8));
+ if (eWeightRes == FcResultMatch)
+ rFA.SetWeight(convertWeight(weight));
+ if (eWidthRes == FcResultMatch)
+ rFA.SetWidthType(convertWidth(width));
+ if (eSpacRes == FcResultMatch)
+ rFA.SetPitch(convertSpacing(spacing));
+ if (eSlantRes == FcResultMatch)
+ rFA.SetItalic(convertSlant(slant));
+ if (eSymbolRes == FcResultMatch)
+ rFA.SetMicrosoftSymbolEncoded(bool(symbol));
+
+ // sort into known fonts
+ fontID nFontID = m_nNextFontID++;
+ m_aFonts.emplace(nFontID, aFont);
+ m_aFontFileToFontID[aBase].insert(nFontID);
+ nFonts++;
+
+ FcPattern* pPattern = pFSet->fonts[i];
+ FcPatternReference(pPattern);
+ FcFontSetAdd(pFilteredSet, pPattern);
+
+ SAL_INFO("vcl.fonts.detail", "inserted font " << family << " as fontID " << nFontID);
+ }
+
+ // tdf#157939 if we drop fonts, drop them from the FcConfig set too so they are not
+ // candidates for suggestions by fontconfig
+ if (pFSet->nfont != pFilteredSet->nfont)
+ rWrapper.replaceFontSet(pFilteredSet);
+ else
+ FcFontSetDestroy(pFilteredSet);
+
+ }
+
+ // how does one get rid of the config ?
+ SAL_INFO("vcl.fonts", "inserted " << nFonts << " fonts from fontconfig");
+}
+
+void PrintFontManager::deinitFontconfig()
+{
+ FontCfgWrapper::release();
+}
+
+void PrintFontManager::addFontconfigDir( const OString& rDirName )
+{
+ const char* pDirName = rDirName.getStr();
+ bool bDirOk = (FcConfigAppFontAddDir(FcConfigGetCurrent(), reinterpret_cast<FcChar8 const *>(pDirName) ) == FcTrue);
+
+ SAL_INFO("vcl.fonts", "FcConfigAppFontAddDir( \"" << pDirName << "\") => " << bDirOk);
+
+ if( !bDirOk )
+ return;
+
+ // load dir-specific fc-config file too if available
+ const OString aConfFileName = rDirName + "/fc_local.conf";
+ FILE* pCfgFile = fopen( aConfFileName.getStr(), "rb" );
+ if( pCfgFile )
+ {
+ fclose( pCfgFile);
+ bool bCfgOk = FcConfigParseAndLoad(FcConfigGetCurrent(),
+ reinterpret_cast<FcChar8 const *>(aConfFileName.getStr()), FcTrue);
+
+ SAL_INFO_IF(!bCfgOk,
+ "vcl.fonts", "FcConfigParseAndLoad( \""
+ << aConfFileName << "\") => " << bCfgOk);
+ } else {
+ SAL_INFO("vcl.fonts", "cannot open " << aConfFileName);
+ }
+}
+
+void PrintFontManager::addFontconfigFile( const OString& rFileName )
+{
+ const char* pFileName = rFileName.getStr();
+ bool bFileOk = (FcConfigAppFontAddFile(FcConfigGetCurrent(), reinterpret_cast<FcChar8 const *>(pFileName) ) == FcTrue);
+
+ SAL_INFO("vcl.fonts", "FcConfigAppFontAddFile(\"" << pFileName << "\") => " << std::boolalpha << bFileOk);
+
+ if( !bFileOk )
+ return;
+
+ // FIXME: we want to add only the newly added font not re-add the whole
+ // application font set.
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+ rWrapper.addFontSet( FcSetApplication );
+}
+
+static void addtopattern(FcPattern *pPattern,
+ FontItalic eItalic, FontWeight eWeight, FontWidth eWidth, FontPitch ePitch)
+{
+ if( eItalic != ITALIC_DONTKNOW )
+ {
+ int nSlant = FC_SLANT_ROMAN;
+ switch( eItalic )
+ {
+ case ITALIC_NORMAL:
+ nSlant = FC_SLANT_ITALIC;
+ break;
+ case ITALIC_OBLIQUE:
+ nSlant = FC_SLANT_OBLIQUE;
+ break;
+ default:
+ break;
+ }
+ FcPatternAddInteger(pPattern, FC_SLANT, nSlant);
+ }
+ if( eWeight != WEIGHT_DONTKNOW )
+ {
+ int nWeight = FC_WEIGHT_NORMAL;
+ switch( eWeight )
+ {
+ case WEIGHT_THIN: nWeight = FC_WEIGHT_THIN;break;
+ case WEIGHT_ULTRALIGHT: nWeight = FC_WEIGHT_ULTRALIGHT;break;
+ case WEIGHT_LIGHT: nWeight = FC_WEIGHT_LIGHT;break;
+ case WEIGHT_SEMILIGHT: nWeight = FC_WEIGHT_BOOK;break;
+ case WEIGHT_NORMAL: nWeight = FC_WEIGHT_NORMAL;break;
+ case WEIGHT_MEDIUM: nWeight = FC_WEIGHT_MEDIUM;break;
+ case WEIGHT_SEMIBOLD: nWeight = FC_WEIGHT_SEMIBOLD;break;
+ case WEIGHT_BOLD: nWeight = FC_WEIGHT_BOLD;break;
+ case WEIGHT_ULTRABOLD: nWeight = FC_WEIGHT_ULTRABOLD;break;
+ case WEIGHT_BLACK: nWeight = FC_WEIGHT_BLACK;break;
+ default:
+ break;
+ }
+ FcPatternAddInteger(pPattern, FC_WEIGHT, nWeight);
+ }
+ if( eWidth != WIDTH_DONTKNOW )
+ {
+ int nWidth = FC_WIDTH_NORMAL;
+ switch( eWidth )
+ {
+ case WIDTH_ULTRA_CONDENSED: nWidth = FC_WIDTH_ULTRACONDENSED;break;
+ case WIDTH_EXTRA_CONDENSED: nWidth = FC_WIDTH_EXTRACONDENSED;break;
+ case WIDTH_CONDENSED: nWidth = FC_WIDTH_CONDENSED;break;
+ case WIDTH_SEMI_CONDENSED: nWidth = FC_WIDTH_SEMICONDENSED;break;
+ case WIDTH_NORMAL: nWidth = FC_WIDTH_NORMAL;break;
+ case WIDTH_SEMI_EXPANDED: nWidth = FC_WIDTH_SEMIEXPANDED;break;
+ case WIDTH_EXPANDED: nWidth = FC_WIDTH_EXPANDED;break;
+ case WIDTH_EXTRA_EXPANDED: nWidth = FC_WIDTH_EXTRAEXPANDED;break;
+ case WIDTH_ULTRA_EXPANDED: nWidth = FC_WIDTH_ULTRAEXPANDED;break;
+ default:
+ break;
+ }
+ FcPatternAddInteger(pPattern, FC_WIDTH, nWidth);
+ }
+ if( ePitch == PITCH_DONTKNOW )
+ return;
+
+ int nSpacing = FC_PROPORTIONAL;
+ switch( ePitch )
+ {
+ case PITCH_FIXED: nSpacing = FC_MONO;break;
+ case PITCH_VARIABLE: nSpacing = FC_PROPORTIONAL;break;
+ default:
+ break;
+ }
+ FcPatternAddInteger(pPattern, FC_SPACING, nSpacing);
+ if (nSpacing == FC_MONO)
+ FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>("monospace"));
+}
+
+namespace
+{
+ //Someday fontconfig will hopefully use bcp47, see:
+ //https://gitlab.freedesktop.org/fontconfig/fontconfig/-/issues/50
+ //In the meantime try something that will fit to workaround, see:
+ //https://gitlab.freedesktop.org/fontconfig/fontconfig/-/issues/30
+ OString mapToFontConfigLangTag(const LanguageTag &rLangTag)
+ {
+ std::shared_ptr<FcStrSet> xLangSet(FcGetLangs(), FcStrSetDestroy);
+ OString sLangAttrib;
+
+ sLangAttrib = OUStringToOString(rLangTag.getBcp47(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
+ if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
+ {
+ return sLangAttrib;
+ }
+
+ sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
+ if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
+ {
+ return sLangAttrib;
+ }
+
+ OString sLang = OUStringToOString(rLangTag.getLanguage(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
+ OString sRegion = OUStringToOString(rLangTag.getCountry(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
+
+ if (!sRegion.isEmpty())
+ {
+ sLangAttrib = sLang + "-" + sRegion;
+ if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
+ {
+ return sLangAttrib;
+ }
+ }
+
+ if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLang.getStr())))
+ {
+ return sLang;
+ }
+
+ return OString();
+ }
+
+ bool isEmoji(sal_uInt32 nCurrentChar)
+ {
+ return u_hasBinaryProperty(nCurrentChar, UCHAR_EMOJI);
+ }
+
+ //returns true if the given code-point couldn't possibly be in rLangTag.
+ bool isImpossibleCodePointForLang(const LanguageTag &rLangTag, sal_uInt32 currentChar)
+ {
+ //a non-default script is set, lets believe it
+ if (rLangTag.hasScript())
+ return false;
+
+ int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT);
+ UScriptCode eScript = static_cast<UScriptCode>(script);
+ bool bIsImpossible = false;
+ OUString sLang = rLangTag.getLanguage();
+ switch (eScript)
+ {
+ //http://en.wiktionary.org/wiki/Category:Oriya_script_languages
+ case USCRIPT_ORIYA:
+ bIsImpossible =
+ sLang != "or" &&
+ sLang != "kxv";
+ break;
+ //http://en.wiktionary.org/wiki/Category:Telugu_script_languages
+ case USCRIPT_TELUGU:
+ bIsImpossible =
+ sLang != "te" &&
+ sLang != "gon" &&
+ sLang != "kfc";
+ break;
+ //http://en.wiktionary.org/wiki/Category:Bengali_script_languages
+ case USCRIPT_BENGALI:
+ bIsImpossible =
+ sLang != "bn" &&
+ sLang != "as" &&
+ sLang != "bpy" &&
+ sLang != "ctg" &&
+ sLang != "sa";
+ break;
+ default:
+ break;
+ }
+ SAL_WARN_IF(bIsImpossible, "vcl.fonts", "In glyph fallback throwing away the language property of "
+ << sLang << " because the detected script for '0x"
+ << OUString::number(currentChar, 16)
+ << "' is " << uscript_getName(eScript)
+ << " and that language doesn't make sense. Autodetecting instead.");
+ return bIsImpossible;
+ }
+
+ OUString getExemplarLangTagForCodePoint(sal_uInt32 currentChar)
+ {
+ if (isEmoji(currentChar))
+ return "und-zsye";
+ int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT);
+ UScriptCode eScript = static_cast<UScriptCode>(script);
+ OStringBuffer aBuf(unicode::getExemplarLanguageForUScriptCode(eScript));
+ if (const char* pScriptCode = uscript_getShortName(eScript))
+ aBuf.append(OStringChar('-') + pScriptCode);
+ return OStringToOUString(aBuf, RTL_TEXTENCODING_UTF8);
+ }
+}
+
+IMPL_LINK_NOARG(PrintFontManager, autoInstallFontLangSupport, Timer *, void)
+{
+ try
+ {
+ using namespace org::freedesktop::PackageKit;
+ css::uno::Reference<XSyncDbusSessionHelper> xSyncDbusSessionHelper(SyncDbusSessionHelper::create(comphelper::getProcessComponentContext()));
+ xSyncDbusSessionHelper->InstallFontconfigResources(comphelper::containerToSequence(m_aCurrentRequests), "hide-finished");
+ }
+ catch (const css::uno::Exception&)
+ {
+ TOOLS_INFO_EXCEPTION("vcl.fonts", "InstallFontconfigResources problem");
+ // Disable this method from now on. It's simply not available on some systems
+ // and leads to an error dialog being shown each time this is called tdf#104883
+ std::shared_ptr<comphelper::ConfigurationChanges> batch( comphelper::ConfigurationChanges::create() );
+ officecfg::Office::Common::PackageKit::EnableFontInstallation::set(false, batch);
+ batch->commit();
+ }
+
+ m_aCurrentRequests.clear();
+}
+
+void PrintFontManager::Substitute(vcl::font::FontSelectPattern &rPattern, OUString& rMissingCodes)
+{
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+
+ // build pattern argument for fontconfig query
+ FcPattern* pPattern = FcPatternCreate();
+
+ // Prefer scalable fonts
+ FcPatternAddBool(pPattern, FC_SCALABLE, FcTrue);
+
+ const OString aTargetName = OUStringToOString( rPattern.maTargetName, RTL_TEXTENCODING_UTF8 );
+ const FcChar8* pTargetNameUtf8 = reinterpret_cast<FcChar8 const *>(aTargetName.getStr());
+ FcPatternAddString(pPattern, FC_FAMILY, pTargetNameUtf8);
+
+ LanguageTag aLangTag(rPattern.meLanguage);
+ OString aLangAttrib = mapToFontConfigLangTag(aLangTag);
+
+ bool bMissingJustBullet = false;
+
+ // Add required Unicode characters, if any
+ if ( !rMissingCodes.isEmpty() )
+ {
+ FcCharSet *codePoints = FcCharSetCreate();
+ bMissingJustBullet = rMissingCodes.getLength() == 1 && rMissingCodes[0] == 0xb7;
+ for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); )
+ {
+ // also handle unicode surrogates
+ const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex );
+ FcCharSetAddChar( codePoints, nCode );
+ //if the codepoint is impossible for this lang tag, then clear it
+ //and autodetect something useful
+ if (!aLangAttrib.isEmpty() && (isImpossibleCodePointForLang(aLangTag, nCode) || isEmoji(nCode)))
+ aLangAttrib.clear();
+ //#i105784#/rhbz#527719 improve selection of fallback font
+ if (aLangAttrib.isEmpty())
+ {
+ aLangTag.reset(getExemplarLangTagForCodePoint(nCode));
+ aLangAttrib = mapToFontConfigLangTag(aLangTag);
+ }
+ }
+ FcPatternAddCharSet(pPattern, FC_CHARSET, codePoints);
+ FcCharSetDestroy(codePoints);
+ }
+
+ if (!aLangAttrib.isEmpty())
+ FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr()));
+
+ addtopattern(pPattern, rPattern.GetItalic(), rPattern.GetWeight(),
+ rPattern.GetWidthType(), rPattern.GetPitch());
+
+ // query fontconfig for a substitute
+ FcConfigSubstitute(FcConfigGetCurrent(), pPattern, FcMatchPattern);
+ FcDefaultSubstitute(pPattern);
+
+ // process the result of the fontconfig query
+ FcResult eResult = FcResultNoMatch;
+ FcFontSet* pFontSet = rWrapper.getFontSet();
+ FcPattern* pResult = FcFontSetMatch(FcConfigGetCurrent(), &pFontSet, 1, pPattern, &eResult);
+ FcPatternDestroy( pPattern );
+
+ FcFontSet* pSet = nullptr;
+ if( pResult )
+ {
+ pSet = FcFontSetCreate();
+ // info: destroying the pSet destroys pResult implicitly
+ // since pResult was "added" to pSet
+ FcFontSetAdd( pSet, pResult );
+ }
+
+ if( pSet )
+ {
+ if( pSet->nfont > 0 )
+ {
+ bool bRet = false;
+
+ //extract the closest match
+ FcChar8* file = nullptr;
+ FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file);
+ int nEntryId = 0;
+ FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nEntryId);
+ if (eIndexRes != FcResultMatch)
+ nEntryId = 0;
+ if( eFileRes == FcResultMatch )
+ {
+ OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
+ splitPath( aOrgPath, aDir, aBase );
+ int nDirID = getDirectoryAtom( aDir );
+ fontID nFontID = findFontFileID(nDirID, aBase, GetCollectionIndex(nEntryId), GetVariationIndex(nEntryId));
+ auto const* pFont = getFont(nFontID);
+ if (pFont)
+ {
+ rPattern.maSearchName = pFont->m_aFontAttributes.GetFamilyName();
+ bRet = true;
+ }
+ }
+
+ SAL_WARN_IF(!bRet, "vcl.fonts", "no FC_FILE found, falling back to name search");
+
+ if (!bRet)
+ {
+ FcChar8* family = nullptr;
+ FcResult eFamilyRes = FcPatternGetString( pSet->fonts[0], FC_FAMILY, 0, &family );
+
+ // get the family name
+ if( eFamilyRes == FcResultMatch )
+ {
+ OString sFamily(reinterpret_cast<char*>(family));
+ std::unordered_map< OString, OString >::const_iterator aI =
+ rWrapper.m_aFontNameToLocalized.find(sFamily);
+ if (aI != rWrapper.m_aFontNameToLocalized.end())
+ sFamily = aI->second;
+ rPattern.maSearchName = OStringToOUString( sFamily, RTL_TEXTENCODING_UTF8 );
+ bRet = true;
+ }
+ }
+
+ if (bRet)
+ {
+ int val = 0;
+ if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WEIGHT, 0, &val))
+ rPattern.SetWeight( convertWeight(val) );
+ if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SLANT, 0, &val))
+ rPattern.SetItalic( convertSlant(val) );
+ if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SPACING, 0, &val))
+ rPattern.SetPitch ( convertSpacing(val) );
+ if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WIDTH, 0, &val))
+ rPattern.SetWidthType ( convertWidth(val) );
+ FcBool bEmbolden;
+ if (FcResultMatch == FcPatternGetBool(pSet->fonts[0], FC_EMBOLDEN, 0, &bEmbolden))
+ rPattern.mbEmbolden = bEmbolden;
+ FcMatrix *pMatrix = nullptr;
+ if (FcResultMatch == FcPatternGetMatrix(pSet->fonts[0], FC_MATRIX, 0, &pMatrix))
+ {
+ rPattern.maItalicMatrix.xx = pMatrix->xx;
+ rPattern.maItalicMatrix.xy = pMatrix->xy;
+ rPattern.maItalicMatrix.yx = pMatrix->yx;
+ rPattern.maItalicMatrix.yy = pMatrix->yy;
+ }
+ }
+
+ // update rMissingCodes by removing resolved code points
+ if( !rMissingCodes.isEmpty() )
+ {
+ std::unique_ptr<sal_uInt32[]> const pRemainingCodes(new sal_uInt32[rMissingCodes.getLength()]);
+ int nRemainingLen = 0;
+ FcCharSet* codePoints;
+ if (!FcPatternGetCharSet(pSet->fonts[0], FC_CHARSET, 0, &codePoints))
+ {
+ for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); )
+ {
+ // also handle surrogates
+ const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex );
+ if (FcCharSetHasChar(codePoints, nCode) != FcTrue)
+ pRemainingCodes[ nRemainingLen++ ] = nCode;
+ }
+ }
+ OUString sStillMissing(pRemainingCodes.get(), nRemainingLen);
+ if (!Application::IsHeadlessModeEnabled() && officecfg::Office::Common::PackageKit::EnableFontInstallation::get())
+ {
+ if (sStillMissing == rMissingCodes) //replaced nothing
+ {
+ //It'd be better if we could ask packagekit using the
+ //missing codepoints or some such rather than using
+ //"language" as a proxy to how fontconfig considers
+ //scripts to default to a given language.
+ for (sal_Int32 i = 0; i < nRemainingLen; ++i)
+ {
+ LanguageTag aOurTag(getExemplarLangTagForCodePoint(pRemainingCodes[i]));
+ OString sTag = OUStringToOString(aOurTag.getBcp47(), RTL_TEXTENCODING_UTF8);
+ if (!m_aPreviousLangSupportRequests.insert(sTag).second)
+ continue;
+ sTag = mapToFontConfigLangTag(aOurTag);
+ if (!sTag.isEmpty() && m_aPreviousLangSupportRequests.find(sTag) == m_aPreviousLangSupportRequests.end())
+ {
+ OString sReq = OString::Concat(":lang=") + sTag;
+ m_aCurrentRequests.push_back(OUString::fromUtf8(sReq));
+ m_aPreviousLangSupportRequests.insert(sTag);
+ }
+ }
+ }
+ if (!m_aCurrentRequests.empty())
+ m_aFontInstallerTimer.Start();
+ }
+ rMissingCodes = sStillMissing;
+ }
+ }
+
+ FcFontSetDestroy( pSet );
+ }
+
+ SAL_INFO("vcl.fonts", "PrintFontManager::Substitute: replacing missing font: '"
+ << rPattern.maTargetName << "' with '" << rPattern.maSearchName
+ << "'");
+
+ static const bool bAbortOnFontSubstitute = [] {
+ const char* pEnv = getenv("SAL_NON_APPLICATION_FONT_USE");
+ return pEnv && strcmp(pEnv, "abort") == 0;
+ }();
+ if (bAbortOnFontSubstitute && rPattern.maTargetName != rPattern.maSearchName)
+ {
+ if (bMissingJustBullet)
+ {
+ // Some fonts exist in "more_fonts", but have no U+00B7 MIDDLE DOT
+ // so will always glyph fallback on measuring mnBulletOffset in
+ // FontMetricData::ImplInitTextLineSize
+ return;
+ }
+ if (rPattern.maTargetName == "Linux Libertine G" && rPattern.maSearchName == "Linux Libertine O")
+ return;
+ SAL_WARN("vcl.fonts", "PrintFontManager::Substitute: missing font: '" << rPattern.maTargetName <<
+ "' try: " << rPattern.maSearchName << " instead");
+ std::cerr << "terminating test due to missing font: " << rPattern.maTargetName << std::endl;
+ std::abort();
+ }
+}
+
+FontConfigFontOptions::~FontConfigFontOptions()
+{
+ FcPatternDestroy(mpPattern);
+}
+
+FcPattern *FontConfigFontOptions::GetPattern() const
+{
+ return mpPattern;
+}
+
+void FontConfigFontOptions::SyncPattern(const OString& rFileName, sal_uInt32 nIndex, sal_uInt32 nVariation, bool bEmbolden)
+{
+ FcPatternDel(mpPattern, FC_FILE);
+ FcPatternAddString(mpPattern, FC_FILE, reinterpret_cast<FcChar8 const *>(rFileName.getStr()));
+ FcPatternDel(mpPattern, FC_INDEX);
+ sal_uInt32 nFcIndex = (nVariation << 16) | nIndex;
+ FcPatternAddInteger(mpPattern, FC_INDEX, nFcIndex);
+ FcPatternDel(mpPattern, FC_EMBOLDEN);
+ FcPatternAddBool(mpPattern, FC_EMBOLDEN, bEmbolden ? FcTrue : FcFalse);
+}
+
+std::unique_ptr<FontConfigFontOptions> PrintFontManager::getFontOptions(const FontAttributes& rInfo, int nSize)
+{
+ FontOptionsKey aKey{ rInfo.GetFamilyName(), nSize, rInfo.GetItalic(),
+ rInfo.GetWeight(), rInfo.GetWidthType(), rInfo.GetPitch() };
+
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+
+ std::unique_ptr<FontConfigFontOptions> pOptions = rWrapper.m_aCachedFontOptions.lookup(aKey);
+ if (pOptions)
+ return pOptions;
+
+ FcConfig* pConfig = FcConfigGetCurrent();
+ FcPattern* pPattern = FcPatternCreate();
+
+ OString sFamily = OUStringToOString(aKey.m_sFamilyName, RTL_TEXTENCODING_UTF8);
+
+ std::unordered_map< OString, OString >::const_iterator aI = rWrapper.m_aLocalizedToCanonical.find(sFamily);
+ if (aI != rWrapper.m_aLocalizedToCanonical.end())
+ sFamily = aI->second;
+ if( !sFamily.isEmpty() )
+ FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(sFamily.getStr()));
+
+ addtopattern(pPattern, aKey.m_eItalic, aKey.m_eWeight, aKey.m_eWidth, aKey.m_ePitch);
+ FcPatternAddDouble(pPattern, FC_PIXEL_SIZE, nSize);
+
+ FcConfigSubstitute(pConfig, pPattern, FcMatchPattern);
+ FontConfigFontOptions::cairo_font_options_substitute(pPattern);
+ FcDefaultSubstitute(pPattern);
+
+ FcResult eResult = FcResultNoMatch;
+ FcFontSet* pFontSet = rWrapper.getFontSet();
+ if (FcPattern* pResult = FcFontSetMatch(pConfig, &pFontSet, 1, pPattern, &eResult))
+ {
+ rWrapper.m_aCachedFontOptions.cache(aKey, pResult);
+ pOptions.reset(new FontConfigFontOptions(pResult));
+ }
+
+ // cleanup
+ FcPatternDestroy( pPattern );
+
+ return pOptions;
+}
+
+
+bool PrintFontManager::matchFont(FontAttributes& rDFA, const css::lang::Locale& rLocale)
+{
+ bool bFound = false;
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+
+ FcConfig* pConfig = FcConfigGetCurrent();
+ FcPattern* pPattern = FcPatternCreate();
+
+ // populate pattern with font characteristics
+ const LanguageTag aLangTag(rLocale);
+ const OString aLangAttrib = mapToFontConfigLangTag(aLangTag);
+ if (!aLangAttrib.isEmpty())
+ FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr()));
+
+ OString aFamily = OUStringToOString(rDFA.GetFamilyName(), RTL_TEXTENCODING_UTF8);
+ if( !aFamily.isEmpty() )
+ FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(aFamily.getStr()));
+
+ addtopattern(pPattern, rDFA.GetItalic(), rDFA.GetWeight(), rDFA.GetWidthType(), rDFA.GetPitch());
+
+ FcConfigSubstitute(pConfig, pPattern, FcMatchPattern);
+ FcDefaultSubstitute(pPattern);
+ FcResult eResult = FcResultNoMatch;
+ FcFontSet *pFontSet = rWrapper.getFontSet();
+ FcPattern* pResult = FcFontSetMatch(pConfig, &pFontSet, 1, pPattern, &eResult);
+ if( pResult )
+ {
+ FcFontSet* pSet = FcFontSetCreate();
+ FcFontSetAdd( pSet, pResult );
+ if( pSet->nfont > 0 )
+ {
+ //extract the closest match
+ FcChar8* file = nullptr;
+ FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file);
+ int nEntryId = 0;
+ FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nEntryId);
+ if (eIndexRes != FcResultMatch)
+ nEntryId = 0;
+ if( eFileRes == FcResultMatch )
+ {
+ OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
+ splitPath( aOrgPath, aDir, aBase );
+ int nDirID = getDirectoryAtom( aDir );
+ fontID nFontID = findFontFileID(nDirID, aBase,
+ GetCollectionIndex(nEntryId),
+ GetVariationIndex(nEntryId));
+ auto const* pFont = getFont(nFontID);
+ if (pFont)
+ {
+ rDFA = pFont->m_aFontAttributes;
+ bFound = true;
+ }
+ }
+ }
+ // info: destroying the pSet destroys pResult implicitly
+ // since pResult was "added" to pSet
+ FcFontSetDestroy( pSet );
+ }
+
+ // cleanup
+ FcPatternDestroy( pPattern );
+
+ return bFound;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/fontmanager/fontmanager.cxx b/vcl/unx/generic/fontmanager/fontmanager.cxx
new file mode 100644
index 0000000000..a9ab5202cb
--- /dev/null
+++ b/vcl/unx/generic/fontmanager/fontmanager.cxx
@@ -0,0 +1,736 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <unistd.h>
+#include <osl/thread.h>
+
+#include <unx/fontmanager.hxx>
+#include <impfontcharmap.hxx>
+#include <unotools/syslocaleoptions.hxx>
+#include <unx/gendata.hxx>
+#include <unx/helper.hxx>
+#include <vcl/fontcharmap.hxx>
+
+#include <tools/urlobj.hxx>
+
+#include <o3tl/string_view.hxx>
+#include <osl/file.hxx>
+
+#include <rtl/ustrbuf.hxx>
+#include <rtl/strbuf.hxx>
+
+#include <sal/macros.h>
+#include <sal/log.hxx>
+
+#include <i18nlangtag/applelangid.hxx>
+
+#include <sft.hxx>
+
+#if OSL_DEBUG_LEVEL > 1
+#include <sys/times.h>
+#include <stdio.h>
+#endif
+
+#include <algorithm>
+#include <set>
+
+#ifdef CALLGRIND_COMPILE
+#include <valgrind/callgrind.h>
+#endif
+
+#include <com/sun/star/beans/XMaterialHolder.hpp>
+
+using namespace vcl;
+using namespace utl;
+using namespace psp;
+using namespace osl;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::lang;
+
+/*
+ * static helpers
+ */
+
+static sal_uInt16 getUInt16BE( const sal_uInt8*& pBuffer )
+{
+ sal_uInt16 nRet = static_cast<sal_uInt16>(pBuffer[1]) |
+ (static_cast<sal_uInt16>(pBuffer[0]) << 8);
+ pBuffer+=2;
+ return nRet;
+}
+
+/*
+ * PrintFont implementations
+ */
+PrintFontManager::PrintFont::PrintFont()
+: m_nDirectory(0)
+, m_nCollectionEntry(0)
+, m_nVariationEntry(0)
+{
+}
+
+/*
+ * one instance only
+ */
+PrintFontManager& PrintFontManager::get()
+{
+ GenericUnixSalData* const pSalData(GetGenericUnixSalData());
+ assert(pSalData);
+ return *pSalData->GetPrintFontManager();
+}
+
+/*
+ * the PrintFontManager
+ */
+
+PrintFontManager::PrintFontManager()
+ : m_nNextFontID( 1 )
+ , m_nNextDirAtom( 1 )
+ , m_aFontInstallerTimer("PrintFontManager m_aFontInstallerTimer")
+{
+ m_aFontInstallerTimer.SetInvokeHandler(LINK(this, PrintFontManager, autoInstallFontLangSupport));
+ m_aFontInstallerTimer.SetTimeout(5000);
+}
+
+PrintFontManager::~PrintFontManager()
+{
+ m_aFontInstallerTimer.Stop();
+ deinitFontconfig();
+}
+
+OString PrintFontManager::getDirectory( int nAtom ) const
+{
+ std::unordered_map< int, OString >::const_iterator it( m_aAtomToDir.find( nAtom ) );
+ return it != m_aAtomToDir.end() ? it->second : OString();
+}
+
+int PrintFontManager::getDirectoryAtom( const OString& rDirectory )
+{
+ int nAtom = 0;
+ std::unordered_map< OString, int >::const_iterator it
+ ( m_aDirToAtom.find( rDirectory ) );
+ if( it != m_aDirToAtom.end() )
+ nAtom = it->second;
+ else
+ {
+ nAtom = m_nNextDirAtom++;
+ m_aDirToAtom[ rDirectory ] = nAtom;
+ m_aAtomToDir[ nAtom ] = rDirectory;
+ }
+ return nAtom;
+}
+
+std::vector<fontID> PrintFontManager::addFontFile( std::u16string_view rFileUrl )
+{
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ INetURLObject aPath( rFileUrl );
+ OString aName(OUStringToOString(aPath.GetLastName(INetURLObject::DecodeMechanism::WithCharset, aEncoding), aEncoding));
+ OString aDir( OUStringToOString(
+ INetURLObject::decode( aPath.GetPath(), INetURLObject::DecodeMechanism::WithCharset, aEncoding ), aEncoding ) );
+
+ int nDirID = getDirectoryAtom( aDir );
+ std::vector<fontID> aFontIds = findFontFileIDs( nDirID, aName );
+ if( aFontIds.empty() )
+ {
+ addFontconfigFile(OUStringToOString(aPath.GetFull(), osl_getThreadTextEncoding()));
+
+ std::vector<PrintFont> aNewFonts = analyzeFontFile(nDirID, aName);
+ for (auto & font : aNewFonts)
+ {
+ fontID nFontId = m_nNextFontID++;
+ m_aFonts[nFontId] = std::move(font);
+ m_aFontFileToFontID[ aName ].insert( nFontId );
+ aFontIds.push_back(nFontId);
+ }
+ }
+ return aFontIds;
+}
+
+std::vector<PrintFontManager::PrintFont> PrintFontManager::analyzeFontFile( int nDirID, const OString& rFontFile, const char *pFormat ) const
+{
+ std::vector<PrintFontManager::PrintFont> aNewFonts;
+
+ OString aDir( getDirectory( nDirID ) );
+
+ OString aFullPath = aDir + "/" + rFontFile;
+
+ bool bSupported;
+ int nFD;
+ int n;
+ if (sscanf(aFullPath.getStr(), "/:FD:/%d%n", &nFD, &n) == 1 && aFullPath.getStr()[n] == '\0')
+ {
+ // Hack, pathname that actually means we will use a pre-opened file descriptor
+ bSupported = true;
+ }
+ else
+ {
+ // #i1872# reject unreadable files
+ if( access( aFullPath.getStr(), R_OK ) )
+ return aNewFonts;
+
+ bSupported = false;
+ if (pFormat)
+ {
+ if (!strcmp(pFormat, "TrueType") ||
+ !strcmp(pFormat, "CFF"))
+ bSupported = true;
+ }
+ if (!bSupported)
+ {
+ OString aExt( rFontFile.copy( rFontFile.lastIndexOf( '.' )+1 ) );
+ if( aExt.equalsIgnoreAsciiCase("ttf")
+ || aExt.equalsIgnoreAsciiCase("ttc")
+ || aExt.equalsIgnoreAsciiCase("tte") // #i33947# for Gaiji support
+ || aExt.equalsIgnoreAsciiCase("otf") ) // check for TTF- and PS-OpenType too
+ bSupported = true;
+ }
+ }
+
+ if (bSupported)
+ {
+ // get number of ttc entries
+ int nLength = CountTTCFonts( aFullPath.getStr() );
+ if (nLength > 0)
+ {
+ SAL_INFO("vcl.fonts", "ttc: " << aFullPath << " contains " << nLength << " fonts");
+
+ for( int i = 0; i < nLength; i++ )
+ {
+ PrintFont aFont;
+ aFont.m_nDirectory = nDirID;
+ aFont.m_aFontFile = rFontFile;
+ aFont.m_nCollectionEntry = i;
+ if (analyzeSfntFile(aFont))
+ aNewFonts.push_back(aFont);
+ }
+ }
+ else
+ {
+ PrintFont aFont;
+ aFont.m_nDirectory = nDirID;
+ aFont.m_aFontFile = rFontFile;
+ aFont.m_nCollectionEntry = 0;
+
+ // need to read the font anyway to get aliases inside the font file
+ if (analyzeSfntFile(aFont))
+ aNewFonts.push_back(aFont);
+ }
+ }
+ return aNewFonts;
+}
+
+fontID PrintFontManager::findFontFileID(int nDirID, const OString& rFontFile, int nFaceIndex, int nVariationIndex) const
+{
+ fontID nID = 0;
+
+ auto set_it = m_aFontFileToFontID.find( rFontFile );
+ if( set_it == m_aFontFileToFontID.end() )
+ return nID;
+
+ for (fontID elem : set_it->second)
+ {
+ auto it = m_aFonts.find(elem);
+ if( it == m_aFonts.end() )
+ continue;
+ const PrintFont& rFont = (*it).second;
+ if (rFont.m_nDirectory == nDirID &&
+ rFont.m_aFontFile == rFontFile &&
+ rFont.m_nCollectionEntry == nFaceIndex &&
+ rFont.m_nVariationEntry == nVariationIndex)
+ {
+ nID = it->first;
+ if (nID)
+ break;
+ }
+ }
+
+ return nID;
+}
+
+std::vector<fontID> PrintFontManager::findFontFileIDs( int nDirID, const OString& rFontFile ) const
+{
+ std::vector<fontID> aIds;
+
+ auto set_it = m_aFontFileToFontID.find( rFontFile );
+ if( set_it == m_aFontFileToFontID.end() )
+ return aIds;
+
+ for (auto const& elem : set_it->second)
+ {
+ auto it = m_aFonts.find(elem);
+ if( it == m_aFonts.end() )
+ continue;
+ const PrintFont& rFont = (*it).second;
+ if (rFont.m_nDirectory == nDirID &&
+ rFont.m_aFontFile == rFontFile)
+ aIds.push_back(it->first);
+ }
+
+ return aIds;
+}
+
+namespace {
+
+OUString convertSfntName( const NameRecord& rNameRecord )
+{
+ OUString aValue;
+ if(
+ ( rNameRecord.platformID == 3 && ( rNameRecord.encodingID == 0 || rNameRecord.encodingID == 1 ) ) // MS, Unicode
+ ||
+ ( rNameRecord.platformID == 0 ) // Apple, Unicode
+ )
+ {
+ OUStringBuffer aName( rNameRecord.sptr.size()/2 );
+ const sal_uInt8* pNameBuffer = rNameRecord.sptr.data();
+ for(size_t n = 0; n < rNameRecord.sptr.size()/2; n++ )
+ aName.append( static_cast<sal_Unicode>(getUInt16BE( pNameBuffer )) );
+ aValue = aName.makeStringAndClear();
+ }
+ else if( rNameRecord.platformID == 3 )
+ {
+ if( rNameRecord.encodingID >= 2 && rNameRecord.encodingID <= 6 )
+ {
+ /*
+ * and now for a special kind of madness:
+ * some fonts encode their byte value string as BE uint16
+ * (leading to stray zero bytes in the string)
+ * while others code two bytes as a uint16 and swap to BE
+ */
+ OStringBuffer aName;
+ const sal_uInt8* pNameBuffer = rNameRecord.sptr.data();
+ for(size_t n = 0; n < rNameRecord.sptr.size()/2; n++ )
+ {
+ sal_Unicode aCode = static_cast<sal_Unicode>(getUInt16BE( pNameBuffer ));
+ char aChar = aCode >> 8;
+ if( aChar )
+ aName.append( aChar );
+ aChar = aCode & 0x00ff;
+ if( aChar )
+ aName.append( aChar );
+ }
+ switch( rNameRecord.encodingID )
+ {
+ case 2:
+ aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_932 );
+ break;
+ case 3:
+ aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_936 );
+ break;
+ case 4:
+ aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_950 );
+ break;
+ case 5:
+ aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_949 );
+ break;
+ case 6:
+ aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_1361 );
+ break;
+ }
+ }
+ }
+ else if( rNameRecord.platformID == 1 )
+ {
+ std::string_view aName(reinterpret_cast<const char*>(rNameRecord.sptr.data()), rNameRecord.sptr.size());
+ rtl_TextEncoding eEncoding = RTL_TEXTENCODING_DONTKNOW;
+ switch (rNameRecord.encodingID)
+ {
+ case 0:
+ eEncoding = RTL_TEXTENCODING_APPLE_ROMAN;
+ break;
+ case 1:
+ eEncoding = RTL_TEXTENCODING_APPLE_JAPANESE;
+ break;
+ case 2:
+ eEncoding = RTL_TEXTENCODING_APPLE_CHINTRAD;
+ break;
+ case 3:
+ eEncoding = RTL_TEXTENCODING_APPLE_KOREAN;
+ break;
+ case 4:
+ eEncoding = RTL_TEXTENCODING_APPLE_ARABIC;
+ break;
+ case 5:
+ eEncoding = RTL_TEXTENCODING_APPLE_HEBREW;
+ break;
+ case 6:
+ eEncoding = RTL_TEXTENCODING_APPLE_GREEK;
+ break;
+ case 7:
+ eEncoding = RTL_TEXTENCODING_APPLE_CYRILLIC;
+ break;
+ case 9:
+ eEncoding = RTL_TEXTENCODING_APPLE_DEVANAGARI;
+ break;
+ case 10:
+ eEncoding = RTL_TEXTENCODING_APPLE_GURMUKHI;
+ break;
+ case 11:
+ eEncoding = RTL_TEXTENCODING_APPLE_GUJARATI;
+ break;
+ case 21:
+ eEncoding = RTL_TEXTENCODING_APPLE_THAI;
+ break;
+ case 25:
+ eEncoding = RTL_TEXTENCODING_APPLE_CHINSIMP;
+ break;
+ case 29:
+ eEncoding = RTL_TEXTENCODING_APPLE_CENTEURO;
+ break;
+ case 32: //Uninterpreted
+ eEncoding = RTL_TEXTENCODING_UTF8;
+ break;
+ default:
+ if (o3tl::starts_with(aName, "Khmer OS") || // encoding '20' (Khmer) isn't implemented
+ o3tl::starts_with(aName, "YoavKtav")) // tdf#152278
+ {
+ eEncoding = RTL_TEXTENCODING_UTF8;
+ }
+ SAL_WARN_IF(eEncoding == RTL_TEXTENCODING_DONTKNOW, "vcl.fonts", "mac encoding " <<
+ rNameRecord.encodingID << " in font '" << aName << "'" <<
+ (rNameRecord.encodingID > 32 ? " is invalid" : " has unimplemented conversion"));
+ break;
+ }
+ if (eEncoding != RTL_TEXTENCODING_DONTKNOW)
+ aValue = OStringToOUString(aName, eEncoding);
+ }
+
+ return aValue;
+}
+
+OUString analyzeSfntFamilyName(void const * pTTFont)
+{
+ OUString aFamily;
+
+ std::vector<NameRecord> aNameRecords;
+ GetTTNameRecords( static_cast<TrueTypeFont const *>(pTTFont), aNameRecords );
+ if( !aNameRecords.empty() )
+ {
+ LanguageTag aUILang(SvtSysLocaleOptions().GetRealUILanguageTag());
+ LanguageType eLang = aUILang.getLanguageType();
+ int nLastMatch = -1;
+ for( size_t i = 0; i < aNameRecords.size(); i++ )
+ {
+ if( aNameRecords[i].nameID != 1 || aNameRecords[i].sptr.empty() )
+ continue;
+ int nMatch = -1;
+ if( aNameRecords[i].platformID == 0 ) // Unicode
+ nMatch = 4000;
+ else if( aNameRecords[i].platformID == 3 )
+ {
+ // this bases on the LanguageType actually being a Win LCID
+ if (aNameRecords[i].languageID == eLang)
+ nMatch = 8000;
+ else if( aNameRecords[i].languageID == LANGUAGE_ENGLISH_US )
+ nMatch = 2000;
+ else if( aNameRecords[i].languageID == LANGUAGE_ENGLISH ||
+ aNameRecords[i].languageID == LANGUAGE_ENGLISH_UK )
+ nMatch = 1500;
+ else
+ nMatch = 1000;
+ }
+ else if (aNameRecords[i].platformID == 1)
+ {
+ AppleLanguageId aAppleId = static_cast<AppleLanguageId>(static_cast<sal_uInt16>(aNameRecords[i].languageID));
+ LanguageTag aApple(makeLanguageTagFromAppleLanguageId(aAppleId));
+ if (aApple == aUILang)
+ nMatch = 8000;
+ else if (aAppleId == AppleLanguageId::ENGLISH)
+ nMatch = 2000;
+ else
+ nMatch = 1000;
+ }
+ OUString aName = convertSfntName( aNameRecords[i] );
+ if (!(aName.isEmpty()) && nMatch > nLastMatch)
+ {
+ nLastMatch = nMatch;
+ aFamily = aName;
+ }
+ }
+ }
+
+ return aFamily;
+}
+
+}
+
+bool PrintFontManager::analyzeSfntFile( PrintFont& rFont ) const
+{
+ bool bSuccess = false;
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OString aFile = getFontFile( rFont );
+ TrueTypeFont* pTTFont = nullptr;
+
+ auto& rDFA = rFont.m_aFontAttributes;
+ rDFA.SetQuality(512);
+
+ auto const e = OpenTTFontFile( aFile.getStr(), rFont.m_nCollectionEntry, &pTTFont );
+ if( e == SFErrCodes::Ok )
+ {
+ TTGlobalFontInfo aInfo;
+ GetTTGlobalFontInfo( pTTFont, & aInfo );
+
+ if (rDFA.GetFamilyName().isEmpty())
+ {
+ OUString aFamily = analyzeSfntFamilyName(pTTFont);
+ if (aFamily.isEmpty())
+ {
+ // poor font does not have a family name
+ // name it to file name minus the extension
+ sal_Int32 dotIndex = rFont.m_aFontFile.lastIndexOf('.');
+ if ( dotIndex == -1 )
+ dotIndex = rFont.m_aFontFile.getLength();
+ aFamily = OStringToOUString(rFont.m_aFontFile.subView(0, dotIndex), aEncoding);
+ }
+
+ rDFA.SetFamilyName(aFamily);
+ }
+
+ if( !aInfo.usubfamily.isEmpty() )
+ rDFA.SetStyleName(aInfo.usubfamily);
+
+ rDFA.SetFamilyType(matchFamilyName(rDFA.GetFamilyName()));
+
+ switch( aInfo.weight )
+ {
+ case FW_THIN: rDFA.SetWeight(WEIGHT_THIN); break;
+ case FW_EXTRALIGHT: rDFA.SetWeight(WEIGHT_ULTRALIGHT); break;
+ case FW_LIGHT: rDFA.SetWeight(WEIGHT_LIGHT); break;
+ case FW_MEDIUM: rDFA.SetWeight(WEIGHT_MEDIUM); break;
+ case FW_SEMIBOLD: rDFA.SetWeight(WEIGHT_SEMIBOLD); break;
+ case FW_BOLD: rDFA.SetWeight(WEIGHT_BOLD); break;
+ case FW_EXTRABOLD: rDFA.SetWeight(WEIGHT_ULTRABOLD); break;
+ case FW_BLACK: rDFA.SetWeight(WEIGHT_BLACK); break;
+
+ case FW_NORMAL:
+ default: rDFA.SetWeight(WEIGHT_NORMAL); break;
+ }
+
+ switch( aInfo.width )
+ {
+ case FWIDTH_ULTRA_CONDENSED: rDFA.SetWidthType(WIDTH_ULTRA_CONDENSED); break;
+ case FWIDTH_EXTRA_CONDENSED: rDFA.SetWidthType(WIDTH_EXTRA_CONDENSED); break;
+ case FWIDTH_CONDENSED: rDFA.SetWidthType(WIDTH_CONDENSED); break;
+ case FWIDTH_SEMI_CONDENSED: rDFA.SetWidthType(WIDTH_SEMI_CONDENSED); break;
+ case FWIDTH_SEMI_EXPANDED: rDFA.SetWidthType(WIDTH_SEMI_EXPANDED); break;
+ case FWIDTH_EXPANDED: rDFA.SetWidthType(WIDTH_EXPANDED); break;
+ case FWIDTH_EXTRA_EXPANDED: rDFA.SetWidthType(WIDTH_EXTRA_EXPANDED); break;
+ case FWIDTH_ULTRA_EXPANDED: rDFA.SetWidthType(WIDTH_ULTRA_EXPANDED); break;
+
+ case FWIDTH_NORMAL:
+ default: rDFA.SetWidthType(WIDTH_NORMAL); break;
+ }
+
+ rDFA.SetPitch(aInfo.pitch ? PITCH_FIXED : PITCH_VARIABLE);
+ rDFA.SetItalic(aInfo.italicAngle == 0 ? ITALIC_NONE : (aInfo.italicAngle < 0 ? ITALIC_NORMAL : ITALIC_OBLIQUE));
+ // #104264# there are fonts that set italic angle 0 although they are
+ // italic; use macstyle bit here
+ if( aInfo.italicAngle == 0 && (aInfo.macStyle & 2) )
+ rDFA.SetItalic(ITALIC_NORMAL);
+
+ rDFA.SetMicrosoftSymbolEncoded(aInfo.microsoftSymbolEncoded);
+
+ CloseTTFont( pTTFont );
+ bSuccess = true;
+ }
+ else
+ SAL_WARN("vcl.fonts", "Could not OpenTTFont \"" << aFile << "\": " << int(e));
+
+ return bSuccess;
+}
+
+void PrintFontManager::initialize()
+{
+ #ifdef CALLGRIND_COMPILE
+ CALLGRIND_TOGGLE_COLLECT();
+ CALLGRIND_ZERO_STATS();
+ #endif
+
+ // initialize can be called more than once, e.g.
+ // gtk-fontconfig-timestamp changes to reflect new font installed and
+ // PrintFontManager::initialize called again
+ {
+ m_nNextFontID = 1;
+ m_aFonts.clear();
+ }
+#if OSL_DEBUG_LEVEL > 1
+ clock_t aStart;
+ clock_t aStep1;
+ clock_t aStep2;
+
+ struct tms tms;
+
+ aStart = times( &tms );
+#endif
+
+ // first try fontconfig
+ initFontconfig();
+
+ // part one - look for downloadable fonts
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ const OUString &rSalPrivatePath = psp::getFontPath();
+
+ // search for the fonts in SAL_PRIVATE_FONTPATH first; those are
+ // the fonts installed with the office
+ if( !rSalPrivatePath.isEmpty() )
+ {
+ OString aPath = OUStringToOString( rSalPrivatePath, aEncoding );
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OString aToken = aPath.getToken( 0, ';', nIndex );
+ normPath( aToken );
+ if (!aToken.isEmpty())
+ addFontconfigDir(aToken);
+ } while( nIndex >= 0 );
+ }
+
+ countFontconfigFonts();
+
+#if OSL_DEBUG_LEVEL > 1
+ aStep1 = times( &tms );
+
+ aStep2 = times( &tms );
+ SAL_INFO("vcl.fonts", "PrintFontManager::initialize: collected "
+ << m_aFonts.size()
+ << " fonts.");
+ double fTick = (double)sysconf( _SC_CLK_TCK );
+ SAL_INFO("vcl.fonts", "Step 1 took "
+ << ((double)(aStep1 - aStart)/fTick)
+ << " seconds.");
+ SAL_INFO("vcl.fonts", "Step 2 took "
+ << ((double)(aStep2 - aStep1)/fTick)
+ << " seconds.");
+#endif
+
+ #ifdef CALLGRIND_COMPILE
+ CALLGRIND_DUMP_STATS();
+ CALLGRIND_TOGGLE_COLLECT();
+ #endif
+}
+
+void PrintFontManager::getFontList( ::std::vector< fontID >& rFontIDs )
+{
+ rFontIDs.clear();
+
+ for (auto const& font : m_aFonts)
+ rFontIDs.push_back(font.first);
+}
+
+int PrintFontManager::getFontFaceNumber( fontID nFontID ) const
+{
+ int nRet = 0;
+ const PrintFont* pFont = getFont( nFontID );
+ if (pFont)
+ {
+ nRet = pFont->m_nCollectionEntry;
+ if (nRet < 0)
+ nRet = 0;
+ }
+ return nRet;
+}
+
+int PrintFontManager::getFontFaceVariation( fontID nFontID ) const
+{
+ int nRet = 0;
+ const PrintFont* pFont = getFont( nFontID );
+ if (pFont)
+ {
+ nRet = pFont->m_nVariationEntry;
+ if (nRet < 0)
+ nRet = 0;
+ }
+ return nRet;
+}
+
+FontFamily PrintFontManager::matchFamilyName( std::u16string_view rFamily )
+{
+ struct family_t {
+ const char* mpName;
+ sal_uInt16 mnLength;
+ FontFamily meType;
+ };
+
+#define InitializeClass( p, a ) p, sizeof(p) - 1, a
+ static const family_t pFamilyMatch[] = {
+ { InitializeClass( "arial", FAMILY_SWISS ) },
+ { InitializeClass( "arioso", FAMILY_SCRIPT ) },
+ { InitializeClass( "avant garde", FAMILY_SWISS ) },
+ { InitializeClass( "avantgarde", FAMILY_SWISS ) },
+ { InitializeClass( "bembo", FAMILY_ROMAN ) },
+ { InitializeClass( "bookman", FAMILY_ROMAN ) },
+ { InitializeClass( "conga", FAMILY_ROMAN ) },
+ { InitializeClass( "courier", FAMILY_MODERN ) },
+ { InitializeClass( "curl", FAMILY_SCRIPT ) },
+ { InitializeClass( "fixed", FAMILY_MODERN ) },
+ { InitializeClass( "gill", FAMILY_SWISS ) },
+ { InitializeClass( "helmet", FAMILY_MODERN ) },
+ { InitializeClass( "helvetica", FAMILY_SWISS ) },
+ { InitializeClass( "international", FAMILY_MODERN ) },
+ { InitializeClass( "lucida", FAMILY_SWISS ) },
+ { InitializeClass( "new century schoolbook", FAMILY_ROMAN ) },
+ { InitializeClass( "palatino", FAMILY_ROMAN ) },
+ { InitializeClass( "roman", FAMILY_ROMAN ) },
+ { InitializeClass( "sans serif", FAMILY_SWISS ) },
+ { InitializeClass( "sansserif", FAMILY_SWISS ) },
+ { InitializeClass( "serf", FAMILY_ROMAN ) },
+ { InitializeClass( "serif", FAMILY_ROMAN ) },
+ { InitializeClass( "times", FAMILY_ROMAN ) },
+ { InitializeClass( "utopia", FAMILY_ROMAN ) },
+ { InitializeClass( "zapf chancery", FAMILY_SCRIPT ) },
+ { InitializeClass( "zapfchancery", FAMILY_SCRIPT ) }
+ };
+
+ OString aFamily = OUStringToOString( rFamily, RTL_TEXTENCODING_ASCII_US );
+ sal_uInt32 nLower = 0;
+ sal_uInt32 nUpper = SAL_N_ELEMENTS(pFamilyMatch);
+
+ while( nLower < nUpper )
+ {
+ sal_uInt32 nCurrent = (nLower + nUpper) / 2;
+ const family_t* pHaystack = pFamilyMatch + nCurrent;
+ sal_Int32 nComparison =
+ rtl_str_compareIgnoreAsciiCase_WithLength
+ (
+ aFamily.getStr(), aFamily.getLength(),
+ pHaystack->mpName, pHaystack->mnLength
+ );
+
+ if( nComparison < 0 )
+ nUpper = nCurrent;
+ else
+ if( nComparison > 0 )
+ nLower = nCurrent + 1;
+ else
+ return pHaystack->meType;
+ }
+
+ return FAMILY_DONTKNOW;
+}
+
+OString PrintFontManager::getFontFile(const PrintFont& rFont) const
+{
+ std::unordered_map< int, OString >::const_iterator it = m_aAtomToDir.find(rFont.m_nDirectory);
+ assert(it != m_aAtomToDir.end());
+ OString aPath = it->second + "/" + rFont.m_aFontFile;
+ return aPath;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/fontmanager/fontsubst.cxx b/vcl/unx/generic/fontmanager/fontsubst.cxx
new file mode 100644
index 0000000000..d4fae2f790
--- /dev/null
+++ b/vcl/unx/generic/fontmanager/fontsubst.cxx
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <unx/geninst.h>
+#include <font/PhysicalFontCollection.hxx>
+#include <font/fontsubstitution.hxx>
+#include <unx/fontmanager.hxx>
+
+// platform specific font substitution hooks
+
+namespace {
+
+class FcPreMatchSubstitution
+: public vcl::font::PreMatchFontSubstitution
+{
+public:
+ bool FindFontSubstitute( vcl::font::FontSelectPattern& ) const override;
+ typedef ::std::pair<vcl::font::FontSelectPattern, vcl::font::FontSelectPattern> value_type;
+private:
+ typedef ::std::list<value_type> CachedFontMapType;
+ mutable CachedFontMapType maCachedFontMap;
+};
+
+class FcGlyphFallbackSubstitution
+: public vcl::font::GlyphFallbackFontSubstitution
+{
+ // TODO: add a cache
+public:
+ bool FindFontSubstitute(vcl::font::FontSelectPattern&, LogicalFontInstance* pLogicalFont, OUString& rMissingCodes) const override;
+};
+
+}
+
+void SalGenericInstance::RegisterFontSubstitutors(vcl::font::PhysicalFontCollection* pFontCollection)
+{
+ // register font fallback substitutions
+ static FcPreMatchSubstitution aSubstPreMatch;
+ pFontCollection->SetPreMatchHook( &aSubstPreMatch );
+
+ // register glyph fallback substitutions
+ static FcGlyphFallbackSubstitution aSubstFallback;
+ pFontCollection->SetFallbackHook( &aSubstFallback );
+}
+
+static vcl::font::FontSelectPattern GetFcSubstitute(const vcl::font::FontSelectPattern &rFontSelData, OUString& rMissingCodes)
+{
+ vcl::font::FontSelectPattern aSubstituted(rFontSelData);
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ rMgr.Substitute(aSubstituted, rMissingCodes);
+ return aSubstituted;
+}
+
+namespace
+{
+ bool uselessmatch(const vcl::font::FontSelectPattern &rOrig, const vcl::font::FontSelectPattern &rNew)
+ {
+ return
+ (
+ rOrig.maTargetName == rNew.maSearchName &&
+ rOrig.GetWeight() == rNew.GetWeight() &&
+ rOrig.GetItalic() == rNew.GetItalic() &&
+ rOrig.GetPitch() == rNew.GetPitch() &&
+ rOrig.GetWidthType() == rNew.GetWidthType()
+ );
+ }
+
+ class equal
+ {
+ private:
+ const vcl::font::FontSelectPattern& mrAttributes;
+ public:
+ explicit equal(const vcl::font::FontSelectPattern& rAttributes)
+ : mrAttributes(rAttributes)
+ {
+ }
+ bool operator()(const FcPreMatchSubstitution::value_type& rOther) const
+ { return rOther.first == mrAttributes; }
+ };
+}
+
+bool FcPreMatchSubstitution::FindFontSubstitute(vcl::font::FontSelectPattern &rFontSelData) const
+{
+ // We don't actually want to talk to Fontconfig at all for symbol fonts
+ if( rFontSelData.IsMicrosoftSymbolEncoded() )
+ return false;
+ // OpenSymbol is a unicode font, but it still deserves to be treated as a symbol font
+ if ( IsOpenSymbol(rFontSelData.maSearchName) )
+ return false;
+
+ //see fdo#41556 and fdo#47636
+ //fontconfig can return e.g. an italic font for a non-italic input and/or
+ //different fonts depending on fontsize, bold, etc settings so don't cache
+ //just on the name, cache map all the input and all the output not just map
+ //from original selection to output fontname
+ vcl::font::FontSelectPattern& rPatternAttributes = rFontSelData;
+ CachedFontMapType &rCachedFontMap = maCachedFontMap;
+ CachedFontMapType::iterator itr = std::find_if(rCachedFontMap.begin(), rCachedFontMap.end(), equal(rPatternAttributes));
+ if (itr != rCachedFontMap.end())
+ {
+ // Cached substitution
+ rFontSelData = itr->second;
+ if (itr != rCachedFontMap.begin())
+ {
+ // MRU, move it to the front
+ rCachedFontMap.splice(rCachedFontMap.begin(), rCachedFontMap, itr);
+ }
+ return true;
+ }
+
+ OUString aDummy;
+ const vcl::font::FontSelectPattern aOut = GetFcSubstitute( rFontSelData, aDummy );
+
+ if( aOut.maSearchName.isEmpty() )
+ return false;
+
+ const bool bHaveSubstitute = !uselessmatch( rFontSelData, aOut );
+
+#ifdef DEBUG
+ std::ostringstream oss;
+ oss << "FcPreMatchSubstitution \""
+ << rFontSelData.maTargetName
+ << "\" bipw="
+ << rFontSelData.GetWeight()
+ << rFontSelData.GetItalic()
+ << rFontSelData.GetPitch()
+ << rFontSelData.GetWidthType()
+ << " -> ";
+ if( !bHaveSubstitute )
+ oss << "no substitute available.";
+ else
+ oss << "\""
+ << aOut.maSearchName
+ << "\" bipw="
+ << aOut.GetWeight()
+ << aOut.GetItalic()
+ << aOut.GetPitch()
+ << aOut.GetWidthType();
+ SAL_INFO("vcl.fonts", oss.str());
+#endif
+
+ if( bHaveSubstitute )
+ {
+ rCachedFontMap.push_front(value_type(rFontSelData, aOut));
+ // Fairly arbitrary limit in this case, but I recall measuring max 8
+ // fonts as the typical max amount of fonts in medium sized documents, so make it
+ // a fair chunk larger to accommodate weird documents./
+ if (rCachedFontMap.size() > 256)
+ rCachedFontMap.pop_back();
+ rFontSelData = aOut;
+ }
+
+ return bHaveSubstitute;
+}
+
+bool FcGlyphFallbackSubstitution::FindFontSubstitute(vcl::font::FontSelectPattern& rFontSelData,
+ LogicalFontInstance* /*pLogicalFont*/,
+ OUString& rMissingCodes ) const
+{
+ // We don't actually want to talk to Fontconfig at all for symbol fonts
+ if( rFontSelData.IsMicrosoftSymbolEncoded() )
+ return false;
+ // OpenSymbol is a unicode font, but it still deserves to be treated as a symbol font
+ if ( IsOpenSymbol(rFontSelData.maSearchName) )
+ return false;
+
+ const vcl::font::FontSelectPattern aOut = GetFcSubstitute( rFontSelData, rMissingCodes );
+ // TODO: cache the unicode + srcfont specific result
+ // FC doing it would be preferable because it knows the invariables
+ // e.g. FC knows the FC rule that all Arial gets replaced by LiberationSans
+ // whereas we would have to check for every size or attribute
+ if( aOut.maSearchName.isEmpty() )
+ return false;
+
+ const bool bHaveSubstitute = !uselessmatch( rFontSelData, aOut );
+
+#ifdef DEBUG
+ std::ostringstream oss;
+ oss << "FcGFSubstitution \""
+ << rFontSelData.maTargetName
+ << "\" bipw="
+ << rFontSelData.GetWeight()
+ << rFontSelData.GetItalic()
+ << rFontSelData.GetPitch()
+ << rFontSelData.GetWidthType()
+ << " -> ";
+ if( !bHaveSubstitute )
+ oss << "no substitute available.";
+ else
+ oss << "\""
+ << aOut.maSearchName
+ << "\" bipw="
+ << aOut.GetWeight()
+ << aOut.GetItalic()
+ << aOut.GetPitch()
+ << aOut.GetWidthType();
+ SAL_INFO("vcl.fonts", oss.str());
+#endif
+
+ if( bHaveSubstitute )
+ rFontSelData = aOut;
+
+ return bHaveSubstitute;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/fontmanager/helper.cxx b/vcl/unx/generic/fontmanager/helper.cxx
new file mode 100644
index 0000000000..afa6d9cb76
--- /dev/null
+++ b/vcl/unx/generic/fontmanager/helper.cxx
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <sys/stat.h>
+#include <limits.h>
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <osl/thread.h>
+#include <rtl/bootstrap.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <tools/urlobj.hxx>
+#include <unx/helper.hxx>
+
+#include <tuple>
+
+using ::rtl::Bootstrap;
+
+namespace psp {
+
+OUString getOfficePath( whichOfficePath ePath )
+{
+ static const auto aPaths = [] {
+ OUString sRoot, sUser, sConfig;
+ Bootstrap::get("BRAND_BASE_DIR", sRoot);
+ Bootstrap aBootstrap(sRoot + "/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap"));
+ aBootstrap.getFrom("UserInstallation", sUser);
+ aBootstrap.getFrom("CustomDataUrl", sConfig);
+ OUString aUPath = sUser + "/user/psprint";
+ if (sRoot.startsWith("file://"))
+ {
+ OUString aSysPath;
+ if (osl::FileBase::getSystemPathFromFileURL(sRoot, aSysPath) == osl::FileBase::E_None)
+ sRoot = aSysPath;
+ }
+ if (sUser.startsWith("file://"))
+ {
+ OUString aSysPath;
+ if (osl::FileBase::getSystemPathFromFileURL(sUser, aSysPath) == osl::FileBase::E_None)
+ sUser = aSysPath;
+ }
+ if (sConfig.startsWith("file://"))
+ {
+ OUString aSysPath;
+ if (osl::FileBase::getSystemPathFromFileURL(sConfig, aSysPath) == osl::FileBase::E_None)
+ sConfig = aSysPath;
+ }
+
+ // ensure user path exists
+ SAL_INFO("vcl.fonts", "Trying to create: " << aUPath);
+ osl::Directory::createPath(aUPath);
+
+ return std::make_tuple(sRoot, sUser, sConfig);
+ }();
+ const auto& [aInstallationRootPath, aUserPath, aConfigPath] = aPaths;
+
+ switch( ePath )
+ {
+ case whichOfficePath::ConfigPath: return aConfigPath;
+ case whichOfficePath::InstallationRootPath: return aInstallationRootPath;
+ case whichOfficePath::UserPath: return aUserPath;
+ }
+ return OUString();
+}
+
+static OString getEnvironmentPath( const char* pKey )
+{
+ OString aPath;
+
+ const char* pValue = getenv( pKey );
+ if( pValue && *pValue )
+ {
+ aPath = OString( pValue );
+ }
+ return aPath;
+}
+
+} // namespace psp
+
+void psp::getPrinterPathList( std::vector< OUString >& rPathList, const char* pSubDir )
+{
+ rPathList.clear();
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+
+ OUStringBuffer aPathBuffer( 256 );
+
+ // append net path
+ aPathBuffer.append( getOfficePath( whichOfficePath::InstallationRootPath ) );
+ if( !aPathBuffer.isEmpty() )
+ {
+ aPathBuffer.append( "/" LIBO_SHARE_FOLDER "/psprint" );
+ if( pSubDir )
+ {
+ aPathBuffer.append( '/' );
+ aPathBuffer.appendAscii( pSubDir );
+ }
+ rPathList.push_back( aPathBuffer.makeStringAndClear() );
+ }
+ // append user path
+ aPathBuffer.append( getOfficePath( whichOfficePath::UserPath ) );
+ if( !aPathBuffer.isEmpty() )
+ {
+ aPathBuffer.append( "/user/psprint" );
+ if( pSubDir )
+ {
+ aPathBuffer.append( '/' );
+ aPathBuffer.appendAscii( pSubDir );
+ }
+ rPathList.push_back( aPathBuffer.makeStringAndClear() );
+ }
+
+ OString aPath( getEnvironmentPath("SAL_PSPRINT") );
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OString aDir( aPath.getToken( 0, ':', nIndex ) );
+ if( aDir.isEmpty() )
+ continue;
+
+ if( pSubDir )
+ {
+ aDir += OString::Concat("/") + pSubDir;
+ }
+ struct stat aStat;
+ if( stat( aDir.getStr(), &aStat ) || ! S_ISDIR( aStat.st_mode ) )
+ continue;
+
+ rPathList.push_back( OStringToOUString( aDir, aEncoding ) );
+ } while( nIndex != -1 );
+
+ #ifdef SYSTEM_PPD_DIR
+ if( pSubDir && rtl_str_compare( pSubDir, PRINTER_PPDDIR ) == 0 )
+ {
+ rPathList.push_back( OStringToOUString( OString( SYSTEM_PPD_DIR ), RTL_TEXTENCODING_UTF8 ) );
+ }
+ #endif
+
+ if( !rPathList.empty() )
+ return;
+
+ // last resort: next to program file (mainly for setup)
+ OUString aExe;
+ if( osl_getExecutableFile( &aExe.pData ) == osl_Process_E_None )
+ {
+ INetURLObject aDir( aExe );
+ aDir.removeSegment();
+ aExe = aDir.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ OUString aSysPath;
+ if( osl_getSystemPathFromFileURL( aExe.pData, &aSysPath.pData ) == osl_File_E_None )
+ {
+ rPathList.push_back( aSysPath );
+ }
+ }
+}
+
+OUString const & psp::getFontPath()
+{
+ static OUString aPath;
+
+ if (aPath.isEmpty())
+ {
+ OUStringBuffer aPathBuffer( 512 );
+
+ OUString aConfigPath( getOfficePath( whichOfficePath::ConfigPath ) );
+ OUString aInstallationRootPath( getOfficePath( whichOfficePath::InstallationRootPath ) );
+ OUString aUserPath( getOfficePath( whichOfficePath::UserPath ) );
+ if (!aInstallationRootPath.isEmpty())
+ {
+ // internal font resources, required for normal operation, like OpenSymbol
+ aPathBuffer.append(aInstallationRootPath
+ + "/" LIBO_SHARE_RESOURCE_FOLDER "/common/fonts;");
+ }
+ if( !aConfigPath.isEmpty() )
+ {
+ // #i53530# Path from CustomDataUrl will completely
+ // replace net share and user paths if the path exists
+ OUString sPath = aConfigPath + "/" LIBO_SHARE_FOLDER "/fonts";
+ // check existence of config path
+ struct stat aStat;
+ if( 0 != stat( OUStringToOString( sPath, osl_getThreadTextEncoding() ).getStr(), &aStat )
+ || ! S_ISDIR( aStat.st_mode ) )
+ aConfigPath.clear();
+ else
+ {
+ aPathBuffer.append(sPath);
+ }
+ }
+ if( aConfigPath.isEmpty() )
+ {
+ if( !aInstallationRootPath.isEmpty() )
+ {
+ aPathBuffer.append( aInstallationRootPath
+ + "/" LIBO_SHARE_FOLDER "/fonts/truetype;");
+ }
+ if( !aUserPath.isEmpty() )
+ {
+ aPathBuffer.append( aUserPath + "/user/fonts" );
+ }
+ }
+
+ aPath = aPathBuffer.makeStringAndClear();
+ SAL_INFO("vcl.fonts", "Initializing font path to: " << aPath);
+ }
+ return aPath;
+}
+
+void psp::normPath( OString& rPath )
+{
+ char buf[PATH_MAX];
+
+ // double slashes and slash at end are probably
+ // removed by realpath anyway, but since this runs
+ // on many different platforms let's play it safe
+ OString aPath = rPath.replaceAll("//"_ostr, "/"_ostr);
+
+ if( aPath.endsWith("/") )
+ aPath = aPath.copy(0, aPath.getLength()-1);
+
+ if( ( aPath.indexOf("./") != -1 ||
+ aPath.indexOf( '~' ) != -1 )
+ && realpath( aPath.getStr(), buf ) )
+ {
+ rPath = buf;
+ }
+ else
+ {
+ rPath = aPath;
+ }
+}
+
+void psp::splitPath( OString& rPath, OString& rDir, OString& rBase )
+{
+ normPath( rPath );
+ sal_Int32 nIndex = rPath.lastIndexOf( '/' );
+ if( nIndex > 0 )
+ rDir = rPath.copy( 0, nIndex );
+ else if( nIndex == 0 ) // root dir
+ rDir = rPath.copy( 0, 1 );
+ if( rPath.getLength() > nIndex+1 )
+ rBase = rPath.copy( nIndex+1 );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.cxx b/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.cxx
new file mode 100644
index 0000000000..5a751f9ea5
--- /dev/null
+++ b/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.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 "X11CairoSalGraphicsImpl.hxx"
+
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <salframe.hxx>
+
+X11CairoSalGraphicsImpl::X11CairoSalGraphicsImpl(X11SalGraphics& rParent, CairoCommon& rCairoCommon)
+ : mrParent(rParent)
+ , mrCairoCommon(rCairoCommon)
+{
+}
+
+tools::Long X11CairoSalGraphicsImpl::GetGraphicsWidth() const
+{
+ if (mrParent.m_pFrame)
+ return mrParent.m_pFrame->maGeometry.width();
+ return mrCairoCommon.m_pSurface ? mrCairoCommon.m_aFrameSize.getX() : 0;
+}
+
+void X11CairoSalGraphicsImpl::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight)
+{
+ mrCairoCommon.drawRect(nX, nY, nWidth, nHeight, getAntiAlias());
+}
+
+void X11CairoSalGraphicsImpl::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry)
+{
+ mrCairoCommon.drawPolygon(nPoints, pPtAry, getAntiAlias());
+}
+
+void X11CairoSalGraphicsImpl::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPointCounts,
+ const Point** pPtAry)
+{
+ mrCairoCommon.drawPolyPolygon(nPoly, pPointCounts, pPtAry, getAntiAlias());
+}
+
+void X11CairoSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+ mrCairoCommon.drawPolyPolygon(rObjectToDevice, rPolyPolygon, fTransparency, getAntiAlias());
+}
+
+void X11CairoSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY)
+{
+ mrCairoCommon.drawPixel(mrCairoCommon.m_oLineColor, nX, nY, getAntiAlias());
+}
+
+void X11CairoSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY, Color aColor)
+{
+ mrCairoCommon.drawPixel(aColor, nX, nY, getAntiAlias());
+}
+
+Color X11CairoSalGraphicsImpl::getPixel(tools::Long nX, tools::Long nY)
+{
+ return CairoCommon::getPixel(mrCairoCommon.m_pSurface, nX, nY);
+}
+
+void X11CairoSalGraphicsImpl::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2,
+ tools::Long nY2)
+{
+ mrCairoCommon.drawLine(nX1, nY1, nX2, nY2, getAntiAlias());
+}
+
+void X11CairoSalGraphicsImpl::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry)
+{
+ mrCairoCommon.drawPolyLine(nPoints, pPtAry, getAntiAlias());
+}
+
+bool X11CairoSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& rPolyLine,
+ double fTransparency, double fLineWidth,
+ const std::vector<double>* pStroke,
+ basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle, bool bPixelSnapHairline)
+{
+ return mrCairoCommon.drawPolyLine(rObjectToDevice, rPolyLine, fTransparency, fLineWidth,
+ pStroke, eLineJoin, eLineCap, fMiterMinimumAngle,
+ bPixelSnapHairline, getAntiAlias());
+}
+
+bool X11CairoSalGraphicsImpl::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, sal_uInt8 nTransparency)
+{
+ return mrCairoCommon.drawAlphaRect(nX, nY, nWidth, nHeight, nTransparency, getAntiAlias());
+}
+
+bool X11CairoSalGraphicsImpl::drawGradient(const tools::PolyPolygon& rPolyPolygon,
+ const Gradient& rGradient)
+{
+ return mrCairoCommon.drawGradient(rPolyPolygon, rGradient, getAntiAlias());
+}
+
+bool X11CairoSalGraphicsImpl::implDrawGradient(basegfx::B2DPolyPolygon const& rPolyPolygon,
+ SalGradient const& rGradient)
+{
+ return mrCairoCommon.implDrawGradient(rPolyPolygon, rGradient, getAntiAlias());
+}
+
+void X11CairoSalGraphicsImpl::invert(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, SalInvert nFlags)
+{
+ mrCairoCommon.invert(nX, nY, nWidth, nHeight, nFlags, getAntiAlias());
+}
+
+void X11CairoSalGraphicsImpl::invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags)
+{
+ mrCairoCommon.invert(nPoints, pPtAry, nFlags, getAntiAlias());
+}
+
+bool X11CairoSalGraphicsImpl::hasFastDrawTransformedBitmap() const
+{
+ return CairoCommon::hasFastDrawTransformedBitmap();
+}
+
+bool X11CairoSalGraphicsImpl::supportsOperation(OutDevSupportType eType) const
+{
+ return CairoCommon::supportsOperation(eType);
+}
+
+void X11CairoSalGraphicsImpl::copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX,
+ tools::Long nSrcY, tools::Long nSrcWidth,
+ tools::Long nSrcHeight, bool /*bWindowInvalidate*/)
+{
+ SalTwoRect aTR(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
+
+ cairo_surface_t* source = mrCairoCommon.m_pSurface;
+ mrCairoCommon.copyBitsCairo(aTR, source, getAntiAlias());
+}
+
+void X11CairoSalGraphicsImpl::copyBits(const SalTwoRect& rTR, SalGraphics* pSrcGraphics)
+{
+ cairo_surface_t* source = nullptr;
+
+ if (pSrcGraphics)
+ {
+ X11CairoSalGraphicsImpl* pSrc
+ = static_cast<X11CairoSalGraphicsImpl*>(pSrcGraphics->GetImpl());
+ source = pSrc->mrCairoCommon.m_pSurface;
+ }
+ else
+ {
+ source = mrCairoCommon.m_pSurface;
+ }
+
+ mrCairoCommon.copyBitsCairo(rTR, source, getAntiAlias());
+}
+
+void X11CairoSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
+{
+ mrCairoCommon.drawBitmap(rPosAry, rSalBitmap, getAntiAlias());
+}
+
+void X11CairoSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransparentBitmap)
+{
+ drawAlphaBitmap(rPosAry, rSalBitmap, rTransparentBitmap);
+}
+
+bool X11CairoSalGraphicsImpl::drawAlphaBitmap(const SalTwoRect& rTR, const SalBitmap& rSrcBitmap,
+ const SalBitmap& rAlphaBmp)
+{
+ return mrCairoCommon.drawAlphaBitmap(rTR, rSrcBitmap, rAlphaBmp, getAntiAlias());
+}
+
+void X11CairoSalGraphicsImpl::drawMask(const SalTwoRect& rTR, const SalBitmap& rSalBitmap,
+ Color nMaskColor)
+{
+ mrCairoCommon.drawMask(rTR, rSalBitmap, nMaskColor, getAntiAlias());
+}
+
+std::shared_ptr<SalBitmap> X11CairoSalGraphicsImpl::getBitmap(tools::Long nX, tools::Long nY,
+ tools::Long nWidth,
+ tools::Long nHeight)
+{
+ return mrCairoCommon.getBitmap(nX, nY, nWidth, nHeight);
+}
+
+void X11CairoSalGraphicsImpl::Init() {}
+
+void X11CairoSalGraphicsImpl::freeResources() {}
+
+bool X11CairoSalGraphicsImpl::drawPolyLineBezier(sal_uInt32, const Point*, const PolyFlags*)
+{
+ return false;
+}
+
+bool X11CairoSalGraphicsImpl::drawPolygonBezier(sal_uInt32, const Point*, const PolyFlags*)
+{
+ return false;
+}
+
+bool X11CairoSalGraphicsImpl::drawPolyPolygonBezier(sal_uInt32, const sal_uInt32*,
+ const Point* const*, const PolyFlags* const*)
+{
+ return false;
+}
+
+bool X11CairoSalGraphicsImpl::drawEPS(tools::Long, tools::Long, tools::Long, tools::Long, void*,
+ sal_uInt32)
+{
+ return false;
+}
+
+bool X11CairoSalGraphicsImpl::blendBitmap(const SalTwoRect&, const SalBitmap&) { return false; }
+
+bool X11CairoSalGraphicsImpl::blendAlphaBitmap(const SalTwoRect&, const SalBitmap&,
+ const SalBitmap&, const SalBitmap&)
+{
+ return false;
+}
+
+bool X11CairoSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap, double fAlpha)
+{
+ return mrCairoCommon.drawTransformedBitmap(rNull, rX, rY, rSourceBitmap, pAlphaBitmap, fAlpha,
+ getAntiAlias());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.hxx b/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.hxx
new file mode 100644
index 0000000000..23547daa05
--- /dev/null
+++ b/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.hxx
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cairo-xlib.h>
+#include <unx/salgdi.h>
+#include <unx/x11/x11gdiimpl.h>
+#include "cairo_xlib_cairo.hxx"
+
+#include <headless/CairoCommon.hxx>
+
+class X11CairoSalGraphicsImpl : public SalGraphicsImpl, public X11GraphicsImpl
+{
+private:
+ X11SalGraphics& mrParent;
+ CairoCommon& mrCairoCommon;
+
+public:
+ X11CairoSalGraphicsImpl(X11SalGraphics& rParent, CairoCommon& rCairoCommon);
+
+ void Init() override;
+
+ OUString getRenderBackendName() const override { return "gen"; }
+
+ // get the depth of the device
+ sal_uInt16 GetBitCount() const override { return mrParent.GetVisual().GetDepth(); }
+
+ // get the width of the device
+ tools::Long GetGraphicsWidth() const override;
+
+ void ResetClipRegion() override { mrCairoCommon.m_aClipRegion.SetNull(); }
+
+ void setClipRegion(const vcl::Region& i_rClip) override
+ {
+ mrCairoCommon.m_aClipRegion = i_rClip;
+ }
+
+ void SetLineColor() override { mrCairoCommon.m_oLineColor = std::nullopt; }
+
+ void SetLineColor(Color nColor) override { mrCairoCommon.m_oLineColor = nColor; }
+
+ void SetFillColor() override { mrCairoCommon.m_oFillColor = std::nullopt; }
+
+ void SetFillColor(Color nColor) override { mrCairoCommon.m_oFillColor = nColor; }
+
+ void SetXORMode(bool bSet, bool bInvertOnly) override
+ {
+ mrCairoCommon.SetXORMode(bSet, bInvertOnly);
+ }
+
+ void SetROPLineColor(SalROPColor nROPColor) override
+ {
+ mrCairoCommon.SetROPLineColor(nROPColor);
+ }
+
+ void SetROPFillColor(SalROPColor nROPColor) override
+ {
+ mrCairoCommon.SetROPFillColor(nROPColor);
+ }
+
+ void clipRegion(cairo_t* cr) { CairoCommon::clipRegion(cr, mrCairoCommon.m_aClipRegion); }
+
+ void drawPixel(tools::Long nX, tools::Long nY) override;
+ void drawPixel(tools::Long nX, tools::Long nY, Color nColor) override;
+ Color getPixel(tools::Long nX, tools::Long nY) override;
+
+ void drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2) override;
+
+ void drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight) override;
+
+ void drawPolygon(sal_uInt32 nPoints, const Point* pPtAry) override;
+
+ void drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point** pPtAry) override;
+
+ void drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency) override;
+
+ void drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry) override;
+
+ bool drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& rPolygon, double fTransparency, double fLineWidth,
+ const std::vector<double>* pStroke, basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
+ bool bPixelSnapHairline) override;
+
+ /** Render solid rectangle with given transparency
+
+ @param nTransparency
+ Transparency value (0-255) to use. 0 blits and opaque, 255 a
+ fully transparent rectangle
+ */
+ bool drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ sal_uInt8 nTransparency) override;
+
+ bool drawGradient(const tools::PolyPolygon& rPolygon, const Gradient& rGradient) override;
+
+ bool implDrawGradient(basegfx::B2DPolyPolygon const& rPolyPolygon,
+ SalGradient const& rGradient) override;
+
+ void invert(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ SalInvert nFlags) override;
+
+ void invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags) override;
+
+ // CopyArea --> No RasterOp, but ClipRegion
+ void copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight, bool bWindowInvalidate) override;
+
+ // CopyBits and DrawBitmap --> RasterOp and ClipRegion
+ // CopyBits() --> pSrcGraphics == NULL, then CopyBits on same Graphics
+ void copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics) override;
+
+ void drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap) override;
+
+ void drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ const SalBitmap& rMaskBitmap) override;
+
+ /** Render bitmap with alpha channel
+
+ @param rSourceBitmap
+ Source bitmap to blit
+
+ @param rAlphaBitmap
+ Alpha channel to use for blitting
+
+ @return true, if the operation succeeded, and false
+ otherwise. In this case, clients should try to emulate alpha
+ compositing themselves
+ */
+ bool drawAlphaBitmap(const SalTwoRect&, const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap) override;
+
+ void drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ Color nMaskColor) override;
+
+ std::shared_ptr<SalBitmap> getBitmap(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight) override;
+
+ bool drawPolyLineBezier(sal_uInt32 nPoints, const Point* pPtAry,
+ const PolyFlags* pFlgAry) override;
+
+ bool drawPolygonBezier(sal_uInt32 nPoints, const Point* pPtAry,
+ const PolyFlags* pFlgAry) override;
+
+ bool drawPolyPolygonBezier(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point* const* pPtAry,
+ const PolyFlags* const* pFlgAry) override;
+
+ bool drawEPS(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ void* pPtr, sal_uInt32 nSize) override;
+
+ bool hasFastDrawTransformedBitmap() const override;
+
+ /** draw transformed bitmap (maybe with alpha) where Null, X, Y define the coordinate system */
+ bool drawTransformedBitmap(const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY, const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap, double fAlpha) override;
+
+ /** Blend bitmap with color channels */
+ bool blendBitmap(const SalTwoRect&, const SalBitmap& rBitmap) override;
+
+ /** Render bitmap by blending using the mask and alpha channel */
+ bool blendAlphaBitmap(const SalTwoRect&, const SalBitmap& rSrcBitmap,
+ const SalBitmap& rMaskBitmap, const SalBitmap& rAlphaBitmap) override;
+
+ bool supportsOperation(OutDevSupportType eType) const override;
+
+ void freeResources() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/cairo_xlib_cairo.cxx b/vcl/unx/generic/gdi/cairo_xlib_cairo.cxx
new file mode 100644
index 0000000000..87758f24d9
--- /dev/null
+++ b/vcl/unx/generic/gdi/cairo_xlib_cairo.cxx
@@ -0,0 +1,277 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrender.h>
+
+#include "cairo_xlib_cairo.hxx"
+
+#include <utility>
+#include <vcl/sysdata.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/virdev.hxx>
+#include <sal/log.hxx>
+
+#include <cairo-xlib.h>
+#include <cairo-xlib-xrender.h>
+
+namespace
+{
+ Pixmap limitXCreatePixmap(Display *display, Drawable d, unsigned int width, unsigned int height, unsigned int depth)
+ {
+ // The X protocol request CreatePixmap puts an upper bound
+ // of 16 bit to the size. And in practice some drivers
+ // fall over with values close to the max.
+
+ // see, e.g. moz#424333, fdo#48961, rhbz#1086714
+ // we've a duplicate of this in vcl :-(
+ if (width > SAL_MAX_INT16-10 || height > SAL_MAX_INT16-10)
+ {
+ SAL_WARN("canvas", "overlarge pixmap: " << width << " x " << height);
+ return None;
+ }
+ return XCreatePixmap(display, d, width, height, depth);
+ }
+}
+
+namespace cairo
+{
+
+ X11SysData::X11SysData() :
+ pDisplay(nullptr),
+ hDrawable(0),
+ pVisual(nullptr),
+ nScreen(0)
+ {}
+
+ X11SysData::X11SysData( const SystemGraphicsData& pSysDat ) :
+ pDisplay(static_cast<_XDisplay*>(pSysDat.pDisplay)),
+ hDrawable(pSysDat.hDrawable),
+ pVisual(static_cast<Visual*>(pSysDat.pVisual)),
+ nScreen(pSysDat.nScreen)
+ {}
+
+ X11SysData::X11SysData( const SystemEnvData& pSysDat, const SalFrame* pReference ) :
+ pDisplay(static_cast<_XDisplay*>(pSysDat.pDisplay)),
+ hDrawable(pSysDat.GetWindowHandle(pReference)),
+ pVisual(static_cast<Visual*>(pSysDat.pVisual)),
+ nScreen(pSysDat.nScreen)
+ {}
+
+ X11Pixmap::~X11Pixmap()
+ {
+ if( mpDisplay && mhDrawable )
+ XFreePixmap( mpDisplay, mhDrawable );
+ }
+
+ /**
+ * Surface::Surface: Create Canvas surface with existing data
+ * @param pSysData Platform native system environment data (struct SystemEnvData in vcl/inc/sysdata.hxx)
+ * @param pSurface Cairo surface
+ *
+ * pSysData contains the platform native Drawable reference
+ * This constructor only stores data, it does no processing.
+ * It is used by e.g. Surface::getSimilar()
+ *
+ * Set the mpSurface as pSurface
+ **/
+ X11Surface::X11Surface( const X11SysData& rSysData,
+ X11PixmapSharedPtr rPixmap,
+ CairoSurfaceSharedPtr pSurface ) :
+ maSysData(rSysData),
+ mpPixmap(std::move(rPixmap)),
+ mpSurface(std::move(pSurface))
+ {}
+
+ /**
+ * Surface::Surface: Create generic Canvas surface using given Cairo Surface
+ *
+ * @param pSurface Cairo Surface
+ *
+ * This constructor only stores data, it does no processing.
+ * It is used with e.g. cairo_image_surface_create_for_data()
+ * Unlike other constructors, mpSysData is set to NULL
+ *
+ * Set the mpSurface as pSurface
+ **/
+ X11Surface::X11Surface( CairoSurfaceSharedPtr pSurface ) :
+ maSysData(),
+ mpSurface(std::move(pSurface))
+ {}
+
+ /**
+ * Surface::Surface: Create Canvas surface from Window reference.
+ * @param pSysData Platform native system environment data (struct SystemEnvData in vcl/inc/sysdata.hxx)
+ * @param x horizontal location of the new surface
+ * @param y vertical location of the new surface
+ * @param width width of the new surface
+ * @param height height of the new surface
+ *
+ * pSysData contains the platform native Window reference.
+ *
+ * pSysData is used to create a surface on the Window
+ *
+ * Set the mpSurface to the new surface or NULL
+ **/
+ X11Surface::X11Surface( const X11SysData& rSysData, int x, int y, int width, int height ) :
+ maSysData(rSysData),
+ mpSurface(
+ cairo_xlib_surface_create( rSysData.pDisplay,
+ rSysData.hDrawable,
+ rSysData.pVisual,
+ width + x, height + y ),
+ &cairo_surface_destroy)
+ {
+ cairo_surface_set_device_offset(mpSurface.get(), x, y );
+ }
+
+ /**
+ * Surface::Surface: Create platform native Canvas surface from BitmapSystemData
+ * @param pSysData Platform native system environment data (struct SystemEnvData in vcl/inc/sysdata.hxx)
+ * @param pBmpData Platform native image data (struct BitmapSystemData in vcl/inc/bitmap.hxx)
+ * @param width width of the new surface
+ * @param height height of the new surface
+ *
+ * The pBmpData provides the imagedata that the created surface should contain.
+ *
+ * Set the mpSurface to the new surface or NULL
+ **/
+ X11Surface::X11Surface( const X11SysData& rSysData,
+ const BitmapSystemData& rData ) :
+ maSysData( rSysData ),
+ mpSurface(
+ cairo_xlib_surface_create( rSysData.pDisplay,
+ reinterpret_cast<Drawable>(rData.aPixmap),
+ rSysData.pVisual,
+ rData.mnWidth, rData.mnHeight ),
+ &cairo_surface_destroy)
+ {
+ }
+
+ /**
+ * Surface::getCairo: Create Cairo (drawing object) for the Canvas surface
+ *
+ * @return new Cairo or NULL
+ **/
+ CairoSharedPtr X11Surface::getCairo() const
+ {
+ return CairoSharedPtr( cairo_create(mpSurface.get()),
+ &cairo_destroy );
+ }
+
+ /**
+ * Surface::getSimilar: Create new similar Canvas surface
+ * @param cairo_content_type format of the new surface (cairo_content_t from cairo/src/cairo.h)
+ * @param width width of the new surface
+ * @param height height of the new surface
+ *
+ * Creates a new Canvas surface. This normally creates platform native surface, even though
+ * generic function is used.
+ *
+ * Cairo surface from cairo_content_type (cairo_content_t)
+ *
+ * @return new surface or NULL
+ **/
+ SurfaceSharedPtr X11Surface::getSimilar(int cairo_content_type, int width, int height ) const
+ {
+ if( maSysData.pDisplay && maSysData.hDrawable )
+ {
+ XRenderPictFormat* pFormat;
+ int nFormat;
+
+ switch (cairo_content_type)
+ {
+ case CAIRO_CONTENT_ALPHA:
+ nFormat = PictStandardA8;
+ break;
+ case CAIRO_CONTENT_COLOR:
+ nFormat = PictStandardRGB24;
+ break;
+ case CAIRO_CONTENT_COLOR_ALPHA:
+ default:
+ nFormat = PictStandardARGB32;
+ break;
+ }
+
+ pFormat = XRenderFindStandardFormat( maSysData.pDisplay, nFormat );
+ Pixmap hPixmap = limitXCreatePixmap( maSysData.pDisplay, maSysData.hDrawable,
+ width > 0 ? width : 1, height > 0 ? height : 1,
+ pFormat->depth );
+
+ return SurfaceSharedPtr(
+ new X11Surface( maSysData,
+ std::make_shared<X11Pixmap>(hPixmap, maSysData.pDisplay),
+ CairoSurfaceSharedPtr(
+ cairo_xlib_surface_create_with_xrender_format(
+ maSysData.pDisplay,
+ hPixmap,
+ ScreenOfDisplay(maSysData.pDisplay, maSysData.nScreen),
+ pFormat, width, height ),
+ &cairo_surface_destroy) ));
+ }
+ else
+ return SurfaceSharedPtr(
+ new X11Surface( maSysData,
+ X11PixmapSharedPtr(),
+ CairoSurfaceSharedPtr(
+ cairo_surface_create_similar( mpSurface.get(),
+ static_cast<cairo_content_t>(cairo_content_type), width, height ),
+ &cairo_surface_destroy )));
+ }
+
+ VclPtr<VirtualDevice> X11Surface::createVirtualDevice() const
+ {
+ SystemGraphicsData aSystemGraphicsData;
+
+ cairo_surface_t* pSurface = mpSurface.get();
+
+ aSystemGraphicsData.nSize = sizeof(SystemGraphicsData);
+ aSystemGraphicsData.hDrawable = mpPixmap ? mpPixmap->mhDrawable : maSysData.hDrawable;
+ aSystemGraphicsData.pSurface = pSurface;
+
+ int width = cairo_xlib_surface_get_width(pSurface);
+ int height = cairo_xlib_surface_get_height(pSurface);
+
+ return VclPtr<VirtualDevice>::Create(aSystemGraphicsData,
+ Size(width, height),
+ DeviceFormat::WITHOUT_ALPHA);
+ }
+
+ /**
+ * Surface::Resize: Resizes the Canvas surface.
+ * @param width new width of the surface
+ * @param height new height of the surface
+ *
+ * Only used on X11.
+ *
+ * @return The new surface or NULL
+ **/
+ bool X11Surface::Resize(int width, int height)
+ {
+ cairo_xlib_surface_set_size(mpSurface.get(), width, height);
+ return true;
+ }
+
+ void X11Surface::flush() const
+ {
+ XSync( maSysData.pDisplay, false );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/cairo_xlib_cairo.hxx b/vcl/unx/generic/gdi/cairo_xlib_cairo.hxx
new file mode 100644
index 0000000000..f0b47a3744
--- /dev/null
+++ b/vcl/unx/generic/gdi/cairo_xlib_cairo.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+#include <vcl/cairo.hxx>
+#include <vcl/salgtype.hxx>
+
+struct BitmapSystemData;
+class SalFrame;
+struct SystemEnvData;
+struct SystemGraphicsData;
+
+namespace cairo {
+
+ /// Holds all X11-output relevant data
+ struct X11SysData
+ {
+ X11SysData();
+ explicit X11SysData( const SystemGraphicsData& );
+ explicit X11SysData( const SystemEnvData&, const SalFrame* pReference );
+
+ _XDisplay* pDisplay; // the relevant display connection
+ Drawable hDrawable; // a drawable
+ Visual* pVisual; // the visual in use
+ int nScreen; // the current screen of the drawable
+ };
+
+ /// RAII wrapper for a pixmap
+ struct X11Pixmap
+ {
+ _XDisplay* mpDisplay; // the relevant display connection
+ Pixmap mhDrawable; // a drawable
+
+ X11Pixmap( Pixmap hDrawable, _XDisplay* pDisplay ) :
+ mpDisplay(pDisplay),
+ mhDrawable(hDrawable)
+ {}
+
+ ~X11Pixmap();
+ };
+
+ typedef std::shared_ptr<X11Pixmap> X11PixmapSharedPtr;
+
+ class X11Surface : public Surface
+ {
+ const X11SysData maSysData;
+ X11PixmapSharedPtr mpPixmap;
+ CairoSurfaceSharedPtr mpSurface;
+
+ X11Surface( const X11SysData& rSysData, X11PixmapSharedPtr aPixmap, CairoSurfaceSharedPtr pSurface );
+
+ public:
+ /// takes over ownership of passed cairo_surface
+ explicit X11Surface( CairoSurfaceSharedPtr pSurface );
+ /// create surface on subarea of given drawable
+ X11Surface( const X11SysData& rSysData, int x, int y, int width, int height );
+ /// create surface for given bitmap data
+ X11Surface( const X11SysData& rSysData, const BitmapSystemData& rBmpData );
+
+ // Surface interface
+ virtual CairoSharedPtr getCairo() const override;
+ virtual CairoSurfaceSharedPtr getCairoSurface() const override { return mpSurface; }
+ virtual SurfaceSharedPtr getSimilar(int cairo_content_type, int width, int height) const override;
+
+ virtual VclPtr<VirtualDevice> createVirtualDevice() const override;
+
+ virtual bool Resize( int width, int height ) override;
+
+ virtual void flush() const override;
+
+ const X11PixmapSharedPtr& getPixmap() const { return mpPixmap; }
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/cairotextrender.cxx b/vcl/unx/generic/gdi/cairotextrender.cxx
new file mode 100644
index 0000000000..7e7ce9ca70
--- /dev/null
+++ b/vcl/unx/generic/gdi/cairotextrender.cxx
@@ -0,0 +1,526 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/scopeguard.hxx>
+#include <unx/cairotextrender.hxx>
+#include <unx/fc_fontoptions.hxx>
+#include <unx/freetype_glyphcache.hxx>
+#include <unx/gendata.hxx>
+#include <headless/CairoCommon.hxx>
+#include <vcl/svapp.hxx>
+#include <sallayout.hxx>
+#include <salinst.hxx>
+
+#include <cairo.h>
+#include <cairo-ft.h>
+#if defined(CAIRO_HAS_SVG_SURFACE)
+#include <cairo-svg.h>
+#elif defined(CAIRO_HAS_PDF_SURFACE)
+#include <cairo-pdf.h>
+#endif
+
+#include <deque>
+
+namespace {
+
+typedef struct FT_FaceRec_* FT_Face;
+
+class CairoFontsCache
+{
+public:
+ struct CacheId
+ {
+ FT_Face maFace;
+ const FontConfigFontOptions *mpOptions;
+ bool mbEmbolden;
+ bool mbVerticalMetrics;
+ bool operator ==(const CacheId& rOther) const
+ {
+ return maFace == rOther.maFace &&
+ mpOptions == rOther.mpOptions &&
+ mbEmbolden == rOther.mbEmbolden &&
+ mbVerticalMetrics == rOther.mbVerticalMetrics;
+ }
+ };
+
+private:
+ typedef std::deque< std::pair<cairo_font_face_t*, CacheId> > LRUFonts;
+ static LRUFonts maLRUFonts;
+public:
+ CairoFontsCache() = delete;
+
+ static void CacheFont(cairo_font_face_t* pFont, const CacheId &rId);
+ static cairo_font_face_t* FindCachedFont(const CacheId &rId);
+};
+
+CairoFontsCache::LRUFonts CairoFontsCache::maLRUFonts;
+
+void CairoFontsCache::CacheFont(cairo_font_face_t* pFont, const CairoFontsCache::CacheId &rId)
+{
+ maLRUFonts.push_front( std::pair<cairo_font_face_t*, CairoFontsCache::CacheId>(pFont, rId) );
+ if (maLRUFonts.size() > 8)
+ {
+ cairo_font_face_destroy(maLRUFonts.back().first);
+ maLRUFonts.pop_back();
+ }
+}
+
+cairo_font_face_t* CairoFontsCache::FindCachedFont(const CairoFontsCache::CacheId &rId)
+{
+ auto aI = std::find_if(maLRUFonts.begin(), maLRUFonts.end(),
+ [&rId](const LRUFonts::value_type& rFont) { return rFont.second == rId; });
+ if (aI != maLRUFonts.end())
+ return aI->first;
+ return nullptr;
+}
+
+}
+
+namespace
+{
+ bool hasRotation(int nRotation)
+ {
+ return nRotation != 0;
+ }
+
+ double toRadian(Degree10 nDegree10th)
+ {
+ return toRadians(3600_deg10 - nDegree10th);
+ }
+
+ cairo_t* syncCairoContext(cairo_t* cr)
+ {
+ //rhbz#1283420 tdf#117413 bodge to force a read from the underlying surface which has
+ //the side effect of making the mysterious xrender related problem go away
+ cairo_surface_t *target = cairo_get_target(cr);
+ if (cairo_surface_get_type(target) == CAIRO_SURFACE_TYPE_XLIB)
+ {
+ cairo_surface_t *throw_away = cairo_surface_create_similar(target, cairo_surface_get_content(target), 1, 1);
+ cairo_t *force_read_cr = cairo_create(throw_away);
+ cairo_set_source_surface(force_read_cr, target, 0, 0);
+ cairo_paint(force_read_cr);
+ cairo_destroy(force_read_cr);
+ cairo_surface_destroy(throw_away);
+ }
+ return cr;
+ }
+}
+
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+extern "C"
+{
+ __attribute__((weak)) void __lsan_disable();
+ __attribute__((weak)) void __lsan_enable();
+}
+#endif
+
+namespace {
+ struct CairoFontOptions
+ {
+ // https://gitlab.freedesktop.org/cairo/cairo/-/merge_requests/235
+ // I don't want to have CAIRO_ROUND_GLYPH_POS_ON set in the cairo
+ // surfaces font_options, but that's private, so tricky to achieve
+ cairo_font_options_t* mpRoundGlyphPosOffOptions;
+
+ CairoFontOptions()
+ {
+ // https://gitlab.freedesktop.org/cairo/cairo/-/merge_requests/235
+ // I don't want to have CAIRO_ROUND_GLYPH_POS_ON set in the cairo surfaces
+ // font_options when trying subpixel rendering, but that's a private
+ // feature of cairo_font_options_t, so tricky to achieve. Hack this by
+ // getting the font options of a backend known to set this private feature
+ // to CAIRO_ROUND_GLYPH_POS_OFF and then set to defaults the public
+ // features and the result can be merged with new font options to set
+ // CAIRO_ROUND_GLYPH_POS_OFF in those
+ mpRoundGlyphPosOffOptions = cairo_font_options_create();
+#if defined(CAIRO_HAS_SVG_SURFACE)
+ // svg, pdf and ps backends have CAIRO_ROUND_GLYPH_POS_OFF by default
+ cairo_surface_t* hack = cairo_svg_surface_create(nullptr, 1, 1);
+#elif defined(CAIRO_HAS_PDF_SURFACE)
+ cairo_surface_t* hack = cairo_pdf_surface_create(nullptr, 1, 1);
+#endif
+ cairo_surface_get_font_options(hack, mpRoundGlyphPosOffOptions);
+ cairo_surface_destroy(hack);
+ cairo_font_options_set_antialias(mpRoundGlyphPosOffOptions, CAIRO_ANTIALIAS_DEFAULT);
+ cairo_font_options_set_subpixel_order(mpRoundGlyphPosOffOptions, CAIRO_SUBPIXEL_ORDER_DEFAULT);
+ cairo_font_options_set_hint_style(mpRoundGlyphPosOffOptions, CAIRO_HINT_STYLE_DEFAULT);
+ cairo_font_options_set_hint_metrics(mpRoundGlyphPosOffOptions, CAIRO_HINT_METRICS_DEFAULT);
+ }
+ ~CairoFontOptions()
+ {
+ cairo_font_options_destroy(mpRoundGlyphPosOffOptions);
+ }
+ static const cairo_font_options_t *get()
+ {
+ static CairoFontOptions opts;
+ return opts.mpRoundGlyphPosOffOptions;
+ }
+ };
+}
+
+CairoTextRender::CairoTextRender(CairoCommon& rCairoCommon)
+ : mrCairoCommon(rCairoCommon)
+{
+}
+
+CairoTextRender::~CairoTextRender()
+{
+}
+
+static void ApplyFont(cairo_t* cr, const CairoFontsCache::CacheId& rId, double nWidth, double nHeight, int nGlyphRotation,
+ const GenericSalLayout& rLayout)
+{
+ cairo_font_face_t* font_face = CairoFontsCache::FindCachedFont(rId);
+ if (!font_face)
+ {
+ const FontConfigFontOptions *pOptions = rId.mpOptions;
+ FcPattern *pPattern = pOptions->GetPattern();
+ font_face = cairo_ft_font_face_create_for_pattern(pPattern);
+ CairoFontsCache::CacheFont(font_face, rId);
+ }
+ cairo_set_font_face(cr, font_face);
+
+ cairo_set_font_size(cr, nHeight);
+
+ cairo_matrix_t m;
+ cairo_matrix_init_identity(&m);
+
+ if (rLayout.GetOrientation())
+ cairo_matrix_rotate(&m, toRadian(rLayout.GetOrientation()));
+
+ cairo_matrix_scale(&m, nWidth, nHeight);
+
+ if (nGlyphRotation)
+ cairo_matrix_rotate(&m, toRadian(Degree10(nGlyphRotation * 900)));
+
+ const LogicalFontInstance& rInstance = rLayout.GetFont();
+ if (rInstance.NeedsArtificialItalic())
+ {
+ cairo_matrix_t shear;
+ cairo_matrix_init_identity(&shear);
+ shear.xy = -shear.xx * ARTIFICIAL_ITALIC_SKEW;
+ cairo_matrix_multiply(&m, &shear, &m);
+ }
+
+ cairo_set_font_matrix(cr, &m);
+}
+
+static CairoFontsCache::CacheId makeCacheId(const GenericSalLayout& rLayout)
+{
+ const FreetypeFontInstance& rInstance = static_cast<FreetypeFontInstance&>(rLayout.GetFont());
+ const FreetypeFont& rFont = rInstance.GetFreetypeFont();
+
+ FT_Face aFace = rFont.GetFtFace();
+ CairoFontsCache::CacheId aId;
+ aId.maFace = aFace;
+ aId.mpOptions = rFont.GetFontOptions();
+ aId.mbEmbolden = rInstance.NeedsArtificialBold();
+ aId.mbVerticalMetrics = false;
+
+ return aId;
+}
+
+void CairoTextRender::DrawTextLayout(const GenericSalLayout& rLayout, const SalGraphics& rGraphics)
+{
+ const LogicalFontInstance& rInstance = rLayout.GetFont();
+
+ const bool bSubpixelPositioning = rLayout.GetSubpixelPositioning();
+
+ /*
+ * It might be ideal to cache surface and cairo context between calls and
+ * only destroy it when the drawable changes, but to do that we need to at
+ * least change the SalFrame etc impls to dtor the SalGraphics *before* the
+ * destruction of the windows they reference
+ */
+ cairo_t *cr = syncCairoContext(getCairoContext());
+ if (!cr)
+ {
+ SAL_WARN("vcl", "no cairo context for text");
+ return;
+ }
+ comphelper::ScopeGuard releaseContext([this, cr]() { releaseCairoContext(cr); });
+
+ std::vector<cairo_glyph_t> cairo_glyphs;
+ std::vector<int> glyph_extrarotation;
+ cairo_glyphs.reserve( 256 );
+
+ double nSnapToSubPixelDiff = 0.0;
+ double nXScale, nYScale;
+ dl_cairo_surface_get_device_scale(cairo_get_target(cr), &nXScale, &nYScale);
+
+ basegfx::B2DPoint aPos;
+ const GlyphItem* pGlyph;
+ const GlyphItem* pPrevGlyph = nullptr;
+ int nStart = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ cairo_glyph_t aGlyph;
+ aGlyph.index = pGlyph->glyphId();
+ aGlyph.x = aPos.getX();
+ aGlyph.y = aPos.getY();
+
+ const bool bVertical = pGlyph->IsVertical();
+ glyph_extrarotation.push_back(bVertical ? 1 : 0);
+
+ if (bSubpixelPositioning)
+ {
+ // tdf#150507 like skia, even when subpixel rendering pixel, snap y
+ if (!bVertical)
+ aGlyph.y = std::floor(aGlyph.y + 0.5);
+ else
+ aGlyph.x = std::floor(aGlyph.x + 0.5);
+
+ // tdf#152094 snap to 1/4 of a pixel after a run of whitespace,
+ // probably a little dubious, but maybe worth a shot for lodpi
+ double& rGlyphDimension = !bVertical ? aGlyph.x : aGlyph.y;
+ const int nSubPixels = 4 * (!bVertical ? nXScale : nYScale);
+ if (pGlyph->IsSpacing())
+ nSnapToSubPixelDiff = 0;
+ else if (pPrevGlyph && pPrevGlyph->IsSpacing())
+ {
+ double nSnapToSubPixel = std::floor(rGlyphDimension * nSubPixels) / nSubPixels;
+ nSnapToSubPixelDiff = rGlyphDimension - nSnapToSubPixel;
+ rGlyphDimension = nSnapToSubPixel;
+ }
+ else
+ rGlyphDimension -= nSnapToSubPixelDiff;
+
+ pPrevGlyph = pGlyph;
+ }
+
+ cairo_glyphs.push_back(aGlyph);
+ }
+
+ const size_t nGlyphs = cairo_glyphs.size();
+ if (!nGlyphs)
+ return;
+
+ const vcl::font::FontSelectPattern& rFSD = rInstance.GetFontSelectPattern();
+ double nHeight = rFSD.mnHeight;
+ double nWidth = rFSD.mnWidth ? rFSD.mnWidth : nHeight;
+ if (nWidth == 0 || nHeight == 0)
+ return;
+
+ if (nHeight > SAL_MAX_UINT16)
+ {
+ // as seen with freetype 2.11.0, so cairo surface status is "fail"
+ // ("error occurred in libfreetype") and no further operations are
+ // executed, so this error then leads to later leaks
+ SAL_WARN("vcl", "rendering text would fail with height: " << nHeight);
+ return;
+ }
+
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+ if (nHeight > 8000)
+ {
+ SAL_WARN("vcl", "rendering text would use > 2G Memory: " << nHeight);
+ return;
+ }
+
+ if (nWidth > 2000)
+ {
+ SAL_WARN("vcl", "rendering text would use > 2G Memory: " << nWidth);
+ return;
+ }
+#endif
+
+ clipRegion(cr);
+
+ cairo_set_source_rgba(cr,
+ mnTextColor.GetRed()/255.0,
+ mnTextColor.GetGreen()/255.0,
+ mnTextColor.GetBlue()/255.0,
+ mnTextColor.GetAlpha()/255.0);
+
+ int nRatio = nWidth * 10 / nHeight;
+
+ // tdf#132112 excessive stretch of underbrace and overbrace can trigger freetype into an error, which propagates to cairo
+ // and once a cairo surface is in an error state, that cannot be cleared and all subsequent drawing fails, so bodge that
+ // with a high degree of stretch we draw the brace without stretch to a temp surface and stretch that to give a far
+ // poorer visual result, but one that can be rendered.
+ if (nGlyphs == 1 && nRatio > 100 && (cairo_glyphs[0].index == 974 || cairo_glyphs[0].index == 975) &&
+ rFSD.maTargetName == "OpenSymbol" && !glyph_extrarotation.back() && !rLayout.GetOrientation())
+ {
+ CairoFontsCache::CacheId aId = makeCacheId(rLayout);
+
+ ApplyFont(cr, aId, nWidth, nHeight, 0, rLayout);
+ cairo_text_extents_t stretched_extents;
+ cairo_glyph_extents(cr, cairo_glyphs.data(), nGlyphs, &stretched_extents);
+
+ ApplyFont(cr, aId, nHeight, nHeight, 0, rLayout);
+ cairo_text_extents_t unstretched_extents;
+ cairo_glyph_extents(cr, cairo_glyphs.data(), nGlyphs, &unstretched_extents);
+
+ cairo_surface_t *target = cairo_get_target(cr);
+ cairo_surface_t *temp_surface = cairo_surface_create_similar(target, cairo_surface_get_content(target),
+ unstretched_extents.width, unstretched_extents.height);
+ cairo_t *temp_cr = cairo_create(temp_surface);
+ cairo_glyph_t glyph;
+ glyph.x = -unstretched_extents.x_bearing;
+ glyph.y = -unstretched_extents.y_bearing;
+ glyph.index = cairo_glyphs[0].index;
+
+ ApplyFont(temp_cr, aId, nHeight, nHeight, 0, rLayout);
+
+ cairo_set_source_rgb(temp_cr,
+ mnTextColor.GetRed()/255.0,
+ mnTextColor.GetGreen()/255.0,
+ mnTextColor.GetBlue()/255.0);
+
+ cairo_show_glyphs(temp_cr, &glyph, 1);
+ cairo_destroy(temp_cr);
+
+ cairo_set_source_surface(cr, temp_surface, cairo_glyphs[0].x, cairo_glyphs[0].y + stretched_extents.y_bearing);
+
+ cairo_pattern_t* sourcepattern = cairo_get_source(cr);
+ cairo_matrix_t matrix;
+ cairo_pattern_get_matrix(sourcepattern, &matrix);
+ cairo_matrix_scale(&matrix, unstretched_extents.width / stretched_extents.width, 1);
+ cairo_pattern_set_matrix(sourcepattern, &matrix);
+
+ cairo_rectangle(cr, cairo_glyphs[0].x, cairo_glyphs[0].y + stretched_extents.y_bearing, stretched_extents.width, stretched_extents.height);
+ cairo_fill(cr);
+
+ cairo_surface_destroy(temp_surface);
+
+ return;
+ }
+
+ if (nRatio >= 5120)
+ {
+ // as seen with freetype 2.12.1, so cairo surface status is "fail"
+ SAL_WARN("vcl", "rendering text would fail with stretch of: " << nRatio / 10.0);
+ return;
+ }
+
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+ if (__lsan_disable)
+ __lsan_disable();
+#endif
+
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ const bool bDisableAA = !rStyleSettings.GetUseFontAAFromSystem() && !rGraphics.getAntiAlias();
+ static bool bAllowDefaultHinting = getenv("SAL_ALLOW_DEFAULT_HINTING") != nullptr;
+
+ const cairo_font_options_t* pFontOptions = GetSalInstance()->GetCairoFontOptions();
+ if (pFontOptions || bDisableAA || bSubpixelPositioning)
+ {
+ cairo_hint_style_t eHintStyle = pFontOptions ? cairo_font_options_get_hint_style(pFontOptions) : CAIRO_HINT_STYLE_DEFAULT;
+ bool bAllowedHintStyle = !bSubpixelPositioning || bAllowDefaultHinting || (eHintStyle == CAIRO_HINT_STYLE_NONE || eHintStyle == CAIRO_HINT_STYLE_SLIGHT);
+
+ if (bDisableAA || !bAllowedHintStyle || bSubpixelPositioning)
+ {
+ // Disable font AA in case global AA setting is supposed to affect
+ // font rendering (not the default) and AA is disabled.
+ cairo_font_options_t* pOptions = pFontOptions ? cairo_font_options_copy(pFontOptions) : cairo_font_options_create();
+
+ if (bDisableAA)
+ cairo_font_options_set_antialias(pOptions, CAIRO_ANTIALIAS_NONE);
+ if (!bAllowedHintStyle)
+ cairo_font_options_set_hint_style(pOptions, CAIRO_HINT_STYLE_SLIGHT);
+ if (bSubpixelPositioning)
+ {
+ // Disable private CAIRO_ROUND_GLYPH_POS_ON by merging with
+ // font options known to have CAIRO_ROUND_GLYPH_POS_OFF
+ cairo_font_options_merge(pOptions, CairoFontOptions::get());
+
+ // a) tdf#153699 skip this with cairo 1.17.8 as it has a problem
+ // See: https://gitlab.freedesktop.org/cairo/cairo/-/issues/643
+ // b) tdf#152675 a similar report for cairo: 1.16.0-4ubuntu1,
+ // assume that everything <= 1.17.8 is unsafe to disable this
+ if (cairo_version() > CAIRO_VERSION_ENCODE(1, 17, 8))
+ cairo_font_options_set_hint_metrics(pOptions, CAIRO_HINT_METRICS_OFF);
+ }
+ cairo_set_font_options(cr, pOptions);
+ cairo_font_options_destroy(pOptions);
+ }
+ else if (pFontOptions)
+ cairo_set_font_options(cr, pFontOptions);
+ }
+
+ CairoFontsCache::CacheId aId = makeCacheId(rLayout);
+
+ std::vector<int>::const_iterator aEnd = glyph_extrarotation.end();
+ std::vector<int>::const_iterator aStart = glyph_extrarotation.begin();
+ std::vector<int>::const_iterator aI = aStart;
+ while (aI != aEnd)
+ {
+ int nGlyphRotation = *aI;
+
+ std::vector<int>::const_iterator aNext = nGlyphRotation?(aI+1):std::find_if(aI+1, aEnd, hasRotation);
+
+ size_t nStartIndex = std::distance(aStart, aI);
+ size_t nLen = std::distance(aI, aNext);
+
+ aId.mbVerticalMetrics = nGlyphRotation != 0.0;
+
+ ApplyFont(cr, aId, nWidth, nHeight, nGlyphRotation, rLayout);
+
+ cairo_show_glyphs(cr, &cairo_glyphs[nStartIndex], nLen);
+ if (cairo_status(cr) != CAIRO_STATUS_SUCCESS)
+ {
+ SAL_WARN("vcl", "rendering text failed with stretch ratio of: " << nRatio << ", " << cairo_status_to_string(cairo_status(cr)));
+ }
+
+#if OSL_DEBUG_LEVEL > 2
+ //draw origin
+ cairo_save (cr);
+ cairo_rectangle (cr, cairo_glyphs[nStartIndex].x, cairo_glyphs[nStartIndex].y, 5, 5);
+ cairo_set_source_rgba (cr, 1, 0, 0, 0.80);
+ cairo_fill (cr);
+ cairo_restore (cr);
+#endif
+
+ aI = aNext;
+ }
+
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+ if (__lsan_enable)
+ __lsan_enable();
+#endif
+}
+
+cairo_t* CairoTextRender::getCairoContext()
+{
+ // Note that cairo_set_antialias (bAntiAlias property) doesn't affect cairo
+ // text rendering. That's affected by cairo_font_options_set_antialias instead.
+ return mrCairoCommon.getCairoContext(/*bXorModeAllowed*/false, /*bAntiAlias*/true);
+}
+
+void CairoTextRender::clipRegion(cairo_t* cr)
+{
+ mrCairoCommon.clipRegion(cr);
+}
+
+void CairoTextRender::releaseCairoContext(cairo_t* cr)
+{
+ mrCairoCommon.releaseCairoContext(cr, /*bXorModeAllowed*/false, basegfx::B2DRange());
+}
+
+void FontConfigFontOptions::cairo_font_options_substitute(FcPattern* pPattern)
+{
+ const cairo_font_options_t* pFontOptions = GetSalInstance()->GetCairoFontOptions();
+ if( !pFontOptions )
+ return;
+ cairo_ft_font_options_substitute(pFontOptions, pPattern);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/font.cxx b/vcl/unx/generic/gdi/font.cxx
new file mode 100644
index 0000000000..19887a9af2
--- /dev/null
+++ b/vcl/unx/generic/gdi/font.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 <sal/config.h>
+
+#include <vcl/sysdata.hxx>
+#include <vcl/fontcharmap.hxx>
+
+#include <unx/salgdi.h>
+#include <textrender.hxx>
+#include <sallayout.hxx>
+
+void X11SalGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+ mxTextRenderImpl->DrawTextLayout(rLayout, *this);
+}
+
+FontCharMapRef X11SalGraphics::GetFontCharMap() const
+{
+ return mxTextRenderImpl->GetFontCharMap();
+}
+
+bool X11SalGraphics::GetFontCapabilities(vcl::FontCapabilities &rGetImplFontCapabilities) const
+{
+ return mxTextRenderImpl->GetFontCapabilities(rGetImplFontCapabilities);
+}
+
+// SalGraphics
+void X11SalGraphics::SetFont(LogicalFontInstance* pEntry, int nFallbackLevel)
+{
+ mxTextRenderImpl->SetFont(pEntry, nFallbackLevel);
+}
+
+void
+X11SalGraphics::SetTextColor( Color nColor )
+{
+ mxTextRenderImpl->SetTextColor(nColor);
+}
+
+bool X11SalGraphics::AddTempDevFont( vcl::font::PhysicalFontCollection* pFontCollection,
+ const OUString& rFileURL,
+ const OUString& rFontName )
+{
+ return mxTextRenderImpl->AddTempDevFont(pFontCollection, rFileURL, rFontName);
+}
+
+void X11SalGraphics::ClearDevFontCache()
+{
+ mxTextRenderImpl->ClearDevFontCache();
+}
+
+void X11SalGraphics::GetDevFontList( vcl::font::PhysicalFontCollection* pFontCollection )
+{
+ mxTextRenderImpl->GetDevFontList(pFontCollection);
+}
+
+void
+X11SalGraphics::GetFontMetric( FontMetricDataRef &rxFontMetric, int nFallbackLevel )
+{
+ mxTextRenderImpl->GetFontMetric(rxFontMetric, nFallbackLevel);
+}
+
+std::unique_ptr<GenericSalLayout> X11SalGraphics::GetTextLayout(int nFallbackLevel)
+{
+ return mxTextRenderImpl->GetTextLayout(nFallbackLevel);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/freetypetextrender.cxx b/vcl/unx/generic/gdi/freetypetextrender.cxx
new file mode 100644
index 0000000000..b524a45ee7
--- /dev/null
+++ b/vcl/unx/generic/gdi/freetypetextrender.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <unx/freetypetextrender.hxx>
+
+#include <unotools/configmgr.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <sal/log.hxx>
+
+#include <unx/fontmanager.hxx>
+#include <unx/geninst.h>
+#include <unx/glyphcache.hxx>
+#include <unx/fc_fontoptions.hxx>
+#include <unx/freetype_glyphcache.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <font/FontMetricData.hxx>
+
+#include <sallayout.hxx>
+
+FreeTypeTextRenderImpl::FreeTypeTextRenderImpl()
+ : mnTextColor(Color(0x00, 0x00, 0x00)) //black
+{
+}
+
+FreeTypeTextRenderImpl::~FreeTypeTextRenderImpl()
+{
+ ReleaseFonts();
+}
+
+void FreeTypeTextRenderImpl::SetFont(LogicalFontInstance *pEntry, int nFallbackLevel)
+{
+ // release all no longer needed font resources
+ for( int i = nFallbackLevel; i < MAX_FALLBACK; ++i )
+ {
+ // old server side font is no longer referenced
+ mpFreetypeFont[i] = nullptr;
+ }
+
+ // return early if there is no new font
+ if( !pEntry )
+ return;
+
+ FreetypeFontInstance* pFreetypeFont = static_cast<FreetypeFontInstance*>(pEntry);
+ mpFreetypeFont[ nFallbackLevel ] = pFreetypeFont;
+
+ // ignore fonts with e.g. corrupted font files
+ if (!mpFreetypeFont[nFallbackLevel]->GetFreetypeFont().TestFont())
+ mpFreetypeFont[nFallbackLevel] = nullptr;
+}
+
+FontCharMapRef FreeTypeTextRenderImpl::GetFontCharMap() const
+{
+ if (!mpFreetypeFont[0])
+ return nullptr;
+ return mpFreetypeFont[0]->GetFontFace()->GetFontCharMap();
+}
+
+bool FreeTypeTextRenderImpl::GetFontCapabilities(vcl::FontCapabilities &rGetImplFontCapabilities) const
+{
+ if (!mpFreetypeFont[0])
+ return false;
+ return mpFreetypeFont[0]->GetFontFace()->GetFontCapabilities(rGetImplFontCapabilities);
+}
+
+// SalGraphics
+void
+FreeTypeTextRenderImpl::SetTextColor( Color nColor )
+{
+ if( mnTextColor != nColor )
+ {
+ mnTextColor = nColor;
+ }
+}
+
+bool FreeTypeTextRenderImpl::AddTempDevFont(vcl::font::PhysicalFontCollection* pFontCollection,
+ const OUString& rFileURL, const OUString& rFontName)
+{
+ // inform PSP font manager
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ std::vector<psp::fontID> aFontIds = rMgr.addFontFile(rFileURL);
+ if (aFontIds.empty())
+ return false;
+
+ FreetypeManager& rFreetypeManager = FreetypeManager::get();
+ for (auto const& nFontId : aFontIds)
+ {
+ // prepare font data
+ auto const* pFont = rMgr.getFont(nFontId);
+ if (!pFont)
+ continue;
+
+ // inform glyph cache of new font
+ FontAttributes aDFA = pFont->m_aFontAttributes;
+ aDFA.IncreaseQualityBy(5800);
+ if (!rFontName.isEmpty())
+ aDFA.SetFamilyName(rFontName);
+
+ int nFaceNum = rMgr.getFontFaceNumber(nFontId);
+ int nVariantNum = rMgr.getFontFaceVariation(nFontId);
+
+ const OString& rFileName = rMgr.getFontFileSysPath(nFontId);
+ rFreetypeManager.AddFontFile(rFileName, nFaceNum, nVariantNum, nFontId, aDFA);
+ }
+
+ // announce new font to device's font list
+ rFreetypeManager.AnnounceFonts(pFontCollection);
+ return true;
+}
+
+void FreeTypeTextRenderImpl::ClearDevFontCache()
+{
+ FreetypeManager::get().ClearFontCache();
+}
+
+void FreeTypeTextRenderImpl::GetDevFontList(vcl::font::PhysicalFontCollection* pFontCollection)
+{
+ // prepare the FreetypeManager using psprint's font infos
+ FreetypeManager& rFreetypeManager = FreetypeManager::get();
+
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ ::std::vector<psp::fontID> aList;
+ rMgr.getFontList(aList);
+ for (auto const& nFontId : aList)
+ {
+ auto const* pFont = rMgr.getFont(nFontId);
+ if (!pFont)
+ continue;
+
+ // normalize face number to the FreetypeManager
+ int nFaceNum = rMgr.getFontFaceNumber(nFontId);
+ int nVariantNum = rMgr.getFontFaceVariation(nFontId);
+
+ // inform FreetypeManager about this font provided by the PsPrint subsystem
+ FontAttributes aDFA = pFont->m_aFontAttributes;
+ aDFA.IncreaseQualityBy(4096);
+ const OString& rFileName = rMgr.getFontFileSysPath(nFontId);
+ rFreetypeManager.AddFontFile(rFileName, nFaceNum, nVariantNum, nFontId, aDFA);
+ }
+
+ // announce glyphcache fonts
+ rFreetypeManager.AnnounceFonts(pFontCollection);
+
+ // register platform specific font substitutions if available
+ SalGenericInstance::RegisterFontSubstitutors(pFontCollection);
+}
+
+void FreeTypeTextRenderImpl::GetFontMetric( FontMetricDataRef& rxFontMetric, int nFallbackLevel )
+{
+ if( nFallbackLevel >= MAX_FALLBACK )
+ return;
+
+ if (mpFreetypeFont[nFallbackLevel])
+ mpFreetypeFont[nFallbackLevel]->GetFreetypeFont().GetFontMetric(rxFontMetric);
+}
+
+std::unique_ptr<GenericSalLayout> FreeTypeTextRenderImpl::GetTextLayout(int nFallbackLevel)
+{
+ assert(mpFreetypeFont[nFallbackLevel]);
+ if (!mpFreetypeFont[nFallbackLevel])
+ return nullptr;
+ return std::make_unique<GenericSalLayout>(*mpFreetypeFont[nFallbackLevel]);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/salgdi.cxx b/vcl/unx/generic/gdi/salgdi.cxx
new file mode 100644
index 0000000000..f296e3cf34
--- /dev/null
+++ b/vcl/unx/generic/gdi/salgdi.cxx
@@ -0,0 +1,298 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/skia/SkiaHelper.hxx>
+#if HAVE_FEATURE_SKIA
+#include <skia/x11/gdiimpl.hxx>
+#include <skia/x11/textrender.hxx>
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+
+#include <headless/svpgdi.hxx>
+
+#include <vcl/sysdata.hxx>
+#include <vcl/virdev.hxx>
+#include <sal/log.hxx>
+
+#include <unx/salunx.h>
+#include <unx/saldisp.hxx>
+#include <unx/salgdi.h>
+#include <unx/x11/xlimits.hxx>
+
+#include <salframe.hxx>
+#include <salgdiimpl.hxx>
+#include <textrender.hxx>
+#include <salvd.hxx>
+
+#include <unx/salframe.h>
+#include <unx/cairotextrender.hxx>
+#include "cairo_xlib_cairo.hxx"
+#include <cairo-xlib.h>
+
+#include "X11CairoSalGraphicsImpl.hxx"
+
+
+// X11Common
+
+X11Common::X11Common()
+ : m_hDrawable(None)
+ , m_pColormap(nullptr)
+{}
+
+// X11SalGraphics
+
+X11SalGraphics::X11SalGraphics():
+ m_pFrame(nullptr),
+ m_pVDev(nullptr),
+ m_nXScreen( 0 )
+{
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled())
+ {
+ mxImpl.reset(new X11SkiaSalGraphicsImpl(*this));
+ mxTextRenderImpl.reset(new SkiaTextRender);
+ }
+ else
+#endif
+ {
+ mxImpl.reset(new X11CairoSalGraphicsImpl(*this, maCairoCommon));
+ mxTextRenderImpl.reset(new CairoTextRender(maCairoCommon));
+ }
+}
+
+X11SalGraphics::~X11SalGraphics() COVERITY_NOEXCEPT_FALSE
+{
+ DeInit();
+ ReleaseFonts();
+ freeResources();
+}
+
+void X11SalGraphics::freeResources()
+{
+ mxImpl->freeResources();
+
+ if( m_pDeleteColormap )
+ {
+ m_pDeleteColormap.reset();
+ maX11Common.m_pColormap = nullptr;
+ }
+}
+
+SalGraphicsImpl* X11SalGraphics::GetImpl() const
+{
+ return mxImpl.get();
+}
+
+void X11SalGraphics::SetDrawable(Drawable aDrawable, cairo_surface_t* pSurface, SalX11Screen nXScreen)
+{
+ maCairoCommon.m_pSurface = pSurface;
+ if (maCairoCommon.m_pSurface)
+ {
+ maCairoCommon.m_aFrameSize.setX(cairo_xlib_surface_get_width(pSurface));
+ maCairoCommon.m_aFrameSize.setY(cairo_xlib_surface_get_height(pSurface));
+ dl_cairo_surface_get_device_scale(pSurface, &maCairoCommon.m_fScale, nullptr);
+ }
+
+ // shortcut if nothing changed
+ if( maX11Common.m_hDrawable == aDrawable )
+ return;
+
+ // free screen specific resources if needed
+ if( nXScreen != m_nXScreen )
+ {
+ freeResources();
+ maX11Common.m_pColormap = &vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetColormap( nXScreen );
+ m_nXScreen = nXScreen;
+ }
+
+ maX11Common.m_hDrawable = aDrawable;
+}
+
+void X11SalGraphics::Init( X11SalFrame& rFrame, Drawable aTarget,
+ SalX11Screen nXScreen )
+{
+ maX11Common.m_pColormap = &vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetColormap(nXScreen);
+ m_nXScreen = nXScreen;
+
+ m_pFrame = &rFrame;
+ m_pVDev = nullptr;
+
+ SetDrawable(aTarget, rFrame.GetSurface(), nXScreen);
+ mxImpl->Init();
+}
+
+void X11SalGraphics::DeInit()
+{
+ mxImpl->DeInit();
+ SetDrawable(None, nullptr, m_nXScreen);
+}
+
+void X11SalGraphics::GetResolution( sal_Int32 &rDPIX, sal_Int32 &rDPIY ) // const
+{
+ char* pForceDpi;
+ if ((pForceDpi = getenv("SAL_FORCEDPI")))
+ {
+ OString sForceDPI(pForceDpi);
+ rDPIX = rDPIY = sForceDPI.toInt32();
+ return;
+ }
+
+ const SalDisplay *pDisplay = GetDisplay();
+ if (!pDisplay)
+ {
+ SAL_WARN( "vcl", "Null display");
+ rDPIX = rDPIY = 96;
+ return;
+ }
+
+ Pair dpi = pDisplay->GetResolution();
+ rDPIX = dpi.A();
+ rDPIY = dpi.B();
+
+ if ( rDPIY > 200 )
+ {
+ rDPIX = Divide( rDPIX * 200, rDPIY );
+ rDPIY = 200;
+ }
+
+ // #i12705# equalize x- and y-resolution if they are close enough
+ if( rDPIX == rDPIY )
+ return;
+
+ // different x- and y- resolutions are usually artifacts of
+ // a wrongly calculated screen size.
+#ifdef DEBUG
+ SAL_INFO("vcl.gdi", "Forcing Resolution from "
+ << std::hex << rDPIX
+ << std::dec << rDPIX
+ << " to "
+ << std::hex << rDPIY
+ << std::dec << rDPIY);
+#endif
+ rDPIX = rDPIY; // y-resolution is more trustworthy
+}
+
+SystemGraphicsData X11SalGraphics::GetGraphicsData() const
+{
+ SystemGraphicsData aRes;
+
+ aRes.nSize = sizeof(aRes);
+ aRes.pDisplay = GetXDisplay();
+ aRes.hDrawable = maX11Common.m_hDrawable;
+ aRes.pVisual = GetVisual().visual;
+ aRes.nScreen = m_nXScreen.getXScreen();
+ return aRes;
+}
+
+void X11SalGraphics::Flush()
+{
+ if( X11GraphicsImpl* x11Impl = dynamic_cast< X11GraphicsImpl* >( mxImpl.get()))
+ x11Impl->Flush();
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+bool X11SalGraphics::SupportsCairo() const
+{
+ return true;
+}
+
+cairo::SurfaceSharedPtr X11SalGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const
+{
+ return std::make_shared<cairo::X11Surface>(rSurface);
+}
+
+namespace
+{
+ cairo::X11SysData getSysData( const vcl::Window& rWindow )
+ {
+ const SystemEnvData* pSysData = rWindow.GetSystemData();
+
+ if( !pSysData )
+ return cairo::X11SysData();
+ else
+ return cairo::X11SysData(*pSysData, rWindow.ImplGetFrame());
+ }
+
+ cairo::X11SysData getSysData( const VirtualDevice& rVirDev )
+ {
+ return cairo::X11SysData( rVirDev.GetSystemGfxData() );
+ }
+}
+
+cairo::SurfaceSharedPtr X11SalGraphics::CreateSurface( const OutputDevice& rRefDevice,
+ int x, int y, int width, int height ) const
+{
+ if( rRefDevice.GetOutDevType() == OUTDEV_WINDOW )
+ return std::make_shared<cairo::X11Surface>(getSysData(*rRefDevice.GetOwnerWindow()),
+ x,y,width,height);
+ if( rRefDevice.IsVirtual() )
+ return std::make_shared<cairo::X11Surface>(getSysData(static_cast<const VirtualDevice&>(rRefDevice)),
+ x,y,width,height);
+ return cairo::SurfaceSharedPtr();
+}
+
+cairo::SurfaceSharedPtr X11SalGraphics::CreateBitmapSurface( const OutputDevice& rRefDevice,
+ const BitmapSystemData& rData,
+ const Size& rSize ) const
+{
+ SAL_INFO("vcl", "requested size: " << rSize.Width() << " x " << rSize.Height()
+ << " available size: " << rData.mnWidth << " x "
+ << rData.mnHeight);
+ if ( rData.mnWidth == rSize.Width() && rData.mnHeight == rSize.Height() )
+ {
+ if( rRefDevice.GetOutDevType() == OUTDEV_WINDOW )
+ return std::make_shared<cairo::X11Surface>(getSysData(*rRefDevice.GetOwnerWindow()), rData );
+ else if( rRefDevice.IsVirtual() )
+ return std::make_shared<cairo::X11Surface>(getSysData(static_cast<const VirtualDevice&>(rRefDevice)), rData );
+ }
+
+ return cairo::SurfaceSharedPtr();
+}
+
+css::uno::Any X11SalGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& rSurface, const basegfx::B2ISize& /*rSize*/) const
+{
+ cairo::X11Surface& rXlibSurface=dynamic_cast<cairo::X11Surface&>(*rSurface);
+ css::uno::Sequence< css::uno::Any > args{
+ css::uno::Any(false), // do not call XFreePixmap on it
+ css::uno::Any(sal_Int64(rXlibSurface.getPixmap()->mhDrawable))
+ };
+ return css::uno::Any(args);
+}
+
+#endif // ENABLE_CAIRO_CANVAS
+
+SalGeometryProvider *X11SalGraphics::GetGeometryProvider() const
+{
+ if (m_pFrame)
+ return static_cast< SalGeometryProvider * >(m_pFrame);
+ else
+ return static_cast< SalGeometryProvider * >(m_pVDev);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/salvd.cxx b/vcl/unx/generic/gdi/salvd.cxx
new file mode 100644
index 0000000000..e0a9d33f6e
--- /dev/null
+++ b/vcl/unx/generic/gdi/salvd.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 <vcl/sysdata.hxx>
+
+#include <X11/Xlib.h>
+
+#include <unx/saldisp.hxx>
+#include <unx/salinst.h>
+#include <unx/salgdi.h>
+#include <unx/salvd.h>
+#include <unx/x11/xlimits.hxx>
+
+#include <config_features.h>
+#include <vcl/skia/SkiaHelper.hxx>
+#if HAVE_FEATURE_SKIA
+#include <skia/x11/salvd.hxx>
+#endif
+#include <cairo-xlib.h>
+
+std::unique_ptr<SalVirtualDevice> X11SalInstance::CreateX11VirtualDevice(const SalGraphics& rGraphics,
+ tools::Long &nDX, tools::Long &nDY, DeviceFormat eFormat, const SystemGraphicsData *pData,
+ std::unique_ptr<X11SalGraphics> pNewGraphics)
+{
+ assert(pNewGraphics);
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled())
+ return std::unique_ptr<SalVirtualDevice>(new X11SkiaSalVirtualDevice(rGraphics, nDX, nDY, pData, std::move(pNewGraphics)));
+ else
+#endif
+ return std::unique_ptr<SalVirtualDevice>(new X11SalVirtualDevice(rGraphics, nDX, nDY, eFormat, pData, std::move(pNewGraphics)));
+}
+
+std::unique_ptr<SalVirtualDevice> X11SalInstance::CreateVirtualDevice(SalGraphics& rGraphics,
+ tools::Long &nDX, tools::Long &nDY, DeviceFormat eFormat, const SystemGraphicsData *pData)
+{
+ return CreateX11VirtualDevice(rGraphics, nDX, nDY, eFormat, pData, std::make_unique<X11SalGraphics>());
+}
+
+void X11SalGraphics::Init(X11SalVirtualDevice *pDevice, SalColormap* pColormap, bool bDeleteColormap)
+{
+ SalDisplay *pDisplay = pDevice->GetDisplay();
+ m_nXScreen = pDevice->GetXScreenNumber();
+
+ int nVisualDepth = pDisplay->GetColormap( m_nXScreen ).GetVisual().GetDepth();
+ int nDeviceDepth = pDevice->GetDepth();
+
+ if( pColormap )
+ {
+ maX11Common.m_pColormap = pColormap;
+ if( bDeleteColormap )
+ m_pDeleteColormap.reset(pColormap);
+ }
+ else if( nDeviceDepth == nVisualDepth )
+ maX11Common.m_pColormap = &pDisplay->GetColormap( m_nXScreen );
+ else if( nDeviceDepth == 1 )
+ {
+ m_pDeleteColormap.reset(new SalColormap());
+ maX11Common.m_pColormap = m_pDeleteColormap.get();
+ }
+
+ m_pVDev = pDevice;
+ m_pFrame = nullptr;
+
+ SetDrawable(pDevice->GetDrawable(), pDevice->GetSurface(), m_nXScreen);
+ mxImpl->Init();
+}
+
+X11SalVirtualDevice::X11SalVirtualDevice(const SalGraphics& rGraphics, tools::Long &nDX, tools::Long &nDY,
+ DeviceFormat /*eFormat*/, const SystemGraphicsData *pData,
+ std::unique_ptr<X11SalGraphics> pNewGraphics) :
+ pGraphics_(std::move(pNewGraphics)),
+ m_nXScreen(0),
+ bGraphics_(false)
+{
+ SalColormap* pColormap = nullptr;
+ bool bDeleteColormap = false;
+
+ sal_uInt16 nBitCount = rGraphics.GetBitCount();
+ pDisplay_ = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ nDepth_ = nBitCount;
+
+ if( pData && pData->hDrawable != None )
+ {
+ ::Window aRoot;
+ int x, y;
+ unsigned int w = 0, h = 0, bw, d;
+ Display* pDisp = pDisplay_->GetDisplay();
+ XGetGeometry( pDisp, pData->hDrawable,
+ &aRoot, &x, &y, &w, &h, &bw, &d );
+ int nScreen = 0;
+ while( nScreen < ScreenCount( pDisp ) )
+ {
+ if( RootWindow( pDisp, nScreen ) == aRoot )
+ break;
+ nScreen++;
+ }
+ nDX_ = static_cast<tools::Long>(w);
+ nDY_ = static_cast<tools::Long>(h);
+ nDX = nDX_;
+ nDY = nDY_;
+ m_nXScreen = SalX11Screen( nScreen );
+ hDrawable_ = pData->hDrawable;
+ bExternPixmap_ = true;
+ }
+ else
+ {
+ nDX_ = nDX;
+ nDY_ = nDY;
+ m_nXScreen = static_cast<const X11SalGraphics&>(rGraphics).GetScreenNumber();
+ hDrawable_ = limitXCreatePixmap( GetXDisplay(),
+ pDisplay_->GetDrawable( m_nXScreen ),
+ nDX_, nDY_,
+ GetDepth() );
+ bExternPixmap_ = false;
+ }
+
+ if( nBitCount != pDisplay_->GetVisual( m_nXScreen ).GetDepth() )
+ {
+ pColormap = new SalColormap( nBitCount );
+ bDeleteColormap = true;
+ }
+
+ pGraphics_->SetLayout( SalLayoutFlags::NONE ); // by default no! mirroring for VirtualDevices, can be enabled with EnableRTL()
+
+ // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget
+ cairo_surface_t* pPreExistingTarget = pData ? static_cast<cairo_surface_t*>(pData->pSurface) : nullptr;
+ if (pPreExistingTarget)
+ {
+ m_bOwnsSurface = false;
+ m_pSurface = pPreExistingTarget;
+ }
+ else
+ {
+ m_bOwnsSurface = true;
+ m_pSurface = cairo_xlib_surface_create(GetXDisplay(), hDrawable_,
+ pDisplay_->GetColormap(m_nXScreen).GetVisual().visual,
+ nDX_, nDY_);
+ }
+
+ pGraphics_->Init(this, pColormap, bDeleteColormap);
+}
+
+X11SalVirtualDevice::~X11SalVirtualDevice()
+{
+ pGraphics_.reset();
+
+ if (m_bOwnsSurface)
+ cairo_surface_destroy(m_pSurface);
+
+ if( GetDrawable() && !bExternPixmap_ )
+ XFreePixmap( GetXDisplay(), GetDrawable() );
+}
+
+SalGraphics* X11SalVirtualDevice::AcquireGraphics()
+{
+ if( bGraphics_ )
+ return nullptr;
+
+ if( pGraphics_ )
+ bGraphics_ = true;
+
+ return pGraphics_.get();
+}
+
+void X11SalVirtualDevice::ReleaseGraphics( SalGraphics* )
+{ bGraphics_ = false; }
+
+bool X11SalVirtualDevice::SetSize( tools::Long nDX, tools::Long nDY )
+{
+ if( bExternPixmap_ )
+ return false;
+
+ if( !nDX ) nDX = 1;
+ if( !nDY ) nDY = 1;
+
+ if (m_bOwnsSurface)
+ cairo_surface_destroy(m_pSurface);
+
+ Pixmap h = limitXCreatePixmap( GetXDisplay(),
+ pDisplay_->GetDrawable( m_nXScreen ),
+ nDX, nDY, nDepth_ );
+
+ if( !h )
+ {
+ if( !GetDrawable() )
+ {
+ hDrawable_ = limitXCreatePixmap( GetXDisplay(),
+ pDisplay_->GetDrawable( m_nXScreen ),
+ 1, 1, nDepth_ );
+ nDX_ = 1;
+ nDY_ = 1;
+ }
+
+ if (m_bOwnsSurface)
+ {
+ m_pSurface = cairo_xlib_surface_create(GetXDisplay(), hDrawable_,
+ pDisplay_->GetColormap(m_nXScreen).GetVisual().visual,
+ nDX_, nDY_);
+ }
+
+ return false;
+ }
+
+ if( GetDrawable() )
+ XFreePixmap( GetXDisplay(), GetDrawable() );
+ hDrawable_ = h;
+
+ nDX_ = nDX;
+ nDY_ = nDY;
+
+ if (m_bOwnsSurface)
+ {
+ m_pSurface = cairo_xlib_surface_create(GetXDisplay(), hDrawable_,
+ pDisplay_->GetColormap(m_nXScreen).GetVisual().visual,
+ nDX_, nDY_);
+ }
+
+ if( pGraphics_ )
+ pGraphics_->Init( this );
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/glyphs/freetype_glyphcache.cxx b/vcl/unx/generic/glyphs/freetype_glyphcache.cxx
new file mode 100644
index 0000000000..5745f94a22
--- /dev/null
+++ b/vcl/unx/generic/glyphs/freetype_glyphcache.cxx
@@ -0,0 +1,824 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <utility>
+#include <vcl/fontcharmap.hxx>
+
+#include <unx/freetype_glyphcache.hxx>
+
+#include <font/LogicalFontInstance.hxx>
+#include <fontattributes.hxx>
+
+#include <unotools/fontdefs.hxx>
+
+#include <tools/poly.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+
+#include <sal/log.hxx>
+#include <osl/module.h>
+
+#include <langboost.hxx>
+#include <font/PhysicalFontCollection.hxx>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+#include FT_MULTIPLE_MASTERS_H
+#include FT_OUTLINE_H
+#include FT_SIZES_H
+#include FT_SYNTHESIS_H
+#include FT_TRUETYPE_TABLES_H
+
+#include <vector>
+
+// TODO: move file mapping stuff to OSL
+#include <unistd.h>
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <unx/fontmanager.hxx>
+#include <impfontcharmap.hxx>
+
+static FT_Library aLibFT = nullptr;
+
+// TODO: remove when the priorities are selected by UI
+// if (AH==0) => disable autohinting
+// if (AA==0) => disable antialiasing
+// if (EB==0) => disable embedded bitmaps
+// if (AA prio <= AH prio) => antialias + autohint
+// if (AH<AA) => do not autohint when antialiasing
+// if (EB<AH) => do not autohint for monochrome
+static int nDefaultPrioEmbedded = 2;
+static int nDefaultPrioAntiAlias = 1;
+
+FreetypeFontFile::FreetypeFontFile( OString aNativeFileName )
+: maNativeFileName(std::move( aNativeFileName )),
+ mpFileMap( nullptr ),
+ mnFileSize( 0 ),
+ mnRefCount( 0 ),
+ mnLangBoost( 0 )
+{
+ // boost font preference if UI language is mentioned in filename
+ int nPos = maNativeFileName.lastIndexOf( '_' );
+ if( nPos == -1 || maNativeFileName[nPos+1] == '.' )
+ mnLangBoost += 0x1000; // no langinfo => good
+ else
+ {
+ static const char* pLangBoost = nullptr;
+ static bool bOnce = true;
+ if( bOnce )
+ {
+ bOnce = false;
+ pLangBoost = vcl::getLangBoost();
+ }
+
+ if( pLangBoost && !strncasecmp( pLangBoost, &maNativeFileName.getStr()[nPos+1], 3 ) )
+ mnLangBoost += 0x2000; // matching langinfo => better
+ }
+}
+
+bool FreetypeFontFile::Map()
+{
+ if (mnRefCount++ == 0)
+ {
+ const char* pFileName = maNativeFileName.getStr();
+ int nFile;
+ int nFD;
+ int n;
+ if( sscanf( pFileName, "/:FD:/%d%n", &nFD, &n ) == 1 && pFileName[n] == '\0' )
+ {
+ lseek( nFD, 0, SEEK_SET );
+ nFile = dup( nFD );
+ }
+ else
+ nFile = open( pFileName, O_RDONLY );
+ if( nFile < 0 )
+ {
+ SAL_WARN("vcl.unx.freetype", "open('" << maNativeFileName << "') failed: " << strerror(errno));
+ return false;
+ }
+
+ struct stat aStat;
+ int nRet = fstat( nFile, &aStat );
+ if (nRet < 0)
+ {
+ SAL_WARN("vcl.unx.freetype", "fstat on '" << maNativeFileName << "' failed: " << strerror(errno));
+ close (nFile);
+ return false;
+ }
+ mnFileSize = aStat.st_size;
+ mpFileMap = static_cast<unsigned char*>(
+ mmap( nullptr, mnFileSize, PROT_READ, MAP_SHARED, nFile, 0 ));
+ if( mpFileMap == MAP_FAILED )
+ {
+ SAL_WARN("vcl.unx.freetype", "mmap of '" << maNativeFileName << "' failed: " << strerror(errno));
+ mpFileMap = nullptr;
+ }
+ else
+ SAL_INFO("vcl.unx.freetype", "mmap'ed '" << maNativeFileName << "' successfully");
+ close( nFile );
+ }
+
+ return (mpFileMap != nullptr);
+}
+
+void FreetypeFontFile::Unmap()
+{
+ if (--mnRefCount != 0)
+ return;
+ assert(mnRefCount >= 0 && "how did this go negative\n");
+ if (mpFileMap)
+ {
+ munmap(mpFileMap, mnFileSize);
+ mpFileMap = nullptr;
+ }
+}
+
+FreetypeFontInfo::FreetypeFontInfo( FontAttributes aDevFontAttributes,
+ FreetypeFontFile* const pFontFile, int nFaceNum, int nFaceVariation, sal_IntPtr nFontId)
+:
+ maFaceFT( nullptr ),
+ mpFontFile(pFontFile),
+ mnFaceNum( nFaceNum ),
+ mnFaceVariation( nFaceVariation ),
+ mnRefCount( 0 ),
+ mnFontId( nFontId ),
+ maDevFontAttributes(std::move( aDevFontAttributes ))
+{
+ // prefer font with low ID
+ maDevFontAttributes.IncreaseQualityBy( 10000 - nFontId );
+ // prefer font with matching file names
+ maDevFontAttributes.IncreaseQualityBy( mpFontFile->GetLangBoost() );
+}
+
+FreetypeFontInfo::~FreetypeFontInfo()
+{
+}
+
+namespace
+{
+ void dlFT_Done_MM_Var(FT_Library library, FT_MM_Var *amaster)
+ {
+#if !HAVE_DLAPI
+ FT_Done_MM_Var(library, amaster);
+#else
+ static auto func = reinterpret_cast<void(*)(FT_Library, FT_MM_Var*)>(
+ osl_getAsciiFunctionSymbol(nullptr, "FT_Done_MM_Var"));
+ if (func)
+ func(library, amaster);
+ else
+ free(amaster);
+#endif
+ }
+}
+
+FT_FaceRec_* FreetypeFontInfo::GetFaceFT()
+{
+ if (!maFaceFT && mpFontFile->Map())
+ {
+ FT_Error rc = FT_New_Memory_Face( aLibFT,
+ mpFontFile->GetBuffer(),
+ mpFontFile->GetFileSize(), mnFaceNum, &maFaceFT );
+ if( (rc != FT_Err_Ok) || (maFaceFT->num_glyphs <= 0) )
+ maFaceFT = nullptr;
+
+ if (maFaceFT && mnFaceVariation)
+ {
+ FT_MM_Var *pFtMMVar;
+ if (FT_Get_MM_Var(maFaceFT, &pFtMMVar) == 0)
+ {
+ if (o3tl::make_unsigned(mnFaceVariation) <= pFtMMVar->num_namedstyles)
+ {
+ FT_Var_Named_Style *instance = &pFtMMVar->namedstyle[mnFaceVariation - 1];
+ FT_Set_Var_Design_Coordinates(maFaceFT, pFtMMVar->num_axis, instance->coords);
+ }
+ dlFT_Done_MM_Var(aLibFT, pFtMMVar);
+ }
+ }
+ }
+
+ ++mnRefCount;
+ return maFaceFT;
+}
+
+void FreetypeFontInfo::ReleaseFaceFT()
+{
+ if (--mnRefCount == 0)
+ {
+ if (maFaceFT)
+ {
+ FT_Done_Face(maFaceFT);
+ maFaceFT = nullptr;
+ }
+ mpFontFile->Unmap();
+ }
+ assert(mnRefCount >= 0 && "how did this go negative\n");
+}
+
+void FreetypeFontInfo::AnnounceFont( vcl::font::PhysicalFontCollection* pFontCollection )
+{
+ rtl::Reference<FreetypeFontFace> pFD(new FreetypeFontFace( this, maDevFontAttributes ));
+ pFontCollection->Add( pFD.get() );
+}
+
+void FreetypeManager::InitFreetype()
+{
+ /*FT_Error rcFT =*/ FT_Init_FreeType( &aLibFT );
+
+ // TODO: remove when the priorities are selected by UI
+ char* pEnv;
+ pEnv = ::getenv( "SAL_EMBEDDED_BITMAP_PRIORITY" );
+ if( pEnv )
+ nDefaultPrioEmbedded = pEnv[0] - '0';
+ pEnv = ::getenv( "SAL_ANTIALIASED_TEXT_PRIORITY" );
+ if( pEnv )
+ nDefaultPrioAntiAlias = pEnv[0] - '0';
+}
+
+FT_Face FreetypeFont::GetFtFace() const
+{
+ FT_Activate_Size( maSizeFT );
+
+ return maFaceFT;
+}
+
+void FreetypeManager::AddFontFile(const OString& rNormalizedName,
+ int nFaceNum, int nVariantNum, sal_IntPtr nFontId, const FontAttributes& rDevFontAttr)
+{
+ if( rNormalizedName.isEmpty() )
+ return;
+
+ if( m_aFontInfoList.find( nFontId ) != m_aFontInfoList.end() )
+ return;
+
+ FreetypeFontInfo* pFontInfo = new FreetypeFontInfo( rDevFontAttr,
+ FindFontFile(rNormalizedName), nFaceNum, nVariantNum, nFontId);
+ m_aFontInfoList[ nFontId ].reset(pFontInfo);
+}
+
+void FreetypeManager::AnnounceFonts( vcl::font::PhysicalFontCollection* pToAdd ) const
+{
+ for (auto const& font : m_aFontInfoList)
+ {
+ FreetypeFontInfo* pFreetypeFontInfo = font.second.get();
+ pFreetypeFontInfo->AnnounceFont( pToAdd );
+ }
+}
+
+FreetypeFont* FreetypeManager::CreateFont(FreetypeFontInstance* pFontInstance)
+{
+ // find a FontInfo matching to the font id
+ if (!pFontInstance)
+ return nullptr;
+
+ const vcl::font::PhysicalFontFace* pFontFace = pFontInstance->GetFontFace();
+ if (!pFontFace)
+ return nullptr;
+
+ sal_IntPtr nFontId = pFontFace->GetFontId();
+ FontInfoList::iterator it = m_aFontInfoList.find(nFontId);
+
+ if (it == m_aFontInfoList.end())
+ return nullptr;
+
+ return new FreetypeFont(*pFontInstance, it->second);
+}
+
+FreetypeFontFace::FreetypeFontFace( FreetypeFontInfo* pFI, const FontAttributes& rDFA )
+: vcl::font::PhysicalFontFace( rDFA ),
+ mpFreetypeFontInfo( pFI )
+{
+ assert(mpFreetypeFontInfo);
+}
+
+rtl::Reference<LogicalFontInstance> FreetypeFontFace::CreateFontInstance(const vcl::font::FontSelectPattern& rFSD) const
+{
+ return new FreetypeFontInstance(*this, rFSD);
+}
+
+namespace
+{
+hb_blob_t* CreateHbBlob(FreetypeFontFile* pFontFile)
+{
+ auto pFileName = pFontFile->GetFileName().getStr();
+ int nFD;
+ int n;
+ if (sscanf(pFileName, "/:FD:/%d%n", &nFD, &n) == 1 && pFileName[n] == '\0')
+ {
+ if (pFontFile->Map())
+ return hb_blob_create(
+ reinterpret_cast<const char*>(pFontFile->GetBuffer()), pFontFile->GetFileSize(),
+ HB_MEMORY_MODE_READONLY, pFontFile,
+ [](void* data) { static_cast<FreetypeFontFile*>(data)->Unmap(); });
+ pFontFile->Unmap();
+ return hb_blob_get_empty();
+ }
+ return hb_blob_create_from_file(pFileName);
+}
+}
+
+hb_face_t* FreetypeFontFace::GetHbFace() const
+{
+ if (!mpHbFace)
+ {
+ auto pFontFile = mpFreetypeFontInfo->GetFontFile();
+ auto nIndex = mpFreetypeFontInfo->GetFontFaceIndex();
+ auto pHbBlob = CreateHbBlob(pFontFile);
+ mpHbFace = hb_face_create(pHbBlob, nIndex);
+ hb_blob_destroy(pHbBlob);
+ }
+ return mpHbFace;
+}
+
+hb_blob_t* FreetypeFontFace::GetHbTable(hb_tag_t nTag) const
+{
+ return hb_face_reference_table(mpHbFace, nTag);
+}
+
+const std::vector<hb_variation_t>& FreetypeFontFace::GetVariations(const LogicalFontInstance&) const
+{
+ if (!mxVariations)
+ {
+ mxVariations.emplace();
+ FT_Face aFaceFT = mpFreetypeFontInfo->GetFaceFT();
+ sal_uInt32 nFaceVariation = mpFreetypeFontInfo->GetFontFaceVariation();
+ if (!(aFaceFT && nFaceVariation))
+ return *mxVariations;
+
+ FT_MM_Var* pFtMMVar;
+ if (FT_Get_MM_Var(aFaceFT, &pFtMMVar) != 0)
+ return *mxVariations;
+
+ if (nFaceVariation <= pFtMMVar->num_namedstyles)
+ {
+ FT_Var_Named_Style* instance = &pFtMMVar->namedstyle[nFaceVariation - 1];
+ mxVariations->resize(pFtMMVar->num_axis);
+ for (FT_UInt i = 0; i < pFtMMVar->num_axis; ++i)
+ {
+ (*mxVariations)[i].tag = pFtMMVar->axis[i].tag;
+ (*mxVariations)[i].value = instance->coords[i] / 65536.0;
+ }
+ }
+ dlFT_Done_MM_Var(aLibFT, pFtMMVar);
+ }
+
+ return *mxVariations;
+}
+
+// FreetypeFont
+
+FreetypeFont::FreetypeFont(FreetypeFontInstance& rFontInstance, std::shared_ptr<FreetypeFontInfo> xFI)
+: mrFontInstance(rFontInstance),
+ mnCos( 0x10000),
+ mnSin( 0 ),
+ mnPrioAntiAlias(nDefaultPrioAntiAlias),
+ mxFontInfo(std::move(xFI)),
+ maFaceFT( nullptr ),
+ maSizeFT( nullptr ),
+ mbFaceOk( false )
+{
+ maFaceFT = mxFontInfo->GetFaceFT();
+
+ const vcl::font::FontSelectPattern& rFSD = rFontInstance.GetFontSelectPattern();
+
+ if( rFSD.mnOrientation )
+ {
+ const double dRad = toRadians(rFSD.mnOrientation);
+ mnCos = static_cast<tools::Long>( 0x10000 * cos( dRad ) + 0.5 );
+ mnSin = static_cast<tools::Long>( 0x10000 * sin( dRad ) + 0.5 );
+ }
+
+ // set the pixel size of the font instance
+ mnWidth = rFSD.mnWidth;
+ if( !mnWidth )
+ mnWidth = rFSD.mnHeight;
+ if (rFSD.mnHeight == 0)
+ {
+ SAL_WARN("vcl", "FreetypeFont divide by zero");
+ mfStretch = 1.0;
+ return;
+ }
+ mfStretch = static_cast<double>(mnWidth) / rFSD.mnHeight;
+ // sanity checks (e.g. #i66394#, #i66244#, #i66537#)
+ if (mnWidth < 0)
+ {
+ SAL_WARN("vcl", "FreetypeFont negative font width of: " << mnWidth);
+ return;
+ }
+
+ SAL_WARN_IF(mfStretch > +64.0 || mfStretch < -64.0, "vcl", "FreetypeFont excessive stretch of: " << mfStretch);
+
+ if( !maFaceFT )
+ return;
+
+ FT_New_Size( maFaceFT, &maSizeFT );
+ FT_Activate_Size( maSizeFT );
+ /* This might fail for color bitmap fonts, but that is fine since we will
+ * not need any glyph data from FreeType in this case */
+ /*FT_Error rc = */ FT_Set_Pixel_Sizes( maFaceFT, mnWidth, rFSD.mnHeight );
+
+ mbFaceOk = true;
+}
+
+namespace
+{
+ std::unique_ptr<FontConfigFontOptions> GetFCFontOptions(const FontAttributes& rFontAttributes, int nSize)
+ {
+ return psp::PrintFontManager::getFontOptions(rFontAttributes, nSize);
+ }
+}
+
+const FontConfigFontOptions* FreetypeFont::GetFontOptions() const
+{
+ if (!mxFontOptions)
+ {
+ mxFontOptions = GetFCFontOptions(mxFontInfo->GetFontAttributes(), mrFontInstance.GetFontSelectPattern().mnHeight);
+ mxFontOptions->SyncPattern(GetFontFileName(), GetFontFaceIndex(), GetFontFaceVariation(), mrFontInstance.NeedsArtificialBold());
+ }
+ return mxFontOptions.get();
+}
+
+const OString& FreetypeFont::GetFontFileName() const
+{
+ return mxFontInfo->GetFontFileName();
+}
+
+int FreetypeFont::GetFontFaceIndex() const
+{
+ return mxFontInfo->GetFontFaceIndex();
+}
+
+int FreetypeFont::GetFontFaceVariation() const
+{
+ return mxFontInfo->GetFontFaceVariation();
+}
+
+FreetypeFont::~FreetypeFont()
+{
+ if( maSizeFT )
+ FT_Done_Size( maSizeFT );
+
+ mxFontInfo->ReleaseFaceFT();
+}
+
+void FreetypeFont::GetFontMetric(FontMetricDataRef const & rxTo) const
+{
+ rxTo->FontAttributes::operator =(mxFontInfo->GetFontAttributes());
+
+ rxTo->SetOrientation(mrFontInstance.GetFontSelectPattern().mnOrientation);
+
+ FT_Activate_Size( maSizeFT );
+
+ rxTo->ImplCalcLineSpacing(&mrFontInstance);
+ rxTo->ImplInitBaselines(&mrFontInstance);
+
+ rxTo->SetSlant( 0 );
+ rxTo->SetWidth( mnWidth );
+
+ const TT_OS2* pOS2 = static_cast<const TT_OS2*>(FT_Get_Sfnt_Table( maFaceFT, ft_sfnt_os2 ));
+ if( pOS2 && (pOS2->version != 0xFFFF) )
+ {
+ // map the panose info from the OS2 table to their VCL counterparts
+ switch( pOS2->panose[0] )
+ {
+ case 1: rxTo->SetFamilyType( FAMILY_ROMAN ); break;
+ case 2: rxTo->SetFamilyType( FAMILY_SWISS ); break;
+ case 3: rxTo->SetFamilyType( FAMILY_MODERN ); break;
+ case 4: rxTo->SetFamilyType( FAMILY_SCRIPT ); break;
+ case 5: rxTo->SetFamilyType( FAMILY_DECORATIVE ); break;
+ // TODO: is it reasonable to override the attribute with DONTKNOW?
+ case 0: // fall through
+ default: rxTo->SetFamilyType( FAMILY_DONTKNOW ); break;
+ }
+
+ switch( pOS2->panose[3] )
+ {
+ case 2: // fall through
+ case 3: // fall through
+ case 4: // fall through
+ case 5: // fall through
+ case 6: // fall through
+ case 7: // fall through
+ case 8: rxTo->SetPitch( PITCH_VARIABLE ); break;
+ case 9: rxTo->SetPitch( PITCH_FIXED ); break;
+ // TODO: is it reasonable to override the attribute with DONTKNOW?
+ case 0: // fall through
+ case 1: // fall through
+ default: rxTo->SetPitch( PITCH_DONTKNOW ); break;
+ }
+ }
+
+ // initialize kashida width
+ rxTo->SetMinKashida(mrFontInstance.GetKashidaWidth());
+}
+
+void FreetypeFont::ApplyGlyphTransform(bool bVertical, FT_Glyph pGlyphFT ) const
+{
+ // shortcut most common case
+ if (!mrFontInstance.GetFontSelectPattern().mnOrientation && !bVertical)
+ return;
+
+ const FT_Size_Metrics& rMetrics = maFaceFT->size->metrics;
+ FT_Vector aVector;
+ FT_Matrix aMatrix;
+
+ bool bStretched = false;
+
+ if (!bVertical)
+ {
+ // straight
+ aVector.x = 0;
+ aVector.y = 0;
+ aMatrix.xx = +mnCos;
+ aMatrix.yy = +mnCos;
+ aMatrix.xy = -mnSin;
+ aMatrix.yx = +mnSin;
+ }
+ else
+ {
+ // left
+ bStretched = (mfStretch != 1.0);
+ aVector.x = static_cast<FT_Pos>(+rMetrics.descender * mfStretch);
+ aVector.y = -rMetrics.ascender;
+ aMatrix.xx = static_cast<FT_Pos>(-mnSin / mfStretch);
+ aMatrix.yy = static_cast<FT_Pos>(-mnSin * mfStretch);
+ aMatrix.xy = static_cast<FT_Pos>(-mnCos * mfStretch);
+ aMatrix.yx = static_cast<FT_Pos>(+mnCos / mfStretch);
+ }
+
+ if( pGlyphFT->format != FT_GLYPH_FORMAT_BITMAP )
+ {
+ FT_Glyph_Transform( pGlyphFT, nullptr, &aVector );
+
+ // orthogonal transforms are better handled by bitmap operations
+ if( bStretched )
+ {
+ // apply non-orthogonal or stretch transformations
+ FT_Glyph_Transform( pGlyphFT, &aMatrix, nullptr );
+ }
+ }
+ else
+ {
+ // FT<=2005 ignores transforms for bitmaps, so do it manually
+ FT_BitmapGlyph pBmpGlyphFT = reinterpret_cast<FT_BitmapGlyph>(pGlyphFT);
+ pBmpGlyphFT->left += (aVector.x + 32) >> 6;
+ pBmpGlyphFT->top += (aVector.y + 32) >> 6;
+ }
+}
+
+bool FreetypeFont::GetAntialiasAdvice() const
+{
+ // TODO: also use GASP info
+ return !mrFontInstance.GetFontSelectPattern().mbNonAntialiased && (mnPrioAntiAlias > 0);
+}
+
+// outline stuff
+
+namespace {
+
+class PolyArgs
+{
+public:
+ PolyArgs( tools::PolyPolygon& rPolyPoly, sal_uInt16 nMaxPoints );
+
+ void AddPoint( tools::Long nX, tools::Long nY, PolyFlags);
+ void ClosePolygon();
+
+ tools::Long GetPosX() const { return maPosition.x;}
+ tools::Long GetPosY() const { return maPosition.y;}
+
+private:
+ tools::PolyPolygon& mrPolyPoly;
+
+ std::unique_ptr<Point[]>
+ mpPointAry;
+ std::unique_ptr<PolyFlags[]>
+ mpFlagAry;
+
+ FT_Vector maPosition;
+ sal_uInt16 mnMaxPoints;
+ sal_uInt16 mnPoints;
+ sal_uInt16 mnPoly;
+ bool bHasOffline;
+
+ PolyArgs(const PolyArgs&) = delete;
+ PolyArgs& operator=(const PolyArgs&) = delete;
+};
+
+}
+
+PolyArgs::PolyArgs( tools::PolyPolygon& rPolyPoly, sal_uInt16 nMaxPoints )
+: mrPolyPoly(rPolyPoly),
+ mnMaxPoints(nMaxPoints),
+ mnPoints(0),
+ mnPoly(0),
+ bHasOffline(false)
+{
+ mpPointAry.reset( new Point[ mnMaxPoints ] );
+ mpFlagAry.reset( new PolyFlags [ mnMaxPoints ] );
+ maPosition.x = maPosition.y = 0;
+}
+
+void PolyArgs::AddPoint( tools::Long nX, tools::Long nY, PolyFlags aFlag )
+{
+ SAL_WARN_IF( (mnPoints >= mnMaxPoints), "vcl", "FTGlyphOutline: AddPoint overflow!" );
+ if( mnPoints >= mnMaxPoints )
+ return;
+
+ maPosition.x = nX;
+ maPosition.y = nY;
+ mpPointAry[ mnPoints ] = Point( nX, nY );
+ mpFlagAry[ mnPoints++ ]= aFlag;
+ bHasOffline |= (aFlag != PolyFlags::Normal);
+}
+
+void PolyArgs::ClosePolygon()
+{
+ if( !mnPoly++ )
+ return;
+
+ // freetype seems to always close the polygon with an ON_CURVE point
+ // PolyPoly wants to close the polygon itself => remove last point
+ SAL_WARN_IF( (mnPoints < 2), "vcl", "FTGlyphOutline: PolyFinishNum failed!" );
+ --mnPoints;
+ SAL_WARN_IF( (mpPointAry[0]!=mpPointAry[mnPoints]), "vcl", "FTGlyphOutline: PolyFinishEq failed!" );
+ SAL_WARN_IF( (mpFlagAry[0]!=PolyFlags::Normal), "vcl", "FTGlyphOutline: PolyFinishFE failed!" );
+ SAL_WARN_IF( (mpFlagAry[mnPoints]!=PolyFlags::Normal), "vcl", "FTGlyphOutline: PolyFinishFS failed!" );
+
+ tools::Polygon aPoly( mnPoints, mpPointAry.get(), (bHasOffline ? mpFlagAry.get() : nullptr) );
+
+ // #i35928#
+ // This may be an invalid polygon, e.g. the last point is a control point.
+ // So close the polygon (and add the first point again) if the last point
+ // is a control point or different from first.
+ // #i48298#
+ // Now really duplicating the first point, to close or correct the
+ // polygon. Also no longer duplicating the flags, but enforcing
+ // PolyFlags::Normal for the newly added last point.
+ const sal_uInt16 nPolySize(aPoly.GetSize());
+ if(nPolySize)
+ {
+ if((aPoly.HasFlags() && PolyFlags::Control == aPoly.GetFlags(nPolySize - 1))
+ || (aPoly.GetPoint(nPolySize - 1) != aPoly.GetPoint(0)))
+ {
+ aPoly.SetSize(nPolySize + 1);
+ aPoly.SetPoint(aPoly.GetPoint(0), nPolySize);
+
+ if(aPoly.HasFlags())
+ {
+ aPoly.SetFlags(nPolySize, PolyFlags::Normal);
+ }
+ }
+ }
+
+ mrPolyPoly.Insert( aPoly );
+ mnPoints = 0;
+ bHasOffline = false;
+}
+
+extern "C" {
+
+// TODO: wait till all compilers accept that calling conventions
+// for functions are the same independent of implementation constness,
+// then uncomment the const-tokens in the function interfaces below
+static int FT_move_to( const FT_Vector* p0, void* vpPolyArgs )
+{
+ PolyArgs& rA = *static_cast<PolyArgs*>(vpPolyArgs);
+
+ // move_to implies a new polygon => finish old polygon first
+ rA.ClosePolygon();
+
+ rA.AddPoint( p0->x, p0->y, PolyFlags::Normal );
+ return 0;
+}
+
+static int FT_line_to( const FT_Vector* p1, void* vpPolyArgs )
+{
+ PolyArgs& rA = *static_cast<PolyArgs*>(vpPolyArgs);
+ rA.AddPoint( p1->x, p1->y, PolyFlags::Normal );
+ return 0;
+}
+
+static int FT_conic_to( const FT_Vector* p1, const FT_Vector* p2, void* vpPolyArgs )
+{
+ PolyArgs& rA = *static_cast<PolyArgs*>(vpPolyArgs);
+
+ // VCL's Polygon only knows cubic beziers
+ const tools::Long nX1 = (2 * rA.GetPosX() + 4 * p1->x + 3) / 6;
+ const tools::Long nY1 = (2 * rA.GetPosY() + 4 * p1->y + 3) / 6;
+ rA.AddPoint( nX1, nY1, PolyFlags::Control );
+
+ const tools::Long nX2 = (2 * p2->x + 4 * p1->x + 3) / 6;
+ const tools::Long nY2 = (2 * p2->y + 4 * p1->y + 3) / 6;
+ rA.AddPoint( nX2, nY2, PolyFlags::Control );
+
+ rA.AddPoint( p2->x, p2->y, PolyFlags::Normal );
+ return 0;
+}
+
+static int FT_cubic_to( const FT_Vector* p1, const FT_Vector* p2, const FT_Vector* p3, void* vpPolyArgs )
+{
+ PolyArgs& rA = *static_cast<PolyArgs*>(vpPolyArgs);
+ rA.AddPoint( p1->x, p1->y, PolyFlags::Control );
+ rA.AddPoint( p2->x, p2->y, PolyFlags::Control );
+ rA.AddPoint( p3->x, p3->y, PolyFlags::Normal );
+ return 0;
+}
+
+} // extern "C"
+
+bool FreetypeFont::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rB2DPolyPoly, bool bIsVertical) const
+{
+ if( maSizeFT )
+ FT_Activate_Size( maSizeFT );
+
+ rB2DPolyPoly.clear();
+
+ FT_Int nLoadFlags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_TRANSFORM;
+
+#ifdef FT_LOAD_TARGET_LIGHT
+ // enable "light hinting" if available
+ nLoadFlags |= FT_LOAD_TARGET_LIGHT;
+#endif
+
+ FT_Error rc = FT_Load_Glyph(maFaceFT, nId, nLoadFlags);
+ if( rc != FT_Err_Ok )
+ return false;
+
+ if (mrFontInstance.NeedsArtificialBold())
+ FT_GlyphSlot_Embolden(maFaceFT->glyph);
+
+ FT_Glyph pGlyphFT;
+ rc = FT_Get_Glyph( maFaceFT->glyph, &pGlyphFT );
+ if( rc != FT_Err_Ok )
+ return false;
+
+ if( pGlyphFT->format != FT_GLYPH_FORMAT_OUTLINE )
+ {
+ FT_Done_Glyph( pGlyphFT );
+ return false;
+ }
+
+ if (mrFontInstance.NeedsArtificialItalic())
+ {
+ FT_Matrix aMatrix;
+ aMatrix.xx = aMatrix.yy = ARTIFICIAL_ITALIC_MATRIX_XX;
+ aMatrix.xy = ARTIFICIAL_ITALIC_MATRIX_XY;
+ aMatrix.yx = 0;
+ FT_Glyph_Transform( pGlyphFT, &aMatrix, nullptr );
+ }
+
+ FT_Outline& rOutline = reinterpret_cast<FT_OutlineGlyphRec*>(pGlyphFT)->outline;
+ if( !rOutline.n_points ) // blank glyphs are ok
+ {
+ FT_Done_Glyph( pGlyphFT );
+ return true;
+ }
+
+ tools::Long nMaxPoints = 1 + rOutline.n_points * 3;
+ tools::PolyPolygon aToolPolyPolygon;
+ PolyArgs aPolyArg( aToolPolyPolygon, nMaxPoints );
+
+ ApplyGlyphTransform(bIsVertical, pGlyphFT);
+
+ FT_Outline_Funcs aFuncs;
+ aFuncs.move_to = &FT_move_to;
+ aFuncs.line_to = &FT_line_to;
+ aFuncs.conic_to = &FT_conic_to;
+ aFuncs.cubic_to = &FT_cubic_to;
+ aFuncs.shift = 0;
+ aFuncs.delta = 0;
+ FT_Outline_Decompose( &rOutline, &aFuncs, static_cast<void*>(&aPolyArg) );
+ aPolyArg.ClosePolygon(); // close last polygon
+ FT_Done_Glyph( pGlyphFT );
+
+ // convert to basegfx polypolygon
+ // TODO: get rid of the intermediate tools polypolygon
+ rB2DPolyPoly = aToolPolyPolygon.getB2DPolyPolygon();
+ rB2DPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix( +0x1p-6, -0x1p-6 ));
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/glyphs/glyphcache.cxx b/vcl/unx/generic/glyphs/glyphcache.cxx
new file mode 100644
index 0000000000..ac3c5e15ab
--- /dev/null
+++ b/vcl/unx/generic/glyphs/glyphcache.cxx
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <stdlib.h>
+#include <unx/freetype_glyphcache.hxx>
+#include <unx/gendata.hxx>
+
+#include <font/LogicalFontInstance.hxx>
+
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+
+FreetypeManager::FreetypeManager()
+{
+ InitFreetype();
+}
+
+FreetypeManager::~FreetypeManager()
+{
+ ClearFontCache();
+}
+
+void FreetypeManager::ClearFontCache()
+{
+ m_aFontInfoList.clear();
+}
+
+FreetypeManager& FreetypeManager::get()
+{
+ GenericUnixSalData* const pSalData(GetGenericUnixSalData());
+ assert(pSalData);
+ return *pSalData->GetFreetypeManager();
+}
+
+FreetypeFontFile* FreetypeManager::FindFontFile(const OString& rNativeFileName)
+{
+ // font file already known? (e.g. for ttc, synthetic, aliased fonts)
+ const char* pFileName = rNativeFileName.getStr();
+ FontFileList::const_iterator it = m_aFontFileList.find(pFileName);
+ if (it != m_aFontFileList.end())
+ return it->second.get();
+
+ // no => create new one
+ FreetypeFontFile* pFontFile = new FreetypeFontFile(rNativeFileName);
+ pFileName = pFontFile->maNativeFileName.getStr();
+ m_aFontFileList[pFileName].reset(pFontFile);
+ return pFontFile;
+}
+
+FreetypeFontInstance::FreetypeFontInstance(const vcl::font::PhysicalFontFace& rPFF, const vcl::font::FontSelectPattern& rFSP)
+ : LogicalFontInstance(rPFF, rFSP)
+ , mxFreetypeFont(FreetypeManager::get().CreateFont(this))
+{
+}
+
+FreetypeFontInstance::~FreetypeFontInstance()
+{
+}
+
+bool FreetypeFontInstance::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rPoly, bool bVertical) const
+{
+ assert(mxFreetypeFont);
+ if (!mxFreetypeFont)
+ return false;
+ return mxFreetypeFont->GetGlyphOutline(nId, rPoly, bVertical);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/genprnpsp.cxx b/vcl/unx/generic/print/genprnpsp.cxx
new file mode 100644
index 0000000000..33990decad
--- /dev/null
+++ b/vcl/unx/generic/print/genprnpsp.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 .
+ */
+
+/**
+ this file implements the sal printer interface (SalPrinter, SalInfoPrinter
+ and some printer relevant methods of SalInstance and SalGraphicsData)
+
+ as underlying library the printer features of psprint are used.
+
+ The query methods of a SalInfoPrinter are implemented by querying psprint
+
+ The job methods of a SalPrinter are implemented by calling psprint
+ printer job functions.
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+// For spawning PDF and FAX generation
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <comphelper/fileurl.hxx>
+#include <o3tl/safeint.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <osl/file.hxx>
+
+#include <utility>
+#include <vcl/gdimtf.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/printer/Options.hxx>
+#include <vcl/print.hxx>
+#include <vcl/QueueInfo.hxx>
+#include <vcl/pdfwriter.hxx>
+#include <printerinfomanager.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/weld.hxx>
+#include <strings.hrc>
+#include <unx/genprn.h>
+#include <unx/geninst.h>
+#include <unx/genpspgraphics.h>
+
+#include <jobset.h>
+#include <print.h>
+#include "prtsetup.hxx"
+#include <salptype.hxx>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+
+using namespace psp;
+using namespace com::sun::star;
+
+static bool getPdfDir( const PrinterInfo& rInfo, OUString &rDir )
+{
+ sal_Int32 nIndex = 0;
+ while( nIndex != -1 )
+ {
+ OUString aToken( rInfo.m_aFeatures.getToken( 0, ',', nIndex ) );
+ if( aToken.startsWith( "pdf=" ) )
+ {
+ sal_Int32 nPos = 0;
+ rDir = aToken.getToken( 1, '=', nPos );
+ if( rDir.isEmpty() && getenv( "HOME" ) )
+ rDir = OUString( getenv( "HOME" ), strlen( getenv( "HOME" ) ), osl_getThreadTextEncoding() );
+ return true;
+ }
+ }
+ return false;
+}
+
+namespace
+{
+ class QueryString : public weld::GenericDialogController
+ {
+ private:
+ OUString& m_rReturnValue;
+
+ std::unique_ptr<weld::Button> m_xOKButton;
+ std::unique_ptr<weld::Label> m_xFixedText;
+ std::unique_ptr<weld::Entry> m_xEdit;
+
+ DECL_LINK( ClickBtnHdl, weld::Button&, void );
+
+ public:
+ // parent window, Query text, initial value
+ QueryString(weld::Window*, OUString const &, OUString &);
+ };
+
+ /*
+ * QueryString
+ */
+ QueryString::QueryString(weld::Window* pParent, OUString const & rQuery, OUString& rRet)
+ : GenericDialogController(pParent, "vcl/ui/querydialog.ui", "QueryDialog")
+ , m_rReturnValue( rRet )
+ , m_xOKButton(m_xBuilder->weld_button("ok"))
+ , m_xFixedText(m_xBuilder->weld_label("label"))
+ , m_xEdit(m_xBuilder->weld_entry("entry"))
+ {
+ m_xOKButton->connect_clicked(LINK(this, QueryString, ClickBtnHdl));
+ m_xFixedText->set_label(rQuery);
+ m_xEdit->set_text(m_rReturnValue);
+ m_xDialog->set_title(rQuery);
+ }
+
+ IMPL_LINK(QueryString, ClickBtnHdl, weld::Button&, rButton, void)
+ {
+ if (&rButton == m_xOKButton.get())
+ {
+ m_rReturnValue = m_xEdit->get_text();
+ m_xDialog->response(RET_OK);
+ }
+ else
+ m_xDialog->response(RET_CANCEL);
+ }
+
+ int QueryFaxNumber(OUString& rNumber)
+ {
+ QueryString aQuery(Application::GetDefDialogParent(), VclResId(SV_PRINT_QUERYFAXNUMBER_TXT), rNumber);
+ return aQuery.run();
+ }
+}
+
+static int PtTo10Mu( int nPoints ) { return static_cast<int>((static_cast<double>(nPoints)*35.27777778)+0.5); }
+
+static int TenMuToPt( int nUnits ) { return static_cast<int>((static_cast<double>(nUnits)/35.27777778)+0.5); }
+
+static void copyJobDataToJobSetup( ImplJobSetup* pJobSetup, JobData& rData )
+{
+ pJobSetup->SetOrientation( rData.m_eOrientation == orientation::Landscape ?
+ Orientation::Landscape : Orientation::Portrait );
+
+ // copy page size
+ OUString aPaper;
+ int width, height;
+
+ rData.m_aContext.getPageSize( aPaper, width, height );
+ pJobSetup->SetPaperFormat( PaperInfo::fromPSName(
+ OUStringToOString( aPaper, RTL_TEXTENCODING_ISO_8859_1 )));
+
+ pJobSetup->SetPaperWidth( 0 );
+ pJobSetup->SetPaperHeight( 0 );
+ if( pJobSetup->GetPaperFormat() == PAPER_USER )
+ {
+ // transform to 100dth mm
+ width = PtTo10Mu( width );
+ height = PtTo10Mu( height );
+
+ if( rData.m_eOrientation == psp::orientation::Portrait )
+ {
+ pJobSetup->SetPaperWidth( width );
+ pJobSetup->SetPaperHeight( height );
+ }
+ else
+ {
+ pJobSetup->SetPaperWidth( height );
+ pJobSetup->SetPaperHeight( width );
+ }
+ }
+
+ // copy input slot
+ const PPDKey* pKey = nullptr;
+ const PPDValue* pValue = nullptr;
+
+ pJobSetup->SetPaperBin( 0 );
+ if( rData.m_pParser )
+ pKey = rData.m_pParser->getKey( "InputSlot" );
+ if( pKey )
+ pValue = rData.m_aContext.getValue( pKey );
+ if( pKey && pValue )
+ {
+ int nPaperBin;
+ for( nPaperBin = 0;
+ pValue != pKey->getValue( nPaperBin ) &&
+ nPaperBin < pKey->countValues();
+ nPaperBin++);
+ pJobSetup->SetPaperBin(
+ nPaperBin == pKey->countValues() ? 0 : nPaperBin);
+ }
+
+ // copy duplex
+ pKey = nullptr;
+ pValue = nullptr;
+
+ pJobSetup->SetDuplexMode( DuplexMode::Unknown );
+ if( rData.m_pParser )
+ pKey = rData.m_pParser->getKey( "Duplex" );
+ if( pKey )
+ pValue = rData.m_aContext.getValue( pKey );
+ if( pKey && pValue )
+ {
+ if( pValue->m_aOption.equalsIgnoreAsciiCase( "None" ) ||
+ pValue->m_aOption.startsWithIgnoreAsciiCase( "Simplex" )
+ )
+ {
+ pJobSetup->SetDuplexMode( DuplexMode::Off);
+ }
+ else if( pValue->m_aOption.equalsIgnoreAsciiCase( "DuplexNoTumble" ) )
+ {
+ pJobSetup->SetDuplexMode( DuplexMode::LongEdge );
+ }
+ else if( pValue->m_aOption.equalsIgnoreAsciiCase( "DuplexTumble" ) )
+ {
+ pJobSetup->SetDuplexMode( DuplexMode::ShortEdge );
+ }
+ }
+
+ // copy the whole context
+
+ sal_uInt32 nBytes;
+ std::unique_ptr<sal_uInt8[]> pBuffer;
+ if( rData.getStreamBuffer( pBuffer, nBytes ) )
+ {
+ pJobSetup->SetDriverData( std::move(pBuffer), nBytes );
+ }
+ else
+ {
+ pJobSetup->SetDriverData( nullptr, 0 );
+ }
+ pJobSetup->SetPapersizeFromSetup( rData.m_bPapersizeFromSetup );
+}
+
+static std::vector<OUString> getFaxNumbers()
+{
+ std::vector<OUString> aFaxNumbers;
+
+ OUString aNewNr;
+ if (QueryFaxNumber(aNewNr))
+ {
+ for (sal_Int32 nIndex {0}; nIndex >= 0; )
+ aFaxNumbers.push_back(aNewNr.getToken( 0, ';', nIndex ));
+ }
+
+ return aFaxNumbers;
+}
+
+/*
+ * SalInstance
+ */
+
+void SalGenericInstance::configurePspInfoPrinter(PspSalInfoPrinter *pPrinter,
+ SalPrinterQueueInfo const * pQueueInfo, ImplJobSetup* pJobSetup)
+{
+ if( !pJobSetup )
+ return;
+
+ PrinterInfoManager& rManager( PrinterInfoManager::get() );
+ PrinterInfo aInfo( rManager.getPrinterInfo( pQueueInfo->maPrinterName ) );
+ pPrinter->m_aJobData = aInfo;
+
+ if( pJobSetup->GetDriverData() )
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(),
+ pJobSetup->GetDriverDataLen(), aInfo );
+
+ pJobSetup->SetSystem( JOBSETUP_SYSTEM_UNIX );
+ pJobSetup->SetPrinterName( pQueueInfo->maPrinterName );
+ pJobSetup->SetDriver( aInfo.m_aDriverName );
+ copyJobDataToJobSetup( pJobSetup, aInfo );
+}
+
+SalInfoPrinter* SalGenericInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pJobSetup )
+{
+ mbPrinterInit = true;
+ // create and initialize SalInfoPrinter
+ PspSalInfoPrinter* pPrinter = new PspSalInfoPrinter();
+ configurePspInfoPrinter(pPrinter, pQueueInfo, pJobSetup);
+ return pPrinter;
+}
+
+void SalGenericInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter )
+{
+ delete pPrinter;
+}
+
+std::unique_ptr<SalPrinter> SalGenericInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
+{
+ mbPrinterInit = true;
+ // create and initialize SalPrinter
+ PspSalPrinter* pPrinter = new PspSalPrinter( pInfoPrinter );
+ pPrinter->m_aJobData = static_cast<PspSalInfoPrinter*>(pInfoPrinter)->m_aJobData;
+
+ return std::unique_ptr<SalPrinter>(pPrinter);
+}
+
+void SalGenericInstance::GetPrinterQueueInfo( ImplPrnQueueList* pList )
+{
+ mbPrinterInit = true;
+ PrinterInfoManager& rManager( PrinterInfoManager::get() );
+ static const char* pNoSyncDetection = getenv( "SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION" );
+ if( ! pNoSyncDetection || ! *pNoSyncDetection )
+ {
+ // #i62663# synchronize possible asynchronouse printer detection now
+ rManager.checkPrintersChanged( true );
+ }
+ ::std::vector< OUString > aPrinters;
+ rManager.listPrinters( aPrinters );
+
+ for (auto const& printer : aPrinters)
+ {
+ const PrinterInfo& rInfo( rManager.getPrinterInfo(printer) );
+ // create new entry
+ std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo);
+ pInfo->maPrinterName = printer;
+ pInfo->maDriver = rInfo.m_aDriverName;
+ pInfo->maLocation = rInfo.m_aLocation;
+ pInfo->maComment = rInfo.m_aComment;
+
+ OUString sPdfDir;
+ if (getPdfDir(rInfo, sPdfDir))
+ pInfo->maLocation = sPdfDir;
+
+ pList->Add( std::move(pInfo) );
+ }
+}
+
+void SalGenericInstance::GetPrinterQueueState( SalPrinterQueueInfo* )
+{
+ mbPrinterInit = true;
+}
+
+OUString SalGenericInstance::GetDefaultPrinter()
+{
+ mbPrinterInit = true;
+ PrinterInfoManager& rManager( PrinterInfoManager::get() );
+ return rManager.getDefaultPrinter();
+}
+
+PspSalInfoPrinter::PspSalInfoPrinter()
+{
+}
+
+PspSalInfoPrinter::~PspSalInfoPrinter()
+{
+}
+
+void PspSalInfoPrinter::InitPaperFormats( const ImplJobSetup* )
+{
+ m_aPaperFormats.clear();
+ m_bPapersInit = true;
+
+ if( !m_aJobData.m_pParser )
+ return;
+
+ const PPDKey* pKey = m_aJobData.m_pParser->getKey( "PageSize" );
+ if( pKey )
+ {
+ int nValues = pKey->countValues();
+ for( int i = 0; i < nValues; i++ )
+ {
+ const PPDValue* pValue = pKey->getValue( i );
+ int nWidth = 0, nHeight = 0;
+ m_aJobData.m_pParser->getPaperDimension( pValue->m_aOption, nWidth, nHeight );
+ PaperInfo aInfo(PtTo10Mu( nWidth ), PtTo10Mu( nHeight ));
+ m_aPaperFormats.push_back( aInfo );
+ }
+ }
+}
+
+int PspSalInfoPrinter::GetLandscapeAngle( const ImplJobSetup* )
+{
+ return 900;
+}
+
+SalGraphics* PspSalInfoPrinter::AcquireGraphics()
+{
+ // return a valid pointer only once
+ // the reasoning behind this is that we could have different
+ // SalGraphics that can run in multiple threads
+ // (future plans)
+ SalGraphics* pRet = nullptr;
+ if( ! m_pGraphics )
+ {
+ m_pGraphics = GetGenericInstance()->CreatePrintGraphics();
+ m_pGraphics->Init(&m_aJobData);
+ pRet = m_pGraphics.get();
+ }
+ return pRet;
+}
+
+void PspSalInfoPrinter::ReleaseGraphics( SalGraphics* pGraphics )
+{
+ if( m_pGraphics.get() == pGraphics )
+ {
+ m_pGraphics.reset();
+ }
+}
+
+bool PspSalInfoPrinter::Setup( weld::Window* pFrame, ImplJobSetup* pJobSetup )
+{
+ if( ! pFrame || ! pJobSetup )
+ return false;
+
+ PrinterInfoManager& rManager = PrinterInfoManager::get();
+
+ PrinterInfo aInfo( rManager.getPrinterInfo( pJobSetup->GetPrinterName() ) );
+ if ( pJobSetup->GetDriverData() )
+ {
+ SetData( JobSetFlags::ALL, pJobSetup );
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aInfo );
+ }
+ aInfo.m_bPapersizeFromSetup = pJobSetup->GetPapersizeFromSetup();
+ aInfo.meSetupMode = pJobSetup->GetPrinterSetupMode();
+
+ if (SetupPrinterDriver(pFrame, aInfo))
+ {
+ pJobSetup->SetDriverData( nullptr, 0 );
+
+ sal_uInt32 nBytes;
+ std::unique_ptr<sal_uInt8[]> pBuffer;
+ aInfo.getStreamBuffer( pBuffer, nBytes );
+ pJobSetup->SetDriverData( std::move(pBuffer), nBytes );
+
+ // copy everything to job setup
+ copyJobDataToJobSetup( pJobSetup, aInfo );
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), m_aJobData );
+ return true;
+ }
+ return false;
+}
+
+// This function gets the driver data and puts it into pJobSetup
+// If pJobSetup->GetDriverData() is NOT NULL, then the independent
+// data should be merged into the driver data
+// If pJobSetup->GetDriverData() IS NULL, then the driver defaults
+// should be merged into the independent data
+bool PspSalInfoPrinter::SetPrinterData( ImplJobSetup* pJobSetup )
+{
+ if( pJobSetup->GetDriverData() )
+ return SetData( JobSetFlags::ALL, pJobSetup );
+
+ copyJobDataToJobSetup( pJobSetup, m_aJobData );
+
+ return true;
+}
+
+// This function merges the independent driver data
+// and sets the new independent data in pJobSetup
+// Only the data must be changed, where the bit
+// in nGetDataFlags is set
+bool PspSalInfoPrinter::SetData(
+ JobSetFlags nSetDataFlags,
+ ImplJobSetup* pJobSetup )
+{
+ JobData aData;
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+
+ if( aData.m_pParser )
+ {
+ const PPDKey* pKey;
+ const PPDValue* pValue;
+
+ // merge orientation if necessary
+ if( nSetDataFlags & JobSetFlags::ORIENTATION )
+ aData.m_eOrientation = pJobSetup->GetOrientation() == Orientation::Landscape ? orientation::Landscape : orientation::Portrait;
+
+ // merge papersize if necessary
+ if( nSetDataFlags & JobSetFlags::PAPERSIZE )
+ {
+ OUString aPaper;
+
+ if( pJobSetup->GetPaperFormat() == PAPER_USER )
+ aPaper = aData.m_pParser->matchPaper(
+ TenMuToPt( pJobSetup->GetPaperWidth() ),
+ TenMuToPt( pJobSetup->GetPaperHeight() ),
+ &aData.m_eOrientation );
+ else
+ aPaper = OStringToOUString(PaperInfo::toPSName(pJobSetup->GetPaperFormat()), RTL_TEXTENCODING_ISO_8859_1);
+
+ pKey = aData.m_pParser->getKey( "PageSize" );
+ pValue = pKey ? pKey->getValueCaseInsensitive( aPaper ) : nullptr;
+
+ // some PPD files do not specify the standard paper names (e.g. C5 instead of EnvC5)
+ // try to find the correct paper anyway using the size
+ if( pKey && ! pValue && pJobSetup->GetPaperFormat() != PAPER_USER )
+ {
+ PaperInfo aInfo( pJobSetup->GetPaperFormat() );
+ aPaper = aData.m_pParser->matchPaper(
+ TenMuToPt( aInfo.getWidth() ),
+ TenMuToPt( aInfo.getHeight() ),
+ &aData.m_eOrientation );
+ pValue = pKey->getValueCaseInsensitive( aPaper );
+ }
+
+ if( ! ( pKey && pValue && aData.m_aContext.setValue( pKey, pValue ) == pValue ) )
+ return false;
+ }
+
+ // merge paperbin if necessary
+ if( nSetDataFlags & JobSetFlags::PAPERBIN )
+ {
+ pKey = aData.m_pParser->getKey( "InputSlot" );
+ if( pKey )
+ {
+ int nPaperBin = pJobSetup->GetPaperBin();
+ if( nPaperBin >= pKey->countValues() )
+ pValue = pKey->getDefaultValue();
+ else
+ pValue = pKey->getValue( pJobSetup->GetPaperBin() );
+
+ // may fail due to constraints;
+ // real paper bin is copied back to jobsetup in that case
+ aData.m_aContext.setValue( pKey, pValue );
+ }
+ // if printer has no InputSlot key simply ignore this setting
+ // (e.g. SGENPRT has no InputSlot)
+ }
+
+ // merge duplex if necessary
+ if( nSetDataFlags & JobSetFlags::DUPLEXMODE )
+ {
+ pKey = aData.m_pParser->getKey( "Duplex" );
+ if( pKey )
+ {
+ pValue = nullptr;
+ switch( pJobSetup->GetDuplexMode() )
+ {
+ case DuplexMode::Off:
+ pValue = pKey->getValue( "None" );
+ if( pValue == nullptr )
+ pValue = pKey->getValue( "SimplexNoTumble" );
+ break;
+ case DuplexMode::ShortEdge:
+ pValue = pKey->getValue( "DuplexTumble" );
+ break;
+ case DuplexMode::LongEdge:
+ pValue = pKey->getValue( "DuplexNoTumble" );
+ break;
+ case DuplexMode::Unknown:
+ default:
+ pValue = nullptr;
+ break;
+ }
+ if( ! pValue )
+ pValue = pKey->getDefaultValue();
+ aData.m_aContext.setValue( pKey, pValue );
+ }
+ }
+ aData.m_bPapersizeFromSetup = pJobSetup->GetPapersizeFromSetup();
+
+ m_aJobData = aData;
+ copyJobDataToJobSetup( pJobSetup, aData );
+ return true;
+ }
+
+ return false;
+}
+
+void PspSalInfoPrinter::GetPageInfo(
+ const ImplJobSetup* pJobSetup,
+ tools::Long& rOutWidth, tools::Long& rOutHeight,
+ Point& rPageOffset,
+ Size& rPaperSize )
+{
+ if( ! pJobSetup )
+ return;
+
+ JobData aData;
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+
+ // get the selected page size
+ if( !aData.m_pParser )
+ return;
+
+
+ OUString aPaper;
+ int width, height;
+ int left = 0, top = 0, right = 0, bottom = 0;
+ int nDPI = aData.m_aContext.getRenderResolution();
+
+ if( aData.m_eOrientation == psp::orientation::Portrait )
+ {
+ aData.m_aContext.getPageSize( aPaper, width, height );
+ aData.m_pParser->getMargins( aPaper, left, right, top, bottom );
+ }
+ else
+ {
+ aData.m_aContext.getPageSize( aPaper, height, width );
+ aData.m_pParser->getMargins( aPaper, top, bottom, right, left );
+ }
+
+ rPaperSize.setWidth( width * nDPI / 72 );
+ rPaperSize.setHeight( height * nDPI / 72 );
+ rPageOffset.setX( left * nDPI / 72 );
+ rPageOffset.setY( top * nDPI / 72 );
+ rOutWidth = ( width - left - right ) * nDPI / 72;
+ rOutHeight = ( height - top - bottom ) * nDPI / 72;
+
+}
+
+sal_uInt16 PspSalInfoPrinter::GetPaperBinCount( const ImplJobSetup* pJobSetup )
+{
+ if( ! pJobSetup )
+ return 0;
+
+ JobData aData;
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+
+ const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey( "InputSlot" ): nullptr;
+ return pKey ? pKey->countValues() : 0;
+}
+
+OUString PspSalInfoPrinter::GetPaperBinName( const ImplJobSetup* pJobSetup, sal_uInt16 nPaperBin )
+{
+ JobData aData;
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+
+ if( aData.m_pParser )
+ {
+ const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey( "InputSlot" ): nullptr;
+ if( ! pKey || nPaperBin >= o3tl::make_unsigned(pKey->countValues()) )
+ return aData.m_pParser->getDefaultInputSlot();
+ const PPDValue* pValue = pKey->getValue( nPaperBin );
+ if( pValue )
+ return aData.m_pParser->translateOption( pKey->getKey(), pValue->m_aOption );
+ }
+
+ return OUString();
+}
+
+sal_uInt32 PspSalInfoPrinter::GetCapabilities( const ImplJobSetup* pJobSetup, PrinterCapType nType )
+{
+ switch( nType )
+ {
+ case PrinterCapType::SupportDialog:
+ return 1;
+ case PrinterCapType::Copies:
+ return 0xffff;
+ case PrinterCapType::CollateCopies:
+ {
+ // PPDs don't mention the number of possible collated copies.
+ // so let's guess as many as we want ?
+ return 0xffff;
+ }
+ case PrinterCapType::SetOrientation:
+ return 1;
+ case PrinterCapType::SetPaperSize:
+ return 1;
+ case PrinterCapType::SetPaper:
+ return 0;
+ case PrinterCapType::Fax:
+ {
+ // see if the PPD contains the fax4CUPS "Dial" option and that it's not set
+ // to "manually"
+ JobData aData = PrinterInfoManager::get().getPrinterInfo(pJobSetup->GetPrinterName());
+ if( pJobSetup->GetDriverData() )
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+ const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey("Dial") : nullptr;
+ const PPDValue* pValue = pKey ? aData.m_aContext.getValue(pKey) : nullptr;
+ if (pValue && !pValue->m_aOption.equalsIgnoreAsciiCase("Manually"))
+ return 1;
+ return 0;
+ }
+
+ case PrinterCapType::PDF:
+ return 1;
+ case PrinterCapType::ExternalDialog:
+ return PrinterInfoManager::get().checkFeatureToken( pJobSetup->GetPrinterName(), "external_dialog" ) ? 1 : 0;
+ case PrinterCapType::UsePullModel:
+ return 1;
+ default: break;
+ }
+ return 0;
+}
+
+/*
+ * SalPrinter
+ */
+PspSalPrinter::PspSalPrinter( SalInfoPrinter* pInfoPrinter )
+ : m_pInfoPrinter( pInfoPrinter )
+{
+}
+
+PspSalPrinter::~PspSalPrinter()
+{
+}
+
+bool PspSalPrinter::StartJob(
+ const OUString* /*pFileName*/,
+ const OUString& /*rJobName*/,
+ const OUString& /*rAppName*/,
+ sal_uInt32 /*nCopies*/,
+ bool /*bCollate*/,
+ bool /*bDirect*/,
+ ImplJobSetup* /*pJobSetup*/ )
+{
+ OSL_FAIL( "should never be called" );
+ return false;
+}
+
+bool PspSalPrinter::EndJob()
+{
+ GetSalInstance()->jobEndedPrinterUpdate();
+ return true;
+}
+
+SalGraphics* PspSalPrinter::StartPage( ImplJobSetup*, bool )
+{
+ OSL_FAIL( "should never be called" );
+ return nullptr;
+}
+
+void PspSalPrinter::EndPage()
+{
+ OSL_FAIL( "should never be called" );
+}
+
+namespace {
+
+struct PDFNewJobParameters
+{
+ Size maPageSize;
+ sal_uInt16 mnPaperBin;
+
+ PDFNewJobParameters( const Size& i_rSize = Size(),
+ sal_uInt16 i_nPaperBin = 0xffff )
+ : maPageSize( i_rSize ), mnPaperBin( i_nPaperBin ) {}
+
+ bool operator==(const PDFNewJobParameters& rComp ) const
+ {
+ const tools::Long nRotatedWidth = rComp.maPageSize.Height();
+ const tools::Long nRotatedHeight = rComp.maPageSize.Width();
+ Size aCompLSSize(nRotatedWidth, nRotatedHeight);
+ return
+ (maPageSize == rComp.maPageSize || maPageSize == aCompLSSize)
+ && mnPaperBin == rComp.mnPaperBin
+ ;
+ }
+
+ bool operator!=(const PDFNewJobParameters& rComp) const
+ {
+ return ! operator==(rComp);
+ }
+};
+
+struct PDFPrintFile
+{
+ OUString maTmpURL;
+ PDFNewJobParameters maParameters;
+
+ PDFPrintFile( OUString i_URL, const PDFNewJobParameters& i_rNewParameters )
+ : maTmpURL(std::move( i_URL ))
+ , maParameters( i_rNewParameters ) {}
+};
+
+}
+
+bool PspSalPrinter::StartJob( const OUString* i_pFileName, const OUString& i_rJobName, const OUString& i_rAppName,
+ ImplJobSetup* i_pSetupData, vcl::PrinterController& i_rController )
+{
+ SAL_INFO( "vcl.unx.print", "StartJob with controller: pFilename = " << (i_pFileName ? *i_pFileName : "<nil>") );
+ // reset IsLastPage
+ i_rController.setLastPage( false );
+ // is this a fax device
+ bool bFax = m_pInfoPrinter->GetCapabilities(i_pSetupData, PrinterCapType::Fax) == 1;
+
+ // update job data
+ if( i_pSetupData )
+ JobData::constructFromStreamBuffer( i_pSetupData->GetDriverData(), i_pSetupData->GetDriverDataLen(), m_aJobData );
+
+ // possibly create one job for collated output
+ int nCopies = i_rController.getPrinter()->GetCopyCount();
+ bool bCollate = i_rController.getPrinter()->IsCollateCopy();
+ bool bSinglePrintJobs = i_rController.getPrinter()->IsSinglePrintJobs();
+
+ // notify start of real print job
+ i_rController.jobStarted();
+
+ // setup PDFWriter context
+ vcl::PDFWriter::PDFWriterContext aContext;
+ aContext.Version = vcl::PDFWriter::PDFVersion::PDF_1_4;
+ aContext.Tagged = false;
+ aContext.DocumentLocale = Application::GetSettings().GetLanguageTag().getLocale();
+ aContext.ColorMode = i_rController.getPrinter()->GetPrinterOptions().IsConvertToGreyscales()
+ ? vcl::PDFWriter::DrawGreyscale : vcl::PDFWriter::DrawColor;
+
+ // prepare doc info
+ aContext.DocumentInfo.Title = i_rJobName;
+ aContext.DocumentInfo.Creator = i_rAppName;
+ aContext.DocumentInfo.Producer = i_rAppName;
+
+ // define how we handle metafiles in PDFWriter
+ vcl::PDFWriter::PlayMetafileContext aMtfContext;
+ aMtfContext.m_bOnlyLosslessCompression = true;
+
+ std::shared_ptr<vcl::PDFWriter> xWriter;
+ std::vector< PDFPrintFile > aPDFFiles;
+ VclPtr<Printer> xPrinter( i_rController.getPrinter() );
+ int nAllPages = i_rController.getFilteredPageCount();
+ i_rController.createProgressDialog();
+ bool bAborted = false;
+ PDFNewJobParameters aLastParm;
+
+ aContext.DPIx = xPrinter->GetDPIX();
+ aContext.DPIy = xPrinter->GetDPIY();
+ for( int nPage = 0; nPage < nAllPages && ! bAborted; nPage++ )
+ {
+ if( nPage == nAllPages-1 )
+ i_rController.setLastPage( true );
+
+ // get the page's metafile
+ GDIMetaFile aPageFile;
+ vcl::PrinterController::PageSize aPageSize = i_rController.getFilteredPageFile( nPage, aPageFile );
+ if( i_rController.isProgressCanceled() )
+ {
+ bAborted = true;
+ if( nPage != nAllPages-1 )
+ {
+ i_rController.createProgressDialog();
+ i_rController.setLastPage( true );
+ i_rController.getFilteredPageFile( nPage, aPageFile );
+ }
+ }
+ else
+ {
+ xPrinter->SetMapMode( MapMode( MapUnit::Map100thMM ) );
+ xPrinter->SetPaperSizeUser( aPageSize.aSize );
+ PDFNewJobParameters aNewParm(xPrinter->GetPaperSize(), xPrinter->GetPaperBin());
+
+ // create PDF writer on demand
+ // either on first page
+ // or on paper format change - cups does not support multiple paper formats per job (yet?)
+ // so we need to start a new job to get a new paper format from the printer
+ // orientation switches (that is switch of height and width) is handled transparently by CUPS
+ if( ! xWriter ||
+ (aNewParm != aLastParm && ! i_pFileName ) )
+ {
+ if( xWriter )
+ {
+ xWriter->Emit();
+ }
+ // produce PDF file
+ OUString aPDFUrl;
+ if( i_pFileName )
+ aPDFUrl = *i_pFileName;
+ else
+ osl_createTempFile( nullptr, nullptr, &aPDFUrl.pData );
+ // normalize to file URL
+ if( !comphelper::isFileUrl(aPDFUrl) )
+ {
+ // this is not a file URL, but it should
+ // form it into an osl friendly file URL
+ OUString aTmp;
+ osl_getFileURLFromSystemPath( aPDFUrl.pData, &aTmp.pData );
+ aPDFUrl = aTmp;
+ }
+ // save current file and paper format
+ aLastParm = aNewParm;
+ aPDFFiles.emplace_back( aPDFUrl, aNewParm );
+ // update context
+ aContext.URL = aPDFUrl;
+
+ // create and initialize PDFWriter
+ xWriter = std::make_shared<vcl::PDFWriter>( aContext, uno::Reference< beans::XMaterialHolder >() );
+ }
+
+ xWriter->NewPage( TenMuToPt( aNewParm.maPageSize.Width() ),
+ TenMuToPt( aNewParm.maPageSize.Height() ),
+ vcl::PDFWriter::Orientation::Portrait );
+
+ xWriter->PlayMetafile( aPageFile, aMtfContext );
+ }
+ }
+
+ // emit the last file
+ if( xWriter )
+ xWriter->Emit();
+
+ // handle collate, copy count and multiple jobs correctly
+ int nOuterJobs = 1;
+ if( bSinglePrintJobs )
+ {
+ nOuterJobs = nCopies;
+ m_aJobData.m_nCopies = 1;
+ }
+ else
+ {
+ if( bCollate )
+ {
+ if (aPDFFiles.size() == 1 && xPrinter->HasSupport(PrinterSupport::CollateCopy))
+ {
+ m_aJobData.setCollate( true );
+ m_aJobData.m_nCopies = nCopies;
+ }
+ else
+ {
+ nOuterJobs = nCopies;
+ m_aJobData.m_nCopies = 1;
+ }
+ }
+ else
+ {
+ m_aJobData.setCollate( false );
+ m_aJobData.m_nCopies = nCopies;
+ }
+ }
+
+ std::vector<OUString> aFaxNumbers;
+
+ // check for fax numbers
+ if (!bAborted && bFax)
+ {
+ aFaxNumbers = getFaxNumbers();
+ bAborted = aFaxNumbers.empty();
+ }
+
+ bool bSuccess(true);
+ // spool files
+ if( ! i_pFileName && ! bAborted )
+ {
+ do
+ {
+ OUString sFaxNumber;
+ if (!aFaxNumbers.empty())
+ {
+ sFaxNumber = aFaxNumbers.back();
+ aFaxNumbers.pop_back();
+ }
+
+ bool bFirstJob = true;
+ for( int nCurJob = 0; nCurJob < nOuterJobs; nCurJob++ )
+ {
+ for( size_t i = 0; i < aPDFFiles.size(); i++ )
+ {
+ oslFileHandle pFile = nullptr;
+ osl_openFile( aPDFFiles[i].maTmpURL.pData, &pFile, osl_File_OpenFlag_Read );
+ if (pFile && (osl_setFilePos(pFile, osl_Pos_Absolut, 0) == osl_File_E_None))
+ {
+ std::vector< char > buffer( 0x10000, 0 );
+ // update job data with current page size
+ Size aPageSize( aPDFFiles[i].maParameters.maPageSize );
+ m_aJobData.setPaper( TenMuToPt( aPageSize.Width() ), TenMuToPt( aPageSize.Height() ) );
+ // update job data with current paperbin
+ m_aJobData.setPaperBin( aPDFFiles[i].maParameters.mnPaperBin );
+
+ // spool current file
+ FILE* fp = PrinterInfoManager::get().startSpool(xPrinter->GetName(), i_rController.isDirectPrint());
+ if( fp )
+ {
+ sal_uInt64 nBytesRead = 0;
+ do
+ {
+ osl_readFile( pFile, buffer.data(), buffer.size(), &nBytesRead );
+ if( nBytesRead > 0 )
+ {
+ size_t nBytesWritten = fwrite(buffer.data(), 1, nBytesRead, fp);
+ OSL_ENSURE(nBytesRead == nBytesWritten, "short write");
+ if (nBytesRead != nBytesWritten)
+ break;
+ }
+ } while( nBytesRead == buffer.size() );
+ OUStringBuffer aBuf( i_rJobName.getLength() + 8 );
+ aBuf.append( i_rJobName );
+ if( i > 0 || nCurJob > 0 )
+ {
+ aBuf.append( ' ' );
+ aBuf.append( sal_Int32( i + nCurJob * aPDFFiles.size() ) );
+ }
+ bSuccess &=
+ PrinterInfoManager::get().endSpool(xPrinter->GetName(), aBuf.makeStringAndClear(), fp, m_aJobData, bFirstJob, sFaxNumber);
+ bFirstJob = false;
+ }
+ }
+ osl_closeFile( pFile );
+ }
+ }
+ }
+ while (!aFaxNumbers.empty());
+ }
+
+ // job has been spooled
+ i_rController.setJobState( bAborted
+ ? view::PrintableState_JOB_ABORTED
+ : (bSuccess ? view::PrintableState_JOB_SPOOLED
+ : view::PrintableState_JOB_SPOOLING_FAILED));
+
+ // clean up the temporary PDF files
+ if( ! i_pFileName || bAborted )
+ {
+ for(PDFPrintFile & rPDFFile : aPDFFiles)
+ {
+ osl_removeFile( rPDFFile.maTmpURL.pData );
+ SAL_INFO( "vcl.unx.print", "removed print PDF file " << rPDFFile.maTmpURL );
+ }
+ }
+
+ return true;
+}
+
+namespace {
+
+class PrinterUpdate
+{
+ static Idle* pPrinterUpdateIdle;
+ static int nActiveJobs;
+
+ static void doUpdate();
+ DECL_STATIC_LINK( PrinterUpdate, UpdateTimerHdl, Timer*, void );
+public:
+ static void update(SalGenericInstance const &rInstance);
+ static void jobEnded();
+};
+
+}
+
+Idle* PrinterUpdate::pPrinterUpdateIdle = nullptr;
+int PrinterUpdate::nActiveJobs = 0;
+
+void PrinterUpdate::doUpdate()
+{
+ ::psp::PrinterInfoManager& rManager( ::psp::PrinterInfoManager::get() );
+ SalGenericInstance *pInst = GetGenericInstance();
+ if( pInst && rManager.checkPrintersChanged( false ) )
+ pInst->PostPrintersChanged();
+}
+
+IMPL_STATIC_LINK_NOARG( PrinterUpdate, UpdateTimerHdl, Timer*, void )
+{
+ if( nActiveJobs < 1 )
+ {
+ doUpdate();
+ delete pPrinterUpdateIdle;
+ pPrinterUpdateIdle = nullptr;
+ }
+ else
+ pPrinterUpdateIdle->Start();
+}
+
+void PrinterUpdate::update(SalGenericInstance const &rInstance)
+{
+ if( Application::GetSettings().GetMiscSettings().GetDisablePrinting() )
+ return;
+
+ if( ! rInstance.isPrinterInit() )
+ {
+ // #i45389# start background printer detection
+ psp::PrinterInfoManager::get();
+ return;
+ }
+
+ if( nActiveJobs < 1 )
+ doUpdate();
+ else if( ! pPrinterUpdateIdle )
+ {
+ pPrinterUpdateIdle = new Idle("PrinterUpdateTimer");
+ pPrinterUpdateIdle->SetPriority( TaskPriority::LOWEST );
+ pPrinterUpdateIdle->SetInvokeHandler( LINK( nullptr, PrinterUpdate, UpdateTimerHdl ) );
+ pPrinterUpdateIdle->Start();
+ }
+}
+
+void SalGenericInstance::updatePrinterUpdate()
+{
+ PrinterUpdate::update(*this);
+}
+
+void PrinterUpdate::jobEnded()
+{
+ nActiveJobs--;
+ if( nActiveJobs < 1 )
+ {
+ if( pPrinterUpdateIdle )
+ {
+ pPrinterUpdateIdle->Stop();
+ delete pPrinterUpdateIdle;
+ pPrinterUpdateIdle = nullptr;
+ doUpdate();
+ }
+ }
+}
+
+void SalGenericInstance::jobEndedPrinterUpdate()
+{
+ PrinterUpdate::jobEnded();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/genpspgraphics.cxx b/vcl/unx/generic/print/genpspgraphics.cxx
new file mode 100644
index 0000000000..66bf452474
--- /dev/null
+++ b/vcl/unx/generic/print/genpspgraphics.cxx
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <vector>
+
+#include <sal/types.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <i18nlangtag/mslangid.hxx>
+#include <jobdata.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <config_cairo_canvas.h>
+
+#include <fontsubset.hxx>
+#include <unx/freetype_glyphcache.hxx>
+#include <unx/geninst.h>
+#include <unx/genpspgraphics.h>
+#include <langboost.hxx>
+#include <font/LogicalFontInstance.hxx>
+#include <fontattributes.hxx>
+#include <font/FontMetricData.hxx>
+#include <font/FontSelectPattern.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <o3tl/string_view.hxx>
+#include <sallayout.hxx>
+
+using namespace psp;
+
+/*******************************************************
+ * GenPspGraphics
+ *******************************************************/
+
+GenPspGraphics::GenPspGraphics()
+ : m_pJobData( nullptr )
+ , m_aTextRenderImpl(m_aCairoCommon)
+ , m_pBackend(new SvpGraphicsBackend(m_aCairoCommon))
+{
+}
+
+void GenPspGraphics::Init(psp::JobData* pJob)
+{
+ m_pJobData = pJob;
+ SetLayout( SalLayoutFlags::NONE );
+}
+
+GenPspGraphics::~GenPspGraphics()
+{
+ ReleaseFonts();
+}
+
+void GenPspGraphics::GetResolution( sal_Int32 &rDPIX, sal_Int32 &rDPIY )
+{
+ if (m_pJobData != nullptr)
+ {
+ int x = m_pJobData->m_aContext.getRenderResolution();
+
+ rDPIX = x;
+ rDPIY = x;
+ }
+}
+
+void GenPspGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+ m_aTextRenderImpl.DrawTextLayout(rLayout, *this);
+}
+
+FontCharMapRef GenPspGraphics::GetFontCharMap() const
+{
+ return m_aTextRenderImpl.GetFontCharMap();
+}
+
+bool GenPspGraphics::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ return m_aTextRenderImpl.GetFontCapabilities(rFontCapabilities);
+}
+
+void GenPspGraphics::SetFont(LogicalFontInstance *pFontInstance, int nFallbackLevel)
+{
+ m_aTextRenderImpl.SetFont(pFontInstance, nFallbackLevel);
+}
+
+void GenPspGraphics::SetTextColor( Color nColor )
+{
+ m_aTextRenderImpl.SetTextColor(nColor);
+}
+
+bool GenPspGraphics::AddTempDevFont( vcl::font::PhysicalFontCollection* pFontCollection,
+ const OUString& rFileURL,
+ const OUString& rFontName )
+{
+ return m_aTextRenderImpl.AddTempDevFont(pFontCollection, rFileURL, rFontName);
+}
+
+void GenPspGraphics::GetDevFontList( vcl::font::PhysicalFontCollection *pFontCollection )
+{
+ m_aTextRenderImpl.GetDevFontList(pFontCollection);
+}
+
+void GenPspGraphics::ClearDevFontCache()
+{
+ m_aTextRenderImpl.ClearDevFontCache();
+}
+
+void GenPspGraphics::GetFontMetric(FontMetricDataRef& rxFontMetric, int nFallbackLevel)
+{
+ m_aTextRenderImpl.GetFontMetric(rxFontMetric, nFallbackLevel);
+}
+
+std::unique_ptr<GenericSalLayout> GenPspGraphics::GetTextLayout(int nFallbackLevel)
+{
+ return m_aTextRenderImpl.GetTextLayout(nFallbackLevel);
+}
+
+namespace vcl
+{
+ const char* getLangBoost()
+ {
+ const char* pLangBoost;
+ const LanguageType eLang = Application::GetSettings().GetUILanguageTag().getLanguageType();
+ if (eLang == LANGUAGE_JAPANESE)
+ pLangBoost = "jan";
+ else if (MsLangId::isKorean(eLang))
+ pLangBoost = "kor";
+ else if (MsLangId::isSimplifiedChinese(eLang))
+ pLangBoost = "zhs";
+ else if (MsLangId::isTraditionalChinese(eLang))
+ pLangBoost = "zht";
+ else
+ pLangBoost = nullptr;
+ return pLangBoost;
+ }
+}
+
+SystemGraphicsData GenPspGraphics::GetGraphicsData() const
+{
+ return SystemGraphicsData();
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+bool GenPspGraphics::SupportsCairo() const
+{
+ return false;
+}
+
+cairo::SurfaceSharedPtr GenPspGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& /*rSurface*/) const
+{
+ return cairo::SurfaceSharedPtr();
+}
+
+cairo::SurfaceSharedPtr GenPspGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int /*x*/, int /*y*/, int /*width*/, int /*height*/) const
+{
+ return cairo::SurfaceSharedPtr();
+}
+
+cairo::SurfaceSharedPtr GenPspGraphics::CreateBitmapSurface(const OutputDevice& /*rRefDevice*/, const BitmapSystemData& /*rData*/, const Size& /*rSize*/) const
+{
+ return cairo::SurfaceSharedPtr();
+}
+
+css::uno::Any GenPspGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& /*rSurface*/, const basegfx::B2ISize& /*rSize*/) const
+{
+ return css::uno::Any();
+}
+
+#endif // ENABLE_CAIRO_CANVAS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/prtsetup.cxx b/vcl/unx/generic/print/prtsetup.cxx
new file mode 100644
index 0000000000..4b0a63c1b3
--- /dev/null
+++ b/vcl/unx/generic/print/prtsetup.cxx
@@ -0,0 +1,465 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "prtsetup.hxx"
+#include <svdata.hxx>
+#include <strings.hrc>
+
+#include <officecfg/Office/Common.hxx>
+
+using namespace psp;
+
+void RTSDialog::insertAllPPDValues(weld::ComboBox& rBox, const PPDParser* pParser, const PPDKey* pKey )
+{
+ if( ! pKey || ! pParser )
+ return;
+
+ const PPDValue* pValue = nullptr;
+ OUString aOptionText;
+
+ for (int i = 0; i < pKey->countValues(); ++i)
+ {
+ pValue = pKey->getValue( i );
+ if (pValue->m_bCustomOption)
+ continue;
+ aOptionText = pParser->translateOption( pKey->getKey(), pValue->m_aOption) ;
+
+ OUString sId(weld::toId(pValue));
+ int nCurrentPos = rBox.find_id(sId);
+ if( m_aJobData.m_aContext.checkConstraints( pKey, pValue ) )
+ {
+ if (nCurrentPos == -1)
+ rBox.append(sId, aOptionText);
+ }
+ else
+ {
+ if (nCurrentPos != -1)
+ rBox.remove(nCurrentPos);
+ }
+ }
+ pValue = m_aJobData.m_aContext.getValue( pKey );
+ if (pValue && !pValue->m_bCustomOption)
+ {
+ OUString sId(weld::toId(pValue));
+ int nPos = rBox.find_id(sId);
+ if (nPos != -1)
+ rBox.set_active(nPos);
+ }
+}
+
+/*
+ * RTSDialog
+ */
+
+RTSDialog::RTSDialog(const PrinterInfo& rJobData, weld::Window* pParent)
+ : GenericDialogController(pParent, "vcl/ui/printerpropertiesdialog.ui", "PrinterPropertiesDialog")
+ , m_aJobData(rJobData)
+ , m_bDataModified(false)
+ , m_xTabControl(m_xBuilder->weld_notebook("tabcontrol"))
+ , m_xOKButton(m_xBuilder->weld_button("ok"))
+ , m_xCancelButton(m_xBuilder->weld_button("cancel"))
+ , m_xPaperPage(new RTSPaperPage(m_xTabControl->get_page("paper"), this))
+ , m_xDevicePage(new RTSDevicePage(m_xTabControl->get_page("device"), this))
+{
+ OUString aTitle(m_xDialog->get_title());
+ m_xDialog->set_title(aTitle.replaceAll("%s", m_aJobData.m_aPrinterName));
+
+ m_xTabControl->connect_enter_page( LINK( this, RTSDialog, ActivatePage ) );
+ m_xOKButton->connect_clicked( LINK( this, RTSDialog, ClickButton ) );
+ m_xCancelButton->connect_clicked( LINK( this, RTSDialog, ClickButton ) );
+ ActivatePage(m_xTabControl->get_current_page_ident());
+}
+
+RTSDialog::~RTSDialog()
+{
+}
+
+IMPL_LINK(RTSDialog, ActivatePage, const OUString&, rPage, void)
+{
+ if (rPage == "paper")
+ m_xPaperPage->update();
+}
+
+IMPL_LINK( RTSDialog, ClickButton, weld::Button&, rButton, void )
+{
+ if (&rButton == m_xOKButton.get())
+ {
+ // refresh the changed values
+ if (m_xPaperPage)
+ {
+ // orientation
+ m_aJobData.m_eOrientation = m_xPaperPage->getOrientation() == 0 ?
+ orientation::Portrait : orientation::Landscape;
+ // assume use of paper size from printer setup if the user
+ // got here via File > Printer Settings ...
+ if ( m_aJobData.meSetupMode == PrinterSetupMode::DocumentGlobal )
+ m_aJobData.m_bPapersizeFromSetup = true;
+ }
+ if( m_xDevicePage )
+ {
+ m_aJobData.m_nColorDepth = m_xDevicePage->getDepth();
+ m_aJobData.m_nColorDevice = m_xDevicePage->getColorDevice();
+ }
+ m_xDialog->response(RET_OK);
+ }
+ else if (&rButton == m_xCancelButton.get())
+ m_xDialog->response(RET_CANCEL);
+}
+
+/*
+ * RTSPaperPage
+ */
+
+RTSPaperPage::RTSPaperPage(weld::Widget* pPage, RTSDialog* pDialog)
+ : m_xBuilder(Application::CreateBuilder(pPage, "vcl/ui/printerpaperpage.ui"))
+ , m_pParent(pDialog)
+ , m_xContainer(m_xBuilder->weld_widget("PrinterPaperPage"))
+ , m_xCbFromSetup(m_xBuilder->weld_check_button("papersizefromsetup"))
+ , m_xPaperText(m_xBuilder->weld_label("paperft"))
+ , m_xPaperBox(m_xBuilder->weld_combo_box("paperlb"))
+ , m_xOrientText(m_xBuilder->weld_label("orientft"))
+ , m_xOrientBox(m_xBuilder->weld_combo_box("orientlb"))
+ , m_xDuplexText(m_xBuilder->weld_label("duplexft"))
+ , m_xDuplexBox(m_xBuilder->weld_combo_box("duplexlb"))
+ , m_xSlotText(m_xBuilder->weld_label("slotft"))
+ , m_xSlotBox(m_xBuilder->weld_combo_box("slotlb"))
+{
+ //PrinterPaperPage
+ m_xPaperBox->connect_changed( LINK( this, RTSPaperPage, SelectHdl ) );
+ m_xOrientBox->connect_changed( LINK( this, RTSPaperPage, SelectHdl ) );
+ m_xDuplexBox->connect_changed( LINK( this, RTSPaperPage, SelectHdl ) );
+ m_xSlotBox->connect_changed( LINK( this, RTSPaperPage, SelectHdl ) );
+ m_xCbFromSetup->connect_toggled( LINK( this, RTSPaperPage, CheckBoxHdl ) );
+
+ update();
+}
+
+RTSPaperPage::~RTSPaperPage()
+{
+}
+
+void RTSPaperPage::update()
+{
+ const PPDKey* pKey = nullptr;
+
+ // orientation
+ m_xOrientBox->set_active(m_pParent->m_aJobData.m_eOrientation == orientation::Portrait ? 0 : 1);
+
+ // duplex
+ if( m_pParent->m_aJobData.m_pParser &&
+ (pKey = m_pParent->m_aJobData.m_pParser->getKey( "Duplex" )) )
+ {
+ m_pParent->insertAllPPDValues( *m_xDuplexBox, m_pParent->m_aJobData.m_pParser, pKey );
+ }
+ else
+ {
+ m_xDuplexText->set_sensitive( false );
+ m_xDuplexBox->set_sensitive( false );
+ }
+
+ // paper
+ if( m_pParent->m_aJobData.m_pParser &&
+ (pKey = m_pParent->m_aJobData.m_pParser->getKey( "PageSize" )) )
+ {
+ m_pParent->insertAllPPDValues( *m_xPaperBox, m_pParent->m_aJobData.m_pParser, pKey );
+ }
+ else
+ {
+ m_xPaperText->set_sensitive( false );
+ m_xPaperBox->set_sensitive( false );
+ }
+
+ // input slots
+ if( m_pParent->m_aJobData.m_pParser &&
+ (pKey = m_pParent->m_aJobData.m_pParser->getKey( "InputSlot" )) )
+ {
+ m_pParent->insertAllPPDValues( *m_xSlotBox, m_pParent->m_aJobData.m_pParser, pKey );
+ }
+ else
+ {
+ m_xSlotText->set_sensitive( false );
+ m_xSlotBox->set_sensitive( false );
+ }
+
+ if ( m_pParent->m_aJobData.meSetupMode != PrinterSetupMode::SingleJob )
+ return;
+
+ m_xCbFromSetup->show();
+
+ if ( m_pParent->m_aJobData.m_bPapersizeFromSetup )
+ m_xCbFromSetup->set_active(m_pParent->m_aJobData.m_bPapersizeFromSetup);
+ // disable those, unless user wants to use papersize from printer prefs
+ // as they have no influence on what's going to be printed anyway
+ else
+ {
+ m_xPaperText->set_sensitive( false );
+ m_xPaperBox->set_sensitive( false );
+ m_xOrientText->set_sensitive( false );
+ m_xOrientBox->set_sensitive( false );
+ }
+}
+
+IMPL_LINK( RTSPaperPage, SelectHdl, weld::ComboBox&, rBox, void )
+{
+ const PPDKey* pKey = nullptr;
+ if( &rBox == m_xPaperBox.get() )
+ {
+ if( m_pParent->m_aJobData.m_pParser )
+ pKey = m_pParent->m_aJobData.m_pParser->getKey( "PageSize" );
+ }
+ else if( &rBox == m_xDuplexBox.get() )
+ {
+ if( m_pParent->m_aJobData.m_pParser )
+ pKey = m_pParent->m_aJobData.m_pParser->getKey( "Duplex" );
+ }
+ else if( &rBox == m_xSlotBox.get() )
+ {
+ if( m_pParent->m_aJobData.m_pParser )
+ pKey = m_pParent->m_aJobData.m_pParser->getKey( "InputSlot" );
+ }
+ else if( &rBox == m_xOrientBox.get() )
+ {
+ m_pParent->m_aJobData.m_eOrientation = m_xOrientBox->get_active() == 0 ? orientation::Portrait : orientation::Landscape;
+ }
+ if( pKey )
+ {
+ PPDValue* pValue = weld::fromId<PPDValue*>(rBox.get_active_id());
+ m_pParent->m_aJobData.m_aContext.setValue( pKey, pValue );
+ update();
+ }
+
+ m_pParent->SetDataModified( true );
+}
+
+IMPL_LINK_NOARG(RTSPaperPage, CheckBoxHdl, weld::Toggleable&, void)
+{
+ bool bFromSetup = m_xCbFromSetup->get_active();
+ m_pParent->m_aJobData.m_bPapersizeFromSetup = bFromSetup;
+ m_xPaperText->set_sensitive(bFromSetup);
+ m_xPaperBox->set_sensitive(bFromSetup);
+ m_xOrientText->set_sensitive(bFromSetup);
+ m_xOrientBox->set_sensitive(bFromSetup);
+ m_pParent->SetDataModified(true);
+}
+
+/*
+ * RTSDevicePage
+ */
+RTSDevicePage::RTSDevicePage(weld::Widget* pPage, RTSDialog* pParent)
+ : m_xBuilder(Application::CreateBuilder(pPage, "vcl/ui/printerdevicepage.ui"))
+ , m_pCustomValue(nullptr)
+ , m_pParent(pParent)
+ , m_xContainer(m_xBuilder->weld_widget("PrinterDevicePage"))
+ , m_xPPDKeyBox(m_xBuilder->weld_tree_view("options"))
+ , m_xPPDValueBox(m_xBuilder->weld_tree_view("values"))
+ , m_xCustomEdit(m_xBuilder->weld_entry("custom"))
+ , m_xSpaceBox(m_xBuilder->weld_combo_box("colorspace"))
+ , m_xDepthBox(m_xBuilder->weld_combo_box("colordepth"))
+ , m_aReselectCustomIdle("RTSDevicePage m_aReselectCustomIdle")
+{
+ m_aReselectCustomIdle.SetInvokeHandler(LINK(this, RTSDevicePage, ImplHandleReselectHdl));
+
+ m_xPPDKeyBox->set_size_request(m_xPPDKeyBox->get_approximate_digit_width() * 32,
+ m_xPPDKeyBox->get_height_rows(12));
+
+ m_xCustomEdit->connect_changed(LINK(this, RTSDevicePage, ModifyHdl));
+
+ m_xPPDKeyBox->connect_changed( LINK( this, RTSDevicePage, SelectHdl ) );
+ m_xPPDValueBox->connect_changed( LINK( this, RTSDevicePage, SelectHdl ) );
+
+ m_xSpaceBox->connect_changed(LINK(this, RTSDevicePage, ComboChangedHdl));
+ m_xDepthBox->connect_changed(LINK(this, RTSDevicePage, ComboChangedHdl));
+
+ switch( m_pParent->m_aJobData.m_nColorDevice )
+ {
+ case 0:
+ m_xSpaceBox->set_active(0);
+ break;
+ case 1:
+ m_xSpaceBox->set_active(1);
+ break;
+ case -1:
+ m_xSpaceBox->set_active(2);
+ break;
+ }
+
+ if (m_pParent->m_aJobData.m_nColorDepth == 8)
+ m_xDepthBox->set_active(0);
+ else if (m_pParent->m_aJobData.m_nColorDepth == 24)
+ m_xDepthBox->set_active(1);
+
+ // fill ppd boxes
+ if( !m_pParent->m_aJobData.m_pParser )
+ return;
+
+ for( int i = 0; i < m_pParent->m_aJobData.m_pParser->getKeys(); i++ )
+ {
+ const PPDKey* pKey = m_pParent->m_aJobData.m_pParser->getKey( i );
+
+ // skip options already shown somewhere else
+ // also skip options from the "InstallableOptions" PPD group
+ // Options in that group define hardware features that are not
+ // job-specific and should better be handled in the system-wide
+ // printer configuration. Keyword is defined in PPD specification
+ // (version 4.3), section 5.4.
+ if( pKey->isUIKey() &&
+ pKey->getKey() != "PageSize" &&
+ pKey->getKey() != "InputSlot" &&
+ pKey->getKey() != "PageRegion" &&
+ pKey->getKey() != "Duplex" &&
+ pKey->getGroup() != "InstallableOptions")
+ {
+ OUString aEntry( m_pParent->m_aJobData.m_pParser->translateKey( pKey->getKey() ) );
+ m_xPPDKeyBox->append(weld::toId(pKey), aEntry);
+ }
+ }
+}
+
+RTSDevicePage::~RTSDevicePage()
+{
+}
+
+sal_uLong RTSDevicePage::getDepth() const
+{
+ sal_uInt16 nSelectPos = m_xDepthBox->get_active();
+ if (nSelectPos == 0)
+ return 8;
+ else
+ return 24;
+}
+
+sal_uLong RTSDevicePage::getColorDevice() const
+{
+ sal_uInt16 nSelectPos = m_xSpaceBox->get_active();
+ switch (nSelectPos)
+ {
+ case 0:
+ return 0;
+ case 1:
+ return 1;
+ case 2:
+ return -1;
+ }
+ return 0;
+}
+
+IMPL_LINK(RTSDevicePage, ModifyHdl, weld::Entry&, rEdit, void)
+{
+ if (m_pCustomValue)
+ {
+ // tdf#123734 Custom PPD option values are a CUPS extension to PPDs and the user-set value
+ // needs to be prefixed with "Custom." in order to be processed properly
+ m_pCustomValue->m_aCustomOption = "Custom." + rEdit.get_text();
+ m_pCustomValue->m_bCustomOptionSetViaApp = true;
+ }
+}
+
+IMPL_LINK( RTSDevicePage, SelectHdl, weld::TreeView&, rBox, void )
+{
+ if (&rBox == m_xPPDKeyBox.get())
+ {
+ const PPDKey* pKey = weld::fromId<PPDKey*>(m_xPPDKeyBox->get_selected_id());
+ FillValueBox( pKey );
+ }
+ else if (&rBox == m_xPPDValueBox.get())
+ {
+ const PPDKey* pKey = weld::fromId<PPDKey*>(m_xPPDKeyBox->get_selected_id());
+ const PPDValue* pValue = weld::fromId<PPDValue*>(m_xPPDValueBox->get_selected_id());
+ if (pKey && pValue)
+ {
+ m_pParent->m_aJobData.m_aContext.setValue( pKey, pValue );
+ ValueBoxChanged(pKey);
+ }
+ }
+ m_pParent->SetDataModified( true );
+}
+
+IMPL_LINK_NOARG( RTSDevicePage, ComboChangedHdl, weld::ComboBox&, void )
+{
+ m_pParent->SetDataModified( true );
+}
+
+void RTSDevicePage::FillValueBox( const PPDKey* pKey )
+{
+ m_xPPDValueBox->clear();
+ m_xCustomEdit->hide();
+
+ if( ! pKey )
+ return;
+
+ const PPDValue* pValue = nullptr;
+ for( int i = 0; i < pKey->countValues(); i++ )
+ {
+ pValue = pKey->getValue( i );
+ if( m_pParent->m_aJobData.m_aContext.checkConstraints( pKey, pValue ) &&
+ m_pParent->m_aJobData.m_pParser )
+ {
+ OUString aEntry;
+ if (pValue->m_bCustomOption)
+ aEntry = VclResId(SV_PRINT_CUSTOM_TXT);
+ else
+ aEntry = m_pParent->m_aJobData.m_pParser->translateOption( pKey->getKey(), pValue->m_aOption);
+ m_xPPDValueBox->append(weld::toId(pValue), aEntry);
+ }
+ }
+ pValue = m_pParent->m_aJobData.m_aContext.getValue( pKey );
+ m_xPPDValueBox->select_id(weld::toId(pValue));
+
+ ValueBoxChanged(pKey);
+}
+
+IMPL_LINK_NOARG(RTSDevicePage, ImplHandleReselectHdl, Timer*, void)
+{
+ //in case selected entry is now not visible select it again to scroll it into view
+ m_xPPDValueBox->select(m_xPPDValueBox->get_selected_index());
+}
+
+void RTSDevicePage::ValueBoxChanged( const PPDKey* pKey )
+{
+ const PPDValue* pValue = m_pParent->m_aJobData.m_aContext.getValue(pKey);
+ if (pValue->m_bCustomOption)
+ {
+ m_pCustomValue = pValue;
+ m_pParent->m_aJobData.m_aContext.setValue(pKey, pValue);
+ // don't show the "Custom." prefix in the UI, s.a. comment in ModifyHdl
+ m_xCustomEdit->set_text(m_pCustomValue->m_aCustomOption.replaceFirst("Custom.", ""));
+ m_xCustomEdit->show();
+ m_aReselectCustomIdle.Start();
+ }
+ else
+ m_xCustomEdit->hide();
+}
+
+int SetupPrinterDriver(weld::Window* pParent, ::psp::PrinterInfo& rJobData)
+{
+ int nRet = 0;
+ RTSDialog aDialog(rJobData, pParent);
+
+ // return 0 if cancel was pressed or if the data
+ // weren't modified, 1 otherwise
+ if (aDialog.run() != RET_CANCEL)
+ {
+ rJobData = aDialog.getSetup();
+ nRet = aDialog.GetDataModified() ? 1 : 0;
+ }
+
+ return nRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/prtsetup.hxx b/vcl/unx/generic/print/prtsetup.hxx
new file mode 100644
index 0000000000..1a32eb3318
--- /dev/null
+++ b/vcl/unx/generic/print/prtsetup.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <vcl/idle.hxx>
+#include <vcl/weld.hxx>
+#include <ppdparser.hxx>
+#include <printerinfomanager.hxx>
+
+class RTSPaperPage;
+class RTSDevicePage;
+
+class RTSDialog : public weld::GenericDialogController
+{
+ friend class RTSPaperPage;
+ friend class RTSDevicePage;
+
+ ::psp::PrinterInfo m_aJobData;
+
+ bool m_bDataModified;
+
+ // controls
+ std::unique_ptr<weld::Notebook> m_xTabControl;
+ std::unique_ptr<weld::Button> m_xOKButton;
+ std::unique_ptr<weld::Button> m_xCancelButton;
+
+ // pages
+ std::unique_ptr<RTSPaperPage> m_xPaperPage;
+ std::unique_ptr<RTSDevicePage> m_xDevicePage;
+
+ DECL_LINK(ActivatePage, const OUString&, void);
+ DECL_LINK(ClickButton, weld::Button&, void);
+
+ // helper functions
+ void insertAllPPDValues(weld::ComboBox&, const psp::PPDParser*, const psp::PPDKey*);
+
+public:
+ RTSDialog(const ::psp::PrinterInfo& rJobData, weld::Window* pParent);
+ virtual ~RTSDialog() override;
+
+ const ::psp::PrinterInfo& getSetup() const { return m_aJobData; }
+
+ void SetDataModified(bool bModified) { m_bDataModified = bModified; }
+ bool GetDataModified() const { return m_bDataModified; }
+};
+
+class RTSPaperPage
+{
+private:
+ std::unique_ptr<weld::Builder> m_xBuilder;
+
+ RTSDialog* m_pParent;
+
+ std::unique_ptr<weld::Widget> m_xContainer;
+
+ std::unique_ptr<weld::CheckButton> m_xCbFromSetup;
+
+ std::unique_ptr<weld::Label> m_xPaperText;
+ std::unique_ptr<weld::ComboBox> m_xPaperBox;
+
+ std::unique_ptr<weld::Label> m_xOrientText;
+ std::unique_ptr<weld::ComboBox> m_xOrientBox;
+
+ std::unique_ptr<weld::Label> m_xDuplexText;
+ std::unique_ptr<weld::ComboBox> m_xDuplexBox;
+
+ std::unique_ptr<weld::Label> m_xSlotText;
+ std::unique_ptr<weld::ComboBox> m_xSlotBox;
+
+ DECL_LINK(SelectHdl, weld::ComboBox&, void);
+ DECL_LINK(CheckBoxHdl, weld::Toggleable&, void);
+
+public:
+ RTSPaperPage(weld::Widget* pPage, RTSDialog* pDialog);
+ ~RTSPaperPage();
+
+ void update();
+
+ sal_Int32 getOrientation() const { return m_xOrientBox->get_active(); }
+};
+
+class RTSDevicePage
+{
+private:
+ std::unique_ptr<weld::Builder> m_xBuilder;
+
+ const psp::PPDValue* m_pCustomValue;
+ RTSDialog* m_pParent;
+
+ std::unique_ptr<weld::Widget> m_xContainer;
+ std::unique_ptr<weld::TreeView> m_xPPDKeyBox;
+ std::unique_ptr<weld::TreeView> m_xPPDValueBox;
+ std::unique_ptr<weld::Entry> m_xCustomEdit;
+
+ std::unique_ptr<weld::ComboBox> m_xSpaceBox;
+ std::unique_ptr<weld::ComboBox> m_xDepthBox;
+
+ void FillValueBox(const ::psp::PPDKey*);
+ void ValueBoxChanged(const ::psp::PPDKey*);
+
+ Idle m_aReselectCustomIdle;
+
+ DECL_LINK(SelectHdl, weld::TreeView&, void);
+ DECL_LINK(ModifyHdl, weld::Entry&, void);
+ DECL_LINK(ComboChangedHdl, weld::ComboBox&, void);
+ DECL_LINK(ImplHandleReselectHdl, Timer*, void);
+
+public:
+ RTSDevicePage(weld::Widget* pPage, RTSDialog* pDialog);
+ ~RTSDevicePage();
+
+ sal_uLong getDepth() const;
+ sal_uLong getColorDevice() const;
+};
+
+int SetupPrinterDriver(weld::Window* pParent, ::psp::PrinterInfo& rJobData);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/psheader.ps b/vcl/unx/generic/print/psheader.ps
new file mode 100644
index 0000000000..49f0f51011
--- /dev/null
+++ b/vcl/unx/generic/print/psheader.ps
@@ -0,0 +1,363 @@
+%
+% This file is part of the LibreOffice project.
+%
+% This Source Code Form is subject to the terms of the Mozilla Public
+% License, v. 2.0. If a copy of the MPL was not distributed with this
+% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%
+% This file incorporates work covered by the following license notice:
+%
+% Licensed to the Apache Software Foundation (ASF) under one or more
+% contributor license agreements. See the NOTICE file distributed
+% with this work for additional information regarding copyright
+% ownership. The ASF licenses this file to you under the Apache
+% License, Version 2.0 (the "License"); you may not use this file
+% except in compliance with the License. You may obtain a copy of
+% the License at http://www.apache.org/licenses/LICENSE-2.0 .
+%
+
+% This is an "unobsfucated version of postscript header" in printerjob.cxx. It
+% was probably kept separate for the comments, but it is not used in itself
+% and probably was not kept in sync with the actual header.
+
+%
+%
+% readpath
+%
+% The intention of readpath is to save disk space since the vcl clip region routines
+% produce a huge amount of lineto/moveto commands
+%
+% The principal idea is to maintain the current point on stack and to provide only deltas
+% in the command. These deltas are added to the current point. The new point is used for
+% the lineto and moveto command and saved on stack for the next command.
+%
+% pathdict implements binary/hex representation of lineto and moveto commands.
+% The command consists of a 1byte opcode to switch between lineto and moveto and the size
+% of the following delta-x and delta-y values. The opcode is read with /rcmd, the two
+% coordinates are read with /rhex. The whole command is executed with /xcmd
+%
+%
+
+/pathdict dup 8 dict def load
+begin
+
+ % the command is of the bit format cxxyy
+ % with c=0 meaning lineto
+ % c=1 meaning moveto
+ % xx is a 2bit value for the number of bytes for x position
+ % yy is the same for y, values are off by one: 00 means 1; 11 means 4 !
+ % the command has been added to 'A' to be always in the ascii character
+ % range. the command is followed by 2*xx + 2*yy hexchars.
+ % '~' denotes the special case of EOD
+ /rcmd {
+ {
+ currentfile 1 string readstring % s bool
+ pop % s
+ 0 get % s[0]
+ % --- check whether s[0] is CR, LF ...
+ dup 32 gt % s > ' ' ? then read on
+ { exit }
+ { pop }
+ ifelse
+ }
+ loop
+
+ dup 126 eq { pop exit } if % -- Exit loop if cmd is '~'
+ 65 sub % cmd=s[0]-'A'
+ % -- Separate yy bits
+ dup 16#3 and 1 add % cmd yy
+ % -- Separate xx bits
+ exch % yy cmd
+ dup 16#C and -2 bitshift
+ 16#3 and 1 add exch % yy xx cmd
+ % -- Separate command bit
+ 16#10 and 16#10 eq % yy xx bool
+ 3 1 roll exch % bool xx yy
+ } def
+
+ % length rhex -- reads a signed hex value of given length
+ % the left most bit of char 0 is considered as the sign (0 means '+', 1 means '-')
+ % the rest of the bits is considered to be the abs value. Please note that this
+ % does not match the C binary representation of integers
+ /rhex {
+ dup 1 sub exch % l-1 l
+ currentfile exch string readhexstring % l-1 substring[l] bool
+ pop
+ dup 0 get dup % l-1 s s[0] s[0]
+ % -- Extract the sign
+ 16#80 and 16#80 eq dup % l-1 s s[0] sign=- sign=-
+ % -- Mask out the sign bit and put value back
+ 3 1 roll % l-1 s sign=- s[0] sign=-
+ { 16#7f and } if % l-1 s sign=- +s[0]
+ 2 index 0 % l-1 s sign=- +s[0] s 0
+ 3 -1 roll put % l-1 s sign=- s 0 +s[0]
+ % -- Read loop: add to prev sum, mul with 256
+ 3 1 roll 0 % sign=- l-1 s Sum=0
+ 0 1 5 -1 roll % sign=- s Sum=0 0 1 l-1
+ { % sign=- s Sum idx
+ 2 index exch % sign=- s Sum s idx
+ get % sign=- s Sum s[idx]
+ add 256 mul % sign=- s Sum=(s[idx]+Sum)*256
+ }
+ for
+ % -- mul was once too often, weave in the sign
+ 256 div % sign=- s Sum/256
+ exch pop % sign=- Sum/256
+ exch { neg } if % (sign=- ? -Sum : Sum)
+ } def
+
+ % execute a single command, the former x and y position is already on stack
+ % only offsets are read from cmdstring
+ /xcmd { % x y
+ rcmd % x y bool wx wy
+ exch rhex % x y bool wy Dx
+ exch rhex % x y bool Dx Dy
+ exch 5 -1 roll % y bool Dy Dx x
+ add exch % y bool X Dy
+ 4 -1 roll add % bool X Y
+ 1 index 1 index % bool X Y X Y
+ 5 -1 roll % X Y X Y bool
+ { moveto }
+ { lineto }
+ ifelse % X Y
+ } def
+end
+
+/readpath
+{
+ 0 0 % push initial-x initial-y
+ pathdict begin
+ { xcmd } loop
+ end
+ pop pop % pop final-x final-y
+} def
+
+%
+%
+% if languagelevel is not in the systemdict then its level 1 interpreter:
+% provide compatibility routines
+%
+%
+
+systemdict /languagelevel known not
+{
+ % string numarray xxshow -
+ % does only work for single byte fonts
+ /xshow {
+ exch dup % a s s
+ length 0 1 % a s l(s) 1 1
+ 3 -1 roll 1 sub % a s 0 1 l(s)-1
+ { % a s idx
+ dup % a s idx idx
+ % -- extract the delta offset
+ 3 index exch get % a s idx a[idx]
+ % -- extract the character
+ exch % a s a[idx] idx
+ 2 index exch get % a s a[idx] s[idx]
+ % -- create a tmp string for show
+ 1 string dup 0 % a s a[idx] s[idx] s1 s1 0
+ 4 -1 roll % a s a[idx] s1 s1 0 s[idx]
+ put % a s a[idx] s1
+ % -- store the current point
+ currentpoint 3 -1 roll % a s a[idx] x y s1
+ % -- draw the character
+ show % a s a[idx] x y
+ % -- move to the offset
+ moveto 0 rmoveto % a s
+ }
+ for
+ pop pop % -
+ } def
+
+ % x y width height rectfill
+ % x y width height rectshow
+ % in contrast to the languagelevel 2 operator
+ % they use and change the currentpath
+ /rectangle {
+ 4 -2 roll % width height x y
+ moveto % width height
+ 1 index 0 rlineto % width height % rmoveto(width, 0)
+ 0 exch rlineto % width % rmoveto(0, height)
+ neg 0 rlineto % - % rmoveto(-width, 0)
+ closepath
+ } def
+
+ /rectfill { rectangle fill } def
+ /rectstroke { rectangle stroke } def
+}
+if
+
+% -- small test program
+% 75 75 moveto /Times-Roman findfont 12 scalefont setfont
+% <292a2b2c2d2e2f30313233343536373839>
+% [5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 5] xshow <21>[0] xshow
+% showpage
+
+%
+%
+% shortcuts for image header with compression
+%
+%
+
+/psp_lzwfilter {
+ currentfile /ASCII85Decode filter /LZWDecode filter
+} def
+/psp_ascii85filter {
+ currentfile /ASCII85Decode filter
+} def
+/psp_lzwstring {
+ psp_lzwfilter 1024 string readstring
+} def
+/psp_ascii85string {
+ psp_ascii85filter 1024 string readstring
+} def
+/psp_imagedict {
+ /psp_bitspercomponent {
+ 3 eq
+ { 1 }
+ { 8 }
+ ifelse
+ } def
+ /psp_decodearray {
+ [ [0 1 0 1 0 1] [0 255] [0 1] [0 255] ] exch get
+ } def
+
+ 7 dict dup
+ /ImageType 1 put dup
+ /Width 7 -1 roll put dup
+ /Height 5 index put dup
+ /BitsPerComponent 4 index
+ psp_bitspercomponent put dup
+ /Decode 5 -1 roll
+ psp_decodearray put dup
+ /ImageMatrix [1 0 0 1 0 0] dup
+ 5 8 -1 roll put put dup
+ /DataSource 4 -1 roll
+ 1 eq
+ { psp_lzwfilter }
+ { psp_ascii85filter }
+ ifelse put
+} def
+
+
+%
+%
+% font encoding and reencoding
+%
+%
+
+/ISO1252Encoding [
+ /.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 /quotesingle
+ /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
+ /grave /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 /unused
+ /Euro /unused /quotesinglbase /florin /quotedblbase /ellipsis /dagger /daggerdbl
+ /circumflex /perthousand /Scaron /guilsinglleft /OE /unused /Zcaron /unused
+ /unused /quoteleft /quoteright /quotedblleft /quotedblright /bullet /endash /emdash
+ /tilde /trademark /scaron /guilsinglright /oe /unused /zcaron /Ydieresis
+ /space /exclamdown /cent /sterling /currency /yen /brokenbar /section
+ /dieresis /copyright /ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron
+ /degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /periodcentered
+ /cedilla /onesuperior /ordmasculine /guillemotright /onequarter /onehalf /threequarters /questiondown
+ /Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla
+ /Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis
+ /Eth /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply
+ /Oslash /Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /germandbls
+ /agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla
+ /egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis
+ /eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide
+ /oslash /ugrave /uacute /ucircumflex /udieresis /yacute /thorn /ydieresis
+] def
+
+% /fontname /encoding psp_findfont
+/psp_findfont {
+ exch dup % encoding fontname fontname
+ findfont % encoding fontname
+ dup length dict
+ begin
+ {
+ 1 index /FID ne
+ { def }
+ { pop pop }
+ ifelse
+ } forall
+ /Encoding 3 -1 roll def
+ currentdict
+ end
+ /psp_reencodedfont exch definefont
+} def
+
+% bshow shows a text in artificial bold
+% this is achieved by first showing the text
+% then stroking its outline over it with
+% the linewidth set to the second parameter
+% usage: (string) num bshow
+
+/bshow {
+ currentlinewidth % save current linewidth
+ 3 1 roll % move it to the last stack position
+ currentpoint % save the current point
+ 3 index % copy the string to show
+ show % show it
+ moveto % move to the original coordinates again
+ setlinewidth % set the linewidth
+ false charpath % create the outline path of the shown string
+ stroke % and stroke it
+ setlinewidth % reset the stored linewidth
+} def
+
+% bxshow shows a text with a delta array in artificial bold
+% that is it does what bshow does for show
+% usage: (string) [deltaarray] num bxshow
+
+/bxshow {
+ currentlinewidth % save linewidth
+ 4 1 roll % move it to the last stack position
+ setlinewidth % set the new linewidth
+ exch % exchange string and delta array
+ dup
+ length % get length of string
+ 1 sub % prepare parameters for {} for
+ 0 1
+ 3 -1 roll
+ {
+ 1 string % create a string object length 1
+ 2 index % get the text
+ 2 index % get charpos (for index variable)
+ get % have char value at charpos
+ 1 index % prepare string for put
+ exch
+ 0
+ exch
+ put % put into string of length 1
+ dup % duplicate the it
+ currentpoint % save current position
+ 3 -1 roll % prepare show
+ show % show the character
+ moveto % move back to beginning
+ currentpoint % save current position
+ 3 -1 roll % prepare outline path of character
+ false charpath
+ stroke % stroke it
+ moveto % move back
+ % now move to next point
+ 2 index % get advance array
+ exch % get charpos
+ get % get advance element
+ 0 rmoveto % advance current position
+ } for
+ pop pop % remove string and delta array
+ setlinewidth % restore linewidth
+} def
diff --git a/vcl/unx/generic/printer/configuration/README b/vcl/unx/generic/printer/configuration/README
new file mode 100644
index 0000000000..c39237a53f
--- /dev/null
+++ b/vcl/unx/generic/printer/configuration/README
@@ -0,0 +1,5 @@
+Contains ppds for use by vcl when not using CUPS
+
+This is used for the print-to-file functionality. These two PPDs
+describe the range of paper sizes and postscript options necessary for
+printing to postscript without a configured printer.
diff --git a/vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS b/vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS
new file mode 100644
index 0000000000..ed5882a593
--- /dev/null
+++ b/vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS
@@ -0,0 +1,582 @@
+*PPD-Adobe: "4.0"
+*%
+*% This file is part of the LibreOffice project.
+*%
+*% This Source Code Form is subject to the terms of the Mozilla Public
+*% License, v. 2.0. If a copy of the MPL was not distributed with this
+*% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*%
+*% This file incorporates work covered by the following license notice:
+*%
+*% Licensed to the Apache Software Foundation (ASF) under one or more
+*% contributor license agreements. See the NOTICE file distributed
+*% with this work for additional information regarding copyright
+*% ownership. The ASF licenses this file to you under the Apache
+*% License, Version 2.0 (the "License")*% you may not use this file
+*% except in compliance with the License. You may obtain a copy of
+*% the License at http://www.apache.org/licenses/LICENSE-2.0 .
+*%
+
+*% The user must print with a PostScript(R) emulator to non PostScript(R)
+*% printers if the system has no specific printer support. This file
+*% allows the user to print to most printers without any modification.
+*% Standard paper sizes and resolutions are defined. There are some
+*% additional definitions for screen or online documents in this file.
+*% To print to a PostScript(R) printer, use the specific PPD file.
+
+*% ===== General =====
+
+*FormatVersion: "4.0"
+*FileVersion: "1.0"
+*LanguageEncoding: ISOLatin1
+*LanguageVersion: English
+*PSVersion: "(1) 1"
+*Product: "(Generic Printer)"
+*ModelName: "Generic Printer"
+*NickName: "Generic Printer"
+*PCFileName: "SGENPRT.PPD"
+
+
+*% ===== Basic Capabilities and Defaults =====
+
+*ColorDevice: True
+*DefaultColorSpace: RGB
+*LanguageLevel: "2"
+*TTRasterizer: Type42
+
+*% --- For None Color or old PostScript(R) printers use following lines ---
+*% *ColorDevice: False
+*% *DefaultColorSpace: Gray
+*% *LanguageLevel: "1"
+
+*FreeVM: "8388608"
+*VariablePaperSize: True
+*FileSystem: False
+*Throughput: "8"
+*Password: "0"
+*ExitServer: "
+ count 0 eq % is the password on the stack?
+ { true }
+ { dup % potential password
+ statusdict /checkpassword get exec not
+ } ifelse
+ { % if no password or not valid
+ (WARNING : Cannot perform the exitserver command.) =
+ (Password supplied is not valid.) =
+ (Please contact the author of this software.) = flush
+ quit
+ } if
+ serverdict /exitserver get exec
+"
+*End
+*Reset: "
+ count 0 eq % is the password on the stack?
+ { true }
+ { dup % potential password
+ statusdict /checkpassword get exec not
+ } ifelse
+ { % if no password or not valid
+ (WARNING : Cannot reset printer.) =
+ (Password supplied is not valid.) =
+ (Please contact the author of this software.) = flush
+ quit
+ } if
+ serverdict /exitserver get exec
+ systemdict /quit get exec
+ (WARNING : Printer Reset Failed.) = flush
+"
+*End
+
+
+*DefaultResolution: 300dpi
+
+*ResScreenFreq 72dpi: "60.0"
+*ResScreenFreq 144dpi: "60.0"
+*ResScreenFreq 300dpi: "60.0"
+*ResScreenFreq 360dpi: "60.0"
+*ResScreenFreq 600dpi: "60.0"
+*ResScreenFreq 720dpi: "60.0"
+*ResScreenFreq 1200dpi: "60.0"
+*ResScreenFreq 1440dpi: "60.0"
+*ResScreenFreq 2400dpi: "60.0"
+*ResScreenAngle 72dpi: "45.0"
+*ResScreenAngle 144dpi: "45.0"
+*ResScreenAngle 300dpi: "45.0"
+*ResScreenAngle 360dpi: "45.0"
+*ResScreenAngle 600dpi: "45.0"
+*ResScreenAngle 720dpi: "45.0"
+*ResScreenAngle 1200dpi: "45.0"
+*ResScreenAngle 1440dpi: "45.0"
+*ResScreenAngle 2400dpi: "45.0"
+
+
+*% ===== Halftone =====
+
+*ContoneOnly: False
+*DefaultHalftoneType: 1
+*ScreenFreq: "60.0"
+*ScreenAngle: "45.0"
+*DefaultScreenProc: Dot
+*ScreenProc Dot: "
+ { abs exch abs 2 copy add 1 gt {1 sub dup mul exch 1 sub
+ dup mul add 1 sub } { dup mul exch dup mul add 1 exch sub }
+ ifelse } bind
+"
+*End
+*ScreenProc Line: "{ exch pop abs neg } bind"
+*ScreenProc Ellipse: "
+ { abs exch abs 2 copy mul exch 4 mul add 3 sub dup 0
+ lt { pop dup mul exch .75 div dup mul add 4 div 1 exch sub }
+ { dup 1 gt { pop 1 exch sub dup mul exch 1 exch sub .75 div
+ dup mul add 4 div 1 sub }
+ { .5 exch sub exch pop exch pop } ifelse } ifelse } bind
+"
+*End
+*ScreenProc Cross: "{ abs exch abs 2 copy gt { exch } if pop neg } bind"
+
+*DefaultTransfer: Null
+*Transfer Null: "{ } bind"
+*Transfer Null.Inverse: "{ 1 exch sub } bind"
+
+
+*% ===== Paper =====
+
+*OpenUI *PageSize: PickOne
+*OrderDependency: 30 AnySetup *PageSize
+*DefaultPageSize: Letter
+*PageSize A0: "<</PageSize [2384 3370] /ImagingBBox null>> setpagedevice"
+*PageSize A1: "<</PageSize [1684 2384] /ImagingBBox null>> setpagedevice"
+*PageSize A2: "<</PageSize [1191 1684] /ImagingBBox null>> setpagedevice"
+*PageSize A3: "<</PageSize [842 1191] /ImagingBBox null>> setpagedevice"
+*PageSize A4: "<</PageSize [595 842] /ImagingBBox null>> setpagedevice"
+*PageSize A5: "<</PageSize [420 595] /ImagingBBox null>> setpagedevice"
+*PageSize A6: "<</PageSize [297 420] /ImagingBBox null>> setpagedevice"
+*PageSize B4: "<</PageSize [728 1032] /ImagingBBox null>> setpagedevice"
+*PageSize B5: "<</PageSize [516 729] /ImagingBBox null>> setpagedevice"
+*PageSize B6: "<</PageSize [363 516] /ImagingBBox null>> setpagedevice"
+*PageSize Legal/US Legal: "<</PageSize [612 1008] /ImagingBBox null>> setpagedevice"
+*PageSize Letter/US Letter: "<</PageSize [612 792] /ImagingBBox null>> setpagedevice"
+*PageSize Executive: "<</PageSize [522 756] /ImagingBBox null>> setpagedevice"
+*PageSize Statement: "<</PageSize [396 612] /ImagingBBox null>> setpagedevice"
+*PageSize Tabloid/US Tabloid: "<</PageSize [792 1224] /ImagingBBox null>> setpagedevice"
+*PageSize Ledger/Ledger Landscape: "<</PageSize [1224 792] /ImagingBBox null>> setpagedevice"
+*PageSize AnsiC/US C: "<</PageSize [1224 1584] /ImagingBBox null>> setpagedevice"
+*PageSize AnsiD/US D: "<</PageSize [1584 2448] /ImagingBBox null>> setpagedevice"
+*PageSize AnsiE/US E: "<</PageSize [2448 3168] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHA/ARCH A: "<</PageSize [648 864] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHB/ARCH B: "<</PageSize [864 1296] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHC/ARCH C: "<</PageSize [1296 1728] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHD/ARCH D: "<</PageSize [1728 2592] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHE/ARCH E: "<</PageSize [2592 3456] /ImagingBBox null>> setpagedevice"
+*PageSize EnvMonarch/Monarch Envelope: "<</PageSize [279 540] /ImagingBBox null>> setpagedevice"
+*PageSize EnvDL/DL Envelope: "<</PageSize [312 624] /ImagingBBox null>> setpagedevice"
+*PageSize EnvC4/C4 Envelope: "<</PageSize [649 918] /ImagingBBox null>> setpagedevice"
+*PageSize EnvC5/C5 Envelope: "<</PageSize [459 649] /ImagingBBox null>> setpagedevice"
+*PageSize EnvC6/C6 Envelope: "<</PageSize [323 459] /ImagingBBox null>> setpagedevice"
+*PageSize Env10/C10 Envelope: "<</PageSize [297 684] /ImagingBBox null>> setpagedevice"
+*PageSize EnvC65/C65 Envelope: "<</PageSize [323 649] /ImagingBBox null>> setpagedevice"
+*PageSize Folio: "<</PageSize [595 935] /ImagingBBox null>> setpagedevice"
+*?PageSize: "
+ save
+ currentpagedevice /PageSize get aload pop
+ 2 copy gt {exch} if
+ (Unknown)
+ 32 dict
+ dup [2384 3370] (A0) put
+ dup [1684 2384] (A1) put
+ dup [1191 1684] (A2) put
+ dup [842 1191] (A3) put
+ dup [595 842] (A4) put
+ dup [420 595] (A5) put
+ dup [297 420] (A6) put
+ dup [728 1032] (B4) put
+ dup [516 729] (B5) put
+ dup [363 516] (B6) put
+ dup [612 1008] (Legal) put
+ dup [612 792] (Letter) put
+ dup [522 756] (Executive) put
+ dup [396 612] (Statement) put
+ dup [792 1224] (Tabloid) put
+ dup [1224 792] (Ledger) put
+ dup [1224 1584] (AnsiC) put
+ dup [1584 2448] (AnsiD) put
+ dup [2448 3168] (AnsiE) put
+ dup [648 864] (ARCHA) put
+ dup [864 1296] (ARCHB) put
+ dup [1296 1728] (ARCHC) put
+ dup [1728 2592] (ARCHD) put
+ dup [2592 3456] (ARCHE) put
+ dup [279 540] (EnvMonarch) put
+ dup [312 624] (EnvDL) put
+ dup [649 918] (EnvC4) put
+ dup [459 649] (EnvC5) put
+ dup [323 459] (EnvC6) put
+ dup [297 684] (Env10) put
+ dup [323 649] (EnvC65) put
+ dup [595 935] (Folio) put
+ { exch aload pop 4 index sub abs 5 le exch
+ 5 index sub abs 5 le and
+ { exch pop exit } { pop } ifelse
+ } bind forall
+ = flush pop pop
+ restore
+"
+*End
+*CloseUI: *PageSize
+
+*OpenUI *PageRegion: PickOne
+*OrderDependency: 40 AnySetup *PageRegion
+*DefaultPageRegion: Letter
+*PageRegion A0: "<</PageSize [2384 3370] /ImagingBBox null>> setpagedevice"
+*PageRegion A1: "<</PageSize [1684 2384] /ImagingBBox null>> setpagedevice"
+*PageRegion A2: "<</PageSize [1191 1684] /ImagingBBox null>> setpagedevice"
+*PageRegion A3: "<</PageSize [842 1191] /ImagingBBox null>> setpagedevice"
+*PageRegion A4: "<</PageSize [595 842] /ImagingBBox null>> setpagedevice"
+*PageRegion A5: "<</PageSize [420 595] /ImagingBBox null>> setpagedevice"
+*PageRegion A6: "<</PageSize [297 420] /ImagingBBox null>> setpagedevice"
+*PageRegion B4: "<</PageSize [728 1032] /ImagingBBox null>> setpagedevice"
+*PageRegion B5: "<</PageSize [516 729] /ImagingBBox null>> setpagedevice"
+*PageRegion B6: "<</PageSize [363 516] /ImagingBBox null>> setpagedevice"
+*PageRegion Legal/US Legal: "<</PageSize [612 1008] /ImagingBBox null>> setpagedevice"
+*PageRegion Letter/US Letter: "<</PageSize [612 792] /ImagingBBox null>> setpagedevice"
+*PageRegion Executive: "<</PageSize [522 756] /ImagingBBox null>> setpagedevice"
+*PageRegion Statement: "<</PageSize [396 612] /ImagingBBox null>> setpagedevice"
+*PageRegion Tabloid/US Tabloid: "<</PageSize [792 1224] /ImagingBBox null>> setpagedevice"
+*PageRegion Ledger/Ledger Landscape: "<</PageSize [1224 792] /ImagingBBox null>> setpagedevice"
+*PageRegion AnsiC/US C: "<</PageSize [1224 1584] /ImagingBBox null>> setpagedevice"
+*PageRegion AnsiD/US D: "<</PageSize [1584 2448] /ImagingBBox null>> setpagedevice"
+*PageRegion AnsiE/US E: "<</PageSize [2448 3168] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHA/ARCH A: "<</PageSize [648 864] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHB/ARCH B: "<</PageSize [864 1296] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHC/ARCH C: "<</PageSize [1296 1728] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHD/ARCH D: "<</PageSize [1728 2592] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHE/ARCH E: "<</PageSize [2592 3456] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvMonarch/Monarch Envelope: "<</PageSize [279 540] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvDL/DL Envelope: "<</PageSize [312 624] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvC4/C4 Envelope: "<</PageSize [649 918] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvC5/C5 Envelope: "<</PageSize [459 649] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvC6/C6 Envelope: "<</PageSize [323 459] /ImagingBBox null>> setpagedevice"
+*PageRegion Env10/C10 Envelope: "<</PageSize [297 684] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvC65/C65 Envelope: "<</PageSize [323 649] /ImagingBBox null>> setpagedevice"
+*PageRegion Folio: "<</PageSize [595 935] /ImagingBBox null>> setpagedevice"
+*CloseUI: *PageRegion
+
+*DefaultImageableArea: Letter
+*ImageableArea A0: "0 0 2384 3370"
+*ImageableArea A1: "0 0 1684 2384"
+*ImageableArea A2: "0 0 1191 1684"
+*ImageableArea A3: "18 18 824 1173"
+*ImageableArea A4: "18 18 577 824"
+*ImageableArea A5: "18 18 402 577"
+*ImageableArea A6: "18 18 279 402"
+*ImageableArea B4: "18 18 710 1014"
+*ImageableArea B5: "18 18 498 711"
+*ImageableArea B6: "18 18 345 498"
+*ImageableArea Legal: "18 18 594 990"
+*ImageableArea Letter: "18 18 594 774"
+*ImageableArea Executive: "18 18 504 738"
+*ImageableArea Statement: "18 18 378 594"
+*ImageableArea Tabloid: "18 18 774 1206"
+*ImageableArea Ledger: "18 18 1206 774"
+*ImageableArea AnsiC: "0 0 1224 1584"
+*ImageableArea AnsiD: "0 0 1584 2448"
+*ImageableArea AnsiE: "0 0 2448 3168"
+*ImageableArea ARCHA: "0 0 648 864"
+*ImageableArea ARCHB: "0 0 864 1296"
+*ImageableArea ARCHC: "0 0 1296 1728"
+*ImageableArea ARCHD: "0 0 1728 2592"
+*ImageableArea ARCHE: "0 0 2592 3456"
+*ImageableArea EnvMonarch: "0 0 279 540"
+*ImageableArea EnvDL: "0 0 312 624"
+*ImageableArea EnvC4: "0 0 649 918"
+*ImageableArea EnvC5: "0 0 459 649"
+*ImageableArea EnvC6: "0 0 323 459"
+*ImageableArea Env10: "0 0 297 684"
+*ImageableArea EnvC65: "0 0 323 649"
+*ImageableArea Folio: "0 0 595 935"
+
+*DefaultPaperDimension: Letter
+*PaperDimension A0: "2384 3370"
+*PaperDimension A1: "1684 2384"
+*PaperDimension A2: "1191 1684"
+*PaperDimension A3: "842 1191"
+*PaperDimension A4: "595 842"
+*PaperDimension A5: "420 595"
+*PaperDimension A6: "297 420"
+*PaperDimension B4: "728 1032"
+*PaperDimension B5: "516 729"
+*PaperDimension B6: "363 516"
+*PaperDimension Legal: "612 1008"
+*PaperDimension Letter: "612 792"
+*PaperDimension Executive: "522 756"
+*PaperDimension Statement: "396 612"
+*PaperDimension Tabloid: "792 1224"
+*PaperDimension Ledger: "1224 792"
+*PaperDimension AnsiC: "1224 1584"
+*PaperDimension AnsiD: "1584 2448"
+*PaperDimension AnsiE: "2448 3168"
+*PaperDimension ARCHA: "648 864"
+*PaperDimension ARCHB: "864 1296"
+*PaperDimension ARCHC: "1296 1728"
+*PaperDimension ARCHD: "1728 2592"
+*PaperDimension ARCHE: "2592 3456"
+*PaperDimension EnvMonarch: "279 540"
+*PaperDimension EnvDL: "312 624"
+*PaperDimension EnvC4: "649 918"
+*PaperDimension EnvC5: "459 649"
+*PaperDimension EnvC6: "323 459"
+*PaperDimension Env10: "297 684"
+*PaperDimension EnvC65: "323 649"
+*PaperDimension Folio: "595 935"
+
+*% ===== Duplex =====
+*OpenUI *Duplex/Duplex: PickOne
+*OrderDependency: 30 AnySetup *Duplex
+*DefaultDuplex: Simplex
+*Duplex Simplex: ""
+*Duplex None/Off: "
+<</Duplex false /Tumble false
+ /Policies << /Duplex 1 /Tumble 1 >>
+>> setpagedevice"
+*Duplex DuplexNoTumble/Long edge:"
+<</Duplex true /Tumble false
+ /Policies << /Duplex 1 /Tumble 1 >>
+>> setpagedevice"
+*Duplex DuplexTumble/Short edge:"
+<</Duplex true /Tumble true
+ /Policies << /Duplex 1 /Tumble 1 >>
+>> setpagedevice"
+*End
+*CloseUI: *Duplex
+
+*% ===== ManualFeed ===
+*OpenUI *ManualFeed/Manual Feed: Boolean
+*OrderDependency: 15 AnySetup *ManualFeed
+*DefaultManualFeed: False
+*ManualFeed False: "
+<< /ManualFeed false /Policies << /ManualFeed 1 >> >> setpagedevice"
+*ManualFeed True: "
+<< /ManualFeed true /Policies << /ManualFeed 1 >> >> setpagedevice"
+*End
+*CloseUI: *ManualFeed
+
+*% ===== Fonts =====
+
+*DefaultFont: Courier
+*Font AvantGarde-Book: Standard "(001.002)" Standard ROM
+*Font AvantGarde-BookOblique: Standard "(001.000)" Standard ROM
+*Font AvantGarde-Demi: Standard "(001.000)" Standard ROM
+*Font AvantGarde-DemiOblique: Standard "(001.000)" Standard ROM
+*Font Bookman-Demi: Standard "(001.000)" Standard ROM
+*Font Bookman-DemiItalic: Standard "(001.000)" Standard ROM
+*Font Bookman-Light: Standard "(001.000)" Standard ROM
+*Font Bookman-LightItalic: Standard "(001.000)" Standard ROM
+*Font Courier: Standard "(001.000)" Standard ROM
+*Font Courier-Bold: Standard "(001.000)" Standard ROM
+*Font Courier-BoldOblique: Standard "(001.000)" Standard ROM
+*Font Courier-Oblique: Standard "(001.000)" Standard ROM
+*Font Helvetica: Standard "(001.000)" Standard ROM
+*Font Helvetica-Bold: Standard "(001.000)" Standard ROM
+*Font Helvetica-BoldOblique: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow-Bold: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow-BoldOblique: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow-Oblique: Standard "(001.000)" Standard ROM
+*Font Helvetica-Oblique: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-Bold: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-BoldItalic: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-Italic: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-Roman: Standard "(001.000)" Standard ROM
+*Font Palatino-Bold: Standard "(001.000)" Standard ROM
+*Font Palatino-BoldItalic: Standard "(001.000)" Standard ROM
+*Font Palatino-Italic: Standard "(001.000)" Standard ROM
+*Font Palatino-Roman: Standard "(001.000)" Standard ROM
+*Font Symbol: Special "(001.001)" Special ROM
+*Font Times-Bold: Standard "(001.000)" Standard ROM
+*Font Times-BoldItalic: Standard "(001.000)" Standard ROM
+*Font Times-Italic: Standard "(001.000)" Standard ROM
+*Font Times-Roman: Standard "(001.000)" Standard ROM
+*Font ZapfChancery-MediumItalic: Standard "(001.000)" Standard ROM
+*Font ZapfDingbats: Special "(001.000)" Special ROM
+*?FontQuery: "
+ save
+ {
+ count 1 gt
+ {
+ exch dup 127 string cvs (/) print print (:) print
+ /Font resourcestatus {pop pop (Yes)} {(No)} ifelse =
+ }
+ { exit } ifelse
+ } bind loop
+ (*) = flush
+ restore
+"
+*End
+
+*?FontList: "
+ save
+ (*) {cvn ==} 128 string /Font resourceforall
+ (*) = flush
+ restore
+"
+*End
+
+
+*% === Printer Messages ===
+
+*Message: "%%[ exitserver: permanent state may be changed ]%%"
+*Message: "%%[ Flushing: rest of job (to end-of-file) will be ignored ]%%"
+*Message: "\FontName\ not found, using Courier"
+
+*% Status (format: %%[ status: <one of these> %%] )
+*Status: "idle"
+*Status: "busy"
+*Status: "waiting"
+*Status: "printing"
+*Status: "PrinterError: timeout, clearing printer"
+*Status: "PrinterError: paper entry misfeed"
+*Status: "PrinterError: warming up"
+*Status: "PrinterError: service call"
+*Status: "PrinterError: no toner cartridge"
+*Status: "PrinterError: no paper tray"
+*Status: "PrinterError: cover open"
+*Status: "PrinterError: resetting printer"
+*Status: "PrinterError: out of paper"
+*Status: "PrinterError: timeout"
+*Status: "PrinterError: manual feed timeout"
+
+*% Input Sources (format: %%[ status: <stat>; source: <one of these>]%% )
+
+*% Printer Error (format: %%[ PrinterError: <one of these>]%%)
+*PrinterError: "timeout, clearing printer"
+*PrinterError: "paper entry misfeed"
+*PrinterError: "warming up"
+*PrinterError: "service call"
+*PrinterError: "no toner cartridge"
+*PrinterError: "no paper tray"
+*PrinterError: "cover open"
+*PrinterError: "resetting printer"
+*PrinterError: "out of paper"
+*PrinterError: "timeout"
+*PrinterError: "manual feed timeout"
+
+
+*% ===== Color Separation =====
+
+*DefaultColorSep: ProcessBlack.60lpi.300dpi/60 lpi / 300 dpi
+*InkName: ProcessBlack/Process Black
+*InkName: CustomColor/Custom Color
+*InkName: ProcessCyan/Process Cyan
+*InkName: ProcessMagenta/Process Magenta
+*InkName: ProcessYellow/Process Yellow
+
+*% --- For 60 lpi / 72 dpi ---
+*ColorSepScreenAngle ProcessBlack.60lpi.72dpi/60 lpi / 72 dpi: "45"
+*ColorSepScreenAngle CustomColor.60lpi.72dpi/60 lpi / 72 dpi: "45"
+*ColorSepScreenAngle ProcessCyan.60lpi.72dpi/60 lpi / 72 dpi: "15"
+*ColorSepScreenAngle ProcessMagenta.60lpi.72dpi/60 lpi / 72 dpi: "75"
+*ColorSepScreenAngle ProcessYellow.60lpi.72dpi/60 lpi / 72 dpi: "0"
+*ColorSepScreenFreq ProcessBlack.60lpi.72dpi/60 lpi / 72 dpi: "60"
+*ColorSepScreenFreq CustomColor.60lpi.72dpi/60 lpi / 72 dpi: "60"
+*ColorSepScreenFreq ProcessCyan.60lpi.72dpi/60 lpi / 72 dpi: "60"
+*ColorSepScreenFreq ProcessMagenta.60lpi.72dpi/60 lpi / 72 dpi: "60"
+*ColorSepScreenFreq ProcessYellow.60lpi.72dpi/60 lpi / 72 dpi: "60"
+
+*% --- For 60 lpi / 144 dpi ---
+*ColorSepScreenAngle ProcessBlack.60lpi.144dpi/60 lpi / 144 dpi: "45"
+*ColorSepScreenAngle CustomColor.60lpi.144dpi/60 lpi / 144 dpi: "45"
+*ColorSepScreenAngle ProcessCyan.60lpi.144dpi/60 lpi / 144 dpi: "15"
+*ColorSepScreenAngle ProcessMagenta.60lpi.144dpi/60 lpi / 144 dpi: "75"
+*ColorSepScreenAngle ProcessYellow.60lpi.144dpi/60 lpi / 144 dpi: "0"
+*ColorSepScreenFreq ProcessBlack.60lpi.144dpi/60 lpi / 144 dpi: "60"
+*ColorSepScreenFreq CustomColor.60lpi.144dpi/60 lpi / 144 dpi: "60"
+*ColorSepScreenFreq ProcessCyan.60lpi.144dpi/60 lpi / 144 dpi: "60"
+*ColorSepScreenFreq ProcessMagenta.60lpi.144dpi/60 lpi / 144 dpi: "60"
+*ColorSepScreenFreq ProcessYellow.60lpi.144dpi/60 lpi / 144 dpi: "60"
+
+*% --- For 60 lpi / 300 dpi ---
+*ColorSepScreenAngle ProcessBlack.60lpi.300dpi/60 lpi / 300 dpi: "45"
+*ColorSepScreenAngle CustomColor.60lpi.300dpi/60 lpi / 300 dpi: "45"
+*ColorSepScreenAngle ProcessCyan.60lpi.300dpi/60 lpi / 300 dpi: "15"
+*ColorSepScreenAngle ProcessMagenta.60lpi.300dpi/60 lpi / 300 dpi: "75"
+*ColorSepScreenAngle ProcessYellow.60lpi.300dpi/60 lpi / 300 dpi: "0"
+*ColorSepScreenFreq ProcessBlack.60lpi.300dpi/60 lpi / 300 dpi: "60"
+*ColorSepScreenFreq CustomColor.60lpi.300dpi/60 lpi / 300 dpi: "60"
+*ColorSepScreenFreq ProcessCyan.60lpi.300dpi/60 lpi / 300 dpi: "60"
+*ColorSepScreenFreq ProcessMagenta.60lpi.300dpi/60 lpi / 300 dpi: "60"
+*ColorSepScreenFreq ProcessYellow.60lpi.300dpi/60 lpi / 300 dpi: "60"
+
+*% --- For 60 lpi / 360 dpi ---
+*ColorSepScreenAngle ProcessBlack.60lpi.360dpi/60 lpi / 360 dpi: "45"
+*ColorSepScreenAngle CustomColor.60lpi.360dpi/60 lpi / 360 dpi: "45"
+*ColorSepScreenAngle ProcessCyan.60lpi.360dpi/60 lpi / 360 dpi: "15"
+*ColorSepScreenAngle ProcessMagenta.60lpi.360dpi/60 lpi / 360 dpi: "75"
+*ColorSepScreenAngle ProcessYellow.60lpi.360dpi/60 lpi / 360 dpi: "0"
+*ColorSepScreenFreq ProcessBlack.60lpi.360dpi/60 lpi / 360 dpi: "60"
+*ColorSepScreenFreq CustomColor.60lpi.360dpi/60 lpi / 360 dpi: "60"
+*ColorSepScreenFreq ProcessCyan.60lpi.360dpi/60 lpi / 360 dpi: "60"
+*ColorSepScreenFreq ProcessMagenta.60lpi.360dpi/60 lpi / 360 dpi: "60"
+*ColorSepScreenFreq ProcessYellow.60lpi.360dpi/60 lpi / 360 dpi: "60"
+
+*% --- For 71 lpi / 600 dpi ---
+*ColorSepScreenAngle ProcessBlack.71lpi.600dpi/71 lpi / 600 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.71lpi.600dpi/71 lpi / 600 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.71lpi.600dpi/71 lpi / 600 dpi: "71.5651"
+*ColorSepScreenAngle ProcessMagenta.71lpi.600dpi/71 lpi / 600 dpi: "18.4349"
+*ColorSepScreenAngle ProcessYellow.71lpi.600dpi/71 lpi / 600 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.71lpi.600dpi/71 lpi / 600 dpi: "70.7107"
+*ColorSepScreenFreq CustomColor.71lpi.600dpi/71 lpi / 600 dpi: "70.7107"
+*ColorSepScreenFreq ProcessCyan.71lpi.600dpi/71 lpi / 600 dpi: "63.2456"
+*ColorSepScreenFreq ProcessMagenta.71lpi.600dpi/71 lpi / 600 dpi: "63.2456"
+*ColorSepScreenFreq ProcessYellow.71lpi.600dpi/71 lpi / 600 dpi: "66.6667"
+
+*% --- For 71 lpi / 720 dpi ---
+*ColorSepScreenAngle ProcessBlack.71lpi.720dpi/71 lpi / 720 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.71lpi.720dpi/71 lpi / 720 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.71lpi.720dpi/71 lpi / 720 dpi: "71.5651"
+*ColorSepScreenAngle ProcessMagenta.71lpi.720dpi/71 lpi / 720 dpi: "18.4349"
+*ColorSepScreenAngle ProcessYellow.71lpi.720dpi/71 lpi / 720 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.71lpi.720dpi/71 lpi / 720 dpi: "70.7107"
+*ColorSepScreenFreq CustomColor.71lpi.720dpi/71 lpi / 720 dpi: "70.7107"
+*ColorSepScreenFreq ProcessCyan.71lpi.720dpi/71 lpi / 720 dpi: "63.2456"
+*ColorSepScreenFreq ProcessMagenta.71lpi.720dpi/71 lpi / 720 dpi: "63.2456"
+*ColorSepScreenFreq ProcessYellow.71lpi.720dpi/71 lpi / 720 dpi: "66.6667"
+
+*% --- For 100 lpi / 1200 dpi ---
+*ColorSepScreenAngle ProcessBlack.100lpi.1200dpi/100 lpi / 1200 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.100lpi.1200dpi/100 lpi / 1200 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.100lpi.1200dpi/100 lpi / 1200 dpi: "15.0"
+*ColorSepScreenAngle ProcessMagenta.100lpi.1200dpi/100 lpi / 1200 dpi: "75.0"
+*ColorSepScreenAngle ProcessYellow.100lpi.1200dpi/100 lpi / 1200 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+*ColorSepScreenFreq CustomColor.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+*ColorSepScreenFreq ProcessCyan.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+*ColorSepScreenFreq ProcessMagenta.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+*ColorSepScreenFreq ProcessYellow.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+
+*% --- For 100 lpi / 1440 dpi ---
+*ColorSepScreenAngle ProcessBlack.100lpi.1440dpi/100 lpi / 1440 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.100lpi.1440dpi/100 lpi / 1440 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.100lpi.1440dpi/100 lpi / 1440 dpi: "15.0"
+*ColorSepScreenAngle ProcessMagenta.100lpi.1440dpi/100 lpi / 1440 dpi: "75.0"
+*ColorSepScreenAngle ProcessYellow.100lpi.1440dpi/100 lpi / 1440 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+*ColorSepScreenFreq CustomColor.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+*ColorSepScreenFreq ProcessCyan.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+*ColorSepScreenFreq ProcessMagenta.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+*ColorSepScreenFreq ProcessYellow.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+
+*% --- For 175 lpi / 2400 dpi ---
+*ColorSepScreenAngle ProcessBlack.175lpi.2400dpi/175 lpi / 2400 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.175lpi.2400dpi/175 lpi / 2400 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.175lpi.2400dpi/175 lpi / 2400 dpi: "15.0"
+*ColorSepScreenAngle ProcessMagenta.175lpi.2400dpi/175 lpi / 2400 dpi: "75.0"
+*ColorSepScreenAngle ProcessYellow.175lpi.2400dpi/175 lpi / 2400 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+*ColorSepScreenFreq CustomColor.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+*ColorSepScreenFreq ProcessCyan.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+*ColorSepScreenFreq ProcessMagenta.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+*ColorSepScreenFreq ProcessYellow.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+
+*% Last Edit Date: March 24 2000
+*% end of PPD file
diff --git a/vcl/unx/generic/printer/configuration/psprint.conf b/vcl/unx/generic/printer/configuration/psprint.conf
new file mode 100644
index 0000000000..015a310d62
--- /dev/null
+++ b/vcl/unx/generic/printer/configuration/psprint.conf
@@ -0,0 +1,94 @@
+;
+; This file is part of the LibreOffice project.
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;
+; This file incorporates work covered by the following license notice:
+;
+; Licensed to the Apache Software Foundation (ASF) under one or more
+; contributor license agreements. See the NOTICE file distributed
+; with this work for additional information regarding copyright
+; ownership. The ASF licenses this file to you under the Apache
+; License, Version 2.0 (the "License"); you may not use this file
+; except in compliance with the License. You may obtain a copy of
+; the License at http://www.apache.org/licenses/LICENSE-2.0 .
+;
+[__Global_Printer_Defaults__]
+; Copies: the default number of copies produced
+; if key is absent the default is 1
+; Copies=1
+
+; Orientation: the default orientation of pages
+; possible Values: Portrait, Landscape
+; if key is absent the default is Portrait
+; Orientation=Portrait
+
+; Scale: the default scaling of output in percent
+; if key is absent the default is 100
+; Scale=100
+
+; MarginAdjust: the default adjustment to driver margins in 1/100 mm
+; MarginAdjust contains corrections for the driver defined margins
+; the values are comma separated
+; the order is: left,right,top,bottom
+; if key is absent the default is 0,0,0,0
+; MarginAdjust=0,0,0,0
+
+; ColorDepth: the default colordepth of the device in bits
+; possible values: 1, 8, 24
+; if key is absent the default is 24
+; ColorDepth=24
+
+; ColorDevice: the default setting whether the device is color capable
+; possible values: 0: driver setting, -1: grey scale, 1: color
+; if key is absent the default is 0
+; ColorDepth=0
+
+; PPD_PageSize: the default page size to use. If a specific printer does
+; not support this page size its default is used instead.
+; possible values: A0, A1, A2, A3, A4, A5, A6, B4, B5, B6,
+; Legal, Letter, Executive, Statement, Tabloid,
+; Ledger, AnsiC, AnsiD, ARCHA, ARCHB, ARCHC,
+; ARCHD, ARCHE, EnvMonarch, EnvC4, EnvC5, EnvC6,
+; Env10, EnvC65, Folio
+; if key is absent the default value is driver specific
+; PPD_PageSize=A4
+
+
+[Generic Printer]
+; for every printer a group with at least the keys
+; "Printer" and "Command" is required
+
+; Printer: contains the base name of the PPD and the Printer name separated by /
+Printer=SGENPRT/Generic Printer
+
+; DefaultPrinter: marks the default printer
+DefaultPrinter=1
+
+; Location: a user readable string that will be shown in the print dialog
+Location=
+
+; Comment: a user readable string that will be shown in the print dialog
+Comment=
+
+; Command: a command line that accepts PostScript as standard input (pipe)
+; note: a shell will be started for the command
+Command=
+
+; QuickCommand: a command line that accepts PostScript as standard input (pipe)
+; this command line will be used instead of the command line given in the
+; "Command" key, if the user presses the direct print button. In this case
+; no print dialog should be shown, neither from the printing application nor
+; from the command line (example "kprinter --nodialog --stdin")
+; note: a shell will be started for the command
+;QuickCommand=
+
+; Features: a string containing additional comma separated properties of a printer
+; currently valid properties:
+; fax for a Fax printer queue
+; pdf=<dir> for a PDF printer where <dir> is the base directory for output files
+; external_dialog to notify that the print command of a printer will show a dialog
+; and therefore the application should not show its own dialog.
+;Features=
diff --git a/vcl/unx/generic/printer/cpdmgr.cxx b/vcl/unx/generic/printer/cpdmgr.cxx
new file mode 100644
index 0000000000..834c1383ef
--- /dev/null
+++ b/vcl/unx/generic/printer/cpdmgr.cxx
@@ -0,0 +1,756 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <unistd.h>
+
+#include <unx/cpdmgr.hxx>
+
+#include <osl/file.h>
+#include <osl/thread.h>
+
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+
+#include <config_dbus.h>
+#include <config_gio.h>
+
+using namespace psp;
+using namespace osl;
+
+#if ENABLE_DBUS && ENABLE_GIO
+// Function to execute when name is acquired on the bus
+void CPDManager::onNameAcquired (GDBusConnection *connection,
+ const gchar *,
+ gpointer user_data)
+{
+ gchar* contents;
+ // Get Interface for introspection
+ if (!g_file_get_contents (FRONTEND_INTERFACE, &contents, nullptr, nullptr))
+ return;
+
+ GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr);
+
+ g_dbus_connection_register_object (connection,
+ "/org/libreoffice/PrintDialog",
+ introspection_data->interfaces[0],
+ nullptr,
+ nullptr, /* user_data */
+ nullptr, /* user_data_free_func */
+ nullptr); /* GError** */
+ g_free(contents);
+ g_dbus_node_info_unref(introspection_data);
+
+ CPDManager* current = static_cast<CPDManager*>(user_data);
+ std::vector<std::pair<std::string, gchar*>> backends = current->getTempBackends();
+ for (auto const& backend : backends)
+ {
+ // Get Interface for introspection
+ if (g_file_get_contents(BACKEND_INTERFACE, &contents, nullptr, nullptr))
+ {
+ introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr);
+ GDBusProxy *proxy = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ introspection_data->interfaces[0],
+ backend.first.c_str(),
+ backend.second,
+ "org.openprinting.PrintBackend",
+ nullptr,
+ nullptr);
+ g_assert (proxy != nullptr);
+ g_dbus_proxy_call(proxy, "ActivateBackend",
+ nullptr,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, nullptr, nullptr, nullptr);
+
+ g_free(contents);
+ g_object_unref(proxy);
+ g_dbus_node_info_unref(introspection_data);
+ }
+ g_free(backend.second);
+ }
+}
+
+void CPDManager::onNameLost (GDBusConnection *,
+ const gchar *name,
+ gpointer)
+{
+ g_message("Name Lost: %s", name);
+}
+
+void CPDManager::printerAdded (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ CPDManager* current = static_cast<CPDManager*>(user_data);
+ GDBusProxy *proxy;
+ proxy = current->getProxy(sender_name);
+ if (proxy == nullptr) {
+ gchar* contents;
+
+ // Get Interface for introspection
+ if (g_file_get_contents ("/usr/share/dbus-1/interfaces/org.openprinting.Backend.xml", &contents, nullptr, nullptr)) {
+ GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr);
+ proxy = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+ introspection_data->interfaces[0],
+ sender_name,
+ object_path,
+ interface_name,
+ nullptr,
+ nullptr);
+
+ g_free(contents);
+ g_dbus_node_info_unref(introspection_data);
+ std::pair<std::string, GDBusProxy *> new_backend (sender_name, proxy);
+ current->addBackend(std::move(new_backend));
+ }
+ }
+ CPDPrinter *pDest = static_cast<CPDPrinter *>(malloc(sizeof(CPDPrinter)));
+ pDest->backend = proxy;
+ g_variant_get (parameters, "(sssssbss)", &(pDest->id), &(pDest->name), &(pDest->info), &(pDest->location), &(pDest->make_and_model), &(pDest->is_accepting_jobs), &(pDest->printer_state), &(pDest->backend_name));
+ std::stringstream printerName;
+ printerName << pDest->name << ", " << pDest->backend_name;
+ std::stringstream uniqueName;
+ uniqueName << pDest->id << ", " << pDest->backend_name;
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OUString aPrinterName = OStringToOUString( printerName.str(), aEncoding );
+ OUString aUniqueName = OStringToOUString( uniqueName.str(), aEncoding );
+ current->addNewPrinter(aPrinterName, aUniqueName, pDest);
+}
+
+void CPDManager::printerRemoved (GDBusConnection *,
+ const gchar *,
+ const gchar *,
+ const gchar *,
+ const gchar *,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ // TODO: Remove every data linked to this particular printer.
+ CPDManager* pManager = static_cast<CPDManager*>(user_data);
+ char* id;
+ char* backend_name;
+ g_variant_get (parameters, "(ss)", &id, &backend_name);
+ std::stringstream uniqueName;
+ uniqueName << id << ", " << backend_name;
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OUString aUniqueName = OStringToOUString( uniqueName.str(), aEncoding );
+ std::unordered_map<OUString, CPDPrinter *>::iterator it = pManager->m_aCPDDestMap.find( aUniqueName );
+ if (it == pManager->m_aCPDDestMap.end()) {
+ SAL_WARN("vcl.unx.print", "CPD trying to remove non-existent printer from list");
+ return;
+ }
+ pManager->m_aCPDDestMap.erase(it);
+ std::unordered_map<OUString, Printer>::iterator printersIt = pManager->m_aPrinters.find( aUniqueName );
+ if (printersIt == pManager->m_aPrinters.end()) {
+ SAL_WARN("vcl.unx.print", "CPD trying to remove non-existent printer from m_aPrinters");
+ return;
+ }
+ pManager->m_aPrinters.erase(printersIt);
+}
+
+GDBusProxy* CPDManager::getProxy(const std::string& target)
+{
+ std::unordered_map<std::string, GDBusProxy *>::const_iterator it = m_pBackends.find(target);
+ if (it == m_pBackends.end()) {
+ return nullptr;
+ }
+ return it->second;
+}
+
+void CPDManager::addBackend(std::pair<std::string, GDBusProxy *> pair) {
+ m_pBackends.insert(pair);
+}
+
+void CPDManager::addTempBackend(const std::pair<std::string, gchar*>& pair)
+{
+ m_tBackends.push_back(pair);
+}
+
+std::vector<std::pair<std::string, gchar*>> const & CPDManager::getTempBackends() const {
+ return m_tBackends;
+}
+
+void CPDManager::addNewPrinter(const OUString& aPrinterName, const OUString& aUniqueName, CPDPrinter *pDest) {
+ m_aCPDDestMap[aUniqueName] = pDest;
+ bool bSetToGlobalDefaults = m_aPrinters.find( aUniqueName ) == m_aPrinters.end();
+ Printer aPrinter = m_aPrinters[ aUniqueName ];
+ if( bSetToGlobalDefaults )
+ aPrinter.m_aInfo = m_aGlobalDefaults;
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+
+ // TODO: I don't know how this should work when we have multiple
+ // sources with multiple possible defaults for each
+ // if( pDest->is_default )
+ // m_aDefaultPrinter = aPrinterName;
+
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ aPrinter.m_aInfo.m_aComment = OStringToOUString(pDest->info, aEncoding);
+ aPrinter.m_aInfo.m_aLocation = OStringToOUString(pDest->location, aEncoding);
+ // note: the parser that goes with the PrinterInfo
+ // is created implicitly by the JobData::operator=()
+ // when it detects the NULL ptr m_pParser.
+ // if we wanted to fill in the parser here this
+ // would mean we'd have to send a dbus message for each and
+ // every printer - which would be really bad runtime
+ // behaviour
+ aPrinter.m_aInfo.m_pParser = nullptr;
+ aPrinter.m_aInfo.m_aContext.setParser( nullptr );
+ std::unordered_map< OUString, PPDContext >::const_iterator c_it = m_aDefaultContexts.find( aUniqueName );
+ if( c_it != m_aDefaultContexts.end() )
+ {
+ aPrinter.m_aInfo.m_pParser = c_it->second.getParser();
+ aPrinter.m_aInfo.m_aContext = c_it->second;
+ }
+ aPrinter.m_aInfo.m_aDriverName = "CPD:" + aUniqueName;
+ m_aPrinters[ aUniqueName ] = aPrinter;
+}
+#endif
+
+/*
+ * CPDManager class
+ */
+
+CPDManager* CPDManager::tryLoadCPD()
+{
+ CPDManager* pManager = nullptr;
+#if ENABLE_DBUS && ENABLE_GIO
+ static const char* pEnv = getenv("SAL_DISABLE_CPD");
+
+ if (!pEnv || !*pEnv) {
+ // interface description XML files are needed in 'onNameAcquired()'
+ if (!g_file_test(FRONTEND_INTERFACE, G_FILE_TEST_IS_REGULAR) ||
+ !g_file_test(BACKEND_INTERFACE, G_FILE_TEST_IS_REGULAR)) {
+ return nullptr;
+ }
+
+ GDir *dir;
+ const gchar *filename;
+ dir = g_dir_open(BACKEND_DIR, 0, nullptr);
+ if (dir != nullptr) {
+ while ((filename = g_dir_read_name(dir))) {
+ if (pManager == nullptr) {
+ pManager = new CPDManager();
+ }
+ gchar* contents;
+ std::stringstream filepath;
+ filepath << BACKEND_DIR << '/' << filename;
+ if (g_file_get_contents(filepath.str().c_str(), &contents, nullptr, nullptr))
+ {
+ std::pair<std::string, gchar*> new_tbackend (filename, contents);
+ pManager->addTempBackend(new_tbackend);
+ }
+ }
+ g_dir_close(dir);
+ }
+ }
+#endif
+ return pManager;
+}
+
+CPDManager::CPDManager() :
+ PrinterInfoManager( PrinterInfoManager::Type::CPD )
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ // Get Destinations number and pointers
+ GError *error = nullptr;
+ m_pConnection = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, &error);
+ g_assert_no_error (error);
+#endif
+}
+
+CPDManager::~CPDManager()
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ g_dbus_connection_emit_signal (m_pConnection,
+ nullptr,
+ "/org/libreoffice/PrintDialog",
+ "org.openprinting.PrintFrontend",
+ "StopListing",
+ nullptr,
+ nullptr);
+ g_dbus_connection_flush_sync (m_pConnection,
+ nullptr,
+ nullptr);
+ g_dbus_connection_close_sync (m_pConnection,
+ nullptr,
+ nullptr);
+ for (auto const& backend : m_pBackends)
+ {
+ g_object_unref(backend.second);
+ }
+ for (auto const& backend : m_aCPDDestMap)
+ {
+ free(backend.second);
+ }
+#endif
+}
+
+
+const PPDParser* CPDManager::createCPDParser( const OUString& rPrinter )
+{
+ const PPDParser* pNewParser = nullptr;
+#if ENABLE_DBUS && ENABLE_GIO
+ OUString aPrinter;
+
+ if( rPrinter.startsWith("CPD:") )
+ aPrinter = rPrinter.copy( 4 );
+ else
+ aPrinter = rPrinter;
+
+ std::unordered_map< OUString, CPDPrinter * >::iterator dest_it =
+ m_aCPDDestMap.find( aPrinter );
+
+ if( dest_it != m_aCPDDestMap.end() )
+ {
+ CPDPrinter* pDest = dest_it->second;
+ GVariant* ret = nullptr;
+ GError* error = nullptr;
+ ret = g_dbus_proxy_call_sync (pDest->backend, "GetAllOptions",
+ g_variant_new("(s)", (pDest->id)),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, nullptr, &error);
+ if (ret != nullptr && error == nullptr)
+ {
+ // TODO: These keys need to be redefined to preserve usage across libreoffice
+ // InputSlot - media-col.media-source?
+ // Font - not needed now as it is required only for ps and we are using pdf
+ // Dial? - for FAX (need to look up PWG spec)
+
+ int num_attribute;
+ GVariantIter *iter_attr, *iter_supported_values;
+ g_variant_get (ret, "(ia(ssia(s)))", &num_attribute, &iter_attr);
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ PPDKey *pKey = nullptr;
+ OUString aValueName;
+ PPDValue* pValue;
+ std::vector<PPDKey*> keys;
+ std::vector<OUString> default_values;
+ for (int i = 0; i < num_attribute; i++) {
+ char *name, *default_value;
+ int num_supported_values;
+ g_variant_iter_loop(iter_attr, "(ssia(s))",
+ &name, &default_value,
+ &num_supported_values, &iter_supported_values);
+ OUString aOptionName = OStringToOUString( name, aEncoding );
+ OUString aDefaultValue = OStringToOUString( default_value, aEncoding );
+ if (aOptionName == "sides") {
+ // Duplex key is used throughout for checking Duplex Support
+ aOptionName = OUString("Duplex");
+ } else if (aOptionName == "printer-resolution") {
+ // Resolution key is used in places
+ aOptionName = OUString("Resolution");
+ } else if (aOptionName == "media") {
+ // PageSize key is used in many places
+ aOptionName = OUString("PageSize");
+ }
+ default_values.push_back(aDefaultValue);
+ pKey = new PPDKey( aOptionName );
+
+ // If number of values are 0, this is not settable via UI
+ if (num_supported_values > 0 && aDefaultValue != "NA")
+ pKey->m_bUIOption = true;
+
+ bool bDefaultFound = false;
+
+ for (int j = 0; j < num_supported_values; j++) {
+ char* value;
+ g_variant_iter_loop(iter_supported_values, "(s)", &value);
+ aValueName = OStringToOUString( value, aEncoding );
+ if (aOptionName == "Duplex") {
+ // Duplex key matches against very specific Values
+ if (aValueName == "one-sided") {
+ aValueName = OUString("None");
+ } else if (aValueName == "two-sided-long-edge") {
+ aValueName = OUString("DuplexNoTumble");
+ } else if (aValueName == "two-sided-short-edge") {
+ aValueName = OUString("DuplexTumble");
+ }
+ }
+
+ pValue = pKey->insertValue( aValueName, eQuoted );
+ if( ! pValue )
+ continue;
+ pValue->m_aValue = aValueName;
+
+ if (aValueName.equals(aDefaultValue)) {
+ pKey->m_pDefaultValue = pValue;
+ bDefaultFound = true;
+ }
+
+ }
+ // This could be done to ensure default values also appear as options:
+ if (!bDefaultFound && pKey->m_bUIOption) {
+ // pValue = pKey->insertValue( aDefaultValue, eQuoted );
+ // if( pValue )
+ // pValue->m_aValue = aDefaultValue;
+ }
+ keys.emplace_back(pKey);
+ }
+
+ pKey = new PPDKey("ModelName");
+ aValueName = OStringToOUString( "", aEncoding );
+ pValue = pKey->insertValue( aValueName, eQuoted );
+ if( pValue )
+ pValue->m_aValue = aValueName;
+ pKey->m_pDefaultValue = pValue;
+ keys.emplace_back(pKey);
+
+ pKey = new PPDKey("NickName");
+ aValueName = OStringToOUString( pDest->name, aEncoding );
+ pValue = pKey->insertValue( aValueName, eQuoted );
+ if( pValue )
+ pValue->m_aValue = aValueName;
+ pKey->m_pDefaultValue = pValue;
+ keys.emplace_back(pKey);
+
+ pNewParser = new PPDParser(aPrinter, keys);
+ PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
+ PPDContext& rContext = m_aDefaultContexts[ aPrinter ];
+ rContext.setParser( pNewParser );
+ setDefaultPaper( rContext );
+ std::vector<OUString>::iterator defit = default_values.begin();
+ for (auto const& key : keys)
+ {
+ const PPDValue* p1Value = key->getValue( *defit );
+ if( p1Value )
+ {
+ if( p1Value != key->getDefaultValue() )
+ {
+ rContext.setValue( key, p1Value, true );
+ SAL_INFO("vcl.unx.print", "key " << pKey->getKey() << " is set to " << *defit);
+ }
+ else
+ SAL_INFO("vcl.unx.print", "key " << pKey->getKey() << " is defaulted to " << *defit);
+ }
+ ++defit;
+ }
+
+ rInfo.m_pParser = pNewParser;
+ rInfo.m_aContext = rContext;
+ g_variant_unref(ret);
+ }
+ else
+ {
+ g_clear_error(&error);
+ SAL_INFO("vcl.unx.print", "CPD GetAllOptions failed, falling back to generic driver");
+ }
+ }
+ else
+ SAL_INFO("vcl.unx.print", "no dest found for printer " << aPrinter);
+
+ if( ! pNewParser )
+ {
+ // get the default PPD
+ pNewParser = PPDParser::getParser( "SGENPRT" );
+ SAL_WARN("vcl.unx.print", "Parsing default SGENPRT PPD" );
+
+ PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
+
+ rInfo.m_pParser = pNewParser;
+ rInfo.m_aContext.setParser( pNewParser );
+ }
+#else
+ (void)rPrinter;
+#endif
+ return pNewParser;
+}
+
+
+void CPDManager::initialize()
+{
+ // get normal printers, clear printer list
+ PrinterInfoManager::initialize();
+#if ENABLE_DBUS && ENABLE_GIO
+ g_bus_own_name_on_connection (m_pConnection,
+ "org.libreoffice.print-dialog",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ onNameAcquired,
+ onNameLost,
+ this,
+ nullptr);
+
+ g_dbus_connection_signal_subscribe (m_pConnection, // DBus Connection
+ nullptr, // Sender Name
+ "org.openprinting.PrintBackend", // Sender Interface
+ "PrinterAdded", // Signal Name
+ nullptr, // Object Path
+ nullptr, // arg0 behaviour
+ G_DBUS_SIGNAL_FLAGS_NONE, // Signal Flags
+ printerAdded, // Callback Function
+ this,
+ nullptr);
+ g_dbus_connection_signal_subscribe (m_pConnection, // DBus Connection
+ nullptr, // Sender Name
+ "org.openprinting.PrintBackend", // Sender Interface
+ "PrinterRemoved", // Signal Name
+ nullptr, // Object Path
+ nullptr, // arg0 behaviour
+ G_DBUS_SIGNAL_FLAGS_NONE, // Signal Flags
+ printerRemoved, // Callback Function
+ this,
+ nullptr);
+
+ // remove everything that is not a CUPS printer and not
+ // a special purpose printer (PDF, Fax)
+ std::unordered_map< OUString, Printer >::iterator it = m_aPrinters.begin();
+ while (it != m_aPrinters.end())
+ {
+ if( m_aCPDDestMap.find( it->first ) != m_aCPDDestMap.end() )
+ {
+ ++it;
+ continue;
+ }
+
+ if( !it->second.m_aInfo.m_aFeatures.isEmpty() )
+ {
+ ++it;
+ continue;
+ }
+ it = m_aPrinters.erase(it);
+ }
+#endif
+}
+
+void CPDManager::setupJobContextData( JobData& rData )
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ std::unordered_map<OUString, CPDPrinter *>::iterator dest_it =
+ m_aCPDDestMap.find( rData.m_aPrinterName );
+
+ if( dest_it == m_aCPDDestMap.end() )
+ return PrinterInfoManager::setupJobContextData( rData );
+
+ std::unordered_map< OUString, Printer >::iterator p_it =
+ m_aPrinters.find( rData.m_aPrinterName );
+ if( p_it == m_aPrinters.end() ) // huh ?
+ {
+ SAL_WARN("vcl.unx.print", "CPD printer list in disorder, "
+ "no dest for printer " << rData.m_aPrinterName);
+ return;
+ }
+
+ if( p_it->second.m_aInfo.m_pParser == nullptr )
+ {
+ // in turn calls createCPDParser
+ // which updates the printer info
+ p_it->second.m_aInfo.m_pParser = PPDParser::getParser( p_it->second.m_aInfo.m_aDriverName );
+ }
+ if( p_it->second.m_aInfo.m_aContext.getParser() == nullptr )
+ {
+ OUString aPrinter;
+ if( p_it->second.m_aInfo.m_aDriverName.startsWith("CPD:") )
+ aPrinter = p_it->second.m_aInfo.m_aDriverName.copy( 4 );
+ else
+ aPrinter = p_it->second.m_aInfo.m_aDriverName;
+
+ p_it->second.m_aInfo.m_aContext = m_aDefaultContexts[ aPrinter ];
+ }
+
+ rData.m_pParser = p_it->second.m_aInfo.m_pParser;
+ rData.m_aContext = p_it->second.m_aInfo.m_aContext;
+#else
+ (void)rData;
+#endif
+}
+
+FILE* CPDManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ SAL_INFO( "vcl.unx.print", "startSpool: " << rPrintername << " " << (bQuickCommand ? "true" : "false") );
+ if( m_aCPDDestMap.find( rPrintername ) == m_aCPDDestMap.end() )
+ {
+ SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::startSpool" );
+ return PrinterInfoManager::startSpool( rPrintername, bQuickCommand );
+ }
+ OUString aTmpURL, aTmpFile;
+ osl_createTempFile( nullptr, nullptr, &aTmpURL.pData );
+ osl_getSystemPathFromFileURL( aTmpURL.pData, &aTmpFile.pData );
+ OString aSysFile = OUStringToOString( aTmpFile, osl_getThreadTextEncoding() );
+ FILE* fp = fopen( aSysFile.getStr(), "w" );
+ if( fp )
+ m_aSpoolFiles[fp] = aSysFile;
+
+ return fp;
+#else
+ (void)rPrintername;
+ (void)bQuickCommand;
+ return nullptr;
+#endif
+}
+
+#if ENABLE_DBUS && ENABLE_GIO
+void CPDManager::getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, const OString& rJobName, int& rNumOptions, GVariant **arr )
+{
+ GVariantBuilder *builder;
+ builder = g_variant_builder_new(G_VARIANT_TYPE("a(ss)"));
+ g_variant_builder_add(builder, "(ss)", "job-name", rJobName.getStr());
+ if( rJob.m_pParser == rJob.m_aContext.getParser() && rJob.m_pParser ) {
+ std::size_t i;
+ std::size_t nKeys = rJob.m_aContext.countValuesModified();
+ ::std::vector< const PPDKey* > aKeys( nKeys );
+ for( i = 0; i < nKeys; i++ )
+ aKeys[i] = rJob.m_aContext.getModifiedKey( i );
+ for( i = 0; i < nKeys; i++ ) {
+ const PPDKey* pKey = aKeys[i];
+ const PPDValue* pValue = rJob.m_aContext.getValue( pKey );
+ OUString sPayLoad;
+ if (pValue) {
+ sPayLoad = pValue->m_bCustomOption ? pValue->m_aCustomOption : pValue->m_aOption;
+ }
+ if (!sPayLoad.isEmpty()) {
+ OString aKey = OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US );
+ OString aValue = OUStringToOString( sPayLoad, RTL_TEXTENCODING_ASCII_US );
+ if (aKey.equals("Duplex"_ostr)) {
+ aKey = "sides"_ostr;
+ } else if (aKey.equals("Resolution"_ostr)) {
+ aKey = "printer-resolution"_ostr;
+ } else if (aKey.equals("PageSize"_ostr)) {
+ aKey = "media"_ostr;
+ }
+ if (aKey.equals("sides"_ostr)) {
+ if (aValue.equals("None"_ostr)) {
+ aValue = "one-sided"_ostr;
+ } else if (aValue.equals("DuplexNoTumble"_ostr)) {
+ aValue = "two-sided-long-edge"_ostr;
+ } else if (aValue.equals("DuplexTumble"_ostr)) {
+ aValue = "two-sided-short-edge"_ostr;
+ }
+ }
+ g_variant_builder_add(builder, "(ss)", aKey.getStr(), aValue.getStr());
+ }
+ }
+ }
+ if( rJob.m_nCopies > 1 )
+ {
+ OString aVal( OString::number( rJob.m_nCopies ) );
+ g_variant_builder_add(builder, "(ss)", "copies", aVal.getStr());
+ rNumOptions++;
+ // TODO: something for collate
+ // Maybe this is the equivalent ipp attribute:
+ if (rJob.m_bCollate) {
+ g_variant_builder_add(builder, "(ss)", "multiple-document-handling", "separate-documents-collated-copies");
+ } else {
+ g_variant_builder_add(builder, "(ss)", "multiple-document-handling", "separate-documents-uncollated-copies");
+ }
+ rNumOptions++;
+ }
+ if( ! bBanner )
+ {
+ g_variant_builder_add(builder, "(ss)", "job-sheets", "none");
+ rNumOptions++;
+ }
+ if (rJob.m_eOrientation == orientation::Portrait) {
+ g_variant_builder_add(builder, "(ss)", "orientation-requested", "portrait");
+ rNumOptions++;
+ } else if (rJob.m_eOrientation == orientation::Landscape) {
+ g_variant_builder_add(builder, "(ss)", "orientation-requested", "landscape");
+ rNumOptions++;
+ }
+ (*arr) = g_variant_new("a(ss)", builder);
+ g_variant_builder_unref(builder);
+}
+#endif
+
+bool CPDManager::endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber )
+{
+ bool success = false;
+#if ENABLE_DBUS && ENABLE_GIO
+ SAL_INFO( "vcl.unx.print", "endSpool: " << rPrintername << "," << rJobTitle << " copy count = " << rDocumentJobData.m_nCopies );
+ std::unordered_map< OUString, CPDPrinter * >::iterator dest_it =
+ m_aCPDDestMap.find( rPrintername );
+ if( dest_it == m_aCPDDestMap.end() )
+ {
+ SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::endSpool" );
+ return PrinterInfoManager::endSpool( rPrintername, rJobTitle, pFile, rDocumentJobData, bBanner, rFaxNumber );
+ }
+
+ std::unordered_map< FILE*, OString, FPtrHash >::const_iterator it = m_aSpoolFiles.find( pFile );
+ if( it != m_aSpoolFiles.end() )
+ {
+ fclose( pFile );
+ rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
+ OString sJobName(OUStringToOString(rJobTitle, aEnc));
+ if (!rFaxNumber.isEmpty())
+ {
+ sJobName = OUStringToOString(rFaxNumber, aEnc);
+ }
+ OString aSysFile = it->second;
+ CPDPrinter* pDest = dest_it->second;
+ GVariant* ret;
+ gint job_id;
+ int nNumOptions = 0;
+ GVariant *pArr = nullptr;
+ getOptionsFromDocumentSetup( rDocumentJobData, bBanner, sJobName, nNumOptions, &pArr );
+ ret = g_dbus_proxy_call_sync (pDest->backend, "printFile",
+ g_variant_new(
+ "(ssi@a(ss))",
+ (pDest->id),
+ aSysFile.getStr(),
+ nNumOptions,
+ pArr
+ ),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, nullptr, nullptr);
+ g_variant_get (ret, "(i)", &job_id);
+ if (job_id != -1) {
+ success = true;
+ }
+ g_variant_unref(ret);
+ unlink( it->second.getStr() );
+ m_aSpoolFiles.erase(it);
+ }
+#else
+ (void)rPrintername;
+ (void)rJobTitle;
+ (void)pFile;
+ (void)rDocumentJobData;
+ (void)bBanner;
+ (void)rFaxNumber;
+#endif
+ return success;
+}
+
+bool CPDManager::checkPrintersChanged( bool )
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ bool bChanged = m_aPrintersChanged;
+ m_aPrintersChanged = false;
+ g_dbus_connection_emit_signal (m_pConnection,
+ nullptr,
+ "/org/libreoffice/PrintDialog",
+ "org.openprinting.PrintFrontend",
+ "RefreshBackend",
+ nullptr,
+ nullptr);
+ return bChanged;
+#else
+ return false;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+
diff --git a/vcl/unx/generic/printer/cupsmgr.cxx b/vcl/unx/generic/printer/cupsmgr.cxx
new file mode 100644
index 0000000000..1fab7a1723
--- /dev/null
+++ b/vcl/unx/generic/printer/cupsmgr.cxx
@@ -0,0 +1,992 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cups/cups.h>
+#include <cups/http.h>
+#include <cups/ipp.h>
+#include <cups/ppd.h>
+
+#include <unistd.h>
+
+#include <unx/cupsmgr.hxx>
+
+#include <o3tl/string_view.hxx>
+#include <osl/thread.h>
+#include <osl/file.h>
+#include <osl/conditn.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+
+#include <officecfg/Office/Common.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/window.hxx>
+
+#include <algorithm>
+#include <cstddef>
+#include <string_view>
+
+using namespace psp;
+using namespace osl;
+
+namespace {
+
+struct GetPPDAttribs
+{
+ osl::Condition m_aCondition;
+ OString m_aParameter;
+ OString m_aResult;
+ int m_nRefs;
+ bool* m_pResetRunning;
+ osl::Mutex* m_pSyncMutex;
+
+ GetPPDAttribs( const char * m_pParameter,
+ bool* pResetRunning, osl::Mutex* pSyncMutex )
+ : m_aParameter( m_pParameter ),
+ m_pResetRunning( pResetRunning ),
+ m_pSyncMutex( pSyncMutex )
+ {
+ m_nRefs = 2;
+ m_aCondition.reset();
+ }
+
+ ~GetPPDAttribs()
+ {
+ if( !m_aResult.isEmpty() )
+ unlink( m_aResult.getStr() );
+ }
+
+ void unref()
+ {
+ if( --m_nRefs == 0 )
+ {
+ *m_pResetRunning = false;
+ delete this;
+ }
+ }
+
+ void executeCall()
+ {
+ // This CUPS method is not at all thread-safe we need
+ // to dup the pointer to a static buffer it returns ASAP
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ const char* pResult = cupsGetPPD(m_aParameter.getStr());
+ OString aResult = pResult ? OString(pResult) : OString();
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+ MutexGuard aGuard( *m_pSyncMutex );
+ m_aResult = aResult;
+ m_aCondition.set();
+ unref();
+ }
+
+ OString waitResult( TimeValue const *pDelay )
+ {
+ m_pSyncMutex->release();
+
+ if (m_aCondition.wait( pDelay ) != Condition::result_ok
+ )
+ {
+ SAL_WARN("vcl.unx.print",
+ "cupsGetPPD " << m_aParameter << " timed out");
+ }
+ m_pSyncMutex->acquire();
+
+ OString aRetval = m_aResult;
+ m_aResult.clear();
+ unref();
+
+ return aRetval;
+ }
+};
+
+}
+
+extern "C" {
+ static void getPPDWorker(void* pData)
+ {
+ osl_setThreadName("CUPSManager getPPDWorker");
+ GetPPDAttribs* pAttribs = static_cast<GetPPDAttribs*>(pData);
+ pAttribs->executeCall();
+ }
+}
+
+OString CUPSManager::threadedCupsGetPPD( const char* pPrinter )
+{
+ OString aResult;
+
+ m_aGetPPDMutex.acquire();
+ // if one thread hangs in cupsGetPPD already, don't start another
+ if( ! m_bPPDThreadRunning )
+ {
+ m_bPPDThreadRunning = true;
+ GetPPDAttribs* pAttribs = new GetPPDAttribs( pPrinter,
+ &m_bPPDThreadRunning,
+ &m_aGetPPDMutex );
+
+ oslThread aThread = osl_createThread( getPPDWorker, pAttribs );
+
+ TimeValue aValue;
+ aValue.Seconds = 5;
+ aValue.Nanosec = 0;
+
+ // NOTE: waitResult release and acquires the GetPPD mutex
+ aResult = pAttribs->waitResult( &aValue );
+ osl_destroyThread( aThread );
+ }
+ m_aGetPPDMutex.release();
+
+ return aResult;
+}
+
+static const char* setPasswordCallback( const char* /*pIn*/ )
+{
+ const char* pRet = nullptr;
+
+ PrinterInfoManager& rMgr = PrinterInfoManager::get();
+ if( rMgr.getType() == PrinterInfoManager::Type::CUPS ) // sanity check
+ pRet = static_cast<CUPSManager&>(rMgr).authenticateUser();
+ return pRet;
+}
+
+/*
+ * CUPSManager class
+ */
+
+CUPSManager* CUPSManager::tryLoadCUPS()
+{
+ CUPSManager* pManager = nullptr;
+ static const char* pEnv = getenv("SAL_DISABLE_CUPS");
+
+ if (!pEnv || !*pEnv)
+ pManager = new CUPSManager();
+ return pManager;
+}
+
+extern "C"
+{
+static void run_dest_thread_stub( void* pThis )
+{
+ osl_setThreadName("CUPSManager cupsGetDests");
+ CUPSManager::runDestThread( pThis );
+}
+}
+
+CUPSManager::CUPSManager() :
+ PrinterInfoManager( PrinterInfoManager::Type::CUPS ),
+ m_nDests( 0 ),
+ m_pDests( nullptr ),
+ m_bNewDests( false ),
+ m_bPPDThreadRunning( false )
+{
+ m_aDestThread = osl_createThread( run_dest_thread_stub, this );
+}
+
+CUPSManager::~CUPSManager()
+{
+ if( m_aDestThread )
+ {
+ osl_joinWithThread( m_aDestThread );
+ osl_destroyThread( m_aDestThread );
+ }
+
+ if (m_nDests && m_pDests)
+ cupsFreeDests( m_nDests, m_pDests );
+}
+
+void CUPSManager::runDestThread( void* pThis )
+{
+ static_cast<CUPSManager*>(pThis)->runDests();
+}
+
+void CUPSManager::runDests()
+{
+ SAL_INFO("vcl.unx.print", "starting cupsGetDests");
+ cups_dest_t* pDests = nullptr;
+
+ // n#722902 - do a fast-failing check for cups working *at all* first
+ http_t* p_http;
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ if( (p_http=httpConnectEncrypt(
+ cupsServer(),
+ ippPort(),
+ cupsEncryption())) == nullptr )
+ return;
+
+ int nDests = cupsGetDests2(p_http, &pDests);
+ SAL_INFO("vcl.unx.print", "came out of cupsGetDests");
+
+ osl::MutexGuard aGuard( m_aCUPSMutex );
+ m_nDests = nDests;
+ m_pDests = pDests;
+ m_bNewDests = true;
+ SAL_INFO("vcl.unx.print", "finished cupsGetDests");
+
+ httpClose(p_http);
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+}
+
+static void SetIfCustomOption(PPDContext& rContext, const cups_option_t& rOption, rtl_TextEncoding aEncoding)
+{
+ if (strncmp(rOption.value, RTL_CONSTASCII_STRINGPARAM("Custom.")) == 0)
+ {
+ const PPDParser* pParser = rContext.getParser();
+ if (!pParser)
+ {
+ // normal for first sight of this printer
+ return;
+ }
+
+ const PPDKey* pKey = pParser->getKey(OStringToOUString(rOption.name, aEncoding));
+ if (!pKey)
+ {
+ SAL_WARN("vcl.unx.print", "Custom key " << rOption.name << " not found");
+ return;
+ }
+
+ const PPDValue* pCustomValue = rContext.getValue(pKey);
+ if (!pCustomValue)
+ {
+ SAL_WARN("vcl.unx.print", "Value for " << rOption.name << " not found");
+ return;
+ }
+
+ if (!pCustomValue->m_bCustomOption)
+ {
+ SAL_WARN("vcl.unx.print", "Value for " << rOption.name << " not set to custom option");
+ return;
+ }
+
+ // seems sensible to keep a value the user explicitly set even if lpoptions was used to set
+ // another default
+ if (pCustomValue->m_bCustomOptionSetViaApp)
+ return;
+ pCustomValue->m_aCustomOption = OStringToOUString(rOption.value, aEncoding);
+ }
+}
+
+void CUPSManager::initialize()
+{
+ // get normal printers, clear printer list
+ PrinterInfoManager::initialize();
+
+ // check whether thread has completed
+ // if not behave like old printing system
+ osl::MutexGuard aGuard( m_aCUPSMutex );
+
+ if( ! m_bNewDests )
+ return;
+
+ // dest thread has run, clean up
+ if( m_aDestThread )
+ {
+ osl_joinWithThread( m_aDestThread );
+ osl_destroyThread( m_aDestThread );
+ m_aDestThread = nullptr;
+ }
+ m_bNewDests = false;
+
+ // clear old stuff
+ m_aCUPSDestMap.clear();
+
+ if( ! (m_nDests && m_pDests ) )
+ return;
+
+ // check for CUPS server(?) > 1.2
+ // since there is no API to query, check for options that were
+ // introduced in dests with 1.2
+ // this is needed to check for %%IncludeFeature support
+ // (#i65684#, #i65491#)
+ cups_dest_t* pDest = m_pDests;
+
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ int nPrinter = m_nDests;
+
+ // reset global default PPD options; these are queried on demand from CUPS
+ m_aGlobalDefaults.m_pParser = nullptr;
+ m_aGlobalDefaults.m_aContext = PPDContext();
+
+ // add CUPS printers, should there be a printer
+ // with the same name as a CUPS printer, overwrite it
+ while( nPrinter-- )
+ {
+ pDest = m_pDests+nPrinter;
+ OUString aPrinterName = OStringToOUString( pDest->name, aEncoding );
+ if( pDest->instance && *pDest->instance )
+ {
+ aPrinterName += "/" +
+ OStringToOUString( pDest->instance, aEncoding );
+ }
+
+ // initialize printer with possible configuration from psprint.conf
+ bool bSetToGlobalDefaults = m_aPrinters.find( aPrinterName ) == m_aPrinters.end();
+ Printer aPrinter = m_aPrinters[ aPrinterName ];
+ if( bSetToGlobalDefaults )
+ aPrinter.m_aInfo = m_aGlobalDefaults;
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+ if( pDest->is_default )
+ m_aDefaultPrinter = aPrinterName;
+
+ // note: the parser that goes with the PrinterInfo
+ // is created implicitly by the JobData::operator=()
+ // when it detects the NULL ptr m_pParser.
+ // if we wanted to fill in the parser here this
+ // would mean we'd have to download PPDs for each and
+ // every printer - which would be really bad runtime
+ // behaviour
+ aPrinter.m_aInfo.m_pParser = nullptr;
+ aPrinter.m_aInfo.m_aContext.setParser( nullptr );
+ std::unordered_map< OUString, PPDContext >::const_iterator c_it = m_aDefaultContexts.find( aPrinterName );
+ if( c_it != m_aDefaultContexts.end() )
+ {
+ aPrinter.m_aInfo.m_pParser = c_it->second.getParser();
+ aPrinter.m_aInfo.m_aContext = c_it->second;
+ }
+ aPrinter.m_aInfo.m_aDriverName = "CUPS:" + aPrinterName;
+
+ for( int k = 0; k < pDest->num_options; k++ )
+ {
+ if(!strcmp(pDest->options[k].name, "printer-info"))
+ aPrinter.m_aInfo.m_aComment=OStringToOUString(pDest->options[k].value, aEncoding);
+ if(!strcmp(pDest->options[k].name, "printer-location"))
+ aPrinter.m_aInfo.m_aLocation=OStringToOUString(pDest->options[k].value, aEncoding);
+ if(!strcmp(pDest->options[k].name, "auth-info-required"))
+ aPrinter.m_aInfo.m_aAuthInfoRequired=OStringToOUString(pDest->options[k].value, aEncoding);
+ // tdf#149439 Update Custom values that may have changed if this is not a newly discovered printer
+ SetIfCustomOption(aPrinter.m_aInfo.m_aContext, pDest->options[k], aEncoding);
+ }
+
+ m_aPrinters[ aPrinter.m_aInfo.m_aPrinterName ] = aPrinter;
+ m_aCUPSDestMap[ aPrinter.m_aInfo.m_aPrinterName ] = nPrinter;
+ }
+
+ // remove everything that is not a CUPS printer and not
+ // a special purpose printer (PDF, Fax)
+ std::unordered_map< OUString, Printer >::iterator it = m_aPrinters.begin();
+ while(it != m_aPrinters.end())
+ {
+ if( m_aCUPSDestMap.find( it->first ) != m_aCUPSDestMap.end() )
+ {
+ ++it;
+ continue;
+ }
+
+ if( !it->second.m_aInfo.m_aFeatures.isEmpty() )
+ {
+ ++it;
+ continue;
+ }
+ it = m_aPrinters.erase(it);
+ }
+
+ cupsSetPasswordCB( setPasswordCallback );
+}
+
+static void updatePrinterContextInfo( ppd_group_t* pPPDGroup, PPDContext& rContext )
+{
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ for( int i = 0; i < pPPDGroup->num_options; i++ )
+ {
+ ppd_option_t* pOption = pPPDGroup->options + i;
+ for( int n = 0; n < pOption->num_choices; n++ )
+ {
+ ppd_choice_t* pChoice = pOption->choices + n;
+ if( pChoice->marked )
+ {
+ const PPDKey* pKey = rContext.getParser()->getKey( OStringToOUString( pOption->keyword, aEncoding ) );
+ if( pKey )
+ {
+ const PPDValue* pValue = pKey->getValue( OStringToOUString( pChoice->choice, aEncoding ) );
+ if( pValue )
+ {
+ if( pValue != pKey->getDefaultValue() )
+ {
+ rContext.setValue( pKey, pValue, true );
+ SAL_INFO("vcl.unx.print", "key " << pOption->keyword << " is set to " << pChoice->choice);
+
+ }
+ else
+ SAL_INFO("vcl.unx.print", "key " << pOption->keyword << " is defaulted to " << pChoice->choice);
+ }
+ else
+ SAL_INFO("vcl.unx.print", "caution: value " << pChoice->choice << " not found in key " << pOption->keyword);
+ }
+ else
+ SAL_INFO("vcl.unx.print", "caution: key " << pOption->keyword << " not found in parser");
+ }
+ }
+ }
+
+ // recurse through subgroups
+ for( int g = 0; g < pPPDGroup->num_subgroups; g++ )
+ {
+ updatePrinterContextInfo( pPPDGroup->subgroups + g, rContext );
+ }
+}
+
+const PPDParser* CUPSManager::createCUPSParser( const OUString& rPrinter )
+{
+ const PPDParser* pNewParser = nullptr;
+ OUString aPrinter;
+
+ if( rPrinter.startsWith("CUPS:") )
+ aPrinter = rPrinter.copy( 5 );
+ else
+ aPrinter = rPrinter;
+
+ if( m_aCUPSMutex.tryToAcquire() )
+ {
+ if (m_nDests && m_pDests)
+ {
+ std::unordered_map< OUString, int >::iterator dest_it =
+ m_aCUPSDestMap.find( aPrinter );
+ if( dest_it != m_aCUPSDestMap.end() )
+ {
+ cups_dest_t* pDest = m_pDests + dest_it->second;
+ OString aPPDFile = threadedCupsGetPPD( pDest->name );
+ SAL_INFO("vcl.unx.print",
+ "PPD for " << aPrinter << " is " << aPPDFile);
+ if( !aPPDFile.isEmpty() )
+ {
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OUString aFileName( OStringToOUString( aPPDFile, aEncoding ) );
+ // update the printer info with context information
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ ppd_file_t* pPPD = ppdOpenFile( aPPDFile.getStr() );
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+ if( pPPD )
+ {
+ // create the new parser
+ PPDParser* pCUPSParser = new PPDParser( aFileName );
+ pCUPSParser->m_aFile = rPrinter;
+ pNewParser = pCUPSParser;
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ /*int nConflicts =*/ cupsMarkOptions( pPPD, pDest->num_options, pDest->options );
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+ SAL_INFO("vcl.unx.print", "processing the following options for printer " << pDest->name << " (instance " << (pDest->instance == nullptr ? "null" : pDest->instance) << "):");
+ for( int k = 0; k < pDest->num_options; k++ )
+ SAL_INFO("vcl.unx.print",
+ " \"" << pDest->options[k].name <<
+ "\" = \"" << pDest->options[k].value << "\"");
+ PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
+
+ // remember the default context for later use
+ PPDContext& rContext = m_aDefaultContexts[ aPrinter ];
+ rContext.setParser( pNewParser );
+ // set system default paper; printer CUPS PPD options
+ // may overwrite it
+ setDefaultPaper( rContext );
+ for( int i = 0; i < pPPD->num_groups; i++ )
+ updatePrinterContextInfo( pPPD->groups + i, rContext );
+
+ // tdf#149439 Set Custom values.
+ for (int k = 0; k < pDest->num_options; ++k)
+ SetIfCustomOption(rContext, pDest->options[k], aEncoding);
+
+ rInfo.m_pParser = pNewParser;
+ rInfo.m_aContext = rContext;
+
+ // clean up the mess
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ ppdClose( pPPD );
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+ }
+ else
+ SAL_INFO("vcl.unx.print", "ppdOpenFile failed, falling back to generic driver");
+
+ // remove temporary PPD file
+ if (!getenv("SAL_CUPS_PPD_RETAIN_TMP"))
+ unlink( aPPDFile.getStr() );
+ }
+ else
+ SAL_INFO("vcl.unx.print", "cupsGetPPD failed, falling back to generic driver");
+ }
+ else
+ SAL_INFO("vcl.unx.print", "no dest found for printer " << aPrinter);
+ }
+ m_aCUPSMutex.release();
+ }
+ else
+ SAL_WARN("vcl.unx.print", "could not acquire CUPS mutex !!!" );
+
+ if( ! pNewParser )
+ {
+ // get the default PPD
+ pNewParser = PPDParser::getParser( "SGENPRT" );
+ SAL_INFO("vcl.unx.print", "Parsing default SGENPRT PPD" );
+
+ PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
+
+ rInfo.m_pParser = pNewParser;
+ rInfo.m_aContext.setParser( pNewParser );
+ }
+
+ return pNewParser;
+}
+
+void CUPSManager::setupJobContextData( JobData& rData )
+{
+ std::unordered_map< OUString, int >::iterator dest_it =
+ m_aCUPSDestMap.find( rData.m_aPrinterName );
+
+ if( dest_it == m_aCUPSDestMap.end() )
+ return PrinterInfoManager::setupJobContextData( rData );
+
+ std::unordered_map< OUString, Printer >::iterator p_it =
+ m_aPrinters.find( rData.m_aPrinterName );
+ if( p_it == m_aPrinters.end() ) // huh ?
+ {
+ SAL_WARN("vcl.unx.print", "CUPS printer list in disorder, "
+ "no dest for printer " << rData.m_aPrinterName);
+ return;
+ }
+
+ if( p_it->second.m_aInfo.m_pParser == nullptr )
+ {
+ // in turn calls createCUPSParser
+ // which updates the printer info
+ p_it->second.m_aInfo.m_pParser = PPDParser::getParser( p_it->second.m_aInfo.m_aDriverName );
+ }
+ if( p_it->second.m_aInfo.m_aContext.getParser() == nullptr )
+ {
+ OUString aPrinter;
+ if( p_it->second.m_aInfo.m_aDriverName.startsWith("CUPS:") )
+ aPrinter = p_it->second.m_aInfo.m_aDriverName.copy( 5 );
+ else
+ aPrinter = p_it->second.m_aInfo.m_aDriverName;
+
+ p_it->second.m_aInfo.m_aContext = m_aDefaultContexts[ aPrinter ];
+ }
+
+ rData.m_pParser = p_it->second.m_aInfo.m_pParser;
+ rData.m_aContext = p_it->second.m_aInfo.m_aContext;
+}
+
+FILE* CUPSManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
+{
+ SAL_INFO( "vcl.unx.print", "startSpool: " << rPrintername << " " << (bQuickCommand ? "true" : "false") );
+
+ if( m_aCUPSDestMap.find( rPrintername ) == m_aCUPSDestMap.end() )
+ {
+ SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::startSpool" );
+ return PrinterInfoManager::startSpool( rPrintername, bQuickCommand );
+ }
+
+ OUString aTmpURL, aTmpFile;
+ osl_createTempFile( nullptr, nullptr, &aTmpURL.pData );
+ osl_getSystemPathFromFileURL( aTmpURL.pData, &aTmpFile.pData );
+ OString aSysFile = OUStringToOString( aTmpFile, osl_getThreadTextEncoding() );
+ FILE* fp = fopen( aSysFile.getStr(), "w" );
+ if( fp )
+ m_aSpoolFiles[fp] = aSysFile;
+
+ return fp;
+}
+
+namespace {
+
+struct less_ppd_key
+{
+ bool operator()(const PPDKey* left, const PPDKey* right)
+ { return left->getOrderDependency() < right->getOrderDependency(); }
+};
+
+}
+
+void CUPSManager::getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, int& rNumOptions, void** rOptions )
+{
+ rNumOptions = 0;
+ *rOptions = nullptr;
+
+ // emit features ordered to OrderDependency
+ // ignore features that are set to default
+
+ // sanity check
+ if( rJob.m_pParser == rJob.m_aContext.getParser() && rJob.m_pParser )
+ {
+ std::size_t i;
+ std::size_t nKeys = rJob.m_aContext.countValuesModified();
+ ::std::vector< const PPDKey* > aKeys( nKeys );
+ for( i = 0; i < nKeys; i++ )
+ aKeys[i] = rJob.m_aContext.getModifiedKey( i );
+ ::std::sort( aKeys.begin(), aKeys.end(), less_ppd_key() );
+
+ for( i = 0; i < nKeys; i++ )
+ {
+ const PPDKey* pKey = aKeys[i];
+ const PPDValue* pValue = rJob.m_aContext.getValue( pKey );
+ OUString sPayLoad;
+ if (pValue && pValue->m_eType == eInvocation)
+ {
+ sPayLoad = pValue->m_bCustomOption ? pValue->m_aCustomOption : pValue->m_aOption;
+ }
+
+ if (!sPayLoad.isEmpty())
+ {
+ OString aKey = OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US );
+ OString aValue = OUStringToOString( sPayLoad, RTL_TEXTENCODING_ASCII_US );
+ rNumOptions = cupsAddOption( aKey.getStr(), aValue.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
+ }
+ }
+ }
+
+ if( rJob.m_nCopies > 1 )
+ {
+ OString aVal( OString::number( rJob.m_nCopies ) );
+ rNumOptions = cupsAddOption( "copies", aVal.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
+ aVal = OString::boolean(rJob.m_bCollate);
+ rNumOptions = cupsAddOption( "collate", aVal.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
+ }
+ if( ! bBanner )
+ {
+ rNumOptions = cupsAddOption( "job-sheets", "none", rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
+ }
+}
+
+namespace
+{
+ class RTSPWDialog : public weld::GenericDialogController
+ {
+ std::unique_ptr<weld::Label> m_xText;
+ std::unique_ptr<weld::Label> m_xDomainLabel;
+ std::unique_ptr<weld::Entry> m_xDomainEdit;
+ std::unique_ptr<weld::Label> m_xUserLabel;
+ std::unique_ptr<weld::Entry> m_xUserEdit;
+ std::unique_ptr<weld::Label> m_xPassLabel;
+ std::unique_ptr<weld::Entry> m_xPassEdit;
+
+ public:
+ RTSPWDialog(weld::Window* pParent, std::string_view rServer, std::string_view rUserName);
+
+ OString getDomain() const
+ {
+ return OUStringToOString( m_xDomainEdit->get_text(), osl_getThreadTextEncoding() );
+ }
+
+ OString getUserName() const
+ {
+ return OUStringToOString( m_xUserEdit->get_text(), osl_getThreadTextEncoding() );
+ }
+
+ OString getPassword() const
+ {
+ return OUStringToOString( m_xPassEdit->get_text(), osl_getThreadTextEncoding() );
+ }
+
+ void SetDomainVisible(bool bShow)
+ {
+ m_xDomainLabel->set_visible(bShow);
+ m_xDomainEdit->set_visible(bShow);
+ }
+
+ void SetUserVisible(bool bShow)
+ {
+ m_xUserLabel->set_visible(bShow);
+ m_xUserEdit->set_visible(bShow);
+ }
+
+ void SetPassVisible(bool bShow)
+ {
+ m_xPassLabel->set_visible(bShow);
+ m_xPassEdit->set_visible(bShow);
+ }
+ };
+
+ RTSPWDialog::RTSPWDialog(weld::Window* pParent, std::string_view rServer, std::string_view rUserName)
+ : GenericDialogController(pParent, "vcl/ui/cupspassworddialog.ui", "CUPSPasswordDialog")
+ , m_xText(m_xBuilder->weld_label("text"))
+ , m_xDomainLabel(m_xBuilder->weld_label("label3"))
+ , m_xDomainEdit(m_xBuilder->weld_entry("domain"))
+ , m_xUserLabel(m_xBuilder->weld_label("label1"))
+ , m_xUserEdit(m_xBuilder->weld_entry("user"))
+ , m_xPassLabel(m_xBuilder->weld_label("label2"))
+ , m_xPassEdit(m_xBuilder->weld_entry("pass"))
+ {
+ OUString aText(m_xText->get_label());
+ aText = aText.replaceFirst("%s", OStringToOUString(rServer, osl_getThreadTextEncoding()));
+ m_xText->set_label(aText);
+ m_xDomainEdit->set_text("WORKGROUP");
+ if (rUserName.empty())
+ m_xUserEdit->grab_focus();
+ else
+ {
+ m_xUserEdit->set_text(OStringToOUString(rUserName, osl_getThreadTextEncoding()));
+ m_xPassEdit->grab_focus();
+ }
+ }
+
+ bool AuthenticateQuery(std::string_view rServer, OString& rUserName, OString& rPassword)
+ {
+ bool bRet = false;
+
+ RTSPWDialog aDialog(Application::GetDefDialogParent(), rServer, rUserName);
+ if (aDialog.run() == RET_OK)
+ {
+ rUserName = aDialog.getUserName();
+ rPassword = aDialog.getPassword();
+ bRet = true;
+ }
+
+ return bRet;
+ }
+}
+
+namespace
+{
+ OString EscapeCupsOption(const OString& rIn)
+ {
+ OStringBuffer sRet;
+ sal_Int32 nLen = rIn.getLength();
+ for (sal_Int32 i = 0; i < nLen; ++i)
+ {
+ switch(rIn[i])
+ {
+ case '\\':
+ case '\'':
+ case '\"':
+ case ',':
+ case ' ':
+ case '\f':
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\v':
+ sRet.append('\\');
+ break;
+ }
+ sRet.append(rIn[i]);
+ }
+ return sRet.makeStringAndClear();
+ }
+}
+
+bool CUPSManager::endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber )
+{
+ SAL_INFO( "vcl.unx.print", "endSpool: " << rPrintername << "," << rJobTitle << " copy count = " << rDocumentJobData.m_nCopies );
+
+ int nJobID = 0;
+
+ osl::MutexGuard aGuard( m_aCUPSMutex );
+
+ std::unordered_map< OUString, int >::iterator dest_it =
+ m_aCUPSDestMap.find( rPrintername );
+ if( dest_it == m_aCUPSDestMap.end() )
+ {
+ SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::endSpool" );
+ return PrinterInfoManager::endSpool( rPrintername, rJobTitle, pFile, rDocumentJobData, bBanner, rFaxNumber );
+ }
+
+ std::unordered_map< FILE*, OString, FPtrHash >::const_iterator it = m_aSpoolFiles.find( pFile );
+ if( it != m_aSpoolFiles.end() )
+ {
+ fclose( pFile );
+ rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
+
+ // setup cups options
+ int nNumOptions = 0;
+ cups_option_t* pOptions = nullptr;
+ auto ppOptions = reinterpret_cast<void**>(&pOptions);
+ getOptionsFromDocumentSetup( rDocumentJobData, bBanner, nNumOptions, ppOptions );
+
+ PrinterInfo aInfo(getPrinterInfo(rPrintername));
+ if (!aInfo.m_aAuthInfoRequired.isEmpty())
+ {
+ bool bDomain(false), bUser(false), bPass(false);
+ sal_Int32 nIndex = 0;
+ do
+ {
+ std::u16string_view aToken = o3tl::getToken(aInfo.m_aAuthInfoRequired, 0, ',', nIndex);
+ if (aToken == u"domain")
+ bDomain = true;
+ else if (aToken == u"username")
+ bUser = true;
+ else if (aToken == u"password")
+ bPass = true;
+ }
+ while (nIndex >= 0);
+
+ if (bDomain || bUser || bPass)
+ {
+ OString sPrinterName(OUStringToOString(rPrintername, RTL_TEXTENCODING_UTF8));
+ OString sUser = cupsUser();
+ RTSPWDialog aDialog(Application::GetDefDialogParent(), sPrinterName, sUser);
+ aDialog.SetDomainVisible(bDomain);
+ aDialog.SetUserVisible(bUser);
+ aDialog.SetPassVisible(bPass);
+
+ if (aDialog.run() == RET_OK)
+ {
+ OString sAuth;
+ if (bDomain)
+ sAuth = EscapeCupsOption(aDialog.getDomain());
+ if (bUser)
+ {
+ if (bDomain)
+ sAuth += ",";
+ sAuth += EscapeCupsOption(aDialog.getUserName());
+ }
+ if (bPass)
+ {
+ if (bUser || bDomain)
+ sAuth += ",";
+ sAuth += EscapeCupsOption(aDialog.getPassword());
+ }
+ nNumOptions = cupsAddOption("auth-info", sAuth.getStr(), nNumOptions, &pOptions);
+ }
+ }
+ }
+
+ OString sJobName(OUStringToOString(rJobTitle, aEnc));
+
+ //fax4CUPS, "the job name will be dialled for you"
+ //so override the jobname with the desired number
+ if (!rFaxNumber.isEmpty())
+ {
+ sJobName = OUStringToOString(rFaxNumber, aEnc);
+ }
+
+ cups_dest_t* pDest = m_pDests + dest_it->second;
+ nJobID = cupsPrintFile(pDest->name,
+ it->second.getStr(),
+ sJobName.getStr(),
+ nNumOptions, pOptions);
+ SAL_INFO("vcl.unx.print", "cupsPrintFile( " << pDest->name << ", "
+ << it->second << ", " << rJobTitle << ", " << nNumOptions
+ << ", " << pOptions << " ) returns " << nJobID);
+ for( int n = 0; n < nNumOptions; n++ )
+ SAL_INFO("vcl.unx.print",
+ " option " << pOptions[n].name << "=" << pOptions[n].value);
+#if OSL_DEBUG_LEVEL > 1
+ OString aCmd( "cp " );
+ aCmd += it->second.getStr();
+ aCmd += OString( " $HOME/cupsprint.ps" );
+ system( aCmd.getStr() );
+#endif
+
+ unlink( it->second.getStr() );
+ m_aSpoolFiles.erase(it);
+ if( pOptions )
+ cupsFreeOptions( nNumOptions, pOptions );
+ }
+
+ return nJobID != 0;
+}
+
+bool CUPSManager::checkPrintersChanged( bool bWait )
+{
+ bool bChanged = false;
+ if( bWait )
+ {
+ if( m_aDestThread )
+ {
+ // initial asynchronous detection still running
+ SAL_INFO("vcl.unx.print", "syncing cups discovery thread");
+ osl_joinWithThread( m_aDestThread );
+ osl_destroyThread( m_aDestThread );
+ m_aDestThread = nullptr;
+ SAL_INFO("vcl.unx.print", "done: syncing cups discovery thread");
+ }
+ else
+ {
+ // #i82321# check for cups printer updates
+ // with this change the whole asynchronous detection in a thread is
+ // almost useless. The only relevance left is for some stalled systems
+ // where the user can set SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION
+ // (see vcl/unx/source/gdi/salprnpsp.cxx)
+ // so that checkPrintersChanged( true ) will never be called
+
+ // there is no way to query CUPS whether the printer list has changed
+ // so get the dest list anew
+ if( m_nDests && m_pDests )
+ cupsFreeDests( m_nDests, m_pDests );
+ m_nDests = 0;
+ m_pDests = nullptr;
+ runDests();
+ }
+ }
+ if( m_aCUPSMutex.tryToAcquire() )
+ {
+ bChanged = m_bNewDests;
+ m_aCUPSMutex.release();
+ }
+
+ if( ! bChanged )
+ {
+ bChanged = PrinterInfoManager::checkPrintersChanged( bWait );
+ // #i54375# ensure new merging with CUPS list in :initialize
+ if( bChanged )
+ m_bNewDests = true;
+ }
+
+ if( bChanged )
+ initialize();
+
+ return bChanged;
+}
+
+const char* CUPSManager::authenticateUser()
+{
+ const char* pRet = nullptr;
+
+ osl::MutexGuard aGuard( m_aCUPSMutex );
+
+ OString aUser = cupsUser();
+ OString aServer = cupsServer();
+ OString aPassword;
+ if (AuthenticateQuery(aServer, aUser, aPassword))
+ {
+ m_aPassword = aPassword;
+ m_aUser = aUser;
+ cupsSetUser( m_aUser.getStr() );
+ pRet = m_aPassword.getStr();
+ }
+
+ return pRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/printer/jobdata.cxx b/vcl/unx/generic/printer/jobdata.cxx
new file mode 100644
index 0000000000..5fc6a78700
--- /dev/null
+++ b/vcl/unx/generic/printer/jobdata.cxx
@@ -0,0 +1,238 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <officecfg/Office/Common.hxx>
+#include <jobdata.hxx>
+#include <printerinfomanager.hxx>
+#include <tools/stream.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <memory>
+
+using namespace psp;
+
+JobData& JobData::operator=(const JobData& rRight)
+{
+ if(this == &rRight)
+ return *this;
+
+ m_nCopies = rRight.m_nCopies;
+ m_bCollate = rRight.m_bCollate;
+ m_nLeftMarginAdjust = rRight.m_nLeftMarginAdjust;
+ m_nRightMarginAdjust = rRight.m_nRightMarginAdjust;
+ m_nTopMarginAdjust = rRight.m_nTopMarginAdjust;
+ m_nBottomMarginAdjust = rRight.m_nBottomMarginAdjust;
+ m_nColorDepth = rRight.m_nColorDepth;
+ m_eOrientation = rRight.m_eOrientation;
+ m_aPrinterName = rRight.m_aPrinterName;
+ m_bPapersizeFromSetup = rRight.m_bPapersizeFromSetup;
+ m_pParser = rRight.m_pParser;
+ m_aContext = rRight.m_aContext;
+ m_nColorDevice = rRight.m_nColorDevice;
+
+ if( !m_pParser && !m_aPrinterName.isEmpty() )
+ {
+ PrinterInfoManager& rMgr = PrinterInfoManager::get();
+ rMgr.setupJobContextData( *this );
+ }
+ return *this;
+}
+
+void JobData::setCollate( bool bCollate )
+{
+ m_bCollate = bCollate;
+ return;
+}
+
+void JobData::setPaper( int i_nWidth, int i_nHeight )
+{
+ if( m_pParser )
+ {
+ OUString aPaper( m_pParser->matchPaper( i_nWidth, i_nHeight ) );
+
+ const PPDKey* pKey = m_pParser->getKey( "PageSize" );
+ const PPDValue* pValue = pKey ? pKey->getValueCaseInsensitive( aPaper ) : nullptr;
+
+ if (pKey && pValue)
+ m_aContext.setValue( pKey, pValue );
+ }
+}
+
+void JobData::setPaperBin( int i_nPaperBin )
+{
+ if( m_pParser )
+ {
+ const PPDKey* pKey = m_pParser->getKey( "InputSlot" );
+ const PPDValue* pValue = pKey ? pKey->getValue( i_nPaperBin ) : nullptr;
+
+ if (pKey && pValue)
+ m_aContext.setValue( pKey, pValue );
+ }
+}
+
+bool JobData::getStreamBuffer( std::unique_ptr<sal_uInt8[]>& pData, sal_uInt32& bytes )
+{
+ // consistency checks
+ if( ! m_pParser )
+ m_pParser = m_aContext.getParser();
+ if( m_pParser != m_aContext.getParser() ||
+ ! m_pParser )
+ return false;
+
+ SvMemoryStream aStream;
+
+ // write header job data
+ aStream.WriteLine("JobData 1");
+
+ OStringBuffer aLine("printer="
+ + OUStringToOString(m_aPrinterName, RTL_TEXTENCODING_UTF8));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("orientation=");
+ if (m_eOrientation == orientation::Landscape)
+ aLine.append("Landscape");
+ else
+ aLine.append("Portrait");
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aStream.WriteLine(Concat2View("copies=" + OString::number(static_cast<sal_Int32>(m_nCopies))));
+ aStream.WriteLine(Concat2View("collate=" + OString::boolean(m_bCollate)));
+
+ aStream.WriteLine(Concat2View(
+ "marginadjustment="
+ + OString::number(static_cast<sal_Int32>(m_nLeftMarginAdjust))
+ + ","
+ + OString::number(static_cast<sal_Int32>(m_nRightMarginAdjust))
+ + ",'"
+ + OString::number(static_cast<sal_Int32>(m_nTopMarginAdjust))
+ + ","
+ + OString::number(static_cast<sal_Int32>(m_nBottomMarginAdjust))));
+
+ aStream.WriteLine(Concat2View("colordepth=" + OString::number(static_cast<sal_Int32>(m_nColorDepth))));
+
+ aStream.WriteLine(Concat2View("colordevice=" + OString::number(static_cast<sal_Int32>(m_nColorDevice))));
+
+ // now append the PPDContext stream buffer
+ aStream.WriteLine( "PPDContextData" );
+ sal_uLong nBytes;
+ std::unique_ptr<char[]> pContextBuffer(m_aContext.getStreamableBuffer( nBytes ));
+ if( nBytes )
+ aStream.WriteBytes( pContextBuffer.get(), nBytes );
+ pContextBuffer.reset();
+
+ // success
+ bytes = static_cast<sal_uInt32>(aStream.Tell());
+ pData = std::make_unique<sal_uInt8[]>( bytes );
+ memcpy( pData.get(), aStream.GetData(), bytes );
+ return true;
+}
+
+bool JobData::constructFromStreamBuffer( const void* pData, sal_uInt32 bytes, JobData& rJobData )
+{
+ SvMemoryStream aStream( const_cast<void*>(pData), bytes, StreamMode::READ );
+ OString aLine;
+ bool bVersion = false;
+ bool bPrinter = false;
+ bool bOrientation = false;
+ bool bCopies = false;
+ bool bContext = false;
+ bool bMargin = false;
+ bool bColorDepth = false;
+ bool bColorDevice = false;
+
+ const char printerEquals[] = "printer=";
+ const char orientatationEquals[] = "orientation=";
+ const char copiesEquals[] = "copies=";
+ const char collateEquals[] = "collate=";
+ const char marginadjustmentEquals[] = "marginadjustment=";
+ const char colordepthEquals[] = "colordepth=";
+ const char colordeviceEquals[] = "colordevice=";
+
+ while( ! aStream.eof() )
+ {
+ aStream.ReadLine( aLine );
+ if (aLine.startsWith("JobData"))
+ bVersion = true;
+ else if (aLine.startsWith(printerEquals))
+ {
+ bPrinter = true;
+ rJobData.m_aPrinterName = OStringToOUString(aLine.subView(RTL_CONSTASCII_LENGTH(printerEquals)), RTL_TEXTENCODING_UTF8);
+ }
+ else if (aLine.startsWith(orientatationEquals))
+ {
+ bOrientation = true;
+ rJobData.m_eOrientation = o3tl::equalsIgnoreAsciiCase(aLine.subView(RTL_CONSTASCII_LENGTH(orientatationEquals)), "landscape") ? orientation::Landscape : orientation::Portrait;
+ }
+ else if (aLine.startsWith(copiesEquals))
+ {
+ bCopies = true;
+ rJobData.m_nCopies = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(copiesEquals)));
+ }
+ else if (aLine.startsWith(collateEquals))
+ {
+ rJobData.m_bCollate = aLine.copy(RTL_CONSTASCII_LENGTH(collateEquals)).toBoolean();
+ }
+ else if (aLine.startsWith(marginadjustmentEquals))
+ {
+ bMargin = true;
+ sal_Int32 nIdx {RTL_CONSTASCII_LENGTH(marginadjustmentEquals)};
+ rJobData.m_nLeftMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx));
+ rJobData.m_nRightMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx));
+ rJobData.m_nTopMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx));
+ rJobData.m_nBottomMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx));
+ }
+ else if (aLine.startsWith(colordepthEquals))
+ {
+ bColorDepth = true;
+ rJobData.m_nColorDepth = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(colordepthEquals)));
+ }
+ else if (aLine.startsWith(colordeviceEquals))
+ {
+ bColorDevice = true;
+ rJobData.m_nColorDevice = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(colordeviceEquals)));
+ }
+ else if (aLine == "PPDContextData" && bPrinter)
+ {
+ PrinterInfoManager& rManager = PrinterInfoManager::get();
+ const PrinterInfo& rInfo = rManager.getPrinterInfo( rJobData.m_aPrinterName );
+ rJobData.m_pParser = PPDParser::getParser( rInfo.m_aDriverName );
+ if( rJobData.m_pParser )
+ {
+ rJobData.m_aContext.setParser( rJobData.m_pParser );
+ sal_uInt64 nBytes = bytes - aStream.Tell();
+ std::vector<char> aRemain(nBytes+1);
+ nBytes = aStream.ReadBytes(aRemain.data(), nBytes);
+ if (nBytes)
+ {
+ aRemain.resize(nBytes+1);
+ aRemain[nBytes] = 0;
+ rJobData.m_aContext.rebuildFromStreamBuffer(aRemain);
+ bContext = true;
+ }
+ }
+ }
+ }
+
+ return bVersion && bPrinter && bOrientation && bCopies && bContext && bMargin && bColorDevice && bColorDepth;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/printer/ppdparser.cxx b/vcl/unx/generic/printer/ppdparser.cxx
new file mode 100644
index 0000000000..2474da3895
--- /dev/null
+++ b/vcl/unx/generic/printer/ppdparser.cxx
@@ -0,0 +1,1951 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <stdlib.h>
+
+#include <comphelper/string.hxx>
+#include <o3tl/string_view.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <ppdparser.hxx>
+#include <strhelper.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+
+#include <unx/helper.hxx>
+#include <unx/cupsmgr.hxx>
+#include <unx/cpdmgr.hxx>
+
+#include <tools/urlobj.hxx>
+#include <tools/stream.hxx>
+#include <tools/zcodec.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <osl/thread.h>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <salhelper/linkhelper.hxx>
+
+#include <com/sun/star/lang/Locale.hpp>
+
+#include <mutex>
+#include <unordered_map>
+
+#ifdef ENABLE_CUPS
+#include <cups/cups.h>
+#endif
+
+#include <config_dbus.h>
+#include <config_gio.h>
+#include <o3tl/hash_combine.hxx>
+
+namespace psp
+{
+ class PPDTranslator
+ {
+ struct LocaleEqual
+ {
+ bool operator()(const css::lang::Locale& i_rLeft,
+ const css::lang::Locale& i_rRight) const
+ {
+ return i_rLeft.Language == i_rRight.Language &&
+ i_rLeft.Country == i_rRight.Country &&
+ i_rLeft.Variant == i_rRight.Variant;
+ }
+ };
+
+ struct LocaleHash
+ {
+ size_t operator()(const css::lang::Locale& rLocale) const
+ {
+ std::size_t seed = 0;
+ o3tl::hash_combine(seed, rLocale.Language.hashCode());
+ o3tl::hash_combine(seed, rLocale.Country.hashCode());
+ o3tl::hash_combine(seed, rLocale.Variant.hashCode());
+ return seed;
+ }
+ };
+
+ typedef std::unordered_map< css::lang::Locale, OUString, LocaleHash, LocaleEqual > translation_map;
+ typedef std::unordered_map< OUString, translation_map > key_translation_map;
+
+ key_translation_map m_aTranslations;
+ public:
+ PPDTranslator() {}
+
+ void insertValue(
+ std::u16string_view i_rKey,
+ std::u16string_view i_rOption,
+ std::u16string_view i_rValue,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale
+ );
+
+ void insertOption( std::u16string_view i_rKey,
+ std::u16string_view i_rOption,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale )
+ {
+ insertValue( i_rKey, i_rOption, u"", i_rTranslation, i_rLocale );
+ }
+
+ void insertKey( std::u16string_view i_rKey,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale = css::lang::Locale() )
+ {
+ insertValue( i_rKey, u"", u"", i_rTranslation, i_rLocale );
+ }
+
+ OUString translateValue(
+ std::u16string_view i_rKey,
+ std::u16string_view i_rOption
+ ) const;
+
+ OUString translateOption( std::u16string_view i_rKey,
+ std::u16string_view i_rOption ) const
+ {
+ return translateValue( i_rKey, i_rOption );
+ }
+
+ OUString translateKey( std::u16string_view i_rKey ) const
+ {
+ return translateValue( i_rKey, u"" );
+ }
+ };
+
+ static css::lang::Locale normalizeInputLocale(
+ const css::lang::Locale& i_rLocale
+ )
+ {
+ css::lang::Locale aLoc( i_rLocale );
+ if( aLoc.Language.isEmpty() )
+ {
+ // empty locale requested, fill in application UI locale
+ aLoc = Application::GetSettings().GetUILanguageTag().getLocale();
+
+ #if OSL_DEBUG_LEVEL > 1
+ static const char* pEnvLocale = getenv( "SAL_PPDPARSER_LOCALE" );
+ if( pEnvLocale && *pEnvLocale )
+ {
+ OString aStr( pEnvLocale );
+ sal_Int32 nLen = aStr.getLength();
+ aLoc.Language = OStringToOUString( aStr.copy( 0, std::min(nLen, 2) ), RTL_TEXTENCODING_MS_1252 );
+ if( nLen >=5 && aStr[2] == '_' )
+ aLoc.Country = OStringToOUString( aStr.copy( 3, 2 ), RTL_TEXTENCODING_MS_1252 );
+ else
+ aLoc.Country.clear();
+ aLoc.Variant.clear();
+ }
+ #endif
+ }
+ /* FIXME-BCP47: using Variant, uppercase? */
+ aLoc.Language = aLoc.Language.toAsciiLowerCase();
+ aLoc.Country = aLoc.Country.toAsciiUpperCase();
+ aLoc.Variant = aLoc.Variant.toAsciiUpperCase();
+
+ return aLoc;
+ }
+
+ void PPDTranslator::insertValue(
+ std::u16string_view i_rKey,
+ std::u16string_view i_rOption,
+ std::u16string_view i_rValue,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale
+ )
+ {
+ OUStringBuffer aKey( i_rKey.size() + i_rOption.size() + i_rValue.size() + 2 );
+ aKey.append( i_rKey );
+ if( !i_rOption.empty() || !i_rValue.empty() )
+ {
+ aKey.append( OUString::Concat(":") + i_rOption );
+ }
+ if( !i_rValue.empty() )
+ {
+ aKey.append( OUString::Concat(":") + i_rValue );
+ }
+ if( !aKey.isEmpty() && !i_rTranslation.isEmpty() )
+ {
+ OUString aK( aKey.makeStringAndClear() );
+ css::lang::Locale aLoc;
+ /* FIXME-BCP47: using Variant, uppercase? */
+ aLoc.Language = i_rLocale.Language.toAsciiLowerCase();
+ aLoc.Country = i_rLocale.Country.toAsciiUpperCase();
+ aLoc.Variant = i_rLocale.Variant.toAsciiUpperCase();
+ m_aTranslations[ aK ][ aLoc ] = i_rTranslation;
+ }
+ }
+
+ OUString PPDTranslator::translateValue(
+ std::u16string_view i_rKey,
+ std::u16string_view i_rOption
+ ) const
+ {
+ OUString aResult;
+
+ OUStringBuffer aKey( i_rKey.size() + i_rOption.size() + 2 );
+ aKey.append( i_rKey );
+ if( !i_rOption.empty() )
+ {
+ aKey.append( OUString::Concat(":") + i_rOption );
+ }
+ if( !aKey.isEmpty() )
+ {
+ OUString aK( aKey.makeStringAndClear() );
+ key_translation_map::const_iterator it = m_aTranslations.find( aK );
+ if( it != m_aTranslations.end() )
+ {
+ const translation_map& rMap( it->second );
+
+ css::lang::Locale aLoc( normalizeInputLocale( css::lang::Locale() ) );
+ /* FIXME-BCP47: use LanguageTag::getFallbackStrings()? */
+ for( int nTry = 0; nTry < 4; nTry++ )
+ {
+ translation_map::const_iterator tr = rMap.find( aLoc );
+ if( tr != rMap.end() )
+ {
+ aResult = tr->second;
+ break;
+ }
+ switch( nTry )
+ {
+ case 0: aLoc.Variant.clear();break;
+ case 1: aLoc.Country.clear();break;
+ case 2: aLoc.Language.clear();break;
+ }
+ }
+ }
+ }
+ return aResult;
+ }
+
+ class PPDCache
+ {
+ public:
+ std::vector< std::unique_ptr<PPDParser> > aAllParsers;
+ std::optional<std::unordered_map< OUString, OUString >> xAllPPDFiles;
+ };
+}
+
+using namespace psp;
+
+namespace
+{
+ PPDCache& getPPDCache()
+ {
+ static PPDCache thePPDCache;
+ return thePPDCache;
+ }
+
+class PPDDecompressStream
+{
+private:
+ PPDDecompressStream(const PPDDecompressStream&) = delete;
+ PPDDecompressStream& operator=(const PPDDecompressStream&) = delete;
+
+ std::unique_ptr<SvFileStream> mpFileStream;
+ std::unique_ptr<SvMemoryStream> mpMemStream;
+ OUString maFileName;
+
+public:
+ explicit PPDDecompressStream( const OUString& rFile );
+ ~PPDDecompressStream();
+
+ bool IsOpen() const;
+ bool eof() const;
+ OString ReadLine();
+ void Open( const OUString& i_rFile );
+ void Close();
+ const OUString& GetFileName() const { return maFileName; }
+};
+
+}
+
+PPDDecompressStream::PPDDecompressStream( const OUString& i_rFile )
+{
+ Open( i_rFile );
+}
+
+PPDDecompressStream::~PPDDecompressStream()
+{
+ Close();
+}
+
+void PPDDecompressStream::Open( const OUString& i_rFile )
+{
+ Close();
+
+ mpFileStream.reset( new SvFileStream( i_rFile, StreamMode::READ ) );
+ maFileName = mpFileStream->GetFileName();
+
+ if( ! mpFileStream->IsOpen() )
+ {
+ Close();
+ return;
+ }
+
+ OString aLine;
+ mpFileStream->ReadLine( aLine );
+ mpFileStream->Seek( 0 );
+
+ // check for compress'ed or gzip'ed file
+ if( aLine.getLength() <= 1 ||
+ static_cast<unsigned char>(aLine[0]) != 0x1f ||
+ static_cast<unsigned char>(aLine[1]) != 0x8b /* check for gzip */ )
+ return;
+
+ // so let's try to decompress the stream
+ mpMemStream.reset( new SvMemoryStream( 4096, 4096 ) );
+ ZCodec aCodec;
+ aCodec.BeginCompression( ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true );
+ tools::Long nComp = aCodec.Decompress( *mpFileStream, *mpMemStream );
+ aCodec.EndCompression();
+ if( nComp < 0 )
+ {
+ // decompression failed, must be an uncompressed stream after all
+ mpMemStream.reset();
+ mpFileStream->Seek( 0 );
+ }
+ else
+ {
+ // compression successful, can get rid of file stream
+ mpFileStream.reset();
+ mpMemStream->Seek( 0 );
+ }
+}
+
+void PPDDecompressStream::Close()
+{
+ mpMemStream.reset();
+ mpFileStream.reset();
+}
+
+bool PPDDecompressStream::IsOpen() const
+{
+ return (mpMemStream || (mpFileStream && mpFileStream->IsOpen()));
+}
+
+bool PPDDecompressStream::eof() const
+{
+ return ( mpMemStream ? mpMemStream->eof() : ( mpFileStream == nullptr || mpFileStream->eof() ) );
+}
+
+OString PPDDecompressStream::ReadLine()
+{
+ OString o_rLine;
+ if( mpMemStream )
+ mpMemStream->ReadLine( o_rLine );
+ else if( mpFileStream )
+ mpFileStream->ReadLine( o_rLine );
+ return o_rLine;
+}
+
+static osl::FileBase::RC resolveLink( const OUString& i_rURL, OUString& o_rResolvedURL, OUString& o_rBaseName, osl::FileStatus::Type& o_rType)
+{
+ salhelper::LinkResolver aResolver(osl_FileStatus_Mask_FileName |
+ osl_FileStatus_Mask_Type |
+ osl_FileStatus_Mask_FileURL);
+
+ osl::FileBase::RC aRet = aResolver.fetchFileStatus(i_rURL, 10/*nLinkLevel*/);
+
+ if (aRet == osl::FileBase::E_None)
+ {
+ o_rResolvedURL = aResolver.m_aStatus.getFileURL();
+ o_rBaseName = aResolver.m_aStatus.getFileName();
+ o_rType = aResolver.m_aStatus.getFileType();
+ }
+
+ return aRet;
+}
+
+void PPDParser::scanPPDDir( const OUString& rDir )
+{
+ static struct suffix_t
+ {
+ const char* pSuffix;
+ const sal_Int32 nSuffixLen;
+ } const pSuffixes[] =
+ { { ".PS", 3 }, { ".PPD", 4 }, { ".PS.GZ", 6 }, { ".PPD.GZ", 7 } };
+
+ PPDCache &rPPDCache = getPPDCache();
+
+ osl::Directory aDir( rDir );
+ if ( aDir.open() != osl::FileBase::E_None )
+ return;
+
+ osl::DirectoryItem aItem;
+
+ INetURLObject aPPDDir(rDir);
+ while( aDir.getNextItem( aItem ) == osl::FileBase::E_None )
+ {
+ osl::FileStatus aStatus( osl_FileStatus_Mask_FileName );
+ if( aItem.getFileStatus( aStatus ) == osl::FileBase::E_None )
+ {
+ OUString aFileURL, aFileName;
+ osl::FileStatus::Type eType = osl::FileStatus::Unknown;
+ OUString aURL = rDir + "/" + aStatus.getFileName();
+
+ if(resolveLink( aURL, aFileURL, aFileName, eType ) == osl::FileBase::E_None)
+ {
+ if( eType == osl::FileStatus::Regular )
+ {
+ INetURLObject aPPDFile = aPPDDir;
+ aPPDFile.Append( aFileName );
+
+ // match extension
+ for(const suffix_t & rSuffix : pSuffixes)
+ {
+ if( aFileName.getLength() > rSuffix.nSuffixLen )
+ {
+ if( aFileName.endsWithIgnoreAsciiCaseAsciiL( rSuffix.pSuffix, rSuffix.nSuffixLen ) )
+ {
+ (*rPPDCache.xAllPPDFiles)[ aFileName.copy( 0, aFileName.getLength() - rSuffix.nSuffixLen ) ] = aPPDFile.PathToFileName();
+ break;
+ }
+ }
+ }
+ }
+ else if( eType == osl::FileStatus::Directory )
+ {
+ scanPPDDir( aFileURL );
+ }
+ }
+ }
+ }
+ aDir.close();
+}
+
+void PPDParser::initPPDFiles(PPDCache &rPPDCache)
+{
+ if( rPPDCache.xAllPPDFiles )
+ return;
+
+ rPPDCache.xAllPPDFiles.emplace();
+
+ // check installation directories
+ std::vector< OUString > aPathList;
+ psp::getPrinterPathList( aPathList, PRINTER_PPDDIR );
+ for (auto const& path : aPathList)
+ {
+ INetURLObject aPPDDir( path, INetProtocol::File, INetURLObject::EncodeMechanism::All );
+ scanPPDDir( aPPDDir.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+ }
+ if( rPPDCache.xAllPPDFiles->find( OUString( "SGENPRT" ) ) != rPPDCache.xAllPPDFiles->end() )
+ return;
+
+ // last try: search in directory of executable (mainly for setup)
+ OUString aExe;
+ if( osl_getExecutableFile( &aExe.pData ) == osl_Process_E_None )
+ {
+ INetURLObject aDir( aExe );
+ aDir.removeSegment();
+ SAL_INFO("vcl.unx.print", "scanning last chance dir: "
+ << aDir.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ scanPPDDir( aDir.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+ SAL_INFO("vcl.unx.print", "SGENPRT "
+ << (rPPDCache.xAllPPDFiles->find("SGENPRT") ==
+ rPPDCache.xAllPPDFiles->end() ? "not found" : "found"));
+ }
+}
+
+OUString PPDParser::getPPDFile( const OUString& rFile )
+{
+ INetURLObject aPPD( rFile, INetProtocol::File, INetURLObject::EncodeMechanism::All );
+ // someone might enter a full qualified name here
+ PPDDecompressStream aStream( aPPD.PathToFileName() );
+ if( ! aStream.IsOpen() )
+ {
+ std::unordered_map< OUString, OUString >::const_iterator it;
+ PPDCache &rPPDCache = getPPDCache();
+
+ bool bRetry = true;
+ do
+ {
+ initPPDFiles(rPPDCache);
+ // some PPD files contain dots beside the extension, so try name first
+ // and cut of points after that
+ OUString aBase( rFile );
+ sal_Int32 nLastIndex = aBase.lastIndexOf( '/' );
+ if( nLastIndex >= 0 )
+ aBase = aBase.copy( nLastIndex+1 );
+ do
+ {
+ it = rPPDCache.xAllPPDFiles->find( aBase );
+ nLastIndex = aBase.lastIndexOf( '.' );
+ if( nLastIndex > 0 )
+ aBase = aBase.copy( 0, nLastIndex );
+ } while( it == rPPDCache.xAllPPDFiles->end() && nLastIndex > 0 );
+
+ if( it == rPPDCache.xAllPPDFiles->end() && bRetry )
+ {
+ // a new file ? rehash
+ rPPDCache.xAllPPDFiles.reset();
+ bRetry = false;
+ // note this is optimized for office start where
+ // no new files occur and initPPDFiles is called only once
+ }
+ } while( ! rPPDCache.xAllPPDFiles );
+
+ if( it != rPPDCache.xAllPPDFiles->end() )
+ aStream.Open( it->second );
+ }
+
+ OUString aRet;
+ if( aStream.IsOpen() )
+ {
+ OString aLine = aStream.ReadLine();
+ if (aLine.startsWith("*PPD-Adobe"))
+ aRet = aStream.GetFileName();
+ else
+ {
+ // our *Include hack does usually not begin
+ // with *PPD-Adobe, so try some lines for *Include
+ int nLines = 10;
+ while (aLine.indexOf("*Include") != 0 && --nLines)
+ aLine = aStream.ReadLine();
+ if( nLines )
+ aRet = aStream.GetFileName();
+ }
+ }
+
+ return aRet;
+}
+
+const PPDParser* PPDParser::getParser( const OUString& rFile )
+{
+ // Recursive because we can get re-entered via CUPSManager::createCUPSParser
+ static std::recursive_mutex aMutex;
+ std::scoped_lock aGuard( aMutex );
+
+ OUString aFile = rFile;
+ if( !rFile.startsWith( "CUPS:" ) && !rFile.startsWith( "CPD:" ) )
+ aFile = getPPDFile( rFile );
+ if( aFile.isEmpty() )
+ {
+ SAL_INFO("vcl.unx.print", "Could not get printer PPD file \""
+ << rFile << "\" !");
+ return nullptr;
+ }
+ else
+ SAL_INFO("vcl.unx.print", "Parsing printer info from \""
+ << rFile << "\" !");
+
+
+ PPDCache &rPPDCache = getPPDCache();
+ for( auto const & i : rPPDCache.aAllParsers )
+ if( i->m_aFile == aFile )
+ return i.get();
+
+ PPDParser* pNewParser = nullptr;
+ if( !aFile.startsWith( "CUPS:" ) && !aFile.startsWith( "CPD:" ) )
+ pNewParser = new PPDParser( aFile );
+ else
+ {
+ PrinterInfoManager& rMgr = PrinterInfoManager::get();
+ if( rMgr.getType() == PrinterInfoManager::Type::CUPS )
+ {
+#ifdef ENABLE_CUPS
+ pNewParser = const_cast<PPDParser*>(static_cast<CUPSManager&>(rMgr).createCUPSParser( aFile ));
+#endif
+ } else if ( rMgr.getType() == PrinterInfoManager::Type::CPD )
+ {
+#if ENABLE_DBUS && ENABLE_GIO
+ pNewParser = const_cast<PPDParser*>(static_cast<CPDManager&>(rMgr).createCPDParser( aFile ));
+#endif
+ }
+ }
+ if( pNewParser )
+ {
+ // this may actually be the SGENPRT parser,
+ // so ensure uniqueness here (but don't remove last we delete us!)
+ if (std::none_of(
+ rPPDCache.aAllParsers.begin(),
+ rPPDCache.aAllParsers.end(),
+ [pNewParser] (std::unique_ptr<PPDParser> const & x) { return x.get() == pNewParser; } ))
+ {
+ // insert new parser to vector
+ rPPDCache.aAllParsers.emplace_back(pNewParser);
+ }
+ }
+ return pNewParser;
+}
+
+PPDParser::PPDParser(OUString aFile, const std::vector<PPDKey*>& keys)
+ : m_aFile(std::move(aFile))
+ , m_aFileEncoding(RTL_TEXTENCODING_MS_1252)
+ , m_pImageableAreas(nullptr)
+ , m_pDefaultPaperDimension(nullptr)
+ , m_pPaperDimensions(nullptr)
+ , m_pDefaultInputSlot(nullptr)
+ , m_pDefaultResolution(nullptr)
+ , m_pTranslator(new PPDTranslator())
+{
+ for (auto & key: keys)
+ {
+ insertKey( std::unique_ptr<PPDKey>(key) );
+ }
+
+ // fill in shortcuts
+ const PPDKey* pKey;
+
+ pKey = getKey( "PageSize" );
+
+ if ( pKey ) {
+ std::unique_ptr<PPDKey> pImageableAreas(new PPDKey("ImageableArea"));
+ std::unique_ptr<PPDKey> pPaperDimensions(new PPDKey("PaperDimension"));
+#if defined(CUPS_VERSION_MAJOR)
+#if (CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR >= 7) || CUPS_VERSION_MAJOR > 1
+ for (int i = 0; i < pKey->countValues(); i++) {
+ const PPDValue* pValue = pKey -> getValue(i);
+ OUString aValueName = pValue -> m_aOption;
+ PPDValue* pImageableAreaValue = pImageableAreas -> insertValue( aValueName, eQuoted );
+ PPDValue* pPaperDimensionValue = pPaperDimensions -> insertValue( aValueName, eQuoted );
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OString o = OUStringToOString( aValueName, aEncoding );
+ pwg_media_t *pPWGMedia = pwgMediaForPWG(o.pData->buffer);
+ if (pPWGMedia != nullptr) {
+ OUStringBuffer aBuf( 256 );
+ aBuf = "0 0 " +
+ OUString::number(PWG_TO_POINTS(pPWGMedia -> width)) +
+ " " +
+ OUString::number(PWG_TO_POINTS(pPWGMedia -> length));
+ if ( pImageableAreaValue )
+ pImageableAreaValue->m_aValue = aBuf.makeStringAndClear();
+ aBuf.append( OUString::number(PWG_TO_POINTS(pPWGMedia -> width))
+ + " "
+ + OUString::number(PWG_TO_POINTS(pPWGMedia -> length) ));
+ if ( pPaperDimensionValue )
+ pPaperDimensionValue->m_aValue = aBuf.makeStringAndClear();
+ if (aValueName.equals(pKey -> getDefaultValue() -> m_aOption)) {
+ pImageableAreas -> m_pDefaultValue = pImageableAreaValue;
+ pPaperDimensions -> m_pDefaultValue = pPaperDimensionValue;
+ }
+ }
+ }
+#endif // HAVE_CUPS_API_1_7
+#endif
+ insertKey(std::move(pImageableAreas));
+ insertKey(std::move(pPaperDimensions));
+ }
+
+ m_pImageableAreas = getKey( "ImageableArea" );
+ const PPDValue* pDefaultImageableArea = nullptr;
+ if( m_pImageableAreas )
+ pDefaultImageableArea = m_pImageableAreas->getDefaultValue();
+ if (m_pImageableAreas == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no ImageableArea in " << m_aFile);
+ }
+ if (pDefaultImageableArea == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no DefaultImageableArea in " << m_aFile);
+ }
+
+ m_pPaperDimensions = getKey( "PaperDimension" );
+ if( m_pPaperDimensions )
+ m_pDefaultPaperDimension = m_pPaperDimensions->getDefaultValue();
+ if (m_pPaperDimensions == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no PaperDimensions in " << m_aFile);
+ }
+ if (m_pDefaultPaperDimension == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no DefaultPaperDimensions in " << m_aFile);
+ }
+
+ auto pResolutions = getKey( "Resolution" );
+ if( pResolutions )
+ m_pDefaultResolution = pResolutions->getDefaultValue();
+ if (pResolutions == nullptr) {
+ SAL_INFO( "vcl.unx.print", "no Resolution in " << m_aFile);
+ }
+ SAL_INFO_IF(!m_pDefaultResolution, "vcl.unx.print", "no DefaultResolution in " + m_aFile);
+
+ auto pInputSlots = getKey( "InputSlot" );
+ if( pInputSlots )
+ m_pDefaultInputSlot = pInputSlots->getDefaultValue();
+ SAL_INFO_IF(!pInputSlots, "vcl.unx.print", "no InputSlot in " << m_aFile);
+ SAL_INFO_IF(!m_pDefaultInputSlot, "vcl.unx.print", "no DefaultInputSlot in " << m_aFile);
+}
+
+PPDParser::PPDParser( OUString aFile ) :
+ m_aFile(std::move( aFile )),
+ m_aFileEncoding( RTL_TEXTENCODING_MS_1252 ),
+ m_pImageableAreas( nullptr ),
+ m_pDefaultPaperDimension( nullptr ),
+ m_pPaperDimensions( nullptr ),
+ m_pDefaultInputSlot( nullptr ),
+ m_pDefaultResolution( nullptr ),
+ m_pTranslator( new PPDTranslator() )
+{
+ // read in the file
+ std::vector< OString > aLines;
+ PPDDecompressStream aStream( m_aFile );
+ if( aStream.IsOpen() )
+ {
+ bool bLanguageEncoding = false;
+ while( ! aStream.eof() )
+ {
+ OString aCurLine = aStream.ReadLine();
+ if( aCurLine.startsWith("*") )
+ {
+ if (aCurLine.matchIgnoreAsciiCase("*include:"))
+ {
+ aCurLine = aCurLine.copy(9);
+ aCurLine = comphelper::string::strip(aCurLine, ' ');
+ aCurLine = comphelper::string::strip(aCurLine, '\t');
+ aCurLine = comphelper::string::stripEnd(aCurLine, '\r');
+ aCurLine = comphelper::string::stripEnd(aCurLine, '\n');
+ aCurLine = comphelper::string::strip(aCurLine, '"');
+ aStream.Close();
+ aStream.Open(getPPDFile(OStringToOUString(aCurLine, m_aFileEncoding)));
+ continue;
+ }
+ else if( ! bLanguageEncoding &&
+ aCurLine.matchIgnoreAsciiCase("*languageencoding") )
+ {
+ bLanguageEncoding = true; // generally only the first one counts
+ OString aLower = aCurLine.toAsciiLowerCase();
+ if( aLower.indexOf("isolatin1", 17 ) != -1 ||
+ aLower.indexOf("windowsansi", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_MS_1252;
+ else if( aLower.indexOf("isolatin2", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_ISO_8859_2;
+ else if( aLower.indexOf("isolatin5", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_ISO_8859_5;
+ else if( aLower.indexOf("jis83-rksj", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_SHIFT_JIS;
+ else if( aLower.indexOf("macstandard", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_APPLE_ROMAN;
+ else if( aLower.indexOf("utf-8", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_UTF8;
+ }
+ }
+ aLines.push_back( aCurLine );
+ }
+ }
+ aStream.Close();
+
+ // now get the Values
+ parse( aLines );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "acquired " << m_aKeys.size()
+ << " Keys from PPD " << m_aFile << ":");
+ for (auto const& key : m_aKeys)
+ {
+ const PPDKey* pKey = key.second.get();
+ char const* pSetupType = "<unknown>";
+ switch( pKey->m_eSetupType )
+ {
+ case PPDKey::SetupType::ExitServer: pSetupType = "ExitServer";break;
+ case PPDKey::SetupType::Prolog: pSetupType = "Prolog";break;
+ case PPDKey::SetupType::DocumentSetup: pSetupType = "DocumentSetup";break;
+ case PPDKey::SetupType::PageSetup: pSetupType = "PageSetup";break;
+ case PPDKey::SetupType::JCLSetup: pSetupType = "JCLSetup";break;
+ case PPDKey::SetupType::AnySetup: pSetupType = "AnySetup";break;
+ default: break;
+ }
+ SAL_INFO("vcl.unx.print", "\t\"" << pKey->getKey() << "\" ("
+ << pKey->countValues() << "values) OrderDependency: "
+ << pKey->m_nOrderDependency << pSetupType );
+ for( int j = 0; j < pKey->countValues(); j++ )
+ {
+ const PPDValue* pValue = pKey->getValue( j );
+ char const* pVType = "<unknown>";
+ switch( pValue->m_eType )
+ {
+ case eInvocation: pVType = "invocation";break;
+ case eQuoted: pVType = "quoted";break;
+ case eString: pVType = "string";break;
+ case eSymbol: pVType = "symbol";break;
+ case eNo: pVType = "no";break;
+ default: break;
+ }
+ SAL_INFO("vcl.unx.print", "\t\t"
+ << (pValue == pKey->m_pDefaultValue ? "(Default:) " : "")
+ << "option: \"" << pValue->m_aOption
+ << "\", value: type " << pVType << " \""
+ << pValue->m_aValue << "\"");
+ }
+ }
+ SAL_INFO("vcl.unx.print",
+ "constraints: (" << m_aConstraints.size() << " found)");
+ for (auto const& constraint : m_aConstraints)
+ {
+ SAL_INFO("vcl.unx.print", "*\"" << constraint.m_pKey1->getKey() << "\" \""
+ << (constraint.m_pOption1 ? constraint.m_pOption1->m_aOption : "<nil>")
+ << "\" *\"" << constraint.m_pKey2->getKey() << "\" \""
+ << (constraint.m_pOption2 ? constraint.m_pOption2->m_aOption : "<nil>")
+ << "\"");
+ }
+#endif
+
+ m_pImageableAreas = getKey( "ImageableArea" );
+ const PPDValue * pDefaultImageableArea = nullptr;
+ if( m_pImageableAreas )
+ pDefaultImageableArea = m_pImageableAreas->getDefaultValue();
+ if (m_pImageableAreas == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no ImageableArea in " << m_aFile);
+ }
+ if (pDefaultImageableArea == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no DefaultImageableArea in " << m_aFile);
+ }
+
+ m_pPaperDimensions = getKey( "PaperDimension" );
+ if( m_pPaperDimensions )
+ m_pDefaultPaperDimension = m_pPaperDimensions->getDefaultValue();
+ if (m_pPaperDimensions == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no PaperDimensions in " << m_aFile);
+ }
+ if (m_pDefaultPaperDimension == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no DefaultPaperDimensions in " << m_aFile);
+ }
+
+ auto pResolutions = getKey( "Resolution" );
+ if( pResolutions )
+ m_pDefaultResolution = pResolutions->getDefaultValue();
+ if (pResolutions == nullptr) {
+ SAL_INFO( "vcl.unx.print", "no Resolution in " << m_aFile);
+ }
+ SAL_INFO_IF(!m_pDefaultResolution, "vcl.unx.print", "no DefaultResolution in " + m_aFile);
+
+ auto pInputSlots = getKey( "InputSlot" );
+ if( pInputSlots )
+ m_pDefaultInputSlot = pInputSlots->getDefaultValue();
+ SAL_INFO_IF(!pInputSlots, "vcl.unx.print", "no InputSlot in " << m_aFile);
+ SAL_INFO_IF(!m_pDefaultInputSlot, "vcl.unx.print", "no DefaultInputSlot in " << m_aFile);
+}
+
+PPDParser::~PPDParser()
+{
+ m_pTranslator.reset();
+}
+
+void PPDParser::insertKey( std::unique_ptr<PPDKey> pKey )
+{
+ m_aOrderedKeys.push_back( pKey.get() );
+ m_aKeys[ pKey->getKey() ] = std::move(pKey);
+}
+
+const PPDKey* PPDParser::getKey( int n ) const
+{
+ return (n >= 0 && o3tl::make_unsigned(n) < m_aOrderedKeys.size()) ? m_aOrderedKeys[n] : nullptr;
+}
+
+const PPDKey* PPDParser::getKey( const OUString& rKey ) const
+{
+ PPDParser::hash_type::const_iterator it = m_aKeys.find( rKey );
+ return it != m_aKeys.end() ? it->second.get() : nullptr;
+}
+
+bool PPDParser::hasKey( const PPDKey* pKey ) const
+{
+ return pKey && ( m_aKeys.find( pKey->getKey() ) != m_aKeys.end() );
+}
+
+static sal_uInt8 getNibble( char cChar )
+{
+ sal_uInt8 nRet = 0;
+ if( cChar >= '0' && cChar <= '9' )
+ nRet = sal_uInt8( cChar - '0' );
+ else if( cChar >= 'A' && cChar <= 'F' )
+ nRet = 10 + sal_uInt8( cChar - 'A' );
+ else if( cChar >= 'a' && cChar <= 'f' )
+ nRet = 10 + sal_uInt8( cChar - 'a' );
+ return nRet;
+}
+
+OUString PPDParser::handleTranslation(const OString& i_rString, bool bIsGlobalized)
+{
+ sal_Int32 nOrigLen = i_rString.getLength();
+ OStringBuffer aTrans( nOrigLen );
+ const char* pStr = i_rString.getStr();
+ const char* pEnd = pStr + nOrigLen;
+ while( pStr < pEnd )
+ {
+ if( *pStr == '<' )
+ {
+ pStr++;
+ char cChar;
+ while( *pStr != '>' && pStr < pEnd-1 )
+ {
+ cChar = getNibble( *pStr++ ) << 4;
+ cChar |= getNibble( *pStr++ );
+ aTrans.append( cChar );
+ }
+ pStr++;
+ }
+ else
+ aTrans.append( *pStr++ );
+ }
+ return OStringToOUString( aTrans, bIsGlobalized ? RTL_TEXTENCODING_UTF8 : m_aFileEncoding );
+}
+
+namespace
+{
+ bool oddDoubleQuoteCount(OStringBuffer &rBuffer)
+ {
+ bool bHasOddCount = false;
+ for (sal_Int32 i = 0; i < rBuffer.getLength(); ++i)
+ {
+ if (rBuffer[i] == '"')
+ bHasOddCount = !bHasOddCount;
+ }
+ return bHasOddCount;
+ }
+}
+
+void PPDParser::parse( ::std::vector< OString >& rLines )
+{
+ // Name for PPD group into which all options are put for which the PPD
+ // does not explicitly define a group.
+ // This is similar to how CUPS handles it,
+ // s. Sweet, Michael R. (2001): Common UNIX Printing System, p. 251:
+ // "Each option in turn is associated with a group stored in the
+ // ppd_group_t structure. Groups can be specified in the PPD file; if an
+ // option is not associated with a group, it is put in a "General" or
+ // "Extra" group depending on the option.
+ static constexpr OString aDefaultPPDGroupName("General"_ostr);
+
+ std::vector< OString >::iterator line = rLines.begin();
+ PPDParser::hash_type::const_iterator keyit;
+
+ // name of the PPD group that is currently being processed
+ OString aCurrentGroup = aDefaultPPDGroupName;
+
+ while( line != rLines.end() )
+ {
+ OString aCurrentLine( *line );
+ ++line;
+
+ SAL_INFO("vcl.unx.print", "Parse line '" << aCurrentLine << "'");
+
+ if (aCurrentLine.getLength() < 2 || aCurrentLine[0] != '*')
+ continue;
+ if( aCurrentLine[1] == '%' )
+ continue;
+
+ OString aKey = GetCommandLineToken( 0, aCurrentLine.getToken(0, ':') );
+ sal_Int32 nPos = aKey.indexOf('/');
+ if (nPos != -1)
+ aKey = aKey.copy(0, nPos);
+ if(!aKey.isEmpty())
+ {
+ aKey = aKey.copy(1); // remove the '*'
+ }
+ if(aKey.isEmpty())
+ {
+ continue;
+ }
+
+ if (aKey == "CloseGroup")
+ {
+ aCurrentGroup = aDefaultPPDGroupName;
+ continue;
+ }
+ if (aKey == "OpenGroup")
+ {
+ OString aGroupName = aCurrentLine;
+ sal_Int32 nPosition = aGroupName.indexOf('/');
+ if (nPosition != -1)
+ {
+ aGroupName = aGroupName.copy(0, nPosition);
+ }
+
+ aCurrentGroup = GetCommandLineToken(1, aGroupName);
+ continue;
+ }
+ if ((aKey == "CloseUI") ||
+ (aKey == "JCLCloseUI") ||
+ (aKey == "End") ||
+ (aKey == "JCLEnd") ||
+ (aKey == "OpenSubGroup") ||
+ (aKey == "CloseSubGroup"))
+ {
+ continue;
+ }
+
+ if ((aKey == "OpenUI") || (aKey == "JCLOpenUI"))
+ {
+ parseOpenUI( aCurrentLine, aCurrentGroup);
+ continue;
+ }
+ else if (aKey == "OrderDependency")
+ {
+ parseOrderDependency( aCurrentLine );
+ continue;
+ }
+ else if (aKey == "UIConstraints" ||
+ aKey == "NonUIConstraints")
+ {
+ continue; // parsed in pass 2
+ }
+ else if( aKey == "CustomPageSize" ) // currently not handled
+ continue;
+ else if (aKey.startsWith("Custom", &aKey) )
+ {
+ //fdo#43049 very basic support for Custom entries, we ignore the
+ //validation params and types
+ OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
+ keyit = m_aKeys.find( aUniKey );
+ if(keyit != m_aKeys.end())
+ {
+ PPDKey* pKey = keyit->second.get();
+ pKey->insertValue("Custom", eInvocation, true);
+ }
+ continue;
+ }
+
+ // default values are parsed in pass 2
+ if (aKey.startsWith("Default"))
+ continue;
+
+ bool bQuery = false;
+ if (aKey[0] == '?')
+ {
+ aKey = aKey.copy(1);
+ bQuery = true;
+ }
+
+ OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
+ // handle CUPS extension for globalized PPDs
+ /* FIXME-BCP47: really only ISO 639-1 two character language codes?
+ * goodnight... */
+ bool bIsGlobalizedLine = false;
+ css::lang::Locale aTransLocale;
+ if( ( aUniKey.getLength() > 3 && aUniKey[ 2 ] == '.' ) ||
+ ( aUniKey.getLength() > 5 && aUniKey[ 2 ] == '_' && aUniKey[ 5 ] == '.' ) )
+ {
+ if( aUniKey[ 2 ] == '.' )
+ {
+ aTransLocale.Language = aUniKey.copy( 0, 2 );
+ aUniKey = aUniKey.copy( 3 );
+ }
+ else
+ {
+ aTransLocale.Language = aUniKey.copy( 0, 2 );
+ aTransLocale.Country = aUniKey.copy( 3, 2 );
+ aUniKey = aUniKey.copy( 6 );
+ }
+ bIsGlobalizedLine = true;
+ }
+
+ OUString aOption;
+ nPos = aCurrentLine.indexOf(':');
+ if( nPos != -1 )
+ {
+ aOption = OStringToOUString(
+ aCurrentLine.subView( 1, nPos-1 ), RTL_TEXTENCODING_MS_1252 );
+ aOption = GetCommandLineToken( 1, aOption );
+ sal_Int32 nTransPos = aOption.indexOf( '/' );
+ if( nTransPos != -1 )
+ aOption = aOption.copy(0, nTransPos);
+ }
+
+ PPDValueType eType = eNo;
+ OUString aValue;
+ OUString aOptionTranslation;
+ OUString aValueTranslation;
+ if( nPos != -1 )
+ {
+ // found a colon, there may be an option
+ OString aLine = aCurrentLine.copy( 1, nPos-1 );
+ aLine = WhitespaceToSpace( aLine );
+ sal_Int32 nTransPos = aLine.indexOf('/');
+ if (nTransPos != -1)
+ aOptionTranslation = handleTranslation( aLine.copy(nTransPos+1), bIsGlobalizedLine );
+
+ // read in more lines if necessary for multiline values
+ aLine = aCurrentLine.copy( nPos+1 );
+ if (!aLine.isEmpty())
+ {
+ OStringBuffer aBuffer(aLine);
+ while (line != rLines.end() && oddDoubleQuoteCount(aBuffer))
+ {
+ // copy the newlines also
+ aBuffer.append("\n" + *line);
+ ++line;
+ }
+ aLine = aBuffer.makeStringAndClear();
+ }
+ aLine = WhitespaceToSpace( aLine );
+
+ // #i100644# handle a missing value (actually a broken PPD)
+ if( aLine.isEmpty() )
+ {
+ if( !aOption.isEmpty() &&
+ !aUniKey.startsWith( "JCL" ) )
+ eType = eInvocation;
+ else
+ eType = eQuoted;
+ }
+ // check for invocation or quoted value
+ else if(aLine[0] == '"')
+ {
+ aLine = aLine.copy(1);
+ nTransPos = aLine.indexOf('"');
+ if (nTransPos == -1)
+ nTransPos = aLine.getLength();
+ aValue = OStringToOUString(aLine.subView(0, nTransPos), RTL_TEXTENCODING_MS_1252);
+ // after the second doublequote can follow a / and a translation
+ if (nTransPos < aLine.getLength() - 2)
+ {
+ aValueTranslation = handleTranslation( aLine.copy( nTransPos+2 ), bIsGlobalizedLine );
+ }
+ // check for quoted value
+ if( !aOption.isEmpty() &&
+ !aUniKey.startsWith( "JCL" ) )
+ eType = eInvocation;
+ else
+ eType = eQuoted;
+ }
+ // check for symbol value
+ else if(aLine[0] == '^')
+ {
+ aLine = aLine.copy(1);
+ aValue = OStringToOUString(aLine, RTL_TEXTENCODING_MS_1252);
+ eType = eSymbol;
+ }
+ else
+ {
+ // must be a string value then
+ // strictly this is false because string values
+ // can contain any whitespace which is reduced
+ // to one space by now
+ // who cares ...
+ nTransPos = aLine.indexOf('/');
+ if (nTransPos == -1)
+ nTransPos = aLine.getLength();
+ aValue = OStringToOUString(aLine.subView(0, nTransPos), RTL_TEXTENCODING_MS_1252);
+ if (nTransPos+1 < aLine.getLength())
+ aValueTranslation = handleTranslation( aLine.copy( nTransPos+1 ), bIsGlobalizedLine );
+ eType = eString;
+ }
+ }
+
+ // handle globalized PPD entries
+ if( bIsGlobalizedLine )
+ {
+ // handle main key translations of form:
+ // *ll_CC.Translation MainKeyword/translated text: ""
+ if( aUniKey == "Translation" )
+ {
+ m_pTranslator->insertKey( aOption, aOptionTranslation, aTransLocale );
+ }
+ // handle options translations of for:
+ // *ll_CC.MainKeyword OptionKeyword/translated text: ""
+ else
+ {
+ m_pTranslator->insertOption( aUniKey, aOption, aOptionTranslation, aTransLocale );
+ }
+ continue;
+ }
+
+ PPDKey* pKey = nullptr;
+ keyit = m_aKeys.find( aUniKey );
+ if( keyit == m_aKeys.end() )
+ {
+ pKey = new PPDKey( aUniKey );
+ insertKey( std::unique_ptr<PPDKey>(pKey) );
+ }
+ else
+ pKey = keyit->second.get();
+
+ if( eType == eNo && bQuery )
+ continue;
+
+ PPDValue* pValue = pKey->insertValue( aOption, eType );
+ if( ! pValue )
+ continue;
+ pValue->m_aValue = aValue;
+
+ if( !aOptionTranslation.isEmpty() )
+ m_pTranslator->insertOption( aUniKey, aOption, aOptionTranslation, aTransLocale );
+ if( !aValueTranslation.isEmpty() )
+ m_pTranslator->insertValue( aUniKey, aOption, aValue, aValueTranslation, aTransLocale );
+
+ // eventually update query and remove from option list
+ if( bQuery && !pKey->m_bQueryValue )
+ {
+ pKey->m_bQueryValue = true;
+ pKey->eraseValue( pValue->m_aOption );
+ }
+ }
+
+ // second pass: fill in defaults
+ for( const auto& aLine : rLines )
+ {
+ if (aLine.startsWith("*Default"))
+ {
+ SAL_INFO("vcl.unx.print", "Found a default: '" << aLine << "'");
+ OUString aKey(OStringToOUString(aLine.subView(8), RTL_TEXTENCODING_MS_1252));
+ sal_Int32 nPos = aKey.indexOf( ':' );
+ if( nPos != -1 )
+ {
+ aKey = aKey.copy(0, nPos);
+ OUString aOption(OStringToOUString(
+ WhitespaceToSpace(aLine.subView(nPos+9)),
+ RTL_TEXTENCODING_MS_1252));
+ keyit = m_aKeys.find( aKey );
+ if( keyit != m_aKeys.end() )
+ {
+ PPDKey* pKey = keyit->second.get();
+ const PPDValue* pDefValue = pKey->getValue( aOption );
+ if( pKey->m_pDefaultValue == nullptr )
+ pKey->m_pDefaultValue = pDefValue;
+ }
+ else
+ {
+ // some PPDs contain defaults for keys that
+ // do not exist otherwise
+ // (example: DefaultResolution)
+ // so invent that key here and have a default value
+ std::unique_ptr<PPDKey> pKey(new PPDKey( aKey ));
+ pKey->insertValue( aOption, eInvocation /*or what ?*/ );
+ pKey->m_pDefaultValue = pKey->getValue( aOption );
+ insertKey( std::move(pKey) );
+ }
+ }
+ }
+ else if (aLine.startsWith("*UIConstraints") ||
+ aLine.startsWith("*NonUIConstraints"))
+ {
+ parseConstraint( aLine );
+ }
+ }
+}
+
+void PPDParser::parseOpenUI(const OString& rLine, std::string_view rPPDGroup)
+{
+ OUString aTranslation;
+ OString aKey = rLine;
+
+ sal_Int32 nPos = aKey.indexOf(':');
+ if( nPos != -1 )
+ aKey = aKey.copy(0, nPos);
+ nPos = aKey.indexOf('/');
+ if( nPos != -1 )
+ {
+ aTranslation = handleTranslation( aKey.copy( nPos + 1 ), false );
+ aKey = aKey.copy(0, nPos);
+ }
+ aKey = GetCommandLineToken( 1, aKey );
+ aKey = aKey.copy(1);
+
+ OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
+ PPDParser::hash_type::const_iterator keyit = m_aKeys.find( aUniKey );
+ PPDKey* pKey;
+ if( keyit == m_aKeys.end() )
+ {
+ pKey = new PPDKey( aUniKey );
+ insertKey( std::unique_ptr<PPDKey>(pKey) );
+ }
+ else
+ pKey = keyit->second.get();
+
+ pKey->m_bUIOption = true;
+ m_pTranslator->insertKey( pKey->getKey(), aTranslation );
+
+ pKey->m_aGroup = OStringToOUString(rPPDGroup, RTL_TEXTENCODING_MS_1252);
+}
+
+void PPDParser::parseOrderDependency(const OString& rLine)
+{
+ OString aLine(rLine);
+ sal_Int32 nPos = aLine.indexOf(':');
+ if( nPos != -1 )
+ aLine = aLine.copy( nPos+1 );
+
+ sal_Int32 nOrder = GetCommandLineToken( 0, aLine ).toInt32();
+ OUString aKey(OStringToOUString(GetCommandLineToken(2, aLine), RTL_TEXTENCODING_MS_1252));
+ if( aKey[ 0 ] != '*' )
+ return; // invalid order dependency
+ aKey = aKey.replaceAt( 0, 1, u"" );
+
+ PPDKey* pKey;
+ PPDParser::hash_type::const_iterator keyit = m_aKeys.find( aKey );
+ if( keyit == m_aKeys.end() )
+ {
+ pKey = new PPDKey( aKey );
+ insertKey( std::unique_ptr<PPDKey>(pKey) );
+ }
+ else
+ pKey = keyit->second.get();
+
+ pKey->m_nOrderDependency = nOrder;
+}
+
+void PPDParser::parseConstraint( const OString& rLine )
+{
+ bool bFailed = false;
+
+ OUString aLine(OStringToOUString(rLine, RTL_TEXTENCODING_MS_1252));
+ sal_Int32 nIdx = rLine.indexOf(':');
+ if (nIdx != -1)
+ aLine = aLine.replaceAt(0, nIdx + 1, u"");
+ PPDConstraint aConstraint;
+ int nTokens = GetCommandLineTokenCount( aLine );
+ for( int i = 0; i < nTokens; i++ )
+ {
+ OUString aToken = GetCommandLineToken( i, aLine );
+ if( !aToken.isEmpty() && aToken[ 0 ] == '*' )
+ {
+ aToken = aToken.replaceAt( 0, 1, u"" );
+ if( aConstraint.m_pKey1 )
+ aConstraint.m_pKey2 = getKey( aToken );
+ else
+ aConstraint.m_pKey1 = getKey( aToken );
+ }
+ else
+ {
+ if( aConstraint.m_pKey2 )
+ {
+ if( ! ( aConstraint.m_pOption2 = aConstraint.m_pKey2->getValue( aToken ) ) )
+ bFailed = true;
+ }
+ else if( aConstraint.m_pKey1 )
+ {
+ if( ! ( aConstraint.m_pOption1 = aConstraint.m_pKey1->getValue( aToken ) ) )
+ bFailed = true;
+ }
+ else
+ // constraint for nonexistent keys; this happens
+ // e.g. in HP4PLUS3
+ bFailed = true;
+ }
+ }
+ // there must be two keywords
+ if( ! aConstraint.m_pKey1 || ! aConstraint.m_pKey2 || bFailed )
+ {
+ SAL_INFO("vcl.unx.print",
+ "Warning: constraint \"" << rLine << "\" is invalid");
+ }
+ else
+ m_aConstraints.push_back( aConstraint );
+}
+
+OUString PPDParser::getDefaultPaperDimension() const
+{
+ if( m_pDefaultPaperDimension )
+ return m_pDefaultPaperDimension->m_aOption;
+
+ return OUString();
+}
+
+bool PPDParser::getMargins(
+ std::u16string_view rPaperName,
+ int& rLeft, int& rRight,
+ int& rUpper, int& rLower ) const
+{
+ if( ! m_pImageableAreas || ! m_pPaperDimensions )
+ return false;
+
+ int nPDim=-1, nImArea=-1, i;
+ for( i = 0; i < m_pImageableAreas->countValues(); i++ )
+ if( rPaperName == m_pImageableAreas->getValue( i )->m_aOption )
+ nImArea = i;
+ for( i = 0; i < m_pPaperDimensions->countValues(); i++ )
+ if( rPaperName == m_pPaperDimensions->getValue( i )->m_aOption )
+ nPDim = i;
+ if( nPDim == -1 || nImArea == -1 )
+ return false;
+
+ double ImLLx, ImLLy, ImURx, ImURy;
+ double PDWidth, PDHeight;
+ OUString aArea = m_pImageableAreas->getValue( nImArea )->m_aValue;
+ ImLLx = StringToDouble( GetCommandLineToken( 0, aArea ) );
+ ImLLy = StringToDouble( GetCommandLineToken( 1, aArea ) );
+ ImURx = StringToDouble( GetCommandLineToken( 2, aArea ) );
+ ImURy = StringToDouble( GetCommandLineToken( 3, aArea ) );
+ aArea = m_pPaperDimensions->getValue( nPDim )->m_aValue;
+ PDWidth = StringToDouble( GetCommandLineToken( 0, aArea ) );
+ PDHeight = StringToDouble( GetCommandLineToken( 1, aArea ) );
+ rLeft = static_cast<int>(ImLLx + 0.5);
+ rLower = static_cast<int>(ImLLy + 0.5);
+ rUpper = static_cast<int>(PDHeight - ImURy + 0.5);
+ rRight = static_cast<int>(PDWidth - ImURx + 0.5);
+
+ return true;
+}
+
+bool PPDParser::getPaperDimension(
+ std::u16string_view rPaperName,
+ int& rWidth, int& rHeight ) const
+{
+ if( ! m_pPaperDimensions )
+ return false;
+
+ int nPDim=-1;
+ for( int i = 0; i < m_pPaperDimensions->countValues(); i++ )
+ if( rPaperName == m_pPaperDimensions->getValue( i )->m_aOption )
+ nPDim = i;
+ if( nPDim == -1 )
+ return false;
+
+ double PDWidth, PDHeight;
+ OUString aArea = m_pPaperDimensions->getValue( nPDim )->m_aValue;
+ PDWidth = StringToDouble( GetCommandLineToken( 0, aArea ) );
+ PDHeight = StringToDouble( GetCommandLineToken( 1, aArea ) );
+ rHeight = static_cast<int>(PDHeight + 0.5);
+ rWidth = static_cast<int>(PDWidth + 0.5);
+
+ return true;
+}
+
+OUString PPDParser::matchPaperImpl(int nWidth, int nHeight, bool bSwaped, psp::orientation* pOrientation) const
+{
+ if( ! m_pPaperDimensions )
+ return OUString();
+
+ int nPDim = -1;
+ double fSort = 2e36, fNewSort;
+
+ for( int i = 0; i < m_pPaperDimensions->countValues(); i++ )
+ {
+ OUString aArea = m_pPaperDimensions->getValue( i )->m_aValue;
+ double PDWidth = StringToDouble( GetCommandLineToken( 0, aArea ) );
+ double PDHeight = StringToDouble( GetCommandLineToken( 1, aArea ) );
+ PDWidth /= static_cast<double>(nWidth);
+ PDHeight /= static_cast<double>(nHeight);
+ if( PDWidth >= 0.9 && PDWidth <= 1.1 &&
+ PDHeight >= 0.9 && PDHeight <= 1.1 )
+ {
+ fNewSort =
+ (1.0-PDWidth)*(1.0-PDWidth) + (1.0-PDHeight)*(1.0-PDHeight);
+ if( fNewSort == 0.0 ) // perfect match
+ return m_pPaperDimensions->getValue( i )->m_aOption;
+
+ if( fNewSort < fSort )
+ {
+ fSort = fNewSort;
+ nPDim = i;
+ }
+ }
+ }
+
+ if (nPDim == -1 && !bSwaped)
+ {
+ // swap portrait/landscape and try again
+ return matchPaperImpl(nHeight, nWidth, true, pOrientation);
+ }
+
+ if (nPDim == -1)
+ return OUString();
+
+ if (bSwaped && pOrientation)
+ {
+ switch (*pOrientation)
+ {
+ case psp::orientation::Portrait:
+ *pOrientation = psp::orientation::Landscape;
+ break;
+ case psp::orientation::Landscape:
+ *pOrientation = psp::orientation::Portrait;
+ break;
+ }
+ }
+
+ return m_pPaperDimensions->getValue( nPDim )->m_aOption;
+}
+
+OUString PPDParser::matchPaper(int nWidth, int nHeight, psp::orientation* pOrientation) const
+{
+ return matchPaperImpl(nHeight, nWidth, true, pOrientation);
+}
+
+OUString PPDParser::getDefaultInputSlot() const
+{
+ if( m_pDefaultInputSlot )
+ return m_pDefaultInputSlot->m_aValue;
+ return OUString();
+}
+
+void PPDParser::getResolutionFromString(std::u16string_view rString,
+ int& rXRes, int& rYRes )
+{
+ rXRes = rYRes = 300;
+
+ const size_t nDPIPos {rString.find( u"dpi" )};
+ if( nDPIPos != std::u16string_view::npos )
+ {
+ const size_t nPos {rString.find( 'x' )};
+ if( nPos != std::u16string_view::npos )
+ {
+ rXRes = o3tl::toInt32(rString.substr( 0, nPos ));
+ rYRes = o3tl::toInt32(rString.substr(nPos+1, nDPIPos - nPos - 1));
+ }
+ else
+ rXRes = rYRes = o3tl::toInt32(rString.substr( 0, nDPIPos ));
+ }
+}
+
+void PPDParser::getDefaultResolution( int& rXRes, int& rYRes ) const
+{
+ if( m_pDefaultResolution )
+ {
+ getResolutionFromString( m_pDefaultResolution->m_aValue, rXRes, rYRes );
+ return;
+ }
+
+ rXRes = 300;
+ rYRes = 300;
+}
+
+OUString PPDParser::translateKey( const OUString& i_rKey ) const
+{
+ OUString aResult( m_pTranslator->translateKey( i_rKey ) );
+ if( aResult.isEmpty() )
+ aResult = i_rKey;
+ return aResult;
+}
+
+OUString PPDParser::translateOption( std::u16string_view i_rKey,
+ const OUString& i_rOption ) const
+{
+ OUString aResult( m_pTranslator->translateOption( i_rKey, i_rOption ) );
+ if( aResult.isEmpty() )
+ aResult = i_rOption;
+ return aResult;
+}
+
+/*
+ * PPDKey
+ */
+
+PPDKey::PPDKey( OUString aKey ) :
+ m_aKey(std::move( aKey )),
+ m_pDefaultValue( nullptr ),
+ m_bQueryValue( false ),
+ m_bUIOption( false ),
+ m_nOrderDependency( 100 )
+{
+}
+
+PPDKey::~PPDKey()
+{
+}
+
+const PPDValue* PPDKey::getValue( int n ) const
+{
+ return (n >= 0 && o3tl::make_unsigned(n) < m_aOrderedValues.size()) ? m_aOrderedValues[n] : nullptr;
+}
+
+const PPDValue* PPDKey::getValue( const OUString& rOption ) const
+{
+ PPDKey::hash_type::const_iterator it = m_aValues.find( rOption );
+ return it != m_aValues.end() ? &it->second : nullptr;
+}
+
+const PPDValue* PPDKey::getValueCaseInsensitive( const OUString& rOption ) const
+{
+ const PPDValue* pValue = getValue( rOption );
+ if( ! pValue )
+ {
+ for( size_t n = 0; n < m_aOrderedValues.size() && ! pValue; n++ )
+ if( m_aOrderedValues[n]->m_aOption.equalsIgnoreAsciiCase( rOption ) )
+ pValue = m_aOrderedValues[n];
+ }
+
+ return pValue;
+}
+
+void PPDKey::eraseValue( const OUString& rOption )
+{
+ PPDKey::hash_type::iterator it = m_aValues.find( rOption );
+ if( it == m_aValues.end() )
+ return;
+
+ auto vit = std::find(m_aOrderedValues.begin(), m_aOrderedValues.end(), &(it->second ));
+ if( vit != m_aOrderedValues.end() )
+ m_aOrderedValues.erase( vit );
+
+ m_aValues.erase( it );
+}
+
+PPDValue* PPDKey::insertValue(const OUString& rOption, PPDValueType eType, bool bCustomOption)
+{
+ if( m_aValues.find( rOption ) != m_aValues.end() )
+ return nullptr;
+
+ PPDValue aValue;
+ aValue.m_aOption = rOption;
+ aValue.m_bCustomOption = bCustomOption;
+ aValue.m_bCustomOptionSetViaApp = false;
+ aValue.m_eType = eType;
+ m_aValues[ rOption ] = aValue;
+ PPDValue* pValue = &m_aValues[rOption];
+ m_aOrderedValues.push_back( pValue );
+ return pValue;
+}
+
+/*
+ * PPDContext
+ */
+
+PPDContext::PPDContext() :
+ m_pParser( nullptr )
+{
+}
+
+PPDContext& PPDContext::operator=( PPDContext&& rCopy )
+{
+ std::swap(m_pParser, rCopy.m_pParser);
+ std::swap(m_aCurrentValues, rCopy.m_aCurrentValues);
+ return *this;
+}
+
+const PPDKey* PPDContext::getModifiedKey( std::size_t n ) const
+{
+ if( m_aCurrentValues.size() <= n )
+ return nullptr;
+
+ hash_type::const_iterator it = m_aCurrentValues.begin();
+ std::advance(it, n);
+ return it->first;
+}
+
+void PPDContext::setParser( const PPDParser* pParser )
+{
+ if( pParser != m_pParser )
+ {
+ m_aCurrentValues.clear();
+ m_pParser = pParser;
+ }
+}
+
+const PPDValue* PPDContext::getValue( const PPDKey* pKey ) const
+{
+ if( ! m_pParser )
+ return nullptr;
+
+ hash_type::const_iterator it = m_aCurrentValues.find( pKey );
+ if( it != m_aCurrentValues.end() )
+ return it->second;
+
+ if( ! m_pParser->hasKey( pKey ) )
+ return nullptr;
+
+ const PPDValue* pValue = pKey->getDefaultValue();
+ if( ! pValue )
+ pValue = pKey->getValue( 0 );
+
+ return pValue;
+}
+
+const PPDValue* PPDContext::setValue( const PPDKey* pKey, const PPDValue* pValue, bool bDontCareForConstraints )
+{
+ if( ! m_pParser || ! pKey )
+ return nullptr;
+
+ // pValue can be NULL - it means ignore this option
+
+ if( ! m_pParser->hasKey( pKey ) )
+ return nullptr;
+
+ // check constraints
+ if( pValue )
+ {
+ if( bDontCareForConstraints )
+ {
+ m_aCurrentValues[ pKey ] = pValue;
+ }
+ else if( checkConstraints( pKey, pValue, true ) )
+ {
+ m_aCurrentValues[ pKey ] = pValue;
+
+ // after setting this value, check all constraints !
+ hash_type::iterator it = m_aCurrentValues.begin();
+ while( it != m_aCurrentValues.end() )
+ {
+ if( it->first != pKey &&
+ ! checkConstraints( it->first, it->second, false ) )
+ {
+ SAL_INFO("vcl.unx.print", "PPDContext::setValue: option "
+ << it->first->getKey()
+ << " (" << it->second->m_aOption
+ << ") is constrained after setting "
+ << pKey->getKey()
+ << " to " << pValue->m_aOption);
+ resetValue( it->first, true );
+ it = m_aCurrentValues.begin();
+ }
+ else
+ ++it;
+ }
+ }
+ }
+ else
+ m_aCurrentValues[ pKey ] = nullptr;
+
+ return pValue;
+}
+
+bool PPDContext::checkConstraints( const PPDKey* pKey, const PPDValue* pValue )
+{
+ if( ! m_pParser || ! pKey || ! pValue )
+ return false;
+
+ // ensure that this key is already in the list if it exists at all
+ if( m_aCurrentValues.find( pKey ) != m_aCurrentValues.end() )
+ return checkConstraints( pKey, pValue, false );
+
+ // it is not in the list, insert it temporarily
+ bool bRet = false;
+ if( m_pParser->hasKey( pKey ) )
+ {
+ const PPDValue* pDefValue = pKey->getDefaultValue();
+ m_aCurrentValues[ pKey ] = pDefValue;
+ bRet = checkConstraints( pKey, pValue, false );
+ m_aCurrentValues.erase( pKey );
+ }
+
+ return bRet;
+}
+
+bool PPDContext::resetValue( const PPDKey* pKey, bool bDefaultable )
+{
+ if( ! pKey || ! m_pParser || ! m_pParser->hasKey( pKey ) )
+ return false;
+
+ const PPDValue* pResetValue = pKey->getValue( "None" );
+ if( ! pResetValue )
+ pResetValue = pKey->getValue( "False" );
+ if( ! pResetValue && bDefaultable )
+ pResetValue = pKey->getDefaultValue();
+
+ bool bRet = pResetValue && ( setValue( pKey, pResetValue ) == pResetValue );
+
+ return bRet;
+}
+
+bool PPDContext::checkConstraints( const PPDKey* pKey, const PPDValue* pNewValue, bool bDoReset )
+{
+ if( ! pNewValue )
+ return true;
+
+ // sanity checks
+ if( ! m_pParser )
+ return false;
+
+ if( pKey->getValue( pNewValue->m_aOption ) != pNewValue )
+ return false;
+
+ // None / False and the default can always be set, but be careful !
+ // setting them might influence constrained values
+ if( pNewValue->m_aOption == "None" || pNewValue->m_aOption == "False" ||
+ pNewValue == pKey->getDefaultValue() )
+ return true;
+
+ const ::std::vector< PPDParser::PPDConstraint >& rConstraints( m_pParser->getConstraints() );
+ for (auto const& constraint : rConstraints)
+ {
+ const PPDKey* pLeft = constraint.m_pKey1;
+ const PPDKey* pRight = constraint.m_pKey2;
+ if( ! pLeft || ! pRight || ( pKey != pLeft && pKey != pRight ) )
+ continue;
+
+ const PPDKey* pOtherKey = pKey == pLeft ? pRight : pLeft;
+ const PPDValue* pOtherKeyOption = pKey == pLeft ? constraint.m_pOption2 : constraint.m_pOption1;
+ const PPDValue* pKeyOption = pKey == pLeft ? constraint.m_pOption1 : constraint.m_pOption2;
+
+ // syntax *Key1 option1 *Key2 option2
+ if( pKeyOption && pOtherKeyOption )
+ {
+ if( pNewValue != pKeyOption )
+ continue;
+ if( pOtherKeyOption == getValue( pOtherKey ) )
+ {
+ return false;
+ }
+ }
+ // syntax *Key1 option *Key2 or *Key1 *Key2 option
+ else if( pOtherKeyOption || pKeyOption )
+ {
+ if( pKeyOption )
+ {
+ if( ! ( pOtherKeyOption = getValue( pOtherKey ) ) )
+ continue; // this should not happen, PPD broken
+
+ if( pKeyOption == pNewValue &&
+ pOtherKeyOption->m_aOption != "None" &&
+ pOtherKeyOption->m_aOption != "False" )
+ {
+ // check if the other value can be reset and
+ // do so if possible
+ if( bDoReset && resetValue( pOtherKey ) )
+ continue;
+
+ return false;
+ }
+ }
+ else if( pOtherKeyOption )
+ {
+ if( getValue( pOtherKey ) == pOtherKeyOption &&
+ pNewValue->m_aOption != "None" &&
+ pNewValue->m_aOption != "False" )
+ return false;
+ }
+ else
+ {
+ // this should not happen, PPD is broken
+ }
+ }
+ // syntax *Key1 *Key2
+ else
+ {
+ const PPDValue* pOtherValue = getValue( pOtherKey );
+ if( pOtherValue->m_aOption != "None" &&
+ pOtherValue->m_aOption != "False" &&
+ pNewValue->m_aOption != "None" &&
+ pNewValue->m_aOption != "False" )
+ return false;
+ }
+ }
+ return true;
+}
+
+char* PPDContext::getStreamableBuffer( sal_uLong& rBytes ) const
+{
+ rBytes = 0;
+ if( m_aCurrentValues.empty() )
+ return nullptr;
+ for (auto const& elem : m_aCurrentValues)
+ {
+ OString aCopy(OUStringToOString(elem.first->getKey(), RTL_TEXTENCODING_MS_1252));
+ rBytes += aCopy.getLength();
+ rBytes += 1; // for ':'
+ if( elem.second )
+ {
+ aCopy = OUStringToOString(elem.second->m_aOption, RTL_TEXTENCODING_MS_1252);
+ rBytes += aCopy.getLength();
+ }
+ else
+ rBytes += 4;
+ rBytes += 1; // for '\0'
+ }
+ rBytes += 1;
+ char* pBuffer = new char[ rBytes ];
+ memset( pBuffer, 0, rBytes );
+ char* pRun = pBuffer;
+ for (auto const& elem : m_aCurrentValues)
+ {
+ OString aCopy(OUStringToOString(elem.first->getKey(), RTL_TEXTENCODING_MS_1252));
+ int nBytes = aCopy.getLength();
+ memcpy( pRun, aCopy.getStr(), nBytes );
+ pRun += nBytes;
+ *pRun++ = ':';
+ if( elem.second )
+ aCopy = OUStringToOString(elem.second->m_aOption, RTL_TEXTENCODING_MS_1252);
+ else
+ aCopy = "*nil"_ostr;
+ nBytes = aCopy.getLength();
+ memcpy( pRun, aCopy.getStr(), nBytes );
+ pRun += nBytes;
+
+ *pRun++ = 0;
+ }
+ return pBuffer;
+}
+
+void PPDContext::rebuildFromStreamBuffer(const std::vector<char> &rBuffer)
+{
+ if( ! m_pParser )
+ return;
+
+ m_aCurrentValues.clear();
+
+ const size_t nBytes = rBuffer.size() - 1;
+ size_t nRun = 0;
+ while (nRun < nBytes && rBuffer[nRun])
+ {
+ OString aLine(rBuffer.data() + nRun);
+ sal_Int32 nPos = aLine.indexOf(':');
+ if( nPos != -1 )
+ {
+ const PPDKey* pKey = m_pParser->getKey( OStringToOUString( aLine.subView( 0, nPos ), RTL_TEXTENCODING_MS_1252 ) );
+ if( pKey )
+ {
+ const PPDValue* pValue = nullptr;
+ OUString aOption(
+ OStringToOUString(aLine.subView(nPos+1), RTL_TEXTENCODING_MS_1252));
+ if (aOption != "*nil")
+ pValue = pKey->getValue( aOption );
+ m_aCurrentValues[ pKey ] = pValue;
+ SAL_INFO("vcl.unx.print",
+ "PPDContext::rebuildFromStreamBuffer: read PPDKeyValue { "
+ << pKey->getKey() << " , "
+ << (pValue ? aOption : "<nil>")
+ << " }");
+ }
+ }
+ nRun += aLine.getLength()+1;
+ }
+}
+
+int PPDContext::getRenderResolution() const
+{
+ // initialize to reasonable default, if parser is not set
+ int nDPI = 300;
+ if( m_pParser )
+ {
+ int nDPIx = 300, nDPIy = 300;
+ const PPDKey* pKey = m_pParser->getKey( "Resolution" );
+ if( pKey )
+ {
+ const PPDValue* pValue = getValue( pKey );
+ if( pValue )
+ PPDParser::getResolutionFromString( pValue->m_aOption, nDPIx, nDPIy );
+ else
+ m_pParser->getDefaultResolution( nDPIx, nDPIy );
+ }
+ else
+ m_pParser->getDefaultResolution( nDPIx, nDPIy );
+
+ nDPI = std::max(nDPIx, nDPIy);
+ }
+ return nDPI;
+}
+
+void PPDContext::getPageSize( OUString& rPaper, int& rWidth, int& rHeight ) const
+{
+ // initialize to reasonable default, if parser is not set
+ rPaper = "A4";
+ rWidth = 595;
+ rHeight = 842;
+ if( !m_pParser )
+ return;
+
+ const PPDKey* pKey = m_pParser->getKey( "PageSize" );
+ if( !pKey )
+ return;
+
+ const PPDValue* pValue = getValue( pKey );
+ if( pValue )
+ {
+ rPaper = pValue->m_aOption;
+ m_pParser->getPaperDimension( rPaper, rWidth, rHeight );
+ }
+ else
+ {
+ rPaper = m_pParser->getDefaultPaperDimension();
+ m_pParser->getDefaultPaperDimension( rWidth, rHeight );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/printer/printerinfomanager.cxx b/vcl/unx/generic/printer/printerinfomanager.cxx
new file mode 100644
index 0000000000..e920051364
--- /dev/null
+++ b/vcl/unx/generic/printer/printerinfomanager.cxx
@@ -0,0 +1,868 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unx/cpdmgr.hxx>
+#include <unx/cupsmgr.hxx>
+#include <unx/gendata.hxx>
+#include <unx/helper.hxx>
+
+#include <tools/urlobj.hxx>
+#include <tools/config.hxx>
+
+#include <i18nutil/paper.hxx>
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+
+#include <osl/file.hxx>
+#include <osl/thread.hxx>
+#include <o3tl/string_view.hxx>
+
+// filename of configuration files
+constexpr OUString PRINT_FILENAME = u"psprint.conf"_ustr;
+// the group of the global defaults
+constexpr OString GLOBAL_DEFAULTS_GROUP = "__Global_Printer_Defaults__"_ostr;
+
+#include <cstddef>
+#include <mutex>
+#include <unordered_set>
+
+using namespace psp;
+using namespace osl;
+
+namespace psp
+{
+ class SystemQueueInfo final : public Thread
+ {
+ mutable std::mutex m_aMutex;
+ bool m_bChanged;
+ std::vector< PrinterInfoManager::SystemPrintQueue >
+ m_aQueues;
+ OUString m_aCommand;
+
+ virtual void SAL_CALL run() override;
+
+ public:
+ SystemQueueInfo();
+ virtual ~SystemQueueInfo() override;
+
+ bool hasChanged() const;
+ OUString getCommand() const;
+
+ // sets changed status to false; therefore not const
+ void getSystemQueues( std::vector< PrinterInfoManager::SystemPrintQueue >& rQueues );
+ };
+} // namespace
+
+/*
+* class PrinterInfoManager
+*/
+
+PrinterInfoManager& PrinterInfoManager::get()
+{
+ // can't move to GenericUnixSalData, because of vcl/null/printerinfomanager.cxx
+ GenericUnixSalData* pSalData = GetGenericUnixSalData();
+ PrinterInfoManager* pPIM = pSalData->m_pPrinterInfoManager.get();
+ if (pPIM)
+ return *pPIM;
+
+ pPIM = CPDManager::tryLoadCPD();
+ if (!pPIM)
+ pPIM = CUPSManager::tryLoadCUPS();
+ if (!pPIM)
+ pPIM = new PrinterInfoManager();
+ pSalData->m_pPrinterInfoManager.reset(pPIM);
+ pPIM->initialize();
+
+ SAL_INFO("vcl.unx.print", "created PrinterInfoManager of type "
+ << static_cast<int>(pPIM->getType()));
+ return *pPIM;
+}
+
+PrinterInfoManager::PrinterInfoManager( Type eType ) :
+ m_eType( eType ),
+ m_aSystemDefaultPaper( "A4" )
+{
+ if( eType == Type::Default )
+ m_pQueueInfo.reset( new SystemQueueInfo );
+
+ m_aSystemDefaultPaper = OStringToOUString(
+ PaperInfo::toPSName(PaperInfo::getSystemDefaultPaper().getPaper()),
+ RTL_TEXTENCODING_UTF8);
+}
+
+PrinterInfoManager::~PrinterInfoManager()
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "PrinterInfoManager: "
+ << "destroyed Manager of type "
+ << ((int) getType()));
+#endif
+}
+
+bool PrinterInfoManager::checkPrintersChanged( bool bWait )
+{
+ // check if files were created, deleted or modified since initialize()
+ bool bChanged = false;
+ for (auto const& watchFile : m_aWatchFiles)
+ {
+ DirectoryItem aItem;
+ if( DirectoryItem::get( watchFile.m_aFilePath, aItem ) )
+ {
+ if( watchFile.m_aModified.Seconds != 0 )
+ {
+ bChanged = true; // file probably has vanished
+ break;
+ }
+ }
+ else
+ {
+ FileStatus aStatus( osl_FileStatus_Mask_ModifyTime );
+ if( aItem.getFileStatus( aStatus ) )
+ {
+ bChanged = true; // unlikely but not impossible
+ break;
+ }
+ else
+ {
+ TimeValue aModified = aStatus.getModifyTime();
+ if( aModified.Seconds != watchFile.m_aModified.Seconds )
+ {
+ bChanged = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if( bWait && m_pQueueInfo )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "syncing printer discovery thread.");
+#endif
+ m_pQueueInfo->join();
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "done: syncing printer discovery thread.");
+#endif
+ }
+
+ if( ! bChanged && m_pQueueInfo )
+ bChanged = m_pQueueInfo->hasChanged();
+ if( bChanged )
+ {
+ initialize();
+ }
+
+ return bChanged;
+}
+
+void PrinterInfoManager::initialize()
+{
+ m_aPrinters.clear();
+ m_aWatchFiles.clear();
+ OUString aDefaultPrinter;
+
+ // first initialize the global defaults
+ // have to iterate over all possible files
+ // there should be only one global setup section in all
+ // available config files
+ m_aGlobalDefaults = PrinterInfo();
+
+ // need a parser for the PPDContext. generic printer should do.
+ m_aGlobalDefaults.m_pParser = PPDParser::getParser( "SGENPRT" );
+ m_aGlobalDefaults.m_aContext.setParser( m_aGlobalDefaults.m_pParser );
+
+ if( ! m_aGlobalDefaults.m_pParser )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "Error: no default PPD file "
+ << "SGENPRT available, shutting down psprint...");
+#endif
+ return;
+ }
+
+ std::vector< OUString > aDirList;
+ psp::getPrinterPathList( aDirList, nullptr );
+ for (auto const& printDir : aDirList)
+ {
+ INetURLObject aFile( printDir, INetProtocol::File, INetURLObject::EncodeMechanism::All );
+ aFile.Append( PRINT_FILENAME );
+ Config aConfig( aFile.PathToFileName() );
+ if( aConfig.HasGroup( GLOBAL_DEFAULTS_GROUP ) )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "found global defaults in "
+ << aFile.PathToFileName());
+#endif
+ aConfig.SetGroup( GLOBAL_DEFAULTS_GROUP );
+
+ OString aValue( aConfig.ReadKey( "Copies"_ostr ) );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nCopies = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "Orientation"_ostr );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_eOrientation = aValue.equalsIgnoreAsciiCase("Landscape") ? orientation::Landscape : orientation::Portrait;
+
+ aValue = aConfig.ReadKey( "MarginAdjust"_ostr );
+ if (!aValue.isEmpty())
+ {
+ sal_Int32 nIdx {0};
+ m_aGlobalDefaults.m_nLeftMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ m_aGlobalDefaults.m_nRightMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ m_aGlobalDefaults.m_nTopMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ m_aGlobalDefaults.m_nBottomMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ }
+
+ aValue = aConfig.ReadKey( "ColorDepth"_ostr, "24"_ostr );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nColorDepth = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "ColorDevice"_ostr );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nColorDevice = aValue.toInt32();
+
+ // get the PPDContext of global JobData
+ for( int nKey = 0; nKey < aConfig.GetKeyCount(); ++nKey )
+ {
+ OString aKey( aConfig.GetKeyName( nKey ) );
+ if (aKey.startsWith("PPD_"))
+ {
+ aValue = aConfig.ReadKey( aKey );
+ const PPDKey* pKey = m_aGlobalDefaults.m_pParser->getKey(OStringToOUString(aKey.subView(4), RTL_TEXTENCODING_ISO_8859_1));
+ if( pKey )
+ {
+ m_aGlobalDefaults.m_aContext.
+ setValue( pKey,
+ aValue == "*nil" ? nullptr : pKey->getValue(OStringToOUString(aValue, RTL_TEXTENCODING_ISO_8859_1)),
+ true );
+ }
+ }
+ }
+ }
+ }
+ setDefaultPaper( m_aGlobalDefaults.m_aContext );
+
+ // now collect all available printers
+ for (auto const& printDir : aDirList)
+ {
+ INetURLObject aDir( printDir, INetProtocol::File, INetURLObject::EncodeMechanism::All );
+ INetURLObject aFile( aDir );
+ aFile.Append( PRINT_FILENAME );
+
+ // check directory validity
+ OUString aUniPath;
+ FileBase::getFileURLFromSystemPath( aDir.PathToFileName(), aUniPath );
+ Directory aDirectory( aUniPath );
+ if( aDirectory.open() )
+ continue;
+ aDirectory.close();
+
+ FileBase::getFileURLFromSystemPath( aFile.PathToFileName(), aUniPath );
+ FileStatus aStatus( osl_FileStatus_Mask_ModifyTime );
+ DirectoryItem aItem;
+
+ // setup WatchFile list
+ WatchFile aWatchFile;
+ aWatchFile.m_aFilePath = aUniPath;
+ if( ! DirectoryItem::get( aUniPath, aItem ) &&
+ ! aItem.getFileStatus( aStatus ) )
+ {
+ aWatchFile.m_aModified = aStatus.getModifyTime();
+ }
+ else
+ {
+ aWatchFile.m_aModified.Seconds = 0;
+ aWatchFile.m_aModified.Nanosec = 0;
+ }
+ m_aWatchFiles.push_back( aWatchFile );
+
+ Config aConfig( aFile.PathToFileName() );
+ for( int nGroup = 0; nGroup < aConfig.GetGroupCount(); nGroup++ )
+ {
+ aConfig.SetGroup( aConfig.GetGroupName( nGroup ) );
+ OString aValue = aConfig.ReadKey( "Printer"_ostr );
+ if (!aValue.isEmpty())
+ {
+ OUString aPrinterName;
+
+ sal_Int32 nNamePos = aValue.indexOf('/');
+ // check for valid value of "Printer"
+ if (nNamePos == -1)
+ continue;
+
+ Printer aPrinter;
+ // initialize to global defaults
+ aPrinter.m_aInfo = m_aGlobalDefaults;
+
+ aPrinterName = OStringToOUString(aValue.subView(nNamePos+1),
+ RTL_TEXTENCODING_UTF8);
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+ aPrinter.m_aInfo.m_aDriverName = OStringToOUString(aValue.subView(0, nNamePos), RTL_TEXTENCODING_UTF8);
+
+ // set parser, merge settings
+ // don't do this for CUPS printers as this is done
+ // by the CUPS system itself
+ if( !aPrinter.m_aInfo.m_aDriverName.startsWith( "CUPS:" ) )
+ {
+ aPrinter.m_aInfo.m_pParser = PPDParser::getParser( aPrinter.m_aInfo.m_aDriverName );
+ aPrinter.m_aInfo.m_aContext.setParser( aPrinter.m_aInfo.m_pParser );
+ // note: setParser also purges the context
+
+ // ignore this printer if its driver is not found
+ if( ! aPrinter.m_aInfo.m_pParser )
+ continue;
+
+ // merge the ppd context keys if the printer has the same keys and values
+ // this is a bit tricky, since it involves mixing two PPDs
+ // without constraints which might end up badly
+ // this feature should be use with caution
+ // it is mainly to select default paper sizes for new printers
+ for( std::size_t nPPDValueModified = 0; nPPDValueModified < m_aGlobalDefaults.m_aContext.countValuesModified(); nPPDValueModified++ )
+ {
+ const PPDKey* pDefKey = m_aGlobalDefaults.m_aContext.getModifiedKey( nPPDValueModified );
+ const PPDValue* pDefValue = m_aGlobalDefaults.m_aContext.getValue( pDefKey );
+ const PPDKey* pPrinterKey = pDefKey ? aPrinter.m_aInfo.m_pParser->getKey( pDefKey->getKey() ) : nullptr;
+ if( pDefKey && pPrinterKey )
+ // at least the options exist in both PPDs
+ {
+ if( pDefValue )
+ {
+ const PPDValue* pPrinterValue = pPrinterKey->getValue( pDefValue->m_aOption );
+ if( pPrinterValue )
+ // the printer has a corresponding option for the key
+ aPrinter.m_aInfo.m_aContext.setValue( pPrinterKey, pPrinterValue );
+ }
+ else
+ aPrinter.m_aInfo.m_aContext.setValue( pPrinterKey, nullptr );
+ }
+ }
+
+ aValue = aConfig.ReadKey( "Command"_ostr );
+ // no printer without a command
+ if (aValue.isEmpty())
+ {
+ /* TODO:
+ * porters: please append your platform to the Solaris
+ * case if your platform has SystemV printing per default.
+ */
+ #if defined __sun
+ aValue = "lp";
+ #else
+ aValue = "lpr"_ostr;
+ #endif
+ }
+ aPrinter.m_aInfo.m_aCommand = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+ }
+
+ aValue = aConfig.ReadKey( "QuickCommand"_ostr );
+ aPrinter.m_aInfo.m_aQuickCommand = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ aValue = aConfig.ReadKey( "Features"_ostr );
+ aPrinter.m_aInfo.m_aFeatures = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ // override the settings in m_aGlobalDefaults if keys exist
+ aValue = aConfig.ReadKey( "DefaultPrinter"_ostr );
+ if (aValue != "0" && !aValue.equalsIgnoreAsciiCase("false"))
+ aDefaultPrinter = aPrinterName;
+
+ aValue = aConfig.ReadKey( "Location"_ostr );
+ aPrinter.m_aInfo.m_aLocation = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ aValue = aConfig.ReadKey( "Comment"_ostr );
+ aPrinter.m_aInfo.m_aComment = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ aValue = aConfig.ReadKey( "Copies"_ostr );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nCopies = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "Orientation"_ostr );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_eOrientation = aValue.equalsIgnoreAsciiCase("Landscape") ? orientation::Landscape : orientation::Portrait;
+
+ aValue = aConfig.ReadKey( "MarginAdjust"_ostr );
+ if (!aValue.isEmpty())
+ {
+ sal_Int32 nIdx {0};
+ aPrinter.m_aInfo.m_nLeftMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ aPrinter.m_aInfo.m_nRightMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ aPrinter.m_aInfo.m_nTopMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ aPrinter.m_aInfo.m_nBottomMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ }
+
+ aValue = aConfig.ReadKey( "ColorDepth"_ostr );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nColorDepth = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "ColorDevice"_ostr );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nColorDevice = aValue.toInt32();
+
+ // now iterate over all keys to extract multi key information:
+ // 1. PPDContext information
+ for( int nKey = 0; nKey < aConfig.GetKeyCount(); ++nKey )
+ {
+ OString aKey( aConfig.GetKeyName( nKey ) );
+ if( aKey.startsWith("PPD_") && aPrinter.m_aInfo.m_pParser )
+ {
+ aValue = aConfig.ReadKey( aKey );
+ const PPDKey* pKey = aPrinter.m_aInfo.m_pParser->getKey(OStringToOUString(aKey.subView(4), RTL_TEXTENCODING_ISO_8859_1));
+ if( pKey )
+ {
+ aPrinter.m_aInfo.m_aContext.
+ setValue( pKey,
+ aValue == "*nil" ? nullptr : pKey->getValue(OStringToOUString(aValue, RTL_TEXTENCODING_ISO_8859_1)),
+ true );
+ }
+ }
+ }
+
+ setDefaultPaper( aPrinter.m_aInfo.m_aContext );
+
+ // finally insert printer
+ FileBase::getFileURLFromSystemPath( aFile.PathToFileName(), aPrinter.m_aFile );
+ std::unordered_map< OUString, Printer >::const_iterator find_it =
+ m_aPrinters.find( aPrinterName );
+ if( find_it != m_aPrinters.end() )
+ {
+ aPrinter.m_aAlternateFiles = find_it->second.m_aAlternateFiles;
+ aPrinter.m_aAlternateFiles.insert( find_it->second.m_aFile );
+ }
+ m_aPrinters[ aPrinterName ] = aPrinter;
+ }
+ }
+ }
+
+ // set default printer
+ if( !m_aPrinters.empty() )
+ {
+ if( m_aPrinters.find( aDefaultPrinter ) == m_aPrinters.end() )
+ aDefaultPrinter = m_aPrinters.begin()->first;
+ }
+ else
+ aDefaultPrinter.clear();
+ m_aDefaultPrinter = aDefaultPrinter;
+
+ if( m_eType != Type::Default )
+ return;
+
+ // add a default printer for every available print queue
+ // merge paper default printer, all else from global defaults
+ PrinterInfo aMergeInfo( m_aGlobalDefaults );
+ aMergeInfo.m_aDriverName = "SGENPRT";
+ aMergeInfo.m_aFeatures = "autoqueue";
+
+ if( !m_aDefaultPrinter.isEmpty() )
+ {
+ PrinterInfo aDefaultInfo( getPrinterInfo( m_aDefaultPrinter ) );
+
+ const PPDKey* pDefKey = aDefaultInfo.m_pParser->getKey( "PageSize" );
+ const PPDKey* pMergeKey = aMergeInfo.m_pParser->getKey( "PageSize" );
+ const PPDValue* pDefValue = aDefaultInfo.m_aContext.getValue( pDefKey );
+ const PPDValue* pMergeValue = pMergeKey ? pMergeKey->getValue( pDefValue->m_aOption ) : nullptr;
+ if( pMergeKey && pMergeValue )
+ aMergeInfo.m_aContext.setValue( pMergeKey, pMergeValue );
+ }
+
+ if( m_pQueueInfo && m_pQueueInfo->hasChanged() )
+ {
+ m_aSystemPrintCommand = m_pQueueInfo->getCommand();
+ m_pQueueInfo->getSystemQueues( m_aSystemPrintQueues );
+ m_pQueueInfo.reset();
+ }
+ for (auto const& printQueue : m_aSystemPrintQueues)
+ {
+ OUString aPrinterName = "<" + printQueue.m_aQueue + ">";
+
+ if( m_aPrinters.find( aPrinterName ) != m_aPrinters.end() )
+ // probably user made this one permanent
+ continue;
+
+ OUString aCmd( m_aSystemPrintCommand );
+ aCmd = aCmd.replaceAll( "(PRINTER)", printQueue.m_aQueue );
+
+ Printer aPrinter;
+
+ // initialize to merged defaults
+ aPrinter.m_aInfo = aMergeInfo;
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+ aPrinter.m_aInfo.m_aCommand = aCmd;
+ aPrinter.m_aInfo.m_aComment = printQueue.m_aComment;
+ aPrinter.m_aInfo.m_aLocation = printQueue.m_aLocation;
+
+ m_aPrinters[ aPrinterName ] = aPrinter;
+ }
+}
+
+void PrinterInfoManager::listPrinters( ::std::vector< OUString >& rVector ) const
+{
+ rVector.clear();
+ for (auto const& printer : m_aPrinters)
+ rVector.push_back(printer.first);
+}
+
+const PrinterInfo& PrinterInfoManager::getPrinterInfo( const OUString& rPrinter ) const
+{
+ static PrinterInfo aEmptyInfo;
+ std::unordered_map< OUString, Printer >::const_iterator it = m_aPrinters.find( rPrinter );
+
+ SAL_WARN_IF( it == m_aPrinters.end(), "vcl", "Do not ask for info about nonexistent printers" );
+
+ return it != m_aPrinters.end() ? it->second.m_aInfo : aEmptyInfo;
+}
+
+bool PrinterInfoManager::checkFeatureToken( const OUString& rPrinterName, std::string_view pToken ) const
+{
+ const PrinterInfo& rPrinterInfo( getPrinterInfo( rPrinterName ) );
+ sal_Int32 nIndex = 0;
+ while( nIndex != -1 )
+ {
+ OUString aOuterToken = rPrinterInfo.m_aFeatures.getToken( 0, ',', nIndex );
+ if( aOuterToken.getToken( 0, '=' ).equalsIgnoreAsciiCaseAscii( pToken ) )
+ return true;
+ }
+ return false;
+}
+
+FILE* PrinterInfoManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
+{
+ const PrinterInfo& rPrinterInfo = getPrinterInfo (rPrintername);
+ const OUString& rCommand = (bQuickCommand && !rPrinterInfo.m_aQuickCommand.isEmpty() ) ?
+ rPrinterInfo.m_aQuickCommand : rPrinterInfo.m_aCommand;
+ OString aShellCommand = OUStringToOString (rCommand, RTL_TEXTENCODING_ISO_8859_1) +
+ " 2>/dev/null";
+
+ return popen (aShellCommand.getStr(), "w");
+}
+
+bool PrinterInfoManager::endSpool( const OUString& /*rPrintername*/, const OUString& /*rJobTitle*/, FILE* pFile, const JobData& /*rDocumentJobData*/, bool /*bBanner*/, const OUString& /*rFaxNumber*/ )
+{
+ return (0 == pclose( pFile ));
+}
+
+void PrinterInfoManager::setupJobContextData( JobData& rData )
+{
+ std::unordered_map< OUString, Printer >::iterator it =
+ m_aPrinters.find( rData.m_aPrinterName );
+ if( it != m_aPrinters.end() )
+ {
+ rData.m_pParser = it->second.m_aInfo.m_pParser;
+ rData.m_aContext = it->second.m_aInfo.m_aContext;
+ }
+}
+
+void PrinterInfoManager::setDefaultPaper( PPDContext& rContext ) const
+{
+ if( ! rContext.getParser() )
+ return;
+
+ const PPDKey* pPageSizeKey = rContext.getParser()->getKey( "PageSize" );
+ if( ! pPageSizeKey )
+ return;
+
+ std::size_t nModified = rContext.countValuesModified();
+ auto set = false;
+ for (std::size_t i = 0; i != nModified; ++i) {
+ if (rContext.getModifiedKey(i) == pPageSizeKey) {
+ set = true;
+ break;
+ }
+ }
+
+ if( set ) // paper was set already, do not modify
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.unx.print", "not setting default paper, already set "
+ << rContext.getValue( pPageSizeKey )->m_aOption);
+#endif
+ return;
+ }
+
+ // paper not set, fill in default value
+ const PPDValue* pPaperVal = nullptr;
+ int nValues = pPageSizeKey->countValues();
+ for( int i = 0; i < nValues && ! pPaperVal; i++ )
+ {
+ const PPDValue* pVal = pPageSizeKey->getValue( i );
+ if( pVal->m_aOption.equalsIgnoreAsciiCase( m_aSystemDefaultPaper ) )
+ pPaperVal = pVal;
+ }
+ if( pPaperVal )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "setting default paper "
+ << pPaperVal->m_aOption);
+#endif
+ rContext.setValue( pPageSizeKey, pPaperVal );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "-> got paper "
+ << rContext.getValue( pPageSizeKey )->m_aOption);
+#endif
+ }
+}
+
+SystemQueueInfo::SystemQueueInfo() :
+ m_bChanged( false )
+{
+ create();
+}
+
+SystemQueueInfo::~SystemQueueInfo()
+{
+ static const char* pNoSyncDetection = getenv( "SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION" );
+ if( ! pNoSyncDetection || !*pNoSyncDetection )
+ join();
+ else
+ terminate();
+}
+
+bool SystemQueueInfo::hasChanged() const
+{
+ std::unique_lock aGuard( m_aMutex );
+ return m_bChanged;
+}
+
+void SystemQueueInfo::getSystemQueues( std::vector< PrinterInfoManager::SystemPrintQueue >& rQueues )
+{
+ std::unique_lock aGuard( m_aMutex );
+ rQueues = m_aQueues;
+ m_bChanged = false;
+}
+
+OUString SystemQueueInfo::getCommand() const
+{
+ std::unique_lock aGuard( m_aMutex );
+ return m_aCommand;
+}
+
+namespace {
+
+struct SystemCommandParameters;
+
+}
+
+typedef void(* tokenHandler)(const std::vector< OString >&,
+ std::vector< PrinterInfoManager::SystemPrintQueue >&,
+ const SystemCommandParameters*);
+
+namespace {
+
+struct SystemCommandParameters
+{
+ const char* pQueueCommand;
+ const char* pPrintCommand;
+ const char* pForeToken;
+ const char* pAftToken;
+ unsigned int nForeTokenCount;
+ tokenHandler pHandler;
+};
+
+}
+
+#if ! (defined(LINUX) || defined(NETBSD) || defined(FREEBSD) || defined(OPENBSD))
+static void lpgetSysQueueTokenHandler(
+ const std::vector< OString >& i_rLines,
+ std::vector< PrinterInfoManager::SystemPrintQueue >& o_rQueues,
+ const SystemCommandParameters* )
+{
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ std::unordered_set< OUString > aUniqueSet;
+ std::unordered_set< OUString > aOnlySet;
+ aUniqueSet.insert( OUString( "_all" ) );
+ aUniqueSet.insert( OUString( "_default" ) );
+
+ // the eventual "all" attribute of the "_all" queue tells us, which
+ // printers are to be used for this user at all
+
+ // find _all: line
+ OString aAllLine( "_all:" );
+ OString aAllAttr( "all=" );
+ auto it = std::find_if(i_rLines.begin(), i_rLines.end(),
+ [&aAllLine](const OString& rLine) { return rLine.indexOf( aAllLine, 0 ) == 0; });
+ if( it != i_rLines.end() )
+ {
+ // now find the "all" attribute
+ ++it;
+ it = std::find_if(it, i_rLines.end(),
+ [&aAllAttr](const OString& rLine) { return WhitespaceToSpace( rLine ).startsWith( aAllAttr ); });
+ if( it != i_rLines.end() )
+ {
+ // insert the comma separated entries into the set of printers to use
+ OString aClean( WhitespaceToSpace( *it ) );
+ sal_Int32 nPos = aAllAttr.getLength();
+ while( nPos != -1 )
+ {
+ OString aTok( aClean.getToken( 0, ',', nPos ) );
+ if( !aTok.isEmpty() )
+ aOnlySet.insert( OStringToOUString( aTok, aEncoding ) );
+ }
+ }
+ }
+
+ bool bInsertAttribute = false;
+ OString aDescrStr( "description=" );
+ OString aLocStr( "location=" );
+ for (auto const& line : i_rLines)
+ {
+ sal_Int32 nPos = 0;
+ // find the begin of a new printer section
+ nPos = line.indexOf( ':', 0 );
+ if( nPos != -1 )
+ {
+ OUString aSysQueue( OStringToOUString( line.copy( 0, nPos ), aEncoding ) );
+ // do not insert duplicates (e.g. lpstat tends to produce such lines)
+ // in case there was a "_all" section, insert only those printer explicitly
+ // set in the "all" attribute
+ if( aUniqueSet.find( aSysQueue ) == aUniqueSet.end() &&
+ ( aOnlySet.empty() || aOnlySet.find( aSysQueue ) != aOnlySet.end() )
+ )
+ {
+ o_rQueues.push_back( PrinterInfoManager::SystemPrintQueue() );
+ o_rQueues.back().m_aQueue = aSysQueue;
+ o_rQueues.back().m_aLocation = aSysQueue;
+ aUniqueSet.insert( aSysQueue );
+ bInsertAttribute = true;
+ }
+ else
+ bInsertAttribute = false;
+ continue;
+ }
+ if( bInsertAttribute && ! o_rQueues.empty() )
+ {
+ // look for "description" attribute, insert as comment
+ nPos = line.indexOf( aDescrStr, 0 );
+ if( nPos != -1 )
+ {
+ OString aComment( WhitespaceToSpace( line.copy(nPos+12) ) );
+ if( !aComment.isEmpty() )
+ o_rQueues.back().m_aComment = OStringToOUString(aComment, aEncoding);
+ continue;
+ }
+ // look for "location" attribute, insert as location
+ nPos = line.indexOf( aLocStr, 0 );
+ if( nPos != -1 )
+ {
+ OString aLoc( WhitespaceToSpace( line.copy(nPos+9) ) );
+ if( !aLoc.isEmpty() )
+ o_rQueues.back().m_aLocation = OStringToOUString(aLoc, aEncoding);
+ continue;
+ }
+ }
+ }
+}
+#endif
+static void standardSysQueueTokenHandler(
+ const std::vector< OString >& i_rLines,
+ std::vector< PrinterInfoManager::SystemPrintQueue >& o_rQueues,
+ const SystemCommandParameters* i_pParms)
+{
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ std::unordered_set< OUString > aUniqueSet;
+ OString aForeToken( i_pParms->pForeToken );
+ OString aAftToken( i_pParms->pAftToken );
+ /* Normal Unix print queue discovery, also used for Darwin 5 LPR printing
+ */
+ for (auto const& line : i_rLines)
+ {
+ sal_Int32 nPos = 0;
+
+ // search for a line describing a printer:
+ // find if there are enough tokens before the name
+ for( unsigned int i = 0; i < i_pParms->nForeTokenCount && nPos != -1; i++ )
+ {
+ nPos = line.indexOf( aForeToken, nPos );
+ if( nPos != -1 && line.getLength() >= nPos+aForeToken.getLength() )
+ nPos += aForeToken.getLength();
+ }
+ if( nPos != -1 )
+ {
+ // find if there is the token after the queue
+ sal_Int32 nAftPos = line.indexOf( aAftToken, nPos );
+ if( nAftPos != -1 )
+ {
+ // get the queue name between fore and aft tokens
+ OUString aSysQueue( OStringToOUString( line.subView( nPos, nAftPos - nPos ), aEncoding ) );
+ // do not insert duplicates (e.g. lpstat tends to produce such lines)
+ if( aUniqueSet.insert( aSysQueue ).second )
+ {
+ o_rQueues.emplace_back( );
+ o_rQueues.back().m_aQueue = aSysQueue;
+ o_rQueues.back().m_aLocation = aSysQueue;
+ }
+ }
+ }
+ }
+}
+
+const struct SystemCommandParameters aParms[] =
+{
+ #if defined(LINUX) || defined(NETBSD) || defined(FREEBSD) || defined(OPENBSD)
+ { "/usr/sbin/lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler },
+ { "lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler },
+ { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpstat -s", "lp -d \"(PRINTER)\"", "system for ", ": ", 1, standardSysQueueTokenHandler }
+ #else
+ { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpget list", "lp -d \"(PRINTER)\"", "", ":", 0, lpgetSysQueueTokenHandler },
+ { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpstat -s", "lp -d \"(PRINTER)\"", "system for ", ": ", 1, standardSysQueueTokenHandler },
+ { "/usr/sbin/lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler },
+ { "lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler }
+ #endif
+};
+
+void SystemQueueInfo::run()
+{
+ osl_setThreadName("LPR psp::SystemQueueInfo");
+
+ char pBuffer[1024];
+ std::vector< OString > aLines;
+
+ /* Discover which command we can use to get a list of all printer queues */
+ for(const auto & rParm : aParms)
+ {
+ aLines.clear();
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "trying print queue command \""
+ << rParm.pQueueCommand
+ << "\" ...");
+#endif
+ OString aCmdLine = rParm.pQueueCommand + OString::Concat(" 2>/dev/null");
+ FILE *pPipe;
+ if( (pPipe = popen( aCmdLine.getStr(), "r" )) )
+ {
+ while( fgets( pBuffer, 1024, pPipe ) )
+ aLines.emplace_back( pBuffer );
+ if( ! pclose( pPipe ) )
+ {
+ std::vector< PrinterInfoManager::SystemPrintQueue > aSysPrintQueues;
+ rParm.pHandler( aLines, aSysPrintQueues, &rParm );
+ std::unique_lock aGuard( m_aMutex );
+ m_bChanged = true;
+ m_aQueues = aSysPrintQueues;
+ m_aCommand = OUString::createFromAscii( rParm.pPrintCommand );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "printing queue command: success.");
+#endif
+ break;
+ }
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "printing queue command: failed.");
+#endif
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/window/salframe.cxx b/vcl/unx/generic/window/salframe.cxx
new file mode 100644
index 0000000000..e3300de343
--- /dev/null
+++ b/vcl/unx/generic/window/salframe.cxx
@@ -0,0 +1,3871 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <string_view>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <tools/debug.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/keycodes.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/BitmapTools.hxx>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <X11/keysym.h>
+#include <X11/extensions/shape.h>
+
+#include <headless/BitmapHelper.hxx>
+#include <headless/svpbmp.hxx>
+#include <unx/saldisp.hxx>
+#include <unx/salgdi.h>
+#include <unx/salframe.h>
+#include <unx/wmadaptor.hxx>
+#include <unx/i18n_ic.hxx>
+#include <unx/i18n_keysym.hxx>
+#include <opengl/zone.hxx>
+
+#include <unx/gensys.h>
+#include <window.h>
+
+#include <sal/macros.h>
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
+#include <com/sun/star/uno/Exception.hpp>
+
+#include <salinst.hxx>
+#include <svdata.hxx>
+#include <bitmaps.hlst>
+
+#include <cairo-xlib.h>
+
+#include <optional>
+
+#include <algorithm>
+
+#ifndef Button6
+# define Button6 6
+#endif
+#ifndef Button7
+# define Button7 7
+#endif
+
+using namespace vcl_sal;
+
+constexpr auto CLIENT_EVENTS = StructureNotifyMask
+ | SubstructureNotifyMask
+ | KeyPressMask
+ | KeyReleaseMask
+ | ButtonPressMask
+ | ButtonReleaseMask
+ | PointerMotionMask
+ | EnterWindowMask
+ | LeaveWindowMask
+ | FocusChangeMask
+ | ExposureMask
+ | VisibilityChangeMask
+ | PropertyChangeMask
+ | ColormapChangeMask;
+
+static ::Window hPresentationWindow = None, hPresFocusWindow = None;
+static ::std::list< ::Window > aPresentationReparentList;
+static int nVisibleFloats = 0;
+
+static void doReparentPresentationDialogues( SalDisplay const * pDisplay )
+{
+ GetGenericUnixSalData()->ErrorTrapPush();
+ for (auto const& elem : aPresentationReparentList)
+ {
+ int x, y;
+ ::Window aRoot, aChild;
+ unsigned int w, h, bw, d;
+ XGetGeometry( pDisplay->GetDisplay(),
+ elem,
+ &aRoot,
+ &x, &y, &w, &h, &bw, &d );
+ XTranslateCoordinates( pDisplay->GetDisplay(),
+ hPresentationWindow,
+ aRoot,
+ x, y,
+ &x, &y,
+ &aChild );
+ XReparentWindow( pDisplay->GetDisplay(),
+ elem,
+ aRoot,
+ x, y );
+ }
+ aPresentationReparentList.clear();
+ if( hPresFocusWindow )
+ XSetInputFocus( pDisplay->GetDisplay(), hPresFocusWindow, PointerRoot, CurrentTime );
+ XSync( pDisplay->GetDisplay(), False );
+ GetGenericUnixSalData()->ErrorTrapPop();
+}
+
+bool X11SalFrame::IsOverrideRedirect() const
+{
+ return
+ ((nStyle_ & SalFrameStyleFlags::INTRO) && !pDisplay_->getWMAdaptor()->supportsSplash())
+ ||
+ (!( nStyle_ & ~SalFrameStyleFlags::DEFAULT ) && !pDisplay_->getWMAdaptor()->supportsFullScreen())
+ ;
+}
+
+bool X11SalFrame::IsFloatGrabWindow() const
+{
+ static const char* pDisableGrab = getenv( "SAL_DISABLE_FLOATGRAB" );
+
+ return
+ ( ( !pDisableGrab || !*pDisableGrab ) &&
+ (
+ (nStyle_ & SalFrameStyleFlags::FLOAT) &&
+ ! (nStyle_ & SalFrameStyleFlags::TOOLTIP) &&
+ ! (nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION)
+ )
+ );
+}
+
+void X11SalFrame::setXEmbedInfo()
+{
+ if( !m_bXEmbed )
+ return;
+
+ tools::Long aInfo[2];
+ aInfo[0] = 1; // XEMBED protocol version
+ aInfo[1] = (bMapped_ ? 1 : 0); // XEMBED_MAPPED
+ XChangeProperty( pDisplay_->GetDisplay(),
+ mhWindow,
+ pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::XEMBED_INFO ),
+ pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::XEMBED_INFO ),
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(aInfo),
+ SAL_N_ELEMENTS(aInfo) );
+}
+
+void X11SalFrame::askForXEmbedFocus( sal_Int32 i_nTimeCode )
+{
+ XEvent aEvent;
+
+ memset( &aEvent, 0, sizeof(aEvent) );
+ aEvent.xclient.window = mhForeignParent;
+ aEvent.xclient.type = ClientMessage;
+ aEvent.xclient.message_type = pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::XEMBED );
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = i_nTimeCode ? i_nTimeCode : CurrentTime;
+ aEvent.xclient.data.l[1] = 3; // XEMBED_REQUEST_FOCUS
+ aEvent.xclient.data.l[2] = 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+
+ GetGenericUnixSalData()->ErrorTrapPush();
+ XSendEvent( pDisplay_->GetDisplay(),
+ mhForeignParent,
+ False, NoEventMask, &aEvent );
+ XSync( pDisplay_->GetDisplay(), False );
+ GetGenericUnixSalData()->ErrorTrapPop();
+}
+
+typedef std::vector< unsigned long > NetWmIconData;
+
+namespace
+{
+ constexpr OUString SV_ICON_SIZE48[] =
+ {
+ MAINAPP_48_8,
+ MAINAPP_48_8,
+ ODT_48_8,
+ OTT_48_8,
+ ODS_48_8,
+ OTS_48_8,
+ ODG_48_8,
+ MAINAPP_48_8,
+ ODP_48_8,
+ MAINAPP_48_8,
+ ODM_48_8,
+ MAINAPP_48_8,
+ ODB_48_8,
+ ODF_48_8
+ };
+
+ constexpr OUString SV_ICON_SIZE32[] =
+ {
+ MAINAPP_32_8,
+ MAINAPP_32_8,
+ ODT_32_8,
+ OTT_32_8,
+ ODS_32_8,
+ OTS_32_8,
+ ODG_32_8,
+ MAINAPP_32_8,
+ ODP_32_8,
+ MAINAPP_32_8,
+ ODM_32_8,
+ MAINAPP_32_8,
+ ODB_32_8,
+ ODF_32_8
+ };
+
+ constexpr OUString SV_ICON_SIZE16[] =
+ {
+ MAINAPP_16_8,
+ MAINAPP_16_8,
+ ODT_16_8,
+ OTT_16_8,
+ ODS_16_8,
+ OTS_16_8,
+ ODG_16_8,
+ MAINAPP_16_8,
+ ODP_16_8,
+ MAINAPP_16_8,
+ ODM_16_8,
+ MAINAPP_16_8,
+ ODB_16_8,
+ ODF_16_8
+ };
+}
+
+static void CreateNetWmAppIcon( sal_uInt16 nIcon, NetWmIconData& netwm_icon )
+{
+ const int sizes[ 3 ] = { 48, 32, 16 };
+ netwm_icon.resize( 48 * 48 + 32 * 32 + 16 * 16 + 3 * 2 );
+ int pos = 0;
+ for(int size : sizes)
+ {
+ OUString sIcon;
+ if( size >= 48 )
+ sIcon = SV_ICON_SIZE48[nIcon];
+ else if( size >= 32 )
+ sIcon = SV_ICON_SIZE32[nIcon];
+ else
+ sIcon = SV_ICON_SIZE16[nIcon];
+
+ BitmapEx aIcon = vcl::bitmap::loadFromName(sIcon, ImageLoadFlags::IgnoreScalingFactor);
+
+ if( aIcon.IsEmpty())
+ continue;
+ vcl::bitmap::convertBitmap32To24Plus8(aIcon, aIcon);
+ Bitmap icon = aIcon.GetBitmap();
+ AlphaMask mask = aIcon.GetAlphaMask();
+ BitmapScopedReadAccess iconData(icon);
+ BitmapScopedReadAccess maskData(mask);
+ netwm_icon[ pos++ ] = size; // width
+ netwm_icon[ pos++ ] = size; // height
+ for( int y = 0; y < size; ++y )
+ for( int x = 0; x < size; ++x )
+ {
+ BitmapColor col = iconData->GetColor( y, x );
+ BitmapColor alpha = maskData->GetColor( y, x );
+ netwm_icon[ pos++ ] = (((( 255 - alpha.GetBlue()) * 256U ) + col.GetRed()) * 256 + col.GetGreen()) * 256 + col.GetBlue();
+ }
+ }
+ netwm_icon.resize( pos );
+}
+
+void X11SalFrame::Init( SalFrameStyleFlags nSalFrameStyle, SalX11Screen nXScreen, SystemParentData const * pParentData, bool bUseGeometry )
+{
+ if( nXScreen.getXScreen() >= GetDisplay()->GetXScreenCount() )
+ nXScreen = GetDisplay()->GetDefaultXScreen();
+ if( mpParent )
+ nXScreen = mpParent->m_nXScreen;
+
+ m_nXScreen = nXScreen;
+ nStyle_ = nSalFrameStyle;
+ XWMHints Hints;
+ Hints.flags = InputHint;
+ Hints.input = (nSalFrameStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) ? False : True;
+ NetWmIconData netwm_icon;
+
+ int x = 0, y = 0;
+ unsigned int w = 500, h = 500;
+ XSetWindowAttributes Attributes;
+
+ int nAttrMask = CWBorderPixel
+ | CWBackPixmap
+ | CWColormap
+ | CWOverrideRedirect
+ | CWEventMask
+ ;
+ Attributes.border_pixel = 0;
+ Attributes.background_pixmap = None;
+ Attributes.colormap = GetDisplay()->GetColormap( m_nXScreen ).GetXColormap();
+ Attributes.override_redirect = False;
+ Attributes.event_mask = CLIENT_EVENTS;
+
+ const SalVisual& rVis = GetDisplay()->GetVisual( m_nXScreen );
+ ::Window aFrameParent = pParentData ? pParentData->aWindow : GetDisplay()->GetRootWindow( m_nXScreen );
+ ::Window aClientLeader = None;
+
+ if( bUseGeometry )
+ {
+ x = maGeometry.x();
+ y = maGeometry.y();
+ w = maGeometry.width();
+ h = maGeometry.height();
+ }
+
+ if( (nSalFrameStyle & SalFrameStyleFlags::FLOAT) &&
+ ! (nSalFrameStyle & SalFrameStyleFlags::OWNERDRAWDECORATION)
+ )
+ {
+ if( nShowState_ == X11ShowState::Unknown )
+ {
+ w = 10;
+ h = 10;
+ }
+ Attributes.override_redirect = True;
+ }
+ else if( nSalFrameStyle & SalFrameStyleFlags::SYSTEMCHILD )
+ {
+ SAL_WARN_IF( !mpParent, "vcl", "SalFrameStyleFlags::SYSTEMCHILD window without parent" );
+ if( mpParent )
+ {
+ aFrameParent = mpParent->mhWindow;
+ // FIXME: since with SalFrameStyleFlags::SYSTEMCHILD
+ // multiple X11SalFrame objects can have the same shell window
+ // dispatching events in saldisp.cxx is unclear (the first frame)
+ // wins. HTH this correctly is unclear yet
+ // for the time being, treat set the shell window to own window
+ // like for a normal frame
+ // mhShellWindow = mpParent->GetShellWindow();
+ }
+ }
+ else if( pParentData )
+ {
+ // plugin parent may be killed unexpectedly by plugging
+ // process; start permanently ignoring X errors...
+ GetGenericUnixSalData()->ErrorTrapPush();
+
+ nStyle_ |= SalFrameStyleFlags::PLUG;
+ Attributes.override_redirect = True;
+ if( pParentData->nSize >= sizeof(SystemParentData) )
+ m_bXEmbed = pParentData->bXEmbedSupport;
+
+ int x_ret, y_ret;
+ unsigned int bw, d;
+ ::Window aRoot, aParent;
+
+ XGetGeometry( GetXDisplay(), pParentData->aWindow,
+ &aRoot, &x_ret, &y_ret, &w, &h, &bw, &d );
+ mhForeignParent = pParentData->aWindow;
+
+ mhShellWindow = aParent = mhForeignParent;
+ ::Window* pChildren;
+ unsigned int nChildren;
+ bool bBreak = false;
+ do
+ {
+ XQueryTree( GetDisplay()->GetDisplay(), mhShellWindow,
+ &aRoot, &aParent, &pChildren, &nChildren );
+ XFree( pChildren );
+ if( aParent != aRoot )
+ mhShellWindow = aParent;
+ int nCount = 0;
+ Atom* pProps = XListProperties( GetDisplay()->GetDisplay(),
+ mhShellWindow,
+ &nCount );
+ for( int i = 0; i < nCount && ! bBreak; ++i )
+ bBreak = (pProps[i] == XA_WM_HINTS);
+ if( pProps )
+ XFree( pProps );
+ } while( aParent != aRoot && ! bBreak );
+
+ // check if this is really one of our own frames
+ // do not change the input mask in that case
+ bool bIsReallyOurFrame = false;
+ for (auto pSalFrame : GetDisplay()->getFrames() )
+ if ( static_cast<const X11SalFrame*>( pSalFrame )->GetWindow() == mhForeignParent )
+ {
+ bIsReallyOurFrame = true;
+ break;
+ }
+ if (!bIsReallyOurFrame)
+ {
+ XSelectInput( GetDisplay()->GetDisplay(), mhForeignParent, StructureNotifyMask | FocusChangeMask );
+ XSelectInput( GetDisplay()->GetDisplay(), mhShellWindow, StructureNotifyMask | FocusChangeMask );
+ }
+ }
+ else
+ {
+ if( ! bUseGeometry )
+ {
+ Size aScreenSize( GetDisplay()->getDataForScreen( m_nXScreen ).m_aSize );
+ w = aScreenSize.Width();
+ h = aScreenSize.Height();
+ if( nSalFrameStyle & SalFrameStyleFlags::SIZEABLE &&
+ nSalFrameStyle & SalFrameStyleFlags::MOVEABLE )
+ {
+ Size aBestFitSize(bestmaxFrameSizeForScreenSize(aScreenSize));
+ w = aBestFitSize.Width();
+ h = aBestFitSize.Height();
+ }
+ if( ! mpParent )
+ {
+ // find the last document window (if any)
+ const X11SalFrame* pFrame = nullptr;
+ bool bIsDocumentWindow = false;
+ for (auto pSalFrame : GetDisplay()->getFrames() )
+ {
+ pFrame = static_cast< const X11SalFrame* >( pSalFrame );
+ if( !pFrame->mpParent
+ && !pFrame->mbFullScreen
+ && ( pFrame->nStyle_ & SalFrameStyleFlags::SIZEABLE )
+ && pFrame->GetUnmirroredGeometry().width()
+ && pFrame->GetUnmirroredGeometry().height() )
+ {
+ bIsDocumentWindow = true;
+ break;
+ }
+ }
+
+ if( bIsDocumentWindow )
+ {
+ // set a document position and size
+ // the first frame gets positioned by the window manager
+ const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() );
+ x = rGeom.x();
+ y = rGeom.y();
+ if( x+static_cast<int>(w)+40 <= static_cast<int>(aScreenSize.Width()) &&
+ y+static_cast<int>(h)+40 <= static_cast<int>(aScreenSize.Height())
+ )
+ {
+ y += 40;
+ x += 40;
+ }
+ else
+ {
+ x = 10; // leave some space for decoration
+ y = 20;
+ }
+ }
+ else if( GetDisplay()->IsXinerama() )
+ {
+ // place frame on same screen as mouse pointer
+ ::Window aRoot, aChild;
+ int root_x = 0, root_y = 0, lx, ly;
+ unsigned int mask;
+ XQueryPointer( GetXDisplay(),
+ GetDisplay()->GetRootWindow( m_nXScreen ),
+ &aRoot, &aChild,
+ &root_x, &root_y, &lx, &ly, &mask );
+ const std::vector< AbsoluteScreenPixelRectangle >& rScreens = GetDisplay()->GetXineramaScreens();
+ for(const auto & rScreen : rScreens)
+ if( rScreen.Contains( AbsoluteScreenPixelPoint( root_x, root_y ) ) )
+ {
+ x = rScreen.Left();
+ y = rScreen.Top();
+ break;
+ }
+ }
+ }
+ }
+ Attributes.win_gravity = pDisplay_->getWMAdaptor()->getInitWinGravity();
+ nAttrMask |= CWWinGravity;
+ if( mpParent )
+ {
+ Attributes.save_under = True;
+ nAttrMask |= CWSaveUnder;
+ }
+ if( IsOverrideRedirect() )
+ Attributes.override_redirect = True;
+ // default icon
+ if( !(nStyle_ & SalFrameStyleFlags::INTRO) && !(nStyle_ & SalFrameStyleFlags::NOICON))
+ {
+ try
+ {
+ CreateNetWmAppIcon( mnIconID != SV_ICON_ID_OFFICE ? mnIconID :
+ (mpParent ? mpParent->mnIconID : SV_ICON_ID_OFFICE), netwm_icon );
+ }
+ catch( css::uno::Exception& )
+ {
+ // can happen - no ucb during early startup
+ }
+ }
+
+ // find the top level frame of the transience hierarchy
+ X11SalFrame* pFrame = this;
+ while( pFrame->mpParent )
+ pFrame = pFrame->mpParent;
+ if( pFrame->nStyle_ & SalFrameStyleFlags::PLUG )
+ {
+ // if the top level window is a plugin window,
+ // then we should place us in the same window group as
+ // the parent application (or none if there is no window group
+ // hint in the parent).
+ if( pFrame->GetShellWindow() )
+ {
+ XWMHints* pWMHints = XGetWMHints( pDisplay_->GetDisplay(),
+ pFrame->GetShellWindow() );
+ if( pWMHints )
+ {
+ if( pWMHints->flags & WindowGroupHint )
+ {
+ Hints.flags |= WindowGroupHint;
+ Hints.window_group = pWMHints->window_group;
+ }
+ XFree( pWMHints );
+ }
+ }
+ }
+ else
+ {
+ Hints.flags |= WindowGroupHint;
+ Hints.window_group = pFrame->GetShellWindow();
+ // note: for a normal document window this will produce None
+ // as the window is not yet created and the shell window is
+ // initialized to None. This must be corrected after window creation.
+ aClientLeader = GetDisplay()->GetDrawable( m_nXScreen );
+ }
+ }
+
+ nShowState_ = X11ShowState::Unknown;
+ bViewable_ = true;
+ bMapped_ = false;
+ nVisibility_ = VisibilityFullyObscured;
+ mhWindow = XCreateWindow( GetXDisplay(),
+ aFrameParent,
+ x, y,
+ w, h,
+ 0,
+ rVis.GetDepth(),
+ InputOutput,
+ rVis.GetVisual(),
+ nAttrMask,
+ &Attributes );
+ mpSurface = cairo_xlib_surface_create(GetXDisplay(), mhWindow,
+ rVis.GetVisual(),
+ w, h);
+
+ // FIXME: see above: fake shell window for now to own window
+ if( pParentData == nullptr )
+ {
+ mhShellWindow = mhWindow;
+ }
+
+ // correct window group if necessary
+ if( (Hints.flags & WindowGroupHint) == WindowGroupHint )
+ {
+ if( Hints.window_group == None )
+ Hints.window_group = GetShellWindow();
+ }
+
+ maGeometry.setPosSize({ x, y }, { w, h });
+ updateScreenNumber();
+
+ XSync( GetXDisplay(), False );
+ setXEmbedInfo();
+
+ Time nUserTime = (nStyle_ & (SalFrameStyleFlags::OWNERDRAWDECORATION | SalFrameStyleFlags::TOOLWINDOW) ) == SalFrameStyleFlags::NONE ?
+ pDisplay_->GetLastUserEventTime() : 0;
+ pDisplay_->getWMAdaptor()->setUserTime( this, nUserTime );
+
+ if( ! pParentData && ! IsChildWindow() && ! Attributes.override_redirect )
+ {
+ XSetWMHints( GetXDisplay(), mhWindow, &Hints );
+ // WM Protocols && internals
+ Atom a[3];
+ int n = 0;
+ a[n++] = pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::WM_DELETE_WINDOW );
+
+// LibreOffice advertises NET_WM_PING atom, so mutter rightfully warns of an unresponsive application during debugging.
+// Hack that out unconditionally for debug builds, as per https://bugzilla.redhat.com/show_bug.cgi?id=981149
+// upstream refuses to make this configurable in any way.
+// NOTE: You need to use the 'gen' backend for this to work (SAL_USE_VCLPLUGIN=gen)
+#if OSL_DEBUG_LEVEL < 1
+ if( pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_PING ) )
+ a[n++] = pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_PING );
+#endif
+
+ if( nSalFrameStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
+ a[n++] = pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::WM_TAKE_FOCUS );
+ XSetWMProtocols( GetXDisplay(), GetShellWindow(), a, n );
+
+ // force wm class hint
+ mnExtStyle = ~0;
+ if (mpParent)
+ m_sWMClass = mpParent->m_sWMClass;
+ SetExtendedFrameStyle( 0 );
+
+ XSizeHints* pHints = XAllocSizeHints();
+ pHints->flags = PWinGravity | PPosition;
+ pHints->win_gravity = GetDisplay()->getWMAdaptor()->getPositionWinGravity();
+ pHints->x = 0;
+ pHints->y = 0;
+ if( mbFullScreen )
+ {
+ pHints->flags |= PMaxSize | PMinSize;
+ pHints->max_width = w+100;
+ pHints->max_height = h+100;
+ pHints->min_width = w;
+ pHints->min_height = h;
+ }
+ XSetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints );
+ XFree (pHints);
+
+ // set PID and WM_CLIENT_MACHINE
+ pDisplay_->getWMAdaptor()->setClientMachine( this );
+ pDisplay_->getWMAdaptor()->setPID( this );
+
+ // set client leader
+ if( aClientLeader )
+ {
+ XChangeProperty( GetXDisplay(),
+ mhWindow,
+ pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::WM_CLIENT_LEADER),
+ XA_WINDOW,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(&aClientLeader),
+ 1
+ );
+ }
+#define DECOFLAGS (SalFrameStyleFlags::MOVEABLE | SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::CLOSEABLE)
+ int nDecoFlags = WMAdaptor::decoration_All;
+ if (m_bIsPartialFullScreen || (nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION))
+ nDecoFlags = 0;
+ else if( (nStyle_ & DECOFLAGS ) != DECOFLAGS || (nStyle_ & SalFrameStyleFlags::TOOLWINDOW) )
+ {
+ if( nStyle_ & DECOFLAGS )
+ // if any decoration, then show a border
+ nDecoFlags = WMAdaptor::decoration_Border;
+ else
+ nDecoFlags = 0;
+
+ if( ! mpParent && (nStyle_ & DECOFLAGS) )
+ // don't add a min button if window should be decorationless
+ nDecoFlags |= WMAdaptor::decoration_MinimizeBtn;
+ if( nStyle_ & SalFrameStyleFlags::CLOSEABLE )
+ nDecoFlags |= WMAdaptor::decoration_CloseBtn;
+ if( nStyle_ & SalFrameStyleFlags::SIZEABLE )
+ {
+ nDecoFlags |= WMAdaptor::decoration_Resize;
+ if( ! (nStyle_ & SalFrameStyleFlags::TOOLWINDOW) )
+ nDecoFlags |= WMAdaptor::decoration_MaximizeBtn;
+ }
+ if( nStyle_ & SalFrameStyleFlags::MOVEABLE )
+ nDecoFlags |= WMAdaptor::decoration_Title;
+ }
+
+ WMWindowType eType = WMWindowType::Normal;
+ if( nStyle_ & SalFrameStyleFlags::INTRO )
+ eType = WMWindowType::Splash;
+ if( (nStyle_ & SalFrameStyleFlags::DIALOG) && hPresentationWindow == None )
+ eType = WMWindowType::ModelessDialogue;
+ if( nStyle_ & SalFrameStyleFlags::TOOLWINDOW )
+ eType = WMWindowType::Utility;
+ if( nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION )
+ eType = WMWindowType::Toolbar;
+ if (m_bIsPartialFullScreen && GetDisplay()->getWMAdaptor()->isLegacyPartialFullscreen())
+ eType = WMWindowType::Dock;
+
+ GetDisplay()->getWMAdaptor()->
+ setFrameTypeAndDecoration( this,
+ eType,
+ nDecoFlags,
+ hPresentationWindow ? nullptr : mpParent );
+
+ if (!m_bIsPartialFullScreen && (nStyle_ & (SalFrameStyleFlags::DEFAULT |
+ SalFrameStyleFlags::OWNERDRAWDECORATION|
+ SalFrameStyleFlags::FLOAT |
+ SalFrameStyleFlags::INTRO))
+ == SalFrameStyleFlags::DEFAULT )
+ pDisplay_->getWMAdaptor()->maximizeFrame( this );
+
+ if( !netwm_icon.empty() && GetDisplay()->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_ICON ))
+ XChangeProperty( GetXDisplay(), mhWindow,
+ GetDisplay()->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_ICON ),
+ XA_CARDINAL, 32, PropModeReplace, reinterpret_cast<unsigned char*>(netwm_icon.data()), netwm_icon.size());
+ }
+
+ m_nWorkArea = GetDisplay()->getWMAdaptor()->getCurrentWorkArea();
+
+ // Pointer
+ SetPointer( PointerStyle::Arrow );
+}
+
+X11SalFrame::X11SalFrame( SalFrame *pParent, SalFrameStyleFlags nSalFrameStyle,
+ SystemParentData const * pSystemParent ) :
+ m_nXScreen( 0 ),
+ maAlwaysOnTopRaiseTimer( "vcl::X11SalFrame maAlwaysOnTopRaiseTimer" )
+{
+ GenericUnixSalData *pData = GetGenericUnixSalData();
+
+ mpParent = static_cast< X11SalFrame* >( pParent );
+
+ mbTransientForRoot = false;
+
+ pDisplay_ = vcl_sal::getSalDisplay(pData);
+ // insert frame in framelist
+ pDisplay_->registerFrame( this );
+
+ mhWindow = None;
+ mpSurface = nullptr;
+ mhShellWindow = None;
+ mhStackingWindow = None;
+ mhForeignParent = None;
+ m_bSetFocusOnMap = false;
+
+ pGraphics_ = nullptr;
+ pFreeGraphics_ = nullptr;
+
+ hCursor_ = None;
+ nCaptured_ = 0;
+
+ mbSendExtKeyModChange = false;
+ mnExtKeyMod = ModKeyFlags::NONE;
+
+ nShowState_ = X11ShowState::Unknown;
+ nWidth_ = 0;
+ nHeight_ = 0;
+ nStyle_ = SalFrameStyleFlags::NONE;
+ mnExtStyle = 0;
+ bAlwaysOnTop_ = false;
+
+ // set bViewable_ to true: hack GetClientSize to report something
+ // different to 0/0 before first map
+ bViewable_ = true;
+ bMapped_ = false;
+ bDefaultPosition_ = true;
+ nVisibility_ = VisibilityFullyObscured;
+ m_nWorkArea = 0;
+ m_bXEmbed = false;
+
+
+ mpInputContext = nullptr;
+ mbInputFocus = False;
+
+ maAlwaysOnTopRaiseTimer.SetInvokeHandler( LINK( this, X11SalFrame, HandleAlwaysOnTopRaise ) );
+ maAlwaysOnTopRaiseTimer.SetTimeout( 100 );
+
+ meWindowType = WMWindowType::Normal;
+ mbMaximizedVert = false;
+ mbMaximizedHorz = false;
+ mbFullScreen = false;
+ m_bIsPartialFullScreen = false;
+
+ mnIconID = SV_ICON_ID_OFFICE;
+
+ if( mpParent )
+ mpParent->maChildren.push_back( this );
+
+ Init( nSalFrameStyle, GetDisplay()->GetDefaultXScreen(), pSystemParent );
+}
+
+X11SalFrame::~X11SalFrame()
+{
+ notifyDelete();
+
+ m_vClipRectangles.clear();
+
+ if( mhStackingWindow )
+ aPresentationReparentList.remove( mhStackingWindow );
+
+ // remove from parent's list
+ if( mpParent )
+ mpParent->maChildren.remove( this );
+
+ // deregister on SalDisplay
+ pDisplay_->deregisterFrame( this );
+
+ // unselect all events, some may be still in the queue anyway
+ if( ! IsSysChildWindow() )
+ XSelectInput( GetXDisplay(), GetShellWindow(), 0 );
+ XSelectInput( GetXDisplay(), GetWindow(), 0 );
+
+ ShowFullScreen( false, 0 );
+
+ if( bMapped_ )
+ Show( false );
+
+ if( mpInputContext )
+ {
+ mpInputContext->UnsetICFocus();
+ mpInputContext->Unmap();
+ mpInputContext.reset();
+ }
+
+ if( GetWindow() == hPresentationWindow )
+ {
+ hPresentationWindow = None;
+ doReparentPresentationDialogues( GetDisplay() );
+ }
+
+ if( pGraphics_ )
+ {
+ pGraphics_->DeInit();
+ pGraphics_.reset();
+ }
+
+ if( pFreeGraphics_ )
+ {
+ pFreeGraphics_->DeInit();
+ pFreeGraphics_.reset();
+ }
+
+ // reset all OpenGL contexts using this window
+ rtl::Reference<OpenGLContext> pContext = ImplGetSVData()->maGDIData.mpLastContext;
+ while( pContext.is() )
+ {
+ if (static_cast<const GLX11Window&>(pContext->getOpenGLWindow()).win == mhWindow)
+ pContext->reset();
+ pContext = pContext->mpPrevContext;
+ }
+
+ if (mpSurface)
+ cairo_surface_destroy(mpSurface);
+
+ XDestroyWindow( GetXDisplay(), mhWindow );
+}
+
+void X11SalFrame::SetExtendedFrameStyle( SalExtStyle nStyle )
+{
+ if( nStyle != mnExtStyle && ! IsChildWindow() )
+ {
+ mnExtStyle = nStyle;
+ updateWMClass();
+ }
+}
+
+const SystemEnvData* X11SalFrame::GetSystemData() const
+{
+ X11SalFrame *pFrame = const_cast<X11SalFrame*>(this);
+ pFrame->maSystemChildData.pDisplay = GetXDisplay();
+ pFrame->maSystemChildData.SetWindowHandle(pFrame->GetWindow());
+ pFrame->maSystemChildData.pSalFrame = pFrame;
+ pFrame->maSystemChildData.pWidget = nullptr;
+ pFrame->maSystemChildData.pVisual = GetDisplay()->GetVisual( m_nXScreen ).GetVisual();
+ pFrame->maSystemChildData.nScreen = m_nXScreen.getXScreen();
+ pFrame->maSystemChildData.aShellWindow = pFrame->GetShellWindow();
+ pFrame->maSystemChildData.toolkit = SystemEnvData::Toolkit::Gen;
+ pFrame->maSystemChildData.platform = SystemEnvData::Platform::Xcb;
+ return &maSystemChildData;
+}
+
+SalGraphics *X11SalFrame::AcquireGraphics()
+{
+ if( pGraphics_ )
+ return nullptr;
+
+ if( pFreeGraphics_ )
+ {
+ pGraphics_ = std::move(pFreeGraphics_);
+ }
+ else
+ {
+ pGraphics_.reset(new X11SalGraphics());
+ pGraphics_->Init(*this, GetWindow(), m_nXScreen);
+ }
+
+ return pGraphics_.get();
+}
+
+void X11SalFrame::ReleaseGraphics( SalGraphics *pGraphics )
+{
+ SAL_WARN_IF( pGraphics != pGraphics_.get(), "vcl", "SalFrame::ReleaseGraphics pGraphics!=pGraphics_" );
+
+ if( pGraphics != pGraphics_.get() )
+ return;
+
+ pFreeGraphics_ = std::move(pGraphics_);
+}
+
+void X11SalFrame::updateGraphics( bool bClear )
+{
+ Drawable aDrawable = bClear ? None : GetWindow();
+ if( pGraphics_ )
+ pGraphics_->SetDrawable( aDrawable, mpSurface, m_nXScreen );
+ if( pFreeGraphics_ )
+ pFreeGraphics_->SetDrawable( aDrawable, mpSurface, m_nXScreen );
+}
+
+void X11SalFrame::SetIcon( sal_uInt16 nIcon )
+{
+ if ( IsChildWindow() )
+ return;
+
+ // 0 == default icon -> #1
+ if ( nIcon == 0 )
+ nIcon = 1;
+
+ mnIconID = nIcon;
+
+ NetWmIconData netwm_icon;
+ CreateNetWmAppIcon( nIcon, netwm_icon );
+
+ if( !netwm_icon.empty() && GetDisplay()->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_ICON ))
+ XChangeProperty( GetXDisplay(), mhWindow,
+ GetDisplay()->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_ICON ),
+ XA_CARDINAL, 32, PropModeReplace, reinterpret_cast<unsigned char*>(netwm_icon.data()), netwm_icon.size());
+}
+
+void X11SalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight )
+{
+ if( IsChildWindow() )
+ return;
+
+ if( !GetShellWindow() ||
+ (nStyle_ & (SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::OWNERDRAWDECORATION) ) == SalFrameStyleFlags::FLOAT )
+ return;
+
+ XSizeHints* pHints = XAllocSizeHints();
+ tools::Long nSupplied = 0;
+ XGetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints,
+ &nSupplied
+ );
+ pHints->max_width = nWidth;
+ pHints->max_height = nHeight;
+ pHints->flags |= PMaxSize;
+ XSetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints );
+ XFree( pHints );
+}
+
+void X11SalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight )
+{
+ if( IsChildWindow() )
+ return;
+
+ if( !GetShellWindow() ||
+ (nStyle_ & (SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::OWNERDRAWDECORATION) ) == SalFrameStyleFlags::FLOAT )
+ return;
+
+ XSizeHints* pHints = XAllocSizeHints();
+ tools::Long nSupplied = 0;
+ XGetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints,
+ &nSupplied
+ );
+ pHints->min_width = nWidth;
+ pHints->min_height = nHeight;
+ pHints->flags |= PMinSize;
+ XSetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints );
+ XFree( pHints );
+}
+
+// Show + Pos (x,y,z) + Size (width,height)
+
+void X11SalFrame::Show( bool bVisible, bool bNoActivate )
+{
+ if( ( bVisible && bMapped_ )
+ || ( !bVisible && !bMapped_ ) )
+ return;
+
+ // HACK: this is a workaround for (at least) kwin
+ // even though transient frames should be kept above their parent
+ // this does not necessarily hold true for DOCK type windows
+ // so artificially set ABOVE and remove it again on hide
+ if( mpParent && mpParent->m_bIsPartialFullScreen && pDisplay_->getWMAdaptor()->isLegacyPartialFullscreen())
+ pDisplay_->getWMAdaptor()->enableAlwaysOnTop( this, bVisible );
+
+ bMapped_ = bVisible;
+ bViewable_ = bVisible;
+ setXEmbedInfo();
+ if( bVisible )
+ {
+ if( ! (nStyle_ & SalFrameStyleFlags::INTRO) )
+ {
+ // hide all INTRO frames
+ for (auto pSalFrame : GetDisplay()->getFrames() )
+ {
+ const X11SalFrame* pFrame = static_cast< const X11SalFrame* >( pSalFrame );
+ // look for intro bit map; if present, hide it
+ if( pFrame->nStyle_ & SalFrameStyleFlags::INTRO )
+ {
+ if( pFrame->bMapped_ )
+ const_cast<X11SalFrame*>(pFrame)->Show( false );
+ }
+ }
+ }
+
+ // update NET_WM_STATE which may have been deleted due to earlier Show(false)
+ if( nShowState_ == X11ShowState::Hidden )
+ GetDisplay()->getWMAdaptor()->frameIsMapping( this );
+
+ /*
+ * Actually this is rather exotic and currently happens only in conjunction
+ * with the basic dialogue editor,
+ * which shows a frame and instantly hides it again. After that the
+ * editor window is shown and the WM takes this as an opportunity
+ * to show our hidden transient frame also. So Show( false ) must
+ * withdraw the frame AND delete the WM_TRANSIENT_FOR property.
+ * In case the frame is shown again, the transient hint must be restored here.
+ */
+ if( ! IsChildWindow()
+ && ! IsOverrideRedirect()
+ && ! IsFloatGrabWindow()
+ && mpParent
+ )
+ {
+ GetDisplay()->getWMAdaptor()->changeReferenceFrame( this, mpParent );
+ }
+
+ // #i45160# switch to desktop where a dialog with parent will appear
+ if( mpParent && mpParent->m_nWorkArea != m_nWorkArea )
+ GetDisplay()->getWMAdaptor()->switchToWorkArea( mpParent->m_nWorkArea );
+
+ if( IsFloatGrabWindow() &&
+ mpParent &&
+ nVisibleFloats == 0 &&
+ ! GetDisplay()->GetCaptureFrame() )
+ {
+ /* #i39420#
+ * outsmart KWin's "focus strictly under mouse" mode
+ * which insists on taking the focus from the document
+ * to the new float. Grab focus to parent frame BEFORE
+ * showing the float (cannot grab it to the float
+ * before show).
+ */
+ XGrabPointer( GetXDisplay(),
+ mpParent->GetWindow(),
+ True,
+ PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
+ GrabModeAsync,
+ GrabModeAsync,
+ None,
+ mpParent ? mpParent->GetCursor() : None,
+ CurrentTime
+ );
+ }
+
+ Time nUserTime = 0;
+ if( ! bNoActivate && !(nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION) )
+ nUserTime = pDisplay_->GetX11ServerTime();
+ GetDisplay()->getWMAdaptor()->setUserTime( this, nUserTime );
+ if( ! bNoActivate && (nStyle_ & SalFrameStyleFlags::TOOLWINDOW) )
+ m_bSetFocusOnMap = true;
+
+ // actually map the window
+ if( m_bXEmbed )
+ askForXEmbedFocus( 0 );
+ else
+ {
+ if( GetWindow() != GetShellWindow() && ! IsSysChildWindow() )
+ {
+ if( IsChildWindow() )
+ XMapWindow( GetXDisplay(), GetShellWindow() );
+ XSelectInput( GetXDisplay(), GetShellWindow(), CLIENT_EVENTS );
+ }
+ if( nStyle_ & SalFrameStyleFlags::FLOAT )
+ XMapRaised( GetXDisplay(), GetWindow() );
+ else
+ XMapWindow( GetXDisplay(), GetWindow() );
+ }
+ XSelectInput( GetXDisplay(), GetWindow(), CLIENT_EVENTS );
+
+ if( maGeometry.width() > 0
+ && maGeometry.height() > 0
+ && ( nWidth_ != static_cast<int>(maGeometry.width())
+ || nHeight_ != static_cast<int>(maGeometry.height()) ) )
+ {
+ nWidth_ = maGeometry.width();
+ nHeight_ = maGeometry.height();
+ }
+
+ XSync( GetXDisplay(), False );
+
+ if( IsFloatGrabWindow() )
+ {
+ /*
+ * Sawfish and twm can be switched to enter-exit focus behaviour. In this case
+ * we must grab the pointer else the dumb WM will put the focus to the
+ * override-redirect float window. The application window will be deactivated
+ * which causes that the floats are destroyed, so the user can never click on
+ * a menu because it vanishes as soon as he enters it.
+ */
+ nVisibleFloats++;
+ if( nVisibleFloats == 1 && ! GetDisplay()->GetCaptureFrame() )
+ {
+ /* #i39420# now move grab to the new float window */
+ XGrabPointer( GetXDisplay(),
+ GetWindow(),
+ True,
+ PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
+ GrabModeAsync,
+ GrabModeAsync,
+ None,
+ mpParent ? mpParent->GetCursor() : None,
+ CurrentTime
+ );
+ }
+ }
+ CallCallback( SalEvent::Resize, nullptr );
+
+ /*
+ * sometimes a message box/dialogue is brought up when a frame is not mapped
+ * the corresponding TRANSIENT_FOR hint is then set to the root window
+ * so that the dialogue shows in all cases. Correct it here if the
+ * frame is shown afterwards.
+ */
+ if( ! IsChildWindow()
+ && ! IsOverrideRedirect()
+ && ! IsFloatGrabWindow()
+ )
+ {
+ for (auto const& child : maChildren)
+ {
+ if( child->mbTransientForRoot )
+ GetDisplay()->getWMAdaptor()->changeReferenceFrame( child, this );
+ }
+ }
+ /*
+ * leave X11ShowState::Unknown as this indicates first mapping
+ * and is only reset int HandleSizeEvent
+ */
+ if( nShowState_ != X11ShowState::Unknown )
+ nShowState_ = X11ShowState::Normal;
+
+ /*
+ * plugged windows don't necessarily get the
+ * focus on show because the parent may already be mapped
+ * and have the focus. So try to set the focus
+ * to the child on Show(true)
+ */
+ if( (nStyle_ & SalFrameStyleFlags::PLUG) && ! m_bXEmbed )
+ XSetInputFocus( GetXDisplay(),
+ GetWindow(),
+ RevertToParent,
+ CurrentTime );
+
+ if( mpParent )
+ {
+ // push this frame so it will be in front of its siblings
+ // only necessary for insane transient behaviour of Dtwm/olwm
+ mpParent->maChildren.remove( this );
+ mpParent->maChildren.push_front(this);
+ }
+ }
+ else
+ {
+ if( getInputContext() )
+ getInputContext()->Unmap();
+
+ if( ! IsChildWindow() )
+ {
+ /* FIXME: Is deleting the property really necessary ? It hurts
+ * owner drawn windows at least.
+ */
+ if( mpParent && ! (nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION) )
+ XDeleteProperty( GetXDisplay(), GetShellWindow(), GetDisplay()->getWMAdaptor()->getAtom( WMAdaptor::WM_TRANSIENT_FOR ) );
+ XWithdrawWindow( GetXDisplay(), GetShellWindow(), m_nXScreen.getXScreen() );
+ }
+ else if( ! m_bXEmbed )
+ XUnmapWindow( GetXDisplay(), GetWindow() );
+
+ nShowState_ = X11ShowState::Hidden;
+ if( IsFloatGrabWindow() && nVisibleFloats )
+ {
+ nVisibleFloats--;
+ if( nVisibleFloats == 0 && ! GetDisplay()->GetCaptureFrame() )
+ XUngrabPointer( GetXDisplay(),
+ CurrentTime );
+ }
+ // flush here; there may be a very seldom race between
+ // the display connection used for clipboard and our connection
+ Flush();
+ }
+}
+
+void X11SalFrame::ToTop( SalFrameToTop nFlags )
+{
+ if( ( nFlags & SalFrameToTop::RestoreWhenMin )
+ && ! ( nStyle_ & SalFrameStyleFlags::FLOAT )
+ && nShowState_ != X11ShowState::Hidden
+ && nShowState_ != X11ShowState::Unknown
+ )
+ {
+ GetDisplay()->getWMAdaptor()->frameIsMapping( this );
+ if( GetWindow() != GetShellWindow() && ! IsSysChildWindow() )
+ XMapWindow( GetXDisplay(), GetShellWindow() );
+ XMapWindow( GetXDisplay(), GetWindow() );
+ }
+
+ ::Window aToTopWindow = IsSysChildWindow() ? GetWindow() : GetShellWindow();
+ if( ! (nFlags & SalFrameToTop::GrabFocusOnly) )
+ {
+ XRaiseWindow( GetXDisplay(), aToTopWindow );
+ }
+
+ if( ( ( nFlags & SalFrameToTop::GrabFocus ) || ( nFlags & SalFrameToTop::GrabFocusOnly ) )
+ && bMapped_ )
+ {
+ if( m_bXEmbed )
+ askForXEmbedFocus( 0 );
+ else
+ XSetInputFocus( GetXDisplay(), aToTopWindow, RevertToParent, CurrentTime );
+ }
+ else if( ( nFlags & SalFrameToTop::RestoreWhenMin ) || ( nFlags & SalFrameToTop::ForegroundTask ) )
+ {
+ Time nTimestamp = pDisplay_->GetX11ServerTime();
+ GetDisplay()->getWMAdaptor()->activateWindow( this, nTimestamp );
+ }
+}
+
+void X11SalFrame::GetWorkArea( AbsoluteScreenPixelRectangle& rWorkArea )
+{
+ rWorkArea = pDisplay_->getWMAdaptor()->getWorkArea( 0 );
+}
+
+void X11SalFrame::GetClientSize( tools::Long &rWidth, tools::Long &rHeight )
+{
+ if( ! bViewable_ )
+ {
+ rWidth = rHeight = 0;
+ return;
+ }
+
+ rWidth = maGeometry.width();
+ rHeight = maGeometry.height();
+
+ if( !rWidth || !rHeight )
+ {
+ XWindowAttributes aAttrib;
+
+ XGetWindowAttributes( GetXDisplay(), GetWindow(), &aAttrib );
+
+ rWidth = aAttrib.width;
+ rHeight = aAttrib.height;
+ maGeometry.setSize({ aAttrib.width, aAttrib.height });
+ }
+}
+
+void X11SalFrame::Center( )
+{
+ int nX, nY;
+ AbsoluteScreenPixelSize aRealScreenSize(GetDisplay()->getDataForScreen(m_nXScreen).m_aSize);
+ AbsoluteScreenPixelRectangle aScreen({ 0, 0 }, aRealScreenSize);
+
+ if( GetDisplay()->IsXinerama() )
+ {
+ // get xinerama screen we are on
+ // if there is a parent, use its center for screen determination
+ // else use the pointer
+ ::Window aRoot, aChild;
+ int root_x, root_y, x, y;
+ unsigned int mask;
+ if( mpParent )
+ {
+ root_x = mpParent->maGeometry.x() + mpParent->maGeometry.width() / 2;
+ root_y = mpParent->maGeometry.y() + mpParent->maGeometry.height() / 2;
+ }
+ else
+ XQueryPointer( GetXDisplay(),
+ GetShellWindow(),
+ &aRoot, &aChild,
+ &root_x, &root_y,
+ &x, &y,
+ &mask );
+ const std::vector< AbsoluteScreenPixelRectangle >& rScreens = GetDisplay()->GetXineramaScreens();
+ for(const auto & rScreen : rScreens)
+ if( rScreen.Contains( AbsoluteScreenPixelPoint( root_x, root_y ) ) )
+ {
+ aScreen.SetPos(rScreen.GetPos());
+ aRealScreenSize = rScreen.GetSize();
+ break;
+ }
+ }
+
+ if( mpParent )
+ {
+ X11SalFrame* pFrame = mpParent;
+ while( pFrame->mpParent )
+ pFrame = pFrame->mpParent;
+ if( pFrame->maGeometry.width() < 1 || pFrame->maGeometry.height() < 1 )
+ {
+ AbsoluteScreenPixelRectangle aRect;
+ pFrame->GetPosSize( aRect );
+ pFrame->maGeometry.setPosSize(tools::Rectangle(aRect));
+ }
+
+ if( pFrame->nStyle_ & SalFrameStyleFlags::PLUG )
+ {
+ ::Window aRoot;
+ unsigned int nScreenWidth, nScreenHeight, bw, depth;
+ int nScreenX, nScreenY;
+ XGetGeometry( GetXDisplay(),
+ pFrame->GetShellWindow(),
+ &aRoot,
+ &nScreenX, &nScreenY,
+ &nScreenWidth, &nScreenHeight,
+ &bw, &depth );
+ aScreen = {{ nScreenX, nScreenY }, Size(nScreenWidth, nScreenHeight)};
+ }
+ else
+ aScreen = AbsoluteScreenPixelRectangle(pFrame->maGeometry.posSize());
+ }
+
+ if( mpParent && mpParent->nShowState_ == X11ShowState::Normal )
+ {
+ if( maGeometry.width() >= mpParent->maGeometry.width() &&
+ maGeometry.height() >= mpParent->maGeometry.height() )
+ {
+ nX = aScreen.getX() + 40;
+ nY = aScreen.getY() + 40;
+ }
+ else
+ {
+ // center the window relative to the top level frame
+ nX = (aScreen.GetWidth() - static_cast<int>(maGeometry.width()) ) / 2 + aScreen.getX();
+ nY = (aScreen.GetHeight() - static_cast<int>(maGeometry.height())) / 2 + aScreen.getY();
+ }
+ }
+ else
+ {
+ // center the window relative to screen
+ nX = (aRealScreenSize.getWidth() - static_cast<int>(maGeometry.width()) ) / 2 + aScreen.getX();
+ nY = (aRealScreenSize.getHeight() - static_cast<int>(maGeometry.height())) / 2 + aScreen.getY();
+ }
+ nX = nX < 0 ? 0 : nX;
+ nY = nY < 0 ? 0 : nY;
+
+ bDefaultPosition_ = False;
+ if( mpParent )
+ {
+ nX -= mpParent->maGeometry.x();
+ nY -= mpParent->maGeometry.y();
+ }
+
+ SetPosSize({ { nX, nY }, maGeometry.size() });
+}
+
+void X11SalFrame::updateScreenNumber()
+{
+ if( GetDisplay()->IsXinerama() && GetDisplay()->GetXineramaScreens().size() > 1 )
+ {
+ AbsoluteScreenPixelPoint aPoint( maGeometry.x(), maGeometry.y() );
+ const std::vector<AbsoluteScreenPixelRectangle>& rScreenRects( GetDisplay()->GetXineramaScreens() );
+ size_t nScreens = rScreenRects.size();
+ for( size_t i = 0; i < nScreens; i++ )
+ {
+ if( rScreenRects[i].Contains( aPoint ) )
+ {
+ maGeometry.setScreen(static_cast<unsigned int>(i));
+ break;
+ }
+ }
+ }
+ else
+ maGeometry.setScreen(m_nXScreen.getXScreen());
+}
+
+void X11SalFrame::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags )
+{
+ if( nStyle_ & SalFrameStyleFlags::PLUG )
+ return;
+
+ // relative positioning in X11SalFrame::SetPosSize
+ AbsoluteScreenPixelRectangle aPosSize( AbsoluteScreenPixelPoint( maGeometry.x(), maGeometry.y() ), AbsoluteScreenPixelSize( maGeometry.width(), maGeometry.height() ) );
+ aPosSize.Normalize();
+
+ if( ! ( nFlags & SAL_FRAME_POSSIZE_X ) )
+ {
+ nX = aPosSize.Left();
+ if( mpParent )
+ nX -= mpParent->maGeometry.x();
+ }
+ if( ! ( nFlags & SAL_FRAME_POSSIZE_Y ) )
+ {
+ nY = aPosSize.Top();
+ if( mpParent )
+ nY -= mpParent->maGeometry.y();
+ }
+ if( ! ( nFlags & SAL_FRAME_POSSIZE_WIDTH ) )
+ nWidth = aPosSize.GetWidth();
+ if( ! ( nFlags & SAL_FRAME_POSSIZE_HEIGHT ) )
+ nHeight = aPosSize.GetHeight();
+
+ aPosSize = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( nX, nY ), AbsoluteScreenPixelSize( nWidth, nHeight ) );
+
+ if( ! ( nFlags & ( SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ) ) )
+ {
+ if( bDefaultPosition_ )
+ {
+ maGeometry.setSize(Size(aPosSize.GetSize()));
+ Center();
+ }
+ else
+ SetSize( Size( nWidth, nHeight ) );
+ }
+ else
+ SetPosSize( aPosSize );
+
+ bDefaultPosition_ = False;
+}
+
+void X11SalFrame::SetAlwaysOnTop( bool bOnTop )
+{
+ if( ! IsOverrideRedirect() )
+ {
+ bAlwaysOnTop_ = bOnTop;
+ pDisplay_->getWMAdaptor()->enableAlwaysOnTop( this, bOnTop );
+ }
+}
+
+constexpr auto FRAMESTATE_MASK_MAXIMIZED_GEOMETRY =
+ vcl::WindowDataMask::MaximizedX | vcl::WindowDataMask::MaximizedY |
+ vcl::WindowDataMask::MaximizedWidth | vcl::WindowDataMask::MaximizedHeight;
+
+void X11SalFrame::SetWindowState( const vcl::WindowData *pState )
+{
+ if (pState == nullptr)
+ return;
+
+ // Request for position or size change
+ if (pState->mask() & vcl::WindowDataMask::PosSize)
+ {
+ /* #i44325#
+ * if maximized, set restore size and guess maximized size from last time
+ * in state change below maximize window
+ */
+ if( ! IsChildWindow() &&
+ (pState->mask() & vcl::WindowDataMask::PosSizeState) == vcl::WindowDataMask::PosSizeState &&
+ (pState->state() & vcl::WindowState::Maximized) &&
+ (pState->mask() & FRAMESTATE_MASK_MAXIMIZED_GEOMETRY) == FRAMESTATE_MASK_MAXIMIZED_GEOMETRY
+ )
+ {
+ XSizeHints* pHints = XAllocSizeHints();
+ tools::Long nSupplied = 0;
+ XGetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints,
+ &nSupplied );
+ pHints->flags |= PPosition | PWinGravity;
+ pHints->x = pState->x();
+ pHints->y = pState->y();
+ pHints->win_gravity = pDisplay_->getWMAdaptor()->getPositionWinGravity();
+ XSetWMNormalHints(GetXDisplay(), GetShellWindow(), pHints);
+ XFree( pHints );
+
+ XMoveResizeWindow(GetXDisplay(), GetShellWindow(), pState->x(), pState->y(),
+ pState->width(), pState->height());
+ // guess maximized geometry from last time
+ maGeometry.setPos({ pState->GetMaximizedX(), pState->GetMaximizedY() });
+ maGeometry.setSize({ pState->GetMaximizedWidth(), pState->GetMaximizedHeight() });
+ cairo_xlib_surface_set_size(mpSurface, pState->GetMaximizedWidth(), pState->GetMaximizedHeight());
+ updateScreenNumber();
+ }
+ else
+ {
+ bool bDoAdjust = false;
+ AbsoluteScreenPixelRectangle aPosSize;
+ // initialize with current geometry
+ if ((pState->mask() & vcl::WindowDataMask::PosSize) != vcl::WindowDataMask::PosSize)
+ GetPosSize(aPosSize);
+
+ sal_uInt16 nPosFlags = 0;
+
+ // change requested properties
+ if (pState->mask() & vcl::WindowDataMask::X)
+ {
+ aPosSize.SetPosX(pState->x() - (mpParent ? mpParent->maGeometry.x() : 0));
+ nPosFlags |= SAL_FRAME_POSSIZE_X;
+ }
+ if (pState->mask() & vcl::WindowDataMask::Y)
+ {
+ aPosSize.SetPosY(pState->y() - (mpParent ? mpParent->maGeometry.y() : 0));
+ nPosFlags |= SAL_FRAME_POSSIZE_Y;
+ }
+ if (pState->mask() & vcl::WindowDataMask::Width)
+ {
+ tools::Long nWidth = pState->width() > 0 ? pState->width() - 1 : 0;
+ aPosSize.setWidth (nWidth);
+ bDoAdjust = true;
+ }
+ if (pState->mask() & vcl::WindowDataMask::Height)
+ {
+ int nHeight = pState->height() > 0 ? pState->height() - 1 : 0;
+ aPosSize.setHeight (nHeight);
+ bDoAdjust = true;
+ }
+
+ const AbsoluteScreenPixelSize& aScreenSize = pDisplay_->getDataForScreen( m_nXScreen ).m_aSize;
+
+ if( bDoAdjust && aPosSize.GetWidth() <= aScreenSize.Width()
+ && aPosSize.GetHeight() <= aScreenSize.Height() )
+ {
+ SalFrameGeometry aGeom = maGeometry;
+
+ if( ! (nStyle_ & ( SalFrameStyleFlags::FLOAT | SalFrameStyleFlags::PLUG ) ) &&
+ mpParent && aGeom.leftDecoration() == 0 && aGeom.topDecoration() == 0)
+ {
+ aGeom = mpParent->maGeometry;
+ if (aGeom.leftDecoration() == 0 && aGeom.topDecoration() == 0)
+ aGeom.setDecorations(5, 20, 5, 5);
+ }
+
+ auto nRight = aPosSize.Right() + (mpParent ? mpParent->maGeometry.x() : 0);
+ auto nBottom = aPosSize.Bottom() + (mpParent ? mpParent->maGeometry.y() : 0);
+ auto nLeft = aPosSize.Left() + (mpParent ? mpParent->maGeometry.x() : 0);
+ auto nTop = aPosSize.Top() + (mpParent ? mpParent->maGeometry.y() : 0);
+
+ // adjust position so that frame fits onto screen
+ if( nRight+static_cast<tools::Long>(aGeom.rightDecoration()) > aScreenSize.Width()-1 )
+ aPosSize.Move( aScreenSize.Width() - nRight - static_cast<tools::Long>(aGeom.rightDecoration()), 0 );
+ if( nBottom+static_cast<tools::Long>(aGeom.bottomDecoration()) > aScreenSize.Height()-1 )
+ aPosSize.Move( 0, aScreenSize.Height() - nBottom - static_cast<tools::Long>(aGeom.bottomDecoration()) );
+ if( nLeft < static_cast<tools::Long>(aGeom.leftDecoration()) )
+ aPosSize.Move( static_cast<tools::Long>(aGeom.leftDecoration()) - nLeft, 0 );
+ if( nTop < static_cast<tools::Long>(aGeom.topDecoration()) )
+ aPosSize.Move( 0, static_cast<tools::Long>(aGeom.topDecoration()) - nTop );
+ }
+
+ SetPosSize(aPosSize.getX(), aPosSize.getY(),
+ aPosSize.GetWidth(), aPosSize.GetHeight(),
+ SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT |
+ nPosFlags);
+ }
+ }
+
+ // request for status change
+ if (!(pState->mask() & vcl::WindowDataMask::State))
+ return;
+
+ if (pState->state() & vcl::WindowState::Maximized)
+ {
+ nShowState_ = X11ShowState::Normal;
+ if( ! (pState->state() & (vcl::WindowState::MaximizedHorz|vcl::WindowState::MaximizedVert) ) )
+ Maximize();
+ else
+ {
+ bool bHorz(pState->state() & vcl::WindowState::MaximizedHorz);
+ bool bVert(pState->state() & vcl::WindowState::MaximizedVert);
+ GetDisplay()->getWMAdaptor()->maximizeFrame( this, bHorz, bVert );
+ }
+ maRestorePosSize = AbsoluteScreenPixelRectangle(pState->posSize());
+ }
+ else if( mbMaximizedHorz || mbMaximizedVert )
+ GetDisplay()->getWMAdaptor()->maximizeFrame( this, false, false );
+
+ if (pState->state() & vcl::WindowState::Minimized)
+ {
+ if (nShowState_ == X11ShowState::Unknown)
+ nShowState_ = X11ShowState::Normal;
+ Minimize();
+ }
+ if (pState->state() & vcl::WindowState::Normal)
+ {
+ if (nShowState_ != X11ShowState::Normal)
+ Restore();
+ }
+}
+
+bool X11SalFrame::GetWindowState( vcl::WindowData* pState )
+{
+ if( X11ShowState::Minimized == nShowState_ )
+ pState->setState(vcl::WindowState::Minimized);
+ else
+ pState->setState(vcl::WindowState::Normal);
+
+ AbsoluteScreenPixelRectangle aPosSize;
+ if( maRestorePosSize.IsEmpty() )
+ GetPosSize( aPosSize );
+ else
+ aPosSize = maRestorePosSize;
+
+ if( mbMaximizedHorz )
+ pState->rState() |= vcl::WindowState::MaximizedHorz;
+ if( mbMaximizedVert )
+ pState->rState() |= vcl::WindowState::MaximizedVert;
+
+ pState->setPosSize(tools::Rectangle(aPosSize));
+ pState->setMask(vcl::WindowDataMask::PosSizeState);
+
+ if (! maRestorePosSize.IsEmpty() )
+ {
+ GetPosSize( aPosSize );
+ pState->rState() |= vcl::WindowState::Maximized;
+ pState->SetMaximizedX(aPosSize.Left());
+ pState->SetMaximizedY(aPosSize.Top());
+ pState->SetMaximizedWidth(aPosSize.GetWidth());
+ pState->SetMaximizedHeight(aPosSize.GetHeight());
+ pState->rMask() |= FRAMESTATE_MASK_MAXIMIZED_GEOMETRY;
+ }
+
+ return true;
+}
+
+void X11SalFrame::SetMenu( SalMenu* )
+{
+}
+
+void X11SalFrame::GetPosSize( AbsoluteScreenPixelRectangle &rPosSize )
+{
+ if( maGeometry.width() < 1 || maGeometry.height() < 1 )
+ {
+ const AbsoluteScreenPixelSize& aScreenSize = pDisplay_->getDataForScreen( m_nXScreen ).m_aSize;
+ tools::Long w = aScreenSize.Width() - maGeometry.leftDecoration() - maGeometry.rightDecoration();
+ tools::Long h = aScreenSize.Height() - maGeometry.topDecoration() - maGeometry.bottomDecoration();
+
+ rPosSize = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( maGeometry.x(), maGeometry.y() ), AbsoluteScreenPixelSize( w, h ) );
+ }
+ else
+ rPosSize = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( maGeometry.x(), maGeometry.y() ),
+ AbsoluteScreenPixelSize( maGeometry.width(), maGeometry.height() ) );
+}
+
+void X11SalFrame::SetSize( const Size &rSize )
+{
+ if( rSize.IsEmpty() )
+ return;
+
+ if( ! ( nStyle_ & SalFrameStyleFlags::SIZEABLE )
+ && ! IsChildWindow()
+ && ( nStyle_ & (SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::OWNERDRAWDECORATION) ) != SalFrameStyleFlags::FLOAT )
+ {
+ XSizeHints* pHints = XAllocSizeHints();
+ tools::Long nSupplied = 0;
+ XGetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints,
+ &nSupplied
+ );
+ pHints->min_width = rSize.Width();
+ pHints->min_height = rSize.Height();
+ pHints->max_width = rSize.Width();
+ pHints->max_height = rSize.Height();
+ pHints->flags |= PMinSize | PMaxSize;
+ XSetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints );
+ XFree( pHints );
+ }
+ XResizeWindow( GetXDisplay(), IsSysChildWindow() ? GetWindow() : GetShellWindow(), rSize.Width(), rSize.Height() );
+ if( GetWindow() != GetShellWindow() )
+ {
+ if( nStyle_ & SalFrameStyleFlags::PLUG )
+ XMoveResizeWindow( GetXDisplay(), GetWindow(), 0, 0, rSize.Width(), rSize.Height() );
+ else
+ XResizeWindow( GetXDisplay(), GetWindow(), rSize.Width(), rSize.Height() );
+ }
+
+ cairo_xlib_surface_set_size(mpSurface, rSize.Width(), rSize.Height());
+ maGeometry.setSize(rSize);
+
+ // allow the external status window to reposition
+ if (mbInputFocus && mpInputContext != nullptr)
+ mpInputContext->SetICFocus ( this );
+}
+
+void X11SalFrame::SetPosSize( const AbsoluteScreenPixelRectangle &rPosSize )
+{
+ XWindowChanges values;
+ values.x = rPosSize.Left();
+ values.y = rPosSize.Top();
+ values.width = rPosSize.GetWidth();
+ values.height = rPosSize.GetHeight();
+
+ if( !values.width || !values.height )
+ return;
+
+ if( mpParent && ! IsSysChildWindow() )
+ {
+ if( AllSettings::GetLayoutRTL() )
+ values.x = mpParent->maGeometry.width()-values.width-1-values.x;
+
+ ::Window aChild;
+ // coordinates are relative to parent, so translate to root coordinates
+ XTranslateCoordinates( GetDisplay()->GetDisplay(),
+ mpParent->GetWindow(),
+ GetDisplay()->GetRootWindow( m_nXScreen ),
+ values.x, values.y,
+ &values.x, &values.y,
+ & aChild );
+ }
+
+ bool bMoved = false;
+ bool bSized = false;
+ if( values.x != maGeometry.x() || values.y != maGeometry.y() )
+ bMoved = true;
+ if( values.width != static_cast<int>(maGeometry.width()) || values.height != static_cast<int>(maGeometry.height()) )
+ bSized = true;
+
+ // do not set WMNormalHints for...
+ if(
+ // child windows
+ ! IsChildWindow()
+ // popups (menu, help window, etc.)
+ && (nStyle_ & (SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::OWNERDRAWDECORATION) ) != SalFrameStyleFlags::FLOAT
+ // shown, sizeable windows
+ && ( nShowState_ == X11ShowState::Unknown ||
+ nShowState_ == X11ShowState::Hidden ||
+ ! ( nStyle_ & SalFrameStyleFlags::SIZEABLE )
+ )
+ )
+ {
+ XSizeHints* pHints = XAllocSizeHints();
+ tools::Long nSupplied = 0;
+ XGetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints,
+ &nSupplied
+ );
+ if( ! ( nStyle_ & SalFrameStyleFlags::SIZEABLE ) )
+ {
+ pHints->min_width = rPosSize.GetWidth();
+ pHints->min_height = rPosSize.GetHeight();
+ pHints->max_width = rPosSize.GetWidth();
+ pHints->max_height = rPosSize.GetHeight();
+ pHints->flags |= PMinSize | PMaxSize;
+ }
+ if( nShowState_ == X11ShowState::Unknown || nShowState_ == X11ShowState::Hidden )
+ {
+ pHints->flags |= PPosition | PWinGravity;
+ pHints->x = values.x;
+ pHints->y = values.y;
+ pHints->win_gravity = pDisplay_->getWMAdaptor()->getPositionWinGravity();
+ }
+ if( mbFullScreen )
+ {
+ pHints->max_width = 10000;
+ pHints->max_height = 10000;
+ pHints->flags |= PMaxSize;
+ }
+ XSetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints );
+ XFree( pHints );
+ }
+
+ XMoveResizeWindow( GetXDisplay(), IsSysChildWindow() ? GetWindow() : GetShellWindow(), values.x, values.y, values.width, values.height );
+ if( GetShellWindow() != GetWindow() )
+ {
+ if( nStyle_ & SalFrameStyleFlags::PLUG )
+ XMoveResizeWindow( GetXDisplay(), GetWindow(), 0, 0, values.width, values.height );
+ else
+ XMoveResizeWindow( GetXDisplay(), GetWindow(), values.x, values.y, values.width, values.height );
+ }
+
+ cairo_xlib_surface_set_size(mpSurface, values.width, values.height);
+ maGeometry.setPosSize({ values.x, values.y }, { values.width, values.height });
+ if( IsSysChildWindow() && mpParent )
+ // translate back to root coordinates
+ maGeometry.move(mpParent->maGeometry.x(), mpParent->maGeometry.y());
+
+ updateScreenNumber();
+ if( bSized && ! bMoved )
+ CallCallback( SalEvent::Resize, nullptr );
+ else if( bMoved && ! bSized )
+ CallCallback( SalEvent::Move, nullptr );
+ else
+ CallCallback( SalEvent::MoveResize, nullptr );
+
+ // allow the external status window to reposition
+ if (mbInputFocus && mpInputContext != nullptr)
+ mpInputContext->SetICFocus ( this );
+}
+
+void X11SalFrame::Minimize()
+{
+ if( IsSysChildWindow() )
+ return;
+
+ if( X11ShowState::Unknown == nShowState_ || X11ShowState::Hidden == nShowState_ )
+ {
+ SAL_WARN( "vcl", "X11SalFrame::Minimize on withdrawn window" );
+ return;
+ }
+
+ if( XIconifyWindow( GetXDisplay(),
+ GetShellWindow(),
+ pDisplay_->GetDefaultXScreen().getXScreen() ) )
+ nShowState_ = X11ShowState::Minimized;
+}
+
+void X11SalFrame::Maximize()
+{
+ if( IsSysChildWindow() )
+ return;
+
+ if( X11ShowState::Minimized == nShowState_ )
+ {
+ GetDisplay()->getWMAdaptor()->frameIsMapping( this );
+ XMapWindow( GetXDisplay(), GetShellWindow() );
+ nShowState_ = X11ShowState::Normal;
+ }
+
+ pDisplay_->getWMAdaptor()->maximizeFrame( this );
+}
+
+void X11SalFrame::Restore()
+{
+ if( IsSysChildWindow() )
+ return;
+
+ if( X11ShowState::Unknown == nShowState_ || X11ShowState::Hidden == nShowState_ )
+ {
+ SAL_INFO( "vcl", "X11SalFrame::Restore on withdrawn window" );
+ return;
+ }
+
+ if( X11ShowState::Minimized == nShowState_ )
+ {
+ GetDisplay()->getWMAdaptor()->frameIsMapping( this );
+ XMapWindow( GetXDisplay(), GetShellWindow() );
+ nShowState_ = X11ShowState::Normal;
+ }
+
+ pDisplay_->getWMAdaptor()->maximizeFrame( this, false, false );
+}
+
+void X11SalFrame::SetScreenNumber( unsigned int nNewScreen )
+{
+ if( nNewScreen == maGeometry.screen() )
+ return;
+
+ if( GetDisplay()->IsXinerama() && GetDisplay()->GetXineramaScreens().size() > 1 )
+ {
+ if( nNewScreen >= GetDisplay()->GetXineramaScreens().size() )
+ return;
+
+ tools::Rectangle aOldScreenRect( GetDisplay()->GetXineramaScreens()[maGeometry.screen()] );
+ tools::Rectangle aNewScreenRect( GetDisplay()->GetXineramaScreens()[nNewScreen] );
+ bool bVisible = bMapped_;
+ if( bVisible )
+ Show( false );
+ maGeometry.setX(aNewScreenRect.Left() + (maGeometry.x() - aOldScreenRect.Left()));
+ maGeometry.setY(aNewScreenRect.Top() + (maGeometry.y() - aOldScreenRect.Top()));
+ createNewWindow( None, m_nXScreen );
+ if( bVisible )
+ Show( true );
+ maGeometry.setScreen(nNewScreen);
+ }
+ else if( nNewScreen < GetDisplay()->GetXScreenCount() )
+ {
+ bool bVisible = bMapped_;
+ if( bVisible )
+ Show( false );
+ createNewWindow( None, SalX11Screen( nNewScreen ) );
+ if( bVisible )
+ Show( true );
+ maGeometry.setScreen(nNewScreen);
+ }
+}
+
+void X11SalFrame::SetApplicationID( const OUString &rWMClass )
+{
+ if( rWMClass != m_sWMClass && ! IsChildWindow() )
+ {
+ m_sWMClass = rWMClass;
+ updateWMClass();
+ for (auto const& child : maChildren)
+ child->SetApplicationID(rWMClass);
+ }
+}
+
+void X11SalFrame::updateWMClass()
+{
+ XClassHint* pClass = XAllocClassHint();
+ OString aResName = SalGenericSystem::getFrameResName();
+ pClass->res_name = const_cast<char*>(aResName.getStr());
+
+ OString aResClass = OUStringToOString(m_sWMClass, RTL_TEXTENCODING_ASCII_US);
+ const char *pResClass = !aResClass.isEmpty() ? aResClass.getStr() :
+ SalGenericSystem::getFrameClassName();
+
+ pClass->res_class = const_cast<char*>(pResClass);
+ XSetClassHint( GetXDisplay(), GetShellWindow(), pClass );
+ XFree( pClass );
+}
+
+void X11SalFrame::ShowFullScreen( bool bFullScreen, sal_Int32 nScreen )
+{
+ if( GetDisplay()->IsXinerama() && GetDisplay()->GetXineramaScreens().size() > 1 )
+ {
+ if( mbFullScreen == bFullScreen )
+ return;
+ if( bFullScreen )
+ {
+ maRestorePosSize = AbsoluteScreenPixelRectangle(maGeometry.posSize());
+ AbsoluteScreenPixelRectangle aRect;
+ if( nScreen < 0 || o3tl::make_unsigned(nScreen) >= GetDisplay()->GetXineramaScreens().size() )
+ aRect = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint(0,0), GetDisplay()->GetScreenSize( m_nXScreen ) );
+ else
+ aRect = GetDisplay()->GetXineramaScreens()[nScreen];
+ m_bIsPartialFullScreen = true;
+ bool bVisible = bMapped_;
+ if( bVisible )
+ Show( false );
+ maGeometry.setPosSize(tools::Rectangle(aRect));
+ mbMaximizedHorz = mbMaximizedVert = false;
+ mbFullScreen = true;
+ createNewWindow( None, m_nXScreen );
+ if( GetDisplay()->getWMAdaptor()->isLegacyPartialFullscreen() )
+ GetDisplay()->getWMAdaptor()->enableAlwaysOnTop( this, true );
+ else
+ GetDisplay()->getWMAdaptor()->showFullScreen( this, true );
+ if( bVisible )
+ Show(true);
+
+ }
+ else
+ {
+ mbFullScreen = false;
+ m_bIsPartialFullScreen = false;
+ bool bVisible = bMapped_;
+ AbsoluteScreenPixelRectangle aRect = maRestorePosSize;
+ maRestorePosSize = AbsoluteScreenPixelRectangle();
+ if( bVisible )
+ Show( false );
+ createNewWindow( None, m_nXScreen );
+ if( !aRect.IsEmpty() )
+ SetPosSize( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(),
+ SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y |
+ SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
+ if( bVisible )
+ Show( true );
+ }
+ }
+ else
+ {
+ if( nScreen < 0 || o3tl::make_unsigned(nScreen) >= GetDisplay()->GetXScreenCount() )
+ nScreen = m_nXScreen.getXScreen();
+ if( nScreen != static_cast<int>(m_nXScreen.getXScreen()) )
+ {
+ bool bVisible = bMapped_;
+ if( mbFullScreen )
+ pDisplay_->getWMAdaptor()->showFullScreen( this, false );
+ if( bVisible )
+ Show( false );
+ createNewWindow( None, SalX11Screen( nScreen ) );
+ if( mbFullScreen )
+ pDisplay_->getWMAdaptor()->showFullScreen( this, true );
+ if( bVisible )
+ Show( true );
+ }
+ if( mbFullScreen == bFullScreen )
+ return;
+
+ pDisplay_->getWMAdaptor()->showFullScreen( this, bFullScreen );
+ }
+}
+
+void X11SalFrame::StartPresentation( bool bStart )
+{
+ maSessionManagerInhibitor.inhibit( bStart,
+ u"presentation",
+ APPLICATION_INHIBIT_IDLE,
+ mhWindow,
+ GetXDisplay() );
+
+ if( ! bStart && hPresentationWindow != None )
+ doReparentPresentationDialogues( GetDisplay() );
+ hPresentationWindow = (bStart && IsOverrideRedirect() ) ? GetWindow() : None;
+
+ if( bStart && hPresentationWindow )
+ {
+ /* #i10559# workaround for WindowMaker: try to restore
+ * current focus after presentation window is gone
+ */
+ int revert_to = 0;
+ XGetInputFocus( GetXDisplay(), &hPresFocusWindow, &revert_to );
+ }
+}
+
+// Pointer
+
+void X11SalFrame::SetPointer( PointerStyle ePointerStyle )
+{
+ hCursor_ = pDisplay_->GetPointer( ePointerStyle );
+ XDefineCursor( GetXDisplay(), GetWindow(), hCursor_ );
+
+ if( IsCaptured() || nVisibleFloats > 0 )
+ XChangeActivePointerGrab( GetXDisplay(),
+ PointerMotionMask|ButtonPressMask|ButtonReleaseMask,
+ hCursor_,
+ CurrentTime );
+}
+
+void X11SalFrame::SetPointerPos(tools::Long nX, tools::Long nY)
+{
+ /* when the application tries to center the mouse in the dialog the
+ * window isn't mapped already. So use coordinates relative to the root window.
+ */
+ unsigned int nWindowLeft = maGeometry.x() + nX;
+ unsigned int nWindowTop = maGeometry.y() + nY;
+
+ XWarpPointer( GetXDisplay(), None, pDisplay_->GetRootWindow( pDisplay_->GetDefaultXScreen() ),
+ 0, 0, 0, 0, nWindowLeft, nWindowTop);
+}
+
+// delay handling of extended text input
+#if !defined(__synchronous_extinput__)
+void
+X11SalFrame::HandleExtTextEvent (XClientMessageEvent const *pEvent)
+{
+ #if SAL_TYPES_SIZEOFLONG > 4
+ void* pExtTextEvent = reinterpret_cast<void*>( (pEvent->data.l[0] & 0xffffffff)
+ | (pEvent->data.l[1] << 32) );
+ #else
+ void* pExtTextEvent = reinterpret_cast<void*>(pEvent->data.l[0]);
+ #endif
+ SalEvent nExtTextEventType = SalEvent(pEvent->data.l[2]);
+
+ CallCallback(nExtTextEventType, pExtTextEvent);
+
+ switch (nExtTextEventType)
+ {
+ case SalEvent::EndExtTextInput:
+ break;
+
+ case SalEvent::ExtTextInput:
+ break;
+
+ default:
+ SAL_WARN("vcl.window",
+ "X11SalFrame::HandleExtTextEvent: invalid extended input.");
+ }
+}
+#endif /* defined(__synchronous_extinput__) */
+
+// PostEvent
+
+bool X11SalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
+{
+ GetDisplay()->SendInternalEvent( this, pData.release() );
+ return true;
+}
+
+// Title
+
+void X11SalFrame::SetTitle( const OUString& rTitle )
+{
+ if( ! ( IsChildWindow() || (nStyle_ & SalFrameStyleFlags::FLOAT ) ) )
+ {
+ m_aTitle = rTitle;
+ GetDisplay()->getWMAdaptor()->setWMName( this, rTitle );
+ }
+}
+
+void X11SalFrame::Flush()
+{
+ if( pGraphics_ )
+ pGraphics_->Flush();
+ XFlush( GetDisplay()->GetDisplay() );
+}
+
+// Keyboard
+
+void X11SalFrame::SetInputContext( SalInputContext* pContext )
+{
+ if (pContext == nullptr)
+ return;
+
+ // 1. We should create an input context for this frame
+ // only when InputContextFlags::Text is set.
+
+ if (!(pContext->mnOptions & InputContextFlags::Text))
+ {
+ if( mpInputContext )
+ mpInputContext->Unmap();
+ return;
+ }
+
+ // 2. We should use on-the-spot inputstyle
+ // only when InputContextFlags::ExtTExt is set.
+
+ if (mpInputContext == nullptr)
+ {
+ mpInputContext.reset( new SalI18N_InputContext( this ) );
+ if (mpInputContext->UseContext())
+ {
+ mpInputContext->ExtendEventMask( GetShellWindow() );
+ if (mbInputFocus)
+ mpInputContext->SetICFocus( this );
+ }
+ }
+ else
+ mpInputContext->Map( this );
+}
+
+void X11SalFrame::EndExtTextInput( EndExtTextInputFlags )
+{
+ if (mpInputContext != nullptr)
+ mpInputContext->EndExtTextInput();
+}
+
+OUString X11SalFrame::GetKeyName( sal_uInt16 nKeyCode )
+{
+ return GetDisplay()->GetKeyName( nKeyCode );
+}
+
+bool X11SalFrame::MapUnicodeToKeyCode( sal_Unicode , LanguageType , vcl::KeyCode& )
+{
+ // not supported yet
+ return false;
+}
+
+LanguageType X11SalFrame::GetInputLanguage()
+{
+ // could be improved by checking unicode ranges of the last input
+ return LANGUAGE_DONTKNOW;
+}
+
+// Settings
+
+void X11SalFrame::UpdateSettings( AllSettings& rSettings )
+{
+ StyleSettings aStyleSettings = rSettings.GetStyleSettings();
+ aStyleSettings.SetCursorBlinkTime( 500 );
+ aStyleSettings.SetMenuBarTextColor( aStyleSettings.GetPersonaMenuBarTextColor().value_or( COL_BLACK ) );
+ rSettings.SetStyleSettings( aStyleSettings );
+}
+
+void X11SalFrame::CaptureMouse( bool bCapture )
+{
+ nCaptured_ = pDisplay_->CaptureMouse( bCapture ? this : nullptr );
+}
+
+void X11SalFrame::SetParent( SalFrame* pNewParent )
+{
+ if( mpParent != pNewParent )
+ {
+ if( mpParent )
+ mpParent->maChildren.remove( this );
+
+ mpParent = static_cast<X11SalFrame*>(pNewParent);
+ mpParent->maChildren.push_back( this );
+ if( mpParent->m_nXScreen != m_nXScreen )
+ createNewWindow( None, mpParent->m_nXScreen );
+ GetDisplay()->getWMAdaptor()->changeReferenceFrame( this, mpParent );
+ }
+}
+
+SalFrame* X11SalFrame::GetParent() const
+{
+ return mpParent;
+}
+
+void X11SalFrame::createNewWindow( ::Window aNewParent, SalX11Screen nXScreen )
+{
+ bool bWasVisible = bMapped_;
+ if( bWasVisible )
+ Show( false );
+
+ if( nXScreen.getXScreen() >= GetDisplay()->GetXScreenCount() )
+ nXScreen = m_nXScreen;
+
+ SystemParentData aParentData;
+ aParentData.nSize = sizeof(SystemParentData);
+ aParentData.aWindow = aNewParent;
+ aParentData.bXEmbedSupport = (aNewParent != None && m_bXEmbed); // caution: this is guesswork
+ if( aNewParent == None )
+ {
+ aParentData.aWindow = None;
+ m_bXEmbed = false;
+ }
+ else
+ {
+ // is new parent a root window ?
+ Display* pDisp = GetDisplay()->GetDisplay();
+ int nScreens = GetDisplay()->GetXScreenCount();
+ for( int i = 0; i < nScreens; i++ )
+ {
+ if( aNewParent == RootWindow( pDisp, i ) )
+ {
+ nXScreen = SalX11Screen( i );
+ aParentData.aWindow = None;
+ m_bXEmbed = false;
+ break;
+ }
+ }
+ }
+
+ // first deinit frame
+ updateGraphics(true);
+ if( mpInputContext )
+ {
+ mpInputContext->UnsetICFocus();
+ mpInputContext->Unmap();
+ }
+ if( GetWindow() == hPresentationWindow )
+ {
+ hPresentationWindow = None;
+ doReparentPresentationDialogues( GetDisplay() );
+ }
+ if (mpSurface)
+ {
+ cairo_surface_destroy(mpSurface);
+ mpSurface = nullptr;
+ }
+ XDestroyWindow( GetXDisplay(), mhWindow );
+ mhWindow = None;
+
+ // now init with new parent again
+ if ( aParentData.aWindow != None )
+ Init( nStyle_ | SalFrameStyleFlags::PLUG, nXScreen, &aParentData );
+ else
+ Init( nStyle_ & ~SalFrameStyleFlags::PLUG, nXScreen, nullptr, true );
+
+ // update graphics if necessary
+ updateGraphics(false);
+
+ if( ! m_aTitle.isEmpty() )
+ SetTitle( m_aTitle );
+
+ if( mpParent )
+ {
+ if( mpParent->m_nXScreen != m_nXScreen )
+ SetParent( nullptr );
+ else
+ pDisplay_->getWMAdaptor()->changeReferenceFrame( this, mpParent );
+ }
+
+ if( bWasVisible )
+ Show( true );
+
+ std::list< X11SalFrame* > aChildren = maChildren;
+ for (auto const& child : aChildren)
+ child->createNewWindow( None, m_nXScreen );
+
+ // FIXME: SalObjects
+}
+
+void X11SalFrame::SetPluginParent( SystemParentData* pNewParent )
+{
+ if( pNewParent->nSize >= sizeof(SystemParentData) )
+ m_bXEmbed = pNewParent->aWindow != None && pNewParent->bXEmbedSupport;
+
+ createNewWindow(pNewParent->aWindow);
+}
+
+// Sound
+void X11SalFrame::Beep()
+{
+ GetDisplay()->Beep();
+}
+
+// Event Handling
+
+static sal_uInt16 sal_GetCode( int state )
+{
+ sal_uInt16 nCode = 0;
+
+ if( state & Button1Mask )
+ nCode |= MOUSE_LEFT;
+ if( state & Button2Mask )
+ nCode |= MOUSE_MIDDLE;
+ if( state & Button3Mask )
+ nCode |= MOUSE_RIGHT;
+
+ if( state & ShiftMask )
+ nCode |= KEY_SHIFT;
+ if( state & ControlMask )
+ nCode |= KEY_MOD1;
+ if( state & Mod1Mask )
+ nCode |= KEY_MOD2;
+
+ // Map Meta/Super modifier to MOD3 on all Unix systems
+ // except macOS
+ if( state & Mod3Mask )
+ nCode |= KEY_MOD3;
+
+ return nCode;
+}
+
+SalFrame::SalPointerState X11SalFrame::GetPointerState()
+{
+ SalPointerState aState;
+ ::Window aRoot, aChild;
+ int rx, ry, wx, wy;
+ unsigned int nMask = 0;
+ XQueryPointer( GetXDisplay(),
+ GetShellWindow(),
+ &aRoot,
+ &aChild,
+ &rx, &ry,
+ &wx, &wy,
+ &nMask
+ );
+
+ aState.maPos = Point(wx, wy);
+ aState.mnState = sal_GetCode( nMask );
+ return aState;
+}
+
+KeyIndicatorState X11SalFrame::GetIndicatorState()
+{
+ return vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetIndicatorState();
+}
+
+void X11SalFrame::SimulateKeyPress( sal_uInt16 nKeyCode )
+{
+ vcl_sal::getSalDisplay(GetGenericUnixSalData())->SimulateKeyPress(nKeyCode);
+}
+
+namespace
+{
+struct CompressWheelEventsData
+{
+ XEvent* firstEvent;
+ bool ignore;
+ int count; // number of compressed events
+};
+
+Bool compressWheelEvents( Display*, XEvent* event, XPointer p )
+{
+ CompressWheelEventsData* data = reinterpret_cast< CompressWheelEventsData* >( p );
+ if( data->ignore )
+ return False; // we're already after the events to compress
+ if( event->type == ButtonPress || event->type == ButtonRelease )
+ {
+ const unsigned int mask = Button1Mask << ( event->xbutton.button - Button1 );
+ if( event->xbutton.button == data->firstEvent->xbutton.button
+ && event->xbutton.window == data->firstEvent->xbutton.window
+ && event->xbutton.x == data->firstEvent->xbutton.x
+ && event->xbutton.y == data->firstEvent->xbutton.y
+ && ( event->xbutton.state | mask ) == ( data->firstEvent->xbutton.state | mask ))
+ {
+ // Count if it's another press (i.e. wheel start event).
+ if( event->type == ButtonPress )
+ ++data->count;
+ return True; // And remove the event from the queue.
+ }
+ }
+ // Non-matching event, skip certain events that cannot possibly affect input processing,
+ // but otherwise ignore all further events.
+ switch( event->type )
+ {
+ case Expose:
+ case NoExpose:
+ break;
+ default:
+ data->ignore = true;
+ break;
+ }
+ return False;
+}
+
+} // namespace
+
+bool X11SalFrame::HandleMouseEvent( XEvent *pEvent )
+{
+ SalMouseEvent aMouseEvt;
+ SalEvent nEvent = SalEvent::NONE;
+ bool bClosePopups = false;
+
+ if( nVisibleFloats && pEvent->type == EnterNotify )
+ return false;
+
+ if( LeaveNotify == pEvent->type || EnterNotify == pEvent->type )
+ {
+ /*
+ * some WMs (and/or) applications have a passive grab on
+ * mouse buttons (XGrabButton). This leads to enter/leave notifies
+ * with mouse buttons pressed in the state mask before the actual
+ * ButtonPress event gets dispatched. But EnterNotify
+ * is reported in vcl as MouseMove event. Some office code
+ * decides that a pressed button in a MouseMove belongs to
+ * a drag operation which leads to doing things differently.
+ *
+ * ignore Enter/LeaveNotify resulting from grabs so that
+ * help windows do not disappear just after appearing
+ *
+ * hopefully this workaround will not break anything.
+ */
+ if( pEvent->xcrossing.mode == NotifyGrab || pEvent->xcrossing.mode == NotifyUngrab )
+ return false;
+
+ aMouseEvt.mnX = pEvent->xcrossing.x;
+ aMouseEvt.mnY = pEvent->xcrossing.y;
+ aMouseEvt.mnTime = pEvent->xcrossing.time;
+ aMouseEvt.mnCode = sal_GetCode( pEvent->xcrossing.state );
+ aMouseEvt.mnButton = 0;
+
+ nEvent = LeaveNotify == pEvent->type
+ ? SalEvent::MouseLeave
+ : SalEvent::MouseMove;
+ }
+ else if( pEvent->type == MotionNotify )
+ {
+ aMouseEvt.mnX = pEvent->xmotion.x;
+ aMouseEvt.mnY = pEvent->xmotion.y;
+ aMouseEvt.mnTime = pEvent->xmotion.time;
+ aMouseEvt.mnCode = sal_GetCode( pEvent->xmotion.state );
+
+ aMouseEvt.mnButton = 0;
+
+ nEvent = SalEvent::MouseMove;
+ if( nVisibleFloats > 0 && mpParent )
+ {
+ Cursor aCursor = mpParent->GetCursor();
+ if( pEvent->xmotion.x >= 0 && pEvent->xmotion.x < static_cast<int>(maGeometry.width()) &&
+ pEvent->xmotion.y >= 0 && pEvent->xmotion.y < static_cast<int>(maGeometry.height()) )
+ aCursor = None;
+
+ XChangeActivePointerGrab( GetXDisplay(),
+ PointerMotionMask|ButtonPressMask|ButtonReleaseMask,
+ aCursor,
+ CurrentTime );
+ }
+ }
+ else
+ {
+ // let mouse events reach the correct window
+ if( nVisibleFloats < 1 )
+ {
+ if( ! (nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION) )
+ XUngrabPointer( GetXDisplay(), CurrentTime );
+ }
+ else if( pEvent->type == ButtonPress )
+ {
+ // see if the user clicks outside all of the floats
+ // if yes release the grab
+ bool bInside = false;
+ for (auto pSalFrame : GetDisplay()->getFrames() )
+ {
+ const X11SalFrame* pFrame = static_cast< const X11SalFrame* >( pSalFrame );
+ if( pFrame->IsFloatGrabWindow() &&
+ pFrame->bMapped_ &&
+ pEvent->xbutton.x_root >= pFrame->maGeometry.x() &&
+ pEvent->xbutton.x_root < pFrame->maGeometry.x() + static_cast<int>(pFrame->maGeometry.width()) &&
+ pEvent->xbutton.y_root >= pFrame->maGeometry.y() &&
+ pEvent->xbutton.y_root < pFrame->maGeometry.y() + static_cast<int>(pFrame->maGeometry.height()) )
+ {
+ bInside = true;
+ break;
+ }
+ }
+ if( ! bInside )
+ {
+ // need not take care of the XUngrabPointer in Show( false )
+ // because XUngrabPointer does not produce errors if pointer
+ // is not grabbed
+ XUngrabPointer( GetXDisplay(), CurrentTime );
+ bClosePopups = true;
+
+ /* #i15246# only close popups if pointer is outside all our frames
+ * cannot use our own geometry data here because stacking
+ * is unknown (the above case implicitly assumes
+ * that floats are on top which should be true)
+ */
+ ::Window aRoot, aChild;
+ int root_x, root_y, win_x, win_y;
+ unsigned int mask_return;
+ if( XQueryPointer( GetXDisplay(),
+ GetDisplay()->GetRootWindow( m_nXScreen ),
+ &aRoot, &aChild,
+ &root_x, &root_y,
+ &win_x, &win_y,
+ &mask_return )
+ && aChild // pointer may not be in any child
+ )
+ {
+ for (auto pSalFrame : GetDisplay()->getFrames() )
+ {
+ const X11SalFrame* pFrame = static_cast< const X11SalFrame* >( pSalFrame );
+ if( ! pFrame->IsFloatGrabWindow()
+ && ( pFrame->GetWindow() == aChild ||
+ pFrame->GetShellWindow() == aChild ||
+ pFrame->GetStackingWindow() == aChild )
+ )
+ {
+ // #i63638# check that pointer is inside window, not
+ // only inside stacking window
+ if( root_x >= pFrame->maGeometry.x() && root_x < sal::static_int_cast< int >(pFrame->maGeometry.x()+pFrame->maGeometry.width()) &&
+ root_y >= pFrame->maGeometry.y() && root_y < sal::static_int_cast< int >(pFrame->maGeometry.x()+pFrame->maGeometry.height()) )
+ {
+ bClosePopups = false;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if( m_bXEmbed && pEvent->xbutton.button == Button1 )
+ askForXEmbedFocus( pEvent->xbutton.time );
+
+ if( pEvent->xbutton.button == Button1 ||
+ pEvent->xbutton.button == Button2 ||
+ pEvent->xbutton.button == Button3 )
+ {
+ aMouseEvt.mnX = pEvent->xbutton.x;
+ aMouseEvt.mnY = pEvent->xbutton.y;
+ aMouseEvt.mnTime = pEvent->xbutton.time;
+ aMouseEvt.mnCode = sal_GetCode( pEvent->xbutton.state );
+
+ if( Button1 == pEvent->xbutton.button )
+ aMouseEvt.mnButton = MOUSE_LEFT;
+ else if( Button2 == pEvent->xbutton.button )
+ aMouseEvt.mnButton = MOUSE_MIDDLE;
+ else if( Button3 == pEvent->xbutton.button )
+ aMouseEvt.mnButton = MOUSE_RIGHT;
+
+ nEvent = ButtonPress == pEvent->type
+ ? SalEvent::MouseButtonDown
+ : SalEvent::MouseButtonUp;
+ }
+ else if( pEvent->xbutton.button == Button4 ||
+ pEvent->xbutton.button == Button5 ||
+ pEvent->xbutton.button == Button6 ||
+ pEvent->xbutton.button == Button7 )
+ {
+ const bool bIncrement(
+ pEvent->xbutton.button == Button4 ||
+ pEvent->xbutton.button == Button6 );
+ const bool bHoriz(
+ pEvent->xbutton.button == Button6 ||
+ pEvent->xbutton.button == Button7 );
+
+ if( pEvent->type == ButtonRelease )
+ return false;
+
+ static sal_uLong nLines = 0;
+ if( ! nLines )
+ {
+ char* pEnv = getenv( "SAL_WHEELLINES" );
+ nLines = pEnv ? atoi( pEnv ) : 3;
+ if( nLines > 10 )
+ nLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL;
+ }
+
+ // Compress consecutive wheel events (way too fine scrolling may cause lags if one scrolling steps takes long).
+ CompressWheelEventsData data;
+ data.firstEvent = pEvent;
+ data.count = 1;
+ XEvent dummy;
+ do
+ {
+ data.ignore = false;
+ } while( XCheckIfEvent( pEvent->xany.display, &dummy, compressWheelEvents, reinterpret_cast< XPointer >( &data )));
+
+ SalWheelMouseEvent aWheelEvt;
+ aWheelEvt.mnTime = pEvent->xbutton.time;
+ aWheelEvt.mnX = pEvent->xbutton.x;
+ aWheelEvt.mnY = pEvent->xbutton.y;
+ aWheelEvt.mnDelta = ( bIncrement ? 120 : -120 ) * data.count;
+ aWheelEvt.mnNotchDelta = bIncrement ? 1 : -1;
+ aWheelEvt.mnScrollLines = nLines * data.count;
+ aWheelEvt.mnCode = sal_GetCode( pEvent->xbutton.state );
+ aWheelEvt.mbHorz = bHoriz;
+
+ nEvent = SalEvent::WheelMouse;
+
+ if( AllSettings::GetLayoutRTL() )
+ aWheelEvt.mnX = nWidth_-1-aWheelEvt.mnX;
+ return CallCallback( nEvent, &aWheelEvt );
+ }
+ }
+
+ bool nRet = false;
+ if( nEvent == SalEvent::MouseLeave
+ || ( aMouseEvt.mnX < nWidth_ && aMouseEvt.mnX > -1 &&
+ aMouseEvt.mnY < nHeight_ && aMouseEvt.mnY > -1 )
+ || pDisplay_->MouseCaptured( this )
+ )
+ {
+ if( AllSettings::GetLayoutRTL() )
+ aMouseEvt.mnX = nWidth_-1-aMouseEvt.mnX;
+ nRet = CallCallback( nEvent, &aMouseEvt );
+ }
+
+ if( bClosePopups )
+ {
+ /* #108213# close popups after dispatching the event outside the popup;
+ * applications do weird things.
+ */
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->mpWinData->mpFirstFloat)
+ {
+ if (!(pSVData->mpWinData->mpFirstFloat->GetPopupModeFlags()
+ & FloatWinPopupFlags::NoAppFocusClose))
+ pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel
+ | FloatWinPopupEndFlags::CloseAll);
+ }
+ }
+
+ return nRet;
+}
+
+namespace {
+
+// F10 means either KEY_F10 or KEY_MENU, which has to be decided
+// in the independent part.
+struct KeyAlternate
+{
+ sal_uInt16 nKeyCode;
+ sal_Unicode nCharCode;
+ KeyAlternate() : nKeyCode( 0 ), nCharCode( 0 ) {}
+ KeyAlternate( sal_uInt16 nKey, sal_Unicode nChar = 0 ) : nKeyCode( nKey ), nCharCode( nChar ) {}
+};
+
+}
+
+static KeyAlternate
+GetAlternateKeyCode( const sal_uInt16 nKeyCode )
+{
+ KeyAlternate aAlternate;
+
+ switch( nKeyCode )
+ {
+ case KEY_F10: aAlternate = KeyAlternate( KEY_MENU );break;
+ case KEY_F24: aAlternate = KeyAlternate( KEY_SUBTRACT, '-' );break;
+ }
+
+ return aAlternate;
+}
+
+void X11SalFrame::beginUnicodeSequence()
+{
+ OUString& rSeq( GetGenericUnixSalData()->GetUnicodeCommand() );
+ vcl::DeletionListener aDeleteWatch( this );
+
+ if( !rSeq.isEmpty() )
+ endUnicodeSequence();
+
+ rSeq = "u";
+
+ if( ! aDeleteWatch.isDeleted() )
+ {
+ ExtTextInputAttr nTextAttr = ExtTextInputAttr::Underline;
+ SalExtTextInputEvent aEv;
+ aEv.maText = rSeq;
+ aEv.mpTextAttr = &nTextAttr;
+ aEv.mnCursorPos = 0;
+ aEv.mnCursorFlags = 0;
+
+ CallCallback(SalEvent::ExtTextInput, static_cast<void*>(&aEv));
+ }
+}
+
+bool X11SalFrame::appendUnicodeSequence( sal_Unicode c )
+{
+ bool bRet = false;
+ OUString& rSeq( GetGenericUnixSalData()->GetUnicodeCommand() );
+ if( !rSeq.isEmpty() )
+ {
+ // range check
+ if( (c >= '0' && c <= '9') ||
+ (c >= 'a' && c <= 'f') ||
+ (c >= 'A' && c <= 'F') )
+ {
+ rSeq += OUStringChar(c);
+ std::vector<ExtTextInputAttr> attribs( rSeq.getLength(), ExtTextInputAttr::Underline );
+
+ SalExtTextInputEvent aEv;
+ aEv.maText = rSeq;
+ aEv.mpTextAttr = attribs.data();
+ aEv.mnCursorPos = 0;
+ aEv.mnCursorFlags = 0;
+
+ CallCallback(SalEvent::ExtTextInput, static_cast<void*>(&aEv));
+ bRet = true;
+ }
+ else
+ bRet = endUnicodeSequence();
+ }
+ else
+ endUnicodeSequence();
+ return bRet;
+}
+
+bool X11SalFrame::endUnicodeSequence()
+{
+ OUString& rSeq( GetGenericUnixSalData()->GetUnicodeCommand() );
+
+ vcl::DeletionListener aDeleteWatch( this );
+ if( rSeq.getLength() > 1 && rSeq.getLength() < 6 )
+ {
+ // cut the "u"
+ std::u16string_view aNumbers( rSeq.subView( 1 ) );
+ sal_uInt32 nValue = o3tl::toUInt32(aNumbers, 16);
+ if( nValue >= 32 )
+ {
+ ExtTextInputAttr nTextAttr = ExtTextInputAttr::Underline;
+ SalExtTextInputEvent aEv;
+ aEv.maText = OUString( sal_Unicode(nValue) );
+ aEv.mpTextAttr = &nTextAttr;
+ aEv.mnCursorPos = 0;
+ aEv.mnCursorFlags = 0;
+ CallCallback(SalEvent::ExtTextInput, static_cast<void*>(&aEv));
+ }
+ }
+ bool bWasInput = !rSeq.isEmpty();
+ rSeq.clear();
+ if( bWasInput && ! aDeleteWatch.isDeleted() )
+ CallCallback(SalEvent::EndExtTextInput, nullptr);
+ return bWasInput;
+}
+
+bool X11SalFrame::HandleKeyEvent( XKeyEvent *pEvent )
+{
+ if( pEvent->type == KeyRelease )
+ {
+ // Ignore autorepeat keyrelease events. If there is a series of keypress+keyrelease+keypress events
+ // generated by holding down a key, and if these are from autorepeat (keyrelease and the following keypress
+ // have the same timestamp), drop the autorepeat keyrelease event. Not exactly sure why this is done
+ // (possibly hiding differences between platforms, or just making it more sensible, because technically
+ // the key has not been released at all).
+ bool ignore = false;
+ // Discard queued excessive autorepeat events.
+ // If the user presses and holds down a key, the autorepeating keypress events
+ // may overload LO (e.g. if the key is PageDown and the LO cannot keep up scrolling).
+ // Reduce the load by simply discarding such excessive events (so for a KeyRelease event,
+ // check if it's followed by matching KeyPress+KeyRelease pair(s) and discard those).
+ // This shouldn't have any negative effects - unlike with normal (non-autorepeat
+ // events), the user is unlikely to rely on the exact number of resulting actions
+ // (since autorepeat generates keypress events rather quickly and it's hard to estimate
+ // how many exactly) and the idea should be just keeping the key pressed until something
+ // happens (in which case more events that just lag LO shouldn't make a difference).
+ Display* dpy = pEvent->display;
+ XKeyEvent previousRelease = *pEvent;
+ while( XPending( dpy ))
+ {
+ XEvent nextEvent1;
+ bool discard1 = false;
+ XNextEvent( dpy, &nextEvent1 );
+ if( nextEvent1.type == KeyPress && nextEvent1.xkey.time == previousRelease.time
+ && !nextEvent1.xkey.send_event && nextEvent1.xkey.window == previousRelease.window
+ && nextEvent1.xkey.state == previousRelease.state && nextEvent1.xkey.keycode == previousRelease.keycode )
+ { // This looks like another autorepeat keypress.
+ ignore = true;
+ if( XPending( dpy ))
+ {
+ XEvent nextEvent2;
+ XNextEvent( dpy, &nextEvent2 );
+ if( nextEvent2.type == KeyRelease && nextEvent2.xkey.time <= ( previousRelease.time + 100 )
+ && !nextEvent2.xkey.send_event && nextEvent2.xkey.window == previousRelease.window
+ && nextEvent2.xkey.state == previousRelease.state && nextEvent2.xkey.keycode == previousRelease.keycode )
+ { // And the matching keyrelease -> drop them both.
+ discard1 = true;
+ previousRelease = nextEvent2.xkey;
+ ignore = false; // There either will be another autorepeating keypress that'll lead to discarding
+ // the pEvent keyrelease, it this discarding makes that keyrelease the last one.
+ }
+ else
+ {
+ XPutBackEvent( dpy, &nextEvent2 );
+ break;
+ }
+ }
+ }
+ if( !discard1 )
+ { // Unrelated event, put back and stop compressing.
+ XPutBackEvent( dpy, &nextEvent1 );
+ break;
+ }
+ }
+ if( ignore ) // This autorepeating keyrelease is followed by another keypress.
+ return false;
+ }
+
+ KeySym nKeySym;
+ KeySym nUnmodifiedKeySym;
+ int nLen = 2048;
+ char *pPrintable = static_cast<char*>(alloca( nLen ));
+
+ // singlebyte code composed by input method, the new default
+ if (mpInputContext != nullptr && mpInputContext->UseContext())
+ {
+ // returns a keysym as well as the pPrintable (in system encoding)
+ // printable may be empty.
+ Status nStatus;
+ nKeySym = pDisplay_->GetKeySym( pEvent, pPrintable, &nLen,
+ &nUnmodifiedKeySym,
+ &nStatus, mpInputContext->GetContext() );
+ if ( nStatus == XBufferOverflow )
+ {
+ // In case of overflow, XmbLookupString (called by GetKeySym)
+ // returns required size
+ // TODO : check if +1 is needed for 0 terminator
+ nLen += 1;
+ pPrintable = static_cast<char*>(alloca( nLen ));
+ nKeySym = pDisplay_->GetKeySym( pEvent, pPrintable, &nLen,
+ &nUnmodifiedKeySym,
+ &nStatus, mpInputContext->GetContext() );
+ }
+ }
+ else
+ {
+ // fallback, this should never ever be called
+ Status nStatus = 0;
+ nKeySym = pDisplay_->GetKeySym( pEvent, pPrintable, &nLen, &nUnmodifiedKeySym, &nStatus );
+ }
+
+ SalKeyEvent aKeyEvt;
+ sal_uInt16 nKeyCode;
+ sal_uInt16 nModCode = 0;
+ char aDummy;
+
+ if( pEvent->state & ShiftMask )
+ nModCode |= KEY_SHIFT;
+ if( pEvent->state & ControlMask )
+ nModCode |= KEY_MOD1;
+ if( pEvent->state & Mod1Mask )
+ nModCode |= KEY_MOD2;
+
+ if( nModCode != (KEY_SHIFT|KEY_MOD1) )
+ endUnicodeSequence();
+
+ if( nKeySym == XK_Shift_L || nKeySym == XK_Shift_R
+ || nKeySym == XK_Control_L || nKeySym == XK_Control_R
+ || nKeySym == XK_Alt_L || nKeySym == XK_Alt_R
+ || nKeySym == XK_Meta_L || nKeySym == XK_Meta_R
+ || nKeySym == XK_Super_L || nKeySym == XK_Super_R )
+ {
+ SalKeyModEvent aModEvt;
+ aModEvt.mbDown = false; // auto-accelerator feature not supported here.
+ aModEvt.mnModKeyCode = ModKeyFlags::NONE;
+ if( pEvent->type == KeyPress && mnExtKeyMod == ModKeyFlags::NONE )
+ mbSendExtKeyModChange = true;
+ else if( pEvent->type == KeyRelease && mbSendExtKeyModChange )
+ {
+ aModEvt.mnModKeyCode = mnExtKeyMod;
+ mnExtKeyMod = ModKeyFlags::NONE;
+ }
+
+ // pressing just the ctrl key leads to a keysym of XK_Control but
+ // the event state does not contain ControlMask. In the release
+ // event it's the other way round: it does contain the Control mask.
+ // The modifier mode therefore has to be adapted manually.
+ ModKeyFlags nExtModMask = ModKeyFlags::NONE;
+ sal_uInt16 nModMask = 0;
+ switch( nKeySym )
+ {
+ case XK_Control_L:
+ nExtModMask = ModKeyFlags::LeftMod1;
+ nModMask = KEY_MOD1;
+ break;
+ case XK_Control_R:
+ nExtModMask = ModKeyFlags::RightMod1;
+ nModMask = KEY_MOD1;
+ break;
+ case XK_Alt_L:
+ nExtModMask = ModKeyFlags::LeftMod2;
+ nModMask = KEY_MOD2;
+ break;
+ case XK_Alt_R:
+ nExtModMask = ModKeyFlags::RightMod2;
+ nModMask = KEY_MOD2;
+ break;
+ case XK_Shift_L:
+ nExtModMask = ModKeyFlags::LeftShift;
+ nModMask = KEY_SHIFT;
+ break;
+ case XK_Shift_R:
+ nExtModMask = ModKeyFlags::RightShift;
+ nModMask = KEY_SHIFT;
+ break;
+ // Map Meta/Super keys to MOD3 modifier on all Unix systems
+ // except macOS
+ case XK_Meta_L:
+ case XK_Super_L:
+ nExtModMask = ModKeyFlags::LeftMod3;
+ nModMask = KEY_MOD3;
+ break;
+ case XK_Meta_R:
+ case XK_Super_R:
+ nExtModMask = ModKeyFlags::RightMod3;
+ nModMask = KEY_MOD3;
+ break;
+ }
+ if( pEvent->type == KeyRelease )
+ {
+ nModCode &= ~nModMask;
+ mnExtKeyMod &= ~nExtModMask;
+ }
+ else
+ {
+ nModCode |= nModMask;
+ mnExtKeyMod |= nExtModMask;
+ }
+
+ aModEvt.mnCode = nModCode;
+
+ return CallCallback( SalEvent::KeyModChange, &aModEvt );
+ }
+
+ mbSendExtKeyModChange = false;
+
+ // try to figure out the vcl code for the keysym
+ // #i52338# use the unmodified KeySym if there is none for the real KeySym
+ // because the independent part has only keycodes for unshifted keys
+ nKeyCode = pDisplay_->GetKeyCode( nKeySym, &aDummy );
+ if( nKeyCode == 0 )
+ nKeyCode = pDisplay_->GetKeyCode( nUnmodifiedKeySym, &aDummy );
+
+ // try to figure out a printable if XmbLookupString returns only a keysym
+ // and NOT a printable. Do not store it in pPrintable[0] since it is expected to
+ // be in system encoding, not unicode.
+ // #i8988##, if KeySym and printable look equally promising then prefer KeySym
+ // the printable is bound to the encoding so the KeySym might contain more
+ // information (in et_EE locale: "Compose + Z + <" delivers "," in printable and
+ // (the desired) Zcaron in KeySym
+ sal_Unicode nKeyString = 0x0;
+ if ( (nLen == 0)
+ || ((nLen == 1) && (nKeySym > 0)) )
+ nKeyString = KeysymToUnicode (nKeySym);
+ // if we have nothing we give up
+ if( !nKeyCode && !nLen && !nKeyString)
+ return false;
+
+ vcl::DeletionListener aDeleteWatch( this );
+
+ if( nModCode == (KEY_SHIFT | KEY_MOD1) && pEvent->type == KeyPress )
+ {
+ sal_uInt16 nSeqKeyCode = pDisplay_->GetKeyCode( nUnmodifiedKeySym, &aDummy );
+ if( nSeqKeyCode == KEY_U )
+ {
+ beginUnicodeSequence();
+ return true;
+ }
+ else if( nSeqKeyCode >= KEY_0 && nSeqKeyCode <= KEY_9 )
+ {
+ if( appendUnicodeSequence( u'0' + sal_Unicode(nSeqKeyCode - KEY_0) ) )
+ return true;
+ }
+ else if( nSeqKeyCode >= KEY_A && nSeqKeyCode <= KEY_F )
+ {
+ if( appendUnicodeSequence( u'a' + sal_Unicode(nSeqKeyCode - KEY_A) ) )
+ return true;
+ }
+ else
+ endUnicodeSequence();
+ }
+
+ if( aDeleteWatch.isDeleted() )
+ return false;
+
+ rtl_TextEncoding nEncoding = osl_getThreadTextEncoding();
+
+ sal_Unicode *pBuffer;
+ sal_Unicode *pString;
+ sal_Size nBufferSize = nLen * 2;
+ sal_Size nSize;
+ pBuffer = static_cast<sal_Unicode*>(malloc( nBufferSize + 2 ));
+ pBuffer[ 0 ] = 0;
+
+ if (nKeyString != 0)
+ {
+ pString = &nKeyString;
+ nSize = 1;
+ }
+ else if (nLen > 0 && nEncoding != RTL_TEXTENCODING_UNICODE)
+ {
+ // create text converter
+ rtl_TextToUnicodeConverter aConverter =
+ rtl_createTextToUnicodeConverter( nEncoding );
+ rtl_TextToUnicodeContext aContext =
+ rtl_createTextToUnicodeContext( aConverter );
+
+ sal_uInt32 nConversionInfo;
+ sal_Size nConvertedChars;
+
+ // convert to single byte text stream
+ nSize = rtl_convertTextToUnicode(
+ aConverter, aContext,
+ pPrintable, nLen,
+ pBuffer, nBufferSize,
+ RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_IGNORE |
+ RTL_TEXTTOUNICODE_FLAGS_INVALID_IGNORE,
+ &nConversionInfo, &nConvertedChars );
+
+ // destroy converter
+ rtl_destroyTextToUnicodeContext( aConverter, aContext );
+ rtl_destroyTextToUnicodeConverter( aConverter );
+
+ pString = pBuffer;
+ }
+ else if (nLen > 0 /* nEncoding == RTL_TEXTENCODING_UNICODE */)
+ {
+ pString = reinterpret_cast<sal_Unicode*>(pPrintable);
+ nSize = nLen;
+ }
+ else
+ {
+ pString = pBuffer;
+ nSize = 0;
+ }
+
+ if ( mpInputContext != nullptr
+ && mpInputContext->UseContext()
+ && KeyRelease != pEvent->type
+ && ( (nSize > 1)
+ || (nSize > 0 && mpInputContext->IsPreeditMode())) )
+ {
+ mpInputContext->CommitKeyEvent(pString, nSize);
+ }
+ else
+ // normal single character keyinput
+ {
+ aKeyEvt.mnCode = nKeyCode | nModCode;
+ aKeyEvt.mnRepeat = 0;
+ aKeyEvt.mnCharCode = pString[ 0 ];
+
+ if( KeyRelease == pEvent->type )
+ {
+ CallCallback( SalEvent::KeyUp, &aKeyEvt );
+ }
+ else
+ {
+ if ( ! CallCallback(SalEvent::KeyInput, &aKeyEvt) )
+ {
+ // independent layer doesn't want to handle key-event, so check
+ // whether the keycode may have an alternate meaning
+ KeyAlternate aAlternate = GetAlternateKeyCode( nKeyCode );
+ if ( aAlternate.nKeyCode != 0 )
+ {
+ aKeyEvt.mnCode = aAlternate.nKeyCode | nModCode;
+ if( aAlternate.nCharCode )
+ aKeyEvt.mnCharCode = aAlternate.nCharCode;
+ CallCallback(SalEvent::KeyInput, &aKeyEvt);
+ }
+ }
+ }
+ }
+
+ // update the spot location for PreeditPosition IME style
+
+ if (! aDeleteWatch.isDeleted())
+ {
+ if (mpInputContext != nullptr && mpInputContext->UseContext())
+ mpInputContext->UpdateSpotLocation();
+ }
+
+ free (pBuffer);
+ return true;
+}
+
+bool X11SalFrame::HandleFocusEvent( XFocusChangeEvent const *pEvent )
+{
+ // ReflectionX in Windows mode changes focus while mouse is grabbed
+ if( nVisibleFloats > 0 && GetDisplay()->getWMAdaptor()->getWindowManagerName() == "ReflectionX Windows" )
+ return true;
+
+ /* ignore focusout resulting from keyboard grabs
+ * we do not grab it and are not interested when
+ * someone else does CDE e.g. does a XGrabKey on arrow keys
+ * handle focus events with mode NotifyWhileGrabbed
+ * because with CDE alt-tab focus changing we do not get
+ * normal focus events
+ * cast focus event to the input context, otherwise the
+ * status window does not follow the application frame
+ */
+
+ if ( mpInputContext != nullptr )
+ {
+ if( FocusIn == pEvent->type )
+ mpInputContext->SetICFocus( this );
+ }
+
+ if ( pEvent->mode == NotifyNormal || pEvent->mode == NotifyWhileGrabbed ||
+ ( ( nStyle_ & SalFrameStyleFlags::PLUG ) && pEvent->window == GetShellWindow() )
+ )
+ {
+ if( hPresentationWindow != None && hPresentationWindow != GetShellWindow() )
+ return false;
+
+ if( FocusIn == pEvent->type )
+ {
+ GetSalInstance()->updatePrinterUpdate();
+ mbInputFocus = True;
+ ImplSVData* pSVData = ImplGetSVData();
+
+ bool nRet = CallCallback( SalEvent::GetFocus, nullptr );
+ if ((mpParent != nullptr && nStyle_ == SalFrameStyleFlags::NONE)
+ && pSVData->mpWinData->mpFirstFloat)
+ {
+ FloatWinPopupFlags nMode = pSVData->mpWinData->mpFirstFloat->GetPopupModeFlags();
+ pSVData->mpWinData->mpFirstFloat->SetPopupModeFlags(
+ nMode & ~FloatWinPopupFlags::NoAppFocusClose);
+ }
+ return nRet;
+ }
+ else
+ {
+ mbInputFocus = False;
+ mbSendExtKeyModChange = false;
+ mnExtKeyMod = ModKeyFlags::NONE;
+ return CallCallback( SalEvent::LoseFocus, nullptr );
+ }
+ }
+
+ return false;
+}
+
+bool X11SalFrame::HandleExposeEvent( XEvent const *pEvent )
+{
+ XRectangle aRect = { 0, 0, 0, 0 };
+ sal_uInt16 nCount = 0;
+
+ if( pEvent->type == Expose )
+ {
+ aRect.x = pEvent->xexpose.x;
+ aRect.y = pEvent->xexpose.y;
+ aRect.width = pEvent->xexpose.width;
+ aRect.height = pEvent->xexpose.height;
+ nCount = pEvent->xexpose.count;
+ }
+ else if( pEvent->type == GraphicsExpose )
+ {
+ aRect.x = pEvent->xgraphicsexpose.x;
+ aRect.y = pEvent->xgraphicsexpose.y;
+ aRect.width = pEvent->xgraphicsexpose.width;
+ aRect.height = pEvent->xgraphicsexpose.height;
+ nCount = pEvent->xgraphicsexpose.count;
+ }
+
+ if( IsOverrideRedirect() && mbFullScreen &&
+ aPresentationReparentList.empty() )
+ // we are in fullscreen mode -> override redirect
+ // focus is possibly lost, so reget it
+ XSetInputFocus( GetXDisplay(), GetShellWindow(), RevertToNone, CurrentTime );
+
+ // width and height are extents, so they are of by one for rectangle
+ maPaintRegion.Union( tools::Rectangle( Point(aRect.x, aRect.y), Size(aRect.width+1, aRect.height+1) ) );
+
+ if( nCount )
+ // wait for last expose rectangle, do not wait for resize timer
+ // if a completed graphics expose sequence is available
+ return true;
+
+ SalPaintEvent aPEvt( maPaintRegion.Left(), maPaintRegion.Top(), maPaintRegion.GetWidth(), maPaintRegion.GetHeight() );
+
+ CallCallback( SalEvent::Paint, &aPEvt );
+ maPaintRegion = tools::Rectangle();
+
+ return true;
+}
+
+void X11SalFrame::RestackChildren( ::Window* pTopLevelWindows, int nTopLevelWindows )
+{
+ if( maChildren.empty() )
+ return;
+
+ int nWindow = nTopLevelWindows;
+ while( nWindow-- )
+ if( pTopLevelWindows[nWindow] == GetStackingWindow() )
+ break;
+ if( nWindow < 0 )
+ return;
+
+ for (auto const& child : maChildren)
+ {
+ if( child->bMapped_ )
+ {
+ int nChild = nWindow;
+ while( nChild-- )
+ {
+ if( pTopLevelWindows[nChild] == child->GetStackingWindow() )
+ {
+ // if a child is behind its parent, place it above the
+ // parent (for insane WMs like Dtwm and olwm)
+ XWindowChanges aCfg;
+ aCfg.sibling = GetStackingWindow();
+ aCfg.stack_mode = Above;
+ XConfigureWindow( GetXDisplay(), child->GetStackingWindow(), CWSibling|CWStackMode, &aCfg );
+ break;
+ }
+ }
+ }
+ }
+ for (auto const& child : maChildren)
+ {
+ child->RestackChildren( pTopLevelWindows, nTopLevelWindows );
+ }
+}
+
+void X11SalFrame::RestackChildren()
+{
+ if( maChildren.empty() )
+ return;
+
+ ::Window aRoot, aParent, *pChildren = nullptr;
+ unsigned int nChildren;
+ if( XQueryTree( GetXDisplay(),
+ GetDisplay()->GetRootWindow( m_nXScreen ),
+ &aRoot,
+ &aParent,
+ &pChildren,
+ &nChildren ) )
+ {
+ RestackChildren( pChildren, nChildren );
+ XFree( pChildren );
+ }
+}
+
+static Bool size_event_predicate( Display*, XEvent* event, XPointer arg )
+{
+ if( event->type != ConfigureNotify )
+ return False;
+ X11SalFrame* frame = reinterpret_cast< X11SalFrame* >( arg );
+ XConfigureEvent* pEvent = &event->xconfigure;
+ if( pEvent->window != frame->GetShellWindow()
+ && pEvent->window != frame->GetWindow()
+ && pEvent->window != frame->GetForeignParent()
+ && pEvent->window != frame->GetStackingWindow())
+ { // ignored at top of HandleSizeEvent()
+ return False;
+ }
+ if( pEvent->window == frame->GetStackingWindow())
+ return False; // filtered later in HandleSizeEvent()
+ // at this point we know that there is another similar event in the queue
+ frame->setPendingSizeEvent();
+ return False; // but do not process the new event out of order
+}
+
+void X11SalFrame::setPendingSizeEvent()
+{
+ mPendingSizeEvent = true;
+}
+
+bool X11SalFrame::HandleSizeEvent( XConfigureEvent *pEvent )
+{
+ // NOTE: if you add more tests in this function, make sure to update size_event_predicate()
+ // so that it finds exactly the same events
+
+ if ( pEvent->window != GetShellWindow()
+ && pEvent->window != GetWindow()
+ && pEvent->window != GetForeignParent()
+ && pEvent->window != GetStackingWindow()
+ )
+ {
+ // could be as well a sys-child window (aka SalObject)
+ return true;
+ }
+
+ if( ( nStyle_ & SalFrameStyleFlags::PLUG ) && pEvent->window == GetShellWindow() )
+ {
+ // just update the children's positions
+ RestackChildren();
+ return true;
+ }
+
+ if( pEvent->window == GetForeignParent() )
+ {
+ XResizeWindow( GetXDisplay(),
+ GetWindow(),
+ pEvent->width,
+ pEvent->height );
+ cairo_xlib_surface_set_size(mpSurface, pEvent->width, pEvent->height);
+ }
+
+ ::Window hDummy;
+ XTranslateCoordinates( GetXDisplay(),
+ GetWindow(),
+ pDisplay_->GetRootWindow( pDisplay_->GetDefaultXScreen() ),
+ 0, 0,
+ &pEvent->x, &pEvent->y,
+ &hDummy );
+
+ if( pEvent->window == GetStackingWindow() )
+ {
+ if( maGeometry.x() != pEvent->x || maGeometry.y() != pEvent->y )
+ {
+ maGeometry.setPos({ pEvent->x, pEvent->y });
+ CallCallback( SalEvent::Move, nullptr );
+ }
+ return true;
+ }
+
+ // check size hints in first time SalFrame::Show
+ if( X11ShowState::Unknown == nShowState_ && bMapped_ )
+ nShowState_ = X11ShowState::Normal;
+
+ // Avoid a race condition where resizing this window to one size and shortly after that
+ // to another size generates first size event with the old size and only after that
+ // with the new size, temporarily making us think the old size is valid (bnc#674806).
+ // So if there is another size event for this window pending, ignore this one.
+ mPendingSizeEvent = false;
+ XEvent dummy;
+ XCheckIfEvent( GetXDisplay(), &dummy, size_event_predicate, reinterpret_cast< XPointer >( this ));
+ if( mPendingSizeEvent )
+ return true;
+
+ nWidth_ = pEvent->width;
+ nHeight_ = pEvent->height;
+
+ bool bMoved = ( pEvent->x != maGeometry.x() || pEvent->y != maGeometry.y() );
+ bool bSized = ( pEvent->width != static_cast<int>(maGeometry.width()) || pEvent->height != static_cast<int>(maGeometry.height()) );
+
+ cairo_xlib_surface_set_size(mpSurface, pEvent->width, pEvent->height);
+ maGeometry.setPosSize({ pEvent->x, pEvent->y }, { pEvent->width, pEvent->height });
+ updateScreenNumber();
+
+ // update children's position
+ RestackChildren();
+
+ if( bSized && ! bMoved )
+ CallCallback( SalEvent::Resize, nullptr );
+ else if( bMoved && ! bSized )
+ CallCallback( SalEvent::Move, nullptr );
+ else if( bMoved && bSized )
+ CallCallback( SalEvent::MoveResize, nullptr );
+
+ return true;
+}
+
+IMPL_LINK_NOARG(X11SalFrame, HandleAlwaysOnTopRaise, Timer *, void)
+{
+ if( bMapped_ )
+ ToTop( SalFrameToTop::NONE );
+}
+
+bool X11SalFrame::HandleReparentEvent( XReparentEvent *pEvent )
+{
+ Display *pDisplay = pEvent->display;
+ ::Window hWM_Parent;
+ ::Window hRoot, *Children, hDummy;
+ unsigned int nChildren;
+
+ static const char* pDisableStackingCheck = getenv( "SAL_DISABLE_STACKING_CHECK" );
+
+ GetGenericUnixSalData()->ErrorTrapPush();
+
+ /*
+ * don't rely on the new parent from the event.
+ * the event may be "out of date", that is the window manager
+ * window may not exist anymore. This can happen if someone
+ * shows a frame and hides it again quickly (not that it would
+ * be very sensible)
+ */
+ hWM_Parent = GetShellWindow();
+ do
+ {
+ Children = nullptr;
+ XQueryTree( pDisplay,
+ hWM_Parent,
+ &hRoot,
+ &hDummy,
+ &Children,
+ &nChildren );
+
+ bool bError = GetGenericUnixSalData()->ErrorTrapPop( false );
+ GetGenericUnixSalData()->ErrorTrapPush();
+
+ if( bError )
+ {
+ hWM_Parent = GetShellWindow();
+ break;
+ }
+ /* this sometimes happens if a Show(true) is
+ * immediately followed by Show(false) (which is braindead anyway)
+ */
+ if( hDummy == hWM_Parent )
+ hDummy = hRoot;
+ if( hDummy != hRoot )
+ hWM_Parent = hDummy;
+ if( Children )
+ XFree( Children );
+ } while( hDummy != hRoot );
+
+ if( GetStackingWindow() == None
+ && hWM_Parent != hPresentationWindow
+ && hWM_Parent != GetShellWindow()
+ && ( ! pDisableStackingCheck || ! *pDisableStackingCheck )
+ )
+ {
+ mhStackingWindow = hWM_Parent;
+ XSelectInput( pDisplay, GetStackingWindow(), StructureNotifyMask );
+ }
+
+ if( hWM_Parent == pDisplay_->GetRootWindow( pDisplay_->GetDefaultXScreen() )
+ || hWM_Parent == GetForeignParent()
+ || pEvent->parent == pDisplay_->GetRootWindow( pDisplay_->GetDefaultXScreen() )
+ || ( nStyle_ & SalFrameStyleFlags::FLOAT ) )
+ {
+ // Reparenting before Destroy
+ aPresentationReparentList.remove( GetStackingWindow() );
+ mhStackingWindow = None;
+ GetGenericUnixSalData()->ErrorTrapPop();
+ return false;
+ }
+
+ /*
+ * evil hack to show decorated windows on top
+ * of override redirect presentation windows:
+ * reparent the window manager window to the presentation window
+ * does not work with non-reparenting WMs
+ * in future this should not be necessary anymore with
+ * _NET_WM_STATE_FULLSCREEN available
+ */
+ if( hPresentationWindow != None
+ && hPresentationWindow != GetWindow()
+ && GetStackingWindow() != None
+ && GetStackingWindow() != GetDisplay()->GetRootWindow( m_nXScreen )
+ )
+ {
+ int x = 0, y = 0;
+ ::Window aChild;
+ XTranslateCoordinates( GetXDisplay(),
+ GetStackingWindow(),
+ GetDisplay()->GetRootWindow( m_nXScreen ),
+ 0, 0,
+ &x, &y,
+ &aChild
+ );
+ XReparentWindow( GetXDisplay(),
+ GetStackingWindow(),
+ hPresentationWindow,
+ x, y
+ );
+ aPresentationReparentList.push_back( GetStackingWindow() );
+ }
+
+ int nLeft = 0, nTop = 0;
+ XTranslateCoordinates( GetXDisplay(),
+ GetShellWindow(),
+ hWM_Parent,
+ 0, 0,
+ &nLeft,
+ &nTop,
+ &hDummy );
+ maGeometry.setLeftDecoration(nLeft > 0 ? nLeft-1 : 0);
+ maGeometry.setTopDecoration(nTop > 0 ? nTop-1 : 0);
+
+ /*
+ * decorations are not symmetric,
+ * so need real geometries here
+ * (this will fail with virtual roots ?)
+ */
+
+ // reset error occurred
+ GetGenericUnixSalData()->ErrorTrapPop();
+ GetGenericUnixSalData()->ErrorTrapPush();
+
+ int xp, yp, x, y;
+ unsigned int wp, w, hp, h, bw, d;
+ XGetGeometry( GetXDisplay(),
+ GetShellWindow(),
+ &hRoot,
+ &x, &y, &w, &h, &bw, &d );
+ XGetGeometry( GetXDisplay(),
+ hWM_Parent,
+ &hRoot,
+ &xp, &yp, &wp, &hp, &bw, &d );
+ bool bResized = false;
+ bool bError = GetGenericUnixSalData()->ErrorTrapPop( false );
+ GetGenericUnixSalData()->ErrorTrapPush();
+
+ if( ! bError )
+ {
+ maGeometry.setRightDecoration(wp - w - maGeometry.leftDecoration());
+ maGeometry.setBottomDecoration(hp - h - maGeometry.topDecoration());
+ bResized = w != o3tl::make_unsigned(maGeometry.width()) ||
+ h != o3tl::make_unsigned(maGeometry.height());
+ /*
+ * note: this works because hWM_Parent is direct child of root,
+ * not necessarily parent of GetShellWindow()
+ */
+ maGeometry.setPosSize({ xp + nLeft, yp + nTop }, { w, h });
+ }
+
+ // limit width and height if we are too large: #47757
+ // olwm and fvwm need this, it doesn't harm the rest
+
+ // #i81311# do this only for sizable frames
+ if( nStyle_ & SalFrameStyleFlags::SIZEABLE )
+ {
+ AbsoluteScreenPixelSize aScreenSize = GetDisplay()->GetScreenSize( m_nXScreen );
+ int nScreenWidth = aScreenSize.Width();
+ int nScreenHeight = aScreenSize.Height();
+ int nFrameWidth = maGeometry.width() + maGeometry.leftDecoration() + maGeometry.rightDecoration();
+ int nFrameHeight = maGeometry.height() + maGeometry.topDecoration() + maGeometry.bottomDecoration();
+
+ if ((nFrameWidth > nScreenWidth) || (nFrameHeight > nScreenHeight))
+ {
+ Size aSize(maGeometry.width(), maGeometry.height());
+
+ if (nFrameWidth > nScreenWidth)
+ aSize.setWidth( nScreenWidth - maGeometry.rightDecoration() - maGeometry.leftDecoration() );
+ if (nFrameHeight > nScreenHeight)
+ aSize.setHeight( nScreenHeight - maGeometry.bottomDecoration() - maGeometry.topDecoration() );
+
+ SetSize( aSize );
+ bResized = false;
+ }
+ }
+ if( bResized )
+ CallCallback( SalEvent::Resize, nullptr );
+
+ GetGenericUnixSalData()->ErrorTrapPop();
+
+ return true;
+}
+
+bool X11SalFrame::HandleStateEvent( XPropertyEvent const *pEvent )
+{
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ unsigned char *prop = nullptr;
+
+ if( 0 != XGetWindowProperty( GetXDisplay(),
+ GetShellWindow(),
+ pEvent->atom, // property
+ 0, // long_offset (32bit)
+ 2, // long_length (32bit)
+ False, // delete
+ pEvent->atom, // req_type
+ &actual_type,
+ &actual_format,
+ &nitems,
+ &bytes_after,
+ &prop )
+ || ! prop
+ )
+ return false;
+
+ DBG_ASSERT( actual_type == pEvent->atom
+ && 32 == actual_format
+ && 2 == nitems
+ && 0 == bytes_after, "HandleStateEvent" );
+
+ if( *reinterpret_cast<unsigned long*>(prop) == NormalState )
+ nShowState_ = X11ShowState::Normal;
+ else if( *reinterpret_cast<unsigned long*>(prop) == IconicState )
+ nShowState_ = X11ShowState::Minimized;
+
+ XFree( prop );
+ return true;
+}
+
+bool X11SalFrame::HandleClientMessage( XClientMessageEvent *pEvent )
+{
+ const WMAdaptor& rWMAdaptor( *pDisplay_->getWMAdaptor() );
+
+#if !defined(__synchronous_extinput__)
+ if( pEvent->message_type == rWMAdaptor.getAtom( WMAdaptor::SAL_EXTTEXTEVENT ) )
+ {
+ HandleExtTextEvent (pEvent);
+ return true;
+ }
+#endif
+ else if( pEvent->message_type == rWMAdaptor.getAtom( WMAdaptor::SAL_QUITEVENT ) )
+ {
+ SAL_WARN( "vcl", "X11SalFrame::Dispatch Quit" );
+ Close(); // ???
+ return true;
+ }
+ else if( pEvent->message_type == rWMAdaptor.getAtom( WMAdaptor::WM_PROTOCOLS ) )
+ {
+ if( static_cast<Atom>(pEvent->data.l[0]) == rWMAdaptor.getAtom( WMAdaptor::NET_WM_PING ) )
+ rWMAdaptor.answerPing( this, pEvent );
+ else if( ! ( nStyle_ & SalFrameStyleFlags::PLUG )
+ && ! (( nStyle_ & SalFrameStyleFlags::FLOAT ) && (nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION))
+ )
+ {
+ if( static_cast<Atom>(pEvent->data.l[0]) == rWMAdaptor.getAtom( WMAdaptor::WM_DELETE_WINDOW ) )
+ {
+ Close();
+ return true;
+ }
+ else if( static_cast<Atom>(pEvent->data.l[0]) == rWMAdaptor.getAtom( WMAdaptor::WM_TAKE_FOCUS ) )
+ {
+ // do nothing, we set the input focus in ToTop() if necessary
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.window", "got WM_TAKE_FOCUS on "
+ << ((nStyle_ &
+ SalFrameStyleFlags::OWNERDRAWDECORATION) ?
+ "ownerdraw" :
+ "NON OWNERDRAW" )
+ << " window.");
+#endif
+ }
+ }
+ }
+ else if( pEvent->message_type == rWMAdaptor.getAtom( WMAdaptor::XEMBED ) &&
+ pEvent->window == GetWindow() )
+ {
+ if( pEvent->data.l[1] == 1 || // XEMBED_WINDOW_ACTIVATE
+ pEvent->data.l[1] == 2 ) // XEMBED_WINDOW_DEACTIVATE
+ {
+ XFocusChangeEvent aEvent;
+ aEvent.type = (pEvent->data.l[1] == 1 ? FocusIn : FocusOut);
+ aEvent.serial = pEvent->serial;
+ aEvent.send_event = True;
+ aEvent.display = pEvent->display;
+ aEvent.window = pEvent->window;
+ aEvent.mode = NotifyNormal;
+ aEvent.detail = NotifyDetailNone;
+ HandleFocusEvent( &aEvent );
+ }
+ }
+ return false;
+}
+
+bool X11SalFrame::Dispatch( XEvent *pEvent )
+{
+ bool nRet = false;
+
+ if( -1 == nCaptured_ )
+ {
+ CaptureMouse( true );
+#ifdef DBG_UTIL
+ if( -1 != nCaptured_ )
+ pDisplay_->DbgPrintDisplayEvent("Captured", pEvent);
+#endif
+ }
+
+ if( pEvent->xany.window == GetShellWindow() || pEvent->xany.window == GetWindow() )
+ {
+ switch( pEvent->type )
+ {
+ case KeyPress:
+ nRet = HandleKeyEvent( &pEvent->xkey );
+ break;
+
+ case KeyRelease:
+ nRet = HandleKeyEvent( &pEvent->xkey );
+ break;
+
+ case ButtonPress:
+ // if we lose the focus in presentation mode
+ // there are good chances that we never get it back
+ // since the WM ignores us
+ if( IsOverrideRedirect() )
+ {
+ XSetInputFocus( GetXDisplay(), GetShellWindow(),
+ RevertToNone, CurrentTime );
+ }
+ [[fallthrough]];
+ case ButtonRelease:
+ case MotionNotify:
+ case EnterNotify:
+ case LeaveNotify:
+ nRet = HandleMouseEvent( pEvent );
+ break;
+
+ case FocusIn:
+ case FocusOut:
+ nRet = HandleFocusEvent( &pEvent->xfocus );
+ break;
+
+ case Expose:
+ case GraphicsExpose:
+ nRet = HandleExposeEvent( pEvent );
+ break;
+
+ case MapNotify:
+ if( pEvent->xmap.window == GetShellWindow() )
+ {
+ if( nShowState_ == X11ShowState::Hidden )
+ {
+ /*
+ * workaround for (at least) KWin 2.2.2
+ * which will map windows that were once transient
+ * even if they are withdrawn when the respective
+ * document is mapped.
+ */
+ if( ! (nStyle_ & SalFrameStyleFlags::PLUG) )
+ XUnmapWindow( GetXDisplay(), GetShellWindow() );
+ break;
+ }
+ bMapped_ = true;
+ bViewable_ = true;
+ nRet = true;
+ if ( mpInputContext != nullptr )
+ mpInputContext->Map( this );
+ CallCallback( SalEvent::Resize, nullptr );
+
+ bool bSetFocus = m_bSetFocusOnMap;
+
+ /*
+ * sometimes a message box/dialogue is brought up when a frame is not mapped
+ * the corresponding TRANSIENT_FOR hint is then set to the root window
+ * so that the dialogue shows in all cases. Correct it here if the
+ * frame is shown afterwards.
+ */
+ if( ! IsChildWindow()
+ && ! IsOverrideRedirect()
+ && ! IsFloatGrabWindow()
+ )
+ {
+ for (auto const& child : maChildren)
+ {
+ if( child->mbTransientForRoot )
+ pDisplay_->getWMAdaptor()->changeReferenceFrame( child, this );
+ }
+ }
+
+ if( hPresentationWindow != None && GetShellWindow() == hPresentationWindow )
+ XSetInputFocus( GetXDisplay(), GetShellWindow(), RevertToParent, CurrentTime );
+
+ if( bSetFocus )
+ {
+ XSetInputFocus( GetXDisplay(),
+ GetShellWindow(),
+ RevertToParent,
+ CurrentTime );
+ }
+
+ RestackChildren();
+ m_bSetFocusOnMap = false;
+ }
+ break;
+
+ case UnmapNotify:
+ if( pEvent->xunmap.window == GetShellWindow() )
+ {
+ bMapped_ = false;
+ bViewable_ = false;
+ nRet = true;
+ if ( mpInputContext != nullptr )
+ mpInputContext->Unmap();
+ CallCallback( SalEvent::Resize, nullptr );
+ }
+ break;
+
+ case ConfigureNotify:
+ if( pEvent->xconfigure.window == GetShellWindow()
+ || pEvent->xconfigure.window == GetWindow() )
+ nRet = HandleSizeEvent( &pEvent->xconfigure );
+ break;
+
+ case VisibilityNotify:
+ nVisibility_ = pEvent->xvisibility.state;
+ nRet = true;
+ if( bAlwaysOnTop_
+ && bMapped_
+ && ! GetDisplay()->getWMAdaptor()->isAlwaysOnTopOK()
+ && nVisibility_ != VisibilityUnobscured )
+ maAlwaysOnTopRaiseTimer.Start();
+ break;
+
+ case ReparentNotify:
+ nRet = HandleReparentEvent( &pEvent->xreparent );
+ break;
+
+ case MappingNotify:
+ break;
+
+ case ColormapNotify:
+ nRet = false;
+ break;
+
+ case PropertyNotify:
+ {
+ if( pEvent->xproperty.atom == pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::WM_STATE ) )
+ nRet = HandleStateEvent( &pEvent->xproperty );
+ else
+ nRet = pDisplay_->getWMAdaptor()->handlePropertyNotify( this, &pEvent->xproperty );
+ break;
+ }
+
+ case ClientMessage:
+ nRet = HandleClientMessage( &pEvent->xclient );
+ break;
+ }
+ }
+ else
+ {
+ switch( pEvent->type )
+ {
+ case FocusIn:
+ case FocusOut:
+ if( ( nStyle_ & SalFrameStyleFlags::PLUG )
+ && ( pEvent->xfocus.window == GetShellWindow()
+ || pEvent->xfocus.window == GetForeignParent() )
+ )
+ {
+ nRet = HandleFocusEvent( &pEvent->xfocus );
+ }
+ break;
+
+ case ConfigureNotify:
+ if( pEvent->xconfigure.window == GetForeignParent() ||
+ pEvent->xconfigure.window == GetShellWindow() )
+ nRet = HandleSizeEvent( &pEvent->xconfigure );
+
+ if( pEvent->xconfigure.window == GetStackingWindow() )
+ nRet = HandleSizeEvent( &pEvent->xconfigure );
+
+ RestackChildren();
+ break;
+ }
+ }
+
+ return nRet;
+}
+
+void X11SalFrame::ResetClipRegion()
+{
+ m_vClipRectangles.clear();
+
+ const int dest_kind = ShapeBounding;
+ const int op = ShapeSet;
+ const int ordering = YSorted;
+
+ XWindowAttributes win_attrib;
+ XRectangle win_size;
+
+ ::Window aShapeWindow = mhShellWindow;
+
+ XGetWindowAttributes ( GetDisplay()->GetDisplay(),
+ aShapeWindow,
+ &win_attrib );
+
+ win_size.x = 0;
+ win_size.y = 0;
+ win_size.width = win_attrib.width;
+ win_size.height = win_attrib.height;
+
+ XShapeCombineRectangles ( GetDisplay()->GetDisplay(),
+ aShapeWindow,
+ dest_kind,
+ 0, 0, // x_off, y_off
+ &win_size, // list of rectangles
+ 1, // number of rectangles
+ op, ordering );
+}
+
+void X11SalFrame::BeginSetClipRegion( sal_uInt32 /*nRects*/ )
+{
+ m_vClipRectangles.clear();
+}
+
+void X11SalFrame::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ m_vClipRectangles.emplace_back( XRectangle { static_cast<short>(nX), static_cast<short>(nY),
+ static_cast<unsigned short>(nWidth), static_cast<unsigned short>(nHeight) } );
+}
+
+void X11SalFrame::EndSetClipRegion()
+{
+ const int dest_kind = ShapeBounding;
+ const int ordering = YSorted;
+ const int op = ShapeSet;
+
+ ::Window aShapeWindow = mhShellWindow;
+ XShapeCombineRectangles ( GetDisplay()->GetDisplay(),
+ aShapeWindow,
+ dest_kind,
+ 0, 0, // x_off, y_off
+ m_vClipRectangles.data(),
+ m_vClipRectangles.size(),
+ op, ordering );
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/window/salobj.cxx b/vcl/unx/generic/window/salobj.cxx
new file mode 100644
index 0000000000..e2571c7911
--- /dev/null
+++ b/vcl/unx/generic/window/salobj.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 .
+ */
+
+#if OSL_DEBUG_LEVEL > 1
+#include <stdio.h>
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/shape.h>
+
+
+#include <vcl/keycodes.hxx>
+#include <vcl/event.hxx>
+#include <sal/log.hxx>
+
+#include <unx/salinst.h>
+#include <unx/saldisp.hxx>
+#include <unx/salobj.h>
+
+#include <salframe.hxx>
+#include <salwtype.hxx>
+
+// SalInstance member to create and destroy a SalObject
+
+SalObject* X11SalInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow )
+{
+ return X11SalObject::CreateObject( pParent, pWindowData, bShow );
+}
+
+X11SalObject* X11SalObject::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow )
+{
+ int error_base, event_base;
+ X11SalObject* pObject = new X11SalObject();
+ SystemEnvData* pObjData = const_cast<SystemEnvData*>(pObject->GetSystemData());
+
+ if ( ! XShapeQueryExtension( static_cast<Display*>(pObjData->pDisplay),
+ &event_base, &error_base ) )
+ {
+ delete pObject;
+ return nullptr;
+ }
+
+ pObject->mpParent = pParent;
+
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ const SystemEnvData* pEnv = pParent->GetSystemData();
+ Display* pDisp = pSalDisp->GetDisplay();
+ ::Window aObjectParent = static_cast<::Window>(pEnv->GetWindowHandle(pParent));
+ pObject->maParentWin = aObjectParent;
+
+ // find out on which screen that window is
+ XWindowAttributes aParentAttr;
+ XGetWindowAttributes( pDisp, aObjectParent, &aParentAttr );
+ SalX11Screen nXScreen( XScreenNumberOfScreen( aParentAttr.screen ) );
+ Visual* pVisual = (pWindowData && pWindowData->pVisual) ?
+ static_cast<Visual*>(pWindowData->pVisual) :
+ pSalDisp->GetVisual( nXScreen ).GetVisual();
+ // get visual info
+ VisualID aVisID = XVisualIDFromVisual( pVisual );
+ XVisualInfo aTemplate;
+ aTemplate.visualid = aVisID;
+ int nVisuals = 0;
+ XVisualInfo* pInfo = XGetVisualInfo( pDisp, VisualIDMask, &aTemplate, &nVisuals );
+ // only one VisualInfo structure can match the visual id
+ SAL_WARN_IF( nVisuals != 1, "vcl", "match count for visual id is not 1" );
+ unsigned int nDepth = pInfo->depth;
+ XFree( pInfo );
+ XSetWindowAttributes aAttribs;
+ aAttribs.event_mask = StructureNotifyMask
+ | ButtonPressMask
+ | ButtonReleaseMask
+ | PointerMotionMask
+ | EnterWindowMask
+ | LeaveWindowMask
+ | FocusChangeMask
+ | ExposureMask
+ ;
+
+ pObject->maPrimary =
+ XCreateSimpleWindow( pDisp,
+ aObjectParent,
+ 0, 0,
+ 1, 1, 0,
+ pSalDisp->GetColormap( nXScreen ).GetBlackPixel(),
+ pSalDisp->GetColormap( nXScreen ).GetWhitePixel()
+ );
+ if( aVisID == pSalDisp->GetVisual( nXScreen ).GetVisualId() )
+ {
+ pObject->maSecondary =
+ XCreateSimpleWindow( pDisp,
+ pObject->maPrimary,
+ 0, 0,
+ 1, 1, 0,
+ pSalDisp->GetColormap( nXScreen ).GetBlackPixel(),
+ pSalDisp->GetColormap( nXScreen ).GetWhitePixel()
+ );
+ }
+ else
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.window", "visual id of vcl "
+ << std::hex
+ << static_cast<unsigned int>
+ (pSalDisp->GetVisual( nXScreen ).GetVisualId())
+ << ", of visual "
+ << static_cast<unsigned int>
+ (aVisID));
+#endif
+ GetGenericUnixSalData()->ErrorTrapPush();
+
+ // create colormap for visual - there might not be one
+ pObject->maColormap = aAttribs.colormap = XCreateColormap(
+ pDisp,
+ pSalDisp->GetRootWindow( nXScreen ),
+ pVisual,
+ AllocNone );
+
+ pObject->maSecondary =
+ XCreateWindow( pDisp,
+ pSalDisp->GetRootWindow( nXScreen ),
+ 0, 0,
+ 1, 1, 0,
+ nDepth, InputOutput,
+ pVisual,
+ CWEventMask|CWColormap, &aAttribs );
+ XSync( pDisp, False );
+ if( GetGenericUnixSalData()->ErrorTrapPop( false ) )
+ {
+ pObject->maSecondary = None;
+ delete pObject;
+ return nullptr;
+ }
+ XReparentWindow( pDisp, pObject->maSecondary, pObject->maPrimary, 0, 0 );
+ }
+
+ GetGenericUnixSalData()->ErrorTrapPush();
+ if( bShow ) {
+ XMapWindow( pDisp, pObject->maSecondary );
+ XMapWindow( pDisp, pObject->maPrimary );
+ }
+
+ pObjData->pDisplay = pDisp;
+ pObjData->SetWindowHandle(pObject->maSecondary);
+ pObjData->pWidget = nullptr;
+ pObjData->pVisual = pVisual;
+
+ XSync(pDisp, False);
+ if( GetGenericUnixSalData()->ErrorTrapPop( false ) )
+ {
+ delete pObject;
+ return nullptr;
+ }
+
+ return pObject;
+}
+
+void X11SalInstance::DestroyObject( SalObject* pObject )
+{
+ delete pObject;
+}
+
+// SalClipRegion is a member of SalObject
+// definition of SalClipRegion my be found in vcl/inc/unx/salobj.h
+
+SalClipRegion::SalClipRegion()
+{
+ ClipRectangleList = nullptr;
+ numClipRectangles = 0;
+ maxClipRectangles = 0;
+}
+
+SalClipRegion::~SalClipRegion()
+{
+}
+
+void
+SalClipRegion::BeginSetClipRegion( sal_uInt32 nRects )
+{
+ ClipRectangleList.reset( new XRectangle[nRects] );
+ numClipRectangles = 0;
+ maxClipRectangles = nRects;
+}
+
+void
+SalClipRegion::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ if ( nWidth && nHeight && (numClipRectangles < maxClipRectangles) )
+ {
+ XRectangle& aRect = ClipRectangleList[numClipRectangles];
+
+ aRect.x = static_cast<short>(nX);
+ aRect.y = static_cast<short>(nY);
+ aRect.width = static_cast<unsigned short>(nWidth);
+ aRect.height= static_cast<unsigned short>(nHeight);
+
+ numClipRectangles++;
+ }
+}
+
+// SalObject Implementation
+X11SalObject::X11SalObject()
+ : mpParent(nullptr)
+ , maParentWin(0)
+ , maPrimary(0)
+ , maSecondary(0)
+ , maColormap(0)
+ , mbVisible(false)
+{
+ maSystemChildData.pDisplay = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay();
+ maSystemChildData.SetWindowHandle(None);
+ maSystemChildData.pSalFrame = nullptr;
+ maSystemChildData.pWidget = nullptr;
+ maSystemChildData.pVisual = nullptr;
+ maSystemChildData.aShellWindow = 0;
+ maSystemChildData.toolkit = SystemEnvData::Toolkit::Gen;
+ maSystemChildData.platform = SystemEnvData::Platform::Xcb;
+
+ std::list< SalObject* >& rObjects = vcl_sal::getSalDisplay(GetGenericUnixSalData())->getSalObjects();
+ rObjects.push_back( this );
+}
+
+X11SalObject::~X11SalObject()
+{
+ std::list< SalObject* >& rObjects = vcl_sal::getSalDisplay(GetGenericUnixSalData())->getSalObjects();
+ rObjects.remove( this );
+
+ GetGenericUnixSalData()->ErrorTrapPush();
+ ::Window aObjectParent = maParentWin;
+ XSetWindowBackgroundPixmap(static_cast<Display*>(maSystemChildData.pDisplay), aObjectParent, None);
+ if ( maSecondary )
+ XDestroyWindow( static_cast<Display*>(maSystemChildData.pDisplay), maSecondary );
+ if ( maPrimary )
+ XDestroyWindow( static_cast<Display*>(maSystemChildData.pDisplay), maPrimary );
+ if ( maColormap )
+ XFreeColormap(static_cast<Display*>(maSystemChildData.pDisplay), maColormap);
+ XSync( static_cast<Display*>(maSystemChildData.pDisplay), False );
+ GetGenericUnixSalData()->ErrorTrapPop();
+}
+
+void
+X11SalObject::ResetClipRegion()
+{
+ maClipRegion.ResetClipRegion();
+
+ const int dest_kind = ShapeBounding;
+ const int op = ShapeSet;
+ const int ordering = YSorted;
+
+ XWindowAttributes win_attrib;
+ XRectangle win_size;
+
+ ::Window aShapeWindow = maPrimary;
+
+ XGetWindowAttributes ( static_cast<Display*>(maSystemChildData.pDisplay),
+ aShapeWindow,
+ &win_attrib );
+
+ win_size.x = 0;
+ win_size.y = 0;
+ win_size.width = win_attrib.width;
+ win_size.height = win_attrib.height;
+
+ XShapeCombineRectangles ( static_cast<Display*>(maSystemChildData.pDisplay),
+ aShapeWindow,
+ dest_kind,
+ 0, 0, // x_off, y_off
+ &win_size, // list of rectangles
+ 1, // number of rectangles
+ op, ordering );
+}
+
+void
+X11SalObject::BeginSetClipRegion( sal_uInt32 nRectCount )
+{
+ maClipRegion.BeginSetClipRegion ( nRectCount );
+}
+
+void
+X11SalObject::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ maClipRegion.UnionClipRegion ( nX, nY, nWidth, nHeight );
+}
+
+void
+X11SalObject::EndSetClipRegion()
+{
+ XRectangle *pRectangles = maClipRegion.EndSetClipRegion ();
+ const int nRectangles = maClipRegion.GetRectangleCount();
+
+ ::Window aShapeWindow = maPrimary;
+
+ XShapeCombineRectangles ( static_cast<Display*>(maSystemChildData.pDisplay),
+ aShapeWindow,
+ ShapeBounding,
+ 0, 0, // x_off, y_off
+ pRectangles,
+ nRectangles,
+ ShapeSet, YSorted );
+}
+
+void
+X11SalObject::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ if ( maPrimary && maSecondary && nWidth && nHeight )
+ {
+ XMoveResizeWindow( static_cast<Display*>(maSystemChildData.pDisplay),
+ maPrimary,
+ nX, nY, nWidth, nHeight );
+ XMoveResizeWindow( static_cast<Display*>(maSystemChildData.pDisplay),
+ maSecondary,
+ 0, 0, nWidth, nHeight );
+ }
+}
+
+void
+X11SalObject::Show( bool bVisible )
+{
+ if (!maSystemChildData.GetWindowHandle(mpParent))
+ return;
+
+ if ( bVisible ) {
+ XMapWindow( static_cast<Display*>(maSystemChildData.pDisplay),
+ maSecondary );
+ XMapWindow( static_cast<Display*>(maSystemChildData.pDisplay),
+ maPrimary );
+ } else {
+ XUnmapWindow( static_cast<Display*>(maSystemChildData.pDisplay),
+ maPrimary );
+ XUnmapWindow( static_cast<Display*>(maSystemChildData.pDisplay),
+ maSecondary );
+ }
+ mbVisible = bVisible;
+}
+
+void X11SalObject::GrabFocus()
+{
+ if( mbVisible )
+ XSetInputFocus( static_cast<Display*>(maSystemChildData.pDisplay),
+ maSystemChildData.GetWindowHandle(mpParent),
+ RevertToNone,
+ CurrentTime );
+}
+
+const SystemEnvData* X11SalObject::GetSystemData() const
+{
+ return &maSystemChildData;
+}
+
+static sal_uInt16 sal_GetCode( int state )
+{
+ sal_uInt16 nCode = 0;
+
+ if( state & Button1Mask )
+ nCode |= MOUSE_LEFT;
+ if( state & Button2Mask )
+ nCode |= MOUSE_MIDDLE;
+ if( state & Button3Mask )
+ nCode |= MOUSE_RIGHT;
+
+ if( state & ShiftMask )
+ nCode |= KEY_SHIFT;
+ if( state & ControlMask )
+ nCode |= KEY_MOD1;
+ if( state & Mod1Mask )
+ nCode |= KEY_MOD2;
+ if( state & Mod3Mask )
+ nCode |= KEY_MOD3;
+
+ return nCode;
+}
+
+bool X11SalObject::Dispatch( XEvent* pEvent )
+{
+ std::list< SalObject* >& rObjects = vcl_sal::getSalDisplay(GetGenericUnixSalData())->getSalObjects();
+
+ for (auto const& elem : rObjects)
+ {
+ X11SalObject* pObject = static_cast<X11SalObject*>(elem);
+ if( pEvent->xany.window == pObject->maPrimary ||
+ pEvent->xany.window == pObject->maSecondary )
+ {
+ if( pObject->IsMouseTransparent() && (
+ pEvent->type == ButtonPress ||
+ pEvent->type == ButtonRelease ||
+ pEvent->type == EnterNotify ||
+ pEvent->type == LeaveNotify ||
+ pEvent->type == MotionNotify
+ )
+ )
+ {
+ SalMouseEvent aEvt;
+ int dest_x, dest_y;
+ ::Window aChild = None;
+ XTranslateCoordinates( pEvent->xbutton.display,
+ pEvent->xbutton.root,
+ pObject->maParentWin,
+ pEvent->xbutton.x_root,
+ pEvent->xbutton.y_root,
+ &dest_x, &dest_y,
+ &aChild );
+ aEvt.mnX = dest_x;
+ aEvt.mnY = dest_y;
+ aEvt.mnTime = pEvent->xbutton.time;
+ aEvt.mnCode = sal_GetCode( pEvent->xbutton.state );
+ aEvt.mnButton = 0;
+ SalEvent nEvent = SalEvent::NONE;
+ if( pEvent->type == ButtonPress ||
+ pEvent->type == ButtonRelease )
+ {
+ switch( pEvent->xbutton.button )
+ {
+ case Button1: aEvt.mnButton = MOUSE_LEFT;break;
+ case Button2: aEvt.mnButton = MOUSE_MIDDLE;break;
+ case Button3: aEvt.mnButton = MOUSE_RIGHT;break;
+ }
+ nEvent = (pEvent->type == ButtonPress) ?
+ SalEvent::MouseButtonDown :
+ SalEvent::MouseButtonUp;
+ }
+ else if( pEvent->type == EnterNotify )
+ nEvent = SalEvent::MouseLeave;
+ else
+ nEvent = SalEvent::MouseMove;
+ pObject->mpParent->CallCallback( nEvent, &aEvt );
+ }
+ else
+ {
+ switch( pEvent->type )
+ {
+ case UnmapNotify:
+ pObject->mbVisible = false;
+ return true;
+ case MapNotify:
+ pObject->mbVisible = true;
+ return true;
+ case ButtonPress:
+ pObject->CallCallback( SalObjEvent::ToTop );
+ return true;
+ case FocusIn:
+ pObject->CallCallback( SalObjEvent::GetFocus );
+ return true;
+ case FocusOut:
+ pObject->CallCallback( SalObjEvent::LoseFocus );
+ return true;
+ default: break;
+ }
+ }
+ return false;
+ }
+ }
+ return false;
+}
+
+void X11SalObject::SetLeaveEnterBackgrounds(const css::uno::Sequence<css::uno::Any>& rLeaveArgs, const css::uno::Sequence<css::uno::Any>& rEnterArgs)
+{
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ Display* pDisp = pSalDisp->GetDisplay();
+ ::Window aObjectParent = maParentWin;
+
+ bool bFreePixmap = false;
+ Pixmap aPixmap = None;
+ if (rEnterArgs.getLength() == 2)
+ {
+ rEnterArgs[0] >>= bFreePixmap;
+ sal_Int64 pixmapHandle = None;
+ rEnterArgs[1] >>= pixmapHandle;
+ aPixmap = pixmapHandle;
+ }
+
+ XSetWindowBackgroundPixmap(pDisp, aObjectParent, aPixmap);
+ if (bFreePixmap)
+ XFreePixmap(pDisp, aPixmap);
+
+ bFreePixmap = false;
+ aPixmap = None;
+ if (rLeaveArgs.getLength() == 2)
+ {
+ rLeaveArgs[0] >>= bFreePixmap;
+ sal_Int64 pixmapHandle = None;
+ rLeaveArgs[1] >>= pixmapHandle;
+ aPixmap = pixmapHandle;
+ }
+
+ XSetWindowBackgroundPixmap(pDisp, maSecondary, aPixmap);
+ if (bFreePixmap)
+ XFreePixmap(pDisp, aPixmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/window/sessioninhibitor.cxx b/vcl/unx/generic/window/sessioninhibitor.cxx
new file mode 100644
index 0000000000..300df9ff80
--- /dev/null
+++ b/vcl/unx/generic/window/sessioninhibitor.cxx
@@ -0,0 +1,370 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <functional>
+
+#include <unx/gensys.h>
+#include <unx/sessioninhibitor.hxx>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#if !defined(__sun)
+#include <X11/extensions/dpms.h>
+#endif
+
+#include <config_gio.h>
+
+#if ENABLE_GIO
+#include <gio/gio.h>
+
+#define FDOSS_DBUS_SERVICE "org.freedesktop.ScreenSaver"
+#define FDOSS_DBUS_PATH "/org/freedesktop/ScreenSaver"
+#define FDOSS_DBUS_INTERFACE "org.freedesktop.ScreenSaver"
+
+#define FDOPM_DBUS_SERVICE "org.freedesktop.PowerManagement.Inhibit"
+#define FDOPM_DBUS_PATH "/org/freedesktop/PowerManagement/Inhibit"
+#define FDOPM_DBUS_INTERFACE "org.freedesktop.PowerManagement.Inhibit"
+
+#define GSM_DBUS_SERVICE "org.gnome.SessionManager"
+#define GSM_DBUS_PATH "/org/gnome/SessionManager"
+#define GSM_DBUS_INTERFACE "org.gnome.SessionManager"
+
+// Mate <= 1.10 uses org.mate.SessionManager, > 1.10 will use org.gnome.SessionManager
+#define MSM_DBUS_SERVICE "org.mate.SessionManager"
+#define MSM_DBUS_PATH "/org/mate/SessionManager"
+#define MSM_DBUS_INTERFACE "org.mate.SessionManager"
+#endif
+
+#include <sal/log.hxx>
+
+void SessionManagerInhibitor::inhibit(bool bInhibit, std::u16string_view sReason, ApplicationInhibitFlags eType,
+ unsigned int window_system_id, std::optional<Display*> pDisplay,
+ const char* application_id)
+{
+ const char* appname = application_id ? application_id : SalGenericSystem::getFrameClassName();
+ const OString aReason = OUStringToOString( sReason, RTL_TEXTENCODING_UTF8 );
+
+ if (eType == APPLICATION_INHIBIT_IDLE)
+ {
+ inhibitFDOSS( bInhibit, appname, aReason.getStr() );
+ inhibitFDOPM( bInhibit, appname, aReason.getStr() );
+ }
+
+ if (eType == APPLICATION_INHIBIT_IDLE && pDisplay)
+ {
+ inhibitXScreenSaver( bInhibit, *pDisplay );
+ inhibitXAutoLock( bInhibit, *pDisplay );
+ inhibitDPMS( bInhibit, *pDisplay );
+ }
+
+ inhibitGSM(bInhibit, appname, aReason.getStr(), eType, window_system_id);
+ inhibitMSM(bInhibit, appname, aReason.getStr(), eType, window_system_id);
+}
+
+#if ENABLE_GIO
+static void dbusInhibit( bool bInhibit,
+ const gchar* service, const gchar* path, const gchar* interface,
+ const std::function<GVariant*( GDBusProxy*, GError*& )>& fInhibit,
+ const std::function<GVariant*( GDBusProxy*, const guint, GError*& )>& fUnInhibit,
+ std::optional<guint>& rCookie )
+{
+ if ( ( !bInhibit && !rCookie ) ||
+ ( bInhibit && rCookie ) )
+ {
+ return;
+ }
+
+ GError *error = nullptr;
+ GDBusConnection *session_connection = g_bus_get_sync( G_BUS_TYPE_SESSION, nullptr, &error );
+ if (session_connection == nullptr) {
+ SAL_WARN( "vcl.sessioninhibitor", "failed to connect to dbus session bus" );
+
+ if (error != nullptr) {
+ SAL_WARN( "vcl.sessioninhibitor", "Error: " << error->message );
+ g_error_free( error );
+ }
+
+ return;
+ }
+
+ GDBusProxy *proxy = g_dbus_proxy_new_sync( session_connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ nullptr,
+ service,
+ path,
+ interface,
+ nullptr,
+ nullptr );
+
+ g_object_unref( G_OBJECT( session_connection ) );
+
+ if (proxy == nullptr) {
+ SAL_INFO( "vcl.sessioninhibitor", "could not get dbus proxy: " << service );
+ return;
+ }
+
+ GVariant *res = nullptr;
+
+ if ( bInhibit )
+ {
+ res = fInhibit( proxy, error );
+
+ if (res != nullptr)
+ {
+ guint nCookie;
+
+ g_variant_get(res, "(u)", &nCookie);
+ g_variant_unref(res);
+
+ rCookie = nCookie;
+ }
+ else
+ {
+ SAL_INFO( "vcl.sessioninhibitor", service << ".Inhibit failed");
+ }
+ }
+ else
+ {
+ res = fUnInhibit( proxy, *rCookie, error );
+ rCookie.reset();
+
+ if (res != nullptr)
+ {
+ g_variant_unref(res);
+ }
+ else
+ {
+ SAL_INFO( "vcl.sessioninhibitor", service << ".UnInhibit failed" );
+ }
+ }
+
+ if (error != nullptr)
+ {
+ SAL_INFO( "vcl.sessioninhibitor", "Error: " << error->message );
+ g_error_free( error );
+ }
+
+ g_object_unref( G_OBJECT( proxy ) );
+}
+#endif // ENABLE_GIO
+
+void SessionManagerInhibitor::inhibitFDOSS( bool bInhibit, const char* appname, const char* reason )
+{
+#if ENABLE_GIO
+ dbusInhibit( bInhibit,
+ FDOSS_DBUS_SERVICE, FDOSS_DBUS_PATH, FDOSS_DBUS_INTERFACE,
+ [appname, reason] ( GDBusProxy *proxy, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "Inhibit",
+ g_variant_new("(ss)", appname, reason),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error );
+ },
+ [] ( GDBusProxy *proxy, const guint nCookie, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "UnInhibit",
+ g_variant_new("(u)", nCookie),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error );
+ },
+ mnFDOSSCookie );
+#else
+ (void) this;
+ (void) bInhibit;
+ (void) appname;
+ (void) reason;
+#endif // ENABLE_GIO
+}
+
+void SessionManagerInhibitor::inhibitFDOPM( bool bInhibit, const char* appname, const char* reason )
+{
+#if ENABLE_GIO
+ dbusInhibit( bInhibit,
+ FDOPM_DBUS_SERVICE, FDOPM_DBUS_PATH, FDOPM_DBUS_INTERFACE,
+ [appname, reason] ( GDBusProxy *proxy, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "Inhibit",
+ g_variant_new("(ss)", appname, reason),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error );
+ },
+ [] ( GDBusProxy *proxy, const guint nCookie, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "UnInhibit",
+ g_variant_new("(u)", nCookie),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error );
+ },
+ mnFDOPMCookie );
+#else
+ (void) this;
+ (void) bInhibit;
+ (void) appname;
+ (void) reason;
+#endif // ENABLE_GIO
+}
+
+void SessionManagerInhibitor::inhibitGSM( bool bInhibit, const char* appname, const char* reason, ApplicationInhibitFlags eType, unsigned int window_system_id )
+{
+#if ENABLE_GIO
+ dbusInhibit( bInhibit,
+ GSM_DBUS_SERVICE, GSM_DBUS_PATH, GSM_DBUS_INTERFACE,
+ [appname, reason, eType, window_system_id] ( GDBusProxy *proxy, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "Inhibit",
+ g_variant_new("(susu)",
+ appname,
+ window_system_id,
+ reason,
+ eType
+ ),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error );
+ },
+ [] ( GDBusProxy *proxy, const guint nCookie, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "Uninhibit",
+ g_variant_new("(u)", nCookie),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error );
+ },
+ mnGSMCookie );
+#else
+ (void) this;
+ (void) bInhibit;
+ (void) appname;
+ (void) reason;
+ (void) eType;
+ (void) window_system_id;
+#endif // ENABLE_GIO
+}
+
+void SessionManagerInhibitor::inhibitMSM( bool bInhibit, const char* appname, const char* reason, ApplicationInhibitFlags eType, unsigned int window_system_id )
+{
+#if ENABLE_GIO
+ dbusInhibit( bInhibit,
+ MSM_DBUS_SERVICE, MSM_DBUS_PATH, MSM_DBUS_INTERFACE,
+ [appname, reason, eType, window_system_id] ( GDBusProxy *proxy, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "Inhibit",
+ g_variant_new("(susu)",
+ appname,
+ window_system_id,
+ reason,
+ eType
+ ),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error );
+ },
+ [] ( GDBusProxy *proxy, const guint nCookie, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "Uninhibit",
+ g_variant_new("(u)", nCookie),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error );
+ },
+ mnMSMCookie );
+#else
+ (void) this;
+ (void) bInhibit;
+ (void) appname;
+ (void) reason;
+ (void) eType;
+ (void) window_system_id;
+#endif // ENABLE_GIO
+}
+
+/**
+ * Disable screensavers using the XSetScreenSaver/XGetScreenSaver API.
+ *
+ * Worth noting: xscreensaver explicitly ignores this and does its own
+ * timeout handling.
+ */
+void SessionManagerInhibitor::inhibitXScreenSaver( bool bInhibit, Display* pDisplay )
+{
+ int nTimeout, nInterval, bPreferBlanking, bAllowExposures;
+ XGetScreenSaver( pDisplay, &nTimeout, &nInterval,
+ &bPreferBlanking, &bAllowExposures );
+
+ // To disable/reenable we simply fiddle the timeout, whilst
+ // retaining all other properties.
+ if ( bInhibit && nTimeout)
+ {
+ mnXScreenSaverTimeout = nTimeout;
+ XResetScreenSaver( pDisplay );
+ XSetScreenSaver( pDisplay, 0, nInterval,
+ bPreferBlanking, bAllowExposures );
+ }
+ else if ( !bInhibit && mnXScreenSaverTimeout )
+ {
+ XSetScreenSaver( pDisplay, *mnXScreenSaverTimeout,
+ nInterval, bPreferBlanking,
+ bAllowExposures );
+ mnXScreenSaverTimeout.reset();
+ }
+}
+
+
+/* definitions from xautolock.c (pl15) */
+#define XAUTOLOCK_DISABLE 1
+#define XAUTOLOCK_ENABLE 2
+
+void SessionManagerInhibitor::inhibitXAutoLock( bool bInhibit, Display* pDisplay )
+{
+ ::Window aRootWindow = RootWindowOfScreen( ScreenOfDisplay( pDisplay, 0 ) );
+
+ Atom nAtom = XInternAtom( pDisplay,
+ "XAUTOLOCK_MESSAGE",
+ False );
+
+ if ( nAtom == None )
+ {
+ return;
+ }
+
+ int nMessage = bInhibit ? XAUTOLOCK_DISABLE : XAUTOLOCK_ENABLE;
+
+ XChangeProperty( pDisplay,
+ aRootWindow,
+ nAtom,
+ XA_INTEGER,
+ 8, // format -- 8 bit quantity
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>( &nMessage ),
+ sizeof( nMessage ) );
+}
+
+void SessionManagerInhibitor::inhibitDPMS( bool bInhibit, Display* pDisplay )
+{
+#if !defined(__sun)
+ int dummy;
+ // This won't change while X11 is running, hence
+ // we can evaluate only once and store as static
+ static bool bDPMSExtensionAvailable = ( DPMSQueryExtension( pDisplay, &dummy, &dummy) != 0 );
+
+ if ( !bDPMSExtensionAvailable )
+ {
+ return;
+ }
+
+ if ( bInhibit )
+ {
+ CARD16 state; // unused by us
+ DPMSInfo( pDisplay, &state, &mbDPMSWasEnabled );
+
+ if ( mbDPMSWasEnabled )
+ {
+ DPMSGetTimeouts( pDisplay,
+ &mnDPMSStandbyTimeout,
+ &mnDPMSSuspendTimeout,
+ &mnDPMSOffTimeout );
+ DPMSSetTimeouts( pDisplay,
+ 0,
+ 0,
+ 0 );
+ }
+ }
+ else if ( !bInhibit && mbDPMSWasEnabled )
+ {
+ DPMSSetTimeouts( pDisplay,
+ mnDPMSStandbyTimeout,
+ mnDPMSSuspendTimeout,
+ mnDPMSOffTimeout );
+ }
+#endif // !defined(__sun)
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/TODO b/vcl/unx/gtk3/a11y/TODO
new file mode 100644
index 0000000000..1048bd96ef
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/TODO
@@ -0,0 +1,49 @@
+cws 'atkbridge'
+#Issue number: i#47890#
+Submitted by: mmeeks
+
+Hacked up prototype of atk bridge
+
+
+Serious problems
+ + Threading/locking:
+ + incoming CORBA calls & the GDK lock
+ + how are these being processed & on what thread ?
+ + are we holding the GDK_THREADS lock ?
+ + can we even do that ?
+ + is it really necessary to be thread safe ?
+ + how does this work in combination with the (unsafe) GAIL code ?
+ + what should incoming CORBA calls be doing ?
+ + esp. since we can't tell if they're coming from
+ in-proc or not either [ though this is unlikely ]
+
+
+Test:
+ + in-line text editing, does the TEXT_CHANGED signal get it right,
+ + why not copy/paste/delete etc. ?
+ + check vs. writer & other bits ...
+ + AtkSelection
+ + AtkHyper*
+
+* At-poke
+ + implement non-gui mode - for to-console event logging
+ + logging
+ + more detail from remaining events
+ + add a Tree navigation thing instead (?)
+ + poke a sub-child (?)
+ + embed a tree widget inside the tree view ?
+ + AtkHyperText testing (?)
+
+
+Known bugs:
+ + AtkText
+ + selection interface - multiple selections ?
+ + word boundary issues
+ + copy AccessibleTextImpl.java's getAfterIndex eg.
+ + the 'getFoo' methods need to use UNO_QUERY_THROW &
+ throw an exception to avoid null pointer dereferences.
+ + AtkAttributeSet (etc.)
+ + AtkEditableText
+ + finish/test AtkTable
+ + HyperLink 'link_activated', HyperText 'link_selected' (?)
+ + tooltips create new toplevels with broken roles.
diff --git a/vcl/unx/gtk3/a11y/atkaction.cxx b/vcl/unx/gtk3/a11y/atkaction.cxx
new file mode 100644
index 0000000000..0a39d3bdf9
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkaction.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 "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleAction.hpp>
+#include <com/sun/star/accessibility/XAccessibleKeyBinding.hpp>
+
+#include <com/sun/star/awt/Key.hpp>
+#include <com/sun/star/awt/KeyModifier.hpp>
+
+#include <rtl/strbuf.hxx>
+#include <algorithm>
+#include <map>
+
+using namespace ::com::sun::star;
+
+// FIXME
+static const gchar *
+getAsConst( const OString& rString )
+{
+ static const int nMax = 10;
+ static OString aUgly[nMax];
+ static int nIdx = 0;
+ nIdx = (nIdx + 1) % nMax;
+ aUgly[nIdx] = rString;
+ return aUgly[ nIdx ].getStr();
+}
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleAction>
+ getAction( AtkAction *action )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( action );
+
+ if( pWrap )
+ {
+ if( !pWrap->mpAction.is() )
+ {
+ pWrap->mpAction.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpAction;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleAction>();
+}
+
+extern "C" {
+
+static gboolean
+action_wrapper_do_action (AtkAction *action,
+ gint i)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleAction> pAction
+ = getAction( action );
+ if( pAction.is() )
+ return pAction->doAccessibleAction( i );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in doAccessibleAction()" );
+ }
+
+ return FALSE;
+}
+
+static gint
+action_wrapper_get_n_actions (AtkAction *action)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleAction> pAction
+ = getAction( action );
+ if( pAction.is() )
+ return pAction->getAccessibleActionCount();
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleActionCount()" );
+ }
+
+ return 0;
+}
+
+static const gchar *
+action_wrapper_get_description (AtkAction *, gint)
+{
+ // GAIL implement this only for cells
+ g_warning( "Not implemented: get_description()" );
+ return "";
+}
+
+static const gchar *
+action_wrapper_get_localized_name (AtkAction *, gint)
+{
+ // GAIL doesn't implement this as well
+ g_warning( "Not implemented: get_localized_name()" );
+ return "";
+}
+
+static const gchar *
+action_wrapper_get_name (AtkAction *action,
+ gint i)
+{
+ static std::map< OUString, const gchar * > aNameMap {
+ { "click", "click" },
+ { "select", "click" } ,
+ { "togglePopup", "push" }
+ };
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleAction> pAction
+ = getAction( action );
+ if( pAction.is() )
+ {
+ std::map< OUString, const gchar * >::iterator iter;
+
+ OUString aDesc( pAction->getAccessibleActionDescription( i ) );
+
+ iter = aNameMap.find( aDesc );
+ if( iter != aNameMap.end() )
+ return iter->second;
+
+ std::pair< const OUString, const gchar * > aNewVal( aDesc,
+ g_strdup( OUStringToConstGChar(aDesc) ) );
+
+ if( aNameMap.insert( aNewVal ).second )
+ return aNewVal.second;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleActionDescription()" );
+ }
+
+ return "";
+}
+
+/*
+* GNOME Expects a string in the format:
+*
+* <mnemonic>;<full-path>;<accelerator>
+*
+* The keybindings in <full-path> should be separated by ":"
+*/
+
+static void
+appendKeyStrokes(OStringBuffer& rBuffer, const uno::Sequence< awt::KeyStroke >& rKeyStrokes)
+{
+ for( const auto& rKeyStroke : rKeyStrokes )
+ {
+ if( rKeyStroke.Modifiers & awt::KeyModifier::SHIFT )
+ rBuffer.append("<Shift>");
+ if( rKeyStroke.Modifiers & awt::KeyModifier::MOD1 )
+ rBuffer.append("<Control>");
+ if( rKeyStroke.Modifiers & awt::KeyModifier::MOD2 )
+ rBuffer.append("<Alt>");
+
+ if( ( rKeyStroke.KeyCode >= awt::Key::A ) && ( rKeyStroke.KeyCode <= awt::Key::Z ) )
+ rBuffer.append( static_cast<char>( 'a' + ( rKeyStroke.KeyCode - awt::Key::A ) ) );
+ else
+ {
+ char c = '\0';
+
+ switch( rKeyStroke.KeyCode )
+ {
+ case awt::Key::TAB: c = '\t'; break;
+ case awt::Key::SPACE: c = ' '; break;
+ case awt::Key::ADD: c = '+'; break;
+ case awt::Key::SUBTRACT: c = '-'; break;
+ case awt::Key::MULTIPLY: c = '*'; break;
+ case awt::Key::DIVIDE: c = '/'; break;
+ case awt::Key::POINT: c = '.'; break;
+ case awt::Key::COMMA: c = ','; break;
+ case awt::Key::LESS: c = '<'; break;
+ case awt::Key::GREATER: c = '>'; break;
+ case awt::Key::EQUAL: c = '='; break;
+ case 0:
+ break;
+ default:
+ g_warning( "Unmapped KeyCode: %d", rKeyStroke.KeyCode );
+ break;
+ }
+
+ if( c != '\0' )
+ rBuffer.append( c );
+ else
+ {
+ // The KeyCode approach did not work, probably a non ascii character
+ // let's hope that there is a character given in KeyChar.
+ rBuffer.append(OUStringToOString(OUStringChar(rKeyStroke.KeyChar), RTL_TEXTENCODING_UTF8));
+ }
+ }
+ }
+}
+
+static const gchar *
+action_wrapper_get_keybinding (AtkAction *action,
+ gint i)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleAction> pAction
+ = getAction( action );
+ if( pAction.is() )
+ {
+ uno::Reference< accessibility::XAccessibleKeyBinding > xBinding( pAction->getAccessibleActionKeyBinding( i ));
+
+ if( xBinding.is() )
+ {
+ OStringBuffer aRet;
+
+ sal_Int32 nmax = std::min( xBinding->getAccessibleKeyBindingCount(), sal_Int32(3) );
+ for( sal_Int32 n = 0; n < nmax; n++ )
+ {
+ appendKeyStrokes( aRet, xBinding->getAccessibleKeyBinding( n ) );
+
+ if( n < 2 )
+ aRet.append( ';' );
+ }
+
+ // !! FIXME !! remember keystroke in wrapper object ?
+ return getAsConst( aRet.makeStringAndClear() );
+ }
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in get_keybinding()" );
+ }
+
+ return "";
+}
+
+static gboolean
+action_wrapper_set_description (AtkAction *, gint, const gchar *)
+{
+ return FALSE;
+}
+
+} // extern "C"
+
+void
+actionIfaceInit (AtkActionIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->do_action = action_wrapper_do_action;
+ iface->get_n_actions = action_wrapper_get_n_actions;
+ iface->get_description = action_wrapper_get_description;
+ iface->get_keybinding = action_wrapper_get_keybinding;
+ iface->get_name = action_wrapper_get_name;
+ iface->get_localized_name = action_wrapper_get_localized_name;
+ iface->set_description = action_wrapper_set_description;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkbridge.cxx b/vcl/unx/gtk3/a11y/atkbridge.cxx
new file mode 100644
index 0000000000..c7cb32ca3c
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkbridge.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unx/gtk/atkbridge.hxx>
+
+#include "atkutil.hxx"
+
+bool InitAtkBridge()
+{
+ ooo_atk_util_ensure_event_listener();
+ return true;
+}
+
+void DeInitAtkBridge() {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkcomponent.cxx b/vcl/unx/gtk3/a11y/atkcomponent.cxx
new file mode 100644
index 0000000000..154f0117f1
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkcomponent.cxx
@@ -0,0 +1,431 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "atkwrapper.hxx"
+#include <com/sun/star/accessibility/XAccessibleComponent.hpp>
+#include <sal/log.hxx>
+#include <gtk/gtk.h>
+
+using namespace ::com::sun::star;
+
+static AtkObjectWrapper* getObjectWrapper(AtkComponent *pComponent)
+{
+ AtkObjectWrapper *pWrap = nullptr;
+ if (ATK_IS_OBJECT_WRAPPER(pComponent))
+ pWrap = ATK_OBJECT_WRAPPER(pComponent);
+ else if (GTK_IS_DRAWING_AREA(pComponent)) //when using a GtkDrawingArea as a custom widget in welded gtk3
+ {
+ GtkWidget* pDrawingArea = GTK_WIDGET(pComponent);
+ AtkObject* pAtkObject = gtk_widget_get_accessible(pDrawingArea);
+ pWrap = ATK_IS_OBJECT_WRAPPER(pAtkObject) ? ATK_OBJECT_WRAPPER(pAtkObject) : nullptr;
+ }
+ return pWrap;
+}
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleComponent>
+ getComponent(AtkObjectWrapper *pWrap)
+{
+ if (pWrap)
+ {
+ if (!pWrap->mpComponent.is())
+ pWrap->mpComponent.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ return pWrap->mpComponent;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleComponent>();
+}
+
+static awt::Point
+lcl_getLocationInWindow(AtkComponent* pAtkComponent,
+ css::uno::Reference<accessibility::XAccessibleComponent> const& xComponent)
+{
+ // calculate position in window by adding the component's position in the parent
+ // to the parent's position in the window (unless parent is a window itself)
+ awt::Point aPos = xComponent->getLocation();
+ AtkObject* pParent = atk_object_get_parent(ATK_OBJECT(pAtkComponent));
+ if (ATK_IS_COMPONENT(pParent) && pParent->role != AtkRole::ATK_ROLE_DIALOG
+ && pParent->role != AtkRole::ATK_ROLE_FILE_CHOOSER
+ && pParent->role != AtkRole::ATK_ROLE_FRAME
+ && pParent->role != AtkRole::ATK_ROLE_WINDOW)
+ {
+ int nX;
+ int nY;
+ atk_component_get_extents(ATK_COMPONENT(pParent), &nX, &nY, nullptr, nullptr, ATK_XY_WINDOW);
+ aPos.X += nX;
+ aPos.Y += nY;
+ }
+
+ return aPos;
+}
+
+/*****************************************************************************/
+
+static awt::Point
+translatePoint( AtkComponent* pAtkComponent,
+ css::uno::Reference<accessibility::XAccessibleComponent> const & pComponent,
+ gint x, gint y, AtkCoordType t)
+{
+ awt::Point aOrigin( 0, 0 );
+ if( t == ATK_XY_SCREEN )
+ aOrigin = pComponent->getLocationOnScreen();
+ else if (t == ATK_XY_WINDOW)
+ aOrigin = lcl_getLocationInWindow(pAtkComponent, pComponent);
+ return awt::Point( x - aOrigin.X, y - aOrigin.Y );
+}
+
+/*****************************************************************************/
+
+extern "C" {
+
+static gboolean
+component_wrapper_grab_focus (AtkComponent *component)
+{
+ AtkObjectWrapper* obj = getObjectWrapper(component);
+ //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
+ if (obj && obj->mpOrig)
+ return atk_component_grab_focus(ATK_COMPONENT(obj->mpOrig));
+
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
+ = getComponent(obj);
+ if( pComponent.is() )
+ {
+ pComponent->grabFocus();
+ return true;
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ g_warning( "Exception in grabFocus()" );
+ }
+
+ return FALSE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+component_wrapper_contains (AtkComponent *component,
+ gint x,
+ gint y,
+ AtkCoordType coord_type)
+{
+ AtkObjectWrapper* obj = getObjectWrapper(component);
+ //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
+ if (obj && obj->mpOrig)
+ return atk_component_contains(ATK_COMPONENT(obj->mpOrig), x, y, coord_type);
+
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
+ = getComponent(obj);
+ if( pComponent.is() )
+ return pComponent->containsPoint(
+ translatePoint(component, pComponent, x, y, coord_type));
+ }
+ catch( const uno::Exception & )
+ {
+ g_warning( "Exception in containsPoint()" );
+ }
+
+ return FALSE;
+}
+
+/*****************************************************************************/
+
+static AtkObject *
+component_wrapper_ref_accessible_at_point (AtkComponent *component,
+ gint x,
+ gint y,
+ AtkCoordType coord_type)
+{
+ AtkObjectWrapper* obj = getObjectWrapper(component);
+ //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
+ if (obj && obj->mpOrig)
+ return atk_component_ref_accessible_at_point(ATK_COMPONENT(obj->mpOrig), x, y, coord_type);
+
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
+ = getComponent(obj);
+
+ if( pComponent.is() )
+ {
+ uno::Reference< accessibility::XAccessible > xAccessible = pComponent->getAccessibleAtPoint(
+ translatePoint(component, pComponent, x, y, coord_type));
+ return atk_object_wrapper_ref( xAccessible );
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ g_warning( "Exception in getAccessibleAtPoint()" );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+static void
+component_wrapper_get_position (AtkComponent *component,
+ gint *x,
+ gint *y,
+ AtkCoordType coord_type)
+{
+ AtkObjectWrapper* obj = getObjectWrapper(component);
+ //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
+ if (obj && obj->mpOrig)
+ {
+ atk_component_get_extents(ATK_COMPONENT(obj->mpOrig), x, y, nullptr, nullptr, coord_type);
+ return;
+ }
+
+ *x = *y = -1;
+
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
+ = getComponent(obj);
+ if( pComponent.is() )
+ {
+ awt::Point aPos;
+
+ if( coord_type == ATK_XY_SCREEN )
+ aPos = pComponent->getLocationOnScreen();
+ else if (coord_type == ATK_XY_WINDOW)
+ aPos = lcl_getLocationInWindow(component, pComponent);
+#if ATK_CHECK_VERSION(2, 30, 0)
+ else if (coord_type == ATK_XY_PARENT)
+#else
+ // ATK_XY_PARENT added in ATK 2.30, so can't use the constant here
+ else
+#endif
+ aPos = pComponent->getLocation();
+#if ATK_CHECK_VERSION(2, 30, 0)
+ else
+ {
+ SAL_WARN("vcl.gtk",
+ "component_wrapper_get_position called with unknown AtkCoordType "
+ << coord_type);
+ return;
+ }
+#endif
+
+ *x = aPos.X;
+ *y = aPos.Y;
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ g_warning( "Exception in getLocation[OnScreen]()" );
+ }
+}
+
+/*****************************************************************************/
+
+static void
+component_wrapper_get_size (AtkComponent *component,
+ gint *width,
+ gint *height)
+{
+ AtkObjectWrapper* obj = getObjectWrapper(component);
+ //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
+ if (obj && obj->mpOrig)
+ {
+ atk_component_get_extents(ATK_COMPONENT(obj->mpOrig), nullptr, nullptr, width, height, ATK_XY_WINDOW);
+ return;
+ }
+
+ *width = *height = -1;
+
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
+ = getComponent(obj);
+ if( pComponent.is() )
+ {
+ awt::Size aSize = pComponent->getSize();
+ *width = aSize.Width;
+ *height = aSize.Height;
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ g_warning( "Exception in getSize()" );
+ }
+}
+
+/*****************************************************************************/
+
+static void
+component_wrapper_get_extents (AtkComponent *component,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height,
+ AtkCoordType coord_type)
+{
+ component_wrapper_get_position( component, x, y, coord_type );
+ component_wrapper_get_size( component, width, height );
+}
+
+/*****************************************************************************/
+
+static gboolean
+component_wrapper_set_extents (AtkComponent *, gint, gint, gint, gint, AtkCoordType)
+{
+ g_warning( "AtkComponent::set_extents unimplementable" );
+ return FALSE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+component_wrapper_set_position (AtkComponent *, gint, gint, AtkCoordType)
+{
+ g_warning( "AtkComponent::set_position unimplementable" );
+ return FALSE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+component_wrapper_set_size (AtkComponent *, gint, gint)
+{
+ g_warning( "AtkComponent::set_size unimplementable" );
+ return FALSE;
+}
+
+/*****************************************************************************/
+
+static AtkLayer
+component_wrapper_get_layer (AtkComponent *component)
+{
+ AtkRole role = atk_object_get_role( ATK_OBJECT( component ) );
+ AtkLayer layer = ATK_LAYER_WIDGET;
+
+ switch (role)
+ {
+ case ATK_ROLE_POPUP_MENU:
+ case ATK_ROLE_MENU_ITEM:
+ case ATK_ROLE_CHECK_MENU_ITEM:
+ case ATK_ROLE_SEPARATOR:
+ case ATK_ROLE_LIST_ITEM:
+ layer = ATK_LAYER_POPUP;
+ break;
+ case ATK_ROLE_MENU:
+ {
+ AtkObject * parent = atk_object_get_parent( ATK_OBJECT( component ) );
+ if( atk_object_get_role( parent ) != ATK_ROLE_MENU_BAR )
+ layer = ATK_LAYER_POPUP;
+ }
+ break;
+
+ case ATK_ROLE_LIST:
+ {
+ AtkObject * parent = atk_object_get_parent( ATK_OBJECT( component ) );
+ if( atk_object_get_role( parent ) == ATK_ROLE_COMBO_BOX )
+ layer = ATK_LAYER_POPUP;
+ }
+ break;
+
+ default:
+ ;
+ }
+
+ return layer;
+}
+
+/*****************************************************************************/
+
+static gint
+component_wrapper_get_mdi_zorder (AtkComponent *)
+{
+ // only needed for ATK_LAYER_MDI (not used) or ATK_LAYER_WINDOW (inherited from GAIL)
+ return G_MININT;
+}
+
+/*****************************************************************************/
+
+// This code is mostly stolen from libgail ..
+
+static guint
+component_wrapper_add_focus_handler (AtkComponent *component,
+ AtkFocusHandler handler)
+{
+ GSignalMatchType match_type;
+ gulong ret;
+ guint signal_id;
+
+ match_type = GSignalMatchType(G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_FUNC);
+ signal_id = g_signal_lookup( "focus-event", ATK_TYPE_OBJECT );
+
+ ret = g_signal_handler_find( component, match_type, signal_id, 0, nullptr,
+ static_cast<gpointer>(&handler), nullptr);
+ if (!ret)
+ {
+ return g_signal_connect_closure_by_id (component,
+ signal_id, 0,
+ g_cclosure_new (
+ G_CALLBACK (handler), nullptr,
+ nullptr),
+ FALSE);
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+/*****************************************************************************/
+
+static void
+component_wrapper_remove_focus_handler (AtkComponent *component,
+ guint handler_id)
+{
+ g_signal_handler_disconnect (component, handler_id);
+}
+
+/*****************************************************************************/
+
+} // extern "C"
+
+void
+componentIfaceInit (AtkComponentIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->add_focus_handler = component_wrapper_add_focus_handler;
+ iface->contains = component_wrapper_contains;
+ iface->get_extents = component_wrapper_get_extents;
+ iface->get_layer = component_wrapper_get_layer;
+ iface->get_mdi_zorder = component_wrapper_get_mdi_zorder;
+ iface->get_position = component_wrapper_get_position;
+ iface->get_size = component_wrapper_get_size;
+ iface->grab_focus = component_wrapper_grab_focus;
+ iface->ref_accessible_at_point = component_wrapper_ref_accessible_at_point;
+ iface->remove_focus_handler = component_wrapper_remove_focus_handler;
+ iface->set_extents = component_wrapper_set_extents;
+ iface->set_position = component_wrapper_set_position;
+ iface->set_size = component_wrapper_set_size;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkeditabletext.cxx b/vcl/unx/gtk3/a11y/atkeditabletext.cxx
new file mode 100644
index 0000000000..f240c32324
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkeditabletext.cxx
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "atkwrapper.hxx"
+#include "atktextattributes.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+
+#include <string.h>
+
+using namespace ::com::sun::star;
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ getEditableText( AtkEditableText *pEditableText )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pEditableText );
+ if( pWrap )
+ {
+ if( !pWrap->mpEditableText.is() )
+ {
+ pWrap->mpEditableText.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpEditableText;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleEditableText>();
+}
+
+/*****************************************************************************/
+
+extern "C" {
+
+static gboolean
+editable_text_wrapper_set_run_attributes( AtkEditableText *text,
+ AtkAttributeSet *attribute_set,
+ gint nStartOffset,
+ gint nEndOffset)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ pEditableText = getEditableText( text );
+ if( pEditableText.is() )
+ {
+ uno::Sequence< beans::PropertyValue > aAttributeList;
+
+ if( attribute_set_map_to_property_values( attribute_set, aAttributeList ) )
+ return pEditableText->setAttributes(nStartOffset, nEndOffset, aAttributeList);
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in setAttributes()" );
+ }
+
+ return FALSE;
+}
+
+static void
+editable_text_wrapper_set_text_contents( AtkEditableText *text,
+ const gchar *string )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ pEditableText = getEditableText( text );
+ if( pEditableText.is() )
+ {
+ OUString aString ( string, strlen(string), RTL_TEXTENCODING_UTF8 );
+ pEditableText->setText( aString );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in setText()" );
+ }
+}
+
+static void
+editable_text_wrapper_insert_text( AtkEditableText *text,
+ const gchar *string,
+ gint length,
+ gint *pos )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ pEditableText = getEditableText( text );
+ if( pEditableText.is() )
+ {
+ OUString aString ( string, length, RTL_TEXTENCODING_UTF8 );
+ if( pEditableText->insertText( aString, *pos ) )
+ *pos += length;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in insertText()" );
+ }
+}
+
+static void
+editable_text_wrapper_cut_text( AtkEditableText *text,
+ gint start,
+ gint end )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ pEditableText = getEditableText( text );
+ if( pEditableText.is() )
+ pEditableText->cutText( start, end );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in cutText()" );
+ }
+}
+
+static void
+editable_text_wrapper_delete_text( AtkEditableText *text,
+ gint start,
+ gint end )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ pEditableText = getEditableText( text );
+ if( pEditableText.is() )
+ pEditableText->deleteText( start, end );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in deleteText()" );
+ }
+}
+
+static void
+editable_text_wrapper_paste_text( AtkEditableText *text,
+ gint pos )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ pEditableText = getEditableText( text );
+ if( pEditableText.is() )
+ pEditableText->pasteText( pos );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in pasteText()" );
+ }
+}
+
+static void
+editable_text_wrapper_copy_text( AtkEditableText *text,
+ gint start,
+ gint end )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ pEditableText = getEditableText( text );
+ if( pEditableText.is() )
+ pEditableText->copyText( start, end );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in copyText()" );
+ }
+}
+
+} // extern "C"
+
+void
+editableTextIfaceInit (AtkEditableTextIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->set_text_contents = editable_text_wrapper_set_text_contents;
+ iface->insert_text = editable_text_wrapper_insert_text;
+ iface->copy_text = editable_text_wrapper_copy_text;
+ iface->cut_text = editable_text_wrapper_cut_text;
+ iface->delete_text = editable_text_wrapper_delete_text;
+ iface->paste_text = editable_text_wrapper_paste_text;
+ iface->set_run_attributes = editable_text_wrapper_set_run_attributes;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkfactory.cxx b/vcl/unx/gtk3/a11y/atkfactory.cxx
new file mode 100644
index 0000000000..2fc407b7bc
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkfactory.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 <unx/gtk/gtkframe.hxx>
+#include <vcl/window.hxx>
+#include "atkwrapper.hxx"
+#include "atkfactory.hxx"
+#include "atkregistry.hxx"
+
+using namespace ::com::sun::star;
+
+extern "C" {
+
+/*
+ * Instances of this dummy object class are returned whenever we have to
+ * create an AtkObject, but can't touch the OOo object anymore since it
+ * is already disposed.
+ */
+
+static AtkStateSet *
+noop_wrapper_ref_state_set( AtkObject * )
+{
+ AtkStateSet *state_set = atk_state_set_new();
+ atk_state_set_add_state( state_set, ATK_STATE_DEFUNCT );
+ return state_set;
+}
+
+static void
+atk_noop_object_wrapper_class_init(AtkNoOpObjectClass *klass)
+{
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS( klass );
+ atk_class->ref_state_set = noop_wrapper_ref_state_set;
+}
+
+static GType
+atk_noop_object_wrapper_get_type()
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ static const GTypeInfo typeInfo =
+ {
+ sizeof (AtkNoOpObjectClass),
+ nullptr,
+ nullptr,
+ reinterpret_cast<GClassInitFunc>(atk_noop_object_wrapper_class_init),
+ nullptr,
+ nullptr,
+ sizeof (AtkObjectWrapper),
+ 0,
+ nullptr,
+ nullptr
+ } ;
+
+ type = g_type_register_static (ATK_TYPE_OBJECT, "OOoAtkNoOpObj", &typeInfo, GTypeFlags(0)) ;
+ }
+ return type;
+}
+
+static AtkObject*
+atk_noop_object_wrapper_new()
+{
+ AtkObject *accessible;
+
+ accessible = static_cast<AtkObject *>(g_object_new (atk_noop_object_wrapper_get_type(), nullptr));
+ g_return_val_if_fail (accessible != nullptr, nullptr);
+
+ accessible->role = ATK_ROLE_INVALID;
+ accessible->layer = ATK_LAYER_INVALID;
+
+ return accessible;
+}
+
+/*
+ * The wrapper factory
+ */
+
+static GType
+wrapper_factory_get_accessible_type()
+{
+ return atk_object_wrapper_get_type();
+}
+
+static AtkObject*
+wrapper_factory_create_accessible( GObject *obj )
+{
+ GtkWidget* pEventBox = gtk_widget_get_parent(GTK_WIDGET(obj));
+
+ // gail_container_real_remove_gtk tries to re-instantiate an accessible
+ // for a widget that is about to vanish ..
+ if (!pEventBox)
+ return atk_noop_object_wrapper_new();
+
+ GtkWidget* pTopLevelGrid = gtk_widget_get_parent(pEventBox);
+ if (!pTopLevelGrid)
+ return atk_noop_object_wrapper_new();
+
+ GtkWidget* pTopLevel = gtk_widget_get_parent(pTopLevelGrid);
+ if (!pTopLevel)
+ return atk_noop_object_wrapper_new();
+
+ GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pTopLevel);
+ g_return_val_if_fail(pFrame != nullptr, atk_noop_object_wrapper_new());
+
+ vcl::Window* pFrameWindow = pFrame->GetWindow();
+ if( pFrameWindow )
+ {
+ vcl::Window* pWindow = pFrameWindow;
+
+ // skip accessible objects already exposed by the frame objects
+ if( WindowType::BORDERWINDOW == pWindow->GetType() )
+ pWindow = pFrameWindow->GetAccessibleChildWindow(0);
+
+ if( pWindow )
+ {
+ uno::Reference< accessibility::XAccessible > xAccessible = pWindow->GetAccessible();
+ if( xAccessible.is() )
+ {
+ AtkObject *accessible = ooo_wrapper_registry_get( xAccessible );
+
+ if( accessible )
+ g_object_ref( G_OBJECT(accessible) );
+ else
+ accessible = atk_object_wrapper_new( xAccessible, gtk_widget_get_accessible(pTopLevel) );
+
+ return accessible;
+ }
+ }
+ }
+
+ return atk_noop_object_wrapper_new();
+}
+
+AtkObject* ooo_fixed_get_accessible(GtkWidget *obj)
+{
+ return wrapper_factory_create_accessible(G_OBJECT(obj));
+}
+
+static void
+wrapper_factory_class_init( AtkObjectFactoryClass *klass )
+{
+ klass->create_accessible = wrapper_factory_create_accessible;
+ klass->get_accessible_type = wrapper_factory_get_accessible_type;
+}
+
+GType
+wrapper_factory_get_type()
+{
+ static GType t = 0;
+
+ if (!t) {
+ static const GTypeInfo tinfo =
+ {
+ sizeof (AtkObjectFactoryClass),
+ nullptr, nullptr, reinterpret_cast<GClassInitFunc>(wrapper_factory_class_init),
+ nullptr, nullptr, sizeof (AtkObjectFactory), 0, nullptr, nullptr
+ };
+
+ t = g_type_register_static (
+ ATK_TYPE_OBJECT_FACTORY, "OOoAtkObjectWrapperFactory",
+ &tinfo, GTypeFlags(0));
+ }
+
+ return t;
+}
+
+} // extern C
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkfactory.hxx b/vcl/unx/gtk3/a11y/atkfactory.hxx
new file mode 100644
index 0000000000..66c52eab76
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkfactory.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 .
+ */
+
+#pragma once
+
+#include <atk/atk.h>
+
+extern "C" {
+
+GType wrapper_factory_get_type();
+
+} // extern "C"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkhypertext.cxx b/vcl/unx/gtk3/a11y/atkhypertext.cxx
new file mode 100644
index 0000000000..83f6817fc9
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkhypertext.cxx
@@ -0,0 +1,277 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleHypertext.hpp>
+
+using namespace ::com::sun::star;
+
+// ---------------------- AtkHyperlink ----------------------
+
+namespace {
+
+struct HyperLink {
+ AtkHyperlink const atk_hyper_link;
+
+ uno::Reference< accessibility::XAccessibleHyperlink > xLink;
+};
+
+}
+
+static uno::Reference< accessibility::XAccessibleHyperlink > const &
+ getHyperlink( AtkHyperlink *pHyperlink )
+{
+ HyperLink *pLink = reinterpret_cast<HyperLink *>(pHyperlink);
+ return pLink->xLink;
+}
+
+static GObjectClass *hyper_parent_class = nullptr;
+
+extern "C" {
+
+static void
+hyper_link_finalize (GObject *obj)
+{
+ HyperLink *hl = reinterpret_cast<HyperLink *>(obj);
+ hl->xLink.clear();
+ hyper_parent_class->finalize (obj);
+}
+
+static gchar *
+hyper_link_get_uri( AtkHyperlink *pLink,
+ gint i )
+{
+ try {
+ uno::Any aAny = getHyperlink( pLink )->getAccessibleActionObject( i );
+ OUString aUri = aAny.get< OUString > ();
+ return OUStringToGChar(aUri);
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in hyper_link_get_uri" );
+ }
+ return nullptr;
+}
+
+static AtkObject *
+hyper_link_get_object( AtkHyperlink *,
+ gint )
+{
+ g_warning( "FIXME: hyper_link_get_object unimplemented" );
+ return nullptr;
+}
+static gint
+hyper_link_get_end_index( AtkHyperlink *pLink )
+{
+ try {
+ return getHyperlink( pLink )->getEndIndex();
+ }
+ catch(const uno::Exception&) {
+ }
+ return -1;
+}
+static gint
+hyper_link_get_start_index( AtkHyperlink *pLink )
+{
+ try {
+ return getHyperlink( pLink )->getStartIndex();
+ }
+ catch(const uno::Exception&) {
+ }
+ return -1;
+}
+static gboolean
+hyper_link_is_valid( AtkHyperlink *pLink )
+{
+ try {
+ return getHyperlink( pLink )->isValid();
+ }
+ catch(const uno::Exception&) {
+ }
+ return FALSE;
+}
+static gint
+hyper_link_get_n_anchors( AtkHyperlink *pLink )
+{
+ try {
+ return getHyperlink( pLink )->getAccessibleActionCount();
+ }
+ catch(const uno::Exception&) {
+ }
+ return 0;
+}
+
+static guint
+hyper_link_link_state( AtkHyperlink * )
+{
+ g_warning( "FIXME: hyper_link_link_state unimplemented" );
+ return 0;
+}
+static gboolean
+hyper_link_is_selected_link( AtkHyperlink * )
+{
+ g_warning( "FIXME: hyper_link_is_selected_link unimplemented" );
+ return FALSE;
+}
+
+static void
+hyper_link_class_init (AtkHyperlinkClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = hyper_link_finalize;
+
+ hyper_parent_class = static_cast<GObjectClass *>(g_type_class_peek_parent (klass));
+
+ klass->get_uri = hyper_link_get_uri;
+ klass->get_object = hyper_link_get_object;
+ klass->get_end_index = hyper_link_get_end_index;
+ klass->get_start_index = hyper_link_get_start_index;
+ klass->is_valid = hyper_link_is_valid;
+ klass->get_n_anchors = hyper_link_get_n_anchors;
+ klass->link_state = hyper_link_link_state;
+ klass->is_selected_link = hyper_link_is_selected_link;
+}
+
+static GType
+hyper_link_get_type()
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo tinfo = {
+ sizeof (AtkHyperlinkClass),
+ nullptr, /* base init */
+ nullptr, /* base finalize */
+ reinterpret_cast<GClassInitFunc>(hyper_link_class_init),
+ nullptr, /* class finalize */
+ nullptr, /* class data */
+ sizeof (HyperLink), /* instance size */
+ 0, /* nb preallocs */
+ nullptr, /* instance init */
+ nullptr /* value table */
+ };
+
+ static const GInterfaceInfo atk_action_info = {
+ reinterpret_cast<GInterfaceInitFunc>(actionIfaceInit),
+ nullptr,
+ nullptr
+ };
+
+ type = g_type_register_static (ATK_TYPE_HYPERLINK,
+ "OOoAtkObjHyperLink", &tinfo,
+ GTypeFlags(0));
+ g_type_add_interface_static (type, ATK_TYPE_ACTION,
+ &atk_action_info);
+ }
+
+ return type;
+}
+
+// ---------------------- AtkHyperText ----------------------
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleHypertext>
+ getHypertext( AtkHypertext *pHypertext )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pHypertext );
+ if( pWrap )
+ {
+ if( !pWrap->mpHypertext.is() )
+ {
+ pWrap->mpHypertext.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpHypertext;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleHypertext>();
+}
+
+static AtkHyperlink *
+hypertext_get_link( AtkHypertext *hypertext,
+ gint link_index)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleHypertext> pHypertext
+ = getHypertext( hypertext );
+ if( pHypertext.is() )
+ {
+ HyperLink *pLink = static_cast<HyperLink *>(g_object_new( hyper_link_get_type(), nullptr ));
+ pLink->xLink = pHypertext->getHyperLink( link_index );
+ if( !pLink->xLink.is() ) {
+ g_object_unref( G_OBJECT( pLink ) );
+ pLink = nullptr;
+ }
+ return ATK_HYPERLINK( pLink );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getHyperLink()" );
+ }
+
+ return nullptr;
+}
+
+static gint
+hypertext_get_n_links( AtkHypertext *hypertext )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleHypertext> pHypertext
+ = getHypertext( hypertext );
+ if( pHypertext.is() )
+ return pHypertext->getHyperLinkCount();
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getHyperLinkCount()" );
+ }
+
+ return 0;
+}
+
+static gint
+hypertext_get_link_index( AtkHypertext *hypertext,
+ gint index)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleHypertext> pHypertext
+ = getHypertext( hypertext );
+ if( pHypertext.is() )
+ return pHypertext->getHyperLinkIndex( index );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getHyperLinkIndex()" );
+ }
+
+ return 0;
+}
+
+} // extern "C"
+
+void
+hypertextIfaceInit (AtkHypertextIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->get_link = hypertext_get_link;
+ iface->get_n_links = hypertext_get_n_links;
+ iface->get_link_index = hypertext_get_link_index;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkimage.cxx b/vcl/unx/gtk3/a11y/atkimage.cxx
new file mode 100644
index 0000000000..da4965b868
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkimage.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 <sal/config.h>
+
+#include <string_view>
+
+#include "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleImage.hpp>
+
+using namespace ::com::sun::star;
+
+// FIXME
+static const gchar *
+getAsConst( std::u16string_view rString )
+{
+ static const int nMax = 10;
+ static OString aUgly[nMax];
+ static int nIdx = 0;
+ nIdx = (nIdx + 1) % nMax;
+ aUgly[nIdx] = OUStringToOString( rString, RTL_TEXTENCODING_UTF8 );
+ return aUgly[ nIdx ].getStr();
+}
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleImage>
+ getImage( AtkImage *pImage )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pImage );
+ if( pWrap )
+ {
+ if( !pWrap->mpImage.is() )
+ {
+ pWrap->mpImage.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpImage;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleImage>();
+}
+
+extern "C" {
+
+static const gchar *
+image_get_image_description( AtkImage *image )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleImage> pImage
+ = getImage( image );
+ if( pImage.is() )
+ return getAsConst( pImage->getAccessibleImageDescription() );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleImageDescription()" );
+ }
+
+ return nullptr;
+}
+
+static void
+image_get_image_position( AtkImage *image,
+ gint *x,
+ gint *y,
+ AtkCoordType coord_type )
+{
+ *x = *y = -1;
+ if( ATK_IS_COMPONENT( image ) )
+ {
+ gint nWidth = -1;
+ gint nHeight = -1;
+ atk_component_get_extents(ATK_COMPONENT(image), x, y, &nWidth, &nHeight, coord_type);
+ }
+ else
+ g_warning( "FIXME: no image position information" );
+}
+
+static void
+image_get_image_size( AtkImage *image,
+ gint *width,
+ gint *height )
+{
+ *width = *height = -1;
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleImage> pImage
+ = getImage( image );
+ if( pImage.is() )
+ {
+ *width = pImage->getAccessibleImageWidth();
+ *height = pImage->getAccessibleImageHeight();
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleImageHeight() or Width" );
+ }
+}
+
+static gboolean
+image_set_image_description( AtkImage *, const gchar * )
+{
+ g_warning ("FIXME: no set image description");
+ return FALSE;
+}
+
+} // extern "C"
+
+void
+imageIfaceInit (AtkImageIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->set_image_description = image_set_image_description;
+ iface->get_image_description = image_get_image_description;
+ iface->get_image_position = image_get_image_position;
+ iface->get_image_size = image_get_image_size;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atklistener.cxx b/vcl/unx/gtk3/a11y/atklistener.cxx
new file mode 100644
index 0000000000..b826ea0306
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atklistener.cxx
@@ -0,0 +1,783 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/accessibility/TextSegment.hpp>
+#include <com/sun/star/accessibility/AccessibleEventId.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp>
+#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp>
+#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
+#include <com/sun/star/accessibility/XAccessibleContext3.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+
+#include "atklistener.hxx"
+#include "atkwrapper.hxx"
+#include <comphelper/sequence.hxx>
+#include <vcl/svapp.hxx>
+
+#include <sal/log.hxx>
+
+#define DEBUG_ATK_LISTENER 0
+
+#if DEBUG_ATK_LISTENER
+#include <iostream>
+#include <sstream>
+#endif
+
+using namespace com::sun::star;
+
+AtkListener::AtkListener( AtkObjectWrapper* pWrapper ) : mpWrapper( pWrapper )
+{
+ if( mpWrapper )
+ {
+ g_object_ref( mpWrapper );
+ updateChildList( mpWrapper->mpContext );
+ }
+}
+
+AtkListener::~AtkListener()
+{
+ if( mpWrapper )
+ g_object_unref( mpWrapper );
+}
+
+/*****************************************************************************/
+
+static AtkStateType mapState( const uno::Any &rAny )
+{
+ sal_Int64 nState = accessibility::AccessibleStateType::INVALID;
+ rAny >>= nState;
+ return mapAtkState( nState );
+}
+
+/*****************************************************************************/
+
+extern "C" {
+ // rhbz#1001768 - down to horrific problems releasing the solar mutex
+ // while destroying a Window - which occurs inside these notifications.
+ static gboolean
+ idle_defunc_state_change( AtkObject *atk_obj )
+ {
+ SolarMutexGuard aGuard;
+
+ // This is an equivalent to a state change to DEFUNC(T).
+ atk_object_notify_state_change( atk_obj, ATK_STATE_DEFUNCT, true );
+ if( atk_get_focus_object() == atk_obj )
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ atk_focus_tracker_notify( nullptr );
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+ g_object_unref( G_OBJECT( atk_obj ) );
+ return false;
+ }
+}
+
+// XEventListener implementation
+void AtkListener::disposing( const lang::EventObject& )
+{
+ if( !mpWrapper )
+ return;
+
+ AtkObject *atk_obj = ATK_OBJECT( mpWrapper );
+
+ // Release all interface references to avoid shutdown problems with
+ // global mutex
+ atk_object_wrapper_dispose( mpWrapper );
+
+ g_idle_add( reinterpret_cast<GSourceFunc>(idle_defunc_state_change),
+ g_object_ref( G_OBJECT( atk_obj ) ) );
+
+ // Release the wrapper object so that it can vanish ..
+ g_object_unref( mpWrapper );
+ mpWrapper = nullptr;
+}
+
+/*****************************************************************************/
+
+static AtkObject *getObjFromAny( const uno::Any &rAny )
+{
+ uno::Reference< accessibility::XAccessible > xAccessible;
+ rAny >>= xAccessible;
+ return xAccessible.is() ? atk_object_wrapper_ref( xAccessible ) : nullptr;
+}
+
+/*****************************************************************************/
+
+// Updates the child list held to provide the old IndexInParent on children_changed::remove
+void AtkListener::updateChildList(
+ css::uno::Reference<css::accessibility::XAccessibleContext> const &
+ pContext)
+{
+ m_aChildList.clear();
+
+ sal_Int64 nStateSet = pContext->getAccessibleStateSet();
+ if( (nStateSet & accessibility::AccessibleStateType::DEFUNC)
+ || (nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS) )
+ return;
+
+ css::uno::Reference<css::accessibility::XAccessibleContext3> xContext3(pContext, css::uno::UNO_QUERY);
+ if (xContext3.is())
+ {
+ m_aChildList = comphelper::sequenceToContainer<std::vector<css::uno::Reference< css::accessibility::XAccessible >>>(xContext3->getAccessibleChildren());
+ }
+ else
+ {
+ sal_Int64 nChildren = pContext->getAccessibleChildCount();
+ assert(o3tl::make_unsigned(nChildren) < m_aChildList.max_size());
+ m_aChildList.resize(nChildren);
+ for(sal_Int64 n = 0; n < nChildren; n++)
+ {
+ try
+ {
+ m_aChildList[n] = pContext->getAccessibleChild(n);
+ }
+ catch (lang::IndexOutOfBoundsException const&)
+ {
+ sal_Int64 nChildren2 = pContext->getAccessibleChildCount();
+ assert(nChildren2 <= n && "consistency?");
+ m_aChildList.resize(std::min(nChildren2, n));
+ break;
+ }
+ }
+ }
+}
+
+/*****************************************************************************/
+
+void AtkListener::handleChildAdded(
+ const uno::Reference< accessibility::XAccessibleContext >& rxParent,
+ const uno::Reference< accessibility::XAccessible>& rxAccessible,
+ sal_Int32 nIndexHint)
+{
+ AtkObject * pChild = rxAccessible.is() ? atk_object_wrapper_ref( rxAccessible ) : nullptr;
+
+ if( !pChild )
+ return;
+
+ if (nIndexHint != -1 && (nIndexHint < 0 || nIndexHint >= static_cast<sal_Int32>(m_aChildList.size())))
+ {
+ SAL_WARN("vcl", "index hint out of range, ignoring");
+ nIndexHint = -1;
+ }
+
+ bool bNeedToFullFullChildList = true;
+ if (nIndexHint != -1)
+ {
+ bNeedToFullFullChildList = false;
+ sal_Int64 nStateSet = rxParent->getAccessibleStateSet();
+ if( !(nStateSet & accessibility::AccessibleStateType::DEFUNC)
+ || (nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS) )
+ {
+ m_aChildList.insert(m_aChildList.begin() + nIndexHint, rxAccessible);
+ if (m_aChildList[nIndexHint] != rxParent->getAccessibleChild(nIndexHint))
+ {
+ SAL_WARN("vcl", "wrong index hint, falling back to updating full child list");
+ bNeedToFullFullChildList = true;
+ }
+ }
+ }
+ if (bNeedToFullFullChildList)
+ updateChildList(rxParent);
+
+ atk_object_wrapper_add_child( mpWrapper, pChild,
+ atk_object_get_index_in_parent( pChild ));
+
+ g_object_unref( pChild );
+}
+
+/*****************************************************************************/
+
+void AtkListener::handleChildRemoved(
+ const uno::Reference< accessibility::XAccessibleContext >& rxParent,
+ const uno::Reference< accessibility::XAccessible>& rxChild,
+ sal_Int32 nChildIndexHint)
+{
+ sal_Int32 nIndex = nChildIndexHint;
+ if (nIndex != -1 && (nIndex < 0 || nIndex >= static_cast<sal_Int32>(m_aChildList.size())))
+ {
+ SAL_WARN("vcl", "index hint out of range, ignoring");
+ nIndex = -1;
+ }
+ if (nIndex != -1 && rxChild != m_aChildList[nIndex])
+ {
+ SAL_WARN("vcl", "index hint points to wrong child, somebody forgot to send accessibility update event");
+ nIndex = -1;
+ }
+
+ // if the hint did not work, search
+ const size_t nmax = m_aChildList.size();
+ if (nIndex == -1)
+ // Locate the child in the children list
+ for( size_t n = 0; n < nmax; ++n )
+ {
+ // Comparing via uno::Reference::operator== is expensive
+ // with lots of objects, so assume we can find it the cheap way
+ // first, which works most of the time.
+ if( rxChild.get() == m_aChildList[n].get() )
+ {
+ nIndex = n;
+ break;
+ }
+ }
+ // The cheap way failed, find it via the more expensive path
+ if (nIndex == -1)
+ for( size_t n = 0; n < nmax; ++n )
+ {
+ if( rxChild == m_aChildList[n] )
+ {
+ nIndex = n;
+ break;
+ }
+ }
+
+ // FIXME: two problems here:
+ // a) we get child-removed events for objects that are no real children
+ // in the accessibility hierarchy or have been removed before due to
+ // some child removing batch.
+ // b) spi_atk_bridge_signal_listener ignores the given parameters
+ // for children_changed events and always asks the parent for the
+ // 0. child, which breaks somehow on vanishing list boxes.
+ // Ignoring "remove" events for objects not in the m_aChildList
+ // for now.
+ if( nIndex < 0 )
+ return;
+
+ uno::Reference<accessibility::XAccessibleEventBroadcaster> xBroadcaster(
+ rxChild->getAccessibleContext(), uno::UNO_QUERY);
+
+ if (xBroadcaster.is())
+ {
+ uno::Reference<accessibility::XAccessibleEventListener> xListener(this);
+ xBroadcaster->removeAccessibleEventListener(xListener);
+ }
+
+ // update child list
+ sal_Int64 nStateSet = rxParent->getAccessibleStateSet();
+ if(!( (nStateSet & accessibility::AccessibleStateType::DEFUNC)
+ || (nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS) ))
+ {
+ m_aChildList.erase(m_aChildList.begin() + nIndex);
+ }
+
+ AtkObject * pChild = atk_object_wrapper_ref( rxChild, false );
+ if( pChild )
+ {
+ atk_object_wrapper_remove_child( mpWrapper, pChild, nIndex );
+ g_object_unref( pChild );
+ }
+}
+
+/*****************************************************************************/
+
+void AtkListener::handleInvalidateChildren(
+ const uno::Reference< accessibility::XAccessibleContext >& rxParent)
+{
+ // Send notifications for all previous children
+ size_t n = m_aChildList.size();
+ while( n-- > 0 )
+ {
+ if( m_aChildList[n].is() )
+ {
+ AtkObject * pChild = atk_object_wrapper_ref( m_aChildList[n], false );
+ if( pChild )
+ {
+ atk_object_wrapper_remove_child( mpWrapper, pChild, n );
+ g_object_unref( pChild );
+ }
+ }
+ }
+
+ updateChildList(rxParent);
+
+ // Send notifications for all new children
+ size_t nmax = m_aChildList.size();
+ for( n = 0; n < nmax; ++n )
+ {
+ if( m_aChildList[n].is() )
+ {
+ AtkObject * pChild = atk_object_wrapper_ref( m_aChildList[n] );
+
+ if( pChild )
+ {
+ atk_object_wrapper_add_child( mpWrapper, pChild, n );
+ g_object_unref( pChild );
+ }
+ }
+ }
+}
+
+/*****************************************************************************/
+
+static uno::Reference< accessibility::XAccessibleContext >
+getAccessibleContextFromSource( const uno::Reference< uno::XInterface >& rxSource )
+{
+ uno::Reference< accessibility::XAccessibleContext > xContext(rxSource, uno::UNO_QUERY);
+ if( ! xContext.is() )
+ {
+ g_warning( "ERROR: Event source does not implement XAccessibleContext" );
+
+ // Second try - query for XAccessible, which should give us access to
+ // XAccessibleContext.
+ uno::Reference< accessibility::XAccessible > xAccessible(rxSource, uno::UNO_QUERY);
+ if( xAccessible.is() )
+ xContext = xAccessible->getAccessibleContext();
+ }
+
+ return xContext;
+}
+
+#if DEBUG_ATK_LISTENER
+
+namespace {
+
+void printNotifyEvent( const accessibility::AccessibleEventObject& rEvent )
+{
+ static std::vector<const char*> aLabels = {
+ 0,
+ "NAME_CHANGED", // 01
+ "DESCRIPTION_CHANGED", // 02
+ "ACTION_CHANGED", // 03
+ "STATE_CHANGED", // 04
+ "ACTIVE_DESCENDANT_CHANGED", // 05
+ "BOUNDRECT_CHANGED", // 06
+ "CHILD", // 07
+ "INVALIDATE_ALL_CHILDREN", // 08
+ "SELECTION_CHANGED", // 09
+ "VISIBLE_DATA_CHANGED", // 10
+ "VALUE_CHANGED", // 11
+ "CONTENT_FLOWS_FROM_RELATION_CHANGED", // 12
+ "CONTENT_FLOWS_TO_RELATION_CHANGED", // 13
+ "CONTROLLED_BY_RELATION_CHANGED", // 14
+ "CONTROLLER_FOR_RELATION_CHANGED", // 15
+ "LABEL_FOR_RELATION_CHANGED", // 16
+ "LABELED_BY_RELATION_CHANGED", // 17
+ "MEMBER_OF_RELATION_CHANGED", // 18
+ "SUB_WINDOW_OF_RELATION_CHANGED", // 19
+ "CARET_CHANGED", // 20
+ "TEXT_SELECTION_CHANGED", // 21
+ "TEXT_CHANGED", // 22
+ "TEXT_ATTRIBUTE_CHANGED", // 23
+ "HYPERTEXT_CHANGED", // 24
+ "TABLE_CAPTION_CHANGED", // 25
+ "TABLE_COLUMN_DESCRIPTION_CHANGED", // 26
+ "TABLE_COLUMN_HEADER_CHANGED", // 27
+ "TABLE_MODEL_CHANGED", // 28
+ "TABLE_ROW_DESCRIPTION_CHANGED", // 29
+ "TABLE_ROW_HEADER_CHANGED", // 30
+ "TABLE_SUMMARY_CHANGED", // 31
+ "LISTBOX_ENTRY_EXPANDED", // 32
+ "LISTBOX_ENTRY_COLLAPSED", // 33
+ "ACTIVE_DESCENDANT_CHANGED_NOFOCUS", // 34
+ "SELECTION_CHANGED_ADD", // 35
+ "SELECTION_CHANGED_REMOVE", // 36
+ "SELECTION_CHANGED_WITHIN", // 37
+ "PAGE_CHANGED", // 38
+ "SECTION_CHANGED", // 39
+ "COLUMN_CHANGED", // 40
+ "ROLE_CHANGED", // 41
+ };
+
+ static std::vector<const char*> aStates = {
+ "INVALID", // 00
+ "ACTIVE", // 01
+ "ARMED", // 02
+ "BUSY", // 03
+ "CHECKED", // 04
+ "DEFUNC", // 05
+ "EDITABLE", // 06
+ "ENABLED", // 07
+ "EXPANDABLE", // 08
+ "EXPANDED", // 09
+ "FOCUSABLE", // 10
+ "FOCUSED", // 11
+ "HORIZONTAL", // 12
+ "ICONIFIED", // 13
+ "INDETERMINATE", // 14
+ "MANAGES_DESCENDANTS", // 15
+ "MODAL", // 16
+ "MULTI_LINE", // 17
+ "MULTI_SELECTABLE", // 18
+ "OPAQUE", // 19
+ "PRESSED", // 20
+ "RESIZABLE", // 21
+ "SELECTABLE", // 22
+ "SELECTED", // 23
+ "SENSITIVE", // 24
+ "SHOWING", // 25
+ "SINGLE_LINE", // 26
+ "STALE", // 27
+ "TRANSIENT", // 28
+ "VERTICAL", // 29
+ "VISIBLE", // 30
+ "MOVEABLE", // 31
+ "DEFAULT", // 32
+ "OFFSCREEN", // 33
+ "COLLAPSE", // 34
+ };
+
+ auto getOrUnknown = [](const std::vector<const char*>& rCont, size_t nIndex) -> std::string
+ {
+ return (nIndex < rCont.size()) ? rCont[nIndex] : "<unknown>";
+ };
+
+ std::ostringstream os;
+ os << "--" << std::endl;
+ os << "* event = " << getOrUnknown(aLabels, rEvent.EventId) << std::endl;
+
+ switch (rEvent.EventId)
+ {
+ case accessibility::AccessibleEventId::STATE_CHANGED:
+ {
+ sal_Int64 nState;
+ if (rEvent.OldValue >>= nState)
+ os << " * old state = " << getOrUnknown(aStates, nState);
+ if (rEvent.NewValue >>= nState)
+ os << " * new state = " << getOrUnknown(aStates, nState);
+
+ os << std::endl;
+ break;
+ }
+ default:
+ ;
+ }
+
+ std::cout << os.str();
+}
+
+}
+
+#endif
+
+void AtkListener::notifyEvent( const accessibility::AccessibleEventObject& aEvent )
+{
+ if( !mpWrapper )
+ return;
+
+ AtkObject *atk_obj = ATK_OBJECT( mpWrapper );
+
+ switch( aEvent.EventId )
+ {
+ // AtkObject signals:
+ // Hierarchy signals
+ case accessibility::AccessibleEventId::CHILD:
+ {
+ uno::Reference< accessibility::XAccessibleContext > xParent;
+ uno::Reference< accessibility::XAccessible > xChild;
+
+ xParent = getAccessibleContextFromSource(aEvent.Source);
+ g_return_if_fail( xParent.is() );
+
+ if( aEvent.OldValue >>= xChild )
+ handleChildRemoved(xParent, xChild, aEvent.IndexHint);
+
+ if( aEvent.NewValue >>= xChild )
+ handleChildAdded(xParent, xChild, aEvent.IndexHint);
+ break;
+ }
+
+ case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN:
+ {
+ uno::Reference< accessibility::XAccessibleContext > xParent = getAccessibleContextFromSource(aEvent.Source);
+ g_return_if_fail( xParent.is() );
+
+ handleInvalidateChildren(xParent);
+ break;
+ }
+
+ case accessibility::AccessibleEventId::NAME_CHANGED:
+ {
+ OUString aName;
+ if( aEvent.NewValue >>= aName )
+ {
+ atk_object_set_name(atk_obj,
+ OUStringToOString(aName, RTL_TEXTENCODING_UTF8).getStr());
+ }
+ break;
+ }
+
+ case accessibility::AccessibleEventId::DESCRIPTION_CHANGED:
+ {
+ OUString aDescription;
+ if( aEvent.NewValue >>= aDescription )
+ {
+ atk_object_set_description(atk_obj,
+ OUStringToOString(aDescription, RTL_TEXTENCODING_UTF8).getStr());
+ }
+ break;
+ }
+
+ case accessibility::AccessibleEventId::STATE_CHANGED:
+ {
+ AtkStateType eOldState = mapState( aEvent.OldValue );
+ AtkStateType eNewState = mapState( aEvent.NewValue );
+
+ bool bState = eNewState != ATK_STATE_INVALID;
+ AtkStateType eRealState = bState ? eNewState : eOldState;
+
+ atk_object_notify_state_change( atk_obj, eRealState, bState );
+ break;
+ }
+
+ case accessibility::AccessibleEventId::BOUNDRECT_CHANGED:
+
+ if( ATK_IS_COMPONENT( atk_obj ) )
+ {
+ AtkRectangle rect;
+
+ atk_component_get_extents( ATK_COMPONENT( atk_obj ),
+ &rect.x,
+ &rect.y,
+ &rect.width,
+ &rect.height,
+ ATK_XY_SCREEN );
+
+ g_signal_emit_by_name( atk_obj, "bounds-changed", &rect );
+ }
+ else
+ g_warning( "bounds-changed event for object not implementing AtkComponent\n");
+ break;
+
+ case accessibility::AccessibleEventId::VISIBLE_DATA_CHANGED:
+ g_signal_emit_by_name( atk_obj, "visible-data-changed" );
+ break;
+
+ case accessibility::AccessibleEventId::ACTIVE_DESCENDANT_CHANGED:
+ {
+ AtkObject *pChild = getObjFromAny( aEvent.NewValue );
+ if( pChild )
+ {
+ g_signal_emit_by_name( atk_obj, "active-descendant-changed", pChild );
+ g_object_unref( pChild );
+ }
+ break;
+ }
+
+ //ACTIVE_DESCENDANT_CHANGED_NOFOCUS (sic) appears to have been added
+ //as a workaround or an aid for the ia2 winaccessibility implementation
+ //so ignore it silently without warning here
+ case accessibility::AccessibleEventId::ACTIVE_DESCENDANT_CHANGED_NOFOCUS:
+ break;
+
+ // #i92103#
+ case accessibility::AccessibleEventId::LISTBOX_ENTRY_EXPANDED:
+ {
+ AtkObject *pChild = getObjFromAny( aEvent.NewValue );
+ if( pChild )
+ {
+ atk_object_notify_state_change( pChild, ATK_STATE_EXPANDED, true );
+ g_object_unref( pChild );
+ }
+ break;
+ }
+
+ case accessibility::AccessibleEventId::LISTBOX_ENTRY_COLLAPSED:
+ {
+ AtkObject *pChild = getObjFromAny( aEvent.NewValue );
+ if( pChild )
+ {
+ atk_object_notify_state_change( pChild, ATK_STATE_EXPANDED, false );
+ g_object_unref( pChild );
+ }
+ break;
+ }
+
+ // AtkAction signals ...
+ case accessibility::AccessibleEventId::ACTION_CHANGED:
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-actions");
+ break;
+
+ // AtkText
+ case accessibility::AccessibleEventId::CARET_CHANGED:
+ {
+ sal_Int32 nPos=0;
+ aEvent.NewValue >>= nPos;
+ g_signal_emit_by_name( atk_obj, "text_caret_moved", nPos );
+ break;
+ }
+ case accessibility::AccessibleEventId::TEXT_CHANGED:
+ {
+ // cf. comphelper/source/misc/accessibletexthelper.cxx (implInitTextChangedEvent)
+ accessibility::TextSegment aDeletedText;
+ accessibility::TextSegment aInsertedText;
+
+ if( aEvent.OldValue >>= aDeletedText )
+ {
+ const OString aDeletedTextUtf8 = OUStringToOString(aDeletedText.SegmentText, RTL_TEXTENCODING_UTF8);
+ g_signal_emit_by_name( atk_obj, "text-remove",
+ static_cast<gint>(aDeletedText.SegmentStart),
+ static_cast<gint>(aDeletedText.SegmentEnd - aDeletedText.SegmentStart),
+ g_strdup(aDeletedTextUtf8.getStr()));
+
+ }
+ if( aEvent.NewValue >>= aInsertedText )
+ {
+ const OString aInsertedTextUtf8 = OUStringToOString(aInsertedText.SegmentText, RTL_TEXTENCODING_UTF8);
+ g_signal_emit_by_name( atk_obj, "text-insert",
+ static_cast<gint>(aInsertedText.SegmentStart),
+ static_cast<gint>(aInsertedText.SegmentEnd - aInsertedText.SegmentStart),
+ g_strdup(aInsertedTextUtf8.getStr()));
+ }
+ break;
+ }
+
+ case accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED:
+ {
+ g_signal_emit_by_name( atk_obj, "text-selection-changed" );
+ break;
+ }
+
+ case accessibility::AccessibleEventId::TEXT_ATTRIBUTE_CHANGED:
+ g_signal_emit_by_name( atk_obj, "text-attributes-changed" );
+ break;
+
+ // AtkValue
+ case accessibility::AccessibleEventId::VALUE_CHANGED:
+ g_object_notify( G_OBJECT( atk_obj ), "accessible-value" );
+ break;
+
+ case accessibility::AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED:
+ case accessibility::AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED:
+ case accessibility::AccessibleEventId::CONTROLLED_BY_RELATION_CHANGED:
+ case accessibility::AccessibleEventId::CONTROLLER_FOR_RELATION_CHANGED:
+ case accessibility::AccessibleEventId::LABEL_FOR_RELATION_CHANGED:
+ case accessibility::AccessibleEventId::LABELED_BY_RELATION_CHANGED:
+ case accessibility::AccessibleEventId::MEMBER_OF_RELATION_CHANGED:
+ case accessibility::AccessibleEventId::SUB_WINDOW_OF_RELATION_CHANGED:
+ // FIXME: ask Bill how Atk copes with this little lot ...
+ break;
+
+ // AtkTable
+ case accessibility::AccessibleEventId::TABLE_MODEL_CHANGED:
+ {
+ accessibility::AccessibleTableModelChange aChange;
+ aEvent.NewValue >>= aChange;
+
+ sal_Int32 nRowsChanged = aChange.LastRow - aChange.FirstRow + 1;
+ sal_Int32 nColumnsChanged = aChange.LastColumn - aChange.FirstColumn + 1;
+
+ switch( aChange.Type )
+ {
+ case accessibility::AccessibleTableModelChangeType::COLUMNS_INSERTED:
+ g_signal_emit_by_name(G_OBJECT(atk_obj), "column-inserted",
+ aChange.FirstColumn, nColumnsChanged);
+ break;
+ case accessibility::AccessibleTableModelChangeType::COLUMNS_REMOVED:
+ g_signal_emit_by_name(G_OBJECT(atk_obj), "column-deleted",
+ aChange.FirstColumn, nColumnsChanged);
+ break;
+ case accessibility::AccessibleTableModelChangeType::ROWS_INSERTED:
+ g_signal_emit_by_name(G_OBJECT(atk_obj), "row-inserted",
+ aChange.FirstRow, nRowsChanged);
+ break;
+ case accessibility::AccessibleTableModelChangeType::ROWS_REMOVED:
+ g_signal_emit_by_name(G_OBJECT(atk_obj), "row-deleted",
+ aChange.FirstRow, nRowsChanged);
+ break;
+ case accessibility::AccessibleTableModelChangeType::UPDATE:
+ // This is not really a model change, is it ?
+ break;
+ default:
+ g_warning( "TESTME: unusual table model change %d\n", aChange.Type );
+ break;
+ }
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "model-changed" );
+ break;
+ }
+
+ case accessibility::AccessibleEventId::TABLE_COLUMN_HEADER_CHANGED:
+ {
+ accessibility::AccessibleTableModelChange aChange;
+ aEvent.NewValue >>= aChange;
+
+ AtkPropertyValues values;
+ memset(&values, 0, sizeof(AtkPropertyValues));
+ g_value_init (&values.new_value, G_TYPE_INT);
+ values.property_name = "accessible-table-column-header";
+
+ for (sal_Int32 nChangedColumn = aChange.FirstColumn; nChangedColumn <= aChange.LastColumn; ++nChangedColumn)
+ {
+ g_value_set_int (&values.new_value, nChangedColumn);
+ g_signal_emit_by_name(G_OBJECT(atk_obj), "property_change::accessible-table-column-header", &values, nullptr);
+ }
+ break;
+ }
+
+ case accessibility::AccessibleEventId::TABLE_CAPTION_CHANGED:
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-caption");
+ break;
+
+ case accessibility::AccessibleEventId::TABLE_COLUMN_DESCRIPTION_CHANGED:
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-column-description");
+ break;
+
+ case accessibility::AccessibleEventId::TABLE_ROW_DESCRIPTION_CHANGED:
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-row-description");
+ break;
+
+ case accessibility::AccessibleEventId::TABLE_ROW_HEADER_CHANGED:
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-row-header");
+ break;
+
+ case accessibility::AccessibleEventId::TABLE_SUMMARY_CHANGED:
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-summary");
+ break;
+
+ case accessibility::AccessibleEventId::SELECTION_CHANGED:
+ case accessibility::AccessibleEventId::SELECTION_CHANGED_ADD:
+ case accessibility::AccessibleEventId::SELECTION_CHANGED_REMOVE:
+ case accessibility::AccessibleEventId::SELECTION_CHANGED_WITHIN:
+ if (ATK_IS_SELECTION(atk_obj))
+ g_signal_emit_by_name(G_OBJECT(atk_obj), "selection_changed");
+ else
+ {
+ // e.g. tdf#122353, when such dialogs become native the problem will go away anyway
+ SAL_INFO("vcl.gtk", "selection change from obj which doesn't support XAccessibleSelection");
+ }
+ break;
+
+ case accessibility::AccessibleEventId::HYPERTEXT_CHANGED:
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-hypertext-offset");
+ break;
+
+ case accessibility::AccessibleEventId::ROLE_CHANGED:
+ {
+ uno::Reference< accessibility::XAccessibleContext > xContext = getAccessibleContextFromSource( aEvent.Source );
+ atk_object_wrapper_set_role(mpWrapper, xContext->getAccessibleRole(), xContext->getAccessibleStateSet());
+ break;
+ }
+
+ case accessibility::AccessibleEventId::PAGE_CHANGED:
+ {
+ /* // If we implemented AtkDocument then I imagine this is what this
+ // handler should look like
+ sal_Int32 nPos=0;
+ aEvent.NewValue >>= nPos;
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "page_changed", nPos );
+ */
+ break;
+ }
+
+ default:
+ SAL_WARN("vcl.gtk", "Unknown event notification: " << aEvent.EventId);
+ break;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atklistener.hxx b/vcl/unx/gtk3/a11y/atklistener.hxx
new file mode 100644
index 0000000000..e286f40e5a
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atklistener.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <com/sun/star/accessibility/XAccessibleEventListener.hpp>
+#include <cppuhelper/implbase.hxx>
+
+#include <vector>
+
+#include "atkwrapper.hxx"
+
+class AtkListener : public ::cppu::WeakImplHelper< css::accessibility::XAccessibleEventListener >
+{
+public:
+ explicit AtkListener(AtkObjectWrapper * pWrapper);
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+
+ // XAccessibleEventListener
+ virtual void SAL_CALL notifyEvent( const css::accessibility::AccessibleEventObject& aEvent ) override;
+
+private:
+
+ AtkObjectWrapper *mpWrapper;
+ std::vector< css::uno::Reference< css::accessibility::XAccessible > >
+ m_aChildList;
+
+ virtual ~AtkListener() override;
+
+ // Updates the child list held to provide the old IndexInParent on children_changed::remove
+ void updateChildList(
+ css::uno::Reference<css::accessibility::XAccessibleContext> const &
+ pContext);
+
+ // Process CHILD_EVENT notifications with a new child added
+ void handleChildAdded(
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& rxParent,
+ const css::uno::Reference< css::accessibility::XAccessible>& rxChild,
+ sal_Int32 nIndexHint);
+
+ // Process CHILD_EVENT notifications with a child removed
+ void handleChildRemoved(
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& rxParent,
+ const css::uno::Reference< css::accessibility::XAccessible>& rxChild,
+ sal_Int32 nIndexHint);
+
+ // Process INVALIDATE_ALL_CHILDREN notification
+ void handleInvalidateChildren(
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& rxParent);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkregistry.cxx b/vcl/unx/gtk3/a11y/atkregistry.cxx
new file mode 100644
index 0000000000..ff96378c41
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkregistry.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 "atkregistry.hxx"
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::uno;
+
+static GHashTable *uno_to_gobject = nullptr;
+
+/*****************************************************************************/
+
+AtkObject *
+ooo_wrapper_registry_get(const Reference< XAccessible >& rxAccessible)
+{
+ if( uno_to_gobject )
+ {
+ gpointer cached =
+ g_hash_table_lookup(uno_to_gobject, static_cast<gpointer>(rxAccessible.get()));
+
+ if( cached )
+ return ATK_OBJECT( cached );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+void
+ooo_wrapper_registry_add(const Reference< XAccessible >& rxAccessible, AtkObject *obj)
+{
+ if( !uno_to_gobject )
+ uno_to_gobject = g_hash_table_new (nullptr, nullptr);
+
+ g_hash_table_insert( uno_to_gobject, static_cast<gpointer>(rxAccessible.get()), obj );
+}
+
+/*****************************************************************************/
+
+void
+ooo_wrapper_registry_remove(
+ css::uno::Reference<css::accessibility::XAccessible> const & pAccessible)
+{
+ if( uno_to_gobject )
+ g_hash_table_remove(
+ uno_to_gobject, static_cast<gpointer>(pAccessible.get()) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkregistry.hxx b/vcl/unx/gtk3/a11y/atkregistry.hxx
new file mode 100644
index 0000000000..b26b838407
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkregistry.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 <com/sun/star/accessibility/XAccessible.hpp>
+#include <atk/atk.h>
+
+AtkObject*
+ooo_wrapper_registry_get(const css::uno::Reference<css::accessibility::XAccessible>& rxAccessible);
+
+void ooo_wrapper_registry_add(
+ const css::uno::Reference<css::accessibility::XAccessible>& rxAccessible, AtkObject* obj);
+
+void ooo_wrapper_registry_remove(
+ css::uno::Reference<css::accessibility::XAccessible> const& pAccessible);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkselection.cxx b/vcl/unx/gtk3/a11y/atkselection.cxx
new file mode 100644
index 0000000000..0e74f1c295
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkselection.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 "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
+#include <sal/log.hxx>
+
+using namespace ::com::sun::star;
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleSelection>
+ getSelection( AtkSelection *pSelection )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pSelection );
+ if( pWrap )
+ {
+ if( !pWrap->mpSelection.is() )
+ {
+ pWrap->mpSelection.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpSelection;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleSelection>();
+}
+
+extern "C" {
+
+static gboolean
+selection_add_selection( AtkSelection *selection,
+ gint i )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
+ = getSelection( selection );
+ if( pSelection.is() )
+ {
+ pSelection->selectAccessibleChild( i );
+ return true;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in selectAccessibleChild()" );
+ }
+
+ return FALSE;
+}
+
+static gboolean
+selection_clear_selection( AtkSelection *selection )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
+ = getSelection( selection );
+ if( pSelection.is() )
+ {
+ pSelection->clearAccessibleSelection();
+ return true;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in clearAccessibleSelection()" );
+ }
+
+ return FALSE;
+}
+
+static AtkObject*
+selection_ref_selection( AtkSelection *selection,
+ gint i )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
+ = getSelection( selection );
+ if( pSelection.is() )
+ return atk_object_wrapper_ref( pSelection->getSelectedAccessibleChild( i ) );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectedAccessibleChild()" );
+ }
+
+ return nullptr;
+}
+
+static gint
+selection_get_selection_count( AtkSelection *selection)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
+ = getSelection( selection );
+ if( pSelection.is() )
+ {
+ sal_Int64 nSelected = pSelection->getSelectedAccessibleChildCount();
+ if (nSelected > std::numeric_limits<gint>::max())
+ {
+ SAL_WARN("vcl.gtk", "selection_get_selection_count: Count exceeds maximum gint value, "
+ "using max gint.");
+ nSelected = std::numeric_limits<gint>::max();
+ }
+ return nSelected;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectedAccessibleChildCount()" );
+ }
+
+ return -1;
+}
+
+static gboolean
+selection_is_child_selected( AtkSelection *selection,
+ gint i)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
+ = getSelection( selection );
+ if( pSelection.is() )
+ return pSelection->isAccessibleChildSelected( i );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in isAccessibleChildSelected()" );
+ }
+
+ return FALSE;
+}
+
+static gboolean
+selection_remove_selection( AtkSelection *selection,
+ gint i )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
+ = getSelection( selection );
+ if( pSelection.is() )
+ {
+ css::uno::Reference<css::accessibility::XAccessible> xAcc = pSelection->getSelectedAccessibleChild(i);
+ if (!xAcc.is())
+ return false;
+
+ css::uno::Reference<css::accessibility::XAccessibleContext> xAccContext = xAcc->getAccessibleContext();
+ const sal_Int64 nChildIndex = xAccContext->getAccessibleIndexInParent();
+ pSelection->deselectAccessibleChild(nChildIndex);
+ return true;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectedAccessibleChild(), getAccessibleIndexInParent() or deselectAccessibleChild()" );
+ }
+
+ return FALSE;
+}
+
+static gboolean
+selection_select_all_selection( AtkSelection *selection)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
+ = getSelection( selection );
+ if( pSelection.is() )
+ {
+ pSelection->selectAllAccessibleChildren();
+ return true;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in selectAllAccessibleChildren()" );
+ }
+
+ return FALSE;
+}
+
+} // extern "C"
+
+void
+selectionIfaceInit( AtkSelectionIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->add_selection = selection_add_selection;
+ iface->clear_selection = selection_clear_selection;
+ iface->ref_selection = selection_ref_selection;
+ iface->get_selection_count = selection_get_selection_count;
+ iface->is_child_selected = selection_is_child_selected;
+ iface->remove_selection = selection_remove_selection;
+ iface->select_all_selection = selection_select_all_selection;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atktable.cxx b/vcl/unx/gtk3/a11y/atktable.cxx
new file mode 100644
index 0000000000..021f3c19c4
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atktable.cxx
@@ -0,0 +1,647 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleTable.hpp>
+#include <com/sun/star/accessibility/XAccessibleTableSelection.hpp>
+#include <comphelper/sequence.hxx>
+#include <sal/log.hxx>
+
+using namespace ::com::sun::star;
+
+static AtkObject *
+atk_object_wrapper_conditional_ref( const uno::Reference< accessibility::XAccessible >& rxAccessible )
+{
+ if( rxAccessible.is() )
+ return atk_object_wrapper_ref( rxAccessible );
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+// FIXME
+static const gchar *
+getAsConst( std::u16string_view rString )
+{
+ static const int nMax = 10;
+ static OString aUgly[nMax];
+ static int nIdx = 0;
+ nIdx = (nIdx + 1) % nMax;
+ aUgly[nIdx] = OUStringToOString( rString, RTL_TEXTENCODING_UTF8 );
+ return aUgly[ nIdx ].getStr();
+}
+
+/*****************************************************************************/
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleTable>
+ getTable( AtkTable *pTable )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pTable );
+ if( pWrap )
+ {
+ if( !pWrap->mpTable.is() )
+ {
+ pWrap->mpTable.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpTable;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleTable>();
+}
+
+static css::uno::Reference<css::accessibility::XAccessibleTableSelection>
+ getTableSelection(AtkTable *pTable)
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER(pTable);
+ if (pWrap)
+ {
+ if (!pWrap->mpTableSelection.is())
+ {
+ pWrap->mpTableSelection.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpTableSelection;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleTableSelection>();
+}
+
+/*****************************************************************************/
+
+extern "C" {
+
+static AtkObject*
+table_wrapper_ref_at (AtkTable *table,
+ gint row,
+ gint column)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable = getTable( table );
+ if( pTable.is() )
+ return atk_object_wrapper_conditional_ref( pTable->getAccessibleCellAt( row, column ) );
+ }
+
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleCellAt()" );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_index_at (AtkTable *table,
+ gint row,
+ gint column)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ {
+ sal_Int64 nIndex = pTable->getAccessibleIndex( row, column );
+ if (nIndex > std::numeric_limits<gint>::max())
+ {
+ // use -2 when the child index is too large to fit into 32 bit to neither use the
+ // valid index of another cell nor -1, which might easily be interpreted as the cell
+ // not/no longer being valid
+ SAL_WARN("vcl.gtk", "table_wrapper_get_index_at: Child index exceeds maximum gint value, "
+ "returning -2.");
+ nIndex = -2;
+ }
+ return nIndex;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleIndex()" );
+ }
+
+ return -1;
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_column_at_index (AtkTable *table,
+ gint nIndex)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->getAccessibleColumn( nIndex );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleColumn()" );
+ }
+
+ return -1;
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_row_at_index( AtkTable *table,
+ gint nIndex )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->getAccessibleRow( nIndex );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleRow()" );
+ }
+
+ return -1;
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_n_columns( AtkTable *table )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->getAccessibleColumnCount();
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleColumnCount()" );
+ }
+
+ return -1;
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_n_rows( AtkTable *table )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->getAccessibleRowCount();
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleRowCount()" );
+ }
+
+ return -1;
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_column_extent_at( AtkTable *table,
+ gint row,
+ gint column )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->getAccessibleColumnExtentAt( row, column );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleColumnExtentAt()" );
+ }
+
+ return -1;
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_row_extent_at( AtkTable *table,
+ gint row,
+ gint column )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->getAccessibleRowExtentAt( row, column );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleRowExtentAt()" );
+ }
+
+ return -1;
+}
+
+/*****************************************************************************/
+
+static AtkObject *
+table_wrapper_get_caption( AtkTable *table )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return atk_object_wrapper_conditional_ref( pTable->getAccessibleCaption() );
+ }
+
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleCaption()" );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+static const gchar *
+table_wrapper_get_row_description( AtkTable *table,
+ gint row )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return getAsConst( pTable->getAccessibleRowDescription( row ) );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleRowDescription()" );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+static const gchar *
+table_wrapper_get_column_description( AtkTable *table,
+ gint column )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return getAsConst( pTable->getAccessibleColumnDescription( column ) );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleColumnDescription()" );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+static AtkObject *
+table_wrapper_get_row_header( AtkTable *table,
+ gint row )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ {
+ uno::Reference< accessibility::XAccessibleTable > xRowHeaders( pTable->getAccessibleRowHeaders() );
+ if( xRowHeaders.is() )
+ return atk_object_wrapper_conditional_ref( xRowHeaders->getAccessibleCellAt( row, 0 ) );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleRowHeaders()" );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+static AtkObject *
+table_wrapper_get_column_header( AtkTable *table,
+ gint column )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ {
+ uno::Reference< accessibility::XAccessibleTable > xColumnHeaders( pTable->getAccessibleColumnHeaders() );
+ if( xColumnHeaders.is() )
+ return atk_object_wrapper_conditional_ref( xColumnHeaders->getAccessibleCellAt( 0, column ) );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleColumnHeaders()" );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+static AtkObject *
+table_wrapper_get_summary( AtkTable *table )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ {
+ return atk_object_wrapper_conditional_ref( pTable->getAccessibleSummary() );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleSummary()" );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+static gint
+convertToGIntArray( const uno::Sequence< ::sal_Int32 >& aSequence, gint **pSelected )
+{
+ if( aSequence.hasElements() )
+ {
+ *pSelected = g_new( gint, aSequence.getLength() );
+
+ *pSelected = comphelper::sequenceToArray(*pSelected, aSequence);
+ }
+
+ return aSequence.getLength();
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_selected_columns( AtkTable *table,
+ gint **pSelected )
+{
+ *pSelected = nullptr;
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return convertToGIntArray( pTable->getSelectedAccessibleColumns(), pSelected );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectedAccessibleColumns()" );
+ }
+
+ return 0;
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_selected_rows( AtkTable *table,
+ gint **pSelected )
+{
+ *pSelected = nullptr;
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return convertToGIntArray( pTable->getSelectedAccessibleRows(), pSelected );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectedAccessibleRows()" );
+ }
+
+ return 0;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_is_column_selected( AtkTable *table,
+ gint column )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->isAccessibleColumnSelected( column );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in isAccessibleColumnSelected()" );
+ }
+
+ return 0;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_is_row_selected( AtkTable *table,
+ gint row )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->isAccessibleRowSelected( row );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in isAccessibleRowSelected()" );
+ }
+
+ return FALSE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_is_selected( AtkTable *table,
+ gint row,
+ gint column )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->isAccessibleSelected( row, column );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in isAccessibleSelected()" );
+ }
+
+ return FALSE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_add_row_selection(AtkTable *pTable, gint row)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTableSelection> xTableSelection = getTableSelection(pTable);
+ if (xTableSelection.is())
+ return xTableSelection->selectRow(row);
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in selectRow()" );
+ }
+
+ return false;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_remove_row_selection(AtkTable *pTable, gint row)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTableSelection> xTableSelection = getTableSelection(pTable);
+ if (xTableSelection.is())
+ return xTableSelection->unselectRow(row);
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in unselectRow()" );
+ }
+
+ return false;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_add_column_selection(AtkTable *pTable, gint column)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTableSelection> xTableSelection = getTableSelection(pTable);
+ if (xTableSelection.is())
+ return xTableSelection->selectColumn(column);
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in selectColumn()" );
+ }
+
+ return false;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_remove_column_selection(AtkTable *pTable, gint column)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTableSelection> xTableSelection = getTableSelection(pTable);
+ if (xTableSelection.is())
+ return xTableSelection->unselectColumn(column);
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in unselectColumn()" );
+ }
+
+ return false;
+}
+
+/*****************************************************************************/
+
+static void
+table_wrapper_set_caption( AtkTable *, AtkObject * )
+{ // meaningless helper
+}
+
+/*****************************************************************************/
+
+static void
+table_wrapper_set_column_description( AtkTable *, gint, const gchar * )
+{ // meaningless helper
+}
+
+/*****************************************************************************/
+
+static void
+table_wrapper_set_column_header( AtkTable *, gint, AtkObject * )
+{ // meaningless helper
+}
+
+/*****************************************************************************/
+
+static void
+table_wrapper_set_row_description( AtkTable *, gint, const gchar * )
+{ // meaningless helper
+}
+
+/*****************************************************************************/
+
+static void
+table_wrapper_set_row_header( AtkTable *, gint, AtkObject * )
+{ // meaningless helper
+}
+
+/*****************************************************************************/
+
+static void
+table_wrapper_set_summary( AtkTable *, AtkObject * )
+{ // meaningless helper
+}
+
+/*****************************************************************************/
+
+} // extern "C"
+
+void
+tableIfaceInit (AtkTableIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->ref_at = table_wrapper_ref_at;
+ iface->get_n_rows = table_wrapper_get_n_rows;
+ iface->get_n_columns = table_wrapper_get_n_columns;
+ iface->get_index_at = table_wrapper_get_index_at;
+ iface->get_column_at_index = table_wrapper_get_column_at_index;
+ iface->get_row_at_index = table_wrapper_get_row_at_index;
+ iface->is_row_selected = table_wrapper_is_row_selected;
+ iface->is_selected = table_wrapper_is_selected;
+ iface->get_selected_rows = table_wrapper_get_selected_rows;
+ iface->add_row_selection = table_wrapper_add_row_selection;
+ iface->remove_row_selection = table_wrapper_remove_row_selection;
+ iface->add_column_selection = table_wrapper_add_column_selection;
+ iface->remove_column_selection = table_wrapper_remove_column_selection;
+ iface->get_selected_columns = table_wrapper_get_selected_columns;
+ iface->is_column_selected = table_wrapper_is_column_selected;
+ iface->get_column_extent_at = table_wrapper_get_column_extent_at;
+ iface->get_row_extent_at = table_wrapper_get_row_extent_at;
+ iface->get_row_header = table_wrapper_get_row_header;
+ iface->set_row_header = table_wrapper_set_row_header;
+ iface->get_column_header = table_wrapper_get_column_header;
+ iface->set_column_header = table_wrapper_set_column_header;
+ iface->get_caption = table_wrapper_get_caption;
+ iface->set_caption = table_wrapper_set_caption;
+ iface->get_summary = table_wrapper_get_summary;
+ iface->set_summary = table_wrapper_set_summary;
+ iface->get_row_description = table_wrapper_get_row_description;
+ iface->set_row_description = table_wrapper_set_row_description;
+ iface->get_column_description = table_wrapper_get_column_description;
+ iface->set_column_description = table_wrapper_set_column_description;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atktablecell.cxx b/vcl/unx/gtk3/a11y/atktablecell.cxx
new file mode 100644
index 0000000000..35d681b062
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atktablecell.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/.
+ */
+
+#include "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleTable.hpp>
+#include <com/sun/star/accessibility/XAccessibleTableSelection.hpp>
+#include <sal/log.hxx>
+
+static css::uno::Reference<css::accessibility::XAccessibleContext>
+getContext(AtkTableCell* pTableCell)
+{
+ AtkObjectWrapper* pWrap = ATK_OBJECT_WRAPPER(pTableCell);
+ if (pWrap)
+ {
+ return pWrap->mpContext;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleContext>();
+}
+
+static css::uno::Reference<css::accessibility::XAccessibleTable>
+getTableParent(AtkTableCell* pTableCell)
+{
+ AtkObject* pParent = atk_object_get_parent(ATK_OBJECT(pTableCell));
+ if (!pParent)
+ return css::uno::Reference<css::accessibility::XAccessibleTable>();
+
+ AtkObjectWrapper* pWrap = ATK_OBJECT_WRAPPER(pParent);
+ if (pWrap)
+ {
+ if (!pWrap->mpTable.is())
+ {
+ pWrap->mpTable.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpTable;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleTable>();
+}
+
+extern "C" {
+
+static int tablecell_wrapper_get_column_span(AtkTableCell* cell)
+{
+ int nColumnExtent = -1;
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext = getContext(cell);
+ if (!xContext.is())
+ return -1;
+
+ css::uno::Reference<css::accessibility::XAccessibleTable> xTable = getTableParent(cell);
+ if (xTable.is())
+ {
+ const sal_Int64 nChildIndex = xContext->getAccessibleIndexInParent();
+ const sal_Int32 nRow = xTable->getAccessibleRow(nChildIndex);
+ const sal_Int32 nColumn = xTable->getAccessibleColumn(nChildIndex);
+ nColumnExtent = xTable->getAccessibleColumnExtentAt(nRow, nColumn);
+ }
+ }
+ catch (const css::uno::Exception&)
+ {
+ g_warning("Exception in tablecell_wrapper_get_column_span");
+ }
+
+ return nColumnExtent;
+}
+
+static GPtrArray* tablecell_wrapper_get_column_header_cells(AtkTableCell* cell)
+{
+ GPtrArray* pHeaderCells = g_ptr_array_new();
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext = getContext(cell);
+ if (!xContext.is())
+ return pHeaderCells;
+
+ css::uno::Reference<css::accessibility::XAccessibleTable> xTable = getTableParent(cell);
+ if (xTable.is())
+ {
+ const sal_Int64 nChildIndex = xContext->getAccessibleIndexInParent();
+ const sal_Int32 nCol = xTable->getAccessibleColumn(nChildIndex);
+ css::uno::Reference<css::accessibility::XAccessibleTable> xHeaders
+ = xTable->getAccessibleColumnHeaders();
+ if (!xHeaders.is())
+ return pHeaderCells;
+
+ for (sal_Int32 nRow = 0; nRow < xHeaders->getAccessibleRowCount(); nRow++)
+ {
+ css::uno::Reference<css::accessibility::XAccessible> xCell
+ = xHeaders->getAccessibleCellAt(nRow, nCol);
+ AtkObject* pCell = atk_object_wrapper_ref(xCell);
+ g_ptr_array_add(pHeaderCells, pCell);
+ }
+ }
+ }
+ catch (const css::uno::Exception&)
+ {
+ g_warning("Exception in tablecell_wrapper_get_column_header_cells");
+ }
+
+ return pHeaderCells;
+}
+
+static gboolean tablecell_wrapper_get_position(AtkTableCell* cell, gint* row, gint* column)
+{
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext = getContext(cell);
+ if (!xContext.is())
+ return false;
+
+ css::uno::Reference<css::accessibility::XAccessibleTable> xTable = getTableParent(cell);
+ if (xTable.is())
+ {
+ const sal_Int64 nChildIndex = xContext->getAccessibleIndexInParent();
+ *row = xTable->getAccessibleRow(nChildIndex);
+ *column = xTable->getAccessibleColumn(nChildIndex);
+ return true;
+ }
+ }
+ catch (const css::uno::Exception&)
+ {
+ g_warning("Exception in tablecell_wrapper_get_position()");
+ }
+
+ return false;
+}
+
+static gint tablecell_wrapper_get_row_span(AtkTableCell* cell)
+{
+ int nRowExtent = -1;
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext = getContext(cell);
+ if (!xContext.is())
+ return -1;
+
+ css::uno::Reference<css::accessibility::XAccessibleTable> xTable = getTableParent(cell);
+ if (xTable.is())
+ {
+ const sal_Int64 nChildIndex = xContext->getAccessibleIndexInParent();
+ const sal_Int32 nRow = xTable->getAccessibleRow(nChildIndex);
+ const sal_Int32 nColumn = xTable->getAccessibleColumn(nChildIndex);
+ nRowExtent = xTable->getAccessibleRowExtentAt(nRow, nColumn);
+ }
+ }
+ catch (const css::uno::Exception&)
+ {
+ g_warning("Exception in tablecell_wrapper_get_row_span");
+ }
+
+ return nRowExtent;
+}
+
+static GPtrArray* tablecell_wrapper_get_row_header_cells(AtkTableCell* cell)
+{
+ GPtrArray* pHeaderCells = g_ptr_array_new();
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext = getContext(cell);
+ if (!xContext.is())
+ return pHeaderCells;
+
+ css::uno::Reference<css::accessibility::XAccessibleTable> xTable = getTableParent(cell);
+ if (xTable.is())
+ {
+ const sal_Int64 nChildIndex = xContext->getAccessibleIndexInParent();
+ const sal_Int32 nRow = xTable->getAccessibleRow(nChildIndex);
+ css::uno::Reference<css::accessibility::XAccessibleTable> xHeaders
+ = xTable->getAccessibleRowHeaders();
+ if (!xHeaders.is())
+ return pHeaderCells;
+
+ for (sal_Int32 nCol = 0; nCol < xHeaders->getAccessibleColumnCount(); nCol++)
+ {
+ css::uno::Reference<css::accessibility::XAccessible> xCell
+ = xHeaders->getAccessibleCellAt(nRow, nCol);
+ AtkObject* pCell = atk_object_wrapper_ref(xCell);
+ g_ptr_array_add(pHeaderCells, pCell);
+ }
+ }
+ }
+ catch (const css::uno::Exception&)
+ {
+ g_warning("Exception in tablecell_wrapper_get_row_header_cells");
+ }
+
+ return pHeaderCells;
+}
+
+static gboolean tablecell_wrapper_get_row_column_span(AtkTableCell* cell, gint* row, gint* column,
+ gint* row_span, gint* column_span)
+{
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext = getContext(cell);
+ if (!xContext.is())
+ return -1;
+
+ css::uno::Reference<css::accessibility::XAccessibleTable> xTable = getTableParent(cell);
+ if (xTable.is())
+ {
+ const sal_Int64 nChildIndex = xContext->getAccessibleIndexInParent();
+ const sal_Int32 nRow = xTable->getAccessibleRow(nChildIndex);
+ const sal_Int32 nColumn = xTable->getAccessibleColumn(nChildIndex);
+ *row = nRow;
+ *column = nColumn;
+ *row_span = xTable->getAccessibleRowExtentAt(nRow, nColumn);
+ *column_span = xTable->getAccessibleColumnExtentAt(nRow, nColumn);
+ return true;
+ }
+ }
+ catch (const css::uno::Exception&)
+ {
+ g_warning("Exception in tablecell_wrapper_get_row_column_span");
+ }
+
+ return false;
+}
+
+static AtkObject* tablecell_wrapper_get_table(AtkTableCell* cell)
+{
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext = getContext(cell);
+ if (!xContext.is())
+ return nullptr;
+
+ css::uno::Reference<css::accessibility::XAccessible> xParent
+ = getContext(cell)->getAccessibleParent();
+ if (!xParent.is())
+ return nullptr;
+
+ return atk_object_wrapper_ref(xParent);
+ }
+
+ catch (const css::uno::Exception&)
+ {
+ g_warning("Exception in tablecell_wrapper_get_table()");
+ }
+
+ return nullptr;
+}
+
+} // extern "C"
+
+void tablecellIfaceInit(AtkTableCellIface* iface)
+{
+ g_return_if_fail(iface != nullptr);
+
+ iface->get_column_span = tablecell_wrapper_get_column_span;
+ iface->get_column_header_cells = tablecell_wrapper_get_column_header_cells;
+ iface->get_position = tablecell_wrapper_get_position;
+ iface->get_row_span = tablecell_wrapper_get_row_span;
+ iface->get_row_header_cells = tablecell_wrapper_get_row_header_cells;
+ iface->get_row_column_span = tablecell_wrapper_get_row_column_span;
+ iface->get_table = tablecell_wrapper_get_table;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atktext.cxx b/vcl/unx/gtk3/a11y/atktext.cxx
new file mode 100644
index 0000000000..8b1ab67422
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atktext.cxx
@@ -0,0 +1,881 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "atkwrapper.hxx"
+#include "atktextattributes.hxx"
+#include <algorithm>
+
+#include <osl/diagnose.h>
+#include <rtl/character.hxx>
+
+#include <com/sun/star/accessibility/AccessibleScrollType.hpp>
+#include <com/sun/star/accessibility/AccessibleTextType.hpp>
+#include <com/sun/star/accessibility/TextSegment.hpp>
+#include <com/sun/star/accessibility/XAccessibleMultiLineText.hpp>
+#include <com/sun/star/accessibility/XAccessibleText.hpp>
+#include <com/sun/star/accessibility/XAccessibleTextAttributes.hpp>
+#include <com/sun/star/accessibility/XAccessibleTextMarkup.hpp>
+#include <com/sun/star/lang/NoSupportException.hpp>
+#include <com/sun/star/text/TextMarkupType.hpp>
+
+using namespace ::com::sun::star;
+
+static sal_Int16
+text_type_from_boundary(AtkTextBoundary boundary_type)
+{
+ switch(boundary_type)
+ {
+ case ATK_TEXT_BOUNDARY_CHAR:
+ return accessibility::AccessibleTextType::CHARACTER;
+ case ATK_TEXT_BOUNDARY_WORD_START:
+ case ATK_TEXT_BOUNDARY_WORD_END:
+ return accessibility::AccessibleTextType::WORD;
+ case ATK_TEXT_BOUNDARY_SENTENCE_START:
+ case ATK_TEXT_BOUNDARY_SENTENCE_END:
+ return accessibility::AccessibleTextType::SENTENCE;
+ case ATK_TEXT_BOUNDARY_LINE_START:
+ case ATK_TEXT_BOUNDARY_LINE_END:
+ return accessibility::AccessibleTextType::LINE;
+ default:
+ return -1;
+ }
+}
+
+/*****************************************************************************/
+
+#if ATK_CHECK_VERSION(2,32,0)
+static accessibility::AccessibleScrollType
+scroll_type_from_scroll_type(AtkScrollType type)
+{
+ switch(type)
+ {
+ case ATK_SCROLL_TOP_LEFT:
+ return accessibility::AccessibleScrollType_SCROLL_TOP_LEFT;
+ case ATK_SCROLL_BOTTOM_RIGHT:
+ return accessibility::AccessibleScrollType_SCROLL_BOTTOM_RIGHT;
+ case ATK_SCROLL_TOP_EDGE:
+ return accessibility::AccessibleScrollType_SCROLL_TOP_EDGE;
+ case ATK_SCROLL_BOTTOM_EDGE:
+ return accessibility::AccessibleScrollType_SCROLL_BOTTOM_EDGE;
+ case ATK_SCROLL_LEFT_EDGE:
+ return accessibility::AccessibleScrollType_SCROLL_LEFT_EDGE;
+ case ATK_SCROLL_RIGHT_EDGE:
+ return accessibility::AccessibleScrollType_SCROLL_RIGHT_EDGE;
+ case ATK_SCROLL_ANYWHERE:
+ return accessibility::AccessibleScrollType_SCROLL_ANYWHERE;
+ default:
+ throw lang::NoSupportException();
+ }
+}
+#endif
+
+/*****************************************************************************/
+
+static gchar *
+adjust_boundaries( css::uno::Reference<css::accessibility::XAccessibleText> const & pText,
+ accessibility::TextSegment const & rTextSegment,
+ AtkTextBoundary boundary_type,
+ gint * start_offset, gint * end_offset )
+{
+ accessibility::TextSegment aTextSegment;
+ OUString aString;
+ gint start = 0, end = 0;
+
+ if( !rTextSegment.SegmentText.isEmpty() )
+ {
+ switch(boundary_type)
+ {
+ case ATK_TEXT_BOUNDARY_CHAR:
+ if ((rTextSegment.SegmentEnd - rTextSegment.SegmentStart) == 1
+ && rtl::isSurrogate(rTextSegment.SegmentText[0]))
+ return nullptr;
+ [[fallthrough]];
+ case ATK_TEXT_BOUNDARY_LINE_START:
+ case ATK_TEXT_BOUNDARY_LINE_END:
+ case ATK_TEXT_BOUNDARY_SENTENCE_START:
+ start = rTextSegment.SegmentStart;
+ end = rTextSegment.SegmentEnd;
+ aString = rTextSegment.SegmentText;
+ break;
+
+ // the OOo break iterator behaves as SENTENCE_START
+ case ATK_TEXT_BOUNDARY_SENTENCE_END:
+ start = rTextSegment.SegmentStart;
+ end = rTextSegment.SegmentEnd;
+
+ if( start > 0 )
+ --start;
+ if( end > 0 && end < pText->getCharacterCount() - 1 )
+ --end;
+
+ aString = pText->getTextRange(start, end);
+ break;
+
+ case ATK_TEXT_BOUNDARY_WORD_START:
+ start = rTextSegment.SegmentStart;
+
+ // Determine the start index of the next segment
+ aTextSegment = pText->getTextBehindIndex(rTextSegment.SegmentEnd,
+ text_type_from_boundary(boundary_type));
+ if( !aTextSegment.SegmentText.isEmpty() )
+ end = aTextSegment.SegmentStart;
+ else
+ end = pText->getCharacterCount();
+
+ aString = pText->getTextRange(start, end);
+ break;
+
+ case ATK_TEXT_BOUNDARY_WORD_END:
+ end = rTextSegment.SegmentEnd;
+
+ // Determine the end index of the previous segment
+ aTextSegment = pText->getTextBeforeIndex(rTextSegment.SegmentStart,
+ text_type_from_boundary(boundary_type));
+ if( !aTextSegment.SegmentText.isEmpty() )
+ start = aTextSegment.SegmentEnd;
+ else
+ start = 0;
+
+ aString = pText->getTextRange(start, end);
+ break;
+
+ default:
+ return nullptr;
+ }
+ }
+
+ *start_offset = start;
+ *end_offset = end;
+
+ return OUStringToGChar(aString);
+}
+
+/*****************************************************************************/
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleText>
+ getText( AtkText *pText )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
+ if( pWrap )
+ {
+ if( !pWrap->mpText.is() )
+ {
+ pWrap->mpText.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpText;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleText>();
+}
+
+/*****************************************************************************/
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleTextMarkup>
+ getTextMarkup( AtkText *pText )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
+ if( pWrap )
+ {
+ if( !pWrap->mpTextMarkup.is() )
+ {
+ pWrap->mpTextMarkup.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpTextMarkup;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleTextMarkup>();
+}
+
+/*****************************************************************************/
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
+ getTextAttributes( AtkText *pText )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
+ if( pWrap )
+ {
+ if( !pWrap->mpTextAttributes.is() )
+ {
+ pWrap->mpTextAttributes.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpTextAttributes;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleTextAttributes>();
+}
+
+/*****************************************************************************/
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleMultiLineText>
+ getMultiLineText( AtkText *pText )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
+ if( pWrap )
+ {
+ if( !pWrap->mpMultiLineText.is() )
+ {
+ pWrap->mpMultiLineText.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpMultiLineText;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleMultiLineText>();
+}
+
+/*****************************************************************************/
+
+extern "C" {
+
+static gchar *
+text_wrapper_get_text (AtkText *text,
+ gint start_offset,
+ gint end_offset)
+{
+ gchar * ret = nullptr;
+
+ g_return_val_if_fail( (end_offset == -1) || (end_offset >= start_offset), nullptr );
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ {
+ OUString aText;
+ sal_Int32 n = pText->getCharacterCount();
+
+ if( start_offset < n )
+ {
+ if( -1 == end_offset )
+ aText = pText->getTextRange(start_offset, n - start_offset);
+ else
+ aText = pText->getTextRange(start_offset, end_offset);
+ }
+
+ ret = g_strdup( OUStringToOString(aText, RTL_TEXTENCODING_UTF8 ).getStr() );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getText()" );
+ }
+
+ return ret;
+}
+
+static gchar *
+text_wrapper_get_text_after_offset (AtkText *text,
+ gint offset,
+ AtkTextBoundary boundary_type,
+ gint *start_offset,
+ gint *end_offset)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ {
+ accessibility::TextSegment aTextSegment = pText->getTextBehindIndex(offset, text_type_from_boundary(boundary_type));
+ return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in get_text_after_offset()" );
+ }
+
+ return nullptr;
+}
+
+static gchar *
+text_wrapper_get_text_at_offset (AtkText *text,
+ gint offset,
+ AtkTextBoundary boundary_type,
+ gint *start_offset,
+ gint *end_offset)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ {
+ /* If the user presses the 'End' key, the caret will be placed behind the last character,
+ * which is the same index as the first character of the next line. In atk the magic offset
+ * '-2' is used to cover this special case.
+ */
+ if (
+ -2 == offset &&
+ (ATK_TEXT_BOUNDARY_LINE_START == boundary_type ||
+ ATK_TEXT_BOUNDARY_LINE_END == boundary_type)
+ )
+ {
+ css::uno::Reference<
+ css::accessibility::XAccessibleMultiLineText> pMultiLineText
+ = getMultiLineText( text );
+ if( pMultiLineText.is() )
+ {
+ accessibility::TextSegment aTextSegment = pMultiLineText->getTextAtLineWithCaret();
+ return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
+ }
+ }
+
+ accessibility::TextSegment aTextSegment = pText->getTextAtIndex(offset, text_type_from_boundary(boundary_type));
+ return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in get_text_at_offset()" );
+ }
+
+ return nullptr;
+}
+
+static gunichar
+text_wrapper_get_character_at_offset (AtkText *text,
+ gint offset)
+{
+ gint start, end;
+ gunichar uc = 0xFFFFFFFF;
+
+ gchar * char_as_string =
+ text_wrapper_get_text_at_offset(text, offset, ATK_TEXT_BOUNDARY_CHAR,
+ &start, &end);
+ if( char_as_string )
+ {
+ uc = g_utf8_get_char( char_as_string );
+ g_free( char_as_string );
+ }
+
+ return uc;
+}
+
+static gchar *
+text_wrapper_get_text_before_offset (AtkText *text,
+ gint offset,
+ AtkTextBoundary boundary_type,
+ gint *start_offset,
+ gint *end_offset)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ {
+ accessibility::TextSegment aTextSegment = pText->getTextBeforeIndex(offset, text_type_from_boundary(boundary_type));
+ return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in text_before_offset()" );
+ }
+
+ return nullptr;
+}
+
+static gint
+text_wrapper_get_caret_offset (AtkText *text)
+{
+ gint offset = -1;
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ offset = pText->getCaretPosition();
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getCaretPosition()" );
+ }
+
+ return offset;
+}
+
+static gboolean
+text_wrapper_set_caret_offset (AtkText *text,
+ gint offset)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ return pText->setCaretPosition( offset );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in setCaretPosition()" );
+ }
+
+ return FALSE;
+}
+
+// #i92232#
+static AtkAttributeSet*
+handle_text_markup_as_run_attribute( css::uno::Reference<css::accessibility::XAccessibleTextMarkup> const & pTextMarkup,
+ const gint nTextMarkupType,
+ const gint offset,
+ AtkAttributeSet* pSet,
+ gint *start_offset,
+ gint *end_offset )
+{
+ const gint nTextMarkupCount( pTextMarkup->getTextMarkupCount( nTextMarkupType ) );
+ for ( gint nTextMarkupIndex = 0;
+ nTextMarkupIndex < nTextMarkupCount;
+ ++nTextMarkupIndex )
+ {
+ accessibility::TextSegment aTextSegment =
+ pTextMarkup->getTextMarkup( nTextMarkupIndex, nTextMarkupType );
+ const gint nStartOffsetTextMarkup = aTextSegment.SegmentStart;
+ const gint nEndOffsetTextMarkup = aTextSegment.SegmentEnd;
+ if ( nStartOffsetTextMarkup <= offset )
+ {
+ if ( offset < nEndOffsetTextMarkup )
+ {
+ // text markup at <offset>
+ *start_offset = ::std::max( *start_offset,
+ nStartOffsetTextMarkup );
+ *end_offset = ::std::min( *end_offset,
+ nEndOffsetTextMarkup );
+ switch ( nTextMarkupType )
+ {
+ case css::text::TextMarkupType::SPELLCHECK:
+ {
+ pSet = attribute_set_prepend_misspelled( pSet );
+ }
+ break;
+ case css::text::TextMarkupType::TRACK_CHANGE_INSERTION:
+ {
+ pSet = attribute_set_prepend_tracked_change_insertion( pSet );
+ }
+ break;
+ case css::text::TextMarkupType::TRACK_CHANGE_DELETION:
+ {
+ pSet = attribute_set_prepend_tracked_change_deletion( pSet );
+ }
+ break;
+ case css::text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE:
+ {
+ pSet = attribute_set_prepend_tracked_change_formatchange( pSet );
+ }
+ break;
+ default:
+ {
+ OSL_ASSERT( false );
+ }
+ }
+ break; // no further iteration needed.
+ }
+ else
+ {
+ *start_offset = ::std::max( *start_offset,
+ nEndOffsetTextMarkup );
+ // continue iteration.
+ }
+ }
+ else
+ {
+ *end_offset = ::std::min( *end_offset,
+ nStartOffsetTextMarkup );
+ break; // no further iteration.
+ }
+ }
+
+ return pSet;
+}
+
+static AtkAttributeSet *
+text_wrapper_get_run_attributes( AtkText *text,
+ gint offset,
+ gint *start_offset,
+ gint *end_offset)
+{
+ AtkAttributeSet *pSet = nullptr;
+
+ try {
+ bool bOffsetsAreValid = false;
+
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is())
+ {
+ uno::Sequence< beans::PropertyValue > aAttributeList;
+
+ css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
+ pTextAttributes = getTextAttributes( text );
+ if(pTextAttributes.is()) // Text attributes are available for paragraphs only
+ {
+ aAttributeList = pTextAttributes->getRunAttributes( offset, uno::Sequence< OUString > () );
+ }
+ else // For other text objects use character attributes
+ {
+ aAttributeList = pText->getCharacterAttributes( offset, uno::Sequence< OUString > () );
+ }
+
+ pSet = attribute_set_new_from_property_values( aAttributeList, true, text );
+ // #i100938#
+ // - always provide start_offset and end_offset
+ {
+ accessibility::TextSegment aTextSegment =
+ pText->getTextAtIndex(offset, accessibility::AccessibleTextType::ATTRIBUTE_RUN);
+
+ *start_offset = aTextSegment.SegmentStart;
+ // #i100938#
+ // Do _not_ increment the end_offset provide by <accessibility::TextSegment> instance
+ *end_offset = aTextSegment.SegmentEnd;
+ bOffsetsAreValid = true;
+ }
+ }
+
+ // Special handling for misspelled text
+ // #i92232#
+ // - add special handling for tracked changes and refactor the
+ // corresponding code for handling misspelled text.
+ css::uno::Reference<css::accessibility::XAccessibleTextMarkup>
+ pTextMarkup = getTextMarkup( text );
+ if( pTextMarkup.is() )
+ {
+ // Get attribute run here if it hasn't been done before
+ if (!bOffsetsAreValid && pText.is())
+ {
+ accessibility::TextSegment aAttributeTextSegment =
+ pText->getTextAtIndex(offset, accessibility::AccessibleTextType::ATTRIBUTE_RUN);
+ *start_offset = aAttributeTextSegment.SegmentStart;
+ *end_offset = aAttributeTextSegment.SegmentEnd;
+ }
+ // handle misspelled text
+ pSet = handle_text_markup_as_run_attribute(
+ pTextMarkup,
+ css::text::TextMarkupType::SPELLCHECK,
+ offset, pSet, start_offset, end_offset );
+ // handle tracked changes
+ pSet = handle_text_markup_as_run_attribute(
+ pTextMarkup,
+ css::text::TextMarkupType::TRACK_CHANGE_INSERTION,
+ offset, pSet, start_offset, end_offset );
+ pSet = handle_text_markup_as_run_attribute(
+ pTextMarkup,
+ css::text::TextMarkupType::TRACK_CHANGE_DELETION,
+ offset, pSet, start_offset, end_offset );
+ pSet = handle_text_markup_as_run_attribute(
+ pTextMarkup,
+ css::text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE,
+ offset, pSet, start_offset, end_offset );
+ }
+ }
+ catch(const uno::Exception&){
+
+ g_warning( "Exception in get_run_attributes()" );
+
+ if( pSet )
+ {
+ atk_attribute_set_free( pSet );
+ pSet = nullptr;
+ }
+ }
+
+ return pSet;
+}
+
+/*****************************************************************************/
+
+static AtkAttributeSet *
+text_wrapper_get_default_attributes( AtkText *text )
+{
+ AtkAttributeSet *pSet = nullptr;
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
+ pTextAttributes = getTextAttributes( text );
+ if( pTextAttributes.is() )
+ {
+ uno::Sequence< beans::PropertyValue > aAttributeList =
+ pTextAttributes->getDefaultAttributes( uno::Sequence< OUString > () );
+
+ pSet = attribute_set_new_from_property_values( aAttributeList, false, text );
+ }
+ }
+ catch(const uno::Exception&) {
+
+ g_warning( "Exception in get_default_attributes()" );
+
+ if( pSet )
+ {
+ atk_attribute_set_free( pSet );
+ pSet = nullptr;
+ }
+ }
+
+ return pSet;
+}
+
+/*****************************************************************************/
+
+static void
+text_wrapper_get_character_extents( AtkText *text,
+ gint offset,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height,
+ AtkCoordType coords )
+{
+ *x = *y = *width = *height = -1;
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ {
+ awt::Rectangle aRect = pText->getCharacterBounds( offset );
+
+ gint origin_x = 0;
+ gint origin_y = 0;
+
+ if (coords == ATK_XY_SCREEN || coords == ATK_XY_WINDOW)
+ {
+ g_return_if_fail( ATK_IS_COMPONENT( text ) );
+ gint nWidth = -1;
+ gint nHeight = -1;
+ atk_component_get_extents(ATK_COMPONENT(text), &origin_x, &origin_y, &nWidth, &nHeight, coords);
+ }
+
+ *x = aRect.X + origin_x;
+ *y = aRect.Y + origin_y;
+ *width = aRect.Width;
+ *height = aRect.Height;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getCharacterBounds" );
+ }
+}
+
+static gint
+text_wrapper_get_character_count (AtkText *text)
+{
+ gint rv = 0;
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ rv = pText->getCharacterCount();
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getCharacterCount" );
+ }
+
+ return rv;
+}
+
+static gint
+text_wrapper_get_offset_at_point (AtkText *text,
+ gint x,
+ gint y,
+ AtkCoordType coords)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ {
+ gint origin_x = 0;
+ gint origin_y = 0;
+
+ if (coords == ATK_XY_SCREEN || coords == ATK_XY_WINDOW)
+ {
+ g_return_val_if_fail( ATK_IS_COMPONENT( text ), -1 );
+ gint nWidth = -1;
+ gint nHeight = -1;
+ atk_component_get_extents(ATK_COMPONENT(text), &origin_x, &origin_y, &nWidth, &nHeight, coords);
+ }
+
+ return pText->getIndexAtPoint( awt::Point(x - origin_x, y - origin_y) );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getIndexAtPoint" );
+ }
+
+ return -1;
+}
+
+// FIXME: the whole series of selections API is problematic ...
+
+static gint
+text_wrapper_get_n_selections (AtkText *text)
+{
+ gint rv = 0;
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ rv = ( pText->getSelectionEnd() > pText->getSelectionStart() ) ? 1 : 0;
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectionEnd() or getSelectionStart()" );
+ }
+
+ return rv;
+}
+
+static gchar *
+text_wrapper_get_selection (AtkText *text,
+ gint selection_num,
+ gint *start_offset,
+ gint *end_offset)
+{
+ g_return_val_if_fail( selection_num == 0, FALSE );
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ {
+ *start_offset = pText->getSelectionStart();
+ *end_offset = pText->getSelectionEnd();
+
+ return OUStringToGChar( pText->getSelectedText() );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectionEnd(), getSelectionStart() or getSelectedText()" );
+ }
+
+ return nullptr;
+}
+
+static gboolean
+text_wrapper_add_selection (AtkText *text,
+ gint start_offset,
+ gint end_offset)
+{
+ // FIXME: can we try to be more compatible by expanding an
+ // existing adjacent selection ?
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ return pText->setSelection( start_offset, end_offset ); // ?
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in setSelection()" );
+ }
+
+ return FALSE;
+}
+
+static gboolean
+text_wrapper_remove_selection (AtkText *text,
+ gint selection_num)
+{
+ g_return_val_if_fail( selection_num == 0, FALSE );
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ return pText->setSelection( 0, 0 ); // ?
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in setSelection()" );
+ }
+
+ return FALSE;
+}
+
+static gboolean
+text_wrapper_set_selection (AtkText *text,
+ gint selection_num,
+ gint start_offset,
+ gint end_offset)
+{
+ g_return_val_if_fail( selection_num == 0, FALSE );
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ return pText->setSelection( start_offset, end_offset );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in setSelection()" );
+ }
+
+ return FALSE;
+}
+
+#if ATK_CHECK_VERSION(2,32,0)
+static gboolean
+text_wrapper_scroll_substring_to(AtkText *text,
+ gint start_offset,
+ gint end_offset,
+ AtkScrollType scroll_type)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+
+ if( pText.is() )
+ return pText->scrollSubstringTo( start_offset, end_offset,
+ scroll_type_from_scroll_type( scroll_type ) );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in scrollSubstringTo()" );
+ }
+
+ return FALSE;
+}
+#endif
+
+} // extern "C"
+
+void
+textIfaceInit (AtkTextIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->get_text = text_wrapper_get_text;
+ iface->get_character_at_offset = text_wrapper_get_character_at_offset;
+ iface->get_text_before_offset = text_wrapper_get_text_before_offset;
+ iface->get_text_at_offset = text_wrapper_get_text_at_offset;
+ iface->get_text_after_offset = text_wrapper_get_text_after_offset;
+ iface->get_caret_offset = text_wrapper_get_caret_offset;
+ iface->set_caret_offset = text_wrapper_set_caret_offset;
+ iface->get_character_count = text_wrapper_get_character_count;
+ iface->get_n_selections = text_wrapper_get_n_selections;
+ iface->get_selection = text_wrapper_get_selection;
+ iface->add_selection = text_wrapper_add_selection;
+ iface->remove_selection = text_wrapper_remove_selection;
+ iface->set_selection = text_wrapper_set_selection;
+ iface->get_run_attributes = text_wrapper_get_run_attributes;
+ iface->get_default_attributes = text_wrapper_get_default_attributes;
+ iface->get_character_extents = text_wrapper_get_character_extents;
+ iface->get_offset_at_point = text_wrapper_get_offset_at_point;
+#if ATK_CHECK_VERSION(2,32,0)
+ iface->scroll_substring_to = text_wrapper_scroll_substring_to;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atktextattributes.cxx b/vcl/unx/gtk3/a11y/atktextattributes.cxx
new file mode 100644
index 0000000000..25b43f480a
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atktextattributes.cxx
@@ -0,0 +1,1388 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "atktextattributes.hxx"
+
+#include <com/sun/star/awt/FontSlant.hpp>
+#include <com/sun/star/awt/FontStrikeout.hpp>
+#include <com/sun/star/awt/FontUnderline.hpp>
+
+#include <com/sun/star/style/CaseMap.hpp>
+#include <com/sun/star/style/LineSpacing.hpp>
+#include <com/sun/star/style/LineSpacingMode.hpp>
+#include <com/sun/star/style/ParagraphAdjust.hpp>
+#include <com/sun/star/style/TabAlign.hpp>
+#include <com/sun/star/style/TabStop.hpp>
+
+#include <com/sun/star/text/WritingMode2.hpp>
+
+#include "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleComponent.hpp>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <tools/UnitConversion.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <stdio.h>
+#include <string.h>
+
+using namespace ::com::sun::star;
+
+typedef gchar* (* AtkTextAttrFunc) ( const uno::Any& rAny );
+typedef bool (* TextPropertyValueFunc) ( uno::Any& rAny, const gchar * value );
+
+#define STRNCMP_PARAM( s ) s,sizeof( s )-1
+
+/*****************************************************************************/
+
+static AtkTextAttribute atk_text_attribute_paragraph_style = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_font_effect = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_decoration = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_line_height = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_rotation = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_shadow = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_tab_interval = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_tab_stops = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_writing_mode = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_vertical_align = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_misspelled = ATK_TEXT_ATTR_INVALID;
+// #i92232#
+static AtkTextAttribute atk_text_attribute_tracked_change = ATK_TEXT_ATTR_INVALID;
+// #i92233#
+static AtkTextAttribute atk_text_attribute_mm_to_pixel_ratio = ATK_TEXT_ATTR_INVALID;
+
+/*****************************************************************************/
+
+/**
+ * !! IMPORTANT NOTE !! : when adding items to this list, KEEP THE LIST SORTED
+ * and re-arrange the enum values accordingly.
+ */
+
+namespace {
+
+enum ExportedAttribute
+{
+ TEXT_ATTRIBUTE_BACKGROUND_COLOR = 0,
+ TEXT_ATTRIBUTE_CASEMAP,
+ TEXT_ATTRIBUTE_FOREGROUND_COLOR,
+ TEXT_ATTRIBUTE_CONTOURED,
+ TEXT_ATTRIBUTE_CHAR_ESCAPEMENT,
+ TEXT_ATTRIBUTE_BLINKING,
+ TEXT_ATTRIBUTE_FONT_NAME,
+ TEXT_ATTRIBUTE_HEIGHT,
+ TEXT_ATTRIBUTE_HIDDEN,
+ TEXT_ATTRIBUTE_KERNING,
+ TEXT_ATTRIBUTE_LOCALE,
+ TEXT_ATTRIBUTE_POSTURE,
+ TEXT_ATTRIBUTE_RELIEF,
+ TEXT_ATTRIBUTE_ROTATION,
+ TEXT_ATTRIBUTE_SCALE,
+ TEXT_ATTRIBUTE_SHADOWED,
+ TEXT_ATTRIBUTE_STRIKETHROUGH,
+ TEXT_ATTRIBUTE_UNDERLINE,
+ TEXT_ATTRIBUTE_WEIGHT,
+ // #i92233#
+ TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO,
+ TEXT_ATTRIBUTE_JUSTIFICATION,
+ TEXT_ATTRIBUTE_BOTTOM_MARGIN,
+ TEXT_ATTRIBUTE_FIRST_LINE_INDENT,
+ TEXT_ATTRIBUTE_LEFT_MARGIN,
+ TEXT_ATTRIBUTE_LINE_SPACING,
+ TEXT_ATTRIBUTE_RIGHT_MARGIN,
+ TEXT_ATTRIBUTE_STYLE_NAME,
+ TEXT_ATTRIBUTE_TAB_STOPS,
+ TEXT_ATTRIBUTE_TOP_MARGIN,
+ TEXT_ATTRIBUTE_WRITING_MODE,
+ TEXT_ATTRIBUTE_LAST
+};
+
+}
+
+static const char * ExportedTextAttributes[TEXT_ATTRIBUTE_LAST] =
+{
+ "CharBackColor", // TEXT_ATTRIBUTE_BACKGROUND_COLOR
+ "CharCaseMap", // TEXT_ATTRIBUTE_CASEMAP
+ "CharColor", // TEXT_ATTRIBUTE_FOREGROUND_COLOR
+ "CharContoured", // TEXT_ATTRIBUTE_CONTOURED
+ "CharEscapement", // TEXT_ATTRIBUTE_CHAR_ESCAPEMENT
+ "CharFlash", // TEXT_ATTRIBUTE_BLINKING
+ "CharFontName", // TEXT_ATTRIBUTE_FONT_NAME
+ "CharHeight", // TEXT_ATTRIBUTE_HEIGHT
+ "CharHidden", // TEXT_ATTRIBUTE_HIDDEN
+ "CharKerning", // TEXT_ATTRIBUTE_KERNING
+ "CharLocale", // TEXT_ATTRIBUTE_LOCALE
+ "CharPosture", // TEXT_ATTRIBUTE_POSTURE
+ "CharRelief", // TEXT_ATTRIBUTE_RELIEF
+ "CharRotation", // TEXT_ATTRIBUTE_ROTATION
+ "CharScaleWidth", // TEXT_ATTRIBUTE_SCALE
+ "CharShadowed", // TEXT_ATTRIBUTE_SHADOWED
+ "CharStrikeout", // TEXT_ATTRIBUTE_STRIKETHROUGH
+ "CharUnderline", // TEXT_ATTRIBUTE_UNDERLINE
+ "CharWeight", // TEXT_ATTRIBUTE_WEIGHT
+ // #i92233#
+ "MMToPixelRatio", // TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO
+ "ParaAdjust", // TEXT_ATTRIBUTE_JUSTIFICATION
+ "ParaBottomMargin", // TEXT_ATTRIBUTE_BOTTOM_MARGIN
+ "ParaFirstLineIndent", // TEXT_ATTRIBUTE_FIRST_LINE_INDENT
+ "ParaLeftMargin", // TEXT_ATTRIBUTE_LEFT_MARGIN
+ "ParaLineSpacing", // TEXT_ATTRIBUTE_LINE_SPACING
+ "ParaRightMargin", // TEXT_ATTRIBUTE_RIGHT_MARGIN
+ "ParaStyleName", // TEXT_ATTRIBUTE_STYLE_NAME
+ "ParaTabStops", // TEXT_ATTRIBUTE_TAB_STOPS
+ "ParaTopMargin", // TEXT_ATTRIBUTE_TOP_MARGIN
+ "WritingMode" // TEXT_ATTRIBUTE_WRITING_MODE
+};
+
+/*****************************************************************************/
+
+static gchar*
+get_value( const uno::Sequence< beans::PropertyValue >& rAttributeList,
+ sal_Int32 nIndex, AtkTextAttrFunc func )
+{
+ if( nIndex != -1 )
+ return func(rAttributeList[nIndex].Value);
+
+ return nullptr;
+}
+
+#define get_bool_value( list, index ) get_value( list, index, Bool2String )
+#define get_height_value( list, index ) get_value( list, index, Float2String )
+#define get_justification_value( list, index ) get_value( list, index, Adjust2Justification )
+#define get_cmm_value( list, index ) get_value( list, index, CMM2UnitString )
+#define get_scale_width( list, index ) get_value( list, index, Scale2String )
+#define get_strikethrough_value( list, index ) get_value( list, index, Strikeout2String )
+#define get_string_value( list, index ) get_value( list, index, GetString )
+#define get_style_value( list, index ) get_value( list, index, FontSlant2Style )
+#define get_underline_value( list, index ) get_value( list, index, Underline2String )
+#define get_variant_value( list, index ) get_value( list, index, CaseMap2String )
+#define get_weight_value( list, index ) get_value( list, index, Weight2String )
+#define get_language_string( list, index ) get_value( list, index, Locale2String )
+
+/*****************************************************************************/
+
+static bool
+InvalidValue( uno::Any&, const gchar * )
+{
+ return false;
+}
+
+/*****************************************************************************/
+
+static gchar*
+Float2String(const uno::Any& rAny)
+{
+ return g_strdup_printf( "%g", rAny.get<float>() );
+}
+
+static bool
+String2Float( uno::Any& rAny, const gchar * value )
+{
+ float fval;
+
+ if( 1 != sscanf( value, "%g", &fval ) )
+ return false;
+
+ rAny <<= fval;
+ return true;
+}
+
+/*****************************************************************************/
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleComponent>
+ getComponent( AtkText *pText )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
+ if( pWrap )
+ {
+ if( !pWrap->mpComponent.is() )
+ {
+ pWrap->mpComponent.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpComponent;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleComponent>();
+}
+
+static gchar*
+get_color_value(const uno::Sequence< beans::PropertyValue >& rAttributeList,
+ const sal_Int32 * pIndexArray,
+ ExportedAttribute attr,
+ AtkText * text)
+{
+ sal_Int32 nColor = -1; // AUTOMATIC
+ sal_Int32 nIndex = pIndexArray[attr];
+
+ if( nIndex != -1 )
+ nColor = rAttributeList[nIndex].Value.get<sal_Int32>();
+
+ /*
+ * Check for color value for 100% alpha white, which means
+ * "automatic". Grab the RGB value from XAccessibleComponent
+ * in this case.
+ */
+
+ if( (nColor == -1) && text )
+ {
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleComponent>
+ pComponent = getComponent( text );
+ if( pComponent.is() )
+ {
+ switch( attr )
+ {
+ case TEXT_ATTRIBUTE_BACKGROUND_COLOR:
+ nColor = pComponent->getBackground();
+ break;
+ case TEXT_ATTRIBUTE_FOREGROUND_COLOR:
+ nColor = pComponent->getForeground();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ catch(const uno::Exception&) {
+ g_warning( "Exception in get[Fore|Back]groundColor()" );
+ }
+ }
+
+ if( nColor != -1 )
+ {
+ sal_uInt8 blue = nColor & 0xFF;
+ sal_uInt8 green = (nColor >> 8) & 0xFF;
+ sal_uInt8 red = (nColor >> 16) & 0xFF;
+
+ return g_strdup_printf( "%u,%u,%u", red, green, blue );
+ }
+
+ return nullptr;
+}
+
+static bool
+String2Color( uno::Any& rAny, const gchar * value )
+{
+ int red, green, blue;
+
+ if( 3 != sscanf( value, "%d,%d,%d", &red, &green, &blue ) )
+ return false;
+
+ sal_Int32 nColor = static_cast<sal_Int32>(blue) | ( static_cast<sal_Int32>(green) << 8 ) | ( static_cast<sal_Int32>(red) << 16 );
+ rAny <<= nColor;
+ return true;
+}
+
+/*****************************************************************************/
+
+static gchar*
+FontSlant2Style(const uno::Any& rAny)
+{
+ const gchar * value = nullptr;
+
+ awt::FontSlant aFontSlant;
+ if(!(rAny >>= aFontSlant))
+ return nullptr;
+
+ switch( aFontSlant )
+ {
+ case awt::FontSlant_NONE:
+ value = "normal";
+ break;
+
+ case awt::FontSlant_OBLIQUE:
+ value = "oblique";
+ break;
+
+ case awt::FontSlant_ITALIC:
+ value = "italic";
+ break;
+
+ case awt::FontSlant_REVERSE_OBLIQUE:
+ value = "reverse oblique";
+ break;
+
+ case awt::FontSlant_REVERSE_ITALIC:
+ value = "reverse italic";
+ break;
+
+ default:
+ break;
+ }
+
+ if( value )
+ return g_strdup( value );
+
+ return nullptr;
+}
+
+static bool
+Style2FontSlant( uno::Any& rAny, const gchar * value )
+{
+ awt::FontSlant aFontSlant;
+
+ if( strncmp( value, STRNCMP_PARAM( "normal" ) ) == 0 )
+ aFontSlant = awt::FontSlant_NONE;
+ else if( strncmp( value, STRNCMP_PARAM( "oblique" ) ) == 0 )
+ aFontSlant = awt::FontSlant_OBLIQUE;
+ else if( strncmp( value, STRNCMP_PARAM( "italic" ) ) == 0 )
+ aFontSlant = awt::FontSlant_ITALIC;
+ else if( strncmp( value, STRNCMP_PARAM( "reverse oblique" ) ) == 0 )
+ aFontSlant = awt::FontSlant_REVERSE_OBLIQUE;
+ else if( strncmp( value, STRNCMP_PARAM( "reverse italic" ) ) == 0 )
+ aFontSlant = awt::FontSlant_REVERSE_ITALIC;
+ else
+ return false;
+
+ rAny <<= aFontSlant;
+ return true;
+}
+
+/*****************************************************************************/
+
+static gchar*
+Weight2String(const uno::Any& rAny)
+{
+ return g_strdup_printf( "%g", rAny.get<float>() * 4 );
+}
+
+static bool
+String2Weight( uno::Any& rAny, const gchar * value )
+{
+ float weight;
+
+ if( 1 != sscanf( value, "%g", &weight ) )
+ return false;
+
+ rAny <<= weight / 4;
+ return true;
+}
+
+/*****************************************************************************/
+
+static gchar*
+Adjust2Justification(const uno::Any& rAny)
+{
+ const gchar * value = nullptr;
+
+ switch( static_cast<style::ParagraphAdjust>(rAny.get<short>()) )
+ {
+ case style::ParagraphAdjust_LEFT:
+ value = "left";
+ break;
+
+ case style::ParagraphAdjust_RIGHT:
+ value = "right";
+ break;
+
+ case style::ParagraphAdjust_BLOCK:
+ case style::ParagraphAdjust_STRETCH:
+ value = "fill";
+ break;
+
+ case style::ParagraphAdjust_CENTER:
+ value = "center";
+ break;
+
+ default:
+ break;
+ }
+
+ if( value )
+ return g_strdup( value );
+
+ return nullptr;
+}
+
+static bool
+Justification2Adjust( uno::Any& rAny, const gchar * value )
+{
+ style::ParagraphAdjust nParagraphAdjust;
+
+ if( strncmp( value, STRNCMP_PARAM( "left" ) ) == 0 )
+ nParagraphAdjust = style::ParagraphAdjust_LEFT;
+ else if( strncmp( value, STRNCMP_PARAM( "right" ) ) == 0 )
+ nParagraphAdjust = style::ParagraphAdjust_RIGHT;
+ else if( strncmp( value, STRNCMP_PARAM( "fill" ) ) == 0 )
+ nParagraphAdjust = style::ParagraphAdjust_BLOCK;
+ else if( strncmp( value, STRNCMP_PARAM( "center" ) ) == 0 )
+ nParagraphAdjust = style::ParagraphAdjust_CENTER;
+ else
+ return false;
+
+ rAny <<= static_cast<short>(nParagraphAdjust);
+ return true;
+}
+
+/*****************************************************************************/
+
+const gchar * const font_strikethrough[] = {
+ "none", // FontStrikeout::NONE
+ "single", // FontStrikeout::SINGLE
+ "double", // FontStrikeout::DOUBLE
+ nullptr, // FontStrikeout::DONTKNOW
+ "bold", // FontStrikeout::BOLD
+ "with /", // FontStrikeout::SLASH
+ "with X" // FontStrikeout::X
+};
+
+static gchar*
+Strikeout2String(const uno::Any& rAny)
+{
+ sal_Int16 n = rAny.get<sal_Int16>();
+
+ if( n >= 0 && o3tl::make_unsigned(n) < SAL_N_ELEMENTS(font_strikethrough) )
+ return g_strdup( font_strikethrough[n] );
+
+ return nullptr;
+}
+
+static bool
+String2Strikeout( uno::Any& rAny, const gchar * value )
+{
+ for( sal_Int16 n=0; n < sal_Int16(SAL_N_ELEMENTS(font_strikethrough)); ++n )
+ {
+ if( ( nullptr != font_strikethrough[n] ) &&
+ 0 == strncmp( value, font_strikethrough[n], strlen( font_strikethrough[n] ) ) )
+ {
+ rAny <<= n;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*****************************************************************************/
+
+static gchar*
+Underline2String(const uno::Any& rAny)
+{
+ const gchar * value = nullptr;
+
+ switch( rAny.get<sal_Int16>() )
+ {
+ case awt::FontUnderline::NONE:
+ value = "none";
+ break;
+
+ case awt::FontUnderline::SINGLE:
+ value = "single";
+ break;
+
+ case awt::FontUnderline::DOUBLE:
+ value = "double";
+ break;
+
+ default:
+ break;
+ }
+
+ if( value )
+ return g_strdup( value );
+
+ return nullptr;
+}
+
+static bool
+String2Underline( uno::Any& rAny, const gchar * value )
+{
+ short nUnderline;
+
+ if( strncmp( value, STRNCMP_PARAM( "none" ) ) == 0 )
+ nUnderline = awt::FontUnderline::NONE;
+ else if( strncmp( value, STRNCMP_PARAM( "single" ) ) == 0 )
+ nUnderline = awt::FontUnderline::SINGLE;
+ else if( strncmp( value, STRNCMP_PARAM( "double" ) ) == 0 )
+ nUnderline = awt::FontUnderline::DOUBLE;
+ else
+ return false;
+
+ rAny <<= nUnderline;
+ return true;
+}
+
+/*****************************************************************************/
+
+static gchar*
+GetString(const uno::Any& rAny)
+{
+ OString aFontName = OUStringToOString( rAny.get< OUString > (), RTL_TEXTENCODING_UTF8 );
+
+ if( !aFontName.isEmpty() )
+ return g_strdup( aFontName.getStr() );
+
+ return nullptr;
+}
+
+static bool
+SetString( uno::Any& rAny, const gchar * value )
+{
+ OString aFontName( value );
+
+ if( !aFontName.isEmpty() )
+ {
+ rAny <<= OStringToOUString( aFontName, RTL_TEXTENCODING_UTF8 );
+ return true;
+ }
+
+ return false;
+}
+
+/*****************************************************************************/
+
+// @see http://developer.gnome.org/doc/API/2.0/atk/AtkText.html#AtkTextAttribute
+
+// CMM = 100th of mm
+static gchar*
+CMM2UnitString(const uno::Any& rAny)
+{
+ double fValue = rAny.get<sal_Int32>();
+ fValue = fValue * 0.01;
+
+ return g_strdup_printf( "%gmm", fValue );
+}
+
+static bool
+UnitString2CMM( uno::Any& rAny, const gchar * value )
+{
+ float fValue = 0.0; // pb: don't use double here because of warning on linux
+
+ if( 1 != sscanf( value, "%gmm", &fValue ) )
+ return false;
+
+ fValue = fValue * 100;
+
+ rAny <<= static_cast<sal_Int32>(fValue);
+ return true;
+}
+
+/*****************************************************************************/
+
+static const gchar * bool_values[] = { "true", "false" };
+
+static gchar *
+Bool2String( const uno::Any& rAny )
+{
+ int n = 1;
+
+ if( rAny.get<bool>() )
+ n = 0;
+
+ return g_strdup( bool_values[n] );
+}
+
+static bool
+String2Bool( uno::Any& rAny, const gchar * value )
+{
+ bool bValue;
+
+ if( strncmp( value, STRNCMP_PARAM( "true" ) ) == 0 )
+ bValue = true;
+ else if( strncmp( value, STRNCMP_PARAM( "false" ) ) == 0 )
+ bValue = false;
+ else
+ return false;
+
+ rAny <<= bValue;
+ return true;
+}
+
+/*****************************************************************************/
+
+static gchar*
+Scale2String( const uno::Any& rAny )
+{
+ return g_strdup_printf( "%g", static_cast<double>(rAny.get< sal_Int16 > ()) / 100 );
+}
+
+static bool
+String2Scale( uno::Any& rAny, const gchar * value )
+{
+ double dval;
+
+ if( 1 != sscanf( value, "%lg", &dval ) )
+ return false;
+
+ rAny <<= static_cast<sal_Int16>(dval * 100);
+ return true;
+}
+
+/*****************************************************************************/
+
+static gchar *
+CaseMap2String( const uno::Any& rAny )
+{
+ const gchar * value;
+
+ switch( rAny.get<short>() )
+ {
+ case style::CaseMap::SMALLCAPS:
+ value = "small_caps";
+ break;
+
+ default:
+ value = "normal";
+ break;
+ }
+
+ return g_strdup(value);
+}
+
+static bool
+String2CaseMap( uno::Any& rAny, const gchar * value )
+{
+ short nCaseMap;
+
+ if( strncmp( value, STRNCMP_PARAM( "normal" ) ) == 0 )
+ nCaseMap = style::CaseMap::NONE;
+ else if( strncmp( value, STRNCMP_PARAM( "small_caps" ) ) == 0 )
+ nCaseMap = style::CaseMap::SMALLCAPS;
+ else
+ return false;
+
+ rAny <<= nCaseMap;
+ return true;
+}
+
+/*****************************************************************************/
+
+const gchar * const font_stretch[] = {
+ "ultra_condensed",
+ "extra_condensed",
+ "condensed",
+ "semi_condensed",
+ "normal",
+ "semi_expanded",
+ "expanded",
+ "extra_expanded",
+ "ultra_expanded"
+};
+
+static gchar*
+Kerning2Stretch(const uno::Any& rAny)
+{
+ sal_Int16 n = rAny.get<sal_Int16>();
+ int i = 4;
+
+ // No good idea for a mapping - just return the basic info
+ if( n < 0 )
+ i=2;
+ else if( n > 0 )
+ i=6;
+
+ return g_strdup(font_stretch[i]);
+}
+
+/*****************************************************************************/
+
+static gchar*
+Locale2String(const uno::Any& rAny)
+{
+ /* FIXME-BCP47: support language tags? And why is country lowercase? */
+ lang::Locale aLocale = rAny.get<lang::Locale> ();
+ LanguageTag aLanguageTag( aLocale);
+ return g_strdup_printf( "%s-%s",
+ OUStringToOString( aLanguageTag.getLanguage(), RTL_TEXTENCODING_ASCII_US).getStr(),
+ OUStringToOString( aLanguageTag.getCountry(), RTL_TEXTENCODING_ASCII_US).toAsciiLowerCase().getStr() );
+}
+
+static bool
+String2Locale( uno::Any& rAny, const gchar * value )
+{
+ /* FIXME-BCP47: support language tags? */
+ bool ret = false;
+
+ gchar ** str_array = g_strsplit_set( value, "-.@", -1 );
+ if( str_array[0] != nullptr )
+ {
+ ret = true;
+
+ lang::Locale aLocale;
+
+ aLocale.Language = OUString::createFromAscii(str_array[0]);
+ if( str_array[1] != nullptr )
+ {
+ gchar * country = g_ascii_strup(str_array[1], -1);
+ aLocale.Country = OUString::createFromAscii(country);
+ g_free(country);
+ }
+
+ rAny <<= aLocale;
+ }
+
+ g_strfreev(str_array);
+ return ret;
+}
+
+/*****************************************************************************/
+
+// @see http://www.w3.org/TR/2002/WD-css3-fonts-20020802/#font-effect-prop
+static const gchar * relief[] = { "none", "emboss", "engrave" };
+const gchar * const outline = "outline";
+
+static gchar *
+get_font_effect(const uno::Sequence< beans::PropertyValue >& rAttributeList,
+ sal_Int32 nContourIndex, sal_Int32 nReliefIndex)
+{
+ if( nContourIndex != -1 )
+ {
+ if( rAttributeList[nContourIndex].Value.get<bool>() )
+ return g_strdup(outline);
+ }
+
+ if( nReliefIndex != -1 )
+ {
+ sal_Int16 n = rAttributeList[nReliefIndex].Value.get<sal_Int16>();
+ if( n < 3)
+ return g_strdup(relief[n]);
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+// @see http://www.w3.org/TR/REC-CSS2/text.html#lining-striking-props
+
+enum
+{
+ DECORATION_NONE = 0,
+ DECORATION_BLINK,
+ DECORATION_UNDERLINE,
+ DECORATION_LINE_THROUGH
+};
+
+static const gchar * decorations[] = { "none", "blink", "underline", "line-through" };
+
+static gchar *
+get_text_decoration(const uno::Sequence< beans::PropertyValue >& rAttributeList,
+ sal_Int32 nBlinkIndex, sal_Int32 nUnderlineIndex,
+ sal_Int16 nStrikeoutIndex)
+{
+ gchar * value_list[4] = { nullptr, nullptr, nullptr, nullptr };
+ gint count = 0;
+
+ // no property value found
+ if( ( nBlinkIndex == -1 ) && (nUnderlineIndex == -1 ) && (nStrikeoutIndex == -1))
+ return nullptr;
+
+ if( nBlinkIndex != -1 )
+ {
+ if( rAttributeList[nBlinkIndex].Value.get<bool>() )
+ value_list[count++] = const_cast <gchar *> (decorations[DECORATION_BLINK]);
+ }
+ if( nUnderlineIndex != -1 )
+ {
+ sal_Int16 n = rAttributeList[nUnderlineIndex].Value.get<sal_Int16> ();
+ if( n != awt::FontUnderline::NONE )
+ value_list[count++] = const_cast <gchar *> (decorations[DECORATION_UNDERLINE]);
+ }
+ if( nStrikeoutIndex != -1 )
+ {
+ sal_Int16 n = rAttributeList[nStrikeoutIndex].Value.get<sal_Int16> ();
+ if( n != awt::FontStrikeout::NONE && n != awt::FontStrikeout::DONTKNOW )
+ value_list[count++] = const_cast <gchar *> (decorations[DECORATION_LINE_THROUGH]);
+ }
+
+ if( count == 0 )
+ value_list[count++] = const_cast <gchar *> (decorations[DECORATION_NONE]);
+
+ return g_strjoinv(" ", value_list);
+}
+
+/*****************************************************************************/
+
+// @see http://www.w3.org/TR/REC-CSS2/text.html#propdef-text-shadow
+
+static const gchar * shadow_values[] = { "none", "black" };
+
+static gchar *
+Bool2Shadow( const uno::Any& rAny )
+{
+ int n = 0;
+
+ if( rAny.get<bool>() )
+ n = 1;
+
+ return g_strdup( shadow_values[n] );
+}
+
+/*****************************************************************************/
+
+static gchar *
+Short2Degree( const uno::Any& rAny )
+{
+ float f = rAny.get<sal_Int16>() / 10.0;
+ return g_strdup_printf( "%g", f );
+}
+
+/*****************************************************************************/
+
+const gchar * const directions[] = { "ltr", "rtl", "rtl", "ltr", "none" };
+
+static gchar *
+WritingMode2Direction( const uno::Any& rAny )
+{
+ sal_Int16 n = rAny.get<sal_Int16>();
+
+ if( 0 <= n && n <= text::WritingMode2::PAGE )
+ return g_strdup(directions[n]);
+
+ return nullptr;
+}
+
+// @see http://www.w3.org/TR/2001/WD-css3-text-20010517/#PrimaryTextAdvanceDirection
+
+const gchar * const writing_modes[] = { "lr-tb", "rl-tb", "tb-rl", "tb-lr", "none" };
+static gchar *
+WritingMode2String( const uno::Any& rAny )
+{
+ sal_Int16 n = rAny.get<sal_Int16>();
+
+ if( 0 <= n && n <= text::WritingMode2::PAGE )
+ return g_strdup(writing_modes[n]);
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+const char * const baseline_values[] = { "baseline", "sub", "super" };
+
+// @see http://www.w3.org/TR/REC-CSS2/visudet.html#propdef-vertical-align
+static gchar *
+Escapement2VerticalAlign( const uno::Any& rAny )
+{
+ sal_Int16 n = rAny.get<sal_Int16>();
+ gchar * ret = nullptr;
+
+ // Values are in %, 101% means "automatic"
+ if( n == 0 )
+ ret = g_strdup(baseline_values[0]);
+ else if( n == 101 )
+ ret = g_strdup(baseline_values[2]);
+ else if( n == -101 )
+ ret = g_strdup(baseline_values[1]);
+ else
+ ret = g_strdup_printf( "%d%%", n );
+
+ return ret;
+}
+
+/*****************************************************************************/
+
+// @see http://www.w3.org/TR/REC-CSS2/visudet.html#propdef-line-height
+static gchar *
+LineSpacing2LineHeight( const uno::Any& rAny )
+{
+ style::LineSpacing ls;
+ gchar * ret = nullptr;
+
+ if( rAny >>= ls )
+ {
+ if( ls.Mode == style::LineSpacingMode::PROP )
+ ret = g_strdup_printf( "%d%%", ls.Height );
+ else if( ls.Mode == style::LineSpacingMode::FIX )
+ ret = g_strdup_printf("%.3gpt", convertMm100ToPoint<double>(ls.Height));
+ }
+
+ return ret;
+}
+
+/*****************************************************************************/
+
+// @see http://www.w3.org/People/howcome/t/970224HTMLERB-CSS/WD-tabs-970117.html
+static gchar *
+TabStopList2String( const uno::Any& rAny, bool default_tabs )
+{
+ uno::Sequence< style::TabStop > theTabStops;
+ gchar * ret = nullptr;
+
+ if( rAny >>= theTabStops)
+ {
+ sal_Unicode lastFillChar = ' ';
+
+ for( const auto& rTabStop : std::as_const(theTabStops) )
+ {
+ bool is_default_tab = (style::TabAlign_DEFAULT == rTabStop.Alignment);
+
+ if( is_default_tab != default_tabs )
+ continue;
+
+ double fValue = rTabStop.Position;
+ fValue = fValue * 0.01;
+
+ const gchar * tab_align = "";
+ switch( rTabStop.Alignment )
+ {
+ case style::TabAlign_LEFT :
+ tab_align = "left ";
+ break;
+ case style::TabAlign_CENTER :
+ tab_align = "center ";
+ break;
+ case style::TabAlign_RIGHT :
+ tab_align = "right ";
+ break;
+ case style::TabAlign_DECIMAL :
+ tab_align = "decimal ";
+ break;
+ default:
+ break;
+ }
+
+ const gchar * lead_char = "";
+
+ if( rTabStop.FillChar != lastFillChar )
+ {
+ lastFillChar = rTabStop.FillChar;
+ switch (lastFillChar)
+ {
+ case ' ':
+ lead_char = "blank ";
+ break;
+
+ case '.':
+ lead_char = "dotted ";
+ break;
+
+ case '-':
+ lead_char = "dashed ";
+ break;
+
+ case '_':
+ lead_char = "lined ";
+ break;
+
+ default:
+ lead_char = "custom ";
+ break;
+ }
+ }
+
+ gchar * tab_str = g_strdup_printf( "%s%s%gmm", lead_char, tab_align, fValue );
+
+ if( ret )
+ {
+ gchar * old_tab_str = ret;
+ ret = g_strconcat(old_tab_str, " ", tab_str, nullptr);
+ g_free( tab_str );
+ g_free( old_tab_str );
+ }
+ else
+ ret = tab_str;
+ }
+ }
+
+ return ret;
+}
+
+static gchar *
+TabStops2String( const uno::Any& rAny )
+{
+ return TabStopList2String(rAny, false);
+}
+
+static gchar *
+DefaultTabStops2String( const uno::Any& rAny )
+{
+ return TabStopList2String(rAny, true);
+}
+
+/*****************************************************************************/
+
+extern "C" {
+
+static int
+attr_compare(const void *p1,const void *p2)
+{
+ const rtl_uString * pustr = static_cast<const rtl_uString *>(p1);
+ const char * pc = *static_cast<const char * const *>(p2);
+
+ return rtl_ustr_ascii_compare_WithLength(pustr->buffer, pustr->length, pc);
+}
+
+}
+
+static void
+find_exported_attributes( sal_Int32 *pArray,
+ const css::uno::Sequence< css::beans::PropertyValue >& rAttributeList )
+{
+ for( sal_Int32 i = 0; i < rAttributeList.getLength(); i++ )
+ {
+ const char ** pAttr = static_cast<const char **>(bsearch(rAttributeList[i].Name.pData,
+ ExportedTextAttributes, TEXT_ATTRIBUTE_LAST, sizeof(const char *),
+ attr_compare));
+
+ if( pAttr )
+ {
+ sal_Int32 nIndex = pAttr - ExportedTextAttributes;
+ pArray[nIndex] = i;
+ }
+ }
+}
+
+/*****************************************************************************/
+
+static AtkAttributeSet*
+attribute_set_prepend( AtkAttributeSet* attribute_set,
+ AtkTextAttribute attribute,
+ gchar * value )
+{
+ if( value )
+ {
+ AtkAttribute *at = static_cast<AtkAttribute *>(g_malloc( sizeof (AtkAttribute) ));
+ at->name = g_strdup( atk_text_attribute_get_name( attribute ) );
+ at->value = value;
+
+ return g_slist_prepend(attribute_set, at);
+ }
+
+ return attribute_set;
+}
+
+/*****************************************************************************/
+
+AtkAttributeSet*
+attribute_set_new_from_property_values(
+ const uno::Sequence< beans::PropertyValue >& rAttributeList,
+ bool run_attributes_only,
+ AtkText *text)
+{
+ AtkAttributeSet* attribute_set = nullptr;
+
+ sal_Int32 aIndexList[TEXT_ATTRIBUTE_LAST] = { -1 };
+
+ // Initialize index array with -1
+ for(sal_Int32 & rn : aIndexList)
+ rn = -1;
+
+ find_exported_attributes(aIndexList, rAttributeList);
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_BG_COLOR,
+ get_color_value(rAttributeList, aIndexList, TEXT_ATTRIBUTE_BACKGROUND_COLOR, run_attributes_only ? nullptr : text ) );
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_FG_COLOR,
+ get_color_value(rAttributeList, aIndexList, TEXT_ATTRIBUTE_FOREGROUND_COLOR, run_attributes_only ? nullptr : text) );
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_INVISIBLE,
+ get_bool_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_HIDDEN]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_UNDERLINE,
+ get_underline_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_UNDERLINE]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STRIKETHROUGH,
+ get_strikethrough_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_STRIKETHROUGH]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_SIZE,
+ get_height_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_HEIGHT]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_WEIGHT,
+ get_weight_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WEIGHT]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_FAMILY_NAME,
+ get_string_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_FONT_NAME]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_VARIANT,
+ get_variant_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CASEMAP]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STYLE,
+ get_style_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_POSTURE]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_SCALE,
+ get_scale_width(rAttributeList, aIndexList[TEXT_ATTRIBUTE_SCALE]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_LANGUAGE,
+ get_language_string(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LOCALE]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_DIRECTION,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WRITING_MODE], WritingMode2Direction));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STRETCH,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_KERNING], Kerning2Stretch));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_font_effect )
+ atk_text_attribute_font_effect = atk_text_attribute_register("font-effect");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_font_effect,
+ get_font_effect(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CONTOURED], aIndexList[TEXT_ATTRIBUTE_RELIEF]));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_decoration )
+ atk_text_attribute_decoration = atk_text_attribute_register("text-decoration");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_decoration,
+ get_text_decoration(rAttributeList, aIndexList[TEXT_ATTRIBUTE_BLINKING],
+ aIndexList[TEXT_ATTRIBUTE_UNDERLINE], aIndexList[TEXT_ATTRIBUTE_STRIKETHROUGH]));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_rotation )
+ atk_text_attribute_rotation = atk_text_attribute_register("text-rotation");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_rotation,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_ROTATION], Short2Degree));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_shadow )
+ atk_text_attribute_shadow = atk_text_attribute_register("text-shadow");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_shadow,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_SHADOWED], Bool2Shadow));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_writing_mode )
+ atk_text_attribute_writing_mode = atk_text_attribute_register("writing-mode");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_writing_mode,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WRITING_MODE], WritingMode2String));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_vertical_align )
+ atk_text_attribute_vertical_align = atk_text_attribute_register("vertical-align");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_vertical_align,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CHAR_ESCAPEMENT], Escapement2VerticalAlign));
+
+ if( run_attributes_only )
+ return attribute_set;
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_LEFT_MARGIN,
+ get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LEFT_MARGIN]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_RIGHT_MARGIN,
+ get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_RIGHT_MARGIN]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_INDENT,
+ get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_FIRST_LINE_INDENT]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_PIXELS_ABOVE_LINES,
+ get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TOP_MARGIN]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_PIXELS_BELOW_LINES,
+ get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_BOTTOM_MARGIN]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_JUSTIFICATION,
+ get_justification_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_JUSTIFICATION]));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_paragraph_style )
+ atk_text_attribute_paragraph_style = atk_text_attribute_register("paragraph-style");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_paragraph_style,
+ get_string_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_STYLE_NAME]));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_line_height )
+ atk_text_attribute_line_height = atk_text_attribute_register("line-height");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_line_height,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LINE_SPACING], LineSpacing2LineHeight));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tab_interval )
+ atk_text_attribute_tab_interval = atk_text_attribute_register("tab-interval");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_tab_interval,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TAB_STOPS], DefaultTabStops2String));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tab_stops )
+ atk_text_attribute_tab_stops = atk_text_attribute_register("tab-stops");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_tab_stops,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TAB_STOPS], TabStops2String));
+
+ // #i92233#
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_mm_to_pixel_ratio )
+ atk_text_attribute_mm_to_pixel_ratio = atk_text_attribute_register("mm-to-pixel-ratio");
+
+ attribute_set = attribute_set_prepend( attribute_set, atk_text_attribute_mm_to_pixel_ratio,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO], Float2String));
+
+ return attribute_set;
+}
+
+AtkAttributeSet*
+attribute_set_new_from_extended_attributes(
+ const css::uno::Reference< css::accessibility::XAccessibleExtendedAttributes >& rExtendedAttributes )
+{
+ AtkAttributeSet *pSet = nullptr;
+
+ // extended attributes is a string of colon-separated pairs of property and value,
+ // with pairs separated by semicolons. Example: "heading-level:2;weight:bold;"
+ uno::Any anyVal = rExtendedAttributes->getExtendedAttributes();
+ OUString sExtendedAttrs;
+ anyVal >>= sExtendedAttrs;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OUString sProperty = sExtendedAttrs.getToken( 0, ';', nIndex );
+
+ sal_Int32 nColonPos = 0;
+ OString sPropertyName = OUStringToOString( o3tl::getToken(sProperty, 0, ':', nColonPos ),
+ RTL_TEXTENCODING_UTF8 );
+ OString sPropertyValue = OUStringToOString( o3tl::getToken(sProperty, 0, ':', nColonPos ),
+ RTL_TEXTENCODING_UTF8 );
+
+ pSet = attribute_set_prepend( pSet,
+ atk_text_attribute_register( sPropertyName.getStr() ),
+ g_strdup_printf( "%s", sPropertyValue.getStr() ) );
+ }
+ while ( nIndex >= 0 && nIndex < sExtendedAttrs.getLength() );
+
+ return pSet;
+}
+
+AtkAttributeSet* attribute_set_prepend_misspelled( AtkAttributeSet* attribute_set )
+{
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_misspelled )
+ atk_text_attribute_misspelled = atk_text_attribute_register( "text-spelling" );
+
+ attribute_set = attribute_set_prepend( attribute_set, atk_text_attribute_misspelled,
+ g_strdup_printf( "misspelled" ) );
+
+ return attribute_set;
+}
+
+// #i92232#
+AtkAttributeSet* attribute_set_prepend_tracked_change_insertion( AtkAttributeSet* attribute_set )
+{
+ if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change )
+ {
+ atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" );
+ }
+
+ attribute_set = attribute_set_prepend( attribute_set,
+ atk_text_attribute_tracked_change,
+ g_strdup_printf( "insertion" ) );
+
+ return attribute_set;
+}
+
+AtkAttributeSet* attribute_set_prepend_tracked_change_deletion( AtkAttributeSet* attribute_set )
+{
+ if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change )
+ {
+ atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" );
+ }
+
+ attribute_set = attribute_set_prepend( attribute_set,
+ atk_text_attribute_tracked_change,
+ g_strdup_printf( "deletion" ) );
+
+ return attribute_set;
+}
+
+AtkAttributeSet* attribute_set_prepend_tracked_change_formatchange( AtkAttributeSet* attribute_set )
+{
+ if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change )
+ {
+ atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" );
+ }
+
+ attribute_set = attribute_set_prepend( attribute_set,
+ atk_text_attribute_tracked_change,
+ g_strdup_printf( "attribute-change" ) );
+
+ return attribute_set;
+}
+
+/*****************************************************************************/
+
+namespace {
+
+struct AtkTextAttrMapping
+{
+ const char * name;
+ TextPropertyValueFunc const toPropertyValue;
+};
+
+}
+
+const AtkTextAttrMapping g_TextAttrMap[] =
+{
+ { "", InvalidValue }, // ATK_TEXT_ATTR_INVALID = 0
+ { "ParaLeftMargin", UnitString2CMM }, // ATK_TEXT_ATTR_LEFT_MARGIN
+ { "ParaRightMargin", UnitString2CMM }, // ATK_TEXT_ATTR_RIGHT_MARGIN
+ { "ParaFirstLineIndent", UnitString2CMM }, // ATK_TEXT_ATTR_INDENT
+ { "CharHidden", String2Bool }, // ATK_TEXT_ATTR_INVISIBLE
+ { "", InvalidValue }, // ATK_TEXT_ATTR_EDITABLE
+ { "ParaTopMargin", UnitString2CMM }, // ATK_TEXT_ATTR_PIXELS_ABOVE_LINES
+ { "ParaBottomMargin", UnitString2CMM }, // ATK_TEXT_ATTR_PIXELS_BELOW_LINES
+ { "", InvalidValue }, // ATK_TEXT_ATTR_PIXELS_INSIDE_WRAP
+ { "", InvalidValue }, // ATK_TEXT_ATTR_BG_FULL_HEIGHT
+ { "", InvalidValue }, // ATK_TEXT_ATTR_RISE
+ { "CharUnderline", String2Underline }, // ATK_TEXT_ATTR_UNDERLINE
+ { "CharStrikeout", String2Strikeout }, // ATK_TEXT_ATTR_STRIKETHROUGH
+ { "CharHeight", String2Float }, // ATK_TEXT_ATTR_SIZE
+ { "CharScaleWidth", String2Scale }, // ATK_TEXT_ATTR_SCALE
+ { "CharWeight", String2Weight }, // ATK_TEXT_ATTR_WEIGHT
+ { "CharLocale", String2Locale }, // ATK_TEXT_ATTR_LANGUAGE
+ { "CharFontName", SetString }, // ATK_TEXT_ATTR_FAMILY_NAME
+ { "CharBackColor", String2Color }, // ATK_TEXT_ATTR_BG_COLOR
+ { "CharColor", String2Color }, // ATK_TEXT_ATTR_FG_COLOR
+ { "", InvalidValue }, // ATK_TEXT_ATTR_BG_STIPPLE
+ { "", InvalidValue }, // ATK_TEXT_ATTR_FG_STIPPLE
+ { "", InvalidValue }, // ATK_TEXT_ATTR_WRAP_MODE
+ { "", InvalidValue }, // ATK_TEXT_ATTR_DIRECTION
+ { "ParaAdjust", Justification2Adjust }, // ATK_TEXT_ATTR_JUSTIFICATION
+ { "", InvalidValue }, // ATK_TEXT_ATTR_STRETCH
+ { "CharCaseMap", String2CaseMap }, // ATK_TEXT_ATTR_VARIANT
+ { "CharPosture", Style2FontSlant } // ATK_TEXT_ATTR_STYLE
+};
+
+/*****************************************************************************/
+
+bool
+attribute_set_map_to_property_values(
+ AtkAttributeSet* attribute_set,
+ uno::Sequence< beans::PropertyValue >& rValueList )
+{
+ // Ensure enough space ..
+ uno::Sequence< beans::PropertyValue > aAttributeList (SAL_N_ELEMENTS(g_TextAttrMap));
+ auto pAttributeList = aAttributeList.getArray();
+
+ sal_Int32 nIndex = 0;
+ for( GSList * item = attribute_set; item != nullptr; item = g_slist_next( item ) )
+ {
+ AtkAttribute* attribute = reinterpret_cast<AtkAttribute *>(item);
+
+ AtkTextAttribute text_attr = atk_text_attribute_for_name( attribute->name );
+ if( text_attr < SAL_N_ELEMENTS(g_TextAttrMap) )
+ {
+ if( g_TextAttrMap[text_attr].name[0] != '\0' )
+ {
+ if( ! g_TextAttrMap[text_attr].toPropertyValue( pAttributeList[nIndex].Value, attribute->value) )
+ return false;
+
+ pAttributeList[nIndex].Name = OUString::createFromAscii( g_TextAttrMap[text_attr].name );
+ pAttributeList[nIndex].State = beans::PropertyState_DIRECT_VALUE;
+ ++nIndex;
+ }
+ }
+ else
+ {
+ // Unsupported text attribute
+ return false;
+ }
+ }
+
+ aAttributeList.realloc( nIndex );
+ rValueList = aAttributeList;
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atktextattributes.hxx b/vcl/unx/gtk3/a11y/atktextattributes.hxx
new file mode 100644
index 0000000000..716ec45bd3
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atktextattributes.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/accessibility/XAccessibleExtendedAttributes.hpp>
+
+#include <atk/atk.h>
+
+AtkAttributeSet* attribute_set_new_from_property_values(
+ const css::uno::Sequence<css::beans::PropertyValue>& rAttributeList, bool run_attributes_only,
+ AtkText* text);
+
+AtkAttributeSet* attribute_set_new_from_extended_attributes(
+ const css::uno::Reference<css::accessibility::XAccessibleExtendedAttributes>&
+ rExtendedAttributes);
+
+bool attribute_set_map_to_property_values(
+ AtkAttributeSet* attribute_set, css::uno::Sequence<css::beans::PropertyValue>& rValueList);
+
+AtkAttributeSet* attribute_set_prepend_misspelled(AtkAttributeSet* attribute_set);
+// #i92232#
+AtkAttributeSet* attribute_set_prepend_tracked_change_insertion(AtkAttributeSet* attribute_set);
+AtkAttributeSet* attribute_set_prepend_tracked_change_deletion(AtkAttributeSet* attribute_set);
+AtkAttributeSet* attribute_set_prepend_tracked_change_formatchange(AtkAttributeSet* attribute_set);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkutil.cxx b/vcl/unx/gtk3/a11y/atkutil.cxx
new file mode 100644
index 0000000000..fc15351374
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkutil.cxx
@@ -0,0 +1,626 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/accessibility/XAccessibleEventBroadcaster.hpp>
+#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
+#include <com/sun/star/accessibility/AccessibleEventId.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/XAccessibleText.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/weakref.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/toolbox.hxx>
+
+#include <unx/gtk/gtkdata.hxx>
+#include "atkwrapper.hxx"
+#include "atkutil.hxx"
+
+#include <cassert>
+#include <set>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+ uno::WeakReference< accessibility::XAccessible > theNextFocusObject;
+}
+
+static guint focus_notify_handler = 0;
+
+/*****************************************************************************/
+
+extern "C" {
+
+static gboolean
+atk_wrapper_focus_idle_handler (gpointer data)
+{
+ SolarMutexGuard aGuard;
+
+ focus_notify_handler = 0;
+
+ uno::Reference< accessibility::XAccessible > xAccessible = theNextFocusObject;
+ if( xAccessible.get() == static_cast < accessibility::XAccessible * > (data) )
+ {
+ AtkObject *atk_obj = xAccessible.is() ? atk_object_wrapper_ref( xAccessible ) : nullptr;
+ // Gail does not notify focus changes to NULL, so do we ..
+ if( atk_obj )
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ atk_focus_tracker_notify(atk_obj);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ // #i93269#
+ // emit text_caret_moved event for <XAccessibleText> object,
+ // if cursor is inside the <XAccessibleText> object.
+ // also emit state-changed:focused event under the same condition.
+ {
+ AtkObjectWrapper* wrapper_obj = ATK_OBJECT_WRAPPER (atk_obj);
+ if( wrapper_obj && !wrapper_obj->mpText.is() )
+ {
+ wrapper_obj->mpText.set(wrapper_obj->mpContext, css::uno::UNO_QUERY);
+ if ( wrapper_obj->mpText.is() )
+ {
+ gint caretPos = -1;
+
+ try {
+ caretPos = wrapper_obj->mpText->getCaretPosition();
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getCaretPosition()" );
+ }
+
+ if ( caretPos != -1 )
+ {
+ atk_object_notify_state_change( atk_obj, ATK_STATE_FOCUSED, true );
+ g_signal_emit_by_name( atk_obj, "text_caret_moved", caretPos );
+ }
+ }
+ }
+ }
+ g_object_unref(atk_obj);
+ }
+ }
+
+ return false;
+}
+
+} // extern "C"
+
+/*****************************************************************************/
+
+static void
+atk_wrapper_focus_tracker_notify_when_idle( const uno::Reference< accessibility::XAccessible > &xAccessible )
+{
+ if( focus_notify_handler )
+ g_source_remove(focus_notify_handler);
+
+ theNextFocusObject = xAccessible;
+
+ focus_notify_handler = g_idle_add (atk_wrapper_focus_idle_handler, xAccessible.get());
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::disposing( const lang::EventObject& aEvent )
+{
+
+ // Unref the object here, but do not remove as listener since the object
+ // might no longer be in a state that safely allows this.
+ if( aEvent.Source.is() )
+ m_aRefList.erase(aEvent.Source);
+
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::notifyEvent( const accessibility::AccessibleEventObject& aEvent )
+{
+ try {
+ switch( aEvent.EventId )
+ {
+ case accessibility::AccessibleEventId::STATE_CHANGED:
+ {
+ sal_Int64 nState = accessibility::AccessibleStateType::INVALID;
+ aEvent.NewValue >>= nState;
+
+ if( accessibility::AccessibleStateType::FOCUSED == nState )
+ atk_wrapper_focus_tracker_notify_when_idle( getAccessible(aEvent) );
+
+ break;
+ }
+
+ case accessibility::AccessibleEventId::CHILD:
+ {
+ uno::Reference< accessibility::XAccessible > xChild;
+ if( (aEvent.OldValue >>= xChild) && xChild.is() )
+ detachRecursive(xChild);
+
+ if( (aEvent.NewValue >>= xChild) && xChild.is() )
+ attachRecursive(xChild);
+
+ break;
+ }
+
+ case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN:
+ {
+ if (uno::Reference< accessibility::XAccessible > xAcc = getAccessible(aEvent))
+ detachRecursive(xAcc);
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ catch( const lang::IndexOutOfBoundsException& )
+ {
+ g_warning("DocumentFocusListener: Focused object has invalid index in parent");
+ }
+}
+
+/*****************************************************************************/
+
+uno::Reference< accessibility::XAccessible > DocumentFocusListener::getAccessible(const lang::EventObject& aEvent )
+{
+ uno::Reference< accessibility::XAccessible > xAccessible(aEvent.Source, uno::UNO_QUERY);
+
+ if( xAccessible.is() )
+ return xAccessible;
+
+ uno::Reference< accessibility::XAccessibleContext > xContext(aEvent.Source, uno::UNO_QUERY);
+
+ if( xContext.is() )
+ {
+ uno::Reference< accessibility::XAccessible > xParent( xContext->getAccessibleParent() );
+ if( xParent.is() )
+ {
+ uno::Reference< accessibility::XAccessibleContext > xParentContext( xParent->getAccessibleContext() );
+ if( xParentContext.is() )
+ {
+ return xParentContext->getAccessibleChild( xContext->getAccessibleIndexInParent() );
+ }
+ }
+ }
+
+ return uno::Reference< accessibility::XAccessible >();
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible
+)
+{
+ uno::Reference< accessibility::XAccessibleContext > xContext =
+ xAccessible->getAccessibleContext();
+
+ if( xContext.is() )
+ attachRecursive(xAccessible, xContext);
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible,
+ const uno::Reference< accessibility::XAccessibleContext >& xContext
+)
+{
+ sal_Int64 nStateSet = xContext->getAccessibleStateSet();
+ attachRecursive(xAccessible, xContext, nStateSet);
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible,
+ const uno::Reference< accessibility::XAccessibleContext >& xContext,
+ sal_Int64 nStateSet
+)
+{
+ if( nStateSet & accessibility::AccessibleStateType::FOCUSED )
+ atk_wrapper_focus_tracker_notify_when_idle( xAccessible );
+
+ uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
+
+ if (!xBroadcaster.is())
+ return;
+
+ // If not already done, add the broadcaster to the list and attach as listener.
+ const uno::Reference< uno::XInterface >& xInterface = xBroadcaster;
+ if( !m_aRefList.insert(xInterface).second )
+ return;
+
+ xBroadcaster->addAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));
+
+ if( ! (nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS) )
+ {
+ sal_Int64 n, nmax = xContext->getAccessibleChildCount();
+ for( n = 0; n < nmax; n++ )
+ {
+ uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) );
+
+ if( xChild.is() )
+ attachRecursive(xChild);
+ }
+ }
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::detachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible
+)
+{
+ uno::Reference< accessibility::XAccessibleContext > xContext =
+ xAccessible->getAccessibleContext();
+
+ if( xContext.is() )
+ detachRecursive(xContext);
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::detachRecursive(
+ const uno::Reference< accessibility::XAccessibleContext >& xContext
+)
+{
+ sal_Int64 nStateSet = xContext->getAccessibleStateSet();
+
+ detachRecursive(xContext, nStateSet);
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::detachRecursive(
+ const uno::Reference< accessibility::XAccessibleContext >& xContext,
+ sal_Int64 nStateSet
+)
+{
+ uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
+
+ if( !xBroadcaster.is() || 0 >= m_aRefList.erase(xBroadcaster) )
+ return;
+
+ xBroadcaster->removeAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));
+
+ if( ! (nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS) )
+ {
+ sal_Int64 n, nmax = xContext->getAccessibleChildCount();
+ for( n = 0; n < nmax; n++ )
+ {
+ uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) );
+
+ if( xChild.is() )
+ detachRecursive(xChild);
+ }
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * page tabs in gtk are widgets, so we need to simulate focus events for those
+ */
+
+static void handle_tabpage_activated(vcl::Window *pWindow)
+{
+ uno::Reference< accessibility::XAccessible > xAccessible =
+ pWindow->GetAccessible();
+
+ if( ! xAccessible.is() )
+ return;
+
+ uno::Reference< accessibility::XAccessibleSelection > xSelection(
+ xAccessible->getAccessibleContext(), uno::UNO_QUERY);
+
+ if( xSelection.is() )
+ atk_wrapper_focus_tracker_notify_when_idle( xSelection->getSelectedAccessibleChild(0) );
+}
+
+/*****************************************************************************/
+
+/*
+ * toolbar items in gtk are widgets, so we need to simulate focus events for those
+ */
+
+static void notify_toolbox_item_focus(ToolBox *pToolBox)
+{
+ uno::Reference< accessibility::XAccessible > xAccessible =
+ pToolBox->GetAccessible();
+
+ if( ! xAccessible.is() )
+ return;
+
+ uno::Reference< accessibility::XAccessibleContext > xContext =
+ xAccessible->getAccessibleContext();
+
+ if( ! xContext.is() )
+ return;
+
+ ToolBox::ImplToolItems::size_type nPos = pToolBox->GetItemPos( pToolBox->GetHighlightItemId() );
+ if( nPos != ToolBox::ITEM_NOTFOUND )
+ atk_wrapper_focus_tracker_notify_when_idle( xContext->getAccessibleChild( nPos ) );
+}
+
+static void handle_toolbox_highlight(vcl::Window *pWindow)
+{
+ ToolBox *pToolBox = static_cast <ToolBox *> (pWindow);
+
+ // Make sure either the toolbox or its parent toolbox has the focus
+ if ( ! pToolBox->HasFocus() )
+ {
+ ToolBox* pToolBoxParent = dynamic_cast< ToolBox* >( pToolBox->GetParent() );
+ if ( ! pToolBoxParent || ! pToolBoxParent->HasFocus() )
+ return;
+ }
+
+ notify_toolbox_item_focus(pToolBox);
+}
+
+static void handle_toolbox_highlightoff(vcl::Window const *pWindow)
+{
+ ToolBox* pToolBoxParent = dynamic_cast< ToolBox* >( pWindow->GetParent() );
+
+ // Notify when leaving sub toolboxes
+ if( pToolBoxParent && pToolBoxParent->HasFocus() )
+ notify_toolbox_item_focus( pToolBoxParent );
+}
+
+/*****************************************************************************/
+
+static void create_wrapper_for_child(
+ const uno::Reference< accessibility::XAccessibleContext >& xContext,
+ sal_Int32 index)
+{
+ if( xContext.is() )
+ {
+ uno::Reference< accessibility::XAccessible > xChild(xContext->getAccessibleChild(index));
+ if( xChild.is() )
+ {
+ // create the wrapper object - it will survive the unref unless it is a transient object
+ g_object_unref( atk_object_wrapper_ref( xChild ) );
+ }
+ }
+}
+
+/*****************************************************************************/
+
+static void handle_toolbox_buttonchange(VclWindowEvent const *pEvent)
+{
+ vcl::Window* pWindow = pEvent->GetWindow();
+ sal_Int32 index = static_cast<sal_Int32>(reinterpret_cast<sal_IntPtr>(pEvent->GetData()));
+
+ if( pWindow && pWindow->IsReallyVisible() )
+ {
+ uno::Reference< accessibility::XAccessible > xAccessible(pWindow->GetAccessible());
+ if( xAccessible.is() )
+ {
+ create_wrapper_for_child(xAccessible->getAccessibleContext(), index);
+ }
+ }
+}
+
+/*****************************************************************************/
+
+namespace {
+
+struct WindowList {
+ ~WindowList() { assert(list.empty()); };
+ // needs to be empty already on DeInitVCL, but at least check it's empty
+ // on exit
+
+ std::set< VclPtr<vcl::Window> > list;
+};
+
+WindowList g_aWindowList;
+
+}
+
+rtl::Reference<DocumentFocusListener> GtkSalData::GetDocumentFocusListener()
+{
+ rtl::Reference<DocumentFocusListener> xDFL = m_xDocumentFocusListener.get();
+ if (!xDFL)
+ {
+ xDFL = new DocumentFocusListener;
+ m_xDocumentFocusListener = xDFL.get();
+ }
+ return xDFL;
+}
+
+static void handle_get_focus(::VclWindowEvent const * pEvent)
+{
+ GtkSalData *const pSalData(GetGtkSalData());
+ assert(pSalData);
+
+ rtl::Reference<DocumentFocusListener> xDocumentFocusListener(pSalData->GetDocumentFocusListener());
+
+ vcl::Window *pWindow = pEvent->GetWindow();
+
+ // The menu bar is handled through VclEventId::MenuHighlightED
+ if( ! pWindow || !pWindow->IsReallyVisible() || pWindow->GetType() == WindowType::MENUBARWINDOW )
+ return;
+
+ // ToolBoxes are handled through VclEventId::ToolboxHighlight
+ if( pWindow->GetType() == WindowType::TOOLBOX )
+ return;
+
+ if( pWindow->GetType() == WindowType::TABCONTROL )
+ {
+ handle_tabpage_activated( pWindow );
+ return;
+ }
+
+ uno::Reference< accessibility::XAccessible > xAccessible =
+ pWindow->GetAccessible();
+
+ if( ! xAccessible.is() )
+ return;
+
+ uno::Reference< accessibility::XAccessibleContext > xContext =
+ xAccessible->getAccessibleContext();
+
+ if( ! xContext.is() )
+ return;
+
+ sal_Int64 nStateSet = xContext->getAccessibleStateSet();
+
+/* the UNO ToolBox wrapper does not (yet?) support XAccessibleSelection, so we
+ * need to add listeners to the children instead of re-using the tabpage stuff
+ */
+ if( (nStateSet & accessibility::AccessibleStateType::FOCUSED) &&
+ ( pWindow->GetType() != WindowType::TREELISTBOX ) )
+ {
+ atk_wrapper_focus_tracker_notify_when_idle( xAccessible );
+ }
+ else
+ {
+ if( g_aWindowList.list.insert(pWindow).second )
+ {
+ try
+ {
+ xDocumentFocusListener->attachRecursive(xAccessible, xContext, nStateSet);
+ }
+ catch (const uno::Exception&)
+ {
+ g_warning( "Exception caught processing focus events" );
+ }
+ }
+ }
+}
+
+/*****************************************************************************/
+
+static void handle_menu_highlighted(::VclMenuEvent const * pEvent)
+{
+ try
+ {
+ Menu* pMenu = pEvent->GetMenu();
+ sal_uInt16 nPos = pEvent->GetItemPos();
+
+ if( pMenu && nPos != 0xFFFF)
+ {
+ uno::Reference< accessibility::XAccessible > xAccessible ( pMenu->GetAccessible() );
+
+ if( xAccessible.is() )
+ {
+ uno::Reference< accessibility::XAccessibleContext > xContext ( xAccessible->getAccessibleContext() );
+
+ if( xContext.is() )
+ atk_wrapper_focus_tracker_notify_when_idle( xContext->getAccessibleChild( nPos ) );
+ }
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ g_warning( "Exception caught processing menu highlight events" );
+ }
+}
+
+/*****************************************************************************/
+
+static void WindowEventHandler(void *, VclSimpleEvent& rEvent)
+{
+ try
+ {
+ switch (rEvent.GetId())
+ {
+ case VclEventId::WindowShow:
+ break;
+ case VclEventId::WindowHide:
+ break;
+ case VclEventId::WindowClose:
+ break;
+ case VclEventId::WindowGetFocus:
+ handle_get_focus(static_cast< ::VclWindowEvent const * >(&rEvent));
+ break;
+ case VclEventId::WindowLoseFocus:
+ break;
+ case VclEventId::WindowMinimize:
+ break;
+ case VclEventId::WindowNormalize:
+ break;
+ case VclEventId::WindowKeyInput:
+ case VclEventId::WindowKeyUp:
+ case VclEventId::WindowCommand:
+ case VclEventId::WindowMouseMove:
+ break;
+
+ case VclEventId::MenuHighlight:
+ if (const VclMenuEvent* pMenuEvent = dynamic_cast<const VclMenuEvent*>(&rEvent))
+ {
+ handle_menu_highlighted(pMenuEvent);
+ }
+ break;
+
+ case VclEventId::ToolboxHighlight:
+ handle_toolbox_highlight(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
+ break;
+
+ case VclEventId::ToolboxButtonStateChanged:
+ handle_toolbox_buttonchange(static_cast< ::VclWindowEvent const * >(&rEvent));
+ break;
+
+ case VclEventId::ObjectDying:
+ g_aWindowList.list.erase( static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow() );
+ [[fallthrough]];
+ case VclEventId::ToolboxHighlightOff:
+ handle_toolbox_highlightoff(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
+ break;
+
+ case VclEventId::TabpageActivate:
+ handle_tabpage_activated(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
+ break;
+
+ case VclEventId::ComboboxSetText:
+ // This looks quite strange to me. Stumbled over this when fixing #i104290#.
+ // This kicked in when leaving the combobox in the toolbar, after that the events worked.
+ // I guess this was a try to work around missing combobox events, which didn't do the full job, and shouldn't be necessary anymore.
+ // Fix for #i104290# was done in toolkit/source/awt/vclxaccessiblecomponent, FOCUSED state for compound controls in general.
+ // create_wrapper_for_children(static_cast< ::VclWindowEvent const * >(pEvent)->GetWindow());
+ break;
+
+ default:
+ break;
+ }
+ }
+ catch (const lang::IndexOutOfBoundsException&)
+ {
+ g_warning("WindowEventHandler: Focused object has invalid index in parent");
+ }
+}
+
+static Link<VclSimpleEvent&,void> g_aEventListenerLink( nullptr, WindowEventHandler );
+
+/*****************************************************************************/
+
+void ooo_atk_util_ensure_event_listener()
+{
+ static bool bInited;
+ if (!bInited)
+ {
+ Application::AddEventListener( g_aEventListenerLink );
+ bInited = true;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkutil.hxx b/vcl/unx/gtk3/a11y/atkutil.hxx
new file mode 100644
index 0000000000..bc3d9d73b9
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkutil.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <atk/atk.h>
+
+void ooo_atk_util_ensure_event_listener();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkvalue.cxx b/vcl/unx/gtk3/a11y/atkvalue.cxx
new file mode 100644
index 0000000000..014164da20
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkvalue.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 "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleValue.hpp>
+
+#include <cmath>
+#include <string.h>
+
+using namespace ::com::sun::star;
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleValue>
+ getValue( AtkValue *pValue )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pValue );
+ if( pWrap )
+ {
+ if( !pWrap->mpValue.is() )
+ {
+ pWrap->mpValue.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpValue;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleValue>();
+}
+
+static void anyToGValue( const uno::Any& aAny, GValue *pValue )
+{
+ // FIXME: expand to lots of types etc.
+ double aDouble=0;
+ aAny >>= aDouble;
+
+ memset( pValue, 0, sizeof( GValue ) );
+ g_value_init( pValue, G_TYPE_DOUBLE );
+ g_value_set_double( pValue, aDouble );
+}
+
+extern "C" {
+
+static void
+value_wrapper_get_current_value( AtkValue *value,
+ GValue *gval )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleValue> pValue
+ = getValue( value );
+ if( pValue.is() )
+ anyToGValue( pValue->getCurrentValue(), gval );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getCurrentValue()" );
+ }
+}
+
+static void
+value_wrapper_get_maximum_value( AtkValue *value,
+ GValue *gval )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleValue> pValue
+ = getValue( value );
+ if( pValue.is() )
+ anyToGValue( pValue->getMaximumValue(), gval );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getCurrentValue()" );
+ }
+}
+
+static void
+value_wrapper_get_minimum_value( AtkValue *value,
+ GValue *gval )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleValue> pValue
+ = getValue( value );
+ if( pValue.is() )
+ anyToGValue( pValue->getMinimumValue(), gval );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getCurrentValue()" );
+ }
+}
+
+static gboolean
+value_wrapper_set_current_value( AtkValue *value,
+ const GValue *gval )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleValue> pValue
+ = getValue( value );
+ if( pValue.is() )
+ {
+ double aDouble = g_value_get_double( gval );
+
+ // Different types of numerical values for XAccessibleValue are possible.
+ // If current value has an integer type, also use that for the new value, to make
+ // sure underlying implementations expecting that can handle the value properly.
+ const css::uno::Any aCurrentValue = pValue->getCurrentValue();
+ if (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_LONG)
+ {
+ const sal_Int32 nValue = std::round<sal_Int32>(aDouble);
+ return pValue->setCurrentValue(css::uno::Any(nValue));
+ }
+ else if (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_HYPER)
+ {
+ const sal_Int64 nValue = std::round<sal_Int64>(aDouble);
+ return pValue->setCurrentValue(css::uno::Any(nValue));
+ }
+
+ return pValue->setCurrentValue( uno::Any(aDouble) );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getCurrentValue()" );
+ }
+
+ return FALSE;
+}
+
+} // extern "C"
+
+void
+valueIfaceInit (AtkValueIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->get_current_value = value_wrapper_get_current_value;
+ iface->get_maximum_value = value_wrapper_get_maximum_value;
+ iface->get_minimum_value = value_wrapper_get_minimum_value;
+ iface->set_current_value = value_wrapper_set_current_value;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkwrapper.cxx b/vcl/unx/gtk3/a11y/atkwrapper.cxx
new file mode 100644
index 0000000000..c946a6e3da
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkwrapper.cxx
@@ -0,0 +1,1103 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/uno/Any.hxx>
+#include <com/sun/star/uno/Type.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/accessibility/AccessibleRelation.hpp>
+#include <com/sun/star/accessibility/AccessibleRelationType.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/XAccessible.hpp>
+#include <com/sun/star/accessibility/XAccessibleText.hpp>
+#include <com/sun/star/accessibility/XAccessibleValue.hpp>
+#include <com/sun/star/accessibility/XAccessibleAction.hpp>
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/XAccessibleContext2.hpp>
+#include <com/sun/star/accessibility/XAccessibleComponent.hpp>
+#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
+#include <com/sun/star/accessibility/XAccessibleRelationSet.hpp>
+#include <com/sun/star/accessibility/XAccessibleTable.hpp>
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+#include <com/sun/star/accessibility/XAccessibleExtendedAttributes.hpp>
+#include <com/sun/star/accessibility/XAccessibleImage.hpp>
+#include <com/sun/star/accessibility/XAccessibleHypertext.hpp>
+#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
+#include <com/sun/star/awt/XWindow.hpp>
+
+#include <rtl/strbuf.hxx>
+#include <osl/diagnose.h>
+#include <comphelper/diagnose_ex.hxx>
+#include <vcl/syschild.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/toolkit/unowrap.hxx>
+
+#include "atkwrapper.hxx"
+#include "atkregistry.hxx"
+#include "atklistener.hxx"
+#include "atktextattributes.hxx"
+
+#include <vector>
+#include <dlfcn.h>
+
+using namespace ::com::sun::star;
+
+static GObjectClass *parent_class = nullptr;
+
+static AtkRelationType mapRelationType( sal_Int16 nRelation )
+{
+ AtkRelationType type = ATK_RELATION_NULL;
+
+ switch( nRelation )
+ {
+ case accessibility::AccessibleRelationType::CONTENT_FLOWS_FROM:
+ type = ATK_RELATION_FLOWS_FROM;
+ break;
+
+ case accessibility::AccessibleRelationType::CONTENT_FLOWS_TO:
+ type = ATK_RELATION_FLOWS_TO;
+ break;
+
+ case accessibility::AccessibleRelationType::CONTROLLED_BY:
+ type = ATK_RELATION_CONTROLLED_BY;
+ break;
+
+ case accessibility::AccessibleRelationType::CONTROLLER_FOR:
+ type = ATK_RELATION_CONTROLLER_FOR;
+ break;
+
+ case accessibility::AccessibleRelationType::LABEL_FOR:
+ type = ATK_RELATION_LABEL_FOR;
+ break;
+
+ case accessibility::AccessibleRelationType::LABELED_BY:
+ type = ATK_RELATION_LABELLED_BY;
+ break;
+
+ case accessibility::AccessibleRelationType::MEMBER_OF:
+ type = ATK_RELATION_MEMBER_OF;
+ break;
+
+ case accessibility::AccessibleRelationType::SUB_WINDOW_OF:
+ type = ATK_RELATION_SUBWINDOW_OF;
+ break;
+
+ case accessibility::AccessibleRelationType::NODE_CHILD_OF:
+ type = ATK_RELATION_NODE_CHILD_OF;
+ break;
+
+ default:
+ break;
+ }
+
+ return type;
+}
+
+AtkStateType mapAtkState( sal_Int64 nState )
+{
+ AtkStateType type = ATK_STATE_INVALID;
+
+ // A perfect / complete mapping ...
+ switch( nState )
+ {
+#define MAP_DIRECT( a ) \
+ case accessibility::AccessibleStateType::a: \
+ type = ATK_STATE_##a; break
+
+ MAP_DIRECT( INVALID );
+ MAP_DIRECT( ACTIVE );
+ MAP_DIRECT( ARMED );
+ MAP_DIRECT( BUSY );
+ MAP_DIRECT( CHECKABLE );
+ MAP_DIRECT( CHECKED );
+ MAP_DIRECT( EDITABLE );
+ MAP_DIRECT( ENABLED );
+ MAP_DIRECT( EXPANDABLE );
+ MAP_DIRECT( EXPANDED );
+ MAP_DIRECT( FOCUSABLE );
+ MAP_DIRECT( FOCUSED );
+ MAP_DIRECT( HORIZONTAL );
+ MAP_DIRECT( ICONIFIED );
+ MAP_DIRECT( INDETERMINATE );
+ MAP_DIRECT( MANAGES_DESCENDANTS );
+ MAP_DIRECT( MODAL );
+ MAP_DIRECT( MULTI_LINE );
+ MAP_DIRECT( OPAQUE );
+ MAP_DIRECT( PRESSED );
+ MAP_DIRECT( RESIZABLE );
+ MAP_DIRECT( SELECTABLE );
+ MAP_DIRECT( SELECTED );
+ MAP_DIRECT( SENSITIVE );
+ MAP_DIRECT( SHOWING );
+ MAP_DIRECT( SINGLE_LINE );
+ MAP_DIRECT( STALE );
+ MAP_DIRECT( TRANSIENT );
+ MAP_DIRECT( VERTICAL );
+ MAP_DIRECT( VISIBLE );
+ MAP_DIRECT( DEFAULT );
+ // a spelling error ...
+ case accessibility::AccessibleStateType::DEFUNC:
+ type = ATK_STATE_DEFUNCT; break;
+ case accessibility::AccessibleStateType::MULTI_SELECTABLE:
+ type = ATK_STATE_MULTISELECTABLE; break;
+ default:
+ //Mis-use ATK_STATE_LAST_DEFINED to check if a state is unmapped
+ //NOTE! Do not report it
+ type = ATK_STATE_LAST_DEFINED;
+ break;
+ }
+
+ return type;
+}
+
+static AtkRole mapToAtkRole(sal_Int16 nRole, sal_Int64 nStates)
+{
+ switch (nRole)
+ {
+ case accessibility::AccessibleRole::UNKNOWN:
+ return ATK_ROLE_UNKNOWN;
+ case accessibility::AccessibleRole::ALERT:
+ return ATK_ROLE_ALERT;
+ case accessibility::AccessibleRole::BLOCK_QUOTE:
+ return ATK_ROLE_BLOCK_QUOTE;
+ case accessibility::AccessibleRole::COLUMN_HEADER:
+ return ATK_ROLE_COLUMN_HEADER;
+ case accessibility::AccessibleRole::CANVAS:
+ return ATK_ROLE_CANVAS;
+ case accessibility::AccessibleRole::CHECK_BOX:
+ return ATK_ROLE_CHECK_BOX;
+ case accessibility::AccessibleRole::CHECK_MENU_ITEM:
+ return ATK_ROLE_CHECK_MENU_ITEM;
+ case accessibility::AccessibleRole::COLOR_CHOOSER:
+ return ATK_ROLE_COLOR_CHOOSER;
+ case accessibility::AccessibleRole::COMBO_BOX:
+ return ATK_ROLE_COMBO_BOX;
+ case accessibility::AccessibleRole::DATE_EDITOR:
+ return ATK_ROLE_DATE_EDITOR;
+ case accessibility::AccessibleRole::DESKTOP_ICON:
+ return ATK_ROLE_DESKTOP_ICON;
+ case accessibility::AccessibleRole::DESKTOP_PANE:
+ return ATK_ROLE_DESKTOP_FRAME;
+ case accessibility::AccessibleRole::DIRECTORY_PANE:
+ return ATK_ROLE_DIRECTORY_PANE;
+ case accessibility::AccessibleRole::DIALOG:
+ return ATK_ROLE_DIALOG;
+ case accessibility::AccessibleRole::DOCUMENT:
+ return ATK_ROLE_DOCUMENT_FRAME;
+ case accessibility::AccessibleRole::EMBEDDED_OBJECT:
+ return ATK_ROLE_EMBEDDED;
+ case accessibility::AccessibleRole::END_NOTE:
+ return ATK_ROLE_FOOTNOTE;
+ case accessibility::AccessibleRole::FILE_CHOOSER:
+ return ATK_ROLE_FILE_CHOOSER;
+ case accessibility::AccessibleRole::FILLER:
+ return ATK_ROLE_FILLER;
+ case accessibility::AccessibleRole::FONT_CHOOSER:
+ return ATK_ROLE_FONT_CHOOSER;
+ case accessibility::AccessibleRole::FOOTER:
+ return ATK_ROLE_FOOTER;
+ case accessibility::AccessibleRole::FOOTNOTE:
+ return ATK_ROLE_FOOTNOTE;
+ case accessibility::AccessibleRole::FRAME:
+ return ATK_ROLE_FRAME;
+ case accessibility::AccessibleRole::GLASS_PANE:
+ return ATK_ROLE_GLASS_PANE;
+ case accessibility::AccessibleRole::GRAPHIC:
+ return ATK_ROLE_IMAGE;
+ case accessibility::AccessibleRole::GROUP_BOX:
+ return ATK_ROLE_GROUPING;
+ case accessibility::AccessibleRole::HEADER:
+ return ATK_ROLE_HEADER;
+ case accessibility::AccessibleRole::HEADING:
+ return ATK_ROLE_HEADING;
+ case accessibility::AccessibleRole::HYPER_LINK:
+ return ATK_ROLE_LINK;
+ case accessibility::AccessibleRole::ICON:
+ return ATK_ROLE_ICON;
+ case accessibility::AccessibleRole::INTERNAL_FRAME:
+ return ATK_ROLE_INTERNAL_FRAME;
+ case accessibility::AccessibleRole::LABEL:
+ return ATK_ROLE_LABEL;
+ case accessibility::AccessibleRole::LAYERED_PANE:
+ return ATK_ROLE_LAYERED_PANE;
+ case accessibility::AccessibleRole::LIST:
+ return ATK_ROLE_LIST;
+ case accessibility::AccessibleRole::LIST_ITEM:
+ return ATK_ROLE_LIST_ITEM;
+ case accessibility::AccessibleRole::MENU:
+ return ATK_ROLE_MENU;
+ case accessibility::AccessibleRole::MENU_BAR:
+ return ATK_ROLE_MENU_BAR;
+ case accessibility::AccessibleRole::MENU_ITEM:
+ return ATK_ROLE_MENU_ITEM;
+ case accessibility::AccessibleRole::OPTION_PANE:
+ return ATK_ROLE_OPTION_PANE;
+ case accessibility::AccessibleRole::PAGE_TAB:
+ return ATK_ROLE_PAGE_TAB;
+ case accessibility::AccessibleRole::PAGE_TAB_LIST:
+ return ATK_ROLE_PAGE_TAB_LIST;
+ case accessibility::AccessibleRole::PANEL:
+ return ATK_ROLE_PANEL;
+ case accessibility::AccessibleRole::PARAGRAPH:
+ return ATK_ROLE_PARAGRAPH;
+ case accessibility::AccessibleRole::PASSWORD_TEXT:
+ return ATK_ROLE_PASSWORD_TEXT;
+ case accessibility::AccessibleRole::POPUP_MENU:
+ return ATK_ROLE_POPUP_MENU;
+ case accessibility::AccessibleRole::PUSH_BUTTON:
+ return ATK_ROLE_PUSH_BUTTON;
+ case accessibility::AccessibleRole::PROGRESS_BAR:
+ return ATK_ROLE_PROGRESS_BAR;
+ case accessibility::AccessibleRole::RADIO_BUTTON:
+ return ATK_ROLE_RADIO_BUTTON;
+ case accessibility::AccessibleRole::RADIO_MENU_ITEM:
+ return ATK_ROLE_RADIO_MENU_ITEM;
+ case accessibility::AccessibleRole::ROW_HEADER:
+ return ATK_ROLE_ROW_HEADER;
+ case accessibility::AccessibleRole::ROOT_PANE:
+ return ATK_ROLE_ROOT_PANE;
+ case accessibility::AccessibleRole::SCROLL_BAR:
+ return ATK_ROLE_SCROLL_BAR;
+ case accessibility::AccessibleRole::SCROLL_PANE:
+ return ATK_ROLE_SCROLL_PANE;
+ case accessibility::AccessibleRole::SHAPE:
+ return ATK_ROLE_PANEL;
+ case accessibility::AccessibleRole::SEPARATOR:
+ return ATK_ROLE_SEPARATOR;
+ case accessibility::AccessibleRole::SLIDER:
+ return ATK_ROLE_SLIDER;
+ case accessibility::AccessibleRole::SPIN_BOX:
+ return ATK_ROLE_SPIN_BUTTON;
+ case accessibility::AccessibleRole::SPLIT_PANE:
+ return ATK_ROLE_SPLIT_PANE;
+ case accessibility::AccessibleRole::STATUS_BAR:
+ return ATK_ROLE_STATUSBAR;
+ case accessibility::AccessibleRole::TABLE:
+ return ATK_ROLE_TABLE;
+ case accessibility::AccessibleRole::TABLE_CELL:
+ return ATK_ROLE_TABLE_CELL;
+ case accessibility::AccessibleRole::TEXT:
+ return ATK_ROLE_TEXT;
+ case accessibility::AccessibleRole::TEXT_FRAME:
+ return ATK_ROLE_PANEL;
+ case accessibility::AccessibleRole::TOGGLE_BUTTON:
+ return ATK_ROLE_TOGGLE_BUTTON;
+ case accessibility::AccessibleRole::TOOL_BAR:
+ return ATK_ROLE_TOOL_BAR;
+ case accessibility::AccessibleRole::TOOL_TIP:
+ return ATK_ROLE_TOOL_TIP;
+ case accessibility::AccessibleRole::TREE:
+ return ATK_ROLE_TREE;
+ case accessibility::AccessibleRole::VIEW_PORT:
+ return ATK_ROLE_VIEWPORT;
+ case accessibility::AccessibleRole::WINDOW:
+ return ATK_ROLE_WINDOW;
+ case accessibility::AccessibleRole::BUTTON_DROPDOWN:
+ {
+ if (nStates & css::accessibility::AccessibleStateType::CHECKABLE)
+ return ATK_ROLE_TOGGLE_BUTTON;
+ return ATK_ROLE_PUSH_BUTTON;
+ }
+ case accessibility::AccessibleRole::BUTTON_MENU:
+#if ATK_CHECK_VERSION(2, 46, 0)
+ return ATK_ROLE_PUSH_BUTTON_MENU;
+#else
+ return ATK_ROLE_PUSH_BUTTON;
+#endif
+ case accessibility::AccessibleRole::CAPTION:
+ return ATK_ROLE_CAPTION;
+ case accessibility::AccessibleRole::CHART:
+ return ATK_ROLE_CHART;
+ case accessibility::AccessibleRole::EDIT_BAR:
+ return ATK_ROLE_EDITBAR;
+ case accessibility::AccessibleRole::FORM:
+ return ATK_ROLE_FORM;
+ case accessibility::AccessibleRole::IMAGE_MAP:
+ return ATK_ROLE_IMAGE_MAP;
+ case accessibility::AccessibleRole::NOTE:
+ return ATK_ROLE_COMMENT;
+ case accessibility::AccessibleRole::PAGE:
+ return ATK_ROLE_PAGE;
+ case accessibility::AccessibleRole::RULER:
+ return ATK_ROLE_RULER;
+ case accessibility::AccessibleRole::SECTION:
+ return ATK_ROLE_SECTION;
+ case accessibility::AccessibleRole::TREE_ITEM:
+ return ATK_ROLE_TREE_ITEM;
+ case accessibility::AccessibleRole::TREE_TABLE:
+ return ATK_ROLE_TREE_TABLE;
+ case accessibility::AccessibleRole::COMMENT:
+ return ATK_ROLE_COMMENT;
+ case accessibility::AccessibleRole::COMMENT_END:
+ return ATK_ROLE_UNKNOWN;
+ case accessibility::AccessibleRole::DOCUMENT_PRESENTATION:
+ return ATK_ROLE_DOCUMENT_PRESENTATION;
+ case accessibility::AccessibleRole::DOCUMENT_SPREADSHEET:
+ return ATK_ROLE_DOCUMENT_SPREADSHEET;
+ case accessibility::AccessibleRole::DOCUMENT_TEXT:
+ return ATK_ROLE_DOCUMENT_TEXT;
+ case accessibility::AccessibleRole::STATIC:
+ return ATK_ROLE_STATIC;
+ case accessibility::AccessibleRole::NOTIFICATION:
+ return ATK_ROLE_NOTIFICATION;
+ default:
+ SAL_WARN("vcl.gtk", "Unmapped accessible role: " << nRole);
+ return ATK_ROLE_UNKNOWN;
+ }
+}
+
+/*****************************************************************************/
+
+extern "C" {
+
+/*****************************************************************************/
+
+static const gchar*
+wrapper_get_name( AtkObject *atk_obj )
+{
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
+
+ if( obj->mpContext.is() )
+ {
+ try {
+ OString aName =
+ OUStringToOString(
+ obj->mpContext->getAccessibleName(),
+ RTL_TEXTENCODING_UTF8);
+
+ int nCmp = atk_obj->name ? rtl_str_compare( atk_obj->name, aName.getStr() ) : -1;
+ if( nCmp != 0 )
+ {
+ if( atk_obj->name )
+ g_free(atk_obj->name);
+ atk_obj->name = g_strdup(aName.getStr());
+
+ return atk_obj->name;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleName()" );
+ }
+ }
+
+ return ATK_OBJECT_CLASS (parent_class)->get_name(atk_obj);
+}
+
+/*****************************************************************************/
+
+static const gchar*
+wrapper_get_description( AtkObject *atk_obj )
+{
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
+
+ if( obj->mpContext.is() )
+ {
+ try {
+ OString aDescription =
+ OUStringToOString(
+ obj->mpContext->getAccessibleDescription(),
+ RTL_TEXTENCODING_UTF8);
+
+ g_free(atk_obj->description);
+ atk_obj->description = g_strdup(aDescription.getStr());
+
+ return atk_obj->description;
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleDescription()" );
+ }
+ }
+
+ return ATK_OBJECT_CLASS (parent_class)->get_description(atk_obj);
+
+}
+
+/*****************************************************************************/
+
+static AtkAttributeSet *
+wrapper_get_attributes( AtkObject *atk_obj )
+{
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER( atk_obj );
+ AtkAttributeSet *pSet = nullptr;
+
+ try
+ {
+ uno::Reference< accessibility::XAccessibleExtendedAttributes >
+ xExtendedAttrs( obj->mpContext, uno::UNO_QUERY );
+ if( xExtendedAttrs.is() )
+ pSet = attribute_set_new_from_extended_attributes( xExtendedAttrs );
+ }
+ catch(const uno::Exception&)
+ {
+ g_warning( "Exception in getAccessibleAttributes()" );
+ }
+
+ return pSet;
+}
+
+/*****************************************************************************/
+
+static gint
+wrapper_get_n_children( AtkObject *atk_obj )
+{
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
+
+ if (obj->mpSysObjChild)
+ return 1;
+
+ gint n = 0;
+
+ if( obj->mpContext.is() )
+ {
+ try {
+ sal_Int64 nChildCount = obj->mpContext->getAccessibleChildCount();
+ if (nChildCount > std::numeric_limits<gint>::max())
+ {
+ SAL_WARN("vcl.gtk", "wrapper_get_n_children: Child count exceeds maximum gint value, "
+ "returning max gint.");
+ nChildCount = std::numeric_limits<gint>::max();
+ }
+ n = nChildCount;
+ }
+ catch(const uno::Exception&) {
+ TOOLS_WARN_EXCEPTION( "vcl", "Exception" );
+ }
+ }
+
+ return n;
+}
+
+/*****************************************************************************/
+
+static AtkObject *
+wrapper_ref_child( AtkObject *atk_obj,
+ gint i )
+{
+ SolarMutexGuard aGuard;
+
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
+
+ if (obj->mpSysObjChild)
+ {
+ g_object_ref(obj->mpSysObjChild);
+ return obj->mpSysObjChild;
+ }
+
+ AtkObject* child = nullptr;
+
+ // see comments above atk_object_wrapper_remove_child
+ if( -1 < i && obj->index_of_child_about_to_be_removed == i )
+ {
+ g_object_ref( obj->child_about_to_be_removed );
+ return obj->child_about_to_be_removed;
+ }
+
+ if( obj->mpContext.is() )
+ {
+ try {
+ uno::Reference< accessibility::XAccessible > xAccessible =
+ obj->mpContext->getAccessibleChild( i );
+
+ child = atk_object_wrapper_ref( xAccessible );
+ }
+ catch(const uno::Exception&) {
+ TOOLS_WARN_EXCEPTION( "vcl", "getAccessibleChild");
+ }
+ }
+
+ return child;
+}
+
+/*****************************************************************************/
+
+static gint
+wrapper_get_index_in_parent( AtkObject *atk_obj )
+{
+ SolarMutexGuard aGuard;
+
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
+
+ //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
+ if (obj->mpOrig)
+ return atk_object_get_index_in_parent(obj->mpOrig);
+
+ gint i = -1;
+
+ if( obj->mpContext.is() )
+ {
+ try {
+ sal_Int64 nIndex = obj->mpContext->getAccessibleIndexInParent();
+ if (nIndex > std::numeric_limits<gint>::max())
+ {
+ // use -2 when the child index is too large to fit into 32 bit to neither use the
+ // valid index of another child nor -1, which would e.g. make Orca interpret the
+ // object as being a zombie
+ SAL_WARN("vcl.gtk", "wrapper_get_index_in_parent: Child index exceeds maximum gint value, "
+ "returning -2.");
+ nIndex = -2;
+ }
+ i = nIndex;
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleIndexInParent()" );
+ }
+ }
+ return i;
+}
+
+/*****************************************************************************/
+
+AtkRelation*
+atk_object_wrapper_relation_new(const accessibility::AccessibleRelation& rRelation)
+{
+ sal_uInt32 nTargetCount = rRelation.TargetSet.getLength();
+
+ std::vector<AtkObject*> aTargets;
+
+ for (const auto& rTarget : rRelation.TargetSet)
+ {
+ uno::Reference< accessibility::XAccessible > xAccessible( rTarget, uno::UNO_QUERY );
+ aTargets.push_back(atk_object_wrapper_ref(xAccessible));
+ }
+
+ AtkRelation *pRel =
+ atk_relation_new(
+ aTargets.data(), nTargetCount,
+ mapRelationType( rRelation.RelationType )
+ );
+
+ return pRel;
+}
+
+static AtkRelationSet *
+wrapper_ref_relation_set( AtkObject *atk_obj )
+{
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
+
+ //if we're a native GtkDrawingArea with custom a11y, use the default toolkit relation set impl
+ if (obj->mpOrig)
+ return atk_object_ref_relation_set(obj->mpOrig);
+
+ AtkRelationSet *pSet = atk_relation_set_new();
+
+ if( obj->mpContext.is() )
+ {
+ try {
+ uno::Reference< accessibility::XAccessibleRelationSet > xRelationSet(
+ obj->mpContext->getAccessibleRelationSet()
+ );
+
+ sal_Int32 nRelations = xRelationSet.is() ? xRelationSet->getRelationCount() : 0;
+ for( sal_Int32 n = 0; n < nRelations; n++ )
+ {
+ AtkRelation *pRel = atk_object_wrapper_relation_new(xRelationSet->getRelation(n));
+ atk_relation_set_add(pSet, pRel);
+ g_object_unref(pRel);
+ }
+ }
+ catch(const uno::Exception &) {
+ g_object_unref( G_OBJECT( pSet ) );
+ pSet = nullptr;
+ }
+ }
+
+ return pSet;
+}
+
+static AtkStateSet *
+wrapper_ref_state_set( AtkObject *atk_obj )
+{
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
+ AtkStateSet *pSet = atk_state_set_new();
+
+ if( obj->mpContext.is() )
+ {
+ try {
+ sal_Int64 nStateSet = obj->mpContext->getAccessibleStateSet();
+
+ if( nStateSet )
+ {
+ for (int i=0; i<63; ++i)
+ {
+ // ATK_STATE_LAST_DEFINED is used to check if the state
+ // is unmapped, do not report it to Atk
+ sal_Int64 nState = sal_Int64(1) << i;
+ if ( (nStateSet & nState) && mapAtkState( nState ) != ATK_STATE_LAST_DEFINED )
+ atk_state_set_add_state( pSet, mapAtkState( nState ) );
+ }
+
+ // We need to emulate FOCUS state for menus, menu-items etc.
+ if( atk_obj == atk_get_focus_object() )
+ atk_state_set_add_state( pSet, ATK_STATE_FOCUSED );
+/* FIXME - should we do this ?
+ else
+ atk_state_set_remove_state( pSet, ATK_STATE_FOCUSED );
+*/
+ }
+ }
+
+ catch(const uno::Exception &) {
+ g_warning( "Exception in wrapper_ref_state_set" );
+ atk_state_set_add_state( pSet, ATK_STATE_DEFUNCT );
+ }
+ }
+ else
+ atk_state_set_add_state( pSet, ATK_STATE_DEFUNCT );
+
+ return pSet;
+}
+
+/*****************************************************************************/
+
+static void
+atk_object_wrapper_finalize (GObject *obj)
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER (obj);
+
+ if( pWrap->mpAccessible.is() )
+ {
+ ooo_wrapper_registry_remove( pWrap->mpAccessible );
+ SolarMutexGuard aGuard;
+ pWrap->mpAccessible.clear();
+ }
+
+ atk_object_wrapper_dispose( pWrap );
+
+ parent_class->finalize( obj );
+}
+
+static void
+atk_object_wrapper_class_init (AtkObjectWrapperClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS( klass );
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS( klass );
+
+ parent_class = static_cast<GObjectClass *>(g_type_class_peek_parent (klass));
+
+ // GObject methods
+ gobject_class->finalize = atk_object_wrapper_finalize;
+
+ // AtkObject methods
+ atk_class->get_name = wrapper_get_name;
+ atk_class->get_description = wrapper_get_description;
+ atk_class->get_attributes = wrapper_get_attributes;
+ atk_class->get_n_children = wrapper_get_n_children;
+ atk_class->ref_child = wrapper_ref_child;
+ atk_class->get_index_in_parent = wrapper_get_index_in_parent;
+ atk_class->ref_relation_set = wrapper_ref_relation_set;
+ atk_class->ref_state_set = wrapper_ref_state_set;
+
+ AtkObjectClass* orig_atk_klass = static_cast<AtkObjectClass*>(g_type_class_ref(ATK_TYPE_OBJECT));
+ // tdf#150496 we want to inherit from GtkAccessible because gtk assumes it can cast to GtkAccessible
+ // but we want the original behaviour we got from atk_object_real_get_parent when we inherited
+ // from AtkObject
+ atk_class->get_parent = orig_atk_klass->get_parent;
+ // and likewise for focus_event
+ atk_class->focus_event = orig_atk_klass->focus_event;
+ g_type_class_unref(orig_atk_klass);
+}
+
+static void
+atk_object_wrapper_init (AtkObjectWrapper *wrapper,
+ AtkObjectWrapperClass*)
+{
+ wrapper->mpAction = nullptr;
+ wrapper->mpComponent = nullptr;
+ wrapper->mpEditableText = nullptr;
+ wrapper->mpHypertext = nullptr;
+ wrapper->mpImage = nullptr;
+ wrapper->mpSelection = nullptr;
+ wrapper->mpTable = nullptr;
+ wrapper->mpTableSelection = nullptr;
+ wrapper->mpText = nullptr;
+ wrapper->mpValue = nullptr;
+}
+
+} // extern "C"
+
+GType
+atk_object_wrapper_get_type()
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ static const GTypeInfo typeInfo =
+ {
+ sizeof (AtkObjectWrapperClass),
+ nullptr,
+ nullptr,
+ reinterpret_cast<GClassInitFunc>(atk_object_wrapper_class_init),
+ nullptr,
+ nullptr,
+ sizeof (AtkObjectWrapper),
+ 0,
+ reinterpret_cast<GInstanceInitFunc>(atk_object_wrapper_init),
+ nullptr
+ } ;
+ type = g_type_register_static (GTK_TYPE_WIDGET_ACCESSIBLE,
+ "OOoAtkObj",
+ &typeInfo, GTypeFlags(0)) ;
+ }
+ return type;
+}
+
+static bool
+isOfType( uno::XInterface *pInterface, const uno::Type & rType )
+{
+ g_return_val_if_fail( pInterface != nullptr, false );
+
+ bool bIs = false;
+ try {
+ uno::Any aRet = pInterface->queryInterface( rType );
+
+ bIs = ( ( typelib_TypeClass_INTERFACE == aRet.pType->eTypeClass ) &&
+ ( aRet.pReserved != nullptr ) );
+ } catch( const uno::Exception &) { }
+
+ return bIs;
+}
+
+// Whether AtkTableCell can be supported for the interface.
+// Returns true if the corresponding XAccessible has role TABLE_CELL
+// and an XAccessibleTable as parent.
+static bool isTableCell(uno::XInterface* pInterface)
+{
+ g_return_val_if_fail(pInterface != nullptr, false);
+
+ try {
+ auto aType = cppu::UnoType<accessibility::XAccessible>::get().getTypeLibType();
+ uno::Any aAcc = pInterface->queryInterface(aType);
+
+ css::uno::Reference<css::accessibility::XAccessible> xAcc;
+ aAcc >>= xAcc;
+ if (!xAcc.is())
+ return false;
+
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext = xAcc->getAccessibleContext();
+ if (!xContext.is() || !(xContext->getAccessibleRole() == accessibility::AccessibleRole::TABLE_CELL))
+ return false;
+
+ css::uno::Reference<css::accessibility::XAccessible> xParent = xContext->getAccessibleParent();
+ if (!xParent.is())
+ return false;
+ css::uno::Reference<css::accessibility::XAccessibleContext> xParentContext = xParent->getAccessibleContext();
+ if (!xParentContext.is())
+ return false;
+
+ css::uno::Reference<css::accessibility::XAccessibleTable> xTable(xParentContext, uno::UNO_QUERY);
+ return xTable.is();
+ }
+ catch(const uno::Exception &)
+ {
+ g_warning("Exception in isTableCell()");
+ }
+
+ return false;
+}
+
+extern "C" {
+typedef GType (* GetGIfaceType ) ();
+}
+const struct {
+ const char *name;
+ GInterfaceInitFunc const aInit;
+ GetGIfaceType const aGetGIfaceType;
+ const uno::Type & (*aGetUnoType) ();
+} aTypeTable[] = {
+// re-location heaven:
+ {
+ "Comp", reinterpret_cast<GInterfaceInitFunc>(componentIfaceInit),
+ atk_component_get_type,
+ cppu::UnoType<accessibility::XAccessibleComponent>::get
+ },
+ {
+ "Act", reinterpret_cast<GInterfaceInitFunc>(actionIfaceInit),
+ atk_action_get_type,
+ cppu::UnoType<accessibility::XAccessibleAction>::get
+ },
+ {
+ "Txt", reinterpret_cast<GInterfaceInitFunc>(textIfaceInit),
+ atk_text_get_type,
+ cppu::UnoType<accessibility::XAccessibleText>::get
+ },
+ {
+ "Val", reinterpret_cast<GInterfaceInitFunc>(valueIfaceInit),
+ atk_value_get_type,
+ cppu::UnoType<accessibility::XAccessibleValue>::get
+ },
+ {
+ "Tab", reinterpret_cast<GInterfaceInitFunc>(tableIfaceInit),
+ atk_table_get_type,
+ cppu::UnoType<accessibility::XAccessibleTable>::get
+ },
+ {
+ "Cell", reinterpret_cast<GInterfaceInitFunc>(tablecellIfaceInit),
+ atk_table_cell_get_type,
+ // there is no UNO a11y interface for table cells, so this case is handled separately below
+ nullptr
+ },
+ {
+ "Edt", reinterpret_cast<GInterfaceInitFunc>(editableTextIfaceInit),
+ atk_editable_text_get_type,
+ cppu::UnoType<accessibility::XAccessibleEditableText>::get
+ },
+ {
+ "Img", reinterpret_cast<GInterfaceInitFunc>(imageIfaceInit),
+ atk_image_get_type,
+ cppu::UnoType<accessibility::XAccessibleImage>::get
+ },
+ {
+ "Hyp", reinterpret_cast<GInterfaceInitFunc>(hypertextIfaceInit),
+ atk_hypertext_get_type,
+ cppu::UnoType<accessibility::XAccessibleHypertext>::get
+ },
+ {
+ "Sel", reinterpret_cast<GInterfaceInitFunc>(selectionIfaceInit),
+ atk_selection_get_type,
+ cppu::UnoType<accessibility::XAccessibleSelection>::get
+ }
+ // AtkDocument is a nastily broken interface (so far)
+ // we could impl. get_document_type perhaps though.
+};
+
+const int aTypeTableSize = G_N_ELEMENTS( aTypeTable );
+
+static GType
+ensureTypeFor( uno::XInterface *pAccessible )
+{
+ int i;
+ bool bTypes[ aTypeTableSize ] = { false, };
+ OStringBuffer aTypeNameBuf( "OOoAtkObj" );
+
+ for( i = 0; i < aTypeTableSize; i++ )
+ {
+ if(!g_strcmp0(aTypeTable[i].name, "Cell"))
+ {
+ // there is no UNO interface for table cells, but AtkTableCell can be supported
+ // for table cells via the methods of the parent that is a table
+ if (isTableCell(pAccessible))
+ {
+ aTypeNameBuf.append(aTypeTable[i].name);
+ bTypes[i] = true;
+ }
+ }
+ else if (isOfType( pAccessible, aTypeTable[i].aGetUnoType() ) )
+ {
+ aTypeNameBuf.append(aTypeTable[i].name);
+ bTypes[i] = true;
+ }
+ }
+
+ OString aTypeName = aTypeNameBuf.makeStringAndClear();
+ GType nType = g_type_from_name( aTypeName.getStr() );
+ if( nType == G_TYPE_INVALID )
+ {
+ GTypeInfo aTypeInfo = {
+ sizeof( AtkObjectWrapperClass ),
+ nullptr, nullptr, nullptr, nullptr, nullptr,
+ sizeof( AtkObjectWrapper ),
+ 0, nullptr, nullptr
+ } ;
+ nType = g_type_register_static( ATK_TYPE_OBJECT_WRAPPER,
+ aTypeName.getStr(), &aTypeInfo,
+ GTypeFlags(0) ) ;
+
+ for( int j = 0; j < aTypeTableSize; j++ )
+ if( bTypes[j] )
+ {
+ GInterfaceInfo aIfaceInfo = { nullptr, nullptr, nullptr };
+ aIfaceInfo.interface_init = aTypeTable[j].aInit;
+ g_type_add_interface_static (nType, aTypeTable[j].aGetGIfaceType(),
+ &aIfaceInfo);
+ }
+ }
+ return nType;
+}
+
+AtkObject *
+atk_object_wrapper_ref( const uno::Reference< accessibility::XAccessible > &rxAccessible, bool create )
+{
+ g_return_val_if_fail( bool(rxAccessible), nullptr );
+
+ AtkObject *obj = ooo_wrapper_registry_get(rxAccessible);
+ if( obj )
+ {
+ g_object_ref( obj );
+ return obj;
+ }
+
+ if( create )
+ return atk_object_wrapper_new( rxAccessible );
+
+ return nullptr;
+}
+
+AtkObject *
+atk_object_wrapper_new( const css::uno::Reference< css::accessibility::XAccessible >& rxAccessible,
+ AtkObject* parent, AtkObject* orig )
+{
+ g_return_val_if_fail( bool(rxAccessible), nullptr );
+
+ AtkObjectWrapper *pWrap = nullptr;
+
+ try {
+ uno::Reference< accessibility::XAccessibleContext > xContext(rxAccessible->getAccessibleContext());
+
+ g_return_val_if_fail( bool(xContext), nullptr );
+
+ GType nType = ensureTypeFor( xContext.get() );
+ gpointer obj = g_object_new( nType, nullptr);
+
+ pWrap = ATK_OBJECT_WRAPPER( obj );
+ pWrap->mpAccessible = rxAccessible;
+
+ pWrap->index_of_child_about_to_be_removed = -1;
+ pWrap->child_about_to_be_removed = nullptr;
+
+ pWrap->mpContext = xContext;
+ pWrap->mpOrig = orig;
+
+ AtkObject* atk_obj = ATK_OBJECT(pWrap);
+ atk_obj->role = mapToAtkRole(xContext->getAccessibleRole(), xContext->getAccessibleStateSet());
+ atk_obj->accessible_parent = parent;
+
+ ooo_wrapper_registry_add( rxAccessible, atk_obj );
+
+ if( parent )
+ g_object_ref( atk_obj->accessible_parent );
+ else
+ {
+ /* gail_focus_tracker remembers the focused object at the first
+ * parent in the hierarchy that is a Gtk+ widget, but at the time the
+ * event gets processed (at idle), it may be too late to create the
+ * hierarchy, so doing it now ..
+ */
+ uno::Reference< accessibility::XAccessible > xParent( xContext->getAccessibleParent() );
+
+ if( xParent.is() )
+ atk_obj->accessible_parent = atk_object_wrapper_ref( xParent );
+ }
+
+ // Attach a listener to the UNO object if it's not TRANSIENT
+ sal_Int64 nStateSet( xContext->getAccessibleStateSet() );
+ if( ! (nStateSet & accessibility::AccessibleStateType::TRANSIENT ) )
+ {
+ uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
+ if( xBroadcaster.is() )
+ {
+ uno::Reference<accessibility::XAccessibleEventListener> xListener(new AtkListener(pWrap));
+ xBroadcaster->addAccessibleEventListener(xListener);
+ }
+ else
+ OSL_ASSERT( false );
+ }
+
+ static auto func = reinterpret_cast<void(*)(AtkObject*, const gchar*)>(dlsym(nullptr, "atk_object_set_accessible_id"));
+ if (func)
+ {
+ css::uno::Reference<css::accessibility::XAccessibleContext2> xContext2(xContext, css::uno::UNO_QUERY);
+ if( xContext2.is() )
+ {
+ OString aId = OUStringToOString( xContext2->getAccessibleId(), RTL_TEXTENCODING_UTF8);
+ (*func)(atk_obj, aId.getStr());
+ }
+ }
+
+ // tdf#141197 if we have a sysobj child then include that in the hierarchy
+ if (UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper())
+ {
+ css::uno::Reference<css::awt::XWindow> xAWTWindow(rxAccessible, css::uno::UNO_QUERY);
+ VclPtr<vcl::Window> xWindow = pWrapper->GetWindow(xAWTWindow);
+ if (xWindow && xWindow->GetType() == WindowType::SYSTEMCHILDWINDOW)
+ {
+ const SystemEnvData* pEnvData = static_cast<SystemChildWindow*>(xWindow.get())->GetSystemData();
+ if (GtkWidget *pSysObj = pEnvData ? static_cast<GtkWidget*>(pEnvData->pWidget) : nullptr)
+ pWrap->mpSysObjChild = gtk_widget_get_accessible(pSysObj);
+ }
+ }
+
+ return ATK_OBJECT( pWrap );
+ }
+ catch (const uno::Exception &)
+ {
+ if( pWrap )
+ g_object_unref( pWrap );
+
+ return nullptr;
+ }
+}
+
+/*****************************************************************************/
+
+void atk_object_wrapper_add_child(AtkObjectWrapper* wrapper, AtkObject *child, gint index)
+{
+ AtkObject *atk_obj = ATK_OBJECT( wrapper );
+
+ atk_object_set_parent( child, atk_obj );
+ g_signal_emit_by_name( atk_obj, "children_changed::add", index, child, nullptr );
+}
+
+/*****************************************************************************/
+
+void atk_object_wrapper_remove_child(AtkObjectWrapper* wrapper, AtkObject *child, gint index)
+{
+ /*
+ * the atk-bridge GTK+ module gets back to the event source to ref the child just
+ * vanishing, so we keep this reference because the semantic on OOo side is different.
+ */
+ wrapper->child_about_to_be_removed = child;
+ wrapper->index_of_child_about_to_be_removed = index;
+
+ g_signal_emit_by_name( ATK_OBJECT( wrapper ), "children_changed::remove", index, child, nullptr );
+
+ wrapper->index_of_child_about_to_be_removed = -1;
+ wrapper->child_about_to_be_removed = nullptr;
+}
+
+/*****************************************************************************/
+
+void atk_object_wrapper_set_role(AtkObjectWrapper* wrapper, sal_Int16 role, sal_Int64 nStates)
+{
+ AtkObject *atk_obj = ATK_OBJECT( wrapper );
+ atk_object_set_role(atk_obj, mapToAtkRole(role, nStates));
+}
+
+/*****************************************************************************/
+
+void atk_object_wrapper_dispose(AtkObjectWrapper* wrapper)
+{
+ wrapper->mpContext.clear();
+ wrapper->mpAction.clear();
+ wrapper->mpComponent.clear();
+ wrapper->mpEditableText.clear();
+ wrapper->mpHypertext.clear();
+ wrapper->mpImage.clear();
+ wrapper->mpSelection.clear();
+ wrapper->mpMultiLineText.clear();
+ wrapper->mpTable.clear();
+ wrapper->mpTableSelection.clear();
+ wrapper->mpText.clear();
+ wrapper->mpTextMarkup.clear();
+ wrapper->mpTextAttributes.clear();
+ wrapper->mpValue.clear();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkwrapper.hxx b/vcl/unx/gtk3/a11y/atkwrapper.hxx
new file mode 100644
index 0000000000..8d1aeb359b
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkwrapper.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 <sal/config.h>
+
+#include <string_view>
+
+#include <atk/atk.h>
+#include <gtk/gtk.h>
+#include <gtk/gtk-a11y.h>
+#include <com/sun/star/accessibility/XAccessible.hpp>
+
+extern "C" {
+
+namespace com::sun::star::accessibility {
+ class XAccessibleAction;
+ class XAccessibleComponent;
+ class XAccessibleEditableText;
+ class XAccessibleHypertext;
+ class XAccessibleImage;
+ class XAccessibleMultiLineText;
+ class XAccessibleSelection;
+ class XAccessibleTable;
+ class XAccessibleTableSelection;
+ class XAccessibleText;
+ class XAccessibleTextMarkup;
+ class XAccessibleTextAttributes;
+ class XAccessibleValue;
+}
+
+struct AtkObjectWrapper
+{
+ GtkWidgetAccessible aParent;
+
+ AtkObject* mpOrig; //if we're a GtkDrawingArea acting as a custom LibreOffice widget, this is the toolkit default impl
+ AtkObject* mpSysObjChild; //if we're a container for a sysobj, then this is the sysobj native gtk AtkObject
+
+ css::uno::Reference<css::accessibility::XAccessible> mpAccessible;
+ css::uno::Reference<css::accessibility::XAccessibleContext> mpContext;
+ css::uno::Reference<css::accessibility::XAccessibleAction> mpAction;
+ css::uno::Reference<css::accessibility::XAccessibleComponent> mpComponent;
+ css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ mpEditableText;
+ css::uno::Reference<css::accessibility::XAccessibleHypertext> mpHypertext;
+ css::uno::Reference<css::accessibility::XAccessibleImage> mpImage;
+ css::uno::Reference<css::accessibility::XAccessibleMultiLineText>
+ mpMultiLineText;
+ css::uno::Reference<css::accessibility::XAccessibleSelection> mpSelection;
+ css::uno::Reference<css::accessibility::XAccessibleTable> mpTable;
+ css::uno::Reference<css::accessibility::XAccessibleTableSelection> mpTableSelection;
+ css::uno::Reference<css::accessibility::XAccessibleText> mpText;
+ css::uno::Reference<css::accessibility::XAccessibleTextMarkup> mpTextMarkup;
+ css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
+ mpTextAttributes;
+ css::uno::Reference<css::accessibility::XAccessibleValue> mpValue;
+
+ AtkObject *child_about_to_be_removed;
+ gint index_of_child_about_to_be_removed;
+// OString * m_pKeyBindings
+};
+
+struct AtkObjectWrapperClass
+{
+ GtkWidgetAccessibleClass aParentClass;
+};
+
+GType atk_object_wrapper_get_type() G_GNUC_CONST;
+AtkObject * atk_object_wrapper_ref(
+ const css::uno::Reference< css::accessibility::XAccessible >& rxAccessible,
+ bool create = true );
+
+AtkObject * atk_object_wrapper_new(
+ const css::uno::Reference< css::accessibility::XAccessible >& rxAccessible,
+ AtkObject* parent = nullptr, AtkObject* orig = nullptr );
+
+void atk_object_wrapper_add_child(AtkObjectWrapper* wrapper, AtkObject *child, gint index);
+void atk_object_wrapper_remove_child(AtkObjectWrapper* wrapper, AtkObject *child, gint index);
+void atk_object_wrapper_set_role(AtkObjectWrapper* wrapper, sal_Int16 role, sal_Int64 nStates);
+
+void atk_object_wrapper_dispose(AtkObjectWrapper* wrapper);
+
+AtkStateType mapAtkState( sal_Int64 nState );
+
+AtkRelation* atk_object_wrapper_relation_new(const css::accessibility::AccessibleRelation& rRelation);
+
+void actionIfaceInit(AtkActionIface *iface);
+void componentIfaceInit(AtkComponentIface *iface);
+void editableTextIfaceInit(AtkEditableTextIface *iface);
+void hypertextIfaceInit(AtkHypertextIface *iface);
+void imageIfaceInit(AtkImageIface *iface);
+void selectionIfaceInit(AtkSelectionIface *iface);
+void tableIfaceInit(AtkTableIface *iface);
+void tablecellIfaceInit(AtkTableCellIface *iface);
+void textIfaceInit(AtkTextIface *iface);
+void valueIfaceInit(AtkValueIface *iface);
+
+} // extern "C"
+
+#define ATK_TYPE_OBJECT_WRAPPER atk_object_wrapper_get_type()
+#define ATK_OBJECT_WRAPPER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), ATK_TYPE_OBJECT_WRAPPER, AtkObjectWrapper))
+#define ATK_IS_OBJECT_WRAPPER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ATK_TYPE_OBJECT_WRAPPER))
+
+static inline gchar *
+OUStringToGChar(std::u16string_view rString )
+{
+ OString aUtf8 = OUStringToOString( rString, RTL_TEXTENCODING_UTF8 );
+ return g_strdup( aUtf8.getStr() );
+}
+
+#define OUStringToConstGChar( string ) OUStringToOString( string, RTL_TEXTENCODING_UTF8 ).getStr()
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/customcellrenderer.cxx b/vcl/unx/gtk3/customcellrenderer.cxx
new file mode 100644
index 0000000000..a6559f99ce
--- /dev/null
+++ b/vcl/unx/gtk3/customcellrenderer.cxx
@@ -0,0 +1,316 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/svapp.hxx>
+#include "customcellrenderer.hxx"
+#if !GTK_CHECK_VERSION(4, 0, 0)
+#include <gtk/gtk-a11y.h>
+#endif
+
+namespace
+{
+struct _CustomCellRendererClass : public GtkCellRendererTextClass
+{
+};
+
+enum
+{
+ PROP_ID = 10000,
+ PROP_INSTANCE_TREE_VIEW = 10001
+};
+}
+
+#if defined __clang__ && GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 68
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-volatile"
+#endif
+G_DEFINE_TYPE(CustomCellRenderer, custom_cell_renderer, GTK_TYPE_CELL_RENDERER_TEXT)
+#if defined __clang__ && GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 68
+#pragma clang diagnostic pop
+#endif
+
+static void custom_cell_renderer_init(CustomCellRenderer* self)
+{
+ {
+ SolarMutexGuard aGuard;
+ new (&self->device) VclPtr<VirtualDevice>;
+ }
+
+ // prevent loplugin:unreffun firing on macro generated function
+ (void)custom_cell_renderer_get_instance_private(self);
+}
+
+static void custom_cell_renderer_get_property(GObject* object, guint param_id, GValue* value,
+ GParamSpec* pspec)
+{
+ CustomCellRenderer* cellsurface = CUSTOM_CELL_RENDERER(object);
+
+ switch (param_id)
+ {
+ case PROP_ID:
+ g_value_set_string(value, cellsurface->id);
+ break;
+ case PROP_INSTANCE_TREE_VIEW:
+ g_value_set_pointer(value, cellsurface->instance);
+ break;
+ default:
+ G_OBJECT_CLASS(custom_cell_renderer_parent_class)
+ ->get_property(object, param_id, value, pspec);
+ break;
+ }
+}
+
+static void custom_cell_renderer_set_property(GObject* object, guint param_id, const GValue* value,
+ GParamSpec* pspec)
+{
+ CustomCellRenderer* cellsurface = CUSTOM_CELL_RENDERER(object);
+
+ switch (param_id)
+ {
+ case PROP_ID:
+ g_free(cellsurface->id);
+ cellsurface->id = g_value_dup_string(value);
+ break;
+ case PROP_INSTANCE_TREE_VIEW:
+ cellsurface->instance = g_value_get_pointer(value);
+ break;
+ default:
+ G_OBJECT_CLASS(custom_cell_renderer_parent_class)
+ ->set_property(object, param_id, value, pspec);
+ break;
+ }
+}
+
+static bool custom_cell_renderer_get_preferred_size(GtkCellRenderer* cell,
+ GtkOrientation orientation, gint* minimum_size,
+ gint* natural_size);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+static void custom_cell_renderer_snapshot(GtkCellRenderer* cell, GtkSnapshot* snapshot,
+ GtkWidget* widget, const GdkRectangle* background_area,
+ const GdkRectangle* cell_area,
+ GtkCellRendererState flags);
+#endif
+
+static void custom_cell_renderer_render(GtkCellRenderer* cell, cairo_t* cr, GtkWidget* widget,
+ const GdkRectangle* background_area,
+ const GdkRectangle* cell_area, GtkCellRendererState flags);
+
+static void custom_cell_renderer_finalize(GObject* object)
+{
+ CustomCellRenderer* cellsurface = CUSTOM_CELL_RENDERER(object);
+
+ g_free(cellsurface->id);
+
+ {
+ SolarMutexGuard aGuard;
+ cellsurface->device.disposeAndClear();
+ cellsurface->device.~VclPtr<VirtualDevice>();
+ }
+
+ G_OBJECT_CLASS(custom_cell_renderer_parent_class)->finalize(object);
+}
+
+static void custom_cell_renderer_get_preferred_width(GtkCellRenderer* cell, GtkWidget* widget,
+ gint* minimum_size, gint* natural_size)
+{
+ if (!custom_cell_renderer_get_preferred_size(cell, GTK_ORIENTATION_HORIZONTAL, minimum_size,
+ natural_size))
+ {
+ // fallback to parent if we're empty
+ GTK_CELL_RENDERER_CLASS(custom_cell_renderer_parent_class)
+ ->get_preferred_width(cell, widget, minimum_size, natural_size);
+ }
+}
+
+static void custom_cell_renderer_get_preferred_height(GtkCellRenderer* cell, GtkWidget* widget,
+ gint* minimum_size, gint* natural_size)
+{
+ if (!custom_cell_renderer_get_preferred_size(cell, GTK_ORIENTATION_VERTICAL, minimum_size,
+ natural_size))
+ {
+ // fallback to parent if we're empty
+ GTK_CELL_RENDERER_CLASS(custom_cell_renderer_parent_class)
+ ->get_preferred_height(cell, widget, minimum_size, natural_size);
+ }
+}
+
+static void custom_cell_renderer_get_preferred_height_for_width(GtkCellRenderer* cell,
+ GtkWidget* widget, gint /*width*/,
+ gint* minimum_height,
+ gint* natural_height)
+{
+ gtk_cell_renderer_get_preferred_height(cell, widget, minimum_height, natural_height);
+}
+
+static void custom_cell_renderer_get_preferred_width_for_height(GtkCellRenderer* cell,
+ GtkWidget* widget, gint /*height*/,
+ gint* minimum_width,
+ gint* natural_width)
+{
+ gtk_cell_renderer_get_preferred_width(cell, widget, minimum_width, natural_width);
+}
+
+void custom_cell_renderer_class_init(CustomCellRendererClass* klass)
+{
+ GtkCellRendererClass* cell_class = GTK_CELL_RENDERER_CLASS(klass);
+ GObjectClass* object_class = G_OBJECT_CLASS(klass);
+
+ /* Hook up functions to set and get our custom cell renderer properties */
+ object_class->get_property = custom_cell_renderer_get_property;
+ object_class->set_property = custom_cell_renderer_set_property;
+
+ custom_cell_renderer_parent_class = g_type_class_peek_parent(klass);
+ object_class->finalize = custom_cell_renderer_finalize;
+
+ cell_class->get_preferred_width = custom_cell_renderer_get_preferred_width;
+ cell_class->get_preferred_height = custom_cell_renderer_get_preferred_height;
+ cell_class->get_preferred_width_for_height
+ = custom_cell_renderer_get_preferred_width_for_height;
+ cell_class->get_preferred_height_for_width
+ = custom_cell_renderer_get_preferred_height_for_width;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ cell_class->snapshot = custom_cell_renderer_snapshot;
+#else
+ cell_class->render = custom_cell_renderer_render;
+#endif
+
+ g_object_class_install_property(
+ object_class, PROP_ID,
+ g_param_spec_string("id", "ID", "The ID of the custom data", nullptr, G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ object_class, PROP_INSTANCE_TREE_VIEW,
+ g_param_spec_pointer("instance", "Instance", "The GtkInstanceTreeView", G_PARAM_READWRITE));
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_cell_renderer_class_set_accessible_type(cell_class, GTK_TYPE_TEXT_CELL_ACCESSIBLE);
+#endif
+}
+
+GtkCellRenderer* custom_cell_renderer_new()
+{
+ return GTK_CELL_RENDERER(g_object_new(CUSTOM_TYPE_CELL_RENDERER, nullptr));
+}
+
+bool custom_cell_renderer_get_preferred_size(GtkCellRenderer* cell, GtkOrientation orientation,
+ gint* minimum_size, gint* natural_size)
+{
+ GValue value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_STRING);
+ g_object_get_property(G_OBJECT(cell), "id", &value);
+
+ const char* pStr = g_value_get_string(&value);
+
+ OUString sId(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+
+ value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_POINTER);
+ g_object_get_property(G_OBJECT(cell), "instance", &value);
+
+ CustomCellRenderer* cellsurface = CUSTOM_CELL_RENDERER(cell);
+
+ Size aSize;
+
+ gpointer pWidget = g_value_get_pointer(&value);
+ if (pWidget)
+ {
+ SolarMutexGuard aGuard;
+ custom_cell_renderer_ensure_device(cellsurface, pWidget);
+ aSize = custom_cell_renderer_get_size(*cellsurface->device, sId, pWidget);
+ }
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (minimum_size)
+ *minimum_size = aSize.Width();
+
+ if (natural_size)
+ *natural_size = aSize.Width();
+ }
+ else
+ {
+ if (minimum_size)
+ *minimum_size = aSize.Height();
+
+ if (natural_size)
+ *natural_size = aSize.Height();
+ }
+
+ return true;
+}
+
+void custom_cell_renderer_render(GtkCellRenderer* cell, cairo_t* cr, GtkWidget* /*widget*/,
+ const GdkRectangle* /*background_area*/,
+ const GdkRectangle* cell_area, GtkCellRendererState flags)
+{
+ GValue value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_STRING);
+ g_object_get_property(G_OBJECT(cell), "id", &value);
+
+ const char* pStr = g_value_get_string(&value);
+ OUString sId(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+
+ value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_POINTER);
+ g_object_get_property(G_OBJECT(cell), "instance", &value);
+
+ CustomCellRenderer* cellsurface = CUSTOM_CELL_RENDERER(cell);
+
+ gpointer pWidget = g_value_get_pointer(&value);
+ if (!pWidget)
+ return;
+
+ SolarMutexGuard aGuard;
+
+ custom_cell_renderer_ensure_device(cellsurface, pWidget);
+
+ Size aSize(cell_area->width, cell_area->height);
+ // false to not bother setting the bg on resize as we'll do that
+ // ourself via cairo
+ cellsurface->device->SetOutputSizePixel(aSize, false);
+
+ cairo_surface_t* pSurface = get_underlying_cairo_surface(*cellsurface->device);
+
+ // fill surface as transparent so it can be blended with the potentially
+ // selected background
+ cairo_t* tempcr = cairo_create(pSurface);
+ cairo_set_source_rgba(tempcr, 0, 0, 0, 0);
+ cairo_set_operator(tempcr, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(tempcr);
+ cairo_destroy(tempcr);
+ cairo_surface_flush(pSurface);
+
+ custom_cell_renderer_render(*cellsurface->device, tools::Rectangle(Point(0, 0), aSize),
+ static_cast<bool>(flags & GTK_CELL_RENDERER_SELECTED), sId,
+ pWidget);
+
+ cairo_surface_mark_dirty(pSurface);
+
+ cairo_set_source_surface(cr, pSurface, cell_area->x, cell_area->y);
+ cairo_paint(cr);
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+static void custom_cell_renderer_snapshot(GtkCellRenderer* cell, GtkSnapshot* snapshot,
+ GtkWidget* widget, const GdkRectangle* background_area,
+ const GdkRectangle* cell_area, GtkCellRendererState flags)
+{
+ graphene_rect_t rect = GRAPHENE_RECT_INIT(
+ static_cast<float>(cell_area->x), static_cast<float>(cell_area->y),
+ static_cast<float>(cell_area->width), static_cast<float>(cell_area->height));
+ cairo_t* cr = gtk_snapshot_append_cairo(GTK_SNAPSHOT(snapshot), &rect);
+ custom_cell_renderer_render(cell, cr, widget, background_area, cell_area, flags);
+ cairo_destroy(cr);
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/customcellrenderer.hxx b/vcl/unx/gtk3/customcellrenderer.hxx
new file mode 100644
index 0000000000..a4d847e7c2
--- /dev/null
+++ b/vcl/unx/gtk3/customcellrenderer.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/.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <vcl/virdev.hxx>
+
+G_BEGIN_DECLS
+
+struct _CustomCellRenderer
+{
+ GtkCellRendererText parent;
+ VclPtr<VirtualDevice> device;
+ gchar* id;
+ gpointer instance;
+};
+
+/*
+ Provide a mechanism to support custom rendering of cells in a GtkTreeView/GtkComboBox
+*/
+
+G_DECLARE_FINAL_TYPE(CustomCellRenderer, custom_cell_renderer, CUSTOM, CELL_RENDERER,
+ GtkCellRendererText)
+
+#define CUSTOM_TYPE_CELL_RENDERER (custom_cell_renderer_get_type())
+
+#define CUSTOM_CELL_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), CUSTOM_TYPE_CELL_RENDERER, CustomCellRenderer))
+
+#define CUSTOM_IS_CELL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), CUSTOM_TYPE_CELL_RENDERER))
+
+GtkCellRenderer* custom_cell_renderer_new();
+
+G_END_DECLS
+
+void custom_cell_renderer_ensure_device(CustomCellRenderer* cellsurface, gpointer user_data);
+Size custom_cell_renderer_get_size(VirtualDevice& rDevice, const OUString& rCellId,
+ gpointer user_data);
+void custom_cell_renderer_render(VirtualDevice& rDevice, const tools::Rectangle& rRect,
+ bool bSelected, const OUString& rId, gpointer user_data);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx
new file mode 100644
index 0000000000..4b93d03617
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx
@@ -0,0 +1,2091 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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_gio.h>
+
+#include <com/sun/star/awt/SystemDependentXWindow.hpp>
+#include <com/sun/star/awt/Toolkit.hpp>
+#include <com/sun/star/awt/XSystemDependentWindowPeer.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/SystemDependent.hpp>
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp>
+#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
+#include <osl/diagnose.h>
+#include <rtl/process.h>
+#include <sal/log.hxx>
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+#include <com/sun/star/ui/dialogs/ControlActions.hpp>
+#include <com/sun/star/uno/Any.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+
+#include <utility>
+#include <vcl/svapp.hxx>
+
+#include <o3tl/string_view.hxx>
+#include <tools/urlobj.hxx>
+#include <unotools/ucbhelper.hxx>
+
+#include <algorithm>
+#include <set>
+#include <string.h>
+#include <string_view>
+
+#include "SalGtkFilePicker.hxx"
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::ui::dialogs::TemplateDescription;
+using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds;
+using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::uno;
+
+struct FilterEntry
+{
+protected:
+ OUString m_sTitle;
+ OUString m_sFilter;
+
+ css::uno::Sequence< css::beans::StringPair > m_aSubFilters;
+
+public:
+ FilterEntry( OUString _aTitle, OUString _aFilter )
+ :m_sTitle(std::move( _aTitle ))
+ ,m_sFilter(std::move( _aFilter ))
+ {
+ }
+
+ const OUString& getTitle() const { return m_sTitle; }
+ const OUString& getFilter() const { return m_sFilter; }
+
+ /// determines if the filter has sub filter (i.e., the filter is a filter group in real)
+ bool hasSubFilters( ) const;
+
+ /** retrieves the filters belonging to the entry
+ */
+ void getSubFilters( css::uno::Sequence< css::beans::StringPair >& _rSubFilterList );
+
+ // helpers for iterating the sub filters
+ const css::beans::StringPair* beginSubFilters() const { return m_aSubFilters.begin(); }
+ const css::beans::StringPair* endSubFilters() const { return m_aSubFilters.end(); }
+};
+
+bool FilterEntry::hasSubFilters() const
+{
+ return m_aSubFilters.hasElements();
+}
+
+void FilterEntry::getSubFilters( css::uno::Sequence< css::beans::StringPair >& _rSubFilterList )
+{
+ _rSubFilterList = m_aSubFilters;
+}
+
+void SalGtkFilePicker::dialog_mapped_cb(GtkWidget *, SalGtkFilePicker *pobjFP)
+{
+ pobjFP->InitialMapping();
+}
+
+void SalGtkFilePicker::InitialMapping()
+{
+ if (!mbPreviewState )
+ {
+ gtk_widget_hide( m_pPreview );
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_file_chooser_set_preview_widget_active( GTK_FILE_CHOOSER( m_pDialog ), false);
+#endif
+ }
+ gtk_widget_set_size_request (m_pPreview, -1, -1);
+}
+
+SalGtkFilePicker::SalGtkFilePicker( const uno::Reference< uno::XComponentContext >& xContext ) :
+ SalGtkPicker( xContext ),
+ SalGtkFilePicker_Base( m_rbHelperMtx ),
+ m_pVBox ( nullptr ),
+ mnHID_FolderChange( 0 ),
+ mnHID_SelectionChange( 0 ),
+ bVersionWidthUnset( false ),
+ mbPreviewState( false ),
+ mbInitialized(false),
+ mHID_Preview( 0 ),
+ m_pPreview( nullptr ),
+ m_pPseudoFilter( nullptr )
+{
+ int i;
+
+ for( i = 0; i < TOGGLE_LAST; i++ )
+ {
+ m_pToggles[i] = nullptr;
+ mbToggleVisibility[i] = false;
+ }
+
+ for( i = 0; i < BUTTON_LAST; i++ )
+ {
+ m_pButtons[i] = nullptr;
+ mbButtonVisibility[i] = false;
+ }
+
+ for( i = 0; i < LIST_LAST; i++ )
+ {
+ m_pHBoxs[i] = nullptr;
+ m_pLists[i] = nullptr;
+ m_pListLabels[i] = nullptr;
+ mbListVisibility[i] = false;
+ }
+
+ OUString aFilePickerTitle = getResString( FILE_PICKER_TITLE_OPEN );
+
+ m_pDialog = GTK_WIDGET(g_object_new(GTK_TYPE_FILE_CHOOSER_DIALOG,
+ "title", OUStringToOString(aFilePickerTitle, RTL_TEXTENCODING_UTF8).getStr(),
+ "action", GTK_FILE_CHOOSER_ACTION_OPEN,
+ nullptr));
+ gtk_window_set_modal(GTK_WINDOW(m_pDialog), true);
+ gtk_dialog_set_default_response( GTK_DIALOG (m_pDialog), GTK_RESPONSE_ACCEPT );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+#if ENABLE_GIO
+ gtk_file_chooser_set_local_only( GTK_FILE_CHOOSER( m_pDialog ), false );
+#endif
+#endif
+
+ gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER( m_pDialog ), false );
+
+ m_pVBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+
+ // We don't want clickable items to have a huge hit-area
+ GtkWidget *pHBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ GtkWidget *pThinVBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_end (GTK_BOX( m_pVBox ), pHBox, false, false, 0);
+ gtk_box_pack_start (GTK_BOX( pHBox ), pThinVBox, false, false, 0);
+#else
+ gtk_box_append(GTK_BOX(m_pVBox), pHBox);
+ gtk_box_prepend(GTK_BOX(m_pVBox), pThinVBox);
+#endif
+ gtk_widget_show( pHBox );
+ gtk_widget_show( pThinVBox );
+
+ OUString aLabel;
+
+ for( i = 0; i < TOGGLE_LAST; i++ )
+ {
+ m_pToggles[i] = gtk_check_button_new();
+
+#define LABEL_TOGGLE( elem ) \
+ case elem : \
+ aLabel = getResString( CHECKBOX_##elem ); \
+ setLabel( CHECKBOX_##elem, aLabel ); \
+ break
+
+ switch( i ) {
+ LABEL_TOGGLE( AUTOEXTENSION );
+ LABEL_TOGGLE( PASSWORD );
+ LABEL_TOGGLE( GPGENCRYPTION );
+ LABEL_TOGGLE( FILTEROPTIONS );
+ LABEL_TOGGLE( READONLY );
+ LABEL_TOGGLE( LINK );
+ LABEL_TOGGLE( PREVIEW );
+ LABEL_TOGGLE( SELECTION );
+ default:
+ SAL_WARN( "vcl.gtk", "Handle unknown control " << i);
+ break;
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_end( GTK_BOX( pThinVBox ), m_pToggles[i], false, false, 0 );
+#else
+ gtk_box_append(GTK_BOX(pThinVBox), m_pToggles[i]);
+#endif
+ }
+
+ for( i = 0; i < LIST_LAST; i++ )
+ {
+ m_pHBoxs[i] = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+
+ GtkListStore *pListStores[ LIST_LAST ];
+ pListStores[i] = gtk_list_store_new (1, G_TYPE_STRING);
+ m_pLists[i] = gtk_combo_box_new_with_model(GTK_TREE_MODEL(pListStores[i]));
+ g_object_unref (pListStores[i]); // owned by the widget.
+ GtkCellRenderer *pCell = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start(
+ GTK_CELL_LAYOUT(m_pLists[i]), pCell, true);
+ gtk_cell_layout_set_attributes(
+ GTK_CELL_LAYOUT (m_pLists[i]), pCell, "text", 0, nullptr);
+
+ m_pListLabels[i] = gtk_label_new( "" );
+
+#define LABEL_LIST( elem ) \
+ case elem : \
+ aLabel = getResString( LISTBOX_##elem##_LABEL ); \
+ setLabel( LISTBOX_##elem##_LABEL, aLabel ); \
+ break
+
+ switch( i )
+ {
+ LABEL_LIST( VERSION );
+ LABEL_LIST( TEMPLATE );
+ LABEL_LIST( IMAGE_TEMPLATE );
+ LABEL_LIST( IMAGE_ANCHOR );
+ default:
+ SAL_WARN( "vcl.gtk", "Handle unknown control " << i);
+ break;
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_end( GTK_BOX( m_pHBoxs[i] ), m_pLists[i], false, false, 0 );
+ gtk_box_pack_end( GTK_BOX( m_pHBoxs[i] ), m_pListLabels[i], false, false, 0 );
+#else
+ gtk_box_append(GTK_BOX(m_pHBoxs[i]), m_pLists[i]);
+ gtk_box_append(GTK_BOX(m_pHBoxs[i]), m_pListLabels[i]);
+#endif
+ gtk_label_set_mnemonic_widget( GTK_LABEL(m_pListLabels[i]), m_pLists[i] );
+ gtk_box_set_spacing( GTK_BOX( m_pHBoxs[i] ), 12 );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_end( GTK_BOX( m_pVBox ), m_pHBoxs[i], false, false, 0 );
+#else
+ gtk_box_append(GTK_BOX(m_pVBox), m_pHBoxs[i]);
+#endif
+ }
+
+ aLabel = getResString( FILE_PICKER_FILE_TYPE );
+ m_pFilterExpander = gtk_expander_new_with_mnemonic(
+ OUStringToOString( aLabel, RTL_TEXTENCODING_UTF8 ).getStr());
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_end( GTK_BOX( m_pVBox ), m_pFilterExpander, false, true, 0 );
+#else
+ gtk_box_append(GTK_BOX(m_pVBox), m_pFilterExpander);
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget *scrolled_window = gtk_scrolled_window_new (nullptr, nullptr);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_SHADOW_IN);
+#else
+ GtkWidget *scrolled_window = gtk_scrolled_window_new();
+ gtk_scrolled_window_set_has_frame(GTK_SCROLLED_WINDOW(scrolled_window), true);
+#endif
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add (GTK_CONTAINER (m_pFilterExpander), scrolled_window);
+#else
+ gtk_expander_set_child(GTK_EXPANDER(m_pFilterExpander), scrolled_window);
+#endif
+ gtk_widget_show (scrolled_window);
+
+ m_pFilterStore = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_STRING,
+ G_TYPE_STRING, G_TYPE_STRING);
+ m_pFilterView = gtk_tree_view_new_with_model (GTK_TREE_MODEL(m_pFilterStore));
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(m_pFilterView), false);
+
+ GtkCellRenderer *cell = nullptr;
+
+ for (i = 0; i < 2; ++i)
+ {
+ GtkTreeViewColumn *column = gtk_tree_view_column_new ();
+ cell = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_set_expand (column, true);
+ gtk_tree_view_column_pack_start (column, cell, false);
+ gtk_tree_view_column_set_attributes (column, cell, "text", i, nullptr);
+ gtk_tree_view_append_column (GTK_TREE_VIEW(m_pFilterView), column);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add (GTK_CONTAINER (scrolled_window), m_pFilterView);
+#else
+ gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window), m_pFilterView);
+#endif
+ gtk_widget_show (m_pFilterView);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_file_chooser_set_extra_widget( GTK_FILE_CHOOSER( m_pDialog ), m_pVBox );
+#endif
+
+ m_pPreview = gtk_image_new();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_file_chooser_set_preview_widget( GTK_FILE_CHOOSER( m_pDialog ), m_pPreview );
+#endif
+
+ g_signal_connect( G_OBJECT( m_pToggles[PREVIEW] ), "toggled",
+ G_CALLBACK( preview_toggled_cb ), this );
+ g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW(m_pFilterView)), "changed",
+ G_CALLBACK ( type_changed_cb ), this);
+ g_signal_connect( G_OBJECT( m_pDialog ), "notify::filter",
+ G_CALLBACK( filter_changed_cb ), this);
+ g_signal_connect( G_OBJECT( m_pFilterExpander ), "activate",
+ G_CALLBACK( expander_changed_cb ), this);
+ g_signal_connect (G_OBJECT( m_pDialog ), "map",
+ G_CALLBACK (dialog_mapped_cb), this);
+
+ gtk_widget_show( m_pVBox );
+
+ PangoLayout *layout = gtk_widget_create_pango_layout (m_pFilterView, nullptr);
+ guint ypad;
+ PangoRectangle row_height;
+ pango_layout_set_markup (layout, "All Files", -1);
+ pango_layout_get_pixel_extents (layout, nullptr, &row_height);
+ g_object_unref (layout);
+
+ g_object_get (cell, "ypad", &ypad, nullptr);
+ guint height = (row_height.height + 2*ypad) * 5;
+ gtk_widget_set_size_request (m_pFilterView, -1, height);
+ gtk_widget_set_size_request (m_pPreview, 1, height);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_file_chooser_set_preview_widget_active( GTK_FILE_CHOOSER( m_pDialog ), true);
+#endif
+}
+
+// XFilePickerNotifier
+
+void SAL_CALL SalGtkFilePicker::addFilePickerListener( const uno::Reference<XFilePickerListener>& xListener )
+{
+ SolarMutexGuard g;
+
+ OSL_ENSURE(!m_xListener.is(),
+ "SalGtkFilePicker only talks with one listener at a time...");
+ m_xListener = xListener;
+}
+
+void SAL_CALL SalGtkFilePicker::removeFilePickerListener( const uno::Reference<XFilePickerListener>& )
+{
+ SolarMutexGuard g;
+
+ m_xListener.clear();
+}
+
+// FilePicker Event functions
+
+void SalGtkFilePicker::impl_fileSelectionChanged( const FilePickerEvent& aEvent )
+{
+ if (m_xListener.is()) m_xListener->fileSelectionChanged( aEvent );
+}
+
+void SalGtkFilePicker::impl_directoryChanged( const FilePickerEvent& aEvent )
+{
+ if (m_xListener.is()) m_xListener->directoryChanged( aEvent );
+}
+
+void SalGtkFilePicker::impl_controlStateChanged( const FilePickerEvent& aEvent )
+{
+ if (m_xListener.is()) m_xListener->controlStateChanged( aEvent );
+}
+
+static bool
+isFilterString( std::u16string_view rFilterString, const char *pMatch )
+{
+ sal_Int32 nIndex = 0;
+ bool bIsFilter = true;
+
+ OUString aMatch(OUString::createFromAscii(pMatch));
+
+ do
+ {
+ std::u16string_view aToken = o3tl::getToken(rFilterString, 0, ';', nIndex );
+ if( !o3tl::starts_with(aToken, aMatch) )
+ {
+ bIsFilter = false;
+ break;
+ }
+ }
+ while( nIndex >= 0 );
+
+ return bIsFilter;
+}
+
+static OUString
+shrinkFilterName( const OUString &rFilterName, bool bAllowNoStar = false )
+{
+ int i;
+ int nBracketLen = -1;
+ int nBracketEnd = -1;
+ const sal_Unicode *pStr = rFilterName.getStr();
+ OUString aRealName = rFilterName;
+
+ for( i = aRealName.getLength() - 1; i > 0; i-- )
+ {
+ if( pStr[i] == ')' )
+ nBracketEnd = i;
+ else if( pStr[i] == '(' )
+ {
+ nBracketLen = nBracketEnd - i;
+ if( nBracketEnd <= 0 )
+ continue;
+ if( isFilterString( rFilterName.subView( i + 1, nBracketLen - 1 ), "*." ) )
+ aRealName = aRealName.replaceAt( i, nBracketLen + 1, u"" );
+ else if (bAllowNoStar)
+ {
+ if( isFilterString( rFilterName.subView( i + 1, nBracketLen - 1 ), ".") )
+ aRealName = aRealName.replaceAt( i, nBracketLen + 1, u"" );
+ }
+ }
+ }
+
+ return aRealName;
+}
+
+namespace {
+
+ struct FilterTitleMatch
+ {
+ protected:
+ const OUString& rTitle;
+
+ public:
+ explicit FilterTitleMatch( const OUString& _rTitle ) : rTitle( _rTitle ) { }
+
+ bool operator () ( const FilterEntry& _rEntry )
+ {
+ bool bMatch;
+ if( !_rEntry.hasSubFilters() )
+ // a real filter
+ bMatch = (_rEntry.getTitle() == rTitle)
+ || (shrinkFilterName(_rEntry.getTitle()) == rTitle);
+ else
+ // a filter group -> search the sub filters
+ bMatch =
+ ::std::any_of(
+ _rEntry.beginSubFilters(),
+ _rEntry.endSubFilters(),
+ *this
+ );
+
+ return bMatch;
+ }
+ bool operator () ( const css::beans::StringPair& _rEntry )
+ {
+ OUString aShrunkName = shrinkFilterName( _rEntry.First );
+ return aShrunkName == rTitle;
+ }
+ };
+}
+
+bool SalGtkFilePicker::FilterNameExists( const OUString& rTitle )
+{
+ bool bRet = false;
+
+ if( m_pFilterVector )
+ bRet =
+ ::std::any_of(
+ m_pFilterVector->begin(),
+ m_pFilterVector->end(),
+ FilterTitleMatch( rTitle )
+ );
+
+ return bRet;
+}
+
+bool SalGtkFilePicker::FilterNameExists( const css::uno::Sequence< css::beans::StringPair >& _rGroupedFilters )
+{
+ bool bRet = false;
+
+ if( m_pFilterVector )
+ {
+ bRet = std::any_of(_rGroupedFilters.begin(), _rGroupedFilters.end(),
+ [&](const css::beans::StringPair& rFilter) {
+ return ::std::any_of( m_pFilterVector->begin(), m_pFilterVector->end(), FilterTitleMatch( rFilter.First ) ); });
+ }
+
+ return bRet;
+}
+
+void SalGtkFilePicker::ensureFilterVector( const OUString& _rInitialCurrentFilter )
+{
+ if( !m_pFilterVector )
+ {
+ m_pFilterVector.reset( new std::vector<FilterEntry> );
+
+ // set the first filter to the current filter
+ if ( m_aCurrentFilter.isEmpty() )
+ m_aCurrentFilter = _rInitialCurrentFilter;
+ }
+}
+
+void SAL_CALL SalGtkFilePicker::appendFilter( const OUString& aTitle, const OUString& aFilter )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ if( FilterNameExists( aTitle ) )
+ throw IllegalArgumentException();
+
+ // ensure that we have a filter list
+ ensureFilterVector( aTitle );
+
+ // append the filter
+ m_pFilterVector->insert( m_pFilterVector->end(), FilterEntry( aTitle, aFilter ) );
+}
+
+void SAL_CALL SalGtkFilePicker::setCurrentFilter( const OUString& aTitle )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ if( aTitle != m_aCurrentFilter )
+ {
+ m_aCurrentFilter = aTitle;
+ SetCurFilter( m_aCurrentFilter );
+ }
+
+ // TODO m_pImpl->setCurrentFilter( aTitle );
+}
+
+void SalGtkFilePicker::updateCurrentFilterFromName(const gchar* filtername)
+{
+ OUString aFilterName(filtername, strlen(filtername), RTL_TEXTENCODING_UTF8);
+ if (m_pFilterVector)
+ {
+ for (auto const& filter : *m_pFilterVector)
+ {
+ if (aFilterName == shrinkFilterName(filter.getTitle()))
+ {
+ m_aCurrentFilter = filter.getTitle();
+ break;
+ }
+ }
+ }
+}
+
+void SalGtkFilePicker::UpdateFilterfromUI()
+{
+ // Update the filtername from the users selection if they have had a chance to do so.
+ // If the user explicitly sets a type then use that, if not then take the implicit type
+ // from the filter of the files glob on which he is currently searching
+ if (!mnHID_FolderChange || !mnHID_SelectionChange)
+ return;
+
+ GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_pFilterView));
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ gchar *title;
+ gtk_tree_model_get (model, &iter, 2, &title, -1);
+ updateCurrentFilterFromName(title);
+ g_free (title);
+ }
+ else if( GtkFileFilter *filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(m_pDialog)))
+ {
+ if (m_pPseudoFilter != filter)
+ updateCurrentFilterFromName(gtk_file_filter_get_name( filter ));
+ else
+ updateCurrentFilterFromName(OUStringToOString( m_aInitialFilter, RTL_TEXTENCODING_UTF8 ).getStr());
+ }
+}
+
+OUString SAL_CALL SalGtkFilePicker::getCurrentFilter()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ UpdateFilterfromUI();
+
+ return m_aCurrentFilter;
+}
+
+// XFilterGroupManager functions
+
+void SAL_CALL SalGtkFilePicker::appendFilterGroup( const OUString& /*sGroupTitle*/, const uno::Sequence<beans::StringPair>& aFilters )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO m_pImpl->appendFilterGroup( sGroupTitle, aFilters );
+ // check the names
+ if( FilterNameExists( aFilters ) )
+ // TODO: a more precise exception message
+ throw IllegalArgumentException();
+
+ // ensure that we have a filter list
+ OUString sInitialCurrentFilter;
+ if( aFilters.hasElements() )
+ sInitialCurrentFilter = aFilters[0].First;
+
+ ensureFilterVector( sInitialCurrentFilter );
+
+ // append the filter
+ for( const auto& rSubFilter : aFilters )
+ m_pFilterVector->insert( m_pFilterVector->end(), FilterEntry( rSubFilter.First, rSubFilter.Second ) );
+
+}
+
+// XFilePicker functions
+
+void SAL_CALL SalGtkFilePicker::setMultiSelectionMode( sal_Bool bMode )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(m_pDialog), bMode );
+}
+
+void SAL_CALL SalGtkFilePicker::setDefaultName( const OUString& aName )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ OString aStr = OUStringToOString( aName, RTL_TEXTENCODING_UTF8 );
+ GtkFileChooserAction eAction = gtk_file_chooser_get_action( GTK_FILE_CHOOSER( m_pDialog ) );
+
+ // set_current_name launches a Gtk critical error if called for other than save
+ if( GTK_FILE_CHOOSER_ACTION_SAVE == eAction )
+ gtk_file_chooser_set_current_name( GTK_FILE_CHOOSER( m_pDialog ), aStr.getStr() );
+}
+
+void SAL_CALL SalGtkFilePicker::setDisplayDirectory( const OUString& rDirectory )
+{
+ SolarMutexGuard g;
+
+ implsetDisplayDirectory(rDirectory);
+}
+
+OUString SAL_CALL SalGtkFilePicker::getDisplayDirectory()
+{
+ SolarMutexGuard g;
+
+ return implgetDisplayDirectory();
+}
+
+uno::Sequence<OUString> SAL_CALL SalGtkFilePicker::getFiles()
+{
+ // no member access => no mutex needed
+
+ uno::Sequence< OUString > aFiles = getSelectedFiles();
+ /*
+ The previous multiselection API design was completely broken
+ and unimplementable for some heterogeneous pseudo-URIs eg. search:
+ Thus crop unconditionally to a single selection.
+ */
+ aFiles.realloc (1);
+ return aFiles;
+}
+
+namespace
+{
+
+bool lcl_matchFilter( std::u16string_view rFilter, std::u16string_view rExt )
+{
+ const sal_Unicode cSep {';'};
+ size_t nIdx {0};
+
+ for (;;)
+ {
+ const size_t nBegin = rFilter.find(rExt, nIdx);
+
+ if (nBegin == std::u16string_view::npos) // not found
+ break;
+
+ // Let nIdx point to end of matched string, useful in order to
+ // check string boundaries and also for a possible next iteration
+ nIdx = nBegin + rExt.size();
+
+ // Check if the found occurrence is an exact match: right side
+ if (nIdx < rFilter.size() && rFilter[nIdx]!=cSep)
+ continue;
+
+ // Check if the found occurrence is an exact match: left side
+ if (nBegin>0 && rFilter[nBegin-1]!=cSep)
+ continue;
+
+ return true;
+ }
+
+ return false;
+}
+
+}
+
+uno::Sequence<OUString> SAL_CALL SalGtkFilePicker::getSelectedFiles()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GSList* pPathList = gtk_file_chooser_get_uris( GTK_FILE_CHOOSER(m_pDialog) );
+ int nCount = g_slist_length( pPathList );
+#else
+ GListModel* pPathList = gtk_file_chooser_get_files(GTK_FILE_CHOOSER(m_pDialog));
+ int nCount = g_list_model_get_n_items(pPathList);
+#endif
+
+ int nIndex = 0;
+
+ // get the current action setting
+ GtkFileChooserAction eAction = gtk_file_chooser_get_action(
+ GTK_FILE_CHOOSER( m_pDialog ));
+
+ uno::Sequence< OUString > aSelectedFiles(nCount);
+ auto aSelectedFilesRange = asNonConstRange(aSelectedFiles);
+
+ // Convert to OOo
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ for( GSList *pElem = pPathList; pElem; pElem = pElem->next)
+ {
+ gchar *pURI = static_cast<gchar*>(pElem->data);
+#else
+ while (gpointer pElem = g_list_model_get_item(pPathList, nIndex))
+ {
+ gchar *pURI = g_file_get_uri(static_cast<GFile*>(pElem));
+#endif
+
+ aSelectedFilesRange[ nIndex ] = uritounicode(pURI);
+
+ if( GTK_FILE_CHOOSER_ACTION_SAVE == eAction )
+ {
+ OUString sFilterName;
+ sal_Int32 nTokenIndex = 0;
+ bool bExtensionTypedIn = false;
+
+ GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_pFilterView));
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ gchar *title = nullptr;
+ gtk_tree_model_get (model, &iter, 2, &title, -1);
+ if (title)
+ sFilterName = OUString( title, strlen( title ), RTL_TEXTENCODING_UTF8 );
+ else
+ sFilterName = OUString();
+ g_free (title);
+ }
+ else
+ {
+ if( aSelectedFiles[nIndex].indexOf('.') > 0 )
+ {
+ std::u16string_view sExtension;
+ nTokenIndex = 0;
+ do
+ sExtension = o3tl::getToken(aSelectedFiles[nIndex], 0, '.', nTokenIndex );
+ while( nTokenIndex >= 0 );
+
+ if( sExtension.size() >= 3 ) // 3 = typical/minimum extension length
+ {
+ OUString aNewFilter;
+ OUString aOldFilter = getCurrentFilter();
+ bool bChangeFilter = true;
+ if ( m_pFilterVector)
+ for (auto const& filter : *m_pFilterVector)
+ {
+ if( lcl_matchFilter( filter.getFilter(), Concat2View(OUString::Concat("*.") + sExtension) ) )
+ {
+ if( aNewFilter.isEmpty() )
+ aNewFilter = filter.getTitle();
+
+ if( aOldFilter == filter.getTitle() )
+ bChangeFilter = false;
+
+ bExtensionTypedIn = true;
+ }
+ }
+ if( bChangeFilter && bExtensionTypedIn )
+ {
+ gchar* pCurrentName = gtk_file_chooser_get_current_name(GTK_FILE_CHOOSER(m_pDialog));
+ setCurrentFilter( aNewFilter );
+ gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(m_pDialog), pCurrentName);
+ g_free(pCurrentName);
+ }
+ }
+ }
+
+ GtkFileFilter *filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(m_pDialog));
+ if (m_pPseudoFilter != filter)
+ {
+ const gchar* filtername = filter ? gtk_file_filter_get_name(filter) : nullptr;
+ if (filtername)
+ sFilterName = OUString(filtername, strlen( filtername ), RTL_TEXTENCODING_UTF8);
+ else
+ sFilterName.clear();
+ }
+ else
+ sFilterName = m_aInitialFilter;
+ }
+
+ if (m_pFilterVector)
+ {
+ auto aVectorIter = ::std::find_if(
+ m_pFilterVector->begin(), m_pFilterVector->end(), FilterTitleMatch(sFilterName) );
+
+ OUString aFilter;
+ if (aVectorIter != m_pFilterVector->end())
+ aFilter = aVectorIter->getFilter();
+
+ nTokenIndex = 0;
+ OUString sToken;
+ do
+ {
+ sToken = aFilter.getToken( 0, '.', nTokenIndex );
+
+ if ( sToken.lastIndexOf( ';' ) != -1 )
+ {
+ sToken = sToken.getToken(0, ';');
+ break;
+ }
+ }
+ while( nTokenIndex >= 0 );
+
+ if( !bExtensionTypedIn && ( sToken != "*" ) )
+ {
+ //if the filename does not already have the auto extension, stick it on
+ OUString sExtension = "." + sToken;
+ OUString &rBase = aSelectedFilesRange[nIndex];
+ sal_Int32 nExtensionIdx = rBase.getLength() - sExtension.getLength();
+ SAL_INFO(
+ "vcl.gtk",
+ "idx are " << rBase.lastIndexOf(sExtension) << " "
+ << nExtensionIdx);
+
+ if( rBase.lastIndexOf( sExtension ) != nExtensionIdx )
+ rBase += sExtension;
+ }
+ }
+
+ }
+
+ nIndex++;
+ g_free( pURI );
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_slist_free( pPathList );
+#else
+ g_object_unref(pPathList);
+#endif
+
+ return aSelectedFiles;
+}
+
+// XExecutableDialog functions
+
+void SAL_CALL SalGtkFilePicker::setTitle( const OUString& rTitle )
+{
+ SolarMutexGuard g;
+
+ implsetTitle(rTitle);
+}
+
+sal_Int16 SAL_CALL SalGtkFilePicker::execute()
+{
+ SolarMutexGuard g;
+
+ if (!mbInitialized)
+ {
+ // tdf#144084 if not initialized default to FILEOPEN_SIMPLE
+ impl_initialize(nullptr, FILEOPEN_SIMPLE);
+ assert(mbInitialized);
+ }
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ sal_Int16 retVal = 0;
+
+ SetFilters();
+
+ // tdf#84431 - set the filter after the corresponding widget is created
+ if ( !m_aCurrentFilter.isEmpty() )
+ SetCurFilter(m_aCurrentFilter);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mnHID_FolderChange =
+ g_signal_connect( GTK_FILE_CHOOSER( m_pDialog ), "current-folder-changed",
+ G_CALLBACK( folder_changed_cb ), static_cast<gpointer>(this) );
+#else
+ // no replacement in 4-0 that I can see :-(
+ mnHID_FolderChange = 0;
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mnHID_SelectionChange =
+ g_signal_connect( GTK_FILE_CHOOSER( m_pDialog ), "selection-changed",
+ G_CALLBACK( selection_changed_cb ), static_cast<gpointer>(this) );
+#else
+ // no replacement in 4-0 that I can see :-(
+ mnHID_SelectionChange = 0;
+#endif
+
+ int btn = GTK_RESPONSE_NO;
+
+ uno::Reference< awt::XExtendedToolkit > xToolkit(
+ awt::Toolkit::create(m_xContext),
+ UNO_QUERY_THROW );
+
+ uno::Reference< frame::XDesktop > xDesktop(
+ frame::Desktop::create(m_xContext),
+ UNO_QUERY_THROW );
+
+ GtkWindow *pParent = GTK_WINDOW(m_pParentWidget);
+ if (!pParent)
+ {
+ SAL_WARN( "vcl.gtk", "no parent widget set");
+ pParent = RunDialog::GetTransientFor();
+ }
+ if (pParent)
+ gtk_window_set_transient_for(GTK_WINDOW(m_pDialog), pParent);
+ rtl::Reference<RunDialog> pRunDialog = new RunDialog(m_pDialog, xToolkit, xDesktop);
+ while( GTK_RESPONSE_NO == btn )
+ {
+ btn = GTK_RESPONSE_YES; // we don't want to repeat unless user clicks NO for file save.
+
+ gint nStatus = pRunDialog->run();
+ switch( nStatus )
+ {
+ case GTK_RESPONSE_ACCEPT:
+ if( GTK_FILE_CHOOSER_ACTION_SAVE == gtk_file_chooser_get_action( GTK_FILE_CHOOSER( m_pDialog ) ) )
+ {
+ Sequence < OUString > aPathSeq = getFiles();
+ if( aPathSeq.getLength() == 1 )
+ {
+ OUString sFileName = aPathSeq[0];
+ if (::utl::UCBContentHelper::Exists(sFileName))
+ {
+ INetURLObject aFileObj(sFileName);
+
+ OString baseName(
+ OUStringToOString(
+ aFileObj.getName(
+ INetURLObject::LAST_SEGMENT,
+ true,
+ INetURLObject::DecodeMechanism::WithCharset
+ ),
+ RTL_TEXTENCODING_UTF8
+ )
+ );
+ OString aMsg(
+ OUStringToOString(
+ getResString( FILE_PICKER_OVERWRITE_PRIMARY ),
+ RTL_TEXTENCODING_UTF8
+ )
+ );
+ OString toReplace("$filename$"_ostr);
+
+ aMsg = aMsg.replaceAt(
+ aMsg.indexOf( toReplace ),
+ toReplace.getLength(),
+ baseName
+ );
+
+ GtkWidget *dlg = gtk_message_dialog_new( nullptr,
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_YES_NO,
+ "%s",
+ aMsg.getStr()
+ );
+
+ GtkWidget* pOkButton = gtk_dialog_get_widget_for_response(GTK_DIALOG(dlg), GTK_RESPONSE_YES);
+ GtkStyleContext* pStyleContext = gtk_widget_get_style_context(pOkButton);
+ gtk_style_context_add_class(pStyleContext, "destructive-action");
+
+ sal_Int32 nSegmentCount = aFileObj.getSegmentCount();
+ if (nSegmentCount >= 2)
+ {
+ OString dirName(
+ OUStringToOString(
+ aFileObj.getName(
+ nSegmentCount-2,
+ true,
+ INetURLObject::DecodeMechanism::WithCharset
+ ),
+ RTL_TEXTENCODING_UTF8
+ )
+ );
+
+ aMsg =
+ OUStringToOString(
+ getResString( FILE_PICKER_OVERWRITE_SECONDARY ),
+ RTL_TEXTENCODING_UTF8
+ );
+
+ toReplace = "$dirname$"_ostr;
+
+ aMsg = aMsg.replaceAt(
+ aMsg.indexOf( toReplace ),
+ toReplace.getLength(),
+ dirName
+ );
+
+ gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( dlg ), "%s", aMsg.getStr() );
+ }
+
+ gtk_window_set_title( GTK_WINDOW( dlg ),
+ OUStringToOString(getResString(FILE_PICKER_TITLE_SAVE ),
+ RTL_TEXTENCODING_UTF8 ).getStr() );
+ gtk_window_set_transient_for(GTK_WINDOW(dlg), GTK_WINDOW(m_pDialog));
+ rtl::Reference<RunDialog> pAnotherDialog = new RunDialog(dlg, xToolkit, xDesktop);
+ btn = pAnotherDialog->run();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(dlg);
+#else
+ gtk_window_destroy(GTK_WINDOW(dlg));
+#endif
+ }
+
+ if( btn == GTK_RESPONSE_YES )
+ retVal = ExecutableDialogResults::OK;
+ }
+ }
+ else
+ retVal = ExecutableDialogResults::OK;
+ break;
+
+ case GTK_RESPONSE_CANCEL:
+ retVal = ExecutableDialogResults::CANCEL;
+ break;
+
+ case 1: //PLAY
+ {
+ FilePickerEvent evt;
+ evt.ElementId = PUSHBUTTON_PLAY;
+ impl_controlStateChanged( evt );
+ btn = GTK_RESPONSE_NO;
+ }
+ break;
+
+ default:
+ retVal = 0;
+ break;
+ }
+ }
+ gtk_widget_hide(m_pDialog);
+
+ if (mnHID_FolderChange)
+ g_signal_handler_disconnect(GTK_FILE_CHOOSER( m_pDialog ), mnHID_FolderChange);
+ if (mnHID_SelectionChange)
+ g_signal_handler_disconnect(GTK_FILE_CHOOSER( m_pDialog ), mnHID_SelectionChange);
+
+ return retVal;
+}
+
+// cf. offapi/com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.idl
+GtkWidget *SalGtkFilePicker::getWidget( sal_Int16 nControlId, GType *pType )
+{
+ GType tType = GTK_TYPE_CHECK_BUTTON; //prevent warning by initializing
+ GtkWidget *pWidget = nullptr;
+
+#define MAP_TOGGLE( elem ) \
+ case ExtendedFilePickerElementIds::CHECKBOX_##elem: \
+ pWidget = m_pToggles[elem]; tType = GTK_TYPE_CHECK_BUTTON; \
+ break
+#define MAP_BUTTON( elem ) \
+ case CommonFilePickerElementIds::PUSHBUTTON_##elem: \
+ pWidget = m_pButtons[elem]; tType = GTK_TYPE_BUTTON; \
+ break
+#define MAP_EXT_BUTTON( elem ) \
+ case ExtendedFilePickerElementIds::PUSHBUTTON_##elem: \
+ pWidget = m_pButtons[elem]; tType = GTK_TYPE_BUTTON; \
+ break
+#define MAP_LIST( elem ) \
+ case ExtendedFilePickerElementIds::LISTBOX_##elem: \
+ pWidget = m_pLists[elem]; tType = GTK_TYPE_COMBO_BOX; \
+ break
+#define MAP_LIST_LABEL( elem ) \
+ case ExtendedFilePickerElementIds::LISTBOX_##elem##_LABEL: \
+ pWidget = m_pListLabels[elem]; tType = GTK_TYPE_LABEL; \
+ break
+
+ switch( nControlId )
+ {
+ MAP_TOGGLE( AUTOEXTENSION );
+ MAP_TOGGLE( PASSWORD );
+ MAP_TOGGLE( GPGENCRYPTION );
+ MAP_TOGGLE( FILTEROPTIONS );
+ MAP_TOGGLE( READONLY );
+ MAP_TOGGLE( LINK );
+ MAP_TOGGLE( PREVIEW );
+ MAP_TOGGLE( SELECTION );
+ MAP_BUTTON( OK );
+ MAP_BUTTON( CANCEL );
+ MAP_EXT_BUTTON( PLAY );
+ MAP_LIST( VERSION );
+ MAP_LIST( TEMPLATE );
+ MAP_LIST( IMAGE_TEMPLATE );
+ MAP_LIST( IMAGE_ANCHOR );
+ MAP_LIST_LABEL( VERSION );
+ MAP_LIST_LABEL( TEMPLATE );
+ MAP_LIST_LABEL( IMAGE_TEMPLATE );
+ MAP_LIST_LABEL( IMAGE_ANCHOR );
+ case CommonFilePickerElementIds::LISTBOX_FILTER_LABEL:
+ // the filter list in gtk typically is not labeled, but has a built-in
+ // tooltip to indicate what it does
+ break;
+ default:
+ SAL_WARN( "vcl.gtk", "Handle unknown control " << nControlId);
+ break;
+ }
+#undef MAP
+
+ if( pType )
+ *pType = tType;
+ return pWidget;
+}
+
+// XFilePickerControlAccess functions
+
+static void HackWidthToFirst(GtkComboBox *pWidget)
+{
+ GtkRequisition requisition;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_size_request(GTK_WIDGET(pWidget), &requisition);
+#else
+ gtk_widget_get_preferred_size(GTK_WIDGET(pWidget), &requisition, nullptr);
+#endif
+ gtk_widget_set_size_request(GTK_WIDGET(pWidget), requisition.width, -1);
+}
+
+static void ComboBoxAppendText(GtkComboBox *pCombo, std::u16string_view rStr)
+{
+ GtkTreeIter aIter;
+ GtkListStore *pStore = GTK_LIST_STORE(gtk_combo_box_get_model(pCombo));
+ OString aStr = OUStringToOString(rStr, RTL_TEXTENCODING_UTF8);
+ gtk_list_store_append(pStore, &aIter);
+ gtk_list_store_set(pStore, &aIter, 0, aStr.getStr(), -1);
+}
+
+void SalGtkFilePicker::HandleSetListValue(GtkComboBox *pWidget, sal_Int16 nControlAction, const uno::Any& rValue)
+{
+ switch (nControlAction)
+ {
+ case ControlActions::ADD_ITEM:
+ {
+ OUString sItem;
+ rValue >>= sItem;
+ ComboBoxAppendText(pWidget, sItem);
+ if (!bVersionWidthUnset)
+ {
+ HackWidthToFirst(pWidget);
+ bVersionWidthUnset = true;
+ }
+ }
+ break;
+ case ControlActions::ADD_ITEMS:
+ {
+ Sequence< OUString > aStringList;
+ rValue >>= aStringList;
+ for (const auto& rString : std::as_const(aStringList))
+ {
+ ComboBoxAppendText(pWidget, rString);
+ if (!bVersionWidthUnset)
+ {
+ HackWidthToFirst(pWidget);
+ bVersionWidthUnset = true;
+ }
+ }
+ }
+ break;
+ case ControlActions::DELETE_ITEM:
+ {
+ sal_Int32 nPos=0;
+ rValue >>= nPos;
+
+ GtkTreeIter aIter;
+ GtkListStore *pStore = GTK_LIST_STORE(
+ gtk_combo_box_get_model(GTK_COMBO_BOX(pWidget)));
+ if(gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pStore), &aIter, nullptr, nPos))
+ gtk_list_store_remove(pStore, &aIter);
+ }
+ break;
+ case ControlActions::DELETE_ITEMS:
+ {
+ gtk_combo_box_set_active(pWidget, -1);
+ GtkListStore *pStore = GTK_LIST_STORE(
+ gtk_combo_box_get_model(GTK_COMBO_BOX(pWidget)));
+ gtk_list_store_clear(pStore);
+ }
+ break;
+ case ControlActions::SET_SELECT_ITEM:
+ {
+ sal_Int32 nPos=0;
+ rValue >>= nPos;
+ gtk_combo_box_set_active(pWidget, nPos);
+ }
+ break;
+ default:
+ SAL_WARN( "vcl.gtk", "undocumented/unimplemented ControlAction for a list " << nControlAction);
+ break;
+ }
+
+ //I think its best to make it insensitive unless there is the chance to
+ //actually select something from the list.
+ gint nItems = gtk_tree_model_iter_n_children(
+ gtk_combo_box_get_model(pWidget), nullptr);
+ gtk_widget_set_sensitive(GTK_WIDGET(pWidget), nItems > 1);
+}
+
+uno::Any SalGtkFilePicker::HandleGetListValue(GtkComboBox *pWidget, sal_Int16 nControlAction)
+{
+ uno::Any aAny;
+ switch (nControlAction)
+ {
+ case ControlActions::GET_ITEMS:
+ {
+ Sequence< OUString > aItemList;
+
+ GtkTreeModel *pTree = gtk_combo_box_get_model(pWidget);
+ GtkTreeIter iter;
+ if (gtk_tree_model_get_iter_first(pTree, &iter))
+ {
+ sal_Int32 nSize = gtk_tree_model_iter_n_children(
+ pTree, nullptr);
+
+ aItemList.realloc(nSize);
+ auto pItemList = aItemList.getArray();
+ for (sal_Int32 i=0; i < nSize; ++i)
+ {
+ gchar *item;
+ gtk_tree_model_get(gtk_combo_box_get_model(pWidget),
+ &iter, 0, &item, -1);
+ pItemList[i] = OUString(item, strlen(item), RTL_TEXTENCODING_UTF8);
+ g_free(item);
+ (void)gtk_tree_model_iter_next(pTree, &iter);
+ }
+ }
+ aAny <<= aItemList;
+ }
+ break;
+ case ControlActions::GET_SELECTED_ITEM:
+ {
+ GtkTreeIter iter;
+ if (gtk_combo_box_get_active_iter(pWidget, &iter))
+ {
+ gchar *item;
+ gtk_tree_model_get(gtk_combo_box_get_model(pWidget),
+ &iter, 0, &item, -1);
+ OUString sItem(item, strlen(item), RTL_TEXTENCODING_UTF8);
+ aAny <<= sItem;
+ g_free(item);
+ }
+ }
+ break;
+ case ControlActions::GET_SELECTED_ITEM_INDEX:
+ {
+ gint nActive = gtk_combo_box_get_active(pWidget);
+ aAny <<= static_cast< sal_Int32 >(nActive);
+ }
+ break;
+ default:
+ SAL_WARN( "vcl.gtk", "undocumented/unimplemented ControlAction for a list " << nControlAction);
+ break;
+ }
+ return aAny;
+}
+
+void SAL_CALL SalGtkFilePicker::setValue( sal_Int16 nControlId, sal_Int16 nControlAction, const uno::Any& rValue )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ GType tType;
+ GtkWidget *pWidget;
+
+ if( !( pWidget = getWidget( nControlId, &tType ) ) )
+ SAL_WARN( "vcl.gtk", "enable unknown control " << nControlId);
+ else if( tType == GTK_TYPE_CHECK_BUTTON)
+ {
+ bool bChecked = false;
+ rValue >>= bChecked;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_check_button_set_active(GTK_CHECK_BUTTON(pWidget), bChecked);
+#else
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pWidget), bChecked);
+#endif
+ }
+ else if( tType == GTK_TYPE_COMBO_BOX )
+ HandleSetListValue(GTK_COMBO_BOX(pWidget), nControlAction, rValue);
+ else
+ {
+ SAL_WARN( "vcl.gtk", "Can't set value on button / list " << nControlId << " " << nControlAction );
+ }
+}
+
+uno::Any SAL_CALL SalGtkFilePicker::getValue( sal_Int16 nControlId, sal_Int16 nControlAction )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ uno::Any aRetval;
+
+ GType tType;
+ GtkWidget *pWidget;
+
+ if( !( pWidget = getWidget( nControlId, &tType ) ) )
+ SAL_WARN( "vcl.gtk", "enable unknown control " << nControlId);
+ else if( tType == GTK_TYPE_CHECK_BUTTON)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ aRetval <<= bool(gtk_check_button_get_active(GTK_CHECK_BUTTON(pWidget)));
+#else
+ aRetval <<= bool(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pWidget)));
+#endif
+ }
+ else if( tType == GTK_TYPE_COMBO_BOX )
+ aRetval = HandleGetListValue(GTK_COMBO_BOX(pWidget), nControlAction);
+ else
+ SAL_WARN( "vcl.gtk", "Can't get value on button / list " << nControlId << " " << nControlAction );
+
+ return aRetval;
+}
+
+void SAL_CALL SalGtkFilePicker::enableControl( sal_Int16 nControlId, sal_Bool bEnable )
+{
+ // skip this built-in one which is Enabled by default
+ if (nControlId == ExtendedFilePickerElementIds::LISTBOX_FILTER_SELECTOR && bEnable)
+ return;
+
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ GtkWidget *pWidget;
+
+ if ( ( pWidget = getWidget( nControlId ) ) )
+ {
+ if( bEnable )
+ {
+ gtk_widget_set_sensitive( pWidget, true );
+ }
+ else
+ {
+ gtk_widget_set_sensitive( pWidget, false );
+ }
+ }
+ else
+ SAL_WARN( "vcl.gtk", "enable unknown control " << nControlId );
+}
+
+void SAL_CALL SalGtkFilePicker::setLabel( sal_Int16 nControlId, const OUString& rLabel )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ GType tType;
+ GtkWidget *pWidget;
+
+ if( !( pWidget = getWidget( nControlId, &tType ) ) )
+ {
+ SAL_WARN_IF(nControlId != CommonFilePickerElementIds::LISTBOX_FILTER_LABEL,
+ "vcl.gtk", "Set label '" << rLabel << "' on unknown control " << nControlId);
+ return;
+ }
+
+ OString aTxt = OUStringToOString( rLabel.replace('~', '_'), RTL_TEXTENCODING_UTF8 );
+ if( tType == GTK_TYPE_CHECK_BUTTON || tType == GTK_TYPE_BUTTON || tType == GTK_TYPE_LABEL )
+ g_object_set( pWidget, "label", aTxt.getStr(),
+ "use_underline", true, nullptr );
+ else
+ SAL_WARN( "vcl.gtk", "Can't set label on list");
+}
+
+OUString SAL_CALL SalGtkFilePicker::getLabel( sal_Int16 nControlId )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ GType tType;
+ OString aTxt;
+ GtkWidget *pWidget;
+
+ if( !( pWidget = getWidget( nControlId, &tType ) ) )
+ SAL_WARN( "vcl.gtk", "Get label on unknown control " << nControlId);
+ else if( tType == GTK_TYPE_CHECK_BUTTON || tType == GTK_TYPE_BUTTON || tType == GTK_TYPE_LABEL )
+ aTxt = gtk_button_get_label( GTK_BUTTON( pWidget ) );
+ else
+ SAL_WARN( "vcl.gtk", "Can't get label on list");
+
+ return OStringToOUString( aTxt, RTL_TEXTENCODING_UTF8 );
+}
+
+// XFilePreview functions
+
+uno::Sequence<sal_Int16> SAL_CALL SalGtkFilePicker::getSupportedImageFormats()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO return m_pImpl->getSupportedImageFormats();
+ return uno::Sequence<sal_Int16>();
+}
+
+sal_Int32 SAL_CALL SalGtkFilePicker::getTargetColorDepth()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO return m_pImpl->getTargetColorDepth();
+ return 0;
+}
+
+sal_Int32 SAL_CALL SalGtkFilePicker::getAvailableWidth()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ return g_PreviewImageWidth;
+}
+
+sal_Int32 SAL_CALL SalGtkFilePicker::getAvailableHeight()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ return g_PreviewImageHeight;
+}
+
+void SAL_CALL SalGtkFilePicker::setImage( sal_Int16 /*aImageFormat*/, const uno::Any& /*aImage*/ )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO m_pImpl->setImage( aImageFormat, aImage );
+}
+
+void SalGtkFilePicker::implChangeType( GtkTreeSelection *selection )
+{
+ OUString aLabel = getResString( FILE_PICKER_FILE_TYPE );
+
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ gchar *title;
+ gtk_tree_model_get (model, &iter, 2, &title, -1);
+ aLabel += ": " +
+ OUString( title, strlen(title), RTL_TEXTENCODING_UTF8 );
+ g_free (title);
+ }
+ gtk_expander_set_label (GTK_EXPANDER (m_pFilterExpander),
+ OUStringToOString( aLabel, RTL_TEXTENCODING_UTF8 ).getStr());
+ FilePickerEvent evt;
+ evt.ElementId = LISTBOX_FILTER;
+ impl_controlStateChanged( evt );
+}
+
+void SalGtkFilePicker::type_changed_cb( GtkTreeSelection *selection, SalGtkFilePicker *pobjFP )
+{
+ pobjFP->implChangeType(selection);
+}
+
+void SalGtkFilePicker::unselect_type()
+{
+ gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(m_pFilterView)));
+}
+
+void SalGtkFilePicker::expander_changed_cb( GtkExpander *expander, SalGtkFilePicker *pobjFP )
+{
+ if (gtk_expander_get_expanded(expander))
+ pobjFP->unselect_type();
+}
+
+void SalGtkFilePicker::filter_changed_cb( GtkFileChooser *, GParamSpec *,
+ SalGtkFilePicker *pobjFP )
+{
+ FilePickerEvent evt;
+ evt.ElementId = LISTBOX_FILTER;
+ SAL_INFO( "vcl.gtk", "filter_changed, isn't it great " << pobjFP );
+ pobjFP->impl_controlStateChanged( evt );
+}
+
+void SalGtkFilePicker::folder_changed_cb( GtkFileChooser *, SalGtkFilePicker *pobjFP )
+{
+ FilePickerEvent evt;
+ SAL_INFO( "vcl.gtk", "folder_changed, isn't it great " << pobjFP );
+ pobjFP->impl_directoryChanged( evt );
+}
+
+void SalGtkFilePicker::selection_changed_cb( GtkFileChooser *, SalGtkFilePicker *pobjFP )
+{
+ FilePickerEvent evt;
+ SAL_INFO( "vcl.gtk", "selection_changed, isn't it great " << pobjFP );
+ pobjFP->impl_fileSelectionChanged( evt );
+}
+
+void SalGtkFilePicker::update_preview_cb( GtkFileChooser *file_chooser, SalGtkFilePicker* pobjFP )
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool have_preview = false;
+
+ GtkWidget* preview = pobjFP->m_pPreview;
+ char* filename = gtk_file_chooser_get_preview_filename( file_chooser );
+
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pobjFP->m_pToggles[PREVIEW])) && filename && g_file_test(filename, G_FILE_TEST_IS_REGULAR))
+ {
+ GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_size(
+ filename,
+ g_PreviewImageWidth,
+ g_PreviewImageHeight, nullptr );
+
+ have_preview = ( pixbuf != nullptr );
+
+ gtk_image_set_from_pixbuf( GTK_IMAGE( preview ), pixbuf );
+ if( pixbuf )
+ g_object_unref( G_OBJECT( pixbuf ) );
+
+ }
+
+ gtk_file_chooser_set_preview_widget_active( file_chooser, have_preview );
+
+ if( filename )
+ g_free( filename );
+#else
+ (void)file_chooser;
+ (void)pobjFP;
+#endif
+}
+
+sal_Bool SAL_CALL SalGtkFilePicker::setShowState( sal_Bool bShowState )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO return m_pImpl->setShowState( bShowState );
+ if( bool(bShowState) != mbPreviewState )
+ {
+ if( bShowState )
+ {
+ // Show
+ if( !mHID_Preview )
+ {
+ mHID_Preview = g_signal_connect(
+ GTK_FILE_CHOOSER( m_pDialog ), "update-preview",
+ G_CALLBACK( update_preview_cb ), static_cast<gpointer>(this) );
+ }
+ gtk_widget_show( m_pPreview );
+ }
+ else
+ {
+ // Hide
+ gtk_widget_hide( m_pPreview );
+ }
+
+ // also emit the signal
+ g_signal_emit_by_name( G_OBJECT( m_pDialog ), "update-preview" );
+
+ mbPreviewState = bShowState;
+ }
+ return true;
+}
+
+sal_Bool SAL_CALL SalGtkFilePicker::getShowState()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ return mbPreviewState;
+}
+
+GtkWidget* SalGtkPicker::GetParentWidget(const uno::Sequence<uno::Any>& rArguments)
+{
+ GtkWidget* pParentWidget = nullptr;
+
+ css::uno::Reference<css::awt::XWindow> xParentWindow;
+ if (rArguments.getLength() > 1)
+ {
+ rArguments[1] >>= xParentWindow;
+ }
+
+ if (xParentWindow.is())
+ {
+ if (SalGtkXWindow* pGtkXWindow = dynamic_cast<SalGtkXWindow*>(xParentWindow.get()))
+ pParentWidget = pGtkXWindow->getGtkWidget();
+ else
+ {
+ css::uno::Reference<css::awt::XSystemDependentWindowPeer> xSysDepWin(xParentWindow, css::uno::UNO_QUERY);
+ if (xSysDepWin.is())
+ {
+ css::uno::Sequence<sal_Int8> aProcessIdent(16);
+ rtl_getGlobalProcessId(reinterpret_cast<sal_uInt8*>(aProcessIdent.getArray()));
+ uno::Any aAny = xSysDepWin->getWindowHandle(aProcessIdent, css::lang::SystemDependent::SYSTEM_XWINDOW);
+ css::awt::SystemDependentXWindow tmp;
+ aAny >>= tmp;
+ pParentWidget = GetGtkSalData()->GetGtkDisplay()->findGtkWidgetForNativeHandle(tmp.WindowHandle);
+ }
+ }
+ }
+
+ return pParentWidget;
+}
+
+// XInitialization
+
+void SAL_CALL SalGtkFilePicker::initialize( const uno::Sequence<uno::Any>& aArguments )
+{
+ // parameter checking
+ uno::Any aAny;
+ if( !aArguments.hasElements() )
+ throw lang::IllegalArgumentException(
+ "no arguments",
+ static_cast<XFilePicker2*>( this ), 1 );
+
+ aAny = aArguments[0];
+
+ if( ( aAny.getValueType() != cppu::UnoType<sal_Int16>::get()) &&
+ (aAny.getValueType() != cppu::UnoType<sal_Int8>::get()) )
+ throw lang::IllegalArgumentException(
+ "invalid argument type",
+ static_cast<XFilePicker2*>( this ), 1 );
+
+ sal_Int16 templateId = -1;
+ aAny >>= templateId;
+
+ impl_initialize(GetParentWidget(aArguments), templateId);
+}
+
+void SalGtkFilePicker::impl_initialize(GtkWidget* pParentWidget, sal_Int16 templateId)
+{
+ m_pParentWidget = pParentWidget;
+
+ GtkFileChooserAction eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ OString sOpen = getOpenText();
+ OString sSave = getSaveText();
+ const gchar *first_button_text;
+
+ SolarMutexGuard g;
+
+ // TODO: extract full semantic from
+ // svtools/source/filepicker/filepicker.cxx (getWinBits)
+ switch( templateId )
+ {
+ case FILEOPEN_SIMPLE:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ break;
+ case FILESAVE_SIMPLE:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = sSave.getStr();
+ break;
+ case FILESAVE_AUTOEXTENSION_PASSWORD:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = sSave.getStr();
+ mbToggleVisibility[PASSWORD] = true;
+ mbToggleVisibility[GPGENCRYPTION] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = sSave.getStr();
+ mbToggleVisibility[PASSWORD] = true;
+ mbToggleVisibility[GPGENCRYPTION] = true;
+ mbToggleVisibility[FILTEROPTIONS] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION_SELECTION:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE; // SELECT_FOLDER ?
+ first_button_text = sSave.getStr();
+ mbToggleVisibility[SELECTION] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION_TEMPLATE:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = sSave.getStr();
+ mbListVisibility[TEMPLATE] = true;
+ // TODO
+ break;
+ case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[LINK] = true;
+ mbToggleVisibility[PREVIEW] = true;
+ mbListVisibility[IMAGE_TEMPLATE] = true;
+ // TODO
+ break;
+ case FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[LINK] = true;
+ mbToggleVisibility[PREVIEW] = true;
+ mbListVisibility[IMAGE_ANCHOR] = true;
+ // TODO
+ break;
+ case FILEOPEN_PLAY:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbButtonVisibility[PLAY] = true;
+ // TODO
+ break;
+ case FILEOPEN_LINK_PLAY:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[LINK] = true;
+ mbButtonVisibility[PLAY] = true;
+ // TODO
+ break;
+ case FILEOPEN_READONLY_VERSION:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[READONLY] = true;
+ mbListVisibility[VERSION] = true;
+ break;
+ case FILEOPEN_LINK_PREVIEW:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[LINK] = true;
+ mbToggleVisibility[PREVIEW] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = sSave.getStr();
+ // TODO
+ break;
+ case FILEOPEN_PREVIEW:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[PREVIEW] = true;
+ // TODO
+ break;
+ default:
+ throw lang::IllegalArgumentException(
+ "Unknown template",
+ static_cast< XFilePicker2* >( this ),
+ 1 );
+ }
+
+ if( GTK_FILE_CHOOSER_ACTION_SAVE == eAction )
+ {
+ OUString aFilePickerTitle(getResString( FILE_PICKER_TITLE_SAVE ));
+ gtk_window_set_title ( GTK_WINDOW( m_pDialog ),
+ OUStringToOString( aFilePickerTitle, RTL_TEXTENCODING_UTF8 ).getStr() );
+ }
+
+ gtk_file_chooser_set_action( GTK_FILE_CHOOSER( m_pDialog ), eAction);
+ m_pButtons[CANCEL] = gtk_dialog_add_button(GTK_DIALOG(m_pDialog), getCancelText().getStr(), GTK_RESPONSE_CANCEL);
+ mbButtonVisibility[CANCEL] = true;
+
+ if (mbButtonVisibility[PLAY])
+ {
+ OString aPlay = OUStringToOString(getResString(PUSHBUTTON_PLAY), RTL_TEXTENCODING_UTF8);
+ m_pButtons[PLAY] = gtk_dialog_add_button(GTK_DIALOG(m_pDialog), aPlay.getStr(), 1);
+ }
+
+ m_pButtons[OK] = gtk_dialog_add_button(GTK_DIALOG(m_pDialog), first_button_text, GTK_RESPONSE_ACCEPT);
+ mbButtonVisibility[OK] = true;
+
+ gtk_dialog_set_default_response( GTK_DIALOG (m_pDialog), GTK_RESPONSE_ACCEPT );
+
+ // Setup special flags
+ for( int nTVIndex = 0; nTVIndex < TOGGLE_LAST; nTVIndex++ )
+ {
+ if( mbToggleVisibility[nTVIndex] )
+ gtk_widget_show( m_pToggles[ nTVIndex ] );
+ }
+
+ for( int nTVIndex = 0; nTVIndex < LIST_LAST; nTVIndex++ )
+ {
+ if( mbListVisibility[nTVIndex] )
+ {
+ gtk_widget_set_sensitive( m_pLists[ nTVIndex ], false );
+ gtk_widget_show( m_pLists[ nTVIndex ] );
+ gtk_widget_show( m_pListLabels[ nTVIndex ] );
+ gtk_widget_show( m_pHBoxs[ nTVIndex ] );
+ }
+ }
+
+ mbInitialized = true;
+}
+
+void SalGtkFilePicker::preview_toggled_cb( GObject *cb, SalGtkFilePicker* pobjFP )
+{
+ if( pobjFP->mbToggleVisibility[PREVIEW] )
+ pobjFP->setShowState( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( cb ) ) );
+}
+
+// XCancellable
+
+void SAL_CALL SalGtkFilePicker::cancel()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO m_pImpl->cancel();
+}
+
+// Misc
+
+void SalGtkFilePicker::SetCurFilter( const OUString& rFilter )
+{
+ // Get all the filters already added
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GListModel *filters = gtk_file_chooser_get_filters(GTK_FILE_CHOOSER(m_pDialog));
+#else
+ GSList *filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(m_pDialog));
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ int nIndex = 0;
+ while (gpointer pElem = g_list_model_get_item(filters, nIndex))
+ {
+ GtkFileFilter* pFilter = static_cast<GtkFileFilter*>(pElem);
+ ++nIndex;
+#else
+ for( GSList *iter = filters; iter; iter = iter->next )
+ {
+ GtkFileFilter* pFilter = static_cast<GtkFileFilter*>( iter->data );
+#endif
+ const gchar * filtername = gtk_file_filter_get_name( pFilter );
+ OUString sFilterName( filtername, strlen( filtername ), RTL_TEXTENCODING_UTF8 );
+
+ OUString aShrunkName = shrinkFilterName( rFilter );
+ if( aShrunkName == sFilterName )
+ {
+ SAL_INFO( "vcl.gtk", "actually setting " << filtername );
+ gtk_file_chooser_set_filter( GTK_FILE_CHOOSER( m_pDialog ), pFilter );
+ break;
+ }
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_slist_free( filters );
+#else
+ g_object_unref (filters);
+#endif
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+extern "C"
+{
+static gboolean
+case_insensitive_filter (const GtkFileFilterInfo *filter_info, gpointer data)
+{
+ bool bRetval = false;
+ const char *pFilter = static_cast<const char *>(data);
+
+ g_return_val_if_fail( data != nullptr, false );
+ g_return_val_if_fail( filter_info != nullptr, false );
+
+ if( !filter_info->uri )
+ return false;
+
+ const char *pExtn = strrchr( filter_info->uri, '.' );
+ if( !pExtn )
+ return false;
+ pExtn++;
+
+ if( !g_ascii_strcasecmp( pFilter, pExtn ) )
+ bRetval = true;
+
+ SAL_INFO( "vcl.gtk", "'" << filter_info->uri << "' match extn '" << pExtn << "' vs '" << pFilter << "' yields " << bRetval );
+
+ return bRetval;
+}
+}
+#endif
+
+GtkFileFilter* SalGtkFilePicker::implAddFilter( const OUString& rFilter, const OUString& rType )
+{
+ GtkFileFilter *filter = gtk_file_filter_new();
+
+ OUString aShrunkName = shrinkFilterName( rFilter );
+ OString aFilterName = OUStringToOString( aShrunkName, RTL_TEXTENCODING_UTF8 );
+ gtk_file_filter_set_name( filter, aFilterName.getStr() );
+
+ OUStringBuffer aTokens;
+
+ bool bAllGlob = rType == "*.*" || rType == "*";
+ if (bAllGlob)
+ gtk_file_filter_add_pattern( filter, "*" );
+ else
+ {
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OUString aToken = rType.getToken( 0, ';', nIndex );
+ // Assume all have the "*.<extn>" syntax
+ sal_Int32 nStarDot = aToken.lastIndexOf( "*." );
+ if (nStarDot >= 0)
+ aToken = aToken.copy( nStarDot + 2 );
+ if (!aToken.isEmpty())
+ {
+ if (!aTokens.isEmpty())
+ aTokens.append(",");
+ aTokens.append(aToken);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_file_filter_add_suffix(filter, aToken.toUtf8().getStr());
+#else
+ gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_URI,
+ case_insensitive_filter,
+ g_strdup( OUStringToOString(aToken, RTL_TEXTENCODING_UTF8).getStr() ),
+ g_free );
+#endif
+
+ SAL_INFO( "vcl.gtk", "fustering with " << aToken );
+ }
+#if OSL_DEBUG_LEVEL > 0
+ else
+ {
+ g_warning( "Duff filter token '%s'\n",
+ OUStringToOString(
+ o3tl::getToken(rType, 0, ';', nIndex ), RTL_TEXTENCODING_UTF8 ).getStr() );
+ }
+#endif
+ }
+ while( nIndex >= 0 );
+ }
+
+ gtk_file_chooser_add_filter( GTK_FILE_CHOOSER( m_pDialog ), filter );
+
+ if (!bAllGlob)
+ {
+ GtkTreeIter iter;
+ gtk_list_store_append (m_pFilterStore, &iter);
+ gtk_list_store_set (m_pFilterStore, &iter,
+ 0, OUStringToOString(shrinkFilterName( rFilter, true ), RTL_TEXTENCODING_UTF8).getStr(),
+ 1, OUStringToOString(aTokens, RTL_TEXTENCODING_UTF8).getStr(),
+ 2, aFilterName.getStr(),
+ 3, OUStringToOString(rType, RTL_TEXTENCODING_UTF8).getStr(),
+ -1);
+ }
+ return filter;
+}
+
+void SalGtkFilePicker::implAddFilterGroup( const Sequence< StringPair >& _rFilters )
+{
+ // Gtk+ has no filter group concept I think so ...
+ // implAddFilter( _rFilter, String() );
+ for( const auto& rSubFilter : _rFilters )
+ implAddFilter( rSubFilter.First, rSubFilter.Second );
+}
+
+void SalGtkFilePicker::SetFilters()
+{
+ if (m_aInitialFilter.isEmpty())
+ m_aInitialFilter = m_aCurrentFilter;
+
+ OUString sPseudoFilter;
+ if( GTK_FILE_CHOOSER_ACTION_SAVE == gtk_file_chooser_get_action( GTK_FILE_CHOOSER( m_pDialog ) ) )
+ {
+ std::set<OUString> aAllFormats;
+ if( m_pFilterVector )
+ {
+ for (auto & filter : *m_pFilterVector)
+ {
+ if( filter.hasSubFilters() )
+ { // it's a filter group
+ css::uno::Sequence< css::beans::StringPair > aSubFilters;
+ filter.getSubFilters( aSubFilters );
+ for( const auto& rSubFilter : std::as_const(aSubFilters) )
+ aAllFormats.insert(rSubFilter.Second);
+ }
+ else
+ aAllFormats.insert(filter.getFilter());
+ }
+ }
+ if (aAllFormats.size() > 1)
+ {
+ OUStringBuffer sAllFilter;
+ for (auto const& format : aAllFormats)
+ {
+ if (!sAllFilter.isEmpty())
+ sAllFilter.append(";");
+ sAllFilter.append(format);
+ }
+ sPseudoFilter = getResString(FILE_PICKER_ALLFORMATS);
+ m_pPseudoFilter = implAddFilter( sPseudoFilter, sAllFilter.makeStringAndClear() );
+ }
+ }
+
+ if( m_pFilterVector )
+ {
+ for (auto & filter : *m_pFilterVector)
+ {
+ if( filter.hasSubFilters() )
+ { // it's a filter group
+
+ css::uno::Sequence< css::beans::StringPair > aSubFilters;
+ filter.getSubFilters( aSubFilters );
+
+ implAddFilterGroup( aSubFilters );
+ }
+ else
+ {
+ // it's a single filter
+
+ implAddFilter( filter.getTitle(), filter.getFilter() );
+ }
+ }
+ }
+
+ // We always hide the expander now and depend on the user using the glob
+ // list, or type a filename suffix, to select a filter by inference.
+ gtk_widget_hide(m_pFilterExpander);
+
+ // set the default filter
+ if (!sPseudoFilter.isEmpty())
+ SetCurFilter( sPseudoFilter );
+ else if(!m_aCurrentFilter.isEmpty())
+ SetCurFilter( m_aCurrentFilter );
+
+ SAL_INFO( "vcl.gtk", "end setting filters");
+}
+
+SalGtkFilePicker::~SalGtkFilePicker()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ SolarMutexGuard g;
+
+ int i;
+
+ for( i = 0; i < TOGGLE_LAST; i++ )
+ gtk_widget_destroy( m_pToggles[i] );
+
+ for( i = 0; i < LIST_LAST; i++ )
+ {
+ gtk_widget_destroy( m_pListLabels[i] );
+ gtk_widget_destroy( m_pLists[i] );
+ gtk_widget_destroy( m_pHBoxs[i] );
+ }
+
+ m_pFilterVector.reset();
+
+ gtk_widget_destroy( m_pVBox );
+#endif
+}
+
+uno::Reference< ui::dialogs::XFilePicker2 >
+GtkInstance::createFilePicker( const css::uno::Reference< css::uno::XComponentContext > &xMSF )
+{
+ return uno::Reference< ui::dialogs::XFilePicker2 >(
+ new SalGtkFilePicker( xMSF ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx
new file mode 100644
index 0000000000..fdc701a203
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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/compbase.hxx>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/ui/dialogs/XFilePickerControlAccess.hpp>
+#include <com/sun/star/ui/dialogs/XFilePreview.hpp>
+#include <com/sun/star/ui/dialogs/XFilePicker3.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/beans/StringPair.hpp>
+
+#include <vector>
+#include <memory>
+#include <rtl/ustring.hxx>
+
+#include "SalGtkPicker.hxx"
+
+// Implementation class for the XFilePicker Interface
+
+struct FilterEntry;
+struct ElementEntry_Impl;
+
+
+typedef cppu::WeakComponentImplHelper<
+ css::ui::dialogs::XFilePickerControlAccess,
+ css::ui::dialogs::XFilePreview,
+ css::ui::dialogs::XFilePicker3,
+ css::lang::XInitialization
+ > SalGtkFilePicker_Base;
+
+class SalGtkFilePicker : public SalGtkPicker, public SalGtkFilePicker_Base
+{
+ public:
+
+ // constructor
+ SalGtkFilePicker( const css::uno::Reference< css::uno::XComponentContext >& xServiceMgr );
+
+ // XFilePickerNotifier
+
+ virtual void SAL_CALL addFilePickerListener( const css::uno::Reference< css::ui::dialogs::XFilePickerListener >& xListener ) override;
+ virtual void SAL_CALL removeFilePickerListener( const css::uno::Reference< css::ui::dialogs::XFilePickerListener >& xListener ) override;
+
+ // XExecutableDialog functions
+
+ virtual void SAL_CALL setTitle( const OUString& aTitle ) override;
+
+ virtual sal_Int16 SAL_CALL execute() override;
+
+ // XFilePicker functions
+
+ virtual void SAL_CALL setMultiSelectionMode( sal_Bool bMode ) override;
+
+ virtual void SAL_CALL setDefaultName( const OUString& aName ) override;
+
+ virtual void SAL_CALL setDisplayDirectory( const OUString& aDirectory ) override;
+
+ virtual OUString SAL_CALL getDisplayDirectory( ) override;
+
+ virtual css::uno::Sequence< OUString > SAL_CALL getFiles( ) override;
+
+ // XFilePicker2 functions
+
+ virtual css::uno::Sequence< OUString > SAL_CALL getSelectedFiles() override;
+
+ // XFilterManager functions
+
+ virtual void SAL_CALL appendFilter( const OUString& aTitle, const OUString& aFilter ) override;
+
+ virtual void SAL_CALL setCurrentFilter( const OUString& aTitle ) override;
+
+ virtual OUString SAL_CALL getCurrentFilter( ) override;
+
+ // XFilterGroupManager functions
+
+ virtual void SAL_CALL appendFilterGroup( const OUString& sGroupTitle, const css::uno::Sequence< css::beans::StringPair >& aFilters ) override;
+
+ // XFilePickerControlAccess functions
+
+ virtual void SAL_CALL setValue( sal_Int16 nControlId, sal_Int16 nControlAction, const css::uno::Any& aValue ) override;
+
+ virtual css::uno::Any SAL_CALL getValue( sal_Int16 aControlId, sal_Int16 aControlAction ) override;
+
+ virtual void SAL_CALL enableControl( sal_Int16 nControlId, sal_Bool bEnable ) override;
+
+ virtual void SAL_CALL setLabel( sal_Int16 nControlId, const OUString& aLabel ) override;
+
+ virtual OUString SAL_CALL getLabel( sal_Int16 nControlId ) override;
+
+ // XFilePreview
+
+ virtual css::uno::Sequence< sal_Int16 > SAL_CALL getSupportedImageFormats( ) override;
+
+ virtual sal_Int32 SAL_CALL getTargetColorDepth( ) override;
+
+ virtual sal_Int32 SAL_CALL getAvailableWidth( ) override;
+
+ virtual sal_Int32 SAL_CALL getAvailableHeight( ) override;
+
+ virtual void SAL_CALL setImage( sal_Int16 aImageFormat, const css::uno::Any& aImage ) override;
+
+ virtual sal_Bool SAL_CALL setShowState( sal_Bool bShowState ) override;
+
+ virtual sal_Bool SAL_CALL getShowState( ) override;
+
+ // XInitialization
+
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+ // XCancellable
+
+ virtual void SAL_CALL cancel( ) override;
+
+ // FilePicker Event functions
+
+ private:
+ SalGtkFilePicker( const SalGtkFilePicker& ) = delete;
+ SalGtkFilePicker& operator=( const SalGtkFilePicker& ) = delete;
+
+ bool FilterNameExists( const OUString& rTitle );
+ bool FilterNameExists( const css::uno::Sequence< css::beans::StringPair >& _rGroupedFilters );
+
+ void ensureFilterVector( const OUString& _rInitialCurrentFilter );
+
+ void impl_fileSelectionChanged( const css::ui::dialogs::FilePickerEvent& aEvent );
+ void impl_directoryChanged( const css::ui::dialogs::FilePickerEvent& aEvent );
+ void impl_controlStateChanged( const css::ui::dialogs::FilePickerEvent& aEvent );
+ void impl_initialize(GtkWidget* pParentWidget, sal_Int16 templateId);
+
+ private:
+ css::uno::Reference< css::ui::dialogs::XFilePickerListener >
+ m_xListener;
+ std::unique_ptr<std::vector<FilterEntry>> m_pFilterVector;
+ GtkWidget *m_pVBox;
+ GtkWidget *m_pFilterExpander;
+ GtkWidget *m_pFilterView;
+ GtkListStore *m_pFilterStore;
+
+ enum {
+ AUTOEXTENSION,
+ PASSWORD,
+ FILTEROPTIONS,
+ READONLY,
+ LINK,
+ PREVIEW,
+ SELECTION,
+ GPGENCRYPTION,
+ TOGGLE_LAST
+ };
+
+ GtkWidget *m_pToggles[ TOGGLE_LAST ];
+
+ bool mbToggleVisibility[TOGGLE_LAST];
+
+ enum {
+ OK,
+ CANCEL,
+ PLAY,
+ BUTTON_LAST };
+
+ GtkWidget *m_pButtons[ BUTTON_LAST ];
+
+ enum {
+ VERSION,
+ TEMPLATE,
+ IMAGE_TEMPLATE,
+ IMAGE_ANCHOR,
+ LIST_LAST
+ };
+
+ GtkWidget *m_pHBoxs[ LIST_LAST ];
+ GtkWidget *m_pLists[ LIST_LAST ];
+ GtkWidget *m_pListLabels[ LIST_LAST ];
+ bool mbListVisibility[ LIST_LAST ];
+ bool mbButtonVisibility[ BUTTON_LAST ];
+ gulong mnHID_FolderChange;
+ gulong mnHID_SelectionChange;
+
+ OUString m_aCurrentFilter;
+ OUString m_aInitialFilter;
+
+ bool bVersionWidthUnset;
+ bool mbPreviewState;
+ bool mbInitialized;
+ gulong mHID_Preview;
+ GtkWidget* m_pPreview;
+ GtkFileFilter* m_pPseudoFilter;
+ static constexpr sal_Int32 g_PreviewImageWidth = 256;
+ static constexpr sal_Int32 g_PreviewImageHeight = 256;
+
+ GtkWidget *getWidget( sal_Int16 nControlId, GType *pType = nullptr);
+
+ void SetCurFilter( const OUString& rFilter );
+ void SetFilters();
+ void UpdateFilterfromUI();
+
+ void implChangeType( GtkTreeSelection *selection );
+ GtkFileFilter * implAddFilter( const OUString& rFilter, const OUString& rType );
+ void implAddFilterGroup(
+ const css::uno::Sequence< css::beans::StringPair>& _rFilters );
+ void updateCurrentFilterFromName(const gchar* filtername);
+ void unselect_type();
+ void InitialMapping();
+
+ void HandleSetListValue(GtkComboBox *pWidget, sal_Int16 nControlAction,
+ const css::uno::Any& rValue);
+ static css::uno::Any HandleGetListValue(GtkComboBox *pWidget, sal_Int16 nControlAction);
+
+ static void expander_changed_cb( GtkExpander *expander, SalGtkFilePicker *pobjFP );
+ static void preview_toggled_cb( GObject *cb, SalGtkFilePicker *pobjFP );
+ static void filter_changed_cb( GtkFileChooser *file_chooser, GParamSpec *pspec, SalGtkFilePicker *pobjFP );
+ static void type_changed_cb( GtkTreeSelection *selection, SalGtkFilePicker *pobjFP );
+ static void folder_changed_cb (GtkFileChooser *file_chooser, SalGtkFilePicker *pobjFP);
+ static void selection_changed_cb (GtkFileChooser *file_chooser, SalGtkFilePicker *pobjFP);
+ static void update_preview_cb (GtkFileChooser *file_chooser, SalGtkFilePicker *pobjFP);
+ static void dialog_mapped_cb(GtkWidget *widget, SalGtkFilePicker *pobjFP);
+ public:
+ virtual ~SalGtkFilePicker() override;
+
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx
new file mode 100644
index 0000000000..57b6e7c53e
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.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 <config_gio.h>
+
+#include <com/sun/star/awt/Toolkit.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+#include <vcl/svapp.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include "SalGtkFolderPicker.hxx"
+#include <sal/log.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+// constructor
+
+SalGtkFolderPicker::SalGtkFolderPicker( const uno::Reference< uno::XComponentContext >& xContext ) :
+ SalGtkPicker( xContext )
+{
+ m_pDialog = gtk_file_chooser_dialog_new(
+ OUStringToOString( getResString( FOLDERPICKER_TITLE ), RTL_TEXTENCODING_UTF8 ).getStr(),
+ nullptr, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, getCancelText().getStr(), GTK_RESPONSE_CANCEL,
+ getOKText().getStr(), GTK_RESPONSE_ACCEPT, nullptr );
+ gtk_window_set_modal(GTK_WINDOW(m_pDialog), true);
+
+ gtk_dialog_set_default_response( GTK_DIALOG (m_pDialog), GTK_RESPONSE_ACCEPT );
+#if !GTK_CHECK_VERSION(4, 0, 0)
+#if ENABLE_GIO
+ gtk_file_chooser_set_local_only( GTK_FILE_CHOOSER( m_pDialog ), false );
+#endif
+#endif
+ gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER( m_pDialog ), false );
+}
+
+void SAL_CALL SalGtkFolderPicker::setDisplayDirectory( const OUString& aDirectory )
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+ OString aTxt = unicodetouri( aDirectory );
+ if( aTxt.isEmpty() ){
+ aTxt = unicodetouri("file:///.");
+ }
+
+ if( aTxt.endsWith("/") )
+ aTxt = aTxt.copy( 0, aTxt.getLength() - 1 );
+
+ SAL_INFO( "vcl", "setting path to " << aTxt );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GFile* pPath = g_file_new_for_uri(aTxt.getStr());
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(m_pDialog), pPath, nullptr);
+ g_object_unref(pPath);
+#else
+ gtk_file_chooser_set_current_folder_uri(GTK_FILE_CHOOSER(m_pDialog), aTxt.getStr());
+#endif
+}
+
+OUString SAL_CALL SalGtkFolderPicker::getDisplayDirectory()
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GFile* pPath =
+ gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(m_pDialog));
+ gchar* pCurrentFolder = g_file_get_uri(pPath);
+ g_object_unref(pPath);
+#else
+ gchar* pCurrentFolder =
+ gtk_file_chooser_get_current_folder_uri(GTK_FILE_CHOOSER(m_pDialog));
+#endif
+
+ OUString aCurrentFolderName = uritounicode(pCurrentFolder);
+ g_free( pCurrentFolder );
+
+ return aCurrentFolderName;
+}
+
+OUString SAL_CALL SalGtkFolderPicker::getDirectory()
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GFile* pPath =
+ gtk_file_chooser_get_file(GTK_FILE_CHOOSER(m_pDialog));
+ gchar* pSelectedFolder = g_file_get_uri(pPath);
+ g_object_unref(pPath);
+#else
+ gchar* pSelectedFolder =
+ gtk_file_chooser_get_uri( GTK_FILE_CHOOSER( m_pDialog ) );
+#endif
+ OUString aSelectedFolderName = uritounicode(pSelectedFolder);
+ g_free( pSelectedFolder );
+
+ return aSelectedFolderName;
+}
+
+void SAL_CALL SalGtkFolderPicker::setDescription( const OUString& /*rDescription*/ )
+{
+}
+
+// XExecutableDialog functions
+
+void SAL_CALL SalGtkFolderPicker::setTitle( const OUString& aTitle )
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+ OString aWindowTitle = OUStringToOString( aTitle, RTL_TEXTENCODING_UTF8 );
+
+ gtk_window_set_title( GTK_WINDOW( m_pDialog ), aWindowTitle.getStr() );
+}
+
+sal_Int16 SAL_CALL SalGtkFolderPicker::execute()
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+ sal_Int16 retVal = 0;
+
+ uno::Reference< awt::XExtendedToolkit > xToolkit =
+ awt::Toolkit::create(m_xContext);
+
+ uno::Reference<frame::XDesktop> xDesktop = frame::Desktop::create(m_xContext);
+
+ GtkWindow *pParent = GTK_WINDOW(m_pParentWidget);
+ if (!pParent)
+ {
+ SAL_WARN( "vcl.gtk", "no parent widget set");
+ pParent = RunDialog::GetTransientFor();
+ }
+ if (pParent)
+ gtk_window_set_transient_for(GTK_WINDOW(m_pDialog), pParent);
+ rtl::Reference<RunDialog> pRunDialog = new RunDialog(m_pDialog, xToolkit, xDesktop);
+ gint nStatus = pRunDialog->run();
+ switch( nStatus )
+ {
+ case GTK_RESPONSE_ACCEPT:
+ retVal = ExecutableDialogResults::OK;
+ break;
+ case GTK_RESPONSE_CANCEL:
+ retVal = ExecutableDialogResults::CANCEL;
+ break;
+ default:
+ retVal = 0;
+ break;
+ }
+ gtk_widget_hide(m_pDialog);
+
+ return retVal;
+}
+
+// XInitialization
+
+void SAL_CALL SalGtkFolderPicker::initialize(const uno::Sequence<uno::Any>& aArguments)
+{
+ m_pParentWidget = GetParentWidget(aArguments);
+}
+
+// XCancellable
+
+void SAL_CALL SalGtkFolderPicker::cancel()
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+ // TODO m_pImpl->cancel();
+}
+
+uno::Reference< ui::dialogs::XFolderPicker2 >
+GtkInstance::createFolderPicker( const uno::Reference< uno::XComponentContext > &xMSF )
+{
+ return uno::Reference< ui::dialogs::XFolderPicker2 >(
+ new SalGtkFolderPicker( xMSF ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.hxx b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.hxx
new file mode 100644
index 0000000000..5b1d8dcebf
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.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 .
+ */
+
+#pragma once
+
+#include <memory>
+#include <rtl/ustring.hxx>
+#include <cppuhelper/implbase.hxx>
+
+#include "SalGtkPicker.hxx"
+
+class SalGtkFolderPicker :
+ public SalGtkPicker,
+ public cppu::WeakImplHelper<css::ui::dialogs::XFolderPicker2, css::lang::XInitialization>
+{
+ public:
+
+ // constructor
+ SalGtkFolderPicker( const css::uno::Reference< css::uno::XComponentContext >& xServiceMgr );
+
+ // XExecutableDialog functions
+
+ virtual void SAL_CALL setTitle( const OUString& aTitle ) override;
+
+ virtual sal_Int16 SAL_CALL execute( ) override;
+
+ // XFolderPicker functions
+
+ virtual void SAL_CALL setDisplayDirectory( const OUString& rDirectory ) override;
+
+ virtual OUString SAL_CALL getDisplayDirectory( ) override;
+
+ virtual OUString SAL_CALL getDirectory( ) override;
+
+ virtual void SAL_CALL setDescription( const OUString& rDescription ) override;
+
+ // XInitialization
+
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+ // XCancellable
+
+ virtual void SAL_CALL cancel( ) override;
+
+ private:
+ SalGtkFolderPicker( const SalGtkFolderPicker& ) = delete;
+ SalGtkFolderPicker& operator=( const SalGtkFolderPicker& ) = delete;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkPicker.cxx b/vcl/unx/gtk3/fpicker/SalGtkPicker.cxx
new file mode 100644
index 0000000000..9d07a11f76
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkPicker.cxx
@@ -0,0 +1,295 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/frame/TerminationVetoException.hpp>
+#include <com/sun/star/lang/XMultiComponentFactory.hpp>
+#include <com/sun/star/uri/ExternalUriReferenceTranslator.hpp>
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <tools/urlobj.hxx>
+
+#include <vcl/window.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include <unx/gtk/gtkframe.hxx>
+#include "SalGtkPicker.hxx"
+
+using namespace ::rtl;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+OUString SalGtkPicker::uritounicode(const gchar* pIn) const
+{
+ if (!pIn)
+ return OUString();
+
+ OUString sURL( pIn, strlen(pIn),
+ RTL_TEXTENCODING_UTF8 );
+
+ INetURLObject aURL(sURL);
+ if (INetProtocol::File == aURL.GetProtocol())
+ {
+ // all the URLs are handled by office in UTF-8
+ // so the Gnome FP related URLs should be converted accordingly
+ OUString aNewURL = uri::ExternalUriReferenceTranslator::create( m_xContext )->translateToInternal(sURL);
+ if( !aNewURL.isEmpty() )
+ sURL = aNewURL;
+ }
+ return sURL;
+}
+
+OString SalGtkPicker::unicodetouri(const OUString &rURL) const
+{
+ // all the URLs are handled by office in UTF-8 ( and encoded with "%xx" codes based on UTF-8 )
+ // so the Gnome FP related URLs should be converted accordingly
+ OString sURL = OUStringToOString(rURL, RTL_TEXTENCODING_UTF8);
+ INetURLObject aURL(rURL);
+ if (INetProtocol::File == aURL.GetProtocol())
+ {
+ OUString aNewURL = uri::ExternalUriReferenceTranslator::create( m_xContext )->translateToExternal(rURL);
+
+ if( !aNewURL.isEmpty() )
+ {
+ // At this point the URL should contain ascii characters only actually
+ sURL = OUStringToOString( aNewURL, osl_getThreadTextEncoding() );
+ }
+ }
+ return sURL;
+}
+
+extern "C"
+{
+ static gboolean canceldialog(RunDialog *pDialog)
+ {
+ SolarMutexGuard g;
+ pDialog->cancel();
+ return false;
+ }
+}
+
+GtkWindow* RunDialog::GetTransientFor()
+{
+ vcl::Window * pWindow = ::Application::GetActiveTopWindow();
+ if (!pWindow)
+ return nullptr;
+ GtkSalFrame *pFrame = dynamic_cast<GtkSalFrame*>(pWindow->ImplGetFrame());
+ if (!pFrame)
+ return nullptr;
+ return GTK_WINDOW(widget_get_toplevel(pFrame->getWindow()));
+}
+
+RunDialog::RunDialog(GtkWidget *pDialog, uno::Reference<awt::XExtendedToolkit> xToolkit,
+ uno::Reference<frame::XDesktop> xDesktop)
+ : cppu::WeakComponentImplHelper<awt::XTopWindowListener, frame::XTerminateListener>(maLock)
+ , mpDialog(pDialog)
+ , mbTerminateDesktop(false)
+ , mxToolkit(std::move(xToolkit))
+ , mxDesktop(std::move(xDesktop))
+{
+}
+
+RunDialog::~RunDialog()
+{
+ SolarMutexGuard g;
+
+ g_source_remove_by_user_data (this);
+}
+
+void SAL_CALL RunDialog::windowOpened(const css::lang::EventObject& e)
+{
+ SolarMutexGuard g;
+
+ //Don't popdown dialogs if a tooltip appears elsewhere, that's ok, but do pop down
+ //if another dialog/frame is launched.
+ css::uno::Reference<css::accessibility::XAccessible> xAccessible(e.Source, css::uno::UNO_QUERY);
+ if (xAccessible.is())
+ {
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext(xAccessible->getAccessibleContext());
+ if (xContext.is() && xContext->getAccessibleRole() == css::accessibility::AccessibleRole::TOOL_TIP)
+ {
+ return;
+ }
+ }
+
+ g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(canceldialog), this, nullptr);
+}
+
+void SAL_CALL RunDialog::queryTermination( const css::lang::EventObject& )
+{
+ SolarMutexGuard g;
+
+ g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(canceldialog), this, nullptr);
+
+ mbTerminateDesktop = true;
+
+ throw css::frame::TerminationVetoException();
+}
+
+void SAL_CALL RunDialog::notifyTermination( const css::lang::EventObject& )
+{
+}
+
+void RunDialog::cancel()
+{
+ gtk_dialog_response( GTK_DIALOG( mpDialog ), GTK_RESPONSE_CANCEL );
+ gtk_widget_hide( mpDialog );
+}
+
+namespace
+{
+ class ExecuteInfo
+ {
+ private:
+ css::uno::Reference<css::frame::XDesktop> mxDesktop;
+ public:
+ ExecuteInfo(css::uno::Reference<css::frame::XDesktop> xDesktop)
+ : mxDesktop(std::move(xDesktop))
+ {
+ }
+ void terminate()
+ {
+ mxDesktop->terminate();
+ }
+ };
+}
+
+gint RunDialog::run()
+{
+ if (mxToolkit.is())
+ mxToolkit->addTopWindowListener(this);
+
+ mxDesktop->addTerminateListener(this);
+
+ // [Inc/Dec]ModalCount on parent frame so it knows it is in modal mode
+ GtkWindow* pParent = gtk_window_get_transient_for(GTK_WINDOW(mpDialog));
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(GTK_WIDGET(pParent)) : nullptr;
+ VclPtr<vcl::Window> xFrameWindow = pFrame ? pFrame->GetWindow() : nullptr;
+ if (xFrameWindow)
+ {
+ xFrameWindow->IncModalCount();
+ xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(true);
+ }
+
+ gint nStatus = gtk_dialog_run(GTK_DIALOG(mpDialog));
+
+ if (xFrameWindow)
+ {
+ xFrameWindow->DecModalCount();
+ xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(false);
+ }
+
+ mxDesktop->removeTerminateListener(this);
+
+ if (mxToolkit.is())
+ mxToolkit->removeTopWindowListener(this);
+
+ if (mbTerminateDesktop)
+ {
+ ExecuteInfo* pExecuteInfo = new ExecuteInfo(mxDesktop);
+ Application::PostUserEvent(LINK(nullptr, RunDialog, TerminateDesktop), pExecuteInfo);
+ }
+
+ return nStatus;
+}
+
+IMPL_STATIC_LINK(RunDialog, TerminateDesktop, void*, p, void)
+{
+ ExecuteInfo* pExecuteInfo = static_cast<ExecuteInfo*>(p);
+ pExecuteInfo->terminate();
+ delete pExecuteInfo;
+}
+
+SalGtkPicker::SalGtkPicker( uno::Reference<uno::XComponentContext> xContext )
+ : m_pParentWidget(nullptr)
+ , m_pDialog(nullptr)
+ , m_xContext(std::move(xContext))
+{
+}
+
+SalGtkPicker::~SalGtkPicker()
+{
+ SolarMutexGuard g;
+
+ if (m_pDialog)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(m_pDialog);
+#else
+ gtk_window_destroy(GTK_WINDOW(m_pDialog));
+#endif
+ }
+}
+
+void SalGtkPicker::implsetDisplayDirectory( const OUString& aDirectory )
+{
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ OString aTxt = unicodetouri(aDirectory);
+ if( aTxt.isEmpty() ){
+ aTxt = unicodetouri("file:///.");
+ }
+
+ if( aTxt.endsWith("/") )
+ aTxt = aTxt.copy( 0, aTxt.getLength() - 1 );
+
+ SAL_INFO( "vcl", "setting path to " << aTxt );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GFile* pPath = g_file_new_for_uri(aTxt.getStr());
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(m_pDialog), pPath, nullptr);
+ g_object_unref(pPath);
+#else
+ gtk_file_chooser_set_current_folder_uri(GTK_FILE_CHOOSER(m_pDialog), aTxt.getStr());
+#endif
+}
+
+OUString SalGtkPicker::implgetDisplayDirectory()
+{
+ OSL_ASSERT( m_pDialog != nullptr );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GFile* pPath =
+ gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(m_pDialog));
+ gchar* pCurrentFolder = g_file_get_uri(pPath);
+ g_object_unref(pPath);
+#else
+ gchar* pCurrentFolder =
+ gtk_file_chooser_get_current_folder_uri(GTK_FILE_CHOOSER(m_pDialog));
+#endif
+ OUString aCurrentFolderName = uritounicode(pCurrentFolder);
+ g_free( pCurrentFolder );
+
+ return aCurrentFolderName;
+}
+
+void SalGtkPicker::implsetTitle( std::u16string_view aTitle )
+{
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ OString aWindowTitle = OUStringToOString( aTitle, RTL_TEXTENCODING_UTF8 );
+
+ gtk_window_set_title( GTK_WINDOW( m_pDialog ), aWindowTitle.getStr() );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkPicker.hxx b/vcl/unx/gtk3/fpicker/SalGtkPicker.hxx
new file mode 100644
index 0000000000..cb11445dae
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkPicker.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <osl/mutex.hxx>
+#include <tools/link.hxx>
+#include <cppuhelper/compbase.hxx>
+
+#include <com/sun/star/awt/XTopWindowListener.hpp>
+#include <com/sun/star/awt/XExtendedToolkit.hpp>
+#include <com/sun/star/frame/XDesktop.hpp>
+#include <com/sun/star/frame/XTerminateListener.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <strings.hrc>
+#include <svdata.hxx>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#define FOLDERPICKER_TITLE 500
+#define FOLDER_PICKER_DEF_DESCRIPTION 501
+#define FILE_PICKER_TITLE_OPEN 502
+#define FILE_PICKER_TITLE_SAVE 503
+#define FILE_PICKER_FILE_TYPE 504
+#define FILE_PICKER_OVERWRITE_PRIMARY 505
+#define FILE_PICKER_OVERWRITE_SECONDARY 506
+#define FILE_PICKER_ALLFORMATS 507
+
+class SalGtkPicker
+{
+ public:
+ SalGtkPicker( css::uno::Reference<css::uno::XComponentContext> xContext );
+ virtual ~SalGtkPicker();
+ protected:
+ osl::Mutex m_rbHelperMtx;
+ GtkWidget *m_pParentWidget;
+ GtkWidget *m_pDialog;
+ protected:
+ /// @throws css::uno::RuntimeException
+ void implsetTitle( std::u16string_view aTitle );
+
+ /// @throws css::lang::IllegalArgumentException
+ /// @throws css::uno::RuntimeException
+ void implsetDisplayDirectory( const OUString& rDirectory );
+
+ /// @throws css::uno::RuntimeException
+ OUString implgetDisplayDirectory( );
+ OUString uritounicode(const gchar *pIn) const;
+ OString unicodetouri(const OUString &rURL) const;
+
+ // to instantiate own services
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+ static GtkWidget* GetParentWidget(const css::uno::Sequence<css::uno::Any>& rArguments);
+
+ static OUString getResString( sal_Int32 aId );
+};
+
+//Run the Gtk Dialog. Watch for any "new windows" created while we're
+//executing and consider that a CANCEL event to avoid e.g. "file cannot be opened"
+//modal dialogs and this one getting locked if some other API call causes this
+//to happen while we're opened waiting for user input, e.g.
+//https://bugzilla.redhat.com/show_bug.cgi?id=441108
+class RunDialog :
+ public cppu::WeakComponentImplHelper<
+ css::awt::XTopWindowListener,
+ css::frame::XTerminateListener >
+{
+private:
+ osl::Mutex maLock;
+ GtkWidget *mpDialog;
+ bool mbTerminateDesktop;
+ css::uno::Reference<css::awt::XExtendedToolkit> mxToolkit;
+ css::uno::Reference<css::frame::XDesktop> mxDesktop;
+ DECL_STATIC_LINK(RunDialog, TerminateDesktop, void*, void);
+public:
+
+ // XTopWindowListener
+ using cppu::WeakComponentImplHelperBase::disposing;
+ virtual void SAL_CALL disposing( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowOpened( const css::lang::EventObject& e ) override;
+ virtual void SAL_CALL windowClosing( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowClosed( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowMinimized( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowNormalized( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowActivated( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowDeactivated( const css::lang::EventObject& ) override {}
+
+ // XTerminateListener
+ virtual void SAL_CALL queryTermination( const css::lang::EventObject& aEvent ) override;
+ virtual void SAL_CALL notifyTermination( const css::lang::EventObject& aEvent ) override;
+public:
+ RunDialog(GtkWidget *pDialog,
+ css::uno::Reference<css::awt::XExtendedToolkit> xToolkit,
+ css::uno::Reference<css::frame::XDesktop> xDesktop);
+ virtual ~RunDialog() override;
+ gint run();
+ void cancel();
+ static GtkWindow* GetTransientFor();
+};
+
+inline OString getCancelText()
+{
+ return VclResId(SV_BUTTONTEXT_CANCEL).replace('~', '_').toUtf8();
+}
+
+inline OString getOpenText()
+{
+ return VclResId(SV_BUTTONTEXT_OPEN).replace('~', '_').toUtf8();
+}
+
+inline OString getSaveText()
+{
+ return VclResId(SV_BUTTONTEXT_SAVE).replace('~', '_').toUtf8();
+}
+
+inline OString getOKText()
+{
+ return VclResId(SV_BUTTONTEXT_OK).replace('~', '_').toUtf8();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/eventnotification.hxx b/vcl/unx/gtk3/fpicker/eventnotification.hxx
new file mode 100644
index 0000000000..214da6da9b
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/eventnotification.hxx
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <com/sun/star/uno/XInterface.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+
+// encapsulate a filepicker event
+// notification, because there are
+// two types of filepicker notifications
+// with and without parameter
+// this is an application of the
+// "command" pattern see GoF
+
+class CEventNotification
+{
+public:
+ virtual ~CEventNotification(){};
+
+ virtual void SAL_CALL notifyEventListener(css::uno::Reference<css::uno::XInterface> xListener)
+ = 0;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/resourceprovider.cxx b/vcl/unx/gtk3/fpicker/resourceprovider.cxx
new file mode 100644
index 0000000000..fa90b81266
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/resourceprovider.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 <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp>
+#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
+
+#include <fpicker/strings.hrc>
+#include <fpicker/fpsofficeResMgr.hxx>
+#include "SalGtkPicker.hxx"
+
+using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds;
+using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds;
+
+// translate control ids to resource ids
+
+const struct
+{
+ sal_Int32 ctrlId;
+ TranslateId resId;
+} CtrlIdToResIdTable[] = {
+ { CHECKBOX_AUTOEXTENSION, STR_SVT_FILEPICKER_AUTO_EXTENSION },
+ { CHECKBOX_PASSWORD, STR_SVT_FILEPICKER_PASSWORD },
+ { CHECKBOX_GPGENCRYPTION, STR_SVT_FILEPICKER_GPGENCRYPT },
+ { CHECKBOX_FILTEROPTIONS, STR_SVT_FILEPICKER_FILTER_OPTIONS },
+ { CHECKBOX_READONLY, STR_SVT_FILEPICKER_READONLY },
+ { CHECKBOX_LINK, STR_SVT_FILEPICKER_INSERT_AS_LINK },
+ { CHECKBOX_PREVIEW, STR_SVT_FILEPICKER_SHOW_PREVIEW },
+ { PUSHBUTTON_PLAY, STR_SVT_FILEPICKER_PLAY },
+ { LISTBOX_VERSION_LABEL, STR_SVT_FILEPICKER_VERSION },
+ { LISTBOX_TEMPLATE_LABEL, STR_SVT_FILEPICKER_TEMPLATES },
+ { LISTBOX_IMAGE_TEMPLATE_LABEL, STR_SVT_FILEPICKER_IMAGE_TEMPLATE },
+ { LISTBOX_IMAGE_ANCHOR_LABEL, STR_SVT_FILEPICKER_IMAGE_ANCHOR },
+ { CHECKBOX_SELECTION, STR_SVT_FILEPICKER_SELECTION },
+ { FOLDERPICKER_TITLE, STR_SVT_FOLDERPICKER_DEFAULT_TITLE },
+ { FOLDER_PICKER_DEF_DESCRIPTION, STR_SVT_FOLDERPICKER_DEFAULT_DESCRIPTION },
+ { FILE_PICKER_OVERWRITE_PRIMARY, STR_SVT_ALREADYEXISTOVERWRITE },
+ { FILE_PICKER_OVERWRITE_SECONDARY, STR_SVT_ALREADYEXISTOVERWRITE_SECONDARY },
+ { FILE_PICKER_ALLFORMATS, STR_SVT_ALLFORMATS },
+ { FILE_PICKER_TITLE_OPEN, STR_FILEDLG_OPEN },
+ { FILE_PICKER_TITLE_SAVE, STR_FILEDLG_SAVE },
+ { FILE_PICKER_FILE_TYPE, STR_FILEDLG_TYPE }
+};
+
+static TranslateId CtrlIdToResId( sal_Int32 aControlId )
+{
+ for (auto & i : CtrlIdToResIdTable)
+ {
+ if ( i.ctrlId == aControlId )
+ return i.resId;
+ }
+ return {};
+}
+
+OUString SalGtkPicker::getResString( sal_Int32 aId )
+{
+ OUString aResString;
+ // translate the control id to a resource id
+ TranslateId pResId = CtrlIdToResId( aId );
+ if (pResId)
+ aResString = FpsResId(pResId);
+ return aResString.replace('~', '_');
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gloactiongroup.cxx b/vcl/unx/gtk3/gloactiongroup.cxx
new file mode 100644
index 0000000000..1b64da0ffa
--- /dev/null
+++ b/vcl/unx/gtk3/gloactiongroup.cxx
@@ -0,0 +1,405 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <unx/gtk/gtksalmenu.hxx>
+
+#include <unx/gtk/gloactiongroup.h>
+
+#include <sal/log.hxx>
+
+/*
+ * GLOAction
+ */
+
+#define G_TYPE_LO_ACTION (g_lo_action_get_type ())
+#define G_LO_ACTION(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
+ G_TYPE_LO_ACTION, GLOAction))
+namespace {
+
+struct GLOAction
+{
+ GObject parent_instance;
+
+ gint item_id; // Menu item ID.
+ bool submenu; // TRUE if action is a submenu action.
+ bool enabled; // TRUE if action is enabled.
+ GVariantType* parameter_type; // A GVariantType with the action parameter type.
+ GVariantType* state_type; // A GVariantType with item state type
+ GVariant* state_hint; // A GVariant with state hints.
+ GVariant* state; // A GVariant with current item state
+};
+
+}
+
+typedef GObjectClass GLOActionClass;
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
+#if defined __clang__
+#if __has_warning("-Wdeprecated-volatile")
+#pragma clang diagnostic ignored "-Wdeprecated-volatile"
+#endif
+#endif
+#endif
+G_DEFINE_TYPE (GLOAction, g_lo_action, G_TYPE_OBJECT);
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+static GLOAction*
+g_lo_action_new()
+{
+ return G_LO_ACTION (g_object_new (G_TYPE_LO_ACTION, nullptr));
+}
+
+static void
+g_lo_action_init (GLOAction *action)
+{
+ action->item_id = -1;
+ action->submenu = false;
+ action->enabled = true;
+ action->parameter_type = nullptr;
+ action->state_type = nullptr;
+ action->state_hint = nullptr;
+ action->state = nullptr;
+}
+
+static void
+g_lo_action_finalize (GObject *object)
+{
+ GLOAction* action = G_LO_ACTION(object);
+
+ if (action->parameter_type)
+ g_variant_type_free (action->parameter_type);
+
+ if (action->state_type)
+ g_variant_type_free (action->state_type);
+
+ if (action->state_hint)
+ g_variant_unref (action->state_hint);
+
+ if (action->state)
+ g_variant_unref (action->state);
+
+ G_OBJECT_CLASS (g_lo_action_parent_class)->finalize (object);
+}
+
+static void
+g_lo_action_class_init (GLOActionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->finalize = g_lo_action_finalize;
+}
+
+/*
+ * GLOActionGroup
+ */
+
+struct GLOActionGroupPrivate
+{
+ GHashTable *table; /* string -> GLOAction */
+};
+
+static void g_lo_action_group_iface_init (GActionGroupInterface *);
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
+#if defined __clang__
+#if __has_warning("-Wdeprecated-volatile")
+#pragma clang diagnostic ignored "-Wdeprecated-volatile"
+#endif
+#endif
+#endif
+G_DEFINE_TYPE_WITH_CODE (GLOActionGroup,
+ g_lo_action_group, G_TYPE_OBJECT,
+ G_ADD_PRIVATE(GLOActionGroup)
+ G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP,
+ g_lo_action_group_iface_init));
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+static gchar **
+g_lo_action_group_list_actions (GActionGroup *group)
+{
+ GLOActionGroup *loGroup = G_LO_ACTION_GROUP (group);
+ GHashTableIter iter;
+ gint n, i = 0;
+ gchar **keys;
+ gpointer key;
+
+ n = g_hash_table_size (loGroup->priv->table);
+ keys = g_new (gchar *, n + 1);
+
+ g_hash_table_iter_init (&iter, loGroup->priv->table);
+ while (g_hash_table_iter_next (&iter, &key, nullptr))
+ keys[i++] = g_strdup (static_cast<gchar*>(key));
+ g_assert_cmpint (i, ==, n);
+ keys[n] = nullptr;
+
+ return keys;
+}
+
+static gboolean
+g_lo_action_group_query_action (GActionGroup *group,
+ const gchar *action_name,
+ gboolean *enabled,
+ const GVariantType **parameter_type,
+ const GVariantType **state_type,
+ GVariant **state_hint,
+ GVariant **state)
+{
+ //SAL_INFO("vcl.unity", "g_lo_action_group_query_action on " << group);
+ GLOActionGroup *lo_group = G_LO_ACTION_GROUP (group);
+ GLOAction* action = G_LO_ACTION (g_hash_table_lookup (lo_group->priv->table, action_name));
+
+ if (action == nullptr)
+ return FALSE;
+
+ if (enabled)
+ {
+ *enabled = action->enabled;
+ }
+
+ if (parameter_type)
+ *parameter_type = action->parameter_type;
+
+ if (state_type)
+ *state_type = action->state_type;
+
+ if (state_hint)
+ *state_hint = (action->state_hint) ? g_variant_ref (action->state_hint) : nullptr;
+
+ if (state)
+ *state = (action->state) ? g_variant_ref (action->state) : nullptr;
+
+ return true;
+}
+
+static void
+g_lo_action_group_perform_submenu_action (GLOActionGroup *group,
+ const gchar *action_name,
+ GVariant *state)
+{
+ bool bState = g_variant_get_boolean (state);
+ SAL_INFO("vcl.unity", "g_lo_action_group_perform_submenu_action on " << group << " to " << bState);
+
+ if (bState)
+ GtkSalMenu::Activate(action_name);
+ else
+ GtkSalMenu::Deactivate(action_name);
+}
+
+static void
+g_lo_action_group_change_state (GActionGroup *group,
+ const gchar *action_name,
+ GVariant *value)
+{
+ SAL_INFO("vcl.unity", "g_lo_action_group_change_state on " << group );
+ g_return_if_fail (value != nullptr);
+
+ g_variant_ref_sink (value);
+
+ if (action_name != nullptr)
+ {
+ GLOActionGroup* lo_group = G_LO_ACTION_GROUP (group);
+ GLOAction* action = G_LO_ACTION (g_hash_table_lookup (lo_group->priv->table, action_name));
+
+ if (action != nullptr)
+ {
+ if (action->submenu)
+ g_lo_action_group_perform_submenu_action (lo_group, action_name, value);
+ else
+ {
+ bool is_new = false;
+
+ /* If action already exists but has no state, it should be removed and added again. */
+ if (action->state_type == nullptr)
+ {
+ g_action_group_action_removed (G_ACTION_GROUP (group), action_name);
+ action->state_type = g_variant_type_copy (g_variant_get_type(value));
+ is_new = true;
+ }
+
+ if (g_variant_is_of_type (value, action->state_type))
+ {
+ if (action->state)
+ g_variant_unref(action->state);
+
+ action->state = g_variant_ref (value);
+
+ if (is_new)
+ g_action_group_action_added (G_ACTION_GROUP (group), action_name);
+ else
+ g_action_group_action_state_changed (group, action_name, value);
+ }
+ }
+ }
+ }
+
+ g_variant_unref (value);
+}
+
+static void
+g_lo_action_group_activate (GActionGroup *group,
+ const gchar *action_name,
+ GVariant *parameter)
+{
+ if (parameter != nullptr)
+ g_action_group_change_action_state(group, action_name, parameter);
+ GtkSalMenu::DispatchCommand(action_name);
+}
+
+void
+g_lo_action_group_insert (GLOActionGroup *group,
+ const gchar *action_name,
+ gint item_id,
+ gboolean submenu)
+{
+ g_lo_action_group_insert_stateful (group, action_name, item_id, submenu, nullptr, nullptr, nullptr, nullptr);
+}
+
+void
+g_lo_action_group_insert_stateful (GLOActionGroup *group,
+ const gchar *action_name,
+ gint item_id,
+ gboolean submenu,
+ const GVariantType *parameter_type,
+ const GVariantType *state_type,
+ GVariant *state_hint,
+ GVariant *state)
+{
+ g_return_if_fail (G_IS_LO_ACTION_GROUP (group));
+
+ GLOAction* old_action = G_LO_ACTION (g_hash_table_lookup (group->priv->table, action_name));
+
+ if (old_action != nullptr && old_action->item_id == item_id)
+ return;
+
+ if (old_action != nullptr)
+ g_lo_action_group_remove (group, action_name);
+
+ GLOAction* action = g_lo_action_new();
+
+ g_hash_table_insert (group->priv->table, g_strdup (action_name), action);
+
+ action->item_id = item_id;
+ action->submenu = submenu;
+
+ if (parameter_type)
+ action->parameter_type = const_cast<GVariantType*>(parameter_type);
+
+ if (state_type)
+ action->state_type = const_cast<GVariantType*>(state_type);
+
+ if (state_hint)
+ action->state_hint = g_variant_ref_sink (state_hint);
+
+ if (state)
+ action->state = g_variant_ref_sink (state);
+
+ g_action_group_action_added (G_ACTION_GROUP (group), action_name);
+}
+
+static void
+g_lo_action_group_finalize (GObject *object)
+{
+ GLOActionGroup *lo_group = G_LO_ACTION_GROUP (object);
+
+ g_hash_table_unref (lo_group->priv->table);
+
+ G_OBJECT_CLASS (g_lo_action_group_parent_class)->finalize (object);
+}
+
+static void
+g_lo_action_group_init (GLOActionGroup *group)
+{
+ SAL_INFO("vcl.unity", "g_lo_action_group_init on " << group);
+ group->priv = static_cast<GLOActionGroupPrivate *>(g_lo_action_group_get_instance_private (group));
+ group->priv->table = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+}
+
+static void
+g_lo_action_group_class_init (GLOActionGroupClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = g_lo_action_group_finalize;
+}
+
+static void
+g_lo_action_group_iface_init (GActionGroupInterface *iface)
+{
+ iface->list_actions = g_lo_action_group_list_actions;
+ iface->query_action = g_lo_action_group_query_action;
+ iface->change_action_state = g_lo_action_group_change_state;
+ iface->activate_action = g_lo_action_group_activate;
+}
+
+GLOActionGroup *
+g_lo_action_group_new()
+{
+ GLOActionGroup* group = G_LO_ACTION_GROUP (g_object_new (G_TYPE_LO_ACTION_GROUP, nullptr));
+ return group;
+}
+
+void
+g_lo_action_group_set_action_enabled (GLOActionGroup *group,
+ const gchar *action_name,
+ gboolean enabled)
+{
+ SAL_INFO("vcl.unity", "g_lo_action_group_set_action_enabled on " << group);
+ g_return_if_fail (G_IS_LO_ACTION_GROUP (group));
+ g_return_if_fail (action_name != nullptr);
+
+ GLOAction* action = G_LO_ACTION (g_hash_table_lookup (group->priv->table, action_name));
+
+ if (action == nullptr)
+ return;
+
+ action->enabled = enabled;
+
+ g_action_group_action_enabled_changed (G_ACTION_GROUP (group), action_name, enabled);
+}
+
+void
+g_lo_action_group_remove (GLOActionGroup *group,
+ const gchar *action_name)
+{
+ SAL_INFO("vcl.unity", "g_lo_action_group_remove on " << group);
+ g_return_if_fail (G_IS_LO_ACTION_GROUP (group));
+
+ if (action_name != nullptr)
+ {
+ g_action_group_action_removed (G_ACTION_GROUP (group), action_name);
+ g_hash_table_remove (group->priv->table, action_name);
+ }
+}
+
+void
+g_lo_action_group_clear (GLOActionGroup *group)
+{
+ SAL_INFO("vcl.unity", "g_lo_action_group_clear on " << group);
+ g_return_if_fail (G_IS_LO_ACTION_GROUP (group));
+
+ GList* keys = g_hash_table_get_keys (group->priv->table);
+
+ for (GList* element = g_list_first (keys); element != nullptr; element = g_list_next (element))
+ {
+ g_lo_action_group_remove (group, static_cast<gchar*>(element->data));
+ }
+
+ g_list_free (keys);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/glomenu.cxx b/vcl/unx/gtk3/glomenu.cxx
new file mode 100644
index 0000000000..a391649bbb
--- /dev/null
+++ b/vcl/unx/gtk3/glomenu.cxx
@@ -0,0 +1,694 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <o3tl/safeint.hxx>
+
+#include <unx/gtk/glomenu.h>
+
+struct GLOMenu
+{
+ GMenuModel const parent_instance;
+
+ GArray *items;
+};
+
+typedef GMenuModelClass GLOMenuClass;
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
+#if defined __clang__
+#if __has_warning("-Wdeprecated-volatile")
+#pragma clang diagnostic ignored "-Wdeprecated-volatile"
+#endif
+#endif
+#endif
+G_DEFINE_TYPE (GLOMenu, g_lo_menu, G_TYPE_MENU_MODEL);
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+namespace {
+
+struct item
+{
+ GHashTable* attributes; // Item attributes.
+ GHashTable* links; // Item links.
+};
+
+}
+
+static void
+g_lo_menu_struct_item_init (struct item *menu_item)
+{
+ menu_item->attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, reinterpret_cast<GDestroyNotify>(g_variant_unref));
+ menu_item->links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+}
+
+/* We treat attribute names the same as GSettings keys:
+ * - only lowercase ascii, digits and '-'
+ * - must start with lowercase
+ * - must not end with '-'
+ * - no consecutive '-'
+ * - not longer than 1024 chars
+ */
+static bool
+valid_attribute_name (const gchar *name)
+{
+ gint i;
+
+ if (!g_ascii_islower (name[0]))
+ return false;
+
+ for (i = 1; name[i]; i++)
+ {
+ if (name[i] != '-' &&
+ !g_ascii_islower (name[i]) &&
+ !g_ascii_isdigit (name[i]))
+ return false;
+
+ if (name[i] == '-' && name[i + 1] == '-')
+ return false;
+ }
+
+ if (name[i - 1] == '-')
+ return false;
+
+ if (i > 1024)
+ return false;
+
+ return true;
+}
+
+/*
+ * GLOMenu
+ */
+
+static gboolean
+g_lo_menu_is_mutable (GMenuModel*)
+{
+ // Menu is always mutable.
+ return true;
+}
+
+static gint
+g_lo_menu_get_n_items (GMenuModel *model)
+{
+ g_return_val_if_fail (model != nullptr, 0);
+ GLOMenu *menu = G_LO_MENU (model);
+ g_return_val_if_fail (menu->items != nullptr, 0);
+
+ return menu->items->len;
+}
+
+gint
+g_lo_menu_get_n_items_from_section (GLOMenu *menu,
+ gint section)
+{
+ g_return_val_if_fail (0 <= section && o3tl::make_unsigned(section) < menu->items->len, 0);
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_val_if_fail (model != nullptr, 0);
+
+ gint length = model->items->len;
+
+ g_object_unref (model);
+
+ return length;
+}
+
+static void
+g_lo_menu_get_item_attributes (GMenuModel *model,
+ gint position,
+ GHashTable **table)
+{
+ GLOMenu *menu = G_LO_MENU (model);
+ *table = g_hash_table_ref (g_array_index (menu->items, struct item, position).attributes);
+}
+
+static void
+g_lo_menu_get_item_links (GMenuModel *model,
+ gint position,
+ GHashTable **table)
+{
+ GLOMenu *menu = G_LO_MENU (model);
+ *table = g_hash_table_ref (g_array_index (menu->items, struct item, position).links);
+}
+
+void
+g_lo_menu_insert (GLOMenu *menu,
+ gint position,
+ const gchar *label)
+{
+ g_lo_menu_insert_section (menu, position, label, nullptr);
+}
+
+void
+g_lo_menu_insert_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *label)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+ g_return_if_fail (0 <= section && o3tl::make_unsigned(section) < menu->items->len);
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ g_lo_menu_insert (model, position, label);
+
+ g_object_unref (model);
+}
+
+GLOMenu *
+g_lo_menu_new()
+{
+ return G_LO_MENU( g_object_new (G_TYPE_LO_MENU, nullptr) );
+}
+
+static void
+g_lo_menu_set_attribute_value (GLOMenu *menu,
+ gint position,
+ const gchar *attribute,
+ GVariant *value)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+ g_return_if_fail (attribute != nullptr);
+ g_return_if_fail (valid_attribute_name (attribute));
+
+ if (position >= static_cast<gint>(menu->items->len))
+ return;
+
+ struct item menu_item = g_array_index (menu->items, struct item, position);
+
+ if (value != nullptr)
+ g_hash_table_insert (menu_item.attributes, g_strdup (attribute), g_variant_ref_sink (value));
+ else
+ g_hash_table_remove (menu_item.attributes, attribute);
+}
+
+static GVariant*
+g_lo_menu_get_attribute_value_from_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *attribute,
+ const GVariantType *type)
+{
+ GMenuModel *model = G_MENU_MODEL (g_lo_menu_get_section (menu, section));
+
+ g_return_val_if_fail (model != nullptr, nullptr);
+
+ GVariant *value = g_menu_model_get_item_attribute_value (model,
+ position,
+ attribute,
+ type);
+
+ g_object_unref (model);
+
+ return value;
+}
+
+void
+g_lo_menu_set_label (GLOMenu *menu,
+ gint position,
+ const gchar *label)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GVariant *value;
+
+ if (label != nullptr)
+ value = g_variant_new_string (label);
+ else
+ value = nullptr;
+
+ g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_LABEL, value);
+}
+
+void
+g_lo_menu_set_icon (GLOMenu *menu,
+ gint position,
+ const GIcon *icon)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GVariant *value;
+
+ if (icon != nullptr)
+ {
+#if GLIB_CHECK_VERSION(2,38,0)
+ value = g_icon_serialize (const_cast<GIcon*>(icon));
+#else
+ value = nullptr;
+#endif
+ }
+ else
+ value = nullptr;
+
+#ifndef G_MENU_ATTRIBUTE_ICON
+# define G_MENU_ATTRIBUTE_ICON "icon"
+#endif
+
+ g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_ICON, value);
+ if (value)
+ g_variant_unref (value);
+}
+
+void
+g_lo_menu_set_label_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *label)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ g_lo_menu_set_label (model, position, label);
+
+ // Notify the update.
+ g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);
+
+ g_object_unref (model);
+}
+
+void
+g_lo_menu_set_icon_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const GIcon *icon)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ g_lo_menu_set_icon (model, position, icon);
+
+ // Notify the update.
+ g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);
+
+ g_object_unref (model);
+}
+
+gchar *
+g_lo_menu_get_label_from_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position)
+{
+ g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);
+
+ GVariant *label_value = g_lo_menu_get_attribute_value_from_item_in_section (menu,
+ section,
+ position,
+ G_MENU_ATTRIBUTE_LABEL,
+ G_VARIANT_TYPE_STRING);
+
+ gchar *label = nullptr;
+
+ if (label_value)
+ {
+ label = g_variant_dup_string (label_value, nullptr);
+ g_variant_unref (label_value);
+ }
+
+ return label;
+}
+
+void
+g_lo_menu_set_action_and_target_value (GLOMenu *menu,
+ gint position,
+ const gchar *action,
+ GVariant *target_value)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GVariant *action_value;
+
+ if (action != nullptr)
+ {
+ action_value = g_variant_new_string (action);
+ }
+ else
+ {
+ action_value = nullptr;
+ target_value = nullptr;
+ }
+
+ g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_ACTION, action_value);
+ g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_TARGET, target_value);
+ g_lo_menu_set_attribute_value (menu, position, G_LO_MENU_ATTRIBUTE_SUBMENU_ACTION, nullptr);
+
+ g_menu_model_items_changed (G_MENU_MODEL (menu), position, 1, 1);
+}
+
+void
+g_lo_menu_set_action_and_target_value_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *command,
+ GVariant *target_value)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ g_lo_menu_set_action_and_target_value (model, position, command, target_value);
+
+ g_object_unref (model);
+}
+
+void
+g_lo_menu_set_accelerator_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *accelerator)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ GVariant *value;
+
+ if (accelerator != nullptr)
+ value = g_variant_new_string (accelerator);
+ else
+ value = nullptr;
+
+ g_lo_menu_set_attribute_value (model, position, G_LO_MENU_ATTRIBUTE_ACCELERATOR, value);
+
+ // Notify the update.
+ g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);
+
+ g_object_unref (model);
+}
+
+gchar *
+g_lo_menu_get_accelerator_from_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position)
+{
+ g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);
+
+ GVariant *accel_value = g_lo_menu_get_attribute_value_from_item_in_section (menu,
+ section,
+ position,
+ G_LO_MENU_ATTRIBUTE_ACCELERATOR,
+ G_VARIANT_TYPE_STRING);
+
+ gchar *accel = nullptr;
+
+ if (accel_value != nullptr)
+ {
+ accel = g_variant_dup_string (accel_value, nullptr);
+ g_variant_unref (accel_value);
+ }
+
+ return accel;
+}
+
+void
+g_lo_menu_set_command_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *command)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ GVariant *value;
+
+ if (command != nullptr)
+ value = g_variant_new_string (command);
+ else
+ value = nullptr;
+
+ g_lo_menu_set_attribute_value (model, position, G_LO_MENU_ATTRIBUTE_COMMAND, value);
+
+ // Notify the update.
+ g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);
+
+ g_object_unref (model);
+}
+
+gchar *
+g_lo_menu_get_command_from_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position)
+{
+ g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);
+
+ GVariant *command_value = g_lo_menu_get_attribute_value_from_item_in_section (menu,
+ section,
+ position,
+ G_LO_MENU_ATTRIBUTE_COMMAND,
+ G_VARIANT_TYPE_STRING);
+
+ gchar *command = nullptr;
+
+ if (command_value != nullptr)
+ {
+ command = g_variant_dup_string (command_value, nullptr);
+ g_variant_unref (command_value);
+ }
+
+ return command;
+}
+
+static void
+g_lo_menu_set_link (GLOMenu *menu,
+ gint position,
+ const gchar *link,
+ GMenuModel *model)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+ g_return_if_fail (link != nullptr);
+ g_return_if_fail (valid_attribute_name (link));
+
+ if (position < 0 || o3tl::make_unsigned(position) >= menu->items->len)
+ position = menu->items->len - 1;
+
+ struct item menu_item = g_array_index (menu->items, struct item, position);
+
+ if (model != nullptr)
+ g_hash_table_insert (menu_item.links, g_strdup (link), g_object_ref (model));
+ else
+ g_hash_table_remove (menu_item.links, link);
+}
+
+void
+g_lo_menu_insert_section (GLOMenu *menu,
+ gint position,
+ const gchar *label,
+ GMenuModel *section)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ if (position < 0 || o3tl::make_unsigned(position) > menu->items->len)
+ position = menu->items->len;
+
+ struct item menu_item;
+
+ g_lo_menu_struct_item_init(&menu_item);
+
+ g_array_insert_val (menu->items, position, menu_item);
+
+ g_lo_menu_set_label (menu, position, label);
+ g_lo_menu_set_link (menu, position, G_MENU_LINK_SECTION, section);
+
+ g_menu_model_items_changed (G_MENU_MODEL (menu), position, 0, 1);
+}
+
+void
+g_lo_menu_new_section (GLOMenu *menu,
+ gint position,
+ const gchar *label)
+{
+ GMenuModel *section = G_MENU_MODEL (g_lo_menu_new());
+
+ g_lo_menu_insert_section (menu, position, label, section);
+
+ g_object_unref (section);
+}
+
+GLOMenu *
+g_lo_menu_get_section (GLOMenu *menu,
+ gint section)
+{
+ g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);
+
+ return G_LO_MENU (G_MENU_MODEL_CLASS (g_lo_menu_parent_class)
+ ->get_item_link (G_MENU_MODEL (menu), section, G_MENU_LINK_SECTION));
+}
+
+void
+g_lo_menu_new_submenu_in_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+ g_return_if_fail (0 <= section && o3tl::make_unsigned(section) < menu->items->len);
+
+ GLOMenu* model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ if (0 <= position && o3tl::make_unsigned(position) < model->items->len) {
+ GMenuModel* submenu = G_MENU_MODEL (g_lo_menu_new());
+
+ g_lo_menu_set_link (model, position, G_MENU_LINK_SUBMENU, submenu);
+
+ g_object_unref (submenu);
+
+ g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);
+
+ g_object_unref (model);
+ }
+}
+
+GLOMenu *
+g_lo_menu_get_submenu_from_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position)
+{
+ g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);
+ g_return_val_if_fail (0 <= section && o3tl::make_unsigned(section) < menu->items->len, nullptr);
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_val_if_fail (model != nullptr, nullptr);
+
+ GLOMenu *submenu = nullptr;
+
+ if (0 <= position && o3tl::make_unsigned(position) < model->items->len)
+ submenu = G_LO_MENU (G_MENU_MODEL_CLASS (g_lo_menu_parent_class)
+ ->get_item_link (G_MENU_MODEL (model), position, G_MENU_LINK_SUBMENU));
+ //submenu = g_menu_model_get_item_link (G_MENU_MODEL (model), position, G_MENU_LINK_SUBMENU);
+
+ g_object_unref (model);
+
+ return submenu;
+}
+
+void
+g_lo_menu_set_submenu_action_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *action)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GMenuModel *model = G_MENU_MODEL (g_lo_menu_get_section (menu, section));
+
+ g_return_if_fail (model != nullptr);
+
+ GVariant *value;
+
+ if (action != nullptr)
+ value = g_variant_new_string (action);
+ else
+ value = nullptr;
+
+ g_lo_menu_set_attribute_value (G_LO_MENU (model), position, G_LO_MENU_ATTRIBUTE_SUBMENU_ACTION, value);
+
+ // Notify the update.
+ g_menu_model_items_changed (model, position, 1, 1);
+
+ g_object_unref (model);
+}
+
+static void
+g_lo_menu_clear_item (struct item *menu_item)
+{
+ if (menu_item->attributes != nullptr)
+ g_hash_table_unref (menu_item->attributes);
+ if (menu_item->links != nullptr)
+ g_hash_table_unref (menu_item->links);
+}
+
+void
+g_lo_menu_remove (GLOMenu *menu,
+ gint position)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+ g_return_if_fail (0 <= position && o3tl::make_unsigned(position) < menu->items->len);
+
+ g_lo_menu_clear_item (&g_array_index (menu->items, struct item, position));
+ g_array_remove_index (menu->items, position);
+ g_menu_model_items_changed (G_MENU_MODEL (menu), position, 1, 0);
+}
+
+void
+g_lo_menu_remove_from_section (GLOMenu *menu,
+ gint section,
+ gint position)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+ g_return_if_fail (0 <= section && o3tl::make_unsigned(section) < menu->items->len);
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ g_lo_menu_remove (model, position);
+
+ g_object_unref (model);
+}
+
+static void
+g_lo_menu_finalize (GObject *object)
+{
+ GLOMenu *menu = G_LO_MENU (object);
+ struct item *items;
+ gint n_items;
+ gint i;
+
+ n_items = menu->items->len;
+ items = reinterpret_cast<struct item *>(g_array_free (menu->items, FALSE));
+ for (i = 0; i < n_items; i++)
+ g_lo_menu_clear_item (&items[i]);
+ g_free (items);
+
+ G_OBJECT_CLASS (g_lo_menu_parent_class)
+ ->finalize (object);
+}
+
+static void
+g_lo_menu_init (GLOMenu *menu)
+{
+ menu->items = g_array_new (FALSE, FALSE, sizeof (struct item));
+}
+
+static void
+g_lo_menu_class_init (GLOMenuClass *klass)
+{
+ GMenuModelClass *model_class = G_MENU_MODEL_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = g_lo_menu_finalize;
+
+ model_class->is_mutable = g_lo_menu_is_mutable;
+ model_class->get_n_items = g_lo_menu_get_n_items;
+ model_class->get_item_attributes = g_lo_menu_get_item_attributes;
+ model_class->get_item_links = g_lo_menu_get_item_links;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtkcairo.cxx b/vcl/unx/gtk3/gtkcairo.cxx
new file mode 100644
index 0000000000..f389f4d087
--- /dev/null
+++ b/vcl/unx/gtk3/gtkcairo.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/.
+ */
+
+#include "gtkcairo.hxx"
+
+#include <utility>
+#include <vcl/sysdata.hxx>
+#include <vcl/virdev.hxx>
+
+#include <unx/gtk/gtkgdi.hxx>
+
+namespace
+{
+ Size get_surface_size(cairo_surface_t *surface)
+ {
+ cairo_t *cr = cairo_create(surface);
+ double x1, x2, y1, y2;
+ cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
+ cairo_destroy(cr);
+ return Size(x2 - x1, y2 - y1);
+ }
+}
+
+namespace cairo
+{
+ /**
+ * Surface::Surface: Create generic Canvas surface using given Cairo Surface
+ *
+ * @param pSurface Cairo Surface
+ *
+ * This constructor only stores data, it does no processing.
+ * It is used with e.g. cairo_image_surface_create_for_data()
+ *
+ * Set the mpSurface as pSurface
+ **/
+ Gtk3Surface::Gtk3Surface(CairoSurfaceSharedPtr pSurface)
+ : mpGraphics(nullptr)
+ , cr(nullptr)
+ , mpSurface(std::move(pSurface))
+ {}
+
+ /**
+ * Surface::Surface: Create Canvas surface from Window reference.
+ * @param x horizontal location of the new surface
+ * @param y vertical location of the new surface
+ * @param width width of the new surface
+ * @param height height of the new surface
+ *
+ * Set the mpSurface to the new surface or NULL
+ **/
+ Gtk3Surface::Gtk3Surface(const GtkSalGraphics* pGraphics, int x, int y, int width, int height)
+ : mpGraphics(pGraphics)
+ , cr(pGraphics->getCairoContext())
+ {
+ cairo_surface_t* surface = cairo_get_target(cr);
+ mpSurface.reset(
+ cairo_surface_create_for_rectangle(surface, x, y, width, height),
+ &cairo_surface_destroy);
+ }
+
+ Gtk3Surface::~Gtk3Surface()
+ {
+ if (cr)
+ cairo_destroy(cr);
+ }
+
+ /**
+ * Surface::getCairo: Create Cairo (drawing object) for the Canvas surface
+ *
+ * @return new Cairo or NULL
+ **/
+ CairoSharedPtr Gtk3Surface::getCairo() const
+ {
+ return CairoSharedPtr( cairo_create(mpSurface.get()),
+ &cairo_destroy );
+ }
+
+ /**
+ * Surface::getSimilar: Create new similar Canvas surface
+ * @param cairo_content_type format of the new surface (cairo_content_t from cairo/src/cairo.h)
+ * @param width width of the new surface
+ * @param height height of the new surface
+ *
+ * Creates a new Canvas surface. This normally creates platform native surface, even though
+ * generic function is used.
+ *
+ * Cairo surface from cairo_content_type (cairo_content_t)
+ *
+ * @return new surface or NULL
+ **/
+ SurfaceSharedPtr Gtk3Surface::getSimilar(int cairo_content_type, int width, int height ) const
+ {
+ return std::make_shared<Gtk3Surface>(
+ CairoSurfaceSharedPtr(
+ cairo_surface_create_similar( mpSurface.get(),
+ static_cast<cairo_content_t>(cairo_content_type), width, height ),
+ &cairo_surface_destroy ));
+ }
+
+ void Gtk3Surface::flush() const
+ {
+ cairo_surface_flush(mpSurface.get());
+ //Wonder if there is any benefit in using cairo_fill/stroke extents on
+ //every canvas call and only redrawing the union of those in a
+ //poor-mans-damage tracking
+ if (mpGraphics)
+ mpGraphics->WidgetQueueDraw();
+ }
+
+ VclPtr<VirtualDevice> Gtk3Surface::createVirtualDevice() const
+ {
+ SystemGraphicsData aSystemGraphicsData;
+
+ aSystemGraphicsData.nSize = sizeof(SystemGraphicsData);
+ aSystemGraphicsData.pSurface = mpSurface.get();
+
+ return VclPtr<VirtualDevice>::Create(aSystemGraphicsData,
+ get_surface_size(mpSurface.get()),
+ DeviceFormat::WITHOUT_ALPHA);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtkcairo.hxx b/vcl/unx/gtk3/gtkcairo.hxx
new file mode 100644
index 0000000000..c5912181fc
--- /dev/null
+++ b/vcl/unx/gtk3/gtkcairo.hxx
@@ -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/.
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <vcl/cairo.hxx>
+
+class GtkSalGraphics;
+class OutputDevice;
+
+namespace cairo {
+
+ class Gtk3Surface : public Surface
+ {
+ const GtkSalGraphics* mpGraphics;
+ cairo_t* cr;
+ CairoSurfaceSharedPtr mpSurface;
+ public:
+ /// takes over ownership of passed cairo_surface
+ explicit Gtk3Surface(CairoSurfaceSharedPtr pSurface);
+ /// create surface on subarea of given drawable
+ explicit Gtk3Surface(const GtkSalGraphics* pGraphics, int x, int y, int width, int height);
+
+ // Surface interface
+ virtual CairoSharedPtr getCairo() const override;
+ virtual CairoSurfaceSharedPtr getCairoSurface() const override { return mpSurface; }
+ virtual SurfaceSharedPtr getSimilar(int nContentType, int width, int height) const override;
+
+ virtual VclPtr<VirtualDevice> createVirtualDevice() const override;
+
+ virtual void flush() const override;
+
+ virtual ~Gtk3Surface() override;
+ };
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtkdata.cxx b/vcl/unx/gtk3/gtkdata.cxx
new file mode 100644
index 0000000000..f595369f8b
--- /dev/null
+++ b/vcl/unx/gtk3/gtkdata.cxx
@@ -0,0 +1,984 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unistd.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#if defined(FREEBSD) || defined(NETBSD)
+#include <sys/types.h>
+#include <sys/time.h>
+#endif
+#include <unx/gtk/gtkbackend.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include <unx/gtk/gtkframe.hxx>
+#include <bitmaps.hlst>
+#include <cursor_hotspots.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/thread.h>
+#include <osl/process.h>
+
+#include <vcl/svapp.hxx>
+#include <sal/log.hxx>
+
+#include <chrono>
+
+using namespace vcl_sal;
+
+/***************************************************************
+ * class GtkSalDisplay *
+ ***************************************************************/
+
+GtkSalDisplay::GtkSalDisplay( GdkDisplay* pDisplay ) :
+ m_pSys( GtkSalSystem::GetSingleton() ),
+ m_pGdkDisplay( pDisplay ),
+ m_bStartupCompleted( false )
+{
+ for(GdkCursor* & rpCsr : m_aCursors)
+ rpCsr = nullptr;
+
+ if ( getenv( "SAL_IGNOREXERRORS" ) )
+ GetGenericUnixSalData()->ErrorTrapPush(); // and leak the trap
+
+ gtk_widget_set_default_direction(AllSettings::GetLayoutRTL() ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
+}
+
+GtkSalDisplay::~GtkSalDisplay()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( !m_bStartupCompleted )
+ gdk_notify_startup_complete();
+
+ for(GdkCursor* & rpCsr : m_aCursors)
+ if( rpCsr )
+ gdk_cursor_unref( rpCsr );
+#endif
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+
+static void signalMonitorsChanged(GListModel*, gpointer data)
+{
+ GtkSalDisplay* pDisp = static_cast<GtkSalDisplay*>(data);
+ pDisp->emitDisplayChanged();
+}
+
+#else
+
+static void signalScreenSizeChanged( GdkScreen* pScreen, gpointer data )
+{
+ GtkSalDisplay* pDisp = static_cast<GtkSalDisplay*>(data);
+ pDisp->screenSizeChanged( pScreen );
+}
+
+static void signalMonitorsChanged( GdkScreen* pScreen, gpointer data )
+{
+ GtkSalDisplay* pDisp = static_cast<GtkSalDisplay*>(data);
+ pDisp->monitorsChanged( pScreen );
+}
+
+void GtkSalDisplay::screenSizeChanged( GdkScreen const * pScreen )
+{
+ m_pSys->countScreenMonitors();
+ if (pScreen)
+ emitDisplayChanged();
+}
+
+void GtkSalDisplay::monitorsChanged( GdkScreen const * pScreen )
+{
+ m_pSys->countScreenMonitors();
+ if (pScreen)
+ emitDisplayChanged();
+}
+#endif
+
+GdkCursor* GtkSalDisplay::getFromSvg(OUString const & name, int nXHot, int nYHot)
+{
+ GdkPixbuf* pPixBuf = load_icon_by_name(name);
+ assert(pPixBuf && "missing image?");
+ if (!pPixBuf)
+ return nullptr;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ guint nDefaultCursorSize = gdk_display_get_default_cursor_size( m_pGdkDisplay );
+ int nPixWidth = gdk_pixbuf_get_width(pPixBuf);
+ int nPixHeight = gdk_pixbuf_get_height(pPixBuf);
+ double fScalefactor = static_cast<double>(nDefaultCursorSize) / std::max(nPixWidth, nPixHeight);
+ GdkPixbuf* pScaledPixBuf = gdk_pixbuf_scale_simple(pPixBuf,
+ nPixWidth * fScalefactor,
+ nPixHeight * fScalefactor,
+ GDK_INTERP_HYPER);
+ g_object_unref(pPixBuf);
+ return gdk_cursor_new_from_pixbuf(m_pGdkDisplay, pScaledPixBuf,
+ nXHot * fScalefactor, nYHot * fScalefactor);
+#else
+ GdkTexture* pTexture = gdk_texture_new_for_pixbuf(pPixBuf);
+ g_object_unref(pPixBuf);
+ return gdk_cursor_new_from_texture(pTexture, nXHot, nYHot, nullptr);
+#endif
+}
+
+#define MAKE_CURSOR( vcl_name, name, name2 ) \
+ case vcl_name: \
+ pCursor = getFromSvg(name2, name##curs_x_hot, name##curs_y_hot); \
+ break
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+#define MAP_BUILTIN( vcl_name, gdk3_name, css_name ) \
+ case vcl_name: \
+ pCursor = gdk_cursor_new_for_display( m_pGdkDisplay, gdk3_name ); \
+ break
+#else
+#define MAP_BUILTIN( vcl_name, gdk3_name, css_name ) \
+ case vcl_name: \
+ pCursor = gdk_cursor_new_from_name(css_name, nullptr); \
+ break
+#endif
+
+GdkCursor *GtkSalDisplay::getCursor( PointerStyle ePointerStyle )
+{
+ if ( !m_aCursors[ ePointerStyle ] )
+ {
+ GdkCursor *pCursor = nullptr;
+
+ switch( ePointerStyle )
+ {
+ MAP_BUILTIN( PointerStyle::Arrow, GDK_LEFT_PTR, "default" );
+ MAP_BUILTIN( PointerStyle::Text, GDK_XTERM, "text" );
+ MAP_BUILTIN( PointerStyle::Help, GDK_QUESTION_ARROW, "help" );
+ MAP_BUILTIN( PointerStyle::Cross, GDK_CROSSHAIR, "crosshair" );
+ MAP_BUILTIN( PointerStyle::Wait, GDK_WATCH, "wait" );
+
+ MAP_BUILTIN( PointerStyle::NSize, GDK_SB_V_DOUBLE_ARROW, "n-resize" );
+ MAP_BUILTIN( PointerStyle::SSize, GDK_SB_V_DOUBLE_ARROW, "s-resize" );
+ MAP_BUILTIN( PointerStyle::WSize, GDK_SB_H_DOUBLE_ARROW, "w-resize" );
+ MAP_BUILTIN( PointerStyle::ESize, GDK_SB_H_DOUBLE_ARROW, "e-resize" );
+
+ MAP_BUILTIN( PointerStyle::NWSize, GDK_TOP_LEFT_CORNER, "nw-resize" );
+ MAP_BUILTIN( PointerStyle::NESize, GDK_TOP_RIGHT_CORNER, "ne-resize" );
+ MAP_BUILTIN( PointerStyle::SWSize, GDK_BOTTOM_LEFT_CORNER, "sw-resize" );
+ MAP_BUILTIN( PointerStyle::SESize, GDK_BOTTOM_RIGHT_CORNER, "se-resize" );
+
+ MAP_BUILTIN( PointerStyle::WindowNSize, GDK_TOP_SIDE, "n-resize" );
+ MAP_BUILTIN( PointerStyle::WindowSSize, GDK_BOTTOM_SIDE, "s-resize" );
+ MAP_BUILTIN( PointerStyle::WindowWSize, GDK_LEFT_SIDE, "w-resize" );
+ MAP_BUILTIN( PointerStyle::WindowESize, GDK_RIGHT_SIDE, "e-resize" );
+
+ MAP_BUILTIN( PointerStyle::WindowNWSize, GDK_TOP_LEFT_CORNER, "nw-resize" );
+ MAP_BUILTIN( PointerStyle::WindowNESize, GDK_TOP_RIGHT_CORNER, "ne-resize" );
+ MAP_BUILTIN( PointerStyle::WindowSWSize, GDK_BOTTOM_LEFT_CORNER, "sw-resize" );
+ MAP_BUILTIN( PointerStyle::WindowSESize, GDK_BOTTOM_RIGHT_CORNER, "se-resize" );
+
+ MAP_BUILTIN( PointerStyle::HSizeBar, GDK_SB_H_DOUBLE_ARROW, "col-resize" );
+ MAP_BUILTIN( PointerStyle::VSizeBar, GDK_SB_V_DOUBLE_ARROW, "row-resize" );
+
+ MAP_BUILTIN( PointerStyle::RefHand, GDK_HAND2, "grab" );
+ MAP_BUILTIN( PointerStyle::Hand, GDK_HAND2, "grab" );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ MAP_BUILTIN( PointerStyle::Pen, GDK_PENCIL, "" );
+#else
+ MAKE_CURSOR( PointerStyle::Pen, pen_, RID_CURSOR_PEN );
+#endif
+
+ MAP_BUILTIN( PointerStyle::HSplit, GDK_SB_H_DOUBLE_ARROW, "col-resize" );
+ MAP_BUILTIN( PointerStyle::VSplit, GDK_SB_V_DOUBLE_ARROW, "row-resize" );
+
+ MAP_BUILTIN( PointerStyle::Move, GDK_FLEUR, "move" );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ MAKE_CURSOR( PointerStyle::Null, null, RID_CURSOR_NULL );
+#else
+ MAP_BUILTIN( PointerStyle::Null, 0, "none" );
+#endif
+
+ MAKE_CURSOR( PointerStyle::Magnify, magnify_, RID_CURSOR_MAGNIFY );
+ MAKE_CURSOR( PointerStyle::Fill, fill_, RID_CURSOR_FILL );
+ MAKE_CURSOR( PointerStyle::MoveData, movedata_, RID_CURSOR_MOVE_DATA );
+ MAKE_CURSOR( PointerStyle::CopyData, copydata_, RID_CURSOR_COPY_DATA );
+ MAKE_CURSOR( PointerStyle::MoveFile, movefile_, RID_CURSOR_MOVE_FILE );
+ MAKE_CURSOR( PointerStyle::CopyFile, copyfile_, RID_CURSOR_COPY_FILE );
+ MAKE_CURSOR( PointerStyle::MoveFiles, movefiles_, RID_CURSOR_MOVE_FILES );
+ MAKE_CURSOR( PointerStyle::CopyFiles, copyfiles_, RID_CURSOR_COPY_FILES );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ MAKE_CURSOR( PointerStyle::NotAllowed, nodrop_, RID_CURSOR_NOT_ALLOWED );
+#else
+ MAP_BUILTIN( PointerStyle::NotAllowed, 0, "not-allowed" );
+#endif
+
+ MAKE_CURSOR( PointerStyle::Rotate, rotate_, RID_CURSOR_ROTATE );
+ MAKE_CURSOR( PointerStyle::HShear, hshear_, RID_CURSOR_H_SHEAR );
+ MAKE_CURSOR( PointerStyle::VShear, vshear_, RID_CURSOR_V_SHEAR );
+ MAKE_CURSOR( PointerStyle::DrawLine, drawline_, RID_CURSOR_DRAW_LINE );
+ MAKE_CURSOR( PointerStyle::DrawRect, drawrect_, RID_CURSOR_DRAW_RECT );
+ MAKE_CURSOR( PointerStyle::DrawPolygon, drawpolygon_, RID_CURSOR_DRAW_POLYGON );
+ MAKE_CURSOR( PointerStyle::DrawBezier, drawbezier_, RID_CURSOR_DRAW_BEZIER );
+ MAKE_CURSOR( PointerStyle::DrawArc, drawarc_, RID_CURSOR_DRAW_ARC );
+ MAKE_CURSOR( PointerStyle::DrawPie, drawpie_, RID_CURSOR_DRAW_PIE );
+ MAKE_CURSOR( PointerStyle::DrawCircleCut, drawcirclecut_, RID_CURSOR_DRAW_CIRCLE_CUT );
+ MAKE_CURSOR( PointerStyle::DrawEllipse, drawellipse_, RID_CURSOR_DRAW_ELLIPSE );
+ MAKE_CURSOR( PointerStyle::DrawConnect, drawconnect_, RID_CURSOR_DRAW_CONNECT );
+ MAKE_CURSOR( PointerStyle::DrawText, drawtext_, RID_CURSOR_DRAW_TEXT );
+ MAKE_CURSOR( PointerStyle::Mirror, mirror_, RID_CURSOR_MIRROR );
+ MAKE_CURSOR( PointerStyle::Crook, crook_, RID_CURSOR_CROOK );
+ MAKE_CURSOR( PointerStyle::Crop, crop_, RID_CURSOR_CROP );
+ MAKE_CURSOR( PointerStyle::MovePoint, movepoint_, RID_CURSOR_MOVE_POINT );
+ MAKE_CURSOR( PointerStyle::MoveBezierWeight, movebezierweight_, RID_CURSOR_MOVE_BEZIER_WEIGHT );
+ MAKE_CURSOR( PointerStyle::DrawFreehand, drawfreehand_, RID_CURSOR_DRAW_FREEHAND );
+ MAKE_CURSOR( PointerStyle::DrawCaption, drawcaption_, RID_CURSOR_DRAW_CAPTION );
+ MAKE_CURSOR( PointerStyle::LinkData, linkdata_, RID_CURSOR_LINK_DATA );
+ MAKE_CURSOR( PointerStyle::MoveDataLink, movedlnk_, RID_CURSOR_MOVE_DATA_LINK );
+ MAKE_CURSOR( PointerStyle::CopyDataLink, copydlnk_, RID_CURSOR_COPY_DATA_LINK );
+ MAKE_CURSOR( PointerStyle::LinkFile, linkfile_, RID_CURSOR_LINK_FILE );
+ MAKE_CURSOR( PointerStyle::MoveFileLink, moveflnk_, RID_CURSOR_MOVE_FILE_LINK );
+ MAKE_CURSOR( PointerStyle::CopyFileLink, copyflnk_, RID_CURSOR_COPY_FILE_LINK );
+ MAKE_CURSOR( PointerStyle::Chart, chart_, RID_CURSOR_CHART );
+ MAKE_CURSOR( PointerStyle::Detective, detective_, RID_CURSOR_DETECTIVE );
+ MAKE_CURSOR( PointerStyle::PivotCol, pivotcol_, RID_CURSOR_PIVOT_COLUMN );
+ MAKE_CURSOR( PointerStyle::PivotRow, pivotrow_, RID_CURSOR_PIVOT_ROW );
+ MAKE_CURSOR( PointerStyle::PivotField, pivotfld_, RID_CURSOR_PIVOT_FIELD );
+ MAKE_CURSOR( PointerStyle::PivotDelete, pivotdel_, RID_CURSOR_PIVOT_DELETE );
+ MAKE_CURSOR( PointerStyle::Chain, chain_, RID_CURSOR_CHAIN );
+ MAKE_CURSOR( PointerStyle::ChainNotAllowed, chainnot_, RID_CURSOR_CHAIN_NOT_ALLOWED );
+ MAKE_CURSOR( PointerStyle::AutoScrollN, asn_, RID_CURSOR_AUTOSCROLL_N );
+ MAKE_CURSOR( PointerStyle::AutoScrollS, ass_, RID_CURSOR_AUTOSCROLL_S );
+ MAKE_CURSOR( PointerStyle::AutoScrollW, asw_, RID_CURSOR_AUTOSCROLL_W );
+ MAKE_CURSOR( PointerStyle::AutoScrollE, ase_, RID_CURSOR_AUTOSCROLL_E );
+ MAKE_CURSOR( PointerStyle::AutoScrollNW, asnw_, RID_CURSOR_AUTOSCROLL_NW );
+ MAKE_CURSOR( PointerStyle::AutoScrollNE, asne_, RID_CURSOR_AUTOSCROLL_NE );
+ MAKE_CURSOR( PointerStyle::AutoScrollSW, assw_, RID_CURSOR_AUTOSCROLL_SW );
+ MAKE_CURSOR( PointerStyle::AutoScrollSE, asse_, RID_CURSOR_AUTOSCROLL_SE );
+ MAKE_CURSOR( PointerStyle::AutoScrollNS, asns_, RID_CURSOR_AUTOSCROLL_NS );
+ MAKE_CURSOR( PointerStyle::AutoScrollWE, aswe_, RID_CURSOR_AUTOSCROLL_WE );
+ MAKE_CURSOR( PointerStyle::AutoScrollNSWE, asnswe_, RID_CURSOR_AUTOSCROLL_NSWE );
+ MAKE_CURSOR( PointerStyle::TextVertical, vertcurs_, RID_CURSOR_TEXT_VERTICAL );
+
+ // #i32329#
+ MAKE_CURSOR( PointerStyle::TabSelectS, tblsels_, RID_CURSOR_TAB_SELECT_S );
+ MAKE_CURSOR( PointerStyle::TabSelectE, tblsele_, RID_CURSOR_TAB_SELECT_E );
+ MAKE_CURSOR( PointerStyle::TabSelectSE, tblselse_, RID_CURSOR_TAB_SELECT_SE );
+ MAKE_CURSOR( PointerStyle::TabSelectW, tblselw_, RID_CURSOR_TAB_SELECT_W );
+ MAKE_CURSOR( PointerStyle::TabSelectSW, tblselsw_, RID_CURSOR_TAB_SELECT_SW );
+
+ MAKE_CURSOR( PointerStyle::HideWhitespace, hidewhitespace_, RID_CURSOR_HIDE_WHITESPACE );
+ MAKE_CURSOR( PointerStyle::ShowWhitespace, showwhitespace_, RID_CURSOR_SHOW_WHITESPACE );
+ MAKE_CURSOR( PointerStyle::FatCross, fatcross_, RID_CURSOR_FATCROSS );
+
+ default:
+ SAL_WARN( "vcl.gtk", "pointer " << static_cast<int>(ePointerStyle) << "not implemented" );
+ break;
+ }
+ if( !pCursor )
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ pCursor = gdk_cursor_new_for_display( m_pGdkDisplay, GDK_LEFT_PTR );
+#else
+ pCursor = gdk_cursor_new_from_name("normal", nullptr);
+#endif
+ }
+
+ m_aCursors[ ePointerStyle ] = pCursor;
+ }
+
+ return m_aCursors[ ePointerStyle ];
+}
+
+int GtkSalDisplay::CaptureMouse( SalFrame* pSFrame )
+{
+ GtkSalFrame* pFrame = static_cast<GtkSalFrame*>(pSFrame);
+
+ if( !pFrame )
+ {
+ if( m_pCapture )
+ static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( false, false, false );
+ m_pCapture = nullptr;
+ return 0;
+ }
+
+ if( m_pCapture )
+ {
+ if( pFrame == m_pCapture )
+ return 1;
+ static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( false, false, false );
+ }
+
+ m_pCapture = pFrame;
+ pFrame->grabPointer( true, false, false );
+ return 1;
+}
+
+/**********************************************************************
+ * class GtkSalData *
+ **********************************************************************/
+
+GtkSalData::GtkSalData()
+ : GenericUnixSalData()
+{
+ m_pUserEvent = nullptr;
+}
+
+#if defined(GDK_WINDOWING_X11)
+static XIOErrorHandler aOrigXIOErrorHandler = nullptr;
+
+extern "C" {
+
+static int XIOErrorHdl(Display *)
+{
+ fprintf(stderr, "X IO Error\n");
+ _exit(1);
+ // avoid crashes in unrelated threads that still run while atexit
+ // handlers are in progress
+}
+
+}
+#endif
+
+GtkSalData::~GtkSalData()
+{
+ // sanity check: at this point nobody should be yielding, but wake them
+ // up anyway before the condition they're waiting on gets destroyed.
+ m_aDispatchCondition.set();
+
+ osl::MutexGuard g( m_aDispatchMutex );
+ if (m_pUserEvent)
+ {
+ g_source_destroy (m_pUserEvent);
+ g_source_unref (m_pUserEvent);
+ m_pUserEvent = nullptr;
+ }
+#if defined(GDK_WINDOWING_X11)
+ if (DLSYM_GDK_IS_X11_DISPLAY(gdk_display_get_default()))
+ XSetIOErrorHandler(aOrigXIOErrorHandler);
+#endif
+}
+
+void GtkSalData::Dispose()
+{
+ deInitNWF();
+}
+
+/// Allows events to be processed, returns true if we processed an event.
+bool GtkSalData::Yield( bool bWait, bool bHandleAllCurrentEvents )
+{
+ /* #i33212# only enter g_main_context_iteration in one thread at any one
+ * time, else one of them potentially will never end as long as there is
+ * another thread in there. Having only one yielding thread actually dispatch
+ * fits the vcl event model (see e.g. the generic plugin).
+ */
+ bool bDispatchThread = false;
+ bool bWasEvent = false;
+ {
+ // release YieldMutex (and re-acquire at block end)
+ SolarMutexReleaser aReleaser;
+ if( m_aDispatchMutex.tryToAcquire() )
+ bDispatchThread = true;
+ else if( ! bWait )
+ {
+ return false; // someone else is waiting already, return
+ }
+
+ if( bDispatchThread )
+ {
+ int nMaxEvents = bHandleAllCurrentEvents ? 100 : 1;
+ bool wasOneEvent = true;
+ while( nMaxEvents-- && wasOneEvent )
+ {
+ wasOneEvent = g_main_context_iteration( nullptr, bWait && !bWasEvent );
+ if( wasOneEvent )
+ bWasEvent = true;
+ }
+ if (m_aException)
+ std::rethrow_exception(m_aException);
+ }
+ else if( bWait )
+ {
+ /* #i41693# in case the dispatch thread hangs in join
+ * for this thread the condition will never be set
+ * workaround: timeout of 1 second an emergency exit
+ */
+ // we are the dispatch thread
+ m_aDispatchCondition.reset();
+ m_aDispatchCondition.wait(std::chrono::seconds(1));
+ }
+ }
+
+ if( bDispatchThread )
+ {
+ m_aDispatchMutex.release();
+ if( bWasEvent )
+ m_aDispatchCondition.set(); // trigger non dispatch thread yields
+ }
+
+ return bWasEvent;
+}
+
+static GtkStyleProvider* CreateStyleProvider()
+{
+ /*
+ set a provider to:
+
+ 1) allow certain widgets to have no padding
+
+ 1.a) little close button in menubar to close back to start-center
+ 1.b) and small buttons in view->data sources (button.small-button)
+ 1.c.1) gtk3 small toolbar button in infobars (toolbar.small-button button)
+ 1.c.2) gtk4 small toolbar button in infobars (box.small-button button)
+ 1.d) comboboxes in the data browser for tdf#137695 (box#combobox button.small-button,
+ which would instead be combobox button.small-button if we didn't replace GtkComboBox,
+ see GtkInstanceComboBox for an explanation for why we do that)
+ 1.e) entry in the data browser for tdf#137695 (entry.small-button)
+ 1.f) spinbutton in the data browser tdf#141633 (spinbutton.small-button)
+
+ 2) hide the unwanted active tab in an 'overflow' notebook of double-decker notebooks.
+ (tdf#122623) it's nigh impossible to have a GtkNotebook without an active (checked) tab,
+ so theme the unwanted tab into invisibility
+ */
+ GtkCssProvider* pStyleProvider = gtk_css_provider_new();
+ static const gchar data[] =
+ "button.small-button, toolbar.small-button button, box.small-button button, "
+ "combobox.small-button *.combo, box#combobox.small-button *.combo, entry.small-button, "
+ "spinbutton.small-button, spinbutton.small-button entry, spinbutton.small-button button { "
+ "padding: 0; margin-left: 0; margin-right: 0; margin-top: 0; margin-bottom: 0;"
+ "border-width: 0; min-height: 0; min-width: 0; }"
+#if GTK_CHECK_VERSION(4, 0, 0)
+ // we basically assumed during dialog design that the frame's were invisible, because
+ // they used to be in the default theme during gtk3
+ "frame { border-style: none; }"
+#endif
+ "notebook.overflow > header.top > tabs > tab:checked { "
+ "box-shadow: none; padding: 0 0 0 0; margin: 0 0 0 0;"
+ "border-image: none; border-image-width: 0 0 0 0;"
+ "background-image: none; background-color: transparent;"
+ "border-radius: 0 0 0 0; border-width: 0 0 0 0;"
+ "border-style: none; border-color: transparent;"
+ "opacity: 0; min-height: 0; min-width: 0; }"
+ // https://css-tricks.com/restart-css-animation/
+ // This animation appears twice with two different names so we can change
+ // the class from "call_attention_1" to "call_attention_2" to restart the
+ // animation
+ "@keyframes shinkandrestore1 { 50% { margin-left: 15px; margin-right: 15px; opacity: 0.5; } }"
+ "@keyframes shinkandrestore2 { 50% { margin-left: 15px; margin-right: 15px; opacity: 0.5; } }"
+ " *.call_attention_1 {"
+ "animation-name: shinkandrestore1; animation-duration: 1s; "
+ "animation-timing-function: linear; animation-iteration-count: 2; }"
+ " *.call_attention_2 {"
+ "animation-name: shinkandrestore2; animation-duration: 1s; "
+ "animation-timing-function: linear; animation-iteration-count: 2; }";
+ css_provider_load_from_data(pStyleProvider, data, -1);
+ return GTK_STYLE_PROVIDER(pStyleProvider);
+}
+
+void GtkSalData::Init()
+{
+ SAL_INFO( "vcl.gtk", "GtkMainloop::Init()" );
+
+ /*
+ * open connection to X11 Display
+ * try in this order:
+ * o -display command line parameter,
+ * o $DISPLAY environment variable
+ * o default display
+ */
+
+ GdkDisplay *pGdkDisp = nullptr;
+
+ // is there a -display command line parameter?
+ rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
+ int nParams = osl_getCommandArgCount();
+ OString aDisplay;
+ OUString aParam, aBin;
+ char** pCmdLineAry = new char*[ nParams+1 ];
+ osl_getExecutableFile( &aParam.pData );
+ osl_getSystemPathFromFileURL( aParam.pData, &aBin.pData );
+ pCmdLineAry[0] = g_strdup( OUStringToOString( aBin, aEnc ).getStr() );
+ for (int i = 0; i < nParams; ++i)
+ {
+ osl_getCommandArg(i, &aParam.pData );
+ OString aBParam( OUStringToOString( aParam, aEnc ) );
+
+ if( aParam == "-display" || aParam == "--display" )
+ {
+ pCmdLineAry[i+1] = g_strdup( "--display" );
+ osl_getCommandArg(i+1, &aParam.pData );
+ aDisplay = OUStringToOString( aParam, aEnc );
+ }
+ else
+ pCmdLineAry[i+1] = g_strdup( aBParam.getStr() );
+ }
+ // add executable
+ nParams++;
+
+ g_set_application_name(SalGenericSystem::getFrameClassName());
+
+ // Set consistent name of the root accessible
+ OUString aAppName = Application::GetAppName();
+ if( !aAppName.isEmpty() )
+ {
+ OString aPrgName = OUStringToOString(aAppName, aEnc);
+ g_set_prgname(aPrgName.getStr());
+ }
+
+ // init gtk/gdk
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_init_check();
+#else
+ gtk_init_check( &nParams, &pCmdLineAry );
+#endif
+
+ for (int i = 0; i < nParams; ++i)
+ g_free( pCmdLineAry[i] );
+ delete [] pCmdLineAry;
+
+#if OSL_DEBUG_LEVEL > 1
+ if (g_getenv("SAL_DEBUG_UPDATES"))
+ gdk_window_set_debug_updates (TRUE);
+#endif
+
+ pGdkDisp = gdk_display_get_default();
+ if ( !pGdkDisp )
+ {
+ OUString aProgramFileURL;
+ osl_getExecutableFile( &aProgramFileURL.pData );
+ OUString aProgramSystemPath;
+ osl_getSystemPathFromFileURL (aProgramFileURL.pData, &aProgramSystemPath.pData);
+ OString aProgramName = OUStringToOString(
+ aProgramSystemPath,
+ osl_getThreadTextEncoding() );
+ fprintf( stderr, "%s X11 error: Can't open display: %s\n",
+ aProgramName.getStr(), aDisplay.getStr());
+ fprintf( stderr, " Set DISPLAY environment variable, use -display option\n");
+ fprintf( stderr, " or check permissions of your X-Server\n");
+ fprintf( stderr, " (See \"man X\" resp. \"man xhost\" for details)\n");
+ fflush( stderr );
+ exit(0);
+ }
+
+ ErrorTrapPush();
+
+#if defined(GDK_WINDOWING_X11)
+ if (DLSYM_GDK_IS_X11_DISPLAY(pGdkDisp))
+ aOrigXIOErrorHandler = XSetIOErrorHandler(XIOErrorHdl);
+#endif
+
+ GtkSalDisplay *pDisplay = new GtkSalDisplay( pGdkDisp );
+ SetDisplay( pDisplay );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ pDisplay->emitDisplayChanged();
+ GListModel *pMonitors = gdk_display_get_monitors(pGdkDisp);
+ g_signal_connect(pMonitors, "items-changed", G_CALLBACK(signalMonitorsChanged), pDisplay);
+
+ gtk_style_context_add_provider_for_display(pGdkDisp, CreateStyleProvider(),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+#else
+ int nScreens = gdk_display_get_n_screens( pGdkDisp );
+ for( int n = 0; n < nScreens; n++ )
+ {
+ GdkScreen *pScreen = gdk_display_get_screen( pGdkDisp, n );
+ if (!pScreen)
+ continue;
+
+ pDisplay->screenSizeChanged( pScreen );
+ pDisplay->monitorsChanged( pScreen );
+ // add signal handler to notify screen size changes
+ g_signal_connect( G_OBJECT(pScreen), "size-changed",
+ G_CALLBACK(signalScreenSizeChanged), pDisplay );
+ g_signal_connect( G_OBJECT(pScreen), "monitors-changed",
+ G_CALLBACK(signalMonitorsChanged), pDisplay );
+
+ gtk_style_context_add_provider_for_screen(pScreen, CreateStyleProvider(),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+#endif
+}
+
+void GtkSalData::ErrorTrapPush()
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+# if defined(GDK_WINDOWING_X11)
+ GdkDisplay* pGdkDisp = gdk_display_get_default();
+ if (DLSYM_GDK_IS_X11_DISPLAY(pGdkDisp))
+ gdk_x11_display_error_trap_push(pGdkDisp);
+# endif
+#else
+ gdk_error_trap_push();
+#endif
+}
+
+bool GtkSalData::ErrorTrapPop( bool bIgnoreError )
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+# if defined(GDK_WINDOWING_X11)
+ GdkDisplay* pGdkDisp = gdk_display_get_default();
+ if (DLSYM_GDK_IS_X11_DISPLAY(pGdkDisp))
+ {
+ if (bIgnoreError)
+ {
+ gdk_x11_display_error_trap_pop_ignored(pGdkDisp); // faster
+ return false;
+ }
+ return gdk_x11_display_error_trap_pop(pGdkDisp) != 0;
+ }
+# endif
+ return false;
+#else
+ if (bIgnoreError)
+ {
+ gdk_error_trap_pop_ignored (); // faster
+ return false;
+ }
+ return gdk_error_trap_pop () != 0;
+#endif
+}
+
+#if !GLIB_CHECK_VERSION(2,32,0)
+#define G_SOURCE_REMOVE FALSE
+#endif
+
+extern "C" {
+
+ struct SalGtkTimeoutSource {
+ GSource aParent;
+ GTimeVal aFireTime;
+ GtkSalTimer *pInstance;
+ };
+
+ static void sal_gtk_timeout_defer( SalGtkTimeoutSource *pTSource )
+ {
+ g_get_current_time( &pTSource->aFireTime );
+ g_time_val_add( &pTSource->aFireTime, pTSource->pInstance->m_nTimeoutMS * 1000 );
+ }
+
+ static gboolean sal_gtk_timeout_expired( SalGtkTimeoutSource *pTSource,
+ gint *nTimeoutMS, GTimeVal const *pTimeNow )
+ {
+ glong nDeltaSec = pTSource->aFireTime.tv_sec - pTimeNow->tv_sec;
+ glong nDeltaUSec = pTSource->aFireTime.tv_usec - pTimeNow->tv_usec;
+ if( nDeltaSec < 0 || ( nDeltaSec == 0 && nDeltaUSec < 0) )
+ {
+ *nTimeoutMS = 0;
+ return true;
+ }
+ if( nDeltaUSec < 0 )
+ {
+ nDeltaUSec += 1000000;
+ nDeltaSec -= 1;
+ }
+ // if the clock changes backwards we need to cope ...
+ if( o3tl::make_unsigned(nDeltaSec) > 1 + ( pTSource->pInstance->m_nTimeoutMS / 1000 ) )
+ {
+ sal_gtk_timeout_defer( pTSource );
+ return true;
+ }
+
+ *nTimeoutMS = MIN( G_MAXINT, ( nDeltaSec * 1000 + (nDeltaUSec + 999) / 1000 ) );
+
+ return *nTimeoutMS == 0;
+ }
+
+ static gboolean sal_gtk_timeout_prepare( GSource *pSource, gint *nTimeoutMS )
+ {
+ SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource);
+
+ GTimeVal aTimeNow;
+ g_get_current_time( &aTimeNow );
+
+ return sal_gtk_timeout_expired( pTSource, nTimeoutMS, &aTimeNow );
+ }
+
+ static gboolean sal_gtk_timeout_check( GSource *pSource )
+ {
+ SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource);
+
+ GTimeVal aTimeNow;
+ g_get_current_time( &aTimeNow );
+
+ return ( pTSource->aFireTime.tv_sec < aTimeNow.tv_sec ||
+ ( pTSource->aFireTime.tv_sec == aTimeNow.tv_sec &&
+ pTSource->aFireTime.tv_usec < aTimeNow.tv_usec ) );
+ }
+
+ static gboolean sal_gtk_timeout_dispatch( GSource *pSource, GSourceFunc, gpointer )
+ {
+ SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource);
+
+ if( !pTSource->pInstance )
+ return FALSE;
+
+ SolarMutexGuard aGuard;
+
+ sal_gtk_timeout_defer( pTSource );
+
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->maSchedCtx.mpSalTimer )
+ pSVData->maSchedCtx.mpSalTimer->CallCallback();
+
+ return G_SOURCE_REMOVE;
+ }
+
+ static GSourceFuncs sal_gtk_timeout_funcs =
+ {
+ sal_gtk_timeout_prepare,
+ sal_gtk_timeout_check,
+ sal_gtk_timeout_dispatch,
+ nullptr, nullptr, nullptr
+ };
+}
+
+static SalGtkTimeoutSource *
+create_sal_gtk_timeout( GtkSalTimer *pTimer )
+{
+ GSource *pSource = g_source_new( &sal_gtk_timeout_funcs, sizeof( SalGtkTimeoutSource ) );
+ SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource);
+ pTSource->pInstance = pTimer;
+
+ // #i36226# timers should be executed with lower priority
+ // than XEvents like in generic plugin
+ g_source_set_priority( pSource, G_PRIORITY_LOW );
+ g_source_set_can_recurse( pSource, true );
+ g_source_set_callback( pSource,
+ /* unused dummy */ g_idle_remove_by_data,
+ nullptr, nullptr );
+ g_source_attach( pSource, g_main_context_default() );
+#ifdef DBG_UTIL
+ g_source_set_name( pSource, "VCL timeout source" );
+#endif
+
+ sal_gtk_timeout_defer( pTSource );
+
+ return pTSource;
+}
+
+GtkSalTimer::GtkSalTimer()
+ : m_pTimeout(nullptr)
+ , m_nTimeoutMS(0)
+{
+}
+
+GtkSalTimer::~GtkSalTimer()
+{
+ GetGtkInstance()->RemoveTimer();
+ Stop();
+}
+
+bool GtkSalTimer::Expired()
+{
+ if( !m_pTimeout || g_source_is_destroyed( &m_pTimeout->aParent ) )
+ return false;
+
+ gint nDummy = 0;
+ GTimeVal aTimeNow;
+ g_get_current_time( &aTimeNow );
+ return !!sal_gtk_timeout_expired( m_pTimeout, &nDummy, &aTimeNow);
+}
+
+void GtkSalTimer::Start( sal_uInt64 nMS )
+{
+ // glib is not 64bit safe in this regard.
+ assert( nMS <= G_MAXINT );
+ if ( nMS > G_MAXINT )
+ nMS = G_MAXINT;
+ m_nTimeoutMS = nMS; // for restarting
+ Stop(); // FIXME: ideally re-use an existing m_pTimeout
+ m_pTimeout = create_sal_gtk_timeout( this );
+}
+
+void GtkSalTimer::Stop()
+{
+ if( m_pTimeout )
+ {
+ g_source_destroy( &m_pTimeout->aParent );
+ g_source_unref( &m_pTimeout->aParent );
+ m_pTimeout = nullptr;
+ }
+}
+
+extern "C" {
+ static gboolean call_userEventFn( void *data )
+ {
+ SolarMutexGuard aGuard;
+ const SalGenericDisplay *pDisplay = GetGenericUnixSalData()->GetDisplay();
+ if ( pDisplay )
+ {
+ GtkSalDisplay *pThisDisplay = static_cast<GtkSalData *>(data)->GetGtkDisplay();
+ assert(static_cast<const SalGenericDisplay *>(pThisDisplay) == pDisplay);
+ pThisDisplay->DispatchInternalEvent();
+ }
+ return true;
+ }
+}
+
+void GtkSalData::TriggerUserEventProcessing()
+{
+ if (m_pUserEvent)
+ g_main_context_wakeup (nullptr); // really needed ?
+ else // nothing pending anyway
+ {
+ m_pUserEvent = g_idle_source_new();
+ // tdf#110737 set user-events to a lower priority than system redraw
+ // events, which is G_PRIORITY_HIGH_IDLE + 20, so presentations
+ // queue-redraw has a chance to be fulfilled
+ g_source_set_priority (m_pUserEvent, G_PRIORITY_HIGH_IDLE + 30);
+ g_source_set_can_recurse (m_pUserEvent, true);
+ g_source_set_callback (m_pUserEvent, call_userEventFn,
+ static_cast<gpointer>(this), nullptr);
+ g_source_attach (m_pUserEvent, g_main_context_default ());
+ }
+}
+
+void GtkSalData::TriggerAllUserEventsProcessed()
+{
+ assert( m_pUserEvent );
+ g_source_destroy( m_pUserEvent );
+ g_source_unref( m_pUserEvent );
+ m_pUserEvent = nullptr;
+}
+
+void GtkSalDisplay::TriggerUserEventProcessing()
+{
+ GetGtkSalData()->TriggerUserEventProcessing();
+}
+
+void GtkSalDisplay::TriggerAllUserEventsProcessed()
+{
+ GetGtkSalData()->TriggerAllUserEventsProcessed();
+}
+
+GtkWidget* GtkSalDisplay::findGtkWidgetForNativeHandle(sal_uIntPtr hWindow) const
+{
+ for (auto pSalFrame : m_aFrames )
+ {
+ const SystemEnvData* pEnvData = pSalFrame->GetSystemData();
+ if (pEnvData->GetWindowHandle(pSalFrame) == hWindow)
+ return GTK_WIDGET(pEnvData->pWidget);
+ }
+ return nullptr;
+}
+
+void GtkSalDisplay::deregisterFrame( SalFrame* pFrame )
+{
+ if( m_pCapture == pFrame )
+ {
+ static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( false, false, false );
+ m_pCapture = nullptr;
+ }
+ SalGenericDisplay::deregisterFrame( pFrame );
+}
+
+namespace {
+
+struct ButtonOrder
+{
+ std::u16string_view m_aType;
+ int m_nPriority;
+};
+
+}
+
+int getButtonPriority(std::u16string_view rType)
+{
+ static const size_t N_TYPES = 8;
+ static const ButtonOrder aDiscardCancelSave[N_TYPES] =
+ {
+ { u"discard", 0 },
+ { u"cancel", 1 },
+ { u"close", 1 },
+ { u"no", 2 },
+ { u"open", 3 },
+ { u"save", 3 },
+ { u"yes", 3 },
+ { u"ok", 3 }
+ };
+
+ static const ButtonOrder aSaveDiscardCancel[N_TYPES] =
+ {
+ { u"open", 0 },
+ { u"save", 0 },
+ { u"yes", 0 },
+ { u"ok", 0 },
+ { u"discard", 1 },
+ { u"no", 1 },
+ { u"cancel", 2 },
+ { u"close", 2 }
+ };
+
+ const ButtonOrder* pOrder = &aDiscardCancelSave[0];
+
+ const OUString &rEnv = Application::GetDesktopEnvironment();
+
+ if (rEnv.equalsIgnoreAsciiCase("windows") ||
+ rEnv.equalsIgnoreAsciiCase("tde") ||
+ rEnv.startsWithIgnoreAsciiCase("kde"))
+ {
+ pOrder = &aSaveDiscardCancel[0];
+ }
+
+ for (size_t i = 0; i < N_TYPES; ++i, ++pOrder)
+ {
+ if (rType == pOrder->m_aType)
+ return pOrder->m_nPriority;
+ }
+
+ return -1;
+}
+
+void container_remove(GtkWidget* pContainer, GtkWidget* pChild)
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_remove(GTK_CONTAINER(pContainer), pChild);
+#else
+ assert(GTK_IS_BOX(pContainer) || GTK_IS_GRID(pContainer) || GTK_IS_POPOVER(pContainer) ||
+ GTK_IS_FIXED(pContainer) || GTK_IS_WINDOW(pContainer));
+ if (GTK_IS_BOX(pContainer))
+ gtk_box_remove(GTK_BOX(pContainer), pChild);
+ else if (GTK_IS_GRID(pContainer))
+ gtk_grid_remove(GTK_GRID(pContainer), pChild);
+ else if (GTK_IS_POPOVER(pContainer))
+ gtk_popover_set_child(GTK_POPOVER(pContainer), nullptr);
+ else if (GTK_IS_WINDOW(pContainer))
+ gtk_window_set_child(GTK_WINDOW(pContainer), nullptr);
+ else if (GTK_IS_FIXED(pContainer))
+ gtk_fixed_remove(GTK_FIXED(pContainer), pChild);
+#endif
+}
+
+void container_add(GtkWidget* pContainer, GtkWidget* pChild)
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(pContainer), pChild);
+#else
+ assert(GTK_IS_BOX(pContainer) || GTK_IS_GRID(pContainer) || GTK_IS_POPOVER(pContainer) ||
+ GTK_IS_FIXED(pContainer) || GTK_IS_WINDOW(pContainer));
+ if (GTK_IS_BOX(pContainer))
+ gtk_box_append(GTK_BOX(pContainer), pChild);
+ else if (GTK_IS_GRID(pContainer))
+ gtk_grid_attach(GTK_GRID(pContainer), pChild, 0, 0, 1, 1);
+ else if (GTK_IS_POPOVER(pContainer))
+ gtk_popover_set_child(GTK_POPOVER(pContainer), pChild);
+ else if (GTK_IS_WINDOW(pContainer))
+ gtk_window_set_child(GTK_WINDOW(pContainer), pChild);
+ else if (GTK_IS_FIXED(pContainer))
+ gtk_fixed_put(GTK_FIXED(pContainer), pChild, 0, 0);
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtkframe.cxx b/vcl/unx/gtk3/gtkframe.cxx
new file mode 100644
index 0000000000..f996b4359b
--- /dev/null
+++ b/vcl/unx/gtk3/gtkframe.cxx
@@ -0,0 +1,6416 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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_version.h>
+
+#include <unx/gtk/gtkframe.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include <unx/gtk/gtkgdi.hxx>
+#include <unx/gtk/gtksalmenu.hxx>
+#include <unx/gtk/hudawareness.h>
+#include <vcl/event.hxx>
+#include <vcl/i18nhelp.hxx>
+#include <vcl/keycodes.hxx>
+#include <unx/geninst.h>
+#include <headless/svpgdi.hxx>
+#include <sal/log.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/toolkit/unowrap.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/window.hxx>
+#include <vcl/settings.hxx>
+
+#include <gtk/gtk.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <unx/gtk/gtkbackend.hxx>
+
+#include <strings.hrc>
+#include <window.h>
+
+#include <basegfx/vector/b2ivector.hxx>
+#include <officecfg/Office/Common.hxx>
+
+#include <dlfcn.h>
+
+#include <algorithm>
+
+#if OSL_DEBUG_LEVEL > 1
+# include <cstdio>
+#endif
+
+#include <i18nlangtag/mslangid.hxx>
+
+#include <cstdlib>
+#include <cmath>
+
+#include <com/sun/star/awt/MouseButton.hpp>
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/util/XModifiable.hpp>
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+# define GDK_ALT_MASK GDK_MOD1_MASK
+# define GDK_TOPLEVEL_STATE_MAXIMIZED GDK_WINDOW_STATE_MAXIMIZED
+# define GDK_TOPLEVEL_STATE_MINIMIZED GDK_WINDOW_STATE_ICONIFIED
+# define gdk_wayland_surface_get_wl_surface gdk_wayland_window_get_wl_surface
+# define gdk_x11_surface_get_xid gdk_x11_window_get_xid
+#endif
+
+using namespace com::sun::star;
+
+int GtkSalFrame::m_nFloats = 0;
+
+static GDBusConnection* pSessionBus = nullptr;
+
+static void EnsureSessionBus()
+{
+ if (!pSessionBus)
+ pSessionBus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
+}
+
+sal_uInt16 GtkSalFrame::GetKeyModCode( guint state )
+{
+ sal_uInt16 nCode = 0;
+ if( state & GDK_SHIFT_MASK )
+ nCode |= KEY_SHIFT;
+ if( state & GDK_CONTROL_MASK )
+ nCode |= KEY_MOD1;
+ if (state & GDK_ALT_MASK)
+ nCode |= KEY_MOD2;
+ if( state & GDK_SUPER_MASK )
+ nCode |= KEY_MOD3;
+ return nCode;
+}
+
+sal_uInt16 GtkSalFrame::GetMouseModCode( guint state )
+{
+ sal_uInt16 nCode = GetKeyModCode( state );
+ if( state & GDK_BUTTON1_MASK )
+ nCode |= MOUSE_LEFT;
+ if( state & GDK_BUTTON2_MASK )
+ nCode |= MOUSE_MIDDLE;
+ if( state & GDK_BUTTON3_MASK )
+ nCode |= MOUSE_RIGHT;
+
+ return nCode;
+}
+
+// KEY_F26 is the last function key known to keycodes.hxx
+static bool IsFunctionKeyVal(guint keyval)
+{
+ return keyval >= GDK_KEY_F1 && keyval <= GDK_KEY_F26;
+}
+
+sal_uInt16 GtkSalFrame::GetKeyCode(guint keyval)
+{
+ sal_uInt16 nCode = 0;
+ if( keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9 )
+ nCode = KEY_0 + (keyval-GDK_KEY_0);
+ else if( keyval >= GDK_KEY_KP_0 && keyval <= GDK_KEY_KP_9 )
+ nCode = KEY_0 + (keyval-GDK_KEY_KP_0);
+ else if( keyval >= GDK_KEY_A && keyval <= GDK_KEY_Z )
+ nCode = KEY_A + (keyval-GDK_KEY_A );
+ else if( keyval >= GDK_KEY_a && keyval <= GDK_KEY_z )
+ nCode = KEY_A + (keyval-GDK_KEY_a );
+ else if (IsFunctionKeyVal(keyval))
+ {
+ switch( keyval )
+ {
+ // - - - - - Sun keyboard, see vcl/unx/source/app/saldisp.cxx
+ // although GDK_KEY_F1 ... GDK_KEY_L10 are known to
+ // gdk/gdkkeysyms.h, they are unlikely to be generated
+ // except possibly by Solaris systems
+ // this whole section needs review
+ case GDK_KEY_L2:
+ nCode = KEY_F12;
+ break;
+ case GDK_KEY_L3: nCode = KEY_PROPERTIES; break;
+ case GDK_KEY_L4: nCode = KEY_UNDO; break;
+ case GDK_KEY_L6: nCode = KEY_COPY; break; // KEY_F16
+ case GDK_KEY_L8: nCode = KEY_PASTE; break; // KEY_F18
+ case GDK_KEY_L10: nCode = KEY_CUT; break; // KEY_F20
+ default:
+ nCode = KEY_F1 + (keyval-GDK_KEY_F1); break;
+ }
+ }
+ else
+ {
+ switch( keyval )
+ {
+ case GDK_KEY_KP_Down:
+ case GDK_KEY_Down: nCode = KEY_DOWN; break;
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_Up: nCode = KEY_UP; break;
+ case GDK_KEY_KP_Left:
+ case GDK_KEY_Left: nCode = KEY_LEFT; break;
+ case GDK_KEY_KP_Right:
+ case GDK_KEY_Right: nCode = KEY_RIGHT; break;
+ case GDK_KEY_KP_Begin:
+ case GDK_KEY_KP_Home:
+ case GDK_KEY_Begin:
+ case GDK_KEY_Home: nCode = KEY_HOME; break;
+ case GDK_KEY_KP_End:
+ case GDK_KEY_End: nCode = KEY_END; break;
+ case GDK_KEY_KP_Page_Up:
+ case GDK_KEY_Page_Up: nCode = KEY_PAGEUP; break;
+ case GDK_KEY_KP_Page_Down:
+ case GDK_KEY_Page_Down: nCode = KEY_PAGEDOWN; break;
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_Return: nCode = KEY_RETURN; break;
+ case GDK_KEY_Escape: nCode = KEY_ESCAPE; break;
+ case GDK_KEY_ISO_Left_Tab:
+ case GDK_KEY_KP_Tab:
+ case GDK_KEY_Tab: nCode = KEY_TAB; break;
+ case GDK_KEY_BackSpace: nCode = KEY_BACKSPACE; break;
+ case GDK_KEY_KP_Space:
+ case GDK_KEY_space: nCode = KEY_SPACE; break;
+ case GDK_KEY_KP_Insert:
+ case GDK_KEY_Insert: nCode = KEY_INSERT; break;
+ case GDK_KEY_KP_Delete:
+ case GDK_KEY_Delete: nCode = KEY_DELETE; break;
+ case GDK_KEY_plus:
+ case GDK_KEY_KP_Add: nCode = KEY_ADD; break;
+ case GDK_KEY_minus:
+ case GDK_KEY_KP_Subtract: nCode = KEY_SUBTRACT; break;
+ case GDK_KEY_asterisk:
+ case GDK_KEY_KP_Multiply: nCode = KEY_MULTIPLY; break;
+ case GDK_KEY_slash:
+ case GDK_KEY_KP_Divide: nCode = KEY_DIVIDE; break;
+ case GDK_KEY_period: nCode = KEY_POINT; break;
+ case GDK_KEY_decimalpoint: nCode = KEY_POINT; break;
+ case GDK_KEY_comma: nCode = KEY_COMMA; break;
+ case GDK_KEY_less: nCode = KEY_LESS; break;
+ case GDK_KEY_greater: nCode = KEY_GREATER; break;
+ case GDK_KEY_KP_Equal:
+ case GDK_KEY_equal: nCode = KEY_EQUAL; break;
+ case GDK_KEY_Find: nCode = KEY_FIND; break;
+ case GDK_KEY_Menu: nCode = KEY_CONTEXTMENU;break;
+ case GDK_KEY_Help: nCode = KEY_HELP; break;
+ case GDK_KEY_Undo: nCode = KEY_UNDO; break;
+ case GDK_KEY_Redo: nCode = KEY_REPEAT; break;
+ // on a sun keyboard this actually is usually SunXK_Stop = 0x0000FF69 (XK_Cancel),
+ // but VCL doesn't have a key definition for that
+ case GDK_KEY_Cancel: nCode = KEY_F11; break;
+ case GDK_KEY_KP_Decimal:
+ case GDK_KEY_KP_Separator: nCode = KEY_DECIMAL; break;
+ case GDK_KEY_asciitilde: nCode = KEY_TILDE; break;
+ case GDK_KEY_leftsinglequotemark:
+ case GDK_KEY_quoteleft: nCode = KEY_QUOTELEFT; break;
+ case GDK_KEY_bracketleft: nCode = KEY_BRACKETLEFT; break;
+ case GDK_KEY_bracketright: nCode = KEY_BRACKETRIGHT; break;
+ case GDK_KEY_semicolon: nCode = KEY_SEMICOLON; break;
+ case GDK_KEY_quoteright: nCode = KEY_QUOTERIGHT; break;
+ case GDK_KEY_braceright: nCode = KEY_RIGHTCURLYBRACKET; break;
+ case GDK_KEY_numbersign: nCode = KEY_NUMBERSIGN; break;
+ case GDK_KEY_Forward: nCode = KEY_XF86FORWARD; break;
+ case GDK_KEY_Back: nCode = KEY_XF86BACK; break;
+ case GDK_KEY_colon: nCode = KEY_COLON; break;
+ // some special cases, also see saldisp.cxx
+ // - - - - - - - - - - - - - Apollo - - - - - - - - - - - - - 0x1000
+ // These can be found in ap_keysym.h
+ case 0x1000FF02: // apXK_Copy
+ nCode = KEY_COPY;
+ break;
+ case 0x1000FF03: // apXK_Cut
+ nCode = KEY_CUT;
+ break;
+ case 0x1000FF04: // apXK_Paste
+ nCode = KEY_PASTE;
+ break;
+ case 0x1000FF14: // apXK_Repeat
+ nCode = KEY_REPEAT;
+ break;
+ // Exit, Save
+ // - - - - - - - - - - - - - - D E C - - - - - - - - - - - - - 0x1000
+ // These can be found in DECkeysym.h
+ case 0x1000FF00:
+ nCode = KEY_DELETE;
+ break;
+ // - - - - - - - - - - - - - - H P - - - - - - - - - - - - - 0x1000
+ // These can be found in HPkeysym.h
+ case 0x1000FF73: // hpXK_DeleteChar
+ nCode = KEY_DELETE;
+ break;
+ case 0x1000FF74: // hpXK_BackTab
+ case 0x1000FF75: // hpXK_KP_BackTab
+ nCode = KEY_TAB;
+ break;
+ // - - - - - - - - - - - - - - I B M - - - - - - - - - - - - -
+ // - - - - - - - - - - - - - - O S F - - - - - - - - - - - - - 0x1004
+ // These also can be found in HPkeysym.h
+ case 0x1004FF02: // osfXK_Copy
+ nCode = KEY_COPY;
+ break;
+ case 0x1004FF03: // osfXK_Cut
+ nCode = KEY_CUT;
+ break;
+ case 0x1004FF04: // osfXK_Paste
+ nCode = KEY_PASTE;
+ break;
+ case 0x1004FF07: // osfXK_BackTab
+ nCode = KEY_TAB;
+ break;
+ case 0x1004FF08: // osfXK_BackSpace
+ nCode = KEY_BACKSPACE;
+ break;
+ case 0x1004FF1B: // osfXK_Escape
+ nCode = KEY_ESCAPE;
+ break;
+ // Up, Down, Left, Right, PageUp, PageDown
+ // - - - - - - - - - - - - - - S C O - - - - - - - - - - - - -
+ // - - - - - - - - - - - - - - S G I - - - - - - - - - - - - - 0x1007
+ // - - - - - - - - - - - - - - S N I - - - - - - - - - - - - -
+ // - - - - - - - - - - - - - - S U N - - - - - - - - - - - - - 0x1005
+ // These can be found in Sunkeysym.h
+ case 0x1005FF10: // SunXK_F36
+ nCode = KEY_F11;
+ break;
+ case 0x1005FF11: // SunXK_F37
+ nCode = KEY_F12;
+ break;
+ case 0x1005FF70: // SunXK_Props
+ nCode = KEY_PROPERTIES;
+ break;
+ case 0x1005FF71: // SunXK_Front
+ nCode = KEY_FRONT;
+ break;
+ case 0x1005FF72: // SunXK_Copy
+ nCode = KEY_COPY;
+ break;
+ case 0x1005FF73: // SunXK_Open
+ nCode = KEY_OPEN;
+ break;
+ case 0x1005FF74: // SunXK_Paste
+ nCode = KEY_PASTE;
+ break;
+ case 0x1005FF75: // SunXK_Cut
+ nCode = KEY_CUT;
+ break;
+ // - - - - - - - - - - - - - X F 8 6 - - - - - - - - - - - - - 0x1008
+ // These can be found in XF86keysym.h
+ // but more importantly they are also available GTK/Gdk version 3
+ // and hence are already provided in gdk/gdkkeysyms.h, and hence
+ // in gdk/gdk.h
+ case GDK_KEY_Copy: nCode = KEY_COPY; break; // 0x1008ff57
+ case GDK_KEY_Cut: nCode = KEY_CUT; break; // 0x1008ff58
+ case GDK_KEY_Open: nCode = KEY_OPEN; break; // 0x1008ff6b
+ case GDK_KEY_Paste: nCode = KEY_PASTE; break; // 0x1008ff6d
+ }
+ }
+
+ return nCode;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+guint GtkSalFrame::GetKeyValFor(GdkKeymap* pKeyMap, guint16 hardware_keycode, guint8 group)
+{
+ guint updated_keyval = 0;
+ gdk_keymap_translate_keyboard_state(pKeyMap, hardware_keycode,
+ GdkModifierType(0), group, &updated_keyval, nullptr, nullptr, nullptr);
+ return updated_keyval;
+}
+#endif
+
+namespace {
+
+// F10 means either KEY_F10 or KEY_MENU, which has to be decided
+// in the independent part.
+struct KeyAlternate
+{
+ sal_uInt16 nKeyCode;
+ sal_Unicode nCharCode;
+ KeyAlternate() : nKeyCode( 0 ), nCharCode( 0 ) {}
+ KeyAlternate( sal_uInt16 nKey, sal_Unicode nChar = 0 ) : nKeyCode( nKey ), nCharCode( nChar ) {}
+};
+
+}
+
+static KeyAlternate
+GetAlternateKeyCode( const sal_uInt16 nKeyCode )
+{
+ KeyAlternate aAlternate;
+
+ switch( nKeyCode )
+ {
+ case KEY_F10: aAlternate = KeyAlternate( KEY_MENU );break;
+ case KEY_F24: aAlternate = KeyAlternate( KEY_SUBTRACT, '-' );break;
+ }
+
+ return aAlternate;
+}
+
+#if OSL_DEBUG_LEVEL > 0
+static bool dumpframes = false;
+#endif
+
+bool GtkSalFrame::doKeyCallback( guint state,
+ guint keyval,
+ guint16 hardware_keycode,
+ guint8 group,
+ sal_Unicode aOrigCode,
+ bool bDown,
+ bool bSendRelease
+ )
+{
+ SalKeyEvent aEvent;
+
+ aEvent.mnCharCode = aOrigCode;
+ aEvent.mnRepeat = 0;
+
+ vcl::DeletionListener aDel( this );
+
+#if OSL_DEBUG_LEVEL > 0
+ const char* pKeyDebug = getenv("VCL_GTK3_PAINTDEBUG");
+
+ if (pKeyDebug && *pKeyDebug == '1')
+ {
+ if (bDown)
+ {
+ // shift-zero forces a re-draw and event is swallowed
+ if (keyval == GDK_KEY_0)
+ {
+ SAL_INFO("vcl.gtk3", "force widget_queue_draw.");
+ gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
+ return false;
+ }
+ else if (keyval == GDK_KEY_1)
+ {
+ SAL_INFO("vcl.gtk3", "force repaint all.");
+ TriggerPaintEvent();
+ return false;
+ }
+ else if (keyval == GDK_KEY_2)
+ {
+ dumpframes = !dumpframes;
+ SAL_INFO("vcl.gtk3", "toggle dump frames to " << dumpframes);
+ return false;
+ }
+ }
+ }
+#endif
+
+ /*
+ * #i42122# translate all keys with Ctrl and/or Alt to group 0 else
+ * shortcuts (e.g. Ctrl-o) will not work but be inserted by the
+ * application
+ *
+ * #i52338# do this for all keys that the independent part has no key code
+ * for
+ *
+ * fdo#41169 rather than use group 0, detect if there is a group which can
+ * be used to input Latin text and use that if possible
+ */
+ aEvent.mnCode = GetKeyCode( keyval );
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( aEvent.mnCode == 0 )
+ {
+ gint best_group = SAL_MAX_INT32;
+
+ // Try and find Latin layout
+ GdkKeymap* keymap = gdk_keymap_get_default();
+ GdkKeymapKey *keys;
+ gint n_keys;
+ if (gdk_keymap_get_entries_for_keyval(keymap, GDK_KEY_A, &keys, &n_keys))
+ {
+ // Find the lowest group that supports Latin layout
+ for (gint i = 0; i < n_keys; ++i)
+ {
+ if (keys[i].level != 0 && keys[i].level != 1)
+ continue;
+ best_group = std::min(best_group, keys[i].group);
+ if (best_group == 0)
+ break;
+ }
+ g_free(keys);
+ }
+
+ //Unavailable, go with original group then I suppose
+ if (best_group == SAL_MAX_INT32)
+ best_group = group;
+
+ guint updated_keyval = GetKeyValFor(keymap, hardware_keycode, best_group);
+ aEvent.mnCode = GetKeyCode(updated_keyval);
+ }
+#else
+ (void)hardware_keycode;
+ (void)group;
+#endif
+
+ aEvent.mnCode |= GetKeyModCode( state );
+
+ bool bStopProcessingKey;
+ if (bDown)
+ {
+ // tdf#152404 Commit uncommitted text before dispatching key shortcuts. In
+ // certain cases such as pressing Control-Alt-C in a Writer document while
+ // there is uncommitted text will call GtkSalFrame::EndExtTextInput() which
+ // will dispatch a SalEvent::EndExtTextInput event. Writer's handler for that
+ // event will delete the uncommitted text and then insert the committed text
+ // but LibreOffice will crash when deleting the uncommitted text because
+ // deletion of the text also removes and deletes the newly inserted comment.
+ if (m_pIMHandler && !m_pIMHandler->m_aInputEvent.maText.isEmpty() && (aEvent.mnCode & (KEY_MOD1 | KEY_MOD2)))
+ m_pIMHandler->doCallEndExtTextInput();
+
+ bStopProcessingKey = CallCallbackExc(SalEvent::KeyInput, &aEvent);
+ // #i46889# copy AlternateKeyCode handling from generic plugin
+ if (!bStopProcessingKey)
+ {
+ KeyAlternate aAlternate = GetAlternateKeyCode( aEvent.mnCode );
+ if( aAlternate.nKeyCode )
+ {
+ aEvent.mnCode = aAlternate.nKeyCode;
+ if( aAlternate.nCharCode )
+ aEvent.mnCharCode = aAlternate.nCharCode;
+ bStopProcessingKey = CallCallbackExc(SalEvent::KeyInput, &aEvent);
+ }
+ }
+ if( bSendRelease && ! aDel.isDeleted() )
+ {
+ CallCallbackExc(SalEvent::KeyUp, &aEvent);
+ }
+ }
+ else
+ bStopProcessingKey = CallCallbackExc(SalEvent::KeyUp, &aEvent);
+ return bStopProcessingKey;
+}
+
+GtkSalFrame::GtkSalFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
+ : m_nXScreen( getDisplay()->GetDefaultXScreen() )
+ , m_pHeaderBar(nullptr)
+ , m_bGraphics(false)
+ , m_nSetFocusSignalId(0)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_aSmoothScrollIdle("GtkSalFrame m_aSmoothScrollIdle")
+#endif
+{
+ getDisplay()->registerFrame( this );
+ m_bDefaultPos = true;
+ m_bDefaultSize = ( (nStyle & SalFrameStyleFlags::SIZEABLE) && ! pParent );
+ Init( pParent, nStyle );
+}
+
+GtkSalFrame::GtkSalFrame( SystemParentData* pSysData )
+ : m_nXScreen( getDisplay()->GetDefaultXScreen() )
+ , m_pHeaderBar(nullptr)
+ , m_bGraphics(false)
+ , m_nSetFocusSignalId(0)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_aSmoothScrollIdle("GtkSalFrame m_aSmoothScrollIdle")
+#endif
+{
+ getDisplay()->registerFrame( this );
+ // permanently ignore errors from our unruly children ...
+ GetGenericUnixSalData()->ErrorTrapPush();
+ m_bDefaultPos = true;
+ m_bDefaultSize = true;
+ Init( pSysData );
+}
+
+// AppMenu watch functions.
+
+static void ObjectDestroyedNotify( gpointer data )
+{
+ if ( data ) {
+ g_object_unref( data );
+ }
+}
+
+#if !GTK_CHECK_VERSION(4,0,0)
+static void hud_activated( gboolean hud_active, gpointer user_data )
+{
+ if ( hud_active )
+ {
+ SolarMutexGuard aGuard;
+ GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
+ GtkSalMenu* pSalMenu = reinterpret_cast< GtkSalMenu* >( pSalFrame->GetMenu() );
+
+ if ( pSalMenu )
+ pSalMenu->UpdateFull();
+ }
+}
+#endif
+
+static void attach_menu_model(GtkSalFrame* pSalFrame)
+{
+ GtkWidget* pWidget = pSalFrame->getWindow();
+ GdkSurface* gdkWindow = widget_get_surface(pWidget);
+
+ if ( gdkWindow == nullptr || g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) != nullptr )
+ return;
+
+ // Create menu model and action group attached to this frame.
+ GMenuModel* pMenuModel = G_MENU_MODEL( g_lo_menu_new() );
+ GActionGroup* pActionGroup = reinterpret_cast<GActionGroup*>(g_lo_action_group_new());
+
+ // Set window properties.
+ g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-menubar", pMenuModel, ObjectDestroyedNotify );
+ g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-action-group", pActionGroup, ObjectDestroyedNotify );
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ // Get a DBus session connection.
+ EnsureSessionBus();
+ if (!pSessionBus)
+ return;
+
+ // Generate menu paths.
+ sal_uIntPtr windowId = GtkSalFrame::GetNativeWindowHandle(pWidget);
+ gchar* aDBusWindowPath = g_strdup_printf( "/org/libreoffice/window/%lu", windowId );
+ gchar* aDBusMenubarPath = g_strdup_printf( "/org/libreoffice/window/%lu/menus/menubar", windowId );
+
+ GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
+#if defined(GDK_WINDOWING_X11)
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_ID", "org.libreoffice" );
+ gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_MENUBAR_OBJECT_PATH", aDBusMenubarPath );
+ gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_WINDOW_OBJECT_PATH", aDBusWindowPath );
+ gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_OBJECT_PATH", "/org/libreoffice" );
+ gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_UNIQUE_BUS_NAME", g_dbus_connection_get_unique_name( pSessionBus ) );
+ }
+#endif
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
+ {
+ gdk_wayland_window_set_dbus_properties_libgtk_only(gdkWindow, "org.libreoffice",
+ nullptr,
+ aDBusMenubarPath,
+ aDBusWindowPath,
+ "/org/libreoffice",
+ g_dbus_connection_get_unique_name( pSessionBus ));
+ }
+#endif
+ // Publish the menu model and the action group.
+ SAL_INFO("vcl.unity", "exporting menu model at " << pMenuModel << " for window " << windowId);
+ pSalFrame->m_nMenuExportId = g_dbus_connection_export_menu_model (pSessionBus, aDBusMenubarPath, pMenuModel, nullptr);
+ SAL_INFO("vcl.unity", "exporting action group at " << pActionGroup << " for window " << windowId);
+ pSalFrame->m_nActionGroupExportId = g_dbus_connection_export_action_group( pSessionBus, aDBusWindowPath, pActionGroup, nullptr);
+ pSalFrame->m_nHudAwarenessId = hud_awareness_register( pSessionBus, aDBusMenubarPath, hud_activated, pSalFrame, nullptr, nullptr );
+
+ g_free( aDBusWindowPath );
+ g_free( aDBusMenubarPath );
+#endif
+}
+
+void on_registrar_available( GDBusConnection * /*connection*/,
+ const gchar * /*name*/,
+ const gchar * /*name_owner*/,
+ gpointer user_data )
+{
+ SolarMutexGuard aGuard;
+
+ GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
+
+ SalMenu* pSalMenu = pSalFrame->GetMenu();
+
+ if ( pSalMenu != nullptr )
+ {
+ GtkSalMenu* pGtkSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
+ pGtkSalMenu->EnableUnity(true);
+ }
+}
+
+// This is called when the registrar becomes unavailable. It shows the menubar.
+void on_registrar_unavailable( GDBusConnection * /*connection*/,
+ const gchar * /*name*/,
+ gpointer user_data )
+{
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.unity", "on_registrar_unavailable");
+
+ GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
+
+ SalMenu* pSalMenu = pSalFrame->GetMenu();
+
+ if ( pSalMenu ) {
+ GtkSalMenu* pGtkSalMenu = static_cast< GtkSalMenu* >( pSalMenu );
+ pGtkSalMenu->EnableUnity(false);
+ }
+}
+
+void GtkSalFrame::EnsureAppMenuWatch()
+{
+ if ( m_nWatcherId )
+ return;
+
+ // Get a DBus session connection.
+ EnsureSessionBus();
+ if (!pSessionBus)
+ return;
+
+ // Publish the menu only if AppMenu registrar is available.
+ m_nWatcherId = g_bus_watch_name_on_connection( pSessionBus,
+ "com.canonical.AppMenu.Registrar",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ on_registrar_available,
+ on_registrar_unavailable,
+ this,
+ nullptr );
+}
+
+void GtkSalFrame::InvalidateGraphics()
+{
+ if( m_pGraphics )
+ {
+ m_bGraphics = false;
+ }
+}
+
+GtkSalFrame::~GtkSalFrame()
+{
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_aSmoothScrollIdle.Stop();
+ m_aSmoothScrollIdle.ClearInvokeHandler();
+#endif
+
+ if (m_pDropTarget)
+ {
+ m_pDropTarget->deinitialize();
+ m_pDropTarget = nullptr;
+ }
+
+ if (m_pDragSource)
+ {
+ m_pDragSource->deinitialize();
+ m_pDragSource= nullptr;
+ }
+
+ InvalidateGraphics();
+
+ if (m_pParent)
+ {
+ m_pParent->m_aChildren.remove( this );
+ }
+
+ getDisplay()->deregisterFrame( this );
+
+ if( m_pRegion )
+ {
+ cairo_region_destroy( m_pRegion );
+ }
+
+ m_pIMHandler.reset();
+
+ //tdf#108705 remove grabs on event widget before
+ //destroying event widget
+ while (m_nGrabLevel)
+ removeGrabLevel();
+
+ {
+ SolarMutexGuard aGuard;
+
+ if (m_nWatcherId)
+ g_bus_unwatch_name(m_nWatcherId);
+
+ if (m_nPortalSettingChangedSignalId)
+ g_signal_handler_disconnect(m_pSettingsPortal, m_nPortalSettingChangedSignalId);
+
+ if (m_pSettingsPortal)
+ g_object_unref(m_pSettingsPortal);
+
+ if (m_nSessionClientSignalId)
+ g_signal_handler_disconnect(m_pSessionClient, m_nSessionClientSignalId);
+
+ if (m_pSessionClient)
+ g_object_unref(m_pSessionClient);
+
+ if (m_pSessionManager)
+ g_object_unref(m_pSessionManager);
+ }
+
+ GtkWidget *pEventWidget = getMouseEventWidget();
+ for (auto handler_id : m_aMouseSignalIds)
+ g_signal_handler_disconnect(G_OBJECT(pEventWidget), handler_id);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( m_pFixedContainer )
+ gtk_widget_destroy( GTK_WIDGET( m_pFixedContainer ) );
+ if( m_pEventBox )
+ gtk_widget_destroy( GTK_WIDGET(m_pEventBox) );
+ if( m_pTopLevelGrid )
+ gtk_widget_destroy( GTK_WIDGET(m_pTopLevelGrid) );
+#else
+ g_signal_handler_disconnect(G_OBJECT(gtk_widget_get_display(pEventWidget)), m_nSettingChangedSignalId);
+#endif
+ {
+ SolarMutexGuard aGuard;
+
+ if( m_pWindow )
+ {
+ g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", nullptr );
+
+ if ( pSessionBus )
+ {
+ if ( m_nHudAwarenessId )
+ hud_awareness_unregister( pSessionBus, m_nHudAwarenessId );
+ if ( m_nMenuExportId )
+ g_dbus_connection_unexport_menu_model( pSessionBus, m_nMenuExportId );
+ if ( m_nActionGroupExportId )
+ g_dbus_connection_unexport_action_group( pSessionBus, m_nActionGroupExportId );
+ }
+ m_xFrameWeld.reset();
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_widget_destroy( m_pWindow );
+#else
+ if (GTK_IS_WINDOW(m_pWindow))
+ gtk_window_destroy(GTK_WINDOW(m_pWindow));
+ else
+ g_clear_pointer(&m_pWindow, gtk_widget_unparent);
+#endif
+ }
+ }
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ if( m_pForeignParent )
+ g_object_unref( G_OBJECT( m_pForeignParent ) );
+ if( m_pForeignTopLevel )
+ g_object_unref( G_OBJECT( m_pForeignTopLevel) );
+#endif
+
+ m_pGraphics.reset();
+
+ if (m_pSurface)
+ cairo_surface_destroy(m_pSurface);
+}
+
+void GtkSalFrame::moveWindow( tools::Long nX, tools::Long nY )
+{
+ if( isChild( false ) )
+ {
+ GtkWidget* pParent = m_pParent ? gtk_widget_get_parent(m_pWindow) : nullptr;
+ // tdf#130414 it's possible that we were reparented and are no longer inside
+ // our original GtkFixed parent
+ if (pParent && GTK_IS_FIXED(pParent))
+ {
+ gtk_fixed_move( GTK_FIXED(pParent),
+ m_pWindow,
+ nX - m_pParent->maGeometry.x(), nY - m_pParent->maGeometry.y() );
+ }
+ return;
+ }
+#if GTK_CHECK_VERSION(4,0,0)
+ if (GTK_IS_POPOVER(m_pWindow))
+ {
+ GdkRectangle aRect;
+ aRect.x = nX;
+ aRect.y = nY;
+ aRect.width = 1;
+ aRect.height = 1;
+ gtk_popover_set_pointing_to(GTK_POPOVER(m_pWindow), &aRect);
+ return;
+ }
+#else
+ gtk_window_move( GTK_WINDOW(m_pWindow), nX, nY );
+#endif
+}
+
+void GtkSalFrame::widget_set_size_request(tools::Long nWidth, tools::Long nHeight)
+{
+ gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), nWidth, nHeight );
+#if GTK_CHECK_VERSION(4,0,0)
+ gtk_widget_set_size_request(GTK_WIDGET(m_pDrawingArea), nWidth, nHeight );
+#endif
+}
+
+void GtkSalFrame::window_resize(tools::Long nWidth, tools::Long nHeight)
+{
+ m_nWidthRequest = nWidth;
+ m_nHeightRequest = nHeight;
+ if (!GTK_IS_WINDOW(m_pWindow))
+ {
+#if GTK_CHECK_VERSION(4,0,0)
+ gtk_widget_set_size_request(GTK_WIDGET(m_pDrawingArea), nWidth, nHeight);
+#endif
+ return;
+ }
+ gtk_window_set_default_size(GTK_WINDOW(m_pWindow), nWidth, nHeight);
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_window_resize(GTK_WINDOW(m_pWindow), nWidth, nHeight);
+#endif
+}
+
+void GtkSalFrame::resizeWindow( tools::Long nWidth, tools::Long nHeight )
+{
+ if( isChild( false ) )
+ {
+ widget_set_size_request(nWidth, nHeight);
+ }
+ else if( ! isChild( true, false ) )
+ window_resize(nWidth, nHeight);
+}
+
+#if !GTK_CHECK_VERSION(4,0,0)
+// tdf#124694 GtkFixed takes the max size of all its children as its
+// preferred size, causing it to not clip its child, but grow instead.
+
+static void
+ooo_fixed_get_preferred_height(GtkWidget*, gint *minimum, gint *natural)
+{
+ *minimum = 0;
+ *natural = 0;
+}
+
+static void
+ooo_fixed_get_preferred_width(GtkWidget*, gint *minimum, gint *natural)
+{
+ *minimum = 0;
+ *natural = 0;
+}
+
+static void
+ooo_fixed_class_init(GtkFixedClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+ widget_class->get_accessible = ooo_fixed_get_accessible;
+ widget_class->get_preferred_height = ooo_fixed_get_preferred_height;
+ widget_class->get_preferred_width = ooo_fixed_get_preferred_width;
+}
+
+/*
+ * Always use a sub-class of GtkFixed we can tag for a11y. This allows us to
+ * utilize GAIL for the toplevel window and toolkit implementation incl.
+ * key event listener support ..
+ */
+
+GType
+ooo_fixed_get_type()
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo tinfo =
+ {
+ sizeof (GtkFixedClass),
+ nullptr, /* base init */
+ nullptr, /* base finalize */
+ reinterpret_cast<GClassInitFunc>(ooo_fixed_class_init), /* class init */
+ nullptr, /* class finalize */
+ nullptr, /* class data */
+ sizeof (GtkFixed), /* instance size */
+ 0, /* nb preallocs */
+ nullptr, /* instance init */
+ nullptr /* value table */
+ };
+
+ type = g_type_register_static( GTK_TYPE_FIXED, "OOoFixed",
+ &tinfo, GTypeFlags(0));
+ }
+
+ return type;
+}
+
+#endif
+
+void GtkSalFrame::updateScreenNumber()
+{
+#if !GTK_CHECK_VERSION(4,0,0)
+ int nScreen = 0;
+ GdkScreen *pScreen = gtk_widget_get_screen( m_pWindow );
+ if( pScreen )
+ nScreen = getDisplay()->getSystem()->getScreenMonitorIdx( pScreen, maGeometry.x(), maGeometry.y() );
+ maGeometry.setScreen(nScreen);
+#endif
+}
+
+GtkWidget *GtkSalFrame::getMouseEventWidget() const
+{
+#if !GTK_CHECK_VERSION(4,0,0)
+ return GTK_WIDGET(m_pEventBox);
+#else
+ return GTK_WIDGET(m_pFixedContainer);
+#endif
+}
+
+static void damaged(void *handle,
+ sal_Int32 nExtentsX, sal_Int32 nExtentsY,
+ sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(handle);
+ pThis->damaged(nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight);
+}
+
+void GtkSalFrame::InitCommon()
+{
+ m_pSurface = nullptr;
+ m_nGrabLevel = 0;
+ m_bSalObjectSetPosSize = false;
+ m_nPortalSettingChangedSignalId = 0;
+ m_nSessionClientSignalId = 0;
+ m_pSettingsPortal = nullptr;
+ m_pSessionManager = nullptr;
+ m_pSessionClient = nullptr;
+
+ m_aDamageHandler.handle = this;
+ m_aDamageHandler.damaged = ::damaged;
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_aSmoothScrollIdle.SetInvokeHandler(LINK(this, GtkSalFrame, AsyncScroll));
+#endif
+
+ m_pTopLevelGrid = GTK_GRID(gtk_grid_new());
+ container_add(m_pWindow, GTK_WIDGET(m_pTopLevelGrid));
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_pEventBox = GTK_EVENT_BOX(gtk_event_box_new());
+ gtk_widget_add_events( GTK_WIDGET(m_pEventBox),
+ GDK_ALL_EVENTS_MASK );
+ gtk_widget_set_vexpand(GTK_WIDGET(m_pEventBox), true);
+ gtk_widget_set_hexpand(GTK_WIDGET(m_pEventBox), true);
+ gtk_grid_attach(m_pTopLevelGrid, GTK_WIDGET(m_pEventBox), 0, 0, 1, 1);
+#endif
+
+ // add the fixed container child,
+ // fixed is needed since we have to position plugin windows
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_pFixedContainer = GTK_FIXED(g_object_new( ooo_fixed_get_type(), nullptr ));
+ m_pDrawingArea = m_pFixedContainer;
+#else
+ m_pOverlay = GTK_OVERLAY(gtk_overlay_new());
+#if GTK_CHECK_VERSION(4,9,0)
+ m_pFixedContainer = GTK_FIXED(g_object_new( ooo_fixed_get_type(), nullptr ));
+#else
+ m_pFixedContainer = GTK_FIXED(gtk_fixed_new());
+#endif
+ m_pDrawingArea = GTK_DRAWING_AREA(gtk_drawing_area_new());
+#endif
+ if (GTK_IS_WINDOW(m_pWindow))
+ {
+ Size aDefWindowSize = calcDefaultSize();
+ gtk_window_set_default_size(GTK_WINDOW(m_pWindow), aDefWindowSize.Width(), aDefWindowSize.Height());
+ }
+ gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), true);
+ gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), 1, 1);
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_container_add( GTK_CONTAINER(m_pEventBox), GTK_WIDGET(m_pFixedContainer) );
+#else
+ gtk_widget_set_vexpand(GTK_WIDGET(m_pOverlay), true);
+ gtk_widget_set_hexpand(GTK_WIDGET(m_pOverlay), true);
+ gtk_grid_attach(m_pTopLevelGrid, GTK_WIDGET(m_pOverlay), 0, 0, 1, 1);
+ gtk_overlay_set_child(m_pOverlay, GTK_WIDGET(m_pDrawingArea));
+ gtk_overlay_add_overlay(m_pOverlay, GTK_WIDGET(m_pFixedContainer));
+#endif
+
+ GtkWidget *pEventWidget = getMouseEventWidget();
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_widget_set_app_paintable(GTK_WIDGET(m_pFixedContainer), true);
+ gtk_widget_set_redraw_on_allocate(GTK_WIDGET(m_pFixedContainer), false);
+#endif
+
+#if GTK_CHECK_VERSION(4,0,0)
+ m_nSettingChangedSignalId = g_signal_connect(G_OBJECT(gtk_widget_get_display(pEventWidget)), "setting-changed", G_CALLBACK(signalStyleUpdated), this);
+#else
+ // use pEventWidget instead of m_pWindow to avoid infinite event loop under Linux Mint Mate 18.3
+ g_signal_connect(G_OBJECT(pEventWidget), "style-updated", G_CALLBACK(signalStyleUpdated), this);
+#endif
+ gtk_widget_set_has_tooltip(pEventWidget, true);
+ // connect signals
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "query-tooltip", G_CALLBACK(signalTooltipQuery), this ));
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-press-event", G_CALLBACK(signalButton), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-release-event", G_CALLBACK(signalButton), this ));
+
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "motion-notify-event", G_CALLBACK(signalMotion), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "leave-notify-event", G_CALLBACK(signalCrossing), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "enter-notify-event", G_CALLBACK(signalCrossing), this ));
+
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "scroll-event", G_CALLBACK(signalScroll), this ));
+#else
+ GtkGesture *pClick = gtk_gesture_click_new();
+ gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pClick), 0);
+ // use GTK_PHASE_TARGET instead of default GTK_PHASE_BUBBLE because I don't
+ // want click events in gtk widgets inside the overlay, e.g. the font size
+ // combobox GtkEntry in the toolbar, to be propagated down to this widget
+ gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(pClick), GTK_PHASE_TARGET);
+ gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pClick));
+ g_signal_connect(pClick, "pressed", G_CALLBACK(gesturePressed), this);
+ g_signal_connect(pClick, "released", G_CALLBACK(gestureReleased), this);
+
+ GtkEventController* pMotionController = gtk_event_controller_motion_new();
+ g_signal_connect(pMotionController, "motion", G_CALLBACK(signalMotion), this);
+ g_signal_connect(pMotionController, "enter", G_CALLBACK(signalEnter), this);
+ g_signal_connect(pMotionController, "leave", G_CALLBACK(signalLeave), this);
+ gtk_widget_add_controller(pEventWidget, pMotionController);
+
+ GtkEventController* pScrollController = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
+ g_signal_connect(pScrollController, "scroll", G_CALLBACK(signalScroll), this);
+ gtk_widget_add_controller(pEventWidget, pScrollController);
+#endif
+
+#if GTK_CHECK_VERSION(4,0,0)
+ GtkGesture* pZoomGesture = gtk_gesture_zoom_new();
+ gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pZoomGesture));
+#else
+ GtkGesture* pZoomGesture = gtk_gesture_zoom_new(GTK_WIDGET(pEventWidget));
+ g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pZoomGesture);
+#endif
+ gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(pZoomGesture),
+ GTK_PHASE_TARGET);
+ // Note that the default zoom gesture signal handler needs to run first to setup correct
+ // scale delta. Otherwise the first "begin" event will always contain scale delta of infinity.
+ g_signal_connect_after(pZoomGesture, "begin", G_CALLBACK(signalZoomBegin), this);
+ g_signal_connect_after(pZoomGesture, "update", G_CALLBACK(signalZoomUpdate), this);
+ g_signal_connect_after(pZoomGesture, "end", G_CALLBACK(signalZoomEnd), this);
+
+#if GTK_CHECK_VERSION(4,0,0)
+ GtkGesture* pRotateGesture = gtk_gesture_rotate_new();
+ gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pRotateGesture));
+#else
+ GtkGesture* pRotateGesture = gtk_gesture_rotate_new(GTK_WIDGET(pEventWidget));
+ g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pRotateGesture);
+#endif
+ gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(pRotateGesture),
+ GTK_PHASE_TARGET);
+ g_signal_connect(pRotateGesture, "begin", G_CALLBACK(signalRotateBegin), this);
+ g_signal_connect(pRotateGesture, "update", G_CALLBACK(signalRotateUpdate), this);
+ g_signal_connect(pRotateGesture, "end", G_CALLBACK(signalRotateEnd), this);
+
+ //Drop Target Stuff
+#if GTK_CHECK_VERSION(4,0,0)
+ GtkDropTargetAsync* pDropTarget = gtk_drop_target_async_new(nullptr, GdkDragAction(GDK_ACTION_ALL));
+ g_signal_connect(G_OBJECT(pDropTarget), "drag-enter", G_CALLBACK(signalDragMotion), this);
+ g_signal_connect(G_OBJECT(pDropTarget), "drag-motion", G_CALLBACK(signalDragMotion), this);
+ g_signal_connect(G_OBJECT(pDropTarget), "drag-leave", G_CALLBACK(signalDragLeave), this);
+ g_signal_connect(G_OBJECT(pDropTarget), "drop", G_CALLBACK(signalDragDrop), this);
+ gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pDropTarget));
+#else
+ gtk_drag_dest_set(GTK_WIDGET(pEventWidget), GtkDestDefaults(0), nullptr, 0, GdkDragAction(0));
+ gtk_drag_dest_set_track_motion(GTK_WIDGET(pEventWidget), true);
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-motion", G_CALLBACK(signalDragMotion), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-drop", G_CALLBACK(signalDragDrop), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-received", G_CALLBACK(signalDragDropReceived), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-leave", G_CALLBACK(signalDragLeave), this ));
+#endif
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ //Drag Source Stuff
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-end", G_CALLBACK(signalDragEnd), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-failed", G_CALLBACK(signalDragFailed), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-delete", G_CALLBACK(signalDragDelete), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-get", G_CALLBACK(signalDragDataGet), this ));
+#endif
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ g_signal_connect( G_OBJECT(m_pFixedContainer), "draw", G_CALLBACK(signalDraw), this );
+ g_signal_connect( G_OBJECT(m_pFixedContainer), "size-allocate", G_CALLBACK(sizeAllocated), this );
+#else
+ gtk_drawing_area_set_draw_func(m_pDrawingArea, signalDraw, this, nullptr);
+ g_signal_connect(G_OBJECT(m_pDrawingArea), "resize", G_CALLBACK(sizeAllocated), this);
+#endif
+
+ g_signal_connect(G_OBJECT(m_pFixedContainer), "realize", G_CALLBACK(signalRealize), this);
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ GtkGesture *pSwipe = gtk_gesture_swipe_new(pEventWidget);
+ g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pSwipe);
+#else
+ GtkGesture *pSwipe = gtk_gesture_swipe_new();
+ gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pSwipe));
+#endif
+ g_signal_connect(pSwipe, "swipe", G_CALLBACK(gestureSwipe), this);
+ gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pSwipe), GTK_PHASE_TARGET);
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ GtkGesture *pLongPress = gtk_gesture_long_press_new(pEventWidget);
+ g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pLongPress);
+#else
+ GtkGesture *pLongPress = gtk_gesture_long_press_new();
+ gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pLongPress));
+#endif
+ g_signal_connect(pLongPress, "pressed", G_CALLBACK(gestureLongPress), this);
+ gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pLongPress), GTK_PHASE_TARGET);
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ g_signal_connect_after( G_OBJECT(m_pWindow), "focus-in-event", G_CALLBACK(signalFocus), this );
+ g_signal_connect_after( G_OBJECT(m_pWindow), "focus-out-event", G_CALLBACK(signalFocus), this );
+ if (GTK_IS_WINDOW(m_pWindow)) // i.e. not if it's a GtkEventBox which doesn't have the signal
+ m_nSetFocusSignalId = g_signal_connect( G_OBJECT(m_pWindow), "set-focus", G_CALLBACK(signalSetFocus), this );
+#else
+ GtkEventController* pFocusController = gtk_event_controller_focus_new();
+ g_signal_connect(pFocusController, "enter", G_CALLBACK(signalFocusEnter), this);
+ g_signal_connect(pFocusController, "leave", G_CALLBACK(signalFocusLeave), this);
+ gtk_widget_set_focusable(pEventWidget, true);
+ gtk_widget_add_controller(pEventWidget, pFocusController);
+ if (GTK_IS_WINDOW(m_pWindow)) // i.e. not if it's a GtkEventBox which doesn't have the property (presumably?)
+ m_nSetFocusSignalId = g_signal_connect( G_OBJECT(m_pWindow), "notify::focus-widget", G_CALLBACK(signalSetFocus), this );
+#endif
+#if !GTK_CHECK_VERSION(4,0,0)
+ g_signal_connect( G_OBJECT(m_pWindow), "map-event", G_CALLBACK(signalMap), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "unmap-event", G_CALLBACK(signalUnmap), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "delete-event", G_CALLBACK(signalDelete), this );
+#else
+ g_signal_connect( G_OBJECT(m_pWindow), "map", G_CALLBACK(signalMap), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "unmap", G_CALLBACK(signalUnmap), this );
+ if (GTK_IS_WINDOW(m_pWindow))
+ g_signal_connect( G_OBJECT(m_pWindow), "close-request", G_CALLBACK(signalDelete), this );
+#endif
+#if !GTK_CHECK_VERSION(4,0,0)
+ g_signal_connect( G_OBJECT(m_pWindow), "configure-event", G_CALLBACK(signalConfigure), this );
+#endif
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ g_signal_connect( G_OBJECT(m_pWindow), "key-press-event", G_CALLBACK(signalKey), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "key-release-event", G_CALLBACK(signalKey), this );
+#else
+ m_pKeyController = GTK_EVENT_CONTROLLER_KEY(gtk_event_controller_key_new());
+ g_signal_connect(m_pKeyController, "key-pressed", G_CALLBACK(signalKeyPressed), this);
+ g_signal_connect(m_pKeyController, "key-released", G_CALLBACK(signalKeyReleased), this);
+ gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(m_pKeyController));
+
+#endif
+ g_signal_connect( G_OBJECT(m_pWindow), "destroy", G_CALLBACK(signalDestroy), this );
+
+ // init members
+ m_nKeyModifiers = ModKeyFlags::NONE;
+ m_bFullscreen = false;
+#if GTK_CHECK_VERSION(4,0,0)
+ m_nState = static_cast<GdkToplevelState>(0);
+#else
+ m_nState = GDK_WINDOW_STATE_WITHDRAWN;
+#endif
+ m_pIMHandler = nullptr;
+ m_pRegion = nullptr;
+ m_pDropTarget = nullptr;
+ m_pDragSource = nullptr;
+ m_bGeometryIsProvisional = false;
+ m_bIconSetWhileUnmapped = false;
+ m_bTooltipBlocked = false;
+ m_ePointerStyle = static_cast<PointerStyle>(0xffff);
+ m_pSalMenu = nullptr;
+ m_nWatcherId = 0;
+ m_nMenuExportId = 0;
+ m_nActionGroupExportId = 0;
+ m_nHudAwarenessId = 0;
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_widget_add_events( m_pWindow,
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
+ GDK_SCROLL_MASK | GDK_TOUCHPAD_GESTURE_MASK
+ );
+#endif
+
+ // show the widgets
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_widget_show_all(GTK_WIDGET(m_pTopLevelGrid));
+#else
+ gtk_widget_show(GTK_WIDGET(m_pTopLevelGrid));
+#endif
+
+ // realize the window, we need an XWindow id
+ gtk_widget_realize( m_pWindow );
+
+ if (GTK_IS_WINDOW(m_pWindow))
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ g_signal_connect(G_OBJECT(m_pWindow), "window-state-event", G_CALLBACK(signalWindowState), this);
+#else
+ GdkSurface* gdkWindow = widget_get_surface(m_pWindow);
+ g_signal_connect(G_OBJECT(gdkWindow), "notify::state", G_CALLBACK(signalWindowState), this);
+#endif
+ }
+
+ //system data
+ m_aSystemData.SetWindowHandle(GetNativeWindowHandle(m_pWindow));
+ m_aSystemData.aShellWindow = reinterpret_cast<sal_IntPtr>(this);
+ m_aSystemData.pSalFrame = this;
+ m_aSystemData.pWidget = m_pWindow;
+ m_aSystemData.nScreen = m_nXScreen.getXScreen();
+ m_aSystemData.toolkit = SystemEnvData::Toolkit::Gtk;
+
+#if defined(GDK_WINDOWING_X11)
+ GdkDisplay *pDisplay = getGdkDisplay();
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ m_aSystemData.pDisplay = gdk_x11_display_get_xdisplay(pDisplay);
+ m_aSystemData.platform = SystemEnvData::Platform::Xcb;
+#if !GTK_CHECK_VERSION(4,0,0)
+ GdkScreen* pScreen = gtk_widget_get_screen(m_pWindow);
+ GdkVisual* pVisual = gdk_screen_get_system_visual(pScreen);
+ m_aSystemData.pVisual = gdk_x11_visual_get_xvisual(pVisual);
+#endif
+ }
+#endif
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
+ {
+ m_aSystemData.pDisplay = gdk_wayland_display_get_wl_display(pDisplay);
+ m_aSystemData.platform = SystemEnvData::Platform::Wayland;
+ }
+#endif
+
+ m_bGraphics = false;
+ m_pGraphics = nullptr;
+
+ m_nFloatFlags = FloatWinPopupFlags::NONE;
+ m_bFloatPositioned = false;
+
+ m_nWidthRequest = 0;
+ m_nHeightRequest = 0;
+
+ // fake an initial geometry, gets updated via configure event or SetPosSize
+ if (m_bDefaultPos || m_bDefaultSize)
+ {
+ Size aDefSize = calcDefaultSize();
+ maGeometry.setPosSize({ -1, -1 }, aDefSize);
+ maGeometry.setDecorations(0, 0, 0, 0);
+ }
+ updateScreenNumber();
+
+ SetIcon(SV_ICON_ID_OFFICE);
+}
+
+GtkSalFrame *GtkSalFrame::getFromWindow( GtkWidget *pWindow )
+{
+ return static_cast<GtkSalFrame *>(g_object_get_data( G_OBJECT( pWindow ), "SalFrame" ));
+}
+
+void GtkSalFrame::DisallowCycleFocusOut()
+{
+ if (!m_nSetFocusSignalId)
+ return;
+ // don't enable/disable can-focus as control enters and leaves
+ // embedded native gtk widgets
+ g_signal_handler_disconnect(G_OBJECT(m_pWindow), m_nSetFocusSignalId);
+ m_nSetFocusSignalId = 0;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // gtk3: set container without can-focus and focus will tab between
+ // the native embedded widgets using the default gtk handling for
+ // that
+ // gtk4: no need because the native widgets are the only
+ // thing in the overlay and the drawing widget is underneath so
+ // the natural focus cycle is sufficient
+ gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), false);
+#endif
+}
+
+bool GtkSalFrame::IsCycleFocusOutDisallowed() const
+{
+ return m_nSetFocusSignalId == 0;
+}
+
+void GtkSalFrame::AllowCycleFocusOut()
+{
+ if (m_nSetFocusSignalId)
+ return;
+#if !GTK_CHECK_VERSION(4,0,0)
+ // enable/disable can-focus as control enters and leaves
+ // embedded native gtk widgets
+ m_nSetFocusSignalId = g_signal_connect(G_OBJECT(m_pWindow), "set-focus", G_CALLBACK(signalSetFocus), this);
+#else
+ m_nSetFocusSignalId = g_signal_connect(G_OBJECT(m_pWindow), "notify::focus-widget", G_CALLBACK(signalSetFocus), this);
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // set container without can-focus and focus will tab between
+ // the native embedded widgets using the default gtk handling for
+ // that
+ // gtk4: no need because the native widgets are the only
+ // thing in the overlay and the drawing widget is underneath so
+ // the natural focus cycle is sufficient
+ gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), true);
+#endif
+}
+
+namespace
+{
+ enum ColorScheme
+ {
+ DEFAULT,
+ PREFER_DARK,
+ PREFER_LIGHT
+ };
+
+ void ReadColorScheme(GDBusProxy* proxy, GVariant** out)
+ {
+ g_autoptr (GVariant) ret =
+ g_dbus_proxy_call_sync(proxy, "Read",
+ g_variant_new ("(ss)", "org.freedesktop.appearance", "color-scheme"),
+ G_DBUS_CALL_FLAGS_NONE, G_MAXINT, nullptr, nullptr);
+ if (!ret)
+ return;
+
+ g_autoptr (GVariant) child = nullptr;
+ g_variant_get(ret, "(v)", &child);
+ g_variant_get(child, "v", out);
+
+ return;
+ }
+}
+
+void GtkSalFrame::SetColorScheme(GVariant* variant)
+{
+ if (!m_pWindow)
+ return;
+
+ guint32 color_scheme;
+
+ switch (officecfg::Office::Common::Misc::Appearance::get())
+ {
+ default:
+ case 0: // Auto
+ {
+ if (variant)
+ {
+ color_scheme = g_variant_get_uint32(variant);
+ if (color_scheme > PREFER_LIGHT)
+ color_scheme = DEFAULT;
+ }
+ else
+ color_scheme = DEFAULT;
+ break;
+ }
+ case 1: // Light
+ color_scheme = PREFER_LIGHT;
+ break;
+ case 2: // Dark
+ color_scheme = PREFER_DARK;
+ break;
+ }
+
+ bool bDarkIconTheme(color_scheme == PREFER_DARK);
+ GtkSettings* pSettings = gtk_widget_get_settings(m_pWindow);
+ g_object_set(pSettings, "gtk-application-prefer-dark-theme", bDarkIconTheme, nullptr);
+}
+
+bool GtkSalFrame::GetUseDarkMode() const
+{
+ if (!m_pWindow)
+ return false;
+ GtkSettings* pSettings = gtk_widget_get_settings(m_pWindow);
+ gboolean bDarkIconTheme = false;
+ g_object_get(pSettings, "gtk-application-prefer-dark-theme", &bDarkIconTheme, nullptr);
+ return bDarkIconTheme;
+}
+
+bool GtkSalFrame::GetUseReducedAnimation() const
+{
+ if (!m_pWindow)
+ return false;
+ GtkSettings* pSettings = gtk_widget_get_settings(m_pWindow);
+ gboolean bAnimations;
+ g_object_get(pSettings, "gtk-enable-animations", &bAnimations, nullptr);
+ return !bAnimations;
+}
+
+static void settings_portal_changed_cb(GDBusProxy*, const char*, const char* signal_name,
+ GVariant* parameters, gpointer frame)
+{
+ if (g_strcmp0(signal_name, "SettingChanged"))
+ return;
+
+ g_autoptr (GVariant) value = nullptr;
+ const char *name_space;
+ const char *name;
+ g_variant_get(parameters, "(&s&sv)", &name_space, &name, &value);
+
+ if (g_strcmp0(name_space, "org.freedesktop.appearance") ||
+ g_strcmp0(name, "color-scheme"))
+ return;
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->SetColorScheme(value);
+}
+
+void GtkSalFrame::ListenPortalSettings()
+{
+ EnsureSessionBus();
+
+ if (!pSessionBus)
+ return;
+
+ m_pSettingsPortal = g_dbus_proxy_new_sync(pSessionBus,
+ G_DBUS_PROXY_FLAGS_NONE,
+ nullptr,
+ "org.freedesktop.portal.Desktop",
+ "/org/freedesktop/portal/desktop",
+ "org.freedesktop.portal.Settings",
+ nullptr,
+ nullptr);
+
+ UpdateDarkMode();
+
+ if (!m_pSettingsPortal)
+ return;
+
+ m_nPortalSettingChangedSignalId = g_signal_connect(m_pSettingsPortal, "g-signal", G_CALLBACK(settings_portal_changed_cb), this);
+}
+
+static void session_client_response(GDBusProxy* client_proxy)
+{
+ g_dbus_proxy_call(client_proxy,
+ "EndSessionResponse",
+ g_variant_new ("(bs)", true, ""),
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT,
+ nullptr, nullptr, nullptr);
+}
+
+// unset documents "modify" flag so they won't veto closing
+static void clear_modify_and_terminate()
+{
+ css::uno::Reference<css::uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext();
+ uno::Reference<frame::XDesktop> xDesktop(frame::Desktop::create(xContext));
+ uno::Reference<css::container::XEnumeration> xComponents = xDesktop->getComponents()->createEnumeration();
+ while (xComponents->hasMoreElements())
+ {
+ css::uno::Reference<css::util::XModifiable> xModifiable(xComponents->nextElement(), css::uno::UNO_QUERY);
+ if (xModifiable)
+ xModifiable->setModified(false);
+ }
+ xDesktop->terminate();
+}
+
+static void session_client_signal(GDBusProxy* client_proxy, const char*, const char* signal_name,
+ GVariant* /*parameters*/, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ if (g_str_equal (signal_name, "QueryEndSession"))
+ {
+ css::uno::Reference<css::uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext();
+ uno::Reference<frame::XDesktop2> xDesktop(frame::Desktop::create(xContext));
+
+ bool bModified = false;
+
+ // find the XModifiable for this GtkSalFrame
+ if (UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper(false))
+ {
+ VclPtr<vcl::Window> xThisWindow = pThis->GetWindow();
+ css::uno::Reference<css::container::XIndexAccess> xList = xDesktop->getFrames();
+ sal_Int32 nFrameCount = xList->getCount();
+ for (sal_Int32 i = 0; i < nFrameCount; ++i)
+ {
+ css::uno::Reference<css::frame::XFrame> xFrame;
+ xList->getByIndex(i) >>= xFrame;
+ if (!xFrame)
+ continue;
+ VclPtr<vcl::Window> xWin = pWrapper->GetWindow(xFrame->getContainerWindow());
+ if (!xWin)
+ continue;
+ if (xWin->GetFrameWindow() != xThisWindow)
+ continue;
+ css::uno::Reference<css::frame::XController> xController = xFrame->getController();
+ if (!xController)
+ break;
+ css::uno::Reference<css::util::XModifiable> xModifiable(xController->getModel(), css::uno::UNO_QUERY);
+ if (!xModifiable)
+ break;
+ bModified = xModifiable->isModified();
+ break;
+ }
+ }
+
+ pThis->SessionManagerInhibit(bModified, APPLICATION_INHIBIT_LOGOUT, VclResId(STR_UNSAVED_DOCUMENTS),
+ gtk_window_get_icon_name(GTK_WINDOW(pThis->getWindow())));
+
+ session_client_response(client_proxy);
+ }
+ else if (g_str_equal (signal_name, "CancelEndSession"))
+ {
+ // restore back to uninhibited (to set again if queried), so frames
+ // that go away before the next logout don't affect that logout
+ pThis->SessionManagerInhibit(false, APPLICATION_INHIBIT_LOGOUT, VclResId(STR_UNSAVED_DOCUMENTS),
+ gtk_window_get_icon_name(GTK_WINDOW(pThis->getWindow())));
+ }
+ else if (g_str_equal (signal_name, "EndSession"))
+ {
+ session_client_response(client_proxy);
+ clear_modify_and_terminate();
+ }
+ else if (g_str_equal (signal_name, "Stop"))
+ {
+ clear_modify_and_terminate();
+ }
+}
+
+void GtkSalFrame::ListenSessionManager()
+{
+ EnsureSessionBus();
+
+ if (!pSessionBus)
+ return;
+
+ m_pSessionManager = g_dbus_proxy_new_sync(pSessionBus,
+ G_DBUS_PROXY_FLAGS_NONE,
+ nullptr,
+ "org.gnome.SessionManager",
+ "/org/gnome/SessionManager",
+ "org.gnome.SessionManager",
+ nullptr,
+ nullptr);
+
+ if (!m_pSessionManager)
+ return;
+
+ GVariant* res = g_dbus_proxy_call_sync(m_pSessionManager,
+ "RegisterClient",
+ g_variant_new ("(ss)", "org.libreoffice", ""),
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT,
+ nullptr,
+ nullptr);
+
+ if (!res)
+ return;
+
+ gchar* client_path;
+ g_variant_get(res, "(o)", &client_path);
+ g_variant_unref(res);
+
+ m_pSessionClient = g_dbus_proxy_new_sync(pSessionBus,
+ G_DBUS_PROXY_FLAGS_NONE,
+ nullptr,
+ "org.gnome.SessionManager",
+ client_path,
+ "org.gnome.SessionManager.ClientPrivate",
+ nullptr,
+ nullptr);
+
+ g_free(client_path);
+
+ if (!m_pSessionClient)
+ return;
+
+ m_nSessionClientSignalId = g_signal_connect(m_pSessionClient, "g-signal", G_CALLBACK(session_client_signal), this);
+}
+
+void GtkSalFrame::UpdateDarkMode()
+{
+ g_autoptr (GVariant) value = nullptr;
+ if (m_pSettingsPortal)
+ ReadColorScheme(m_pSettingsPortal, &value);
+ SetColorScheme(value);
+}
+
+#if GTK_CHECK_VERSION(4,0,0)
+static void PopoverClosed(GtkPopover*, GtkSalFrame* pThis)
+{
+ SolarMutexGuard aGuard;
+ pThis->closePopup();
+}
+#endif
+
+void GtkSalFrame::Init( SalFrame* pParent, SalFrameStyleFlags nStyle )
+{
+ if( nStyle & SalFrameStyleFlags::DEFAULT ) // ensure default style
+ {
+ nStyle |= SalFrameStyleFlags::MOVEABLE | SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::CLOSEABLE;
+ nStyle &= ~SalFrameStyleFlags::FLOAT;
+ }
+
+ m_pParent = static_cast<GtkSalFrame*>(pParent);
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_pForeignParent = nullptr;
+ m_aForeignParentWindow = None;
+ m_pForeignTopLevel = nullptr;
+ m_aForeignTopLevelWindow = None;
+#endif
+ m_nStyle = nStyle;
+
+ bool bPopup = ((nStyle & SalFrameStyleFlags::FLOAT) &&
+ !(nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION));
+
+ if( nStyle & SalFrameStyleFlags::SYSTEMCHILD )
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_pWindow = gtk_event_box_new();
+#else
+ m_pWindow = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+#endif
+ if( m_pParent )
+ {
+ // insert into container
+ gtk_fixed_put( m_pParent->getFixedContainer(),
+ m_pWindow, 0, 0 );
+ }
+ }
+ else
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_pWindow = gtk_window_new(bPopup ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL);
+#else
+ if (!bPopup)
+ m_pWindow = gtk_window_new();
+ else
+ {
+ m_pWindow = gtk_popover_new();
+ gtk_popover_set_has_arrow(GTK_POPOVER(m_pWindow), false);
+ g_signal_connect(m_pWindow, "closed", G_CALLBACK(PopoverClosed), this);
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ // hook up F1 to show help for embedded native gtk widgets
+ GtkAccelGroup *pGroup = gtk_accel_group_new();
+ GClosure* closure = g_cclosure_new(G_CALLBACK(GtkSalFrame::NativeWidgetHelpPressed), GTK_WINDOW(m_pWindow), nullptr);
+ gtk_accel_group_connect(pGroup, GDK_KEY_F1, static_cast<GdkModifierType>(0), GTK_ACCEL_LOCKED, closure);
+ gtk_window_add_accel_group(GTK_WINDOW(m_pWindow), pGroup);
+#endif
+ }
+
+ g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", this );
+ g_object_set_data( G_OBJECT( m_pWindow ), "libo-version", const_cast<char *>(LIBO_VERSION_DOTTED));
+
+ // force wm class hint
+ if (!isChild())
+ {
+ if (m_pParent)
+ m_sWMClass = m_pParent->m_sWMClass;
+ updateWMClass();
+ }
+
+ if (GTK_IS_WINDOW(m_pWindow))
+ {
+ if (m_pParent)
+ {
+ GtkWidget* pTopLevel = widget_get_toplevel(m_pParent->m_pWindow);
+#if !GTK_CHECK_VERSION(4,0,0)
+ if (!isChild())
+ gtk_window_set_screen(GTK_WINDOW(m_pWindow), gtk_widget_get_screen(pTopLevel));
+#endif
+
+ if (!(m_pParent->m_nStyle & SalFrameStyleFlags::PLUG))
+ gtk_window_set_transient_for(GTK_WINDOW(m_pWindow), GTK_WINDOW(pTopLevel));
+ m_pParent->m_aChildren.push_back( this );
+ gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pTopLevel)), GTK_WINDOW(m_pWindow));
+ }
+ else
+ {
+ gtk_window_group_add_window(gtk_window_group_new(), GTK_WINDOW(m_pWindow));
+ g_object_unref(gtk_window_get_group(GTK_WINDOW(m_pWindow)));
+ }
+ }
+ else if (GTK_IS_POPOVER(m_pWindow))
+ {
+ assert(m_pParent);
+ gtk_widget_set_parent(m_pWindow, m_pParent->getMouseEventWidget());
+ }
+
+ // set window type
+ bool bDecoHandling =
+ ! isChild() &&
+ ( ! (nStyle & SalFrameStyleFlags::FLOAT) ||
+ (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) );
+
+ if( bDecoHandling )
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ GdkWindowTypeHint eType = GDK_WINDOW_TYPE_HINT_NORMAL;
+ if( (nStyle & SalFrameStyleFlags::DIALOG) && m_pParent != nullptr )
+ eType = GDK_WINDOW_TYPE_HINT_DIALOG;
+#endif
+ if( nStyle & SalFrameStyleFlags::INTRO )
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_window_set_role( GTK_WINDOW(m_pWindow), "splashscreen" );
+ eType = GDK_WINDOW_TYPE_HINT_SPLASHSCREEN;
+#endif
+ }
+ else if( nStyle & SalFrameStyleFlags::TOOLWINDOW )
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ eType = GDK_WINDOW_TYPE_HINT_DIALOG;
+ gtk_window_set_skip_taskbar_hint( GTK_WINDOW(m_pWindow), true );
+#endif
+ }
+ else if( nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ eType = GDK_WINDOW_TYPE_HINT_TOOLBAR;
+ gtk_window_set_focus_on_map(GTK_WINDOW(m_pWindow), false);
+#endif
+ gtk_window_set_decorated(GTK_WINDOW(m_pWindow), false);
+ }
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), eType );
+ gtk_window_set_gravity( GTK_WINDOW(m_pWindow), GDK_GRAVITY_STATIC );
+#endif
+ gtk_window_set_resizable( GTK_WINDOW(m_pWindow), bool(nStyle & SalFrameStyleFlags::SIZEABLE) );
+
+#if !GTK_CHECK_VERSION(4,0,0)
+#if defined(GDK_WINDOWING_WAYLAND)
+ //rhbz#1392145 under wayland/csd if we've overridden the default widget direction in order to set LibreOffice's
+ //UI to the configured ui language but the system ui locale is a different text direction, then the toplevel
+ //built-in close button of the titlebar follows the overridden direction rather than continue in the same
+ //direction as every other titlebar on the user's desktop. So if they don't match set an explicit
+ //header bar with the desired 'outside' direction
+ if ((eType == GDK_WINDOW_TYPE_HINT_NORMAL || eType == GDK_WINDOW_TYPE_HINT_DIALOG) && DLSYM_GDK_IS_WAYLAND_DISPLAY(GtkSalFrame::getGdkDisplay()))
+ {
+ const bool bDesktopIsRTL = MsLangId::isRightToLeft(MsLangId::getConfiguredSystemUILanguage());
+ const bool bAppIsRTL = gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL;
+ if (bDesktopIsRTL != bAppIsRTL)
+ {
+ m_pHeaderBar = GTK_HEADER_BAR(gtk_header_bar_new());
+ gtk_widget_set_direction(GTK_WIDGET(m_pHeaderBar), bDesktopIsRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
+ gtk_header_bar_set_show_close_button(m_pHeaderBar, true);
+ gtk_window_set_titlebar(GTK_WINDOW(m_pWindow), GTK_WIDGET(m_pHeaderBar));
+ gtk_widget_show(GTK_WIDGET(m_pHeaderBar));
+ }
+ }
+#endif
+#endif
+ }
+#if !GTK_CHECK_VERSION(4,0,0)
+ else if( nStyle & SalFrameStyleFlags::FLOAT )
+ gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), GDK_WINDOW_TYPE_HINT_POPUP_MENU );
+#endif
+
+ InitCommon();
+
+ if (!bPopup)
+ {
+ // Enable GMenuModel native menu
+ attach_menu_model(this);
+
+ // Listen to portal settings for e.g. prefer dark theme
+ ListenPortalSettings();
+
+ // Listen to session manager for e.g. query-end
+ ListenSessionManager();
+ }
+}
+
+#if !GTK_CHECK_VERSION(4,0,0)
+GdkNativeWindow GtkSalFrame::findTopLevelSystemWindow( GdkNativeWindow )
+{
+ //FIXME: no findToplevelSystemWindow
+ return 0;
+}
+#endif
+
+void GtkSalFrame::Init( SystemParentData* pSysData )
+{
+ m_pParent = nullptr;
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_aForeignParentWindow = pSysData->aWindow;
+ m_pForeignParent = nullptr;
+ m_aForeignTopLevelWindow = findTopLevelSystemWindow(pSysData->aWindow);
+ m_pForeignTopLevel = gdk_x11_window_foreign_new_for_display( getGdkDisplay(), m_aForeignTopLevelWindow );
+ gdk_window_set_events( m_pForeignTopLevel, GDK_STRUCTURE_MASK );
+
+ if( pSysData->nSize > sizeof(pSysData->nSize)+sizeof(pSysData->aWindow) && pSysData->bXEmbedSupport )
+ {
+ m_pWindow = gtk_plug_new_for_display( getGdkDisplay(), pSysData->aWindow );
+ gtk_widget_set_can_default(m_pWindow, true);
+ gtk_widget_set_can_focus(m_pWindow, true);
+ gtk_widget_set_sensitive(m_pWindow, true);
+ }
+ else
+ {
+ m_pWindow = gtk_window_new( GTK_WINDOW_POPUP );
+ }
+#endif
+ m_nStyle = SalFrameStyleFlags::PLUG;
+ InitCommon();
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_pForeignParent = gdk_x11_window_foreign_new_for_display( getGdkDisplay(), m_aForeignParentWindow );
+ gdk_window_set_events( m_pForeignParent, GDK_STRUCTURE_MASK );
+#else
+ (void)pSysData;
+#endif
+
+ //FIXME: Handling embedded windows, is going to be fun ...
+}
+
+void GtkSalFrame::SetExtendedFrameStyle(SalExtStyle)
+{
+}
+
+SalGraphics* GtkSalFrame::AcquireGraphics()
+{
+ if( m_bGraphics )
+ return nullptr;
+
+ if( !m_pGraphics )
+ {
+ m_pGraphics.reset( new GtkSalGraphics( this, m_pWindow ) );
+ if (!m_pSurface)
+ {
+ AllocateFrame();
+ TriggerPaintEvent();
+ }
+ m_pGraphics->setSurface(m_pSurface, m_aFrameSize);
+ }
+ m_bGraphics = true;
+ return m_pGraphics.get();
+}
+
+void GtkSalFrame::ReleaseGraphics( SalGraphics* pGraphics )
+{
+ (void) pGraphics;
+ assert( pGraphics == m_pGraphics.get() );
+ m_bGraphics = false;
+}
+
+bool GtkSalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
+{
+ getDisplay()->SendInternalEvent( this, pData.release() );
+ return true;
+}
+
+void GtkSalFrame::SetTitle( const OUString& rTitle )
+{
+ if (m_pWindow && GTK_IS_WINDOW(m_pWindow) && !isChild())
+ {
+ OString sTitle(OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8));
+ gtk_window_set_title(GTK_WINDOW(m_pWindow), sTitle.getStr());
+#if !GTK_CHECK_VERSION(4,0,0)
+ if (m_pHeaderBar)
+ gtk_header_bar_set_title(m_pHeaderBar, sTitle.getStr());
+#endif
+ }
+}
+
+void GtkSalFrame::SetIcon(const char* appicon)
+{
+ gtk_window_set_icon_name(GTK_WINDOW(m_pWindow), appicon);
+
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (!DLSYM_GDK_IS_WAYLAND_DISPLAY(getGdkDisplay()))
+ return;
+
+#if GTK_CHECK_VERSION(4,0,0)
+ GdkSurface* gdkWindow = gtk_native_get_surface(gtk_widget_get_native(m_pWindow));
+ gdk_wayland_toplevel_set_application_id((GDK_TOPLEVEL(gdkWindow)), appicon);
+#else
+ static auto set_application_id = reinterpret_cast<void (*) (GdkWindow*, const char*)>(
+ dlsym(nullptr, "gdk_wayland_window_set_application_id"));
+ if (set_application_id)
+ {
+ GdkSurface* gdkWindow = widget_get_surface(m_pWindow);
+ set_application_id(gdkWindow, appicon);
+ }
+#endif
+ // gdk_wayland_window_set_application_id doesn't seem to work before
+ // the window is mapped, so set this for real when/if we are mapped
+ m_bIconSetWhileUnmapped = !gtk_widget_get_mapped(m_pWindow);
+#endif
+}
+
+void GtkSalFrame::SetIcon( sal_uInt16 nIcon )
+{
+ if( (m_nStyle & (SalFrameStyleFlags::PLUG|SalFrameStyleFlags::SYSTEMCHILD|SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::INTRO|SalFrameStyleFlags::OWNERDRAWDECORATION))
+ || ! m_pWindow )
+ return;
+
+ gchar* appicon;
+
+ if (nIcon == SV_ICON_ID_TEXT)
+ appicon = g_strdup ("libreoffice-writer");
+ else if (nIcon == SV_ICON_ID_SPREADSHEET)
+ appicon = g_strdup ("libreoffice-calc");
+ else if (nIcon == SV_ICON_ID_DRAWING)
+ appicon = g_strdup ("libreoffice-draw");
+ else if (nIcon == SV_ICON_ID_PRESENTATION)
+ appicon = g_strdup ("libreoffice-impress");
+ else if (nIcon == SV_ICON_ID_DATABASE)
+ appicon = g_strdup ("libreoffice-base");
+ else if (nIcon == SV_ICON_ID_FORMULA)
+ appicon = g_strdup ("libreoffice-math");
+ else
+ appicon = g_strdup ("libreoffice-startcenter");
+
+ SetIcon(appicon);
+
+ g_free (appicon);
+}
+
+void GtkSalFrame::SetMenu( SalMenu* pSalMenu )
+{
+ m_pSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
+}
+
+SalMenu* GtkSalFrame::GetMenu()
+{
+ return m_pSalMenu;
+}
+
+void GtkSalFrame::Center()
+{
+ if (!GTK_IS_WINDOW(m_pWindow))
+ return;
+#if !GTK_CHECK_VERSION(4,0,0)
+ if (m_pParent)
+ gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER_ON_PARENT);
+ else
+ gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER);
+#endif
+}
+
+Size GtkSalFrame::calcDefaultSize()
+{
+ Size aScreenSize(getDisplay()->GetScreenSize(GetDisplayScreen()));
+ int scale = gtk_widget_get_scale_factor(m_pWindow);
+ aScreenSize.setWidth( aScreenSize.Width() / scale );
+ aScreenSize.setHeight( aScreenSize.Height() / scale );
+ return bestmaxFrameSizeForScreenSize(aScreenSize);
+}
+
+void GtkSalFrame::SetDefaultSize()
+{
+ Size aDefSize = calcDefaultSize();
+
+ SetPosSize( 0, 0, aDefSize.Width(), aDefSize.Height(),
+ SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
+
+ if( (m_nStyle & SalFrameStyleFlags::DEFAULT) && m_pWindow )
+ gtk_window_maximize( GTK_WINDOW(m_pWindow) );
+}
+
+void GtkSalFrame::Show( bool bVisible, bool /*bNoActivate*/ )
+{
+ if( !m_pWindow )
+ return;
+
+ if( bVisible )
+ {
+ getDisplay()->startupNotificationCompleted();
+
+ if( m_bDefaultPos )
+ Center();
+ if( m_bDefaultSize )
+ SetDefaultSize();
+ setMinMaxSize();
+
+ if (isFloatGrabWindow() && !getDisplay()->GetCaptureFrame())
+ {
+ m_pParent->grabPointer(true, true, true);
+ m_pParent->addGrabLevel();
+ }
+
+#if defined(GDK_WINDOWING_WAYLAND) && !GTK_CHECK_VERSION(4,0,0)
+ /*
+ rhbz#1334915, gnome#779143, tdf#100158
+ https://gitlab.gnome.org/GNOME/gtk/-/issues/767
+
+ before gdk_wayland_window_set_application_id was available gtk
+ under wayland lacked a way to change the app_id of a window, so
+ brute force everything as a startcenter when initially shown to at
+ least get the default LibreOffice icon and not the broken app icon
+ */
+ static bool bAppIdImmutable = DLSYM_GDK_IS_WAYLAND_DISPLAY(getGdkDisplay()) &&
+ !dlsym(nullptr, "gdk_wayland_window_set_application_id");
+ if (bAppIdImmutable)
+ {
+ OString sOrigName(g_get_prgname());
+ g_set_prgname("libreoffice-startcenter");
+ gtk_widget_show(m_pWindow);
+ g_set_prgname(sOrigName.getStr());
+ }
+ else
+ {
+ gtk_widget_show(m_pWindow);
+ }
+#else
+ gtk_widget_show(m_pWindow);
+#endif
+
+ if( isFloatGrabWindow() )
+ {
+ m_nFloats++;
+ if (!getDisplay()->GetCaptureFrame())
+ {
+ grabPointer(true, true, true);
+ addGrabLevel();
+ }
+ // #i44068# reset parent's IM context
+ if( m_pParent )
+ m_pParent->EndExtTextInput(EndExtTextInputFlags::NONE);
+ }
+ }
+ else
+ {
+ if( isFloatGrabWindow() )
+ {
+ m_nFloats--;
+ if (!getDisplay()->GetCaptureFrame())
+ {
+ removeGrabLevel();
+ grabPointer(false, true, false);
+ m_pParent->removeGrabLevel();
+ bool bParentIsFloatGrabWindow = m_pParent->isFloatGrabWindow();
+ m_pParent->grabPointer(bParentIsFloatGrabWindow, true, bParentIsFloatGrabWindow);
+ }
+ }
+ gtk_widget_hide( m_pWindow );
+ if( m_pIMHandler )
+ m_pIMHandler->focusChanged( false );
+ }
+}
+
+void GtkSalFrame::setMinMaxSize()
+{
+ /* #i34504# metacity (and possibly others) do not treat
+ * _NET_WM_STATE_FULLSCREEN and max_width/height independently;
+ * whether they should is undefined. So don't set the max size hint
+ * for a full screen window.
+ */
+ if( !m_pWindow || isChild() )
+ return;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkGeometry aGeo;
+ int aHints = 0;
+ if( m_nStyle & SalFrameStyleFlags::SIZEABLE )
+ {
+ if( m_aMinSize.Width() && m_aMinSize.Height() && ! m_bFullscreen )
+ {
+ aGeo.min_width = m_aMinSize.Width();
+ aGeo.min_height = m_aMinSize.Height();
+ aHints |= GDK_HINT_MIN_SIZE;
+ }
+ if( m_aMaxSize.Width() && m_aMaxSize.Height() && ! m_bFullscreen )
+ {
+ aGeo.max_width = m_aMaxSize.Width();
+ aGeo.max_height = m_aMaxSize.Height();
+ aHints |= GDK_HINT_MAX_SIZE;
+ }
+ }
+ else
+ {
+ if (!m_bFullscreen && m_nWidthRequest && m_nHeightRequest)
+ {
+ aGeo.min_width = m_nWidthRequest;
+ aGeo.min_height = m_nHeightRequest;
+ aHints |= GDK_HINT_MIN_SIZE;
+
+ aGeo.max_width = m_nWidthRequest;
+ aGeo.max_height = m_nHeightRequest;
+ aHints |= GDK_HINT_MAX_SIZE;
+ }
+ }
+
+ if( m_bFullscreen && m_aMaxSize.Width() && m_aMaxSize.Height() )
+ {
+ aGeo.max_width = m_aMaxSize.Width();
+ aGeo.max_height = m_aMaxSize.Height();
+ aHints |= GDK_HINT_MAX_SIZE;
+ }
+ if( aHints )
+ {
+ gtk_window_set_geometry_hints( GTK_WINDOW(m_pWindow),
+ nullptr,
+ &aGeo,
+ GdkWindowHints( aHints ) );
+ }
+#endif
+}
+
+void GtkSalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight )
+{
+ if( ! isChild() )
+ {
+ m_aMaxSize = Size( nWidth, nHeight );
+ setMinMaxSize();
+ }
+}
+void GtkSalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight )
+{
+ if( ! isChild() )
+ {
+ m_aMinSize = Size( nWidth, nHeight );
+ if( m_pWindow )
+ {
+ widget_set_size_request(nWidth, nHeight);
+ setMinMaxSize();
+ }
+ }
+}
+
+void GtkSalFrame::AllocateFrame()
+{
+ basegfx::B2IVector aFrameSize( maGeometry.width(), maGeometry.height() );
+ if (m_pSurface && m_aFrameSize.getX() == aFrameSize.getX() &&
+ m_aFrameSize.getY() == aFrameSize.getY() )
+ return;
+
+ if( aFrameSize.getX() == 0 )
+ aFrameSize.setX( 1 );
+ if( aFrameSize.getY() == 0 )
+ aFrameSize.setY( 1 );
+
+ if (m_pSurface)
+ cairo_surface_destroy(m_pSurface);
+
+ m_pSurface = surface_create_similar_surface(widget_get_surface(m_pWindow),
+ CAIRO_CONTENT_COLOR_ALPHA,
+ aFrameSize.getX(),
+ aFrameSize.getY());
+ m_aFrameSize = aFrameSize;
+
+ cairo_surface_set_user_data(m_pSurface, SvpSalGraphics::getDamageKey(), &m_aDamageHandler, nullptr);
+ SAL_INFO("vcl.gtk3", "allocated Frame size of " << maGeometry.width() << " x " << maGeometry.height());
+
+ if (m_pGraphics)
+ m_pGraphics->setSurface(m_pSurface, m_aFrameSize);
+}
+
+void GtkSalFrame::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags )
+{
+ if( !m_pWindow || isChild( true, false ) )
+ return;
+
+ if( (nFlags & ( SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT )) &&
+ (nWidth > 0 && nHeight > 0 ) // sometimes stupid things happen
+ )
+ {
+ m_bDefaultSize = false;
+
+ maGeometry.setSize({ nWidth, nHeight });
+
+ if (isChild(false) || GTK_IS_POPOVER(m_pWindow))
+ widget_set_size_request(nWidth, nHeight);
+ else if( ! ( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED ) )
+ window_resize(nWidth, nHeight);
+
+ setMinMaxSize();
+ }
+ else if( m_bDefaultSize )
+ SetDefaultSize();
+
+ m_bDefaultSize = false;
+
+ if( nFlags & ( SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ) )
+ {
+ if( m_pParent )
+ {
+ if( AllSettings::GetLayoutRTL() )
+ nX = m_pParent->maGeometry.width()-m_nWidthRequest-1-nX;
+ nX += m_pParent->maGeometry.x();
+ nY += m_pParent->maGeometry.y();
+ }
+
+ if (nFlags & SAL_FRAME_POSSIZE_X)
+ maGeometry.setX(nX);
+ if (nFlags & SAL_FRAME_POSSIZE_Y)
+ maGeometry.setY(nY);
+ m_bGeometryIsProvisional = true;
+
+ m_bDefaultPos = false;
+
+ moveWindow(maGeometry.x(), maGeometry.y());
+
+ updateScreenNumber();
+ }
+ else if( m_bDefaultPos )
+ Center();
+
+ m_bDefaultPos = false;
+}
+
+void GtkSalFrame::GetClientSize( tools::Long& rWidth, tools::Long& rHeight )
+{
+ if( m_pWindow && !(m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) )
+ {
+ rWidth = maGeometry.width();
+ rHeight = maGeometry.height();
+ }
+ else
+ rWidth = rHeight = 0;
+}
+
+void GtkSalFrame::GetWorkArea( AbsoluteScreenPixelRectangle& rRect )
+{
+ GdkRectangle aRect;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkScreen *pScreen = gtk_widget_get_screen(m_pWindow);
+ AbsoluteScreenPixelRectangle aRetRect;
+ int max = gdk_screen_get_n_monitors (pScreen);
+ for (int i = 0; i < max; ++i)
+ {
+ gdk_screen_get_monitor_workarea(pScreen, i, &aRect);
+ AbsoluteScreenPixelRectangle aMonitorRect(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height);
+ aRetRect.Union(aMonitorRect);
+ }
+ rRect = aRetRect;
+#else
+ GdkDisplay* pDisplay = gtk_widget_get_display(m_pWindow);
+ GdkSurface* gdkWindow = widget_get_surface(m_pWindow);
+ GdkMonitor* pMonitor = gdk_display_get_monitor_at_surface(pDisplay, gdkWindow);
+ gdk_monitor_get_geometry(pMonitor, &aRect);
+ rRect = AbsoluteScreenPixelRectangle(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height);
+#endif
+}
+
+SalFrame* GtkSalFrame::GetParent() const
+{
+ return m_pParent;
+}
+
+void GtkSalFrame::SetWindowState(const vcl::WindowData* pState)
+{
+ if( ! m_pWindow || ! pState || isChild( true, false ) )
+ return;
+
+ const vcl::WindowDataMask nMaxGeometryMask = vcl::WindowDataMask::PosSize |
+ vcl::WindowDataMask::MaximizedX | vcl::WindowDataMask::MaximizedY |
+ vcl::WindowDataMask::MaximizedWidth | vcl::WindowDataMask::MaximizedHeight;
+
+ if( (pState->mask() & vcl::WindowDataMask::State) &&
+ ! ( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED ) &&
+ (pState->state() & vcl::WindowState::Maximized) &&
+ (pState->mask() & nMaxGeometryMask) == nMaxGeometryMask )
+ {
+ resizeWindow(pState->width(), pState->height());
+ moveWindow(pState->x(), pState->y());
+ m_bDefaultPos = m_bDefaultSize = false;
+
+ updateScreenNumber();
+
+ m_nState = GdkToplevelState(m_nState | GDK_TOPLEVEL_STATE_MAXIMIZED);
+ m_aRestorePosSize = pState->posSize();
+ }
+ else if (pState->mask() & vcl::WindowDataMask::PosSize)
+ {
+ sal_uInt16 nPosSizeFlags = 0;
+ tools::Long nX = pState->x() - (m_pParent ? m_pParent->maGeometry.x() : 0);
+ tools::Long nY = pState->y() - (m_pParent ? m_pParent->maGeometry.y() : 0);
+ if (pState->mask() & vcl::WindowDataMask::X)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
+ else
+ nX = maGeometry.x() - (m_pParent ? m_pParent->maGeometry.x() : 0);
+ if (pState->mask() & vcl::WindowDataMask::Y)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
+ else
+ nY = maGeometry.y() - (m_pParent ? m_pParent->maGeometry.y() : 0);
+ if (pState->mask() & vcl::WindowDataMask::Width)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
+ if (pState->mask() & vcl::WindowDataMask::Height)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
+ SetPosSize(nX, nY, pState->width(), pState->height(), nPosSizeFlags);
+ }
+
+ if (pState->mask() & vcl::WindowDataMask::State && !isChild())
+ {
+ if (pState->state() & vcl::WindowState::Maximized)
+ gtk_window_maximize( GTK_WINDOW(m_pWindow) );
+ else
+ gtk_window_unmaximize( GTK_WINDOW(m_pWindow) );
+ /* #i42379# there is no rollup state in GDK; and rolled up windows are
+ * (probably depending on the WM) reported as iconified. If we iconify a
+ * window here that was e.g. a dialog, then it will be unmapped but still
+ * not be displayed in the task list, so it's an iconified window that
+ * the user cannot get out of this state. So do not set the iconified state
+ * on windows with a parent (that is transient frames) since these tend
+ * to not be represented in an icon task list.
+ */
+ bool bMinimize = pState->state() & vcl::WindowState::Minimized && !m_pParent;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (bMinimize)
+ gtk_window_minimize(GTK_WINDOW(m_pWindow));
+ else
+ gtk_window_unminimize(GTK_WINDOW(m_pWindow));
+#else
+ if (bMinimize)
+ gtk_window_iconify(GTK_WINDOW(m_pWindow));
+ else
+ gtk_window_deiconify(GTK_WINDOW(m_pWindow));
+#endif
+ }
+ TriggerPaintEvent();
+}
+
+namespace
+{
+ void GetPosAndSize(GtkWindow *pWindow, tools::Long& rX, tools::Long &rY, tools::Long &rWidth, tools::Long &rHeight)
+ {
+ gint width, height;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gint root_x, root_y;
+ gtk_window_get_position(GTK_WINDOW(pWindow), &root_x, &root_y);
+ rX = root_x;
+ rY = root_y;
+
+ gtk_window_get_size(GTK_WINDOW(pWindow), &width, &height);
+#else
+ rX = 0;
+ rY = 0;
+ gtk_window_get_default_size(GTK_WINDOW(pWindow), &width, &height);
+#endif
+ rWidth = width;
+ rHeight = height;
+ }
+
+ tools::Rectangle GetPosAndSize(GtkWindow *pWindow)
+ {
+ tools::Long nX, nY, nWidth, nHeight;
+ GetPosAndSize(pWindow, nX, nY, nWidth, nHeight);
+ return tools::Rectangle(nX, nY, nX + nWidth, nY + nHeight);
+ }
+}
+
+bool GtkSalFrame::GetWindowState(vcl::WindowData* pState)
+{
+ pState->setState(vcl::WindowState::Normal);
+ pState->setMask(vcl::WindowDataMask::PosSizeState);
+
+ // rollup ? gtk 2.2 does not seem to support the shaded state
+ if( m_nState & GDK_TOPLEVEL_STATE_MINIMIZED )
+ pState->rState() |= vcl::WindowState::Minimized;
+ if( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED )
+ {
+ pState->rState() |= vcl::WindowState::Maximized;
+ pState->setPosSize(m_aRestorePosSize);
+ tools::Rectangle aPosSize = GetPosAndSize(GTK_WINDOW(m_pWindow));
+ pState->SetMaximizedX(aPosSize.Left());
+ pState->SetMaximizedY(aPosSize.Top());
+ pState->SetMaximizedWidth(aPosSize.GetWidth());
+ pState->SetMaximizedHeight(aPosSize.GetHeight());
+ pState->rMask() |= vcl::WindowDataMask::MaximizedX |
+ vcl::WindowDataMask::MaximizedY |
+ vcl::WindowDataMask::MaximizedWidth |
+ vcl::WindowDataMask::MaximizedHeight;
+ }
+ else
+ pState->setPosSize(GetPosAndSize(GTK_WINDOW(m_pWindow)));
+
+ return true;
+}
+
+void GtkSalFrame::SetScreen( unsigned int nNewScreen, SetType eType, tools::Rectangle const *pSize )
+{
+ if( !m_pWindow )
+ return;
+
+ if (maGeometry.screen() == nNewScreen && eType == SetType::RetainSize)
+ return;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ int nX = maGeometry.x(), nY = maGeometry.y(),
+ nWidth = maGeometry.width(), nHeight = maGeometry.height();
+ GdkScreen *pScreen = nullptr;
+ GdkRectangle aNewMonitor;
+
+ bool bSpanAllScreens = nNewScreen == static_cast<unsigned int>(-1);
+ bool bSpanMonitorsWhenFullscreen = bSpanAllScreens && getDisplay()->getSystem()->GetDisplayScreenCount() > 1;
+ gint nMonitor = -1;
+ if (bSpanMonitorsWhenFullscreen) //span all screens
+ {
+ pScreen = gtk_widget_get_screen( m_pWindow );
+ aNewMonitor.x = 0;
+ aNewMonitor.y = 0;
+ aNewMonitor.width = gdk_screen_get_width(pScreen);
+ aNewMonitor.height = gdk_screen_get_height(pScreen);
+ }
+ else
+ {
+ bool bSameMonitor = false;
+
+ if (!bSpanAllScreens)
+ {
+ pScreen = getDisplay()->getSystem()->getScreenMonitorFromIdx( nNewScreen, nMonitor );
+ if (!pScreen)
+ {
+ g_warning ("Attempt to move GtkSalFrame to invalid screen %d => "
+ "fallback to current\n", nNewScreen);
+ }
+ }
+
+ if (!pScreen)
+ {
+ pScreen = gtk_widget_get_screen( m_pWindow );
+ bSameMonitor = true;
+ }
+
+ // Heavy lifting, need to move screen ...
+ if( pScreen != gtk_widget_get_screen( m_pWindow ))
+ gtk_window_set_screen( GTK_WINDOW( m_pWindow ), pScreen );
+
+ gint nOldMonitor = gdk_screen_get_monitor_at_window(
+ pScreen, widget_get_surface( m_pWindow ) );
+ if (bSameMonitor)
+ nMonitor = nOldMonitor;
+
+ #if OSL_DEBUG_LEVEL > 1
+ if( nMonitor == nOldMonitor )
+ g_warning( "An apparently pointless SetScreen - should we elide it ?" );
+ #endif
+
+ GdkRectangle aOldMonitor;
+ gdk_screen_get_monitor_geometry( pScreen, nOldMonitor, &aOldMonitor );
+ gdk_screen_get_monitor_geometry( pScreen, nMonitor, &aNewMonitor );
+
+ nX = aNewMonitor.x + nX - aOldMonitor.x;
+ nY = aNewMonitor.y + nY - aOldMonitor.y;
+ }
+
+ bool bResize = false;
+ bool bVisible = gtk_widget_get_mapped( m_pWindow );
+ if( bVisible )
+ Show( false );
+
+ if( eType == SetType::Fullscreen )
+ {
+ nX = aNewMonitor.x;
+ nY = aNewMonitor.y;
+ nWidth = aNewMonitor.width;
+ nHeight = aNewMonitor.height;
+ bResize = true;
+
+ // #i110881# for the benefit of compiz set a max size here
+ // else setting to fullscreen fails for unknown reasons
+ m_aMaxSize.setWidth( aNewMonitor.width );
+ m_aMaxSize.setHeight( aNewMonitor.height );
+ }
+
+ if( pSize && eType == SetType::UnFullscreen )
+ {
+ nX = pSize->Left();
+ nY = pSize->Top();
+ nWidth = pSize->GetWidth();
+ nHeight = pSize->GetHeight();
+ bResize = true;
+ }
+
+ if (bResize)
+ {
+ // temporarily re-sizeable
+ if( !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
+ gtk_window_set_resizable( GTK_WINDOW(m_pWindow), true );
+ window_resize(nWidth, nHeight);
+ }
+
+ gtk_window_move(GTK_WINDOW(m_pWindow), nX, nY);
+
+ GdkFullscreenMode eMode =
+ bSpanMonitorsWhenFullscreen ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR;
+
+ gdk_window_set_fullscreen_mode(widget_get_surface(m_pWindow), eMode);
+
+ GtkWidget* pMenuBarContainerWidget = m_pSalMenu ? m_pSalMenu->GetMenuBarContainerWidget() : nullptr;
+ if( eType == SetType::Fullscreen )
+ {
+ if (pMenuBarContainerWidget)
+ gtk_widget_hide(pMenuBarContainerWidget);
+ if (bSpanMonitorsWhenFullscreen)
+ gtk_window_fullscreen(GTK_WINDOW(m_pWindow));
+ else
+ gtk_window_fullscreen_on_monitor(GTK_WINDOW(m_pWindow), pScreen, nMonitor);
+
+ }
+ else if( eType == SetType::UnFullscreen )
+ {
+ if (pMenuBarContainerWidget)
+ gtk_widget_show(pMenuBarContainerWidget);
+ gtk_window_unfullscreen( GTK_WINDOW( m_pWindow ) );
+ }
+
+ if( eType == SetType::UnFullscreen &&
+ !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
+ gtk_window_set_resizable( GTK_WINDOW( m_pWindow ), FALSE );
+
+ // FIXME: we should really let gtk+ handle our widget hierarchy ...
+ if( m_pParent && gtk_widget_get_screen( m_pParent->m_pWindow ) != pScreen )
+ SetParent( nullptr );
+
+ std::list< GtkSalFrame* > aChildren = m_aChildren;
+ for (auto const& child : aChildren)
+ child->SetScreen( nNewScreen, SetType::RetainSize );
+
+ m_bDefaultPos = m_bDefaultSize = false;
+ updateScreenNumber();
+
+ if( bVisible )
+ Show( true );
+
+#else
+ (void)pSize; // assume restore will restore the original size without our help
+
+ bool bSpanMonitorsWhenFullscreen = nNewScreen == static_cast<unsigned int>(-1);
+
+ GdkFullscreenMode eMode =
+ bSpanMonitorsWhenFullscreen ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR;
+
+ g_object_set(widget_get_surface(m_pWindow), "fullscreen-mode", eMode, nullptr);
+
+ GtkWidget* pMenuBarContainerWidget = m_pSalMenu ? m_pSalMenu->GetMenuBarContainerWidget() : nullptr;
+ if (eType == SetType::Fullscreen)
+ {
+ if (!(m_nStyle & SalFrameStyleFlags::SIZEABLE))
+ {
+ // temp make it resizable, restore when unfullscreened
+ gtk_window_set_resizable(GTK_WINDOW(m_pWindow), true);
+ }
+
+ if (pMenuBarContainerWidget)
+ gtk_widget_hide(pMenuBarContainerWidget);
+ if (bSpanMonitorsWhenFullscreen)
+ gtk_window_fullscreen(GTK_WINDOW(m_pWindow));
+ else
+ {
+ GdkDisplay* pDisplay = gtk_widget_get_display(m_pWindow);
+ GListModel* pList = gdk_display_get_monitors(pDisplay);
+ GdkMonitor* pMonitor = static_cast<GdkMonitor*>(g_list_model_get_item(pList, nNewScreen));
+ if (!pMonitor)
+ pMonitor = gdk_display_get_monitor_at_surface(pDisplay, widget_get_surface(m_pWindow));
+ gtk_window_fullscreen_on_monitor(GTK_WINDOW(m_pWindow), pMonitor);
+ }
+ }
+ else if (eType == SetType::UnFullscreen)
+ {
+ if (pMenuBarContainerWidget)
+ gtk_widget_show(pMenuBarContainerWidget);
+ gtk_window_unfullscreen(GTK_WINDOW(m_pWindow));
+
+ if (!(m_nStyle & SalFrameStyleFlags::SIZEABLE))
+ {
+ // restore temp resizability
+ gtk_window_set_resizable(GTK_WINDOW(m_pWindow), false);
+ }
+ }
+
+ for (auto const& child : m_aChildren)
+ child->SetScreen(nNewScreen, SetType::RetainSize);
+
+ m_bDefaultPos = m_bDefaultSize = false;
+ updateScreenNumber();
+#endif
+}
+
+void GtkSalFrame::SetScreenNumber( unsigned int nNewScreen )
+{
+ SetScreen( nNewScreen, SetType::RetainSize );
+}
+
+void GtkSalFrame::updateWMClass()
+{
+ if (!DLSYM_GDK_IS_X11_DISPLAY(getGdkDisplay()))
+ return;
+
+ if (!gtk_widget_get_realized(m_pWindow))
+ return;
+
+ OString aResClass = OUStringToOString(m_sWMClass, RTL_TEXTENCODING_ASCII_US);
+ const char *pResClass = !aResClass.isEmpty() ? aResClass.getStr() :
+ SalGenericSystem::getFrameClassName();
+ XClassHint* pClass = XAllocClassHint();
+ OString aResName = SalGenericSystem::getFrameResName();
+ pClass->res_name = const_cast<char*>(aResName.getStr());
+ pClass->res_class = const_cast<char*>(pResClass);
+ Display *display = gdk_x11_display_get_xdisplay(getGdkDisplay());
+ XSetClassHint( display,
+ GtkSalFrame::GetNativeWindowHandle(m_pWindow),
+ pClass );
+ XFree( pClass );
+}
+
+void GtkSalFrame::SetApplicationID( const OUString &rWMClass )
+{
+ if( rWMClass != m_sWMClass && ! isChild() )
+ {
+ m_sWMClass = rWMClass;
+ updateWMClass();
+
+ for (auto const& child : m_aChildren)
+ child->SetApplicationID(rWMClass);
+ }
+}
+
+void GtkSalFrame::ShowFullScreen( bool bFullScreen, sal_Int32 nScreen )
+{
+ m_bFullscreen = bFullScreen;
+
+ if( !m_pWindow || isChild() )
+ return;
+
+ if( bFullScreen )
+ {
+ m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(m_pWindow));
+ SetScreen( nScreen, SetType::Fullscreen );
+ }
+ else
+ {
+ SetScreen( nScreen, SetType::UnFullscreen,
+ !m_aRestorePosSize.IsEmpty() ? &m_aRestorePosSize : nullptr );
+ m_aRestorePosSize = tools::Rectangle();
+ }
+}
+
+void GtkSalFrame::SessionManagerInhibit(bool bStart, ApplicationInhibitFlags eType, std::u16string_view sReason, const char* application_id)
+{
+ guint nWindow(0);
+ std::optional<Display*> aDisplay;
+
+ if (DLSYM_GDK_IS_X11_DISPLAY(getGdkDisplay()))
+ {
+ nWindow = GtkSalFrame::GetNativeWindowHandle(m_pWindow);
+ aDisplay = gdk_x11_display_get_xdisplay(getGdkDisplay());
+ }
+
+ m_SessionManagerInhibitor.inhibit(bStart, sReason, eType,
+ nWindow, aDisplay, application_id);
+}
+
+void GtkSalFrame::StartPresentation( bool bStart )
+{
+ SessionManagerInhibit(bStart, APPLICATION_INHIBIT_IDLE, u"presentation", nullptr);
+}
+
+void GtkSalFrame::SetAlwaysOnTop( bool bOnTop )
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( m_pWindow )
+ gtk_window_set_keep_above( GTK_WINDOW( m_pWindow ), bOnTop );
+#else
+ (void)bOnTop;
+#endif
+}
+
+static guint32 nLastUserInputTime = GDK_CURRENT_TIME;
+
+guint32 GtkSalFrame::GetLastInputEventTime()
+{
+ return nLastUserInputTime;
+}
+
+void GtkSalFrame::UpdateLastInputEventTime(guint32 nUserInputTime)
+{
+ //gtk3 can generate a synthetic crossing event with a useless 0
+ //(GDK_CURRENT_TIME) timestamp on showing a menu from the main
+ //menubar, which is unhelpful, so ignore the 0 timestamps
+ if (nUserInputTime == GDK_CURRENT_TIME)
+ return;
+ nLastUserInputTime = nUserInputTime;
+}
+
+void GtkSalFrame::ToTop( SalFrameToTop nFlags )
+{
+ if( !m_pWindow )
+ return;
+
+ if( isChild( false ) )
+ GrabFocus();
+ else if( gtk_widget_get_mapped( m_pWindow ) )
+ {
+ auto nTimestamp = GetLastInputEventTime();
+#ifdef GDK_WINDOWING_X11
+ GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ nTimestamp = gdk_x11_display_get_user_time(pDisplay);
+#endif
+ if (!(nFlags & SalFrameToTop::GrabFocusOnly))
+ gtk_window_present_with_time(GTK_WINDOW(m_pWindow), nTimestamp);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ else
+ gdk_window_focus(widget_get_surface(m_pWindow), nTimestamp);
+#endif
+ GrabFocus();
+ }
+ else
+ {
+ if( nFlags & SalFrameToTop::RestoreWhenMin )
+ gtk_window_present( GTK_WINDOW(m_pWindow) );
+ }
+}
+
+void GtkSalFrame::SetPointer( PointerStyle ePointerStyle )
+{
+ if( !m_pWindow || ePointerStyle == m_ePointerStyle )
+ return;
+
+ m_ePointerStyle = ePointerStyle;
+ GdkCursor *pCursor = getDisplay()->getCursor( ePointerStyle );
+ widget_set_cursor(GTK_WIDGET(m_pWindow), pCursor);
+}
+
+void GtkSalFrame::grabPointer( bool bGrab, bool bKeyboardAlso, bool bOwnerEvents )
+{
+ if (bGrab)
+ {
+ // tdf#135779 move focus back inside usual input window out of any
+ // other gtk widgets before grabbing the pointer
+ GrabFocus();
+ }
+
+ static const char* pEnv = getenv( "SAL_NO_MOUSEGRABS" );
+ if (pEnv && *pEnv)
+ return;
+
+ if (!m_pWindow)
+ return;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkSeat* pSeat = gdk_display_get_default_seat(getGdkDisplay());
+ if (bGrab)
+ {
+ GdkSeatCapabilities eCapability = bKeyboardAlso ? GDK_SEAT_CAPABILITY_ALL : GDK_SEAT_CAPABILITY_ALL_POINTING;
+ gdk_seat_grab(pSeat, widget_get_surface(getMouseEventWidget()), eCapability,
+ bOwnerEvents, nullptr, nullptr, nullptr, nullptr);
+ }
+ else
+ {
+ gdk_seat_ungrab(pSeat);
+ }
+#else
+ (void)bKeyboardAlso;
+ (void)bOwnerEvents;
+#endif
+}
+
+void GtkSalFrame::CaptureMouse( bool bCapture )
+{
+ getDisplay()->CaptureMouse( bCapture ? this : nullptr );
+}
+
+void GtkSalFrame::SetPointerPos( tools::Long nX, tools::Long nY )
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkSalFrame* pFrame = this;
+ while( pFrame && pFrame->isChild( false ) )
+ pFrame = pFrame->m_pParent;
+ if( ! pFrame )
+ return;
+
+ GdkScreen *pScreen = gtk_widget_get_screen(pFrame->m_pWindow);
+ GdkDisplay *pDisplay = gdk_screen_get_display( pScreen );
+
+ /* when the application tries to center the mouse in the dialog the
+ * window isn't mapped already. So use coordinates relative to the root window.
+ */
+ unsigned int nWindowLeft = maGeometry.x() + nX;
+ unsigned int nWindowTop = maGeometry.y() + nY;
+
+ GdkDeviceManager* pManager = gdk_display_get_device_manager(pDisplay);
+ gdk_device_warp(gdk_device_manager_get_client_pointer(pManager), pScreen, nWindowLeft, nWindowTop);
+
+ // #i38648# ask for the next motion hint
+ gint x, y;
+ GdkModifierType mask;
+ gdk_window_get_pointer( widget_get_surface(pFrame->m_pWindow) , &x, &y, &mask );
+#else
+ (void)nX;
+ (void)nY;
+#endif
+}
+
+void GtkSalFrame::Flush()
+{
+ gdk_display_flush( getGdkDisplay() );
+}
+
+void GtkSalFrame::KeyCodeToGdkKey(const vcl::KeyCode& rKeyCode,
+ guint* pGdkKeyCode, GdkModifierType *pGdkModifiers)
+{
+ if ( pGdkKeyCode == nullptr || pGdkModifiers == nullptr )
+ return;
+
+ // Get GDK key modifiers
+ GdkModifierType nModifiers = GdkModifierType(0);
+
+ if ( rKeyCode.IsShift() )
+ nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_SHIFT_MASK );
+
+ if ( rKeyCode.IsMod1() )
+ nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_CONTROL_MASK );
+
+ if ( rKeyCode.IsMod2() )
+ nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_ALT_MASK );
+
+ *pGdkModifiers = nModifiers;
+
+ // Get GDK keycode.
+ guint nKeyCode = 0;
+
+ guint nCode = rKeyCode.GetCode();
+
+ if ( nCode >= KEY_0 && nCode <= KEY_9 )
+ nKeyCode = ( nCode - KEY_0 ) + GDK_KEY_0;
+ else if ( nCode >= KEY_A && nCode <= KEY_Z )
+ nKeyCode = ( nCode - KEY_A ) + GDK_KEY_A;
+ else if ( nCode >= KEY_F1 && nCode <= KEY_F26 )
+ nKeyCode = ( nCode - KEY_F1 ) + GDK_KEY_F1;
+ else
+ {
+ switch (nCode)
+ {
+ case KEY_DOWN: nKeyCode = GDK_KEY_Down; break;
+ case KEY_UP: nKeyCode = GDK_KEY_Up; break;
+ case KEY_LEFT: nKeyCode = GDK_KEY_Left; break;
+ case KEY_RIGHT: nKeyCode = GDK_KEY_Right; break;
+ case KEY_HOME: nKeyCode = GDK_KEY_Home; break;
+ case KEY_END: nKeyCode = GDK_KEY_End; break;
+ case KEY_PAGEUP: nKeyCode = GDK_KEY_Page_Up; break;
+ case KEY_PAGEDOWN: nKeyCode = GDK_KEY_Page_Down; break;
+ case KEY_RETURN: nKeyCode = GDK_KEY_Return; break;
+ case KEY_ESCAPE: nKeyCode = GDK_KEY_Escape; break;
+ case KEY_TAB: nKeyCode = GDK_KEY_Tab; break;
+ case KEY_BACKSPACE: nKeyCode = GDK_KEY_BackSpace; break;
+ case KEY_SPACE: nKeyCode = GDK_KEY_space; break;
+ case KEY_INSERT: nKeyCode = GDK_KEY_Insert; break;
+ case KEY_DELETE: nKeyCode = GDK_KEY_Delete; break;
+ case KEY_ADD: nKeyCode = GDK_KEY_plus; break;
+ case KEY_SUBTRACT: nKeyCode = GDK_KEY_minus; break;
+ case KEY_MULTIPLY: nKeyCode = GDK_KEY_asterisk; break;
+ case KEY_DIVIDE: nKeyCode = GDK_KEY_slash; break;
+ case KEY_POINT: nKeyCode = GDK_KEY_period; break;
+ case KEY_COMMA: nKeyCode = GDK_KEY_comma; break;
+ case KEY_LESS: nKeyCode = GDK_KEY_less; break;
+ case KEY_GREATER: nKeyCode = GDK_KEY_greater; break;
+ case KEY_EQUAL: nKeyCode = GDK_KEY_equal; break;
+ case KEY_FIND: nKeyCode = GDK_KEY_Find; break;
+ case KEY_CONTEXTMENU: nKeyCode = GDK_KEY_Menu; break;
+ case KEY_HELP: nKeyCode = GDK_KEY_Help; break;
+ case KEY_UNDO: nKeyCode = GDK_KEY_Undo; break;
+ case KEY_REPEAT: nKeyCode = GDK_KEY_Redo; break;
+ case KEY_DECIMAL: nKeyCode = GDK_KEY_KP_Decimal; break;
+ case KEY_TILDE: nKeyCode = GDK_KEY_asciitilde; break;
+ case KEY_QUOTELEFT: nKeyCode = GDK_KEY_quoteleft; break;
+ case KEY_BRACKETLEFT: nKeyCode = GDK_KEY_bracketleft; break;
+ case KEY_BRACKETRIGHT: nKeyCode = GDK_KEY_bracketright; break;
+ case KEY_SEMICOLON: nKeyCode = GDK_KEY_semicolon; break;
+ case KEY_QUOTERIGHT: nKeyCode = GDK_KEY_quoteright; break;
+ case KEY_RIGHTCURLYBRACKET: nKeyCode = GDK_KEY_braceright; break;
+ case KEY_NUMBERSIGN: nKeyCode = GDK_KEY_numbersign; break;
+ case KEY_XF86FORWARD: nKeyCode = GDK_KEY_Forward; break;
+ case KEY_XF86BACK: nKeyCode = GDK_KEY_Back; break;
+ case KEY_COLON: nKeyCode = GDK_KEY_colon; break;
+
+ // Special cases
+ case KEY_COPY: nKeyCode = GDK_KEY_Copy; break;
+ case KEY_CUT: nKeyCode = GDK_KEY_Cut; break;
+ case KEY_PASTE: nKeyCode = GDK_KEY_Paste; break;
+ case KEY_OPEN: nKeyCode = GDK_KEY_Open; break;
+ }
+ }
+
+ *pGdkKeyCode = nKeyCode;
+}
+
+OUString GtkSalFrame::GetKeyName( sal_uInt16 nKeyCode )
+{
+ guint nGtkKeyCode;
+ GdkModifierType nGtkModifiers;
+ KeyCodeToGdkKey(nKeyCode, &nGtkKeyCode, &nGtkModifiers );
+
+ gchar* pName = gtk_accelerator_get_label(nGtkKeyCode, nGtkModifiers);
+ OUString aRet = OStringToOUString(pName, RTL_TEXTENCODING_UTF8);
+ g_free(pName);
+ return aRet;
+}
+
+GdkDisplay *GtkSalFrame::getGdkDisplay()
+{
+ return GetGtkSalData()->GetGdkDisplay();
+}
+
+GtkSalDisplay *GtkSalFrame::getDisplay()
+{
+ return GetGtkSalData()->GetGtkDisplay();
+}
+
+SalFrame::SalPointerState GtkSalFrame::GetPointerState()
+{
+ SalPointerState aState;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkScreen* pScreen;
+ gint x, y;
+ GdkModifierType aMask;
+ gdk_display_get_pointer( getGdkDisplay(), &pScreen, &x, &y, &aMask );
+ aState.maPos = Point( x - maGeometry.x(), y - maGeometry.y() );
+ aState.mnState = GetMouseModCode( aMask );
+#endif
+ return aState;
+}
+
+KeyIndicatorState GtkSalFrame::GetIndicatorState()
+{
+ KeyIndicatorState nState = KeyIndicatorState::NONE;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkKeymap *pKeyMap = gdk_keymap_get_for_display(getGdkDisplay());
+
+ if (gdk_keymap_get_caps_lock_state(pKeyMap))
+ nState |= KeyIndicatorState::CAPSLOCK;
+ if (gdk_keymap_get_num_lock_state(pKeyMap))
+ nState |= KeyIndicatorState::NUMLOCK;
+ if (gdk_keymap_get_scroll_lock_state(pKeyMap))
+ nState |= KeyIndicatorState::SCROLLLOCK;
+#endif
+
+ return nState;
+}
+
+void GtkSalFrame::SimulateKeyPress( sal_uInt16 nKeyCode )
+{
+ g_warning ("missing simulate keypress %d", nKeyCode);
+}
+
+void GtkSalFrame::SetInputContext( SalInputContext* pContext )
+{
+ if( ! pContext )
+ return;
+
+ if( ! (pContext->mnOptions & InputContextFlags::Text) )
+ return;
+
+ // create a new im context
+ if( ! m_pIMHandler )
+ m_pIMHandler.reset( new IMHandler( this ) );
+}
+
+void GtkSalFrame::EndExtTextInput( EndExtTextInputFlags nFlags )
+{
+ if( m_pIMHandler )
+ m_pIMHandler->endExtTextInput( nFlags );
+}
+
+bool GtkSalFrame::MapUnicodeToKeyCode( sal_Unicode , LanguageType , vcl::KeyCode& )
+{
+ // not supported yet
+ return false;
+}
+
+LanguageType GtkSalFrame::GetInputLanguage()
+{
+ return LANGUAGE_DONTKNOW;
+}
+
+void GtkSalFrame::UpdateSettings( AllSettings& rSettings )
+{
+ if( ! m_pWindow )
+ return;
+
+ GtkSalGraphics* pGraphics = m_pGraphics.get();
+ bool bFreeGraphics = false;
+ if( ! pGraphics )
+ {
+ pGraphics = static_cast<GtkSalGraphics*>(AcquireGraphics());
+ if ( !pGraphics )
+ {
+ SAL_WARN("vcl.gtk3", "Could not get graphics - unable to update settings");
+ return;
+ }
+ bFreeGraphics = true;
+ }
+
+ pGraphics->UpdateSettings( rSettings );
+
+ if( bFreeGraphics )
+ ReleaseGraphics( pGraphics );
+}
+
+void GtkSalFrame::Beep()
+{
+ gdk_display_beep( getGdkDisplay() );
+}
+
+const SystemEnvData* GtkSalFrame::GetSystemData() const
+{
+ return &m_aSystemData;
+}
+
+void GtkSalFrame::ResolveWindowHandle(SystemEnvData& rData) const
+{
+ if (!rData.pWidget)
+ return;
+ SAL_WARN("vcl.gtk3", "its undesirable to need the NativeWindowHandle, see tdf#139609");
+ rData.SetWindowHandle(GetNativeWindowHandle(static_cast<GtkWidget*>(rData.pWidget)));
+}
+
+void GtkSalFrame::SetParent( SalFrame* pNewParent )
+{
+ GtkWindow* pWindow = GTK_IS_WINDOW(m_pWindow) ? GTK_WINDOW(m_pWindow) : nullptr;
+ if (m_pParent)
+ {
+ if (pWindow && GTK_IS_WINDOW(m_pParent->m_pWindow))
+ gtk_window_group_remove_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), pWindow);
+ m_pParent->m_aChildren.remove(this);
+ }
+ m_pParent = static_cast<GtkSalFrame*>(pNewParent);
+ if (m_pParent)
+ {
+ m_pParent->m_aChildren.push_back(this);
+ if (pWindow && GTK_IS_WINDOW(m_pParent->m_pWindow))
+ gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), pWindow);
+ }
+ if (!isChild() && pWindow)
+ gtk_window_set_transient_for( pWindow,
+ (m_pParent && ! m_pParent->isChild(true,false)) ? GTK_WINDOW(m_pParent->m_pWindow) : nullptr
+ );
+}
+
+void GtkSalFrame::SetPluginParent( SystemParentData* )
+{
+ //FIXME: no SetPluginParent impl. for gtk3
+}
+
+void GtkSalFrame::ResetClipRegion()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( m_pWindow )
+ gdk_window_shape_combine_region( widget_get_surface( m_pWindow ), nullptr, 0, 0 );
+#endif
+}
+
+void GtkSalFrame::BeginSetClipRegion( sal_uInt32 )
+{
+ if( m_pRegion )
+ cairo_region_destroy( m_pRegion );
+ m_pRegion = cairo_region_create();
+}
+
+void GtkSalFrame::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ if( m_pRegion )
+ {
+ GdkRectangle aRect;
+ aRect.x = nX;
+ aRect.y = nY;
+ aRect.width = nWidth;
+ aRect.height = nHeight;
+ cairo_region_union_rectangle( m_pRegion, &aRect );
+ }
+}
+
+void GtkSalFrame::EndSetClipRegion()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( m_pWindow && m_pRegion )
+ gdk_window_shape_combine_region( widget_get_surface(m_pWindow), m_pRegion, 0, 0 );
+#endif
+}
+
+void GtkSalFrame::PositionByToolkit(const tools::Rectangle& rRect, FloatWinPopupFlags nFlags)
+{
+ if ( ImplGetSVData()->maNWFData.mbCanDetermineWindowPosition &&
+ // tdf#152155 cannot determine window positions of popup listboxes on sidebar
+ nFlags != LISTBOX_FLOATWINPOPUPFLAGS )
+ {
+ return;
+ }
+
+ m_aFloatRect = rRect;
+ m_nFloatFlags = nFlags;
+ m_bFloatPositioned = true;
+}
+
+void GtkSalFrame::SetModal(bool bModal)
+{
+ if (!m_pWindow)
+ return;
+ gtk_window_set_modal(GTK_WINDOW(m_pWindow), bModal);
+}
+
+bool GtkSalFrame::GetModal() const
+{
+ if (!m_pWindow)
+ return false;
+ return gtk_window_get_modal(GTK_WINDOW(m_pWindow));
+}
+
+gboolean GtkSalFrame::signalTooltipQuery(GtkWidget*, gint /*x*/, gint /*y*/,
+ gboolean /*keyboard_mode*/, GtkTooltip *tooltip,
+ gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (pThis->m_aTooltip.isEmpty() || pThis->m_bTooltipBlocked)
+ return false;
+ gtk_tooltip_set_text(tooltip,
+ OUStringToOString(pThis->m_aTooltip, RTL_TEXTENCODING_UTF8).getStr());
+ GdkRectangle aHelpArea;
+ aHelpArea.x = pThis->m_aHelpArea.Left();
+ aHelpArea.y = pThis->m_aHelpArea.Top();
+ aHelpArea.width = pThis->m_aHelpArea.GetWidth();
+ aHelpArea.height = pThis->m_aHelpArea.GetHeight();
+ if (AllSettings::GetLayoutRTL())
+ aHelpArea.x = pThis->maGeometry.width()-aHelpArea.width-1-aHelpArea.x;
+ gtk_tooltip_set_tip_area(tooltip, &aHelpArea);
+ return true;
+}
+
+bool GtkSalFrame::ShowTooltip(const OUString& rHelpText, const tools::Rectangle& rHelpArea)
+{
+ m_aTooltip = rHelpText;
+ m_aHelpArea = rHelpArea;
+ gtk_widget_trigger_tooltip_query(getMouseEventWidget());
+ return true;
+}
+
+void GtkSalFrame::BlockTooltip()
+{
+ m_bTooltipBlocked = true;
+}
+
+void GtkSalFrame::UnblockTooltip()
+{
+ m_bTooltipBlocked = false;
+}
+
+void GtkSalFrame::HideTooltip()
+{
+ m_aTooltip.clear();
+ GtkWidget* pEventWidget = getMouseEventWidget();
+ gtk_widget_trigger_tooltip_query(pEventWidget);
+}
+
+namespace
+{
+ void set_pointing_to(GtkPopover *pPopOver, vcl::Window* pParent, const tools::Rectangle& rHelpArea, const SalFrameGeometry& rGeometry)
+ {
+ GdkRectangle aRect;
+ aRect.x = FloatingWindow::ImplConvertToAbsPos(pParent, rHelpArea).Left() - rGeometry.x();
+ aRect.y = rHelpArea.Top();
+ aRect.width = 1;
+ aRect.height = 1;
+
+ GtkPositionType ePos = gtk_popover_get_position(pPopOver);
+ switch (ePos)
+ {
+ case GTK_POS_BOTTOM:
+ case GTK_POS_TOP:
+ aRect.width = rHelpArea.GetWidth();
+ break;
+ case GTK_POS_RIGHT:
+ case GTK_POS_LEFT:
+ aRect.height = rHelpArea.GetHeight();
+ break;
+ }
+
+ gtk_popover_set_pointing_to(pPopOver, &aRect);
+ }
+}
+
+void* GtkSalFrame::ShowPopover(const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea, QuickHelpFlags nFlags)
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget *pWidget = gtk_popover_new(getMouseEventWidget());
+#else
+ GtkWidget *pWidget = gtk_popover_new();
+ gtk_widget_set_parent(pWidget, getMouseEventWidget());
+#endif
+ OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
+ GtkWidget *pLabel = gtk_label_new(sUTF.getStr());
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(pWidget), pLabel);
+#else
+ gtk_popover_set_child(GTK_POPOVER(pWidget), pLabel);
+#endif
+
+ if (nFlags & QuickHelpFlags::Top)
+ gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_BOTTOM);
+ else if (nFlags & QuickHelpFlags::Bottom)
+ gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_TOP);
+ else if (nFlags & QuickHelpFlags::Left)
+ gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_RIGHT);
+ else if (nFlags & QuickHelpFlags::Right)
+ gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_LEFT);
+
+ set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_popover_set_modal(GTK_POPOVER(pWidget), false);
+#else
+ gtk_popover_set_autohide(GTK_POPOVER(pWidget), false);
+#endif
+
+ gtk_widget_show(pLabel);
+ gtk_widget_show(pWidget);
+
+ return pWidget;
+}
+
+bool GtkSalFrame::UpdatePopover(void* nId, const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea)
+{
+ GtkWidget *pWidget = static_cast<GtkWidget*>(nId);
+
+ set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget *pLabel = gtk_bin_get_child(GTK_BIN(pWidget));
+#else
+ GtkWidget *pLabel = gtk_popover_get_child(GTK_POPOVER(pWidget));
+#endif
+ OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
+ gtk_label_set_text(GTK_LABEL(pLabel), sUTF.getStr());
+
+ return true;
+}
+
+bool GtkSalFrame::HidePopover(void* nId)
+{
+ GtkWidget *pWidget = static_cast<GtkWidget*>(nId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(pWidget);
+#else
+ g_clear_pointer(&pWidget, gtk_widget_unparent);
+#endif
+ return true;
+}
+
+void GtkSalFrame::addGrabLevel()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (m_nGrabLevel == 0)
+ gtk_grab_add(getMouseEventWidget());
+#endif
+ ++m_nGrabLevel;
+}
+
+void GtkSalFrame::removeGrabLevel()
+{
+ if (m_nGrabLevel > 0)
+ {
+ --m_nGrabLevel;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (m_nGrabLevel == 0)
+ gtk_grab_remove(getMouseEventWidget());
+#endif
+ }
+}
+
+void GtkSalFrame::closePopup()
+{
+ if (!m_nFloats)
+ return;
+ ImplSVData* pSVData = ImplGetSVData();
+ if (!pSVData->mpWinData->mpFirstFloat)
+ return;
+ if (pSVData->mpWinData->mpFirstFloat->ImplGetFrame() != this)
+ return;
+ pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll);
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+namespace
+{
+ //tdf#117981 translate embedded video window mouse events to parent coordinates
+ void translate_coords(GdkWindow* pSourceWindow, GtkWidget* pTargetWidget, int& rEventX, int& rEventY)
+ {
+ gpointer user_data=nullptr;
+ gdk_window_get_user_data(pSourceWindow, &user_data);
+ GtkWidget* pRealEventWidget = static_cast<GtkWidget*>(user_data);
+ if (pRealEventWidget)
+ {
+ gtk_coord fX(0.0), fY(0.0);
+ gtk_widget_translate_coordinates(pRealEventWidget, pTargetWidget, rEventX, rEventY, &fX, &fY);
+ rEventX = fX;
+ rEventY = fY;
+ }
+ }
+}
+#endif
+
+void GtkSalFrame::GrabFocus()
+{
+ GtkWidget* pGrabWidget;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_EVENT_BOX(m_pWindow))
+ pGrabWidget = GTK_WIDGET(m_pWindow);
+ else
+ pGrabWidget = GTK_WIDGET(m_pFixedContainer);
+ // m_nSetFocusSignalId is 0 for the DisallowCycleFocusOut case where
+ // we don't allow focus to enter the toplevel, but expect it to
+ // stay in some embedded native gtk widget
+ if (!gtk_widget_get_can_focus(pGrabWidget) && m_nSetFocusSignalId)
+ gtk_widget_set_can_focus(pGrabWidget, true);
+#else
+ pGrabWidget = GTK_WIDGET(m_pFixedContainer);
+#endif
+ if (!gtk_widget_has_focus(pGrabWidget))
+ {
+ gtk_widget_grab_focus(pGrabWidget);
+ if (m_pIMHandler)
+ m_pIMHandler->focusChanged(true);
+ }
+}
+
+bool GtkSalFrame::DrawingAreaButton(SalEvent nEventType, int nEventX, int nEventY, int nButton, guint32 nTime, guint nState)
+{
+ UpdateLastInputEventTime(nTime);
+
+ SalMouseEvent aEvent;
+ switch(nButton)
+ {
+ case 1: aEvent.mnButton = MOUSE_LEFT; break;
+ case 2: aEvent.mnButton = MOUSE_MIDDLE; break;
+ case 3: aEvent.mnButton = MOUSE_RIGHT; break;
+ default: return false;
+ }
+
+ aEvent.mnTime = nTime;
+ aEvent.mnX = nEventX;
+ aEvent.mnY = nEventY;
+ aEvent.mnCode = GetMouseModCode(nState);
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = maGeometry.width()-1-aEvent.mnX;
+
+ CallCallbackExc(nEventType, &aEvent);
+
+ return true;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+
+void GtkSalFrame::UpdateGeometryFromEvent(int x_root, int y_root, int nEventX, int nEventY)
+{
+ //tdf#151509 don't overwrite geometry for system children
+ if (m_nStyle & SalFrameStyleFlags::SYSTEMCHILD)
+ return;
+
+ int frame_x = x_root - nEventX;
+ int frame_y = y_root - nEventY;
+ if (m_bGeometryIsProvisional || frame_x != maGeometry.x() || frame_y != maGeometry.y())
+ {
+ m_bGeometryIsProvisional = false;
+ maGeometry.setPos({ frame_x, frame_y });
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->maNWFData.mbCanDetermineWindowPosition)
+ CallCallbackExc(SalEvent::Move, nullptr);
+ }
+}
+
+gboolean GtkSalFrame::signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ GtkWidget* pEventWidget = pThis->getMouseEventWidget();
+ bool bDifferentEventWindow = pEvent->window != widget_get_surface(pEventWidget);
+
+ if (pEvent->type == GDK_BUTTON_PRESS)
+ {
+ // tdf#120764 It isn't allowed under wayland to have two visible popups that share
+ // the same top level parent. The problem is that since gtk 3.24 tooltips are also
+ // implemented as popups, which means that we cannot show any popup if there is a
+ // visible tooltip. In fact, gtk will hide the tooltip by itself after this handler,
+ // in case of a button press event. But if we intend to show a popup on button press
+ // it will be too late, so just do it here:
+ pThis->HideTooltip();
+
+ // focus on click
+ if (!bDifferentEventWindow)
+ pThis->GrabFocus();
+ }
+
+ SalEvent nEventType = SalEvent::NONE;
+ switch( pEvent->type )
+ {
+ case GDK_BUTTON_PRESS:
+ nEventType = SalEvent::MouseButtonDown;
+ break;
+ case GDK_BUTTON_RELEASE:
+ nEventType = SalEvent::MouseButtonUp;
+ break;
+ default:
+ return false;
+ }
+
+ vcl::DeletionListener aDel( pThis );
+
+ if (pThis->isFloatGrabWindow())
+ {
+ //rhbz#1505379 if the window that got the event isn't our one, or there's none
+ //of our windows under the mouse then close this popup window
+ if (bDifferentEventWindow ||
+ gdk_device_get_window_at_position(pEvent->device, nullptr, nullptr) == nullptr)
+ {
+ if (pEvent->type == GDK_BUTTON_PRESS)
+ pThis->closePopup();
+ else if (pEvent->type == GDK_BUTTON_RELEASE)
+ return true;
+ }
+ }
+
+ int nEventX = pEvent->x;
+ int nEventY = pEvent->y;
+
+ if (bDifferentEventWindow)
+ translate_coords(pEvent->window, pEventWidget, nEventX, nEventY);
+
+ if (!aDel.isDeleted())
+ pThis->UpdateGeometryFromEvent(pEvent->x_root, pEvent->y_root, nEventX, nEventY);
+
+ bool bRet = false;
+ if (!aDel.isDeleted())
+ {
+ bRet = pThis->DrawingAreaButton(nEventType,
+ nEventX,
+ nEventY,
+ pEvent->button,
+ pEvent->time,
+ pEvent->state);
+ }
+
+ return bRet;
+}
+#else
+void GtkSalFrame::gesturePressed(GtkGestureClick* pGesture, int /*n_press*/, gdouble x, gdouble y, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->gestureButton(pGesture, SalEvent::MouseButtonDown, x, y);
+}
+
+void GtkSalFrame::gestureReleased(GtkGestureClick* pGesture, int /*n_press*/, gdouble x, gdouble y, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->gestureButton(pGesture, SalEvent::MouseButtonUp, x, y);
+}
+
+void GtkSalFrame::gestureButton(GtkGestureClick* pGesture, SalEvent nEventType, gdouble x, gdouble y)
+{
+ GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pGesture));
+ GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pGesture));
+ int nButton = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(pGesture));
+ DrawingAreaButton(nEventType, x, y, nButton, gdk_event_get_time(pEvent), eType);
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::LaunchAsyncScroll(GdkEvent const * pEvent)
+{
+ //if we don't match previous pending states, flush that queue now
+ if (!m_aPendingScrollEvents.empty() && pEvent->scroll.state != m_aPendingScrollEvents.back()->scroll.state)
+ {
+ m_aSmoothScrollIdle.Stop();
+ m_aSmoothScrollIdle.Invoke();
+ assert(m_aPendingScrollEvents.empty());
+ }
+ //add scroll event to queue
+ m_aPendingScrollEvents.push_back(gdk_event_copy(pEvent));
+ if (!m_aSmoothScrollIdle.IsActive())
+ m_aSmoothScrollIdle.Start();
+}
+#endif
+
+void GtkSalFrame::DrawingAreaScroll(double delta_x, double delta_y, int nEventX, int nEventY, guint32 nTime, guint nState)
+{
+ SalWheelMouseEvent aEvent;
+
+ aEvent.mnTime = nTime;
+ aEvent.mnX = nEventX;
+ // --- RTL --- (mirror mouse pos)
+ if (AllSettings::GetLayoutRTL())
+ aEvent.mnX = maGeometry.width() - 1 - aEvent.mnX;
+ aEvent.mnY = nEventY;
+ aEvent.mnCode = GetMouseModCode(nState);
+
+ // rhbz#1344042 "Traditionally" in gtk3 we took a single up/down event as
+ // equating to 3 scroll lines and a delta of 120. So scale the delta here
+ // by 120 where a single mouse wheel click is an incoming delta_x of 1
+ // and divide that by 40 to get the number of scroll lines
+ if (delta_x != 0.0)
+ {
+ aEvent.mnDelta = -delta_x * 120;
+ aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
+ if (aEvent.mnDelta == 0)
+ aEvent.mnDelta = aEvent.mnNotchDelta;
+ aEvent.mbHorz = true;
+ aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0;
+ CallCallbackExc(SalEvent::WheelMouse, &aEvent);
+ }
+
+ if (delta_y != 0.0)
+ {
+ aEvent.mnDelta = -delta_y * 120;
+ aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
+ if (aEvent.mnDelta == 0)
+ aEvent.mnDelta = aEvent.mnNotchDelta;
+ aEvent.mbHorz = false;
+ aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0;
+ CallCallbackExc(SalEvent::WheelMouse, &aEvent);
+ }
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+IMPL_LINK_NOARG(GtkSalFrame, AsyncScroll, Timer *, void)
+{
+ assert(!m_aPendingScrollEvents.empty());
+
+ GdkEvent* pEvent = m_aPendingScrollEvents.back();
+ auto nEventX = pEvent->scroll.x;
+ auto nEventY = pEvent->scroll.y;
+ auto nTime = pEvent->scroll.time;
+ auto nState = pEvent->scroll.state;
+
+ double delta_x(0.0), delta_y(0.0);
+ for (auto pSubEvent : m_aPendingScrollEvents)
+ {
+ delta_x += pSubEvent->scroll.delta_x;
+ delta_y += pSubEvent->scroll.delta_y;
+ gdk_event_free(pSubEvent);
+ }
+ m_aPendingScrollEvents.clear();
+
+ DrawingAreaScroll(delta_x, delta_y, nEventX, nEventY, nTime, nState);
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+SalWheelMouseEvent GtkSalFrame::GetWheelEvent(const GdkEventScroll& rEvent)
+{
+ SalWheelMouseEvent aEvent;
+
+ aEvent.mnTime = rEvent.time;
+ aEvent.mnX = static_cast<sal_uLong>(rEvent.x);
+ aEvent.mnY = static_cast<sal_uLong>(rEvent.y);
+ aEvent.mnCode = GetMouseModCode(rEvent.state);
+
+ switch (rEvent.direction)
+ {
+ case GDK_SCROLL_UP:
+ aEvent.mnDelta = 120;
+ aEvent.mnNotchDelta = 1;
+ aEvent.mnScrollLines = 3;
+ aEvent.mbHorz = false;
+ break;
+
+ case GDK_SCROLL_DOWN:
+ aEvent.mnDelta = -120;
+ aEvent.mnNotchDelta = -1;
+ aEvent.mnScrollLines = 3;
+ aEvent.mbHorz = false;
+ break;
+
+ case GDK_SCROLL_LEFT:
+ aEvent.mnDelta = 120;
+ aEvent.mnNotchDelta = 1;
+ aEvent.mnScrollLines = 3;
+ aEvent.mbHorz = true;
+ break;
+
+ case GDK_SCROLL_RIGHT:
+ aEvent.mnDelta = -120;
+ aEvent.mnNotchDelta = -1;
+ aEvent.mnScrollLines = 3;
+ aEvent.mbHorz = true;
+ break;
+ default:
+ break;
+ }
+
+ return aEvent;
+}
+
+gboolean GtkSalFrame::signalScroll(GtkWidget*, GdkEvent* pInEvent, gpointer frame)
+{
+ GdkEventScroll& rEvent = pInEvent->scroll;
+
+ UpdateLastInputEventTime(rEvent.time);
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ if (rEvent.direction == GDK_SCROLL_SMOOTH)
+ {
+ pThis->LaunchAsyncScroll(pInEvent);
+ return true;
+ }
+
+ //if we have smooth scrolling previous pending states, flush that queue now
+ if (!pThis->m_aPendingScrollEvents.empty())
+ {
+ pThis->m_aSmoothScrollIdle.Stop();
+ pThis->m_aSmoothScrollIdle.Invoke();
+ assert(pThis->m_aPendingScrollEvents.empty());
+ }
+
+ SalWheelMouseEvent aEvent(GetWheelEvent(rEvent));
+
+ // --- RTL --- (mirror mouse pos)
+ if (AllSettings::GetLayoutRTL())
+ aEvent.mnX = pThis->maGeometry.width() - 1 - aEvent.mnX;
+
+ pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
+
+ return true;
+}
+#else
+gboolean GtkSalFrame::signalScroll(GtkEventControllerScroll* pController, double delta_x, double delta_y, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
+ GdkModifierType eState = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
+
+ auto nTime = gdk_event_get_time(pEvent);
+
+ UpdateLastInputEventTime(nTime);
+
+ double nEventX(0.0), nEventY(0.0);
+ gdk_event_get_position(pEvent, &nEventX, &nEventY);
+
+ pThis->DrawingAreaScroll(delta_x, delta_y, nEventX, nEventY, nTime, eState);
+
+ return true;
+}
+
+gboolean GtkSalFrame::event_controller_scroll_forward(GtkEventControllerScroll* pController, double delta_x, double delta_y)
+{
+ return GtkSalFrame::signalScroll(pController, delta_x, delta_y, this);
+}
+
+#endif
+
+void GtkSalFrame::gestureSwipe(GtkGestureSwipe* gesture, gdouble velocity_x, gdouble velocity_y, gpointer frame)
+{
+ gdouble x, y;
+ GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
+ //I feel I want the first point of the sequence, not the last point which
+ //the docs say this gives, but for the moment assume we start and end
+ //within the same vcl window
+ if (gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y))
+ {
+ SalGestureSwipeEvent aEvent;
+ aEvent.mnVelocityX = velocity_x;
+ aEvent.mnVelocityY = velocity_y;
+ aEvent.mnX = x;
+ aEvent.mnY = y;
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->CallCallbackExc(SalEvent::GestureSwipe, &aEvent);
+ }
+}
+
+void GtkSalFrame::gestureLongPress(GtkGestureLongPress* gesture, gdouble x, gdouble y, gpointer frame)
+{
+ GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
+ if (gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y))
+ {
+ SalGestureLongPressEvent aEvent;
+ aEvent.mnX = x;
+ aEvent.mnY = y;
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->CallCallbackExc(SalEvent::GestureLongPress, &aEvent);
+ }
+}
+
+void GtkSalFrame::DrawingAreaMotion(int nEventX, int nEventY, guint32 nTime, guint nState)
+{
+ UpdateLastInputEventTime(nTime);
+
+ SalMouseEvent aEvent;
+ aEvent.mnTime = nTime;
+ aEvent.mnX = nEventX;
+ aEvent.mnY = nEventY;
+ aEvent.mnCode = GetMouseModCode(nState);
+ aEvent.mnButton = 0;
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = maGeometry.width() - 1 - aEvent.mnX;
+
+ CallCallbackExc(SalEvent::MouseMove, &aEvent);
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalMotion(GtkEventControllerMotion *pController, double x, double y, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
+ GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
+ pThis->DrawingAreaMotion(x, y, gdk_event_get_time(pEvent), eType);
+}
+#else
+gboolean GtkSalFrame::signalMotion( GtkWidget*, GdkEventMotion* pEvent, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ GtkWidget* pEventWidget = pThis->getMouseEventWidget();
+ bool bDifferentEventWindow = pEvent->window != widget_get_surface(pEventWidget);
+
+ //If a menu, e.g. font name dropdown, is open, then under wayland moving the
+ //mouse in the top left corner of the toplevel window in a
+ //0,0,float-width,float-height area generates motion events which are
+ //delivered to the dropdown
+ if (pThis->isFloatGrabWindow() && bDifferentEventWindow)
+ return true;
+
+ vcl::DeletionListener aDel( pThis );
+
+ int nEventX = pEvent->x;
+ int nEventY = pEvent->y;
+
+ if (bDifferentEventWindow)
+ translate_coords(pEvent->window, pEventWidget, nEventX, nEventY);
+
+ pThis->UpdateGeometryFromEvent(pEvent->x_root, pEvent->y_root, nEventX, nEventY);
+
+ if (!aDel.isDeleted())
+ pThis->DrawingAreaMotion(nEventX, nEventY, pEvent->time, pEvent->state);
+
+ if (!aDel.isDeleted())
+ {
+ // ask for the next hint
+ gint x, y;
+ GdkModifierType mask;
+ gdk_window_get_pointer( widget_get_surface(GTK_WIDGET(pThis->m_pWindow)), &x, &y, &mask );
+ }
+
+ return true;
+}
+#endif
+
+void GtkSalFrame::DrawingAreaCrossing(SalEvent nEventType, int nEventX, int nEventY, guint32 nTime, guint nState)
+{
+ UpdateLastInputEventTime(nTime);
+
+ SalMouseEvent aEvent;
+ aEvent.mnTime = nTime;
+ aEvent.mnX = nEventX;
+ aEvent.mnY = nEventY;
+ aEvent.mnCode = GetMouseModCode(nState);
+ aEvent.mnButton = 0;
+
+ if (AllSettings::GetLayoutRTL())
+ aEvent.mnX = maGeometry.width()-1-aEvent.mnX;
+
+ CallCallbackExc(nEventType, &aEvent);
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalEnter(GtkEventControllerMotion *pController, double x, double y, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
+ GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
+ pThis->DrawingAreaCrossing(SalEvent::MouseMove, x, y, pEvent ? gdk_event_get_time(pEvent) : GDK_CURRENT_TIME, eType);
+}
+
+void GtkSalFrame::signalLeave(GtkEventControllerMotion *pController, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
+ GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
+ pThis->DrawingAreaCrossing(SalEvent::MouseLeave, -1, -1, pEvent ? gdk_event_get_time(pEvent) : GDK_CURRENT_TIME, eType);
+}
+#else
+gboolean GtkSalFrame::signalCrossing( GtkWidget*, GdkEventCrossing* pEvent, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->DrawingAreaCrossing((pEvent->type == GDK_ENTER_NOTIFY) ? SalEvent::MouseMove : SalEvent::MouseLeave,
+ pEvent->x,
+ pEvent->y,
+ pEvent->time,
+ pEvent->state);
+ return true;
+}
+#endif
+
+cairo_t* GtkSalFrame::getCairoContext() const
+{
+ cairo_t* cr = cairo_create(m_pSurface);
+ assert(cr);
+ return cr;
+}
+
+void GtkSalFrame::damaged(sal_Int32 nExtentsX, sal_Int32 nExtentsY,
+ sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight) const
+{
+#if OSL_DEBUG_LEVEL > 0
+ if (dumpframes)
+ {
+ static int frame;
+ OString tmp("/tmp/frame" + OString::number(frame++) + ".png");
+ cairo_t* cr = getCairoContext();
+ cairo_surface_write_to_png(cairo_get_target(cr), tmp.getStr());
+ cairo_destroy(cr);
+ }
+#endif
+
+ // quite a bit of noise in RTL mode with negative widths
+ if (nExtentsWidth <= 0 || nExtentsHeight <= 0)
+ return;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_queue_draw_area(GTK_WIDGET(m_pDrawingArea),
+ nExtentsX, nExtentsY,
+ nExtentsWidth, nExtentsHeight);
+#else
+ gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
+ (void)nExtentsX;
+ (void)nExtentsY;
+#endif
+}
+
+// blit our backing cairo surface to the target cairo context
+void GtkSalFrame::DrawingAreaDraw(cairo_t *cr)
+{
+ cairo_set_source_surface(cr, m_pSurface, 0, 0);
+ cairo_paint(cr);
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalFrame::signalDraw(GtkWidget*, cairo_t *cr, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->DrawingAreaDraw(cr);
+ return false;
+}
+#else
+void GtkSalFrame::signalDraw(GtkDrawingArea*, cairo_t *cr, int /*width*/, int /*height*/, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->DrawingAreaDraw(cr);
+}
+#endif
+
+void GtkSalFrame::DrawingAreaResized(GtkWidget* pWidget, int nWidth, int nHeight)
+{
+ // ignore size-allocations that occur during configuring an embedded SalObject
+ if (m_bSalObjectSetPosSize)
+ return;
+ maGeometry.setSize({ nWidth, nHeight });
+ bool bRealized = gtk_widget_get_realized(pWidget);
+ if (bRealized)
+ AllocateFrame();
+ CallCallbackExc( SalEvent::Resize, nullptr );
+ if (bRealized)
+ TriggerPaintEvent();
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, GdkRectangle *pAllocation, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->DrawingAreaResized(pWidget, pAllocation->width, pAllocation->height);
+}
+#else
+void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, int nWidth, int nHeight, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->DrawingAreaResized(pWidget, nWidth, nHeight);
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+namespace {
+
+void swapDirection(GdkGravity& gravity)
+{
+ if (gravity == GDK_GRAVITY_NORTH_WEST)
+ gravity = GDK_GRAVITY_NORTH_EAST;
+ else if (gravity == GDK_GRAVITY_NORTH_EAST)
+ gravity = GDK_GRAVITY_NORTH_WEST;
+ else if (gravity == GDK_GRAVITY_SOUTH_WEST)
+ gravity = GDK_GRAVITY_SOUTH_EAST;
+ else if (gravity == GDK_GRAVITY_SOUTH_EAST)
+ gravity = GDK_GRAVITY_SOUTH_WEST;
+}
+
+}
+#endif
+
+void GtkSalFrame::signalRealize(GtkWidget*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->AllocateFrame();
+ if (pThis->m_bSalObjectSetPosSize)
+ return;
+ pThis->TriggerPaintEvent();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!pThis->m_bFloatPositioned)
+ return;
+
+ static auto window_move_to_rect = reinterpret_cast<void (*) (GdkWindow*, const GdkRectangle*, GdkGravity,
+ GdkGravity, GdkAnchorHints, gint, gint)>(
+ dlsym(nullptr, "gdk_window_move_to_rect"));
+ if (!window_move_to_rect)
+ return;
+
+ GdkGravity rect_anchor = GDK_GRAVITY_SOUTH_WEST, menu_anchor = GDK_GRAVITY_NORTH_WEST;
+
+ if (pThis->m_nFloatFlags & FloatWinPopupFlags::Left)
+ {
+ rect_anchor = GDK_GRAVITY_NORTH_WEST;
+ menu_anchor = GDK_GRAVITY_NORTH_EAST;
+ }
+ else if (pThis->m_nFloatFlags & FloatWinPopupFlags::Up)
+ {
+ rect_anchor = GDK_GRAVITY_NORTH_WEST;
+ menu_anchor = GDK_GRAVITY_SOUTH_WEST;
+ }
+ else if (pThis->m_nFloatFlags & FloatWinPopupFlags::Right)
+ {
+ rect_anchor = GDK_GRAVITY_NORTH_EAST;
+ }
+
+ VclPtr<vcl::Window> pVclParent = pThis->GetWindow()->GetParent();
+ if (pVclParent->GetOutDev()->HasMirroredGraphics() && pVclParent->IsRTLEnabled())
+ {
+ swapDirection(rect_anchor);
+ swapDirection(menu_anchor);
+ }
+
+ AbsoluteScreenPixelRectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pVclParent, pThis->m_aFloatRect);
+ if (gdk_window_get_window_type(widget_get_surface(pThis->m_pParent->m_pWindow)) != GDK_WINDOW_TOPLEVEL)
+ {
+ // See tdf#152155 for an example
+ gtk_coord nX(0), nY(0.0);
+ gtk_widget_translate_coordinates(pThis->m_pParent->m_pWindow, widget_get_toplevel(pThis->m_pParent->m_pWindow), 0, 0, &nX, &nY);
+ aFloatRect.Move(nX, nY);
+ }
+
+ GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
+ static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
+
+ GdkSurface* gdkWindow = widget_get_surface(pThis->m_pWindow);
+ window_move_to_rect(gdkWindow, &rect, rect_anchor, menu_anchor, static_cast<GdkAnchorHints>(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE), 0, 0);
+#endif
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalFrame::signalConfigure(GtkWidget*, GdkEventConfigure* pEvent, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ bool bMoved = false;
+ int x = pEvent->x, y = pEvent->y;
+
+ /* #i31785# claims we cannot trust the x,y members of the event;
+ * they are e.g. not set correctly on maximize/demaximize;
+ * yet the gdkdisplay-x11.c code handling configure_events has
+ * done this XTranslateCoordinates work since the day ~zero.
+ */
+ if (pThis->m_bGeometryIsProvisional || x != pThis->maGeometry.x() || y != pThis->maGeometry.y() )
+ {
+ bMoved = true;
+ pThis->m_bGeometryIsProvisional = false;
+ pThis->maGeometry.setPos({ x, y });
+ }
+
+ // update decoration hints
+ GdkRectangle aRect;
+ gdk_window_get_frame_extents( widget_get_surface(GTK_WIDGET(pThis->m_pWindow)), &aRect );
+ pThis->maGeometry.setTopDecoration(y - aRect.y);
+ pThis->maGeometry.setBottomDecoration(aRect.y + aRect.height - y - pEvent->height);
+ pThis->maGeometry.setLeftDecoration(x - aRect.x);
+ pThis->maGeometry.setRightDecoration(aRect.x + aRect.width - x - pEvent->width);
+ pThis->updateScreenNumber();
+
+ if (bMoved)
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->maNWFData.mbCanDetermineWindowPosition)
+ pThis->CallCallbackExc(SalEvent::Move, nullptr);
+ }
+
+ return false;
+}
+#endif
+
+void GtkSalFrame::queue_draw()
+{
+ gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
+}
+
+void GtkSalFrame::TriggerPaintEvent()
+{
+ //Under gtk2 we can basically paint directly into the XWindow and on
+ //additional "expose-event" events we can re-render the missing pieces
+ //
+ //Under gtk3 we have to keep our own buffer up to date and flush it into
+ //the given cairo context on "draw". So we emit a paint event on
+ //opportune resize trigger events to initially fill our backbuffer and then
+ //keep it up to date with our direct paints and tell gtk those regions
+ //have changed and then blit them into the provided cairo context when
+ //we get the "draw"
+ //
+ //The other alternative was to always paint everything on "draw", but
+ //that duplicates the amount of drawing and is hideously slow
+ SAL_INFO("vcl.gtk3", "force painting" << 0 << "," << 0 << " " << maGeometry.width() << "x" << maGeometry.height());
+ SalPaintEvent aPaintEvt(0, 0, maGeometry.width(), maGeometry.height(), true);
+ CallCallbackExc(SalEvent::Paint, &aPaintEvt);
+ queue_draw();
+}
+
+void GtkSalFrame::DrawingAreaFocusInOut(SalEvent nEventType)
+{
+ SalGenericInstance* pSalInstance = GetGenericInstance();
+
+ // check if printers have changed (analogous to salframe focus handler)
+ pSalInstance->updatePrinterUpdate();
+
+ if (nEventType == SalEvent::LoseFocus)
+ m_nKeyModifiers = ModKeyFlags::NONE;
+
+ if (m_pIMHandler)
+ {
+ bool bFocusInAnotherGtkWidget = false;
+ if (GTK_IS_WINDOW(m_pWindow))
+ {
+ GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(m_pWindow));
+ bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(m_pFixedContainer);
+ }
+ if (!bFocusInAnotherGtkWidget)
+ m_pIMHandler->focusChanged(nEventType == SalEvent::GetFocus);
+ }
+
+ // ask for changed printers like generic implementation
+ if (nEventType == SalEvent::GetFocus && pSalInstance->isPrinterInit())
+ pSalInstance->updatePrinterUpdate();
+
+ CallCallbackExc(nEventType, nullptr);
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalFrame::signalFocus( GtkWidget*, GdkEventFocus* pEvent, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ SalGenericInstance *pSalInstance = GetGenericInstance();
+
+ // check if printers have changed (analogous to salframe focus handler)
+ pSalInstance->updatePrinterUpdate();
+
+ if( !pEvent->in )
+ pThis->m_nKeyModifiers = ModKeyFlags::NONE;
+
+ if( pThis->m_pIMHandler )
+ {
+ bool bFocusInAnotherGtkWidget = false;
+ if (GTK_IS_WINDOW(pThis->m_pWindow))
+ {
+ GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
+ bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(pThis->m_pFixedContainer);
+ }
+ if (!bFocusInAnotherGtkWidget)
+ pThis->m_pIMHandler->focusChanged( pEvent->in != 0 );
+ }
+
+ // ask for changed printers like generic implementation
+ if( pEvent->in && pSalInstance->isPrinterInit() )
+ pSalInstance->updatePrinterUpdate();
+
+ // FIXME: find out who the hell steals the focus from our frame
+ // while we have the pointer grabbed, this should not come from
+ // the window manager. Is this an event that was still queued ?
+ // The focus does not seem to get set inside our process
+ // in the meantime do not propagate focus get/lose if floats are open
+ if( m_nFloats == 0 )
+ {
+ GtkWidget* pGrabWidget;
+ if (GTK_IS_EVENT_BOX(pThis->m_pWindow))
+ pGrabWidget = GTK_WIDGET(pThis->m_pWindow);
+ else
+ pGrabWidget = GTK_WIDGET(pThis->m_pFixedContainer);
+ bool bHasFocus = gtk_widget_has_focus(pGrabWidget);
+ pThis->CallCallbackExc(bHasFocus ? SalEvent::GetFocus : SalEvent::LoseFocus, nullptr);
+ }
+
+ return false;
+}
+#else
+void GtkSalFrame::signalFocusEnter(GtkEventControllerFocus*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->DrawingAreaFocusInOut(SalEvent::GetFocus);
+}
+
+void GtkSalFrame::signalFocusLeave(GtkEventControllerFocus*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->DrawingAreaFocusInOut(SalEvent::LoseFocus);
+}
+#endif
+
+// change of focus between native widgets within the toplevel
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalSetFocus(GtkWindow*, GtkWidget* pWidget, gpointer frame)
+#else
+void GtkSalFrame::signalSetFocus(GtkWindow*, GParamSpec*, gpointer frame)
+#endif
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ GtkWidget* pGrabWidget = GTK_WIDGET(pThis->m_pFixedContainer);
+
+ GtkWidget* pTopLevel = widget_get_toplevel(pGrabWidget);
+ // see commentary in GtkSalObjectWidgetClip::Show
+ if (pTopLevel && g_object_get_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange"))
+ return;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pWidget = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
+#endif
+
+ // tdf#129634 interpret losing focus as focus passing explicitly to another widget
+ bool bLoseFocus = pWidget && pWidget != pGrabWidget;
+
+ // do not propagate focus get/lose if floats are open
+ pThis->CallCallbackExc(bLoseFocus ? SalEvent::LoseFocus : SalEvent::GetFocus, nullptr);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_can_focus(GTK_WIDGET(pThis->m_pFixedContainer), !bLoseFocus);
+#endif
+}
+
+void GtkSalFrame::WindowMap()
+{
+ if (m_bIconSetWhileUnmapped)
+ SetIcon(gtk_window_get_icon_name(GTK_WINDOW(m_pWindow)));
+
+ CallCallbackExc( SalEvent::Resize, nullptr );
+ TriggerPaintEvent();
+}
+
+void GtkSalFrame::WindowUnmap()
+{
+ CallCallbackExc( SalEvent::Resize, nullptr );
+
+ if (m_bFloatPositioned)
+ {
+ // Unrealize is needed for cases where we reuse the same popup
+ // (e.g. the font name control), making the realize signal fire
+ // again on next show.
+ gtk_widget_unrealize(m_pWindow);
+ m_bFloatPositioned = false;
+ }
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalMap(GtkWidget*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->WindowMap();
+}
+
+void GtkSalFrame::signalUnmap(GtkWidget*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->WindowUnmap();
+}
+#else
+gboolean GtkSalFrame::signalMap(GtkWidget*, GdkEvent*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->WindowMap();
+ return false;
+}
+
+gboolean GtkSalFrame::signalUnmap(GtkWidget*, GdkEvent*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->WindowUnmap();
+ return false;
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+
+static bool key_forward(GdkEventKey* pEvent, GtkWindow* pDest)
+{
+ gpointer pClass = g_type_class_ref(GTK_TYPE_WINDOW);
+ GtkWidgetClass* pWindowClass = GTK_WIDGET_CLASS(pClass);
+ bool bHandled = pEvent->type == GDK_KEY_PRESS
+ ? pWindowClass->key_press_event(GTK_WIDGET(pDest), pEvent)
+ : pWindowClass->key_release_event(GTK_WIDGET(pDest), pEvent);
+ g_type_class_unref(pClass);
+ return bHandled;
+}
+
+static bool activate_menubar_mnemonic(GtkWidget* pWidget, guint nKeyval)
+{
+ const char* pLabel = gtk_menu_item_get_label(GTK_MENU_ITEM(pWidget));
+ gunichar cAccelChar = 0;
+ if (!pango_parse_markup(pLabel, -1, '_', nullptr, nullptr, &cAccelChar, nullptr))
+ return false;
+ if (!cAccelChar)
+ return false;
+ auto nMnemonicKeyval = gdk_keyval_to_lower(gdk_unicode_to_keyval(cAccelChar));
+ if (nKeyval == nMnemonicKeyval)
+ return gtk_widget_mnemonic_activate(pWidget, false);
+ return false;
+}
+
+bool GtkSalFrame::HandleMenubarMnemonic(guint eState, guint nKeyval)
+{
+ bool bUsedInMenuBar = false;
+ if (eState & GDK_ALT_MASK)
+ {
+ if (GtkWidget* pMenuBar = m_pSalMenu ? m_pSalMenu->GetMenuBarWidget() : nullptr)
+ {
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pMenuBar));
+ for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
+ {
+ bUsedInMenuBar = activate_menubar_mnemonic(static_cast<GtkWidget*>(pChild->data), nKeyval);
+ if (bUsedInMenuBar)
+ break;
+ }
+ g_list_free(pChildren);
+ }
+ }
+ return bUsedInMenuBar;
+}
+
+gboolean GtkSalFrame::signalKey(GtkWidget* pWidget, GdkEventKey* pEvent, gpointer frame)
+{
+ UpdateLastInputEventTime(pEvent->time);
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ bool bFocusInAnotherGtkWidget = false;
+
+ VclPtr<vcl::Window> xTopLevelInterimWindow;
+
+ if (GTK_IS_WINDOW(pThis->m_pWindow))
+ {
+ GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
+ bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(pThis->m_pFixedContainer);
+ if (bFocusInAnotherGtkWidget)
+ {
+ if (!gtk_widget_get_realized(pFocusWindow))
+ return true;
+
+ // if the focus is not in our main widget, see if there is a handler
+ // for this key stroke in GtkWindow first
+ if (key_forward(pEvent, GTK_WINDOW(pThis->m_pWindow)))
+ return true;
+
+ // Is focus inside an InterimItemWindow? In which case find that
+ // InterimItemWindow and send unconsumed keystrokes to it to
+ // support ctrl-q etc shortcuts. Only bother to search for the
+ // InterimItemWindow if it is a toplevel that fills its frame, or
+ // the keystroke is sufficiently special its worth passing on,
+ // e.g. F6 to switch between task-panels or F5 to close a navigator
+ if (pThis->IsCycleFocusOutDisallowed() || IsFunctionKeyVal(pEvent->keyval))
+ {
+ GtkWidget* pSearch = pFocusWindow;
+ while (pSearch)
+ {
+ void* pData = g_object_get_data(G_OBJECT(pSearch), "InterimWindowGlue");
+ if (pData)
+ {
+ xTopLevelInterimWindow = static_cast<vcl::Window*>(pData);
+ break;
+ }
+ pSearch = gtk_widget_get_parent(pSearch);
+ }
+ }
+ }
+ }
+
+ if (pThis->isFloatGrabWindow())
+ return signalKey(pWidget, pEvent, pThis->m_pParent);
+
+ vcl::DeletionListener aDel( pThis );
+
+ if (!bFocusInAnotherGtkWidget && pThis->m_pIMHandler && pThis->m_pIMHandler->handleKeyEvent(pEvent))
+ return true;
+
+ bool bStopProcessingKey = false;
+
+ // handle modifiers
+ if( pEvent->keyval == GDK_KEY_Shift_L || pEvent->keyval == GDK_KEY_Shift_R ||
+ pEvent->keyval == GDK_KEY_Control_L || pEvent->keyval == GDK_KEY_Control_R ||
+ pEvent->keyval == GDK_KEY_Alt_L || pEvent->keyval == GDK_KEY_Alt_R ||
+ pEvent->keyval == GDK_KEY_Meta_L || pEvent->keyval == GDK_KEY_Meta_R ||
+ pEvent->keyval == GDK_KEY_Super_L || pEvent->keyval == GDK_KEY_Super_R )
+ {
+ sal_uInt16 nModCode = GetKeyModCode( pEvent->state );
+ ModKeyFlags nExtModMask = ModKeyFlags::NONE;
+ sal_uInt16 nModMask = 0;
+ // pressing just the ctrl key leads to a keysym of XK_Control but
+ // the event state does not contain ControlMask. In the release
+ // event it's the other way round: it does contain the Control mask.
+ // The modifier mode therefore has to be adapted manually.
+ switch( pEvent->keyval )
+ {
+ case GDK_KEY_Control_L:
+ nExtModMask = ModKeyFlags::LeftMod1;
+ nModMask = KEY_MOD1;
+ break;
+ case GDK_KEY_Control_R:
+ nExtModMask = ModKeyFlags::RightMod1;
+ nModMask = KEY_MOD1;
+ break;
+ case GDK_KEY_Alt_L:
+ nExtModMask = ModKeyFlags::LeftMod2;
+ nModMask = KEY_MOD2;
+ break;
+ case GDK_KEY_Alt_R:
+ nExtModMask = ModKeyFlags::RightMod2;
+ nModMask = KEY_MOD2;
+ break;
+ case GDK_KEY_Shift_L:
+ nExtModMask = ModKeyFlags::LeftShift;
+ nModMask = KEY_SHIFT;
+ break;
+ case GDK_KEY_Shift_R:
+ nExtModMask = ModKeyFlags::RightShift;
+ nModMask = KEY_SHIFT;
+ break;
+ // Map Meta/Super to MOD3 modifier on all Unix systems
+ // except macOS
+ case GDK_KEY_Meta_L:
+ case GDK_KEY_Super_L:
+ nExtModMask = ModKeyFlags::LeftMod3;
+ nModMask = KEY_MOD3;
+ break;
+ case GDK_KEY_Meta_R:
+ case GDK_KEY_Super_R:
+ nExtModMask = ModKeyFlags::RightMod3;
+ nModMask = KEY_MOD3;
+ break;
+ }
+
+ SalKeyModEvent aModEvt;
+ aModEvt.mbDown = pEvent->type == GDK_KEY_PRESS;
+
+ if( pEvent->type == GDK_KEY_RELEASE )
+ {
+ aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
+ aModEvt.mnCode = nModCode & ~nModMask;
+ pThis->m_nKeyModifiers &= ~nExtModMask;
+ }
+ else
+ {
+ aModEvt.mnCode = nModCode | nModMask;
+ pThis->m_nKeyModifiers |= nExtModMask;
+ aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
+ }
+
+ pThis->CallCallbackExc( SalEvent::KeyModChange, &aModEvt );
+ }
+ else
+ {
+ bool bRestoreDisallowCycleFocusOut = false;
+
+ VclPtr<vcl::Window> xOrigFrameFocusWin;
+ VclPtr<vcl::Window> xOrigFocusWin;
+ if (xTopLevelInterimWindow)
+ {
+ // Focus is inside an InterimItemWindow so send unconsumed
+ // keystrokes to by setting it as the mpFocusWin
+ VclPtr<vcl::Window> xVclWindow = pThis->GetWindow();
+ ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
+ xOrigFrameFocusWin = pFrameData->mpFocusWin;
+ pFrameData->mpFocusWin = xTopLevelInterimWindow;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ xOrigFocusWin = pSVData->mpWinData->mpFocusWin;
+ pSVData->mpWinData->mpFocusWin = xTopLevelInterimWindow;
+
+ if (pEvent->keyval == GDK_KEY_F6 && pThis->IsCycleFocusOutDisallowed())
+ {
+ // For F6, allow the focus to leave the InterimItemWindow
+ pThis->AllowCycleFocusOut();
+ bRestoreDisallowCycleFocusOut = true;
+ }
+ }
+
+ bStopProcessingKey = pThis->doKeyCallback(pEvent->state,
+ pEvent->keyval,
+ pEvent->hardware_keycode,
+ pEvent->group,
+ sal_Unicode(gdk_keyval_to_unicode( pEvent->keyval )),
+ (pEvent->type == GDK_KEY_PRESS),
+ false);
+
+ // tdf#144846 If this is registered as a menubar mnemonic then ensure
+ // that any other widget won't be considered as a candidate by taking
+ // over the task of launch the menubar menu outself
+ // The code was moved here from its original position at beginning
+ // of this function in order to resolve tdf#146174.
+ if (!bStopProcessingKey && // module key handler did not process key
+ pEvent->type == GDK_KEY_PRESS && // module key handler handles only GDK_KEY_PRESS
+ GTK_IS_WINDOW(pThis->m_pWindow) &&
+ pThis->HandleMenubarMnemonic(pEvent->state, pEvent->keyval))
+ {
+ return true;
+ }
+
+ if (!aDel.isDeleted())
+ {
+ pThis->m_nKeyModifiers = ModKeyFlags::NONE;
+
+ if (xTopLevelInterimWindow)
+ {
+ // Focus was inside an InterimItemWindow, restore the original
+ // focus win, unless the focus was changed away from the
+ // InterimItemWindow which should only be possible with F6
+ VclPtr<vcl::Window> xVclWindow = pThis->GetWindow();
+ ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
+ if (pFrameData->mpFocusWin == xTopLevelInterimWindow)
+ pFrameData->mpFocusWin = xOrigFrameFocusWin;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->mpWinData->mpFocusWin == xTopLevelInterimWindow)
+ pSVData->mpWinData->mpFocusWin = xOrigFocusWin;
+
+ if (bRestoreDisallowCycleFocusOut)
+ {
+ // undo the above AllowCycleFocusOut for F6
+ pThis->DisallowCycleFocusOut();
+ }
+ }
+ }
+
+ }
+
+ if (!bFocusInAnotherGtkWidget && !aDel.isDeleted() && pThis->m_pIMHandler)
+ pThis->m_pIMHandler->updateIMSpotLocation();
+
+ return bStopProcessingKey;
+}
+#else
+
+bool GtkSalFrame::DrawingAreaKey(GtkEventControllerKey* pController, SalEvent nEventType, guint keyval, guint keycode, guint state)
+{
+ guint32 nTime = gdk_event_get_time(gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController)));
+ UpdateLastInputEventTime(nTime);
+
+ bool bFocusInAnotherGtkWidget = false;
+
+ VclPtr<vcl::Window> xTopLevelInterimWindow;
+
+ if (GTK_IS_WINDOW(m_pWindow))
+ {
+ GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(m_pWindow));
+ bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(m_pFixedContainer);
+ if (bFocusInAnotherGtkWidget)
+ {
+ if (!gtk_widget_get_realized(pFocusWindow))
+ return true;
+ // if the focus is not in our main widget, see if there is a handler
+ // for this key stroke in GtkWindow first
+ bool bHandled = gtk_event_controller_key_forward(pController, m_pWindow);
+ if (bHandled)
+ return true;
+
+ // Is focus inside an InterimItemWindow? In which case find that
+ // InterimItemWindow and send unconsumed keystrokes to it to
+ // support ctrl-q etc shortcuts. Only bother to search for the
+ // InterimItemWindow if it is a toplevel that fills its frame, or
+ // the keystroke is sufficiently special its worth passing on,
+ // e.g. F6 to switch between task-panels or F5 to close a navigator
+ if (IsCycleFocusOutDisallowed() || IsFunctionKeyVal(keyval))
+ {
+ GtkWidget* pSearch = pFocusWindow;
+ while (pSearch)
+ {
+ void* pData = g_object_get_data(G_OBJECT(pSearch), "InterimWindowGlue");
+ if (pData)
+ {
+ xTopLevelInterimWindow = static_cast<vcl::Window*>(pData);
+ break;
+ }
+ pSearch = gtk_widget_get_parent(pSearch);
+ }
+ }
+ }
+ }
+
+ vcl::DeletionListener aDel(this);
+
+ bool bStopProcessingKey = false;
+
+ // handle modifiers
+ if( keyval == GDK_KEY_Shift_L || keyval == GDK_KEY_Shift_R ||
+ keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R ||
+ keyval == GDK_KEY_Alt_L || keyval == GDK_KEY_Alt_R ||
+ keyval == GDK_KEY_Meta_L || keyval == GDK_KEY_Meta_R ||
+ keyval == GDK_KEY_Super_L || keyval == GDK_KEY_Super_R )
+ {
+ sal_uInt16 nModCode = GetKeyModCode(state);
+ ModKeyFlags nExtModMask = ModKeyFlags::NONE;
+ sal_uInt16 nModMask = 0;
+ // pressing just the ctrl key leads to a keysym of XK_Control but
+ // the event state does not contain ControlMask. In the release
+ // event it's the other way round: it does contain the Control mask.
+ // The modifier mode therefore has to be adapted manually.
+ switch (keyval)
+ {
+ case GDK_KEY_Control_L:
+ nExtModMask = ModKeyFlags::LeftMod1;
+ nModMask = KEY_MOD1;
+ break;
+ case GDK_KEY_Control_R:
+ nExtModMask = ModKeyFlags::RightMod1;
+ nModMask = KEY_MOD1;
+ break;
+ case GDK_KEY_Alt_L:
+ nExtModMask = ModKeyFlags::LeftMod2;
+ nModMask = KEY_MOD2;
+ break;
+ case GDK_KEY_Alt_R:
+ nExtModMask = ModKeyFlags::RightMod2;
+ nModMask = KEY_MOD2;
+ break;
+ case GDK_KEY_Shift_L:
+ nExtModMask = ModKeyFlags::LeftShift;
+ nModMask = KEY_SHIFT;
+ break;
+ case GDK_KEY_Shift_R:
+ nExtModMask = ModKeyFlags::RightShift;
+ nModMask = KEY_SHIFT;
+ break;
+ // Map Meta/Super to MOD3 modifier on all Unix systems
+ // except macOS
+ case GDK_KEY_Meta_L:
+ case GDK_KEY_Super_L:
+ nExtModMask = ModKeyFlags::LeftMod3;
+ nModMask = KEY_MOD3;
+ break;
+ case GDK_KEY_Meta_R:
+ case GDK_KEY_Super_R:
+ nExtModMask = ModKeyFlags::RightMod3;
+ nModMask = KEY_MOD3;
+ break;
+ }
+
+ SalKeyModEvent aModEvt;
+ aModEvt.mbDown = nEventType == SalEvent::KeyInput;
+
+ if (!aModEvt.mbDown)
+ {
+ aModEvt.mnModKeyCode = m_nKeyModifiers;
+ aModEvt.mnCode = nModCode & ~nModMask;
+ m_nKeyModifiers &= ~nExtModMask;
+ }
+ else
+ {
+ aModEvt.mnCode = nModCode | nModMask;
+ m_nKeyModifiers |= nExtModMask;
+ aModEvt.mnModKeyCode = m_nKeyModifiers;
+ }
+
+ CallCallbackExc(SalEvent::KeyModChange, &aModEvt);
+ }
+ else
+ {
+ bool bRestoreDisallowCycleFocusOut = false;
+
+ VclPtr<vcl::Window> xOrigFrameFocusWin;
+ VclPtr<vcl::Window> xOrigFocusWin;
+ if (xTopLevelInterimWindow)
+ {
+ // Focus is inside an InterimItemWindow so send unconsumed
+ // keystrokes to by setting it as the mpFocusWin
+ VclPtr<vcl::Window> xVclWindow = GetWindow();
+ ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
+ xOrigFrameFocusWin = pFrameData->mpFocusWin;
+ pFrameData->mpFocusWin = xTopLevelInterimWindow;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ xOrigFocusWin = pSVData->mpWinData->mpFocusWin;
+ pSVData->mpWinData->mpFocusWin = xTopLevelInterimWindow;
+
+ if (keyval == GDK_KEY_F6 && IsCycleFocusOutDisallowed())
+ {
+ // For F6, allow the focus to leave the InterimItemWindow
+ AllowCycleFocusOut();
+ bRestoreDisallowCycleFocusOut = true;
+ }
+ }
+
+
+ bStopProcessingKey = doKeyCallback(state,
+ keyval,
+ keycode,
+ 0, // group
+ sal_Unicode(gdk_keyval_to_unicode(keyval)),
+ nEventType == SalEvent::KeyInput,
+ false);
+
+ if (!aDel.isDeleted())
+ {
+ m_nKeyModifiers = ModKeyFlags::NONE;
+
+ if (xTopLevelInterimWindow)
+ {
+ // Focus was inside an InterimItemWindow, restore the original
+ // focus win, unless the focus was changed away from the
+ // InterimItemWindow which should only be possible with F6
+ VclPtr<vcl::Window> xVclWindow = GetWindow();
+ ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
+ if (pFrameData->mpFocusWin == xTopLevelInterimWindow)
+ pFrameData->mpFocusWin = xOrigFrameFocusWin;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->mpWinData->mpFocusWin == xTopLevelInterimWindow)
+ pSVData->mpWinData->mpFocusWin = xOrigFocusWin;
+
+ if (bRestoreDisallowCycleFocusOut)
+ {
+ // undo the above AllowCycleFocusOut for F6
+ DisallowCycleFocusOut();
+ }
+ }
+ }
+ }
+
+ if (m_pIMHandler)
+ m_pIMHandler->updateIMSpotLocation();
+
+ return bStopProcessingKey;
+}
+
+gboolean GtkSalFrame::signalKeyPressed(GtkEventControllerKey* pController, guint keyval, guint keycode, GdkModifierType state, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ return pThis->DrawingAreaKey(pController, SalEvent::KeyInput, keyval, keycode, state);
+}
+
+gboolean GtkSalFrame::signalKeyReleased(GtkEventControllerKey* pController, guint keyval, guint keycode, GdkModifierType state, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ return pThis->DrawingAreaKey(pController, SalEvent::KeyUp, keyval, keycode, state);
+}
+#endif
+
+bool GtkSalFrame::WindowCloseRequest()
+{
+ CallCallbackExc(SalEvent::Close, nullptr);
+ return true;
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalFrame::signalDelete(GtkWidget*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ return pThis->WindowCloseRequest();
+}
+#else
+gboolean GtkSalFrame::signalDelete(GtkWidget*, GdkEvent*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ return pThis->WindowCloseRequest();
+}
+#endif
+
+const cairo_font_options_t* GtkSalFrame::get_font_options()
+{
+ GtkWidget* pWidget = getMouseEventWidget();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ PangoContext* pContext = gtk_widget_get_pango_context(pWidget);
+ assert(pContext);
+ return pango_cairo_context_get_font_options(pContext);
+#else
+ return gdk_screen_get_font_options(gtk_widget_get_screen(pWidget));
+#endif
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalStyleUpdated(GtkWidget*, const gchar* /*pSetting*/, gpointer frame)
+#else
+void GtkSalFrame::signalStyleUpdated(GtkWidget*, gpointer frame)
+#endif
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ // note: settings changed for multiple frames is avoided in winproc.cxx ImplHandleSettings
+ GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::SettingsChanged );
+
+ // a plausible alternative might be to send SalEvent::FontChanged if pSetting starts with "gtk-xft"
+
+ // fire off font-changed when the system cairo font hints change
+ GtkInstance *pInstance = GetGtkInstance();
+ const cairo_font_options_t* pLastCairoFontOptions = pInstance->GetLastSeenCairoFontOptions();
+ const cairo_font_options_t* pCurrentCairoFontOptions = pThis->get_font_options();
+ bool bFontSettingsChanged = true;
+ if (pLastCairoFontOptions && pCurrentCairoFontOptions)
+ bFontSettingsChanged = !cairo_font_options_equal(pLastCairoFontOptions, pCurrentCairoFontOptions);
+ else if (!pLastCairoFontOptions && !pCurrentCairoFontOptions)
+ bFontSettingsChanged = false;
+ if (bFontSettingsChanged)
+ {
+ pInstance->ResetLastSeenCairoFontOptions(pCurrentCairoFontOptions);
+ GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::FontChanged );
+ }
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalFrame::signalWindowState( GtkWidget*, GdkEvent* pEvent, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if( (pThis->m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) != (pEvent->window_state.new_window_state & GDK_TOPLEVEL_STATE_MINIMIZED) )
+ {
+ GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize );
+ pThis->TriggerPaintEvent();
+ }
+
+ if ((pEvent->window_state.new_window_state & GDK_TOPLEVEL_STATE_MAXIMIZED) &&
+ !(pThis->m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED))
+ {
+ pThis->m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(pThis->m_pWindow));
+ }
+
+ if ((pEvent->window_state.new_window_state & GDK_WINDOW_STATE_WITHDRAWN) &&
+ !(pThis->m_nState & GDK_WINDOW_STATE_WITHDRAWN))
+ {
+ if (pThis->isFloatGrabWindow())
+ pThis->closePopup();
+ }
+
+ pThis->m_nState = pEvent->window_state.new_window_state;
+
+ return false;
+}
+#else
+void GtkSalFrame::signalWindowState(GdkToplevel* pSurface, GParamSpec*, gpointer frame)
+{
+ GdkToplevelState eNewWindowState = gdk_toplevel_get_state(pSurface);
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if( (pThis->m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) != (eNewWindowState & GDK_TOPLEVEL_STATE_MINIMIZED) )
+ {
+ GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize );
+ pThis->TriggerPaintEvent();
+ }
+
+ if ((eNewWindowState & GDK_TOPLEVEL_STATE_MAXIMIZED) &&
+ !(pThis->m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED))
+ {
+ pThis->m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(pThis->m_pWindow));
+ }
+
+ pThis->m_nState = eNewWindowState;
+}
+#endif
+
+namespace
+{
+ bool handleSignalZoom(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame,
+ GestureEventZoomType eEventType)
+ {
+ gdouble x = 0;
+ gdouble y = 0;
+ gtk_gesture_get_point(gesture, sequence, &x, &y);
+
+ SalGestureZoomEvent aEvent;
+ aEvent.meEventType = eEventType;
+ aEvent.mnX = x;
+ aEvent.mnY = y;
+ aEvent.mfScaleDelta = gtk_gesture_zoom_get_scale_delta(GTK_GESTURE_ZOOM(gesture));
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->CallCallbackExc(SalEvent::GestureZoom, &aEvent);
+ return true;
+ }
+
+ bool handleSignalRotate(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame,
+ GestureEventRotateType eEventType)
+ {
+ gdouble x = 0;
+ gdouble y = 0;
+ gtk_gesture_get_point(gesture, sequence, &x, &y);
+
+ SalGestureRotateEvent aEvent;
+ aEvent.meEventType = eEventType;
+ aEvent.mnX = x;
+ aEvent.mnY = y;
+ aEvent.mfAngleDelta = gtk_gesture_rotate_get_angle_delta(GTK_GESTURE_ROTATE(gesture));
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->CallCallbackExc(SalEvent::GestureRotate, &aEvent);
+ return true;
+ }
+}
+
+bool GtkSalFrame::signalZoomBegin(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame)
+{
+ return handleSignalZoom(gesture, sequence, frame, GestureEventZoomType::Begin);
+}
+
+bool GtkSalFrame::signalZoomUpdate(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame)
+{
+ return handleSignalZoom(gesture, sequence, frame, GestureEventZoomType::Update);
+}
+
+bool GtkSalFrame::signalZoomEnd(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame)
+{
+ return handleSignalZoom(gesture, sequence, frame, GestureEventZoomType::End);
+}
+
+bool GtkSalFrame::signalRotateBegin(GtkGesture* gesture, GdkEventSequence* sequence,
+ gpointer frame)
+{
+ return handleSignalRotate(gesture, sequence, frame, GestureEventRotateType::Begin);
+}
+
+bool GtkSalFrame::signalRotateUpdate(GtkGesture* gesture, GdkEventSequence* sequence,
+ gpointer frame)
+{
+ return handleSignalRotate(gesture, sequence, frame, GestureEventRotateType::Update);
+}
+
+bool GtkSalFrame::signalRotateEnd(GtkGesture* gesture, GdkEventSequence* sequence,
+ gpointer frame)
+{
+ return handleSignalRotate(gesture, sequence, frame, GestureEventRotateType::End);
+}
+
+namespace
+{
+ GdkDragAction VclToGdk(sal_Int8 dragOperation)
+ {
+ GdkDragAction eRet(static_cast<GdkDragAction>(0));
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
+ eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_COPY);
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
+ eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_MOVE);
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
+ eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_LINK);
+ return eRet;
+ }
+
+ sal_Int8 GdkToVcl(GdkDragAction dragOperation)
+ {
+ sal_Int8 nRet(0);
+ if (dragOperation & GDK_ACTION_COPY)
+ nRet |= css::datatransfer::dnd::DNDConstants::ACTION_COPY;
+ if (dragOperation & GDK_ACTION_MOVE)
+ nRet |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ if (dragOperation & GDK_ACTION_LINK)
+ nRet |= css::datatransfer::dnd::DNDConstants::ACTION_LINK;
+ return nRet;
+ }
+}
+
+namespace
+{
+ GdkDragAction getPreferredDragAction(sal_Int8 dragOperation)
+ {
+ GdkDragAction eAct(static_cast<GdkDragAction>(0));
+
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
+ eAct = GDK_ACTION_MOVE;
+ else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
+ eAct = GDK_ACTION_COPY;
+ else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
+ eAct = GDK_ACTION_LINK;
+
+ return eAct;
+ }
+}
+
+static bool g_DropSuccessSet = false;
+static bool g_DropSuccess = false;
+
+namespace {
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+
+void read_drop_async_completed(GObject* source, GAsyncResult* res, gpointer user_data)
+{
+ GdkDrop* drop = GDK_DROP(source);
+ read_transfer_result* pRes = static_cast<read_transfer_result*>(user_data);
+
+ GInputStream* pResult = gdk_drop_read_finish(drop, res, nullptr, nullptr);
+
+ if (!pResult)
+ {
+ pRes->bDone = true;
+ g_main_context_wakeup(nullptr);
+ return;
+ }
+
+ pRes->aVector.resize(read_transfer_result::BlockSize);
+
+ g_input_stream_read_async(pResult,
+ pRes->aVector.data(),
+ pRes->aVector.size(),
+ G_PRIORITY_DEFAULT,
+ nullptr,
+ read_transfer_result::read_block_async_completed,
+ user_data);
+}
+#endif
+
+class GtkDropTargetDropContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDropContext>
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkDragContext *m_pContext;
+ guint m_nTime;
+#else
+ GdkDrop* m_pDrop;
+#endif
+public:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkDropTargetDropContext(GdkDragContext* pContext, guint nTime)
+ : m_pContext(pContext)
+ , m_nTime(nTime)
+#else
+ GtkDropTargetDropContext(GdkDrop* pDrop)
+ : m_pDrop(pDrop)
+#endif
+ {
+ }
+
+ // XDropTargetDropContext
+ virtual void SAL_CALL acceptDrop(sal_Int8 dragOperation) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
+#else
+ GdkDragAction eDragAction = getPreferredDragAction(dragOperation);
+ gdk_drop_status(m_pDrop,
+ static_cast<GdkDragAction>(eDragAction | gdk_drop_get_actions(m_pDrop)),
+ eDragAction);
+#endif
+ }
+
+ virtual void SAL_CALL rejectDrop() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
+#else
+ gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), static_cast<GdkDragAction>(0));
+#endif
+ }
+
+ virtual void SAL_CALL dropComplete(sal_Bool bSuccess) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_drag_finish(m_pContext, bSuccess, false, m_nTime);
+#else
+ // should we do something better here
+ gdk_drop_finish(m_pDrop, bSuccess
+ ? gdk_drop_get_actions(m_pDrop)
+ : static_cast<GdkDragAction>(0));
+#endif
+ if (GtkInstDragSource::g_ActiveDragSource)
+ {
+ g_DropSuccessSet = true;
+ g_DropSuccess = bSuccess;
+ }
+ }
+};
+
+}
+
+class GtkDnDTransferable : public GtkTransferable
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkDragContext *m_pContext;
+ guint m_nTime;
+ GtkWidget *m_pWidget;
+ GtkInstDropTarget* m_pDropTarget;
+#else
+ GdkDrop* m_pDrop;
+#endif
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GMainLoop *m_pLoop;
+ GtkSelectionData *m_pData;
+#endif
+public:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkDnDTransferable(GdkDragContext *pContext, guint nTime, GtkWidget *pWidget, GtkInstDropTarget *pDropTarget)
+ : m_pContext(pContext)
+ , m_nTime(nTime)
+ , m_pWidget(pWidget)
+ , m_pDropTarget(pDropTarget)
+#else
+ GtkDnDTransferable(GdkDrop *pDrop)
+ : m_pDrop(pDrop)
+#endif
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_pLoop(nullptr)
+ , m_pData(nullptr)
+#endif
+ {
+ }
+
+ virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override
+ {
+ css::datatransfer::DataFlavor aFlavor(rFlavor);
+ if (aFlavor.MimeType == "text/plain;charset=utf-16")
+ aFlavor.MimeType = "text/plain;charset=utf-8";
+
+ auto it = m_aMimeTypeToGtkType.find(aFlavor.MimeType);
+ if (it == m_aMimeTypeToGtkType.end())
+ return css::uno::Any();
+
+ css::uno::Any aRet;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ /* like gtk_clipboard_wait_for_contents run a sub loop
+ * waiting for drag-data-received triggered from
+ * gtk_drag_get_data
+ */
+ {
+ m_pLoop = g_main_loop_new(nullptr, true);
+ m_pDropTarget->SetFormatConversionRequest(this);
+
+ gtk_drag_get_data(m_pWidget, m_pContext, it->second, m_nTime);
+
+ if (g_main_loop_is_running(m_pLoop))
+ main_loop_run(m_pLoop);
+
+ g_main_loop_unref(m_pLoop);
+ m_pLoop = nullptr;
+ m_pDropTarget->SetFormatConversionRequest(nullptr);
+ }
+
+ if (aFlavor.MimeType == "text/plain;charset=utf-8")
+ {
+ OUString aStr;
+ gchar *pText = reinterpret_cast<gchar*>(gtk_selection_data_get_text(m_pData));
+ if (pText)
+ aStr = OStringToOUString(pText, RTL_TEXTENCODING_UTF8);
+ g_free(pText);
+ aRet <<= aStr.replaceAll("\r\n", "\n");
+ }
+ else
+ {
+ gint length(0);
+ const guchar *rawdata = gtk_selection_data_get_data_with_length(m_pData,
+ &length);
+ // seen here was rawhide == nullptr and length set to -1
+ if (rawdata)
+ {
+ css::uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
+ aRet <<= aSeq;
+ }
+ }
+
+ gtk_selection_data_free(m_pData);
+#else
+ SalInstance* pInstance = GetSalInstance();
+ read_transfer_result aRes;
+ const char *mime_types[] = { it->second.getStr(), nullptr };
+
+ gdk_drop_read_async(m_pDrop,
+ mime_types,
+ G_PRIORITY_DEFAULT,
+ nullptr,
+ read_drop_async_completed,
+ &aRes);
+
+ while (!aRes.bDone)
+ pInstance->DoYield(true, false);
+
+ if (aFlavor.MimeType == "text/plain;charset=utf-8")
+ aRet <<= aRes.get_as_string();
+ else
+ aRet <<= aRes.get_as_sequence();
+#endif
+ return aRet;
+ }
+
+ virtual std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ std::vector<GdkAtom> targets;
+ for (GList* l = gdk_drag_context_list_targets(m_pContext); l; l = l->next)
+ targets.push_back(static_cast<GdkAtom>(l->data));
+ return GtkTransferable::getTransferDataFlavorsAsVector(targets.data(), targets.size());
+#else
+ GdkContentFormats* pFormats = gdk_drop_get_formats(m_pDrop);
+ gsize n_targets;
+ const char * const *targets = gdk_content_formats_get_mime_types(pFormats, &n_targets);
+ return GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
+#endif
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ void LoopEnd(GtkSelectionData *pData)
+ {
+ m_pData = pData;
+ g_main_loop_quit(m_pLoop);
+ }
+#endif
+};
+
+// For LibreOffice internal D&D we provide the Transferable without Gtk
+// intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
+GtkInstDragSource* GtkInstDragSource::g_ActiveDragSource;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+
+gboolean GtkSalFrame::signalDragDrop(GtkDropTargetAsync* context, GdkDrop* drop, double x, double y, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return false;
+ return pThis->m_pDropTarget->signalDragDrop(context, drop, x, y);
+}
+#else
+gboolean GtkSalFrame::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return false;
+ return pThis->m_pDropTarget->signalDragDrop(pWidget, context, x, y, time);
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkInstDropTarget::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time)
+#else
+gboolean GtkInstDropTarget::signalDragDrop(GtkDropTargetAsync* context, GdkDrop* drop, double x, double y)
+#endif
+{
+ // remove the deferred dragExit, as we'll do a drop
+#ifndef NDEBUG
+ bool res =
+#endif
+ g_idle_remove_by_data(this);
+#ifndef NDEBUG
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ assert(res);
+#else
+ (void)res;
+#endif
+#endif
+
+ css::datatransfer::dnd::DropTargetDropEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(this);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ aEvent.Context = new GtkDropTargetDropContext(context, time);
+#else
+ aEvent.Context = new GtkDropTargetDropContext(drop);
+#endif
+ aEvent.LocationX = x;
+ aEvent.LocationY = y;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ aEvent.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
+#else
+ aEvent.DropAction = GdkToVcl(getPreferredDragAction(GdkToVcl(gdk_drop_get_actions(drop))));
+#endif
+ // ACTION_DEFAULT is documented as...
+ // 'This means the user did not press any key during the Drag and Drop operation
+ // and the action that was combined with ACTION_DEFAULT is the system default action'
+ // in tdf#107031 writer won't insert a link when a heading is dragged from the
+ // navigator unless this is set. Its unclear really what ACTION_DEFAULT means,
+ // there is a deprecated 'GDK_ACTION_DEFAULT Means nothing, and should not be used'
+ // possible equivalent in gtk.
+ // So (tdf#109227) set ACTION_DEFAULT if no modifier key is held down
+#if !GTK_CHECK_VERSION(4,0,0)
+ aEvent.SourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
+ GdkModifierType mask;
+ gdk_window_get_pointer(widget_get_surface(pWidget), nullptr, nullptr, &mask);
+#else
+ aEvent.SourceActions = GdkToVcl(gdk_drop_get_actions(drop));
+ GdkModifierType mask = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(context));
+#endif
+ if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
+ aEvent.DropAction |= css::datatransfer::dnd::DNDConstants::ACTION_DEFAULT;
+
+ css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
+ // For LibreOffice internal D&D we provide the Transferable without Gtk
+ // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
+ if (GtkInstDragSource::g_ActiveDragSource)
+ xTransferable = GtkInstDragSource::g_ActiveDragSource->GetTransferable();
+ else
+ {
+#if GTK_CHECK_VERSION(4,0,0)
+ xTransferable = new GtkDnDTransferable(drop);
+#else
+ xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
+#endif
+ }
+ aEvent.Transferable = xTransferable;
+
+ fire_drop(aEvent);
+
+ return true;
+}
+
+namespace {
+
+class GtkDropTargetDragContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDragContext>
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkDragContext *m_pContext;
+ guint m_nTime;
+#else
+ GdkDrop* m_pDrop;
+#endif
+public:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkDropTargetDragContext(GdkDragContext *pContext, guint nTime)
+ : m_pContext(pContext)
+ , m_nTime(nTime)
+#else
+ GtkDropTargetDragContext(GdkDrop* pDrop)
+ : m_pDrop(pDrop)
+#endif
+ {
+ }
+
+ virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
+#else
+ gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), getPreferredDragAction(dragOperation));
+#endif
+ }
+
+ virtual void SAL_CALL rejectDrag() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
+#else
+ gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), static_cast<GdkDragAction>(0));
+#endif
+ }
+};
+
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return;
+ pThis->m_pDropTarget->signalDragDropReceived(pWidget, context, x, y, data, ttype, time);
+}
+
+void GtkInstDropTarget::signalDragDropReceived(GtkWidget* /*pWidget*/, GdkDragContext * /*context*/, gint /*x*/, gint /*y*/, GtkSelectionData* data, guint /*ttype*/, guint /*time*/)
+{
+ /*
+ * If we get a drop, then we will call like gtk_clipboard_wait_for_contents
+ * with a loop inside a loop to get the right format, so if this is the
+ * case return to the outer loop here with a copy of the desired data
+ *
+ * don't look at me like that.
+ */
+ if (!m_pFormatConversionRequest)
+ return;
+
+ m_pFormatConversionRequest->LoopEnd(gtk_selection_data_copy(data));
+}
+#endif
+
+#if GTK_CHECK_VERSION(4,0,0)
+GdkDragAction GtkSalFrame::signalDragMotion(GtkDropTargetAsync *dest, GdkDrop *drop, double x, double y, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return GdkDragAction(0);
+ return pThis->m_pDropTarget->signalDragMotion(dest, drop, x, y);
+}
+#else
+gboolean GtkSalFrame::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return false;
+ return pThis->m_pDropTarget->signalDragMotion(pWidget, context, x, y, time);
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4,0,0)
+gboolean GtkInstDropTarget::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time)
+#else
+GdkDragAction GtkInstDropTarget::signalDragMotion(GtkDropTargetAsync *context, GdkDrop *pDrop, double x, double y)
+#endif
+{
+ if (!m_bInDrag)
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) : pWidget;
+ gtk_drag_highlight(pHighlightWidget);
+#else
+ GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) :
+ gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(context));
+ gtk_widget_set_state_flags(pHighlightWidget, GTK_STATE_FLAG_DROP_ACTIVE, false);
+#endif
+ }
+
+ css::datatransfer::dnd::DropTargetDragEnterEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(this);
+#if !GTK_CHECK_VERSION(4,0,0)
+ rtl::Reference<GtkDropTargetDragContext> pContext = new GtkDropTargetDragContext(context, time);
+#else
+ rtl::Reference<GtkDropTargetDragContext> pContext = new GtkDropTargetDragContext(pDrop);
+#endif
+ //preliminary accept the Drag and select the preferred action, the fire_* will
+ //inform the original caller of our choice and the callsite can decide
+ //to overrule this choice. i.e. typically here we default to ACTION_MOVE
+#if !GTK_CHECK_VERSION(4,0,0)
+ sal_Int8 nSourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
+ GdkModifierType mask;
+ gdk_window_get_pointer(widget_get_surface(pWidget), nullptr, nullptr, &mask);
+#else
+ sal_Int8 nSourceActions = GdkToVcl(gdk_drop_get_actions(pDrop));
+ GdkModifierType mask = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(context));
+#endif
+
+ // tdf#124411 default to move if drag originates within LO itself, default
+ // to copy if it comes from outside, this is similar to srcAndDestEqual
+ // in macosx DropTarget::determineDropAction equivalent
+ sal_Int8 nNewDropAction = GtkInstDragSource::g_ActiveDragSource ?
+ css::datatransfer::dnd::DNDConstants::ACTION_MOVE :
+ css::datatransfer::dnd::DNDConstants::ACTION_COPY;
+
+ // tdf#109227 if a modifier is held down, default to the matching
+ // action for that modifier combo, otherwise pick the preferred
+ // default from the possible source actions
+ if ((mask & GDK_SHIFT_MASK) && !(mask & GDK_CONTROL_MASK))
+ nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ else if ((mask & GDK_CONTROL_MASK) && !(mask & GDK_SHIFT_MASK))
+ nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
+ else if ((mask & GDK_SHIFT_MASK) && (mask & GDK_CONTROL_MASK) )
+ nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_LINK;
+ nNewDropAction &= nSourceActions;
+
+ GdkDragAction eAction;
+ if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) && !nNewDropAction)
+ eAction = getPreferredDragAction(nSourceActions);
+ else
+ eAction = getPreferredDragAction(nNewDropAction);
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ gdk_drag_status(context, eAction, time);
+#else
+ gdk_drop_status(pDrop,
+ static_cast<GdkDragAction>(eAction | gdk_drop_get_actions(pDrop)),
+ eAction);
+#endif
+ aEvent.Context = pContext;
+ aEvent.LocationX = x;
+ aEvent.LocationY = y;
+ //under wayland at least, the action selected by gdk_drag_status on the
+ //context is not immediately available via gdk_drag_context_get_selected_action
+ //so here we set the DropAction from what we selected on the context, not
+ //what the context says is selected
+ aEvent.DropAction = GdkToVcl(eAction);
+ aEvent.SourceActions = nSourceActions;
+
+ if (!m_bInDrag)
+ {
+ css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
+ // For LibreOffice internal D&D we provide the Transferable without Gtk
+ // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
+ if (GtkInstDragSource::g_ActiveDragSource)
+ xTransferable = GtkInstDragSource::g_ActiveDragSource->GetTransferable();
+ else
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
+#else
+ xTransferable = new GtkDnDTransferable(pDrop);
+#endif
+ }
+ css::uno::Sequence<css::datatransfer::DataFlavor> aFormats = xTransferable->getTransferDataFlavors();
+ aEvent.SupportedDataFlavors = aFormats;
+ fire_dragEnter(aEvent);
+ m_bInDrag = true;
+ }
+ else
+ {
+ fire_dragOver(aEvent);
+ }
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ return true;
+#else
+ return eAction;
+#endif
+}
+
+#if GTK_CHECK_VERSION(4,0,0)
+void GtkSalFrame::signalDragLeave(GtkDropTargetAsync* pDest, GdkDrop* /*drop*/, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return;
+ pThis->m_pDropTarget->signalDragLeave(gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(pDest)));
+}
+#else
+void GtkSalFrame::signalDragLeave(GtkWidget* pWidget, GdkDragContext* /*context*/, guint /*time*/, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return;
+ pThis->m_pDropTarget->signalDragLeave(pWidget);
+}
+#endif
+
+static gboolean lcl_deferred_dragExit(gpointer user_data)
+{
+ GtkInstDropTarget* pThis = static_cast<GtkInstDropTarget*>(user_data);
+ css::datatransfer::dnd::DropTargetEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(pThis);
+ pThis->fire_dragExit(aEvent);
+ return false;
+}
+
+void GtkInstDropTarget::signalDragLeave(GtkWidget *pWidget)
+{
+ m_bInDrag = false;
+
+ GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) : pWidget;
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_drag_unhighlight(pHighlightWidget);
+#else
+ gtk_widget_unset_state_flags(pHighlightWidget, GTK_STATE_FLAG_DROP_ACTIVE);
+#endif
+
+ // defer fire_dragExit, since gtk also sends a drag-leave before the drop, while
+ // LO expect to either handle the drop or the exit... at least in Writer.
+ // but since we don't know there will be a drop following the leave, defer the
+ // exit handling to an idle.
+ g_idle_add(lcl_deferred_dragExit, this);
+}
+
+void GtkSalFrame::signalDestroy( GtkWidget* pObj, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if( pObj != pThis->m_pWindow )
+ return;
+
+ pThis->m_aDamageHandler.damaged = nullptr;
+ pThis->m_aDamageHandler.handle = nullptr;
+ if (pThis->m_pSurface)
+ cairo_surface_set_user_data(pThis->m_pSurface, SvpSalGraphics::getDamageKey(), nullptr, nullptr);
+ pThis->m_pFixedContainer = nullptr;
+ pThis->m_pDrawingArea = nullptr;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ pThis->m_pEventBox = nullptr;
+#endif
+ pThis->m_pTopLevelGrid = nullptr;
+ pThis->m_pWindow = nullptr;
+ pThis->m_xFrameWeld.reset();
+ pThis->InvalidateGraphics();
+}
+
+// GtkSalFrame::IMHandler
+
+GtkSalFrame::IMHandler::IMHandler( GtkSalFrame* pFrame )
+: m_pFrame(pFrame),
+ m_nPrevKeyPresses( 0 ),
+ m_pIMContext( nullptr ),
+ m_bFocused( true ),
+ m_bPreeditJustChanged( false )
+{
+ m_aInputEvent.mpTextAttr = nullptr;
+ createIMContext();
+}
+
+GtkSalFrame::IMHandler::~IMHandler()
+{
+ // cancel an eventual event posted to begin preedit again
+ GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
+ deleteIMContext();
+}
+
+void GtkSalFrame::IMHandler::createIMContext()
+{
+ if( m_pIMContext )
+ return;
+
+ m_pIMContext = gtk_im_multicontext_new ();
+ g_signal_connect( m_pIMContext, "commit",
+ G_CALLBACK (signalIMCommit), this );
+ g_signal_connect( m_pIMContext, "preedit_changed",
+ G_CALLBACK (signalIMPreeditChanged), this );
+ g_signal_connect( m_pIMContext, "retrieve_surrounding",
+ G_CALLBACK (signalIMRetrieveSurrounding), this );
+ g_signal_connect( m_pIMContext, "delete_surrounding",
+ G_CALLBACK (signalIMDeleteSurrounding), this );
+ g_signal_connect( m_pIMContext, "preedit_start",
+ G_CALLBACK (signalIMPreeditStart), this );
+ g_signal_connect( m_pIMContext, "preedit_end",
+ G_CALLBACK (signalIMPreeditEnd), this );
+
+ GetGenericUnixSalData()->ErrorTrapPush();
+ im_context_set_client_widget(m_pIMContext, m_pFrame->getMouseEventWidget());
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_event_controller_key_set_im_context(m_pFrame->m_pKeyController, m_pIMContext);
+#endif
+ gtk_im_context_focus_in( m_pIMContext );
+ GetGenericUnixSalData()->ErrorTrapPop();
+ m_bFocused = true;
+
+}
+
+void GtkSalFrame::IMHandler::deleteIMContext()
+{
+ if( !m_pIMContext )
+ return;
+
+ // first give IC a chance to deinitialize
+ GetGenericUnixSalData()->ErrorTrapPush();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_event_controller_key_set_im_context(m_pFrame->m_pKeyController, nullptr);
+#endif
+ im_context_set_client_widget(m_pIMContext, nullptr);
+ GetGenericUnixSalData()->ErrorTrapPop();
+ // destroy old IC
+ g_object_unref( m_pIMContext );
+ m_pIMContext = nullptr;
+}
+
+void GtkSalFrame::IMHandler::doCallEndExtTextInput()
+{
+ m_aInputEvent.mpTextAttr = nullptr;
+ m_pFrame->CallCallbackExc( SalEvent::EndExtTextInput, nullptr );
+}
+
+void GtkSalFrame::IMHandler::updateIMSpotLocation()
+{
+ SalExtTextInputPosEvent aPosEvent;
+ m_pFrame->CallCallbackExc( SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent) );
+ GdkRectangle aArea;
+ aArea.x = aPosEvent.mnX;
+ aArea.y = aPosEvent.mnY;
+ aArea.width = aPosEvent.mnWidth;
+ aArea.height = aPosEvent.mnHeight;
+ GetGenericUnixSalData()->ErrorTrapPush();
+ gtk_im_context_set_cursor_location( m_pIMContext, &aArea );
+ GetGenericUnixSalData()->ErrorTrapPop();
+}
+
+void GtkSalFrame::IMHandler::sendEmptyCommit()
+{
+ vcl::DeletionListener aDel( m_pFrame );
+
+ SalExtTextInputEvent aEmptyEv;
+ aEmptyEv.mpTextAttr = nullptr;
+ aEmptyEv.maText.clear();
+ aEmptyEv.mnCursorPos = 0;
+ aEmptyEv.mnCursorFlags = 0;
+ m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&aEmptyEv) );
+ if( ! aDel.isDeleted() )
+ m_pFrame->CallCallbackExc( SalEvent::EndExtTextInput, nullptr );
+}
+
+void GtkSalFrame::IMHandler::endExtTextInput( EndExtTextInputFlags /*nFlags*/ )
+{
+ gtk_im_context_reset ( m_pIMContext );
+
+ if( !m_aInputEvent.mpTextAttr )
+ return;
+
+ vcl::DeletionListener aDel( m_pFrame );
+ // delete preedit in sal (commit an empty string)
+ sendEmptyCommit();
+ if( ! aDel.isDeleted() )
+ {
+ // mark previous preedit state again (will e.g. be sent at focus gain)
+ m_aInputEvent.mpTextAttr = m_aInputFlags.data();
+ if( m_bFocused )
+ {
+ // begin preedit again
+ GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
+ }
+ }
+}
+
+void GtkSalFrame::IMHandler::focusChanged( bool bFocusIn )
+{
+ m_bFocused = bFocusIn;
+ if( bFocusIn )
+ {
+ GetGenericUnixSalData()->ErrorTrapPush();
+ gtk_im_context_focus_in( m_pIMContext );
+ GetGenericUnixSalData()->ErrorTrapPop();
+ if( m_aInputEvent.mpTextAttr )
+ {
+ sendEmptyCommit();
+ // begin preedit again
+ GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
+ }
+ }
+ else
+ {
+ GetGenericUnixSalData()->ErrorTrapPush();
+ gtk_im_context_focus_out( m_pIMContext );
+ GetGenericUnixSalData()->ErrorTrapPop();
+ // cancel an eventual event posted to begin preedit again
+ GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
+ }
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+bool GtkSalFrame::IMHandler::handleKeyEvent( GdkEventKey* pEvent )
+{
+ vcl::DeletionListener aDel( m_pFrame );
+
+ if( pEvent->type == GDK_KEY_PRESS )
+ {
+ // Add this key press event to the list of previous key presses
+ // to which we compare key release events. If a later key release
+ // event has a matching key press event in this list, we swallow
+ // the key release because some GTK Input Methods don't swallow it
+ // for us.
+ m_aPrevKeyPresses.emplace_back(pEvent );
+ m_nPrevKeyPresses++;
+
+ // Also pop off the earliest key press event if there are more than 10
+ // already.
+ while (m_nPrevKeyPresses > 10)
+ {
+ m_aPrevKeyPresses.pop_front();
+ m_nPrevKeyPresses--;
+ }
+
+ GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) );
+
+ // #i51353# update spot location on every key input since we cannot
+ // know which key may activate a preedit choice window
+ updateIMSpotLocation();
+ if( aDel.isDeleted() )
+ return true;
+
+ bool bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
+ g_object_unref( pRef );
+
+ if( aDel.isDeleted() )
+ return true;
+
+ m_bPreeditJustChanged = false;
+
+ if( bResult )
+ return true;
+ else
+ {
+ SAL_WARN_IF( m_nPrevKeyPresses <= 0, "vcl.gtk3", "key press has vanished !" );
+ if( ! m_aPrevKeyPresses.empty() ) // sanity check
+ {
+ // event was not swallowed, do not filter a following
+ // key release event
+ // note: this relies on gtk_im_context_filter_keypress
+ // returning without calling a handler (in the "not swallowed"
+ // case ) which might change the previous key press list so
+ // we would pop the wrong event here
+ m_aPrevKeyPresses.pop_back();
+ m_nPrevKeyPresses--;
+ }
+ }
+ }
+
+ // Determine if we got an earlier key press event corresponding to this key release
+ if (pEvent->type == GDK_KEY_RELEASE)
+ {
+ GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) );
+ bool bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
+ g_object_unref( pRef );
+
+ if( aDel.isDeleted() )
+ return true;
+
+ m_bPreeditJustChanged = false;
+
+ auto iter = std::find(m_aPrevKeyPresses.begin(), m_aPrevKeyPresses.end(), pEvent);
+ // If we found a corresponding previous key press event, swallow the release
+ // and remove the earlier key press from our list
+ if (iter != m_aPrevKeyPresses.end())
+ {
+ m_aPrevKeyPresses.erase(iter);
+ m_nPrevKeyPresses--;
+ return true;
+ }
+
+ if( bResult )
+ return true;
+ }
+
+ return false;
+}
+
+/* FIXME:
+* #122282# still more hacking: some IMEs never start a preedit but simply commit
+* in this case we cannot commit a single character. Workaround: do not do the
+* single key hack for enter or space if the unicode committed does not match
+*/
+
+static bool checkSingleKeyCommitHack( guint keyval, sal_Unicode cCode )
+{
+ bool bRet = true;
+ switch( keyval )
+ {
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_Return:
+ if( cCode != '\n' && cCode != '\r' )
+ bRet = false;
+ break;
+ case GDK_KEY_space:
+ case GDK_KEY_KP_Space:
+ if( cCode != ' ' )
+ bRet = false;
+ break;
+ default:
+ break;
+ }
+ return bRet;
+}
+
+void GtkSalFrame::IMHandler::signalIMCommit( GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler )
+{
+ GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
+
+ SolarMutexGuard aGuard;
+ vcl::DeletionListener aDel( pThis->m_pFrame );
+ {
+ const bool bWasPreedit =
+ (pThis->m_aInputEvent.mpTextAttr != nullptr) ||
+ pThis->m_bPreeditJustChanged;
+
+ pThis->m_aInputEvent.mpTextAttr = nullptr;
+ pThis->m_aInputEvent.maText = OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 );
+ pThis->m_aInputEvent.mnCursorPos = pThis->m_aInputEvent.maText.getLength();
+ pThis->m_aInputEvent.mnCursorFlags = 0;
+
+ pThis->m_aInputFlags.clear();
+
+ /* necessary HACK: all keyboard input comes in here as soon as an IMContext is set
+ * which is logical and consequent. But since even simple input like
+ * <space> comes through the commit signal instead of signalKey
+ * and all kinds of windows only implement KeyInput (e.g. PushButtons,
+ * RadioButtons and a lot of other Controls), will send a single
+ * KeyInput/KeyUp sequence instead of an ExtText event if there
+ * never was a preedit and the text is only one character.
+ *
+ * In this case there the last ExtText event must have been
+ * SalEvent::EndExtTextInput, either because of a regular commit
+ * or because there never was a preedit.
+ */
+ bool bSingleCommit = false;
+ if( ! bWasPreedit
+ && pThis->m_aInputEvent.maText.getLength() == 1
+ && ! pThis->m_aPrevKeyPresses.empty()
+ )
+ {
+ const PreviousKeyPress& rKP = pThis->m_aPrevKeyPresses.back();
+ sal_Unicode aOrigCode = pThis->m_aInputEvent.maText[0];
+
+ if( checkSingleKeyCommitHack( rKP.keyval, aOrigCode ) )
+ {
+ pThis->m_pFrame->doKeyCallback( rKP.state, rKP.keyval, rKP.hardware_keycode, rKP.group, aOrigCode, true, true );
+ bSingleCommit = true;
+ }
+ }
+ if( ! bSingleCommit )
+ {
+ pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
+ if( ! aDel.isDeleted() )
+ pThis->doCallEndExtTextInput();
+ }
+ if( ! aDel.isDeleted() )
+ {
+ // reset input event
+ pThis->m_aInputEvent.maText.clear();
+ pThis->m_aInputEvent.mnCursorPos = 0;
+ pThis->updateIMSpotLocation();
+ }
+ }
+}
+#else
+void GtkSalFrame::IMHandler::signalIMCommit( GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler )
+{
+ GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
+
+ SolarMutexGuard aGuard;
+ vcl::DeletionListener aDel( pThis->m_pFrame );
+ {
+#if 0
+ const bool bWasPreedit =
+ (pThis->m_aInputEvent.mpTextAttr != nullptr) ||
+ pThis->m_bPreeditJustChanged;
+#endif
+
+ pThis->m_aInputEvent.mpTextAttr = nullptr;
+ pThis->m_aInputEvent.maText = OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 );
+ pThis->m_aInputEvent.mnCursorPos = pThis->m_aInputEvent.maText.getLength();
+ pThis->m_aInputEvent.mnCursorFlags = 0;
+
+ pThis->m_aInputFlags.clear();
+
+ /* necessary HACK: all keyboard input comes in here as soon as an IMContext is set
+ * which is logical and consequent. But since even simple input like
+ * <space> comes through the commit signal instead of signalKey
+ * and all kinds of windows only implement KeyInput (e.g. PushButtons,
+ * RadioButtons and a lot of other Controls), will send a single
+ * KeyInput/KeyUp sequence instead of an ExtText event if there
+ * never was a preedit and the text is only one character.
+ *
+ * In this case there the last ExtText event must have been
+ * SalEvent::EndExtTextInput, either because of a regular commit
+ * or because there never was a preedit.
+ */
+ bool bSingleCommit = false;
+#if 0
+ // TODO this needs a rethink to work again if necessary
+ if( ! bWasPreedit
+ && pThis->m_aInputEvent.maText.getLength() == 1
+ && ! pThis->m_aPrevKeyPresses.empty()
+ )
+ {
+ const PreviousKeyPress& rKP = pThis->m_aPrevKeyPresses.back();
+ sal_Unicode aOrigCode = pThis->m_aInputEvent.maText[0];
+
+ if( checkSingleKeyCommitHack( rKP.keyval, aOrigCode ) )
+ {
+ pThis->m_pFrame->doKeyCallback( rKP.state, rKP.keyval, rKP.hardware_keycode, rKP.group, aOrigCode, true, true );
+ bSingleCommit = true;
+ }
+ }
+#endif
+ if( ! bSingleCommit )
+ {
+ pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
+ if( ! aDel.isDeleted() )
+ pThis->doCallEndExtTextInput();
+ }
+ if( ! aDel.isDeleted() )
+ {
+ // reset input event
+ pThis->m_aInputEvent.maText.clear();
+ pThis->m_aInputEvent.mnCursorPos = 0;
+ pThis->updateIMSpotLocation();
+ }
+ }
+}
+#endif
+
+OUString GtkSalFrame::GetPreeditDetails(GtkIMContext* pIMContext, std::vector<ExtTextInputAttr>& rInputFlags, sal_Int32& rCursorPos, sal_uInt8& rCursorFlags)
+{
+ char* pText = nullptr;
+ PangoAttrList* pAttrs = nullptr;
+ gint nCursorPos = 0;
+
+ gtk_im_context_get_preedit_string( pIMContext,
+ &pText,
+ &pAttrs,
+ &nCursorPos );
+
+ gint nUtf8Len = pText ? strlen(pText) : 0;
+ OUString sText = pText ? OUString(pText, nUtf8Len, RTL_TEXTENCODING_UTF8) : OUString();
+
+ std::vector<sal_Int32> aUtf16Offsets;
+ for (sal_Int32 nUtf16Offset = 0; nUtf16Offset < sText.getLength(); sText.iterateCodePoints(&nUtf16Offset))
+ aUtf16Offsets.push_back(nUtf16Offset);
+
+ sal_Int32 nUtf32Len = aUtf16Offsets.size();
+ // from the above loop filling aUtf16Offsets, we know that its size() fits into sal_Int32
+ aUtf16Offsets.push_back(sText.getLength());
+
+ // sanitize the CurPos which is in utf-32
+ if (nCursorPos < 0)
+ nCursorPos = 0;
+ else if (nCursorPos > nUtf32Len)
+ nCursorPos = nUtf32Len;
+
+ rCursorPos = aUtf16Offsets[nCursorPos];
+ rCursorFlags = 0;
+
+ rInputFlags.resize(std::max(1, static_cast<int>(sText.getLength())), ExtTextInputAttr::NONE);
+
+ PangoAttrIterator *iter = pango_attr_list_get_iterator(pAttrs);
+ do
+ {
+ GSList *attr_list = nullptr;
+ GSList *tmp_list = nullptr;
+ gint nUtf8Start, nUtf8End;
+ ExtTextInputAttr sal_attr = ExtTextInputAttr::NONE;
+
+ // docs say... "Get the range of the current segment ... the stored
+ // return values are signed, not unsigned like the values in
+ // PangoAttribute", which implies that the units are otherwise the same
+ // as that of PangoAttribute whose docs state these units are "in
+ // bytes"
+ // so this is the utf8 range
+ pango_attr_iterator_range(iter, &nUtf8Start, &nUtf8End);
+
+ // sanitize the utf8 range
+ nUtf8Start = std::min(nUtf8Start, nUtf8Len);
+ nUtf8End = std::min(nUtf8End, nUtf8Len);
+ if (nUtf8Start >= nUtf8End)
+ continue;
+
+ // get the utf32 range
+ sal_Int32 nUtf32Start = g_utf8_pointer_to_offset(pText, pText + nUtf8Start);
+ sal_Int32 nUtf32End = g_utf8_pointer_to_offset(pText, pText + nUtf8End);
+
+ // sanitize the utf32 range
+ nUtf32Start = std::min(nUtf32Start, nUtf32Len);
+ nUtf32End = std::min(nUtf32End, nUtf32Len);
+ if (nUtf32Start >= nUtf32End)
+ continue;
+
+ tmp_list = attr_list = pango_attr_iterator_get_attrs (iter);
+ while (tmp_list)
+ {
+ PangoAttribute *pango_attr = static_cast<PangoAttribute *>(tmp_list->data);
+
+ switch (pango_attr->klass->type)
+ {
+ case PANGO_ATTR_BACKGROUND:
+ sal_attr |= ExtTextInputAttr::Highlight;
+ rCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
+ break;
+ case PANGO_ATTR_UNDERLINE:
+ {
+ PangoAttrInt* pango_underline = reinterpret_cast<PangoAttrInt*>(pango_attr);
+ switch (pango_underline->value)
+ {
+ case PANGO_UNDERLINE_NONE:
+ break;
+ case PANGO_UNDERLINE_DOUBLE:
+ sal_attr |= ExtTextInputAttr::DoubleUnderline;
+ break;
+ default:
+ sal_attr |= ExtTextInputAttr::Underline;
+ break;
+ }
+ break;
+ }
+ case PANGO_ATTR_STRIKETHROUGH:
+ sal_attr |= ExtTextInputAttr::RedText;
+ break;
+ default:
+ break;
+ }
+ pango_attribute_destroy (pango_attr);
+ tmp_list = tmp_list->next;
+ }
+ if (!attr_list)
+ sal_attr |= ExtTextInputAttr::Underline;
+ g_slist_free (attr_list);
+
+ // Set the sal attributes on our text
+ // rhbz#1648281 apply over our utf-16 range derived from the input utf-32 range
+ for (sal_Int32 i = aUtf16Offsets[nUtf32Start]; i < aUtf16Offsets[nUtf32End]; ++i)
+ {
+ SAL_WARN_IF(i >= static_cast<int>(rInputFlags.size()),
+ "vcl.gtk3", "pango attrib out of range. Broken range: "
+ << aUtf16Offsets[nUtf32Start] << "," << aUtf16Offsets[nUtf32End] << " Legal range: 0,"
+ << rInputFlags.size());
+ if (i >= static_cast<int>(rInputFlags.size()))
+ continue;
+ rInputFlags[i] |= sal_attr;
+ }
+ } while (pango_attr_iterator_next (iter));
+ pango_attr_iterator_destroy(iter);
+
+ g_free( pText );
+ pango_attr_list_unref( pAttrs );
+
+ return sText;
+}
+
+void GtkSalFrame::IMHandler::signalIMPreeditChanged( GtkIMContext* pIMContext, gpointer im_handler )
+{
+ GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
+
+ sal_Int32 nCursorPos(0);
+ sal_uInt8 nCursorFlags(0);
+ std::vector<ExtTextInputAttr> aInputFlags;
+ OUString sText = GtkSalFrame::GetPreeditDetails(pIMContext, aInputFlags, nCursorPos, nCursorFlags);
+ if (sText.isEmpty() && pThis->m_aInputEvent.maText.isEmpty())
+ {
+ // change from nothing to nothing -> do not start preedit
+ // e.g. this will activate input into a calc cell without
+ // user input
+ return;
+ }
+
+ pThis->m_bPreeditJustChanged = true;
+
+ bool bEndPreedit = sText.isEmpty() && pThis->m_aInputEvent.mpTextAttr != nullptr;
+ pThis->m_aInputEvent.maText = sText;
+ pThis->m_aInputEvent.mnCursorPos = nCursorPos;
+ pThis->m_aInputEvent.mnCursorFlags = nCursorFlags;
+ pThis->m_aInputFlags = aInputFlags;
+ pThis->m_aInputEvent.mpTextAttr = pThis->m_aInputFlags.data();
+
+ SolarMutexGuard aGuard;
+ vcl::DeletionListener aDel( pThis->m_pFrame );
+
+ pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
+ if( bEndPreedit && ! aDel.isDeleted() )
+ pThis->doCallEndExtTextInput();
+ if( ! aDel.isDeleted() )
+ pThis->updateIMSpotLocation();
+}
+
+void GtkSalFrame::IMHandler::signalIMPreeditStart( GtkIMContext*, gpointer /*im_handler*/ )
+{
+}
+
+void GtkSalFrame::IMHandler::signalIMPreeditEnd( GtkIMContext*, gpointer im_handler )
+{
+ GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
+
+ pThis->m_bPreeditJustChanged = true;
+
+ SolarMutexGuard aGuard;
+ vcl::DeletionListener aDel( pThis->m_pFrame );
+ pThis->doCallEndExtTextInput();
+ if( ! aDel.isDeleted() )
+ pThis->updateIMSpotLocation();
+}
+
+gboolean GtkSalFrame::IMHandler::signalIMRetrieveSurrounding( GtkIMContext* pContext, gpointer im_handler )
+{
+ GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
+
+ SalSurroundingTextRequestEvent aEvt;
+ aEvt.maText.clear();
+ aEvt.mnStart = aEvt.mnEnd = 0;
+
+ SolarMutexGuard aGuard;
+ pThis->m_pFrame->CallCallback(SalEvent::SurroundingTextRequest, &aEvt);
+
+ OString sUTF = OUStringToOString(aEvt.maText, RTL_TEXTENCODING_UTF8);
+ std::u16string_view sCursorText(aEvt.maText.subView(0, aEvt.mnStart));
+ gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
+ OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
+ return true;
+}
+
+gboolean GtkSalFrame::IMHandler::signalIMDeleteSurrounding( GtkIMContext*, gint offset, gint nchars,
+ gpointer im_handler )
+{
+ GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
+
+ // First get the surrounding text
+ SalSurroundingTextRequestEvent aSurroundingTextEvt;
+ aSurroundingTextEvt.maText.clear();
+ aSurroundingTextEvt.mnStart = aSurroundingTextEvt.mnEnd = 0;
+
+ SolarMutexGuard aGuard;
+ pThis->m_pFrame->CallCallback(SalEvent::SurroundingTextRequest, &aSurroundingTextEvt);
+
+ // Turn offset, nchars into a utf-16 selection
+ Selection aSelection = SalFrame::CalcDeleteSurroundingSelection(aSurroundingTextEvt.maText,
+ aSurroundingTextEvt.mnStart,
+ offset, nchars);
+ Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32);
+ if (aSelection == aInvalid)
+ return false;
+
+ SalSurroundingTextSelectionChangeEvent aEvt;
+ aEvt.mnStart = aSelection.Min();
+ aEvt.mnEnd = aSelection.Max();
+
+ pThis->m_pFrame->CallCallback(SalEvent::DeleteSurroundingTextRequest, &aEvt);
+
+ aSelection = Selection(aEvt.mnStart, aEvt.mnEnd);
+ if (aSelection == aInvalid)
+ return false;
+
+ return true;
+}
+
+AbsoluteScreenPixelSize GtkSalDisplay::GetScreenSize( int nDisplayScreen )
+{
+ AbsoluteScreenPixelRectangle aRect = m_pSys->GetDisplayScreenPosSizePixel( nDisplayScreen );
+ return AbsoluteScreenPixelSize( aRect.GetWidth(), aRect.GetHeight() );
+}
+
+sal_uIntPtr GtkSalFrame::GetNativeWindowHandle(GtkWidget *pWidget)
+{
+ GdkSurface* pSurface = widget_get_surface(pWidget);
+ GdkDisplay *pDisplay = getGdkDisplay();
+
+#if defined(GDK_WINDOWING_X11)
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ return gdk_x11_surface_get_xid(pSurface);
+ }
+#endif
+
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
+ {
+ return reinterpret_cast<sal_uIntPtr>(gdk_wayland_surface_get_wl_surface(pSurface));
+ }
+#endif
+
+ return 0;
+}
+
+void GtkInstDragSource::set_datatransfer(const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
+ const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener)
+{
+ m_xListener = rListener;
+ m_xTrans = rTrans;
+}
+
+void GtkInstDragSource::setActiveDragSource()
+{
+ // For LibreOffice internal D&D we provide the Transferable without Gtk
+ // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
+ g_ActiveDragSource = this;
+ g_DropSuccessSet = false;
+ g_DropSuccess = false;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+std::vector<GtkTargetEntry> GtkInstDragSource::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
+{
+ return m_aConversionHelper.FormatsToGtk(rFormats);
+}
+#endif
+
+void GtkInstDragSource::startDrag(const datatransfer::dnd::DragGestureEvent& rEvent,
+ sal_Int8 sourceActions, sal_Int32 /*cursor*/, sal_Int32 /*image*/,
+ const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
+ const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener)
+{
+ set_datatransfer(rTrans, rListener);
+
+ if (m_pFrame)
+ {
+ setActiveDragSource();
+
+ m_pFrame->startDrag(rEvent, rTrans, m_aConversionHelper ,VclToGdk(sourceActions));
+ }
+ else
+ dragFailed();
+}
+
+void GtkSalFrame::startDrag(const css::datatransfer::dnd::DragGestureEvent& rEvent,
+ const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
+ VclToGtkHelper& rConversionHelper,
+ GdkDragAction sourceActions)
+{
+ SolarMutexGuard aGuard;
+
+ assert(m_pDragSource);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+
+ GdkSeat *pSeat = gdk_display_get_default_seat(getGdkDisplay());
+ GdkDrag* pDrag = gdk_drag_begin(widget_get_surface(getMouseEventWidget()),
+ gdk_seat_get_pointer(pSeat),
+ transerable_content_new(&rConversionHelper, rTrans.get()),
+ sourceActions,
+ rEvent.DragOriginX, rEvent.DragOriginY);
+
+ g_signal_connect(G_OBJECT(pDrag), "drop-performed", G_CALLBACK(signalDragEnd), this);
+ g_signal_connect(G_OBJECT(pDrag), "cancel", G_CALLBACK(signalDragFailed), this);
+ g_signal_connect(G_OBJECT(pDrag), "dnd-finished", G_CALLBACK(signalDragDelete), this);
+
+#else
+
+ auto aFormats = rTrans->getTransferDataFlavors();
+ auto aGtkTargets = rConversionHelper.FormatsToGtk(aFormats);
+
+ GtkTargetList *pTargetList = gtk_target_list_new(aGtkTargets.data(), aGtkTargets.size());
+
+ gint nDragButton = 1; // default to left button
+ css::awt::MouseEvent aEvent;
+ if (rEvent.Event >>= aEvent)
+ {
+ if (aEvent.Buttons & css::awt::MouseButton::LEFT )
+ nDragButton = 1;
+ else if (aEvent.Buttons & css::awt::MouseButton::RIGHT)
+ nDragButton = 3;
+ else if (aEvent.Buttons & css::awt::MouseButton::MIDDLE)
+ nDragButton = 2;
+ }
+
+ GdkEvent aFakeEvent;
+ memset(&aFakeEvent, 0, sizeof(GdkEvent));
+ aFakeEvent.type = GDK_BUTTON_PRESS;
+ aFakeEvent.button.window = widget_get_surface(getMouseEventWidget());
+ aFakeEvent.button.time = GDK_CURRENT_TIME;
+
+ aFakeEvent.button.device = gtk_get_current_event_device();
+ // if no current event to determine device, or (tdf#140272) the device will be unsuitable then find an
+ // appropriate device to use.
+ if (!aFakeEvent.button.device || !gdk_device_get_window_at_position(aFakeEvent.button.device, nullptr, nullptr))
+ {
+ GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(getGdkDisplay());
+ GList* pDevices = gdk_device_manager_list_devices(pDeviceManager, GDK_DEVICE_TYPE_MASTER);
+ for (GList* pEntry = pDevices; pEntry; pEntry = pEntry->next)
+ {
+ GdkDevice* pDevice = static_cast<GdkDevice*>(pEntry->data);
+ if (gdk_device_get_source(pDevice) == GDK_SOURCE_KEYBOARD)
+ continue;
+ if (gdk_device_get_window_at_position(pDevice, nullptr, nullptr))
+ {
+ aFakeEvent.button.device = pDevice;
+ break;
+ }
+ }
+ g_list_free(pDevices);
+ }
+
+ GdkDragContext *pDrag;
+ if (!aFakeEvent.button.device || !gdk_device_get_window_at_position(aFakeEvent.button.device, nullptr, nullptr))
+ pDrag = nullptr;
+ else
+ pDrag = gtk_drag_begin_with_coordinates(getMouseEventWidget(),
+ pTargetList,
+ sourceActions,
+ nDragButton,
+ &aFakeEvent,
+ rEvent.DragOriginX,
+ rEvent.DragOriginY);
+
+ gtk_target_list_unref(pTargetList);
+
+ for (auto &a : aGtkTargets)
+ g_free(a.target);
+#endif
+
+ if (!pDrag)
+ m_pDragSource->dragFailed();
+}
+
+void GtkInstDragSource::dragFailed()
+{
+ if (m_xListener.is())
+ {
+ datatransfer::dnd::DragSourceDropEvent aEv;
+ aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_NONE;
+ aEv.DropSuccess = false;
+ auto xListener = m_xListener;
+ m_xListener.clear();
+ xListener->dragDropEnd(aEv);
+ }
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalDragFailed(GdkDrag* /*drag*/, GdkDragCancelReason /*reason*/, gpointer frame)
+#else
+gboolean GtkSalFrame::signalDragFailed(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkDragResult /*result*/, gpointer frame)
+#endif
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (pThis->m_pDragSource)
+ pThis->m_pDragSource->dragFailed();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return false;
+#endif
+}
+
+void GtkInstDragSource::dragDelete()
+{
+ if (m_xListener.is())
+ {
+ datatransfer::dnd::DragSourceDropEvent aEv;
+ aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ aEv.DropSuccess = true;
+ auto xListener = m_xListener;
+ m_xListener.clear();
+ xListener->dragDropEnd(aEv);
+ }
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalDragDelete(GdkDrag* /*context*/, gpointer frame)
+#else
+void GtkSalFrame::signalDragDelete(GtkWidget* /*widget*/, GdkDragContext* /*context*/, gpointer frame)
+#endif
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDragSource)
+ return;
+ pThis->m_pDragSource->dragDelete();
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void GtkInstDragSource::dragEnd(GdkDrag* context)
+#else
+void GtkInstDragSource::dragEnd(GdkDragContext* context)
+#endif
+{
+ if (m_xListener.is())
+ {
+ datatransfer::dnd::DragSourceDropEvent aEv;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ aEv.DropAction = GdkToVcl(gdk_drag_get_selected_action(context));
+#else
+ aEv.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
+#endif
+ // an internal drop can accept the drop but fail with dropComplete( false )
+ // this is different than the GTK API
+ if (g_DropSuccessSet)
+ aEv.DropSuccess = g_DropSuccess;
+ else
+ aEv.DropSuccess = true;
+ auto xListener = m_xListener;
+ m_xListener.clear();
+ xListener->dragDropEnd(aEv);
+ }
+ g_ActiveDragSource = nullptr;
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalDragEnd(GdkDrag* context, gpointer frame)
+#else
+void GtkSalFrame::signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer frame)
+#endif
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDragSource)
+ return;
+ pThis->m_pDragSource->dragEnd(context);
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void GtkInstDragSource::dragDataGet(GtkSelectionData *data, guint info)
+{
+ m_aConversionHelper.setSelectionData(m_xTrans, data, info);
+}
+
+void GtkSalFrame::signalDragDataGet(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkSelectionData *data, guint info,
+ guint /*time*/, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDragSource)
+ return;
+ pThis->m_pDragSource->dragDataGet(data, info);
+}
+#endif
+
+bool GtkSalFrame::CallCallbackExc(SalEvent nEvent, const void* pEvent) const
+{
+ SolarMutexGuard aGuard;
+ bool nRet = false;
+ try
+ {
+ nRet = CallCallback(nEvent, pEvent);
+ }
+ catch (...)
+ {
+ GetGtkSalData()->setException(std::current_exception());
+ }
+ return nRet;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::nopaint_container_resize_children(GtkContainer *pContainer)
+{
+ bool bOrigSalObjectSetPosSize = m_bSalObjectSetPosSize;
+ m_bSalObjectSetPosSize = true;
+ gtk_container_resize_children(pContainer);
+ m_bSalObjectSetPosSize = bOrigSalObjectSetPosSize;
+}
+#endif
+
+GdkEvent* GtkSalFrame::makeFakeKeyPress(GtkWidget* pWidget)
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkEvent *event = gdk_event_new(GDK_KEY_PRESS);
+ event->key.window = GDK_WINDOW(g_object_ref(widget_get_surface(pWidget)));
+
+ GdkSeat *seat = gdk_display_get_default_seat(gtk_widget_get_display(pWidget));
+ gdk_event_set_device(event, gdk_seat_get_keyboard(seat));
+
+ event->key.send_event = 1 /* TRUE */;
+ event->key.time = gtk_get_current_event_time();
+ event->key.state = 0;
+ event->key.keyval = 0;
+ event->key.length = 0;
+ event->key.string = nullptr;
+ event->key.hardware_keycode = 0;
+ event->key.group = 0;
+ event->key.is_modifier = false;
+ return event;
+#else
+ (void)pWidget;
+ return nullptr;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtkinst.cxx b/vcl/unx/gtk3/gtkinst.cxx
new file mode 100644
index 0000000000..c76d6291ce
--- /dev/null
+++ b/vcl/unx/gtk3/gtkinst.cxx
@@ -0,0 +1,24855 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <deque>
+#include <optional>
+#include <stack>
+#include <string.h>
+#include <string_view>
+
+#include <dndhelper.hxx>
+#include <osl/process.h>
+#include <osl/file.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include <unx/genprn.h>
+#include <unx/salobj.h>
+#include <unx/gtk/gtkgdi.hxx>
+#include <unx/gtk/gtkframe.hxx>
+#include <unx/gtk/gtkobject.hxx>
+#include <unx/gtk/atkbridge.hxx>
+#include <unx/gtk/gtksalmenu.hxx>
+#include <headless/svpvd.hxx>
+#include <headless/svpbmp.hxx>
+#include <utility>
+#include <vcl/builder.hxx>
+#include <vcl/inputtypes.hxx>
+#include <vcl/specialchars.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/transfer.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <unx/genpspgraphics.h>
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+#include <rtl/uri.hxx>
+
+#include <vcl/settings.hxx>
+
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+#include "a11y/atkwrapper.hxx"
+#endif
+#include <com/sun/star/awt/XVclWindowPeer.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
+#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
+#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#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 <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/string.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <rtl/bootstrap.hxx>
+#include <o3tl/unreachable.hxx>
+#include <o3tl/string_view.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/zformat.hxx>
+#include <tools/helpers.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <unotools/resmgr.hxx>
+#include <unotools/tempfile.hxx>
+#include <unx/gstsink.hxx>
+#include <vcl/ImageTree.hxx>
+#include <vcl/abstdlg.hxx>
+#include <vcl/event.hxx>
+#include <vcl/i18nhelp.hxx>
+#include <vcl/quickselectionengine.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/filter/PngImageWriter.hxx>
+#include <vcl/stdtext.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/wrkwin.hxx>
+#include "customcellrenderer.hxx"
+#include <strings.hrc>
+#include <window.h>
+#include <numeric>
+#include <boost/property_tree/ptree.hpp>
+#include <opengl/zone.hxx>
+
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+
+extern "C"
+{
+ #define GET_YIELD_MUTEX() static_cast<GtkYieldMutex*>(GetSalInstance()->GetYieldMutex())
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void GdkThreadsEnter()
+ {
+ GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
+ pYieldMutex->ThreadsEnter();
+ }
+ static void GdkThreadsLeave()
+ {
+ GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
+ pYieldMutex->ThreadsLeave();
+ }
+#endif
+
+ VCLPLUG_GTK_PUBLIC SalInstance* create_SalInstance()
+ {
+ SAL_INFO(
+ "vcl.gtk",
+ "create vcl plugin instance with gtk version " << gtk_get_major_version()
+ << " " << gtk_get_minor_version() << " " << gtk_get_micro_version());
+
+ if (gtk_get_major_version() == 3 && gtk_get_minor_version() < 18)
+ {
+ g_warning("require gtk >= 3.18 for theme expectations");
+ return nullptr;
+ }
+
+ // for gtk2 it is always built with X support, so this is always called
+ // for gtk3 it is normally built with X and Wayland support, if
+ // X is supported GDK_WINDOWING_X11 is defined and this is always
+ // called, regardless of if we're running under X or Wayland.
+ // We can't use (DLSYM_GDK_IS_X11_DISPLAY(pDisplay)) to only do it under
+ // X, because we need to do it earlier than we have a display
+#if defined(GDK_WINDOWING_X11)
+ /* #i92121# workaround deadlocks in the X11 implementation
+ */
+ static const char* pNoXInitThreads = getenv( "SAL_NO_XINITTHREADS" );
+ /* #i90094#
+ from now on we know that an X connection will be
+ established, so protect X against itself
+ */
+ if( ! ( pNoXInitThreads && *pNoXInitThreads ) )
+ XInitThreads();
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // init gdk thread protection
+ bool const sup = g_thread_supported();
+ // extracted from the 'if' to avoid Clang -Wunreachable-code
+ if ( !sup )
+ g_thread_init( nullptr );
+
+ gdk_threads_set_lock_functions (GdkThreadsEnter, GdkThreadsLeave);
+ SAL_INFO("vcl.gtk", "Hooked gdk threads locks");
+#endif
+
+ auto pYieldMutex = std::make_unique<GtkYieldMutex>();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gdk_threads_init();
+#endif
+
+ GtkInstance* pInstance = new GtkInstance( std::move(pYieldMutex) );
+ SAL_INFO("vcl.gtk", "creating GtkInstance " << pInstance);
+
+ // Create SalData, this does not leak
+ new GtkSalData();
+
+ return pInstance;
+ }
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+static VclInputFlags categorizeEvent(const GdkEvent *pEvent)
+{
+ VclInputFlags nType = VclInputFlags::NONE;
+ switch (gdk_event_get_event_type(pEvent))
+ {
+ case GDK_MOTION_NOTIFY:
+ case GDK_BUTTON_PRESS:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+#endif
+ case GDK_BUTTON_RELEASE:
+ case GDK_ENTER_NOTIFY:
+ case GDK_LEAVE_NOTIFY:
+ case GDK_SCROLL:
+ nType = VclInputFlags::MOUSE;
+ break;
+ case GDK_KEY_PRESS:
+ // case GDK_KEY_RELEASE: //similar to the X11SalInstance one
+ nType = VclInputFlags::KEYBOARD;
+ break;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ case GDK_EXPOSE:
+ nType = VclInputFlags::PAINT;
+ break;
+#endif
+ default:
+ nType = VclInputFlags::OTHER;
+ break;
+ }
+ return nType;
+}
+#endif
+
+GtkInstance::GtkInstance( std::unique_ptr<SalYieldMutex> pMutex )
+ : SvpSalInstance( std::move(pMutex) )
+ , m_pTimer(nullptr)
+ , bNeedsInit(true)
+ , m_pLastCairoFontOptions(nullptr)
+{
+ m_bSupportsOpenGL = true;
+}
+
+//We want to defer initializing gtk until we are after uno has been
+//bootstrapped so we can ask the config what the UI language is so that we can
+//force that in as $LANGUAGE to get gtk to render widgets RTL if we have a RTL
+//UI in a LTR locale
+void GtkInstance::AfterAppInit()
+{
+ EnsureInit();
+}
+
+void GtkInstance::EnsureInit()
+{
+ if (!bNeedsInit)
+ return;
+ // initialize SalData
+ GtkSalData *pSalData = GetGtkSalData();
+ pSalData->Init();
+ GtkSalData::initNWF();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ InitAtkBridge();
+#endif
+
+ ImplSVData* pSVData = ImplGetSVData();
+#ifdef GTK_TOOLKIT_NAME
+ pSVData->maAppData.mxToolkitName = OUString(GTK_TOOLKIT_NAME);
+#else
+ pSVData->maAppData.mxToolkitName = OUString("gtk3");
+#endif
+
+ bNeedsInit = false;
+}
+
+GtkInstance::~GtkInstance()
+{
+ assert( nullptr == m_pTimer );
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ DeInitAtkBridge();
+#endif
+ ResetLastSeenCairoFontOptions(nullptr);
+}
+
+SalFrame* GtkInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
+{
+ EnsureInit();
+ return new GtkSalFrame( pParent, nStyle );
+}
+
+SalFrame* GtkInstance::CreateChildFrame( SystemParentData* pParentData, SalFrameStyleFlags )
+{
+ EnsureInit();
+ return new GtkSalFrame( pParentData );
+}
+
+SalObject* GtkInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow )
+{
+ EnsureInit();
+ //FIXME: Missing CreateObject functionality ...
+ if (pWindowData && pWindowData->bClipUsingNativeWidget)
+ return new GtkSalObjectWidgetClip(static_cast<GtkSalFrame*>(pParent), bShow);
+ return new GtkSalObject(static_cast<GtkSalFrame*>(pParent), bShow);
+}
+
+extern "C"
+{
+ typedef void*(* getDefaultFnc)();
+ typedef void(* addItemFnc)(void *, const char *);
+}
+
+void GtkInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString&, const OUString&)
+{
+ EnsureInit();
+ OString sGtkURL;
+ rtl_TextEncoding aSystemEnc = osl_getThreadTextEncoding();
+ if ((aSystemEnc == RTL_TEXTENCODING_UTF8) || !rFileUrl.startsWith( "file://" ))
+ sGtkURL = OUStringToOString(rFileUrl, RTL_TEXTENCODING_UTF8);
+ else
+ {
+ //Non-utf8 locales are a bad idea if trying to work with non-ascii filenames
+ //Decode %XX components
+ OUString sDecodedUri = rtl::Uri::decode(rFileUrl.copy(7), rtl_UriDecodeToIuri, RTL_TEXTENCODING_UTF8);
+ //Convert back to system locale encoding
+ OString sSystemUrl = OUStringToOString(sDecodedUri, aSystemEnc);
+ //Encode to an escaped ASCII-encoded URI
+ gchar *g_uri = g_filename_to_uri(sSystemUrl.getStr(), nullptr, nullptr);
+ sGtkURL = OString(g_uri);
+ g_free(g_uri);
+ }
+ GtkRecentManager *manager = gtk_recent_manager_get_default ();
+ gtk_recent_manager_add_item (manager, sGtkURL.getStr());
+}
+
+SalInfoPrinter* GtkInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pSetupData )
+{
+ EnsureInit();
+ mbPrinterInit = true;
+ // create and initialize SalInfoPrinter
+ PspSalInfoPrinter* pPrinter = new PspSalInfoPrinter;
+ configurePspInfoPrinter(pPrinter, pQueueInfo, pSetupData);
+ return pPrinter;
+}
+
+std::unique_ptr<SalPrinter> GtkInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
+{
+ EnsureInit();
+ mbPrinterInit = true;
+ return std::unique_ptr<SalPrinter>(new PspSalPrinter(pInfoPrinter));
+}
+
+/*
+ * These methods always occur in pairs
+ * A ThreadsEnter is followed by a ThreadsLeave
+ * We need to queue up the recursive lock count
+ * for each pair, so we can accurately restore
+ * it later.
+ */
+thread_local std::stack<sal_uInt32> GtkYieldMutex::yieldCounts;
+
+void GtkYieldMutex::ThreadsEnter()
+{
+ acquire();
+ if (yieldCounts.empty())
+ return;
+ auto n = yieldCounts.top();
+ yieldCounts.pop();
+
+ const bool bUndoingLeaveWithoutEnter = n == 0;
+ // if the ThreadsLeave bLeaveWithoutEnter of true condition occurred to
+ // create this entry then return early undoing the initial acquire of the
+ // function
+ if G_UNLIKELY(bUndoingLeaveWithoutEnter)
+ {
+ release();
+ return;
+ }
+
+ assert(n > 0);
+ n--;
+ if (n > 0)
+ acquire(n);
+}
+
+void GtkYieldMutex::ThreadsLeave()
+{
+ const bool bLeaveWithoutEnter = m_nCount == 0;
+ SAL_WARN_IF(bLeaveWithoutEnter, "vcl.gtk", "gdk_threads_leave without matching gdk_threads_enter");
+ yieldCounts.push(m_nCount);
+ if G_UNLIKELY(bLeaveWithoutEnter) // this ideally shouldn't happen, but can due to the gtk3 file dialog
+ return;
+ release(true);
+}
+
+std::unique_ptr<SalVirtualDevice> GtkInstance::CreateVirtualDevice( SalGraphics &rG,
+ tools::Long &nDX, tools::Long &nDY,
+ DeviceFormat /*eFormat*/,
+ const SystemGraphicsData* pGd )
+{
+ EnsureInit();
+ SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(&rG);
+ assert(pSvpSalGraphics);
+ // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget
+ cairo_surface_t* pPreExistingTarget = pGd ? static_cast<cairo_surface_t*>(pGd->pSurface) : nullptr;
+ std::unique_ptr<SalVirtualDevice> xNew(new SvpSalVirtualDevice(pSvpSalGraphics->getSurface(), pPreExistingTarget));
+ if (!xNew->SetSize(nDX, nDY))
+ xNew.reset();
+ return xNew;
+}
+
+std::shared_ptr<SalBitmap> GtkInstance::CreateSalBitmap()
+{
+ EnsureInit();
+ return SvpSalInstance::CreateSalBitmap();
+}
+
+std::unique_ptr<SalMenu> GtkInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
+{
+ EnsureInit();
+ GtkSalMenu* pSalMenu = new GtkSalMenu( bMenuBar );
+ pSalMenu->SetMenu( pVCLMenu );
+ return std::unique_ptr<SalMenu>(pSalMenu);
+}
+
+std::unique_ptr<SalMenuItem> GtkInstance::CreateMenuItem( const SalItemParams & rItemData )
+{
+ EnsureInit();
+ return std::unique_ptr<SalMenuItem>(new GtkSalMenuItem( &rItemData ));
+}
+
+SalTimer* GtkInstance::CreateSalTimer()
+{
+ EnsureInit();
+ assert( nullptr == m_pTimer );
+ if ( nullptr == m_pTimer )
+ m_pTimer = new GtkSalTimer();
+ return m_pTimer;
+}
+
+void GtkInstance::RemoveTimer ()
+{
+ EnsureInit();
+ m_pTimer = nullptr;
+}
+
+bool GtkInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
+{
+ EnsureInit();
+ return GetGtkSalData()->Yield( bWait, bHandleAllCurrentEvents );
+}
+
+bool GtkInstance::IsTimerExpired()
+{
+ EnsureInit();
+ return (m_pTimer && m_pTimer->Expired());
+}
+
+namespace
+{
+ bool DisplayHasAnyInput()
+ {
+ GdkDisplay* pDisplay = gdk_display_get_default();
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
+ {
+ bool bRet = false;
+ wl_display* pWLDisplay = gdk_wayland_display_get_wl_display(pDisplay);
+ static auto wayland_display_get_fd = reinterpret_cast<int (*) (wl_display*)>(dlsym(nullptr, "wl_display_get_fd"));
+ if (wayland_display_get_fd)
+ {
+ GPollFD aPollFD;
+ aPollFD.fd = wayland_display_get_fd(pWLDisplay);
+ aPollFD.events = G_IO_IN | G_IO_ERR | G_IO_HUP;
+ bRet = g_poll(&aPollFD, 1, 0) > 0;
+ }
+ return bRet;
+ }
+#endif
+#if defined(GDK_WINDOWING_X11)
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ GPollFD aPollFD;
+ aPollFD.fd = ConnectionNumber(gdk_x11_display_get_xdisplay(pDisplay));
+ aPollFD.events = G_IO_IN;
+ return g_poll(&aPollFD, 1, 0) > 0;
+ }
+#endif
+ return false;
+ }
+}
+
+bool GtkInstance::AnyInput( VclInputFlags nType )
+{
+ EnsureInit();
+ if( (nType & VclInputFlags::TIMER) && IsTimerExpired() )
+ return true;
+
+ // strip timer bits now
+ nType = nType & ~VclInputFlags::TIMER;
+
+ static constexpr VclInputFlags ANY_INPUT_EXCLUDING_TIMER = VCL_INPUT_ANY & ~VclInputFlags::TIMER;
+
+ const bool bCheckForAnyInput = nType == ANY_INPUT_EXCLUDING_TIMER;
+
+ bool bRet = false;
+
+ if (bCheckForAnyInput)
+ bRet = DisplayHasAnyInput();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkDisplay* pDisplay = gdk_display_get_default();
+ if (!gdk_display_has_pending(pDisplay))
+ return bRet;
+
+ if (bCheckForAnyInput)
+ return true;
+
+ std::deque<GdkEvent*> aEvents;
+ GdkEvent *pEvent = nullptr;
+ while ((pEvent = gdk_display_get_event(pDisplay)))
+ {
+ aEvents.push_back(pEvent);
+ VclInputFlags nEventType = categorizeEvent(pEvent);
+ if ( (nEventType & nType) || ( nEventType == VclInputFlags::NONE && (nType & VclInputFlags::OTHER) ) )
+ {
+ bRet = true;
+ }
+ }
+
+ while (!aEvents.empty())
+ {
+ pEvent = aEvents.front();
+ gdk_display_put_event(pDisplay, pEvent);
+ gdk_event_free(pEvent);
+ aEvents.pop_front();
+ }
+#endif
+
+ return bRet;
+}
+
+std::unique_ptr<GenPspGraphics> GtkInstance::CreatePrintGraphics()
+{
+ EnsureInit();
+ return std::make_unique<GenPspGraphics>();
+}
+
+const cairo_font_options_t* GtkInstance::GetCairoFontOptions()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ const cairo_font_options_t* pCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
+#else
+ auto pDefaultWin = ImplGetDefaultWindow();
+ assert(pDefaultWin);
+ SalFrame* pDefaultFrame = pDefaultWin->ImplGetFrame();
+ GtkSalFrame* pGtkFrame = dynamic_cast<GtkSalFrame*>(pDefaultFrame);
+ assert(pGtkFrame);
+ const cairo_font_options_t* pCairoFontOptions = pGtkFrame->get_font_options();
+#endif
+ if (!m_pLastCairoFontOptions && pCairoFontOptions)
+ m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
+ return pCairoFontOptions;
+}
+
+const cairo_font_options_t* GtkInstance::GetLastSeenCairoFontOptions() const
+{
+ return m_pLastCairoFontOptions;
+}
+
+void GtkInstance::ResetLastSeenCairoFontOptions(const cairo_font_options_t* pCairoFontOptions)
+{
+ if (m_pLastCairoFontOptions)
+ cairo_font_options_destroy(m_pLastCairoFontOptions);
+ if (pCairoFontOptions)
+ m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
+ else
+ m_pLastCairoFontOptions = nullptr;
+}
+
+
+namespace
+{
+ struct TypeEntry
+ {
+ const char* pNativeType; // string corresponding to nAtom for the case of nAtom being uninitialized
+ const char* pType; // Mime encoding on our side
+ };
+
+ const TypeEntry aConversionTab[] =
+ {
+ { "ISO10646-1", "text/plain;charset=utf-16" },
+ { "UTF8_STRING", "text/plain;charset=utf-8" },
+ { "UTF-8", "text/plain;charset=utf-8" },
+ { "text/plain;charset=UTF-8", "text/plain;charset=utf-8" },
+ // ISO encodings
+ { "ISO8859-2", "text/plain;charset=iso8859-2" },
+ { "ISO8859-3", "text/plain;charset=iso8859-3" },
+ { "ISO8859-4", "text/plain;charset=iso8859-4" },
+ { "ISO8859-5", "text/plain;charset=iso8859-5" },
+ { "ISO8859-6", "text/plain;charset=iso8859-6" },
+ { "ISO8859-7", "text/plain;charset=iso8859-7" },
+ { "ISO8859-8", "text/plain;charset=iso8859-8" },
+ { "ISO8859-9", "text/plain;charset=iso8859-9" },
+ { "ISO8859-10", "text/plain;charset=iso8859-10" },
+ { "ISO8859-13", "text/plain;charset=iso8859-13" },
+ { "ISO8859-14", "text/plain;charset=iso8859-14" },
+ { "ISO8859-15", "text/plain;charset=iso8859-15" },
+ // asian encodings
+ { "JISX0201.1976-0", "text/plain;charset=jisx0201.1976-0" },
+ { "JISX0208.1983-0", "text/plain;charset=jisx0208.1983-0" },
+ { "JISX0208.1990-0", "text/plain;charset=jisx0208.1990-0" },
+ { "JISX0212.1990-0", "text/plain;charset=jisx0212.1990-0" },
+ { "GB2312.1980-0", "text/plain;charset=gb2312.1980-0" },
+ { "KSC5601.1992-0", "text/plain;charset=ksc5601.1992-0" },
+ // eastern european encodings
+ { "KOI8-R", "text/plain;charset=koi8-r" },
+ { "KOI8-U", "text/plain;charset=koi8-u" },
+ // String (== iso8859-1)
+ { "STRING", "text/plain;charset=iso8859-1" },
+ // special for compound text
+ { "COMPOUND_TEXT", "text/plain;charset=compound_text" },
+
+ // PIXMAP
+ { "PIXMAP", "image/bmp" }
+ };
+
+ class DataFlavorEq
+ {
+ private:
+ const css::datatransfer::DataFlavor& m_rData;
+ public:
+ explicit DataFlavorEq(const css::datatransfer::DataFlavor& rData) : m_rData(rData) {}
+ bool operator() (const css::datatransfer::DataFlavor& rData) const
+ {
+ return rData.MimeType == m_rData.MimeType &&
+ rData.DataType == m_rData.DataType;
+ }
+ };
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+std::vector<css::datatransfer::DataFlavor> GtkTransferable::getTransferDataFlavorsAsVector(const char * const *targets, gint n_targets)
+#else
+std::vector<css::datatransfer::DataFlavor> GtkTransferable::getTransferDataFlavorsAsVector(GdkAtom *targets, gint n_targets)
+#endif
+{
+ std::vector<css::datatransfer::DataFlavor> aVector;
+
+ bool bHaveText = false, bHaveUTF16 = false;
+
+ for (gint i = 0; i < n_targets; ++i)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ const gchar* pName = targets[i];
+#else
+ gchar* pName = gdk_atom_name(targets[i]);
+#endif
+ const char* pFinalName = pName;
+ css::datatransfer::DataFlavor aFlavor;
+
+ // omit text/plain;charset=unicode since it is not well defined
+ if (rtl_str_compare(pName, "text/plain;charset=unicode") == 0)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_free(pName);
+#endif
+ continue;
+ }
+
+ for (size_t j = 0; j < SAL_N_ELEMENTS(aConversionTab); ++j)
+ {
+ if (rtl_str_compare(pName, aConversionTab[j].pNativeType) == 0)
+ {
+ pFinalName = aConversionTab[j].pType;
+ break;
+ }
+ }
+
+ // There are more non-MIME-types reported that are not translated by
+ // aConversionTab, like "SAVE_TARGETS", "INTEGER", "ATOM"; just filter
+ // them out for now before they confuse this code's clients:
+ if (rtl_str_indexOfChar(pFinalName, '/') == -1)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_free(pName);
+#endif
+ continue;
+ }
+
+ aFlavor.MimeType = OUString(pFinalName,
+ strlen(pFinalName),
+ RTL_TEXTENCODING_UTF8);
+
+ m_aMimeTypeToGtkType[aFlavor.MimeType] = targets[i];
+
+ aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ sal_Int32 nIndex(0);
+ if (o3tl::getToken(aFlavor.MimeType, 0, ';', nIndex) == u"text/plain")
+ {
+ bHaveText = true;
+ std::u16string_view aToken(o3tl::getToken(aFlavor.MimeType, 0, ';', nIndex));
+ if (aToken == u"charset=utf-16")
+ {
+ bHaveUTF16 = true;
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ }
+ }
+ aVector.push_back(aFlavor);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_free(pName);
+#endif
+ }
+
+ //If we have text, but no UTF-16 format which is basically the only
+ //text-format LibreOffice supports for cnp then claim we do and we
+ //will convert on demand
+ if (bHaveText && !bHaveUTF16)
+ {
+ css::datatransfer::DataFlavor aFlavor;
+ aFlavor.MimeType = "text/plain;charset=utf-16";
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ aVector.push_back(aFlavor);
+ }
+
+ return aVector;
+}
+
+css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL GtkTransferable::getTransferDataFlavors()
+{
+ return comphelper::containerToSequence(getTransferDataFlavorsAsVector());
+}
+
+sal_Bool SAL_CALL GtkTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
+{
+ const std::vector<css::datatransfer::DataFlavor> aAll =
+ getTransferDataFlavorsAsVector();
+
+ return std::any_of(aAll.begin(), aAll.end(), DataFlavorEq(rFlavor));
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void read_transfer_result::read_block_async_completed(GObject* source, GAsyncResult* res, gpointer user_data)
+{
+ GInputStream* stream = G_INPUT_STREAM(source);
+ read_transfer_result* pRes = static_cast<read_transfer_result*>(user_data);
+
+ gsize bytes_read = g_input_stream_read_finish(stream, res, nullptr);
+
+ bool bFinished = bytes_read == 0;
+
+ if (bFinished)
+ {
+ g_object_unref(stream);
+ pRes->aVector.resize(pRes->nRead);
+ pRes->bDone = true;
+ g_main_context_wakeup(nullptr);
+ return;
+ }
+
+ pRes->nRead += bytes_read;
+
+ pRes->aVector.resize(pRes->nRead + read_transfer_result::BlockSize);
+
+ g_input_stream_read_async(stream,
+ pRes->aVector.data() + pRes->nRead,
+ read_transfer_result::BlockSize,
+ G_PRIORITY_DEFAULT,
+ nullptr,
+ read_block_async_completed,
+ user_data);
+}
+
+OUString read_transfer_result::get_as_string() const
+{
+ const char* pStr = reinterpret_cast<const char*>(aVector.data());
+ return OUString(pStr, aVector.size(), RTL_TEXTENCODING_UTF8).replaceAll("\r\n", "\n");
+}
+
+css::uno::Sequence<sal_Int8> read_transfer_result::get_as_sequence() const
+{
+ return css::uno::Sequence<sal_Int8>(aVector.data(), aVector.size());
+}
+#endif
+
+namespace {
+
+GdkClipboard* clipboard_get(SelectionType eSelection)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (eSelection == SELECTION_CLIPBOARD)
+ return gdk_display_get_clipboard(gdk_display_get_default());
+ return gdk_display_get_primary_clipboard(gdk_display_get_default());
+#else
+ return gtk_clipboard_get(eSelection == SELECTION_CLIPBOARD ? GDK_SELECTION_CLIPBOARD : GDK_SELECTION_PRIMARY);
+#endif
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+
+void read_clipboard_async_completed(GObject* source, GAsyncResult* res, gpointer user_data)
+{
+ GdkClipboard* clipboard = GDK_CLIPBOARD(source);
+ read_transfer_result* pRes = static_cast<read_transfer_result*>(user_data);
+
+ GInputStream* pResult = gdk_clipboard_read_finish(clipboard, res, nullptr, nullptr);
+
+ if (!pResult)
+ {
+ pRes->bDone = true;
+ g_main_context_wakeup(nullptr);
+ return;
+ }
+
+ pRes->aVector.resize(read_transfer_result::BlockSize);
+
+ g_input_stream_read_async(pResult,
+ pRes->aVector.data(),
+ pRes->aVector.size(),
+ G_PRIORITY_DEFAULT,
+ nullptr,
+ read_transfer_result::read_block_async_completed,
+ user_data);
+}
+
+#endif
+
+class GtkClipboardTransferable : public GtkTransferable
+{
+private:
+ SelectionType m_eSelection;
+
+public:
+
+ explicit GtkClipboardTransferable(SelectionType eSelection)
+ : m_eSelection(eSelection)
+ {
+ }
+
+ /*
+ * XTransferable
+ */
+
+ virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override
+ {
+ css::uno::Any aRet;
+
+ css::datatransfer::DataFlavor aFlavor(rFlavor);
+ if (aFlavor.MimeType == "text/plain;charset=utf-16")
+ aFlavor.MimeType = "text/plain;charset=utf-8";
+
+ GdkClipboard* clipboard = clipboard_get(m_eSelection);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (aFlavor.MimeType == "text/plain;charset=utf-8")
+ {
+ gchar *pText = gtk_clipboard_wait_for_text(clipboard);
+ OUString aStr(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pText);
+ aRet <<= aStr.replaceAll("\r\n", "\n");
+ return aRet;
+ }
+#endif
+
+ auto it = m_aMimeTypeToGtkType.find(aFlavor.MimeType);
+ if (it == m_aMimeTypeToGtkType.end())
+ return css::uno::Any();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard,
+ it->second);
+ if (!data)
+ {
+ return css::uno::Any();
+ }
+ gint length;
+ const guchar *rawdata = gtk_selection_data_get_data_with_length(data,
+ &length);
+ Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
+ gtk_selection_data_free(data);
+ aRet <<= aSeq;
+#else
+ SalInstance* pInstance = GetSalInstance();
+ read_transfer_result aRes;
+ const char *mime_types[] = { it->second.getStr(), nullptr };
+
+ gdk_clipboard_read_async(clipboard,
+ mime_types,
+ G_PRIORITY_DEFAULT,
+ nullptr,
+ read_clipboard_async_completed,
+ &aRes);
+
+ while (!aRes.bDone)
+ pInstance->DoYield(true, false);
+
+ if (aFlavor.MimeType == "text/plain;charset=utf-8")
+ aRet <<= aRes.get_as_string();
+ else
+ aRet <<= aRes.get_as_sequence();
+#endif
+ return aRet;
+ }
+
+ std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector()
+ override
+ {
+ std::vector<css::datatransfer::DataFlavor> aVector;
+
+ GdkClipboard* clipboard = clipboard_get(m_eSelection);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GdkContentFormats* pFormats = gdk_clipboard_get_formats(clipboard);
+ gsize n_targets;
+ const char * const *targets = gdk_content_formats_get_mime_types(pFormats, &n_targets);
+ aVector = GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
+#else
+ GdkAtom *targets;
+ gint n_targets;
+ if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
+ {
+ aVector = GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
+ g_free(targets);
+ }
+#endif
+
+ return aVector;
+ }
+};
+
+class VclGtkClipboard :
+ public cppu::WeakComponentImplHelper<
+ datatransfer::clipboard::XSystemClipboard,
+ datatransfer::clipboard::XFlushableClipboard,
+ XServiceInfo>
+{
+ SelectionType m_eSelection;
+ osl::Mutex m_aMutex;
+ gulong m_nOwnerChangedSignalId;
+ ImplSVEvent* m_pSetClipboardEvent;
+ Reference<css::datatransfer::XTransferable> m_aContents;
+ Reference<css::datatransfer::clipboard::XClipboardOwner> m_aOwner;
+ std::vector< Reference<css::datatransfer::clipboard::XClipboardListener> > m_aListeners;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ std::vector<OString> m_aGtkTargets;
+ TransferableContent* m_pClipboardContent;
+#else
+ std::vector<GtkTargetEntry> m_aGtkTargets;
+#endif
+ VclToGtkHelper m_aConversionHelper;
+
+ DECL_LINK(AsyncSetGtkClipboard, void*, void);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ DECL_LINK(DetachClipboard, void*, void);
+#endif
+
+public:
+
+ explicit VclGtkClipboard(SelectionType eSelection);
+ virtual ~VclGtkClipboard() override;
+
+ /*
+ * XServiceInfo
+ */
+
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ /*
+ * 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;
+
+ /*
+ * XFlushableClipboard
+ */
+ virtual void SAL_CALL flushClipboard() 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;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ void ClipboardGet(GtkSelectionData *selection_data, guint info);
+#endif
+ void OwnerPossiblyChanged(GdkClipboard *clipboard);
+ void ClipboardClear();
+ void SetGtkClipboard();
+ void SyncGtkClipboard();
+};
+
+}
+
+OUString VclGtkClipboard::getImplementationName()
+{
+ return "com.sun.star.datatransfer.VclGtkClipboard";
+}
+
+Sequence< OUString > VclGtkClipboard::getSupportedServiceNames()
+{
+ Sequence<OUString> aRet { "com.sun.star.datatransfer.clipboard.SystemClipboard" };
+ return aRet;
+}
+
+sal_Bool VclGtkClipboard::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Reference< css::datatransfer::XTransferable > VclGtkClipboard::getContents()
+{
+ if (!m_aContents.is())
+ {
+ //tdf#93887 This is the system clipboard/selection. We fetch it when we are not
+ //the owner of the clipboard and have not already fetched it.
+ m_aContents = new GtkClipboardTransferable(m_eSelection);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (m_pClipboardContent)
+ transerable_content_set_transferable(m_pClipboardContent, m_aContents.get());
+#endif
+ }
+ return m_aContents;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void VclGtkClipboard::ClipboardGet(GtkSelectionData *selection_data, guint info)
+{
+ if (!m_aContents.is())
+ return;
+ // tdf#129809 take a reference in case m_aContents is replaced during this
+ // call
+ Reference<datatransfer::XTransferable> xCurrentContents(m_aContents);
+ m_aConversionHelper.setSelectionData(xCurrentContents, selection_data, info);
+}
+
+namespace
+{
+ const OString& getPID()
+ {
+ static OString sPID;
+ if (!sPID.getLength())
+ {
+ oslProcessIdentifier aProcessId = 0;
+ oslProcessInfo info;
+ info.Size = sizeof (oslProcessInfo);
+ if (osl_getProcessInfo(nullptr, osl_Process_IDENTIFIER, &info) == osl_Process_E_None)
+ aProcessId = info.Ident;
+ sPID = OString::number(aProcessId);
+ }
+ return sPID;
+ }
+
+ void ClipboardGetFunc(GdkClipboard* /*clipboard*/, GtkSelectionData *selection_data,
+ guint info,
+ gpointer user_data_or_owner)
+ {
+ VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
+ pThis->ClipboardGet(selection_data, info);
+ }
+
+ void ClipboardClearFunc(GdkClipboard* /*clipboard*/, gpointer user_data_or_owner)
+ {
+ VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
+ pThis->ClipboardClear();
+ }
+}
+#endif
+
+namespace
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ void handle_owner_change(GdkClipboard *clipboard, gpointer user_data)
+ {
+ VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data);
+ pThis->OwnerPossiblyChanged(clipboard);
+ }
+#else
+ void handle_owner_change(GdkClipboard *clipboard, GdkEvent* /*event*/, gpointer user_data)
+ {
+ VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data);
+ pThis->OwnerPossiblyChanged(clipboard);
+ }
+#endif
+}
+
+void VclGtkClipboard::OwnerPossiblyChanged(GdkClipboard* clipboard)
+{
+ SyncGtkClipboard(); // tdf#138183 do any pending SetGtkClipboard calls
+ if (!m_aContents.is())
+ return;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ bool bSelf = gdk_clipboard_is_local(clipboard);
+#else
+ //if gdk_display_supports_selection_notification is not supported, e.g. like
+ //right now under wayland, then you only get owner-changed notifications at
+ //opportune times when the selection might have changed. So here
+ //we see if the selection supports a dummy selection type identifying
+ //our pid, in which case it's us.
+ bool bSelf = false;
+
+ //disconnect and reconnect after gtk_clipboard_wait_for_targets to
+ //avoid possible recursion
+ g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
+
+ OString sTunnel = "application/x-libreoffice-internal-id-" + getPID();
+ GdkAtom *targets;
+ gint n_targets;
+ if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
+ {
+ for (gint i = 0; i < n_targets && !bSelf; ++i)
+ {
+ gchar* pName = gdk_atom_name(targets[i]);
+ if (strcmp(pName, sTunnel.getStr()) == 0)
+ {
+ bSelf = true;
+ }
+ g_free(pName);
+ }
+
+ g_free(targets);
+ }
+
+ m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
+ G_CALLBACK(handle_owner_change), this);
+#endif
+
+ if (!bSelf)
+ {
+ //null out m_aContents to return control to the system-one which
+ //will be retrieved if getContents is called again
+ setContents(Reference<css::datatransfer::XTransferable>(),
+ Reference<css::datatransfer::clipboard::XClipboardOwner>());
+ }
+}
+
+void VclGtkClipboard::ClipboardClear()
+{
+ if (m_pSetClipboardEvent)
+ {
+ Application::RemoveUserEvent(m_pSetClipboardEvent);
+ m_pSetClipboardEvent = nullptr;
+ }
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ for (auto &a : m_aGtkTargets)
+ g_free(a.target);
+#endif
+ m_aGtkTargets.clear();
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+IMPL_LINK_NOARG(VclGtkClipboard, DetachClipboard, void*, void)
+{
+ ClipboardClear();
+}
+
+OString VclToGtkHelper::makeGtkTargetEntry(const css::datatransfer::DataFlavor& rFlavor)
+{
+ OString aEntry = OUStringToOString(rFlavor.MimeType, RTL_TEXTENCODING_UTF8);
+ auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
+ DataFlavorEq(rFlavor));
+ if (it == aInfoToFlavor.end())
+ aInfoToFlavor.push_back(rFlavor);
+ return aEntry;
+}
+#else
+GtkTargetEntry VclToGtkHelper::makeGtkTargetEntry(const css::datatransfer::DataFlavor& rFlavor)
+{
+ GtkTargetEntry aEntry;
+ aEntry.target =
+ g_strdup(OUStringToOString(rFlavor.MimeType, RTL_TEXTENCODING_UTF8).getStr());
+ aEntry.flags = 0;
+ auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
+ DataFlavorEq(rFlavor));
+ if (it != aInfoToFlavor.end())
+ aEntry.info = std::distance(aInfoToFlavor.begin(), it);
+ else
+ {
+ aEntry.info = aInfoToFlavor.size();
+ aInfoToFlavor.push_back(rFlavor);
+ }
+ return aEntry;
+}
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+
+namespace
+{
+ void write_mime_type_done(GObject* pStream, GAsyncResult* pResult, gpointer pTaskPtr)
+ {
+ GTask* pTask = static_cast<GTask*>(pTaskPtr);
+
+ GError* pError = nullptr;
+ if (!g_output_stream_write_all_finish(G_OUTPUT_STREAM(pStream),
+ pResult, nullptr, &pError))
+ {
+ g_task_return_error(pTask, pError);
+ }
+ else
+ {
+ g_task_return_boolean(pTask, true);
+ }
+
+ g_object_unref(pTask);
+ }
+
+ class MimeTypeEq
+ {
+ private:
+ const OUString& m_rMimeType;
+ public:
+ explicit MimeTypeEq(const OUString& rMimeType) : m_rMimeType(rMimeType) {}
+ bool operator() (const css::datatransfer::DataFlavor& rData) const
+ {
+ return rData.MimeType == m_rMimeType;
+ }
+ };
+}
+
+void VclToGtkHelper::setSelectionData(const Reference<css::datatransfer::XTransferable> &rTrans,
+ GdkContentProvider* provider,
+ const char* mime_type,
+ GOutputStream* stream,
+ int io_priority,
+ GCancellable* cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task = g_task_new(provider, cancellable, callback, user_data);
+ g_task_set_priority(task, io_priority);
+
+ OUString sMimeType(mime_type, strlen(mime_type), RTL_TEXTENCODING_UTF8);
+
+ auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
+ MimeTypeEq(sMimeType));
+ if (it == aInfoToFlavor.end())
+ {
+ SAL_WARN( "vcl.gtk", "unknown mime-type request from clipboard");
+ g_task_return_new_error(task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "unknown mime-type “%s†request from clipboard", mime_type);
+ g_object_unref(task);
+ return;
+ }
+
+ css::datatransfer::DataFlavor aFlavor(*it);
+ if (aFlavor.MimeType == "UTF8_STRING" || aFlavor.MimeType == "STRING")
+ aFlavor.MimeType = "text/plain;charset=utf-8";
+
+ Sequence<sal_Int8> aData;
+ Any aValue;
+
+ try
+ {
+ aValue = rTrans->getTransferData(aFlavor);
+ }
+ catch (...)
+ {
+ }
+
+ if (aValue.getValueTypeClass() == TypeClass_STRING)
+ {
+ OUString aString;
+ aValue >>= aString;
+ aData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aString.getStr()), aString.getLength() * sizeof( sal_Unicode ) );
+ }
+ else if (aValue.getValueType() == cppu::UnoType<Sequence< sal_Int8 >>::get())
+ {
+ aValue >>= aData;
+ }
+ else if (aFlavor.MimeType == "text/plain;charset=utf-8")
+ {
+ //didn't have utf-8, try utf-16 and convert
+ aFlavor.MimeType = "text/plain;charset=utf-16";
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ try
+ {
+ aValue = rTrans->getTransferData(aFlavor);
+ }
+ catch (...)
+ {
+ }
+ OUString aString;
+ aValue >>= aString;
+ OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
+
+ g_output_stream_write_all_async(stream, aUTF8String.getStr(), aUTF8String.getLength(),
+ io_priority, cancellable, write_mime_type_done, task);
+ return;
+ }
+
+ g_output_stream_write_all_async(stream, aData.getArray(), aData.getLength(),
+ io_priority, cancellable, write_mime_type_done, task);
+}
+#else
+void VclToGtkHelper::setSelectionData(const Reference<css::datatransfer::XTransferable> &rTrans,
+ GtkSelectionData *selection_data, guint info)
+{
+ GdkAtom type(gdk_atom_intern(OUStringToOString(aInfoToFlavor[info].MimeType,
+ RTL_TEXTENCODING_UTF8).getStr(),
+ false));
+
+ css::datatransfer::DataFlavor aFlavor(aInfoToFlavor[info]);
+ if (aFlavor.MimeType == "UTF8_STRING" || aFlavor.MimeType == "STRING")
+ aFlavor.MimeType = "text/plain;charset=utf-8";
+
+ Sequence<sal_Int8> aData;
+ Any aValue;
+
+ try
+ {
+ aValue = rTrans->getTransferData(aFlavor);
+ }
+ catch (...)
+ {
+ }
+
+ if (aValue.getValueTypeClass() == TypeClass_STRING)
+ {
+ OUString aString;
+ aValue >>= aString;
+ aData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aString.getStr()), aString.getLength() * sizeof( sal_Unicode ) );
+ }
+ else if (aValue.getValueType() == cppu::UnoType<Sequence< sal_Int8 >>::get())
+ {
+ aValue >>= aData;
+ }
+ else if (aFlavor.MimeType == "text/plain;charset=utf-8")
+ {
+ //didn't have utf-8, try utf-16 and convert
+ aFlavor.MimeType = "text/plain;charset=utf-16";
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ try
+ {
+ aValue = rTrans->getTransferData(aFlavor);
+ }
+ catch (...)
+ {
+ }
+ OUString aString;
+ aValue >>= aString;
+ OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
+ gtk_selection_data_set(selection_data, type, 8,
+ reinterpret_cast<const guchar *>(aUTF8String.getStr()),
+ aUTF8String.getLength());
+ return;
+ }
+
+ gtk_selection_data_set(selection_data, type, 8,
+ reinterpret_cast<const guchar *>(aData.getArray()),
+ aData.getLength());
+}
+#endif
+
+VclGtkClipboard::VclGtkClipboard(SelectionType eSelection)
+ : cppu::WeakComponentImplHelper<datatransfer::clipboard::XSystemClipboard,
+ datatransfer::clipboard::XFlushableClipboard, XServiceInfo>
+ (m_aMutex)
+ , m_eSelection(eSelection)
+ , m_pSetClipboardEvent(nullptr)
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_pClipboardContent(nullptr)
+#endif
+{
+ GdkClipboard* clipboard = clipboard_get(m_eSelection);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_nOwnerChangedSignalId = g_signal_connect(clipboard, "changed",
+ G_CALLBACK(handle_owner_change), this);
+#else
+ m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
+ G_CALLBACK(handle_owner_change), this);
+#endif
+}
+
+void VclGtkClipboard::flushClipboard()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ SolarMutexGuard aGuard;
+
+ if (m_eSelection != SELECTION_CLIPBOARD)
+ return;
+
+ GdkClipboard* clipboard = clipboard_get(m_eSelection);
+ gtk_clipboard_store(clipboard);
+#endif
+}
+
+VclGtkClipboard::~VclGtkClipboard()
+{
+ GdkClipboard* clipboard = clipboard_get(m_eSelection);
+ g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
+ if (!m_aGtkTargets.empty())
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gdk_clipboard_set_content(clipboard, nullptr);
+ m_pClipboardContent = nullptr;
+#else
+ gtk_clipboard_clear(clipboard);
+#endif
+ ClipboardClear();
+ }
+ assert(!m_pSetClipboardEvent);
+ assert(m_aGtkTargets.empty());
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+std::vector<OString> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
+#else
+std::vector<GtkTargetEntry> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
+#endif
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ std::vector<OString> aGtkTargets;
+#else
+ std::vector<GtkTargetEntry> aGtkTargets;
+#endif
+
+ bool bHaveText(false), bHaveUTF8(false);
+ for (const css::datatransfer::DataFlavor& rFlavor : rFormats)
+ {
+ sal_Int32 nIndex(0);
+ if (o3tl::getToken(rFlavor.MimeType, 0, ';', nIndex) == u"text/plain")
+ {
+ bHaveText = true;
+ std::u16string_view aToken(o3tl::getToken(rFlavor.MimeType, 0, ';', nIndex));
+ if (aToken == u"charset=utf-8")
+ {
+ bHaveUTF8 = true;
+ }
+ }
+ aGtkTargets.push_back(makeGtkTargetEntry(rFlavor));
+ }
+
+ if (bHaveText)
+ {
+ css::datatransfer::DataFlavor aFlavor;
+ aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+ if (!bHaveUTF8)
+ {
+ aFlavor.MimeType = "text/plain;charset=utf-8";
+ aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
+ }
+ aFlavor.MimeType = "UTF8_STRING";
+ aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
+ aFlavor.MimeType = "STRING";
+ aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
+ }
+
+ return aGtkTargets;
+}
+
+IMPL_LINK_NOARG(VclGtkClipboard, AsyncSetGtkClipboard, void*, void)
+{
+ osl::Guard aGuard( m_aMutex );
+ m_pSetClipboardEvent = nullptr;
+ SetGtkClipboard();
+}
+
+void VclGtkClipboard::SyncGtkClipboard()
+{
+ osl::Guard aGuard(m_aMutex);
+ if (m_pSetClipboardEvent)
+ {
+ Application::RemoveUserEvent(m_pSetClipboardEvent);
+ m_pSetClipboardEvent = nullptr;
+ SetGtkClipboard();
+ }
+}
+
+void VclGtkClipboard::SetGtkClipboard()
+{
+ GdkClipboard* clipboard = clipboard_get(m_eSelection);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_pClipboardContent = TRANSFERABLE_CONTENT(transerable_content_new(&m_aConversionHelper, m_aContents.get()));
+ transerable_content_set_detach_clipboard_link(m_pClipboardContent, LINK(this, VclGtkClipboard, DetachClipboard));
+ gdk_clipboard_set_content(clipboard, GDK_CONTENT_PROVIDER(m_pClipboardContent));
+#else
+ gtk_clipboard_set_with_data(clipboard, m_aGtkTargets.data(), m_aGtkTargets.size(),
+ ClipboardGetFunc, ClipboardClearFunc, this);
+ gtk_clipboard_set_can_store(clipboard, m_aGtkTargets.data(), m_aGtkTargets.size());
+#endif
+}
+
+void VclGtkClipboard::setContents(
+ const Reference< css::datatransfer::XTransferable >& xTrans,
+ const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner )
+{
+ css::uno::Sequence<css::datatransfer::DataFlavor> aFormats;
+ if (xTrans.is())
+ {
+ aFormats = xTrans->getTransferDataFlavors();
+ }
+
+ osl::ClearableMutexGuard aGuard( m_aMutex );
+ Reference< datatransfer::clipboard::XClipboardOwner > xOldOwner( m_aOwner );
+ Reference< datatransfer::XTransferable > xOldContents( m_aContents );
+ m_aContents = xTrans;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (m_pClipboardContent)
+ transerable_content_set_transferable(m_pClipboardContent, m_aContents.get());
+#endif
+ m_aOwner = xClipboardOwner;
+
+ std::vector< Reference< datatransfer::clipboard::XClipboardListener > > aListeners( m_aListeners );
+ datatransfer::clipboard::ClipboardEvent aEv;
+
+ GdkClipboard* clipboard = clipboard_get(m_eSelection);
+ if (!m_aGtkTargets.empty())
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gdk_clipboard_set_content(clipboard, nullptr);
+ m_pClipboardContent = nullptr;
+#else
+ gtk_clipboard_clear(clipboard);
+#endif
+ ClipboardClear();
+ }
+ assert(m_aGtkTargets.empty());
+ if (m_aContents.is())
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ std::vector<OString> aGtkTargets(m_aConversionHelper.FormatsToGtk(aFormats));
+#else
+ std::vector<GtkTargetEntry> aGtkTargets(m_aConversionHelper.FormatsToGtk(aFormats));
+#endif
+ if (!aGtkTargets.empty())
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkTargetEntry aEntry;
+ OString sTunnel = "application/x-libreoffice-internal-id-" + getPID();
+ aEntry.target = g_strdup(sTunnel.getStr());
+ aEntry.flags = 0;
+ aEntry.info = 0;
+ aGtkTargets.push_back(aEntry);
+#endif
+ m_aGtkTargets = aGtkTargets;
+
+ if (!m_pSetClipboardEvent)
+ m_pSetClipboardEvent = Application::PostUserEvent(LINK(this, VclGtkClipboard, AsyncSetGtkClipboard));
+ }
+ }
+
+ aEv.Contents = getContents();
+
+ aGuard.clear();
+
+ if (xOldOwner.is() && xOldOwner != xClipboardOwner)
+ xOldOwner->lostOwnership( this, xOldContents );
+ for (auto const& listener : aListeners)
+ {
+ listener->changedContents( aEv );
+ }
+}
+
+OUString VclGtkClipboard::getName()
+{
+ return (m_eSelection == SELECTION_CLIPBOARD) ? OUString("CLIPBOARD") : OUString("PRIMARY");
+}
+
+sal_Int8 VclGtkClipboard::getRenderingCapabilities()
+{
+ return 0;
+}
+
+void VclGtkClipboard::addClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
+{
+ osl::Guard aGuard( m_aMutex );
+
+ m_aListeners.push_back( listener );
+}
+
+void VclGtkClipboard::removeClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
+{
+ osl::Guard aGuard( m_aMutex );
+
+ std::erase(m_aListeners, listener);
+}
+
+Reference< XInterface > GtkInstance::CreateClipboard(const Sequence< Any >& arguments)
+{
+ if ( IsRunningUnitTest() )
+ return SalInstance::CreateClipboard( arguments );
+
+ OUString sel;
+ if (!arguments.hasElements()) {
+ sel = "CLIPBOARD";
+ } else if (arguments.getLength() != 1 || !(arguments[0] >>= sel)) {
+ throw css::lang::IllegalArgumentException(
+ "bad GtkInstance::CreateClipboard arguments",
+ css::uno::Reference<css::uno::XInterface>(), -1);
+ }
+
+ SelectionType eSelection = (sel == "CLIPBOARD") ? SELECTION_CLIPBOARD : SELECTION_PRIMARY;
+
+ if (m_aClipboards[eSelection].is())
+ return m_aClipboards[eSelection];
+
+ Reference<XInterface> xClipboard(getXWeak(new VclGtkClipboard(eSelection)));
+ m_aClipboards[eSelection] = xClipboard;
+ return xClipboard;
+}
+
+GtkInstDropTarget::GtkInstDropTarget()
+ : WeakComponentImplHelper(m_aMutex)
+ , m_pFrame(nullptr)
+ , m_pFormatConversionRequest(nullptr)
+ , m_bActive(false)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_bInDrag(false)
+#endif
+ , m_nDefaultActions(0)
+{
+}
+
+OUString SAL_CALL GtkInstDropTarget::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.VclGtkDropTarget";
+}
+
+sal_Bool SAL_CALL GtkInstDropTarget::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL GtkInstDropTarget::getSupportedServiceNames()
+{
+ Sequence<OUString> aRet { "com.sun.star.datatransfer.dnd.GtkDropTarget" };
+ return aRet;
+}
+
+GtkInstDropTarget::~GtkInstDropTarget()
+{
+ if (m_pFrame)
+ m_pFrame->deregisterDropTarget(this);
+}
+
+void GtkInstDropTarget::deinitialize()
+{
+ m_pFrame = nullptr;
+ m_bActive = false;
+}
+
+void GtkInstDropTarget::initialize(const Sequence<Any>& rArguments)
+{
+ if (rArguments.getLength() < 2)
+ {
+ throw RuntimeException("DropTarget::initialize: Cannot install window event handler",
+ getXWeak());
+ }
+
+ sal_IntPtr nFrame = 0;
+ rArguments.getConstArray()[1] >>= nFrame;
+
+ if (!nFrame)
+ {
+ throw RuntimeException("DropTarget::initialize: missing SalFrame",
+ getXWeak());
+ }
+
+ m_pFrame = reinterpret_cast<GtkSalFrame*>(nFrame);
+ m_pFrame->registerDropTarget(this);
+ m_bActive = true;
+}
+
+void GtkInstDropTarget::addDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
+
+ m_aListeners.push_back( xListener );
+}
+
+void GtkInstDropTarget::removeDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
+
+ std::erase(m_aListeners, xListener);
+}
+
+void GtkInstDropTarget::fire_drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde)
+{
+ osl::ClearableGuard<osl::Mutex> aGuard( m_aMutex );
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ {
+ listener->drop( dtde );
+ }
+}
+
+void GtkInstDropTarget::fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde)
+{
+ osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ {
+ listener->dragEnter( dtde );
+ }
+}
+
+void GtkInstDropTarget::fire_dragOver(const css::datatransfer::dnd::DropTargetDragEvent& dtde)
+{
+ osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ {
+ listener->dragOver( dtde );
+ }
+}
+
+void GtkInstDropTarget::fire_dragExit(const css::datatransfer::dnd::DropTargetEvent& dte)
+{
+ osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ {
+ listener->dragExit( dte );
+ }
+}
+
+sal_Bool GtkInstDropTarget::isActive()
+{
+ return m_bActive;
+}
+
+void GtkInstDropTarget::setActive(sal_Bool bActive)
+{
+ m_bActive = bActive;
+}
+
+sal_Int8 GtkInstDropTarget::getDefaultActions()
+{
+ return m_nDefaultActions;
+}
+
+void GtkInstDropTarget::setDefaultActions(sal_Int8 nDefaultActions)
+{
+ m_nDefaultActions = nDefaultActions;
+}
+
+Reference<XInterface> GtkInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv)
+{
+ return vcl::X11DnDHelper(new GtkInstDropTarget(), pSysEnv->aShellWindow);
+}
+
+GtkInstDragSource::~GtkInstDragSource()
+{
+ if (m_pFrame)
+ m_pFrame->deregisterDragSource(this);
+
+ if (GtkInstDragSource::g_ActiveDragSource == this)
+ {
+ SAL_WARN( "vcl.gtk", "dragEnd should have been called on GtkInstDragSource before dtor");
+ GtkInstDragSource::g_ActiveDragSource = nullptr;
+ }
+}
+
+void GtkInstDragSource::deinitialize()
+{
+ m_pFrame = nullptr;
+}
+
+sal_Bool GtkInstDragSource::isDragImageSupported()
+{
+ return true;
+}
+
+sal_Int32 GtkInstDragSource::getDefaultCursor( sal_Int8 )
+{
+ return 0;
+}
+
+void GtkInstDragSource::initialize(const css::uno::Sequence<css::uno::Any >& rArguments)
+{
+ if (rArguments.getLength() < 2)
+ {
+ throw RuntimeException("DragSource::initialize: Cannot install window event handler",
+ getXWeak());
+ }
+
+ sal_IntPtr nFrame = 0;
+ rArguments.getConstArray()[1] >>= nFrame;
+
+ if (!nFrame)
+ {
+ throw RuntimeException("DragSource::initialize: missing SalFrame",
+ getXWeak());
+ }
+
+ m_pFrame = reinterpret_cast<GtkSalFrame*>(nFrame);
+ m_pFrame->registerDragSource(this);
+}
+
+OUString SAL_CALL GtkInstDragSource::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.VclGtkDragSource";
+}
+
+sal_Bool SAL_CALL GtkInstDragSource::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL GtkInstDragSource::getSupportedServiceNames()
+{
+ Sequence<OUString> aRet { "com.sun.star.datatransfer.dnd.GtkDragSource" };
+ return aRet;
+}
+
+Reference<XInterface> GtkInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv)
+{
+ return vcl::X11DnDHelper(new GtkInstDragSource(), pSysEnv->aShellWindow);
+}
+
+namespace {
+
+class GtkOpenGLContext : public OpenGLContext
+{
+ GLWindow m_aGLWin;
+ GtkWidget *m_pGLArea;
+ GdkGLContext *m_pContext;
+ gulong m_nDestroySignalId;
+ gulong m_nRenderSignalId;
+ guint m_nAreaFrameBuffer;
+ guint m_nFrameBuffer;
+ guint m_nRenderBuffer;
+ guint m_nDepthBuffer;
+ guint m_nFrameScratchBuffer;
+ guint m_nRenderScratchBuffer;
+ guint m_nDepthScratchBuffer;
+
+public:
+ GtkOpenGLContext()
+ : m_pGLArea(nullptr)
+ , m_pContext(nullptr)
+ , m_nDestroySignalId(0)
+ , m_nRenderSignalId(0)
+ , m_nAreaFrameBuffer(0)
+ , m_nFrameBuffer(0)
+ , m_nRenderBuffer(0)
+ , m_nDepthBuffer(0)
+ , m_nFrameScratchBuffer(0)
+ , m_nRenderScratchBuffer(0)
+ , m_nDepthScratchBuffer(0)
+ {
+ }
+
+ virtual void initWindow() override
+ {
+ if( !m_pChildWindow )
+ {
+ SystemWindowData winData = generateWinData(mpWindow, mbRequestLegacyContext);
+ m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
+ }
+
+ if (m_pChildWindow)
+ {
+ InitChildWindow(m_pChildWindow.get());
+ }
+ }
+
+private:
+ virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
+ virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }
+
+ static void signalDestroy(GtkWidget*, gpointer context)
+ {
+ GtkOpenGLContext* pThis = static_cast<GtkOpenGLContext*>(context);
+ pThis->m_pGLArea = nullptr;
+ pThis->m_nDestroySignalId = 0;
+ pThis->m_nRenderSignalId = 0;
+ }
+
+ static gboolean signalRender(GtkGLArea*, GdkGLContext*, gpointer window)
+ {
+ GtkOpenGLContext* pThis = static_cast<GtkOpenGLContext*>(window);
+
+ int scale = gtk_widget_get_scale_factor(pThis->m_pGLArea);
+ int width = pThis->m_aGLWin.Width * scale;
+ int height = pThis->m_aGLWin.Height * scale;
+
+ glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, pThis->m_nAreaFrameBuffer);
+ glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
+
+ glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
+ GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
+
+ gdk_gl_context_make_current(pThis->m_pContext);
+ return true;
+ }
+
+ virtual void adjustToNewSize() override
+ {
+ if (!m_pGLArea)
+ return;
+
+ int scale = gtk_widget_get_scale_factor(m_pGLArea);
+ int width = m_aGLWin.Width * scale;
+ int height = m_aGLWin.Height * scale;
+
+ // seen in tdf#124729 width/height of 0 leading to GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
+ int allocwidth = std::max(width, 1);
+ int allocheight = std::max(height, 1);
+
+ gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea));
+ if (GError *pError = gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea)))
+ {
+ SAL_WARN("vcl.gtk", "gtk gl area error: " << pError->message);
+ return;
+ }
+
+ glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderBuffer);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, allocwidth, allocheight);
+ glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBuffer);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, allocwidth, allocheight);
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nAreaFrameBuffer);
+
+ glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_RENDERBUFFER_EXT, m_nRenderBuffer);
+ glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
+ GL_RENDERBUFFER_EXT, m_nDepthBuffer);
+
+ gdk_gl_context_make_current(m_pContext);
+ glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderBuffer);
+ glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBuffer);
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameBuffer);
+
+ glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_RENDERBUFFER_EXT, m_nRenderBuffer);
+ glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
+ GL_RENDERBUFFER_EXT, m_nDepthBuffer);
+ glViewport(0, 0, width, height);
+
+ glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderScratchBuffer);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, allocwidth, allocheight);
+ glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthScratchBuffer);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, allocwidth, allocheight);
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
+
+ glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
+ glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
+ GL_RENDERBUFFER_EXT, m_nDepthScratchBuffer);
+
+ glViewport(0, 0, width, height);
+ }
+
+ // Use a throw away toplevel to determine the OpenGL version because once
+ // an GdkGLContext is created for a window then it seems that
+ // glGenVertexArrays will always be called when the window gets rendered.
+ static int GetOpenGLVersion()
+ {
+ int nMajorGLVersion(0);
+
+ GtkWidget* pWindow;
+#if !GTK_CHECK_VERSION(4,0,0)
+ pWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+#else
+ pWindow = gtk_window_new();
+#endif
+
+ gtk_widget_realize(pWindow);
+
+ if (GdkSurface* pSurface = widget_get_surface(pWindow))
+ {
+ if (GdkGLContext* pContext = surface_create_gl_context(pSurface))
+ {
+ if (gdk_gl_context_realize(pContext, nullptr))
+ {
+ OpenGLZone aZone;
+ gdk_gl_context_make_current(pContext);
+ gdk_gl_context_get_version(pContext, &nMajorGLVersion, nullptr);
+ gdk_gl_context_clear_current();
+ }
+ g_object_unref(pContext);
+ }
+ }
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_widget_destroy(pWindow);
+#else
+ gtk_window_destroy(GTK_WINDOW(pWindow));
+#endif
+ return nMajorGLVersion;
+ }
+
+ virtual bool ImplInit() override
+ {
+ static int nOpenGLVersion = GetOpenGLVersion();
+ if (nOpenGLVersion < 3)
+ {
+ SAL_WARN("vcl.gtk", "gtk GL requires glGenVertexArrays which is OpenGL 3, while system provides: " << nOpenGLVersion);
+ return false;
+ }
+
+ const SystemEnvData* pEnvData = m_pChildWindow->GetSystemData();
+ GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
+ m_pGLArea = gtk_gl_area_new();
+ m_nDestroySignalId = g_signal_connect(G_OBJECT(m_pGLArea), "destroy", G_CALLBACK(signalDestroy), this);
+ m_nRenderSignalId = g_signal_connect(G_OBJECT(m_pGLArea), "render", G_CALLBACK(signalRender), this);
+ gtk_gl_area_set_has_depth_buffer(GTK_GL_AREA(m_pGLArea), true);
+ gtk_gl_area_set_auto_render(GTK_GL_AREA(m_pGLArea), false);
+ gtk_widget_set_hexpand(m_pGLArea, true);
+ gtk_widget_set_vexpand(m_pGLArea, true);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(pParent), m_pGLArea);
+ gtk_widget_show_all(pParent);
+#else
+ gtk_grid_attach(GTK_GRID(pParent), m_pGLArea, 0, 0, 1, 1);
+ gtk_widget_show(pParent);
+ gtk_widget_show(m_pGLArea);
+#endif
+
+ gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea));
+ if (GError *pError = gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea)))
+ {
+ SAL_WARN("vcl.gtk", "gtk gl area error: " << pError->message);
+ return false;
+ }
+
+ gtk_gl_area_attach_buffers(GTK_GL_AREA(m_pGLArea));
+ glGenFramebuffersEXT(1, &m_nAreaFrameBuffer);
+
+ GdkSurface* pWindow = widget_get_surface(pParent);
+ m_pContext = surface_create_gl_context(pWindow);
+ if (!m_pContext)
+ return false;
+
+ if (!gdk_gl_context_realize(m_pContext, nullptr))
+ return false;
+
+ gdk_gl_context_make_current(m_pContext);
+ glGenFramebuffersEXT(1, &m_nFrameBuffer);
+ glGenRenderbuffersEXT(1, &m_nRenderBuffer);
+ glGenRenderbuffersEXT(1, &m_nDepthBuffer);
+ glGenFramebuffersEXT(1, &m_nFrameScratchBuffer);
+ glGenRenderbuffersEXT(1, &m_nRenderScratchBuffer);
+ glGenRenderbuffersEXT(1, &m_nDepthScratchBuffer);
+
+ bool bRet = InitGL();
+ InitGLDebugging();
+ return bRet;
+ }
+
+ virtual void restoreDefaultFramebuffer() override
+ {
+ OpenGLContext::restoreDefaultFramebuffer();
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
+ glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
+ }
+
+ virtual void makeCurrent() override
+ {
+ if (isCurrent())
+ return;
+
+ clearCurrent();
+
+ if (m_pGLArea)
+ {
+ int scale = gtk_widget_get_scale_factor(m_pGLArea);
+ int width = m_aGLWin.Width * scale;
+ int height = m_aGLWin.Height * scale;
+
+ gdk_gl_context_make_current(m_pContext);
+
+ glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderScratchBuffer);
+ glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthScratchBuffer);
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
+ glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
+ glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
+ GL_RENDERBUFFER_EXT, m_nDepthScratchBuffer);
+ glViewport(0, 0, width, height);
+ }
+
+ registerAsCurrent();
+ }
+
+ virtual void destroyCurrentContext() override
+ {
+ gdk_gl_context_clear_current();
+ }
+
+ virtual bool isCurrent() override
+ {
+ return m_pGLArea && gdk_gl_context_get_current() == m_pContext;
+ }
+
+ virtual void sync() override
+ {
+ }
+
+ virtual void resetCurrent() override
+ {
+ clearCurrent();
+ gdk_gl_context_clear_current();
+ }
+
+ virtual void swapBuffers() override
+ {
+ int scale = gtk_widget_get_scale_factor(m_pGLArea);
+ int width = m_aGLWin.Width * scale;
+ int height = m_aGLWin.Height * scale;
+
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_nFrameBuffer);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, m_nFrameScratchBuffer);
+ glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
+
+ glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
+ GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
+
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_nFrameScratchBuffer);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
+
+ gtk_gl_area_queue_render(GTK_GL_AREA(m_pGLArea));
+ BuffersSwapped();
+ }
+
+ virtual ~GtkOpenGLContext() override
+ {
+ if (m_nDestroySignalId)
+ g_signal_handler_disconnect(m_pGLArea, m_nDestroySignalId);
+ if (m_nRenderSignalId)
+ g_signal_handler_disconnect(m_pGLArea, m_nRenderSignalId);
+ if (m_pContext)
+ g_clear_object(&m_pContext);
+ }
+};
+
+}
+
+OpenGLContext* GtkInstance::CreateOpenGLContext()
+{
+ return new GtkOpenGLContext;
+}
+
+// tdf#123800 avoid requiring wayland at runtime just because it existed at buildtime
+bool DLSYM_GDK_IS_WAYLAND_DISPLAY(GdkDisplay* pDisplay)
+{
+ static auto get_type = reinterpret_cast<GType (*) (void)>(dlsym(nullptr, "gdk_wayland_display_get_type"));
+ if (!get_type)
+ return false;
+ static bool bResult = G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
+ return bResult;
+}
+
+bool DLSYM_GDK_IS_X11_DISPLAY(GdkDisplay* pDisplay)
+{
+ static auto get_type = reinterpret_cast<GType (*) (void)>(dlsym(nullptr, "gdk_x11_display_get_type"));
+ if (!get_type)
+ return false;
+ static bool bResult = G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
+ return bResult;
+}
+
+namespace
+{
+
+class GtkInstanceBuilder;
+
+ void set_help_id(const GtkWidget *pWidget, std::u16string_view rHelpId)
+ {
+ gchar *helpid = g_strdup(OUStringToOString(rHelpId, RTL_TEXTENCODING_UTF8).getStr());
+ g_object_set_data_full(G_OBJECT(pWidget), "g-lo-helpid", helpid, g_free);
+ }
+
+ OUString get_help_id(const GtkWidget *pWidget)
+ {
+ void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-helpid");
+ const gchar* pStr = static_cast<const gchar*>(pData);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ KeyEvent CreateKeyEvent(guint keyval, guint16 hardware_keycode, guint state, guint8 group)
+ {
+ sal_uInt16 nKeyCode = GtkSalFrame::GetKeyCode(keyval);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (nKeyCode == 0)
+ {
+ guint updated_keyval = GtkSalFrame::GetKeyValFor(gdk_keymap_get_default(), hardware_keycode, group);
+ nKeyCode = GtkSalFrame::GetKeyCode(updated_keyval);
+ }
+#else
+ (void)hardware_keycode;
+ (void)group;
+#endif
+ nKeyCode |= GtkSalFrame::GetKeyModCode(state);
+ return KeyEvent(gdk_keyval_to_unicode(keyval), nKeyCode, 0);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ KeyEvent GtkToVcl(const GdkEventKey& rEvent)
+ {
+ return CreateKeyEvent(rEvent.keyval, rEvent.hardware_keycode, rEvent.state, rEvent.group);
+ }
+#endif
+}
+
+static MouseEventModifiers ImplGetMouseButtonMode(sal_uInt16 nButton, sal_uInt16 nCode)
+{
+ MouseEventModifiers nMode = MouseEventModifiers::NONE;
+ if ( nButton == MOUSE_LEFT )
+ nMode |= MouseEventModifiers::SIMPLECLICK;
+ if ( (nButton == MOUSE_LEFT) && !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT)) )
+ nMode |= MouseEventModifiers::SELECT;
+ if ( (nButton == MOUSE_LEFT) && (nCode & KEY_MOD1) &&
+ !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_SHIFT)) )
+ nMode |= MouseEventModifiers::MULTISELECT;
+ if ( (nButton == MOUSE_LEFT) && (nCode & KEY_SHIFT) &&
+ !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_MOD1)) )
+ nMode |= MouseEventModifiers::RANGESELECT;
+ return nMode;
+}
+
+static MouseEventModifiers ImplGetMouseMoveMode(sal_uInt16 nCode)
+{
+ MouseEventModifiers nMode = MouseEventModifiers::NONE;
+ if ( !nCode )
+ nMode |= MouseEventModifiers::SIMPLEMOVE;
+ if ( (nCode & MOUSE_LEFT) && !(nCode & KEY_MOD1) )
+ nMode |= MouseEventModifiers::DRAGMOVE;
+ if ( (nCode & MOUSE_LEFT) && (nCode & KEY_MOD1) )
+ nMode |= MouseEventModifiers::DRAGCOPY;
+ return nMode;
+}
+
+namespace
+{
+ bool SwapForRTL(GtkWidget* pWidget)
+ {
+ GtkTextDirection eDir = gtk_widget_get_direction(pWidget);
+ if (eDir == GTK_TEXT_DIR_RTL)
+ return true;
+ if (eDir == GTK_TEXT_DIR_LTR)
+ return false;
+ return AllSettings::GetLayoutRTL();
+ }
+
+ GtkWidget* getPopupRect(GtkWidget* pWidget, const tools::Rectangle& rInRect, GdkRectangle& rOutRect)
+ {
+ if (GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pWidget))
+ {
+ // this is the relatively unusual case where pParent is the toplevel GtkSalFrame and not a stock GtkWidget
+ // so use the same style of logic as GtkSalMenu::ShowNativePopupMenu to get the right position
+ AbsoluteScreenPixelRectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pFrame->GetWindow(), rInRect);
+ aFloatRect.Move(-pFrame->maGeometry.x(), -pFrame->maGeometry.y());
+
+ rOutRect = GdkRectangle{static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
+ static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
+
+ pWidget = pFrame->getMouseEventWidget();
+ }
+ else
+ {
+ rOutRect = GdkRectangle{static_cast<int>(rInRect.Left()), static_cast<int>(rInRect.Top()),
+ static_cast<int>(rInRect.GetWidth()), static_cast<int>(rInRect.GetHeight())};
+ if (SwapForRTL(pWidget))
+ rOutRect.x = gtk_widget_get_allocated_width(pWidget) - rOutRect.width - 1 - rOutRect.x;
+ }
+ return pWidget;
+ }
+
+ void replaceWidget(GtkWidget* pWidget, GtkWidget* pReplacement)
+ {
+ // remove the widget and replace it with pReplacement
+ GtkWidget* pParent = gtk_widget_get_parent(pWidget);
+
+ // if pWidget was un-parented then don't bother
+ if (!pParent)
+ return;
+
+ g_object_ref(pWidget);
+
+ gint nTopAttach(0), nLeftAttach(0), nHeight(1), nWidth(1);
+ if (GTK_IS_GRID(pParent))
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_child_get(GTK_CONTAINER(pParent), pWidget,
+ "left-attach", &nLeftAttach,
+ "top-attach", &nTopAttach,
+ "width", &nWidth,
+ "height", &nHeight,
+ nullptr);
+#else
+ gtk_grid_query_child(GTK_GRID(pParent), pWidget,
+ &nLeftAttach, &nTopAttach,
+ &nWidth, &nHeight);
+#endif
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gboolean bExpand(false), bFill(false);
+ GtkPackType ePackType(GTK_PACK_START);
+ guint nPadding(0);
+ gint nPosition(0);
+ if (GTK_IS_BOX(pParent))
+ {
+ gtk_container_child_get(GTK_CONTAINER(pParent), pWidget,
+ "expand", &bExpand,
+ "fill", &bFill,
+ "pack-type", &ePackType,
+ "padding", &nPadding,
+ "position", &nPosition,
+ nullptr);
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // for gtk3 remove before replacement inserted, or there are warnings
+ // from GTK_BIN about having two children
+ container_remove(pParent, pWidget);
+#endif
+
+ gtk_widget_set_visible(pReplacement, gtk_widget_get_visible(pWidget));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_no_show_all(pReplacement, gtk_widget_get_no_show_all(pWidget));
+#endif
+
+ int nReqWidth, nReqHeight;
+ gtk_widget_get_size_request(pWidget, &nReqWidth, &nReqHeight);
+ gtk_widget_set_size_request(pReplacement, nReqWidth, nReqHeight);
+
+ static GQuark quark_size_groups = g_quark_from_static_string("gtk-widget-size-groups");
+ GSList* pSizeGroups = static_cast<GSList*>(g_object_get_qdata(G_OBJECT(pWidget), quark_size_groups));
+ while (pSizeGroups)
+ {
+ GtkSizeGroup *pSizeGroup = static_cast<GtkSizeGroup*>(pSizeGroups->data);
+ pSizeGroups = pSizeGroups->next;
+ gtk_size_group_remove_widget(pSizeGroup, pWidget);
+ gtk_size_group_add_widget(pSizeGroup, pReplacement);
+ }
+
+ // tdf#135368 change the mnemonic to point to our replacement
+ GList* pLabels = gtk_widget_list_mnemonic_labels(pWidget);
+ for (GList* pLabel = g_list_first(pLabels); pLabel; pLabel = g_list_next(pLabel))
+ {
+ GtkWidget* pLabelWidget = static_cast<GtkWidget*>(pLabel->data);
+ if (!GTK_IS_LABEL(pLabelWidget))
+ continue;
+ gtk_label_set_mnemonic_widget(GTK_LABEL(pLabelWidget), pReplacement);
+ }
+ g_list_free(pLabels);
+
+
+ if (GTK_IS_GRID(pParent))
+ {
+ gtk_grid_attach(GTK_GRID(pParent), pReplacement, nLeftAttach, nTopAttach, nWidth, nHeight);
+ }
+ else if (GTK_IS_BOX(pParent))
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_start(GTK_BOX(pParent), pReplacement, bExpand, bFill, nPadding);
+ gtk_container_child_set(GTK_CONTAINER(pParent), pReplacement,
+ "pack-type", ePackType,
+ "position", nPosition,
+ nullptr);
+#else
+ gtk_box_insert_child_after(GTK_BOX(pParent), pReplacement, pWidget);
+#endif
+ }
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ else
+ gtk_container_add(GTK_CONTAINER(pParent), pReplacement);
+#endif
+
+ if (gtk_widget_get_hexpand_set(pWidget))
+ gtk_widget_set_hexpand(pReplacement, gtk_widget_get_hexpand(pWidget));
+
+ if (gtk_widget_get_vexpand_set(pWidget))
+ gtk_widget_set_vexpand(pReplacement, gtk_widget_get_vexpand(pWidget));
+
+ gtk_widget_set_halign(pReplacement, gtk_widget_get_halign(pWidget));
+ gtk_widget_set_valign(pReplacement, gtk_widget_get_valign(pWidget));
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ // for gtk4 remove after replacement inserted so we could use gtk_box_insert_child_after
+ container_remove(pParent, pWidget);
+#endif
+
+ // coverity[freed_arg : FALSE] - this does not free pWidget, it is reffed by pReplacement
+ g_object_unref(pWidget);
+ }
+
+ void insertAsParent(GtkWidget* pWidget, GtkWidget* pReplacement)
+ {
+ g_object_ref(pWidget);
+
+ replaceWidget(pWidget, pReplacement);
+
+ // coverity[pass_freed_arg : FALSE] - pWidget is not freed here due to initial g_object_ref
+ container_add(pReplacement, pWidget);
+
+ // coverity[freed_arg : FALSE] - this does not free pWidget, it is reffed by pReplacement
+ g_object_unref(pWidget);
+ }
+
+ GtkWidget* ensureEventWidget(GtkWidget* pWidget)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return pWidget;
+#else
+
+ if (!pWidget)
+ return nullptr;
+
+ GtkWidget* pMouseEventBox;
+ // not every widget has a GdkWindow and can get any event, so if we
+ // want an event it doesn't have, insert a GtkEventBox so we can get
+ // those
+ if (gtk_widget_get_has_window(pWidget))
+ pMouseEventBox = pWidget;
+ else
+ {
+ // remove the widget and replace it with an eventbox and put the old
+ // widget into it
+ pMouseEventBox = gtk_event_box_new();
+ gtk_event_box_set_above_child(GTK_EVENT_BOX(pMouseEventBox), false);
+ gtk_event_box_set_visible_window(GTK_EVENT_BOX(pMouseEventBox), false);
+ insertAsParent(pWidget, pMouseEventBox);
+ }
+
+ return pMouseEventBox;
+#endif
+ }
+}
+
+namespace {
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+GdkDragAction VclToGdk(sal_Int8 dragOperation)
+{
+ GdkDragAction eRet(static_cast<GdkDragAction>(0));
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
+ eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_COPY);
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
+ eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_MOVE);
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
+ eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_LINK);
+ return eRet;
+}
+#endif
+
+GtkWindow* get_active_window()
+{
+ GtkWindow* pFocus = nullptr;
+
+ GList* pList = gtk_window_list_toplevels();
+
+ for (GList* pEntry = pList; pEntry; pEntry = pEntry->next)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (gtk_window_is_active(GTK_WINDOW(pEntry->data)))
+#else
+ if (gtk_window_has_toplevel_focus(GTK_WINDOW(pEntry->data)))
+#endif
+ {
+ pFocus = GTK_WINDOW(pEntry->data);
+ break;
+ }
+ }
+
+ g_list_free(pList);
+
+ return pFocus;
+}
+
+void LocalizeDecimalSeparator(guint& keyval)
+{
+ const bool bDecimalKey = keyval == GDK_KEY_KP_Decimal || keyval == GDK_KEY_KP_Separator;
+ // #i1820# (and tdf#154623) use locale specific decimal separator
+ if (bDecimalKey && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
+ {
+ GtkWindow* pFocusWin = get_active_window();
+ GtkWidget* pFocus = pFocusWin ? gtk_window_get_focus(pFocusWin) : nullptr;
+ // tdf#138932 except if the target is a GtkEntry used for passwords
+ // GTK4: TODO is it a GtkEntry or a child GtkText that has the focus in this situation?
+ if (!pFocus || !GTK_IS_ENTRY(pFocus) || gtk_entry_get_visibility(GTK_ENTRY(pFocus)))
+ {
+ OUString aSep(Application::GetSettings().GetLocaleDataWrapper().getNumDecimalSep());
+ keyval = aSep[0];
+ }
+ }
+}
+
+void set_cursor(GtkWidget* pWidget, const char *pName)
+{
+ if (!gtk_widget_get_realized(pWidget))
+ gtk_widget_realize(pWidget);
+ GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GdkCursor *pCursor = pName ? gdk_cursor_new_from_name(pName, nullptr) : nullptr;
+#else
+ GdkCursor *pCursor = pName ? gdk_cursor_new_from_name(pDisplay, pName) : nullptr;
+#endif
+ widget_set_cursor(pWidget, pCursor);
+ gdk_display_flush(pDisplay);
+ if (pCursor)
+ g_object_unref(pCursor);
+}
+
+vcl::Font get_font(GtkWidget* pWidget)
+{
+ PangoContext* pContext = gtk_widget_get_pango_context(pWidget);
+ return pango_to_vcl(pango_context_get_font_description(pContext),
+ Application::GetSettings().GetUILanguageTag().getLocale());
+}
+
+}
+
+OUString get_buildable_id(GtkBuildable* pWidget)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ const gchar* pStr = gtk_buildable_get_buildable_id(pWidget);
+#else
+ const gchar* pStr = gtk_buildable_get_name(pWidget);
+#endif
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+}
+
+void set_buildable_id(GtkBuildable* pWidget, const OUString& rId)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkBuildableIface *iface = GTK_BUILDABLE_GET_IFACE(pWidget);
+ (*iface->set_id)(pWidget, OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
+#else
+ gtk_buildable_set_name(pWidget, OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
+#endif
+}
+
+namespace {
+
+class GtkInstanceWidget : public virtual weld::Widget
+{
+protected:
+ GtkWidget* m_pWidget;
+ GtkWidget* m_pMouseEventBox;
+ GtkInstanceBuilder* m_pBuilder;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ DECL_LINK(async_drag_cancel, void*, void);
+#endif
+
+ bool IsFirstFreeze() const { return m_nFreezeCount == 0; }
+ bool IsLastThaw() const { return m_nFreezeCount == 1; }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalFocusIn(GtkEventControllerFocus*, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_focus_in();
+ }
+#else
+ static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_focus_in();
+ return false;
+ }
+#endif
+
+ void signal_focus_in()
+ {
+ GtkWidget* pTopLevel = widget_get_toplevel(m_pWidget);
+ // see commentary in GtkSalObjectWidgetClip::Show
+ if (pTopLevel && g_object_get_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange"))
+ return;
+
+ m_aFocusInHdl.Call(*this);
+ }
+
+ static gboolean signalMnemonicActivate(GtkWidget*, gboolean, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_mnemonic_activate();
+ }
+
+ bool signal_mnemonic_activate()
+ {
+ return m_aMnemonicActivateHdl.Call(*this);
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalFocusOut(GtkEventControllerFocus*, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_focus_in();
+ }
+#else
+ static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_focus_out();
+ return false;
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ void launch_drag_cancel(GdkDragContext* context)
+ {
+ // post our drag cancel to happen at the next available event cycle
+ if (m_pDragCancelEvent)
+ return;
+ g_object_ref(context);
+ m_pDragCancelEvent = Application::PostUserEvent(LINK(this, GtkInstanceWidget, async_drag_cancel), context);
+ }
+#endif
+
+ void signal_focus_out()
+ {
+ GtkWidget* pTopLevel = widget_get_toplevel(m_pWidget);
+ // see commentary in GtkSalObjectWidgetClip::Show
+ if (pTopLevel && g_object_get_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange"))
+ return;
+
+ m_aFocusOutHdl.Call(*this);
+ }
+
+ virtual void ensureMouseEventWidget()
+ {
+ if (!m_pMouseEventBox)
+ m_pMouseEventBox = ::ensureEventWidget(m_pWidget);
+ }
+
+ void ensureButtonPressSignal()
+ {
+ if (!m_nButtonPressSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkEventController* pClickController = get_click_controller();
+ m_nButtonPressSignalId = g_signal_connect(pClickController, "pressed", G_CALLBACK(signalButtonPress), this);
+#else
+ ensureMouseEventWidget();
+ m_nButtonPressSignalId = g_signal_connect(m_pMouseEventBox, "button-press-event", G_CALLBACK(signalButtonPress), this);
+#endif
+ }
+ }
+
+ void ensureButtonReleaseSignal()
+ {
+ if (!m_nButtonReleaseSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkEventController* pClickController = get_click_controller();
+ m_nButtonReleaseSignalId = g_signal_connect(pClickController, "released", G_CALLBACK(signalButtonRelease), this);
+#else
+ ensureMouseEventWidget();
+ m_nButtonReleaseSignalId = g_signal_connect(m_pMouseEventBox, "button-release-event", G_CALLBACK(signalButtonRelease), this);
+#endif
+ }
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalPopupMenu(GtkWidget* pWidget, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ //center it when we don't know where else to use
+ Point aPos(gtk_widget_get_allocated_width(pWidget) / 2,
+ gtk_widget_get_allocated_height(pWidget) / 2);
+ CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, false);
+ return pThis->signal_popup_menu(aCEvt);
+ }
+#endif
+
+ virtual void connect_style_updated(const Link<Widget&, void>& rLink) override
+ {
+ if (m_aStyleUpdatedHdl.IsSet())
+ ImplGetDefaultWindow()->RemoveEventListener(LINK(this, GtkInstanceWidget, SettingsChangedHdl));
+ weld::Widget::connect_style_updated(rLink);
+ if (m_aStyleUpdatedHdl.IsSet())
+ ImplGetDefaultWindow()->AddEventListener(LINK(this, GtkInstanceWidget, SettingsChangedHdl));
+ }
+
+ bool SwapForRTL() const
+ {
+ return ::SwapForRTL(m_pWidget);
+ }
+
+ void do_enable_drag_source(const rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants)
+ {
+ ensure_drag_source();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ auto aFormats = rHelper->getTransferDataFlavors();
+ std::vector<GtkTargetEntry> aGtkTargets(m_xDragSource->FormatsToGtk(aFormats));
+
+ m_eDragAction = VclToGdk(eDNDConstants);
+ drag_source_set(aGtkTargets, m_eDragAction);
+
+ for (auto &a : aGtkTargets)
+ g_free(a.target);
+
+ m_xDragSource->set_datatransfer(rHelper, rHelper);
+#else
+ (void)rHelper;
+ (void)eDNDConstants;
+#endif
+ }
+
+ void localizeDecimalSeparator()
+ {
+ // tdf#128867 if localize decimal separator is active we will always
+ // need to be able to change the output of the decimal key press
+ if (!m_nKeyPressSignalId && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_nKeyPressSignalId = g_signal_connect(get_key_controller(), "key-pressed", G_CALLBACK(signalKeyPressed), this);
+#else
+ m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
+#endif
+ }
+ }
+
+ void ensure_drag_begin_end()
+ {
+ if (!m_nDragBeginSignalId)
+ {
+ // using "after" due to https://gitlab.gnome.org/GNOME/pygobject/issues/251
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_nDragBeginSignalId = g_signal_connect_after(get_drag_controller(), "drag-begin", G_CALLBACK(signalDragBegin), this);
+#else
+ m_nDragBeginSignalId = g_signal_connect_after(m_pWidget, "drag-begin", G_CALLBACK(signalDragBegin), this);
+#endif
+ }
+ if (!m_nDragEndSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_nDragEndSignalId = g_signal_connect(get_drag_controller(), "drag-end", G_CALLBACK(signalDragEnd), this);
+#else
+ m_nDragEndSignalId = g_signal_connect(m_pWidget, "drag-end", G_CALLBACK(signalDragEnd), this);
+#endif
+ }
+ }
+
+ void DisconnectMouseEvents()
+ {
+ if (m_nButtonPressSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(get_click_controller(), m_nButtonPressSignalId);
+#else
+ g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonPressSignalId);
+#endif
+ m_nButtonPressSignalId = 0;
+ }
+ if (m_nMotionSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(get_motion_controller(), m_nMotionSignalId);
+#else
+ g_signal_handler_disconnect(m_pMouseEventBox, m_nMotionSignalId);
+#endif
+ m_nMotionSignalId = 0;
+ }
+ if (m_nLeaveSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(get_motion_controller(), m_nLeaveSignalId);
+#else
+ g_signal_handler_disconnect(m_pMouseEventBox, m_nLeaveSignalId);
+#endif
+ m_nLeaveSignalId = 0;
+ }
+ if (m_nEnterSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(get_motion_controller(), m_nEnterSignalId);
+#else
+ g_signal_handler_disconnect(m_pMouseEventBox, m_nEnterSignalId);
+#endif
+ m_nEnterSignalId = 0;
+ }
+ if (m_nButtonReleaseSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(get_click_controller(), m_nButtonReleaseSignalId);
+#else
+ g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonReleaseSignalId);
+#endif
+ m_nButtonReleaseSignalId = 0;
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!m_pMouseEventBox || m_pMouseEventBox == m_pWidget)
+ return;
+
+ // GtkWindow replacement for GtkPopover case
+ if (!GTK_IS_EVENT_BOX(m_pMouseEventBox))
+ {
+ m_pMouseEventBox = nullptr;
+ return;
+ }
+
+ // put things back they way we found them
+ GtkWidget* pParent = gtk_widget_get_parent(m_pMouseEventBox);
+
+ g_object_ref(m_pWidget);
+ gtk_container_remove(GTK_CONTAINER(m_pMouseEventBox), m_pWidget);
+
+ gtk_widget_destroy(m_pMouseEventBox);
+
+ gtk_container_add(GTK_CONTAINER(pParent), m_pWidget);
+ // coverity[freed_arg : FALSE] - this does not free m_pWidget, it is reffed by pParent
+ g_object_unref(m_pWidget);
+
+ m_pMouseEventBox = m_pWidget;
+#endif
+ }
+
+private:
+ bool m_bTakeOwnership;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool m_bDraggedOver;
+#endif
+ int m_nWaitCount;
+ int m_nFreezeCount;
+ sal_uInt16 m_nLastMouseButton;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ sal_uInt16 m_nLastMouseClicks;
+#endif
+ int m_nPressedButton;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+protected:
+ int m_nPressStartX;
+ int m_nPressStartY;
+private:
+#endif
+ ImplSVEvent* m_pDragCancelEvent;
+ GtkCssProvider* m_pBgCssProvider;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkDragAction m_eDragAction;
+#endif
+ gulong m_nFocusInSignalId;
+ gulong m_nMnemonicActivateSignalId;
+ gulong m_nFocusOutSignalId;
+ gulong m_nKeyPressSignalId;
+ gulong m_nKeyReleaseSignalId;
+protected:
+ gulong m_nSizeAllocateSignalId;
+private:
+ gulong m_nButtonPressSignalId;
+ gulong m_nMotionSignalId;
+ gulong m_nLeaveSignalId;
+ gulong m_nEnterSignalId;
+ gulong m_nButtonReleaseSignalId;
+ gulong m_nDragMotionSignalId;
+ gulong m_nDragDropSignalId;
+ gulong m_nDragDropReceivedSignalId;
+ gulong m_nDragLeaveSignalId;
+ gulong m_nDragBeginSignalId;
+ gulong m_nDragEndSignalId;
+ gulong m_nDragFailedSignalId;
+ gulong m_nDragDataDeleteignalId;
+ gulong m_nDragGetSignalId;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ int m_nGrabCount;
+ GtkEventController* m_pFocusController;
+ GtkEventController* m_pClickController;
+ GtkEventController* m_pMotionController;
+ GtkEventController* m_pDragController;
+ GtkEventController* m_pKeyController;
+#endif
+
+ rtl::Reference<GtkInstDropTarget> m_xDropTarget;
+ rtl::Reference<GtkInstDragSource> m_xDragSource;
+
+ static void signalSizeAllocate(GtkWidget*, GdkRectangle* allocation, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_size_allocate(allocation->width, allocation->height);
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalKeyPressed(GtkEventControllerKey*, guint keyval, guint keycode, GdkModifierType state, gpointer widget)
+ {
+ LocalizeDecimalSeparator(keyval);
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ return pThis->signal_key_press(keyval, keycode, state);
+ }
+
+ static gboolean signalKeyReleased(GtkEventControllerKey*, guint keyval, guint keycode, GdkModifierType state, gpointer widget)
+ {
+ LocalizeDecimalSeparator(keyval);
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ return pThis->signal_key_release(keyval, keycode, state);
+ }
+#else
+ static gboolean signalKey(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ LocalizeDecimalSeparator(pEvent->keyval);
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ if (pEvent->type == GDK_KEY_PRESS)
+ return pThis->signal_key_press(pEvent);
+ return pThis->signal_key_release(pEvent);
+ }
+#endif
+
+ virtual bool signal_popup_menu(const CommandEvent&)
+ {
+ return false;
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalButtonPress(GtkGestureClick* pGesture, int n_press, gdouble x, gdouble y, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_button(pGesture, SalEvent::MouseButtonDown, n_press, x, y);
+ }
+
+ static void signalButtonRelease(GtkGestureClick* pGesture, int n_press, gdouble x, gdouble y, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_button(pGesture, SalEvent::MouseButtonUp, n_press, x, y);
+ }
+
+ void signal_button(GtkGestureClick* pGesture, SalEvent nEventType, int n_press, gdouble x, gdouble y)
+ {
+ m_nPressedButton = -1;
+
+ Point aPos(x, y);
+ if (SwapForRTL())
+ aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
+
+ if (n_press == 1)
+ {
+ GdkEventSequence* pSequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(pGesture));
+ GdkEvent* pEvent = gtk_gesture_get_last_event(GTK_GESTURE(pGesture), pSequence);
+ if (gdk_event_triggers_context_menu(pEvent))
+ {
+ //if handled for context menu, stop processing
+ CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, true);
+ if (signal_popup_menu(aCEvt))
+ {
+ gtk_gesture_set_state(GTK_GESTURE(pGesture), GTK_EVENT_SEQUENCE_CLAIMED);
+ return;
+ }
+ }
+ }
+
+ GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pGesture));
+ int nButton = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(pGesture));
+
+ switch (nButton)
+ {
+ case 1:
+ m_nLastMouseButton = MOUSE_LEFT;
+ break;
+ case 2:
+ m_nLastMouseButton = MOUSE_MIDDLE;
+ break;
+ case 3:
+ m_nLastMouseButton = MOUSE_RIGHT;
+ break;
+ default:
+ return;
+ }
+
+ sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(eType);
+ // strip out which buttons are involved from the nModCode and replace with m_nLastMouseButton
+ sal_uInt16 nCode = m_nLastMouseButton | (nModCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2));
+ MouseEvent aMEvt(aPos, n_press, ImplGetMouseButtonMode(m_nLastMouseButton, nModCode), nCode, nCode);
+
+ if (nEventType == SalEvent::MouseButtonDown && m_aMousePressHdl.Call(aMEvt))
+ gtk_gesture_set_state(GTK_GESTURE(pGesture), GTK_EVENT_SEQUENCE_CLAIMED);
+
+ if (nEventType == SalEvent::MouseButtonUp && m_aMouseReleaseHdl.Call(aMEvt))
+ gtk_gesture_set_state(GTK_GESTURE(pGesture), GTK_EVENT_SEQUENCE_CLAIMED);
+ }
+
+#else
+
+ static gboolean signalButtonPress(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_button(pEvent);
+ }
+
+ static gboolean signalButtonRelease(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_button(pEvent);
+ }
+
+ bool signal_button(GdkEventButton* pEvent)
+ {
+ m_nPressedButton = -1;
+
+ Point aPos(pEvent->x, pEvent->y);
+ if (SwapForRTL())
+ aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
+
+ if (gdk_event_triggers_context_menu(reinterpret_cast<GdkEvent*>(pEvent)) && pEvent->type == GDK_BUTTON_PRESS)
+ {
+ //if handled for context menu, stop processing
+ CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, true);
+ if (signal_popup_menu(aCEvt))
+ return true;
+ }
+
+ /* Save press to possibly begin a drag */
+ if (pEvent->type != GDK_BUTTON_RELEASE)
+ {
+ m_nPressedButton = pEvent->button;
+ m_nPressStartX = pEvent->x;
+ m_nPressStartY = pEvent->y;
+ }
+
+ if (!m_aMousePressHdl.IsSet() && !m_aMouseReleaseHdl.IsSet())
+ return false;
+
+ SalEvent nEventType = SalEvent::NONE;
+ switch (pEvent->type)
+ {
+ case GDK_BUTTON_PRESS:
+ if (GdkEvent* pPeekEvent = gdk_event_peek())
+ {
+ bool bSkip = pPeekEvent->type == GDK_2BUTTON_PRESS ||
+ pPeekEvent->type == GDK_3BUTTON_PRESS;
+ gdk_event_free(pPeekEvent);
+ if (bSkip)
+ {
+ return false;
+ }
+ }
+ nEventType = SalEvent::MouseButtonDown;
+ m_nLastMouseClicks = 1;
+ break;
+ case GDK_2BUTTON_PRESS:
+ m_nLastMouseClicks = 2;
+ nEventType = SalEvent::MouseButtonDown;
+ break;
+ case GDK_3BUTTON_PRESS:
+ m_nLastMouseClicks = 3;
+ nEventType = SalEvent::MouseButtonDown;
+ break;
+ case GDK_BUTTON_RELEASE:
+ nEventType = SalEvent::MouseButtonUp;
+ break;
+ default:
+ return false;
+ }
+
+ switch (pEvent->button)
+ {
+ case 1:
+ m_nLastMouseButton = MOUSE_LEFT;
+ break;
+ case 2:
+ m_nLastMouseButton = MOUSE_MIDDLE;
+ break;
+ case 3:
+ m_nLastMouseButton = MOUSE_RIGHT;
+ break;
+ default:
+ return false;
+ }
+
+ sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
+ // strip out which buttons are involved from the nModCode and replace with m_nLastMouseButton
+ sal_uInt16 nCode = m_nLastMouseButton | (nModCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2));
+ MouseEvent aMEvt(aPos, m_nLastMouseClicks, ImplGetMouseButtonMode(m_nLastMouseButton, nModCode), nCode, nCode);
+
+ if (nEventType == SalEvent::MouseButtonDown)
+ {
+ if (!m_aMousePressHdl.IsSet())
+ return false;
+ return m_aMousePressHdl.Call(aMEvt);
+ }
+
+ if (!m_aMouseReleaseHdl.IsSet())
+ return false;
+ return m_aMouseReleaseHdl.Call(aMEvt);
+ }
+#endif
+
+ bool simple_signal_motion(double x, double y, guint nState)
+ {
+ if (!m_aMouseMotionHdl.IsSet())
+ return false;
+
+ Point aPos(x, y);
+ if (SwapForRTL())
+ aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
+ sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(nState);
+ MouseEvent aMEvt(aPos, 0, ImplGetMouseMoveMode(nModCode), nModCode, nModCode);
+
+ return m_aMouseMotionHdl.Call(aMEvt);
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalMotion(GtkEventControllerMotion *pController, double x, double y, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
+
+ SolarMutexGuard aGuard;
+ pThis->simple_signal_motion(x, y, eType);
+ }
+
+#else
+ static gboolean signalMotion(GtkWidget*, GdkEventMotion* pEvent, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_motion(pEvent);
+ }
+
+ bool signal_motion(const GdkEventMotion* pEvent)
+ {
+ const bool bDragData = m_eDragAction != 0 && m_nPressedButton != -1 && m_xDragSource.is() && gtk_drag_source_get_target_list(m_pWidget);
+ bool bUnsetDragIcon(false);
+ if (bDragData && gtk_drag_check_threshold(m_pWidget, m_nPressStartX, m_nPressStartY, pEvent->x, pEvent->y) && !do_signal_drag_begin(bUnsetDragIcon))
+ {
+ GdkDragContext* pContext = gtk_drag_begin_with_coordinates(m_pWidget,
+ gtk_drag_source_get_target_list(m_pWidget),
+ m_eDragAction,
+ m_nPressedButton,
+ const_cast<GdkEvent*>(reinterpret_cast<const GdkEvent*>(pEvent)),
+ m_nPressStartX, m_nPressStartY);
+
+ if (pContext && bUnsetDragIcon)
+ {
+ cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
+ gtk_drag_set_icon_surface(pContext, surface);
+ cairo_surface_destroy(surface);
+ }
+
+ m_nPressedButton = -1;
+ return false;
+ }
+
+ return simple_signal_motion(pEvent->x, pEvent->y, pEvent->state);
+ }
+#endif
+
+ bool signal_crossing(double x, double y, guint nState, MouseEventModifiers eMouseEventModifiers)
+ {
+ if (!m_aMouseMotionHdl.IsSet())
+ return false;
+
+ Point aPos(x, y);
+ if (SwapForRTL())
+ aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
+ sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(nState);
+ MouseEventModifiers eModifiers = ImplGetMouseMoveMode(nModCode);
+ eModifiers = eModifiers | eMouseEventModifiers;
+ MouseEvent aMEvt(aPos, 0, eModifiers, nModCode, nModCode);
+
+ m_aMouseMotionHdl.Call(aMEvt);
+ return false;
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalEnter(GtkEventControllerMotion *pController, double x, double y, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
+ SolarMutexGuard aGuard;
+ pThis->signal_crossing(x, y, eType, MouseEventModifiers::ENTERWINDOW);
+ }
+
+ static void signalLeave(GtkEventControllerMotion *pController, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
+ SolarMutexGuard aGuard;
+ pThis->signal_crossing(-1, -1, eType, MouseEventModifiers::LEAVEWINDOW);
+ }
+#else
+ static gboolean signalCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ MouseEventModifiers eMouseEventModifiers = pEvent->type == GDK_ENTER_NOTIFY ? MouseEventModifiers::ENTERWINDOW : MouseEventModifiers::LEAVEWINDOW;
+ SolarMutexGuard aGuard;
+ return pThis->signal_crossing(pEvent->x, pEvent->y, pEvent->state, eMouseEventModifiers);
+ }
+#endif
+
+ virtual void drag_started()
+ {
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ if (!pThis->m_bDraggedOver)
+ {
+ pThis->m_bDraggedOver = true;
+ pThis->drag_started();
+ }
+ return pThis->m_xDropTarget->signalDragMotion(pWidget, context, x, y, time);
+ }
+
+ static gboolean signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ return pThis->m_xDropTarget->signalDragDrop(pWidget, context, x, y, time);
+ }
+
+ static void signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ pThis->m_xDropTarget->signalDragDropReceived(pWidget, context, x, y, data, ttype, time);
+ }
+#endif
+
+ virtual void drag_ended()
+ {
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void signalDragLeave(GtkWidget* pWidget, GdkDragContext*, guint /*time*/, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ pThis->m_xDropTarget->signalDragLeave(pWidget);
+ if (pThis->m_bDraggedOver)
+ {
+ pThis->m_bDraggedOver = false;
+ pThis->drag_ended();
+ }
+ }
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalDragBegin(GtkDragSource* context, GdkDrag*, gpointer widget)
+#else
+ static void signalDragBegin(GtkWidget*, GdkDragContext* context, gpointer widget)
+#endif
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ pThis->signal_drag_begin(context);
+ }
+
+ void ensure_drag_source()
+ {
+ if (!m_xDragSource)
+ {
+ m_xDragSource.set(new GtkInstDragSource);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_nDragFailedSignalId = g_signal_connect(m_pWidget, "drag-failed", G_CALLBACK(signalDragFailed), this);
+ m_nDragDataDeleteignalId = g_signal_connect(m_pWidget, "drag-data-delete", G_CALLBACK(signalDragDelete), this);
+ m_nDragGetSignalId = g_signal_connect(m_pWidget, "drag-data-get", G_CALLBACK(signalDragDataGet), this);
+#endif
+
+ ensure_drag_begin_end();
+ }
+ }
+
+ virtual bool do_signal_drag_begin(bool& rUnsetDragIcon)
+ {
+ rUnsetDragIcon = false;
+ return false;
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ virtual void drag_set_icon(GtkDragSource*)
+#else
+ virtual void drag_set_icon(GdkDragContext*)
+#endif
+ {
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ void signal_drag_begin(GtkDragSource* context)
+#else
+ void signal_drag_begin(GdkDragContext* context)
+#endif
+ {
+ bool bUnsetDragIcon(false);
+ if (do_signal_drag_begin(bUnsetDragIcon))
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ launch_drag_cancel(context);
+#endif
+ return;
+ }
+ if (bUnsetDragIcon)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
+ gtk_drag_set_icon_surface(context, surface);
+ cairo_surface_destroy(surface);
+#endif
+ }
+ else
+ {
+ drag_set_icon(context);
+ }
+
+ if (!m_xDragSource)
+ return;
+ m_xDragSource->setActiveDragSource();
+ }
+
+ virtual void do_signal_drag_end()
+ {
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalDragEnd(GtkGestureDrag* /*gesture*/, double /*offset_x*/, double /*offset_y*/, gpointer widget)
+#else
+ static void signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer widget)
+#endif
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ pThis->do_signal_drag_end();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (pThis->m_xDragSource.is())
+ pThis->m_xDragSource->dragEnd(context);
+#endif
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalDragFailed(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkDragResult /*result*/, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ pThis->m_xDragSource->dragFailed();
+ return false;
+ }
+
+ static void signalDragDelete(GtkWidget* /*widget*/, GdkDragContext* /*context*/, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ pThis->m_xDragSource->dragDelete();
+ }
+
+ static void signalDragDataGet(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkSelectionData *data, guint info,
+ guint /*time*/, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ pThis->m_xDragSource->dragDataGet(data, info);
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ virtual void drag_source_set(const std::vector<GtkTargetEntry>& rGtkTargets, GdkDragAction eDragAction)
+ {
+ if (rGtkTargets.empty() && !eDragAction)
+ gtk_drag_source_unset(m_pWidget);
+ else
+ gtk_drag_source_set(m_pWidget, GDK_BUTTON1_MASK, rGtkTargets.data(), rGtkTargets.size(), eDragAction);
+ }
+#endif
+
+ void do_set_background(const Color& rColor)
+ {
+ const bool bRemoveColor = rColor == COL_AUTO;
+ if (bRemoveColor && !m_pBgCssProvider)
+ return;
+ GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(GTK_WIDGET(m_pWidget));
+ if (m_pBgCssProvider)
+ {
+ gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider));
+ m_pBgCssProvider = nullptr;
+ }
+ if (bRemoveColor)
+ return;
+ OUString sColor = rColor.AsRGBHexString();
+ m_pBgCssProvider = gtk_css_provider_new();
+ OUString aBuffer = "* { background-color: #" + sColor + "; }";
+ OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
+ css_provider_load_from_data(m_pBgCssProvider, aResult.getStr(), aResult.getLength());
+ gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+ DECL_LINK(SettingsChangedHdl, VclWindowEvent&, void);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void update_style(GtkWidget* pWidget, gpointer pData)
+ {
+ if (GTK_IS_CONTAINER(pWidget))
+ gtk_container_foreach(GTK_CONTAINER(pWidget), update_style, pData);
+ GtkWidgetClass* pWidgetClass = GTK_WIDGET_GET_CLASS(pWidget);
+ pWidgetClass->style_updated(pWidget);
+ }
+#endif
+
+public:
+ GtkInstanceWidget(GtkWidget* pWidget, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : m_pWidget(pWidget)
+ , m_pMouseEventBox(nullptr)
+ , m_pBuilder(pBuilder)
+ , m_bTakeOwnership(bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_bDraggedOver(false)
+#endif
+ , m_nWaitCount(0)
+ , m_nFreezeCount(0)
+ , m_nLastMouseButton(0)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_nLastMouseClicks(0)
+#endif
+ , m_nPressedButton(-1)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_nPressStartX(-1)
+ , m_nPressStartY(-1)
+#endif
+ , m_pDragCancelEvent(nullptr)
+ , m_pBgCssProvider(nullptr)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_eDragAction(GdkDragAction(0))
+#endif
+ , m_nFocusInSignalId(0)
+ , m_nMnemonicActivateSignalId(0)
+ , m_nFocusOutSignalId(0)
+ , m_nKeyPressSignalId(0)
+ , m_nKeyReleaseSignalId(0)
+ , m_nSizeAllocateSignalId(0)
+ , m_nButtonPressSignalId(0)
+ , m_nMotionSignalId(0)
+ , m_nLeaveSignalId(0)
+ , m_nEnterSignalId(0)
+ , m_nButtonReleaseSignalId(0)
+ , m_nDragMotionSignalId(0)
+ , m_nDragDropSignalId(0)
+ , m_nDragDropReceivedSignalId(0)
+ , m_nDragLeaveSignalId(0)
+ , m_nDragBeginSignalId(0)
+ , m_nDragEndSignalId(0)
+ , m_nDragFailedSignalId(0)
+ , m_nDragDataDeleteignalId(0)
+ , m_nDragGetSignalId(0)
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_nGrabCount(0)
+ , m_pFocusController(nullptr)
+ , m_pClickController(nullptr)
+ , m_pMotionController(nullptr)
+ , m_pDragController(nullptr)
+ , m_pKeyController(nullptr)
+#endif
+ {
+ if (!bTakeOwnership)
+ g_object_ref(m_pWidget);
+
+ localizeDecimalSeparator();
+ }
+
+ virtual void connect_key_press(const Link<const KeyEvent&, bool>& rLink) override
+ {
+ if (!m_nKeyPressSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_nKeyPressSignalId = g_signal_connect(get_key_controller(), "key-pressed", G_CALLBACK(signalKeyPressed), this);
+#else
+ m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
+#endif
+ }
+ weld::Widget::connect_key_press(rLink);
+ }
+
+ virtual void connect_key_release(const Link<const KeyEvent&, bool>& rLink) override
+ {
+ if (!m_nKeyReleaseSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_nKeyReleaseSignalId = g_signal_connect(get_key_controller(), "key-released", G_CALLBACK(signalKeyReleased), this);
+#else
+ m_nKeyReleaseSignalId = g_signal_connect(m_pWidget, "key-release-event", G_CALLBACK(signalKey), this);
+#endif
+ }
+ weld::Widget::connect_key_release(rLink);
+ }
+
+ virtual void connect_mouse_press(const Link<const MouseEvent&, bool>& rLink) override
+ {
+ ensureButtonPressSignal();
+ weld::Widget::connect_mouse_press(rLink);
+ }
+
+ virtual void connect_mouse_move(const Link<const MouseEvent&, bool>& rLink) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkEventController* pMotionController = get_motion_controller();
+ if (!m_nMotionSignalId)
+ m_nMotionSignalId = g_signal_connect(pMotionController, "motion", G_CALLBACK(signalMotion), this);
+ if (!m_nLeaveSignalId)
+ m_nLeaveSignalId = g_signal_connect(pMotionController, "leave", G_CALLBACK(signalEnter), this);
+ if (!m_nEnterSignalId)
+ m_nEnterSignalId = g_signal_connect(pMotionController, "enter", G_CALLBACK(signalLeave), this);
+#else
+ ensureMouseEventWidget();
+ if (!m_nMotionSignalId)
+ m_nMotionSignalId = g_signal_connect(m_pMouseEventBox, "motion-notify-event", G_CALLBACK(signalMotion), this);
+ if (!m_nLeaveSignalId)
+ m_nLeaveSignalId = g_signal_connect(m_pMouseEventBox, "leave-notify-event", G_CALLBACK(signalCrossing), this);
+ if (!m_nEnterSignalId)
+ m_nEnterSignalId = g_signal_connect(m_pMouseEventBox, "enter-notify-event", G_CALLBACK(signalCrossing), this);
+#endif
+ weld::Widget::connect_mouse_move(rLink);
+ }
+
+ virtual void connect_mouse_release(const Link<const MouseEvent&, bool>& rLink) override
+ {
+ ensureButtonReleaseSignal();
+ weld::Widget::connect_mouse_release(rLink);
+ }
+
+ virtual void set_sensitive(bool sensitive) override
+ {
+ gtk_widget_set_sensitive(m_pWidget, sensitive);
+ }
+
+ virtual bool get_sensitive() const override
+ {
+ return gtk_widget_get_sensitive(m_pWidget);
+ }
+
+ virtual bool get_visible() const override
+ {
+ return gtk_widget_get_visible(m_pWidget);
+ }
+
+ virtual bool is_visible() const override
+ {
+ return gtk_widget_is_visible(m_pWidget);
+ }
+
+ virtual void set_can_focus(bool bCanFocus) override
+ {
+ gtk_widget_set_can_focus(m_pWidget, bCanFocus);
+ }
+
+ virtual void grab_focus() override
+ {
+ if (has_focus())
+ return;
+ gtk_widget_grab_focus(m_pWidget);
+ }
+
+ virtual bool has_focus() const override
+ {
+ return gtk_widget_has_focus(m_pWidget);
+ }
+
+ virtual bool is_active() const override
+ {
+ GtkWindow* pTopLevel = GTK_WINDOW(widget_get_toplevel(m_pWidget));
+ return pTopLevel && gtk_window_is_active(pTopLevel) && has_focus();
+ }
+
+ // is the focus in a child of this widget, where a transient popup attached
+ // to a widget is considered a child of that widget
+ virtual bool has_child_focus() const override
+ {
+ GtkWindow* pFocusWin = get_active_window();
+ if (!pFocusWin)
+ return false;
+ GtkWidget* pFocus = gtk_window_get_focus(pFocusWin);
+ if (pFocus && gtk_widget_is_ancestor(pFocus, m_pWidget))
+ return true;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pAttachedTo = gtk_window_get_attached_to(pFocusWin);
+ if (!pAttachedTo)
+ return false;
+ if (pAttachedTo == m_pWidget || gtk_widget_is_ancestor(pAttachedTo, m_pWidget))
+ return true;
+#endif
+ return false;
+ }
+
+ virtual void show() override
+ {
+ gtk_widget_show(m_pWidget);
+ }
+
+ virtual void hide() override
+ {
+ gtk_widget_hide(m_pWidget);
+ }
+
+ virtual void set_size_request(int nWidth, int nHeight) override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_VIEWPORT(pParent))
+ pParent = gtk_widget_get_parent(pParent);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ {
+ gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(pParent), nWidth);
+ gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(pParent), nHeight);
+ }
+ gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
+ }
+
+ virtual Size get_size_request() const override
+ {
+ int nWidth, nHeight;
+ gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
+ return Size(nWidth, nHeight);
+ }
+
+ virtual Size get_preferred_size() const override
+ {
+ GtkRequisition size;
+ gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
+ return Size(size.width, size.height);
+ }
+
+ virtual float get_approximate_digit_width() const override
+ {
+ PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
+ PangoFontMetrics* pMetrics = pango_context_get_metrics(pContext,
+ pango_context_get_font_description(pContext),
+ pango_context_get_language(pContext));
+ float nDigitWidth = pango_font_metrics_get_approximate_digit_width(pMetrics);
+ pango_font_metrics_unref(pMetrics);
+
+ return nDigitWidth / PANGO_SCALE;
+ }
+
+ virtual int get_text_height() const override
+ {
+ PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
+ PangoFontMetrics* pMetrics = pango_context_get_metrics(pContext,
+ pango_context_get_font_description(pContext),
+ pango_context_get_language(pContext));
+ int nLineHeight = pango_font_metrics_get_ascent(pMetrics) + pango_font_metrics_get_descent(pMetrics);
+ pango_font_metrics_unref(pMetrics);
+ return nLineHeight / PANGO_SCALE;
+ }
+
+ virtual Size get_pixel_size(const OUString& rText) const override
+ {
+ OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ PangoLayout* pLayout = gtk_widget_create_pango_layout(m_pWidget, aStr.getStr());
+ gint nWidth, nHeight;
+ pango_layout_get_pixel_size(pLayout, &nWidth, &nHeight);
+ g_object_unref(pLayout);
+ return Size(nWidth, nHeight);
+ }
+
+ virtual vcl::Font get_font() override
+ {
+ return ::get_font(m_pWidget);
+ }
+
+ virtual void set_grid_left_attach(int nAttach) override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ int row, width, height;
+ gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, nullptr, &row, &width, &height);
+ g_object_ref(m_pWidget);
+ gtk_grid_remove(GTK_GRID(pParent), m_pWidget);
+ gtk_grid_attach(GTK_GRID(pParent), m_pWidget, nAttach, row, width, height);
+ g_object_unref(m_pWidget);
+#else
+ gtk_container_child_set(GTK_CONTAINER(pParent), m_pWidget, "left-attach", nAttach, nullptr);
+#endif
+ }
+
+ virtual int get_grid_left_attach() const override
+ {
+ gint nAttach(0);
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, &nAttach, nullptr, nullptr, nullptr);
+#else
+ gtk_container_child_get(GTK_CONTAINER(pParent), m_pWidget, "left-attach", &nAttach, nullptr);
+#endif
+ return nAttach;
+ }
+
+ virtual void set_grid_width(int nCols) override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ int col, row, height;
+ gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, &col, &row, nullptr, &height);
+ g_object_ref(m_pWidget);
+ gtk_grid_remove(GTK_GRID(pParent), m_pWidget);
+ gtk_grid_attach(GTK_GRID(pParent), m_pWidget, col, row, nCols, height);
+ g_object_unref(m_pWidget);
+#else
+ gtk_container_child_set(GTK_CONTAINER(pParent), m_pWidget, "width", nCols, nullptr);
+#endif
+ }
+
+ virtual void set_grid_top_attach(int nAttach) override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ int col, width, height;
+ gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, &col, nullptr, &width, &height);
+ g_object_ref(m_pWidget);
+ gtk_grid_remove(GTK_GRID(pParent), m_pWidget);
+ gtk_grid_attach(GTK_GRID(pParent), m_pWidget, col, nAttach, width, height);
+ g_object_unref(m_pWidget);
+#else
+ gtk_container_child_set(GTK_CONTAINER(pParent), m_pWidget, "top-attach", nAttach, nullptr);
+#endif
+ }
+
+ virtual int get_grid_top_attach() const override
+ {
+ gint nAttach(0);
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, nullptr, &nAttach, nullptr, nullptr);
+#else
+ gtk_container_child_get(GTK_CONTAINER(pParent), m_pWidget, "top-attach", &nAttach, nullptr);
+#endif
+ return nAttach;
+ }
+
+ virtual void set_hexpand(bool bExpand) override
+ {
+ gtk_widget_set_hexpand(m_pWidget, bExpand);
+ }
+
+ virtual bool get_hexpand() const override
+ {
+ return gtk_widget_get_hexpand(m_pWidget);
+ }
+
+ virtual void set_vexpand(bool bExpand) override
+ {
+ gtk_widget_set_vexpand(m_pWidget, bExpand);
+ }
+
+ virtual bool get_vexpand() const override
+ {
+ return gtk_widget_get_vexpand(m_pWidget);
+ }
+
+ virtual void set_margin_top(int nMargin) override
+ {
+ gtk_widget_set_margin_top(m_pWidget, nMargin);
+ }
+
+ virtual void set_margin_bottom(int nMargin) override
+ {
+ gtk_widget_set_margin_bottom(m_pWidget, nMargin);
+ }
+
+ virtual void set_margin_start(int nMargin) override
+ {
+ gtk_widget_set_margin_start(m_pWidget, nMargin);
+ }
+
+ virtual void set_margin_end(int nMargin) override
+ {
+ gtk_widget_set_margin_end(m_pWidget, nMargin);
+ }
+
+ virtual int get_margin_top() const override
+ {
+ return gtk_widget_get_margin_top(m_pWidget);
+ }
+
+ virtual int get_margin_bottom() const override
+ {
+ return gtk_widget_get_margin_bottom(m_pWidget);
+ }
+
+ virtual int get_margin_start() const override
+ {
+ return gtk_widget_get_margin_start(m_pWidget);
+ }
+
+ virtual int get_margin_end() const override
+ {
+ return gtk_widget_get_margin_end(m_pWidget);
+ }
+
+ virtual void set_accessible_name(const OUString& rName) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_accessible_update_property(GTK_ACCESSIBLE(m_pWidget), GTK_ACCESSIBLE_PROPERTY_LABEL,
+ OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr(), -1);
+#else
+ AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
+ if (!pAtkObject)
+ return;
+ atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
+#endif
+ }
+
+ virtual void set_accessible_description(const OUString& rDescription) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_accessible_update_property(GTK_ACCESSIBLE(m_pWidget), GTK_ACCESSIBLE_PROPERTY_DESCRIPTION,
+ OUStringToOString(rDescription, RTL_TEXTENCODING_UTF8).getStr(), -1);
+#else
+ AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
+ if (!pAtkObject)
+ return;
+ atk_object_set_description(pAtkObject, OUStringToOString(rDescription, RTL_TEXTENCODING_UTF8).getStr());
+#endif
+ }
+
+ virtual OUString get_accessible_name() const override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
+ const char* pStr = pAtkObject ? atk_object_get_name(pAtkObject) : nullptr;
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+#else
+ char* pStr = gtk_test_accessible_check_property(GTK_ACCESSIBLE(m_pWidget), GTK_ACCESSIBLE_PROPERTY_LABEL, nullptr);
+ OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pStr);
+ return sRet;
+#endif
+ }
+
+ virtual OUString get_accessible_description() const override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
+ const char* pStr = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+#else
+ char* pStr = gtk_test_accessible_check_property(GTK_ACCESSIBLE(m_pWidget), GTK_ACCESSIBLE_PROPERTY_DESCRIPTION, nullptr);
+ OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pStr);
+ return sRet;
+#endif
+ }
+
+ virtual void set_accessible_relation_labeled_by(weld::Widget* pLabel) override
+ {
+ GtkWidget* pGtkLabel = pLabel ? dynamic_cast<GtkInstanceWidget&>(*pLabel).getWidget() : nullptr;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_accessible_update_relation(GTK_ACCESSIBLE(m_pWidget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY,
+ pGtkLabel, nullptr,
+ -1);
+#else
+ AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
+ if (!pAtkObject)
+ return;
+ AtkObject *pAtkLabel = pGtkLabel ? gtk_widget_get_accessible(pGtkLabel) : nullptr;
+ AtkRelationSet *pRelationSet = atk_object_ref_relation_set(pAtkObject);
+ AtkRelation *pRelation = atk_relation_set_get_relation_by_type(pRelationSet, ATK_RELATION_LABELLED_BY);
+ if (pRelation)
+ {
+ // clear ATK_RELATION_LABEL_FOR from old label
+ GPtrArray* pOldLabelTarget = atk_relation_get_target(pRelation);
+ guint nElements = pOldLabelTarget ? pOldLabelTarget->len : 0;
+ for (guint i = 0; i < nElements; ++i)
+ {
+ gpointer pOldLabelObject = g_ptr_array_index(pOldLabelTarget, i);
+ AtkRelationSet *pOldLabelRelationSet = atk_object_ref_relation_set(ATK_OBJECT(pOldLabelObject));
+ if (AtkRelation *pOldLabelRelation = atk_relation_set_get_relation_by_type(pRelationSet, ATK_RELATION_LABEL_FOR))
+ atk_relation_set_remove(pOldLabelRelationSet, pOldLabelRelation);
+ g_object_unref(pOldLabelRelationSet);
+ }
+ atk_relation_set_remove(pRelationSet, pRelation);
+ }
+
+ if (pAtkLabel)
+ {
+ AtkObject *obj_array_labelled_by[1];
+ obj_array_labelled_by[0] = pAtkLabel;
+ pRelation = atk_relation_new(obj_array_labelled_by, 1, ATK_RELATION_LABELLED_BY);
+ atk_relation_set_add(pRelationSet, pRelation);
+
+ // add ATK_RELATION_LABEL_FOR to new label to match
+ AtkRelationSet *pNewLabelRelationSet = atk_object_ref_relation_set(pAtkLabel);
+ AtkRelation *pNewLabelRelation = atk_relation_set_get_relation_by_type(pNewLabelRelationSet, ATK_RELATION_LABEL_FOR);
+ if (pNewLabelRelation)
+ atk_relation_set_remove(pNewLabelRelationSet, pRelation);
+ AtkObject *obj_array_label_for[1];
+ obj_array_label_for[0] = pAtkObject;
+ pNewLabelRelation = atk_relation_new(obj_array_label_for, 1, ATK_RELATION_LABEL_FOR);
+ atk_relation_set_add(pNewLabelRelationSet, pNewLabelRelation);
+ g_object_unref(pNewLabelRelationSet);
+ }
+
+ g_object_unref(pRelationSet);
+#endif
+ }
+
+ virtual bool get_extents_relative_to(const weld::Widget& rRelative, int& x, int &y, int& width, int &height) const override
+ {
+ //for toplevel windows this is sadly futile under wayland, so we can't tell where a dialog is in order to allow
+ //the document underneath to auto-scroll to place content in a visible location
+ gtk_coord fX(0.0), fY(0.0);
+ bool ret = gtk_widget_translate_coordinates(m_pWidget,
+ dynamic_cast<const GtkInstanceWidget&>(rRelative).getWidget(),
+ 0, 0, &fX, &fY);
+ x = fX;
+ y = fY;
+ width = gtk_widget_get_allocated_width(m_pWidget);
+ height = gtk_widget_get_allocated_height(m_pWidget);
+ return ret;
+ }
+
+ virtual void set_tooltip_text(const OUString& rTip) override
+ {
+ gtk_widget_set_tooltip_text(m_pWidget, OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ virtual OUString get_tooltip_text() const override
+ {
+ const gchar* pStr = gtk_widget_get_tooltip_text(m_pWidget);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ virtual void set_cursor_data(void * /*pData*/) override {};
+
+ virtual std::unique_ptr<weld::Container> weld_parent() const override;
+
+ virtual OUString get_buildable_name() const override
+ {
+ return ::get_buildable_id(GTK_BUILDABLE(m_pWidget));
+ }
+
+ virtual void set_buildable_name(const OUString& rId) override
+ {
+ ::set_buildable_id(GTK_BUILDABLE(m_pWidget), rId);
+ }
+
+ virtual void set_help_id(const OUString& rHelpId) override
+ {
+ ::set_help_id(m_pWidget, rHelpId);
+ }
+
+ virtual OUString get_help_id() const override
+ {
+ OUString sRet = ::get_help_id(m_pWidget);
+ if (sRet.isEmpty())
+ sRet = "null";
+ return sRet;
+ }
+
+ GtkWidget* getWidget() const
+ {
+ return m_pWidget;
+ }
+
+ GtkWindow* getWindow() const
+ {
+ return GTK_WINDOW(widget_get_toplevel(m_pWidget));
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkEventController* get_focus_controller()
+ {
+ if (!m_pFocusController)
+ {
+ gtk_widget_set_focusable(m_pWidget, true);
+ m_pFocusController = gtk_event_controller_focus_new();
+ gtk_widget_add_controller(m_pWidget, m_pFocusController);
+ }
+ return m_pFocusController;
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkEventController* get_click_controller()
+ {
+ if (!m_pClickController)
+ {
+ GtkGesture *pClick = gtk_gesture_click_new();
+ gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pClick), 0);
+ m_pClickController = GTK_EVENT_CONTROLLER(pClick);
+ gtk_widget_add_controller(m_pWidget, m_pClickController);
+ }
+ return m_pClickController;
+ }
+
+ GtkEventController* get_motion_controller()
+ {
+ if (!m_pMotionController)
+ {
+ m_pMotionController = gtk_event_controller_motion_new();
+ gtk_widget_add_controller(m_pWidget, m_pMotionController);
+ }
+ return m_pMotionController;
+ }
+
+ GtkEventController* get_drag_controller()
+ {
+ if (!m_pDragController)
+ {
+ GtkDragSource* pDrag = gtk_drag_source_new();
+ m_pDragController = GTK_EVENT_CONTROLLER(pDrag);
+ gtk_widget_add_controller(m_pWidget, m_pDragController);
+ }
+ return m_pDragController;
+ }
+
+ GtkEventController* get_key_controller()
+ {
+ if (!m_pKeyController)
+ {
+ m_pKeyController = gtk_event_controller_key_new();
+ gtk_widget_add_controller(m_pWidget, m_pKeyController);
+ }
+ return m_pKeyController;
+ }
+
+#endif
+
+
+#endif
+
+ virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
+ {
+ if (!m_nFocusInSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_nFocusInSignalId = g_signal_connect(get_focus_controller(), "enter", G_CALLBACK(signalFocusIn), this);
+#else
+ m_nFocusInSignalId = g_signal_connect(m_pWidget, "focus-in-event", G_CALLBACK(signalFocusIn), this);
+#endif
+ }
+
+ weld::Widget::connect_focus_in(rLink);
+ }
+
+ virtual void connect_mnemonic_activate(const Link<Widget&, bool>& rLink) override
+ {
+ if (!m_nMnemonicActivateSignalId)
+ m_nMnemonicActivateSignalId = g_signal_connect(m_pWidget, "mnemonic-activate", G_CALLBACK(signalMnemonicActivate), this);
+ weld::Widget::connect_mnemonic_activate(rLink);
+ }
+
+ virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
+ {
+ if (!m_nFocusOutSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_nFocusOutSignalId = g_signal_connect(get_focus_controller(), "leave", G_CALLBACK(signalFocusOut), this);
+#else
+ m_nFocusOutSignalId = g_signal_connect(m_pWidget, "focus-out-event", G_CALLBACK(signalFocusOut), this);
+#endif
+ }
+ weld::Widget::connect_focus_out(rLink);
+ }
+
+ virtual void connect_size_allocate(const Link<const Size&, void>& rLink) override
+ {
+ m_nSizeAllocateSignalId = g_signal_connect(m_pWidget, "size-allocate", G_CALLBACK(signalSizeAllocate), this);
+ weld::Widget::connect_size_allocate(rLink);
+ }
+
+ virtual void signal_size_allocate(guint nWidth, guint nHeight)
+ {
+ m_aSizeAllocateHdl.Call(Size(nWidth, nHeight));
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ bool signal_key_press(guint keyval, guint keycode, GdkModifierType state)
+ {
+ if (m_aKeyPressHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ return m_aKeyPressHdl.Call(CreateKeyEvent(keyval, keycode, state, 0));
+ }
+ return false;
+ }
+
+ bool signal_key_release(guint keyval, guint keycode, GdkModifierType state)
+ {
+ if (m_aKeyReleaseHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ return m_aKeyReleaseHdl.Call(CreateKeyEvent(keyval, keycode, state, 0));
+ }
+ return false;
+ }
+#else
+
+ virtual bool do_signal_key_press(const GdkEventKey* pEvent)
+ {
+ if (m_aKeyPressHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ return m_aKeyPressHdl.Call(GtkToVcl(*pEvent));
+ }
+ return false;
+ }
+
+ virtual bool do_signal_key_release(const GdkEventKey* pEvent)
+ {
+ if (m_aKeyReleaseHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ return m_aKeyReleaseHdl.Call(GtkToVcl(*pEvent));
+ }
+ return false;
+ }
+
+ bool signal_key_press(const GdkEventKey* pEvent)
+ {
+ return do_signal_key_press(pEvent);
+ }
+
+ bool signal_key_release(const GdkEventKey* pEvent)
+ {
+ return do_signal_key_release(pEvent);
+ }
+#endif
+
+ virtual void grab_add() override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ ++m_nGrabCount;
+#else
+ gtk_grab_add(m_pWidget);
+#endif
+ }
+
+ virtual bool has_grab() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return m_nGrabCount != 0;
+#else
+ return gtk_widget_has_grab(m_pWidget);
+#endif
+ }
+
+ virtual void grab_remove() override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ --m_nGrabCount;
+#else
+ gtk_grab_remove(m_pWidget);
+#endif
+ }
+
+ virtual bool get_direction() const override
+ {
+ return gtk_widget_get_direction(m_pWidget) == GTK_TEXT_DIR_RTL;
+ }
+
+ virtual void set_direction(bool bRTL) override
+ {
+ gtk_widget_set_direction(m_pWidget, bRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
+ }
+
+ virtual void freeze() override
+ {
+ ++m_nFreezeCount;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_freeze_child_notify(m_pWidget);
+#endif
+ g_object_freeze_notify(G_OBJECT(m_pWidget));
+ }
+
+ virtual void thaw() override
+ {
+ --m_nFreezeCount;
+ g_object_thaw_notify(G_OBJECT(m_pWidget));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_thaw_child_notify(m_pWidget);
+#endif
+ }
+
+ virtual void set_busy_cursor(bool bBusy) override
+ {
+ if (bBusy)
+ ++m_nWaitCount;
+ else
+ --m_nWaitCount;
+ if (m_nWaitCount == 1)
+ set_cursor(m_pWidget, "progress");
+ else if (m_nWaitCount == 0)
+ set_cursor(m_pWidget, nullptr);
+ assert (m_nWaitCount >= 0);
+ }
+
+ virtual void queue_resize() override
+ {
+ gtk_widget_queue_resize(m_pWidget);
+ }
+
+ virtual css::uno::Reference<css::datatransfer::dnd::XDropTarget> get_drop_target() override
+ {
+ if (!m_xDropTarget)
+ {
+ m_xDropTarget.set(new GtkInstDropTarget);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!gtk_drag_dest_get_track_motion(m_pWidget))
+ {
+ gtk_drag_dest_set(m_pWidget, GtkDestDefaults(0), nullptr, 0, GdkDragAction(0));
+ gtk_drag_dest_set_track_motion(m_pWidget, true);
+ }
+ m_nDragMotionSignalId = g_signal_connect(m_pWidget, "drag-motion", G_CALLBACK(signalDragMotion), this);
+ m_nDragDropSignalId = g_signal_connect(m_pWidget, "drag-drop", G_CALLBACK(signalDragDrop), this);
+ m_nDragDropReceivedSignalId = g_signal_connect(m_pWidget, "drag-data-received", G_CALLBACK(signalDragDropReceived), this);
+ m_nDragLeaveSignalId = g_signal_connect(m_pWidget, "drag-leave", G_CALLBACK(signalDragLeave), this);
+#endif
+ }
+ return m_xDropTarget;
+ }
+
+ virtual css::uno::Reference<css::datatransfer::clipboard::XClipboard> get_clipboard() const override
+ {
+ // the gen backend can have per-frame clipboards which is (presumably) useful for LibreOffice Online
+ // but normal usage is the shared system clipboard
+ return GetSystemClipboard();
+ }
+
+ virtual void connect_get_property_tree(const Link<tools::JsonWriter&, void>& /*rLink*/) override
+ {
+ //not implemented for the gtk variant
+ }
+
+ virtual void get_property_tree(tools::JsonWriter& /*rJsonWriter*/) override
+ {
+ //not implemented for the gtk variant
+ }
+
+ virtual void call_attention_to() override
+ {
+ // Change the class name to restart the animation under
+ // its other name: https://css-tricks.com/restart-css-animation/
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (gtk_widget_has_css_class(m_pWidget, "call_attention_1"))
+ {
+ gtk_widget_remove_css_class(m_pWidget, "call_attention_1");
+ gtk_widget_add_css_class(m_pWidget, "call_attention_2");
+ }
+ else
+ {
+ gtk_widget_remove_css_class(m_pWidget, "call_attention_2");
+ gtk_widget_add_css_class(m_pWidget, "call_attention_1");
+ }
+#else
+ GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(m_pWidget);
+ if (gtk_style_context_has_class(pWidgetContext, "call_attention_1"))
+ {
+ gtk_style_context_remove_class(pWidgetContext, "call_attention_1");
+ gtk_style_context_add_class(pWidgetContext, "call_attention_2");
+ }
+ else
+ {
+ gtk_style_context_remove_class(pWidgetContext, "call_attention_2");
+ gtk_style_context_add_class(pWidgetContext, "call_attention_1");
+ }
+#endif
+ }
+
+ virtual void set_stack_background() override
+ {
+ do_set_background(Application::GetSettings().GetStyleSettings().GetWindowColor());
+ }
+
+ virtual void set_title_background() override
+ {
+ do_set_background(Application::GetSettings().GetStyleSettings().GetShadowColor());
+ }
+
+ virtual void set_highlight_background() override
+ {
+ do_set_background(Application::GetSettings().GetStyleSettings().GetHighlightColor());
+ }
+
+ virtual void set_background(const Color& rColor) override
+ {
+ do_set_background(rColor);
+ }
+
+ virtual void set_toolbar_background() override
+ {
+ // no-op
+ }
+
+ virtual ~GtkInstanceWidget() override
+ {
+ if (m_aStyleUpdatedHdl.IsSet())
+ ImplGetDefaultWindow()->RemoveEventListener(LINK(this, GtkInstanceWidget, SettingsChangedHdl));
+
+ if (m_pDragCancelEvent)
+ Application::RemoveUserEvent(m_pDragCancelEvent);
+ if (m_nDragMotionSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nDragMotionSignalId);
+ if (m_nDragDropSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nDragDropSignalId);
+ if (m_nDragDropReceivedSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nDragDropReceivedSignalId);
+ if (m_nDragLeaveSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nDragLeaveSignalId);
+ if (m_nDragEndSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(get_drag_controller(), m_nDragEndSignalId);
+#else
+ g_signal_handler_disconnect(m_pWidget, m_nDragEndSignalId);
+#endif
+ }
+ if (m_nDragBeginSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(get_drag_controller(), m_nDragBeginSignalId);
+#else
+ g_signal_handler_disconnect(m_pWidget, m_nDragBeginSignalId);
+#endif
+ }
+ if (m_nDragFailedSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nDragFailedSignalId);
+ if (m_nDragDataDeleteignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nDragDataDeleteignalId);
+ if (m_nDragGetSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nDragGetSignalId);
+ if (m_nKeyPressSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(get_key_controller(), m_nKeyPressSignalId);
+#else
+ g_signal_handler_disconnect(m_pWidget, m_nKeyPressSignalId);
+#endif
+ }
+ if (m_nKeyReleaseSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(get_key_controller(), m_nKeyReleaseSignalId);
+#else
+ g_signal_handler_disconnect(m_pWidget, m_nKeyReleaseSignalId);
+#endif
+ }
+
+ if (m_nFocusInSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(get_focus_controller(), m_nFocusInSignalId);
+#else
+ g_signal_handler_disconnect(m_pWidget, m_nFocusInSignalId);
+#endif
+ }
+ if (m_nMnemonicActivateSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nMnemonicActivateSignalId);
+ if (m_nFocusOutSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(get_focus_controller(), m_nFocusOutSignalId);
+#else
+ g_signal_handler_disconnect(m_pWidget, m_nFocusOutSignalId);
+#endif
+ }
+ if (m_nSizeAllocateSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nSizeAllocateSignalId);
+
+ do_set_background(COL_AUTO);
+
+ DisconnectMouseEvents();
+
+ if (m_bTakeOwnership)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(m_pWidget);
+#else
+ gtk_window_destroy(GTK_WINDOW(m_pWidget));
+#endif
+ }
+ else
+ g_object_unref(m_pWidget);
+ }
+
+ virtual void disable_notify_events()
+ {
+ if (m_nFocusInSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_block(get_focus_controller(), m_nFocusInSignalId);
+#else
+ g_signal_handler_block(m_pWidget, m_nFocusInSignalId);
+#endif
+ }
+ if (m_nMnemonicActivateSignalId)
+ g_signal_handler_block(m_pWidget, m_nMnemonicActivateSignalId);
+ if (m_nFocusOutSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_block(get_focus_controller(), m_nFocusOutSignalId);
+#else
+ g_signal_handler_block(m_pWidget, m_nFocusOutSignalId);
+#endif
+ }
+ if (m_nSizeAllocateSignalId)
+ g_signal_handler_block(m_pWidget, m_nSizeAllocateSignalId);
+ }
+
+ virtual void enable_notify_events()
+ {
+ if (m_nSizeAllocateSignalId)
+ g_signal_handler_unblock(m_pWidget, m_nSizeAllocateSignalId);
+ if (m_nFocusOutSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_unblock(get_focus_controller(), m_nFocusOutSignalId);
+#else
+ g_signal_handler_unblock(m_pWidget, m_nFocusOutSignalId);
+#endif
+ }
+ if (m_nMnemonicActivateSignalId)
+ g_signal_handler_unblock(m_pWidget, m_nMnemonicActivateSignalId);
+
+ if (m_nFocusInSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_unblock(get_focus_controller(), m_nFocusInSignalId);
+#else
+ g_signal_handler_unblock(m_pWidget, m_nFocusInSignalId);
+#endif
+ }
+ }
+
+ virtual void help_hierarchy_foreach(const std::function<bool(const OUString&)>& func) override;
+
+ virtual OUString strip_mnemonic(const OUString &rLabel) const override
+ {
+ return rLabel.replaceFirst("_", "");
+ }
+
+ virtual VclPtr<VirtualDevice> create_virtual_device() const override
+ {
+ // create with no separate alpha layer like everything sane does
+ auto xRet = VclPtr<VirtualDevice>::Create();
+ xRet->SetBackground(COL_TRANSPARENT);
+ return xRet;
+ }
+
+ virtual void draw(OutputDevice& rOutput, const Point& rPos, const Size& rPixelSize) override
+ {
+ // detect if we have to manually setup its size
+ bool bAlreadyRealized = gtk_widget_get_realized(m_pWidget);
+ // has to be visible for draw to work
+ bool bAlreadyVisible = gtk_widget_get_visible(m_pWidget);
+ // has to be mapped for draw to work
+ bool bAlreadyMapped = gtk_widget_get_mapped(m_pWidget);
+
+ if (!bAlreadyRealized)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ /*
+ tdf#141633 The "sample db" example (Mockup.odb) has multiline
+ entries used in its "Journal Entry" column. Those are painted by
+ taking snapshots of a never-really-shown textview widget.
+ Without this style_updated then the textview is always drawn
+ using its original default font size and changing the page zoom
+ has no effect on the size of text in the "Journal Entry" column.
+ */
+ update_style(m_pWidget, nullptr);
+#endif
+ gtk_widget_realize(m_pWidget);
+ }
+ if (!bAlreadyVisible)
+ gtk_widget_show(m_pWidget);
+ if (!bAlreadyMapped)
+ gtk_widget_map(m_pWidget);
+
+ assert(gtk_widget_is_drawable(m_pWidget)); // all that should result in this holding
+
+ // turn off animations, otherwise we get a frame of an animation sequence
+ gboolean bAnimations;
+ GtkSettings* pSettings = gtk_widget_get_settings(m_pWidget);
+ g_object_get(pSettings, "gtk-enable-animations", &bAnimations, nullptr);
+ if (bAnimations)
+ g_object_set(pSettings, "gtk-enable-animations", false, nullptr);
+
+ Size aSize(rPixelSize);
+
+ GtkAllocation aOrigAllocation;
+ gtk_widget_get_allocation(m_pWidget, &aOrigAllocation);
+
+ GtkAllocation aNewAllocation {aOrigAllocation.x,
+ aOrigAllocation.y,
+ static_cast<int>(aSize.Width()),
+ static_cast<int>(aSize.Height()) };
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_size_allocate(m_pWidget, &aNewAllocation);
+#else
+ gtk_widget_size_allocate(m_pWidget, &aNewAllocation, 0);
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_CONTAINER(m_pWidget))
+ gtk_container_resize_children(GTK_CONTAINER(m_pWidget));
+#endif
+
+ VclPtr<VirtualDevice> xOutput(VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA));
+ xOutput->SetOutputSizePixel(aSize);
+
+ switch (rOutput.GetOutDevType())
+ {
+ case OUTDEV_WINDOW:
+ case OUTDEV_VIRDEV:
+ xOutput->DrawOutDev(Point(), aSize, rPos, aSize, rOutput);
+ break;
+ case OUTDEV_PRINTER:
+ case OUTDEV_PDF:
+ xOutput->SetBackground(rOutput.GetBackground());
+ xOutput->Erase();
+ break;
+ }
+
+ cairo_surface_t* pSurface = get_underlying_cairo_surface(*xOutput);
+ cairo_t* cr = cairo_create(pSurface);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_draw(m_pWidget, cr);
+#else
+ GtkSnapshot* pSnapshot = gtk_snapshot_new();
+ GtkWidgetClass* pWidgetClass = GTK_WIDGET_GET_CLASS(m_pWidget);
+ pWidgetClass->snapshot(m_pWidget, pSnapshot);
+ GskRenderNode* pNode = gtk_snapshot_free_to_node(pSnapshot);
+ gsk_render_node_draw(pNode, cr);
+ gsk_render_node_unref(pNode);
+#endif
+
+ cairo_destroy(cr);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_allocation(m_pWidget, &aOrigAllocation);
+ gtk_widget_size_allocate(m_pWidget, &aOrigAllocation);
+#else
+ gtk_widget_size_allocate(m_pWidget, &aOrigAllocation, 0);
+#endif
+
+ switch (rOutput.GetOutDevType())
+ {
+ case OUTDEV_WINDOW:
+ case OUTDEV_VIRDEV:
+ rOutput.DrawOutDev(rPos, aSize, Point(), aSize, *xOutput);
+ break;
+ case OUTDEV_PRINTER:
+ case OUTDEV_PDF:
+ rOutput.DrawBitmapEx(rPos, xOutput->GetBitmapEx(Point(), aSize));
+ break;
+ }
+
+ if (bAnimations)
+ g_object_set(pSettings, "gtk-enable-animations", true, nullptr);
+
+ if (!bAlreadyMapped)
+ gtk_widget_unmap(m_pWidget);
+ if (!bAlreadyVisible)
+ gtk_widget_hide(m_pWidget);
+ if (!bAlreadyRealized)
+ gtk_widget_unrealize(m_pWidget);
+ }
+};
+
+}
+
+IMPL_LINK(GtkInstanceWidget, SettingsChangedHdl, VclWindowEvent&, rEvent, void)
+{
+ if (rEvent.GetId() != VclEventId::WindowDataChanged)
+ return;
+
+ DataChangedEvent* pData = static_cast<DataChangedEvent*>(rEvent.GetData());
+ if (pData->GetType() == DataChangedEventType::SETTINGS)
+ m_aStyleUpdatedHdl.Call(*this);
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+IMPL_LINK(GtkInstanceWidget, async_drag_cancel, void*, arg, void)
+{
+ m_pDragCancelEvent = nullptr;
+ GdkDragContext* context = static_cast<GdkDragContext*>(arg);
+
+ // tdf#132477 simply calling gtk_drag_cancel on the treeview dnd under X
+ // doesn't seem to work as hoped for (though under wayland all is well).
+ // Under X the next (allowed) drag effort doesn't work to drop anything,
+ // but a then repeated attempt does.
+ // emitting cancel to get gtk to cancel the drag for us does work as hoped for.
+ g_signal_emit_by_name(context, "cancel", 0, GDK_DRAG_CANCEL_USER_CANCELLED);
+
+ g_object_unref(context);
+}
+#endif
+
+namespace
+{
+ OString MapToGtkAccelerator(const OUString &rStr)
+ {
+ return OUStringToOString(rStr.replaceFirst("~", "_"), RTL_TEXTENCODING_UTF8);
+ }
+
+ OUString get_label(GtkLabel* pLabel)
+ {
+ const gchar* pStr = gtk_label_get_label(pLabel);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ void set_label(GtkLabel* pLabel, const OUString& rText)
+ {
+ gtk_label_set_label(pLabel, MapToGtkAccelerator(rText).getStr());
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* find_label_widget(GtkWidget* pContainer)
+ {
+ GtkWidget* pLabel = nullptr;
+ for (GtkWidget* pChild = gtk_widget_get_first_child(pContainer);
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ if (GTK_IS_LABEL(pChild))
+ {
+ pLabel = pChild;
+ break;
+ }
+ else
+ {
+ pLabel = find_label_widget(pChild);
+ if (pLabel)
+ break;
+ }
+ }
+ return pLabel;
+ }
+
+ GtkWidget* find_image_widget(GtkWidget* pContainer)
+ {
+ GtkWidget* pImage = nullptr;
+ for (GtkWidget* pChild = gtk_widget_get_first_child(pContainer);
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ if (GTK_IS_IMAGE(pChild))
+ {
+ pImage = pChild;
+ break;
+ }
+ else
+ {
+ pImage = find_image_widget(pChild);
+ if (pImage)
+ break;
+ }
+ }
+ return pImage;
+ }
+#else
+ GtkWidget* find_label_widget(GtkContainer* pContainer)
+ {
+ GList* pChildren = gtk_container_get_children(pContainer);
+
+ GtkWidget* pChild = nullptr;
+ for (GList* pCandidate = pChildren; pCandidate; pCandidate = pCandidate->next)
+ {
+ if (GTK_IS_LABEL(pCandidate->data))
+ {
+ pChild = GTK_WIDGET(pCandidate->data);
+ break;
+ }
+ else if (GTK_IS_CONTAINER(pCandidate->data))
+ {
+ pChild = find_label_widget(GTK_CONTAINER(pCandidate->data));
+ if (pChild)
+ break;
+ }
+ }
+ g_list_free(pChildren);
+
+ return pChild;
+ }
+
+ GtkWidget* find_image_widget(GtkContainer* pContainer)
+ {
+ GList* pChildren = gtk_container_get_children(pContainer);
+
+ GtkWidget* pChild = nullptr;
+ for (GList* pCandidate = pChildren; pCandidate; pCandidate = pCandidate->next)
+ {
+ if (GTK_IS_IMAGE(pCandidate->data))
+ {
+ pChild = GTK_WIDGET(pCandidate->data);
+ break;
+ }
+ else if (GTK_IS_CONTAINER(pCandidate->data))
+ {
+ pChild = find_image_widget(GTK_CONTAINER(pCandidate->data));
+ if (pChild)
+ break;
+ }
+ }
+ g_list_free(pChildren);
+
+ return pChild;
+ }
+#endif
+
+ GtkLabel* get_label_widget(GtkWidget* pButton)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pButton));
+
+ if (GTK_IS_CONTAINER(pChild))
+ pChild = find_label_widget(GTK_CONTAINER(pChild));
+ else if (!GTK_IS_LABEL(pChild))
+ pChild = nullptr;
+
+ return GTK_LABEL(pChild);
+#else
+ return GTK_LABEL(find_label_widget(pButton));
+#endif
+ }
+
+ GtkImage* get_image_widget(GtkWidget *pButton)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pButton));
+
+ if (GTK_IS_CONTAINER(pChild))
+ pChild = find_image_widget(GTK_CONTAINER(pChild));
+ else if (!GTK_IS_IMAGE(pChild))
+ pChild = nullptr;
+
+ return GTK_IMAGE(pChild);
+#else
+ return GTK_IMAGE(find_image_widget(pButton));
+#endif
+ }
+
+ OUString button_get_label(GtkButton* pButton)
+ {
+ if (GtkLabel* pLabel = get_label_widget(GTK_WIDGET(pButton)))
+ return ::get_label(pLabel);
+ const gchar* pStr = gtk_button_get_label(pButton);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ void button_set_label(GtkButton* pButton, const OUString& rText)
+ {
+ if (GtkLabel* pLabel = get_label_widget(GTK_WIDGET(pButton)))
+ {
+ ::set_label(pLabel, rText);
+ gtk_widget_set_visible(GTK_WIDGET(pLabel), true);
+ return;
+ }
+ gtk_button_set_label(pButton, MapToGtkAccelerator(rText).getStr());
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ OUString get_label(GtkCheckButton* pButton)
+ {
+ const gchar* pStr = gtk_check_button_get_label(pButton);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ void set_label(GtkCheckButton* pButton, const OUString& rText)
+ {
+ gtk_check_button_set_label(pButton, MapToGtkAccelerator(rText).getStr());
+ }
+#endif
+
+ OUString get_title(GtkWindow* pWindow)
+ {
+ const gchar* pStr = gtk_window_get_title(pWindow);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ void set_title(GtkWindow* pWindow, std::u16string_view rTitle)
+ {
+ gtk_window_set_title(pWindow, OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ OUString get_primary_text(GtkMessageDialog* pMessageDialog)
+ {
+ gchar* pText = nullptr;
+ g_object_get(G_OBJECT(pMessageDialog), "text", &pText, nullptr);
+ return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ void set_primary_text(GtkMessageDialog* pMessageDialog, std::u16string_view rText)
+ {
+ g_object_set(G_OBJECT(pMessageDialog), "text",
+ OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
+ nullptr);
+ }
+
+ void set_secondary_text(GtkMessageDialog* pMessageDialog, std::u16string_view rText)
+ {
+ g_object_set(G_OBJECT(pMessageDialog), "secondary-text",
+ OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
+ nullptr);
+ }
+
+ OUString get_secondary_text(GtkMessageDialog* pMessageDialog)
+ {
+ gchar* pText = nullptr;
+ g_object_get(G_OBJECT(pMessageDialog), "secondary-text", &pText, nullptr);
+ return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ }
+}
+
+namespace
+{
+ GdkPixbuf* load_icon_from_stream(SvMemoryStream& rStream)
+ {
+ auto nLength = rStream.TellEnd();
+ if (!nLength)
+ return nullptr;
+ const guchar* pData = static_cast<const guchar*>(rStream.GetData());
+ assert((*pData == 137 || *pData == '<') && "if we want to support more than png or svg this function must change");
+ // if we know the image type, it's a little faster to hand the type over and skip the type detection.
+ GdkPixbufLoader *pixbuf_loader = gdk_pixbuf_loader_new_with_type(*pData == 137 ? "png" : "svg", nullptr);
+ gdk_pixbuf_loader_write(pixbuf_loader, pData, nLength, nullptr);
+ gdk_pixbuf_loader_close(pixbuf_loader, nullptr);
+ GdkPixbuf* pixbuf = gdk_pixbuf_loader_get_pixbuf(pixbuf_loader);
+ if (pixbuf)
+ g_object_ref(pixbuf);
+ g_object_unref(pixbuf_loader);
+ return pixbuf;
+ }
+
+ std::shared_ptr<SvMemoryStream> get_icon_stream_by_name_theme_lang(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
+ {
+ return ImageTree::get().getImageStream(rIconName, rIconTheme, rUILang);
+ }
+
+ GdkPixbuf* load_icon_by_name_theme_lang(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
+ {
+ auto xMemStm = get_icon_stream_by_name_theme_lang(rIconName, rIconTheme, rUILang);
+ if (!xMemStm)
+ return nullptr;
+ return load_icon_from_stream(*xMemStm);
+ }
+
+ std::unique_ptr<utl::TempFileNamed> get_icon_stream_as_file_by_name_theme_lang(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
+ {
+ uno::Reference<io::XInputStream> xInputStream = ImageTree::get().getImageXInputStream(rIconName, rIconTheme, rUILang);
+ if (!xInputStream)
+ return nullptr;
+
+ std::unique_ptr<utl::TempFileNamed> xRet(new utl::TempFileNamed);
+ xRet->EnableKillingFile(true);
+ SvStream* pStream = xRet->GetStream(StreamMode::WRITE);
+
+ for (;;)
+ {
+ const sal_Int32 nSize(2048);
+ uno::Sequence<sal_Int8> aData(nSize);
+ sal_Int32 nRead = xInputStream->readBytes(aData, nSize);
+ pStream->WriteBytes(aData.getConstArray(), nRead);
+ if (nRead < nSize)
+ break;
+ }
+ xRet->CloseStream();
+
+ return xRet;
+ }
+
+ std::unique_ptr<utl::TempFileNamed> get_icon_stream_as_file(const OUString& rIconName)
+ {
+ OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ OUString sUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
+ return get_icon_stream_as_file_by_name_theme_lang(rIconName, sIconTheme, sUILang);
+ }
+}
+
+GdkPixbuf* load_icon_by_name(const OUString& rIconName)
+{
+ OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ OUString sUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
+ return load_icon_by_name_theme_lang(rIconName, sIconTheme, sUILang);
+}
+
+namespace
+{
+ Image mirrorImage(const Image& rImage)
+ {
+ BitmapEx aMirrBitmapEx(rImage.GetBitmapEx());
+ aMirrBitmapEx.Mirror(BmpMirrorFlags::Horizontal);
+ return Image(aMirrBitmapEx);
+ }
+
+ GdkPixbuf* getPixbuf(const css::uno::Reference<css::graphic::XGraphic>& rImage, bool bMirror = false)
+ {
+ Image aImage(rImage);
+ if (bMirror)
+ aImage = mirrorImage(aImage);
+
+ OUString sStock(aImage.GetStock());
+ if (!sStock.isEmpty())
+ return load_icon_by_name(sStock);
+
+ SvMemoryStream aMemStm;
+
+ // We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed.
+ css::uno::Sequence<css::beans::PropertyValue> aFilterData{ comphelper::makePropertyValue(
+ "Compression", sal_Int32(1)) };
+ auto aBitmapEx = aImage.GetBitmapEx();
+ vcl::PngImageWriter aWriter(aMemStm);
+ aWriter.setParameters(aFilterData);
+ aWriter.write(aBitmapEx);
+
+ return load_icon_from_stream(aMemStm);
+ }
+
+ // tdf#151898 as far as I can see only gtk_image_new_from_file (or gtk_image_new_from_resource) can support the use of a
+ // scalable input format to create a hidpi GtkImage, rather than an upscaled lodpi one so forced to go via a file here
+ std::unique_ptr<utl::TempFileNamed> getImageFile(const css::uno::Reference<css::graphic::XGraphic>& rImage, bool bMirror = false)
+ {
+ Image aImage(rImage);
+ if (bMirror)
+ aImage = mirrorImage(aImage);
+
+ OUString sStock(aImage.GetStock());
+ if (!sStock.isEmpty())
+ return get_icon_stream_as_file(sStock);
+
+ std::unique_ptr<utl::TempFileNamed> xRet(new utl::TempFileNamed);
+ xRet->EnableKillingFile(true);
+ SvStream* pStream = xRet->GetStream(StreamMode::WRITE);
+
+ // We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed.
+ css::uno::Sequence<css::beans::PropertyValue> aFilterData{ comphelper::makePropertyValue(
+ "Compression", sal_Int32(1)) };
+ auto aBitmapEx = aImage.GetBitmapEx();
+ vcl::PngImageWriter aWriter(*pStream);
+ aWriter.setParameters(aFilterData);
+ aWriter.write(aBitmapEx);
+
+ xRet->CloseStream();
+ return xRet;
+ }
+
+ GdkPixbuf* getPixbuf(const VirtualDevice& rDevice)
+ {
+ Size aSize(rDevice.GetOutputSizePixel());
+ cairo_surface_t* orig_surface = get_underlying_cairo_surface(rDevice);
+ double fXScale, fYScale;
+ dl_cairo_surface_get_device_scale(orig_surface, &fXScale, &fYScale);
+
+ cairo_surface_t* surface;
+ if (fXScale != 1.0 || fYScale != -1)
+ {
+ surface = cairo_surface_create_similar_image(orig_surface,
+ CAIRO_FORMAT_ARGB32,
+ aSize.Width(),
+ aSize.Height());
+ cairo_t* cr = cairo_create(surface);
+ cairo_set_source_surface(cr, orig_surface, 0, 0);
+ cairo_paint(cr);
+ cairo_destroy(cr);
+ }
+ else
+ surface = orig_surface;
+
+ GdkPixbuf* pRet = gdk_pixbuf_get_from_surface(surface, 0, 0, aSize.Width(), aSize.Height());
+
+ if (surface != orig_surface)
+ cairo_surface_destroy(surface);
+
+ return pRet;
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ cairo_surface_t* render_paintable_to_surface(GdkPaintable *paintable, int nWidth, int nHeight)
+ {
+ cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nWidth, nHeight);
+
+ GtkSnapshot* snapshot = gtk_snapshot_new();
+ gdk_paintable_snapshot(paintable, snapshot, nWidth, nHeight);
+ GskRenderNode* node = gtk_snapshot_free_to_node(snapshot);
+
+ cairo_t* cr = cairo_create(surface);
+ gsk_render_node_draw(node, cr);
+ cairo_destroy(cr);
+
+ gsk_render_node_unref(node);
+
+ return surface;
+ }
+#endif
+
+ GdkPixbuf* getPixbuf(const OUString& rIconName)
+ {
+ if (rIconName.isEmpty())
+ return nullptr;
+
+ GdkPixbuf* pixbuf = nullptr;
+ if (rIconName.lastIndexOf('.') != rIconName.getLength() - 4)
+ {
+ assert((rIconName== "dialog-warning" || rIconName== "dialog-error" || rIconName== "dialog-information") &&
+ "unknown stock image");
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkIconTheme *icon_theme = gtk_icon_theme_get_for_display(gdk_display_get_default());
+ GtkIconPaintable *icon = gtk_icon_theme_lookup_icon(icon_theme,
+ OUStringToOString(rIconName, RTL_TEXTENCODING_UTF8).getStr(),
+ nullptr,
+ 16,
+ 1,
+ AllSettings::GetLayoutRTL() ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR,
+ static_cast<GtkIconLookupFlags>(0));
+ GdkPaintable* paintable = GDK_PAINTABLE(icon);
+ int nWidth = gdk_paintable_get_intrinsic_width(paintable);
+ int nHeight = gdk_paintable_get_intrinsic_height(paintable);
+ cairo_surface_t* surface = render_paintable_to_surface(paintable, nWidth, nHeight);
+ pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, nWidth, nHeight);
+ cairo_surface_destroy(surface);
+#else
+ GtkIconTheme *icon_theme = gtk_icon_theme_get_default();
+ GError *error = nullptr;
+ pixbuf = gtk_icon_theme_load_icon(icon_theme, OUStringToOString(rIconName, RTL_TEXTENCODING_UTF8).getStr(),
+ 16, GTK_ICON_LOOKUP_USE_BUILTIN, &error);
+#endif
+ }
+ else
+ {
+ const AllSettings& rSettings = Application::GetSettings();
+ pixbuf = load_icon_by_name_theme_lang(rIconName,
+ rSettings.GetStyleSettings().DetermineIconTheme(),
+ rSettings.GetUILanguageTag().getBcp47());
+ }
+ return pixbuf;
+ }
+}
+
+namespace
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ SurfacePaintable* paintable_new_from_virtual_device(const VirtualDevice& rImageSurface)
+ {
+ cairo_surface_t* surface = get_underlying_cairo_surface(rImageSurface);
+
+ Size aSize(rImageSurface.GetOutputSizePixel());
+ cairo_surface_t* target = cairo_surface_create_similar(surface,
+ cairo_surface_get_content(surface),
+ aSize.Width(),
+ aSize.Height());
+ cairo_t* cr = cairo_create(target);
+ cairo_set_source_surface(cr, surface, 0, 0);
+ cairo_paint(cr);
+ cairo_destroy(cr);
+
+ SurfacePaintable* pPaintable = SURFACE_PAINTABLE(g_object_new(surface_paintable_get_type(), nullptr));
+ surface_paintable_set_source(pPaintable, target, aSize.Width(), aSize.Height());
+ return pPaintable;
+ }
+
+ GtkWidget* image_new_from_virtual_device(const VirtualDevice& rImageSurface)
+ {
+ SurfacePaintable* paintable = paintable_new_from_virtual_device(rImageSurface);
+ return gtk_image_new_from_paintable(GDK_PAINTABLE(paintable));
+ }
+
+ GtkWidget* picture_new_from_virtual_device(const VirtualDevice& rImageSurface)
+ {
+ SurfacePaintable* paintable = paintable_new_from_virtual_device(rImageSurface);
+ return gtk_picture_new_for_paintable(GDK_PAINTABLE(paintable));
+ }
+
+#else
+ GtkWidget* image_new_from_virtual_device(const VirtualDevice& rImageSurface)
+ {
+ GtkWidget* pImage = nullptr;
+ cairo_surface_t* surface = get_underlying_cairo_surface(rImageSurface);
+
+ Size aSize(rImageSurface.GetOutputSizePixel());
+ cairo_surface_t* target = cairo_surface_create_similar(surface,
+ cairo_surface_get_content(surface),
+ aSize.Width(),
+ aSize.Height());
+ cairo_t* cr = cairo_create(target);
+ cairo_set_source_surface(cr, surface, 0, 0);
+ cairo_paint(cr);
+ cairo_destroy(cr);
+
+ pImage = gtk_image_new_from_surface(target);
+ cairo_surface_destroy(target);
+ return pImage;
+ }
+#endif
+
+ GtkWidget* image_new_from_xgraphic(const css::uno::Reference<css::graphic::XGraphic>& rIcon, bool bMirror)
+ {
+ GtkWidget* pImage = nullptr;
+ if (auto xTempFile = getImageFile(rIcon, bMirror))
+ pImage = gtk_image_new_from_file(OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
+ return pImage;
+ }
+
+ GtkWidget* image_new_from_icon_name(const OUString& rIconName)
+ {
+ GtkWidget* pImage = nullptr;
+ if (auto xTempFile = get_icon_stream_as_file(rIconName))
+ pImage = gtk_image_new_from_file(OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
+ return pImage;
+ }
+
+ GtkWidget* image_new_from_icon_name_theme_lang(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
+ {
+ GtkWidget* pImage = nullptr;
+ if (auto xTempFile = get_icon_stream_as_file_by_name_theme_lang(rIconName, rIconTheme, rUILang))
+ pImage = gtk_image_new_from_file(OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
+ return pImage;
+ }
+
+ void image_set_from_icon_name(GtkImage* pImage, const OUString& rIconName)
+ {
+ if (auto xTempFile = get_icon_stream_as_file(rIconName))
+ gtk_image_set_from_file(pImage, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
+ else
+ gtk_image_set_from_pixbuf(pImage, nullptr);
+ }
+
+ void image_set_from_icon_name_theme_lang(GtkImage* pImage, const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
+ {
+ if (auto xTempFile = get_icon_stream_as_file_by_name_theme_lang(rIconName, rIconTheme, rUILang))
+ gtk_image_set_from_file(pImage, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
+ else
+ gtk_image_set_from_pixbuf(pImage, nullptr);
+ }
+
+ void image_set_from_virtual_device(GtkImage* pImage, const VirtualDevice* pDevice)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_image_set_from_paintable(pImage, pDevice ? GDK_PAINTABLE(paintable_new_from_virtual_device(*pDevice)) : nullptr);
+#else
+ gtk_image_set_from_surface(pImage, pDevice ? get_underlying_cairo_surface(*pDevice) : nullptr);
+#endif
+ }
+
+ void image_set_from_xgraphic(GtkImage* pImage, const css::uno::Reference<css::graphic::XGraphic>& rImage)
+ {
+ if (auto xTempFile = getImageFile(rImage, false))
+ gtk_image_set_from_file(pImage, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
+ else
+ gtk_image_set_from_pixbuf(pImage, nullptr);
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ void picture_set_from_icon_name(GtkPicture* pPicture, const OUString& rIconName)
+ {
+ if (auto xTempFile = get_icon_stream_as_file(rIconName))
+ gtk_picture_set_filename(pPicture, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
+ else
+ gtk_picture_set_pixbuf(pPicture, nullptr);
+ }
+
+ void picture_set_from_icon_name_theme_lang(GtkPicture* pPicture, const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
+ {
+ if (auto xTempFile = get_icon_stream_as_file_by_name_theme_lang(rIconName, rIconTheme, rUILang))
+ gtk_picture_set_filename(pPicture, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
+ else
+ gtk_picture_set_pixbuf(pPicture, nullptr);
+ }
+
+ void picture_set_from_virtual_device(GtkPicture* pPicture, const VirtualDevice* pDevice)
+ {
+ if (!pDevice)
+ gtk_picture_set_paintable(pPicture, nullptr);
+ else
+ gtk_picture_set_paintable(pPicture, GDK_PAINTABLE(paintable_new_from_virtual_device(*pDevice)));
+ }
+
+ void picture_set_from_xgraphic(GtkPicture* pPicture, const css::uno::Reference<css::graphic::XGraphic>& rPicture)
+ {
+ if (auto xTempFile = getImageFile(rPicture, false))
+ gtk_picture_set_filename(pPicture, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
+ else
+ gtk_picture_set_pixbuf(pPicture, nullptr);
+ }
+#endif
+
+ void button_set_from_icon_name(GtkButton* pButton, const OUString& rIconName)
+ {
+ if (GtkImage* pImage = get_image_widget(GTK_WIDGET(pButton)))
+ {
+ ::image_set_from_icon_name(pImage, rIconName);
+ gtk_widget_set_visible(GTK_WIDGET(pImage), true);
+ return;
+ }
+
+ GtkWidget* pImage = image_new_from_icon_name(rIconName);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_button_set_child(pButton, pImage);
+#else
+ gtk_button_set_image(pButton, pImage);
+#endif
+ }
+
+ void button_set_image(GtkButton* pButton, const VirtualDevice* pDevice)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_button_set_always_show_image(pButton, true);
+ gtk_button_set_image_position(pButton, GTK_POS_LEFT);
+#endif
+ GtkWidget* pImage = pDevice ? image_new_from_virtual_device(*pDevice) : nullptr;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_button_set_child(pButton, pImage);
+#else
+ gtk_button_set_image(pButton, pImage);
+#endif
+ }
+
+ void button_set_image(GtkButton* pButton, const css::uno::Reference<css::graphic::XGraphic>& rImage)
+ {
+ if (GtkImage* pImage = get_image_widget(GTK_WIDGET(pButton)))
+ {
+ ::image_set_from_xgraphic(pImage, rImage);
+ gtk_widget_set_visible(GTK_WIDGET(pImage), true);
+ return;
+ }
+
+ GtkWidget* pImage = image_new_from_xgraphic(rImage, false);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_button_set_child(pButton, pImage);
+#else
+ gtk_button_set_image(pButton, pImage);
+#endif
+ }
+
+
+class MenuHelper
+{
+protected:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkMenu* m_pMenu;
+
+ std::map<OUString, GtkMenuItem*> m_aMap;
+#else
+ GtkPopoverMenu* m_pMenu;
+
+ o3tl::sorted_vector<OString> m_aInsertedActions; // must outlive m_aActionEntries
+ std::map<OUString, OString> m_aIdToAction;
+ std::set<OUString> m_aHiddenIds;
+ std::vector<GActionEntry> m_aActionEntries;
+ GActionGroup* m_pActionGroup;
+ // move 'invisible' entries to m_pHiddenActionGroup
+ GActionGroup* m_pHiddenActionGroup;
+#endif
+ bool m_bTakeOwnership;
+private:
+
+ virtual void signal_item_activate(const OUString& rIdent) = 0;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void collect(GtkWidget* pItem, gpointer widget)
+ {
+ GtkMenuItem* pMenuItem = GTK_MENU_ITEM(pItem);
+ if (GtkWidget* pSubMenu = gtk_menu_item_get_submenu(pMenuItem))
+ gtk_container_foreach(GTK_CONTAINER(pSubMenu), collect, widget);
+ MenuHelper* pThis = static_cast<MenuHelper*>(widget);
+ pThis->add_to_map(pMenuItem);
+ }
+
+ static void signalActivate(GtkMenuItem* pItem, gpointer widget)
+ {
+ MenuHelper* pThis = static_cast<MenuHelper*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_item_activate(::get_buildable_id(GTK_BUILDABLE(pItem)));
+ }
+#else
+ static std::pair<GMenuModel*, int> find_id(GMenuModel* pMenuModel, const OUString& rId)
+ {
+ for (int i = 0, nCount = g_menu_model_get_n_items(pMenuModel); i < nCount; ++i)
+ {
+ OUString sTarget;
+ char *id;
+ if (g_menu_model_get_item_attribute(pMenuModel, i, "target", "s", &id))
+ {
+ sTarget = OStringToOUString(id, RTL_TEXTENCODING_UTF8);
+ g_free(id);
+ }
+
+ if (sTarget == rId)
+ return std::make_pair(pMenuModel, i);
+
+ if (GMenuModel* pSectionModel = g_menu_model_get_item_link(pMenuModel, i, G_MENU_LINK_SECTION))
+ {
+ std::pair<GMenuModel*, int> aRet = find_id(pSectionModel, rId);
+ if (aRet.first)
+ return aRet;
+ }
+ if (GMenuModel* pSubMenuModel = g_menu_model_get_item_link(pMenuModel, i, G_MENU_LINK_SUBMENU))
+ {
+ std::pair<GMenuModel*, int> aRet = find_id(pSubMenuModel, rId);
+ if (aRet.first)
+ return aRet;
+ }
+ }
+
+ return std::make_pair(nullptr, -1);
+ }
+
+ void clear_actions()
+ {
+ for (const auto& rAction : m_aActionEntries)
+ {
+ g_action_map_remove_action(G_ACTION_MAP(m_pActionGroup), rAction.name);
+ g_action_map_remove_action(G_ACTION_MAP(m_pHiddenActionGroup), rAction.name);
+ }
+ m_aActionEntries.clear();
+ m_aInsertedActions.clear();
+ m_aIdToAction.clear();
+ }
+
+ static void action_activated(GSimpleAction*, GVariant* pParameter, gpointer widget)
+ {
+ gsize nLength(0);
+ const gchar* pStr = g_variant_get_string(pParameter, &nLength);
+ OUString aStr(pStr, nLength, RTL_TEXTENCODING_UTF8);
+ MenuHelper* pThis = static_cast<MenuHelper*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_item_activate(aStr);
+ }
+#endif
+
+public:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ MenuHelper(GtkMenu* pMenu, bool bTakeOwnership)
+#else
+ MenuHelper(GtkPopoverMenu* pMenu, bool bTakeOwnership)
+#endif
+ : m_pMenu(pMenu)
+ , m_bTakeOwnership(bTakeOwnership)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!m_pMenu)
+ return;
+ gtk_container_foreach(GTK_CONTAINER(m_pMenu), collect, this);
+#else
+ m_pActionGroup = G_ACTION_GROUP(g_simple_action_group_new());
+ m_pHiddenActionGroup = G_ACTION_GROUP(g_simple_action_group_new());
+#endif
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ void add_to_map(GtkMenuItem* pMenuItem)
+ {
+ OUString id = ::get_buildable_id(GTK_BUILDABLE(pMenuItem));
+ m_aMap[id] = pMenuItem;
+ g_signal_connect(pMenuItem, "activate", G_CALLBACK(signalActivate), this);
+ }
+
+ void remove_from_map(GtkMenuItem* pMenuItem)
+ {
+ OUString id = ::get_buildable_id(GTK_BUILDABLE(pMenuItem));
+ auto iter = m_aMap.find(id);
+ g_signal_handlers_disconnect_by_data(pMenuItem, this);
+ m_aMap.erase(iter);
+ }
+
+ void disable_item_notify_events()
+ {
+ for (auto& a : m_aMap)
+ g_signal_handlers_block_by_func(a.second, reinterpret_cast<void*>(signalActivate), this);
+ }
+
+ void enable_item_notify_events()
+ {
+ for (auto& a : m_aMap)
+ g_signal_handlers_unblock_by_func(a.second, reinterpret_cast<void*>(signalActivate), this);
+ }
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ /* LibreOffice likes to think of separators between menu entries, while gtk likes
+ to think of sections of menus with separators drawn between sections. We always
+ arrange to have a section in a menu so toplevel menumodels comprise of
+ sections and we move entries between sections on pretending to insert separators */
+ static std::pair<GMenuModel*, int> get_section_and_pos_for(GMenuModel* pMenuModel, int pos)
+ {
+ int nSectionCount = g_menu_model_get_n_items(pMenuModel);
+ assert(nSectionCount);
+
+ GMenuModel* pSectionModel = nullptr;
+ int nIndexWithinSection = 0;
+
+ int nExternalPos = 0;
+ for (int nSection = 0; nSection < nSectionCount; ++nSection)
+ {
+ pSectionModel = g_menu_model_get_item_link(pMenuModel, nSection, G_MENU_LINK_SECTION);
+ assert(pSectionModel);
+ int nCount = g_menu_model_get_n_items(pSectionModel);
+ for (nIndexWithinSection = 0; nIndexWithinSection < nCount; ++nIndexWithinSection)
+ {
+ if (pos == nExternalPos)
+ break;
+ ++nExternalPos;
+ }
+ ++nExternalPos;
+ }
+
+ return std::make_pair(pSectionModel, nIndexWithinSection);
+ }
+
+ static int count_immediate_children(GMenuModel* pMenuModel)
+ {
+ int nSectionCount = g_menu_model_get_n_items(pMenuModel);
+ assert(nSectionCount);
+
+ int nExternalPos = 0;
+ for (int nSection = 0; nSection < nSectionCount; ++nSection)
+ {
+ GMenuModel* pSectionModel = g_menu_model_get_item_link(pMenuModel, nSection, G_MENU_LINK_SECTION);
+ assert(pSectionModel);
+ int nCount = g_menu_model_get_n_items(pSectionModel);
+ for (int nIndexWithinSection = 0; nIndexWithinSection < nCount; ++nIndexWithinSection)
+ {
+ ++nExternalPos;
+ }
+ ++nExternalPos;
+ }
+
+ return nExternalPos - 1;
+ }
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ void process_menu_model(GMenuModel* pMenuModel)
+ {
+ for (int i = 0, nCount = g_menu_model_get_n_items(pMenuModel); i < nCount; ++i)
+ {
+ OString sAction;
+ OUString sTarget;
+ char *id;
+ if (g_menu_model_get_item_attribute(pMenuModel, i, "action", "s", &id))
+ {
+ assert(o3tl::starts_with(id, "menu."));
+
+ sAction = OString(id + 5);
+
+ auto res = m_aInsertedActions.insert(sAction);
+ if (res.second)
+ {
+ // the const char* arg isn't copied by anything so it must continue to exist for the life time of
+ // the action group
+ if (sAction.startsWith("radio."))
+ m_aActionEntries.push_back({res.first->getStr(), action_activated, "s", "'none'", nullptr, {}});
+ else
+ m_aActionEntries.push_back({res.first->getStr(), action_activated, "s", nullptr, nullptr, {}});
+ }
+
+ g_free(id);
+ }
+
+ if (g_menu_model_get_item_attribute(pMenuModel, i, "target", "s", &id))
+ {
+ sTarget = OStringToOUString(id, RTL_TEXTENCODING_UTF8);
+ g_free(id);
+ }
+
+ m_aIdToAction[sTarget] = sAction;
+
+ if (GMenuModel* pSectionModel = g_menu_model_get_item_link(pMenuModel, i, G_MENU_LINK_SECTION))
+ process_menu_model(pSectionModel);
+ if (GMenuModel* pSubMenuModel = g_menu_model_get_item_link(pMenuModel, i, G_MENU_LINK_SUBMENU))
+ process_menu_model(pSubMenuModel);
+ }
+ }
+
+ // build an action group for the menu, "action" is the normal menu entry case
+ // the others are radiogroups
+ void update_action_group_from_popover_model()
+ {
+ clear_actions();
+
+ if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
+ {
+ process_menu_model(pMenuModel);
+ }
+
+ // move hidden entries to m_pHiddenActionGroup
+ g_action_map_add_action_entries(G_ACTION_MAP(m_pActionGroup), m_aActionEntries.data(), m_aActionEntries.size(), this);
+ for (const auto& id : m_aHiddenIds)
+ {
+ GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(m_pActionGroup), m_aIdToAction[id].getStr());
+ g_action_map_add_action(G_ACTION_MAP(m_pHiddenActionGroup), pAction);
+ g_action_map_remove_action(G_ACTION_MAP(m_pActionGroup), m_aIdToAction[id].getStr());
+ }
+ }
+#endif
+
+ void insert_item(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, const VirtualDevice* pImageSurface,
+ TriState eCheckRadioFalse)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pImage = nullptr;
+ if (pIconName && !pIconName->isEmpty())
+ pImage = image_new_from_icon_name(*pIconName);
+ else if (pImageSurface)
+ pImage = image_new_from_virtual_device(*pImageSurface);
+
+ GtkWidget *pItem;
+ if (pImage)
+ {
+ GtkBox *pBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
+ GtkWidget *pLabel = gtk_label_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
+ pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new() : gtk_menu_item_new();
+ gtk_box_pack_start(pBox, pImage, true, true, 0);
+ gtk_box_pack_start(pBox, pLabel, true, true, 0);
+ gtk_container_add(GTK_CONTAINER(pItem), GTK_WIDGET(pBox));
+ gtk_widget_show_all(pItem);
+ }
+ else
+ {
+ pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr())
+ : gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
+ }
+
+ if (eCheckRadioFalse == TRISTATE_FALSE)
+ gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(pItem), true);
+
+ ::set_buildable_id(GTK_BUILDABLE(pItem), rId);
+ gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
+ gtk_widget_show(pItem);
+ add_to_map(GTK_MENU_ITEM(pItem));
+ if (pos != -1)
+ gtk_menu_reorder_child(m_pMenu, pItem, pos);
+#else
+ (void)pIconName; (void)pImageSurface;
+
+ if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
+ {
+ auto aSectionAndPos = get_section_and_pos_for(pMenuModel, pos);
+ GMenu* pMenu = G_MENU(aSectionAndPos.first);
+ // action with a target value ... the action name and target value are separated by a double
+ // colon ... For example: "app.action::target"
+ OUString sActionAndTarget;
+ if (eCheckRadioFalse == TRISTATE_INDET)
+ sActionAndTarget = "menu.normal." + rId + "::" + rId;
+ else
+ sActionAndTarget = "menu.radio." + rId + "::" + rId;
+ g_menu_insert(pMenu, aSectionAndPos.second, MapToGtkAccelerator(rStr).getStr(), sActionAndTarget.toUtf8().getStr());
+
+ assert(eCheckRadioFalse == TRISTATE_INDET); // come back to this later
+
+ // TODO not redo entire group
+ update_action_group_from_popover_model();
+ }
+#endif
+ }
+
+ void insert_separator(int pos, const OUString& rId)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pItem = gtk_separator_menu_item_new();
+ ::set_buildable_id(GTK_BUILDABLE(pItem), rId);
+ gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
+ gtk_widget_show(pItem);
+ add_to_map(GTK_MENU_ITEM(pItem));
+ if (pos != -1)
+ gtk_menu_reorder_child(m_pMenu, pItem, pos);
+#else
+ if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
+ {
+ auto aSectionAndPos = get_section_and_pos_for(pMenuModel, pos);
+
+ for (int nSection = 0, nSectionCount = g_menu_model_get_n_items(pMenuModel); nSection < nSectionCount; ++nSection)
+ {
+ GMenuModel* pSectionModel = g_menu_model_get_item_link(pMenuModel, nSection, G_MENU_LINK_SECTION);
+ assert(pSectionModel);
+ if (aSectionAndPos.first == pSectionModel)
+ {
+ GMenu* pNewSection = g_menu_new();
+ GMenuItem* pSectionItem = g_menu_item_new_section(nullptr, G_MENU_MODEL(pNewSection));
+ OUString sActionAndTarget = "menu.separator." + rId + "::" + rId;
+ g_menu_item_set_detailed_action(pSectionItem, sActionAndTarget.toUtf8().getStr());
+ g_menu_insert_item(G_MENU(pMenuModel), nSection + 1, pSectionItem);
+ int nOldSectionCount = g_menu_model_get_n_items(pSectionModel);
+ for (int i = nOldSectionCount - 1; i >= aSectionAndPos.second; --i)
+ {
+ GMenuItem* pMenuItem = g_menu_item_new_from_model(pSectionModel, i);
+ g_menu_prepend_item(pNewSection, pMenuItem);
+ g_menu_remove(G_MENU(pSectionModel), i);
+ g_object_unref(pMenuItem);
+ }
+ g_object_unref(pSectionItem);
+ g_object_unref(pNewSection);
+ }
+ }
+ }
+
+#endif
+ }
+
+ void remove_item(const OUString& rIdent)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkMenuItem* pMenuItem = m_aMap[rIdent];
+ remove_from_map(pMenuItem);
+ gtk_widget_destroy(GTK_WIDGET(pMenuItem));
+#else
+ if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
+ {
+ std::pair<GMenuModel*, int> aRes = find_id(pMenuModel, rIdent);
+ if (!aRes.first)
+ return;
+ g_menu_remove(G_MENU(aRes.first), aRes.second);
+ }
+#endif
+ }
+
+ void set_item_sensitive(const OUString& rIdent, bool bSensitive)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup;
+ GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(pActionGroup), m_aIdToAction[rIdent].getStr());
+ g_simple_action_set_enabled(G_SIMPLE_ACTION(pAction), bSensitive);
+#else
+ gtk_widget_set_sensitive(GTK_WIDGET(m_aMap[rIdent]), bSensitive);
+#endif
+ }
+
+ bool get_item_sensitive(const OUString& rIdent) const
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup;
+ GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(pActionGroup), m_aIdToAction.find(rIdent)->second.getStr());
+ return g_action_get_enabled(pAction);
+#else
+ return gtk_widget_get_sensitive(GTK_WIDGET(m_aMap.find(rIdent)->second));
+#endif
+ }
+
+ void set_item_active(const OUString& rIdent, bool bActive)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ disable_item_notify_events();
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(m_aMap[rIdent]), bActive);
+ enable_item_notify_events();
+#else
+ GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup;
+ g_action_group_change_action_state(pActionGroup, m_aIdToAction[rIdent].getStr(),
+ g_variant_new_string(bActive ? OUStringToOString(rIdent, RTL_TEXTENCODING_UTF8).getStr() : "'none'"));
+#endif
+ }
+
+ bool get_item_active(const OUString& rIdent) const
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(m_aMap.find(rIdent)->second));
+#else
+ GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup;
+ GVariant* pState = g_action_group_get_action_state(pActionGroup, m_aIdToAction.find(rIdent)->second.getStr());
+ if (!pState)
+ return false;
+ const char *pStateString = g_variant_get_string(pState, nullptr);
+ bool bInactive = g_strcmp0(pStateString, "'none'") == 0;
+ g_variant_unref(pState);
+ return bInactive;
+#endif
+ }
+
+ void set_item_label(const OUString& rIdent, const OUString& rText)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_menu_item_set_label(m_aMap[rIdent], MapToGtkAccelerator(rText).getStr());
+#else
+ if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
+ {
+ std::pair<GMenuModel*, int> aRes = find_id(pMenuModel, rIdent);
+ if (!aRes.first)
+ return;
+ // clone the original item, remove the original, insert the replacement at
+ // the original location
+ GMenuItem* pMenuItem = g_menu_item_new_from_model(aRes.first, aRes.second);
+ g_menu_remove(G_MENU(aRes.first), aRes.second);
+ g_menu_item_set_label(pMenuItem, MapToGtkAccelerator(rText).getStr());
+ g_menu_insert_item(G_MENU(aRes.first), aRes.second, pMenuItem);
+ g_object_unref(pMenuItem);
+ }
+#endif
+ }
+
+ OUString get_item_label(const OUString& rIdent) const
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ const gchar* pText = gtk_menu_item_get_label(m_aMap.find(rIdent)->second);
+ return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+#else
+ if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
+ {
+ std::pair<GMenuModel*, int> aRes = find_id(pMenuModel, rIdent);
+ if (!aRes.first)
+ return OUString();
+
+ // clone the original item to query its label
+ GMenuItem* pMenuItem = g_menu_item_new_from_model(aRes.first, aRes.second);
+ char *pLabel = nullptr;
+ g_menu_item_get_attribute(pMenuItem, G_MENU_ATTRIBUTE_LABEL, "&s", &pLabel);
+ OUString aRet(pLabel, pLabel ? strlen(pLabel) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pLabel);
+ g_object_unref(pMenuItem);
+ return aRet;
+ }
+ return OUString();
+#endif
+ }
+
+ void set_item_visible(const OUString& rIdent, bool bShow)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pWidget = GTK_WIDGET(m_aMap[rIdent]);
+ if (bShow)
+ gtk_widget_show(pWidget);
+ else
+ gtk_widget_hide(pWidget);
+#else
+ bool bOldVisible = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end();
+ if (bShow == bOldVisible)
+ return;
+
+ if (!bShow)
+ {
+ GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(m_pActionGroup), m_aIdToAction[rIdent].getStr());
+ g_action_map_add_action(G_ACTION_MAP(m_pHiddenActionGroup), pAction);
+ g_action_map_remove_action(G_ACTION_MAP(m_pActionGroup), m_aIdToAction[rIdent].getStr());
+ m_aHiddenIds.insert(rIdent);
+ }
+ else
+ {
+ GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(m_pHiddenActionGroup), m_aIdToAction[rIdent].getStr());
+ g_action_map_add_action(G_ACTION_MAP(m_pActionGroup), pAction);
+ g_action_map_remove_action(G_ACTION_MAP(m_pHiddenActionGroup), m_aIdToAction[rIdent].getStr());
+ m_aHiddenIds.erase(rIdent);
+ }
+#endif
+ }
+
+ OUString get_item_id(int pos) const
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
+ gpointer pMenuItem = g_list_nth_data(pChildren, pos);
+ OUString id = ::get_buildable_id(GTK_BUILDABLE(pMenuItem));
+ g_list_free(pChildren);
+ return id;
+#else
+ OUString sTarget;
+ if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
+ {
+ auto aSectionAndPos = get_section_and_pos_for(pMenuModel, pos);
+ char *id;
+ if (g_menu_model_get_item_attribute(aSectionAndPos.first, aSectionAndPos.second, "target", "s", &id))
+ {
+ sTarget = OStringToOUString(id, RTL_TEXTENCODING_UTF8);
+ g_free(id);
+ }
+ }
+ return sTarget;
+#endif
+ }
+
+ int get_n_children() const
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
+ int nLen = g_list_length(pChildren);
+ g_list_free(pChildren);
+ return nLen;
+#else
+ if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
+ return count_immediate_children(pMenuModel);
+ return 0;
+#endif
+ }
+
+ void clear_items()
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ for (const auto& a : m_aMap)
+ {
+ GtkMenuItem* pMenuItem = a.second;
+ g_signal_handlers_disconnect_by_data(pMenuItem, this);
+ gtk_widget_destroy(GTK_WIDGET(pMenuItem));
+ }
+ m_aMap.clear();
+#else
+ if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
+ {
+ GMenu* pMenu = G_MENU(pMenuModel);
+ g_menu_remove_all(pMenu);
+ g_menu_insert_section(pMenu, 0, nullptr, G_MENU_MODEL(g_menu_new()));
+ m_aHiddenIds.clear();
+ update_action_group_from_popover_model();
+ }
+#endif
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkMenu* getMenu() const
+#else
+ GtkPopoverMenu* getMenu() const
+#endif
+ {
+ return m_pMenu;
+ }
+
+ virtual ~MenuHelper()
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ for (auto& a : m_aMap)
+ g_signal_handlers_disconnect_by_data(a.second, this);
+ if (m_bTakeOwnership)
+ gtk_widget_destroy(GTK_WIDGET(m_pMenu));
+#else
+ g_object_unref(m_pActionGroup);
+ g_object_unref(m_pHiddenActionGroup);
+#endif
+ }
+};
+
+class GtkInstanceSizeGroup : public weld::SizeGroup
+{
+private:
+ GtkSizeGroup* m_pGroup;
+public:
+ GtkInstanceSizeGroup()
+ : m_pGroup(gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL))
+ {
+ }
+ virtual void add_widget(weld::Widget* pWidget) override
+ {
+ GtkInstanceWidget* pVclWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
+ assert(pVclWidget);
+ gtk_size_group_add_widget(m_pGroup, pVclWidget->getWidget());
+ }
+ virtual void set_mode(VclSizeGroupMode eVclMode) override
+ {
+ GtkSizeGroupMode eGtkMode(GTK_SIZE_GROUP_NONE);
+ switch (eVclMode)
+ {
+ case VclSizeGroupMode::NONE:
+ eGtkMode = GTK_SIZE_GROUP_NONE;
+ break;
+ case VclSizeGroupMode::Horizontal:
+ eGtkMode = GTK_SIZE_GROUP_HORIZONTAL;
+ break;
+ case VclSizeGroupMode::Vertical:
+ eGtkMode = GTK_SIZE_GROUP_VERTICAL;
+ break;
+ case VclSizeGroupMode::Both:
+ eGtkMode = GTK_SIZE_GROUP_BOTH;
+ break;
+ }
+ gtk_size_group_set_mode(m_pGroup, eGtkMode);
+ }
+ virtual ~GtkInstanceSizeGroup() override
+ {
+ g_object_unref(m_pGroup);
+ }
+};
+
+class ChildFrame : public WorkWindow
+{
+private:
+ Idle maLayoutIdle;
+
+ DECL_LINK(ImplHandleLayoutTimerHdl, Timer*, void);
+public:
+ ChildFrame(vcl::Window* pParent, WinBits nStyle)
+ : WorkWindow(pParent, nStyle)
+ , maLayoutIdle( "ChildFrame maLayoutIdle" )
+ {
+ maLayoutIdle.SetPriority(TaskPriority::RESIZE);
+ maLayoutIdle.SetInvokeHandler( LINK( this, ChildFrame, ImplHandleLayoutTimerHdl ) );
+ }
+
+ virtual void dispose() override
+ {
+ maLayoutIdle.Stop();
+ WorkWindow::dispose();
+ }
+
+ virtual void queue_resize(StateChangedType eReason = StateChangedType::Layout) override
+ {
+ WorkWindow::queue_resize(eReason);
+ if (maLayoutIdle.IsActive())
+ return;
+ maLayoutIdle.Start();
+ }
+
+ void Layout()
+ {
+ if (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild))
+ pChild->SetPosSizePixel(Point(0, 0), GetSizePixel());
+ }
+
+ virtual void Resize() override
+ {
+ maLayoutIdle.Stop();
+ Layout();
+ WorkWindow::Resize();
+ }
+};
+
+IMPL_LINK_NOARG(ChildFrame, ImplHandleLayoutTimerHdl, Timer*, void)
+{
+ Layout();
+}
+
+class GtkInstanceContainer : public GtkInstanceWidget, public virtual weld::Container
+{
+private:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkContainer* m_pContainer;
+#else
+ GtkWidget* m_pContainer;
+#endif
+ gulong m_nSetFocusChildSignalId;
+ bool m_bChildHasFocus;
+
+ void signal_set_focus_child(bool bChildHasFocus)
+ {
+ if (m_bChildHasFocus != bChildHasFocus)
+ {
+ m_bChildHasFocus = bChildHasFocus;
+ signal_container_focus_changed();
+ }
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void signalSetFocusChild(GtkContainer*, GtkWidget* pChild, gpointer widget)
+ {
+ GtkInstanceContainer* pThis = static_cast<GtkInstanceContainer*>(widget);
+ pThis->signal_set_focus_child(pChild != nullptr);
+ }
+#endif
+
+public:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkInstanceContainer(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pContainer), pBuilder, bTakeOwnership)
+#else
+ GtkInstanceContainer(GtkWidget* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(pContainer, pBuilder, bTakeOwnership)
+#endif
+ , m_pContainer(pContainer)
+ , m_nSetFocusChildSignalId(0)
+ , m_bChildHasFocus(false)
+ {
+ }
+
+ virtual void connect_container_focus_changed(const Link<Container&, void>& rLink) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!m_nSetFocusChildSignalId)
+ m_nSetFocusChildSignalId = g_signal_connect(G_OBJECT(m_pContainer), "set-focus-child", G_CALLBACK(signalSetFocusChild), this);
+#endif
+ weld::Container::connect_container_focus_changed(rLink);
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* getContainer() { return m_pContainer; }
+#else
+ GtkContainer* getContainer() { return m_pContainer; }
+#endif
+
+ virtual void child_grab_focus() override
+ {
+ gtk_widget_grab_focus(m_pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ bool bHasFocusChild = gtk_widget_get_focus_child(GTK_WIDGET(m_pContainer));
+#else
+ bool bHasFocusChild = gtk_container_get_focus_child(m_pContainer);
+#endif
+ if (!bHasFocusChild)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (GtkWidget* pChild = gtk_widget_get_first_child(m_pContainer))
+ {
+ gtk_widget_set_focus_child(m_pContainer, pChild);
+ bHasFocusChild = true;
+ }
+#else
+ GList* pChildren = gtk_container_get_children(m_pContainer);
+ if (GList* pChild = g_list_first(pChildren))
+ {
+ gtk_container_set_focus_child(m_pContainer, static_cast<GtkWidget*>(pChild->data));
+ bHasFocusChild = true;
+ }
+ g_list_free(pChildren);
+#endif
+ }
+
+ if (bHasFocusChild)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_child_focus(gtk_widget_get_focus_child(m_pWidget), GTK_DIR_TAB_FORWARD);
+#else
+ gtk_widget_child_focus(gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget)), GTK_DIR_TAB_FORWARD);
+#endif
+ }
+
+ }
+
+ virtual void move(weld::Widget* pWidget, weld::Container* pNewParent) override
+ {
+ GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
+ assert(pGtkWidget);
+ GtkWidget* pChild = pGtkWidget->getWidget();
+ g_object_ref(pChild);
+ auto pOldContainer = getContainer();
+ container_remove(GTK_WIDGET(pOldContainer), pChild);
+
+ GtkInstanceContainer* pNewGtkParent = dynamic_cast<GtkInstanceContainer*>(pNewParent);
+ assert(!pNewParent || pNewGtkParent);
+ if (pNewGtkParent)
+ {
+ auto pNewContainer = pNewGtkParent->getContainer();
+ container_add(GTK_WIDGET(pNewContainer), pChild);
+ }
+ g_object_unref(pChild);
+ }
+
+ virtual css::uno::Reference<css::awt::XWindow> CreateChildFrame() override
+ {
+ // This will cause a GtkSalFrame to be created. With WB_SYSTEMCHILDWINDOW set it
+ // will create a toplevel GtkEventBox window
+ auto xEmbedWindow = VclPtr<ChildFrame>::Create(ImplGetDefaultWindow(), WB_SYSTEMCHILDWINDOW | WB_DIALOGCONTROL | WB_CHILDDLGCTRL);
+ SalFrame* pFrame = xEmbedWindow->ImplGetFrame();
+ GtkSalFrame* pGtkFrame = dynamic_cast<GtkSalFrame*>(pFrame);
+ assert(pGtkFrame);
+
+ // relocate that toplevel GtkEventBox into this widget
+ GtkWidget* pWindow = pGtkFrame->getWindow();
+
+ GtkWidget* pParent = gtk_widget_get_parent(pWindow);
+
+ g_object_ref(pWindow);
+ container_remove(pParent, pWindow);
+ container_add(GTK_WIDGET(m_pContainer), pWindow);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_child_set(m_pContainer, pWindow, "expand", true, "fill", true, nullptr);
+#endif
+ gtk_widget_set_hexpand(pWindow, true);
+ gtk_widget_set_vexpand(pWindow, true);
+ gtk_widget_realize(pWindow);
+ gtk_widget_set_can_focus(pWindow, true);
+ g_object_unref(pWindow);
+
+ // NoActivate otherwise Show grab focus to this widget
+ xEmbedWindow->Show(true, ShowFlags::NoActivate);
+ css::uno::Reference<css::awt::XWindow> xWindow(xEmbedWindow->GetComponentInterface(), css::uno::UNO_QUERY);
+ return xWindow;
+ }
+
+ virtual ~GtkInstanceContainer() override
+ {
+ if (m_nSetFocusChildSignalId)
+ g_signal_handler_disconnect(m_pContainer, m_nSetFocusChildSignalId);
+ }
+};
+
+}
+
+std::unique_ptr<weld::Container> GtkInstanceWidget::weld_parent() const
+{
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (!pParent)
+ return nullptr;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return std::make_unique<GtkInstanceContainer>(GTK_CONTAINER(pParent), m_pBuilder, false);
+#else
+ return std::make_unique<GtkInstanceContainer>(pParent, m_pBuilder, false);
+#endif
+}
+
+namespace {
+
+bool sortButtons(const GtkWidget* pA, const GtkWidget* pB)
+{
+ //order within groups according to platform rules
+ return getButtonPriority(get_buildable_id(GTK_BUILDABLE(pA))) <
+ getButtonPriority(get_buildable_id(GTK_BUILDABLE(pB)));
+}
+
+void sort_native_button_order(GtkBox* pContainer)
+{
+ std::vector<GtkWidget*> aChildren;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(pContainer));
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ aChildren.push_back(pChild);
+ }
+#else
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pContainer));
+ for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
+ aChildren.push_back(static_cast<GtkWidget*>(pChild->data));
+ g_list_free(pChildren);
+#endif
+
+ //sort child order within parent so that we match the platform button order
+ std::stable_sort(aChildren.begin(), aChildren.end(), sortButtons);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (size_t pos = 0; pos < aChildren.size(); ++pos)
+ gtk_box_reorder_child_after(pContainer, aChildren[pos], pos ? aChildren[pos - 1] : nullptr);
+#else
+ for (size_t pos = 0; pos < aChildren.size(); ++pos)
+ gtk_box_reorder_child(pContainer, aChildren[pos], pos);
+#endif
+}
+
+class GtkInstanceBox : public GtkInstanceContainer, public virtual weld::Box
+{
+private:
+ GtkBox* m_pBox;
+
+public:
+ GtkInstanceBox(GtkBox* pBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ : GtkInstanceContainer(GTK_CONTAINER(pBox), pBuilder, bTakeOwnership)
+#else
+ : GtkInstanceContainer(GTK_WIDGET(pBox), pBuilder, bTakeOwnership)
+#endif
+ , m_pBox(pBox)
+ {
+ }
+
+ virtual void reorder_child(weld::Widget* pWidget, int nNewPosition) override
+ {
+ GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
+ assert(pGtkWidget);
+ GtkWidget* pChild = pGtkWidget->getWidget();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_reorder_child(m_pBox, pChild, nNewPosition);
+#else
+ if (nNewPosition == 0)
+ gtk_box_reorder_child_after(m_pBox, pChild, nullptr);
+ else
+ {
+ int nNewSiblingPos = nNewPosition - 1;
+ int nChildPosition = 0;
+ for (GtkWidget* pNewSibling = gtk_widget_get_first_child(GTK_WIDGET(m_pBox));
+ pNewSibling; pNewSibling = gtk_widget_get_next_sibling(pNewSibling))
+ {
+ if (nChildPosition == nNewSiblingPos)
+ {
+ gtk_box_reorder_child_after(m_pBox, pChild, pNewSibling);
+ break;
+ }
+ ++nChildPosition;
+ }
+ }
+#endif
+ }
+
+ virtual void sort_native_button_order() override
+ {
+ ::sort_native_button_order(m_pBox);
+ }
+};
+
+}
+
+namespace
+{
+ Point get_csd_offset(GtkWidget* pTopLevel)
+ {
+ // try and omit drawing CSD under wayland
+ GtkWidget* pChild = widget_get_first_child(pTopLevel);
+
+ gtk_coord x, y;
+ gtk_widget_translate_coordinates(pChild, pTopLevel, 0, 0, &x, &y);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ int innerborder = gtk_container_get_border_width(GTK_CONTAINER(pChild));
+ int outerborder = gtk_container_get_border_width(GTK_CONTAINER(pTopLevel));
+ int totalborder = outerborder + innerborder;
+ x -= totalborder;
+ y -= totalborder;
+#endif
+
+ return Point(x, y);
+ }
+
+ void do_collect_screenshot_data(GtkWidget* pItem, gpointer data)
+ {
+ GtkWidget* pTopLevel = widget_get_toplevel(pItem);
+
+ gtk_coord x, y;
+ gtk_widget_translate_coordinates(pItem, pTopLevel, 0, 0, &x, &y);
+
+ Point aOffset = get_csd_offset(pTopLevel);
+
+ GtkAllocation alloc;
+ gtk_widget_get_allocation(pItem, &alloc);
+
+ const basegfx::B2IPoint aCurrentTopLeft(x - aOffset.X(), y - aOffset.Y());
+ const basegfx::B2IRange aCurrentRange(aCurrentTopLeft, aCurrentTopLeft + basegfx::B2IPoint(alloc.width, alloc.height));
+
+ if (!aCurrentRange.isEmpty())
+ {
+ weld::ScreenShotCollection* pCollection = static_cast<weld::ScreenShotCollection*>(data);
+ pCollection->emplace_back(::get_help_id(pItem), aCurrentRange);
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (GtkWidget* pChild = gtk_widget_get_first_child(pItem);
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ do_collect_screenshot_data(pChild, data);
+ }
+#else
+ if (GTK_IS_CONTAINER(pItem))
+ gtk_container_forall(GTK_CONTAINER(pItem), do_collect_screenshot_data, data);
+#endif
+ }
+
+ AbsoluteScreenPixelRectangle get_monitor_workarea(GtkWidget* pWindow)
+ {
+ GdkRectangle aRect;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkScreen* pScreen = gtk_widget_get_screen(pWindow);
+ gint nMonitor = gdk_screen_get_monitor_at_window(pScreen, widget_get_surface(pWindow));
+ gdk_screen_get_monitor_workarea(pScreen, nMonitor, &aRect);
+#else
+ GdkDisplay* pDisplay = gtk_widget_get_display(pWindow);
+ GdkSurface* gdkWindow = widget_get_surface(pWindow);
+ GdkMonitor* pMonitor = gdk_display_get_monitor_at_surface(pDisplay, gdkWindow);
+ gdk_monitor_get_geometry(pMonitor, &aRect);
+#endif
+ return AbsoluteScreenPixelRectangle(aRect.x, aRect.y, aRect.x + aRect.width, aRect.y + aRect.height);
+ }
+
+
+class GtkInstanceWindow : public GtkInstanceContainer, public virtual weld::Window
+{
+private:
+ GtkWindow* m_pWindow;
+ rtl::Reference<SalGtkXWindow> m_xWindow; //uno api
+ gulong m_nToplevelFocusChangedSignalId;
+protected:
+ std::optional<Point> m_aPosWhileInvis; //tdf#146648 store last known position when visible to return as pos if hidden
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void implResetDefault(GtkWidget *pWidget, gpointer user_data)
+ {
+ if (GTK_IS_BUTTON(pWidget))
+ g_object_set(G_OBJECT(pWidget), "has-default", false, nullptr);
+ if (GTK_IS_CONTAINER(pWidget))
+ gtk_container_forall(GTK_CONTAINER(pWidget), implResetDefault, user_data);
+ }
+
+ void recursively_unset_default_buttons()
+ {
+ implResetDefault(GTK_WIDGET(m_pWindow), nullptr);
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean help_pressed(GtkAccelGroup*, GObject*, guint, GdkModifierType, gpointer widget)
+ {
+ GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
+ pThis->help();
+ return true;
+ }
+#endif
+
+ static void signalToplevelFocusChanged(GtkWindow*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
+ pThis->signal_container_focus_changed();
+ }
+
+ bool isPositioningAllowed() const
+ {
+ // no X/Y positioning under Wayland
+ GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
+ return !DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay);
+ }
+
+protected:
+ void help();
+public:
+ GtkInstanceWindow(GtkWindow* pWindow, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ : GtkInstanceContainer(GTK_CONTAINER(pWindow), pBuilder, bTakeOwnership)
+#else
+ : GtkInstanceContainer(GTK_WIDGET(pWindow), pBuilder, bTakeOwnership)
+#endif
+ , m_pWindow(pWindow)
+ , m_nToplevelFocusChangedSignalId(0)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ const bool bIsFrameWeld = pBuilder == nullptr;
+ if (!bIsFrameWeld)
+ {
+ //hook up F1 to show help
+ GtkAccelGroup *pGroup = gtk_accel_group_new();
+ GClosure* closure = g_cclosure_new(G_CALLBACK(help_pressed), this, nullptr);
+ gtk_accel_group_connect(pGroup, GDK_KEY_F1, static_cast<GdkModifierType>(0), GTK_ACCEL_LOCKED, closure);
+ gtk_window_add_accel_group(pWindow, pGroup);
+ }
+#endif
+ }
+
+ virtual void set_title(const OUString& rTitle) override
+ {
+ ::set_title(m_pWindow, rTitle);
+ }
+
+ virtual OUString get_title() const override
+ {
+ return ::get_title(m_pWindow);
+ }
+
+ virtual css::uno::Reference<css::awt::XWindow> GetXWindow() override
+ {
+ if (!m_xWindow.is())
+ m_xWindow.set(new SalGtkXWindow(this, m_pWidget));
+ return m_xWindow;
+ }
+
+ virtual void set_modal(bool bModal) override
+ {
+ gtk_window_set_modal(m_pWindow, bModal);
+ }
+
+ virtual bool get_modal() const override
+ {
+ return gtk_window_get_modal(m_pWindow);
+ }
+
+ virtual void resize_to_request() override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_window_set_default_size(m_pWindow, 1, 1);
+#else
+ gtk_window_resize(m_pWindow, 1, 1);
+#endif
+ }
+
+ virtual void window_move(int x, int y) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_window_move(m_pWindow, x, y);
+#else
+ (void)x;
+ (void)y;
+#endif
+ }
+
+ virtual SystemEnvData get_system_data() const override
+ {
+ GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(GTK_WIDGET(m_pWindow));
+ assert(pFrame && "nothing should call this impl, yet anyway, if ever, except on result of GetFrameWeld()");
+ const SystemEnvData* pEnvData = pFrame->GetSystemData();
+ assert(pEnvData);
+ return *pEnvData;
+ }
+
+ virtual Size get_size() const override
+ {
+ int current_width, current_height;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_window_get_size(m_pWindow, &current_width, &current_height);
+#else
+ gtk_window_get_default_size(m_pWindow, &current_width, &current_height);
+#endif
+ return Size(current_width, current_height);
+ }
+
+ virtual Point get_position() const override
+ {
+ if (m_aPosWhileInvis)
+ {
+ assert(!get_visible());
+ return *m_aPosWhileInvis;
+ }
+
+ int current_x(0), current_y(0);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_window_get_position(m_pWindow, &current_x, &current_y);
+#endif
+ return Point(current_x, current_y);
+ }
+
+ virtual void show() override
+ {
+ m_aPosWhileInvis.reset();
+ GtkInstanceContainer::show();
+ }
+
+ virtual void hide() override
+ {
+ if (is_visible())
+ m_aPosWhileInvis = get_position();
+ GtkInstanceContainer::hide();
+ }
+
+ virtual AbsoluteScreenPixelRectangle get_monitor_workarea() const override
+ {
+ return ::get_monitor_workarea(GTK_WIDGET(m_pWindow));
+ }
+
+ virtual void set_centered_on_parent(bool bTrackGeometryRequests) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (bTrackGeometryRequests)
+ gtk_window_set_position(m_pWindow, GTK_WIN_POS_CENTER_ALWAYS);
+ else
+ gtk_window_set_position(m_pWindow, GTK_WIN_POS_CENTER_ON_PARENT);
+#else
+ (void)bTrackGeometryRequests;
+#endif
+ }
+
+ virtual bool get_resizable() const override
+ {
+ return gtk_window_get_resizable(m_pWindow);
+ }
+
+ virtual bool has_toplevel_focus() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_window_is_active(m_pWindow);
+#else
+ return gtk_window_has_toplevel_focus(m_pWindow);
+#endif
+ }
+
+ virtual void present() override
+ {
+ gtk_window_present(m_pWindow);
+ }
+
+ virtual void change_default_widget(weld::Widget* pOld, weld::Widget* pNew) override
+ {
+ GtkInstanceWidget* pGtkNew = dynamic_cast<GtkInstanceWidget*>(pNew);
+ GtkWidget* pWidgetNew = pGtkNew ? pGtkNew->getWidget() : nullptr;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_window_set_default_widget(m_pWindow, pWidgetNew);
+ (void)pOld;
+#else
+ GtkInstanceWidget* pGtkOld = dynamic_cast<GtkInstanceWidget*>(pOld);
+ GtkWidget* pWidgetOld = pGtkOld ? pGtkOld->getWidget() : nullptr;
+ if (pWidgetOld)
+ g_object_set(G_OBJECT(pWidgetOld), "has-default", false, nullptr);
+ else
+ recursively_unset_default_buttons();
+ if (pWidgetNew)
+ g_object_set(G_OBJECT(pWidgetNew), "has-default", true, nullptr);
+#endif
+ }
+
+ virtual bool is_default_widget(const weld::Widget* pCandidate) const override
+ {
+ const GtkInstanceWidget* pGtkCandidate = dynamic_cast<const GtkInstanceWidget*>(pCandidate);
+ GtkWidget* pWidget = pGtkCandidate ? pGtkCandidate->getWidget() : nullptr;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return pWidget && gtk_window_get_default_widget(m_pWindow) == pWidget;
+#else
+ gboolean has_default(false);
+ if (pWidget)
+ g_object_get(G_OBJECT(pWidget), "has-default", &has_default, nullptr);
+ return has_default;
+#endif
+ }
+
+ virtual void set_window_state(const OUString& rStr) override
+ {
+ const vcl::WindowData aData(rStr);
+ const auto nMask = aData.mask();
+ const auto nState = aData.state() & vcl::WindowState::SystemMask;
+
+ if ((nMask & vcl::WindowDataMask::Size) == vcl::WindowDataMask::Size)
+ {
+ gtk_window_set_default_size(m_pWindow, aData.width(), aData.height());
+ }
+ if (nMask & vcl::WindowDataMask::State)
+ {
+ if (nState & vcl::WindowState::Maximized)
+ gtk_window_maximize(m_pWindow);
+ else
+ gtk_window_unmaximize(m_pWindow);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (isPositioningAllowed() && ((nMask & vcl::WindowDataMask::Pos) == vcl::WindowDataMask::Pos))
+ {
+ gtk_window_move(m_pWindow, aData.x(), aData.y());
+ }
+#endif
+ }
+
+ virtual OUString get_window_state(vcl::WindowDataMask nMask) const override
+ {
+ bool bPositioningAllowed = isPositioningAllowed();
+
+ vcl::WindowData aData;
+ vcl::WindowDataMask nAvailable = vcl::WindowDataMask::State | vcl::WindowDataMask::Size;
+ if (bPositioningAllowed)
+ nAvailable |= vcl::WindowDataMask::Pos;
+ aData.setMask(nMask & nAvailable);
+
+ if (nMask & vcl::WindowDataMask::State)
+ {
+ vcl::WindowState nState = vcl::WindowState::Normal;
+ if (gtk_window_is_maximized(m_pWindow))
+ nState |= vcl::WindowState::Maximized;
+ aData.setState(nState);
+ }
+
+ if (bPositioningAllowed && (nMask & vcl::WindowDataMask::Pos))
+ aData.setPos(get_position());
+
+ if (nMask & vcl::WindowDataMask::Size)
+ aData.setSize(get_size());
+
+ return aData.toStr();
+ }
+
+ virtual void connect_container_focus_changed(const Link<Container&, void>& rLink) override
+ {
+ if (!m_nToplevelFocusChangedSignalId)
+ m_nToplevelFocusChangedSignalId = g_signal_connect(m_pWindow, "notify::has-toplevel-focus", G_CALLBACK(signalToplevelFocusChanged), this);
+ weld::Container::connect_container_focus_changed(rLink);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ if (m_nToplevelFocusChangedSignalId)
+ g_signal_handler_block(m_pWidget, m_nToplevelFocusChangedSignalId);
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::enable_notify_events();
+ if (m_nToplevelFocusChangedSignalId)
+ g_signal_handler_unblock(m_pWidget, m_nToplevelFocusChangedSignalId);
+ }
+
+ virtual VclPtr<VirtualDevice> screenshot() override
+ {
+ // detect if we have to manually setup its size
+ bool bAlreadyRealized = gtk_widget_get_realized(GTK_WIDGET(m_pWindow));
+ // has to be visible for draw to work
+ bool bAlreadyVisible = gtk_widget_get_visible(GTK_WIDGET(m_pWindow));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!bAlreadyVisible)
+ {
+ if (GTK_IS_DIALOG(m_pWindow))
+ sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pWindow))));
+ gtk_widget_show(GTK_WIDGET(m_pWindow));
+ }
+#endif
+
+ if (!bAlreadyRealized)
+ {
+ GtkAllocation allocation;
+ gtk_widget_realize(GTK_WIDGET(m_pWindow));
+ gtk_widget_get_allocation(GTK_WIDGET(m_pWindow), &allocation);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_size_allocate(GTK_WIDGET(m_pWindow), &allocation);
+#else
+ gtk_widget_size_allocate(GTK_WIDGET(m_pWindow), &allocation, 0);
+#endif
+ }
+
+ VclPtr<VirtualDevice> xOutput(VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA));
+ xOutput->SetOutputSizePixel(get_size());
+ cairo_surface_t* pSurface = get_underlying_cairo_surface(*xOutput);
+ cairo_t* cr = cairo_create(pSurface);
+
+ Point aOffset = get_csd_offset(GTK_WIDGET(m_pWindow));
+
+ cairo_translate(cr, -aOffset.X(), -aOffset.Y());
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_draw(GTK_WIDGET(m_pWindow), cr);
+#else
+ GtkSnapshot* pSnapshot = gtk_snapshot_new();
+ GtkWidgetClass* pWidgetClass = GTK_WIDGET_GET_CLASS(GTK_WIDGET(m_pWindow));
+ pWidgetClass->snapshot(GTK_WIDGET(m_pWindow), pSnapshot);
+ GskRenderNode* pNode = gtk_snapshot_free_to_node(pSnapshot);
+ gsk_render_node_draw(pNode, cr);
+ gsk_render_node_unref(pNode);
+#endif
+
+ cairo_destroy(cr);
+
+ if (!bAlreadyVisible)
+ gtk_widget_hide(GTK_WIDGET(m_pWindow));
+ if (!bAlreadyRealized)
+ gtk_widget_unrealize(GTK_WIDGET(m_pWindow));
+
+ return xOutput;
+ }
+
+ virtual weld::ScreenShotCollection collect_screenshot_data() override
+ {
+ weld::ScreenShotCollection aRet;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pWindow));
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ do_collect_screenshot_data(pChild, &aRet);
+ }
+#else
+ gtk_container_foreach(GTK_CONTAINER(m_pWindow), do_collect_screenshot_data, &aRet);
+#endif
+
+ return aRet;
+ }
+
+ virtual const vcl::ILibreOfficeKitNotifier* GetLOKNotifier() override
+ {
+ // dummy implementation
+ return nullptr;
+ }
+
+ virtual ~GtkInstanceWindow() override
+ {
+ if (m_nToplevelFocusChangedSignalId)
+ g_signal_handler_disconnect(m_pWindow, m_nToplevelFocusChangedSignalId);
+ if (m_xWindow.is())
+ m_xWindow->clear();
+ }
+};
+
+class GtkInstanceDialog;
+
+struct DialogRunner
+{
+ GtkWindow* m_pDialog;
+ GtkInstanceDialog *m_pInstance;
+ gint m_nResponseId;
+ GMainLoop *m_pLoop;
+ VclPtr<vcl::Window> m_xFrameWindow;
+ int m_nModalDepth;
+
+ DialogRunner(GtkWindow* pDialog, GtkInstanceDialog* pInstance)
+ : m_pDialog(pDialog)
+ , m_pInstance(pInstance)
+ , m_nResponseId(GTK_RESPONSE_NONE)
+ , m_pLoop(nullptr)
+ , m_nModalDepth(0)
+ {
+ GtkWindow* pParent = gtk_window_get_transient_for(m_pDialog);
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(GTK_WIDGET(pParent)) : nullptr;
+ m_xFrameWindow = pFrame ? pFrame->GetWindow() : nullptr;
+ }
+
+ bool loop_is_running() const
+ {
+ return m_pLoop && g_main_loop_is_running(m_pLoop);
+ }
+
+ void loop_quit()
+ {
+ if (g_main_loop_is_running(m_pLoop))
+ g_main_loop_quit(m_pLoop);
+ }
+
+ static void signal_response(GtkDialog*, gint nResponseId, gpointer data);
+ static void signal_cancel(GtkAssistant*, gpointer data);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signal_delete(GtkDialog* pDialog, GdkEventAny*, gpointer data)
+ {
+ DialogRunner* pThis = static_cast<DialogRunner*>(data);
+ if (GTK_IS_ASSISTANT(pThis->m_pDialog))
+ {
+ // An assistant isn't a dialog, but we want to treat it like one
+ signal_response(pDialog, GTK_RESPONSE_DELETE_EVENT, data);
+ }
+ else
+ pThis->loop_quit();
+ return true; /* Do not destroy */
+ }
+#endif
+
+ static void signal_destroy(GtkDialog*, gpointer data)
+ {
+ DialogRunner* pThis = static_cast<DialogRunner*>(data);
+ pThis->loop_quit();
+ }
+
+ void inc_modal_count()
+ {
+ if (m_xFrameWindow)
+ {
+ m_xFrameWindow->IncModalCount();
+ if (m_nModalDepth == 0)
+ m_xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(true);
+ ++m_nModalDepth;
+ }
+ }
+
+ void dec_modal_count()
+ {
+ if (m_xFrameWindow)
+ {
+ m_xFrameWindow->DecModalCount();
+ --m_nModalDepth;
+ if (m_nModalDepth == 0)
+ m_xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(false);
+ }
+ }
+
+ // same as gtk_dialog_run except that unmap doesn't auto-respond
+ // so we can hide the dialog and restore it without a response getting
+ // triggered
+ gint run()
+ {
+ g_object_ref(m_pDialog);
+
+ inc_modal_count();
+
+ bool bWasModal = gtk_window_get_modal(m_pDialog);
+ if (!bWasModal)
+ gtk_window_set_modal(m_pDialog, true);
+
+ if (!gtk_widget_get_visible(GTK_WIDGET(m_pDialog)))
+ gtk_widget_show(GTK_WIDGET(m_pDialog));
+
+ gulong nSignalResponseId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signal_response), this) : 0;
+ gulong nSignalCancelId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signal_cancel), this) : 0;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signal_delete), this);
+#endif
+ gulong nSignalDestroyId = g_signal_connect(m_pDialog, "destroy", G_CALLBACK(signal_destroy), this);
+
+ m_pLoop = g_main_loop_new(nullptr, false);
+ m_nResponseId = GTK_RESPONSE_NONE;
+
+ main_loop_run(m_pLoop);
+
+ g_main_loop_unref(m_pLoop);
+
+ m_pLoop = nullptr;
+
+ if (!bWasModal)
+ gtk_window_set_modal(m_pDialog, false);
+
+ if (nSignalResponseId)
+ g_signal_handler_disconnect(m_pDialog, nSignalResponseId);
+ if (nSignalCancelId)
+ g_signal_handler_disconnect(m_pDialog, nSignalCancelId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pDialog, nSignalDeleteId);
+#endif
+ g_signal_handler_disconnect(m_pDialog, nSignalDestroyId);
+
+ dec_modal_count();
+
+ g_object_unref(m_pDialog);
+
+ return m_nResponseId;
+ }
+
+ ~DialogRunner()
+ {
+ if (m_xFrameWindow && m_nModalDepth)
+ {
+ // if, like the calc validation dialog does, the modality was
+ // toggled off during execution ensure that on cleanup the parent
+ // is left in the state it was found
+ while (m_nModalDepth++ < 0)
+ m_xFrameWindow->IncModalCount();
+ }
+ }
+};
+
+}
+
+typedef std::set<GtkWidget*> winset;
+
+namespace
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ void collectVisibleChildren(GtkWidget* pTop, winset& rVisibleWidgets)
+ {
+ for (GtkWidget* pChild = gtk_widget_get_first_child(pTop);
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ if (!gtk_widget_get_visible(pChild))
+ continue;
+ rVisibleWidgets.insert(pChild);
+ collectVisibleChildren(pChild, rVisibleWidgets);
+ }
+ }
+#endif
+
+ void hideUnless(GtkWidget* pTop, const winset& rVisibleWidgets,
+ std::vector<GtkWidget*> &rWasVisibleWidgets)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (GtkWidget* pChild = gtk_widget_get_first_child(pTop);
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ if (!gtk_widget_get_visible(pChild))
+ continue;
+ if (rVisibleWidgets.find(pChild) == rVisibleWidgets.end())
+ {
+ g_object_ref(pChild);
+ rWasVisibleWidgets.emplace_back(pChild);
+ gtk_widget_hide(pChild);
+ }
+ else
+ {
+ hideUnless(pChild, rVisibleWidgets, rWasVisibleWidgets);
+ }
+ }
+#else
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pTop));
+ for (GList* pEntry = g_list_first(pChildren); pEntry; pEntry = g_list_next(pEntry))
+ {
+ GtkWidget* pChild = static_cast<GtkWidget*>(pEntry->data);
+ if (!gtk_widget_get_visible(pChild))
+ continue;
+ if (rVisibleWidgets.find(pChild) == rVisibleWidgets.end())
+ {
+ g_object_ref(pChild);
+ rWasVisibleWidgets.emplace_back(pChild);
+ gtk_widget_hide(pChild);
+ }
+ else if (GTK_IS_CONTAINER(pChild))
+ {
+ hideUnless(pChild, rVisibleWidgets, rWasVisibleWidgets);
+ }
+ }
+ g_list_free(pChildren);
+#endif
+ }
+
+class GtkInstanceButton;
+
+class GtkInstanceDialog : public GtkInstanceWindow, public virtual weld::Dialog
+{
+private:
+ GtkWindow* m_pDialog;
+ DialogRunner m_aDialogRun;
+ std::shared_ptr<weld::DialogController> m_xDialogController;
+ // Used to keep ourself alive during a runAsync(when doing runAsync without a DialogController)
+ std::shared_ptr<weld::Dialog> m_xRunAsyncSelf;
+ std::function<void(sal_Int32)> m_aFunc;
+ gulong m_nCloseSignalId;
+ gulong m_nResponseSignalId;
+ gulong m_nCancelSignalId;
+ gulong m_nSignalDeleteId;
+
+ // for calc ref dialog that shrink to range selection widgets and resize back
+ GtkWidget* m_pRefEdit;
+ std::vector<GtkWidget*> m_aHiddenWidgets; // vector of hidden Controls
+ int m_nOldEditWidth; // Original width of the input field
+ int m_nOldEditWidthReq; // Original width request of the input field
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ int m_nOldBorderWidth; // border width for expanded dialog
+#endif
+
+ void signal_close()
+ {
+ close(true);
+ }
+
+ static void signalClose(GtkWidget*, gpointer widget)
+ {
+ GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
+ pThis->signal_close();
+ }
+
+ static void signalAsyncResponse(GtkWidget*, gint ret, gpointer widget)
+ {
+ GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
+ pThis->asyncresponse(ret);
+ }
+
+ static void signalAsyncCancel(GtkAssistant*, gpointer widget)
+ {
+ GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
+ // make esc in an assistant act as if cancel button was pressed
+ pThis->close(false);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalAsyncDelete(GtkWidget* pDialog, GdkEventAny*, gpointer widget)
+ {
+ GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
+ if (GTK_IS_ASSISTANT(pThis->m_pDialog))
+ {
+ // An assistant isn't a dialog, but we want to treat it like one
+ signalAsyncResponse(pDialog, GTK_RESPONSE_DELETE_EVENT, widget);
+ }
+ return true; /* Do not destroy */
+ }
+#endif
+
+ static int GtkToVcl(int ret)
+ {
+ if (ret == GTK_RESPONSE_OK)
+ ret = RET_OK;
+ else if (ret == GTK_RESPONSE_CANCEL)
+ ret = RET_CANCEL;
+ else if (ret == GTK_RESPONSE_DELETE_EVENT)
+ ret = RET_CANCEL;
+ else if (ret == GTK_RESPONSE_CLOSE)
+ ret = RET_CLOSE;
+ else if (ret == GTK_RESPONSE_YES)
+ ret = RET_YES;
+ else if (ret == GTK_RESPONSE_NO)
+ ret = RET_NO;
+ else if (ret == GTK_RESPONSE_HELP)
+ ret = RET_HELP;
+ return ret;
+ }
+
+ static int VclToGtk(int nResponse)
+ {
+ if (nResponse == RET_OK)
+ return GTK_RESPONSE_OK;
+ else if (nResponse == RET_CANCEL)
+ return GTK_RESPONSE_CANCEL;
+ else if (nResponse == RET_CLOSE)
+ return GTK_RESPONSE_CLOSE;
+ else if (nResponse == RET_YES)
+ return GTK_RESPONSE_YES;
+ else if (nResponse == RET_NO)
+ return GTK_RESPONSE_NO;
+ else if (nResponse == RET_HELP)
+ return GTK_RESPONSE_HELP;
+ return nResponse;
+ }
+
+ void asyncresponse(gint ret);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void signalActivate(GtkMenuItem*, gpointer data)
+ {
+ bool* pActivate = static_cast<bool*>(data);
+ *pActivate = true;
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool signal_screenshot_popup_menu(const GdkEventButton* pEvent)
+ {
+ GtkWidget *pMenu = gtk_menu_new();
+
+ GtkWidget* pMenuItem = gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(VclResId(SV_BUTTONTEXT_SCREENSHOT)).getStr());
+ gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);
+ bool bActivate(false);
+ g_signal_connect(pMenuItem, "activate", G_CALLBACK(signalActivate), &bActivate);
+ gtk_widget_show(pMenuItem);
+
+ int button, event_time;
+ if (pEvent)
+ {
+ button = pEvent->button;
+ event_time = pEvent->time;
+ }
+ else
+ {
+ button = 0;
+ event_time = gtk_get_current_event_time();
+ }
+
+ gtk_menu_attach_to_widget(GTK_MENU(pMenu), GTK_WIDGET(m_pDialog), nullptr);
+
+ GMainLoop* pLoop = g_main_loop_new(nullptr, true);
+ gulong nSignalId = g_signal_connect_swapped(G_OBJECT(pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
+
+ gtk_menu_popup(GTK_MENU(pMenu), nullptr, nullptr, nullptr, nullptr, button, event_time);
+
+ if (g_main_loop_is_running(pLoop))
+ main_loop_run(pLoop);
+
+ g_main_loop_unref(pLoop);
+ g_signal_handler_disconnect(pMenu, nSignalId);
+ gtk_menu_detach(GTK_MENU(pMenu));
+
+ if (bActivate)
+ {
+ // open screenshot annotation dialog
+ VclAbstractDialogFactory* pFact = VclAbstractDialogFactory::Create();
+ VclPtr<AbstractScreenshotAnnotationDlg> xTmp = pFact->CreateScreenshotAnnotationDlg(*this);
+ ScopedVclPtr<AbstractScreenshotAnnotationDlg> xDialog(xTmp);
+ xDialog->Execute();
+ }
+
+ return false;
+ }
+#endif
+
+ static gboolean signalScreenshotPopupMenu(GtkWidget*, gpointer widget)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
+ return pThis->signal_screenshot_popup_menu(nullptr);
+#else
+ (void)widget;
+ return false;
+#endif
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalScreenshotButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_screenshot_button(pEvent);
+ }
+
+ bool signal_screenshot_button(GdkEventButton* pEvent)
+ {
+ if (gdk_event_triggers_context_menu(reinterpret_cast<GdkEvent*>(pEvent)) && pEvent->type == GDK_BUTTON_PRESS)
+ {
+ //if handled for context menu, stop processing
+ return signal_screenshot_popup_menu(pEvent);
+ }
+ return false;
+ }
+#endif
+
+public:
+ GtkInstanceDialog(GtkWindow* pDialog, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWindow(pDialog, pBuilder, bTakeOwnership)
+ , m_pDialog(pDialog)
+ , m_aDialogRun(pDialog, this)
+ , m_nResponseSignalId(0)
+ , m_nCancelSignalId(0)
+ , m_nSignalDeleteId(0)
+ , m_pRefEdit(nullptr)
+ , m_nOldEditWidth(0)
+ , m_nOldEditWidthReq(0)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_nOldBorderWidth(0)
+#endif
+ {
+ if (GTK_IS_DIALOG(m_pDialog) || GTK_IS_ASSISTANT(m_pDialog))
+ m_nCloseSignalId = g_signal_connect(m_pDialog, "close", G_CALLBACK(signalClose), this);
+ else
+ m_nCloseSignalId = 0;
+ const bool bScreenshotMode(officecfg::Office::Common::Misc::ScreenshotMode::get());
+ if (bScreenshotMode)
+ {
+ g_signal_connect(m_pDialog, "popup-menu", G_CALLBACK(signalScreenshotPopupMenu), this);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_connect(m_pDialog, "button-press-event", G_CALLBACK(signalScreenshotButton), this);
+#endif
+ }
+ }
+
+ virtual bool runAsync(std::shared_ptr<weld::DialogController> rDialogController, const std::function<void(sal_Int32)>& func) override
+ {
+ assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
+
+ m_xDialogController = rDialogController;
+ m_aFunc = func;
+
+ if (get_modal())
+ m_aDialogRun.inc_modal_count();
+ show();
+
+ m_nResponseSignalId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signalAsyncResponse), this) : 0;
+ m_nCancelSignalId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signalAsyncCancel), this) : 0;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);
+#endif
+
+ return true;
+ }
+
+ virtual bool runAsync(std::shared_ptr<Dialog> const & rxSelf, const std::function<void(sal_Int32)>& func) override
+ {
+ assert( rxSelf.get() == this );
+ assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
+
+ // In order to store a shared_ptr to ourself, we have to have been constructed by make_shared,
+ // which is that rxSelf enforces.
+ m_xRunAsyncSelf = rxSelf;
+ m_aFunc = func;
+
+ if (get_modal())
+ m_aDialogRun.inc_modal_count();
+ show();
+
+ m_nResponseSignalId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signalAsyncResponse), this) : 0;
+ m_nCancelSignalId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signalAsyncCancel), this) : 0;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);
+#endif
+
+ return true;
+ }
+
+ GtkInstanceButton* has_click_handler(int nResponse);
+
+ virtual int run() override;
+
+ virtual void show() override
+ {
+ if (gtk_widget_get_visible(m_pWidget))
+ return;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_DIALOG(m_pDialog))
+ sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
+#endif
+ GtkInstanceWindow::show();
+ }
+
+ virtual void set_modal(bool bModal) override
+ {
+ if (get_modal() == bModal)
+ return;
+ GtkInstanceWindow::set_modal(bModal);
+ /* if change the dialog modality while its running, then also change the parent LibreOffice window
+ modal count, we typically expect the dialog modality to be restored to its original state
+
+ This change modality while running case is for...
+
+ a) the calc/chart dialogs which put up an extra range chooser
+ dialog, hides the original, the user can select a range of cells and
+ on completion the original dialog is restored
+
+ b) the validity dialog in calc
+ */
+ // tdf#135567 we know we are running in the sync case if loop_is_running is true
+ // but for the async case we instead check for m_xDialogController which is set in
+ // runAsync and cleared in asyncresponse
+ if (m_aDialogRun.loop_is_running() || m_xDialogController)
+ {
+ if (bModal)
+ m_aDialogRun.inc_modal_count();
+ else
+ m_aDialogRun.dec_modal_count();
+ }
+ }
+
+ virtual void response(int nResponse) override;
+
+ virtual void add_button(const OUString& rText, int nResponse, const OUString& rHelpId) override
+ {
+ GtkWidget* pWidget = gtk_dialog_add_button(GTK_DIALOG(m_pDialog), MapToGtkAccelerator(rText).getStr(), VclToGtk(nResponse));
+ if (!rHelpId.isEmpty())
+ ::set_help_id(pWidget, rHelpId);
+ }
+
+ virtual void set_default_response(int nResponse) override
+ {
+ gtk_dialog_set_default_response(GTK_DIALOG(m_pDialog), VclToGtk(nResponse));
+ }
+
+ virtual GtkButton* get_widget_for_response(int nGtkResponse)
+ {
+ return GTK_BUTTON(gtk_dialog_get_widget_for_response(GTK_DIALOG(m_pDialog), nGtkResponse));
+ }
+
+ virtual weld::Button* weld_widget_for_response(int nVclResponse) override;
+
+ virtual Container* weld_content_area() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return new GtkInstanceContainer(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog))), m_pBuilder, false);
+#else
+ return new GtkInstanceContainer(gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog)), m_pBuilder, false);
+#endif
+ }
+
+ virtual void collapse(weld::Widget* pEdit, weld::Widget* pButton) override
+ {
+ GtkInstanceWidget* pVclEdit = dynamic_cast<GtkInstanceWidget*>(pEdit);
+ assert(pVclEdit);
+ GtkInstanceWidget* pVclButton = dynamic_cast<GtkInstanceWidget*>(pButton);
+
+ GtkWidget* pRefEdit = pVclEdit->getWidget();
+ GtkWidget* pRefBtn = pVclButton ? pVclButton->getWidget() : nullptr;
+
+ m_nOldEditWidth = gtk_widget_get_allocated_width(pRefEdit);
+
+ gtk_widget_get_size_request(pRefEdit, &m_nOldEditWidthReq, nullptr);
+
+ //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;
+ GtkWidget *pContentArea = gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog));
+ for (GtkWidget *pCandidate = pRefEdit;
+ pCandidate && pCandidate != pContentArea && gtk_widget_get_visible(pCandidate);
+ pCandidate = gtk_widget_get_parent(pCandidate))
+ {
+ aVisibleWidgets.insert(pCandidate);
+ }
+#if GTK_CHECK_VERSION(4, 0, 0)
+ collectVisibleChildren(pRefEdit, aVisibleWidgets);
+#endif
+ if (pRefBtn)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ collectVisibleChildren(pRefBtn, aVisibleWidgets);
+#endif
+ //same again with pRefBtn, except stop if there's a
+ //shared parent in the existing widgets
+ for (GtkWidget *pCandidate = pRefBtn;
+ pCandidate && pCandidate != pContentArea && gtk_widget_get_visible(pCandidate);
+ pCandidate = gtk_widget_get_parent(pCandidate))
+ {
+ if (aVisibleWidgets.insert(pCandidate).second)
+ break;
+ }
+ }
+
+ //hide everything except the aVisibleWidgets
+ hideUnless(pContentArea, aVisibleWidgets, m_aHiddenWidgets);
+ gtk_widget_set_size_request(pRefEdit, m_nOldEditWidth, -1);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_nOldBorderWidth = gtk_container_get_border_width(GTK_CONTAINER(m_pDialog));
+ gtk_container_set_border_width(GTK_CONTAINER(m_pDialog), 0);
+ if (GtkWidget* pActionArea = gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog)))
+ gtk_widget_hide(pActionArea);
+ gtk_widget_show_all(pRefEdit);
+ if (pRefBtn)
+ gtk_widget_show_all(pRefBtn);
+#else
+ if (GtkWidget* pActionArea = gtk_dialog_get_header_bar(GTK_DIALOG(m_pDialog)))
+ gtk_widget_hide(pActionArea);
+#endif
+
+ // calc's insert->function is springing back to its original size if the ref-button
+ // is used to shrink the dialog down and then the user clicks in the calc area to do
+ // the selection
+ bool bWorkaroundSizeSpringingBack = DLSYM_GDK_IS_WAYLAND_DISPLAY(gtk_widget_get_display(m_pWidget));
+ if (bWorkaroundSizeSpringingBack)
+ gtk_widget_unmap(GTK_WIDGET(m_pDialog));
+
+ resize_to_request();
+
+ if (bWorkaroundSizeSpringingBack)
+ gtk_widget_map(GTK_WIDGET(m_pDialog));
+
+ m_pRefEdit = pRefEdit;
+ }
+
+ virtual void undo_collapse() override
+ {
+ // All others: Show();
+ for (GtkWidget* pWindow : m_aHiddenWidgets)
+ {
+ gtk_widget_show(pWindow);
+ g_object_unref(pWindow);
+ }
+ m_aHiddenWidgets.clear();
+
+ gtk_widget_set_size_request(m_pRefEdit, m_nOldEditWidthReq, -1);
+ m_pRefEdit = nullptr;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_set_border_width(GTK_CONTAINER(m_pDialog), m_nOldBorderWidth);
+ if (GtkWidget* pActionArea = gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog)))
+ gtk_widget_show(pActionArea);
+#else
+ if (GtkWidget* pActionArea = gtk_dialog_get_header_bar(GTK_DIALOG(m_pDialog)))
+ gtk_widget_show(pActionArea);
+#endif
+ resize_to_request();
+ present();
+ }
+
+ void close(bool bCloseSignal);
+
+ virtual void SetInstallLOKNotifierHdl(const Link<void*, vcl::ILibreOfficeKitNotifier*>&) override
+ {
+ //not implemented for the gtk variant
+ }
+
+ virtual ~GtkInstanceDialog() override
+ {
+ if (!m_aHiddenWidgets.empty())
+ {
+ for (GtkWidget* pWindow : m_aHiddenWidgets)
+ g_object_unref(pWindow);
+ m_aHiddenWidgets.clear();
+ }
+
+ if (m_nCloseSignalId)
+ g_signal_handler_disconnect(m_pDialog, m_nCloseSignalId);
+ assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
+ }
+};
+
+}
+
+void DialogRunner::signal_response(GtkDialog*, gint nResponseId, gpointer data)
+{
+ DialogRunner* pThis = static_cast<DialogRunner*>(data);
+
+ // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
+ if (nResponseId == GTK_RESPONSE_DELETE_EVENT)
+ {
+ pThis->m_pInstance->close(false);
+ return;
+ }
+
+ pThis->m_nResponseId = nResponseId;
+ pThis->loop_quit();
+}
+
+void DialogRunner::signal_cancel(GtkAssistant*, gpointer data)
+{
+ DialogRunner* pThis = static_cast<DialogRunner*>(data);
+
+ // make esc in an assistant act as if cancel button was pressed
+ pThis->m_pInstance->close(false);
+}
+
+namespace {
+
+class GtkInstanceMessageDialog : public GtkInstanceDialog, public virtual weld::MessageDialog
+{
+private:
+ GtkMessageDialog* m_pMessageDialog;
+public:
+ GtkInstanceMessageDialog(GtkMessageDialog* pMessageDialog, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceDialog(GTK_WINDOW(pMessageDialog), pBuilder, bTakeOwnership)
+ , m_pMessageDialog(pMessageDialog)
+ {
+ }
+
+ virtual void set_primary_text(const OUString& rText) override
+ {
+ ::set_primary_text(m_pMessageDialog, rText);
+ }
+
+ virtual OUString get_primary_text() const override
+ {
+ return ::get_primary_text(m_pMessageDialog);
+ }
+
+ virtual void set_secondary_text(const OUString& rText) override
+ {
+ ::set_secondary_text(m_pMessageDialog, rText);
+ }
+
+ virtual OUString get_secondary_text() const override
+ {
+ return ::get_secondary_text(m_pMessageDialog);
+ }
+
+ virtual Container* weld_message_area() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return new GtkInstanceContainer(GTK_CONTAINER(gtk_message_dialog_get_message_area(m_pMessageDialog)), m_pBuilder, false);
+#else
+ return new GtkInstanceContainer(gtk_message_dialog_get_message_area(m_pMessageDialog), m_pBuilder, false);
+#endif
+ }
+};
+
+void set_label_wrap(GtkLabel* pLabel, bool bWrap)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_label_set_wrap(pLabel, bWrap);
+#else
+ gtk_label_set_line_wrap(pLabel, bWrap);
+#endif
+}
+
+class GtkInstanceAssistant : public GtkInstanceDialog, public virtual weld::Assistant
+{
+private:
+ GtkAssistant* m_pAssistant;
+ GtkWidget* m_pSidebar;
+ GtkWidget* m_pSidebarEventBox;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkButtonBox* m_pButtonBox;
+#else
+ GtkBox* m_pButtonBox;
+ GtkEventController* m_pSidebarClickController;
+#endif
+ GtkButton* m_pHelp;
+ GtkButton* m_pBack;
+ GtkButton* m_pNext;
+ GtkButton* m_pFinish;
+ GtkButton* m_pCancel;
+ gulong m_nButtonPressSignalId;
+ std::vector<std::unique_ptr<GtkInstanceContainer>> m_aPages;
+ std::map<OUString, bool> m_aNotClickable;
+
+ int find_page(std::u16string_view ident) const
+ {
+ int nPages = gtk_assistant_get_n_pages(m_pAssistant);
+ for (int i = 0; i < nPages; ++i)
+ {
+ GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, i);
+ OUString sBuildableName = ::get_buildable_id(GTK_BUILDABLE(pPage));
+ if (sBuildableName == ident)
+ return i;
+ }
+ return -1;
+ }
+
+ static void wrap_sidebar_label(GtkWidget *pWidget, gpointer /*user_data*/)
+ {
+ if (GTK_IS_LABEL(pWidget))
+ {
+ ::set_label_wrap(GTK_LABEL(pWidget), true);
+ gtk_label_set_width_chars(GTK_LABEL(pWidget), 22);
+ gtk_label_set_max_width_chars(GTK_LABEL(pWidget), 22);
+ }
+ }
+
+ static void find_sidebar(GtkWidget *pWidget, gpointer user_data)
+ {
+ OUString sBuildableName = ::get_buildable_id(GTK_BUILDABLE(pWidget));
+ if (sBuildableName == "sidebar")
+ {
+ GtkWidget **ppSidebar = static_cast<GtkWidget**>(user_data);
+ *ppSidebar = pWidget;
+ }
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_CONTAINER(pWidget))
+ gtk_container_forall(GTK_CONTAINER(pWidget), find_sidebar, user_data);
+#endif
+ }
+
+ static void signalHelpClicked(GtkButton*, gpointer widget)
+ {
+ GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
+ pThis->signal_help_clicked();
+ }
+
+ void signal_help_clicked()
+ {
+ help();
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalButton(GtkGestureClick* /*pGesture*/, int /*n_press*/, gdouble x, gdouble y, gpointer widget)
+ {
+ GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_button(x, y);
+ }
+#else
+ static gboolean signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_button(pEvent->x, pEvent->y);
+ }
+#endif
+
+ bool signal_button(gtk_coord event_x, gtk_coord event_y)
+ {
+ int nNewCurrentPage = -1;
+
+ GtkAllocation allocation;
+
+ int nPageIndex = 0;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (GtkWidget* pWidget = gtk_widget_get_first_child(m_pSidebar);
+ pWidget; pWidget = gtk_widget_get_next_sibling(pWidget))
+ {
+#else
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pSidebar));
+ for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
+ {
+ GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
+#endif
+ if (!gtk_widget_get_visible(pWidget))
+ continue;
+
+ gtk_widget_get_allocation(pWidget, &allocation);
+
+ gtk_coord dest_x1, dest_y1;
+ gtk_widget_translate_coordinates(pWidget,
+ m_pSidebarEventBox,
+ 0,
+ 0,
+ &dest_x1,
+ &dest_y1);
+
+ gtk_coord dest_x2, dest_y2;
+ gtk_widget_translate_coordinates(pWidget,
+ m_pSidebarEventBox,
+ allocation.width,
+ allocation.height,
+ &dest_x2,
+ &dest_y2);
+
+
+ if (event_x >= dest_x1 && event_x <= dest_x2 && event_y >= dest_y1 && event_y <= dest_y2)
+ {
+ nNewCurrentPage = nPageIndex;
+ break;
+ }
+
+ ++nPageIndex;
+ }
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_list_free(pChildren);
+#endif
+
+ if (nNewCurrentPage != -1 && nNewCurrentPage != get_current_page())
+ {
+ OUString sIdent = get_page_ident(nNewCurrentPage);
+ if (!m_aNotClickable[sIdent] && !signal_jump_page(sIdent))
+ set_current_page(nNewCurrentPage);
+ }
+
+ return false;
+ }
+
+public:
+ GtkInstanceAssistant(GtkAssistant* pAssistant, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceDialog(GTK_WINDOW(pAssistant), pBuilder, bTakeOwnership)
+ , m_pAssistant(pAssistant)
+ , m_pSidebar(nullptr)
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_pSidebarClickController(nullptr)
+#endif
+ , m_nButtonPressSignalId(0)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_pButtonBox = GTK_BUTTON_BOX(gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL));
+ gtk_button_box_set_layout(m_pButtonBox, GTK_BUTTONBOX_END);
+ gtk_box_set_spacing(GTK_BOX(m_pButtonBox), 6);
+#else
+ m_pButtonBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
+#endif
+
+ m_pBack = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Back)).getStr()));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_can_default(GTK_WIDGET(m_pBack), true);
+#endif
+ ::set_buildable_id(GTK_BUILDABLE(m_pBack), "previous");
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_append(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pBack));
+#else
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pBack), false, false, 0);
+#endif
+
+ m_pNext = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Next)).getStr()));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_can_default(GTK_WIDGET(m_pNext), true);
+#endif
+ ::set_buildable_id(GTK_BUILDABLE(m_pNext), "next");
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_append(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pNext));
+#else
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pNext), false, false, 0);
+#endif
+
+ m_pCancel = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Cancel)).getStr()));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_can_default(GTK_WIDGET(m_pCancel), true);
+#endif
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_append(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pCancel));
+#else
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pCancel), false, false, 0);
+#endif
+
+ m_pFinish = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Finish)).getStr()));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_can_default(GTK_WIDGET(m_pFinish), true);
+#endif
+ ::set_buildable_id(GTK_BUILDABLE(m_pFinish), "finish");
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_append(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pFinish));
+#else
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pFinish), false, false, 0);
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_pHelp = GTK_BUTTON(gtk_button_new_from_icon_name("help-browser-symbolic"));
+#else
+ m_pHelp = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Help)).getStr()));
+#endif
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_can_default(GTK_WIDGET(m_pHelp), true);
+#endif
+ g_signal_connect(m_pHelp, "clicked", G_CALLBACK(signalHelpClicked), this);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_prepend(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pHelp));
+ gtk_widget_set_hexpand(GTK_WIDGET(m_pHelp), true);
+ gtk_widget_set_halign(GTK_WIDGET(m_pHelp), GTK_ALIGN_START);
+#else
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pHelp), false, false, 0);
+#endif
+
+ gtk_assistant_add_action_widget(pAssistant, GTK_WIDGET(m_pButtonBox));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_button_box_set_child_secondary(m_pButtonBox, GTK_WIDGET(m_pHelp), true);
+#endif
+ gtk_widget_set_hexpand(GTK_WIDGET(m_pButtonBox), true);
+
+ GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pButtonBox));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_child_set(GTK_CONTAINER(pParent), GTK_WIDGET(m_pButtonBox), "expand", true, "fill", true, nullptr);
+#endif
+ gtk_widget_set_halign(pParent, GTK_ALIGN_FILL);
+
+ // Hide the built-in ones early so we get a nice optimal size for the width without
+ // including the unused contents
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (GtkWidget* pChild = gtk_widget_get_first_child(pParent);
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ gtk_widget_hide(pChild);
+ }
+#else
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pParent));
+ for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
+ {
+ GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
+ gtk_widget_hide(pWidget);
+ }
+ g_list_free(pChildren);
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_show_all(GTK_WIDGET(m_pButtonBox));
+#else
+ gtk_widget_show(GTK_WIDGET(m_pButtonBox));
+#endif
+
+ find_sidebar(GTK_WIDGET(m_pAssistant), &m_pSidebar);
+
+ m_pSidebarEventBox = ::ensureEventWidget(m_pSidebar);
+ if (m_pSidebarEventBox)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkGesture *pClick = gtk_gesture_click_new();
+ gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pClick), 0);
+ m_pSidebarClickController = GTK_EVENT_CONTROLLER(pClick);
+ gtk_widget_add_controller(m_pSidebarEventBox, m_pSidebarClickController);
+ m_nButtonPressSignalId = g_signal_connect(m_pSidebarClickController, "pressed", G_CALLBACK(signalButton), this);
+#else
+ m_nButtonPressSignalId = g_signal_connect(m_pSidebarEventBox, "button-press-event", G_CALLBACK(signalButton), this);
+#endif
+ }
+ }
+
+ virtual int get_current_page() const override
+ {
+ return gtk_assistant_get_current_page(m_pAssistant);
+ }
+
+ virtual int get_n_pages() const override
+ {
+ return gtk_assistant_get_n_pages(m_pAssistant);
+ }
+
+ virtual OUString get_page_ident(int nPage) const override
+ {
+ const GtkWidget* pWidget = gtk_assistant_get_nth_page(m_pAssistant, nPage);
+ return ::get_buildable_id(GTK_BUILDABLE(pWidget));
+ }
+
+ virtual OUString get_current_page_ident() const override
+ {
+ return get_page_ident(get_current_page());
+ }
+
+ virtual void set_current_page(int nPage) override
+ {
+ OString sDialogTitle(gtk_window_get_title(GTK_WINDOW(m_pAssistant)));
+
+ gtk_assistant_set_current_page(m_pAssistant, nPage);
+
+ // if the page doesn't have a title, then the dialog will now have no
+ // title, so restore the original title as a fallback
+ GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nPage);
+ if (!gtk_assistant_get_page_title(m_pAssistant, pPage))
+ gtk_window_set_title(GTK_WINDOW(m_pAssistant), sDialogTitle.getStr());
+ }
+
+ virtual void set_current_page(const OUString& rIdent) override
+ {
+ int nPage = find_page(rIdent);
+ if (nPage == -1)
+ return;
+ set_current_page(nPage);
+ }
+
+ virtual void set_page_title(const OUString& rIdent, const OUString& rTitle) override
+ {
+ int nIndex = find_page(rIdent);
+ if (nIndex == -1)
+ return;
+ GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nIndex);
+ gtk_assistant_set_page_title(m_pAssistant, pPage,
+ OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_forall(GTK_CONTAINER(m_pSidebar), wrap_sidebar_label, nullptr);
+#endif
+ }
+
+ virtual OUString get_page_title(const OUString& rIdent) const override
+ {
+ int nIndex = find_page(rIdent);
+ if (nIndex == -1)
+ return OUString();
+ GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nIndex);
+ const gchar* pStr = gtk_assistant_get_page_title(m_pAssistant, pPage);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ virtual void set_page_sensitive(const OUString& rIdent, bool bSensitive) override
+ {
+ m_aNotClickable[rIdent] = !bSensitive;
+ }
+
+ virtual void set_page_index(const OUString& rIdent, int nNewIndex) override
+ {
+ int nOldIndex = find_page(rIdent);
+ if (nOldIndex == -1)
+ return;
+
+ if (nOldIndex == nNewIndex)
+ return;
+
+ GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nOldIndex);
+
+ g_object_ref(pPage);
+ std::optional<OString> sTitle;
+ if (auto const title = gtk_assistant_get_page_title(m_pAssistant, pPage)) {
+ sTitle = title;
+ }
+ gtk_assistant_remove_page(m_pAssistant, nOldIndex);
+ gtk_assistant_insert_page(m_pAssistant, pPage, nNewIndex);
+ gtk_assistant_set_page_type(m_pAssistant, pPage, GTK_ASSISTANT_PAGE_CUSTOM);
+ gtk_assistant_set_page_title(m_pAssistant, pPage, sTitle ? sTitle->getStr() : nullptr);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_forall(GTK_CONTAINER(m_pSidebar), wrap_sidebar_label, nullptr);
+#endif
+ g_object_unref(pPage);
+ }
+
+ virtual weld::Container* append_page(const OUString& rIdent) override
+ {
+ disable_notify_events();
+
+ GtkWidget *pChild = gtk_grid_new();
+ ::set_buildable_id(GTK_BUILDABLE(pChild), rIdent);
+ gtk_assistant_append_page(m_pAssistant, pChild);
+ gtk_assistant_set_page_type(m_pAssistant, pChild, GTK_ASSISTANT_PAGE_CUSTOM);
+ gtk_widget_show(pChild);
+
+ enable_notify_events();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_aPages.emplace_back(new GtkInstanceContainer(GTK_CONTAINER(pChild), m_pBuilder, false));
+#else
+ m_aPages.emplace_back(new GtkInstanceContainer(pChild, m_pBuilder, false));
+#endif
+
+ return m_aPages.back().get();
+ }
+
+ virtual void set_page_side_help_id(const OUString& rHelpId) override
+ {
+ if (!m_pSidebar)
+ return;
+ ::set_help_id(m_pSidebar, rHelpId);
+ }
+
+ virtual GtkButton* get_widget_for_response(int nGtkResponse) override
+ {
+ GtkButton* pButton = nullptr;
+ if (nGtkResponse == GTK_RESPONSE_YES)
+ pButton = m_pNext;
+ else if (nGtkResponse == GTK_RESPONSE_NO)
+ pButton = m_pBack;
+ else if (nGtkResponse == GTK_RESPONSE_OK)
+ pButton = m_pFinish;
+ else if (nGtkResponse == GTK_RESPONSE_CANCEL)
+ pButton = m_pCancel;
+ else if (nGtkResponse == GTK_RESPONSE_HELP)
+ pButton = m_pHelp;
+ return pButton;
+ }
+
+ virtual void set_page_side_image(const OUString& /*rImage*/) override
+ {
+ // Since GTK+ 3.2, sidebar images are not shown anymore
+ }
+
+ virtual ~GtkInstanceAssistant() override
+ {
+ if (m_nButtonPressSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pSidebarClickController, m_nButtonPressSignalId);
+#else
+ g_signal_handler_disconnect(m_pSidebarEventBox, m_nButtonPressSignalId);
+#endif
+ }
+ }
+};
+
+class GtkInstanceFrame : public GtkInstanceContainer, public virtual weld::Frame
+{
+private:
+ GtkFrame* m_pFrame;
+public:
+ GtkInstanceFrame(GtkFrame* pFrame, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ : GtkInstanceContainer(GTK_CONTAINER(pFrame), pBuilder, bTakeOwnership)
+#else
+ : GtkInstanceContainer(GTK_WIDGET(pFrame), pBuilder, bTakeOwnership)
+#endif
+ , m_pFrame(pFrame)
+ {
+ }
+
+ virtual void set_label(const OUString& rText) override
+ {
+ gtk_label_set_label(GTK_LABEL(gtk_frame_get_label_widget(m_pFrame)), rText.replaceFirst("~", "").toUtf8().getStr());
+ }
+
+ virtual OUString get_label() const override
+ {
+ const gchar* pStr = gtk_frame_get_label(m_pFrame);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ virtual std::unique_ptr<weld::Label> weld_label_widget() const override;
+};
+
+class GtkInstancePaned : public GtkInstanceContainer, public virtual weld::Paned
+{
+private:
+ GtkPaned* m_pPaned;
+public:
+ GtkInstancePaned(GtkPaned* pPaned, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ : GtkInstanceContainer(GTK_CONTAINER(pPaned), pBuilder, bTakeOwnership)
+#else
+ : GtkInstanceContainer(GTK_WIDGET(pPaned), pBuilder, bTakeOwnership)
+#endif
+ , m_pPaned(pPaned)
+ {
+ }
+
+ virtual void set_position(int nPos) override
+ {
+ gtk_paned_set_position(m_pPaned, nPos);
+ }
+
+ virtual int get_position() const override
+ {
+ return gtk_paned_get_position(m_pPaned);
+ }
+};
+
+}
+
+static GType immobilized_viewport_get_type();
+static gpointer immobilized_viewport_parent_class;
+
+#ifndef NDEBUG
+# define IMMOBILIZED_TYPE_VIEWPORT (immobilized_viewport_get_type())
+# define IMMOBILIZED_IS_VIEWPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), IMMOBILIZED_TYPE_VIEWPORT))
+#endif
+
+namespace {
+
+struct ImmobilizedViewportPrivate
+{
+ GtkAdjustment *hadjustment;
+ GtkAdjustment *vadjustment;
+};
+
+}
+
+#define IMMOBILIZED_VIEWPORT_PRIVATE_DATA "ImmobilizedViewportPrivateData"
+
+enum
+{
+ PROP_0,
+ PROP_HADJUSTMENT,
+ PROP_VADJUSTMENT,
+ PROP_HSCROLL_POLICY,
+ PROP_VSCROLL_POLICY,
+ PROP_SHADOW_TYPE
+};
+
+static void viewport_set_adjustment(GtkViewport *viewport,
+ GtkOrientation orientation,
+ GtkAdjustment *adjustment)
+{
+ ImmobilizedViewportPrivate* priv =
+ static_cast<ImmobilizedViewportPrivate*>(g_object_get_data(G_OBJECT(viewport),
+ IMMOBILIZED_VIEWPORT_PRIVATE_DATA));
+
+ if (!adjustment)
+ adjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (priv->hadjustment)
+ g_object_unref(priv->hadjustment);
+ priv->hadjustment = adjustment;
+ }
+ else
+ {
+ if (priv->vadjustment)
+ g_object_unref(priv->vadjustment);
+ priv->vadjustment = adjustment;
+ }
+
+ g_object_ref_sink(adjustment);
+}
+
+static void
+immobilized_viewport_set_property(GObject* object,
+ guint prop_id,
+ const GValue* value,
+ GParamSpec* /*pspec*/)
+{
+ GtkViewport *viewport = GTK_VIEWPORT(object);
+
+ switch (prop_id)
+ {
+ case PROP_HADJUSTMENT:
+ viewport_set_adjustment(viewport, GTK_ORIENTATION_HORIZONTAL, GTK_ADJUSTMENT(g_value_get_object(value)));
+ break;
+ case PROP_VADJUSTMENT:
+ viewport_set_adjustment(viewport, GTK_ORIENTATION_VERTICAL, GTK_ADJUSTMENT(g_value_get_object(value)));
+ break;
+ case PROP_HSCROLL_POLICY:
+ case PROP_VSCROLL_POLICY:
+ break;
+ default:
+ SAL_WARN( "vcl.gtk", "unknown property\n");
+ break;
+ }
+}
+
+static void
+immobilized_viewport_get_property(GObject* object,
+ guint prop_id,
+ GValue* value,
+ GParamSpec* /*pspec*/)
+{
+ ImmobilizedViewportPrivate* priv =
+ static_cast<ImmobilizedViewportPrivate*>(g_object_get_data(object,
+ IMMOBILIZED_VIEWPORT_PRIVATE_DATA));
+
+ switch (prop_id)
+ {
+ case PROP_HADJUSTMENT:
+ g_value_set_object(value, priv->hadjustment);
+ break;
+ case PROP_VADJUSTMENT:
+ g_value_set_object(value, priv->vadjustment);
+ break;
+ case PROP_HSCROLL_POLICY:
+ g_value_set_enum(value, GTK_SCROLL_MINIMUM);
+ break;
+ case PROP_VSCROLL_POLICY:
+ g_value_set_enum(value, GTK_SCROLL_MINIMUM);
+ break;
+ default:
+ SAL_WARN( "vcl.gtk", "unknown property\n");
+ break;
+ }
+}
+
+static ImmobilizedViewportPrivate*
+immobilized_viewport_new_private_data()
+{
+ ImmobilizedViewportPrivate* priv = g_slice_new0(ImmobilizedViewportPrivate);
+ priv->hadjustment = nullptr;
+ priv->vadjustment = nullptr;
+ return priv;
+}
+
+static void
+immobilized_viewport_instance_init(GTypeInstance *instance, gpointer /*klass*/)
+{
+ GObject* object = G_OBJECT(instance);
+ g_object_set_data(object, IMMOBILIZED_VIEWPORT_PRIVATE_DATA,
+ immobilized_viewport_new_private_data());
+}
+
+static void
+immobilized_viewport_finalize(GObject* object)
+{
+ void* priv = g_object_get_data(object, IMMOBILIZED_VIEWPORT_PRIVATE_DATA);
+ if (priv)
+ {
+ g_slice_free(ImmobilizedViewportPrivate, priv);
+ g_object_set_data(object, IMMOBILIZED_VIEWPORT_PRIVATE_DATA, nullptr);
+ }
+ G_OBJECT_CLASS(immobilized_viewport_parent_class)->finalize(object);
+}
+
+static void immobilized_viewport_class_init(GtkWidgetClass* klass)
+{
+ immobilized_viewport_parent_class = g_type_class_peek_parent(klass);
+
+ GObjectClass* o_class = G_OBJECT_CLASS(klass);
+
+ /* GObject signals */
+ o_class->finalize = immobilized_viewport_finalize;
+ o_class->set_property = immobilized_viewport_set_property;
+ o_class->get_property = immobilized_viewport_get_property;
+
+ /* Properties */
+ g_object_class_override_property(o_class, PROP_HADJUSTMENT, "hadjustment");
+ g_object_class_override_property(o_class, PROP_VADJUSTMENT, "vadjustment");
+ g_object_class_override_property(o_class, PROP_HSCROLL_POLICY, "hscroll-policy");
+ g_object_class_override_property(o_class, PROP_VSCROLL_POLICY, "vscroll-policy");
+}
+
+GType immobilized_viewport_get_type()
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ GTypeQuery query;
+ g_type_query(gtk_viewport_get_type(), &query);
+
+ static const GTypeInfo tinfo =
+ {
+ static_cast<guint16>(query.class_size),
+ nullptr, /* base init */
+ nullptr, /* base finalize */
+ reinterpret_cast<GClassInitFunc>(immobilized_viewport_class_init), /* class init */
+ nullptr, /* class finalize */
+ nullptr, /* class data */
+ static_cast<guint16>(query.instance_size), /* instance size */
+ 0, /* nb preallocs */
+ immobilized_viewport_instance_init, /* instance init */
+ nullptr /* value table */
+ };
+
+ type = g_type_register_static(GTK_TYPE_VIEWPORT, "ImmobilizedViewport",
+ &tinfo, GTypeFlags(0));
+ }
+
+ return type;
+}
+
+static VclPolicyType GtkToVcl(GtkPolicyType eType)
+{
+ VclPolicyType eRet(VclPolicyType::NEVER);
+ switch (eType)
+ {
+ case GTK_POLICY_ALWAYS:
+ eRet = VclPolicyType::ALWAYS;
+ break;
+ case GTK_POLICY_AUTOMATIC:
+ eRet = VclPolicyType::AUTOMATIC;
+ break;
+ case GTK_POLICY_EXTERNAL:
+ case GTK_POLICY_NEVER:
+ eRet = VclPolicyType::NEVER;
+ break;
+ }
+ return eRet;
+}
+
+static GtkPolicyType VclToGtk(VclPolicyType eType)
+{
+ GtkPolicyType eRet(GTK_POLICY_ALWAYS);
+ switch (eType)
+ {
+ case VclPolicyType::ALWAYS:
+ eRet = GTK_POLICY_ALWAYS;
+ break;
+ case VclPolicyType::AUTOMATIC:
+ eRet = GTK_POLICY_AUTOMATIC;
+ break;
+ case VclPolicyType::NEVER:
+ eRet = GTK_POLICY_NEVER;
+ break;
+ }
+ return eRet;
+}
+
+static GtkMessageType VclToGtk(VclMessageType eType)
+{
+ GtkMessageType eRet(GTK_MESSAGE_INFO);
+ switch (eType)
+ {
+ case VclMessageType::Info:
+ eRet = GTK_MESSAGE_INFO;
+ break;
+ case VclMessageType::Warning:
+ eRet = GTK_MESSAGE_WARNING;
+ break;
+ case VclMessageType::Question:
+ eRet = GTK_MESSAGE_QUESTION;
+ break;
+ case VclMessageType::Error:
+ eRet = GTK_MESSAGE_ERROR;
+ break;
+ case VclMessageType::Other:
+ eRet = GTK_MESSAGE_OTHER;
+ break;
+ }
+ return eRet;
+}
+
+static GtkButtonsType VclToGtk(VclButtonsType eType)
+{
+ GtkButtonsType eRet(GTK_BUTTONS_NONE);
+ switch (eType)
+ {
+ case VclButtonsType::NONE:
+ eRet = GTK_BUTTONS_NONE;
+ break;
+ case VclButtonsType::Ok:
+ eRet = GTK_BUTTONS_OK;
+ break;
+ case VclButtonsType::Close:
+ eRet = GTK_BUTTONS_CLOSE;
+ break;
+ case VclButtonsType::Cancel:
+ eRet = GTK_BUTTONS_CANCEL;
+ break;
+ case VclButtonsType::YesNo:
+ eRet = GTK_BUTTONS_YES_NO;
+ break;
+ case VclButtonsType::OkCancel:
+ eRet = GTK_BUTTONS_OK_CANCEL;
+ break;
+ }
+ return eRet;
+}
+
+static GtkSelectionMode VclToGtk(SelectionMode eType)
+{
+ GtkSelectionMode eRet(GTK_SELECTION_NONE);
+ switch (eType)
+ {
+ case SelectionMode::NONE:
+ eRet = GTK_SELECTION_NONE;
+ break;
+ case SelectionMode::Single:
+ eRet = GTK_SELECTION_SINGLE;
+ break;
+ case SelectionMode::Range:
+ eRet = GTK_SELECTION_BROWSE;
+ break;
+ case SelectionMode::Multiple:
+ eRet = GTK_SELECTION_MULTIPLE;
+ break;
+ }
+ return eRet;
+}
+
+namespace {
+
+class GtkInstanceScrolledWindow final : public GtkInstanceContainer, public virtual weld::ScrolledWindow
+{
+private:
+ GtkScrolledWindow* m_pScrolledWindow;
+ GtkWidget *m_pOrigViewport;
+ GtkCssProvider* m_pScrollBarCssProvider;
+ GtkAdjustment* m_pVAdjustment;
+ GtkAdjustment* m_pHAdjustment;
+ gulong m_nVAdjustChangedSignalId;
+ gulong m_nHAdjustChangedSignalId;
+
+ static void signalVAdjustValueChanged(GtkAdjustment*, gpointer widget)
+ {
+ GtkInstanceScrolledWindow* pThis = static_cast<GtkInstanceScrolledWindow*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_vadjustment_changed();
+ }
+
+ static void signalHAdjustValueChanged(GtkAdjustment*, gpointer widget)
+ {
+ GtkInstanceScrolledWindow* pThis = static_cast<GtkInstanceScrolledWindow*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_hadjustment_changed();
+ }
+
+public:
+ GtkInstanceScrolledWindow(GtkScrolledWindow* pScrolledWindow, GtkInstanceBuilder* pBuilder, bool bTakeOwnership, bool bUserManagedScrolling)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ : GtkInstanceContainer(GTK_CONTAINER(pScrolledWindow), pBuilder, bTakeOwnership)
+#else
+ : GtkInstanceContainer(GTK_WIDGET(pScrolledWindow), pBuilder, bTakeOwnership)
+#endif
+ , m_pScrolledWindow(pScrolledWindow)
+ , m_pOrigViewport(nullptr)
+ , m_pScrollBarCssProvider(nullptr)
+ , m_pVAdjustment(gtk_scrolled_window_get_vadjustment(m_pScrolledWindow))
+ , m_pHAdjustment(gtk_scrolled_window_get_hadjustment(m_pScrolledWindow))
+ , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustValueChanged), this))
+ , m_nHAdjustChangedSignalId(g_signal_connect(m_pHAdjustment, "value-changed", G_CALLBACK(signalHAdjustValueChanged), this))
+ {
+ if (bUserManagedScrolling)
+ set_user_managed_scrolling();
+ }
+
+ void set_user_managed_scrolling()
+ {
+ disable_notify_events();
+ //remove the original viewport and replace it with our bodged one which
+ //doesn't do any scrolling and expects its child to figure it out somehow
+ assert(!m_pOrigViewport);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget *pViewport = gtk_scrolled_window_get_child(m_pScrolledWindow);
+#else
+ GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
+#endif
+ assert(GTK_IS_VIEWPORT(pViewport));
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget *pChild= gtk_viewport_get_child(GTK_VIEWPORT(pViewport));
+#else
+ GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
+#endif
+ g_object_ref(pChild);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_viewport_set_child(GTK_VIEWPORT(pViewport), nullptr);
+#else
+ gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
+#endif
+ g_object_ref(pViewport);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_scrolled_window_set_child(m_pScrolledWindow, nullptr);
+#else
+ gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
+#endif
+ GtkWidget* pNewViewport = GTK_WIDGET(g_object_new(immobilized_viewport_get_type(), nullptr));
+ gtk_widget_show(pNewViewport);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_scrolled_window_set_child(m_pScrolledWindow, pNewViewport);
+ gtk_viewport_set_child(GTK_VIEWPORT(pNewViewport), pChild);
+#else
+ gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), pNewViewport);
+ gtk_container_add(GTK_CONTAINER(pNewViewport), pChild);
+#endif
+ g_object_unref(pChild);
+ m_pOrigViewport = pViewport;
+ enable_notify_events();
+ }
+
+ virtual void hadjustment_configure(int value, int lower, int upper,
+ int step_increment, int page_increment,
+ int page_size) override
+ {
+ disable_notify_events();
+ if (SwapForRTL())
+ value = upper - (value - lower + page_size);
+ gtk_adjustment_configure(m_pHAdjustment, value, lower, upper, step_increment, page_increment, page_size);
+ enable_notify_events();
+ }
+
+ virtual int hadjustment_get_value() const override
+ {
+ int value = gtk_adjustment_get_value(m_pHAdjustment);
+
+ if (SwapForRTL())
+ {
+ int upper = gtk_adjustment_get_upper(m_pHAdjustment);
+ int lower = gtk_adjustment_get_lower(m_pHAdjustment);
+ int page_size = gtk_adjustment_get_page_size(m_pHAdjustment);
+ value = lower + (upper - value - page_size);
+ }
+
+ return value;
+ }
+
+ virtual void hadjustment_set_value(int value) override
+ {
+ disable_notify_events();
+
+ if (SwapForRTL())
+ {
+ int upper = gtk_adjustment_get_upper(m_pHAdjustment);
+ int lower = gtk_adjustment_get_lower(m_pHAdjustment);
+ int page_size = gtk_adjustment_get_page_size(m_pHAdjustment);
+ value = upper - (value - lower + page_size);
+ }
+
+ gtk_adjustment_set_value(m_pHAdjustment, value);
+ enable_notify_events();
+ }
+
+ virtual int hadjustment_get_upper() const override
+ {
+ return gtk_adjustment_get_upper(m_pHAdjustment);
+ }
+
+ virtual void hadjustment_set_upper(int upper) override
+ {
+ disable_notify_events();
+ gtk_adjustment_set_upper(m_pHAdjustment, upper);
+ enable_notify_events();
+ }
+
+ virtual int hadjustment_get_page_size() const override
+ {
+ return gtk_adjustment_get_page_size(m_pHAdjustment);
+ }
+
+ virtual void hadjustment_set_page_size(int size) override
+ {
+ gtk_adjustment_set_page_size(m_pHAdjustment, size);
+ }
+
+ virtual void hadjustment_set_page_increment(int size) override
+ {
+ gtk_adjustment_set_page_increment(m_pHAdjustment, size);
+ }
+
+ virtual void hadjustment_set_step_increment(int size) override
+ {
+ gtk_adjustment_set_step_increment(m_pHAdjustment, size);
+ }
+
+ virtual void set_hpolicy(VclPolicyType eHPolicy) override
+ {
+ GtkPolicyType eGtkVPolicy;
+ gtk_scrolled_window_get_policy(m_pScrolledWindow, nullptr, &eGtkVPolicy);
+ gtk_scrolled_window_set_policy(m_pScrolledWindow, VclToGtk(eHPolicy), eGtkVPolicy);
+ }
+
+ virtual VclPolicyType get_hpolicy() const override
+ {
+ GtkPolicyType eGtkHPolicy;
+ gtk_scrolled_window_get_policy(m_pScrolledWindow, &eGtkHPolicy, nullptr);
+ return GtkToVcl(eGtkHPolicy);
+ }
+
+ virtual void vadjustment_configure(int value, int lower, int upper,
+ int step_increment, int page_increment,
+ int page_size) override
+ {
+ disable_notify_events();
+ gtk_adjustment_configure(m_pVAdjustment, value, lower, upper, step_increment, page_increment, page_size);
+ enable_notify_events();
+ }
+
+ virtual int vadjustment_get_value() const override
+ {
+ return gtk_adjustment_get_value(m_pVAdjustment);
+ }
+
+ virtual void vadjustment_set_value(int value) override
+ {
+ disable_notify_events();
+ gtk_adjustment_set_value(m_pVAdjustment, value);
+ enable_notify_events();
+ }
+
+ virtual int vadjustment_get_upper() const override
+ {
+ return gtk_adjustment_get_upper(m_pVAdjustment);
+ }
+
+ virtual void vadjustment_set_upper(int upper) override
+ {
+ disable_notify_events();
+ gtk_adjustment_set_upper(m_pVAdjustment, upper);
+ enable_notify_events();
+ }
+
+ virtual int vadjustment_get_lower() const override
+ {
+ return gtk_adjustment_get_lower(m_pVAdjustment);
+ }
+
+ virtual void vadjustment_set_lower(int lower) override
+ {
+ disable_notify_events();
+ gtk_adjustment_set_lower(m_pVAdjustment, lower);
+ enable_notify_events();
+ }
+
+ virtual int vadjustment_get_page_size() const override
+ {
+ return gtk_adjustment_get_page_size(m_pVAdjustment);
+ }
+
+ virtual void vadjustment_set_page_size(int size) override
+ {
+ gtk_adjustment_set_page_size(m_pVAdjustment, size);
+ }
+
+ virtual void vadjustment_set_page_increment(int size) override
+ {
+ gtk_adjustment_set_page_increment(m_pVAdjustment, size);
+ }
+
+ virtual void vadjustment_set_step_increment(int size) override
+ {
+ gtk_adjustment_set_step_increment(m_pVAdjustment, size);
+ }
+
+ virtual void set_vpolicy(VclPolicyType eVPolicy) override
+ {
+ GtkPolicyType eGtkHPolicy;
+ gtk_scrolled_window_get_policy(m_pScrolledWindow, &eGtkHPolicy, nullptr);
+ gtk_scrolled_window_set_policy(m_pScrolledWindow, eGtkHPolicy, VclToGtk(eVPolicy));
+ }
+
+ virtual VclPolicyType get_vpolicy() const override
+ {
+ GtkPolicyType eGtkVPolicy;
+ gtk_scrolled_window_get_policy(m_pScrolledWindow, nullptr, &eGtkVPolicy);
+ return GtkToVcl(eGtkVPolicy);
+ }
+
+ virtual int get_scroll_thickness() const override
+ {
+ if (gtk_scrolled_window_get_overlay_scrolling(m_pScrolledWindow))
+ return 0;
+ GtkRequisition size;
+ gtk_widget_get_preferred_size(gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow), nullptr, &size);
+ return size.width;
+ }
+
+ virtual void set_scroll_thickness(int nThickness) override
+ {
+ GtkWidget *pHorzBar = gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow);
+ GtkWidget *pVertBar = gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow);
+ gtk_widget_set_size_request(pHorzBar, -1, nThickness);
+ gtk_widget_set_size_request(pVertBar, nThickness, -1);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pVAdjustment, m_nVAdjustChangedSignalId);
+ g_signal_handler_block(m_pHAdjustment, m_nHAdjustChangedSignalId);
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::enable_notify_events();
+ g_signal_handler_unblock(m_pVAdjustment, m_nVAdjustChangedSignalId);
+ g_signal_handler_unblock(m_pHAdjustment, m_nHAdjustChangedSignalId);
+ }
+
+ virtual void customize_scrollbars(const Color& rBackgroundColor,
+ const Color& rShadowColor,
+ const Color& rFaceColor) override
+ {
+ GtkWidget *pHorzBar = gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow);
+ GtkWidget *pVertBar = gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow);
+ GtkStyleContext *pHorzContext = gtk_widget_get_style_context(pHorzBar);
+ GtkStyleContext *pVertContext = gtk_widget_get_style_context(pVertBar);
+ if (m_pScrollBarCssProvider)
+ {
+ gtk_style_context_remove_provider(pHorzContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider));
+ gtk_style_context_remove_provider(pVertContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider));
+ }
+
+ m_pScrollBarCssProvider = gtk_css_provider_new();
+ // intentionally 'trough' a long, narrow open container.
+ OUString aBuffer = "scrollbar contents trough { background-color: #" + rBackgroundColor.AsRGBHexString() + "; } "
+ "scrollbar contents trough slider { background-color: #" + rShadowColor.AsRGBHexString() + "; } "
+ "scrollbar contents button { background-color: #" + rFaceColor.AsRGBHexString() + "; } "
+ "scrollbar contents button { color: #000000; } "
+ "scrollbar contents button:disabled { color: #7f7f7f; }";
+ OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
+ css_provider_load_from_data(m_pScrollBarCssProvider, aResult.getStr(), aResult.getLength());
+
+ gtk_style_context_add_provider(pHorzContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ gtk_style_context_add_provider(pVertContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+ virtual ~GtkInstanceScrolledWindow() override
+ {
+ // we use GtkInstanceContainer::[disable|enable]_notify_events later on
+ // to avoid touching these removed handlers
+ g_signal_handler_disconnect(m_pVAdjustment, m_nVAdjustChangedSignalId);
+ g_signal_handler_disconnect(m_pHAdjustment, m_nHAdjustChangedSignalId);
+
+ if (m_pScrollBarCssProvider)
+ {
+ GtkStyleContext *pHorzContext = gtk_widget_get_style_context(gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow));
+ GtkStyleContext *pVertContext = gtk_widget_get_style_context(gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow));
+ gtk_style_context_remove_provider(pHorzContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider));
+ gtk_style_context_remove_provider(pVertContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider));
+ m_pScrollBarCssProvider = nullptr;
+ }
+
+ //put it back the way it was
+ if (!m_pOrigViewport)
+ return;
+
+ GtkInstanceContainer::disable_notify_events();
+
+ // force in new adjustment to drop the built-in handlers on value-changed
+ // which are getting called eventually by the gtk_container_add call
+ // and which access the scrolled window indicators which, in the case
+ // of user-managed scrolling windows in toolbar popups during popdown
+ // are nullptr causing crashes when the scrolling windows is not at its
+ // initial 0,0 position
+ GtkAdjustment *pVAdjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+ gtk_scrolled_window_set_vadjustment(m_pScrolledWindow, pVAdjustment);
+ GtkAdjustment *pHAdjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+ gtk_scrolled_window_set_hadjustment(m_pScrolledWindow, pHAdjustment);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget *pViewport = gtk_scrolled_window_get_child(m_pScrolledWindow);
+#else
+ GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
+#endif
+ assert(IMMOBILIZED_IS_VIEWPORT(pViewport));
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget *pChild= gtk_viewport_get_child(GTK_VIEWPORT(pViewport));
+#else
+ GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
+#endif
+ g_object_ref(pChild);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_viewport_set_child(GTK_VIEWPORT(pViewport), nullptr);
+#else
+ gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
+#endif
+ g_object_ref(pViewport);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_scrolled_window_set_child(m_pScrolledWindow, nullptr);
+#else
+ gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_scrolled_window_set_child(m_pScrolledWindow, m_pOrigViewport);
+#else
+ gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), m_pOrigViewport);
+#endif
+ // coverity[freed_arg : FALSE] - this does not free m_pOrigViewport, it is reffed by m_pScrolledWindow
+ g_object_unref(m_pOrigViewport);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_viewport_set_child(GTK_VIEWPORT(m_pOrigViewport), pChild);
+#else
+ gtk_container_add(GTK_CONTAINER(m_pOrigViewport), pChild);
+#endif
+ g_object_unref(pChild);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(pViewport);
+#endif
+ g_object_unref(pViewport);
+ m_pOrigViewport = nullptr;
+ GtkInstanceContainer::enable_notify_events();
+ }
+};
+
+class GtkInstanceScrollbar final : public GtkInstanceWidget, public virtual weld::Scrollbar
+{
+private:
+ GtkScrollbar* m_pScrollbar;
+ GtkAdjustment* m_pAdjustment;
+ GtkCssProvider* m_pThicknessCssProvider;
+ gulong m_nAdjustChangedSignalId;
+
+ static void signalAdjustValueChanged(GtkAdjustment*, gpointer widget)
+ {
+ GtkInstanceScrollbar* pThis = static_cast<GtkInstanceScrollbar*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_adjustment_changed();
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ // if the widget is inside a GtkSalFrame then ensure the event is processed by the GtkSalFrame and not the
+ // GtkScrollbar
+ static gboolean signalScroll(GtkEventControllerScroll* pController, double delta_x, double delta_y, gpointer widget)
+ {
+ GtkInstanceScrollbar* pThis = static_cast<GtkInstanceScrollbar*>(widget);
+
+ GtkWidget* pParent = widget_get_toplevel(GTK_WIDGET(pThis->m_pScrollbar));
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
+
+ return pFrame && pFrame->event_controller_scroll_forward(pController, delta_x, delta_y);
+ }
+#else
+ static gboolean signalScroll(GtkWidget* pWidget, GdkEventScroll* /*pEvent*/, gpointer widget)
+ {
+ GtkInstanceScrollbar* pThis = static_cast<GtkInstanceScrollbar*>(widget);
+
+ GtkWidget* pParent = widget_get_toplevel(GTK_WIDGET(pThis->m_pScrollbar));
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
+
+ if (pFrame)
+ g_signal_stop_emission_by_name(pWidget, "scroll-event");
+
+ return false;
+ }
+#endif
+
+public:
+ GtkInstanceScrollbar(GtkScrollbar* pScrollbar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pScrollbar), pBuilder, bTakeOwnership)
+ , m_pScrollbar(pScrollbar)
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_pAdjustment(gtk_scrollbar_get_adjustment(m_pScrollbar))
+#else
+ , m_pAdjustment(gtk_range_get_adjustment(GTK_RANGE(m_pScrollbar)))
+#endif
+ , m_pThicknessCssProvider(nullptr)
+ , m_nAdjustChangedSignalId(g_signal_connect(m_pAdjustment, "value-changed", G_CALLBACK(signalAdjustValueChanged), this))
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkEventController* pScrollController = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
+ gtk_event_controller_set_propagation_phase(pScrollController, GTK_PHASE_CAPTURE);
+ g_signal_connect(pScrollController, "scroll", G_CALLBACK(signalScroll), this);
+ gtk_widget_add_controller(GTK_WIDGET(pScrollbar), pScrollController);
+#else
+ g_signal_connect(pScrollbar, "scroll-event", G_CALLBACK(signalScroll), this);
+#endif
+ }
+
+ virtual void adjustment_configure(int value, int lower, int upper,
+ int step_increment, int page_increment,
+ int page_size) override
+ {
+ disable_notify_events();
+ gtk_adjustment_configure(m_pAdjustment, value, lower, upper, step_increment, page_increment, page_size);
+ enable_notify_events();
+ }
+
+ virtual int adjustment_get_value() const override
+ {
+ return gtk_adjustment_get_value(m_pAdjustment);
+ }
+
+ virtual void adjustment_set_value(int value) override
+ {
+ disable_notify_events();
+ gtk_adjustment_set_value(m_pAdjustment, value);
+ enable_notify_events();
+ }
+
+ virtual int adjustment_get_upper() const override
+ {
+ return gtk_adjustment_get_upper(m_pAdjustment);
+ }
+
+ virtual void adjustment_set_upper(int upper) override
+ {
+ disable_notify_events();
+ gtk_adjustment_set_upper(m_pAdjustment, upper);
+ enable_notify_events();
+ }
+
+ virtual int adjustment_get_lower() const override
+ {
+ return gtk_adjustment_get_lower(m_pAdjustment);
+ }
+
+ virtual void adjustment_set_lower(int lower) override
+ {
+ disable_notify_events();
+ gtk_adjustment_set_lower(m_pAdjustment, lower);
+ enable_notify_events();
+ }
+
+ virtual int adjustment_get_page_size() const override
+ {
+ return gtk_adjustment_get_page_size(m_pAdjustment);
+ }
+
+ virtual void adjustment_set_page_size(int size) override
+ {
+ gtk_adjustment_set_page_size(m_pAdjustment, size);
+ }
+
+ virtual int adjustment_get_page_increment() const override
+ {
+ return gtk_adjustment_get_page_increment(m_pAdjustment);
+ }
+
+ virtual void adjustment_set_page_increment(int size) override
+ {
+ gtk_adjustment_set_page_increment(m_pAdjustment, size);
+ }
+
+ virtual int adjustment_get_step_increment() const override
+ {
+ return gtk_adjustment_get_step_increment(m_pAdjustment);
+ }
+
+ virtual void adjustment_set_step_increment(int size) override
+ {
+ gtk_adjustment_set_step_increment(m_pAdjustment, size);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pAdjustment, m_nAdjustChangedSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pAdjustment, m_nAdjustChangedSignalId);
+ }
+
+ virtual ScrollType get_scroll_type() const override
+ {
+ // tdf#153049 want a mousewheel spin to be treated as DontKnow
+ return has_grab() ? ScrollType::Drag : ScrollType::DontKnow;
+ }
+
+ virtual int get_scroll_thickness() const override
+ {
+ if (gtk_orientable_get_orientation(GTK_ORIENTABLE(m_pScrollbar)) == GTK_ORIENTATION_HORIZONTAL)
+ return gtk_widget_get_allocated_height(GTK_WIDGET(m_pScrollbar));
+ return gtk_widget_get_allocated_width(GTK_WIDGET(m_pScrollbar));
+ }
+
+ virtual void set_scroll_thickness(int nThickness) override
+ {
+ GtkStyleContext *pStyleContext = gtk_widget_get_style_context(GTK_WIDGET(m_pScrollbar));
+
+ if (m_pThicknessCssProvider)
+ {
+ gtk_style_context_remove_provider(pStyleContext, GTK_STYLE_PROVIDER(m_pThicknessCssProvider));
+ m_pThicknessCssProvider = nullptr;
+ }
+
+ m_pThicknessCssProvider = gtk_css_provider_new();
+ int nSlider = nThickness > 6 ? nThickness - 6 : 1;
+ const OString sData = "slider { min-height: " + OString::number(nSlider) + "px;"
+ " min-width: " + OString::number(nSlider) + "px; }";
+ css_provider_load_from_data(m_pThicknessCssProvider, sData.getStr(), sData.getLength());
+ gtk_style_context_add_provider(pStyleContext, GTK_STYLE_PROVIDER(m_pThicknessCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ if (gtk_orientable_get_orientation(GTK_ORIENTABLE(m_pScrollbar)) == GTK_ORIENTATION_HORIZONTAL)
+ gtk_widget_set_size_request(GTK_WIDGET(m_pScrollbar), -1, nThickness);
+ else
+ gtk_widget_set_size_request(GTK_WIDGET(m_pScrollbar), nThickness, -1);
+ }
+
+ virtual ~GtkInstanceScrollbar() override
+ {
+ g_signal_handler_disconnect(m_pAdjustment, m_nAdjustChangedSignalId);
+ if (m_pThicknessCssProvider)
+ {
+ GtkStyleContext *pStyleContext = gtk_widget_get_style_context(GTK_WIDGET(m_pScrollbar));
+ gtk_style_context_remove_provider(pStyleContext, GTK_STYLE_PROVIDER(m_pThicknessCssProvider));
+ }
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstanceNotebook : public GtkInstanceWidget, public virtual weld::Notebook
+{
+private:
+ GtkNotebook* m_pNotebook;
+ GtkBox* m_pOverFlowBox;
+ GtkNotebook* m_pOverFlowNotebook;
+ gulong m_nSwitchPageSignalId;
+ gulong m_nOverFlowSwitchPageSignalId;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ NotifyingLayout* m_pLayout;
+#else
+ gulong m_nNotebookSizeAllocateSignalId;
+ gulong m_nFocusSignalId;
+#endif
+ gulong m_nChangeCurrentPageId;
+ guint m_nLaunchSplitTimeoutId;
+ bool m_bOverFlowBoxActive;
+ bool m_bOverFlowBoxIsStart;
+ bool m_bInternalPageChange;
+ int m_nStartTabCount;
+ int m_nEndTabCount;
+ mutable std::vector<std::unique_ptr<GtkInstanceContainer>> m_aPages;
+
+ static void signalSwitchPage(GtkNotebook*, GtkWidget*, guint nNewPage, gpointer widget)
+ {
+ GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_switch_page(nNewPage);
+ }
+
+ static gboolean launch_overflow_switch_page(GtkInstanceNotebook* pThis)
+ {
+ SolarMutexGuard aGuard;
+ pThis->signal_overflow_switch_page();
+ return false;
+ }
+
+ static void signalOverFlowSwitchPage(GtkNotebook*, GtkWidget*, guint, gpointer widget)
+ {
+ g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(launch_overflow_switch_page), widget, nullptr);
+ }
+
+ void signal_switch_page(int nNewPage)
+ {
+ if (m_bOverFlowBoxIsStart)
+ {
+ auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
+ // add count of overflow pages, minus the extra tab
+ nNewPage += nOverFlowLen;
+ }
+
+ bool bAllow = m_bInternalPageChange || !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
+ if (!bAllow)
+ {
+ g_signal_stop_emission_by_name(m_pNotebook, "switch-page");
+ return;
+ }
+ if (m_bOverFlowBoxActive)
+ gtk_notebook_set_current_page(m_pOverFlowNotebook, gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1);
+ OUString sNewIdent(get_page_ident(nNewPage));
+ if (!m_bInternalPageChange)
+ m_aEnterPageHdl.Call(sNewIdent);
+ }
+
+ void unsplit_notebooks()
+ {
+ int nOverFlowPages = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
+ int nMainPages = gtk_notebook_get_n_pages(m_pNotebook);
+ int nPageIndex = 0;
+ if (!m_bOverFlowBoxIsStart)
+ nPageIndex += nMainPages;
+
+ // take the overflow pages, and put them back at the end of the normal one
+ int i = nMainPages;
+ while (nOverFlowPages)
+ {
+ OUString sIdent(get_page_ident(m_pOverFlowNotebook, 0));
+ OUString sLabel(get_tab_label_text(m_pOverFlowNotebook, 0));
+ remove_page(m_pOverFlowNotebook, sIdent);
+
+ GtkWidget* pPage = m_aPages[nPageIndex]->getWidget();
+ insert_page(m_pNotebook, sIdent, sLabel, pPage, -1);
+
+ GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook,
+ gtk_notebook_get_nth_page(m_pNotebook, i));
+ gtk_widget_set_hexpand(pTabWidget, true);
+ --nOverFlowPages;
+ ++i;
+ ++nPageIndex;
+ }
+
+ // remove the dangling placeholder tab page
+ remove_page(m_pOverFlowNotebook, u"useless");
+ }
+
+ // a tab has been selected on the overflow notebook
+ void signal_overflow_switch_page()
+ {
+ int nNewPage = gtk_notebook_get_current_page(m_pOverFlowNotebook);
+ int nOverFlowPages = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
+ if (nNewPage == nOverFlowPages)
+ {
+ // the useless tab which is there because there has to be an active tab
+ return;
+ }
+
+ // check if we are allowed leave before attempting to resplit the notebooks
+ bool bAllow = !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
+ if (!bAllow)
+ return;
+
+ disable_notify_events();
+
+ // take the overflow pages, and put them back at the end of the normal one
+ unsplit_notebooks();
+
+ // now redo the split, the pages will be split the other way around this time
+ std::swap(m_nStartTabCount, m_nEndTabCount);
+ split_notebooks();
+
+ // coverity[pass_freed_arg : FALSE] - m_pNotebook is not freed here
+ gtk_notebook_set_current_page(m_pNotebook, nNewPage);
+
+ enable_notify_events();
+
+ // trigger main notebook switch-page callback
+ OUString sNewIdent(get_page_ident(m_pNotebook, nNewPage));
+ m_aEnterPageHdl.Call(sNewIdent);
+ }
+
+ static OUString get_page_ident(GtkNotebook *pNotebook, guint nPage)
+ {
+ const GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, gtk_notebook_get_nth_page(pNotebook, nPage));
+ return ::get_buildable_id(GTK_BUILDABLE(pTabWidget));
+ }
+
+ static gint get_page_number(GtkNotebook *pNotebook, std::u16string_view ident)
+ {
+ gint nPages = gtk_notebook_get_n_pages(pNotebook);
+ for (gint i = 0; i < nPages; ++i)
+ {
+ const GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, gtk_notebook_get_nth_page(pNotebook, i));
+ OUString sBuildableName = ::get_buildable_id(GTK_BUILDABLE(pTabWidget));
+ if (sBuildableName == ident)
+ return i;
+ }
+ return -1;
+ }
+
+ int remove_page(GtkNotebook *pNotebook, std::u16string_view ident)
+ {
+ disable_notify_events();
+ int nPageNumber = get_page_number(pNotebook, ident);
+ assert(nPageNumber != -1 && "asked to remove page that doesn't exist");
+ gtk_notebook_remove_page(pNotebook, nPageNumber);
+ enable_notify_events();
+ return nPageNumber;
+ }
+
+ static OUString get_tab_label_text(GtkNotebook *pNotebook, guint nPage)
+ {
+ const gchar* pStr = gtk_notebook_get_tab_label_text(pNotebook, gtk_notebook_get_nth_page(pNotebook, nPage));
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ static void set_tab_label_text(GtkNotebook *pNotebook, guint nPage, const OUString& rText)
+ {
+ OString sUtf8(rText.toUtf8());
+
+ GtkWidget* pPage = gtk_notebook_get_nth_page(pNotebook, nPage);
+
+ // tdf#128241 if there's already a label here, reuse it so the buildable
+ // name remains the same, gtk_notebook_set_tab_label_text will replace
+ // the label widget with a new one
+ GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, pPage);
+ if (pTabWidget && GTK_IS_LABEL(pTabWidget))
+ {
+ gtk_label_set_label(GTK_LABEL(pTabWidget), sUtf8.getStr());
+ return;
+ }
+
+ gtk_notebook_set_tab_label_text(pNotebook, pPage, sUtf8.getStr());
+ }
+
+ void append_useless_page(GtkNotebook *pNotebook)
+ {
+ disable_notify_events();
+
+ GtkWidget *pTabWidget = gtk_fixed_new();
+ ::set_buildable_id(GTK_BUILDABLE(pTabWidget), "useless");
+
+ GtkWidget *pChild = gtk_grid_new();
+ gtk_notebook_append_page(pNotebook, pChild, pTabWidget);
+ gtk_widget_show(pChild);
+ gtk_widget_show(pTabWidget);
+
+ enable_notify_events();
+ }
+
+ void insert_page(GtkNotebook *pNotebook, const OUString& rIdent, const OUString& rLabel, GtkWidget *pChild, int nPos)
+ {
+ disable_notify_events();
+
+ GtkWidget *pTabWidget = gtk_label_new_with_mnemonic(MapToGtkAccelerator(rLabel).getStr());
+ ::set_buildable_id(GTK_BUILDABLE(pTabWidget), rIdent);
+ gtk_notebook_insert_page(pNotebook, pChild, pTabWidget, nPos);
+ gtk_widget_show(pChild);
+ gtk_widget_show(pTabWidget);
+
+ if (nPos != -1)
+ {
+ unsigned int nPageIndex = static_cast<unsigned int>(nPos);
+ if (nPageIndex < m_aPages.size())
+ m_aPages.insert(m_aPages.begin() + nPageIndex, nullptr);
+ }
+
+ enable_notify_events();
+ }
+
+ void make_overflow_boxes()
+ {
+ m_pOverFlowBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
+ GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pNotebook));
+ container_add(pParent, GTK_WIDGET(m_pOverFlowBox));
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_append(m_pOverFlowBox, GTK_WIDGET(m_pOverFlowNotebook));
+#else
+ gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pOverFlowNotebook), false, false, 0);
+#endif
+ g_object_ref(m_pNotebook);
+ container_remove(pParent, GTK_WIDGET(m_pNotebook));
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_append(m_pOverFlowBox, GTK_WIDGET(m_pNotebook));
+#else
+ gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pNotebook), true, true, 0);
+#endif
+ // coverity[freed_arg : FALSE] - this does not free m_pNotebook , it is reffed by pParent
+ g_object_unref(m_pNotebook);
+ gtk_widget_show(GTK_WIDGET(m_pOverFlowBox));
+ }
+
+ void split_notebooks()
+ {
+ // get the original preferred size for the notebook, the sane width
+ // expected here depends on the notebooks all initially having
+ // scrollable tabs enabled
+ GtkAllocation alloc;
+ gtk_widget_get_allocation(GTK_WIDGET(m_pNotebook), &alloc);
+
+ // toggle the direction of the split since the last time
+ m_bOverFlowBoxIsStart = !m_bOverFlowBoxIsStart;
+ if (!m_pOverFlowBox)
+ make_overflow_boxes();
+
+ // don't scroll the tabs anymore
+ // coverity[pass_freed_arg : FALSE] - m_pNotebook is not freed here
+ gtk_notebook_set_scrollable(m_pNotebook, false);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_freeze_child_notify(GTK_WIDGET(m_pNotebook));
+ gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
+#else
+ g_object_freeze_notify(G_OBJECT(m_pNotebook));
+ g_object_freeze_notify(G_OBJECT(m_pOverFlowNotebook));
+#endif
+
+ gtk_widget_show(GTK_WIDGET(m_pOverFlowNotebook));
+
+ gint nPages;
+
+ GtkRequisition size1, size2;
+
+ if (!m_nStartTabCount && !m_nEndTabCount)
+ {
+ nPages = gtk_notebook_get_n_pages(m_pNotebook);
+
+ std::vector<int> aLabelWidths;
+ //move tabs to the overflow notebook
+ for (int i = 0; i < nPages; ++i)
+ {
+ OUString sLabel(get_tab_label_text(m_pNotebook, i));
+ aLabelWidths.push_back(get_pixel_size(sLabel).Width());
+ }
+ int row_width = std::accumulate(aLabelWidths.begin(), aLabelWidths.end(), 0) / 2;
+ int count = 0;
+ for (int i = 0; i < nPages; ++i)
+ {
+ count += aLabelWidths[i];
+ if (count >= row_width)
+ {
+ m_nStartTabCount = i;
+ break;
+ }
+ }
+
+ m_nEndTabCount = nPages - m_nStartTabCount;
+ }
+
+ //move the tabs to the overflow notebook
+ int i = 0;
+ int nOverFlowPages = m_nStartTabCount;
+ while (nOverFlowPages)
+ {
+ OUString sIdent(get_page_ident(m_pNotebook, 0));
+ OUString sLabel(get_tab_label_text(m_pNotebook, 0));
+ remove_page(m_pNotebook, sIdent);
+ insert_page(m_pOverFlowNotebook, sIdent, sLabel, gtk_grid_new(), -1);
+ GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pOverFlowNotebook,
+ gtk_notebook_get_nth_page(m_pOverFlowNotebook, i));
+ gtk_widget_set_hexpand(pTabWidget, true);
+
+ --nOverFlowPages;
+ ++i;
+ }
+
+ for (i = 0; i < m_nEndTabCount; ++i)
+ {
+ GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook,
+ gtk_notebook_get_nth_page(m_pNotebook, i));
+ gtk_widget_set_hexpand(pTabWidget, true);
+ }
+
+ // have to have some tab as the active tab of the overflow notebook
+ append_useless_page(m_pOverFlowNotebook);
+ gtk_notebook_set_current_page(m_pOverFlowNotebook, gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1);
+ if (gtk_widget_has_focus(GTK_WIDGET(m_pOverFlowNotebook)))
+ gtk_widget_grab_focus(GTK_WIDGET(m_pNotebook));
+
+ // add this temporarily to the normal notebook to measure how wide
+ // the row would be if switched to the other notebook
+ append_useless_page(m_pNotebook);
+
+ gtk_widget_get_preferred_size(GTK_WIDGET(m_pNotebook), nullptr, &size1);
+ gtk_widget_get_preferred_size(GTK_WIDGET(m_pOverFlowNotebook), nullptr, &size2);
+
+ auto nWidth = std::max(size1.width, size2.width);
+ gtk_widget_set_size_request(GTK_WIDGET(m_pNotebook), nWidth, alloc.height);
+ gtk_widget_set_size_request(GTK_WIDGET(m_pOverFlowNotebook), nWidth, -1);
+
+ // remove it once we've measured it
+ remove_page(m_pNotebook, u"useless");
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
+ gtk_widget_thaw_child_notify(GTK_WIDGET(m_pNotebook));
+#else
+ g_object_thaw_notify(G_OBJECT(m_pOverFlowNotebook));
+ g_object_thaw_notify(G_OBJECT(m_pNotebook));
+#endif
+
+ m_bOverFlowBoxActive = true;
+ }
+
+ static gboolean launch_split_notebooks(GtkInstanceNotebook* pThis)
+ {
+ int nCurrentPage = pThis->get_current_page();
+ pThis->split_notebooks();
+ pThis->set_current_page(nCurrentPage);
+ pThis->m_nLaunchSplitTimeoutId = 0;
+ return false;
+ }
+
+ // tdf#120371
+ // https://developer.gnome.org/hig-book/unstable/controls-notebooks.html.en#controls-too-many-tabs
+ // if no of tabs > 6, but only if the notebook would auto-scroll, then split the tabs over
+ // two notebooks. Checking for the auto-scroll allows themes like Ambience under Ubuntu 16.04 to keep
+ // tabs in a single row when they would fit
+ void signal_notebook_size_allocate()
+ {
+ if (m_bOverFlowBoxActive || m_nLaunchSplitTimeoutId)
+ return;
+ disable_notify_events();
+ gint nPages = gtk_notebook_get_n_pages(m_pNotebook);
+ if (nPages > 6 && gtk_notebook_get_tab_pos(m_pNotebook) == GTK_POS_TOP)
+ {
+ for (gint i = 0; i < nPages; ++i)
+ {
+ GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook, gtk_notebook_get_nth_page(m_pNotebook, i));
+#if GTK_CHECK_VERSION(4, 0, 0)
+ bool bTabVisible = gtk_widget_get_child_visible(gtk_widget_get_parent(pTabWidget));
+#else
+ bool bTabVisible = gtk_widget_get_child_visible(pTabWidget);
+#endif
+ if (!bTabVisible)
+ {
+ m_nLaunchSplitTimeoutId = g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(launch_split_notebooks), this, nullptr);
+ break;
+ }
+ }
+ }
+ enable_notify_events();
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ DECL_LINK(SizeAllocateHdl, void*, void);
+#else
+ static void signalSizeAllocate(GtkWidget*, GdkRectangle*, gpointer widget)
+ {
+ GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
+ pThis->signal_notebook_size_allocate();
+ }
+#endif
+
+ bool signal_focus(GtkDirectionType direction)
+ {
+ if (!m_bOverFlowBoxActive)
+ return false;
+
+ int nPage = gtk_notebook_get_current_page(m_pNotebook);
+ if (direction == GTK_DIR_LEFT && nPage == 0)
+ {
+ auto nOverFlowLen = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
+ gtk_notebook_set_current_page(m_pOverFlowNotebook, nOverFlowLen - 1);
+ return true;
+ }
+ else if (direction == GTK_DIR_RIGHT && nPage == gtk_notebook_get_n_pages(m_pNotebook) - 1)
+ {
+ gtk_notebook_set_current_page(m_pOverFlowNotebook, 0);
+ return true;
+ }
+
+ return false;
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalFocus(GtkNotebook* notebook, GtkDirectionType direction, gpointer widget)
+ {
+ // if the notebook widget itself has focus
+ if (gtk_widget_is_focus(GTK_WIDGET(notebook)))
+ {
+ GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
+ return pThis->signal_focus(direction);
+ }
+ return false;
+ }
+#endif
+
+ // ctrl + page_up/ page_down
+ bool signal_change_current_page(gint arg1)
+ {
+ bool bHandled = signal_focus(arg1 < 0 ? GTK_DIR_LEFT : GTK_DIR_RIGHT);
+ if (bHandled)
+ g_signal_stop_emission_by_name(m_pNotebook, "change-current-page");
+ return false;
+ }
+
+ static gboolean signalChangeCurrentPage(GtkNotebook*, gint arg1, gpointer widget)
+ {
+ if (arg1 == 0)
+ return true;
+ GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
+ return pThis->signal_change_current_page(arg1);
+ }
+
+public:
+ GtkInstanceNotebook(GtkNotebook* pNotebook, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pNotebook), pBuilder, bTakeOwnership)
+ , m_pNotebook(pNotebook)
+ , m_pOverFlowBox(nullptr)
+ , m_pOverFlowNotebook(GTK_NOTEBOOK(gtk_notebook_new()))
+ , m_nSwitchPageSignalId(g_signal_connect(pNotebook, "switch-page", G_CALLBACK(signalSwitchPage), this))
+ , m_nOverFlowSwitchPageSignalId(g_signal_connect(m_pOverFlowNotebook, "switch-page", G_CALLBACK(signalOverFlowSwitchPage), this))
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_pLayout(nullptr)
+#else
+ , m_nNotebookSizeAllocateSignalId(0)
+ , m_nFocusSignalId(g_signal_connect(pNotebook, "focus", G_CALLBACK(signalFocus), this))
+#endif
+ , m_nChangeCurrentPageId(g_signal_connect(pNotebook, "change-current-page", G_CALLBACK(signalChangeCurrentPage), this))
+ , m_nLaunchSplitTimeoutId(0)
+ , m_bOverFlowBoxActive(false)
+ , m_bOverFlowBoxIsStart(false)
+ , m_bInternalPageChange(false)
+ , m_nStartTabCount(0)
+ , m_nEndTabCount(0)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_add_events(GTK_WIDGET(pNotebook), GDK_SCROLL_MASK);
+#endif
+ gint nPages = gtk_notebook_get_n_pages(m_pNotebook);
+ if (nPages > 6)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_nNotebookSizeAllocateSignalId = g_signal_connect_after(pNotebook, "size-allocate", G_CALLBACK(signalSizeAllocate), this);
+#else
+ m_pLayout = NOTIFYING_LAYOUT(g_object_new(notifying_layout_get_type(), nullptr));
+ notifying_layout_start_watch(m_pLayout, GTK_WIDGET(pNotebook), LINK(this, GtkInstanceNotebook, SizeAllocateHdl));
+#endif
+ }
+ gtk_notebook_set_show_border(m_pOverFlowNotebook, false);
+
+ // tdf#122623 it's nigh impossible to have a GtkNotebook without an active (checked) tab, so try and theme
+ // the unwanted tab into invisibility via the 'overflow' class themed by global CreateStyleProvider
+ GtkStyleContext *pNotebookContext = gtk_widget_get_style_context(GTK_WIDGET(m_pOverFlowNotebook));
+ gtk_style_context_add_class(pNotebookContext, "overflow");
+ }
+
+ virtual int get_current_page() const override
+ {
+ int nPage = gtk_notebook_get_current_page(m_pNotebook);
+ if (nPage == -1)
+ return nPage;
+ if (m_bOverFlowBoxIsStart)
+ {
+ auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
+ // add count of overflow pages, minus the extra tab
+ nPage += nOverFlowLen;
+ }
+ return nPage;
+ }
+
+ virtual OUString get_page_ident(int nPage) const override
+ {
+ auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
+ auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
+ if (m_bOverFlowBoxIsStart)
+ {
+ if (nPage < nOverFlowLen)
+ return get_page_ident(m_pOverFlowNotebook, nPage);
+ nPage -= nOverFlowLen;
+ return get_page_ident(m_pNotebook, nPage);
+ }
+ else
+ {
+ if (nPage < nMainLen)
+ return get_page_ident(m_pNotebook, nPage);
+ nPage -= nMainLen;
+ return get_page_ident(m_pOverFlowNotebook, nPage);
+ }
+ }
+
+ virtual OUString get_current_page_ident() const override
+ {
+ const int nPage = get_current_page();
+ return nPage != -1 ? get_page_ident(nPage) : OUString();
+ }
+
+ virtual int get_page_index(const OUString& rIdent) const override
+ {
+ auto nMainIndex = get_page_number(m_pNotebook, rIdent);
+ auto nOverFlowIndex = get_page_number(m_pOverFlowNotebook, rIdent);
+
+ if (nMainIndex == -1 && nOverFlowIndex == -1)
+ return -1;
+
+ if (m_bOverFlowBoxIsStart)
+ {
+ if (nOverFlowIndex != -1)
+ return nOverFlowIndex;
+ else
+ {
+ auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
+ return nMainIndex + nOverFlowLen;
+ }
+ }
+ else
+ {
+ if (nMainIndex != -1)
+ return nMainIndex;
+ else
+ {
+ auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
+ return nOverFlowIndex + nMainLen;
+ }
+ }
+ }
+
+ virtual weld::Container* get_page(const OUString& rIdent) const override
+ {
+ int nPage = get_page_index(rIdent);
+ if (nPage < 0)
+ return nullptr;
+
+ GtkWidget* pChild;
+ if (m_bOverFlowBoxIsStart)
+ {
+ auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
+ if (nPage < nOverFlowLen)
+ pChild = gtk_notebook_get_nth_page(m_pOverFlowNotebook, nPage);
+ else
+ {
+ nPage -= nOverFlowLen;
+ pChild = gtk_notebook_get_nth_page(m_pNotebook, nPage);
+ }
+ }
+ else
+ {
+ auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
+ if (nPage < nMainLen)
+ pChild = gtk_notebook_get_nth_page(m_pNotebook, nPage);
+ else
+ {
+ nPage -= nMainLen;
+ pChild = gtk_notebook_get_nth_page(m_pOverFlowNotebook, nPage);
+ }
+ }
+
+ unsigned int nPageIndex = static_cast<unsigned int>(nPage);
+ if (m_aPages.size() < nPageIndex + 1)
+ m_aPages.resize(nPageIndex + 1);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!m_aPages[nPageIndex])
+ m_aPages[nPageIndex].reset(new GtkInstanceContainer(GTK_CONTAINER(pChild), m_pBuilder, false));
+#else
+ if (!m_aPages[nPageIndex])
+ m_aPages[nPageIndex].reset(new GtkInstanceContainer(pChild, m_pBuilder, false));
+#endif
+ return m_aPages[nPageIndex].get();
+ }
+
+ virtual void set_current_page(int nPage) override
+ {
+ // normally we'd call disable_notify_events/enable_notify_events here,
+ // but the notebook is complicated by the need to support the
+ // double-decker hackery so for simplicity just flag that the page
+ // change is not a directly user-triggered one
+ bool bInternalPageChange = m_bInternalPageChange;
+ m_bInternalPageChange = true;
+
+ if (m_bOverFlowBoxIsStart)
+ {
+ auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
+ if (nPage < nOverFlowLen)
+ gtk_notebook_set_current_page(m_pOverFlowNotebook, nPage);
+ else
+ {
+ nPage -= nOverFlowLen;
+ gtk_notebook_set_current_page(m_pNotebook, nPage);
+ }
+ }
+ else
+ {
+ auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
+ if (nPage < nMainLen)
+ gtk_notebook_set_current_page(m_pNotebook, nPage);
+ else
+ {
+ nPage -= nMainLen;
+ gtk_notebook_set_current_page(m_pOverFlowNotebook, nPage);
+ }
+ }
+
+ m_bInternalPageChange = bInternalPageChange;
+ }
+
+ virtual void set_current_page(const OUString& rIdent) override
+ {
+ gint nPage = get_page_index(rIdent);
+ set_current_page(nPage);
+ }
+
+ virtual int get_n_pages() const override
+ {
+ int nLen = gtk_notebook_get_n_pages(m_pNotebook);
+ if (m_bOverFlowBoxActive)
+ nLen += gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
+ return nLen;
+ }
+
+ virtual OUString get_tab_label_text(const OUString& rIdent) const override
+ {
+ gint nPageNum = get_page_number(m_pNotebook, rIdent);
+ if (nPageNum != -1)
+ return get_tab_label_text(m_pNotebook, nPageNum);
+ nPageNum = get_page_number(m_pOverFlowNotebook, rIdent);
+ if (nPageNum != -1)
+ return get_tab_label_text(m_pOverFlowNotebook, nPageNum);
+ return OUString();
+ }
+
+ virtual void set_tab_label_text(const OUString& rIdent, const OUString& rText) override
+ {
+ gint nPageNum = get_page_number(m_pNotebook, rIdent);
+ if (nPageNum != -1)
+ {
+ set_tab_label_text(m_pNotebook, nPageNum, rText);
+ return;
+ }
+ nPageNum = get_page_number(m_pOverFlowNotebook, rIdent);
+ if (nPageNum != -1)
+ {
+ set_tab_label_text(m_pOverFlowNotebook, nPageNum, rText);
+ }
+ }
+
+ virtual void set_show_tabs(bool bShow) override
+ {
+ if (m_bOverFlowBoxActive)
+ {
+ unsplit_notebooks();
+ reset_split_data();
+ }
+
+ gtk_notebook_set_show_tabs(m_pNotebook, bShow);
+ gtk_notebook_set_show_tabs(m_pOverFlowNotebook, bShow);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pNotebook, m_nSwitchPageSignalId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_block(m_pNotebook, m_nFocusSignalId);
+#endif
+ g_signal_handler_block(m_pNotebook, m_nChangeCurrentPageId);
+ g_signal_handler_block(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
+#endif
+ g_object_freeze_notify(G_OBJECT(m_pOverFlowNotebook));
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_object_thaw_notify(G_OBJECT(m_pOverFlowNotebook));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
+#endif
+ g_signal_handler_unblock(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
+ g_signal_handler_unblock(m_pNotebook, m_nSwitchPageSignalId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_unblock(m_pNotebook, m_nFocusSignalId);
+#endif
+ g_signal_handler_unblock(m_pNotebook, m_nChangeCurrentPageId);
+ }
+
+ void reset_split_data()
+ {
+ // reset overflow and allow it to be recalculated if necessary
+ gtk_widget_hide(GTK_WIDGET(m_pOverFlowNotebook));
+ m_bOverFlowBoxActive = false;
+ m_nStartTabCount = 0;
+ m_nEndTabCount = 0;
+ }
+
+ virtual void remove_page(const OUString& rIdent) override
+ {
+ if (m_bOverFlowBoxActive)
+ {
+ unsplit_notebooks();
+ reset_split_data();
+ }
+
+ unsigned int nPageIndex = remove_page(m_pNotebook, rIdent);
+ if (nPageIndex < m_aPages.size())
+ m_aPages.erase(m_aPages.begin() + nPageIndex);
+ }
+
+ virtual void insert_page(const OUString& rIdent, const OUString& rLabel, int nPos) override
+ {
+ if (m_bOverFlowBoxActive)
+ {
+ unsplit_notebooks();
+ reset_split_data();
+ }
+
+ // reset overflow and allow it to be recalculated if necessary
+ gtk_widget_hide(GTK_WIDGET(m_pOverFlowNotebook));
+ m_bOverFlowBoxActive = false;
+
+ insert_page(m_pNotebook, rIdent, rLabel, gtk_grid_new(), nPos);
+ }
+
+ virtual ~GtkInstanceNotebook() override
+ {
+ if (m_nLaunchSplitTimeoutId)
+ g_source_remove(m_nLaunchSplitTimeoutId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (m_nNotebookSizeAllocateSignalId)
+ g_signal_handler_disconnect(m_pNotebook, m_nNotebookSizeAllocateSignalId);
+#else
+ if (m_pLayout)
+ {
+ // put it back how we found it initially
+ notifying_layout_stop_watch(m_pLayout);
+ }
+#endif
+ g_signal_handler_disconnect(m_pNotebook, m_nSwitchPageSignalId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pNotebook, m_nFocusSignalId);
+#endif
+ g_signal_handler_disconnect(m_pNotebook, m_nChangeCurrentPageId);
+ g_signal_handler_disconnect(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(GTK_WIDGET(m_pOverFlowNotebook));
+#else
+ GtkWidget* pOverFlowWidget = GTK_WIDGET(m_pOverFlowNotebook);
+ g_clear_pointer(&pOverFlowWidget, gtk_widget_unparent);
+#endif
+ if (!m_pOverFlowBox)
+ return;
+
+ // put it back to how we found it initially
+ GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pOverFlowBox));
+ g_object_ref(m_pNotebook);
+ container_remove(GTK_WIDGET(m_pOverFlowBox), GTK_WIDGET(m_pNotebook));
+ container_add(GTK_WIDGET(pParent), GTK_WIDGET(m_pNotebook));
+ g_object_unref(m_pNotebook);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(GTK_WIDGET(m_pOverFlowBox));
+#else
+ GtkWidget* pOverFlowBox = GTK_WIDGET(m_pOverFlowBox);
+ g_clear_pointer(&pOverFlowBox, gtk_widget_unparent);
+#endif
+ }
+};
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+IMPL_LINK_NOARG(GtkInstanceNotebook, SizeAllocateHdl, void*, void)
+{
+ signal_notebook_size_allocate();
+}
+#endif
+
+
+OUString vcl_font_to_css(const vcl::Font& rFont)
+{
+ OUStringBuffer sCSS(
+ "font-family: \"" + rFont.GetFamilyName() + "\"; "
+ "font-size: " + OUString::number(rFont.GetFontSize().Height()) + "pt; ");
+ switch (rFont.GetItalic())
+ {
+ case ITALIC_NONE:
+ sCSS.append("font-style: normal; ");
+ break;
+ case ITALIC_NORMAL:
+ sCSS.append("font-style: italic; ");
+ break;
+ case ITALIC_OBLIQUE:
+ sCSS.append("font-style: oblique; ");
+ break;
+ default:
+ break;
+ }
+ switch (rFont.GetWeight())
+ {
+ case WEIGHT_ULTRALIGHT:
+ sCSS.append("font-weight: 200; ");
+ break;
+ case WEIGHT_LIGHT:
+ sCSS.append("font-weight: 300; ");
+ break;
+ case WEIGHT_NORMAL:
+ sCSS.append("font-weight: 400; ");
+ break;
+ case WEIGHT_BOLD:
+ sCSS.append("font-weight: 700; ");
+ break;
+ case WEIGHT_ULTRABOLD:
+ sCSS.append("font-weight: 800; ");
+ break;
+ default:
+ break;
+ }
+ switch (rFont.GetWidthType())
+ {
+ case WIDTH_ULTRA_CONDENSED:
+ sCSS.append("font-stretch: ultra-condensed; ");
+ break;
+ case WIDTH_EXTRA_CONDENSED:
+ sCSS.append("font-stretch: extra-condensed; ");
+ break;
+ case WIDTH_CONDENSED:
+ sCSS.append("font-stretch: condensed; ");
+ break;
+ case WIDTH_SEMI_CONDENSED:
+ sCSS.append("font-stretch: semi-condensed; ");
+ break;
+ case WIDTH_NORMAL:
+ sCSS.append("font-stretch: normal; ");
+ break;
+ case WIDTH_SEMI_EXPANDED:
+ sCSS.append("font-stretch: semi-expanded; ");
+ break;
+ case WIDTH_EXPANDED:
+ sCSS.append("font-stretch: expanded; ");
+ break;
+ case WIDTH_EXTRA_EXPANDED:
+ sCSS.append("font-stretch: extra-expanded; ");
+ break;
+ case WIDTH_ULTRA_EXPANDED:
+ sCSS.append("font-stretch: ultra-expanded; ");
+ break;
+ default:
+ break;
+ }
+ return sCSS.toString();
+}
+
+void update_attr_list(PangoAttrList* pAttrList, const vcl::Font& rFont)
+{
+ pango_attr_list_change(pAttrList, pango_attr_family_new(OUStringToOString(rFont.GetFamilyName(), RTL_TEXTENCODING_UTF8).getStr()));
+ pango_attr_list_change(pAttrList, pango_attr_size_new(rFont.GetFontSize().Height() * PANGO_SCALE));
+
+ switch (rFont.GetItalic())
+ {
+ case ITALIC_NONE:
+ pango_attr_list_change(pAttrList, pango_attr_style_new(PANGO_STYLE_NORMAL));
+ break;
+ case ITALIC_NORMAL:
+ pango_attr_list_change(pAttrList, pango_attr_style_new(PANGO_STYLE_ITALIC));
+ break;
+ case ITALIC_OBLIQUE:
+ pango_attr_list_change(pAttrList, pango_attr_style_new(PANGO_STYLE_OBLIQUE));
+ break;
+ default:
+ break;
+ }
+ switch (rFont.GetWeight())
+ {
+ case WEIGHT_ULTRALIGHT:
+ pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRALIGHT));
+ break;
+ case WEIGHT_LIGHT:
+ pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_LIGHT));
+ break;
+ case WEIGHT_NORMAL:
+ pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_NORMAL));
+ break;
+ case WEIGHT_BOLD:
+ pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
+ break;
+ case WEIGHT_ULTRABOLD:
+ pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRABOLD));
+ break;
+ default:
+ break;
+ }
+ switch (rFont.GetWidthType())
+ {
+ case WIDTH_ULTRA_CONDENSED:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_CONDENSED));
+ break;
+ case WIDTH_EXTRA_CONDENSED:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_CONDENSED));
+ break;
+ case WIDTH_CONDENSED:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_CONDENSED));
+ break;
+ case WIDTH_SEMI_CONDENSED:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_CONDENSED));
+ break;
+ case WIDTH_NORMAL:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_NORMAL));
+ break;
+ case WIDTH_SEMI_EXPANDED:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_EXPANDED));
+ break;
+ case WIDTH_EXPANDED:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXPANDED));
+ break;
+ case WIDTH_EXTRA_EXPANDED:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_EXPANDED));
+ break;
+ case WIDTH_ULTRA_EXPANDED:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_EXPANDED));
+ break;
+ default:
+ break;
+ }
+}
+
+gboolean filter_pango_attrs(PangoAttribute *attr, gpointer data)
+{
+ PangoAttrType* pFilterAttrs = static_cast<PangoAttrType*>(data);
+ while (*pFilterAttrs)
+ {
+ if (attr->klass->type == *pFilterAttrs)
+ return true;
+ ++pFilterAttrs;
+ }
+ return false;
+}
+
+void set_font(GtkLabel* pLabel, const vcl::Font& rFont)
+{
+ PangoAttrList* pOrigList = gtk_label_get_attributes(pLabel);
+ PangoAttrList* pAttrList = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
+
+ if (pOrigList)
+ {
+ // tdf#143443 remove both PANGO_ATTR_ABSOLUTE_SIZE and PANGO_ATTR_SIZE
+ // because pango_attr_list_change(..., pango_attr_size_new...) isn't
+ // sufficient on its own to ensure a new size sticks.
+ PangoAttrType aFilterAttrs[] = {PANGO_ATTR_ABSOLUTE_SIZE, PANGO_ATTR_SIZE, PANGO_ATTR_INVALID};
+ PangoAttrList* pRemovedAttrs = pango_attr_list_filter(pAttrList, filter_pango_attrs, &aFilterAttrs);
+ pango_attr_list_unref(pRemovedAttrs);
+ }
+
+ update_attr_list(pAttrList, rFont);
+ gtk_label_set_attributes(pLabel, pAttrList);
+ pango_attr_list_unref(pAttrList);
+}
+
+}
+
+namespace {
+
+class WidgetBackground
+{
+private:
+ GtkWidget* m_pWidget;
+ GtkCssProvider* m_pCustomCssProvider;
+ std::unique_ptr<utl::TempFileNamed> m_xCustomImage;
+
+public:
+ // See: https://developer.gnome.org/Buttons/
+ void use_custom_content(const VirtualDevice* pDevice)
+ {
+ GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(m_pWidget);
+
+ if (m_pCustomCssProvider)
+ {
+ gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pCustomCssProvider));
+ m_pCustomCssProvider = nullptr;
+ }
+
+ m_xCustomImage.reset();
+
+ if (!pDevice)
+ return;
+
+ m_xCustomImage.reset(new utl::TempFileNamed);
+ m_xCustomImage->EnableKillingFile(true);
+
+ cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
+ Size aSize = pDevice->GetOutputSizePixel();
+ cairo_surface_write_to_png(surface, OUStringToOString(m_xCustomImage->GetFileName(), osl_getThreadTextEncoding()).getStr());
+
+ m_pCustomCssProvider = gtk_css_provider_new();
+ OUString aBuffer = "* { background-image: url(\"" + m_xCustomImage->GetURL() + "\"); "
+ "background-size: " + OUString::number(aSize.Width()) + "px " + OUString::number(aSize.Height()) + "px; "
+ "border-radius: 0; border-width: 0; }";
+ OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
+ css_provider_load_from_data(m_pCustomCssProvider, aResult.getStr(), aResult.getLength());
+ gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pCustomCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+public:
+ WidgetBackground(GtkWidget* pWidget)
+ : m_pWidget(pWidget)
+ , m_pCustomCssProvider(nullptr)
+ {
+ }
+
+ ~WidgetBackground()
+ {
+ if (m_pCustomCssProvider)
+ use_custom_content(nullptr);
+ assert(!m_pCustomCssProvider);
+ }
+};
+
+class WidgetFont
+{
+private:
+ GtkWidget* m_pWidget;
+ GtkCssProvider* m_pFontCssProvider;
+ std::unique_ptr<vcl::Font> m_xFont;
+public:
+ WidgetFont(GtkWidget* pWidget)
+ : m_pWidget(pWidget)
+ , m_pFontCssProvider(nullptr)
+ {
+ }
+
+ void use_custom_font(const vcl::Font* pFont, std::u16string_view rCSSSelector)
+ {
+ GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(m_pWidget);
+ if (m_pFontCssProvider)
+ {
+ gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFontCssProvider));
+ m_pFontCssProvider = nullptr;
+ }
+
+ m_xFont.reset();
+
+ if (!pFont)
+ return;
+
+ m_xFont.reset(new vcl::Font(*pFont));
+ m_pFontCssProvider = gtk_css_provider_new();
+ OUString aBuffer = rCSSSelector + OUString::Concat(" { ") + vcl_font_to_css(*pFont) + OUString::Concat(" }");
+ OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
+ css_provider_load_from_data(m_pFontCssProvider, aResult.getStr(), aResult.getLength());
+ gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFontCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+ const vcl::Font* get_custom_font() const
+ {
+ return m_xFont.get();
+ }
+
+ ~WidgetFont()
+ {
+ if (m_pFontCssProvider)
+ use_custom_font(nullptr, u"");
+ assert(!m_pFontCssProvider);
+ }
+};
+
+class GtkInstanceButton : public GtkInstanceWidget, public virtual weld::Button
+{
+private:
+ GtkButton* m_pButton;
+ gulong m_nSignalId;
+ std::optional<vcl::Font> m_xFont;
+ WidgetBackground m_aCustomBackground;
+
+ static void signalClicked(GtkButton*, gpointer widget)
+ {
+ GtkInstanceButton* pThis = static_cast<GtkInstanceButton*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_clicked();
+ }
+
+ virtual void ensureMouseEventWidget() override
+ {
+ // The GtkButton is sufficient to get mouse events without an intermediate GtkEventBox
+ if (!m_pMouseEventBox)
+ m_pMouseEventBox = m_pWidget;
+ }
+
+public:
+ GtkInstanceButton(GtkButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
+ , m_pButton(pButton)
+ , m_nSignalId(g_signal_connect(pButton, "clicked", G_CALLBACK(signalClicked), this))
+ , m_aCustomBackground(GTK_WIDGET(pButton))
+ {
+ g_object_set_data(G_OBJECT(m_pButton), "g-lo-GtkInstanceButton", this);
+ }
+
+ virtual void set_label(const OUString& rText) override
+ {
+ ::button_set_label(m_pButton, rText);
+ }
+
+ virtual void set_image(VirtualDevice* pDevice) override
+ {
+ ::button_set_image(m_pButton, pDevice);
+ }
+
+ virtual void set_from_icon_name(const OUString& rIconName) override
+ {
+ ::button_set_from_icon_name(m_pButton, rIconName);
+ }
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
+ {
+ ::button_set_image(m_pButton, rImage);
+ }
+
+ virtual void set_custom_button(VirtualDevice* pDevice) override
+ {
+ m_aCustomBackground.use_custom_content(pDevice);
+ }
+
+ virtual OUString get_label() const override
+ {
+ return ::button_get_label(m_pButton);
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_xFont = rFont;
+ GtkLabel* pChild = ::get_label_widget(GTK_WIDGET(m_pButton));
+ ::set_font(pChild, rFont);
+ }
+
+ virtual vcl::Font get_font() override
+ {
+ if (m_xFont)
+ return *m_xFont;
+ return GtkInstanceWidget::get_font();
+ }
+
+ // allow us to block buttons with click handlers making dialogs return a response
+ bool has_click_handler() const
+ {
+ return m_aClickHdl.IsSet();
+ }
+
+ void clear_click_handler()
+ {
+ m_aClickHdl = Link<Button&, void>();
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pButton, m_nSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pButton, m_nSignalId);
+ }
+
+ virtual ~GtkInstanceButton() override
+ {
+ g_object_steal_data(G_OBJECT(m_pButton), "g-lo-GtkInstanceButton");
+ g_signal_handler_disconnect(m_pButton, m_nSignalId);
+ }
+};
+
+}
+
+void GtkInstanceDialog::asyncresponse(gint ret)
+{
+ SolarMutexGuard aGuard;
+
+ if (ret == GTK_RESPONSE_HELP)
+ {
+ help();
+ return;
+ }
+
+ GtkInstanceButton* pClickHandler = has_click_handler(ret);
+ if (pClickHandler)
+ {
+ // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
+ if (ret == GTK_RESPONSE_DELETE_EVENT)
+ close(false);
+ return;
+ }
+
+ if (get_modal())
+ m_aDialogRun.dec_modal_count();
+ hide();
+
+ // move the self pointer, otherwise it might be de-allocated by time we try to reset it
+ auto xRunAsyncSelf = std::move(m_xRunAsyncSelf);
+ auto xDialogController = std::move(m_xDialogController);
+ auto aFunc = std::move(m_aFunc);
+
+ auto nResponseSignalId = m_nResponseSignalId;
+ auto nCancelSignalId = m_nCancelSignalId;
+ auto nSignalDeleteId = m_nSignalDeleteId;
+ m_nResponseSignalId = 0;
+ m_nCancelSignalId = 0;
+ m_nSignalDeleteId = 0;
+
+ if (aFunc)
+ aFunc(GtkToVcl(ret));
+
+ if (nResponseSignalId)
+ g_signal_handler_disconnect(m_pDialog, nResponseSignalId);
+ if (nCancelSignalId)
+ g_signal_handler_disconnect(m_pDialog, nCancelSignalId);
+ if (nSignalDeleteId)
+ g_signal_handler_disconnect(m_pDialog, nSignalDeleteId);
+
+ xDialogController.reset();
+ xRunAsyncSelf.reset();
+}
+
+int GtkInstanceDialog::run()
+{
+ // tdf#150723 "run" will make the dialog visible so drop m_aPosWhileInvis like show
+ m_aPosWhileInvis.reset();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_DIALOG(m_pDialog))
+ sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
+#endif
+ int ret;
+ while (true)
+ {
+ ret = m_aDialogRun.run();
+ if (ret == GTK_RESPONSE_HELP)
+ {
+ help();
+ continue;
+ }
+ else if (has_click_handler(ret))
+ continue;
+ break;
+ }
+ hide();
+ return GtkToVcl(ret);
+}
+
+weld::Button* GtkInstanceDialog::weld_widget_for_response(int nVclResponse)
+{
+ GtkButton* pButton = get_widget_for_response(VclToGtk(nVclResponse));
+ if (!pButton)
+ return nullptr;
+ return new GtkInstanceButton(pButton, m_pBuilder, false);
+}
+
+void GtkInstanceDialog::response(int nResponse)
+{
+ int nGtkResponse = VclToGtk(nResponse);
+ //unblock this response now when activated through code
+ if (GtkButton* pWidget = get_widget_for_response(nGtkResponse))
+ {
+ void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceButton");
+ GtkInstanceButton* pButton = static_cast<GtkInstanceButton*>(pData);
+ if (pButton)
+ pButton->clear_click_handler();
+ }
+ if (GTK_IS_DIALOG(m_pDialog))
+ gtk_dialog_response(GTK_DIALOG(m_pDialog), nGtkResponse);
+ else if (GTK_IS_ASSISTANT(m_pDialog))
+ {
+ if (!m_aDialogRun.loop_is_running())
+ asyncresponse(nGtkResponse);
+ else
+ {
+ m_aDialogRun.m_nResponseId = nGtkResponse;
+ m_aDialogRun.loop_quit();
+ }
+ }
+}
+
+void GtkInstanceDialog::close(bool bCloseSignal)
+{
+ GtkInstanceButton* pClickHandler = has_click_handler(GTK_RESPONSE_CANCEL);
+ if (pClickHandler)
+ {
+ if (bCloseSignal)
+ g_signal_stop_emission_by_name(m_pDialog, "close");
+ // make esc (bCloseSignal == true) or window-delete (bCloseSignal == false)
+ // act as if cancel button was pressed
+ pClickHandler->clicked();
+ return;
+ }
+ response(RET_CANCEL);
+}
+
+GtkInstanceButton* GtkInstanceDialog::has_click_handler(int nResponse)
+{
+ GtkInstanceButton* pButton = nullptr;
+ // e.g. map GTK_RESPONSE_DELETE_EVENT to GTK_RESPONSE_CANCEL
+ nResponse = VclToGtk(GtkToVcl(nResponse));
+ if (GtkButton* pWidget = get_widget_for_response(nResponse))
+ {
+ void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceButton");
+ pButton = static_cast<GtkInstanceButton*>(pData);
+ if (pButton && !pButton->has_click_handler())
+ pButton = nullptr;
+ }
+ return pButton;
+}
+
+namespace {
+
+class GtkInstanceToggleButton : public GtkInstanceButton, public virtual weld::ToggleButton
+{
+protected:
+ GtkToggleButton* m_pToggleButton;
+ gulong m_nToggledSignalId;
+private:
+ static void signalToggled(GtkToggleButton*, gpointer widget)
+ {
+ GtkInstanceToggleButton* pThis = static_cast<GtkInstanceToggleButton*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_toggled();
+ }
+public:
+ GtkInstanceToggleButton(GtkToggleButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceButton(GTK_BUTTON(pButton), pBuilder, bTakeOwnership)
+ , m_pToggleButton(pButton)
+ , m_nToggledSignalId(g_signal_connect(m_pToggleButton, "toggled", G_CALLBACK(signalToggled), this))
+ {
+ }
+
+ virtual void set_active(bool active) override
+ {
+ disable_notify_events();
+ set_inconsistent(false);
+ gtk_toggle_button_set_active(m_pToggleButton, active);
+ enable_notify_events();
+ }
+
+ virtual bool get_active() const override
+ {
+ return gtk_toggle_button_get_active(m_pToggleButton);
+ }
+
+ virtual void set_inconsistent(bool inconsistent) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (inconsistent)
+ gtk_widget_set_state_flags(GTK_WIDGET(m_pToggleButton), GTK_STATE_FLAG_INCONSISTENT, false);
+ else
+ gtk_widget_unset_state_flags(GTK_WIDGET(m_pToggleButton), GTK_STATE_FLAG_INCONSISTENT);
+#else
+ gtk_toggle_button_set_inconsistent(m_pToggleButton, inconsistent);
+#endif
+ }
+
+ virtual bool get_inconsistent() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_widget_get_state_flags(GTK_WIDGET(m_pToggleButton)) & GTK_STATE_FLAG_INCONSISTENT;
+#else
+ return gtk_toggle_button_get_inconsistent(m_pToggleButton);
+#endif
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pToggleButton, m_nToggledSignalId);
+ GtkInstanceButton::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceButton::enable_notify_events();
+ g_signal_handler_unblock(m_pToggleButton, m_nToggledSignalId);
+ }
+
+ virtual ~GtkInstanceToggleButton() override
+ {
+ g_signal_handler_disconnect(m_pToggleButton, m_nToggledSignalId);
+ }
+};
+
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+
+namespace {
+
+void do_grab(GtkWidget* pWidget)
+{
+ GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
+ GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
+ gdk_seat_grab(pSeat, widget_get_surface(pWidget),
+ GDK_SEAT_CAPABILITY_KEYBOARD, true, nullptr, nullptr, nullptr, nullptr);
+}
+
+void do_ungrab(GtkWidget* pWidget)
+{
+ GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
+ GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
+ gdk_seat_ungrab(pSeat);
+}
+
+GtkPositionType show_menu_older_gtk(GtkWidget* pMenuButton, GtkWindow* pMenu, const GdkRectangle& rAnchor,
+ weld::Placement ePlace, bool bTryShrink)
+{
+ //place the toplevel just below its launcher button
+ GtkWidget* pToplevel = widget_get_toplevel(pMenuButton);
+ gtk_coord x, y, absx, absy;
+ gtk_widget_translate_coordinates(pMenuButton, pToplevel, rAnchor.x, rAnchor.y, &x, &y);
+ GdkSurface* pWindow = widget_get_surface(pToplevel);
+ gdk_window_get_position(pWindow, &absx, &absy);
+
+ x += absx;
+ y += absy;
+
+ gint nButtonHeight = rAnchor.height;
+ gint nButtonWidth = rAnchor.width;
+ if (ePlace == weld::Placement::Under)
+ y += nButtonHeight;
+ else
+ x += nButtonWidth;
+
+ gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), pMenu);
+ gtk_window_set_transient_for(pMenu, GTK_WINDOW(pToplevel));
+
+ gint nMenuWidth, nMenuHeight;
+ gtk_widget_get_size_request(GTK_WIDGET(pMenu), &nMenuWidth, &nMenuHeight);
+
+ if (nMenuWidth == -1 || nMenuHeight == -1)
+ {
+ GtkRequisition req;
+ gtk_widget_get_preferred_size(GTK_WIDGET(pMenu), nullptr, &req);
+ if (nMenuWidth == -1)
+ nMenuWidth = req.width;
+ if (nMenuHeight == -1)
+ nMenuHeight = req.height;
+ }
+
+ bool bSwapForRTL = SwapForRTL(pMenuButton);
+ if (bSwapForRTL)
+ {
+ if (ePlace == weld::Placement::Under)
+ x += nButtonWidth;
+ else
+ x -= nButtonWidth;
+ x -= nMenuWidth;
+ }
+
+ tools::Rectangle aWorkArea(::get_monitor_workarea(pMenuButton));
+
+ // shrink it a little, I find it reassuring to see a little margin with a
+ // long menu to know the menu is fully on screen
+ aWorkArea.AdjustTop(8);
+ aWorkArea.AdjustBottom(-8);
+ aWorkArea.AdjustLeft(8);
+ aWorkArea.AdjustRight(-8);
+
+ GtkPositionType ePosUsed;
+
+ if (ePlace == weld::Placement::Under)
+ {
+ gint endx = x + nMenuWidth;
+ if (endx > aWorkArea.Right())
+ x -= endx - aWorkArea.Right();
+ if (x < 0)
+ x = 0;
+
+ ePosUsed = GTK_POS_BOTTOM;
+ gint endy = y + nMenuHeight;
+ gint nMissingBelow = endy - aWorkArea.Bottom();
+ if (nMissingBelow > 0)
+ {
+ gint nNewY = y - (nButtonHeight + nMenuHeight);
+ gint nMissingAbove = aWorkArea.Top() - nNewY;
+ if (nMissingAbove > 0)
+ {
+ if (bTryShrink)
+ {
+ if (nMissingBelow <= nMissingAbove)
+ nMenuHeight -= nMissingBelow;
+ else
+ {
+ nMenuHeight -= nMissingAbove;
+ y = aWorkArea.Top();
+ ePosUsed = GTK_POS_TOP;
+ }
+ gtk_widget_set_size_request(GTK_WIDGET(pMenu), nMenuWidth, nMenuHeight);
+ }
+ else
+ {
+ if (nMissingBelow <= nMissingAbove)
+ y -= nMissingBelow;
+ else
+ {
+ y = aWorkArea.Top();
+ ePosUsed = GTK_POS_TOP;
+ }
+ }
+ }
+ else
+ {
+ y = nNewY;
+ ePosUsed = GTK_POS_TOP;
+ }
+ }
+ }
+ else
+ {
+ if (!bSwapForRTL)
+ {
+ ePosUsed = GTK_POS_RIGHT;
+ gint endx = x + nMenuWidth;
+ gint nMissingAfter = endx - aWorkArea.Right();
+ if (nMissingAfter > 0)
+ {
+ gint nNewX = x - (nButtonWidth + nMenuWidth);
+ if (nNewX >= aWorkArea.Left())
+ {
+ x = nNewX;
+ ePosUsed = GTK_POS_LEFT;
+ }
+ }
+ }
+ else
+ {
+ ePosUsed = GTK_POS_LEFT;
+ gint startx = x;
+ gint nMissingBefore = aWorkArea.Left() - startx;
+ if (nMissingBefore > 0)
+ {
+ gint nNewX = x + (nButtonWidth + nMenuWidth);
+ if (nNewX + nMenuWidth < aWorkArea.Right())
+ {
+ x = nNewX;
+ ePosUsed = GTK_POS_RIGHT;
+ }
+ }
+ }
+ }
+
+ gtk_window_move(pMenu, x, y);
+
+ return ePosUsed;
+}
+
+bool show_menu_newer_gtk(GtkWidget* pComboBox, GtkWindow* pMenu, const GdkRectangle &rAnchor,
+ weld::Placement ePlace, bool bTryShrink)
+{
+ static auto window_move_to_rect = reinterpret_cast<void (*) (GdkWindow*, const GdkRectangle*, GdkGravity,
+ GdkGravity, GdkAnchorHints, gint, gint)>(
+ dlsym(nullptr, "gdk_window_move_to_rect"));
+ if (!window_move_to_rect)
+ return false;
+
+ // under wayland gdk_window_move_to_rect works great for me, but in my current
+ // gtk 3.24 under X it leaves part of long menus outside the work area
+ GdkDisplay *pDisplay = gtk_widget_get_display(pComboBox);
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ return false;
+
+ //place the toplevel just below its launcher button
+ GtkWidget* pToplevel = widget_get_toplevel(pComboBox);
+ gtk_coord x, y;
+ gtk_widget_translate_coordinates(pComboBox, pToplevel, rAnchor.x, rAnchor.y, &x, &y);
+
+ gtk_widget_realize(GTK_WIDGET(pMenu));
+ gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), pMenu);
+ gtk_window_set_transient_for(pMenu, GTK_WINDOW(pToplevel));
+
+ bool bSwapForRTL = SwapForRTL(GTK_WIDGET(pComboBox));
+
+ GdkGravity rect_anchor;
+ GdkGravity menu_anchor;
+
+ if (ePlace == weld::Placement::Under)
+ {
+ rect_anchor = !bSwapForRTL ? GDK_GRAVITY_SOUTH_WEST : GDK_GRAVITY_SOUTH_EAST;
+ menu_anchor = !bSwapForRTL ? GDK_GRAVITY_NORTH_WEST : GDK_GRAVITY_NORTH_EAST;
+ }
+ else
+ {
+ rect_anchor = !bSwapForRTL ? GDK_GRAVITY_NORTH_EAST : GDK_GRAVITY_NORTH_WEST;
+ menu_anchor = !bSwapForRTL ? GDK_GRAVITY_NORTH_WEST : GDK_GRAVITY_NORTH_EAST;
+ }
+
+ GdkAnchorHints anchor_hints = static_cast<GdkAnchorHints>(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE);
+ if (bTryShrink)
+ anchor_hints = static_cast<GdkAnchorHints>(anchor_hints | GDK_ANCHOR_RESIZE);
+ GdkRectangle rect {x, y, rAnchor.width, rAnchor.height};
+ GdkSurface* toplevel = widget_get_surface(GTK_WIDGET(pMenu));
+
+ window_move_to_rect(toplevel, &rect, rect_anchor, menu_anchor, anchor_hints,
+ 0, 0);
+
+ return true;
+}
+
+GtkPositionType show_menu(GtkWidget* pMenuButton, GtkWindow* pMenu, const GdkRectangle& rAnchor,
+ weld::Placement ePlace, bool bTryShrink)
+{
+ // we only use ePosUsed in the replacement-for-X-popover case of a
+ // MenuButton, so we only need it when show_menu_older_gtk is used
+ GtkPositionType ePosUsed = GTK_POS_BOTTOM;
+
+ // tdf#120764 It isn't allowed under wayland to have two visible popups that share
+ // the same top level parent. The problem is that since gtk 3.24 tooltips are also
+ // implemented as popups, which means that we cannot show any popup if there is a
+ // visible tooltip.
+ GtkWidget* pParent = widget_get_toplevel(pMenuButton);
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
+ if (pFrame)
+ {
+ // hide any current tooltip
+ pFrame->HideTooltip();
+ // don't allow any more to appear until menu is dismissed
+ pFrame->BlockTooltip();
+ }
+
+ // try with gdk_window_move_to_rect, but if that's not available, try without
+ if (!show_menu_newer_gtk(pMenuButton, pMenu, rAnchor, ePlace, bTryShrink))
+ ePosUsed = show_menu_older_gtk(pMenuButton, pMenu, rAnchor, ePlace, bTryShrink);
+ gtk_widget_show_all(GTK_WIDGET(pMenu));
+ gtk_widget_grab_focus(GTK_WIDGET(pMenu));
+ do_grab(GTK_WIDGET(pMenu));
+
+ return ePosUsed;
+}
+
+}
+#endif
+
+namespace {
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+bool button_event_is_outside(GtkWidget* pMenuHack, GdkEventButton* pEvent)
+{
+ //we want to pop down if the button was released outside our popup
+ gdouble x = pEvent->x_root;
+ gdouble y = pEvent->y_root;
+
+ gint window_x, window_y;
+ GdkSurface* pWindow = widget_get_surface(pMenuHack);
+ gdk_window_get_position(pWindow, &window_x, &window_y);
+
+ GtkAllocation alloc;
+ gtk_widget_get_allocation(pMenuHack, &alloc);
+ gint x1 = window_x;
+ gint y1 = window_y;
+ gint x2 = x1 + alloc.width;
+ gint y2 = y1 + alloc.height;
+
+ if (x > x1 && x < x2 && y > y1 && y < y2)
+ return false;
+
+ return true;
+}
+
+GtkPositionType MovePopoverContentsToWindow(GtkWidget* pPopover, GtkWindow* pMenuHack, GtkWidget* pAnchor,
+ const GdkRectangle& rAnchor, weld::Placement ePlace)
+{
+ //set border width
+ gtk_container_set_border_width(GTK_CONTAINER(pMenuHack), gtk_container_get_border_width(GTK_CONTAINER(pPopover)));
+
+ //steal popover contents and smuggle into toplevel display window
+ GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pPopover));
+ g_object_ref(pChild);
+ gtk_container_remove(GTK_CONTAINER(pPopover), pChild);
+ gtk_container_add(GTK_CONTAINER(pMenuHack), pChild);
+ g_object_unref(pChild);
+
+ GtkPositionType eRet = show_menu(pAnchor, pMenuHack, rAnchor, ePlace, false);
+
+ gtk_grab_add(GTK_WIDGET(pMenuHack));
+
+ GdkSurface* pSurface = widget_get_surface(GTK_WIDGET(pMenuHack));
+ g_object_set_data(G_OBJECT(pSurface), "g-lo-InstancePopup", GINT_TO_POINTER(true));
+
+ return eRet;
+}
+
+void MoveWindowContentsToPopover(GtkWindow* pMenuHack, GtkWidget* pPopover, GtkWidget* pAnchor)
+{
+ bool bHadFocus = gtk_window_has_toplevel_focus(pMenuHack);
+
+ do_ungrab(GTK_WIDGET(pMenuHack));
+
+ gtk_grab_remove(GTK_WIDGET(pMenuHack));
+
+ gtk_widget_hide(GTK_WIDGET(pMenuHack));
+ //put contents back from where the came from
+ GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pMenuHack));
+ g_object_ref(pChild);
+ gtk_container_remove(GTK_CONTAINER(pMenuHack), pChild);
+ gtk_container_add(GTK_CONTAINER(pPopover), pChild);
+ g_object_unref(pChild);
+
+ GdkSurface* pSurface = widget_get_surface(GTK_WIDGET(pMenuHack));
+ g_object_set_data(G_OBJECT(pSurface), "g-lo-InstancePopup", GINT_TO_POINTER(false));
+
+ // so gdk_window_move_to_rect will work again the next time
+ gtk_widget_unrealize(GTK_WIDGET(pMenuHack));
+
+ gtk_widget_set_size_request(GTK_WIDGET(pMenuHack), -1, -1);
+
+ // undo show_menu tooltip blocking
+ GtkWidget* pParent = widget_get_toplevel(pAnchor);
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
+ if (pFrame)
+ pFrame->UnblockTooltip();
+
+ if (bHadFocus)
+ {
+ GdkSurface* pParentSurface = pParent ? widget_get_surface(pParent) : nullptr;
+ void* pParentIsPopover = pParentSurface ? g_object_get_data(G_OBJECT(pParentSurface), "g-lo-InstancePopup") : nullptr;
+ if (pParentIsPopover)
+ do_grab(pAnchor);
+ gtk_widget_grab_focus(pAnchor);
+ }
+}
+
+#endif
+
+/* four types of uses of this
+ a) textual menubutton, always with pan-down symbol, e.g. math, format, font, modify
+ b) image + text, always with additional pan-down symbol, e.g. writer, format, watermark
+ c) gear menu, never with text and without pan-down symbol where there is a replacement
+ icon for pan-down, e.g. file, new, templates
+ d) image, always with additional pan-down symbol, e.g. calc, insert, header/footer */
+#if !GTK_CHECK_VERSION(4, 0, 0)
+class GtkInstanceMenuButton : public GtkInstanceToggleButton, public MenuHelper, public virtual weld::MenuButton
+#else
+class GtkInstanceMenuButton : public GtkInstanceWidget, public MenuHelper, public virtual weld::MenuButton
+#endif
+{
+protected:
+ GtkMenuButton* m_pMenuButton;
+private:
+ GtkBox* m_pBox;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkImage* m_pImage;
+#else
+ GtkPicture* m_pImage;
+ GtkToggleButton* m_pMenuButtonToggleButton;
+#endif
+ GtkWidget* m_pLabel;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ //popover cannot escape dialog under X so stick up own window instead
+ GtkWindow* m_pMenuHack;
+ //when doing so, if it's a toolbar menubutton align the menu to the full toolitem
+ GtkWidget* m_pMenuHackAlign;
+ bool m_nButtonPressSeen;
+ gulong m_nSignalId;
+#endif
+ GtkWidget* m_pPopover;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nToggledSignalId;
+ std::optional<vcl::Font> m_xFont;
+ WidgetBackground m_aCustomBackground;
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void signalMenuButtonToggled(GtkWidget*, gpointer widget)
+ {
+ GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->menu_toggled();
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ void menu_toggled()
+ {
+ if (!m_pMenuHack)
+ return;
+ if (!get_active())
+ {
+ m_nButtonPressSeen = false;
+ MoveWindowContentsToPopover(m_pMenuHack, m_pPopover, GTK_WIDGET(m_pMenuButton));
+ }
+ else
+ {
+ GtkWidget* pAnchor = m_pMenuHackAlign ? m_pMenuHackAlign : GTK_WIDGET(m_pMenuButton);
+ GdkRectangle aAnchor {0, 0, gtk_widget_get_allocated_width(pAnchor), gtk_widget_get_allocated_height(pAnchor) };
+ GtkPositionType ePosUsed = MovePopoverContentsToWindow(m_pPopover, m_pMenuHack, pAnchor, aAnchor, weld::Placement::Under);
+ // tdf#132540 keep the placeholder popover on this same side as the replacement menu
+ gtk_popover_set_position(gtk_menu_button_get_popover(m_pMenuButton), ePosUsed);
+ }
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
+ {
+ GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
+ pThis->grab_broken(pEvent);
+ }
+
+ void grab_broken(const GdkEventGrabBroken *event)
+ {
+ if (event->grab_window == nullptr)
+ {
+ set_active(false);
+ }
+ else if (!g_object_get_data(G_OBJECT(event->grab_window), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
+ {
+ //try and regrab, so when we lose the grab to the menu of the color palette
+ //combobox we regain it so the color palette doesn't itself disappear on next
+ //click on the color palette combobox
+ do_grab(GTK_WIDGET(m_pMenuHack));
+ }
+ }
+
+ static gboolean signalButtonPress(GtkWidget* /*pWidget*/, GdkEventButton* /*pEvent*/, gpointer widget)
+ {
+ GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
+ pThis->m_nButtonPressSeen = true;
+ return false;
+ }
+
+ static gboolean signalButtonRelease(GtkWidget* /*pWidget*/, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
+ if (pThis->m_nButtonPressSeen && button_event_is_outside(GTK_WIDGET(pThis->m_pMenuHack), pEvent))
+ pThis->set_active(false);
+ return false;
+ }
+
+ static gboolean keyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
+ return pThis->key_press(pEvent);
+ }
+
+ bool key_press(const GdkEventKey* pEvent)
+ {
+ if (pEvent->keyval == GDK_KEY_Escape)
+ {
+ set_active(false);
+ return true;
+ }
+ return false;
+ }
+#endif
+
+ void ensure_image_widget()
+ {
+ if (m_pImage)
+ return;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_pImage = GTK_IMAGE(gtk_image_new());
+ gtk_box_pack_start(m_pBox, GTK_WIDGET(m_pImage), false, false, 0);
+ gtk_box_reorder_child(m_pBox, GTK_WIDGET(m_pImage), 0);
+#else
+ m_pImage = GTK_PICTURE(gtk_picture_new());
+ gtk_widget_set_halign(GTK_WIDGET(m_pImage), GTK_ALIGN_CENTER);
+ gtk_widget_set_valign(GTK_WIDGET(m_pImage), GTK_ALIGN_CENTER);
+ gtk_box_prepend(m_pBox, GTK_WIDGET(m_pImage));
+ gtk_widget_set_halign(m_pLabel, GTK_ALIGN_START);
+#endif
+ gtk_widget_show(GTK_WIDGET(m_pImage));
+ }
+
+ static void signalFlagsChanged(GtkToggleButton* pToggleButton, GtkStateFlags flags, gpointer widget)
+ {
+ GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
+ bool bOldChecked = flags & GTK_STATE_FLAG_CHECKED;
+ bool bNewChecked = gtk_widget_get_state_flags(GTK_WIDGET(pToggleButton)) & GTK_STATE_FLAG_CHECKED;
+ if (bOldChecked == bNewChecked)
+ return;
+ if (bOldChecked && gtk_widget_get_focus_on_click(GTK_WIDGET(pToggleButton)))
+ {
+ // grab focus back to the toggle button if the menu was popped down
+ gtk_widget_grab_focus(GTK_WIDGET(pToggleButton));
+ }
+ SolarMutexGuard aGuard;
+ pThis->signal_toggled();
+ }
+
+public:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkInstanceMenuButton(GtkMenuButton* pMenuButton, GtkWidget* pMenuAlign, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pMenuButton), pBuilder, bTakeOwnership)
+ , MenuHelper(gtk_menu_button_get_popup(pMenuButton), false)
+#else
+ GtkInstanceMenuButton(GtkMenuButton* pMenuButton, GtkWidget* pMenuAlign, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pMenuButton), pBuilder, bTakeOwnership)
+ , MenuHelper(GTK_POPOVER_MENU(gtk_menu_button_get_popover(pMenuButton)), false)
+#endif
+ , m_pMenuButton(pMenuButton)
+ , m_pImage(nullptr)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_pMenuHack(nullptr)
+ , m_pMenuHackAlign(pMenuAlign)
+ , m_nButtonPressSeen(true)
+ , m_nSignalId(0)
+#endif
+ , m_pPopover(nullptr)
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_aCustomBackground(GTK_WIDGET(pMenuButton))
+#endif
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // tdf#142924 "toggled" is too late to use to populate changes to the menu,
+ // so use "state-flag-changed" on GTK_STATE_FLAG_CHECKED instead which
+ // happens before "toggled"
+ g_signal_handler_disconnect(m_pToggleButton, m_nToggledSignalId);
+ m_nToggledSignalId = g_signal_connect(m_pToggleButton, "state-flags-changed", G_CALLBACK(signalFlagsChanged), this);
+
+ m_pLabel = gtk_bin_get_child(GTK_BIN(m_pMenuButton));
+ m_pImage = get_image_widget(GTK_WIDGET(m_pMenuButton));
+ m_pBox = formatMenuButton(m_pLabel);
+#else
+ GtkWidget* pToggleButton = gtk_widget_get_first_child(GTK_WIDGET(m_pMenuButton));
+ assert(GTK_IS_TOGGLE_BUTTON(pToggleButton));
+ m_pMenuButtonToggleButton = GTK_TOGGLE_BUTTON(pToggleButton);
+ m_nToggledSignalId = g_signal_connect(m_pMenuButtonToggleButton, "state-flags-changed", G_CALLBACK(signalFlagsChanged), this);
+ GtkWidget* pChild = gtk_button_get_child(GTK_BUTTON(pToggleButton));
+ m_pBox = GTK_IS_BOX(pChild) ? GTK_BOX(pChild) : nullptr;
+ m_pLabel = m_pBox ? gtk_widget_get_first_child(GTK_WIDGET(m_pBox)) : nullptr;
+ (void)pMenuAlign;
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_insert_action_group(GTK_WIDGET(m_pMenuButton), "menu", m_pActionGroup);
+
+ update_action_group_from_popover_model();
+#endif
+ }
+
+ virtual void set_size_request(int nWidth, int nHeight) override
+ {
+ // tweak the label to get a narrower size to stick
+ if (GTK_IS_LABEL(m_pLabel))
+ gtk_label_set_ellipsize(GTK_LABEL(m_pLabel), PANGO_ELLIPSIZE_MIDDLE);
+ gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
+ }
+
+ virtual void set_label(const OUString& rText) override
+ {
+ ::set_label(GTK_LABEL(m_pLabel), rText);
+ }
+
+ virtual OUString get_label() const override
+ {
+ return ::get_label(GTK_LABEL(m_pLabel));
+ }
+
+ virtual void set_image(VirtualDevice* pDevice) override
+ {
+ ensure_image_widget();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ picture_set_from_virtual_device(m_pImage, pDevice);
+#else
+ image_set_from_virtual_device(m_pImage, pDevice);
+#endif
+ }
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
+ {
+ ensure_image_widget();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ picture_set_from_xgraphic(m_pImage, rImage);
+#else
+ image_set_from_xgraphic(m_pImage, rImage);
+#endif
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ virtual void set_from_icon_name(const OUString& rIconName) override
+ {
+ ensure_image_widget();
+ picture_set_from_icon_name(m_pImage, rIconName);
+ }
+
+ virtual void set_custom_button(VirtualDevice* pDevice) override
+ {
+ m_aCustomBackground.use_custom_content(pDevice);
+ }
+
+ virtual void set_inconsistent(bool inconsistent) override
+ {
+ if (inconsistent)
+ gtk_widget_set_state_flags(GTK_WIDGET(m_pMenuButton), GTK_STATE_FLAG_INCONSISTENT, false);
+ else
+ gtk_widget_unset_state_flags(GTK_WIDGET(m_pMenuButton), GTK_STATE_FLAG_INCONSISTENT);
+ }
+
+ virtual bool get_inconsistent() const override
+ {
+ return gtk_widget_get_state_flags(GTK_WIDGET(m_pMenuButton)) & GTK_STATE_FLAG_INCONSISTENT;
+ }
+
+ virtual void set_active(bool active) override
+ {
+ disable_notify_events();
+ set_inconsistent(false);
+ if (active)
+ gtk_menu_button_popup(m_pMenuButton);
+ else
+ gtk_menu_button_popdown(m_pMenuButton);
+ enable_notify_events();
+ }
+
+ virtual bool get_active() const override
+ {
+ GtkPopover* pPopover = gtk_menu_button_get_popover(m_pMenuButton);
+ return pPopover && gtk_widget_get_visible(GTK_WIDGET(pPopover));
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_xFont = rFont;
+ GtkLabel* pChild = ::get_label_widget(GTK_WIDGET(m_pMenuButton));
+ ::set_font(pChild, rFont);
+ }
+
+ virtual vcl::Font get_font() override
+ {
+ if (m_xFont)
+ return *m_xFont;
+ return GtkInstanceWidget::get_font();
+ }
+#else
+ virtual void set_active(bool bActive) override
+ {
+ bool bWasActive = get_active();
+ GtkInstanceToggleButton::set_active(bActive);
+ if (bWasActive && !bActive && gtk_widget_get_focus_on_click(GTK_WIDGET(m_pMenuButton)))
+ {
+ // grab focus back to the toggle button if the menu was popped down
+ gtk_widget_grab_focus(GTK_WIDGET(m_pMenuButton));
+ }
+ }
+#endif
+
+ virtual void insert_item(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, VirtualDevice* pImageSurface, TriState eCheckRadioFalse) override
+ {
+ MenuHelper::insert_item(pos, rId, rStr, pIconName, pImageSurface, eCheckRadioFalse);
+ }
+
+ virtual void insert_separator(int pos, const OUString& rId) override
+ {
+ MenuHelper::insert_separator(pos, rId);
+ }
+
+ virtual void remove_item(const OUString& rId) override
+ {
+ MenuHelper::remove_item(rId);
+ }
+
+ virtual void clear() override
+ {
+ MenuHelper::clear_items();
+ }
+
+ virtual void set_item_active(const OUString& rIdent, bool bActive) override
+ {
+ MenuHelper::set_item_active(rIdent, bActive);
+ }
+
+ virtual void set_item_sensitive(const OUString& rIdent, bool bSensitive) override
+ {
+ MenuHelper::set_item_sensitive(rIdent, bSensitive);
+ }
+
+ virtual void set_item_label(const OUString& rIdent, const OUString& rLabel) override
+ {
+ MenuHelper::set_item_label(rIdent, rLabel);
+ }
+
+ virtual OUString get_item_label(const OUString& rIdent) const override
+ {
+ return MenuHelper::get_item_label(rIdent);
+ }
+
+ virtual void set_item_visible(const OUString& rIdent, bool bVisible) override
+ {
+ MenuHelper::set_item_visible(rIdent, bVisible);
+ }
+
+ virtual void signal_item_activate(const OUString& rIdent) override
+ {
+ signal_selected(rIdent);
+ }
+
+ virtual void set_popover(weld::Widget* pPopover) override
+ {
+ GtkInstanceWidget* pPopoverWidget = dynamic_cast<GtkInstanceWidget*>(pPopover);
+ m_pPopover = pPopoverWidget ? pPopoverWidget->getWidget() : nullptr;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_menu_button_set_popover(m_pMenuButton, m_pPopover);
+ update_action_group_from_popover_model();
+ return;
+#else
+
+ if (!m_pPopover)
+ {
+ gtk_menu_button_set_popover(m_pMenuButton, nullptr);
+ return;
+ }
+
+ if (!m_pMenuHack)
+ {
+ //under wayland a Popover will work to "escape" the parent dialog, not
+ //so under X, so come up with this hack to use a raw GtkWindow
+ GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay) && gtk_popover_get_constrain_to(GTK_POPOVER(m_pPopover)) == GTK_POPOVER_CONSTRAINT_NONE)
+ {
+ m_pMenuHack = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
+ gtk_window_set_type_hint(m_pMenuHack, GDK_WINDOW_TYPE_HINT_COMBO);
+ // See writer "format, watermark" for true here. Can't interact with the replacement popover otherwise.
+ gtk_window_set_modal(m_pMenuHack, true);
+ gtk_window_set_resizable(m_pMenuHack, false);
+ m_nSignalId = g_signal_connect(GTK_TOGGLE_BUTTON(m_pMenuButton), "toggled", G_CALLBACK(signalMenuButtonToggled), this);
+ g_signal_connect(m_pMenuHack, "key-press-event", G_CALLBACK(keyPress), this);
+ g_signal_connect(m_pMenuHack, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
+ g_signal_connect(m_pMenuHack, "button-press-event", G_CALLBACK(signalButtonPress), this);
+ g_signal_connect(m_pMenuHack, "button-release-event", G_CALLBACK(signalButtonRelease), this);
+ }
+ }
+
+ if (m_pMenuHack)
+ {
+ GtkWidget* pPlaceHolder = gtk_popover_new(GTK_WIDGET(m_pMenuButton));
+ gtk_popover_set_transitions_enabled(GTK_POPOVER(pPlaceHolder), false);
+
+ // tdf#132540 theme the unwanted popover into invisibility
+ GtkStyleContext *pPopoverContext = gtk_widget_get_style_context(pPlaceHolder);
+ GtkCssProvider *pProvider = gtk_css_provider_new();
+ static const gchar data[] = "popover { box-shadow: none; padding: 0 0 0 0; margin: 0 0 0 0; border-image: none; border-image-width: 0 0 0 0; background-image: none; background-color: transparent; border-radius: 0 0 0 0; border-width: 0 0 0 0; border-style: none; border-color: transparent; opacity: 0; min-height: 0; min-width: 0; }";
+ css_provider_load_from_data(pProvider, data, -1);
+ gtk_style_context_add_provider(pPopoverContext, GTK_STYLE_PROVIDER(pProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ gtk_menu_button_set_popover(m_pMenuButton, pPlaceHolder);
+ }
+ else
+ {
+ gtk_menu_button_set_popover(m_pMenuButton, m_pPopover);
+ gtk_widget_show_all(m_pPopover);
+ }
+#endif
+ }
+
+ void set_menu(weld::Menu* pMenu);
+
+ static GtkBox* formatMenuButton(GtkWidget* pLabel)
+ {
+ // format the GtkMenuButton "manually" so we can have the dropdown image in GtkMenuButtons shown
+ // on the right at the same time as an image is shown on the left
+ g_object_ref(pLabel);
+ GtkWidget* pContainer = gtk_widget_get_parent(pLabel);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_remove(GTK_CONTAINER(pContainer), pLabel);
+#else
+ gtk_box_remove(GTK_BOX(pContainer), pLabel);
+#endif
+
+ gint nImageSpacing(2);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkStyleContext *pContext = gtk_widget_get_style_context(pContainer);
+ gtk_style_context_get_style(pContext, "image-spacing", &nImageSpacing, nullptr);
+#endif
+ GtkBox* pBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, nImageSpacing));
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_start(pBox, pLabel, true, true, 0);
+#else
+ gtk_widget_set_halign(pLabel, GTK_ALIGN_START);
+ gtk_box_prepend(pBox, pLabel);
+#endif
+ g_object_unref(pLabel);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (gtk_toggle_button_get_mode(GTK_TOGGLE_BUTTON(pContainer)))
+ gtk_box_pack_end(pBox, gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_BUTTON), false, false, 0);
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(pContainer), GTK_WIDGET(pBox));
+#else
+ gtk_box_prepend(GTK_BOX(pContainer), GTK_WIDGET(pBox));
+#endif
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_show_all(GTK_WIDGET(pBox));
+#else
+ gtk_widget_show(GTK_WIDGET(pBox));
+#endif
+
+ return pBox;
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pMenuButtonToggleButton, m_nToggledSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pMenuButtonToggleButton, m_nToggledSignalId);
+ }
+#endif
+
+ virtual ~GtkInstanceMenuButton() override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pMenuButtonToggleButton, m_nToggledSignalId);
+ gtk_widget_insert_action_group(GTK_WIDGET(m_pMenuButton), "menu", nullptr);
+#else
+ if (m_pMenuHack)
+ {
+ g_signal_handler_disconnect(m_pMenuButton, m_nSignalId);
+ gtk_menu_button_set_popover(m_pMenuButton, nullptr);
+ gtk_widget_destroy(GTK_WIDGET(m_pMenuHack));
+ }
+#endif
+ }
+};
+
+class GtkInstanceMenuToggleButton : public GtkInstanceToggleButton, public MenuHelper
+ , public virtual weld::MenuToggleButton
+{
+private:
+ GtkBox* m_pContainer;
+ GtkButton* m_pToggleMenuButton;
+ GtkMenuButton* m_pMenuButton;
+ gulong m_nMenuBtnClickedId;
+ gulong m_nToggleStateFlagsChangedId;
+ gulong m_nMenuBtnStateFlagsChangedId;
+
+ static void signalToggleStateFlagsChanged(GtkWidget* pWidget, GtkStateFlags /*eFlags*/, gpointer widget)
+ {
+ GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
+ // mirror togglebutton state to menubutton
+ gtk_widget_set_state_flags(GTK_WIDGET(pThis->m_pToggleMenuButton), gtk_widget_get_state_flags(pWidget), true);
+ }
+
+ static void signalMenuBtnStateFlagsChanged(GtkWidget* pWidget, GtkStateFlags /*eFlags*/, gpointer widget)
+ {
+ GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
+ // mirror menubutton to togglebutton, keeping depressed state of menubutton
+ GtkStateFlags eToggleFlags = gtk_widget_get_state_flags(GTK_WIDGET(pThis->m_pToggleButton));
+ GtkStateFlags eFlags = gtk_widget_get_state_flags(pWidget);
+ GtkStateFlags eFinalFlags = static_cast<GtkStateFlags>((eFlags & ~GTK_STATE_FLAG_ACTIVE) |
+ (eToggleFlags & GTK_STATE_FLAG_ACTIVE));
+ gtk_widget_set_state_flags(GTK_WIDGET(pThis->m_pToggleButton), eFinalFlags, true);
+ }
+
+ static void signalMenuBtnClicked(GtkButton*, gpointer widget)
+ {
+ GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
+ pThis->launch_menu();
+ }
+
+ void launch_menu()
+ {
+ gtk_widget_set_state_flags(GTK_WIDGET(m_pToggleMenuButton), gtk_widget_get_state_flags(GTK_WIDGET(m_pToggleButton)), true);
+ GtkWidget* pWidget = GTK_WIDGET(m_pToggleButton);
+
+ //run in a sub main loop because we need to keep vcl PopupMenu alive to use
+ //it during DispatchCommand, returning now to the outer loop causes the
+ //launching PopupMenu to be destroyed, instead run the subloop here
+ //until the gtk menu is destroyed
+ GMainLoop* pLoop = g_main_loop_new(nullptr, true);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "closed", G_CALLBACK(g_main_loop_quit), pLoop);
+
+ g_object_ref(m_pMenu);
+ gtk_menu_button_set_popover(m_pMenuButton, nullptr);
+ gtk_widget_set_parent(GTK_WIDGET(m_pMenu), pWidget);
+ gtk_popover_set_position(GTK_POPOVER(m_pMenu), GTK_POS_BOTTOM);
+ gtk_popover_popup(GTK_POPOVER(m_pMenu));
+#else
+ gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
+
+#if GTK_CHECK_VERSION(3,22,0)
+ if (gtk_check_version(3, 22, 0) == nullptr)
+ {
+ // Send a keyboard event through gtk_main_do_event to toggle any active tooltip offs
+ // before trying to launch the menu
+ // https://gitlab.gnome.org/GNOME/gtk/issues/1785
+ // Fixed in GTK 2.34
+ GdkEvent *pKeyEvent = GtkSalFrame::makeFakeKeyPress(pWidget);
+ gtk_main_do_event(pKeyEvent);
+
+ GdkEvent *pTriggerEvent = gtk_get_current_event();
+ if (!pTriggerEvent)
+ pTriggerEvent = pKeyEvent;
+
+ gtk_menu_popup_at_widget(m_pMenu, pWidget, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, pTriggerEvent);
+
+ gdk_event_free(pKeyEvent);
+ }
+ else
+#endif
+ {
+ guint nButton;
+ guint32 nTime;
+
+ //typically there is an event, and we can then distinguish if this was
+ //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
+ //doesn't)
+ GdkEvent *pEvent = gtk_get_current_event();
+ if (pEvent)
+ {
+ gdk_event_get_button(pEvent, &nButton);
+ nTime = gdk_event_get_time(pEvent);
+ }
+ else
+ {
+ nButton = 0;
+ nTime = GtkSalFrame::GetLastInputEventTime();
+ }
+
+ gtk_menu_popup(m_pMenu, nullptr, nullptr, nullptr, nullptr, nButton, nTime);
+ }
+#endif
+
+ if (g_main_loop_is_running(pLoop))
+ main_loop_run(pLoop);
+
+ g_main_loop_unref(pLoop);
+ g_signal_handler_disconnect(m_pMenu, nSignalId);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_unparent(GTK_WIDGET(m_pMenu));
+ gtk_menu_button_set_popover(m_pMenuButton, GTK_WIDGET(m_pMenu));
+ g_object_unref(m_pMenu);
+#endif
+
+ }
+
+ static gboolean signalMenuToggleButton(GtkWidget*, gboolean bGroupCycling, gpointer widget)
+ {
+ GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
+ return gtk_widget_mnemonic_activate(GTK_WIDGET(pThis->m_pToggleButton), bGroupCycling);
+ }
+
+public:
+ GtkInstanceMenuToggleButton(GtkBuilder* pMenuToggleButtonBuilder, GtkMenuButton* pMenuButton,
+ GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(gtk_builder_get_object(pMenuToggleButtonBuilder, "togglebutton")),
+ pBuilder, bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , MenuHelper(gtk_menu_button_get_popup(pMenuButton), false)
+#else
+ , MenuHelper(GTK_POPOVER_MENU(gtk_menu_button_get_popover(pMenuButton)), false)
+#endif
+ , m_pContainer(GTK_BOX(gtk_builder_get_object(pMenuToggleButtonBuilder, "box")))
+ , m_pToggleMenuButton(GTK_BUTTON(gtk_builder_get_object(pMenuToggleButtonBuilder, "menubutton")))
+ , m_pMenuButton(pMenuButton)
+ , m_nMenuBtnClickedId(g_signal_connect(m_pToggleMenuButton, "clicked", G_CALLBACK(signalMenuBtnClicked), this))
+ , m_nToggleStateFlagsChangedId(g_signal_connect(m_pToggleButton, "state-flags-changed", G_CALLBACK(signalToggleStateFlagsChanged), this))
+ , m_nMenuBtnStateFlagsChangedId(g_signal_connect(m_pToggleMenuButton, "state-flags-changed", G_CALLBACK(signalMenuBtnStateFlagsChanged), this))
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkInstanceMenuButton::formatMenuButton(gtk_bin_get_child(GTK_BIN(m_pMenuButton)));
+#endif
+
+ insertAsParent(GTK_WIDGET(m_pMenuButton), GTK_WIDGET(m_pContainer));
+ gtk_widget_hide(GTK_WIDGET(m_pMenuButton));
+
+ // move the first GtkMenuButton child, as created by GtkInstanceMenuButton ctor, into the GtkToggleButton
+ // instead, leaving just the indicator behind in the GtkMenuButton
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pButtonBox = gtk_bin_get_child(GTK_BIN(m_pMenuButton));
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pButtonBox));
+ int nGroup = 0;
+ for (GList* pChild = g_list_first(pChildren); pChild && nGroup < 2; pChild = g_list_next(pChild), ++nGroup)
+ {
+ GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
+ g_object_ref(pWidget);
+ gtk_container_remove(GTK_CONTAINER(pButtonBox), pWidget);
+ if (nGroup == 0)
+ gtk_container_add(GTK_CONTAINER(m_pToggleButton), pWidget);
+ else
+ gtk_container_add(GTK_CONTAINER(m_pToggleMenuButton), pWidget);
+ gtk_widget_show_all(pWidget);
+ g_object_unref(pWidget);
+ }
+ g_list_free(pChildren);
+#else
+ GtkWidget* pChild;
+ if (gtk_check_version(4, 5, 0) == nullptr)
+ {
+ pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pMenuButton));
+ pChild = gtk_widget_get_first_child(pChild);
+ pChild = gtk_widget_get_first_child(pChild);
+ }
+ else
+ pChild = gtk_widget_get_last_child(GTK_WIDGET(m_pMenuButton));
+ g_object_ref(pChild);
+ gtk_widget_unparent(pChild);
+ gtk_button_set_child(GTK_BUTTON(m_pToggleButton), pChild);
+ g_object_unref(pChild);
+#endif
+
+ // match the GtkToggleButton relief to the GtkMenuButton
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ const GtkReliefStyle eStyle = gtk_button_get_relief(GTK_BUTTON(m_pMenuButton));
+ gtk_button_set_relief(GTK_BUTTON(m_pToggleButton), eStyle);
+ gtk_button_set_relief(GTK_BUTTON(m_pToggleMenuButton), eStyle);
+#else
+ const bool bStyle = gtk_menu_button_get_has_frame(GTK_MENU_BUTTON(m_pMenuButton));
+ gtk_button_set_has_frame(GTK_BUTTON(m_pToggleButton), bStyle);
+ gtk_button_set_has_frame(GTK_BUTTON(m_pToggleMenuButton), bStyle);
+#endif
+
+ // move the GtkMenuButton margins up to the new parent
+ gtk_widget_set_margin_top(GTK_WIDGET(m_pContainer),
+ gtk_widget_get_margin_top(GTK_WIDGET(m_pMenuButton)));
+ gtk_widget_set_margin_bottom(GTK_WIDGET(m_pContainer),
+ gtk_widget_get_margin_bottom(GTK_WIDGET(m_pMenuButton)));
+ gtk_widget_set_margin_start(GTK_WIDGET(m_pContainer),
+ gtk_widget_get_margin_start(GTK_WIDGET(m_pMenuButton)));
+ gtk_widget_set_margin_end(GTK_WIDGET(m_pContainer),
+ gtk_widget_get_margin_end(GTK_WIDGET(m_pMenuButton)));
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_menu_detach(m_pMenu);
+ gtk_menu_attach_to_widget(m_pMenu, GTK_WIDGET(m_pToggleButton), nullptr);
+#else
+ gtk_widget_insert_action_group(GTK_WIDGET(m_pContainer), "menu", m_pActionGroup);
+
+ update_action_group_from_popover_model();
+#endif
+
+ g_signal_connect(m_pContainer, "mnemonic-activate", G_CALLBACK(signalMenuToggleButton), this);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pToggleMenuButton, m_nMenuBtnClickedId);
+ GtkInstanceToggleButton::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceToggleButton::enable_notify_events();
+ g_signal_handler_unblock(m_pToggleMenuButton, m_nMenuBtnClickedId);
+ }
+
+ virtual ~GtkInstanceMenuToggleButton()
+ {
+ g_signal_handler_disconnect(m_pToggleButton, m_nToggleStateFlagsChangedId);
+ g_signal_handler_disconnect(m_pToggleMenuButton, m_nMenuBtnStateFlagsChangedId);
+ g_signal_handler_disconnect(m_pToggleMenuButton, m_nMenuBtnClickedId);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pChild = gtk_button_get_child(GTK_BUTTON(m_pToggleButton));
+ g_object_ref(pChild);
+ gtk_button_set_child(GTK_BUTTON(m_pToggleButton), nullptr);
+ gtk_widget_unparent(pChild);
+ gtk_widget_set_parent(pChild, GTK_WIDGET(m_pMenuButton));
+ g_object_unref(pChild);
+#endif
+ }
+
+ virtual void insert_item(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, VirtualDevice* pImageSurface, TriState eCheckRadioFalse) override
+ {
+ MenuHelper::insert_item(pos, rId, rStr, pIconName, pImageSurface, eCheckRadioFalse);
+ }
+
+ virtual void insert_separator(int pos, const OUString& rId) override
+ {
+ MenuHelper::insert_separator(pos, rId);
+ }
+
+ virtual void remove_item(const OUString& rId) override
+ {
+ MenuHelper::remove_item(rId);
+ }
+
+ virtual void clear() override
+ {
+ MenuHelper::clear_items();
+ }
+
+ virtual void set_item_active(const OUString& rIdent, bool bActive) override
+ {
+ MenuHelper::set_item_active(rIdent, bActive);
+ }
+
+ virtual void set_item_sensitive(const OUString& rIdent, bool bSensitive) override
+ {
+ MenuHelper::set_item_sensitive(rIdent, bSensitive);
+ }
+
+ virtual void set_item_label(const OUString& rIdent, const OUString& rLabel) override
+ {
+ MenuHelper::set_item_label(rIdent, rLabel);
+ }
+
+ virtual OUString get_item_label(const OUString& rIdent) const override
+ {
+ return MenuHelper::get_item_label(rIdent);
+ }
+
+ virtual void set_item_visible(const OUString& rIdent, bool bVisible) override
+ {
+ MenuHelper::set_item_visible(rIdent, bVisible);
+ }
+
+ virtual void signal_item_activate(const OUString& rIdent) override
+ {
+ signal_selected(rIdent);
+ }
+
+ virtual void set_popover(weld::Widget* /*pPopover*/) override
+ {
+ assert(false && "not implemented");
+ }
+};
+
+class GtkInstanceMenu : public MenuHelper, public virtual weld::Menu
+{
+protected:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ std::vector<GtkMenuItem*> m_aExtraItems;
+#endif
+ OUString m_sActivated;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ MenuHelper* m_pTopLevelMenuHelper;
+#endif
+
+private:
+ virtual void signal_item_activate(const OUString& rIdent) override
+ {
+ m_sActivated = rIdent;
+ weld::Menu::signal_activate(m_sActivated);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ void clear_extras()
+ {
+ if (m_aExtraItems.empty())
+ return;
+ if (m_pTopLevelMenuHelper)
+ {
+ for (auto a : m_aExtraItems)
+ m_pTopLevelMenuHelper->remove_from_map(a);
+ }
+ m_aExtraItems.clear();
+ }
+#endif
+
+public:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkInstanceMenu(GtkMenu* pMenu, bool bTakeOwnership)
+#else
+ GtkInstanceMenu(GtkPopoverMenu* pMenu, bool bTakeOwnership)
+#endif
+ : MenuHelper(pMenu, bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_pTopLevelMenuHelper(nullptr)
+#endif
+ {
+ g_object_set_data(G_OBJECT(m_pMenu), "g-lo-GtkInstanceMenu", this);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // tdf#122527 if we're welding a submenu of a menu of a MenuButton,
+ // then find that MenuButton parent so that when adding items to this
+ // menu we can inform the MenuButton of their addition
+ GtkMenu* pTopLevelMenu = pMenu;
+ while (true)
+ {
+ GtkWidget* pAttached = gtk_menu_get_attach_widget(pTopLevelMenu);
+ if (!pAttached || !GTK_IS_MENU_ITEM(pAttached))
+ break;
+ GtkWidget* pParent = gtk_widget_get_parent(pAttached);
+ if (!pParent || !GTK_IS_MENU(pParent))
+ break;
+ pTopLevelMenu = GTK_MENU(pParent);
+ }
+ if (pTopLevelMenu == pMenu)
+ return;
+
+ // maybe the toplevel is a menubutton
+ GtkWidget* pAttached = gtk_menu_get_attach_widget(pTopLevelMenu);
+ if (pAttached && GTK_IS_MENU_BUTTON(pAttached))
+ {
+ void* pData = g_object_get_data(G_OBJECT(pAttached), "g-lo-GtkInstanceButton");
+ m_pTopLevelMenuHelper = dynamic_cast<GtkInstanceMenuButton*>(static_cast<GtkInstanceButton*>(pData));
+ }
+ // or maybe a menu
+ if (!m_pTopLevelMenuHelper)
+ {
+ void* pData = g_object_get_data(G_OBJECT(pTopLevelMenu), "g-lo-GtkInstanceMenu");
+ m_pTopLevelMenuHelper = static_cast<GtkInstanceMenu*>(pData);
+ }
+#else
+ update_action_group_from_popover_model();
+#endif
+ }
+
+ virtual OUString popup_at_rect(weld::Widget* pParent, const tools::Rectangle& rRect, weld::Placement ePlace) override
+ {
+ m_sActivated.clear();
+
+ GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
+ assert(pGtkWidget);
+ GtkWidget* pWidget = pGtkWidget->getWidget();
+
+ //run in a sub main loop because we need to keep vcl PopupMenu alive to use
+ //it during DispatchCommand, returning now to the outer loop causes the
+ //launching PopupMenu to be destroyed, instead run the subloop here
+ //until the gtk menu is destroyed
+ GMainLoop* pLoop = g_main_loop_new(nullptr, true);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_insert_action_group(pWidget, "menu", m_pActionGroup);
+
+ gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "closed", G_CALLBACK(g_main_loop_quit), pLoop);
+
+ GdkRectangle aRect;
+ pWidget = getPopupRect(pWidget, rRect, aRect);
+
+ GtkWidget* pOrigParent = gtk_widget_get_parent(GTK_WIDGET(m_pMenu));
+ gtk_widget_set_parent(GTK_WIDGET(m_pMenu), pWidget);
+ gtk_popover_set_pointing_to(GTK_POPOVER(m_pMenu), &aRect);
+ if (ePlace == weld::Placement::Under)
+ gtk_popover_set_position(GTK_POPOVER(m_pMenu), GTK_POS_BOTTOM);
+ else
+ {
+ if (SwapForRTL(pWidget))
+ gtk_popover_set_position(GTK_POPOVER(m_pMenu), GTK_POS_LEFT);
+ else
+ gtk_popover_set_position(GTK_POPOVER(m_pMenu), GTK_POS_RIGHT);
+ }
+ gtk_popover_popup(GTK_POPOVER(m_pMenu));
+#else
+ gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
+
+#if GTK_CHECK_VERSION(3,22,0)
+ if (gtk_check_version(3, 22, 0) == nullptr)
+ {
+ GdkRectangle aRect;
+ pWidget = getPopupRect(pWidget, rRect, aRect);
+ gtk_menu_attach_to_widget(m_pMenu, pWidget, nullptr);
+
+ // Send a keyboard event through gtk_main_do_event to toggle any active tooltip offs
+ // before trying to launch the menu
+ // https://gitlab.gnome.org/GNOME/gtk/issues/1785
+ // Fixed in GTK 2.34
+ GdkEvent *pKeyEvent = GtkSalFrame::makeFakeKeyPress(pWidget);
+ gtk_main_do_event(pKeyEvent);
+
+ GdkEvent *pTriggerEvent = gtk_get_current_event();
+ if (!pTriggerEvent)
+ pTriggerEvent = pKeyEvent;
+
+ bool bSwapForRTL = SwapForRTL(pWidget);
+
+ if (ePlace == weld::Placement::Under)
+ {
+ if (bSwapForRTL)
+ gtk_menu_popup_at_rect(m_pMenu, widget_get_surface(pWidget), &aRect, GDK_GRAVITY_SOUTH_EAST, GDK_GRAVITY_NORTH_EAST, pTriggerEvent);
+ else
+ gtk_menu_popup_at_rect(m_pMenu, widget_get_surface(pWidget), &aRect, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, pTriggerEvent);
+ }
+ else
+ {
+ if (bSwapForRTL)
+ gtk_menu_popup_at_rect(m_pMenu, widget_get_surface(pWidget), &aRect, GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_NORTH_EAST, pTriggerEvent);
+ else
+ gtk_menu_popup_at_rect(m_pMenu, widget_get_surface(pWidget), &aRect, GDK_GRAVITY_NORTH_EAST, GDK_GRAVITY_NORTH_WEST, pTriggerEvent);
+ }
+
+ gdk_event_free(pKeyEvent);
+ }
+ else
+#else
+ (void) rRect;
+#endif
+ {
+ gtk_menu_attach_to_widget(m_pMenu, pWidget, nullptr);
+
+ guint nButton;
+ guint32 nTime;
+
+ //typically there is an event, and we can then distinguish if this was
+ //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
+ //doesn't)
+ GdkEvent *pEvent = gtk_get_current_event();
+ if (pEvent)
+ {
+ if (!gdk_event_get_button(pEvent, &nButton))
+ nButton = 0;
+ nTime = gdk_event_get_time(pEvent);
+ }
+ else
+ {
+ nButton = 0;
+ nTime = GtkSalFrame::GetLastInputEventTime();
+ }
+
+ gtk_menu_popup(m_pMenu, nullptr, nullptr, nullptr, nullptr, nButton, nTime);
+ }
+#endif
+
+ if (g_main_loop_is_running(pLoop))
+ main_loop_run(pLoop);
+
+ g_main_loop_unref(pLoop);
+ g_signal_handler_disconnect(m_pMenu, nSignalId);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (!pOrigParent)
+ gtk_widget_unparent(GTK_WIDGET(m_pMenu));
+ else
+ gtk_widget_set_parent(GTK_WIDGET(m_pMenu), pOrigParent);
+
+ gtk_widget_insert_action_group(pWidget, "menu", nullptr);
+#else
+ gtk_menu_detach(m_pMenu);
+#endif
+
+ return m_sActivated;
+ }
+
+ virtual void set_sensitive(const OUString& rIdent, bool bSensitive) override
+ {
+ set_item_sensitive(rIdent, bSensitive);
+ }
+
+ virtual bool get_sensitive(const OUString& rIdent) const override
+ {
+ return get_item_sensitive(rIdent);
+ }
+
+ virtual void set_active(const OUString& rIdent, bool bActive) override
+ {
+ set_item_active(rIdent, bActive);
+ }
+
+ virtual bool get_active(const OUString& rIdent) const override
+ {
+ return get_item_active(rIdent);
+ }
+
+ virtual void set_visible(const OUString& rIdent, bool bShow) override
+ {
+ set_item_visible(rIdent, bShow);
+ }
+
+ virtual void set_label(const OUString& rIdent, const OUString& rLabel) override
+ {
+ set_item_label(rIdent, rLabel);
+ }
+
+ virtual OUString get_label(const OUString& rIdent) const override
+ {
+ return get_item_label(rIdent);
+ }
+
+ virtual void insert_separator(int pos, const OUString& rId) override
+ {
+ MenuHelper::insert_separator(pos, rId);
+ }
+
+ virtual void clear() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ clear_extras();
+#endif
+ MenuHelper::clear_items();
+ }
+
+ virtual void insert(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, VirtualDevice* pImageSurface,
+ const css::uno::Reference<css::graphic::XGraphic>& rGraphic,
+ TriState eCheckRadioFalse) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pImage = nullptr;
+ if (pIconName)
+ pImage = image_new_from_icon_name(*pIconName);
+ else if (pImageSurface)
+ {
+ pImage = image_new_from_virtual_device(*pImageSurface);
+ }
+ else if (rGraphic)
+ {
+ pImage = image_new_from_xgraphic(rGraphic, false);
+ }
+
+ GtkWidget *pItem;
+ if (pImage)
+ {
+ GtkBox *pBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
+ GtkWidget *pLabel = gtk_label_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
+ gtk_label_set_xalign(GTK_LABEL(pLabel), 0.0);
+ pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new() : gtk_menu_item_new();
+ gtk_box_pack_start(pBox, pImage, false, true, 0);
+ gtk_box_pack_start(pBox, pLabel, true, true, 0);
+ gtk_container_add(GTK_CONTAINER(pItem), GTK_WIDGET(pBox));
+ gtk_widget_show_all(pItem);
+ }
+ else
+ {
+ pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr())
+ : gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
+ }
+
+ if (eCheckRadioFalse == TRISTATE_FALSE)
+ gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(pItem), true);
+
+ ::set_buildable_id(GTK_BUILDABLE(pItem), rId);
+ gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
+ gtk_widget_show(pItem);
+ GtkMenuItem* pMenuItem = GTK_MENU_ITEM(pItem);
+ m_aExtraItems.push_back(pMenuItem);
+ add_to_map(pMenuItem);
+ if (m_pTopLevelMenuHelper)
+ m_pTopLevelMenuHelper->add_to_map(pMenuItem);
+ if (pos != -1)
+ gtk_menu_reorder_child(m_pMenu, pItem, pos);
+#else
+ SAL_WARN("vcl.gtk", "needs to be implemented for gtk4");
+ (void)pIconName;
+ (void)pImageSurface;
+ (void)rGraphic;
+
+ if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
+ {
+ auto aSectionAndPos = get_section_and_pos_for(pMenuModel, pos);
+ GMenu* pMenu = G_MENU(aSectionAndPos.first);
+ // action with a target value ... the action name and target value are separated by a double
+ // colon ... For example: "app.action::target"
+ OUString sActionAndTarget;
+ if (eCheckRadioFalse == TRISTATE_INDET)
+ sActionAndTarget = "menu.normal." + rId + "::" + rId;
+ else
+ sActionAndTarget = "menu.radio." + rId + "::" + rId;
+ g_menu_insert(pMenu, aSectionAndPos.second, MapToGtkAccelerator(rStr).getStr(), sActionAndTarget.toUtf8().getStr());
+
+ assert(eCheckRadioFalse == TRISTATE_INDET); // come back to this later
+
+ // TODO not redo entire group
+ update_action_group_from_popover_model();
+ }
+
+#endif
+ }
+
+ virtual OUString get_id(int pos) const override
+ {
+ return get_item_id(pos);
+ }
+
+ virtual int n_children() const override
+ {
+ return get_n_children();
+ }
+
+ virtual void set_item_help_id(const OUString& rIdent, const OUString& rHelpId) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ ::set_help_id(GTK_WIDGET(m_aMap[rIdent]), rHelpId);
+#else
+ (void)rIdent;
+ (void)rHelpId;
+#endif
+ }
+
+ void remove(const OUString& rIdent) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!m_aExtraItems.empty())
+ {
+ GtkMenuItem* pMenuItem = m_aMap[rIdent];
+ auto iter = std::find(m_aExtraItems.begin(), m_aExtraItems.end(), pMenuItem);
+ if (iter != m_aExtraItems.end())
+ {
+ if (m_pTopLevelMenuHelper)
+ m_pTopLevelMenuHelper->remove_from_map(pMenuItem);
+ m_aExtraItems.erase(iter);
+ }
+ }
+#endif
+ MenuHelper::remove_item(rIdent);
+ }
+
+ virtual ~GtkInstanceMenu() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ clear_extras();
+#endif
+ g_object_steal_data(G_OBJECT(m_pMenu), "g-lo-GtkInstanceMenu");
+ }
+};
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ vcl::ImageType GtkToVcl(GtkIconSize eSize)
+ {
+ vcl::ImageType eRet;
+ switch (eSize)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ case GTK_ICON_SIZE_MENU:
+ case GTK_ICON_SIZE_SMALL_TOOLBAR:
+ case GTK_ICON_SIZE_BUTTON:
+ eRet = vcl::ImageType::Size16;
+ break;
+ case GTK_ICON_SIZE_LARGE_TOOLBAR:
+ eRet = vcl::ImageType::Size26;
+ break;
+ case GTK_ICON_SIZE_DND:
+ case GTK_ICON_SIZE_DIALOG:
+ eRet = vcl::ImageType::Size32;
+ break;
+ default:
+ case GTK_ICON_SIZE_INVALID:
+ eRet = vcl::ImageType::Small;
+ break;
+#else
+ case GTK_ICON_SIZE_LARGE:
+ eRet = vcl::ImageType::Size32;
+ break;
+ case GTK_ICON_SIZE_NORMAL:
+ default:
+ eRet = vcl::ImageType::Size16;
+ break;
+#endif
+ }
+ return eRet;
+ }
+
+ GtkIconSize VclToGtk(vcl::ImageType eSize)
+ {
+ GtkIconSize eRet;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ switch (eSize)
+ {
+ case vcl::ImageType::Size16:
+ eRet = GTK_ICON_SIZE_SMALL_TOOLBAR;
+ break;
+ case vcl::ImageType::Size26:
+ eRet = GTK_ICON_SIZE_LARGE_TOOLBAR;
+ break;
+ case vcl::ImageType::Size32:
+ eRet = GTK_ICON_SIZE_DIALOG;
+ break;
+ default:
+ O3TL_UNREACHABLE;
+ }
+#else
+ switch (eSize)
+ {
+ case vcl::ImageType::Size26:
+ case vcl::ImageType::Size32:
+ eRet = GTK_ICON_SIZE_LARGE;
+ break;
+ case vcl::ImageType::Size16:
+ default:
+ eRet = GTK_ICON_SIZE_NORMAL;
+ break;
+ }
+#endif
+ return eRet;
+ }
+
+ // tdf#153885 for wayland if the popover window is the application
+ // window, constrain it within the application window so it won't
+ // be cut off screen. Leave dialog hosted ones alone, like
+ // format, watermark, which are likely presented in the middle
+ // of the screen and are too small to constrain the popover inside.
+ void ConstrainApplicationWindowPopovers(GtkToggleButton* pItem)
+ {
+#if defined(GDK_WINDOWING_WAYLAND)
+ GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(pItem));
+ if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay) && GTK_IS_MENU_BUTTON(pItem))
+ {
+ GtkMenuButton* pMenuButton = GTK_MENU_BUTTON(pItem);
+ if (GtkPopover* pPopover = gtk_menu_button_get_popover(pMenuButton))
+ {
+ if (gtk_popover_get_constrain_to(pPopover) == GTK_POPOVER_CONSTRAINT_NONE)
+ {
+ GtkWidget* pTopLevel = widget_get_toplevel(GTK_WIDGET(pItem));
+ GtkSalFrame* pFrame = pTopLevel ? GtkSalFrame::getFromWindow(pTopLevel) : nullptr;
+ if (pFrame)
+ {
+ // the toplevel is an application window
+ gtk_popover_set_constrain_to(pPopover, GTK_POPOVER_CONSTRAINT_WINDOW);
+ }
+ }
+ }
+ }
+#else
+ (void)pItem;
+#endif
+ }
+
+#endif
+}
+
+void GtkInstanceMenuButton::set_menu(weld::Menu* pMenu)
+{
+ GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
+ m_pPopover = nullptr;
+ m_pMenu = pPopoverWidget ? pPopoverWidget->getMenu() : nullptr;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_menu_button_set_popup(m_pMenuButton, GTK_WIDGET(m_pMenu));
+#else
+ gtk_menu_button_set_popover(m_pMenuButton, GTK_WIDGET(m_pMenu));
+ update_action_group_from_popover_model();
+#endif
+}
+
+namespace {
+
+class GtkInstanceToolbar : public GtkInstanceWidget, public virtual weld::Toolbar
+{
+private:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkToolbar* m_pToolbar;
+#else
+ GtkBox* m_pToolbar;
+ vcl::ImageType m_eImageType;
+#endif
+ GtkCssProvider *m_pMenuButtonProvider;
+
+ std::map<OUString, GtkWidget*> m_aMap;
+ std::map<OUString, std::unique_ptr<GtkInstanceMenuButton>> m_aMenuButtonMap;
+ std::map<OUString, bool> m_aMirroredMap;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // at the time of writing there is no gtk_menu_tool_button_set_popover available
+ // though there will be in the future
+ // https://gitlab.gnome.org/GNOME/gtk/commit/03e30431a8af9a947a0c4ccab545f24da16bfe17?w=1
+ static void find_menu_button(GtkWidget *pWidget, gpointer user_data)
+ {
+ if (g_strcmp0(gtk_widget_get_name(pWidget), "GtkMenuButton") == 0)
+ {
+ GtkWidget **ppToggleButton = static_cast<GtkWidget**>(user_data);
+ *ppToggleButton = pWidget;
+ }
+ else if (GTK_IS_CONTAINER(pWidget))
+ gtk_container_forall(GTK_CONTAINER(pWidget), find_menu_button, user_data);
+ }
+
+ static void find_menupeer_button(GtkWidget *pWidget, gpointer user_data)
+ {
+ if (g_strcmp0(gtk_widget_get_name(pWidget), "GtkButton") == 0)
+ {
+ GtkWidget **ppButton = static_cast<GtkWidget**>(user_data);
+ *ppButton = pWidget;
+ }
+ else if (GTK_IS_CONTAINER(pWidget))
+ gtk_container_forall(GTK_CONTAINER(pWidget), find_menupeer_button, user_data);
+ }
+#endif
+
+ static void collect(GtkWidget* pItem, gpointer widget)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!GTK_IS_TOOL_ITEM(pItem))
+ return;
+#endif
+ GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
+
+ GtkMenuButton* pMenuButton = nullptr;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_MENU_TOOL_BUTTON(pItem))
+ find_menu_button(pItem, &pMenuButton);
+#else
+ if (GTK_IS_MENU_BUTTON(pItem))
+ pMenuButton = GTK_MENU_BUTTON(pItem);
+#endif
+
+ pThis->add_to_map(pItem, pMenuButton);
+ }
+
+ void add_to_map(GtkWidget* pToolItem, GtkMenuButton* pMenuButton)
+ {
+ OUString id = ::get_buildable_id(GTK_BUILDABLE(pToolItem));
+ m_aMap[id] = pToolItem;
+ if (pMenuButton)
+ {
+ m_aMenuButtonMap[id] = std::make_unique<GtkInstanceMenuButton>(pMenuButton, GTK_WIDGET(pToolItem), m_pBuilder, false);
+ // so that, e.g. with focus initially in writer main document then
+ // after clicking the heading menu in the writer navigator focus is
+ // left in the main document and not in the toolbar
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_button_set_focus_on_click(GTK_BUTTON(pMenuButton), false);
+ g_signal_connect(pMenuButton, "toggled", G_CALLBACK(signalItemToggled), this);
+#else
+ gtk_widget_set_focus_on_click(GTK_WIDGET(pMenuButton), false);
+
+ GtkWidget* pToggleButton = gtk_widget_get_first_child(GTK_WIDGET(pMenuButton));
+ assert(GTK_IS_TOGGLE_BUTTON(pToggleButton));
+ g_signal_connect(pToggleButton, "toggled", G_CALLBACK(signalItemToggled), this);
+#endif
+
+ // by default the GtkMenuButton down arrow button is as wide as
+ // a normal button and LibreOffice's original ones are very
+ // narrow, that assumption is fairly baked into the toolbar and
+ // sidebar designs, try and minimize the width of the dropdown
+ // zone.
+ GtkStyleContext *pButtonContext = gtk_widget_get_style_context(GTK_WIDGET(pMenuButton));
+
+ if (!m_pMenuButtonProvider)
+ {
+ m_pMenuButtonProvider = gtk_css_provider_new();
+ static const gchar data[] = "* { "
+ "padding: 0;"
+ "margin-left: 0px;"
+ "margin-right: 0px;"
+ "min-width: 4px;"
+ "}";
+ css_provider_load_from_data(m_pMenuButtonProvider, data, -1);
+ }
+
+ gtk_style_context_add_provider(pButtonContext,
+ GTK_STYLE_PROVIDER(m_pMenuButtonProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!GTK_IS_TOOL_BUTTON(pToolItem))
+#else
+ if (!GTK_IS_BUTTON(pToolItem))
+#endif
+ {
+ return;
+ }
+ g_signal_connect(pToolItem, "clicked", G_CALLBACK(signalItemClicked), this);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void signalItemClicked(GtkToolButton* pItem, gpointer widget)
+#else
+ static void signalItemClicked(GtkButton* pItem, gpointer widget)
+#endif
+ {
+ GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_item_clicked(pItem);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ void signal_item_clicked(GtkToolButton* pItem)
+#else
+ void signal_item_clicked(GtkButton* pItem)
+#endif
+ {
+ signal_clicked(::get_buildable_id(GTK_BUILDABLE(pItem)));
+ }
+
+ static void signalItemToggled(GtkToggleButton* pItem, gpointer widget)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ ConstrainApplicationWindowPopovers(pItem);
+#endif
+ GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_item_toggled(pItem);
+ }
+
+ void signal_item_toggled(GtkToggleButton* pItem)
+ {
+ for (const auto& a : m_aMenuButtonMap)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (a.second->getWidget() == GTK_WIDGET(pItem))
+#else
+ if (a.second->getWidget() == gtk_widget_get_parent(GTK_WIDGET(pItem)))
+#endif
+ {
+ signal_toggle_menu(a.first);
+ break;
+ }
+ }
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void set_item_image(GtkWidget* pItem, GtkWidget* pImage)
+ {
+ if (GTK_IS_BUTTON(pItem))
+ gtk_button_set_child(GTK_BUTTON(pItem), pImage);
+ else if (GTK_IS_MENU_BUTTON(pItem))
+ {
+ // TODO after gtk 4.6 is released require that version and drop this
+ static auto menu_button_set_child = reinterpret_cast<void (*) (GtkMenuButton*, GtkWidget*)>(dlsym(nullptr, "gtk_menu_button_set_child"));
+ if (menu_button_set_child)
+ menu_button_set_child(GTK_MENU_BUTTON(pItem), pImage);
+ }
+ // versions of gtk4 > 4.2.1 might do this on their own
+ gtk_widget_remove_css_class(pItem, "text-button");
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void set_item_image(GtkToolButton* pItem, const css::uno::Reference<css::graphic::XGraphic>& rIcon, bool bMirror)
+#else
+ static void set_item_image(GtkWidget* pItem, const css::uno::Reference<css::graphic::XGraphic>& rIcon, bool bMirror)
+#endif
+ {
+ GtkWidget* pImage = image_new_from_xgraphic(rIcon, bMirror);
+ if (pImage)
+ gtk_widget_show(pImage);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_tool_button_set_icon_widget(pItem, pImage);
+#else
+ set_item_image(pItem, pImage);
+#endif
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ void set_item_image(GtkToolButton* pItem, const VirtualDevice* pDevice)
+#else
+ void set_item_image(GtkWidget* pItem, const VirtualDevice* pDevice)
+#endif
+ {
+ GtkWidget* pImage = nullptr;
+
+ if (pDevice)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ pImage = picture_new_from_virtual_device(*pDevice);
+#else
+ pImage = image_new_from_virtual_device(*pDevice);
+#endif
+ gtk_widget_show(pImage);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_tool_button_set_icon_widget(pItem, pImage);
+#else
+ set_item_image(pItem, pImage);
+#endif
+ gtk_widget_queue_draw(GTK_WIDGET(m_pToolbar));
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* toolbar_get_nth_item(int nIndex) const
+ {
+ return GTK_WIDGET(gtk_toolbar_get_nth_item(m_pToolbar, nIndex));
+ }
+#else
+ GtkWidget* toolbar_get_nth_item(int nIndex) const
+ {
+ int i = 0;
+ for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pToolbar));
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ if (i == nIndex)
+ return pChild;
+ ++i;
+ }
+ return nullptr;
+ }
+#endif
+public:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkInstanceToolbar(GtkToolbar* pToolbar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+#else
+ GtkInstanceToolbar(GtkBox* pToolbar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+#endif
+ : GtkInstanceWidget(GTK_WIDGET(pToolbar), pBuilder, bTakeOwnership)
+ , m_pToolbar(pToolbar)
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_eImageType(vcl::ImageType::Size16)
+#endif
+ , m_pMenuButtonProvider(nullptr)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(pToolbar));
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ collect(pChild, this);
+ }
+#else
+ gtk_container_foreach(GTK_CONTAINER(pToolbar), collect, this);
+#endif
+ }
+
+ void disable_item_notify_events()
+ {
+ for (auto& a : m_aMap)
+ {
+ g_signal_handlers_block_by_func(a.second, reinterpret_cast<void*>(signalItemClicked), this);
+ }
+ }
+
+ void enable_item_notify_events()
+ {
+ for (auto& a : m_aMap)
+ {
+ g_signal_handlers_unblock_by_func(a.second, reinterpret_cast<void*>(signalItemClicked), this);
+ }
+ }
+
+ virtual void set_item_sensitive(const OUString& rIdent, bool bSensitive) override
+ {
+ disable_item_notify_events();
+ gtk_widget_set_sensitive(GTK_WIDGET(m_aMap[rIdent]), bSensitive);
+ enable_item_notify_events();
+ }
+
+ virtual bool get_item_sensitive(const OUString& rIdent) const override
+ {
+ return gtk_widget_get_sensitive(GTK_WIDGET(m_aMap.find(rIdent)->second));
+ }
+
+ virtual void set_item_visible(const OUString& rIdent, bool bVisible) override
+ {
+ disable_item_notify_events();
+ gtk_widget_set_visible(GTK_WIDGET(m_aMap[rIdent]), bVisible);
+ enable_item_notify_events();
+ }
+
+ virtual void set_item_help_id(const OUString& rIdent, const OUString& rHelpId) override
+ {
+ ::set_help_id(GTK_WIDGET(m_aMap[rIdent]), rHelpId);
+ }
+
+ virtual bool get_item_visible(const OUString& rIdent) const override
+ {
+ return gtk_widget_get_visible(GTK_WIDGET(m_aMap.find(rIdent)->second));
+ }
+
+ virtual void set_item_active(const OUString& rIdent, bool bActive) override
+ {
+ disable_item_notify_events();
+
+ GtkWidget* pToolButton = m_aMap.find(rIdent)->second;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_TOGGLE_TOOL_BUTTON(pToolButton))
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(pToolButton), bActive);
+ else
+ {
+ GtkButton* pButton = nullptr;
+ // there is no GtkMenuToggleToolButton so abuse the CHECKED state of the GtkMenuToolButton button
+ // to emulate one
+ find_menupeer_button(GTK_WIDGET(pToolButton), &pButton);
+ if (pButton)
+ {
+ auto eState = gtk_widget_get_state_flags(GTK_WIDGET(pButton)) & ~GTK_STATE_FLAG_CHECKED;
+ if (bActive)
+ eState |= GTK_STATE_FLAG_CHECKED;
+ gtk_widget_set_state_flags(GTK_WIDGET(pButton), static_cast<GtkStateFlags>(eState), true);
+ }
+ }
+#else
+ GtkWidget* pWidget;
+ if (GTK_IS_MENU_BUTTON(pToolButton))
+ {
+ pWidget = gtk_widget_get_first_child(pToolButton);
+ assert(GTK_IS_TOGGLE_BUTTON(pWidget));
+ }
+ else
+ pWidget = pToolButton;
+ auto eState = gtk_widget_get_state_flags(pWidget) & ~GTK_STATE_FLAG_CHECKED;
+ if (bActive)
+ eState |= GTK_STATE_FLAG_CHECKED;
+ gtk_widget_set_state_flags(pWidget, static_cast<GtkStateFlags>(eState), true);
+#endif
+
+ enable_item_notify_events();
+ }
+
+ virtual bool get_item_active(const OUString& rIdent) const override
+ {
+ GtkWidget* pToolButton = m_aMap.find(rIdent)->second;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_TOGGLE_TOOL_BUTTON(pToolButton))
+ return gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(pToolButton));
+ else
+ {
+ GtkButton* pButton = nullptr;
+ // there is no GtkMenuToggleToolButton so abuse the CHECKED state of the GtkMenuToolButton button
+ // to emulate one
+ find_menupeer_button(GTK_WIDGET(pToolButton), &pButton);
+ if (pButton)
+ {
+ return gtk_widget_get_state_flags(GTK_WIDGET(pButton)) & GTK_STATE_FLAG_CHECKED;
+ }
+ }
+#else
+ GtkWidget* pWidget;
+ if (GTK_IS_MENU_BUTTON(pToolButton))
+ {
+ pWidget = gtk_widget_get_first_child(pToolButton);
+ assert(GTK_IS_TOGGLE_BUTTON(pWidget));
+ }
+ else
+ pWidget = pToolButton;
+ return gtk_widget_get_state_flags(pWidget) & GTK_STATE_FLAG_CHECKED;
+#endif
+
+ return false;
+ }
+
+ virtual void set_menu_item_active(const OUString& rIdent, bool bActive) override
+ {
+ disable_item_notify_events();
+
+ auto aFind = m_aMenuButtonMap.find(rIdent);
+ assert (aFind != m_aMenuButtonMap.end());
+ aFind->second->set_active(bActive);
+
+ enable_item_notify_events();
+ }
+
+ virtual bool get_menu_item_active(const OUString& rIdent) const override
+ {
+ auto aFind = m_aMenuButtonMap.find(rIdent);
+ assert (aFind != m_aMenuButtonMap.end());
+ return aFind->second->get_active();
+ }
+
+ virtual void insert_item(int pos, const OUString& rId) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkToolItem* pItem = gtk_tool_button_new(nullptr, OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
+#else
+ GtkWidget* pItem = gtk_button_new();
+#endif
+ ::set_buildable_id(GTK_BUILDABLE(pItem), rId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_toolbar_insert(m_pToolbar, pItem, pos);
+#else
+ gtk_box_insert_child_after(m_pToolbar, pItem, toolbar_get_nth_item(pos - 1));
+#endif
+ gtk_widget_show(GTK_WIDGET(pItem));
+ add_to_map(GTK_WIDGET(pItem), nullptr);
+ }
+
+ virtual void insert_separator(int pos, const OUString& rId) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkToolItem* pItem = gtk_separator_tool_item_new();
+#else
+ GtkWidget* pItem = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
+#endif
+ ::set_buildable_id(GTK_BUILDABLE(pItem), rId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_toolbar_insert(m_pToolbar, pItem, pos);
+#else
+ gtk_box_insert_child_after(m_pToolbar, pItem, toolbar_get_nth_item(pos - 1));
+#endif
+ gtk_widget_show(GTK_WIDGET(pItem));
+ }
+
+ virtual void set_item_popover(const OUString& rIdent, weld::Widget* pPopover) override
+ {
+ m_aMenuButtonMap[rIdent]->set_popover(pPopover);
+ }
+
+ virtual void set_item_menu(const OUString& rIdent, weld::Menu* pMenu) override
+ {
+ m_aMenuButtonMap[rIdent]->set_menu(pMenu);
+ }
+
+ virtual int get_n_items() const override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_toolbar_get_n_items(m_pToolbar);
+#else
+ int n_items = 0;
+ for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pToolbar));
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ ++n_items;
+ }
+ return n_items;
+#endif
+ }
+
+ virtual OUString get_item_ident(int nIndex) const override
+ {
+ auto* pItem = toolbar_get_nth_item(nIndex);
+ return ::get_buildable_id(GTK_BUILDABLE(pItem));
+ }
+
+ virtual void set_item_ident(int nIndex, const OUString& rIdent) override
+ {
+ OUString sOldIdent(get_item_ident(nIndex));
+ m_aMap.erase(m_aMap.find(sOldIdent));
+
+ auto* pItem = toolbar_get_nth_item(nIndex);
+ ::set_buildable_id(GTK_BUILDABLE(pItem), rIdent);
+
+ // to keep the ids unique, if the new id is already in use by an item,
+ // change the id of that item to the now unused old ident of this item
+ auto aFind = m_aMap.find(rIdent);
+ if (aFind != m_aMap.end())
+ {
+ GtkWidget* pDupIdItem = aFind->second;
+ ::set_buildable_id(GTK_BUILDABLE(pDupIdItem), sOldIdent);
+ m_aMap[sOldIdent] = pDupIdItem;
+ }
+
+ m_aMap[rIdent] = pItem;
+ }
+
+ virtual void set_item_label(int nIndex, const OUString& rLabel) override
+ {
+ auto* pItem = toolbar_get_nth_item(nIndex);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem), MapToGtkAccelerator(rLabel).getStr());
+#else
+ if (!GTK_IS_BUTTON(pItem))
+ return;
+ ::button_set_label(GTK_BUTTON(pItem), rLabel);
+#endif
+ }
+
+ virtual void set_item_label(const OUString& rIdent, const OUString& rLabel) override
+ {
+ GtkWidget* pItem = m_aMap[rIdent];
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem), MapToGtkAccelerator(rLabel).getStr());
+#else
+ if (!pItem || !GTK_IS_BUTTON(pItem))
+ return;
+ ::button_set_label(GTK_BUTTON(pItem), rLabel);
+#endif
+ }
+
+ OUString get_item_label(const OUString& rIdent) const override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ const gchar* pText = gtk_tool_button_get_label(GTK_TOOL_BUTTON(m_aMap.find(rIdent)->second));
+#else
+ const gchar* pText = gtk_button_get_label(GTK_BUTTON(m_aMap.find(rIdent)->second));
+#endif
+ return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ virtual void set_item_icon_name(const OUString& rIdent, const OUString& rIconName) override
+ {
+ GtkWidget* pItem = m_aMap[rIdent];
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
+ return;
+#else
+ if (!pItem || !GTK_IS_BUTTON(pItem))
+ return;
+#endif
+
+ GtkWidget* pImage = image_new_from_icon_name(rIconName);
+ if (pImage)
+ gtk_widget_show(pImage);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(pItem), pImage);
+#else
+ gtk_button_set_child(GTK_BUTTON(pItem), pImage);
+ // versions of gtk4 > 4.2.1 might do this on their own
+ gtk_widget_remove_css_class(GTK_WIDGET(pItem), "text-button");
+#endif
+ }
+
+ virtual void set_item_image_mirrored(const OUString& rIdent, bool bMirrored) override
+ {
+ m_aMirroredMap[rIdent] = bMirrored;
+ }
+
+ virtual void set_item_image(const OUString& rIdent, const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
+ {
+ GtkWidget* pItem = m_aMap[rIdent];
+ auto it = m_aMirroredMap.find(rIdent);
+ bool bMirrored = it != m_aMirroredMap.end() && it->second;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ set_item_image(GTK_TOOL_BUTTON(pItem), rIcon, bMirrored);
+#else
+ if (!pItem)
+ return;
+ set_item_image(pItem, rIcon, bMirrored);
+#endif
+ }
+
+ virtual void set_item_image(const OUString& rIdent, VirtualDevice* pDevice) override
+ {
+ GtkWidget* pItem = m_aMap[rIdent];
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ set_item_image(GTK_TOOL_BUTTON(pItem), pDevice);
+#else
+ if (!pItem)
+ return;
+ set_item_image(pItem, pDevice);
+#endif
+ }
+
+ virtual void set_item_image(int nIndex, const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
+ {
+ auto* pItem = toolbar_get_nth_item(nIndex);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ set_item_image(GTK_TOOL_BUTTON(pItem), rIcon, false);
+#else
+ set_item_image(pItem, rIcon, false);
+#endif
+ }
+
+ virtual void set_item_tooltip_text(int nIndex, const OUString& rTip) override
+ {
+ auto* pItem = toolbar_get_nth_item(nIndex);
+ gtk_widget_set_tooltip_text(GTK_WIDGET(pItem), OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ virtual void set_item_tooltip_text(const OUString& rIdent, const OUString& rTip) override
+ {
+ GtkWidget* pItem = GTK_WIDGET(m_aMap[rIdent]);
+ gtk_widget_set_tooltip_text(pItem, OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ virtual OUString get_item_tooltip_text(const OUString& rIdent) const override
+ {
+ GtkWidget* pItem = GTK_WIDGET(m_aMap.find(rIdent)->second);
+ const gchar* pStr = gtk_widget_get_tooltip_text(pItem);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ virtual vcl::ImageType get_icon_size() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return m_eImageType;
+#else
+ return GtkToVcl(gtk_toolbar_get_icon_size(m_pToolbar));
+#endif
+ }
+
+ virtual void set_icon_size(vcl::ImageType eType) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_eImageType = eType;
+#else
+ gtk_toolbar_set_icon_size(m_pToolbar, VclToGtk(eType));
+#endif
+ }
+
+ virtual sal_uInt16 get_modifier_state() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GdkDisplay* pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pToolbar));
+ GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
+ GdkDevice* pDevice = gdk_seat_get_keyboard(pSeat);
+ guint nState = gdk_device_get_modifier_state(pDevice);
+#else
+ GdkKeymap* pKeymap = gdk_keymap_get_default();
+ guint nState = gdk_keymap_get_modifier_state(pKeymap);
+#endif
+ return GtkSalFrame::GetKeyModCode(nState);
+ }
+
+ virtual int get_drop_index(const Point& rPoint) const override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_toolbar_get_drop_index(m_pToolbar, rPoint.X(), rPoint.Y());
+#else
+ GtkWidget* pToolbar = GTK_WIDGET(m_pToolbar);
+ GtkWidget* pTarget = gtk_widget_pick(pToolbar, rPoint.X(), rPoint.Y(), GTK_PICK_DEFAULT);
+ if (!pTarget || pTarget == pToolbar)
+ return -1;
+ int i = 0;
+ for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pToolbar));
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ if (pChild == pTarget)
+ return i;
+ ++i;
+ }
+ return -1;
+#endif
+ }
+
+ virtual bool has_focus() const override
+ {
+ if (gtk_widget_has_focus(m_pWidget))
+ return true;
+
+ GtkWidget* pTopLevel = widget_get_toplevel(m_pWidget);
+ if (!GTK_IS_WINDOW(pTopLevel))
+ return false;
+ GtkWidget* pFocus = gtk_window_get_focus(GTK_WINDOW(pTopLevel));
+ if (!pFocus)
+ return false;
+ return gtk_widget_is_ancestor(pFocus, m_pWidget);
+ }
+
+ virtual void grab_focus() override
+ {
+ if (has_focus())
+ return;
+ gtk_widget_grab_focus(m_pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ bool bHasFocusChild = gtk_widget_get_focus_child(m_pWidget);
+#else
+ bool bHasFocusChild = gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget));
+#endif
+ if (!bHasFocusChild)
+ {
+ if (auto* pItem = toolbar_get_nth_item(0))
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_focus_child(m_pWidget, GTK_WIDGET(pItem));
+#else
+ gtk_container_set_focus_child(GTK_CONTAINER(m_pWidget), GTK_WIDGET(pItem));
+#endif
+ bHasFocusChild = true;
+ }
+ }
+ if (bHasFocusChild)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_child_focus(gtk_widget_get_focus_child(m_pWidget), GTK_DIR_TAB_FORWARD);
+#else
+ gtk_widget_child_focus(gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget)), GTK_DIR_TAB_FORWARD);
+#endif
+ }
+ }
+
+ virtual ~GtkInstanceToolbar() override
+ {
+ for (auto& a : m_aMap)
+ g_signal_handlers_disconnect_by_data(a.second, this);
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstanceLinkButton : public GtkInstanceWidget, public virtual weld::LinkButton
+{
+private:
+ GtkLinkButton* m_pButton;
+ gulong m_nSignalId;
+
+ static bool signalActivateLink(GtkButton*, gpointer widget)
+ {
+ GtkInstanceLinkButton* pThis = static_cast<GtkInstanceLinkButton*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_activate_link();
+ }
+
+public:
+ GtkInstanceLinkButton(GtkLinkButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
+ , m_pButton(pButton)
+ , m_nSignalId(g_signal_connect(pButton, "activate-link", G_CALLBACK(signalActivateLink), this))
+ {
+ }
+
+ virtual void set_label(const OUString& rText) override
+ {
+ ::button_set_label(GTK_BUTTON(m_pButton), rText);
+ }
+
+ virtual OUString get_label() const override
+ {
+ return ::button_get_label(GTK_BUTTON(m_pButton));
+ }
+
+ virtual void set_uri(const OUString& rText) override
+ {
+ gtk_link_button_set_uri(m_pButton, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ virtual void set_label_wrap(bool bWrap) override
+ {
+ GtkLabel* pChild = ::get_label_widget(GTK_WIDGET(m_pButton));
+ ::set_label_wrap(pChild, bWrap);
+ gtk_label_set_max_width_chars(pChild, 1);
+ }
+
+ virtual OUString get_uri() const override
+ {
+ const gchar* pStr = gtk_link_button_get_uri(m_pButton);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pButton, m_nSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pButton, m_nSignalId);
+ }
+
+ virtual ~GtkInstanceLinkButton() override
+ {
+ g_signal_handler_disconnect(m_pButton, m_nSignalId);
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstanceCheckButton : public GtkInstanceWidget, public virtual weld::CheckButton
+{
+private:
+ GtkCheckButton* m_pCheckButton;
+ gulong m_nSignalId;
+
+ static void signalToggled(void*, gpointer widget)
+ {
+ GtkInstanceCheckButton* pThis = static_cast<GtkInstanceCheckButton*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_toggled();
+ }
+
+public:
+ GtkInstanceCheckButton(GtkCheckButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
+ , m_pCheckButton(pButton)
+ , m_nSignalId(g_signal_connect(m_pCheckButton, "toggled", G_CALLBACK(signalToggled), this))
+ {
+ }
+
+ virtual void set_active(bool active) override
+ {
+ disable_notify_events();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_check_button_set_inconsistent(m_pCheckButton, false);
+ gtk_check_button_set_active(m_pCheckButton, active);
+#else
+ gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(m_pCheckButton), false);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pCheckButton), active);
+#endif
+ enable_notify_events();
+ }
+
+ virtual bool get_active() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_check_button_get_active(m_pCheckButton);
+#else
+ return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pCheckButton));
+#endif
+ }
+
+ virtual void set_inconsistent(bool inconsistent) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_check_button_set_inconsistent(m_pCheckButton, inconsistent);
+#else
+ gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(m_pCheckButton), inconsistent);
+#endif
+ }
+
+ virtual bool get_inconsistent() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_check_button_get_inconsistent(m_pCheckButton);
+#else
+ return gtk_toggle_button_get_inconsistent(GTK_TOGGLE_BUTTON(m_pCheckButton));
+#endif
+ }
+
+ virtual void set_label(const OUString& rText) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_check_button_set_label(m_pCheckButton, MapToGtkAccelerator(rText).getStr());
+#else
+ ::button_set_label(GTK_BUTTON(m_pCheckButton), rText);
+#endif
+ }
+
+ virtual OUString get_label() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ const gchar* pStr = gtk_check_button_get_label(m_pCheckButton);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+#else
+ return ::button_get_label(GTK_BUTTON(m_pCheckButton));
+#endif
+ }
+
+ virtual void set_label_wrap(bool bWrap) override
+ {
+ GtkLabel* pChild = ::get_label_widget(GTK_WIDGET(m_pCheckButton));
+ ::set_label_wrap(pChild, bWrap);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pCheckButton, m_nSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pCheckButton, m_nSignalId);
+ }
+
+ virtual ~GtkInstanceCheckButton() override
+ {
+ g_signal_handler_disconnect(m_pCheckButton, m_nSignalId);
+ }
+};
+
+class GtkInstanceRadioButton : public GtkInstanceCheckButton, public virtual weld::RadioButton
+{
+public:
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkInstanceRadioButton(GtkCheckButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceCheckButton(pButton, pBuilder, bTakeOwnership)
+#else
+ GtkInstanceRadioButton(GtkRadioButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceCheckButton(GTK_CHECK_BUTTON(pButton), pBuilder, bTakeOwnership)
+#endif
+ {
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstanceScale : public GtkInstanceWidget, public virtual weld::Scale
+{
+private:
+ GtkScale* m_pScale;
+ gulong m_nValueChangedSignalId;
+
+ static void signalValueChanged(GtkScale*, gpointer widget)
+ {
+ GtkInstanceScale* pThis = static_cast<GtkInstanceScale*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_value_changed();
+ }
+
+public:
+ GtkInstanceScale(GtkScale* pScale, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pScale), pBuilder, bTakeOwnership)
+ , m_pScale(pScale)
+ , m_nValueChangedSignalId(g_signal_connect(m_pScale, "value-changed", G_CALLBACK(signalValueChanged), this))
+ {
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pScale, m_nValueChangedSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pScale, m_nValueChangedSignalId);
+ }
+
+ virtual void set_value(int value) override
+ {
+ disable_notify_events();
+ gtk_range_set_value(GTK_RANGE(m_pScale), value);
+ enable_notify_events();
+ }
+
+ virtual void set_range(int min, int max) override
+ {
+ disable_notify_events();
+ gtk_range_set_range(GTK_RANGE(m_pScale), min, max);
+ enable_notify_events();
+ }
+
+ virtual void set_increments(int step, int page) override
+ {
+ disable_notify_events();
+ gtk_range_set_increments(GTK_RANGE(m_pScale), step, page);
+ enable_notify_events();
+ }
+
+ virtual void get_increments(int& step, int& page) const override
+ {
+ GtkAdjustment* pAdjustment = gtk_range_get_adjustment(GTK_RANGE(m_pScale));
+ step = gtk_adjustment_get_step_increment(pAdjustment);
+ page = gtk_adjustment_get_page_increment(pAdjustment);
+ }
+
+ virtual int get_value() const override
+ {
+ return gtk_range_get_value(GTK_RANGE(m_pScale));
+ }
+
+ virtual ~GtkInstanceScale() override
+ {
+ g_signal_handler_disconnect(m_pScale, m_nValueChangedSignalId);
+ }
+};
+
+class GtkInstanceProgressBar : public GtkInstanceWidget, public virtual weld::ProgressBar
+{
+private:
+ GtkProgressBar* m_pProgressBar;
+
+public:
+ GtkInstanceProgressBar(GtkProgressBar* pProgressBar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pProgressBar), pBuilder, bTakeOwnership)
+ , m_pProgressBar(pProgressBar)
+ {
+ }
+
+ virtual void set_percentage(int value) override
+ {
+ gtk_progress_bar_set_fraction(m_pProgressBar, value / 100.0);
+ }
+
+ virtual OUString get_text() const override
+ {
+ const gchar* pText = gtk_progress_bar_get_text(m_pProgressBar);
+ OUString sRet(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ return sRet;
+ }
+
+ virtual void set_text(const OUString& rText) override
+ {
+ gtk_progress_bar_set_text(m_pProgressBar, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+ }
+};
+
+class GtkInstanceLevelBar : public GtkInstanceWidget, public virtual weld::LevelBar
+{
+private:
+ GtkLevelBar* m_pLevelBar;
+
+public:
+ GtkInstanceLevelBar(GtkLevelBar* pLevelBar, GtkInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pLevelBar), pBuilder, bTakeOwnership)
+ , m_pLevelBar(pLevelBar)
+ {
+ }
+
+ virtual void set_percentage(double fPercentage) override
+ {
+ gtk_level_bar_set_value(m_pLevelBar, fPercentage / 100.0);
+ }
+};
+
+class GtkInstanceSpinner : public GtkInstanceWidget, public virtual weld::Spinner
+{
+private:
+ GtkSpinner* m_pSpinner;
+
+public:
+ GtkInstanceSpinner(GtkSpinner* pSpinner, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pSpinner), pBuilder, bTakeOwnership)
+ , m_pSpinner(pSpinner)
+ {
+ }
+
+ virtual void start() override
+ {
+ gtk_spinner_start(m_pSpinner);
+ }
+
+ virtual void stop() override
+ {
+ gtk_spinner_stop(m_pSpinner);
+ }
+};
+
+class GtkInstanceImage : public GtkInstanceWidget, public virtual weld::Image
+{
+private:
+ GtkImage* m_pImage;
+
+public:
+ GtkInstanceImage(GtkImage* pImage, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pImage), pBuilder, bTakeOwnership)
+ , m_pImage(pImage)
+ {
+ }
+
+ virtual void set_from_icon_name(const OUString& rIconName) override
+ {
+ image_set_from_icon_name(m_pImage, rIconName);
+ }
+
+ virtual void set_image(VirtualDevice* pDevice) override
+ {
+ image_set_from_virtual_device(m_pImage, pDevice);
+ }
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
+ {
+ image_set_from_xgraphic(m_pImage, rImage);
+ }
+};
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+class GtkInstancePicture: public GtkInstanceWidget, public virtual weld::Image
+{
+private:
+ GtkPicture* m_pPicture;
+
+public:
+ GtkInstancePicture(GtkPicture* pPicture, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pPicture), pBuilder, bTakeOwnership)
+ , m_pPicture(pPicture)
+ {
+ gtk_picture_set_can_shrink(m_pPicture, true);
+ }
+
+ virtual void set_from_icon_name(const OUString& rIconName) override
+ {
+ picture_set_from_icon_name(m_pPicture, rIconName);
+ }
+
+ virtual void set_image(VirtualDevice* pDevice) override
+ {
+ picture_set_from_virtual_device(m_pPicture, pDevice);
+ }
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rPicture) override
+ {
+ picture_set_from_xgraphic(m_pPicture, rPicture);
+ }
+};
+#endif
+
+class GtkInstanceCalendar : public GtkInstanceWidget, public virtual weld::Calendar
+{
+private:
+ GtkCalendar* m_pCalendar;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkEventController* m_pKeyController;
+#endif
+ gulong m_nDaySelectedSignalId;
+ gulong m_nDaySelectedDoubleClickSignalId;
+ gulong m_nKeyPressEventSignalId;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nButtonPressEventSignalId;
+#endif
+
+ static void signalDaySelected(GtkCalendar*, gpointer widget)
+ {
+ GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_selected();
+ }
+
+ static void signalDaySelectedDoubleClick(GtkCalendar*, gpointer widget)
+ {
+ GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_activated();
+ }
+
+ bool signal_key_press(guint nKeyVal)
+ {
+ if (nKeyVal == GDK_KEY_Return || nKeyVal == GDK_KEY_KP_Enter)
+ {
+ SolarMutexGuard aGuard;
+ signal_activated();
+ return true;
+ }
+ return false;
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalKeyPress(GtkEventControllerKey*, guint nKeyVal, guint /*nKeyCode*/, GdkModifierType, gpointer widget)
+ {
+ GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
+ return pThis->signal_key_press(nKeyVal);
+ }
+#else
+ static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
+ return pThis->signal_key_press(pEvent->keyval);
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalButton(GtkWidget*, GdkEventButton*, gpointer)
+ {
+ // don't let button press get to parent window, for the case of the
+ // ImplCFieldFloatWin floating window belonging to CalendarField where
+ // the click on the calendar continues to the parent GtkWindow and
+ // closePopup is called by GtkSalFrame::signalButton because the click
+ // window isn't that of the floating parent GtkWindow
+ return true;
+ }
+#endif
+
+public:
+ GtkInstanceCalendar(GtkCalendar* pCalendar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pCalendar), pBuilder, bTakeOwnership)
+ , m_pCalendar(pCalendar)
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_pKeyController(gtk_event_controller_key_new())
+#endif
+ , m_nDaySelectedSignalId(g_signal_connect(pCalendar, "day-selected", G_CALLBACK(signalDaySelected), this))
+ , m_nDaySelectedDoubleClickSignalId(g_signal_connect(pCalendar, "day-selected-double-click", G_CALLBACK(signalDaySelectedDoubleClick), this))
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_nKeyPressEventSignalId(g_signal_connect(m_pKeyController, "key-pressed", G_CALLBACK(signalKeyPress), this))
+#else
+ , m_nKeyPressEventSignalId(g_signal_connect(pCalendar, "key-press-event", G_CALLBACK(signalKeyPress), this))
+ , m_nButtonPressEventSignalId(g_signal_connect_after(pCalendar, "button-press-event", G_CALLBACK(signalButton), this))
+#endif
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_add_controller(GTK_WIDGET(m_pCalendar), m_pKeyController);
+#endif
+ }
+
+ virtual void set_date(const Date& rDate) override
+ {
+ if (!rDate.IsValidAndGregorian())
+ return;
+
+ disable_notify_events();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GDateTime* pDateTime = g_date_time_new_local(rDate.GetYear(), rDate.GetMonth(), rDate.GetDay(), 0, 0, 0);
+ gtk_calendar_select_day(m_pCalendar, pDateTime);
+ g_date_time_unref(pDateTime);
+#else
+ gtk_calendar_select_month(m_pCalendar, rDate.GetMonth() - 1, rDate.GetYear());
+ gtk_calendar_select_day(m_pCalendar, rDate.GetDay());
+#endif
+ enable_notify_events();
+ }
+
+ virtual Date get_date() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GDateTime* pDateTime = gtk_calendar_get_date(m_pCalendar);
+ Date aDate(g_date_time_get_day_of_month(pDateTime),
+ g_date_time_get_month(pDateTime),
+ g_date_time_get_year(pDateTime));
+ g_date_time_unref(pDateTime);
+ return aDate;
+#else
+ guint year, month, day;
+ gtk_calendar_get_date(m_pCalendar, &year, &month, &day);
+ return Date(day, month + 1, year);
+#endif
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
+ g_signal_handler_block(m_pCalendar, m_nDaySelectedSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pCalendar, m_nDaySelectedSignalId);
+ g_signal_handler_unblock(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
+ }
+
+ virtual ~GtkInstanceCalendar() override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pKeyController, m_nKeyPressEventSignalId);
+#else
+ g_signal_handler_disconnect(m_pCalendar, m_nButtonPressEventSignalId);
+ g_signal_handler_disconnect(m_pCalendar, m_nKeyPressEventSignalId);
+#endif
+ g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
+ g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedSignalId);
+ }
+};
+
+}
+
+namespace
+{
+ // CSS nodes: entry[.flat][.warning][.error]
+ void set_widget_css_message_type(GtkWidget* pWidget, weld::EntryMessageType eType)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_remove_css_class(pWidget, "error");
+ gtk_widget_remove_css_class(pWidget, "warning");
+#else
+ GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(pWidget);
+ gtk_style_context_remove_class(pWidgetContext, "error");
+ gtk_style_context_remove_class(pWidgetContext, "warning");
+#endif
+
+ switch (eType)
+ {
+ case weld::EntryMessageType::Normal:
+ break;
+ case weld::EntryMessageType::Warning:
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_add_css_class(pWidget, "warning");
+#else
+ gtk_style_context_add_class(pWidgetContext, "warning");
+#endif
+ break;
+ case weld::EntryMessageType::Error:
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_add_css_class(pWidget, "error");
+#else
+ gtk_style_context_add_class(pWidgetContext, "error");
+#endif
+ break;
+ }
+ }
+
+ void set_entry_message_type(GtkEntry* pEntry, weld::EntryMessageType eType)
+ {
+ set_widget_css_message_type(GTK_WIDGET(pEntry), eType);
+ switch (eType)
+ {
+ case weld::EntryMessageType::Normal:
+ gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, nullptr);
+ break;
+ case weld::EntryMessageType::Warning:
+ gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning");
+ break;
+ case weld::EntryMessageType::Error:
+ gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-error");
+ break;
+ }
+ }
+}
+
+namespace
+{
+
+class GtkInstanceEditable : public GtkInstanceWidget, public virtual weld::Entry
+{
+protected:
+ GtkEditable* m_pEditable;
+ GtkWidget* m_pDelegate;
+ WidgetFont m_aCustomFont;
+private:
+ gulong m_nChangedSignalId;
+ gulong m_nInsertTextSignalId;
+ gulong m_nCursorPosSignalId;
+ gulong m_nSelectionPosSignalId;
+ gulong m_nActivateSignalId;
+
+ static void signalChanged(GtkEditable*, gpointer widget)
+ {
+ GtkInstanceEditable* pThis = static_cast<GtkInstanceEditable*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_changed();
+ }
+
+ static void signalInsertText(GtkEditable* pEditable, const gchar* pNewText, gint nNewTextLength,
+ gint* position, gpointer widget)
+ {
+ GtkInstanceEditable* pThis = static_cast<GtkInstanceEditable*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_insert_text(pEditable, pNewText, nNewTextLength, position);
+ }
+
+ void signal_insert_text(GtkEditable* pEditable, const gchar* pNewText, gint nNewTextLength, gint* position)
+ {
+ if (!m_aInsertTextHdl.IsSet())
+ return;
+ OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
+ const bool bContinue = m_aInsertTextHdl.Call(sText);
+ if (bContinue && !sText.isEmpty())
+ {
+ OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
+ g_signal_handlers_block_by_func(pEditable, reinterpret_cast<gpointer>(signalInsertText), this);
+ gtk_editable_insert_text(pEditable, sFinalText.getStr(), sFinalText.getLength(), position);
+ g_signal_handlers_unblock_by_func(pEditable, reinterpret_cast<gpointer>(signalInsertText), this);
+ }
+ g_signal_stop_emission_by_name(pEditable, "insert-text");
+ }
+
+ static void signalCursorPosition(void*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceEditable* pThis = static_cast<GtkInstanceEditable*>(widget);
+ pThis->signal_cursor_position();
+ }
+
+ static void signalActivate(void*, gpointer widget)
+ {
+ GtkInstanceEditable* pThis = static_cast<GtkInstanceEditable*>(widget);
+ pThis->signal_activate();
+ }
+
+ virtual void ensureMouseEventWidget() override
+ {
+ // The GtkEntry is sufficient to get mouse events without an intermediate GtkEventBox
+ if (!m_pMouseEventBox)
+ m_pMouseEventBox = m_pDelegate;
+ }
+
+protected:
+
+ virtual void signal_activate()
+ {
+ if (m_aActivateHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ if (m_aActivateHdl.Call(*this))
+ g_signal_stop_emission_by_name(m_pDelegate, "activate");
+ }
+ }
+
+ PangoAttrList* get_attributes()
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_text_get_attributes(GTK_TEXT(m_pDelegate));
+#else
+ return gtk_entry_get_attributes(GTK_ENTRY(m_pDelegate));
+#endif
+ }
+
+ void set_attributes(PangoAttrList* pAttrs)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_text_set_attributes(GTK_TEXT(m_pDelegate), pAttrs);
+#else
+ gtk_entry_set_attributes(GTK_ENTRY(m_pDelegate), pAttrs);
+#endif
+ }
+
+public:
+ GtkInstanceEditable(GtkWidget* pWidget, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(pWidget, pBuilder, bTakeOwnership)
+ , m_pEditable(GTK_EDITABLE(pWidget))
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_pDelegate(GTK_WIDGET(gtk_editable_get_delegate(m_pEditable)))
+#else
+ , m_pDelegate(pWidget)
+#endif
+ , m_aCustomFont(m_pWidget)
+ , m_nChangedSignalId(g_signal_connect(m_pEditable, "changed", G_CALLBACK(signalChanged), this))
+ , m_nInsertTextSignalId(g_signal_connect(m_pEditable, "insert-text", G_CALLBACK(signalInsertText), this))
+ , m_nCursorPosSignalId(g_signal_connect(m_pEditable, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
+ , m_nSelectionPosSignalId(g_signal_connect(m_pEditable, "notify::selection-bound", G_CALLBACK(signalCursorPosition), this))
+ , m_nActivateSignalId(g_signal_connect(m_pDelegate, "activate", G_CALLBACK(signalActivate), this))
+ {
+ }
+
+ virtual void set_text(const OUString& rText) override
+ {
+ disable_notify_events();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+#else
+ gtk_entry_set_text(GTK_ENTRY(m_pDelegate), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+#endif
+ enable_notify_events();
+ }
+
+ virtual OUString get_text() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ const gchar* pText = gtk_editable_get_text(m_pEditable);
+#else
+ const gchar* pText = gtk_entry_get_text(GTK_ENTRY(m_pDelegate));
+#endif
+ OUString sRet(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ return sRet;
+ }
+
+ virtual void set_width_chars(int nChars) override
+ {
+ disable_notify_events();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_editable_set_width_chars(m_pEditable, nChars);
+ gtk_editable_set_max_width_chars(m_pEditable, nChars);
+#else
+ gtk_entry_set_width_chars(GTK_ENTRY(m_pDelegate), nChars);
+ gtk_entry_set_max_width_chars(GTK_ENTRY(m_pDelegate), nChars);
+#endif
+ enable_notify_events();
+ }
+
+ virtual int get_width_chars() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_editable_get_width_chars(m_pEditable);
+#else
+ return gtk_entry_get_width_chars(GTK_ENTRY(m_pDelegate));
+#endif
+ }
+
+ virtual void set_max_length(int nChars) override
+ {
+ disable_notify_events();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_text_set_max_length(GTK_TEXT(m_pDelegate), nChars);
+#else
+ gtk_entry_set_max_length(GTK_ENTRY(m_pDelegate), nChars);
+#endif
+ enable_notify_events();
+ }
+
+ virtual void select_region(int nStartPos, int nEndPos) override
+ {
+ disable_notify_events();
+ gtk_editable_select_region(m_pEditable, nStartPos, nEndPos);
+ enable_notify_events();
+ }
+
+ bool get_selection_bounds(int& rStartPos, int& rEndPos) override
+ {
+ return gtk_editable_get_selection_bounds(m_pEditable, &rStartPos, &rEndPos);
+ }
+
+ virtual void replace_selection(const OUString& rText) override
+ {
+ disable_notify_events();
+ gtk_editable_delete_selection(m_pEditable);
+ OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gint position = gtk_editable_get_position(m_pEditable);
+ gtk_editable_insert_text(m_pEditable, sText.getStr(), sText.getLength(),
+ &position);
+ enable_notify_events();
+ }
+
+ virtual void set_position(int nCursorPos) override
+ {
+ disable_notify_events();
+ gtk_editable_set_position(m_pEditable, nCursorPos);
+ enable_notify_events();
+ }
+
+ virtual int get_position() const override
+ {
+ return gtk_editable_get_position(m_pEditable);
+ }
+
+ virtual void set_editable(bool bEditable) override
+ {
+ gtk_editable_set_editable(m_pEditable, bEditable);
+ }
+
+ virtual bool get_editable() const override
+ {
+ return gtk_editable_get_editable(m_pEditable);
+ }
+
+ virtual void set_overwrite_mode(bool bOn) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_text_set_overwrite_mode(GTK_TEXT(m_pDelegate), bOn);
+#else
+ gtk_entry_set_overwrite_mode(GTK_ENTRY(m_pDelegate), bOn);
+#endif
+ }
+
+ virtual bool get_overwrite_mode() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_text_get_overwrite_mode(GTK_TEXT(m_pDelegate));
+#else
+ return gtk_entry_get_overwrite_mode(GTK_ENTRY(m_pDelegate));
+#endif
+ }
+
+ virtual void set_message_type(weld::EntryMessageType eType) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (!GTK_IS_ENTRY(m_pDelegate))
+ {
+ ::set_widget_css_message_type(m_pDelegate, eType);
+ return;
+ }
+#endif
+ ::set_entry_message_type(GTK_ENTRY(m_pDelegate), eType);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pDelegate, m_nActivateSignalId);
+ g_signal_handler_block(m_pEditable, m_nSelectionPosSignalId);
+ g_signal_handler_block(m_pEditable, m_nCursorPosSignalId);
+ g_signal_handler_block(m_pEditable, m_nInsertTextSignalId);
+ g_signal_handler_block(m_pEditable, m_nChangedSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pEditable, m_nChangedSignalId);
+ g_signal_handler_unblock(m_pEditable, m_nInsertTextSignalId);
+ g_signal_handler_unblock(m_pEditable, m_nCursorPosSignalId);
+ g_signal_handler_unblock(m_pEditable, m_nSelectionPosSignalId);
+ g_signal_handler_unblock(m_pDelegate, m_nActivateSignalId);
+ }
+
+ virtual vcl::Font get_font() override
+ {
+ if (const vcl::Font* pFont = m_aCustomFont.get_custom_font())
+ return *pFont;
+ return GtkInstanceWidget::get_font();
+ }
+
+ void set_font_color(const Color& rColor) override
+ {
+ PangoAttrList* pOrigList = get_attributes();
+ if (rColor == COL_AUTO && !pOrigList) // nothing to do
+ return;
+
+ PangoAttrType aFilterAttrs[] = {PANGO_ATTR_FOREGROUND, PANGO_ATTR_INVALID};
+
+ PangoAttrList* pAttrs = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
+ PangoAttrList* pRemovedAttrs = pOrigList ? pango_attr_list_filter(pAttrs, filter_pango_attrs, &aFilterAttrs) : nullptr;
+
+ if (rColor != COL_AUTO)
+ pango_attr_list_insert(pAttrs, pango_attr_foreground_new(rColor.GetRed()/255.0, rColor.GetGreen()/255.0, rColor.GetBlue()/255.0));
+
+ set_attributes(pAttrs);
+ pango_attr_list_unref(pAttrs);
+ pango_attr_list_unref(pRemovedAttrs);
+ }
+
+ void fire_signal_changed()
+ {
+ signal_changed();
+ }
+
+ virtual void cut_clipboard() override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_activate_action(m_pDelegate, "cut.clipboard", nullptr);
+#else
+ gtk_editable_cut_clipboard(m_pEditable);
+#endif
+ }
+
+ virtual void copy_clipboard() override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_activate_action(m_pDelegate, "copy.clipboard", nullptr);
+#else
+ gtk_editable_copy_clipboard(m_pEditable);
+#endif
+ }
+
+ virtual void paste_clipboard() override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_activate_action(m_pDelegate, "paste.clipboard", nullptr);
+#else
+ gtk_editable_paste_clipboard(m_pEditable);
+#endif
+ }
+
+ virtual void set_placeholder_text(const OUString& rText) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_text_set_placeholder_text(GTK_TEXT(m_pDelegate), rText.toUtf8().getStr());
+#else
+ gtk_entry_set_placeholder_text(GTK_ENTRY(m_pDelegate), rText.toUtf8().getStr());
+#endif
+ }
+
+ virtual void grab_focus() override
+ {
+ if (has_focus())
+ return;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_text_grab_focus_without_selecting(GTK_TEXT(m_pDelegate));
+#else
+ gtk_entry_grab_focus_without_selecting(GTK_ENTRY(m_pDelegate));
+#endif
+ }
+
+ virtual void set_alignment(TxtAlign eXAlign) override
+ {
+ gfloat xalign = 0;
+ switch (eXAlign)
+ {
+ case TxtAlign::Left:
+ xalign = 0.0;
+ break;
+ case TxtAlign::Center:
+ xalign = 0.5;
+ break;
+ case TxtAlign::Right:
+ xalign = 1.0;
+ break;
+ }
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_editable_set_alignment(m_pEditable, xalign);
+#else
+ gtk_entry_set_alignment(GTK_ENTRY(m_pDelegate), xalign);
+#endif
+ }
+
+ virtual ~GtkInstanceEditable() override
+ {
+ g_signal_handler_disconnect(m_pDelegate, m_nActivateSignalId);
+ g_signal_handler_disconnect(m_pEditable, m_nSelectionPosSignalId);
+ g_signal_handler_disconnect(m_pEditable, m_nCursorPosSignalId);
+ g_signal_handler_disconnect(m_pEditable, m_nInsertTextSignalId);
+ g_signal_handler_disconnect(m_pEditable, m_nChangedSignalId);
+ }
+};
+
+class GtkInstanceEntry : public GtkInstanceEditable
+{
+private:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkEntry* m_pEntry;
+ GtkOverlay* m_pPlaceHolderReplacement;
+ GtkLabel* m_pPlaceHolderLabel;
+ gulong m_nEntryFocusInSignalId;
+ gulong m_nEntryFocusOutSignalId;
+ gulong m_nEntryTextLengthSignalId;
+ gulong m_nEntryScrollOffsetSignalId;
+ guint m_nUpdatePlaceholderReplacementIdle;
+
+ static gboolean do_update_placeholder_replacement(gpointer widget)
+ {
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ pThis->update_placeholder_replacement();
+ return false;
+ }
+
+ void update_placeholder_replacement()
+ {
+ m_nUpdatePlaceholderReplacementIdle = 0;
+
+ const char* placeholder_text = gtk_entry_get_placeholder_text(m_pEntry);
+ const bool bShow = placeholder_text && !gtk_entry_get_text_length(m_pEntry) &&
+ gtk_widget_has_focus(GTK_WIDGET(m_pEntry));
+ if (bShow)
+ {
+ GdkRectangle text_area;
+ gtk_entry_get_text_area(m_pEntry, &text_area);
+ gint x;
+ gtk_entry_get_layout_offsets(m_pEntry, &x, nullptr);
+ gtk_widget_set_margin_start(GTK_WIDGET(m_pPlaceHolderLabel), x);
+ gtk_widget_set_margin_end(GTK_WIDGET(m_pPlaceHolderLabel), x);
+ gtk_label_set_text(m_pPlaceHolderLabel, placeholder_text);
+ gtk_widget_show(GTK_WIDGET(m_pPlaceHolderLabel));
+ }
+ else
+ gtk_widget_hide(GTK_WIDGET(m_pPlaceHolderLabel));
+ }
+
+ void launch_update_placeholder_replacement()
+ {
+ // do it in the next event cycle so the GtkEntry has done its layout
+ // and gtk_entry_get_layout_offsets returns the right results
+ if (m_nUpdatePlaceholderReplacementIdle)
+ return;
+ // G_PRIORITY_LOW so gtk's idles are run before this
+ m_nUpdatePlaceholderReplacementIdle = g_idle_add_full(G_PRIORITY_LOW, do_update_placeholder_replacement, this, nullptr);
+ }
+
+ static gboolean signalEntryFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ pThis->launch_update_placeholder_replacement();
+ return false;
+ }
+
+ static gboolean signalEntryFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ pThis->launch_update_placeholder_replacement();
+ return false;
+ }
+
+ static void signalEntryTextLength(void*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ pThis->launch_update_placeholder_replacement();
+ }
+
+ static void signalEntryScrollOffset(void*, GParamSpec*, gpointer widget)
+ {
+ // this property affects the x-position of the text area
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ pThis->launch_update_placeholder_replacement();
+ }
+
+#endif
+
+public:
+ GtkInstanceEntry(GtkEntry* pEntry, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceEditable(GTK_WIDGET(pEntry), pBuilder, bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_pEntry(pEntry)
+ , m_pPlaceHolderReplacement(nullptr)
+ , m_pPlaceHolderLabel(nullptr)
+ , m_nEntryFocusInSignalId(0)
+ , m_nEntryFocusOutSignalId(0)
+ , m_nEntryTextLengthSignalId(0)
+ , m_nEntryScrollOffsetSignalId(0)
+ , m_nUpdatePlaceholderReplacementIdle(0)
+#endif
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // tdf#150810 fake getting placeholders visible even when GtkEntry has focus in gtk3.
+ // In gtk4 this works out of the box, for gtk3 fake it by having a GtkLabel in an
+ // overlay and show that label if the placeholder would be shown if there was
+ // no focus
+ const char* pPlaceHolderText = gtk_entry_get_placeholder_text(m_pEntry);
+ if (pPlaceHolderText ? strlen(pPlaceHolderText) : 0)
+ {
+ m_pPlaceHolderReplacement = GTK_OVERLAY(gtk_overlay_new());
+ m_pPlaceHolderLabel = GTK_LABEL(gtk_label_new(nullptr));
+
+ GtkStyleContext *pStyleContext = gtk_widget_get_style_context(GTK_WIDGET(m_pEntry));
+ GdkRGBA fg = { 0.5, 0.5, 0.5, 0.0 };
+ gtk_style_context_lookup_color(pStyleContext, "placeholder_text_color", &fg);
+
+ auto red = std::clamp(fg.red * 65535 + 0.5, 0.0, 65535.0);
+ auto green = std::clamp(fg.green * 65535 + 0.5, 0.0, 65535.0);
+ auto blue = std::clamp(fg.blue * 65535 + 0.5, 0.0, 65535.0);
+
+ PangoAttribute *pAttr = pango_attr_foreground_new(red, green, blue);
+ pAttr->start_index = 0;
+ pAttr->end_index = G_MAXINT;
+ PangoAttrList* pAttrList = pango_attr_list_new();
+ pango_attr_list_insert(pAttrList, pAttr);
+ gtk_label_set_attributes(m_pPlaceHolderLabel, pAttrList);
+ pango_attr_list_unref(pAttrList);
+
+ // The GtkEntry will have the placeholder as the text to analyze here, assumes there is no initial text, just placeholder
+ const bool bRTL = PANGO_DIRECTION_RTL == pango_context_get_base_dir(pango_layout_get_context(gtk_entry_get_layout(m_pEntry)));
+ SAL_WARN_IF(gtk_entry_get_text_length(m_pEntry), "vcl.gtk", "don't have a placeholder set, but also initial text");
+ gtk_label_set_xalign(m_pPlaceHolderLabel, bRTL ? 1.0 : 0.0);
+
+ gtk_overlay_add_overlay(m_pPlaceHolderReplacement, GTK_WIDGET(m_pPlaceHolderLabel));
+ insertAsParent(GTK_WIDGET(m_pEntry), GTK_WIDGET(m_pPlaceHolderReplacement));
+ m_nEntryFocusInSignalId = g_signal_connect_after(m_pEntry, "focus-in-event", G_CALLBACK(signalEntryFocusIn), this);
+ m_nEntryFocusOutSignalId = g_signal_connect_after(m_pEntry, "focus-out-event", G_CALLBACK(signalEntryFocusOut), this);
+ m_nEntryTextLengthSignalId = g_signal_connect(m_pEntry, "notify::text-length", G_CALLBACK(signalEntryTextLength), this);
+ m_nEntryScrollOffsetSignalId = g_signal_connect(m_pEntry, "notify::scroll-offset", G_CALLBACK(signalEntryScrollOffset), this);
+ }
+#endif
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_aCustomFont.use_custom_font(&rFont, u"entry");
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+
+ virtual void show() override
+ {
+ GtkInstanceEditable::show();
+ if (m_pPlaceHolderReplacement)
+ gtk_widget_show(GTK_WIDGET(m_pPlaceHolderReplacement));
+ }
+
+ virtual void hide() override
+ {
+ if (m_pPlaceHolderReplacement)
+ gtk_widget_hide(GTK_WIDGET(m_pPlaceHolderReplacement));
+ GtkInstanceEditable::hide();
+ }
+
+ virtual ~GtkInstanceEntry() override
+ {
+ if (m_nUpdatePlaceholderReplacementIdle)
+ g_source_remove(m_nUpdatePlaceholderReplacementIdle);
+ if (m_nEntryFocusInSignalId)
+ g_signal_handler_disconnect(m_pEntry, m_nEntryFocusInSignalId);
+ if (m_nEntryFocusOutSignalId)
+ g_signal_handler_disconnect(m_pEntry, m_nEntryFocusOutSignalId);
+ if (m_nEntryTextLengthSignalId)
+ g_signal_handler_disconnect(m_pEntry, m_nEntryTextLengthSignalId);
+ if (m_nEntryScrollOffsetSignalId)
+ g_signal_handler_disconnect(m_pEntry, m_nEntryScrollOffsetSignalId);
+ }
+#endif
+};
+
+}
+
+namespace
+{
+
+ struct Search
+ {
+ OString str;
+ int index;
+ int col;
+ Search(std::u16string_view rText, int nCol)
+ : str(OUStringToOString(rText, RTL_TEXTENCODING_UTF8))
+ , index(-1)
+ , col(nCol)
+ {
+ }
+ };
+
+ gboolean foreach_find(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, gpointer data)
+ {
+ Search* search = static_cast<Search*>(data);
+ gchar *pStr = nullptr;
+ gtk_tree_model_get(model, iter, search->col, &pStr, -1);
+ bool found = strcmp(pStr, search->str.getStr()) == 0;
+ if (found)
+ {
+ gint depth;
+ gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
+ search->index = indices[depth-1];
+ }
+ g_free(pStr);
+ return found;
+ }
+
+ void insert_row(GtkListStore* pListStore, GtkTreeIter& iter, int pos, const OUString* pId, std::u16string_view rText, const OUString* pIconName, const VirtualDevice* pDevice)
+ {
+ if (!pIconName && !pDevice)
+ {
+ gtk_list_store_insert_with_values(pListStore, &iter, pos,
+ 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
+ 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
+ -1);
+ }
+ else
+ {
+ if (pIconName)
+ {
+ GdkPixbuf* pixbuf = getPixbuf(*pIconName);
+
+ gtk_list_store_insert_with_values(pListStore, &iter, pos,
+ 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
+ 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
+ 2, pixbuf,
+ -1);
+
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+ else
+ {
+ cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
+
+ Size aSize(pDevice->GetOutputSizePixel());
+ cairo_surface_t* target = cairo_surface_create_similar(surface,
+ cairo_surface_get_content(surface),
+ aSize.Width(),
+ aSize.Height());
+
+ cairo_t* cr = cairo_create(target);
+ cairo_set_source_surface(cr, surface, 0, 0);
+ cairo_paint(cr);
+ cairo_destroy(cr);
+
+ gtk_list_store_insert_with_values(pListStore, &iter, pos,
+ 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
+ 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
+ 3, target,
+ -1);
+ cairo_surface_destroy(target);
+ }
+ }
+ }
+}
+
+namespace
+{
+ gint default_sort_func(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b, gpointer data)
+ {
+ comphelper::string::NaturalStringSorter* pSorter = static_cast<comphelper::string::NaturalStringSorter*>(data);
+ gchar* pName1;
+ gchar* pName2;
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(pModel);
+ gint sort_column_id(0);
+ gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr);
+ gtk_tree_model_get(pModel, a, sort_column_id, &pName1, -1);
+ gtk_tree_model_get(pModel, b, sort_column_id, &pName2, -1);
+ gint ret = pSorter->compare(OUString(pName1, pName1 ? strlen(pName1) : 0, RTL_TEXTENCODING_UTF8),
+ OUString(pName2, pName2 ? strlen(pName2) : 0, RTL_TEXTENCODING_UTF8));
+ g_free(pName1);
+ g_free(pName2);
+ return ret;
+ }
+
+ int starts_with(GtkTreeModel* pTreeModel, const OUString& rStr, int col, int nStartRow, bool bCaseSensitive)
+ {
+ GtkTreeIter iter;
+ if (!gtk_tree_model_iter_nth_child(pTreeModel, &iter, nullptr, nStartRow))
+ return -1;
+
+ const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
+ int nRet = nStartRow;
+ do
+ {
+ gchar* pStr;
+ gtk_tree_model_get(pTreeModel, &iter, col, &pStr, -1);
+ OUString aStr(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pStr);
+ const bool bMatch = !bCaseSensitive ? rI18nHelper.MatchString(rStr, aStr) : aStr.startsWith(rStr);
+ if (bMatch)
+ return nRet;
+ ++nRet;
+ } while (gtk_tree_model_iter_next(pTreeModel, &iter));
+
+ return -1;
+ }
+
+ struct GtkInstanceTreeIter : public weld::TreeIter
+ {
+ GtkInstanceTreeIter(const GtkInstanceTreeIter* pOrig)
+ {
+ if (pOrig)
+ iter = pOrig->iter;
+ else
+ memset(&iter, 0, sizeof(iter));
+ }
+ GtkInstanceTreeIter(const GtkTreeIter& rOrig)
+ {
+ memcpy(&iter, &rOrig, sizeof(iter));
+ }
+ virtual bool equal(const TreeIter& rOther) const override
+ {
+ return memcmp(&iter, &static_cast<const GtkInstanceTreeIter&>(rOther).iter, sizeof(GtkTreeIter)) == 0;
+ }
+ GtkTreeIter iter;
+ };
+
+ class GtkInstanceTreeView;
+
+}
+
+static GtkInstanceTreeView* g_DragSource;
+
+namespace {
+
+struct CompareGtkTreePath
+{
+ bool operator()(const GtkTreePath* lhs, const GtkTreePath* rhs) const
+ {
+ return gtk_tree_path_compare(lhs, rhs) < 0;
+ }
+};
+
+int get_height_row(GtkTreeView* pTreeView, GList* pColumns)
+{
+ gint nMaxRowHeight = 0;
+ for (GList* pEntry = g_list_first(pColumns); pEntry; pEntry = g_list_next(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ gint nRowHeight;
+ gtk_cell_renderer_get_preferred_height(pCellRenderer, GTK_WIDGET(pTreeView), nullptr, &nRowHeight);
+ nMaxRowHeight = std::max(nMaxRowHeight, nRowHeight);
+ }
+ g_list_free(pRenderers);
+ }
+ return nMaxRowHeight;
+}
+
+int get_height_row_separator(GtkTreeView* pTreeView)
+{
+ // gtk4: _TREE_VIEW_VERTICAL_SEPARATOR define in gtk/gtktreeview.c
+ gint nVerticalSeparator = 2;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_style_get(GTK_WIDGET(pTreeView), "vertical-separator", &nVerticalSeparator, nullptr);
+#else
+ (void)pTreeView;
+#endif
+ return nVerticalSeparator;
+}
+
+int get_height_rows(GtkTreeView* pTreeView, GList* pColumns, int nRows)
+{
+ gint nMaxRowHeight = get_height_row(pTreeView, pColumns);
+ gint nVerticalSeparator = get_height_row_separator(pTreeView);
+ return (nMaxRowHeight * nRows) + (nVerticalSeparator * nRows) / 2;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+int get_height_rows(int nRowHeight, int nSeparatorHeight, int nRows)
+{
+ return (nRowHeight * nRows) + (nSeparatorHeight * (nRows + 1));
+}
+#endif
+
+tools::Rectangle get_row_area(GtkTreeView* pTreeView, GList* pColumns, GtkTreePath* pPath)
+{
+ tools::Rectangle aRet;
+
+ GdkRectangle aRect;
+ for (GList* pEntry = g_list_last(pColumns); pEntry; pEntry = g_list_previous(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ gtk_tree_view_get_cell_area(pTreeView, pPath, pColumn, &aRect);
+ aRet.Union(tools::Rectangle(aRect.x, aRect.y, aRect.x + aRect.width, aRect.y + aRect.height));
+ }
+
+ return aRet;
+}
+
+struct GtkTreeRowReferenceDeleter
+{
+ void operator()(GtkTreeRowReference* p) const
+ {
+ gtk_tree_row_reference_free(p);
+ }
+};
+
+bool separator_function(const GtkTreePath* path, const std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>>& rSeparatorRows)
+{
+ bool bFound = false;
+ for (auto& a : rSeparatorRows)
+ {
+ GtkTreePath* seppath = gtk_tree_row_reference_get_path(a.get());
+ if (seppath)
+ {
+ bFound = gtk_tree_path_compare(path, seppath) == 0;
+ gtk_tree_path_free(seppath);
+ }
+ if (bFound)
+ break;
+ }
+ return bFound;
+}
+
+void tree_store_set(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, ...)
+{
+ va_list args;
+
+ va_start(args, pIter);
+ gtk_tree_store_set_valist(GTK_TREE_STORE(pTreeModel), pIter, args);
+ va_end(args);
+}
+
+void list_store_set(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, ...)
+{
+ va_list args;
+
+ va_start(args, pIter);
+ gtk_list_store_set_valist(GTK_LIST_STORE(pTreeModel), pIter, args);
+ va_end(args);
+}
+
+void tree_store_insert_with_values(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPos,
+ gint nTextCol, const gchar* pText,
+ gint nIdCol, const gchar* pId)
+{
+ gtk_tree_store_insert_with_values(GTK_TREE_STORE(pTreeModel), pIter, pParent, nPos,
+ nTextCol, pText, nIdCol, pId, -1);
+}
+
+void list_store_insert_with_values(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPos,
+ gint nTextCol, const gchar* pText,
+ gint nIdCol, const gchar* pId)
+{
+ assert(!pParent); (void)pParent;
+ gtk_list_store_insert_with_values(GTK_LIST_STORE(pTreeModel), pIter, nPos,
+ nTextCol, pText, nIdCol, pId, -1);
+}
+
+void tree_store_prepend(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent)
+{
+ gtk_tree_store_prepend(GTK_TREE_STORE(pTreeModel), pIter, pParent);
+}
+
+void list_store_prepend(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent)
+{
+ assert(!pParent); (void)pParent;
+ gtk_list_store_prepend(GTK_LIST_STORE(pTreeModel), pIter);
+}
+
+void tree_store_insert(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPosition)
+{
+ gtk_tree_store_insert(GTK_TREE_STORE(pTreeModel), pIter, pParent, nPosition);
+}
+
+void list_store_insert(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPosition)
+{
+ assert(!pParent); (void)pParent;
+ gtk_list_store_insert(GTK_LIST_STORE(pTreeModel), pIter, nPosition);
+}
+
+void tree_store_clear(GtkTreeModel* pTreeModel)
+{
+ gtk_tree_store_clear(GTK_TREE_STORE(pTreeModel));
+}
+
+void list_store_clear(GtkTreeModel* pTreeModel)
+{
+ gtk_list_store_clear(GTK_LIST_STORE(pTreeModel));
+}
+
+bool tree_store_remove(GtkTreeModel* pTreeModel, GtkTreeIter *pIter)
+{
+ return gtk_tree_store_remove(GTK_TREE_STORE(pTreeModel), pIter);
+}
+
+bool list_store_remove(GtkTreeModel* pTreeModel, GtkTreeIter *pIter)
+{
+ return gtk_list_store_remove(GTK_LIST_STORE(pTreeModel), pIter);
+}
+
+void tree_store_swap(GtkTreeModel* pTreeModel, GtkTreeIter* pIter1, GtkTreeIter* pIter2)
+{
+ gtk_tree_store_swap(GTK_TREE_STORE(pTreeModel), pIter1, pIter2);
+}
+
+void list_store_swap(GtkTreeModel* pTreeModel, GtkTreeIter* pIter1, GtkTreeIter* pIter2)
+{
+ gtk_list_store_swap(GTK_LIST_STORE(pTreeModel), pIter1, pIter2);
+}
+
+void tree_store_set_value(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gint nColumn, GValue* pValue)
+{
+ gtk_tree_store_set_value(GTK_TREE_STORE(pTreeModel), pIter, nColumn, pValue);
+}
+
+void list_store_set_value(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gint nColumn, GValue* pValue)
+{
+ gtk_list_store_set_value(GTK_LIST_STORE(pTreeModel), pIter, nColumn, pValue);
+}
+
+int promote_arg(bool bArg)
+{
+ return static_cast<int>(bArg);
+}
+
+class GtkInstanceTreeView : public GtkInstanceWidget, public virtual weld::TreeView
+{
+private:
+ GtkTreeView* m_pTreeView;
+ GtkTreeModel* m_pTreeModel;
+
+ typedef void(*setterFnc)(GtkTreeModel*, GtkTreeIter*, ...);
+ setterFnc m_Setter;
+
+ typedef void(*insertWithValuesFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*, gint, gint, const gchar*, gint, const gchar*);
+ insertWithValuesFnc m_InsertWithValues;
+
+ typedef void(*insertFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*, gint);
+ insertFnc m_Insert;
+
+ typedef void(*prependFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*);
+ prependFnc m_Prepend;
+
+ typedef void(*clearFnc)(GtkTreeModel*);
+ clearFnc m_Clear;
+
+ typedef bool(*removeFnc)(GtkTreeModel*, GtkTreeIter*);
+ removeFnc m_Remove;
+
+ typedef void(*swapFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*);
+ swapFnc m_Swap;
+
+ typedef void(*setValueFnc)(GtkTreeModel*, GtkTreeIter*, gint, GValue*);
+ setValueFnc m_SetValue;
+
+ std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
+ GList *m_pColumns;
+ std::vector<gulong> m_aColumnSignalIds;
+ // map from toggle column to toggle visibility column
+ std::map<int, int> m_aToggleVisMap;
+ // map from toggle column to tristate column
+ std::map<int, int> m_aToggleTriStateMap;
+ // map from text column to text weight column
+ std::map<int, int> m_aWeightMap;
+ // map from text column to sensitive column
+ std::map<int, int> m_aSensitiveMap;
+ // map from text column to indent column
+ std::map<int, int> m_aIndentMap;
+ // map from text column to text align column
+ std::map<int, int> m_aAlignMap;
+ // currently expanding parent that logically, but not currently physically,
+ // contain placeholders
+ o3tl::sorted_vector<GtkTreePath*, CompareGtkTreePath> m_aExpandingPlaceHolderParents;
+ // which rows are separators (rare)
+ std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>> m_aSeparatorRows;
+ std::vector<GtkSortType> m_aSavedSortTypes;
+ std::vector<int> m_aSavedSortColumns;
+ bool m_bWorkAroundBadDragRegion;
+ bool m_bInDrag;
+ bool m_bChangedByMouse;
+ gint m_nTextCol;
+ gint m_nTextView;
+ gint m_nImageCol;
+ gint m_nExpanderToggleCol;
+ gint m_nExpanderImageCol;
+ gint m_nIdCol;
+ int m_nPendingVAdjustment;
+ gulong m_nChangedSignalId;
+ gulong m_nRowActivatedSignalId;
+ gulong m_nTestExpandRowSignalId;
+ gulong m_nTestCollapseRowSignalId;
+ gulong m_nVAdjustmentChangedSignalId;
+ gulong m_nRowDeletedSignalId;
+ gulong m_nRowInsertedSignalId;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nPopupMenuSignalId;
+ gulong m_nKeyPressSignalId;
+ gulong m_nCrossingSignalid;
+#endif
+ gulong m_nQueryTooltipSignalId;
+ GtkAdjustment* m_pVAdjustment;
+ ImplSVEvent* m_pChangeEvent;
+
+ DECL_LINK(async_signal_changed, void*, void);
+
+ void launch_signal_changed()
+ {
+ //tdf#117991 selection change is sent before the focus change, and focus change
+ //is what will cause a spinbutton that currently has the focus to set its contents
+ //as the spin button value. So any LibreOffice callbacks on
+ //signal-change would happen before the spinbutton value-change occurs.
+ //To avoid this, send the signal-change to LibreOffice to occur after focus-change
+ //has been processed
+ if (m_pChangeEvent)
+ Application::RemoveUserEvent(m_pChangeEvent);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkEvent *pEvent = gtk_get_current_event();
+ m_bChangedByMouse = pEvent && categorizeEvent(pEvent) == VclInputFlags::MOUSE;
+#else
+ //TODO maybe iterate over gtk_widget_observe_controllers looking for a motion controller
+#endif
+
+ m_pChangeEvent = Application::PostUserEvent(LINK(this, GtkInstanceTreeView, async_signal_changed));
+ }
+
+ static void signalChanged(GtkTreeView*, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ pThis->launch_signal_changed();
+ }
+
+ void handle_row_activated()
+ {
+ if (signal_row_activated())
+ return;
+ GtkInstanceTreeIter aIter(nullptr);
+ if (!get_cursor(&aIter))
+ return;
+ if (gtk_tree_model_iter_has_child(m_pTreeModel, &aIter.iter))
+ get_row_expanded(aIter) ? collapse_row(aIter) : expand_row(aIter);
+ }
+
+ static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->handle_row_activated();
+ }
+
+ virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
+ {
+ return m_aPopupMenuHdl.Call(rCEvt);
+ }
+
+ void insert_row(GtkTreeIter& iter, const GtkTreeIter* parent, int pos, const OUString* pId, const OUString* pText,
+ const OUString* pIconName, const VirtualDevice* pDevice)
+ {
+ m_InsertWithValues(m_pTreeModel, &iter, const_cast<GtkTreeIter*>(parent), pos,
+ m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
+ m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr());
+
+ if (pIconName)
+ {
+ GdkPixbuf* pixbuf = getPixbuf(*pIconName);
+ m_Setter(m_pTreeModel, &iter, m_nImageCol, pixbuf, -1);
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+ else if (pDevice)
+ {
+ cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
+
+ Size aSize(pDevice->GetOutputSizePixel());
+ cairo_surface_t* target = cairo_surface_create_similar(surface,
+ cairo_surface_get_content(surface),
+ aSize.Width(),
+ aSize.Height());
+
+ cairo_t* cr = cairo_create(target);
+ cairo_set_source_surface(cr, surface, 0, 0);
+ cairo_paint(cr);
+ cairo_destroy(cr);
+
+ m_Setter(m_pTreeModel, &iter, m_nImageCol, target, -1);
+ cairo_surface_destroy(target);
+ }
+ }
+
+ bool separator_function(const GtkTreePath* path)
+ {
+ return ::separator_function(path, m_aSeparatorRows);
+ }
+
+ static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter);
+ bool bRet = pThis->separator_function(path);
+ gtk_tree_path_free(path);
+ return bRet;
+ }
+
+ OUString get(const GtkTreeIter& iter, int col) const
+ {
+ gchar* pStr;
+ gtk_tree_model_get(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, &pStr, -1);
+ OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pStr);
+ return sRet;
+ }
+
+ OUString get(int pos, int col) const
+ {
+ OUString sRet;
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ sRet = get(iter, col);
+ return sRet;
+ }
+
+ gint get_int(const GtkTreeIter& iter, int col) const
+ {
+ gint nRet(-1);
+ gtk_tree_model_get(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, &nRet, -1);
+ return nRet;
+ }
+
+ gint get_int(int pos, int col) const
+ {
+ gint nRet(-1);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ nRet = get_int(iter, col);
+ gtk_tree_model_get(m_pTreeModel, &iter, col, &nRet, -1);
+ return nRet;
+ }
+
+ bool get_bool(const GtkTreeIter& iter, int col) const
+ {
+ gboolean bRet(false);
+ gtk_tree_model_get(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, &bRet, -1);
+ return bRet;
+ }
+
+ bool get_bool(int pos, int col) const
+ {
+ bool bRet(false);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ bRet = get_bool(iter, col);
+ return bRet;
+ }
+
+ void set_toggle(const GtkTreeIter& iter, TriState eState, int col)
+ {
+ if (col == -1)
+ col = m_nExpanderToggleCol;
+ else
+ col = to_internal_model(col);
+
+ if (eState == TRISTATE_INDET)
+ {
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter),
+ m_aToggleVisMap[col], promote_arg(true), // checkbuttons are invisible until toggled on or off
+ m_aToggleTriStateMap[col], promote_arg(true), // tristate on
+ -1);
+ }
+ else
+ {
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter),
+ m_aToggleVisMap[col], promote_arg(true), // checkbuttons are invisible until toggled on or off
+ m_aToggleTriStateMap[col], promote_arg(false), // tristate off
+ col, promote_arg(eState == TRISTATE_TRUE), // set toggle state
+ -1);
+ }
+ }
+
+ void set(const GtkTreeIter& iter, int col, std::u16string_view rText)
+ {
+ OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, aStr.getStr(), -1);
+ }
+
+ void set(int pos, int col, std::u16string_view rText)
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ set(iter, col, rText);
+ }
+
+ void set(const GtkTreeIter& iter, int col, bool bOn)
+ {
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, promote_arg(bOn), -1);
+ }
+
+ void set(int pos, int col, bool bOn)
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ set(iter, col, bOn);
+ }
+
+ void set(const GtkTreeIter& iter, int col, gint bInt)
+ {
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, bInt, -1);
+ }
+
+ void set(int pos, int col, gint bInt)
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ set(iter, col, bInt);
+ }
+
+ void set(const GtkTreeIter& iter, int col, double fValue)
+ {
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, fValue, -1);
+ }
+
+ void set(int pos, int col, double fValue)
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ set(iter, col, fValue);
+ }
+
+ static gboolean signalTestExpandRow(GtkTreeView*, GtkTreeIter* iter, GtkTreePath*, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ return !pThis->signal_test_expand_row(*iter);
+ }
+
+ static gboolean signalTestCollapseRow(GtkTreeView*, GtkTreeIter* iter, GtkTreePath*, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ return !pThis->signal_test_collapse_row(*iter);
+ }
+
+ bool child_is_placeholder(GtkInstanceTreeIter& rGtkIter) const
+ {
+ GtkTreePath* pPath = gtk_tree_model_get_path(m_pTreeModel, &rGtkIter.iter);
+ bool bExpanding = m_aExpandingPlaceHolderParents.count(pPath);
+ gtk_tree_path_free(pPath);
+ if (bExpanding)
+ return true;
+
+ bool bPlaceHolder = false;
+ GtkTreeIter tmp;
+ if (gtk_tree_model_iter_children(m_pTreeModel, &tmp, &rGtkIter.iter))
+ {
+ rGtkIter.iter = tmp;
+ if (get_text(rGtkIter, -1) == "<dummy>")
+ {
+ bPlaceHolder = true;
+ }
+ }
+ return bPlaceHolder;
+ }
+
+ bool signal_test_expand_row(GtkTreeIter& iter)
+ {
+ disable_notify_events();
+
+ // if there's a preexisting placeholder child, required to make this
+ // potentially expandable in the first place, now we remove it
+ GtkInstanceTreeIter aIter(iter);
+ GtkTreePath* pPlaceHolderPath = nullptr;
+ bool bPlaceHolder = child_is_placeholder(aIter);
+ if (bPlaceHolder)
+ {
+ m_Remove(m_pTreeModel, &aIter.iter);
+
+ pPlaceHolderPath = gtk_tree_model_get_path(m_pTreeModel, &iter);
+ m_aExpandingPlaceHolderParents.insert(pPlaceHolderPath);
+ }
+
+ aIter.iter = iter;
+ bool bRet = signal_expanding(aIter);
+
+ if (bPlaceHolder)
+ {
+ //expand disallowed, restore placeholder
+ if (!bRet)
+ {
+ GtkTreeIter subiter;
+ OUString sDummy("<dummy>");
+ insert_row(subiter, &iter, -1, nullptr, &sDummy, nullptr, nullptr);
+ }
+ m_aExpandingPlaceHolderParents.erase(pPlaceHolderPath);
+ gtk_tree_path_free(pPlaceHolderPath);
+ }
+
+ enable_notify_events();
+ return bRet;
+ }
+
+ bool signal_test_collapse_row(const GtkTreeIter& iter)
+ {
+ disable_notify_events();
+
+ GtkInstanceTreeIter aIter(iter);
+ bool bRet = signal_collapsing(aIter);
+
+ enable_notify_events();
+ return bRet;
+ }
+
+ static void signalCellToggled(GtkCellRendererToggle* pCell, const gchar *path, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ void* pData = g_object_get_data(G_OBJECT(pCell), "g-lo-CellIndex");
+ pThis->signal_cell_toggled(path, reinterpret_cast<sal_IntPtr>(pData));
+ }
+
+ void signal_cell_toggled(const gchar *path, int nCol)
+ {
+ GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
+
+ // additionally set the cursor into the row the toggled element is in
+ gtk_tree_view_set_cursor(m_pTreeView, tree_path, nullptr, false);
+
+ GtkTreeIter iter;
+ gtk_tree_model_get_iter(m_pTreeModel, &iter, tree_path);
+
+ gboolean bRet(false);
+ gtk_tree_model_get(m_pTreeModel, &iter, nCol, &bRet, -1);
+ bRet = !bRet;
+ m_Setter(m_pTreeModel, &iter, nCol, bRet, -1);
+
+ set(iter, m_aToggleTriStateMap[nCol], false);
+
+ signal_toggled(iter_col(GtkInstanceTreeIter(iter), to_external_model(nCol)));
+
+ gtk_tree_path_free(tree_path);
+ }
+
+ DECL_LINK(async_stop_cell_editing, void*, void);
+
+ static void signalCellEditingStarted(GtkCellRenderer*, GtkCellEditable*, const gchar *path, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ if (!pThis->signal_cell_editing_started(path))
+ Application::PostUserEvent(LINK(pThis, GtkInstanceTreeView, async_stop_cell_editing));
+ }
+
+ bool signal_cell_editing_started(const gchar *path)
+ {
+ GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
+
+ GtkInstanceTreeIter aGtkIter(nullptr);
+ gtk_tree_model_get_iter(m_pTreeModel, &aGtkIter.iter, tree_path);
+ gtk_tree_path_free(tree_path);
+
+ return signal_editing_started(aGtkIter);
+ }
+
+ static void signalCellEdited(GtkCellRendererText* pCell, const gchar *path, const gchar *pNewText, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ pThis->signal_cell_edited(pCell, path, pNewText);
+ }
+
+ static void restoreNonEditable(GObject* pCell)
+ {
+ if (g_object_get_data(pCell, "g-lo-RestoreNonEditable"))
+ {
+ g_object_set(pCell, "editable", false, "editable-set", false, nullptr);
+ g_object_set_data(pCell, "g-lo-RestoreNonEditable", reinterpret_cast<gpointer>(false));
+ }
+ }
+
+ void signal_cell_edited(GtkCellRendererText* pCell, const gchar *path, const gchar* pNewText)
+ {
+ GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
+
+ GtkInstanceTreeIter aGtkIter(nullptr);
+ gtk_tree_model_get_iter(m_pTreeModel, &aGtkIter.iter, tree_path);
+ gtk_tree_path_free(tree_path);
+
+ OUString sText(pNewText, pNewText ? strlen(pNewText) : 0, RTL_TEXTENCODING_UTF8);
+ if (signal_editing_done(iter_string(aGtkIter, sText)))
+ {
+ void* pData = g_object_get_data(G_OBJECT(pCell), "g-lo-CellIndex");
+ set(aGtkIter.iter, reinterpret_cast<sal_IntPtr>(pData), sText);
+ }
+
+ restoreNonEditable(G_OBJECT(pCell));
+ }
+
+ static void signalCellEditingCanceled(GtkCellRenderer* pCell, gpointer /*widget*/)
+ {
+ restoreNonEditable(G_OBJECT(pCell));
+ }
+
+ void signal_column_clicked(GtkTreeViewColumn* pClickedColumn)
+ {
+ int nIndex(0);
+ for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ if (pColumn == pClickedColumn)
+ {
+ TreeView::signal_column_clicked(nIndex);
+ break;
+ }
+ ++nIndex;
+ }
+ }
+
+ static void signalColumnClicked(GtkTreeViewColumn* pColumn, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ pThis->signal_column_clicked(pColumn);
+ }
+
+ static void signalVAdjustmentChanged(GtkAdjustment*, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ pThis->signal_visible_range_changed();
+ }
+
+ // The outside concept of a column maps to a gtk CellRenderer, rather than
+ // a TreeViewColumn. If the first TreeViewColumn has a leading Toggle Renderer
+ // and/or a leading Image Renderer, those are considered special expander
+ // columns and precede index 0 and can be accessed via outside index -1
+ int to_external_model(int modelcol) const
+ {
+ if (m_nExpanderToggleCol != -1)
+ --modelcol;
+ if (m_nExpanderImageCol != -1)
+ --modelcol;
+ return modelcol;
+ }
+
+ int to_internal_model(int modelcol) const
+ {
+ if (m_nExpanderToggleCol != -1)
+ ++modelcol;
+ if (m_nExpanderImageCol != -1)
+ ++modelcol;
+ return modelcol;
+ }
+
+ void set_column_editable(int nCol, bool bEditable)
+ {
+ nCol = to_internal_model(nCol);
+
+ for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
+ if (reinterpret_cast<sal_IntPtr>(pData) == nCol)
+ {
+ g_object_set(G_OBJECT(pCellRenderer), "editable", bEditable, "editable-set", true, nullptr);
+ break;
+ }
+ }
+ g_list_free(pRenderers);
+ }
+ }
+
+ static void signalRowDeleted(GtkTreeModel*, GtkTreePath*, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ pThis->signal_model_changed();
+ }
+
+ static void signalRowInserted(GtkTreeModel*, GtkTreePath*, GtkTreeIter*, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ pThis->signal_model_changed();
+ }
+
+ static gint sortFunc(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ return pThis->sort_func(pModel, a, b);
+ }
+
+ gint sort_func(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b)
+ {
+ if (m_aCustomSort)
+ return m_aCustomSort(GtkInstanceTreeIter(*a), GtkInstanceTreeIter(*b));
+ return default_sort_func(pModel, a, b, m_xSorter.get());
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool signal_key_press(GdkEventKey* pEvent)
+ {
+ if (pEvent->keyval != GDK_KEY_Left && pEvent->keyval != GDK_KEY_Right)
+ return false;
+
+ GtkInstanceTreeIter aIter(nullptr);
+ if (!get_cursor(&aIter))
+ return false;
+
+ bool bHasChild = gtk_tree_model_iter_has_child(m_pTreeModel, &aIter.iter);
+
+ if (pEvent->keyval == GDK_KEY_Right)
+ {
+ if (bHasChild && !get_row_expanded(aIter))
+ {
+ expand_row(aIter);
+ return true;
+ }
+ return false;
+ }
+
+ if (bHasChild && get_row_expanded(aIter))
+ {
+ collapse_row(aIter);
+ return true;
+ }
+
+ if (iter_parent(aIter))
+ {
+ unselect_all();
+ set_cursor(aIter);
+ select(aIter);
+ return true;
+ }
+
+ return false;
+ }
+
+ static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ return pThis->signal_key_press(pEvent);
+ }
+#endif
+
+ static gboolean signalQueryTooltip(GtkWidget* /*pGtkWidget*/, gint x, gint y,
+ gboolean keyboard_tip, GtkTooltip *tooltip,
+ gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ GtkTreeIter iter;
+ GtkTreeView *pTreeView = pThis->m_pTreeView;
+ GtkTreeModel *pModel = gtk_tree_view_get_model(pTreeView);
+ GtkTreePath *pPath = nullptr;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (!gtk_tree_view_get_tooltip_context(pTreeView, x, y, keyboard_tip, &pModel, &pPath, &iter))
+ return false;
+#else
+ if (!gtk_tree_view_get_tooltip_context(pTreeView, &x, &y, keyboard_tip, &pModel, &pPath, &iter))
+ return false;
+#endif
+ OUString aTooltip = pThis->signal_query_tooltip(GtkInstanceTreeIter(iter));
+ if (!aTooltip.isEmpty())
+ {
+ gtk_tooltip_set_text(tooltip, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
+ gtk_tree_view_set_tooltip_row(pTreeView, tooltip, pPath);
+ }
+ gtk_tree_path_free(pPath);
+ return !aTooltip.isEmpty();
+ }
+
+ void last_child(GtkTreeModel* pModel, GtkTreeIter* result, GtkTreeIter* pParent, int nChildren) const
+ {
+ gtk_tree_model_iter_nth_child(pModel, result, pParent, nChildren - 1);
+ nChildren = gtk_tree_model_iter_n_children(pModel, result);
+ if (nChildren)
+ {
+ GtkTreeIter newparent(*result);
+ last_child(pModel, result, &newparent, nChildren);
+ }
+ }
+
+ GtkTreePath* get_path_of_last_entry(GtkTreeModel *pModel)
+ {
+ GtkTreePath *lastpath;
+ // find the last entry in the model for comparison
+ int nChildren = gtk_tree_model_iter_n_children(pModel, nullptr);
+ if (!nChildren)
+ lastpath = gtk_tree_path_new_from_indices(0, -1);
+ else
+ {
+ GtkTreeIter iter;
+ last_child(pModel, &iter, nullptr, nChildren);
+ lastpath = gtk_tree_model_get_path(pModel, &iter);
+ }
+ return lastpath;
+ }
+
+ void set_font_color(const GtkTreeIter& iter, const Color& rColor)
+ {
+ if (rColor == COL_AUTO)
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), m_nIdCol + 1, nullptr, -1);
+ else
+ {
+ GdkRGBA aColor{rColor.GetRed()/255.0f, rColor.GetGreen()/255.0f, rColor.GetBlue()/255.0f, 0};
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), m_nIdCol + 1, &aColor, -1);
+ }
+ }
+
+ int get_expander_size() const
+ {
+ // gtk4: _TREE_VIEW_EXPANDER_SIZE define in gtk/gtktreeview.c
+ gint nExpanderSize = 16;
+ // gtk4: _TREE_VIEW_HORIZONTAL_SEPARATOR define in gtk/gtktreeview.c
+ gint nHorizontalSeparator = 4;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_style_get(GTK_WIDGET(m_pTreeView),
+ "expander-size", &nExpanderSize,
+ "horizontal-separator", &nHorizontalSeparator,
+ nullptr);
+#endif
+
+ return nExpanderSize + (nHorizontalSeparator/ 2);
+ }
+
+ void real_vadjustment_set_value(int value)
+ {
+ disable_notify_events();
+ gtk_adjustment_set_value(m_pVAdjustment, value);
+ enable_notify_events();
+ }
+
+ static gboolean setAdjustmentCallback(GtkWidget*, GdkFrameClock*, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ if (pThis->m_nPendingVAdjustment != -1)
+ {
+ pThis->real_vadjustment_set_value(pThis->m_nPendingVAdjustment);
+ pThis->m_nPendingVAdjustment = -1;
+ }
+ return false;
+ }
+
+ bool iter_next(weld::TreeIter& rIter, bool bOnlyExpanded) const
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ GtkTreeIter tmp;
+ GtkTreeIter iter = rGtkIter.iter;
+
+ bool ret = gtk_tree_model_iter_children(m_pTreeModel, &tmp, &iter);
+ if (ret && bOnlyExpanded && !get_row_expanded(rGtkIter))
+ ret = false;
+ rGtkIter.iter = tmp;
+ if (ret)
+ {
+ //on-demand dummy entry doesn't count
+ if (get_text(rGtkIter, -1) == "<dummy>")
+ return iter_next(rGtkIter, bOnlyExpanded);
+ return true;
+ }
+
+ tmp = iter;
+ if (gtk_tree_model_iter_next(m_pTreeModel, &tmp))
+ {
+ rGtkIter.iter = tmp;
+ //on-demand dummy entry doesn't count
+ if (get_text(rGtkIter, -1) == "<dummy>")
+ return iter_next(rGtkIter, bOnlyExpanded);
+ return true;
+ }
+ // Move up level(s) until we find the level where the next node exists.
+ while (gtk_tree_model_iter_parent(m_pTreeModel, &tmp, &iter))
+ {
+ iter = tmp;
+ if (gtk_tree_model_iter_next(m_pTreeModel, &tmp))
+ {
+ rGtkIter.iter = tmp;
+ //on-demand dummy entry doesn't count
+ if (get_text(rGtkIter, -1) == "<dummy>")
+ return iter_next(rGtkIter, bOnlyExpanded);
+ return true;
+ }
+ }
+ return false;
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // tdf#154565 ignore the crossing event if it was triggered ultimately by a
+ // key stroke which is likely from exiting the search box. This way we can
+ // avoid the problem that with hover-selection that after return is used in
+ // the search box, selecting a matching row, that during teardown of the
+ // widget the box is hidden, and the crossing notification triggers
+ // selection of a different row under the mouse. If needs be this could be
+ // refined further to only happen for a specific key or other details of
+ // the triggering event
+ static gboolean signalCrossing(GtkWidget*, GdkEventCrossing*, gpointer)
+ {
+ if (GdkEvent *pEvent = gtk_get_current_event())
+ {
+ const bool bCrossingTriggeredByKeyStroke = gdk_event_get_event_type(pEvent) == GDK_KEY_PRESS;
+ gdk_event_free(pEvent);
+ return bCrossingTriggeredByKeyStroke;
+ }
+
+ return false;
+ }
+#endif
+
+public:
+ GtkInstanceTreeView(GtkTreeView* pTreeView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pTreeView), pBuilder, bTakeOwnership)
+ , m_pTreeView(pTreeView)
+ , m_pTreeModel(gtk_tree_view_get_model(m_pTreeView))
+ , m_bWorkAroundBadDragRegion(false)
+ , m_bInDrag(false)
+ , m_bChangedByMouse(false)
+ , m_nTextCol(-1)
+ , m_nTextView(-1)
+ , m_nImageCol(-1)
+ , m_nExpanderToggleCol(-1)
+ , m_nExpanderImageCol(-1)
+ , m_nPendingVAdjustment(-1)
+ , m_nChangedSignalId(g_signal_connect(gtk_tree_view_get_selection(pTreeView), "changed",
+ G_CALLBACK(signalChanged), this))
+ , m_nRowActivatedSignalId(g_signal_connect(pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
+ , m_nTestExpandRowSignalId(g_signal_connect(pTreeView, "test-expand-row", G_CALLBACK(signalTestExpandRow), this))
+ , m_nTestCollapseRowSignalId(g_signal_connect(pTreeView, "test-collapse-row", G_CALLBACK(signalTestCollapseRow), this))
+ , m_nVAdjustmentChangedSignalId(0)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_nPopupMenuSignalId(g_signal_connect(pTreeView, "popup-menu", G_CALLBACK(signalPopupMenu), this))
+ , m_nKeyPressSignalId(g_signal_connect(pTreeView, "key-press-event", G_CALLBACK(signalKeyPress), this))
+ , m_nCrossingSignalid(g_signal_connect(pTreeView, "enter-notify-event", G_CALLBACK(signalCrossing), this))
+#endif
+ , m_nQueryTooltipSignalId(0)
+ , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTreeView)))
+ , m_pChangeEvent(nullptr)
+ {
+ if (GTK_IS_TREE_STORE(m_pTreeModel))
+ {
+ m_Setter = tree_store_set;
+ m_InsertWithValues = tree_store_insert_with_values;
+ m_Insert = tree_store_insert;
+ m_Prepend = tree_store_prepend;
+ m_Remove = tree_store_remove;
+ m_Swap = tree_store_swap;
+ m_SetValue = tree_store_set_value;
+ m_Clear = tree_store_clear;
+ }
+ else
+ {
+ /*
+ tdf#136559 see: https://gitlab.gnome.org/GNOME/gtk/-/issues/2693
+ If we only need a list and not a tree we can get a performance boost from using a ListStore
+ */
+ assert(!gtk_tree_view_get_show_expanders(m_pTreeView) && "a liststore can only be used if no tree structure is needed");
+ m_Setter = list_store_set;
+ m_InsertWithValues = list_store_insert_with_values;
+ m_Insert = list_store_insert;
+ m_Prepend = list_store_prepend;
+ m_Remove = list_store_remove;
+ m_Swap = list_store_swap;
+ m_SetValue = list_store_set_value;
+ m_Clear = list_store_clear;
+ }
+
+ /* The outside concept of a column maps to a gtk CellRenderer, rather than
+ a TreeViewColumn. If the first TreeViewColumn has a leading Toggle Renderer
+ and/or a leading Image Renderer, those are considered special expander
+ columns and precede index 0 and can be accessed via outside index -1
+ */
+ m_pColumns = gtk_tree_view_get_columns(m_pTreeView);
+ int nIndex(0);
+ int nViewColumn(0);
+ for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ m_aColumnSignalIds.push_back(g_signal_connect(pColumn, "clicked", G_CALLBACK(signalColumnClicked), this));
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
+ {
+ if (m_nTextCol == -1)
+ {
+ m_nTextCol = nIndex;
+ m_nTextView = nViewColumn;
+ }
+ m_aWeightMap[nIndex] = -1;
+ m_aSensitiveMap[nIndex] = -1;
+ m_aIndentMap[nIndex] = -1;
+ m_aAlignMap[nIndex] = -1;
+ g_signal_connect(G_OBJECT(pCellRenderer), "editing-started", G_CALLBACK(signalCellEditingStarted), this);
+ g_signal_connect(G_OBJECT(pCellRenderer), "editing-canceled", G_CALLBACK(signalCellEditingCanceled), this);
+ g_signal_connect(G_OBJECT(pCellRenderer), "edited", G_CALLBACK(signalCellEdited), this);
+ }
+ else if (GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
+ {
+ const bool bExpander = nIndex == 0 || (nIndex == 1 && m_nExpanderImageCol == 0);
+ if (bExpander)
+ m_nExpanderToggleCol = nIndex;
+ g_signal_connect(G_OBJECT(pCellRenderer), "toggled", G_CALLBACK(signalCellToggled), this);
+ m_aToggleVisMap[nIndex] = -1;
+ m_aToggleTriStateMap[nIndex] = -1;
+ }
+ else if (GTK_IS_CELL_RENDERER_PIXBUF(pCellRenderer))
+ {
+ const bool bExpander = g_list_next(pRenderer) != nullptr;
+ if (bExpander && m_nExpanderImageCol == -1)
+ m_nExpanderImageCol = nIndex;
+ else if (m_nImageCol == -1)
+ m_nImageCol = nIndex;
+ }
+ g_object_set_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex", reinterpret_cast<gpointer>(nIndex));
+ ++nIndex;
+ }
+ g_list_free(pRenderers);
+ ++nViewColumn;
+ }
+
+ m_nIdCol = nIndex++;
+
+ for (auto& a : m_aToggleVisMap)
+ a.second = nIndex++;
+ for (auto& a : m_aToggleTriStateMap)
+ a.second = nIndex++;
+ for (auto& a : m_aWeightMap)
+ a.second = nIndex++;
+ for (auto& a : m_aSensitiveMap)
+ a.second = nIndex++;
+ for (auto& a : m_aIndentMap)
+ a.second = nIndex++;
+ for (auto& a : m_aAlignMap)
+ a.second = nIndex++;
+
+ ensure_drag_begin_end();
+
+ m_nRowDeletedSignalId = g_signal_connect(m_pTreeModel, "row-deleted", G_CALLBACK(signalRowDeleted), this);
+ m_nRowInsertedSignalId = g_signal_connect(m_pTreeModel, "row-inserted", G_CALLBACK(signalRowInserted), this);
+ }
+
+ virtual void connect_query_tooltip(const Link<const weld::TreeIter&, OUString>& rLink) override
+ {
+ weld::TreeView::connect_query_tooltip(rLink);
+ m_nQueryTooltipSignalId = g_signal_connect(m_pTreeView, "query-tooltip", G_CALLBACK(signalQueryTooltip), this);
+ }
+
+ virtual void columns_autosize() override
+ {
+ gtk_tree_view_columns_autosize(m_pTreeView);
+ }
+
+ virtual void set_column_fixed_widths(const std::vector<int>& rWidths) override
+ {
+ GList* pEntry = g_list_first(m_pColumns);
+ for (auto nWidth : rWidths)
+ {
+ assert(pEntry && "wrong count");
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ gtk_tree_view_column_set_fixed_width(pColumn, nWidth);
+ pEntry = g_list_next(pEntry);
+ }
+ }
+
+ virtual void set_column_editables(const std::vector<bool>& rEditables) override
+ {
+ size_t nTabCount = rEditables.size();
+ for (size_t i = 0 ; i < nTabCount; ++i)
+ set_column_editable(i, rEditables[i]);
+ }
+
+ virtual void set_centered_column(int nCol) override
+ {
+ for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
+ if (reinterpret_cast<sal_IntPtr>(pData) == nCol)
+ {
+ g_object_set(G_OBJECT(pCellRenderer), "xalign", 0.5, nullptr);
+ break;
+ }
+ }
+ g_list_free(pRenderers);
+ }
+ }
+
+ virtual int get_column_width(int nColumn) const override
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
+ assert(pColumn && "wrong count");
+ int nWidth = gtk_tree_view_column_get_width(pColumn);
+ // https://github.com/exaile/exaile/issues/580
+ // after setting fixed_width on a column and requesting width before
+ // gtk has a chance to do its layout of the column means that the width
+ // request hasn't come into effect
+ if (!nWidth)
+ nWidth = gtk_tree_view_column_get_fixed_width(pColumn);
+ return nWidth;
+ }
+
+ virtual OUString get_column_title(int nColumn) const override
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
+ assert(pColumn && "wrong count");
+ const gchar* pTitle = gtk_tree_view_column_get_title(pColumn);
+ OUString sRet(pTitle, pTitle ? strlen(pTitle) : 0, RTL_TEXTENCODING_UTF8);
+ return sRet;
+ }
+
+ virtual void set_column_title(int nColumn, const OUString& rTitle) override
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
+ assert(pColumn && "wrong count");
+ gtk_tree_view_column_set_title(pColumn, OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ virtual void set_column_custom_renderer(int nColumn, bool bEnable) override
+ {
+ assert(n_children() == 0 && "tree must be empty");
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
+ assert(pColumn && "wrong count");
+
+ GtkCellRenderer* pExpander = nullptr;
+ GtkCellRenderer* pToggle = nullptr;
+
+ // migrate existing editable setting to the new renderer
+ gboolean is_editable(false);
+ void* pEditCellData(nullptr);
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+
+ void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
+ auto nCellIndex = reinterpret_cast<sal_IntPtr>(pData);
+
+ if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
+ {
+ g_object_get(pCellRenderer, "editable", &is_editable, nullptr);
+ pEditCellData = pData;
+ break;
+ }
+ else if (GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
+ {
+ if (nCellIndex == m_nExpanderToggleCol)
+ {
+ pToggle = pCellRenderer;
+ g_object_ref(pToggle);
+ }
+ }
+ else if (GTK_IS_CELL_RENDERER_PIXBUF(pCellRenderer))
+ {
+ if (nCellIndex == m_nExpanderImageCol)
+ {
+ pExpander = pCellRenderer;
+ g_object_ref(pExpander);
+ }
+ }
+
+ }
+ g_list_free(pRenderers);
+
+ GtkCellRenderer* pRenderer;
+
+ gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn));
+ if (pExpander)
+ {
+ gtk_tree_view_column_pack_start(pColumn, pExpander, false);
+ gtk_tree_view_column_add_attribute(pColumn, pExpander, "pixbuf", m_nExpanderImageCol);
+ g_object_unref(pExpander);
+ }
+ if (pToggle)
+ {
+ gtk_tree_view_column_pack_start(pColumn, pToggle, false);
+ gtk_tree_view_column_add_attribute(pColumn, pToggle, "active", m_nExpanderToggleCol);
+ gtk_tree_view_column_add_attribute(pColumn, pToggle, "active", m_nExpanderToggleCol);
+ gtk_tree_view_column_add_attribute(pColumn, pToggle, "visible", m_aToggleTriStateMap[m_nExpanderToggleCol]);
+ g_object_unref(pToggle);
+ }
+
+ if (bEnable)
+ {
+ pRenderer = custom_cell_renderer_new();
+ GValue value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_POINTER);
+ g_value_set_pointer(&value, static_cast<gpointer>(this));
+ g_object_set_property(G_OBJECT(pRenderer), "instance", &value);
+ gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "id", m_nIdCol);
+ }
+ else
+ {
+ pRenderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
+ }
+
+ if (is_editable)
+ {
+ g_object_set(pRenderer, "editable", true, "editable-set", true, nullptr);
+ g_object_set_data(G_OBJECT(pRenderer), "g-lo-CellIndex", pEditCellData);
+ g_signal_connect(pRenderer, "editing-started", G_CALLBACK(signalCellEditingStarted), this);
+ g_signal_connect(pRenderer, "editing-canceled", G_CALLBACK(signalCellEditingCanceled), this);
+ g_signal_connect(pRenderer, "edited", G_CALLBACK(signalCellEdited), this);
+ }
+ }
+
+ virtual void queue_draw() override
+ {
+ gtk_widget_queue_draw(GTK_WIDGET(m_pTreeView));
+ }
+
+ virtual void insert(const weld::TreeIter* pParent, int pos, const OUString* pText, const OUString* pId, const OUString* pIconName,
+ VirtualDevice* pImageSurface,
+ bool bChildrenOnDemand, weld::TreeIter* pRet) override
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ const GtkInstanceTreeIter* pGtkIter = static_cast<const GtkInstanceTreeIter*>(pParent);
+ insert_row(iter, pGtkIter ? &pGtkIter->iter : nullptr, pos, pId, pText, pIconName, pImageSurface);
+ if (bChildrenOnDemand)
+ {
+ GtkTreeIter subiter;
+ OUString sDummy("<dummy>");
+ insert_row(subiter, &iter, -1, nullptr, &sDummy, nullptr, nullptr);
+ }
+ if (pRet)
+ {
+ GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
+ pGtkRetIter->iter = iter;
+ }
+ enable_notify_events();
+ }
+
+ virtual void insert_separator(int pos, const OUString& rId) override
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ if (!gtk_tree_view_get_row_separator_func(m_pTreeView))
+ gtk_tree_view_set_row_separator_func(m_pTreeView, separatorFunction, this, nullptr);
+ insert_row(iter, nullptr, pos, &rId, nullptr, nullptr, nullptr);
+ GtkTreePath* pPath = gtk_tree_model_get_path(m_pTreeModel, &iter);
+ m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(m_pTreeModel, pPath));
+ gtk_tree_path_free(pPath);
+ enable_notify_events();
+ }
+
+ virtual void set_font_color(int pos, const Color& rColor) override
+ {
+ GtkTreeIter iter;
+ gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
+ set_font_color(iter, rColor);
+ }
+
+ virtual void set_font_color(const weld::TreeIter& rIter, const Color& rColor) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ set_font_color(rGtkIter.iter, rColor);
+ }
+
+ virtual void remove(int pos) override
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
+ m_Remove(m_pTreeModel, &iter);
+ enable_notify_events();
+ }
+
+ virtual int find_text(const OUString& rText) const override
+ {
+ Search aSearch(rText, m_nTextCol);
+ gtk_tree_model_foreach(m_pTreeModel, foreach_find, &aSearch);
+ return aSearch.index;
+ }
+
+ virtual int find_id(const OUString& rId) const override
+ {
+ Search aSearch(rId, m_nIdCol);
+ gtk_tree_model_foreach(m_pTreeModel, foreach_find, &aSearch);
+ return aSearch.index;
+ }
+
+ virtual void bulk_insert_for_each(int nSourceCount, const std::function<void(weld::TreeIter&, int nSourceIndex)>& func,
+ const weld::TreeIter* pParent,
+ const std::vector<int>* pFixedWidths) override
+ {
+ GtkInstanceTreeIter* pGtkIter = const_cast<GtkInstanceTreeIter*>(static_cast<const GtkInstanceTreeIter*>(pParent));
+
+ freeze();
+ if (!pGtkIter)
+ clear();
+ else
+ {
+ GtkTreeIter restore(pGtkIter->iter);
+
+ if (iter_children(*pGtkIter))
+ while (m_Remove(m_pTreeModel, &pGtkIter->iter));
+
+ pGtkIter->iter = restore;
+ }
+ GtkInstanceTreeIter aGtkIter(nullptr);
+
+ if (pFixedWidths)
+ set_column_fixed_widths(*pFixedWidths);
+
+ while (nSourceCount)
+ {
+ // tdf#125241 inserting backwards is massively faster
+ m_Prepend(m_pTreeModel, &aGtkIter.iter, pGtkIter ? &pGtkIter->iter : nullptr);
+ func(aGtkIter, --nSourceCount);
+ }
+
+ thaw();
+ }
+
+ virtual void swap(int pos1, int pos2) override
+ {
+ disable_notify_events();
+
+ GtkTreeIter iter1;
+ gtk_tree_model_iter_nth_child(m_pTreeModel, &iter1, nullptr, pos1);
+
+ GtkTreeIter iter2;
+ gtk_tree_model_iter_nth_child(m_pTreeModel, &iter2, nullptr, pos2);
+
+ m_Swap(m_pTreeModel, &iter1, &iter2);
+
+ enable_notify_events();
+ }
+
+ virtual void clear() override
+ {
+ disable_notify_events();
+ gtk_tree_view_set_row_separator_func(m_pTreeView, nullptr, nullptr, nullptr);
+ m_aSeparatorRows.clear();
+ m_Clear(m_pTreeModel);
+ enable_notify_events();
+ }
+
+ virtual void make_sorted() override
+ {
+ // thaw wants to restore sort state of freeze
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
+ m_xSorter.reset(new comphelper::string::NaturalStringSorter(
+ ::comphelper::getProcessComponentContext(),
+ Application::GetSettings().GetUILanguageTag().getLocale()));
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, sortFunc, this, nullptr);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
+ }
+
+ virtual void make_unsorted() override
+ {
+ m_xSorter.reset();
+ int nSortColumn;
+ GtkSortType eSortType;
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
+ gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, eSortType);
+ }
+
+ virtual void set_sort_order(bool bAscending) override
+ {
+ GtkSortType eSortType = bAscending ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
+
+ gint sort_column_id(0);
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr);
+ gtk_tree_sortable_set_sort_column_id(pSortable, sort_column_id, eSortType);
+ }
+
+ virtual bool get_sort_order() const override
+ {
+ int nSortColumn;
+ GtkSortType eSortType;
+
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
+ return nSortColumn != GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID && eSortType == GTK_SORT_ASCENDING;
+ }
+
+ virtual void set_sort_indicator(TriState eState, int col) override
+ {
+ assert(col >= 0 && "cannot sort on expander column");
+
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
+ assert(pColumn && "wrong count");
+ if (eState == TRISTATE_INDET)
+ gtk_tree_view_column_set_sort_indicator(pColumn, false);
+ else
+ {
+ gtk_tree_view_column_set_sort_indicator(pColumn, true);
+ GtkSortType eSortType = eState == TRISTATE_TRUE ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
+ gtk_tree_view_column_set_sort_order(pColumn, eSortType);
+ }
+ }
+
+ virtual TriState get_sort_indicator(int col) const override
+ {
+ assert(col >= 0 && "cannot sort on expander column");
+
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
+ if (!gtk_tree_view_column_get_sort_indicator(pColumn))
+ return TRISTATE_INDET;
+ return gtk_tree_view_column_get_sort_order(pColumn) == GTK_SORT_ASCENDING ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+
+ virtual int get_sort_column() const override
+ {
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gint sort_column_id(0);
+ if (!gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr))
+ return -1;
+ return to_external_model(sort_column_id);
+ }
+
+ virtual void set_sort_column(int nColumn) override
+ {
+ if (nColumn == -1)
+ {
+ make_unsorted();
+ return;
+ }
+ GtkSortType eSortType;
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_get_sort_column_id(pSortable, nullptr, &eSortType);
+ int nSortCol = to_internal_model(nColumn);
+ gtk_tree_sortable_set_sort_func(pSortable, nSortCol, sortFunc, this, nullptr);
+ gtk_tree_sortable_set_sort_column_id(pSortable, nSortCol, eSortType);
+ }
+
+ virtual void set_sort_func(const std::function<int(const weld::TreeIter&, const weld::TreeIter&)>& func) override
+ {
+ weld::TreeView::set_sort_func(func);
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_sort_column_changed(pSortable);
+ }
+
+ virtual int n_children() const override
+ {
+ return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr);
+ }
+
+ virtual int iter_n_children(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ return gtk_tree_model_iter_n_children(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ }
+
+ virtual void select(int pos) override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "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))
+ {
+ gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(m_pTreeView));
+ }
+ else
+ {
+ GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
+ gtk_tree_selection_select_path(gtk_tree_view_get_selection(m_pTreeView), path);
+ gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
+ gtk_tree_path_free(path);
+ }
+ enable_notify_events();
+ }
+
+ virtual void set_cursor(int pos) override
+ {
+ disable_notify_events();
+ GtkTreePath* path;
+ if (pos != -1)
+ {
+ path = gtk_tree_path_new_from_indices(pos, -1);
+ gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
+ }
+ else
+ path = gtk_tree_path_new_from_indices(G_MAXINT, -1);
+ gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
+ gtk_tree_path_free(path);
+ enable_notify_events();
+ }
+
+ virtual void scroll_to_row(int pos) override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
+ disable_notify_events();
+ GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
+ gtk_tree_view_expand_to_path(m_pTreeView, path);
+ gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, true, 0, 0);
+ gtk_tree_path_free(path);
+ enable_notify_events();
+ }
+
+ virtual bool is_selected(int pos) const override
+ {
+ GtkTreeIter iter;
+ gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
+ return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(m_pTreeView), &iter);
+ }
+
+ virtual void unselect(int pos) override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "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))
+ {
+ gtk_tree_selection_select_all(gtk_tree_view_get_selection(m_pTreeView));
+ }
+ else
+ {
+ GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
+ gtk_tree_selection_unselect_path(gtk_tree_view_get_selection(m_pTreeView), path);
+ gtk_tree_path_free(path);
+ }
+ enable_notify_events();
+ }
+
+ virtual std::vector<int> get_selected_rows() const override
+ {
+ std::vector<int> aRows;
+
+ GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), nullptr);
+ for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
+ {
+ GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
+
+ gint depth;
+ gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
+ int nRow = indices[depth-1];
+
+ aRows.push_back(nRow);
+ }
+ g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
+
+ return aRows;
+ }
+
+ virtual void all_foreach(const std::function<bool(weld::TreeIter&)>& func) override
+ {
+ g_object_freeze_notify(G_OBJECT(m_pTreeModel));
+
+ GtkInstanceTreeIter aGtkIter(nullptr);
+ if (get_iter_first(aGtkIter))
+ {
+ do
+ {
+ if (func(aGtkIter))
+ break;
+ } while (iter_next(aGtkIter));
+ }
+
+ g_object_thaw_notify(G_OBJECT(m_pTreeModel));
+ }
+
+ virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
+ {
+ g_object_freeze_notify(G_OBJECT(m_pTreeModel));
+
+ GtkInstanceTreeIter aGtkIter(nullptr);
+
+ GtkTreeModel* pModel;
+ GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
+ for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
+ {
+ GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
+ gtk_tree_model_get_iter(pModel, &aGtkIter.iter, path);
+ if (func(aGtkIter))
+ break;
+ }
+ g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
+
+ g_object_thaw_notify(G_OBJECT(m_pTreeModel));
+ }
+
+ virtual void visible_foreach(const std::function<bool(weld::TreeIter&)>& func) override
+ {
+ g_object_freeze_notify(G_OBJECT(m_pTreeModel));
+
+ GtkTreePath* start_path;
+ GtkTreePath* end_path;
+
+ if (!gtk_tree_view_get_visible_range(m_pTreeView, &start_path, &end_path))
+ return;
+
+ GtkInstanceTreeIter aGtkIter(nullptr);
+ gtk_tree_model_get_iter(m_pTreeModel, &aGtkIter.iter, start_path);
+
+ do
+ {
+ if (func(aGtkIter))
+ break;
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, &aGtkIter.iter);
+ bool bContinue = gtk_tree_path_compare(path, end_path) != 0;
+ gtk_tree_path_free(path);
+ if (!bContinue)
+ break;
+ if (!iter_next(aGtkIter))
+ break;
+ } while(true);
+
+ gtk_tree_path_free(start_path);
+ gtk_tree_path_free(end_path);
+
+ g_object_thaw_notify(G_OBJECT(m_pTreeModel));
+ }
+
+ virtual void connect_visible_range_changed(const Link<weld::TreeView&, void>& rLink) override
+ {
+ weld::TreeView::connect_visible_range_changed(rLink);
+ if (!m_nVAdjustmentChangedSignalId)
+ {
+ GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
+ m_nVAdjustmentChangedSignalId = g_signal_connect(pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustmentChanged), this);
+ }
+ }
+
+ virtual bool is_selected(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ }
+
+ virtual OUString get_text(int pos, int col) const override
+ {
+ if (col == -1)
+ col = m_nTextCol;
+ else
+ col = to_internal_model(col);
+ return get(pos, col);
+ }
+
+ virtual void set_text(int pos, const OUString& rText, int col) override
+ {
+ if (col == -1)
+ col = m_nTextCol;
+ else
+ col = to_internal_model(col);
+ set(pos, col, rText);
+ }
+
+ virtual TriState get_toggle(int pos, int col) const override
+ {
+ if (col == -1)
+ col = m_nExpanderToggleCol;
+ else
+ col = to_internal_model(col);
+
+ const auto iter = m_aToggleTriStateMap.find(col);
+ assert(iter != m_aToggleTriStateMap.end());
+ if (get_bool(pos, iter->second))
+ return TRISTATE_INDET;
+ return get_bool(pos, col) ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+
+ virtual TriState get_toggle(const weld::TreeIter& rIter, int col) const override
+ {
+ if (col == -1)
+ col = m_nExpanderToggleCol;
+ else
+ col = to_internal_model(col);
+
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ const auto iter = m_aToggleTriStateMap.find(col);
+ assert(iter != m_aToggleTriStateMap.end());
+ if (get_bool(rGtkIter.iter, iter->second))
+ return TRISTATE_INDET;
+ return get_bool(rGtkIter.iter, col) ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+
+ virtual void set_toggle(const weld::TreeIter& rIter, TriState eState, int col) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ set_toggle(rGtkIter.iter, eState, col);
+ }
+
+ virtual void set_toggle(int pos, TriState eState, int col) override
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ set_toggle(iter, eState, col);
+ }
+
+ virtual void enable_toggle_buttons(weld::ColumnToggleType eType) override
+ {
+ for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ if (!GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
+ continue;
+ GtkCellRendererToggle* pToggle = GTK_CELL_RENDERER_TOGGLE(pCellRenderer);
+ gtk_cell_renderer_toggle_set_radio(pToggle, eType == weld::ColumnToggleType::Radio);
+ }
+ g_list_free(pRenderers);
+ }
+ }
+
+ virtual void set_clicks_to_toggle(int /*nToggleBehavior*/) override
+ {
+ }
+
+ virtual void set_extra_row_indent(const weld::TreeIter& rIter, int nIndentLevel) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ set(rGtkIter.iter, m_aIndentMap[m_nTextCol], nIndentLevel * get_expander_size());
+ }
+
+ virtual void set_text_emphasis(const weld::TreeIter& rIter, bool bOn, int col) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ auto weight = bOn ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL;
+ if (col == -1)
+ {
+ for (const auto& elem : m_aWeightMap)
+ set(rGtkIter.iter, elem.second, weight);
+ return;
+ }
+ col = to_internal_model(col);
+ set(rGtkIter.iter, m_aWeightMap[col], weight);
+ }
+
+ virtual void set_text_emphasis(int pos, bool bOn, int col) override
+ {
+ auto weight = bOn ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL;
+ if (col == -1)
+ {
+ for (const auto& elem : m_aWeightMap)
+ set(pos, elem.second, weight);
+ return;
+ }
+ col = to_internal_model(col);
+ set(pos, m_aWeightMap[col], weight);
+ }
+
+ virtual bool get_text_emphasis(const weld::TreeIter& rIter, int col) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ col = to_internal_model(col);
+ const auto iter = m_aWeightMap.find(col);
+ assert(iter != m_aWeightMap.end());
+ return get_int(rGtkIter.iter, iter->second) == PANGO_WEIGHT_BOLD;
+ }
+
+ virtual bool get_text_emphasis(int pos, int col) const override
+ {
+ col = to_internal_model(col);
+ const auto iter = m_aWeightMap.find(col);
+ assert(iter != m_aWeightMap.end());
+ return get_int(pos, iter->second) == PANGO_WEIGHT_BOLD;
+ }
+
+ virtual void set_text_align(const weld::TreeIter& rIter, double fAlign, int col) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ col = to_internal_model(col);
+ set(rGtkIter.iter, m_aAlignMap[col], fAlign);
+ }
+
+ virtual void set_text_align(int pos, double fAlign, int col) override
+ {
+ col = to_internal_model(col);
+ set(pos, m_aAlignMap[col], fAlign);
+ }
+
+ using GtkInstanceWidget::set_sensitive;
+ using GtkInstanceWidget::get_sensitive;
+
+ virtual void set_sensitive(int pos, bool bSensitive, int col) override
+ {
+ if (col == -1)
+ {
+ for (const auto& elem : m_aSensitiveMap)
+ set(pos, elem.second, bSensitive);
+ }
+ else
+ {
+ col = to_internal_model(col);
+ set(pos, m_aSensitiveMap[col], bSensitive);
+ }
+ }
+
+ virtual bool get_sensitive(int pos, int col) const override
+ {
+ col = to_internal_model(col);
+ const auto iter = m_aSensitiveMap.find(col);
+ assert(iter != m_aSensitiveMap.end());
+ return get_bool(pos, iter->second);
+ }
+
+ virtual void set_sensitive(const weld::TreeIter& rIter, bool bSensitive, int col) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ if (col == -1)
+ {
+ for (const auto& elem : m_aSensitiveMap)
+ set(rGtkIter.iter, elem.second, bSensitive);
+ }
+ else
+ {
+ col = to_internal_model(col);
+ set(rGtkIter.iter, m_aSensitiveMap[col], bSensitive);
+ }
+ }
+
+ virtual bool get_sensitive(const weld::TreeIter& rIter, int col) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ col = to_internal_model(col);
+ const auto iter = m_aSensitiveMap.find(col);
+ assert(iter != m_aSensitiveMap.end());
+ return get_bool(rGtkIter.iter, iter->second);
+ }
+
+ void set_image(const GtkTreeIter& iter, int col, GdkPixbuf* pixbuf)
+ {
+ if (col == -1)
+ col = m_nExpanderImageCol;
+ else
+ col = to_internal_model(col);
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, pixbuf, -1);
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+
+ void set_image(int pos, GdkPixbuf* pixbuf, int col)
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ {
+ set_image(iter, col, pixbuf);
+ }
+ }
+
+ virtual void set_image(int pos, const css::uno::Reference<css::graphic::XGraphic>& rImage, int col) override
+ {
+ set_image(pos, getPixbuf(rImage), col);
+ }
+
+ virtual void set_image(int pos, const OUString& rImage, int col) override
+ {
+ set_image(pos, getPixbuf(rImage), col);
+ }
+
+ virtual void set_image(int pos, VirtualDevice& rImage, int col) override
+ {
+ set_image(pos, getPixbuf(rImage), col);
+ }
+
+ virtual void set_image(const weld::TreeIter& rIter, const css::uno::Reference<css::graphic::XGraphic>& rImage, int col) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ set_image(rGtkIter.iter, col, getPixbuf(rImage));
+ }
+
+ virtual void set_image(const weld::TreeIter& rIter, const OUString& rImage, int col) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ set_image(rGtkIter.iter, col, getPixbuf(rImage));
+ }
+
+ virtual void set_image(const weld::TreeIter& rIter, VirtualDevice& rImage, int col) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ set_image(rGtkIter.iter, col, getPixbuf(rImage));
+ }
+
+ virtual OUString get_id(int pos) const override
+ {
+ return get(pos, m_nIdCol);
+ }
+
+ virtual void set_id(int pos, const OUString& rId) override
+ {
+ return set(pos, m_nIdCol, rId);
+ }
+
+ virtual int get_iter_index_in_parent(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+
+ gint depth;
+ gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
+ int nRet = indices[depth-1];
+
+ gtk_tree_path_free(path);
+
+ return nRet;
+ }
+
+ virtual int iter_compare(const weld::TreeIter& a, const weld::TreeIter& b) const override
+ {
+ const GtkInstanceTreeIter& rGtkIterA = static_cast<const GtkInstanceTreeIter&>(a);
+ const GtkInstanceTreeIter& rGtkIterB = static_cast<const GtkInstanceTreeIter&>(b);
+
+ GtkTreePath* pathA = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIterA.iter));
+ GtkTreePath* pathB = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIterB.iter));
+
+ int nRet = gtk_tree_path_compare(pathA, pathB);
+
+ gtk_tree_path_free(pathB);
+ gtk_tree_path_free(pathA);
+
+ return nRet;
+ }
+
+ // by copy and delete of old copy
+ void move_subtree(GtkTreeIter& rFromIter, GtkTreeIter* pGtkParentIter, int nIndexInNewParent)
+ {
+ int nCols = gtk_tree_model_get_n_columns(m_pTreeModel);
+ GValue value;
+
+ GtkTreeIter toiter;
+ m_Insert(m_pTreeModel, &toiter, pGtkParentIter, nIndexInNewParent);
+
+ for (int i = 0; i < nCols; ++i)
+ {
+ memset(&value, 0, sizeof(GValue));
+ gtk_tree_model_get_value(m_pTreeModel, &rFromIter, i, &value);
+ m_SetValue(m_pTreeModel, &toiter, i, &value);
+ g_value_unset(&value);
+ }
+
+ GtkTreeIter tmpfromiter;
+ if (gtk_tree_model_iter_children(m_pTreeModel, &tmpfromiter, &rFromIter))
+ {
+ int j = 0;
+ do
+ {
+ move_subtree(tmpfromiter, &toiter, j++);
+ } while (gtk_tree_model_iter_next(m_pTreeModel, &tmpfromiter));
+ }
+
+ m_Remove(m_pTreeModel, &rFromIter);
+ }
+
+ virtual void move_subtree(weld::TreeIter& rNode, const weld::TreeIter* pNewParent, int nIndexInNewParent) override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rNode);
+ const GtkInstanceTreeIter* pGtkParentIter = static_cast<const GtkInstanceTreeIter*>(pNewParent);
+ move_subtree(rGtkIter.iter, pGtkParentIter ? const_cast<GtkTreeIter*>(&pGtkParentIter->iter) : nullptr, nIndexInNewParent);
+ }
+
+ virtual int get_selected_index() const override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
+ int nRet = -1;
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(m_pTreeView);
+ if (gtk_tree_selection_get_mode(selection) != GTK_SELECTION_MULTIPLE)
+ {
+ GtkTreeIter iter;
+ GtkTreeModel* pModel;
+ if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(m_pTreeView), &pModel, &iter))
+ {
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, &iter);
+
+ gint depth;
+ gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
+ nRet = indices[depth-1];
+
+ gtk_tree_path_free(path);
+ }
+ }
+ else
+ {
+ auto vec = get_selected_rows();
+ return vec.empty() ? -1 : vec[0];
+ }
+ return nRet;
+ }
+
+ bool get_selected_iterator(GtkTreeIter* pIter) const
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
+ bool bRet = false;
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(m_pTreeView);
+ if (gtk_tree_selection_get_mode(selection) != GTK_SELECTION_MULTIPLE)
+ bRet = gtk_tree_selection_get_selected(gtk_tree_view_get_selection(m_pTreeView), nullptr, pIter);
+ else
+ {
+ GtkTreeModel* pModel;
+ GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
+ for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
+ {
+ if (pIter)
+ {
+ GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
+ gtk_tree_model_get_iter(pModel, pIter, path);
+ }
+ bRet = true;
+ break;
+ }
+ g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
+ }
+ return bRet;
+ }
+
+ virtual OUString get_selected_text() const override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
+ GtkTreeIter iter;
+ if (get_selected_iterator(&iter))
+ return get(iter, m_nTextCol);
+ return OUString();
+ }
+
+ virtual OUString get_selected_id() const override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
+ GtkTreeIter iter;
+ if (get_selected_iterator(&iter))
+ return get(iter, m_nIdCol);
+ return OUString();
+ }
+
+ virtual std::unique_ptr<weld::TreeIter> make_iterator(const weld::TreeIter* pOrig) const override
+ {
+ return std::unique_ptr<weld::TreeIter>(new GtkInstanceTreeIter(static_cast<const GtkInstanceTreeIter*>(pOrig)));
+ }
+
+ virtual void copy_iterator(const weld::TreeIter& rSource, weld::TreeIter& rDest) const override
+ {
+ const GtkInstanceTreeIter& rGtkSource(static_cast<const GtkInstanceTreeIter&>(rSource));
+ GtkInstanceTreeIter& rGtkDest(static_cast<GtkInstanceTreeIter&>(rDest));
+ rGtkDest.iter = rGtkSource.iter;
+ }
+
+ virtual bool get_selected(weld::TreeIter* pIter) const override
+ {
+ GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
+ return get_selected_iterator(pGtkIter ? &pGtkIter->iter : nullptr);
+ }
+
+ virtual bool get_cursor(weld::TreeIter* pIter) const override
+ {
+ GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
+ GtkTreePath* path;
+ gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
+ if (pGtkIter && path)
+ {
+ gtk_tree_model_get_iter(m_pTreeModel, &pGtkIter->iter, path);
+ }
+ if (!path)
+ return false;
+ gtk_tree_path_free(path);
+ return true;
+ }
+
+ virtual int get_cursor_index() const override
+ {
+ int nRet = -1;
+
+ GtkTreePath* path;
+ gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
+ if (path)
+ {
+ gint depth;
+ gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
+ nRet = indices[depth-1];
+ gtk_tree_path_free(path);
+ }
+
+ return nRet;
+ }
+
+ virtual void set_cursor(const weld::TreeIter& rIter) override
+ {
+ disable_notify_events();
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreeIter Iter;
+ if (gtk_tree_model_iter_parent(m_pTreeModel, &Iter, const_cast<GtkTreeIter*>(&rGtkIter.iter)))
+ {
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, &Iter);
+ if (!gtk_tree_view_row_expanded(m_pTreeView, path))
+ gtk_tree_view_expand_to_path(m_pTreeView, path);
+ gtk_tree_path_free(path);
+ }
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
+ gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
+ gtk_tree_path_free(path);
+ enable_notify_events();
+ }
+
+ virtual bool get_iter_first(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ return gtk_tree_model_get_iter_first(m_pTreeModel, &rGtkIter.iter);
+ }
+
+ virtual bool iter_next_sibling(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ return gtk_tree_model_iter_next(m_pTreeModel, &rGtkIter.iter);
+ }
+
+ virtual bool iter_previous_sibling(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ return gtk_tree_model_iter_previous(m_pTreeModel, &rGtkIter.iter);
+ }
+
+ virtual bool iter_next(weld::TreeIter& rIter) const override
+ {
+ return iter_next(rIter, false);
+ }
+
+ virtual bool iter_previous(weld::TreeIter& rIter) const override
+ {
+ bool ret = false;
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ GtkTreeIter iter = rGtkIter.iter;
+ GtkTreeIter tmp = iter;
+ if (gtk_tree_model_iter_previous(m_pTreeModel, &tmp))
+ {
+ // Move down level(s) until we find the level where the last node exists.
+ int nChildren = gtk_tree_model_iter_n_children(m_pTreeModel, &tmp);
+ if (!nChildren)
+ rGtkIter.iter = tmp;
+ else
+ last_child(m_pTreeModel, &rGtkIter.iter, &tmp, nChildren);
+ ret = true;
+ }
+ else
+ {
+ // Move up level
+ if (gtk_tree_model_iter_parent(m_pTreeModel, &tmp, &iter))
+ {
+ rGtkIter.iter = tmp;
+ ret = true;
+ }
+ }
+
+ if (ret)
+ {
+ //on-demand dummy entry doesn't count
+ if (get_text(rGtkIter, -1) == "<dummy>")
+ return iter_previous(rGtkIter);
+ return true;
+ }
+
+ return false;
+ }
+
+ virtual bool iter_children(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ GtkTreeIter tmp;
+ bool ret = gtk_tree_model_iter_children(m_pTreeModel, &tmp, &rGtkIter.iter);
+ rGtkIter.iter = tmp;
+ if (ret)
+ {
+ //on-demand dummy entry doesn't count
+ return get_text(rGtkIter, -1) != "<dummy>";
+ }
+ return ret;
+ }
+
+ virtual bool iter_parent(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ GtkTreeIter tmp;
+ bool ret = gtk_tree_model_iter_parent(m_pTreeModel, &tmp, &rGtkIter.iter);
+ rGtkIter.iter = tmp;
+ return ret;
+ }
+
+ virtual void remove(const weld::TreeIter& rIter) override
+ {
+ disable_notify_events();
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ m_Remove(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ enable_notify_events();
+ }
+
+ virtual void remove_selection() override
+ {
+ disable_notify_events();
+
+ std::vector<GtkTreeIter> aIters;
+ GtkTreeModel* pModel;
+ GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
+ for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
+ {
+ GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
+ aIters.emplace_back();
+ gtk_tree_model_get_iter(pModel, &aIters.back(), path);
+ }
+ g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
+
+ for (auto& iter : aIters)
+ m_Remove(m_pTreeModel, &iter);
+
+ enable_notify_events();
+ }
+
+ virtual void select(const weld::TreeIter& rIter) override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
+ disable_notify_events();
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ gtk_tree_selection_select_iter(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ enable_notify_events();
+ }
+
+ virtual void scroll_to_row(const weld::TreeIter& rIter) override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
+ disable_notify_events();
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ gtk_tree_view_expand_to_path(m_pTreeView, path);
+ gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, true, 0, 0);
+ gtk_tree_path_free(path);
+ enable_notify_events();
+ }
+
+ virtual void unselect(const weld::TreeIter& rIter) override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
+ disable_notify_events();
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ gtk_tree_selection_unselect_iter(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ enable_notify_events();
+ }
+
+ virtual int get_iter_depth(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ int ret = gtk_tree_path_get_depth(path) - 1;
+ gtk_tree_path_free(path);
+ return ret;
+ }
+
+ virtual bool iter_has_child(const weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter aTempCopy(static_cast<const GtkInstanceTreeIter*>(&rIter));
+ return iter_children(aTempCopy);
+ }
+
+ virtual bool get_row_expanded(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ bool ret = gtk_tree_view_row_expanded(m_pTreeView, path);
+ gtk_tree_path_free(path);
+ return ret;
+ }
+
+ virtual bool get_children_on_demand(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkInstanceTreeIter aIter(&rGtkIter);
+ return child_is_placeholder(aIter);
+ }
+
+ virtual void set_children_on_demand(const weld::TreeIter& rIter, bool bChildrenOnDemand) override
+ {
+ disable_notify_events();
+
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkInstanceTreeIter aPlaceHolderIter(&rGtkIter);
+
+ bool bPlaceHolder = child_is_placeholder(aPlaceHolderIter);
+
+ if (bChildrenOnDemand && !bPlaceHolder)
+ {
+ GtkTreeIter subiter;
+ OUString sDummy("<dummy>");
+ insert_row(subiter, &rGtkIter.iter, -1, nullptr, &sDummy, nullptr, nullptr);
+ }
+ else if (!bChildrenOnDemand && bPlaceHolder)
+ remove(aPlaceHolderIter);
+
+ enable_notify_events();
+ }
+
+ virtual void expand_row(const weld::TreeIter& rIter) override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't expand when frozen");
+
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ if (!gtk_tree_view_row_expanded(m_pTreeView, path))
+ gtk_tree_view_expand_to_path(m_pTreeView, path);
+ gtk_tree_path_free(path);
+ }
+
+ virtual void collapse_row(const weld::TreeIter& rIter) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ if (gtk_tree_view_row_expanded(m_pTreeView, path))
+ gtk_tree_view_collapse_row(m_pTreeView, path);
+ gtk_tree_path_free(path);
+ }
+
+ virtual OUString get_text(const weld::TreeIter& rIter, int col) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ if (col == -1)
+ col = m_nTextCol;
+ else
+ col = to_internal_model(col);
+ return get(rGtkIter.iter, col);
+ }
+
+ virtual void set_text(const weld::TreeIter& rIter, const OUString& rText, int col) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ if (col == -1)
+ col = m_nTextCol;
+ else
+ col = to_internal_model(col);
+ set(rGtkIter.iter, col, rText);
+ }
+
+ virtual OUString get_id(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ return get(rGtkIter.iter, m_nIdCol);
+ }
+
+ virtual void set_id(const weld::TreeIter& rIter, const OUString& rId) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ set(rGtkIter.iter, m_nIdCol, rId);
+ }
+
+ virtual void freeze() override
+ {
+ disable_notify_events();
+ bool bIsFirstFreeze = IsFirstFreeze();
+ GtkInstanceWidget::freeze();
+ if (bIsFirstFreeze)
+ {
+ g_object_ref(m_pTreeModel);
+ gtk_tree_view_set_model(m_pTreeView, nullptr);
+ g_object_freeze_notify(G_OBJECT(m_pTreeModel));
+ if (m_xSorter)
+ {
+ int nSortColumn;
+ GtkSortType eSortType;
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
+ gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, eSortType);
+
+ m_aSavedSortColumns.push_back(nSortColumn);
+ m_aSavedSortTypes.push_back(eSortType);
+ }
+ }
+ enable_notify_events();
+ }
+
+ virtual void thaw() override
+ {
+ disable_notify_events();
+ if (IsLastThaw())
+ {
+ if (m_xSorter)
+ {
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_aSavedSortColumns.back(), m_aSavedSortTypes.back());
+ m_aSavedSortTypes.pop_back();
+ m_aSavedSortColumns.pop_back();
+ }
+ g_object_thaw_notify(G_OBJECT(m_pTreeModel));
+ gtk_tree_view_set_model(m_pTreeView, GTK_TREE_MODEL(m_pTreeModel));
+ g_object_unref(m_pTreeModel);
+ }
+ GtkInstanceWidget::thaw();
+ enable_notify_events();
+ }
+
+ virtual int get_height_rows(int nRows) const override
+ {
+ return ::get_height_rows(m_pTreeView, m_pColumns, nRows);
+ }
+
+ virtual Size get_size_request() const override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ {
+ return Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
+ gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
+ }
+ int nWidth, nHeight;
+ gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
+ return Size(nWidth, nHeight);
+ }
+
+ virtual Size get_preferred_size() const override
+ {
+ Size aRet(-1, -1);
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ {
+ aRet = Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
+ gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
+ }
+ GtkRequisition size;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // sometimes gtk gives a bad outcome for gtk_widget_get_preferred_size if GtkTreeView's
+ // do_validate_rows hasn't been run before querying the preferred size, if we call
+ // gtk_widget_get_preferred_width first, we can guarantee do_validate_rows gets called
+ gtk_widget_get_preferred_width(m_pWidget, nullptr, &size.width);
+#endif
+ gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
+ if (aRet.Width() == -1)
+ aRet.setWidth(size.width);
+ if (aRet.Height() == -1)
+ aRet.setHeight(size.height);
+ return aRet;
+ }
+
+ virtual void show() override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ gtk_widget_show(pParent);
+ gtk_widget_show(m_pWidget);
+ }
+
+ virtual void hide() override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ gtk_widget_hide(pParent);
+ gtk_widget_hide(m_pWidget);
+ }
+
+ virtual void enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants) override
+ {
+ do_enable_drag_source(rHelper, eDNDConstants);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ virtual void drag_source_set(const std::vector<GtkTargetEntry>& rGtkTargets, GdkDragAction eDragAction) override
+ {
+ if (rGtkTargets.empty() && !eDragAction)
+ gtk_tree_view_unset_rows_drag_source(m_pTreeView);
+ else
+ gtk_tree_view_enable_model_drag_source(m_pTreeView, GDK_BUTTON1_MASK, rGtkTargets.data(), rGtkTargets.size(), eDragAction);
+ }
+#endif
+
+ virtual void set_selection_mode(SelectionMode eMode) override
+ {
+ disable_notify_events();
+ gtk_tree_selection_set_mode(gtk_tree_view_get_selection(m_pTreeView), VclToGtk(eMode));
+ enable_notify_events();
+ }
+
+ virtual int count_selected_rows() const override
+ {
+ return gtk_tree_selection_count_selected_rows(gtk_tree_view_get_selection(m_pTreeView));
+ }
+
+ int starts_with(const OUString& rStr, int nStartRow, bool bCaseSensitive)
+ {
+ return ::starts_with(m_pTreeModel, rStr, m_nTextCol, nStartRow, bCaseSensitive);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
+ g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
+
+ g_signal_handler_block(m_pTreeModel, m_nRowDeletedSignalId);
+ g_signal_handler_block(m_pTreeModel, m_nRowInsertedSignalId);
+
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+
+ g_signal_handler_unblock(m_pTreeModel, m_nRowDeletedSignalId);
+ g_signal_handler_unblock(m_pTreeModel, m_nRowInsertedSignalId);
+
+ g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
+ g_signal_handler_unblock(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
+ }
+
+ virtual void connect_popup_menu(const Link<const CommandEvent&, bool>& rLink) override
+ {
+ ensureButtonPressSignal();
+ weld::TreeView::connect_popup_menu(rLink);
+ }
+
+ virtual bool get_dest_row_at_pos(const Point &rPos, weld::TreeIter* pResult, bool bDnDMode, bool bAutoScroll) override
+ {
+ if (rPos.X() < 0 || rPos.Y() < 0)
+ {
+ // short-circuit to avoid "gtk_tree_view_get_dest_row_at_pos: assertion 'drag_x >= 0'" g_assert
+ return false;
+ }
+
+ const bool bAsTree = gtk_tree_view_get_enable_tree_lines(m_pTreeView);
+
+ // to keep it simple we'll default to always drop before the current row
+ // except for the special edge cases
+ GtkTreeViewDropPosition pos = bAsTree ? GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : GTK_TREE_VIEW_DROP_BEFORE;
+
+ // unhighlight current highlighted row
+ gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, pos);
+
+ if (m_bWorkAroundBadDragRegion)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_unset_state_flags(GTK_WIDGET(m_pTreeView), GTK_STATE_FLAG_DROP_ACTIVE);
+#else
+ gtk_drag_unhighlight(GTK_WIDGET(m_pTreeView));
+#endif
+ }
+
+ GtkTreePath *path = nullptr;
+ GtkTreeViewDropPosition gtkpos = bAsTree ? GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : GTK_TREE_VIEW_DROP_BEFORE;
+ bool ret = gtk_tree_view_get_dest_row_at_pos(m_pTreeView, rPos.X(), rPos.Y(),
+ &path, &gtkpos);
+
+ // find the last entry in the model for comparison
+ GtkTreePath *lastpath = get_path_of_last_entry(m_pTreeModel);
+
+ if (!ret)
+ {
+ // empty space, draw an indicator at the last entry
+ assert(!path);
+ path = gtk_tree_path_copy(lastpath);
+ pos = GTK_TREE_VIEW_DROP_AFTER;
+ }
+ else if (bDnDMode && gtk_tree_path_compare(path, lastpath) == 0)
+ {
+ // if we're on the last entry, see if gtk thinks
+ // the drop should be before or after it, and if
+ // its after, treat it like a drop into empty
+ // space, i.e. append it
+ if (gtkpos == GTK_TREE_VIEW_DROP_AFTER ||
+ gtkpos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
+ {
+ ret = false;
+ pos = bAsTree ? gtkpos : GTK_TREE_VIEW_DROP_AFTER;
+ }
+ }
+
+ if (ret && pResult)
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(*pResult);
+ gtk_tree_model_get_iter(m_pTreeModel, &rGtkIter.iter, path);
+ }
+
+ if (m_bInDrag && bDnDMode)
+ {
+ // highlight the row
+ gtk_tree_view_set_drag_dest_row(m_pTreeView, path, pos);
+ }
+
+ assert(path);
+ gtk_tree_path_free(path);
+ gtk_tree_path_free(lastpath);
+
+ if (bAutoScroll)
+ {
+ // auto scroll if we're close to the edges
+ GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
+ double fStep = gtk_adjustment_get_step_increment(pVAdjustment);
+ if (rPos.Y() < fStep)
+ {
+ double fValue = gtk_adjustment_get_value(pVAdjustment) - fStep;
+ if (fValue < 0)
+ fValue = 0.0;
+ gtk_adjustment_set_value(pVAdjustment, fValue);
+ }
+ else
+ {
+ GdkRectangle aRect;
+ gtk_tree_view_get_visible_rect(m_pTreeView, &aRect);
+ if (rPos.Y() > aRect.height - fStep)
+ {
+ double fValue = gtk_adjustment_get_value(pVAdjustment) + fStep;
+ double fMax = gtk_adjustment_get_upper(pVAdjustment);
+ if (fValue > fMax)
+ fValue = fMax;
+ gtk_adjustment_set_value(pVAdjustment, fValue);
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ virtual void unset_drag_dest_row() override
+ {
+ gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, GTK_TREE_VIEW_DROP_BEFORE);
+ }
+
+ virtual tools::Rectangle get_row_area(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreePath* pPath = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ tools::Rectangle aRet = ::get_row_area(m_pTreeView, m_pColumns, pPath);
+ gtk_tree_path_free(pPath);
+ return aRet;
+ }
+
+ virtual void start_editing(const weld::TreeIter& rIter) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+
+ GtkTreeViewColumn* pColumn = nullptr;
+
+ for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
+ {
+ GtkTreeViewColumn* pTestColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+
+ // see if this column is editable
+ gboolean is_editable(false);
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pTestColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
+ {
+ g_object_get(pCellRenderer, "editable", &is_editable, nullptr);
+ if (is_editable)
+ {
+ pColumn = pTestColumn;
+ break;
+ }
+ }
+ }
+ g_list_free(pRenderers);
+
+ if (is_editable)
+ break;
+ }
+
+ // if nothing explicit editable, allow editing of cells which are not
+ // usually editable, so we can have double click do its usual
+ // row-activate but if we explicitly want to edit (remote files dialog)
+ // we can still do that
+ if (!pColumn)
+ {
+ pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, m_nTextView));
+ assert(pColumn && "wrong column");
+
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
+ {
+ g_object_set(pCellRenderer, "editable", true, "editable-set", true, nullptr);
+ g_object_set_data(G_OBJECT(pCellRenderer), "g-lo-RestoreNonEditable", reinterpret_cast<gpointer>(true));
+ break;
+ }
+ }
+ g_list_free(pRenderers);
+ }
+
+ gtk_tree_view_scroll_to_cell(m_pTreeView, path, pColumn, false, 0, 0);
+ gtk_tree_view_set_cursor(m_pTreeView, path, pColumn, true);
+
+ gtk_tree_path_free(path);
+ }
+
+ virtual void end_editing() override
+ {
+ GtkTreeViewColumn *focus_column = nullptr;
+ gtk_tree_view_get_cursor(m_pTreeView, nullptr, &focus_column);
+ if (focus_column)
+ gtk_cell_area_stop_editing(gtk_cell_layout_get_area(GTK_CELL_LAYOUT(focus_column)), true);
+ }
+
+ virtual TreeView* get_drag_source() const override
+ {
+ return g_DragSource;
+ }
+
+ virtual bool do_signal_drag_begin(bool& rUnsetDragIcon) override
+ {
+ if (m_aDragBeginHdl.Call(rUnsetDragIcon))
+ return true;
+ g_DragSource = this;
+ return false;
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ virtual void drag_set_icon(GtkDragSource*) override
+ {
+ }
+#else
+ virtual void drag_set_icon(GdkDragContext* context) override
+ {
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(m_pTreeView);
+ if (gtk_tree_selection_get_mode(selection) == GTK_SELECTION_MULTIPLE)
+ {
+ int nWidth = 0;
+ int nHeight = 0;
+
+ GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), nullptr);
+ std::vector<cairo_surface_t*> surfaces;
+ std::vector<int> heights;
+ for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
+ {
+ GtkTreePath* pPath = static_cast<GtkTreePath*>(pItem->data);
+
+ surfaces.push_back(gtk_tree_view_create_row_drag_icon(m_pTreeView, pPath));
+
+ double x1, x2, y1, y2;
+ cairo_t* cr = cairo_create(surfaces.back());
+ cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
+ cairo_destroy(cr);
+
+ heights.push_back(y2 - y1);
+
+ nWidth = std::max(nWidth, static_cast<int>(x2 - x1));
+ nHeight += heights.back();
+ }
+ g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
+
+ // if it's just one, then don't do anything and leave the default dnd icon as-is
+ if (surfaces.size() > 1)
+ {
+ cairo_surface_t* target = cairo_surface_create_similar(surfaces[0],
+ cairo_surface_get_content(surfaces[0]),
+ nWidth,
+ nHeight);
+
+ cairo_t* cr = cairo_create(target);
+
+ double y_pos = 0;
+ for (size_t i = 0; i < surfaces.size(); ++i)
+ {
+ cairo_set_source_surface(cr, surfaces[i], 2, y_pos + 2);
+ cairo_rectangle(cr, 0, y_pos, nWidth, heights[i]);
+ cairo_fill(cr);
+ y_pos += heights[i];
+ }
+
+ cairo_destroy(cr);
+
+ double fXScale, fYScale;
+ dl_cairo_surface_get_device_scale(target, &fXScale, &fYScale);
+ cairo_surface_set_device_offset(target,
+ - m_nPressStartX * fXScale,
+ 0);
+
+ gtk_drag_set_icon_surface(context, target);
+ cairo_surface_destroy(target);
+ }
+
+ for (auto surface : surfaces)
+ cairo_surface_destroy(surface);
+ }
+ }
+#endif
+
+ virtual void do_signal_drag_end() override
+ {
+ g_DragSource = nullptr;
+ }
+
+ // Under gtk 3.24.8 dragging into the TreeView is not highlighting
+ // entire TreeView widget, just the rectangle which has no entries
+ // in it, so as a workaround highlight the parent container
+ // on drag start, and undo it on drag end, and trigger removal
+ // of the treeview's highlight effort
+ virtual void drag_started() override
+ {
+ m_bInDrag = true;
+ GtkWidget* pWidget = GTK_WIDGET(m_pTreeView);
+ GtkWidget* pParent = gtk_widget_get_parent(pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_unset_state_flags(pWidget, GTK_STATE_FLAG_DROP_ACTIVE);
+ gtk_widget_set_state_flags(pParent, GTK_STATE_FLAG_DROP_ACTIVE, false);
+#else
+ gtk_drag_unhighlight(pWidget);
+ gtk_drag_highlight(pParent);
+#endif
+ m_bWorkAroundBadDragRegion = true;
+ }
+ }
+
+ virtual void drag_ended() override
+ {
+ m_bInDrag = false;
+ if (m_bWorkAroundBadDragRegion)
+ {
+ GtkWidget* pWidget = GTK_WIDGET(m_pTreeView);
+ GtkWidget* pParent = gtk_widget_get_parent(pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_unset_state_flags(pParent, GTK_STATE_FLAG_DROP_ACTIVE);
+#else
+ gtk_drag_unhighlight(pParent);
+#endif
+ m_bWorkAroundBadDragRegion = false;
+ }
+ // unhighlight the row
+ gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, GTK_TREE_VIEW_DROP_BEFORE);
+ }
+
+ virtual int vadjustment_get_value() const override
+ {
+ if (m_nPendingVAdjustment != -1)
+ return m_nPendingVAdjustment;
+ return gtk_adjustment_get_value(m_pVAdjustment);
+ }
+
+ virtual void vadjustment_set_value(int value) override
+ {
+ disable_notify_events();
+
+ /* This rube goldberg device is to remove flicker from setting the
+ scroll position of a GtkTreeView directly after clearing it and
+ filling it. As a specific example the writer navigator with ~100
+ tables, scroll to the end, right click on an entry near the end
+ and rename it, the tree is cleared and refilled and an attempt
+ made to set the scroll position of the freshly refilled tree to
+ the same point as before the clear.
+ */
+
+ // This forces the tree to recalculate now its preferred size
+ // after being cleared
+ GtkRequisition size;
+ gtk_widget_get_preferred_size(GTK_WIDGET(m_pTreeView), nullptr, &size);
+
+ m_nPendingVAdjustment = value;
+
+ // The value set here just has to be different to the final value
+ // set later so that isn't a no-op
+ gtk_adjustment_set_value(m_pVAdjustment, value - 0.0001);
+
+ // This will set the desired m_nPendingVAdjustment value right
+ // before the tree gets drawn
+ gtk_widget_add_tick_callback(GTK_WIDGET(m_pTreeView), setAdjustmentCallback, this, nullptr);
+
+ enable_notify_events();
+ }
+
+ void call_signal_custom_render(VirtualDevice& rOutput, const tools::Rectangle& rRect, bool bSelected, const OUString& rId)
+ {
+ signal_custom_render(rOutput, rRect, bSelected, rId);
+ }
+
+ Size call_signal_custom_get_size(VirtualDevice& rOutput, const OUString& rId)
+ {
+ return signal_custom_get_size(rOutput, rId);
+ }
+
+ virtual void set_show_expanders(bool bShow) override
+ {
+ gtk_tree_view_set_show_expanders(m_pTreeView, bShow);
+ }
+
+ virtual bool changed_by_hover() const override
+ {
+ return m_bChangedByMouse;
+ }
+
+ virtual ~GtkInstanceTreeView() override
+ {
+ if (m_pChangeEvent)
+ Application::RemoveUserEvent(m_pChangeEvent);
+ if (m_nQueryTooltipSignalId)
+ g_signal_handler_disconnect(m_pTreeView, m_nQueryTooltipSignalId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pTreeView, m_nCrossingSignalid);
+ g_signal_handler_disconnect(m_pTreeView, m_nKeyPressSignalId);
+ g_signal_handler_disconnect(m_pTreeView, m_nPopupMenuSignalId);
+#endif
+ g_signal_handler_disconnect(m_pTreeModel, m_nRowDeletedSignalId);
+ g_signal_handler_disconnect(m_pTreeModel, m_nRowInsertedSignalId);
+
+ if (m_nVAdjustmentChangedSignalId)
+ {
+ GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
+ g_signal_handler_disconnect(pVAdjustment, m_nVAdjustmentChangedSignalId);
+ }
+
+ g_signal_handler_disconnect(m_pTreeView, m_nTestCollapseRowSignalId);
+ g_signal_handler_disconnect(m_pTreeView, m_nTestExpandRowSignalId);
+ g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
+ g_signal_handler_disconnect(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
+
+ if (g_DragSource == this)
+ g_DragSource = nullptr;
+
+ GValue value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_POINTER);
+ g_value_set_pointer(&value, static_cast<gpointer>(nullptr));
+
+ for (GList* pEntry = g_list_last(m_pColumns); pEntry; pEntry = g_list_previous(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ g_signal_handler_disconnect(pColumn, m_aColumnSignalIds.back());
+ m_aColumnSignalIds.pop_back();
+
+ // unset "instance" to avoid dangling "instance" points in any CustomCellRenderers
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ if (!CUSTOM_IS_CELL_RENDERER(pCellRenderer))
+ continue;
+ g_object_set_property(G_OBJECT(pCellRenderer), "instance", &value);
+ }
+ g_list_free(pRenderers);
+ }
+ g_list_free(m_pColumns);
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(GtkInstanceTreeView, async_signal_changed, void*, void)
+{
+ m_pChangeEvent = nullptr;
+ signal_changed();
+ m_bChangedByMouse = false;
+}
+
+IMPL_LINK_NOARG(GtkInstanceTreeView, async_stop_cell_editing, void*, void)
+{
+ end_editing();
+}
+
+namespace {
+
+class GtkInstanceIconView : public GtkInstanceWidget, public virtual weld::IconView
+{
+private:
+ GtkIconView* m_pIconView;
+ GtkTreeStore* m_pTreeStore;
+ gint m_nTextCol;
+ gint m_nImageCol;
+ gint m_nIdCol;
+ gulong m_nSelectionChangedSignalId;
+ gulong m_nItemActivatedSignalId;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nPopupMenu;
+#endif
+ gulong m_nQueryTooltipSignalId = 0;
+ ImplSVEvent* m_pSelectionChangeEvent;
+
+ DECL_LINK(async_signal_selection_changed, void*, void);
+
+ bool signal_command(const CommandEvent& rCEvt)
+ {
+ return m_aCommandHdl.Call(rCEvt);
+ }
+
+ virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
+ {
+ return signal_command(rCEvt);
+ }
+
+ void launch_signal_selection_changed()
+ {
+ //tdf#117991 selection change is sent before the focus change, and focus change
+ //is what will cause a spinbutton that currently has the focus to set its contents
+ //as the spin button value. So any LibreOffice callbacks on
+ //signal-change would happen before the spinbutton value-change occurs.
+ //To avoid this, send the signal-change to LibreOffice to occur after focus-change
+ //has been processed
+ if (m_pSelectionChangeEvent)
+ Application::RemoveUserEvent(m_pSelectionChangeEvent);
+ m_pSelectionChangeEvent = Application::PostUserEvent(LINK(this, GtkInstanceIconView, async_signal_selection_changed));
+ }
+
+ static void signalSelectionChanged(GtkIconView*, gpointer widget)
+ {
+ GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
+ pThis->launch_signal_selection_changed();
+ }
+
+ void handle_item_activated()
+ {
+ if (signal_item_activated())
+ return;
+ }
+
+ static void signalItemActivated(GtkIconView*, GtkTreePath*, gpointer widget)
+ {
+ GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->handle_item_activated();
+ }
+
+ static gboolean signalQueryTooltip(GtkWidget* /*pGtkWidget*/, gint x, gint y,
+ gboolean keyboard_tip, GtkTooltip* tooltip,
+ gpointer widget)
+ {
+ GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
+ GtkTreeIter iter;
+ GtkIconView* pIconView = pThis->m_pIconView;
+ GtkTreeModel* pModel = gtk_icon_view_get_model(pIconView);
+ GtkTreePath* pPath = nullptr;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (!gtk_icon_view_get_tooltip_context(pIconView, x, y, keyboard_tip, &pModel, &pPath, &iter))
+ return false;
+#else
+ if (!gtk_icon_view_get_tooltip_context(pIconView, &x, &y, keyboard_tip, &pModel, &pPath, &iter))
+ return false;
+#endif
+ OUString aTooltip = pThis->signal_query_tooltip(GtkInstanceTreeIter(iter));
+ if (!aTooltip.isEmpty())
+ {
+ gtk_tooltip_set_text(tooltip, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
+ gtk_icon_view_set_tooltip_item(pIconView, tooltip, pPath);
+ }
+ gtk_tree_path_free(pPath);
+ return !aTooltip.isEmpty();
+ }
+
+ /* Set the item's tooltip text as its accessible description as well. */
+ void set_item_accessible_description_from_tooltip(GtkTreeIter& iter)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ (void)iter;
+#else
+ AtkObject* pAtkObject = gtk_widget_get_accessible(GTK_WIDGET(m_pIconView));
+ assert(pAtkObject);
+ GtkTreePath* pPath = gtk_tree_model_get_path(GTK_TREE_MODEL(m_pTreeStore), &iter);
+ assert(gtk_tree_path_get_depth(pPath) == 1);
+ int* indices = gtk_tree_path_get_indices(pPath);
+ const int nIndex = indices[0];
+ assert(nIndex < atk_object_get_n_accessible_children(pAtkObject)
+ && "item index too high for ItemView's accessible child count");
+
+ const OUString sTooltipText = signal_query_tooltip(GtkInstanceTreeIter(iter));
+ AtkObject* pChild = atk_object_ref_accessible_child(pAtkObject, nIndex);
+ atk_object_set_description(pChild,
+ OUStringToOString(sTooltipText, RTL_TEXTENCODING_UTF8).getStr());
+ g_object_unref(pChild);
+#endif
+ }
+
+ void insert_item(GtkTreeIter& iter, int pos, const OUString* pId, const OUString* pText, const OUString* pIconName)
+ {
+ // m_nTextCol may be -1, so pass it last, to not terminate the sequence before the Id value
+ gtk_tree_store_insert_with_values(m_pTreeStore, &iter, nullptr, pos,
+ m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
+ m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
+ -1);
+ if (pIconName)
+ {
+ GdkPixbuf* pixbuf = getPixbuf(*pIconName);
+ gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, pixbuf, -1);
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+
+ set_item_accessible_description_from_tooltip(iter);
+ }
+
+ void insert_item(GtkTreeIter& iter, int pos, const OUString* pId, const OUString* pText, const VirtualDevice* pIcon)
+ {
+ // m_nTextCol may be -1, so pass it last, to not terminate the sequence before the Id value
+ gtk_tree_store_insert_with_values(m_pTreeStore, &iter, nullptr, pos,
+ m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
+ m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
+ -1);
+ if (pIcon)
+ {
+ GdkPixbuf* pixbuf = getPixbuf(*pIcon);
+ gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, pixbuf, -1);
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+
+ set_item_accessible_description_from_tooltip(iter);
+ }
+
+ OUString get(const GtkTreeIter& iter, int col) const
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ gchar* pStr;
+ gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &pStr, -1);
+ OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pStr);
+ return sRet;
+ }
+
+ bool get_selected_iterator(GtkTreeIter* pIter) const
+ {
+ assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
+ bool bRet = false;
+ {
+ GtkTreeModel* pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
+ for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
+ {
+ if (pIter)
+ {
+ GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
+ gtk_tree_model_get_iter(pModel, pIter, path);
+ }
+ bRet = true;
+ break;
+ }
+ g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
+ }
+ return bRet;
+ }
+
+public:
+ GtkInstanceIconView(GtkIconView* pIconView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pIconView), pBuilder, bTakeOwnership)
+ , m_pIconView(pIconView)
+ , m_pTreeStore(GTK_TREE_STORE(gtk_icon_view_get_model(m_pIconView)))
+ , m_nTextCol(gtk_icon_view_get_text_column(m_pIconView)) // May be -1
+ , m_nImageCol(gtk_icon_view_get_pixbuf_column(m_pIconView))
+ , m_nSelectionChangedSignalId(g_signal_connect(pIconView, "selection-changed",
+ G_CALLBACK(signalSelectionChanged), this))
+ , m_nItemActivatedSignalId(g_signal_connect(pIconView, "item-activated", G_CALLBACK(signalItemActivated), this))
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_nPopupMenu(g_signal_connect(pIconView, "popup-menu", G_CALLBACK(signalPopupMenu), this))
+#endif
+ , m_pSelectionChangeEvent(nullptr)
+ {
+ m_nIdCol = std::max(m_nTextCol, m_nImageCol) + 1;
+ }
+
+ virtual int get_item_width() const override
+ {
+ return gtk_icon_view_get_item_width(m_pIconView);
+ }
+
+ virtual void set_item_width(int width) override
+ {
+ gtk_icon_view_set_item_width(m_pIconView, width);
+ }
+
+ virtual void insert(int pos, const OUString* pText, const OUString* pId, const OUString* pIconName, weld::TreeIter* pRet) override
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ insert_item(iter, pos, pId, pText, pIconName);
+ if (pRet)
+ {
+ GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
+ pGtkRetIter->iter = iter;
+ }
+ enable_notify_events();
+ }
+
+ virtual void insert(int pos, const OUString* pText, const OUString* pId, const VirtualDevice* pIcon, weld::TreeIter* pRet) override
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ insert_item(iter, pos, pId, pText, pIcon);
+ if (pRet)
+ {
+ GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
+ pGtkRetIter->iter = iter;
+ }
+ enable_notify_events();
+ }
+
+ virtual void insert_separator(int /* pos */, const OUString* /* pId */) override
+ {
+ // TODO: can't just copy from GtkInstanceTreeView, since there's
+ // no IconView analog for gtk_tree_view_get_row_separator_func
+ }
+
+ virtual void connect_query_tooltip(const Link<const weld::TreeIter&, OUString>& rLink) override
+ {
+ weld::IconView::connect_query_tooltip(rLink);
+ m_nQueryTooltipSignalId = g_signal_connect(m_pIconView, "query-tooltip", G_CALLBACK(signalQueryTooltip), this);
+ gtk_widget_set_has_tooltip(GTK_WIDGET(m_pIconView), true);
+ }
+
+ virtual void connect_get_property_tree_elem(const Link<const weld::json_prop_query&, bool>& /*rLink*/) override
+ {
+ //not implemented for the gtk variant
+ }
+
+ virtual OUString get_selected_id() const override
+ {
+ assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
+ GtkTreeIter iter;
+ if (get_selected_iterator(&iter))
+ return get(iter, m_nIdCol);
+ return OUString();
+ }
+
+ virtual void clear() override
+ {
+ disable_notify_events();
+ gtk_tree_store_clear(m_pTreeStore);
+ enable_notify_events();
+ }
+
+ virtual void freeze() override
+ {
+ disable_notify_events();
+ bool bIsFirstFreeze = IsFirstFreeze();
+ GtkInstanceWidget::freeze();
+ if (bIsFirstFreeze)
+ g_object_freeze_notify(G_OBJECT(m_pTreeStore));
+ enable_notify_events();
+ }
+
+ virtual void thaw() override
+ {
+ disable_notify_events();
+ if (IsLastThaw())
+ g_object_thaw_notify(G_OBJECT(m_pTreeStore));
+ GtkInstanceWidget::thaw();
+ enable_notify_events();
+ }
+
+ virtual Size get_size_request() const override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ {
+ return Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
+ gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
+ }
+ int nWidth, nHeight;
+ gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
+ return Size(nWidth, nHeight);
+ }
+
+ virtual Size get_preferred_size() const override
+ {
+ Size aRet(-1, -1);
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ {
+ aRet = Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
+ gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
+ }
+ GtkRequisition size;
+ gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
+ if (aRet.Width() == -1)
+ aRet.setWidth(size.width);
+ if (aRet.Height() == -1)
+ aRet.setHeight(size.height);
+ return aRet;
+ }
+
+ virtual void show() override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ gtk_widget_show(pParent);
+ gtk_widget_show(m_pWidget);
+ }
+
+ virtual void hide() override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ gtk_widget_hide(pParent);
+ gtk_widget_hide(m_pWidget);
+ }
+
+ virtual OUString get_selected_text() const override
+ {
+ assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
+ GtkTreeIter iter;
+ if (get_selected_iterator(&iter))
+ return get(iter, m_nTextCol);
+ return OUString();
+ }
+
+ virtual int count_selected_items() const override
+ {
+ GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
+ int nRet = g_list_length(pList);
+ g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
+ return nRet;
+ }
+
+ virtual void select(int pos) override
+ {
+ assert(gtk_icon_view_get_model(m_pIconView) && "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))
+ {
+ gtk_icon_view_unselect_all(m_pIconView);
+ }
+ else
+ {
+ GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
+ gtk_icon_view_select_path(m_pIconView, path);
+ gtk_icon_view_scroll_to_path(m_pIconView, path, false, 0, 0);
+ gtk_tree_path_free(path);
+ }
+ enable_notify_events();
+ }
+
+ virtual void unselect(int pos) override
+ {
+ assert(gtk_icon_view_get_model(m_pIconView) && "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))
+ {
+ gtk_icon_view_select_all(m_pIconView);
+ }
+ else
+ {
+ GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
+ gtk_icon_view_select_path(m_pIconView, path);
+ gtk_tree_path_free(path);
+ }
+ enable_notify_events();
+ }
+
+ virtual bool get_selected(weld::TreeIter* pIter) const override
+ {
+ GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
+ return get_selected_iterator(pGtkIter ? &pGtkIter->iter : nullptr);
+ }
+
+ virtual bool get_cursor(weld::TreeIter* pIter) const override
+ {
+ GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
+ GtkTreePath* path;
+ gtk_icon_view_get_cursor(m_pIconView, &path, nullptr);
+ if (pGtkIter && path)
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ gtk_tree_model_get_iter(pModel, &pGtkIter->iter, path);
+ }
+ return path != nullptr;
+ }
+
+ virtual void set_cursor(const weld::TreeIter& rIter) override
+ {
+ disable_notify_events();
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ gtk_icon_view_set_cursor(m_pIconView, path, nullptr, false);
+ gtk_tree_path_free(path);
+ enable_notify_events();
+ }
+
+ virtual bool get_iter_first(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ return gtk_tree_model_get_iter_first(pModel, &rGtkIter.iter);
+ }
+
+ virtual void scroll_to_item(const weld::TreeIter& rIter) override
+ {
+ assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
+ disable_notify_events();
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ gtk_icon_view_scroll_to_path(m_pIconView, path, false, 0, 0);
+ gtk_tree_path_free(path);
+ enable_notify_events();
+ }
+
+ virtual std::unique_ptr<weld::TreeIter> make_iterator(const weld::TreeIter* pOrig) const override
+ {
+ return std::unique_ptr<weld::TreeIter>(new GtkInstanceTreeIter(static_cast<const GtkInstanceTreeIter*>(pOrig)));
+ }
+
+ virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
+ {
+ GtkInstanceTreeIter aGtkIter(nullptr);
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
+ for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
+ {
+ GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
+ gtk_tree_model_get_iter(pModel, &aGtkIter.iter, path);
+ if (func(aGtkIter))
+ break;
+ }
+ g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
+ }
+
+ virtual int n_children() const override
+ {
+ return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_pTreeStore), nullptr);
+ }
+
+ virtual OUString get_id(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ return get(rGtkIter.iter, m_nIdCol);
+ }
+
+ virtual OUString get_text(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ return get(rGtkIter.iter, m_nTextCol);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pIconView, m_nSelectionChangedSignalId);
+ g_signal_handler_block(m_pIconView, m_nItemActivatedSignalId);
+
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+
+ g_signal_handler_unblock(m_pIconView, m_nItemActivatedSignalId);
+ g_signal_handler_unblock(m_pIconView, m_nSelectionChangedSignalId);
+ }
+
+ virtual ~GtkInstanceIconView() override
+ {
+ if (m_pSelectionChangeEvent)
+ Application::RemoveUserEvent(m_pSelectionChangeEvent);
+
+ if (m_nQueryTooltipSignalId)
+ g_signal_handler_disconnect(m_pIconView, m_nQueryTooltipSignalId);
+
+ g_signal_handler_disconnect(m_pIconView, m_nItemActivatedSignalId);
+ g_signal_handler_disconnect(m_pIconView, m_nSelectionChangedSignalId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pIconView, m_nPopupMenu);
+#endif
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(GtkInstanceIconView, async_signal_selection_changed, void*, void)
+{
+ m_pSelectionChangeEvent = nullptr;
+ signal_selection_changed();
+}
+
+namespace {
+
+void signalDestroyFlag(GtkWidget*, gpointer destroyed)
+{
+ bool* pDestroyed = static_cast<bool*>(destroyed);
+ *pDestroyed = true;
+}
+
+class GtkInstanceSpinButton : public GtkInstanceEditable, public virtual weld::SpinButton
+{
+private:
+ GtkSpinButton* m_pButton;
+ gulong m_nValueChangedSignalId;
+ gulong m_nOutputSignalId;
+ gulong m_nInputSignalId;
+ bool m_bFormatting;
+ bool m_bBlockOutput;
+ bool m_bBlank;
+
+ static void signalValueChanged(GtkSpinButton*, gpointer widget)
+ {
+ GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->m_bBlank = false;
+ pThis->signal_value_changed();
+ }
+
+ bool guarded_signal_output()
+ {
+ if (m_bBlockOutput)
+ return true;
+ m_bFormatting = true;
+ bool bRet = signal_output();
+ m_bFormatting = false;
+ return bRet;
+ }
+
+ static gboolean signalOutput(GtkSpinButton*, gpointer widget)
+ {
+ GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->guarded_signal_output();
+ }
+
+ static gint signalInput(GtkSpinButton*, gdouble* new_value, gpointer widget)
+ {
+ GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
+ SolarMutexGuard aGuard;
+ int result;
+ TriState eHandled = pThis->signal_input(&result);
+ if (eHandled == TRISTATE_INDET)
+ return 0;
+ if (eHandled == TRISTATE_TRUE)
+ {
+ *new_value = pThis->toGtk(result);
+ return 1;
+ }
+ return GTK_INPUT_ERROR;
+ }
+
+ virtual void signal_activate() override
+ {
+ bool bActivateDestroy(false);
+ gulong nDestroySignalId = g_signal_connect(m_pButton, "destroy", G_CALLBACK(signalDestroyFlag), &bActivateDestroy);
+ gtk_spin_button_update(m_pButton);
+ if (bActivateDestroy)
+ return;
+ g_signal_handler_disconnect(m_pButton, nDestroySignalId);
+ GtkInstanceEditable::signal_activate();
+ }
+
+ double toGtk(sal_Int64 nValue) const
+ {
+ return static_cast<double>(nValue) / Power10(get_digits());
+ }
+
+ sal_Int64 fromGtk(double fValue) const
+ {
+ return FRound(fValue * Power10(get_digits()));
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalScroll(GtkWidget* pWidget, GdkEventScroll* /*pEvent*/, gpointer /*widget*/)
+ {
+ // tdf#149823 follow WheelBehavior setting, so if we don't have focus
+ // we don't react to the scroll-event.
+ MouseWheelBehaviour nWheelBehavior(Application::GetSettings().GetMouseSettings().GetWheelBehavior());
+ switch (nWheelBehavior)
+ {
+ case MouseWheelBehaviour::ALWAYS:
+ break;
+ case MouseWheelBehaviour::Disable:
+ g_signal_stop_emission_by_name(pWidget, "scroll-event");
+ break;
+ case MouseWheelBehaviour::FocusOnly:
+ if (!gtk_widget_has_focus(pWidget))
+ g_signal_stop_emission_by_name(pWidget, "scroll-event");
+ break;
+ }
+ return false;
+ }
+#endif
+
+public:
+ GtkInstanceSpinButton(GtkSpinButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceEditable(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
+ , m_pButton(pButton)
+ , m_nValueChangedSignalId(g_signal_connect(pButton, "value-changed", G_CALLBACK(signalValueChanged), this))
+ , m_nOutputSignalId(g_signal_connect(pButton, "output", G_CALLBACK(signalOutput), this))
+ , m_nInputSignalId(g_signal_connect(pButton, "input", G_CALLBACK(signalInput), this))
+ , m_bFormatting(false)
+ , m_bBlockOutput(false)
+ , m_bBlank(false)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_text_set_activates_default(GTK_TEXT(m_pDelegate), true);
+#endif
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_connect(pButton, "scroll-event", G_CALLBACK(signalScroll), this);
+#endif
+ }
+
+ virtual sal_Int64 get_value() const override
+ {
+ return fromGtk(gtk_spin_button_get_value(m_pButton));
+ }
+
+ virtual void set_value(sal_Int64 value) override
+ {
+ disable_notify_events();
+ m_bBlank = false;
+ gtk_spin_button_set_value(m_pButton, toGtk(value));
+ enable_notify_events();
+ }
+
+ virtual void set_text(const OUString& rText) override
+ {
+ disable_notify_events();
+ // tdf#122786 if we're just formatting a value, then we're done,
+ // however if set_text has been called directly we want to update our
+ // value from this new text, but don't want to reformat with that value
+ if (!m_bFormatting)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+#else
+ gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+#endif
+
+ m_bBlockOutput = true;
+ gtk_spin_button_update(m_pButton);
+ m_bBlank = rText.isEmpty();
+ m_bBlockOutput = false;
+ }
+ else
+ {
+ bool bKeepBlank = m_bBlank && get_value() == 0;
+ if (!bKeepBlank)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+#else
+ gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+#endif
+ m_bBlank = false;
+ }
+ }
+ enable_notify_events();
+ }
+
+ virtual void set_range(sal_Int64 min, sal_Int64 max) override
+ {
+ disable_notify_events();
+ gtk_spin_button_set_range(m_pButton, toGtk(min), toGtk(max));
+ enable_notify_events();
+ }
+
+ virtual void get_range(sal_Int64& min, sal_Int64& max) const override
+ {
+ double gtkmin, gtkmax;
+ gtk_spin_button_get_range(m_pButton, &gtkmin, &gtkmax);
+ min = fromGtk(gtkmin);
+ max = fromGtk(gtkmax);
+ }
+
+ virtual void set_increments(int step, int page) override
+ {
+ disable_notify_events();
+ gtk_spin_button_set_increments(m_pButton, toGtk(step), toGtk(page));
+ enable_notify_events();
+ }
+
+ virtual void get_increments(int& step, int& page) const override
+ {
+ double gtkstep, gtkpage;
+ gtk_spin_button_get_increments(m_pButton, &gtkstep, &gtkpage);
+ step = fromGtk(gtkstep);
+ page = fromGtk(gtkpage);
+ }
+
+ virtual void set_digits(unsigned int digits) override
+ {
+ disable_notify_events();
+ gtk_spin_button_set_digits(m_pButton, digits);
+ enable_notify_events();
+ }
+
+ virtual unsigned int get_digits() const override
+ {
+ return gtk_spin_button_get_digits(m_pButton);
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_aCustomFont.use_custom_font(&rFont, u"spinbutton");
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pButton, m_nValueChangedSignalId);
+ GtkInstanceEditable::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceEditable::enable_notify_events();
+ g_signal_handler_unblock(m_pButton, m_nValueChangedSignalId);
+ }
+
+ virtual ~GtkInstanceSpinButton() override
+ {
+ g_signal_handler_disconnect(m_pButton, m_nInputSignalId);
+ g_signal_handler_disconnect(m_pButton, m_nOutputSignalId);
+ g_signal_handler_disconnect(m_pButton, m_nValueChangedSignalId);
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstanceFormattedSpinButton : public GtkInstanceEditable, public virtual weld::FormattedSpinButton
+{
+private:
+ GtkSpinButton* m_pButton;
+ std::unique_ptr<weld::EntryFormatter> m_xOwnFormatter;
+ weld::EntryFormatter* m_pFormatter;
+ gulong m_nValueChangedSignalId;
+ gulong m_nOutputSignalId;
+ gulong m_nInputSignalId;
+ bool m_bEmptyField;
+ bool m_bSyncingValue;
+ double m_dValueWhenEmpty;
+
+ bool signal_output()
+ {
+ double fValue = gtk_spin_button_get_value(m_pButton);
+ m_bEmptyField &= fValue == m_dValueWhenEmpty;
+ if (!m_bEmptyField)
+ GetFormatter().SetValue(fValue);
+ return true;
+ }
+
+ static gboolean signalOutput(GtkSpinButton*, gpointer widget)
+ {
+ GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_output();
+ }
+
+ gint signal_input(double* value)
+ {
+ Formatter& rFormatter = GetFormatter();
+ rFormatter.Modify();
+ // if the blank-mode is enabled then if the input is empty don't parse
+ // the input but keep the value as it is. store what the value the
+ // blank is associated with and until the value is changed, or the text
+ // is updated from the outside, don't output that value
+ m_bEmptyField = rFormatter.IsEmptyFieldEnabled() && get_text().isEmpty();
+ if (m_bEmptyField)
+ {
+ m_dValueWhenEmpty = gtk_spin_button_get_value(m_pButton);
+ *value = m_dValueWhenEmpty;
+ }
+ else
+ *value = rFormatter.GetValue();
+ return 1;
+ }
+
+ static gint signalInput(GtkSpinButton*, gdouble* new_value, gpointer widget)
+ {
+ GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_input(new_value);
+ }
+
+ static void signalValueChanged(GtkSpinButton*, gpointer widget)
+ {
+ GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_value_changed();
+ }
+
+public:
+ GtkInstanceFormattedSpinButton(GtkSpinButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceEditable(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
+ , m_pButton(pButton)
+ , m_pFormatter(nullptr)
+ , m_nValueChangedSignalId(g_signal_connect(pButton, "value-changed", G_CALLBACK(signalValueChanged), this))
+ , m_nOutputSignalId(g_signal_connect(pButton, "output", G_CALLBACK(signalOutput), this))
+ , m_nInputSignalId(g_signal_connect(pButton, "input", G_CALLBACK(signalInput), this))
+ , m_bEmptyField(false)
+ , m_bSyncingValue(false)
+ , m_dValueWhenEmpty(0.0)
+ {
+ }
+
+ virtual void set_text(const OUString& rText) override
+ {
+ GtkInstanceEditable::set_text(rText);
+ Formatter& rFormatter = GetFormatter();
+ m_bEmptyField = rFormatter.IsEmptyFieldEnabled() && rText.isEmpty();
+ if (m_bEmptyField)
+ m_dValueWhenEmpty = gtk_spin_button_get_value(m_pButton);
+ }
+
+ virtual void connect_changed(const Link<weld::Entry&, void>& rLink) override
+ {
+ if (!m_pFormatter) // once a formatter is set, it takes over "changed"
+ {
+ GtkInstanceEditable::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"
+ {
+ GtkInstanceEditable::connect_focus_out(rLink);
+ return;
+ }
+ m_pFormatter->connect_focus_out(rLink);
+ }
+
+ virtual void SetFormatter(weld::EntryFormatter* pFormatter) override
+ {
+ m_xOwnFormatter.reset();
+ m_pFormatter = pFormatter;
+ sync_range_from_formatter();
+ sync_value_from_formatter();
+ sync_increments_from_formatter();
+ }
+
+ virtual weld::EntryFormatter& GetFormatter() override
+ {
+ if (!m_pFormatter)
+ {
+ auto aFocusOutHdl = m_aFocusOutHdl;
+ m_aFocusOutHdl = Link<weld::Widget&, void>();
+ auto aChangeHdl = m_aChangeHdl;
+ m_aChangeHdl = Link<weld::Entry&, void>();
+
+ double fValue = gtk_spin_button_get_value(m_pButton);
+ double fMin, fMax;
+ gtk_spin_button_get_range(m_pButton, &fMin, &fMax);
+ double fStep;
+ gtk_spin_button_get_increments(m_pButton, &fStep, nullptr);
+ m_xOwnFormatter.reset(new weld::EntryFormatter(*this));
+ m_xOwnFormatter->SetMinValue(fMin);
+ m_xOwnFormatter->SetMaxValue(fMax);
+ m_xOwnFormatter->SetSpinSize(fStep);
+ m_xOwnFormatter->SetValue(fValue);
+
+ m_xOwnFormatter->connect_focus_out(aFocusOutHdl);
+ m_xOwnFormatter->connect_changed(aChangeHdl);
+
+ m_pFormatter = m_xOwnFormatter.get();
+ }
+ return *m_pFormatter;
+ }
+
+ virtual void sync_value_from_formatter() override
+ {
+ if (!m_pFormatter)
+ return;
+ // tdf#135317 avoid reenterence
+ if (m_bSyncingValue)
+ return;
+ m_bSyncingValue = true;
+ disable_notify_events();
+ // tdf#138519 use gtk_adjustment_set_value instead of gtk_spin_button_set_value because the
+ // latter doesn't change the value if the new value is less than an EPSILON diff of 1e-10
+ // from the old value
+ gtk_adjustment_set_value(gtk_spin_button_get_adjustment(m_pButton), m_pFormatter->GetValue());
+ enable_notify_events();
+ m_bSyncingValue = false;
+ }
+
+ virtual void sync_range_from_formatter() override
+ {
+ if (!m_pFormatter)
+ return;
+ disable_notify_events();
+ double fMin = m_pFormatter->HasMinValue() ? m_pFormatter->GetMinValue() : std::numeric_limits<double>::lowest();
+ double fMax = m_pFormatter->HasMaxValue() ? m_pFormatter->GetMaxValue() : std::numeric_limits<double>::max();
+ gtk_spin_button_set_range(m_pButton, fMin, fMax);
+ enable_notify_events();
+ }
+
+ virtual void sync_increments_from_formatter() override
+ {
+ if (!m_pFormatter)
+ return;
+ disable_notify_events();
+ double fSpinSize = m_pFormatter->GetSpinSize();
+ gtk_spin_button_set_increments(m_pButton, fSpinSize, fSpinSize * 10);
+ enable_notify_events();
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_aCustomFont.use_custom_font(&rFont, u"spinbutton");
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pButton, m_nValueChangedSignalId);
+ GtkInstanceEditable::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceEditable::enable_notify_events();
+ g_signal_handler_unblock(m_pButton, m_nValueChangedSignalId);
+ }
+
+ virtual ~GtkInstanceFormattedSpinButton() override
+ {
+ g_signal_handler_disconnect(m_pButton, m_nInputSignalId);
+ g_signal_handler_disconnect(m_pButton, m_nOutputSignalId);
+ g_signal_handler_disconnect(m_pButton, m_nValueChangedSignalId);
+
+ m_pFormatter = nullptr;
+ m_xOwnFormatter.reset();
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstanceLabel : public GtkInstanceWidget, public virtual weld::Label
+{
+private:
+ GtkLabel* m_pLabel;
+
+ void set_text_background_color(const Color& rColor)
+ {
+ guint16 nRed = rColor.GetRed() << 8;
+ guint16 nGreen = rColor.GetGreen() << 8;
+ guint16 nBlue = rColor.GetBlue() << 8;
+
+ PangoAttrType aFilterAttrs[] = {PANGO_ATTR_BACKGROUND, PANGO_ATTR_INVALID};
+
+ PangoAttrList* pOrigList = gtk_label_get_attributes(m_pLabel);
+ PangoAttrList* pAttrs = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
+ PangoAttrList* pRemovedAttrs = pOrigList ? pango_attr_list_filter(pAttrs, filter_pango_attrs, &aFilterAttrs) : nullptr;
+ pango_attr_list_insert(pAttrs, pango_attr_background_new(nRed, nGreen, nBlue));
+ gtk_label_set_attributes(m_pLabel, pAttrs);
+ pango_attr_list_unref(pAttrs);
+ pango_attr_list_unref(pRemovedAttrs);
+ }
+
+ void set_text_foreground_color(const Color& rColor, bool bSetBold)
+ {
+ guint16 nRed = rColor.GetRed() << 8;
+ guint16 nGreen = rColor.GetGreen() << 8;
+ guint16 nBlue = rColor.GetBlue() << 8;
+
+ PangoAttrType aFilterAttrs[] = {PANGO_ATTR_FOREGROUND, PANGO_ATTR_WEIGHT, PANGO_ATTR_INVALID};
+
+ if (!bSetBold)
+ aFilterAttrs[1] = PANGO_ATTR_INVALID;
+
+ PangoAttrList* pOrigList = gtk_label_get_attributes(m_pLabel);
+ PangoAttrList* pAttrs = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
+ PangoAttrList* pRemovedAttrs = pOrigList ? pango_attr_list_filter(pAttrs, filter_pango_attrs, &aFilterAttrs) : nullptr;
+ if (rColor != COL_AUTO)
+ pango_attr_list_insert(pAttrs, pango_attr_foreground_new(nRed, nGreen, nBlue));
+ if (bSetBold)
+ pango_attr_list_insert(pAttrs, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
+ gtk_label_set_attributes(m_pLabel, pAttrs);
+ pango_attr_list_unref(pAttrs);
+ pango_attr_list_unref(pRemovedAttrs);
+ }
+
+public:
+ GtkInstanceLabel(GtkLabel* pLabel, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pLabel), pBuilder, bTakeOwnership)
+ , m_pLabel(pLabel)
+ {
+ }
+
+ virtual void set_label(const OUString& rText) override
+ {
+ ::set_label(m_pLabel, rText);
+ }
+
+ virtual OUString get_label() const override
+ {
+ return ::get_label(m_pLabel);
+ }
+
+ virtual void set_mnemonic_widget(Widget* pTarget) override
+ {
+ assert(!gtk_label_get_selectable(m_pLabel) && "don't use set_mnemonic_widget on selectable labels, for consistency with gen backend");
+ GtkInstanceWidget* pTargetWidget = dynamic_cast<GtkInstanceWidget*>(pTarget);
+ gtk_label_set_mnemonic_widget(m_pLabel, pTargetWidget ? pTargetWidget->getWidget() : nullptr);
+ }
+
+ virtual void set_label_type(weld::LabelType eType) override
+ {
+ switch (eType)
+ {
+ case weld::LabelType::Normal:
+ gtk_label_set_attributes(m_pLabel, nullptr);
+ break;
+ case weld::LabelType::Warning:
+ set_text_background_color(Application::GetSettings().GetStyleSettings().GetWarningColor());
+ break;
+ case weld::LabelType::Error:
+ set_text_background_color(Application::GetSettings().GetStyleSettings().GetHighlightColor());
+ break;
+ case weld::LabelType::Title:
+ set_text_foreground_color(Application::GetSettings().GetStyleSettings().GetLightColor(), true);
+ break;
+ }
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ ::set_font(m_pLabel, rFont);
+ }
+
+ virtual void set_font_color(const Color& rColor) override
+ {
+ set_text_foreground_color(rColor, false);
+ }
+};
+
+}
+
+std::unique_ptr<weld::Label> GtkInstanceFrame::weld_label_widget() const
+{
+ GtkWidget* pLabel = gtk_frame_get_label_widget(m_pFrame);
+ if (!pLabel || !GTK_IS_LABEL(pLabel))
+ return nullptr;
+ return std::make_unique<GtkInstanceLabel>(GTK_LABEL(pLabel), m_pBuilder, false);
+}
+
+namespace {
+
+GdkClipboard* widget_get_clipboard(GtkWidget* pWidget)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_widget_get_clipboard(pWidget);
+#else
+ return gtk_widget_get_clipboard(pWidget, GDK_SELECTION_CLIPBOARD);
+#endif
+}
+
+class GtkInstanceTextView : public GtkInstanceWidget, public virtual weld::TextView
+{
+private:
+ GtkTextView* m_pTextView;
+ GtkTextBuffer* m_pTextBuffer;
+ GtkAdjustment* m_pVAdjustment;
+ GtkCssProvider* m_pFgCssProvider;
+ WidgetFont m_aCustomFont;
+ int m_nMaxTextLength;
+ gulong m_nChangedSignalId; // we don't disable/enable this one, it's to implement max-length
+ gulong m_nInsertTextSignalId;
+ gulong m_nCursorPosSignalId;
+ gulong m_nHasSelectionSignalId; // we don't disable/enable this one, it's to implement
+ // auto-scroll to cursor on losing selection
+ gulong m_nVAdjustChangedSignalId;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nButtonPressEvent; // we don't disable/enable this one, it's to block mouse
+ // click down from getting to (potential) toplevel
+ // GtkSalFrame parent, which grabs focus away
+
+ static gboolean signalButtonPressEvent(GtkWidget*, GdkEventButton*, gpointer)
+ {
+ // e.g. on clicking on the help TextView in OTableDesignHelpBar the currently displayed text shouldn't disappear
+ return true;
+ }
+#endif
+
+ static void signalChanged(GtkTextBuffer*, gpointer widget)
+ {
+ GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_changed();
+ }
+
+ static void signalInserText(GtkTextBuffer *pBuffer, GtkTextIter *pLocation, gchar* /*pText*/, gint /*nLen*/, gpointer widget)
+ {
+ GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
+ pThis->insert_text(pBuffer, pLocation);
+ }
+
+ void insert_text(GtkTextBuffer *pBuffer, GtkTextIter *pLocation)
+ {
+ if (m_nMaxTextLength)
+ {
+ gint nCount = gtk_text_buffer_get_char_count(pBuffer);
+ if (nCount > m_nMaxTextLength)
+ {
+ GtkTextIter nStart, nEnd;
+ gtk_text_buffer_get_iter_at_offset(m_pTextBuffer, &nStart, m_nMaxTextLength);
+ gtk_text_buffer_get_end_iter(m_pTextBuffer, &nEnd);
+ gtk_text_buffer_delete(m_pTextBuffer, &nStart, &nEnd);
+ gtk_text_iter_assign(pLocation, &nStart);
+ }
+ }
+ }
+
+ static void signalCursorPosition(GtkTextBuffer*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
+ pThis->signal_cursor_position();
+ }
+
+ static void signalHasSelection(GtkTextBuffer*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
+ pThis->signal_has_selection();
+ }
+
+ void signal_has_selection()
+ {
+ /*
+ in the data browser (Data Sources, shift+ctrl+f4), entering a
+ multiline cell selects all, on cursoring to the right, the selection
+ is lost and the cursor is at the end but gtk doesn't auto-scroll to
+ the cursor so if the text needs scrolling to see the cursor it is off
+ screen, another cursor makes gtk auto-scroll as wanted. So on losing
+ selection help gtk out and do the initial scroll ourselves here
+ */
+ if (!gtk_text_buffer_get_has_selection(m_pTextBuffer))
+ {
+ GtkTextMark* pMark = gtk_text_buffer_get_insert(m_pTextBuffer);
+ gtk_text_view_scroll_mark_onscreen(m_pTextView, pMark);
+ }
+ }
+
+ static void signalVAdjustValueChanged(GtkAdjustment*, gpointer widget)
+ {
+ GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_vadjustment_changed();
+ }
+
+public:
+ GtkInstanceTextView(GtkTextView* pTextView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pTextView), pBuilder, bTakeOwnership)
+ , m_pTextView(pTextView)
+ , m_pTextBuffer(gtk_text_view_get_buffer(pTextView))
+ , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTextView)))
+ , m_pFgCssProvider(nullptr)
+ , m_aCustomFont(m_pWidget)
+ , m_nMaxTextLength(0)
+ , m_nChangedSignalId(g_signal_connect(m_pTextBuffer, "changed", G_CALLBACK(signalChanged), this))
+ , m_nInsertTextSignalId(g_signal_connect_after(m_pTextBuffer, "insert-text", G_CALLBACK(signalInserText), this))
+ , m_nCursorPosSignalId(g_signal_connect(m_pTextBuffer, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
+ , m_nHasSelectionSignalId(g_signal_connect(m_pTextBuffer, "notify::has-selection", G_CALLBACK(signalHasSelection), this))
+ , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustValueChanged), this))
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_nButtonPressEvent(g_signal_connect_after(m_pTextView, "button-press-event", G_CALLBACK(signalButtonPressEvent), this))
+#endif
+ {
+ }
+
+ virtual void set_size_request(int nWidth, int nHeight) override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ {
+ gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(pParent), nWidth);
+ gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(pParent), nHeight);
+ return;
+ }
+ gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
+ }
+
+ virtual void set_text(const OUString& rText) override
+ {
+ disable_notify_events();
+ OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gtk_text_buffer_set_text(m_pTextBuffer, sText.getStr(), sText.getLength());
+ enable_notify_events();
+ }
+
+ virtual OUString get_text() const override
+ {
+ GtkTextIter start, end;
+ gtk_text_buffer_get_bounds(m_pTextBuffer, &start, &end);
+ char* pStr = gtk_text_buffer_get_text(m_pTextBuffer, &start, &end, true);
+ OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pStr);
+ return sRet;
+ }
+
+ virtual void replace_selection(const OUString& rText) override
+ {
+ disable_notify_events();
+ gtk_text_buffer_delete_selection(m_pTextBuffer, false, gtk_text_view_get_editable(m_pTextView));
+ OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gtk_text_buffer_insert_at_cursor(m_pTextBuffer, sText.getStr(), sText.getLength());
+ enable_notify_events();
+ }
+
+ virtual bool get_selection_bounds(int& rStartPos, int& rEndPos) override
+ {
+ GtkTextIter start, end;
+ gtk_text_buffer_get_selection_bounds(m_pTextBuffer, &start, &end);
+ rStartPos = gtk_text_iter_get_offset(&start);
+ rEndPos = gtk_text_iter_get_offset(&end);
+ return rStartPos != rEndPos;
+ }
+
+ virtual void select_region(int nStartPos, int nEndPos) override
+ {
+ disable_notify_events();
+ GtkTextIter start, end;
+ gtk_text_buffer_get_iter_at_offset(m_pTextBuffer, &start, nStartPos);
+ gtk_text_buffer_get_iter_at_offset(m_pTextBuffer, &end, nEndPos);
+ gtk_text_buffer_select_range(m_pTextBuffer, &start, &end);
+ GtkTextMark* mark = gtk_text_buffer_create_mark(m_pTextBuffer, "scroll", &end, true);
+ gtk_text_view_scroll_mark_onscreen(m_pTextView, mark);
+ enable_notify_events();
+ }
+
+ virtual void set_editable(bool bEditable) override
+ {
+ gtk_text_view_set_editable(m_pTextView, bEditable);
+ }
+
+ virtual bool get_editable() const override
+ {
+ return gtk_text_view_get_editable(m_pTextView);
+ }
+
+ virtual void set_max_length(int nChars) override
+ {
+ m_nMaxTextLength = nChars;
+ }
+
+ virtual void set_monospace(bool bMonospace) override
+ {
+ gtk_text_view_set_monospace(m_pTextView, bMonospace);
+ }
+
+ virtual void set_font_color(const Color& rColor) override
+ {
+ const bool bRemoveColor = rColor == COL_AUTO;
+ if (bRemoveColor && !m_pFgCssProvider)
+ return;
+ GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(GTK_WIDGET(m_pTextView));
+ if (m_pFgCssProvider)
+ {
+ gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFgCssProvider));
+ m_pFgCssProvider = nullptr;
+ }
+ if (bRemoveColor)
+ return;
+ OUString sColor = rColor.AsRGBHexString();
+ m_pFgCssProvider = gtk_css_provider_new();
+ OUString aBuffer = "textview text { color: #" + sColor + "; }";
+ OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
+ css_provider_load_from_data(m_pFgCssProvider, aResult.getStr(), aResult.getLength());
+ gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFgCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_aCustomFont.use_custom_font(&rFont, u"textview");
+ }
+
+ virtual vcl::Font get_font() override
+ {
+ if (const vcl::Font* pFont = m_aCustomFont.get_custom_font())
+ return *pFont;
+ return GtkInstanceWidget::get_font();
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pVAdjustment, m_nVAdjustChangedSignalId);
+ g_signal_handler_block(m_pTextBuffer, m_nCursorPosSignalId);
+ g_signal_handler_block(m_pTextBuffer, m_nChangedSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pTextBuffer, m_nChangedSignalId);
+ g_signal_handler_unblock(m_pTextBuffer, m_nCursorPosSignalId);
+ g_signal_handler_unblock(m_pVAdjustment, m_nVAdjustChangedSignalId);
+ }
+
+ // in gtk, 'up' when on the first line, will jump to the start of the line
+ // if not there already
+ virtual bool can_move_cursor_with_up() const override
+ {
+ GtkTextIter start, end;
+ gtk_text_buffer_get_selection_bounds(m_pTextBuffer, &start, &end);
+ return !gtk_text_iter_equal(&start, &end) || !gtk_text_iter_is_start(&start);
+ }
+
+ // in gtk, 'down' when on the first line, will jump to the end of the line
+ // if not there already
+ virtual bool can_move_cursor_with_down() const override
+ {
+ GtkTextIter start, end;
+ gtk_text_buffer_get_selection_bounds(m_pTextBuffer, &start, &end);
+ return !gtk_text_iter_equal(&start, &end) || !gtk_text_iter_is_end(&end);
+ }
+
+ virtual void cut_clipboard() override
+ {
+ GdkClipboard *pClipboard = widget_get_clipboard(GTK_WIDGET(m_pTextView));
+ gtk_text_buffer_cut_clipboard(m_pTextBuffer, pClipboard, get_editable());
+ }
+
+ virtual void copy_clipboard() override
+ {
+ GdkClipboard *pClipboard = widget_get_clipboard(GTK_WIDGET(m_pTextView));
+ gtk_text_buffer_copy_clipboard(m_pTextBuffer, pClipboard);
+ }
+
+ virtual void paste_clipboard() override
+ {
+ GdkClipboard *pClipboard = widget_get_clipboard(GTK_WIDGET(m_pTextView));
+ gtk_text_buffer_paste_clipboard(m_pTextBuffer, pClipboard, nullptr, get_editable());
+ }
+
+ virtual void set_alignment(TxtAlign eXAlign) override
+ {
+ GtkJustification eJust = GTK_JUSTIFY_LEFT;
+ switch (eXAlign)
+ {
+ case TxtAlign::Left:
+ eJust = GTK_JUSTIFY_LEFT;
+ break;
+ case TxtAlign::Center:
+ eJust = GTK_JUSTIFY_CENTER;
+ break;
+ case TxtAlign::Right:
+ eJust = GTK_JUSTIFY_RIGHT;
+ break;
+ }
+ gtk_text_view_set_justification(m_pTextView, eJust);
+ }
+
+ virtual int vadjustment_get_value() const override
+ {
+ return gtk_adjustment_get_value(m_pVAdjustment);
+ }
+
+ virtual void vadjustment_set_value(int value) override
+ {
+ disable_notify_events();
+ gtk_adjustment_set_value(m_pVAdjustment, value);
+ enable_notify_events();
+ }
+
+ virtual int vadjustment_get_upper() const override
+ {
+ return gtk_adjustment_get_upper(m_pVAdjustment);
+ }
+
+ virtual int vadjustment_get_lower() const override
+ {
+ return gtk_adjustment_get_lower(m_pVAdjustment);
+ }
+
+ virtual int vadjustment_get_page_size() const override
+ {
+ return gtk_adjustment_get_page_size(m_pVAdjustment);
+ }
+
+ virtual void show() override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ gtk_widget_show(pParent);
+ gtk_widget_show(m_pWidget);
+ }
+
+ virtual void hide() override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ gtk_widget_hide(pParent);
+ gtk_widget_hide(m_pWidget);
+ }
+
+ virtual ~GtkInstanceTextView() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pTextView, m_nButtonPressEvent);
+#endif
+ g_signal_handler_disconnect(m_pVAdjustment, m_nVAdjustChangedSignalId);
+ g_signal_handler_disconnect(m_pTextBuffer, m_nInsertTextSignalId);
+ g_signal_handler_disconnect(m_pTextBuffer, m_nChangedSignalId);
+ g_signal_handler_disconnect(m_pTextBuffer, m_nCursorPosSignalId);
+ g_signal_handler_disconnect(m_pTextBuffer, m_nHasSelectionSignalId);
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstanceDrawingArea;
+
+// IMHandler
+class IMHandler
+{
+private:
+ GtkInstanceDrawingArea* m_pArea;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkEventController* m_pFocusController;
+#endif
+ GtkIMContext* m_pIMContext;
+ OUString m_sPreeditText;
+ gulong m_nFocusInSignalId;
+ gulong m_nFocusOutSignalId;
+ bool m_bExtTextInput;
+
+public:
+ IMHandler(GtkInstanceDrawingArea* pArea);
+
+ void signalFocus(bool bIn);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalFocusIn(GtkEventControllerFocus*, gpointer im_handler);
+#else
+ static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer im_handler);
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalFocusOut(GtkEventControllerFocus*, gpointer im_handler);
+#else
+ static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer im_handler);
+#endif
+
+ ~IMHandler();
+
+ void updateIMSpotLocation();
+
+ void set_cursor_location(const tools::Rectangle& rRect);
+
+ static void signalIMCommit(GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler);
+
+ static void signalIMPreeditChanged(GtkIMContext* pIMContext, gpointer im_handler);
+
+ static gboolean signalIMRetrieveSurrounding(GtkIMContext* pContext, gpointer im_handler);
+
+ static gboolean signalIMDeleteSurrounding(GtkIMContext*, gint nOffset, gint nChars,
+ gpointer im_handler);
+
+ void StartExtTextInput();
+
+ static void signalIMPreeditStart(GtkIMContext*, gpointer im_handler);
+
+ void EndExtTextInput();
+
+ static void signalIMPreeditEnd(GtkIMContext*, gpointer im_handler);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool im_context_filter_keypress(const GdkEventKey* pEvent);
+#endif
+};
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+AtkObject* (*default_drawing_area_get_accessible)(GtkWidget *widget);
+#endif
+
+class GtkInstanceDrawingArea : public GtkInstanceWidget, public virtual weld::DrawingArea
+{
+private:
+ GtkDrawingArea* m_pDrawingArea;
+ a11yref m_xAccessible;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ AtkObject *m_pAccessible;
+#endif
+ ScopedVclPtrInstance<VirtualDevice> m_xDevice;
+ std::unique_ptr<IMHandler> m_xIMHandler;
+ cairo_surface_t* m_pSurface;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nDrawSignalId;
+#endif
+ gulong m_nQueryTooltip;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nPopupMenu;
+ gulong m_nScrollEvent;
+#endif
+ GtkGesture *m_pZoomGesture;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalDraw(GtkDrawingArea*, cairo_t *cr, int /*width*/, int /*height*/, gpointer widget)
+#else
+ static gboolean signalDraw(GtkWidget*, cairo_t* cr, gpointer widget)
+#endif
+ {
+ GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_draw(cr);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return false;
+#endif
+ }
+ void signal_draw(cairo_t* cr)
+ {
+ if (!m_pSurface)
+ return;
+
+ GdkRectangle rect;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ double clip_x1, clip_x2, clip_y1, clip_y2;
+ cairo_clip_extents(cr, &clip_x1, &clip_y1, &clip_x2, &clip_y2);
+ rect.x = clip_x1;
+ rect.y = clip_y1;
+ rect.width = clip_x2 - clip_x1;
+ rect.height = clip_y2 - clip_y1;
+ if (rect.width <= 0 || rect.height <= 0)
+ return;
+#else
+ if (!gdk_cairo_get_clip_rectangle(cr, &rect))
+ return;
+#endif
+
+ tools::Rectangle aRect(Point(rect.x, rect.y), Size(rect.width, rect.height));
+ aRect = m_xDevice->PixelToLogic(aRect);
+ m_xDevice->Erase(aRect);
+ m_aDrawHdl.Call(std::pair<vcl::RenderContext&, const tools::Rectangle&>(*m_xDevice, aRect));
+ cairo_surface_mark_dirty(m_pSurface);
+
+ cairo_set_source_surface(cr, m_pSurface, 0, 0);
+ cairo_paint(cr);
+
+ tools::Rectangle aFocusRect(m_aGetFocusRectHdl.Call(*this));
+ if (!aFocusRect.IsEmpty())
+ {
+ gtk_render_focus(gtk_widget_get_style_context(GTK_WIDGET(m_pDrawingArea)), cr,
+ aFocusRect.Left(), aFocusRect.Top(), aFocusRect.GetWidth(), aFocusRect.GetHeight());
+ }
+ }
+ virtual void signal_size_allocate(guint nWidth, guint nHeight) override
+ {
+ Size aNewSize(nWidth, nHeight);
+ if (m_pSurface && aNewSize == m_xDevice->GetOutputSizePixel())
+ {
+ // unchanged
+ return;
+ }
+ m_xDevice->SetOutputSizePixel(Size(nWidth, nHeight));
+ m_pSurface = get_underlying_cairo_surface(*m_xDevice);
+ GtkInstanceWidget::signal_size_allocate(nWidth, nHeight);
+ }
+ static gboolean signalQueryTooltip(GtkWidget* pGtkWidget, gint x, gint y,
+ gboolean /*keyboard_mode*/, GtkTooltip *tooltip,
+ gpointer widget)
+ {
+ GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
+ tools::Rectangle aHelpArea(x, y);
+ OUString aTooltip = pThis->signal_query_tooltip(aHelpArea);
+ if (aTooltip.isEmpty())
+ return false;
+ gtk_tooltip_set_text(tooltip, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
+ GdkRectangle aGdkHelpArea;
+ aGdkHelpArea.x = aHelpArea.Left();
+ aGdkHelpArea.y = aHelpArea.Top();
+ aGdkHelpArea.width = aHelpArea.GetWidth();
+ aGdkHelpArea.height = aHelpArea.GetHeight();
+ if (pThis->SwapForRTL())
+ aGdkHelpArea.x = gtk_widget_get_allocated_width(pGtkWidget) - aGdkHelpArea.width - 1 - aGdkHelpArea.x;
+ gtk_tooltip_set_tip_area(tooltip, &aGdkHelpArea);
+ return true;
+ }
+ virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
+ {
+ return signal_command(rCEvt);
+ }
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool signal_scroll(const GdkEventScroll* pEvent)
+ {
+ SalWheelMouseEvent aEvt(GtkSalFrame::GetWheelEvent(*pEvent));
+
+ if (SwapForRTL())
+ aEvt.mnX = gtk_widget_get_allocated_width(m_pWidget) - 1 - aEvt.mnX;
+
+ CommandWheelMode nMode;
+ sal_uInt16 nCode = aEvt.mnCode;
+ bool bHorz = aEvt.mbHorz;
+ 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;
+ }
+
+ CommandWheelData aWheelData(aEvt.mnDelta, aEvt.mnNotchDelta, aEvt.mnScrollLines,
+ nMode, nCode, bHorz, aEvt.mbDeltaIsPixel);
+ CommandEvent aCEvt(Point(aEvt.mnX, aEvt.mnY), CommandEventId::Wheel, true, &aWheelData);
+ return m_aCommandHdl.Call(aCEvt);
+ }
+ static gboolean signalScroll(GtkWidget*, GdkEventScroll* pEvent, gpointer widget)
+ {
+ GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
+ return pThis->signal_scroll(pEvent);
+ }
+#endif
+
+ bool handleSignalZoom(GtkGesture* gesture, GdkEventSequence* sequence,
+ GestureEventZoomType eEventType)
+ {
+ gdouble x = 0;
+ gdouble y = 0;
+ gtk_gesture_get_point(gesture, sequence, &x, &y);
+
+ double fScaleDelta = gtk_gesture_zoom_get_scale_delta(GTK_GESTURE_ZOOM(gesture));
+
+ CommandGestureZoomData aGestureData(x, y, eEventType, fScaleDelta);
+ CommandEvent aCEvt(Point(x, y), CommandEventId::GestureZoom, true, &aGestureData);
+ return m_aCommandHdl.Call(aCEvt);
+ }
+
+ static bool signalZoomBegin(GtkGesture* gesture, GdkEventSequence* sequence, gpointer widget)
+ {
+ GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
+ return pThis->handleSignalZoom(gesture, sequence, GestureEventZoomType::Begin);
+ }
+
+ static bool signalZoomUpdate(GtkGesture* gesture, GdkEventSequence* sequence, gpointer widget)
+ {
+ GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
+ return pThis->handleSignalZoom(gesture, sequence, GestureEventZoomType::Update);
+ }
+
+ static bool signalZoomEnd(GtkGesture* gesture, GdkEventSequence* sequence, gpointer widget)
+ {
+ GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
+ return pThis->handleSignalZoom(gesture, sequence, GestureEventZoomType::End);
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalResize(GtkDrawingArea*, int nWidth, int nHeight, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_size_allocate(nWidth, nHeight);
+ }
+#endif
+
+public:
+ GtkInstanceDrawingArea(GtkDrawingArea* pDrawingArea, GtkInstanceBuilder* pBuilder, a11yref xA11y, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pDrawingArea), pBuilder, bTakeOwnership)
+ , m_pDrawingArea(pDrawingArea)
+ , m_xAccessible(std::move(xA11y))
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_pAccessible(nullptr)
+#endif
+ , m_xDevice(DeviceFormat::WITHOUT_ALPHA)
+ , m_pSurface(nullptr)
+ , m_nQueryTooltip(g_signal_connect(m_pDrawingArea, "query-tooltip", G_CALLBACK(signalQueryTooltip), this))
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_nPopupMenu(g_signal_connect(m_pDrawingArea, "popup-menu", G_CALLBACK(signalPopupMenu), this))
+ , m_nScrollEvent(g_signal_connect(m_pDrawingArea, "scroll-event", G_CALLBACK(signalScroll), this))
+#endif
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_drawing_area_set_draw_func(m_pDrawingArea, signalDraw, this, nullptr);
+#else
+ m_nDrawSignalId = g_signal_connect(m_pDrawingArea, "draw", G_CALLBACK(signalDraw), this);
+ gtk_widget_add_events(GTK_WIDGET(pDrawingArea), GDK_TOUCHPAD_GESTURE_MASK);
+#endif
+
+ ensureMouseEventWidget();
+#if GTK_CHECK_VERSION(4,0,0)
+ m_pZoomGesture = gtk_gesture_zoom_new();
+ gtk_widget_add_controller(m_pMouseEventBox, GTK_EVENT_CONTROLLER(m_pZoomGesture));
+#else
+ m_pZoomGesture = gtk_gesture_zoom_new(m_pMouseEventBox);
+#endif
+ gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(m_pZoomGesture),
+ GTK_PHASE_TARGET);
+ // Note that the default zoom gesture signal handler needs to run first to setup correct
+ // scale delta. Otherwise the first "begin" event will always contain scale delta of infinity.
+ g_signal_connect_after(m_pZoomGesture, "begin", G_CALLBACK(signalZoomBegin), this);
+ g_signal_connect_after(m_pZoomGesture, "update", G_CALLBACK(signalZoomUpdate), this);
+ g_signal_connect_after(m_pZoomGesture, "end", G_CALLBACK(signalZoomEnd), this);
+
+ gtk_widget_set_has_tooltip(m_pWidget, true);
+ g_object_set_data(G_OBJECT(m_pDrawingArea), "g-lo-GtkInstanceDrawingArea", this);
+ m_xDevice->EnableRTL(get_direction());
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ AtkObject* GetAtkObject(AtkObject* pDefaultAccessible)
+ {
+ if (!m_pAccessible && m_xAccessible.is())
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ m_pAccessible = atk_object_wrapper_new(m_xAccessible, gtk_widget_get_accessible(pParent), pDefaultAccessible);
+ if (m_pAccessible)
+ g_object_ref(m_pAccessible);
+ }
+ return m_pAccessible;
+ }
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ virtual void connect_size_allocate(const Link<const Size&, void>& rLink) override
+ {
+ m_nSizeAllocateSignalId = g_signal_connect(m_pWidget, "resize", G_CALLBACK(signalResize), this);
+ weld::Widget::connect_size_allocate(rLink);
+ }
+#endif
+
+ virtual void connect_mouse_press(const Link<const MouseEvent&, bool>& rLink) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!(gtk_widget_get_events(m_pWidget) & GDK_BUTTON_PRESS_MASK))
+ gtk_widget_add_events(m_pWidget, GDK_BUTTON_PRESS_MASK);
+#endif
+ GtkInstanceWidget::connect_mouse_press(rLink);
+ }
+
+ virtual void connect_mouse_release(const Link<const MouseEvent&, bool>& rLink) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!(gtk_widget_get_events(m_pWidget) & GDK_BUTTON_RELEASE_MASK))
+ gtk_widget_add_events(m_pWidget, GDK_BUTTON_RELEASE_MASK);
+#endif
+ GtkInstanceWidget::connect_mouse_release(rLink);
+ }
+
+ virtual void set_direction(bool bRTL) override
+ {
+ GtkInstanceWidget::set_direction(bRTL);
+ m_xDevice->EnableRTL(bRTL);
+ }
+
+ virtual void set_cursor(PointerStyle ePointerStyle) override
+ {
+ GdkCursor *pCursor = GtkSalFrame::getDisplay()->getCursor(ePointerStyle);
+ if (!gtk_widget_get_realized(GTK_WIDGET(m_pDrawingArea)))
+ gtk_widget_realize(GTK_WIDGET(m_pDrawingArea));
+ widget_set_cursor(GTK_WIDGET(m_pDrawingArea), pCursor);
+ }
+
+ virtual Point get_pointer_position() const override
+ {
+ GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
+ GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
+ GdkDevice* pPointer = gdk_seat_get_pointer(pSeat);
+ double x(-1), y(-1);
+ GdkSurface* pWin = widget_get_surface(m_pWidget);
+ surface_get_device_position(pWin, pPointer, x, y, nullptr);
+ return Point(x, y);
+ }
+
+ virtual void set_input_context(const InputContext& rInputContext) override;
+
+ virtual void im_context_set_cursor_location(const tools::Rectangle& rCursorRect, int nExtTextInputWidth) override;
+
+ int im_context_get_surrounding(OUString& rSurroundingText)
+ {
+ return signal_im_context_get_surrounding(rSurroundingText);
+ }
+
+ bool im_context_delete_surrounding(const Selection& rRange)
+ {
+ return signal_im_context_delete_surrounding(rRange);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ virtual bool do_signal_key_press(const GdkEventKey* pEvent) override;
+ virtual bool do_signal_key_release(const GdkEventKey* pEvent) override;
+#endif
+
+ virtual void queue_draw() override
+ {
+ gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
+ }
+
+ virtual void queue_draw_area(int x, int y, int width, int height) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ tools::Rectangle aRect(Point(x, y), Size(width, height));
+ aRect = m_xDevice->LogicToPixel(aRect);
+ gtk_widget_queue_draw_area(GTK_WIDGET(m_pDrawingArea), aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight());
+#else
+ (void)x; (void)y; (void)width; (void)height;
+ queue_draw();
+#endif
+ }
+
+ virtual a11yref get_accessible_parent() override
+ {
+ //get_accessible_parent should only be needed for the vcl implementation,
+ //in the gtk impl the native AtkObject parent set via
+ //atk_object_wrapper_new(m_xAccessible, gtk_widget_get_accessible(pParent));
+ //should negate the need.
+ assert(false && "get_accessible_parent should only be called on a vcl impl");
+ return uno::Reference<css::accessibility::XAccessible>();
+ }
+
+ virtual a11yrelationset get_accessible_relation_set() override
+ {
+ //get_accessible_relation_set should only be needed for the vcl implementation,
+ //in the gtk impl the native equivalent should negate the need.
+ assert(false && "get_accessible_relation_set should only be called on a vcl impl");
+ return uno::Reference<css::accessibility::XAccessibleRelationSet>();
+ }
+
+ virtual AbsoluteScreenPixelPoint get_accessible_location_on_screen() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
+#endif
+ gint x(0), y(0);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (pAtkObject && ATK_IS_COMPONENT(pAtkObject))
+ atk_component_get_extents(ATK_COMPONENT(pAtkObject), &x, &y, nullptr, nullptr, ATK_XY_SCREEN);
+#endif
+ return AbsoluteScreenPixelPoint(x, y);
+ }
+
+ virtual void set_accessible_name(const OUString& rName) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
+ if (!pAtkObject)
+ return;
+ atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
+#else
+ (void)rName;
+#endif
+ }
+
+ virtual OUString get_accessible_name() const override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
+ const char* pStr = pAtkObject ? atk_object_get_name(pAtkObject) : nullptr;
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+#else
+ return OUString();
+#endif
+ }
+
+ virtual OUString get_accessible_description() const override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
+ const char* pStr = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+#else
+ return OUString();
+#endif
+ }
+
+ virtual void enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants) override
+ {
+ do_enable_drag_source(rHelper, eDNDConstants);
+ }
+
+ virtual bool do_signal_drag_begin(bool& rUnsetDragIcon) override
+ {
+ rUnsetDragIcon = false;
+ if (m_aDragBeginHdl.Call(*this))
+ return true;
+ return false;
+ }
+
+ virtual ~GtkInstanceDrawingArea() override
+ {
+#if GTK_CHECK_VERSION(4,0,0)
+ gtk_widget_remove_controller(m_pMouseEventBox, GTK_EVENT_CONTROLLER(m_pZoomGesture));
+#else
+ g_clear_object(&m_pZoomGesture);
+#endif
+
+ g_object_steal_data(G_OBJECT(m_pDrawingArea), "g-lo-GtkInstanceDrawingArea");
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (m_pAccessible)
+ g_object_unref(m_pAccessible);
+#endif
+ css::uno::Reference<css::lang::XComponent> xComp(m_xAccessible, css::uno::UNO_QUERY);
+ if (xComp.is())
+ xComp->dispose();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pDrawingArea, m_nScrollEvent);
+#endif
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pDrawingArea, m_nPopupMenu);
+#endif
+ g_signal_handler_disconnect(m_pDrawingArea, m_nQueryTooltip);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_drawing_area_set_draw_func(m_pDrawingArea, nullptr, nullptr, nullptr);
+#else
+ g_signal_handler_disconnect(m_pDrawingArea, m_nDrawSignalId);
+#endif
+ }
+
+ virtual OutputDevice& get_ref_device() override
+ {
+ return *m_xDevice;
+ }
+
+ bool signal_command(const CommandEvent& rCEvt)
+ {
+ return m_aCommandHdl.Call(rCEvt);
+ }
+
+ virtual void click(const Point& rPos) override
+ {
+ MouseEvent aEvent(rPos);
+ m_aMousePressHdl.Call(aEvent);
+ m_aMouseReleaseHdl.Call(aEvent);
+ }
+};
+
+IMHandler::IMHandler(GtkInstanceDrawingArea* pArea)
+ : m_pArea(pArea)
+ , m_pIMContext(gtk_im_multicontext_new())
+ , m_bExtTextInput(false)
+{
+ GtkWidget* pWidget = m_pArea->getWidget();
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_pFocusController = gtk_event_controller_focus_new();
+ gtk_widget_add_controller(pWidget, m_pFocusController);
+
+ m_nFocusInSignalId = g_signal_connect(m_pFocusController, "enter", G_CALLBACK(signalFocusIn), this);
+ m_nFocusOutSignalId = g_signal_connect(m_pFocusController, "leave", G_CALLBACK(signalFocusOut), this);
+#else
+ m_nFocusInSignalId = g_signal_connect(pWidget, "focus-in-event", G_CALLBACK(signalFocusIn), this);
+ m_nFocusOutSignalId = g_signal_connect(pWidget, "focus-out-event", G_CALLBACK(signalFocusOut), this);
+#endif
+
+ g_signal_connect(m_pIMContext, "preedit-start", G_CALLBACK(signalIMPreeditStart), this);
+ g_signal_connect(m_pIMContext, "preedit-end", G_CALLBACK(signalIMPreeditEnd), this);
+ g_signal_connect(m_pIMContext, "commit", G_CALLBACK(signalIMCommit), this);
+ g_signal_connect(m_pIMContext, "preedit-changed", G_CALLBACK(signalIMPreeditChanged), this);
+ g_signal_connect(m_pIMContext, "retrieve-surrounding", G_CALLBACK(signalIMRetrieveSurrounding), this);
+ g_signal_connect(m_pIMContext, "delete-surrounding", G_CALLBACK(signalIMDeleteSurrounding), this);
+
+ if (!gtk_widget_get_realized(pWidget))
+ gtk_widget_realize(pWidget);
+ im_context_set_client_widget(m_pIMContext, pWidget);
+ if (gtk_widget_has_focus(m_pArea->getWidget()))
+ gtk_im_context_focus_in(m_pIMContext);
+}
+
+void IMHandler::signalFocus(bool bIn)
+{
+ if (bIn)
+ gtk_im_context_focus_in(m_pIMContext);
+ else
+ gtk_im_context_focus_out(m_pIMContext);
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void IMHandler::signalFocusIn(GtkEventControllerFocus*, gpointer im_handler)
+#else
+gboolean IMHandler::signalFocusIn(GtkWidget*, GdkEvent*, gpointer im_handler)
+#endif
+{
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+ pThis->signalFocus(true);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return false;
+#endif
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void IMHandler::signalFocusOut(GtkEventControllerFocus*, gpointer im_handler)
+#else
+gboolean IMHandler::signalFocusOut(GtkWidget*, GdkEvent*, gpointer im_handler)
+#endif
+{
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+ pThis->signalFocus(false);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return false;
+#endif
+}
+
+IMHandler::~IMHandler()
+{
+ EndExtTextInput();
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pFocusController, m_nFocusOutSignalId);
+ g_signal_handler_disconnect(m_pFocusController, m_nFocusInSignalId);
+#else
+ g_signal_handler_disconnect(m_pArea->getWidget(), m_nFocusOutSignalId);
+ g_signal_handler_disconnect(m_pArea->getWidget(), m_nFocusInSignalId);
+#endif
+
+ if (gtk_widget_has_focus(m_pArea->getWidget()))
+ gtk_im_context_focus_out(m_pIMContext);
+
+ // first give IC a chance to deinitialize
+ im_context_set_client_widget(m_pIMContext, nullptr);
+ // destroy old IC
+ g_object_unref(m_pIMContext);
+}
+
+void IMHandler::updateIMSpotLocation()
+{
+ CommandEvent aCEvt(Point(), CommandEventId::CursorPos);
+ // we expect set_cursor_location to get triggered by this
+ m_pArea->signal_command(aCEvt);
+}
+
+void IMHandler::set_cursor_location(const tools::Rectangle& rRect)
+{
+ GdkRectangle aArea{static_cast<int>(rRect.Left()), static_cast<int>(rRect.Top()),
+ static_cast<int>(rRect.GetWidth()), static_cast<int>(rRect.GetHeight())};
+ gtk_im_context_set_cursor_location(m_pIMContext, &aArea);
+}
+
+void IMHandler::signalIMCommit(GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler)
+{
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+
+ SolarMutexGuard aGuard;
+
+ // at least editeng expects to have seen a start before accepting a commit
+ pThis->StartExtTextInput();
+
+ OUString sText(pText, strlen(pText), RTL_TEXTENCODING_UTF8);
+ CommandExtTextInputData aData(sText, nullptr, sText.getLength(), 0, false);
+ CommandEvent aCEvt(Point(), CommandEventId::ExtTextInput, false, &aData);
+ pThis->m_pArea->signal_command(aCEvt);
+
+ pThis->updateIMSpotLocation();
+
+ pThis->EndExtTextInput();
+
+ pThis->m_sPreeditText.clear();
+}
+
+void IMHandler::signalIMPreeditChanged(GtkIMContext* pIMContext, gpointer im_handler)
+{
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+
+ SolarMutexGuard aGuard;
+
+ sal_Int32 nCursorPos(0);
+ sal_uInt8 nCursorFlags(0);
+ std::vector<ExtTextInputAttr> aInputFlags;
+ OUString sText = GtkSalFrame::GetPreeditDetails(pIMContext, aInputFlags, nCursorPos, nCursorFlags);
+
+ // change from nothing to nothing -> do not start preedit e.g. this
+ // will activate input into a calc cell without user input
+ if (sText.isEmpty() && pThis->m_sPreeditText.isEmpty())
+ return;
+
+ pThis->m_sPreeditText = sText;
+
+ CommandExtTextInputData aData(sText, aInputFlags.data(), nCursorPos, nCursorFlags, false);
+ CommandEvent aCEvt(Point(), CommandEventId::ExtTextInput, false, &aData);
+ pThis->m_pArea->signal_command(aCEvt);
+
+ pThis->updateIMSpotLocation();
+}
+
+gboolean IMHandler::signalIMRetrieveSurrounding(GtkIMContext* pContext, gpointer im_handler)
+{
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+
+ SolarMutexGuard aGuard;
+
+ OUString sSurroundingText;
+ int nCursorIndex = pThis->m_pArea->im_context_get_surrounding(sSurroundingText);
+
+ if (nCursorIndex != -1)
+ {
+ OString sUTF = OUStringToOString(sSurroundingText, RTL_TEXTENCODING_UTF8);
+ std::u16string_view sCursorText(sSurroundingText.subView(0, nCursorIndex));
+ gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
+ OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
+ }
+
+ return true;
+}
+
+gboolean IMHandler::signalIMDeleteSurrounding(GtkIMContext*, gint nOffset, gint nChars,
+ gpointer im_handler)
+{
+ bool bRet = false;
+
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+
+ SolarMutexGuard aGuard;
+
+ OUString sSurroundingText;
+ sal_Int32 nCursorIndex = pThis->m_pArea->im_context_get_surrounding(sSurroundingText);
+
+ Selection aSelection = SalFrame::CalcDeleteSurroundingSelection(sSurroundingText, nCursorIndex, nOffset, nChars);
+ if (aSelection != Selection(SAL_MAX_UINT32, SAL_MAX_UINT32))
+ bRet = pThis->m_pArea->im_context_delete_surrounding(aSelection);
+ return bRet;
+}
+
+void IMHandler::StartExtTextInput()
+{
+ if (m_bExtTextInput)
+ return;
+ CommandEvent aCEvt(Point(), CommandEventId::StartExtTextInput);
+ m_pArea->signal_command(aCEvt);
+ m_bExtTextInput = true;
+}
+
+void IMHandler::signalIMPreeditStart(GtkIMContext*, gpointer im_handler)
+{
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+ SolarMutexGuard aGuard;
+ pThis->StartExtTextInput();
+ pThis->updateIMSpotLocation();
+}
+
+void IMHandler::EndExtTextInput()
+{
+ if (!m_bExtTextInput)
+ return;
+ CommandEvent aCEvt(Point(), CommandEventId::EndExtTextInput);
+ m_pArea->signal_command(aCEvt);
+ m_bExtTextInput = false;
+}
+
+void IMHandler::signalIMPreeditEnd(GtkIMContext*, gpointer im_handler)
+{
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+ SolarMutexGuard aGuard;
+ pThis->updateIMSpotLocation();
+ pThis->EndExtTextInput();
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+bool IMHandler::im_context_filter_keypress(const GdkEventKey* pEvent)
+{
+ return gtk_im_context_filter_keypress(m_pIMContext, const_cast<GdkEventKey*>(pEvent));
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+bool GtkInstanceDrawingArea::do_signal_key_press(const GdkEventKey* pEvent)
+{
+ if (m_xIMHandler && m_xIMHandler->im_context_filter_keypress(pEvent))
+ return true;
+ return GtkInstanceWidget::do_signal_key_press(pEvent);
+}
+
+bool GtkInstanceDrawingArea::do_signal_key_release(const GdkEventKey* pEvent)
+{
+ if (m_xIMHandler && m_xIMHandler->im_context_filter_keypress(pEvent))
+ return true;
+ return GtkInstanceWidget::do_signal_key_release(pEvent);
+}
+#endif
+
+void GtkInstanceDrawingArea::set_input_context(const InputContext& rInputContext)
+{
+ bool bUseIm(rInputContext.GetOptions() & InputContextFlags::Text);
+ if (!bUseIm)
+ {
+ m_xIMHandler.reset();
+ return;
+ }
+ // create a new im context
+ if (!m_xIMHandler)
+ m_xIMHandler.reset(new IMHandler(this));
+}
+
+void GtkInstanceDrawingArea::im_context_set_cursor_location(const tools::Rectangle& rCursorRect, int /*nExtTextInputWidth*/)
+{
+ if (!m_xIMHandler)
+ return;
+ m_xIMHandler->set_cursor_location(rCursorRect);
+}
+
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+
+static void InsertSpecialChar(GtkEntry *pEntry)
+{
+ if (auto pImplFncGetSpecialChars = vcl::GetGetSpecialCharsFunction())
+ {
+ weld::Window* pDialogParent = nullptr;
+
+ GtkWidget* pTopLevel = widget_get_toplevel(GTK_WIDGET(pEntry));
+ if (GtkSalFrame* pFrame = pTopLevel ? GtkSalFrame::getFromWindow(pTopLevel) : nullptr)
+ pDialogParent = pFrame->GetFrameWeld();
+
+ std::unique_ptr<GtkInstanceWindow> xFrameWeld;
+ if (!pDialogParent && pTopLevel)
+ {
+ xFrameWeld.reset(new GtkInstanceWindow(GTK_WINDOW(pTopLevel), nullptr, false));
+ pDialogParent = xFrameWeld.get();
+ }
+
+ OUString aChars = pImplFncGetSpecialChars(pDialogParent, ::get_font(GTK_WIDGET(pEntry)));
+ if (!aChars.isEmpty())
+ {
+ gtk_editable_delete_selection(GTK_EDITABLE(pEntry));
+ gint position = gtk_editable_get_position(GTK_EDITABLE(pEntry));
+ OString sText(OUStringToOString(aChars, RTL_TEXTENCODING_UTF8));
+ gtk_editable_insert_text(GTK_EDITABLE(pEntry), sText.getStr(), sText.getLength(),
+ &position);
+ gtk_editable_set_position(GTK_EDITABLE(pEntry), position);
+ }
+ }
+}
+
+static gboolean signalEntryInsertSpecialCharKeyPress(GtkEntry* pEntry, GdkEventKey* pEvent, gpointer)
+{
+ if ((pEvent->keyval == GDK_KEY_S || pEvent->keyval == GDK_KEY_s) &&
+ (pEvent->state & GDK_MODIFIER_MASK) == static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK))
+ {
+ InsertSpecialChar(pEntry);
+ return true;
+ }
+ return false;
+}
+
+static void signalActivateEntryInsertSpecialChar(GtkEntry *pEntry)
+{
+ InsertSpecialChar(pEntry);
+}
+
+static void signalEntryPopulatePopup(GtkEntry* pEntry, GtkWidget* pMenu, gpointer)
+{
+ if (!GTK_IS_MENU(pMenu))
+ return;
+
+ if (!vcl::GetGetSpecialCharsFunction())
+ return;
+
+ GtkWidget *item = gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(VclResId(STR_SPECIAL_CHARACTER_MENU_ENTRY)).getStr());
+ gtk_widget_show(item);
+ g_signal_connect_swapped(item, "activate", G_CALLBACK(signalActivateEntryInsertSpecialChar), pEntry);
+ gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), item);
+}
+
+#endif
+
+namespace {
+
+GtkBuilder* makeMenuToggleButtonBuilder()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ OUString aUri(AllSettings::GetUIRootDir() + "vcl/ui/menutogglebutton3.ui");
+#else
+ OUString aUri(AllSettings::GetUIRootDir() + "vcl/ui/menutogglebutton4.ui");
+#endif
+ OUString aPath;
+ osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
+ return gtk_builder_new_from_file(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr());
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+
+GtkBuilder* makeComboBoxBuilder()
+{
+ OUString aUri(AllSettings::GetUIRootDir() + "vcl/ui/combobox.ui");
+ OUString aPath;
+ osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
+ return gtk_builder_new_from_file(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr());
+}
+
+// pop down the toplevel combobox menu when something is activated from a custom
+// submenu, i.e. wysiwyg style menu
+class CustomRenderMenuButtonHelper : public MenuHelper
+{
+private:
+ GtkToggleButton* m_pComboBox;
+public:
+ CustomRenderMenuButtonHelper(GtkMenu* pMenu, GtkToggleButton* pComboBox)
+ : MenuHelper(pMenu, false)
+ , m_pComboBox(pComboBox)
+ {
+ }
+ virtual void signal_item_activate(const OUString& /*rIdent*/) override
+ {
+ gtk_toggle_button_set_active(m_pComboBox, false);
+ }
+};
+
+#endif
+
+gboolean signalTooltipQuery(GtkWidget* pWidget, gint /*x*/, gint /*y*/,
+ gboolean /*keyboard_mode*/, GtkTooltip *tooltip)
+{
+ const ImplSVHelpData& aHelpData = ImplGetSVHelpData();
+ if (aHelpData.mbBalloonHelp) // extended tips
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // by default use accessible description
+ AtkObject* pAtkObject = gtk_widget_get_accessible(pWidget);
+ const char* pDesc = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
+ if (pDesc && pDesc[0])
+ {
+ gtk_tooltip_set_text(tooltip, pDesc);
+ return true;
+ }
+#endif
+
+ // fallback to the mechanism which needs help installed
+ OUString sHelpId = ::get_help_id(pWidget);
+ Help* pHelp = !sHelpId.isEmpty() ? Application::GetHelp() : nullptr;
+ if (pHelp)
+ {
+ OUString sHelpText = pHelp->GetHelpText(sHelpId, static_cast<weld::Widget*>(nullptr));
+ if (!sHelpText.isEmpty())
+ {
+ gtk_tooltip_set_text(tooltip, OUStringToOString(sHelpText, RTL_TEXTENCODING_UTF8).getStr());
+ return true;
+ }
+ }
+ }
+
+ const char* pDesc = gtk_widget_get_tooltip_text(pWidget);
+ if (pDesc && pDesc[0])
+ {
+ gtk_tooltip_set_text(tooltip, pDesc);
+ return true;
+ }
+
+ return false;
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+
+class GtkInstanceComboBox : public GtkInstanceWidget, public vcl::ISearchableStringList, public virtual weld::ComboBox
+{
+private:
+ GtkComboBox* m_pComboBox;
+// GtkOverlay* m_pOverlay;
+// GtkTreeView* m_pTreeView;
+// GtkMenuButton* m_pOverlayButton; // button that the StyleDropdown uses on an active row
+ GtkWidget* m_pMenuWindow;
+ GtkTreeModel* m_pTreeModel;
+ GtkCellRenderer* m_pButtonTextRenderer;
+ GtkWidget* m_pEntry;
+ GtkEditable* m_pEditable;
+// GtkCellView* m_pCellView;
+ GtkEventController* m_pKeyController;
+ GtkEventController* m_pEntryKeyController;
+ GtkEventController* m_pMenuKeyController;
+ GtkEventController* m_pEntryFocusController;
+// std::unique_ptr<CustomRenderMenuButtonHelper> m_xCustomMenuButtonHelper;
+ WidgetFont m_aCustomFont;
+ std::optional<vcl::Font> m_xEntryFont;
+ std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
+ vcl::QuickSelectionEngine m_aQuickSelectionEngine;
+ std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>> m_aSeparatorRows;
+#if 0
+ OUString m_sMenuButtonRow;
+#endif
+// bool m_bHoverSelection;
+// bool m_bMouseInOverlayButton;
+ bool m_bPopupActive;
+ bool m_bAutoComplete;
+ bool m_bAutoCompleteCaseSensitive;
+ bool m_bChangedByMenu;
+ bool m_bCustomRenderer;
+ bool m_bUserSelectEntry;
+ gint m_nTextCol;
+ gint m_nIdCol;
+// gulong m_nToggleFocusInSignalId;
+// gulong m_nToggleFocusOutSignalId;
+// gulong m_nRowActivatedSignalId;
+ gulong m_nChangedSignalId;
+ gulong m_nPopupShownSignalId;
+ gulong m_nKeyPressEventSignalId;
+ gulong m_nEntryInsertTextSignalId;
+ gulong m_nEntryActivateSignalId;
+ gulong m_nEntryFocusInSignalId;
+ gulong m_nEntryFocusOutSignalId;
+ gulong m_nEntryKeyPressEventSignalId;
+ guint m_nAutoCompleteIdleId;
+// gint m_nNonCustomLineHeight;
+ gint m_nPrePopupCursorPos;
+ int m_nMRUCount;
+ int m_nMaxMRUCount;
+
+ static gboolean idleAutoComplete(gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->auto_complete();
+ return false;
+ }
+
+ void auto_complete()
+ {
+ m_nAutoCompleteIdleId = 0;
+ OUString aStartText = get_active_text();
+ int nStartPos, nEndPos;
+ get_entry_selection_bounds(nStartPos, nEndPos);
+ int nMaxSelection = std::max(nStartPos, nEndPos);
+ if (nMaxSelection != aStartText.getLength())
+ return;
+
+ disable_notify_events();
+ int nActive = get_active();
+ int nStart = nActive;
+
+ if (nStart == -1)
+ nStart = 0;
+
+ int nPos = -1;
+
+ int nZeroRow = 0;
+ if (m_nMRUCount)
+ nZeroRow += (m_nMRUCount + 1);
+
+ if (!m_bAutoCompleteCaseSensitive)
+ {
+ // Try match case insensitive from current position
+ nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, false);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case insensitive, but from start
+ nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, false);
+ }
+ }
+
+ if (nPos == -1)
+ {
+ // Try match case sensitive from current position
+ nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, true);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case sensitive, but from start
+ nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, true);
+ }
+ }
+
+ if (nPos != -1)
+ {
+ OUString aText = get_text_including_mru(nPos);
+ if (aText != aStartText)
+ {
+ SolarMutexGuard aGuard;
+ set_active_including_mru(nPos, true);
+ }
+ select_entry_region(aText.getLength(), aStartText.getLength());
+ }
+ enable_notify_events();
+ }
+
+ static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
+ gint* position, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
+ }
+
+ void signal_entry_insert_text(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength, gint* position)
+ {
+ if (m_bPopupActive) // not entered by the user
+ return;
+
+ // first filter inserted text
+ if (m_aEntryInsertTextHdl.IsSet())
+ {
+ OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
+ const bool bContinue = m_aEntryInsertTextHdl.Call(sText);
+ if (bContinue && !sText.isEmpty())
+ {
+ OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
+ g_signal_handlers_block_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
+ gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position);
+ g_signal_handlers_unblock_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
+ }
+ g_signal_stop_emission_by_name(pEntry, "insert-text");
+ }
+
+ if (m_bAutoComplete)
+ {
+ // now check for autocompletes
+ if (m_nAutoCompleteIdleId)
+ g_source_remove(m_nAutoCompleteIdleId);
+ m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
+ }
+ }
+
+ static void signalChanged(GtkComboBox*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->fire_signal_changed();
+ }
+
+ void fire_signal_changed()
+ {
+ m_bUserSelectEntry = true;
+ m_bChangedByMenu = m_bPopupActive;
+ signal_changed();
+ m_bChangedByMenu = false;
+ }
+
+ static void signalPopupToggled(GObject*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_popup_toggled();
+ }
+
+#if 0
+ int get_popup_height(gint& rPopupWidth)
+ {
+ const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
+
+ int nMaxRows = rSettings.GetListBoxMaximumLineCount();
+ bool bAddScrollWidth = false;
+ int nRows = get_count_including_mru();
+ if (nMaxRows < nRows)
+ {
+ nRows = nMaxRows;
+ bAddScrollWidth = true;
+ }
+
+ GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
+ gint nRowHeight = get_height_row(m_pTreeView, pColumns);
+ g_list_free(pColumns);
+
+ gint nSeparatorHeight = get_height_row_separator(m_pTreeView);
+ gint nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nRows);
+
+ // if we're using a custom renderer, limit the height to the height nMaxRows would be
+ // for a normal renderer, and then round down to how many custom rows fit in that
+ // space
+ if (m_nNonCustomLineHeight != -1 && nRowHeight)
+ {
+ gint nNormalHeight = get_height_rows(m_nNonCustomLineHeight, nSeparatorHeight, nMaxRows);
+ if (nHeight > nNormalHeight)
+ {
+ gint nRowsOnly = nNormalHeight - get_height_rows(0, nSeparatorHeight, nMaxRows);
+ gint nCustomRows = (nRowsOnly + (nRowHeight - 1)) / nRowHeight;
+ nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nCustomRows);
+ }
+ }
+
+ if (bAddScrollWidth)
+ rPopupWidth += rSettings.GetScrollBarSize();
+
+ return nHeight;
+ }
+#endif
+
+ bool toggle_button_get_active()
+ {
+ GValue value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_BOOLEAN);
+ g_object_get_property(G_OBJECT(m_pComboBox), "popup-shown", &value);
+ return g_value_get_boolean(&value);
+ }
+
+ void menu_toggled()
+ {
+ if (!m_bPopupActive)
+ {
+#if 0
+ if (m_bHoverSelection)
+ {
+ // turn hover selection back off until mouse is moved again
+ // *after* menu is shown again
+ gtk_tree_view_set_hover_selection(m_pTreeView, false);
+ m_bHoverSelection = false;
+ }
+#endif
+
+ if (!m_bUserSelectEntry)
+ set_active_including_mru(m_nPrePopupCursorPos, true);
+
+#if 0
+ // undo show_menu tooltip blocking
+ GtkWidget* pParent = widget_get_toplevel(m_pToggleButton);
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
+ if (pFrame)
+ pFrame->UnblockTooltip();
+#endif
+ }
+ else
+ {
+ m_nPrePopupCursorPos = get_active();
+
+ m_bUserSelectEntry = false;
+
+ // if we are in mru mode always start with the cursor at the top of the menu
+ if (m_nMaxMRUCount)
+ set_active_including_mru(0, true);
+ }
+ }
+
+ virtual void signal_popup_toggled() override
+ {
+ m_aQuickSelectionEngine.Reset();
+
+ bool bOldPopupActive = m_bPopupActive;
+ m_bPopupActive = toggle_button_get_active();
+
+ menu_toggled();
+
+ if (bOldPopupActive != m_bPopupActive)
+ {
+ ComboBox::signal_popup_toggled();
+ // restore focus to the GtkEntry when the popup is gone, which
+ // is what the vcl case does, to ease the transition a little,
+ // but don't do it if the focus was moved out of togglebutton
+ // by something else already (e.g. font combobox in toolbar
+ // on a "direct pick" from the menu which moves focus into
+ // the main document
+ if (!m_bPopupActive && m_pEntry && has_child_focus())
+ {
+ disable_notify_events();
+ gtk_widget_grab_focus(m_pEntry);
+ enable_notify_events();
+ }
+ }
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalEntryFocusIn(GtkEventControllerFocus*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_entry_focus_in();
+ }
+#else
+ static gboolean signalEntryFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_entry_focus_in();
+ return false;
+ }
+#endif
+
+ void signal_entry_focus_in()
+ {
+ signal_focus_in();
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalEntryFocusOut(GtkEventControllerFocus*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_entry_focus_out();
+ }
+#else
+ static gboolean signalEntryFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_entry_focus_out();
+ return false;
+ }
+#endif
+
+ void signal_entry_focus_out()
+ {
+ // if we have an untidy selection on losing focus remove the selection
+ int nStartPos, nEndPos;
+ if (get_entry_selection_bounds(nStartPos, nEndPos))
+ {
+ int nMin = std::min(nStartPos, nEndPos);
+ int nMax = std::max(nStartPos, nEndPos);
+ if (nMin != 0 || nMax != get_active_text().getLength())
+ select_entry_region(0, 0);
+ }
+ signal_focus_out();
+ }
+
+ static void signalEntryActivate(GtkEntry*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_entry_activate();
+ }
+
+ void signal_entry_activate()
+ {
+ if (m_aEntryActivateHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ if (m_aEntryActivateHdl.Call(*this))
+ g_signal_stop_emission_by_name(m_pEntry, "activate");
+ }
+ update_mru();
+ }
+
+ OUString get(int pos, int col) const
+ {
+ OUString sRet;
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ {
+ gchar* pStr;
+ gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
+ sRet = OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pStr);
+ }
+ return sRet;
+ }
+
+ void set(int pos, int col, std::u16string_view rText)
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ {
+ OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gtk_list_store_set(GTK_LIST_STORE(m_pTreeModel), &iter, col, aStr.getStr(), -1);
+ }
+ }
+
+ int find(std::u16string_view rStr, int col, bool bSearchMRUArea) const
+ {
+ GtkTreeIter iter;
+ if (!gtk_tree_model_get_iter_first(m_pTreeModel, &iter))
+ return -1;
+
+ int nRet = 0;
+
+ if (!bSearchMRUArea && m_nMRUCount)
+ {
+ if (!gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, m_nMRUCount + 1))
+ return -1;
+ nRet += (m_nMRUCount + 1);
+ }
+
+ OString aStr(OUStringToOString(rStr, RTL_TEXTENCODING_UTF8));
+ do
+ {
+ gchar* pStr;
+ gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
+ const bool bEqual = g_strcmp0(pStr, aStr.getStr()) == 0;
+ g_free(pStr);
+ if (bEqual)
+ return nRet;
+ ++nRet;
+ } while (gtk_tree_model_iter_next(m_pTreeModel, &iter));
+
+ return -1;
+ }
+
+ bool separator_function(const GtkTreePath* path)
+ {
+ return ::separator_function(path, m_aSeparatorRows);
+ }
+
+ bool separator_function(int pos)
+ {
+ GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
+ bool bRet = separator_function(path);
+ gtk_tree_path_free(path);
+ return bRet;
+ }
+
+ static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter);
+ bool bRet = pThis->separator_function(path);
+ gtk_tree_path_free(path);
+ return bRet;
+ }
+
+ // https://gitlab.gnome.org/GNOME/gtk/issues/310
+ //
+ // in the absence of a built-in solution
+ // a) support typeahead for the case where there is no entry widget, typing ahead
+ // into the button itself will select via the vcl selection engine, a matching
+ // entry
+ static gboolean signalKeyPress(GtkEventControllerKey*, guint keyval, guint keycode, GdkModifierType state, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_key_press(CreateKeyEvent(keyval, keycode, state, 0));
+ }
+
+ // tdf#131076 we want return in a ComboBox to act like return in a
+ // GtkEntry and activate the default dialog/assistant button
+ bool combobox_activate()
+ {
+ GtkWidget *pComboBox = GTK_WIDGET(m_pComboBox);
+ GtkWidget *pToplevel = widget_get_toplevel(pComboBox);
+ GtkWindow *pWindow = GTK_WINDOW(pToplevel);
+ if (!pWindow)
+ return false;
+ if (!GTK_IS_DIALOG(pWindow) && !GTK_IS_ASSISTANT(pWindow))
+ return false;
+ bool bDone = false;
+ GtkWidget *pDefaultWidget = gtk_window_get_default_widget(pWindow);
+ if (pDefaultWidget && pDefaultWidget != pComboBox && gtk_widget_get_sensitive(pDefaultWidget))
+ bDone = gtk_widget_activate(pDefaultWidget);
+ return bDone;
+ }
+
+ static gboolean signalEntryKeyPress(GtkEventControllerKey*, guint keyval, guint keycode, GdkModifierType state, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ LocalizeDecimalSeparator(keyval);
+ return pThis->signal_entry_key_press(CreateKeyEvent(keyval, keycode, state, 0));
+ }
+
+ bool signal_entry_key_press(const KeyEvent& rKEvt)
+ {
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ bool bDone = false;
+
+ auto nCode = aKeyCode.GetCode();
+ switch (nCode)
+ {
+ case KEY_DOWN:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nCount = get_count_including_mru();
+ int nActive = get_active_including_mru() + 1;
+ while (nActive < nCount && separator_function(nActive))
+ ++nActive;
+ if (nActive < nCount)
+ set_active_including_mru(nActive, true);
+ bDone = true;
+ }
+ else if (nKeyMod == KEY_MOD2 && !m_bPopupActive)
+ {
+ gtk_combo_box_popup(m_pComboBox);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_UP:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
+ int nActive = get_active_including_mru() - 1;
+ while (nActive >= nStartBound && separator_function(nActive))
+ --nActive;
+ if (nActive >= nStartBound)
+ set_active_including_mru(nActive, true);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_PAGEUP:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nCount = get_count_including_mru();
+ int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
+ int nActive = nStartBound;
+ while (nActive < nCount && separator_function(nActive))
+ ++nActive;
+ if (nActive < nCount)
+ set_active_including_mru(nActive, true);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_PAGEDOWN:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nActive = get_count_including_mru() - 1;
+ int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
+ while (nActive >= nStartBound && separator_function(nActive))
+ --nActive;
+ if (nActive >= nStartBound)
+ set_active_including_mru(nActive, true);
+ bDone = true;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return bDone;
+ }
+
+ bool signal_key_press(const KeyEvent& rKEvt)
+ {
+#if 0
+ if (m_bHoverSelection)
+ {
+ // once a key is pressed, turn off hover selection until mouse is
+ // moved again otherwise when the treeview scrolls it jumps to the
+ // position under the mouse.
+ gtk_tree_view_set_hover_selection(m_pTreeView, false);
+ m_bHoverSelection = false;
+ }
+#endif
+
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ bool bDone = false;
+
+ auto nCode = aKeyCode.GetCode();
+ switch (nCode)
+ {
+ case KEY_DOWN:
+ case KEY_UP:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN:
+ case KEY_HOME:
+ case KEY_END:
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ case KEY_RETURN:
+ {
+ m_aQuickSelectionEngine.Reset();
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ // tdf#131076 don't let bare return toggle menu popup active, but do allow deactivate
+ if (nCode == KEY_RETURN && !nKeyMod)
+ {
+ if (!m_bPopupActive)
+ bDone = combobox_activate();
+ else
+ {
+ // treat 'return' as if the active entry was clicked on
+ signalChanged(m_pComboBox, this);
+ gtk_combo_box_popdown(m_pComboBox);
+ bDone = true;
+ }
+ }
+ else if (nCode == KEY_UP && nKeyMod == KEY_MOD2 && m_bPopupActive)
+ {
+ gtk_combo_box_popdown(m_pComboBox);
+ bDone = true;
+ }
+ else if (nCode == KEY_DOWN && nKeyMod == KEY_MOD2 && !m_bPopupActive)
+ {
+ gtk_combo_box_popup(m_pComboBox);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_ESCAPE:
+ {
+ m_aQuickSelectionEngine.Reset();
+ if (m_bPopupActive)
+ {
+ gtk_combo_box_popdown(m_pComboBox);
+ bDone = true;
+ }
+ break;
+ }
+ default:
+ // tdf#131076 let base space toggle menu popup when it's not already visible
+ if (nCode == KEY_SPACE && !aKeyCode.GetModifier() && !m_bPopupActive)
+ bDone = false;
+ else
+ bDone = m_aQuickSelectionEngine.HandleKeyEvent(rKEvt);
+ break;
+ }
+
+ if (!bDone)
+ {
+ if (!m_pEntry)
+ bDone = signal_entry_key_press(rKEvt);
+ else
+ {
+ // with gtk4-4.2.1 the unconsumed keystrokes don't appear to get to
+ // the GtkEntry for up/down to move to the next entry without this extra help
+ // (which means this extra indirection is probably effectively
+ // the same as if calling signal_entry_key_press directly here)
+ bDone = gtk_event_controller_key_forward(GTK_EVENT_CONTROLLER_KEY(m_pMenuKeyController), m_pEntry);
+ }
+ }
+
+ return bDone;
+ }
+
+ vcl::StringEntryIdentifier typeahead_getEntry(int nPos, OUString& out_entryText) const
+ {
+ int nEntryCount(get_count_including_mru());
+ if (nPos >= nEntryCount)
+ nPos = 0;
+ out_entryText = get_text_including_mru(nPos);
+
+ // vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based
+ // => normalize
+ return reinterpret_cast<vcl::StringEntryIdentifier>(nPos + 1);
+ }
+
+ static int typeahead_getEntryPos(vcl::StringEntryIdentifier entry)
+ {
+ // our pos is 0-based, but StringEntryIdentifier does not allow for a NULL
+ return reinterpret_cast<sal_Int64>(entry) - 1;
+ }
+
+ int tree_view_get_cursor() const
+ {
+ int nRet = -1;
+#if 0
+ GtkTreePath* path;
+ gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
+ if (path)
+ {
+ gint depth;
+ gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
+ nRet = indices[depth-1];
+ gtk_tree_path_free(path);
+ }
+#endif
+
+ return nRet;
+ }
+
+ int get_selected_entry() const
+ {
+ if (m_bPopupActive)
+ return tree_view_get_cursor();
+ else
+ return get_active_including_mru();
+ }
+
+ void set_typeahead_selected_entry(int nSelect)
+ {
+ set_active_including_mru(nSelect, true);
+ }
+
+ virtual vcl::StringEntryIdentifier CurrentEntry(OUString& out_entryText) const override
+ {
+ int nCurrentPos = get_selected_entry();
+ return typeahead_getEntry((nCurrentPos == -1) ? 0 : nCurrentPos, out_entryText);
+ }
+
+ virtual vcl::StringEntryIdentifier NextEntry(vcl::StringEntryIdentifier currentEntry, OUString& out_entryText) const override
+ {
+ int nNextPos = typeahead_getEntryPos(currentEntry) + 1;
+ return typeahead_getEntry(nNextPos, out_entryText);
+ }
+
+ virtual void SelectEntry(vcl::StringEntryIdentifier entry) override
+ {
+ int nSelect = typeahead_getEntryPos(entry);
+ if (nSelect == get_selected_entry())
+ {
+ // 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
+ int nCount = get_count_including_mru();
+ if (nSelect >= nCount)
+ nSelect = nCount ? nCount-1 : -1;
+
+ set_typeahead_selected_entry(nSelect);
+ }
+
+#if 0
+ static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->grab_broken(pEvent);
+ }
+
+ void grab_broken(const GdkEventGrabBroken *event)
+ {
+ if (event->grab_window == nullptr)
+ {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+ }
+ else if (!g_object_get_data(G_OBJECT(event->grab_window), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
+ {
+ //try and regrab, so when we lose the grab to the menu of the color palette
+ //combobox we regain it so the color palette doesn't itself disappear on next
+ //click on the color palette combobox
+ do_grab(GTK_WIDGET(m_pMenuWindow));
+ }
+ }
+
+ static gboolean signalButtonPress(GtkWidget* pWidget, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ return pThis->button_press(pWidget, pEvent);
+ }
+
+ bool button_press(GtkWidget* pWidget, GdkEventButton* pEvent)
+ {
+ //we want to pop down if the button was pressed outside our popup
+ gdouble x = pEvent->x_root;
+ gdouble y = pEvent->y_root;
+ gint xoffset, yoffset;
+ gdk_window_get_root_origin(widget_get_surface(pWidget), &xoffset, &yoffset);
+
+ GtkAllocation alloc;
+ gtk_widget_get_allocation(pWidget, &alloc);
+ xoffset += alloc.x;
+ yoffset += alloc.y;
+
+ gtk_widget_get_allocation(GTK_WIDGET(m_pMenuWindow), &alloc);
+ gint x1 = alloc.x + xoffset;
+ gint y1 = alloc.y + yoffset;
+ gint x2 = x1 + alloc.width;
+ gint y2 = y1 + alloc.height;
+
+ if (x > x1 && x < x2 && y > y1 && y < y2)
+ return false;
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+
+ return false;
+ }
+
+ static gboolean signalMotion(GtkWidget*, GdkEventMotion*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_motion();
+ return false;
+ }
+
+ void signal_motion()
+ {
+ // if hover-selection was disabled after pressing a key, then turn it back on again
+ if (!m_bHoverSelection && !m_bMouseInOverlayButton)
+ {
+ gtk_tree_view_set_hover_selection(m_pTreeView, true);
+ m_bHoverSelection = true;
+ }
+ }
+#endif
+
+ static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->handle_row_activated();
+ }
+
+ void handle_row_activated()
+ {
+ m_bUserSelectEntry = true;
+ m_bChangedByMenu = true;
+ disable_notify_events();
+ int nActive = get_active();
+ if (m_pEditable)
+ gtk_editable_set_text(m_pEditable, OUStringToOString(get_text(nActive), RTL_TEXTENCODING_UTF8).getStr());
+#if 0
+ else
+ tree_view_set_cursor(nActive);
+#endif
+ enable_notify_events();
+// gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+ fire_signal_changed();
+ update_mru();
+ }
+
+ void do_clear()
+ {
+ disable_notify_events();
+ gtk_combo_box_set_row_separator_func(m_pComboBox, nullptr, nullptr, nullptr);
+ m_aSeparatorRows.clear();
+ gtk_list_store_clear(GTK_LIST_STORE(m_pTreeModel));
+ m_nMRUCount = 0;
+ enable_notify_events();
+ }
+
+ virtual int get_max_mru_count() const override
+ {
+ return m_nMaxMRUCount;
+ }
+
+ virtual void set_max_mru_count(int nMaxMRUCount) override
+ {
+ m_nMaxMRUCount = nMaxMRUCount;
+ update_mru();
+ }
+
+ void update_mru()
+ {
+ int nMRUCount = m_nMRUCount;
+
+ if (m_nMaxMRUCount)
+ {
+ OUString sActiveText = get_active_text();
+ OUString sActiveId = get_active_id();
+ insert_including_mru(0, sActiveText, &sActiveId, nullptr, nullptr);
+ ++m_nMRUCount;
+
+ for (int i = 1; i < m_nMRUCount - 1; ++i)
+ {
+ if (get_text_including_mru(i) == sActiveText)
+ {
+ remove_including_mru(i);
+ --m_nMRUCount;
+ break;
+ }
+ }
+ }
+
+ while (m_nMRUCount > m_nMaxMRUCount)
+ {
+ remove_including_mru(m_nMRUCount - 1);
+ --m_nMRUCount;
+ }
+
+ if (m_nMRUCount && !nMRUCount)
+ insert_separator_including_mru(m_nMRUCount, "separator");
+ else if (!m_nMRUCount && nMRUCount)
+ remove_including_mru(m_nMRUCount); // remove separator
+ }
+
+ int get_count_including_mru() const
+ {
+ return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr);
+ }
+
+ int get_active_including_mru() const
+ {
+ return gtk_combo_box_get_active(m_pComboBox);
+ }
+
+ void set_active_including_mru(int pos, bool bInteractive)
+ {
+ disable_notify_events();
+
+ gtk_combo_box_set_active(m_pComboBox, pos);
+
+ m_bChangedByMenu = false;
+ enable_notify_events();
+
+ if (bInteractive && !m_bPopupActive)
+ signal_changed();
+ }
+
+ int find_text_including_mru(std::u16string_view rStr, bool bSearchMRU) const
+ {
+ return find(rStr, m_nTextCol, bSearchMRU);
+ }
+
+ int find_id_including_mru(std::u16string_view rId, bool bSearchMRU) const
+ {
+ return find(rId, m_nIdCol, bSearchMRU);
+ }
+
+ OUString get_text_including_mru(int pos) const
+ {
+ return get(pos, m_nTextCol);
+ }
+
+ OUString get_id_including_mru(int pos) const
+ {
+ return get(pos, m_nIdCol);
+ }
+
+ void set_id_including_mru(int pos, std::u16string_view rId)
+ {
+ set(pos, m_nIdCol, rId);
+ }
+
+ void remove_including_mru(int pos)
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
+ if (!m_aSeparatorRows.empty())
+ {
+ bool bFound = false;
+
+ GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
+
+ for (auto aIter = m_aSeparatorRows.begin(); aIter != m_aSeparatorRows.end(); ++aIter)
+ {
+ GtkTreePath* seppath = gtk_tree_row_reference_get_path(aIter->get());
+ if (seppath)
+ {
+ if (gtk_tree_path_compare(pPath, seppath) == 0)
+ bFound = true;
+ gtk_tree_path_free(seppath);
+ }
+ if (bFound)
+ {
+ m_aSeparatorRows.erase(aIter);
+ break;
+ }
+ }
+
+ gtk_tree_path_free(pPath);
+ }
+ gtk_list_store_remove(GTK_LIST_STORE(m_pTreeModel), &iter);
+ enable_notify_events();
+ }
+
+ void insert_separator_including_mru(int pos, const OUString& rId)
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ if (!gtk_combo_box_get_row_separator_func(m_pComboBox))
+ gtk_combo_box_set_row_separator_func(m_pComboBox, separatorFunction, this, nullptr);
+ insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, &rId, u"", nullptr, nullptr);
+ GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
+ m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(m_pTreeModel, pPath));
+ gtk_tree_path_free(pPath);
+ enable_notify_events();
+ }
+
+ void insert_including_mru(int pos, std::u16string_view rText, const OUString* pId, const OUString* pIconName, const VirtualDevice* pImageSurface)
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, pId, rText, pIconName, pImageSurface);
+ enable_notify_events();
+ }
+
+#if 0
+ static gboolean signalGetChildPosition(GtkOverlay*, GtkWidget*, GdkRectangle* pAllocation, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ return pThis->signal_get_child_position(pAllocation);
+ }
+
+ bool signal_get_child_position(GdkRectangle* pAllocation)
+ {
+ if (!gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton)))
+ return false;
+ if (!gtk_widget_get_realized(GTK_WIDGET(m_pTreeView)))
+ return false;
+ int nRow = find_id_including_mru(m_sMenuButtonRow, true);
+ if (nRow == -1)
+ return false;
+
+ gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &pAllocation->width, nullptr);
+
+ GtkTreePath* pPath = gtk_tree_path_new_from_indices(nRow, -1);
+ GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
+ tools::Rectangle aRect = get_row_area(m_pTreeView, pColumns, pPath);
+ gtk_tree_path_free(pPath);
+ g_list_free(pColumns);
+
+ pAllocation->x = aRect.Right() - pAllocation->width;
+ pAllocation->y = aRect.Top();
+ pAllocation->height = aRect.GetHeight();
+
+ return true;
+ }
+
+ static gboolean signalOverlayButtonCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_overlay_button_crossing(pEvent->type == GDK_ENTER_NOTIFY);
+ return false;
+ }
+
+ void signal_overlay_button_crossing(bool bEnter)
+ {
+ m_bMouseInOverlayButton = bEnter;
+ if (!bEnter)
+ return;
+
+ if (m_bHoverSelection)
+ {
+ // once toggled button is pressed, turn off hover selection until
+ // mouse leaves the overlay button
+ gtk_tree_view_set_hover_selection(m_pTreeView, false);
+ m_bHoverSelection = false;
+ }
+ int nRow = find_id_including_mru(m_sMenuButtonRow, true);
+ assert(nRow != -1);
+ tree_view_set_cursor(nRow); // select the buttons row
+ }
+#endif
+
+ int include_mru(int pos)
+ {
+ if (m_nMRUCount && pos != -1)
+ pos += (m_nMRUCount + 1);
+ return pos;
+ }
+
+public:
+ GtkInstanceComboBox(GtkComboBox* pComboBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pComboBox), pBuilder, bTakeOwnership)
+ , m_pComboBox(pComboBox)
+// , m_pOverlay(GTK_OVERLAY(gtk_builder_get_object(pComboBuilder, "overlay")))
+// , m_pTreeView(GTK_TREE_VIEW(gtk_builder_get_object(pComboBuilder, "treeview")))
+// , m_pOverlayButton(GTK_MENU_BUTTON(gtk_builder_get_object(pComboBuilder, "overlaybutton")))
+ , m_pMenuWindow(nullptr)
+ , m_pTreeModel(gtk_combo_box_get_model(pComboBox))
+ , m_pButtonTextRenderer(nullptr)
+// , m_pToggleButton(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "button")))
+ , m_pEntry(GTK_IS_ENTRY(gtk_combo_box_get_child(pComboBox)) ? gtk_combo_box_get_child(pComboBox) : nullptr)
+ , m_pEditable(GTK_EDITABLE(m_pEntry))
+ , m_aCustomFont(m_pWidget)
+// , m_pCellView(nullptr)
+ , m_aQuickSelectionEngine(*this)
+// , m_bHoverSelection(false)
+// , m_bMouseInOverlayButton(false)
+ , m_bPopupActive(false)
+ , m_bAutoComplete(false)
+ , m_bAutoCompleteCaseSensitive(false)
+ , m_bChangedByMenu(false)
+ , m_bCustomRenderer(false)
+ , m_bUserSelectEntry(false)
+ , m_nTextCol(gtk_combo_box_get_entry_text_column(pComboBox))
+ , m_nIdCol(gtk_combo_box_get_id_column(pComboBox))
+// , m_nToggleFocusInSignalId(0)
+// , m_nToggleFocusOutSignalId(0)
+// , m_nRowActivatedSignalId(g_signal_connect(m_pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
+ , m_nChangedSignalId(g_signal_connect(m_pComboBox, "changed", G_CALLBACK(signalChanged), this))
+ , m_nPopupShownSignalId(g_signal_connect(m_pComboBox, "notify::popup-shown", G_CALLBACK(signalPopupToggled), this))
+ , m_nAutoCompleteIdleId(0)
+// , m_nNonCustomLineHeight(-1)
+ , m_nPrePopupCursorPos(-1)
+ , m_nMRUCount(0)
+ , m_nMaxMRUCount(0)
+ {
+ for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pComboBox));
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ if (GTK_IS_POPOVER(pChild))
+ {
+ m_pMenuWindow = pChild;
+ break;
+ }
+ }
+ SAL_WARN_IF(!m_pMenuWindow, "vcl.gtk", "GtkInstanceComboBox: couldn't find popup menu");
+
+ bool bHasEntry = gtk_combo_box_get_has_entry(m_pComboBox);
+ bool bPixbufUsedSurface = gtk_tree_model_get_n_columns(m_pTreeModel) == 4;
+
+ bool bFindButtonTextRenderer = !bHasEntry;
+ GtkCellLayout* pCellLayout = GTK_CELL_LAYOUT(m_pComboBox);
+ GList* cells = gtk_cell_layout_get_cells(pCellLayout);
+ guint i = g_list_length(cells) - 1;;
+ // reorder the cell renderers
+ for (GList* pRenderer = g_list_first(cells); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ gtk_cell_layout_reorder(pCellLayout, pCellRenderer, i--);
+ if (bFindButtonTextRenderer)
+ {
+ m_pButtonTextRenderer = pCellRenderer;
+ bFindButtonTextRenderer = false;
+ }
+ }
+
+ // Seeing as GtkCellRendererPixbuf no longer takes a surface, then insert our own replacement
+ // to render that instead here
+ if (bPixbufUsedSurface)
+ {
+ GtkCellRenderer* pSurfaceRenderer = surface_cell_renderer_new();
+ gtk_cell_layout_pack_start(pCellLayout, pSurfaceRenderer, false);
+ gtk_cell_layout_reorder(pCellLayout, pSurfaceRenderer, 0);
+ gtk_cell_layout_set_attributes(pCellLayout, pSurfaceRenderer, "surface", 3, nullptr);
+ }
+
+ if (bHasEntry)
+ {
+ m_bAutoComplete = true;
+ m_nEntryInsertTextSignalId = g_signal_connect(m_pEditable, "insert-text", G_CALLBACK(signalEntryInsertText), this);
+ m_nEntryActivateSignalId = g_signal_connect(m_pEntry, "activate", G_CALLBACK(signalEntryActivate), this);
+ m_pEntryFocusController = GTK_EVENT_CONTROLLER(gtk_event_controller_focus_new());
+ m_nEntryFocusInSignalId = g_signal_connect(m_pEntryFocusController, "enter", G_CALLBACK(signalEntryFocusIn), this);
+ m_nEntryFocusOutSignalId = g_signal_connect(m_pEntryFocusController, "leave", G_CALLBACK(signalEntryFocusOut), this);
+ gtk_widget_add_controller(m_pEntry, m_pEntryFocusController);
+ m_pEntryKeyController = GTK_EVENT_CONTROLLER(gtk_event_controller_key_new());
+ m_nEntryKeyPressEventSignalId = g_signal_connect(m_pEntryKeyController, "key-pressed", G_CALLBACK(signalEntryKeyPress), this);
+ gtk_widget_add_controller(m_pEntry, m_pEntryKeyController);
+ m_nKeyPressEventSignalId = 0;
+ m_pKeyController = nullptr;
+ }
+ else
+ {
+ m_nEntryInsertTextSignalId = 0;
+ m_nEntryActivateSignalId = 0;
+ m_pEntryFocusController = nullptr;
+ m_nEntryFocusInSignalId = 0;
+ m_nEntryFocusOutSignalId = 0;
+ m_pEntryKeyController = nullptr;
+ m_nEntryKeyPressEventSignalId = 0;
+ m_pKeyController = GTK_EVENT_CONTROLLER(gtk_event_controller_key_new());
+ m_nKeyPressEventSignalId = g_signal_connect(m_pKeyController, "key-pressed", G_CALLBACK(signalKeyPress), this);
+ gtk_widget_add_controller(GTK_WIDGET(m_pComboBox), m_pKeyController);
+ }
+
+// g_signal_connect(m_pMenuWindow, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
+// g_signal_connect(m_pMenuWindow, "button-press-event", G_CALLBACK(signalButtonPress), this);
+// g_signal_connect(m_pMenuWindow, "motion-notify-event", G_CALLBACK(signalMotion), this);
+
+ // support typeahead for the menu itself, typing into the menu will
+ // select via the vcl selection engine, a matching entry.
+ if (m_pMenuWindow)
+ {
+ m_pMenuKeyController = GTK_EVENT_CONTROLLER(gtk_event_controller_key_new());
+ g_signal_connect(m_pMenuKeyController, "key-pressed", G_CALLBACK(signalKeyPress), this);
+ gtk_widget_add_controller(m_pMenuWindow, m_pMenuKeyController);
+ }
+ else
+ m_pMenuKeyController = nullptr;
+#if 0
+ g_signal_connect(m_pOverlay, "get-child-position", G_CALLBACK(signalGetChildPosition), this);
+ gtk_overlay_add_overlay(m_pOverlay, GTK_WIDGET(m_pOverlayButton));
+ g_signal_connect(m_pOverlayButton, "leave-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
+ g_signal_connect(m_pOverlayButton, "enter-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
+#endif
+ }
+
+ virtual int get_active() const override
+ {
+ int nActive = get_active_including_mru();
+ if (nActive == -1)
+ return -1;
+
+ if (m_nMRUCount)
+ {
+ if (nActive < m_nMRUCount)
+ nActive = find_text(get_text_including_mru(nActive));
+ else
+ nActive -= (m_nMRUCount + 1);
+ }
+
+ return nActive;
+ }
+
+ virtual OUString get_active_id() const override
+ {
+ int nActive = get_active();
+ return nActive != -1 ? get_id(nActive) : OUString();
+ }
+
+ virtual void set_active_id(const OUString& rStr) override
+ {
+ set_active(find_id(rStr));
+ m_bChangedByMenu = false;
+ }
+
+ virtual void set_size_request(int nWidth, int nHeight) override
+ {
+ if (m_pButtonTextRenderer)
+ {
+ // tweak the cell render to get a narrower size to stick
+ if (nWidth != -1)
+ {
+ // this bit isn't great, I really want to be able to ellipse the text in the comboboxtext itself and let
+ // the popup menu render them in full, in the interim ellipse both of them
+ g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_MIDDLE, nullptr);
+
+ // to find out how much of the width of the combobox belongs to the cell, set
+ // the cell and widget to the min cell width and see what the difference is
+ int min;
+ gtk_cell_renderer_get_preferred_width(m_pButtonTextRenderer, m_pWidget, &min, nullptr);
+ gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, min, -1);
+ gtk_widget_set_size_request(m_pWidget, min, -1);
+ int nNonCellWidth = get_preferred_size().Width() - min;
+
+ int nCellWidth = nWidth - nNonCellWidth;
+ if (nCellWidth >= 0)
+ {
+ // now set the cell to the max width which it can be within the
+ // requested widget width
+ gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, nWidth - nNonCellWidth, -1);
+ }
+ }
+ else
+ {
+ g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_NONE, nullptr);
+ gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, -1, -1);
+ }
+ }
+
+ gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
+ }
+
+ virtual void set_active(int pos) override
+ {
+ set_active_including_mru(include_mru(pos), false);
+ }
+
+ virtual OUString get_active_text() const override
+ {
+ if (m_pEditable)
+ {
+ const gchar* pText = gtk_editable_get_text(m_pEditable);
+ return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ int nActive = get_active();
+ if (nActive == -1)
+ return OUString();
+
+ return get_text(nActive);
+ }
+
+ virtual OUString get_text(int pos) const override
+ {
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ return get_text_including_mru(pos);
+ }
+
+ virtual OUString get_id(int pos) const override
+ {
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ return get_id_including_mru(pos);
+ }
+
+ virtual void set_id(int pos, const OUString& rId) override
+ {
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ set_id_including_mru(pos, rId);
+ }
+
+ virtual void insert_vector(const std::vector<weld::ComboBoxEntry>& rItems, bool bKeepExisting) override
+ {
+ freeze();
+
+ int nInsertionPoint;
+ if (!bKeepExisting)
+ {
+ clear();
+ nInsertionPoint = 0;
+ }
+ else
+ nInsertionPoint = get_count();
+
+ GtkTreeIter iter;
+ // tdf#125241 inserting backwards is faster
+ for (auto aI = rItems.rbegin(); aI != rItems.rend(); ++aI)
+ {
+ const auto& rItem = *aI;
+ insert_row(GTK_LIST_STORE(m_pTreeModel), iter, nInsertionPoint, rItem.sId.isEmpty() ? nullptr : &rItem.sId,
+ rItem.sString, rItem.sImage.isEmpty() ? nullptr : &rItem.sImage, nullptr);
+ }
+
+ thaw();
+ }
+
+ virtual void remove(int pos) override
+ {
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ remove_including_mru(pos);
+ }
+
+ virtual void insert(int pos, const OUString& rText, const OUString* pId, const OUString* pIconName, VirtualDevice* pImageSurface) override
+ {
+ insert_including_mru(include_mru(pos), rText, pId, pIconName, pImageSurface);
+ }
+
+ virtual void insert_separator(int pos, const OUString& rId) override
+ {
+ pos = pos == -1 ? get_count() : pos;
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ insert_separator_including_mru(pos, rId);
+ }
+
+ virtual int get_count() const override
+ {
+ int nCount = get_count_including_mru();
+ if (m_nMRUCount)
+ nCount -= (m_nMRUCount + 1);
+ return nCount;
+ }
+
+ virtual int find_text(const OUString& rStr) const override
+ {
+ int nPos = find_text_including_mru(rStr, false);
+ if (nPos != -1 && m_nMRUCount)
+ nPos -= (m_nMRUCount + 1);
+ return nPos;
+ }
+
+ virtual int find_id(const OUString& rId) const override
+ {
+ int nPos = find_id_including_mru(rId, false);
+ if (nPos != -1 && m_nMRUCount)
+ nPos -= (m_nMRUCount + 1);
+ return nPos;
+ }
+
+ virtual void clear() override
+ {
+ do_clear();
+ }
+
+ virtual void make_sorted() override
+ {
+ m_xSorter.reset(new comphelper::string::NaturalStringSorter(
+ ::comphelper::getProcessComponentContext(),
+ Application::GetSettings().GetUILanguageTag().getLocale()));
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
+ gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, default_sort_func, m_xSorter.get(), nullptr);
+ }
+
+ virtual bool has_entry() const override
+ {
+ return gtk_combo_box_get_has_entry(m_pComboBox);
+ }
+
+ virtual void set_entry_message_type(weld::EntryMessageType eType) override
+ {
+ assert(m_pEntry);
+ ::set_entry_message_type(GTK_ENTRY(m_pEntry), eType);
+ }
+
+ virtual void set_entry_text(const OUString& rText) override
+ {
+ assert(m_pEditable);
+ disable_notify_events();
+ gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+ enable_notify_events();
+ }
+
+ virtual void set_entry_width_chars(int nChars) override
+ {
+ assert(m_pEditable);
+ disable_notify_events();
+ gtk_editable_set_width_chars(m_pEditable, nChars);
+ gtk_editable_set_max_width_chars(m_pEditable, nChars);
+ enable_notify_events();
+ }
+
+ virtual void set_entry_max_length(int nChars) override
+ {
+ assert(m_pEntry);
+ disable_notify_events();
+ gtk_entry_set_max_length(GTK_ENTRY(m_pEntry), nChars);
+ enable_notify_events();
+ }
+
+ virtual void select_entry_region(int nStartPos, int nEndPos) override
+ {
+ assert(m_pEditable);
+ disable_notify_events();
+ gtk_editable_select_region(m_pEditable, nStartPos, nEndPos);
+ enable_notify_events();
+ }
+
+ virtual bool get_entry_selection_bounds(int& rStartPos, int &rEndPos) override
+ {
+ assert(m_pEditable);
+ return gtk_editable_get_selection_bounds(m_pEditable, &rStartPos, &rEndPos);
+ }
+
+ virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
+ {
+ m_bAutoComplete = bEnable;
+ m_bAutoCompleteCaseSensitive = bCaseSensitive;
+ }
+
+ virtual void set_entry_placeholder_text(const OUString& rText) override
+ {
+ assert(m_pEntry);
+ gtk_entry_set_placeholder_text(GTK_ENTRY(m_pEntry), rText.toUtf8().getStr());
+ }
+
+ virtual void set_entry_editable(bool bEditable) override
+ {
+ assert(m_pEditable);
+ gtk_editable_set_editable(m_pEditable, bEditable);
+ }
+
+ virtual void cut_entry_clipboard() override
+ {
+ assert(m_pEntry);
+ gtk_widget_activate_action(m_pEntry, "cut.clipboard", nullptr);
+ }
+
+ virtual void copy_entry_clipboard() override
+ {
+ assert(m_pEntry);
+ gtk_widget_activate_action(m_pEntry, "copy.clipboard", nullptr);
+ }
+
+ virtual void paste_entry_clipboard() override
+ {
+ assert(m_pEntry);
+ gtk_widget_activate_action(m_pEntry, "paste.clipboard", nullptr);
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_aCustomFont.use_custom_font(&rFont, u"combobox");
+ }
+
+ virtual vcl::Font get_font() override
+ {
+ if (const vcl::Font* pFont = m_aCustomFont.get_custom_font())
+ return *pFont;
+ return GtkInstanceWidget::get_font();
+ }
+
+ virtual void set_entry_font(const vcl::Font& rFont) override
+ {
+ m_xEntryFont = rFont;
+ assert(m_pEntry);
+ PangoAttrList* pOrigList = gtk_entry_get_attributes(GTK_ENTRY(m_pEntry));
+ PangoAttrList* pAttrList = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
+ update_attr_list(pAttrList, rFont);
+ gtk_entry_set_attributes(GTK_ENTRY(m_pEntry), pAttrList);
+ pango_attr_list_unref(pAttrList);
+ }
+
+ virtual vcl::Font get_entry_font() override
+ {
+ if (m_xEntryFont)
+ return *m_xEntryFont;
+ assert(m_pEntry);
+ PangoContext* pContext = gtk_widget_get_pango_context(m_pEntry);
+ return pango_to_vcl(pango_context_get_font_description(pContext),
+ Application::GetSettings().GetUILanguageTag().getLocale());
+ }
+
+ virtual void disable_notify_events() override
+ {
+ if (m_pEditable)
+ {
+ g_signal_handler_block(m_pEditable, m_nEntryInsertTextSignalId);
+ g_signal_handler_block(m_pEntry, m_nEntryActivateSignalId);
+ g_signal_handler_block(m_pEntryFocusController, m_nEntryFocusInSignalId);
+ g_signal_handler_block(m_pEntryFocusController, m_nEntryFocusOutSignalId);
+ g_signal_handler_block(m_pEntryKeyController, m_nEntryKeyPressEventSignalId);
+ }
+ else
+ g_signal_handler_block(m_pKeyController, m_nKeyPressEventSignalId);
+
+// if (m_nToggleFocusInSignalId)
+// g_signal_handler_block(m_pToggleButton, m_nToggleFocusInSignalId);
+// if (m_nToggleFocusOutSignalId)
+// g_signal_handler_block(m_pToggleButton, m_nToggleFocusOutSignalId);
+// g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
+ g_signal_handler_block(m_pComboBox, m_nPopupShownSignalId);
+ g_signal_handler_block(m_pComboBox, m_nChangedSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pComboBox, m_nChangedSignalId);
+ g_signal_handler_unblock(m_pComboBox, m_nPopupShownSignalId);
+// g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
+// if (m_nToggleFocusInSignalId)
+// g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusInSignalId);
+// if (m_nToggleFocusOutSignalId)
+// g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusOutSignalId);
+ if (m_pEditable)
+ {
+ g_signal_handler_unblock(m_pEntry, m_nEntryActivateSignalId);
+ g_signal_handler_unblock(m_pEntryFocusController, m_nEntryFocusInSignalId);
+ g_signal_handler_unblock(m_pEntryFocusController, m_nEntryFocusOutSignalId);
+ g_signal_handler_unblock(m_pEntryKeyController, m_nEntryKeyPressEventSignalId);
+ g_signal_handler_unblock(m_pEditable, m_nEntryInsertTextSignalId);
+ }
+ else
+ g_signal_handler_unblock(m_pKeyController, m_nKeyPressEventSignalId);
+ }
+
+ virtual void freeze() override
+ {
+ disable_notify_events();
+ bool bIsFirstFreeze = IsFirstFreeze();
+ GtkInstanceWidget::freeze();
+ if (bIsFirstFreeze)
+ {
+ g_object_ref(m_pTreeModel);
+// gtk_tree_view_set_model(m_pTreeView, nullptr);
+ g_object_freeze_notify(G_OBJECT(m_pTreeModel));
+ if (m_xSorter)
+ {
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
+ }
+ }
+ enable_notify_events();
+ }
+
+ virtual void thaw() override
+ {
+ disable_notify_events();
+ if (IsLastThaw())
+ {
+ if (m_xSorter)
+ {
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
+ }
+ g_object_thaw_notify(G_OBJECT(m_pTreeModel));
+// gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
+ g_object_unref(m_pTreeModel);
+ }
+ GtkInstanceWidget::thaw();
+ enable_notify_events();
+ }
+
+ virtual bool get_popup_shown() const override
+ {
+ return m_bPopupActive;
+ }
+
+ virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
+ {
+// if (!m_nToggleFocusInSignalId)
+// m_nToggleFocusInSignalId = g_signal_connect_after(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this);
+ GtkInstanceWidget::connect_focus_in(rLink);
+ }
+
+ virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
+ {
+// if (!m_nToggleFocusOutSignalId)
+// m_nToggleFocusOutSignalId = g_signal_connect_after(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this);
+ GtkInstanceWidget::connect_focus_out(rLink);
+ }
+
+ virtual void grab_focus() override
+ {
+ if (has_focus())
+ return;
+ if (m_pEntry)
+ gtk_widget_grab_focus(m_pEntry);
+ else
+ {
+// gtk_widget_grab_focus(m_pToggleButton);
+ gtk_widget_grab_focus(GTK_WIDGET(m_pComboBox));
+ }
+ }
+
+ virtual bool has_focus() const override
+ {
+ if (m_pEntry && gtk_widget_has_focus(m_pEntry))
+ return true;
+
+// if (gtk_widget_has_focus(m_pToggleButton))
+// return true;
+
+#if 0
+ if (gtk_widget_get_visible(GTK_WIDGET(m_pMenuWindow)))
+ {
+ if (gtk_widget_has_focus(GTK_WIDGET(m_pOverlayButton)) || gtk_widget_has_focus(GTK_WIDGET(m_pTreeView)))
+ return true;
+ }
+#endif
+
+ return GtkInstanceWidget::has_focus();
+ }
+
+ virtual bool changed_by_direct_pick() const override
+ {
+ return m_bChangedByMenu;
+ }
+
+ virtual void set_custom_renderer(bool bOn) override
+ {
+ if (bOn == m_bCustomRenderer)
+ return;
+#if 0
+ GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
+ // keep the original height around for optimal popup height calculation
+ m_nNonCustomLineHeight = bOn ? ::get_height_row(m_pTreeView, pColumns) : -1;
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pColumns->data);
+ gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn));
+ if (bOn)
+ {
+ GtkCellRenderer *pRenderer = custom_cell_renderer_new();
+ GValue value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_POINTER);
+ g_value_set_pointer(&value, static_cast<gpointer>(this));
+ g_object_set_property(G_OBJECT(pRenderer), "instance", &value);
+ gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "id", m_nIdCol);
+ }
+ else
+ {
+ GtkCellRenderer *pRenderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
+ }
+ g_list_free(pColumns);
+ m_bCustomRenderer = bOn;
+#endif
+ }
+
+ void call_signal_custom_render(VirtualDevice& rOutput, const tools::Rectangle& rRect, bool bSelected, const OUString& rId)
+ {
+ signal_custom_render(rOutput, rRect, bSelected, rId);
+ }
+
+ Size call_signal_custom_get_size(VirtualDevice& rOutput)
+ {
+ return signal_custom_get_size(rOutput);
+ }
+
+ VclPtr<VirtualDevice> create_render_virtual_device() const override
+ {
+ return create_virtual_device();
+ }
+
+ virtual void set_item_menu(const OUString& rIdent, weld::Menu* pMenu) override
+ {
+#if 0
+ m_xCustomMenuButtonHelper.reset();
+ GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
+ GtkWidget* pMenuWidget = GTK_WIDGET(pPopoverWidget ? pPopoverWidget->getMenu() : nullptr);
+ gtk_menu_button_set_popup(m_pOverlayButton, pMenuWidget);
+ gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), pMenuWidget != nullptr);
+ gtk_widget_queue_resize_no_redraw(GTK_WIDGET(m_pOverlayButton)); // force location recalc
+ if (pMenuWidget)
+ m_xCustomMenuButtonHelper.reset(new CustomRenderMenuButtonHelper(GTK_MENU(pMenuWidget), GTK_TOGGLE_BUTTON(m_pToggleButton)));
+ m_sMenuButtonRow = rIdent;
+#else
+ (void)rIdent; (void)pMenu;
+#endif
+ }
+
+ OUString get_mru_entries() const override
+ {
+ const sal_Unicode cSep = ';';
+
+ OUStringBuffer aEntries;
+ for (sal_Int32 n = 0; n < m_nMRUCount; n++)
+ {
+ aEntries.append(get_text_including_mru(n));
+ if (n < m_nMRUCount - 1)
+ aEntries.append(cSep);
+ }
+ return aEntries.makeStringAndClear();
+ }
+
+ virtual void set_mru_entries(const OUString& rEntries) override
+ {
+ const sal_Unicode cSep = ';';
+
+ // Remove old MRU entries
+ for (sal_Int32 n = m_nMRUCount; n;)
+ remove_including_mru(--n);
+
+ sal_Int32 nMRUCount = 0;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OUString aEntry = rEntries.getToken(0, cSep, nIndex);
+ // Accept only existing entries
+ int nPos = find_text(aEntry);
+ if (nPos != -1)
+ {
+ OUString sId = get_id(nPos);
+ insert_including_mru(0, aEntry, &sId, nullptr, nullptr);
+ ++nMRUCount;
+ }
+ }
+ while (nIndex >= 0);
+
+ if (nMRUCount && !m_nMRUCount)
+ insert_separator_including_mru(nMRUCount, "separator");
+ else if (!nMRUCount && m_nMRUCount)
+ remove_including_mru(m_nMRUCount); // remove separator
+
+ m_nMRUCount = nMRUCount;
+ }
+
+ int get_menu_button_width() const override
+ {
+#if 0
+ bool bVisible = gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton));
+ if (!bVisible)
+ gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), true);
+ gint nWidth;
+ gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &nWidth, nullptr);
+ if (!bVisible)
+ gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), false);
+ return nWidth;
+#else
+ return 0;
+#endif
+ }
+
+ virtual ~GtkInstanceComboBox() override
+ {
+// m_xCustomMenuButtonHelper.reset();
+ do_clear();
+ if (m_nAutoCompleteIdleId)
+ g_source_remove(m_nAutoCompleteIdleId);
+ if (m_pEditable)
+ {
+ g_signal_handler_disconnect(m_pEditable, m_nEntryInsertTextSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nEntryActivateSignalId);
+ g_signal_handler_disconnect(m_pEntryFocusController, m_nEntryFocusInSignalId);
+ g_signal_handler_disconnect(m_pEntryFocusController, m_nEntryFocusOutSignalId);
+ g_signal_handler_disconnect(m_pEntryKeyController, m_nEntryKeyPressEventSignalId);
+ }
+ else
+ g_signal_handler_disconnect(m_pKeyController, m_nKeyPressEventSignalId);
+// if (m_nToggleFocusInSignalId)
+// g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusInSignalId);
+// if (m_nToggleFocusOutSignalId)
+// g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusOutSignalId);
+// g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
+ g_signal_handler_disconnect(m_pComboBox, m_nPopupShownSignalId);
+ g_signal_handler_disconnect(m_pComboBox, m_nChangedSignalId);
+
+// gtk_tree_view_set_model(m_pTreeView, nullptr);
+
+ }
+};
+
+#else
+
+class GtkInstanceComboBox : public GtkInstanceContainer, public vcl::ISearchableStringList, public virtual weld::ComboBox
+{
+private:
+ GtkBuilder* m_pComboBuilder;
+ GtkComboBox* m_pComboBox;
+ GtkOverlay* m_pOverlay;
+ GtkTreeView* m_pTreeView;
+ GtkMenuButton* m_pOverlayButton; // button that the StyleDropdown uses on an active row
+ GtkWindow* m_pMenuWindow;
+ GtkTreeModel* m_pTreeModel;
+ GtkCellRenderer* m_pButtonTextRenderer;
+ GtkCellRenderer* m_pMenuTextRenderer;
+ GtkWidget* m_pToggleButton;
+ GtkWidget* m_pEntry;
+ GtkCellView* m_pCellView;
+ WidgetFont m_aCustomFont;
+ std::unique_ptr<CustomRenderMenuButtonHelper> m_xCustomMenuButtonHelper;
+ std::optional<vcl::Font> m_xEntryFont;
+ std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
+ vcl::QuickSelectionEngine m_aQuickSelectionEngine;
+ std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>> m_aSeparatorRows;
+ OUString m_sMenuButtonRow;
+ bool m_bHoverSelection;
+ bool m_bMouseInOverlayButton;
+ bool m_bPopupActive;
+ bool m_bAutoComplete;
+ bool m_bAutoCompleteCaseSensitive;
+ bool m_bChangedByMenu;
+ bool m_bCustomRenderer;
+ bool m_bActivateCalled;
+ gint m_nTextCol;
+ gint m_nIdCol;
+ gulong m_nToggleFocusInSignalId;
+ gulong m_nToggleFocusOutSignalId;
+ gulong m_nRowActivatedSignalId;
+ gulong m_nChangedSignalId;
+ gulong m_nPopupShownSignalId;
+ gulong m_nKeyPressEventSignalId;
+ gulong m_nEntryInsertTextSignalId;
+ gulong m_nEntryActivateSignalId;
+ gulong m_nEntryFocusInSignalId;
+ gulong m_nEntryFocusOutSignalId;
+ gulong m_nEntryKeyPressEventSignalId;
+ gulong m_nEntryPopulatePopupMenuSignalId;
+ guint m_nAutoCompleteIdleId;
+ gint m_nNonCustomLineHeight;
+ gint m_nPrePopupCursorPos;
+ int m_nMRUCount;
+ int m_nMaxMRUCount;
+
+ static gboolean idleAutoComplete(gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->auto_complete();
+ return false;
+ }
+
+ void auto_complete()
+ {
+ m_nAutoCompleteIdleId = 0;
+ OUString aStartText = get_active_text();
+ int nStartPos, nEndPos;
+ get_entry_selection_bounds(nStartPos, nEndPos);
+ int nMaxSelection = std::max(nStartPos, nEndPos);
+ if (nMaxSelection != aStartText.getLength())
+ return;
+
+ disable_notify_events();
+ int nActive = get_active();
+ int nStart = nActive;
+
+ if (nStart == -1)
+ nStart = 0;
+
+ int nPos = -1;
+
+ int nZeroRow = 0;
+ if (m_nMRUCount)
+ nZeroRow += (m_nMRUCount + 1);
+
+ if (!m_bAutoCompleteCaseSensitive)
+ {
+ // Try match case insensitive from current position
+ nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, false);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case insensitive, but from start
+ nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, false);
+ }
+ }
+
+ if (nPos == -1)
+ {
+ // Try match case sensitive from current position
+ nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, true);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case sensitive, but from start
+ nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, true);
+ }
+ }
+
+ if (nPos != -1)
+ {
+ OUString aText = get_text_including_mru(nPos);
+ if (aText != aStartText)
+ {
+ SolarMutexGuard aGuard;
+ set_active_including_mru(nPos, true);
+ }
+ select_entry_region(aText.getLength(), aStartText.getLength());
+ }
+ enable_notify_events();
+ }
+
+ static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
+ gint* position, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
+ }
+
+ void signal_entry_insert_text(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength, gint* position)
+ {
+ // first filter inserted text
+ if (m_aEntryInsertTextHdl.IsSet())
+ {
+ OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
+ const bool bContinue = m_aEntryInsertTextHdl.Call(sText);
+ if (bContinue && !sText.isEmpty())
+ {
+ OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
+ g_signal_handlers_block_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
+ gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position);
+ g_signal_handlers_unblock_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
+ }
+ g_signal_stop_emission_by_name(pEntry, "insert-text");
+ }
+ if (m_bAutoComplete)
+ {
+ // now check for autocompletes
+ if (m_nAutoCompleteIdleId)
+ g_source_remove(m_nAutoCompleteIdleId);
+ m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
+ }
+ }
+
+ static void signalChanged(GtkEntry*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->fire_signal_changed();
+ }
+
+ void fire_signal_changed()
+ {
+ signal_changed();
+ m_bChangedByMenu = false;
+ }
+
+ static void signalPopupToggled(GtkToggleButton* /*pToggleButton*/, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_popup_toggled();
+ }
+
+ int get_popup_height(gint& rPopupWidth)
+ {
+ const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
+
+ int nMaxRows = rSettings.GetListBoxMaximumLineCount();
+ bool bAddScrollWidth = false;
+ int nRows = get_count_including_mru();
+ if (nMaxRows < nRows)
+ {
+ nRows = nMaxRows;
+ bAddScrollWidth = true;
+ }
+
+ GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
+ gint nRowHeight = get_height_row(m_pTreeView, pColumns);
+ g_list_free(pColumns);
+
+ gint nSeparatorHeight = get_height_row_separator(m_pTreeView);
+ gint nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nRows);
+
+ // if we're using a custom renderer, limit the height to the height nMaxRows would be
+ // for a normal renderer, and then round down to how many custom rows fit in that
+ // space
+ if (m_nNonCustomLineHeight != -1 && nRowHeight)
+ {
+ gint nNormalHeight = get_height_rows(m_nNonCustomLineHeight, nSeparatorHeight, nMaxRows);
+ if (nHeight > nNormalHeight)
+ {
+ gint nRowsOnly = nNormalHeight - get_height_rows(0, nSeparatorHeight, nMaxRows);
+ gint nCustomRows = (nRowsOnly + (nRowHeight - 1)) / nRowHeight;
+ nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nCustomRows);
+ }
+ }
+
+ if (bAddScrollWidth)
+ rPopupWidth += rSettings.GetScrollBarSize();
+
+ return nHeight;
+ }
+
+ void menu_toggled()
+ {
+ if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton)))
+ {
+ if (m_bHoverSelection)
+ {
+ // turn hover selection back off until mouse is moved again
+ // *after* menu is shown again
+ gtk_tree_view_set_hover_selection(m_pTreeView, false);
+ m_bHoverSelection = false;
+ }
+
+ bool bHadFocus = gtk_window_has_toplevel_focus(m_pMenuWindow);
+
+ do_ungrab(GTK_WIDGET(m_pMenuWindow));
+
+ gtk_widget_hide(GTK_WIDGET(m_pMenuWindow));
+
+ GdkSurface* pSurface = widget_get_surface(GTK_WIDGET(m_pMenuWindow));
+ g_object_set_data(G_OBJECT(pSurface), "g-lo-InstancePopup", GINT_TO_POINTER(false));
+
+ // so gdk_window_move_to_rect will work again the next time
+ gtk_widget_unrealize(GTK_WIDGET(m_pMenuWindow));
+
+ gtk_widget_set_size_request(GTK_WIDGET(m_pMenuWindow), -1, -1);
+
+ if (!m_bActivateCalled)
+ tree_view_set_cursor(m_nPrePopupCursorPos);
+
+ // undo show_menu tooltip blocking
+ GtkWidget* pParent = widget_get_toplevel(m_pToggleButton);
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
+ if (pFrame)
+ pFrame->UnblockTooltip();
+
+ if (bHadFocus)
+ {
+ GdkSurface* pParentSurface = pParent ? widget_get_surface(pParent) : nullptr;
+ void* pParentIsPopover = pParentSurface ? g_object_get_data(G_OBJECT(pParentSurface), "g-lo-InstancePopup") : nullptr;
+ if (pParentIsPopover)
+ do_grab(m_pToggleButton);
+ gtk_widget_grab_focus(m_pToggleButton);
+ }
+ }
+ else
+ {
+ GtkWidget* pComboBox = GTK_WIDGET(getContainer());
+
+ gint nComboWidth = gtk_widget_get_allocated_width(pComboBox);
+ GtkRequisition size;
+ gtk_widget_get_preferred_size(GTK_WIDGET(m_pMenuWindow), nullptr, &size);
+
+ gint nPopupWidth = size.width;
+ gint nPopupHeight = get_popup_height(nPopupWidth);
+ nPopupWidth = std::max(nPopupWidth, nComboWidth);
+
+ gtk_widget_set_size_request(GTK_WIDGET(m_pMenuWindow), nPopupWidth, nPopupHeight);
+
+ m_nPrePopupCursorPos = get_active();
+
+ m_bActivateCalled = false;
+
+ // if we are in mru mode always start with the cursor at the top of the menu
+ if (m_nMaxMRUCount)
+ tree_view_set_cursor(0);
+
+ GdkRectangle aAnchor {0, 0, gtk_widget_get_allocated_width(pComboBox), gtk_widget_get_allocated_height(pComboBox) };
+ show_menu(pComboBox, m_pMenuWindow, aAnchor, weld::Placement::Under, true);
+ GdkSurface* pSurface = widget_get_surface(GTK_WIDGET(m_pMenuWindow));
+ g_object_set_data(G_OBJECT(pSurface), "g-lo-InstancePopup", GINT_TO_POINTER(true));
+ }
+ }
+
+ virtual void signal_popup_toggled() override
+ {
+ m_aQuickSelectionEngine.Reset();
+
+ menu_toggled();
+
+ bool bIsShown = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton));
+ if (m_bPopupActive == bIsShown)
+ return;
+
+ m_bPopupActive = bIsShown;
+ ComboBox::signal_popup_toggled();
+ if (!m_bPopupActive && m_pEntry)
+ {
+ disable_notify_events();
+ //restore focus to the GtkEntry when the popup is gone, which
+ //is what the vcl case does, to ease the transition a little
+ gtk_widget_grab_focus(m_pEntry);
+ enable_notify_events();
+ }
+ }
+
+ static gboolean signalEntryFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_entry_focus_in();
+ return false;
+ }
+
+ void signal_entry_focus_in()
+ {
+ signal_focus_in();
+ }
+
+ static gboolean signalEntryFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_entry_focus_out();
+ return false;
+ }
+
+ void signal_entry_focus_out()
+ {
+ // if we have an untidy selection on losing focus remove the selection
+ int nStartPos, nEndPos;
+ if (get_entry_selection_bounds(nStartPos, nEndPos))
+ {
+ int nMin = std::min(nStartPos, nEndPos);
+ int nMax = std::max(nStartPos, nEndPos);
+ if (nMin != 0 || nMax != get_active_text().getLength())
+ select_entry_region(0, 0);
+ }
+ signal_focus_out();
+ }
+
+ static void signalEntryActivate(GtkEntry*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_entry_activate();
+ }
+
+ void signal_entry_activate()
+ {
+ if (m_aEntryActivateHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ if (m_aEntryActivateHdl.Call(*this))
+ g_signal_stop_emission_by_name(m_pEntry, "activate");
+ }
+ update_mru();
+ }
+
+ OUString get(int pos, int col) const
+ {
+ OUString sRet;
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ {
+ gchar* pStr;
+ gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
+ sRet = OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pStr);
+ }
+ return sRet;
+ }
+
+ void set(int pos, int col, std::u16string_view rText)
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ {
+ OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gtk_list_store_set(GTK_LIST_STORE(m_pTreeModel), &iter, col, aStr.getStr(), -1);
+ }
+ }
+
+ int find(std::u16string_view rStr, int col, bool bSearchMRUArea) const
+ {
+ GtkTreeIter iter;
+ if (!gtk_tree_model_get_iter_first(m_pTreeModel, &iter))
+ return -1;
+
+ int nRet = 0;
+
+ if (!bSearchMRUArea && m_nMRUCount)
+ {
+ if (!gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, m_nMRUCount + 1))
+ return -1;
+ nRet += (m_nMRUCount + 1);
+ }
+
+ OString aStr(OUStringToOString(rStr, RTL_TEXTENCODING_UTF8));
+ do
+ {
+ gchar* pStr;
+ gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
+ const bool bEqual = g_strcmp0(pStr, aStr.getStr()) == 0;
+ g_free(pStr);
+ if (bEqual)
+ return nRet;
+ ++nRet;
+ } while (gtk_tree_model_iter_next(m_pTreeModel, &iter));
+
+ return -1;
+ }
+
+ bool separator_function(const GtkTreePath* path)
+ {
+ return ::separator_function(path, m_aSeparatorRows);
+ }
+
+ bool separator_function(int pos)
+ {
+ GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
+ bool bRet = separator_function(path);
+ gtk_tree_path_free(path);
+ return bRet;
+ }
+
+ static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter);
+ bool bRet = pThis->separator_function(path);
+ gtk_tree_path_free(path);
+ return bRet;
+ }
+
+ // https://gitlab.gnome.org/GNOME/gtk/issues/310
+ //
+ // in the absence of a built-in solution
+ // a) support typeahead for the case where there is no entry widget, typing ahead
+ // into the button itself will select via the vcl selection engine, a matching
+ // entry
+ static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ return pThis->signal_key_press(pEvent);
+ }
+
+ // tdf#131076 we want return in a ComboBox to act like return in a
+ // GtkEntry and activate the default dialog/assistant button
+ bool combobox_activate()
+ {
+ GtkWidget *pComboBox = GTK_WIDGET(m_pToggleButton);
+ GtkWidget *pToplevel = widget_get_toplevel(pComboBox);
+ GtkWindow *pWindow = GTK_WINDOW(pToplevel);
+ if (!pWindow)
+ return false;
+ if (!GTK_IS_DIALOG(pWindow) && !GTK_IS_ASSISTANT(pWindow))
+ return false;
+ bool bDone = false;
+ GtkWidget *pDefaultWidget = gtk_window_get_default_widget(pWindow);
+ if (pDefaultWidget && pDefaultWidget != m_pToggleButton && gtk_widget_get_sensitive(pDefaultWidget))
+ bDone = gtk_widget_activate(pDefaultWidget);
+ return bDone;
+ }
+
+ static gboolean signalEntryKeyPress(GtkEntry* pEntry, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ LocalizeDecimalSeparator(pEvent->keyval);
+ if (signalEntryInsertSpecialCharKeyPress(pEntry, pEvent, nullptr))
+ return true;
+ return pThis->signal_entry_key_press(pEvent);
+ }
+
+ bool signal_entry_key_press(const GdkEventKey* pEvent)
+ {
+ KeyEvent aKEvt(GtkToVcl(*pEvent));
+
+ vcl::KeyCode aKeyCode = aKEvt.GetKeyCode();
+
+ bool bDone = false;
+
+ auto nCode = aKeyCode.GetCode();
+ switch (nCode)
+ {
+ case KEY_DOWN:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nCount = get_count_including_mru();
+ int nActive = get_active_including_mru() + 1;
+ while (nActive < nCount && separator_function(nActive))
+ ++nActive;
+ if (nActive < nCount)
+ set_active_including_mru(nActive, true);
+ bDone = true;
+ }
+ else if (nKeyMod == KEY_MOD2 && !m_bPopupActive)
+ {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), true);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_UP:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nStartBound = m_bPopupActive || !m_nMRUCount ? 0 : (m_nMRUCount + 1);
+ int nActive = get_active_including_mru() - 1;
+ while (nActive >= nStartBound && separator_function(nActive))
+ --nActive;
+ if (nActive >= nStartBound)
+ set_active_including_mru(nActive, true);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_PAGEUP:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nCount = get_count_including_mru();
+ int nStartBound = m_bPopupActive || !m_nMaxMRUCount ? 0 : (m_nMRUCount + 1);
+ int nActive = nStartBound;
+ while (nActive < nCount && separator_function(nActive))
+ ++nActive;
+ if (nActive < nCount)
+ set_active_including_mru(nActive, true);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_PAGEDOWN:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nActive = get_count_including_mru() - 1;
+ int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
+ while (nActive >= nStartBound && separator_function(nActive))
+ --nActive;
+ if (nActive >= nStartBound)
+ set_active_including_mru(nActive, true);
+ bDone = true;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return bDone;
+ }
+
+ bool signal_key_press(const GdkEventKey* pEvent)
+ {
+ if (m_bHoverSelection)
+ {
+ // once a key is pressed, turn off hover selection until mouse is
+ // moved again otherwise when the treeview scrolls it jumps to the
+ // position under the mouse.
+ gtk_tree_view_set_hover_selection(m_pTreeView, false);
+ m_bHoverSelection = false;
+ }
+
+ KeyEvent aKEvt(GtkToVcl(*pEvent));
+
+ vcl::KeyCode aKeyCode = aKEvt.GetKeyCode();
+
+ bool bDone = false;
+
+ auto nCode = aKeyCode.GetCode();
+ switch (nCode)
+ {
+ case KEY_DOWN:
+ case KEY_UP:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN:
+ case KEY_HOME:
+ case KEY_END:
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ case KEY_RETURN:
+ {
+ m_aQuickSelectionEngine.Reset();
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ // tdf#131076 don't let bare return toggle menu popup active, but do allow deactivate
+ if (nCode == KEY_RETURN && !nKeyMod && !m_bPopupActive)
+ bDone = combobox_activate();
+ else if (nCode == KEY_UP && nKeyMod == KEY_MOD2 && m_bPopupActive)
+ {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+ bDone = true;
+ }
+ else if (nCode == KEY_DOWN && nKeyMod == KEY_MOD2 && !m_bPopupActive)
+ {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), true);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_ESCAPE:
+ {
+ m_aQuickSelectionEngine.Reset();
+ if (m_bPopupActive)
+ {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+ bDone = true;
+ }
+ break;
+ }
+ default:
+ // tdf#131076 let base space toggle menu popup when it's not already visible
+ if (nCode == KEY_SPACE && !aKeyCode.GetModifier() && !m_bPopupActive)
+ bDone = false;
+ else
+ bDone = m_aQuickSelectionEngine.HandleKeyEvent(aKEvt);
+ break;
+ }
+
+ if (!bDone && !m_pEntry)
+ bDone = signal_entry_key_press(pEvent);
+
+ return bDone;
+ }
+
+ vcl::StringEntryIdentifier typeahead_getEntry(int nPos, OUString& out_entryText) const
+ {
+ int nEntryCount(get_count_including_mru());
+ if (nPos >= nEntryCount)
+ nPos = 0;
+ out_entryText = get_text_including_mru(nPos);
+
+ // vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based
+ // => normalize
+ return reinterpret_cast<vcl::StringEntryIdentifier>(nPos + 1);
+ }
+
+ static int typeahead_getEntryPos(vcl::StringEntryIdentifier entry)
+ {
+ // our pos is 0-based, but StringEntryIdentifier does not allow for a NULL
+ return reinterpret_cast<sal_Int64>(entry) - 1;
+ }
+
+ void tree_view_set_cursor(int pos)
+ {
+ GtkTreePath* path;
+ if (pos == -1)
+ {
+ path = gtk_tree_path_new_from_indices(G_MAXINT, -1);
+ gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(m_pTreeView));
+ if (m_pCellView)
+ gtk_cell_view_set_displayed_row(m_pCellView, nullptr);
+ }
+ else
+ {
+ path = gtk_tree_path_new_from_indices(pos, -1);
+ if (gtk_tree_view_get_model(m_pTreeView))
+ gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
+ if (m_pCellView)
+ gtk_cell_view_set_displayed_row(m_pCellView, path);
+ }
+ gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
+ gtk_tree_path_free(path);
+ }
+
+ int tree_view_get_cursor() const
+ {
+ int nRet = -1;
+
+ GtkTreePath* path;
+ gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
+ if (path)
+ {
+ gint depth;
+ gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
+ nRet = indices[depth-1];
+ gtk_tree_path_free(path);
+ }
+
+ return nRet;
+ }
+
+ int get_selected_entry() const
+ {
+ if (m_bPopupActive)
+ return tree_view_get_cursor();
+ else
+ return get_active_including_mru();
+ }
+
+ void set_typeahead_selected_entry(int nSelect)
+ {
+ if (m_bPopupActive)
+ tree_view_set_cursor(nSelect);
+ else
+ set_active_including_mru(nSelect, true);
+ }
+
+ virtual vcl::StringEntryIdentifier CurrentEntry(OUString& out_entryText) const override
+ {
+ int nCurrentPos = get_selected_entry();
+ return typeahead_getEntry((nCurrentPos == -1) ? 0 : nCurrentPos, out_entryText);
+ }
+
+ virtual vcl::StringEntryIdentifier NextEntry(vcl::StringEntryIdentifier currentEntry, OUString& out_entryText) const override
+ {
+ int nNextPos = typeahead_getEntryPos(currentEntry) + 1;
+ return typeahead_getEntry(nNextPos, out_entryText);
+ }
+
+ virtual void SelectEntry(vcl::StringEntryIdentifier entry) override
+ {
+ int nSelect = typeahead_getEntryPos(entry);
+ if (nSelect == get_selected_entry())
+ {
+ // 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
+ int nCount = get_count_including_mru();
+ if (nSelect >= nCount)
+ nSelect = nCount ? nCount-1 : -1;
+
+ set_typeahead_selected_entry(nSelect);
+ }
+
+ static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->grab_broken(pEvent);
+ }
+
+ void grab_broken(const GdkEventGrabBroken *event)
+ {
+ if (event->grab_window == nullptr)
+ {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+ }
+ else if (!g_object_get_data(G_OBJECT(event->grab_window), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
+ {
+ //try and regrab, so when we lose the grab to the menu of the color palette
+ //combobox we regain it so the color palette doesn't itself disappear on next
+ //click on the color palette combobox
+ do_grab(GTK_WIDGET(m_pMenuWindow));
+ }
+ }
+
+ static gboolean signalButtonPress(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ return pThis->button_press(pEvent);
+ }
+
+ bool button_press(GdkEventButton* pEvent)
+ {
+ //we want to pop down if the button was pressed outside our popup
+ if (button_event_is_outside(GTK_WIDGET(m_pMenuWindow), pEvent))
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+ return false;
+ }
+
+ static gboolean signalMotion(GtkWidget*, GdkEventMotion*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_motion();
+ return false;
+ }
+
+ void signal_motion()
+ {
+ // if hover-selection was disabled after pressing a key, then turn it back on again
+ if (!m_bHoverSelection && !m_bMouseInOverlayButton)
+ {
+ gtk_tree_view_set_hover_selection(m_pTreeView, true);
+ m_bHoverSelection = true;
+ }
+ }
+
+ static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->handle_row_activated();
+ }
+
+ void handle_row_activated()
+ {
+ m_bActivateCalled = true;
+ m_bChangedByMenu = true;
+ disable_notify_events();
+ int nActive = get_active();
+ if (m_pEntry)
+ gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(get_text(nActive), RTL_TEXTENCODING_UTF8).getStr());
+ else
+ tree_view_set_cursor(nActive);
+ enable_notify_events();
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+ fire_signal_changed();
+ update_mru();
+ }
+
+ void do_clear()
+ {
+ disable_notify_events();
+ gtk_tree_view_set_row_separator_func(m_pTreeView, nullptr, nullptr, nullptr);
+ m_aSeparatorRows.clear();
+ gtk_list_store_clear(GTK_LIST_STORE(m_pTreeModel));
+ m_nMRUCount = 0;
+ enable_notify_events();
+ }
+
+ virtual int get_max_mru_count() const override
+ {
+ return m_nMaxMRUCount;
+ }
+
+ virtual void set_max_mru_count(int nMaxMRUCount) override
+ {
+ m_nMaxMRUCount = nMaxMRUCount;
+ update_mru();
+ }
+
+ void update_mru()
+ {
+ int nMRUCount = m_nMRUCount;
+
+ if (m_nMaxMRUCount)
+ {
+ OUString sActiveText = get_active_text();
+ OUString sActiveId = get_active_id();
+ insert_including_mru(0, sActiveText, &sActiveId, nullptr, nullptr);
+ ++m_nMRUCount;
+
+ for (int i = 1; i < m_nMRUCount - 1; ++i)
+ {
+ if (get_text_including_mru(i) == sActiveText)
+ {
+ remove_including_mru(i);
+ --m_nMRUCount;
+ break;
+ }
+ }
+ }
+
+ while (m_nMRUCount > m_nMaxMRUCount)
+ {
+ remove_including_mru(m_nMRUCount - 1);
+ --m_nMRUCount;
+ }
+
+ if (m_nMRUCount && !nMRUCount)
+ insert_separator_including_mru(m_nMRUCount, "separator");
+ else if (!m_nMRUCount && nMRUCount)
+ remove_including_mru(m_nMRUCount); // remove separator
+ }
+
+ int get_count_including_mru() const
+ {
+ return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr);
+ }
+
+ int get_active_including_mru() const
+ {
+ return tree_view_get_cursor();
+ }
+
+ void set_active_including_mru(int pos, bool bInteractive)
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't set_active when frozen, set_active after thaw. Note selection doesn't survive a freeze");
+
+ disable_notify_events();
+
+ tree_view_set_cursor(pos);
+
+ if (m_pEntry)
+ {
+ if (pos != -1)
+ gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(get_text_including_mru(pos), RTL_TEXTENCODING_UTF8).getStr());
+ else
+ gtk_entry_set_text(GTK_ENTRY(m_pEntry), "");
+ }
+
+ m_bChangedByMenu = false;
+ enable_notify_events();
+
+ if (bInteractive && !m_bPopupActive)
+ signal_changed();
+ }
+
+ int find_text_including_mru(std::u16string_view rStr, bool bSearchMRU) const
+ {
+ return find(rStr, m_nTextCol, bSearchMRU);
+ }
+
+ int find_id_including_mru(std::u16string_view rId, bool bSearchMRU) const
+ {
+ return find(rId, m_nIdCol, bSearchMRU);
+ }
+
+ OUString get_text_including_mru(int pos) const
+ {
+ return get(pos, m_nTextCol);
+ }
+
+ OUString get_id_including_mru(int pos) const
+ {
+ return get(pos, m_nIdCol);
+ }
+
+ void set_id_including_mru(int pos, std::u16string_view rId)
+ {
+ set(pos, m_nIdCol, rId);
+ }
+
+ void remove_including_mru(int pos)
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
+ if (!m_aSeparatorRows.empty())
+ {
+ bool bFound = false;
+
+ GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
+
+ for (auto aIter = m_aSeparatorRows.begin(); aIter != m_aSeparatorRows.end(); ++aIter)
+ {
+ GtkTreePath* seppath = gtk_tree_row_reference_get_path(aIter->get());
+ if (seppath)
+ {
+ if (gtk_tree_path_compare(pPath, seppath) == 0)
+ bFound = true;
+ gtk_tree_path_free(seppath);
+ }
+ if (bFound)
+ {
+ m_aSeparatorRows.erase(aIter);
+ break;
+ }
+ }
+
+ gtk_tree_path_free(pPath);
+ }
+ gtk_list_store_remove(GTK_LIST_STORE(m_pTreeModel), &iter);
+ enable_notify_events();
+ }
+
+ void insert_separator_including_mru(int pos, const OUString& rId)
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ if (!gtk_tree_view_get_row_separator_func(m_pTreeView))
+ gtk_tree_view_set_row_separator_func(m_pTreeView, separatorFunction, this, nullptr);
+ insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, &rId, u"", nullptr, nullptr);
+ GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
+ m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(m_pTreeModel, pPath));
+ gtk_tree_path_free(pPath);
+ enable_notify_events();
+ }
+
+ void insert_including_mru(int pos, std::u16string_view rText, const OUString* pId, const OUString* pIconName, const VirtualDevice* pImageSurface)
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, pId, rText, pIconName, pImageSurface);
+ enable_notify_events();
+ }
+
+ static gboolean signalGetChildPosition(GtkOverlay*, GtkWidget*, GdkRectangle* pAllocation, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ return pThis->signal_get_child_position(pAllocation);
+ }
+
+ bool signal_get_child_position(GdkRectangle* pAllocation)
+ {
+ if (!gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton)))
+ return false;
+ if (!gtk_widget_get_realized(GTK_WIDGET(m_pTreeView)))
+ return false;
+ int nRow = find_id_including_mru(m_sMenuButtonRow, true);
+ if (nRow == -1)
+ return false;
+
+ gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &pAllocation->width, nullptr);
+
+ GtkTreePath* pPath = gtk_tree_path_new_from_indices(nRow, -1);
+ GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
+ tools::Rectangle aRect = get_row_area(m_pTreeView, pColumns, pPath);
+ gtk_tree_path_free(pPath);
+ g_list_free(pColumns);
+
+ pAllocation->x = aRect.Right() - pAllocation->width;
+ pAllocation->y = aRect.Top();
+ pAllocation->height = aRect.GetHeight();
+
+ return true;
+ }
+
+ static gboolean signalOverlayButtonCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_overlay_button_crossing(pEvent->type == GDK_ENTER_NOTIFY);
+ return false;
+ }
+
+ void signal_overlay_button_crossing(bool bEnter)
+ {
+ m_bMouseInOverlayButton = bEnter;
+ if (!bEnter)
+ return;
+
+ if (m_bHoverSelection)
+ {
+ // once toggled button is pressed, turn off hover selection until
+ // mouse leaves the overlay button
+ gtk_tree_view_set_hover_selection(m_pTreeView, false);
+ m_bHoverSelection = false;
+ }
+ int nRow = find_id_including_mru(m_sMenuButtonRow, true);
+ assert(nRow != -1);
+ tree_view_set_cursor(nRow); // select the buttons row
+ }
+
+ void signal_combo_mnemonic_activate()
+ {
+ if (m_pEntry)
+ gtk_widget_grab_focus(m_pEntry);
+ else
+ gtk_widget_grab_focus(m_pToggleButton);
+ }
+
+ static gboolean signalComboMnemonicActivate(GtkWidget*, gboolean, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_combo_mnemonic_activate();
+ return true;
+ }
+
+ static gboolean signalComboTooltipQuery(GtkWidget* /*pWidget*/, gint x, gint y,
+ gboolean keyboard_mode, GtkTooltip *tooltip,
+ gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ return signalTooltipQuery(GTK_WIDGET(pThis->m_pComboBox), x, y, keyboard_mode, tooltip);
+ }
+
+ int include_mru(int pos)
+ {
+ if (m_nMRUCount && pos != -1)
+ pos += (m_nMRUCount + 1);
+ return pos;
+ }
+
+public:
+ GtkInstanceComboBox(GtkBuilder* pComboBuilder, GtkComboBox* pComboBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceContainer(GTK_CONTAINER(gtk_builder_get_object(pComboBuilder, "box")), pBuilder, bTakeOwnership)
+ , m_pComboBuilder(pComboBuilder)
+ , m_pComboBox(pComboBox)
+ , m_pOverlay(GTK_OVERLAY(gtk_builder_get_object(pComboBuilder, "overlay")))
+ , m_pTreeView(GTK_TREE_VIEW(gtk_builder_get_object(pComboBuilder, "treeview")))
+ , m_pOverlayButton(GTK_MENU_BUTTON(gtk_builder_get_object(pComboBuilder, "overlaybutton")))
+ , m_pMenuWindow(GTK_WINDOW(gtk_builder_get_object(pComboBuilder, "popup")))
+ , m_pTreeModel(gtk_combo_box_get_model(pComboBox))
+ , m_pButtonTextRenderer(nullptr)
+ , m_pToggleButton(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "button")))
+ , m_pEntry(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "entry")))
+ , m_pCellView(nullptr)
+ , m_aCustomFont(m_pWidget)
+ , m_aQuickSelectionEngine(*this)
+ , m_bHoverSelection(false)
+ , m_bMouseInOverlayButton(false)
+ , m_bPopupActive(false)
+ , m_bAutoComplete(false)
+ , m_bAutoCompleteCaseSensitive(false)
+ , m_bChangedByMenu(false)
+ , m_bCustomRenderer(false)
+ , m_bActivateCalled(false)
+ , m_nTextCol(gtk_combo_box_get_entry_text_column(pComboBox))
+ , m_nIdCol(gtk_combo_box_get_id_column(pComboBox))
+ , m_nToggleFocusInSignalId(0)
+ , m_nToggleFocusOutSignalId(0)
+ , m_nRowActivatedSignalId(g_signal_connect(m_pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
+ , m_nChangedSignalId(g_signal_connect(m_pEntry, "changed", G_CALLBACK(signalChanged), this))
+ , m_nPopupShownSignalId(g_signal_connect(m_pToggleButton, "toggled", G_CALLBACK(signalPopupToggled), this))
+ , m_nAutoCompleteIdleId(0)
+ , m_nNonCustomLineHeight(-1)
+ , m_nPrePopupCursorPos(-1)
+ , m_nMRUCount(0)
+ , m_nMaxMRUCount(0)
+ {
+ int nActive = gtk_combo_box_get_active(m_pComboBox);
+
+ if (gtk_style_context_has_class(gtk_widget_get_style_context(GTK_WIDGET(m_pComboBox)), "small-button"))
+ gtk_style_context_add_class(gtk_widget_get_style_context(GTK_WIDGET(getContainer())), "small-button");
+
+ if (gtk_widget_get_has_tooltip(GTK_WIDGET(m_pComboBox)))
+ {
+ gtk_widget_set_has_tooltip(GTK_WIDGET(getContainer()), true);
+ g_signal_connect(getContainer(), "query-tooltip", G_CALLBACK(signalComboTooltipQuery), this);
+ }
+
+ insertAsParent(GTK_WIDGET(m_pComboBox), GTK_WIDGET(getContainer()));
+ gtk_widget_set_visible(GTK_WIDGET(m_pComboBox), false);
+ gtk_widget_set_no_show_all(GTK_WIDGET(m_pComboBox), true);
+
+ gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
+ /* tdf#136455 gtk_combo_box_set_model with a null Model should be good
+ enough. But in practice, while the ComboBox model is unset, GTK
+ doesn't unset the ComboBox menus model, so that remains listening to
+ additions to the ListStore and slowing things down massively.
+ Using a new model does reset the menu to listen to that unused one instead */
+ gtk_combo_box_set_model(m_pComboBox, GTK_TREE_MODEL(gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING)));
+
+ GtkTreeViewColumn* pCol = gtk_tree_view_column_new();
+ gtk_tree_view_append_column(m_pTreeView, pCol);
+
+ bool bPixbufUsedSurface = gtk_tree_model_get_n_columns(m_pTreeModel) == 4;
+
+ GList* cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_pComboBox));
+ // move the cell renderers from the combobox to the replacement treeview
+ m_pMenuTextRenderer = static_cast<GtkCellRenderer*>(cells->data);
+ for (GList* pRenderer = g_list_first(cells); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ bool bTextRenderer = pCellRenderer == m_pMenuTextRenderer;
+ gtk_tree_view_column_pack_end(pCol, pCellRenderer, bTextRenderer);
+ if (!bTextRenderer)
+ {
+ if (bPixbufUsedSurface)
+ gtk_tree_view_column_set_attributes(pCol, pCellRenderer, "surface", 3, nullptr);
+ else
+ gtk_tree_view_column_set_attributes(pCol, pCellRenderer, "pixbuf", 2, nullptr);
+ }
+ }
+
+ gtk_tree_view_column_set_attributes(pCol, m_pMenuTextRenderer, "text", m_nTextCol, nullptr);
+
+ if (gtk_combo_box_get_has_entry(m_pComboBox))
+ {
+ m_bAutoComplete = true;
+ m_nEntryInsertTextSignalId = g_signal_connect(m_pEntry, "insert-text", G_CALLBACK(signalEntryInsertText), this);
+ m_nEntryActivateSignalId = g_signal_connect(m_pEntry, "activate", G_CALLBACK(signalEntryActivate), this);
+ m_nEntryFocusInSignalId = g_signal_connect(m_pEntry, "focus-in-event", G_CALLBACK(signalEntryFocusIn), this);
+ m_nEntryFocusOutSignalId = g_signal_connect(m_pEntry, "focus-out-event", G_CALLBACK(signalEntryFocusOut), this);
+ m_nEntryKeyPressEventSignalId = g_signal_connect(m_pEntry, "key-press-event", G_CALLBACK(signalEntryKeyPress), this);
+ m_nEntryPopulatePopupMenuSignalId = g_signal_connect(m_pEntry, "populate-popup", G_CALLBACK(signalEntryPopulatePopup), nullptr);
+ m_nKeyPressEventSignalId = 0;
+ }
+ else
+ {
+ gtk_widget_set_visible(m_pEntry, false);
+ m_pEntry = nullptr;
+
+ GtkWidget* pArrow = GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "arrow"));
+ gtk_container_child_set(getContainer(), m_pToggleButton, "expand", true, nullptr);
+
+ auto m_pCellArea = gtk_cell_area_box_new();
+ m_pCellView = GTK_CELL_VIEW(gtk_cell_view_new_with_context(m_pCellArea, nullptr));
+ gtk_widget_set_hexpand(GTK_WIDGET(m_pCellView), true);
+ GtkBox* pBox = GTK_BOX(gtk_widget_get_parent(pArrow));
+
+ gint nImageSpacing(2);
+ GtkStyleContext *pContext = gtk_widget_get_style_context(GTK_WIDGET(m_pToggleButton));
+ gtk_style_context_get_style(pContext, "image-spacing", &nImageSpacing, nullptr);
+ gtk_box_set_spacing(pBox, nImageSpacing);
+
+ gtk_box_pack_start(pBox, GTK_WIDGET(m_pCellView), false, true, 0);
+
+ gtk_cell_view_set_fit_model(m_pCellView, true);
+ gtk_cell_view_set_model(m_pCellView, m_pTreeModel);
+
+ m_pButtonTextRenderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pCellView), m_pButtonTextRenderer, true);
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), m_pButtonTextRenderer, "text", m_nTextCol, nullptr);
+ if (g_list_length(cells) > 1)
+ {
+ GtkCellRenderer* pCellRenderer = gtk_cell_renderer_pixbuf_new();
+ gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, false);
+ if (bPixbufUsedSurface)
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, "surface", 3, nullptr);
+ else
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, "pixbuf", 2, nullptr);
+ }
+
+ gtk_widget_show_all(GTK_WIDGET(m_pCellView));
+
+ m_nEntryInsertTextSignalId = 0;
+ m_nEntryActivateSignalId = 0;
+ m_nEntryFocusInSignalId = 0;
+ m_nEntryFocusOutSignalId = 0;
+ m_nEntryKeyPressEventSignalId = 0;
+ m_nEntryPopulatePopupMenuSignalId = 0;
+ m_nKeyPressEventSignalId = g_signal_connect(m_pToggleButton, "key-press-event", G_CALLBACK(signalKeyPress), this);
+ }
+
+ g_list_free(cells);
+
+ if (nActive != -1)
+ tree_view_set_cursor(nActive);
+
+ g_signal_connect(getContainer(), "mnemonic-activate", G_CALLBACK(signalComboMnemonicActivate), this);
+
+ g_signal_connect(m_pMenuWindow, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
+ g_signal_connect(m_pMenuWindow, "button-press-event", G_CALLBACK(signalButtonPress), this);
+ g_signal_connect(m_pMenuWindow, "motion-notify-event", G_CALLBACK(signalMotion), this);
+ // support typeahead for the menu itself, typing into the menu will
+ // select via the vcl selection engine, a matching entry.
+ g_signal_connect(m_pMenuWindow, "key-press-event", G_CALLBACK(signalKeyPress), this);
+
+ g_signal_connect(m_pOverlay, "get-child-position", G_CALLBACK(signalGetChildPosition), this);
+ gtk_overlay_add_overlay(m_pOverlay, GTK_WIDGET(m_pOverlayButton));
+ g_signal_connect(m_pOverlayButton, "leave-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
+ g_signal_connect(m_pOverlayButton, "enter-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
+ }
+
+ virtual int get_active() const override
+ {
+ int nActive = get_active_including_mru();
+ if (nActive == -1)
+ return -1;
+
+ if (m_nMRUCount)
+ {
+ if (nActive < m_nMRUCount)
+ nActive = find_text(get_text_including_mru(nActive));
+ else
+ nActive -= (m_nMRUCount + 1);
+ }
+
+ return nActive;
+ }
+
+ virtual OUString get_active_id() const override
+ {
+ int nActive = get_active();
+ return nActive != -1 ? get_id(nActive) : OUString();
+ }
+
+ virtual void set_active_id(const OUString& rStr) override
+ {
+ set_active(find_id(rStr));
+ m_bChangedByMenu = false;
+ }
+
+ virtual void set_size_request(int nWidth, int nHeight) override
+ {
+ if (m_pButtonTextRenderer)
+ {
+ // tweak the cell render to get a narrower size to stick
+ if (nWidth != -1)
+ {
+ // this bit isn't great, I really want to be able to ellipse the text in the comboboxtext itself and let
+ // the popup menu render them in full, in the interim ellipse both of them
+ g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_MIDDLE, nullptr);
+
+ // to find out how much of the width of the combobox belongs to the cell, set
+ // the cell and widget to the min cell width and see what the difference is
+ int min;
+ gtk_cell_renderer_get_preferred_width(m_pButtonTextRenderer, m_pWidget, &min, nullptr);
+ gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, min, -1);
+ gtk_widget_set_size_request(m_pWidget, min, -1);
+ int nNonCellWidth = get_preferred_size().Width() - min;
+
+ int nCellWidth = nWidth - nNonCellWidth;
+ if (nCellWidth >= 0)
+ {
+ // now set the cell to the max width which it can be within the
+ // requested widget width
+ gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, nWidth - nNonCellWidth, -1);
+ }
+ }
+ else
+ {
+ g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_NONE, nullptr);
+ gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, -1, -1);
+ }
+ }
+
+ gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
+ }
+
+ virtual void set_active(int pos) override
+ {
+ set_active_including_mru(include_mru(pos), false);
+ }
+
+ virtual OUString get_active_text() const override
+ {
+ if (m_pEntry)
+ {
+ const gchar* pText = gtk_entry_get_text(GTK_ENTRY(m_pEntry));
+ return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ int nActive = get_active();
+ if (nActive == -1)
+ return OUString();
+
+ return get_text(nActive);
+ }
+
+ virtual OUString get_text(int pos) const override
+ {
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ return get_text_including_mru(pos);
+ }
+
+ virtual OUString get_id(int pos) const override
+ {
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ return get_id_including_mru(pos);
+ }
+
+ virtual void set_id(int pos, const OUString& rId) override
+ {
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ set_id_including_mru(pos, rId);
+ }
+
+ virtual void insert_vector(const std::vector<weld::ComboBoxEntry>& rItems, bool bKeepExisting) override
+ {
+ freeze();
+
+ int nInsertionPoint;
+ if (!bKeepExisting)
+ {
+ clear();
+ nInsertionPoint = 0;
+ }
+ else
+ nInsertionPoint = get_count();
+
+ GtkTreeIter iter;
+ // tdf#125241 inserting backwards is faster
+ for (auto aI = rItems.rbegin(); aI != rItems.rend(); ++aI)
+ {
+ const auto& rItem = *aI;
+ insert_row(GTK_LIST_STORE(m_pTreeModel), iter, nInsertionPoint, rItem.sId.isEmpty() ? nullptr : &rItem.sId,
+ rItem.sString, rItem.sImage.isEmpty() ? nullptr : &rItem.sImage, nullptr);
+ }
+
+ thaw();
+ }
+
+ virtual void remove(int pos) override
+ {
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ remove_including_mru(pos);
+ }
+
+ virtual void insert(int pos, const OUString& rText, const OUString* pId, const OUString* pIconName, VirtualDevice* pImageSurface) override
+ {
+ insert_including_mru(include_mru(pos), rText, pId, pIconName, pImageSurface);
+ }
+
+ virtual void insert_separator(int pos, const OUString& rId) override
+ {
+ pos = pos == -1 ? get_count() : pos;
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ insert_separator_including_mru(pos, rId);
+ }
+
+ virtual int get_count() const override
+ {
+ int nCount = get_count_including_mru();
+ if (m_nMRUCount)
+ nCount -= (m_nMRUCount + 1);
+ return nCount;
+ }
+
+ virtual int find_text(const OUString& rStr) const override
+ {
+ int nPos = find_text_including_mru(rStr, false);
+ if (nPos != -1 && m_nMRUCount)
+ nPos -= (m_nMRUCount + 1);
+ return nPos;
+ }
+
+ virtual int find_id(const OUString& rId) const override
+ {
+ int nPos = find_id_including_mru(rId, false);
+ if (nPos != -1 && m_nMRUCount)
+ nPos -= (m_nMRUCount + 1);
+ return nPos;
+ }
+
+ virtual void clear() override
+ {
+ do_clear();
+ }
+
+ virtual void make_sorted() override
+ {
+ m_xSorter.reset(new comphelper::string::NaturalStringSorter(
+ ::comphelper::getProcessComponentContext(),
+ Application::GetSettings().GetUILanguageTag().getLocale()));
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
+ gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, default_sort_func, m_xSorter.get(), nullptr);
+ }
+
+ virtual bool has_entry() const override
+ {
+ return gtk_combo_box_get_has_entry(m_pComboBox);
+ }
+
+ virtual void set_entry_message_type(weld::EntryMessageType eType) override
+ {
+ assert(m_pEntry);
+ ::set_entry_message_type(GTK_ENTRY(m_pEntry), eType);
+ }
+
+ virtual void set_entry_text(const OUString& rText) override
+ {
+ assert(m_pEntry);
+ disable_notify_events();
+ gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+ enable_notify_events();
+ }
+
+ virtual void set_entry_width_chars(int nChars) override
+ {
+ assert(m_pEntry);
+ disable_notify_events();
+ gtk_entry_set_width_chars(GTK_ENTRY(m_pEntry), nChars);
+ gtk_entry_set_max_width_chars(GTK_ENTRY(m_pEntry), nChars);
+ enable_notify_events();
+ }
+
+ virtual void set_entry_max_length(int nChars) override
+ {
+ assert(m_pEntry);
+ disable_notify_events();
+ gtk_entry_set_max_length(GTK_ENTRY(m_pEntry), nChars);
+ enable_notify_events();
+ }
+
+ virtual void select_entry_region(int nStartPos, int nEndPos) override
+ {
+ assert(m_pEntry);
+ disable_notify_events();
+ gtk_editable_select_region(GTK_EDITABLE(m_pEntry), nStartPos, nEndPos);
+ enable_notify_events();
+ }
+
+ virtual bool get_entry_selection_bounds(int& rStartPos, int &rEndPos) override
+ {
+ assert(m_pEntry);
+ return gtk_editable_get_selection_bounds(GTK_EDITABLE(m_pEntry), &rStartPos, &rEndPos);
+ }
+
+ virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
+ {
+ m_bAutoComplete = bEnable;
+ m_bAutoCompleteCaseSensitive = bCaseSensitive;
+ }
+
+ virtual void set_entry_placeholder_text(const OUString& rText) override
+ {
+ assert(m_pEntry);
+ gtk_entry_set_placeholder_text(GTK_ENTRY(m_pEntry), rText.toUtf8().getStr());
+ }
+
+ virtual void set_entry_editable(bool bEditable) override
+ {
+ assert(m_pEntry);
+ gtk_editable_set_editable(GTK_EDITABLE(m_pEntry), bEditable);
+ }
+
+ virtual void cut_entry_clipboard() override
+ {
+ assert(m_pEntry);
+ gtk_editable_cut_clipboard(GTK_EDITABLE(m_pEntry));
+ }
+
+ virtual void copy_entry_clipboard() override
+ {
+ assert(m_pEntry);
+ gtk_editable_copy_clipboard(GTK_EDITABLE(m_pEntry));
+ }
+
+ virtual void paste_entry_clipboard() override
+ {
+ assert(m_pEntry);
+ gtk_editable_paste_clipboard(GTK_EDITABLE(m_pEntry));
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_aCustomFont.use_custom_font(&rFont, u"box#combobox");
+ }
+
+ virtual vcl::Font get_font() override
+ {
+ if (const vcl::Font* pFont = m_aCustomFont.get_custom_font())
+ return *pFont;
+ return GtkInstanceWidget::get_font();
+ }
+
+ virtual void set_entry_font(const vcl::Font& rFont) override
+ {
+ m_xEntryFont = rFont;
+ assert(m_pEntry);
+ PangoAttrList* pOrigList = gtk_entry_get_attributes(GTK_ENTRY(m_pEntry));
+ PangoAttrList* pAttrList = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
+ update_attr_list(pAttrList, rFont);
+ gtk_entry_set_attributes(GTK_ENTRY(m_pEntry), pAttrList);
+ pango_attr_list_unref(pAttrList);
+ }
+
+ virtual vcl::Font get_entry_font() override
+ {
+ if (m_xEntryFont)
+ return *m_xEntryFont;
+ assert(m_pEntry);
+ PangoContext* pContext = gtk_widget_get_pango_context(m_pEntry);
+ return pango_to_vcl(pango_context_get_font_description(pContext),
+ Application::GetSettings().GetUILanguageTag().getLocale());
+ }
+
+ virtual void disable_notify_events() override
+ {
+ if (m_pEntry)
+ {
+ g_signal_handler_block(m_pEntry, m_nEntryInsertTextSignalId);
+ g_signal_handler_block(m_pEntry, m_nEntryActivateSignalId);
+ g_signal_handler_block(m_pEntry, m_nEntryFocusInSignalId);
+ g_signal_handler_block(m_pEntry, m_nEntryFocusOutSignalId);
+ g_signal_handler_block(m_pEntry, m_nEntryKeyPressEventSignalId);
+ g_signal_handler_block(m_pEntry, m_nChangedSignalId);
+ }
+ else
+ g_signal_handler_block(m_pToggleButton, m_nKeyPressEventSignalId);
+ if (m_nToggleFocusInSignalId)
+ g_signal_handler_block(m_pToggleButton, m_nToggleFocusInSignalId);
+ if (m_nToggleFocusOutSignalId)
+ g_signal_handler_block(m_pToggleButton, m_nToggleFocusOutSignalId);
+ g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
+ g_signal_handler_block(m_pToggleButton, m_nPopupShownSignalId);
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::enable_notify_events();
+ g_signal_handler_unblock(m_pToggleButton, m_nPopupShownSignalId);
+ g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
+ if (m_nToggleFocusInSignalId)
+ g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusInSignalId);
+ if (m_nToggleFocusOutSignalId)
+ g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusOutSignalId);
+ if (m_pEntry)
+ {
+ g_signal_handler_unblock(m_pEntry, m_nChangedSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nEntryActivateSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nEntryFocusInSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nEntryFocusOutSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nEntryKeyPressEventSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nEntryInsertTextSignalId);
+ }
+ else
+ g_signal_handler_unblock(m_pToggleButton, m_nKeyPressEventSignalId);
+ }
+
+ virtual void freeze() override
+ {
+ disable_notify_events();
+ bool bIsFirstFreeze = IsFirstFreeze();
+ GtkInstanceContainer::freeze();
+ if (bIsFirstFreeze)
+ {
+ g_object_ref(m_pTreeModel);
+ gtk_tree_view_set_model(m_pTreeView, nullptr);
+ g_object_freeze_notify(G_OBJECT(m_pTreeModel));
+ if (m_xSorter)
+ {
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
+ }
+ }
+ enable_notify_events();
+ }
+
+ virtual void thaw() override
+ {
+ disable_notify_events();
+ if (IsLastThaw())
+ {
+ if (m_xSorter)
+ {
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
+ }
+ g_object_thaw_notify(G_OBJECT(m_pTreeModel));
+ gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
+ g_object_unref(m_pTreeModel);
+ }
+ GtkInstanceContainer::thaw();
+ enable_notify_events();
+ }
+
+ virtual bool get_popup_shown() const override
+ {
+ return m_bPopupActive;
+ }
+
+ virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
+ {
+ if (!m_nToggleFocusInSignalId)
+ m_nToggleFocusInSignalId = g_signal_connect_after(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this);
+ GtkInstanceContainer::connect_focus_in(rLink);
+ }
+
+ virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
+ {
+ if (!m_nToggleFocusOutSignalId)
+ m_nToggleFocusOutSignalId = g_signal_connect_after(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this);
+ GtkInstanceContainer::connect_focus_out(rLink);
+ }
+
+ virtual void grab_focus() override
+ {
+ if (has_focus())
+ return;
+ if (m_pEntry)
+ gtk_widget_grab_focus(m_pEntry);
+ else
+ gtk_widget_grab_focus(m_pToggleButton);
+ }
+
+ virtual bool has_focus() const override
+ {
+ if (m_pEntry && gtk_widget_has_focus(m_pEntry))
+ return true;
+
+ if (gtk_widget_has_focus(m_pToggleButton))
+ return true;
+
+ if (gtk_widget_get_visible(GTK_WIDGET(m_pMenuWindow)))
+ {
+ if (gtk_widget_has_focus(GTK_WIDGET(m_pOverlayButton)) || gtk_widget_has_focus(GTK_WIDGET(m_pTreeView)))
+ return true;
+ }
+
+ return GtkInstanceWidget::has_focus();
+ }
+
+ virtual bool changed_by_direct_pick() const override
+ {
+ return m_bChangedByMenu;
+ }
+
+ virtual void set_custom_renderer(bool bOn) override
+ {
+ if (bOn == m_bCustomRenderer)
+ return;
+ GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
+ // keep the original height around for optimal popup height calculation
+ m_nNonCustomLineHeight = bOn ? ::get_height_row(m_pTreeView, pColumns) : -1;
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pColumns->data);
+ gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn));
+ if (bOn)
+ {
+ GtkCellRenderer *pRenderer = custom_cell_renderer_new();
+ GValue value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_POINTER);
+ g_value_set_pointer(&value, static_cast<gpointer>(this));
+ g_object_set_property(G_OBJECT(pRenderer), "instance", &value);
+ gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "id", m_nIdCol);
+ }
+ else
+ {
+ GtkCellRenderer *pRenderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
+ }
+ g_list_free(pColumns);
+ m_bCustomRenderer = bOn;
+ }
+
+ void call_signal_custom_render(VirtualDevice& rOutput, const tools::Rectangle& rRect, bool bSelected, const OUString& rId)
+ {
+ signal_custom_render(rOutput, rRect, bSelected, rId);
+ }
+
+ Size call_signal_custom_get_size(VirtualDevice& rOutput)
+ {
+ return signal_custom_get_size(rOutput);
+ }
+
+ VclPtr<VirtualDevice> create_render_virtual_device() const override
+ {
+ return create_virtual_device();
+ }
+
+ virtual void set_item_menu(const OUString& rIdent, weld::Menu* pMenu) override
+ {
+ m_xCustomMenuButtonHelper.reset();
+ GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
+ GtkWidget* pMenuWidget = GTK_WIDGET(pPopoverWidget ? pPopoverWidget->getMenu() : nullptr);
+ gtk_menu_button_set_popup(m_pOverlayButton, pMenuWidget);
+ gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), pMenuWidget != nullptr);
+ gtk_widget_queue_resize_no_redraw(GTK_WIDGET(m_pOverlayButton)); // force location recalc
+ if (pMenuWidget)
+ m_xCustomMenuButtonHelper.reset(new CustomRenderMenuButtonHelper(GTK_MENU(pMenuWidget), GTK_TOGGLE_BUTTON(m_pToggleButton)));
+ m_sMenuButtonRow = rIdent;
+ }
+
+ OUString get_mru_entries() const override
+ {
+ const sal_Unicode cSep = ';';
+
+ OUStringBuffer aEntries;
+ for (sal_Int32 n = 0; n < m_nMRUCount; n++)
+ {
+ aEntries.append(get_text_including_mru(n));
+ if (n < m_nMRUCount - 1)
+ aEntries.append(cSep);
+ }
+ return aEntries.makeStringAndClear();
+ }
+
+ virtual void set_mru_entries(const OUString& rEntries) override
+ {
+ const sal_Unicode cSep = ';';
+
+ // Remove old MRU entries
+ for (sal_Int32 n = m_nMRUCount; n;)
+ remove_including_mru(--n);
+
+ sal_Int32 nMRUCount = 0;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OUString aEntry = rEntries.getToken(0, cSep, nIndex);
+ // Accept only existing entries
+ int nPos = find_text(aEntry);
+ if (nPos != -1)
+ {
+ OUString sId = get_id(nPos);
+ insert_including_mru(0, aEntry, &sId, nullptr, nullptr);
+ ++nMRUCount;
+ }
+ }
+ while (nIndex >= 0);
+
+ if (nMRUCount && !m_nMRUCount)
+ insert_separator_including_mru(nMRUCount, "separator");
+ else if (!nMRUCount && m_nMRUCount)
+ remove_including_mru(m_nMRUCount); // remove separator
+
+ m_nMRUCount = nMRUCount;
+ }
+
+ int get_menu_button_width() const override
+ {
+ bool bVisible = gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton));
+ if (!bVisible)
+ gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), true);
+ gint nWidth;
+ gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &nWidth, nullptr);
+ if (!bVisible)
+ gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), false);
+ return nWidth;
+ }
+
+ virtual ~GtkInstanceComboBox() override
+ {
+ m_xCustomMenuButtonHelper.reset();
+ do_clear();
+ if (m_nAutoCompleteIdleId)
+ g_source_remove(m_nAutoCompleteIdleId);
+ if (m_pEntry)
+ {
+ g_signal_handler_disconnect(m_pEntry, m_nChangedSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nEntryInsertTextSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nEntryActivateSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nEntryFocusInSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nEntryFocusOutSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nEntryKeyPressEventSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nEntryPopulatePopupMenuSignalId);
+ }
+ else
+ g_signal_handler_disconnect(m_pToggleButton, m_nKeyPressEventSignalId);
+ if (m_nToggleFocusInSignalId)
+ g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusInSignalId);
+ if (m_nToggleFocusOutSignalId)
+ g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusOutSignalId);
+ g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
+ g_signal_handler_disconnect(m_pToggleButton, m_nPopupShownSignalId);
+
+ gtk_combo_box_set_model(m_pComboBox, m_pTreeModel);
+ gtk_tree_view_set_model(m_pTreeView, nullptr);
+
+ // restore original hierarchy in dtor so a new GtkInstanceComboBox will
+ // result in the same layout each time
+ {
+ DisconnectMouseEvents();
+
+ g_object_ref(m_pComboBox);
+
+ GtkContainer* pContainer = getContainer();
+
+ gtk_container_remove(pContainer, GTK_WIDGET(m_pComboBox));
+
+ replaceWidget(GTK_WIDGET(pContainer), GTK_WIDGET(m_pComboBox));
+
+ g_object_unref(m_pComboBox);
+ }
+
+ g_object_unref(m_pComboBuilder);
+ }
+};
+
+#endif
+
+}
+
+void custom_cell_renderer_ensure_device(CustomCellRenderer *cellsurface, gpointer user_data)
+{
+ if (!cellsurface->device)
+ {
+ cellsurface->device = VclPtr<VirtualDevice>::Create();
+ cellsurface->device->SetBackground(COL_TRANSPARENT);
+ GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(user_data);
+ // expand the point size of the desired font to the equivalent pixel size
+ weld::SetPointFont(*cellsurface->device, pWidget->get_font());
+ }
+}
+
+Size custom_cell_renderer_get_size(VirtualDevice& rDevice, const OUString& rCellId, gpointer user_data)
+{
+ GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(user_data);
+ if (GtkInstanceTreeView* pTreeView = dynamic_cast<GtkInstanceTreeView*>(pWidget))
+ return pTreeView->call_signal_custom_get_size(rDevice, rCellId);
+ else if (GtkInstanceComboBox* pComboBox = dynamic_cast<GtkInstanceComboBox*>(pWidget))
+ return pComboBox->call_signal_custom_get_size(rDevice);
+ return Size();
+}
+
+void custom_cell_renderer_render(VirtualDevice& rDevice, const tools::Rectangle& rRect, bool bSelected, const OUString& rCellId, gpointer user_data)
+{
+ GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(user_data);
+ if (GtkInstanceTreeView* pTreeView = dynamic_cast<GtkInstanceTreeView*>(pWidget))
+ pTreeView->call_signal_custom_render(rDevice, rRect, bSelected, rCellId);
+ else if (GtkInstanceComboBox* pComboBox = dynamic_cast<GtkInstanceComboBox*>(pWidget))
+ pComboBox->call_signal_custom_render(rDevice, rRect, bSelected, rCellId);
+}
+
+namespace {
+
+class GtkInstanceEntryTreeView : public GtkInstanceContainer, public virtual weld::EntryTreeView
+{
+private:
+ GtkInstanceEntry* m_pEntry;
+ GtkInstanceTreeView* m_pTreeView;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nKeyPressSignalId;
+#endif
+ gulong m_nEntryInsertTextSignalId;
+ guint m_nAutoCompleteIdleId;
+ bool m_bAutoCompleteCaseSensitive;
+ bool m_bTreeChange;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool signal_key_press(GdkEventKey* pEvent)
+ {
+ if (GtkSalFrame::GetMouseModCode(pEvent->state)) // only with no modifiers held
+ return false;
+
+ if (pEvent->keyval == GDK_KEY_KP_Up || pEvent->keyval == GDK_KEY_Up || pEvent->keyval == GDK_KEY_KP_Page_Up || pEvent->keyval == GDK_KEY_Page_Up ||
+ pEvent->keyval == GDK_KEY_KP_Down || pEvent->keyval == GDK_KEY_Down || pEvent->keyval == GDK_KEY_KP_Page_Down || pEvent->keyval == GDK_KEY_Page_Down)
+ {
+ gboolean ret;
+ disable_notify_events();
+ GtkWidget* pWidget = m_pTreeView->getWidget();
+ if (m_pTreeView->get_selected_index() == -1)
+ {
+ m_pTreeView->set_cursor(0);
+ m_pTreeView->select(0);
+ m_xEntry->set_text(m_xTreeView->get_selected_text());
+ }
+ else
+ {
+ gtk_widget_grab_focus(pWidget);
+ g_signal_emit_by_name(pWidget, "key-press-event", pEvent, &ret);
+ m_xEntry->set_text(m_xTreeView->get_selected_text());
+ gtk_widget_grab_focus(m_pEntry->getWidget());
+ }
+ m_xEntry->select_region(0, -1);
+ enable_notify_events();
+ m_bTreeChange = true;
+ m_pEntry->fire_signal_changed();
+ m_bTreeChange = false;
+ return true;
+ }
+ return false;
+ }
+
+ static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
+ return pThis->signal_key_press(pEvent);
+ }
+#endif
+
+ static gboolean idleAutoComplete(gpointer widget)
+ {
+ GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
+ pThis->auto_complete();
+ return false;
+ }
+
+ void auto_complete()
+ {
+ m_nAutoCompleteIdleId = 0;
+ OUString aStartText = get_active_text();
+ int nStartPos, nEndPos;
+ get_entry_selection_bounds(nStartPos, nEndPos);
+ int nMaxSelection = std::max(nStartPos, nEndPos);
+ if (nMaxSelection != aStartText.getLength())
+ return;
+
+ disable_notify_events();
+ int nActive = get_active();
+ int nStart = nActive;
+
+ if (nStart == -1)
+ nStart = 0;
+
+ // Try match case sensitive from current position
+ int nPos = m_pTreeView->starts_with(aStartText, nStart, true);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case insensitive, but from start
+ nPos = m_pTreeView->starts_with(aStartText, 0, true);
+ }
+
+ if (!m_bAutoCompleteCaseSensitive)
+ {
+ // Try match case insensitive from current position
+ nPos = m_pTreeView->starts_with(aStartText, nStart, false);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case insensitive, but from start
+ nPos = m_pTreeView->starts_with(aStartText, 0, false);
+ }
+ }
+
+ if (nPos == -1)
+ {
+ // Try match case sensitive from current position
+ nPos = m_pTreeView->starts_with(aStartText, nStart, true);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case sensitive, but from start
+ nPos = m_pTreeView->starts_with(aStartText, 0, true);
+ }
+ }
+
+ if (nPos != -1)
+ {
+ OUString aText = get_text(nPos);
+ if (aText != aStartText)
+ set_active_text(aText);
+ select_entry_region(aText.getLength(), aStartText.getLength());
+ }
+ enable_notify_events();
+ }
+
+ void signal_entry_insert_text(GtkEntry*, const gchar*, gint, gint*)
+ {
+ // now check for autocompletes
+ if (m_nAutoCompleteIdleId)
+ g_source_remove(m_nAutoCompleteIdleId);
+ m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
+ }
+
+ static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
+ gint* position, gpointer widget)
+ {
+ GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
+ pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
+ }
+
+
+public:
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkInstanceEntryTreeView(GtkWidget* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership,
+ std::unique_ptr<weld::Entry> xEntry, std::unique_ptr<weld::TreeView> xTreeView)
+#else
+ GtkInstanceEntryTreeView(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership,
+ std::unique_ptr<weld::Entry> xEntry, std::unique_ptr<weld::TreeView> xTreeView)
+#endif
+ : EntryTreeView(std::move(xEntry), std::move(xTreeView))
+ , GtkInstanceContainer(pContainer, pBuilder, bTakeOwnership)
+ , m_pEntry(dynamic_cast<GtkInstanceEntry*>(m_xEntry.get()))
+ , m_pTreeView(dynamic_cast<GtkInstanceTreeView*>(m_xTreeView.get()))
+ , m_nAutoCompleteIdleId(0)
+ , m_bAutoCompleteCaseSensitive(false)
+ , m_bTreeChange(false)
+ {
+ assert(m_pEntry);
+ GtkWidget* pWidget = m_pEntry->getWidget();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_nKeyPressSignalId = g_signal_connect(pWidget, "key-press-event", G_CALLBACK(signalKeyPress), this);
+#endif
+ m_nEntryInsertTextSignalId = g_signal_connect(pWidget, "insert-text", G_CALLBACK(signalEntryInsertText), this);
+ }
+
+ virtual void insert_separator(int /*pos*/, const OUString& /*rId*/) override
+ {
+ assert(false);
+ }
+
+ virtual void make_sorted() override
+ {
+ GtkWidget* pTreeView = m_pTreeView->getWidget();
+ GtkTreeModel* pModel = gtk_tree_view_get_model(GTK_TREE_VIEW(pTreeView));
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(pModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, 1, GTK_SORT_ASCENDING);
+ }
+
+ virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
+ {
+ assert(!bEnable && "not implemented yet"); (void)bEnable;
+ m_bAutoCompleteCaseSensitive = bCaseSensitive;
+ }
+
+ virtual void set_entry_placeholder_text(const OUString& rText) override
+ {
+ m_xEntry->set_placeholder_text(rText);
+ }
+
+ virtual void set_entry_editable(bool bEditable) override
+ {
+ m_xEntry->set_editable(bEditable);
+ }
+
+ virtual void cut_entry_clipboard() override
+ {
+ m_xEntry->cut_clipboard();
+ }
+
+ virtual void copy_entry_clipboard() override
+ {
+ m_xEntry->copy_clipboard();
+ }
+
+ virtual void paste_entry_clipboard() override
+ {
+ m_xEntry->paste_clipboard();
+ }
+
+ virtual void set_font(const vcl::Font&) override
+ {
+ assert(false && "not implemented");
+ }
+
+ virtual void set_entry_font(const vcl::Font& rFont) override
+ {
+ m_xEntry->set_font(rFont);
+ }
+
+ virtual vcl::Font get_entry_font() override
+ {
+ return m_xEntry->get_font();
+ }
+
+ 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 void disable_notify_events() override
+ {
+ GtkWidget* pWidget = m_pEntry->getWidget();
+ g_signal_handler_block(pWidget, m_nEntryInsertTextSignalId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_block(pWidget, m_nKeyPressSignalId);
+#endif
+ m_pTreeView->disable_notify_events();
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkWidget* pWidget = m_pEntry->getWidget();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_unblock(pWidget, m_nKeyPressSignalId);
+#endif
+ g_signal_handler_unblock(pWidget, m_nEntryInsertTextSignalId);
+ m_pTreeView->enable_notify_events();
+ GtkInstanceContainer::enable_notify_events();
+ }
+
+ 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 OUString&, weld::Menu*) override
+ {
+ assert(false && "not implemented");
+ }
+
+ VclPtr<VirtualDevice> create_render_virtual_device() const override
+ {
+ return create_virtual_device();
+ }
+
+ int get_menu_button_width() const override
+ {
+ assert(false && "not implemented");
+ return 0;
+ }
+
+ virtual ~GtkInstanceEntryTreeView() override
+ {
+ if (m_nAutoCompleteIdleId)
+ g_source_remove(m_nAutoCompleteIdleId);
+ GtkWidget* pWidget = m_pEntry->getWidget();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(pWidget, m_nKeyPressSignalId);
+#endif
+ g_signal_handler_disconnect(pWidget, m_nEntryInsertTextSignalId);
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstanceExpander : public GtkInstanceWidget, public virtual weld::Expander
+{
+private:
+ GtkExpander* m_pExpander;
+ gulong m_nSignalId;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nButtonPressEventSignalId;
+ gulong m_nMappedSignalId;
+#endif
+
+ static void signalExpanded(GtkExpander* /*pExpander*/, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceExpander* pThis = static_cast<GtkInstanceExpander*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_expanded();
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalButton(GtkWidget*, GdkEventButton*, gpointer)
+ {
+ // don't let button press get to parent window, for the case of the
+ // an expander in a sidebar where otherwise single click to expand
+ // doesn't work
+ return true;
+ }
+
+ /* tdf#141186 if the expander is initially collapsed then when mapped all its
+ children are mapped too. If they are mapped then the mnemonics of the
+ children are taken into account on shortcuts and non-visible children in a
+ collapsed expander can be triggered which is confusing.
+
+ If the expander is expanded and collapsed the child is unmapped and the
+ problem doesn't occur.
+
+ So to avoid the problem of an initially collapsed expander, listen to
+ the map event and if the expander is mapped but collapsed then unmap the
+ child of the expander.
+
+ This problem was seen in gtk3-3.24.33 and not with gtk4-4.6.4 so a gtk3
+ fix only needed.
+ */
+ static void signalMap(GtkWidget*, gpointer widget)
+ {
+ GtkInstanceExpander* pThis = static_cast<GtkInstanceExpander*>(widget);
+ if (!gtk_expander_get_expanded(pThis->m_pExpander))
+ {
+ if (GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pThis->m_pExpander)))
+ gtk_widget_unmap(pChild);
+ }
+ }
+#endif
+
+public:
+ GtkInstanceExpander(GtkExpander* pExpander, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pExpander), pBuilder, bTakeOwnership)
+ , m_pExpander(pExpander)
+ , m_nSignalId(g_signal_connect(m_pExpander, "notify::expanded", G_CALLBACK(signalExpanded), this))
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_nButtonPressEventSignalId(g_signal_connect_after(m_pExpander, "button-press-event", G_CALLBACK(signalButton), this))
+ , m_nMappedSignalId(g_signal_connect_after(m_pExpander, "map", G_CALLBACK(signalMap), this))
+#endif
+ {
+ }
+
+ virtual void set_label(const OUString& rText) override
+ {
+ ::set_label(GTK_LABEL(gtk_expander_get_label_widget(m_pExpander)), rText);
+ }
+
+ virtual OUString get_label() const override
+ {
+ return ::get_label(GTK_LABEL(gtk_expander_get_label_widget(m_pExpander)));
+ }
+
+ virtual bool get_expanded() const override
+ {
+ return gtk_expander_get_expanded(m_pExpander);
+ }
+
+ virtual void set_expanded(bool bExpand) override
+ {
+ gtk_expander_set_expanded(m_pExpander, bExpand);
+ }
+
+ virtual ~GtkInstanceExpander() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pExpander, m_nMappedSignalId);
+ g_signal_handler_disconnect(m_pExpander, m_nButtonPressEventSignalId);
+#endif
+ g_signal_handler_disconnect(m_pExpander, m_nSignalId);
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstancePopover : public GtkInstanceContainer, public virtual weld::Popover
+{
+private:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ //popover cannot escape dialog under X so we might need to stick up own window instead
+ GtkWindow* m_pMenuHack;
+ bool m_bMenuPoppedUp;
+ bool m_nButtonPressSeen;
+#endif
+ GtkPopover* m_pPopover;
+ gulong m_nSignalId;
+ ImplSVEvent* m_pClosedEvent;
+
+ static void signalClosed(GtkPopover*, gpointer widget)
+ {
+ GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+ // call signal-closed async so the closed callback isn't called
+ // while the GtkPopover handler is still in-execution
+ pThis->launch_signal_closed();
+ }
+
+ DECL_LINK(async_signal_closed, void*, void);
+
+ void launch_signal_closed()
+ {
+ if (m_pClosedEvent)
+ Application::RemoveUserEvent(m_pClosedEvent);
+ m_pClosedEvent = Application::PostUserEvent(LINK(this, GtkInstancePopover, async_signal_closed));
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean keyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+ return pThis->key_press(pEvent);
+ }
+
+ bool key_press(const GdkEventKey* pEvent)
+ {
+ if (pEvent->keyval == GDK_KEY_Escape)
+ {
+ popdown();
+ return true;
+ }
+ return false;
+ }
+
+ static gboolean signalButtonPress(GtkWidget* /*pWidget*/, GdkEventButton* /*pEvent*/, gpointer widget)
+ {
+ GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+ pThis->m_nButtonPressSeen = true;
+ return false;
+ }
+
+ static gboolean signalButtonRelease(GtkWidget* /*pWidget*/, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+ if (pThis->m_nButtonPressSeen && button_event_is_outside(GTK_WIDGET(pThis->m_pMenuHack), pEvent))
+ pThis->popdown();
+ return false;
+ }
+
+ bool forward_event_if_popup_under_mouse(GdkEvent* pEvent)
+ {
+ GtkWidget* pEventWidget = gtk_get_event_widget(pEvent);
+ GtkWidget* pTopLevel = widget_get_toplevel(pEventWidget);
+
+ if (pTopLevel == GTK_WIDGET(m_pMenuHack))
+ return false;
+
+ GdkSurface* pSurface = widget_get_surface(pTopLevel);
+ void* pMouseEnteredAnotherPopup = g_object_get_data(G_OBJECT(pSurface), "g-lo-InstancePopup");
+ if (!pMouseEnteredAnotherPopup)
+ return false;
+
+ return gtk_widget_event(pEventWidget, pEvent);
+ }
+
+ static gboolean signalButtonCrossing(GtkWidget*, GdkEvent* pEvent, gpointer widget)
+ {
+ GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+ return pThis->forward_event_if_popup_under_mouse(pEvent);
+ }
+
+ static gboolean signalMotion(GtkWidget*, GdkEvent* pEvent, gpointer widget)
+ {
+ GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+ return pThis->forward_event_if_popup_under_mouse(pEvent);
+ }
+
+ static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
+ {
+ GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+ pThis->grab_broken(pEvent);
+ }
+
+ void grab_broken(const GdkEventGrabBroken *event)
+ {
+ if (event->grab_window == nullptr)
+ {
+ popdown();
+ }
+ else if (!g_object_get_data(G_OBJECT(event->grab_window), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
+ {
+ //try and regrab, so when we lose the grab to the menu of the color palette
+ //combobox we regain it so the color palette doesn't itself disappear on next
+ //click on the color palette combobox
+ do_grab(GTK_WIDGET(m_pMenuHack));
+ }
+ }
+
+#endif
+
+public:
+ GtkInstancePopover(GtkPopover* pPopover, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ : GtkInstanceContainer(GTK_CONTAINER(pPopover), pBuilder, bTakeOwnership)
+ , m_pMenuHack(nullptr)
+ , m_bMenuPoppedUp(false)
+ , m_nButtonPressSeen(false)
+#else
+ : GtkInstanceContainer(GTK_WIDGET(pPopover), pBuilder, bTakeOwnership)
+#endif
+ , m_pPopover(pPopover)
+ , m_nSignalId(g_signal_connect(m_pPopover, "closed", G_CALLBACK(signalClosed), this))
+ , m_pClosedEvent(nullptr)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ //under wayland a Popover will work to "escape" the parent dialog, not
+ //so under X, so come up with this hack to use a raw GtkWindow
+ GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pPopover));
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ m_pMenuHack = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
+ gtk_window_set_type_hint(m_pMenuHack, GDK_WINDOW_TYPE_HINT_COMBO);
+ gtk_window_set_resizable(m_pMenuHack, false);
+ g_signal_connect(m_pMenuHack, "key-press-event", G_CALLBACK(keyPress), this);
+ g_signal_connect(m_pMenuHack, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
+ g_signal_connect(m_pMenuHack, "button-press-event", G_CALLBACK(signalButtonPress), this);
+ g_signal_connect(m_pMenuHack, "button-release-event", G_CALLBACK(signalButtonRelease), this);
+ // to emulate a modeless popover we forward the leave/enter/motion events to the widgets
+ // they would have gone to a if we were really modeless
+ if (!gtk_popover_get_modal(m_pPopover))
+ {
+ g_signal_connect(m_pMenuHack, "leave-notify-event", G_CALLBACK(signalButtonCrossing), this);
+ g_signal_connect(m_pMenuHack, "enter-notify-event", G_CALLBACK(signalButtonCrossing), this);
+ g_signal_connect(m_pMenuHack, "motion-notify-event", G_CALLBACK(signalMotion), this);
+ }
+ }
+#endif
+ }
+
+ virtual void popup_at_rect(weld::Widget* pParent, const tools::Rectangle& rRect, weld::Placement ePlace) override
+ {
+ GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
+ assert(pGtkWidget);
+
+ GtkWidget* pWidget = pGtkWidget->getWidget();
+
+ GdkRectangle aRect;
+ pWidget = getPopupRect(pWidget, rRect, aRect);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_parent(GTK_WIDGET(m_pPopover), pWidget);
+#else
+ gtk_popover_set_relative_to(m_pPopover, pWidget);
+#endif
+ gtk_popover_set_pointing_to(m_pPopover, &aRect);
+
+ if (ePlace == weld::Placement::Under)
+ gtk_popover_set_position(m_pPopover, GTK_POS_BOTTOM);
+ else
+ {
+ if (::SwapForRTL(pWidget))
+ gtk_popover_set_position(m_pPopover, GTK_POS_LEFT);
+ else
+ gtk_popover_set_position(m_pPopover, GTK_POS_RIGHT);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ //under wayland a Popover will work to "escape" the parent dialog, not
+ //so under X, so come up with this hack to use a raw GtkWindow
+ GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pPopover));
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ if (!m_bMenuPoppedUp)
+ {
+ MovePopoverContentsToWindow(GTK_WIDGET(m_pPopover), m_pMenuHack, pWidget, aRect, ePlace);
+ m_bMenuPoppedUp = true;
+ }
+ return;
+ }
+#endif
+
+ gtk_popover_popup(m_pPopover);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ virtual bool get_visible() const override
+ {
+ if (m_pMenuHack)
+ return gtk_widget_get_visible(GTK_WIDGET(m_pMenuHack));
+ return gtk_widget_get_visible(m_pWidget);
+ }
+
+ virtual void ensureMouseEventWidget() override
+ {
+ if (!m_pMouseEventBox && m_pMenuHack)
+ {
+ m_pMouseEventBox = GTK_WIDGET(m_pMenuHack);
+ return;
+ }
+ GtkInstanceContainer::ensureMouseEventWidget();
+ }
+#endif
+
+ virtual void popdown() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ //under wayland a Popover will work to "escape" the parent dialog, not
+ //so under X, so come up with this hack to use a raw GtkWindow
+ GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pPopover));
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ if (m_bMenuPoppedUp)
+ {
+ m_nButtonPressSeen = false;
+ MoveWindowContentsToPopover(m_pMenuHack, GTK_WIDGET(m_pPopover), gtk_popover_get_relative_to(m_pPopover));
+ m_bMenuPoppedUp = false;
+ signal_closed();
+ }
+ return;
+ }
+#endif
+
+ gtk_popover_popdown(m_pPopover);
+ }
+
+ void PopdownAndFlushClosedSignal()
+ {
+ if (get_visible())
+ popdown();
+ if (m_pClosedEvent)
+ {
+ Application::RemoveUserEvent(m_pClosedEvent);
+ async_signal_closed(nullptr);
+ }
+ }
+
+ virtual void resize_to_request() override
+ {
+ // resizing to request is what gtk does automatically
+ }
+
+ virtual ~GtkInstancePopover() override
+ {
+ PopdownAndFlushClosedSignal();
+ DisconnectMouseEvents();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (m_pMenuHack)
+ gtk_widget_destroy(GTK_WIDGET(m_pMenuHack));
+#endif
+ g_signal_handler_disconnect(m_pPopover, m_nSignalId);
+ }
+};
+
+IMPL_LINK_NOARG(GtkInstancePopover, async_signal_closed, void*, void)
+{
+ m_pClosedEvent = nullptr;
+ signal_closed();
+}
+
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+
+namespace
+{
+
+AtkObject* drawing_area_get_accessible(GtkWidget *pWidget)
+{
+ AtkObject* pDefaultAccessible = default_drawing_area_get_accessible(pWidget);
+ void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceDrawingArea");
+ GtkInstanceDrawingArea* pDrawingArea = static_cast<GtkInstanceDrawingArea*>(pData);
+ AtkObject *pAtkObj = pDrawingArea ? pDrawingArea->GetAtkObject(pDefaultAccessible) : nullptr;
+ if (pAtkObj)
+ return pAtkObj;
+ return pDefaultAccessible;
+}
+
+void ensure_intercept_drawing_area_accessibility()
+{
+ static bool bDone;
+ if (!bDone)
+ {
+ gpointer pClass = g_type_class_ref(GTK_TYPE_DRAWING_AREA);
+ GtkWidgetClass* pWidgetClass = GTK_WIDGET_CLASS(pClass);
+ default_drawing_area_get_accessible = pWidgetClass->get_accessible;
+ pWidgetClass->get_accessible = drawing_area_get_accessible;
+ g_type_class_unref(pClass);
+ bDone = true;
+ }
+}
+
+void ensure_disable_ctrl_page_up_down(GType eType)
+{
+ gpointer pClass = g_type_class_ref(eType);
+ GtkWidgetClass* pWidgetClass = GTK_WIDGET_CLASS(pClass);
+ GtkBindingSet* pBindingSet = gtk_binding_set_by_class(pWidgetClass);
+ gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Up, GDK_CONTROL_MASK);
+ gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Up, static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK));
+ gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Down, GDK_CONTROL_MASK);
+ gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Down, static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK));
+ g_type_class_unref(pClass);
+}
+
+// tdf#130400 disable ctrl+page_up and ctrl+page_down bindings so the
+// keystrokes are consumed by the surrounding notebook bindings instead
+void ensure_disable_ctrl_page_up_down_bindings()
+{
+ static bool bDone;
+ if (!bDone)
+ {
+ ensure_disable_ctrl_page_up_down(GTK_TYPE_TREE_VIEW);
+ ensure_disable_ctrl_page_up_down(GTK_TYPE_SPIN_BUTTON);
+ bDone = true;
+ }
+}
+
+}
+#endif
+
+namespace {
+
+bool IsAllowedBuiltInIcon(std::u16string_view iconName)
+{
+ // limit the named icons to those known by VclBuilder
+ return VclBuilder::mapStockToSymbol(iconName) != SymbolType::DONTKNOW;
+}
+
+}
+
+namespace {
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void silence_gwarning(const gchar* /*log_domain*/,
+ GLogLevelFlags /*log_level*/,
+ const gchar* /*message*/,
+ gpointer /*user_data*/)
+{
+}
+#endif
+
+void load_ui_file(GtkBuilder* pBuilder, const OUString& rUri)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ builder_add_from_gtk3_file(pBuilder, rUri);
+#else
+ guint nLogHandlerId = 0;
+ GLogLevelFlags nFatalMask(static_cast<GLogLevelFlags>(G_LOG_FLAG_RECURSION|G_LOG_LEVEL_ERROR));
+ if (rUri.endsWith("sfx/ui/tabbarcontents.ui"))
+ {
+ // gtk unhelpfully has a bogus warning for the accelerator in this .ui because it assumes menus with accelerators
+ // if attached to something are attached to a MenuShell, but it's a MenuButton in this case. Turn off warnings, and
+ // in the case of fatal-warnings temp disable fatal warnings, for this case.
+ nLogHandlerId = g_log_set_handler("GLib-GObject",
+ static_cast<GLogLevelFlags>(G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION),
+ silence_gwarning, nullptr);
+ nFatalMask = g_log_set_always_fatal(nFatalMask);
+ }
+
+ OUString aPath;
+ osl::FileBase::getSystemPathFromFileURL(rUri, aPath);
+ GError *err = nullptr;
+ auto rc = gtk_builder_add_from_file(pBuilder, OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr(), &err);
+
+ if (nLogHandlerId)
+ {
+ g_log_remove_handler("GLib-GObject", nLogHandlerId);
+ g_log_set_always_fatal(nFatalMask);
+ }
+
+ if (!rc)
+ {
+ SAL_WARN( "vcl.gtk", "GtkInstanceBuilder: error when calling gtk_builder_add_from_file: " << err->message);
+ g_error_free(err);
+ }
+ assert(rc && "could not load UI file");
+#endif
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void fix_expander(GtkExpander* pExpander, GParamSpec*, gpointer)
+{
+ if (gtk_expander_get_resize_toplevel(pExpander))
+ {
+ GtkWidget *pToplevel = widget_get_toplevel(GTK_WIDGET(pExpander));
+
+ // https://gitlab.gnome.org/GNOME/gtk/issues/70
+ // I imagine at some point a release with a fix will be available in which
+ // case this can be avoided depending on version number
+ if (pToplevel && GTK_IS_WINDOW(pToplevel) && gtk_widget_get_realized(pToplevel))
+ {
+ int nToplevelWidth, nToplevelHeight;
+ int nChildHeight;
+
+ GtkWidget* child = gtk_bin_get_child(GTK_BIN(pExpander));
+ gtk_widget_get_preferred_height(child, &nChildHeight, nullptr);
+ gtk_window_get_size(GTK_WINDOW(pToplevel), &nToplevelWidth, &nToplevelHeight);
+
+ if (gtk_expander_get_expanded(pExpander))
+ nToplevelHeight += nChildHeight;
+ else
+ nToplevelHeight -= nChildHeight;
+
+ gtk_window_resize(GTK_WINDOW(pToplevel), nToplevelWidth, nToplevelHeight);
+ }
+ }
+}
+#endif
+
+class GtkInstanceBuilder : public weld::Builder
+{
+private:
+ ResHookProc m_pStringReplace;
+ OUString m_aHelpRoot;
+ OUString m_aIconTheme;
+ OUString m_aUILang;
+ GtkBuilder* m_pBuilder;
+ GSList* m_pObjectList;
+ GtkWidget* m_pParentWidget;
+ gulong m_nNotifySignalId;
+ std::vector<GtkButton*> m_aMnemonicButtons;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ std::vector<GtkCheckButton*> m_aMnemonicCheckButtons;
+#endif
+ std::vector<GtkLabel*> m_aMnemonicLabels;
+
+ VclPtr<SystemChildWindow> m_xInterimGlue;
+ bool m_bAllowCycleFocusOut;
+
+ void postprocess_widget(GtkWidget* pWidget)
+ {
+ const bool bHideHelp = comphelper::LibreOfficeKit::isActive() &&
+ officecfg::Office::Common::Help::HelpRootURL::get().isEmpty();
+
+ //fixup icons
+ //wanted: better way to do this, e.g. make gtk use gio for
+ //loading from a filename and provide gio protocol handler
+ //for our image in a zip urls
+ //
+ //unpack the images and keep them as dirs and just
+ //add the paths to the gtk icon theme dir
+ if (GTK_IS_IMAGE(pWidget))
+ {
+ GtkImage* pImage = GTK_IMAGE(pWidget);
+ if (const gchar* icon_name = image_get_icon_name(pImage))
+ {
+ OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
+ if (!IsAllowedBuiltInIcon(aIconName))
+ image_set_from_icon_name_theme_lang(pImage, aIconName, m_aIconTheme, m_aUILang);
+ }
+ }
+#if GTK_CHECK_VERSION(4, 0, 0)
+ else if (GTK_IS_PICTURE(pWidget))
+ {
+ GtkPicture* pPicture = GTK_PICTURE(pWidget);
+ if (GFile* icon_file = gtk_picture_get_file(pPicture))
+ {
+ char* icon_name = g_file_get_uri(icon_file);
+ OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
+ g_free(icon_name);
+ assert(aIconName.startsWith("private:///graphicrepository/"));
+ aIconName.startsWith("private:///graphicrepository/", &aIconName);
+ picture_set_from_icon_name_theme_lang(GTK_PICTURE(pWidget), aIconName, m_aIconTheme, m_aUILang);
+ }
+ }
+#endif
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ else if (GTK_IS_TOOL_BUTTON(pWidget))
+ {
+ GtkToolButton* pToolButton = GTK_TOOL_BUTTON(pWidget);
+ if (const gchar* icon_name = gtk_tool_button_get_icon_name(pToolButton))
+ {
+ OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
+ if (!IsAllowedBuiltInIcon(aIconName))
+ {
+ if (GtkWidget* pImage = image_new_from_icon_name_theme_lang(aIconName, m_aIconTheme, m_aUILang))
+ {
+ gtk_tool_button_set_icon_widget(pToolButton, pImage);
+ gtk_widget_show(pImage);
+ }
+ }
+ }
+
+ // if no tooltip reuse the label as default tooltip
+ if (!gtk_widget_get_tooltip_text(pWidget))
+ {
+ if (const gchar* label = gtk_tool_button_get_label(pToolButton))
+ gtk_widget_set_tooltip_text(pWidget, label);
+ }
+ }
+ else if (GTK_IS_EXPANDER(pWidget))
+ {
+ g_signal_connect(pWidget, "notify::expanded", G_CALLBACK(fix_expander), this);
+ }
+#else
+ else if (GTK_IS_BUTTON(pWidget))
+ {
+ GtkButton* pButton = GTK_BUTTON(pWidget);
+ if (const gchar* icon_name = gtk_button_get_icon_name(pButton))
+ {
+ OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
+ if (!IsAllowedBuiltInIcon(aIconName))
+ {
+ if (GtkWidget* pImage = image_new_from_icon_name_theme_lang(aIconName, m_aIconTheme, m_aUILang))
+ {
+ gtk_widget_set_halign(pImage, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign(pImage, GTK_ALIGN_CENTER);
+ gtk_button_set_child(pButton, pImage);
+ gtk_widget_show(pImage);
+ }
+ }
+ }
+ }
+ else if (GTK_IS_MENU_BUTTON(pWidget))
+ {
+ GtkMenuButton* pButton = GTK_MENU_BUTTON(pWidget);
+ if (const gchar* icon_name = gtk_menu_button_get_icon_name(pButton))
+ {
+ OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
+ if (!IsAllowedBuiltInIcon(aIconName))
+ {
+ if (GtkWidget* pImage = image_new_from_icon_name_theme_lang(aIconName, m_aIconTheme, m_aUILang))
+ {
+ gtk_widget_set_halign(pImage, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign(pImage, GTK_ALIGN_CENTER);
+ // TODO after gtk 4.6 is released require that version and drop this
+ static auto menu_button_set_child = reinterpret_cast<void (*) (GtkMenuButton*, GtkWidget*)>(dlsym(nullptr, "gtk_menu_button_set_child"));
+ if (menu_button_set_child)
+ menu_button_set_child(pButton, pImage);
+ gtk_widget_show(pImage);
+ }
+ }
+ }
+ }
+#endif
+
+ //set helpids
+ OUString sBuildableName = ::get_buildable_id(GTK_BUILDABLE(pWidget));
+ if (!sBuildableName.isEmpty())
+ {
+ OUString sHelpId = m_aHelpRoot + sBuildableName;
+ set_help_id(pWidget, sHelpId);
+ //hook up for extended help
+ const ImplSVHelpData& aHelpData = ImplGetSVHelpData();
+ if (aHelpData.mbBalloonHelp && !GTK_IS_DIALOG(pWidget) && !GTK_IS_ASSISTANT(pWidget))
+ {
+ gtk_widget_set_has_tooltip(pWidget, true);
+ g_signal_connect(pWidget, "query-tooltip", G_CALLBACK(signalTooltipQuery), nullptr);
+ }
+
+ if (bHideHelp && sBuildableName == "help")
+ gtk_widget_hide(pWidget);
+ }
+
+ if (m_pStringReplace)
+ {
+ // tdf#136498 %PRODUCTNAME shown in tool tips
+ const char* pTooltip = gtk_widget_get_tooltip_text(pWidget);
+ if (pTooltip && pTooltip[0])
+ {
+ OUString aTooltip(pTooltip, strlen(pTooltip), RTL_TEXTENCODING_UTF8);
+ aTooltip = (*m_pStringReplace)(aTooltip);
+ gtk_widget_set_tooltip_text(pWidget, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
+ }
+ }
+
+ // expand placeholder and collect potentially missing mnemonics
+ if (GTK_IS_BUTTON(pWidget))
+ {
+ GtkButton* pButton = GTK_BUTTON(pWidget);
+ if (m_pStringReplace)
+ {
+ OUString aLabel(button_get_label(pButton));
+ if (!aLabel.isEmpty())
+ button_set_label(pButton, (*m_pStringReplace)(aLabel));
+ }
+ if (gtk_button_get_use_underline(pButton))
+ m_aMnemonicButtons.push_back(pButton);
+ }
+#if GTK_CHECK_VERSION(4, 0, 0)
+ else if (GTK_IS_CHECK_BUTTON(pWidget))
+ {
+ GtkCheckButton* pButton = GTK_CHECK_BUTTON(pWidget);
+ if (m_pStringReplace)
+ {
+ OUString aLabel(get_label(pButton));
+ if (!aLabel.isEmpty())
+ set_label(pButton, (*m_pStringReplace)(aLabel));
+ }
+ if (gtk_check_button_get_use_underline(pButton))
+ m_aMnemonicCheckButtons.push_back(pButton);
+ }
+#endif
+ else if (GTK_IS_LABEL(pWidget))
+ {
+ GtkLabel* pLabel = GTK_LABEL(pWidget);
+ if (m_pStringReplace)
+ {
+ OUString aLabel(get_label(pLabel));
+ if (!aLabel.isEmpty())
+ set_label(pLabel, (*m_pStringReplace)(aLabel));
+ }
+ if (gtk_label_get_use_underline(pLabel))
+ m_aMnemonicLabels.push_back(pLabel);
+ }
+ else if (GTK_IS_TEXT_VIEW(pWidget))
+ {
+ GtkTextView* pTextView = GTK_TEXT_VIEW(pWidget);
+ if (m_pStringReplace)
+ {
+ GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(pTextView);
+ GtkTextIter start, end;
+ gtk_text_buffer_get_bounds(pBuffer, &start, &end);
+ char* pTextStr = gtk_text_buffer_get_text(pBuffer, &start, &end, true);
+ int nTextLen = pTextStr ? strlen(pTextStr) : 0;
+ if (nTextLen)
+ {
+ OUString sOldText(pTextStr, nTextLen, RTL_TEXTENCODING_UTF8);
+ OString sText(OUStringToOString((*m_pStringReplace)(sOldText), RTL_TEXTENCODING_UTF8));
+ gtk_text_buffer_set_text(pBuffer, sText.getStr(), sText.getLength());
+ }
+ g_free(pTextStr);
+ }
+ }
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ else if (GTK_IS_ENTRY(pWidget))
+ {
+ g_signal_connect(pWidget, "key-press-event", G_CALLBACK(signalEntryInsertSpecialCharKeyPress), nullptr);
+ g_signal_connect(pWidget, "populate-popup", G_CALLBACK(signalEntryPopulatePopup), nullptr);
+ }
+#endif
+ else if (GTK_IS_WINDOW(pWidget))
+ {
+ if (m_pStringReplace)
+ {
+ GtkWindow* pWindow = GTK_WINDOW(pWidget);
+ set_title(pWindow, (*m_pStringReplace)(get_title(pWindow)));
+ if (GTK_IS_MESSAGE_DIALOG(pWindow))
+ {
+ GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(pWindow);
+ set_primary_text(pMessageDialog, (*m_pStringReplace)(get_primary_text(pMessageDialog)));
+ set_secondary_text(pMessageDialog, (*m_pStringReplace)(get_secondary_text(pMessageDialog)));
+ }
+ }
+ }
+ }
+
+ //GtkBuilder sets translation domain during parse, and unsets it again afterwards.
+ //In order for GtkBuilder to find the translations bindtextdomain has to be called
+ //for the domain. So here on the first setting of "domain" we call Translate::Create
+ //to make sure that happens. Without this, if some other part of LibreOffice has
+ //used the translation machinery for this domain it will still work, but if it
+ //hasn't, e.g. tdf#119929, then the translation fails
+ void translation_domain_set()
+ {
+ Translate::Create(gtk_builder_get_translation_domain(m_pBuilder), LanguageTag(m_aUILang));
+ g_signal_handler_disconnect(m_pBuilder, m_nNotifySignalId);
+ }
+
+ static void signalNotify(GObject*, GParamSpec *pSpec, gpointer pData)
+ {
+ g_return_if_fail(pSpec != nullptr);
+ if (strcmp(pSpec->name, "translation-domain") == 0)
+ {
+ GtkInstanceBuilder* pBuilder = static_cast<GtkInstanceBuilder*>(pData);
+ pBuilder->translation_domain_set();
+ }
+ }
+
+ static void postprocess(gpointer data, gpointer user_data)
+ {
+ GObject* pObject = static_cast<GObject*>(data);
+ if (!GTK_IS_WIDGET(pObject))
+ return;
+ GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
+ pThis->postprocess_widget(GTK_WIDGET(pObject));
+ }
+
+ void DisallowCycleFocusOut()
+ {
+ assert(!m_bAllowCycleFocusOut); // we only expect this to be called when this holds
+
+ GtkWidget* pTopLevel = widget_get_toplevel(m_pParentWidget);
+ assert(pTopLevel);
+ GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pTopLevel);
+ assert(pFrame);
+ // unhook handler and let gtk cycle its own way through this widget's
+ // children because it has no non-gtk siblings
+ pFrame->DisallowCycleFocusOut();
+ }
+
+ static void signalMap(GtkWidget*, gpointer user_data)
+ {
+ GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
+ // tdf#138047 wait until map to do this because the final SalFrame may
+ // not be the same as at ctor time
+ pThis->DisallowCycleFocusOut();
+ }
+
+ void AllowCycleFocusOut()
+ {
+ assert(!m_bAllowCycleFocusOut); // we only expect this to be called when this holds
+
+ GtkWidget* pTopLevel = widget_get_toplevel(m_pParentWidget);
+ assert(pTopLevel);
+ GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pTopLevel);
+ assert(pFrame);
+ // rehook handler and let vcl cycle its own way through this widget's
+ // children
+ pFrame->AllowCycleFocusOut();
+
+ // tdf#145567 if the focus is in this hierarchy then, now that we are tearing down,
+ // move focus to the usual focus candidate for the frame
+ GtkWindow* pFocusWin = get_active_window();
+ GtkWidget* pFocus = pFocusWin ? gtk_window_get_focus(pFocusWin) : nullptr;
+ bool bHasFocus = pFocus && gtk_widget_is_ancestor(pFocus, pTopLevel);
+ if (bHasFocus)
+ pFrame->GrabFocus();
+ }
+
+ static void signalUnmap(GtkWidget*, gpointer user_data)
+ {
+ GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
+ pThis->AllowCycleFocusOut();
+ }
+
+public:
+ GtkInstanceBuilder(GtkWidget* pParent, std::u16string_view rUIRoot, const OUString& rUIFile,
+ SystemChildWindow* pInterimGlue, bool bAllowCycleFocusOut)
+ : weld::Builder()
+ , m_pStringReplace(Translate::GetReadStringHook())
+ , m_pParentWidget(pParent)
+ , m_nNotifySignalId(0)
+ , m_xInterimGlue(pInterimGlue)
+ , m_bAllowCycleFocusOut(bAllowCycleFocusOut)
+ {
+ OUString sHelpRoot(rUIFile);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ ensure_intercept_drawing_area_accessibility();
+ ensure_disable_ctrl_page_up_down_bindings();
+#endif
+
+ sal_Int32 nIdx = sHelpRoot.lastIndexOf('.');
+ if (nIdx != -1)
+ sHelpRoot = sHelpRoot.copy(0, nIdx);
+ sHelpRoot += "/";
+ m_aHelpRoot = sHelpRoot;
+ m_aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ m_aUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
+
+ OUString aUri(rUIRoot + rUIFile);
+
+ m_pBuilder = gtk_builder_new();
+ m_nNotifySignalId = g_signal_connect_data(G_OBJECT(m_pBuilder), "notify", G_CALLBACK(signalNotify), this, nullptr, G_CONNECT_AFTER);
+
+ load_ui_file(m_pBuilder, aUri);
+
+ m_pObjectList = gtk_builder_get_objects(m_pBuilder);
+ g_slist_foreach(m_pObjectList, postprocess, this);
+
+ GenerateMissingMnemonics();
+
+ if (m_xInterimGlue)
+ {
+ assert(m_pParentWidget);
+ g_object_set_data(G_OBJECT(m_pParentWidget), "InterimWindowGlue", m_xInterimGlue.get());
+
+ if (!m_bAllowCycleFocusOut)
+ {
+ g_signal_connect(G_OBJECT(m_pParentWidget), "map", G_CALLBACK(signalMap), this);
+ g_signal_connect(G_OBJECT(m_pParentWidget), "unmap", G_CALLBACK(signalUnmap), this);
+ }
+ }
+ }
+
+ void GenerateMissingMnemonics()
+ {
+ MnemonicGenerator aMnemonicGenerator('_');
+ for (const auto a : m_aMnemonicButtons)
+ aMnemonicGenerator.RegisterMnemonic(button_get_label(a));
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (const auto a : m_aMnemonicCheckButtons)
+ aMnemonicGenerator.RegisterMnemonic(get_label(a));
+#endif
+ for (const auto a : m_aMnemonicLabels)
+ aMnemonicGenerator.RegisterMnemonic(get_label(a));
+
+ for (const auto a : m_aMnemonicButtons)
+ {
+ OUString aLabel(button_get_label(a));
+ OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
+ if (aLabel == aNewLabel)
+ continue;
+ button_set_label(a, aNewLabel);
+ }
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (const auto a : m_aMnemonicCheckButtons)
+ {
+ OUString aLabel(get_label(a));
+ OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
+ if (aLabel == aNewLabel)
+ continue;
+ set_label(a, aNewLabel);
+ }
+#endif
+ for (const auto a : m_aMnemonicLabels)
+ {
+ OUString aLabel(get_label(a));
+ OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
+ if (aLabel == aNewLabel)
+ continue;
+ set_label(a, aNewLabel);
+ }
+
+ m_aMnemonicLabels.clear();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_aMnemonicCheckButtons.clear();
+#endif
+ m_aMnemonicButtons.clear();
+ }
+
+ OUString get_current_page_help_id()
+ {
+ OUString sPageHelpId;
+ // check to see if there is a notebook called tabcontrol and get the
+ // helpid for the current page of that
+ std::unique_ptr<weld::Notebook> xNotebook(weld_notebook("tabcontrol"));
+ if (xNotebook)
+ {
+ if (GtkInstanceContainer* pPage = dynamic_cast<GtkInstanceContainer*>(xNotebook->get_page(xNotebook->get_current_page_ident())))
+ {
+ GtkWidget* pContainer = pPage->getWidget();
+ if (GtkWidget* pPageWidget = widget_get_first_child(pContainer))
+ sPageHelpId = ::get_help_id(pPageWidget);
+ }
+ }
+ return sPageHelpId;
+ }
+
+ virtual ~GtkInstanceBuilder() override
+ {
+ g_slist_free(m_pObjectList);
+ g_object_unref(m_pBuilder);
+
+ if (m_xInterimGlue && !m_bAllowCycleFocusOut)
+ AllowCycleFocusOut();
+
+ m_xInterimGlue.disposeAndClear();
+ }
+
+ //ideally we would have/use weld::Container add and explicitly
+ //call add when we want to do this, but in the vcl impl the
+ //parent has to be set when the child is created, so for the
+ //gtk impl emulate this by doing this implicitly at weld time
+ void auto_add_parentless_widgets_to_container(GtkWidget* pWidget)
+ {
+ if (GTK_IS_POPOVER(pWidget))
+ return;
+ if (GTK_IS_WINDOW(pWidget))
+ return;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (!gtk_widget_get_parent(pWidget))
+ gtk_widget_set_parent(pWidget, m_pParentWidget);
+#else
+ if (widget_get_toplevel(pWidget) == pWidget)
+ gtk_container_add(GTK_CONTAINER(m_pParentWidget), pWidget);
+#endif
+ }
+
+ virtual std::unique_ptr<weld::MessageDialog> weld_message_dialog(const OUString &id) override
+ {
+ GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pMessageDialog)
+ return nullptr;
+ gtk_window_set_transient_for(GTK_WINDOW(pMessageDialog), GTK_WINDOW(widget_get_toplevel(m_pParentWidget)));
+ return std::make_unique<GtkInstanceMessageDialog>(pMessageDialog, this, true);
+ }
+
+ virtual std::unique_ptr<weld::Assistant> weld_assistant(const OUString &id) override
+ {
+ GtkAssistant* pAssistant = GTK_ASSISTANT(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pAssistant)
+ return nullptr;
+ if (m_pParentWidget)
+ gtk_window_set_transient_for(GTK_WINDOW(pAssistant), GTK_WINDOW(widget_get_toplevel(m_pParentWidget)));
+ return std::make_unique<GtkInstanceAssistant>(pAssistant, this, true);
+ }
+
+ virtual std::unique_ptr<weld::Dialog> weld_dialog(const OUString &id) override
+ {
+ GtkWindow* pDialog = GTK_WINDOW(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pDialog)
+ return nullptr;
+ if (m_pParentWidget)
+ gtk_window_set_transient_for(pDialog, GTK_WINDOW(widget_get_toplevel(m_pParentWidget)));
+ return std::make_unique<GtkInstanceDialog>(pDialog, this, true);
+ }
+
+ virtual std::unique_ptr<weld::Window> create_screenshot_window() override
+ {
+ GtkWidget* pTopLevel = nullptr;
+
+ for (GSList* l = m_pObjectList; l; l = g_slist_next(l))
+ {
+ GObject* pObj = static_cast<GObject*>(l->data);
+
+ if (!GTK_IS_WIDGET(pObj) || gtk_widget_get_parent(GTK_WIDGET(pObj)))
+ continue;
+
+ if (!pTopLevel)
+ pTopLevel = GTK_WIDGET(pObj);
+ else if (GTK_IS_WINDOW(pObj))
+ pTopLevel = GTK_WIDGET(pObj);
+ }
+
+ if (!pTopLevel)
+ return nullptr;
+
+ GtkWindow* pDialog;
+ if (GTK_IS_WINDOW(pTopLevel))
+ pDialog = GTK_WINDOW(pTopLevel);
+ else
+ {
+ pDialog = GTK_WINDOW(gtk_dialog_new());
+ ::set_help_id(GTK_WIDGET(pDialog), ::get_help_id(pTopLevel));
+
+ GtkWidget* pContentArea = gtk_dialog_get_content_area(GTK_DIALOG(pDialog));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(pContentArea), pTopLevel);
+ gtk_widget_show_all(pTopLevel);
+#else
+ gtk_box_append(GTK_BOX(pContentArea), pTopLevel);
+ gtk_widget_show(pTopLevel);
+#endif
+ }
+
+ if (m_pParentWidget)
+ gtk_window_set_transient_for(pDialog, GTK_WINDOW(widget_get_toplevel(m_pParentWidget)));
+ return std::make_unique<GtkInstanceDialog>(pDialog, this, true);
+ }
+
+ virtual std::unique_ptr<weld::Widget> weld_widget(const OUString &id) override
+ {
+ GtkWidget* pWidget = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pWidget)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(pWidget);
+ return std::make_unique<GtkInstanceWidget>(pWidget, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Container> weld_container(const OUString &id) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+#else
+ GtkWidget* pContainer = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+#endif
+ if (!pContainer)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
+ return std::make_unique<GtkInstanceContainer>(pContainer, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Box> weld_box(const OUString &id) override
+ {
+ GtkBox* pBox = GTK_BOX(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pBox)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pBox));
+ return std::make_unique<GtkInstanceBox>(pBox, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Paned> weld_paned(const OUString &id) override
+ {
+ GtkPaned* pPaned = GTK_PANED(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pPaned)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pPaned));
+ return std::make_unique<GtkInstancePaned>(pPaned, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Frame> weld_frame(const OUString &id) override
+ {
+ GtkFrame* pFrame = GTK_FRAME(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pFrame)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pFrame));
+ return std::make_unique<GtkInstanceFrame>(pFrame, this, false);
+ }
+
+ virtual std::unique_ptr<weld::ScrolledWindow> weld_scrolled_window(const OUString &id, bool bUserManagedScrolling = false) override
+ {
+ GtkScrolledWindow* pScrolledWindow = GTK_SCROLLED_WINDOW(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pScrolledWindow)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pScrolledWindow));
+ return std::make_unique<GtkInstanceScrolledWindow>(pScrolledWindow, this, false, bUserManagedScrolling);
+ }
+
+ virtual std::unique_ptr<weld::Notebook> weld_notebook(const OUString &id) override
+ {
+ GtkNotebook* pNotebook = GTK_NOTEBOOK(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pNotebook)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pNotebook));
+ return std::make_unique<GtkInstanceNotebook>(pNotebook, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Button> weld_button(const OUString &id) override
+ {
+ GtkButton* pButton = GTK_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
+ return std::make_unique<GtkInstanceButton>(pButton, this, false);
+ }
+
+ virtual std::unique_ptr<weld::MenuButton> weld_menu_button(const OUString &id) override
+ {
+ GtkMenuButton* pButton = GTK_MENU_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
+ return std::make_unique<GtkInstanceMenuButton>(pButton, nullptr, this, false);
+ }
+
+ virtual std::unique_ptr<weld::MenuToggleButton> weld_menu_toggle_button(const OUString &id) override
+ {
+ GtkMenuButton* pButton = GTK_MENU_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
+ // gtk doesn't come with exactly the same concept
+ GtkBuilder* pMenuToggleButton = makeMenuToggleButtonBuilder();
+ return std::make_unique<GtkInstanceMenuToggleButton>(pMenuToggleButton, pButton, this, false);
+ }
+
+ virtual std::unique_ptr<weld::LinkButton> weld_link_button(const OUString &id) override
+ {
+ GtkLinkButton* pButton = GTK_LINK_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
+ return std::make_unique<GtkInstanceLinkButton>(pButton, this, false);
+ }
+
+ virtual std::unique_ptr<weld::ToggleButton> weld_toggle_button(const OUString &id) override
+ {
+ GtkToggleButton* pToggleButton = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pToggleButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pToggleButton));
+ return std::make_unique<GtkInstanceToggleButton>(pToggleButton, this, false);
+ }
+
+ virtual std::unique_ptr<weld::RadioButton> weld_radio_button(const OUString &id) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkCheckButton* pRadioButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+#else
+ GtkRadioButton* pRadioButton = GTK_RADIO_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+#endif
+ if (!pRadioButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pRadioButton));
+ return std::make_unique<GtkInstanceRadioButton>(pRadioButton, this, false);
+ }
+
+ virtual std::unique_ptr<weld::CheckButton> weld_check_button(const OUString &id) override
+ {
+ GtkCheckButton* pCheckButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pCheckButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pCheckButton));
+ return std::make_unique<GtkInstanceCheckButton>(pCheckButton, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Scale> weld_scale(const OUString &id) override
+ {
+ GtkScale* pScale = GTK_SCALE(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pScale)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pScale));
+ return std::make_unique<GtkInstanceScale>(pScale, this, false);
+ }
+
+ virtual std::unique_ptr<weld::ProgressBar> weld_progress_bar(const OUString &id) override
+ {
+ GtkProgressBar* pProgressBar = GTK_PROGRESS_BAR(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pProgressBar)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pProgressBar));
+ return std::make_unique<GtkInstanceProgressBar>(pProgressBar, this, false);
+ }
+
+ virtual std::unique_ptr<weld::LevelBar> weld_level_bar(const OUString& id) override
+ {
+ GtkLevelBar* pLevelBar = GTK_LEVEL_BAR(gtk_builder_get_object(
+ m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pLevelBar)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pLevelBar));
+ return std::make_unique<GtkInstanceLevelBar>(pLevelBar, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Spinner> weld_spinner(const OUString &id) override
+ {
+ GtkSpinner* pSpinner = GTK_SPINNER(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pSpinner)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinner));
+ return std::make_unique<GtkInstanceSpinner>(pSpinner, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Image> weld_image(const OUString &id) override
+ {
+ GtkWidget* pWidget = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pWidget)
+ return nullptr;
+ if (GTK_IS_IMAGE(pWidget))
+ {
+ auto_add_parentless_widgets_to_container(pWidget);
+ return std::make_unique<GtkInstanceImage>(GTK_IMAGE(pWidget), this, false);
+ }
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_PICTURE(pWidget))
+ {
+ auto_add_parentless_widgets_to_container(pWidget);
+ return std::make_unique<GtkInstancePicture>(GTK_PICTURE(pWidget), this, false);
+ }
+#endif
+ return nullptr;
+ }
+
+ virtual std::unique_ptr<weld::Calendar> weld_calendar(const OUString &id) override
+ {
+ GtkCalendar* pCalendar = GTK_CALENDAR(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pCalendar)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pCalendar));
+ return std::make_unique<GtkInstanceCalendar>(pCalendar, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Entry> weld_entry(const OUString &id) override
+ {
+ GtkEntry* pEntry = GTK_ENTRY(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pEntry)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pEntry));
+ return std::make_unique<GtkInstanceEntry>(pEntry, this, false);
+ }
+
+ virtual std::unique_ptr<weld::SpinButton> weld_spin_button(const OUString &id) override
+ {
+ GtkSpinButton* pSpinButton = GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pSpinButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton));
+ return std::make_unique<GtkInstanceSpinButton>(pSpinButton, this, false);
+ }
+
+ virtual std::unique_ptr<weld::MetricSpinButton> weld_metric_spin_button(const OUString& id, FieldUnit eUnit) override
+ {
+ return std::make_unique<weld::MetricSpinButton>(weld_spin_button(id), eUnit);
+ }
+
+ virtual std::unique_ptr<weld::FormattedSpinButton> weld_formatted_spin_button(const OUString &id) override
+ {
+ GtkSpinButton* pSpinButton = GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pSpinButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton));
+ return std::make_unique<GtkInstanceFormattedSpinButton>(pSpinButton, this, false);
+ }
+
+ virtual std::unique_ptr<weld::ComboBox> weld_combo_box(const OUString &id) override
+ {
+ GtkComboBox* pComboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pComboBox)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pComboBox));
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return std::make_unique<GtkInstanceComboBox>(pComboBox, this, false);
+#else
+ /* we replace GtkComboBox because of difficulties with too tall menus
+
+ 1) https://gitlab.gnome.org/GNOME/gtk/issues/1910
+ has_entry long menus take forever to appear (tdf#125388)
+
+ on measuring each row, the GtkComboBox GtkTreeMenu will call
+ its area_apply_attributes_cb function on the row, but that calls
+ gtk_tree_menu_get_path_item which then loops through each child of the
+ menu looking for the widget of the row, so performance drops to useless.
+
+ All area_apply_attributes_cb does it set menu item sensitivity, so block it from running
+ with fragile hackery which assumes that the unwanted callback is the only one with a
+
+ 2) https://gitlab.gnome.org/GNOME/gtk/issues/94
+ when a super tall combobox menu is activated, and the selected
+ entry is sufficiently far down the list, then the menu doesn't
+ appear under wayland
+
+ 3) https://gitlab.gnome.org/GNOME/gtk/issues/310
+ no typeahead support
+
+ 4) we want to be able to control the width of the button, but have a drop down menu which
+ is not limited to the width of the button
+
+ 5) https://bugs.documentfoundation.org/show_bug.cgi?id=131120
+ super tall menu doesn't appear under X sometimes
+ */
+ GtkBuilder* pComboBuilder = makeComboBoxBuilder();
+ return std::make_unique<GtkInstanceComboBox>(pComboBuilder, pComboBox, this, false);
+#endif
+ }
+
+ virtual std::unique_ptr<weld::TreeView> weld_tree_view(const OUString &id) override
+ {
+ GtkTreeView* pTreeView = GTK_TREE_VIEW(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pTreeView)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pTreeView));
+ return std::make_unique<GtkInstanceTreeView>(pTreeView, this, false);
+ }
+
+ virtual std::unique_ptr<weld::IconView> weld_icon_view(const OUString &id) override
+ {
+ GtkIconView* pIconView = GTK_ICON_VIEW(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pIconView)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pIconView));
+ return std::make_unique<GtkInstanceIconView>(pIconView, this, false);
+ }
+
+ virtual std::unique_ptr<weld::EntryTreeView> weld_entry_tree_view(const OUString& containerid, const OUString& entryid, const OUString& treeviewid) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pContainer = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, OUStringToOString(containerid, RTL_TEXTENCODING_UTF8).getStr()));
+#else
+ GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, OUStringToOString(containerid, RTL_TEXTENCODING_UTF8).getStr()));
+#endif
+ if (!pContainer)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
+ return std::make_unique<GtkInstanceEntryTreeView>(pContainer, this, false,
+ weld_entry(entryid),
+ weld_tree_view(treeviewid));
+ }
+
+ virtual std::unique_ptr<weld::Label> weld_label(const OUString &id) override
+ {
+ GtkLabel* pLabel = GTK_LABEL(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pLabel)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pLabel));
+ return std::make_unique<GtkInstanceLabel>(pLabel, this, false);
+ }
+
+ virtual std::unique_ptr<weld::TextView> weld_text_view(const OUString &id) override
+ {
+ GtkTextView* pTextView = GTK_TEXT_VIEW(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pTextView)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pTextView));
+ return std::make_unique<GtkInstanceTextView>(pTextView, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Expander> weld_expander(const OUString &id) override
+ {
+ GtkExpander* pExpander = GTK_EXPANDER(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pExpander)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pExpander));
+ return std::make_unique<GtkInstanceExpander>(pExpander, this, false);
+ }
+
+ virtual std::unique_ptr<weld::DrawingArea> weld_drawing_area(const OUString &id, const a11yref& rA11y,
+ FactoryFunction /*pUITestFactoryFunction*/, void* /*pUserData*/) override
+ {
+ GtkDrawingArea* pDrawingArea = GTK_DRAWING_AREA(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pDrawingArea)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pDrawingArea));
+ return std::make_unique<GtkInstanceDrawingArea>(pDrawingArea, this, rA11y, false);
+ }
+
+ virtual std::unique_ptr<weld::Menu> weld_menu(const OUString &id) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkPopoverMenu* pMenu = GTK_POPOVER_MENU(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+#else
+ GtkMenu* pMenu = GTK_MENU(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+#endif
+ if (!pMenu)
+ return nullptr;
+ return std::make_unique<GtkInstanceMenu>(pMenu, true);
+ }
+
+ virtual std::unique_ptr<weld::Popover> weld_popover(const OUString &id) override
+ {
+ GtkPopover* pPopover = GTK_POPOVER(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pPopover)
+ return nullptr;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return std::make_unique<GtkInstancePopover>(pPopover, this, false);
+#else
+ return std::make_unique<GtkInstancePopover>(pPopover, this, true);
+#endif
+ }
+
+ virtual std::unique_ptr<weld::Toolbar> weld_toolbar(const OUString &id) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkBox* pToolbar = GTK_BOX(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+#else
+ GtkToolbar* pToolbar = GTK_TOOLBAR(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+#endif
+ if (!pToolbar)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pToolbar));
+ return std::make_unique<GtkInstanceToolbar>(pToolbar, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Scrollbar> weld_scrollbar(const OUString &id) override
+ {
+ GtkScrollbar* pScrollbar = GTK_SCROLLBAR(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
+ if (!pScrollbar)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pScrollbar));
+ return std::make_unique<GtkInstanceScrollbar>(pScrollbar, this, false);
+ }
+
+ virtual std::unique_ptr<weld::SizeGroup> create_size_group() override
+ {
+ return std::make_unique<GtkInstanceSizeGroup>();
+ }
+};
+
+}
+
+void GtkInstanceWindow::help()
+{
+ //show help for widget with keyboard focus
+ GtkWidget* pWidget = gtk_window_get_focus(m_pWindow);
+ if (!pWidget)
+ pWidget = GTK_WIDGET(m_pWindow);
+ OUString sHelpId = ::get_help_id(pWidget);
+ while (sHelpId.isEmpty())
+ {
+ pWidget = gtk_widget_get_parent(pWidget);
+ if (!pWidget)
+ break;
+ sHelpId = ::get_help_id(pWidget);
+ }
+ std::unique_ptr<weld::Widget> xTemp(pWidget != m_pWidget ? new GtkInstanceWidget(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;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // 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"))
+ {
+ OUString 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
+ GtkContainer* pContainer = nullptr;
+ if (GTK_IS_DIALOG(m_pWindow))
+ pContainer = GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pWindow)));
+ else if (GTK_IS_ASSISTANT(m_pWindow))
+ {
+ GtkAssistant* pAssistant = GTK_ASSISTANT(m_pWindow);
+ pContainer = GTK_CONTAINER(gtk_assistant_get_nth_page(pAssistant, gtk_assistant_get_current_page(pAssistant)));
+ }
+ if (pContainer)
+ {
+ GtkWidget* pContentWidget = widget_get_first_child(GTK_WIDGET(pContainer));
+ if (pContentWidget)
+ sHelpId = ::get_help_id(pContentWidget);
+ }
+ }
+ }
+#endif
+ pHelp->Start(sHelpId, 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 GtkInstanceWidget::help_hierarchy_foreach(const std::function<bool(const OUString&)>& func)
+{
+ GtkWidget* pParent = m_pWidget;
+ while ((pParent = gtk_widget_get_parent(pParent)))
+ {
+ if (func(::get_help_id(pParent)))
+ return;
+ }
+}
+
+std::unique_ptr<weld::Builder> GtkInstance::CreateBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile)
+{
+ GtkInstanceWidget* pParentWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
+ GtkWidget* pBuilderParent = pParentWidget ? pParentWidget->getWidget() : nullptr;
+ return std::make_unique<GtkInstanceBuilder>(pBuilderParent, rUIRoot, rUIFile, nullptr, true);
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+// tdf#135965 for the case of native widgets inside a GtkSalFrame and F1 pressed, run help
+// on gtk widget help ids until we hit a vcl parent and then use vcl window help ids
+gboolean GtkSalFrame::NativeWidgetHelpPressed(GtkAccelGroup*, GObject*, guint, GdkModifierType, gpointer pFrame)
+{
+ Help* pHelp = Application::GetHelp();
+ if (!pHelp)
+ return true;
+
+ GtkWindow* pWindow = static_cast<GtkWindow*>(pFrame);
+
+ vcl::Window* pChildWindow = nullptr;
+
+ //show help for widget with keyboard focus
+ GtkWidget* pWidget = gtk_window_get_focus(pWindow);
+ if (!pWidget)
+ pWidget = GTK_WIDGET(pWindow);
+ OUString sHelpId = ::get_help_id(pWidget);
+ while (sHelpId.isEmpty())
+ {
+ pWidget = gtk_widget_get_parent(pWidget);
+ if (!pWidget)
+ break;
+ pChildWindow = static_cast<vcl::Window*>(g_object_get_data(G_OBJECT(pWidget), "InterimWindowGlue"));
+ if (pChildWindow)
+ {
+ sHelpId = pChildWindow->GetHelpId();
+ break;
+ }
+ sHelpId = ::get_help_id(pWidget);
+ }
+
+ if (pChildWindow)
+ {
+ while (sHelpId.isEmpty())
+ {
+ pChildWindow = pChildWindow->GetParent();
+ if (!pChildWindow)
+ break;
+ sHelpId = pChildWindow->GetHelpId();
+ }
+ if (!pChildWindow)
+ return true;
+ pHelp->Start(sHelpId, pChildWindow);
+ return true;
+ }
+
+ if (!pWidget)
+ return true;
+ std::unique_ptr<weld::Widget> xTemp(new GtkInstanceWidget(pWidget, nullptr, false));
+ pHelp->Start(sHelpId, xTemp.get());
+ return true;
+}
+#endif
+
+std::unique_ptr<weld::Builder> GtkInstance::CreateInterimBuilder(vcl::Window* pParent, const OUString& rUIRoot, const OUString& rUIFile,
+ bool bAllowCycleFocusOut, sal_uInt64)
+{
+ // Create a foreign window which we know is a GtkGrid and make the native widgets a child of that, so we can
+ // support GtkWidgets within a vcl::Window
+ SystemWindowData winData = {};
+ winData.bClipUsingNativeWidget = true;
+ auto xEmbedWindow = VclPtr<SystemChildWindow>::Create(pParent, 0, &winData, false);
+ xEmbedWindow->Show(true, ShowFlags::NoActivate);
+ xEmbedWindow->set_expand(true);
+
+ const SystemEnvData* pEnvData = xEmbedWindow->GetSystemData();
+ if (!pEnvData)
+ return nullptr;
+
+ GtkWidget *pWindow = static_cast<GtkWidget*>(pEnvData->pWidget);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_show_all(pWindow);
+#else
+ gtk_widget_show(pWindow);
+#endif
+
+ // build the widget tree as a child of the GtkEventBox GtkGrid parent
+ return std::make_unique<GtkInstanceBuilder>(pWindow, rUIRoot, rUIFile, xEmbedWindow.get(), bAllowCycleFocusOut);
+}
+
+weld::MessageDialog* GtkInstance::CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType, VclButtonsType eButtonsType, const OUString &rPrimaryMessage)
+{
+ GtkInstanceWidget* pParentInstance = dynamic_cast<GtkInstanceWidget*>(pParent);
+ GtkWindow* pParentWindow = pParentInstance ? pParentInstance->getWindow() : nullptr;
+ GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(pParentWindow, GTK_DIALOG_MODAL,
+ VclToGtk(eMessageType), VclToGtk(eButtonsType), "%s",
+ OUStringToOString(rPrimaryMessage, RTL_TEXTENCODING_UTF8).getStr()));
+ return new GtkInstanceMessageDialog(pMessageDialog, nullptr, true);
+}
+
+weld::Window* GtkInstance::GetFrameWeld(const css::uno::Reference<css::awt::XWindow>& rWindow)
+{
+ if (SalGtkXWindow* pGtkXWindow = dynamic_cast<SalGtkXWindow*>(rWindow.get()))
+ return pGtkXWindow->getFrameWeld();
+ return SalInstance::GetFrameWeld(rWindow);
+}
+
+weld::Window* GtkSalFrame::GetFrameWeld() const
+{
+ if (!m_xFrameWeld)
+ m_xFrameWeld.reset(new GtkInstanceWindow(GTK_WINDOW(widget_get_toplevel(getWindow())), nullptr, false));
+ return m_xFrameWeld.get();
+}
+
+void* GtkInstance::CreateGStreamerSink(const SystemChildWindow *pWindow)
+{
+#if ENABLE_GSTREAMER_1_0
+ auto aSymbol = gstElementFactoryNameSymbol();
+ if (!aSymbol)
+ return nullptr;
+
+ const SystemEnvData* pEnvData = pWindow->GetSystemData();
+ if (!pEnvData)
+ return nullptr;
+
+ GstElement* pVideosink = aSymbol("gtksink", "gtksink");
+ if (!pVideosink)
+ return nullptr;
+
+ GtkWidget *pGstWidget;
+ g_object_get(pVideosink, "widget", &pGstWidget, nullptr);
+ gtk_widget_set_vexpand(pGstWidget, true);
+ gtk_widget_set_hexpand(pGstWidget, true);
+
+ GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(pParent), pGstWidget);
+#endif
+ g_object_unref(pGstWidget);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_show_all(pParent);
+#else
+ gtk_widget_show(pParent);
+#endif
+
+ return pVideosink;
+#else
+ (void)pWindow;
+ return nullptr;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtkobject.cxx b/vcl/unx/gtk3/gtkobject.cxx
new file mode 100644
index 0000000000..bd553137a8
--- /dev/null
+++ b/vcl/unx/gtk3/gtkobject.cxx
@@ -0,0 +1,611 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unx/gtk/gtkbackend.hxx>
+#include <unx/gtk/gtkobject.hxx>
+#include <unx/gtk/gtkframe.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <vcl/event.hxx>
+
+GtkSalObjectBase::GtkSalObjectBase(GtkSalFrame* pParent)
+ : m_pSocket(nullptr)
+ , m_pParent(pParent)
+ , m_pRegion(nullptr)
+{
+}
+
+GtkSalObject::GtkSalObject(GtkSalFrame* pParent, bool bShow)
+ : GtkSalObjectBase(pParent)
+{
+ if (!m_pParent)
+ return;
+
+ // our plug window
+ m_pSocket = gtk_grid_new();
+ Show( bShow );
+ // insert into container
+ gtk_fixed_put( pParent->getFixedContainer(),
+ m_pSocket,
+ 0, 0 );
+
+ Init();
+
+ g_signal_connect( G_OBJECT(m_pSocket), "destroy", G_CALLBACK(signalDestroy), this );
+
+ // #i59255# necessary due to sync effects with java child windows
+ pParent->Flush();
+}
+
+void GtkSalObjectBase::Init()
+{
+ // realize so we can get a window id
+ gtk_widget_realize( m_pSocket );
+
+ // system data
+ // tdf#139609 deliberately defer using m_pParent->GetNativeWindowHandle(m_pSocket)) to set m_aSystemData.aWindow
+ // unless its explicitly needed
+ m_aSystemData.aShellWindow = reinterpret_cast<sal_IntPtr>(this);
+ m_aSystemData.pSalFrame = nullptr;
+ m_aSystemData.pWidget = m_pSocket;
+ m_aSystemData.nScreen = m_pParent->getXScreenNumber().getXScreen();
+ m_aSystemData.toolkit = SystemEnvData::Toolkit::Gtk;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkScreen* pScreen = gtk_widget_get_screen(m_pParent->getWindow());
+ GdkVisual* pVisual = gdk_screen_get_system_visual(pScreen);
+
+#if defined(GDK_WINDOWING_X11)
+ GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ m_aSystemData.pDisplay = gdk_x11_display_get_xdisplay(pDisplay);
+ m_aSystemData.pVisual = gdk_x11_visual_get_xvisual(pVisual);
+ m_aSystemData.platform = SystemEnvData::Platform::Xcb;
+ }
+#endif
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
+ {
+ m_aSystemData.pDisplay = gdk_wayland_display_get_wl_display(pDisplay);
+ m_aSystemData.platform = SystemEnvData::Platform::Wayland;
+ }
+#endif
+
+ g_signal_connect( G_OBJECT(m_pSocket), "button-press-event", G_CALLBACK(signalButton), this );
+ g_signal_connect( G_OBJECT(m_pSocket), "button-release-event", G_CALLBACK(signalButton), this );
+ g_signal_connect( G_OBJECT(m_pSocket), "focus-in-event", G_CALLBACK(signalFocus), this );
+ g_signal_connect( G_OBJECT(m_pSocket), "focus-out-event", G_CALLBACK(signalFocus), this );
+#endif
+}
+
+GtkSalObjectBase::~GtkSalObjectBase()
+{
+ if( m_pRegion )
+ {
+ cairo_region_destroy( m_pRegion );
+ }
+}
+
+GtkSalObject::~GtkSalObject()
+{
+ if( !m_pSocket )
+ return;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_unparent(m_pSocket);
+#else
+ // remove socket from parent frame's fixed container
+ gtk_container_remove( GTK_CONTAINER(gtk_widget_get_parent(m_pSocket)),
+ m_pSocket );
+ // get rid of the socket
+ // actually the gtk_container_remove should let the ref count
+ // of the socket sink to 0 and destroy it (see signalDestroy)
+ // this is just a sanity check
+ if( m_pSocket )
+ gtk_widget_destroy( m_pSocket );
+#endif
+}
+
+void GtkSalObject::ResetClipRegion()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( m_pSocket )
+ gdk_window_shape_combine_region( widget_get_surface(m_pSocket), nullptr, 0, 0 );
+#endif
+}
+
+void GtkSalObjectBase::BeginSetClipRegion( sal_uInt32 )
+{
+ if (m_pRegion)
+ cairo_region_destroy(m_pRegion);
+ m_pRegion = cairo_region_create();
+}
+
+void GtkSalObjectBase::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ GdkRectangle aRect;
+ aRect.x = nX;
+ aRect.y = nY;
+ aRect.width = nWidth;
+ aRect.height = nHeight;
+
+ cairo_region_union_rectangle( m_pRegion, &aRect );
+}
+
+void GtkSalObject::EndSetClipRegion()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( m_pSocket )
+ gdk_window_shape_combine_region( widget_get_surface(m_pSocket), m_pRegion, 0, 0 );
+#endif
+}
+
+void GtkSalObject::SetPosSize(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight)
+{
+ if (m_pSocket)
+ {
+ GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pSocket));
+ gtk_fixed_move( pContainer, m_pSocket, nX, nY );
+ gtk_widget_set_size_request( m_pSocket, nWidth, nHeight );
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_pParent->nopaint_container_resize_children(GTK_CONTAINER(pContainer));
+#endif
+ }
+}
+
+void GtkSalObject::Reparent(SalFrame* pFrame)
+{
+ GtkSalFrame* pNewParent = static_cast<GtkSalFrame*>(pFrame);
+ if (m_pSocket)
+ {
+ GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pSocket));
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gint nX(0), nY(0);
+ gtk_container_child_get(GTK_CONTAINER(pContainer), m_pSocket,
+ "x", &nX,
+ "y", &nY,
+ nullptr);
+#else
+ double nX(0), nY(0);
+ gtk_fixed_get_child_position(pContainer, m_pSocket, &nX, &nY);
+#endif
+
+ g_object_ref(m_pSocket);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_remove(GTK_CONTAINER(pContainer), m_pSocket);
+#else
+ gtk_fixed_remove(pContainer, m_pSocket);
+#endif
+
+ gtk_fixed_put(pNewParent->getFixedContainer(),
+ m_pSocket,
+ nX, nY);
+
+ g_object_unref(m_pSocket);
+ }
+ m_pParent = pNewParent;
+}
+
+void GtkSalObject::Show( bool bVisible )
+{
+ if( m_pSocket )
+ {
+ if( bVisible )
+ gtk_widget_show(m_pSocket);
+ else
+ gtk_widget_hide(m_pSocket);
+ }
+}
+
+Size GtkSalObjectBase::GetOptimalSize() const
+{
+ if (m_pSocket)
+ {
+ bool bVisible = gtk_widget_get_visible(m_pSocket);
+ if (!bVisible)
+ gtk_widget_set_visible(m_pSocket, true);
+
+ // Undo SetPosSize before getting its preferred size
+ gint width(-1), height(-1);
+ gtk_widget_get_size_request(m_pSocket, &width, &height);
+ gtk_widget_set_size_request(m_pSocket, -1, -1);
+
+ GtkRequisition size;
+ gtk_widget_get_preferred_size(m_pSocket, nullptr, &size);
+
+ // Restore SetPosSize size
+ gtk_widget_set_size_request(m_pSocket, width, height);
+
+ if (!bVisible)
+ gtk_widget_set_visible(m_pSocket, false);
+ return Size(size.width, size.height);
+ }
+ return Size();
+}
+
+const SystemEnvData* GtkSalObjectBase::GetSystemData() const
+{
+ return &m_aSystemData;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalObjectBase::signalButton( GtkWidget*, GdkEventButton* pEvent, gpointer object )
+{
+ GtkSalObjectBase* pThis = static_cast<GtkSalObject*>(object);
+
+ if( pEvent->type == GDK_BUTTON_PRESS )
+ {
+ pThis->CallCallback( SalObjEvent::ToTop );
+ }
+
+ return FALSE;
+}
+
+gboolean GtkSalObjectBase::signalFocus( GtkWidget*, GdkEventFocus* pEvent, gpointer object )
+{
+ GtkSalObjectBase* pThis = static_cast<GtkSalObject*>(object);
+
+ pThis->CallCallback( pEvent->in ? SalObjEvent::GetFocus : SalObjEvent::LoseFocus );
+
+ return FALSE;
+}
+#endif
+
+void GtkSalObject::signalDestroy( GtkWidget* pObj, gpointer object )
+{
+ GtkSalObject* pThis = static_cast<GtkSalObject*>(object);
+ if( pObj == pThis->m_pSocket )
+ {
+ pThis->m_pSocket = nullptr;
+ }
+}
+
+void GtkSalObjectBase::SetForwardKey( bool bEnable )
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( bEnable )
+ gtk_widget_add_events( GTK_WIDGET( m_pSocket ), GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK );
+ else
+ gtk_widget_set_events( GTK_WIDGET( m_pSocket ), ~(GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK) & gtk_widget_get_events( GTK_WIDGET( m_pSocket ) ) );
+#else
+ (void)bEnable;
+#endif
+}
+
+GtkSalObjectWidgetClip::GtkSalObjectWidgetClip(GtkSalFrame* pParent, bool bShow)
+ : GtkSalObjectBase(pParent)
+ , m_pScrolledWindow(nullptr)
+ , m_pViewPort(nullptr)
+ , m_pBgCssProvider(nullptr)
+{
+ if( !pParent )
+ return;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_pScrolledWindow = gtk_scrolled_window_new(nullptr, nullptr);
+ g_signal_connect(m_pScrolledWindow, "scroll-event", G_CALLBACK(signalScroll), this);
+#else
+ m_pScrolledWindow = gtk_scrolled_window_new();
+ GtkEventController* pScrollController = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
+ g_signal_connect(pScrollController, "scroll", G_CALLBACK(signalScroll), this);
+ gtk_widget_add_controller(m_pScrolledWindow, pScrollController);
+#endif
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(m_pScrolledWindow),
+ GTK_POLICY_EXTERNAL, GTK_POLICY_EXTERNAL);
+
+ // insert into container
+ gtk_fixed_put( pParent->getFixedContainer(),
+ m_pScrolledWindow,
+ 0, 0 );
+
+ // deliberately without adjustments to avoid gtk's auto adjustment on changing focus
+ m_pViewPort = gtk_viewport_new(nullptr, nullptr);
+
+ // force in a fake background of a suitable color
+ SetViewPortBackground();
+
+ ImplGetDefaultWindow()->AddEventListener(LINK(this, GtkSalObjectWidgetClip, SettingsChangedHdl));
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), m_pViewPort);
+#else
+ gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(m_pScrolledWindow), m_pViewPort);
+#endif
+ gtk_widget_show(m_pViewPort);
+
+ // our plug window
+ m_pSocket = gtk_grid_new();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(m_pViewPort), m_pSocket);
+#else
+ gtk_viewport_set_child(GTK_VIEWPORT(m_pViewPort), m_pSocket);
+#endif
+ gtk_widget_show(m_pSocket);
+
+ Show(bShow);
+
+ Init();
+
+ g_signal_connect( G_OBJECT(m_pSocket), "destroy", G_CALLBACK(signalDestroy), this );
+}
+
+// force in a fake background of a suitable color
+void GtkSalObjectWidgetClip::SetViewPortBackground()
+{
+ GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(m_pViewPort);
+ if (m_pBgCssProvider)
+ gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider));
+ m_pBgCssProvider = gtk_css_provider_new();
+ OUString sColor = Application::GetSettings().GetStyleSettings().GetDialogColor().AsRGBHexString();
+ OUString aBuffer = "* { background-color: #" + sColor + "; }";
+ OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
+ css_provider_load_from_data(m_pBgCssProvider, aResult.getStr(), aResult.getLength());
+ gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+}
+
+IMPL_LINK(GtkSalObjectWidgetClip, SettingsChangedHdl, VclWindowEvent&, rEvent, void)
+{
+ if (rEvent.GetId() != VclEventId::WindowDataChanged)
+ return;
+
+ DataChangedEvent* pData = static_cast<DataChangedEvent*>(rEvent.GetData());
+ if (pData->GetType() == DataChangedEventType::SETTINGS)
+ SetViewPortBackground();
+}
+
+GtkSalObjectWidgetClip::~GtkSalObjectWidgetClip()
+{
+ ImplGetDefaultWindow()->RemoveEventListener(LINK(this, GtkSalObjectWidgetClip, SettingsChangedHdl));
+
+ if( !m_pSocket )
+ return;
+
+ // remove socket from parent frame's fixed container
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_remove( GTK_CONTAINER(gtk_widget_get_parent(m_pScrolledWindow)),
+ m_pScrolledWindow );
+
+ // get rid of the socket
+ // actually the gtk_container_remove should let the ref count
+ // of the socket sink to 0 and destroy it (see signalDestroy)
+ // this is just a sanity check
+ if( m_pScrolledWindow )
+ gtk_widget_destroy( m_pScrolledWindow );
+#else
+ gtk_fixed_remove(GTK_FIXED(gtk_widget_get_parent(m_pScrolledWindow)),
+ m_pScrolledWindow);
+#endif
+}
+
+void GtkSalObjectWidgetClip::ResetClipRegion()
+{
+ m_aClipRect = tools::Rectangle();
+ ApplyClipRegion();
+}
+
+void GtkSalObjectWidgetClip::EndSetClipRegion()
+{
+ int nRects = cairo_region_num_rectangles(m_pRegion);
+ assert(nRects == 0 || nRects == 1);
+ if (nRects == 0)
+ m_aClipRect = tools::Rectangle();
+ else
+ {
+ cairo_rectangle_int_t rectangle;
+ cairo_region_get_rectangle(m_pRegion, 0, &rectangle);
+ m_aClipRect = tools::Rectangle(Point(rectangle.x, rectangle.y), Size(rectangle.width, rectangle.height));
+ }
+ ApplyClipRegion();
+}
+
+void GtkSalObjectWidgetClip::ApplyClipRegion()
+{
+ if( !m_pSocket )
+ return;
+
+ GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pScrolledWindow));
+
+ GtkAllocation allocation;
+ allocation.x = m_aRect.Left() + m_aClipRect.Left();
+ allocation.y = m_aRect.Top() + m_aClipRect.Top();
+ if (m_aClipRect.IsEmpty())
+ {
+ allocation.width = m_aRect.GetWidth();
+ allocation.height = m_aRect.GetHeight();
+ }
+ else
+ {
+ allocation.width = m_aClipRect.GetWidth();
+ allocation.height = m_aClipRect.GetHeight();
+ }
+
+ if (AllSettings::GetLayoutRTL())
+ {
+ GtkAllocation aParentAllocation;
+ gtk_widget_get_allocation(GTK_WIDGET(pContainer), &aParentAllocation);
+ gtk_fixed_move(pContainer, m_pScrolledWindow, aParentAllocation.width - allocation.width - 1 - allocation.x, allocation.y);
+ }
+ else
+ gtk_fixed_move(pContainer, m_pScrolledWindow, allocation.x, allocation.y);
+ gtk_widget_set_size_request(m_pScrolledWindow, allocation.width, allocation.height);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_size_allocate(m_pScrolledWindow, &allocation);
+#else
+ gtk_widget_size_allocate(m_pScrolledWindow, &allocation, 0);
+#endif
+
+ gtk_adjustment_set_value(gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(m_pScrolledWindow)), m_aClipRect.Left());
+ gtk_adjustment_set_value(gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(m_pScrolledWindow)), m_aClipRect.Top());
+}
+
+void GtkSalObjectWidgetClip::SetPosSize(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight)
+{
+ m_aRect = tools::Rectangle(Point(nX, nY), Size(nWidth, nHeight));
+ if (m_pSocket)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pScrolledWindow));
+#endif
+ gtk_widget_set_size_request(m_pSocket, nWidth, nHeight);
+ ApplyClipRegion();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_pParent->nopaint_container_resize_children(GTK_CONTAINER(pContainer));
+#endif
+ }
+}
+
+void GtkSalObjectWidgetClip::Reparent(SalFrame* pFrame)
+{
+ GtkSalFrame* pNewParent = static_cast<GtkSalFrame*>(pFrame);
+ if (m_pSocket)
+ {
+ GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pScrolledWindow));
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gint nX(0), nY(0);
+ gtk_container_child_get(GTK_CONTAINER(pContainer), m_pScrolledWindow,
+ "x", &nX,
+ "y", &nY,
+ nullptr);
+#else
+ double nX(0), nY(0);
+ gtk_fixed_get_child_position(pContainer, m_pScrolledWindow, &nX, &nY);
+#endif
+
+ g_object_ref(m_pScrolledWindow);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_remove(GTK_CONTAINER(pContainer), m_pScrolledWindow);
+#else
+ gtk_fixed_remove(pContainer, m_pScrolledWindow);
+#endif
+
+ gtk_fixed_put(pNewParent->getFixedContainer(),
+ m_pScrolledWindow,
+ nX, nY);
+
+ g_object_unref(m_pScrolledWindow);
+ }
+ m_pParent = pNewParent;
+}
+
+void GtkSalObjectWidgetClip::Show( bool bVisible )
+{
+ if (!m_pSocket)
+ return;
+ bool bCurrentVis = gtk_widget_get_visible(m_pScrolledWindow);
+ if (bVisible == bCurrentVis)
+ return;
+ if( bVisible )
+ {
+ gtk_widget_show(m_pScrolledWindow);
+ // tdf#146641 allocations attempted while hidden are discarded by gtk,
+ // so on transition to visible ApplyClipRegion needs to be called
+ ApplyClipRegion();
+ }
+ else
+ {
+ // on hiding the widget, if a child has focus gtk will want to move the focus out of the widget
+ // but we want to keep the focus where it is, e.g. writer's comments in margin feature put
+ // cursor in a sidebar comment and scroll the page so the comment is invisible, we want the focus
+ // to stay in the invisible widget, so its there when we scroll back or on a keypress the widget
+ // gets the keystroke and scrolls back to make it visible again
+ GtkWidget* pTopLevel = widget_get_toplevel(m_pScrolledWindow);
+ GtkWidget* pOldFocus = GTK_IS_WINDOW(pTopLevel) ? gtk_window_get_focus(GTK_WINDOW(pTopLevel)) : nullptr;
+
+ g_object_set_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange", GINT_TO_POINTER(true) );
+
+ gtk_widget_hide(m_pScrolledWindow);
+
+ GtkWidget* pNewFocus = GTK_IS_WINDOW(pTopLevel) ? gtk_window_get_focus(GTK_WINDOW(pTopLevel)) : nullptr;
+ if (pOldFocus && pOldFocus != pNewFocus)
+ gtk_widget_grab_focus(pOldFocus);
+
+ g_object_set_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange", GINT_TO_POINTER(false) );
+ }
+}
+
+void GtkSalObjectWidgetClip::signalDestroy( GtkWidget* pObj, gpointer object )
+{
+ GtkSalObjectWidgetClip* pThis = static_cast<GtkSalObjectWidgetClip*>(object);
+ if( pObj == pThis->m_pSocket )
+ {
+ pThis->m_pSocket = nullptr;
+ pThis->m_pScrolledWindow = nullptr;
+ }
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalObjectWidgetClip::signalScroll(GtkWidget* pScrolledWindow, GdkEvent* pEvent, gpointer object)
+{
+ GtkSalObjectWidgetClip* pThis = static_cast<GtkSalObjectWidgetClip*>(object);
+ return pThis->signal_scroll(pScrolledWindow, pEvent);
+}
+#else
+gboolean GtkSalObjectWidgetClip::signalScroll(GtkEventControllerScroll* pController, double delta_x, double delta_y, gpointer object)
+{
+ GtkSalObjectWidgetClip* pThis = static_cast<GtkSalObjectWidgetClip*>(object);
+ return pThis->signal_scroll(pController, delta_x, delta_y);
+}
+#endif
+
+// forward the wheel scroll events onto the main window instead
+#if !GTK_CHECK_VERSION(4, 0, 0)
+bool GtkSalObjectWidgetClip::signal_scroll(GtkWidget*, GdkEvent* pEvent)
+{
+ GtkWidget* pEventWidget = gtk_get_event_widget(pEvent);
+
+ GtkWidget* pMouseEventWidget = m_pParent->getMouseEventWidget();
+
+ gtk_coord dest_x, dest_y;
+ gtk_widget_translate_coordinates(pEventWidget,
+ pMouseEventWidget,
+ pEvent->scroll.x,
+ pEvent->scroll.y,
+ &dest_x,
+ &dest_y);
+ pEvent->scroll.x = dest_x;
+ pEvent->scroll.y = dest_y;
+
+ GtkSalFrame::signalScroll(pMouseEventWidget, pEvent, m_pParent);
+ return true;
+}
+#else
+bool GtkSalObjectWidgetClip::signal_scroll(GtkEventControllerScroll* pController, double delta_x, double delta_y)
+{
+ GtkWidget* pEventWidget = m_pScrolledWindow;
+
+ GtkWidget* pMouseEventWidget = m_pParent->getMouseEventWidget();
+
+ gtk_coord dest_x, dest_y;
+ gtk_widget_translate_coordinates(pEventWidget,
+ pMouseEventWidget,
+ delta_x,
+ delta_y,
+ &dest_x,
+ &dest_y);
+ delta_x = dest_x;
+ delta_y = dest_y;
+
+ GtkSalFrame::signalScroll(pController, delta_x, delta_y, m_pParent);
+ return true;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtksalmenu.cxx b/vcl/unx/gtk3/gtksalmenu.cxx
new file mode 100644
index 0000000000..a510473650
--- /dev/null
+++ b/vcl/unx/gtk3/gtksalmenu.cxx
@@ -0,0 +1,1646 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <unx/gtk/gtksalmenu.hxx>
+
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/glomenu.h>
+#include <unx/gtk/gloactiongroup.h>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/filter/PngImageWriter.hxx>
+#include <vcl/pdfwriter.hxx> // for escapeStringXML
+
+#include <o3tl/string_view.hxx>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <window.h>
+#include <strings.hrc>
+
+static bool bUnityMode = false;
+
+/*
+ * This function generates a unique command name for each menu item
+ */
+static gchar* GetCommandForItem(GtkSalMenu* pParentMenu, sal_uInt16 nItemId)
+{
+ OString aCommand = "window-" +
+ OString::number(reinterpret_cast<sal_uIntPtr>(pParentMenu)) +
+ "-" + OString::number(nItemId);
+ return g_strdup(aCommand.getStr());
+}
+
+static gchar* GetCommandForItem(GtkSalMenuItem* pSalMenuItem)
+{
+ return GetCommandForItem(pSalMenuItem->mpParentMenu,
+ pSalMenuItem->mnId);
+}
+
+bool GtkSalMenu::PrepUpdate() const
+{
+ return mpMenuModel && mpActionGroup;
+}
+
+/*
+ * Menu updating methods
+ */
+
+static void RemoveSpareItemsFromNativeMenu( GLOMenu* pMenu, GList** pOldCommandList, unsigned nSection, unsigned nValidItems )
+{
+ sal_Int32 nSectionItems = g_lo_menu_get_n_items_from_section( pMenu, nSection );
+
+ while ( nSectionItems > static_cast<sal_Int32>(nValidItems) )
+ {
+ gchar* aCommand = g_lo_menu_get_command_from_item_in_section( pMenu, nSection, --nSectionItems );
+
+ if ( aCommand != nullptr && pOldCommandList != nullptr )
+ *pOldCommandList = g_list_append( *pOldCommandList, g_strdup( aCommand ) );
+
+ g_free( aCommand );
+
+ g_lo_menu_remove_from_section( pMenu, nSection, nSectionItems );
+ }
+}
+
+typedef std::pair<GtkSalMenu*, sal_uInt16> MenuAndId;
+
+namespace
+{
+ MenuAndId decode_command(const gchar *action_name)
+ {
+ std::string_view sCommand(action_name);
+
+ sal_Int32 nIndex = 0;
+ std::string_view sWindow = o3tl::getToken(sCommand, 0, '-', nIndex);
+ std::string_view sGtkSalMenu = o3tl::getToken(sCommand, 0, '-', nIndex);
+ std::string_view sItemId = o3tl::getToken(sCommand, 0, '-', nIndex);
+
+ GtkSalMenu* pSalSubMenu = reinterpret_cast<GtkSalMenu*>(o3tl::toInt64(sGtkSalMenu));
+
+ assert(sWindow == "window" && pSalSubMenu);
+ (void) sWindow;
+
+ return MenuAndId(pSalSubMenu, o3tl::toInt32(sItemId));
+ }
+}
+
+static void RemoveDisabledItemsFromNativeMenu(GLOMenu* pMenu, GList** pOldCommandList,
+ sal_Int32 nSection, GActionGroup* pActionGroup)
+{
+ while (nSection >= 0)
+ {
+ sal_Int32 nSectionItems = g_lo_menu_get_n_items_from_section( pMenu, nSection );
+ while (nSectionItems--)
+ {
+ gchar* pCommand = g_lo_menu_get_command_from_item_in_section(pMenu, nSection, nSectionItems);
+ // remove disabled entries
+ bool bRemove = !g_action_group_get_action_enabled(pActionGroup, pCommand);
+ if (!bRemove)
+ {
+ //also remove any empty submenus
+ GLOMenu* pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section(pMenu, nSection, nSectionItems);
+ if (pSubMenuModel)
+ {
+ gint nSubMenuSections = g_menu_model_get_n_items(G_MENU_MODEL(pSubMenuModel));
+ if (nSubMenuSections == 0)
+ bRemove = true;
+ else if (nSubMenuSections == 1)
+ {
+ gint nItems = g_lo_menu_get_n_items_from_section(pSubMenuModel, 0);
+ if (nItems == 0)
+ bRemove = true;
+ else if (nItems == 1)
+ {
+ //If the only entry is the "No Selection Possible" entry, then we are allowed
+ //to removed it
+ gchar* pSubCommand = g_lo_menu_get_command_from_item_in_section(pSubMenuModel, 0, 0);
+ MenuAndId aMenuAndId(decode_command(pSubCommand));
+ bRemove = aMenuAndId.second == 0xFFFF;
+ g_free(pSubCommand);
+ }
+ }
+ }
+ }
+
+ if (bRemove)
+ {
+ //but tdf#86850 Always display clipboard functions
+ bRemove = g_strcmp0(pCommand, ".uno:Cut") &&
+ g_strcmp0(pCommand, ".uno:Copy") &&
+ g_strcmp0(pCommand, ".uno:Paste");
+ }
+
+ if (bRemove)
+ {
+ if (pCommand != nullptr && pOldCommandList != nullptr)
+ *pOldCommandList = g_list_append(*pOldCommandList, g_strdup(pCommand));
+ g_lo_menu_remove_from_section(pMenu, nSection, nSectionItems);
+ }
+
+ g_free(pCommand);
+ }
+ --nSection;
+ }
+}
+
+static void RemoveSpareSectionsFromNativeMenu( GLOMenu* pMenu, GList** pOldCommandList, sal_Int32 nLastSection )
+{
+ if ( pMenu == nullptr || pOldCommandList == nullptr )
+ return;
+
+ sal_Int32 n = g_menu_model_get_n_items( G_MENU_MODEL( pMenu ) ) - 1;
+
+ for ( ; n > nLastSection; n--)
+ {
+ RemoveSpareItemsFromNativeMenu( pMenu, pOldCommandList, n, 0 );
+ g_lo_menu_remove( pMenu, n );
+ }
+}
+
+static gint CompareStr( gpointer str1, gpointer str2 )
+{
+ return g_strcmp0( static_cast<const gchar*>(str1), static_cast<const gchar*>(str2) );
+}
+
+static void RemoveUnusedCommands( GLOActionGroup* pActionGroup, GList* pOldCommandList, GList* pNewCommandList )
+{
+ if ( pActionGroup == nullptr || pOldCommandList == nullptr )
+ {
+ g_list_free_full( pOldCommandList, g_free );
+ g_list_free_full( pNewCommandList, g_free );
+ return;
+ }
+
+ while ( pNewCommandList != nullptr )
+ {
+ GList* pNewCommand = g_list_first( pNewCommandList );
+ pNewCommandList = g_list_remove_link( pNewCommandList, pNewCommand );
+
+ gpointer aCommand = g_list_nth_data( pNewCommand, 0 );
+
+ GList* pOldCommand = g_list_find_custom( pOldCommandList, aCommand, reinterpret_cast<GCompareFunc>(CompareStr) );
+
+ if ( pOldCommand != nullptr )
+ {
+ pOldCommandList = g_list_remove_link( pOldCommandList, pOldCommand );
+ g_list_free_full( pOldCommand, g_free );
+ }
+
+ g_list_free_full( pNewCommand, g_free );
+ }
+
+ while ( pOldCommandList != nullptr )
+ {
+ GList* pCommand = g_list_first( pOldCommandList );
+ pOldCommandList = g_list_remove_link( pOldCommandList, pCommand );
+
+ gchar* aCommand = static_cast<gchar*>(g_list_nth_data( pCommand, 0 ));
+
+ g_lo_action_group_remove( pActionGroup, aCommand );
+
+ g_list_free_full( pCommand, g_free );
+ }
+}
+
+void GtkSalMenu::ImplUpdate(bool bRecurse, bool bRemoveDisabledEntries)
+{
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.unity", "ImplUpdate pre PrepUpdate");
+ if( !PrepUpdate() )
+ return;
+
+ if (mbNeedsUpdate)
+ {
+ mbNeedsUpdate = false;
+ if (mbMenuBar && maUpdateMenuBarIdle.IsActive())
+ {
+ maUpdateMenuBarIdle.Stop();
+ // tdf#124391 Prevent doubled menus in global menu
+ if (!bUnityMode)
+ {
+ maUpdateMenuBarIdle.Invoke();
+ return;
+ }
+ }
+ }
+
+ Menu* pVCLMenu = mpVCLMenu;
+ GLOMenu* pLOMenu = G_LO_MENU( mpMenuModel );
+ GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
+ SAL_INFO("vcl.unity", "Syncing vcl menu " << pVCLMenu << " to menu model " << pLOMenu << " and action group " << pActionGroup);
+ GList *pOldCommandList = nullptr;
+ GList *pNewCommandList = nullptr;
+
+ sal_uInt16 nLOMenuSize = g_menu_model_get_n_items( G_MENU_MODEL( pLOMenu ) );
+
+ if ( nLOMenuSize == 0 )
+ g_lo_menu_new_section( pLOMenu, 0, nullptr );
+
+ sal_Int32 nSection = 0;
+ sal_Int32 nItemPos = 0;
+ sal_Int32 validItems = 0;
+ sal_Int32 nItem;
+
+ for ( nItem = 0; nItem < static_cast<sal_Int32>(GetItemCount()); nItem++ ) {
+ if ( !IsItemVisible( nItem ) )
+ continue;
+
+ GtkSalMenuItem *pSalMenuItem = GetItemAtPos( nItem );
+ sal_uInt16 nId = pSalMenuItem->mnId;
+
+ // PopupMenu::ImplExecute might add <No Selection Possible> entry to top-level
+ // popup menu, but we have our own implementation below, so skip that one.
+ if ( nId == 0xFFFF )
+ continue;
+
+ if ( pSalMenuItem->mnType == MenuItemType::SEPARATOR )
+ {
+ // Delete extra items from current section.
+ RemoveSpareItemsFromNativeMenu( pLOMenu, &pOldCommandList, nSection, validItems );
+
+ nSection++;
+ nItemPos = 0;
+ validItems = 0;
+
+ if ( nLOMenuSize <= nSection )
+ {
+ g_lo_menu_new_section( pLOMenu, nSection, nullptr );
+ nLOMenuSize++;
+ }
+
+ continue;
+ }
+
+ if ( nItemPos >= g_lo_menu_get_n_items_from_section( pLOMenu, nSection ) )
+ g_lo_menu_insert_in_section( pLOMenu, nSection, nItemPos, "EMPTY STRING" );
+
+ // Get internal menu item values.
+ OUString aText = pVCLMenu->GetItemText( nId );
+ Image aImage = pVCLMenu->GetItemImage( nId );
+ bool bEnabled = pVCLMenu->IsItemEnabled( nId );
+ vcl::KeyCode nAccelKey = pVCLMenu->GetAccelKey( nId );
+ bool bChecked = pVCLMenu->IsItemChecked( nId );
+ MenuItemBits itemBits = pVCLMenu->GetItemBits( nId );
+
+ // Store current item command in command list.
+ gchar *aCurrentCommand = g_lo_menu_get_command_from_item_in_section( pLOMenu, nSection, nItemPos );
+
+ if ( aCurrentCommand != nullptr )
+ pOldCommandList = g_list_append( pOldCommandList, aCurrentCommand );
+
+ // Get the new command for the item.
+ gchar* aNativeCommand = GetCommandForItem(pSalMenuItem);
+
+ // Force updating of native menu labels.
+ NativeSetItemText( nSection, nItemPos, aText );
+ NativeSetItemIcon( nSection, nItemPos, aImage );
+ NativeSetAccelerator(nSection, nItemPos, nAccelKey, nAccelKey.GetName());
+
+ if ( g_strcmp0( aNativeCommand, "" ) != 0 && pSalMenuItem->mpSubMenu == nullptr )
+ {
+ NativeSetItemCommand( nSection, nItemPos, nId, aNativeCommand, itemBits, bChecked, false );
+ NativeCheckItem( nSection, nItemPos, itemBits, bChecked );
+ NativeSetEnableItem( aNativeCommand, bEnabled );
+
+ pNewCommandList = g_list_append( pNewCommandList, g_strdup( aNativeCommand ) );
+ }
+
+ GtkSalMenu* pSubmenu = pSalMenuItem->mpSubMenu;
+
+ if ( pSubmenu && pSubmenu->GetMenu() )
+ {
+ bool bNonMenuChangedToMenu = NativeSetItemCommand( nSection, nItemPos, nId, aNativeCommand, itemBits, false, true );
+ pNewCommandList = g_list_append( pNewCommandList, g_strdup( aNativeCommand ) );
+
+ GLOMenu* pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section( pLOMenu, nSection, nItemPos );
+
+ if ( pSubMenuModel == nullptr )
+ {
+ g_lo_menu_new_submenu_in_item_in_section( pLOMenu, nSection, nItemPos );
+ pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section( pLOMenu, nSection, nItemPos );
+ }
+
+ assert(pSubMenuModel);
+
+ if (bRecurse || bNonMenuChangedToMenu)
+ {
+ SAL_INFO("vcl.unity", "preparing submenu " << pSubMenuModel << " to menu model " << G_MENU_MODEL(pSubMenuModel) << " and action group " << G_ACTION_GROUP(pActionGroup));
+ pSubmenu->SetMenuModel( G_MENU_MODEL( pSubMenuModel ) );
+ pSubmenu->SetActionGroup( G_ACTION_GROUP( pActionGroup ) );
+ pSubmenu->ImplUpdate(true, bRemoveDisabledEntries);
+ }
+
+ g_object_unref( pSubMenuModel );
+ }
+
+ g_free( aNativeCommand );
+
+ ++nItemPos;
+ ++validItems;
+ }
+
+ if (bRemoveDisabledEntries)
+ {
+ // Delete disabled items in last section.
+ RemoveDisabledItemsFromNativeMenu(pLOMenu, &pOldCommandList, nSection, G_ACTION_GROUP(pActionGroup));
+ }
+
+ // Delete extra items in last section.
+ RemoveSpareItemsFromNativeMenu( pLOMenu, &pOldCommandList, nSection, validItems );
+
+ // Delete extra sections.
+ RemoveSpareSectionsFromNativeMenu( pLOMenu, &pOldCommandList, nSection );
+
+ // Delete unused commands.
+ RemoveUnusedCommands( pActionGroup, pOldCommandList, pNewCommandList );
+
+ // Resolves: tdf#103166 if the menu is empty, add a disabled
+ // <No Selection Possible> placeholder.
+ sal_Int32 nSectionsCount = g_menu_model_get_n_items(G_MENU_MODEL(pLOMenu));
+ gint nItemsCount = 0;
+ for (nSection = 0; nSection < nSectionsCount; ++nSection)
+ {
+ nItemsCount += g_lo_menu_get_n_items_from_section(pLOMenu, nSection);
+ if (nItemsCount)
+ break;
+ }
+ if (!nItemsCount)
+ {
+ gchar* aNativeCommand = GetCommandForItem(this, 0xFFFF);
+ OUString aPlaceholderText(VclResId(SV_RESID_STRING_NOSELECTIONPOSSIBLE));
+ g_lo_menu_insert_in_section(pLOMenu, nSection-1, 0,
+ OUStringToOString(aPlaceholderText, RTL_TEXTENCODING_UTF8).getStr());
+ NativeSetItemCommand(nSection-1, 0, 0xFFFF, aNativeCommand, MenuItemBits::NONE, false, false);
+ NativeSetEnableItem(aNativeCommand, false);
+ g_free(aNativeCommand);
+ }
+}
+
+void GtkSalMenu::Update()
+{
+ //find out if top level is a menubar or not, if not, then it's a popup menu
+ //hierarchy and in those we hide (most) disabled entries
+ const GtkSalMenu* pMenu = this;
+ while (pMenu->mpParentSalMenu)
+ pMenu = pMenu->mpParentSalMenu;
+
+ bool bAlwaysShowDisabledEntries;
+ if (pMenu->mbMenuBar)
+ bAlwaysShowDisabledEntries = !bool(mpVCLMenu->GetMenuFlags() & MenuFlags::HideDisabledEntries);
+ else
+ bAlwaysShowDisabledEntries = bool(mpVCLMenu->GetMenuFlags() & MenuFlags::AlwaysShowDisabledEntries);
+
+ ImplUpdate(false, !bAlwaysShowDisabledEntries);
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+static void MenuPositionFunc(GtkMenu* menu, gint* x, gint* y, gboolean* push_in, gpointer user_data)
+{
+ Point *pPos = static_cast<Point*>(user_data);
+ *x = pPos->X();
+ if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL)
+ {
+ GtkRequisition natural_size;
+ gtk_widget_get_preferred_size(GTK_WIDGET(menu), nullptr, &natural_size);
+ *x -= natural_size.width;
+ }
+ *y = pPos->Y();
+ *push_in = false;
+}
+#endif
+
+static void MenuClosed(GtkPopover* pWidget, GMainLoop* pLoop)
+{
+ // gtk4 4.4.0: click on an entry in a submenu of a menu crashes without this workaround
+ gtk_widget_grab_focus(gtk_widget_get_parent(GTK_WIDGET(pWidget)));
+ g_main_loop_quit(pLoop);
+}
+
+bool GtkSalMenu::ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangle& rRect,
+ FloatWinPopupFlags nFlags)
+{
+ VclPtr<vcl::Window> xParent = pWin->ImplGetWindowImpl()->mpRealParent;
+ mpFrame = static_cast<GtkSalFrame*>(xParent->ImplGetFrame());
+
+ GLOActionGroup* pActionGroup = g_lo_action_group_new();
+ mpActionGroup = G_ACTION_GROUP(pActionGroup);
+ mpMenuModel = G_MENU_MODEL(g_lo_menu_new());
+ // Generate the main menu structure, populates mpMenuModel
+ UpdateFull();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mpMenuWidget = gtk_menu_new_from_model(mpMenuModel);
+ gtk_menu_attach_to_widget(GTK_MENU(mpMenuWidget), mpFrame->getMouseEventWidget(), nullptr);
+#else
+ mpMenuWidget = gtk_popover_menu_new_from_model(mpMenuModel);
+ gtk_widget_set_parent(mpMenuWidget, mpFrame->getMouseEventWidget());
+ gtk_popover_set_has_arrow(GTK_POPOVER(mpMenuWidget), false);
+#endif
+ gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", mpActionGroup);
+
+ //run in a sub main loop because we need to keep vcl PopupMenu alive to use
+ //it during DispatchCommand, returning now to the outer loop causes the
+ //launching PopupMenu to be destroyed, instead run the subloop here
+ //until the gtk menu is destroyed
+ GMainLoop* pLoop = g_main_loop_new(nullptr, true);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_connect(G_OBJECT(mpMenuWidget), "closed", G_CALLBACK(MenuClosed), pLoop);
+#else
+ g_signal_connect(G_OBJECT(mpMenuWidget), "deactivate", G_CALLBACK(MenuClosed), pLoop);
+#endif
+
+
+ // tdf#120764 It isn't allowed under wayland to have two visible popups that share
+ // the same top level parent. The problem is that since gtk 3.24 tooltips are also
+ // implemented as popups, which means that we cannot show any popup if there is a
+ // visible tooltip.
+ // hide any current tooltip
+ mpFrame->HideTooltip();
+ // don't allow any more to appear until menu is dismissed
+ mpFrame->BlockTooltip();
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ AbsoluteScreenPixelRectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect);
+ aFloatRect.Move(-mpFrame->maGeometry.x(), -mpFrame->maGeometry.y());
+ GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
+ static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
+
+ gtk_popover_set_pointing_to(GTK_POPOVER(mpMenuWidget), &rect);
+
+ if (nFlags & FloatWinPopupFlags::Left)
+ gtk_popover_set_position(GTK_POPOVER(mpMenuWidget), GTK_POS_LEFT);
+ else if (nFlags & FloatWinPopupFlags::Up)
+ gtk_popover_set_position(GTK_POPOVER(mpMenuWidget), GTK_POS_TOP);
+ else if (nFlags & FloatWinPopupFlags::Right)
+ gtk_popover_set_position(GTK_POPOVER(mpMenuWidget), GTK_POS_RIGHT);
+ else
+ gtk_popover_set_position(GTK_POPOVER(mpMenuWidget), GTK_POS_BOTTOM);
+
+ gtk_popover_popup(GTK_POPOVER(mpMenuWidget));
+#else
+#if GTK_CHECK_VERSION(3,22,0)
+ if (gtk_check_version(3, 22, 0) == nullptr)
+ {
+ AbsoluteScreenPixelRectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect);
+ aFloatRect.Move(-mpFrame->maGeometry.x(), -mpFrame->maGeometry.y());
+ GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
+ static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
+
+ GdkGravity rect_anchor = GDK_GRAVITY_SOUTH_WEST, menu_anchor = GDK_GRAVITY_NORTH_WEST;
+
+ if (nFlags & FloatWinPopupFlags::Left)
+ {
+ rect_anchor = GDK_GRAVITY_NORTH_WEST;
+ menu_anchor = GDK_GRAVITY_NORTH_EAST;
+ }
+ else if (nFlags & FloatWinPopupFlags::Up)
+ {
+ rect_anchor = GDK_GRAVITY_NORTH_WEST;
+ menu_anchor = GDK_GRAVITY_SOUTH_WEST;
+ }
+ else if (nFlags & FloatWinPopupFlags::Right)
+ {
+ rect_anchor = GDK_GRAVITY_NORTH_EAST;
+ }
+
+ GdkSurface* gdkWindow = widget_get_surface(mpFrame->getMouseEventWidget());
+ gtk_menu_popup_at_rect(GTK_MENU(mpMenuWidget), gdkWindow, &rect, rect_anchor, menu_anchor, nullptr);
+ }
+ else
+#endif
+ {
+ guint nButton;
+ guint32 nTime;
+
+ //typically there is an event, and we can then distinguish if this was
+ //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
+ //doesn't)
+ GdkEvent *pEvent = gtk_get_current_event();
+ if (pEvent)
+ {
+ gdk_event_get_button(pEvent, &nButton);
+ nTime = gdk_event_get_time(pEvent);
+ }
+ else
+ {
+ nButton = 0;
+ nTime = GtkSalFrame::GetLastInputEventTime();
+ }
+
+ // Do the same strange semantics as vcl popup windows to arrive at a frame geometry
+ // in mirrored UI case; best done by actually executing the same code.
+ // (see code in FloatingWindow::StartPopupMode)
+ sal_uInt16 nArrangeIndex;
+ Point aPos = FloatingWindow::ImplCalcPos(pWin, rRect, nFlags, nArrangeIndex);
+ AbsoluteScreenPixelPoint aPosAbs = FloatingWindow::ImplConvertToAbsPos(xParent, aPos);
+
+ gtk_menu_popup(GTK_MENU(mpMenuWidget), nullptr, nullptr, MenuPositionFunc,
+ &aPosAbs, nButton, nTime);
+ }
+#endif
+
+ if (g_main_loop_is_running(pLoop))
+ main_loop_run(pLoop);
+
+ g_main_loop_unref(pLoop);
+
+ mpVCLMenu->Deactivate();
+
+ g_object_unref(mpActionGroup);
+ ClearActionGroupAndMenuModel();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(mpMenuWidget);
+#else
+ gtk_widget_unparent(mpMenuWidget);
+#endif
+ mpMenuWidget = nullptr;
+
+ gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", nullptr);
+
+ // undo tooltip blocking
+ mpFrame->UnblockTooltip();
+
+ mpFrame = nullptr;
+
+ return true;
+}
+
+/*
+ * GtkSalMenu
+ */
+
+GtkSalMenu::GtkSalMenu( bool bMenuBar ) :
+ maUpdateMenuBarIdle("Native Gtk Menu Update Idle"),
+ mbInActivateCallback( false ),
+ mbMenuBar( bMenuBar ),
+ mbNeedsUpdate( false ),
+ mbReturnFocusToDocument( false ),
+ mbAddedGrab( false ),
+ mpMenuBarContainerWidget( nullptr ),
+ mpMenuAllowShrinkWidget( nullptr ),
+ mpMenuBarWidget( nullptr ),
+ mpMenuWidget( nullptr ),
+ mpMenuBarContainerProvider( nullptr ),
+ mpMenuBarProvider( nullptr ),
+ mpCloseButton( nullptr ),
+ mpVCLMenu( nullptr ),
+ mpParentSalMenu( nullptr ),
+ mpFrame( nullptr ),
+ mpMenuModel( nullptr ),
+ mpActionGroup( nullptr )
+{
+ //typically this only gets called after the menu has been customized on the
+ //next idle slot, in the normal case of a new menubar SetFrame is called
+ //directly long before this idle would get called.
+ maUpdateMenuBarIdle.SetPriority(TaskPriority::HIGHEST);
+ maUpdateMenuBarIdle.SetInvokeHandler(LINK(this, GtkSalMenu, MenuBarHierarchyChangeHandler));
+}
+
+IMPL_LINK_NOARG(GtkSalMenu, MenuBarHierarchyChangeHandler, Timer *, void)
+{
+ SAL_WARN_IF(!mpFrame, "vcl.gtk", "MenuBar layout changed, but no frame for some reason!");
+ if (!mpFrame)
+ return;
+ SetFrame(mpFrame);
+}
+
+void GtkSalMenu::SetNeedsUpdate()
+{
+ GtkSalMenu* pMenu = this;
+ // start that the menu and its parents are in need of an update
+ // on the next activation
+ while (pMenu && !pMenu->mbNeedsUpdate)
+ {
+ pMenu->mbNeedsUpdate = true;
+ pMenu = pMenu->mpParentSalMenu;
+ }
+ // only if a menubar is directly updated do we force in a full
+ // structure update
+ if (mbMenuBar && !maUpdateMenuBarIdle.IsActive())
+ maUpdateMenuBarIdle.Start();
+}
+
+void GtkSalMenu::SetMenuModel(GMenuModel* pMenuModel)
+{
+ if (mpMenuModel)
+ g_object_unref(mpMenuModel);
+ mpMenuModel = pMenuModel;
+ if (mpMenuModel)
+ g_object_ref(mpMenuModel);
+}
+
+GtkSalMenu::~GtkSalMenu()
+{
+ SolarMutexGuard aGuard;
+
+ // tdf#140225 we expect all items to be removed by Menu::dispose
+ // before this dtor is called
+ assert(maItems.empty());
+
+ DestroyMenuBarWidget();
+
+ if (mpMenuModel)
+ g_object_unref(mpMenuModel);
+
+ if (mpFrame)
+ mpFrame->SetMenu(nullptr);
+}
+
+bool GtkSalMenu::VisibleMenuBar()
+{
+ return mbMenuBar && (bUnityMode || mpMenuBarContainerWidget);
+}
+
+void GtkSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos )
+{
+ SolarMutexGuard aGuard;
+ GtkSalMenuItem *pItem = static_cast<GtkSalMenuItem*>( pSalMenuItem );
+
+ if ( nPos == MENU_APPEND )
+ maItems.push_back( pItem );
+ else
+ maItems.insert( maItems.begin() + nPos, pItem );
+
+ pItem->mpParentMenu = this;
+
+ SetNeedsUpdate();
+}
+
+void GtkSalMenu::RemoveItem( unsigned nPos )
+{
+ SolarMutexGuard aGuard;
+
+ // tdf#140225 clear associated action when the item is removed
+ if (mpActionGroup)
+ {
+ GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP(mpActionGroup);
+ gchar* pCommand = GetCommandForItem(maItems[nPos]);
+ g_lo_action_group_remove(pActionGroup, pCommand);
+ g_free(pCommand);
+ }
+
+ maItems.erase( maItems.begin() + nPos );
+ SetNeedsUpdate();
+}
+
+void GtkSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned )
+{
+ SolarMutexGuard aGuard;
+ GtkSalMenuItem *pItem = static_cast< GtkSalMenuItem* >( pSalMenuItem );
+ GtkSalMenu *pGtkSubMenu = static_cast< GtkSalMenu* >( pSubMenu );
+
+ if ( pGtkSubMenu == nullptr )
+ return;
+
+ pGtkSubMenu->mpParentSalMenu = this;
+ pItem->mpSubMenu = pGtkSubMenu;
+
+ SetNeedsUpdate();
+}
+
+static void CloseMenuBar(GtkWidget *, gpointer pMenu)
+{
+ Application::PostUserEvent(static_cast<MenuBar*>(pMenu)->GetCloseButtonClickHdl());
+}
+
+GtkWidget* GtkSalMenu::AddButton(GtkWidget *pImage)
+{
+ GtkWidget* pButton = gtk_button_new();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_button_set_relief(GTK_BUTTON(pButton), GTK_RELIEF_NONE);
+ gtk_button_set_focus_on_click(GTK_BUTTON(pButton), false);
+#else
+ gtk_button_set_has_frame(GTK_BUTTON(pButton), false);
+ gtk_widget_set_focus_on_click(pButton, false);
+#endif
+
+ gtk_widget_set_can_focus(pButton, false);
+
+ GtkStyleContext *pButtonContext = gtk_widget_get_style_context(GTK_WIDGET(pButton));
+
+ gtk_style_context_add_class(pButtonContext, "flat");
+ gtk_style_context_add_class(pButtonContext, "small-button");
+
+ gtk_widget_show(pImage);
+
+ gtk_widget_set_valign(pButton, GTK_ALIGN_CENTER);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(pButton), pImage);
+ gtk_widget_show_all(pButton);
+#else
+ gtk_button_set_child(GTK_BUTTON(pButton), pImage);
+#endif
+ return pButton;
+}
+
+void GtkSalMenu::ShowCloseButton(bool bShow)
+{
+ assert(mbMenuBar);
+ if (!mpMenuBarContainerWidget)
+ return;
+
+ if (!bShow)
+ {
+ if (mpCloseButton)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(mpCloseButton);
+#else
+ g_clear_pointer(&mpCloseButton, gtk_widget_unparent);
+#endif
+ mpCloseButton = nullptr;
+ }
+ return;
+ }
+
+ if (mpCloseButton)
+ return;
+
+ GIcon* pIcon = g_themed_icon_new_with_default_fallbacks("window-close-symbolic");
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pImage = gtk_image_new_from_gicon(pIcon, GTK_ICON_SIZE_MENU);
+#else
+ GtkWidget* pImage = gtk_image_new_from_gicon(pIcon);
+#endif
+ g_object_unref(pIcon);
+
+ mpCloseButton = AddButton(pImage);
+
+ gtk_widget_set_margin_end(mpCloseButton, 8);
+
+ OUString sToolTip(VclResId(SV_HELPTEXT_CLOSEDOCUMENT));
+ gtk_widget_set_tooltip_text(mpCloseButton, sToolTip.toUtf8().getStr());
+
+ MenuBar *pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
+ g_signal_connect(mpCloseButton, "clicked", G_CALLBACK(CloseMenuBar), pVclMenuBar);
+
+ gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), mpCloseButton, 1, 0, 1, 1);
+}
+
+namespace
+{
+ void DestroyMemoryStream(gpointer data)
+ {
+ SvMemoryStream* pMemStm = static_cast<SvMemoryStream*>(data);
+ delete pMemStm;
+ }
+}
+
+static void MenuButtonClicked(GtkWidget* pWidget, gpointer pMenu)
+{
+ OUString aId(get_buildable_id(GTK_BUILDABLE(pWidget)));
+ static_cast<MenuBar*>(pMenu)->HandleMenuButtonEvent(aId.toUInt32());
+}
+
+bool GtkSalMenu::AddMenuBarButton(const SalMenuButtonItem& rNewItem)
+{
+ if (!mbMenuBar)
+ return false;
+
+ if (!mpMenuBarContainerWidget)
+ return false;
+
+ GtkWidget* pImage = nullptr;
+ if (!!rNewItem.maImage)
+ {
+ SvMemoryStream* pMemStm = new SvMemoryStream;
+ auto aBitmapEx = rNewItem.maImage.GetBitmapEx();
+ vcl::PngImageWriter aWriter(*pMemStm);
+ aWriter.write(aBitmapEx);
+
+ GBytes *pBytes = g_bytes_new_with_free_func(pMemStm->GetData(),
+ pMemStm->TellEnd(),
+ DestroyMemoryStream,
+ pMemStm);
+
+ GIcon *pIcon = g_bytes_icon_new(pBytes);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ pImage = gtk_image_new_from_gicon(pIcon, GTK_ICON_SIZE_MENU);
+#else
+ pImage = gtk_image_new_from_gicon(pIcon);
+#endif
+ g_object_unref(pIcon);
+ }
+
+ GtkWidget* pButton = AddButton(pImage);
+
+ maExtraButtons.emplace_back(rNewItem.mnId, pButton);
+
+ set_buildable_id(GTK_BUILDABLE(pButton), OUString::number(rNewItem.mnId));
+
+ gtk_widget_set_tooltip_text(pButton, rNewItem.maToolTipText.toUtf8().getStr());
+
+ MenuBar *pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
+ g_signal_connect(pButton, "clicked", G_CALLBACK(MenuButtonClicked), pVclMenuBar);
+
+ if (mpCloseButton)
+ {
+ gtk_grid_insert_next_to(GTK_GRID(mpMenuBarContainerWidget), mpCloseButton, GTK_POS_LEFT);
+ gtk_grid_attach_next_to(GTK_GRID(mpMenuBarContainerWidget), pButton, mpCloseButton,
+ GTK_POS_LEFT, 1, 1);
+ }
+ else
+ gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), pButton, 1, 0, 1, 1);
+
+ return true;
+}
+
+void GtkSalMenu::RemoveMenuBarButton( sal_uInt16 nId )
+{
+ const auto it = std::find_if(maExtraButtons.begin(), maExtraButtons.end(), [&nId](const auto &item) {
+ return item.first == nId; });
+ if (it == maExtraButtons.end())
+ return;
+
+ gint nAttach(0);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_child_get(GTK_CONTAINER(mpMenuBarContainerWidget), it->second, "left-attach", &nAttach, nullptr);
+ gtk_widget_destroy(it->second);
+#else
+ gtk_grid_query_child(GTK_GRID(mpMenuBarContainerWidget), it->second, &nAttach, nullptr, nullptr, nullptr);
+ g_clear_pointer(&(it->second), gtk_widget_unparent);
+#endif
+ gtk_grid_remove_column(GTK_GRID(mpMenuBarContainerWidget), nAttach);
+ maExtraButtons.erase(it);
+}
+
+tools::Rectangle GtkSalMenu::GetMenuBarButtonRectPixel(sal_uInt16 nId, SalFrame* pReferenceFrame)
+{
+ if (!pReferenceFrame)
+ return tools::Rectangle();
+
+ const auto it = std::find_if(maExtraButtons.begin(), maExtraButtons.end(), [&nId](const auto &item) {
+ return item.first == nId; });
+ if (it == maExtraButtons.end())
+ return tools::Rectangle();
+
+ GtkWidget* pButton = it->second;
+
+ GtkSalFrame* pFrame = static_cast<GtkSalFrame*>(pReferenceFrame);
+
+ gtk_coord x, y;
+ if (!gtk_widget_translate_coordinates(pButton, GTK_WIDGET(pFrame->getMouseEventWidget()), 0, 0, &x, &y))
+ return tools::Rectangle();
+
+ return tools::Rectangle(Point(x, y), Size(gtk_widget_get_allocated_width(pButton),
+ gtk_widget_get_allocated_height(pButton)));
+}
+
+//Typically when the menubar is deactivated we want the focus to return
+//to where it came from. If the menubar was activated because of F6
+//moving focus into the associated VCL menubar then on pressing ESC
+//or any other normal reason for deactivation we want focus to return
+//to the document, definitely not still stuck in the associated
+//VCL menubar. But if F6 is pressed while the menubar is activated
+//we want to pass that F6 back to the VCL menubar which will move
+//focus to the next pane by itself.
+void GtkSalMenu::ReturnFocus()
+{
+ if (mbAddedGrab)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_grab_remove(mpMenuBarWidget);
+#endif
+ mbAddedGrab = false;
+ }
+ if (!mbReturnFocusToDocument)
+ gtk_widget_grab_focus(mpFrame->getMouseEventWidget());
+ else
+ mpFrame->GetWindow()->GrabFocusToDocument();
+ mbReturnFocusToDocument = false;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalMenu::SignalKey(GdkEventKey const * pEvent)
+{
+ if (pEvent->keyval == GDK_KEY_F6)
+ {
+ mbReturnFocusToDocument = false;
+ gtk_menu_shell_cancel(GTK_MENU_SHELL(mpMenuBarWidget));
+ //because we return false here, the keypress will continue
+ //to propagate and in the case that vcl focus is in
+ //the vcl menubar then that will also process F6 and move
+ //to the next pane
+ }
+ return false;
+}
+#endif
+
+//The GtkSalMenu is owned by a Vcl Menu/MenuBar. In the menubar
+//case the vcl menubar is present and "visible", but with a 0 height
+//so it not apparent. Normally it acts as though it is not there when
+//a Native menubar is active. If we return true here, then for keyboard
+//activation and traversal with F6 through panes then the vcl menubar
+//acts as though it *is* present and we translate its take focus and F6
+//traversal key events into the gtk menubar equivalents.
+bool GtkSalMenu::CanGetFocus() const
+{
+ return mpMenuBarWidget != nullptr;
+}
+
+bool GtkSalMenu::TakeFocus()
+{
+ if (!mpMenuBarWidget)
+ return false;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ //Send a keyboard event to the gtk menubar to let it know it has been
+ //activated via the keyboard. Doesn't do anything except cause the gtk
+ //menubar "keyboard_mode" member to get set to true, so typically mnemonics
+ //are shown which will serve as indication that the menubar has focus
+ //(given that we want to show it with no menus popped down)
+ GdkEvent *event = GtkSalFrame::makeFakeKeyPress(mpMenuBarWidget);
+ gtk_widget_event(mpMenuBarWidget, event);
+ gdk_event_free(event);
+
+ //this pairing results in a menubar with keyboard focus with no menus
+ //auto-popped down
+ gtk_grab_add(mpMenuBarWidget);
+
+ mbAddedGrab = true;
+ gtk_menu_shell_select_first(GTK_MENU_SHELL(mpMenuBarWidget), false);
+ gtk_menu_shell_deselect(GTK_MENU_SHELL(mpMenuBarWidget));
+#endif
+ mbReturnFocusToDocument = true;
+ return true;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+static void MenuBarReturnFocus(GtkMenuShell*, gpointer menu)
+{
+ GtkSalFrame::UpdateLastInputEventTime(gtk_get_current_event_time());
+ GtkSalMenu* pMenu = static_cast<GtkSalMenu*>(menu);
+ pMenu->ReturnFocus();
+}
+
+static gboolean MenuBarSignalKey(GtkWidget*, GdkEventKey* pEvent, gpointer menu)
+{
+ GtkSalMenu* pMenu = static_cast<GtkSalMenu*>(menu);
+ return pMenu->SignalKey(pEvent);
+}
+#endif
+
+void GtkSalMenu::CreateMenuBarWidget()
+{
+ if (mpMenuBarContainerWidget)
+ return;
+
+ GtkGrid* pGrid = mpFrame->getTopLevelGridWidget();
+ mpMenuBarContainerWidget = gtk_grid_new();
+
+ gtk_widget_set_hexpand(GTK_WIDGET(mpMenuBarContainerWidget), true);
+ gtk_grid_insert_row(pGrid, 0);
+ gtk_grid_attach(pGrid, mpMenuBarContainerWidget, 0, 0, 1, 1);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mpMenuAllowShrinkWidget = gtk_scrolled_window_new(nullptr, nullptr);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_SHADOW_NONE);
+ // tdf#129634 don't allow this scrolled window as a candidate to tab into
+ gtk_widget_set_can_focus(GTK_WIDGET(mpMenuAllowShrinkWidget), false);
+#else
+ mpMenuAllowShrinkWidget = gtk_scrolled_window_new();
+ gtk_scrolled_window_set_has_frame(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), false);
+#endif
+ // tdf#116290 external policy on scrolledwindow will not show a scrollbar,
+ // but still allow scrolled window to not be sized to the child content.
+ // So the menubar can be shrunk past its nominal smallest width.
+ // Unlike a hack using GtkFixed/GtkLayout the correct placement of the menubar occurs under RTL
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_POLICY_EXTERNAL, GTK_POLICY_NEVER);
+ gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), mpMenuAllowShrinkWidget, 0, 0, 1, 1);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mpMenuBarWidget = gtk_menu_bar_new_from_model(mpMenuModel);
+#else
+ mpMenuBarWidget = gtk_popover_menu_bar_new_from_model(mpMenuModel);
+#endif
+
+ gtk_widget_insert_action_group(mpMenuBarWidget, "win", mpActionGroup);
+ gtk_widget_set_hexpand(GTK_WIDGET(mpMenuBarWidget), true);
+ gtk_widget_set_hexpand(mpMenuAllowShrinkWidget, true);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(mpMenuAllowShrinkWidget), mpMenuBarWidget);
+#else
+ gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), mpMenuBarWidget);
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_connect(G_OBJECT(mpMenuBarWidget), "deactivate", G_CALLBACK(MenuBarReturnFocus), this);
+ g_signal_connect(G_OBJECT(mpMenuBarWidget), "key-press-event", G_CALLBACK(MenuBarSignalKey), this);
+#endif
+
+ gtk_widget_show(mpMenuBarWidget);
+ gtk_widget_show(mpMenuAllowShrinkWidget);
+ gtk_widget_show(mpMenuBarContainerWidget);
+
+ ShowCloseButton( static_cast<MenuBar*>(mpVCLMenu.get())->HasCloseButton() );
+
+ ApplyPersona();
+}
+
+void GtkSalMenu::ApplyPersona()
+{
+ if (!mpMenuBarContainerWidget)
+ return;
+ assert(mbMenuBar);
+ // I'm dubious about the persona theming feature, but as it exists, lets try and support
+ // it, apply the image to the mpMenuBarContainerWidget
+ const BitmapEx& rPersonaBitmap = Application::GetSettings().GetStyleSettings().GetPersonaHeader();
+
+ GtkStyleContext *pMenuBarContainerContext = gtk_widget_get_style_context(GTK_WIDGET(mpMenuBarContainerWidget));
+ if (mpMenuBarContainerProvider)
+ {
+ gtk_style_context_remove_provider(pMenuBarContainerContext, GTK_STYLE_PROVIDER(mpMenuBarContainerProvider));
+ mpMenuBarContainerProvider = nullptr;
+ }
+ GtkStyleContext *pMenuBarContext = gtk_widget_get_style_context(GTK_WIDGET(mpMenuBarWidget));
+ if (mpMenuBarProvider)
+ {
+ gtk_style_context_remove_provider(pMenuBarContext, GTK_STYLE_PROVIDER(mpMenuBarProvider));
+ mpMenuBarProvider = nullptr;
+ }
+
+ if (!rPersonaBitmap.IsEmpty())
+ {
+ if (maPersonaBitmap != rPersonaBitmap)
+ {
+ mxPersonaImage.reset(new utl::TempFileNamed);
+ mxPersonaImage->EnableKillingFile(true);
+ SvStream* pStream = mxPersonaImage->GetStream(StreamMode::WRITE);
+ vcl::PngImageWriter aPNGWriter(*pStream);
+ aPNGWriter.write(rPersonaBitmap);
+ mxPersonaImage->CloseStream();
+ }
+
+ mpMenuBarContainerProvider = gtk_css_provider_new();
+ OUString aBuffer = "* { background-image: url(\"" + mxPersonaImage->GetURL() + "\"); background-position: top right; }";
+ OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
+ css_provider_load_from_data(mpMenuBarContainerProvider, aResult.getStr(), aResult.getLength());
+ gtk_style_context_add_provider(pMenuBarContainerContext, GTK_STYLE_PROVIDER(mpMenuBarContainerProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+
+ // force the menubar to be transparent when persona is active otherwise for
+ // me the menubar becomes gray when its in the backdrop
+ mpMenuBarProvider = gtk_css_provider_new();
+ static const gchar data[] = "* { "
+ "background-image: none;"
+ "background-color: transparent;"
+ "}";
+ css_provider_load_from_data(mpMenuBarProvider, data, -1);
+ gtk_style_context_add_provider(pMenuBarContext,
+ GTK_STYLE_PROVIDER(mpMenuBarProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ maPersonaBitmap = rPersonaBitmap;
+}
+
+void GtkSalMenu::DestroyMenuBarWidget()
+{
+ if (!mpMenuBarContainerWidget)
+ return;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // tdf#140225 call cancel before destroying it in case there are some
+ // active menus popped open
+ gtk_menu_shell_cancel(GTK_MENU_SHELL(mpMenuBarWidget));
+
+ gtk_widget_destroy(mpMenuBarContainerWidget);
+#else
+ g_clear_pointer(&mpMenuBarContainerWidget, gtk_widget_unparent);
+#endif
+ mpMenuBarContainerWidget = nullptr;
+ mpMenuBarWidget = nullptr;
+ mpCloseButton = nullptr;
+}
+
+void GtkSalMenu::SetFrame(const SalFrame* pFrame)
+{
+ SolarMutexGuard aGuard;
+ assert(mbMenuBar);
+ SAL_INFO("vcl.unity", "GtkSalMenu set to frame");
+ mpFrame = const_cast<GtkSalFrame*>(static_cast<const GtkSalFrame*>(pFrame));
+
+ // if we had a menu on the GtkSalMenu we have to free it as we generate a
+ // full menu anyway and we might need to reuse an existing model and
+ // actiongroup
+ mpFrame->SetMenu( this );
+ mpFrame->EnsureAppMenuWatch();
+
+ // Clean menu model and action group if needed.
+ GtkWidget* pWidget = mpFrame->getWindow();
+ GdkSurface* gdkWindow = widget_get_surface(pWidget);
+
+ GLOMenu* pMenuModel = G_LO_MENU( g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) );
+ GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-action-group" ) );
+ SAL_INFO("vcl.unity", "Found menu model: " << pMenuModel << " and action group: " << pActionGroup);
+
+ if ( pMenuModel )
+ {
+ if ( g_menu_model_get_n_items( G_MENU_MODEL( pMenuModel ) ) > 0 )
+ g_lo_menu_remove( pMenuModel, 0 );
+
+ mpMenuModel = G_MENU_MODEL( g_lo_menu_new() );
+ }
+
+ if ( pActionGroup )
+ {
+ g_lo_action_group_clear( pActionGroup );
+ mpActionGroup = G_ACTION_GROUP( pActionGroup );
+ }
+
+ // Generate the main menu structure.
+ if ( PrepUpdate() )
+ UpdateFull();
+
+ g_lo_menu_insert_section( pMenuModel, 0, nullptr, mpMenuModel );
+
+ if (!bUnityMode && static_cast<MenuBar*>(mpVCLMenu.get())->IsDisplayable())
+ {
+ DestroyMenuBarWidget();
+ CreateMenuBarWidget();
+ }
+}
+
+const GtkSalFrame* GtkSalMenu::GetFrame() const
+{
+ SolarMutexGuard aGuard;
+ const GtkSalMenu* pMenu = this;
+ while( pMenu && ! pMenu->mpFrame )
+ pMenu = pMenu->mpParentSalMenu;
+ return pMenu ? pMenu->mpFrame : nullptr;
+}
+
+void GtkSalMenu::NativeCheckItem( unsigned nSection, unsigned nItemPos, MenuItemBits bits, gboolean bCheck )
+{
+ SolarMutexGuard aGuard;
+
+ if ( mpActionGroup == nullptr )
+ return;
+
+ gchar* aCommand = g_lo_menu_get_command_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );
+
+ if ( aCommand != nullptr || g_strcmp0( aCommand, "" ) != 0 )
+ {
+ GVariant *pCheckValue = nullptr;
+ GVariant *pCurrentState = g_action_group_get_action_state( mpActionGroup, aCommand );
+
+ if ( bits & MenuItemBits::RADIOCHECK )
+ pCheckValue = bCheck ? g_variant_new_string( aCommand ) : g_variant_new_string( "" );
+ else
+ {
+ // By default, all checked items are checkmark buttons.
+ if (bCheck || pCurrentState != nullptr)
+ pCheckValue = g_variant_new_boolean( bCheck );
+ }
+
+ if ( pCheckValue != nullptr )
+ {
+ if ( pCurrentState == nullptr || g_variant_equal( pCurrentState, pCheckValue ) == FALSE )
+ {
+ g_action_group_change_action_state( mpActionGroup, aCommand, pCheckValue );
+ }
+ else
+ {
+ g_variant_unref (pCheckValue);
+ }
+ }
+
+ if ( pCurrentState != nullptr )
+ g_variant_unref( pCurrentState );
+ }
+
+ if ( aCommand )
+ g_free( aCommand );
+}
+
+void GtkSalMenu::NativeSetEnableItem( gchar const * aCommand, gboolean bEnable )
+{
+ SolarMutexGuard aGuard;
+ GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
+
+ if ( g_action_group_get_action_enabled( G_ACTION_GROUP( pActionGroup ), aCommand ) != bEnable )
+ g_lo_action_group_set_action_enabled( pActionGroup, aCommand, bEnable );
+}
+
+void GtkSalMenu::NativeSetItemText( unsigned nSection, unsigned nItemPos, const OUString& rText )
+{
+ SolarMutexGuard aGuard;
+ // Escape all underscores so that they don't get interpreted as hotkeys
+ OUString aText = rText.replaceAll( "_", "__" );
+ // Replace the LibreOffice hotkey identifier with an underscore
+ aText = aText.replace( '~', '_' );
+ OString aConvertedText = OUStringToOString( aText, RTL_TEXTENCODING_UTF8 );
+
+ // Update item text only when necessary.
+ gchar* aLabel = g_lo_menu_get_label_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );
+
+ if ( aLabel == nullptr || g_strcmp0( aLabel, aConvertedText.getStr() ) != 0 )
+ g_lo_menu_set_label_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, aConvertedText.getStr() );
+
+ if ( aLabel )
+ g_free( aLabel );
+}
+
+void GtkSalMenu::NativeSetItemIcon( unsigned nSection, unsigned nItemPos, const Image& rImage )
+{
+#if GLIB_CHECK_VERSION(2,38,0)
+ if (!rImage && mbHasNullItemIcon)
+ return;
+
+ SolarMutexGuard aGuard;
+
+ if (!!rImage)
+ {
+ SvMemoryStream* pMemStm = new SvMemoryStream;
+ auto aBitmapEx = rImage.GetBitmapEx();
+ vcl::PngImageWriter aWriter(*pMemStm);
+ aWriter.write(aBitmapEx);
+
+ GBytes *pBytes = g_bytes_new_with_free_func(pMemStm->GetData(),
+ pMemStm->TellEnd(),
+ DestroyMemoryStream,
+ pMemStm);
+
+ GIcon *pIcon = g_bytes_icon_new(pBytes);
+
+ g_lo_menu_set_icon_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, pIcon );
+ g_object_unref(pIcon);
+ g_bytes_unref(pBytes);
+ mbHasNullItemIcon = false;
+ }
+ else
+ {
+ g_lo_menu_set_icon_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, nullptr );
+ mbHasNullItemIcon = true;
+ }
+#else
+ (void)nSection;
+ (void)nItemPos;
+ (void)rImage;
+#endif
+}
+
+void GtkSalMenu::NativeSetAccelerator( unsigned nSection, unsigned nItemPos, const vcl::KeyCode& rKeyCode, std::u16string_view rKeyName )
+{
+ SolarMutexGuard aGuard;
+
+ if ( rKeyName.empty() )
+ return;
+
+ guint nKeyCode;
+ GdkModifierType nModifiers;
+ GtkSalFrame::KeyCodeToGdkKey(rKeyCode, &nKeyCode, &nModifiers);
+
+ gchar* aAccelerator = gtk_accelerator_name( nKeyCode, nModifiers );
+
+ gchar* aCurrentAccel = g_lo_menu_get_accelerator_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );
+
+ if ( aCurrentAccel == nullptr && g_strcmp0( aCurrentAccel, aAccelerator ) != 0 )
+ g_lo_menu_set_accelerator_to_item_in_section ( G_LO_MENU( mpMenuModel ), nSection, nItemPos, aAccelerator );
+
+ g_free( aAccelerator );
+ g_free( aCurrentAccel );
+}
+
+bool GtkSalMenu::NativeSetItemCommand( unsigned nSection,
+ unsigned nItemPos,
+ sal_uInt16 nId,
+ const gchar* aCommand,
+ MenuItemBits nBits,
+ bool bChecked,
+ bool bIsSubmenu )
+{
+ bool bSubMenuAddedOrRemoved = false;
+
+ SolarMutexGuard aGuard;
+ GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
+
+ GVariant *pTarget = nullptr;
+
+ if (g_action_group_has_action(mpActionGroup, aCommand))
+ g_lo_action_group_remove(pActionGroup, aCommand);
+
+ if ( ( nBits & MenuItemBits::CHECKABLE ) || bIsSubmenu )
+ {
+ // Item is a checkmark button.
+ GVariantType* pStateType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_BOOLEAN) );
+ GVariant* pState = g_variant_new_boolean( bChecked );
+
+ g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, bIsSubmenu, nullptr, pStateType, nullptr, pState );
+ }
+ else if ( nBits & MenuItemBits::RADIOCHECK )
+ {
+ // Item is a radio button.
+ GVariantType* pParameterType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_STRING) );
+ GVariantType* pStateType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_STRING) );
+ GVariant* pState = g_variant_new_string( "" );
+ pTarget = g_variant_new_string( aCommand );
+
+ g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, FALSE, pParameterType, pStateType, nullptr, pState );
+ }
+ else
+ {
+ // Item is not special, so insert a stateless action.
+ g_lo_action_group_insert( pActionGroup, aCommand, nId, FALSE );
+ }
+
+ GLOMenu* pMenu = G_LO_MENU( mpMenuModel );
+
+ // Menu item is not updated unless it's necessary.
+ gchar* aCurrentCommand = g_lo_menu_get_command_from_item_in_section( pMenu, nSection, nItemPos );
+
+ if ( aCurrentCommand == nullptr || g_strcmp0( aCurrentCommand, aCommand ) != 0 )
+ {
+ bool bOldHasSubmenu = g_lo_menu_get_submenu_from_item_in_section(pMenu, nSection, nItemPos) != nullptr;
+ bSubMenuAddedOrRemoved = bOldHasSubmenu != bIsSubmenu;
+ if (bSubMenuAddedOrRemoved)
+ {
+ //tdf#98636 it's not good enough to unset the "submenu-action" attribute to change something
+ //from a submenu to a non-submenu item, so remove the old one entirely and re-add it to
+ //support achieving that
+ gchar* pLabel = g_lo_menu_get_label_from_item_in_section(pMenu, nSection, nItemPos);
+ g_lo_menu_remove_from_section(pMenu, nSection, nItemPos);
+ g_lo_menu_insert_in_section(pMenu, nSection, nItemPos, pLabel);
+ g_free(pLabel);
+ }
+
+ g_lo_menu_set_command_to_item_in_section( pMenu, nSection, nItemPos, aCommand );
+
+ gchar* aItemCommand = g_strconcat("win.", aCommand, nullptr );
+
+ if ( bIsSubmenu )
+ g_lo_menu_set_submenu_action_to_item_in_section( pMenu, nSection, nItemPos, aItemCommand );
+ else
+ {
+ g_lo_menu_set_action_and_target_value_to_item_in_section( pMenu, nSection, nItemPos, aItemCommand, pTarget );
+ pTarget = nullptr;
+ }
+
+ g_free( aItemCommand );
+ }
+
+ if ( aCurrentCommand )
+ g_free( aCurrentCommand );
+
+ if (pTarget)
+ g_variant_unref(pTarget);
+
+ return bSubMenuAddedOrRemoved;
+}
+
+GtkSalMenu* GtkSalMenu::GetTopLevel()
+{
+ GtkSalMenu *pMenu = this;
+ while (pMenu->mpParentSalMenu)
+ pMenu = pMenu->mpParentSalMenu;
+ return pMenu;
+}
+
+void GtkSalMenu::DispatchCommand(const gchar *pCommand)
+{
+ SolarMutexGuard aGuard;
+ MenuAndId aMenuAndId = decode_command(pCommand);
+ GtkSalMenu* pSalSubMenu = aMenuAndId.first;
+ GtkSalMenu* pTopLevel = pSalSubMenu->GetTopLevel();
+
+ // tdf#125803 spacebar will toggle radios and checkbuttons without automatically
+ // closing the menu. To handle this properly I imagine we need to set groups for the
+ // radiobuttons so the others visually untoggle when the active one is toggled and
+ // we would further need to teach vcl that the state can change more than once.
+ //
+ // or we could unconditionally deactivate the menus if regardless of what particular
+ // type of menu item got activated
+ if (pTopLevel->mpMenuBarWidget)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_menu_shell_deactivate(GTK_MENU_SHELL(pTopLevel->mpMenuBarWidget));
+#endif
+ }
+ if (pTopLevel->mpMenuWidget)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_popover_popdown(GTK_POPOVER(pTopLevel->mpMenuWidget));
+#else
+ gtk_menu_shell_deactivate(GTK_MENU_SHELL(pTopLevel->mpMenuWidget));
+#endif
+ }
+
+ pTopLevel->GetMenu()->HandleMenuCommandEvent(pSalSubMenu->GetMenu(), aMenuAndId.second);
+}
+
+void GtkSalMenu::ActivateAllSubmenus(Menu* pMenuBar)
+{
+ // We can re-enter this method via the new event loop that gets created
+ // in GtkClipboardTransferable::getTransferDataFlavorsAsVector, so use the InActivateCallback
+ // flag to detect that and skip some startup work.
+ if (mbInActivateCallback)
+ return;
+
+ mbInActivateCallback = true;
+ pMenuBar->HandleMenuActivateEvent(GetMenu());
+ mbInActivateCallback = false;
+ for (GtkSalMenuItem* pSalItem : maItems)
+ {
+ if ( pSalItem->mpSubMenu != nullptr )
+ {
+ pSalItem->mpSubMenu->ActivateAllSubmenus(pMenuBar);
+ }
+ }
+ Update();
+ pMenuBar->HandleMenuDeActivateEvent(GetMenu());
+}
+
+void GtkSalMenu::ClearActionGroupAndMenuModel()
+{
+ SetMenuModel(nullptr);
+ mpActionGroup = nullptr;
+ for (GtkSalMenuItem* pSalItem : maItems)
+ {
+ if ( pSalItem->mpSubMenu != nullptr )
+ {
+ pSalItem->mpSubMenu->ClearActionGroupAndMenuModel();
+ }
+ }
+}
+
+void GtkSalMenu::Activate(const gchar* pCommand)
+{
+ MenuAndId aMenuAndId = decode_command(pCommand);
+ GtkSalMenu* pSalMenu = aMenuAndId.first;
+ Menu* pVclMenu = pSalMenu->GetMenu();
+ if (pVclMenu->isDisposed())
+ return;
+ GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
+ Menu* pVclSubMenu = pVclMenu->GetPopupMenu(aMenuAndId.second);
+ GtkSalMenu* pSubMenu = pSalMenu->GetItemAtPos(pVclMenu->GetItemPos(aMenuAndId.second))->mpSubMenu;
+
+ pSubMenu->mbInActivateCallback = true;
+ pTopLevel->GetMenu()->HandleMenuActivateEvent(pVclSubMenu);
+ pSubMenu->mbInActivateCallback = false;
+ pVclSubMenu->UpdateNativeMenu();
+}
+
+void GtkSalMenu::Deactivate(const gchar* pCommand)
+{
+ MenuAndId aMenuAndId = decode_command(pCommand);
+ GtkSalMenu* pSalMenu = aMenuAndId.first;
+ Menu* pVclMenu = pSalMenu->GetMenu();
+ if (pVclMenu->isDisposed())
+ return;
+ GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
+ Menu* pVclSubMenu = pVclMenu->GetPopupMenu(aMenuAndId.second);
+ pTopLevel->GetMenu()->HandleMenuDeActivateEvent(pVclSubMenu);
+}
+
+void GtkSalMenu::EnableUnity(bool bEnable)
+{
+ bUnityMode = bEnable;
+
+ MenuBar* pMenuBar(static_cast<MenuBar*>(mpVCLMenu.get()));
+ bool bDisplayable(pMenuBar->IsDisplayable());
+
+ if (bEnable)
+ {
+ DestroyMenuBarWidget();
+ UpdateFull();
+ if (!bDisplayable)
+ ShowMenuBar(false);
+ }
+ else
+ {
+ Update();
+ ShowMenuBar(bDisplayable);
+ }
+
+ pMenuBar->LayoutChanged();
+}
+
+void GtkSalMenu::ShowMenuBar( bool bVisible )
+{
+ // Unity tdf#106271: Can't hide global menu, so empty it instead when user wants to hide menubar,
+ if (bUnityMode)
+ {
+ if (bVisible)
+ Update();
+ else if (mpMenuModel && g_menu_model_get_n_items(G_MENU_MODEL(mpMenuModel)) > 0)
+ g_lo_menu_remove(G_LO_MENU(mpMenuModel), 0);
+ }
+ else if (bVisible)
+ CreateMenuBarWidget();
+ else
+ DestroyMenuBarWidget();
+}
+
+bool GtkSalMenu::IsItemVisible( unsigned nPos )
+{
+ SolarMutexGuard aGuard;
+ bool bVisible = false;
+
+ if ( nPos < maItems.size() )
+ bVisible = maItems[ nPos ]->mbVisible;
+
+ return bVisible;
+}
+
+void GtkSalMenu::CheckItem( unsigned, bool )
+{
+}
+
+void GtkSalMenu::EnableItem( unsigned nPos, bool bEnable )
+{
+ SolarMutexGuard aGuard;
+ if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar && ( nPos < maItems.size() ) )
+ {
+ gchar* pCommand = GetCommandForItem( GetItemAtPos( nPos ) );
+ NativeSetEnableItem( pCommand, bEnable );
+ g_free( pCommand );
+ }
+}
+
+void GtkSalMenu::ShowItem( unsigned nPos, bool bShow )
+{
+ SolarMutexGuard aGuard;
+ if ( nPos < maItems.size() )
+ {
+ maItems[ nPos ]->mbVisible = bShow;
+ if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar )
+ Update();
+ }
+}
+
+void GtkSalMenu::SetItemText( unsigned nPos, SalMenuItem* pSalMenuItem, const OUString& rText )
+{
+ SolarMutexGuard aGuard;
+ if ( !bUnityMode || mbInActivateCallback || mbNeedsUpdate || !GetTopLevel()->mbMenuBar || ( nPos >= maItems.size() ) )
+ return;
+
+ gchar* pCommand = GetCommandForItem( static_cast< GtkSalMenuItem* >( pSalMenuItem ) );
+
+ gint nSectionsCount = g_menu_model_get_n_items( mpMenuModel );
+ for ( gint nSection = 0; nSection < nSectionsCount; ++nSection )
+ {
+ gint nItemsCount = g_lo_menu_get_n_items_from_section( G_LO_MENU( mpMenuModel ), nSection );
+ for ( gint nItem = 0; nItem < nItemsCount; ++nItem )
+ {
+ gchar* pCommandFromModel = g_lo_menu_get_command_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItem );
+
+ if ( !g_strcmp0( pCommandFromModel, pCommand ) )
+ {
+ NativeSetItemText( nSection, nItem, rText );
+ g_free( pCommandFromModel );
+ g_free( pCommand );
+ return;
+ }
+
+ g_free( pCommandFromModel );
+ }
+ }
+
+ g_free( pCommand );
+}
+
+void GtkSalMenu::SetItemImage( unsigned, SalMenuItem*, const Image& )
+{
+}
+
+void GtkSalMenu::SetAccelerator( unsigned, SalMenuItem*, const vcl::KeyCode&, const OUString& )
+{
+}
+
+void GtkSalMenu::GetSystemMenuData( SystemMenuData* )
+{
+}
+
+int GtkSalMenu::GetMenuBarHeight() const
+{
+ return mpMenuBarWidget ? gtk_widget_get_allocated_height(mpMenuBarWidget) : 0;
+}
+
+/*
+ * GtkSalMenuItem
+ */
+
+GtkSalMenuItem::GtkSalMenuItem( const SalItemParams* pItemData ) :
+ mpParentMenu( nullptr ),
+ mpSubMenu( nullptr ),
+ mnType( pItemData->eType ),
+ mnId( pItemData->nId ),
+ mbVisible( true )
+{
+}
+
+GtkSalMenuItem::~GtkSalMenuItem()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtksys.cxx b/vcl/unx/gtk3/gtksys.cxx
new file mode 100644
index 0000000000..4dc191bd67
--- /dev/null
+++ b/vcl/unx/gtk3/gtksys.cxx
@@ -0,0 +1,330 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <gtk/gtk.h>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include <unx/gtk/gtksys.hxx>
+#include <unx/gtk/gtkbackend.hxx>
+#include <osl/module.h>
+
+GtkSalSystem *GtkSalSystem::GetSingleton()
+{
+ static GtkSalSystem *pSingleton = new GtkSalSystem();
+ return pSingleton;
+}
+
+SalSystem *GtkInstance::CreateSalSystem()
+{
+ return GtkSalSystem::GetSingleton();
+}
+
+GtkSalSystem::GtkSalSystem()
+{
+ mpDisplay = gdk_display_get_default();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ countScreenMonitors();
+#endif
+ // rhbz#1285356, native look will be gtk2, which crashes
+ // when gtk3 is already loaded. Until there is a solution
+ // java-side force look and feel to something that doesn't
+ // crash when we are using gtk3
+ setenv("STOC_FORCE_SYSTEM_LAF", "true", 1);
+}
+
+GtkSalSystem::~GtkSalSystem()
+{
+}
+
+namespace
+{
+
+struct GdkRectangleCoincidentLess
+{
+ // fdo#78799 - detect and elide overlaying monitors of different sizes
+ bool operator()(GdkRectangle const& rLeft, GdkRectangle const& rRight)
+ {
+ return
+ rLeft.x < rRight.x
+ || rLeft.y < rRight.y
+ ;
+ }
+};
+struct GdkRectangleCoincident
+{
+ // fdo#78799 - detect and elide overlaying monitors of different sizes
+ bool operator()(GdkRectangle const& rLeft, GdkRectangle const& rRight)
+ {
+ return
+ rLeft.x == rRight.x
+ && rLeft.y == rRight.y
+ ;
+ }
+};
+
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+/**
+ * GtkSalSystem::countScreenMonitors()
+ *
+ * This method builds the vector which allows us to map from VCL's
+ * idea of linear integer ScreenNumber to gtk+'s rather more
+ * complicated screen + monitor concept.
+ */
+void
+GtkSalSystem::countScreenMonitors()
+{
+ maScreenMonitors.clear();
+ for (gint i = 0; i < gdk_display_get_n_screens(mpDisplay); i++)
+ {
+ GdkScreen* const pScreen(gdk_display_get_screen(mpDisplay, i));
+ gint nMonitors(pScreen ? gdk_screen_get_n_monitors(pScreen) : 0);
+ if (nMonitors > 1)
+ {
+ std::vector<GdkRectangle> aGeometries;
+ aGeometries.reserve(nMonitors);
+ for (gint j(0); j != nMonitors; ++j)
+ {
+ GdkRectangle aGeometry;
+ gdk_screen_get_monitor_geometry(pScreen, j, &aGeometry);
+ aGeometries.push_back(aGeometry);
+ }
+ std::sort(aGeometries.begin(), aGeometries.end(),
+ GdkRectangleCoincidentLess());
+ const std::vector<GdkRectangle>::iterator aUniqueEnd(
+ std::unique(aGeometries.begin(), aGeometries.end(),
+ GdkRectangleCoincident()));
+ nMonitors = std::distance(aGeometries.begin(), aUniqueEnd);
+ }
+ maScreenMonitors.emplace_back(pScreen, nMonitors);
+ }
+}
+#endif
+
+SalX11Screen
+GtkSalSystem::getXScreenFromDisplayScreen(unsigned int nScreen)
+{
+ if (!DLSYM_GDK_IS_X11_DISPLAY(mpDisplay))
+ return SalX11Screen (0);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GdkX11Screen *pScreen = gdk_x11_display_get_screen(mpDisplay);
+ (void)nScreen;
+#else
+ gint nMonitor;
+ GdkScreen *pScreen = getScreenMonitorFromIdx (nScreen, nMonitor);
+ if (!pScreen)
+ return SalX11Screen (0);
+#endif
+ return SalX11Screen(gdk_x11_screen_get_screen_number(pScreen));
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+GdkScreen *
+GtkSalSystem::getScreenMonitorFromIdx (int nIdx, gint &nMonitor)
+{
+ GdkScreen *pScreen = nullptr;
+ for (auto const& screenMonitor : maScreenMonitors)
+ {
+ pScreen = screenMonitor.first;
+ if (!pScreen)
+ break;
+ if (nIdx >= screenMonitor.second)
+ nIdx -= screenMonitor.second;
+ else
+ break;
+ }
+ nMonitor = nIdx;
+
+ // handle invalid monitor indexes as non-existent screens
+ if (nMonitor < 0 || (pScreen && nMonitor >= gdk_screen_get_n_monitors (pScreen)))
+ pScreen = nullptr;
+
+ return pScreen;
+}
+
+int
+GtkSalSystem::getScreenIdxFromPtr (GdkScreen *pScreen)
+{
+ int nIdx = 0;
+ for (auto const& screenMonitor : maScreenMonitors)
+ {
+ if (screenMonitor.first == pScreen)
+ return nIdx;
+ nIdx += screenMonitor.second;
+ }
+ g_warning ("failed to find screen %p", pScreen);
+ return 0;
+}
+
+int GtkSalSystem::getScreenMonitorIdx (GdkScreen *pScreen,
+ int nX, int nY)
+{
+ // TODO: this will fail horribly for exotic combinations like two
+ // monitors in mirror mode and one extra. Hopefully such
+ // abominations are not used (or, even better, not possible) in
+ // practice .-)
+ return getScreenIdxFromPtr (pScreen) +
+ gdk_screen_get_monitor_at_point (pScreen, nX, nY);
+}
+#endif
+
+unsigned int GtkSalSystem::GetDisplayScreenCount()
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return g_list_model_get_n_items(gdk_display_get_monitors(mpDisplay));
+#else
+ gint nMonitor;
+ (void)getScreenMonitorFromIdx (G_MAXINT, nMonitor);
+ return G_MAXINT - nMonitor;
+#endif
+}
+
+unsigned int GtkSalSystem::GetDisplayBuiltInScreen()
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+#if defined(GDK_WINDOWING_X11)
+ if (DLSYM_GDK_IS_X11_DISPLAY(mpDisplay))
+ {
+ GdkMonitor* pPrimary = gdk_x11_display_get_primary_monitor(mpDisplay);
+ GListModel* pList = gdk_display_get_monitors(mpDisplay);
+ int nIndex = 0;
+ while (gpointer pElem = g_list_model_get_item(pList, nIndex))
+ {
+ if (pElem == pPrimary)
+ return nIndex;
+ ++nIndex;
+ }
+ }
+#endif
+ // nothing for wayland ?, hope for the best that its at index 0
+ return 0;
+#else // !GTK_CHECK_VERSION(4, 0, 0)
+ GdkScreen *pDefault = gdk_display_get_default_screen (mpDisplay);
+ int idx = getScreenIdxFromPtr (pDefault);
+ return idx + gdk_screen_get_primary_monitor(pDefault);
+#endif
+}
+
+AbsoluteScreenPixelRectangle GtkSalSystem::GetDisplayScreenPosSizePixel(unsigned int nScreen)
+{
+ GdkRectangle aRect;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GListModel* pList = gdk_display_get_monitors(mpDisplay);
+ GdkMonitor* pMonitor = static_cast<GdkMonitor*>(g_list_model_get_item(pList, nScreen));
+ if (!pMonitor)
+ return AbsoluteScreenPixelRectangle();
+ gdk_monitor_get_geometry(pMonitor, &aRect);
+#else
+ gint nMonitor;
+ GdkScreen *pScreen;
+ pScreen = getScreenMonitorFromIdx (nScreen, nMonitor);
+ if (!pScreen)
+ return AbsoluteScreenPixelRectangle();
+ gdk_screen_get_monitor_geometry (pScreen, nMonitor, &aRect);
+#endif
+ return AbsoluteScreenPixelRectangle(AbsoluteScreenPixelPoint(aRect.x, aRect.y), AbsoluteScreenPixelSize(aRect.width, aRect.height));
+}
+
+// convert ~ to indicate mnemonic to '_'
+static OString MapToGtkAccelerator(const OUString &rStr)
+{
+ return OUStringToOString(rStr.replaceFirst("~", "_"), RTL_TEXTENCODING_UTF8);
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+
+namespace
+{
+ struct DialogLoop
+ {
+ GMainLoop* pLoop = nullptr;
+ gint nResponseId = GTK_RESPONSE_NONE;
+ gulong nSignalResponseId = 0;
+ gulong nSignalCloseRequestId= 0;
+
+ static gboolean DialogClose(GtkWindow* pDialog, gpointer /*data*/)
+ {
+ gtk_dialog_response(GTK_DIALOG(pDialog), GTK_RESPONSE_CANCEL);
+ return true;
+ }
+
+ static void DialogResponse(GtkDialog* pDialog, gint nResponseId, gpointer data)
+ {
+ DialogLoop* pDialogLoop = static_cast<DialogLoop*>(data);
+ g_signal_handler_disconnect(pDialog, pDialogLoop->nSignalResponseId);
+ g_signal_handler_disconnect(pDialog, pDialogLoop->nSignalCloseRequestId);
+ pDialogLoop->nResponseId = nResponseId;
+ g_main_loop_quit(pDialogLoop->pLoop);
+ }
+
+ int run(GtkDialog *pDialog)
+ {
+ nSignalResponseId = g_signal_connect(pDialog, "response", G_CALLBACK(DialogResponse), this);
+ nSignalCloseRequestId = g_signal_connect(pDialog, "close-request", G_CALLBACK(DialogClose), this);
+ gtk_window_present(GTK_WINDOW(pDialog));
+ pLoop = g_main_loop_new(nullptr, false);
+ main_loop_run(pLoop);
+ g_main_loop_unref(pLoop);
+ return nResponseId;
+ }
+
+ };
+}
+
+gint gtk_dialog_run(GtkDialog* pDialog)
+{
+ DialogLoop aDialogLoop;
+ return aDialogLoop.run(pDialog);
+}
+
+#endif
+
+int GtkSalSystem::ShowNativeDialog (const OUString& rTitle, const OUString& rMessage,
+ const std::vector< OUString >& rButtonNames)
+{
+ OString aTitle (OUStringToOString (rTitle, RTL_TEXTENCODING_UTF8));
+ OString aMessage (OUStringToOString (rMessage, RTL_TEXTENCODING_UTF8));
+
+ GtkDialog *pDialog = GTK_DIALOG (
+ g_object_new (GTK_TYPE_MESSAGE_DIALOG,
+ "title", aTitle.getStr(),
+ "message-type", int(GTK_MESSAGE_WARNING),
+ "text", aMessage.getStr(),
+ nullptr));
+ int nButton = 0;
+ for (auto const& buttonName : rButtonNames)
+ gtk_dialog_add_button (pDialog, MapToGtkAccelerator(buttonName).getStr(), nButton++);
+ gtk_dialog_set_default_response (pDialog, 0/*nDefaultButton*/);
+
+ nButton = gtk_dialog_run (pDialog);
+ if (nButton < 0)
+ nButton = -1;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(GTK_WIDGET(pDialog));
+#else
+ gtk_window_destroy(GTK_WINDOW(pDialog));
+#endif
+
+ return nButton;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/hudawareness.cxx b/vcl/unx/gtk3/hudawareness.cxx
new file mode 100644
index 0000000000..ebcbaf747f
--- /dev/null
+++ b/vcl/unx/gtk3/hudawareness.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 <string.h>
+
+#include <unx/gtk/hudawareness.h>
+
+namespace {
+
+struct HudAwarenessHandle
+{
+ gpointer connection;
+ HudAwarenessCallback callback;
+ gpointer user_data;
+ GDestroyNotify notify;
+};
+
+}
+
+static void
+hud_awareness_method_call (GDBusConnection * /* connection */,
+ const gchar * /* sender */,
+ const gchar * /* object_path */,
+ const gchar * /* interface_name */,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ HudAwarenessHandle *handle = static_cast<HudAwarenessHandle*>(user_data);
+
+ if (g_str_equal (method_name, "HudActiveChanged"))
+ {
+ gboolean active;
+
+ g_variant_get (parameters, "(b)", &active);
+
+ (* handle->callback) (active, handle->user_data);
+ }
+
+ g_dbus_method_invocation_return_value (invocation, nullptr);
+}
+
+guint
+hud_awareness_register (GDBusConnection *connection,
+ const gchar *object_path,
+ HudAwarenessCallback callback,
+ gpointer user_data,
+ GDestroyNotify notify,
+ GError **error)
+{
+ static GDBusInterfaceInfo *iface;
+ static GDBusNodeInfo *info;
+ GDBusInterfaceVTable vtable;
+ HudAwarenessHandle *handle;
+ guint object_id;
+
+ memset (static_cast<void *>(&vtable), 0, sizeof (vtable));
+ vtable.method_call = hud_awareness_method_call;
+
+ if G_UNLIKELY (iface == nullptr)
+ {
+ GError *local_error = nullptr;
+
+ info = g_dbus_node_info_new_for_xml ("<node>"
+ "<interface name='com.canonical.hud.Awareness'>"
+ "<method name='CheckAwareness'/>"
+ "<method name='HudActiveChanged'>"
+ "<arg type='b'/>"
+ "</method>"
+ "</interface>"
+ "</node>",
+ &local_error);
+ g_assert_no_error (local_error);
+ iface = g_dbus_node_info_lookup_interface (info, "com.canonical.hud.Awareness");
+ g_assert (iface != nullptr);
+ }
+
+ handle = static_cast<HudAwarenessHandle*>(g_malloc (sizeof (HudAwarenessHandle)));
+
+ object_id = g_dbus_connection_register_object (connection, object_path, iface, &vtable, handle, &g_free, error);
+
+ if (object_id == 0)
+ {
+ g_free (handle);
+ return 0;
+ }
+
+ handle->connection = g_object_ref(connection);
+ handle->callback = callback;
+ handle->user_data = user_data;
+ handle->notify = notify;
+
+ return object_id;
+}
+
+void
+hud_awareness_unregister (GDBusConnection *connection,
+ guint subscription_id)
+{
+ g_dbus_connection_unregister_object (connection, subscription_id);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/salnativewidgets-gtk.cxx b/vcl/unx/gtk3/salnativewidgets-gtk.cxx
new file mode 100644
index 0000000000..a3a82edaa1
--- /dev/null
+++ b/vcl/unx/gtk3/salnativewidgets-gtk.cxx
@@ -0,0 +1,3082 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <osl/module.h>
+
+#include <config_cairo_canvas.h>
+
+#include <unx/gtk/gtkframe.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include <unx/gtk/gtkgdi.hxx>
+#include <unx/gtk/gtkbackend.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/settings.hxx>
+#include <unx/fontmanager.hxx>
+
+#include "gtkcairo.hxx"
+#include <optional>
+
+GtkStyleContext* GtkSalGraphics::mpWindowStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpLinkButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpEntryStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpTextViewStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpVScrollbarStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpVScrollbarContentsStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpVScrollbarTroughStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpVScrollbarSliderStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpVScrollbarButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpHScrollbarStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpHScrollbarContentsStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpHScrollbarTroughStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpHScrollbarSliderStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpHScrollbarButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpToolbarStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpToolButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpToolbarSeparatorStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpCheckButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpCheckButtonCheckStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpRadioButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpRadioButtonRadioStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpSpinStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpSpinUpStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpSpinDownStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpComboboxStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpComboboxBoxStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpComboboxEntryStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpComboboxButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpComboboxButtonBoxStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpComboboxButtonArrowStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpListboxStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpListboxBoxStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpListboxButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpListboxButtonBoxStyle= nullptr;
+GtkStyleContext* GtkSalGraphics::mpListboxButtonArrowStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpFrameInStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpFrameOutStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpFixedHoriLineStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpFixedVertLineStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpTreeHeaderButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpProgressBarStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpProgressBarTroughStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpProgressBarProgressStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpNotebookStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpNotebookStackStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpNotebookHeaderStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpNotebookHeaderTabsStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpNotebookHeaderTabsTabStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpNotebookHeaderTabsTabLabelStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpNotebookHeaderTabsTabActiveLabelStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpNotebookHeaderTabsTabHoverLabelStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpMenuBarStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpMenuBarItemStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpMenuWindowStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpMenuStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpMenuItemStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpMenuItemArrowStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpMenuItemLabelStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpCheckMenuItemStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpCheckMenuItemCheckStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpRadioMenuItemStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpRadioMenuItemRadioStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpSeparatorMenuItemStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpSeparatorMenuItemSeparatorStyle = nullptr;
+gint GtkSalGraphics::mnVerticalSeparatorMinWidth = 0;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+static void style_context_get_margin(GtkStyleContext *pContext, GtkBorder *pMargin)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_style_context_get_margin(pContext, pMargin);
+#else
+ gtk_style_context_get_margin(pContext, gtk_style_context_get_state(pContext), pMargin);
+#endif
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+static void style_context_get_border(GtkStyleContext* pContext, GtkBorder* pBorder)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_style_context_get_border(pContext, pBorder);
+#else
+ gtk_style_context_get_border(pContext, gtk_style_context_get_state(pContext), pBorder);
+#endif
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+static void style_context_get_padding(GtkStyleContext* pContext, GtkBorder* pPadding)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_style_context_get_padding(pContext, pPadding);
+#else
+ gtk_style_context_get_padding(pContext, gtk_style_context_get_state(pContext), pPadding);
+#endif
+}
+#endif
+
+bool GtkSalGraphics::style_loaded = false;
+/************************************************************************
+ * State conversion
+ ************************************************************************/
+#if !GTK_CHECK_VERSION(4, 0, 0)
+static GtkStateFlags NWConvertVCLStateToGTKState(ControlState nVCLState)
+{
+ GtkStateFlags nGTKState = GTK_STATE_FLAG_NORMAL;
+
+ if (!( nVCLState & ControlState::ENABLED ))
+ {
+ nGTKState = GTK_STATE_FLAG_INSENSITIVE;
+ }
+
+ if ( nVCLState & ControlState::PRESSED )
+ {
+ nGTKState = static_cast<GtkStateFlags>(nGTKState | GTK_STATE_FLAG_ACTIVE);
+ }
+
+ if ( nVCLState & ControlState::ROLLOVER )
+ {
+ nGTKState = static_cast<GtkStateFlags>(nGTKState | GTK_STATE_FLAG_PRELIGHT);
+ }
+
+ if ( nVCLState & ControlState::SELECTED )
+ nGTKState = static_cast<GtkStateFlags>(nGTKState | GTK_STATE_FLAG_SELECTED);
+
+ if ( nVCLState & ControlState::FOCUSED )
+ nGTKState = static_cast<GtkStateFlags>(nGTKState | GTK_STATE_FLAG_FOCUSED);
+
+ if (AllSettings::GetLayoutRTL())
+ {
+ nGTKState = static_cast<GtkStateFlags>(nGTKState | GTK_STATE_FLAG_DIR_RTL);
+ }
+ else
+ {
+ nGTKState = static_cast<GtkStateFlags>(nGTKState | GTK_STATE_FLAG_DIR_LTR);
+ }
+
+ return nGTKState;
+}
+
+namespace {
+
+enum class RenderType {
+ BackgroundAndFrame = 1,
+ Check,
+ Background,
+ MenuSeparator,
+ ToolbarSeparator,
+ Separator,
+ Arrow,
+ Radio,
+ Scrollbar,
+ Spinbutton,
+ Combobox,
+ Expander,
+ Icon,
+ Progress,
+ TabItem,
+ Focus
+};
+
+}
+
+static void NWCalcArrowRect( const tools::Rectangle& rButton, tools::Rectangle& rArrow )
+{
+ // Size the arrow appropriately
+ Size aSize( rButton.GetWidth()/2, rButton.GetHeight()/2 );
+ rArrow.SetSize( aSize );
+
+ rArrow.SetPos( Point(
+ rButton.Left() + ( rButton.GetWidth() - rArrow.GetWidth() ) / 2,
+ rButton.Top() + ( rButton.GetHeight() - rArrow.GetHeight() ) / 2
+ ) );
+}
+
+tools::Rectangle GtkSalGraphics::NWGetSpinButtonRect( ControlPart nPart, tools::Rectangle aAreaRect)
+{
+ gint w, h;
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h);
+ gint icon_size = std::max(w, h);
+
+ GtkBorder padding, border;
+ style_context_get_padding(mpSpinUpStyle, &padding);
+ style_context_get_border(mpSpinUpStyle, &border);
+
+ gint buttonWidth = icon_size + padding.left + padding.right +
+ border.left + border.right;
+
+ tools::Rectangle buttonRect(Point(0, aAreaRect.Top()), Size(buttonWidth, 0));
+ buttonRect.setHeight(aAreaRect.GetHeight());
+ tools::Rectangle partRect(buttonRect);
+ if ( nPart == ControlPart::ButtonUp )
+ {
+ if (AllSettings::GetLayoutRTL())
+ partRect.SetPosX(aAreaRect.Left());
+ else
+ partRect.SetPosX(aAreaRect.Left() + (aAreaRect.GetWidth() - buttonRect.GetWidth()));
+ }
+ else if( nPart == ControlPart::ButtonDown )
+ {
+ if (AllSettings::GetLayoutRTL())
+ partRect.SetPosX(aAreaRect.Left() + buttonRect.GetWidth());
+ else
+ partRect.SetPosX(aAreaRect.Left() + (aAreaRect.GetWidth() - 2 * buttonRect.GetWidth()));
+ }
+ else
+ {
+ if (AllSettings::GetLayoutRTL())
+ {
+ partRect.SetRight( aAreaRect.Left() + aAreaRect.GetWidth() );
+ partRect.SetLeft( aAreaRect.Left() + (2 * buttonRect.GetWidth()) - 1 );
+ }
+ else
+ {
+ partRect.SetRight( (aAreaRect.Left() + (aAreaRect.GetWidth() - 2 * buttonRect.GetWidth())) - 1 );
+ partRect.SetLeft( aAreaRect.Left() );
+ }
+ partRect.SetTop( aAreaRect.Top() );
+ partRect.SetBottom( aAreaRect.Bottom() );
+ }
+
+ return partRect;
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+namespace
+{
+ void QuerySize(GtkStyleContext *pContext, Size &rSize)
+ {
+ GtkBorder margin, border, padding;
+
+ style_context_get_margin(pContext, &margin);
+ style_context_get_border(pContext, &border);
+ style_context_get_padding(pContext, &padding);
+
+ int nMinWidth(0), nMinHeight(0);
+ GtkStateFlags stateflags = gtk_style_context_get_state (pContext);
+ gtk_style_context_get(pContext, stateflags,
+ "min-width", &nMinWidth, "min-height", &nMinHeight, nullptr);
+ nMinWidth += margin.left + margin.right + border.left + border.right + padding.left + padding.right;
+ nMinHeight += margin.top + margin.bottom + border.top + border.bottom + padding.top + padding.bottom;
+
+ rSize = Size(std::max<tools::Long>(rSize.Width(), nMinWidth), std::max<tools::Long>(rSize.Height(), nMinHeight));
+ }
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+tools::Rectangle GtkSalGraphics::NWGetScrollButtonRect( ControlPart nPart, tools::Rectangle aAreaRect )
+{
+ tools::Rectangle buttonRect;
+
+ gboolean has_forward;
+ gboolean has_forward2;
+ gboolean has_backward;
+ gboolean has_backward2;
+
+ GtkStyleContext* pScrollbarStyle = nullptr;
+ if ((nPart == ControlPart::ButtonLeft) || (nPart == ControlPart::ButtonRight))
+ pScrollbarStyle = mpHScrollbarStyle;
+ else // (nPart == ControlPart::ButtonUp) || (nPart == ControlPart::ButtonDown)
+ pScrollbarStyle = mpVScrollbarStyle;
+
+ gtk_style_context_get_style( pScrollbarStyle,
+ "has-forward-stepper", &has_forward,
+ "has-secondary-forward-stepper", &has_forward2,
+ "has-backward-stepper", &has_backward,
+ "has-secondary-backward-stepper", &has_backward2, nullptr );
+
+ gint nFirst = 0;
+ gint nSecond = 0;
+
+ if ( has_forward ) nSecond += 1;
+ if ( has_forward2 ) nFirst += 1;
+ if ( has_backward ) nFirst += 1;
+ if ( has_backward2 ) nSecond += 1;
+
+ Size aSize;
+ if (nPart == ControlPart::ButtonLeft || nPart == ControlPart::ButtonRight)
+ {
+ QuerySize(mpHScrollbarStyle, aSize);
+ QuerySize(mpHScrollbarContentsStyle, aSize);
+ QuerySize(mpHScrollbarButtonStyle, aSize);
+ }
+ else
+ {
+ QuerySize(mpVScrollbarStyle, aSize);
+ QuerySize(mpVScrollbarContentsStyle, aSize);
+ QuerySize(mpVScrollbarButtonStyle, aSize);
+ }
+
+ if (nPart == ControlPart::ButtonUp)
+ {
+ aSize.setHeight( aSize.Height() * nFirst );
+ buttonRect.SetLeft(aAreaRect.Left());
+ buttonRect.SetTop(aAreaRect.Top());
+ }
+ else if (nPart == ControlPart::ButtonLeft)
+ {
+ aSize.setWidth( aSize.Width() * nFirst );
+ buttonRect.SetLeft(aAreaRect.Left());
+ buttonRect.SetTop(aAreaRect.Top());
+ }
+ else if (nPart == ControlPart::ButtonDown)
+ {
+ aSize.setHeight( aSize.Height() * nSecond );
+ buttonRect.SetLeft(aAreaRect.Left());
+ buttonRect.SetTop(aAreaRect.Top() + aAreaRect.GetHeight() - aSize.Height());
+ }
+ else if (nPart == ControlPart::ButtonRight)
+ {
+ aSize.setWidth( aSize.Width() * nSecond );
+ buttonRect.SetLeft(aAreaRect.Left() + aAreaRect.GetWidth() - aSize.Width());
+ buttonRect.SetTop(aAreaRect.Top());
+ }
+
+ buttonRect.SetSize(aSize);
+
+ return buttonRect;
+}
+#endif
+
+static GtkWidget* gCacheWindow;
+static GtkWidget* gDumbContainer;
+#if GTK_CHECK_VERSION(4, 0, 0)
+static GtkWidget* gVScrollbar;
+static GtkWidget* gHScrollbar;
+static GtkWidget* gTextView;
+#else
+static GtkWidget* gComboBox;
+static GtkWidget* gListBox;
+static GtkWidget* gSpinBox;
+static GtkWidget* gTreeViewWidget;
+#endif
+static GtkWidget* gEntryBox;
+
+namespace
+{
+ void style_context_set_state(GtkStyleContext* context, GtkStateFlags flags)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ do
+ {
+ gtk_style_context_set_state(context, flags);
+ }
+ while ((context = gtk_style_context_get_parent(context)));
+#else
+ gtk_style_context_set_state(context, flags);
+#endif
+ }
+
+ class StyleContextSave
+ {
+ private:
+ std::vector<std::pair<GtkStyleContext*, GtkStateFlags>> m_aStates;
+ public:
+ void save(GtkStyleContext* context)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ do
+ {
+ m_aStates.emplace_back(context, gtk_style_context_get_state(context));
+ }
+ while ((context = gtk_style_context_get_parent(context)));
+#else
+ m_aStates.emplace_back(context, gtk_style_context_get_state(context));
+#endif
+ }
+ void restore()
+ {
+ for (auto a = m_aStates.rbegin(); a != m_aStates.rend(); ++a)
+ {
+ gtk_style_context_set_state(a->first, a->second);
+ }
+ m_aStates.clear();
+ }
+ };
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ tools::Rectangle render_common(GtkStyleContext *pContext, cairo_t *cr, const tools::Rectangle &rIn, GtkStateFlags flags)
+ {
+ if (!pContext)
+ return rIn;
+
+ gtk_style_context_set_state(pContext, flags);
+
+ tools::Rectangle aRect(rIn);
+ GtkBorder margin;
+ style_context_get_margin(pContext, &margin);
+
+ aRect.AdjustLeft(margin.left );
+ aRect.AdjustTop(margin.top );
+ aRect.AdjustRight( -(margin.right) );
+ aRect.AdjustBottom( -(margin.bottom) );
+
+ gtk_render_background(pContext, cr, aRect.Left(), aRect.Top(),
+ aRect.GetWidth(), aRect.GetHeight());
+ gtk_render_frame(pContext, cr, aRect.Left(), aRect.Top(),
+ aRect.GetWidth(), aRect.GetHeight());
+
+ GtkBorder border, padding;
+ style_context_get_border(pContext, &border);
+ style_context_get_padding(pContext, &padding);
+
+ aRect.AdjustLeft(border.left + padding.left );
+ aRect.AdjustTop(border.top + padding.top );
+ aRect.AdjustRight( -(border.right + padding.right) );
+ aRect.AdjustBottom( -(border.bottom + padding.bottom) );
+
+ return aRect;
+ }
+#endif
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalGraphics::PaintScrollbar(GtkStyleContext *context,
+ cairo_t *cr,
+ const tools::Rectangle& rControlRectangle,
+ ControlPart nPart,
+ const ImplControlValue& rValue )
+{
+ assert(rValue.getType() == ControlType::Scrollbar);
+ const ScrollbarValue& rScrollbarVal = static_cast<const ScrollbarValue&>(rValue);
+ tools::Rectangle scrollbarRect;
+ GtkStateFlags stateFlags;
+ GtkOrientation scrollbarOrientation;
+ tools::Rectangle thumbRect = rScrollbarVal.maThumbRect;
+ tools::Rectangle button11BoundRect = rScrollbarVal.maButton1Rect; // backward
+ tools::Rectangle button22BoundRect = rScrollbarVal.maButton2Rect; // forward
+ tools::Rectangle button12BoundRect = rScrollbarVal.maButton1Rect; // secondary forward
+ tools::Rectangle button21BoundRect = rScrollbarVal.maButton2Rect; // secondary backward
+ gdouble arrow1Angle; // backward
+ gdouble arrow2Angle; // forward
+ tools::Rectangle arrowRect;
+ gint slider_width = 0;
+ gint stepper_size = 0;
+
+ // make controlvalue rectangles relative to area
+ thumbRect.Move( -rControlRectangle.Left(), -rControlRectangle.Top() );
+ button11BoundRect.Move( -rControlRectangle.Left(), -rControlRectangle.Top() );
+ button22BoundRect.Move( -rControlRectangle.Left(), -rControlRectangle.Top() );
+ button12BoundRect.Move( -rControlRectangle.Left(), -rControlRectangle.Top() );
+ button21BoundRect.Move( -rControlRectangle.Left(), -rControlRectangle.Top() );
+
+ // Find the overall bounding rect of the control
+ scrollbarRect = rControlRectangle;
+ if (scrollbarRect.IsEmpty())
+ return;
+
+ gint slider_side;
+ Size aSize;
+ if (nPart == ControlPart::DrawBackgroundHorz)
+ {
+ QuerySize(mpHScrollbarStyle, aSize);
+ QuerySize(mpHScrollbarContentsStyle, aSize);
+ QuerySize(mpHScrollbarTroughStyle, aSize);
+ QuerySize(mpHScrollbarSliderStyle, aSize);
+ slider_side = aSize.Height();
+ gtk_style_context_get(mpHScrollbarButtonStyle,
+ gtk_style_context_get_state(mpHScrollbarButtonStyle),
+ "min-height", &slider_width,
+ "min-width", &stepper_size, nullptr);
+ }
+ else
+ {
+ QuerySize(mpVScrollbarStyle, aSize);
+ QuerySize(mpVScrollbarContentsStyle, aSize);
+ QuerySize(mpVScrollbarTroughStyle, aSize);
+ QuerySize(mpVScrollbarSliderStyle, aSize);
+ slider_side = aSize.Width();
+ gtk_style_context_get(mpVScrollbarButtonStyle,
+ gtk_style_context_get_state(mpVScrollbarButtonStyle),
+ "min-width", &slider_width,
+ "min-height", &stepper_size, nullptr);
+ }
+
+ gboolean has_forward;
+ gboolean has_forward2;
+ gboolean has_backward;
+ gboolean has_backward2;
+
+ gtk_style_context_get_style( context,
+ "has-forward-stepper", &has_forward,
+ "has-secondary-forward-stepper", &has_forward2,
+ "has-backward-stepper", &has_backward,
+ "has-secondary-backward-stepper", &has_backward2, nullptr );
+
+ if ( nPart == ControlPart::DrawBackgroundHorz )
+ {
+ // Center vertically in the track
+ scrollbarRect.Move( 0, (scrollbarRect.GetHeight() - slider_side) / 2 );
+ scrollbarRect.SetSize( Size( scrollbarRect.GetWidth(), slider_side ) );
+ thumbRect.Move( 0, (scrollbarRect.GetHeight() - slider_side) / 2 );
+ thumbRect.SetSize( Size( thumbRect.GetWidth(), slider_side ) );
+
+ scrollbarOrientation = GTK_ORIENTATION_HORIZONTAL;
+ arrow1Angle = G_PI * 3 / 2;
+ arrow2Angle = G_PI / 2;
+
+ if ( has_backward )
+ {
+ button12BoundRect.Move( stepper_size,
+ (scrollbarRect.GetHeight() - slider_width) / 2 );
+ }
+
+ button11BoundRect.Move( 0, (scrollbarRect.GetHeight() - slider_width) / 2 );
+ button11BoundRect.SetSize( Size( stepper_size, slider_width ) );
+ button12BoundRect.SetSize( Size( stepper_size, slider_width ) );
+
+ if ( has_backward2 )
+ {
+ button22BoundRect.Move( stepper_size, (scrollbarRect.GetHeight() - slider_width) / 2 );
+ button21BoundRect.Move( 0, (scrollbarRect.GetHeight() - slider_width) / 2 );
+ }
+ else
+ {
+ button22BoundRect.Move( 0, (scrollbarRect.GetHeight() - slider_width) / 2 );
+ }
+
+ button21BoundRect.SetSize( Size( stepper_size, slider_width ) );
+ button22BoundRect.SetSize( Size( stepper_size, slider_width ) );
+ }
+ else
+ {
+ // Center horizontally in the track
+ scrollbarRect.Move( (scrollbarRect.GetWidth() - slider_side) / 2, 0 );
+ scrollbarRect.SetSize( Size( slider_side, scrollbarRect.GetHeight() ) );
+ thumbRect.Move( (scrollbarRect.GetWidth() - slider_side) / 2, 0 );
+ thumbRect.SetSize( Size( slider_side, thumbRect.GetHeight() ) );
+
+ scrollbarOrientation = GTK_ORIENTATION_VERTICAL;
+ arrow1Angle = 0;
+ arrow2Angle = G_PI;
+
+ if ( has_backward )
+ {
+ button12BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2,
+ stepper_size );
+ }
+ button11BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, 0 );
+ button11BoundRect.SetSize( Size( slider_width, stepper_size ) );
+ button12BoundRect.SetSize( Size( slider_width, stepper_size ) );
+
+ if ( has_backward2 )
+ {
+ button22BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, stepper_size );
+ button21BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, 0 );
+ }
+ else
+ {
+ button22BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, 0 );
+ }
+
+ button21BoundRect.SetSize( Size( slider_width, stepper_size ) );
+ button22BoundRect.SetSize( Size( slider_width, stepper_size ) );
+ }
+
+ bool has_slider = !thumbRect.IsEmpty();
+
+ // ----------------- CONTENTS
+ GtkStyleContext* pScrollbarContentsStyle = scrollbarOrientation == GTK_ORIENTATION_VERTICAL ?
+ mpVScrollbarContentsStyle : mpHScrollbarContentsStyle;
+
+ gtk_render_background(gtk_widget_get_style_context(gCacheWindow), cr, 0, 0,
+ scrollbarRect.GetWidth(), scrollbarRect.GetHeight() );
+
+ gtk_render_background(context, cr, 0, 0,
+ scrollbarRect.GetWidth(), scrollbarRect.GetHeight() );
+ gtk_render_frame(context, cr, 0, 0,
+ scrollbarRect.GetWidth(), scrollbarRect.GetHeight() );
+
+ gtk_render_background(pScrollbarContentsStyle, cr, 0, 0,
+ scrollbarRect.GetWidth(), scrollbarRect.GetHeight() );
+ gtk_render_frame(pScrollbarContentsStyle, cr, 0, 0,
+ scrollbarRect.GetWidth(), scrollbarRect.GetHeight() );
+
+ bool backwardButtonInsensitive =
+ rScrollbarVal.mnCur == rScrollbarVal.mnMin;
+ bool forwardButtonInsensitive = rScrollbarVal.mnMax == 0 ||
+ rScrollbarVal.mnCur + rScrollbarVal.mnVisibleSize >= rScrollbarVal.mnMax;
+
+ // ----------------- BUTTON 1
+ if ( has_backward )
+ {
+ stateFlags = NWConvertVCLStateToGTKState(rScrollbarVal.mnButton1State);
+ if ( backwardButtonInsensitive )
+ stateFlags = GTK_STATE_FLAG_INSENSITIVE;
+
+ GtkStyleContext* pScrollbarButtonStyle = scrollbarOrientation == GTK_ORIENTATION_VERTICAL ?
+ mpVScrollbarButtonStyle : mpHScrollbarButtonStyle;
+
+ gtk_style_context_set_state(pScrollbarButtonStyle, stateFlags);
+
+ gtk_render_background(pScrollbarButtonStyle, cr,
+ button11BoundRect.Left(), button11BoundRect.Top(),
+ button11BoundRect.GetWidth(), button11BoundRect.GetHeight() );
+ gtk_render_frame(pScrollbarButtonStyle, cr,
+ button11BoundRect.Left(), button11BoundRect.Top(),
+ button11BoundRect.GetWidth(), button11BoundRect.GetHeight() );
+
+ // ----------------- ARROW 1
+ NWCalcArrowRect( button11BoundRect, arrowRect );
+ gtk_render_arrow(pScrollbarButtonStyle, cr,
+ arrow1Angle,
+ arrowRect.Left(), arrowRect.Top(),
+ MIN(arrowRect.GetWidth(), arrowRect.GetHeight()) );
+ }
+ if ( has_forward2 )
+ {
+ stateFlags = NWConvertVCLStateToGTKState(rScrollbarVal.mnButton2State);
+ if ( forwardButtonInsensitive )
+ stateFlags = GTK_STATE_FLAG_INSENSITIVE;
+
+ GtkStyleContext* pScrollbarButtonStyle = scrollbarOrientation == GTK_ORIENTATION_VERTICAL ?
+ mpVScrollbarButtonStyle : mpHScrollbarButtonStyle;
+
+ gtk_style_context_set_state(pScrollbarButtonStyle, stateFlags);
+
+ gtk_render_background(pScrollbarButtonStyle, cr,
+ button12BoundRect.Left(), button12BoundRect.Top(),
+ button12BoundRect.GetWidth(), button12BoundRect.GetHeight() );
+ gtk_render_frame(pScrollbarButtonStyle, cr,
+ button12BoundRect.Left(), button12BoundRect.Top(),
+ button12BoundRect.GetWidth(), button12BoundRect.GetHeight() );
+
+ // ----------------- ARROW 1
+ NWCalcArrowRect( button12BoundRect, arrowRect );
+ gtk_render_arrow(pScrollbarButtonStyle, cr,
+ arrow2Angle,
+ arrowRect.Left(), arrowRect.Top(),
+ MIN(arrowRect.GetWidth(), arrowRect.GetHeight()) );
+ }
+ // ----------------- BUTTON 2
+
+ if ( has_forward )
+ {
+ stateFlags = NWConvertVCLStateToGTKState(rScrollbarVal.mnButton2State);
+ if ( forwardButtonInsensitive )
+ stateFlags = GTK_STATE_FLAG_INSENSITIVE;
+
+ GtkStyleContext* pScrollbarButtonStyle = scrollbarOrientation == GTK_ORIENTATION_VERTICAL ?
+ mpVScrollbarButtonStyle : mpHScrollbarButtonStyle;
+
+ gtk_style_context_set_state(pScrollbarButtonStyle, stateFlags);
+
+ gtk_render_background(pScrollbarButtonStyle, cr,
+ button22BoundRect.Left(), button22BoundRect.Top(),
+ button22BoundRect.GetWidth(), button22BoundRect.GetHeight() );
+ gtk_render_frame(pScrollbarButtonStyle, cr,
+ button22BoundRect.Left(), button22BoundRect.Top(),
+ button22BoundRect.GetWidth(), button22BoundRect.GetHeight() );
+
+ // ----------------- ARROW 2
+ NWCalcArrowRect( button22BoundRect, arrowRect );
+ gtk_render_arrow(pScrollbarButtonStyle, cr,
+ arrow2Angle,
+ arrowRect.Left(), arrowRect.Top(),
+ MIN(arrowRect.GetWidth(), arrowRect.GetHeight()) );
+ }
+
+ if ( has_backward2 )
+ {
+ stateFlags = NWConvertVCLStateToGTKState(rScrollbarVal.mnButton1State);
+ if ( backwardButtonInsensitive )
+ stateFlags = GTK_STATE_FLAG_INSENSITIVE;
+
+ GtkStyleContext* pScrollbarButtonStyle = scrollbarOrientation == GTK_ORIENTATION_VERTICAL ?
+ mpVScrollbarButtonStyle : mpHScrollbarButtonStyle;
+
+ gtk_style_context_set_state(pScrollbarButtonStyle, stateFlags);
+
+ gtk_render_background(pScrollbarButtonStyle, cr,
+ button21BoundRect.Left(), button21BoundRect.Top(),
+ button21BoundRect.GetWidth(), button21BoundRect.GetHeight() );
+ gtk_render_frame(pScrollbarButtonStyle, cr,
+ button21BoundRect.Left(), button21BoundRect.Top(),
+ button21BoundRect.GetWidth(), button21BoundRect.GetHeight() );
+
+ // ----------------- ARROW 2
+ NWCalcArrowRect( button21BoundRect, arrowRect );
+ gtk_render_arrow(pScrollbarButtonStyle, cr,
+ arrow1Angle,
+ arrowRect.Left(), arrowRect.Top(),
+ MIN(arrowRect.GetWidth(), arrowRect.GetHeight()) );
+ }
+
+ // ----------------- TROUGH
+ // trackrect matches that of ScrollBar::ImplCalc
+ tools::Rectangle aTrackRect(Point(0, 0), scrollbarRect.GetSize());
+ if (nPart == ControlPart::DrawBackgroundHorz)
+ {
+ tools::Rectangle aBtn1Rect = NWGetScrollButtonRect(ControlPart::ButtonLeft, aTrackRect);
+ tools::Rectangle aBtn2Rect = NWGetScrollButtonRect(ControlPart::ButtonRight, aTrackRect);
+ if (!aBtn1Rect.IsWidthEmpty())
+ aTrackRect.SetLeft( aBtn1Rect.Right() );
+ if (!aBtn2Rect.IsWidthEmpty())
+ aTrackRect.SetRight( aBtn2Rect.Left() );
+ }
+ else
+ {
+ tools::Rectangle aBtn1Rect = NWGetScrollButtonRect(ControlPart::ButtonUp, aTrackRect);
+ tools::Rectangle aBtn2Rect = NWGetScrollButtonRect(ControlPart::ButtonDown, aTrackRect);
+ if (!aBtn1Rect.IsHeightEmpty())
+ aTrackRect.SetTop( aBtn1Rect.Bottom() + 1 );
+ if (!aBtn2Rect.IsHeightEmpty())
+ aTrackRect.SetBottom( aBtn2Rect.Top() );
+ }
+
+ GtkStyleContext* pScrollbarTroughStyle = scrollbarOrientation == GTK_ORIENTATION_VERTICAL ?
+ mpVScrollbarTroughStyle : mpHScrollbarTroughStyle;
+ gtk_render_background(pScrollbarTroughStyle, cr, aTrackRect.Left(), aTrackRect.Top(),
+ aTrackRect.GetWidth(), aTrackRect.GetHeight() );
+ gtk_render_frame(pScrollbarTroughStyle, cr, aTrackRect.Left(), aTrackRect.Top(),
+ aTrackRect.GetWidth(), aTrackRect.GetHeight() );
+
+ // ----------------- THUMB
+ if ( !has_slider )
+ return;
+
+ stateFlags = NWConvertVCLStateToGTKState(rScrollbarVal.mnThumbState);
+ if ( rScrollbarVal.mnThumbState & ControlState::PRESSED )
+ stateFlags = static_cast<GtkStateFlags>(stateFlags | GTK_STATE_FLAG_PRELIGHT);
+
+ GtkStyleContext* pScrollbarSliderStyle = scrollbarOrientation == GTK_ORIENTATION_VERTICAL ?
+ mpVScrollbarSliderStyle : mpHScrollbarSliderStyle;
+
+ gtk_style_context_set_state(pScrollbarSliderStyle, stateFlags);
+
+ GtkBorder margin;
+ style_context_get_margin(pScrollbarSliderStyle, &margin);
+
+ gtk_render_background(pScrollbarSliderStyle, cr,
+ thumbRect.Left() + margin.left, thumbRect.Top() + margin.top,
+ thumbRect.GetWidth() - margin.left - margin.right,
+ thumbRect.GetHeight() - margin.top - margin.bottom);
+
+ gtk_render_frame(pScrollbarSliderStyle, cr,
+ thumbRect.Left() + margin.left, thumbRect.Top() + margin.top,
+ thumbRect.GetWidth() - margin.left - margin.right,
+ thumbRect.GetHeight() - margin.top - margin.bottom);
+}
+
+void GtkSalGraphics::PaintOneSpinButton( GtkStyleContext *context,
+ cairo_t *cr,
+ ControlPart nPart,
+ tools::Rectangle aAreaRect,
+ ControlState nState )
+{
+ GtkBorder padding, border;
+
+ GtkStateFlags stateFlags = NWConvertVCLStateToGTKState(nState);
+ tools::Rectangle buttonRect = NWGetSpinButtonRect( nPart, aAreaRect );
+
+ gtk_style_context_set_state(context, stateFlags);
+
+ style_context_get_padding(context, &padding);
+ style_context_get_border(context, &border);
+
+ gtk_render_background(context, cr,
+ buttonRect.Left(), buttonRect.Top(),
+ buttonRect.GetWidth(), buttonRect.GetHeight() );
+
+ gint iconWidth = buttonRect.GetWidth() - padding.left - padding.right - border.left - border.right;
+ gint iconHeight = buttonRect.GetHeight() - padding.top - padding.bottom - border.top - border.bottom;
+
+ const char* icon = (nPart == ControlPart::ButtonUp) ? "list-add-symbolic" : "list-remove-symbolic";
+ GtkIconTheme *pIconTheme = gtk_icon_theme_get_for_screen(gtk_widget_get_screen(mpWindow));
+
+ gint scale = gtk_style_context_get_scale (context);
+ GtkIconInfo *info = gtk_icon_theme_lookup_icon_for_scale(pIconTheme, icon, std::min(iconWidth, iconHeight), scale,
+ static_cast<GtkIconLookupFlags>(0));
+
+ GdkPixbuf *pixbuf = gtk_icon_info_load_symbolic_for_context(info, context, nullptr, nullptr);
+ g_object_unref(info);
+
+ iconWidth = gdk_pixbuf_get_width(pixbuf)/scale;
+ iconHeight = gdk_pixbuf_get_height(pixbuf)/scale;
+ tools::Rectangle arrowRect(buttonRect.Center() - Point(iconWidth / 2, iconHeight / 2),
+ Size(iconWidth, iconHeight));
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_scale (context, 1);
+ gtk_render_icon(context, cr, pixbuf, arrowRect.Left(), arrowRect.Top());
+ gtk_style_context_restore (context);
+ g_object_unref(pixbuf);
+
+ gtk_render_frame(context, cr,
+ buttonRect.Left(), buttonRect.Top(),
+ buttonRect.GetWidth(), buttonRect.GetHeight() );
+}
+
+void GtkSalGraphics::PaintSpinButton(GtkStateFlags flags,
+ cairo_t *cr,
+ const tools::Rectangle& rControlRectangle,
+ ControlPart nPart,
+ const ImplControlValue& rValue )
+{
+ const SpinbuttonValue *pSpinVal = (rValue.getType() == ControlType::SpinButtons) ? static_cast<const SpinbuttonValue *>(&rValue) : nullptr;
+ ControlPart upBtnPart = ControlPart::ButtonUp;
+ ControlState upBtnState = ControlState::NONE;
+ ControlPart downBtnPart = ControlPart::ButtonDown;
+ ControlState downBtnState = ControlState::NONE;
+
+ if ( pSpinVal )
+ {
+ upBtnPart = pSpinVal->mnUpperPart;
+ upBtnState = pSpinVal->mnUpperState;
+
+ downBtnPart = pSpinVal->mnLowerPart;
+ downBtnState = pSpinVal->mnLowerState;
+ }
+
+ if (nPart == ControlPart::Entire)
+ {
+ gtk_style_context_set_state(mpWindowStyle, flags);
+
+ gtk_render_background(mpWindowStyle, cr,
+ 0, 0,
+ rControlRectangle.GetWidth(), rControlRectangle.GetHeight());
+
+ gtk_style_context_set_state(mpSpinStyle, flags);
+
+ gtk_render_background(mpSpinStyle, cr,
+ 0, 0,
+ rControlRectangle.GetWidth(), rControlRectangle.GetHeight());
+ }
+
+ cairo_translate(cr, -rControlRectangle.Left(), -rControlRectangle.Top());
+ PaintOneSpinButton(mpSpinUpStyle, cr, upBtnPart, rControlRectangle, upBtnState );
+ PaintOneSpinButton(mpSpinDownStyle, cr, downBtnPart, rControlRectangle, downBtnState );
+ cairo_translate(cr, rControlRectangle.Left(), rControlRectangle.Top());
+
+ if (nPart == ControlPart::Entire)
+ {
+ gtk_render_frame(mpSpinStyle, cr,
+ 0, 0,
+ rControlRectangle.GetWidth(), rControlRectangle.GetHeight() );
+ }
+}
+
+#define FALLBACK_ARROW_SIZE gint(11 * 0.85)
+
+tools::Rectangle GtkSalGraphics::NWGetComboBoxButtonRect(ControlType nType,
+ ControlPart nPart,
+ tools::Rectangle aAreaRect )
+{
+ tools::Rectangle aButtonRect;
+
+ GtkBorder padding;
+ if (nType == ControlType::Listbox)
+ style_context_get_padding(mpListboxButtonStyle, &padding);
+ else
+ style_context_get_padding(mpButtonStyle, &padding);
+
+ gint nArrowWidth = FALLBACK_ARROW_SIZE;
+ gtk_style_context_get(mpComboboxButtonArrowStyle,
+ gtk_style_context_get_state(mpComboboxButtonArrowStyle),
+ "min-width", &nArrowWidth, nullptr);
+
+ gint nButtonWidth = nArrowWidth + padding.left + padding.right;
+ if( nPart == ControlPart::ButtonDown )
+ {
+ Point aPos(aAreaRect.Left() + aAreaRect.GetWidth() - nButtonWidth, aAreaRect.Top());
+ if (AllSettings::GetLayoutRTL())
+ aPos.setX( aAreaRect.Left() );
+ aButtonRect.SetSize( Size( nButtonWidth, aAreaRect.GetHeight() ) );
+ aButtonRect.SetPos(aPos);
+ }
+ else if( nPart == ControlPart::SubEdit )
+ {
+ gint adjust_left = padding.left;
+ gint adjust_top = padding.top;
+ gint adjust_right = padding.right;
+ gint adjust_bottom = padding.bottom;
+
+ aButtonRect.SetSize( Size( aAreaRect.GetWidth() - nButtonWidth - (adjust_left + adjust_right),
+ aAreaRect.GetHeight() - (adjust_top + adjust_bottom)) );
+ Point aEditPos = aAreaRect.TopLeft();
+ if (AllSettings::GetLayoutRTL())
+ aEditPos.AdjustX(nButtonWidth );
+ else
+ aEditPos.AdjustX(adjust_left );
+ aEditPos.AdjustY(adjust_top );
+ aButtonRect.SetPos( aEditPos );
+ }
+
+ return aButtonRect;
+}
+
+void GtkSalGraphics::PaintCombobox( GtkStateFlags flags, cairo_t *cr,
+ const tools::Rectangle& rControlRectangle,
+ ControlType nType,
+ ControlPart nPart )
+{
+ tools::Rectangle areaRect;
+ tools::Rectangle buttonRect;
+ tools::Rectangle arrowRect;
+
+ // Find the overall bounding rect of the buttons's drawing area,
+ // plus its actual draw rect excluding adornment
+ areaRect = rControlRectangle;
+
+ buttonRect = NWGetComboBoxButtonRect(ControlType::Combobox, ControlPart::ButtonDown, areaRect);
+
+ tools::Rectangle aEditBoxRect( areaRect );
+ aEditBoxRect.SetSize( Size( areaRect.GetWidth() - buttonRect.GetWidth(), aEditBoxRect.GetHeight() ) );
+ if (AllSettings::GetLayoutRTL())
+ aEditBoxRect.SetPos( Point( areaRect.Left() + buttonRect.GetWidth(), areaRect.Top() ) );
+
+ gint arrow_width = FALLBACK_ARROW_SIZE, arrow_height = FALLBACK_ARROW_SIZE;
+ if (nType == ControlType::Combobox)
+ {
+ gtk_style_context_get(mpComboboxButtonArrowStyle,
+ gtk_style_context_get_state(mpComboboxButtonArrowStyle),
+ "min-width", &arrow_width, "min-height", &arrow_height, nullptr);
+ }
+ else if (nType == ControlType::Listbox)
+ {
+ gtk_style_context_get(mpListboxButtonArrowStyle,
+ gtk_style_context_get_state(mpListboxButtonArrowStyle),
+ "min-width", &arrow_width, "min-height", &arrow_height, nullptr);
+ }
+
+ arrowRect.SetSize(Size(arrow_width, arrow_height));
+ arrowRect.SetPos( Point( buttonRect.Left() + static_cast<gint>((buttonRect.GetWidth() - arrowRect.GetWidth()) / 2),
+ buttonRect.Top() + static_cast<gint>((buttonRect.GetHeight() - arrowRect.GetHeight()) / 2) ) );
+
+
+ tools::Rectangle aRect(Point(0, 0), Size(areaRect.GetWidth(), areaRect.GetHeight()));
+
+ if (nType == ControlType::Combobox)
+ {
+ if( nPart == ControlPart::Entire )
+ {
+ render_common(mpComboboxStyle, cr, aRect, flags);
+ render_common(mpComboboxBoxStyle, cr, aRect, flags);
+ tools::Rectangle aEntryRect(Point(aEditBoxRect.Left() - areaRect.Left(),
+ aEditBoxRect.Top() - areaRect.Top()),
+ Size(aEditBoxRect.GetWidth(), aEditBoxRect.GetHeight()));
+
+ GtkJunctionSides eJuncSides = gtk_style_context_get_junction_sides(mpComboboxEntryStyle);
+ if (AllSettings::GetLayoutRTL())
+ gtk_style_context_set_junction_sides(mpComboboxEntryStyle, GTK_JUNCTION_LEFT);
+ else
+ gtk_style_context_set_junction_sides(mpComboboxEntryStyle, GTK_JUNCTION_RIGHT);
+ render_common(mpComboboxEntryStyle, cr, aEntryRect, flags);
+ gtk_style_context_set_junction_sides(mpComboboxEntryStyle, eJuncSides);
+ }
+
+ tools::Rectangle aButtonRect(Point(buttonRect.Left() - areaRect.Left(), buttonRect.Top() - areaRect.Top()),
+ Size(buttonRect.GetWidth(), buttonRect.GetHeight()));
+ GtkJunctionSides eJuncSides = gtk_style_context_get_junction_sides(mpComboboxButtonStyle);
+ if (AllSettings::GetLayoutRTL())
+ gtk_style_context_set_junction_sides(mpComboboxButtonStyle, GTK_JUNCTION_RIGHT);
+ else
+ gtk_style_context_set_junction_sides(mpComboboxButtonStyle, GTK_JUNCTION_LEFT);
+ render_common(mpComboboxButtonStyle, cr, aButtonRect, flags);
+ gtk_style_context_set_junction_sides(mpComboboxButtonStyle, eJuncSides);
+
+ gtk_render_arrow(mpComboboxButtonArrowStyle, cr,
+ G_PI,
+ (arrowRect.Left() - areaRect.Left()), (arrowRect.Top() - areaRect.Top()),
+ arrowRect.GetWidth() );
+ }
+ else if (nType == ControlType::Listbox)
+ {
+ if( nPart == ControlPart::ListboxWindow )
+ {
+ /* render the popup window with the menu style */
+ gtk_render_frame(mpMenuStyle, cr,
+ 0, 0,
+ areaRect.GetWidth(), areaRect.GetHeight());
+ }
+ else
+ {
+ render_common(mpListboxStyle, cr, aRect, flags);
+ render_common(mpListboxButtonStyle, cr, aRect, flags);
+ render_common(mpListboxBoxStyle, cr, aRect, flags);
+
+ gtk_render_arrow(mpListboxButtonArrowStyle, cr,
+ G_PI,
+ (arrowRect.Left() - areaRect.Left()), (arrowRect.Top() - areaRect.Top()),
+ arrowRect.GetWidth() );
+ }
+ }
+}
+
+static void appendComboEntry(GtkWidgetPath* pSiblingsPath)
+{
+ gtk_widget_path_append_type(pSiblingsPath, GTK_TYPE_ENTRY);
+ gtk_widget_path_iter_set_object_name(pSiblingsPath, -1, "entry");
+ gtk_widget_path_iter_add_class(pSiblingsPath, -1, "combo");
+}
+
+static void appendComboButton(GtkWidgetPath* pSiblingsPath)
+{
+ gtk_widget_path_append_type(pSiblingsPath, GTK_TYPE_BUTTON);
+ gtk_widget_path_iter_set_object_name(pSiblingsPath, -1, "button");
+ gtk_widget_path_iter_add_class(pSiblingsPath, -1, "combo");
+}
+
+static GtkWidgetPath* buildLTRComboSiblingsPath()
+{
+ GtkWidgetPath* pSiblingsPath = gtk_widget_path_new();
+
+ appendComboEntry(pSiblingsPath);
+ appendComboButton(pSiblingsPath);
+
+ return pSiblingsPath;
+}
+
+static GtkWidgetPath* buildRTLComboSiblingsPath()
+{
+ GtkWidgetPath* pSiblingsPath = gtk_widget_path_new();
+
+ appendComboButton(pSiblingsPath);
+ appendComboEntry(pSiblingsPath);
+
+ return pSiblingsPath;
+}
+
+
+GtkStyleContext* GtkSalGraphics::makeContext(GtkWidgetPath *pPath, GtkStyleContext *pParent)
+{
+ GtkStyleContext* context = gtk_style_context_new();
+ gtk_style_context_set_screen(context, gtk_widget_get_screen(mpWindow));
+ gtk_style_context_set_path(context, pPath);
+ if (pParent == nullptr)
+ {
+ GtkWidget* pTopLevel = widget_get_toplevel(mpWindow);
+ GtkStyleContext* pStyle = gtk_widget_get_style_context(pTopLevel);
+ gtk_style_context_set_parent(context, pStyle);
+ gtk_style_context_set_scale (context, gtk_style_context_get_scale (pStyle));
+ }
+ else
+ {
+ gtk_style_context_set_parent(context, pParent);
+ gtk_style_context_set_scale (context, gtk_style_context_get_scale (pParent));
+ }
+ gtk_widget_path_unref(pPath);
+ return context;
+}
+
+GtkStyleContext* GtkSalGraphics::createStyleContext(GtkControlPart ePart)
+{
+ switch (ePart)
+ {
+ case GtkControlPart::ToplevelWindow:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "window");
+ gtk_widget_path_iter_add_class(path, -1, "background");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::Button:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "button");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::LinkButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "button");
+ gtk_widget_path_iter_add_class(path, -1, "link");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::CheckButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_CHECK_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "checkbutton");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::CheckButtonCheck:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpCheckButtonStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_CHECK_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "check");
+ return makeContext(path, mpCheckButtonStyle);
+ }
+ case GtkControlPart::RadioButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_RADIO_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "radiobutton");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::RadioButtonRadio:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpRadioButtonStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_RADIO_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "radio");
+ return makeContext(path, mpRadioButtonStyle);
+ }
+ case GtkControlPart::ComboboxBoxButtonBoxArrow:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpComboboxButtonBoxStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_RADIO_BUTTON);
+ gtk_widget_path_append_type(path, GTK_TYPE_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "arrow");
+ return makeContext(path, mpComboboxButtonBoxStyle);
+ }
+ case GtkControlPart::ListboxBoxButtonBoxArrow:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpListboxButtonBoxStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_RADIO_BUTTON);
+ gtk_widget_path_append_type(path, GTK_TYPE_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "arrow");
+ return makeContext(path, mpListboxButtonBoxStyle);
+ }
+ case GtkControlPart::Entry:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_ENTRY);
+ gtk_widget_path_iter_set_object_name(path, -1, "entry");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::Combobox:
+ case GtkControlPart::Listbox:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "combobox");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::ComboboxBox:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpComboboxStyle));
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "box");
+ gtk_widget_path_iter_add_class(path, -1, "horizontal");
+ gtk_widget_path_iter_add_class(path, -1, "linked");
+ return makeContext(path, mpComboboxStyle);
+ }
+ case GtkControlPart::ListboxBox:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpListboxStyle));
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "box");
+ gtk_widget_path_iter_add_class(path, -1, "horizontal");
+ gtk_widget_path_iter_add_class(path, -1, "linked");
+ return makeContext(path, mpListboxStyle);
+ }
+ case GtkControlPart::ComboboxBoxEntry:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpComboboxBoxStyle));
+ GtkWidgetPath* pSiblingsPath;
+ if (AllSettings::GetLayoutRTL())
+ {
+ pSiblingsPath = buildRTLComboSiblingsPath();
+ gtk_widget_path_append_with_siblings(path, pSiblingsPath, 1);
+ }
+ else
+ {
+ pSiblingsPath = buildLTRComboSiblingsPath();
+ gtk_widget_path_append_with_siblings(path, pSiblingsPath, 0);
+ }
+ gtk_widget_path_unref(pSiblingsPath);
+ return makeContext(path, mpComboboxBoxStyle);
+ }
+ case GtkControlPart::ComboboxBoxButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpComboboxBoxStyle));
+ GtkWidgetPath* pSiblingsPath;
+ if (AllSettings::GetLayoutRTL())
+ {
+ pSiblingsPath = buildRTLComboSiblingsPath();
+ gtk_widget_path_append_with_siblings(path, pSiblingsPath, 0);
+ }
+ else
+ {
+ pSiblingsPath = buildLTRComboSiblingsPath();
+ gtk_widget_path_append_with_siblings(path, pSiblingsPath, 1);
+ }
+ gtk_widget_path_unref(pSiblingsPath);
+ return makeContext(path, mpComboboxBoxStyle);
+ }
+ case GtkControlPart::ListboxBoxButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpListboxBoxStyle));
+ GtkWidgetPath* pSiblingsPath = gtk_widget_path_new();
+
+ gtk_widget_path_append_type(pSiblingsPath, GTK_TYPE_BUTTON);
+ gtk_widget_path_iter_set_object_name(pSiblingsPath, -1, "button");
+ gtk_widget_path_iter_add_class(pSiblingsPath, -1, "combo");
+
+ gtk_widget_path_append_with_siblings(path, pSiblingsPath, 0);
+ gtk_widget_path_unref(pSiblingsPath);
+ return makeContext(path, mpListboxBoxStyle);
+ }
+ case GtkControlPart::ComboboxBoxButtonBox:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpComboboxButtonStyle));
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "box");
+ gtk_widget_path_iter_add_class(path, -1, "horizontal");
+ return makeContext(path, mpComboboxButtonStyle);
+ }
+ case GtkControlPart::ListboxBoxButtonBox:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpListboxButtonStyle));
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "box");
+ gtk_widget_path_iter_add_class(path, -1, "horizontal");
+ return makeContext(path, mpListboxButtonStyle);
+ }
+ case GtkControlPart::SpinButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpWindowStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_SPIN_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "spinbutton");
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_HORIZONTAL);
+ return makeContext(path, mpWindowStyle);
+ }
+ case GtkControlPart::SpinButtonUpButton:
+ case GtkControlPart::SpinButtonDownButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpSpinStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_SPIN_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "button");
+ gtk_widget_path_iter_add_class(path, -1, ePart == GtkControlPart::SpinButtonUpButton ? "up" : "down");
+ return makeContext(path, mpSpinStyle);
+ }
+ case GtkControlPart::ScrollbarVertical:
+ case GtkControlPart::ScrollbarHorizontal:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_SCROLLBAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "scrollbar");
+ gtk_widget_path_iter_add_class(path, -1, ePart == GtkControlPart::ScrollbarVertical ? "vertical" : "horizontal");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::ScrollbarVerticalContents:
+ case GtkControlPart::ScrollbarHorizontalContents:
+ {
+ GtkStyleContext *pParent =
+ (ePart == GtkControlPart::ScrollbarVerticalContents) ? mpVScrollbarStyle : mpHScrollbarStyle;
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(pParent));
+ gtk_widget_path_append_type(path, GTK_TYPE_SCROLLBAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "contents");
+ return makeContext(path, pParent);
+ }
+ case GtkControlPart::ScrollbarVerticalTrough:
+ case GtkControlPart::ScrollbarHorizontalTrough:
+ {
+ GtkStyleContext *pParent =
+ (ePart == GtkControlPart::ScrollbarVerticalTrough) ? mpVScrollbarContentsStyle : mpHScrollbarContentsStyle;
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(pParent));
+ gtk_widget_path_append_type(path, GTK_TYPE_SCROLLBAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "trough");
+ return makeContext(path, pParent);
+ }
+ case GtkControlPart::ScrollbarVerticalSlider:
+ case GtkControlPart::ScrollbarHorizontalSlider:
+ {
+ GtkStyleContext *pParent =
+ (ePart == GtkControlPart::ScrollbarVerticalSlider) ? mpVScrollbarTroughStyle : mpHScrollbarTroughStyle;
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(pParent));
+ gtk_widget_path_append_type(path, GTK_TYPE_SCROLLBAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "slider");
+ return makeContext(path, pParent);
+ }
+ case GtkControlPart::ScrollbarVerticalButton:
+ case GtkControlPart::ScrollbarHorizontalButton:
+ {
+ GtkStyleContext *pParent =
+ (ePart == GtkControlPart::ScrollbarVerticalButton) ? mpVScrollbarStyle : mpHScrollbarStyle;
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(pParent));
+ gtk_widget_path_append_type(path, GTK_TYPE_SCROLLBAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "button");
+ return makeContext(path, pParent);
+ }
+ case GtkControlPart::ProgressBar:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_PROGRESS_BAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "progressbar");
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_HORIZONTAL);
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::ProgressBarTrough:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpProgressBarStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_PROGRESS_BAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "trough");
+ return makeContext(path, mpProgressBarStyle);
+ }
+ case GtkControlPart::ProgressBarProgress:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpProgressBarTroughStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_PROGRESS_BAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "progress");
+ return makeContext(path, mpProgressBarTroughStyle);
+ }
+ case GtkControlPart::Notebook:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpWindowStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_NOTEBOOK);
+ gtk_widget_path_iter_set_object_name(path, -1, "notebook");
+ return makeContext(path, mpWindowStyle);
+ }
+ case GtkControlPart::NotebookStack:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpNotebookStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_NOTEBOOK);
+ gtk_widget_path_iter_set_object_name(path, -1, "stack");
+ return makeContext(path, mpNotebookStyle);
+ }
+ case GtkControlPart::NotebookHeader:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpNotebookStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_NOTEBOOK);
+ gtk_widget_path_iter_set_object_name(path, -1, "header");
+ gtk_widget_path_iter_add_class(path, -1, "frame");
+ gtk_widget_path_iter_add_class(path, -1, "top");
+ return makeContext(path, mpNotebookStyle);
+ }
+ case GtkControlPart::NotebookHeaderTabs:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpNotebookHeaderStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_NOTEBOOK);
+ gtk_widget_path_iter_set_object_name(path, -1, "tabs");
+ gtk_widget_path_iter_add_class(path, -1, "top");
+ return makeContext(path, mpNotebookHeaderStyle);
+ }
+ case GtkControlPart::NotebookHeaderTabsTab:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpNotebookHeaderTabsStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_NOTEBOOK);
+ gtk_widget_path_iter_set_object_name(path, -1, "tab");
+ gtk_widget_path_iter_add_class(path, -1, "top");
+ return makeContext(path, mpNotebookHeaderTabsStyle);
+ }
+ case GtkControlPart::NotebookHeaderTabsTabLabel:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpNotebookHeaderTabsTabStyle));
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "label");
+ return makeContext(path, mpNotebookHeaderTabsTabStyle);
+ }
+ case GtkControlPart::NotebookHeaderTabsTabActiveLabel:
+ case GtkControlPart::NotebookHeaderTabsTabHoverLabel:
+ return mpNotebookHeaderTabsTabLabelStyle;
+ case GtkControlPart::FrameBorder:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_FRAME);
+ gtk_widget_path_iter_set_object_name(path, -1, "frame");
+ gtk_widget_path_iter_add_class(path, -1, "frame");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::MenuBar:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpWindowStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_MENU_BAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "menubar");
+ return makeContext(path, mpWindowStyle);
+ }
+ case GtkControlPart::MenuBarItem:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuBarStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "menuitem");
+ return makeContext(path, mpMenuBarStyle);
+ }
+ case GtkControlPart::MenuWindow:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuBarItemStyle));
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "window");
+ gtk_widget_path_iter_add_class(path, -1, "background");
+ gtk_widget_path_iter_add_class(path, -1, "popup");
+ return makeContext(path, mpMenuBarItemStyle);
+ }
+ case GtkControlPart::Menu:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuWindowStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_MENU);
+ gtk_widget_path_iter_set_object_name(path, -1, "menu");
+ return makeContext(path, mpMenuWindowStyle);
+ }
+ case GtkControlPart::MenuItem:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "menuitem");
+ return makeContext(path, mpMenuStyle);
+ }
+ case GtkControlPart::MenuItemLabel:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuItemStyle));
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "label");
+ return makeContext(path, mpMenuItemStyle);
+ }
+ case GtkControlPart::MenuItemArrow:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuItemStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "arrow");
+ return makeContext(path, mpMenuItemStyle);
+ }
+ case GtkControlPart::CheckMenuItem:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_CHECK_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "menuitem");
+ return makeContext(path, mpMenuStyle);
+ }
+ case GtkControlPart::CheckMenuItemCheck:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpCheckMenuItemStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_CHECK_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "check");
+ return makeContext(path, mpCheckMenuItemStyle);
+ }
+ case GtkControlPart::RadioMenuItem:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_RADIO_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "menuitem");
+ return makeContext(path, mpMenuStyle);
+ }
+ case GtkControlPart::RadioMenuItemRadio:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpRadioMenuItemStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_RADIO_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "radio");
+ return makeContext(path, mpRadioMenuItemStyle);
+ }
+ case GtkControlPart::SeparatorMenuItem:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_SEPARATOR_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "menuitem");
+ return makeContext(path, mpMenuStyle);
+ }
+ case GtkControlPart::SeparatorMenuItemSeparator:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpSeparatorMenuItemStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_SEPARATOR_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "separator");
+ return makeContext(path, mpSeparatorMenuItemStyle);
+ }
+ }
+
+ return nullptr;
+}
+
+#ifndef GTK_STYLE_CLASS_POPUP
+constexpr OUStringLiteral GTK_STYLE_CLASS_POPUP = u"popup";
+#endif
+#ifndef GTK_STYLE_CLASS_LABEL
+constexpr OUStringLiteral GTK_STYLE_CLASS_LABEL = u"label";
+#endif
+
+void GtkSalGraphics::PaintCheckOrRadio(cairo_t *cr, GtkStyleContext *context,
+ const tools::Rectangle& rControlRectangle, bool bIsCheck, bool bInMenu)
+{
+ gint indicator_size;
+ gtk_style_context_get_style(context, "indicator-size", &indicator_size, nullptr);
+
+ gint x = (rControlRectangle.GetWidth() - indicator_size) / 2;
+ gint y = (rControlRectangle.GetHeight() - indicator_size) / 2;
+
+ if (!bInMenu)
+ gtk_render_background(context, cr, x, y, indicator_size, indicator_size);
+
+ if (bIsCheck)
+ gtk_render_check(context, cr, x, y, indicator_size, indicator_size);
+ else
+ gtk_render_option(context, cr, x, y, indicator_size, indicator_size);
+
+ gtk_render_frame(context, cr, x, y, indicator_size, indicator_size);
+}
+
+void GtkSalGraphics::PaintCheck(cairo_t *cr, GtkStyleContext *context,
+ const tools::Rectangle& rControlRectangle, bool bInMenu)
+{
+ PaintCheckOrRadio(cr, context, rControlRectangle, true, bInMenu);
+}
+
+void GtkSalGraphics::PaintRadio(cairo_t *cr, GtkStyleContext *context,
+ const tools::Rectangle& rControlRectangle, bool bInMenu)
+{
+ PaintCheckOrRadio(cr, context, rControlRectangle, false, bInMenu);
+}
+
+static gfloat getArrowSize(GtkStyleContext* context)
+{
+ gint min_width, min_weight;
+ gtk_style_context_get_style(context, "min-width", &min_width, nullptr);
+ gtk_style_context_get_style(context, "min-height", &min_weight, nullptr);
+ gfloat arrow_size = 11 * MAX (min_width, min_weight);
+ return arrow_size;
+}
+
+namespace
+{
+ void draw_vertical_separator(GtkStyleContext *context, cairo_t *cr, const tools::Rectangle& rControlRegion, gint nSeparatorWidth)
+ {
+ tools::Long nX = 0;
+ tools::Long nY = 0;
+
+ gint nHalfSeparatorWidth = nSeparatorWidth / 2;
+ gint nHalfRegionWidth = rControlRegion.GetWidth() / 2;
+
+ nX = nX + nHalfRegionWidth - nHalfSeparatorWidth;
+ nY = rControlRegion.GetHeight() > 5 ? 1 : 0;
+ int nHeight = rControlRegion.GetHeight() - (2 * nY);
+
+ gtk_render_background(context, cr, nX, nY, nSeparatorWidth, nHeight);
+ gtk_render_frame(context, cr, nX, nY, nSeparatorWidth, nHeight);
+ }
+
+ void draw_horizontal_separator(GtkStyleContext *context, cairo_t *cr, const tools::Rectangle& rControlRegion)
+ {
+ tools::Long nX = 0;
+ tools::Long nY = 0;
+
+ gint nSeparatorHeight = 1;
+
+ gtk_style_context_get(context,
+ gtk_style_context_get_state(context),
+ "min-height", &nSeparatorHeight, nullptr);
+
+ gint nHalfSeparatorHeight = nSeparatorHeight / 2;
+ gint nHalfRegionHeight = rControlRegion.GetHeight() / 2;
+
+ nY = nY + nHalfRegionHeight - nHalfSeparatorHeight;
+ nX = rControlRegion.GetWidth() > 5 ? 1 : 0;
+ int nWidth = rControlRegion.GetWidth() - (2 * nX);
+
+ gtk_render_background(context, cr, nX, nY, nWidth, nSeparatorHeight);
+ gtk_render_frame(context, cr, nX, nY, nWidth, nSeparatorHeight);
+ }
+}
+#endif
+
+void GtkSalGraphics::handleDamage(const tools::Rectangle& rDamagedRegion)
+{
+ assert(m_pWidgetDraw);
+ assert(!rDamagedRegion.IsEmpty());
+ mpFrame->damaged(rDamagedRegion.Left(), rDamagedRegion.Top(), rDamagedRegion.GetWidth(), rDamagedRegion.GetHeight());
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+bool GtkSalGraphics::drawNativeControl( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion,
+ ControlState nState, const ImplControlValue& rValue,
+ const OUString&, const Color& rBackgroundColor)
+{
+ RenderType renderType = nPart == ControlPart::Focus ? RenderType::Focus : RenderType::BackgroundAndFrame;
+ GtkStyleContext *context = nullptr;
+ GdkPixbuf *pixbuf = nullptr;
+ bool bInMenu = false;
+
+ GtkStateFlags flags = NWConvertVCLStateToGTKState(nState);
+
+ switch(nType)
+ {
+ case ControlType::Spinbox:
+ case ControlType::SpinButtons:
+ context = mpSpinStyle;
+ renderType = RenderType::Spinbutton;
+ break;
+ case ControlType::Editbox:
+ context = mpEntryStyle;
+ break;
+ case ControlType::MultilineEditbox:
+ context = mpTextViewStyle;
+ break;
+ case ControlType::Combobox:
+ context = mpComboboxStyle;
+ renderType = RenderType::Combobox;
+ break;
+ case ControlType::Listbox:
+ if (nPart == ControlPart::Focus)
+ {
+ renderType = RenderType::Focus;
+ context = mpListboxButtonStyle;
+ }
+ else
+ {
+ renderType = RenderType::Combobox;
+ context = mpListboxStyle;
+ }
+ break;
+ case ControlType::MenuPopup:
+ bInMenu = true;
+
+ // map selected menu entries in vcl parlance to gtk prelight
+ if (nPart >= ControlPart::MenuItem && nPart <= ControlPart::SubmenuArrow && (nState & ControlState::SELECTED))
+ flags = static_cast<GtkStateFlags>(flags | GTK_STATE_FLAG_PRELIGHT);
+ flags = static_cast<GtkStateFlags>(flags & ~GTK_STATE_FLAG_ACTIVE);
+ switch(nPart)
+ {
+ case ControlPart::MenuItem:
+ context = mpMenuItemStyle;
+ renderType = RenderType::BackgroundAndFrame;
+ break;
+ case ControlPart::MenuItemCheckMark:
+ context = mpCheckMenuItemCheckStyle;
+ renderType = RenderType::Check;
+ nType = ControlType::Checkbox;
+ if (nState & ControlState::PRESSED)
+ {
+ flags = static_cast<GtkStateFlags>(flags | GTK_STATE_FLAG_CHECKED);
+ }
+ break;
+ case ControlPart::MenuItemRadioMark:
+ context = mpRadioMenuItemRadioStyle;
+ renderType = RenderType::Radio;
+ nType = ControlType::Radiobutton;
+ if (nState & ControlState::PRESSED)
+ {
+ flags = static_cast<GtkStateFlags>(flags | GTK_STATE_FLAG_CHECKED);
+ }
+ break;
+ case ControlPart::Separator:
+ context = mpSeparatorMenuItemSeparatorStyle;
+ flags = GtkStateFlags(GTK_STATE_FLAG_BACKDROP | GTK_STATE_FLAG_INSENSITIVE); //GTK_STATE_FLAG_BACKDROP hack ?
+ renderType = RenderType::MenuSeparator;
+ break;
+ case ControlPart::SubmenuArrow:
+ context = mpMenuItemArrowStyle;
+ renderType = RenderType::Arrow;
+ break;
+ case ControlPart::Entire:
+ context = mpMenuStyle;
+ renderType = RenderType::Background;
+ break;
+ default: break;
+ }
+ break;
+ case ControlType::Toolbar:
+ switch(nPart)
+ {
+ case ControlPart::DrawBackgroundHorz:
+ case ControlPart::DrawBackgroundVert:
+ context = mpToolbarStyle;
+ break;
+ case ControlPart::Button:
+ /* For all checkbuttons in the toolbars */
+ flags = static_cast<GtkStateFlags>(flags |
+ ( (rValue.getTristateVal() == ButtonValue::On) ? GTK_STATE_FLAG_CHECKED : GTK_STATE_FLAG_NORMAL));
+ context = mpToolButtonStyle;
+ break;
+ case ControlPart::SeparatorVert:
+ context = mpToolbarSeparatorStyle;
+ renderType = RenderType::ToolbarSeparator;
+ break;
+ default:
+ return false;
+ }
+ break;
+ case ControlType::Radiobutton:
+ flags = static_cast<GtkStateFlags>(flags |
+ ( (rValue.getTristateVal() == ButtonValue::On) ? GTK_STATE_FLAG_CHECKED : GTK_STATE_FLAG_NORMAL));
+ context = mpRadioButtonRadioStyle;
+ renderType = nPart == ControlPart::Focus ? RenderType::Focus : RenderType::Radio;
+ break;
+ case ControlType::Checkbox:
+ flags = static_cast<GtkStateFlags>(flags |
+ ( (rValue.getTristateVal() == ButtonValue::On) ? GTK_STATE_FLAG_CHECKED :
+ (rValue.getTristateVal() == ButtonValue::Mixed) ? GTK_STATE_FLAG_INCONSISTENT :
+ GTK_STATE_FLAG_NORMAL));
+ context = mpCheckButtonCheckStyle;
+ renderType = nPart == ControlPart::Focus ? RenderType::Focus : RenderType::Check;
+ break;
+ case ControlType::Pushbutton:
+ context = mpButtonStyle;
+ break;
+ case ControlType::Scrollbar:
+ switch(nPart)
+ {
+ case ControlPart::DrawBackgroundVert:
+ case ControlPart::DrawBackgroundHorz:
+ context = (nPart == ControlPart::DrawBackgroundVert)
+ ? mpVScrollbarStyle : mpHScrollbarStyle;
+ renderType = RenderType::Scrollbar;
+ break;
+ default: break;
+ }
+ break;
+ case ControlType::ListNet:
+ return true;
+ case ControlType::TabPane:
+ context = mpNotebookStyle;
+ break;
+ case ControlType::TabBody:
+ context = mpNotebookStackStyle;
+ break;
+ case ControlType::TabHeader:
+ context = mpNotebookHeaderStyle;
+ break;
+ case ControlType::TabItem:
+ context = mpNotebookHeaderTabsTabStyle;
+ if (nState & ControlState::SELECTED)
+ flags = static_cast<GtkStateFlags>(flags | GTK_STATE_FLAG_CHECKED);
+ renderType = RenderType::TabItem;
+ break;
+ case ControlType::WindowBackground:
+ context = gtk_widget_get_style_context(widget_get_toplevel(mpWindow));
+ break;
+ case ControlType::Frame:
+ {
+ DrawFrameStyle nStyle = static_cast<DrawFrameStyle>(rValue.getNumericVal() & 0x0f);
+ if (nStyle == DrawFrameStyle::In)
+ context = mpFrameOutStyle;
+ else
+ context = mpFrameInStyle;
+ break;
+ }
+ case ControlType::Menubar:
+ if (nPart == ControlPart::MenuItem)
+ {
+ context = mpMenuBarItemStyle;
+
+ flags = (!(nState & ControlState::ENABLED)) ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL;
+ if (nState & ControlState::SELECTED)
+ flags = static_cast<GtkStateFlags>(flags | GTK_STATE_FLAG_PRELIGHT);
+ }
+ else
+ {
+ context = mpMenuBarStyle;
+ }
+ break;
+ case ControlType::Fixedline:
+ context = nPart == ControlPart::SeparatorHorz ? mpFixedHoriLineStyle : mpFixedVertLineStyle;
+ renderType = RenderType::Separator;
+ break;
+ case ControlType::ListNode:
+ {
+ context = mpTreeHeaderButtonStyle;
+ ButtonValue aButtonValue = rValue.getTristateVal();
+ if (aButtonValue == ButtonValue::On)
+ flags = static_cast<GtkStateFlags>(flags | GTK_STATE_FLAG_CHECKED);
+ renderType = RenderType::Expander;
+ break;
+ }
+ case ControlType::ListHeader:
+ context = mpTreeHeaderButtonStyle;
+ if (nPart == ControlPart::Arrow)
+ {
+ const char* icon = (rValue.getNumericVal() & 1) ? "pan-down-symbolic" : "pan-up-symbolic";
+ GtkIconTheme *pIconTheme = gtk_icon_theme_get_for_screen(gtk_widget_get_screen(mpWindow));
+ pixbuf = gtk_icon_theme_load_icon_for_scale(pIconTheme, icon,
+ std::max(rControlRegion.GetWidth(), rControlRegion.GetHeight()),
+ gtk_style_context_get_scale (context),
+ static_cast<GtkIconLookupFlags>(0), nullptr);
+ flags = GTK_STATE_FLAG_SELECTED;
+ renderType = RenderType::Icon;
+ }
+ break;
+ case ControlType::Progress:
+ context = mpProgressBarProgressStyle;
+ renderType = RenderType::Progress;
+ break;
+ default:
+ return false;
+ }
+
+ cairo_t *cr = getCairoContext();
+ clipRegion(cr);
+ cairo_translate(cr, rControlRegion.Left(), rControlRegion.Top());
+
+ tools::Long nX = 0;
+ tools::Long nY = 0;
+ tools::Long nWidth = rControlRegion.GetWidth();
+ tools::Long nHeight = rControlRegion.GetHeight();
+
+ StyleContextSave aContextState;
+ aContextState.save(context);
+ style_context_set_state(context, flags);
+
+ // apply background in style, if explicitly set
+ // note: for more complex controls that use multiple styles for their elements,
+ // background may have to be applied for more of those as well (s. case RenderType::Combobox below)
+ GtkCssProvider* pBgCssProvider = nullptr;
+ if (rBackgroundColor != COL_AUTO)
+ {
+ const OUString sColorCss = "* { background-color: #" + rBackgroundColor.AsRGBHexString() + "; }";
+ const OString aResult = OUStringToOString(sColorCss, RTL_TEXTENCODING_UTF8);
+ pBgCssProvider = gtk_css_provider_new();
+ css_provider_load_from_data(pBgCssProvider, aResult.getStr(), aResult.getLength());
+ gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(pBgCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+ switch(renderType)
+ {
+ case RenderType::Background:
+ case RenderType::BackgroundAndFrame:
+ gtk_render_background(context, cr, nX, nY, nWidth, nHeight);
+ if (renderType == RenderType::BackgroundAndFrame)
+ {
+ gtk_render_frame(context, cr, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case RenderType::Check:
+ {
+ PaintCheck(cr, context, rControlRegion, bInMenu);
+ break;
+ }
+ case RenderType::Radio:
+ {
+ PaintRadio(cr, context, rControlRegion, bInMenu);
+ break;
+ }
+ case RenderType::MenuSeparator:
+ gtk_render_line(context, cr,
+ 0, rControlRegion.GetHeight() / 2,
+ rControlRegion.GetWidth() - 1, rControlRegion.GetHeight() / 2);
+ break;
+ case RenderType::ToolbarSeparator:
+ {
+ draw_vertical_separator(context, cr, rControlRegion, mnVerticalSeparatorMinWidth);
+ break;
+ }
+ case RenderType::Separator:
+ if (nPart == ControlPart::SeparatorHorz)
+ draw_horizontal_separator(context, cr, rControlRegion);
+ else
+ draw_vertical_separator(context, cr, rControlRegion, mnVerticalSeparatorMinWidth);
+ break;
+ case RenderType::Arrow:
+ gtk_render_arrow(context, cr,
+ G_PI / 2, 0, 0,
+ MIN(rControlRegion.GetWidth(), 1 + rControlRegion.GetHeight()));
+ break;
+ case RenderType::Expander:
+ gtk_render_expander(context, cr, -2, -2, nWidth+4, nHeight+4);
+ break;
+ case RenderType::Scrollbar:
+ PaintScrollbar(context, cr, rControlRegion, nPart, rValue);
+ break;
+ case RenderType::Spinbutton:
+ PaintSpinButton(flags, cr, rControlRegion, nPart, rValue);
+ break;
+ case RenderType::Combobox:
+ if (pBgCssProvider)
+ {
+ if (nType == ControlType::Combobox)
+ {
+ gtk_style_context_add_provider(mpComboboxEntryStyle, GTK_STYLE_PROVIDER(pBgCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ else if (nType == ControlType::Listbox)
+ {
+ gtk_style_context_add_provider(mpListboxBoxStyle, GTK_STYLE_PROVIDER(pBgCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ }
+ PaintCombobox(flags, cr, rControlRegion, nType, nPart);
+ if (pBgCssProvider)
+ {
+ if (nType == ControlType::Combobox)
+ gtk_style_context_remove_provider(mpComboboxEntryStyle, GTK_STYLE_PROVIDER(pBgCssProvider));
+ else if (nType == ControlType::Listbox)
+ gtk_style_context_remove_provider(mpListboxBoxStyle, GTK_STYLE_PROVIDER(pBgCssProvider));
+ }
+ break;
+ case RenderType::Icon:
+ gtk_style_context_save (context);
+ gtk_style_context_set_scale (context, 1);
+ gtk_render_icon(context, cr, pixbuf, nX, nY);
+ gtk_style_context_restore (context);
+ g_object_unref(pixbuf);
+ break;
+ case RenderType::Focus:
+ {
+ if (nType == ControlType::Checkbox ||
+ nType == ControlType::Radiobutton)
+ {
+ nX -= 2; nY -=2;
+ nHeight += 4; nWidth += 4;
+ }
+ else
+ {
+ GtkBorder border;
+
+ style_context_get_border(context, &border);
+
+ nX += border.left;
+ nY += border.top;
+ nWidth -= border.left + border.right;
+ nHeight -= border.top + border.bottom;
+ }
+
+ gtk_render_focus(context, cr, nX, nY, nWidth, nHeight);
+
+ break;
+ }
+ case RenderType::Progress:
+ {
+ gtk_render_background(mpProgressBarTroughStyle, cr, nX, nY, nWidth, nHeight);
+
+ tools::Long nProgressWidth = rValue.getNumericVal();
+ if (nProgressWidth)
+ {
+ GtkBorder padding;
+ style_context_get_padding(context, &padding);
+
+ nX += padding.left;
+ nY += padding.top;
+ nHeight -= (padding.top + padding.bottom);
+ nProgressWidth -= (padding.left + padding.right);
+ gtk_render_background(context, cr, nX, nY, nProgressWidth, nHeight);
+ gtk_render_frame(context, cr, nX, nY, nProgressWidth, nHeight);
+ }
+
+ gtk_render_frame(mpProgressBarTroughStyle, cr, nX, nY, nWidth, nHeight);
+
+ break;
+ }
+ case RenderType::TabItem:
+ {
+ gint initial_gap(0);
+ gtk_style_context_get_style(mpNotebookStyle,
+ "initial-gap", &initial_gap,
+ nullptr);
+
+ nX += initial_gap/2;
+ nWidth -= initial_gap;
+ tools::Rectangle aRect(Point(nX, nY), Size(nWidth, nHeight));
+ render_common(mpNotebookHeaderTabsTabStyle, cr, aRect, flags);
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (pBgCssProvider)
+ {
+ gtk_style_context_remove_provider(context, GTK_STYLE_PROVIDER(pBgCssProvider));
+ }
+ aContextState.restore();
+
+ cairo_destroy(cr); // unref
+
+ if (!rControlRegion.IsEmpty())
+ mpFrame->damaged(rControlRegion.Left(), rControlRegion.Top(), rControlRegion.GetWidth(), rControlRegion.GetHeight());
+
+ return true;
+}
+
+static tools::Rectangle GetWidgetSize(const tools::Rectangle& rControlRegion, GtkWidget* widget)
+{
+ GtkRequisition aReq;
+ gtk_widget_get_preferred_size(widget, nullptr, &aReq);
+ tools::Long nHeight = std::max<tools::Long>(rControlRegion.GetHeight(), aReq.height);
+ return tools::Rectangle(rControlRegion.TopLeft(), Size(rControlRegion.GetWidth(), nHeight));
+}
+
+static tools::Rectangle AdjustRectForTextBordersPadding(GtkStyleContext* pStyle, tools::Long nContentWidth, tools::Long nContentHeight, const tools::Rectangle& rControlRegion)
+{
+ GtkBorder border;
+ style_context_get_border(pStyle, &border);
+
+ GtkBorder padding;
+ style_context_get_padding(pStyle, &padding);
+
+ gint nWidgetHeight = nContentHeight + padding.top + padding.bottom + border.top + border.bottom;
+ nWidgetHeight = std::max(std::max<gint>(nWidgetHeight, rControlRegion.GetHeight()), 34);
+
+ gint nWidgetWidth = nContentWidth + padding.left + padding.right + border.left + border.right;
+ nWidgetWidth = std::max<gint>(nWidgetWidth, rControlRegion.GetWidth());
+
+ tools::Rectangle aEditRect(rControlRegion.TopLeft(), Size(nWidgetWidth, nWidgetHeight));
+
+ return aEditRect;
+}
+
+bool GtkSalGraphics::getNativeControlRegion( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion, ControlState,
+ const ImplControlValue& rValue, const OUString&,
+ tools::Rectangle &rNativeBoundingRegion, tools::Rectangle &rNativeContentRegion )
+{
+ /* TODO: all this functions needs improvements */
+ tools::Rectangle aEditRect = rControlRegion;
+ gint indicator_size, indicator_spacing;
+
+ if(((nType == ControlType::Checkbox) || (nType == ControlType::Radiobutton)) &&
+ nPart == ControlPart::Entire)
+ {
+ rNativeBoundingRegion = rControlRegion;
+
+ GtkStyleContext *pButtonStyle = (nType == ControlType::Checkbox) ? mpCheckButtonCheckStyle : mpRadioButtonRadioStyle;
+
+
+ gtk_style_context_get_style( pButtonStyle,
+ "indicator-size", &indicator_size,
+ "indicator-spacing", &indicator_spacing,
+ nullptr );
+
+ GtkBorder border;
+ style_context_get_border(pButtonStyle, &border);
+
+ GtkBorder padding;
+ style_context_get_padding(pButtonStyle, &padding);
+
+
+ indicator_size += 2*indicator_spacing + border.left + padding.left + border.right + padding.right;
+ tools::Rectangle aIndicatorRect( Point( 0,
+ (rControlRegion.GetHeight()-indicator_size)/2),
+ Size( indicator_size, indicator_size ) );
+ rNativeContentRegion = aIndicatorRect;
+
+ return true;
+ }
+ else if( nType == ControlType::MenuPopup)
+ {
+ if ((nPart == ControlPart::MenuItemCheckMark) ||
+ (nPart == ControlPart::MenuItemRadioMark) )
+ {
+ indicator_size = 0;
+
+ GtkStyleContext *pMenuItemStyle = (nPart == ControlPart::MenuItemCheckMark ) ? mpCheckMenuItemCheckStyle
+ : mpRadioMenuItemRadioStyle;
+
+ gtk_style_context_get_style( pMenuItemStyle,
+ "indicator-size", &indicator_size,
+ nullptr );
+
+ gint point = MAX(0, rControlRegion.GetHeight() - indicator_size);
+ aEditRect = tools::Rectangle( Point( 0, point / 2),
+ Size( indicator_size, indicator_size ) );
+ }
+ else if (nPart == ControlPart::Separator)
+ {
+ gint separator_height, separator_width, wide_separators;
+
+ gtk_style_context_get_style (mpSeparatorMenuItemSeparatorStyle,
+ "wide-separators", &wide_separators,
+ "separator-width", &separator_width,
+ "separator-height", &separator_height,
+ nullptr);
+
+ aEditRect = tools::Rectangle( aEditRect.TopLeft(),
+ Size( aEditRect.GetWidth(), wide_separators ? separator_height : 1 ) );
+ }
+ else if (nPart == ControlPart::SubmenuArrow)
+ {
+ gfloat arrow_size = getArrowSize(mpMenuItemArrowStyle);
+ aEditRect = tools::Rectangle( aEditRect.TopLeft(),
+ Size( arrow_size, arrow_size ) );
+ }
+ }
+ else if ( (nType==ControlType::Scrollbar) &&
+ ((nPart==ControlPart::ButtonLeft) || (nPart==ControlPart::ButtonRight) ||
+ (nPart==ControlPart::ButtonUp) || (nPart==ControlPart::ButtonDown) ) )
+ {
+ rNativeBoundingRegion = NWGetScrollButtonRect( nPart, rControlRegion );
+ rNativeContentRegion = rNativeBoundingRegion;
+
+ if (!rNativeContentRegion.GetWidth())
+ rNativeContentRegion.SetRight( rNativeContentRegion.Left() + 1 );
+ if (!rNativeContentRegion.GetHeight())
+ rNativeContentRegion.SetBottom( rNativeContentRegion.Top() + 1 );
+
+ return true;
+ }
+ else if ( (nType==ControlType::Spinbox) &&
+ ((nPart==ControlPart::ButtonUp) || (nPart==ControlPart::ButtonDown) ||
+ (nPart==ControlPart::SubEdit)) )
+ {
+ tools::Rectangle aControlRegion(GetWidgetSize(rControlRegion, gSpinBox));
+ aEditRect = NWGetSpinButtonRect(nPart, aControlRegion);
+ }
+ else if ( (nType==ControlType::Combobox) &&
+ ((nPart==ControlPart::ButtonDown) || (nPart==ControlPart::SubEdit)) )
+ {
+ aEditRect = NWGetComboBoxButtonRect(nType, nPart, rControlRegion);
+ }
+ else if ( (nType==ControlType::Listbox) &&
+ ((nPart==ControlPart::ButtonDown) || (nPart==ControlPart::SubEdit)) )
+ {
+ aEditRect = NWGetComboBoxButtonRect(nType, nPart, rControlRegion);
+ }
+ else if (nType == ControlType::Editbox && nPart == ControlPart::Entire)
+ {
+ aEditRect = GetWidgetSize(rControlRegion, gEntryBox);
+ }
+ else if (nType == ControlType::Listbox && nPart == ControlPart::Entire)
+ {
+ aEditRect = GetWidgetSize(rControlRegion, gListBox);
+ }
+ else if (nType == ControlType::Combobox && nPart == ControlPart::Entire)
+ {
+ aEditRect = GetWidgetSize(rControlRegion, gComboBox);
+ }
+ else if (nType == ControlType::Spinbox && nPart == ControlPart::Entire)
+ {
+ aEditRect = GetWidgetSize(rControlRegion, gSpinBox);
+ }
+ else if (nType == ControlType::TabItem && nPart == ControlPart::Entire)
+ {
+ const TabitemValue& rTabitemValue = static_cast<const TabitemValue&>(rValue);
+ const tools::Rectangle& rTabitemRect = rTabitemValue.getContentRect();
+
+ aEditRect = AdjustRectForTextBordersPadding(mpNotebookHeaderTabsTabStyle, rTabitemRect.GetWidth(),
+ rTabitemRect.GetHeight(), rControlRegion);
+ }
+ else if (nType == ControlType::Frame && nPart == ControlPart::Border)
+ {
+ aEditRect = rControlRegion;
+
+ GtkBorder padding;
+ style_context_get_padding(mpFrameInStyle, &padding);
+
+ GtkBorder border;
+ style_context_get_border(mpFrameInStyle, &border);
+
+ int x1 = aEditRect.Left();
+ int y1 = aEditRect.Top();
+ int x2 = aEditRect.Right();
+ int y2 = aEditRect.Bottom();
+
+ rNativeBoundingRegion = aEditRect;
+ rNativeContentRegion = tools::Rectangle(x1 + (padding.left + border.left),
+ y1 + (padding.top + border.top),
+ x2 - (padding.right + border.right),
+ y2 - (padding.bottom + border.bottom));
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+
+ rNativeBoundingRegion = aEditRect;
+ rNativeContentRegion = rNativeBoundingRegion;
+
+ return true;
+}
+#endif
+
+/************************************************************************
+ * helper for GtkSalFrame
+ ************************************************************************/
+static ::Color getColor( const GdkRGBA& rCol )
+{
+ return ::Color( static_cast<int>(rCol.red * 0xFFFF) >> 8, static_cast<int>(rCol.green * 0xFFFF) >> 8, static_cast<int>(rCol.blue * 0xFFFF) >> 8 );
+}
+
+static ::Color style_context_get_background_color(GtkStyleContext* pStyle)
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkRGBA background_color;
+ gtk_style_context_get_background_color(pStyle, gtk_style_context_get_state(pStyle), &background_color);
+ return getColor(background_color);
+#else
+ cairo_surface_t *target = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
+ cairo_t* cr = cairo_create(target);
+ gtk_render_background(pStyle, cr, 0, 0, 1, 1);
+ cairo_destroy(cr);
+
+ cairo_surface_flush(target);
+ vcl::bitmap::lookup_table const & unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
+ unsigned char *data = cairo_image_surface_get_data(target);
+ sal_uInt8 a = data[SVP_CAIRO_ALPHA];
+ sal_uInt8 b = unpremultiply_table[a][data[SVP_CAIRO_BLUE]];
+ sal_uInt8 g = unpremultiply_table[a][data[SVP_CAIRO_GREEN]];
+ sal_uInt8 r = unpremultiply_table[a][data[SVP_CAIRO_RED]];
+ Color aColor(r, g, b);
+ cairo_surface_destroy(target);
+
+ return aColor;
+#endif
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+static vcl::Font getFont(GtkStyleContext* pStyle, const css::lang::Locale& rLocale)
+{
+ const PangoFontDescription* font = gtk_style_context_get_font(pStyle, gtk_style_context_get_state(pStyle));
+ return pango_to_vcl(font, rLocale);
+}
+#endif
+
+vcl::Font pango_to_vcl(const PangoFontDescription* font, const css::lang::Locale& rLocale)
+{
+ OString aFamily = pango_font_description_get_family( font );
+ PangoStyle eStyle = pango_font_description_get_style( font );
+ PangoWeight eWeight = pango_font_description_get_weight( font );
+ PangoStretch eStretch = pango_font_description_get_stretch( font );
+
+ FontAttributes aDFA;
+
+ // set family name
+ aDFA.SetFamilyName(OStringToOUString(aFamily, RTL_TEXTENCODING_UTF8));
+
+ // set italic
+ switch( eStyle )
+ {
+ case PANGO_STYLE_NORMAL: aDFA.SetItalic(ITALIC_NONE);break;
+ case PANGO_STYLE_ITALIC: aDFA.SetItalic(ITALIC_NORMAL);break;
+ case PANGO_STYLE_OBLIQUE: aDFA.SetItalic(ITALIC_OBLIQUE);break;
+ }
+
+ // set weight
+ if( eWeight <= PANGO_WEIGHT_ULTRALIGHT )
+ aDFA.SetWeight(WEIGHT_ULTRALIGHT);
+ else if( eWeight <= PANGO_WEIGHT_LIGHT )
+ aDFA.SetWeight(WEIGHT_LIGHT);
+ else if( eWeight <= PANGO_WEIGHT_NORMAL )
+ aDFA.SetWeight(WEIGHT_NORMAL);
+ else if( eWeight <= PANGO_WEIGHT_BOLD )
+ aDFA.SetWeight(WEIGHT_BOLD);
+ else
+ aDFA.SetWeight(WEIGHT_ULTRABOLD);
+
+ // set width
+ switch( eStretch )
+ {
+ case PANGO_STRETCH_ULTRA_CONDENSED: aDFA.SetWidthType(WIDTH_ULTRA_CONDENSED);break;
+ case PANGO_STRETCH_EXTRA_CONDENSED: aDFA.SetWidthType(WIDTH_EXTRA_CONDENSED);break;
+ case PANGO_STRETCH_CONDENSED: aDFA.SetWidthType(WIDTH_CONDENSED);break;
+ case PANGO_STRETCH_SEMI_CONDENSED: aDFA.SetWidthType(WIDTH_SEMI_CONDENSED);break;
+ case PANGO_STRETCH_NORMAL: aDFA.SetWidthType(WIDTH_NORMAL);break;
+ case PANGO_STRETCH_SEMI_EXPANDED: aDFA.SetWidthType(WIDTH_SEMI_EXPANDED);break;
+ case PANGO_STRETCH_EXPANDED: aDFA.SetWidthType(WIDTH_EXPANDED);break;
+ case PANGO_STRETCH_EXTRA_EXPANDED: aDFA.SetWidthType(WIDTH_EXTRA_EXPANDED);break;
+ case PANGO_STRETCH_ULTRA_EXPANDED: aDFA.SetWidthType(WIDTH_ULTRA_EXPANDED);break;
+ }
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.gtk3", "font name BEFORE system match: \""
+ << aFamily << "\".");
+#endif
+
+ // match font to e.g. resolve "Sans"
+ bool bFound = psp::PrintFontManager::get().matchFont(aDFA, rLocale);
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.gtk3", "font match "
+ << (bFound ? "succeeded" : "failed")
+ << ", name AFTER: \""
+ << aDFA.GetFamilyName()
+ << "\".");
+#else
+ (void) bFound;
+#endif
+
+ int nPangoHeight = pango_font_description_get_size(font) / PANGO_SCALE;
+
+ if (pango_font_description_get_size_is_absolute(font))
+ {
+ const sal_Int32 nDPIY = 96;
+ nPangoHeight = nPangoHeight * 72;
+ nPangoHeight = nPangoHeight + nDPIY / 2;
+ nPangoHeight = nPangoHeight / nDPIY;
+ }
+
+ vcl::Font aFont(aDFA.GetFamilyName(), Size(0, nPangoHeight));
+ if (aDFA.GetWeight() != WEIGHT_DONTKNOW)
+ aFont.SetWeight(aDFA.GetWeight());
+ if (aDFA.GetWidthType() != WIDTH_DONTKNOW)
+ aFont.SetWidthType(aDFA.GetWidthType());
+ if (aDFA.GetItalic() != ITALIC_DONTKNOW)
+ aFont.SetItalic(aDFA.GetItalic());
+ if (aDFA.GetPitch() != PITCH_DONTKNOW)
+ aFont.SetPitch(aDFA.GetPitch());
+ return aFont;
+}
+
+bool GtkSalGraphics::updateSettings(AllSettings& rSettings)
+{
+ GtkWidget* pTopLevel = widget_get_toplevel(mpWindow);
+ GtkStyleContext* pStyle = gtk_widget_get_style_context(pTopLevel);
+ StyleContextSave aContextState;
+ aContextState.save(pStyle);
+ GtkSettings* pSettings = gtk_widget_get_settings(pTopLevel);
+ StyleSettings aStyleSet = rSettings.GetStyleSettings();
+
+ // text colors
+ GdkRGBA text_color;
+ style_context_set_state(pStyle, GTK_STATE_FLAG_NORMAL);
+ style_context_get_color(pStyle, &text_color);
+ ::Color aTextColor = getColor( text_color );
+ aStyleSet.SetDialogTextColor( aTextColor );
+ aStyleSet.SetButtonTextColor( aTextColor );
+ aStyleSet.SetDefaultActionButtonTextColor(aTextColor);
+ aStyleSet.SetActionButtonTextColor(aTextColor);
+ aStyleSet.SetListBoxWindowTextColor( aTextColor );
+ aStyleSet.SetRadioCheckTextColor( aTextColor );
+ aStyleSet.SetGroupTextColor( aTextColor );
+ aStyleSet.SetLabelTextColor( aTextColor );
+ aStyleSet.SetWindowTextColor( aTextColor );
+ aStyleSet.SetFieldTextColor( aTextColor );
+
+ // background colors
+ ::Color aBackColor = style_context_get_background_color(pStyle);
+ aStyleSet.BatchSetBackgrounds( aBackColor );
+
+ // UI font
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gchar* pFontname = nullptr;
+ g_object_get(pSettings, "gtk-font-name", &pFontname, nullptr);
+ PangoFontDescription* pFontDesc = pango_font_description_from_string(pFontname);
+ vcl::Font aFont(pango_to_vcl(pFontDesc, rSettings.GetUILanguageTag().getLocale()));
+ pango_font_description_free(pFontDesc);
+#else
+ vcl::Font aFont(getFont(pStyle, rSettings.GetUILanguageTag().getLocale()));
+#endif
+
+ aStyleSet.BatchSetFonts( aFont, aFont);
+
+ aFont.SetWeight( WEIGHT_BOLD );
+ aStyleSet.SetTitleFont( aFont );
+ aStyleSet.SetFloatTitleFont( aFont );
+
+ // mouse over text colors
+ style_context_set_state(pStyle, GTK_STATE_FLAG_PRELIGHT);
+ style_context_get_color(pStyle, &text_color);
+ aTextColor = getColor(text_color);
+ aStyleSet.SetDefaultButtonTextColor(aTextColor);
+ aStyleSet.SetDefaultButtonRolloverTextColor(aTextColor);
+ aStyleSet.SetDefaultButtonPressedRolloverTextColor(aTextColor);
+ aStyleSet.SetButtonRolloverTextColor(aTextColor);
+ aStyleSet.SetDefaultActionButtonRolloverTextColor(aTextColor);
+ aStyleSet.SetDefaultActionButtonPressedRolloverTextColor(aTextColor);
+ aStyleSet.SetActionButtonRolloverTextColor(aTextColor);
+ aStyleSet.SetActionButtonPressedRolloverTextColor(aTextColor);
+ aStyleSet.SetFlatButtonTextColor(aTextColor);
+ aStyleSet.SetFlatButtonPressedRolloverTextColor(aTextColor);
+ aStyleSet.SetFlatButtonRolloverTextColor(aTextColor);
+ aStyleSet.SetFieldRolloverTextColor(aTextColor);
+
+ aContextState.restore();
+
+ // button mouse over colors
+ {
+ GdkRGBA normal_button_rollover_text_color, pressed_button_rollover_text_color;
+ aContextState.save(mpButtonStyle);
+ style_context_set_state(mpButtonStyle, GTK_STATE_FLAG_PRELIGHT);
+ style_context_get_color(mpButtonStyle, &normal_button_rollover_text_color);
+ aTextColor = getColor(normal_button_rollover_text_color);
+ aStyleSet.SetButtonRolloverTextColor( aTextColor );
+ style_context_set_state(mpButtonStyle, static_cast<GtkStateFlags>(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE));
+ style_context_get_color(mpButtonStyle, &pressed_button_rollover_text_color);
+ aTextColor = getColor(pressed_button_rollover_text_color);
+ style_context_set_state(mpButtonStyle, GTK_STATE_FLAG_NORMAL);
+ aStyleSet.SetButtonPressedRolloverTextColor( aTextColor );
+ aContextState.restore();
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // tooltip colors
+ {
+ GtkWidgetPath *pCPath = gtk_widget_path_new();
+ guint pos = gtk_widget_path_append_type(pCPath, GTK_TYPE_WINDOW);
+ gtk_widget_path_iter_add_class(pCPath, pos, GTK_STYLE_CLASS_TOOLTIP);
+ pos = gtk_widget_path_append_type (pCPath, GTK_TYPE_LABEL);
+ gtk_widget_path_iter_add_class(pCPath, pos, GTK_STYLE_CLASS_LABEL);
+ GtkStyleContext *pCStyle = makeContext (pCPath, nullptr);
+ aContextState.save(pCStyle);
+
+ GdkRGBA tooltip_fg_color;
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_NORMAL);
+ style_context_get_color(pCStyle, &tooltip_fg_color);
+ ::Color aTooltipBgColor = style_context_get_background_color(pCStyle);
+
+ aContextState.restore();
+ g_object_unref( pCStyle );
+
+ aStyleSet.SetHelpColor(aTooltipBgColor);
+ aStyleSet.SetHelpTextColor( getColor( tooltip_fg_color ));
+ }
+#endif
+
+ GdkRGBA color;
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // construct style context for text view
+ GtkWidgetPath *pCPath = gtk_widget_path_new();
+ gtk_widget_path_append_type( pCPath, GTK_TYPE_TEXT_VIEW );
+ gtk_widget_path_iter_add_class( pCPath, -1, GTK_STYLE_CLASS_VIEW );
+ GtkStyleContext *pCStyle = makeContext( pCPath, nullptr );
+#else
+ GtkStyleContext *pCStyle = gtk_widget_get_style_context(gTextView);
+#endif
+ aContextState.save(pCStyle);
+
+ // highlighting colors
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_SELECTED);
+ ::Color aHighlightColor = style_context_get_background_color(pCStyle);
+ style_context_get_color(pCStyle, &text_color);
+ ::Color aHighlightTextColor = getColor( text_color );
+ aStyleSet.SetAccentColor( aHighlightColor ); // https://debugpointnews.com/gnome-native-accent-colour-announcement/
+ aStyleSet.SetHighlightColor( aHighlightColor );
+ aStyleSet.SetHighlightTextColor( aHighlightTextColor );
+ aStyleSet.SetListBoxWindowHighlightColor( aHighlightColor );
+ aStyleSet.SetListBoxWindowHighlightTextColor( aHighlightTextColor );
+ // make active like highlight, except with a small contrast. Note, see
+ // a GtkListBoxRow in a GtkStackSidebar for a gtk widget with a
+ // difference between highlighted and highlighted with focus.
+ aHighlightColor.IncreaseLuminance(16);
+ aStyleSet.SetActiveColor( aHighlightColor );
+ aStyleSet.SetActiveTextColor( aHighlightTextColor );
+
+ // warning color
+ GdkRGBA warning_color;
+ if (gtk_style_context_lookup_color(pCStyle, "warning_color", &warning_color))
+ aStyleSet.SetWarningColor(getColor(warning_color));
+
+ // field background color
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_NORMAL);
+ ::Color aBackFieldColor = style_context_get_background_color(pCStyle);
+ aStyleSet.SetFieldColor( aBackFieldColor );
+ // This baby is the default page/paper color
+ aStyleSet.SetWindowColor( aBackFieldColor );
+ // listbox background color
+ aStyleSet.SetListBoxWindowBackgroundColor( aBackFieldColor );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ double caretAspectRatio = 0.04f;
+ g_object_get(pSettings, "gtk-cursor-aspect-ratio", &caretAspectRatio, nullptr);
+#else
+ // Cursor width
+ gfloat caretAspectRatio = 0.04f;
+ gtk_style_context_get_style( pCStyle, "cursor-aspect-ratio", &caretAspectRatio, nullptr );
+#endif
+ // Assume 20px tall for the ratio computation, which should give reasonable results
+ aStyleSet.SetCursorSize(20 * caretAspectRatio + 1);
+
+ // Dark shadow color
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_INSENSITIVE);
+ style_context_get_color(pCStyle, &color);
+ ::Color aDarkShadowColor = getColor( color );
+ aStyleSet.SetDarkShadowColor( aDarkShadowColor );
+
+ ::Color aShadowColor(aBackColor);
+ if (aDarkShadowColor.GetLuminance() > aBackColor.GetLuminance())
+ aShadowColor.IncreaseLuminance(64);
+ else
+ aShadowColor.DecreaseLuminance(64);
+ aStyleSet.SetShadowColor(aShadowColor);
+
+ aContextState.restore();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_object_unref( pCStyle );
+#endif
+
+ // Tab colors
+ aStyleSet.SetActiveTabColor( aBackFieldColor ); // same as the window color.
+ aStyleSet.SetInactiveTabColor( aBackColor );
+ }
+
+ // menu disabled entries handling
+ aStyleSet.SetSkipDisabledInMenus( true );
+ aStyleSet.SetPreferredContextMenuShortcuts( false );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ aContextState.save(mpMenuItemLabelStyle);
+
+ // menu colors
+ style_context_set_state(mpMenuStyle, GTK_STATE_FLAG_NORMAL);
+ aBackColor = style_context_get_background_color(mpMenuStyle);
+ aStyleSet.SetMenuColor( aBackColor );
+
+ // menu bar
+ style_context_set_state(mpMenuBarStyle, GTK_STATE_FLAG_NORMAL);
+ aBackColor = style_context_get_background_color(mpMenuBarStyle);
+ aStyleSet.SetMenuBarColor( aBackColor );
+ aStyleSet.SetMenuBarRolloverColor( aBackColor );
+
+ style_context_set_state(mpMenuBarItemStyle, GTK_STATE_FLAG_NORMAL);
+ style_context_get_color(mpMenuBarItemStyle, &text_color);
+ aTextColor = aStyleSet.GetPersonaMenuBarTextColor().value_or( getColor( text_color ) );
+ aStyleSet.SetMenuBarTextColor( aTextColor );
+ aStyleSet.SetMenuBarRolloverTextColor( aTextColor );
+
+ style_context_set_state(mpMenuBarItemStyle, GTK_STATE_FLAG_PRELIGHT);
+ style_context_get_color(mpMenuBarItemStyle, &text_color);
+ aTextColor = aStyleSet.GetPersonaMenuBarTextColor().value_or( getColor( text_color ) );
+ aStyleSet.SetMenuBarHighlightTextColor( aTextColor );
+
+ // menu items
+ style_context_set_state(mpMenuItemLabelStyle, GTK_STATE_FLAG_NORMAL);
+ style_context_get_color(mpMenuItemLabelStyle, &color);
+ aTextColor = getColor(color);
+ aStyleSet.SetMenuTextColor(aTextColor);
+
+ style_context_set_state(mpMenuItemLabelStyle, GTK_STATE_FLAG_PRELIGHT);
+ ::Color aHighlightColor = style_context_get_background_color(mpMenuItemLabelStyle);
+ aStyleSet.SetMenuHighlightColor( aHighlightColor );
+
+ style_context_get_color(mpMenuItemLabelStyle, &color);
+ ::Color aHighlightTextColor = getColor( color );
+ aStyleSet.SetMenuHighlightTextColor( aHighlightTextColor );
+
+ aContextState.restore();
+#endif
+
+ // hyperlink colors
+ aContextState.save(mpLinkButtonStyle);
+ style_context_set_state(mpLinkButtonStyle, GTK_STATE_FLAG_LINK);
+ style_context_get_color(mpLinkButtonStyle, &text_color);
+ aStyleSet.SetLinkColor(getColor(text_color));
+ style_context_set_state(mpLinkButtonStyle, GTK_STATE_FLAG_VISITED);
+ style_context_get_color(mpLinkButtonStyle, &text_color);
+ aStyleSet.SetVisitedLinkColor(getColor(text_color));
+ aContextState.restore();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ {
+ GtkStyleContext *pCStyle = mpNotebookHeaderTabsTabLabelStyle;
+ aContextState.save(pCStyle);
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_NORMAL);
+ style_context_get_color(pCStyle, &text_color);
+ aTextColor = getColor( text_color );
+ aStyleSet.SetTabTextColor(aTextColor);
+ aStyleSet.SetTabFont(getFont(mpNotebookHeaderTabsTabLabelStyle, rSettings.GetUILanguageTag().getLocale()));
+ aContextState.restore();
+ }
+
+ {
+ GtkStyleContext *pCStyle = mpToolButtonStyle;
+ aContextState.save(pCStyle);
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_NORMAL);
+ style_context_get_color(pCStyle, &text_color);
+ aTextColor = getColor( text_color );
+ aStyleSet.SetToolTextColor(aTextColor);
+ aStyleSet.SetToolFont(getFont(mpToolButtonStyle, rSettings.GetUILanguageTag().getLocale()));
+ aContextState.restore();
+ }
+
+ // mouse over text colors
+ {
+ GtkStyleContext *pCStyle = mpNotebookHeaderTabsTabHoverLabelStyle;
+ aContextState.save(pCStyle);
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_PRELIGHT);
+ style_context_get_color(pCStyle, &text_color);
+ aTextColor = getColor( text_color );
+ aStyleSet.SetTabRolloverTextColor(aTextColor);
+ aContextState.restore();
+ }
+
+ {
+ GtkStyleContext *pCStyle = mpNotebookHeaderTabsTabActiveLabelStyle;
+ aContextState.save(pCStyle);
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_CHECKED);
+ style_context_get_color(pCStyle, &text_color);
+ aTextColor = getColor( text_color );
+ aStyleSet.SetTabHighlightTextColor(aTextColor);
+ aContextState.restore();
+ }
+#endif
+
+ // get cursor blink time
+ gboolean blink = false;
+
+ g_object_get( pSettings, "gtk-cursor-blink", &blink, nullptr );
+ if( blink )
+ {
+ gint blink_time = static_cast<gint>(STYLE_CURSOR_NOBLINKTIME);
+ g_object_get( pSettings, "gtk-cursor-blink-time", &blink_time, nullptr );
+ // set the blink_time if there is a setting and it is reasonable
+ // else leave the default value
+ if( blink_time > 100 )
+ aStyleSet.SetCursorBlinkTime( blink_time/2 );
+ }
+ else
+ aStyleSet.SetCursorBlinkTime( STYLE_CURSOR_NOBLINKTIME );
+
+ MouseSettings aMouseSettings = rSettings.GetMouseSettings();
+ int iDoubleClickTime, iDoubleClickDistance, iDragThreshold;
+ static const int MENU_POPUP_DELAY = 225;
+ g_object_get( pSettings,
+ "gtk-double-click-time", &iDoubleClickTime,
+ "gtk-double-click-distance", &iDoubleClickDistance,
+ "gtk-dnd-drag-threshold", &iDragThreshold,
+ nullptr );
+ aMouseSettings.SetDoubleClickTime( iDoubleClickTime );
+ aMouseSettings.SetDoubleClickWidth( iDoubleClickDistance );
+ aMouseSettings.SetDoubleClickHeight( iDoubleClickDistance );
+ aMouseSettings.SetStartDragWidth( iDragThreshold );
+ aMouseSettings.SetStartDragHeight( iDragThreshold );
+ aMouseSettings.SetMenuDelay( MENU_POPUP_DELAY );
+ rSettings.SetMouseSettings( aMouseSettings );
+
+ gboolean primarybuttonwarps = false;
+ g_object_get( pSettings,
+ "gtk-primary-button-warps-slider", &primarybuttonwarps,
+ nullptr );
+ aStyleSet.SetPreferredUseImagesInMenus(false);
+ aStyleSet.SetPrimaryButtonWarpsSlider(primarybuttonwarps);
+
+ // set scrollbar settings
+ gint min_slider_length = 21;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkRequisition natural_size;
+ gtk_widget_get_preferred_size(gHScrollbar, nullptr, &natural_size);
+ aStyleSet.SetScrollBarSize(natural_size.height);
+#else
+ // Grab some button style attributes
+ Size aSize;
+ QuerySize(mpHScrollbarStyle, aSize);
+ QuerySize(mpHScrollbarContentsStyle, aSize);
+ QuerySize(mpHScrollbarTroughStyle, aSize);
+ QuerySize(mpHScrollbarSliderStyle, aSize);
+
+ gboolean has_forward, has_forward2, has_backward, has_backward2;
+ gtk_style_context_get_style(mpHScrollbarStyle,
+ "has-forward-stepper", &has_forward,
+ "has-secondary-forward-stepper", &has_forward2,
+ "has-backward-stepper", &has_backward,
+ "has-secondary-backward-stepper", &has_backward2, nullptr);
+ if (has_forward || has_backward || has_forward2 || has_backward2)
+ QuerySize(mpHScrollbarButtonStyle, aSize);
+
+ aStyleSet.SetScrollBarSize(aSize.Height());
+
+ gtk_style_context_get(mpVScrollbarSliderStyle, gtk_style_context_get_state(mpVScrollbarSliderStyle),
+ "min-height", &min_slider_length,
+ nullptr);
+#endif
+ aStyleSet.SetMinThumbSize(min_slider_length);
+
+ // preferred icon style
+ gchar* pIconThemeName = nullptr;
+ gboolean bDarkIconTheme = false;
+ g_object_get(pSettings, "gtk-icon-theme-name", &pIconThemeName,
+ "gtk-application-prefer-dark-theme", &bDarkIconTheme,
+ nullptr );
+ OUString sIconThemeName(OUString::createFromAscii(pIconThemeName));
+ aStyleSet.SetPreferredIconTheme(sIconThemeName, bDarkIconTheme);
+ g_free( pIconThemeName );
+
+ aStyleSet.SetToolbarIconSize( ToolbarIconSize::Large );
+
+ gchar* pThemeName = nullptr;
+ g_object_get( pSettings, "gtk-theme-name", &pThemeName, nullptr );
+ SAL_INFO("vcl.gtk3", "Theme name is \""
+ << pThemeName
+ << "\".");
+ // High contrast
+ aStyleSet.SetHighContrastMode(g_strcmp0(pThemeName, "HighContrast") == 0);
+ g_free(pThemeName);
+
+ // finally update the collected settings
+ rSettings.SetStyleSettings( aStyleSet );
+
+ return true;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+bool GtkSalGraphics::isNativeControlSupported( ControlType nType, ControlPart nPart )
+{
+ switch(nType)
+ {
+ case ControlType::Pushbutton:
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ case ControlType::Progress:
+ case ControlType::ListNode:
+ case ControlType::ListNet:
+ if (nPart==ControlPart::Entire || nPart == ControlPart::Focus)
+ return true;
+ break;
+
+ case ControlType::Scrollbar:
+ if(nPart==ControlPart::DrawBackgroundHorz || nPart==ControlPart::DrawBackgroundVert ||
+ nPart==ControlPart::Entire || nPart==ControlPart::HasThreeButtons)
+ return true;
+ break;
+
+ case ControlType::Editbox:
+ case ControlType::MultilineEditbox:
+ if (nPart==ControlPart::Entire || nPart==ControlPart::HasBackgroundTexture)
+ return true;
+ break;
+
+ case ControlType::Combobox:
+ if (nPart==ControlPart::Entire || nPart==ControlPart::HasBackgroundTexture || nPart == ControlPart::AllButtons)
+ return true;
+ break;
+
+ case ControlType::Spinbox:
+ if (nPart==ControlPart::Entire || nPart==ControlPart::HasBackgroundTexture || nPart == ControlPart::AllButtons || nPart == ControlPart::ButtonUp || nPart == ControlPart::ButtonDown)
+ return true;
+ break;
+
+ case ControlType::SpinButtons:
+ if (nPart==ControlPart::Entire || nPart==ControlPart::AllButtons)
+ return true;
+ break;
+
+ case ControlType::Frame:
+ case ControlType::WindowBackground:
+ return true;
+
+ case ControlType::TabItem:
+ case ControlType::TabHeader:
+ case ControlType::TabPane:
+ case ControlType::TabBody:
+ if(nPart==ControlPart::Entire || nPart==ControlPart::TabsDrawRtl)
+ return true;
+ break;
+
+ case ControlType::Listbox:
+ if (nPart==ControlPart::Entire || nPart==ControlPart::ListboxWindow || nPart==ControlPart::HasBackgroundTexture || nPart == ControlPart::Focus)
+ return true;
+ break;
+
+ case ControlType::Toolbar:
+ if( nPart==ControlPart::Entire
+// || nPart==ControlPart::DrawBackgroundHorz
+// || nPart==ControlPart::DrawBackgroundVert
+// || nPart==ControlPart::ThumbHorz
+// || nPart==ControlPart::ThumbVert
+ || nPart==ControlPart::Button
+// || nPart==ControlPart::SeparatorHorz
+ || nPart==ControlPart::SeparatorVert
+ )
+ return true;
+ break;
+
+ case ControlType::Menubar:
+ if (nPart==ControlPart::Entire || nPart==ControlPart::MenuItem)
+ return true;
+ break;
+
+ case ControlType::MenuPopup:
+ if (nPart==ControlPart::Entire
+ || nPart==ControlPart::MenuItem
+ || nPart==ControlPart::MenuItemCheckMark
+ || nPart==ControlPart::MenuItemRadioMark
+ || nPart==ControlPart::Separator
+ || nPart==ControlPart::SubmenuArrow
+ )
+ return true;
+ break;
+
+// case ControlType::Slider:
+// if(nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea)
+// return true;
+// break;
+
+ case ControlType::Fixedline:
+ if (nPart == ControlPart::SeparatorVert || nPart == ControlPart::SeparatorHorz)
+ return true;
+ break;
+
+ case ControlType::ListHeader:
+ if (nPart == ControlPart::Button || nPart == ControlPart::Arrow)
+ return true;
+ break;
+ default: break;
+ }
+
+ SAL_INFO("vcl.gtk", "Unhandled is native supported for Type:" << static_cast<int>(nType) << ", Part" << static_cast<int>(nPart));
+ return false;
+}
+#endif
+
+#if ENABLE_CAIRO_CANVAS
+
+bool GtkSalGraphics::SupportsCairo() const
+{
+ return true;
+}
+
+cairo::SurfaceSharedPtr GtkSalGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const
+{
+ return std::make_shared<cairo::Gtk3Surface>(rSurface);
+}
+
+cairo::SurfaceSharedPtr GtkSalGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int x, int y, int width, int height) const
+{
+ return std::make_shared<cairo::Gtk3Surface>(this, x, y, width, height);
+}
+
+#endif
+
+void GtkSalGraphics::WidgetQueueDraw() const
+{
+ //request gtk to sync the entire contents
+ mpFrame->queue_draw();
+}
+
+namespace {
+
+void getStyleContext(GtkStyleContext** style, GtkWidget* widget)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_fixed_put(GTK_FIXED(gDumbContainer), widget, 0, 0);
+#else
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), widget);
+#endif
+ *style = gtk_widget_get_style_context(widget);
+ g_object_ref(*style);
+}
+
+}
+
+void GtkSalData::initNWF()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maNWFData.mbFlatMenu = true;
+ pSVData->maNWFData.mbDockingAreaAvoidTBFrames = true;
+ pSVData->maNWFData.mbCanDrawWidgetAnySize = true;
+ pSVData->maNWFData.mbDDListBoxNoTextArea = true;
+ pSVData->maNWFData.mbNoFocusRects = true;
+ pSVData->maNWFData.mbNoFocusRectsForFlatButtons = true;
+ pSVData->maNWFData.mbAutoAccel = true;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ pSVData->maNWFData.mbNoFrameJunctionForPopups = true;
+#endif
+
+#if defined(GDK_WINDOWING_WAYLAND)
+ //gnome#768128 for the car crash that is wayland
+ //and floating dockable toolbars
+ GdkDisplay *pDisplay = gdk_display_get_default();
+ if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
+ pSVData->maNWFData.mbCanDetermineWindowPosition = false;
+#endif
+}
+
+void GtkSalData::deInitNWF()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (gCacheWindow)
+ gtk_widget_destroy(gCacheWindow);
+#endif
+}
+
+GtkSalGraphics::GtkSalGraphics( GtkSalFrame *pFrame, GtkWidget *pWindow )
+ : mpFrame( pFrame ),
+ mpWindow( pWindow )
+{
+ if (style_loaded)
+ return;
+
+ style_loaded = true;
+
+ /* Load the GtkStyleContexts, it might be a bit slow, but usually,
+ * gtk apps create a lot of widgets at startup, so, it shouldn't be
+ * too slow */
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gCacheWindow = gtk_window_new();
+#else
+ gCacheWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+#endif
+ gDumbContainer = gtk_fixed_new();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_window_set_child(GTK_WINDOW(gCacheWindow), gDumbContainer);
+#else
+ gtk_container_add(GTK_CONTAINER(gCacheWindow), gDumbContainer);
+#endif
+ gtk_widget_realize(gDumbContainer);
+ gtk_widget_realize(gCacheWindow);
+
+ gEntryBox = gtk_entry_new();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_fixed_put(GTK_FIXED(gDumbContainer), gEntryBox, 0, 0);
+#else
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gEntryBox);
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mpWindowStyle = createStyleContext(GtkControlPart::ToplevelWindow);
+ mpEntryStyle = createStyleContext(GtkControlPart::Entry);
+#else
+ mpWindowStyle = gtk_widget_get_style_context(gCacheWindow);
+ getStyleContext(&mpEntryStyle, gtk_entry_new());
+#endif
+
+ getStyleContext(&mpTextViewStyle, gtk_text_view_new());
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mpButtonStyle = createStyleContext(GtkControlPart::Button);
+ mpLinkButtonStyle = createStyleContext(GtkControlPart::LinkButton);
+#else
+ getStyleContext(&mpButtonStyle, gtk_button_new());
+ getStyleContext(&mpLinkButtonStyle, gtk_link_button_new("https://www.libreoffice.org"));
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pToolbar = gtk_toolbar_new();
+ mpToolbarStyle = gtk_widget_get_style_context(pToolbar);
+ gtk_style_context_add_class(mpToolbarStyle, GTK_STYLE_CLASS_TOOLBAR);
+
+ GtkToolItem *item = gtk_separator_tool_item_new();
+ gtk_toolbar_insert(GTK_TOOLBAR(pToolbar), item, -1);
+ mpToolbarSeparatorStyle = gtk_widget_get_style_context(GTK_WIDGET(item));
+ gtk_style_context_get(mpToolbarSeparatorStyle,
+ gtk_style_context_get_state(mpToolbarSeparatorStyle),
+ "min-width", &mnVerticalSeparatorMinWidth, nullptr);
+
+ GtkWidget *pButton = gtk_button_new();
+ item = gtk_tool_button_new(pButton, nullptr);
+ gtk_toolbar_insert(GTK_TOOLBAR(pToolbar), item, -1);
+ mpToolButtonStyle = gtk_widget_get_style_context(GTK_WIDGET(pButton));
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gVScrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, nullptr);
+ gtk_fixed_put(GTK_FIXED(gDumbContainer), gVScrollbar, 0, 0);
+ gtk_widget_show(gVScrollbar);
+ mpVScrollbarStyle = gtk_widget_get_style_context(gVScrollbar);
+
+ gHScrollbar = gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, nullptr);
+ gtk_fixed_put(GTK_FIXED(gDumbContainer), gHScrollbar, 0, 0);
+ gtk_widget_show(gHScrollbar);
+ mpHScrollbarStyle = gtk_widget_get_style_context(gHScrollbar);
+
+ gTextView = gtk_text_view_new();
+ gtk_fixed_put(GTK_FIXED(gDumbContainer), gTextView, 0, 0);
+ gtk_widget_show(gTextView);
+#else
+ mpVScrollbarStyle = createStyleContext(GtkControlPart::ScrollbarVertical);
+ mpVScrollbarContentsStyle = createStyleContext(GtkControlPart::ScrollbarVerticalContents);
+ mpVScrollbarTroughStyle = createStyleContext(GtkControlPart::ScrollbarVerticalTrough);
+ mpVScrollbarSliderStyle = createStyleContext(GtkControlPart::ScrollbarVerticalSlider);
+ mpVScrollbarButtonStyle = createStyleContext(GtkControlPart::ScrollbarVerticalButton);
+ mpHScrollbarStyle = createStyleContext(GtkControlPart::ScrollbarHorizontal);
+ mpHScrollbarContentsStyle = createStyleContext(GtkControlPart::ScrollbarHorizontalContents);
+ mpHScrollbarTroughStyle = createStyleContext(GtkControlPart::ScrollbarHorizontalTrough);
+ mpHScrollbarSliderStyle = createStyleContext(GtkControlPart::ScrollbarHorizontalSlider);
+ mpHScrollbarButtonStyle = createStyleContext(GtkControlPart::ScrollbarHorizontalButton);
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mpCheckButtonStyle = createStyleContext(GtkControlPart::CheckButton);
+ mpCheckButtonCheckStyle = createStyleContext(GtkControlPart::CheckButtonCheck);
+
+ mpRadioButtonStyle = createStyleContext(GtkControlPart::RadioButton);
+ mpRadioButtonRadioStyle = createStyleContext(GtkControlPart::RadioButtonRadio);
+
+ /* Spinbutton */
+ gSpinBox = gtk_spin_button_new(nullptr, 0, 0);
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gSpinBox);
+ mpSpinStyle = createStyleContext(GtkControlPart::SpinButton);
+ mpSpinUpStyle = createStyleContext(GtkControlPart::SpinButtonUpButton);
+ mpSpinDownStyle = createStyleContext(GtkControlPart::SpinButtonDownButton);
+
+ /* NoteBook */
+ mpNotebookStyle = createStyleContext(GtkControlPart::Notebook);
+ mpNotebookStackStyle = createStyleContext(GtkControlPart::NotebookStack);
+ mpNotebookHeaderStyle = createStyleContext(GtkControlPart::NotebookHeader);
+ mpNotebookHeaderTabsStyle = createStyleContext(GtkControlPart::NotebookHeaderTabs);
+ mpNotebookHeaderTabsTabStyle = createStyleContext(GtkControlPart::NotebookHeaderTabsTab);
+ mpNotebookHeaderTabsTabLabelStyle = createStyleContext(GtkControlPart::NotebookHeaderTabsTabLabel);
+ mpNotebookHeaderTabsTabActiveLabelStyle = createStyleContext(GtkControlPart::NotebookHeaderTabsTabActiveLabel);
+ mpNotebookHeaderTabsTabHoverLabelStyle = createStyleContext(GtkControlPart::NotebookHeaderTabsTabHoverLabel);
+
+ /* Combobox */
+ gComboBox = gtk_combo_box_text_new_with_entry();
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gComboBox);
+ mpComboboxStyle = createStyleContext(GtkControlPart::Combobox);
+ mpComboboxBoxStyle = createStyleContext(GtkControlPart::ComboboxBox);
+ mpComboboxEntryStyle = createStyleContext(GtkControlPart::ComboboxBoxEntry);
+ mpComboboxButtonStyle = createStyleContext(GtkControlPart::ComboboxBoxButton);
+ mpComboboxButtonBoxStyle = createStyleContext(GtkControlPart::ComboboxBoxButtonBox);
+ mpComboboxButtonArrowStyle = createStyleContext(GtkControlPart::ComboboxBoxButtonBoxArrow);
+
+ /* Listbox */
+ gListBox = gtk_combo_box_text_new();
+ gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(gListBox), "sample");
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gListBox);
+ mpListboxStyle = createStyleContext(GtkControlPart::Listbox);
+ mpListboxBoxStyle = createStyleContext(GtkControlPart::ListboxBox);
+ mpListboxButtonStyle = createStyleContext(GtkControlPart::ListboxBoxButton);
+ mpListboxButtonBoxStyle = createStyleContext(GtkControlPart::ListboxBoxButtonBox);
+ mpListboxButtonArrowStyle = createStyleContext(GtkControlPart::ListboxBoxButtonBoxArrow);
+
+ mpMenuBarStyle = createStyleContext(GtkControlPart::MenuBar);
+ mpMenuBarItemStyle = createStyleContext(GtkControlPart::MenuBarItem);
+
+ /* Menu */
+ mpMenuWindowStyle = createStyleContext(GtkControlPart::MenuWindow);
+ mpMenuStyle = createStyleContext(GtkControlPart::Menu);
+
+ mpMenuItemStyle = createStyleContext(GtkControlPart::MenuItem);
+ mpMenuItemLabelStyle = createStyleContext(GtkControlPart::MenuItemLabel);
+ mpMenuItemArrowStyle = createStyleContext(GtkControlPart::MenuItemArrow);
+ mpCheckMenuItemStyle = createStyleContext(GtkControlPart::CheckMenuItem);
+ mpCheckMenuItemCheckStyle = createStyleContext(GtkControlPart::CheckMenuItemCheck);
+ mpRadioMenuItemStyle = createStyleContext(GtkControlPart::RadioMenuItem);
+ mpRadioMenuItemRadioStyle = createStyleContext(GtkControlPart::RadioMenuItemRadio);
+ mpSeparatorMenuItemStyle = createStyleContext(GtkControlPart::SeparatorMenuItem);
+ mpSeparatorMenuItemSeparatorStyle = createStyleContext(GtkControlPart::SeparatorMenuItemSeparator);
+
+ /* Frames */
+ mpFrameOutStyle = mpFrameInStyle = createStyleContext(GtkControlPart::FrameBorder);
+ getStyleContext(&mpFixedHoriLineStyle, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
+ getStyleContext(&mpFixedVertLineStyle, gtk_separator_new(GTK_ORIENTATION_VERTICAL));
+
+
+ /* Tree List */
+ gTreeViewWidget = gtk_tree_view_new();
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gTreeViewWidget);
+
+ GtkTreeViewColumn* firstTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(firstTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(gTreeViewWidget), firstTreeViewColumn);
+
+ GtkTreeViewColumn* middleTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(middleTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(gTreeViewWidget), middleTreeViewColumn);
+ gtk_tree_view_set_expander_column(GTK_TREE_VIEW(gTreeViewWidget), middleTreeViewColumn);
+
+ GtkTreeViewColumn* lastTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(lastTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(gTreeViewWidget), lastTreeViewColumn);
+
+ /* Use the middle column's header for our button */
+ GtkWidget* pTreeHeaderCellWidget = gtk_tree_view_column_get_button(middleTreeViewColumn);
+ mpTreeHeaderButtonStyle = gtk_widget_get_style_context(pTreeHeaderCellWidget);
+
+ /* Progress Bar */
+ mpProgressBarStyle = createStyleContext(GtkControlPart::ProgressBar);
+ mpProgressBarTroughStyle = createStyleContext(GtkControlPart::ProgressBarTrough);
+ mpProgressBarProgressStyle = createStyleContext(GtkControlPart::ProgressBarProgress);
+
+ gtk_widget_show_all(gDumbContainer);
+#endif
+}
+
+void GtkSalGraphics::GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY)
+{
+ char* pForceDpi;
+ if ((pForceDpi = getenv("SAL_FORCEDPI")))
+ {
+ OString sForceDPI(pForceDpi);
+ rDPIX = rDPIY = sForceDPI.toInt32();
+ return;
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkScreen* pScreen = gtk_widget_get_screen(mpWindow);
+ double fResolution = -1.0;
+ g_object_get(pScreen, "resolution", &fResolution, nullptr);
+
+ if (fResolution > 0.0)
+ rDPIX = rDPIY = sal_Int32(fResolution);
+ else
+ rDPIX = rDPIY = 96;
+#else
+ rDPIX = rDPIY = 96;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/FPServiceInfo.hxx b/vcl/unx/gtk3_kde5/FPServiceInfo.hxx
new file mode 100644
index 0000000000..1fbb8fd276
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/FPServiceInfo.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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
+
+// the service names
+#define FILE_PICKER_SERVICE_NAME "com.sun.star.ui.dialogs.Gtk3KDE5FilePicker"
+
+// the implementation names
+#define FILE_PICKER_IMPL_NAME "com.sun.star.ui.dialogs.Gtk3KDE5FilePicker"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkaction.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkaction.cxx
new file mode 100644
index 0000000000..6b9f5e3d8c
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkaction.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atkaction.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkbridge.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkbridge.cxx
new file mode 100644
index 0000000000..e07fc6c295
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkbridge.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atkbridge.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkcomponent.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkcomponent.cxx
new file mode 100644
index 0000000000..5eac70ebd9
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkcomponent.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atkcomponent.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkeditabletext.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkeditabletext.cxx
new file mode 100644
index 0000000000..792e432e13
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkeditabletext.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atkeditabletext.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkfactory.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkfactory.cxx
new file mode 100644
index 0000000000..2ddc5c55c4
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkfactory.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atkfactory.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkhypertext.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkhypertext.cxx
new file mode 100644
index 0000000000..f3fa99006e
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkhypertext.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atkhypertext.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkimage.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkimage.cxx
new file mode 100644
index 0000000000..eb3acc7134
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkimage.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atkimage.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atklistener.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atklistener.cxx
new file mode 100644
index 0000000000..cf10d29dfd
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atklistener.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atklistener.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkregistry.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkregistry.cxx
new file mode 100644
index 0000000000..cc10fbfdd5
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkregistry.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atkregistry.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkselection.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkselection.cxx
new file mode 100644
index 0000000000..4c7fc5c15f
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkselection.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atkselection.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktable.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktable.cxx
new file mode 100644
index 0000000000..672125d1a8
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktable.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atktable.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktablecell.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktablecell.cxx
new file mode 100644
index 0000000000..fdbb6f6263
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktablecell.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atktablecell.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktext.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktext.cxx
new file mode 100644
index 0000000000..de1d1f06ca
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktext.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atktext.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktextattributes.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktextattributes.cxx
new file mode 100644
index 0000000000..aeac0e02dc
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktextattributes.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atktextattributes.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkutil.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkutil.cxx
new file mode 100644
index 0000000000..193b08e9ab
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkutil.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atkutil.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkvalue.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkvalue.cxx
new file mode 100644
index 0000000000..75825b4e12
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkvalue.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atkvalue.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwrapper.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwrapper.cxx
new file mode 100644
index 0000000000..a1bcc2e290
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwrapper.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/a11y/atkwrapper.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/filepicker_ipc_commands.hxx b/vcl/unx/gtk3_kde5/filepicker_ipc_commands.hxx
new file mode 100644
index 0000000000..1d0925257b
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/filepicker_ipc_commands.hxx
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <cstdint>
+#include <iostream>
+#include <vector>
+
+#include <sal/types.h>
+#include <com/sun/star/uno/Sequence.hxx>
+
+// #define DEBUG_FILEPICKER_IPC
+
+namespace rtl
+{
+class OUString;
+}
+class QString;
+
+enum class Commands : uint16_t
+{
+ SetTitle,
+ SetWinId,
+ Execute,
+ SetMultiSelectionMode,
+ SetDefaultName,
+ SetDisplayDirectory,
+ GetDisplayDirectory,
+ GetSelectedFiles,
+ AppendFilter,
+ SetCurrentFilter,
+ GetCurrentFilter,
+ SetValue,
+ GetValue,
+ EnableControl,
+ SetLabel,
+ GetLabel,
+ AddCheckBox,
+ Initialize,
+ Quit,
+ EnablePickFolderMode,
+};
+
+inline std::vector<char> readIpcStringArg(std::istream& stream)
+{
+ uint32_t length = 0;
+ stream >> length;
+ stream.ignore(); // skip space separator
+ std::vector<char> buffer(length, '\0');
+ stream.read(buffer.data(), length);
+ return buffer;
+}
+
+void readIpcArg(std::istream& stream, OUString& string);
+void readIpcArg(std::istream& stream, QString& string);
+void readIpcArg(std::istream& stream, css::uno::Sequence<OUString>& seq);
+
+inline void readIpcArg(std::istream& stream, Commands& value)
+{
+ uint16_t v = 0;
+ stream >> v;
+ stream.ignore(); // skip space
+ value = static_cast<Commands>(v);
+}
+
+void readIpcArg(std::istream&, sal_Bool) = delete;
+
+inline void readIpcArg(std::istream& stream, bool& value)
+{
+ stream >> value;
+ stream.ignore(); // skip space
+}
+
+inline void readIpcArg(std::istream& stream, sal_Int16& value)
+{
+ stream >> value;
+ stream.ignore(); // skip space
+}
+
+inline void readIpcArg(std::istream& stream, sal_uIntPtr& value)
+{
+ stream >> value;
+ stream.ignore(); // skip space
+}
+
+#if SAL_TYPES_SIZEOFPOINTER == 4
+inline void readIpcArg(std::istream& stream, uint64_t& value)
+{
+ stream >> value;
+ stream.ignore(); // skip space
+}
+#endif
+
+inline void readIpcArgs(std::istream& /*stream*/)
+{
+ // end of arguments, nothing to do
+}
+
+template <typename T, typename... Args>
+inline void readIpcArgs(std::istream& stream, T& arg, Args&... args)
+{
+ readIpcArg(stream, arg);
+ readIpcArgs(stream, args...);
+}
+
+void sendIpcArg(std::ostream& stream, const OUString& string);
+void sendIpcArg(std::ostream& stream, const QString& string);
+
+inline void sendIpcStringArg(std::ostream& stream, uint32_t length, const char* string)
+{
+ stream << length << ' ';
+ stream.write(string, length);
+ stream << ' ';
+}
+
+inline void sendIpcArg(std::ostream& stream, Commands value)
+{
+ stream << static_cast<uint16_t>(value) << ' ';
+}
+
+void sendIpcArg(std::ostream&, sal_Bool) = delete;
+
+inline void sendIpcArg(std::ostream& stream, bool value) { stream << value << ' '; }
+
+inline void sendIpcArg(std::ostream& stream, sal_Int16 value) { stream << value << ' '; }
+
+inline void sendIpcArg(std::ostream& stream, sal_uIntPtr value) { stream << value << ' '; }
+
+#if SAL_TYPES_SIZEOFPOINTER == 4
+inline void sendIpcArg(std::ostream& stream, uint64_t value) { stream << value << ' '; }
+#endif
+
+inline void sendIpcArgsImpl(std::ostream& stream)
+{
+ // end of arguments, flush stream
+ stream << std::endl;
+}
+
+template <typename T, typename... Args>
+inline void sendIpcArgsImpl(std::ostream& stream, const T& arg, const Args&... args)
+{
+ sendIpcArg(stream, arg);
+ sendIpcArgsImpl(stream, args...);
+}
+
+template <typename T, typename... Args>
+inline void sendIpcArgs(std::ostream& stream, const T& arg, const Args&... args)
+{
+ sendIpcArgsImpl(stream, arg, args...);
+#ifdef DEBUG_FILEPICKER_IPC
+ std::cerr << "IPC MSG: ";
+ sendIpcArgsImpl(std::cerr, arg, args...);
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_cairo.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_cairo.cxx
new file mode 100644
index 0000000000..ed8780447f
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_cairo.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/gtkcairo.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_customcellrenderer.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_customcellrenderer.cxx
new file mode 100644
index 0000000000..aebe2c89ed
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_customcellrenderer.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../gtk3/customcellrenderer.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.cxx
new file mode 100644
index 0000000000..d3a053a080
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.cxx
@@ -0,0 +1,455 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <QUrl>
+#include <KFileWidget>
+
+#include "gtk3_kde5_filepicker.hxx"
+
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp>
+#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
+
+#include <sal/log.hxx>
+#include <vcl/svapp.hxx>
+#include "FPServiceInfo.hxx"
+
+#undef Region
+
+#include <fpicker/strings.hrc>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::ui::dialogs::TemplateDescription;
+using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds;
+using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::uno;
+
+// helper functions
+
+namespace
+{
+uno::Sequence<OUString> FilePicker_getSupportedServiceNames()
+{
+ return { "com.sun.star.ui.dialogs.FilePicker", "com.sun.star.ui.dialogs.SystemFilePicker",
+ "com.sun.star.ui.dialogs.Gtk3KDE5FilePicker" };
+}
+}
+
+// Gtk3KDE5FilePicker
+
+Gtk3KDE5FilePicker::Gtk3KDE5FilePicker(const uno::Reference<uno::XComponentContext>&)
+ : Gtk3KDE5FilePicker_Base(_helperMutex)
+{
+ setMultiSelectionMode(false);
+
+ // tdf#124598 dummy KWidget use to make gtk3_kde5 VCL plugin link against KIO libraries
+ QString sDummyStr;
+ QUrl aUrl = KFileWidget::getStartUrl(QUrl(), sDummyStr);
+ aUrl.setPath("/dev/null");
+}
+
+Gtk3KDE5FilePicker::~Gtk3KDE5FilePicker() = default;
+
+void SAL_CALL
+Gtk3KDE5FilePicker::addFilePickerListener(const uno::Reference<XFilePickerListener>& xListener)
+{
+ SolarMutexGuard aGuard;
+ m_xListener = xListener;
+}
+
+void SAL_CALL
+Gtk3KDE5FilePicker::removeFilePickerListener(const uno::Reference<XFilePickerListener>&)
+{
+ SolarMutexGuard aGuard;
+ m_xListener.clear();
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::setTitle(const OUString& title)
+{
+ m_ipc.sendCommand(Commands::SetTitle, title);
+}
+
+sal_Int16 SAL_CALL Gtk3KDE5FilePicker::execute()
+{
+ SolarMutexGuard g;
+ return m_ipc.execute();
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::setMultiSelectionMode(sal_Bool multiSelect)
+{
+ m_ipc.sendCommand(Commands::SetMultiSelectionMode, bool(multiSelect));
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::setDefaultName(const OUString& name)
+{
+ m_ipc.sendCommand(Commands::SetDefaultName, name);
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::setDisplayDirectory(const OUString& dir)
+{
+ m_ipc.sendCommand(Commands::SetDisplayDirectory, dir);
+}
+
+OUString SAL_CALL Gtk3KDE5FilePicker::getDisplayDirectory()
+{
+ auto id = m_ipc.sendCommand(Commands::GetDisplayDirectory);
+ OUString dir;
+ m_ipc.readResponse(id, dir);
+ return dir;
+}
+
+uno::Sequence<OUString> SAL_CALL Gtk3KDE5FilePicker::getFiles()
+{
+ uno::Sequence<OUString> seq = getSelectedFiles();
+ if (seq.getLength() > 1)
+ seq.realloc(1);
+ return seq;
+}
+
+uno::Sequence<OUString> SAL_CALL Gtk3KDE5FilePicker::getSelectedFiles()
+{
+ auto id = m_ipc.sendCommand(Commands::GetSelectedFiles);
+ uno::Sequence<OUString> seq;
+ m_ipc.readResponse(id, seq);
+ return seq;
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::appendFilter(const OUString& title, const OUString& filter)
+{
+ m_ipc.sendCommand(Commands::AppendFilter, title, filter);
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::setCurrentFilter(const OUString& title)
+{
+ m_ipc.sendCommand(Commands::SetCurrentFilter, title);
+}
+
+OUString SAL_CALL Gtk3KDE5FilePicker::getCurrentFilter()
+{
+ auto id = m_ipc.sendCommand(Commands::GetCurrentFilter);
+ OUString filter;
+ m_ipc.readResponse(id, filter);
+ return filter;
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::appendFilterGroup(const OUString& /*rGroupTitle*/,
+ const uno::Sequence<beans::StringPair>& filters)
+{
+ const sal_uInt16 length = filters.getLength();
+ for (sal_uInt16 i = 0; i < length; ++i)
+ {
+ beans::StringPair aPair = filters[i];
+ appendFilter(aPair.First, aPair.Second);
+ }
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::setValue(sal_Int16 controlId, sal_Int16 nControlAction,
+ const uno::Any& value)
+{
+ if (value.has<bool>())
+ {
+ m_ipc.sendCommand(Commands::SetValue, controlId, nControlAction, value.get<bool>());
+ }
+ else
+ {
+ SAL_INFO("vcl.gtkkde5", "set value of unhandled type " << controlId);
+ }
+}
+
+uno::Any SAL_CALL Gtk3KDE5FilePicker::getValue(sal_Int16 controlId, sal_Int16 nControlAction)
+{
+ if (CHECKBOX_AUTOEXTENSION == controlId)
+ // We ignore this one and rely on QFileDialog to provide the function.
+ // Always return false, to pretend we do not support this, otherwise
+ // LO core would try to be smart and cut the extension in some places,
+ // interfering with QFileDialog's handling of it. QFileDialog also
+ // saves the value of the setting, so LO core is not needed for that either.
+ return uno::Any(false);
+
+ auto id = m_ipc.sendCommand(Commands::GetValue, controlId, nControlAction);
+
+ bool value = false;
+ m_ipc.readResponse(id, value);
+
+ return uno::Any(value);
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::enableControl(sal_Int16 controlId, sal_Bool enable)
+{
+ m_ipc.sendCommand(Commands::EnableControl, controlId, bool(enable));
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::setLabel(sal_Int16 controlId, const OUString& label)
+{
+ m_ipc.sendCommand(Commands::SetLabel, controlId, label);
+}
+
+OUString SAL_CALL Gtk3KDE5FilePicker::getLabel(sal_Int16 controlId)
+{
+ auto id = m_ipc.sendCommand(Commands::GetLabel, controlId);
+ OUString label;
+ m_ipc.readResponse(id, label);
+ return label;
+}
+
+void Gtk3KDE5FilePicker::addCustomControl(sal_Int16 controlId)
+{
+ TranslateId resId;
+
+ switch (controlId)
+ {
+ case CHECKBOX_AUTOEXTENSION:
+ resId = STR_SVT_FILEPICKER_AUTO_EXTENSION;
+ break;
+ case CHECKBOX_PASSWORD:
+ resId = STR_SVT_FILEPICKER_PASSWORD;
+ break;
+ case CHECKBOX_FILTEROPTIONS:
+ resId = STR_SVT_FILEPICKER_FILTER_OPTIONS;
+ break;
+ case CHECKBOX_READONLY:
+ resId = STR_SVT_FILEPICKER_READONLY;
+ break;
+ case CHECKBOX_LINK:
+ resId = STR_SVT_FILEPICKER_INSERT_AS_LINK;
+ break;
+ case CHECKBOX_PREVIEW:
+ resId = STR_SVT_FILEPICKER_SHOW_PREVIEW;
+ break;
+ case CHECKBOX_SELECTION:
+ resId = STR_SVT_FILEPICKER_SELECTION;
+ break;
+ case CHECKBOX_GPGENCRYPTION:
+ resId = STR_SVT_FILEPICKER_GPGENCRYPT;
+ break;
+ case PUSHBUTTON_PLAY:
+ resId = STR_SVT_FILEPICKER_PLAY;
+ break;
+ case LISTBOX_VERSION:
+ resId = STR_SVT_FILEPICKER_VERSION;
+ break;
+ case LISTBOX_TEMPLATE:
+ resId = STR_SVT_FILEPICKER_TEMPLATES;
+ break;
+ case LISTBOX_IMAGE_TEMPLATE:
+ resId = STR_SVT_FILEPICKER_IMAGE_TEMPLATE;
+ break;
+ case LISTBOX_IMAGE_ANCHOR:
+ resId = STR_SVT_FILEPICKER_IMAGE_ANCHOR;
+ break;
+ case LISTBOX_VERSION_LABEL:
+ case LISTBOX_TEMPLATE_LABEL:
+ case LISTBOX_IMAGE_TEMPLATE_LABEL:
+ case LISTBOX_IMAGE_ANCHOR_LABEL:
+ case LISTBOX_FILTER_SELECTOR:
+ break;
+ }
+
+ switch (controlId)
+ {
+ case CHECKBOX_AUTOEXTENSION:
+ case CHECKBOX_PASSWORD:
+ case CHECKBOX_FILTEROPTIONS:
+ case CHECKBOX_READONLY:
+ case CHECKBOX_LINK:
+ case CHECKBOX_PREVIEW:
+ case CHECKBOX_SELECTION:
+ case CHECKBOX_GPGENCRYPTION:
+ {
+ // the checkbox is created even for CHECKBOX_AUTOEXTENSION to simplify
+ // code, but the checkbox is hidden and ignored
+ bool hidden = controlId == CHECKBOX_AUTOEXTENSION;
+
+ m_ipc.sendCommand(Commands::AddCheckBox, controlId, hidden, getResString(resId));
+
+ break;
+ }
+ case PUSHBUTTON_PLAY:
+ case LISTBOX_VERSION:
+ case LISTBOX_TEMPLATE:
+ case LISTBOX_IMAGE_TEMPLATE:
+ case LISTBOX_IMAGE_ANCHOR:
+ case LISTBOX_VERSION_LABEL:
+ case LISTBOX_TEMPLATE_LABEL:
+ case LISTBOX_IMAGE_TEMPLATE_LABEL:
+ case LISTBOX_IMAGE_ANCHOR_LABEL:
+ case LISTBOX_FILTER_SELECTOR:
+ break;
+ }
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::initialize(const uno::Sequence<uno::Any>& args)
+{
+ // parameter checking
+ uno::Any arg;
+ if (args.getLength() == 0)
+ {
+ throw lang::IllegalArgumentException("no arguments", static_cast<XFilePicker2*>(this), 1);
+ }
+
+ arg = args[0];
+
+ if ((arg.getValueType() != cppu::UnoType<sal_Int16>::get())
+ && (arg.getValueType() != cppu::UnoType<sal_Int8>::get()))
+ {
+ throw lang::IllegalArgumentException("invalid argument type",
+ static_cast<XFilePicker2*>(this), 1);
+ }
+
+ sal_Int16 templateId = -1;
+ arg >>= templateId;
+
+ bool saveDialog = false;
+ switch (templateId)
+ {
+ case FILEOPEN_SIMPLE:
+ break;
+
+ case FILESAVE_SIMPLE:
+ saveDialog = true;
+ break;
+
+ case FILESAVE_AUTOEXTENSION:
+ saveDialog = true;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ break;
+
+ case FILESAVE_AUTOEXTENSION_PASSWORD:
+ {
+ saveDialog = true;
+ addCustomControl(CHECKBOX_PASSWORD);
+ addCustomControl(CHECKBOX_GPGENCRYPTION);
+ break;
+ }
+ case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS:
+ {
+ saveDialog = true;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ addCustomControl(CHECKBOX_PASSWORD);
+ addCustomControl(CHECKBOX_GPGENCRYPTION);
+ addCustomControl(CHECKBOX_FILTEROPTIONS);
+ break;
+ }
+ case FILESAVE_AUTOEXTENSION_SELECTION:
+ saveDialog = true;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ addCustomControl(CHECKBOX_SELECTION);
+ break;
+
+ case FILESAVE_AUTOEXTENSION_TEMPLATE:
+ saveDialog = true;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ addCustomControl(LISTBOX_TEMPLATE);
+ break;
+
+ case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE:
+ addCustomControl(CHECKBOX_LINK);
+ addCustomControl(CHECKBOX_PREVIEW);
+ addCustomControl(LISTBOX_IMAGE_TEMPLATE);
+ break;
+
+ case FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR:
+ addCustomControl(CHECKBOX_LINK);
+ addCustomControl(CHECKBOX_PREVIEW);
+ addCustomControl(LISTBOX_IMAGE_ANCHOR);
+ break;
+
+ case FILEOPEN_PLAY:
+ addCustomControl(PUSHBUTTON_PLAY);
+ break;
+
+ case FILEOPEN_LINK_PLAY:
+ addCustomControl(CHECKBOX_LINK);
+ addCustomControl(PUSHBUTTON_PLAY);
+ break;
+
+ case FILEOPEN_READONLY_VERSION:
+ addCustomControl(CHECKBOX_READONLY);
+ addCustomControl(LISTBOX_VERSION);
+ break;
+
+ case FILEOPEN_LINK_PREVIEW:
+ addCustomControl(CHECKBOX_LINK);
+ addCustomControl(CHECKBOX_PREVIEW);
+ break;
+
+ case FILEOPEN_PREVIEW:
+ addCustomControl(CHECKBOX_PREVIEW);
+ break;
+
+ default:
+ SAL_INFO("vcl.gtkkde5", "unknown templates " << templateId);
+ return;
+ }
+
+ setTitle(getResString(saveDialog ? STR_FILEDLG_SAVE : STR_FILEDLG_OPEN));
+
+ m_ipc.sendCommand(Commands::Initialize, saveDialog);
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::cancel()
+{
+ // TODO
+}
+
+void Gtk3KDE5FilePicker::disposing(const lang::EventObject& rEvent)
+{
+ uno::Reference<XFilePickerListener> xFilePickerListener(rEvent.Source, uno::UNO_QUERY);
+
+ if (xFilePickerListener.is())
+ {
+ removeFilePickerListener(xFilePickerListener);
+ }
+}
+
+OUString SAL_CALL Gtk3KDE5FilePicker::getImplementationName() { return FILE_PICKER_IMPL_NAME; }
+
+sal_Bool SAL_CALL Gtk3KDE5FilePicker::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+uno::Sequence<OUString> SAL_CALL Gtk3KDE5FilePicker::getSupportedServiceNames()
+{
+ return FilePicker_getSupportedServiceNames();
+}
+
+void Gtk3KDE5FilePicker::filterChanged()
+{
+ FilePickerEvent aEvent;
+ aEvent.ElementId = LISTBOX_FILTER;
+ SAL_INFO("vcl.gtkkde5", "filter changed");
+ if (m_xListener.is())
+ m_xListener->controlStateChanged(aEvent);
+}
+
+void Gtk3KDE5FilePicker::selectionChanged()
+{
+ FilePickerEvent aEvent;
+ SAL_INFO("vcl.gtkkde5", "file selection changed");
+ if (m_xListener.is())
+ m_xListener->fileSelectionChanged(aEvent);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.hxx b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.hxx
new file mode 100644
index 0000000000..7ce3910048
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.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 <cppuhelper/compbase.hxx>
+
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/ui/dialogs/XFilePicker3.hpp>
+#include <com/sun/star/ui/dialogs/XFilePickerControlAccess.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <osl/mutex.hxx>
+
+#include "gtk3_kde5_filepicker_ipc.hxx"
+
+typedef ::cppu::WeakComponentImplHelper<css::ui::dialogs::XFilePicker3,
+ css::ui::dialogs::XFilePickerControlAccess
+ // TODO css::ui::dialogs::XFilePreview
+ ,
+ css::lang::XInitialization, css::lang::XServiceInfo>
+ Gtk3KDE5FilePicker_Base;
+
+class Gtk3KDE5FilePicker : public Gtk3KDE5FilePicker_Base
+{
+protected:
+ css::uno::Reference<css::ui::dialogs::XFilePickerListener> m_xListener;
+
+ osl::Mutex _helperMutex;
+ Gtk3KDE5FilePickerIpc m_ipc;
+
+public:
+ explicit Gtk3KDE5FilePicker(const css::uno::Reference<css::uno::XComponentContext>&);
+ virtual ~Gtk3KDE5FilePicker() override;
+
+ // XFilePickerNotifier
+ virtual void SAL_CALL addFilePickerListener(
+ const css::uno::Reference<css::ui::dialogs::XFilePickerListener>& xListener) override;
+ virtual void SAL_CALL removeFilePickerListener(
+ const css::uno::Reference<css::ui::dialogs::XFilePickerListener>& xListener) override;
+
+ // XExecutableDialog functions
+ virtual void SAL_CALL setTitle(const OUString& rTitle) override;
+ virtual sal_Int16 SAL_CALL execute() override;
+
+ // XFilePicker functions
+ virtual void SAL_CALL setMultiSelectionMode(sal_Bool bMode) override;
+ virtual void SAL_CALL setDefaultName(const OUString& rName) override;
+ virtual void SAL_CALL setDisplayDirectory(const OUString& rDirectory) override;
+ virtual OUString SAL_CALL getDisplayDirectory() override;
+ virtual css::uno::Sequence<OUString> SAL_CALL getFiles() override;
+
+ // XFilterManager functions
+ virtual void SAL_CALL appendFilter(const OUString& rTitle, const OUString& rFilter) override;
+ virtual void SAL_CALL setCurrentFilter(const OUString& rTitle) override;
+ virtual OUString SAL_CALL getCurrentFilter() override;
+
+ // XFilterGroupManager functions
+ virtual void SAL_CALL
+ appendFilterGroup(const OUString& rGroupTitle,
+ const css::uno::Sequence<css::beans::StringPair>& rFilters) override;
+
+ // XFilePickerControlAccess functions
+ virtual void SAL_CALL setValue(sal_Int16 nControlId, sal_Int16 nControlAction,
+ const css::uno::Any& rValue) override;
+ virtual css::uno::Any SAL_CALL getValue(sal_Int16 nControlId,
+ sal_Int16 nControlAction) override;
+ virtual void SAL_CALL enableControl(sal_Int16 nControlId, sal_Bool bEnable) override;
+ virtual void SAL_CALL setLabel(sal_Int16 nControlId, const OUString& rLabel) override;
+ virtual OUString SAL_CALL getLabel(sal_Int16 nControlId) override;
+
+ /* TODO XFilePreview
+
+ virtual css::uno::Sequence< sal_Int16 > SAL_CALL getSupportedImageFormats( );
+ virtual sal_Int32 SAL_CALL getTargetColorDepth( );
+ virtual sal_Int32 SAL_CALL getAvailableWidth( );
+ virtual sal_Int32 SAL_CALL getAvailableHeight( );
+ virtual void SAL_CALL setImage( sal_Int16 aImageFormat, const css::uno::Any &rImage );
+ virtual sal_Bool SAL_CALL setShowState( sal_Bool bShowState );
+ virtual sal_Bool SAL_CALL getShowState( );
+ */
+
+ // XFilePicker2 functions
+ virtual css::uno::Sequence<OUString> SAL_CALL getSelectedFiles() override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize(const css::uno::Sequence<css::uno::Any>& rArguments) override;
+
+ // XCancellable
+ virtual void SAL_CALL cancel() override;
+
+ // XEventListener
+ virtual void disposing(const css::lang::EventObject& rEvent);
+ using cppu::WeakComponentImplHelperBase::disposing;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override;
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+private:
+ Gtk3KDE5FilePicker(const Gtk3KDE5FilePicker&) = delete;
+ Gtk3KDE5FilePicker& operator=(const Gtk3KDE5FilePicker&) = delete;
+
+ //add a custom control widget to the file dialog
+ void addCustomControl(sal_Int16 controlId);
+
+ // emit XFilePickerListener controlStateChanged event
+ void filterChanged();
+ // emit XFilePickerListener fileSelectionChanged event
+ void selectionChanged();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.cxx
new file mode 100644
index 0000000000..ebfa0fbd7e
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.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 <sal/config.h>
+
+#include <string_view>
+
+#include "gtk3_kde5_filepicker_ipc.hxx"
+
+#undef Region
+
+#include <system_error>
+
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+
+#include <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/weld.hxx>
+
+#include <osl/file.h>
+#include <osl/process.h>
+
+#include <gtk/gtk.h>
+
+#include <boost/filesystem/path.hpp>
+
+#include <fpicker/fpsofficeResMgr.hxx>
+
+using namespace ::com::sun::star::ui::dialogs;
+
+// helper functions
+
+namespace
+{
+OUString applicationDirPath()
+{
+ OUString applicationFilePath;
+ osl_getExecutableFile(&applicationFilePath.pData);
+ OUString applicationSystemPath;
+ osl_getSystemPathFromFileURL(applicationFilePath.pData, &applicationSystemPath.pData);
+ const auto utf8Path = applicationSystemPath.toUtf8();
+ auto ret = boost::filesystem::path(utf8Path.getStr(), utf8Path.getStr() + utf8Path.getLength());
+ ret.remove_filename();
+ return OUString::fromUtf8(std::string_view(ret.c_str()));
+}
+
+OUString findPickerExecutable()
+{
+ const auto path = applicationDirPath();
+ OUString ret;
+ osl_searchFileURL(u"lo_kde5filepicker"_ustr.pData, path.pData, &ret.pData);
+ if (ret.isEmpty())
+ throw std::system_error(std::make_error_code(std::errc::no_such_file_or_directory),
+ "could not find lo_kde5filepicker executable");
+ return ret;
+}
+}
+
+void readIpcArg(std::istream& stream, OUString& str)
+{
+ const auto buffer = readIpcStringArg(stream);
+ str = OUString::fromUtf8(std::string_view(buffer.data(), buffer.size()));
+}
+
+void readIpcArg(std::istream& stream, css::uno::Sequence<OUString>& seq)
+{
+ uint32_t numFiles = 0;
+ stream >> numFiles;
+ stream.ignore(); // skip space;
+ seq.realloc(numFiles);
+ OUString* pSeq = seq.getArray();
+ for (size_t i = 0; i < numFiles; ++i)
+ {
+ readIpcArg(stream, pSeq[i]);
+ }
+}
+
+void sendIpcArg(std::ostream& stream, const OUString& string)
+{
+ const auto utf8 = string.toUtf8();
+ sendIpcStringArg(stream, utf8.getLength(), utf8.getStr());
+}
+
+OUString getResString(TranslateId pResId)
+{
+ if (!pResId)
+ return {};
+
+ return FpsResId(pResId);
+}
+
+// handles the IPC commands for dialog execution and ends the dummy Gtk dialog once the IPC response is there
+static void handleIpcForExecute(Gtk3KDE5FilePickerIpc* pFilePickerIpc, GtkWidget* pDummyDialog,
+ bool* bResult)
+{
+ auto id = pFilePickerIpc->sendCommand(Commands::Execute);
+ pFilePickerIpc->readResponse(id, *bResult);
+
+ // end the dummy dialog
+ gtk_widget_hide(pDummyDialog);
+}
+
+// Gtk3KDE5FilePicker
+
+Gtk3KDE5FilePickerIpc::Gtk3KDE5FilePickerIpc()
+{
+ const auto exe = findPickerExecutable();
+ oslProcessError result;
+ oslSecurity pSecurity = osl_getCurrentSecurity();
+ result = osl_executeProcess_WithRedirectedIO(exe.pData, nullptr, 0, osl_Process_NORMAL,
+ pSecurity, nullptr, nullptr, 0, &m_process,
+ &m_inputWrite, &m_outputRead, nullptr);
+ osl_freeSecurityHandle(pSecurity);
+ if (result != osl_Process_E_None)
+ throw std::system_error(std::make_error_code(std::errc::no_such_process),
+ "could not start lo_kde5filepicker executable");
+}
+
+Gtk3KDE5FilePickerIpc::~Gtk3KDE5FilePickerIpc()
+{
+ if (!m_process)
+ return;
+
+ sendCommand(Commands::Quit);
+ osl_joinProcess(m_process);
+
+ if (m_inputWrite)
+ osl_closeFile(m_inputWrite);
+ if (m_outputRead)
+ osl_closeFile(m_outputRead);
+ osl_freeProcessHandle(m_process);
+}
+
+sal_Int16 Gtk3KDE5FilePickerIpc::execute()
+{
+ auto restoreMainWindow = blockMainWindow();
+
+ // dummy gtk dialog that will take care of processing events,
+ // not meant to be actually seen by user
+ GtkWidget* pDummyDialog = gtk_dialog_new();
+
+ bool accepted = false;
+
+ // send IPC command and read response in a separate thread
+ std::thread aIpcHandler(&handleIpcForExecute, this, pDummyDialog, &accepted);
+
+ // make dummy dialog not to be seen by user
+ gtk_window_set_decorated(GTK_WINDOW(pDummyDialog), false);
+ gtk_window_set_default_size(GTK_WINDOW(pDummyDialog), 0, 0);
+ gtk_window_set_accept_focus(GTK_WINDOW(pDummyDialog), false);
+ // gtk_widget_set_opacity() only has the desired effect when widget is already shown
+ gtk_widget_show(pDummyDialog);
+ gtk_widget_set_opacity(pDummyDialog, 0);
+ // run dialog, leaving event processing to GTK
+ // dialog will be closed by the separate 'aIpcHandler' thread once the IPC response is there
+ gtk_dialog_run(GTK_DIALOG(pDummyDialog));
+
+ aIpcHandler.join();
+
+ gtk_widget_destroy(pDummyDialog);
+
+ if (restoreMainWindow)
+ restoreMainWindow();
+
+ return accepted ? ExecutableDialogResults::OK : ExecutableDialogResults::CANCEL;
+}
+
+static gboolean ignoreDeleteEvent(GtkWidget* /*widget*/, GdkEvent* /*event*/,
+ gpointer /*user_data*/)
+{
+ return true;
+}
+
+std::function<void()> Gtk3KDE5FilePickerIpc::blockMainWindow()
+{
+ weld::Window* pParentWin = Application::GetDefDialogParent();
+ if (!pParentWin)
+ return {};
+
+ const SystemEnvData aSysData = pParentWin->get_system_data();
+ auto* pMainWindow = static_cast<GtkWidget*>(aSysData.pWidget);
+ if (!pMainWindow)
+ return {};
+
+ sendCommand(Commands::SetWinId, aSysData.GetWindowHandle(aSysData.pSalFrame));
+
+ SolarMutexGuard guard;
+ auto deleteEventSignalId = g_signal_lookup("delete_event", gtk_widget_get_type());
+
+ // disable the mainwindow
+ gtk_widget_set_sensitive(pMainWindow, false);
+
+ // block the GtkSalFrame delete_event handler
+ auto blockedHandler = g_signal_handler_find(
+ pMainWindow, static_cast<GSignalMatchType>(G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_DATA),
+ deleteEventSignalId, 0, nullptr, nullptr, aSysData.pSalFrame);
+ g_signal_handler_block(pMainWindow, blockedHandler);
+
+ // prevent the window from being closed
+ auto ignoreDeleteEventHandler
+ = g_signal_connect(pMainWindow, "delete_event", G_CALLBACK(ignoreDeleteEvent), nullptr);
+
+ return [pMainWindow, ignoreDeleteEventHandler, blockedHandler] {
+ SolarMutexGuard cleanupGuard;
+ // re-enable window
+ gtk_widget_set_sensitive(pMainWindow, true);
+
+ // allow it to be closed again
+ g_signal_handler_disconnect(pMainWindow, ignoreDeleteEventHandler);
+
+ // unblock the GtkSalFrame handler
+ g_signal_handler_unblock(pMainWindow, blockedHandler);
+ };
+}
+
+void Gtk3KDE5FilePickerIpc::writeResponseLine(const std::string& line)
+{
+ sal_uInt64 bytesWritten = 0;
+ osl_writeFile(m_inputWrite, line.c_str(), line.size(), &bytesWritten);
+}
+
+std::string Gtk3KDE5FilePickerIpc::readResponseLine()
+{
+ if (!m_responseBuffer.empty()) // check whether we have a line in our buffer
+ {
+ std::size_t it = m_responseBuffer.find('\n');
+ if (it != std::string::npos)
+ {
+ auto ret = m_responseBuffer.substr(0, it);
+ m_responseBuffer.erase(0, it + 1);
+ return ret;
+ }
+ }
+
+ const sal_uInt64 BUF_SIZE = 1024;
+ char buffer[BUF_SIZE];
+ while (true)
+ {
+ sal_uInt64 bytesRead = 0;
+ auto err = osl_readFile(m_outputRead, buffer, BUF_SIZE, &bytesRead);
+ auto it = std::find(buffer, buffer + bytesRead, '\n');
+ if (it != buffer + bytesRead) // check whether the chunk we read contains an EOL
+ {
+ // if so, append that part to the buffer and return it
+ std::string ret = m_responseBuffer.append(buffer, it);
+ // but keep anything else we may have read in our buffer
+ ++it;
+ m_responseBuffer.assign(it, buffer + bytesRead);
+ return ret;
+ }
+ // otherwise append everything we read to the buffer and try again
+ m_responseBuffer.append(buffer, bytesRead);
+
+ if (err != osl_File_E_None && err != osl_File_E_AGAIN)
+ break;
+ }
+ return {};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.hxx b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.hxx
new file mode 100644
index 0000000000..58ca3eb452
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <osl/process.h>
+#include <unotools/resmgr.hxx>
+
+#include "filepicker_ipc_commands.hxx"
+
+#include <functional>
+#include <mutex>
+#include <thread>
+#include <sstream>
+
+OUString getResString(TranslateId pResId);
+
+class Gtk3KDE5FilePickerIpc
+{
+protected:
+ oslProcess m_process;
+ oslFileHandle m_inputWrite;
+ oslFileHandle m_outputRead;
+ // simple multiplexing: every command gets its own ID that can be used to
+ // read the corresponding response
+ uint64_t m_msgId = 1;
+ std::mutex m_mutex;
+ uint64_t m_incomingResponse = 0;
+ std::string m_responseBuffer;
+ std::stringstream m_responseStream;
+
+public:
+ explicit Gtk3KDE5FilePickerIpc();
+ ~Gtk3KDE5FilePickerIpc();
+
+ sal_Int16 execute();
+
+ void writeResponseLine(const std::string& line);
+
+ template <typename... Args> uint64_t sendCommand(Commands command, const Args&... args)
+ {
+ auto id = m_msgId;
+ ++m_msgId;
+ std::stringstream stream;
+ sendIpcArgs(stream, id, command, args...);
+ writeResponseLine(stream.str());
+ return id;
+ }
+
+ std::string readResponseLine();
+
+ // workaround gcc <= 4.8 bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55914
+ template <int...> struct seq
+ {
+ };
+ template <int N, int... S> struct gens : gens<N - 1, N - 1, S...>
+ {
+ };
+ template <int... S> struct gens<0, S...>
+ {
+ typedef seq<S...> type;
+ };
+ template <typename... Args> struct ArgsReader
+ {
+ ArgsReader(Args&... args)
+ : m_args(args...)
+ {
+ }
+
+ void operator()(std::istream& stream)
+ {
+ callFunc(stream, typename gens<sizeof...(Args)>::type());
+ }
+
+ private:
+ template <int... S> void callFunc(std::istream& stream, seq<S...>)
+ {
+ readIpcArgs(stream, std::get<S>(m_args)...);
+ }
+
+ std::tuple<Args&...> m_args;
+ };
+
+ template <typename... Args> void readResponse(uint64_t id, Args&... args)
+ {
+ ArgsReader<Args...> argsReader(args...);
+ while (true)
+ {
+ // only let one thread read at any given time
+ std::scoped_lock<std::mutex> lock(m_mutex);
+
+ // check if we need to read (and potentially wait) a response ID
+ if (m_incomingResponse == 0)
+ {
+ m_responseStream.clear();
+ m_responseStream.str(readResponseLine());
+ readIpcArgs(m_responseStream, m_incomingResponse);
+ }
+
+ if (m_incomingResponse == id)
+ {
+ // the response we are waiting for came in
+ argsReader(m_responseStream);
+ m_incomingResponse = 0;
+ break;
+ }
+ else
+ {
+ // the next response answers some other request, yield
+ std::this_thread::yield();
+ }
+ }
+ }
+
+private:
+ std::function<void()> blockMainWindow();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker.cxx
new file mode 100644
index 0000000000..c1569e6be2
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker.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 "gtk3_kde5_folderpicker.hxx"
+
+#include <vcl/svapp.hxx>
+
+#include <fpicker/strings.hrc>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+// constructor
+
+Gtk3KDE5FolderPicker::Gtk3KDE5FolderPicker(
+ const uno::Reference<uno::XComponentContext>& /*xContext*/)
+{
+ m_ipc.sendCommand(Commands::EnablePickFolderMode);
+ setTitle(getResString(STR_SVT_FOLDERPICKER_DEFAULT_TITLE));
+}
+
+Gtk3KDE5FolderPicker::~Gtk3KDE5FolderPicker() = default;
+
+void SAL_CALL Gtk3KDE5FolderPicker::setDisplayDirectory(const OUString& aDirectory)
+{
+ m_ipc.sendCommand(Commands::SetDisplayDirectory, aDirectory);
+}
+
+OUString SAL_CALL Gtk3KDE5FolderPicker::getDisplayDirectory()
+{
+ auto id = m_ipc.sendCommand(Commands::GetDisplayDirectory);
+ OUString ret;
+ m_ipc.readResponse(id, ret);
+ return ret;
+}
+
+OUString SAL_CALL Gtk3KDE5FolderPicker::getDirectory()
+{
+ auto id = m_ipc.sendCommand(Commands::GetSelectedFiles);
+ uno::Sequence<OUString> seq;
+ m_ipc.readResponse(id, seq);
+ return seq.hasElements() ? seq[0] : OUString();
+}
+
+void SAL_CALL Gtk3KDE5FolderPicker::setDescription(const OUString& /*rDescription*/) {}
+
+// XExecutableDialog functions
+
+void SAL_CALL Gtk3KDE5FolderPicker::setTitle(const OUString& aTitle)
+{
+ m_ipc.sendCommand(Commands::SetTitle, aTitle);
+}
+
+sal_Int16 SAL_CALL Gtk3KDE5FolderPicker::execute()
+{
+ SolarMutexGuard g;
+ return m_ipc.execute();
+}
+
+// XCancellable
+
+void SAL_CALL Gtk3KDE5FolderPicker::cancel()
+{
+ // TODO
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker.hxx b/vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker.hxx
new file mode 100644
index 0000000000..9801a072ac
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <cppuhelper/implbase.hxx>
+
+#include <com/sun/star/ui/dialogs/XFolderPicker2.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include "gtk3_kde5_filepicker_ipc.hxx"
+
+class Gtk3KDE5FolderPicker : public cppu::WeakImplHelper<css::ui::dialogs::XFolderPicker2>
+{
+protected:
+ Gtk3KDE5FilePickerIpc m_ipc;
+
+public:
+ // constructor
+ explicit Gtk3KDE5FolderPicker(
+ const css::uno::Reference<css::uno::XComponentContext>& xServiceMgr);
+ virtual ~Gtk3KDE5FolderPicker() override;
+
+ // XExecutableDialog functions
+ virtual void SAL_CALL setTitle(const OUString& aTitle) override;
+ virtual sal_Int16 SAL_CALL execute() override;
+
+ // XFolderPicker functions
+ virtual void SAL_CALL setDisplayDirectory(const OUString& rDirectory) override;
+ virtual OUString SAL_CALL getDisplayDirectory() override;
+ virtual OUString SAL_CALL getDirectory() override;
+ virtual void SAL_CALL setDescription(const OUString& rDescription) override;
+
+ // XCancellable
+ virtual void SAL_CALL cancel() override;
+
+private:
+ Gtk3KDE5FolderPicker(const Gtk3KDE5FolderPicker&) = delete;
+ Gtk3KDE5FolderPicker& operator=(const Gtk3KDE5FolderPicker&) = delete;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_gloactiongroup.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_gloactiongroup.cxx
new file mode 100644
index 0000000000..eccf49d3df
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_gloactiongroup.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/gloactiongroup.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_glomenu.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_glomenu.cxx
new file mode 100644
index 0000000000..979c9fba07
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_glomenu.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/glomenu.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_gtkdata.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_gtkdata.cxx
new file mode 100644
index 0000000000..f03dce99a7
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_gtkdata.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/gtkdata.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_gtkframe.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_gtkframe.cxx
new file mode 100644
index 0000000000..8d8a07a3f2
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_gtkframe.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/gtkframe.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_gtkinst.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_gtkinst.cxx
new file mode 100644
index 0000000000..dd6aace5dc
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_gtkinst.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 .
+ */
+
+// make gtk3 plug advertise correctly as kde5 hybrid
+#define GTK_TOOLKIT_NAME "gtk3_kde5"
+#include "../gtk3/gtkinst.cxx"
+
+#include "gtk3_kde5_filepicker.hxx"
+#include "gtk3_kde5_folderpicker.hxx"
+
+#include <system_error>
+
+uno::Reference<ui::dialogs::XFilePicker2>
+GtkInstance::createFilePicker(const uno::Reference<uno::XComponentContext>& xMSF)
+{
+ try
+ {
+ return uno::Reference<ui::dialogs::XFilePicker2>(new Gtk3KDE5FilePicker(xMSF));
+ }
+ catch (const std::system_error& error)
+ {
+ OSL_FAIL(error.what());
+ return { nullptr };
+ }
+}
+
+uno::Reference<ui::dialogs::XFolderPicker2>
+GtkInstance::createFolderPicker(const uno::Reference<uno::XComponentContext>& xMSF)
+{
+ try
+ {
+ return uno::Reference<ui::dialogs::XFolderPicker2>(new Gtk3KDE5FolderPicker(xMSF));
+ }
+ catch (const std::system_error& error)
+ {
+ OSL_FAIL(error.what());
+ return { nullptr };
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_gtkobject.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_gtkobject.cxx
new file mode 100644
index 0000000000..948c6bd3a8
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_gtkobject.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/gtkobject.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_gtksalmenu.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_gtksalmenu.cxx
new file mode 100644
index 0000000000..7963e6b603
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_gtksalmenu.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/gtksalmenu.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_gtksys.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_gtksys.cxx
new file mode 100644
index 0000000000..3f3ad9f1d3
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_gtksys.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/gtksys.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_hudawareness.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_hudawareness.cxx
new file mode 100644
index 0000000000..e0b8f1812d
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_hudawareness.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/hudawareness.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_salnativewidgets-gtk.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_salnativewidgets-gtk.cxx
new file mode 100644
index 0000000000..428ff82042
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_salnativewidgets-gtk.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/salnativewidgets-gtk.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/kde5_filepicker.cxx b/vcl/unx/gtk3_kde5/kde5_filepicker.cxx
new file mode 100644
index 0000000000..28aee235a3
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/kde5_filepicker.cxx
@@ -0,0 +1,286 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 "kde5_filepicker.hxx"
+
+#include <KWindowSystem>
+#include <KFileWidget>
+
+#include <QtCore/QDebug>
+#include <QtCore/QUrl>
+#include <QtWidgets/QCheckBox>
+#include <QtWidgets/QFileDialog>
+#include <QtWidgets/QGridLayout>
+#include <QtWidgets/QWidget>
+#include <QtWidgets/QApplication>
+
+// KDE5FilePicker
+
+KDE5FilePicker::KDE5FilePicker(QObject* parent)
+ : QObject(parent)
+ , _dialog(new QFileDialog(nullptr, {}, QDir::homePath()))
+ , _extraControls(new QWidget)
+ , _layout(new QGridLayout(_extraControls))
+ , _winId(0)
+{
+ _dialog->setSupportedSchemes({
+ QStringLiteral("file"), QStringLiteral("http"), QStringLiteral("https"),
+ QStringLiteral("webdav"), QStringLiteral("webdavs"), QStringLiteral("smb"),
+ QStringLiteral(""), // this makes removable devices shown
+ });
+
+ setMultiSelectionMode(false);
+
+ connect(_dialog, &QFileDialog::filterSelected, this, &KDE5FilePicker::filterChanged);
+ connect(_dialog, &QFileDialog::fileSelected, this, &KDE5FilePicker::selectionChanged);
+
+ setupCustomWidgets();
+}
+
+void KDE5FilePicker::enableFolderMode()
+{
+ _dialog->setOption(QFileDialog::ShowDirsOnly, true);
+ // Workaround for https://bugs.kde.org/show_bug.cgi?id=406464 :
+ // Don't set file mode to QFileDialog::Directory when native KDE Plasma 5
+ // file dialog is used, since clicking on directory "bar" inside directory "foo"
+ // and then confirming would return "foo" rather than "foo/bar";
+ // on the other hand, non-native file dialog needs 'QFileDialog::Directory'
+ // and doesn't allow folder selection otherwise
+ if (Application::GetDesktopEnvironment() != "PLASMA5")
+ {
+ _dialog->setFileMode(QFileDialog::Directory);
+ }
+}
+
+KDE5FilePicker::~KDE5FilePicker()
+{
+ delete _extraControls;
+ delete _dialog;
+}
+
+void KDE5FilePicker::setTitle(const QString& title) { _dialog->setWindowTitle(title); }
+
+bool KDE5FilePicker::execute()
+{
+ if (!_filters.isEmpty())
+ _dialog->setNameFilters(_filters);
+ if (!_currentFilter.isEmpty())
+ _dialog->selectNameFilter(_currentFilter);
+
+ _dialog->show();
+ //block and wait for user input
+ return _dialog->exec() == QFileDialog::Accepted;
+}
+
+void KDE5FilePicker::setMultiSelectionMode(bool multiSelect)
+{
+ if (_dialog->acceptMode() == QFileDialog::AcceptSave)
+ return;
+
+ _dialog->setFileMode(multiSelect ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile);
+}
+
+void KDE5FilePicker::setDefaultName(const QString& name) { _dialog->selectFile(name); }
+
+void KDE5FilePicker::setDisplayDirectory(const QString& dir)
+{
+ _dialog->setDirectoryUrl(QUrl(dir));
+}
+
+QString KDE5FilePicker::getDisplayDirectory() const { return _dialog->directoryUrl().url(); }
+
+QList<QUrl> KDE5FilePicker::getSelectedFiles() const { return _dialog->selectedUrls(); }
+
+void KDE5FilePicker::appendFilter(const QString& title, const QString& filter)
+{
+ QString t = title;
+ QString f = filter;
+ // '/' need to be escaped else they are assumed to be mime types by kfiledialog
+ //see the docs
+ t.replace("/", "\\/");
+
+ // openoffice gives us filters separated by ';' qt dialogs just want space separated
+ f.replace(";", " ");
+
+ // make sure "*.*" is not used as "all files"
+ f.replace("*.*", "*");
+
+ _filters << QStringLiteral("%1 (%2)").arg(t, f);
+ _titleToFilters[t] = _filters.constLast();
+}
+
+void KDE5FilePicker::setCurrentFilter(const QString& title)
+{
+ _currentFilter = _titleToFilters.value(title);
+}
+
+QString KDE5FilePicker::getCurrentFilter() const
+{
+ QString filter = _titleToFilters.key(_dialog->selectedNameFilter());
+
+ //default if not found
+ if (filter.isEmpty())
+ filter = "ODF Text Document (.odt)";
+
+ return filter;
+}
+
+void KDE5FilePicker::setValue(sal_Int16 controlId, sal_Int16 /*nControlAction*/, bool value)
+{
+ if (_customWidgets.contains(controlId))
+ {
+ QCheckBox* cb = dynamic_cast<QCheckBox*>(_customWidgets.value(controlId));
+ if (cb)
+ cb->setChecked(value);
+ }
+ else
+ qWarning() << "set value on unknown control" << controlId;
+}
+
+bool KDE5FilePicker::getValue(sal_Int16 controlId, sal_Int16 /*nControlAction*/) const
+{
+ bool ret = false;
+ if (_customWidgets.contains(controlId))
+ {
+ QCheckBox* cb = dynamic_cast<QCheckBox*>(_customWidgets.value(controlId));
+ if (cb)
+ ret = cb->isChecked();
+ }
+ else
+ qWarning() << "get value on unknown control" << controlId;
+
+ return ret;
+}
+
+void KDE5FilePicker::enableControl(sal_Int16 controlId, bool enable)
+{
+ if (_customWidgets.contains(controlId))
+ _customWidgets.value(controlId)->setEnabled(enable);
+ else
+ qWarning() << "enable on unknown control" << controlId;
+}
+
+void KDE5FilePicker::setLabel(sal_Int16 controlId, const QString& label)
+{
+ if (_customWidgets.contains(controlId))
+ {
+ QCheckBox* cb = dynamic_cast<QCheckBox*>(_customWidgets.value(controlId));
+ if (cb)
+ cb->setText(label);
+ }
+ else
+ qWarning() << "set label on unknown control" << controlId;
+}
+
+QString KDE5FilePicker::getLabel(sal_Int16 controlId) const
+{
+ QString label;
+ if (_customWidgets.contains(controlId))
+ {
+ QCheckBox* cb = dynamic_cast<QCheckBox*>(_customWidgets.value(controlId));
+ if (cb)
+ label = cb->text();
+ }
+ else
+ qWarning() << "get label on unknown control" << controlId;
+
+ return label;
+}
+
+void KDE5FilePicker::addCheckBox(sal_Int16 controlId, const QString& label, bool hidden)
+{
+ auto resString = label;
+ resString.replace('~', '&');
+
+ auto widget = new QCheckBox(resString, _extraControls);
+ widget->setHidden(hidden);
+ if (!hidden)
+ {
+ _layout->addWidget(widget);
+ }
+ _customWidgets.insert(controlId, widget);
+}
+
+void KDE5FilePicker::initialize(bool saveDialog)
+{
+ //default is opening
+ QFileDialog::AcceptMode operationMode
+ = saveDialog ? QFileDialog::AcceptSave : QFileDialog::AcceptOpen;
+
+ _dialog->setAcceptMode(operationMode);
+
+ if (saveDialog)
+ _dialog->setFileMode(QFileDialog::AnyFile);
+}
+
+void KDE5FilePicker::setWinId(sal_uInt64 winId) { _winId = winId; }
+
+void KDE5FilePicker::setupCustomWidgets()
+{
+ // When using the platform-native Plasma/KDE5 file picker, we currently rely on KFileWidget
+ // being present to add the custom controls visible (s. 'eventFilter' method).
+ // Since this doesn't work for other desktop environments, use a non-native
+ // dialog there in order not to lose the custom controls and insert the custom
+ // widget in the layout returned by QFileDialog::layout()
+ // (which returns nullptr for native file dialogs)
+ if (Application::GetDesktopEnvironment() == "PLASMA5")
+ {
+ qApp->installEventFilter(this);
+ }
+ else
+ {
+ _dialog->setOption(QFileDialog::DontUseNativeDialog);
+ QGridLayout* pLayout = static_cast<QGridLayout*>(_dialog->layout());
+ assert(pLayout);
+ const int row = pLayout->rowCount();
+ pLayout->addWidget(_extraControls, row, 1);
+ }
+}
+
+bool KDE5FilePicker::eventFilter(QObject* o, QEvent* e)
+{
+ if (e->type() == QEvent::Show && o->isWidgetType())
+ {
+ auto* w = static_cast<QWidget*>(o);
+ if (!w->parentWidget() && w->isModal())
+ {
+ /*
+ To replace when baseline will include kwindowsystem >= 5.62 with:
+ w->setAttribute(Qt::WA_NativeWindow, true);
+ KWindowSystem::setMainWindow(w->windowHandle(), _winId);
+ */
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ KWindowSystem::setMainWindow(w, _winId);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ if (auto* fileWidget = w->findChild<KFileWidget*>({}, Qt::FindDirectChildrenOnly))
+ {
+ fileWidget->setCustomWidget(_extraControls);
+ // remove event filter again; the only purpose was to set the custom widget here
+ qApp->removeEventFilter(this);
+ }
+ }
+ }
+ return QObject::eventFilter(o, e);
+}
+
+#include <kde5_filepicker.moc>
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/kde5_filepicker.hxx b/vcl/unx/gtk3_kde5/kde5_filepicker.hxx
new file mode 100644
index 0000000000..74f94d2221
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/kde5_filepicker.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <QtCore/QObject>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtCore/QHash>
+
+#include <sal/types.h>
+
+class QFileDialog;
+class QWidget;
+class QGridLayout;
+
+class KDE5FilePicker : public QObject
+{
+ Q_OBJECT
+protected:
+ //the dialog to display
+ QFileDialog* _dialog;
+
+ //running filter string to add to dialog
+ QStringList _filters;
+ // map of filter titles to full filter for selection
+ QHash<QString, QString> _titleToFilters;
+ // string to set the current filter
+ QString _currentFilter;
+
+ //mapping of SAL control ID's to created custom controls
+ QHash<sal_Int16, QWidget*> _customWidgets;
+
+ //widget to contain extra custom controls
+ QWidget* _extraControls;
+
+ //layout for extra custom controls
+ QGridLayout* _layout;
+
+ sal_uInt64 _winId;
+
+public:
+ explicit KDE5FilePicker(QObject* parent = nullptr);
+ ~KDE5FilePicker() override;
+
+ void enableFolderMode();
+
+ // XExecutableDialog functions
+ void setTitle(const QString& rTitle);
+ bool execute();
+
+ // XFilePicker functions
+ void setMultiSelectionMode(bool bMode);
+ void setDefaultName(const QString& rName);
+ void setDisplayDirectory(const QString& rDirectory);
+ QString getDisplayDirectory() const;
+
+ // XFilterManager functions
+ void appendFilter(const QString& rTitle, const QString& rFilter);
+ void setCurrentFilter(const QString& rTitle);
+ QString getCurrentFilter() const;
+
+ // XFilePickerControlAccess functions
+ void setValue(sal_Int16 nControlId, sal_Int16 nControlAction, bool rValue);
+ bool getValue(sal_Int16 nControlId, sal_Int16 nControlAction) const;
+ void enableControl(sal_Int16 nControlId, bool bEnable);
+ void setLabel(sal_Int16 nControlId, const QString& rLabel);
+ QString getLabel(sal_Int16 nControlId) const;
+
+ // XFilePicker2 functions
+ QList<QUrl> getSelectedFiles() const;
+
+ // XInitialization
+ void initialize(bool saveDialog);
+
+ //add a custom control widget to the file dialog
+ void addCheckBox(sal_Int16 nControlId, const QString& label, bool hidden);
+
+ void setWinId(sal_uInt64 winId);
+
+private:
+ Q_DISABLE_COPY(KDE5FilePicker)
+ // adds the custom controls to the dialog
+ void setupCustomWidgets();
+
+protected:
+ bool eventFilter(QObject* watched, QEvent* event) override;
+
+Q_SIGNALS:
+ void filterChanged();
+ void selectionChanged();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.cxx b/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.cxx
new file mode 100644
index 0000000000..7f0bfff985
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.cxx
@@ -0,0 +1,374 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "kde5_filepicker_ipc.hxx"
+
+#include <QUrl>
+#include <QApplication>
+#include <QDebug>
+
+#include <iostream>
+
+#include "kde5_filepicker.hxx"
+
+void readIpcArg(std::istream& stream, QString& string)
+{
+ const auto buffer = readIpcStringArg(stream);
+ string = QString::fromUtf8(buffer.data(), buffer.size());
+}
+
+void sendIpcArg(std::ostream& stream, const QString& string)
+{
+ const auto utf8 = string.toUtf8();
+ sendIpcStringArg(stream, utf8.size(), utf8.data());
+}
+
+static void sendIpcArg(std::ostream& stream, const QStringList& list)
+{
+ stream << static_cast<uint32_t>(list.size()) << ' ';
+ for (const auto& entry : list)
+ {
+ sendIpcArg(stream, entry);
+ }
+}
+
+static void readCommandArgs(Commands command, QList<QVariant>& args)
+{
+ switch (command)
+ {
+ case Commands::SetTitle:
+ {
+ QString title;
+ readIpcArgs(std::cin, title);
+ args.append(title);
+ break;
+ }
+ case Commands::SetWinId:
+ {
+ sal_uIntPtr winId = 0;
+ readIpcArgs(std::cin, winId);
+ QVariant aWinIdVariant;
+ aWinIdVariant.setValue(winId);
+ args.append(aWinIdVariant);
+ break;
+ }
+ case Commands::SetMultiSelectionMode:
+ {
+ bool multiSelection = false;
+ readIpcArgs(std::cin, multiSelection);
+ args.append(multiSelection);
+ break;
+ }
+ case Commands::SetDefaultName:
+ {
+ QString name;
+ readIpcArgs(std::cin, name);
+ args.append(name);
+ break;
+ }
+ case Commands::SetDisplayDirectory:
+ {
+ QString dir;
+ readIpcArgs(std::cin, dir);
+ args.append(dir);
+ break;
+ }
+ case Commands::AppendFilter:
+ {
+ QString title, filter;
+ readIpcArgs(std::cin, title, filter);
+ args.append(title);
+ args.append(filter);
+ break;
+ }
+ case Commands::SetCurrentFilter:
+ {
+ QString title;
+ readIpcArgs(std::cin, title);
+ args.append(title);
+ break;
+ }
+ case Commands::SetValue:
+ {
+ sal_Int16 controlId = 0;
+ sal_Int16 nControlAction = 0;
+ bool value = false;
+ readIpcArgs(std::cin, controlId, nControlAction, value);
+ args.append(controlId);
+ args.append(nControlAction);
+ args.append(value);
+ break;
+ }
+ case Commands::GetValue:
+ {
+ sal_Int16 controlId = 0;
+ sal_Int16 nControlAction = 0;
+ readIpcArgs(std::cin, controlId, nControlAction);
+ args.append(controlId);
+ args.append(nControlAction);
+ break;
+ }
+ case Commands::EnableControl:
+ {
+ sal_Int16 controlId = 0;
+ bool enabled = false;
+ readIpcArgs(std::cin, controlId, enabled);
+ args.append(controlId);
+ args.append(enabled);
+ break;
+ }
+ case Commands::SetLabel:
+ {
+ sal_Int16 controlId = 0;
+ QString label;
+ readIpcArgs(std::cin, controlId, label);
+ args.append(controlId);
+ args.append(label);
+ break;
+ }
+ case Commands::GetLabel:
+ {
+ sal_Int16 controlId = 0;
+ readIpcArgs(std::cin, controlId);
+ args.append(controlId);
+ break;
+ }
+ case Commands::AddCheckBox:
+ {
+ sal_Int16 controlId = 0;
+ bool hidden = false;
+ QString label;
+ readIpcArgs(std::cin, controlId, hidden, label);
+ args.append(controlId);
+ args.append(hidden);
+ args.append(label);
+ break;
+ }
+ case Commands::Initialize:
+ {
+ bool saveDialog = false;
+ readIpcArgs(std::cin, saveDialog);
+ args.append(saveDialog);
+ break;
+ }
+ default:
+ {
+ // no extra parameters/arguments
+ break;
+ }
+ };
+}
+
+static void readCommands(FilePickerIpc* ipc)
+{
+ while (!std::cin.eof())
+ {
+ uint64_t messageId = 0;
+ Commands command;
+ readIpcArgs(std::cin, messageId, command);
+
+ // retrieve additional command-specific arguments
+ QList<QVariant> args;
+ readCommandArgs(command, args);
+
+ emit ipc->commandReceived(messageId, command, args);
+
+ // stop processing once 'Quit' command has been sent
+ if (command == Commands::Quit)
+ {
+ return;
+ }
+ }
+}
+
+FilePickerIpc::FilePickerIpc(KDE5FilePicker* filePicker, QObject* parent)
+ : QObject(parent)
+ , m_filePicker(filePicker)
+{
+ // required to be able to pass those via signal/slot
+ qRegisterMetaType<uint64_t>("uint64_t");
+ qRegisterMetaType<Commands>("Commands");
+
+ connect(this, &FilePickerIpc::commandReceived, this, &FilePickerIpc::handleCommand);
+
+ // read IPC commands and their args in a separate thread, so this does not block everything else;
+ // 'commandReceived' signal is emitted every time a command and its args have been read;
+ // thread will run until the filepicker process is terminated
+ m_ipcReaderThread = std::make_unique<std::thread>(readCommands, this);
+}
+
+FilePickerIpc::~FilePickerIpc()
+{
+ // join thread that reads commands
+ m_ipcReaderThread->join();
+};
+
+bool FilePickerIpc::handleCommand(uint64_t messageId, Commands command, QList<QVariant> args)
+{
+ switch (command)
+ {
+ case Commands::SetTitle:
+ {
+ QString title = args.takeFirst().toString();
+ m_filePicker->setTitle(title);
+ return true;
+ }
+ case Commands::SetWinId:
+ {
+ sal_uIntPtr winId = args.takeFirst().value<sal_uIntPtr>();
+ m_filePicker->setWinId(winId);
+ return true;
+ }
+ case Commands::Execute:
+ {
+ sendIpcArgs(std::cout, messageId, m_filePicker->execute());
+ return true;
+ }
+ case Commands::SetMultiSelectionMode:
+ {
+ bool multiSelection = args.takeFirst().toBool();
+ m_filePicker->setMultiSelectionMode(multiSelection);
+ return true;
+ }
+ case Commands::SetDefaultName:
+ {
+ QString name = args.takeFirst().toString();
+ m_filePicker->setDefaultName(name);
+ return true;
+ }
+ case Commands::SetDisplayDirectory:
+ {
+ QString dir = args.takeFirst().toString();
+ m_filePicker->setDisplayDirectory(dir);
+ return true;
+ }
+ case Commands::GetDisplayDirectory:
+ {
+ sendIpcArgs(std::cout, messageId, m_filePicker->getDisplayDirectory());
+ return true;
+ }
+ case Commands::GetSelectedFiles:
+ {
+ QStringList files;
+ for (auto const& url_ : m_filePicker->getSelectedFiles())
+ {
+ auto url = url_;
+ if (url.scheme() == QLatin1String("webdav")
+ || url.scheme() == QLatin1String("webdavs"))
+ {
+ // translate webdav and webdavs URLs into a format supported by LO
+ url.setScheme(QLatin1String("vnd.sun.star.") + url.scheme());
+ }
+ else if (url.scheme() == QLatin1String("smb"))
+ {
+ // clear the user name - the GIO backend does not support this apparently
+ // when no username is available, it will ask for the password
+ url.setUserName({});
+ }
+ files << url.toString();
+ }
+ sendIpcArgs(std::cout, messageId, files);
+ return true;
+ }
+ case Commands::AppendFilter:
+ {
+ QString title = args.takeFirst().toString();
+ QString filter = args.takeFirst().toString();
+ m_filePicker->appendFilter(title, filter);
+ return true;
+ }
+ case Commands::SetCurrentFilter:
+ {
+ QString title = args.takeFirst().toString();
+ m_filePicker->setCurrentFilter(title);
+ return true;
+ }
+ case Commands::GetCurrentFilter:
+ {
+ sendIpcArgs(std::cout, messageId, m_filePicker->getCurrentFilter());
+ return true;
+ }
+ case Commands::SetValue:
+ {
+ sal_Int16 controlId = args.takeFirst().value<sal_Int16>();
+ sal_Int16 nControlAction = args.takeFirst().value<sal_Int16>();
+ bool value = args.takeFirst().toBool();
+ m_filePicker->setValue(controlId, nControlAction, value);
+ return true;
+ }
+ case Commands::GetValue:
+ {
+ sal_Int16 controlId = args.takeFirst().value<sal_Int16>();
+ sal_Int16 nControlAction = args.takeFirst().value<sal_Int16>();
+ sendIpcArgs(std::cout, messageId, m_filePicker->getValue(controlId, nControlAction));
+ return true;
+ }
+ case Commands::EnableControl:
+ {
+ sal_Int16 controlId = args.takeFirst().value<sal_Int16>();
+ bool enabled = args.takeFirst().toBool();
+ m_filePicker->enableControl(controlId, enabled);
+ return true;
+ }
+ case Commands::SetLabel:
+ {
+ sal_Int16 controlId = args.takeFirst().value<sal_Int16>();
+ QString label = args.takeFirst().toString();
+ m_filePicker->setLabel(controlId, label);
+ return true;
+ }
+ case Commands::GetLabel:
+ {
+ sal_Int16 controlId = args.takeFirst().value<sal_Int16>();
+ sendIpcArgs(std::cout, messageId, m_filePicker->getLabel(controlId));
+ return true;
+ }
+ case Commands::AddCheckBox:
+ {
+ sal_Int16 controlId = args.takeFirst().value<sal_Int16>();
+ bool hidden = args.takeFirst().toBool();
+ QString label = args.takeFirst().toString();
+ m_filePicker->addCheckBox(controlId, label, hidden);
+ return true;
+ }
+ case Commands::Initialize:
+ {
+ bool saveDialog = args.takeFirst().toBool();
+ m_filePicker->initialize(saveDialog);
+ return true;
+ }
+ case Commands::EnablePickFolderMode:
+ {
+ m_filePicker->enableFolderMode();
+ return true;
+ }
+ case Commands::Quit:
+ {
+ QCoreApplication::quit();
+ return false;
+ }
+ }
+ qWarning() << "unhandled command " << static_cast<uint16_t>(command);
+ QCoreApplication::exit(1);
+ return false;
+}
+
+#include <kde5_filepicker_ipc.moc>
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.hxx b/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.hxx
new file mode 100644
index 0000000000..fa9be696c5
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.hxx
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <memory>
+#include <thread>
+
+#include <QObject>
+
+#include "filepicker_ipc_commands.hxx"
+
+class KDE5FilePicker;
+class WinIdEmbedder;
+class QSocketNotifier;
+
+class FilePickerIpc : public QObject
+{
+ Q_OBJECT
+public:
+ explicit FilePickerIpc(KDE5FilePicker* filePicker, QObject* parent = nullptr);
+ ~FilePickerIpc() override;
+
+private:
+ KDE5FilePicker* m_filePicker = nullptr;
+ std::unique_ptr<std::thread> m_ipcReaderThread;
+
+private Q_SLOTS:
+ bool handleCommand(uint64_t messageId, Commands command, QList<QVariant> args);
+
+Q_SIGNALS:
+ bool commandReceived(uint64_t messageId, Commands command, QList<QVariant> args);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/kde5_lo_filepicker_main.cxx b/vcl/unx/gtk3_kde5/kde5_lo_filepicker_main.cxx
new file mode 100644
index 0000000000..4e73e9ee45
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/kde5_lo_filepicker_main.cxx
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "kde5_filepicker.hxx"
+#include "kde5_filepicker_ipc.hxx"
+
+#include <QApplication>
+#include <QCommandLineParser>
+
+#include <config_version.h>
+
+int main(int argc, char** argv)
+{
+ QApplication::setOrganizationName("LibreOffice");
+ QApplication::setOrganizationDomain("libreoffice.org");
+ QApplication::setApplicationName(QStringLiteral("lo_kde5filepicker"));
+ QApplication::setQuitOnLastWindowClosed(false);
+ QApplication::setApplicationVersion(LIBO_VERSION_DOTTED);
+
+ QApplication app(argc, argv);
+
+ QCommandLineParser parser;
+ parser.setApplicationDescription(
+ QObject::tr("Helper executable for LibreOffice KDE/Plasma integration.\n"
+ "Do not run this executable directly. Rather, use it indirectly via "
+ "the gtk3_kde5 VCL plugin (SAL_USE_VCLPLUGIN=gtk3_kde5)."));
+ parser.addVersionOption();
+ parser.addHelpOption();
+ parser.process(app);
+
+ KDE5FilePicker filePicker;
+ FilePickerIpc ipc(&filePicker);
+
+ return QApplication::exec();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/a11y.cxx b/vcl/unx/gtk4/a11y.cxx
new file mode 100644
index 0000000000..c8103471b0
--- /dev/null
+++ b/vcl/unx/gtk4/a11y.cxx
@@ -0,0 +1,851 @@
+/* -*- 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 <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/XAccessibleComponent.hpp>
+#include <com/sun/star/accessibility/XAccessibleExtendedAttributes.hpp>
+#include <com/sun/star/accessibility/XAccessibleText.hpp>
+#include <com/sun/star/accessibility/XAccessibleValue.hpp>
+#include <unx/gtk/gtkframe.hxx>
+#include <gtk/gtk.h>
+#include <o3tl/string_view.hxx>
+
+#if GTK_CHECK_VERSION(4, 9, 0)
+
+#include "a11y.hxx"
+
+#define OOO_TYPE_FIXED (ooo_fixed_get_type())
+#define OOO_FIXED(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), OOO_TYPE_FIXED, OOoFixed))
+// #define OOO_IS_FIXED(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), OOO_TYPE_FIXED))
+
+struct OOoFixed
+{
+ GtkFixed parent_instance;
+ GtkATContext* at_context;
+};
+
+struct OOoFixedClass
+{
+ GtkFixedClass parent_class;
+};
+
+static GtkAccessibleRole
+map_accessible_role(const css::uno::Reference<css::accessibility::XAccessible>& rAccessible)
+{
+ GtkAccessibleRole eRole(GTK_ACCESSIBLE_ROLE_WIDGET);
+
+ if (rAccessible.is())
+ {
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext(
+ rAccessible->getAccessibleContext());
+ // https://w3c.github.io/core-aam/#mapping_role
+ // https://hg.mozilla.org/mozilla-central/file/tip/accessible/base/RoleMap.h
+ // https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gtk/a11y/gtkatspiutils.c
+ switch (xContext->getAccessibleRole())
+ {
+ case css::accessibility::AccessibleRole::ALERT:
+ case css::accessibility::AccessibleRole::NOTIFICATION:
+ eRole = GTK_ACCESSIBLE_ROLE_ALERT;
+ break;
+ case css::accessibility::AccessibleRole::BLOCK_QUOTE:
+#if GTK_CHECK_VERSION(4, 13, 4)
+ eRole = GTK_ACCESSIBLE_ROLE_BLOCK_QUOTE;
+#else
+ eRole = GTK_ACCESSIBLE_ROLE_GROUP;
+#endif
+ break;
+ case css::accessibility::AccessibleRole::CAPTION:
+ eRole = GTK_ACCESSIBLE_ROLE_CAPTION;
+ break;
+ case css::accessibility::AccessibleRole::COLUMN_HEADER:
+ eRole = GTK_ACCESSIBLE_ROLE_COLUMN_HEADER;
+ break;
+ case css::accessibility::AccessibleRole::COMBO_BOX:
+ eRole = GTK_ACCESSIBLE_ROLE_COMBO_BOX;
+ break;
+ case css::accessibility::AccessibleRole::DIALOG:
+ case css::accessibility::AccessibleRole::FILE_CHOOSER:
+ eRole = GTK_ACCESSIBLE_ROLE_DIALOG;
+ break;
+ case css::accessibility::AccessibleRole::FOOTNOTE:
+ case css::accessibility::AccessibleRole::NOTE:
+ eRole = GTK_ACCESSIBLE_ROLE_NOTE;
+ break;
+ case css::accessibility::AccessibleRole::FORM:
+ eRole = GTK_ACCESSIBLE_ROLE_FORM;
+ break;
+ case css::accessibility::AccessibleRole::HEADING:
+ eRole = GTK_ACCESSIBLE_ROLE_HEADING;
+ break;
+ case css::accessibility::AccessibleRole::HYPER_LINK:
+ eRole = GTK_ACCESSIBLE_ROLE_LINK;
+ break;
+ case css::accessibility::AccessibleRole::PANEL:
+ eRole = GTK_ACCESSIBLE_ROLE_GROUP;
+ break;
+ case css::accessibility::AccessibleRole::ROOT_PANE:
+ eRole = GTK_ACCESSIBLE_ROLE_GROUP;
+ break;
+ case css::accessibility::AccessibleRole::MENU_BAR:
+ eRole = GTK_ACCESSIBLE_ROLE_MENU_BAR;
+ break;
+ case css::accessibility::AccessibleRole::STATUS_BAR:
+ eRole = GTK_ACCESSIBLE_ROLE_STATUS;
+ break;
+ case css::accessibility::AccessibleRole::MENU:
+ case css::accessibility::AccessibleRole::POPUP_MENU:
+ eRole = GTK_ACCESSIBLE_ROLE_MENU;
+ break;
+ case css::accessibility::AccessibleRole::SPLIT_PANE:
+ eRole = GTK_ACCESSIBLE_ROLE_GROUP;
+ break;
+ case css::accessibility::AccessibleRole::TOOL_BAR:
+ eRole = GTK_ACCESSIBLE_ROLE_TOOLBAR;
+ break;
+ case css::accessibility::AccessibleRole::LABEL:
+ case css::accessibility::AccessibleRole::STATIC:
+ eRole = GTK_ACCESSIBLE_ROLE_LABEL;
+ break;
+ case css::accessibility::AccessibleRole::LIST:
+ eRole = GTK_ACCESSIBLE_ROLE_LIST;
+ break;
+ case css::accessibility::AccessibleRole::LIST_ITEM:
+ eRole = GTK_ACCESSIBLE_ROLE_LIST_ITEM;
+ break;
+ case css::accessibility::AccessibleRole::MENU_ITEM:
+ eRole = GTK_ACCESSIBLE_ROLE_MENU_ITEM;
+ break;
+ case css::accessibility::AccessibleRole::SEPARATOR:
+ eRole = GTK_ACCESSIBLE_ROLE_SEPARATOR;
+ break;
+ case css::accessibility::AccessibleRole::CHECK_BOX:
+ eRole = GTK_ACCESSIBLE_ROLE_CHECKBOX;
+ break;
+ case css::accessibility::AccessibleRole::CHECK_MENU_ITEM:
+ eRole = GTK_ACCESSIBLE_ROLE_MENU_ITEM_CHECKBOX;
+ break;
+ case css::accessibility::AccessibleRole::RADIO_MENU_ITEM:
+ eRole = GTK_ACCESSIBLE_ROLE_MENU_ITEM_RADIO;
+ break;
+ case css::accessibility::AccessibleRole::DOCUMENT:
+ case css::accessibility::AccessibleRole::DOCUMENT_PRESENTATION:
+ case css::accessibility::AccessibleRole::DOCUMENT_SPREADSHEET:
+ case css::accessibility::AccessibleRole::DOCUMENT_TEXT:
+ eRole = GTK_ACCESSIBLE_ROLE_DOCUMENT;
+ break;
+ case css::accessibility::AccessibleRole::ROW_HEADER:
+ eRole = GTK_ACCESSIBLE_ROLE_ROW_HEADER;
+ break;
+ case css::accessibility::AccessibleRole::RULER:
+ eRole = GTK_ACCESSIBLE_ROLE_WIDGET;
+ break;
+ case css::accessibility::AccessibleRole::PARAGRAPH:
+#if GTK_CHECK_VERSION(4, 13, 1)
+ eRole = GTK_ACCESSIBLE_ROLE_PARAGRAPH;
+#else
+ eRole = GTK_ACCESSIBLE_ROLE_GROUP;
+#endif
+ break;
+ case css::accessibility::AccessibleRole::FILLER:
+ eRole = GTK_ACCESSIBLE_ROLE_GENERIC;
+ break;
+ case css::accessibility::AccessibleRole::PUSH_BUTTON:
+ case css::accessibility::AccessibleRole::BUTTON_DROPDOWN:
+ case css::accessibility::AccessibleRole::BUTTON_MENU:
+ eRole = GTK_ACCESSIBLE_ROLE_BUTTON;
+ break;
+ case css::accessibility::AccessibleRole::TOGGLE_BUTTON:
+ eRole = GTK_ACCESSIBLE_ROLE_TOGGLE_BUTTON;
+ break;
+ case css::accessibility::AccessibleRole::TABLE:
+ eRole = GTK_ACCESSIBLE_ROLE_TABLE;
+ break;
+ case css::accessibility::AccessibleRole::TABLE_CELL:
+ eRole = GTK_ACCESSIBLE_ROLE_CELL;
+ break;
+ case css::accessibility::AccessibleRole::PAGE_TAB:
+ eRole = GTK_ACCESSIBLE_ROLE_TAB;
+ break;
+ case css::accessibility::AccessibleRole::PAGE_TAB_LIST:
+ eRole = GTK_ACCESSIBLE_ROLE_TAB_LIST;
+ break;
+ case css::accessibility::AccessibleRole::PROGRESS_BAR:
+ eRole = GTK_ACCESSIBLE_ROLE_PROGRESS_BAR;
+ break;
+ case css::accessibility::AccessibleRole::RADIO_BUTTON:
+ eRole = GTK_ACCESSIBLE_ROLE_RADIO;
+ break;
+ case css::accessibility::AccessibleRole::SCROLL_BAR:
+ eRole = GTK_ACCESSIBLE_ROLE_SCROLLBAR;
+ break;
+ case css::accessibility::AccessibleRole::SLIDER:
+ eRole = GTK_ACCESSIBLE_ROLE_SLIDER;
+ break;
+ case css::accessibility::AccessibleRole::SPIN_BOX:
+ eRole = GTK_ACCESSIBLE_ROLE_SPIN_BUTTON;
+ break;
+ case css::accessibility::AccessibleRole::TEXT:
+ case css::accessibility::AccessibleRole::PASSWORD_TEXT:
+ eRole = GTK_ACCESSIBLE_ROLE_TEXT_BOX;
+ break;
+ case css::accessibility::AccessibleRole::TOOL_TIP:
+ eRole = GTK_ACCESSIBLE_ROLE_TOOLTIP;
+ break;
+ case css::accessibility::AccessibleRole::TREE:
+ eRole = GTK_ACCESSIBLE_ROLE_TREE;
+ break;
+ case css::accessibility::AccessibleRole::TREE_ITEM:
+ eRole = GTK_ACCESSIBLE_ROLE_TREE_ITEM;
+ break;
+ case css::accessibility::AccessibleRole::TREE_TABLE:
+ eRole = GTK_ACCESSIBLE_ROLE_TREE_GRID;
+ break;
+ case css::accessibility::AccessibleRole::GRAPHIC:
+ case css::accessibility::AccessibleRole::ICON:
+ case css::accessibility::AccessibleRole::SHAPE:
+ eRole = GTK_ACCESSIBLE_ROLE_IMG;
+ break;
+ default:
+ SAL_WARN("vcl.gtk",
+ "unmapped GtkAccessibleRole: " << xContext->getAccessibleRole());
+ break;
+ }
+ }
+
+ return eRole;
+}
+
+static css::uno::Reference<css::accessibility::XAccessible> get_uno_accessible(GtkWidget* pWidget)
+{
+ GtkWidget* pTopLevel = widget_get_toplevel(pWidget);
+ if (!pTopLevel)
+ return nullptr;
+
+ GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pTopLevel);
+ if (!pFrame)
+ return nullptr;
+
+ vcl::Window* pFrameWindow = pFrame->GetWindow();
+ if (!pFrameWindow)
+ return nullptr;
+
+ vcl::Window* pWindow = pFrameWindow;
+
+ // skip accessible objects already exposed by the frame objects
+ if (WindowType::BORDERWINDOW == pWindow->GetType())
+ pWindow = pFrameWindow->GetAccessibleChildWindow(0);
+
+ if (!pWindow)
+ return nullptr;
+
+ return pWindow->GetAccessible();
+}
+
+/**
+ * Based on the states set in xContext, set the corresponding Gtk states/properties
+ * in pGtkAccessible.
+ */
+static void applyStates(GtkAccessible* pGtkAccessible,
+ css::uno::Reference<css::accessibility::XAccessibleContext>& xContext)
+{
+ assert(pGtkAccessible);
+
+ if (!xContext.is())
+ return;
+
+ // Gtk differentiates between GtkAccessibleState and GtkAccessibleProperty
+ // (both handled here) and GtkAccessiblePlatformState (handled in
+ // 'lo_accessible_get_platform_state')
+ const sal_Int64 nStates = xContext->getAccessibleStateSet();
+ gtk_accessible_update_property(
+ pGtkAccessible, GTK_ACCESSIBLE_PROPERTY_MODAL,
+ bool(nStates & com::sun::star::accessibility::AccessibleStateType::MODAL),
+ GTK_ACCESSIBLE_PROPERTY_MULTI_LINE,
+ bool(nStates & com::sun::star::accessibility::AccessibleStateType::MULTI_LINE),
+ GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE,
+ bool(nStates & com::sun::star::accessibility::AccessibleStateType::MULTI_SELECTABLE),
+ GTK_ACCESSIBLE_PROPERTY_READ_ONLY,
+ bool(!(nStates & com::sun::star::accessibility::AccessibleStateType::EDITABLE)), -1);
+ if (nStates & com::sun::star::accessibility::AccessibleStateType::HORIZONTAL)
+ {
+ gtk_accessible_update_property(pGtkAccessible, GTK_ACCESSIBLE_PROPERTY_ORIENTATION,
+ GTK_ORIENTATION_HORIZONTAL, -1);
+ }
+ else if (nStates & com::sun::star::accessibility::AccessibleStateType::VERTICAL)
+ {
+ gtk_accessible_update_property(pGtkAccessible, GTK_ACCESSIBLE_PROPERTY_ORIENTATION,
+ GTK_ORIENTATION_VERTICAL, -1);
+ }
+
+ gtk_accessible_update_state(
+ pGtkAccessible, GTK_ACCESSIBLE_STATE_BUSY,
+ bool(nStates & com::sun::star::accessibility::AccessibleStateType::BUSY),
+ GTK_ACCESSIBLE_STATE_DISABLED,
+ bool(!(nStates & com::sun::star::accessibility::AccessibleStateType::ENABLED)),
+ GTK_ACCESSIBLE_STATE_EXPANDED,
+ bool(nStates & com::sun::star::accessibility::AccessibleStateType::EXPANDED),
+ GTK_ACCESSIBLE_STATE_SELECTED,
+ bool(nStates & com::sun::star::accessibility::AccessibleStateType::SELECTED), -1);
+
+ // when explicitly setting any value for GTK_ACCESSIBLE_STATE_CHECKED,
+ // Gtk will also report ATSPI_STATE_CHECKABLE on the AT-SPI layer
+ if (nStates & com::sun::star::accessibility::AccessibleStateType::CHECKABLE)
+ {
+ GtkAccessibleTristate eState = GTK_ACCESSIBLE_TRISTATE_FALSE;
+ if (nStates & com::sun::star::accessibility::AccessibleStateType::INDETERMINATE)
+ eState = GTK_ACCESSIBLE_TRISTATE_MIXED;
+ else if (nStates & com::sun::star::accessibility::AccessibleStateType::CHECKED)
+ eState = GTK_ACCESSIBLE_TRISTATE_TRUE;
+ gtk_accessible_update_state(pGtkAccessible, GTK_ACCESSIBLE_STATE_CHECKED, eState, -1);
+ }
+
+ const sal_Int16 nRole = xContext->getAccessibleRole();
+ if (nRole == com::sun::star::accessibility::AccessibleRole::TOGGLE_BUTTON)
+ {
+ GtkAccessibleTristate eState = GTK_ACCESSIBLE_TRISTATE_FALSE;
+ if (nStates & com::sun::star::accessibility::AccessibleStateType::INDETERMINATE)
+ eState = GTK_ACCESSIBLE_TRISTATE_MIXED;
+ else if (nStates & com::sun::star::accessibility::AccessibleStateType::PRESSED)
+ eState = GTK_ACCESSIBLE_TRISTATE_TRUE;
+ gtk_accessible_update_state(pGtkAccessible, GTK_ACCESSIBLE_STATE_PRESSED, eState, -1);
+ }
+}
+
+static void applyObjectAttribute(GtkAccessible* pGtkAccessible, std::u16string_view rName,
+ const OUString& rValue)
+{
+ assert(pGtkAccessible);
+
+ if (rName == u"colindextext")
+ {
+ gtk_accessible_update_relation(pGtkAccessible, GTK_ACCESSIBLE_RELATION_COL_INDEX_TEXT,
+ rValue.toUtf8().getStr(), -1);
+ }
+ else if (rName == u"level")
+ {
+ const int nLevel = o3tl::toInt32(rValue);
+ gtk_accessible_update_property(pGtkAccessible, GTK_ACCESSIBLE_PROPERTY_LEVEL, nLevel, -1);
+ }
+ else if (rName == u"rowindextext")
+ {
+ gtk_accessible_update_relation(pGtkAccessible, GTK_ACCESSIBLE_RELATION_ROW_INDEX_TEXT,
+ rValue.toUtf8().getStr(), -1);
+ }
+}
+
+/**
+ * Based on the object attributes set for xContext, set the corresponding Gtk equivalents
+ * in pGtkAccessible, where applicable.
+ */
+static void
+applyObjectAttributes(GtkAccessible* pGtkAccessible,
+ css::uno::Reference<css::accessibility::XAccessibleContext>& xContext)
+{
+ assert(pGtkAccessible);
+
+ css::uno::Reference<css::accessibility::XAccessibleExtendedAttributes> xAttributes(
+ xContext, css::uno::UNO_QUERY);
+ if (!xAttributes.is())
+ return;
+
+ OUString sAttrs;
+ xAttributes->getExtendedAttributes() >>= sAttrs;
+
+ sal_Int32 nIndex = 0;
+ do
+ {
+ const OUString sAttribute = sAttrs.getToken(0, ';', nIndex);
+ sal_Int32 nColonPos = 0;
+ const OUString sName = sAttribute.getToken(0, ':', nColonPos);
+ const OUString sValue = sAttribute.getToken(0, ':', nColonPos);
+ assert(nColonPos == -1
+ && "Too many colons in attribute that should have \"name:value\" syntax");
+
+ applyObjectAttribute(pGtkAccessible, sName, sValue);
+ } while (nIndex >= 0);
+}
+
+#define LO_TYPE_ACCESSIBLE (lo_accessible_get_type())
+#define LO_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), LO_TYPE_ACCESSIBLE, LoAccessible))
+// #define LO_IS_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), LO_TYPE_ACCESSIBLE))
+
+struct LoAccessible
+{
+ GObject parent_instance;
+ GdkDisplay* display;
+ GtkAccessible* parent;
+ GtkATContext* at_context;
+ css::uno::Reference<css::accessibility::XAccessible> uno_accessible;
+};
+
+struct LoAccessibleClass
+{
+ GObjectClass parent_class;
+};
+
+#if GTK_CHECK_VERSION(4, 10, 0)
+static void lo_accessible_range_init(GtkAccessibleRangeInterface* iface);
+static gboolean lo_accessible_range_set_current_value(GtkAccessibleRange* self, double fNewValue);
+#endif
+
+extern "C" {
+typedef GType (*GetGIfaceType)();
+}
+const struct
+{
+ const char* name;
+ GInterfaceInitFunc const aInit;
+ GetGIfaceType const aGetGIfaceType;
+ const css::uno::Type& (*aGetUnoType)();
+} TYPE_TABLE[] = {
+#if GTK_CHECK_VERSION(4, 10, 0)
+ { "Value", reinterpret_cast<GInterfaceInitFunc>(lo_accessible_range_init),
+ gtk_accessible_range_get_type, cppu::UnoType<css::accessibility::XAccessibleValue>::get }
+#endif
+};
+
+static bool isOfType(css::uno::XInterface* xInterface, const css::uno::Type& rType)
+{
+ if (!xInterface)
+ return false;
+
+ try
+ {
+ css::uno::Any aRet = xInterface->queryInterface(rType);
+ const bool bIs = (typelib_TypeClass_INTERFACE == aRet.pType->eTypeClass)
+ && (aRet.pReserved != nullptr);
+ return bIs;
+ }
+ catch (const css::uno::Exception&)
+ {
+ return false;
+ }
+}
+
+static GType ensureTypeFor(css::uno::XInterface* xAccessible)
+{
+ OStringBuffer aTypeNameBuf("OOoGtkAccessibleObj");
+ std::vector<bool> bTypes(std::size(TYPE_TABLE), false);
+ for (size_t i = 0; i < std::size(TYPE_TABLE); i++)
+ {
+ if (isOfType(xAccessible, TYPE_TABLE[i].aGetUnoType()))
+ {
+ aTypeNameBuf.append(TYPE_TABLE[i].name);
+ bTypes[i] = true;
+ }
+ }
+
+ const OString aTypeName = aTypeNameBuf.makeStringAndClear();
+ GType nType = g_type_from_name(aTypeName.getStr());
+ if (nType != G_TYPE_INVALID)
+ return nType;
+
+ GTypeInfo aTypeInfo = { sizeof(LoAccessibleClass), nullptr, nullptr, nullptr, nullptr, nullptr,
+ sizeof(LoAccessible), 0, nullptr, nullptr };
+ nType
+ = g_type_register_static(LO_TYPE_ACCESSIBLE, aTypeName.getStr(), &aTypeInfo, GTypeFlags(0));
+
+ for (size_t i = 0; i < std::size(TYPE_TABLE); i++)
+ {
+ if (bTypes[i])
+ {
+ GInterfaceInfo aIfaceInfo = { nullptr, nullptr, nullptr };
+ aIfaceInfo.interface_init = TYPE_TABLE[i].aInit;
+ g_type_add_interface_static(nType, TYPE_TABLE[i].aGetGIfaceType(), &aIfaceInfo);
+ }
+ }
+ return nType;
+}
+
+enum
+{
+ CHILD_PROP_0,
+ LAST_CHILD_PROP,
+
+ PROP_ACCESSIBLE_ROLE
+};
+
+static void lo_accessible_get_property(GObject* object, guint property_id, GValue* value,
+ GParamSpec* pspec)
+{
+ LoAccessible* accessible = LO_ACCESSIBLE(object);
+
+ switch (property_id)
+ {
+ case PROP_ACCESSIBLE_ROLE:
+ {
+ GtkAccessibleRole eRole(map_accessible_role(accessible->uno_accessible));
+ g_value_set_enum(value, eRole);
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void lo_accessible_set_property(GObject* object, guint property_id, const GValue* /*value*/,
+ GParamSpec* pspec)
+{
+ // LoAccessible* accessible = LO_ACCESSIBLE(object);
+
+ switch (property_id)
+ {
+ case PROP_ACCESSIBLE_ROLE:
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static GtkAccessible* lo_accessible_get_accessible_parent(GtkAccessible* accessible)
+{
+ LoAccessible* lo_accessible = LO_ACCESSIBLE(accessible);
+ if (!lo_accessible->parent)
+ return nullptr;
+ return GTK_ACCESSIBLE(g_object_ref(lo_accessible->parent));
+}
+
+static GtkATContext* lo_accessible_get_at_context(GtkAccessible* self)
+{
+ LoAccessible* pAccessible = LO_ACCESSIBLE(self);
+
+ GtkAccessibleRole eRole = map_accessible_role(pAccessible->uno_accessible);
+
+ if (!pAccessible->at_context
+ || gtk_at_context_get_accessible_role(pAccessible->at_context) != eRole)
+ {
+ pAccessible->at_context = gtk_at_context_create(eRole, self, pAccessible->display);
+ if (!pAccessible->at_context)
+ return nullptr;
+ }
+
+ return g_object_ref(pAccessible->at_context);
+}
+
+static GtkAccessible* lo_accessible_get_first_accessible_child(GtkAccessible* self);
+
+static GtkAccessible* lo_accessible_get_next_accessible_sibling(GtkAccessible* self);
+
+static gboolean lo_accessible_get_platform_state(GtkAccessible* self,
+ GtkAccessiblePlatformState state);
+
+static gboolean lo_accessible_get_bounds(GtkAccessible* accessible, int* x, int* y, int* width,
+ int* height);
+
+static void lo_accessible_accessible_init(GtkAccessibleInterface* iface)
+{
+ iface->get_accessible_parent = lo_accessible_get_accessible_parent;
+ iface->get_at_context = lo_accessible_get_at_context;
+ iface->get_bounds = lo_accessible_get_bounds;
+ iface->get_first_accessible_child = lo_accessible_get_first_accessible_child;
+ iface->get_next_accessible_sibling = lo_accessible_get_next_accessible_sibling;
+ iface->get_platform_state = lo_accessible_get_platform_state;
+}
+
+#if GTK_CHECK_VERSION(4, 10, 0)
+static void lo_accessible_range_init(GtkAccessibleRangeInterface* iface)
+{
+ iface->set_current_value = lo_accessible_range_set_current_value;
+}
+#endif
+
+G_DEFINE_TYPE_WITH_CODE(LoAccessible, lo_accessible, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE(GTK_TYPE_ACCESSIBLE, lo_accessible_accessible_init))
+
+static void lo_accessible_class_init(LoAccessibleClass* klass)
+{
+ GObjectClass* object_class = G_OBJECT_CLASS(klass);
+
+ // object_class->finalize = lo_accessible_finalize;
+ // object_class->dispose = lo_accessible_dispose;
+ object_class->get_property = lo_accessible_get_property;
+ object_class->set_property = lo_accessible_set_property;
+ // object_class->constructed = lo_accessible_constructed;
+
+ // g_object_class_install_properties(object_class, LAST_CHILD_PROP, lo_accessible_props);
+ g_object_class_override_property(object_class, PROP_ACCESSIBLE_ROLE, "accessible-role");
+}
+
+static LoAccessible*
+lo_accessible_new(GdkDisplay* pDisplay, GtkAccessible* pParent,
+ const css::uno::Reference<css::accessibility::XAccessible>& rAccessible)
+{
+ assert(rAccessible.is());
+
+ GType nType = ensureTypeFor(rAccessible.get());
+ LoAccessible* ret = LO_ACCESSIBLE(g_object_new(nType, nullptr));
+ ret->display = pDisplay;
+ ret->parent = pParent;
+ ret->uno_accessible = rAccessible;
+
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext(
+ ret->uno_accessible->getAccessibleContext());
+ assert(xContext.is() && "No accessible context");
+
+ GtkAccessible* pGtkAccessible = GTK_ACCESSIBLE(ret);
+
+ // set accessible name and description
+ gtk_accessible_update_property(pGtkAccessible, GTK_ACCESSIBLE_PROPERTY_LABEL,
+ xContext->getAccessibleName().toUtf8().getStr(),
+ GTK_ACCESSIBLE_PROPERTY_DESCRIPTION,
+ xContext->getAccessibleDescription().toUtf8().getStr(), -1);
+
+ applyStates(pGtkAccessible, xContext);
+
+ applyObjectAttributes(GTK_ACCESSIBLE(ret), xContext);
+
+ // set values from XAccessibleValue interface if that's implemented
+ css::uno::Reference<css::accessibility::XAccessibleValue> xAccessibleValue(xContext,
+ css::uno::UNO_QUERY);
+ if (xAccessibleValue.is())
+ {
+ double fCurrentValue = 0, fMinValue = 0, fMaxValue = 0;
+ xAccessibleValue->getCurrentValue() >>= fCurrentValue;
+ xAccessibleValue->getMinimumValue() >>= fMinValue;
+ xAccessibleValue->getMaximumValue() >>= fMaxValue;
+ gtk_accessible_update_property(GTK_ACCESSIBLE(ret), GTK_ACCESSIBLE_PROPERTY_VALUE_NOW,
+ fCurrentValue, GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, fMinValue,
+ GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, fMaxValue, -1);
+ }
+
+ return ret;
+}
+
+static gboolean lo_accessible_get_bounds(GtkAccessible* self, int* x, int* y, int* width,
+ int* height)
+{
+ LoAccessible* pAccessible = LO_ACCESSIBLE(self);
+
+ if (!pAccessible->uno_accessible)
+ return false;
+
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext(
+ pAccessible->uno_accessible->getAccessibleContext());
+ css::uno::Reference<css::accessibility::XAccessibleComponent> xAccessibleComponent(
+ xContext, css::uno::UNO_QUERY);
+ if (!xAccessibleComponent)
+ return false;
+
+ css::awt::Rectangle aBounds = xAccessibleComponent->getBounds();
+ *x = aBounds.X;
+ *y = aBounds.Y;
+ *width = aBounds.Width;
+ *height = aBounds.Height;
+ return true;
+}
+
+static GtkAccessible* lo_accessible_get_first_accessible_child(GtkAccessible* self)
+{
+ LoAccessible* pAccessible = LO_ACCESSIBLE(self);
+
+ if (!pAccessible->uno_accessible)
+ return nullptr;
+
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext(
+ pAccessible->uno_accessible->getAccessibleContext());
+ if (!xContext->getAccessibleChildCount())
+ return nullptr;
+ css::uno::Reference<css::accessibility::XAccessible> xFirstChild(
+ xContext->getAccessibleChild(0));
+ if (!xFirstChild)
+ return nullptr;
+
+ LoAccessible* child_accessible = lo_accessible_new(pAccessible->display, self, xFirstChild);
+ return GTK_ACCESSIBLE(g_object_ref(child_accessible));
+}
+
+static GtkAccessible* lo_accessible_get_next_accessible_sibling(GtkAccessible* self)
+{
+ LoAccessible* pAccessible = LO_ACCESSIBLE(self);
+
+ if (!pAccessible->uno_accessible)
+ return nullptr;
+
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext(
+ pAccessible->uno_accessible->getAccessibleContext());
+ sal_Int64 nThisChildIndex = xContext->getAccessibleIndexInParent();
+ assert(nThisChildIndex != -1);
+ sal_Int64 nNextChildIndex = nThisChildIndex + 1;
+
+ css::uno::Reference<css::accessibility::XAccessible> xParent = xContext->getAccessibleParent();
+ css::uno::Reference<css::accessibility::XAccessibleContext> xParentContext(
+ xParent->getAccessibleContext());
+ if (nNextChildIndex >= xParentContext->getAccessibleChildCount())
+ return nullptr;
+ css::uno::Reference<css::accessibility::XAccessible> xNextChild(
+ xParentContext->getAccessibleChild(nNextChildIndex));
+ if (!xNextChild)
+ return nullptr;
+
+ LoAccessible* child_accessible
+ = lo_accessible_new(pAccessible->display, pAccessible->parent, xNextChild);
+ return GTK_ACCESSIBLE(g_object_ref(child_accessible));
+}
+
+static gboolean lo_accessible_get_platform_state(GtkAccessible* self,
+ GtkAccessiblePlatformState state)
+{
+ LoAccessible* pAccessible = LO_ACCESSIBLE(self);
+
+ if (!pAccessible->uno_accessible)
+ return false;
+
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext(
+ pAccessible->uno_accessible->getAccessibleContext());
+ sal_Int64 nStateSet = xContext->getAccessibleStateSet();
+
+ switch (state)
+ {
+ case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE:
+ return (nStateSet & css::accessibility::AccessibleStateType::FOCUSABLE) != 0;
+ case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED:
+ return (nStateSet & css::accessibility::AccessibleStateType::FOCUSED) != 0;
+ case GTK_ACCESSIBLE_PLATFORM_STATE_ACTIVE:
+ return (nStateSet & css::accessibility::AccessibleStateType::ACTIVE) != 0;
+ }
+
+ return false;
+}
+
+#if GTK_CHECK_VERSION(4, 10, 0)
+static gboolean lo_accessible_range_set_current_value(GtkAccessibleRange* self, double fNewValue)
+{
+ // return 'true' in any case, since otherwise no proper AT-SPI DBus reply gets sent
+ // and the app crashes, s.
+ // https://gitlab.gnome.org/GNOME/gtk/-/issues/6150
+ // https://gitlab.gnome.org/GNOME/gtk/-/commit/0dbd2bd09eff8c9233e45338a05daf2a835529ab
+
+ LoAccessible* pAccessible = LO_ACCESSIBLE(self);
+ if (!pAccessible->uno_accessible)
+ return true;
+
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext(
+ pAccessible->uno_accessible->getAccessibleContext());
+
+ css::uno::Reference<css::accessibility::XAccessibleValue> xValue(xContext, css::uno::UNO_QUERY);
+ if (!xValue.is())
+ return true;
+
+ // Different types of numerical values for XAccessibleValue are possible.
+ // If current value has an integer type, also use that for the new value, to make
+ // sure underlying implementations expecting that can handle the value properly.
+ const css::uno::Any aCurrentValue = xValue->getCurrentValue();
+ if (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_LONG)
+ {
+ const sal_Int32 nValue = std::round<sal_Int32>(fNewValue);
+ xValue->setCurrentValue(css::uno::Any(nValue));
+ return true;
+ }
+ else if (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_HYPER)
+ {
+ const sal_Int64 nValue = std::round<sal_Int64>(fNewValue);
+ xValue->setCurrentValue(css::uno::Any(nValue));
+ return true;
+ }
+
+ css::uno::Any aValue;
+ aValue <<= fNewValue;
+ xValue->setCurrentValue(aValue);
+ return true;
+}
+#endif
+
+static void lo_accessible_init(LoAccessible* /*iface*/) {}
+
+static GtkATContext* get_at_context(GtkAccessible* self)
+{
+ OOoFixed* pFixed = OOO_FIXED(self);
+
+ css::uno::Reference<css::accessibility::XAccessible> xAccessible(
+ get_uno_accessible(GTK_WIDGET(pFixed)));
+ GtkAccessibleRole eRole = map_accessible_role(xAccessible);
+
+ if (!pFixed->at_context || gtk_at_context_get_accessible_role(pFixed->at_context) != eRole)
+ {
+ // if (pFixed->at_context)
+ // g_clear_object(&pFixed->at_context);
+
+ pFixed->at_context
+ = gtk_at_context_create(eRole, self, gtk_widget_get_display(GTK_WIDGET(pFixed)));
+ if (!pFixed->at_context)
+ return nullptr;
+ }
+
+ return g_object_ref(pFixed->at_context);
+}
+
+#if 0
+gboolean get_platform_state(GtkAccessible* self, GtkAccessiblePlatformState state)
+{
+ return false;
+}
+#endif
+
+static gboolean get_bounds(GtkAccessible* accessible, int* x, int* y, int* width, int* height)
+{
+ OOoFixed* pFixed = OOO_FIXED(accessible);
+ css::uno::Reference<css::accessibility::XAccessible> xAccessible(
+ get_uno_accessible(GTK_WIDGET(pFixed)));
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext(
+ xAccessible->getAccessibleContext());
+ css::uno::Reference<css::accessibility::XAccessibleComponent> xAccessibleComponent(
+ xContext, css::uno::UNO_QUERY);
+
+ css::awt::Rectangle aBounds = xAccessibleComponent->getBounds();
+ *x = aBounds.X;
+ *y = aBounds.Y;
+ *width = aBounds.Width;
+ *height = aBounds.Height;
+ return true;
+}
+
+static GtkAccessible* get_first_accessible_child(GtkAccessible* accessible)
+{
+ OOoFixed* pFixed = OOO_FIXED(accessible);
+ css::uno::Reference<css::accessibility::XAccessible> xAccessible(
+ get_uno_accessible(GTK_WIDGET(pFixed)));
+ if (!xAccessible)
+ return nullptr;
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext(
+ xAccessible->getAccessibleContext());
+ if (!xContext->getAccessibleChildCount())
+ return nullptr;
+ css::uno::Reference<css::accessibility::XAccessible> xFirstChild(
+ xContext->getAccessibleChild(0));
+ LoAccessible* child_accessible
+ = lo_accessible_new(gtk_widget_get_display(GTK_WIDGET(pFixed)), accessible, xFirstChild);
+ return GTK_ACCESSIBLE(g_object_ref(child_accessible));
+}
+
+static void ooo_fixed_accessible_init(GtkAccessibleInterface* iface)
+{
+ GtkAccessibleInterface* parent_iface
+ = static_cast<GtkAccessibleInterface*>(g_type_interface_peek_parent(iface));
+ iface->get_at_context = get_at_context;
+ iface->get_bounds = get_bounds;
+ iface->get_first_accessible_child = get_first_accessible_child;
+ iface->get_platform_state = parent_iface->get_platform_state;
+ // iface->get_platform_state = get_platform_state;
+}
+
+G_DEFINE_TYPE_WITH_CODE(OOoFixed, ooo_fixed, GTK_TYPE_FIXED,
+ G_IMPLEMENT_INTERFACE(GTK_TYPE_ACCESSIBLE, ooo_fixed_accessible_init))
+
+static void ooo_fixed_class_init(OOoFixedClass* /*klass*/) {}
+
+static void ooo_fixed_init(OOoFixed* /*area*/) {}
+
+GtkWidget* ooo_fixed_new() { return GTK_WIDGET(g_object_new(OOO_TYPE_FIXED, nullptr)); }
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/unx/gtk4/a11y.hxx b/vcl/unx/gtk4/a11y.hxx
new file mode 100644
index 0000000000..90711515bc
--- /dev/null
+++ b/vcl/unx/gtk4/a11y.hxx
@@ -0,0 +1,24 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+//TODO: Silence various loplugin:external and loplugin:unreffun in (WIP?) a11y.cxx for now:
+struct LoAccessible;
+struct LoAccessibleClass;
+struct OOoFixed;
+struct OOoFixedClass;
+static inline gpointer lo_accessible_get_instance_private(LoAccessible*);
+GType lo_accessible_get_type();
+static inline gpointer ooo_fixed_get_instance_private(OOoFixed*);
+GtkWidget* ooo_fixed_new();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/unx/gtk4/convert3to4.cxx b/vcl/unx/gtk4/convert3to4.cxx
new file mode 100644
index 0000000000..16c7403bb2
--- /dev/null
+++ b/vcl/unx/gtk4/convert3to4.cxx
@@ -0,0 +1,1603 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/beans/XPropertySet.hpp>
+#include <com/sun/star/io/TempFile.hpp>
+#include <com/sun/star/xml/dom/DocumentBuilder.hpp>
+#include <com/sun/star/xml/sax/Writer.hpp>
+#include <com/sun/star/xml/sax/XSAXSerializable.hpp>
+#include <comphelper/processfactory.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <vcl/builder.hxx>
+#include <cairo/cairo-gobject.h>
+#include "convert3to4.hxx"
+
+namespace
+{
+typedef std::pair<css::uno::Reference<css::xml::dom::XNode>, OUString> named_node;
+
+bool sortButtonNodes(const named_node& rA, const named_node& rB)
+{
+ OUString sA(rA.second);
+ OUString sB(rB.second);
+ //order within groups according to platform rules
+ return getButtonPriority(sA) < getButtonPriority(sB);
+}
+
+// <property name="spacing">6</property>
+css::uno::Reference<css::xml::dom::XNode>
+CreateProperty(const css::uno::Reference<css::xml::dom::XDocument>& xDoc, const OUString& rPropName,
+ const OUString& rValue)
+{
+ css::uno::Reference<css::xml::dom::XElement> xProperty = xDoc->createElement("property");
+ css::uno::Reference<css::xml::dom::XAttr> xPropName = xDoc->createAttribute("name");
+ xPropName->setValue(rPropName);
+ xProperty->setAttributeNode(xPropName);
+ css::uno::Reference<css::xml::dom::XText> xValue = xDoc->createTextNode(rValue);
+ xProperty->appendChild(xValue);
+ return xProperty;
+}
+
+bool ToplevelIsMessageDialog(const css::uno::Reference<css::xml::dom::XNode>& xNode)
+{
+ for (css::uno::Reference<css::xml::dom::XNode> xObjectCandidate = xNode->getParentNode();
+ xObjectCandidate.is(); xObjectCandidate = xObjectCandidate->getParentNode())
+ {
+ if (xObjectCandidate->getNodeName() == "object")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xObjectMap
+ = xObjectCandidate->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xClass = xObjectMap->getNamedItem("class");
+ if (xClass->getNodeValue() == "GtkMessageDialog")
+ return true;
+ }
+ }
+ return false;
+}
+
+void insertAsFirstChild(const css::uno::Reference<css::xml::dom::XNode>& xParentNode,
+ const css::uno::Reference<css::xml::dom::XNode>& xChildNode)
+{
+ auto xFirstChild = xParentNode->getFirstChild();
+ if (xFirstChild.is())
+ xParentNode->insertBefore(xChildNode, xFirstChild);
+ else
+ xParentNode->appendChild(xChildNode);
+}
+
+void SetPropertyOnTopLevel(const css::uno::Reference<css::xml::dom::XNode>& xNode,
+ const css::uno::Reference<css::xml::dom::XNode>& xProperty)
+{
+ for (css::uno::Reference<css::xml::dom::XNode> xObjectCandidate = xNode->getParentNode();
+ xObjectCandidate.is(); xObjectCandidate = xObjectCandidate->getParentNode())
+ {
+ if (xObjectCandidate->getNodeName() == "object")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xObjectMap
+ = xObjectCandidate->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xClass = xObjectMap->getNamedItem("class");
+ if (xClass->getNodeValue() == "GtkDialog")
+ {
+ insertAsFirstChild(xObjectCandidate, xProperty);
+ break;
+ }
+ }
+ }
+}
+
+OUString GetParentObjectType(const css::uno::Reference<css::xml::dom::XNode>& xNode)
+{
+ auto xParent = xNode->getParentNode();
+ assert(xParent->getNodeName() == "object");
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap = xParent->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xClass = xParentMap->getNamedItem("class");
+ return xClass->getNodeValue();
+}
+
+css::uno::Reference<css::xml::dom::XNode>
+GetChildObject(const css::uno::Reference<css::xml::dom::XNode>& xChild)
+{
+ for (css::uno::Reference<css::xml::dom::XNode> xObjectCandidate = xChild->getFirstChild();
+ xObjectCandidate.is(); xObjectCandidate = xObjectCandidate->getNextSibling())
+ {
+ if (xObjectCandidate->getNodeName() == "object")
+ return xObjectCandidate;
+ }
+ return nullptr;
+}
+
+// currently runs the risk of duplicate margin-* properties if there was already such as well
+// as the border
+void AddBorderAsMargins(const css::uno::Reference<css::xml::dom::XNode>& xNode,
+ const OUString& rBorderWidth)
+{
+ auto xDoc = xNode->getOwnerDocument();
+
+ auto xMarginEnd = CreateProperty(xDoc, "margin-end", rBorderWidth);
+ insertAsFirstChild(xNode, xMarginEnd);
+ xNode->insertBefore(CreateProperty(xDoc, "margin-top", rBorderWidth), xMarginEnd);
+ xNode->insertBefore(CreateProperty(xDoc, "margin-bottom", rBorderWidth), xMarginEnd);
+ xNode->insertBefore(CreateProperty(xDoc, "margin-start", rBorderWidth), xMarginEnd);
+}
+
+struct MenuEntry
+{
+ bool m_bDrawAsRadio;
+ css::uno::Reference<css::xml::dom::XNode> m_xPropertyLabel;
+
+ MenuEntry(bool bDrawAsRadio, const css::uno::Reference<css::xml::dom::XNode>& rPropertyLabel)
+ : m_bDrawAsRadio(bDrawAsRadio)
+ , m_xPropertyLabel(rPropertyLabel)
+ {
+ }
+};
+
+MenuEntry ConvertMenu(css::uno::Reference<css::xml::dom::XNode>& xMenuSection,
+ const css::uno::Reference<css::xml::dom::XNode>& xNode)
+{
+ bool bDrawAsRadio = false;
+ css::uno::Reference<css::xml::dom::XNode> xPropertyLabel;
+
+ css::uno::Reference<css::xml::dom::XNode> xChild = xNode->getFirstChild();
+ while (xChild.is())
+ {
+ if (xChild->getNodeName() == "property")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xName = xMap->getNamedItem("name");
+ OUString sName(xName->getNodeValue().replace('_', '-'));
+
+ if (sName == "label")
+ {
+ xPropertyLabel = xChild;
+ }
+ else if (sName == "draw-as-radio")
+ {
+ bDrawAsRadio = toBool(xChild->getFirstChild()->getNodeValue());
+ }
+ }
+
+ auto xNextChild = xChild->getNextSibling();
+
+ auto xSavedMenuSection = xMenuSection;
+
+ if (xChild->getNodeName() == "object")
+ {
+ auto xDoc = xChild->getOwnerDocument();
+
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xClass = xMap->getNamedItem("class");
+ OUString sClass(xClass->getNodeValue());
+
+ if (sClass == "GtkMenuItem" || sClass == "GtkRadioMenuItem")
+ {
+ /* <item> */
+ css::uno::Reference<css::xml::dom::XElement> xItem = xDoc->createElement("item");
+ xMenuSection->appendChild(xItem);
+ }
+ else if (sClass == "GtkSeparatorMenuItem")
+ {
+ /* <section> */
+ css::uno::Reference<css::xml::dom::XElement> xSection
+ = xDoc->createElement("section");
+ xMenuSection->getParentNode()->appendChild(xSection);
+ xMenuSection = xSection;
+ xSavedMenuSection = xMenuSection;
+ }
+ else if (sClass == "GtkMenu")
+ {
+ xMenuSection->removeChild(xMenuSection->getLastChild()); // remove preceding <item>
+
+ css::uno::Reference<css::xml::dom::XElement> xSubMenu
+ = xDoc->createElement("submenu");
+ css::uno::Reference<css::xml::dom::XAttr> xIdAttr = xDoc->createAttribute("id");
+
+ css::uno::Reference<css::xml::dom::XNode> xId = xMap->getNamedItem("id");
+ OUString sId(xId->getNodeValue());
+
+ xIdAttr->setValue(sId);
+ xSubMenu->setAttributeNode(xIdAttr);
+ xMenuSection->appendChild(xSubMenu);
+
+ css::uno::Reference<css::xml::dom::XElement> xSection
+ = xDoc->createElement("section");
+ xSubMenu->appendChild(xSection);
+
+ xMenuSection = xSection;
+ xSavedMenuSection = xMenuSection;
+ }
+ }
+
+ bool bChildDrawAsRadio = false;
+ css::uno::Reference<css::xml::dom::XNode> xChildPropertyLabel;
+ if (xChild->hasChildNodes())
+ {
+ MenuEntry aEntry = ConvertMenu(xMenuSection, xChild);
+ bChildDrawAsRadio = aEntry.m_bDrawAsRadio;
+ xChildPropertyLabel = aEntry.m_xPropertyLabel;
+ }
+
+ if (xChild->getNodeName() == "object")
+ {
+ xMenuSection = xSavedMenuSection;
+
+ auto xDoc = xChild->getOwnerDocument();
+
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xClass = xMap->getNamedItem("class");
+ OUString sClass(xClass->getNodeValue());
+
+ if (sClass == "GtkMenuItem" || sClass == "GtkRadioMenuItem")
+ {
+ css::uno::Reference<css::xml::dom::XNode> xId = xMap->getNamedItem("id");
+ OUString sId = xId->getNodeValue();
+
+ /*
+ <attribute name='label' translatable='yes'>whatever</attribute>
+ <attribute name='action'>menu.action</attribute>
+ <attribute name='target'>id</attribute>
+ */
+ auto xItem = xMenuSection->getLastChild();
+
+ if (xChildPropertyLabel)
+ {
+ css::uno::Reference<css::xml::dom::XElement> xChildPropertyElem(
+ xChildPropertyLabel, css::uno::UNO_QUERY_THROW);
+
+ css::uno::Reference<css::xml::dom::XElement> xLabelAttr
+ = xDoc->createElement("attribute");
+
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xLabelMap
+ = xChildPropertyLabel->getAttributes();
+ while (xLabelMap->getLength())
+ {
+ css::uno::Reference<css::xml::dom::XAttr> xAttr(xLabelMap->item(0),
+ css::uno::UNO_QUERY_THROW);
+ xLabelAttr->setAttributeNode(
+ xChildPropertyElem->removeAttributeNode(xAttr));
+ }
+ xLabelAttr->appendChild(
+ xChildPropertyLabel->removeChild(xChildPropertyLabel->getFirstChild()));
+
+ xChildPropertyLabel->getParentNode()->removeChild(xChildPropertyLabel);
+ xItem->appendChild(xLabelAttr);
+ }
+
+ css::uno::Reference<css::xml::dom::XElement> xActionAttr
+ = xDoc->createElement("attribute");
+ css::uno::Reference<css::xml::dom::XAttr> xActionName
+ = xDoc->createAttribute("name");
+ xActionName->setValue("action");
+ xActionAttr->setAttributeNode(xActionName);
+ if (bChildDrawAsRadio)
+ xActionAttr->appendChild(xDoc->createTextNode("menu.radio." + sId));
+ else
+ xActionAttr->appendChild(xDoc->createTextNode("menu.normal." + sId));
+ xItem->appendChild(xActionAttr);
+
+ css::uno::Reference<css::xml::dom::XElement> xTargetAttr
+ = xDoc->createElement("attribute");
+ css::uno::Reference<css::xml::dom::XAttr> xTargetName
+ = xDoc->createAttribute("name");
+ xTargetName->setValue("target");
+ xTargetAttr->setAttributeNode(xTargetName);
+ xTargetAttr->appendChild(xDoc->createTextNode(sId));
+ xItem->appendChild(xTargetAttr);
+
+ css::uno::Reference<css::xml::dom::XElement> xHiddenWhenAttr
+ = xDoc->createElement("attribute");
+ css::uno::Reference<css::xml::dom::XAttr> xHiddenWhenName
+ = xDoc->createAttribute("name");
+ xHiddenWhenName->setValue("hidden-when");
+ xHiddenWhenAttr->setAttributeNode(xHiddenWhenName);
+ xHiddenWhenAttr->appendChild(xDoc->createTextNode("action-missing"));
+ xItem->appendChild(xHiddenWhenAttr);
+ }
+ }
+
+ xChild = xNextChild;
+ }
+
+ return MenuEntry(bDrawAsRadio, xPropertyLabel);
+}
+
+struct ConvertResult
+{
+ bool m_bChildCanFocus;
+ bool m_bHasVisible;
+ bool m_bHasIconSize;
+ bool m_bAlwaysShowImage;
+ bool m_bUseUnderline;
+ bool m_bVertOrientation;
+ bool m_bXAlign;
+ GtkPositionType m_eImagePos;
+ css::uno::Reference<css::xml::dom::XNode> m_xPropertyLabel;
+ css::uno::Reference<css::xml::dom::XNode> m_xPropertyIconName;
+
+ ConvertResult(bool bChildCanFocus, bool bHasVisible, bool bHasIconSize, bool bAlwaysShowImage,
+ bool bUseUnderline, bool bVertOrientation, bool bXAlign,
+ GtkPositionType eImagePos,
+ const css::uno::Reference<css::xml::dom::XNode>& rPropertyLabel,
+ const css::uno::Reference<css::xml::dom::XNode>& rPropertyIconName)
+ : m_bChildCanFocus(bChildCanFocus)
+ , m_bHasVisible(bHasVisible)
+ , m_bHasIconSize(bHasIconSize)
+ , m_bAlwaysShowImage(bAlwaysShowImage)
+ , m_bUseUnderline(bUseUnderline)
+ , m_bVertOrientation(bVertOrientation)
+ , m_bXAlign(bXAlign)
+ , m_eImagePos(eImagePos)
+ , m_xPropertyLabel(rPropertyLabel)
+ , m_xPropertyIconName(rPropertyIconName)
+ {
+ }
+};
+
+bool IsAllowedBuiltInIcon(std::u16string_view iconName)
+{
+ // limit the named icons to those known by VclBuilder
+ return VclBuilder::mapStockToSymbol(iconName) != SymbolType::DONTKNOW;
+}
+
+ConvertResult Convert3To4(const css::uno::Reference<css::xml::dom::XNode>& xNode)
+{
+ css::uno::Reference<css::xml::dom::XNodeList> xNodeList = xNode->getChildNodes();
+ if (!xNodeList.is())
+ {
+ return ConvertResult(false, false, false, false, false, false, false, GTK_POS_LEFT, nullptr,
+ nullptr);
+ }
+
+ std::vector<css::uno::Reference<css::xml::dom::XNode>> xRemoveList;
+
+ OUString sBorderWidth;
+ bool bChildCanFocus = false;
+ bool bHasVisible = false;
+ bool bHasIconSize = false;
+ bool bAlwaysShowImage = false;
+ GtkPositionType eImagePos = GTK_POS_LEFT;
+ bool bUseUnderline = false;
+ bool bVertOrientation = false;
+ bool bXAlign = false;
+ css::uno::Reference<css::xml::dom::XNode> xPropertyLabel;
+ css::uno::Reference<css::xml::dom::XNode> xPropertyIconName;
+ css::uno::Reference<css::xml::dom::XNode> xCantFocus;
+
+ css::uno::Reference<css::xml::dom::XElement> xGeneratedImageChild;
+
+ css::uno::Reference<css::xml::dom::XNode> xChild = xNode->getFirstChild();
+ while (xChild.is())
+ {
+ if (xChild->getNodeName() == "requires")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xLib = xMap->getNamedItem("lib");
+ assert(xLib->getNodeValue() == "gtk+");
+ xLib->setNodeValue("gtk");
+ css::uno::Reference<css::xml::dom::XNode> xVersion = xMap->getNamedItem("version");
+ assert(xVersion->getNodeValue() == "3.20");
+ xVersion->setNodeValue("4.0");
+ }
+ else if (xChild->getNodeName() == "property")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xName = xMap->getNamedItem("name");
+ OUString sName(xName->getNodeValue().replace('_', '-'));
+
+ if (sName == "border-width")
+ sBorderWidth = xChild->getFirstChild()->getNodeValue();
+
+ if (sName == "has-default")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap
+ = xChild->getParentNode()->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xId = xParentMap->getNamedItem("id");
+ auto xDoc = xChild->getOwnerDocument();
+ auto xDefaultWidget = CreateProperty(xDoc, "default-widget", xId->getNodeValue());
+ SetPropertyOnTopLevel(xChild, xDefaultWidget);
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "has-focus" || sName == "is-focus")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap
+ = xChild->getParentNode()->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xId = xParentMap->getNamedItem("id");
+ auto xDoc = xChild->getOwnerDocument();
+ auto xDefaultWidget = CreateProperty(xDoc, "focus-widget", xId->getNodeValue());
+ SetPropertyOnTopLevel(xChild, xDefaultWidget);
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "can-focus")
+ {
+ bChildCanFocus = toBool(xChild->getFirstChild()->getNodeValue());
+ if (!bChildCanFocus)
+ {
+ OUString sParentClass = GetParentObjectType(xChild);
+ if (sParentClass == "GtkBox" || sParentClass == "GtkGrid"
+ || sParentClass == "GtkViewport")
+ {
+ // e.g. for the case of notebooks without children yet, just remove the can't focus property
+ // from Boxes and Grids
+ xRemoveList.push_back(xChild);
+ }
+ else if (sParentClass == "GtkComboBoxText")
+ {
+ // this was always a bit finicky in gtk3, fix it up to default to can-focus
+ xRemoveList.push_back(xChild);
+ }
+ else
+ {
+ // otherwise mark the property as needing removal if there turns out to be a child
+ // with can-focus of true, in which case remove this parent conflicting property
+ xCantFocus = xChild;
+ }
+ }
+ }
+
+ if (sName == "label")
+ {
+ OUString sParentClass = GetParentObjectType(xChild);
+ if (sParentClass == "GtkToolButton" || sParentClass == "GtkMenuToolButton"
+ || sParentClass == "GtkToggleToolButton")
+ {
+ xName->setNodeValue("tooltip-text");
+ }
+ xPropertyLabel = xChild;
+ }
+
+ if (sName == "modal")
+ {
+ OUString sParentClass = GetParentObjectType(xChild);
+ if (sParentClass == "GtkPopover")
+ xName->setNodeValue("autohide");
+ }
+
+ if (sName == "visible")
+ bHasVisible = true;
+
+ if (sName == "icon-name")
+ xPropertyIconName = xChild;
+
+ if (sName == "show-arrow")
+ xRemoveList.push_back(xChild);
+
+ if (sName == "events")
+ xRemoveList.push_back(xChild);
+
+ if (sName == "constrain-to")
+ xRemoveList.push_back(xChild);
+
+ if (sName == "activates-default")
+ {
+ if (GetParentObjectType(xChild) == "GtkSpinButton")
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "width-chars")
+ {
+ if (GetParentObjectType(xChild) == "GtkEntry")
+ {
+ // I don't quite get what the difference should be wrt width-chars and max-width-chars
+ // but glade doesn't write max-width-chars and in gtk4 where we have width-chars, e.g
+ // print dialog, then max-width-chars gives the effect we wanted with width-chars
+ auto xDoc = xChild->getOwnerDocument();
+ auto mMaxWidthChars = CreateProperty(xDoc, "max-width-chars",
+ xChild->getFirstChild()->getNodeValue());
+ xChild->getParentNode()->insertBefore(mMaxWidthChars, xChild);
+ }
+ }
+
+ // remove 'Help' button label and replace with a help icon instead. Unless the toplevel is a message dialog
+ if (sName == "label" && GetParentObjectType(xChild) == "GtkButton"
+ && !ToplevelIsMessageDialog(xChild))
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap
+ = xChild->getParentNode()->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xId = xParentMap->getNamedItem("id");
+ if (xId && xId->getNodeValue() == "help")
+ {
+ auto xDoc = xChild->getOwnerDocument();
+ auto xIconName = CreateProperty(xDoc, "icon-name", "help-browser-symbolic");
+ xChild->getParentNode()->insertBefore(xIconName, xChild);
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (sName == "icon-size")
+ {
+ if (GetParentObjectType(xChild) == "GtkImage")
+ {
+ bHasIconSize = true;
+
+ OUString sSize = xChild->getFirstChild()->getNodeValue();
+ /*
+ old:
+ 3 -> GTK_ICON_SIZE_LARGE_TOOLBAR: Size appropriate for large toolbars (24px)
+ 5 -> GTK_ICON_SIZE_DND: Size appropriate for drag and drop (32px)
+ 6 -> GTK_ICON_SIZE_DIALOG: Size appropriate for dialogs (48px)
+
+ new:
+ 2 -> GTK_ICON_SIZE_LARGE
+ */
+ if (sSize == "3" || sSize == "5" || sSize == "6")
+ {
+ auto xDoc = xChild->getOwnerDocument();
+ auto xIconSize = CreateProperty(xDoc, "icon-size", "2");
+ xChild->getParentNode()->insertBefore(xIconSize, xChild);
+ }
+
+ xRemoveList.push_back(xChild);
+ }
+
+ if (GetParentObjectType(xChild) == "GtkToolbar")
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "truncate-multiline")
+ {
+ if (GetParentObjectType(xChild) == "GtkSpinButton")
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "has-frame")
+ {
+ if (GetParentObjectType(xChild) == "GtkSpinButton")
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "toolbar-style")
+ {
+ // is there an equivalent for this ?
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "shadow-type")
+ {
+ if (GetParentObjectType(xChild) == "GtkFrame")
+ xRemoveList.push_back(xChild);
+ else if (GetParentObjectType(xChild) == "GtkScrolledWindow")
+ {
+ bool bHasFrame = xChild->getFirstChild()->getNodeValue() != "none";
+ auto xDoc = xChild->getOwnerDocument();
+ auto xHasFrame = CreateProperty(
+ xDoc, "has-frame", bHasFrame ? OUString("True") : OUString("False"));
+ xChild->getParentNode()->insertBefore(xHasFrame, xChild);
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (sName == "always-show-image")
+ {
+ if (GetParentObjectType(xChild) == "GtkButton"
+ || GetParentObjectType(xChild) == "GtkMenuButton"
+ || GetParentObjectType(xChild) == "GtkToggleButton")
+ {
+ // we will turn always-show-image into a GtkBox child for
+ // GtkButton and a GtkLabel child for the GtkBox and move
+ // the label property into it.
+ bAlwaysShowImage = toBool(xChild->getFirstChild()->getNodeValue());
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (sName == "image-position")
+ {
+ if (GetParentObjectType(xChild) == "GtkButton")
+ {
+ // we will turn always-show-image into a GtkBox child for
+ // GtkButton and a GtkLabel child for the GtkBox and move
+ // the label property into it.
+ OUString sImagePos = xChild->getFirstChild()->getNodeValue();
+ if (sImagePos == "top")
+ eImagePos = GTK_POS_TOP;
+ else if (sImagePos == "bottom")
+ eImagePos = GTK_POS_BOTTOM;
+ else if (sImagePos == "right")
+ eImagePos = GTK_POS_RIGHT;
+ else
+ assert(sImagePos == "left");
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (sName == "use-underline")
+ bUseUnderline = toBool(xChild->getFirstChild()->getNodeValue());
+
+ if (sName == "orientation")
+ bVertOrientation = xChild->getFirstChild()->getNodeValue() == "vertical";
+
+ if (sName == "relief")
+ {
+ if (GetParentObjectType(xChild) == "GtkToggleButton"
+ || GetParentObjectType(xChild) == "GtkMenuButton"
+ || GetParentObjectType(xChild) == "GtkLinkButton"
+ || GetParentObjectType(xChild) == "GtkButton")
+ {
+ assert(xChild->getFirstChild()->getNodeValue() == "none");
+ auto xDoc = xChild->getOwnerDocument();
+ auto xHasFrame = CreateProperty(xDoc, "has-frame", "False");
+ xChild->getParentNode()->insertBefore(xHasFrame, xChild);
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (sName == "xalign")
+ {
+ if (GetParentObjectType(xChild) == "GtkLinkButton"
+ || GetParentObjectType(xChild) == "GtkMenuButton"
+ || GetParentObjectType(xChild) == "GtkToggleButton"
+ || GetParentObjectType(xChild) == "GtkButton")
+ {
+ // TODO expand into a GtkLabel child with alignment on that instead
+ assert(xChild->getFirstChild()->getNodeValue() == "0");
+ bXAlign = true;
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (sName == "use-popover")
+ {
+ if (GetParentObjectType(xChild) == "GtkMenuButton")
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "hscrollbar-policy")
+ {
+ if (GetParentObjectType(xChild) == "GtkScrolledWindow")
+ {
+ if (xChild->getFirstChild()->getNodeValue() == "never")
+ {
+ auto xDoc = xChild->getOwnerDocument();
+ auto xHasFrame = CreateProperty(xDoc, "propagate-natural-width", "True");
+ xChild->getParentNode()->insertBefore(xHasFrame, xChild);
+ }
+ }
+ }
+
+ if (sName == "vscrollbar-policy")
+ {
+ if (GetParentObjectType(xChild) == "GtkScrolledWindow")
+ {
+ if (xChild->getFirstChild()->getNodeValue() == "never")
+ {
+ auto xDoc = xChild->getOwnerDocument();
+ auto xHasFrame = CreateProperty(xDoc, "propagate-natural-height", "True");
+ xChild->getParentNode()->insertBefore(xHasFrame, xChild);
+ }
+ }
+ }
+
+ if (sName == "popup")
+ {
+ if (GetParentObjectType(xChild) == "GtkMenuButton")
+ {
+ OUString sMenuName = xChild->getFirstChild()->getNodeValue();
+ auto xDoc = xChild->getOwnerDocument();
+ auto xPopover = CreateProperty(xDoc, "popover", sMenuName);
+ xChild->getParentNode()->insertBefore(xPopover, xChild);
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (sName == "image")
+ {
+ if (GetParentObjectType(xChild) == "GtkButton"
+ || GetParentObjectType(xChild) == "GtkMenuButton"
+ || GetParentObjectType(xChild) == "GtkToggleButton")
+ {
+ // find the image object, expected to be a child of "interface"
+ auto xObjectCandidate = xChild->getParentNode();
+ if (xObjectCandidate->getNodeName() == "object")
+ {
+ OUString sImageId = xChild->getFirstChild()->getNodeValue();
+
+ css::uno::Reference<css::xml::dom::XNode> xRootCandidate
+ = xChild->getParentNode();
+ while (xRootCandidate)
+ {
+ if (xRootCandidate->getNodeName() == "interface")
+ break;
+ xRootCandidate = xRootCandidate->getParentNode();
+ }
+
+ css::uno::Reference<css::xml::dom::XNode> xImageNode;
+
+ for (auto xImageCandidate = xRootCandidate->getFirstChild();
+ xImageCandidate.is();
+ xImageCandidate = xImageCandidate->getNextSibling())
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xImageCandidateMap
+ = xImageCandidate->getAttributes();
+ if (!xImageCandidateMap.is())
+ continue;
+ css::uno::Reference<css::xml::dom::XNode> xId
+ = xImageCandidateMap->getNamedItem("id");
+ if (xId && xId->getNodeValue() == sImageId)
+ {
+ xImageNode = xImageCandidate;
+ break;
+ }
+ }
+
+ auto xDoc = xChild->getOwnerDocument();
+
+ // relocate it to be a child of this GtkButton
+ xGeneratedImageChild = xDoc->createElement("child");
+ xGeneratedImageChild->appendChild(
+ xImageNode->getParentNode()->removeChild(xImageNode));
+ xObjectCandidate->appendChild(xGeneratedImageChild);
+ }
+
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (sName == "draw-indicator")
+ {
+ assert(toBool(xChild->getFirstChild()->getNodeValue()));
+ if (GetParentObjectType(xChild) == "GtkMenuButton" && gtk_get_minor_version() >= 4)
+ {
+ auto xDoc = xChild->getOwnerDocument();
+ auto xAlwaysShowArrow = CreateProperty(xDoc, "always-show-arrow", "True");
+ xChild->getParentNode()->insertBefore(xAlwaysShowArrow, xChild);
+ }
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "type-hint" || sName == "skip-taskbar-hint" || sName == "can-default"
+ || sName == "border-width" || sName == "layout-style" || sName == "no-show-all"
+ || sName == "ignore-hidden" || sName == "window-position")
+ {
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "AtkObject::accessible-description")
+ xName->setNodeValue("description");
+
+ if (sName == "AtkObject::accessible-name")
+ xName->setNodeValue("label");
+
+ if (sName == "AtkObject::accessible-role")
+ xName->setNodeValue("accessible-role");
+ }
+ else if (xChild->getNodeName() == "child")
+ {
+ bool bContentArea = false;
+
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xName = xMap->getNamedItem("internal-child");
+ if (xName)
+ {
+ OUString sName(xName->getNodeValue());
+ if (sName == "vbox")
+ {
+ xName->setNodeValue("content_area");
+ bContentArea = true;
+ }
+ else if (sName == "accessible")
+ {
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (bContentArea)
+ {
+ css::uno::Reference<css::xml::dom::XNode> xObject = GetChildObject(xChild);
+ if (xObject)
+ {
+ auto xDoc = xChild->getOwnerDocument();
+
+ auto xVExpand = CreateProperty(xDoc, "vexpand", "True");
+ insertAsFirstChild(xObject, xVExpand);
+
+ if (!sBorderWidth.isEmpty())
+ {
+ AddBorderAsMargins(xObject, sBorderWidth);
+ sBorderWidth.clear();
+ }
+ }
+ }
+ }
+ else if (xChild->getNodeName() == "packing")
+ {
+ // remove "packing" and if its grid packing insert a replacement "layout" into
+ // the associated "object"
+ auto xDoc = xChild->getOwnerDocument();
+ css::uno::Reference<css::xml::dom::XElement> xNew = xDoc->createElement("layout");
+
+ bool bGridPacking = false;
+
+ // iterate over all children and append them to the new element
+ for (css::uno::Reference<css::xml::dom::XNode> xCurrent = xChild->getFirstChild();
+ xCurrent.is(); xCurrent = xChild->getFirstChild())
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xCurrent->getAttributes();
+ if (xMap.is())
+ {
+ css::uno::Reference<css::xml::dom::XNode> xName = xMap->getNamedItem("name");
+ OUString sName(xName->getNodeValue().replace('_', '-'));
+ if (sName == "left-attach")
+ {
+ xName->setNodeValue("column");
+ bGridPacking = true;
+ }
+ else if (sName == "top-attach")
+ {
+ xName->setNodeValue("row");
+ bGridPacking = true;
+ }
+ else if (sName == "width")
+ {
+ xName->setNodeValue("column-span");
+ bGridPacking = true;
+ }
+ else if (sName == "height")
+ {
+ xName->setNodeValue("row-span");
+ bGridPacking = true;
+ }
+ else if (sName == "secondary")
+ {
+ // turn parent tag of <child> into <child type="start">
+ auto xParent = xChild->getParentNode();
+ css::uno::Reference<css::xml::dom::XAttr> xTypeStart
+ = xDoc->createAttribute("type");
+ xTypeStart->setValue("start");
+ css::uno::Reference<css::xml::dom::XElement> xElem(
+ xParent, css::uno::UNO_QUERY_THROW);
+ xElem->setAttributeNode(xTypeStart);
+ }
+ else if (sName == "pack-type")
+ {
+ // turn parent tag of <child> into <child type="start">
+ auto xParent = xChild->getParentNode();
+
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap
+ = xParent->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xParentType
+ = xParentMap->getNamedItem("type");
+ assert(!xParentType || xParentType->getNodeValue() == "titlebar");
+ if (!xParentType)
+ {
+ css::uno::Reference<css::xml::dom::XAttr> xTypeStart
+ = xDoc->createAttribute("type");
+ xTypeStart->setValue(xCurrent->getFirstChild()->getNodeValue());
+ css::uno::Reference<css::xml::dom::XElement> xElem(
+ xParent, css::uno::UNO_QUERY_THROW);
+ xElem->setAttributeNode(xTypeStart);
+ }
+ }
+ }
+ xNew->appendChild(xChild->removeChild(xCurrent));
+ }
+
+ if (bGridPacking)
+ {
+ // go back to parent and find the object child and insert this "layout" as a
+ // new child of the object
+ auto xParent = xChild->getParentNode();
+ css::uno::Reference<css::xml::dom::XNode> xObject = GetChildObject(xParent);
+ if (xObject)
+ xObject->appendChild(xNew);
+ }
+
+ xRemoveList.push_back(xChild);
+ }
+ else if (xChild->getNodeName() == "accelerator")
+ {
+ // TODO is anything like this supported anymore in .ui files
+ xRemoveList.push_back(xChild);
+ }
+ else if (xChild->getNodeName() == "relation")
+ {
+ auto xDoc = xChild->getOwnerDocument();
+
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xType = xMap->getNamedItem("type");
+ if (xType->getNodeValue() == "label-for")
+ {
+ // there will be a matching labelled-by which should be sufficient in the gtk4 world
+ xRemoveList.push_back(xChild);
+ }
+ if (xType->getNodeValue() == "controlled-by")
+ {
+ // there will be a matching controller-for converted to -> controls
+ // which should be sufficient in the gtk4 world
+ xRemoveList.push_back(xChild);
+ }
+ else
+ {
+ css::uno::Reference<css::xml::dom::XNode> xTarget = xMap->getNamedItem("target");
+
+ css::uno::Reference<css::xml::dom::XText> xValue
+ = xDoc->createTextNode(xTarget->getNodeValue());
+ xChild->appendChild(xValue);
+
+ css::uno::Reference<css::xml::dom::XAttr> xName = xDoc->createAttribute("name");
+ if (xType->getNodeValue() == "controller-for")
+ xName->setValue("controls");
+ else
+ xName->setValue(xType->getNodeValue());
+
+ css::uno::Reference<css::xml::dom::XElement> xElem(xChild,
+ css::uno::UNO_QUERY_THROW);
+ xElem->setAttributeNode(xName);
+ xElem->removeAttribute("type");
+ xElem->removeAttribute("target");
+ }
+ }
+
+ auto xNextChild = xChild->getNextSibling();
+
+ if (xChild->getNodeName() == "object")
+ {
+ auto xDoc = xChild->getOwnerDocument();
+
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xClass = xMap->getNamedItem("class");
+ OUString sClass(xClass->getNodeValue());
+
+ if (sClass == "GtkMenu")
+ {
+ css::uno::Reference<css::xml::dom::XNode> xId = xMap->getNamedItem("id");
+ OUString sId(xId->getNodeValue() + "-menu-model");
+
+ // <menu id='menubar'>
+ css::uno::Reference<css::xml::dom::XElement> xMenu = xDoc->createElement("menu");
+ css::uno::Reference<css::xml::dom::XAttr> xIdAttr = xDoc->createAttribute("id");
+ xIdAttr->setValue(sId);
+ xMenu->setAttributeNode(xIdAttr);
+ xChild->getParentNode()->insertBefore(xMenu, xChild);
+
+ css::uno::Reference<css::xml::dom::XElement> xSection
+ = xDoc->createElement("section");
+ xMenu->appendChild(xSection);
+
+ css::uno::Reference<css::xml::dom::XNode> xMenuSection(xSection);
+ ConvertMenu(xMenuSection, xChild);
+
+ // now remove GtkMenu contents
+ while (true)
+ {
+ auto xFirstChild = xChild->getFirstChild();
+ if (!xFirstChild.is())
+ break;
+ xChild->removeChild(xFirstChild);
+ }
+
+ // change to GtkPopoverMenu
+ xClass->setNodeValue("GtkPopoverMenu");
+
+ // <property name="menu-model">
+ xChild->appendChild(CreateProperty(xDoc, "menu-model", sId));
+ xChild->appendChild(CreateProperty(xDoc, "has-arrow", "False"));
+ xChild->appendChild(CreateProperty(xDoc, "visible", "False"));
+ }
+ }
+
+ bool bChildHasIconSize = false;
+ bool bChildHasVisible = false;
+ bool bChildAlwaysShowImage = false;
+ GtkPositionType eChildImagePos = GTK_POS_LEFT;
+ bool bChildUseUnderline = false;
+ bool bChildVertOrientation = false;
+ bool bChildXAlign = false;
+ css::uno::Reference<css::xml::dom::XNode> xChildPropertyLabel;
+ css::uno::Reference<css::xml::dom::XNode> xChildPropertyIconName;
+ if (xChild->hasChildNodes() && xChild != xGeneratedImageChild)
+ {
+ auto aChildRes = Convert3To4(xChild);
+ bChildCanFocus |= aChildRes.m_bChildCanFocus;
+ if (bChildCanFocus && xCantFocus.is())
+ {
+ xNode->removeChild(xCantFocus);
+ xCantFocus.clear();
+ }
+ if (xChild->getNodeName() == "object")
+ {
+ bChildHasVisible = aChildRes.m_bHasVisible;
+ bChildHasIconSize = aChildRes.m_bHasIconSize;
+ bChildAlwaysShowImage = aChildRes.m_bAlwaysShowImage;
+ eChildImagePos = aChildRes.m_eImagePos;
+ bChildUseUnderline = aChildRes.m_bUseUnderline;
+ bChildVertOrientation = aChildRes.m_bVertOrientation;
+ bChildXAlign = aChildRes.m_bXAlign;
+ xChildPropertyLabel = aChildRes.m_xPropertyLabel;
+ xChildPropertyIconName = aChildRes.m_xPropertyIconName;
+ }
+ }
+
+ if (xChild->getNodeName() == "object")
+ {
+ auto xDoc = xChild->getOwnerDocument();
+
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xClass = xMap->getNamedItem("class");
+ OUString sClass(xClass->getNodeValue());
+
+ auto xInternalChildCandidate = xChild->getParentNode();
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xInternalChildCandidateMap
+ = xInternalChildCandidate->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xInternalChild
+ = xInternalChildCandidateMap->getNamedItem("internal-child");
+
+ // turn default gtk3 invisibility for widget objects into explicit invisible, but ignore internal-children
+ if (!bChildHasVisible && !xInternalChild)
+ {
+ if (sClass == "GtkBox" || sClass == "GtkButton" || sClass == "GtkCalendar"
+ || sClass == "GtkCheckButton" || sClass == "GtkRadioButton"
+ || sClass == "GtkComboBox" || sClass == "GtkComboBoxText"
+ || sClass == "GtkDrawingArea" || sClass == "GtkEntry" || sClass == "GtkExpander"
+ || sClass == "GtkFrame" || sClass == "GtkGrid" || sClass == "GtkImage"
+ || sClass == "GtkLabel" || sClass == "GtkMenuButton" || sClass == "GtkNotebook"
+ || sClass == "GtkOverlay" || sClass == "GtkPaned" || sClass == "GtkProgressBar"
+ || sClass == "GtkScrolledWindow" || sClass == "GtkScrollbar"
+ || sClass == "GtkSeparator" || sClass == "GtkSpinButton"
+ || sClass == "GtkSpinner" || sClass == "GtkTextView" || sClass == "GtkTreeView"
+ || sClass == "GtkViewport" || sClass == "GtkLinkButton"
+ || sClass == "GtkToggleButton" || sClass == "GtkButtonBox")
+
+ {
+ auto xVisible = CreateProperty(xDoc, "visible", "False");
+ insertAsFirstChild(xChild, xVisible);
+ }
+ }
+
+ if (sClass == "GtkButtonBox")
+ {
+ if (xInternalChild && xInternalChild->getNodeValue() == "action_area"
+ && !ToplevelIsMessageDialog(xChild))
+ {
+ xClass->setNodeValue("GtkHeaderBar");
+ auto xSpacingNode = CreateProperty(xDoc, "show-title-buttons", "False");
+ insertAsFirstChild(xChild, xSpacingNode);
+
+ // move the replacement GtkHeaderBar up to before the content_area
+ auto xContentAreaCandidate = xChild->getParentNode();
+ while (xContentAreaCandidate)
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xChildMap
+ = xContentAreaCandidate->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xName
+ = xChildMap->getNamedItem("internal-child");
+ if (xName && xName->getNodeValue() == "content_area")
+ {
+ auto xActionArea = xChild->getParentNode();
+
+ xActionArea->getParentNode()->removeChild(xActionArea);
+
+ css::uno::Reference<css::xml::dom::XAttr> xTypeTitleBar
+ = xDoc->createAttribute("type");
+ xTypeTitleBar->setValue("titlebar");
+ css::uno::Reference<css::xml::dom::XElement> xElem(
+ xActionArea, css::uno::UNO_QUERY_THROW);
+ xElem->setAttributeNode(xTypeTitleBar);
+ xElem->removeAttribute("internal-child");
+
+ xContentAreaCandidate->getParentNode()->insertBefore(
+ xActionArea, xContentAreaCandidate);
+
+ std::vector<named_node> aChildren;
+
+ css::uno::Reference<css::xml::dom::XNode> xTitleChild
+ = xChild->getFirstChild();
+ while (xTitleChild.is())
+ {
+ auto xNextTitleChild = xTitleChild->getNextSibling();
+ if (xTitleChild->getNodeName() == "child")
+ {
+ OUString sNodeId;
+
+ css::uno::Reference<css::xml::dom::XNode> xObject
+ = GetChildObject(xTitleChild);
+ if (xObject)
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xObjectMap
+ = xObject->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xObjectId
+ = xObjectMap->getNamedItem("id");
+ sNodeId = xObjectId->getNodeValue();
+ }
+
+ aChildren.push_back(std::make_pair(xTitleChild, sNodeId));
+ }
+ else if (xTitleChild->getNodeName() == "property")
+ {
+ // remove any <property name="homogeneous"> tag
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xTitleChildMap
+ = xTitleChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xPropName
+ = xTitleChildMap->getNamedItem("name");
+ OUString sPropName(xPropName->getNodeValue().replace('_', '-'));
+ if (sPropName == "homogeneous")
+ xChild->removeChild(xTitleChild);
+ }
+
+ xTitleChild = xNextTitleChild;
+ }
+
+ //sort child order within parent so that we match the platform button order
+ std::stable_sort(aChildren.begin(), aChildren.end(), sortButtonNodes);
+
+ int nNonHelpButtonCount = 0;
+
+ for (const auto& rTitleChild : aChildren)
+ {
+ xChild->removeChild(rTitleChild.first);
+ if (rTitleChild.second != "help")
+ ++nNonHelpButtonCount;
+ }
+
+ std::reverse(aChildren.begin(), aChildren.end());
+
+ for (const auto& rTitleChild : aChildren)
+ {
+ xChild->appendChild(rTitleChild.first);
+
+ css::uno::Reference<css::xml::dom::XElement> xChildElem(
+ rTitleChild.first, css::uno::UNO_QUERY_THROW);
+ if (!xChildElem->hasAttribute("type"))
+ {
+ // turn parent tag of <child> into <child type="end"> except for cancel/close which we'll
+ // put at start unless there is nothing at end
+ css::uno::Reference<css::xml::dom::XAttr> xTypeEnd
+ = xDoc->createAttribute("type");
+ if (nNonHelpButtonCount >= 2
+ && (rTitleChild.second == "cancel"
+ || rTitleChild.second == "close"))
+ xTypeEnd->setValue("start");
+ else
+ xTypeEnd->setValue("end");
+ xChildElem->setAttributeNode(xTypeEnd);
+ }
+ }
+
+ auto xUseHeaderBar = CreateProperty(xDoc, "use-header-bar", "1");
+ SetPropertyOnTopLevel(xContentAreaCandidate, xUseHeaderBar);
+
+ break;
+ }
+ xContentAreaCandidate = xContentAreaCandidate->getParentNode();
+ }
+ }
+ else // GtkMessageDialog
+ xClass->setNodeValue("GtkBox");
+ }
+ else if (sClass == "GtkToolbar")
+ {
+ xClass->setNodeValue("GtkBox");
+ css::uno::Reference<css::xml::dom::XElement> xStyle = xDoc->createElement("style");
+ css::uno::Reference<css::xml::dom::XElement> xToolbarClass
+ = xDoc->createElement("class");
+ css::uno::Reference<css::xml::dom::XAttr> xPropName = xDoc->createAttribute("name");
+ xPropName->setValue("toolbar");
+ xToolbarClass->setAttributeNode(xPropName);
+ xStyle->appendChild(xToolbarClass);
+ xChild->appendChild(xStyle);
+ }
+ else if (sClass == "GtkToolButton")
+ {
+ xClass->setNodeValue("GtkButton");
+ }
+ else if (sClass == "GtkToolItem")
+ {
+ xClass->setNodeValue("GtkBox");
+ }
+ else if (sClass == "GtkMenuToolButton")
+ {
+ xClass->setNodeValue("GtkMenuButton");
+ if (gtk_get_minor_version() >= 4)
+ {
+ auto xAlwaysShowArrow = CreateProperty(xDoc, "always-show-arrow", "True");
+ insertAsFirstChild(xChild, xAlwaysShowArrow);
+ }
+ }
+ else if (sClass == "GtkRadioToolButton")
+ {
+ xClass->setNodeValue("GtkCheckButton");
+ }
+ else if (sClass == "GtkToggleToolButton")
+ {
+ xClass->setNodeValue("GtkToggleButton");
+ }
+ else if (sClass == "GtkSeparatorToolItem")
+ {
+ xClass->setNodeValue("GtkSeparator");
+ }
+ else if (sClass == "GtkBox")
+ {
+ // reverse the order of the pack-type=end widgets
+ std::vector<css::uno::Reference<css::xml::dom::XNode>> aPackEnds;
+ std::vector<css::uno::Reference<css::xml::dom::XNode>> aPackStarts;
+ css::uno::Reference<css::xml::dom::XNode> xBoxChild = xChild->getFirstChild();
+ while (xBoxChild.is())
+ {
+ auto xNextBoxChild = xBoxChild->getNextSibling();
+
+ if (xBoxChild->getNodeName() == "child")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xBoxChildMap
+ = xBoxChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xType
+ = xBoxChildMap->getNamedItem("type");
+ if (xType && xType->getNodeValue() == "end")
+ aPackEnds.push_back(xChild->removeChild(xBoxChild));
+ else
+ aPackStarts.push_back(xBoxChild);
+ }
+
+ xBoxChild = xNextBoxChild;
+ }
+
+ if (!aPackEnds.empty())
+ {
+ std::reverse(aPackEnds.begin(), aPackEnds.end());
+
+ if (!bChildVertOrientation)
+ {
+ bool bHasStartObject = false;
+ bool bLastStartExpands = false;
+ if (!aPackStarts.empty())
+ {
+ css::uno::Reference<css::xml::dom::XNode> xLastStartObject;
+ for (auto it = aPackStarts.rbegin(); it != aPackStarts.rend(); ++it)
+ {
+ xLastStartObject = GetChildObject(*it);
+ if (xLastStartObject.is())
+ {
+ bHasStartObject = true;
+ for (css::uno::Reference<css::xml::dom::XNode> xExpandCandidate
+ = xLastStartObject->getFirstChild();
+ xExpandCandidate.is();
+ xExpandCandidate = xExpandCandidate->getNextSibling())
+ {
+ if (xExpandCandidate->getNodeName() == "property")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap>
+ xExpandMap = xExpandCandidate->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xName
+ = xExpandMap->getNamedItem("name");
+ OUString sPropName(xName->getNodeValue());
+ if (sPropName == "hexpand")
+ {
+ bLastStartExpands
+ = toBool(xExpandCandidate->getFirstChild()
+ ->getNodeValue());
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ if (bHasStartObject && !bLastStartExpands)
+ {
+ auto xAlign = CreateProperty(xDoc, "halign", "end");
+ insertAsFirstChild(GetChildObject(aPackEnds[0]), xAlign);
+ auto xExpand = CreateProperty(xDoc, "hexpand", "True");
+ insertAsFirstChild(GetChildObject(aPackEnds[0]), xExpand);
+ }
+ }
+
+ for (auto& xPackEnd : aPackEnds)
+ xChild->appendChild(xPackEnd);
+ }
+ }
+ else if (sClass == "GtkRadioButton")
+ {
+ xClass->setNodeValue("GtkCheckButton");
+ }
+ else if (sClass == "GtkImage")
+ {
+ /* a) keep symbolic icon-names as GtkImage, e.g. writer, format, columns, next/prev
+ buttons
+ b) assume that an explicit icon-size represents a request for a scaled icon
+ so keep those as GtkImage. e.g. hyperlink dialog notebook tab images and
+ calc paste special button images
+ c) turn everything else into a GtkPicture, e.g. help, about. If a GtkPicture
+ ends up used to display just a simple icon that's generally not a problem.
+ */
+ bool bKeepAsImage = false;
+ if (bChildHasIconSize)
+ bKeepAsImage = true;
+ else if (xChildPropertyIconName.is())
+ {
+ OUString sIconName(xChildPropertyIconName->getFirstChild()->getNodeValue());
+ bool bHasSymbolicIconName = IsAllowedBuiltInIcon(sIconName);
+ if (bHasSymbolicIconName)
+ {
+ if (sIconName != "missing-image")
+ bKeepAsImage = true;
+ else
+ {
+ // If the symbolic icon-name is missing-image then decide to make
+ // it a GtkPicture if it has a parent widget and keep it as GtkImage
+ // if it has just the root "interface" as parent.
+ // for e.g. view, user interface
+ css::uno::Reference<css::xml::dom::XNode> xParent
+ = xChild->getParentNode();
+ bKeepAsImage = xParent->getNodeName() == "interface";
+ if (!bKeepAsImage)
+ xChild->removeChild(xChildPropertyIconName);
+ }
+ }
+ else
+ {
+ // private:graphicrepository/ would be turned by gio (?) into private:///graphicrepository/
+ // so use private:///graphicrepository/ here. At the moment we just want this to be transported
+ // as-is to postprocess_widget. Though it might be nice to register a protocol handler with gio
+ // to avoid us doing the load in that second pass.
+ auto xUri = CreateProperty(xDoc, "file",
+ "private:///graphicrepository/" + sIconName);
+ xChild->insertBefore(xUri, xChildPropertyIconName);
+ // calc, insert, header and footer, custom header menubutton icon
+ auto xCanShrink = CreateProperty(xDoc, "can-shrink", "False");
+ xChild->insertBefore(xCanShrink, xChildPropertyIconName);
+ xChild->removeChild(xChildPropertyIconName);
+ }
+ }
+ if (!bKeepAsImage)
+ xClass->setNodeValue("GtkPicture");
+ }
+ else if (sClass == "GtkPopover" && !bHasVisible)
+ {
+ auto xVisible = CreateProperty(xDoc, "visible", "False");
+ insertAsFirstChild(xChild, xVisible);
+ }
+ else if (sClass == "AtkObject")
+ {
+ css::uno::Reference<css::xml::dom::XNode> xParentObject = xNode->getParentNode();
+ css::uno::Reference<css::xml::dom::XElement> xAccessibility
+ = xDoc->createElement("accessibility");
+ xParentObject->insertBefore(xAccessibility, xNode);
+
+ // move the converted old AtkObject:: properties into a new accessibility parent
+ css::uno::Reference<css::xml::dom::XNode> xRole;
+ for (css::uno::Reference<css::xml::dom::XNode> xCurrent = xChild->getFirstChild();
+ xCurrent.is(); xCurrent = xChild->getFirstChild())
+ {
+ if (css::uno::Reference<css::xml::dom::XNamedNodeMap> xCurrentMap
+ = xCurrent->getAttributes())
+ {
+ css::uno::Reference<css::xml::dom::XNode> xName
+ = xCurrentMap->getNamedItem("name");
+ OUString sName(xName->getNodeValue());
+ if (sName == "accessible-role")
+ {
+ xRole = xCurrent;
+ }
+ }
+
+ xAccessibility->appendChild(xChild->removeChild(xCurrent));
+ }
+
+ if (xRole)
+ {
+ auto xRoleText = xRole->getFirstChild();
+ if (xRoleText.is())
+ {
+ OUString sText = xRoleText->getNodeValue();
+ // don't really know what the right solution here should be, but "static" isn't
+ // a role that exists in gtk4, nothing seems to match exactly, but maybe "alert"
+ // is a useful place to land for now
+ if (sText == "static")
+ xRoleText->setNodeValue("alert");
+ }
+ // move out of accessibility section to property section
+ xParentObject->insertBefore(xRole->getParentNode()->removeChild(xRole),
+ xAccessibility);
+ }
+ }
+
+ // only create the child box for GtkButton/GtkToggleButton
+ if (bChildAlwaysShowImage)
+ {
+ auto xImageCandidateNode = xChild->getLastChild();
+ if (xImageCandidateNode && xImageCandidateNode->getNodeName() != "child")
+ xImageCandidateNode.clear();
+ if (xImageCandidateNode)
+ xChild->removeChild(xImageCandidateNode);
+
+ // for GtkMenuButton if this is a gearmenu with just an icon
+ // then "icon-name" is used for the indicator and there is
+ // expected to be no text. If there is a GtkPicture then treat
+ // this like a GtkButton and presumably it's a ToggleMenuButton
+ // and the relocation of contents happens in the builder
+ if (sClass == "GtkMenuButton")
+ {
+ bChildAlwaysShowImage = false;
+ if (xImageCandidateNode)
+ {
+ bChildAlwaysShowImage = true;
+ auto xImageObject = GetChildObject(xImageCandidateNode);
+ auto xProp = xImageObject->getFirstChild();
+ while (xProp.is())
+ {
+ if (xProp->getNodeName() == "property")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xPropMap
+ = xProp->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xPropName
+ = xPropMap->getNamedItem("name");
+ OUString sPropName(xPropName->getNodeValue().replace('_', '-'));
+ if (sPropName == "icon-name")
+ {
+ OUString sIconName(xProp->getFirstChild()->getNodeValue());
+ auto xIconName = CreateProperty(xDoc, "icon-name", sIconName);
+ insertAsFirstChild(xChild, xIconName);
+ bChildAlwaysShowImage = false;
+ break;
+ }
+ }
+
+ xProp = xProp->getNextSibling();
+ }
+ }
+ }
+
+ if (bChildAlwaysShowImage)
+ {
+ css::uno::Reference<css::xml::dom::XElement> xNewChildNode
+ = xDoc->createElement("child");
+ css::uno::Reference<css::xml::dom::XElement> xNewObjectNode
+ = xDoc->createElement("object");
+ css::uno::Reference<css::xml::dom::XAttr> xBoxClassName
+ = xDoc->createAttribute("class");
+ xBoxClassName->setValue("GtkBox");
+ xNewObjectNode->setAttributeNode(xBoxClassName);
+
+ if (eChildImagePos == GTK_POS_TOP || eChildImagePos == GTK_POS_BOTTOM)
+ {
+ auto xOrientation = CreateProperty(xDoc, "orientation", "vertical");
+ xNewObjectNode->appendChild(xOrientation);
+ }
+
+ xNewObjectNode->appendChild(CreateProperty(xDoc, "spacing", "6"));
+ if (!bChildXAlign)
+ xNewObjectNode->appendChild(CreateProperty(xDoc, "halign", "center"));
+
+ xNewChildNode->appendChild(xNewObjectNode);
+
+ xChild->appendChild(xNewChildNode);
+
+ css::uno::Reference<css::xml::dom::XElement> xNewLabelChildNode
+ = xDoc->createElement("child");
+ css::uno::Reference<css::xml::dom::XElement> xNewChildObjectNode
+ = xDoc->createElement("object");
+ css::uno::Reference<css::xml::dom::XAttr> xLabelClassName
+ = xDoc->createAttribute("class");
+ xLabelClassName->setValue("GtkLabel");
+ xNewChildObjectNode->setAttributeNode(xLabelClassName);
+ if (xChildPropertyLabel)
+ {
+ xNewChildObjectNode->appendChild(
+ xChildPropertyLabel->getParentNode()->removeChild(xChildPropertyLabel));
+ }
+ else
+ {
+ auto xNotVisible = CreateProperty(xDoc, "visible", "False");
+ xNewChildObjectNode->appendChild(xNotVisible);
+ }
+ if (bChildUseUnderline)
+ {
+ auto xUseUnderline = CreateProperty(xDoc, "use-underline", "True");
+ xNewChildObjectNode->appendChild(xUseUnderline);
+ }
+ xNewLabelChildNode->appendChild(xNewChildObjectNode);
+
+ if (eChildImagePos == GTK_POS_LEFT || eChildImagePos == GTK_POS_TOP)
+ {
+ if (xImageCandidateNode)
+ xNewObjectNode->appendChild(xImageCandidateNode);
+ xNewObjectNode->appendChild(xNewLabelChildNode);
+ }
+ else
+ {
+ xNewObjectNode->appendChild(xNewLabelChildNode);
+ if (xImageCandidateNode)
+ xNewObjectNode->appendChild(xImageCandidateNode);
+ }
+ }
+ }
+ }
+ else if (xChild->getNodeName() == "items")
+ {
+ // gtk4-4.6.7 won't set an active item if the active item
+ // property precedes the list of combobox items so if we
+ // have "items" move them to the start
+ insertAsFirstChild(xNode, xNode->removeChild(xChild));
+ }
+
+ xChild = xNextChild;
+ }
+
+ if (!sBorderWidth.isEmpty())
+ AddBorderAsMargins(xNode, sBorderWidth);
+
+ for (auto& xRemove : xRemoveList)
+ xNode->removeChild(xRemove);
+
+ return ConvertResult(bChildCanFocus, bHasVisible, bHasIconSize, bAlwaysShowImage, bUseUnderline,
+ bVertOrientation, bXAlign, eImagePos, xPropertyLabel, xPropertyIconName);
+}
+
+std::once_flag cairo_surface_type_flag;
+
+void ensure_cairo_surface_type()
+{
+ // cairo_gobject_surface_get_type needs to be called at least once for
+ // g_type_from_name to be able to resolve "CairoSurface". In gtk3 there was fallback
+ // mechanism to attempt to resolve such "missing" types which is not in place for
+ // gtk4 so ensure it can be found explicitly
+ std::call_once(cairo_surface_type_flag, []() { cairo_gobject_surface_get_type(); });
+}
+}
+
+void builder_add_from_gtk3_file(GtkBuilder* pBuilder, const OUString& rUri)
+{
+ GError* err = nullptr;
+
+ // load the xml
+ css::uno::Reference<css::uno::XComponentContext> xContext
+ = ::comphelper::getProcessComponentContext();
+ css::uno::Reference<css::xml::dom::XDocumentBuilder> xBuilder
+ = css::xml::dom::DocumentBuilder::create(xContext);
+ css::uno::Reference<css::xml::dom::XDocument> xDocument = xBuilder->parseURI(rUri);
+
+ // convert it from gtk3 to gtk4
+ Convert3To4(xDocument);
+
+ css::uno::Reference<css::beans::XPropertySet> xTempFile(css::io::TempFile::create(xContext),
+ css::uno::UNO_QUERY_THROW);
+ css::uno::Reference<css::io::XStream> xTempStream(xTempFile, css::uno::UNO_QUERY_THROW);
+ xTempFile->setPropertyValue("RemoveFile", css::uno::Any(false));
+
+ // serialize it back to xml
+ css::uno::Reference<css::xml::sax::XSAXSerializable> xSerializer(xDocument,
+ css::uno::UNO_QUERY_THROW);
+ css::uno::Reference<css::xml::sax::XWriter> xWriter = css::xml::sax::Writer::create(xContext);
+ css::uno::Reference<css::io::XOutputStream> xTempOut = xTempStream->getOutputStream();
+ xWriter->setOutputStream(xTempOut);
+ xSerializer->serialize(
+ css::uno::Reference<css::xml::sax::XDocumentHandler>(xWriter, css::uno::UNO_QUERY_THROW),
+ css::uno::Sequence<css::beans::StringPair>());
+
+ ensure_cairo_surface_type();
+
+ // feed it to GtkBuilder
+ css::uno::Reference<css::io::XSeekable> xTempSeek(xTempStream, css::uno::UNO_QUERY_THROW);
+ xTempSeek->seek(0);
+ auto xInput = xTempStream->getInputStream();
+ css::uno::Sequence<sal_Int8> bytes;
+ sal_Int32 nToRead = xInput->available();
+ while (true)
+ {
+ sal_Int32 nRead = xInput->readBytes(bytes, std::max<sal_Int32>(nToRead, 4096));
+ if (!nRead)
+ break;
+ // fprintf(stderr, "text is %s\n", reinterpret_cast<const gchar*>(bytes.getArray()));
+ bool rc = gtk_builder_add_from_string(
+ pBuilder, reinterpret_cast<const gchar*>(bytes.getArray()), nRead, &err);
+ if (!rc)
+ {
+ SAL_WARN("vcl.gtk",
+ "GtkInstanceBuilder: error when calling gtk_builder_add_from_string: "
+ << err->message);
+ g_error_free(err);
+ }
+ assert(rc && "could not load UI file");
+ // in the real world the first loop has read the entire file because its all 'available' without blocking
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/convert3to4.hxx b/vcl/unx/gtk4/convert3to4.hxx
new file mode 100644
index 0000000000..d75d9fd910
--- /dev/null
+++ b/vcl/unx/gtk4/convert3to4.hxx
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <rtl/ustring.hxx>
+#include <gtk/gtk.h>
+
+// convert gtk3 .ui to gtk4 and load it
+void builder_add_from_gtk3_file(GtkBuilder* pBuilder, const OUString& rUri);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/customcellrenderer.cxx b/vcl/unx/gtk4/customcellrenderer.cxx
new file mode 100644
index 0000000000..aebe2c89ed
--- /dev/null
+++ b/vcl/unx/gtk4/customcellrenderer.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../gtk3/customcellrenderer.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/unx/gtk4/fpicker/SalGtkFilePicker.cxx b/vcl/unx/gtk4/fpicker/SalGtkFilePicker.cxx
new file mode 100644
index 0000000000..a820b49956
--- /dev/null
+++ b/vcl/unx/gtk4/fpicker/SalGtkFilePicker.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/fpicker/SalGtkFilePicker.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/fpicker/SalGtkFilePicker.hxx b/vcl/unx/gtk4/fpicker/SalGtkFilePicker.hxx
new file mode 100644
index 0000000000..44ea7bd44b
--- /dev/null
+++ b/vcl/unx/gtk4/fpicker/SalGtkFilePicker.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/fpicker/SalGtkFilePicker.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.cxx b/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.cxx
new file mode 100644
index 0000000000..41dfdf0216
--- /dev/null
+++ b/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/fpicker/SalGtkFolderPicker.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.hxx b/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.hxx
new file mode 100644
index 0000000000..129a699336
--- /dev/null
+++ b/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/fpicker/SalGtkFolderPicker.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/fpicker/SalGtkPicker.cxx b/vcl/unx/gtk4/fpicker/SalGtkPicker.cxx
new file mode 100644
index 0000000000..159ef47040
--- /dev/null
+++ b/vcl/unx/gtk4/fpicker/SalGtkPicker.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/fpicker/SalGtkPicker.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/fpicker/SalGtkPicker.hxx b/vcl/unx/gtk4/fpicker/SalGtkPicker.hxx
new file mode 100644
index 0000000000..0432004a05
--- /dev/null
+++ b/vcl/unx/gtk4/fpicker/SalGtkPicker.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/fpicker/SalGtkPicker.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/fpicker/eventnotification.hxx b/vcl/unx/gtk4/fpicker/eventnotification.hxx
new file mode 100644
index 0000000000..b1e16bd653
--- /dev/null
+++ b/vcl/unx/gtk4/fpicker/eventnotification.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/fpicker/eventnotification.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/fpicker/resourceprovider.cxx b/vcl/unx/gtk4/fpicker/resourceprovider.cxx
new file mode 100644
index 0000000000..d4251cc94a
--- /dev/null
+++ b/vcl/unx/gtk4/fpicker/resourceprovider.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/fpicker/resourceprovider.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gloactiongroup.cxx b/vcl/unx/gtk4/gloactiongroup.cxx
new file mode 100644
index 0000000000..f147af2834
--- /dev/null
+++ b/vcl/unx/gtk4/gloactiongroup.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../gtk3/gloactiongroup.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/glomenu.cxx b/vcl/unx/gtk4/glomenu.cxx
new file mode 100644
index 0000000000..cae9bc03a4
--- /dev/null
+++ b/vcl/unx/gtk4/glomenu.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../gtk3/glomenu.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtkcairo.cxx b/vcl/unx/gtk4/gtkcairo.cxx
new file mode 100644
index 0000000000..3178fdea02
--- /dev/null
+++ b/vcl/unx/gtk4/gtkcairo.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../gtk3/gtkcairo.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtkcairo.hxx b/vcl/unx/gtk4/gtkcairo.hxx
new file mode 100644
index 0000000000..97f63101dd
--- /dev/null
+++ b/vcl/unx/gtk4/gtkcairo.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../gtk3/gtkdata.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtkdata.cxx b/vcl/unx/gtk4/gtkdata.cxx
new file mode 100644
index 0000000000..41d85217ce
--- /dev/null
+++ b/vcl/unx/gtk4/gtkdata.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../gtk3/gtkdata.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtkframe.cxx b/vcl/unx/gtk4/gtkframe.cxx
new file mode 100644
index 0000000000..e446ba8a55
--- /dev/null
+++ b/vcl/unx/gtk4/gtkframe.cxx
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "transferableprovider.hxx"
+
+#include "../gtk3/gtkframe.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtkinst.cxx b/vcl/unx/gtk4/gtkinst.cxx
new file mode 100644
index 0000000000..658c01233c
--- /dev/null
+++ b/vcl/unx/gtk4/gtkinst.cxx
@@ -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/.
+ */
+
+// make gtk4 plug advertise correctly as gtk4
+#define GTK_TOOLKIT_NAME "gtk4"
+
+#include "convert3to4.hxx"
+#include "notifyinglayout.hxx"
+#include "surfacecellrenderer.hxx"
+#include "surfacepaintable.hxx"
+#include "transferableprovider.hxx"
+
+#include "../gtk3/gtkinst.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtkobject.cxx b/vcl/unx/gtk4/gtkobject.cxx
new file mode 100644
index 0000000000..8eb5dcf1a9
--- /dev/null
+++ b/vcl/unx/gtk4/gtkobject.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../gtk3/gtkobject.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtksalmenu.cxx b/vcl/unx/gtk4/gtksalmenu.cxx
new file mode 100644
index 0000000000..8950781246
--- /dev/null
+++ b/vcl/unx/gtk4/gtksalmenu.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../gtk3/gtksalmenu.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtksys.cxx b/vcl/unx/gtk4/gtksys.cxx
new file mode 100644
index 0000000000..dfdcf0a7c4
--- /dev/null
+++ b/vcl/unx/gtk4/gtksys.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../gtk3/gtksys.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/hudawareness.cxx b/vcl/unx/gtk4/hudawareness.cxx
new file mode 100644
index 0000000000..b2683b2bd7
--- /dev/null
+++ b/vcl/unx/gtk4/hudawareness.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../gtk3/hudawareness.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/notifyinglayout.cxx b/vcl/unx/gtk4/notifyinglayout.cxx
new file mode 100644
index 0000000000..46b9a2d95f
--- /dev/null
+++ b/vcl/unx/gtk4/notifyinglayout.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/.
+ */
+
+#include "notifyinglayout.hxx"
+
+struct _NotifyingLayout
+{
+ GtkLayoutManager parent_instance;
+
+ GtkWidget* m_pWidget;
+ GtkLayoutManager* m_pOrigManager;
+ Link<void*, void> m_aLink;
+};
+
+G_DEFINE_TYPE(NotifyingLayout, notifying_layout, GTK_TYPE_LAYOUT_MANAGER)
+
+static void notifying_layout_measure(GtkLayoutManager* pLayoutManager, GtkWidget* widget,
+ GtkOrientation orientation, int for_size, int* minimum,
+ int* natural, int* minimum_baseline, int* natural_baseline)
+{
+ NotifyingLayout* self = NOTIFYING_LAYOUT(pLayoutManager);
+ GtkLayoutManagerClass* pKlass
+ = GTK_LAYOUT_MANAGER_CLASS(G_OBJECT_GET_CLASS(self->m_pOrigManager));
+ pKlass->measure(self->m_pOrigManager, widget, orientation, for_size, minimum, natural,
+ minimum_baseline, natural_baseline);
+}
+
+static void notifying_layout_allocate(GtkLayoutManager* pLayoutManager, GtkWidget* widget,
+ int width, int height, int baseline)
+{
+ NotifyingLayout* self = NOTIFYING_LAYOUT(pLayoutManager);
+ GtkLayoutManagerClass* pKlass
+ = GTK_LAYOUT_MANAGER_CLASS(G_OBJECT_GET_CLASS(self->m_pOrigManager));
+ pKlass->allocate(self->m_pOrigManager, widget, width, height, baseline);
+ self->m_aLink.Call(nullptr);
+}
+
+static GtkSizeRequestMode notifying_layout_get_request_mode(GtkLayoutManager* pLayoutManager,
+ GtkWidget* widget)
+{
+ NotifyingLayout* self = NOTIFYING_LAYOUT(pLayoutManager);
+ GtkLayoutManagerClass* pKlass
+ = GTK_LAYOUT_MANAGER_CLASS(G_OBJECT_GET_CLASS(self->m_pOrigManager));
+ return pKlass->get_request_mode(self->m_pOrigManager, widget);
+}
+
+static void notifying_layout_class_init(NotifyingLayoutClass* klass)
+{
+ GtkLayoutManagerClass* layout_class = GTK_LAYOUT_MANAGER_CLASS(klass);
+
+ layout_class->get_request_mode = notifying_layout_get_request_mode;
+ layout_class->measure = notifying_layout_measure;
+ layout_class->allocate = notifying_layout_allocate;
+}
+
+static void notifying_layout_init(NotifyingLayout* self)
+{
+ self->m_pWidget = nullptr;
+ self->m_pOrigManager = nullptr;
+
+ // prevent loplugin:unreffun firing on macro generated function
+ (void)notifying_layout_get_instance_private(self);
+}
+
+void notifying_layout_start_watch(NotifyingLayout* self, GtkWidget* pWidget,
+ const Link<void*, void>& rLink)
+{
+ self->m_pWidget = pWidget;
+ self->m_aLink = rLink;
+
+ self->m_pOrigManager = gtk_widget_get_layout_manager(self->m_pWidget);
+ g_object_ref(self->m_pOrigManager);
+
+ gtk_widget_set_layout_manager(pWidget, GTK_LAYOUT_MANAGER(self));
+}
+
+void notifying_layout_stop_watch(NotifyingLayout* self)
+{
+ gtk_widget_set_layout_manager(self->m_pWidget, self->m_pOrigManager);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/notifyinglayout.hxx b/vcl/unx/gtk4/notifyinglayout.hxx
new file mode 100644
index 0000000000..a717a30613
--- /dev/null
+++ b/vcl/unx/gtk4/notifyinglayout.hxx
@@ -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/.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <tools/link.hxx>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE(NotifyingLayout, notifying_layout, NOTIFYING, LAYOUT, GtkLayoutManager)
+
+/*
+ Replace the existing GtkLayoutManager of pWidget with pLayout instead which will
+ forward all requests to the original GtkLayoutManager but additionally call
+ rLink when a size is allocated to the pWidget.
+
+ This provides a workaround for the removal of the size-allocate signal in gtk4
+*/
+void notifying_layout_start_watch(NotifyingLayout* pLayout, GtkWidget* pWidget,
+ const Link<void*, void>& rLink);
+
+/*
+ Undo a previous notifying_layout_start_watch.
+*/
+void notifying_layout_stop_watch(NotifyingLayout* pLayout);
+
+G_END_DECLS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/salnativewidgets-gtk.cxx b/vcl/unx/gtk4/salnativewidgets-gtk.cxx
new file mode 100644
index 0000000000..ed036e98c9
--- /dev/null
+++ b/vcl/unx/gtk4/salnativewidgets-gtk.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../gtk3/salnativewidgets-gtk.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/surfacecellrenderer.cxx b/vcl/unx/gtk4/surfacecellrenderer.cxx
new file mode 100644
index 0000000000..2ae0782b98
--- /dev/null
+++ b/vcl/unx/gtk4/surfacecellrenderer.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/.
+ */
+
+#include <vcl/svapp.hxx>
+#include "surfacecellrenderer.hxx"
+#include <cairo/cairo-gobject.h>
+
+namespace
+{
+struct _SurfaceCellRendererClass : public GtkCellRendererClass
+{
+};
+
+enum
+{
+ PROP_SURFACE = 10000,
+};
+}
+
+G_DEFINE_TYPE(SurfaceCellRenderer, surface_cell_renderer, GTK_TYPE_CELL_RENDERER)
+
+static void surface_cell_renderer_init(SurfaceCellRenderer* self)
+{
+ self->surface = nullptr;
+ // prevent loplugin:unreffun firing on macro generated function
+ (void)surface_cell_renderer_get_instance_private(self);
+}
+
+static void surface_cell_renderer_get_property(GObject* object, guint param_id, GValue* value,
+ GParamSpec* pspec)
+{
+ SurfaceCellRenderer* cellsurface = SURFACE_CELL_RENDERER(object);
+
+ switch (param_id)
+ {
+ case PROP_SURFACE:
+ g_value_set_boxed(value, cellsurface->surface);
+ break;
+ default:
+ G_OBJECT_CLASS(surface_cell_renderer_parent_class)
+ ->get_property(object, param_id, value, pspec);
+ break;
+ }
+}
+
+static void surface_cell_renderer_set_property(GObject* object, guint param_id, const GValue* value,
+ GParamSpec* pspec)
+{
+ SurfaceCellRenderer* cellsurface = SURFACE_CELL_RENDERER(object);
+
+ switch (param_id)
+ {
+ case PROP_SURFACE:
+ if (cellsurface->surface)
+ cairo_surface_destroy(cellsurface->surface);
+ cellsurface->surface = static_cast<cairo_surface_t*>(g_value_get_boxed(value));
+ if (cellsurface->surface)
+ cairo_surface_reference(cellsurface->surface);
+ break;
+ default:
+ G_OBJECT_CLASS(surface_cell_renderer_parent_class)
+ ->set_property(object, param_id, value, pspec);
+ break;
+ }
+}
+
+static bool surface_cell_renderer_get_preferred_size(GtkCellRenderer* cell,
+ GtkOrientation orientation, gint* minimum_size,
+ gint* natural_size);
+
+static void surface_cell_renderer_snapshot(GtkCellRenderer* cell, GtkSnapshot* snapshot,
+ GtkWidget* widget, const GdkRectangle* background_area,
+ const GdkRectangle* cell_area,
+ GtkCellRendererState flags);
+
+static void surface_cell_renderer_render(GtkCellRenderer* cell, cairo_t* cr, GtkWidget* widget,
+ const GdkRectangle* background_area,
+ const GdkRectangle* cell_area, GtkCellRendererState flags);
+
+static void surface_cell_renderer_finalize(GObject* object)
+{
+ SurfaceCellRenderer* cellsurface = SURFACE_CELL_RENDERER(object);
+
+ if (cellsurface->surface)
+ cairo_surface_destroy(cellsurface->surface);
+
+ G_OBJECT_CLASS(surface_cell_renderer_parent_class)->finalize(object);
+}
+
+static void surface_cell_renderer_get_preferred_width(GtkCellRenderer* cell, GtkWidget* widget,
+ gint* minimum_size, gint* natural_size)
+{
+ if (!surface_cell_renderer_get_preferred_size(cell, GTK_ORIENTATION_HORIZONTAL, minimum_size,
+ natural_size))
+ {
+ // fallback to parent if we're empty
+ GTK_CELL_RENDERER_CLASS(surface_cell_renderer_parent_class)
+ ->get_preferred_width(cell, widget, minimum_size, natural_size);
+ }
+}
+
+static void surface_cell_renderer_get_preferred_height(GtkCellRenderer* cell, GtkWidget* widget,
+ gint* minimum_size, gint* natural_size)
+{
+ if (!surface_cell_renderer_get_preferred_size(cell, GTK_ORIENTATION_VERTICAL, minimum_size,
+ natural_size))
+ {
+ // fallback to parent if we're empty
+ GTK_CELL_RENDERER_CLASS(surface_cell_renderer_parent_class)
+ ->get_preferred_height(cell, widget, minimum_size, natural_size);
+ }
+}
+
+static void surface_cell_renderer_get_preferred_height_for_width(GtkCellRenderer* cell,
+ GtkWidget* widget, gint /*width*/,
+ gint* minimum_height,
+ gint* natural_height)
+{
+ gtk_cell_renderer_get_preferred_height(cell, widget, minimum_height, natural_height);
+}
+
+static void surface_cell_renderer_get_preferred_width_for_height(GtkCellRenderer* cell,
+ GtkWidget* widget, gint /*height*/,
+ gint* minimum_width,
+ gint* natural_width)
+{
+ gtk_cell_renderer_get_preferred_width(cell, widget, minimum_width, natural_width);
+}
+
+void surface_cell_renderer_class_init(SurfaceCellRendererClass* klass)
+{
+ GtkCellRendererClass* cell_class = GTK_CELL_RENDERER_CLASS(klass);
+ GObjectClass* object_class = G_OBJECT_CLASS(klass);
+
+ /* Hook up functions to set and get our custom cell renderer properties */
+ object_class->get_property = surface_cell_renderer_get_property;
+ object_class->set_property = surface_cell_renderer_set_property;
+
+ surface_cell_renderer_parent_class = g_type_class_peek_parent(klass);
+ object_class->finalize = surface_cell_renderer_finalize;
+
+ cell_class->get_preferred_width = surface_cell_renderer_get_preferred_width;
+ cell_class->get_preferred_height = surface_cell_renderer_get_preferred_height;
+ cell_class->get_preferred_width_for_height
+ = surface_cell_renderer_get_preferred_width_for_height;
+ cell_class->get_preferred_height_for_width
+ = surface_cell_renderer_get_preferred_height_for_width;
+
+ cell_class->snapshot = surface_cell_renderer_snapshot;
+
+ g_object_class_install_property(
+ object_class, PROP_SURFACE,
+ g_param_spec_boxed("surface", "Surface", "The cairo surface to render",
+ CAIRO_GOBJECT_TYPE_SURFACE, G_PARAM_READWRITE));
+}
+
+GtkCellRenderer* surface_cell_renderer_new()
+{
+ return GTK_CELL_RENDERER(g_object_new(SURFACE_TYPE_CELL_RENDERER, nullptr));
+}
+
+static void get_surface_size(cairo_surface_t* pSurface, int& rWidth, int& rHeight)
+{
+ double x1, x2, y1, y2;
+ cairo_t* cr = cairo_create(pSurface);
+ cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
+ cairo_destroy(cr);
+
+ rWidth = x2 - x1;
+ rHeight = y2 - y1;
+}
+
+bool surface_cell_renderer_get_preferred_size(GtkCellRenderer* cell, GtkOrientation orientation,
+ gint* minimum_size, gint* natural_size)
+{
+ SurfaceCellRenderer* cellsurface = SURFACE_CELL_RENDERER(cell);
+
+ int nWidth = 0;
+ int nHeight = 0;
+
+ if (cellsurface->surface)
+ get_surface_size(cellsurface->surface, nWidth, nHeight);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (minimum_size)
+ *minimum_size = nWidth;
+
+ if (natural_size)
+ *natural_size = nWidth;
+ }
+ else
+ {
+ if (minimum_size)
+ *minimum_size = nHeight;
+
+ if (natural_size)
+ *natural_size = nHeight;
+ }
+
+ return true;
+}
+
+void surface_cell_renderer_render(GtkCellRenderer* cell, cairo_t* cr, GtkWidget* /*widget*/,
+ const GdkRectangle* /*background_area*/,
+ const GdkRectangle* cell_area, GtkCellRendererState /*flags*/)
+{
+ SurfaceCellRenderer* cellsurface = SURFACE_CELL_RENDERER(cell);
+ if (!cellsurface->surface)
+ return;
+
+ int nWidth, nHeight;
+ get_surface_size(cellsurface->surface, nWidth, nHeight);
+ int nXOffset = (cell_area->width - nWidth) / 2;
+ int nYOffset = (cell_area->height - nHeight) / 2;
+
+ cairo_set_source_surface(cr, cellsurface->surface, cell_area->x + nXOffset,
+ cell_area->y + nYOffset);
+ cairo_paint(cr);
+}
+
+static void surface_cell_renderer_snapshot(GtkCellRenderer* cell, GtkSnapshot* snapshot,
+ GtkWidget* widget, const GdkRectangle* background_area,
+ const GdkRectangle* cell_area,
+ GtkCellRendererState flags)
+{
+ graphene_rect_t rect = GRAPHENE_RECT_INIT(
+ static_cast<float>(cell_area->x), static_cast<float>(cell_area->y),
+ static_cast<float>(cell_area->width), static_cast<float>(cell_area->height));
+ cairo_t* cr = gtk_snapshot_append_cairo(GTK_SNAPSHOT(snapshot), &rect);
+ surface_cell_renderer_render(cell, cr, widget, background_area, cell_area, flags);
+ cairo_destroy(cr);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/surfacecellrenderer.hxx b/vcl/unx/gtk4/surfacecellrenderer.hxx
new file mode 100644
index 0000000000..d908c04ceb
--- /dev/null
+++ b/vcl/unx/gtk4/surfacecellrenderer.hxx
@@ -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/.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <cairo.h>
+
+G_BEGIN_DECLS
+
+struct _SurfaceCellRenderer
+{
+ GtkCellRenderer parent;
+ cairo_surface_t* surface;
+};
+
+/*
+ Provide a mechanism to support rendering a cairo surface in a GtkComboBox
+*/
+
+G_DECLARE_FINAL_TYPE(SurfaceCellRenderer, surface_cell_renderer, SURFACE, CELL_RENDERER,
+ GtkCellRenderer)
+
+#define SURFACE_TYPE_CELL_RENDERER (surface_cell_renderer_get_type())
+
+#define SURFACE_CELL_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SURFACE_TYPE_CELL_RENDERER, SurfaceCellRenderer))
+
+#define SURFACE_IS_CELL_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SURFACE_TYPE_CELL_RENDERER))
+
+GtkCellRenderer* surface_cell_renderer_new();
+
+G_END_DECLS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/surfacepaintable.cxx b/vcl/unx/gtk4/surfacepaintable.cxx
new file mode 100644
index 0000000000..2e8aa98b26
--- /dev/null
+++ b/vcl/unx/gtk4/surfacepaintable.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 "surfacepaintable.hxx"
+
+struct _SurfacePaintable
+{
+ GObject parent_instance;
+ int width;
+ int height;
+ cairo_surface_t* surface;
+};
+
+namespace
+{
+struct _SurfacePaintableClass : public GObjectClass
+{
+};
+}
+
+static void surface_paintable_snapshot(GdkPaintable* paintable, GdkSnapshot* snapshot, double width,
+ double height)
+{
+ graphene_rect_t rect
+ = GRAPHENE_RECT_INIT(0.0f, 0.0f, static_cast<float>(width), static_cast<float>(height));
+ SurfacePaintable* self = SURFACE_PAINTABLE(paintable);
+ cairo_t* cr = gtk_snapshot_append_cairo(GTK_SNAPSHOT(snapshot), &rect);
+ cairo_set_source_surface(cr, self->surface, 0, 0);
+ cairo_paint(cr);
+ cairo_destroy(cr);
+}
+
+static GdkPaintableFlags surface_paintable_get_flags(GdkPaintable* /*paintable*/)
+{
+ return static_cast<GdkPaintableFlags>(GDK_PAINTABLE_STATIC_SIZE
+ | GDK_PAINTABLE_STATIC_CONTENTS);
+}
+
+static int surface_paintable_get_intrinsic_width(GdkPaintable* paintable)
+{
+ SurfacePaintable* self = SURFACE_PAINTABLE(paintable);
+ return self->width;
+}
+
+static int surface_paintable_get_intrinsic_height(GdkPaintable* paintable)
+{
+ SurfacePaintable* self = SURFACE_PAINTABLE(paintable);
+ return self->height;
+}
+
+static void surface_paintable_init_interface(GdkPaintableInterface* iface)
+{
+ iface->snapshot = surface_paintable_snapshot;
+ iface->get_flags = surface_paintable_get_flags;
+ iface->get_intrinsic_width = surface_paintable_get_intrinsic_width;
+ iface->get_intrinsic_height = surface_paintable_get_intrinsic_height;
+}
+
+G_DEFINE_TYPE_WITH_CODE(SurfacePaintable, surface_paintable, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE(GDK_TYPE_PAINTABLE,
+ surface_paintable_init_interface));
+
+static void surface_paintable_init(SurfacePaintable* self)
+{
+ self->width = 0;
+ self->height = 0;
+ self->surface = nullptr;
+
+ // prevent loplugin:unreffun firing on macro generated function
+ (void)surface_paintable_get_instance_private(self);
+}
+
+static void surface_paintable_dispose(GObject* object)
+{
+ SurfacePaintable* self = SURFACE_PAINTABLE(object);
+ cairo_surface_destroy(self->surface);
+ G_OBJECT_CLASS(surface_paintable_parent_class)->dispose(object);
+}
+
+static void surface_paintable_class_init(SurfacePaintableClass* klass)
+{
+ GObjectClass* object_class = G_OBJECT_CLASS(klass);
+ object_class->dispose = surface_paintable_dispose;
+}
+
+void surface_paintable_set_source(SurfacePaintable* pPaintable, cairo_surface_t* pSource,
+ int nWidth, int nHeight)
+{
+ pPaintable->surface = pSource;
+ pPaintable->width = nWidth;
+ pPaintable->height = nHeight;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/surfacepaintable.hxx b/vcl/unx/gtk4/surfacepaintable.hxx
new file mode 100644
index 0000000000..3e8c79f8ac
--- /dev/null
+++ b/vcl/unx/gtk4/surfacepaintable.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/.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <cairo.h>
+
+G_BEGIN_DECLS
+
+/*
+ Provide a mechanism to allow continuing to use cairo surface where a GdkPaintable
+ is required
+*/
+
+G_DECLARE_FINAL_TYPE(SurfacePaintable, surface_paintable, SURFACE, PAINTABLE, GObject)
+
+/*
+ Set the surface to paint, takes ownership of pSource
+*/
+void surface_paintable_set_source(SurfacePaintable* pPaintable, cairo_surface_t* pSource,
+ int nWidth, int nHeight);
+
+G_END_DECLS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/transferableprovider.cxx b/vcl/unx/gtk4/transferableprovider.cxx
new file mode 100644
index 0000000000..554b80c0d4
--- /dev/null
+++ b/vcl/unx/gtk4/transferableprovider.cxx
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/datatransfer/XTransferable.hpp>
+#include <unx/gtk/gtkinst.hxx>
+#include "transferableprovider.hxx"
+
+#define TRANSFERABLE_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), transerable_content_get_type(), TransferableContent))
+
+struct _TransferableContent
+{
+ GdkContentProvider parent;
+ VclToGtkHelper* m_pConversionHelper;
+ css::datatransfer::XTransferable* m_pContents;
+ Link<void*, void> m_aDetachClipboardLink;
+};
+
+namespace
+{
+struct _TransferableContentClass : public GdkContentProviderClass
+{
+};
+}
+
+G_DEFINE_TYPE(TransferableContent, transerable_content, GDK_TYPE_CONTENT_PROVIDER)
+
+static void transerable_content_write_mime_type_async(GdkContentProvider* provider,
+ const char* mime_type, GOutputStream* stream,
+ int io_priority, GCancellable* cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TransferableContent* self = TRANSFERABLE_CONTENT(provider);
+ if (!self->m_pContents)
+ return;
+ // tdf#129809 take a reference in case m_aContents is replaced during this
+ // call
+ css::uno::Reference<css::datatransfer::XTransferable> xCurrentContents(self->m_pContents);
+ self->m_pConversionHelper->setSelectionData(xCurrentContents, provider, mime_type, stream,
+ io_priority, cancellable, callback, user_data);
+}
+
+static gboolean transerable_content_write_mime_type_finish(GdkContentProvider*,
+ GAsyncResult* result, GError** error)
+{
+ return g_task_propagate_boolean(G_TASK(result), error);
+}
+
+static GdkContentFormats* transerable_content_ref_formats(GdkContentProvider* provider)
+{
+ TransferableContent* self = TRANSFERABLE_CONTENT(provider);
+ css::uno::Reference<css::datatransfer::XTransferable> xCurrentContents(self->m_pContents);
+ if (!xCurrentContents)
+ return nullptr;
+
+ auto aFormats = xCurrentContents->getTransferDataFlavors();
+ std::vector<OString> aGtkTargets(self->m_pConversionHelper->FormatsToGtk(aFormats));
+
+ GdkContentFormatsBuilder* pBuilder = gdk_content_formats_builder_new();
+ for (const auto& rFormat : aGtkTargets)
+ gdk_content_formats_builder_add_mime_type(pBuilder, rFormat.getStr());
+ return gdk_content_formats_builder_free_to_formats(pBuilder);
+}
+
+static void transerable_content_detach_clipboard(GdkContentProvider* provider, GdkClipboard*)
+{
+ TransferableContent* self = TRANSFERABLE_CONTENT(provider);
+ self->m_aDetachClipboardLink.Call(nullptr);
+}
+
+static void transerable_content_class_init(TransferableContentClass* klass)
+{
+ GdkContentProviderClass* provider_class = GDK_CONTENT_PROVIDER_CLASS(klass);
+
+ provider_class->ref_formats = transerable_content_ref_formats;
+ provider_class->detach_clipboard = transerable_content_detach_clipboard;
+ provider_class->write_mime_type_async = transerable_content_write_mime_type_async;
+ provider_class->write_mime_type_finish = transerable_content_write_mime_type_finish;
+}
+
+static void transerable_content_init(TransferableContent* self)
+{
+ self->m_pConversionHelper = nullptr;
+ self->m_pContents = nullptr;
+ // prevent loplugin:unreffun firing on macro generated function
+ (void)transerable_content_get_instance_private(self);
+}
+
+void transerable_content_set_transferable(TransferableContent* pContent,
+ css::datatransfer::XTransferable* pTransferable)
+{
+ pContent->m_pContents = pTransferable;
+}
+
+void transerable_content_set_detach_clipboard_link(TransferableContent* pContent,
+ const Link<void*, void>& rDetachClipboardLink)
+{
+ pContent->m_aDetachClipboardLink = rDetachClipboardLink;
+}
+
+GdkContentProvider* transerable_content_new(VclToGtkHelper* pConversionHelper,
+ css::datatransfer::XTransferable* pTransferable)
+{
+ TransferableContent* content
+ = TRANSFERABLE_CONTENT(g_object_new(transerable_content_get_type(), nullptr));
+ content->m_pConversionHelper = pConversionHelper;
+ content->m_pContents = pTransferable;
+ return GDK_CONTENT_PROVIDER(content);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/transferableprovider.hxx b/vcl/unx/gtk4/transferableprovider.hxx
new file mode 100644
index 0000000000..df4d643d9f
--- /dev/null
+++ b/vcl/unx/gtk4/transferableprovider.hxx
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <tools/link.hxx>
+
+#include <gtk/gtk.h>
+
+struct VclToGtkHelper;
+
+namespace com::sun::star::datatransfer
+{
+class XTransferable;
+}
+
+G_BEGIN_DECLS
+
+/*
+ Provide a mechanism to provide data from a LibreOffice XTransferable via a
+ GdkContentProvider for gtk clipboard or dnd
+*/
+
+G_DECLARE_FINAL_TYPE(TransferableContent, transerable_content, TRANSFERABLE, CONTENT,
+ GdkContentProvider)
+
+GdkContentProvider* transerable_content_new(VclToGtkHelper* pConversionHelper,
+ css::datatransfer::XTransferable* pTransferable);
+
+/*
+ Change to a new XTransferable
+ */
+void transerable_content_set_transferable(TransferableContent* pContent,
+ css::datatransfer::XTransferable* pTransferable);
+
+/*
+ If the GdkContentProvider is used by a clipboard call rDetachClipboardLink on losing
+ ownership of the clipboard
+*/
+void transerable_content_set_detach_clipboard_link(TransferableContent* pContent,
+ const Link<void*, void>& rDetachClipboardLink);
+
+G_END_DECLS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kf5/KFFilePicker.cxx b/vcl/unx/kf5/KFFilePicker.cxx
new file mode 100644
index 0000000000..8d0344b4cb
--- /dev/null
+++ b/vcl/unx/kf5/KFFilePicker.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 "KFFilePicker.hxx"
+#include <KFFilePicker.moc>
+
+#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <osl/mutex.hxx>
+
+#include <QtInstance.hxx>
+
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QGridLayout>
+#include <QtWidgets/QWidget>
+#include <KFileWidget>
+
+using namespace ::com::sun::star;
+using ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION;
+
+namespace
+{
+uno::Sequence<OUString> FilePicker_getSupportedServiceNames()
+{
+ return { "com.sun.star.ui.dialogs.FilePicker", "com.sun.star.ui.dialogs.SystemFilePicker",
+ "com.sun.star.ui.dialogs.KFFilePicker", "com.sun.star.ui.dialogs.KFFolderPicker" };
+}
+}
+
+// KFFilePicker
+
+KFFilePicker::KFFilePicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode eMode)
+ // Native KF5/KF6 filepicker does not add file extension automatically
+ : QtFilePicker(context, eMode, true)
+ , _layout(new QGridLayout(m_pExtraControls))
+{
+ // only columns 0 and 1 are used by controls (s. QtFilePicker::addCustomControl);
+ // set stretch for (unused) column 2 in order for the controls to only take the space
+ // they actually need and avoid empty space in between
+ _layout->setColumnStretch(2, 1);
+
+ // set layout so custom widgets show up in our native file dialog
+ setCustomControlWidgetLayout(_layout.get());
+
+ m_pFileDialog->setSupportedSchemes({
+ QStringLiteral("file"), QStringLiteral("http"), QStringLiteral("https"),
+ QStringLiteral("webdav"), QStringLiteral("webdavs"), QStringLiteral("smb"),
+ QStringLiteral(""), // this makes removable devices shown
+ });
+
+ // used to set the custom controls
+ qApp->installEventFilter(this);
+}
+
+// XFilePickerControlAccess
+void SAL_CALL KFFilePicker::setValue(sal_Int16 controlId, sal_Int16 nControlAction,
+ const uno::Any& value)
+{
+ if (CHECKBOX_AUTOEXTENSION == controlId)
+ // We ignore this one and rely on QFileDialog to provide the functionality
+ return;
+
+ QtFilePicker::setValue(controlId, nControlAction, value);
+}
+
+uno::Any SAL_CALL KFFilePicker::getValue(sal_Int16 controlId, sal_Int16 nControlAction)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ uno::Any ret;
+ pSalInst->RunInMainThread([&ret, this, controlId, nControlAction]() {
+ ret = getValue(controlId, nControlAction);
+ });
+ return ret;
+ }
+
+ if (CHECKBOX_AUTOEXTENSION == controlId)
+ // We ignore this one and rely on QFileDialog to provide the function.
+ // Always return false, to pretend we do not support this, otherwise
+ // LO core would try to be smart and cut the extension in some places,
+ // interfering with QFileDialog's handling of it. QFileDialog also
+ // saves the value of the setting, so LO core is not needed for that either.
+ return uno::Any(false);
+
+ return QtFilePicker::getValue(controlId, nControlAction);
+}
+
+void SAL_CALL KFFilePicker::enableControl(sal_Int16 controlId, sal_Bool enable)
+{
+ if (CHECKBOX_AUTOEXTENSION == controlId)
+ // We ignore this one and rely on QFileDialog to provide the functionality
+ return;
+
+ QtFilePicker::enableControl(controlId, enable);
+}
+
+void SAL_CALL KFFilePicker::setLabel(sal_Int16 controlId, const OUString& label)
+{
+ if (CHECKBOX_AUTOEXTENSION == controlId)
+ // We ignore this one and rely on QFileDialog to provide the functionality
+ return;
+
+ QtFilePicker::setLabel(controlId, label);
+}
+
+OUString SAL_CALL KFFilePicker::getLabel(sal_Int16 controlId)
+{
+ // We ignore this one and rely on QFileDialog to provide the functionality
+ if (CHECKBOX_AUTOEXTENSION == controlId)
+ return "";
+
+ return QtFilePicker::getLabel(controlId);
+}
+
+void KFFilePicker::addCustomControl(sal_Int16 controlId)
+{
+ // native KF5/KF6 filepicker has its own autoextension checkbox,
+ // therefore avoid adding yet another one
+ if (controlId == CHECKBOX_AUTOEXTENSION)
+ return;
+
+ QtFilePicker::addCustomControl(controlId);
+}
+
+// XServiceInfo
+OUString SAL_CALL KFFilePicker::getImplementationName()
+{
+ return "com.sun.star.ui.dialogs.KFFilePicker";
+}
+
+sal_Bool SAL_CALL KFFilePicker::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+uno::Sequence<OUString> SAL_CALL KFFilePicker::getSupportedServiceNames()
+{
+ return FilePicker_getSupportedServiceNames();
+}
+
+bool KFFilePicker::eventFilter(QObject* o, QEvent* e)
+{
+ if (e->type() == QEvent::Show && o->isWidgetType())
+ {
+ auto* w = static_cast<QWidget*>(o);
+ if (!w->parentWidget() && w->isModal())
+ {
+ if (auto* fileWidget = w->findChild<KFileWidget*>({}, Qt::FindDirectChildrenOnly))
+ {
+ fileWidget->setCustomWidget(m_pExtraControls);
+ // remove event filter again; the only purpose was to set the custom widget here
+ qApp->removeEventFilter(this);
+ }
+ }
+ }
+ return QObject::eventFilter(o, e);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kf5/KFFilePicker.hxx b/vcl/unx/kf5/KFFilePicker.hxx
new file mode 100644
index 0000000000..cbbf50792c
--- /dev/null
+++ b/vcl/unx/kf5/KFFilePicker.hxx
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <QtFilePicker.hxx>
+#include <memory>
+
+class QGridLayout;
+
+class KFFilePicker final : public QtFilePicker
+{
+ Q_OBJECT
+
+private:
+ //layout for extra custom controls
+ std::unique_ptr<QGridLayout> _layout;
+
+public:
+ explicit KFFilePicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode);
+
+ // XFilePickerControlAccess functions
+ virtual void SAL_CALL setValue(sal_Int16 nControlId, sal_Int16 nControlAction,
+ const css::uno::Any& rValue) override;
+ virtual css::uno::Any SAL_CALL getValue(sal_Int16 nControlId,
+ sal_Int16 nControlAction) override;
+ virtual void SAL_CALL enableControl(sal_Int16 nControlId, sal_Bool bEnable) override;
+ virtual void SAL_CALL setLabel(sal_Int16 nControlId, const OUString& rLabel) override;
+ virtual OUString SAL_CALL getLabel(sal_Int16 nControlId) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override;
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+private:
+ //add a custom control widget to the file dialog
+ void addCustomControl(sal_Int16 controlId) override;
+ bool eventFilter(QObject* watched, QEvent* event) override;
+
+private Q_SLOTS:
+ // the KF5/KF6 file picker has its own automatic extension handling
+ void updateAutomaticFileExtension() override {}
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kf5/KFSalInstance.cxx b/vcl/unx/kf5/KFSalInstance.cxx
new file mode 100644
index 0000000000..cc280e9d99
--- /dev/null
+++ b/vcl/unx/kf5/KFSalInstance.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <utility>
+
+#include <KConfigCore/KConfigGroup>
+#include <KConfigCore/KSharedConfig>
+#include <QtWidgets/QApplication>
+
+#include <sal/log.hxx>
+
+#include <QtData.hxx>
+
+#include "KFFilePicker.hxx"
+#include "KFSalInstance.hxx"
+
+using namespace com::sun::star;
+
+KFSalInstance::KFSalInstance(std::unique_ptr<QApplication>& pQApp)
+ : QtInstance(pQApp)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ const OUString sToolkit = u"kf" + OUString::number(QT_VERSION_MAJOR);
+ pSVData->maAppData.mxToolkitName = constructToolkitID(sToolkit);
+}
+
+bool KFSalInstance::GetUseReducedAnimation()
+{
+ // since Qt doesn not have a standard way to disable animations for the toolkit
+ // use the animation speed setting when on KDE Plasma, in accordance with how kde-gtk-config
+ // sets the Gtk setting based on that:
+ // https://invent.kde.org/plasma/kde-gtk-config/-/blob/881ae01ad361a03396f7f327365f225ef87688e8/kded/configvalueprovider.cpp#L239
+ // (ideally, this should probably be done in the desktop backend rather than directly
+ // in the VCL plugin)
+ const OUString sDesktop = Application::GetDesktopEnvironment();
+ if (sDesktop == "PLASMA5" || sDesktop == "PLASMA6")
+ {
+ KSharedConfigPtr pSharedConfig = KSharedConfig::openConfig();
+ KConfigGroup aGeneralConfig = pSharedConfig->group(QStringLiteral("KDE"));
+ const qreal fAnimationSpeedModifier
+ = qMax(0.0, aGeneralConfig.readEntry("AnimationDurationFactor", 1.0));
+ return qFuzzyIsNull(fAnimationSpeedModifier);
+ }
+
+ return QtInstance::GetUseReducedAnimation();
+}
+
+bool KFSalInstance::hasNativeFileSelection() const
+{
+ const OUString sDesktop = Application::GetDesktopEnvironment();
+ if (sDesktop == "PLASMA5" || sDesktop == "PLASMA6")
+ return true;
+ return QtInstance::hasNativeFileSelection();
+}
+
+rtl::Reference<QtFilePicker>
+KFSalInstance::createPicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode eMode)
+{
+ if (!IsMainThread())
+ {
+ SolarMutexGuard g;
+ rtl::Reference<QtFilePicker> pPicker;
+ RunInMainThread([&, this]() { pPicker = createPicker(context, eMode); });
+ assert(pPicker);
+ return pPicker;
+ }
+
+ // In order to insert custom controls, KFFilePicker currently relies on KFileWidget
+ // being used in the native file picker, which is only the case for KDE Plasma.
+ // Therefore, return the plain qt5/qt6 one in order to not lose custom controls otherwise.
+ const OUString sDesktop = Application::GetDesktopEnvironment();
+ if (sDesktop == "PLASMA5" || sDesktop == "PLASMA6")
+ return new KFFilePicker(context, eMode);
+ return QtInstance::createPicker(context, eMode);
+}
+
+extern "C" {
+VCLPLUG_KF_PUBLIC SalInstance* create_SalInstance()
+{
+ std::unique_ptr<char* []> pFakeArgv;
+ std::unique_ptr<int> pFakeArgc;
+ std::vector<FreeableCStr> aFakeArgvFreeable;
+ QtInstance::AllocFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable);
+
+ std::unique_ptr<QApplication> pQApp
+ = QtInstance::CreateQApplication(*pFakeArgc, pFakeArgv.get());
+
+ KFSalInstance* pInstance = new KFSalInstance(pQApp);
+ pInstance->MoveFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable);
+
+ new QtData();
+
+ return pInstance;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kf5/KFSalInstance.hxx b/vcl/unx/kf5/KFSalInstance.hxx
new file mode 100644
index 0000000000..86795a1ba9
--- /dev/null
+++ b/vcl/unx/kf5/KFSalInstance.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <QtInstance.hxx>
+
+class KFSalInstance final : public QtInstance
+{
+ bool hasNativeFileSelection() const override;
+ rtl::Reference<QtFilePicker>
+ createPicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode) override;
+
+ virtual bool GetUseReducedAnimation() override;
+
+public:
+ explicit KFSalInstance(std::unique_ptr<QApplication>& pQApp);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kf6/KFFilePicker.cxx b/vcl/unx/kf6/KFFilePicker.cxx
new file mode 100644
index 0000000000..17672c8345
--- /dev/null
+++ b/vcl/unx/kf6/KFFilePicker.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../kf5/KFFilePicker.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kf6/KFFilePicker.hxx b/vcl/unx/kf6/KFFilePicker.hxx
new file mode 100644
index 0000000000..b0c790552d
--- /dev/null
+++ b/vcl/unx/kf6/KFFilePicker.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../kf5/KFFilePicker.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kf6/KFSalInstance.cxx b/vcl/unx/kf6/KFSalInstance.cxx
new file mode 100644
index 0000000000..7595c93bd2
--- /dev/null
+++ b/vcl/unx/kf6/KFSalInstance.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../kf5/KFSalInstance.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kf6/KFSalInstance.hxx b/vcl/unx/kf6/KFSalInstance.hxx
new file mode 100644
index 0000000000..9c648d9336
--- /dev/null
+++ b/vcl/unx/kf6/KFSalInstance.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../kf5/KFSalInstance.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/x11/x11sys.cxx b/vcl/unx/x11/x11sys.cxx
new file mode 100644
index 0000000000..8f9478eb3b
--- /dev/null
+++ b/vcl/unx/x11/x11sys.cxx
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unx/salunx.h>
+#include <unx/salinst.h>
+#include <unx/saldisp.hxx>
+#include <unx/x11/x11sys.hxx>
+
+#include <vcl/weld.hxx>
+
+#include <svdata.hxx>
+
+#include <rtl/ustrbuf.hxx>
+#include <osl/thread.h>
+
+SalSystem* X11SalInstance::CreateSalSystem()
+{
+ return new X11SalSystem();
+}
+
+X11SalSystem::~X11SalSystem()
+{
+}
+
+// for the moment only handle xinerama case
+unsigned int X11SalSystem::GetDisplayScreenCount()
+{
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ return pSalDisp->IsXinerama() ? pSalDisp->GetXineramaScreens().size() :
+ pSalDisp->GetXScreenCount();
+}
+
+unsigned int X11SalSystem::GetDisplayBuiltInScreen()
+{
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ return pSalDisp->GetDefaultXScreen().getXScreen();
+}
+
+AbsoluteScreenPixelRectangle X11SalSystem::GetDisplayScreenPosSizePixel( unsigned int nScreen )
+{
+ AbsoluteScreenPixelRectangle aRet;
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ if( pSalDisp->IsXinerama() )
+ {
+ const std::vector< AbsoluteScreenPixelRectangle >& rScreens = pSalDisp->GetXineramaScreens();
+
+ // we shouldn't be able to pick a screen > number of screens available
+ assert(nScreen < rScreens.size() );
+
+ if( nScreen < rScreens.size() )
+ aRet = rScreens[nScreen];
+ }
+ else
+ {
+ const SalDisplay::ScreenData& rScreen =
+ pSalDisp->getDataForScreen( SalX11Screen( nScreen ) );
+ aRet = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( 0, 0 ), rScreen.m_aSize );
+ }
+
+ return aRet;
+}
+
+int X11SalSystem::ShowNativeDialog( const OUString& rTitle, const OUString& rMessage, const std::vector< OUString >& rButtons )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->mpIntroWindow )
+ pSVData->mpIntroWindow->Hide();
+
+ std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Warning, VclButtonsType::NONE,
+ rMessage));
+ xWarn->set_title(rTitle);
+
+ sal_uInt16 nButton = 0;
+ for (auto const& button : rButtons)
+ xWarn->add_button(button, nButton++);
+ xWarn->set_default_response(0);
+
+ return xWarn->run();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/x11/xlimits.cxx b/vcl/unx/x11/xlimits.cxx
new file mode 100644
index 0000000000..b8509cbd24
--- /dev/null
+++ b/vcl/unx/x11/xlimits.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/.
+ */
+
+#include <sal/log.hxx>
+#include <unx/x11/xlimits.hxx>
+
+Pixmap limitXCreatePixmap(Display *display, Drawable d, unsigned int width, unsigned int height, unsigned int depth)
+{
+ // The X protocol request CreatePixmap puts an upper bound
+ // of 16 bit to the size. And in practice some drivers
+ // fall over with values close to the max.
+
+ // see, e.g. moz#424333, fdo#48961, rhbz#1086714
+ // we've a duplicate of this in canvas :-(
+ if (width > SAL_MAX_INT16-10 || height > SAL_MAX_INT16-10)
+ {
+ SAL_WARN("vcl", "overlarge pixmap: " << width << " x " << height);
+ return None;
+ }
+ return XCreatePixmap(display, d, width, height, depth);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/vcl.common.component b/vcl/vcl.common.component
new file mode 100644
index 0000000000..3069a1c79d
--- /dev/null
+++ b/vcl/vcl.common.component
@@ -0,0 +1,89 @@
+<?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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+-->
+
+<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@"
+ prefix="vcl" xmlns="http://openoffice.org/2010/uno-components">
+ <implementation name="com.sun.star.comp.datatransfer.dnd.OleDragSource_V1">
+ <service name="com.sun.star.datatransfer.dnd.OleDragSource"/>
+ <optional/>
+ </implementation>
+ <implementation name="com.sun.star.comp.datatransfer.dnd.OleDropTarget_V1">
+ <service name="com.sun.star.datatransfer.dnd.OleDropTarget"/>
+ <optional/>
+ </implementation>
+ <implementation name="com.sun.star.comp.graphic.GraphicMapper"
+ constructor="com_sun_star_comp_graphic_GraphicMapper_get_implementation">
+ <service name="com.sun.star.graphic.GraphicMapper"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.graphic.GraphicProvider"
+ constructor="com_sun_star_comp_graphic_GraphicProvider_get_implementation">
+ <service name="com.sun.star.graphic.GraphicProvider"/>
+ </implementation>
+ <implementation name="com.sun.star.datatransfer.clipboard.AquaClipboard"
+ constructor="vcl_SystemClipboard_get_implementation">
+ <service name="com.sun.star.datatransfer.clipboard.SystemClipboard"/>
+ <optional/>
+ </implementation>
+ <implementation name="com.sun.star.datatransfer.clipboard.iOSClipboard"
+ constructor="vcl_SystemClipboard_get_implementation">
+ <service name="com.sun.star.datatransfer.clipboard.SystemClipboard"/>
+ <optional/>
+ </implementation>
+ <implementation name="com.sun.star.datatransfer.dnd.XdndDropTarget">
+ <service name="com.sun.star.datatransfer.dnd.X11DropTarget"/>
+ <optional/>
+ </implementation>
+ <implementation name="com.sun.star.datatransfer.dnd.XdndSupport">
+ <service name="com.sun.star.datatransfer.dnd.X11DragSource"/>
+ <optional/>
+ </implementation>
+ <implementation name="com.sun.star.datatransfer.MimeCntTypeFactory"
+ constructor="dtrans_CMimeContentTypeFactory_get_implementation">
+ <service name="com.sun.star.datatransfer.MimeContentTypeFactory"/>
+ </implementation>
+ <implementation name="com.sun.star.datatransfer.VCLGenericClipboard"
+ constructor="vcl_SystemClipboard_get_implementation">
+ <service name="com.sun.star.datatransfer.clipboard.SystemClipboard"/>
+ <optional/>
+ </implementation>
+ <implementation name="com.sun.star.datatransfer.X11ClipboardSupport"
+ constructor="vcl_SystemClipboard_get_implementation">
+ <service name="com.sun.star.datatransfer.clipboard.SystemClipboard"/>
+ <optional/>
+ </implementation>
+ <implementation name="com.sun.star.frame.VCLSessionManagerClient"
+ constructor="com_sun_star_frame_VCLSessionManagerClient_get_implementation">
+ <service name="com.sun.star.frame.SessionManagerClient"/>
+ <optional/>
+ </implementation>
+ <implementation name="com.sun.star.graphic.GraphicObject"
+ constructor="com_sun_star_graphic_GraphicObject_get_implementation">
+ <service name="com.sun.star.graphic.GraphicObject"/>
+ </implementation>
+ <implementation name="org.libreoffice.uitest.UITest"
+ constructor="UITest_get_implementation">
+ <service name="com.sun.star.ui.test.UITest"/>
+ <optional/>
+ </implementation>
+ <implementation name="vcl::FontIdentificator"
+ constructor="vcl_FontIdentificator_get_implementation">
+ <service name="com.sun.star.awt.FontIdentificator"/>
+ <optional/>
+ </implementation>
+</component>
diff --git a/vcl/vcl.common.component.android b/vcl/vcl.common.component.android
new file mode 100644
index 0000000000..6f03bb1050
--- /dev/null
+++ b/vcl/vcl.common.component.android
@@ -0,0 +1,9 @@
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+com.sun.star.datatransfer.VCLGenericClipboard
+com.sun.star.frame.VCLSessionManagerClient
+vcl::FontIdentificator
diff --git a/vcl/vcl.common.component.headless b/vcl/vcl.common.component.headless
new file mode 100644
index 0000000000..4300832e94
--- /dev/null
+++ b/vcl/vcl.common.component.headless
@@ -0,0 +1,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/.
+
+com.sun.star.frame.VCLSessionManagerClient
+vcl::FontIdentificator
diff --git a/vcl/vcl.common.component.ios b/vcl/vcl.common.component.ios
new file mode 100644
index 0000000000..a9c21bd5b8
--- /dev/null
+++ b/vcl/vcl.common.component.ios
@@ -0,0 +1,9 @@
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+com.sun.star.datatransfer.clipboard.iOSClipboard
+com.sun.star.frame.VCLSessionManagerClient
+vcl::FontIdentificator
diff --git a/vcl/vcl.common.component.macosx b/vcl/vcl.common.component.macosx
new file mode 100644
index 0000000000..bc990c02f0
--- /dev/null
+++ b/vcl/vcl.common.component.macosx
@@ -0,0 +1,12 @@
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+com.sun.star.comp.datatransfer.dnd.OleDragSource_V1
+com.sun.star.comp.datatransfer.dnd.OleDropTarget_V1
+com.sun.star.datatransfer.clipboard.AquaClipboard
+com.sun.star.frame.VCLSessionManagerClient
+org.libreoffice.uitest.UITest
+vcl::FontIdentificator
diff --git a/vcl/vcl.common.component.unx b/vcl/vcl.common.component.unx
new file mode 100644
index 0000000000..bd2e44569a
--- /dev/null
+++ b/vcl/vcl.common.component.unx
@@ -0,0 +1,12 @@
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+com.sun.star.datatransfer.dnd.XdndDropTarget
+com.sun.star.datatransfer.dnd.XdndSupport
+com.sun.star.datatransfer.X11ClipboardSupport
+com.sun.star.frame.VCLSessionManagerClient
+org.libreoffice.uitest.UITest
+vcl::FontIdentificator
diff --git a/vcl/vcl.common.component.windows b/vcl/vcl.common.component.windows
new file mode 100644
index 0000000000..5df902ffc6
--- /dev/null
+++ b/vcl/vcl.common.component.windows
@@ -0,0 +1,9 @@
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+com.sun.star.frame.VCLSessionManagerClient
+org.libreoffice.uitest.UITest
+vcl::FontIdentificator
diff --git a/vcl/vclplug_win.component b/vcl/vclplug_win.component
new file mode 100644
index 0000000000..0187827d54
--- /dev/null
+++ b/vcl/vclplug_win.component
@@ -0,0 +1,46 @@
+<?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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+-->
+
+<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@"
+ xmlns="http://openoffice.org/2010/uno-components">
+ <implementation name="com.sun.star.datatransfer.DataFormatTranslator"
+ constructor="dtrans_CDataFormatTranslatorUNO_get_implementation" single-instance="true">
+ <service name="com.sun.star.datatransfer.DataFormatTranslator"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.datatransfer.dnd.OleDragSource_V1"
+ constructor="dtrans_DragSource_get_implementation">
+ <service name="com.sun.star.datatransfer.dnd.OleDragSource"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.datatransfer.dnd.OleDropTarget_V1"
+ constructor="dtrans_DropTarget_get_implementation">
+ <service name="com.sun.star.datatransfer.dnd.OleDropTarget"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.datatransfer.ClipboardManager"
+ constructor="dtrans_ClipboardManager_get_implementation">
+ <service name="com.sun.star.datatransfer.clipboard.ClipboardManager"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.datatransfer.clipboard.GenericClipboard"
+ constructor="dtrans_GenericClipboard_get_implementation">
+ <service name="com.sun.star.datatransfer.clipboard.GenericClipboard"/>
+ </implementation>
+ <implementation name="com.sun.star.datatransfer.clipboard.ClipboardW32"
+ constructor="dtrans_CWinClipboard_get_implementation" single-instance="true">
+ <service name="com.sun.star.datatransfer.clipboard.SystemClipboard"/>
+ </implementation>
+</component>
diff --git a/vcl/win/app/fileregistration.cxx b/vcl/win/app/fileregistration.cxx
new file mode 100644
index 0000000000..ec7ccbcee2
--- /dev/null
+++ b/vcl/win/app/fileregistration.cxx
@@ -0,0 +1,182 @@
+/* -*- 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/.
+ */
+
+#if !defined(NTDDI_VERSION) || NTDDI_VERSION < NTDDI_WIN8
+#define NTDDI_VERSION NTDDI_WIN8 // needed for IApplicationActivationManager
+#endif
+
+#include <sal/config.h>
+
+#include <comphelper/scopeguard.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <unotools/resmgr.hxx>
+#include <vcl/abstdlg.hxx>
+#include <vcl/fileregistration.hxx>
+
+#include <strings.hrc>
+#include <svdata.hxx>
+
+#include <utility>
+
+#include <prewin.h>
+#include <Shobjidl.h>
+#include <systools/win32/comtools.hxx>
+#include <versionhelpers.h>
+#include <postwin.h>
+
+#define MAX_LONG_PATH 32767
+
+namespace vcl::fileregistration
+{
+static void LaunchModernSettingsDialogDefaultApps()
+{
+ sal::systools::COMReference<IApplicationActivationManager> pIf(
+ CLSID_ApplicationActivationManager, nullptr, CLSCTX_INPROC_SERVER);
+
+ DWORD pid;
+ HRESULT hr = pIf->ActivateApplication(L"windows.immersivecontrolpanel_cw5n1h2txyewy"
+ L"!microsoft.windows.immersivecontrolpanel",
+ L"page=SettingsPageAppsDefaults", AO_NONE, &pid);
+ if (SUCCEEDED(hr))
+ {
+ // Do not check error because we could at least open
+ // the "Default apps" setting.
+ pIf->ActivateApplication(L"windows.immersivecontrolpanel_cw5n1h2txyewy"
+ L"!microsoft.windows.immersivecontrolpanel",
+ L"page=SettingsPageAppsDefaults"
+ L"&target=SettingsPageAppsDefaultsDefaultAppsListView",
+ AO_NONE, &pid);
+ }
+}
+
+static HRESULT
+IsPathDefaultForClass(sal::systools::COMReference<IApplicationAssociationRegistration>& pAAR,
+ LPCWSTR aClassName, LPCWSTR progID)
+{
+ // Make sure the Prog ID matches what we have
+ sal::systools::CoTaskMemAllocated<wchar_t> registeredApp;
+ HRESULT hr
+ = pAAR->QueryCurrentDefault(aClassName, AT_FILEEXTENSION, AL_EFFECTIVE, &registeredApp);
+ if (SUCCEEDED(hr))
+ {
+ if (wcsnicmp(registeredApp, progID, wcslen(progID)) == 0)
+ hr = S_OK;
+ else
+ hr = S_FALSE;
+ }
+
+ return hr;
+}
+
+static bool IsDefaultAppInstalledInReg()
+{
+ const wchar_t* keyPath = L"SOFTWARE\\LibreOffice\\UNO\\InstallPath";
+
+ WCHAR szRegPath[MAX_LONG_PATH];
+ DWORD cbData = static_cast<DWORD>(MAX_LONG_PATH * sizeof(WCHAR));
+ auto rc = RegGetValueW(HKEY_LOCAL_MACHINE, keyPath, nullptr, RRF_RT_REG_SZ, nullptr,
+ static_cast<PVOID>(szRegPath), &cbData);
+ if (rc != ERROR_SUCCESS)
+ return false;
+
+ WCHAR szProcPath[MAX_LONG_PATH];
+ if (!GetModuleFileNameW(nullptr, szProcPath, MAX_LONG_PATH))
+ return false;
+
+ WCHAR szFullProcPath[MAX_LONG_PATH];
+ if (!GetFullPathNameW(szProcPath, MAX_LONG_PATH, szFullProcPath, nullptr))
+ return false;
+
+ if (!GetLongPathNameW(szFullProcPath, szFullProcPath, MAX_LONG_PATH))
+ return false;
+
+ if (!GetLongPathNameW(szRegPath, szRegPath, MAX_LONG_PATH))
+ return false;
+
+ if (wcslen(szRegPath) > 0 && wcsstr(szFullProcPath, szRegPath) != nullptr)
+ return true;
+
+ return false;
+}
+
+void LaunchRegistrationUI()
+{
+ try
+ {
+ sal::systools::CoInitializeGuard aGuard(COINIT_APARTMENTTHREADED);
+ if (IsWindows10OrGreater())
+ {
+ LaunchModernSettingsDialogDefaultApps();
+ }
+ else
+ {
+ sal::systools::COMReference<IApplicationAssociationRegistrationUI> pIf(
+ CLSID_ApplicationAssociationRegistrationUI, nullptr, CLSCTX_INPROC_SERVER);
+
+ // LaunchAdvancedAssociationUI only works for applications registered under
+ // Software\RegisteredApplications. See scp2/source/ooo/registryitem_ooo.scp
+ const OUString expanded = Translate::ExpandVariables("%PRODUCTNAME %PRODUCTVERSION");
+ pIf->LaunchAdvancedAssociationUI(o3tl::toW(expanded.getStr()));
+ }
+ }
+ catch (...)
+ {
+ // Just ignore any error here: this is not something we need to make sure to succeed
+ }
+}
+
+void CheckFileExtRegistration(weld::Window* pDialogParent)
+{
+ if (!officecfg::Office::Common::Misc::PerformFileExtCheck::get())
+ return;
+
+ if (!IsDefaultAppInstalledInReg())
+ return;
+
+ sal::systools::CoInitializeGuard aGuard(COINIT_APARTMENTTHREADED, false,
+ sal::systools::CoInitializeGuard::WhenFailed::NoThrow);
+ sal::systools::COMReference<IApplicationAssociationRegistration> pAAR;
+ try
+ {
+ pAAR.CoCreateInstance(CLSID_ApplicationAssociationRegistration, nullptr,
+ CLSCTX_INPROC_SERVER);
+ }
+ catch (...)
+ {
+ // Just return on any error here: this is not something we need to make sure to succeed
+ return;
+ }
+
+ static const std::pair<LPCWSTR, LPCWSTR> formats[] = {
+ { L".odp", L"LibreOffice.ImpressDocument.1" },
+ { L".odt", L"LibreOffice.WriterDocument.1" },
+ { L".ods", L"LibreOffice.CalcDocument.1" },
+ };
+ OUString aNonDefaults;
+
+ for (const auto & [ szExt, szProgId ] : formats)
+ {
+ if (IsPathDefaultForClass(pAAR, szExt, szProgId) == S_FALSE)
+ aNonDefaults += OUString::Concat(o3tl::toU(szExt)) + "\n";
+ }
+
+ if (!aNonDefaults.isEmpty())
+ {
+ OUString aMsg(VclResId(STR_FILEEXT_NONDEFAULT_ASK_MSG).replaceFirst("$1", aNonDefaults));
+
+ VclAbstractDialogFactory* pFact = VclAbstractDialogFactory::Create();
+ ScopedVclPtr<VclAbstractDialog> pDlg(pFact->CreateFileExtCheckDialog(
+ pDialogParent, VclResId(STR_FILEEXT_NONDEFAULT_ASK_TITLE), aMsg));
+ pDlg->Execute();
+ }
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/win/app/saldata.cxx b/vcl/win/app/saldata.cxx
new file mode 100644
index 0000000000..31fa661636
--- /dev/null
+++ b/vcl/win/app/saldata.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 <svsys.h>
+#include <rtl/tencinfo.h>
+#include <vcl/svapp.hxx>
+
+#include <win/saldata.hxx>
+
+rtl_TextEncoding ImplSalGetSystemEncoding()
+{
+ static const UINT nOldAnsiCodePage = 0;
+ static rtl_TextEncoding eEncoding = RTL_TEXTENCODING_MS_1252;
+
+ UINT nAnsiCodePage = GetACP();
+ if ( nAnsiCodePage != nOldAnsiCodePage )
+ {
+ rtl_TextEncoding nEnc
+ = rtl_getTextEncodingFromWindowsCodePage(nAnsiCodePage);
+ if (nEnc != RTL_TEXTENCODING_DONTKNOW)
+ eEncoding = nEnc;
+ }
+
+ return eEncoding;
+}
+
+OUString ImplSalGetUniString(const char* pStr, sal_Int32 const nLen)
+{
+ return OUString( pStr, (-1 == nLen) ? strlen(pStr) : nLen,
+ ImplSalGetSystemEncoding(),
+ RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_DEFAULT |
+ RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_DEFAULT |
+ RTL_TEXTTOUNICODE_FLAGS_INVALID_DEFAULT );
+}
+
+int ImplSalWICompareAscii( const wchar_t* pStr1, const char* pStr2 )
+{
+ int nRet;
+ char c2;
+ do
+ {
+ // change to LowerCase if the char is between 'A' and 'Z'
+ wchar_t c1 = *pStr1;
+ c2 = *pStr2;
+ if ( (c1 >= 65) && (c1 <= 90) )
+ c1 += 32;
+ if ( (c2 >= 65) && (c2 <= 90) )
+ c2 += 32;
+ nRet = static_cast<sal_Int32>(c1)- static_cast<sal_Int32>(static_cast<unsigned char>(c2));
+ if ( nRet != 0 )
+ break;
+
+ pStr1++;
+ pStr2++;
+ }
+ while ( c2 );
+
+ return nRet;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/app/salinfo.cxx b/vcl/win/app/salinfo.cxx
new file mode 100644
index 0000000000..23d542d23b
--- /dev/null
+++ b/vcl/win/app/salinfo.cxx
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svsys.h>
+#include <o3tl/char16_t2wchar_t.hxx>
+
+#include <sal/log.hxx>
+#include <vcl/window.hxx>
+
+#include <win/salsys.h>
+#include <win/salframe.h>
+#include <win/salinst.h>
+#include <win/saldata.hxx>
+
+#include <svdata.hxx>
+
+#include <unordered_map>
+
+SalSystem* WinSalInstance::CreateSalSystem()
+{
+ return new WinSalSystem();
+}
+
+WinSalSystem::~WinSalSystem()
+{
+}
+
+static BOOL CALLBACK ImplEnumMonitorProc( HMONITOR hMonitor,
+ HDC hDC,
+ LPRECT lpRect,
+ LPARAM dwData )
+{
+ WinSalSystem* pSys = reinterpret_cast<WinSalSystem*>(dwData);
+ return pSys->handleMonitorCallback( reinterpret_cast<sal_IntPtr>(hMonitor),
+ reinterpret_cast<sal_IntPtr>(hDC),
+ reinterpret_cast<sal_IntPtr>(lpRect) );
+}
+
+bool WinSalSystem::handleMonitorCallback( sal_IntPtr hMonitor, sal_IntPtr, sal_IntPtr )
+{
+ MONITORINFOEXW aInfo;
+ aInfo.cbSize = sizeof( aInfo );
+ if( GetMonitorInfoW( reinterpret_cast<HMONITOR>(hMonitor), &aInfo ) )
+ {
+ aInfo.szDevice[CCHDEVICENAME-1] = 0;
+ OUString aDeviceName( o3tl::toU(aInfo.szDevice) );
+ std::map< OUString, unsigned int >::const_iterator it =
+ m_aDeviceNameToMonitor.find( aDeviceName );
+ if( it != m_aDeviceNameToMonitor.end() )
+ {
+ DisplayMonitor& rMon( m_aMonitors[ it->second ] );
+ rMon.m_aArea = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( aInfo.rcMonitor.left,
+ aInfo.rcMonitor.top ),
+ AbsoluteScreenPixelSize( aInfo.rcMonitor.right - aInfo.rcMonitor.left,
+ aInfo.rcMonitor.bottom - aInfo.rcMonitor.top ) );
+ if( (aInfo.dwFlags & MONITORINFOF_PRIMARY) != 0 )
+ m_nPrimary = it->second;
+ }
+ }
+ return true;
+}
+
+void WinSalSystem::clearMonitors()
+{
+ m_aMonitors.clear();
+ m_nPrimary = 0;
+}
+
+bool WinSalSystem::initMonitors()
+{
+ if( m_aMonitors.size() > 0 )
+ return true;
+
+ int nMonitors = GetSystemMetrics( SM_CMONITORS );
+ if( nMonitors == 1 )
+ {
+ int w = GetSystemMetrics( SM_CXSCREEN );
+ int h = GetSystemMetrics( SM_CYSCREEN );
+ AbsoluteScreenPixelRectangle aRect(AbsoluteScreenPixelPoint(), AbsoluteScreenPixelSize( w, h ));
+ m_aMonitors.push_back( DisplayMonitor( OUString(), aRect ) );
+ m_aDeviceNameToMonitor[ OUString() ] = 0;
+ m_nPrimary = 0;
+ }
+ else
+ {
+ DISPLAY_DEVICEW aDev;
+ aDev.cb = sizeof( aDev );
+ DWORD nDevice = 0;
+ std::unordered_map< OUString, int > aDeviceStringCount;
+ while( EnumDisplayDevicesW( nullptr, nDevice++, &aDev, 0 ) )
+ {
+ if( (aDev.StateFlags & DISPLAY_DEVICE_ACTIVE)
+ && !(aDev.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) ) // sort out non/disabled monitors
+ {
+ aDev.DeviceName[31] = 0;
+ aDev.DeviceString[127] = 0;
+ OUString aDeviceName( o3tl::toU(aDev.DeviceName) );
+ OUString aDeviceString( o3tl::toU(aDev.DeviceString) );
+ aDeviceStringCount[ aDeviceString ]++;
+ m_aDeviceNameToMonitor[ aDeviceName ] = m_aMonitors.size();
+ m_aMonitors.push_back( DisplayMonitor( aDeviceString,
+ AbsoluteScreenPixelRectangle() ) );
+ }
+ }
+ HDC aDesktopRC = GetDC( nullptr );
+ EnumDisplayMonitors( aDesktopRC, nullptr, ImplEnumMonitorProc, reinterpret_cast<LPARAM>(this) );
+
+ // append monitor numbers to name strings
+ std::unordered_map< OUString, int > aDevCount( aDeviceStringCount );
+ unsigned int nMonitorCount = m_aMonitors.size();
+ for( unsigned int i = 0; i < nMonitorCount; i++ )
+ {
+ const OUString& rDev( m_aMonitors[i].m_aName );
+ if( aDeviceStringCount[ rDev ] > 1 )
+ {
+ int nInstance = aDeviceStringCount[ rDev ] - (-- aDevCount[ rDev ] );
+ m_aMonitors[ i ].m_aName = rDev + " (" + OUString::number( nInstance ) + ")";
+ }
+ }
+ ReleaseDC(nullptr, aDesktopRC);
+ }
+
+ return m_aMonitors.size() > 0;
+}
+
+unsigned int WinSalSystem::GetDisplayScreenCount()
+{
+ initMonitors();
+ return m_aMonitors.size();
+}
+
+unsigned int WinSalSystem::GetDisplayBuiltInScreen()
+{
+ initMonitors();
+ return m_nPrimary;
+}
+
+AbsoluteScreenPixelRectangle WinSalSystem::GetDisplayScreenPosSizePixel( unsigned int nScreen )
+{
+ initMonitors();
+ if (nScreen >= m_aMonitors.size())
+ {
+ SAL_WARN("vcl", "Requested screen size/pos for screen #"
+ << nScreen << ", but only " << m_aMonitors.size() << " screens found.");
+ assert(false);
+ return AbsoluteScreenPixelRectangle();
+ }
+ return m_aMonitors[nScreen].m_aArea;
+}
+
+int WinSalSystem::ShowNativeMessageBox(const OUString& rTitle, const OUString& rMessage)
+{
+ ImplHideSplash();
+ return MessageBoxW(
+ nullptr,
+ o3tl::toW(rMessage.getStr()),
+ o3tl::toW(rTitle.getStr()),
+ MB_TASKMODAL | MB_SETFOREGROUND | MB_ICONWARNING | MB_DEFBUTTON1);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/app/salinst.cxx b/vcl/win/app/salinst.cxx
new file mode 100644
index 0000000000..eb4adb853e
--- /dev/null
+++ b/vcl/win/app/salinst.cxx
@@ -0,0 +1,1008 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <svsys.h>
+#include <process.h>
+
+#include <osl/conditn.hxx>
+#include <osl/file.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+#include <tools/time.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/solarmutex.hxx>
+#include <comphelper/windowserrorstring.hxx>
+#include <com/sun/star/uno/Reference.h>
+#include <o3tl/char16_t2wchar_t.hxx>
+
+#include <dndhelper.hxx>
+#include <vcl/inputtypes.hxx>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/timer.hxx>
+#include <vclpluginapi.h>
+
+#include <win/dnd_source.hxx>
+#include <win/dnd_target.hxx>
+#include <win/wincomp.hxx>
+#include <win/salids.hrc>
+#include <win/saldata.hxx>
+#include <win/salinst.h>
+#include <win/salframe.h>
+#include <win/salobj.h>
+#include <win/saltimer.h>
+#include <win/salbmp.h>
+#include <win/winlayout.hxx>
+
+#include <config_features.h>
+#include <vcl/skia/SkiaHelper.hxx>
+#if HAVE_FEATURE_SKIA
+#include <config_skia.h>
+#include <skia/salbmp.hxx>
+#include <skia/win/gdiimpl.hxx>
+#endif
+
+#include <salsys.hxx>
+
+#include <desktop/crashreport.hxx>
+
+#include <prewin.h>
+
+#include <gdiplus.h>
+#include <shlobj.h>
+
+#include <postwin.h>
+
+static LRESULT CALLBACK SalComWndProcW( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam );
+
+class SalYieldMutex : public comphelper::SolarMutex
+{
+public: // for ImplSalYield() and ImplSalYieldMutexAcquireWithWait()
+ osl::Condition m_condition; /// for MsgWaitForMultipleObjects()
+
+protected:
+ virtual void doAcquire( sal_uInt32 nLockCount ) override;
+ virtual sal_uInt32 doRelease( bool bUnlockAll ) override;
+
+ static void BeforeReleaseHandler();
+
+public:
+ explicit SalYieldMutex();
+
+ virtual bool IsCurrentThread() const override;
+ virtual bool tryToAcquire() override;
+};
+
+SalYieldMutex::SalYieldMutex()
+{
+ SetBeforeReleaseHandler( &SalYieldMutex::BeforeReleaseHandler );
+}
+
+void SalYieldMutex::BeforeReleaseHandler()
+{
+ OpenGLContext::prepareForYield();
+
+ if ( GetSalData()->mnAppThreadId != GetCurrentThreadId() )
+ {
+ // If we don't call these message, the Output from the
+ // Java clients doesn't come in the right order
+ GdiFlush();
+ }
+}
+
+/// note: while VCL is fully up and running (other threads started and
+/// before shutdown), the main thread must acquire SolarMutex only via
+/// this function to avoid deadlock
+void SalYieldMutex::doAcquire( sal_uInt32 nLockCount )
+{
+ WinSalInstance* pInst = GetSalData()->mpInstance;
+ if ( pInst && pInst->IsMainThread() )
+ {
+ if ( pInst->m_nNoYieldLock )
+ return;
+ // tdf#96887 If this is the main thread, then we must wait for two things:
+ // - the yield mutex being unlocked
+ // - SendMessage() being triggered
+ // This can nicely be done using MsgWaitForMultipleObjects, which is called in
+ // m_condition.wait(). The 2nd one is
+ // needed because if we don't reschedule, then we create deadlocks if a
+ // Window's create/destroy is called via SendMessage() from another thread.
+ // Have a look at the osl_waitCondition implementation for more info.
+ do {
+ // Calling Condition::reset frequently turns out to be a little expensive,
+ // and the vast majority of the time there is no contention, so first
+ // try just acquiring the mutex.
+ if (m_aMutex.tryToAcquire())
+ break;
+ // reset condition *before* acquiring!
+ m_condition.reset();
+ if (m_aMutex.tryToAcquire())
+ break;
+ // wait for SalYieldMutex::release() to set the condition
+ osl::Condition::Result res = m_condition.wait();
+ assert(osl::Condition::Result::result_ok == res);
+ (void) res;
+ }
+ while ( true );
+ }
+ else
+ m_aMutex.acquire();
+ ++m_nCount;
+ --nLockCount;
+
+ comphelper::SolarMutex::doAcquire( nLockCount );
+}
+
+sal_uInt32 SalYieldMutex::doRelease( const bool bUnlockAll )
+{
+ WinSalInstance* pInst = GetSalData()->mpInstance;
+ if ( pInst && pInst->m_nNoYieldLock && pInst->IsMainThread() )
+ return 1;
+
+ sal_uInt32 nCount = comphelper::SolarMutex::doRelease( bUnlockAll );
+ // wake up ImplSalYieldMutexAcquireWithWait() after release
+ if ( 0 == m_nCount )
+ m_condition.set();
+ return nCount;
+}
+
+bool SalYieldMutex::tryToAcquire()
+{
+ WinSalInstance* pInst = GetSalData()->mpInstance;
+ if ( pInst )
+ {
+ if ( pInst->m_nNoYieldLock && pInst->IsMainThread() )
+ return true;
+ else
+ return comphelper::SolarMutex::tryToAcquire();
+ }
+ else
+ return false;
+}
+
+void ImplSalYieldMutexAcquireWithWait( sal_uInt32 nCount )
+{
+ WinSalInstance* pInst = GetSalData()->mpInstance;
+ if ( pInst )
+ pInst->GetYieldMutex()->acquire( nCount );
+}
+
+bool ImplSalYieldMutexTryToAcquire()
+{
+ WinSalInstance* pInst = GetSalData()->mpInstance;
+ return pInst && pInst->GetYieldMutex()->tryToAcquire();
+}
+
+void ImplSalYieldMutexRelease()
+{
+ WinSalInstance* pInst = GetSalData()->mpInstance;
+ if ( pInst )
+ {
+ GdiFlush();
+ pInst->GetYieldMutex()->release();
+ }
+}
+
+bool SalYieldMutex::IsCurrentThread() const
+{
+ if ( !GetSalData()->mpInstance->m_nNoYieldLock )
+ return SolarMutex::IsCurrentThread();
+ else
+ return GetSalData()->mpInstance->IsMainThread();
+}
+
+void SalData::initKeyCodeMap()
+{
+ UINT nKey;
+ #define initKey( a, b )\
+ nKey = LOWORD( VkKeyScanW( a ) );\
+ if( nKey < 0xffff )\
+ maVKMap[ nKey ] = b;
+
+ maVKMap.clear();
+
+ initKey( L'+', KEY_ADD );
+ initKey( L'-', KEY_SUBTRACT );
+ initKey( L'*', KEY_MULTIPLY );
+ initKey( L'/', KEY_DIVIDE );
+ initKey( L'.', KEY_POINT );
+ initKey( L',', KEY_COMMA );
+ initKey( L'<', KEY_LESS );
+ initKey( L'>', KEY_GREATER );
+ initKey( L'=', KEY_EQUAL );
+ initKey( L'~', KEY_TILDE );
+ initKey( L'`', KEY_QUOTELEFT );
+ initKey( L'[', KEY_BRACKETLEFT );
+ initKey( L']', KEY_BRACKETRIGHT );
+ initKey( L';', KEY_SEMICOLON );
+ initKey( L'\'', KEY_QUOTERIGHT );
+ initKey( L'}', KEY_RIGHTCURLYBRACKET );
+ initKey( L'#', KEY_NUMBERSIGN);
+ initKey( L':', KEY_COLON );
+}
+
+// SalData
+
+SalData::SalData()
+ : sal::systools::CoInitializeGuard(COINIT_APARTMENTTHREADED, false,
+ sal::systools::CoInitializeGuard::WhenFailed::NoThrow)
+ // put main thread in Single Threaded Apartment (STA)
+{
+ mhInst = nullptr; // default instance handle
+ mnCmdShow = 0; // default frame show style
+ mhDitherPal = nullptr; // dither palette
+ mhDitherDIB = nullptr; // dither memory handle
+ mpDitherDIB = nullptr; // dither memory
+ mpDitherDIBData = nullptr; // beginning of DIB data
+ mpDitherDiff = nullptr; // Dither mapping table
+ mpDitherLow = nullptr; // Dither mapping table
+ mpDitherHigh = nullptr; // Dither mapping table
+ mhSalObjMsgHook = nullptr; // hook to get interesting msg for SalObject
+ mhWantLeaveMsg = nullptr; // window handle, that want a MOUSELEAVE message
+ mpInstance = nullptr; // pointer of first instance
+ mpFirstFrame = nullptr; // pointer of first frame
+ mpFirstObject = nullptr; // pointer of first object window
+ mpFirstVD = nullptr; // first VirDev
+ mpFirstPrinter = nullptr; // first printing printer
+ mh50Bmp = nullptr; // 50% Bitmap
+ mh50Brush = nullptr; // 50% Brush
+ int i;
+ for(i=0; i<MAX_STOCKPEN; i++)
+ {
+ maStockPenColorAry[i] = 0;
+ mhStockPenAry[i] = nullptr;
+ }
+ for(i=0; i<MAX_STOCKBRUSH; i++)
+ {
+ maStockBrushColorAry[i] = 0;
+ mhStockBrushAry[i] = nullptr;
+ }
+ mnStockPenCount = 0; // count of static pens
+ mnStockBrushCount = 0; // count of static brushes
+ mnSalObjWantKeyEvt = 0; // KeyEvent for the SalObj hook
+ mnCacheDCInUse = 0; // count of CacheDC in use
+ mbObjClassInit = false; // is SALOBJECTCLASS initialised
+ mbInPalChange = false; // is in WM_QUERYNEWPALETTE
+ mnAppThreadId = 0; // Id from Application-Thread
+ mpFirstIcon = nullptr; // icon cache, points to first icon, NULL if none
+ mpSharedTempFontItem = nullptr;
+ mpOtherTempFontItem = nullptr;
+ mbThemeChanged = false; // true if visual theme was changed: throw away theme handles
+ mbThemeMenuSupport = false;
+
+ // init with NULL
+ gdiplusToken = 0;
+
+ initKeyCodeMap();
+
+ SetSalData( this );
+ initNWF();
+
+ static Gdiplus::GdiplusStartupInput gdiplusStartupInput;
+ Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
+}
+
+SalData::~SalData()
+{
+ deInitNWF();
+ SetSalData( nullptr );
+
+ if (gdiplusToken)
+ Gdiplus::GdiplusShutdown(gdiplusToken);
+}
+
+bool OSSupportsDarkMode()
+{
+ bool bRet = false;
+ if (HMODULE h_ntdll = GetModuleHandleW(L"ntdll.dll"))
+ {
+ typedef LONG(WINAPI* RtlGetVersion_t)(PRTL_OSVERSIONINFOW);
+ if (auto RtlGetVersion
+ = reinterpret_cast<RtlGetVersion_t>(GetProcAddress(h_ntdll, "RtlGetVersion")))
+ {
+ RTL_OSVERSIONINFOW vi2{};
+ vi2.dwOSVersionInfoSize = sizeof(vi2);
+ if (RtlGetVersion(&vi2) == 0)
+ {
+ if (vi2.dwMajorVersion > 10)
+ bRet = true;
+ else if (vi2.dwMajorVersion == 10)
+ {
+ if (vi2.dwMinorVersion > 0)
+ bRet = true;
+ else if (vi2.dwBuildNumber >= 18362)
+ bRet = true;
+ }
+ }
+ }
+ }
+ return bRet;
+}
+
+namespace {
+
+enum PreferredAppMode
+{
+ Default,
+ AllowDark,
+ ForceDark,
+ ForceLight,
+ Max
+};
+
+}
+
+extern "C" {
+VCLPLUG_WIN_PUBLIC SalInstance* create_SalInstance()
+{
+ SalData* pSalData = new SalData();
+
+ STARTUPINFOW aSI;
+ aSI.cb = sizeof( aSI );
+ GetStartupInfoW( &aSI );
+ pSalData->mhInst = GetModuleHandleW( nullptr );
+ pSalData->mnCmdShow = aSI.wShowWindow;
+
+ pSalData->mnAppThreadId = GetCurrentThreadId();
+
+ static bool bSetAllowDarkMode = OSSupportsDarkMode(); // too early to additionally check LibreOffice's config
+ if (bSetAllowDarkMode)
+ {
+ typedef PreferredAppMode(WINAPI* SetPreferredAppMode_t)(PreferredAppMode);
+ if (HINSTANCE hUxthemeLib = LoadLibraryExW(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32))
+ {
+ if (auto SetPreferredAppMode = reinterpret_cast<SetPreferredAppMode_t>(GetProcAddress(hUxthemeLib, MAKEINTRESOURCEA(135))))
+ SetPreferredAppMode(AllowDark);
+ FreeLibrary(hUxthemeLib);
+ }
+ }
+
+ // register frame class
+ WNDCLASSEXW aWndClassEx;
+ aWndClassEx.cbSize = sizeof( aWndClassEx );
+ aWndClassEx.style = CS_OWNDC;
+ aWndClassEx.lpfnWndProc = SalFrameWndProcW;
+ aWndClassEx.cbClsExtra = 0;
+ aWndClassEx.cbWndExtra = SAL_FRAME_WNDEXTRA;
+ aWndClassEx.hInstance = pSalData->mhInst;
+ aWndClassEx.hCursor = nullptr;
+ aWndClassEx.hbrBackground = nullptr;
+ aWndClassEx.lpszMenuName = nullptr;
+ aWndClassEx.lpszClassName = SAL_FRAME_CLASSNAMEW;
+ ImplLoadSalIcon( SAL_RESID_ICON_DEFAULT, aWndClassEx.hIcon, aWndClassEx.hIconSm );
+ if ( !RegisterClassExW( &aWndClassEx ) )
+ return nullptr;
+
+ aWndClassEx.hIcon = nullptr;
+ aWndClassEx.hIconSm = nullptr;
+ aWndClassEx.style |= CS_SAVEBITS;
+ aWndClassEx.lpszClassName = SAL_SUBFRAME_CLASSNAMEW;
+ if ( !RegisterClassExW( &aWndClassEx ) )
+ return nullptr;
+
+ // shadow effect for popups on XP
+ aWndClassEx.style |= CS_DROPSHADOW;
+ aWndClassEx.lpszClassName = SAL_TMPSUBFRAME_CLASSNAMEW;
+ if ( !RegisterClassExW( &aWndClassEx ) )
+ return nullptr;
+
+ aWndClassEx.style = 0;
+ aWndClassEx.lpfnWndProc = SalComWndProcW;
+ aWndClassEx.cbWndExtra = 0;
+ aWndClassEx.lpszClassName = SAL_COM_CLASSNAMEW;
+ if ( !RegisterClassExW( &aWndClassEx ) )
+ return nullptr;
+
+ HWND hComWnd = CreateWindowExW( WS_EX_TOOLWINDOW, SAL_COM_CLASSNAMEW,
+ L"", WS_POPUP, 0, 0, 0, 0, nullptr, nullptr,
+ pSalData->mhInst, nullptr );
+ if ( !hComWnd )
+ return nullptr;
+
+ WinSalInstance* pInst = new WinSalInstance;
+
+ // init instance (only one instance in this version !!!)
+ pSalData->mpInstance = pInst;
+ pInst->mhInst = pSalData->mhInst;
+ pInst->mhComWnd = hComWnd;
+
+ // init static GDI Data
+ ImplInitSalGDI();
+
+ return pInst;
+}
+}
+
+WinSalInstance::WinSalInstance()
+ : SalInstance(std::make_unique<SalYieldMutex>())
+ , mhInst( nullptr )
+ , mhComWnd( nullptr )
+ , m_nNoYieldLock( 0 )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mxToolkitName = OUString("win");
+ m_bSupportsOpenGL = true;
+#if HAVE_FEATURE_SKIA
+ WinSkiaSalGraphicsImpl::prepareSkia();
+#if SKIA_USE_BITMAP32
+ if (SkiaHelper::isVCLSkiaEnabled())
+ m_bSupportsBitmap32 = true;
+#endif
+#endif
+}
+
+WinSalInstance::~WinSalInstance()
+{
+ ImplFreeSalGDI();
+ DestroyWindow( mhComWnd );
+#if HAVE_FEATURE_SKIA
+ SkiaHelper::cleanup();
+#endif
+}
+
+void WinSalInstance::AfterAppInit()
+{
+// (1) Ideally this would be done at the place that creates the thread, but since this thread is normally
+// just the default/main thread, that is not possible.
+// (2) Don't do this on unix, where it causes tools like pstree on Linux to
+// confusingly report soffice.bin as VCL Main instead.
+ osl_setThreadName("VCL Main");
+}
+
+static LRESULT ImplSalDispatchMessage( const MSG* pMsg )
+{
+ SalData* pSalData = GetSalData();
+ if ( pSalData->mpFirstObject && ImplSalPreDispatchMsg( pMsg ) )
+ return 0;
+ LRESULT lResult = DispatchMessageW( pMsg );
+ if ( pSalData->mpFirstObject )
+ ImplSalPostDispatchMsg( pMsg );
+ return lResult;
+}
+
+// probably can't be static, because of SalTimer friend? (static gives C4211)
+bool ImplSalYield(const bool bWait, const bool bHandleAllCurrentEvents)
+{
+ // used to abort further message processing on tick count wraps
+ static sal_uInt32 nLastTicks = 0;
+
+ // we should never yield in m_nNoYieldLock mode!
+ const bool bNoYieldLock = (GetSalData()->mpInstance->m_nNoYieldLock > 0);
+ assert(!bNoYieldLock);
+ if (bNoYieldLock)
+ return false;
+
+ MSG aMsg;
+ bool bWasMsg = false, bWasTimeoutMsg = false;
+ WinSalTimer* pTimer = static_cast<WinSalTimer*>(ImplGetSVData()->maSchedCtx.mpSalTimer);
+
+ sal_uInt32 nCurTicks = GetTickCount();
+
+ do
+ {
+ if (!PeekMessageW(&aMsg, nullptr, 0, 0, PM_REMOVE))
+ break;
+
+ bWasMsg = true;
+ TranslateMessage(&aMsg);
+ LRESULT nRet = ImplSalDispatchMessage(&aMsg);
+
+ bWasTimeoutMsg |= (SAL_MSG_TIMER_CALLBACK == aMsg.message) && static_cast<bool>(nRet);
+
+ if (!bHandleAllCurrentEvents)
+ break;
+
+ if ((aMsg.time > nCurTicks) && (nLastTicks <= nCurTicks || aMsg.time < nLastTicks))
+ break;
+ }
+ while( true );
+
+ // 0ms timeouts are handled out-of-bounds to prevent busy-locking the
+ // event loop with timeout messages.
+ // We ensure we never handle more than one timeout per call.
+ // This way we'll always process a normal system message.
+ if ( !bWasTimeoutMsg && pTimer && pTimer->IsDirectTimeout() )
+ {
+ pTimer->ImplHandleElapsedTimer();
+ bWasMsg = true;
+ }
+
+ nLastTicks = nCurTicks;
+
+ if ( bWait && !bWasMsg )
+ {
+ switch (GetMessageW(&aMsg, nullptr, 0, 0))
+ {
+ case -1:
+ SAL_WARN("vcl.schedule", "GetMessageW failed: " << WindowsErrorString(GetLastError()));
+ // should we std::abort() / SalAbort here?
+ break;
+ case 0:
+ SAL_INFO("vcl.schedule", "GetMessageW received WM_QUIT while waiting");
+ break;
+ default:
+ bWasMsg = true;
+ TranslateMessage(&aMsg);
+ ImplSalDispatchMessage(&aMsg);
+ break;
+ }
+ }
+
+ // If we enabled ForceRealTimer mode skipping our direct timeout processing,
+ // mainly because some Windows API call spawns its own nested message loop,
+ // switch back to our own processing (like after window resize or move)
+ if ( pTimer )
+ pTimer->SetForceRealTimer( false );
+
+ return bWasMsg;
+}
+
+bool WinSalInstance::IsMainThread() const
+{
+ const SalData* pSalData = GetSalData();
+ return pSalData->mnAppThreadId == GetCurrentThreadId();
+}
+
+bool WinSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
+{
+ bool bDidWork = false;
+ SolarMutexReleaser aReleaser;
+ if ( !IsMainThread() )
+ {
+ bDidWork = SendMessageW( mhComWnd, SAL_MSG_THREADYIELD,
+ WPARAM(false), static_cast<LPARAM>(bHandleAllCurrentEvents) );
+ if ( !bDidWork && bWait )
+ {
+ maWaitingYieldCond.reset();
+ maWaitingYieldCond.wait();
+ bDidWork = true;
+ }
+ }
+ else
+ {
+ bDidWork = ImplSalYield( bWait, bHandleAllCurrentEvents );
+ if ( bDidWork )
+ maWaitingYieldCond.set();
+ }
+
+ return bDidWork;
+}
+
+#define CASE_NOYIELDLOCK( salmsg, function ) \
+ case salmsg: \
+ if (bIsOtherThreadMessage) \
+ { \
+ ++pInst->m_nNoYieldLock; \
+ function; \
+ --pInst->m_nNoYieldLock; \
+ } \
+ else \
+ { \
+ DBG_TESTSOLARMUTEX(); \
+ function; \
+ } \
+ break;
+
+#define CASE_NOYIELDLOCK_RESULT( salmsg, function ) \
+ case salmsg: \
+ if (bIsOtherThreadMessage) \
+ { \
+ ++pInst->m_nNoYieldLock; \
+ nRet = reinterpret_cast<LRESULT>( function ); \
+ --pInst->m_nNoYieldLock; \
+ } \
+ else \
+ { \
+ DBG_TESTSOLARMUTEX(); \
+ nRet = reinterpret_cast<LRESULT>( function ); \
+ } \
+ break;
+
+LRESULT CALLBACK SalComWndProc( HWND, UINT nMsg, WPARAM wParam, LPARAM lParam, bool& rDef )
+{
+ const bool bIsOtherThreadMessage = InSendMessage();
+ LRESULT nRet = 0;
+ WinSalInstance *pInst = GetSalData()->mpInstance;
+ WinSalTimer *const pTimer = static_cast<WinSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
+
+ SAL_INFO("vcl.gdi.wndproc", "SalComWndProc(nMsg=" << nMsg << ", wParam=" << wParam
+ << ", lParam=" << lParam << "); inSendMsg: " << bIsOtherThreadMessage);
+
+ if (ImplGetSVData()->mbDeInit)
+ {
+ SAL_WARN("vcl.gdi.wndproc", "ignoring timer event because we are shutting down");
+ return 0;
+ }
+
+ switch ( nMsg )
+ {
+ case SAL_MSG_THREADYIELD:
+ assert( !static_cast<bool>(wParam) );
+ nRet = static_cast<LRESULT>(ImplSalYield(
+ false, static_cast<bool>( lParam ) ));
+ break;
+
+ case SAL_MSG_STARTTIMER:
+ {
+ auto const nParam = static_cast<sal_uInt64>( lParam );
+ sal_uInt64 nTime = tools::Time::GetSystemTicks();
+ if ( nTime < nParam )
+ nTime = nParam - nTime;
+ else
+ nTime = 0;
+ assert( pTimer != nullptr );
+ pTimer->ImplStart( nTime );
+ break;
+ }
+
+ case SAL_MSG_STOPTIMER:
+ assert( pTimer != nullptr );
+ pTimer->ImplStop();
+ break;
+
+ CASE_NOYIELDLOCK_RESULT( SAL_MSG_CREATEFRAME, ImplSalCreateFrame( GetSalData()->mpInstance,
+ reinterpret_cast<HWND>(lParam), static_cast<SalFrameStyleFlags>(wParam)) )
+ CASE_NOYIELDLOCK_RESULT( SAL_MSG_RECREATEHWND, ImplSalReCreateHWND(
+ reinterpret_cast<HWND>(wParam), reinterpret_cast<HWND>(lParam), false) )
+ CASE_NOYIELDLOCK_RESULT( SAL_MSG_RECREATECHILDHWND, ImplSalReCreateHWND(
+ reinterpret_cast<HWND>(wParam), reinterpret_cast<HWND>(lParam), true) )
+ CASE_NOYIELDLOCK( SAL_MSG_DESTROYFRAME, delete reinterpret_cast<SalFrame*>(lParam) )
+
+ case SAL_MSG_DESTROYHWND:
+ // We only destroy the native window here. We do NOT destroy the SalFrame contained
+ // in the structure (GetWindowPtr()).
+ if (DestroyWindow(reinterpret_cast<HWND>(lParam)) == 0)
+ {
+ OSL_FAIL("DestroyWindow failed!");
+ // Failure: We remove the SalFrame from the window structure. So we avoid that
+ // the window structure may contain an invalid pointer, once the SalFrame is deleted.
+ SetWindowPtr(reinterpret_cast<HWND>(lParam), nullptr);
+ }
+ break;
+
+ CASE_NOYIELDLOCK_RESULT( SAL_MSG_CREATEOBJECT, ImplSalCreateObject(
+ GetSalData()->mpInstance, reinterpret_cast<WinSalFrame*>(lParam)) )
+ CASE_NOYIELDLOCK( SAL_MSG_DESTROYOBJECT, delete reinterpret_cast<SalObject*>(lParam) )
+ CASE_NOYIELDLOCK_RESULT( SAL_MSG_GETCACHEDDC, GetDCEx(
+ reinterpret_cast<HWND>(wParam), nullptr, DCX_CACHE) )
+ CASE_NOYIELDLOCK( SAL_MSG_RELEASEDC, ReleaseDC(
+ reinterpret_cast<HWND>(wParam), reinterpret_cast<HDC>(lParam)) )
+
+ case SAL_MSG_TIMER_CALLBACK:
+ assert( pTimer != nullptr );
+ pTimer->ImplHandleTimerEvent( wParam );
+ break;
+
+ case WM_TIMER:
+ assert( pTimer != nullptr );
+ pTimer->ImplHandle_WM_TIMER( wParam );
+ break;
+
+ case SAL_MSG_FORCE_REAL_TIMER:
+ assert(pTimer != nullptr);
+ pTimer->SetForceRealTimer(true);
+ break;
+
+ case SAL_MSG_DUMMY:
+ break;
+
+ default:
+ rDef = true;
+ break;
+ }
+
+ return nRet;
+}
+
+#undef CASE_NOYIELDLOCK
+#undef CASE_NOYIELDLOCK_RESULT
+
+LRESULT CALLBACK SalComWndProcW( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam )
+{
+ bool bDef = false;
+ LRESULT nRet = 0;
+ __try
+ {
+ nRet = SalComWndProc( hWnd, nMsg, wParam, lParam, bDef );
+ }
+ __except(WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(GetExceptionCode(), GetExceptionInformation()))
+ {
+ }
+ if ( bDef )
+ {
+ if ( !ImplHandleGlobalMsg( hWnd, nMsg, wParam, lParam, nRet ) )
+ nRet = DefWindowProcW( hWnd, nMsg, wParam, lParam );
+ }
+ return nRet;
+}
+
+bool WinSalInstance::AnyInput( VclInputFlags nType )
+{
+ if ( nType & VclInputFlags::TIMER )
+ {
+ const WinSalTimer* pTimer = static_cast<WinSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
+ if ( pTimer && pTimer->HasTimerElapsed() )
+ return true;
+ }
+
+ // Note: Do not use PeekMessage(), despite the name it may dispatch events,
+ // even with PM_NOREMOVE specified, which may lead to unwanted recursion.
+
+ if ( (nType & VCL_INPUT_ANY) == VCL_INPUT_ANY )
+ {
+ // revert bugfix for #108919# which never reported timeouts when called from the timer handler
+ // which made the application completely unresponsive during background formatting
+ if ( GetQueueStatus( QS_ALLEVENTS ))
+ return true;
+ }
+ else
+ {
+ UINT flags = 0;
+
+ // This code previously considered modifier keys as OTHER,
+ // but that makes this hard to do without PeekMessage,
+ // is inconsistent with the X11 backend, and I see no good reason.
+ if ( nType & VclInputFlags::KEYBOARD )
+ flags |= QS_KEY;
+
+ if ( nType & VclInputFlags::MOUSE )
+ flags |= QS_MOUSE;
+
+ if ( nType & VclInputFlags::PAINT )
+ flags |= QS_PAINT;
+
+ if ( nType & VclInputFlags::TIMER )
+ flags |= QS_TIMER;
+
+ if( nType & VclInputFlags::OTHER )
+ flags |= QS_ALLEVENTS & ~QS_KEY & ~QS_MOUSE & ~QS_PAINT & ~QS_TIMER;
+
+ if( GetQueueStatus( flags ))
+ return true;
+ }
+
+ return false;
+}
+
+SalFrame* WinSalInstance::CreateChildFrame( SystemParentData* pSystemParentData, SalFrameStyleFlags nSalFrameStyle )
+{
+ // to switch to Main-Thread
+ return reinterpret_cast<SalFrame*>(static_cast<sal_IntPtr>(SendMessageW( mhComWnd, SAL_MSG_CREATEFRAME, static_cast<WPARAM>(nSalFrameStyle), reinterpret_cast<LPARAM>(pSystemParentData->hWnd) )));
+}
+
+SalFrame* WinSalInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nSalFrameStyle )
+{
+ // to switch to Main-Thread
+ HWND hWndParent;
+ if ( pParent )
+ hWndParent = static_cast<WinSalFrame*>(pParent)->mhWnd;
+ else
+ hWndParent = nullptr;
+ return reinterpret_cast<SalFrame*>(static_cast<sal_IntPtr>(SendMessageW( mhComWnd, SAL_MSG_CREATEFRAME, static_cast<WPARAM>(nSalFrameStyle), reinterpret_cast<LPARAM>(hWndParent) )));
+}
+
+void WinSalInstance::DestroyFrame( SalFrame* pFrame )
+{
+ OpenGLContext::prepareForYield();
+ SendMessageW( mhComWnd, SAL_MSG_DESTROYFRAME, 0, reinterpret_cast<LPARAM>(pFrame) );
+}
+
+SalObject* WinSalInstance::CreateObject( SalFrame* pParent,
+ SystemWindowData* /*pWindowData*/, // SystemWindowData meaningless on Windows
+ bool /*bShow*/ )
+{
+ // to switch to Main-Thread
+ return reinterpret_cast<SalObject*>(static_cast<sal_IntPtr>(SendMessageW( mhComWnd, SAL_MSG_CREATEOBJECT, 0, reinterpret_cast<LPARAM>(static_cast<WinSalFrame*>(pParent)) )));
+}
+
+void WinSalInstance::DestroyObject( SalObject* pObject )
+{
+ SendMessageW( mhComWnd, SAL_MSG_DESTROYOBJECT, 0, reinterpret_cast<LPARAM>(pObject) );
+}
+
+OUString WinSalInstance::GetConnectionIdentifier()
+{
+ return OUString();
+}
+
+/** Add a file to the system shells recent document list if there is any.
+ This function may have no effect under Unix because there is no
+ standard API among the different desktop managers.
+
+ @param aFileUrl
+ The file url of the document.
+*/
+void WinSalInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString& /*rMimeType*/, const OUString& rDocumentService)
+{
+ if (Application::IsHeadlessModeEnabled())
+ return;
+
+ OUString system_path;
+ osl::FileBase::RC rc = osl::FileBase::getSystemPathFromFileURL(rFileUrl, system_path);
+
+ OSL_ENSURE(osl::FileBase::E_None == rc, "Invalid file url");
+
+ if (osl::FileBase::E_None == rc)
+ {
+ IShellItem* pShellItem = nullptr;
+
+ HRESULT hr = SHCreateItemFromParsingName(o3tl::toW(system_path.getStr()), nullptr, IID_PPV_ARGS(&pShellItem));
+
+ if ( SUCCEEDED(hr) && pShellItem )
+ {
+ OUString sApplicationName;
+
+ if ( rDocumentService == "com.sun.star.text.TextDocument" ||
+ rDocumentService == "com.sun.star.text.GlobalDocument" ||
+ rDocumentService == "com.sun.star.text.WebDocument" ||
+ rDocumentService == "com.sun.star.xforms.XMLFormDocument" )
+ sApplicationName = "Writer";
+ else if ( rDocumentService == "com.sun.star.sheet.SpreadsheetDocument" ||
+ rDocumentService == "com.sun.star.chart2.ChartDocument" )
+ sApplicationName = "Calc";
+ else if ( rDocumentService == "com.sun.star.presentation.PresentationDocument" )
+ sApplicationName = "Impress";
+ else if ( rDocumentService == "com.sun.star.drawing.DrawingDocument" )
+ sApplicationName = "Draw";
+ else if ( rDocumentService == "com.sun.star.formula.FormulaProperties" )
+ sApplicationName = "Math";
+ else if ( rDocumentService == "com.sun.star.sdb.DatabaseDocument" ||
+ rDocumentService == "com.sun.star.sdb.OfficeDatabaseDocument" ||
+ rDocumentService == "com.sun.star.sdb.RelationDesign" ||
+ rDocumentService == "com.sun.star.sdb.QueryDesign" ||
+ rDocumentService == "com.sun.star.sdb.TableDesign" ||
+ rDocumentService == "com.sun.star.sdb.DataSourceBrowser" )
+ sApplicationName = "Base";
+
+ if ( !sApplicationName.isEmpty() )
+ {
+ OUString sApplicationID("TheDocumentFoundation.LibreOffice." + sApplicationName);
+
+ SHARDAPPIDINFO info;
+ info.psi = pShellItem;
+ info.pszAppID = o3tl::toW(sApplicationID.getStr());
+
+ SHAddToRecentDocs ( SHARD_APPIDINFO, &info );
+ return;
+ }
+ }
+ // For whatever reason, we could not use the SHARD_APPIDINFO semantics
+ SHAddToRecentDocs(SHARD_PATHW, system_path.getStr());
+ }
+}
+
+SalTimer* WinSalInstance::CreateSalTimer()
+{
+ return new WinSalTimer();
+}
+
+std::shared_ptr<SalBitmap> WinSalInstance::CreateSalBitmap()
+{
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled())
+ return std::make_shared<SkiaSalBitmap>();
+ else
+#endif
+ return std::make_shared<WinSalBitmap>();
+}
+
+int WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(int, LPEXCEPTION_POINTERS pExceptionInfo)
+{
+ // Decide if an exception is a c++ (mostly UNO) exception or a process violation.
+ // Depending on this information we pass process violations directly to our signal handler ...
+ // and c++ (UNO) exceptions are sended to the following code on the current stack.
+ // Problem behind: user32.dll sometime consumes exceptions/process violations .-)
+ // see also #112221#
+
+ static const DWORD EXCEPTION_MSC_CPP_EXCEPTION = 0xE06D7363;
+
+ if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_MSC_CPP_EXCEPTION)
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ return UnhandledExceptionFilter( pExceptionInfo );
+}
+
+typedef LONG NTSTATUS;
+typedef NTSTATUS(WINAPI* RtlGetVersion_t)(PRTL_OSVERSIONINFOW);
+constexpr NTSTATUS STATUS_SUCCESS = 0x00000000;
+
+OUString WinSalInstance::getOSVersion()
+{
+ OUStringBuffer aVer(50); // capacity for string like "Windows 6.1 Service Pack 1 build 7601"
+ aVer.append("Windows ");
+ // 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
+ bool bHaveVerFromKernel32 = false;
+ if (HMODULE h_kernel32 = GetModuleHandleW(L"kernel32.dll"))
+ {
+ wchar_t szPath[MAX_PATH];
+ DWORD dwCount = GetModuleFileNameW(h_kernel32, szPath, SAL_N_ELEMENTS(szPath));
+ 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* vi1 = static_cast<VS_FIXEDFILEINFO*>(pBlock);
+ aVer.append(OUString::number(HIWORD(vi1->dwProductVersionMS)) + "."
+ + OUString::number(LOWORD(vi1->dwProductVersionMS)));
+ bHaveVerFromKernel32 = true;
+ }
+ }
+ }
+ }
+ }
+ // Now use RtlGetVersion (which is not subject to deprecation for GetVersion(Ex) API)
+ // to get build number and SP info
+ bool bHaveVerFromRtlGetVersion = false;
+ if (HMODULE h_ntdll = GetModuleHandleW(L"ntdll.dll"))
+ {
+ if (auto RtlGetVersion
+ = reinterpret_cast<RtlGetVersion_t>(GetProcAddress(h_ntdll, "RtlGetVersion")))
+ {
+ RTL_OSVERSIONINFOW vi2{}; // initialize with zeroes - a better alternative to memset
+ vi2.dwOSVersionInfoSize = sizeof(vi2);
+ if (STATUS_SUCCESS == RtlGetVersion(&vi2))
+ {
+ if (!bHaveVerFromKernel32) // we failed above; let's hope this would be useful
+ aVer.append(OUString::number(vi2.dwMajorVersion) + "."
+ + OUString::number(vi2.dwMinorVersion));
+ aVer.append(" ");
+ if (vi2.szCSDVersion[0])
+ aVer.append(OUString::Concat(o3tl::toU(vi2.szCSDVersion)) + " ");
+ aVer.append("Build " + OUString::number(vi2.dwBuildNumber));
+ bHaveVerFromRtlGetVersion = true;
+ }
+ }
+ }
+ if (!bHaveVerFromKernel32 && !bHaveVerFromRtlGetVersion)
+ aVer.append("unknown");
+ return aVer.makeStringAndClear();
+}
+
+void WinSalInstance::BeforeAbort(const OUString&, bool)
+{
+ ImplFreeSalGDI();
+}
+
+css::uno::Reference<css::uno::XInterface> WinSalInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv)
+{
+ return vcl::OleDnDHelper(new DragSource(comphelper::getProcessComponentContext()),
+ reinterpret_cast<sal_IntPtr>(pSysEnv->hWnd), vcl::DragOrDrop::Drag);
+}
+
+css::uno::Reference<css::uno::XInterface> WinSalInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv)
+{
+ return vcl::OleDnDHelper(new DropTarget(comphelper::getProcessComponentContext()),
+ reinterpret_cast<sal_IntPtr>(pSysEnv->hWnd), vcl::DragOrDrop::Drop);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/app/salshl.cxx b/vcl/win/app/salshl.cxx
new file mode 100644
index 0000000000..5619ece1f0
--- /dev/null
+++ b/vcl/win/app/salshl.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 <svsys.h>
+#include <sal/log.hxx>
+#include <win/saldata.hxx>
+
+SalShlData aSalShlData;
+
+extern "C" BOOL WINAPI DllMain(HINSTANCE hInst, DWORD nReason, LPVOID)
+{
+ if ( nReason == DLL_PROCESS_ATTACH )
+ aSalShlData.mhInst = hInst;
+ return 1;
+}
+
+HCURSOR ImplLoadSalCursor( int nId )
+{
+ SAL_WARN_IF( !aSalShlData.mhInst, "vcl", "no DLL instance handle" );
+
+ HCURSOR hCursor = LoadCursorW( aSalShlData.mhInst, MAKEINTRESOURCEW( nId ) );
+
+ SAL_WARN_IF( !hCursor, "vcl", "cursor not found in sal resource" );
+
+ return hCursor;
+}
+
+HBITMAP ImplLoadSalBitmap( int nId )
+{
+ SAL_WARN_IF( !aSalShlData.mhInst, "vcl", "no DLL instance handle" );
+
+ HBITMAP hBitmap = LoadBitmapW( aSalShlData.mhInst, MAKEINTRESOURCEW( nId ) );
+
+ SAL_WARN_IF( !hBitmap, "vcl", "bitmap not found in sal resource" );
+
+ return hBitmap;
+}
+
+bool ImplLoadSalIcon( int nId, HICON& rIcon, HICON& rSmallIcon )
+{
+ SAL_WARN_IF( !aSalShlData.mhInst, "vcl", "no DLL instance handle" );
+
+ SalData* pSalData = GetSalData();
+
+ // check the cache first
+ SalIcon *pSalIcon = pSalData->mpFirstIcon;
+ while( pSalIcon )
+ {
+ if( pSalIcon->nId != nId )
+ pSalIcon = pSalIcon->pNext;
+ else
+ {
+ rIcon = pSalIcon->hIcon;
+ rSmallIcon = pSalIcon->hSmallIcon;
+ return (rSmallIcon != nullptr);
+ }
+ }
+
+ // Try at first to load the icons from the application exe file
+ rIcon = static_cast<HICON>(LoadImageW( pSalData->mhInst, MAKEINTRESOURCEW( nId ),
+ IMAGE_ICON, GetSystemMetrics( SM_CXICON ), GetSystemMetrics( SM_CYICON ),
+ LR_DEFAULTCOLOR ));
+ if ( !rIcon )
+ {
+ // If the application don't provide these icons, then we try
+ // to load the icon from the VCL resource
+ rIcon = static_cast<HICON>(LoadImageW( aSalShlData.mhInst, MAKEINTRESOURCEW( nId ),
+ IMAGE_ICON, GetSystemMetrics( SM_CXICON ), GetSystemMetrics( SM_CYICON ),
+ LR_DEFAULTCOLOR ));
+ if ( rIcon )
+ {
+ rSmallIcon = static_cast<HICON>(LoadImageW( aSalShlData.mhInst, MAKEINTRESOURCEW( nId ),
+ IMAGE_ICON, GetSystemMetrics( SM_CXSMICON ), GetSystemMetrics( SM_CYSMICON ),
+ LR_DEFAULTCOLOR ));
+ }
+ else
+ rSmallIcon = nullptr;
+ }
+ else
+ {
+ rSmallIcon = static_cast<HICON>(LoadImageW( pSalData->mhInst, MAKEINTRESOURCEW( nId ),
+ IMAGE_ICON, GetSystemMetrics( SM_CXSMICON ), GetSystemMetrics( SM_CYSMICON ),
+ LR_DEFAULTCOLOR ));
+ }
+
+ if( rIcon )
+ {
+ // add to icon cache
+ pSalData->mpFirstIcon = new SalIcon{
+ nId, rIcon, rSmallIcon, pSalData->mpFirstIcon};
+ }
+
+ return (rSmallIcon != nullptr);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/app/saltimer.cxx b/vcl/win/app/saltimer.cxx
new file mode 100644
index 0000000000..1ccab54e96
--- /dev/null
+++ b/vcl/win/app/saltimer.cxx
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/time.hxx>
+
+#include <svsys.h>
+#include <win/saldata.hxx>
+#include <win/saltimer.h>
+#include <win/salinst.h>
+
+void CALLBACK SalTimerProc(PVOID pParameter, BOOLEAN bTimerOrWaitFired);
+
+// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms687003%28v=vs.85%29.aspx
+// (and related pages) for details about the Timer Queues.
+
+// in order to prevent concurrent execution of ImplSalStartTimer and double
+// deletion of timer (which is extremely likely, given that
+// INVALID_HANDLE_VALUE waits for the callback to run on the main thread),
+// this must run on the main thread too
+void WinSalTimer::ImplStop()
+{
+ SalData *const pSalData = GetSalData();
+ const WinSalInstance *pInst = pSalData->mpInstance;
+ assert( !pInst || pSalData->mnAppThreadId == GetCurrentThreadId() );
+
+ if ( m_bSetTimerRunning )
+ {
+ m_bSetTimerRunning = false;
+ KillTimer( pInst->mhComWnd, m_aWmTimerId );
+ }
+ m_bDirectTimeout = false;
+
+ const HANDLE hTimer = m_nTimerId;
+ if ( nullptr == hTimer )
+ return;
+
+ m_nTimerId = nullptr;
+ DeleteTimerQueueTimer( nullptr, hTimer, INVALID_HANDLE_VALUE );
+ // Keep InvalidateEvent after DeleteTimerQueueTimer, because the event id
+ // is set in SalTimerProc, which DeleteTimerQueueTimer will finish or cancel.
+ InvalidateEvent();
+}
+
+void WinSalTimer::ImplStart( sal_uInt64 nMS )
+{
+#if !defined NDEBUG
+ SalData* pSalData = GetSalData();
+ assert( !pSalData->mpInstance || pSalData->mnAppThreadId == GetCurrentThreadId() );
+#endif
+
+ // DueTime parameter is a DWORD, which is always an unsigned 32bit
+ if (nMS > SAL_MAX_UINT32)
+ nMS = SAL_MAX_UINT32;
+
+ // cannot change a one-shot timer, so delete it and create a new one
+ ImplStop();
+
+ // directly indicate an elapsed timer
+ m_bDirectTimeout = ( 0 == nMS );
+ // probably WT_EXECUTEONLYONCE is not needed, but it enforces Period
+ // to be 0 and should not hurt; also see
+ // https://www.microsoft.com/msj/0499/pooling/pooling.aspx
+ if ( !m_bDirectTimeout )
+ CreateTimerQueueTimer(&m_nTimerId, nullptr, SalTimerProc, this,
+ nMS, 0, WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE);
+ else if ( m_bForceRealTimer )
+ {
+ // so we don't block the nested message queue in move and resize
+ // with posted 0ms SAL_MSG_TIMER_CALLBACK messages
+ SetTimer( GetSalData()->mpInstance->mhComWnd, m_aWmTimerId,
+ USER_TIMER_MINIMUM, nullptr );
+ m_bSetTimerRunning = true;
+ }
+ // we don't need any wakeup message, as this code can just run in the
+ // main thread!
+}
+
+WinSalTimer::WinSalTimer()
+ : m_nTimerId( nullptr )
+ , m_bDirectTimeout( false )
+ , m_bForceRealTimer( false )
+ , m_bSetTimerRunning( false )
+{
+}
+
+WinSalTimer::~WinSalTimer()
+{
+ Stop();
+}
+
+void WinSalTimer::Start( sal_uInt64 nMS )
+{
+ WinSalInstance *pInst = GetSalData()->mpInstance;
+ if ( pInst && !pInst->IsMainThread() )
+ {
+ bool const ret = PostMessageW(pInst->mhComWnd,
+ SAL_MSG_STARTTIMER, 0, static_cast<LPARAM>(tools::Time::GetSystemTicks()) + nMS);
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+ }
+ else
+ ImplStart( nMS );
+}
+
+void WinSalTimer::Stop()
+{
+ WinSalInstance *pInst = GetSalData()->mpInstance;
+ if ( pInst && !pInst->IsMainThread() )
+ {
+ bool const ret = PostMessageW(pInst->mhComWnd,
+ SAL_MSG_STOPTIMER, 0, 0);
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+ }
+ else
+ ImplStop();
+}
+
+/**
+ * This gets invoked from a Timer Queue thread.
+ * Don't acquire the SolarMutex to avoid deadlocks.
+ */
+void CALLBACK SalTimerProc(PVOID data, BOOLEAN)
+{
+ __try
+ {
+ WinSalTimer *pTimer = static_cast<WinSalTimer*>( data );
+ bool const ret = PostMessageW(
+ GetSalData()->mpInstance->mhComWnd, SAL_MSG_TIMER_CALLBACK,
+ static_cast<WPARAM>(pTimer->GetNextEventVersion()), 0 );
+#if OSL_DEBUG_LEVEL > 0
+ if (!ret) // SEH prevents using SAL_WARN here?
+ fputs("ERROR: PostMessage() failed!\n", stderr);
+#endif
+ }
+ __except(WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(GetExceptionCode(), GetExceptionInformation()))
+ {
+ }
+}
+
+void WinSalTimer::ImplHandleElapsedTimer()
+{
+ // Test for MouseLeave
+ SalTestMouseLeave();
+
+ m_bDirectTimeout = false;
+ ImplSalYieldMutexAcquireWithWait();
+ CallCallback();
+ ImplSalYieldMutexRelease();
+}
+
+void WinSalTimer::ImplHandleTimerEvent( const WPARAM aWPARAM )
+{
+ assert( aWPARAM <= SAL_MAX_INT32 );
+ if ( !IsValidEventVersion( static_cast<sal_Int32>( aWPARAM ) ) )
+ return;
+
+ ImplHandleElapsedTimer();
+}
+
+void WinSalTimer::SetForceRealTimer( const bool bVal )
+{
+ if ( m_bForceRealTimer == bVal )
+ return;
+
+ m_bForceRealTimer = bVal;
+
+ // we need a real timer, as m_bDirectTimeout won't be processed
+ if ( bVal && m_bDirectTimeout )
+ Start( 0 );
+}
+
+void WinSalTimer::ImplHandle_WM_TIMER( const WPARAM aWPARAM )
+{
+ assert( m_aWmTimerId == aWPARAM );
+ if ( !(m_aWmTimerId == aWPARAM && m_bSetTimerRunning) )
+ return;
+
+ m_bSetTimerRunning = false;
+ KillTimer( GetSalData()->mpInstance->mhComWnd, m_aWmTimerId );
+ ImplHandleElapsedTimer();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/APNDataObject.cxx b/vcl/win/dtrans/APNDataObject.cxx
new file mode 100644
index 0000000000..2492647f26
--- /dev/null
+++ b/vcl/win/dtrans/APNDataObject.cxx
@@ -0,0 +1,320 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "APNDataObject.hxx"
+#include <osl/diagnose.h>
+
+#include <systools/win32/comtools.hxx>
+
+#define FREE_HGLOB_ON_RELEASE TRUE
+#define KEEP_HGLOB_ON_RELEASE FALSE
+
+// ctor
+
+CAPNDataObject::CAPNDataObject( IDataObjectPtr rIDataObject ) :
+ m_rIDataObjectOrg( rIDataObject ),
+ m_hGlobal( nullptr ),
+ m_nRefCnt( 0 )
+{
+
+ OSL_ENSURE( m_rIDataObjectOrg.get( ), "constructing CAPNDataObject with empty data object" );
+
+ // we marshal the IDataObject interface pointer here so
+ // that it can be unmarshalled multiple times when this
+ // class will be used from another apartment
+ sal::systools::COMReference<IStream> pStm;
+ HRESULT hr = CreateStreamOnHGlobal( nullptr, KEEP_HGLOB_ON_RELEASE, &pStm );
+
+ OSL_ENSURE( E_INVALIDARG != hr, "invalid args passed to CreateStreamOnHGlobal" );
+
+ if ( SUCCEEDED( hr ) )
+ {
+ HRESULT hr_marshal = CoMarshalInterface(
+ pStm,
+ __uuidof(IDataObject),
+ m_rIDataObjectOrg,
+ MSHCTX_LOCAL,
+ nullptr,
+ MSHLFLAGS_TABLEWEAK );
+
+ OSL_ENSURE( CO_E_NOTINITIALIZED != hr_marshal, "COM is not initialized" );
+
+ // marshalling may fail if COM is not initialized
+ // for the calling thread which is a program time
+ // error or because of stream errors which are runtime
+ // errors for instance E_OUTOFMEMORY etc.
+
+ hr = GetHGlobalFromStream(pStm, &m_hGlobal );
+
+ OSL_ENSURE( E_INVALIDARG != hr, "invalid stream passed to GetHGlobalFromStream" );
+
+ // if the marshalling failed we free the
+ // global memory again and set m_hGlobal
+ // to a defined value
+ if (FAILED(hr_marshal))
+ {
+ OSL_FAIL("marshalling failed");
+
+ HGLOBAL hGlobal =
+ GlobalFree(m_hGlobal);
+ OSL_ENSURE(nullptr == hGlobal, "GlobalFree failed");
+ m_hGlobal = nullptr;
+ }
+ }
+}
+
+CAPNDataObject::~CAPNDataObject( )
+{
+ if (m_hGlobal)
+ {
+ sal::systools::COMReference<IStream> pStm;
+ HRESULT hr = CreateStreamOnHGlobal(m_hGlobal, FREE_HGLOB_ON_RELEASE, &pStm);
+
+ OSL_ENSURE( E_INVALIDARG != hr, "invalid args passed to CreateStreamOnHGlobal" );
+
+ if (SUCCEEDED(hr))
+ {
+ hr = CoReleaseMarshalData(pStm);
+ OSL_ENSURE(SUCCEEDED(hr), "CoReleaseMarshalData failed");
+ }
+ }
+}
+
+// IUnknown->QueryInterface
+
+STDMETHODIMP CAPNDataObject::QueryInterface( REFIID iid, void** ppvObject )
+{
+ OSL_ASSERT( nullptr != ppvObject );
+
+ if ( nullptr == ppvObject )
+ return E_INVALIDARG;
+
+ HRESULT hr = E_NOINTERFACE;
+ *ppvObject = nullptr;
+
+ if ( ( __uuidof( IUnknown ) == iid ) || ( __uuidof( IDataObject ) == iid ) )
+ {
+ *ppvObject = static_cast< IUnknown* >( this );
+ AddRef( );
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+// IUnknown->AddRef
+
+STDMETHODIMP_(ULONG) CAPNDataObject::AddRef( )
+{
+ return static_cast< ULONG >( InterlockedIncrement( &m_nRefCnt ) );
+}
+
+// IUnknown->Release
+
+STDMETHODIMP_(ULONG) CAPNDataObject::Release( )
+{
+ // we need a helper variable because it's not allowed to access
+ // a member variable after an object is destroyed
+ ULONG nRefCnt = static_cast< ULONG >( InterlockedDecrement( &m_nRefCnt ) );
+
+ if ( 0 == nRefCnt )
+ delete this;
+
+ return nRefCnt;
+}
+
+// IDataObject->GetData
+
+STDMETHODIMP CAPNDataObject::GetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium )
+{
+ HRESULT hr = m_rIDataObjectOrg->GetData( pFormatetc, pmedium );
+
+ if (RPC_E_WRONG_THREAD == hr)
+ {
+ IDataObjectPtr pIDOTmp;
+ hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp);
+
+ if (SUCCEEDED(hr))
+ hr = pIDOTmp->GetData(pFormatetc, pmedium);
+ }
+ return hr;
+}
+
+// IDataObject->EnumFormatEtc
+
+STDMETHODIMP CAPNDataObject::EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC** ppenumFormatetc )
+{
+ HRESULT hr = m_rIDataObjectOrg->EnumFormatEtc(dwDirection, ppenumFormatetc);
+
+ if (RPC_E_WRONG_THREAD == hr)
+ {
+ IDataObjectPtr pIDOTmp;
+ hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp);
+
+ if (SUCCEEDED(hr))
+ hr = pIDOTmp->EnumFormatEtc(dwDirection, ppenumFormatetc);
+ }
+ return hr;
+}
+
+// IDataObject->QueryGetData
+
+STDMETHODIMP CAPNDataObject::QueryGetData( FORMATETC * pFormatetc )
+{
+ HRESULT hr = m_rIDataObjectOrg->QueryGetData( pFormatetc );
+
+ if (RPC_E_WRONG_THREAD == hr)
+ {
+ IDataObjectPtr pIDOTmp;
+ hr = MarshalIDataObjectIntoCurrentApartment( &pIDOTmp );
+
+ if (SUCCEEDED(hr))
+ hr = pIDOTmp->QueryGetData(pFormatetc);
+ }
+ return hr;
+}
+
+// IDataObject->GetDataHere
+
+STDMETHODIMP CAPNDataObject::GetDataHere( FORMATETC * pFormatetc, STGMEDIUM * pmedium )
+{
+ HRESULT hr = m_rIDataObjectOrg->GetDataHere(pFormatetc, pmedium);
+
+ if (RPC_E_WRONG_THREAD == hr)
+ {
+ IDataObjectPtr pIDOTmp;
+ hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp);
+
+ if (SUCCEEDED(hr))
+ hr = pIDOTmp->GetDataHere(pFormatetc, pmedium);
+ }
+ return hr;
+}
+
+// IDataObject->GetCanonicalFormatEtc
+
+STDMETHODIMP CAPNDataObject::GetCanonicalFormatEtc(FORMATETC * pFormatectIn, FORMATETC * pFormatetcOut)
+{
+ HRESULT hr = m_rIDataObjectOrg->GetCanonicalFormatEtc( pFormatectIn, pFormatetcOut );
+
+ if (RPC_E_WRONG_THREAD == hr)
+ {
+ IDataObjectPtr pIDOTmp;
+ hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp);
+
+ if (SUCCEEDED(hr))
+ hr = pIDOTmp->GetCanonicalFormatEtc(pFormatectIn, pFormatetcOut);
+ }
+ return hr;
+}
+
+// IDataObject->SetData
+
+STDMETHODIMP CAPNDataObject::SetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium, BOOL fRelease )
+{
+ HRESULT hr = m_rIDataObjectOrg->SetData( pFormatetc, pmedium, fRelease );
+
+ if (RPC_E_WRONG_THREAD == hr)
+ {
+ IDataObjectPtr pIDOTmp;
+ hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp);
+
+ if (SUCCEEDED(hr))
+ hr = pIDOTmp->SetData(pFormatetc, pmedium, fRelease);
+ }
+ return hr;
+}
+
+// IDataObject->DAdvise
+
+STDMETHODIMP CAPNDataObject::DAdvise( FORMATETC * pFormatetc, DWORD advf, IAdviseSink * pAdvSink, DWORD * pdwConnection )
+{
+ HRESULT hr = m_rIDataObjectOrg->DAdvise(pFormatetc, advf, pAdvSink, pdwConnection);
+
+ if (RPC_E_WRONG_THREAD == hr)
+ {
+ IDataObjectPtr pIDOTmp;
+ hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp);
+
+ if (SUCCEEDED(hr))
+ hr = pIDOTmp->DAdvise(pFormatetc, advf, pAdvSink, pdwConnection);
+ }
+ return hr;
+}
+
+// IDataObject->DUnadvise
+
+STDMETHODIMP CAPNDataObject::DUnadvise( DWORD dwConnection )
+{
+ HRESULT hr = m_rIDataObjectOrg->DUnadvise( dwConnection );
+
+ if (RPC_E_WRONG_THREAD == hr)
+ {
+ IDataObjectPtr pIDOTmp;
+ hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp);
+
+ if (SUCCEEDED(hr))
+ hr = pIDOTmp->DUnadvise(dwConnection);
+ }
+ return hr;
+}
+
+// IDataObject->EnumDAdvise
+
+STDMETHODIMP CAPNDataObject::EnumDAdvise( IEnumSTATDATA ** ppenumAdvise )
+{
+ HRESULT hr = m_rIDataObjectOrg->EnumDAdvise(ppenumAdvise);
+
+ if (RPC_E_WRONG_THREAD == hr)
+ {
+ IDataObjectPtr pIDOTmp;
+ hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp);
+
+ if (SUCCEEDED(hr))
+ hr = pIDOTmp->EnumDAdvise(ppenumAdvise);
+ }
+ return hr;
+}
+
+// helper function
+
+HRESULT CAPNDataObject::MarshalIDataObjectIntoCurrentApartment( IDataObject** ppIDataObj )
+{
+ OSL_ASSERT(nullptr != ppIDataObj);
+
+ *ppIDataObj = nullptr;
+ HRESULT hr = E_FAIL;
+
+ if (m_hGlobal)
+ {
+ sal::systools::COMReference<IStream> pStm;
+ hr = CreateStreamOnHGlobal(m_hGlobal, KEEP_HGLOB_ON_RELEASE, &pStm);
+
+ OSL_ENSURE(E_INVALIDARG != hr, "CreateStreamOnHGlobal with invalid args called");
+
+ if (SUCCEEDED(hr))
+ {
+ hr = CoUnmarshalInterface(pStm, IID_PPV_ARGS(ppIDataObj));
+ OSL_ENSURE(CO_E_NOTINITIALIZED != hr, "COM is not initialized");
+ }
+ }
+ return hr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/APNDataObject.hxx b/vcl/win/dtrans/APNDataObject.hxx
new file mode 100644
index 0000000000..5de8ccfcc8
--- /dev/null
+++ b/vcl/win/dtrans/APNDataObject.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 .
+ */
+
+#pragma once
+
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <objidl.h>
+
+#include <systools/win32/comtools.hxx>
+
+/*
+ an APartment Neutral dataobject wrapper; this wrapper of an IDataObject
+ pointer can be used from any apartment without RPC_E_WRONG_THREAD
+ which normally occurs if an apartment tries to use an interface
+ pointer of another apartment; we use containment to hold the original
+ DataObject
+*/
+class CAPNDataObject : public IDataObject
+{
+public:
+ explicit CAPNDataObject(IDataObjectPtr rIDataObject);
+ virtual ~CAPNDataObject();
+
+ //IUnknown interface methods
+
+ STDMETHODIMP QueryInterface(REFIID iid, void** ppvObject) override;
+ STDMETHODIMP_( ULONG ) AddRef( ) override;
+ STDMETHODIMP_( ULONG ) Release( ) override;
+
+ // IDataObject interface methods
+
+ STDMETHODIMP GetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium ) override;
+ STDMETHODIMP GetDataHere( FORMATETC * pFormatetc, STGMEDIUM * pmedium ) override;
+ STDMETHODIMP QueryGetData( FORMATETC * pFormatetc ) override;
+ STDMETHODIMP GetCanonicalFormatEtc( FORMATETC * pFormatectIn, FORMATETC * pFormatetcOut ) override;
+ STDMETHODIMP SetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium, BOOL fRelease ) override;
+ STDMETHODIMP EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC** ppenumFormatetc ) override;
+ STDMETHODIMP DAdvise( FORMATETC * pFormatetc, DWORD advf, IAdviseSink * pAdvSink, DWORD* pdwConnection ) override;
+ STDMETHODIMP DUnadvise( DWORD dwConnection ) override;
+ STDMETHODIMP EnumDAdvise( IEnumSTATDATA** ppenumAdvise ) override;
+
+private:
+ HRESULT MarshalIDataObjectIntoCurrentApartment( IDataObject** ppIDataObj );
+
+private:
+ IDataObjectPtr m_rIDataObjectOrg;
+ HGLOBAL m_hGlobal;
+ LONG m_nRefCnt;
+
+// prevent copy and assignment
+private:
+ CAPNDataObject( const CAPNDataObject& theOther ) = delete;
+ CAPNDataObject& operator=( const CAPNDataObject& theOther ) = delete;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/DOTransferable.cxx b/vcl/win/dtrans/DOTransferable.cxx
new file mode 100644
index 0000000000..c2b54bf83a
--- /dev/null
+++ b/vcl/win/dtrans/DOTransferable.cxx
@@ -0,0 +1,577 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <rtl/process.h>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+
+#include "DOTransferable.hxx"
+#include "ImplHelper.hxx"
+#include "WinClip.hxx"
+#include "WinClipboard.hxx"
+#include "DTransHelper.hxx"
+#include "TxtCnvtHlp.hxx"
+#include "MimeAttrib.hxx"
+#include "FmtFilter.hxx"
+#include "Fetc.hxx"
+#include <com/sun/star/container/NoSuchElementException.hpp>
+#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+
+using namespace osl;
+using namespace cppu;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::io;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::container;
+
+namespace
+{
+ const Type CPPUTYPE_SEQINT8 = cppu::UnoType<Sequence< sal_Int8 >>::get();
+ const Type CPPUTYPE_OUSTRING = cppu::UnoType<OUString>::get();
+
+ bool isValidFlavor( const DataFlavor& aFlavor )
+ {
+ return ( aFlavor.MimeType.getLength( ) &&
+ ( ( aFlavor.DataType == CPPUTYPE_SEQINT8 ) ||
+ ( aFlavor.DataType == CPPUTYPE_OUSTRING ) ) );
+ }
+
+void clipDataToByteStream( CLIPFORMAT cf, STGMEDIUM stgmedium, CDOTransferable::ByteSequence_t& aByteSequence )
+{
+ CStgTransferHelper memTransferHelper;
+ LPSTREAM pStream = nullptr;
+
+ switch( stgmedium.tymed )
+ {
+ case TYMED_HGLOBAL:
+ memTransferHelper.init( stgmedium.hGlobal );
+ break;
+
+ case TYMED_MFPICT:
+ memTransferHelper.init( stgmedium.hMetaFilePict );
+ break;
+
+ case TYMED_ENHMF:
+ memTransferHelper.init( stgmedium.hEnhMetaFile );
+ break;
+
+ case TYMED_ISTREAM:
+ pStream = stgmedium.pstm;
+ break;
+
+ default:
+ throw UnsupportedFlavorException( );
+ break;
+ }
+
+ if (pStream)
+ {
+ // We have a stream, read from it.
+ STATSTG aStat;
+ HRESULT hr = pStream->Stat(&aStat, STATFLAG_NONAME);
+ if (FAILED(hr))
+ {
+ SAL_WARN("vcl.win.dtrans", "clipDataToByteStream: Stat() failed");
+ return;
+ }
+
+ size_t nMemSize = aStat.cbSize.QuadPart;
+ aByteSequence.realloc(nMemSize);
+ LARGE_INTEGER li;
+ li.QuadPart = 0;
+ hr = pStream->Seek(li, STREAM_SEEK_SET, nullptr);
+ if (FAILED(hr))
+ {
+ SAL_WARN("vcl.win.dtrans", "clipDataToByteStream: Seek() failed");
+ }
+
+ ULONG nRead = 0;
+ hr = pStream->Read(aByteSequence.getArray(), nMemSize, &nRead);
+ if (FAILED(hr))
+ {
+ SAL_WARN("vcl.win.dtrans", "clipDataToByteStream: Read() failed");
+ }
+ if (nRead < nMemSize)
+ {
+ SAL_WARN("vcl.win.dtrans", "clipDataToByteStream: Read() was partial");
+ }
+
+ return;
+ }
+
+ int nMemSize = memTransferHelper.memSize( cf );
+ aByteSequence.realloc( nMemSize );
+ memTransferHelper.read( aByteSequence.getArray( ), nMemSize );
+}
+
+OUString byteStreamToOUString( CDOTransferable::ByteSequence_t& aByteStream )
+{
+ sal_Int32 nWChars;
+ sal_Int32 nMemSize = aByteStream.getLength( );
+
+ // if there is a trailing L"\0" subtract 1 from length
+ // for unknown reason, the sequence may sometimes arrive empty
+ if ( aByteStream.getLength( ) > 1 &&
+ 0 == aByteStream[ aByteStream.getLength( ) - 2 ] &&
+ 0 == aByteStream[ aByteStream.getLength( ) - 1 ] )
+ nWChars = static_cast< sal_Int32 >( nMemSize / sizeof( sal_Unicode ) ) - 1;
+ else
+ nWChars = static_cast< sal_Int32 >( nMemSize / sizeof( sal_Unicode ) );
+
+ return OUString( reinterpret_cast< sal_Unicode* >( aByteStream.getArray( ) ), nWChars );
+}
+
+Any byteStreamToAny( CDOTransferable::ByteSequence_t& aByteStream, const Type& aRequestedDataType )
+{
+ Any aAny;
+
+ if ( aRequestedDataType == CPPUTYPE_OUSTRING )
+ {
+ OUString str = byteStreamToOUString( aByteStream );
+ if (str.isEmpty())
+ throw RuntimeException();
+ aAny <<= str;
+ }
+ else
+ aAny <<= aByteStream;
+
+ return aAny;
+}
+
+bool cmpFullMediaType(
+ const Reference< XMimeContentType >& xLhs, const Reference< XMimeContentType >& xRhs )
+{
+ return xLhs->getFullMediaType().equalsIgnoreAsciiCase( xRhs->getFullMediaType( ) );
+}
+
+bool cmpAllContentTypeParameter(
+ const Reference< XMimeContentType >& xLhs, const Reference< XMimeContentType >& xRhs )
+{
+ Sequence< OUString > xLhsFlavors = xLhs->getParameters( );
+ Sequence< OUString > xRhsFlavors = xRhs->getParameters( );
+ bool bRet = true;
+
+ try
+ {
+ if ( xLhsFlavors.getLength( ) == xRhsFlavors.getLength( ) )
+ {
+ OUString pLhs;
+ OUString pRhs;
+
+ for ( sal_Int32 i = 0; i < xLhsFlavors.getLength( ); i++ )
+ {
+ pLhs = xLhs->getParameterValue( xLhsFlavors[i] );
+ pRhs = xRhs->getParameterValue( xLhsFlavors[i] );
+
+ if ( !pLhs.equalsIgnoreAsciiCase( pRhs ) )
+ {
+ bRet = false;
+ break;
+ }
+ }
+ }
+ else
+ bRet = false;
+ }
+ catch( NoSuchElementException& )
+ {
+ bRet = false;
+ }
+ catch( IllegalArgumentException& )
+ {
+ bRet = false;
+ }
+
+ return bRet;
+}
+
+} // end namespace
+
+CDOTransferable::CDOTransferable(
+ const Reference< XComponentContext >& rxContext, IDataObjectPtr rDataObject ) :
+ m_rDataObject( rDataObject ),
+ m_xContext( rxContext ),
+ m_DataFormatTranslator( rxContext ),
+ m_bUnicodeRegistered( false ),
+ m_TxtFormatOnClipboard( CF_INVALID )
+{
+ initFlavorList();
+}
+
+CDOTransferable::CDOTransferable(
+ const Reference<XComponentContext>& rxContext,
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboard>& xClipboard,
+ const std::vector<sal_uInt32>& rFormats)
+ : m_xClipboard(xClipboard)
+ , m_xContext(rxContext)
+ , m_DataFormatTranslator(rxContext)
+ , m_bUnicodeRegistered(false)
+ , m_TxtFormatOnClipboard(CF_INVALID)
+{
+ initFlavorListFromFormatList(rFormats);
+}
+
+Any SAL_CALL CDOTransferable::getTransferData( const DataFlavor& aFlavor )
+{
+ OSL_ASSERT( isValidFlavor( aFlavor ) );
+
+ std::unique_lock aGuard( m_aMutex );
+
+ // convert dataflavor to formatetc
+
+ CFormatEtc fetc = m_DataFormatTranslator.getFormatEtcFromDataFlavor( aFlavor );
+ OSL_ASSERT( CF_INVALID != fetc.getClipformat() );
+
+ // get the data from clipboard in a byte stream
+
+ ByteSequence_t clipDataStream;
+
+ try
+ {
+ clipDataStream = getClipboardData( fetc );
+ }
+ catch( UnsupportedFlavorException& )
+ {
+ if ( CDataFormatTranslator::isUnicodeTextFormat( fetc.getClipformat( ) ) &&
+ m_bUnicodeRegistered )
+ {
+ OUString aUnicodeText = synthesizeUnicodeText( );
+ Any aAny( aUnicodeText );
+ return aAny;
+ }
+ // #i124085# CF_DIBV5 should not be possible, but keep for reading from the
+ // clipboard for being on the safe side
+ else if(CF_DIBV5 == fetc.getClipformat())
+ {
+ // #i123407# CF_DIBV5 has priority; if the try to fetch this failed,
+ // check CF_DIB availability as an alternative
+ fetc.setClipformat(CF_DIB);
+
+ clipDataStream = getClipboardData( fetc );
+ // pass UnsupportedFlavorException out, tried all possibilities
+ }
+ else
+ throw; // pass through exception
+ }
+
+ // return the data as any
+
+ return byteStreamToAny( clipDataStream, aFlavor.DataType );
+}
+
+// getTransferDataFlavors
+
+Sequence< DataFlavor > SAL_CALL CDOTransferable::getTransferDataFlavors( )
+{
+ return m_FlavorList;
+}
+
+// isDataFlavorSupported
+// returns true if we find a DataFlavor with the same MimeType and
+// DataType
+
+sal_Bool SAL_CALL CDOTransferable::isDataFlavorSupported( const DataFlavor& aFlavor )
+{
+ OSL_ASSERT( isValidFlavor( aFlavor ) );
+
+ for ( DataFlavor const & df : std::as_const(m_FlavorList) )
+ if ( compareDataFlavors( aFlavor, df ) )
+ return true;
+
+ return false;
+}
+
+// the list of dataflavors currently on the clipboard will be initialized
+// only once; if the client of this Transferable will hold a reference
+// to it and the underlying clipboard content changes, the client does
+// possible operate on an invalid list
+// if there is only text on the clipboard we will also offer unicode text
+// an synthesize this format on the fly if requested, to accomplish this
+// we save the first offered text format which we will later use for the
+// conversion
+
+void CDOTransferable::initFlavorList( )
+{
+ std::vector<sal_uInt32> aFormats;
+ sal::systools::COMReference<IEnumFORMATETC> pEnumFormatEtc;
+ HRESULT hr = m_rDataObject->EnumFormatEtc( DATADIR_GET, &pEnumFormatEtc );
+ if ( SUCCEEDED( hr ) )
+ {
+ pEnumFormatEtc->Reset( );
+
+ FORMATETC fetc;
+ while ( S_OK == pEnumFormatEtc->Next( 1, &fetc, nullptr ) )
+ {
+ aFormats.push_back(fetc.cfFormat);
+ // see MSDN IEnumFORMATETC
+ CoTaskMemFree( fetc.ptd );
+ }
+ initFlavorListFromFormatList(aFormats);
+ }
+}
+
+void CDOTransferable::initFlavorListFromFormatList(const std::vector<sal_uInt32>& rFormats)
+{
+ for (sal_uInt32 cfFormat : rFormats)
+ {
+ // we use locales only to determine the
+ // charset if there is text on the clipboard
+ // we don't offer this format
+ if (CF_LOCALE == cfFormat)
+ continue;
+
+ // if text or oemtext is offered we pretend to have unicode text
+ if (CDataFormatTranslator::isTextFormat(cfFormat))
+ {
+ if (!m_bUnicodeRegistered)
+ {
+ m_TxtFormatOnClipboard = cfFormat;
+ m_bUnicodeRegistered = true;
+
+ // register unicode text as format
+ addSupportedFlavor(formatEtcToDataFlavor(CF_UNICODETEXT));
+ }
+ }
+ else
+ addSupportedFlavor(formatEtcToDataFlavor(cfFormat));
+ }
+}
+
+inline
+void CDOTransferable::addSupportedFlavor( const DataFlavor& aFlavor )
+{
+ // we ignore all formats that couldn't be translated
+ if ( aFlavor.MimeType.getLength( ) )
+ {
+ OSL_ASSERT( isValidFlavor( aFlavor ) );
+
+ m_FlavorList.realloc( m_FlavorList.getLength( ) + 1 );
+ m_FlavorList.getArray()[m_FlavorList.getLength( ) - 1] = aFlavor;
+ }
+}
+
+DataFlavor CDOTransferable::formatEtcToDataFlavor(sal_uInt32 cfFormat)
+{
+ return m_DataFormatTranslator.getDataFlavorFromFormatEtc(cfFormat);
+}
+
+// returns the current locale on clipboard; if there is no locale on
+// clipboard the function returns the current thread locale
+
+LCID CDOTransferable::getLocaleFromClipboard( )
+{
+ LCID lcid = GetThreadLocale( );
+
+ try
+ {
+ CFormatEtc fetc = CDataFormatTranslator::getFormatEtcForClipformat( CF_LOCALE );
+ ByteSequence_t aLCIDSeq = getClipboardData( fetc );
+ lcid = *reinterpret_cast<LCID*>( aLCIDSeq.getArray( ) );
+
+ // because of a Win95/98 Bug; there the high word
+ // of a locale has the same value as the
+ // low word e.g. 0x07040704 that's not right
+ // correct is 0x00000704
+ lcid &= 0x0000FFFF;
+ }
+ catch(...)
+ {
+ // we take the default locale
+ }
+
+ return lcid;
+}
+
+void CDOTransferable::tryToGetIDataObjectIfAbsent()
+{
+ if (!m_rDataObject.is())
+ {
+ auto xClipboard = m_xClipboard.get(); // holding the reference while we get the object
+ if (CWinClipboard* pWinClipboard = dynamic_cast<CWinClipboard*>(xClipboard.get()))
+ {
+ m_rDataObject = pWinClipboard->getIDataObject();
+ }
+ }
+}
+
+// I think it's not necessary to call ReleaseStgMedium
+// in case of failures because nothing should have been
+// allocated etc.
+
+CDOTransferable::ByteSequence_t CDOTransferable::getClipboardData( CFormatEtc& aFormatEtc )
+{
+ STGMEDIUM stgmedium;
+ tryToGetIDataObjectIfAbsent();
+ if (!m_rDataObject.is()) // Maybe we are shutting down, and clipboard is already destroyed?
+ throw RuntimeException();
+ HRESULT hr = m_rDataObject->GetData( aFormatEtc, &stgmedium );
+
+ // in case of failure to get a WMF metafile handle, try to get a memory block
+ if( FAILED( hr ) &&
+ ( CF_METAFILEPICT == aFormatEtc.getClipformat() ) &&
+ ( TYMED_MFPICT == aFormatEtc.getTymed() ) )
+ {
+ CFormatEtc aTempFormat( aFormatEtc );
+ aTempFormat.setTymed( TYMED_HGLOBAL );
+ hr = m_rDataObject->GetData( aTempFormat, &stgmedium );
+ }
+
+ if (FAILED(hr) && aFormatEtc.getTymed() == TYMED_HGLOBAL)
+ {
+ // Handle type is not memory, try stream.
+ CFormatEtc aTempFormat(aFormatEtc);
+ aTempFormat.setTymed(TYMED_ISTREAM);
+ hr = m_rDataObject->GetData(aTempFormat, &stgmedium);
+ }
+
+ if ( FAILED( hr ) )
+ {
+ OSL_ASSERT( (hr != E_INVALIDARG) &&
+ (hr != DV_E_DVASPECT) &&
+ (hr != DV_E_LINDEX) &&
+ (hr != DV_E_TYMED) );
+
+ if ( DV_E_FORMATETC == hr )
+ throw UnsupportedFlavorException( );
+ else if ( STG_E_MEDIUMFULL == hr )
+ throw IOException( );
+ else
+ throw RuntimeException( );
+ }
+
+ ByteSequence_t byteStream;
+
+ try
+ {
+ if ( CF_ENHMETAFILE == aFormatEtc.getClipformat() )
+ byteStream = WinENHMFPictToOOMFPict( stgmedium.hEnhMetaFile );
+ else if (CF_HDROP == aFormatEtc.getClipformat())
+ byteStream = CF_HDROPToFileList(stgmedium.hGlobal);
+ else if ( CF_BITMAP == aFormatEtc.getClipformat() )
+ {
+ byteStream = WinBITMAPToOOBMP(stgmedium.hBitmap);
+ if( aFormatEtc.getTymed() == TYMED_GDI &&
+ ! stgmedium.pUnkForRelease )
+ {
+ DeleteObject(stgmedium.hBitmap);
+ }
+ }
+ else
+ {
+ clipDataToByteStream( aFormatEtc.getClipformat( ), stgmedium, byteStream );
+
+ // format conversion if necessary
+ // #i124085# DIBV5 should not happen currently, but keep as a hint here
+ if(CF_DIBV5 == aFormatEtc.getClipformat() || CF_DIB == aFormatEtc.getClipformat())
+ {
+ byteStream = WinDIBToOOBMP(byteStream);
+ }
+ else if(CF_METAFILEPICT == aFormatEtc.getClipformat())
+ {
+ byteStream = WinMFPictToOOMFPict(byteStream);
+ }
+ }
+
+ ReleaseStgMedium( &stgmedium );
+ }
+ catch( CStgTransferHelper::CStgTransferException& )
+ {
+ ReleaseStgMedium( &stgmedium );
+ throw IOException( );
+ }
+
+ return byteStream;
+}
+
+OUString CDOTransferable::synthesizeUnicodeText( )
+{
+ ByteSequence_t aTextSequence;
+ CFormatEtc fetc;
+ LCID lcid = getLocaleFromClipboard( );
+ sal_uInt32 cpForTxtCnvt = 0;
+
+ if ( CF_TEXT == m_TxtFormatOnClipboard )
+ {
+ fetc = CDataFormatTranslator::getFormatEtcForClipformat( CF_TEXT );
+ aTextSequence = getClipboardData( fetc );
+
+ // determine the codepage used for text conversion
+ cpForTxtCnvt = getWinCPFromLocaleId( lcid, LOCALE_IDEFAULTANSICODEPAGE ).toInt32( );
+ }
+ else if ( CF_OEMTEXT == m_TxtFormatOnClipboard )
+ {
+ fetc = CDataFormatTranslator::getFormatEtcForClipformat( CF_OEMTEXT );
+ aTextSequence = getClipboardData( fetc );
+
+ // determine the codepage used for text conversion
+ cpForTxtCnvt = getWinCPFromLocaleId( lcid, LOCALE_IDEFAULTCODEPAGE ).toInt32( );
+ }
+ else
+ OSL_ASSERT( false );
+
+ CStgTransferHelper stgTransferHelper;
+
+ // convert the text
+ MultiByteToWideCharEx( cpForTxtCnvt,
+ reinterpret_cast<char*>( aTextSequence.getArray( ) ),
+ -1,
+ stgTransferHelper,
+ false);
+
+ CRawHGlobalPtr ptrHGlob(stgTransferHelper);
+ sal_Unicode* pWChar = static_cast<sal_Unicode*>(ptrHGlob.GetMemPtr());
+
+ return OUString(pWChar);
+}
+
+bool CDOTransferable::compareDataFlavors(
+ const DataFlavor& lhs, const DataFlavor& rhs )
+{
+ if ( !m_rXMimeCntFactory.is( ) )
+ {
+ m_rXMimeCntFactory = MimeContentTypeFactory::create( m_xContext );
+ }
+
+ bool bRet = false;
+
+ try
+ {
+ Reference< XMimeContentType > xLhs( m_rXMimeCntFactory->createMimeContentType( lhs.MimeType ) );
+ Reference< XMimeContentType > xRhs( m_rXMimeCntFactory->createMimeContentType( rhs.MimeType ) );
+
+ if ( cmpFullMediaType( xLhs, xRhs ) )
+ {
+ bRet = cmpAllContentTypeParameter( xLhs, xRhs );
+ }
+ }
+ catch( IllegalArgumentException& )
+ {
+ OSL_FAIL( "Invalid content type detected" );
+ bRet = false;
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/DOTransferable.hxx b/vcl/win/dtrans/DOTransferable.hxx
new file mode 100644
index 0000000000..1824c7b448
--- /dev/null
+++ b/vcl/win/dtrans/DOTransferable.hxx
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <com/sun/star/datatransfer/XTransferable.hpp>
+
+#include <cppuhelper/implbase.hxx>
+#include "DataFmtTransl.hxx"
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/XMimeContentType.hpp>
+#include <com/sun/star/datatransfer/XSystemTransferable.hpp>
+#include <cppuhelper/weakref.hxx>
+
+#include <systools/win32/comtools.hxx>
+
+#include <mutex>
+#include <vector>
+
+// forward
+class CFormatEtc;
+
+class CDOTransferable : public ::cppu::WeakImplHelper<
+ css::datatransfer::XTransferable>
+{
+public:
+ typedef css::uno::Sequence< sal_Int8 > ByteSequence_t;
+
+ // XTransferable
+
+ virtual css::uno::Any SAL_CALL getTransferData( const css::datatransfer::DataFlavor& aFlavor ) override;
+
+ virtual css::uno::Sequence< css::datatransfer::DataFlavor > SAL_CALL getTransferDataFlavors( ) override;
+
+ virtual sal_Bool SAL_CALL isDataFlavorSupported( const css::datatransfer::DataFlavor& aFlavor ) override;
+
+ explicit CDOTransferable(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboard>& xClipboard,
+ const std::vector<sal_uInt32>& rFormats);
+
+ explicit CDOTransferable(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ IDataObjectPtr rDataObject );
+
+private:
+ // some helper functions
+
+ void initFlavorList( );
+ void initFlavorListFromFormatList(const std::vector<sal_uInt32>& rFormats);
+
+ void addSupportedFlavor( const css::datatransfer::DataFlavor& aFlavor );
+ css::datatransfer::DataFlavor formatEtcToDataFlavor(sal_uInt32 cfFormat);
+
+ void tryToGetIDataObjectIfAbsent();
+ ByteSequence_t getClipboardData( CFormatEtc& aFormatEtc );
+ OUString synthesizeUnicodeText( );
+
+ LCID getLocaleFromClipboard( );
+
+ bool compareDataFlavors( const css::datatransfer::DataFlavor& lhs,
+ const css::datatransfer::DataFlavor& rhs );
+
+private:
+ css::uno::WeakReference<css::datatransfer::clipboard::XClipboard> m_xClipboard;
+ IDataObjectPtr m_rDataObject;
+ css::uno::Sequence< css::datatransfer::DataFlavor > m_FlavorList;
+ const css::uno::Reference< css::uno::XComponentContext > m_xContext;
+ CDataFormatTranslator m_DataFormatTranslator;
+ css::uno::Reference< css::datatransfer::XMimeContentTypeFactory > m_rXMimeCntFactory;
+ std::mutex m_aMutex;
+ bool m_bUnicodeRegistered;
+ CLIPFORMAT m_TxtFormatOnClipboard;
+
+// non supported operations
+private:
+ CDOTransferable( const CDOTransferable& );
+ CDOTransferable& operator=( const CDOTransferable& );
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/DTransHelper.cxx b/vcl/win/dtrans/DTransHelper.cxx
new file mode 100644
index 0000000000..66d18f9fb5
--- /dev/null
+++ b/vcl/win/dtrans/DTransHelper.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 <rtl/ustring.h>
+#include <osl/diagnose.h>
+#include "DTransHelper.hxx"
+
+// implementation
+
+CStgTransferHelper::CStgTransferHelper( bool bAutoInit,
+ HGLOBAL hGlob,
+ bool bDelStgOnRelease ) :
+ m_lpStream( nullptr ),
+ m_bDelStgOnRelease( bDelStgOnRelease )
+{
+ if ( bAutoInit )
+ init( hGlob, m_bDelStgOnRelease );
+}
+
+// dtor
+
+CStgTransferHelper::~CStgTransferHelper( )
+{
+ if ( m_lpStream )
+ m_lpStream->Release( );
+}
+
+// TransferData into the
+
+void CStgTransferHelper::write( const void* lpData, ULONG cb, ULONG* cbWritten )
+{
+ HRESULT hr = E_FAIL;
+
+ if ( m_lpStream )
+ hr = m_lpStream->Write( lpData, cb, cbWritten );
+
+ if ( FAILED( hr ) )
+ throw CStgTransferException( hr );
+
+#if OSL_DEBUG_LEVEL > 0
+ HGLOBAL hGlob;
+ hr = GetHGlobalFromStream( m_lpStream, &hGlob );
+ OSL_ASSERT( SUCCEEDED( hr ) );
+
+ /*DWORD dwSize =*/ GlobalSize( hGlob );
+ /*LPVOID lpdbgData =*/ GlobalLock( hGlob );
+ GlobalUnlock( hGlob );
+#endif
+}
+
+// read
+
+void CStgTransferHelper::read( LPVOID pv, ULONG cb, ULONG* pcbRead )
+{
+ HRESULT hr = E_FAIL;
+
+ if ( m_lpStream )
+ hr = m_lpStream->Read( pv, cb , pcbRead );
+
+ if ( FAILED( hr ) )
+ throw CStgTransferException( hr );
+}
+
+// GetHGlobal
+
+HGLOBAL CStgTransferHelper::getHGlobal( ) const
+{
+ OSL_ASSERT( m_lpStream );
+
+ HGLOBAL hGlob = nullptr;
+
+ if ( m_lpStream )
+ {
+ HRESULT hr = GetHGlobalFromStream( m_lpStream, &hGlob );
+ if ( FAILED( hr ) )
+ hGlob = nullptr;
+ }
+
+ return hGlob;
+}
+
+// getIStream
+
+void CStgTransferHelper::getIStream( LPSTREAM* ppStream )
+{
+ OSL_ASSERT( ppStream );
+ *ppStream = m_lpStream;
+ if ( *ppStream )
+ static_cast< LPUNKNOWN >( *ppStream )->AddRef( );
+}
+
+// Init
+
+void CStgTransferHelper::init( SIZE_T newSize,
+ sal_uInt32 uiFlags,
+ bool bDelStgOnRelease )
+{
+ cleanup( );
+
+ m_bDelStgOnRelease = bDelStgOnRelease;
+
+ HGLOBAL hGlob = GlobalAlloc( uiFlags, newSize );
+ if ( nullptr == hGlob )
+ throw CStgTransferException( STG_E_MEDIUMFULL );
+
+ HRESULT hr = CreateStreamOnHGlobal( hGlob, m_bDelStgOnRelease, &m_lpStream );
+ if ( FAILED( hr ) )
+ {
+ GlobalFree( hGlob );
+ m_lpStream = nullptr;
+ throw CStgTransferException( hr );
+ }
+
+#if OSL_DEBUG_LEVEL > 0
+ STATSTG statstg;
+ hr = m_lpStream->Stat( &statstg, STATFLAG_DEFAULT );
+ OSL_ASSERT( SUCCEEDED( hr ) );
+#endif
+}
+
+// Init
+
+void CStgTransferHelper::init( HGLOBAL hGlob,
+ bool bDelStgOnRelease )
+{
+ cleanup( );
+
+ m_bDelStgOnRelease = bDelStgOnRelease;
+
+ HRESULT hr = CreateStreamOnHGlobal( hGlob, m_bDelStgOnRelease, &m_lpStream );
+ if ( FAILED( hr ) )
+ throw CStgTransferException( hr );
+}
+
+// free the global memory and invalidate the stream pointer
+
+void CStgTransferHelper::cleanup( )
+{
+ if ( m_lpStream && !m_bDelStgOnRelease )
+ {
+ HGLOBAL hGlob;
+ GetHGlobalFromStream( m_lpStream, &hGlob );
+ GlobalFree( hGlob );
+ }
+
+ if ( m_lpStream )
+ {
+ m_lpStream->Release( );
+ m_lpStream = nullptr;
+ }
+}
+
+// return the size of memory we point to
+
+sal_uInt32 CStgTransferHelper::memSize( CLIPFORMAT cf ) const
+{
+ DWORD dwSize = 0;
+
+ if ( nullptr != m_lpStream )
+ {
+ HGLOBAL hGlob;
+ GetHGlobalFromStream( m_lpStream, &hGlob );
+
+ if ( CF_TEXT == cf || RegisterClipboardFormatW( L"HTML Format" ) == cf )
+ {
+ char* pText = static_cast< char* >( GlobalLock( hGlob ) );
+ if ( pText )
+ {
+ dwSize = strlen(pText) + 1; // strlen + trailing '\0'
+ GlobalUnlock( hGlob );
+ }
+ }
+ else if ( CF_UNICODETEXT == cf )
+ {
+ sal_Unicode* pText = static_cast< sal_Unicode* >( GlobalLock( hGlob ) );
+ if ( pText )
+ {
+ dwSize = rtl_ustr_getLength( pText ) * sizeof( sal_Unicode );
+ GlobalUnlock( hGlob );
+ }
+ }
+ else
+ dwSize = GlobalSize( hGlob );
+ }
+
+ return dwSize;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/DTransHelper.hxx b/vcl/win/dtrans/DTransHelper.hxx
new file mode 100644
index 0000000000..d8ab3349cb
--- /dev/null
+++ b/vcl/win/dtrans/DTransHelper.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <objidl.h>
+#include "WinClip.hxx"
+
+#define AUTO_INIT true
+
+// a helper class to manage a global memory area, the clients can write
+// into the global memory area and extract the handle to the global mem
+// note: not thread-safe
+
+class CStgTransferHelper
+{
+public:
+ // will be thrown in case of failures
+ class CStgTransferException
+ {
+ public:
+ HRESULT m_hr;
+ explicit CStgTransferException( HRESULT hr ) : m_hr( hr ) {};
+ };
+
+public:
+ CStgTransferHelper(
+ bool bAutoInit = false,
+ HGLOBAL hGlob = nullptr,
+ bool bDelStgOnRelease = false );
+
+ ~CStgTransferHelper( );
+
+ void write( const void* lpData, ULONG cb, ULONG* cbWritten = nullptr );
+ void read( LPVOID pv, ULONG cb, ULONG* pcbRead = nullptr );
+
+ HGLOBAL getHGlobal( ) const;
+ void getIStream( LPSTREAM* ppStream );
+
+ void init(
+ SIZE_T newSize,
+ sal_uInt32 uiFlags = GHND,
+ bool bDelStgOnRelease = false );
+
+ void init(
+ HGLOBAL hGlob,
+ bool bDelStgOnRelease = false );
+
+ // returns the size of the managed memory
+ sal_uInt32 memSize( CLIPFORMAT cf = CF_INVALID ) const;
+
+ // free the global memory and necessary
+ // release the internal stream pointer
+ void cleanup( );
+
+private:
+ LPSTREAM m_lpStream;
+ bool m_bDelStgOnRelease;
+
+private:
+ CStgTransferHelper( const CStgTransferHelper& );
+ CStgTransferHelper& operator=( const CStgTransferHelper& );
+};
+
+// something like an auto-pointer - allows access to the memory belonging
+// to a HGLOBAL and automatically unlocks a global memory at destruction
+// time
+
+class CRawHGlobalPtr
+{
+public:
+
+ // ctor
+
+ explicit CRawHGlobalPtr( HGLOBAL hGlob ) :
+ m_hGlob( hGlob ),
+ m_bIsLocked( false ),
+ m_pGlobMem( nullptr )
+ {
+ }
+
+ // ctor
+
+ explicit CRawHGlobalPtr( const CStgTransferHelper& theHGlobalHelper ) :
+ m_hGlob( theHGlobalHelper.getHGlobal( ) ),
+ m_bIsLocked( false ),
+ m_pGlobMem( nullptr )
+ {
+ }
+
+ // dtor
+
+ ~CRawHGlobalPtr( )
+ {
+ if ( m_bIsLocked )
+ GlobalUnlock( m_hGlob );
+ }
+
+ // lock the global memory (initializes a
+ // pointer to this memory)
+
+ BOOL Lock( )
+ {
+ if ( !m_bIsLocked && ( nullptr != m_hGlob ) )
+ {
+ m_pGlobMem = GlobalLock( m_hGlob );
+ m_bIsLocked = ( nullptr != m_pGlobMem );
+ }
+
+ return m_bIsLocked;
+ }
+
+ // unlock the global memory (invalidates the
+ // pointer to this memory)
+
+ BOOL Unlock( )
+ {
+ GlobalUnlock( m_hGlob );
+ m_bIsLocked = false;
+ m_pGlobMem = nullptr;
+
+ return ( NO_ERROR == GetLastError( ) );
+ }
+
+ // locks the global memory and returns a
+ // pointer to this memory
+
+ LPVOID GetMemPtr( )
+ {
+ Lock( );
+ return m_pGlobMem;
+ }
+
+ // size of mem we point to
+
+ int MemSize( ) const
+ {
+ return GlobalSize( m_hGlob );
+ }
+
+private:
+ HGLOBAL m_hGlob;
+ bool m_bIsLocked;
+ LPVOID m_pGlobMem;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/DataFmtTransl.cxx b/vcl/win/dtrans/DataFmtTransl.cxx
new file mode 100644
index 0000000000..5daf0b453f
--- /dev/null
+++ b/vcl/win/dtrans/DataFmtTransl.cxx
@@ -0,0 +1,252 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "DataFmtTransl.hxx"
+#include <rtl/string.hxx>
+#include <osl/diagnose.h>
+#include <rtl/tencinfo.h>
+#include "ImplHelper.hxx"
+#include "WinClip.hxx"
+#include "MimeAttrib.hxx"
+#include "DTransHelper.hxx"
+#include <rtl/string.h>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include "Fetc.hxx"
+#include <com/sun/star/datatransfer/DataFormatTranslator.hpp>
+
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <shlobj.h>
+
+using namespace com::sun::star::uno;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::lang;
+
+const Type CPPUTYPE_SALINT32 = cppu::UnoType<sal_Int32>::get();
+const Type CPPUTYPE_SALINT8 = cppu::UnoType<sal_Int8>::get();
+const Type CPPUTYPE_OUSTRING = cppu::UnoType<OUString>::get();
+const Type CPPUTYPE_SEQSALINT8 = cppu::UnoType<Sequence< sal_Int8>>::get();
+const sal_Int32 MAX_CLIPFORMAT_NAME = 256;
+
+CDataFormatTranslator::CDataFormatTranslator( const Reference< XComponentContext >& rxContext )
+{
+ m_XDataFormatTranslator = DataFormatTranslator::create( rxContext );
+}
+
+CFormatEtc CDataFormatTranslator::getFormatEtcFromDataFlavor( const DataFlavor& aDataFlavor ) const
+{
+ sal_Int32 cf = CF_INVALID;
+
+ try
+ {
+ if( m_XDataFormatTranslator.is( ) )
+ {
+ Any aFormat = m_XDataFormatTranslator->getSystemDataTypeFromDataFlavor( aDataFlavor );
+
+ if ( aFormat.hasValue( ) )
+ {
+ if ( aFormat.getValueType( ) == CPPUTYPE_SALINT32 )
+ {
+ aFormat >>= cf;
+ OSL_ENSURE( CF_INVALID != cf, "Invalid Clipboard format delivered" );
+ }
+ else if ( aFormat.getValueType( ) == CPPUTYPE_OUSTRING )
+ {
+ OUString aClipFmtName;
+ aFormat >>= aClipFmtName;
+
+ OSL_ASSERT( aClipFmtName.getLength( ) );
+ cf = RegisterClipboardFormatW( o3tl::toW(aClipFmtName.getStr( )) );
+
+ OSL_ENSURE( CF_INVALID != cf, "RegisterClipboardFormat failed" );
+ }
+ else
+ OSL_FAIL( "Wrong Any-Type detected" );
+ }
+ }
+ }
+ catch( ... )
+ {
+ OSL_FAIL( "Unexpected error" );
+ }
+
+ return sal::static_int_cast<CFormatEtc>(getFormatEtcForClipformat( sal::static_int_cast<CLIPFORMAT>(cf) ));
+}
+
+DataFlavor CDataFormatTranslator::getDataFlavorFromFormatEtc(sal_uInt32 cfFormat, LCID lcid) const
+{
+ DataFlavor aFlavor;
+
+ try
+ {
+ CLIPFORMAT aClipformat = cfFormat;
+
+ Any aAny;
+ aAny <<= static_cast< sal_Int32 >( aClipformat );
+
+ if ( isOemOrAnsiTextFormat( aClipformat ) )
+ {
+ aFlavor.MimeType = "text/plain;charset=";
+ aFlavor.MimeType += getTextCharsetFromLCID( lcid, aClipformat );
+
+ aFlavor.HumanPresentableName = "OEM/ANSI Text";
+ aFlavor.DataType = CPPUTYPE_SEQSALINT8;
+ }
+ else if ( CF_INVALID != aClipformat )
+ {
+ if ( m_XDataFormatTranslator.is( ) )
+ {
+ aFlavor = m_XDataFormatTranslator->getDataFlavorFromSystemDataType( aAny );
+
+ if ( !aFlavor.MimeType.getLength( ) )
+ {
+ // lookup of DataFlavor from clipboard format id
+ // failed, so we try to resolve via clipboard
+ // format name
+ OUString clipFormatName = getClipboardFormatName( aClipformat );
+
+ // if we could not get a clipboard format name an
+ // error must have occurred or it is a standard
+ // clipboard format that we don't translate, e.g.
+ // CF_BITMAP (the office only uses CF_DIB)
+ if ( clipFormatName.getLength( ) )
+ {
+ aAny <<= clipFormatName;
+ aFlavor = m_XDataFormatTranslator->getDataFlavorFromSystemDataType( aAny );
+ }
+ }
+ }
+ }
+ }
+ catch( ... )
+ {
+ OSL_FAIL( "Unexpected error" );
+ }
+
+ return aFlavor;
+}
+
+CFormatEtc CDataFormatTranslator::getFormatEtcForClipformatName( const OUString& aClipFmtName )
+{
+ // check parameter
+ if ( !aClipFmtName.getLength( ) )
+ return CFormatEtc( CF_INVALID );
+
+ CLIPFORMAT cf = sal::static_int_cast<CLIPFORMAT>(RegisterClipboardFormatW( o3tl::toW(aClipFmtName.getStr( )) ));
+ return getFormatEtcForClipformat( cf );
+}
+
+OUString CDataFormatTranslator::getClipboardFormatName( CLIPFORMAT aClipformat )
+{
+ OSL_PRECOND( CF_INVALID != aClipformat, "Invalid clipboard format" );
+
+ sal_Unicode wBuff[ MAX_CLIPFORMAT_NAME + 1 ]; // Null terminator isn't counted, apparently.
+ sal_Int32 nLen = GetClipboardFormatNameW( aClipformat, o3tl::toW(wBuff), MAX_CLIPFORMAT_NAME );
+
+ return OUString( wBuff, nLen );
+}
+
+CFormatEtc CDataFormatTranslator::getFormatEtcForClipformat( CLIPFORMAT cf )
+{
+ CFormatEtc fetc( cf, TYMED_NULL, nullptr, DVASPECT_CONTENT );
+
+ switch( cf )
+ {
+ case CF_METAFILEPICT:
+ fetc.setTymed( TYMED_MFPICT );
+ break;
+
+ case CF_ENHMETAFILE:
+ fetc.setTymed( TYMED_ENHMF );
+ break;
+
+ default:
+ fetc.setTymed( TYMED_HGLOBAL /*| TYMED_ISTREAM*/ );
+ }
+
+ /*
+ hack: in order to paste urls copied by Internet Explorer
+ with "copy link" we set the lindex member to 0
+ but if we really want to support CFSTR_FILECONTENT and
+ the accompany format CFSTR_FILEDESCRIPTOR (FileGroupDescriptor)
+ the client of the clipboard service has to provide a id
+ of which FileContents it wants to paste
+ see MSDN: "Handling Shell Data Transfer Scenarios"
+ */
+ if ( cf == RegisterClipboardFormat( CFSTR_FILECONTENTS ) )
+ fetc.setLindex( 0 );
+
+ return fetc;
+}
+
+bool CDataFormatTranslator::isOemOrAnsiTextFormat( CLIPFORMAT cf )
+{
+ return ( (cf == CF_TEXT) || (cf == CF_OEMTEXT) );
+}
+
+bool CDataFormatTranslator::isUnicodeTextFormat( CLIPFORMAT cf )
+{
+ return ( cf == CF_UNICODETEXT );
+}
+
+bool CDataFormatTranslator::isTextFormat( CLIPFORMAT cf )
+{
+ return ( isOemOrAnsiTextFormat( cf ) || isUnicodeTextFormat( cf ) );
+}
+
+bool CDataFormatTranslator::isHTMLFormat( CLIPFORMAT cf )
+{
+ OUString clipFormatName = getClipboardFormatName( cf );
+ return ( clipFormatName == "HTML Format" );
+}
+
+bool CDataFormatTranslator::isTextHtmlFormat( CLIPFORMAT cf )
+{
+ OUString clipFormatName = getClipboardFormatName( cf );
+ return clipFormatName.equalsIgnoreAsciiCase( "HTML (HyperText Markup Language)" );
+}
+
+OUString CDataFormatTranslator::getTextCharsetFromLCID( LCID lcid, CLIPFORMAT aClipformat )
+{
+ OSL_ASSERT( isOemOrAnsiTextFormat( aClipformat ) );
+
+ OUString charset;
+ if ( CF_TEXT == aClipformat )
+ {
+ charset = getMimeCharsetFromLocaleId(
+ lcid,
+ LOCALE_IDEFAULTANSICODEPAGE,
+ PRE_WINDOWS_CODEPAGE );
+ }
+ else if ( CF_OEMTEXT == aClipformat )
+ {
+ charset = getMimeCharsetFromLocaleId(
+ lcid,
+ LOCALE_IDEFAULTCODEPAGE,
+ PRE_OEM_CODEPAGE );
+ }
+ else // CF_UNICODE
+ OSL_ASSERT( false );
+
+ return charset;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/DataFmtTransl.hxx b/vcl/win/dtrans/DataFmtTransl.hxx
new file mode 100644
index 0000000000..e003f2538b
--- /dev/null
+++ b/vcl/win/dtrans/DataFmtTransl.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 .
+ */
+
+#pragma once
+
+#include <com/sun/star/datatransfer/XDataFormatTranslator.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <sal/types.h>
+#include <rtl/ustring.hxx>
+
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <objidl.h>
+
+// declaration
+
+class CFormatEtc;
+
+class CDataFormatTranslator
+{
+public:
+ explicit CDataFormatTranslator( const css::uno::Reference< css::uno::XComponentContext >& rxContext );
+
+ CFormatEtc getFormatEtcFromDataFlavor( const css::datatransfer::DataFlavor& aDataFlavor ) const;
+ css::datatransfer::DataFlavor getDataFlavorFromFormatEtc(
+ sal_uInt32 cfFormat, LCID lcid = GetThreadLocale()) const;
+
+ static CFormatEtc getFormatEtcForClipformat( CLIPFORMAT cf );
+ static CFormatEtc getFormatEtcForClipformatName( const OUString& aClipFmtName );
+ static OUString getClipboardFormatName( CLIPFORMAT aClipformat );
+
+ static bool isHTMLFormat( CLIPFORMAT cf );
+ static bool isTextHtmlFormat( CLIPFORMAT cf );
+ static bool isOemOrAnsiTextFormat( CLIPFORMAT cf );
+ static bool isUnicodeTextFormat( CLIPFORMAT cf );
+ static bool isTextFormat( CLIPFORMAT cf );
+
+private:
+ static OUString getTextCharsetFromLCID( LCID lcid, CLIPFORMAT aClipformat );
+
+private:
+ css::uno::Reference< css::datatransfer::XDataFormatTranslator > m_XDataFormatTranslator;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/DtObjFactory.cxx b/vcl/win/dtrans/DtObjFactory.cxx
new file mode 100644
index 0000000000..8fe03789b1
--- /dev/null
+++ b/vcl/win/dtrans/DtObjFactory.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "DtObjFactory.hxx"
+#include "XTDataObject.hxx"
+
+using namespace com::sun::star::uno;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::lang;
+
+IDataObjectPtr
+CDTransObjFactory::createDataObjFromTransferable(const Reference<XComponentContext>& rxContext,
+ const Reference<XTransferable>& refXTransferable)
+{
+ return (IDataObjectPtr(new CXTDataObject(rxContext, refXTransferable)));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/DtObjFactory.hxx b/vcl/win/dtrans/DtObjFactory.hxx
new file mode 100644
index 0000000000..b5d5f50075
--- /dev/null
+++ b/vcl/win/dtrans/DtObjFactory.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 <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <systools/win32/comtools.hxx>
+
+namespace CDTransObjFactory
+{
+ IDataObjectPtr createDataObjFromTransferable( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ const css::uno::Reference< css::datatransfer::XTransferable >& refXTransferable );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/Fetc.cxx b/vcl/win/dtrans/Fetc.cxx
new file mode 100644
index 0000000000..9bcb2f609f
--- /dev/null
+++ b/vcl/win/dtrans/Fetc.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 "Fetc.hxx"
+#include "ImplHelper.hxx"
+
+CFormatEtc::CFormatEtc( )
+{
+ m_FormatEtc.cfFormat = 0;
+ m_FormatEtc.ptd = nullptr;
+ m_FormatEtc.dwAspect = 0;
+ m_FormatEtc.lindex = -1;
+ m_FormatEtc.tymed = TYMED_NULL;
+}
+
+// transfer of ownership
+
+CFormatEtc::CFormatEtc( const FORMATETC& aFormatEtc )
+{
+ CopyFormatEtc( &m_FormatEtc, &const_cast< FORMATETC& >( aFormatEtc ) );
+}
+
+CFormatEtc::~CFormatEtc( )
+{
+ DeleteTargetDevice( m_FormatEtc.ptd );
+}
+
+CFormatEtc::CFormatEtc( CLIPFORMAT cf, DWORD tymed, DVTARGETDEVICE* ptd, DWORD dwAspect, LONG lindex )
+{
+ m_FormatEtc.cfFormat = cf;
+ m_FormatEtc.ptd = CopyTargetDevice( ptd );
+ m_FormatEtc.dwAspect = dwAspect;
+ m_FormatEtc.lindex = lindex;
+ m_FormatEtc.tymed = tymed;
+}
+
+CFormatEtc::CFormatEtc( const CFormatEtc& theOther )
+{
+ m_FormatEtc.cfFormat = theOther.m_FormatEtc.cfFormat;
+ m_FormatEtc.ptd = CopyTargetDevice( theOther.m_FormatEtc.ptd );
+ m_FormatEtc.dwAspect = theOther.m_FormatEtc.dwAspect;
+ m_FormatEtc.lindex = theOther.m_FormatEtc.lindex;
+ m_FormatEtc.tymed = theOther.m_FormatEtc.tymed;
+}
+
+CFormatEtc& CFormatEtc::operator=( const CFormatEtc& theOther )
+{
+ if ( this != &theOther )
+ {
+ DeleteTargetDevice( m_FormatEtc.ptd );
+
+ m_FormatEtc.cfFormat = theOther.m_FormatEtc.cfFormat;
+ m_FormatEtc.ptd = CopyTargetDevice( theOther.m_FormatEtc.ptd );
+ m_FormatEtc.dwAspect = theOther.m_FormatEtc.dwAspect;
+ m_FormatEtc.lindex = theOther.m_FormatEtc.lindex;
+ m_FormatEtc.tymed = theOther.m_FormatEtc.tymed;
+ }
+
+ return *this;
+}
+
+CFormatEtc::operator FORMATETC*( )
+{
+ return &m_FormatEtc;
+}
+
+CFormatEtc::operator FORMATETC( )
+{
+ return m_FormatEtc;
+}
+
+void CFormatEtc::getFORMATETC( LPFORMATETC lpFormatEtc )
+{
+ OSL_ASSERT( lpFormatEtc );
+ OSL_ASSERT( !IsBadWritePtr( lpFormatEtc, sizeof( FORMATETC ) ) );
+
+ CopyFormatEtc( lpFormatEtc, &m_FormatEtc );
+}
+
+CLIPFORMAT CFormatEtc::getClipformat( ) const
+{
+ return m_FormatEtc.cfFormat;
+}
+
+DWORD CFormatEtc::getTymed( ) const
+{
+ return m_FormatEtc.tymed;
+}
+
+void CFormatEtc::getTargetDevice( DVTARGETDEVICE** lpDvTargetDevice ) const
+{
+ OSL_ASSERT( lpDvTargetDevice );
+ OSL_ASSERT( !IsBadWritePtr( lpDvTargetDevice, sizeof( DVTARGETDEVICE ) ) );
+
+ *lpDvTargetDevice = nullptr;
+
+ if ( m_FormatEtc.ptd )
+ *lpDvTargetDevice = CopyTargetDevice( m_FormatEtc.ptd );
+}
+
+DWORD CFormatEtc::getDvAspect( ) const
+{
+ return m_FormatEtc.dwAspect;
+}
+
+LONG CFormatEtc::getLindex( ) const
+{
+ return m_FormatEtc.lindex;
+}
+
+void CFormatEtc::setClipformat( CLIPFORMAT cf )
+{
+ m_FormatEtc.cfFormat = cf;
+}
+
+void CFormatEtc::setTymed( DWORD tymed )
+{
+ m_FormatEtc.tymed = tymed;
+}
+
+// transfer of ownership!
+
+void CFormatEtc::setTargetDevice( DVTARGETDEVICE* ptd )
+{
+ DeleteTargetDevice( m_FormatEtc.ptd );
+ m_FormatEtc.ptd = ptd;
+}
+
+void CFormatEtc::setDvAspect( DWORD dwAspect )
+{
+ m_FormatEtc.dwAspect = dwAspect;
+}
+
+void CFormatEtc::setLindex( LONG lindex )
+{
+ m_FormatEtc.lindex = lindex;
+}
+
+bool operator==( const CFormatEtc& lhs, const CFormatEtc& rhs )
+{
+ return CompareFormatEtc( &lhs.m_FormatEtc, &rhs.m_FormatEtc );
+}
+
+bool operator!=( const CFormatEtc& lhs, const CFormatEtc& rhs )
+{
+ return !( lhs == rhs );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/Fetc.hxx b/vcl/win/dtrans/Fetc.hxx
new file mode 100644
index 0000000000..e0880acd64
--- /dev/null
+++ b/vcl/win/dtrans/Fetc.hxx
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <objidl.h>
+
+/**********************************************************************
+ stl container elements must fulfill the following requirements:
+ 1. they need a copy ctor and assignment operator(?)
+ 2. they must be comparable
+ because the FORMATETC structure has a pointer to a TARGETDEVICE
+ structure we need a simple wrapper class to fulfill these needs
+***********************************************************************/
+
+class CFormatEtc
+{
+public:
+ CFormatEtc( );
+ explicit CFormatEtc( const FORMATETC& aFormatEtc );
+ CFormatEtc( CLIPFORMAT cf, DWORD tymed = TYMED_HGLOBAL, DVTARGETDEVICE* ptd = nullptr, DWORD dwAspect = DVASPECT_CONTENT, LONG lindex = -1 );
+ CFormatEtc( const CFormatEtc& theOther );
+
+ ~CFormatEtc( );
+
+ CFormatEtc& operator=( const CFormatEtc& theOther );
+ operator FORMATETC*( );
+ operator FORMATETC( );
+
+ void getFORMATETC( LPFORMATETC lpFormatEtc );
+
+ CLIPFORMAT getClipformat( ) const;
+ DWORD getTymed( ) const;
+ void getTargetDevice( DVTARGETDEVICE** ptd ) const;
+ DWORD getDvAspect( ) const;
+ LONG getLindex( ) const;
+
+ void setClipformat( CLIPFORMAT cf );
+ void setTymed( DWORD tymed );
+ void setTargetDevice( DVTARGETDEVICE* ptd );
+ void setDvAspect( DWORD dwAspect );
+ void setLindex( LONG lindex );
+
+private:
+ FORMATETC m_FormatEtc;
+
+ friend bool operator==( const CFormatEtc& lhs, const CFormatEtc& rhs );
+ friend bool operator!=( const CFormatEtc& lhs, const CFormatEtc& rhs );
+};
+
+bool operator==( const CFormatEtc& lhs, const CFormatEtc& rhs );
+bool operator!=( const CFormatEtc& lhs, const CFormatEtc& rhs );
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/FetcList.cxx b/vcl/win/dtrans/FetcList.cxx
new file mode 100644
index 0000000000..5ace3cdca6
--- /dev/null
+++ b/vcl/win/dtrans/FetcList.cxx
@@ -0,0 +1,340 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 "FetcList.hxx"
+#include "Fetc.hxx"
+#include <com/sun/star/container/NoSuchElementException.hpp>
+#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/XMimeContentType.hpp>
+
+#include "DataFmtTransl.hxx"
+#include "ImplHelper.hxx"
+#include "WinClip.hxx"
+
+#include <algorithm>
+
+#include "MimeAttrib.hxx"
+
+using namespace com::sun::star::uno;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::container;
+
+LCID CFormatRegistrar::m_TxtLocale = 0;
+sal_uInt32 CFormatRegistrar::m_TxtCodePage = GetACP( );
+
+CFormatEtcContainer::CFormatEtcContainer( )
+{
+ m_EnumIterator = m_FormatMap.begin( );
+}
+
+void CFormatEtcContainer::addFormatEtc( const CFormatEtc& fetc )
+{
+ m_FormatMap.push_back( fetc );
+}
+
+void CFormatEtcContainer::removeFormatEtc( const CFormatEtc& fetc )
+{
+ FormatEtcMap_t::iterator iter =
+ find( m_FormatMap.begin(), m_FormatMap.end(), fetc );
+
+ if ( iter != m_FormatMap.end( ) )
+ m_FormatMap.erase( iter );
+}
+
+void CFormatEtcContainer::removeAllFormatEtc( )
+{
+ m_FormatMap.clear( );
+}
+
+bool CFormatEtcContainer::hasFormatEtc( const CFormatEtc& fetc ) const
+{
+ FormatEtcMap_t::const_iterator iter =
+ find( m_FormatMap.begin(), m_FormatMap.end(), fetc );
+
+ return ( iter != m_FormatMap.end( ) );
+}
+
+bool CFormatEtcContainer::hasElements( ) const
+{
+ return !m_FormatMap.empty();
+}
+
+void CFormatEtcContainer::beginEnumFormatEtc( )
+{
+ m_EnumIterator = m_FormatMap.begin( );
+}
+
+sal_uInt32 CFormatEtcContainer::nextFormatEtc( LPFORMATETC lpFetc,
+ sal_uInt32 aNum )
+{
+ OSL_ASSERT( lpFetc );
+ OSL_ASSERT( !IsBadWritePtr( lpFetc, sizeof( FORMATETC ) * aNum ) );
+
+ sal_uInt32 nFetched = 0;
+
+ for ( sal_uInt32 i = 0; i < aNum; i++, nFetched++, lpFetc++, ++m_EnumIterator )
+ {
+ if ( m_EnumIterator == m_FormatMap.end() )
+ break;
+ CopyFormatEtc( lpFetc, *m_EnumIterator );
+ }
+
+ return nFetched;
+}
+
+bool CFormatEtcContainer::skipFormatEtc( sal_uInt32 aNum )
+{
+ FormatEtcMap_t::const_iterator iter_end = m_FormatMap.end( );
+ for ( sal_uInt32 i = 0;
+ (i < aNum) && (m_EnumIterator != iter_end);
+ i++, ++m_EnumIterator )
+ ;/* intentionally left empty */
+
+ return ( m_EnumIterator != m_FormatMap.end( ) );
+}
+
+CFormatRegistrar::CFormatRegistrar( const Reference< XComponentContext >& rxContext,
+ const CDataFormatTranslator& aDataFormatTranslator ) :
+ m_DataFormatTranslator( aDataFormatTranslator ),
+ m_bHasSynthesizedLocale( false ),
+ m_xContext( rxContext )
+{
+}
+
+// this function converts all DataFlavors of the given FlavorList into
+// an appropriate FORMATETC structure, for some formats like unicodetext,
+// text and text/html we will offer an accompany format e.g.:
+//
+// DataFlavor | Registered Clipformat | Registered accompany clipformat
+// -------------------------|---------------------------|-----------------------------------
+// text/plain;charset=ansi | CF_TEXT | CF_UNICODETEXT
+// | | CF_LOCALE (if charset != GetACP()
+// | |
+// text/plain;charset=oem | CF_OEMTEXT | CF_UNICODETEXT
+// | | CF_LOCALE (if charset != GetOEMCP()
+// | |
+// text/plain;charset=utf-16| CF_UNICODETEXT | CF_TEXT
+// | |
+// text/html | HTML (Hypertext ...) | HTML Format
+// | |
+//
+// if some tries to register different text formats with different charsets the last
+// registered wins and the others are ignored
+
+void CFormatRegistrar::RegisterFormats(
+ const Reference< XTransferable >& aXTransferable, CFormatEtcContainer& aFormatEtcContainer )
+{
+ Sequence< DataFlavor > aFlavorList = aXTransferable->getTransferDataFlavors( );
+ sal_Int32 nFlavors = aFlavorList.getLength( );
+ bool bUnicodeRegistered = false;
+ DataFlavor aFlavor;
+
+ for( sal_Int32 i = 0; i < nFlavors; i++ )
+ {
+ aFlavor = aFlavorList[i];
+ CFormatEtc fetc = m_DataFormatTranslator.getFormatEtcFromDataFlavor( aFlavor );
+
+ // maybe an internal format so we ignore it
+ if ( CF_INVALID == fetc.getClipformat( ) )
+ continue;
+
+ if ( !needsToSynthesizeAccompanyFormats( fetc ) )
+ aFormatEtcContainer.addFormatEtc( fetc );
+ else
+ {
+ // if we haven't registered any text format up to now
+ if ( CDataFormatTranslator::isTextFormat( fetc.getClipformat() ) && !bUnicodeRegistered )
+ {
+ // if the transferable supports unicode text we ignore
+ // any further text format the transferable offers
+ // because we can create it from Unicode text in addition
+ // we register CF_TEXT for non unicode clients
+ if ( CDataFormatTranslator::isUnicodeTextFormat( fetc.getClipformat() ) )
+ {
+ aFormatEtcContainer.addFormatEtc( fetc ); // add CF_UNICODE
+ aFormatEtcContainer.addFormatEtc(
+ CDataFormatTranslator::getFormatEtcForClipformat( CF_TEXT ) ); // add CF_TEXT
+ bUnicodeRegistered = true;
+ }
+ else if ( !hasUnicodeFlavor( aXTransferable ) )
+ {
+ // we try to investigate the charset and make a valid
+ // windows codepage from this charset the default
+ // return value is the result of GetACP( )
+ OUString charset = getCharsetFromDataFlavor( aFlavor );
+ sal_uInt32 txtCP = getWinCPFromMimeCharset( charset );
+
+ // we try to get a Locale appropriate for this codepage
+ if ( findLocaleForTextCodePage( ) )
+ {
+ m_TxtCodePage = txtCP;
+
+ aFormatEtcContainer.addFormatEtc(
+ CDataFormatTranslator::getFormatEtcForClipformat( CF_UNICODETEXT ) );
+
+ if ( !IsOEMCP( m_TxtCodePage ) )
+ aFormatEtcContainer.addFormatEtc(
+ CDataFormatTranslator::getFormatEtcForClipformat( CF_TEXT ) );
+ else
+ aFormatEtcContainer.addFormatEtc(
+ CDataFormatTranslator::getFormatEtcForClipformat( CF_OEMTEXT ) );
+
+ aFormatEtcContainer.addFormatEtc(
+ CDataFormatTranslator::getFormatEtcForClipformat( CF_LOCALE ) );
+
+ // we save the flavor so it's easier when
+ // queried for it in XTDataObject::GetData(...)
+ m_RegisteredTextFlavor = aFlavor;
+ m_bHasSynthesizedLocale = true;
+ }
+ }
+ }
+ else if ( CDataFormatTranslator::isTextHtmlFormat( fetc.getClipformat( ) ) ) // Html (Hyper Text...)
+ {
+ // we add text/html ( HTML (HyperText Markup Language) )
+ aFormatEtcContainer.addFormatEtc( fetc );
+
+ // and HTML Format
+ aFormatEtcContainer.addFormatEtc(
+ CDataFormatTranslator::getFormatEtcForClipformatName( "HTML Format" ) );
+ }
+ }
+ }
+}
+
+bool CFormatRegistrar::hasSynthesizedLocale( ) const
+{
+ return m_bHasSynthesizedLocale;
+}
+
+LCID CFormatRegistrar::getSynthesizedLocale( )
+{
+ return m_TxtLocale;
+}
+
+sal_uInt32 CFormatRegistrar::getRegisteredTextCodePage( )
+{
+ return m_TxtCodePage;
+}
+
+DataFlavor CFormatRegistrar::getRegisteredTextFlavor( ) const
+{
+ return m_RegisteredTextFlavor;
+}
+
+bool CFormatRegistrar::isSynthesizeableFormat( const CFormatEtc& aFormatEtc )
+{
+ return ( CDataFormatTranslator::isOemOrAnsiTextFormat( aFormatEtc.getClipformat() ) ||
+ CDataFormatTranslator::isUnicodeTextFormat( aFormatEtc.getClipformat() ) ||
+ CDataFormatTranslator::isHTMLFormat( aFormatEtc.getClipformat() ) );
+}
+
+inline
+bool CFormatRegistrar::needsToSynthesizeAccompanyFormats( const CFormatEtc& aFormatEtc )
+{
+ return ( CDataFormatTranslator::isOemOrAnsiTextFormat( aFormatEtc.getClipformat() ) ||
+ CDataFormatTranslator::isUnicodeTextFormat( aFormatEtc.getClipformat() ) ||
+ CDataFormatTranslator::isTextHtmlFormat( aFormatEtc.getClipformat( ) ) );
+}
+
+OUString CFormatRegistrar::getCharsetFromDataFlavor( const DataFlavor& aFlavor )
+{
+ OUString charset;
+
+ try
+ {
+ Reference< XMimeContentTypeFactory > xMimeFac =
+ MimeContentTypeFactory::create(m_xContext);
+
+ Reference< XMimeContentType > xMimeType( xMimeFac->createMimeContentType( aFlavor.MimeType ) );
+ if ( xMimeType->hasParameter( TEXTPLAIN_PARAM_CHARSET ) )
+ charset = xMimeType->getParameterValue( TEXTPLAIN_PARAM_CHARSET );
+ else
+ charset = getMimeCharsetFromWinCP( GetACP( ), PRE_WINDOWS_CODEPAGE );
+ }
+ catch(NoSuchElementException&)
+ {
+ OSL_FAIL( "Unexpected" );
+ }
+ catch(...)
+ {
+ OSL_FAIL( "Invalid data flavor" );
+ }
+
+ return charset;
+}
+
+bool CFormatRegistrar::hasUnicodeFlavor( const Reference< XTransferable >& aXTransferable ) const
+{
+ DataFlavor aFlavor = m_DataFormatTranslator.getDataFlavorFromFormatEtc(CF_UNICODETEXT);
+
+ return aXTransferable->isDataFlavorSupported( aFlavor );
+}
+
+bool CFormatRegistrar::findLocaleForTextCodePage( )
+{
+ m_TxtLocale = 0;
+ EnumSystemLocalesA( CFormatRegistrar::EnumLocalesProc, LCID_INSTALLED );
+ return IsValidLocale( m_TxtLocale, LCID_INSTALLED );
+}
+
+bool CFormatRegistrar::isLocaleCodePage( LCID lcid, LCTYPE lctype, sal_uInt32 codepage )
+{
+ char buff[6];
+ sal_uInt32 localeCodePage;
+
+ OSL_ASSERT( IsValidLocale( lcid, LCID_INSTALLED ) );
+
+ // get the ansi codepage of the current locale
+ GetLocaleInfoA( lcid, lctype, buff, sizeof( buff ) );
+ localeCodePage = atol( buff );
+
+ return ( localeCodePage == codepage );
+}
+
+inline
+bool CFormatRegistrar::isLocaleOemCodePage( LCID lcid, sal_uInt32 codepage )
+{
+ return isLocaleCodePage( lcid, LOCALE_IDEFAULTCODEPAGE, codepage );
+}
+
+inline
+bool CFormatRegistrar::isLocaleAnsiCodePage( LCID lcid, sal_uInt32 codepage )
+{
+ return isLocaleCodePage( lcid, LOCALE_IDEFAULTANSICODEPAGE, codepage );
+}
+
+BOOL CALLBACK CFormatRegistrar::EnumLocalesProc( LPSTR lpLocaleStr )
+{
+ // the lpLocaleStr parameter is hexadecimal
+ LCID lcid = strtol( lpLocaleStr, nullptr, 16 );
+
+ if ( isLocaleAnsiCodePage( lcid, CFormatRegistrar::m_TxtCodePage ) ||
+ isLocaleOemCodePage( lcid, CFormatRegistrar::m_TxtCodePage ) )
+ {
+ CFormatRegistrar::m_TxtLocale = lcid;
+ return false; // stop enumerating
+ }
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/FetcList.hxx b/vcl/win/dtrans/FetcList.hxx
new file mode 100644
index 0000000000..2df28ff575
--- /dev/null
+++ b/vcl/win/dtrans/FetcList.hxx
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include "Fetc.hxx"
+
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+
+#include <vector>
+
+/*****************************************************************
+ a simple container for FORMATECT structures
+ instances of this class are not thread-safe
+*****************************************************************/
+
+class CFormatEtcContainer
+{
+public:
+ CFormatEtcContainer( );
+
+ // duplicates not allowed
+ void addFormatEtc( const CFormatEtc& fetc );
+
+ // removes the specified formatetc
+ void removeFormatEtc( const CFormatEtc& fetc );
+
+ // removes the formatetc at pos
+ void removeAllFormatEtc( );
+
+ bool hasFormatEtc( const CFormatEtc& fetc ) const;
+
+ bool hasElements( ) const;
+
+ // begin enumeration
+ void beginEnumFormatEtc( );
+
+ // copies the specified number of formatetc structures starting
+ // at the current enum position
+ // the return value is the number of copied elements; if the
+ // current enum position is at the end the return value is 0
+ sal_uInt32 nextFormatEtc( LPFORMATETC lpFetc, sal_uInt32 aNum = 1 );
+
+ // skips the specified number of elements in the container
+ bool skipFormatEtc( sal_uInt32 aNum );
+
+protected:
+ typedef std::vector< CFormatEtc > FormatEtcMap_t;
+
+private:
+ FormatEtcMap_t m_FormatMap;
+ FormatEtcMap_t::iterator m_EnumIterator;
+};
+
+/*****************************************************************
+ a helper class which converts data flavors to clipformats,
+ creates an appropriate formatetc structures and if possible
+ synthesizes clipboard formats if necessary, e.g. if text
+ is provided a locale will also be provided;
+ the class registers the formatetc within a CFormatEtcContainer
+
+ instances of this class are not thread-safe and multiple
+ instances of this class would use the same static variables
+ that's why this class should not be used by multiple threads,
+ only one thread of a process should use it
+*****************************************************************/
+
+// forward
+class CDataFormatTranslator;
+
+class CFormatRegistrar
+{
+public:
+ CFormatRegistrar( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ const CDataFormatTranslator& aDataFormatTranslator );
+
+ void RegisterFormats( const css::uno::Reference< css::datatransfer::XTransferable >& aXTransferable,
+ CFormatEtcContainer& aFormatEtcContainer );
+
+ bool hasSynthesizedLocale( ) const;
+ static LCID getSynthesizedLocale( );
+ static sal_uInt32 getRegisteredTextCodePage( );
+ css::datatransfer::DataFlavor getRegisteredTextFlavor( ) const;
+
+ static bool isSynthesizeableFormat( const CFormatEtc& aFormatEtc );
+ static bool needsToSynthesizeAccompanyFormats( const CFormatEtc& aFormatEtc );
+
+private:
+ OUString getCharsetFromDataFlavor( const css::datatransfer::DataFlavor& aFlavor );
+
+ bool hasUnicodeFlavor(
+ const css::uno::Reference< css::datatransfer::XTransferable >& aXTransferable ) const;
+
+ static bool findLocaleForTextCodePage( );
+
+ static bool isLocaleOemCodePage( LCID lcid, sal_uInt32 codepage );
+ static bool isLocaleAnsiCodePage( LCID lcid, sal_uInt32 codepage );
+ static bool isLocaleCodePage( LCID lcid, LCTYPE lctype, sal_uInt32 codepage );
+
+ static BOOL CALLBACK EnumLocalesProc( LPSTR lpLocaleStr );
+
+private:
+ const CDataFormatTranslator& m_DataFormatTranslator;
+ bool m_bHasSynthesizedLocale;
+ css::datatransfer::DataFlavor m_RegisteredTextFlavor;
+
+ const css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+ static LCID m_TxtLocale;
+ static sal_uInt32 m_TxtCodePage;
+
+private:
+ CFormatRegistrar( const CFormatRegistrar& );
+ CFormatRegistrar& operator=( const CFormatRegistrar& );
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/FmtFilter.cxx b/vcl/win/dtrans/FmtFilter.cxx
new file mode 100644
index 0000000000..27d051f7e8
--- /dev/null
+++ b/vcl/win/dtrans/FmtFilter.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 <string.h>
+
+#include "FmtFilter.hxx"
+
+#include <o3tl/safeint.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <osl/diagnose.h>
+
+#include <shobjidl.h>
+#include <shlguid.h>
+#include <objidl.h>
+#include <shellapi.h>
+
+#include <string>
+#include <sstream>
+#include <vector>
+#include <iomanip>
+
+#include <systools/win32/comtools.hxx>
+
+using namespace com::sun::star::uno;
+
+namespace {
+
+#pragma pack(2)
+struct METAFILEHEADER
+{
+ DWORD key;
+ short hmf;
+ SMALL_RECT bbox;
+ WORD inch;
+ DWORD reserved;
+ WORD checksum;
+};
+#pragma pack()
+
+}
+
+// convert a windows metafile picture to a LibreOffice metafile picture
+
+Sequence< sal_Int8 > WinMFPictToOOMFPict( Sequence< sal_Int8 >& aMetaFilePict )
+{
+ OSL_ASSERT( aMetaFilePict.getLength( ) == sizeof( METAFILEPICT ) );
+
+ Sequence< sal_Int8 > mfpictStream;
+ METAFILEPICT* pMFPict = reinterpret_cast< METAFILEPICT* >( aMetaFilePict.getArray( ) );
+ HMETAFILE hMf = pMFPict->hMF;
+ sal_uInt32 nCount = GetMetaFileBitsEx( hMf, 0, nullptr );
+
+ if ( nCount > 0 )
+ {
+ mfpictStream.realloc( nCount + sizeof( METAFILEHEADER ) );
+
+ METAFILEHEADER* pMFHeader = reinterpret_cast< METAFILEHEADER* >( mfpictStream.getArray( ) );
+ SMALL_RECT aRect = { 0,
+ 0,
+ static_cast< short >( pMFPict->xExt ),
+ static_cast< short >( pMFPict->yExt ) };
+ USHORT nInch;
+
+ switch( pMFPict->mm )
+ {
+ case MM_TEXT:
+ nInch = o3tl::convert(1, o3tl::Length::in, o3tl::Length::pt);
+ break;
+
+ case MM_LOMETRIC:
+ nInch = o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm10);
+ break;
+
+ case MM_HIMETRIC:
+ nInch = o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100);
+ break;
+
+ case MM_LOENGLISH:
+ nInch = o3tl::convert(1, o3tl::Length::in, o3tl::Length::in100);
+ break;
+
+ case MM_HIENGLISH:
+ case MM_ISOTROPIC:
+ case MM_ANISOTROPIC:
+ nInch = o3tl::convert(1, o3tl::Length::in, o3tl::Length::in1000);
+ break;
+
+ case MM_TWIPS:
+ nInch = o3tl::convert(1, o3tl::Length::in, o3tl::Length::twip);
+ break;
+
+ default:
+ nInch = o3tl::convert(1, o3tl::Length::in, o3tl::Length::master);
+ }
+
+ pMFHeader->key = 0x9AC6CDD7L;
+ pMFHeader->hmf = 0;
+ pMFHeader->bbox = aRect;
+ pMFHeader->inch = nInch;
+ pMFHeader->reserved = 0;
+ pMFHeader->checksum = 0;
+
+ char* pMFBuff = reinterpret_cast< char* >( mfpictStream.getArray( ) );
+
+ nCount = GetMetaFileBitsEx( pMFPict->hMF, nCount, pMFBuff + sizeof( METAFILEHEADER ) );
+ OSL_ASSERT( nCount > 0 );
+ }
+
+ return mfpictStream;
+}
+
+// convert a windows enhanced metafile to a LibreOffice metafile
+
+Sequence< sal_Int8 > WinENHMFPictToOOMFPict( HENHMETAFILE hEnhMetaFile )
+{
+ Sequence< sal_Int8 > aRet;
+ UINT nSize = 0;
+
+ if( hEnhMetaFile &&
+ ( ( nSize = GetEnhMetaFileBits( hEnhMetaFile, 0, nullptr ) ) != 0 ) )
+ {
+ aRet.realloc( nSize );
+
+ if( GetEnhMetaFileBits( hEnhMetaFile, nSize, reinterpret_cast<unsigned char*>(aRet.getArray()) ) != nSize )
+ aRet.realloc( 0 );
+ }
+
+ return aRet;
+}
+
+// convert a LibreOffice metafile picture to a windows metafile picture
+
+HMETAFILEPICT OOMFPictToWinMFPict( Sequence< sal_Int8 > const & aOOMetaFilePict )
+{
+ HMETAFILEPICT hPict = nullptr;
+ HMETAFILE hMtf = SetMetaFileBitsEx( aOOMetaFilePict.getLength(), reinterpret_cast<unsigned char const *>(aOOMetaFilePict.getConstArray()) );
+
+ if( hMtf )
+ {
+ METAFILEPICT* pPict = static_cast<METAFILEPICT*>(GlobalLock( hPict = GlobalAlloc( GHND, sizeof( METAFILEPICT ) ) ));
+
+ pPict->mm = 8;
+ pPict->xExt = 0;
+ pPict->yExt = 0;
+ pPict->hMF = hMtf;
+
+ GlobalUnlock( hPict );
+ }
+
+ return hPict;
+}
+
+// convert a LibreOffice metafile picture to a windows enhanced metafile picture
+
+HENHMETAFILE OOMFPictToWinENHMFPict( Sequence< sal_Int8 > const & aOOMetaFilePict )
+{
+ HENHMETAFILE hEnhMtf = SetEnhMetaFileBits( aOOMetaFilePict.getLength(), reinterpret_cast<unsigned char const *>(aOOMetaFilePict.getConstArray()) );
+
+ return hEnhMtf;
+}
+
+// convert a windows device independent bitmap into a LibreOffice bitmap
+
+Sequence< sal_Int8 > WinDIBToOOBMP( const Sequence< sal_Int8 >& aWinDIB )
+{
+ OSL_ENSURE(o3tl::make_unsigned(aWinDIB.getLength()) > sizeof(BITMAPINFOHEADER), "CF_DIBV5/CF_DIB too small (!)");
+ Sequence< sal_Int8 > ooBmpStream;
+
+ ooBmpStream.realloc(aWinDIB.getLength( ) + sizeof(BITMAPFILEHEADER));
+ const BITMAPINFOHEADER* pBmpInfoHdr = reinterpret_cast< const BITMAPINFOHEADER* >(aWinDIB.getConstArray());
+ BITMAPFILEHEADER* pBmpFileHdr = reinterpret_cast< BITMAPFILEHEADER* >(ooBmpStream.getArray());
+ const DWORD nSizeInfoOrV5(pBmpInfoHdr->biSize > sizeof(BITMAPINFOHEADER) ? sizeof(BITMAPV5HEADER) : sizeof(BITMAPINFOHEADER));
+ DWORD nOffset(sizeof(BITMAPFILEHEADER) + nSizeInfoOrV5);
+
+ memcpy(pBmpFileHdr + 1, pBmpInfoHdr, aWinDIB.getLength());
+
+ if(pBmpInfoHdr->biBitCount <= 8)
+ {
+ nOffset += (pBmpInfoHdr->biClrUsed ? pBmpInfoHdr->biClrUsed : (1 << pBmpInfoHdr->biBitCount)) << 2;
+ }
+ else if((BI_BITFIELDS == pBmpInfoHdr->biCompression ) && ((16 == pBmpInfoHdr->biBitCount ) || (32 == pBmpInfoHdr->biBitCount )))
+ {
+ nOffset += 12;
+ }
+
+ pBmpFileHdr->bfType = ('M' << 8) | 'B';
+ pBmpFileHdr->bfSize = 0; // maybe: nMemSize + sizeof(BITMAPFILEHEADER)
+ pBmpFileHdr->bfReserved1 = 0;
+ pBmpFileHdr->bfReserved2 = 0;
+ pBmpFileHdr->bfOffBits = nOffset;
+
+ return ooBmpStream;
+}
+
+// convert a LibreOffice bitmap into a windows device independent bitmap
+
+Sequence< sal_Int8 > OOBmpToWinDIB( Sequence< sal_Int8 >& aOOBmp )
+{
+ Sequence< sal_Int8 > winDIBStream( aOOBmp.getLength( ) - sizeof( BITMAPFILEHEADER ) );
+
+ memcpy( winDIBStream.getArray( ),
+ aOOBmp.getArray( ) + sizeof( BITMAPFILEHEADER ),
+ aOOBmp.getLength( ) - sizeof( BITMAPFILEHEADER ) );
+
+ return winDIBStream;
+}
+
+static std::string GetHtmlFormatHeader(size_t startHtml, size_t endHtml, size_t startFragment, size_t endFragment)
+{
+ std::ostringstream htmlHeader;
+ htmlHeader << "Version:1.0" << '\r' << '\n';
+ htmlHeader << "StartHTML:" << std::setw(10) << std::setfill('0') << std::dec << startHtml << '\r' << '\n';
+ htmlHeader << "EndHTML:" << std::setw(10) << std::setfill('0') << std::dec << endHtml << '\r' << '\n';
+ htmlHeader << "StartFragment:" << std::setw(10) << std::setfill('0') << std::dec << startFragment << '\r' << '\n';
+ htmlHeader << "EndFragment:" << std::setw(10) << std::setfill('0') << std::dec << endFragment << '\r' << '\n';
+ return htmlHeader.str();
+}
+
+// the case of these tags has to match what we output in our filters
+// both tags don't allow parameters
+const std::string TAG_HTML("<html>");
+const std::string TAG_END_HTML("</html>");
+
+// The body tag may have parameters so we need to search for the
+// closing '>' manually e.g. <body param> #92840#
+const std::string TAG_BODY("<body");
+const std::string TAG_END_BODY("</body");
+
+Sequence<sal_Int8> TextHtmlToHTMLFormat(Sequence<sal_Int8> const & aTextHtml)
+{
+ OSL_ASSERT(aTextHtml.getLength() > 0);
+
+ if (aTextHtml.getLength() <= 0)
+ return Sequence<sal_Int8>();
+
+ // fill the buffer with dummy values to calc the exact length
+ std::string dummyHtmlHeader = GetHtmlFormatHeader(0, 0, 0, 0);
+ size_t lHtmlFormatHeader = dummyHtmlHeader.length();
+
+ std::string textHtml(
+ reinterpret_cast<const char*>(aTextHtml.getConstArray()),
+ reinterpret_cast<const char*>(aTextHtml.getConstArray()) + aTextHtml.getLength());
+
+ std::string::size_type nStartHtml = textHtml.find(TAG_HTML) + lHtmlFormatHeader - 1; // we start one before '<HTML>' Word 2000 does also so
+ std::string::size_type nEndHtml = textHtml.find(TAG_END_HTML) + lHtmlFormatHeader + TAG_END_HTML.length() + 1; // our SOffice 5.2 wants 2 behind </HTML>?
+
+ // The body tag may have parameters so we need to search for the
+ // closing '>' manually e.g. <BODY param> #92840#
+ std::string::size_type nStartFragment = textHtml.find(">", textHtml.find(TAG_BODY)) + lHtmlFormatHeader + 1;
+ std::string::size_type nEndFragment = textHtml.find(TAG_END_BODY) + lHtmlFormatHeader;
+
+ std::string htmlFormat = GetHtmlFormatHeader(nStartHtml, nEndHtml, nStartFragment, nEndFragment);
+ htmlFormat += textHtml;
+
+ Sequence<sal_Int8> byteSequence(htmlFormat.length() + 1); // space the trailing '\0'
+ memset(byteSequence.getArray(), 0, byteSequence.getLength());
+
+ memcpy(
+ static_cast<void*>(byteSequence.getArray()),
+ static_cast<const void*>(htmlFormat.c_str()),
+ htmlFormat.length());
+
+ return byteSequence;
+}
+
+static std::wstring getFileExtension(const std::wstring& aFilename)
+{
+ std::wstring::size_type idx = aFilename.rfind(L".");
+ if (idx != std::wstring::npos)
+ {
+ return std::wstring(aFilename, idx);
+ }
+ return std::wstring();
+}
+
+const std::wstring SHELL_LINK_FILE_EXTENSION = L".lnk";
+
+static bool isShellLink(const std::wstring& aFilename)
+{
+ std::wstring ext = getFileExtension(aFilename);
+ return (_wcsicmp(ext.c_str(), SHELL_LINK_FILE_EXTENSION.c_str()) == 0);
+}
+
+/** Resolve a Windows Shell Link (lnk) file. If a resolution
+ is not possible simply return the provided name of the
+ lnk file. */
+static std::wstring getShellLinkTarget(const std::wstring& aLnkFile)
+{
+ OSL_ASSERT(isShellLink(aLnkFile));
+
+ std::wstring target = aLnkFile;
+
+ try
+ {
+ sal::systools::COMReference<IShellLinkW> pIShellLink;
+ pIShellLink.CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER);
+
+ sal::systools::COMReference<IPersistFile> pIPersistFile(pIShellLink,
+ sal::systools::COM_QUERY_THROW);
+
+ HRESULT hr = pIPersistFile->Load(aLnkFile.c_str(), STGM_READ);
+ if (FAILED(hr))
+ return target;
+
+ hr = pIShellLink->Resolve(nullptr, SLR_UPDATE | SLR_NO_UI);
+ if (FAILED(hr))
+ return target;
+
+ wchar_t pathW[MAX_PATH];
+ WIN32_FIND_DATAW wfd;
+ hr = pIShellLink->GetPath(pathW, MAX_PATH, &wfd, SLGP_RAWPATH);
+ if (FAILED(hr))
+ return target;
+
+ target = pathW;
+ }
+ catch(sal::systools::ComError& ex)
+ {
+ OSL_FAIL(ex.what());
+ }
+ return target;
+}
+
+typedef Sequence<sal_Int8> ByteSequence_t;
+
+/* Calculate the size required for turning a string list into
+ a double '\0' terminated string buffer */
+static size_t CalcSizeForStringListBuffer(const std::vector<std::wstring>& fileList)
+{
+ if ( fileList.empty() )
+ return 0;
+
+ size_t size = 1; // one for the very final '\0'
+ for (auto const& elem : fileList)
+ {
+ size += elem.length() + 1; // length including terminating '\0'
+ }
+ return (size * sizeof(std::vector<std::wstring>::value_type::value_type));
+}
+
+static ByteSequence_t FileListToByteSequence(const std::vector<std::wstring>& fileList)
+{
+ ByteSequence_t bseq;
+ size_t size = CalcSizeForStringListBuffer(fileList);
+
+ if (size > 0)
+ {
+ bseq.realloc(size);
+ wchar_t* p = reinterpret_cast<wchar_t*>(bseq.getArray());
+ ZeroMemory(p, size);
+
+ for (auto const& elem : fileList)
+ {
+ wcsncpy(p, elem.c_str(), elem.length());
+ p += (elem.length() + 1);
+ }
+ }
+ return bseq;
+}
+
+css::uno::Sequence<sal_Int8> CF_HDROPToFileList(HGLOBAL hGlobal)
+{
+ UINT nFiles = DragQueryFileW(static_cast<HDROP>(hGlobal), 0xFFFFFFFF, nullptr, 0);
+ std::vector<std::wstring> files;
+
+ for (UINT i = 0; i < nFiles; i++)
+ {
+ wchar_t buff[MAX_PATH];
+ /*UINT size =*/ DragQueryFileW(static_cast<HDROP>(hGlobal), i, buff, MAX_PATH);
+ std::wstring filename = buff;
+ if (isShellLink(filename))
+ filename = getShellLinkTarget(filename);
+ files.push_back(filename);
+ }
+ return FileListToByteSequence(files);
+}
+
+// convert a windows bitmap handle into a LibreOffice bitmap
+
+Sequence< sal_Int8 > WinBITMAPToOOBMP( HBITMAP aHBMP )
+{
+ Sequence< sal_Int8 > ooBmpStream;
+
+ SIZE aBmpSize;
+ if( GetBitmapDimensionEx( aHBMP, &aBmpSize ) )
+ {
+ // fill bitmap info header
+ size_t nDataBytes = 4 * aBmpSize.cy * aBmpSize.cy;
+ Sequence< sal_Int8 > aBitmapStream(
+ sizeof(BITMAPINFO) +
+ nDataBytes
+ );
+ PBITMAPINFOHEADER pBmp = reinterpret_cast<PBITMAPINFOHEADER>(aBitmapStream.getArray());
+ pBmp->biSize = sizeof( BITMAPINFOHEADER );
+ pBmp->biWidth = aBmpSize.cx;
+ pBmp->biHeight = aBmpSize.cy;
+ pBmp->biPlanes = 1;
+ pBmp->biBitCount = 32;
+ pBmp->biCompression = BI_RGB;
+ pBmp->biSizeImage = static_cast<DWORD>(nDataBytes);
+ pBmp->biXPelsPerMeter = 1000;
+ pBmp->biYPelsPerMeter = 1000;
+ pBmp->biClrUsed = 0;
+ pBmp->biClrImportant = 0;
+ if( GetDIBits( nullptr, // DC, 0 is a default GC, basically that of the desktop
+ aHBMP,
+ 0, aBmpSize.cy,
+ aBitmapStream.getArray() + sizeof(BITMAPINFO),
+ reinterpret_cast<LPBITMAPINFO>(pBmp),
+ DIB_RGB_COLORS ) )
+ {
+ ooBmpStream = WinDIBToOOBMP( aBitmapStream );
+ }
+ }
+
+ return ooBmpStream;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/FmtFilter.hxx b/vcl/win/dtrans/FmtFilter.hxx
new file mode 100644
index 0000000000..b4fb1e1fc3
--- /dev/null
+++ b/vcl/win/dtrans/FmtFilter.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/types.h>
+
+#include <com/sun/star/uno/Sequence.hxx>
+
+#if !defined WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <objidl.h>
+
+/*------------------------------------------------------------------------
+ input:
+ aMetaFilePict - a sequence of bytes containing a METAFILEPICT struct
+------------------------------------------------------------------------*/
+css::uno::Sequence<sal_Int8> WinMFPictToOOMFPict(css::uno::Sequence<sal_Int8>& aMetaFilePict);
+css::uno::Sequence<sal_Int8> WinENHMFPictToOOMFPict(HENHMETAFILE hEnhMetaFile);
+
+/*------------------------------------------------------------------------
+ input:
+ aByteStream - a sequence of bytes containing a LibreOffice metafile
+ picture with a leading METAFILEHEADER
+------------------------------------------------------------------------*/
+HMETAFILEPICT OOMFPictToWinMFPict(css::uno::Sequence<sal_Int8> const& aOOMetaFilePict);
+HENHMETAFILE OOMFPictToWinENHMFPict(css::uno::Sequence<sal_Int8> const& aOOMetaFilePict);
+
+/*------------------------------------------------------------------------
+ input:
+ aWinDIB - sequence of bytes containing a windows device independent
+ bitmap
+------------------------------------------------------------------------*/
+css::uno::Sequence<sal_Int8> WinDIBToOOBMP(const css::uno::Sequence<sal_Int8>& aWinDIB);
+
+/*------------------------------------------------------------------------
+ input:
+ aWinDIB - sequence of bytes containing a windows bitmap handle
+------------------------------------------------------------------------*/
+css::uno::Sequence<sal_Int8> WinBITMAPToOOBMP(HBITMAP);
+
+/*------------------------------------------------------------------------
+ input:
+ aOOBmp - sequence of bytes containing a LibreOffice bitmap
+ May contain CF_DIBV5 or CF_DIB, but removing the BITMAPFILEHEADER
+ is always the same size
+------------------------------------------------------------------------*/
+css::uno::Sequence<sal_Int8> OOBmpToWinDIB(css::uno::Sequence<sal_Int8>& aOOBmp);
+
+/*------------------------------------------------------------------------
+ input:
+ aTextHtml - a sequence of text/html which will be converted to the
+ HTML Format; the HTML Format has header before the real html data
+ the Format is described in the MSDN Library under HTML Clipboard
+ Format
+------------------------------------------------------------------------*/
+css::uno::Sequence<sal_Int8> TextHtmlToHTMLFormat(css::uno::Sequence<sal_Int8> const& aTextHtml);
+
+/**
+ Return a FileList in which Windows Shell Links (lnk) are resolved.
+ If for whatever reason a resolution is not possible leave the
+ original lnk file.
+*/
+css::uno::Sequence<sal_Int8> CF_HDROPToFileList(HGLOBAL hGlobal);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/ImplHelper.cxx b/vcl/win/dtrans/ImplHelper.cxx
new file mode 100644
index 0000000000..84c7383b2c
--- /dev/null
+++ b/vcl/win/dtrans/ImplHelper.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 <osl/diagnose.h>
+#include "ImplHelper.hxx"
+#include <rtl/tencinfo.h>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <string.h>
+#include <memory>
+
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+
+#include <vector>
+
+#define FORMATETC_EXACT_MATCH 1
+#define FORMATETC_PARTIAL_MATCH -1
+#define FORMATETC_NO_MATCH 0
+
+// returns a windows codepage appropriate to the
+// given mime charset parameter value
+
+sal_uInt32 getWinCPFromMimeCharset( const OUString& charset )
+{
+ sal_uInt32 winCP = GetACP( );
+
+ if ( charset.getLength( ) )
+ {
+ OString osCharset(
+ charset.getStr( ), charset.getLength( ), RTL_TEXTENCODING_ASCII_US );
+
+ rtl_TextEncoding txtEnc =
+ rtl_getTextEncodingFromMimeCharset( osCharset.getStr( ) );
+
+ sal_uIntPtr winChrs = rtl_getBestWindowsCharsetFromTextEncoding( txtEnc );
+
+ CHARSETINFO chrsInf;
+ bool bRet = TranslateCharsetInfo( reinterpret_cast<DWORD*>(winChrs), &chrsInf, TCI_SRCCHARSET );
+
+ // if one of the above functions fails
+ // we will return the current ANSI codepage
+ // of this thread
+ if ( bRet )
+ winCP = chrsInf.ciACP;
+ }
+
+ return winCP;
+}
+
+// returns a windows codepage appropriate to the
+// given locale and locale type
+
+OUString getWinCPFromLocaleId( LCID lcid, LCTYPE lctype )
+{
+ OSL_ASSERT( IsValidLocale( lcid, LCID_SUPPORTED ) );
+
+ // we set a default value
+ OUString winCP;
+
+ // set a default value
+ if ( LOCALE_IDEFAULTCODEPAGE == lctype )
+ {
+ winCP = OUString::number( static_cast<sal_Int32>(GetOEMCP( )) );
+ }
+ else if ( LOCALE_IDEFAULTANSICODEPAGE == lctype )
+ {
+ winCP = OUString::number( static_cast<sal_Int32>(GetACP( )) );
+ }
+ else
+ OSL_ASSERT( false );
+
+ // First, get required buffer size, in characters
+ int nResult = GetLocaleInfoW(
+ lcid, lctype, nullptr, 0 );
+
+ OSL_ASSERT( nResult );
+
+ if ( nResult )
+ {
+ std::unique_ptr<wchar_t[]> buff( new wchar_t[nResult] );
+ // Now get the actual data
+ nResult = GetLocaleInfoW( lcid, lctype, buff.get(), nResult );
+
+ OSL_ASSERT(nResult);
+
+ if (nResult)
+ winCP = o3tl::toU( buff.get() );
+
+ }
+
+ return winCP;
+}
+
+// returns a mime charset parameter value appropriate
+// to the given codepage, optional a prefix can be
+// given, e.g. "windows-" or "cp"
+
+OUString getMimeCharsetFromWinCP( sal_uInt32 cp, std::u16string_view aPrefix )
+{
+ return aPrefix + cptostr( cp );
+}
+
+// returns a mime charset parameter value appropriate
+// to the given locale id and locale type, optional a
+// prefix can be given, e.g. "windows-" or "cp"
+
+OUString getMimeCharsetFromLocaleId( LCID lcid, LCTYPE lctype, std::u16string_view aPrefix )
+{
+ OUString charset = getWinCPFromLocaleId( lcid, lctype );
+ return aPrefix + charset;
+}
+
+// IsOEMCP
+
+bool IsOEMCP( sal_uInt32 codepage )
+{
+ OSL_ASSERT( IsValidCodePage( codepage ) );
+
+ sal_uInt32 arrOEMCP[] = { 437, 708, 709, 710, 720, 737,
+ 775, 850, 852, 855, 857, 860,
+ 861, 862, 863, 864, 865, 866,
+ 869, 874, 932, 936, 949, 950, 1361 };
+
+ for ( size_t i = 0; i < SAL_N_ELEMENTS( arrOEMCP ); ++i )
+ if ( arrOEMCP[i] == codepage )
+ return true;
+
+ return false;
+}
+
+// converts a codepage into its string representation
+
+OUString cptostr( sal_uInt32 codepage )
+{
+ OSL_ASSERT( IsValidCodePage( codepage ) );
+
+ return OUString::number( static_cast<sal_Int64>( codepage ) );
+}
+
+// OleStdDeleteTargetDevice()
+//
+// Purpose:
+//
+// Parameters:
+//
+// Return Value:
+// SCODE - S_OK if successful
+void DeleteTargetDevice( DVTARGETDEVICE* ptd )
+{
+ __try
+ {
+ CoTaskMemFree( ptd );
+ }
+ __except( EXCEPTION_EXECUTE_HANDLER )
+ {
+ OSL_FAIL( "Error DeleteTargetDevice" );
+ }
+}
+
+// OleStdCopyTargetDevice()
+//
+// Purpose:
+// duplicate a TARGETDEVICE struct. this function allocates memory for
+// the copy. the caller MUST free the allocated copy when done with it
+// using the standard allocator returned from CoGetMalloc.
+// (OleStdFree can be used to free the copy).
+//
+// Parameters:
+// ptdSrc pointer to source TARGETDEVICE
+//
+// Return Value:
+// pointer to allocated copy of ptdSrc
+// if ptdSrc==NULL then returns NULL is returned.
+// if ptdSrc!=NULL and memory allocation fails, then NULL is returned
+DVTARGETDEVICE* CopyTargetDevice( DVTARGETDEVICE* ptdSrc )
+{
+ DVTARGETDEVICE* ptdDest = nullptr;
+
+ __try
+ {
+ if ( nullptr != ptdSrc )
+ {
+ ptdDest = static_cast< DVTARGETDEVICE* >( CoTaskMemAlloc( ptdSrc->tdSize ) );
+ memcpy( ptdDest, ptdSrc, static_cast< size_t >( ptdSrc->tdSize ) );
+ }
+ }
+ __except( EXCEPTION_EXECUTE_HANDLER )
+ {
+ }
+
+ return ptdDest;
+}
+
+// OleStdCopyFormatEtc()
+//
+// Purpose:
+// Copies the contents of a FORMATETC structure. this function takes
+// special care to copy correctly copying the pointer to the TARGETDEVICE
+// contained within the source FORMATETC structure.
+// if the source FORMATETC has a non-NULL TARGETDEVICE, then a copy
+// of the TARGETDEVICE will be allocated for the destination of the
+// FORMATETC (petcDest).
+//
+// NOTE: the caller MUST free the allocated copy of the TARGETDEVICE
+// within the destination FORMATETC when done with it
+// using the standard allocator returned from CoGetMalloc.
+// (OleStdFree can be used to free the copy).
+//
+// Parameters:
+// petcDest pointer to destination FORMATETC
+// petcSrc pointer to source FORMATETC
+//
+// Return Value:
+// returns TRUE if copy was successful;
+// returns FALSE if not successful, e.g. one or both of the pointers
+// were invalid or the pointers were equal
+bool CopyFormatEtc( LPFORMATETC petcDest, LPFORMATETC petcSrc )
+{
+ bool bRet = false;
+
+ __try
+ {
+ if ( petcDest != petcSrc )
+ {
+
+ petcDest->cfFormat = petcSrc->cfFormat;
+
+ petcDest->ptd = nullptr;
+ if ( nullptr != petcSrc->ptd )
+ petcDest->ptd = CopyTargetDevice(petcSrc->ptd);
+
+ petcDest->dwAspect = petcSrc->dwAspect;
+ petcDest->lindex = petcSrc->lindex;
+ petcDest->tymed = petcSrc->tymed;
+
+ bRet = true;
+ }
+ }
+ __except( EXCEPTION_EXECUTE_HANDLER )
+ {
+ OSL_FAIL( "Error CopyFormatEtc" );
+ }
+
+ return bRet;
+}
+
+// returns:
+// 1 for exact match,
+// 0 for no match,
+// -1 for partial match (which is defined to mean the left is a subset
+// of the right: fewer aspects, null target device, fewer medium).
+
+sal_Int32 CompareFormatEtc( const FORMATETC* pFetcLhs, const FORMATETC* pFetcRhs )
+{
+ sal_Int32 nMatch = FORMATETC_EXACT_MATCH;
+
+ __try
+ {
+ if ( pFetcLhs != pFetcRhs )
+ {
+ if ( ( pFetcLhs->cfFormat != pFetcRhs->cfFormat ) ||
+ ( pFetcLhs->lindex != pFetcRhs->lindex ) ||
+ !CompareTargetDevice( pFetcLhs->ptd, pFetcRhs->ptd ) )
+ {
+ nMatch = FORMATETC_NO_MATCH;
+ }
+
+ else if ( pFetcLhs->dwAspect == pFetcRhs->dwAspect )
+ // same aspects; equal
+ ;
+ else if ( ( pFetcLhs->dwAspect & ~pFetcRhs->dwAspect ) != 0 )
+ {
+ // left not subset of aspects of right; not equal
+ nMatch = FORMATETC_NO_MATCH;
+ }
+ else
+ // left subset of right
+ nMatch = FORMATETC_PARTIAL_MATCH;
+
+ if ( nMatch == FORMATETC_EXACT_MATCH || nMatch == FORMATETC_PARTIAL_MATCH )
+ {
+ if ( pFetcLhs->tymed == pFetcRhs->tymed )
+ // same medium flags; equal
+ ;
+ else if ( ( pFetcLhs->tymed & ~pFetcRhs->tymed ) != 0 )
+ {
+ // left not subset of medium flags of right; not equal
+ nMatch = FORMATETC_NO_MATCH;
+ }
+ else
+ // left subset of right
+ nMatch = FORMATETC_PARTIAL_MATCH;
+ }
+ }
+ }
+ __except( EXCEPTION_EXECUTE_HANDLER )
+ {
+ OSL_FAIL( "Error CompareFormatEtc" );
+ nMatch = FORMATETC_NO_MATCH;
+ }
+
+ return nMatch;
+}
+
+bool CompareTargetDevice( DVTARGETDEVICE* ptdLeft, DVTARGETDEVICE const * ptdRight )
+{
+ bool bRet = false;
+
+ __try
+ {
+ if ( ptdLeft == ptdRight )
+ {
+ // same address of td; must be same (handles NULL case)
+ bRet = true;
+ }
+
+ // one of the two is NULL
+ else if ( ( nullptr != ptdRight ) && ( nullptr != ptdLeft ) )
+
+ if ( ptdLeft->tdSize == ptdRight->tdSize )
+
+ if ( memcmp( ptdLeft, ptdRight, ptdLeft->tdSize ) == 0 )
+ bRet = true;
+ }
+ __except( EXCEPTION_EXECUTE_HANDLER )
+ {
+ OSL_FAIL( "Error CompareTargetDevice" );
+ bRet = false;
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/ImplHelper.hxx b/vcl/win/dtrans/ImplHelper.hxx
new file mode 100644
index 0000000000..df6731ec7a
--- /dev/null
+++ b/vcl/win/dtrans/ImplHelper.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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <sal/types.h>
+#include <rtl/ustring.hxx>
+
+#if !defined WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <objidl.h>
+
+// target device and formatetc helper
+void DeleteTargetDevice(DVTARGETDEVICE* ptd);
+bool CopyFormatEtc(LPFORMATETC petcDest, LPFORMATETC petcSrc);
+sal_Int32 CompareFormatEtc(const FORMATETC* pFetcLeft, const FORMATETC* pFetcRight);
+bool CompareTargetDevice(DVTARGETDEVICE* ptdLeft, DVTARGETDEVICE const* ptdRight);
+DVTARGETDEVICE* CopyTargetDevice(DVTARGETDEVICE* ptdSrc);
+
+// some codepage helper functions
+
+// returns a windows codepage appropriate to the
+// given mime charset parameter value
+
+sal_uInt32 getWinCPFromMimeCharset(const OUString& charset);
+
+// returns a windows codepage appropriate to the
+// given locale and locale type
+
+OUString getWinCPFromLocaleId(LCID lcid, LCTYPE lctype);
+
+// returns a mime charset parameter value appropriate
+// to the given codepage, optional a prefix can be
+// given, e.g. "windows-" or "cp"
+
+OUString getMimeCharsetFromWinCP(sal_uInt32 cp, std::u16string_view aPrefix);
+
+// returns a mime charset parameter value appropriate
+// to the given locale id and locale type, optional a
+// prefix can be given, e.g. "windows-" or "cp"
+
+OUString getMimeCharsetFromLocaleId(LCID lcid, LCTYPE lctype, std::u16string_view aPrefix);
+
+// returns true, if a given codepage is an oem codepage
+
+bool IsOEMCP(sal_uInt32 codepage);
+
+// converts a codepage into a string representation
+
+OUString cptostr(sal_uInt32 codepage);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/MimeAttrib.hxx b/vcl/win/dtrans/MimeAttrib.hxx
new file mode 100644
index 0000000000..48dd5fc11e
--- /dev/null
+++ b/vcl/win/dtrans/MimeAttrib.hxx
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+
+constexpr OUString TEXTPLAIN_PARAM_CHARSET(u"charset"_ustr);
+
+constexpr OUString PRE_WINDOWS_CODEPAGE(u"windows"_ustr);
+constexpr OUString PRE_OEM_CODEPAGE(u"cp"_ustr);
+constexpr OUString CHARSET_UTF16(u"utf-16"_ustr);
+constexpr OUString CHARSET_UNICODE(u"unicode"_ustr);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/MtaOleClipb.cxx b/vcl/win/dtrans/MtaOleClipb.cxx
new file mode 100644
index 0000000000..fcf16df1ad
--- /dev/null
+++ b/vcl/win/dtrans/MtaOleClipb.cxx
@@ -0,0 +1,748 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/*
+ MtaOleClipb.cxx - documentation
+
+ This class setup a single threaded apartment (sta) thread to deal with
+ the ole clipboard, which runs only in an sta thread.
+ The consequence is that callback from the ole clipboard are in the
+ context of this sta thread. In the soffice applications this may lead
+ to problems because they all use the one and only mutex called
+ SolarMutex.
+ In order to transfer clipboard requests to our sta thread we use a
+ hidden window and forward these requests via window messages.
+*/
+
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+
+#include "MtaOleClipb.hxx"
+
+#include <svsys.h>
+#include <win/saldata.hxx>
+
+#include <osl/thread.h>
+
+#include <wchar.h>
+#include <process.h>
+
+#include <systools/win32/comtools.hxx>
+#include <systools/win32/retry_if_failed.hxx>
+
+#include <comphelper/windowserrorstring.hxx>
+
+namespace /* private */
+{
+ const wchar_t g_szWndClsName[] = L"MtaOleReqWnd###";
+
+ // messages constants
+
+ const sal_uInt32 MSG_SETCLIPBOARD = WM_USER + 0x0001;
+ const sal_uInt32 MSG_GETCLIPBOARD = WM_USER + 0x0002;
+ const sal_uInt32 MSG_REGCLIPVIEWER = WM_USER + 0x0003;
+ const sal_uInt32 MSG_FLUSHCLIPBOARD = WM_USER + 0x0004;
+ const sal_uInt32 MSG_SHUTDOWN = WM_USER + 0x0005;
+
+ const sal_uInt32 MAX_WAITTIME = 10000; // msec
+ const sal_uInt32 MAX_WAIT_SHUTDOWN = 10000; // msec
+
+ const bool MANUAL_RESET = true;
+ const bool INIT_NONSIGNALED = false;
+
+ /* Cannot use osl conditions because they are blocking
+ without waking up on messages sent by another thread
+ this leads to deadlocks because we are blocking the
+ communication between inter-thread marshalled COM
+ pointers.
+ COM Proxy-Stub communication uses SendMessages for
+ synchronization purposes.
+ */
+ class Win32Condition
+ {
+ public:
+ Win32Condition() = default;
+
+ ~Win32Condition() { CloseHandle(m_hEvent); }
+
+ // wait infinite for own event (or abort event) be signaled
+ // leave messages sent through
+ bool wait(HANDLE hEvtAbort)
+ {
+ const HANDLE hWaitArray[2] = { m_hEvent, hEvtAbort };
+ while (true)
+ {
+ DWORD dwResult
+ = MsgWaitForMultipleObjects(2, hWaitArray, FALSE, INFINITE, QS_SENDMESSAGE);
+
+ switch (dwResult)
+ {
+ case WAIT_OBJECT_0: // wait successful
+ return true;
+
+ case WAIT_OBJECT_0 + 1: // wait aborted
+ return false;
+
+ case WAIT_OBJECT_0 + 2:
+ {
+ /* PeekMessage processes all messages in the SendMessage
+ queue that's what we want, messages from the PostMessage
+ queue stay untouched */
+ MSG msg;
+ PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE);
+
+ break;
+ }
+
+ default: // WAIT_FAILED?
+ return false;
+ }
+ }
+ }
+
+ // set the event
+ void set() { SetEvent(m_hEvent); }
+
+ private:
+ HANDLE m_hEvent = CreateEventW(nullptr, MANUAL_RESET, INIT_NONSIGNALED, nullptr);
+
+ // prevent copy/assignment
+ Win32Condition(const Win32Condition&) = delete;
+ Win32Condition& operator=(const Win32Condition&) = delete;
+ };
+
+ // we use one condition for every request
+
+ struct MsgCtx
+ {
+ Win32Condition aCondition;
+ HRESULT hr;
+ };
+
+} /* namespace private */
+
+// static member initialization
+
+CMtaOleClipboard* CMtaOleClipboard::s_theMtaOleClipboardInst = nullptr;
+
+// marshal an IDataObject
+
+//inline
+static HRESULT MarshalIDataObjectInStream( IDataObject* pIDataObject, LPSTREAM* ppStream )
+{
+ OSL_ASSERT( nullptr != pIDataObject );
+ OSL_ASSERT( nullptr != ppStream );
+
+ *ppStream = nullptr;
+ return CoMarshalInterThreadInterfaceInStream(
+ __uuidof(IDataObject), //The IID of interface to be marshalled
+ pIDataObject, //The interface pointer
+ ppStream //IStream pointer
+ );
+}
+
+// unmarshal an IDataObject
+
+//inline
+static HRESULT UnmarshalIDataObjectAndReleaseStream( LPSTREAM lpStream, IDataObject** ppIDataObject )
+{
+ OSL_ASSERT( nullptr != lpStream );
+ OSL_ASSERT( nullptr != ppIDataObject );
+
+ *ppIDataObject = nullptr;
+ return CoGetInterfaceAndReleaseStream(
+ lpStream,
+ __uuidof(IDataObject),
+ reinterpret_cast<LPVOID*>(ppIDataObject));
+}
+
+// helper class to ensure that the calling thread has com initialized
+
+namespace {
+
+class CAutoComInit
+{
+public:
+ /*
+ to be safe we call CoInitializeEx
+ although it is not necessary if
+ the calling thread was created
+ using osl_CreateThread because
+ this function calls CoInitializeEx
+ for every thread it creates
+ */
+ CAutoComInit( ) : m_hResult( CoInitializeEx( nullptr, COINIT_APARTMENTTHREADED ) )
+ {
+ if ( S_OK == m_hResult )
+ OSL_FAIL(
+ "com was not yet initialized, the thread was not created using osl_createThread" );
+ else if ( FAILED( m_hResult ) && !( RPC_E_CHANGED_MODE == m_hResult ) )
+ OSL_FAIL(
+ "com could not be initialized, maybe the thread was not created using osl_createThread" );
+ }
+
+ ~CAutoComInit( )
+ {
+ /*
+ we only call CoUninitialize when
+ CoInitializeEx returned S_FALSE, what
+ means that com was already initialize
+ for that thread so we keep the balance
+ if CoInitializeEx returned S_OK what means
+ com was not yet initialized we better
+ let com initialized or we may run into
+ the realm of undefined behaviour
+ */
+ if ( m_hResult == S_FALSE )
+ CoUninitialize( );
+ }
+
+private:
+ HRESULT m_hResult;
+};
+
+}
+
+// ctor
+
+CMtaOleClipboard::CMtaOleClipboard( ) :
+ m_hOleThread( nullptr ),
+ m_uOleThreadId( 0 ),
+ // signals that the thread was successfully setup
+ m_hEvtThrdReady(CreateEventW( nullptr, MANUAL_RESET, INIT_NONSIGNALED, nullptr )),
+ m_hwndMtaOleReqWnd( nullptr ),
+ // signals that the window is destroyed - to stop waiting any winproc result
+ m_hEvtWndDisposed(CreateEventW(nullptr, MANUAL_RESET, INIT_NONSIGNALED, nullptr)),
+ m_MtaOleReqWndClassAtom( 0 ),
+ m_pfncClipViewerCallback( nullptr ),
+ m_bRunClipboardNotifierThread( true ),
+ m_hClipboardChangedEvent( m_hClipboardChangedNotifierEvents[0] ),
+ m_hTerminateClipboardChangedNotifierEvent( m_hClipboardChangedNotifierEvents[1] ),
+ m_ClipboardChangedEventCount( 0 )
+{
+ OSL_ASSERT( nullptr != m_hEvtThrdReady );
+ SAL_WARN_IF(!m_hEvtWndDisposed, "vcl.win.dtrans", "CreateEventW failed: m_hEvtWndDisposed is nullptr");
+
+ s_theMtaOleClipboardInst = this;
+
+ m_hOleThread = reinterpret_cast<HANDLE>(_beginthreadex(
+ nullptr, 0, CMtaOleClipboard::oleThreadProc, this, 0, &m_uOleThreadId ));
+ OSL_ASSERT( nullptr != m_hOleThread );
+
+ // setup the clipboard changed notifier thread
+
+ m_hClipboardChangedNotifierEvents[0] = CreateEventW( nullptr, MANUAL_RESET, INIT_NONSIGNALED, nullptr );
+ OSL_ASSERT( nullptr != m_hClipboardChangedNotifierEvents[0] );
+
+ m_hClipboardChangedNotifierEvents[1] = CreateEventW( nullptr, MANUAL_RESET, INIT_NONSIGNALED, nullptr );
+ OSL_ASSERT( nullptr != m_hClipboardChangedNotifierEvents[1] );
+
+ m_hClipboardChangedNotifierThread = reinterpret_cast<HANDLE>(_beginthreadex(
+ nullptr, 0, CMtaOleClipboard::clipboardChangedNotifierThreadProc, this, 0, nullptr ));
+
+ OSL_ASSERT( nullptr != m_hClipboardChangedNotifierThread );
+}
+
+// dtor
+
+CMtaOleClipboard::~CMtaOleClipboard( )
+{
+ // block calling threads out
+ if ( nullptr != m_hEvtThrdReady )
+ ResetEvent( m_hEvtThrdReady );
+
+ // terminate the clipboard changed notifier thread
+ m_bRunClipboardNotifierThread = false;
+ SetEvent( m_hTerminateClipboardChangedNotifierEvent );
+
+ // unblock whoever could still wait for event processing
+ if (m_hEvtWndDisposed)
+ SetEvent(m_hEvtWndDisposed);
+
+ sal_uInt32 dwResult = WaitForSingleObject(
+ m_hClipboardChangedNotifierThread, MAX_WAIT_SHUTDOWN );
+
+ OSL_ENSURE( dwResult == WAIT_OBJECT_0, "clipboard notifier thread could not terminate" );
+
+ if ( nullptr != m_hClipboardChangedNotifierThread )
+ CloseHandle( m_hClipboardChangedNotifierThread );
+
+ if ( nullptr != m_hClipboardChangedNotifierEvents[0] )
+ CloseHandle( m_hClipboardChangedNotifierEvents[0] );
+
+ if ( nullptr != m_hClipboardChangedNotifierEvents[1] )
+ CloseHandle( m_hClipboardChangedNotifierEvents[1] );
+
+ // end the thread
+ // because DestroyWindow can only be called
+ // from within the thread that created the window
+ sendMessage( MSG_SHUTDOWN );
+
+ // wait for thread shutdown
+ dwResult = WaitForSingleObject( m_hOleThread, MAX_WAIT_SHUTDOWN );
+ OSL_ENSURE( dwResult == WAIT_OBJECT_0, "OleThread could not terminate" );
+
+ if ( nullptr != m_hOleThread )
+ CloseHandle( m_hOleThread );
+
+ if ( nullptr != m_hEvtThrdReady )
+ CloseHandle( m_hEvtThrdReady );
+
+ if (m_hEvtWndDisposed)
+ CloseHandle(m_hEvtWndDisposed);
+
+ if ( m_MtaOleReqWndClassAtom )
+ UnregisterClassW( g_szWndClsName, nullptr );
+
+ OSL_ENSURE( ( nullptr == m_pfncClipViewerCallback ),
+ "Clipboard viewer not properly unregistered" );
+}
+
+HRESULT CMtaOleClipboard::flushClipboard( )
+{
+ if ( !WaitForThreadReady( ) )
+ {
+ OSL_FAIL( "clipboard sta thread not ready" );
+ return E_FAIL;
+ }
+
+ OSL_ENSURE( GetCurrentThreadId( ) != m_uOleThreadId,
+ "flushClipboard from within clipboard sta thread called" );
+
+ MsgCtx aMsgCtx;
+
+ const bool bWaitSuccess = postMessage(MSG_FLUSHCLIPBOARD, 0, reinterpret_cast<LPARAM>(&aMsgCtx))
+ && aMsgCtx.aCondition.wait(m_hEvtWndDisposed);
+
+ return bWaitSuccess ? aMsgCtx.hr : E_ABORT;
+}
+
+HRESULT CMtaOleClipboard::getClipboard( IDataObject** ppIDataObject )
+{
+ OSL_PRECOND( nullptr != ppIDataObject, "invalid parameter" );
+ OSL_PRECOND( GetCurrentThreadId( ) != m_uOleThreadId, "getClipboard from within clipboard sta thread called" );
+
+ if ( !WaitForThreadReady( ) )
+ {
+ OSL_FAIL( "clipboard sta thread not ready" );
+ return E_FAIL;
+ }
+
+ CAutoComInit comAutoInit;
+
+ LPSTREAM lpStream;
+
+ *ppIDataObject = nullptr;
+
+ MsgCtx aMsgCtx;
+
+ const bool bWaitSuccess = postMessage(MSG_GETCLIPBOARD, reinterpret_cast<WPARAM>(&lpStream),
+ reinterpret_cast<LPARAM>(&aMsgCtx))
+ && aMsgCtx.aCondition.wait(m_hEvtWndDisposed);
+
+ HRESULT hr = bWaitSuccess ? aMsgCtx.hr : E_ABORT;
+
+ if ( SUCCEEDED( hr ) )
+ {
+ hr = UnmarshalIDataObjectAndReleaseStream( lpStream, ppIDataObject );
+ OSL_ENSURE( SUCCEEDED( hr ), "unmarshalling clipboard data object failed" );
+ }
+
+ return hr;
+}
+
+// this is an asynchronous method that's why we don't wait until the
+// request is completed
+
+HRESULT CMtaOleClipboard::setClipboard( IDataObject* pIDataObject )
+{
+ if ( !WaitForThreadReady( ) )
+ {
+ OSL_FAIL( "clipboard sta thread not ready" );
+ return E_FAIL;
+ }
+
+ CAutoComInit comAutoInit;
+
+ OSL_ENSURE( GetCurrentThreadId( ) != m_uOleThreadId, "setClipboard from within the clipboard sta thread called" );
+
+ // because we marshall this request
+ // into the sta thread we better
+ // acquire the interface here so
+ // that the object will not be
+ // destroyed before the ole clipboard
+ // can acquire it
+ // remember: pIDataObject may be NULL
+ // which is a request to clear the
+ // current clipboard content
+ if ( pIDataObject )
+ pIDataObject->AddRef( );
+
+ postMessage(
+ MSG_SETCLIPBOARD,
+ reinterpret_cast< WPARAM >( pIDataObject ) );
+
+ // because this is an asynchronous function
+ // the return value is useless
+ return S_OK;
+}
+
+// register a clipboard viewer
+
+bool CMtaOleClipboard::registerClipViewer( LPFNC_CLIPVIEWER_CALLBACK_t pfncClipViewerCallback )
+{
+ if ( !WaitForThreadReady( ) )
+ {
+ OSL_FAIL( "clipboard sta thread not ready" );
+ return false;
+ }
+
+ OSL_ENSURE( GetCurrentThreadId( ) != m_uOleThreadId, "registerClipViewer from within the OleThread called" );
+
+ MsgCtx aMsgCtx;
+
+ if (postMessage(MSG_REGCLIPVIEWER, reinterpret_cast<WPARAM>(pfncClipViewerCallback),
+ reinterpret_cast<LPARAM>(&aMsgCtx)))
+ aMsgCtx.aCondition.wait(m_hEvtWndDisposed);
+
+ return false;
+}
+
+// register a clipboard viewer
+
+bool CMtaOleClipboard::onRegisterClipViewer( LPFNC_CLIPVIEWER_CALLBACK_t pfncClipViewerCallback )
+{
+ bool bRet = false;
+
+ // we need exclusive access because the clipboard changed notifier
+ // thread also accesses this variable
+ std::unique_lock aGuard( m_pfncClipViewerCallbackMutex );
+
+ // register if not yet done
+ if ( ( nullptr != pfncClipViewerCallback ) && ( nullptr == m_pfncClipViewerCallback ) )
+ {
+ // SetClipboardViewer sends a WM_DRAWCLIPBOARD message we ignore
+ // this message if we register ourself as clip viewer
+ m_bInRegisterClipViewer = true;
+ bRet = AddClipboardFormatListener(m_hwndMtaOleReqWnd);
+ m_bInRegisterClipViewer = false;
+
+ // save the new callback function
+ m_pfncClipViewerCallback = pfncClipViewerCallback;
+ }
+ else if ( ( nullptr == pfncClipViewerCallback ) && ( nullptr != m_pfncClipViewerCallback ) )
+ {
+ m_pfncClipViewerCallback = nullptr;
+
+ // unregister if input parameter is NULL and we previously registered
+ // as clipboard viewer
+ bRet = RemoveClipboardFormatListener(m_hwndMtaOleReqWnd);
+ }
+
+ return bRet;
+}
+
+HRESULT CMtaOleClipboard::onSetClipboard( IDataObject* pIDataObject )
+{
+ return sal::systools::RetryIfFailed(10, 100,
+ [pIDataObject] { return OleSetClipboard(pIDataObject); });
+}
+
+HRESULT CMtaOleClipboard::onGetClipboard( LPSTREAM* ppStream )
+{
+ OSL_ASSERT(nullptr != ppStream);
+
+ IDataObjectPtr pIDataObject;
+
+ // forward the request to the OleClipboard
+ HRESULT hr
+ = sal::systools::RetryIfFailed(10, 100, [p = &pIDataObject] { return OleGetClipboard(p); });
+ if ( SUCCEEDED( hr ) )
+ {
+ hr = MarshalIDataObjectInStream(pIDataObject.get(), ppStream);
+ OSL_ENSURE(SUCCEEDED(hr), "marshalling clipboard data object failed");
+ }
+ return hr;
+}
+
+// flush the ole-clipboard
+
+HRESULT CMtaOleClipboard::onFlushClipboard( )
+{
+ return sal::systools::RetryIfFailed(10, 100, [] { return OleFlushClipboard(); });
+}
+
+// handle clipboard update event
+
+LRESULT CMtaOleClipboard::onClipboardUpdate()
+{
+ // we don't send a notification if we are
+ // registering ourself as clipboard
+ if ( !m_bInRegisterClipViewer )
+ {
+ std::unique_lock aGuard( m_ClipboardChangedEventCountMutex );
+
+ m_ClipboardChangedEventCount++;
+ SetEvent( m_hClipboardChangedEvent );
+ }
+
+ return 0;
+}
+
+// SendMessage so we don't need to supply the HWND if we send
+// something to our wrapped window
+
+LRESULT CMtaOleClipboard::sendMessage( UINT msg, WPARAM wParam, LPARAM lParam )
+{
+ return ::SendMessageW( m_hwndMtaOleReqWnd, msg, wParam, lParam );
+}
+
+// PostMessage so we don't need to supply the HWND if we send
+// something to our wrapped window
+
+bool CMtaOleClipboard::postMessage( UINT msg, WPARAM wParam, LPARAM lParam )
+{
+ bool const ret = PostMessageW(m_hwndMtaOleReqWnd, msg, wParam, lParam);
+ SAL_WARN_IF(!ret, "vcl.win.dtrans", "ERROR: PostMessage() failed!");
+ return ret;
+}
+
+// the window proc
+
+LRESULT CALLBACK CMtaOleClipboard::mtaOleReqWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ LRESULT lResult = 0;
+
+ // get a connection to the class-instance via the static member
+ CMtaOleClipboard* pImpl = CMtaOleClipboard::s_theMtaOleClipboardInst;
+ OSL_ASSERT( nullptr != pImpl );
+
+ switch( uMsg )
+ {
+ case MSG_SETCLIPBOARD:
+ {
+ IDataObject* pIDataObject = reinterpret_cast< IDataObject* >( wParam );
+ CMtaOleClipboard::onSetClipboard( pIDataObject );
+
+ // in setClipboard we did acquire the
+ // interface pointer in order to prevent
+ // destruction of the object before the
+ // ole clipboard can acquire the interface
+ // now we release the interface so that
+ // our lostOwnership mechanism works
+ // remember: pIDataObject may be NULL
+ if ( pIDataObject )
+ pIDataObject->Release( );
+ }
+ break;
+
+ case MSG_GETCLIPBOARD:
+ {
+ MsgCtx* aMsgCtx = reinterpret_cast< MsgCtx* >( lParam );
+ OSL_ASSERT( aMsgCtx );
+
+ aMsgCtx->hr = CMtaOleClipboard::onGetClipboard( reinterpret_cast< LPSTREAM* >(wParam) );
+ aMsgCtx->aCondition.set( );
+ }
+ break;
+
+ case MSG_FLUSHCLIPBOARD:
+ {
+ MsgCtx* aMsgCtx = reinterpret_cast< MsgCtx* >( lParam );
+ OSL_ASSERT( aMsgCtx );
+
+ aMsgCtx->hr = CMtaOleClipboard::onFlushClipboard( );
+ aMsgCtx->aCondition.set( );
+ }
+ break;
+
+ case MSG_REGCLIPVIEWER:
+ {
+ MsgCtx* pMsgCtx = reinterpret_cast<MsgCtx*>(lParam);
+ SAL_WARN_IF(!pMsgCtx, "vcl.win.dtrans", "pMsgCtx is nullptr");
+
+ pImpl->onRegisterClipViewer(
+ reinterpret_cast<CMtaOleClipboard::LPFNC_CLIPVIEWER_CALLBACK_t>(wParam));
+ pMsgCtx->aCondition.set();
+ }
+ break;
+
+ case WM_CLIPBOARDUPDATE:
+ lResult = pImpl->onClipboardUpdate();
+ break;
+
+ case MSG_SHUTDOWN:
+ DestroyWindow( pImpl->m_hwndMtaOleReqWnd );
+ break;
+
+ // force the sta thread to end
+ case WM_DESTROY:
+ SetEvent(pImpl->m_hEvtWndDisposed); // stop waiting for conditions set by this wndproc
+ PostQuitMessage( 0 );
+ break;
+
+ default:
+ lResult = DefWindowProcW( hWnd, uMsg, wParam, lParam );
+ break;
+ }
+
+ return lResult;
+}
+
+void CMtaOleClipboard::createMtaOleReqWnd( )
+{
+ WNDCLASSEXW wcex;
+
+ SalData* pSalData = GetSalData();
+ OSL_ASSERT(nullptr != pSalData->mhInst);
+
+ ZeroMemory( &wcex, sizeof(wcex) );
+
+ wcex.cbSize = sizeof(wcex);
+ wcex.style = 0;
+ wcex.lpfnWndProc = CMtaOleClipboard::mtaOleReqWndProc;
+ wcex.cbClsExtra = 0;
+ wcex.cbWndExtra = 0;
+ wcex.hInstance = pSalData->mhInst;
+ wcex.hIcon = nullptr;
+ wcex.hCursor = nullptr;
+ wcex.hbrBackground = nullptr;
+ wcex.lpszMenuName = nullptr;
+ wcex.lpszClassName = g_szWndClsName;
+ wcex.hIconSm = nullptr;
+
+ m_MtaOleReqWndClassAtom = RegisterClassExW( &wcex );
+
+ if ( 0 != m_MtaOleReqWndClassAtom )
+ m_hwndMtaOleReqWnd = CreateWindowW(
+ g_szWndClsName, nullptr, 0, 0, 0, 0, 0, nullptr, nullptr, pSalData->mhInst, nullptr );
+}
+
+unsigned int CMtaOleClipboard::run( )
+{
+ HRESULT hr = OleInitialize( nullptr );
+ OSL_ASSERT( SUCCEEDED( hr ) );
+
+ createMtaOleReqWnd( );
+
+ unsigned int nRet = ~0U; // = error
+
+ if ( IsWindow( m_hwndMtaOleReqWnd ) )
+ {
+ if ( nullptr != m_hEvtThrdReady )
+ SetEvent( m_hEvtThrdReady );
+
+ nRet = 0;
+
+ // pumping messages
+ for (;;)
+ {
+ MSG msg;
+ int const bRet = GetMessageW(&msg, nullptr, 0, 0);
+ if (bRet == 0)
+ {
+ break;
+ }
+ if (-1 == bRet)
+ {
+ SAL_WARN("vcl.win.dtrans", "GetMessageW failed: " << WindowsErrorString(GetLastError()));
+ nRet = ~0U;
+ break;
+ }
+ DispatchMessageW(&msg);
+ }
+ }
+
+ OleUninitialize( );
+
+ return nRet;
+}
+
+unsigned __stdcall CMtaOleClipboard::oleThreadProc( void* pParam )
+{
+ osl_setThreadName("CMtaOleClipboard::run()");
+
+ CMtaOleClipboard* pInst =
+ static_cast<CMtaOleClipboard*>( pParam );
+ OSL_ASSERT( nullptr != pInst );
+
+ return pInst->run( );
+}
+
+unsigned __stdcall CMtaOleClipboard::clipboardChangedNotifierThreadProc(void* pParam)
+{
+ osl_setThreadName("CMtaOleClipboard::clipboardChangedNotifierThreadProc()");
+ CMtaOleClipboard* pInst = static_cast< CMtaOleClipboard* >( pParam );
+ OSL_ASSERT( nullptr != pInst );
+
+ sal::systools::CoInitializeGuard aGuard(COINIT_APARTMENTTHREADED, false,
+ sal::systools::CoInitializeGuard::WhenFailed::NoThrow);
+
+ // assuming we don't need a lock for
+ // a boolean variable like m_bRun...
+ while ( pInst->m_bRunClipboardNotifierThread )
+ {
+ // process window messages because of CoInitializeEx
+ MSG Msg;
+ while (PeekMessageW(&Msg, nullptr, 0, 0, PM_REMOVE))
+ DispatchMessageW(&Msg);
+
+ // wait for clipboard changed or terminate event
+ MsgWaitForMultipleObjects(2, pInst->m_hClipboardChangedNotifierEvents, false, INFINITE,
+ QS_ALLINPUT | QS_ALLPOSTMESSAGE);
+
+ std::unique_lock aGuard2( pInst->m_ClipboardChangedEventCountMutex );
+
+ if ( pInst->m_ClipboardChangedEventCount > 0 )
+ {
+ pInst->m_ClipboardChangedEventCount--;
+ if ( 0 == pInst->m_ClipboardChangedEventCount )
+ ResetEvent( pInst->m_hClipboardChangedEvent );
+
+ aGuard2.unlock( );
+
+ // nobody should touch m_pfncClipViewerCallback while we do
+ std::unique_lock aClipViewerGuard( pInst->m_pfncClipViewerCallbackMutex );
+
+ // notify all clipboard listener
+ if ( pInst->m_pfncClipViewerCallback )
+ pInst->m_pfncClipViewerCallback( );
+ }
+ else
+ aGuard2.unlock( );
+ }
+
+ return 0;
+}
+
+bool CMtaOleClipboard::WaitForThreadReady( ) const
+{
+ bool bRet = false;
+
+ if ( nullptr != m_hEvtThrdReady )
+ {
+ DWORD dwResult = WaitForSingleObject(
+ m_hEvtThrdReady, MAX_WAITTIME );
+ bRet = ( dwResult == WAIT_OBJECT_0 );
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/MtaOleClipb.hxx b/vcl/win/dtrans/MtaOleClipb.hxx
new file mode 100644
index 0000000000..f76becb50c
--- /dev/null
+++ b/vcl/win/dtrans/MtaOleClipb.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/types.h>
+#include <mutex>
+
+#include <objidl.h>
+
+// the Mta-Ole clipboard class is for internal use only!
+// only one instance of this class should be created, the
+// user has to ensure this!
+// the class is not thread-safe because it will be used
+// only from within the clipboard service and the methods
+// of the clipboard service are already synchronized
+
+class CMtaOleClipboard
+{
+public:
+ typedef void ( WINAPI *LPFNC_CLIPVIEWER_CALLBACK_t )( void );
+
+public:
+ CMtaOleClipboard( );
+ ~CMtaOleClipboard( );
+
+ // clipboard functions
+ HRESULT setClipboard( IDataObject* pIDataObject );
+ HRESULT getClipboard( IDataObject** ppIDataObject );
+ HRESULT flushClipboard( );
+
+ // register/unregister a clipboard viewer; there can only
+ // be one at a time; parameter NULL means unregister
+ // a clipboard viewer
+ // returns true on success else false; use GetLastError( ) in
+ // false case
+ bool registerClipViewer( LPFNC_CLIPVIEWER_CALLBACK_t pfncClipViewerCallback );
+
+private:
+ unsigned int run( );
+
+ // create a hidden window which serves as a request target; so we
+ // guarantee synchronization
+ void createMtaOleReqWnd( );
+
+ // message support
+ bool postMessage( UINT msg, WPARAM wParam = 0, LPARAM lParam = 0 );
+ LRESULT sendMessage( UINT msg, WPARAM wParam = 0, LPARAM lParam = 0 );
+
+ // message handler functions; remember these functions are called
+ // from a different thread context!
+
+ static HRESULT onSetClipboard( IDataObject* pIDataObject );
+ static HRESULT onGetClipboard( LPSTREAM* ppStream );
+ static HRESULT onFlushClipboard( );
+ bool onRegisterClipViewer( LPFNC_CLIPVIEWER_CALLBACK_t pfncClipViewerCallback );
+
+ // win32 clipboard listener support
+ LRESULT onClipboardUpdate();
+
+ static LRESULT CALLBACK mtaOleReqWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
+ static unsigned __stdcall oleThreadProc(void* pParam);
+
+ static unsigned __stdcall clipboardChangedNotifierThreadProc(void* pParam);
+
+ bool WaitForThreadReady( ) const;
+
+private:
+ HANDLE m_hOleThread;
+ unsigned m_uOleThreadId;
+ HANDLE m_hEvtThrdReady;
+ HWND m_hwndMtaOleReqWnd;
+ HANDLE m_hEvtWndDisposed;
+ ATOM m_MtaOleReqWndClassAtom;
+ LPFNC_CLIPVIEWER_CALLBACK_t m_pfncClipViewerCallback;
+ bool m_bInRegisterClipViewer;
+
+ bool m_bRunClipboardNotifierThread;
+ HANDLE m_hClipboardChangedNotifierThread;
+ HANDLE m_hClipboardChangedNotifierEvents[2];
+ HANDLE& m_hClipboardChangedEvent;
+ HANDLE& m_hTerminateClipboardChangedNotifierEvent;
+ std::mutex m_ClipboardChangedEventCountMutex;
+ sal_Int32 m_ClipboardChangedEventCount;
+
+ std::mutex m_pfncClipViewerCallbackMutex;
+
+ static CMtaOleClipboard* s_theMtaOleClipboardInst;
+
+ CMtaOleClipboard( const CMtaOleClipboard& ) = delete;
+ CMtaOleClipboard& operator=( const CMtaOleClipboard& ) = delete;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/TxtCnvtHlp.cxx b/vcl/win/dtrans/TxtCnvtHlp.cxx
new file mode 100644
index 0000000000..d7ab386fc1
--- /dev/null
+++ b/vcl/win/dtrans/TxtCnvtHlp.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 <osl/diagnose.h>
+
+#include "TxtCnvtHlp.hxx"
+#include "DTransHelper.hxx"
+#include "ImplHelper.hxx"
+
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::uno;
+
+// assuming a '\0' terminated string if no length specified
+
+static int CalcBuffSizeForTextConversion( UINT code_page, LPCSTR lpMultiByteString, int nLen = -1 )
+{
+ return ( MultiByteToWideChar( code_page,
+ 0,
+ lpMultiByteString,
+ nLen,
+ nullptr,
+ 0 ) * sizeof( sal_Unicode ) );
+}
+
+// assuming a '\0' terminated string if no length specified
+
+static int CalcBuffSizeForTextConversion( UINT code_page, LPCWSTR lpWideCharString, int nLen = -1 )
+{
+ return WideCharToMultiByte( code_page,
+ 0,
+ lpWideCharString,
+ nLen,
+ nullptr,
+ 0,
+ nullptr,
+ nullptr );
+}
+
+// converts text in one code page into unicode text
+// automatically calculates the necessary buffer size and allocates
+// the buffer
+
+int MultiByteToWideCharEx( UINT cp_src,
+ LPCSTR lpMultiByteString,
+ int lenStr,
+ CStgTransferHelper& refDTransHelper,
+ BOOL bEnsureTrailingZero )
+{
+ OSL_ASSERT( IsValidCodePage( cp_src ) );
+ OSL_ASSERT( nullptr != lpMultiByteString );
+
+ // calculate the required buff size
+ int reqSize = CalcBuffSizeForTextConversion( cp_src, lpMultiByteString, lenStr );
+
+ if ( bEnsureTrailingZero )
+ reqSize += sizeof( sal_Unicode );
+
+ // initialize the data-transfer helper
+ refDTransHelper.init( reqSize );
+
+ // setup a global memory pointer
+ CRawHGlobalPtr ptrHGlob( refDTransHelper );
+
+ // do the conversion and return
+ return MultiByteToWideChar( cp_src,
+ 0,
+ lpMultiByteString,
+ lenStr,
+ static_cast< LPWSTR >( ptrHGlob.GetMemPtr( ) ),
+ ptrHGlob.MemSize( ) );
+}
+
+// converts unicode text into text of the specified code page
+// automatically calculates the necessary buffer size and allocates
+// the buffer
+
+int WideCharToMultiByteEx( UINT cp_dest,
+ LPCWSTR lpWideCharString,
+ int lenStr,
+ CStgTransferHelper& refDTransHelper,
+ BOOL bEnsureTrailingZero )
+{
+ OSL_ASSERT( IsValidCodePage( cp_dest ) );
+ OSL_ASSERT( nullptr != lpWideCharString );
+
+ // calculate the required buff size
+ int reqSize = CalcBuffSizeForTextConversion( cp_dest, lpWideCharString, lenStr );
+
+ if ( bEnsureTrailingZero )
+ reqSize += sizeof( sal_Int8 );
+
+ // initialize the data-transfer helper
+ refDTransHelper.init( reqSize );
+
+ // setup a global memory pointer
+ CRawHGlobalPtr ptrHGlob( refDTransHelper );
+
+ // do the conversion and return
+ return WideCharToMultiByte( cp_dest,
+ 0,
+ lpWideCharString,
+ lenStr,
+ static_cast< LPSTR >( ptrHGlob.GetMemPtr( ) ),
+ ptrHGlob.MemSize( ),
+ nullptr,
+ nullptr );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/TxtCnvtHlp.hxx b/vcl/win/dtrans/TxtCnvtHlp.hxx
new file mode 100644
index 0000000000..5294879337
--- /dev/null
+++ b/vcl/win/dtrans/TxtCnvtHlp.hxx
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <com/sun/star/datatransfer/DataFlavor.hpp>
+
+#include "DTransHelper.hxx"
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+int MultiByteToWideCharEx( UINT cp_src,
+ LPCSTR lpMultiByteString,
+ int lenStr,
+ CStgTransferHelper& refDTransHelper,
+ BOOL bEnsureTrailingZero = TRUE );
+
+int WideCharToMultiByteEx( UINT cp_dest,
+ LPCWSTR lpWideCharString,
+ int lenStr,
+ CStgTransferHelper& refDTransHelper,
+ BOOL bEnsureTrailingZero = TRUE );
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/WinClip.hxx b/vcl/win/dtrans/WinClip.hxx
new file mode 100644
index 0000000000..f90d5eea06
--- /dev/null
+++ b/vcl/win/dtrans/WinClip.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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>
+
+const sal_Int32 CF_INVALID = 0;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/WinClipboard.cxx b/vcl/win/dtrans/WinClipboard.cxx
new file mode 100644
index 0000000000..1a8eaea151
--- /dev/null
+++ b/vcl/win/dtrans/WinClipboard.cxx
@@ -0,0 +1,383 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <comphelper/diagnose_ex.hxx>
+#include <com/sun/star/datatransfer/clipboard/ClipboardEvent.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/weak.hxx>
+#include <vcl/svapp.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+#include <com/sun/star/datatransfer/clipboard/RenderingCapabilities.hpp>
+#include "XNotifyingDataObject.hxx"
+
+#include <systools/win32/comtools.hxx>
+#include "DtObjFactory.hxx"
+#include "APNDataObject.hxx"
+#include "DOTransferable.hxx"
+#include "WinClipboard.hxx"
+
+#if !defined WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <ole2.h>
+#include <objidl.h>
+
+using namespace com::sun::star;
+
+namespace
+{
+CWinClipboard* s_pCWinClipbImpl = nullptr;
+osl::Mutex s_aClipboardSingletonMutex;
+}
+
+/*XEventListener,*/
+CWinClipboard::CWinClipboard(const uno::Reference<uno::XComponentContext>& rxContext,
+ const OUString& aClipboardName)
+ : m_xContext(rxContext)
+ , m_itsName(aClipboardName)
+ , m_pCurrentClipContent(nullptr)
+{
+ // necessary to reassociate from
+ // the static callback function
+ {
+ osl::MutexGuard aGuard(s_aClipboardSingletonMutex);
+ s_pCWinClipbImpl = this;
+ }
+
+ registerClipboardViewer();
+}
+
+CWinClipboard::~CWinClipboard()
+{
+ {
+ osl::MutexGuard aGuard(s_aClipboardSingletonMutex);
+ s_pCWinClipbImpl = nullptr;
+ }
+
+ unregisterClipboardViewer();
+}
+
+void CWinClipboard::disposing(std::unique_lock<std::mutex>& mutex)
+{
+ {
+ osl::MutexGuard aGuard(s_aClipboardSingletonMutex);
+ s_pCWinClipbImpl = nullptr;
+ }
+
+ unregisterClipboardViewer();
+
+ WeakComponentImplHelper::disposing(mutex);
+}
+
+// XClipboard
+
+// to avoid unnecessary traffic we check first if there is a clipboard
+// content which was set via setContent, in this case we don't need
+// to query the content from the clipboard, create a new wrapper object
+// and so on, we simply return the original XTransferable instead of our
+// DOTransferable
+
+uno::Reference<datatransfer::XTransferable> SAL_CALL CWinClipboard::getContents()
+{
+ osl::MutexGuard aGuard(m_aContentMutex);
+
+ if (m_bDisposed)
+ throw lang::DisposedException("object is already disposed",
+ static_cast<XClipboardEx*>(this));
+
+ // use the shortcut or create a transferable from
+ // system clipboard
+ {
+ osl::MutexGuard aGuard2(m_aContentCacheMutex);
+
+ if (nullptr != m_pCurrentClipContent)
+ return m_pCurrentClipContent->m_XTransferable;
+
+ // Content cached?
+ if (m_foreignContent.is())
+ return m_foreignContent;
+
+ // release the mutex, so that the variable may be
+ // changed by other threads
+ }
+
+ uno::Reference<datatransfer::XTransferable> rClipContent;
+
+ // get the current format list from clipboard
+ if (UINT nFormats; !GetUpdatedClipboardFormats(nullptr, 0, &nFormats)
+ && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ {
+ std::vector<UINT> aUINTFormats(nFormats);
+ if (GetUpdatedClipboardFormats(aUINTFormats.data(), nFormats, &nFormats))
+ {
+ std::vector<sal_uInt32> aFormats(aUINTFormats.begin(), aUINTFormats.end());
+ rClipContent = new CDOTransferable(m_xContext, this, aFormats);
+
+ osl::MutexGuard aGuard2(m_aContentCacheMutex);
+ m_foreignContent = rClipContent;
+ }
+ }
+
+ return rClipContent;
+}
+
+IDataObjectPtr CWinClipboard::getIDataObject()
+{
+ osl::MutexGuard aGuard(m_aContentMutex);
+
+ if (m_bDisposed)
+ throw lang::DisposedException("object is already disposed",
+ static_cast<XClipboardEx*>(this));
+
+ // get the current dataobject from clipboard
+ IDataObjectPtr pIDataObject;
+ HRESULT hr = m_MtaOleClipboard.getClipboard(&pIDataObject);
+
+ if (SUCCEEDED(hr))
+ {
+ // create an apartment neutral dataobject and initialize it with a
+ // com smart pointer to the IDataObject from clipboard
+ pIDataObject = new CAPNDataObject(pIDataObject);
+ }
+
+ return pIDataObject;
+}
+
+void SAL_CALL CWinClipboard::setContents(
+ const uno::Reference<datatransfer::XTransferable>& xTransferable,
+ const uno::Reference<datatransfer::clipboard::XClipboardOwner>& xClipboardOwner)
+{
+ osl::MutexGuard aGuard(m_aContentMutex);
+
+ if (m_bDisposed)
+ throw lang::DisposedException("object is already disposed",
+ static_cast<XClipboardEx*>(this));
+
+ IDataObjectPtr pIDataObj;
+
+ if (xTransferable.is())
+ {
+ {
+ osl::MutexGuard aGuard2(m_aContentCacheMutex);
+
+ m_foreignContent.clear();
+
+ m_pCurrentClipContent = new CXNotifyingDataObject(
+ CDTransObjFactory::createDataObjFromTransferable(m_xContext, xTransferable),
+ xTransferable, xClipboardOwner, this);
+ }
+
+ pIDataObj = IDataObjectPtr(m_pCurrentClipContent);
+ }
+
+ m_MtaOleClipboard.setClipboard(pIDataObj.get());
+}
+
+OUString SAL_CALL CWinClipboard::getName()
+{
+ if (m_bDisposed)
+ throw lang::DisposedException("object is already disposed",
+ static_cast<XClipboardEx*>(this));
+
+ return m_itsName;
+}
+
+// XFlushableClipboard
+
+void SAL_CALL CWinClipboard::flushClipboard()
+{
+ osl::MutexGuard aGuard(m_aContentMutex);
+
+ if (m_bDisposed)
+ throw lang::DisposedException("object is already disposed",
+ static_cast<XClipboardEx*>(this));
+
+ // actually it should be ClearableMutexGuard aGuard( m_aContentCacheMutex );
+ // but it does not work since FlushClipboard does a callback and frees DataObject
+ // which results in a deadlock in onReleaseDataObject.
+ // FlushClipboard had to be synchron in order to prevent shutdown until all
+ // clipboard-formats are rendered.
+ // The request is needed to prevent flushing if we are not clipboard owner (it is
+ // not known what happens if we flush but aren't clipboard owner).
+ // It may be possible to move the request to the clipboard STA thread by saving the
+ // DataObject and call OleIsCurrentClipboard before flushing.
+
+ if (nullptr != m_pCurrentClipContent)
+ m_MtaOleClipboard.flushClipboard();
+}
+
+// XClipboardEx
+
+sal_Int8 SAL_CALL CWinClipboard::getRenderingCapabilities()
+{
+ if (m_bDisposed)
+ throw lang::DisposedException("object is already disposed",
+ static_cast<XClipboardEx*>(this));
+
+ using namespace datatransfer::clipboard::RenderingCapabilities;
+ return (Delayed | Persistent);
+}
+
+// XClipboardNotifier
+
+void SAL_CALL CWinClipboard::addClipboardListener(
+ const uno::Reference<datatransfer::clipboard::XClipboardListener>& listener)
+{
+ if (m_bDisposed)
+ throw lang::DisposedException("object is already disposed",
+ static_cast<XClipboardEx*>(this));
+
+ // check input parameter
+ if (!listener.is())
+ throw lang::IllegalArgumentException("empty reference", static_cast<XClipboardEx*>(this),
+ 1);
+
+ std::unique_lock aGuard(m_aMutex);
+ maClipboardListeners.addInterface(aGuard, listener);
+}
+
+void SAL_CALL CWinClipboard::removeClipboardListener(
+ const uno::Reference<datatransfer::clipboard::XClipboardListener>& listener)
+{
+ if (m_bDisposed)
+ throw lang::DisposedException("object is already disposed",
+ static_cast<XClipboardEx*>(this));
+
+ // check input parameter
+ if (!listener.is())
+ throw lang::IllegalArgumentException("empty reference", static_cast<XClipboardEx*>(this),
+ 1);
+
+ std::unique_lock aGuard(m_aMutex);
+ maClipboardListeners.removeInterface(aGuard, listener);
+}
+
+void CWinClipboard::notifyAllClipboardListener()
+{
+ if (m_bDisposed)
+ return;
+
+ std::unique_lock aGuard(m_aMutex);
+ if (m_bDisposed)
+ return;
+
+ if (!maClipboardListeners.getLength(aGuard))
+ return;
+
+ try
+ {
+ uno::Reference<datatransfer::XTransferable> rXTransf(getContents());
+ datatransfer::clipboard::ClipboardEvent aClipbEvent(static_cast<XClipboard*>(this),
+ rXTransf);
+ maClipboardListeners.notifyEach(
+ aGuard, &datatransfer::clipboard::XClipboardListener::changedContents, aClipbEvent);
+ }
+ catch (const lang::DisposedException&)
+ {
+ OSL_FAIL("Service Manager disposed");
+
+ aGuard.unlock();
+ // no further clipboard changed notifications
+ unregisterClipboardViewer();
+ }
+}
+
+// XServiceInfo
+
+OUString SAL_CALL CWinClipboard::getImplementationName()
+{
+ return "com.sun.star.datatransfer.clipboard.ClipboardW32";
+}
+
+sal_Bool SAL_CALL CWinClipboard::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+uno::Sequence<OUString> SAL_CALL CWinClipboard::getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.clipboard.SystemClipboard" };
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+dtrans_CWinClipboard_get_implementation(css::uno::XComponentContext* context,
+ css::uno::Sequence<css::uno::Any> const& args)
+{
+ // We run unit tests in parallel, which is a problem when touching a shared resource
+ // like the system clipboard, so rather use the dummy GenericClipboard.
+ static const bool bRunningUnitTest = getenv("LO_TESTNAME");
+
+ if (bRunningUnitTest)
+ {
+ SolarMutexGuard aGuard;
+ auto xClipboard = ImplGetSVData()->mpDefInst->CreateClipboard(args);
+ if (xClipboard.is())
+ xClipboard->acquire();
+ return xClipboard.get();
+ }
+ else
+ {
+ return cppu::acquire(new CWinClipboard(context, ""));
+ }
+}
+
+void CWinClipboard::onReleaseDataObject(CXNotifyingDataObject* theCaller)
+{
+ OSL_ASSERT(nullptr != theCaller);
+
+ if (theCaller)
+ theCaller->lostOwnership();
+
+ // if the current caller is the one we currently hold, then set it to NULL
+ // because an external source must be the clipboardowner now
+ osl::MutexGuard aGuard(m_aContentCacheMutex);
+
+ if (m_pCurrentClipContent == theCaller)
+ m_pCurrentClipContent = nullptr;
+}
+
+void CWinClipboard::registerClipboardViewer()
+{
+ m_MtaOleClipboard.registerClipViewer(CWinClipboard::onClipboardContentChanged);
+}
+
+void CWinClipboard::unregisterClipboardViewer() { m_MtaOleClipboard.registerClipViewer(nullptr); }
+
+void WINAPI CWinClipboard::onClipboardContentChanged()
+{
+ osl::MutexGuard aGuard(s_aClipboardSingletonMutex);
+
+ // reassociation to instance through static member
+ if (nullptr != s_pCWinClipbImpl)
+ {
+ s_pCWinClipbImpl->m_foreignContent.clear();
+ s_pCWinClipbImpl->notifyAllClipboardListener();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/WinClipboard.hxx b/vcl/win/dtrans/WinClipboard.hxx
new file mode 100644
index 0000000000..fbaa1b2062
--- /dev/null
+++ b/vcl/win/dtrans/WinClipboard.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <comphelper/compbase.hxx>
+#include <comphelper/interfacecontainer4.hxx>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardOwner.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
+#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
+#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <osl/conditn.hxx>
+#include <systools/win32/comtools.hxx>
+
+#include "MtaOleClipb.hxx"
+#include "XNotifyingDataObject.hxx"
+
+// implements the XClipboard[Ex] ... interfaces
+// for the clipboard viewer mechanism we need a static callback function
+// and a static member to reassociate from this static function to the
+// class instance
+// watch out: we are using only one static member variable and not a list
+// because we assume to be instantiated only once
+// this will be assured by a OneInstanceFactory of the service and not
+// by this class!
+
+class CWinClipboard final
+ : public comphelper::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard,
+ css::datatransfer::clipboard::XFlushableClipboard,
+ css::lang::XServiceInfo>
+{
+ friend STDMETHODIMP_(ULONG) CXNotifyingDataObject::Release();
+
+ css::uno::Reference<css::uno::XComponentContext> m_xContext;
+ const OUString m_itsName;
+ CMtaOleClipboard m_MtaOleClipboard;
+ CXNotifyingDataObject* m_pCurrentClipContent;
+ com::sun::star::uno::Reference<com::sun::star::datatransfer::XTransferable> m_foreignContent;
+ osl::Mutex m_aContentMutex;
+ osl::Mutex m_aContentCacheMutex;
+ comphelper::OInterfaceContainerHelper4<css::datatransfer::clipboard::XClipboardListener>
+ maClipboardListeners;
+
+ void notifyAllClipboardListener();
+ void onReleaseDataObject(CXNotifyingDataObject* theCaller);
+
+ void registerClipboardViewer();
+ void unregisterClipboardViewer();
+
+ static void WINAPI onClipboardContentChanged();
+
+public:
+ CWinClipboard(const css::uno::Reference<css::uno::XComponentContext>& rxContext,
+ const OUString& aClipboardName);
+ virtual ~CWinClipboard() override;
+
+ // XClipboard
+ virtual css::uno::Reference<css::datatransfer::XTransferable> SAL_CALL getContents() override;
+ virtual void SAL_CALL setContents(
+ const css::uno::Reference<css::datatransfer::XTransferable>& xTransferable,
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& xClipboardOwner)
+ override;
+ virtual OUString SAL_CALL getName() override;
+
+ // XFlushableClipboard
+ virtual void SAL_CALL flushClipboard() override;
+
+ // XClipboardEx
+ virtual sal_Int8 SAL_CALL getRenderingCapabilities() override;
+
+ // XClipboardNotifier
+ virtual void SAL_CALL addClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+ override;
+ virtual void SAL_CALL removeClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+ 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;
+
+ IDataObjectPtr getIDataObject();
+
+ virtual void disposing(std::unique_lock<std::mutex>&) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/XNotifyingDataObject.cxx b/vcl/win/dtrans/XNotifyingDataObject.cxx
new file mode 100644
index 0000000000..aab168f043
--- /dev/null
+++ b/vcl/win/dtrans/XNotifyingDataObject.cxx
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <comphelper/diagnose_ex.hxx>
+#include "XNotifyingDataObject.hxx"
+#include "WinClipboard.hxx"
+
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::datatransfer::clipboard;
+using com::sun::star::uno::RuntimeException;
+using com::sun::star::uno::Reference;
+
+CXNotifyingDataObject::CXNotifyingDataObject(
+ const IDataObjectPtr& aIDataObject,
+ const Reference< XTransferable >& aXTransferable,
+ const Reference< XClipboardOwner >& aXClipOwner,
+ CWinClipboard* const theWinClipoard) :
+ m_nRefCnt( 0 ),
+ m_aIDataObject( aIDataObject ),
+ m_XTransferable( aXTransferable ),
+ m_XClipboardOwner( aXClipOwner ),
+ m_pWinClipImpl( theWinClipoard )
+{
+}
+
+STDMETHODIMP CXNotifyingDataObject::QueryInterface( REFIID iid, void** ppvObject )
+{
+ if ( nullptr == ppvObject )
+ return E_INVALIDARG;
+
+ HRESULT hr = E_NOINTERFACE;
+
+ *ppvObject = nullptr;
+ if ( ( __uuidof( IUnknown ) == iid ) ||
+ ( __uuidof( IDataObject ) == iid ) )
+ {
+ *ppvObject = static_cast< IUnknown* >( this );
+ static_cast<LPUNKNOWN>(*ppvObject)->AddRef( );
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+STDMETHODIMP_(ULONG) CXNotifyingDataObject::AddRef( )
+{
+ return static_cast< ULONG >( InterlockedIncrement( &m_nRefCnt ) );
+}
+
+STDMETHODIMP_(ULONG) CXNotifyingDataObject::Release( )
+{
+ ULONG nRefCnt =
+ static_cast< ULONG >( InterlockedDecrement( &m_nRefCnt ) );
+
+ if ( 0 == nRefCnt )
+ {
+ if ( m_pWinClipImpl )
+ m_pWinClipImpl->onReleaseDataObject( this );
+
+ delete this;
+ }
+
+ return nRefCnt;
+}
+
+STDMETHODIMP CXNotifyingDataObject::GetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium )
+{
+ return m_aIDataObject->GetData(pFormatetc, pmedium);
+}
+
+STDMETHODIMP CXNotifyingDataObject::EnumFormatEtc(
+ DWORD dwDirection, IEnumFORMATETC** ppenumFormatetc )
+{
+ return m_aIDataObject->EnumFormatEtc(dwDirection, ppenumFormatetc);
+}
+
+STDMETHODIMP CXNotifyingDataObject::QueryGetData( FORMATETC * pFormatetc )
+{
+ return m_aIDataObject->QueryGetData(pFormatetc);
+}
+
+STDMETHODIMP CXNotifyingDataObject::GetDataHere( FORMATETC * lpFetc, STGMEDIUM * lpStgMedium )
+{
+ return m_aIDataObject->GetDataHere(lpFetc, lpStgMedium);
+}
+
+STDMETHODIMP CXNotifyingDataObject::GetCanonicalFormatEtc( FORMATETC * lpFetc, FORMATETC * lpCanonicalFetc )
+{
+ return m_aIDataObject->GetCanonicalFormatEtc(lpFetc, lpCanonicalFetc);
+}
+
+STDMETHODIMP CXNotifyingDataObject::SetData( FORMATETC * lpFetc, STGMEDIUM * lpStgMedium, BOOL bRelease )
+{
+ return m_aIDataObject->SetData( lpFetc, lpStgMedium, bRelease );
+}
+
+STDMETHODIMP CXNotifyingDataObject::DAdvise(
+ FORMATETC * lpFetc, DWORD advf, IAdviseSink * lpAdvSink, DWORD* pdwConnection )
+{
+ return m_aIDataObject->DAdvise( lpFetc, advf, lpAdvSink, pdwConnection );
+}
+
+STDMETHODIMP CXNotifyingDataObject::DUnadvise( DWORD dwConnection )
+{
+ return m_aIDataObject->DUnadvise( dwConnection );
+}
+
+STDMETHODIMP CXNotifyingDataObject::EnumDAdvise( IEnumSTATDATA ** ppenumAdvise )
+{
+ return m_aIDataObject->EnumDAdvise( ppenumAdvise );
+}
+
+CXNotifyingDataObject::operator IDataObject*( )
+{
+ return static_cast< IDataObject* >( this );
+}
+
+void CXNotifyingDataObject::lostOwnership( )
+{
+ try
+ {
+ if (m_XClipboardOwner.is())
+ m_XClipboardOwner->lostOwnership(
+ static_cast<XClipboardEx*>(m_pWinClipImpl), m_XTransferable);
+ }
+ catch(RuntimeException&)
+ {
+ TOOLS_WARN_EXCEPTION( "vcl", "" );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/XNotifyingDataObject.hxx b/vcl/win/dtrans/XNotifyingDataObject.hxx
new file mode 100644
index 0000000000..408413a5de
--- /dev/null
+++ b/vcl/win/dtrans/XNotifyingDataObject.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardOwner.hpp>
+
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <objidl.h>
+
+#include <systools/win32/comtools.hxx>
+
+/*--------------------------------------------------------------------------
+ To implement the lostOwnership mechanism cleanly we need this wrapper
+ object
+----------------------------------------------------------------------------*/
+
+// forward
+class CWinClipboard;
+
+class CXNotifyingDataObject final : public IDataObject
+{
+public:
+ CXNotifyingDataObject(
+ const IDataObjectPtr& aIDataObject,
+ const css::uno::Reference< css::datatransfer::XTransferable >& aXTransferable,
+ const css::uno::Reference< css::datatransfer::clipboard::XClipboardOwner >& aXClipOwner,
+ CWinClipboard* const theWinClipoard);
+
+ virtual ~CXNotifyingDataObject() {}
+
+ // ole interface implementation
+
+ //IUnknown interface methods
+ STDMETHODIMP QueryInterface(REFIID iid, void** ppvObject) override;
+ STDMETHODIMP_( ULONG ) AddRef( ) override;
+ STDMETHODIMP_( ULONG ) Release( ) override;
+
+ // IDataObject interface methods
+ STDMETHODIMP GetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium ) override;
+ STDMETHODIMP GetDataHere( FORMATETC * pFormatetc, STGMEDIUM * pmedium ) override;
+ STDMETHODIMP QueryGetData( FORMATETC * pFormatetc ) override;
+ STDMETHODIMP GetCanonicalFormatEtc( FORMATETC * pFormatectIn, FORMATETC * pFormatetcOut ) override;
+ STDMETHODIMP SetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium, BOOL fRelease ) override;
+ STDMETHODIMP EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC** ppenumFormatetc ) override;
+ STDMETHODIMP DAdvise( FORMATETC * pFormatetc, DWORD advf, IAdviseSink * pAdvSink, DWORD* pdwConnection ) override;
+ STDMETHODIMP DUnadvise( DWORD dwConnection ) override;
+ STDMETHODIMP EnumDAdvise( IEnumSTATDATA** ppenumAdvise ) override;
+
+ operator IDataObject*( );
+
+private:
+ void lostOwnership( );
+
+ sal_Int32 m_nRefCnt;
+ IDataObjectPtr m_aIDataObject;
+ const css::uno::Reference< css::datatransfer::XTransferable > m_XTransferable;
+ const css::uno::Reference< css::datatransfer::clipboard::XClipboardOwner > m_XClipboardOwner;
+ CWinClipboard* const m_pWinClipImpl;
+
+ friend class CWinClipboard;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/XTDataObject.cxx b/vcl/win/dtrans/XTDataObject.cxx
new file mode 100644
index 0000000000..b3f5ccb303
--- /dev/null
+++ b/vcl/win/dtrans/XTDataObject.cxx
@@ -0,0 +1,751 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <o3tl/char16_t2wchar_t.hxx>
+#include <o3tl/safeint.hxx>
+
+#include "XTDataObject.hxx"
+#include <com/sun/star/datatransfer/DataFlavor.hpp>
+#include "ImplHelper.hxx"
+#include "DTransHelper.hxx"
+#include "TxtCnvtHlp.hxx"
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
+#include <com/sun/star/awt/AsyncCallback.hpp>
+#include <com/sun/star/awt/XCallback.hpp>
+#include "FmtFilter.hxx"
+#include <cppuhelper/implbase.hxx>
+
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <shlobj.h>
+
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::datatransfer::clipboard;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+
+namespace {
+
+void setupStgMedium( const FORMATETC& fetc,
+ CStgTransferHelper& stgTransHlp,
+ STGMEDIUM& stgmedium )
+{
+ stgmedium.pUnkForRelease = nullptr;
+
+ if ( fetc.cfFormat == CF_METAFILEPICT )
+ {
+ stgmedium.tymed = TYMED_MFPICT;
+ stgmedium.hMetaFilePict = static_cast< HMETAFILEPICT >( stgTransHlp.getHGlobal( ) );
+ }
+ else if ( fetc.cfFormat == CF_ENHMETAFILE )
+ {
+ stgmedium.tymed = TYMED_ENHMF;
+ stgmedium.hEnhMetaFile = static_cast< HENHMETAFILE >( stgTransHlp.getHGlobal( ) );
+ }
+ else if ( fetc.tymed & TYMED_HGLOBAL )
+ {
+ stgmedium.tymed = TYMED_HGLOBAL;
+ stgmedium.hGlobal = stgTransHlp.getHGlobal( );
+ }
+ else if ( fetc.tymed & TYMED_ISTREAM )
+ {
+ stgmedium.tymed = TYMED_ISTREAM;
+ stgTransHlp.getIStream( &stgmedium.pstm );
+ }
+ else
+ OSL_ASSERT( false );
+}
+
+/**
+ We need to destroy XTransferable in the main thread to avoid dead lock
+ when locking in the clipboard thread. So we transfer the ownership of the
+ XTransferable reference to this object and release it when the callback
+ is executed in main thread.
+*/
+class AsyncDereference : public cppu::WeakImplHelper<css::awt::XCallback>
+{
+ Reference<XTransferable> maTransferable;
+
+public:
+ AsyncDereference(css::uno::Reference<css::datatransfer::XTransferable> const & rTransferable)
+ : maTransferable(rTransferable)
+ {}
+
+ virtual void SAL_CALL notify(css::uno::Any const &) override
+ {
+ maTransferable.clear();
+ }
+};
+
+// a helper class that will be thrown by the function validateFormatEtc
+
+class CInvalidFormatEtcException
+{
+public:
+ HRESULT m_hr;
+ explicit CInvalidFormatEtcException( HRESULT hr ) : m_hr( hr ) {};
+};
+
+void validateFormatEtc( LPFORMATETC lpFormatEtc )
+{
+ OSL_ASSERT( lpFormatEtc );
+
+ if ( lpFormatEtc->lindex != -1 )
+ throw CInvalidFormatEtcException( DV_E_LINDEX );
+
+ if ( !(lpFormatEtc->dwAspect & DVASPECT_CONTENT) &&
+ !(lpFormatEtc->dwAspect & DVASPECT_SHORTNAME) )
+ throw CInvalidFormatEtcException( DV_E_DVASPECT );
+
+ if ( !(lpFormatEtc->tymed & TYMED_HGLOBAL) &&
+ !(lpFormatEtc->tymed & TYMED_ISTREAM) &&
+ !(lpFormatEtc->tymed & TYMED_MFPICT) &&
+ !(lpFormatEtc->tymed & TYMED_ENHMF) )
+ throw CInvalidFormatEtcException( DV_E_TYMED );
+
+ if ( lpFormatEtc->cfFormat == CF_METAFILEPICT &&
+ !(lpFormatEtc->tymed & TYMED_MFPICT) )
+ throw CInvalidFormatEtcException( DV_E_TYMED );
+
+ if ( lpFormatEtc->cfFormat == CF_ENHMETAFILE &&
+ !(lpFormatEtc->tymed & TYMED_ENHMF) )
+ throw CInvalidFormatEtcException( DV_E_TYMED );
+}
+
+void invalidateStgMedium( STGMEDIUM& stgmedium )
+{
+ stgmedium.tymed = TYMED_NULL;
+}
+
+HRESULT translateStgExceptionCode( HRESULT hr )
+{
+ HRESULT hrTransl;
+
+ switch( hr )
+ {
+ case STG_E_MEDIUMFULL:
+ hrTransl = hr;
+ break;
+
+ default:
+ hrTransl = E_UNEXPECTED;
+ break;
+ }
+
+ return hrTransl;
+}
+
+// inline
+void renderDataAndSetupStgMedium(
+ const sal_Int8* lpStorage, const FORMATETC& fetc, sal_uInt32 nInitStgSize,
+ sal_uInt32 nBytesToTransfer, STGMEDIUM& stgmedium )
+{
+ OSL_PRECOND( !nInitStgSize || (nInitStgSize >= nBytesToTransfer),
+ "Memory size less than number of bytes to transfer" );
+
+ CStgTransferHelper stgTransfHelper( AUTO_INIT );
+
+ // setup storage size
+ if ( nInitStgSize > 0 )
+ stgTransfHelper.init( nInitStgSize );
+
+#if OSL_DEBUG_LEVEL > 0
+ sal_uInt32 nBytesWritten = 0;
+ stgTransfHelper.write( lpStorage, nBytesToTransfer, &nBytesWritten );
+ OSL_ASSERT( nBytesWritten == nBytesToTransfer );
+#else
+ stgTransfHelper.write( lpStorage, nBytesToTransfer );
+#endif
+
+ setupStgMedium( fetc, stgTransfHelper, stgmedium );
+}
+
+}
+
+CXTDataObject::CXTDataObject( const Reference< XComponentContext >& rxContext,
+ const Reference< XTransferable >& aXTransferable )
+ : m_nRefCnt( 0 )
+ , m_XTransferable( aXTransferable )
+ , m_XComponentContext( rxContext )
+ , m_bFormatEtcContainerInitialized( false )
+ , m_DataFormatTranslator( rxContext )
+ , m_FormatRegistrar( rxContext, m_DataFormatTranslator )
+{
+}
+
+CXTDataObject::~CXTDataObject()
+{
+ css::awt::AsyncCallback::create(m_XComponentContext)->addCallback(
+ new AsyncDereference(m_XTransferable),
+ css::uno::Any());
+}
+
+// IUnknown->QueryInterface
+
+STDMETHODIMP CXTDataObject::QueryInterface( REFIID iid, void** ppvObject )
+{
+ if ( nullptr == ppvObject )
+ return E_INVALIDARG;
+
+ HRESULT hr = E_NOINTERFACE;
+
+ *ppvObject = nullptr;
+ if ( ( __uuidof( IUnknown ) == iid ) ||
+ ( __uuidof( IDataObject ) == iid ) )
+ {
+ *ppvObject = static_cast< IUnknown* >( this );
+ static_cast<LPUNKNOWN>(*ppvObject)->AddRef( );
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+// IUnknown->AddRef
+
+STDMETHODIMP_(ULONG) CXTDataObject::AddRef( )
+{
+ return static_cast< ULONG >( InterlockedIncrement( &m_nRefCnt ) );
+}
+
+// IUnknown->Release
+
+STDMETHODIMP_(ULONG) CXTDataObject::Release( )
+{
+ ULONG nRefCnt =
+ static_cast< ULONG >( InterlockedDecrement( &m_nRefCnt ) );
+
+ if ( 0 == nRefCnt )
+ delete this;
+
+ return nRefCnt;
+}
+
+STDMETHODIMP CXTDataObject::GetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium )
+{
+ if ( !(pFormatetc && pmedium) )
+ return E_INVALIDARG;
+
+ try
+ {
+ // prepare data transfer
+ invalidateStgMedium( *pmedium );
+ validateFormatEtc( pFormatetc );
+
+ // handle locale request, because locale is an artificial format for us
+ if ( CF_LOCALE == pFormatetc->cfFormat )
+ renderLocaleAndSetupStgMedium( *pFormatetc, *pmedium );
+ else if ( CF_UNICODETEXT == pFormatetc->cfFormat )
+ renderUnicodeAndSetupStgMedium( *pFormatetc, *pmedium );
+ else
+ renderAnyDataAndSetupStgMedium( *pFormatetc, *pmedium );
+ }
+ catch(UnsupportedFlavorException&)
+ {
+ HRESULT hr = DV_E_FORMATETC;
+
+ CFormatEtc aFormatetc(*pFormatetc);
+ if (CFormatRegistrar::isSynthesizeableFormat(aFormatetc))
+ hr = renderSynthesizedFormatAndSetupStgMedium( *pFormatetc, *pmedium );
+
+ return hr;
+ }
+ catch( CInvalidFormatEtcException& ex )
+ {
+ return ex.m_hr;
+ }
+ catch( CStgTransferHelper::CStgTransferException& ex )
+ {
+ return translateStgExceptionCode( ex.m_hr );
+ }
+ catch(...)
+ {
+ return E_UNEXPECTED;
+ }
+
+ return S_OK;
+}
+
+//inline
+void CXTDataObject::renderLocaleAndSetupStgMedium(
+ FORMATETC const & fetc, STGMEDIUM& stgmedium )
+{
+ if ( !m_FormatRegistrar.hasSynthesizedLocale( ) )
+ throw CInvalidFormatEtcException( DV_E_FORMATETC );
+ LCID lcid = CFormatRegistrar::getSynthesizedLocale( );
+ renderDataAndSetupStgMedium(
+ reinterpret_cast< sal_Int8* >( &lcid ),
+ fetc,
+ 0,
+ sizeof( LCID ),
+ stgmedium );
+}
+
+void CXTDataObject::renderUnicodeAndSetupStgMedium(
+ FORMATETC const & fetc, STGMEDIUM& stgmedium )
+{
+ DataFlavor aFlavor = formatEtcToDataFlavor( fetc );
+
+ Any aAny = m_XTransferable->getTransferData( aFlavor );
+
+ // unfortunately not all transferables fulfill the
+ // spec. and do throw an UnsupportedFlavorException
+ // so we must check the any
+ if ( !aAny.hasValue( ) )
+ {
+ OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" );
+ throw UnsupportedFlavorException( );
+ }
+
+ OUString aText;
+ aAny >>= aText;
+
+ sal_uInt32 nBytesToTransfer = aText.getLength( ) * sizeof( sal_Unicode );
+
+ // to be sure there is an ending 0
+ sal_uInt32 nRequiredMemSize = nBytesToTransfer + sizeof( sal_Unicode );
+
+ renderDataAndSetupStgMedium(
+ reinterpret_cast< const sal_Int8* >( aText.getStr( ) ),
+ fetc,
+ nRequiredMemSize,
+ nBytesToTransfer,
+ stgmedium );
+}
+
+void CXTDataObject::renderAnyDataAndSetupStgMedium(
+ FORMATETC& fetc, STGMEDIUM& stgmedium )
+{
+ DataFlavor aFlavor = formatEtcToDataFlavor( fetc );
+
+ Any aAny = m_XTransferable->getTransferData( aFlavor );
+
+ // unfortunately not all transferables fulfill the
+ // spec. and do throw an UnsupportedFlavorException
+ // so we must check the any
+ if ( !aAny.hasValue( ) )
+ {
+ OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" );
+ throw UnsupportedFlavorException( );
+ }
+
+ Sequence< sal_Int8 > clipDataStream;
+ aAny >>= clipDataStream;
+
+ sal_uInt32 nRequiredMemSize = 0;
+ if ( CDataFormatTranslator::isOemOrAnsiTextFormat( fetc.cfFormat ) )
+ nRequiredMemSize = sizeof( sal_Int8 ) * clipDataStream.getLength( ) + 1;
+
+ // prepare data for transmission
+ // #i124085# DIBV5 should not happen for now, but keep as hint here
+ if ( CF_DIBV5 == fetc.cfFormat || CF_DIB == fetc.cfFormat )
+ {
+#ifdef DBG_UTIL
+ if(CF_DIBV5 == fetc.cfFormat)
+ {
+ OSL_ENSURE(o3tl::make_unsigned(clipDataStream.getLength()) > (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPV5HEADER)), "Wrong size on CF_DIBV5 data (!)");
+ }
+ else // CF_DIB == fetc.cfFormat
+ {
+ OSL_ENSURE(o3tl::make_unsigned(clipDataStream.getLength()) > (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)), "Wrong size on CF_DIB data (!)");
+ }
+#endif
+
+ // remove BITMAPFILEHEADER
+ clipDataStream = OOBmpToWinDIB( clipDataStream );
+ }
+
+ if ( CF_METAFILEPICT == fetc.cfFormat )
+ {
+ stgmedium.tymed = TYMED_MFPICT;
+ stgmedium.hMetaFilePict = OOMFPictToWinMFPict( clipDataStream );
+ stgmedium.pUnkForRelease = nullptr;
+ }
+ else if( CF_ENHMETAFILE == fetc.cfFormat )
+ {
+ stgmedium.tymed = TYMED_ENHMF;
+ stgmedium.hMetaFilePict = OOMFPictToWinENHMFPict( clipDataStream );
+ stgmedium.pUnkForRelease = nullptr;
+ }
+ else
+ renderDataAndSetupStgMedium(
+ clipDataStream.getArray( ),
+ fetc,
+ nRequiredMemSize,
+ clipDataStream.getLength( ),
+ stgmedium );
+}
+
+HRESULT CXTDataObject::renderSynthesizedFormatAndSetupStgMedium( FORMATETC& fetc, STGMEDIUM& stgmedium )
+{
+ HRESULT hr = S_OK;
+
+ try
+ {
+ if ( CF_UNICODETEXT == fetc.cfFormat )
+ // the transferable seems to have only text
+ renderSynthesizedUnicodeAndSetupStgMedium( fetc, stgmedium );
+ else if ( CDataFormatTranslator::isOemOrAnsiTextFormat( fetc.cfFormat ) )
+ // the transferable seems to have only unicode text
+ renderSynthesizedTextAndSetupStgMedium( fetc, stgmedium );
+ else
+ // the transferable seems to have only text/html
+ renderSynthesizedHtmlAndSetupStgMedium( fetc, stgmedium );
+ }
+ catch(UnsupportedFlavorException&)
+ {
+ hr = DV_E_FORMATETC;
+ }
+ catch( CInvalidFormatEtcException& )
+ {
+ OSL_FAIL( "Unexpected exception" );
+ }
+ catch( CStgTransferHelper::CStgTransferException& ex )
+ {
+ return translateStgExceptionCode( ex.m_hr );
+ }
+ catch(...)
+ {
+ hr = E_UNEXPECTED;
+ }
+
+ return hr;
+}
+
+// the transferable must have only text, so we will synthesize unicode text
+
+void CXTDataObject::renderSynthesizedUnicodeAndSetupStgMedium( FORMATETC const & fetc, STGMEDIUM& stgmedium )
+{
+ OSL_ASSERT( CF_UNICODETEXT == fetc.cfFormat );
+
+ Any aAny = m_XTransferable->getTransferData( m_FormatRegistrar.getRegisteredTextFlavor( ) );
+
+ // unfortunately not all transferables fulfill the
+ // spec. and do throw an UnsupportedFlavorException
+ // so we must check the any
+ if ( !aAny.hasValue( ) )
+ {
+ OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" );
+ throw UnsupportedFlavorException( );
+ }
+
+ Sequence< sal_Int8 > aText;
+ aAny >>= aText;
+
+ CStgTransferHelper stgTransfHelper;
+
+ MultiByteToWideCharEx(
+ CFormatRegistrar::getRegisteredTextCodePage( ),
+ reinterpret_cast< char* >( aText.getArray( ) ),
+ aText.getLength( ),
+ stgTransfHelper );
+
+ setupStgMedium( fetc, stgTransfHelper, stgmedium );
+}
+
+// the transferable must have only unicode text so we will synthesize text
+
+void CXTDataObject::renderSynthesizedTextAndSetupStgMedium( FORMATETC& fetc, STGMEDIUM& stgmedium )
+{
+ OSL_ASSERT( CDataFormatTranslator::isOemOrAnsiTextFormat( fetc.cfFormat ) );
+
+ DataFlavor aFlavor = formatEtcToDataFlavor(
+ CDataFormatTranslator::getFormatEtcForClipformat( CF_UNICODETEXT ) );
+
+ Any aAny = m_XTransferable->getTransferData( aFlavor );
+
+ // unfortunately not all transferables fulfill the
+ // spec. and do throw an UnsupportedFlavorException
+ // so we must check the any
+ if ( !aAny.hasValue( ) )
+ {
+ OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" );
+ throw UnsupportedFlavorException( );
+ }
+
+ OUString aUnicodeText;
+ aAny >>= aUnicodeText;
+
+ CStgTransferHelper stgTransfHelper;
+
+ WideCharToMultiByteEx(
+ GetACP( ),
+ o3tl::toW( aUnicodeText.getStr( ) ),
+ aUnicodeText.getLength( ),
+ stgTransfHelper );
+
+ setupStgMedium( fetc, stgTransfHelper, stgmedium );
+}
+
+void CXTDataObject::renderSynthesizedHtmlAndSetupStgMedium( FORMATETC& fetc, STGMEDIUM& stgmedium )
+{
+ OSL_ASSERT( CDataFormatTranslator::isHTMLFormat( fetc.cfFormat ) );
+
+ DataFlavor aFlavor;
+
+ // creating a DataFlavor on the fly
+ aFlavor.MimeType = "text/html";
+ aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ Any aAny = m_XTransferable->getTransferData( aFlavor );
+
+ // unfortunately not all transferables fulfill the
+ // spec. and do throw an UnsupportedFlavorException
+ // so we must check the any
+ if ( !aAny.hasValue( ) )
+ {
+ OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" );
+ throw UnsupportedFlavorException( );
+ }
+
+ Sequence< sal_Int8 > aTextHtmlSequence;
+ aAny >>= aTextHtmlSequence;
+
+ Sequence< sal_Int8 > aHTMLFormatSequence = TextHtmlToHTMLFormat( aTextHtmlSequence );
+
+ sal_uInt32 nBytesToTransfer = aHTMLFormatSequence.getLength( );
+
+ renderDataAndSetupStgMedium(
+ reinterpret_cast< const sal_Int8* >( aHTMLFormatSequence.getArray( ) ),
+ fetc,
+ 0,
+ nBytesToTransfer,
+ stgmedium );
+}
+
+// IDataObject->EnumFormatEtc
+
+STDMETHODIMP CXTDataObject::EnumFormatEtc(
+ DWORD dwDirection, IEnumFORMATETC** ppenumFormatetc )
+{
+ if ( nullptr == ppenumFormatetc )
+ return E_INVALIDARG;
+
+ if ( DATADIR_SET == dwDirection )
+ return E_NOTIMPL;
+
+ *ppenumFormatetc = nullptr;
+
+ InitializeFormatEtcContainer( );
+
+ HRESULT hr;
+ if ( DATADIR_GET == dwDirection )
+ {
+ *ppenumFormatetc = new CEnumFormatEtc( this, m_FormatEtcContainer );
+ static_cast< LPUNKNOWN >( *ppenumFormatetc )->AddRef( );
+
+ hr = S_OK;
+ }
+ else
+ hr = E_INVALIDARG;
+
+ return hr;
+}
+
+// IDataObject->QueryGetData
+
+STDMETHODIMP CXTDataObject::QueryGetData( FORMATETC * pFormatetc )
+{
+ if ( (nullptr == pFormatetc) || IsBadReadPtr( pFormatetc, sizeof( FORMATETC ) ) )
+ return E_INVALIDARG;
+
+ InitializeFormatEtcContainer( );
+
+ CFormatEtc aFormatetc(*pFormatetc);
+ return m_FormatEtcContainer.hasFormatEtc(aFormatetc) ? S_OK : S_FALSE;
+}
+
+// IDataObject->GetDataHere
+
+STDMETHODIMP CXTDataObject::GetDataHere( FORMATETC *, STGMEDIUM * )
+{
+ return E_NOTIMPL;
+}
+
+// IDataObject->GetCanonicalFormatEtc
+
+STDMETHODIMP CXTDataObject::GetCanonicalFormatEtc( FORMATETC *, FORMATETC * )
+{
+ return E_NOTIMPL;
+}
+
+// IDataObject->SetData
+
+STDMETHODIMP CXTDataObject::SetData( FORMATETC *, STGMEDIUM *, BOOL )
+{
+ return E_NOTIMPL;
+}
+
+// IDataObject->DAdvise
+
+STDMETHODIMP CXTDataObject::DAdvise( FORMATETC *, DWORD, IAdviseSink *, DWORD * )
+{
+ return E_NOTIMPL;
+}
+
+// IDataObject->DUnadvise
+
+STDMETHODIMP CXTDataObject::DUnadvise( DWORD )
+{
+ return E_NOTIMPL;
+}
+
+// IDataObject->EnumDAdvise
+
+STDMETHODIMP CXTDataObject::EnumDAdvise( IEnumSTATDATA ** )
+{
+ return E_NOTIMPL;
+}
+
+// for our convenience
+
+CXTDataObject::operator IDataObject*( )
+{
+ return static_cast< IDataObject* >( this );
+}
+
+inline
+DataFlavor CXTDataObject::formatEtcToDataFlavor( const FORMATETC& aFormatEtc ) const
+{
+ DataFlavor aFlavor;
+
+ if ( m_FormatRegistrar.hasSynthesizedLocale( ) )
+ aFlavor = m_DataFormatTranslator.getDataFlavorFromFormatEtc(
+ aFormatEtc.cfFormat, CFormatRegistrar::getSynthesizedLocale());
+ else
+ aFlavor = m_DataFormatTranslator.getDataFlavorFromFormatEtc(aFormatEtc.cfFormat);
+
+ if ( !aFlavor.MimeType.getLength( ) )
+ throw UnsupportedFlavorException( );
+
+ return aFlavor;
+}
+
+inline void CXTDataObject::InitializeFormatEtcContainer( )
+{
+ if ( !m_bFormatEtcContainerInitialized )
+ {
+ m_FormatRegistrar.RegisterFormats( m_XTransferable, m_FormatEtcContainer );
+ m_bFormatEtcContainerInitialized = true;
+ }
+}
+
+CEnumFormatEtc::CEnumFormatEtc( LPUNKNOWN lpUnkOuter, const CFormatEtcContainer& aFormatEtcContainer ) :
+ m_nRefCnt( 0 ),
+ m_lpUnkOuter( lpUnkOuter ),
+ m_FormatEtcContainer( aFormatEtcContainer )
+{
+ Reset( );
+}
+
+// IUnknown->QueryInterface
+
+STDMETHODIMP CEnumFormatEtc::QueryInterface( REFIID iid, void** ppvObject )
+{
+ if ( nullptr == ppvObject )
+ return E_INVALIDARG;
+
+ HRESULT hr = E_NOINTERFACE;
+
+ *ppvObject = nullptr;
+
+ if ( ( __uuidof( IUnknown ) == iid ) ||
+ ( __uuidof( IEnumFORMATETC ) == iid ) )
+ {
+ *ppvObject = static_cast< IUnknown* >( this );
+ static_cast< LPUNKNOWN >( *ppvObject )->AddRef( );
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+// IUnknown->AddRef
+
+STDMETHODIMP_(ULONG) CEnumFormatEtc::AddRef( )
+{
+ // keep the dataobject alive
+ m_lpUnkOuter->AddRef( );
+ return InterlockedIncrement( &m_nRefCnt );
+}
+
+// IUnknown->Release
+
+STDMETHODIMP_(ULONG) CEnumFormatEtc::Release( )
+{
+ // release the outer dataobject
+ m_lpUnkOuter->Release( );
+
+ ULONG nRefCnt = InterlockedDecrement( &m_nRefCnt );
+ if ( 0 == nRefCnt )
+ delete this;
+
+ return nRefCnt;
+}
+
+// IEnumFORMATETC->Next
+
+STDMETHODIMP CEnumFormatEtc::Next( ULONG nRequested, FORMATETC * lpDest, ULONG* lpFetched )
+{
+ if ( ( nRequested < 1 ) ||
+ (( nRequested > 1 ) && ( nullptr == lpFetched )) ||
+ IsBadWritePtr( lpDest, sizeof( FORMATETC ) * nRequested ) )
+ return E_INVALIDARG;
+
+ sal_uInt32 nFetched = m_FormatEtcContainer.nextFormatEtc( lpDest, nRequested );
+
+ if ( nullptr != lpFetched )
+ *lpFetched = nFetched;
+
+ return (nFetched == nRequested) ? S_OK : S_FALSE;
+}
+
+// IEnumFORMATETC->Skip
+
+STDMETHODIMP CEnumFormatEtc::Skip( ULONG celt )
+{
+ return m_FormatEtcContainer.skipFormatEtc( celt ) ? S_OK : S_FALSE;
+}
+
+// IEnumFORMATETC->Reset
+
+STDMETHODIMP CEnumFormatEtc::Reset( )
+{
+ m_FormatEtcContainer.beginEnumFormatEtc( );
+ return S_OK;
+}
+
+// IEnumFORMATETC->Clone
+
+STDMETHODIMP CEnumFormatEtc::Clone( IEnumFORMATETC** ppenum )
+{
+ if ( nullptr == ppenum )
+ return E_INVALIDARG;
+
+ *ppenum = new CEnumFormatEtc( m_lpUnkOuter, m_FormatEtcContainer );
+ static_cast< LPUNKNOWN >( *ppenum )->AddRef( );
+
+ return S_OK;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/XTDataObject.hxx b/vcl/win/dtrans/XTDataObject.hxx
new file mode 100644
index 0000000000..77f8c53f29
--- /dev/null
+++ b/vcl/win/dtrans/XTDataObject.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardOwner.hpp>
+
+#include "DataFmtTransl.hxx"
+
+#include "FetcList.hxx"
+
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <ole2.h>
+#include <objidl.h>
+
+/*--------------------------------------------------------------------------
+ - the function principle of the windows clipboard:
+ a data provider offers all formats he can deliver on the clipboard
+ a clipboard client ask for the available formats on the clipboard
+ and decides if there is a format he can use
+ if there is one, he requests the data in this format
+
+ - This class inherits from IDataObject and so can be placed on the
+ OleClipboard. The class wraps a transferable object which is the
+ original DataSource
+ - DataFlavors offered by this transferable will be translated into
+ appropriate clipboard formats
+ - if the transferable contains text data always text and unicodetext
+ will be offered or vice versa
+ - text data will be automatically converted between text and unicode text
+ - although the transferable may support text in different charsets
+ (codepages) only text in one codepage can be offered by the clipboard
+
+----------------------------------------------------------------------------*/
+
+class CStgTransferHelper;
+
+class CXTDataObject : public IDataObject
+{
+public:
+ CXTDataObject( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ const css::uno::Reference< css::datatransfer::XTransferable >& aXTransferable );
+ virtual ~CXTDataObject();
+
+ // ole interface implementation
+
+ //IUnknown interface methods
+ STDMETHODIMP QueryInterface(REFIID iid, void** ppvObject) override;
+ STDMETHODIMP_( ULONG ) AddRef( ) override;
+ STDMETHODIMP_( ULONG ) Release( ) override;
+
+ // IDataObject interface methods
+ STDMETHODIMP GetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium ) override;
+ STDMETHODIMP GetDataHere( FORMATETC * pFormatetc, STGMEDIUM * pmedium ) override;
+ STDMETHODIMP QueryGetData( FORMATETC * pFormatetc ) override;
+ STDMETHODIMP GetCanonicalFormatEtc( FORMATETC * pFormatectIn, FORMATETC * pFormatetcOut ) override;
+ STDMETHODIMP SetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium, BOOL fRelease ) override;
+ STDMETHODIMP EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC** ppenumFormatetc ) override;
+ STDMETHODIMP DAdvise( FORMATETC * pFormatetc, DWORD advf, IAdviseSink * pAdvSink, DWORD* pdwConnection ) override;
+ STDMETHODIMP DUnadvise( DWORD dwConnection ) override;
+ STDMETHODIMP EnumDAdvise( IEnumSTATDATA** ppenumAdvise ) override;
+
+ operator IDataObject*( );
+
+private:
+ css::datatransfer::DataFlavor formatEtcToDataFlavor( const FORMATETC& aFormatEtc ) const;
+
+ void renderLocaleAndSetupStgMedium( FORMATETC const & fetc, STGMEDIUM& stgmedium );
+ void renderUnicodeAndSetupStgMedium( FORMATETC const & fetc, STGMEDIUM& stgmedium );
+ void renderAnyDataAndSetupStgMedium( FORMATETC& fetc, STGMEDIUM& stgmedium );
+
+ HRESULT renderSynthesizedFormatAndSetupStgMedium( FORMATETC& fetc, STGMEDIUM& stgmedium );
+ void renderSynthesizedUnicodeAndSetupStgMedium( FORMATETC const & fetc, STGMEDIUM& stgmedium );
+ void renderSynthesizedTextAndSetupStgMedium( FORMATETC& fetc, STGMEDIUM& stgmedium );
+ void renderSynthesizedHtmlAndSetupStgMedium( FORMATETC& fetc, STGMEDIUM& stgmedium );
+
+ inline void InitializeFormatEtcContainer( );
+
+private:
+ LONG m_nRefCnt;
+ css::uno::Reference< css::datatransfer::XTransferable > m_XTransferable;
+ css::uno::Reference< css::uno::XComponentContext> m_XComponentContext;
+ CFormatEtcContainer m_FormatEtcContainer;
+ bool m_bFormatEtcContainerInitialized;
+ CDataFormatTranslator m_DataFormatTranslator;
+ CFormatRegistrar m_FormatRegistrar;
+};
+
+class CEnumFormatEtc : public IEnumFORMATETC
+{
+public:
+ CEnumFormatEtc( LPUNKNOWN lpUnkOuter, const CFormatEtcContainer& aFormatEtcContainer );
+ virtual ~CEnumFormatEtc() {}
+
+ // IUnknown
+ STDMETHODIMP QueryInterface( REFIID iid, void** ppvObject ) override;
+ STDMETHODIMP_( ULONG ) AddRef( ) override;
+ STDMETHODIMP_( ULONG ) Release( ) override;
+
+ //IEnumFORMATETC
+ STDMETHODIMP Next( ULONG nRequested, FORMATETC * lpDest, ULONG* lpFetched ) override;
+ STDMETHODIMP Skip( ULONG celt ) override;
+ STDMETHODIMP Reset( ) override;
+ STDMETHODIMP Clone( IEnumFORMATETC** ppenum ) override;
+
+private:
+ LONG m_nRefCnt;
+ LPUNKNOWN m_lpUnkOuter;
+ CFormatEtcContainer m_FormatEtcContainer;
+};
+
+typedef CEnumFormatEtc *PCEnumFormatEtc;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/clipboardmanager.cxx b/vcl/win/dtrans/clipboardmanager.cxx
new file mode 100644
index 0000000000..bff5aec49f
--- /dev/null
+++ b/vcl/win/dtrans/clipboardmanager.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 "clipboardmanager.hxx"
+#include <com/sun/star/container/ElementExistException.hpp>
+#include <com/sun/star/container/NoSuchElementException.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <comphelper/sequence.hxx>
+#include <rtl/ref.hxx>
+
+using namespace com::sun::star::container;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::datatransfer::clipboard;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace cppu;
+using namespace osl;
+
+using ::dtrans::ClipboardManager;
+
+static std::mutex g_InstanceGuard;
+static rtl::Reference<ClipboardManager> g_Instance;
+static bool g_Disposed = false;
+
+
+ClipboardManager::ClipboardManager():
+ m_aDefaultName(OUString("default"))
+{
+}
+
+ClipboardManager::~ClipboardManager()
+{
+}
+
+OUString SAL_CALL ClipboardManager::getImplementationName( )
+{
+ return "com.sun.star.comp.datatransfer.ClipboardManager";
+}
+
+sal_Bool SAL_CALL ClipboardManager::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SAL_CALL ClipboardManager::getSupportedServiceNames( )
+{
+ return { "com.sun.star.datatransfer.clipboard.ClipboardManager" };
+}
+
+Reference< XClipboard > SAL_CALL ClipboardManager::getClipboard( const OUString& aName )
+{
+ std::unique_lock aGuard(m_aMutex);
+
+ // object is disposed already
+ if (m_bDisposed)
+ throw DisposedException("object is disposed.",
+ static_cast < XClipboardManager * > (this));
+
+ ClipboardMap::iterator iter =
+ m_aClipboardMap.find(aName.getLength() ? aName : m_aDefaultName);
+
+ if (iter != m_aClipboardMap.end())
+ return iter->second;
+
+ throw NoSuchElementException(aName, static_cast < XClipboardManager * > (this));
+}
+
+void SAL_CALL ClipboardManager::addClipboard( const Reference< XClipboard >& xClipboard )
+{
+ OSL_ASSERT(xClipboard.is());
+
+ // check parameter
+ if (!xClipboard.is())
+ throw IllegalArgumentException("empty reference",
+ static_cast < XClipboardManager * > (this), 1);
+
+ // the name "default" is reserved for internal use
+ OUString aName = xClipboard->getName();
+ if ( m_aDefaultName == aName )
+ throw IllegalArgumentException("name reserved",
+ static_cast < XClipboardManager * > (this), 1);
+
+ // try to add new clipboard to the list
+ std::unique_lock aGuard(m_aMutex);
+ if (!m_bDisposed)
+ {
+ std::pair< const OUString, Reference< XClipboard > > value (
+ aName.getLength() ? aName : m_aDefaultName,
+ xClipboard );
+
+ std::pair< ClipboardMap::iterator, bool > p = m_aClipboardMap.insert(value);
+ aGuard.unlock();
+
+ // insert failed, element must exist already
+ if (!p.second)
+ throw ElementExistException(aName, static_cast < XClipboardManager * > (this));
+
+ // request disposing notifications
+ Reference< XComponent > xComponent(xClipboard, UNO_QUERY);
+ if (xComponent.is())
+ xComponent->addEventListener(static_cast < XEventListener * > (this));
+ }
+}
+
+void SAL_CALL ClipboardManager::removeClipboard( const OUString& aName )
+{
+ std::unique_lock aGuard(m_aMutex);
+ if (!m_bDisposed)
+ m_aClipboardMap.erase(aName.getLength() ? aName : m_aDefaultName );
+}
+
+Sequence< OUString > SAL_CALL ClipboardManager::listClipboardNames()
+{
+ std::unique_lock aGuard(m_aMutex);
+
+ if (m_bDisposed)
+ throw DisposedException("object is disposed.",
+ static_cast < XClipboardManager * > (this));
+
+ return comphelper::mapKeysToSequence(m_aClipboardMap);
+}
+
+void ClipboardManager::disposing(std::unique_lock<std::mutex>& rGuard)
+{
+ rGuard.unlock();
+
+ {
+ std::unique_lock aGuard(g_InstanceGuard);
+ g_Instance.clear();
+ g_Disposed = true;
+ }
+
+ // removeClipboard is still allowed here, so make a copy of the
+ // list (to ensure integrity) and clear the original.
+ rGuard.lock();
+ ClipboardMap aCopy;
+ std::swap(aCopy, m_aClipboardMap);
+ rGuard.unlock();
+
+ // dispose all clipboards still in list
+ for (auto const& elem : aCopy)
+ {
+ Reference< XComponent > xComponent(elem.second, UNO_QUERY);
+ if (xComponent.is())
+ {
+ try
+ {
+ xComponent->removeEventListener(static_cast < XEventListener * > (this));
+ xComponent->dispose();
+ }
+ catch (const Exception&)
+ {
+ // exceptions can be safely ignored here.
+ }
+ }
+ }
+}
+
+void SAL_CALL ClipboardManager::disposing( const EventObject& event )
+{
+ Reference < XClipboard > xClipboard(event.Source, UNO_QUERY);
+
+ if (xClipboard.is())
+ removeClipboard(xClipboard->getName());
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+dtrans_ClipboardManager_get_implementation(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
+{
+ std::unique_lock aGuard(g_InstanceGuard);
+ if (g_Disposed)
+ return nullptr;
+ if (!g_Instance)
+ g_Instance.set(new ClipboardManager());
+ return cppu::acquire(g_Instance.get());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/clipboardmanager.hxx b/vcl/win/dtrans/clipboardmanager.hxx
new file mode 100644
index 0000000000..27f9ddbdae
--- /dev/null
+++ b/vcl/win/dtrans/clipboardmanager.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <comphelper/compbase.hxx>
+
+#include <com/sun/star/datatransfer/clipboard/XClipboardManager.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+
+#include <map>
+
+typedef std::map< OUString, css::uno::Reference< css::datatransfer::clipboard::XClipboard > > ClipboardMap;
+
+namespace dtrans
+{
+
+ class ClipboardManager : public ::comphelper::WeakComponentImplHelper <
+ css::datatransfer::clipboard::XClipboardManager,
+ css::lang::XEventListener,
+ css::lang::XServiceInfo >
+ {
+ ClipboardMap m_aClipboardMap;
+
+ const OUString m_aDefaultName;
+
+ virtual ~ClipboardManager() override;
+ protected:
+ using WeakComponentImplHelperBase::disposing;
+ public:
+
+ ClipboardManager();
+
+ /*
+ * 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;
+
+ /*
+ * XComponent
+ */
+
+ virtual void disposing(std::unique_lock<std::mutex>& rGuard) override;
+
+ /*
+ * XEventListener
+ */
+
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+
+ /*
+ * XClipboardManager
+ */
+
+ virtual css::uno::Reference< css::datatransfer::clipboard::XClipboard > SAL_CALL getClipboard( const OUString& aName ) override;
+
+ virtual void SAL_CALL addClipboard( const css::uno::Reference< css::datatransfer::clipboard::XClipboard >& xClipboard ) override;
+
+ virtual void SAL_CALL removeClipboard( const OUString& aName ) override;
+
+ virtual css::uno::Sequence< OUString > SAL_CALL listClipboardNames( ) override;
+
+ };
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/ftransl.cxx b/vcl/win/dtrans/ftransl.cxx
new file mode 100644
index 0000000000..20056bba0e
--- /dev/null
+++ b/vcl/win/dtrans/ftransl.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 <sal/config.h>
+
+#include <string_view>
+
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+
+#include "ftransl.hxx"
+#include <com/sun/star/container/NoSuchElementException.hpp>
+#include <com/sun/star/datatransfer/XMimeContentType.hpp>
+#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/weak.hxx>
+#include "ImplHelper.hxx"
+
+#include <shlobj.h>
+
+#define CPPUTYPE_SEQSALINT8 cppu::UnoType<Sequence< sal_Int8 >>::get()
+#define CPPUTYPE_DEFAULT CPPUTYPE_SEQSALINT8
+
+constexpr OUString Windows_FormatName = u"windows_formatname"_ustr;
+const css::uno::Type CppuType_ByteSequence = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get();
+const css::uno::Type CppuType_String = ::cppu::UnoType<OUString>::get();
+
+using namespace osl;
+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::container;
+
+namespace
+{
+
+struct FormatEntry
+{
+ FormatEntry(
+ const char *mime_content_type,
+ const char *human_presentable_name,
+ const char *native_format_name,
+ CLIPFORMAT std_clipboard_format_id,
+ css::uno::Type const & cppu_type
+ );
+
+ css::datatransfer::DataFlavor aDataFlavor;
+ OUString aNativeFormatName;
+ sal_Int32 aStandardFormatId;
+};
+
+}
+
+FormatEntry::FormatEntry(
+ const char *mime_content_type,
+ const char *human_presentable_name,
+ const char *native_format_name,
+ CLIPFORMAT std_clipboard_format_id,
+ css::uno::Type const & cppu_type)
+ : aDataFlavor( OUString::createFromAscii(mime_content_type), OUString::createFromAscii(human_presentable_name), cppu_type)
+{
+ if (native_format_name)
+ aNativeFormatName = OUString::createFromAscii(native_format_name);
+ else
+ aNativeFormatName = OUString::createFromAscii(human_presentable_name);
+
+ aStandardFormatId = std_clipboard_format_id;
+}
+
+// to optimize searching we add all entries with a
+// standard clipboard format number first, in the
+// table before the entries with CF_INVALID
+// if we are searching for a standard clipboard
+// format number we can stop if we find the first
+// CF_INVALID
+
+const std::vector< FormatEntry > g_TranslTable {
+ //SotClipboardFormatId::DIF
+ FormatEntry("application/x-openoffice-dif;windows_formatname=\"DIF\"", "DIF", "DIF", CF_DIF, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::BITMAP
+
+ // #i124085# CF_DIBV5 disabled, leads to problems at export. To fully support, using
+ // an own mime-type may be necessary. I have tried that, but saw no real advantages
+ // with different apps when exchanging bitmap-based data. So, disabled for now. At
+ // the same time increased png format exchange for better interoperability
+ // FormatEntry("application/x-openoffice-bitmap;windows_formatname=\"Bitmap\"", "Bitmap", "Bitmap", CF_DIBV5, CPPUTYPE_DEFAULT),
+
+ FormatEntry("application/x-openoffice-bitmap;windows_formatname=\"Bitmap\"", "Bitmap", "Bitmap", CF_DIB, CPPUTYPE_DEFAULT),
+ FormatEntry("application/x-openoffice-bitmap;windows_formatname=\"Bitmap\"", "Bitmap", "Bitmap", CF_BITMAP, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::STRING
+ FormatEntry("text/plain;charset=utf-16", "Unicode-Text", "", CF_UNICODETEXT, CppuType_String),
+ // Format Locale - for internal use
+ FormatEntry("application/x-openoffice-locale;windows_formatname=\"Locale\"", "Locale", "Locale", CF_LOCALE, CPPUTYPE_DEFAULT),
+ // SOT_FORMAT_WMF
+ FormatEntry("application/x-openoffice-wmf;windows_formatname=\"Image WMF\"", "Windows MetaFile", "Image WMF", CF_METAFILEPICT, CPPUTYPE_DEFAULT),
+ // SOT_FORMAT_EMF
+ FormatEntry("application/x-openoffice-emf;windows_formatname=\"Image EMF\"", "Windows Enhanced MetaFile", "Image EMF", CF_ENHMETAFILE, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::FILE_LIST
+ FormatEntry("application/x-openoffice-filelist;windows_formatname=\"FileList\"", "FileList", "FileList", CF_HDROP, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::SYLK
+ FormatEntry("application/x-openoffice-sylk;windows_formatname=\"Sylk\"", "Sylk", "Sylk", CF_SYLK, CPPUTYPE_DEFAULT ),
+ // SotClipboardFormatId::GDIMETAFILE
+ FormatEntry("application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\"", "GDIMetaFile", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::PRIVATE
+ FormatEntry("application/x-openoffice-private;windows_formatname=\"Private\"", "Private", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::SIMPLE_FILE
+ FormatEntry("application/x-openoffice-file;windows_formatname=\"FileNameW\"", "FileName", nullptr, CF_INVALID, CppuType_String),
+ // SotClipboardFormatId::RTF
+ FormatEntry("text/rtf", "Rich Text Format", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::DRAWING
+ FormatEntry("application/x-openoffice-drawing;windows_formatname=\"Drawing Format\"", "Drawing Format", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::SVXB
+ FormatEntry("application/x-openoffice-svbx;windows_formatname=\"SVXB (StarView Bitmap/Animation)\"", "SVXB (StarView Bitmap/Animation)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::SVIM
+ FormatEntry("application/x-openoffice-svim;windows_formatname=\"SVIM (StarView ImageMap)\"", "SVIM (StarView ImageMap)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::XFA
+ FormatEntry("application/x-libreoffice-xfa;windows_formatname=\"XFA (XOutDev FillAttr Any)\"", "XFA (XOutDev FillAttr Any)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT
+ FormatEntry("application/vnd.oasis.opendocument.text-flat-xml", "EditEngine ODF", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::INTERNALLINK_STATE
+ FormatEntry("application/x-openoffice-internallink-state;windows_formatname=\"StatusInfo of SvxInternalLink\"", "StatusInfo of SvxInternalLink", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::SOLK
+ FormatEntry("application/x-openoffice-solk;windows_formatname=\"SOLK (StarOffice Link)\"", "SOLK (StarOffice Link)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::NETSCAPE_BOOKMARK
+ FormatEntry("application/x-openoffice-netscape-bookmark;windows_formatname=\"Netscape Bookmark\"", "Netscape Bookmark", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::TREELISTBOX
+ FormatEntry("application/x-openoffice-treelistbox;windows_formatname=\"SV_LBOX_DD_FORMAT\"", "SV_LBOX_DD_FORMAT", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::NATIVE
+ FormatEntry("application/x-openoffice-native;windows_formatname=\"Native\"", "Native", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::OWNERLINK
+ FormatEntry("application/x-openoffice-ownerlink;windows_formatname=\"OwnerLink\"", "OwnerLink", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::STARSERVER
+ FormatEntry("application/x-openoffice-starserver;windows_formatname=\"StarServerFormat\"", "StarServerFormat", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::STAROBJECT
+ FormatEntry("application/x-openoffice-starobject;windows_formatname=\"StarObjectFormat\"", "StarObjectFormat", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::APPLETOBJECT
+ FormatEntry("application/x-openoffice-appletobject;windows_formatname=\"Applet Object\"", "Applet Object", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::PLUGIN_OBJECT
+ FormatEntry("application/x-openoffice-plugin-object;windows_formatname=\"PlugIn Object\"", "PlugIn Object", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::STARWRITER_30
+ FormatEntry("application/x-openoffice-starwriter-30;windows_formatname=\"StarWriter 3.0\"", "StarWriter 3.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARWRITER_40
+ FormatEntry("application/x-openoffice-starwriter-40;windows_formatname=\"StarWriter 4.0\"", "StarWriter 4.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARWRITER_50
+ FormatEntry("application/x-openoffice-starwriter-50;windows_formatname=\"StarWriter 5.0\"", "StarWriter 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARWRITERWEB_40
+ FormatEntry("application/x-openoffice-starwriterweb-40;windows_formatname=\"StarWriter/Web 4.0\"", "StarWriter/Web 4.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARWRITERWEB_50
+ FormatEntry("application/x-openoffice-starwriterweb-50;windows_formatname=\"StarWriter/Web 5.0\"", "StarWriter/Web 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARWRITERGLOB_40
+ FormatEntry("application/x-openoffice-starwriterglob-40;windows_formatname=\"StarWriter/Global 4.0\"", "StarWriter/Global 4.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::STARWRITERGLOB_50
+ FormatEntry("application/x-openoffice-starwriterglob-50;windows_formatname=\"StarWriter/Global 5.0\"", "StarWriter/Global 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARDRAW
+ FormatEntry("application/x-openoffice-stardraw;windows_formatname=\"StarDrawDocument\"", "StarDrawDocument", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARDRAW_40
+ FormatEntry("application/x-openoffice-stardraw-40;windows_formatname=\"StarDrawDocument 4.0\"", "StarDrawDocument 4.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARIMPRESS_50
+ FormatEntry("application/x-openoffice-starimpress-50;windows_formatname=\"StarImpress 5.0\"", "StarImpress 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::STARDRAW_50
+ FormatEntry("application/x-openoffice-stardraw-50;windows_formatname=\"StarDraw 5.0\"", "StarDraw 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARCALC
+ FormatEntry("application/x-openoffice-starcalc;windows_formatname=\"StarCalcDocument\"", "StarCalcDocument", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARCALC_40
+ FormatEntry("application/x-openoffice-starcalc-40;windows_formatname=\"StarCalc 4.0\"", "StarCalc 4.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::STARCALC_50
+ FormatEntry("application/x-openoffice-starcalc-50;windows_formatname=\"StarCalc 5.0\"", "StarCalc 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::STARCHART
+ FormatEntry("application/x-openoffice-starchart;windows_formatname=\"StarChartDocument\"", "StarChartDocument", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::STARCHART_40
+ FormatEntry("application/x-openoffice-starchart-40;windows_formatname=\"StarChartDocument 4.0\"", "StarChartDocument 4.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::STARCHART_50
+ FormatEntry("application/x-openoffice-starchart-50;windows_formatname=\"StarChart 5.0\"", "StarChart 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARIMAGE
+ FormatEntry("application/x-openoffice-starimage;windows_formatname=\"StarImageDocument\"", "StarImageDocument", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARIMAGE_40
+ FormatEntry("application/x-openoffice-starimage-40;windows_formatname=\"StarImageDocument 4.0\"", "StarImageDocument 4.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARIMAGE_50
+ FormatEntry("application/x-openoffice-starimage-50;windows_formatname=\"StarImage 5.0\"", "StarImage 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARMATH
+ FormatEntry("application/x-openoffice-starmath;windows_formatname=\"StarMath\"", "StarMath", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARMATH_40
+ FormatEntry("application/x-openoffice-starmath-40;windows_formatname=\"StarMathDocument 4.0\"", "StarMathDocument 4.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARMATH_50
+ FormatEntry("application/x-openoffice-starmath-50;windows_formatname=\"StarMath 5.0\"", "StarMath 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STAROBJECT_PAINTDOC
+ FormatEntry("application/x-openoffice-starobject-paintdoc;windows_formatname=\"StarObjectPaintDocument\"", "StarObjectPaintDocument", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::FILLED_AREA
+ FormatEntry("application/x-openoffice-filled-area;windows_formatname=\"FilledArea\"", "FilledArea", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::HTML
+ FormatEntry("text/html", "HTML (HyperText Markup Language)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::HTML_SIMPLE
+ FormatEntry("application/x-openoffice-html-simple;windows_formatname=\"HTML Format\"", "HTML Format", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::CHAOS
+ FormatEntry("application/x-openoffice-chaos;windows_formatname=\"FORMAT_CHAOS\"", "FORMAT_CHAOS", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::CNT_MSGATTACHFILE
+ FormatEntry("application/x-openoffice-msgattachfile;windows_formatname=\"CNT_MSGATTACHFILE_FORMAT\"", "CNT_MSGATTACHFILE_FORMAT", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::BIFF_5
+ FormatEntry("application/x-openoffice-biff5;windows_formatname=\"Biff5\"", "Biff5", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::BIFF__5
+ FormatEntry("application/x-openoffice-biff-5;windows_formatname=\"Biff 5\"", "Biff 5", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::BIFF_8
+ FormatEntry("application/x-openoffice-biff-8;windows_formatname=\"Biff8\"", "Biff8", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::SYLK_BIGCAPS
+ FormatEntry("application/x-openoffice-sylk-bigcaps;windows_formatname=\"SYLK\"", "SYLK", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::LINK
+ FormatEntry("application/x-openoffice-link;windows_formatname=\"Link\"", "Link", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARDRAW_TABBAR
+ FormatEntry("application/x-openoffice-stardraw-tabbar;windows_formatname=\"StarDraw TabBar\"", "StarDraw TabBar", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::SONLK
+ FormatEntry("application/x-openoffice-sonlk;windows_formatname=\"SONLK (StarOffice Navi Link)\"", "SONLK (StarOffice Navi Link)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::MSWORD_DOC
+ FormatEntry("application/msword", "MSWordDoc", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STAR_FRAMESET_DOC
+ FormatEntry("application/x-openoffice-star-frameset-doc;windows_formatname=\"StarFrameSetDocument\"", "StarFrameSetDocument", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::OFFICE_DOC
+ FormatEntry("application/x-openoffice-office-doc;windows_formatname=\"OfficeDocument\"", "OfficeDocument", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::NOTES_DOCINFO
+ FormatEntry("application/x-openoffice-notes-docinfo;windows_formatname=\"NotesDocInfo\"", "NotesDocInfo", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::NOTES_HNOTE
+ FormatEntry("application/x-openoffice-notes-hnote;windows_formatname=\"NoteshNote\"", "NoteshNote", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::NOTES_NATIVE
+ FormatEntry("application/x-openoffice-notes-native;windows_formatname=\"Native\"", "Native", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::SFX_DOC
+ FormatEntry("application/x-openoffice-sfx-doc;windows_formatname=\"SfxDocument\"", "SfxDocument", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::EVDF
+ FormatEntry("application/x-openoffice-evdf;windows_formatname=\"EVDF (Explorer View Dummy Format)\"", "EVDF (Explorer View Dummy Format)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::ESDF
+ FormatEntry("application/x-openoffice-esdf;windows_formatname=\"ESDF (Explorer Search Dummy Format)\"", "ESDF (Explorer Search Dummy Format)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::IDF
+ FormatEntry("application/x-openoffice-idf;windows_formatname=\"IDF (Iconview Dummy Format)\"", "IDF (Iconview Dummy Format)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::EFTP
+ FormatEntry("application/x-openoffice-eftp;windows_formatname=\"EFTP (Explorer Ftp File)\"", "EFTP (Explorer Ftp File)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::EFD
+ FormatEntry("application/x-openoffice-efd;windows_formatname=\"EFD (Explorer Ftp Dir)\"", "EFD (Explorer Ftp Dir)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::SVX_FORMFIELDEXCH
+ FormatEntry("application/x-openoffice-svx-formfieldexch;windows_formatname=\"SvxFormFieldExch\"", "SvxFormFieldExch", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::EXTENDED_TABBAR
+ FormatEntry("application/x-openoffice-extended-tabbar;windows_formatname=\"ExtendedTabBar\"", "ExtendedTabBar", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::SBA_DATAEXCHANGE
+ FormatEntry("application/x-openoffice-sba-dataexchange;windows_formatname=\"SBA-DATAFORMAT\"", "SBA-DATAFORMAT", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::SBA_FIELDDATAEXCHANGE
+ FormatEntry("application/x-openoffice-sba-fielddataexchange;windows_formatname=\"SBA-FIELDFORMAT\"", "SBA-FIELDFORMAT", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::SBA_PRIVATE_URL
+ FormatEntry("application/x-openoffice-sba-private-url;windows_formatname=\"SBA-PRIVATEURLFORMAT\"", "SBA-PRIVATEURLFORMAT", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::SBA_TABED
+ FormatEntry("application/x-openoffice-sba-tabed;windows_formatname=\"Tabed\"", "Tabed", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::SBA_TABID
+ FormatEntry("application/x-openoffice-sba-tabid;windows_formatname=\"Tabid\"", "Tabid", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::SBA_JOIN
+ FormatEntry("application/x-openoffice-sba-join;windows_formatname=\"SBA-JOINFORMAT\"", "SBA-JOINFORMAT", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::OBJECTDESCRIPTOR
+ FormatEntry("application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star Object Descriptor (XML)\"", "Star Object Descriptor (XML)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::LINKSRCDESCRIPTOR
+ FormatEntry("application/x-openoffice-linksrcdescriptor-xml;windows_formatname=\"Star Link Source Descriptor (XML)\"", "Star Link Source Descriptor (XML)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::EMBED_SOURCE
+ FormatEntry("application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"", "Star Embed Source (XML)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::LINK_SOURCE
+ FormatEntry("application/x-openoffice-link-source-xml;windows_formatname=\"Star Link Source (XML)\"", "Star Link Source (XML)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::EMBEDDED_OBJ
+ FormatEntry("application/x-openoffice-embedded-obj-xml;windows_formatname=\"Star Embedded Object (XML)\"", "Star Embedded Object (XML)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::FILECONTENT
+ FormatEntry("application/x-openoffice-filecontent;windows_formatname=\"" CFSTR_FILECONTENTS "\"", CFSTR_FILECONTENTS, nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::FILEGRPDESCRIPTOR
+ FormatEntry("application/x-openoffice-filegrpdescriptor;windows_formatname=\"" CFSTR_FILEDESCRIPTOR "\"", CFSTR_FILEDESCRIPTOR, nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::FILENAME
+ FormatEntry("application/x-openoffice-filename;windows_formatname=\"" CFSTR_FILENAME "\"", CFSTR_FILENAME, nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::SD_OLE
+ FormatEntry("application/x-openoffice-sd-ole;windows_formatname=\"SD-OLE\"", "SD-OLE", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::EMBEDDED_OBJ_OLE
+ FormatEntry("application/x-openoffice-embedded-obj-ole;windows_formatname=\"Embedded Object\"", "Embedded Object", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::EMBED_SOURCE_OLE
+ FormatEntry("application/x-openoffice-embed-source-ole;windows_formatname=\"Embed Source\"", "Embed Source", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::OBJECTDESCRIPTOR_OLE
+ FormatEntry("application/x-openoffice-objectdescriptor-ole;windows_formatname=\"Object Descriptor\"", "Object Descriptor", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::LINKSRCDESCRIPTOR_OLE
+ FormatEntry("application/x-openoffice-linkdescriptor-ole;windows_formatname=\"Link Source Descriptor\"", "Link Source Descriptor", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::LINK_SOURCE_OLE
+ FormatEntry("application/x-openoffice-link-source-ole;windows_formatname=\"Link Source\"", "Link Source", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::SBA_CTRLDATAEXCHANGE
+ FormatEntry("application/x-openoffice-sba-ctrldataexchange;windows_formatname=\"SBA-CTRLFORMAT\"", "SBA-CTRLFORMAT", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::OUTPLACE_OBJ
+ FormatEntry("application/x-openoffice-outplace-obj;windows_formatname=\"OutPlace Object\"", "OutPlace Object", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::CNT_OWN_CLIP
+ FormatEntry("application/x-openoffice-cnt-own-clip;windows_formatname=\"CntOwnClipboard\"", "CntOwnClipboard", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::INET_IMAGE
+ FormatEntry("application/x-openoffice-inet-image;windows_formatname=\"SO-INet-Image\"", "SO-INet-Image", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::NETSCAPE_IMAGE
+ FormatEntry("application/x-openoffice-netscape-image;windows_formatname=\"Netscape Image Format\"", "Netscape Image Format", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::SBA_FORMEXCHANGE
+ FormatEntry("application/x-openoffice-sba-formexchange;windows_formatname=\"SBA_FORMEXCHANGE\"", "SBA_FORMEXCHANGE", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::SBA_REPORTEXCHANGE
+ FormatEntry("application/x-openoffice-sba-reportexchange;windows_formatname=\"SBA_REPORTEXCHANGE\"", "SBA_REPORTEXCHANGE", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::UNIFORMRESOURCELOCATOR
+ FormatEntry("application/x-openoffice-uniformresourcelocator;windows_formatname=\"UniformResourceLocatorW\"", "UniformResourceLocator", nullptr, CF_INVALID, CppuType_String),
+ //SotClipboardFormatId::STARCHARTDOCUMENT_50
+ FormatEntry("application/x-openoffice-starchartdocument-50;windows_formatname=\"StarChartDocument 5.0\"", "StarChartDocument 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::GRAPHOBJ
+ FormatEntry("application/x-openoffice-graphobj;windows_formatname=\"Graphic Object\"", "Graphic Object", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARWRITER_60
+ FormatEntry("application/vnd.sun.xml.writer", "Writer 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARWRITERWEB_60
+ FormatEntry("application/vnd.sun.xml.writer.web", "Writer/Web 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARWRITERGLOB_60
+ FormatEntry("application/vnd.sun.xml.writer.global", "Writer/Global 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARWDRAW_60
+ FormatEntry("application/vnd.sun.xml.draw", "Draw 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARIMPRESS_60
+ FormatEntry("application/vnd.sun.xml.impress", "Impress 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARCALC_60
+ FormatEntry("application/vnd.sun.xml.calc", "Calc 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARCHART_60
+ FormatEntry("application/vnd.sun.xml.chart", "Chart 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::STARMATH_60
+ FormatEntry("application/vnd.sun.xml.math", "Math 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::DIALOG_60
+ FormatEntry("application/vnd.sun.xml.dialog", "Dialog 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::BMP
+ FormatEntry("image/bmp", "Windows Bitmap", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::PNG
+ FormatEntry("image/png", "PNG", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::MATHML
+ FormatEntry("application/mathml+xml", "MathML", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::DUMMY3
+ FormatEntry("application/x-openoffice-dummy3;windows_formatname=\"SO_DUMMYFORMAT_3\"", "SO_DUMMYFORMAT_3", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ //SotClipboardFormatId::DUMMY4
+ FormatEntry("application/x-openoffice-dummy4;windows_formatname=\"SO_DUMMYFORMAT_4\"", "SO_DUMMYFORMAT_4", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ // SotClipboardFormatId::RICHTEXT
+ FormatEntry("text/richtext", "Richtext Format", nullptr, CF_INVALID, CPPUTYPE_DEFAULT),
+ };
+
+namespace {
+
+void findDataFlavorForStandardFormatId( sal_Int32 aStandardFormatId, DataFlavor& aDataFlavor )
+{
+ /*
+ we stop search if we find the first CF_INVALID
+ because in the translation table the entries with a
+ standard clipboard format id appear before the other
+ entries with CF_INVALID
+ */
+ std::vector< FormatEntry >::const_iterator citer = std::find_if(g_TranslTable.begin(), g_TranslTable.end(),
+ [&aStandardFormatId](const FormatEntry& rEntry) {
+ return rEntry.aStandardFormatId == aStandardFormatId
+ || rEntry.aStandardFormatId == CF_INVALID;
+ });
+ if (citer != g_TranslTable.end() && citer->aStandardFormatId == aStandardFormatId)
+ aDataFlavor = citer->aDataFlavor;
+}
+
+void findDataFlavorForNativeFormatName( const OUString& aNativeFormatName, DataFlavor& aDataFlavor )
+{
+ std::vector< FormatEntry >::const_iterator citer = std::find_if(g_TranslTable.begin(), g_TranslTable.end(),
+ [&aNativeFormatName](const FormatEntry& rEntry) {
+ return aNativeFormatName.equalsIgnoreAsciiCase(rEntry.aNativeFormatName); });
+ if (citer != g_TranslTable.end())
+ aDataFlavor = citer->aDataFlavor;
+}
+
+void findStandardFormatIdForCharset( const OUString& aCharset, Any& aAny )
+{
+ if ( aCharset.equalsIgnoreAsciiCase( "utf-16" ) )
+ aAny <<= static_cast< sal_Int32 >( CF_UNICODETEXT );
+ else
+ {
+ sal_Int32 wincp = getWinCPFromMimeCharset( aCharset );
+ if ( IsOEMCP ( wincp ) )
+ aAny <<= static_cast< sal_Int32 >( CF_OEMTEXT );
+ }
+}
+
+void setStandardFormatIdForNativeFormatName( const OUString& aNativeFormatName, Any& aAny )
+{
+ std::vector< FormatEntry >::const_iterator citer = std::find_if(g_TranslTable.begin(), g_TranslTable.end(),
+ [&aNativeFormatName](const FormatEntry& rEntry) {
+ return aNativeFormatName.equalsIgnoreAsciiCase(rEntry.aNativeFormatName)
+ && (CF_INVALID != rEntry.aStandardFormatId);
+ });
+ if (citer != g_TranslTable.end())
+ aAny <<= citer->aStandardFormatId;
+}
+
+void findStdFormatIdOrNativeFormatNameForFullMediaType(
+ const Reference< XMimeContentTypeFactory >& aRefXMimeFactory,
+ const OUString& aFullMediaType,
+ Any& aAny )
+{
+ std::vector< FormatEntry >::const_iterator citer = std::find_if(g_TranslTable.begin(), g_TranslTable.end(),
+ [&aRefXMimeFactory, &aFullMediaType](const FormatEntry& rEntry) {
+ Reference<XMimeContentType> refXMime( aRefXMimeFactory->createMimeContentType(rEntry.aDataFlavor.MimeType) );
+ return aFullMediaType.equalsIgnoreAsciiCase(refXMime->getFullMediaType());
+ });
+ if (citer != g_TranslTable.end())
+ {
+ sal_Int32 cf = citer->aStandardFormatId;
+ if ( CF_INVALID != cf )
+ aAny <<= cf;
+ else
+ {
+ OSL_ENSURE( citer->aNativeFormatName.getLength( ),
+ "Invalid standard format id and empty native format name in translation table" );
+ aAny <<= citer->aNativeFormatName;
+ }
+ }
+}
+
+bool isTextPlainMediaType( std::u16string_view fullMediaType )
+{
+ return o3tl::equalsIgnoreAsciiCase(fullMediaType, u"text/plain");
+}
+
+DataFlavor mkDataFlv(const OUString& cnttype, const OUString& hpname, Type dtype)
+{
+ DataFlavor dflv;
+ dflv.MimeType = cnttype;
+ dflv.HumanPresentableName = hpname;
+ dflv.DataType = dtype;
+ return dflv;
+}
+
+}
+
+CDataFormatTranslatorUNO::CDataFormatTranslatorUNO( const Reference< XComponentContext >& rxContext ) :
+ m_xContext( rxContext )
+{
+}
+
+Any SAL_CALL CDataFormatTranslatorUNO::getSystemDataTypeFromDataFlavor( const DataFlavor& aDataFlavor )
+{
+ Any aAny;
+
+ try
+ {
+ Reference< XMimeContentTypeFactory > refXMimeCntFactory = MimeContentTypeFactory::create( m_xContext );
+
+ Reference< XMimeContentType >
+ refXMimeCntType( refXMimeCntFactory->createMimeContentType( aDataFlavor.MimeType ) );
+
+ OUString fullMediaType = refXMimeCntType->getFullMediaType( );
+ if ( isTextPlainMediaType( fullMediaType ) )
+ {
+ // default is CF_TEXT
+ aAny <<= static_cast< sal_Int32 >( CF_TEXT );
+
+ if ( refXMimeCntType->hasParameter( "charset" ) )
+ {
+ // but maybe it is unicode text or oem text
+ OUString charset = refXMimeCntType->getParameterValue( "charset" );
+ findStandardFormatIdForCharset( charset, aAny );
+ }
+ }
+ else
+ {
+ if (refXMimeCntType->hasParameter(Windows_FormatName))
+ {
+ OUString winfmtname = refXMimeCntType->getParameterValue(Windows_FormatName);
+ aAny <<= winfmtname;
+
+ setStandardFormatIdForNativeFormatName( winfmtname, aAny );
+ }
+ else
+ findStdFormatIdOrNativeFormatNameForFullMediaType( refXMimeCntFactory, fullMediaType, aAny );
+ }
+ }
+ catch( IllegalArgumentException& )
+ {
+ OSL_FAIL( "Invalid content-type detected!" );
+ }
+ catch( NoSuchElementException& )
+ {
+ OSL_FAIL( "Illegal content-type parameter" );
+ }
+ catch( ... )
+ {
+ OSL_FAIL( "Unexpected error" );
+ throw;
+ }
+
+ return aAny;
+}
+
+DataFlavor SAL_CALL CDataFormatTranslatorUNO::getDataFlavorFromSystemDataType( const Any& aSysDataType )
+{
+ OSL_PRECOND( aSysDataType.hasValue( ), "Empty system data type delivered" );
+
+ DataFlavor aFlavor = mkDataFlv( OUString(), OUString(), CPPUTYPE_SEQSALINT8 );
+
+ if ( aSysDataType.getValueType( ) == cppu::UnoType<sal_Int32>::get() )
+ {
+ sal_Int32 clipformat = CF_INVALID;
+ aSysDataType >>= clipformat;
+ if ( CF_INVALID != clipformat )
+ findDataFlavorForStandardFormatId( clipformat, aFlavor );
+ }
+ else if ( aSysDataType.getValueType( ) == cppu::UnoType<OUString>::get() )
+ {
+ OUString nativeFormatName;
+ aSysDataType >>= nativeFormatName;
+
+ findDataFlavorForNativeFormatName( nativeFormatName, aFlavor );
+ }
+ else
+ OSL_FAIL( "Invalid data type received" );
+
+ return aFlavor;
+}
+
+// XServiceInfo
+
+OUString SAL_CALL CDataFormatTranslatorUNO::getImplementationName( )
+{
+ return "com.sun.star.datatransfer.DataFormatTranslator";
+}
+
+sal_Bool SAL_CALL CDataFormatTranslatorUNO::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SAL_CALL CDataFormatTranslatorUNO::getSupportedServiceNames( )
+{
+ return { "com.sun.star.datatransfer.DataFormatTranslator" };
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+dtrans_CDataFormatTranslatorUNO_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new CDataFormatTranslatorUNO(context));
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/ftransl.hxx b/vcl/win/dtrans/ftransl.hxx
new file mode 100644
index 0000000000..7a5041be5f
--- /dev/null
+++ b/vcl/win/dtrans/ftransl.hxx
@@ -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 .
+ */
+
+#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/XDataFormatTranslator.hpp>
+#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include "WinClip.hxx"
+
+#include <vector>
+
+class CDataFormatTranslatorUNO : public
+ cppu::WeakImplHelper< css::datatransfer::XDataFormatTranslator,
+ css::lang::XServiceInfo >
+{
+
+public:
+ explicit CDataFormatTranslatorUNO( const css::uno::Reference< css::uno::XComponentContext >& rxContext );
+
+ // XDataFormatTranslator
+
+ virtual css::uno::Any SAL_CALL getSystemDataTypeFromDataFlavor( const css::datatransfer::DataFlavor& aDataFlavor ) override;
+
+ virtual css::datatransfer::DataFlavor SAL_CALL getDataFlavorFromSystemDataType( const css::uno::Any& aSysDataType ) 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;
+
+private:
+ const css::uno::Reference< css::uno::XComponentContext > m_xContext;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/generic_clipboard.cxx b/vcl/win/dtrans/generic_clipboard.cxx
new file mode 100644
index 0000000000..fd822f091e
--- /dev/null
+++ b/vcl/win/dtrans/generic_clipboard.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 "generic_clipboard.hxx"
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/datatransfer/clipboard/RenderingCapabilities.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <osl/diagnose.h>
+
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::datatransfer::clipboard;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace cppu;
+using namespace osl;
+
+using ::dtrans::GenericClipboard;
+
+GenericClipboard::GenericClipboard() :
+ m_bInitialized(false)
+{
+}
+
+GenericClipboard::~GenericClipboard()
+{
+}
+
+void SAL_CALL GenericClipboard::initialize( const Sequence< Any >& aArguments )
+{
+ if (!m_bInitialized)
+ {
+ for (Any const & arg : aArguments)
+ if (arg.getValueType() == cppu::UnoType<OUString>::get())
+ {
+ arg >>= m_aName;
+ break;
+ }
+ }
+}
+
+OUString SAL_CALL GenericClipboard::getImplementationName( )
+{
+ return "com.sun.star.comp.datatransfer.clipboard.GenericClipboard";
+}
+
+sal_Bool SAL_CALL GenericClipboard::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SAL_CALL GenericClipboard::getSupportedServiceNames( )
+{
+ return { "com.sun.star.datatransfer.clipboard.GenericClipboard" };
+}
+
+Reference< XTransferable > SAL_CALL GenericClipboard::getContents()
+{
+ std::unique_lock aGuard(m_aMutex);
+ return m_aContents;
+}
+
+void SAL_CALL GenericClipboard::setContents(const Reference< XTransferable >& xTrans,
+ const Reference< XClipboardOwner >& xClipboardOwner )
+{
+ // remember old values for callbacks before setting the new ones.
+ std::unique_lock aGuard(m_aMutex);
+
+ Reference< XClipboardOwner > oldOwner(m_aOwner);
+ m_aOwner = xClipboardOwner;
+
+ Reference< XTransferable > oldContents(m_aContents);
+ m_aContents = xTrans;
+
+ aGuard.unlock();
+
+ // notify old owner on loss of ownership
+ if( oldOwner.is() )
+ oldOwner->lostOwnership(static_cast < XClipboard * > (this), oldContents);
+
+ // notify all listeners on content changes
+ aGuard.lock();
+ ClipboardEvent aEvent(static_cast < XClipboard * > (this), m_aContents);
+ maClipboardListeners.notifyEach(aGuard, &XClipboardListener::changedContents, aEvent);
+}
+
+OUString SAL_CALL GenericClipboard::getName()
+{
+ return m_aName;
+}
+
+sal_Int8 SAL_CALL GenericClipboard::getRenderingCapabilities()
+{
+ return RenderingCapabilities::Delayed;
+}
+
+void SAL_CALL GenericClipboard::addClipboardListener( const Reference< XClipboardListener >& listener )
+{
+ std::unique_lock aGuard( m_aMutex );
+ OSL_ENSURE( !m_bDisposed, "object is disposed" );
+ if (!m_bDisposed)
+ maClipboardListeners.addInterface( aGuard, listener );
+}
+
+void SAL_CALL GenericClipboard::removeClipboardListener( const Reference< XClipboardListener >& listener )
+{
+ std::unique_lock aGuard( m_aMutex );
+ OSL_ENSURE( !m_bDisposed, "object is disposed" );
+ if (!m_bDisposed)
+ maClipboardListeners.removeInterface( aGuard, listener );
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+dtrans_GenericClipboard_get_implementation(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new GenericClipboard());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/generic_clipboard.hxx b/vcl/win/dtrans/generic_clipboard.hxx
new file mode 100644
index 0000000000..cc1ad976b3
--- /dev/null
+++ b/vcl/win/dtrans/generic_clipboard.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <comphelper/compbase.hxx>
+
+#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
+
+#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+
+namespace dtrans
+{
+
+ class GenericClipboard : public ::comphelper::WeakComponentImplHelper <
+ css::datatransfer::clipboard::XClipboardEx,
+ css::datatransfer::clipboard::XClipboardNotifier,
+ css::lang::XServiceInfo,
+ css::lang::XInitialization >
+ {
+ OUString m_aName;
+
+ css::uno::Reference< css::datatransfer::XTransferable > m_aContents;
+ css::uno::Reference< css::datatransfer::clipboard::XClipboardOwner > m_aOwner;
+ comphelper::OInterfaceContainerHelper4<css::datatransfer::clipboard::XClipboardListener> maClipboardListeners;
+
+ bool m_bInitialized;
+ virtual ~GenericClipboard() override;
+
+ public:
+
+ GenericClipboard();
+
+ /*
+ * XInitialization
+ */
+
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) 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;
+
+ /*
+ * XClipboard
+ */
+
+ virtual css::uno::Reference< css::datatransfer::XTransferable > SAL_CALL getContents() override;
+
+ virtual void SAL_CALL setContents(
+ const css::uno::Reference< css::datatransfer::XTransferable >& xTrans,
+ const css::uno::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 css::uno::Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
+
+ virtual void SAL_CALL removeClipboardListener(
+ const css::uno::Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
+
+ };
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/globals.cxx b/vcl/win/dtrans/globals.cxx
new file mode 100644
index 0000000000..fcddef22e6
--- /dev/null
+++ b/vcl/win/dtrans/globals.cxx
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/datatransfer/dnd/DNDConstants.hpp>
+
+#include "globals.hxx"
+
+//--> TRA
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+
+// used as shortcut when drag-source and drop-target are the same
+css::uno::Reference< css::datatransfer::XTransferable > g_XTransferable;
+
+//<-- TRA
+
+using namespace com::sun::star::datatransfer::dnd::DNDConstants;
+
+sal_Int8 dndOleKeysToAction( DWORD grfKeyState, sal_Int8 nSourceActions)
+{
+ sal_Int8 ret= 0;
+
+ // no MK_ALT, MK_CONTROL, MK_SHIFT
+ if( !(grfKeyState & MK_CONTROL) &&
+ !(grfKeyState & MK_ALT) &&
+ !(grfKeyState & MK_RBUTTON) &&
+ !(grfKeyState & MK_SHIFT))
+ {
+ if( nSourceActions & ACTION_MOVE )
+ {
+ ret= ACTION_DEFAULT | ACTION_MOVE;
+ }
+
+ else if( nSourceActions & ACTION_COPY )
+ {
+ ret= ACTION_COPY;
+ }
+
+ else if( nSourceActions & ACTION_LINK )
+ {
+ ret= ACTION_LINK;
+ }
+
+ else
+ ret = 0;
+ }
+ else if( grfKeyState & MK_SHIFT &&
+ !(grfKeyState & MK_CONTROL))
+ {
+ ret= ACTION_MOVE;
+ }
+ else if ( grfKeyState & MK_CONTROL &&
+ !(grfKeyState & MK_SHIFT) )
+ {
+ ret= ACTION_COPY;
+ }
+ else if ( grfKeyState & MK_CONTROL &&
+ grfKeyState & MK_SHIFT)
+ {
+ ret= ACTION_LINK;
+ }
+ else if ( grfKeyState & MK_RBUTTON ||
+ grfKeyState & MK_ALT)
+ {
+ ret= ACTION_COPY_OR_MOVE | ACTION_LINK;
+ }
+ return ret;
+}
+
+sal_Int8 dndOleDropEffectsToActions( DWORD dwEffect)
+{
+ sal_Int8 ret= ACTION_NONE;
+ if( dwEffect & DROPEFFECT_COPY)
+ ret |= ACTION_COPY;
+ if( dwEffect & DROPEFFECT_MOVE)
+ ret |= ACTION_MOVE;
+ if( dwEffect & DROPEFFECT_LINK)
+ ret |= ACTION_LINK;
+
+ return ret;
+}
+
+DWORD dndActionsToDropEffects( sal_Int8 actions)
+{
+ DWORD ret= DROPEFFECT_NONE;
+ if( actions & ACTION_MOVE)
+ ret |= DROPEFFECT_MOVE;
+ if( actions & ACTION_COPY)
+ ret |= DROPEFFECT_COPY;
+ if( actions & ACTION_LINK)
+ ret |= DROPEFFECT_LINK;
+ if( actions & ACTION_DEFAULT)
+ ret |= DROPEFFECT_COPY;
+ return ret;
+}
+
+DWORD dndActionsToSingleDropEffect( sal_Int8 actions)
+{
+ DWORD effects= dndActionsToDropEffects( actions);
+
+ sal_Int8 countEffect= 0;
+
+ if( effects & DROPEFFECT_MOVE)
+ countEffect++;
+ if( effects & DROPEFFECT_COPY)
+ countEffect++;
+ if( effects & DROPEFFECT_LINK)
+ countEffect++;
+
+ // DROPEFFECT_MOVE is the default effect
+ DWORD retVal= countEffect > 1 ? DROPEFFECT_MOVE : effects;
+ return retVal;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/globals.hxx b/vcl/win/dtrans/globals.hxx
new file mode 100644
index 0000000000..9bb174d0b6
--- /dev/null
+++ b/vcl/win/dtrans/globals.hxx
@@ -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 .
+ */
+#pragma once
+
+#include <sal/config.h>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <osl/mutex.hxx>
+
+namespace com::sun::star::datatransfer
+{
+class XTransferable;
+}
+
+#include <wtypes.h>
+#include <sal/types.h>
+
+// This maps key states as occur as parameter, e.g. in IDropTarget::DragEnter,
+// IDropSource::QueryContinueDrag, to actions as are declared in
+// css::datatransfer::dnd::DNDConstants ( ACTION_MOVE etc).
+// If the grfKeyState indicates the ALt or right mousebutton then the returned
+// values combines all possible actions. This is because those keys and buttons are
+// used when the user expect a menu to appear when he drops. The menu then
+// contains entries, such as move, copy, link, cancel.
+// An odd fact is that the argument grfKeyState in IDropTarget::Drop does not
+// contain mouse buttons (winnt 4 SP6). That indicates that the right mouse button
+// is not considered relevant in a drag operation. Contrarily the file explorer
+// gives that button a special meaning: the user has to select the effect from
+// a context menu on drop.
+sal_Int8 dndOleKeysToAction(DWORD grfKeyState, sal_Int8 sourceActions);
+
+// The function maps a windows DROPEFFECTs to actions
+// ( css::datatransfer::dnd::DNDConstants).
+// The argument can be a combination of different DROPEFFECTS,
+// In that case the return value is also a combination of the
+// appropriate actions.
+sal_Int8 dndOleDropEffectsToActions(DWORD dwEffect);
+
+// The function maps actions ( css::datatransfer::dnd::DNDConstants)
+// to window DROPEFFECTs.
+// The argument can be a combination of different actions
+// In that case the return value is also a combination of the
+// appropriate DROPEFFECTS.
+DWORD dndActionsToDropEffects(sal_Int8 actions);
+
+// If the argument constitutes only one action then it is mapped to the
+// corresponding DROPEFFECT otherwise DROPEFFECT_MOVE is returned. This is
+// why move is the default effect (no modifiers pressed, or right mouse button
+// or Alt).
+DWORD dndActionsToSingleDropEffect(sal_Int8 actions);
+
+extern css::uno::Reference<css::datatransfer::XTransferable> g_XTransferable;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/idroptarget.cxx b/vcl/win/dtrans/idroptarget.cxx
new file mode 100644
index 0000000000..8b403eb7a3
--- /dev/null
+++ b/vcl/win/dtrans/idroptarget.cxx
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "idroptarget.hxx"
+
+IDropTargetImpl::IDropTargetImpl( DropTarget& pTarget): m_nRefCount( 0),
+ m_rDropTarget( pTarget)
+{
+}
+
+IDropTargetImpl::~IDropTargetImpl()
+{
+}
+
+//IDropTarget
+HRESULT STDMETHODCALLTYPE IDropTargetImpl::QueryInterface( REFIID riid, void **ppvObject)
+{
+ if( !ppvObject)
+ return E_POINTER;
+ *ppvObject= nullptr;
+
+ if( riid == __uuidof( IUnknown))
+ *ppvObject= static_cast<IUnknown*>( this);
+ else if ( riid == __uuidof( IDropTarget))
+ *ppvObject= static_cast<IDropTarget*>( this);
+
+ if(*ppvObject)
+ {
+ AddRef();
+ return S_OK;
+ }
+ else
+ return E_NOINTERFACE;
+
+}
+
+ULONG STDMETHODCALLTYPE IDropTargetImpl::AddRef()
+{
+ return InterlockedIncrement( &m_nRefCount);
+}
+
+ULONG STDMETHODCALLTYPE IDropTargetImpl::Release()
+{
+ LONG count= InterlockedDecrement( &m_nRefCount);
+ if( m_nRefCount == 0 )
+ delete this;
+ return count;
+}
+
+STDMETHODIMP IDropTargetImpl::DragEnter( IDataObject __RPC_FAR *pDataObj,
+ DWORD grfKeyState,
+ POINTL pt,
+ DWORD *pdwEffect)
+{
+ return m_rDropTarget.DragEnter( pDataObj, grfKeyState,
+ pt, pdwEffect);
+}
+
+STDMETHODIMP IDropTargetImpl::DragOver( DWORD grfKeyState,
+ POINTL pt,
+ DWORD *pdwEffect)
+{
+ return m_rDropTarget.DragOver( grfKeyState, pt, pdwEffect);
+}
+
+STDMETHODIMP IDropTargetImpl::DragLeave()
+{
+ return m_rDropTarget.DragLeave();
+}
+
+STDMETHODIMP IDropTargetImpl::Drop( IDataObject *pDataObj,
+ DWORD grfKeyState,
+ POINTL pt,
+ DWORD __RPC_FAR *pdwEffect)
+{
+ return m_rDropTarget.Drop( pDataObj, grfKeyState,
+ pt, pdwEffect);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/idroptarget.hxx b/vcl/win/dtrans/idroptarget.hxx
new file mode 100644
index 0000000000..5871373c6c
--- /dev/null
+++ b/vcl/win/dtrans/idroptarget.hxx
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <win/dnd_target.hxx>
+
+class IDropTargetImpl: public IDropTarget
+{
+ LONG m_nRefCount;
+ // Calls to IDropTarget functions are delegated to a DropTarget.
+ DropTarget& m_rDropTarget;
+
+ virtual ~IDropTargetImpl(); // delete is only called by IUnknown::Release
+ IDropTargetImpl( const IDropTargetImpl& );
+ IDropTargetImpl& operator=( const IDropTargetImpl& );
+public:
+ explicit IDropTargetImpl(DropTarget& rTarget);
+
+ // IDropTarget
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(
+ /* [in] */ REFIID riid,
+ /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject) override;
+
+ virtual ULONG STDMETHODCALLTYPE AddRef( ) override;
+
+ virtual ULONG STDMETHODCALLTYPE Release( ) override;
+
+ virtual HRESULT STDMETHODCALLTYPE DragEnter(
+ /* [unique][in] */ IDataObject __RPC_FAR *pDataObj,
+ /* [in] */ DWORD grfKeyState,
+ /* [in] */ POINTL pt,
+ /* [out][in] */ DWORD __RPC_FAR *pdwEffect) override;
+
+ virtual HRESULT STDMETHODCALLTYPE DragOver(
+ /* [in] */ DWORD grfKeyState,
+ /* [in] */ POINTL pt,
+ /* [out][in] */ DWORD __RPC_FAR *pdwEffect) override;
+
+ virtual HRESULT STDMETHODCALLTYPE DragLeave( ) override;
+
+ virtual HRESULT STDMETHODCALLTYPE Drop(
+ /* [unique][in] */ IDataObject __RPC_FAR *pDataObj,
+ /* [in] */ DWORD grfKeyState,
+ /* [in] */ POINTL pt,
+ /* [out][in] */ DWORD __RPC_FAR *pdwEffect) override;
+
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/source.cxx b/vcl/win/dtrans/source.cxx
new file mode 100644
index 0000000000..94124b23dd
--- /dev/null
+++ b/vcl/win/dtrans/source.cxx
@@ -0,0 +1,373 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/awt/MouseButton.hpp>
+#include <com/sun/star/awt/MouseEvent.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <o3tl/any.hxx>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <process.h>
+#include <memory>
+
+#include <win/dnd_source.hxx>
+#include "globals.hxx"
+#include "sourcecontext.hxx"
+#include "DtObjFactory.hxx"
+
+#include <rtl/ustring.h>
+#include <osl/thread.h>
+#include <winuser.h>
+#include <stdio.h>
+
+using namespace cppu;
+using namespace osl;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::datatransfer::dnd;
+using namespace com::sun::star::datatransfer::dnd::DNDConstants;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::awt::MouseButton;
+using namespace com::sun::star::awt;
+using namespace com::sun::star::lang;
+
+static unsigned __stdcall DndOleSTAFunc(void* pParams);
+
+DragSource::DragSource( const Reference<XComponentContext>& rxContext):
+ WeakComponentImplHelper< XDragSource, XInitialization, XServiceInfo >(m_aMutex),
+ m_xContext( rxContext ),
+// m_pcurrentContext_impl(0),
+ m_hAppWindow(nullptr),
+ m_MouseButton(0),
+ m_RunningDndOperationCount(0)
+{
+}
+
+DragSource::~DragSource()
+{
+}
+
+/** First start a new drag and drop thread if
+ the last one has finished
+
+ ????
+ Do we really need a separate thread for
+ every Dnd operation or only if the source
+ thread is an MTA thread
+ ????
+*/
+void DragSource::StartDragImpl(
+ const DragGestureEvent& trigger,
+ sal_Int8 sourceActions,
+ sal_Int32 /*cursor*/,
+ sal_Int32 /*image*/,
+ const Reference<XTransferable >& trans,
+ const Reference<XDragSourceListener >& listener )
+{
+ // The actions supported by the drag source
+ m_sourceActions= sourceActions;
+ // We need to know which mouse button triggered the operation.
+ // If it was the left one, then the drop occurs when that button
+ // has been released and if it was the right one then the drop
+ // occurs when the right button has been released. If the event is not
+ // set then we assume that the left button is pressed.
+ MouseEvent evtMouse;
+ trigger.Event >>= evtMouse;
+ m_MouseButton= evtMouse.Buttons;
+
+ // The SourceContext class administers the XDragSourceListener s and
+ // fires events to them. An instance only exists in the scope of this
+ // function. However, the drag and drop operation causes callbacks
+ // to the IDropSource interface implemented in this class (but only
+ // while this function executes). The source context is also used
+ // in DragSource::QueryContinueDrag.
+ m_currentContext = new SourceContext(this, listener);
+
+ // Convert the XTransferable data object into an IDataObject object;
+
+ //--> TRA
+ g_XTransferable = trans;
+ //<-- TRA
+
+ m_spDataObject= CDTransObjFactory::createDataObjFromTransferable(
+ m_xContext, trans);
+
+ // Obtain the id of the thread that created the window
+ DWORD processId;
+ m_threadIdWindow= GetWindowThreadProcessId( m_hAppWindow, &processId);
+
+ // hold the instance for the DnD thread, it's too late
+ // to acquire at the start of the thread procedure
+ // the thread procedure is responsible for the release
+ acquire();
+
+ // The thread accesses members of this instance but does not call acquire.
+ // Hopefully this instance is not destroyed before the thread has terminated.
+ HANDLE hThread
+ = reinterpret_cast<HANDLE>(_beginthreadex(nullptr, 0, DndOleSTAFunc, this, 0, nullptr));
+
+ // detach from thread
+ CloseHandle(hThread);
+}
+
+// XInitialization
+/** aArguments contains a machine id */
+void SAL_CALL DragSource::initialize( const Sequence< Any >& aArguments )
+{
+ if( aArguments.getLength() >=2)
+ m_hAppWindow= reinterpret_cast<HWND>(static_cast<sal_uIntPtr>(*o3tl::doAccess<sal_uInt64>(aArguments[1])));
+ OSL_ASSERT( IsWindow( m_hAppWindow) );
+}
+
+/** XDragSource */
+sal_Bool SAL_CALL DragSource::isDragImageSupported( )
+{
+ return false;
+}
+
+sal_Int32 SAL_CALL DragSource::getDefaultCursor( sal_Int8 /*dragAction*/ )
+{
+ return 0;
+}
+
+/** Notifies the XDragSourceListener by
+ calling dragDropEnd */
+void SAL_CALL DragSource::startDrag(
+ const DragGestureEvent& trigger,
+ sal_Int8 sourceActions,
+ sal_Int32 cursor,
+ sal_Int32 image,
+ const Reference<XTransferable >& trans,
+ const Reference<XDragSourceListener >& listener )
+{
+ // Allow only one running dnd operation at a time,
+ // see XDragSource documentation
+
+ LONG cnt = InterlockedIncrement(&m_RunningDndOperationCount);
+
+ if (1 == cnt)
+ {
+ StartDragImpl(trigger, sourceActions, cursor, image, trans, listener);
+ }
+ else
+ {
+ cnt = InterlockedDecrement(&m_RunningDndOperationCount);
+
+ DragSourceDropEvent dsde;
+
+ dsde.DropAction = ACTION_NONE;
+ dsde.DropSuccess = false;
+
+ try
+ {
+ listener->dragDropEnd(dsde);
+ }
+ catch(RuntimeException&)
+ {
+ TOOLS_WARN_EXCEPTION( "vcl", "Runtime exception during event dispatching");
+ }
+ }
+}
+
+/** IDropTarget */
+HRESULT STDMETHODCALLTYPE DragSource::QueryInterface( REFIID riid, void **ppvObject)
+{
+ if( !ppvObject)
+ return E_POINTER;
+ *ppvObject= nullptr;
+
+ if( riid == __uuidof( IUnknown) )
+ *ppvObject= static_cast<IUnknown*>( this);
+ else if ( riid == __uuidof( IDropSource) )
+ *ppvObject= static_cast<IDropSource*>( this);
+
+ if(*ppvObject)
+ {
+ AddRef();
+ return S_OK;
+ }
+ else
+ return E_NOINTERFACE;
+
+}
+
+ULONG STDMETHODCALLTYPE DragSource::AddRef()
+{
+ acquire();
+ return static_cast<ULONG>(m_refCount);
+}
+
+ULONG STDMETHODCALLTYPE DragSource::Release()
+{
+ ULONG ref= m_refCount;
+ release();
+ return --ref;
+}
+
+/** IDropSource */
+HRESULT STDMETHODCALLTYPE DragSource::QueryContinueDrag(
+/* [in] */ BOOL fEscapePressed,
+/* [in] */ DWORD grfKeyState)
+{
+#if defined DBG_CONSOLE_OUT
+ printf("\nDragSource::QueryContinueDrag");
+#endif
+
+ HRESULT retVal= S_OK; // default continue DnD
+
+ if (fEscapePressed)
+ {
+ retVal= DRAGDROP_S_CANCEL;
+ }
+ else
+ {
+ if( ( m_MouseButton == MouseButton::RIGHT && !(grfKeyState & MK_RBUTTON) ) ||
+ ( m_MouseButton == MouseButton::MIDDLE && !(grfKeyState & MK_MBUTTON) ) ||
+ ( m_MouseButton == MouseButton::LEFT && !(grfKeyState & MK_LBUTTON) ) ||
+ ( m_MouseButton == 0 && !(grfKeyState & MK_LBUTTON) ) )
+ {
+ retVal= DRAGDROP_S_DROP;
+ }
+ }
+
+ // fire dropActionChanged event.
+ // this is actually done by the context, which also detects whether the action
+ // changed at all
+ sal_Int8 dropAction= fEscapePressed ? ACTION_NONE :
+ dndOleKeysToAction( grfKeyState, m_sourceActions);
+
+ sal_Int8 userAction= fEscapePressed ? ACTION_NONE :
+ dndOleKeysToAction( grfKeyState, -1 );
+
+ static_cast<SourceContext*>(m_currentContext.get())->fire_dropActionChanged(
+ dropAction, userAction);
+
+ return retVal;
+}
+
+HRESULT STDMETHODCALLTYPE DragSource::GiveFeedback(
+/* [in] */ DWORD
+#if defined DBG_CONSOLE_OUT
+dwEffect
+#endif
+)
+{
+#if defined DBG_CONSOLE_OUT
+ printf("\nDragSource::GiveFeedback %d", dwEffect);
+#endif
+
+ return DRAGDROP_S_USEDEFAULTCURSORS;
+}
+
+// XServiceInfo
+OUString SAL_CALL DragSource::getImplementationName( )
+{
+ return "com.sun.star.comp.datatransfer.dnd.OleDragSource_V1";
+}
+// XServiceInfo
+sal_Bool SAL_CALL DragSource::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SAL_CALL DragSource::getSupportedServiceNames( )
+{
+ return { "com.sun.star.datatransfer.dnd.OleDragSource" };
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+dtrans_DragSource_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new DragSource(context));
+}
+
+/** This function is called as extra thread from
+ DragSource::executeDrag. The function
+ carries out a drag and drop operation by calling
+ DoDragDrop. The thread also notifies all
+ XSourceListener. */
+unsigned __stdcall DndOleSTAFunc(void* pParams)
+{
+ osl_setThreadName("DragSource DndOleSTAFunc");
+
+ // The structure contains all arguments for DoDragDrop and other
+ DragSource *pSource= static_cast<DragSource*>(pParams);
+
+ // Drag and drop only works in a thread in which OleInitialize is called.
+ HRESULT hr= OleInitialize( nullptr);
+
+ if(SUCCEEDED(hr))
+ {
+ // We force the creation of a thread message queue. This is necessary
+ // for a later call to AttachThreadInput
+ MSG msgtemp;
+ PeekMessageW( &msgtemp, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
+
+ DWORD threadId= GetCurrentThreadId();
+
+ // This thread is attached to the thread that created the window. Hence
+ // this thread also receives all mouse and keyboard messages which are
+ // needed by DoDragDrop
+ AttachThreadInput( threadId , pSource->m_threadIdWindow, TRUE );
+
+ DWORD dwEffect= 0;
+ hr= DoDragDrop(
+ pSource->m_spDataObject.get(),
+ static_cast<IDropSource*>(pSource),
+ dndActionsToDropEffects( pSource->m_sourceActions),
+ &dwEffect);
+
+ // #105428 detach my message queue from the other threads
+ // message queue before calling fire_dragDropEnd else
+ // the office may appear to hang sometimes
+ AttachThreadInput( threadId, pSource->m_threadIdWindow, FALSE);
+
+ //--> TRA
+ // clear the global transferable again
+ g_XTransferable.clear();
+ //<-- TRA
+
+ OSL_ENSURE( hr != E_INVALIDARG, "IDataObject impl does not contain valid data");
+
+ //Fire event
+ sal_Int8 action= hr == DRAGDROP_S_DROP ? dndOleDropEffectsToActions( dwEffect) : ACTION_NONE;
+
+ static_cast<SourceContext*>(pSource->m_currentContext.get())->fire_dragDropEnd(
+ hr == DRAGDROP_S_DROP, action);
+
+ // Destroy SourceContextslkfgj
+ pSource->m_currentContext= nullptr;
+ // Destroy the XTransferable wrapper
+ pSource->m_spDataObject=nullptr;
+
+ OleUninitialize();
+ }
+
+ InterlockedDecrement(&pSource->m_RunningDndOperationCount);
+
+ // the DragSource was manually acquired by
+ // thread starting method DelayedStartDrag
+ pSource->release();
+
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/sourcecontext.cxx b/vcl/win/dtrans/sourcecontext.cxx
new file mode 100644
index 0000000000..c0e6371c5b
--- /dev/null
+++ b/vcl/win/dtrans/sourcecontext.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 <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+
+#include "sourcecontext.hxx"
+
+using namespace com::sun::star::datatransfer::dnd;
+using namespace com::sun::star::datatransfer::dnd::DNDConstants;
+
+SourceContext::SourceContext( DragSource* pSource,
+ const Reference<XDragSourceListener>& listener):
+ WeakComponentImplHelper<XDragSourceContext>(m_aMutex),
+ m_pDragSource( pSource),
+ m_dragSource( static_cast<XDragSource*>( m_pDragSource) )
+{
+#if OSL_DEBUG_LEVEL > 1
+ if( listener.is())
+#endif
+ rBHelper.addListener( cppu::UnoType<decltype(listener)>::get(), listener );
+}
+
+SourceContext::~SourceContext()
+{
+}
+
+void SourceContext::addDragSourceListener(
+ const Reference<XDragSourceListener >& )
+{
+}
+
+void SourceContext::removeDragSourceListener(
+ const Reference<XDragSourceListener >& )
+{
+}
+
+sal_Int32 SAL_CALL SourceContext::getCurrentCursor( )
+{
+ return 0;
+}
+
+void SAL_CALL SourceContext::setCursor( sal_Int32 /*cursorId*/ )
+{
+}
+
+void SAL_CALL SourceContext::setImage( sal_Int32 /*imageId*/ )
+{
+}
+
+void SAL_CALL SourceContext::transferablesFlavorsChanged( )
+{
+}
+
+// non -interface functions
+// Fires XDragSourceListener::dragDropEnd events.
+void SourceContext::fire_dragDropEnd( bool success, sal_Int8 effect)
+{
+
+ DragSourceDropEvent e;
+
+ if( success )
+ {
+ e.DropAction= effect;
+ e.DropSuccess= true;
+ }
+ else
+ {
+ e.DropAction= ACTION_NONE;
+ e.DropSuccess= false;
+ }
+ e.DragSource= m_dragSource;
+ e.DragSourceContext= static_cast<XDragSourceContext*>( this);
+ e.Source.set( static_cast<XDragSourceContext*>( this), UNO_QUERY);
+
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer(
+ cppu::UnoType<XDragSourceListener>::get());
+
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer);
+ while( iter.hasMoreElements())
+ {
+ Reference<XDragSourceListener> listener(
+ static_cast<XDragSourceListener*>( iter.next()));
+ listener->dragDropEnd( e);
+ }
+ }
+}
+
+void SourceContext::fire_dropActionChanged( sal_Int8 dropAction, sal_Int8 userAction)
+{
+ if( m_currentAction != dropAction)
+ {
+ m_currentAction= dropAction;
+ DragSourceDragEvent e;
+ e.DropAction= dropAction;
+ e.UserAction= userAction;
+ e.DragSource= m_dragSource;
+ e.DragSourceContext= static_cast<XDragSourceContext*>( this);
+ e.Source.set( static_cast<XDragSourceContext*>( this), UNO_QUERY);
+
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer(
+ cppu::UnoType<XDragSourceListener>::get());
+
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer);
+ while( iter.hasMoreElements())
+ {
+ Reference<XDragSourceListener> listener(
+ static_cast<XDragSourceListener*>( iter.next()));
+ listener->dropActionChanged( e);
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/sourcecontext.hxx b/vcl/win/dtrans/sourcecontext.hxx
new file mode 100644
index 0000000000..4471747956
--- /dev/null
+++ b/vcl/win/dtrans/sourcecontext.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 .
+ */
+#pragma once
+
+#include <com/sun/star/datatransfer/dnd/XDragSourceContext.hpp>
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/basemutex.hxx>
+
+#include <win/dnd_source.hxx>
+
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::datatransfer::dnd;
+using namespace ::cppu;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+
+// This class fires events to XDragSourceListener implementations.
+// Of that interface only dragDropEnd and dropActionChanged are called.
+// The functions dragEnter, dragExit and dragOver are not supported
+// currently.
+// An instance of SourceContext only lives as long as the drag and drop
+// operation lasts.
+class SourceContext : public cppu::BaseMutex, public WeakComponentImplHelper<XDragSourceContext>
+{
+ DragSource* m_pDragSource;
+ Reference<XDragSource> m_dragSource;
+ // the action ( copy, move etc)
+ sal_Int8 m_currentAction;
+
+public:
+ SourceContext(DragSource* pSource, const Reference<XDragSourceListener>& listener);
+ ~SourceContext() override;
+ SourceContext(const SourceContext&) = delete;
+ SourceContext& operator=(const SourceContext&) = delete;
+
+ /// @throws RuntimeException
+ virtual void addDragSourceListener(const Reference<XDragSourceListener>& dsl);
+ /// @throws RuntimeException
+ virtual void removeDragSourceListener(const Reference<XDragSourceListener>& dsl);
+ virtual sal_Int32 SAL_CALL getCurrentCursor() override;
+ virtual void SAL_CALL setCursor(sal_Int32 cursorId) override;
+ virtual void SAL_CALL setImage(sal_Int32 imageId) override;
+ virtual void SAL_CALL transferablesFlavorsChanged() override;
+
+ // non - interface functions
+ void fire_dragDropEnd(bool success, sal_Int8 byte);
+ void fire_dropActionChanged(sal_Int8 dropAction, sal_Int8 userAction);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/target.cxx b/vcl/win/dtrans/target.cxx
new file mode 100644
index 0000000000..abe53379ea
--- /dev/null
+++ b/vcl/win/dtrans/target.cxx
@@ -0,0 +1,646 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <o3tl/any.hxx>
+
+#include <stdio.h>
+#include <win/dnd_target.hxx>
+#include "idroptarget.hxx"
+#include "globals.hxx"
+#include "targetdropcontext.hxx"
+#include "targetdragcontext.hxx"
+#include <rtl/ustring.h>
+#include <osl/thread.h>
+#include <sal/log.hxx>
+#include <comphelper/windowserrorstring.hxx>
+
+#include "DOTransferable.hxx"
+
+using namespace cppu;
+using namespace osl;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::datatransfer::dnd;
+using namespace com::sun::star::datatransfer::dnd::DNDConstants;
+
+#define WM_REGISTERDRAGDROP WM_USER + 1
+#define WM_REVOKEDRAGDROP WM_USER + 2
+
+unsigned __stdcall DndTargetOleSTAFunc(void* pParams);
+
+DropTarget::DropTarget( const Reference<XComponentContext>& rxContext):
+ WeakComponentImplHelper<XInitialization,XDropTarget, XServiceInfo>(m_aMutex),
+ m_hWnd( nullptr),
+ m_threadIdWindow(0),
+ m_threadIdTarget(0),
+ m_hOleThread(nullptr),
+ m_oleThreadId( 0),
+ m_pDropTarget( nullptr),
+ m_xContext( rxContext ),
+ m_bActive(true),
+ m_nDefaultActions(ACTION_COPY|ACTION_MOVE|ACTION_LINK|ACTION_DEFAULT),
+ m_nCurrentDropAction( ACTION_NONE),
+ m_nLastDropAction(0),
+ m_bDropComplete(false)
+{
+}
+
+DropTarget::~DropTarget()
+{
+}
+// called from WeakComponentImplHelperX::dispose
+// WeakComponentImplHelper calls disposing before it destroys
+// itself.
+// NOTE: RevokeDragDrop decrements the ref count on the IDropTarget
+// interface. (m_pDropTarget)
+// If the HWND is invalid then it doesn't decrement and
+// the IDropTarget object will live on. MEMORY LEAK
+void SAL_CALL DropTarget::disposing()
+{
+ if( m_threadIdTarget)
+ {
+ // Call RevokeDragDrop and wait for the OLE thread to die;
+ PostThreadMessageW( m_threadIdTarget, WM_REVOKEDRAGDROP, reinterpret_cast<WPARAM>(this), 0);
+ WaitForSingleObject( m_hOleThread, INFINITE);
+ CloseHandle( m_hOleThread);
+ //OSL_ENSURE( SUCCEEDED( hr), "HWND not valid!" );
+ }
+ else
+ {
+ RevokeDragDrop( m_hWnd);
+ m_hWnd= nullptr;
+ }
+ if( m_pDropTarget)
+ {
+ CoLockObjectExternal( m_pDropTarget, FALSE, TRUE);
+ m_pDropTarget->Release();
+ m_pDropTarget = nullptr;
+ }
+
+ if( m_oleThreadId)
+ {
+ if( m_oleThreadId == CoGetCurrentProcess() )
+ OleUninitialize();
+ }
+
+}
+
+void SAL_CALL DropTarget::initialize( const Sequence< Any >& aArguments )
+{
+ // The window must be registered for Dnd by RegisterDragDrop. We must ensure
+ // that RegisterDragDrop is called from an STA ( OleInitialize) thread.
+ // As long as the window is registered we need to receive OLE messages in
+ // an OLE thread. That is to say, if DropTarget::initialize was called from an
+ // MTA thread then we create an OLE thread in which the window is registered.
+ // The thread will stay alive until aver RevokeDragDrop has been called.
+
+ // Additionally even if RegisterDragDrop is called from an STA thread we have
+ // to ensure that it is called from the same thread that created the Window
+ // otherwise messages sent during DND won't reach the windows message queue.
+ // Calling AttachThreadInput first would resolve this problem but would block
+ // the message queue of the calling thread. So if the current thread
+ // (even if it's an STA thread) and the thread that created the window are not
+ // identical we need to create a new thread as we do when the calling thread is
+ // an MTA thread.
+
+ if( aArguments.getLength() > 0)
+ {
+ // Get the window handle from aArgument. It is needed for RegisterDragDrop.
+ m_hWnd= reinterpret_cast<HWND>(static_cast<sal_uIntPtr>(*o3tl::doAccess<sal_uInt64>(aArguments[0])));
+ OSL_ASSERT( IsWindow( m_hWnd) );
+
+ // Obtain the id of the thread that created the window
+ m_threadIdWindow= GetWindowThreadProcessId( m_hWnd, nullptr);
+
+ HRESULT hr= OleInitialize( nullptr);
+
+ // Current thread is MTA or Current thread and Window thread are not identical
+ if( hr == RPC_E_CHANGED_MODE || GetCurrentThreadId() != m_threadIdWindow )
+ {
+ OSL_ENSURE( ! m_threadIdTarget,"initialize was called twice");
+ // create the IDropTargetImplementation
+ m_pDropTarget= new IDropTargetImpl( *this );
+ m_pDropTarget->AddRef();
+
+ // Obtain the id of the thread that created the window
+ m_threadIdWindow= GetWindowThreadProcessId( m_hWnd, nullptr);
+ // The event is set by the thread that we will create momentarily.
+ // It indicates that the thread is ready to receive messages.
+ HANDLE m_evtThreadReady= CreateEventW( nullptr, FALSE, FALSE, nullptr);
+
+ m_hOleThread = reinterpret_cast<HANDLE>(_beginthreadex(nullptr, 0, DndTargetOleSTAFunc,
+ &m_evtThreadReady, 0, &m_threadIdTarget));
+ WaitForSingleObject( m_evtThreadReady, INFINITE);
+ CloseHandle( m_evtThreadReady);
+ PostThreadMessageW( m_threadIdTarget, WM_REGISTERDRAGDROP, reinterpret_cast<WPARAM>(this), 0);
+ }
+ else if( hr == S_OK || hr == S_FALSE)
+ {
+ // current thread is STA
+ // If OleInitialize has been called by the caller then we must not call
+ // OleUninitialize
+ if( hr == S_OK)
+ {
+ // caller did not call OleInitialize, so we call OleUninitialize
+ // remember the thread that will call OleUninitialize
+ m_oleThreadId= CoGetCurrentProcess(); // get a unique thread id
+ }
+
+ // Get the window handle from aArgument. It is needed for RegisterDragDrop.
+ // create the IDropTargetImplementation
+ m_pDropTarget= new IDropTargetImpl( *this );
+ m_pDropTarget->AddRef();
+ // CoLockObjectExternal is prescribed by the protocol. It bumps up the ref count
+ if( SUCCEEDED( CoLockObjectExternal( m_pDropTarget, TRUE, FALSE)))
+ {
+ if( FAILED( RegisterDragDrop( m_hWnd, m_pDropTarget) ) )
+ {
+ // do clean up if drag and drop is not possible
+ CoLockObjectExternal( m_pDropTarget, FALSE, FALSE);
+ m_pDropTarget->Release();
+ m_pDropTarget = nullptr;
+ m_hWnd= nullptr;
+ }
+ }
+ }
+ else
+ throw Exception("OleInitialize failed with " + OUString::number(hr), nullptr);
+
+ }
+}
+
+// This function is called as extra thread from DragSource::startDrag.
+// The function carries out a drag and drop operation by calling
+// DoDragDrop. The thread also notifies all XSourceListener.
+unsigned __stdcall DndTargetOleSTAFunc(void* pParams)
+{
+ osl_setThreadName("DropTarget DndTargetOleSTAFunc");
+
+ HRESULT hr= OleInitialize( nullptr);
+ if( SUCCEEDED( hr) )
+ {
+ MSG msg;
+ // force the creation of a message queue
+ PeekMessageW( &msg, nullptr, 0, 0, PM_NOREMOVE);
+ // Signal the creator ( DropTarget::initialize) that the thread is
+ // ready to receive messages.
+ SetEvent( *static_cast<HANDLE*>(pParams));
+ // Thread id is needed for attaching this message queue to the one of the
+ // thread where the window was created.
+ DWORD threadId= GetCurrentThreadId();
+ // We force the creation of a thread message queue. This is necessary
+ // for a later call to AttachThreadInput
+ for (;;)
+ {
+ int const bRet = GetMessageW(&msg, nullptr, 0, 0);
+ if (bRet == 0)
+ {
+ break;
+ }
+ if (-1 == bRet)
+ {
+ SAL_WARN("vcl.win.dtrans", "GetMessageW failed: " << WindowsErrorString(GetLastError()));
+ break;
+ }
+ if( msg.message == WM_REGISTERDRAGDROP)
+ {
+ DropTarget *pTarget= reinterpret_cast<DropTarget*>(msg.wParam);
+ // This thread is attached to the thread that created the window. Hence
+ // this thread also receives all mouse and keyboard messages which are
+ // needed
+ AttachThreadInput( threadId , pTarget->m_threadIdWindow, TRUE );
+
+ if( SUCCEEDED( CoLockObjectExternal(pTarget-> m_pDropTarget, TRUE, FALSE)))
+ {
+ if( FAILED( RegisterDragDrop( pTarget-> m_hWnd, pTarget-> m_pDropTarget) ) )
+ {
+ // do clean up if drag and drop is not possible
+ CoLockObjectExternal( pTarget->m_pDropTarget, FALSE, FALSE);
+ pTarget->m_pDropTarget->Release();
+ pTarget->m_pDropTarget = nullptr;
+ pTarget->m_hWnd= nullptr;
+ }
+ }
+ }
+ else if( msg.message == WM_REVOKEDRAGDROP)
+ {
+ DropTarget *pTarget= reinterpret_cast<DropTarget*>(msg.wParam);
+ RevokeDragDrop( pTarget-> m_hWnd);
+ // Detach this thread from the window thread
+ AttachThreadInput( threadId, pTarget->m_threadIdWindow, FALSE);
+ pTarget->m_hWnd= nullptr;
+ break;
+ }
+ TranslateMessage( &msg);
+ DispatchMessageW( &msg);
+ }
+ OleUninitialize();
+ }
+ return 0;
+}
+
+// XServiceInfo
+OUString SAL_CALL DropTarget::getImplementationName( )
+{
+ return "com.sun.star.comp.datatransfer.dnd.OleDropTarget_V1";
+}
+// XServiceInfo
+sal_Bool SAL_CALL DropTarget::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SAL_CALL DropTarget::getSupportedServiceNames( )
+{
+ return { "com.sun.star.datatransfer.dnd.OleDropTarget" };
+}
+
+// XDropTarget
+void SAL_CALL DropTarget::addDropTargetListener( const Reference< XDropTargetListener >& dtl )
+{
+ rBHelper.addListener( cppu::UnoType<decltype(dtl)>::get(), dtl );
+}
+
+void SAL_CALL DropTarget::removeDropTargetListener( const Reference< XDropTargetListener >& dtl )
+{
+ rBHelper.removeListener( cppu::UnoType<decltype(dtl)>::get(), dtl );
+}
+
+sal_Bool SAL_CALL DropTarget::isActive( )
+{
+ return m_bActive; //m_bDropTargetRegistered;
+}
+
+void SAL_CALL DropTarget::setActive( sal_Bool _b )
+{
+ MutexGuard g(m_aMutex);
+ m_bActive= _b;
+}
+
+sal_Int8 SAL_CALL DropTarget::getDefaultActions( )
+{
+ return m_nDefaultActions;
+}
+
+void SAL_CALL DropTarget::setDefaultActions( sal_Int8 actions )
+{
+ OSL_ENSURE( actions < 8, "No valid default actions");
+ m_nDefaultActions= actions;
+}
+
+HRESULT DropTarget::DragEnter( IDataObject *pDataObj,
+ DWORD grfKeyState,
+ POINTL pt,
+ DWORD *pdwEffect)
+{
+#if defined DBG_CONSOLE_OUT
+ printf("\nDropTarget::DragEnter state: %x effect %d", grfKeyState, *pdwEffect);
+#endif
+ if( m_bActive )
+ {
+ // Intersection of pdwEffect and the allowed actions ( setDefaultActions)
+ m_nCurrentDropAction= getFilteredActions( grfKeyState, *pdwEffect);
+ // m_nLastDropAction has to be set by a listener. If no listener calls
+ //XDropTargetDragContext::acceptDrag and specifies an action then pdwEffect
+ // will be DROPEFFECT_NONE throughout
+ m_nLastDropAction= ACTION_DEFAULT | ACTION_MOVE;
+
+ m_currentDragContext = new TargetDragContext(this);
+
+ //--> TRA
+
+ // shortcut
+ if ( g_XTransferable.is( ) )
+ m_currentData = g_XTransferable;
+ else
+ {
+ // Convert the IDataObject to a XTransferable
+ m_currentData = new CDOTransferable(m_xContext, IDataObjectPtr(pDataObj));
+ }
+
+ //<-- TRA
+
+ if( m_nCurrentDropAction != ACTION_NONE)
+ {
+ DropTargetDragEnterEvent e;
+ e.SupportedDataFlavors= m_currentData->getTransferDataFlavors();
+ e.DropAction= m_nCurrentDropAction;
+ e.Source.set( static_cast<XDropTarget*>(this),UNO_QUERY);
+ e.Context= m_currentDragContext;
+ POINT point={ pt.x, pt.y};
+ ScreenToClient( m_hWnd, &point);
+ e.LocationX= point.x;
+ e.LocationY= point.y;
+ e.SourceActions= dndOleDropEffectsToActions( *pdwEffect);
+
+ fire_dragEnter( e);
+ // Check if the action derived from grfKeyState (m_nCurrentDropAction) or the action set
+ // by the listener (m_nCurrentDropAction) is allowed by the source. Only an allowed action is set
+ // in pdwEffect. The listener notification is asynchronous, that is we cannot expect that the listener
+ // has already reacted to the notification.
+ // If there is more than one valid action which is the case when ALT or RIGHT MOUSE BUTTON is pressed
+ // then getDropEffect returns DROPEFFECT_MOVE which is the default value if no other modifier is pressed.
+ // On drop the target should present the user a dialog from which the user may change the action.
+ sal_Int8 allowedActions= dndOleDropEffectsToActions( *pdwEffect);
+ *pdwEffect= dndActionsToSingleDropEffect( m_nLastDropAction & allowedActions);
+ }
+ else
+ {
+ *pdwEffect= DROPEFFECT_NONE;
+ }
+ }
+ return S_OK;
+}
+
+HRESULT DropTarget::DragOver( DWORD grfKeyState,
+ POINTL pt,
+ DWORD *pdwEffect)
+{
+ if( m_bActive)
+ {
+ m_nCurrentDropAction= getFilteredActions( grfKeyState, *pdwEffect);
+
+ if( m_nCurrentDropAction)
+ {
+ DropTargetDragEvent e;
+ e.DropAction= m_nCurrentDropAction;
+ e.Source.set(static_cast<XDropTarget*>(this),UNO_QUERY);
+ e.Context= m_currentDragContext;
+ POINT point={ pt.x, pt.y};
+ ScreenToClient( m_hWnd, &point);
+ e.LocationX= point.x;
+ e.LocationY= point.y;
+ e.SourceActions= dndOleDropEffectsToActions( *pdwEffect);
+
+ // if grfKeyState has changed since the last DragOver then fire events.
+ // A listener might change m_nCurrentDropAction by calling the
+ // XDropTargetDragContext::acceptDrag function. But this is not important
+ // because in the afterwards fired dragOver event the action reflects
+ // grgKeyState again.
+ if( m_nLastDropAction != m_nCurrentDropAction)
+ fire_dropActionChanged( e);
+
+ // The Event contains a XDropTargetDragContext implementation.
+ fire_dragOver( e);
+ // Check if the action derived from grfKeyState (m_nCurrentDropAction) or the action set
+ // by the listener (m_nCurrentDropAction) is allowed by the source. Only an allowed action is set
+ // in pdwEffect. The listener notification is asynchronous, that is we cannot expect that the listener
+ // has already reacted to the notification.
+ // If there is more than one valid action which is the case when ALT or RIGHT MOUSE BUTTON is pressed
+ // then getDropEffect returns DROPEFFECT_MOVE which is the default value if no other modifier is pressed.
+ // On drop the target should present the user a dialog from which the user may change the action.
+ sal_Int8 allowedActions= dndOleDropEffectsToActions( *pdwEffect);
+ // set the last action to the current if listener has not changed the value yet
+ *pdwEffect= dndActionsToSingleDropEffect( m_nLastDropAction & allowedActions);
+ }
+ else
+ {
+ *pdwEffect= DROPEFFECT_NONE;
+ }
+ }
+#if defined DBG_CONSOLE_OUT
+ printf("\nDropTarget::DragOver %d", *pdwEffect );
+#endif
+ return S_OK;
+}
+
+HRESULT DropTarget::DragLeave()
+{
+#if defined DBG_CONSOLE_OUT
+ printf("\nDropTarget::DragLeave");
+#endif
+ if( m_bActive)
+ {
+
+ m_currentData=nullptr;
+ m_currentDragContext= nullptr;
+ m_currentDropContext= nullptr;
+ m_nLastDropAction= 0;
+
+ if( m_nDefaultActions != ACTION_NONE)
+ {
+ DropTargetEvent e;
+ e.Source= static_cast<XDropTarget*>(this);
+
+ fire_dragExit( e);
+ }
+ }
+ return S_OK;
+}
+
+HRESULT DropTarget::Drop( IDataObject * /*pDataObj*/,
+ DWORD grfKeyState,
+ POINTL pt,
+ DWORD *pdwEffect)
+{
+#if defined DBG_CONSOLE_OUT
+ printf("\nDropTarget::Drop");
+#endif
+ if( m_bActive)
+ {
+
+ m_bDropComplete= false;
+
+ m_nCurrentDropAction= getFilteredActions( grfKeyState, *pdwEffect);
+ m_currentDropContext = new TargetDropContext(this);
+ if( m_nCurrentDropAction)
+ {
+ DropTargetDropEvent e;
+ e.DropAction= m_nCurrentDropAction;
+ e.Source.set( static_cast<XDropTarget*>(this), UNO_QUERY);
+ e.Context= m_currentDropContext;
+ POINT point={ pt.x, pt.y};
+ ScreenToClient( m_hWnd, &point);
+ e.LocationX= point.x;
+ e.LocationY= point.y;
+ e.SourceActions= dndOleDropEffectsToActions( *pdwEffect);
+ e.Transferable= m_currentData;
+ fire_drop( e);
+
+ //if fire_drop returns than a listener might have modified m_nCurrentDropAction
+ if( m_bDropComplete )
+ {
+ sal_Int8 allowedActions= dndOleDropEffectsToActions( *pdwEffect);
+ *pdwEffect= dndActionsToSingleDropEffect( m_nCurrentDropAction & allowedActions);
+ }
+ else
+ *pdwEffect= DROPEFFECT_NONE;
+ }
+ else
+ *pdwEffect= DROPEFFECT_NONE;
+
+ m_currentData= nullptr;
+ m_currentDragContext= nullptr;
+ m_currentDropContext= nullptr;
+ m_nLastDropAction= 0;
+ }
+ return S_OK;
+}
+
+void DropTarget::fire_drop( const DropTargetDropEvent& dte)
+{
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer);
+ while( iter.hasMoreElements())
+ {
+ Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
+ listener->drop( dte);
+ }
+ }
+}
+
+void DropTarget::fire_dragEnter( const DropTargetDragEnterEvent& e )
+{
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer);
+ while( iter.hasMoreElements())
+ {
+ Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
+ listener->dragEnter( e);
+ }
+ }
+}
+
+void DropTarget::fire_dragExit( const DropTargetEvent& dte )
+{
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer);
+ while( iter.hasMoreElements())
+ {
+ Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
+ listener->dragExit( dte);
+ }
+ }
+}
+
+void DropTarget::fire_dragOver( const DropTargetDragEvent& dtde )
+{
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer );
+ while( iter.hasMoreElements())
+ {
+ Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
+ listener->dragOver( dtde);
+ }
+ }
+}
+
+void DropTarget::fire_dropActionChanged( const DropTargetDragEvent& dtde )
+{
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer);
+ while( iter.hasMoreElements())
+ {
+ Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
+ listener->dropActionChanged( dtde);
+ }
+ }
+}
+
+// Non - interface functions
+// DropTarget fires events to XDropTargetListeners. The event object contains an
+// XDropTargetDropContext implementation. When the listener calls on that interface
+// then the calls are delegated from DropContext (XDropTargetDropContext) to these
+// functions.
+// Only one listener which visible area is affected is allowed to call on
+// XDropTargetDropContext
+// Returning sal_False would cause the XDropTargetDropContext or ..DragContext implementation
+// to throw an InvalidDNDOperationException, meaning that a Drag is not currently performed.
+// return sal_False results in throwing an InvalidDNDOperationException in the caller.
+
+void DropTarget::_acceptDrop(sal_Int8 dropOperation, const Reference<XDropTargetDropContext>& context)
+{
+ if( context == m_currentDropContext)
+ {
+ m_nCurrentDropAction= dropOperation;
+ }
+}
+
+void DropTarget::_rejectDrop( const Reference<XDropTargetDropContext>& context)
+{
+ if( context == m_currentDropContext)
+ {
+ m_nCurrentDropAction= ACTION_NONE;
+ }
+}
+
+void DropTarget::_dropComplete(bool success, const Reference<XDropTargetDropContext>& context)
+{
+ if(context == m_currentDropContext)
+ {
+ m_bDropComplete= success;
+ }
+}
+
+// DropTarget fires events to XDropTargetListeners. The event object can contains an
+// XDropTargetDragContext implementation. When the listener calls on that interface
+// then the calls are delegated from DragContext (XDropTargetDragContext) to these
+// functions.
+// Only one listener which visible area is affected is allowed to call on
+// XDropTargetDragContext
+void DropTarget::_acceptDrag( sal_Int8 dragOperation, const Reference<XDropTargetDragContext>& context)
+{
+ if( context == m_currentDragContext)
+ {
+ m_nLastDropAction= dragOperation;
+ }
+}
+
+void DropTarget::_rejectDrag( const Reference<XDropTargetDragContext>& context)
+{
+ if(context == m_currentDragContext)
+ {
+ m_nLastDropAction= ACTION_NONE;
+ }
+}
+
+// This function determines the action dependent on the pressed
+// key modifiers ( CTRL, SHIFT, ALT, Right Mouse Button). The result
+// is then checked against the allowed actions which can be set through
+// XDropTarget::setDefaultActions. Only those values which are also
+// default actions are returned. If setDefaultActions has not been called
+// beforehand the default actions comprise all possible actions.
+// params: grfKeyState - the modifier keys and mouse buttons currently pressed
+inline sal_Int8 DropTarget::getFilteredActions( DWORD grfKeyState, DWORD dwEffect)
+{
+ sal_Int8 actions= dndOleKeysToAction( grfKeyState, dndOleDropEffectsToActions( dwEffect));
+ return actions & m_nDefaultActions;
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+dtrans_DropTarget_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new DropTarget(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/targetdragcontext.cxx b/vcl/win/dtrans/targetdragcontext.cxx
new file mode 100644
index 0000000000..71e345b74c
--- /dev/null
+++ b/vcl/win/dtrans/targetdragcontext.cxx
@@ -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 .
+ */
+
+#include "targetdragcontext.hxx"
+
+TargetDragContext::TargetDragContext(DropTarget* p)
+{
+ m_pDropTarget = p;
+ p->acquire();
+}
+
+TargetDragContext::~TargetDragContext() { m_pDropTarget->release(); }
+
+void SAL_CALL TargetDragContext::acceptDrag(sal_Int8 dragOperation)
+{
+ m_pDropTarget->_acceptDrag(dragOperation, static_cast<XDropTargetDragContext*>(this));
+}
+void SAL_CALL TargetDragContext::rejectDrag()
+{
+ m_pDropTarget->_rejectDrag(static_cast<XDropTargetDragContext*>(this));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/targetdragcontext.hxx b/vcl/win/dtrans/targetdragcontext.hxx
new file mode 100644
index 0000000000..a64ab6a30b
--- /dev/null
+++ b/vcl/win/dtrans/targetdragcontext.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/datatransfer/dnd/XDropTargetDragContext.hpp>
+#include <com/sun/star/datatransfer/DataFlavor.hpp>
+
+#include <win/dnd_target.hxx>
+
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::datatransfer::dnd;
+using namespace ::cppu;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+
+class TargetDragContext : public WeakImplHelper<XDropTargetDragContext>
+{
+ // some calls to the functions of XDropTargetDragContext are delegated
+ // to non-interface functions of m_pDropTarget
+ DropTarget* m_pDropTarget;
+
+public:
+ explicit TargetDragContext(DropTarget* pTarget);
+ ~TargetDragContext() override;
+ TargetDragContext(const TargetDragContext&) = delete;
+ TargetDragContext& operator=(const TargetDragContext&) = delete;
+
+ virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) override;
+ virtual void SAL_CALL rejectDrag() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/targetdropcontext.cxx b/vcl/win/dtrans/targetdropcontext.cxx
new file mode 100644
index 0000000000..fe65389431
--- /dev/null
+++ b/vcl/win/dtrans/targetdropcontext.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 "targetdropcontext.hxx"
+
+using namespace ::com::sun::star::datatransfer::dnd;
+using namespace ::cppu;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+
+TargetDropContext::TargetDropContext(DropTarget* p)
+{
+ m_pDropTarget = p;
+ p->acquire();
+}
+
+TargetDropContext::~TargetDropContext() { m_pDropTarget->release(); }
+
+void SAL_CALL TargetDropContext::acceptDrop(sal_Int8 dropOperation)
+{
+ m_pDropTarget->_acceptDrop(dropOperation, static_cast<XDropTargetDropContext*>(this));
+}
+
+void SAL_CALL TargetDropContext::rejectDrop()
+{
+ m_pDropTarget->_rejectDrop(static_cast<XDropTargetDropContext*>(this));
+}
+
+void SAL_CALL TargetDropContext::dropComplete(sal_Bool success)
+{
+ m_pDropTarget->_dropComplete(success, static_cast<XDropTargetDropContext*>(this));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/dtrans/targetdropcontext.hxx b/vcl/win/dtrans/targetdropcontext.hxx
new file mode 100644
index 0000000000..938a23f850
--- /dev/null
+++ b/vcl/win/dtrans/targetdropcontext.hxx
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <com/sun/star/datatransfer/dnd/XDropTargetDropContext.hpp>
+
+#include <win/dnd_target.hxx>
+
+using namespace ::com::sun::star::datatransfer::dnd;
+using namespace ::cppu;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+
+class TargetDropContext : public WeakImplHelper<XDropTargetDropContext>
+{
+ // calls to the functions of XDropTargetDropContext are delegated
+ // to non-interface functions of m_pDropTarget
+ DropTarget* m_pDropTarget;
+
+public:
+ explicit TargetDropContext(DropTarget* pTarget);
+ ~TargetDropContext() override;
+ TargetDropContext(const TargetDropContext&) = delete;
+ TargetDropContext& operator=(const TargetDropContext&) = delete;
+
+ // XDropTargetDragContext
+ virtual void SAL_CALL acceptDrop(sal_Int8 dropOperation) override;
+ virtual void SAL_CALL rejectDrop() override;
+
+ // XDropTargetDropContext (inherits XDropTargetDragContext)
+ virtual void SAL_CALL dropComplete(sal_Bool success) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/gdi/DWriteTextRenderer.cxx b/vcl/win/gdi/DWriteTextRenderer.cxx
new file mode 100644
index 0000000000..3d3dac83c6
--- /dev/null
+++ b/vcl/win/gdi/DWriteTextRenderer.cxx
@@ -0,0 +1,341 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <win/salgdi.h>
+#include <win/saldata.hxx>
+#include <ImplOutDevData.hxx>
+
+#include <win/DWriteTextRenderer.hxx>
+
+#include <sft.hxx>
+#include <sallayout.hxx>
+
+#include <shlwapi.h>
+#include <winver.h>
+
+#include <comphelper/windowserrorstring.hxx>
+#include <o3tl/safeint.hxx>
+#include <sal/log.hxx>
+
+namespace
+{
+
+D2DTextAntiAliasMode lclGetSystemTextAntiAliasMode()
+{
+ D2DTextAntiAliasMode eMode = D2DTextAntiAliasMode::Default;
+
+ BOOL bFontSmoothing;
+ if (!SystemParametersInfoW(SPI_GETFONTSMOOTHING, 0, &bFontSmoothing, 0))
+ return eMode;
+
+ if (bFontSmoothing)
+ {
+ eMode = D2DTextAntiAliasMode::AntiAliased;
+
+ UINT nType;
+ if (SystemParametersInfoW(SPI_GETFONTSMOOTHINGTYPE, 0, &nType, 0) && nType == FE_FONTSMOOTHINGCLEARTYPE)
+ eMode = D2DTextAntiAliasMode::ClearType;
+ }
+ else
+ {
+ eMode = D2DTextAntiAliasMode::Aliased;
+ }
+
+ return eMode;
+}
+
+IDWriteRenderingParams* lclSetRenderingMode(IDWriteFactory* pDWriteFactory, DWRITE_RENDERING_MODE eRenderingMode)
+{
+ IDWriteRenderingParams* pDefaultParameters = nullptr;
+ pDWriteFactory->CreateRenderingParams(&pDefaultParameters);
+
+ IDWriteRenderingParams* pParameters = nullptr;
+ pDWriteFactory->CreateCustomRenderingParams(
+ pDefaultParameters->GetGamma(),
+ pDefaultParameters->GetEnhancedContrast(),
+ pDefaultParameters->GetClearTypeLevel(),
+ pDefaultParameters->GetPixelGeometry(),
+ eRenderingMode,
+ &pParameters);
+ return pParameters;
+}
+
+#ifdef SAL_LOG_WARN
+HRESULT checkResult(HRESULT hr, const char* file, size_t line)
+{
+ if (FAILED(hr))
+ {
+ OUString sLocationString = OUString::createFromAscii(file) + ":" + OUString::number(line) + " ";
+ SAL_DETAIL_LOG_STREAM(SAL_DETAIL_ENABLE_LOG_WARN, ::SAL_DETAIL_LOG_LEVEL_WARN,
+ "vcl.gdi", sLocationString.toUtf8().getStr(),
+ "HRESULT failed with: 0x" << OUString::number(hr, 16) << ": " << WindowsErrorStringFromHRESULT(hr));
+ }
+ return hr;
+}
+
+#define CHECKHR(funct) checkResult(funct, __FILE__, __LINE__)
+#else
+#define CHECKHR(funct) (funct)
+#endif
+
+
+} // end anonymous namespace
+
+D2DWriteTextOutRenderer::D2DWriteTextOutRenderer(bool bRenderingModeNatural)
+ : mpD2DFactory(nullptr),
+ mpRT(nullptr),
+ mRTProps(D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT,
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
+ 0, 0)),
+ mbRenderingModeNatural(bRenderingModeNatural),
+ meTextAntiAliasMode(D2DTextAntiAliasMode::Default)
+{
+ WinSalGraphics::getDWriteFactory(&mpDWriteFactory);
+ HRESULT hr = S_OK;
+ hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), nullptr, reinterpret_cast<void **>(&mpD2DFactory));
+ if (SUCCEEDED(hr))
+ hr = CreateRenderTarget(bRenderingModeNatural);
+ meTextAntiAliasMode = lclGetSystemTextAntiAliasMode();
+}
+
+D2DWriteTextOutRenderer::~D2DWriteTextOutRenderer()
+{
+ if (mpRT)
+ mpRT->Release();
+ if (mpD2DFactory)
+ mpD2DFactory->Release();
+}
+
+void D2DWriteTextOutRenderer::applyTextAntiAliasMode(bool bRenderingModeNatural)
+{
+ D2D1_TEXT_ANTIALIAS_MODE eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
+ DWRITE_RENDERING_MODE eRenderingMode = DWRITE_RENDERING_MODE_DEFAULT;
+ switch (meTextAntiAliasMode)
+ {
+ case D2DTextAntiAliasMode::Default:
+ eRenderingMode = DWRITE_RENDERING_MODE_DEFAULT;
+ eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
+ break;
+ case D2DTextAntiAliasMode::Aliased:
+ eRenderingMode = DWRITE_RENDERING_MODE_ALIASED;
+ eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
+ break;
+ case D2DTextAntiAliasMode::AntiAliased:
+ eRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC;
+ eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
+ break;
+ case D2DTextAntiAliasMode::ClearType:
+ eRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC;
+ eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
+ break;
+ default:
+ break;
+ }
+
+ if (bRenderingModeNatural)
+ eRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL;
+
+ mpRT->SetTextRenderingParams(lclSetRenderingMode(mpDWriteFactory, eRenderingMode));
+ mpRT->SetTextAntialiasMode(eTextAAMode);
+}
+
+HRESULT D2DWriteTextOutRenderer::CreateRenderTarget(bool bRenderingModeNatural)
+{
+ if (mpRT)
+ {
+ mpRT->Release();
+ mpRT = nullptr;
+ }
+ HRESULT hr = CHECKHR(mpD2DFactory->CreateDCRenderTarget(&mRTProps, &mpRT));
+ if (SUCCEEDED(hr))
+ applyTextAntiAliasMode(bRenderingModeNatural);
+ return hr;
+}
+
+bool D2DWriteTextOutRenderer::Ready() const
+{
+ return mpRT;
+}
+
+HRESULT D2DWriteTextOutRenderer::BindDC(HDC hDC, tools::Rectangle const & rRect)
+{
+ RECT const rc = {
+ o3tl::narrowing<LONG>(rRect.Left()), o3tl::narrowing<LONG>(rRect.Top()),
+ o3tl::narrowing<LONG>(rRect.Right()), o3tl::narrowing<LONG>(rRect.Bottom()) };
+ return CHECKHR(mpRT->BindDC(hDC, &rc));
+}
+
+bool D2DWriteTextOutRenderer::operator()(GenericSalLayout const & rLayout, SalGraphics& rGraphics, HDC hDC, bool bRenderingModeNatural)
+{
+ bool bRetry = false;
+ bool bResult = false;
+ int nCount = 0;
+ do
+ {
+ bRetry = false;
+ bResult = performRender(rLayout, rGraphics, hDC, bRetry, bRenderingModeNatural);
+ nCount++;
+ } while (bRetry && nCount < 3);
+ return bResult;
+}
+
+bool D2DWriteTextOutRenderer::performRender(GenericSalLayout const & rLayout, SalGraphics& rGraphics, HDC hDC, bool& bRetry, bool bRenderingModeNatural)
+{
+ if (!Ready())
+ return false;
+
+ HRESULT hr = S_OK;
+ hr = BindDC(hDC);
+
+ if (hr == D2DERR_RECREATE_TARGET)
+ {
+ CreateRenderTarget(bRenderingModeNatural);
+ bRetry = true;
+ return false;
+ }
+ if (FAILED(hr))
+ {
+ // If for any reason we can't bind fallback to legacy APIs.
+ return ExTextOutRenderer()(rLayout, rGraphics, hDC, bRenderingModeNatural);
+ }
+
+ const WinFontInstance& rWinFont = static_cast<const WinFontInstance&>(rLayout.GetFont());
+ float fHScale = rWinFont.getHScale();
+
+ float lfEmHeight = 0;
+ IDWriteFontFace* pFontFace = GetDWriteFace(rWinFont, &lfEmHeight);
+ if (!pFontFace)
+ return false;
+
+ tools::Rectangle bounds;
+ bool succeeded = rLayout.GetBoundRect(bounds);
+ if (succeeded)
+ {
+ hr = BindDC(hDC, bounds); // Update the bounding rect.
+ succeeded &= SUCCEEDED(hr);
+ }
+
+ ID2D1SolidColorBrush* pBrush = nullptr;
+ if (succeeded)
+ {
+ COLORREF bgrTextColor = GetTextColor(hDC);
+ D2D1::ColorF aD2DColor(GetRValue(bgrTextColor) / 255.0f, GetGValue(bgrTextColor) / 255.0f, GetBValue(bgrTextColor) / 255.0f);
+ succeeded &= SUCCEEDED(CHECKHR(mpRT->CreateSolidColorBrush(aD2DColor, &pBrush)));
+ }
+
+ if (succeeded)
+ {
+ mpRT->BeginDraw();
+
+ int nStart = 0;
+ basegfx::B2DPoint aPos;
+ const GlyphItem* pGlyph;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ UINT16 glyphIndices[] = { static_cast<UINT16>(pGlyph->glyphId()) };
+ FLOAT glyphAdvances[] = { static_cast<FLOAT>(pGlyph->newWidth()) / fHScale };
+ DWRITE_GLYPH_OFFSET glyphOffsets[] = { { 0.0f, 0.0f }, };
+ D2D1_POINT_2F baseline = { static_cast<FLOAT>(aPos.getX() - bounds.Left()) / fHScale,
+ static_cast<FLOAT>(aPos.getY() - bounds.Top()) };
+ WinFontTransformGuard aTransformGuard(mpRT, fHScale, rLayout, baseline, pGlyph->IsVertical());
+ DWRITE_GLYPH_RUN glyphs = {
+ pFontFace,
+ lfEmHeight,
+ 1,
+ glyphIndices,
+ glyphAdvances,
+ glyphOffsets,
+ false,
+ 0
+ };
+
+ mpRT->DrawGlyphRun(baseline, &glyphs, pBrush);
+ }
+
+ hr = CHECKHR(mpRT->EndDraw());
+ }
+
+ if (pBrush)
+ pBrush->Release();
+
+ if (hr == D2DERR_RECREATE_TARGET)
+ {
+ CreateRenderTarget(bRenderingModeNatural);
+ bRetry = true;
+ }
+
+ return succeeded;
+}
+
+IDWriteFontFace* D2DWriteTextOutRenderer::GetDWriteFace(const WinFontInstance& rWinFont,
+ float* lfSize) const
+{
+ auto pFontFace = rWinFont.GetDWFontFace();
+ if (pFontFace)
+ {
+ LOGFONTW aLogFont;
+ HFONT hFont = rWinFont.GetHFONT();
+
+ GetObjectW(hFont, sizeof(LOGFONTW), &aLogFont);
+ float dpix, dpiy;
+ mpRT->GetDpi(&dpix, &dpiy);
+ *lfSize = aLogFont.lfHeight * 96.0f / dpiy;
+
+ assert(*lfSize < 0);
+ *lfSize *= -1;
+ }
+
+ return pFontFace;
+}
+
+WinFontTransformGuard::WinFontTransformGuard(ID2D1RenderTarget* pRenderTarget, float fHScale,
+ const GenericSalLayout& rLayout,
+ const D2D1_POINT_2F& rBaseline,
+ bool bIsVertical)
+ : mpRenderTarget(pRenderTarget)
+{
+ pRenderTarget->GetTransform(&maTransform);
+ D2D1::Matrix3x2F aTransform = maTransform;
+ if (fHScale != 1.0f)
+ {
+ aTransform
+ = aTransform * D2D1::Matrix3x2F::Scale(D2D1::Size(fHScale, 1.0f), D2D1::Point2F(0, 0));
+ }
+
+ Degree10 angle = rLayout.GetOrientation();
+
+ if (bIsVertical)
+ angle += 900_deg10;
+
+ if (angle)
+ {
+ // DWrite angle is in clockwise degrees, our orientation is in counter-clockwise 10th
+ // degrees.
+ aTransform = aTransform
+ * D2D1::Matrix3x2F::Rotation(
+ -toDegrees(angle), rBaseline);
+ }
+ mpRenderTarget->SetTransform(aTransform);
+}
+
+WinFontTransformGuard::~WinFontTransformGuard() { mpRenderTarget->SetTransform(maTransform); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/gdi/dw-extra.h b/vcl/win/gdi/dw-extra.h
new file mode 100644
index 0000000000..4c07d81d21
--- /dev/null
+++ b/vcl/win/gdi/dw-extra.h
@@ -0,0 +1,141 @@
+//
+// copied from:
+// https://hg.mozilla.org/mozilla-central/file/704f09a557a4dfc9057f1672b711789f64f74a82/gfx/2d/dw-extra.h
+//
+
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * New DirectWrite interfaces based on Win10 Fall Creators Update versions
+ * of dwrite_3.h and dcommon.h (from SDK 10.0.17061.0). This particular
+ * subset of declarations is intended to be just sufficient to compile the
+ * Gecko DirectWrite font code; it omits many other new interfaces, etc.
+ */
+
+#ifndef DWRITE_EXTRA_H
+#define DWRITE_EXTRA_H
+
+#pragma once
+
+interface IDWriteFontResource;
+interface IDWriteFontFaceReference1;
+
+enum DWRITE_GLYPH_IMAGE_FORMATS {
+ DWRITE_GLYPH_IMAGE_FORMATS_NONE = 0x00000000,
+ DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE = 0x00000001,
+ DWRITE_GLYPH_IMAGE_FORMATS_CFF = 0x00000002,
+ DWRITE_GLYPH_IMAGE_FORMATS_COLR = 0x00000004,
+ DWRITE_GLYPH_IMAGE_FORMATS_SVG = 0x00000008,
+ DWRITE_GLYPH_IMAGE_FORMATS_PNG = 0x00000010,
+ DWRITE_GLYPH_IMAGE_FORMATS_JPEG = 0x00000020,
+ DWRITE_GLYPH_IMAGE_FORMATS_TIFF = 0x00000040,
+ DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8 = 0x00000080,
+};
+
+#ifdef DEFINE_ENUM_FLAG_OPERATORS
+DEFINE_ENUM_FLAG_OPERATORS(DWRITE_GLYPH_IMAGE_FORMATS);
+#endif
+
+#define DWRITE_MAKE_FONT_AXIS_TAG(a, b, c, d) \
+ (static_cast<DWRITE_FONT_AXIS_TAG>(DWRITE_MAKE_OPENTYPE_TAG(a, b, c, d)))
+
+enum DWRITE_FONT_AXIS_TAG : UINT32 {
+ DWRITE_FONT_AXIS_TAG_WEIGHT = DWRITE_MAKE_FONT_AXIS_TAG('w', 'g', 'h', 't'),
+ DWRITE_FONT_AXIS_TAG_WIDTH = DWRITE_MAKE_FONT_AXIS_TAG('w', 'd', 't', 'h'),
+ DWRITE_FONT_AXIS_TAG_SLANT = DWRITE_MAKE_FONT_AXIS_TAG('s', 'l', 'n', 't'),
+ DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE =
+ DWRITE_MAKE_FONT_AXIS_TAG('o', 'p', 's', 'z'),
+ DWRITE_FONT_AXIS_TAG_ITALIC = DWRITE_MAKE_FONT_AXIS_TAG('i', 't', 'a', 'l'),
+};
+
+enum DWRITE_FONT_AXIS_ATTRIBUTES {
+ DWRITE_FONT_AXIS_ATTRIBUTES_NONE = 0x0000,
+ DWRITE_FONT_AXIS_ATTRIBUTES_VARIABLE = 0x0001,
+ DWRITE_FONT_AXIS_ATTRIBUTES_HIDDEN = 0x0002,
+};
+
+struct DWRITE_FONT_AXIS_VALUE {
+ DWRITE_FONT_AXIS_TAG axisTag;
+ FLOAT value;
+};
+
+struct DWRITE_FONT_AXIS_RANGE {
+ DWRITE_FONT_AXIS_TAG axisTag;
+ FLOAT minValue;
+ FLOAT maxValue;
+};
+
+struct DWRITE_GLYPH_IMAGE_DATA {
+ const void* imageData;
+ UINT32 imageDataSize;
+ UINT32 uniqueDataId;
+ UINT32 pixelsPerEm;
+ D2D1_SIZE_U pixelSize;
+ D2D1_POINT_2L horizontalLeftOrigin;
+ D2D1_POINT_2L horizontalRightOrigin;
+ D2D1_POINT_2L verticalTopOrigin;
+ D2D1_POINT_2L verticalBottomOrigin;
+};
+
+interface DWRITE_DECLARE_INTERFACE("27F2A904-4EB8-441D-9678-0563F53E3E2F")
+ IDWriteFontFace4 : public IDWriteFontFace3 {
+ STDMETHOD_(DWRITE_GLYPH_IMAGE_FORMATS, GetGlyphImageFormats)() PURE;
+ STDMETHOD(GetGlyphImageFormats)
+ (UINT16 glyphId, UINT32 pixelsPerEmFirst, UINT32 pixelsPerEmLast,
+ _Out_ DWRITE_GLYPH_IMAGE_FORMATS* glyphImageFormats) PURE;
+ STDMETHOD(GetGlyphImageData)
+ (_In_ UINT16 glyphId, UINT32 pixelsPerEm,
+ DWRITE_GLYPH_IMAGE_FORMATS glyphImageFormat,
+ _Out_ DWRITE_GLYPH_IMAGE_DATA* glyphData,
+ _Outptr_result_maybenull_ void** glyphDataContext) PURE;
+ STDMETHOD_(void, ReleaseGlyphImageData)(void* glyphDataContext) PURE;
+};
+
+interface DWRITE_DECLARE_INTERFACE("98EFF3A5-B667-479A-B145-E2FA5B9FDC29")
+ IDWriteFontFace5 : public IDWriteFontFace4 {
+ STDMETHOD_(UINT32, GetFontAxisValueCount)() PURE;
+ STDMETHOD(GetFontAxisValues)
+ (_Out_writes_(fontAxisValueCount) DWRITE_FONT_AXIS_VALUE* fontAxisValues,
+ UINT32 fontAxisValueCount) PURE;
+ STDMETHOD_(BOOL, HasVariations)() PURE;
+ STDMETHOD(GetFontResource)
+ (_COM_Outptr_ IDWriteFontResource** fontResource) PURE;
+ STDMETHOD_(BOOL, Equals)(IDWriteFontFace* fontFace) PURE;
+};
+
+interface DWRITE_DECLARE_INTERFACE("1F803A76-6871-48E8-987F-B975551C50F2")
+ IDWriteFontResource : public IUnknown {
+ STDMETHOD(GetFontFile)(_COM_Outptr_ IDWriteFontFile** fontFile) PURE;
+ STDMETHOD_(UINT32, GetFontFaceIndex)() PURE;
+ STDMETHOD_(UINT32, GetFontAxisCount)() PURE;
+ STDMETHOD(GetDefaultFontAxisValues)
+ (_Out_writes_(fontAxisValueCount) DWRITE_FONT_AXIS_VALUE* fontAxisValues,
+ UINT32 fontAxisValueCount) PURE;
+ STDMETHOD(GetFontAxisRanges)
+ (_Out_writes_(fontAxisRangeCount) DWRITE_FONT_AXIS_RANGE* fontAxisRanges,
+ UINT32 fontAxisRangeCount) PURE;
+ STDMETHOD_(DWRITE_FONT_AXIS_ATTRIBUTES, GetFontAxisAttributes)
+ (UINT32 axisIndex) PURE;
+ STDMETHOD(GetAxisNames)
+ (UINT32 axisIndex, _COM_Outptr_ IDWriteLocalizedStrings** names) PURE;
+ STDMETHOD_(UINT32, GetAxisValueNameCount)(UINT32 axisIndex) PURE;
+ STDMETHOD(GetAxisValueNames)
+ (UINT32 axisIndex, UINT32 axisValueIndex,
+ _Out_ DWRITE_FONT_AXIS_RANGE* fontAxisRange,
+ _COM_Outptr_ IDWriteLocalizedStrings** names) PURE;
+ STDMETHOD_(BOOL, HasVariations)() PURE;
+ STDMETHOD(CreateFontFace)
+ (DWRITE_FONT_SIMULATIONS fontSimulations,
+ _In_reads_(fontAxisValueCount) DWRITE_FONT_AXIS_VALUE const* fontAxisValues,
+ UINT32 fontAxisValueCount, _COM_Outptr_ IDWriteFontFace5** fontFace) PURE;
+ STDMETHOD(CreateFontFaceReference)
+ (DWRITE_FONT_SIMULATIONS fontSimulations,
+ _In_reads_(fontAxisValueCount) DWRITE_FONT_AXIS_VALUE const* fontAxisValues,
+ UINT32 fontAxisValueCount,
+ _COM_Outptr_ IDWriteFontFaceReference1** fontFaceReference) PURE;
+};
+
+#endif /* DWRITE_EXTRA_H */
diff --git a/vcl/win/gdi/gdiimpl.cxx b/vcl/win/gdi/gdiimpl.cxx
new file mode 100644
index 0000000000..5cb6a05bda
--- /dev/null
+++ b/vcl/win/gdi/gdiimpl.cxx
@@ -0,0 +1,2731 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <memory>
+#include <numeric>
+
+#include <svsys.h>
+
+#include "gdiimpl.hxx"
+
+#include <string.h>
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/poly.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <win/wincomp.hxx>
+#include <win/saldata.hxx>
+#include <win/salgdi.h>
+#include <win/salbmp.h>
+#include <win/scoped_gdi.hxx>
+#include <vcl/BitmapAccessMode.hxx>
+#include <vcl/BitmapBuffer.hxx>
+#include <vcl/BitmapPalette.hxx>
+#include <win/salframe.h>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/utils/systemdependentdata.hxx>
+
+#include <win/salids.hrc>
+#include <ControlCacheKey.hxx>
+
+#include <prewin.h>
+
+#include <gdiplus.h>
+#include <gdiplusenums.h>
+#include <gdipluscolor.h>
+
+#include <postwin.h>
+
+#define SAL_POLYPOLYCOUNT_STACKBUF 8
+#define SAL_POLYPOLYPOINTS_STACKBUF 64
+
+#define SAL_POLY_STACKBUF 32
+
+namespace {
+
+// #100127# Fill point and flag memory from array of points which
+// might also contain bezier control points for the PolyDraw() GDI method
+// Make sure pWinPointAry and pWinFlagAry are big enough
+void ImplPreparePolyDraw( bool bCloseFigures,
+ sal_uLong nPoly,
+ const sal_uInt32* pPoints,
+ const Point* const* pPtAry,
+ const PolyFlags* const* pFlgAry,
+ POINT* pWinPointAry,
+ BYTE* pWinFlagAry )
+{
+ sal_uLong nCurrPoly;
+ for( nCurrPoly=0; nCurrPoly<nPoly; ++nCurrPoly )
+ {
+ const Point* pCurrPoint = *pPtAry++;
+ const PolyFlags* pCurrFlag = *pFlgAry++;
+ const sal_uInt32 nCurrPoints = *pPoints++;
+ const bool bHaveFlagArray( pCurrFlag );
+ sal_uLong nCurrPoint;
+
+ if( nCurrPoints )
+ {
+ // start figure
+ *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
+ pCurrPoint++;
+ *pWinFlagAry++ = PT_MOVETO;
+ ++pCurrFlag;
+
+ for( nCurrPoint=1; nCurrPoint<nCurrPoints; )
+ {
+ // #102067# Check existence of flag array
+ if( bHaveFlagArray &&
+ ( nCurrPoint + 2 ) < nCurrPoints )
+ {
+ PolyFlags P4( pCurrFlag[ 2 ] );
+
+ if( ( PolyFlags::Control == pCurrFlag[ 0 ] ) &&
+ ( PolyFlags::Control == pCurrFlag[ 1 ] ) &&
+ ( PolyFlags::Normal == P4 || PolyFlags::Smooth == P4 || PolyFlags::Symmetric == P4 ) )
+ {
+ // control point one
+ *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
+ pCurrPoint++;
+ *pWinFlagAry++ = PT_BEZIERTO;
+
+ // control point two
+ *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
+ pCurrPoint++;
+ *pWinFlagAry++ = PT_BEZIERTO;
+
+ // end point
+ *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
+ pCurrPoint++;
+ *pWinFlagAry++ = PT_BEZIERTO;
+
+ nCurrPoint += 3;
+ pCurrFlag += 3;
+ continue;
+ }
+ }
+
+ // regular line point
+ *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
+ pCurrPoint++;
+ *pWinFlagAry++ = PT_LINETO;
+ ++pCurrFlag;
+ ++nCurrPoint;
+ }
+
+ // end figure?
+ if( bCloseFigures )
+ pWinFlagAry[-1] |= PT_CLOSEFIGURE;
+ }
+ }
+}
+
+Color ImplGetROPColor( SalROPColor nROPColor )
+{
+ Color nColor;
+ if ( nROPColor == SalROPColor::N0 )
+ nColor = Color( 0, 0, 0 );
+ else
+ nColor = Color( 255, 255, 255 );
+ return nColor;
+}
+
+bool IsDitherColor(BYTE nRed, BYTE nGreen, BYTE nBlue)
+{
+ constexpr sal_uInt8 DITHER_PAL_DELTA = 51;
+
+ return !(nRed % DITHER_PAL_DELTA) &&
+ !(nGreen % DITHER_PAL_DELTA) &&
+ !(nBlue % DITHER_PAL_DELTA);
+}
+
+bool IsPaletteColor(BYTE nRed, BYTE nGreen, BYTE nBlue)
+{
+ static const PALETTEENTRY aImplSalSysPalEntryAry[] =
+ {
+ { 0, 0, 0, 0 },
+ { 0, 0, 0x80, 0 },
+ { 0, 0x80, 0, 0 },
+ { 0, 0x80, 0x80, 0 },
+ { 0x80, 0, 0, 0 },
+ { 0x80, 0, 0x80, 0 },
+ { 0x80, 0x80, 0, 0 },
+ { 0x80, 0x80, 0x80, 0 },
+ { 0xC0, 0xC0, 0xC0, 0 },
+ { 0, 0, 0xFF, 0 },
+ { 0, 0xFF, 0, 0 },
+ { 0, 0xFF, 0xFF, 0 },
+ { 0xFF, 0, 0, 0 },
+ { 0xFF, 0, 0xFF, 0 },
+ { 0xFF, 0xFF, 0, 0 },
+ { 0xFF, 0xFF, 0xFF, 0 }
+ };
+
+ for (const auto& rPalEntry : aImplSalSysPalEntryAry)
+ {
+ if(rPalEntry.peRed == nRed &&
+ rPalEntry.peGreen == nGreen &&
+ rPalEntry.peBlue == nBlue)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool IsExtraColor(BYTE nRed, BYTE nGreen, BYTE nBlue)
+{
+ return (nRed == 0) && (nGreen == 184) && (nBlue == 255);
+}
+
+bool ImplIsPaletteEntry(BYTE nRed, BYTE nGreen, BYTE nBlue)
+{
+ return IsDitherColor(nRed, nGreen, nBlue) ||
+ IsPaletteColor(nRed, nGreen, nBlue) ||
+ IsExtraColor(nRed, nGreen, nBlue);
+}
+
+} // namespace
+
+WinSalGraphicsImpl::WinSalGraphicsImpl(WinSalGraphics& rParent):
+ mrParent(rParent),
+ mbXORMode(false),
+ mbPen(false),
+ mhPen(nullptr),
+ mbStockPen(false),
+ mbBrush(false),
+ mbStockBrush(false),
+ mhBrush(nullptr)
+{
+}
+
+WinSalGraphicsImpl::~WinSalGraphicsImpl()
+{
+ if ( mhPen )
+ {
+ if ( !mbStockPen )
+ DeletePen( mhPen );
+ }
+
+ if ( mhBrush )
+ {
+ if ( !mbStockBrush )
+ DeleteBrush( mhBrush );
+ }
+}
+
+void WinSalGraphicsImpl::Init()
+{
+}
+
+void WinSalGraphicsImpl::freeResources()
+{
+}
+
+bool WinSalGraphicsImpl::drawEPS(tools::Long, tools::Long, tools::Long, tools::Long, void*, sal_uInt32)
+{
+ return false;
+}
+
+void WinSalGraphicsImpl::copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics )
+{
+ HDC hSrcDC;
+ DWORD nRop;
+
+ if ( pSrcGraphics )
+ hSrcDC = static_cast<WinSalGraphics*>(pSrcGraphics)->getHDC();
+ else
+ hSrcDC = mrParent.getHDC();
+
+ if ( mbXORMode )
+ nRop = SRCINVERT;
+ else
+ nRop = SRCCOPY;
+
+ if ( (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) &&
+ (rPosAry.mnSrcHeight == rPosAry.mnDestHeight) )
+ {
+ BitBlt( mrParent.getHDC(),
+ static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
+ static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
+ hSrcDC,
+ static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
+ nRop );
+ }
+ else
+ {
+ int nOldStretchMode = SetStretchBltMode( mrParent.getHDC(), STRETCH_DELETESCANS );
+ StretchBlt( mrParent.getHDC(),
+ static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
+ static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
+ hSrcDC,
+ static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
+ static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight),
+ nRop );
+ SetStretchBltMode( mrParent.getHDC(), nOldStretchMode );
+ }
+}
+
+namespace
+{
+
+void MakeInvisibleArea(const RECT& rSrcRect,
+ int nLeft, int nTop, int nRight, int nBottom,
+ HRGN& rhInvalidateRgn)
+{
+ if (!rhInvalidateRgn)
+ {
+ rhInvalidateRgn = CreateRectRgnIndirect(&rSrcRect);
+ }
+
+ ScopedHRGN hTempRgn(CreateRectRgn(nLeft, nTop, nRight, nBottom));
+ CombineRgn(rhInvalidateRgn, rhInvalidateRgn, hTempRgn.get(), RGN_DIFF);
+}
+
+void ImplCalcOutSideRgn( const RECT& rSrcRect,
+ int nLeft, int nTop, int nRight, int nBottom,
+ HRGN& rhInvalidateRgn )
+{
+ // calculate area outside the visible region
+ if (rSrcRect.left < nLeft)
+ {
+ MakeInvisibleArea(rSrcRect, -31999, 0, nLeft, 31999, rhInvalidateRgn);
+ }
+ if (rSrcRect.top < nTop)
+ {
+ MakeInvisibleArea(rSrcRect, 0, -31999, 31999, nTop, rhInvalidateRgn);
+ }
+ if (rSrcRect.right > nRight)
+ {
+ MakeInvisibleArea(rSrcRect, nRight, 0, 31999, 31999, rhInvalidateRgn);
+ }
+ if (rSrcRect.bottom > nBottom)
+ {
+ MakeInvisibleArea(rSrcRect, 0, nBottom, 31999, 31999, rhInvalidateRgn);
+ }
+}
+
+} // namespace
+
+void WinSalGraphicsImpl::copyArea( tools::Long nDestX, tools::Long nDestY,
+ tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight,
+ bool bWindowInvalidate )
+{
+ bool bRestoreClipRgn = false;
+ HRGN hOldClipRgn = nullptr;
+ int nOldClipRgnType = ERROR;
+ HRGN hInvalidateRgn = nullptr;
+
+ // do we have to invalidate also the overlapping regions?
+ if ( bWindowInvalidate && mrParent.isWindow() )
+ {
+ // compute and invalidate those parts that were either off-screen or covered by other windows
+ // while performing the above BitBlt
+ // those regions then have to be invalidated as they contain useless/wrong data
+ RECT aSrcRect;
+ RECT aClipRect;
+ RECT aTempRect;
+ RECT aTempRect2;
+ HRGN hTempRgn;
+ HWND hWnd;
+
+ // restrict srcRect to this window (calc intersection)
+ aSrcRect.left = static_cast<int>(nSrcX);
+ aSrcRect.top = static_cast<int>(nSrcY);
+ aSrcRect.right = aSrcRect.left+static_cast<int>(nSrcWidth);
+ aSrcRect.bottom = aSrcRect.top+static_cast<int>(nSrcHeight);
+ GetClientRect( mrParent.gethWnd(), &aClipRect );
+ if ( IntersectRect( &aSrcRect, &aSrcRect, &aClipRect ) )
+ {
+ // transform srcRect to screen coordinates
+ POINT aPt;
+ aPt.x = 0;
+ aPt.y = 0;
+ ClientToScreen( mrParent.gethWnd(), &aPt );
+ aSrcRect.left += aPt.x;
+ aSrcRect.top += aPt.y;
+ aSrcRect.right += aPt.x;
+ aSrcRect.bottom += aPt.y;
+ hInvalidateRgn = nullptr;
+
+ // compute the parts that are off screen (ie invisible)
+ RECT theScreen;
+ ImplSalGetWorkArea( nullptr, &theScreen, nullptr ); // find the screen area taking multiple monitors into account
+ ImplCalcOutSideRgn( aSrcRect, theScreen.left, theScreen.top, theScreen.right, theScreen.bottom, hInvalidateRgn );
+
+ // calculate regions that are covered by other windows
+ HRGN hTempRgn2 = nullptr;
+ HWND hWndTopWindow = mrParent.gethWnd();
+ // Find the TopLevel Window, because only Windows which are in
+ // in the foreground of our TopLevel window must be considered
+ if ( GetWindowStyle( hWndTopWindow ) & WS_CHILD )
+ {
+ RECT aTempRect3 = aSrcRect;
+ do
+ {
+ hWndTopWindow = ::GetParent( hWndTopWindow );
+
+ // Test if the Parent clips our window
+ GetClientRect( hWndTopWindow, &aTempRect );
+ POINT aPt2;
+ aPt2.x = 0;
+ aPt2.y = 0;
+ ClientToScreen( hWndTopWindow, &aPt2 );
+ aTempRect.left += aPt2.x;
+ aTempRect.top += aPt2.y;
+ aTempRect.right += aPt2.x;
+ aTempRect.bottom += aPt2.y;
+ IntersectRect( &aTempRect3, &aTempRect3, &aTempRect );
+ }
+ while ( GetWindowStyle( hWndTopWindow ) & WS_CHILD );
+
+ // If one or more Parents clip our window, then we must
+ // calculate the outside area
+ if ( !EqualRect( &aSrcRect, &aTempRect3 ) )
+ {
+ ImplCalcOutSideRgn( aSrcRect,
+ aTempRect3.left, aTempRect3.top,
+ aTempRect3.right, aTempRect3.bottom,
+ hInvalidateRgn );
+ }
+ }
+ // retrieve the top-most (z-order) child window
+ hWnd = GetWindow( GetDesktopWindow(), GW_CHILD );
+ while ( hWnd )
+ {
+ if ( hWnd == hWndTopWindow )
+ break;
+ if ( IsWindowVisible( hWnd ) && !IsIconic( hWnd ) )
+ {
+ GetWindowRect( hWnd, &aTempRect );
+ if ( IntersectRect( &aTempRect2, &aSrcRect, &aTempRect ) )
+ {
+ // hWnd covers part or all of aSrcRect
+ if ( !hInvalidateRgn )
+ hInvalidateRgn = CreateRectRgnIndirect( &aSrcRect );
+
+ // get full bounding box of hWnd
+ hTempRgn = CreateRectRgnIndirect( &aTempRect );
+
+ // get region of hWnd (the window may be shaped)
+ if ( !hTempRgn2 )
+ hTempRgn2 = CreateRectRgn( 0, 0, 0, 0 );
+ int nRgnType = GetWindowRgn( hWnd, hTempRgn2 );
+ if ( (nRgnType != ERROR) && (nRgnType != NULLREGION) )
+ {
+ // convert window region to screen coordinates
+ OffsetRgn( hTempRgn2, aTempRect.left, aTempRect.top );
+ // and intersect with the window's bounding box
+ CombineRgn( hTempRgn, hTempRgn, hTempRgn2, RGN_AND );
+ }
+ // finally compute that part of aSrcRect which is not covered by any parts of hWnd
+ CombineRgn( hInvalidateRgn, hInvalidateRgn, hTempRgn, RGN_DIFF );
+ DeleteRegion( hTempRgn );
+ }
+ }
+ // retrieve the next window in the z-order, i.e. the window below hwnd
+ hWnd = GetWindow( hWnd, GW_HWNDNEXT );
+ }
+ if ( hTempRgn2 )
+ DeleteRegion( hTempRgn2 );
+ if ( hInvalidateRgn )
+ {
+ // hInvalidateRgn contains the fully visible parts of the original srcRect
+ hTempRgn = CreateRectRgnIndirect( &aSrcRect );
+ // subtract it from the original rect to get the occluded parts
+ int nRgnType = CombineRgn( hInvalidateRgn, hTempRgn, hInvalidateRgn, RGN_DIFF );
+ DeleteRegion( hTempRgn );
+
+ if ( (nRgnType != ERROR) && (nRgnType != NULLREGION) )
+ {
+ // move the occluded parts to the destination pos
+ int nOffX = static_cast<int>(nDestX-nSrcX);
+ int nOffY = static_cast<int>(nDestY-nSrcY);
+ OffsetRgn( hInvalidateRgn, nOffX-aPt.x, nOffY-aPt.y );
+
+ // by excluding hInvalidateRgn from the system's clip region
+ // we will prevent bitblt from copying useless data
+ // especially now shadows from overlapping windows will appear (#i36344)
+ hOldClipRgn = CreateRectRgn( 0, 0, 0, 0 );
+ nOldClipRgnType = GetClipRgn( mrParent.getHDC(), hOldClipRgn );
+
+ bRestoreClipRgn = true; // indicate changed clipregion and force invalidate
+ ExtSelectClipRgn( mrParent.getHDC(), hInvalidateRgn, RGN_DIFF );
+ }
+ }
+ }
+ }
+
+ BitBlt( mrParent.getHDC(),
+ static_cast<int>(nDestX), static_cast<int>(nDestY),
+ static_cast<int>(nSrcWidth), static_cast<int>(nSrcHeight),
+ mrParent.getHDC(),
+ static_cast<int>(nSrcX), static_cast<int>(nSrcY),
+ SRCCOPY );
+
+ if( bRestoreClipRgn )
+ {
+ // restore old clip region
+ if( nOldClipRgnType != ERROR )
+ SelectClipRgn( mrParent.getHDC(), hOldClipRgn);
+ DeleteRegion( hOldClipRgn );
+
+ // invalidate regions that were not copied
+ bool bInvalidate = true;
+
+ // Combine Invalidate vcl::Region with existing ClipRegion
+ HRGN hTempRgn = CreateRectRgn( 0, 0, 0, 0 );
+ if ( GetClipRgn( mrParent.getHDC(), hTempRgn ) == 1 )
+ {
+ int nRgnType = CombineRgn( hInvalidateRgn, hTempRgn, hInvalidateRgn, RGN_AND );
+ if ( (nRgnType == ERROR) || (nRgnType == NULLREGION) )
+ bInvalidate = false;
+ }
+ DeleteRegion( hTempRgn );
+
+ if ( bInvalidate )
+ {
+ InvalidateRgn( mrParent.gethWnd(), hInvalidateRgn, TRUE );
+ // here we only initiate an update if this is the MainThread,
+ // so that there is no deadlock when handling the Paint event,
+ // as the SolarMutex is already held by this Thread
+ SalData* pSalData = GetSalData();
+ DWORD nCurThreadId = GetCurrentThreadId();
+ if ( pSalData->mnAppThreadId == nCurThreadId )
+ UpdateWindow( mrParent.gethWnd() );
+ }
+
+ DeleteRegion( hInvalidateRgn );
+ }
+
+}
+
+namespace {
+
+void ImplDrawBitmap( HDC hDC, const SalTwoRect& rPosAry, const WinSalBitmap& rSalBitmap,
+ bool bPrinter, int nDrawMode )
+{
+ if( hDC )
+ {
+ HGLOBAL hDrawDIB;
+ HBITMAP hDrawDDB = rSalBitmap.ImplGethDDB();
+ std::unique_ptr<WinSalBitmap> xTmpSalBmp;
+ bool bPrintDDB = ( bPrinter && hDrawDDB );
+
+ if( bPrintDDB )
+ {
+ xTmpSalBmp.reset(new WinSalBitmap);
+ xTmpSalBmp->Create(rSalBitmap, vcl::bitDepthToPixelFormat(rSalBitmap.GetBitCount()));
+ hDrawDIB = xTmpSalBmp->ImplGethDIB();
+ }
+ else
+ hDrawDIB = rSalBitmap.ImplGethDIB();
+
+ if( hDrawDIB )
+ {
+ PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( hDrawDIB ));
+ PBYTE pBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize +
+ WinSalBitmap::ImplGetDIBColorCount( hDrawDIB ) * sizeof( RGBQUAD );
+ const int nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS );
+
+ StretchDIBits( hDC,
+ static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
+ static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
+ static_cast<int>(rPosAry.mnSrcX), static_cast<int>(pBI->bmiHeader.biHeight - rPosAry.mnSrcHeight - rPosAry.mnSrcY),
+ static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight),
+ pBits, pBI, DIB_RGB_COLORS, nDrawMode );
+
+ GlobalUnlock( hDrawDIB );
+ SetStretchBltMode( hDC, nOldStretchMode );
+ }
+ else if( hDrawDDB && !bPrintDDB )
+ {
+ ScopedCachedHDC<CACHED_HDC_DRAW> hBmpDC(hDrawDDB);
+
+ COLORREF nOldBkColor = RGB(0xFF,0xFF,0xFF);
+ COLORREF nOldTextColor = RGB(0,0,0);
+ bool bMono = ( rSalBitmap.GetBitCount() == 1 );
+
+ if( bMono )
+ {
+ COLORREF nBkColor = RGB( 0xFF, 0xFF, 0xFF );
+ COLORREF nTextColor = RGB( 0x00, 0x00, 0x00 );
+ //fdo#33455 handle 1 bit depth pngs with palette entries
+ //to set fore/back colors
+ if (BitmapBuffer* pBitmapBuffer = const_cast<WinSalBitmap&>(rSalBitmap).AcquireBuffer(BitmapAccessMode::Info))
+ {
+ const BitmapPalette& rPalette = pBitmapBuffer->maPalette;
+ if (rPalette.GetEntryCount() == 2)
+ {
+ Color nCol = rPalette[0];
+ nTextColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() );
+ nCol = rPalette[1];
+ nBkColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() );
+ }
+ const_cast<WinSalBitmap&>(rSalBitmap).ReleaseBuffer(pBitmapBuffer, BitmapAccessMode::Info);
+ }
+ nOldBkColor = SetBkColor( hDC, nBkColor );
+ nOldTextColor = ::SetTextColor( hDC, nTextColor );
+ }
+
+ if ( (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) &&
+ (rPosAry.mnSrcHeight == rPosAry.mnDestHeight) )
+ {
+ BitBlt( hDC,
+ static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
+ static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
+ hBmpDC.get(),
+ static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
+ nDrawMode );
+ }
+ else
+ {
+ const int nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS );
+
+ StretchBlt( hDC,
+ static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
+ static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
+ hBmpDC.get(),
+ static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
+ static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight),
+ nDrawMode );
+
+ SetStretchBltMode( hDC, nOldStretchMode );
+ }
+
+ if( bMono )
+ {
+ SetBkColor( hDC, nOldBkColor );
+ ::SetTextColor( hDC, nOldTextColor );
+ }
+ }
+ }
+}
+
+} // namespace
+
+void WinSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
+{
+ bool bTryDirectPaint(!mrParent.isPrinter() && !mbXORMode);
+
+ if(bTryDirectPaint)
+ {
+ // only paint direct when no scaling and no MapMode, else the
+ // more expensive conversions may be done for short-time Bitmap/BitmapEx
+ // used for buffering only
+ if(rPosAry.mnSrcWidth == rPosAry.mnDestWidth && rPosAry.mnSrcHeight == rPosAry.mnDestHeight)
+ {
+ bTryDirectPaint = false;
+ }
+ }
+
+ // try to draw using GdiPlus directly
+ if(bTryDirectPaint && TryDrawBitmapGDIPlus(rPosAry, rSalBitmap))
+ {
+ return;
+ }
+
+ // fall back old stuff
+ assert(dynamic_cast<const WinSalBitmap*>(&rSalBitmap));
+
+ ImplDrawBitmap(mrParent.getHDC(), rPosAry, static_cast<const WinSalBitmap&>(rSalBitmap),
+ mrParent.isPrinter(),
+ mbXORMode ? SRCINVERT : SRCCOPY );
+}
+
+void WinSalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSSalBitmap,
+ const SalBitmap& rSTransparentBitmap )
+{
+ SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No transparency print possible!" );
+ bool bTryDirectPaint(!mrParent.isPrinter() && !mbXORMode);
+
+ // try to draw using GdiPlus directly
+ if(bTryDirectPaint && drawAlphaBitmap(rPosAry, rSSalBitmap, rSTransparentBitmap))
+ {
+ return;
+ }
+
+ assert(dynamic_cast<const WinSalBitmap*>(&rSSalBitmap));
+ assert(dynamic_cast<const WinSalBitmap*>(&rSTransparentBitmap));
+
+ const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap);
+ const WinSalBitmap& rTransparentBitmap = static_cast<const WinSalBitmap&>(rSTransparentBitmap);
+
+ SalTwoRect aPosAry = rPosAry;
+ int nDstX = static_cast<int>(aPosAry.mnDestX);
+ int nDstY = static_cast<int>(aPosAry.mnDestY);
+ int nDstWidth = static_cast<int>(aPosAry.mnDestWidth);
+ int nDstHeight = static_cast<int>(aPosAry.mnDestHeight);
+ HDC hDC = mrParent.getHDC();
+
+ ScopedHBITMAP hMemBitmap;
+ ScopedHBITMAP hMaskBitmap;
+
+ if( ( nDstWidth > CACHED_HDC_DEFEXT ) || ( nDstHeight > CACHED_HDC_DEFEXT ) )
+ {
+ hMemBitmap.reset(CreateCompatibleBitmap(hDC, nDstWidth, nDstHeight));
+ hMaskBitmap.reset(CreateCompatibleBitmap(hDC, nDstWidth, nDstHeight));
+ }
+
+ ScopedCachedHDC<CACHED_HDC_1> hMemDC(hMemBitmap.get());
+ ScopedCachedHDC<CACHED_HDC_2> hMaskDC(hMaskBitmap.get());
+
+ aPosAry.mnDestX = aPosAry.mnDestY = 0;
+ BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hDC, nDstX, nDstY, SRCCOPY );
+
+ // WIN/WNT seems to have a minor problem mapping the correct color of the
+ // mask to the palette if we draw the DIB directly ==> draw DDB
+ if( ( GetBitCount() <= 8 ) && rTransparentBitmap.ImplGethDIB() && rTransparentBitmap.GetBitCount() == 1 )
+ {
+ WinSalBitmap aTmp;
+
+ if( aTmp.Create( rTransparentBitmap, &mrParent ) )
+ ImplDrawBitmap( hMaskDC.get(), aPosAry, aTmp, false, SRCCOPY );
+ }
+ else
+ ImplDrawBitmap( hMaskDC.get(), aPosAry, rTransparentBitmap, false, SRCCOPY );
+
+ // now MemDC contains background, MaskDC the transparency mask
+
+ // #105055# Respect XOR mode
+ if( mbXORMode )
+ {
+ ImplDrawBitmap( hMaskDC.get(), aPosAry, rSalBitmap, false, SRCERASE );
+ // now MaskDC contains the bitmap area with black background
+ BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCINVERT );
+ // now MemDC contains background XORed bitmap area on top
+ }
+ else
+ {
+ BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCAND );
+ // now MemDC contains background with masked-out bitmap area
+ ImplDrawBitmap( hMaskDC.get(), aPosAry, rSalBitmap, false, SRCERASE );
+ // now MaskDC contains the bitmap area with black background
+ BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCPAINT );
+ // now MemDC contains background and bitmap merged together
+ }
+ // copy to output DC
+ BitBlt( hDC, nDstX, nDstY, nDstWidth, nDstHeight, hMemDC.get(), 0, 0, SRCCOPY );
+}
+
+bool WinSalGraphicsImpl::drawAlphaRect( tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, sal_uInt8 nTransparency )
+{
+ if( mbPen || !mbBrush || mbXORMode )
+ return false; // can only perform solid fills without XOR.
+
+ ScopedCachedHDC<CACHED_HDC_1> hMemDC(nullptr);
+ SetPixel( hMemDC.get(), int(0), int(0), mnBrushColor );
+
+ BLENDFUNCTION aFunc = {
+ AC_SRC_OVER,
+ 0,
+ sal::static_int_cast<sal_uInt8>(255 - 255L*nTransparency/100),
+ 0
+ };
+
+ // hMemDC contains a 1x1 bitmap of the right color - stretch-blit
+ // that to dest hdc
+ bool bRet = GdiAlphaBlend(mrParent.getHDC(), nX, nY, nWidth, nHeight,
+ hMemDC.get(), 0,0,1,1,
+ aFunc ) == TRUE;
+
+ return bRet;
+}
+
+void WinSalGraphicsImpl::drawMask(const SalTwoRect& rPosAry,
+ const SalBitmap& rSSalBitmap,
+ Color nMaskColor)
+{
+ SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No transparency print possible!" );
+
+ assert(dynamic_cast<const WinSalBitmap*>(&rSSalBitmap));
+
+ const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap);
+
+ SalTwoRect aPosAry = rPosAry;
+ const HDC hDC = mrParent.getHDC();
+
+ ScopedSelectedHBRUSH hBrush(hDC, CreateSolidBrush(RGB(nMaskColor.GetRed(),
+ nMaskColor.GetGreen(),
+ nMaskColor.GetBlue())));
+
+ // WIN/WNT seems to have a minor problem mapping the correct color of the
+ // mask to the palette if we draw the DIB directly ==> draw DDB
+ if( ( GetBitCount() <= 8 ) && rSalBitmap.ImplGethDIB() && rSalBitmap.GetBitCount() == 1 )
+ {
+ WinSalBitmap aTmp;
+
+ if( aTmp.Create( rSalBitmap, &mrParent ) )
+ ImplDrawBitmap( hDC, aPosAry, aTmp, false, 0x00B8074AUL );
+ }
+ else
+ ImplDrawBitmap( hDC, aPosAry, rSalBitmap, false, 0x00B8074AUL );
+}
+
+std::shared_ptr<SalBitmap> WinSalGraphicsImpl::getBitmap( tools::Long nX, tools::Long nY, tools::Long nDX, tools::Long nDY )
+{
+ SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No ::GetBitmap() from printer possible!" );
+
+ std::shared_ptr<WinSalBitmap> pSalBitmap;
+
+ nDX = std::abs( nDX );
+ nDY = std::abs( nDY );
+
+ HDC hDC = mrParent.getHDC();
+ HBITMAP hBmpBitmap = CreateCompatibleBitmap( hDC, nDX, nDY );
+ bool bRet;
+
+ {
+ ScopedCachedHDC<CACHED_HDC_1> hBmpDC(hBmpBitmap);
+
+ bRet = BitBlt(hBmpDC.get(), 0, 0,
+ static_cast<int>(nDX), static_cast<int>(nDY), hDC,
+ static_cast<int>(nX), static_cast<int>(nY), SRCCOPY) ? TRUE : FALSE;
+ }
+
+ if( bRet )
+ {
+ pSalBitmap = std::make_shared<WinSalBitmap>();
+
+ if( !pSalBitmap->Create( hBmpBitmap ) )
+ {
+ pSalBitmap.reset();
+ }
+ }
+ else
+ {
+ // #124826# avoid resource leak! Happens when running without desktop access (remote desktop, service, may be screensavers)
+ DeleteBitmap( hBmpBitmap );
+ }
+
+ return pSalBitmap;
+}
+
+Color WinSalGraphicsImpl::getPixel( tools::Long nX, tools::Long nY )
+{
+ COLORREF aWinCol = ::GetPixel( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY) );
+
+ if ( CLR_INVALID == aWinCol )
+ return Color( 0, 0, 0 );
+ else
+ return Color( GetRValue( aWinCol ),
+ GetGValue( aWinCol ),
+ GetBValue( aWinCol ) );
+}
+
+namespace
+{
+
+HBRUSH Get50PercentBrush()
+{
+ SalData* pSalData = GetSalData();
+ if ( !pSalData->mh50Brush )
+ {
+ if ( !pSalData->mh50Bmp )
+ pSalData->mh50Bmp = ImplLoadSalBitmap( SAL_RESID_BITMAP_50 );
+ pSalData->mh50Brush = CreatePatternBrush( pSalData->mh50Bmp );
+ }
+
+ return pSalData->mh50Brush;
+}
+
+} // namespace
+
+void WinSalGraphicsImpl::invert( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, SalInvert nFlags )
+{
+ if ( nFlags & SalInvert::TrackFrame )
+ {
+ HPEN hDotPen = CreatePen( PS_DOT, 0, 0 );
+ HPEN hOldPen = SelectPen( mrParent.getHDC(), hDotPen );
+ HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), GetStockBrush( NULL_BRUSH ) );
+ int nOldROP = SetROP2( mrParent.getHDC(), R2_NOT );
+
+ Rectangle( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nX+nWidth), static_cast<int>(nY+nHeight) );
+
+ SetROP2( mrParent.getHDC(), nOldROP );
+ SelectPen( mrParent.getHDC(), hOldPen );
+ SelectBrush( mrParent.getHDC(), hOldBrush );
+ DeletePen( hDotPen );
+ }
+ else if ( nFlags & SalInvert::N50 )
+ {
+ COLORREF nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 );
+ HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), Get50PercentBrush() );
+ PatBlt( mrParent.getHDC(), nX, nY, nWidth, nHeight, PATINVERT );
+ ::SetTextColor( mrParent.getHDC(), nOldTextColor );
+ SelectBrush( mrParent.getHDC(), hOldBrush );
+ }
+ else
+ {
+ RECT aRect;
+ aRect.left = static_cast<int>(nX);
+ aRect.top = static_cast<int>(nY);
+ aRect.right = static_cast<int>(nX)+nWidth;
+ aRect.bottom = static_cast<int>(nY)+nHeight;
+ ::InvertRect( mrParent.getHDC(), &aRect );
+ }
+}
+
+void WinSalGraphicsImpl::invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nSalFlags )
+{
+ HPEN hPen;
+ HPEN hOldPen;
+ HBRUSH hBrush;
+ HBRUSH hOldBrush = nullptr;
+ COLORREF nOldTextColor RGB(0,0,0);
+ int nOldROP = SetROP2( mrParent.getHDC(), R2_NOT );
+
+ if ( nSalFlags & SalInvert::TrackFrame )
+ hPen = CreatePen( PS_DOT, 0, 0 );
+ else
+ {
+
+ if ( nSalFlags & SalInvert::N50 )
+ hBrush = Get50PercentBrush();
+ else
+ hBrush = GetStockBrush( BLACK_BRUSH );
+
+ hPen = GetStockPen( NULL_PEN );
+ nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 );
+ hOldBrush = SelectBrush( mrParent.getHDC(), hBrush );
+ }
+ hOldPen = SelectPen( mrParent.getHDC(), hPen );
+
+ std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]);
+ for (sal_uInt32 i=0; i<nPoints; ++i)
+ pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) };
+
+ // for Windows 95 and its maximum number of points
+ if ( nSalFlags & SalInvert::TrackFrame )
+ {
+ if ( !Polyline( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
+ Polyline( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS );
+ }
+ else
+ {
+ if ( !Polygon( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
+ Polygon( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS );
+ }
+
+ SetROP2( mrParent.getHDC(), nOldROP );
+ SelectPen( mrParent.getHDC(), hOldPen );
+
+ if ( nSalFlags & SalInvert::TrackFrame )
+ DeletePen( hPen );
+ else
+ {
+ ::SetTextColor( mrParent.getHDC(), nOldTextColor );
+ SelectBrush( mrParent.getHDC(), hOldBrush );
+ }
+}
+
+sal_uInt16 WinSalGraphicsImpl::GetBitCount() const
+{
+ return static_cast<sal_uInt16>(GetDeviceCaps( mrParent.getHDC(), BITSPIXEL ));
+}
+
+tools::Long WinSalGraphicsImpl::GetGraphicsWidth() const
+{
+ if( mrParent.gethWnd() && IsWindow( mrParent.gethWnd() ) )
+ {
+ WinSalFrame* pFrame = GetWindowPtr( mrParent.gethWnd() );
+ if( pFrame )
+ {
+ if (pFrame->maGeometry.width())
+ return pFrame->maGeometry.width();
+ else
+ {
+ // TODO: perhaps not needed, maGeometry should always be up-to-date
+ RECT aRect;
+ GetClientRect( mrParent.gethWnd(), &aRect );
+ return aRect.right;
+ }
+ }
+ }
+
+ return 0;
+}
+
+void WinSalGraphicsImpl::ResetClipRegion()
+{
+ if ( mrParent.mhRegion )
+ {
+ DeleteRegion( mrParent.mhRegion );
+ mrParent.mhRegion = nullptr;
+ }
+
+ SelectClipRgn( mrParent.getHDC(), nullptr );
+}
+
+static bool containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolygon& rCandidate)
+{
+ if(rCandidate.areControlPointsUsed())
+ {
+ return false;
+ }
+
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount < 2)
+ {
+ return true;
+ }
+
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount + 1 : nPointCount);
+ basegfx::B2DPoint aLast(rCandidate.getB2DPoint(0));
+
+ for(sal_uInt32 a(1); a < nEdgeCount; a++)
+ {
+ const sal_uInt32 nNextIndex(a % nPointCount);
+ const basegfx::B2DPoint aCurrent(rCandidate.getB2DPoint(nNextIndex));
+
+ if(!basegfx::fTools::equal(aLast.getX(), aCurrent.getX()) && !basegfx::fTools::equal(aLast.getY(), aCurrent.getY()))
+ {
+ return false;
+ }
+
+ aLast = aCurrent;
+ }
+
+ return true;
+}
+
+static bool containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolyPolygon& rCandidate)
+{
+ if(rCandidate.areControlPointsUsed())
+ {
+ return false;
+ }
+
+ for(auto const& rPolygon : rCandidate)
+ {
+ if(!containsOnlyHorizontalAndVerticalEdges(rPolygon))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void WinSalGraphicsImpl::setClipRegion( const vcl::Region& i_rClip )
+{
+ if ( mrParent.mhRegion )
+ {
+ DeleteRegion( mrParent.mhRegion );
+ mrParent.mhRegion = nullptr;
+ }
+
+ bool bUsePolygon(i_rClip.HasPolyPolygonOrB2DPolyPolygon());
+ static bool bTryToAvoidPolygon(true);
+
+ // #i122149# try to avoid usage of tools::PolyPolygon ClipRegions when tools::PolyPolygon is no curve
+ // and only contains horizontal/vertical edges. In that case, use the fallback
+ // in GetRegionRectangles which will use vcl::Region::GetAsRegionBand() which will do
+ // the correct polygon-to-RegionBand transformation.
+ // Background is that when using the same Rectangle as rectangle or as Polygon
+ // clip region will lead to different results; the polygon-based one will be
+ // one pixel less to the right and down (see GDI docu for CreatePolygonRgn). This
+ // again is because of the polygon-nature and it's classic handling when filling.
+ // This also means that all cases which use a 'true' polygon-based incarnation of
+ // a vcl::Region should know what they do - it may lead to repaint errors.
+ if(bUsePolygon && bTryToAvoidPolygon)
+ {
+ const basegfx::B2DPolyPolygon aPolyPolygon( i_rClip.GetAsB2DPolyPolygon() );
+
+ if(!aPolyPolygon.areControlPointsUsed())
+ {
+ if(containsOnlyHorizontalAndVerticalEdges(aPolyPolygon))
+ {
+ bUsePolygon = false;
+ }
+ }
+ }
+
+ if(bUsePolygon)
+ {
+ // #i122149# check the comment above to know that this may lead to potential repaint
+ // problems. It may be solved (if needed) by scaling the polygon by one in X
+ // and Y. Currently the workaround to only use it if really unavoidable will
+ // solve most cases. When someone is really using polygon-based Regions he
+ // should know what he is doing.
+ // Added code to do that scaling to check if it works, testing it.
+ const basegfx::B2DPolyPolygon aPolyPolygon( i_rClip.GetAsB2DPolyPolygon() );
+ const sal_uInt32 nCount(aPolyPolygon.count());
+
+ if( nCount )
+ {
+ std::vector< POINT > aPolyPoints;
+ aPolyPoints.reserve( 1024 );
+ std::vector< INT > aPolyCounts( nCount, 0 );
+ basegfx::B2DHomMatrix aExpand;
+ sal_uInt32 nTargetCount(0);
+ static bool bExpandByOneInXandY(true);
+
+ if(bExpandByOneInXandY)
+ {
+ const basegfx::B2DRange aRangeS(aPolyPolygon.getB2DRange());
+ const basegfx::B2DRange aRangeT(aRangeS.getMinimum(), aRangeS.getMaximum() + basegfx::B2DTuple(1.0, 1.0));
+ aExpand = basegfx::utils::createSourceRangeTargetRangeTransform(aRangeS, aRangeT);
+ }
+
+ for(auto const& rPolygon : aPolyPolygon)
+ {
+ const basegfx::B2DPolygon aPoly(
+ basegfx::utils::adaptiveSubdivideByDistance(
+ rPolygon,
+ 1));
+ const sal_uInt32 nPoints(aPoly.count());
+
+ // tdf#40863 For CustomShapes there is a hack (see
+ // f64ef72743e55389e446e0d4bc6febd475011023) that adds polygons
+ // with a single point in top-left and bottom-right corner
+ // of the BoundRect to be able to determine the correct BoundRect
+ // in the slideshow. Unfortunately, CreatePolyPolygonRgn below
+ // fails with polygons containing a single pixel, so clipping is
+ // lost. For now, use only polygons with more than two points - the
+ // ones that may have an area.
+ // Note: polygons with one point which are curves may have an area,
+ // but the polygon is already subdivided here, so no need to test
+ // this.
+ if(nPoints > 2)
+ {
+ aPolyCounts[nTargetCount] = nPoints;
+ nTargetCount++;
+
+ for( sal_uInt32 b = 0; b < nPoints; b++ )
+ {
+ basegfx::B2DPoint aPt(aPoly.getB2DPoint(b));
+
+ if(bExpandByOneInXandY)
+ {
+ aPt = aExpand * aPt;
+ }
+
+ POINT aPOINT;
+ // #i122149# do correct rounding
+ aPOINT.x = basegfx::fround(aPt.getX());
+ aPOINT.y = basegfx::fround(aPt.getY());
+ aPolyPoints.push_back( aPOINT );
+ }
+ }
+ }
+
+ if(nTargetCount)
+ {
+ mrParent.mhRegion = CreatePolyPolygonRgn( aPolyPoints.data(), aPolyCounts.data(), nTargetCount, ALTERNATE );
+ }
+ }
+ }
+ else
+ {
+ RectangleVector aRectangles;
+ i_rClip.GetRegionRectangles(aRectangles);
+
+ sal_uLong nRectBufSize = sizeof(RECT)*aRectangles.size();
+ if ( aRectangles.size() < SAL_CLIPRECT_COUNT )
+ {
+ if ( !mrParent.mpStdClipRgnData )
+ mrParent.mpStdClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+(SAL_CLIPRECT_COUNT*sizeof(RECT))]);
+ mrParent.mpClipRgnData = mrParent.mpStdClipRgnData;
+ }
+ else
+ mrParent.mpClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+nRectBufSize]);
+ mrParent.mpClipRgnData->rdh.dwSize = sizeof( RGNDATAHEADER );
+ mrParent.mpClipRgnData->rdh.iType = RDH_RECTANGLES;
+ mrParent.mpClipRgnData->rdh.nCount = aRectangles.size();
+ mrParent.mpClipRgnData->rdh.nRgnSize = nRectBufSize;
+ RECT* pBoundRect = &(mrParent.mpClipRgnData->rdh.rcBound);
+ SetRectEmpty( pBoundRect );
+ RECT* pNextClipRect = reinterpret_cast<RECT*>(&(mrParent.mpClipRgnData->Buffer));
+ bool bFirstClipRect = true;
+
+ for (auto const& rectangle : aRectangles)
+ {
+ const tools::Long nW(rectangle.GetWidth());
+ const tools::Long nH(rectangle.GetHeight());
+
+ if(nW && nH)
+ {
+ const tools::Long nRight(rectangle.Left() + nW);
+ const tools::Long nBottom(rectangle.Top() + nH);
+
+ if(bFirstClipRect)
+ {
+ pBoundRect->left = rectangle.Left();
+ pBoundRect->top = rectangle.Top();
+ pBoundRect->right = nRight;
+ pBoundRect->bottom = nBottom;
+ bFirstClipRect = false;
+ }
+ else
+ {
+ if(rectangle.Left() < pBoundRect->left)
+ {
+ pBoundRect->left = static_cast<int>(rectangle.Left());
+ }
+
+ if(rectangle.Top() < pBoundRect->top)
+ {
+ pBoundRect->top = static_cast<int>(rectangle.Top());
+ }
+
+ if(nRight > pBoundRect->right)
+ {
+ pBoundRect->right = static_cast<int>(nRight);
+ }
+
+ if(nBottom > pBoundRect->bottom)
+ {
+ pBoundRect->bottom = static_cast<int>(nBottom);
+ }
+ }
+
+ pNextClipRect->left = static_cast<int>(rectangle.Left());
+ pNextClipRect->top = static_cast<int>(rectangle.Top());
+ pNextClipRect->right = static_cast<int>(nRight);
+ pNextClipRect->bottom = static_cast<int>(nBottom);
+ pNextClipRect++;
+ }
+ else
+ {
+ mrParent.mpClipRgnData->rdh.nCount--;
+ mrParent.mpClipRgnData->rdh.nRgnSize -= sizeof( RECT );
+ }
+ }
+
+ // create clip region from ClipRgnData
+ if(0 == mrParent.mpClipRgnData->rdh.nCount)
+ {
+ // #i123585# region is empty; this may happen when e.g. a tools::PolyPolygon is given
+ // that contains no polygons or only empty ones (no width/height). This is
+ // perfectly fine and we are done, except setting it (see end of method)
+ }
+ else if(1 == mrParent.mpClipRgnData->rdh.nCount)
+ {
+ RECT* pRect = &(mrParent.mpClipRgnData->rdh.rcBound);
+ mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top,
+ pRect->right, pRect->bottom );
+ }
+ else if(mrParent.mpClipRgnData->rdh.nCount > 1)
+ {
+ sal_uLong nSize = mrParent.mpClipRgnData->rdh.nRgnSize+sizeof(RGNDATAHEADER);
+ mrParent.mhRegion = ExtCreateRegion( nullptr, nSize, mrParent.mpClipRgnData );
+
+ // if ExtCreateRegion(...) is not supported
+ if( !mrParent.mhRegion )
+ {
+ RGNDATAHEADER const & pHeader = mrParent.mpClipRgnData->rdh;
+
+ if( pHeader.nCount )
+ {
+ RECT* pRect = reinterpret_cast<RECT*>(mrParent.mpClipRgnData->Buffer);
+ mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top, pRect->right, pRect->bottom );
+ pRect++;
+
+ for( sal_uLong n = 1; n < pHeader.nCount; n++, pRect++ )
+ {
+ ScopedHRGN hRgn(CreateRectRgn(pRect->left, pRect->top, pRect->right, pRect->bottom));
+ CombineRgn( mrParent.mhRegion, mrParent.mhRegion, hRgn.get(), RGN_OR );
+ }
+ }
+ }
+
+ if ( mrParent.mpClipRgnData != mrParent.mpStdClipRgnData )
+ delete [] reinterpret_cast<BYTE*>(mrParent.mpClipRgnData);
+ }
+ }
+
+ if( mrParent.mhRegion )
+ {
+ SelectClipRgn( mrParent.getHDC(), mrParent.mhRegion );
+
+ // debug code if you want to check range of the newly applied ClipRegion
+ //RECT aBound;
+ //const int aRegionType = GetRgnBox(mrParent.mhRegion, &aBound);
+
+ //bool bBla = true;
+ }
+ else
+ {
+ // #i123585# See above, this is a valid case, execute it
+ SelectClipRgn( mrParent.getHDC(), nullptr );
+ }
+}
+
+void WinSalGraphicsImpl::SetLineColor()
+{
+ ResetPen(GetStockPen(NULL_PEN));
+
+ // set new data
+ mbPen = false;
+ mbStockPen = true;
+}
+
+void WinSalGraphicsImpl::SetLineColor(Color nColor)
+{
+ COLORREF nPenColor = PALETTERGB(nColor.GetRed(),
+ nColor.GetGreen(),
+ nColor.GetBlue());
+ bool bStockPen = false;
+
+ HPEN hNewPen = SearchStockPen(nPenColor);
+ if (hNewPen)
+ bStockPen = true;
+ else
+ hNewPen = MakePen(nColor);
+
+ ResetPen(hNewPen);
+
+ // set new data
+ mnPenColor = nPenColor;
+ maLineColor = nColor;
+ mbPen = true;
+ mbStockPen = bStockPen;
+}
+
+HPEN WinSalGraphicsImpl::SearchStockPen(COLORREF nPenColor)
+{
+ // Only screen, because printer has problems, when we use stock objects.
+ if (!mrParent.isPrinter())
+ {
+ const SalData* pSalData = GetSalData();
+
+ for (sal_uInt16 i = 0; i < pSalData->mnStockPenCount; i++)
+ {
+ if (nPenColor == pSalData->maStockPenColorAry[i])
+ return pSalData->mhStockPenAry[i];
+ }
+ }
+
+ return nullptr;
+}
+
+HPEN WinSalGraphicsImpl::MakePen(Color nColor)
+{
+ COLORREF nPenColor = PALETTERGB(nColor.GetRed(),
+ nColor.GetGreen(),
+ nColor.GetBlue());
+
+ if (!mrParent.isPrinter())
+ {
+ if (GetSalData()->mhDitherPal && ImplIsSysColorEntry(nColor))
+ {
+ nPenColor = PALRGB_TO_RGB(nPenColor);
+ }
+ }
+
+ return CreatePen(PS_SOLID, mrParent.mnPenWidth, nPenColor);
+}
+
+void WinSalGraphicsImpl::ResetPen(HPEN hNewPen)
+{
+ HPEN hOldPen = SelectPen(mrParent.getHDC(), hNewPen);
+
+ if (mhPen)
+ {
+ if (!mbStockPen)
+ {
+ DeletePen(mhPen);
+ }
+ }
+ else
+ {
+ mrParent.mhDefPen = hOldPen;
+ }
+
+ mhPen = hNewPen;
+}
+
+void WinSalGraphicsImpl::SetFillColor()
+{
+ ResetBrush(GetStockBrush(NULL_BRUSH));
+
+ // set new data
+ mbBrush = false;
+ mbStockBrush = true;
+}
+
+void WinSalGraphicsImpl::SetFillColor(Color nColor)
+{
+ COLORREF nBrushColor = PALETTERGB(nColor.GetRed(),
+ nColor.GetGreen(),
+ nColor.GetBlue());
+ bool bStockBrush = false;
+
+ HBRUSH hNewBrush = SearchStockBrush(nBrushColor);
+ if (hNewBrush)
+ bStockBrush = true;
+ else
+ hNewBrush = MakeBrush(nColor);
+
+ ResetBrush(hNewBrush);
+
+ // set new data
+ mnBrushColor = nBrushColor;
+ maFillColor = nColor;
+ mbBrush = true;
+ mbStockBrush = bStockBrush;
+}
+
+HBRUSH WinSalGraphicsImpl::SearchStockBrush(COLORREF nBrushColor)
+{
+ // Only screen, because printer has problems, when we use stock objects.
+ if (!mrParent.isPrinter())
+ {
+ const SalData* pSalData = GetSalData();
+
+ for (sal_uInt16 i = 0; i < pSalData->mnStockBrushCount; i++)
+ {
+ if (nBrushColor == pSalData->maStockBrushColorAry[i])
+ return pSalData->mhStockBrushAry[i];
+ }
+ }
+
+ return nullptr;
+}
+
+namespace
+{
+
+BYTE GetDitherMappingValue(BYTE nVal, BYTE nThres, const SalData* pSalData)
+{
+ return (pSalData->mpDitherDiff[nVal] > nThres) ?
+ pSalData->mpDitherHigh[nVal] : pSalData->mpDitherLow[nVal];
+}
+
+HBRUSH Make16BitDIBPatternBrush(Color nColor)
+{
+ const SalData* pSalData = GetSalData();
+
+ const BYTE nRed = nColor.GetRed();
+ const BYTE nGreen = nColor.GetGreen();
+ const BYTE nBlue = nColor.GetBlue();
+
+ static const BYTE aOrdDither16Bit[8][8] =
+ {
+ { 0, 6, 1, 7, 0, 6, 1, 7 },
+ { 4, 2, 5, 3, 4, 2, 5, 3 },
+ { 1, 7, 0, 6, 1, 7, 0, 6 },
+ { 5, 3, 4, 2, 5, 3, 4, 2 },
+ { 0, 6, 1, 7, 0, 6, 1, 7 },
+ { 4, 2, 5, 3, 4, 2, 5, 3 },
+ { 1, 7, 0, 6, 1, 7, 0, 6 },
+ { 5, 3, 4, 2, 5, 3, 4, 2 }
+ };
+
+ BYTE* pTmp = pSalData->mpDitherDIBData;
+
+ for(int nY = 0; nY < 8; ++nY)
+ {
+ for(int nX = 0; nX < 8; ++nX)
+ {
+ const BYTE nThres = aOrdDither16Bit[nY][nX];
+ *pTmp++ = GetDitherMappingValue(nBlue, nThres, pSalData);
+ *pTmp++ = GetDitherMappingValue(nGreen, nThres, pSalData);
+ *pTmp++ = GetDitherMappingValue(nRed, nThres, pSalData);
+ }
+ }
+
+ return CreateDIBPatternBrush(pSalData->mhDitherDIB, DIB_RGB_COLORS);
+}
+
+HBRUSH Make8BitDIBPatternBrush(Color nColor)
+{
+ const SalData* pSalData = GetSalData();
+
+ const BYTE nRed = nColor.GetRed();
+ const BYTE nGreen = nColor.GetGreen();
+ const BYTE nBlue = nColor.GetBlue();
+
+ static const BYTE aOrdDither8Bit[8][8] =
+ {
+ { 0, 38, 9, 48, 2, 40, 12, 50 },
+ { 25, 12, 35, 22, 28, 15, 37, 24 },
+ { 6, 44, 3, 41, 8, 47, 5, 44 },
+ { 32, 19, 28, 16, 34, 21, 31, 18 },
+ { 1, 40, 11, 49, 0, 39, 10, 48 },
+ { 27, 14, 36, 24, 26, 13, 36, 23 },
+ { 8, 46, 4, 43, 7, 45, 4, 42 },
+ { 33, 20, 30, 17, 32, 20, 29, 16 }
+ };
+
+ BYTE* pTmp = pSalData->mpDitherDIBData;
+
+ for (int nY = 0; nY < 8; ++nY)
+ {
+ for (int nX = 0; nX < 8; ++nX)
+ {
+ const BYTE nThres = aOrdDither8Bit[nY][nX];
+ *pTmp = GetDitherMappingValue(nRed, nThres, pSalData) +
+ GetDitherMappingValue(nGreen, nThres, pSalData) * 6 +
+ GetDitherMappingValue(nBlue, nThres, pSalData) * 36;
+ pTmp++;
+ }
+ }
+
+ return CreateDIBPatternBrush(pSalData->mhDitherDIB, DIB_PAL_COLORS);
+}
+
+} // namespace
+
+HBRUSH WinSalGraphicsImpl::MakeBrush(Color nColor)
+{
+ const SalData* pSalData = GetSalData();
+
+ const BYTE nRed = nColor.GetRed();
+ const BYTE nGreen = nColor.GetGreen();
+ const BYTE nBlue = nColor.GetBlue();
+ const COLORREF nBrushColor = PALETTERGB(nRed, nGreen, nBlue);
+
+ if (mrParent.isPrinter() || !pSalData->mhDitherDIB)
+ return CreateSolidBrush(nBrushColor);
+
+ if (24 == reinterpret_cast<BITMAPINFOHEADER*>(pSalData->mpDitherDIB)->biBitCount)
+ return Make16BitDIBPatternBrush(nColor);
+
+ if (ImplIsSysColorEntry(nColor))
+ return CreateSolidBrush(PALRGB_TO_RGB(nBrushColor));
+
+ if (ImplIsPaletteEntry(nRed, nGreen, nBlue))
+ return CreateSolidBrush(nBrushColor);
+
+ return Make8BitDIBPatternBrush(nColor);
+}
+
+void WinSalGraphicsImpl::ResetBrush(HBRUSH hNewBrush)
+{
+ HBRUSH hOldBrush = SelectBrush(mrParent.getHDC(), hNewBrush);
+
+ if (mhBrush)
+ {
+ if (!mbStockBrush)
+ {
+ DeleteBrush(mhBrush);
+ }
+ }
+ else
+ {
+ mrParent.mhDefBrush = hOldBrush;
+ }
+
+ mhBrush = hNewBrush;
+}
+
+void WinSalGraphicsImpl::SetXORMode( bool bSet, bool )
+{
+ mbXORMode = bSet;
+ ::SetROP2( mrParent.getHDC(), bSet ? R2_XORPEN : R2_COPYPEN );
+}
+
+void WinSalGraphicsImpl::SetROPLineColor( SalROPColor nROPColor )
+{
+ SetLineColor( ImplGetROPColor( nROPColor ) );
+}
+
+void WinSalGraphicsImpl::SetROPFillColor( SalROPColor nROPColor )
+{
+ SetFillColor( ImplGetROPColor( nROPColor ) );
+}
+
+void WinSalGraphicsImpl::DrawPixelImpl( tools::Long nX, tools::Long nY, COLORREF crColor )
+{
+ const HDC hDC = mrParent.getHDC();
+
+ if (!mbXORMode)
+ {
+ SetPixel(hDC, static_cast<int>(nX), static_cast<int>(nY), crColor);
+ return;
+ }
+
+ ScopedSelectedHBRUSH hBrush(hDC, CreateSolidBrush(crColor));
+ PatBlt(hDC, static_cast<int>(nX), static_cast<int>(nY), int(1), int(1), PATINVERT);
+}
+
+void WinSalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY )
+{
+ DrawPixelImpl( nX, nY, mnPenColor );
+}
+
+void WinSalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY, Color nColor )
+{
+ COLORREF nCol = PALETTERGB( nColor.GetRed(),
+ nColor.GetGreen(),
+ nColor.GetBlue() );
+
+ if ( !mrParent.isPrinter() &&
+ GetSalData()->mhDitherPal &&
+ ImplIsSysColorEntry( nColor ) )
+ nCol = PALRGB_TO_RGB( nCol );
+
+ DrawPixelImpl( nX, nY, nCol );
+}
+
+void WinSalGraphicsImpl::drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 )
+{
+ MoveToEx( mrParent.getHDC(), static_cast<int>(nX1), static_cast<int>(nY1), nullptr );
+
+ LineTo( mrParent.getHDC(), static_cast<int>(nX2), static_cast<int>(nY2) );
+
+ // LineTo doesn't draw the last pixel
+ if ( !mrParent.isPrinter() )
+ DrawPixelImpl( nX2, nY2, mnPenColor );
+}
+
+void WinSalGraphicsImpl::drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ if ( !mbPen )
+ {
+ if ( !mrParent.isPrinter() )
+ {
+ PatBlt( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nWidth), static_cast<int>(nHeight),
+ mbXORMode ? PATINVERT : PATCOPY );
+ }
+ else
+ {
+ RECT aWinRect;
+ aWinRect.left = nX;
+ aWinRect.top = nY;
+ aWinRect.right = nX+nWidth;
+ aWinRect.bottom = nY+nHeight;
+ ::FillRect( mrParent.getHDC(), &aWinRect, mhBrush );
+ }
+ }
+ else
+ Rectangle( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nX+nWidth), static_cast<int>(nY+nHeight) );
+}
+
+void WinSalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry )
+{
+ std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]);
+ for (sal_uInt32 i=0; i<nPoints; ++i)
+ pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) };
+
+ // for Windows 95 and its maximum number of points
+ if ( !Polyline( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
+ Polyline( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS );
+
+ // Polyline seems to uses LineTo, which doesn't paint the last pixel (see 87eb8f8ee)
+ if ( !mrParent.isPrinter() )
+ DrawPixelImpl( pWinPtAry[nPoints-1].x, pWinPtAry[nPoints-1].y, mnPenColor );
+}
+
+void WinSalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const Point* pPtAry )
+{
+ std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]);
+ for (sal_uInt32 i=0; i<nPoints; ++i)
+ pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) };
+
+ // for Windows 95 and its maximum number of points
+ if ( !Polygon( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) )
+ Polygon( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS );
+}
+
+void WinSalGraphicsImpl::drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point** pPtAry )
+{
+ UINT aWinPointAry[SAL_POLYPOLYCOUNT_STACKBUF];
+ UINT* pWinPointAry;
+ UINT nPolyPolyPoints = 0;
+ UINT nPoints;
+ UINT i;
+
+ if ( nPoly <= SAL_POLYPOLYCOUNT_STACKBUF )
+ pWinPointAry = aWinPointAry;
+ else
+ pWinPointAry = new UINT[nPoly];
+
+ for ( i = 0; i < static_cast<UINT>(nPoly); i++ )
+ {
+ nPoints = static_cast<UINT>(pPoints[i])+1;
+ pWinPointAry[i] = nPoints;
+ nPolyPolyPoints += nPoints;
+ }
+
+ POINT aWinPointAryAry[SAL_POLYPOLYPOINTS_STACKBUF];
+ POINT* pWinPointAryAry;
+ if ( nPolyPolyPoints <= SAL_POLYPOLYPOINTS_STACKBUF )
+ pWinPointAryAry = aWinPointAryAry;
+ else
+ pWinPointAryAry = new POINT[nPolyPolyPoints];
+ UINT n = 0;
+ for ( i = 0; i < static_cast<UINT>(nPoly); i++ )
+ {
+ nPoints = pWinPointAry[i];
+ const Point* pPolyAry = pPtAry[i];
+ for (sal_uInt32 j=0; j<nPoints-1; ++j)
+ pWinPointAryAry[n+j] = POINT { static_cast<LONG>(pPolyAry[j].getX()), static_cast<LONG>(pPolyAry[j].getY()) };
+ pWinPointAryAry[n+nPoints-1] = pWinPointAryAry[n];
+ n += nPoints;
+ }
+
+ if ( !PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast<int*>(pWinPointAry), static_cast<UINT>(nPoly) ) &&
+ (nPolyPolyPoints > MAX_64KSALPOINTS) )
+ {
+ nPolyPolyPoints = 0;
+ nPoly = 0;
+ do
+ {
+ nPolyPolyPoints += pWinPointAry[static_cast<UINT>(nPoly)];
+ nPoly++;
+ }
+ while ( nPolyPolyPoints < MAX_64KSALPOINTS );
+ nPoly--;
+ if ( pWinPointAry[static_cast<UINT>(nPoly)] > MAX_64KSALPOINTS )
+ pWinPointAry[static_cast<UINT>(nPoly)] = MAX_64KSALPOINTS;
+ if ( nPoly == 1 )
+ Polygon( mrParent.getHDC(), pWinPointAryAry, *pWinPointAry );
+ else
+ PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast<int*>(pWinPointAry), nPoly );
+ }
+
+ if ( pWinPointAry != aWinPointAry )
+ delete [] pWinPointAry;
+ if ( pWinPointAryAry != aWinPointAryAry )
+ delete [] pWinPointAryAry;
+}
+
+bool WinSalGraphicsImpl::drawPolyLineBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry )
+{
+ // #100127# draw an array of points which might also contain bezier control points
+ if (!nPoints)
+ return true;
+
+ const HDC hdc = mrParent.getHDC();
+
+ // TODO: profile whether the following options are faster:
+ // a) look ahead and draw consecutive bezier or line segments by PolyBezierTo/PolyLineTo resp.
+ // b) convert our flag array to window's and use PolyDraw
+ MoveToEx(hdc, static_cast<LONG>(pPtAry->getX()), static_cast<LONG>(pPtAry->getY()), nullptr);
+ ++pPtAry;
+ ++pFlgAry;
+
+ for(sal_uInt32 i = 1; i < nPoints; ++i)
+ {
+ if(*pFlgAry != PolyFlags::Control)
+ {
+ LineTo(hdc, pPtAry->getX(), pPtAry->getY());
+ }
+ else if(nPoints - i > 2)
+ {
+ POINT bezierPoints[] = {
+ POINT { static_cast<LONG>(pPtAry[0].getX()), static_cast<LONG>(pPtAry[0].getY()) },
+ POINT { static_cast<LONG>(pPtAry[1].getX()), static_cast<LONG>(pPtAry[1].getY()) },
+ POINT { static_cast<LONG>(pPtAry[2].getX()), static_cast<LONG>(pPtAry[2].getY()) },
+ };
+ PolyBezierTo(hdc, bezierPoints, 3);
+ i += 2;
+ pPtAry += 2;
+ pFlgAry += 2;
+ }
+
+ ++pPtAry;
+ ++pFlgAry;
+ }
+
+ return true;
+}
+
+bool WinSalGraphicsImpl::drawPolygonBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry )
+{
+ POINT aStackAry1[SAL_POLY_STACKBUF];
+ BYTE aStackAry2[SAL_POLY_STACKBUF];
+ POINT* pWinPointAry;
+ BYTE* pWinFlagAry;
+ if( nPoints > SAL_POLY_STACKBUF )
+ {
+ pWinPointAry = new POINT[ nPoints ];
+ pWinFlagAry = new BYTE[ nPoints ];
+ }
+ else
+ {
+ pWinPointAry = aStackAry1;
+ pWinFlagAry = aStackAry2;
+ }
+
+ sal_uInt32 nPoints_i32(nPoints);
+ ImplPreparePolyDraw(true, 1, &nPoints_i32, &pPtAry, &pFlgAry, pWinPointAry, pWinFlagAry);
+
+ bool bRet( false );
+
+ if( BeginPath( mrParent.getHDC() ) )
+ {
+ PolyDraw(mrParent.getHDC(), pWinPointAry, pWinFlagAry, nPoints);
+
+ if( EndPath( mrParent.getHDC() ) )
+ {
+ if( StrokeAndFillPath( mrParent.getHDC() ) )
+ bRet = true;
+ }
+ }
+
+ if( pWinPointAry != aStackAry1 )
+ {
+ delete [] pWinPointAry;
+ delete [] pWinFlagAry;
+ }
+
+ return bRet;
+}
+
+bool WinSalGraphicsImpl::drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point* const* pPtAry, const PolyFlags* const* pFlgAry )
+{
+ sal_uLong nCurrPoly, nTotalPoints;
+ const sal_uInt32* pCurrPoints = pPoints;
+ for( nCurrPoly=0, nTotalPoints=0; nCurrPoly<nPoly; ++nCurrPoly )
+ nTotalPoints += *pCurrPoints++;
+
+ POINT aStackAry1[SAL_POLY_STACKBUF];
+ BYTE aStackAry2[SAL_POLY_STACKBUF];
+ POINT* pWinPointAry;
+ BYTE* pWinFlagAry;
+ if( nTotalPoints > SAL_POLY_STACKBUF )
+ {
+ pWinPointAry = new POINT[ nTotalPoints ];
+ pWinFlagAry = new BYTE[ nTotalPoints ];
+ }
+ else
+ {
+ pWinPointAry = aStackAry1;
+ pWinFlagAry = aStackAry2;
+ }
+
+ ImplPreparePolyDraw(true, nPoly, pPoints, pPtAry, pFlgAry, pWinPointAry, pWinFlagAry);
+
+ bool bRet( false );
+
+ if( BeginPath( mrParent.getHDC() ) )
+ {
+ PolyDraw(mrParent.getHDC(), pWinPointAry, pWinFlagAry, nTotalPoints);
+
+ if( EndPath( mrParent.getHDC() ) )
+ {
+ if( StrokeAndFillPath( mrParent.getHDC() ) )
+ bRet = true;
+ }
+ }
+
+ if( pWinPointAry != aStackAry1 )
+ {
+ delete [] pWinPointAry;
+ delete [] pWinFlagAry;
+ }
+
+ return bRet;
+}
+
+static basegfx::B2DPoint impPixelSnap(
+ const basegfx::B2DPolygon& rPolygon,
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ basegfx::B2DHomMatrix& rObjectToDeviceInv,
+ sal_uInt32 nIndex)
+{
+ const sal_uInt32 nCount(rPolygon.count());
+
+ // get the data
+ const basegfx::B2ITuple aPrevTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount)));
+ const basegfx::B2DPoint aCurrPoint(rObjectToDevice * rPolygon.getB2DPoint(nIndex));
+ const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
+ const basegfx::B2ITuple aNextTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount)));
+
+ // get the states
+ const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
+ const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
+ const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
+ const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
+ const bool bSnapX(bPrevVertical || bNextVertical);
+ const bool bSnapY(bPrevHorizontal || bNextHorizontal);
+
+ if(bSnapX || bSnapY)
+ {
+ basegfx::B2DPoint aSnappedPoint(
+ bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
+ bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());
+
+ if(rObjectToDeviceInv.isIdentity())
+ {
+ rObjectToDeviceInv = rObjectToDevice;
+ rObjectToDeviceInv.invert();
+ }
+
+ aSnappedPoint *= rObjectToDeviceInv;
+
+ return aSnappedPoint;
+ }
+
+ return rPolygon.getB2DPoint(nIndex);
+}
+
+static void impAddB2DPolygonToGDIPlusGraphicsPathReal(
+ Gdiplus::GraphicsPath& rGraphicsPath,
+ const basegfx::B2DPolygon& rPolygon,
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ bool bNoLineJoin,
+ bool bPixelSnapHairline)
+{
+ sal_uInt32 nCount(rPolygon.count());
+
+ if(nCount)
+ {
+ const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nCount : nCount - 1);
+
+ if(nEdgeCount)
+ {
+ const bool bControls(rPolygon.areControlPointsUsed());
+ basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0));
+ basegfx::B2DHomMatrix aObjectToDeviceInv;
+
+ if(bPixelSnapHairline)
+ {
+ aCurr = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, 0);
+ }
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nCount);
+ basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex));
+ const bool b1stControlPointUsed(bControls && rPolygon.isNextControlPointUsed(a));
+ const bool b2ndControlPointUsed(bControls && rPolygon.isPrevControlPointUsed(nNextIndex));
+
+ if(bPixelSnapHairline)
+ {
+ aNext = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nNextIndex);
+ }
+
+ if(b1stControlPointUsed || b2ndControlPointUsed)
+ {
+ basegfx::B2DPoint aCa(rPolygon.getNextControlPoint(a));
+ basegfx::B2DPoint aCb(rPolygon.getPrevControlPoint(nNextIndex));
+
+ // tdf#99165 MS Gdiplus cannot handle creating correct extra geometry for fat lines
+ // with LineCap or LineJoin when a bezier segment starts or ends trivial, e.g. has
+ // no 1st or 2nd control point, despite that these are mathematically correct definitions
+ // (basegfx can handle that).
+ // Caution: This error (and it's correction) might be necessary for other graphical
+ // sub-systems in a similar way.
+ // tdf#101026 The 1st attempt to create a mathematically correct replacement control
+ // vector was wrong. Best alternative is one as close as possible which means short.
+ if(!b1stControlPointUsed)
+ {
+ aCa = aCurr + ((aCb - aCurr) * 0.0005);
+ }
+ else if(!b2ndControlPointUsed)
+ {
+ aCb = aNext + ((aCa - aNext) * 0.0005);
+ }
+
+ rGraphicsPath.AddBezier(
+ static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()),
+ static_cast< Gdiplus::REAL >(aCa.getX()), static_cast< Gdiplus::REAL >(aCa.getY()),
+ static_cast< Gdiplus::REAL >(aCb.getX()), static_cast< Gdiplus::REAL >(aCb.getY()),
+ static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY()));
+ }
+ else
+ {
+ rGraphicsPath.AddLine(
+ static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()),
+ static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY()));
+ }
+
+ if(a + 1 < nEdgeCount)
+ {
+ aCurr = aNext;
+
+ if(bNoLineJoin)
+ {
+ rGraphicsPath.StartFigure();
+ }
+ }
+ }
+ }
+ }
+}
+
+namespace {
+
+class SystemDependentData_GraphicsPath : public basegfx::SystemDependentData
+{
+private:
+ // the path data itself
+ std::shared_ptr<Gdiplus::GraphicsPath> mpGraphicsPath;
+
+ // all other values the triangulation is based on and
+ // need to be compared with to check for data validity
+ bool mbNoLineJoin;
+ std::vector< double > maStroke;
+
+public:
+ SystemDependentData_GraphicsPath(
+ std::shared_ptr<Gdiplus::GraphicsPath>& rpGraphicsPath,
+ bool bNoLineJoin,
+ const std::vector< double >* pStroke); // MM01
+
+ // read access
+ std::shared_ptr<Gdiplus::GraphicsPath>& getGraphicsPath() { return mpGraphicsPath; }
+ bool getNoLineJoin() const { return mbNoLineJoin; }
+ const std::vector< double >& getStroke() const { return maStroke; }
+
+ virtual sal_Int64 estimateUsageInBytes() const override;
+};
+
+}
+
+SystemDependentData_GraphicsPath::SystemDependentData_GraphicsPath(
+ std::shared_ptr<Gdiplus::GraphicsPath>& rpGraphicsPath,
+ bool bNoLineJoin,
+ const std::vector< double >* pStroke)
+: basegfx::SystemDependentData(Application::GetSystemDependentDataManager()),
+ mpGraphicsPath(rpGraphicsPath),
+ mbNoLineJoin(bNoLineJoin),
+ maStroke()
+{
+ if(nullptr != pStroke)
+ {
+ maStroke = *pStroke;
+ }
+}
+
+sal_Int64 SystemDependentData_GraphicsPath::estimateUsageInBytes() const
+{
+ sal_Int64 nRetval(0);
+
+ if(mpGraphicsPath)
+ {
+ const INT nPointCount(mpGraphicsPath->GetPointCount());
+
+ if(0 != nPointCount)
+ {
+ // Each point has
+ // - 2 x sizeof(Gdiplus::REAL)
+ // - 1 byte (see GetPathTypes in docu)
+ nRetval = nPointCount * ((2 * sizeof(Gdiplus::REAL)) + 1);
+ }
+ }
+
+ return nRetval;
+}
+
+void WinSalGraphicsImpl::drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+ const sal_uInt32 nCount(rPolyPolygon.count());
+
+ if(!mbBrush || 0 == nCount || fTransparency < 0.0 || fTransparency > 1.0)
+ {
+ return;
+ }
+
+ Gdiplus::Graphics aGraphics(mrParent.getHDC());
+ const sal_uInt8 aTrans(sal_uInt8(255) - static_cast<sal_uInt8>(basegfx::fround(fTransparency * 255.0)));
+ const Gdiplus::Color aTestColor(aTrans, maFillColor.GetRed(), maFillColor.GetGreen(), maFillColor.GetBlue());
+ const Gdiplus::SolidBrush aSolidBrush(aTestColor.GetValue());
+
+ // Set full (Object-to-Device) transformation - if used
+ if(rObjectToDevice.isIdentity())
+ {
+ aGraphics.ResetTransform();
+ }
+ else
+ {
+ Gdiplus::Matrix aMatrix;
+
+ aMatrix.SetElements(
+ rObjectToDevice.get(0, 0),
+ rObjectToDevice.get(1, 0),
+ rObjectToDevice.get(0, 1),
+ rObjectToDevice.get(1, 1),
+ rObjectToDevice.get(0, 2),
+ rObjectToDevice.get(1, 2));
+ aGraphics.SetTransform(&aMatrix);
+ }
+
+ // prepare local instance of Gdiplus::GraphicsPath
+ std::shared_ptr<Gdiplus::GraphicsPath> pGraphicsPath;
+
+ // try to access buffered data
+ std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath(
+ rPolyPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>());
+
+ if(pSystemDependentData_GraphicsPath)
+ {
+ // copy buffered data
+ pGraphicsPath = pSystemDependentData_GraphicsPath->getGraphicsPath();
+ }
+ else
+ {
+ // Note: In principle we could use the same buffered geometry at line
+ // and fill polygons. Checked that in a first try, used
+ // GraphicsPath::AddPath from Gdiplus combined with below used
+ // StartFigure/CloseFigure, worked well (thus the line-draw version
+ // may create non-closed partial Polygon data).
+ //
+ // But in current reality it gets not used due to e.g.
+ // SdrPathPrimitive2D::create2DDecomposition creating transformed
+ // line and fill polygon-primitives (what could be changed).
+ //
+ // There will probably be more hindrances here in other rendering paths
+ // which could all be found - intention to do this would be: Use more
+ // transformations, less modifications of B2DPolygons/B2DPolyPolygons.
+ //
+ // A fix for SdrPathPrimitive2D would be to create the sub-geometry
+ // and embed into a TransformPrimitive2D containing the transformation.
+ //
+ // A 2nd problem is that the NoLineJoin mode (basegfx::B2DLineJoin::NONE
+ // && !bIsHairline) creates polygon fill infos that are not reusable
+ // for the fill case (see ::drawPolyLine below) - thus we would need a
+ // bool and/or two system-dependent paths buffered - doable, but complicated.
+ //
+ // All in all: Make B2DPolyPolygon a SystemDependentDataProvider and buffer
+ // the whole to-be-filled PolyPolygon independent from evtl. line-polygon
+ // (at least for now...)
+
+ // create data
+ pGraphicsPath = std::make_shared<Gdiplus::GraphicsPath>();
+
+ for(sal_uInt32 a(0); a < nCount; a++)
+ {
+ if(0 != a)
+ {
+ // #i101491# not needed for first run
+ pGraphicsPath->StartFigure();
+ }
+
+ impAddB2DPolygonToGDIPlusGraphicsPathReal(
+ *pGraphicsPath,
+ rPolyPolygon.getB2DPolygon(a),
+ rObjectToDevice, // not used due to the two 'false' values below, but to not forget later
+ false,
+ false);
+
+ pGraphicsPath->CloseFigure();
+ }
+
+ // add to buffering mechanism
+ rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>(
+ pGraphicsPath,
+ false,
+ nullptr);
+ }
+
+ if(mrParent.getAntiAlias())
+ {
+ aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
+ }
+ else
+ {
+ aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
+ }
+
+ if(mrParent.isPrinter())
+ {
+ // #i121591#
+ // Normally GdiPlus should not be used for printing at all since printers cannot
+ // print transparent filled polygon geometry and normally this does not happen
+ // since OutputDevice::RemoveTransparenciesFromMetaFile is used as preparation
+ // and no transparent parts should remain for printing. But this can be overridden
+ // by the user and thus happens. This call can only come (currently) from
+ // OutputDevice::DrawTransparent, see comments there with the same TaskID.
+ // If it is used, the mapping for the printer is wrong and needs to be corrected. I
+ // checked that there is *no* transformation set and estimated that a stable factor
+ // dependent of the printer's DPI is used. Create and set a transformation here to
+ // correct this.
+ const Gdiplus::REAL aDpiX(aGraphics.GetDpiX());
+ const Gdiplus::REAL aDpiY(aGraphics.GetDpiY());
+
+ // Now the transformation maybe/is already used (see above), so do
+ // modify it without resetting to not destroy it.
+ // I double-checked with MS docu that Gdiplus::MatrixOrderAppend does what
+ // we need - in our notation, would be a multiply from left to execute
+ // current transform first and this scale last.
+ // I tried to trigger this code using Print from the menu and various
+ // targets, but got no hit, thus maybe obsolete anyways. If someone knows
+ // more, feel free to remove it.
+ // One more hint: This *may* also be needed now in ::drawPolyLine below
+ // since it also uses transformations now.
+ //
+ // aGraphics.ResetTransform();
+
+ aGraphics.ScaleTransform(
+ Gdiplus::REAL(100.0) / aDpiX,
+ Gdiplus::REAL(100.0) / aDpiY,
+ Gdiplus::MatrixOrderAppend);
+ }
+
+ // use created or buffered data
+ aGraphics.FillPath(
+ &aSolidBrush,
+ &(*pGraphicsPath));
+}
+
+bool WinSalGraphicsImpl::drawPolyLine(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& rPolygon,
+ double fTransparency,
+ double fLineWidth,
+ const std::vector< double >* pStroke, // MM01
+ basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle,
+ bool bPixelSnapHairline)
+{
+ // MM01 check done for simple reasons
+ if(!mbPen || !rPolygon.count() || fTransparency < 0.0 || fTransparency > 1.0)
+ {
+ return true;
+ }
+
+ // need to check/handle LineWidth when ObjectToDevice transformation is used
+ const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity());
+ const bool bIsHairline(fLineWidth == 0);
+
+ // tdf#124848 calculate-back logical LineWidth for a hairline
+ // since this implementation hands over the transformation to
+ // the graphic sub-system
+ if(bIsHairline)
+ {
+ fLineWidth = 1.0;
+
+ if(!bObjectToDeviceIsIdentity)
+ {
+ basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice);
+ aObjectToDeviceInv.invert();
+ fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(fLineWidth, 0)).getLength();
+ }
+ }
+
+ Gdiplus::Graphics aGraphics(mrParent.getHDC());
+ const sal_uInt8 aTrans = static_cast<sal_uInt8>(basegfx::fround( 255 * (1.0 - fTransparency) ));
+ const Gdiplus::Color aTestColor(aTrans, maLineColor.GetRed(), maLineColor.GetGreen(), maLineColor.GetBlue());
+ Gdiplus::Pen aPen(aTestColor.GetValue(), Gdiplus::REAL(fLineWidth));
+ bool bNoLineJoin(false);
+
+ // Set full (Object-to-Device) transformation - if used
+ if(bObjectToDeviceIsIdentity)
+ {
+ aGraphics.ResetTransform();
+ }
+ else
+ {
+ Gdiplus::Matrix aMatrix;
+
+ aMatrix.SetElements(
+ rObjectToDevice.get(0, 0),
+ rObjectToDevice.get(1, 0),
+ rObjectToDevice.get(0, 1),
+ rObjectToDevice.get(1, 1),
+ rObjectToDevice.get(0, 2),
+ rObjectToDevice.get(1, 2));
+ aGraphics.SetTransform(&aMatrix);
+ }
+
+ switch(eLineJoin)
+ {
+ case basegfx::B2DLineJoin::NONE :
+ {
+ if(!bIsHairline)
+ {
+ bNoLineJoin = true;
+ }
+ break;
+ }
+ case basegfx::B2DLineJoin::Bevel :
+ {
+ aPen.SetLineJoin(Gdiplus::LineJoinBevel);
+ break;
+ }
+ case basegfx::B2DLineJoin::Miter :
+ {
+ const Gdiplus::REAL aMiterLimit(1.0/sin(fMiterMinimumAngle/2.0));
+
+ aPen.SetMiterLimit(aMiterLimit);
+ // tdf#99165 MS's LineJoinMiter creates non standard conform miter additional
+ // graphics, somewhere clipped in some distance from the edge point, dependent
+ // of MiterLimit. The more default-like option is LineJoinMiterClipped, so use
+ // that instead
+ aPen.SetLineJoin(Gdiplus::LineJoinMiterClipped);
+ break;
+ }
+ case basegfx::B2DLineJoin::Round :
+ {
+ aPen.SetLineJoin(Gdiplus::LineJoinRound);
+ break;
+ }
+ }
+
+ switch(eLineCap)
+ {
+ default: /*css::drawing::LineCap_BUTT*/
+ {
+ // nothing to do
+ break;
+ }
+ case css::drawing::LineCap_ROUND:
+ {
+ aPen.SetStartCap(Gdiplus::LineCapRound);
+ aPen.SetEndCap(Gdiplus::LineCapRound);
+ break;
+ }
+ case css::drawing::LineCap_SQUARE:
+ {
+ aPen.SetStartCap(Gdiplus::LineCapSquare);
+ aPen.SetEndCap(Gdiplus::LineCapSquare);
+ break;
+ }
+ }
+
+ // prepare local instance of Gdiplus::GraphicsPath
+ std::shared_ptr<Gdiplus::GraphicsPath> pGraphicsPath;
+
+ // try to access buffered data
+ std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath(
+ rPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>());
+
+ // MM01 need to do line dashing as fallback stuff here now
+ const double fDotDashLength(nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0);
+ const bool bStrokeUsed(0.0 != fDotDashLength);
+ assert(!bStrokeUsed || (bStrokeUsed && pStroke));
+
+ // MM01 decide if to stroke directly
+ static bool bDoDirectGDIPlusStroke(true);
+
+ // activate to stroke directly
+ if(bDoDirectGDIPlusStroke && bStrokeUsed)
+ {
+ // tdf#124848 the fix of tdf#130478 that was needed here before
+ // gets much easier when already handling the hairline case above,
+ // the back-calculated logical linewidth is already here, just use it.
+ // Still be careful - a zero LineWidth *should* not happen, but...
+ std::vector<Gdiplus::REAL> aDashArray(pStroke->size());
+ const double fFactor(fLineWidth == 0 ? 1.0 : 1.0 / fLineWidth);
+
+ // tdf#134128. ODF adds caps to the dashes and dots, but GDI makes caps from the
+ // dash or dot themselves. We tweak aDashArray to look the same in GDI (e.g. Impress edit mode)
+ // and other renders (e.g. Impress slide show), while keeping the total length of the
+ // pattern.
+ // Patterns are always a sequence dash space dash space ...
+ if (eLineCap != css::drawing::LineCap_BUTT)
+ {
+ size_t nSize = pStroke->size();
+ // We want to treat dash and space in pairs. There should be no odd size. If so, we ignore
+ // last item.
+ nSize /= 2;
+ for(size_t a(0); a < nSize; a++)
+ {
+ double fDashLengthRel = (*pStroke)[2 * a] * fFactor;
+ double fSpaceLengthRel = (*pStroke)[2 * a + 1] * fFactor;
+ // GDI allows only positive lengths for space, Skia negative lengths too. Thus the
+ // appearance is different, in case space is too small.
+ double fCorrect = fSpaceLengthRel - 1.0 <= 0 ? fSpaceLengthRel - 0.01 : 1.0;
+ aDashArray[2 * a] = Gdiplus::REAL(fDashLengthRel + fCorrect);
+ aDashArray[2 * a + 1] = Gdiplus::REAL(fSpaceLengthRel - fCorrect);
+ }
+ }
+ else
+ {
+ for(size_t a(0); a < pStroke->size(); a++)
+ {
+ aDashArray[a] = Gdiplus::REAL((*pStroke)[a] * fFactor);
+ }
+ }
+ if (eLineCap == css::drawing::LineCap_ROUND)
+ aPen.SetDashCap(Gdiplus::DashCapRound);
+ else
+ aPen.SetDashCap(Gdiplus::DashCapFlat); // "square" doesn't exist in Gdiplus
+ aPen.SetDashOffset(Gdiplus::REAL(0.0));
+ aPen.SetDashPattern(aDashArray.data(), aDashArray.size());
+ }
+
+ if(!bDoDirectGDIPlusStroke && pSystemDependentData_GraphicsPath)
+ {
+ // MM01 - check on stroke change. Used against not used, or if oth used,
+ // equal or different? Triangulation geometry creation depends heavily
+ // on stroke, independent of being transformation independent
+ const bool bStrokeWasUsed(!pSystemDependentData_GraphicsPath->getStroke().empty());
+
+ if(bStrokeWasUsed != bStrokeUsed
+ || (bStrokeUsed && *pStroke != pSystemDependentData_GraphicsPath->getStroke()))
+ {
+ // data invalid, forget
+ pSystemDependentData_GraphicsPath.reset();
+ }
+ }
+
+ if(pSystemDependentData_GraphicsPath)
+ {
+ // check data validity
+ if (pSystemDependentData_GraphicsPath->getNoLineJoin() != bNoLineJoin
+ || bPixelSnapHairline /*tdf#124700*/)
+ {
+ // data invalid, forget
+ pSystemDependentData_GraphicsPath.reset();
+ }
+ }
+
+ if(pSystemDependentData_GraphicsPath)
+ {
+ // copy buffered data
+ pGraphicsPath = pSystemDependentData_GraphicsPath->getGraphicsPath();
+ }
+ else
+ {
+ // fill data of buffered data
+ pGraphicsPath = std::make_shared<Gdiplus::GraphicsPath>();
+
+ if(!bDoDirectGDIPlusStroke && bStrokeUsed)
+ {
+ // MM01 need to do line dashing as fallback stuff here now
+ basegfx::B2DPolyPolygon aPolyPolygonLine;
+
+ // apply LineStyle
+ basegfx::utils::applyLineDashing(
+ rPolygon, // source
+ *pStroke, // pattern
+ &aPolyPolygonLine, // target for lines
+ nullptr, // target for gaps
+ fDotDashLength); // full length if available
+
+ // MM01 checked/verified, ok
+ for(sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
+ {
+ const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
+ pGraphicsPath->StartFigure();
+ impAddB2DPolygonToGDIPlusGraphicsPathReal(
+ *pGraphicsPath,
+ aPolyLine,
+ rObjectToDevice,
+ bNoLineJoin,
+ bPixelSnapHairline);
+ }
+ }
+ else
+ {
+ // no line dashing or direct stroke, just copy
+ impAddB2DPolygonToGDIPlusGraphicsPathReal(
+ *pGraphicsPath,
+ rPolygon,
+ rObjectToDevice,
+ bNoLineJoin,
+ bPixelSnapHairline);
+
+ if(rPolygon.isClosed() && !bNoLineJoin)
+ {
+ // #i101491# needed to create the correct line joins
+ pGraphicsPath->CloseFigure();
+ }
+ }
+
+ // add to buffering mechanism
+ if (!bPixelSnapHairline /*tdf#124700*/)
+ {
+ rPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>(
+ pGraphicsPath,
+ bNoLineJoin,
+ pStroke);
+ }
+ }
+
+ if(mrParent.getAntiAlias())
+ {
+ aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
+ }
+ else
+ {
+ aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
+ }
+
+ if(mrParent.isPrinter())
+ {
+ // tdf#122384 As mentioned above in WinSalGraphicsImpl::drawPolyPolygon
+ // (look for 'One more hint: This *may* also be needed now in'...).
+ // See comments in same spot above *urgently* before doing changes here,
+ // these comments are *still fully valid* at this place (!)
+ const Gdiplus::REAL aDpiX(aGraphics.GetDpiX());
+ const Gdiplus::REAL aDpiY(aGraphics.GetDpiY());
+
+ aGraphics.ScaleTransform(
+ Gdiplus::REAL(100.0) / aDpiX,
+ Gdiplus::REAL(100.0) / aDpiY,
+ Gdiplus::MatrixOrderAppend);
+ }
+
+ aGraphics.DrawPath(
+ &aPen,
+ &(*pGraphicsPath));
+
+ return true;
+}
+
+static void paintToGdiPlus(
+ Gdiplus::Graphics& rGraphics,
+ const SalTwoRect& rTR,
+ Gdiplus::Bitmap& rBitmap)
+{
+ // only parts of source are used
+ Gdiplus::PointF aDestPoints[3];
+ Gdiplus::ImageAttributes aAttributes;
+
+ // define target region as parallelogram
+ aDestPoints[0].X = Gdiplus::REAL(rTR.mnDestX);
+ aDestPoints[0].Y = Gdiplus::REAL(rTR.mnDestY);
+ aDestPoints[1].X = Gdiplus::REAL(rTR.mnDestX + rTR.mnDestWidth);
+ aDestPoints[1].Y = Gdiplus::REAL(rTR.mnDestY);
+ aDestPoints[2].X = Gdiplus::REAL(rTR.mnDestX);
+ aDestPoints[2].Y = Gdiplus::REAL(rTR.mnDestY + rTR.mnDestHeight);
+
+ aAttributes.SetWrapMode(Gdiplus::WrapModeTileFlipXY);
+
+ rGraphics.DrawImage(
+ &rBitmap,
+ aDestPoints,
+ 3,
+ Gdiplus::REAL(rTR.mnSrcX),
+ Gdiplus::REAL(rTR.mnSrcY),
+ Gdiplus::REAL(rTR.mnSrcWidth),
+ Gdiplus::REAL(rTR.mnSrcHeight),
+ Gdiplus::UnitPixel,
+ &aAttributes);
+}
+
+static void setInterpolationMode(
+ Gdiplus::Graphics& rGraphics,
+ tools::Long rSrcWidth,
+ tools::Long rDestWidth,
+ tools::Long rSrcHeight,
+ tools::Long rDestHeight)
+{
+ const bool bSameWidth(rSrcWidth == rDestWidth);
+ const bool bSameHeight(rSrcHeight == rDestHeight);
+
+ if(bSameWidth && bSameHeight)
+ {
+ rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeInvalid);
+ }
+ else if(rDestWidth > rSrcWidth && rDestHeight > rSrcHeight)
+ {
+ rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeDefault);
+ }
+ else if(rDestWidth < rSrcWidth && rDestHeight < rSrcHeight)
+ {
+ rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeBicubic);
+ }
+ else
+ {
+ rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeDefault);
+ }
+}
+
+bool WinSalGraphicsImpl::TryDrawBitmapGDIPlus(const SalTwoRect& rTR, const SalBitmap& rSrcBitmap)
+{
+ if(rTR.mnSrcWidth && rTR.mnSrcHeight && rTR.mnDestWidth && rTR.mnDestHeight)
+ {
+ assert(dynamic_cast<const WinSalBitmap*>(&rSrcBitmap));
+
+ const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSrcBitmap);
+ std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap());
+
+ if(aARGB)
+ {
+ Gdiplus::Graphics aGraphics(mrParent.getHDC());
+
+ setInterpolationMode(
+ aGraphics,
+ rTR.mnSrcWidth,
+ rTR.mnDestWidth,
+ rTR.mnSrcHeight,
+ rTR.mnDestHeight);
+
+ paintToGdiPlus(
+ aGraphics,
+ rTR,
+ *aARGB);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool WinSalGraphicsImpl::blendBitmap(
+ const SalTwoRect&,
+ const SalBitmap&)
+{
+ return false;
+}
+
+bool WinSalGraphicsImpl::blendAlphaBitmap(
+ const SalTwoRect&,
+ const SalBitmap&,
+ const SalBitmap&,
+ const SalBitmap&)
+{
+ return false;
+}
+
+bool WinSalGraphicsImpl::drawAlphaBitmap(
+ const SalTwoRect& rTR,
+ const SalBitmap& rSrcBitmap,
+ const SalBitmap& rAlphaBmp)
+{
+ if(rTR.mnSrcWidth && rTR.mnSrcHeight && rTR.mnDestWidth && rTR.mnDestHeight)
+ {
+ assert(dynamic_cast<const WinSalBitmap*>(&rSrcBitmap));
+ assert(dynamic_cast<const WinSalBitmap*>(&rAlphaBmp));
+
+ const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSrcBitmap);
+ const WinSalBitmap& rSalAlpha = static_cast< const WinSalBitmap& >(rAlphaBmp);
+ std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap(&rSalAlpha));
+
+ if(aARGB)
+ {
+ Gdiplus::Graphics aGraphics(mrParent.getHDC());
+
+ setInterpolationMode(
+ aGraphics,
+ rTR.mnSrcWidth,
+ rTR.mnDestWidth,
+ rTR.mnSrcHeight,
+ rTR.mnDestHeight);
+
+ paintToGdiPlus(
+ aGraphics,
+ rTR,
+ *aARGB);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool WinSalGraphicsImpl::drawTransformedBitmap(
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap,
+ double fAlpha)
+{
+ assert(dynamic_cast<const WinSalBitmap*>(&rSourceBitmap));
+ assert(!pAlphaBitmap || dynamic_cast<const WinSalBitmap*>(pAlphaBitmap));
+
+ if( fAlpha != 1.0 )
+ return false;
+
+ const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSourceBitmap);
+ const WinSalBitmap* pSalAlpha = static_cast< const WinSalBitmap* >(pAlphaBitmap);
+ std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap(pSalAlpha));
+
+ if(aARGB)
+ {
+ const tools::Long nSrcWidth(aARGB->GetWidth());
+ const tools::Long nSrcHeight(aARGB->GetHeight());
+
+ if(nSrcWidth && nSrcHeight)
+ {
+ const tools::Long nDestWidth(basegfx::fround(basegfx::B2DVector(rX - rNull).getLength()));
+ const tools::Long nDestHeight(basegfx::fround(basegfx::B2DVector(rY - rNull).getLength()));
+
+ if(nDestWidth && nDestHeight)
+ {
+ Gdiplus::Graphics aGraphics(mrParent.getHDC());
+ Gdiplus::PointF aDestPoints[3];
+ Gdiplus::ImageAttributes aAttributes;
+
+ setInterpolationMode(
+ aGraphics,
+ nSrcWidth,
+ nDestWidth,
+ nSrcHeight,
+ nDestHeight);
+
+ // this mode is only capable of drawing the whole bitmap to a parallelogram
+ aDestPoints[0].X = Gdiplus::REAL(rNull.getX());
+ aDestPoints[0].Y = Gdiplus::REAL(rNull.getY());
+ aDestPoints[1].X = Gdiplus::REAL(rX.getX());
+ aDestPoints[1].Y = Gdiplus::REAL(rX.getY());
+ aDestPoints[2].X = Gdiplus::REAL(rY.getX());
+ aDestPoints[2].Y = Gdiplus::REAL(rY.getY());
+
+ aAttributes.SetWrapMode(Gdiplus::WrapModeTileFlipXY);
+
+ aGraphics.DrawImage(
+ aARGB.get(),
+ aDestPoints,
+ 3,
+ Gdiplus::REAL(0.0),
+ Gdiplus::REAL(0.0),
+ Gdiplus::REAL(nSrcWidth),
+ Gdiplus::REAL(nSrcHeight),
+ Gdiplus::UnitPixel,
+ &aAttributes);
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool WinSalGraphicsImpl::hasFastDrawTransformedBitmap() const
+{
+ return false;
+}
+
+bool WinSalGraphicsImpl::drawGradient(const tools::PolyPolygon& /*rPolygon*/,
+ const Gradient& /*rGradient*/)
+{
+ return false;
+}
+
+bool WinSalGraphicsImpl::implDrawGradient(basegfx::B2DPolyPolygon const & /*rPolyPolygon*/,
+ SalGradient const & /*rGradient*/)
+{
+ return false;
+}
+
+bool WinSalGraphicsImpl::supportsOperation(OutDevSupportType eType) const
+{
+ bool bRet = false;
+
+ switch (eType)
+ {
+ case OutDevSupportType::TransparentRect:
+ bRet = mrParent.mbVirDev || mrParent.mbWindow;
+ break;
+ default:
+ break;
+ }
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/gdi/gdiimpl.hxx b/vcl/win/gdi/gdiimpl.hxx
new file mode 100644
index 0000000000..9d4fa32233
--- /dev/null
+++ b/vcl/win/gdi/gdiimpl.hxx
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this 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 <salgdiimpl.hxx>
+#include <tools/long.hxx>
+#include <win/salgdi.h>
+#include <win/wingdiimpl.hxx>
+
+#include <vcl/gradient.hxx>
+
+#include <svsys.h>
+#include <ControlCacheKey.hxx>
+
+class WinSalGraphics;
+
+class WinSalGraphicsImpl : public SalGraphicsImpl, public WinSalGraphicsImplBase
+{
+private:
+
+ WinSalGraphics& mrParent;
+ bool mbXORMode : 1; // _every_ output with RasterOp XOR
+ bool mbPen : 1; // is Pen (FALSE == NULL_PEN)
+ HPEN mhPen; // Pen
+ bool mbStockPen : 1; // is Pen a stockpen
+ bool mbBrush : 1; // is Brush (FALSE == NULL_BRUSH)
+ bool mbStockBrush : 1; // is Brush a stockbrush
+ HBRUSH mhBrush; // Brush
+ COLORREF mnPenColor; // PenColor
+ COLORREF mnBrushColor; // BrushColor
+
+ // remember RGB values for SetLineColor/SetFillColor
+ Color maLineColor;
+ Color maFillColor;
+
+ bool TryDrawBitmapGDIPlus(const SalTwoRect& rTR, const SalBitmap& rSrcBitmap);
+ void DrawPixelImpl(tools::Long nX, tools::Long nY, COLORREF crColor);
+
+ HPEN SearchStockPen(COLORREF nPenColor);
+ HPEN MakePen(Color nColor);
+ void ResetPen(HPEN hNewPen);
+
+ HBRUSH SearchStockBrush(COLORREF nBrushColor);
+ HBRUSH MakeBrush(Color nColor);
+ void ResetBrush(HBRUSH hNewBrush);
+public:
+
+ explicit WinSalGraphicsImpl(WinSalGraphics& rParent);
+
+ virtual ~WinSalGraphicsImpl() override;
+
+ virtual void Init() override;
+
+ virtual void freeResources() override;
+
+ virtual OUString getRenderBackendName() const override { return "gdi"; }
+
+ virtual void setClipRegion( const vcl::Region& ) override;
+ //
+ // get the depth of the device
+ virtual sal_uInt16 GetBitCount() const override;
+
+ // get the width of the device
+ virtual tools::Long GetGraphicsWidth() const override;
+
+ // set the clip region to empty
+ virtual void ResetClipRegion() override;
+
+ // set the line color to transparent (= don't draw lines)
+
+ virtual void SetLineColor() override;
+
+ // set the line color to a specific color
+ virtual void SetLineColor( Color nColor ) override;
+
+ // set the fill color to transparent (= don't fill)
+ virtual void SetFillColor() override;
+
+ // set the fill color to a specific color, shapes will be
+ // filled accordingly
+ virtual void SetFillColor( Color nColor ) override;
+
+ // enable/disable XOR drawing
+ virtual void SetXORMode( bool bSet, bool bInvertOnly ) override;
+
+ // set line color for raster operations
+ virtual void SetROPLineColor( SalROPColor nROPColor ) override;
+
+ // set fill color for raster operations
+ virtual void SetROPFillColor( SalROPColor nROPColor ) override;
+
+ // draw --> LineColor and FillColor and RasterOp and ClipRegion
+ virtual void drawPixel( tools::Long nX, tools::Long nY ) override;
+ virtual void drawPixel( tools::Long nX, tools::Long nY, Color nColor ) override;
+
+ virtual void drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 ) override;
+
+ virtual void drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+
+ virtual void drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry ) override;
+
+ virtual void drawPolygon( sal_uInt32 nPoints, const Point* pPtAry ) override;
+
+ virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, const Point** pPtAry ) override;
+
+ virtual void drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon&,
+ double fTransparency) override;
+
+ virtual bool drawPolyLine(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon&,
+ double fTransparency,
+ double fLineWidth,
+ const std::vector< double >* pStroke, // MM01
+ basegfx::B2DLineJoin,
+ css::drawing::LineCap,
+ double fMiterMinimumAngle,
+ bool bPixelSnapHairline) override;
+
+ virtual bool drawPolyLineBezier(
+ sal_uInt32 nPoints,
+ const Point* pPtAry,
+ const PolyFlags* pFlgAry ) override;
+
+ virtual bool drawPolygonBezier(
+ sal_uInt32 nPoints,
+ const Point* pPtAry,
+ const PolyFlags* pFlgAry ) override;
+
+ virtual bool drawPolyPolygonBezier(
+ sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const Point* const* pPtAry,
+ const PolyFlags* const* pFlgAry ) override;
+
+ // CopyArea --> No RasterOp, but ClipRegion
+ virtual void copyArea(
+ tools::Long nDestX, tools::Long nDestY,
+ tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight, bool bWindowInvalidate ) override;
+
+ // CopyBits and DrawBitmap --> RasterOp and ClipRegion
+ // CopyBits() --> pSrcGraphics == NULL, then CopyBits on same Graphics
+ virtual void copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) override;
+
+ virtual void drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap ) override;
+
+ virtual void drawBitmap(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rMaskBitmap ) override;
+
+ virtual void drawMask(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ Color nMaskColor ) override;
+
+ virtual std::shared_ptr<SalBitmap> getBitmap( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+
+ virtual Color getPixel( tools::Long nX, tools::Long nY ) override;
+
+ // invert --> ClipRegion (only Windows or VirDevs)
+ virtual void invert(
+ tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ SalInvert nFlags) override;
+
+ virtual void invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags ) override;
+
+ virtual bool drawEPS(
+ tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ void* pPtr,
+ sal_uInt32 nSize ) override;
+
+ virtual bool blendBitmap(
+ const SalTwoRect&,
+ const SalBitmap& rBitmap ) override;
+
+ virtual bool blendAlphaBitmap(
+ const SalTwoRect&,
+ const SalBitmap& rSrcBitmap,
+ const SalBitmap& rMaskBitmap,
+ const SalBitmap& rAlphaBitmap ) override;
+
+ /** Render bitmap with alpha channel
+
+ @param rSourceBitmap
+ Source bitmap to blit
+
+ @param rAlphaBitmap
+ Alpha channel to use for blitting
+
+ @return true, if the operation succeeded, and false
+ otherwise. In this case, clients should try to emulate alpha
+ compositing themselves
+ */
+ virtual bool drawAlphaBitmap(
+ const SalTwoRect&,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap ) override;
+
+ /** draw transformed bitmap (maybe with alpha) where Null, X, Y define the coordinate system */
+ virtual bool drawTransformedBitmap(
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap,
+ double fAlpha) override;
+
+ virtual bool hasFastDrawTransformedBitmap() const override;
+
+ /** Render solid rectangle with given transparency
+
+ @param nTransparency
+ Transparency value (0-255) to use. 0 blits and opaque, 255 a
+ fully transparent rectangle
+ */
+ virtual bool drawAlphaRect(
+ tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ sal_uInt8 nTransparency ) override;
+
+
+ virtual bool drawGradient(const tools::PolyPolygon& rPolygon,
+ const Gradient& rGradient) override;
+ virtual bool implDrawGradient(basegfx::B2DPolyPolygon const & rPolyPolygon,
+ SalGradient const & rGradient) override;
+
+ virtual bool supportsOperation(OutDevSupportType eType) const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/gdi/salbmp.cxx b/vcl/win/gdi/salbmp.cxx
new file mode 100644
index 0000000000..71c099e952
--- /dev/null
+++ b/vcl/win/gdi/salbmp.cxx
@@ -0,0 +1,910 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svsys.h>
+#include <vcl/bitmap.hxx>
+#include <vcl/BitmapAccessMode.hxx>
+#include <vcl/BitmapBuffer.hxx>
+#include <vcl/BitmapPalette.hxx>
+#include <vcl/ColorMask.hxx>
+#include <vcl/Scanline.hxx>
+#include <com/sun/star/beans/XFastPropertySet.hpp>
+#include <win/wincomp.hxx>
+#include <win/salgdi.h>
+#include <win/saldata.hxx>
+#include <win/salbmp.h>
+#include <string.h>
+#include <vcl/timer.hxx>
+#include <cppuhelper/basemutex.hxx>
+#include <sal/log.hxx>
+#include <tools/helpers.hxx>
+#include <map>
+
+#include <prewin.h>
+#include <gdiplus.h>
+#include <postwin.h>
+
+#if defined _MSC_VER
+#undef min
+#undef max
+#endif
+
+WinSalBitmap::WinSalBitmap()
+: SalBitmap(),
+ basegfx::SystemDependentDataHolder(),
+ maSize(),
+ mhDIB(nullptr),
+ mhDDB(nullptr),
+ mnBitCount(0)
+{
+}
+
+WinSalBitmap::~WinSalBitmap()
+{
+ Destroy();
+}
+
+void WinSalBitmap::Destroy()
+{
+ if( mhDIB )
+ GlobalFree( mhDIB );
+ else if( mhDDB )
+ DeleteObject( mhDDB );
+
+ maSize = Size();
+ mnBitCount = 0;
+}
+
+namespace {
+
+class SystemDependentData_GdiPlusBitmap : public basegfx::SystemDependentData
+{
+private:
+ std::shared_ptr<Gdiplus::Bitmap> mpGdiPlusBitmap;
+ const WinSalBitmap* mpAssociatedAlpha;
+
+public:
+ SystemDependentData_GdiPlusBitmap(
+ const std::shared_ptr<Gdiplus::Bitmap>& rGdiPlusBitmap,
+ const WinSalBitmap* pAssociatedAlpha);
+
+ const WinSalBitmap* getAssociatedAlpha() const { return mpAssociatedAlpha; }
+ const std::shared_ptr<Gdiplus::Bitmap>& getGdiPlusBitmap() const { return mpGdiPlusBitmap; }
+
+ virtual sal_Int64 estimateUsageInBytes() const override;
+};
+
+}
+
+SystemDependentData_GdiPlusBitmap::SystemDependentData_GdiPlusBitmap(
+ const std::shared_ptr<Gdiplus::Bitmap>& rGdiPlusBitmap,
+ const WinSalBitmap* pAssociatedAlpha)
+: basegfx::SystemDependentData(Application::GetSystemDependentDataManager()),
+ mpGdiPlusBitmap(rGdiPlusBitmap),
+ mpAssociatedAlpha(pAssociatedAlpha)
+{
+}
+
+sal_Int64 SystemDependentData_GdiPlusBitmap::estimateUsageInBytes() const
+{
+ sal_Int64 nRetval(0);
+
+ if(mpGdiPlusBitmap)
+ {
+ const UINT nWidth(mpGdiPlusBitmap->GetWidth());
+ const UINT nHeight(mpGdiPlusBitmap->GetHeight());
+
+ if(0 != nWidth && 0 != nHeight)
+ {
+ nRetval = nWidth * nHeight;
+
+ switch(mpGdiPlusBitmap->GetPixelFormat())
+ {
+ case PixelFormat1bppIndexed:
+ nRetval /= 8;
+ break;
+ case PixelFormat4bppIndexed:
+ nRetval /= 4;
+ break;
+ case PixelFormat16bppGrayScale:
+ case PixelFormat16bppRGB555:
+ case PixelFormat16bppRGB565:
+ case PixelFormat16bppARGB1555:
+ nRetval *= 2;
+ break;
+ case PixelFormat24bppRGB:
+ nRetval *= 3;
+ break;
+ case PixelFormat32bppRGB:
+ case PixelFormat32bppARGB:
+ case PixelFormat32bppPARGB:
+ case PixelFormat32bppCMYK:
+ nRetval *= 4;
+ break;
+ case PixelFormat48bppRGB:
+ nRetval *= 6;
+ break;
+ case PixelFormat64bppARGB:
+ case PixelFormat64bppPARGB:
+ nRetval *= 8;
+ break;
+ default:
+ case PixelFormat8bppIndexed:
+ break;
+ }
+ }
+ }
+
+ return nRetval;
+}
+
+std::shared_ptr< Gdiplus::Bitmap > WinSalBitmap::ImplGetGdiPlusBitmap(const WinSalBitmap* pAlphaSource) const
+{
+ std::shared_ptr< Gdiplus::Bitmap > aRetval;
+
+ // try to access buffered data
+ std::shared_ptr<SystemDependentData_GdiPlusBitmap> pSystemDependentData_GdiPlusBitmap(
+ getSystemDependentData<SystemDependentData_GdiPlusBitmap>());
+
+ if(pSystemDependentData_GdiPlusBitmap)
+ {
+ // check data validity
+ if(pSystemDependentData_GdiPlusBitmap->getAssociatedAlpha() != pAlphaSource
+ || 0 == maSize.Width()
+ || 0 == maSize.Height())
+ {
+ // #122350# if associated alpha with which the GDIPlus was constructed has changed
+ // it is necessary to remove it from buffer, reset reference to it and reconstruct
+ // data invalid, forget
+ pSystemDependentData_GdiPlusBitmap.reset();
+ }
+ }
+
+ if(pSystemDependentData_GdiPlusBitmap)
+ {
+ // use from buffer
+ aRetval = pSystemDependentData_GdiPlusBitmap->getGdiPlusBitmap();
+ }
+ else if(!maSize.IsEmpty())
+ {
+ // create and set data
+ const WinSalBitmap* pAssociatedAlpha(nullptr);
+
+ if(pAlphaSource)
+ {
+ aRetval = const_cast< WinSalBitmap* >(this)->ImplCreateGdiPlusBitmap(*pAlphaSource);
+ pAssociatedAlpha = pAlphaSource;
+ }
+ else
+ {
+ aRetval = const_cast< WinSalBitmap* >(this)->ImplCreateGdiPlusBitmap();
+ pAssociatedAlpha = nullptr;
+ }
+
+ // add to buffering mechanism
+ addOrReplaceSystemDependentData<SystemDependentData_GdiPlusBitmap>(
+ aRetval,
+ pAssociatedAlpha);
+ }
+
+ return aRetval;
+}
+
+std::shared_ptr<Gdiplus::Bitmap> WinSalBitmap::ImplCreateGdiPlusBitmap()
+{
+ std::shared_ptr<Gdiplus::Bitmap> pRetval;
+ WinSalBitmap* pSalRGB = this;
+ std::unique_ptr<WinSalBitmap> pExtraWinSalRGB;
+
+ if(!pSalRGB->ImplGethDIB())
+ {
+ // we need DIB for success with AcquireBuffer, create a replacement WinSalBitmap
+ pExtraWinSalRGB.reset(new WinSalBitmap());
+ pExtraWinSalRGB->Create(*pSalRGB, vcl::bitDepthToPixelFormat(pSalRGB->GetBitCount()));
+ pSalRGB = pExtraWinSalRGB.get();
+ }
+
+ BitmapBuffer* pRGB = pSalRGB->AcquireBuffer(BitmapAccessMode::Read);
+ std::optional<BitmapBuffer> pExtraRGB;
+
+ if(pRGB && ScanlineFormat::N24BitTcBgr != RemoveScanline(pRGB->mnFormat))
+ {
+ // convert source bitmap to BMP_FORMAT_24BIT_TC_BGR format if not yet in that format
+ SalTwoRect aSalTwoRect(0, 0, pRGB->mnWidth, pRGB->mnHeight, 0, 0, pRGB->mnWidth, pRGB->mnHeight);
+ pExtraRGB = StretchAndConvert(
+ *pRGB,
+ aSalTwoRect,
+ ScanlineFormat::N24BitTcBgr);
+
+ pSalRGB->ReleaseBuffer(pRGB, BitmapAccessMode::Write);
+ pRGB = pExtraRGB ? &*pExtraRGB : nullptr;
+ }
+
+ if(pRGB
+ && pRGB->mnWidth > 0
+ && pRGB->mnHeight > 0
+ && ScanlineFormat::N24BitTcBgr == RemoveScanline(pRGB->mnFormat))
+ {
+ const sal_uInt32 nW(pRGB->mnWidth);
+ const sal_uInt32 nH(pRGB->mnHeight);
+
+ pRetval = std::make_shared<Gdiplus::Bitmap>(nW, nH, PixelFormat24bppRGB);
+
+ if ( pRetval->GetLastStatus() == Gdiplus::Ok )
+ {
+ sal_uInt8* pSrcRGB(pRGB->mpBits);
+ const sal_uInt32 nExtraRGB(pRGB->mnScanlineSize - (nW * 3));
+ const bool bTopDown(pRGB->mnFormat & ScanlineFormat::TopDown);
+ const Gdiplus::Rect aAllRect(0, 0, nW, nH);
+ Gdiplus::BitmapData aGdiPlusBitmapData;
+ pRetval->LockBits(&aAllRect, Gdiplus::ImageLockModeWrite, PixelFormat24bppRGB, &aGdiPlusBitmapData);
+
+ // copy data to Gdiplus::Bitmap; format is BGR here in both cases, so memcpy is possible
+ for(sal_uInt32 y(0); y < nH; y++)
+ {
+ const sal_uInt32 nYInsert(bTopDown ? y : nH - y - 1);
+ sal_uInt8* targetPixels = static_cast<sal_uInt8*>(aGdiPlusBitmapData.Scan0) + (nYInsert * aGdiPlusBitmapData.Stride);
+
+ memcpy(targetPixels, pSrcRGB, nW * 3);
+ pSrcRGB += nW * 3 + nExtraRGB;
+ }
+
+ pRetval->UnlockBits(&aGdiPlusBitmapData);
+ }
+ else
+ {
+ pRetval.reset();
+ }
+ }
+
+ if(pExtraRGB)
+ {
+ // #i123478# shockingly, BitmapBuffer does not free the memory it is controlling
+ // in its destructor, this *has to be done by hand*. Doing it here now
+ delete[] pExtraRGB->mpBits;
+ pExtraRGB.reset();
+ }
+ else
+ {
+ pSalRGB->ReleaseBuffer(pRGB, BitmapAccessMode::Read);
+ }
+
+ return pRetval;
+}
+
+std::shared_ptr<Gdiplus::Bitmap> WinSalBitmap::ImplCreateGdiPlusBitmap(const WinSalBitmap& rAlphaSource)
+{
+ std::shared_ptr<Gdiplus::Bitmap> pRetval;
+ WinSalBitmap* pSalRGB = this;
+ std::unique_ptr<WinSalBitmap> pExtraWinSalRGB;
+
+ if(!pSalRGB->ImplGethDIB())
+ {
+ // we need DIB for success with AcquireBuffer, create a replacement WinSalBitmap
+ pExtraWinSalRGB.reset(new WinSalBitmap());
+ pExtraWinSalRGB->Create(*pSalRGB, vcl::bitDepthToPixelFormat(pSalRGB->GetBitCount()));
+ pSalRGB = pExtraWinSalRGB.get();
+ }
+
+ BitmapBuffer* pRGB = pSalRGB->AcquireBuffer(BitmapAccessMode::Read);
+ std::optional<BitmapBuffer> pExtraRGB;
+
+ if(pRGB && ScanlineFormat::N24BitTcBgr != RemoveScanline(pRGB->mnFormat))
+ {
+ // convert source bitmap to canlineFormat::N24BitTcBgr format if not yet in that format
+ SalTwoRect aSalTwoRect(0, 0, pRGB->mnWidth, pRGB->mnHeight, 0, 0, pRGB->mnWidth, pRGB->mnHeight);
+ pExtraRGB = StretchAndConvert(
+ *pRGB,
+ aSalTwoRect,
+ ScanlineFormat::N24BitTcBgr);
+
+ pSalRGB->ReleaseBuffer(pRGB, BitmapAccessMode::Read);
+ pRGB = pExtraRGB ? &*pExtraRGB : nullptr;
+ }
+
+ WinSalBitmap* pSalA = const_cast< WinSalBitmap* >(&rAlphaSource);
+ std::unique_ptr<WinSalBitmap> pExtraWinSalA;
+
+ if(!pSalA->ImplGethDIB())
+ {
+ // we need DIB for success with AcquireBuffer, create a replacement WinSalBitmap
+ pExtraWinSalA.reset(new WinSalBitmap());
+ pExtraWinSalA->Create(*pSalA, vcl::bitDepthToPixelFormat(pSalA->GetBitCount()));
+ pSalA = pExtraWinSalA.get();
+ }
+
+ BitmapBuffer* pA = pSalA->AcquireBuffer(BitmapAccessMode::Read);
+ std::optional<BitmapBuffer> pExtraA;
+
+ if(pA && ScanlineFormat::N8BitPal != RemoveScanline(pA->mnFormat))
+ {
+ // convert alpha bitmap to ScanlineFormat::N8BitPal format if not yet in that format
+ SalTwoRect aSalTwoRect(0, 0, pA->mnWidth, pA->mnHeight, 0, 0, pA->mnWidth, pA->mnHeight);
+ const BitmapPalette& rTargetPalette = Bitmap::GetGreyPalette(256);
+
+ pExtraA = StretchAndConvert(
+ *pA,
+ aSalTwoRect,
+ ScanlineFormat::N8BitPal,
+ rTargetPalette);
+
+ pSalA->ReleaseBuffer(pA, BitmapAccessMode::Read);
+ pA = pExtraA ? &*pExtraA : nullptr;
+ }
+
+ if(pRGB
+ && pA
+ && pRGB->mnWidth > 0
+ && pRGB->mnHeight > 0
+ && pRGB->mnWidth == pA->mnWidth
+ && pRGB->mnHeight == pA->mnHeight
+ && ScanlineFormat::N24BitTcBgr == RemoveScanline(pRGB->mnFormat)
+ && ScanlineFormat::N8BitPal == RemoveScanline(pA->mnFormat))
+ {
+ // we have alpha and bitmap in known formats, create GdiPlus Bitmap as 32bit ARGB
+ const sal_uInt32 nW(pRGB->mnWidth);
+ const sal_uInt32 nH(pRGB->mnHeight);
+
+ pRetval = std::make_shared<Gdiplus::Bitmap>(nW, nH, PixelFormat32bppARGB);
+
+ if ( pRetval->GetLastStatus() == Gdiplus::Ok ) // 2nd place to secure with new Gdiplus::Bitmap
+ {
+ sal_uInt8* pSrcRGB(pRGB->mpBits);
+ sal_uInt8* pSrcA(pA->mpBits);
+ const sal_uInt32 nExtraRGB(pRGB->mnScanlineSize - (nW * 3));
+ const sal_uInt32 nExtraA(pA->mnScanlineSize - nW);
+ const bool bTopDown(pRGB->mnFormat & ScanlineFormat::TopDown);
+ const Gdiplus::Rect aAllRect(0, 0, nW, nH);
+ Gdiplus::BitmapData aGdiPlusBitmapData;
+ pRetval->LockBits(&aAllRect, Gdiplus::ImageLockModeWrite, PixelFormat32bppARGB, &aGdiPlusBitmapData);
+
+ // copy data to Gdiplus::Bitmap; format is BGRA; need to mix BGR from Bitmap and
+ // A from alpha, so inner loop is needed (who invented BitmapEx..?)
+ for(sal_uInt32 y(0); y < nH; y++)
+ {
+ const sal_uInt32 nYInsert(bTopDown ? y : nH - y - 1);
+ sal_uInt8* targetPixels = static_cast<sal_uInt8*>(aGdiPlusBitmapData.Scan0) + (nYInsert * aGdiPlusBitmapData.Stride);
+
+ for(sal_uInt32 x(0); x < nW; x++)
+ {
+ *targetPixels++ = *pSrcRGB++;
+ *targetPixels++ = *pSrcRGB++;
+ *targetPixels++ = *pSrcRGB++;
+ *targetPixels++ = *pSrcA++;
+ }
+
+ pSrcRGB += nExtraRGB;
+ pSrcA += nExtraA;
+ }
+
+ pRetval->UnlockBits(&aGdiPlusBitmapData);
+ }
+ else
+ {
+ pRetval.reset();
+ }
+ }
+
+ if(pExtraA)
+ {
+ // #i123478# shockingly, BitmapBuffer does not free the memory it is controlling
+ // in its destructor, this *has to be done handish*. Doing it here now
+ delete[] pExtraA->mpBits;
+ pExtraA.reset();
+ }
+ else
+ {
+ pSalA->ReleaseBuffer(pA, BitmapAccessMode::Read);
+ }
+
+ pExtraWinSalA.reset();
+
+ if(pExtraRGB)
+ {
+ // #i123478# shockingly, BitmapBuffer does not free the memory it is controlling
+ // in its destructor, this *has to be done by hand*. Doing it here now
+ delete[] pExtraRGB->mpBits;
+ pExtraRGB.reset();
+ }
+ else
+ {
+ pSalRGB->ReleaseBuffer(pRGB, BitmapAccessMode::Read);
+ }
+
+ pExtraWinSalRGB.reset();
+
+ return pRetval;
+}
+
+bool WinSalBitmap::Create( HANDLE hBitmap )
+{
+ bool bRet = true;
+
+ mhDDB = static_cast<HBITMAP>( hBitmap );
+
+ if( mhDIB )
+ {
+ PBITMAPINFOHEADER pBIH = static_cast<PBITMAPINFOHEADER>(GlobalLock( mhDIB ));
+
+ maSize = Size( pBIH->biWidth, pBIH->biHeight );
+ mnBitCount = pBIH->biBitCount;
+
+ if( mnBitCount )
+ mnBitCount = ( mnBitCount <= 1 ) ? 1 : ( mnBitCount <= 4 ) ? 4 : ( mnBitCount <= 8 ) ? 8 : 24;
+
+ GlobalUnlock( mhDIB );
+ }
+ else if( mhDDB )
+ {
+ BITMAP aDDBInfo;
+
+ if( GetObjectW( mhDDB, sizeof( aDDBInfo ), &aDDBInfo ) )
+ {
+ maSize = Size( aDDBInfo.bmWidth, aDDBInfo.bmHeight );
+ mnBitCount = aDDBInfo.bmPlanes * aDDBInfo.bmBitsPixel;
+
+ if( mnBitCount )
+ {
+ mnBitCount = ( mnBitCount <= 1 ) ? 1 :
+ ( mnBitCount <= 4 ) ? 4 :
+ ( mnBitCount <= 8 ) ? 8 : 24;
+ }
+ }
+ else
+ {
+ mhDDB = nullptr;
+ bRet = false;
+ }
+ }
+ else
+ bRet = false;
+
+ return bRet;
+}
+
+bool WinSalBitmap::Create(const Size& rSize, vcl::PixelFormat ePixelFormat, const BitmapPalette& rPal)
+{
+ bool bRet = false;
+
+ mhDIB = ImplCreateDIB(rSize, ePixelFormat, rPal);
+
+ if( mhDIB )
+ {
+ maSize = rSize;
+ mnBitCount = vcl::pixelFormatBitCount(ePixelFormat);
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool WinSalBitmap::Create( const SalBitmap& rSSalBitmap )
+{
+ bool bRet = false;
+ const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap);
+
+ if ( rSalBitmap.mhDIB || rSalBitmap.mhDDB )
+ {
+ HANDLE hNewHdl = ImplCopyDIBOrDDB( rSalBitmap.mhDIB ? rSalBitmap.mhDIB : rSalBitmap.mhDDB,
+ rSalBitmap.mhDIB != nullptr );
+
+ if ( hNewHdl )
+ {
+ if( rSalBitmap.mhDIB )
+ mhDIB = static_cast<HGLOBAL>(hNewHdl);
+ else if( rSalBitmap.mhDDB )
+ mhDDB = static_cast<HBITMAP>(hNewHdl);
+
+ maSize = rSalBitmap.maSize;
+ mnBitCount = rSalBitmap.mnBitCount;
+
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+bool WinSalBitmap::Create( const SalBitmap& rSSalBmp, SalGraphics* pSGraphics )
+{
+ bool bRet = false;
+
+ const WinSalBitmap& rSalBmp = static_cast<const WinSalBitmap&>(rSSalBmp);
+ WinSalGraphics* pGraphics = static_cast<WinSalGraphics*>(pSGraphics);
+
+ if( rSalBmp.mhDIB )
+ {
+ PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( rSalBmp.mhDIB ));
+ HDC hDC = pGraphics->getHDC();
+ HBITMAP hNewDDB;
+ BITMAP aDDBInfo;
+ PBYTE pBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize +
+ ImplGetDIBColorCount( rSalBmp.mhDIB ) * sizeof( RGBQUAD );
+
+ if( pBI->bmiHeader.biBitCount == 1 )
+ {
+ hNewDDB = CreateBitmap( pBI->bmiHeader.biWidth, pBI->bmiHeader.biHeight, 1, 1, nullptr );
+
+ if( hNewDDB )
+ SetDIBits( hDC, hNewDDB, 0, pBI->bmiHeader.biHeight, pBits, pBI, DIB_RGB_COLORS );
+ }
+ else
+ hNewDDB = CreateDIBitmap( hDC, &pBI->bmiHeader, CBM_INIT, pBits, pBI, DIB_RGB_COLORS );
+
+ GlobalUnlock( rSalBmp.mhDIB );
+
+ if( hNewDDB && GetObjectW( hNewDDB, sizeof( aDDBInfo ), &aDDBInfo ) )
+ {
+ mhDDB = hNewDDB;
+ maSize = Size( aDDBInfo.bmWidth, aDDBInfo.bmHeight );
+ mnBitCount = aDDBInfo.bmPlanes * aDDBInfo.bmBitsPixel;
+
+ bRet = true;
+ }
+ else if( hNewDDB )
+ DeleteObject( hNewDDB );
+ }
+
+ return bRet;
+}
+
+bool WinSalBitmap::Create(const SalBitmap& rSSalBmp, vcl::PixelFormat eNewPixelFormat)
+{
+ bool bRet = false;
+
+ const WinSalBitmap& rSalBmp = static_cast<const WinSalBitmap&>(rSSalBmp);
+
+ if( rSalBmp.mhDDB )
+ {
+ mhDIB = ImplCreateDIB( rSalBmp.maSize, eNewPixelFormat, BitmapPalette() );
+
+ if( mhDIB )
+ {
+ PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( mhDIB ));
+ const int nLines = static_cast<int>(rSalBmp.maSize.Height());
+ HDC hDC = GetDC( nullptr );
+ PBYTE pBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize +
+ ImplGetDIBColorCount( mhDIB ) * sizeof( RGBQUAD );
+ SalData* pSalData = GetSalData();
+ HPALETTE hOldPal = nullptr;
+
+ if ( pSalData->mhDitherPal )
+ {
+ hOldPal = SelectPalette( hDC, pSalData->mhDitherPal, TRUE );
+ RealizePalette( hDC );
+ }
+
+ if( GetDIBits( hDC, rSalBmp.mhDDB, 0, nLines, pBits, pBI, DIB_RGB_COLORS ) == nLines )
+ {
+ GlobalUnlock( mhDIB );
+ maSize = rSalBmp.maSize;
+ mnBitCount = vcl::pixelFormatBitCount(eNewPixelFormat);
+ bRet = true;
+ }
+ else
+ {
+ GlobalUnlock( mhDIB );
+ GlobalFree( mhDIB );
+ mhDIB = nullptr;
+ }
+
+ if( hOldPal )
+ SelectPalette( hDC, hOldPal, TRUE );
+
+ ReleaseDC( nullptr, hDC );
+ }
+ }
+
+ return bRet;
+}
+
+bool WinSalBitmap::Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& rBitmapCanvas, Size& /*rSize*/, bool bMask )
+{
+ css::uno::Reference< css::beans::XFastPropertySet >
+ xFastPropertySet( rBitmapCanvas, css::uno::UNO_QUERY );
+
+ if( xFastPropertySet ) {
+ css::uno::Sequence< css::uno::Any > args;
+
+ if( xFastPropertySet->getFastPropertyValue(bMask ? 2 : 1) >>= args ) {
+ sal_Int64 aHBmp64;
+
+ if( args[0] >>= aHBmp64 ) {
+ return Create( reinterpret_cast<HANDLE>(aHBmp64) );
+ }
+ }
+ }
+ return false;
+}
+
+sal_uInt16 WinSalBitmap::ImplGetDIBColorCount( HGLOBAL hDIB )
+{
+ sal_uInt16 nColors = 0;
+
+ if( hDIB )
+ {
+ PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( hDIB ));
+
+ if ( pBI->bmiHeader.biSize != sizeof( BITMAPCOREHEADER ) )
+ {
+ if( pBI->bmiHeader.biBitCount <= 8 )
+ {
+ if ( pBI->bmiHeader.biClrUsed )
+ nColors = static_cast<sal_uInt16>(pBI->bmiHeader.biClrUsed);
+ else
+ nColors = 1 << pBI->bmiHeader.biBitCount;
+ }
+ }
+ else if( reinterpret_cast<PBITMAPCOREHEADER>(pBI)->bcBitCount <= 8 )
+ nColors = 1 << reinterpret_cast<PBITMAPCOREHEADER>(pBI)->bcBitCount;
+
+ GlobalUnlock( hDIB );
+ }
+
+ return nColors;
+}
+
+HGLOBAL WinSalBitmap::ImplCreateDIB(const Size& rSize, vcl::PixelFormat ePixelFormat, const BitmapPalette& rPal)
+{
+ HGLOBAL hDIB = nullptr;
+
+ if( rSize.IsEmpty() )
+ return hDIB;
+
+ const auto nBits = vcl::pixelFormatBitCount(ePixelFormat);
+
+ // calculate bitmap size in Bytes
+ const sal_uLong nAlignedWidth4Bytes = AlignedWidth4Bytes(nBits * rSize.Width());
+ const sal_uLong nImageSize = nAlignedWidth4Bytes * rSize.Height();
+ bool bOverflow = (nImageSize / nAlignedWidth4Bytes) != static_cast<sal_uLong>(rSize.Height());
+ if( bOverflow )
+ return hDIB;
+
+ // allocate bitmap memory including header and palette
+ sal_uInt16 nColors = 0;
+ if (ePixelFormat <= vcl::PixelFormat::N8_BPP)
+ nColors = vcl::numberOfColors(ePixelFormat);
+
+ const sal_uLong nHeaderSize = sizeof( BITMAPINFOHEADER ) + nColors * sizeof( RGBQUAD );
+ bOverflow = (nHeaderSize + nImageSize) < nImageSize;
+ if( bOverflow )
+ return hDIB;
+
+ hDIB = GlobalAlloc( GHND, nHeaderSize + nImageSize );
+ if( !hDIB )
+ return hDIB;
+
+ PBITMAPINFO pBI = static_cast<PBITMAPINFO>( GlobalLock( hDIB ) );
+ PBITMAPINFOHEADER pBIH = reinterpret_cast<PBITMAPINFOHEADER>( pBI );
+
+ pBIH->biSize = sizeof( BITMAPINFOHEADER );
+ pBIH->biWidth = rSize.Width();
+ pBIH->biHeight = rSize.Height();
+ pBIH->biPlanes = 1;
+ pBIH->biBitCount = nBits;
+ pBIH->biCompression = BI_RGB;
+ pBIH->biSizeImage = nImageSize;
+ pBIH->biXPelsPerMeter = 0;
+ pBIH->biYPelsPerMeter = 0;
+ pBIH->biClrUsed = 0;
+ pBIH->biClrImportant = 0;
+
+ if( nColors )
+ {
+ // copy the palette entries if any
+ const sal_uInt16 nMinCount = std::min( nColors, rPal.GetEntryCount() );
+ if( nMinCount )
+ memcpy( pBI->bmiColors, rPal.ImplGetColorBuffer(), nMinCount * sizeof(RGBQUAD) );
+ }
+
+ GlobalUnlock( hDIB );
+
+ return hDIB;
+}
+
+HANDLE WinSalBitmap::ImplCopyDIBOrDDB( HANDLE hHdl, bool bDIB )
+{
+ HANDLE hCopy = nullptr;
+
+ if ( bDIB && hHdl )
+ {
+ const sal_uLong nSize = GlobalSize( hHdl );
+
+ if ( (hCopy = GlobalAlloc( GHND, nSize )) != nullptr )
+ {
+ memcpy( GlobalLock( hCopy ), GlobalLock( hHdl ), nSize );
+
+ GlobalUnlock( hCopy );
+ GlobalUnlock( hHdl );
+ }
+ }
+ else if ( hHdl )
+ {
+ BITMAP aBmp;
+
+ // find out size of source bitmap
+ GetObjectW( hHdl, sizeof( aBmp ), &aBmp );
+
+ // create destination bitmap
+ if ( (hCopy = CreateBitmapIndirect( &aBmp )) != nullptr )
+ {
+ HDC hBmpDC = CreateCompatibleDC( nullptr );
+ HBITMAP hBmpOld = static_cast<HBITMAP>(SelectObject( hBmpDC, hHdl ));
+ HDC hCopyDC = CreateCompatibleDC( hBmpDC );
+ HBITMAP hCopyOld = static_cast<HBITMAP>(SelectObject( hCopyDC, hCopy ));
+
+ BitBlt( hCopyDC, 0, 0, aBmp.bmWidth, aBmp.bmHeight, hBmpDC, 0, 0, SRCCOPY );
+
+ SelectObject( hCopyDC, hCopyOld );
+ DeleteDC( hCopyDC );
+
+ SelectObject( hBmpDC, hBmpOld );
+ DeleteDC( hBmpDC );
+ }
+ }
+
+ return hCopy;
+}
+
+BitmapBuffer* WinSalBitmap::AcquireBuffer( BitmapAccessMode /*nMode*/ )
+{
+ std::unique_ptr<BitmapBuffer> pBuffer;
+
+ if( mhDIB )
+ {
+ PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( mhDIB ));
+ PBITMAPINFOHEADER pBIH = &pBI->bmiHeader;
+
+ if( pBIH->biPlanes == 1 )
+ {
+ pBuffer.reset(new BitmapBuffer);
+
+ pBuffer->mnFormat = pBIH->biBitCount == 1 ? ScanlineFormat::N1BitMsbPal :
+ pBIH->biBitCount == 8 ? ScanlineFormat::N8BitPal :
+ pBIH->biBitCount == 24 ? ScanlineFormat::N24BitTcBgr :
+ pBIH->biBitCount == 32 ? ScanlineFormat::N32BitTcMask :
+ ScanlineFormat::NONE;
+
+ if( RemoveScanline( pBuffer->mnFormat ) != ScanlineFormat::NONE )
+ {
+ pBuffer->mnWidth = maSize.Width();
+ pBuffer->mnHeight = maSize.Height();
+ pBuffer->mnScanlineSize = AlignedWidth4Bytes( maSize.Width() * pBIH->biBitCount );
+ pBuffer->mnBitCount = static_cast<sal_uInt16>(pBIH->biBitCount);
+
+ if( pBuffer->mnBitCount <= 8 )
+ {
+ const sal_uInt16 nPalCount = ImplGetDIBColorCount( mhDIB );
+
+ pBuffer->maPalette.SetEntryCount( nPalCount );
+ memcpy( pBuffer->maPalette.ImplGetColorBuffer(), pBI->bmiColors, nPalCount * sizeof( RGBQUAD ) );
+ pBuffer->mpBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize + nPalCount * sizeof( RGBQUAD );
+ }
+ else if( ( pBIH->biBitCount == 16 ) || ( pBIH->biBitCount == 32 ) )
+ {
+ sal_uLong nOffset = 0;
+
+ if( pBIH->biCompression == BI_BITFIELDS )
+ {
+ nOffset = 3 * sizeof( RGBQUAD );
+ ColorMaskElement aRedMask(*reinterpret_cast<UINT32*>(&pBI->bmiColors[ 0 ]));
+ aRedMask.CalcMaskShift();
+ ColorMaskElement aGreenMask(*reinterpret_cast<UINT32*>(&pBI->bmiColors[ 1 ]));
+ aGreenMask.CalcMaskShift();
+ ColorMaskElement aBlueMask(*reinterpret_cast<UINT32*>(&pBI->bmiColors[ 2 ]));
+ aBlueMask.CalcMaskShift();
+ pBuffer->maColorMask = ColorMask(aRedMask, aGreenMask, aBlueMask);
+ }
+ else if( pBIH->biBitCount == 16 )
+ {
+ ColorMaskElement aRedMask(0x00007c00UL);
+ aRedMask.CalcMaskShift();
+ ColorMaskElement aGreenMask(0x000003e0UL);
+ aGreenMask.CalcMaskShift();
+ ColorMaskElement aBlueMask(0x0000001fUL);
+ aBlueMask.CalcMaskShift();
+ pBuffer->maColorMask = ColorMask(aRedMask, aGreenMask, aBlueMask);
+ }
+ else
+ {
+ ColorMaskElement aRedMask(0x00ff0000UL);
+ aRedMask.CalcMaskShift();
+ ColorMaskElement aGreenMask(0x0000ff00UL);
+ aGreenMask.CalcMaskShift();
+ ColorMaskElement aBlueMask(0x000000ffUL);
+ aBlueMask.CalcMaskShift();
+ pBuffer->maColorMask = ColorMask(aRedMask, aGreenMask, aBlueMask);
+ }
+
+ pBuffer->mpBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize + nOffset;
+ }
+ else
+ pBuffer->mpBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize;
+ }
+ else
+ {
+ GlobalUnlock( mhDIB );
+ pBuffer.reset();
+ }
+ }
+ else
+ GlobalUnlock( mhDIB );
+ }
+
+ return pBuffer.release();
+}
+
+void WinSalBitmap::ReleaseBuffer( BitmapBuffer* pBuffer, BitmapAccessMode nMode )
+{
+ if( pBuffer )
+ {
+ if( mhDIB )
+ {
+ if( nMode == BitmapAccessMode::Write && !!pBuffer->maPalette )
+ {
+ PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( mhDIB ));
+ const sal_uInt16 nCount = pBuffer->maPalette.GetEntryCount();
+ const sal_uInt16 nDIBColorCount = ImplGetDIBColorCount( mhDIB );
+ memcpy( pBI->bmiColors, pBuffer->maPalette.ImplGetColorBuffer(), std::min( nDIBColorCount, nCount ) * sizeof( RGBQUAD ) );
+ GlobalUnlock( mhDIB );
+ }
+
+ GlobalUnlock( mhDIB );
+ }
+
+ delete pBuffer;
+ }
+ if( nMode == BitmapAccessMode::Write )
+ InvalidateChecksum();
+}
+
+bool WinSalBitmap::GetSystemData( BitmapSystemData& rData )
+{
+ bool bRet = false;
+ if( mhDIB || mhDDB )
+ {
+ bRet = true;
+ rData.pDIB = mhDIB;
+ const Size& rSize = GetSize ();
+ rData.mnWidth = rSize.Width();
+ rData.mnHeight = rSize.Height();
+ }
+ return bRet;
+}
+
+bool WinSalBitmap::ScalingSupported() const
+{
+ return false;
+}
+
+bool WinSalBitmap::Scale( const double& /*rScaleX*/, const double& /*rScaleY*/, BmpScaleFlag /*nScaleFlag*/ )
+{
+ return false;
+}
+
+bool WinSalBitmap::Replace( const Color& /*rSearchColor*/, const Color& /*rReplaceColor*/, sal_uInt8 /*nTol*/ )
+{
+ return false;
+}
+
+const basegfx::SystemDependentDataHolder* WinSalBitmap::accessSystemDependentDataHolder() const
+{
+ return this;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/gdi/salfont.cxx b/vcl/win/gdi/salfont.cxx
new file mode 100644
index 0000000000..51ddcce741
--- /dev/null
+++ b/vcl/win/gdi/salfont.cxx
@@ -0,0 +1,1377 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <config_folders.h>
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <set>
+#include <string.h>
+#include <string_view>
+#include <svsys.h>
+#include <vector>
+
+#include <dwrite_3.h>
+// Currently, we build with _WIN32_WINNT=0x0601 (Windows 7), which means newer
+// declarations in dwrite_3.h will not be visible.
+#if WINVER < 0x0A00
+# include "dw-extra.h"
+#endif
+
+#include <o3tl/lru_map.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <rtl/bootstrap.hxx>
+#include <rtl/tencinfo.h>
+#include <sal/log.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <tools/helpers.hxx>
+#include <tools/stream.hxx>
+#include <tools/urlobj.hxx>
+#include <unotools/fontcfg.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <comphelper/windowserrorstring.hxx>
+
+#include <font/FontSelectPattern.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <font/PhysicalFontFaceCollection.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <font/fontsubstitution.hxx>
+#include <sft.hxx>
+#include <win/saldata.hxx>
+#include <win/salgdi.h>
+#include <win/winlayout.hxx>
+#include <win/wingdiimpl.hxx>
+#include <impfontcharmap.hxx>
+#include <font/FontMetricData.hxx>
+#include <impglyphitem.hxx>
+
+#if HAVE_FEATURE_SKIA
+#include <vcl/skia/SkiaHelper.hxx>
+#include <skia/win/font.hxx>
+#endif
+
+using namespace vcl;
+
+static FIXED FixedFromDouble( double d )
+{
+ const tools::Long l = static_cast<tools::Long>( d * 65536. );
+ return *reinterpret_cast<FIXED const *>(&l);
+}
+
+static int IntTimes256FromFixed(FIXED f)
+{
+ int nFixedTimes256 = (f.value << 8) + ((f.fract+0x80) >> 8);
+ return nFixedTimes256;
+}
+
+// platform specific font substitution hooks for glyph fallback enhancement
+
+namespace {
+
+class WinPreMatchFontSubstititution
+: public vcl::font::PreMatchFontSubstitution
+{
+public:
+ bool FindFontSubstitute(vcl::font::FontSelectPattern&) const override;
+};
+
+class WinGlyphFallbackSubstititution
+: public vcl::font::GlyphFallbackFontSubstitution
+{
+public:
+ bool FindFontSubstitute(vcl::font::FontSelectPattern&, LogicalFontInstance* pLogicalFont, OUString& rMissingChars) const override;
+};
+
+// does a font face hold the given missing characters?
+bool HasMissingChars(vcl::font::PhysicalFontFace* pFace, OUString& rMissingChars)
+{
+ FontCharMapRef xFontCharMap = pFace->GetFontCharMap();
+
+ // avoid fonts with unknown CMAP subtables for glyph fallback
+ if( !xFontCharMap.is() || xFontCharMap->IsDefaultMap() )
+ return false;
+
+ int nMatchCount = 0;
+ std::vector<sal_UCS4> rRemainingCodes;
+ const sal_Int32 nStrLen = rMissingChars.getLength();
+ sal_Int32 nStrIdx = 0;
+ while (nStrIdx < nStrLen)
+ {
+ const sal_UCS4 uChar = rMissingChars.iterateCodePoints( &nStrIdx );
+ if (xFontCharMap->HasChar(uChar))
+ nMatchCount++;
+ else
+ rRemainingCodes.push_back(uChar);
+ }
+
+ xFontCharMap = nullptr;
+
+ if (nMatchCount > 0)
+ rMissingChars = OUString(rRemainingCodes.data(), rRemainingCodes.size());
+
+ return nMatchCount > 0;
+}
+
+ //used by 2-level font fallback
+ vcl::font::PhysicalFontFamily* findDevFontListByLocale(const vcl::font::PhysicalFontCollection &rFontCollection,
+ const LanguageTag& rLanguageTag )
+ {
+ // get the default font for a specified locale
+ const utl::DefaultFontConfiguration& rDefaults = utl::DefaultFontConfiguration::get();
+ const OUString aDefault = rDefaults.getUserInterfaceFont(rLanguageTag);
+ return rFontCollection.FindFontFamilyByTokenNames(aDefault);
+ }
+}
+
+// These are Win 3.1 bitmap fonts using "FON" font format
+// which is not supported with DirectWrite so let's substitute them
+// with a font that is supported and always available.
+// Based on:
+// https://dxr.mozilla.org/mozilla-esr10/source/gfx/thebes/gfxDWriteFontList.cpp#1057
+const std::map<OUString, OUString> aBitmapFontSubs =
+{
+ { "MS Sans Serif", "Microsoft Sans Serif" },
+ { "MS Serif", "Times New Roman" },
+ { "Small Fonts", "Arial" },
+ { "Courier", "Courier New" },
+ { "Roman", "Times New Roman" },
+ { "Script", "Mistral" }
+};
+
+// TODO: See if Windows have API that we can use here to improve font fallback.
+bool WinPreMatchFontSubstititution::FindFontSubstitute(vcl::font::FontSelectPattern& rFontSelData) const
+{
+ if (rFontSelData.IsMicrosoftSymbolEncoded() || IsOpenSymbol(rFontSelData.maSearchName))
+ return false;
+
+ for (const auto& aSub : aBitmapFontSubs)
+ {
+ if (rFontSelData.maSearchName == GetEnglishSearchFontName(aSub.first))
+ {
+ rFontSelData.maSearchName = aSub.second;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// find a fallback font for missing characters
+// TODO: should stylistic matches be searched and preferred?
+bool WinGlyphFallbackSubstititution::FindFontSubstitute(vcl::font::FontSelectPattern& rFontSelData, LogicalFontInstance* /*pLogicalFont*/, OUString& rMissingChars) const
+{
+ // guess a locale matching to the missing chars
+ LanguageType eLang = rFontSelData.meLanguage;
+ LanguageTag aLanguageTag( eLang);
+
+ // fall back to default UI locale if the font language is inconclusive
+ if( eLang == LANGUAGE_DONTKNOW )
+ aLanguageTag = Application::GetSettings().GetUILanguageTag();
+
+ // first level fallback:
+ // try use the locale specific default fonts defined in VCL.xcu
+ const vcl::font::PhysicalFontCollection* pFontCollection = ImplGetSVData()->maGDIData.mxScreenFontList.get();
+ vcl::font::PhysicalFontFamily* pFontFamily = findDevFontListByLocale(*pFontCollection, aLanguageTag);
+ if( pFontFamily )
+ {
+ vcl::font::PhysicalFontFace* pFace = pFontFamily->FindBestFontFace( rFontSelData );
+ if( HasMissingChars( pFace, rMissingChars ) )
+ {
+ rFontSelData.maSearchName = pFontFamily->GetSearchName();
+ return true;
+ }
+ }
+
+ // are the missing characters symbols?
+ pFontFamily = pFontCollection->FindFontFamilyByAttributes( ImplFontAttrs::Symbol,
+ rFontSelData.GetWeight(),
+ rFontSelData.GetWidthType(),
+ rFontSelData.GetItalic(),
+ rFontSelData.maSearchName );
+ if( pFontFamily )
+ {
+ vcl::font::PhysicalFontFace* pFace = pFontFamily->FindBestFontFace( rFontSelData );
+ if( HasMissingChars( pFace, rMissingChars ) )
+ {
+ rFontSelData.maSearchName = pFontFamily->GetSearchName();
+ return true;
+ }
+ }
+
+ // last level fallback, check each font type face one by one
+ std::unique_ptr<vcl::font::PhysicalFontFaceCollection> pTestFontList = pFontCollection->GetFontFaceCollection();
+ // limit the count of fonts to be checked to prevent hangs
+ static const int MAX_GFBFONT_COUNT = 600;
+ int nTestFontCount = pTestFontList->Count();
+ if( nTestFontCount > MAX_GFBFONT_COUNT )
+ nTestFontCount = MAX_GFBFONT_COUNT;
+
+ bool bFound = false;
+ for( int i = 0; i < nTestFontCount; ++i )
+ {
+ vcl::font::PhysicalFontFace* pFace = pTestFontList->Get( i );
+ bFound = HasMissingChars( pFace, rMissingChars );
+ if( !bFound )
+ continue;
+ rFontSelData.maSearchName = pFace->GetFamilyName();
+ break;
+ }
+
+ return bFound;
+}
+
+namespace {
+
+struct ImplEnumInfo
+{
+ HDC mhDC;
+ vcl::font::PhysicalFontCollection* mpList;
+ OUString* mpName;
+ LOGFONTW* mpLogFont;
+ bool mbPrinter;
+ int mnFontCount;
+};
+
+}
+
+static rtl_TextEncoding ImplCharSetToSal( BYTE nCharSet )
+{
+ rtl_TextEncoding eTextEncoding;
+
+ if ( nCharSet == OEM_CHARSET )
+ {
+ UINT nCP = static_cast<sal_uInt16>(GetOEMCP());
+ switch ( nCP )
+ {
+ // It is unclear why these two (undefined?) code page numbers are
+ // handled specially here:
+ case 1004: eTextEncoding = RTL_TEXTENCODING_MS_1252; break;
+ case 65400: eTextEncoding = RTL_TEXTENCODING_SYMBOL; break;
+ default:
+ eTextEncoding = rtl_getTextEncodingFromWindowsCodePage(nCP);
+ break;
+ }
+ }
+ else
+ {
+ if( nCharSet )
+ eTextEncoding = rtl_getTextEncodingFromWindowsCharset( nCharSet );
+ else
+ eTextEncoding = RTL_TEXTENCODING_UNICODE;
+ }
+
+ return eTextEncoding;
+}
+
+static FontFamily ImplFamilyToSal( BYTE nFamily )
+{
+ switch ( nFamily & 0xF0 )
+ {
+ case FF_DECORATIVE:
+ return FAMILY_DECORATIVE;
+
+ case FF_MODERN:
+ return FAMILY_MODERN;
+
+ case FF_ROMAN:
+ return FAMILY_ROMAN;
+
+ case FF_SCRIPT:
+ return FAMILY_SCRIPT;
+
+ case FF_SWISS:
+ return FAMILY_SWISS;
+
+ default:
+ break;
+ }
+
+ return FAMILY_DONTKNOW;
+}
+
+static BYTE ImplFamilyToWin( FontFamily eFamily )
+{
+ switch ( eFamily )
+ {
+ case FAMILY_DECORATIVE:
+ return FF_DECORATIVE;
+
+ case FAMILY_MODERN:
+ return FF_MODERN;
+
+ case FAMILY_ROMAN:
+ return FF_ROMAN;
+
+ case FAMILY_SCRIPT:
+ return FF_SCRIPT;
+
+ case FAMILY_SWISS:
+ return FF_SWISS;
+
+ case FAMILY_SYSTEM:
+ return FF_SWISS;
+
+ default:
+ break;
+ }
+
+ return FF_DONTCARE;
+}
+
+static FontWeight ImplWeightToSal( int nWeight )
+{
+ if ( nWeight <= FW_THIN )
+ return WEIGHT_THIN;
+ else if ( nWeight <= FW_ULTRALIGHT )
+ return WEIGHT_ULTRALIGHT;
+ else if ( nWeight <= FW_LIGHT )
+ return WEIGHT_LIGHT;
+ else if ( nWeight < FW_MEDIUM )
+ return WEIGHT_NORMAL;
+ else if ( nWeight == FW_MEDIUM )
+ return WEIGHT_MEDIUM;
+ else if ( nWeight <= FW_SEMIBOLD )
+ return WEIGHT_SEMIBOLD;
+ else if ( nWeight <= FW_BOLD )
+ return WEIGHT_BOLD;
+ else if ( nWeight <= FW_ULTRABOLD )
+ return WEIGHT_ULTRABOLD;
+ else
+ return WEIGHT_BLACK;
+}
+
+static int ImplWeightToWin( FontWeight eWeight )
+{
+ switch ( eWeight )
+ {
+ case WEIGHT_THIN:
+ return FW_THIN;
+
+ case WEIGHT_ULTRALIGHT:
+ return FW_ULTRALIGHT;
+
+ case WEIGHT_LIGHT:
+ return FW_LIGHT;
+
+ case WEIGHT_SEMILIGHT:
+ case WEIGHT_NORMAL:
+ return FW_NORMAL;
+
+ case WEIGHT_MEDIUM:
+ return FW_MEDIUM;
+
+ case WEIGHT_SEMIBOLD:
+ return FW_SEMIBOLD;
+
+ case WEIGHT_BOLD:
+ return FW_BOLD;
+
+ case WEIGHT_ULTRABOLD:
+ return FW_ULTRABOLD;
+
+ case WEIGHT_BLACK:
+ return FW_BLACK;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static FontPitch ImplLogPitchToSal( BYTE nPitch )
+{
+ if ( nPitch & FIXED_PITCH )
+ return PITCH_FIXED;
+ else
+ return PITCH_VARIABLE;
+}
+
+static FontPitch ImplMetricPitchToSal( BYTE nPitch )
+{
+ // Grrrr! See NT help
+ if ( !(nPitch & TMPF_FIXED_PITCH) )
+ return PITCH_FIXED;
+ else
+ return PITCH_VARIABLE;
+}
+
+static BYTE ImplPitchToWin( FontPitch ePitch )
+{
+ if ( ePitch == PITCH_FIXED )
+ return FIXED_PITCH;
+ else if ( ePitch == PITCH_VARIABLE )
+ return VARIABLE_PITCH;
+ else
+ return DEFAULT_PITCH;
+}
+
+static FontAttributes WinFont2DevFontAttributes( const ENUMLOGFONTEXW& rEnumFont,
+ const NEWTEXTMETRICW& rMetric)
+{
+ FontAttributes aDFA;
+
+ const LOGFONTW rLogFont = rEnumFont.elfLogFont;
+
+ // get font face attributes
+ aDFA.SetFamilyType(ImplFamilyToSal( rLogFont.lfPitchAndFamily ));
+ aDFA.SetWidthType(WIDTH_DONTKNOW);
+ aDFA.SetWeight(ImplWeightToSal( rLogFont.lfWeight ));
+ aDFA.SetItalic((rLogFont.lfItalic) ? ITALIC_NORMAL : ITALIC_NONE);
+ aDFA.SetPitch(ImplLogPitchToSal( rLogFont.lfPitchAndFamily ));
+ aDFA.SetMicrosoftSymbolEncoded(rLogFont.lfCharSet == SYMBOL_CHARSET);
+
+ // get the font face name
+ aDFA.SetFamilyName(OUString(o3tl::toU(rLogFont.lfFaceName)));
+
+ // use the face's style name only if it looks reasonable
+ const wchar_t* pStyleName = rEnumFont.elfStyle;
+ const wchar_t* pEnd = pStyleName + sizeof(rEnumFont.elfStyle)/sizeof(*rEnumFont.elfStyle);
+ const wchar_t* p = pStyleName;
+ for(; *p && (p < pEnd); ++p )
+ if( *p < 0x0020 )
+ break;
+ if( p < pEnd )
+ aDFA.SetStyleName(OUString(o3tl::toU(pStyleName)));
+
+ // heuristics for font quality
+ // - opentypeTT > truetype
+ aDFA.SetQuality( 0 );
+ if( rMetric.tmPitchAndFamily & TMPF_TRUETYPE )
+ aDFA.IncreaseQualityBy( 50 );
+ if( 0 != (rMetric.ntmFlags & (NTM_TT_OPENTYPE | NTM_PS_OPENTYPE)) )
+ aDFA.IncreaseQualityBy( 10 );
+
+ // TODO: add alias names
+ return aDFA;
+}
+
+void ImplSalLogFontToFontW( HDC hDC, const LOGFONTW& rLogFont, Font& rFont )
+{
+ OUString aFontName( o3tl::toU(rLogFont.lfFaceName) );
+ if (!aFontName.isEmpty())
+ {
+ rFont.SetFamilyName( aFontName );
+ rFont.SetCharSet( ImplCharSetToSal( rLogFont.lfCharSet ) );
+ rFont.SetFamily( ImplFamilyToSal( rLogFont.lfPitchAndFamily ) );
+ rFont.SetPitch( ImplLogPitchToSal( rLogFont.lfPitchAndFamily ) );
+ rFont.SetWeight( ImplWeightToSal( rLogFont.lfWeight ) );
+
+ tools::Long nFontHeight = rLogFont.lfHeight;
+ if ( nFontHeight < 0 )
+ nFontHeight = -nFontHeight;
+ tools::Long nDPIY = GetDeviceCaps( hDC, LOGPIXELSY );
+ if( !nDPIY )
+ nDPIY = 600;
+ nFontHeight *= 72;
+ nFontHeight += nDPIY/2;
+ nFontHeight /= nDPIY;
+ rFont.SetFontSize( Size( 0, nFontHeight ) );
+ rFont.SetOrientation( Degree10(static_cast<sal_Int16>(rLogFont.lfEscapement)) );
+ if ( rLogFont.lfItalic )
+ rFont.SetItalic( ITALIC_NORMAL );
+ else
+ rFont.SetItalic( ITALIC_NONE );
+ if ( rLogFont.lfUnderline )
+ rFont.SetUnderline( LINESTYLE_SINGLE );
+ else
+ rFont.SetUnderline( LINESTYLE_NONE );
+ if ( rLogFont.lfStrikeOut )
+ rFont.SetStrikeout( STRIKEOUT_SINGLE );
+ else
+ rFont.SetStrikeout( STRIKEOUT_NONE );
+ }
+}
+
+WinFontFace::WinFontFace(const ENUMLOGFONTEXW& rEnumFont, const NEWTEXTMETRICW& rMetric)
+: vcl::font::PhysicalFontFace(WinFont2DevFontAttributes(rEnumFont, rMetric)),
+ mnId( 0 ),
+ meWinCharSet(rEnumFont.elfLogFont.lfCharSet),
+ mnPitchAndFamily(rMetric.tmPitchAndFamily),
+ maLogFont(rEnumFont.elfLogFont)
+{
+}
+
+WinFontFace::~WinFontFace()
+{
+}
+
+sal_IntPtr WinFontFace::GetFontId() const
+{
+ return mnId;
+}
+
+rtl::Reference<LogicalFontInstance> WinFontFace::CreateFontInstance(const vcl::font::FontSelectPattern& rFSD) const
+{
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled())
+ return new SkiaWinFontInstance(*this, rFSD);
+#endif
+ return new WinFontInstance(*this, rFSD);
+}
+
+const std::vector<hb_variation_t>&
+WinFontFace::GetVariations(const LogicalFontInstance& rFont) const
+{
+ if (!mxVariations)
+ {
+ mxVariations.emplace();
+ auto pDWFontFace = static_cast<const WinFontInstance&>(rFont).GetDWFontFace();
+ if (pDWFontFace)
+ {
+ sal::systools::COMReference<IDWriteFontFace5> xDWFontFace5;
+ auto hr = pDWFontFace->QueryInterface(__uuidof(IDWriteFontFace5),
+ reinterpret_cast<void**>(&xDWFontFace5));
+ if (SUCCEEDED(hr) && xDWFontFace5->HasVariations())
+ {
+ std::vector<DWRITE_FONT_AXIS_VALUE> aAxisValues(
+ xDWFontFace5->GetFontAxisValueCount());
+ hr = xDWFontFace5->GetFontAxisValues(aAxisValues.data(), aAxisValues.size());
+ if (SUCCEEDED(hr))
+ {
+ mxVariations->reserve(aAxisValues.size());
+ for (auto& rAxisValue : aAxisValues)
+ mxVariations->push_back(
+ { OSL_NETDWORD(rAxisValue.axisTag), rAxisValue.value });
+ }
+ }
+ }
+ }
+
+ return *mxVariations;
+}
+
+namespace
+{
+struct BlobReference
+{
+ hb_blob_t* mpBlob;
+ BlobReference(hb_blob_t* pBlob)
+ : mpBlob(pBlob)
+ {
+ hb_blob_reference(mpBlob);
+ }
+ BlobReference(BlobReference&& other) noexcept
+ : mpBlob(other.mpBlob)
+ {
+ other.mpBlob = nullptr;
+ }
+ BlobReference& operator=(BlobReference&& other)
+ {
+ std::swap(mpBlob, other.mpBlob);
+ return *this;
+ }
+ BlobReference(const BlobReference& other) = delete;
+ BlobReference& operator=(BlobReference& other) = delete;
+ ~BlobReference() { hb_blob_destroy(mpBlob); }
+};
+}
+
+using BlobCacheKey = std::pair<sal_IntPtr, hb_tag_t>;
+
+namespace
+{
+struct BlobCacheKeyHash
+{
+ std::size_t operator()(BlobCacheKey const& rKey) const
+ {
+ std::size_t seed = 0;
+ o3tl::hash_combine(seed, rKey.first);
+ o3tl::hash_combine(seed, rKey.second);
+ return seed;
+ }
+};
+}
+
+hb_blob_t* WinFontFace::GetHbTable(hb_tag_t nTag) const
+{
+ static o3tl::lru_map<BlobCacheKey, BlobReference, BlobCacheKeyHash> gCache(50);
+ BlobCacheKey aCacheKey{ GetFontId(), nTag };
+ auto it = gCache.find(aCacheKey);
+ if (it != gCache.end())
+ {
+ hb_blob_reference(it->second.mpBlob);
+ return it->second.mpBlob;
+ }
+
+ sal_uLong nLength = 0;
+ unsigned char* pBuffer = nullptr;
+
+ HDC hDC(::GetDC(nullptr));
+ HFONT hFont = ::CreateFontIndirectW(&maLogFont);
+ HFONT hOldFont = ::SelectFont(hDC, hFont);
+
+ nLength = ::GetFontData(hDC, OSL_NETDWORD(nTag), 0, nullptr, 0);
+ if (nLength > 0 && nLength != GDI_ERROR)
+ {
+ pBuffer = new unsigned char[nLength];
+ ::GetFontData(hDC, OSL_NETDWORD(nTag), 0, pBuffer, nLength);
+ }
+
+ ::SelectFont(hDC, hOldFont);
+ ::DeleteFont(hFont);
+ ::ReleaseDC(nullptr, hDC);
+
+ hb_blob_t* pBlob = nullptr;
+
+ if (pBuffer)
+ pBlob = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength, HB_MEMORY_MODE_READONLY,
+ pBuffer, [](void* data) { delete[] static_cast<unsigned char*>(data); });
+
+ gCache.insert({ aCacheKey, BlobReference(pBlob) });
+ return pBlob;
+}
+
+void WinSalGraphics::SetTextColor( Color nColor )
+{
+ COLORREF aCol = PALETTERGB( nColor.GetRed(),
+ nColor.GetGreen(),
+ nColor.GetBlue() );
+
+ if( !mbPrinter &&
+ GetSalData()->mhDitherPal &&
+ ImplIsSysColorEntry( nColor ) )
+ {
+ aCol = PALRGB_TO_RGB( aCol );
+ }
+
+ ::SetTextColor( getHDC(), aCol );
+}
+
+static int CALLBACK SalEnumQueryFontProcExW( const LOGFONTW*, const TEXTMETRICW*, DWORD, LPARAM lParam )
+{
+ *reinterpret_cast<bool*>(lParam) = true;
+ return 0;
+}
+
+void ImplGetLogFontFromFontSelect( const vcl::font::FontSelectPattern& rFont,
+ const vcl::font::PhysicalFontFace* pFontFace,
+ LOGFONTW& rLogFont )
+{
+ OUString aName;
+ if (pFontFace)
+ aName = pFontFace->GetFamilyName();
+ else
+ aName = rFont.GetFamilyName().getToken( 0, ';' );
+
+ UINT nNameLen = aName.getLength();
+ if (nNameLen >= LF_FACESIZE)
+ nNameLen = LF_FACESIZE - 1;
+ memcpy( rLogFont.lfFaceName, aName.getStr(), nNameLen*sizeof( wchar_t ) );
+ rLogFont.lfFaceName[nNameLen] = 0;
+
+ if (pFontFace)
+ {
+ const WinFontFace* pWinFontData = static_cast<const WinFontFace*>(pFontFace);
+ rLogFont.lfCharSet = pWinFontData->GetCharSet();
+ rLogFont.lfPitchAndFamily = pWinFontData->GetPitchAndFamily();
+ }
+ else
+ {
+ rLogFont.lfCharSet = rFont.IsMicrosoftSymbolEncoded() ? SYMBOL_CHARSET : DEFAULT_CHARSET;
+ rLogFont.lfPitchAndFamily = ImplPitchToWin( rFont.GetPitch() )
+ | ImplFamilyToWin( rFont.GetFamilyType() );
+ }
+
+ rLogFont.lfWeight = ImplWeightToWin( rFont.GetWeight() );
+ rLogFont.lfHeight = static_cast<LONG>(-rFont.mnHeight);
+ rLogFont.lfWidth = static_cast<LONG>(rFont.mnWidth);
+ rLogFont.lfUnderline = 0;
+ rLogFont.lfStrikeOut = 0;
+ rLogFont.lfItalic = BYTE(rFont.GetItalic() != ITALIC_NONE);
+ rLogFont.lfEscapement = rFont.mnOrientation.get();
+ rLogFont.lfOrientation = rLogFont.lfEscapement;
+ rLogFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
+ rLogFont.lfQuality = DEFAULT_QUALITY;
+ rLogFont.lfOutPrecision = OUT_TT_PRECIS;
+ if ( rFont.mnOrientation )
+ rLogFont.lfClipPrecision |= CLIP_LH_ANGLES;
+
+ // disable antialiasing if requested
+ if ( rFont.mbNonAntialiased )
+ rLogFont.lfQuality = NONANTIALIASED_QUALITY;
+
+}
+
+std::tuple<HFONT,bool,sal_Int32> WinSalGraphics::ImplDoSetFont(HDC hDC, vcl::font::FontSelectPattern const & i_rFont,
+ const vcl::font::PhysicalFontFace * i_pFontFace,
+ HFONT& o_rOldFont)
+{
+ HFONT hNewFont = nullptr;
+
+ LOGFONTW aLogFont;
+ ImplGetLogFontFromFontSelect( i_rFont, i_pFontFace, aLogFont );
+
+ bool bIsCJKVerticalFont = false;
+ // select vertical mode for printing if requested and available
+ if ( i_rFont.mbVertical && mbPrinter )
+ {
+ constexpr size_t nLen = sizeof(aLogFont.lfFaceName) - sizeof(aLogFont.lfFaceName[0]);
+ // vertical fonts start with an '@'
+ memmove( &aLogFont.lfFaceName[1], &aLogFont.lfFaceName[0], nLen );
+ aLogFont.lfFaceName[0] = '@';
+ aLogFont.lfFaceName[LF_FACESIZE - 1] = 0;
+
+ // check availability of vertical mode for this font
+ EnumFontFamiliesExW( getHDC(), &aLogFont, SalEnumQueryFontProcExW,
+ reinterpret_cast<LPARAM>(&bIsCJKVerticalFont), 0 );
+ if( !bIsCJKVerticalFont )
+ {
+ // restore non-vertical name if not vertical mode isn't available
+ memcpy( &aLogFont.lfFaceName[0], &aLogFont.lfFaceName[1], nLen );
+ aLogFont.lfFaceName[LF_FACESIZE - 1] = 0;
+ }
+ }
+
+ hNewFont = ::CreateFontIndirectW( &aLogFont );
+
+ HDC hdcScreen = nullptr;
+ if( mbVirDev )
+ // only required for virtual devices, see below for details
+ hdcScreen = GetDC(nullptr);
+ if( hdcScreen )
+ {
+ // select font into screen hdc first to get an antialiased font
+ // and instantly restore the default font!
+ // see knowledge base article 305290:
+ // "PRB: Fonts Not Drawn Antialiased on Device Context for DirectDraw Surface"
+ SelectFont( hdcScreen, SelectFont( hdcScreen , hNewFont ) );
+ }
+ o_rOldFont = ::SelectFont(hDC, hNewFont);
+
+ TEXTMETRICW aTextMetricW;
+ if (!::GetTextMetricsW(hDC, &aTextMetricW))
+ {
+ // the selected font doesn't work => try a replacement
+ // TODO: use its font fallback instead
+ lstrcpynW( aLogFont.lfFaceName, L"Courier New", 12 );
+ aLogFont.lfPitchAndFamily = FIXED_PITCH;
+ HFONT hNewFont2 = CreateFontIndirectW( &aLogFont );
+ SelectFont(hDC, hNewFont2);
+ DeleteFont( hNewFont );
+ hNewFont = hNewFont2;
+ bIsCJKVerticalFont = false;
+ }
+
+ if( hdcScreen )
+ ::ReleaseDC( nullptr, hdcScreen );
+
+ return std::make_tuple(hNewFont, bIsCJKVerticalFont, static_cast<sal_Int32>(aTextMetricW.tmDescent));
+}
+
+void WinSalGraphics::SetFont(LogicalFontInstance* pFont, int nFallbackLevel)
+{
+ assert(nFallbackLevel >= 0 && nFallbackLevel < MAX_FALLBACK);
+
+ // return early if there is no new font
+ if( !pFont )
+ {
+ if (!mpWinFontEntry[nFallbackLevel].is())
+ return;
+
+ // DeInitGraphics doesn't free the cached fonts, so mhDefFont might be nullptr
+ if (mhDefFont)
+ {
+ ::SelectFont(getHDC(), mhDefFont);
+ mhDefFont = nullptr;
+ }
+
+ // release no longer referenced font handles
+ for( int i = nFallbackLevel; i < MAX_FALLBACK; ++i )
+ mpWinFontEntry[i] = nullptr;
+ return;
+ }
+
+ WinFontInstance *pFontInstance = static_cast<WinFontInstance*>(pFont);
+ mpWinFontEntry[ nFallbackLevel ] = pFontInstance;
+
+ HFONT hOldFont = nullptr;
+ HFONT hNewFont = pFontInstance->GetHFONT();
+ if (!hNewFont)
+ {
+ pFontInstance->SetGraphics(this);
+ hNewFont = pFontInstance->GetHFONT();
+ }
+ hOldFont = ::SelectFont(getHDC(), hNewFont);
+
+ // keep default font
+ if( !mhDefFont )
+ mhDefFont = hOldFont;
+ else
+ {
+ // release no longer referenced font handles
+ for( int i = nFallbackLevel + 1; i < MAX_FALLBACK && mpWinFontEntry[i].is(); ++i )
+ mpWinFontEntry[i] = nullptr;
+ }
+}
+
+void WinSalGraphics::GetFontMetric( FontMetricDataRef& rxFontMetric, int nFallbackLevel )
+{
+ // temporarily change the HDC to the font in the fallback level
+ rtl::Reference<WinFontInstance> pFontInstance = mpWinFontEntry[nFallbackLevel];
+ const HFONT hOldFont = SelectFont(getHDC(), pFontInstance->GetHFONT());
+
+ wchar_t aFaceName[LF_FACESIZE+60];
+ if( GetTextFaceW( getHDC(), SAL_N_ELEMENTS(aFaceName), aFaceName ) )
+ rxFontMetric->SetFamilyName(OUString(o3tl::toU(aFaceName)));
+
+ rxFontMetric->SetMinKashida(pFontInstance->GetKashidaWidth());
+ rxFontMetric->ImplCalcLineSpacing(pFontInstance.get());
+ rxFontMetric->ImplInitBaselines(pFontInstance.get());
+
+ // get the font metric
+ OUTLINETEXTMETRICW aOutlineMetric;
+ const bool bOK = GetOutlineTextMetricsW(getHDC(), sizeof(aOutlineMetric), &aOutlineMetric);
+ // restore the HDC to the font in the base level
+ SelectFont( getHDC(), hOldFont );
+ if( !bOK )
+ return;
+
+ TEXTMETRICW aWinMetric = aOutlineMetric.otmTextMetrics;
+
+ // device independent font attributes
+ rxFontMetric->SetFamilyType(ImplFamilyToSal( aWinMetric.tmPitchAndFamily ));
+ rxFontMetric->SetMicrosoftSymbolEncoded(aWinMetric.tmCharSet == SYMBOL_CHARSET);
+ rxFontMetric->SetWeight(ImplWeightToSal( aWinMetric.tmWeight ));
+ rxFontMetric->SetPitch(ImplMetricPitchToSal( aWinMetric.tmPitchAndFamily ));
+ rxFontMetric->SetItalic(aWinMetric.tmItalic ? ITALIC_NORMAL : ITALIC_NONE);
+ rxFontMetric->SetSlant( 0 );
+
+ // transformation dependent font metrics
+ rxFontMetric->SetWidth(aWinMetric.tmAveCharWidth);
+}
+
+FontCharMapRef WinSalGraphics::GetFontCharMap() const
+{
+ if (!mpWinFontEntry[0])
+ {
+ return FontCharMapRef( new FontCharMap() );
+ }
+ return mpWinFontEntry[0]->GetFontFace()->GetFontCharMap();
+}
+
+bool WinSalGraphics::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ if (!mpWinFontEntry[0])
+ return false;
+ return mpWinFontEntry[0]->GetFontFace()->GetFontCapabilities(rFontCapabilities);
+}
+
+static int CALLBACK SalEnumFontsProcExW( const LOGFONTW* lpelfe,
+ const TEXTMETRICW* lpntme,
+ DWORD nFontType, LPARAM lParam )
+{
+ ENUMLOGFONTEXW const * pLogFont
+ = reinterpret_cast<ENUMLOGFONTEXW const *>(lpelfe);
+ NEWTEXTMETRICEXW const * pMetric
+ = reinterpret_cast<NEWTEXTMETRICEXW const *>(lpntme);
+ ImplEnumInfo* pInfo = reinterpret_cast<ImplEnumInfo*>(lParam);
+ if ( !pInfo->mpName )
+ {
+ // Ignore vertical fonts
+ if ( pLogFont->elfLogFont.lfFaceName[0] != '@' )
+ {
+ OUString aName(o3tl::toU(pLogFont->elfLogFont.lfFaceName));
+ pInfo->mpName = &aName;
+ memcpy(pInfo->mpLogFont->lfFaceName, pLogFont->elfLogFont.lfFaceName, (aName.getLength()+1)*sizeof(wchar_t));
+ pInfo->mpLogFont->lfCharSet = pLogFont->elfLogFont.lfCharSet;
+ EnumFontFamiliesExW(pInfo->mhDC, pInfo->mpLogFont, SalEnumFontsProcExW,
+ reinterpret_cast<LPARAM>(pInfo), 0);
+ pInfo->mpLogFont->lfFaceName[0] = '\0';
+ pInfo->mpLogFont->lfCharSet = DEFAULT_CHARSET;
+ pInfo->mpName = nullptr;
+ }
+ }
+ else
+ {
+ // Ignore non-device fonts on printers.
+ if (pInfo->mbPrinter)
+ {
+ if ((nFontType & RASTER_FONTTYPE) && !(nFontType & DEVICE_FONTTYPE))
+ {
+ SAL_INFO("vcl.fonts", "Unsupported printer font ignored: " << OUString(o3tl::toU(pLogFont->elfLogFont.lfFaceName)));
+ return 1;
+ }
+ }
+ // Only SFNT fonts are supported, ignore anything else.
+ else if (!(nFontType & TRUETYPE_FONTTYPE) &&
+ !(pMetric->ntmTm.ntmFlags & NTM_PS_OPENTYPE) &&
+ !(pMetric->ntmTm.ntmFlags & NTM_TT_OPENTYPE))
+ {
+ SAL_INFO("vcl.fonts", "Unsupported font ignored: " << OUString(o3tl::toU(pLogFont->elfLogFont.lfFaceName)));
+ return 1;
+ }
+
+ rtl::Reference<WinFontFace> pData = new WinFontFace(*pLogFont, pMetric->ntmTm);
+ pData->SetFontId( sal_IntPtr( pInfo->mnFontCount++ ) );
+
+ pInfo->mpList->Add( pData.get() );
+ SAL_INFO("vcl.fonts", "SalEnumFontsProcExW: font added: " << pData->GetFamilyName() << " " << pData->GetStyleName());
+ }
+
+ return 1;
+}
+
+struct TempFontItem
+{
+ OUString maFontResourcePath;
+ TempFontItem* mpNextItem;
+};
+
+static int lcl_AddFontResource(SalData& rSalData, const OUString& rFontFileURL, bool bShared)
+{
+ OUString aFontSystemPath;
+ OSL_VERIFY(!osl::FileBase::getSystemPathFromFileURL(rFontFileURL, aFontSystemPath));
+
+ int nRet = AddFontResourceExW(o3tl::toW(aFontSystemPath.getStr()), FR_PRIVATE, nullptr);
+ SAL_WARN_IF(nRet <= 0, "vcl.fonts", "AddFontResourceExW failed for " << rFontFileURL);
+ if (nRet > 0)
+ {
+ TempFontItem* pNewItem = new TempFontItem;
+ pNewItem->maFontResourcePath = aFontSystemPath;
+ if (bShared)
+ {
+ pNewItem->mpNextItem = rSalData.mpSharedTempFontItem;
+ rSalData.mpSharedTempFontItem = pNewItem;
+ }
+ else
+ {
+ pNewItem->mpNextItem = rSalData.mpOtherTempFontItem;
+ rSalData.mpOtherTempFontItem = pNewItem;
+ }
+ }
+ return nRet;
+}
+
+void ImplReleaseTempFonts(SalData& rSalData, bool bAll)
+{
+ while (TempFontItem* p = rSalData.mpOtherTempFontItem)
+ {
+ RemoveFontResourceExW(o3tl::toW(p->maFontResourcePath.getStr()), FR_PRIVATE, nullptr);
+ rSalData.mpOtherTempFontItem = p->mpNextItem;
+ delete p;
+ }
+
+ if (!bAll)
+ return;
+
+ while (TempFontItem* p = rSalData.mpSharedTempFontItem)
+ {
+ RemoveFontResourceExW(o3tl::toW(p->maFontResourcePath.getStr()), FR_PRIVATE, nullptr);
+ rSalData.mpSharedTempFontItem = p->mpNextItem;
+ delete p;
+ }
+}
+
+static OUString lcl_GetFontFamilyName(std::u16string_view rFontFileURL)
+{
+ // Create temporary file name
+ OUString aTempFileURL;
+ if (osl::File::E_None != osl::File::createTempFile(nullptr, nullptr, &aTempFileURL))
+ return OUString();
+ osl::File::remove(aTempFileURL);
+ OUString aResSystemPath;
+ osl::FileBase::getSystemPathFromFileURL(aTempFileURL, aResSystemPath);
+
+ // Create font resource file (.fot)
+ // There is a limit of 127 characters for the full path passed via lpszFile, so we have to
+ // split the font URL and pass it as two parameters. As a result we can't use
+ // CreateScalableFontResource for renaming, as it now expects the font in the system path.
+ // But it's still good to use it for family name extraction, we're currently after.
+ // BTW: it doesn't help to prefix the lpszFile with \\?\ to support larger paths.
+ // TODO: use TTLoadEmbeddedFont (needs an EOT as input, so we have to add a header to the TTF)
+ // TODO: forward the EOT from the AddTempDevFont call side, if VCL supports it
+ INetURLObject aTTFUrl(rFontFileURL);
+ // GetBase() strips the extension
+ OUString aFilename = aTTFUrl.GetLastName(INetURLObject::DecodeMechanism::WithCharset);
+ if (!CreateScalableFontResourceW(0, o3tl::toW(aResSystemPath.getStr()),
+ o3tl::toW(aFilename.getStr()), o3tl::toW(aTTFUrl.GetPath().getStr())))
+ {
+ sal_uInt32 nError = GetLastError();
+ SAL_WARN("vcl.fonts", "CreateScalableFontResource failed for " << aResSystemPath << " "
+ << aFilename << " " << aTTFUrl.GetPath() << " " << nError);
+ return OUString();
+ }
+
+ // Open and read the font resource file
+ osl::File aFotFile(aTempFileURL);
+ if (osl::FileBase::E_None != aFotFile.open(osl_File_OpenFlag_Read))
+ return OUString();
+
+ sal_uInt64 nBytesRead = 0;
+ char aBuffer[4096];
+ aFotFile.read( aBuffer, sizeof( aBuffer ), nBytesRead );
+ // clean up temporary resource file
+ aFotFile.close();
+ osl::File::remove(aTempFileURL);
+
+ // retrieve font family name from byte offset 0x4F6
+ static const sal_uInt64 nNameOfs = 0x4F6;
+ sal_uInt64 nPos = nNameOfs;
+ for (; (nPos < nBytesRead) && (aBuffer[nPos] != 0); nPos++);
+ if (nPos >= nBytesRead || (nPos == nNameOfs))
+ return OUString();
+
+ return OUString(aBuffer + nNameOfs, nPos - nNameOfs, osl_getThreadTextEncoding());
+}
+
+bool WinSalGraphics::AddTempDevFont(vcl::font::PhysicalFontCollection* pFontCollection,
+ const OUString& rFontFileURL, const OUString& rFontName)
+{
+ OUString aFontFamily = lcl_GetFontFamilyName(rFontFileURL);
+ if (aFontFamily.isEmpty())
+ {
+ SAL_WARN("vcl.fonts", "error extracting font family from " << rFontFileURL);
+ return false;
+ }
+
+ if (rFontName != aFontFamily)
+ {
+ SAL_WARN("vcl.fonts", "font family renaming not implemented; skipping embedded " << rFontName);
+ return false;
+ }
+
+ int nFonts = lcl_AddFontResource(*GetSalData(), rFontFileURL, false);
+ if (nFonts <= 0)
+ return false;
+
+ ImplEnumInfo aInfo;
+ aInfo.mhDC = getHDC();
+ aInfo.mpList = pFontCollection;
+ aInfo.mpName = &aFontFamily;
+ aInfo.mbPrinter = mbPrinter;
+ aInfo.mnFontCount = pFontCollection->Count();
+ const int nExpectedFontCount = aInfo.mnFontCount + nFonts;
+
+ LOGFONTW aLogFont = {};
+ aLogFont.lfCharSet = DEFAULT_CHARSET;
+ aInfo.mpLogFont = &aLogFont;
+
+ // add the font to the PhysicalFontCollection
+ EnumFontFamiliesExW(getHDC(), &aLogFont,
+ SalEnumFontsProcExW, reinterpret_cast<LPARAM>(&aInfo), 0);
+
+ SAL_WARN_IF(nExpectedFontCount != pFontCollection->Count(), "vcl.fonts",
+ "temp font was registered but is not in enumeration: " << rFontFileURL);
+
+ return true;
+}
+
+void WinSalGraphics::GetDevFontList( vcl::font::PhysicalFontCollection* pFontCollection )
+{
+ // make sure all LO shared fonts are registered temporarily
+ static std::once_flag init;
+ std::call_once(init, []()
+ {
+ auto registerFontsIn = [](const OUString& dir) {
+ // collect fonts in font path that could not be registered
+ osl::Directory aFontDir(dir);
+ osl::FileBase::RC rcOSL = aFontDir.open();
+ if (rcOSL == osl::FileBase::E_None)
+ {
+ osl::DirectoryItem aDirItem;
+ SalData* pSalData = GetSalData();
+ assert(pSalData);
+
+ while (aFontDir.getNextItem(aDirItem, 10) == osl::FileBase::E_None)
+ {
+ osl::FileStatus aFileStatus(osl_FileStatus_Mask_FileURL);
+ rcOSL = aDirItem.getFileStatus(aFileStatus);
+ if (rcOSL == osl::FileBase::E_None)
+ lcl_AddFontResource(*pSalData, aFileStatus.getFileURL(), true);
+ }
+ }
+ };
+
+ // determine font path
+ // since we are only interested in fonts that could not be
+ // registered before because of missing administration rights
+ // only the font path of the user installation is needed
+ OUString aPath("$BRAND_BASE_DIR");
+ rtl_bootstrap_expandMacros(&aPath.pData);
+
+ // internal font resources, required for normal operation, like OpenSymbol
+ registerFontsIn(aPath + "/" LIBO_SHARE_RESOURCE_FOLDER "/common/fonts");
+
+ // collect fonts in font path that could not be registered
+ registerFontsIn(aPath + "/" LIBO_SHARE_FOLDER "/fonts/truetype");
+
+ return true;
+ });
+
+ ImplEnumInfo aInfo;
+ aInfo.mhDC = getHDC();
+ aInfo.mpList = pFontCollection;
+ aInfo.mpName = nullptr;
+ aInfo.mbPrinter = mbPrinter;
+ aInfo.mnFontCount = 0;
+
+ LOGFONTW aLogFont = {};
+ aLogFont.lfCharSet = DEFAULT_CHARSET;
+ aInfo.mpLogFont = &aLogFont;
+
+ // fill the PhysicalFontCollection
+ EnumFontFamiliesExW( getHDC(), &aLogFont,
+ SalEnumFontsProcExW, reinterpret_cast<LPARAM>(&aInfo), 0 );
+
+ // set glyph fallback hook
+ static WinGlyphFallbackSubstititution aSubstFallback;
+ static WinPreMatchFontSubstititution aPreMatchFont;
+ pFontCollection->SetFallbackHook( &aSubstFallback );
+ pFontCollection->SetPreMatchHook(&aPreMatchFont);
+}
+
+void WinSalGraphics::ClearDevFontCache()
+{
+ mWinSalGraphicsImplBase->ClearDevFontCache();
+ ImplReleaseTempFonts(*GetSalData(), false);
+}
+
+bool WinFontInstance::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rB2DPolyPoly, bool) const
+{
+ rB2DPolyPoly.clear();
+
+ assert(m_pGraphics);
+ HDC hDC = m_pGraphics->getHDC();
+ const HFONT hOrigFont = static_cast<HFONT>(GetCurrentObject(hDC, OBJ_FONT));
+ const HFONT hFont = GetHFONT();
+ if (hFont != hOrigFont)
+ SelectObject(hDC, hFont);
+
+ const ::comphelper::ScopeGuard aFontRestoreScopeGuard([hFont, hOrigFont, hDC]()
+ { if (hFont != hOrigFont) SelectObject(hDC, hOrigFont); });
+
+ // use unity matrix
+ MAT2 aMat;
+ aMat.eM11 = aMat.eM22 = FixedFromDouble( 1.0 );
+ aMat.eM12 = aMat.eM21 = FixedFromDouble( 0.0 );
+
+ UINT nGGOFlags = GGO_NATIVE;
+ nGGOFlags |= GGO_GLYPH_INDEX;
+
+ GLYPHMETRICS aGlyphMetrics;
+ const DWORD nSize1 = ::GetGlyphOutlineW(hDC, nId, nGGOFlags, &aGlyphMetrics, 0, nullptr, &aMat);
+ if( !nSize1 ) // blank glyphs are ok
+ return true;
+ else if( nSize1 == GDI_ERROR )
+ return false;
+
+ BYTE* pData = new BYTE[ nSize1 ];
+ const DWORD nSize2 = ::GetGlyphOutlineW(hDC, nId, nGGOFlags,
+ &aGlyphMetrics, nSize1, pData, &aMat );
+
+ if( nSize1 != nSize2 )
+ return false;
+
+ // TODO: avoid tools polygon by creating B2DPolygon directly
+ int nPtSize = 512;
+ Point* pPoints = new Point[ nPtSize ];
+ PolyFlags* pFlags = new PolyFlags[ nPtSize ];
+
+ TTPOLYGONHEADER* pHeader = reinterpret_cast<TTPOLYGONHEADER*>(pData);
+ while( reinterpret_cast<BYTE*>(pHeader) < pData+nSize2 )
+ {
+ // only outline data is interesting
+ if( pHeader->dwType != TT_POLYGON_TYPE )
+ break;
+
+ // get start point; next start points are end points
+ // of previous segment
+ sal_uInt16 nPnt = 0;
+
+ tools::Long nX = IntTimes256FromFixed( pHeader->pfxStart.x );
+ tools::Long nY = IntTimes256FromFixed( pHeader->pfxStart.y );
+ pPoints[ nPnt ] = Point( nX, nY );
+ pFlags[ nPnt++ ] = PolyFlags::Normal;
+
+ bool bHasOfflinePoints = false;
+ TTPOLYCURVE* pCurve = reinterpret_cast<TTPOLYCURVE*>( pHeader + 1 );
+ pHeader = reinterpret_cast<TTPOLYGONHEADER*>( reinterpret_cast<BYTE*>(pHeader) + pHeader->cb );
+ while( reinterpret_cast<BYTE*>(pCurve) < reinterpret_cast<BYTE*>(pHeader) )
+ {
+ int nNeededSize = nPnt + 16 + 3 * pCurve->cpfx;
+ if( nPtSize < nNeededSize )
+ {
+ Point* pOldPoints = pPoints;
+ PolyFlags* pOldFlags = pFlags;
+ nPtSize = 2 * nNeededSize;
+ pPoints = new Point[ nPtSize ];
+ pFlags = new PolyFlags[ nPtSize ];
+ for( sal_uInt16 i = 0; i < nPnt; ++i )
+ {
+ pPoints[ i ] = pOldPoints[ i ];
+ pFlags[ i ] = pOldFlags[ i ];
+ }
+ delete[] pOldPoints;
+ delete[] pOldFlags;
+ }
+
+ int i = 0;
+ if( TT_PRIM_LINE == pCurve->wType )
+ {
+ while( i < pCurve->cpfx )
+ {
+ nX = IntTimes256FromFixed( pCurve->apfx[ i ].x );
+ nY = IntTimes256FromFixed( pCurve->apfx[ i ].y );
+ ++i;
+ pPoints[ nPnt ] = Point( nX, nY );
+ pFlags[ nPnt ] = PolyFlags::Normal;
+ ++nPnt;
+ }
+ }
+ else if( TT_PRIM_QSPLINE == pCurve->wType )
+ {
+ bHasOfflinePoints = true;
+ while( i < pCurve->cpfx )
+ {
+ // get control point of quadratic bezier spline
+ nX = IntTimes256FromFixed( pCurve->apfx[ i ].x );
+ nY = IntTimes256FromFixed( pCurve->apfx[ i ].y );
+ ++i;
+ Point aControlP( nX, nY );
+
+ // calculate first cubic control point
+ // P0 = 1/3 * (PBeg + 2 * PQControl)
+ nX = pPoints[ nPnt-1 ].X() + 2 * aControlP.X();
+ nY = pPoints[ nPnt-1 ].Y() + 2 * aControlP.Y();
+ pPoints[ nPnt+0 ] = Point( (2*nX+3)/6, (2*nY+3)/6 );
+ pFlags[ nPnt+0 ] = PolyFlags::Control;
+
+ // calculate endpoint of segment
+ nX = IntTimes256FromFixed( pCurve->apfx[ i ].x );
+ nY = IntTimes256FromFixed( pCurve->apfx[ i ].y );
+
+ if ( i+1 >= pCurve->cpfx )
+ {
+ // endpoint is either last point in segment => advance
+ ++i;
+ }
+ else
+ {
+ // or endpoint is the middle of two control points
+ nX += IntTimes256FromFixed( pCurve->apfx[ i-1 ].x );
+ nY += IntTimes256FromFixed( pCurve->apfx[ i-1 ].y );
+ nX = (nX + 1) / 2;
+ nY = (nY + 1) / 2;
+ // no need to advance, because the current point
+ // is the control point in next bezier spline
+ }
+
+ pPoints[ nPnt+2 ] = Point( nX, nY );
+ pFlags[ nPnt+2 ] = PolyFlags::Normal;
+
+ // calculate second cubic control point
+ // P1 = 1/3 * (PEnd + 2 * PQControl)
+ nX = pPoints[ nPnt+2 ].X() + 2 * aControlP.X();
+ nY = pPoints[ nPnt+2 ].Y() + 2 * aControlP.Y();
+ pPoints[ nPnt+1 ] = Point( (2*nX+3)/6, (2*nY+3)/6 );
+ pFlags[ nPnt+1 ] = PolyFlags::Control;
+
+ nPnt += 3;
+ }
+ }
+
+ // next curve segment
+ pCurve = reinterpret_cast<TTPOLYCURVE*>(&pCurve->apfx[ i ]);
+ }
+
+ // end point is start point for closed contour
+ // disabled, because Polygon class closes the contour itself
+ // pPoints[nPnt++] = pPoints[0];
+ // #i35928#
+ // Added again, but add only when not yet closed
+ if(pPoints[nPnt - 1] != pPoints[0])
+ {
+ if( bHasOfflinePoints )
+ pFlags[nPnt] = pFlags[0];
+
+ pPoints[nPnt++] = pPoints[0];
+ }
+
+ // convert y-coordinates W32 -> VCL
+ for( int i = 0; i < nPnt; ++i )
+ pPoints[i].setY(-pPoints[i].Y());
+
+ // insert into polypolygon
+ tools::Polygon aPoly( nPnt, pPoints, (bHasOfflinePoints ? pFlags : nullptr) );
+ // convert to B2DPolyPolygon
+ // TODO: get rid of the intermediate PolyPolygon
+ rB2DPolyPoly.append( aPoly.getB2DPolygon() );
+ }
+
+ delete[] pPoints;
+ delete[] pFlags;
+
+ delete[] pData;
+
+ // rescaling needed for the tools::PolyPolygon conversion
+ if( rB2DPolyPoly.count() )
+ {
+ const double fFactor(1.0f/256);
+ rB2DPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix(fFactor, fFactor));
+ }
+
+ return true;
+}
+
+IDWriteFontFace* WinFontInstance::GetDWFontFace() const
+{
+ if (!mxDWFontFace)
+ {
+ assert(m_pGraphics);
+ HDC hDC = m_pGraphics->getHDC();
+ const HFONT hOrigFont = static_cast<HFONT>(GetCurrentObject(hDC, OBJ_FONT));
+ const HFONT hFont = GetHFONT();
+ if (hFont != hOrigFont)
+ SelectObject(hDC, hFont);
+
+ const ::comphelper::ScopeGuard aFontRestoreScopeGuard([hFont, hOrigFont, hDC]() {
+ if (hFont != hOrigFont)
+ SelectObject(hDC, hOrigFont);
+ });
+
+ IDWriteGdiInterop* pDWriteGdiInterop;
+ WinSalGraphics::getDWriteFactory(nullptr, &pDWriteGdiInterop);
+
+ HRESULT hr = pDWriteGdiInterop->CreateFontFaceFromHdc(hDC, &mxDWFontFace);
+ if (FAILED(hr))
+ {
+ SAL_WARN("vcl.fonts", "HRESULT 0x" << OUString::number(hr, 16) << ": "
+ << WindowsErrorStringFromHRESULT(hr));
+ mxDWFontFace = nullptr;
+ }
+ }
+
+ return mxDWFontFace;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/win/gdi/salgdi.cxx b/vcl/win/gdi/salgdi.cxx
new file mode 100644
index 0000000000..cb4b500a4a
--- /dev/null
+++ b/vcl/win/gdi/salgdi.cxx
@@ -0,0 +1,1139 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <svsys.h>
+#include <rtl/strbuf.hxx>
+#include <tools/poly.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <comphelper/windowserrorstring.hxx>
+#include <win/wincomp.hxx>
+#include <win/saldata.hxx>
+#include <win/salgdi.h>
+#include <win/salframe.h>
+#include <win/salvd.h>
+#include <win/winlayout.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+#include <salgdiimpl.hxx>
+#include "gdiimpl.hxx"
+
+#include <config_features.h>
+#include <vcl/skia/SkiaHelper.hxx>
+#if HAVE_FEATURE_SKIA
+#include <skia/win/gdiimpl.hxx>
+#endif
+
+
+#define DITHER_PAL_DELTA 51
+#define DITHER_PAL_STEPS 6
+#define DITHER_PAL_COUNT (DITHER_PAL_STEPS*DITHER_PAL_STEPS*DITHER_PAL_STEPS)
+#define DITHER_MAX_SYSCOLOR 16
+#define DITHER_EXTRA_COLORS 1
+
+namespace
+{
+
+struct SysColorEntry
+{
+ DWORD nRGB;
+ SysColorEntry* pNext;
+};
+
+SysColorEntry* pFirstSysColor = nullptr;
+SysColorEntry* pActSysColor = nullptr;
+
+void DeleteSysColorList()
+{
+ SysColorEntry* pEntry = pFirstSysColor;
+ pActSysColor = pFirstSysColor = nullptr;
+
+ while( pEntry )
+ {
+ SysColorEntry* pTmp = pEntry->pNext;
+ delete pEntry;
+ pEntry = pTmp;
+ }
+}
+
+} // namespace
+
+// Blue7
+static PALETTEENTRY aImplExtraColor1 =
+{
+ 0, 184, 255, 0
+};
+
+static PALETTEENTRY aImplSalSysPalEntryAry[ DITHER_MAX_SYSCOLOR ] =
+{
+{ 0, 0, 0, 0 },
+{ 0, 0, 0x80, 0 },
+{ 0, 0x80, 0, 0 },
+{ 0, 0x80, 0x80, 0 },
+{ 0x80, 0, 0, 0 },
+{ 0x80, 0, 0x80, 0 },
+{ 0x80, 0x80, 0, 0 },
+{ 0x80, 0x80, 0x80, 0 },
+{ 0xC0, 0xC0, 0xC0, 0 },
+{ 0, 0, 0xFF, 0 },
+{ 0, 0xFF, 0, 0 },
+{ 0, 0xFF, 0xFF, 0 },
+{ 0xFF, 0, 0, 0 },
+{ 0xFF, 0, 0xFF, 0 },
+{ 0xFF, 0xFF, 0, 0 },
+{ 0xFF, 0xFF, 0xFF, 0 }
+};
+
+// we must create pens with 1-pixel width; otherwise the S3-graphics card
+// map has many paint problems when drawing polygons/polyLines and a
+// complex is set
+#define GSL_PEN_WIDTH 1
+
+void ImplInitSalGDI()
+{
+ SalData* pSalData = GetSalData();
+
+ pSalData->mbResourcesAlreadyFreed = false;
+
+ // init stock brushes
+ pSalData->maStockPenColorAry[0] = PALETTERGB( 0, 0, 0 );
+ pSalData->maStockPenColorAry[1] = PALETTERGB( 0xFF, 0xFF, 0xFF );
+ pSalData->maStockPenColorAry[2] = PALETTERGB( 0xC0, 0xC0, 0xC0 );
+ pSalData->maStockPenColorAry[3] = PALETTERGB( 0x80, 0x80, 0x80 );
+ pSalData->mhStockPenAry[0] = CreatePen( PS_SOLID, GSL_PEN_WIDTH, pSalData->maStockPenColorAry[0] );
+ pSalData->mhStockPenAry[1] = CreatePen( PS_SOLID, GSL_PEN_WIDTH, pSalData->maStockPenColorAry[1] );
+ pSalData->mhStockPenAry[2] = CreatePen( PS_SOLID, GSL_PEN_WIDTH, pSalData->maStockPenColorAry[2] );
+ pSalData->mhStockPenAry[3] = CreatePen( PS_SOLID, GSL_PEN_WIDTH, pSalData->maStockPenColorAry[3] );
+ pSalData->mnStockPenCount = 4;
+
+ pSalData->maStockBrushColorAry[0] = PALETTERGB( 0, 0, 0 );
+ pSalData->maStockBrushColorAry[1] = PALETTERGB( 0xFF, 0xFF, 0xFF );
+ pSalData->maStockBrushColorAry[2] = PALETTERGB( 0xC0, 0xC0, 0xC0 );
+ pSalData->maStockBrushColorAry[3] = PALETTERGB( 0x80, 0x80, 0x80 );
+ pSalData->mhStockBrushAry[0] = CreateSolidBrush( pSalData->maStockBrushColorAry[0] );
+ pSalData->mhStockBrushAry[1] = CreateSolidBrush( pSalData->maStockBrushColorAry[1] );
+ pSalData->mhStockBrushAry[2] = CreateSolidBrush( pSalData->maStockBrushColorAry[2] );
+ pSalData->mhStockBrushAry[3] = CreateSolidBrush( pSalData->maStockBrushColorAry[3] );
+ pSalData->mnStockBrushCount = 4;
+
+ // initialize temporary font lists
+ pSalData->mpSharedTempFontItem = nullptr;
+ pSalData->mpOtherTempFontItem = nullptr;
+
+ // support palettes for 256 color displays
+ HDC hDC = GetDC( nullptr );
+ int nBitsPixel = GetDeviceCaps( hDC, BITSPIXEL );
+ int nPlanes = GetDeviceCaps( hDC, PLANES );
+ int nRasterCaps = GetDeviceCaps( hDC, RASTERCAPS );
+ int nBitCount = nBitsPixel * nPlanes;
+
+ if ( (nBitCount > 8) && (nBitCount < 24) )
+ {
+ // test if we have to dither
+ HDC hMemDC = ::CreateCompatibleDC( hDC );
+ HBITMAP hMemBmp = ::CreateCompatibleBitmap( hDC, 8, 8 );
+ HBITMAP hBmpOld = static_cast<HBITMAP>(::SelectObject( hMemDC, hMemBmp ));
+ HBRUSH hMemBrush = ::CreateSolidBrush( PALETTERGB( 175, 171, 169 ) );
+ HBRUSH hBrushOld = static_cast<HBRUSH>(::SelectObject( hMemDC, hMemBrush ));
+ bool bDither16 = true;
+
+ ::PatBlt( hMemDC, 0, 0, 8, 8, PATCOPY );
+ const COLORREF aCol( ::GetPixel( hMemDC, 0, 0 ) );
+
+ for( int nY = 0; ( nY < 8 ) && bDither16; nY++ )
+ for( int nX = 0; ( nX < 8 ) && bDither16; nX++ )
+ if( ::GetPixel( hMemDC, nX, nY ) != aCol )
+ bDither16 = false;
+
+ ::SelectObject( hMemDC, hBrushOld );
+ ::DeleteObject( hMemBrush );
+ ::SelectObject( hMemDC, hBmpOld );
+ ::DeleteObject( hMemBmp );
+ ::DeleteDC( hMemDC );
+
+ if( bDither16 )
+ {
+ // create DIBPattern for 16Bit dithering
+ tools::Long n;
+
+ pSalData->mhDitherDIB = GlobalAlloc( GMEM_FIXED, sizeof( BITMAPINFOHEADER ) + 192 );
+ pSalData->mpDitherDIB = static_cast<BYTE*>(GlobalLock( pSalData->mhDitherDIB ));
+ pSalData->mpDitherDiff.reset(new tools::Long[ 256 ]);
+ pSalData->mpDitherLow.reset(new BYTE[ 256 ]);
+ pSalData->mpDitherHigh.reset(new BYTE[ 256 ]);
+ pSalData->mpDitherDIBData = pSalData->mpDitherDIB + sizeof( BITMAPINFOHEADER );
+ memset( pSalData->mpDitherDIB, 0, sizeof( BITMAPINFOHEADER ) );
+
+ BITMAPINFOHEADER* pBIH = reinterpret_cast<BITMAPINFOHEADER*>(pSalData->mpDitherDIB);
+
+ pBIH->biSize = sizeof( BITMAPINFOHEADER );
+ pBIH->biWidth = 8;
+ pBIH->biHeight = 8;
+ pBIH->biPlanes = 1;
+ pBIH->biBitCount = 24;
+
+ for( n = 0; n < 256; n++ )
+ pSalData->mpDitherDiff[ n ] = n - ( n & 248L );
+
+ for( n = 0; n < 256; n++ )
+ pSalData->mpDitherLow[ n ] = static_cast<BYTE>( n & 248 );
+
+ for( n = 0; n < 256; n++ )
+ pSalData->mpDitherHigh[ n ] = static_cast<BYTE>(std::min( pSalData->mpDitherLow[ n ] + 8, 255 ));
+ }
+ }
+ else if ( (nRasterCaps & RC_PALETTE) && (nBitCount == 8) )
+ {
+ BYTE nRed, nGreen, nBlue;
+ BYTE nR, nG, nB;
+ PALETTEENTRY* pPalEntry;
+ LOGPALETTE* pLogPal;
+ const sal_uInt16 nDitherPalCount = DITHER_PAL_COUNT;
+ sal_uLong nTotalCount = DITHER_MAX_SYSCOLOR + nDitherPalCount + DITHER_EXTRA_COLORS;
+
+ // create logical palette
+ pLogPal = reinterpret_cast<LOGPALETTE*>(new char[ sizeof( LOGPALETTE ) + ( nTotalCount * sizeof( PALETTEENTRY ) ) ]);
+ pLogPal->palVersion = 0x0300;
+ pLogPal->palNumEntries = static_cast<sal_uInt16>(nTotalCount);
+ pPalEntry = pLogPal->palPalEntry;
+
+ // Standard colors
+ memcpy( pPalEntry, aImplSalSysPalEntryAry, DITHER_MAX_SYSCOLOR * sizeof( PALETTEENTRY ) );
+ pPalEntry += DITHER_MAX_SYSCOLOR;
+
+ // own palette (6/6/6)
+ for( nB=0, nBlue=0; nB < DITHER_PAL_STEPS; nB++, nBlue += DITHER_PAL_DELTA )
+ {
+ for( nG=0, nGreen=0; nG < DITHER_PAL_STEPS; nG++, nGreen += DITHER_PAL_DELTA )
+ {
+ for( nR=0, nRed=0; nR < DITHER_PAL_STEPS; nR++, nRed += DITHER_PAL_DELTA )
+ {
+ pPalEntry->peRed = nRed;
+ pPalEntry->peGreen = nGreen;
+ pPalEntry->peBlue = nBlue;
+ pPalEntry->peFlags = 0;
+ pPalEntry++;
+ }
+ }
+ }
+
+ // insert special 'Blue' as standard drawing color
+ *pPalEntry++ = aImplExtraColor1;
+
+ // create palette
+ pSalData->mhDitherPal = CreatePalette( pLogPal );
+ delete[] reinterpret_cast<char*>(pLogPal);
+
+ if( pSalData->mhDitherPal )
+ {
+ // create DIBPattern for 8Bit dithering
+ tools::Long const nSize = sizeof( BITMAPINFOHEADER ) + ( 256 * sizeof( short ) ) + 64;
+ tools::Long n;
+
+ pSalData->mhDitherDIB = GlobalAlloc( GMEM_FIXED, nSize );
+ pSalData->mpDitherDIB = static_cast<BYTE*>(GlobalLock( pSalData->mhDitherDIB ));
+ pSalData->mpDitherDiff.reset(new tools::Long[ 256 ]);
+ pSalData->mpDitherLow.reset(new BYTE[ 256 ]);
+ pSalData->mpDitherHigh.reset(new BYTE[ 256 ]);
+ pSalData->mpDitherDIBData = pSalData->mpDitherDIB + sizeof( BITMAPINFOHEADER ) + ( 256 * sizeof( short ) );
+ memset( pSalData->mpDitherDIB, 0, sizeof( BITMAPINFOHEADER ) );
+
+ BITMAPINFOHEADER* pBIH = reinterpret_cast<BITMAPINFOHEADER*>(pSalData->mpDitherDIB);
+ short* pColors = reinterpret_cast<short*>( pSalData->mpDitherDIB + sizeof( BITMAPINFOHEADER ) );
+
+ pBIH->biSize = sizeof( BITMAPINFOHEADER );
+ pBIH->biWidth = 8;
+ pBIH->biHeight = 8;
+ pBIH->biPlanes = 1;
+ pBIH->biBitCount = 8;
+
+ for( n = 0; n < nDitherPalCount; n++ )
+ pColors[ n ] = static_cast<short>( n + DITHER_MAX_SYSCOLOR );
+
+ for( n = 0; n < 256; n++ )
+ pSalData->mpDitherDiff[ n ] = n % 51;
+
+ for( n = 0; n < 256; n++ )
+ pSalData->mpDitherLow[ n ] = static_cast<BYTE>( n / 51 );
+
+ for( n = 0; n < 256; n++ )
+ pSalData->mpDitherHigh[ n ] = static_cast<BYTE>(std::min( pSalData->mpDitherLow[ n ] + 1, 5 ));
+ }
+
+ // get system color entries
+ ImplUpdateSysColorEntries();
+ }
+
+ ReleaseDC( nullptr, hDC );
+}
+
+void ImplFreeSalGDI()
+{
+ SalData* pSalData = GetSalData();
+
+ if (pSalData->mbResourcesAlreadyFreed)
+ return;
+
+ // destroy stock objects
+ int i;
+ for ( i = 0; i < pSalData->mnStockPenCount; i++ )
+ DeletePen( pSalData->mhStockPenAry[i] );
+ for ( i = 0; i < pSalData->mnStockBrushCount; i++ )
+ DeleteBrush( pSalData->mhStockBrushAry[i] );
+
+ // delete 50% Brush
+ if ( pSalData->mh50Brush )
+ {
+ DeleteBrush( pSalData->mh50Brush );
+ pSalData->mh50Brush = nullptr;
+ }
+
+ // delete 50% Bitmap
+ if ( pSalData->mh50Bmp )
+ {
+ DeleteBitmap( pSalData->mh50Bmp );
+ pSalData->mh50Bmp = nullptr;
+ }
+
+ ImplClearHDCCache( pSalData );
+
+ // delete Ditherpalette, if existing
+ if ( pSalData->mhDitherPal )
+ {
+ DeleteObject( pSalData->mhDitherPal );
+ pSalData->mhDitherPal = nullptr;
+ }
+
+ // delete buffers for dithering DIB patterns, if necessary
+ if ( pSalData->mhDitherDIB )
+ {
+ GlobalUnlock( pSalData->mhDitherDIB );
+ GlobalFree( pSalData->mhDitherDIB );
+ pSalData->mhDitherDIB = nullptr;
+ pSalData->mpDitherDiff.reset();
+ pSalData->mpDitherLow.reset();
+ pSalData->mpDitherHigh.reset();
+ }
+
+ DeleteSysColorList();
+
+ // delete icon cache
+ SalIcon* pIcon = pSalData->mpFirstIcon;
+ pSalData->mpFirstIcon = nullptr;
+ while( pIcon )
+ {
+ SalIcon* pTmp = pIcon->pNext;
+ DestroyIcon( pIcon->hIcon );
+ DestroyIcon( pIcon->hSmallIcon );
+ delete pIcon;
+ pIcon = pTmp;
+ }
+
+ // delete temporary font list
+ ImplReleaseTempFonts(*pSalData, true);
+
+ pSalData->mbResourcesAlreadyFreed = true;
+}
+
+int ImplIsSysColorEntry( Color nColor )
+{
+ SysColorEntry* pEntry = pFirstSysColor;
+ const DWORD nTestRGB = static_cast<DWORD>(RGB( nColor.GetRed(),
+ nColor.GetGreen(),
+ nColor.GetBlue() ));
+
+ while ( pEntry )
+ {
+ if ( pEntry->nRGB == nTestRGB )
+ return TRUE;
+ pEntry = pEntry->pNext;
+ }
+
+ return FALSE;
+}
+
+static int ImplIsPaletteEntry( BYTE nRed, BYTE nGreen, BYTE nBlue )
+{
+ // dither color?
+ if ( !(nRed % DITHER_PAL_DELTA) && !(nGreen % DITHER_PAL_DELTA) && !(nBlue % DITHER_PAL_DELTA) )
+ return TRUE;
+
+ PALETTEENTRY* pPalEntry = aImplSalSysPalEntryAry;
+
+ // standard palette color?
+ for ( sal_uInt16 i = 0; i < DITHER_MAX_SYSCOLOR; i++, pPalEntry++ )
+ {
+ if( pPalEntry->peRed == nRed && pPalEntry->peGreen == nGreen && pPalEntry->peBlue == nBlue )
+ return TRUE;
+ }
+
+ // extra color?
+ if ( aImplExtraColor1.peRed == nRed &&
+ aImplExtraColor1.peGreen == nGreen &&
+ aImplExtraColor1.peBlue == nBlue )
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void ImplInsertSysColorEntry( int nSysIndex )
+{
+ const DWORD nRGB = GetSysColor( nSysIndex );
+
+ if ( !ImplIsPaletteEntry( GetRValue( nRGB ), GetGValue( nRGB ), GetBValue( nRGB ) ) )
+ {
+ if ( !pFirstSysColor )
+ {
+ pActSysColor = pFirstSysColor = new SysColorEntry;
+ pFirstSysColor->nRGB = nRGB;
+ pFirstSysColor->pNext = nullptr;
+ }
+ else
+ {
+ pActSysColor = pActSysColor->pNext = new SysColorEntry;
+ pActSysColor->nRGB = nRGB;
+ pActSysColor->pNext = nullptr;
+ }
+ }
+}
+
+void ImplUpdateSysColorEntries()
+{
+ DeleteSysColorList();
+
+ // create new sys color list
+ ImplInsertSysColorEntry( COLOR_ACTIVEBORDER );
+ ImplInsertSysColorEntry( COLOR_INACTIVEBORDER );
+ ImplInsertSysColorEntry( COLOR_GRADIENTACTIVECAPTION );
+ ImplInsertSysColorEntry( COLOR_GRADIENTINACTIVECAPTION );
+ ImplInsertSysColorEntry( COLOR_3DFACE );
+ ImplInsertSysColorEntry( COLOR_3DHILIGHT );
+ ImplInsertSysColorEntry( COLOR_3DLIGHT );
+ ImplInsertSysColorEntry( COLOR_3DSHADOW );
+ ImplInsertSysColorEntry( COLOR_3DDKSHADOW );
+ ImplInsertSysColorEntry( COLOR_INFOBK );
+ ImplInsertSysColorEntry( COLOR_INFOTEXT );
+ ImplInsertSysColorEntry( COLOR_BTNTEXT );
+ ImplInsertSysColorEntry( COLOR_WINDOW );
+ ImplInsertSysColorEntry( COLOR_WINDOWTEXT );
+ ImplInsertSysColorEntry( COLOR_HIGHLIGHT );
+ ImplInsertSysColorEntry( COLOR_HIGHLIGHTTEXT );
+ ImplInsertSysColorEntry( COLOR_MENU );
+ ImplInsertSysColorEntry( COLOR_MENUTEXT );
+ ImplInsertSysColorEntry( COLOR_ACTIVECAPTION );
+ ImplInsertSysColorEntry( COLOR_CAPTIONTEXT );
+ ImplInsertSysColorEntry( COLOR_INACTIVECAPTION );
+ ImplInsertSysColorEntry( COLOR_INACTIVECAPTIONTEXT );
+}
+
+void WinSalGraphics::InitGraphics()
+{
+ if (!getHDC())
+ return;
+
+ // calculate the minimal line width for the printer
+ if ( isPrinter() )
+ {
+ int nDPIX = GetDeviceCaps( getHDC(), LOGPIXELSX );
+ if ( nDPIX <= 300 )
+ mnPenWidth = 0;
+ else
+ mnPenWidth = nDPIX/300;
+ }
+
+ ::SetTextAlign( getHDC(), TA_BASELINE | TA_LEFT | TA_NOUPDATECP );
+ ::SetBkMode( getHDC(), TRANSPARENT );
+ ::SetROP2( getHDC(), R2_COPYPEN );
+
+ mpImpl->Init();
+}
+
+void WinSalGraphics::DeInitGraphics()
+{
+ if (!getHDC())
+ return;
+
+ // clear clip region
+ SelectClipRgn( getHDC(), nullptr );
+ // select default objects
+ if ( mhDefPen )
+ {
+ SelectPen( getHDC(), mhDefPen );
+ mhDefPen = nullptr;
+ }
+ if ( mhDefBrush )
+ {
+ SelectBrush( getHDC(), mhDefBrush );
+ mhDefBrush = nullptr;
+ }
+ if ( mhDefFont )
+ {
+ SelectFont( getHDC(), mhDefFont );
+ mhDefFont = nullptr;
+ }
+ setPalette(nullptr);
+
+ mpImpl->DeInit();
+}
+
+void WinSalGraphics::setHDC(HDC aNew)
+{
+ DeInitGraphics();
+ mhLocalDC = aNew;
+ InitGraphics();
+}
+
+HDC ImplGetCachedDC( sal_uLong nID, HBITMAP hBmp )
+{
+ SalData* pSalData = GetSalData();
+ HDCCache* pC = &pSalData->maHDCCache[ nID ];
+
+ if( !pC->mhDC )
+ {
+ HDC hDC = GetDC( nullptr );
+
+ // create new DC with DefaultBitmap
+ pC->mhDC = CreateCompatibleDC( hDC );
+
+ if( pSalData->mhDitherPal )
+ {
+ pC->mhDefPal = SelectPalette( pC->mhDC, pSalData->mhDitherPal, TRUE );
+ RealizePalette( pC->mhDC );
+ }
+
+ pC->mhSelBmp = CreateCompatibleBitmap( hDC, CACHED_HDC_DEFEXT, CACHED_HDC_DEFEXT );
+ pC->mhDefBmp = static_cast<HBITMAP>(SelectObject( pC->mhDC, pC->mhSelBmp ));
+
+ ReleaseDC( nullptr, hDC );
+ }
+
+ if ( hBmp )
+ SelectObject( pC->mhDC, pC->mhActBmp = hBmp );
+ else
+ pC->mhActBmp = nullptr;
+
+ return pC->mhDC;
+}
+
+void ImplReleaseCachedDC( sal_uLong nID )
+{
+ SalData* pSalData = GetSalData();
+ HDCCache* pC = &pSalData->maHDCCache[ nID ];
+
+ if ( pC->mhActBmp )
+ SelectObject( pC->mhDC, pC->mhSelBmp );
+}
+
+void ImplClearHDCCache( SalData* pData )
+{
+ for( sal_uLong i = 0; i < CACHESIZE_HDC; i++ )
+ {
+ HDCCache* pC = &pData->maHDCCache[ i ];
+
+ if( pC->mhDC )
+ {
+ SelectObject( pC->mhDC, pC->mhDefBmp );
+
+ if( pC->mhDefPal )
+ SelectPalette( pC->mhDC, pC->mhDefPal, TRUE );
+
+ DeleteDC( pC->mhDC );
+ DeleteObject( pC->mhSelBmp );
+ }
+ }
+}
+
+std::unique_ptr< CompatibleDC > CompatibleDC::create(SalGraphics &rGraphics, int x, int y, int width, int height)
+{
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled())
+ return std::make_unique< SkiaCompatibleDC >( rGraphics, x, y, width, height );
+#endif
+ return std::unique_ptr< CompatibleDC >( new CompatibleDC( rGraphics, x, y, width, height ));
+}
+
+CompatibleDC::CompatibleDC(SalGraphics &rGraphics, int x, int y, int width, int height, bool disable)
+ : mhBitmap(nullptr)
+ , mpData(nullptr)
+ , maRects(0, 0, width, height, x, y, width, height)
+ , mpImpl(nullptr)
+{
+ WinSalGraphics& rWinGraphics = static_cast<WinSalGraphics&>(rGraphics);
+
+ if( disable )
+ {
+ // we avoid the OpenGL drawing, instead we draw directly to the DC
+ mhCompatibleDC = rWinGraphics.getHDC();
+ return;
+ }
+
+ mpImpl = rWinGraphics.getWinSalGraphicsImplBase();
+ mhCompatibleDC = CreateCompatibleDC(rWinGraphics.getHDC());
+
+ // move the origin so that we always paint at 0,0 - to keep the bitmap
+ // small
+ OffsetViewportOrgEx(mhCompatibleDC, -x, -y, nullptr);
+
+ mhBitmap = WinSalVirtualDevice::ImplCreateVirDevBitmap(mhCompatibleDC, width, height, 32, reinterpret_cast<void **>(&mpData));
+
+ mhOrigBitmap = static_cast<HBITMAP>(SelectObject(mhCompatibleDC, mhBitmap));
+}
+
+CompatibleDC::~CompatibleDC()
+{
+ if (mpImpl)
+ {
+ SelectObject(mhCompatibleDC, mhOrigBitmap);
+ DeleteObject(mhBitmap);
+ DeleteDC(mhCompatibleDC);
+ }
+}
+
+void CompatibleDC::fill(sal_uInt32 color)
+{
+ if (!mpData)
+ return;
+
+ sal_uInt32 *p = mpData;
+ for (int i = maRects.mnSrcWidth * maRects.mnSrcHeight; i > 0; --i)
+ *p++ = color;
+}
+
+WinSalGraphics::WinSalGraphics(WinSalGraphics::Type eType, bool bScreen, HWND hWnd, [[maybe_unused]] SalGeometryProvider *pProvider):
+ mhLocalDC(nullptr),
+ mbPrinter(eType == WinSalGraphics::PRINTER),
+ mbVirDev(eType == WinSalGraphics::VIRTUAL_DEVICE),
+ mbWindow(eType == WinSalGraphics::WINDOW),
+ mbScreen(bScreen),
+ mhWnd(hWnd),
+ mhRegion(nullptr),
+ mhDefPen(nullptr),
+ mhDefBrush(nullptr),
+ mhDefFont(nullptr),
+ mhDefPal(nullptr),
+ mpStdClipRgnData(nullptr),
+ mnPenWidth(GSL_PEN_WIDTH)
+{
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled() && !mbPrinter)
+ {
+ auto const impl = new WinSkiaSalGraphicsImpl(*this, pProvider);
+ mpImpl.reset(impl);
+ mWinSalGraphicsImplBase = impl;
+ }
+ else
+#endif
+ {
+ auto const impl = new WinSalGraphicsImpl(*this);
+ mpImpl.reset(impl);
+ mWinSalGraphicsImplBase = impl;
+ }
+}
+
+WinSalGraphics::~WinSalGraphics()
+{
+ // free obsolete GDI objects
+ ReleaseFonts();
+
+ if ( mhRegion )
+ {
+ DeleteRegion( mhRegion );
+ mhRegion = nullptr;
+ }
+
+ // delete cache data
+ delete [] reinterpret_cast<BYTE*>(mpStdClipRgnData);
+
+ setHDC(nullptr);
+}
+
+SalGraphicsImpl* WinSalGraphics::GetImpl() const
+{
+ return mpImpl.get();
+}
+
+bool WinSalGraphics::isPrinter() const
+{
+ return mbPrinter;
+}
+
+bool WinSalGraphics::isVirtualDevice() const
+{
+ return mbVirDev;
+}
+
+bool WinSalGraphics::isWindow() const
+{
+ return mbWindow;
+}
+
+bool WinSalGraphics::isScreen() const
+{
+ return mbScreen;
+}
+
+HWND WinSalGraphics::gethWnd()
+{
+ return mhWnd;
+}
+
+void WinSalGraphics::setHWND(HWND hWnd)
+{
+ mhWnd = hWnd;
+}
+
+HPALETTE WinSalGraphics::getDefPal() const
+{
+ assert(getHDC() || !mhDefPal);
+ return mhDefPal;
+}
+
+UINT WinSalGraphics::setPalette(HPALETTE hNewPal, BOOL bForceBkgd)
+{
+ UINT res = GDI_ERROR;
+
+ if (!getHDC())
+ {
+ assert(!mhDefPal);
+ return res;
+ }
+
+ if (hNewPal)
+ {
+ HPALETTE hOldPal = SelectPalette(getHDC(), hNewPal, bForceBkgd);
+ if (hOldPal)
+ {
+ if (!mhDefPal)
+ mhDefPal = hOldPal;
+ res = RealizePalette(getHDC());
+ }
+ }
+ else
+ {
+ res = 0;
+ if (mhDefPal)
+ {
+ SelectPalette(getHDC(), mhDefPal, bForceBkgd);
+ mhDefPal = nullptr;
+ }
+ }
+
+ return res;
+}
+
+HRGN WinSalGraphics::getRegion() const
+{
+ return mhRegion;
+}
+
+void WinSalGraphics::GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY )
+{
+ rDPIX = GetDeviceCaps( getHDC(), LOGPIXELSX );
+ rDPIY = GetDeviceCaps( getHDC(), LOGPIXELSY );
+
+ // #111139# this fixes the symptom of div by zero on startup
+ // however, printing will fail most likely as communication with
+ // the printer seems not to work in this case
+ if( !rDPIX || !rDPIY )
+ rDPIX = rDPIY = 600;
+}
+
+void WinSalGraphics::getDWriteFactory(IDWriteFactory** pFactory, IDWriteGdiInterop** pInterop)
+{
+ if (!bDWriteDone)
+ {
+ HRESULT hr = S_OK;
+ hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory),
+ reinterpret_cast<IUnknown**>(&mxDWriteFactory));
+ if (FAILED(hr))
+ {
+ SAL_WARN("vcl.fonts", "HRESULT 0x" << OUString::number(hr, 16) << ": "
+ << WindowsErrorStringFromHRESULT(hr));
+ abort();
+ }
+
+ hr = mxDWriteFactory->GetGdiInterop(&mxDWriteGdiInterop);
+ if (FAILED(hr))
+ {
+ SAL_WARN("vcl.fonts", "HRESULT 0x" << OUString::number(hr, 16) << ": "
+ << WindowsErrorStringFromHRESULT(hr));
+ abort();
+ }
+
+ bDWriteDone = true;
+ }
+
+ if (pFactory)
+ *pFactory = mxDWriteFactory.get();
+ if (pInterop)
+ *pInterop = mxDWriteGdiInterop.get();
+}
+
+sal_uInt16 WinSalGraphics::GetBitCount() const
+{
+ return mpImpl->GetBitCount();
+}
+
+tools::Long WinSalGraphics::GetGraphicsWidth() const
+{
+ return mpImpl->GetGraphicsWidth();
+}
+
+void WinSalGraphics::Flush()
+{
+ mWinSalGraphicsImplBase->Flush();
+}
+
+void WinSalGraphics::ResetClipRegion()
+{
+ mpImpl->ResetClipRegion();
+}
+
+void WinSalGraphics::setClipRegion( const vcl::Region& i_rClip )
+{
+ mpImpl->setClipRegion( i_rClip );
+}
+
+void WinSalGraphics::SetLineColor()
+{
+ mpImpl->SetLineColor();
+}
+
+void WinSalGraphics::SetLineColor( Color nColor )
+{
+ mpImpl->SetLineColor( nColor );
+}
+
+void WinSalGraphics::SetFillColor()
+{
+ mpImpl->SetFillColor();
+}
+
+void WinSalGraphics::SetFillColor( Color nColor )
+{
+ mpImpl->SetFillColor( nColor );
+}
+
+void WinSalGraphics::SetXORMode( bool bSet, bool bInvertOnly )
+{
+ mpImpl->SetXORMode( bSet, bInvertOnly );
+}
+
+void WinSalGraphics::SetROPLineColor( SalROPColor nROPColor )
+{
+ mpImpl->SetROPLineColor( nROPColor );
+}
+
+void WinSalGraphics::SetROPFillColor( SalROPColor nROPColor )
+{
+ mpImpl->SetROPFillColor( nROPColor );
+}
+
+void WinSalGraphics::drawPixel( tools::Long nX, tools::Long nY )
+{
+ mpImpl->drawPixel( nX, nY );
+}
+
+void WinSalGraphics::drawPixel( tools::Long nX, tools::Long nY, Color nColor )
+{
+ mpImpl->drawPixel( nX, nY, nColor );
+}
+
+void WinSalGraphics::drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 )
+{
+ mpImpl->drawLine( nX1, nY1, nX2, nY2 );
+}
+
+void WinSalGraphics::drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ mpImpl->drawRect( nX, nY, nWidth, nHeight );
+}
+
+void WinSalGraphics::drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry )
+{
+ mpImpl->drawPolyLine( nPoints, pPtAry );
+}
+
+void WinSalGraphics::drawPolygon( sal_uInt32 nPoints, const Point* pPtAry )
+{
+ mpImpl->drawPolygon( nPoints, pPtAry );
+}
+
+void WinSalGraphics::drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point** pPtAry )
+{
+ mpImpl->drawPolyPolygon( nPoly, pPoints, pPtAry );
+}
+
+bool WinSalGraphics::drawPolyLineBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry )
+{
+ return mpImpl->drawPolyLineBezier( nPoints, pPtAry, pFlgAry );
+}
+
+bool WinSalGraphics::drawPolygonBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry )
+{
+ return mpImpl->drawPolygonBezier( nPoints, pPtAry, pFlgAry );
+}
+
+bool WinSalGraphics::drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point* const* pPtAry, const PolyFlags* const* pFlgAry )
+{
+ return mpImpl->drawPolyPolygonBezier( nPoly, pPoints, pPtAry, pFlgAry );
+}
+
+bool WinSalGraphics::drawGradient(const tools::PolyPolygon& rPoly, const Gradient& rGradient)
+{
+ return mpImpl->drawGradient(rPoly, rGradient);
+}
+
+bool WinSalGraphics::implDrawGradient(basegfx::B2DPolyPolygon const & rPolyPolygon, SalGradient const & rGradient)
+{
+ return mpImpl->implDrawGradient(rPolyPolygon, rGradient);
+}
+
+static BYTE* ImplSearchEntry( BYTE* pSource, BYTE 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 bool ImplGetBoundingBox( double* nNumb, BYTE* pSource, sal_uLong nSize )
+{
+ bool bRetValue = false;
+ BYTE* pDest = ImplSearchEntry( pSource, reinterpret_cast<BYTE const *>("%%BoundingBox:"), nSize, 14 );
+ if ( pDest )
+ {
+ nNumb[0] = nNumb[1] = nNumb[2] = nNumb[3] = 0;
+ pDest += 14;
+
+ int nSizeLeft = nSize - ( pDest - pSource );
+ if ( nSizeLeft > 100 )
+ nSizeLeft = 100; // only 100 bytes following the bounding box will be checked
+
+ int i;
+ for ( i = 0; ( i < 4 ) && nSizeLeft; i++ )
+ {
+ int nDivision = 1;
+ bool bDivision = false;
+ bool bNegative = false;
+ bool bValid = true;
+
+ while ( ( --nSizeLeft ) && ( ( *pDest == ' ' ) || ( *pDest == 0x9 ) ) ) pDest++;
+ BYTE nByte = *pDest;
+ while ( nSizeLeft && ( 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' ) )
+ nSizeLeft = 1; // error parsing the bounding box values
+ else if ( bValid )
+ {
+ if ( bDivision )
+ nDivision*=10;
+ nNumb[i] *= 10;
+ nNumb[i] += nByte - '0';
+ }
+ break;
+ }
+ nSizeLeft--;
+ nByte = *(++pDest);
+ }
+ if ( bNegative )
+ nNumb[i] = -nNumb[i];
+ if ( bDivision && ( nDivision != 1 ) )
+ nNumb[i] /= nDivision;
+ }
+ if ( i == 4 )
+ bRetValue = true;
+ }
+ return bRetValue;
+}
+
+#define POSTSCRIPT_BUFSIZE 0x4000 // MAXIMUM BUFSIZE EQ 0xFFFF
+
+bool WinSalGraphics::drawEPS( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, void* pPtr, sal_uInt32 nSize )
+{
+ bool bRetValue = false;
+
+ if ( mbPrinter )
+ {
+ int nEscape = POSTSCRIPT_PASSTHROUGH;
+
+ if ( Escape( getHDC(), QUERYESCSUPPORT, sizeof( int ), reinterpret_cast<LPSTR>(&nEscape), nullptr ) )
+ {
+ double nBoundingBox[4];
+
+ if ( ImplGetBoundingBox( nBoundingBox, static_cast<BYTE*>(pPtr), nSize ) )
+ {
+ OStringBuffer aBuf( POSTSCRIPT_BUFSIZE );
+
+ // reserve place for a sal_uInt16
+ aBuf.append( "aa" );
+
+ // #107797# Write out EPS encapsulation header
+
+ // directly taken from the PLRM 3.0, p. 726. Note:
+ // this will definitely cause problems when
+ // recursively creating and embedding PostScript files
+ // in OOo, since we use statically-named variables
+ // here (namely, b4_Inc_state_salWin, dict_count_salWin and
+ // op_count_salWin). Currently, I have no idea on how to
+ // work around that, except from scanning and
+ // interpreting the EPS for unused identifiers.
+
+ // append the real text
+ aBuf.append( "\n\n/b4_Inc_state_salWin save def\n"
+ "/dict_count_salWin countdictstack def\n"
+ "/op_count_salWin count 1 sub def\n"
+ "userdict begin\n"
+ "/showpage {} def\n"
+ "0 setgray 0 setlinecap\n"
+ "1 setlinewidth 0 setlinejoin\n"
+ "10 setmiterlimit [] 0 setdash newpath\n"
+ "/languagelevel where\n"
+ "{\n"
+ " pop languagelevel\n"
+ " 1 ne\n"
+ " {\n"
+ " false setstrokeadjust false setoverprint\n"
+ " } if\n"
+ "} if\n\n" );
+
+ // #i10737# Apply clipping manually
+
+ // Windows seems to ignore any clipping at the HDC,
+ // when followed by a POSTSCRIPT_PASSTHROUGH
+
+ // Check whether we've got a clipping, consisting of
+ // exactly one rect (other cases should be, but aren't
+ // handled currently)
+
+ // TODO: Handle more than one rectangle here (take
+ // care, the buffer can handle only POSTSCRIPT_BUFSIZE
+ // characters!)
+ if ( mhRegion != nullptr &&
+ mpStdClipRgnData != nullptr &&
+ mpClipRgnData == mpStdClipRgnData &&
+ mpClipRgnData->rdh.nCount == 1 )
+ {
+ RECT* pRect = &(mpClipRgnData->rdh.rcBound);
+
+ aBuf.append( "\nnewpath\n"
+ + OString::number(pRect->left) + " " + OString::number(pRect->top)
+ + " moveto\n"
+ + OString::number(pRect->right) + " " + OString::number(pRect->top)
+ + " lineto\n"
+ + OString::number(pRect->right) + " "
+ + OString::number(pRect->bottom) + " lineto\n"
+ + OString::number(pRect->left) + " "
+ + OString::number(pRect->bottom) + " lineto\n"
+ "closepath\n"
+ "clip\n"
+ "newpath\n" );
+ }
+
+ // #107797# Write out buffer
+
+ *reinterpret_cast<sal_uInt16*>(const_cast<char *>(aBuf.getStr())) = static_cast<sal_uInt16>( aBuf.getLength() - 2 );
+ Escape ( getHDC(), nEscape, aBuf.getLength(), aBuf.getStr(), nullptr );
+
+ // #107797# Write out EPS transformation code
+
+ double dM11 = nWidth / ( nBoundingBox[2] - nBoundingBox[0] );
+ double dM22 = nHeight / (nBoundingBox[1] - nBoundingBox[3] );
+ // reserve a sal_uInt16 again
+ aBuf.setLength( 2 );
+ aBuf.append( "\n\n[" + OString::number(dM11) + " 0 0 " + OString::number(dM22) + " "
+ + OString::number(nX - ( dM11 * nBoundingBox[0] )) + " "
+ + OString::number(nY - ( dM22 * nBoundingBox[3] )) + "] concat\n"
+ "%%BeginDocument:\n" );
+ *reinterpret_cast<sal_uInt16*>(const_cast<char *>(aBuf.getStr())) = static_cast<sal_uInt16>( aBuf.getLength() - 2 );
+ Escape ( getHDC(), nEscape, aBuf.getLength(), aBuf.getStr(), nullptr );
+
+ // #107797# Write out actual EPS content
+
+ sal_uLong nToDo = nSize;
+ sal_uLong nDoNow;
+ while ( nToDo )
+ {
+ nDoNow = nToDo;
+ if ( nToDo > POSTSCRIPT_BUFSIZE - 2 )
+ nDoNow = POSTSCRIPT_BUFSIZE - 2;
+ // the following is based on the string buffer allocation
+ // of size POSTSCRIPT_BUFSIZE at construction time of aBuf
+ *reinterpret_cast<sal_uInt16*>(const_cast<char *>(aBuf.getStr())) = static_cast<sal_uInt16>(nDoNow);
+ memcpy( const_cast<char *>(aBuf.getStr() + 2), static_cast<BYTE*>(pPtr) + nSize - nToDo, nDoNow );
+ sal_uLong nResult = Escape ( getHDC(), nEscape, nDoNow + 2, aBuf.getStr(), nullptr );
+ if (!nResult )
+ break;
+ nToDo -= nResult;
+ }
+
+ // #107797# Write out EPS encapsulation footer
+
+ // reserve a sal_uInt16 again
+ aBuf.setLength( 2 );
+ aBuf.append( "%%EndDocument\n"
+ "count op_count_salWin sub {pop} repeat\n"
+ "countdictstack dict_count_salWin sub {end} repeat\n"
+ "b4_Inc_state_salWin restore\n\n" );
+ *reinterpret_cast<sal_uInt16*>(const_cast<char *>(aBuf.getStr())) = static_cast<sal_uInt16>( aBuf.getLength() - 2 );
+ Escape ( getHDC(), nEscape, aBuf.getLength(), aBuf.getStr(), nullptr );
+ bRetValue = true;
+ }
+ }
+ }
+
+ return bRetValue;
+}
+
+SystemGraphicsData WinSalGraphics::GetGraphicsData() const
+{
+ SystemGraphicsData aRes;
+ aRes.nSize = sizeof(aRes);
+ aRes.hDC = getHDC();
+ return aRes;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/gdi/salgdi2.cxx b/vcl/win/gdi/salgdi2.cxx
new file mode 100644
index 0000000000..409fcc74bd
--- /dev/null
+++ b/vcl/win/gdi/salgdi2.cxx
@@ -0,0 +1,240 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <string.h>
+#include <stdlib.h>
+
+#include <svsys.h>
+
+#include <win/wincomp.hxx>
+#include <win/salbmp.h>
+#include <win/saldata.hxx>
+#include <win/salids.hrc>
+#include <win/salgdi.h>
+#include <win/salframe.h>
+
+#include <vcl/BitmapAccessMode.hxx>
+#include <vcl/BitmapBuffer.hxx>
+#include <vcl/BitmapPalette.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/Scanline.hxx>
+#include <salgdiimpl.hxx>
+
+#include <config_features.h>
+#if HAVE_FEATURE_SKIA
+#include <skia/win/gdiimpl.hxx>
+#include <skia/salbmp.hxx>
+#endif
+
+
+bool WinSalGraphics::supportsOperation( OutDevSupportType eType ) const
+{
+ return mpImpl->supportsOperation(eType);
+}
+
+void WinSalGraphics::copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics )
+{
+ mpImpl->copyBits( rPosAry, pSrcGraphics );
+}
+
+void WinSalGraphics::copyArea( tools::Long nDestX, tools::Long nDestY,
+ tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight,
+ bool bWindowInvalidate )
+{
+ mpImpl->copyArea( nDestX, nDestY, nSrcX, nSrcY,
+ nSrcWidth, nSrcHeight, bWindowInvalidate );
+}
+
+namespace
+{
+
+class ColorScanlineConverter
+{
+public:
+ ScanlineFormat meSourceFormat;
+
+ int mnComponentSize;
+ int mnComponentExchangeIndex;
+
+ tools::Long mnScanlineSize;
+
+ ColorScanlineConverter(ScanlineFormat eSourceFormat, int nComponentSize, tools::Long nScanlineSize)
+ : meSourceFormat(eSourceFormat)
+ , mnComponentSize(nComponentSize)
+ , mnComponentExchangeIndex(0)
+ , mnScanlineSize(nScanlineSize)
+ {
+ if (meSourceFormat == ScanlineFormat::N32BitTcAbgr ||
+ meSourceFormat == ScanlineFormat::N32BitTcArgb)
+ {
+ mnComponentExchangeIndex = 1;
+ }
+ }
+
+ void convertScanline(sal_uInt8* pSource, sal_uInt8* pDestination)
+ {
+ for (tools::Long x = 0; x < mnScanlineSize; x += mnComponentSize)
+ {
+ for (int i = 0; i < mnComponentSize; ++i)
+ {
+ pDestination[x + i] = pSource[x + i];
+ }
+ pDestination[x + mnComponentExchangeIndex + 0] = pSource[x + mnComponentExchangeIndex + 2];
+ pDestination[x + mnComponentExchangeIndex + 2] = pSource[x + mnComponentExchangeIndex + 0];
+ }
+ }
+};
+
+void convertToWinSalBitmap(SalBitmap& rSalBitmap, WinSalBitmap& rWinSalBitmap)
+{
+ BitmapPalette aBitmapPalette;
+#if HAVE_FEATURE_SKIA
+ if(SkiaSalBitmap* pSkiaSalBitmap = dynamic_cast<SkiaSalBitmap*>(&rSalBitmap))
+ aBitmapPalette = pSkiaSalBitmap->Palette();
+#endif
+
+ BitmapBuffer* pRead = rSalBitmap.AcquireBuffer(BitmapAccessMode::Read);
+
+ rWinSalBitmap.Create(rSalBitmap.GetSize(), vcl::bitDepthToPixelFormat(rSalBitmap.GetBitCount()), aBitmapPalette);
+ BitmapBuffer* pWrite = rWinSalBitmap.AcquireBuffer(BitmapAccessMode::Write);
+
+ sal_uInt8* pSource(pRead->mpBits);
+ sal_uInt8* pDestination(pWrite->mpBits);
+ tools::Long readRowChange = pRead->mnScanlineSize;
+ if(pRead->mnFormat & ScanlineFormat::TopDown)
+ {
+ pSource += pRead->mnScanlineSize * (pRead->mnHeight - 1);
+ readRowChange = -readRowChange;
+ }
+
+ std::unique_ptr<ColorScanlineConverter> pConverter;
+
+ if (RemoveScanline(pRead->mnFormat) == ScanlineFormat::N24BitTcRgb)
+ pConverter.reset(new ColorScanlineConverter(ScanlineFormat::N24BitTcRgb,
+ 3, pRead->mnScanlineSize));
+ else if (RemoveScanline(pRead->mnFormat) == ScanlineFormat::N32BitTcRgba)
+ pConverter.reset(new ColorScanlineConverter(ScanlineFormat::N32BitTcRgba,
+ 4, pRead->mnScanlineSize));
+ if (pConverter)
+ {
+ for (tools::Long y = 0; y < pRead->mnHeight; y++)
+ {
+ pConverter->convertScanline(pSource, pDestination);
+ pSource += readRowChange;
+ pDestination += pWrite->mnScanlineSize;
+ }
+ }
+ else
+ {
+ for (tools::Long y = 0; y < pRead->mnHeight; y++)
+ {
+ memcpy(pDestination, pSource, pRead->mnScanlineSize);
+ pSource += readRowChange;
+ pDestination += pWrite->mnScanlineSize;
+ }
+ }
+ rWinSalBitmap.ReleaseBuffer(pWrite, BitmapAccessMode::Write);
+
+ rSalBitmap.ReleaseBuffer(pRead, BitmapAccessMode::Read);
+}
+
+} // end anonymous namespace
+
+void WinSalGraphics::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
+{
+ if (dynamic_cast<const WinSalBitmap*>(&rSalBitmap) == nullptr
+#if HAVE_FEATURE_SKIA
+ && dynamic_cast<WinSkiaSalGraphicsImpl*>(mpImpl.get()) == nullptr
+#endif
+ )
+ {
+ std::unique_ptr<WinSalBitmap> pWinSalBitmap(new WinSalBitmap());
+ SalBitmap& rConstBitmap = const_cast<SalBitmap&>(rSalBitmap);
+ convertToWinSalBitmap(rConstBitmap, *pWinSalBitmap);
+ mpImpl->drawBitmap(rPosAry, *pWinSalBitmap);
+ }
+ else
+ {
+ mpImpl->drawBitmap(rPosAry, rSalBitmap);
+ }
+}
+
+void WinSalGraphics::drawBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSSalBitmap,
+ const SalBitmap& rSTransparentBitmap )
+{
+ if (dynamic_cast<const WinSalBitmap*>(&rSSalBitmap) == nullptr
+#if HAVE_FEATURE_SKIA
+ && dynamic_cast<WinSkiaSalGraphicsImpl*>(mpImpl.get()) == nullptr
+#endif
+ )
+ {
+ std::unique_ptr<WinSalBitmap> pWinSalBitmap(new WinSalBitmap());
+ SalBitmap& rConstBitmap = const_cast<SalBitmap&>(rSSalBitmap);
+ convertToWinSalBitmap(rConstBitmap, *pWinSalBitmap);
+
+
+ std::unique_ptr<WinSalBitmap> pWinTransparentSalBitmap(new WinSalBitmap());
+ SalBitmap& rConstTransparentBitmap = const_cast<SalBitmap&>(rSTransparentBitmap);
+ convertToWinSalBitmap(rConstTransparentBitmap, *pWinTransparentSalBitmap);
+
+ mpImpl->drawBitmap(rPosAry, *pWinSalBitmap, *pWinTransparentSalBitmap);
+ }
+ else
+ {
+ mpImpl->drawBitmap(rPosAry, rSSalBitmap, rSTransparentBitmap);
+ }
+}
+
+bool WinSalGraphics::drawAlphaRect( tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, sal_uInt8 nTransparency )
+{
+ return mpImpl->drawAlphaRect( nX, nY, nWidth, nHeight, nTransparency );
+}
+
+void WinSalGraphics::drawMask( const SalTwoRect& rPosAry,
+ const SalBitmap& rSSalBitmap,
+ Color nMaskColor )
+{
+ mpImpl->drawMask( rPosAry, rSSalBitmap, nMaskColor );
+}
+
+std::shared_ptr<SalBitmap> WinSalGraphics::getBitmap( tools::Long nX, tools::Long nY, tools::Long nDX, tools::Long nDY )
+{
+ return mpImpl->getBitmap( nX, nY, nDX, nDY );
+}
+
+Color WinSalGraphics::getPixel( tools::Long nX, tools::Long nY )
+{
+ return mpImpl->getPixel( nX, nY );
+}
+
+void WinSalGraphics::invert( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, SalInvert nFlags )
+{
+ mpImpl->invert( nX, nY, nWidth, nHeight, nFlags );
+}
+
+void WinSalGraphics::invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nSalFlags )
+{
+ mpImpl->invert( nPoints, pPtAry, nSalFlags );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/gdi/salgdi_gdiplus.cxx b/vcl/win/gdi/salgdi_gdiplus.cxx
new file mode 100644
index 0000000000..198f755b23
--- /dev/null
+++ b/vcl/win/gdi/salgdi_gdiplus.cxx
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <svsys.h>
+#include <win/wincomp.hxx>
+#include <win/saldata.hxx>
+#include <win/salgdi.h>
+#include <win/salbmp.h>
+
+#include "gdiimpl.hxx"
+
+void WinSalGraphics::drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+ mpImpl->drawPolyPolygon(
+ rObjectToDevice,
+ rPolyPolygon,
+ fTransparency);
+}
+
+bool WinSalGraphics::drawPolyLine(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& rPolygon,
+ double fTransparency,
+ double fLineWidth,
+ const std::vector< double >* pStroke, // MM01
+ basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle,
+ bool bPixelSnapHairline)
+{
+ return mpImpl->drawPolyLine(
+ rObjectToDevice,
+ rPolygon,
+ fTransparency,
+ fLineWidth,
+ pStroke, // MM01
+ eLineJoin,
+ eLineCap,
+ fMiterMinimumAngle,
+ bPixelSnapHairline);
+}
+
+bool WinSalGraphics::blendBitmap(
+ const SalTwoRect& rTR,
+ const SalBitmap& rBmp)
+{
+ return mpImpl->blendBitmap(rTR, rBmp);
+}
+
+bool WinSalGraphics::blendAlphaBitmap(
+ const SalTwoRect& rTR,
+ const SalBitmap& rSrcBmp,
+ const SalBitmap& rMaskBmp,
+ const SalBitmap& rAlphaBmp)
+{
+ return mpImpl->blendAlphaBitmap(rTR, rSrcBmp, rMaskBmp, rAlphaBmp);
+}
+
+bool WinSalGraphics::drawAlphaBitmap(
+ const SalTwoRect& rTR,
+ const SalBitmap& rSrcBitmap,
+ const SalBitmap& rAlphaBmp)
+{
+ return mpImpl->drawAlphaBitmap(rTR, rSrcBitmap, rAlphaBmp);
+}
+
+bool WinSalGraphics::drawTransformedBitmap(
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap,
+ double fAlpha)
+{
+ return mpImpl->drawTransformedBitmap(rNull, rX, rY,
+ rSourceBitmap, pAlphaBitmap, fAlpha);
+}
+
+bool WinSalGraphics::hasFastDrawTransformedBitmap() const
+{
+ return mpImpl->hasFastDrawTransformedBitmap();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/gdi/salnativewidgets-luna.cxx b/vcl/win/gdi/salnativewidgets-luna.cxx
new file mode 100644
index 0000000000..318009acad
--- /dev/null
+++ b/vcl/win/gdi/salnativewidgets-luna.cxx
@@ -0,0 +1,1634 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+// General info:
+// http://msdn.microsoft.com/en-us/library/windows/desktop/hh270423%28v=vs.85%29.aspx
+// http://msdn.microsoft.com/en-us/library/windows/desktop/bb773178%28v=vs.85%29.aspx
+
+// Useful tool to explore the themes & their rendering:
+// http://privat.rejbrand.se/UxExplore.exe
+// (found at http://stackoverflow.com/questions/4009701/windows-visual-themes-gallery-of-parts-and-states/4009712#4009712)
+
+// Theme subclasses:
+// http://msdn.microsoft.com/en-us/library/windows/desktop/bb773218%28v=vs.85%29.aspx
+
+// Drawing in non-client area (general DWM-related info):
+// http://msdn.microsoft.com/en-us/library/windows/desktop/bb688195%28v=vs.85%29.aspx
+
+#include <rtl/ustring.h>
+
+#include <osl/diagnose.h>
+#include <osl/module.h>
+#include <o3tl/char16_t2wchar_t.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <toolbarvalue.hxx>
+#include <menubarvalue.hxx>
+
+#include <win/svsys.h>
+#include <win/salgdi.h>
+#include <win/saldata.hxx>
+#include <win/salframe.h>
+#include <win/scoped_gdi.hxx>
+#include <win/wingdiimpl.hxx>
+
+#include <uxtheme.h>
+#include <vssym32.h>
+
+#include <map>
+#include <string>
+#include <optional>
+#include <ControlCacheKey.hxx>
+
+typedef std::map< std::wstring, HTHEME > ThemeMap;
+static ThemeMap aThemeMap;
+
+/*********************************************************
+ * Initialize XP theming and local stuff
+ *********************************************************/
+void SalData::initNWF()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // the menu bar and the top docking area should have a common background (gradient)
+ pSVData->maNWFData.mbMenuBarDockingAreaCommonBG = true;
+}
+
+// *********************************************************
+// * Release theming handles
+// ********************************************************
+void SalData::deInitNWF()
+{
+ for( auto& rEntry : aThemeMap )
+ CloseThemeData(rEntry.second);
+ aThemeMap.clear();
+}
+
+static HTHEME getThemeHandle(HWND hWnd, LPCWSTR name, WinSalGraphicsImplBase* pGraphicsImpl)
+{
+ if( GetSalData()->mbThemeChanged )
+ {
+ // throw away invalid theme handles
+ SalData::deInitNWF();
+ // throw away native control cache
+ pGraphicsImpl->ClearNativeControlCache();
+ GetSalData()->mbThemeChanged = false;
+ }
+
+ ThemeMap::iterator iter;
+ if( (iter = aThemeMap.find( name )) != aThemeMap.end() )
+ return iter->second;
+ // theme not found -> add it to map
+ HTHEME hTheme = OpenThemeData( hWnd, name );
+ if( hTheme != nullptr )
+ aThemeMap[name] = hTheme;
+ return hTheme;
+}
+
+bool WinSalGraphics::isNativeControlSupported( ControlType nType, ControlPart nPart )
+{
+ HTHEME hTheme = nullptr;
+
+ switch( nType )
+ {
+ case ControlType::Pushbutton:
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle(mhWnd, L"Button", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::Scrollbar:
+ if( nPart == ControlPart::DrawBackgroundHorz || nPart == ControlPart::DrawBackgroundVert )
+ return false; // no background painting needed
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle(mhWnd, L"Scrollbar", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::Combobox:
+ if( nPart == ControlPart::HasBackgroundTexture )
+ return false; // we do not paint the inner part (ie the selection background/focus indication)
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle(mhWnd, L"Edit", mWinSalGraphicsImplBase);
+ else if( nPart == ControlPart::ButtonDown )
+ hTheme = getThemeHandle(mhWnd, L"Combobox", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::Spinbox:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle(mhWnd, L"Edit", mWinSalGraphicsImplBase);
+ else if( nPart == ControlPart::AllButtons ||
+ nPart == ControlPart::ButtonUp || nPart == ControlPart::ButtonDown ||
+ nPart == ControlPart::ButtonLeft|| nPart == ControlPart::ButtonRight )
+ hTheme = getThemeHandle(mhWnd, L"Spin", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::SpinButtons:
+ if( nPart == ControlPart::Entire || nPart == ControlPart::AllButtons )
+ hTheme = getThemeHandle(mhWnd, L"Spin", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::Editbox:
+ case ControlType::MultilineEditbox:
+ if( nPart == ControlPart::HasBackgroundTexture )
+ return false; // we do not paint the inner part (ie the selection background/focus indication)
+ //return TRUE;
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle(mhWnd, L"Edit", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::Listbox:
+ if( nPart == ControlPart::HasBackgroundTexture )
+ return false; // we do not paint the inner part (ie the selection background/focus indication)
+ if( nPart == ControlPart::Entire || nPart == ControlPart::ListboxWindow )
+ hTheme = getThemeHandle(mhWnd, L"Listview", mWinSalGraphicsImplBase);
+ else if( nPart == ControlPart::ButtonDown )
+ hTheme = getThemeHandle(mhWnd, L"Combobox", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::TabPane:
+ case ControlType::TabBody:
+ case ControlType::TabItem:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle(mhWnd, L"Tab", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::Toolbar:
+ if( nPart == ControlPart::Entire || nPart == ControlPart::Button )
+ hTheme = getThemeHandle(mhWnd, L"Toolbar", mWinSalGraphicsImplBase);
+ else
+ // use rebar theme for grip and background
+ hTheme = getThemeHandle(mhWnd, L"Rebar", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::Menubar:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle(mhWnd, L"Rebar", mWinSalGraphicsImplBase);
+ else if( GetSalData()->mbThemeMenuSupport )
+ {
+ if( nPart == ControlPart::MenuItem )
+ hTheme = getThemeHandle(mhWnd, L"Menu", mWinSalGraphicsImplBase);
+ }
+ break;
+ case ControlType::MenuPopup:
+ if( GetSalData()->mbThemeMenuSupport )
+ {
+ if( nPart == ControlPart::Entire ||
+ nPart == ControlPart::MenuItem ||
+ nPart == ControlPart::MenuItemCheckMark ||
+ nPart == ControlPart::MenuItemRadioMark ||
+ nPart == ControlPart::Separator )
+ hTheme = getThemeHandle(mhWnd, L"Menu", mWinSalGraphicsImplBase);
+ }
+ break;
+ case ControlType::Progress:
+ case ControlType::LevelBar:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle(mhWnd, L"Progress", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::Slider:
+ if( nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea )
+ hTheme = getThemeHandle(mhWnd, L"Trackbar", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::ListNode:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle(mhWnd, L"TreeView", mWinSalGraphicsImplBase);
+ break;
+ default:
+ hTheme = nullptr;
+ break;
+ }
+
+ return (hTheme != nullptr);
+}
+
+bool WinSalGraphics::hitTestNativeControl( ControlType,
+ ControlPart,
+ const tools::Rectangle&,
+ const Point&,
+ bool& )
+{
+ return false;
+}
+
+static bool ImplDrawTheme( HTHEME hTheme, HDC hDC, int iPart, int iState, RECT rc, const OUString& aStr)
+{
+ HRESULT hr = DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr);
+
+ if( aStr.getLength() )
+ {
+ RECT rcContent;
+ hr = GetThemeBackgroundContentRect( hTheme, hDC, iPart, iState, &rc, &rcContent);
+ hr = DrawThemeText( hTheme, hDC, iPart, iState,
+ o3tl::toW(aStr.getStr()), -1,
+ DT_CENTER | DT_VCENTER | DT_SINGLELINE,
+ 0, &rcContent);
+ }
+ return (hr == S_OK);
+}
+
+// TS_TRUE returns optimal size
+static std::optional<Size> ImplGetThemeSize(HTHEME hTheme, HDC hDC, int iPart, int iState, LPCRECT pRect, THEMESIZE eTS = TS_TRUE)
+{
+ if (SIZE aSz; SUCCEEDED(GetThemePartSize(hTheme, hDC, iPart, iState, pRect, eTS, &aSz)))
+ return Size(aSz.cx, aSz.cy);
+ return {};
+}
+
+static tools::Rectangle ImplGetThemeRect( HTHEME hTheme, HDC hDC, int iPart, int iState, const tools::Rectangle& /* aRect */, THEMESIZE eTS = TS_TRUE )
+{
+ if (const std::optional<Size> oSz = ImplGetThemeSize(hTheme, hDC, iPart, iState, nullptr, eTS))
+ return tools::Rectangle( 0, 0, oSz->Width(), oSz->Height() );
+ else
+ return tools::Rectangle();
+}
+
+// Helper functions
+
+static void ImplConvertSpinbuttonValues( ControlPart nControlPart, const ControlState& rState, const tools::Rectangle& rRect,
+ int* pLunaPart, int *pLunaState, RECT *pRect )
+{
+ if( nControlPart == ControlPart::ButtonDown )
+ {
+ *pLunaPart = SPNP_DOWN;
+ if( rState & ControlState::PRESSED )
+ *pLunaState = DNS_PRESSED;
+ else if( !(rState & ControlState::ENABLED) )
+ *pLunaState = DNS_DISABLED;
+ else if( rState & ControlState::ROLLOVER )
+ *pLunaState = DNS_HOT;
+ else
+ *pLunaState = DNS_NORMAL;
+ }
+ if( nControlPart == ControlPart::ButtonUp )
+ {
+ *pLunaPart = SPNP_UP;
+ if( rState & ControlState::PRESSED )
+ *pLunaState = UPS_PRESSED;
+ else if( !(rState & ControlState::ENABLED) )
+ *pLunaState = UPS_DISABLED;
+ else if( rState & ControlState::ROLLOVER )
+ *pLunaState = UPS_HOT;
+ else
+ *pLunaState = UPS_NORMAL;
+ }
+ if( nControlPart == ControlPart::ButtonRight )
+ {
+ *pLunaPart = SPNP_UPHORZ;
+ if( rState & ControlState::PRESSED )
+ *pLunaState = DNHZS_PRESSED;
+ else if( !(rState & ControlState::ENABLED) )
+ *pLunaState = DNHZS_DISABLED;
+ else if( rState & ControlState::ROLLOVER )
+ *pLunaState = DNHZS_HOT;
+ else
+ *pLunaState = DNHZS_NORMAL;
+ }
+ if( nControlPart == ControlPart::ButtonLeft )
+ {
+ *pLunaPart = SPNP_DOWNHORZ;
+ if( rState & ControlState::PRESSED )
+ *pLunaState = UPHZS_PRESSED;
+ else if( !(rState & ControlState::ENABLED) )
+ *pLunaState = UPHZS_DISABLED;
+ else if( rState & ControlState::ROLLOVER )
+ *pLunaState = UPHZS_HOT;
+ else
+ *pLunaState = UPHZS_NORMAL;
+ }
+
+ pRect->left = rRect.Left();
+ pRect->right = rRect.Right()+1;
+ pRect->top = rRect.Top();
+ pRect->bottom = rRect.Bottom()+1;
+}
+
+/// Draw an own toolbar style on Windows Vista or later, looks better there
+static void impl_drawAeroToolbar( HDC hDC, RECT rc, bool bHorizontal )
+{
+ if ( rc.top == 0 && bHorizontal )
+ {
+ const int GRADIENT_HEIGHT = 32;
+
+ LONG gradient_break = rc.top;
+ LONG gradient_bottom = rc.bottom - 1;
+ GRADIENT_RECT g_rect[1] = { { 0, 1 } };
+
+ // very slow gradient at the top (if we have space for that)
+ if ( gradient_bottom - rc.top > GRADIENT_HEIGHT )
+ {
+ gradient_break = gradient_bottom - GRADIENT_HEIGHT;
+
+ TRIVERTEX vert[2] = {
+ { rc.left, rc.top, 0xff00, 0xff00, 0xff00, 0xff00 },
+ { rc.right, gradient_break, 0xfa00, 0xfa00, 0xfa00, 0xff00 },
+ };
+ GdiGradientFill( hDC, vert, 2, g_rect, 1, GRADIENT_FILL_RECT_V );
+ }
+
+ // gradient at the bottom
+ TRIVERTEX vert[2] = {
+ { rc.left, gradient_break, 0xfa00, 0xfa00, 0xfa00, 0xff00 },
+ { rc.right, gradient_bottom, 0xf000, 0xf000, 0xf000, 0xff00 }
+ };
+ GdiGradientFill( hDC, vert, 2, g_rect, 1, GRADIENT_FILL_RECT_V );
+
+ // and a darker horizontal line under that
+ ScopedSelectedHPEN hPen(hDC, CreatePen(PS_SOLID, 1, RGB( 0xb0, 0xb0, 0xb0)));
+
+ MoveToEx( hDC, rc.left, gradient_bottom, nullptr );
+ LineTo( hDC, rc.right, gradient_bottom );
+ }
+ else
+ {
+ ScopedHBRUSH hbrush(CreateSolidBrush(RGB(0xf0, 0xf0, 0xf0)));
+ FillRect(hDC, &rc, hbrush.get());
+
+ // darker line to distinguish the toolbar and viewshell
+ // it is drawn only for the horizontal toolbars; it did not look well
+ // when done for the vertical ones too
+ if ( bHorizontal )
+ {
+ LONG from_x, from_y, to_x, to_y;
+
+ from_x = rc.left;
+ to_x = rc.right;
+ from_y = to_y = rc.top;
+
+ ScopedSelectedHPEN hPen(hDC, CreatePen(PS_SOLID, 1, RGB( 0xb0, 0xb0, 0xb0)));
+
+ MoveToEx( hDC, from_x, from_y, nullptr );
+ LineTo( hDC, to_x, to_y );
+ }
+ }
+}
+
+static bool implDrawNativeMenuMark(HDC hDC, HTHEME hTheme, RECT rc, ControlPart nPart,
+ ControlState nState, OUString const& aCaption)
+{
+ int iState = (nState & ControlState::ENABLED) ? MCB_NORMAL : MCB_DISABLED;
+ ImplDrawTheme(hTheme, hDC, MENU_POPUPCHECKBACKGROUND, iState, rc, aCaption);
+ if (nPart == ControlPart::MenuItemCheckMark)
+ iState = (nState & ControlState::ENABLED) ? MC_CHECKMARKNORMAL : MC_CHECKMARKDISABLED;
+ else
+ iState = (nState & ControlState::ENABLED) ? MC_BULLETNORMAL : MC_BULLETDISABLED;
+ // tdf#133697: Get true size of mark, to avoid stretching
+ if (auto oSize = ImplGetThemeSize(hTheme, hDC, MENU_POPUPCHECK, iState, &rc))
+ {
+ // center the mark inside the passed rectangle
+ if (const auto dx = (rc.right - rc.left - oSize->Width() + 1) / 2; dx > 0)
+ {
+ rc.left += dx;
+ rc.right = rc.left + oSize->Width();
+ }
+ if (const auto dy = (rc.bottom - rc.top - oSize->Height() + 1) / 2; dy > 0)
+ {
+ rc.top += dy;
+ rc.bottom = rc.top + oSize->Height();
+ }
+ }
+ return ImplDrawTheme(hTheme, hDC, MENU_POPUPCHECK, iState, rc, aCaption);
+}
+
+bool UseDarkMode()
+{
+ static bool bOSSupportsDarkMode = OSSupportsDarkMode();
+ if (!bOSSupportsDarkMode)
+ return false;
+
+ bool bRet(false);
+ switch (MiscSettings::GetDarkMode())
+ {
+ case 0: // auto
+ default:
+ {
+ HINSTANCE hUxthemeLib = LoadLibraryExW(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
+ if (!hUxthemeLib)
+ return false;
+
+ typedef bool(WINAPI* ShouldAppsUseDarkMode_t)();
+ if (auto ShouldAppsUseDarkMode = reinterpret_cast<ShouldAppsUseDarkMode_t>(GetProcAddress(hUxthemeLib, MAKEINTRESOURCEA(132))))
+ bRet = ShouldAppsUseDarkMode();
+
+ FreeLibrary(hUxthemeLib);
+ break;
+ }
+ case 1: // light
+ bRet = false;
+ break;
+ case 2: // dark
+ bRet = true;
+ break;
+ }
+
+ return bRet;
+}
+
+static bool ImplDrawNativeControl( HDC hDC, HTHEME hTheme, RECT rc,
+ ControlType nType,
+ ControlPart nPart,
+ ControlState nState,
+ const ImplControlValue& aValue,
+ OUString const & aCaption,
+ bool bUseDarkMode )
+{
+ // a listbox dropdown is actually a combobox dropdown
+ if( nType == ControlType::Listbox )
+ if( nPart == ControlPart::ButtonDown )
+ nType = ControlType::Combobox;
+
+ // draw entire combobox as a large edit box
+ if( nType == ControlType::Combobox )
+ if( nPart == ControlPart::Entire )
+ nType = ControlType::Editbox;
+
+ // draw entire spinbox as a large edit box
+ if( nType == ControlType::Spinbox )
+ if( nPart == ControlPart::Entire )
+ nType = ControlType::Editbox;
+
+ int iPart(0), iState(0);
+ if( nType == ControlType::Scrollbar )
+ {
+ HRESULT hr;
+ if( nPart == ControlPart::ButtonUp )
+ {
+ iPart = SBP_ARROWBTN;
+ if( nState & ControlState::PRESSED )
+ iState = ABS_UPPRESSED;
+ else if( !(nState & ControlState::ENABLED) )
+ iState = ABS_UPDISABLED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = ABS_UPHOT;
+ else
+ iState = ABS_UPNORMAL;
+ hr = DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr);
+ return (hr == S_OK);
+ }
+ if( nPart == ControlPart::ButtonDown )
+ {
+ iPart = SBP_ARROWBTN;
+ if( nState & ControlState::PRESSED )
+ iState = ABS_DOWNPRESSED;
+ else if( !(nState & ControlState::ENABLED) )
+ iState = ABS_DOWNDISABLED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = ABS_DOWNHOT;
+ else
+ iState = ABS_DOWNNORMAL;
+ hr = DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr);
+ return (hr == S_OK);
+ }
+ if( nPart == ControlPart::ButtonLeft )
+ {
+ iPart = SBP_ARROWBTN;
+ if( nState & ControlState::PRESSED )
+ iState = ABS_LEFTPRESSED;
+ else if( !(nState & ControlState::ENABLED) )
+ iState = ABS_LEFTDISABLED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = ABS_LEFTHOT;
+ else
+ iState = ABS_LEFTNORMAL;
+ hr = DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr);
+ return (hr == S_OK);
+ }
+ if( nPart == ControlPart::ButtonRight )
+ {
+ iPart = SBP_ARROWBTN;
+ if( nState & ControlState::PRESSED )
+ iState = ABS_RIGHTPRESSED;
+ else if( !(nState & ControlState::ENABLED) )
+ iState = ABS_RIGHTDISABLED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = ABS_RIGHTHOT;
+ else
+ iState = ABS_RIGHTNORMAL;
+ hr = DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr);
+ return (hr == S_OK);
+ }
+ if( nPart == ControlPart::ThumbHorz || nPart == ControlPart::ThumbVert )
+ {
+ iPart = (nPart == ControlPart::ThumbHorz) ? SBP_THUMBBTNHORZ : SBP_THUMBBTNVERT;
+ if( nState & ControlState::PRESSED )
+ iState = SCRBS_PRESSED;
+ else if( !(nState & ControlState::ENABLED) )
+ iState = SCRBS_DISABLED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = SCRBS_HOT;
+ else
+ iState = SCRBS_NORMAL;
+
+ SIZE sz;
+ GetThemePartSize(hTheme, hDC, iPart, iState, nullptr, TS_MIN, &sz);
+ GetThemePartSize(hTheme, hDC, iPart, iState, nullptr, TS_TRUE, &sz);
+ GetThemePartSize(hTheme, hDC, iPart, iState, nullptr, TS_DRAW, &sz);
+
+ hr = DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr);
+ // paint gripper on thumb if enough space
+ if( ( (nPart == ControlPart::ThumbVert) && (rc.bottom-rc.top > 12) ) ||
+ ( (nPart == ControlPart::ThumbHorz) && (rc.right-rc.left > 12) ) )
+ {
+ iPart = (nPart == ControlPart::ThumbHorz) ? SBP_GRIPPERHORZ : SBP_GRIPPERVERT;
+ iState = 0;
+ DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr);
+ }
+ return (hr == S_OK);
+ }
+ if( nPart == ControlPart::TrackHorzLeft || nPart == ControlPart::TrackHorzRight || nPart == ControlPart::TrackVertUpper || nPart == ControlPart::TrackVertLower )
+ {
+ switch( nPart )
+ {
+ case ControlPart::TrackHorzLeft: iPart = SBP_UPPERTRACKHORZ; break;
+ case ControlPart::TrackHorzRight: iPart = SBP_LOWERTRACKHORZ; break;
+ case ControlPart::TrackVertUpper: iPart = SBP_UPPERTRACKVERT; break;
+ case ControlPart::TrackVertLower: iPart = SBP_LOWERTRACKVERT; break;
+ default: break;
+ }
+
+ if( nState & ControlState::PRESSED )
+ iState = SCRBS_PRESSED;
+ else if( !(nState & ControlState::ENABLED) )
+ iState = SCRBS_DISABLED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = SCRBS_HOT;
+ else
+ iState = SCRBS_NORMAL;
+ hr = DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr);
+ return (hr == S_OK);
+ }
+ }
+ if( nType == ControlType::SpinButtons && nPart == ControlPart::AllButtons )
+ {
+ if( aValue.getType() == ControlType::SpinButtons )
+ {
+ const SpinbuttonValue* pValue = (aValue.getType() == ControlType::SpinButtons) ? static_cast<const SpinbuttonValue*>(&aValue) : nullptr;
+
+ RECT rect;
+ ImplConvertSpinbuttonValues( pValue->mnUpperPart, pValue->mnUpperState, pValue->maUpperRect, &iPart, &iState, &rect );
+ bool bOk = ImplDrawTheme( hTheme, hDC, iPart, iState, rect, aCaption);
+
+ if( bOk )
+ {
+ ImplConvertSpinbuttonValues( pValue->mnLowerPart, pValue->mnLowerState, pValue->maLowerRect, &iPart, &iState, &rect );
+ bOk = ImplDrawTheme( hTheme, hDC, iPart, iState, rect, aCaption);
+ }
+
+ return bOk;
+ }
+ }
+ if( nType == ControlType::Spinbox )
+ {
+ if( nPart == ControlPart::AllButtons )
+ {
+ if( aValue.getType() == ControlType::SpinButtons )
+ {
+ const SpinbuttonValue *pValue = static_cast<const SpinbuttonValue*>(&aValue);
+
+ RECT rect;
+ ImplConvertSpinbuttonValues( pValue->mnUpperPart, pValue->mnUpperState, pValue->maUpperRect, &iPart, &iState, &rect );
+ bool bOk = ImplDrawTheme( hTheme, hDC, iPart, iState, rect, aCaption);
+
+ if( bOk )
+ {
+ ImplConvertSpinbuttonValues( pValue->mnLowerPart, pValue->mnLowerState, pValue->maLowerRect, &iPart, &iState, &rect );
+ bOk = ImplDrawTheme( hTheme, hDC, iPart, iState, rect, aCaption);
+ }
+
+ return bOk;
+ }
+ }
+
+ if( nPart == ControlPart::ButtonDown )
+ {
+ iPart = SPNP_DOWN;
+ if( nState & ControlState::PRESSED )
+ iState = DNS_PRESSED;
+ else if( !(nState & ControlState::ENABLED) )
+ iState = DNS_DISABLED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = DNS_HOT;
+ else
+ iState = DNS_NORMAL;
+ }
+ if( nPart == ControlPart::ButtonUp )
+ {
+ iPart = SPNP_UP;
+ if( nState & ControlState::PRESSED )
+ iState = UPS_PRESSED;
+ else if( !(nState & ControlState::ENABLED) )
+ iState = UPS_DISABLED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = UPS_HOT;
+ else
+ iState = UPS_NORMAL;
+ }
+ if( nPart == ControlPart::ButtonRight )
+ {
+ iPart = SPNP_DOWNHORZ;
+ if( nState & ControlState::PRESSED )
+ iState = DNHZS_PRESSED;
+ else if( !(nState & ControlState::ENABLED) )
+ iState = DNHZS_DISABLED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = DNHZS_HOT;
+ else
+ iState = DNHZS_NORMAL;
+ }
+ if( nPart == ControlPart::ButtonLeft )
+ {
+ iPart = SPNP_UPHORZ;
+ if( nState & ControlState::PRESSED )
+ iState = UPHZS_PRESSED;
+ else if( !(nState & ControlState::ENABLED) )
+ iState = UPHZS_DISABLED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = UPHZS_HOT;
+ else
+ iState = UPHZS_NORMAL;
+ }
+ if( nPart == ControlPart::ButtonLeft || nPart == ControlPart::ButtonRight || nPart == ControlPart::ButtonUp || nPart == ControlPart::ButtonDown )
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+ if( nType == ControlType::Combobox )
+ {
+ if( nPart == ControlPart::ButtonDown )
+ {
+ iPart = CP_DROPDOWNBUTTON;
+ if( nState & ControlState::PRESSED )
+ iState = CBXS_PRESSED;
+ else if( !(nState & ControlState::ENABLED) )
+ iState = CBXS_DISABLED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = CBXS_HOT;
+ else
+ iState = CBXS_NORMAL;
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+ }
+ if( nType == ControlType::Pushbutton )
+ {
+ iPart = BP_PUSHBUTTON;
+ if( nState & ControlState::PRESSED )
+ iState = PBS_PRESSED;
+ else if( !(nState & ControlState::ENABLED) )
+ iState = PBS_DISABLED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = PBS_HOT;
+ else if( nState & ControlState::DEFAULT )
+ iState = PBS_DEFAULTED;
+ //else if( nState & ControlState::FOCUSED )
+ // iState = PBS_DEFAULTED; // may need to draw focus rect
+ else
+ iState = PBS_NORMAL;
+
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+
+ if( nType == ControlType::Radiobutton )
+ {
+ iPart = BP_RADIOBUTTON;
+ bool bChecked = ( aValue.getTristateVal() == ButtonValue::On );
+
+ if( nState & ControlState::PRESSED )
+ iState = bChecked ? RBS_CHECKEDPRESSED : RBS_UNCHECKEDPRESSED;
+ else if( !(nState & ControlState::ENABLED) )
+ iState = bChecked ? RBS_CHECKEDDISABLED : RBS_UNCHECKEDDISABLED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = bChecked ? RBS_CHECKEDHOT : RBS_UNCHECKEDHOT;
+ else
+ iState = bChecked ? RBS_CHECKEDNORMAL : RBS_UNCHECKEDNORMAL;
+
+ //if( nState & ControlState::FOCUSED )
+ // iState |= PBS_DEFAULTED; // may need to draw focus rect
+
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+
+ if( nType == ControlType::Checkbox )
+ {
+ iPart = BP_CHECKBOX;
+ ButtonValue v = aValue.getTristateVal();
+
+ if( nState & ControlState::PRESSED )
+ iState = (v == ButtonValue::On) ? CBS_CHECKEDPRESSED :
+ ( (v == ButtonValue::Off) ? CBS_UNCHECKEDPRESSED : CBS_MIXEDPRESSED );
+ else if( !(nState & ControlState::ENABLED) )
+ iState = (v == ButtonValue::On) ? CBS_CHECKEDDISABLED :
+ ( (v == ButtonValue::Off) ? CBS_UNCHECKEDDISABLED : CBS_MIXEDDISABLED );
+ else if( nState & ControlState::ROLLOVER )
+ iState = (v == ButtonValue::On) ? CBS_CHECKEDHOT :
+ ( (v == ButtonValue::Off) ? CBS_UNCHECKEDHOT : CBS_MIXEDHOT );
+ else
+ iState = (v == ButtonValue::On) ? CBS_CHECKEDNORMAL :
+ ( (v == ButtonValue::Off) ? CBS_UNCHECKEDNORMAL : CBS_MIXEDNORMAL );
+
+ //if( nState & ControlState::FOCUSED )
+ // iState |= PBS_DEFAULTED; // may need to draw focus rect
+
+ //SIZE sz;
+ //THEMESIZE eSize = TS_DRAW; // TS_MIN, TS_TRUE, TS_DRAW
+ //GetThemePartSize( hTheme, hDC, iPart, iState, &rc, eSize, &sz);
+
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+
+ if (nType == ControlType::Editbox)
+ {
+ iPart = EP_EDITBORDER_NOSCROLL;
+ if( !(nState & ControlState::ENABLED) )
+ iState = EPSN_DISABLED;
+ else if( nState & ControlState::FOCUSED )
+ iState = EPSN_FOCUSED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = EPSN_HOT;
+ else
+ iState = EPSN_NORMAL;
+
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+
+ if (nType == ControlType::MultilineEditbox)
+ {
+ iPart = EP_EDITTEXT;
+ if( !(nState & ControlState::ENABLED) )
+ iState = ETS_DISABLED;
+ else if( nState & ControlState::FOCUSED )
+ iState = ETS_FOCUSED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = ETS_HOT;
+ else
+ iState = ETS_NORMAL;
+
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+
+ if( nType == ControlType::Listbox )
+ {
+ if( nPart == ControlPart::Entire || nPart == ControlPart::ListboxWindow )
+ {
+ iPart = LVP_EMPTYTEXT; // ??? no idea which part to choose here
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+ }
+
+ if( nType == ControlType::TabPane )
+ {
+ // tabpane in tabcontrols gets drawn in "darkmode" as if it was a
+ // a "light" theme, so bodge this by drawing a frame directly
+ if (bUseDarkMode)
+ {
+ Color aColor(Application::GetSettings().GetStyleSettings().GetDisableColor());
+ ScopedHBRUSH hbrush(CreateSolidBrush(RGB(aColor.GetRed(),
+ aColor.GetGreen(),
+ aColor.GetBlue())));
+ FrameRect(hDC, &rc, hbrush.get());
+ return true;
+ }
+ iPart = TABP_PANE;
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+
+ if( nType == ControlType::TabBody )
+ {
+ // tabbody in main window gets drawn in white in "darkmode", so bodge this here
+ if (bUseDarkMode)
+ {
+ Color aColor(Application::GetSettings().GetStyleSettings().GetWindowColor());
+ ScopedHBRUSH hbrush(CreateSolidBrush(RGB(aColor.GetRed(),
+ aColor.GetGreen(),
+ aColor.GetBlue())));
+ FillRect(hDC, &rc, hbrush.get());
+ return true;
+ }
+
+ iPart = TABP_BODY;
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+
+ if( nType == ControlType::TabItem )
+ {
+ iPart = TABP_TABITEMLEFTEDGE;
+ rc.bottom--;
+
+ OSL_ASSERT( aValue.getType() == ControlType::TabItem );
+
+ const TabitemValue& rValue = static_cast<const TabitemValue&>(aValue);
+ if (rValue.isBothAligned())
+ {
+ iPart = TABP_TABITEMLEFTEDGE;
+ rc.right--;
+ }
+ else if (rValue.isLeftAligned())
+ iPart = TABP_TABITEMLEFTEDGE;
+ else if (rValue.isRightAligned())
+ iPart = TABP_TABITEMRIGHTEDGE;
+ else
+ iPart = TABP_TABITEM;
+
+ if( !(nState & ControlState::ENABLED) )
+ iState = TILES_DISABLED;
+ else if( nState & ControlState::SELECTED )
+ {
+ iState = TILES_SELECTED;
+ // increase the selected tab
+ rc.left-=2;
+ if (rValue.isBothAligned())
+ {
+ if (rValue.isLeftAligned() || rValue.isNotAligned())
+ rc.right+=2;
+ if (rValue.isRightAligned())
+ rc.right+=1;
+ }
+ rc.top-=2;
+ rc.bottom+=2;
+ }
+ else if( nState & ControlState::ROLLOVER )
+ iState = TILES_HOT;
+ else if( nState & ControlState::FOCUSED )
+ iState = TILES_FOCUSED; // may need to draw focus rect
+ else
+ iState = TILES_NORMAL;
+
+ // tabitem in tabcontrols gets drawn in "darkmode" as if it was a
+ // a "light" theme, so bodge this by drawing with a button instead
+ if (bUseDarkMode)
+ {
+ Color aColor;
+ if (iState == TILES_SELECTED)
+ aColor = Application::GetSettings().GetStyleSettings().GetActiveTabColor();
+ else
+ aColor = Application::GetSettings().GetStyleSettings().GetInactiveTabColor();
+ ScopedHBRUSH hbrush(CreateSolidBrush(RGB(aColor.GetRed(),
+ aColor.GetGreen(),
+ aColor.GetBlue())));
+ FillRect(hDC, &rc, hbrush.get());
+
+ aColor = Application::GetSettings().GetStyleSettings().GetDisableColor();
+ ScopedSelectedHPEN hPen(hDC, CreatePen(PS_SOLID, 1, RGB(aColor.GetRed(),
+ aColor.GetGreen(),
+ aColor.GetBlue())));
+ POINT apt[4];
+ apt[0].x = rc.left;
+ apt[0].y = rc.bottom - (iPart == TABP_TABITEMLEFTEDGE ? 1 : 2);
+ apt[1].x = rc.left;
+ apt[1].y = rc.top;
+ apt[2].x = rc.right;
+ apt[2].y = rc.top;
+ apt[3].x = rc.right;
+ apt[3].y = rc.bottom - 1;
+ Polyline(hDC, apt, SAL_N_ELEMENTS(apt));
+ return true;
+ }
+
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+
+ if( nType == ControlType::Toolbar )
+ {
+ if( nPart == ControlPart::Button )
+ {
+ iPart = TP_BUTTON;
+ bool bChecked = ( aValue.getTristateVal() == ButtonValue::On );
+ if( !(nState & ControlState::ENABLED) )
+ //iState = TS_DISABLED;
+ // disabled buttons are typically not painted at all but we need visual
+ // feedback when travelling by keyboard over disabled entries
+ iState = TS_HOT;
+ else if( nState & ControlState::PRESSED )
+ iState = TS_PRESSED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = bChecked ? TS_HOTCHECKED : TS_HOT;
+ else
+ iState = bChecked ? TS_CHECKED : TS_NORMAL;
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+ else if( nPart == ControlPart::ThumbHorz || nPart == ControlPart::ThumbVert )
+ {
+ // the vertical gripper is not supported in most themes and it makes no
+ // sense to only support horizontal gripper
+ //iPart = (nPart == ControlPart::ThumbHorz) ? RP_GRIPPERVERT : RP_GRIPPER;
+ //return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+ else if( nPart == ControlPart::DrawBackgroundHorz || nPart == ControlPart::DrawBackgroundVert )
+ {
+ if( aValue.getType() == ControlType::Toolbar )
+ {
+ const ToolbarValue *pValue = static_cast<const ToolbarValue*>(&aValue);
+ if( pValue->mbIsTopDockingArea )
+ rc.top = 0; // extend potential gradient to cover menu bar as well
+ }
+
+ // toolbar in main window gets drawn in white in "darkmode", so bodge this here
+ if (bUseDarkMode)
+ {
+ Color aColor(Application::GetSettings().GetStyleSettings().GetWindowColor());
+ ScopedHBRUSH hbrush(CreateSolidBrush(RGB(aColor.GetRed(),
+ aColor.GetGreen(),
+ aColor.GetBlue())));
+ FillRect(hDC, &rc, hbrush.get());
+ return true;
+ }
+
+ // make it more compatible with Aero
+ if (ImplGetSVData()->maNWFData.mbDockingAreaAvoidTBFrames &&
+ !Application::GetSettings().GetStyleSettings().GetHighContrastMode())
+ {
+ impl_drawAeroToolbar( hDC, rc, nPart == ControlPart::DrawBackgroundHorz );
+ return true;
+ }
+
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+ }
+
+ if( nType == ControlType::Menubar )
+ {
+ if( nPart == ControlPart::Entire )
+ {
+ if( aValue.getType() == ControlType::Menubar )
+ {
+ const MenubarValue *pValue = static_cast<const MenubarValue*>(&aValue);
+ rc.bottom += pValue->maTopDockingAreaHeight; // extend potential gradient to cover docking area as well
+
+ // menubar in main window gets drawn in white in "darkmode", so bodge this here
+ if (bUseDarkMode)
+ {
+ Color aColor(Application::GetSettings().GetStyleSettings().GetWindowColor());
+ ScopedHBRUSH hbrush(CreateSolidBrush(RGB(aColor.GetRed(),
+ aColor.GetGreen(),
+ aColor.GetBlue())));
+ FillRect(hDC, &rc, hbrush.get());
+ return true;
+ }
+
+ // make it more compatible with Aero
+ if (ImplGetSVData()->maNWFData.mbDockingAreaAvoidTBFrames &&
+ !Application::GetSettings().GetStyleSettings().GetHighContrastMode())
+ {
+ impl_drawAeroToolbar( hDC, rc, true );
+ return true;
+ }
+ }
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption);
+ }
+ else if( nPart == ControlPart::MenuItem )
+ {
+ if( nState & ControlState::ENABLED )
+ {
+ if( nState & ControlState::SELECTED )
+ iState = MBI_PUSHED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = MBI_HOT;
+ else
+ iState = MBI_NORMAL;
+
+ if(GetSalData()->mbThemeMenuSupport && Application::GetSettings().GetStyleSettings().GetHighContrastMode()
+ && ( nState & (ControlState::SELECTED | nState & ControlState::ROLLOVER )))
+ {
+ Color aColor(Application::GetSettings().GetStyleSettings().GetHighlightColor());
+ ScopedHBRUSH hbrush(CreateSolidBrush(RGB(aColor.GetRed(),
+ aColor.GetGreen(),
+ aColor.GetBlue())));
+ FillRect(hDC, &rc, hbrush.get());
+ return true;
+ }
+ }
+ else
+ {
+ if( nState & ControlState::SELECTED )
+ iState = MBI_DISABLEDPUSHED;
+ else if( nState & ControlState::ROLLOVER )
+ iState = MBI_DISABLEDHOT;
+ else
+ iState = MBI_DISABLED;
+ }
+ return ImplDrawTheme( hTheme, hDC, MENU_BARITEM, iState, rc, aCaption );
+ }
+ }
+
+ if( nType == ControlType::Progress || nType == ControlType::LevelBar )
+ {
+ if( nPart != ControlPart::Entire )
+ return false;
+
+ int nPartIdBackground = PP_BAR;
+ if( nType == ControlType::LevelBar )
+ {
+ nPartIdBackground = PP_TRANSPARENTBAR;
+ iState = PBBS_PARTIAL;
+ }
+
+ if( ! ImplDrawTheme( hTheme, hDC, nPartIdBackground, iState, rc, aCaption) )
+ return false;
+ RECT aProgressRect = rc;
+ if( GetThemeBackgroundContentRect( hTheme, hDC, PP_BAR, iState, &rc, &aProgressRect) != S_OK )
+ return false;
+
+ tools::Long nProgressWidth = aValue.getNumericVal();
+ nProgressWidth *= (aProgressRect.right - aProgressRect.left);
+ nProgressWidth /= (rc.right - rc.left);
+ if( AllSettings::GetLayoutRTL() )
+ aProgressRect.left = aProgressRect.right - nProgressWidth;
+ else
+ aProgressRect.right = aProgressRect.left + nProgressWidth;
+
+ if (nType == ControlType::LevelBar)
+ {
+ const auto nPercentage
+ = aValue.getNumericVal() * 100 / std::max(LONG{ 1 }, (rc.right - rc.left));
+
+ COLORREF aBrushColor{};
+ if (nPercentage < 25)
+ aBrushColor = RGB(255, 0, 0);
+ else if (nPercentage < 50)
+ aBrushColor = RGB(255, 255, 0);
+ else if (nPercentage < 75)
+ aBrushColor = RGB(0, 0, 255);
+ else
+ aBrushColor = RGB(0, 255, 0);
+
+ ScopedHBRUSH hBrush(CreateSolidBrush(aBrushColor));
+ FillRect(hDC, &aProgressRect, hBrush.get());
+ return true;
+ }
+
+ return ImplDrawTheme( hTheme, hDC, PP_CHUNK, iState, aProgressRect, aCaption );
+ }
+
+ if( nType == ControlType::Slider )
+ {
+ iPart = (nPart == ControlPart::TrackHorzArea) ? TKP_TRACK : TKP_TRACKVERT;
+ iState = (nPart == ControlPart::TrackHorzArea) ? static_cast<int>(TRS_NORMAL) : static_cast<int>(TRVS_NORMAL);
+
+ tools::Rectangle aTrackRect = ImplGetThemeRect( hTheme, hDC, iPart, iState, tools::Rectangle() );
+ RECT aTRect = rc;
+ if( nPart == ControlPart::TrackHorzArea )
+ {
+ tools::Long nH = aTrackRect.GetHeight();
+ aTRect.top += (rc.bottom - rc.top - nH)/2;
+ aTRect.bottom = aTRect.top + nH;
+ }
+ else
+ {
+ tools::Long nW = aTrackRect.GetWidth();
+ aTRect.left += (rc.right - rc.left - nW)/2;
+ aTRect.right = aTRect.left + nW;
+ }
+ ImplDrawTheme( hTheme, hDC, iPart, iState, aTRect, aCaption );
+
+ RECT aThumbRect;
+ OSL_ASSERT( aValue.getType() == ControlType::Slider );
+ const SliderValue* pVal = static_cast<const SliderValue*>(&aValue);
+ aThumbRect.left = pVal->maThumbRect.Left();
+ aThumbRect.top = pVal->maThumbRect.Top();
+ aThumbRect.right = pVal->maThumbRect.Right();
+ aThumbRect.bottom = pVal->maThumbRect.Bottom();
+ iPart = (nPart == ControlPart::TrackHorzArea) ? TKP_THUMB : TKP_THUMBVERT;
+ iState = (nState & ControlState::ENABLED) ? TUS_NORMAL : TUS_DISABLED;
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, aThumbRect, aCaption );
+ }
+
+ if( nType == ControlType::ListNode )
+ {
+ if( nPart != ControlPart::Entire )
+ return false;
+
+ ButtonValue aButtonValue = aValue.getTristateVal();
+ iPart = TVP_GLYPH;
+ switch( aButtonValue )
+ {
+ case ButtonValue::On:
+ iState = GLPS_OPENED;
+ break;
+ case ButtonValue::Off:
+ iState = GLPS_CLOSED;
+ break;
+ default:
+ return false;
+ }
+ return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption );
+ }
+
+ if( GetSalData()->mbThemeMenuSupport )
+ {
+ if( nType == ControlType::MenuPopup )
+ {
+ if( nPart == ControlPart::Entire )
+ {
+ RECT aGutterRC = rc;
+ if( AllSettings::GetLayoutRTL() )
+ {
+ aGutterRC.right -= aValue.getNumericVal()+1;
+ aGutterRC.left = aGutterRC.right-3;
+ }
+ else
+ {
+ aGutterRC.left += aValue.getNumericVal();
+ aGutterRC.right = aGutterRC.left+3;
+ }
+ return
+ ImplDrawTheme( hTheme, hDC, MENU_POPUPBACKGROUND, 0, rc, aCaption ) &&
+ ImplDrawTheme( hTheme, hDC, MENU_POPUPGUTTER, 0, aGutterRC, aCaption )
+ ;
+ }
+ else if( nPart == ControlPart::MenuItem )
+ {
+ if( nState & ControlState::ENABLED )
+ iState = (nState & ControlState::SELECTED) ? MPI_HOT : MPI_NORMAL;
+ else
+ iState = (nState & ControlState::SELECTED) ? MPI_DISABLEDHOT : MPI_DISABLED;
+ return ImplDrawTheme( hTheme, hDC, MENU_POPUPITEM, iState, rc, aCaption );
+ }
+ else if( nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark )
+ {
+ if (nState & ControlState::PRESSED)
+ return implDrawNativeMenuMark(hDC, hTheme, rc, nPart, nState, aCaption);
+ else
+ return true; // unchecked: do nothing
+ }
+ else if( nPart == ControlPart::Separator )
+ {
+ // adjust for gutter position
+ if( AllSettings::GetLayoutRTL() )
+ rc.right -= aValue.getNumericVal()+1;
+ else
+ rc.left += aValue.getNumericVal()+1;
+ tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC,
+ MENU_POPUPSEPARATOR, 0, tools::Rectangle( rc.left, rc.top, rc.right, rc.bottom ) ) );
+ // center the separator inside the passed rectangle
+ auto const nDY = ((rc.bottom - rc.top + 1) - aRect.GetHeight()) / 2;
+ rc.top += nDY;
+ rc.bottom = rc.top+aRect.GetHeight()-1;
+ return ImplDrawTheme( hTheme, hDC, MENU_POPUPSEPARATOR, 0, rc, aCaption );
+ }
+ }
+ }
+
+ return false;
+}
+
+bool WinSalGraphics::drawNativeControl( ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ ControlState nState,
+ const ImplControlValue& aValue,
+ const OUString& aCaption,
+ const Color& /*rBackgroundColor*/ )
+{
+ bool bOk = false;
+ HTHEME hTheme = nullptr;
+
+ tools::Rectangle buttonRect = rControlRegion;
+ tools::Rectangle cacheRect = rControlRegion;
+ Size keySize = cacheRect.GetSize();
+
+ WinSalGraphicsImplBase* pImpl = mWinSalGraphicsImplBase;
+ if( !pImpl->UseRenderNativeControl())
+ pImpl = nullptr;
+
+ // tdf#95618 - A few controls render outside the region they're given.
+ if (pImpl && nType == ControlType::TabItem)
+ {
+ tools::Rectangle rNativeBoundingRegion;
+ tools::Rectangle rNativeContentRegion;
+ if (getNativeControlRegion(nType, nPart, rControlRegion, nState, aValue, aCaption,
+ rNativeBoundingRegion, rNativeContentRegion))
+ {
+ cacheRect = rNativeBoundingRegion;
+ keySize = rNativeBoundingRegion.GetSize();
+ }
+ }
+
+
+ ControlCacheKey aControlCacheKey(nType, nPart, nState, keySize);
+ if (pImpl != nullptr && pImpl->TryRenderCachedNativeControl(aControlCacheKey, buttonRect.Left(), buttonRect.Top()))
+ {
+ return true;
+ }
+
+ const bool bUseDarkMode = UseDarkMode();
+ if (bUseDarkMode)
+ SetWindowTheme(mhWnd, L"Explorer", nullptr);
+
+ switch( nType )
+ {
+ case ControlType::Pushbutton:
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ hTheme = getThemeHandle(mhWnd, L"Button", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::Scrollbar:
+ if (bUseDarkMode)
+ {
+ // tdf#153273 undo the earlier SetWindowTheme, and use an explicit Explorer::Scrollbar
+ // a) with "Scrollbar" and SetWindowTheme(... "Explorer" ...) then scrollbars in dialog
+ // and main windows are dark, but dropdowns are light
+ // b) with "Explorer::Scrollbar" and SetWindowTheme(... "Explorer" ...) then scrollbars
+ // in dropdowns are dark, but scrollbars in dialogs and main windows are sort of "extra
+ // dark"
+ // c) with "Explorer::Scrollbar" and no SetWindowTheme both cases are dark
+ SetWindowTheme(mhWnd, nullptr, nullptr);
+ hTheme = getThemeHandle(mhWnd, L"Explorer::Scrollbar", mWinSalGraphicsImplBase);
+ }
+ else
+ hTheme = getThemeHandle(mhWnd, L"Scrollbar", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::Combobox:
+ if( nPart == ControlPart::Entire )
+ {
+ if (bUseDarkMode && !(nState & ControlState::FOCUSED))
+ SetWindowTheme(mhWnd, L"CFD", nullptr);
+ hTheme = getThemeHandle(mhWnd, L"Edit", mWinSalGraphicsImplBase);
+ }
+ else if( nPart == ControlPart::ButtonDown )
+ {
+ if (bUseDarkMode)
+ SetWindowTheme(mhWnd, L"CFD", nullptr);
+ hTheme = getThemeHandle(mhWnd, L"Combobox", mWinSalGraphicsImplBase);
+ }
+ break;
+ case ControlType::Spinbox:
+ if( nPart == ControlPart::Entire )
+ {
+ if (bUseDarkMode && !(nState & ControlState::FOCUSED))
+ SetWindowTheme(mhWnd, L"CFD", nullptr);
+ hTheme = getThemeHandle(mhWnd, L"Edit", mWinSalGraphicsImplBase);
+ }
+ else
+ hTheme = getThemeHandle(mhWnd, L"Spin", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::SpinButtons:
+ hTheme = getThemeHandle(mhWnd, L"Spin", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::Editbox:
+ if (bUseDarkMode && !(nState & ControlState::FOCUSED))
+ SetWindowTheme(mhWnd, L"CFD", nullptr);
+ hTheme = getThemeHandle(mhWnd, L"Edit", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::MultilineEditbox:
+ hTheme = getThemeHandle(mhWnd, L"Edit", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::Listbox:
+ if( nPart == ControlPart::Entire || nPart == ControlPart::ListboxWindow )
+ hTheme = getThemeHandle(mhWnd, L"Listview", mWinSalGraphicsImplBase);
+ else if( nPart == ControlPart::ButtonDown )
+ {
+ if (bUseDarkMode)
+ SetWindowTheme(mhWnd, L"CFD", nullptr);
+ hTheme = getThemeHandle(mhWnd, L"Combobox", mWinSalGraphicsImplBase);
+ }
+ break;
+ case ControlType::TabBody:
+ hTheme = getThemeHandle(mhWnd, L"Tab", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::TabPane:
+ case ControlType::TabItem:
+ hTheme = getThemeHandle(mhWnd, L"Tab", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::Toolbar:
+ if( nPart == ControlPart::Entire || nPart == ControlPart::Button )
+ hTheme = getThemeHandle(mhWnd, L"Toolbar", mWinSalGraphicsImplBase);
+ else
+ // use rebar for grip and background
+ hTheme = getThemeHandle(mhWnd, L"Rebar", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::Menubar:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle(mhWnd, L"Rebar", mWinSalGraphicsImplBase);
+ else if( GetSalData()->mbThemeMenuSupport )
+ {
+ if( nPart == ControlPart::MenuItem )
+ hTheme = getThemeHandle(mhWnd, L"Menu", mWinSalGraphicsImplBase);
+ }
+ break;
+ case ControlType::Progress:
+ case ControlType::LevelBar:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle(mhWnd, L"Progress", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::ListNode:
+ if( nPart == ControlPart::Entire )
+ hTheme = getThemeHandle(mhWnd, L"TreeView", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::Slider:
+ if( nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea )
+ hTheme = getThemeHandle(mhWnd, L"Trackbar", mWinSalGraphicsImplBase);
+ break;
+ case ControlType::MenuPopup:
+ if( GetSalData()->mbThemeMenuSupport )
+ {
+ if( nPart == ControlPart::Entire || nPart == ControlPart::MenuItem ||
+ nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark ||
+ nPart == ControlPart::Separator
+ )
+ hTheme = getThemeHandle(mhWnd, L"Menu", mWinSalGraphicsImplBase);
+ }
+ break;
+ default:
+ hTheme = nullptr;
+ break;
+ }
+
+ if( !hTheme )
+ {
+ if (bUseDarkMode)
+ SetWindowTheme(mhWnd, nullptr, nullptr);
+ return false;
+ }
+
+ RECT rc;
+ rc.left = buttonRect.Left();
+ rc.right = buttonRect.Right()+1;
+ rc.top = buttonRect.Top();
+ rc.bottom = buttonRect.Bottom()+1;
+
+ OUString aCaptionStr(aCaption.replace('~', '&')); // translate mnemonics
+
+ if (pImpl == nullptr)
+ {
+ // set default text alignment
+ int ta = SetTextAlign(getHDC(), TA_LEFT|TA_TOP|TA_NOUPDATECP);
+
+ bOk = ImplDrawNativeControl(getHDC(), hTheme, rc, nType, nPart, nState, aValue, aCaptionStr, bUseDarkMode);
+
+ // restore alignment
+ SetTextAlign(getHDC(), ta);
+ }
+ else
+ {
+ // We can do OpenGL/Skia
+ std::unique_ptr<CompatibleDC> aBlackDC(CompatibleDC::create(*this, cacheRect.Left(), cacheRect.Top(), cacheRect.GetWidth()+1, cacheRect.GetHeight()+1));
+ SetTextAlign(aBlackDC->getCompatibleHDC(), TA_LEFT|TA_TOP|TA_NOUPDATECP);
+ aBlackDC->fill(RGB(0, 0, 0));
+
+ std::unique_ptr<CompatibleDC> aWhiteDC(CompatibleDC::create(*this, cacheRect.Left(), cacheRect.Top(), cacheRect.GetWidth()+1, cacheRect.GetHeight()+1));
+ SetTextAlign(aWhiteDC->getCompatibleHDC(), TA_LEFT|TA_TOP|TA_NOUPDATECP);
+ aWhiteDC->fill(RGB(0xff, 0xff, 0xff));
+
+ if (ImplDrawNativeControl(aBlackDC->getCompatibleHDC(), hTheme, rc, nType, nPart, nState, aValue, aCaptionStr, bUseDarkMode) &&
+ ImplDrawNativeControl(aWhiteDC->getCompatibleHDC(), hTheme, rc, nType, nPart, nState, aValue, aCaptionStr, bUseDarkMode))
+ {
+ bOk = pImpl->RenderAndCacheNativeControl(*aWhiteDC, *aBlackDC, cacheRect.Left(), cacheRect.Top(), aControlCacheKey);
+ }
+ }
+
+ if (bUseDarkMode)
+ SetWindowTheme(mhWnd, nullptr, nullptr);
+ return bOk;
+}
+
+bool WinSalGraphics::getNativeControlRegion( ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ ControlState nState,
+ const ImplControlValue& rControlValue,
+ const OUString&,
+ tools::Rectangle &rNativeBoundingRegion,
+ tools::Rectangle &rNativeContentRegion )
+{
+ bool bRet = false;
+
+ // FIXME: rNativeBoundingRegion has a different origin
+ // depending on which part is used; horrors.
+
+ HDC hDC = GetDC( mhWnd );
+ if( nType == ControlType::Toolbar )
+ {
+ if( nPart == ControlPart::ThumbHorz || nPart == ControlPart::ThumbVert )
+ {
+ /*
+ // the vertical gripper is not supported in most themes and it makes no
+ // sense to only support horizontal gripper
+
+ HTHEME hTheme = getThemeHandle(mhWnd, L"Rebar", mWinSalGraphicsImplBase);
+ if( hTheme )
+ {
+ tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, nPart == ControlPart::ThumbHorz ? RP_GRIPPERVERT : RP_GRIPPER,
+ 0, rControlRegion.GetBoundRect() ) );
+ if( nPart == ControlPart::ThumbHorz && !aRect.IsEmpty() )
+ {
+ tools::Rectangle aVertRect( 0, 0, aRect.getHeight(), aRect.getWidth() );
+ rNativeContentRegion = aVertRect;
+ }
+ else
+ rNativeContentRegion = aRect;
+ rNativeBoundingRegion = rNativeContentRegion;
+ if( !rNativeContentRegion.IsEmpty() )
+ bRet = TRUE;
+ }
+ */
+ }
+ if( nPart == ControlPart::Button )
+ {
+ HTHEME hTheme = getThemeHandle(mhWnd, L"Toolbar", mWinSalGraphicsImplBase);
+ if( hTheme )
+ {
+ tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, TP_SPLITBUTTONDROPDOWN,
+ TS_HOT, rControlRegion ) );
+ rNativeContentRegion = aRect;
+ rNativeBoundingRegion = rNativeContentRegion;
+ if( !rNativeContentRegion.IsEmpty() )
+ bRet = true;
+ }
+ }
+ }
+ if( nType == ControlType::Progress && nPart == ControlPart::Entire )
+ {
+ HTHEME hTheme = getThemeHandle(mhWnd, L"Progress", mWinSalGraphicsImplBase);
+ if( hTheme )
+ {
+ tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, PP_BAR,
+ 0, rControlRegion ) );
+ rNativeContentRegion = aRect;
+ rNativeBoundingRegion = rNativeContentRegion;
+ if( !rNativeContentRegion.IsEmpty() )
+ bRet = true;
+ }
+ }
+ if( (nType == ControlType::Listbox || nType == ControlType::Combobox ) && nPart == ControlPart::Entire )
+ {
+ HTHEME hTheme = getThemeHandle(mhWnd, L"Combobox", mWinSalGraphicsImplBase);
+ if( hTheme )
+ {
+ tools::Rectangle aBoxRect( rControlRegion );
+ tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, CP_DROPDOWNBUTTON,
+ CBXS_NORMAL, aBoxRect ) );
+ if( aRect.GetHeight() > aBoxRect.GetHeight() )
+ aBoxRect.SetBottom( aBoxRect.Top() + aRect.GetHeight() );
+ if( aRect.GetWidth() > aBoxRect.GetWidth() )
+ aBoxRect.SetRight( aBoxRect.Left() + aRect.GetWidth() );
+ rNativeContentRegion = aBoxRect;
+ rNativeBoundingRegion = rNativeContentRegion;
+ if( !aRect.IsEmpty() )
+ bRet = true;
+ }
+ }
+
+ if( (nType == ControlType::Editbox || nType == ControlType::Spinbox) && nPart == ControlPart::Entire )
+ {
+ HTHEME hTheme = getThemeHandle(mhWnd, L"Edit", mWinSalGraphicsImplBase);
+ if( hTheme )
+ {
+ // get border size
+ tools::Rectangle aBoxRect( rControlRegion );
+ tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, EP_BACKGROUNDWITHBORDER,
+ EBWBS_HOT, aBoxRect ) );
+ // ad app font height
+ NONCLIENTMETRICSW aNonClientMetrics;
+ aNonClientMetrics.cbSize = sizeof( aNonClientMetrics );
+ if ( SystemParametersInfoW( SPI_GETNONCLIENTMETRICS, sizeof( aNonClientMetrics ), &aNonClientMetrics, 0 ) )
+ {
+ LONG nFontHeight = aNonClientMetrics.lfMessageFont.lfHeight;
+ if( nFontHeight < 0 )
+ nFontHeight = -nFontHeight;
+
+ if( aRect.GetHeight() && nFontHeight )
+ {
+ aRect.AdjustBottom(aRect.GetHeight());
+ aRect.AdjustBottom(nFontHeight);
+ if( aRect.GetHeight() > aBoxRect.GetHeight() )
+ aBoxRect.SetBottom( aBoxRect.Top() + aRect.GetHeight() );
+ if( aRect.GetWidth() > aBoxRect.GetWidth() )
+ aBoxRect.SetRight( aBoxRect.Left() + aRect.GetWidth() );
+ rNativeContentRegion = aBoxRect;
+ rNativeBoundingRegion = rNativeContentRegion;
+ bRet = true;
+ }
+ }
+ }
+ }
+
+ if( GetSalData()->mbThemeMenuSupport )
+ {
+ if( nType == ControlType::MenuPopup )
+ {
+ if( nPart == ControlPart::MenuItemCheckMark ||
+ nPart == ControlPart::MenuItemRadioMark )
+ {
+ HTHEME hTheme = getThemeHandle(mhWnd, L"Menu", mWinSalGraphicsImplBase);
+ tools::Rectangle aBoxRect( rControlRegion );
+ tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC,
+ MENU_POPUPCHECK,
+ MC_CHECKMARKNORMAL,
+ aBoxRect ) );
+ if (!aRect.IsEmpty())
+ {
+ if (MARGINS mg;
+ SUCCEEDED(GetThemeMargins(hTheme, hDC, MENU_POPUPCHECK, MC_CHECKMARKNORMAL,
+ TMT_CONTENTMARGINS, nullptr, &mg)))
+ {
+ aRect.AdjustLeft(-mg.cxLeftWidth);
+ aRect.AdjustRight(mg.cxRightWidth);
+ aRect.AdjustTop(-mg.cyTopHeight);
+ aRect.AdjustBottom(mg.cyBottomHeight);
+ }
+ rNativeContentRegion = rNativeBoundingRegion = aRect;
+ bRet = true;
+ }
+ }
+ }
+ }
+
+ if( nType == ControlType::Slider && ( (nPart == ControlPart::ThumbHorz) || (nPart == ControlPart::ThumbVert) ) )
+ {
+ HTHEME hTheme = getThemeHandle(mhWnd, L"Trackbar", mWinSalGraphicsImplBase);
+ if( hTheme )
+ {
+ int iPart = (nPart == ControlPart::ThumbHorz) ? TKP_THUMB : TKP_THUMBVERT;
+ int iState = (nPart == ControlPart::ThumbHorz) ? static_cast<int>(TUS_NORMAL) : static_cast<int>(TUVS_NORMAL);
+ tools::Rectangle aThumbRect = ImplGetThemeRect( hTheme, hDC, iPart, iState, tools::Rectangle() );
+ if( nPart == ControlPart::ThumbHorz )
+ {
+ tools::Long nW = aThumbRect.GetWidth();
+ tools::Rectangle aRect( rControlRegion );
+ aRect.SetRight( aRect.Left() + nW - 1 );
+ rNativeContentRegion = aRect;
+ rNativeBoundingRegion = rNativeContentRegion;
+ }
+ else
+ {
+ tools::Long nH = aThumbRect.GetHeight();
+ tools::Rectangle aRect( rControlRegion );
+ aRect.SetBottom( aRect.Top() + nH - 1 );
+ rNativeContentRegion = aRect;
+ rNativeBoundingRegion = rNativeContentRegion;
+ }
+ bRet = true;
+ }
+ }
+
+ if ( ( nType == ControlType::TabItem ) && ( nPart == ControlPart::Entire ) )
+ {
+ tools::Rectangle aControlRect( rControlRegion );
+ rNativeContentRegion = aControlRect;
+
+ aControlRect.AdjustBottom(-1);
+
+ if( rControlValue.getType() == ControlType::TabItem )
+ {
+ const TabitemValue& rValue = static_cast<const TabitemValue&>(rControlValue);
+ if (rValue.isBothAligned())
+ aControlRect.AdjustRight(-1);
+
+ if ( nState & ControlState::SELECTED )
+ {
+ aControlRect.AdjustLeft(-2);
+ if (!rValue.isBothAligned())
+ {
+ if (rValue.isLeftAligned() || rValue.isNotAligned())
+ aControlRect.AdjustRight(2);
+ if (rValue.isRightAligned())
+ aControlRect.AdjustRight(1);
+ }
+ aControlRect.AdjustTop(-2);
+ aControlRect.AdjustBottom(2);
+ }
+ }
+ rNativeBoundingRegion = aControlRect;
+ bRet = true;
+ }
+
+ ReleaseDC( mhWnd, hDC );
+ return bRet;
+}
+
+void WinSalGraphics::updateSettingsNative( AllSettings& rSettings )
+{
+ if ( !IsThemeActive() )
+ return;
+
+ StyleSettings aStyleSettings = rSettings.GetStyleSettings();
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // don't draw frame around each and every toolbar
+ pSVData->maNWFData.mbDockingAreaAvoidTBFrames = true;
+
+ // FIXME get the color directly from the theme, not from the settings
+ Color aMenuBarTextColor = aStyleSettings.GetPersonaMenuBarTextColor().value_or( aStyleSettings.GetMenuTextColor() );
+ // in aero menuitem highlight text is drawn in the same color as normal
+ // high contrast highlight color is not related to persona and not apply blur or transparency
+ if( !aStyleSettings.GetHighContrastMode() )
+ {
+ aStyleSettings.SetMenuHighlightTextColor( aStyleSettings.GetMenuTextColor() );
+ aStyleSettings.SetMenuBarRolloverTextColor( aMenuBarTextColor );
+ aStyleSettings.SetMenuBarHighlightTextColor( aMenuBarTextColor );
+ }
+
+ pSVData->maNWFData.mnMenuFormatBorderX = 2;
+ pSVData->maNWFData.mnMenuFormatBorderY = 2;
+ pSVData->maNWFData.maMenuBarHighlightTextColor = aMenuBarTextColor;
+ GetSalData()->mbThemeMenuSupport = true;
+
+ rSettings.SetStyleSettings( aStyleSettings );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/gdi/salprn.cxx b/vcl/win/gdi/salprn.cxx
new file mode 100644
index 0000000000..3302efa2d9
--- /dev/null
+++ b/vcl/win/gdi/salprn.cxx
@@ -0,0 +1,1604 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <memory>
+#include <vector>
+#include <string.h>
+
+#include <svsys.h>
+
+#include <osl/module.h>
+#include <o3tl/char16_t2wchar_t.hxx>
+
+#include <tools/urlobj.hxx>
+
+#include <vcl/weld.hxx>
+#include <vcl/QueueInfo.hxx>
+
+#include <win/wincomp.hxx>
+#include <win/saldata.hxx>
+#include <win/salinst.h>
+#include <win/salgdi.h>
+#include <win/salframe.h>
+#include <win/salprn.h>
+
+#include <salptype.hxx>
+#include <print.h>
+#include <jobset.h>
+
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+#include <com/sun/star/ui/dialogs/FilePicker.hpp>
+#include <com/sun/star/ui/dialogs/XFilterManager.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/windowsdebugoutput.hxx>
+
+#include <vcl/threadex.hxx>
+
+#include <malloc.h>
+
+#include <winspool.h>
+#if defined GetDefaultPrinter
+# undef GetDefaultPrinter
+#endif
+#if defined SetPrinterData
+# undef SetPrinterData
+#endif
+
+#define CATCH_DRIVER_EX_BEGIN \
+ __try \
+ {
+#define CATCH_DRIVER_EX_END(mes, p) \
+ } \
+ __except(WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(GetExceptionCode(), GetExceptionInformation()))\
+ { \
+ OSL_FAIL( mes ); \
+ p->markInvalid(); \
+ }
+#define CATCH_DRIVER_EX_END_2(mes) \
+ } \
+ __except(WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(GetExceptionCode(), GetExceptionInformation()))\
+ { \
+ OSL_FAIL( mes ); \
+ }
+
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::ui::dialogs;
+
+static DEVMODEW const * SAL_DEVMODE_W( const ImplJobSetup* pSetupData )
+{
+ DEVMODEW const * pRet = nullptr;
+ SalDriverData const * pDrv = reinterpret_cast<SalDriverData const *>(pSetupData->GetDriverData());
+ if( pSetupData->GetDriverDataLen() >= sizeof(DEVMODEW)+sizeof(SalDriverData)-1 )
+ pRet = reinterpret_cast<DEVMODEW const *>((pSetupData->GetDriverData()) + (pDrv->mnDriverOffset));
+ return pRet;
+}
+
+static PrintQueueFlags ImplWinQueueStatusToSal( DWORD nWinStatus )
+{
+ PrintQueueFlags nStatus = PrintQueueFlags::NONE;
+ if ( nWinStatus & PRINTER_STATUS_PAUSED )
+ nStatus |= PrintQueueFlags::Paused;
+ if ( nWinStatus & PRINTER_STATUS_ERROR )
+ nStatus |= PrintQueueFlags::Error;
+ if ( nWinStatus & PRINTER_STATUS_PENDING_DELETION )
+ nStatus |= PrintQueueFlags::PendingDeletion;
+ if ( nWinStatus & PRINTER_STATUS_PAPER_JAM )
+ nStatus |= PrintQueueFlags::PaperJam;
+ if ( nWinStatus & PRINTER_STATUS_PAPER_OUT )
+ nStatus |= PrintQueueFlags::PaperOut;
+ if ( nWinStatus & PRINTER_STATUS_MANUAL_FEED )
+ nStatus |= PrintQueueFlags::ManualFeed;
+ if ( nWinStatus & PRINTER_STATUS_PAPER_PROBLEM )
+ nStatus |= PrintQueueFlags::PaperProblem;
+ if ( nWinStatus & PRINTER_STATUS_OFFLINE )
+ nStatus |= PrintQueueFlags::Offline;
+ if ( nWinStatus & PRINTER_STATUS_IO_ACTIVE )
+ nStatus |= PrintQueueFlags::IOActive;
+ if ( nWinStatus & PRINTER_STATUS_BUSY )
+ nStatus |= PrintQueueFlags::Busy;
+ if ( nWinStatus & PRINTER_STATUS_PRINTING )
+ nStatus |= PrintQueueFlags::Printing;
+ if ( nWinStatus & PRINTER_STATUS_OUTPUT_BIN_FULL )
+ nStatus |= PrintQueueFlags::OutputBinFull;
+ if ( nWinStatus & PRINTER_STATUS_WAITING )
+ nStatus |= PrintQueueFlags::Waiting;
+ if ( nWinStatus & PRINTER_STATUS_PROCESSING )
+ nStatus |= PrintQueueFlags::Processing;
+ if ( nWinStatus & PRINTER_STATUS_INITIALIZING )
+ nStatus |= PrintQueueFlags::Initializing;
+ if ( nWinStatus & PRINTER_STATUS_WARMING_UP )
+ nStatus |= PrintQueueFlags::WarmingUp;
+ if ( nWinStatus & PRINTER_STATUS_TONER_LOW )
+ nStatus |= PrintQueueFlags::TonerLow;
+ if ( nWinStatus & PRINTER_STATUS_NO_TONER )
+ nStatus |= PrintQueueFlags::NoToner;
+ if ( nWinStatus & PRINTER_STATUS_PAGE_PUNT )
+ nStatus |= PrintQueueFlags::PagePunt;
+ if ( nWinStatus & PRINTER_STATUS_USER_INTERVENTION )
+ nStatus |= PrintQueueFlags::UserIntervention;
+ if ( nWinStatus & PRINTER_STATUS_OUT_OF_MEMORY )
+ nStatus |= PrintQueueFlags::OutOfMemory;
+ if ( nWinStatus & PRINTER_STATUS_DOOR_OPEN )
+ nStatus |= PrintQueueFlags::DoorOpen;
+ if ( nWinStatus & PRINTER_STATUS_SERVER_UNKNOWN )
+ nStatus |= PrintQueueFlags::StatusUnknown;
+ if ( nWinStatus & PRINTER_STATUS_POWER_SAVE )
+ nStatus |= PrintQueueFlags::PowerSave;
+ if ( nStatus == PrintQueueFlags::NONE && !(nWinStatus & PRINTER_STATUS_NOT_AVAILABLE) )
+ nStatus |= PrintQueueFlags::Ready;
+ return nStatus;
+}
+
+
+void WinSalInstance::GetPrinterQueueInfo( ImplPrnQueueList* pList )
+{
+ DWORD i;
+ DWORD nBytes = 0;
+ DWORD nInfoPrn4 = 0;
+ EnumPrintersW( PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, nullptr, 4, nullptr, 0, &nBytes, &nInfoPrn4 );
+ if ( nBytes )
+ {
+ PRINTER_INFO_4W* pWinInfo4 = static_cast<PRINTER_INFO_4W*>(std::malloc( nBytes ));
+ if ( EnumPrintersW( PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, nullptr, 4, reinterpret_cast<LPBYTE>(pWinInfo4), nBytes, &nBytes, &nInfoPrn4 ) )
+ {
+ for ( i = 0; i < nInfoPrn4; i++ )
+ {
+ std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo);
+ pInfo->maPrinterName = o3tl::toU(pWinInfo4[i].pPrinterName);
+ pInfo->mnStatus = PrintQueueFlags::NONE;
+ pInfo->mnJobs = 0;
+ pList->Add( std::move(pInfo) );
+ }
+ }
+ std::free( pWinInfo4 );
+ }
+}
+
+void WinSalInstance::GetPrinterQueueState( SalPrinterQueueInfo* pInfo )
+{
+ HANDLE hPrinter = nullptr;
+ LPWSTR pPrnName = const_cast<LPWSTR>(o3tl::toW(pInfo->maPrinterName.getStr()));
+ if( OpenPrinterW( pPrnName, &hPrinter, nullptr ) )
+ {
+ DWORD nBytes = 0;
+ GetPrinterW( hPrinter, 2, nullptr, 0, &nBytes );
+ if( nBytes )
+ {
+ PRINTER_INFO_2W* pWinInfo2 = static_cast<PRINTER_INFO_2W*>(std::malloc(nBytes));
+ if( GetPrinterW( hPrinter, 2, reinterpret_cast<LPBYTE>(pWinInfo2), nBytes, &nBytes ) )
+ {
+ if( pWinInfo2->pDriverName )
+ pInfo->maDriver = o3tl::toU(pWinInfo2->pDriverName);
+ OUString aPortName;
+ if ( pWinInfo2->pPortName )
+ aPortName = o3tl::toU(pWinInfo2->pPortName);
+ // pLocation can be 0 (the Windows docu doesn't describe this)
+ if ( pWinInfo2->pLocation && *pWinInfo2->pLocation )
+ pInfo->maLocation = o3tl::toU(pWinInfo2->pLocation);
+ else
+ pInfo->maLocation = aPortName;
+ // pComment can be 0 (the Windows docu doesn't describe this)
+ if ( pWinInfo2->pComment )
+ pInfo->maComment = o3tl::toU(pWinInfo2->pComment);
+ pInfo->mnStatus = ImplWinQueueStatusToSal( pWinInfo2->Status );
+ pInfo->mnJobs = pWinInfo2->cJobs;
+ if( ! pInfo->moPortName )
+ pInfo->moPortName = aPortName;
+ }
+ std::free(pWinInfo2);
+ }
+ ClosePrinter( hPrinter );
+ }
+}
+
+OUString WinSalInstance::GetDefaultPrinter()
+{
+ DWORD nChars = 0;
+ GetDefaultPrinterW( nullptr, &nChars );
+ if( nChars )
+ {
+ std::vector<WCHAR> pStr(nChars);
+ if (GetDefaultPrinterW(pStr.data(), &nChars))
+ return OUString(o3tl::toU(pStr.data()));
+ }
+ return OUString();
+}
+
+static DWORD ImplDeviceCaps( WinSalInfoPrinter const * pPrinter, WORD nCaps,
+ BYTE* pOutput, const ImplJobSetup* pSetupData )
+{
+ DEVMODEW const * pDevMode;
+ if ( !pSetupData || !pSetupData->GetDriverData() )
+ pDevMode = nullptr;
+ else
+ pDevMode = SAL_DEVMODE_W( pSetupData );
+
+ return DeviceCapabilitiesW( o3tl::toW(pPrinter->maDeviceName.getStr()),
+ o3tl::toW(pPrinter->maPortName.getStr()),
+ nCaps, reinterpret_cast<LPWSTR>(pOutput), pDevMode );
+}
+
+static bool ImplTestSalJobSetup( WinSalInfoPrinter const * pPrinter,
+ ImplJobSetup* pSetupData, bool bDelete )
+{
+ if ( pSetupData && pSetupData->GetDriverData() )
+ {
+ // signature and size must fit to avoid using
+ // JobSetups from a wrong system
+
+ // initialize versions from jobsetup
+ // those will be overwritten with driver's version
+ DEVMODEW const * pDevModeW = nullptr;
+ LONG dmSpecVersion = -1;
+ LONG dmDriverVersion = -1;
+ SalDriverData const * pSalDriverData = reinterpret_cast<SalDriverData const *>(pSetupData->GetDriverData());
+ BYTE const * pDriverData = reinterpret_cast<BYTE const *>(pSalDriverData) + pSalDriverData->mnDriverOffset;
+ pDevModeW = reinterpret_cast<DEVMODEW const *>(pDriverData);
+
+ LONG nSysJobSize = -1;
+ if( pPrinter && pDevModeW )
+ {
+ // just too many driver crashes in that area -> check the dmSpecVersion and dmDriverVersion fields always !!!
+ // this prevents using the jobsetup between different Windows versions (eg from XP to 9x) but we
+ // can avoid potential driver crashes as their jobsetups are often not compatible
+ // #110800#, #111151#, #112381#, #i16580#, #i14173# and perhaps #112375#
+ HANDLE hPrn;
+ LPWSTR pPrinterNameW = const_cast<LPWSTR>(o3tl::toW(pPrinter->maDeviceName.getStr()));
+ if ( !OpenPrinterW( pPrinterNameW, &hPrn, nullptr ) )
+ return false;
+
+ // #131642# hPrn==HGDI_ERROR even though OpenPrinter() succeeded!
+ if( hPrn == HGDI_ERROR )
+ return false;
+
+ nSysJobSize = DocumentPropertiesW( nullptr, hPrn,
+ pPrinterNameW,
+ nullptr, nullptr, 0 );
+
+ if( nSysJobSize < 0 )
+ {
+ ClosePrinter( hPrn );
+ return false;
+ }
+ DEVMODEW *pBuffer = static_cast<DEVMODEW*>(_alloca( nSysJobSize ));
+ LONG nRet = DocumentPropertiesW( nullptr, hPrn,
+ pPrinterNameW,
+ pBuffer, nullptr, DM_OUT_BUFFER );
+ if( nRet < 0 )
+ {
+ ClosePrinter( hPrn );
+ return false;
+ }
+
+ // the spec version differs between the windows platforms, ie 98,NT,2000/XP
+ // this allows us to throw away printer settings from other platforms that might crash a buggy driver
+ // we check the driver version as well
+ dmSpecVersion = pBuffer->dmSpecVersion;
+ dmDriverVersion = pBuffer->dmDriverVersion;
+
+ ClosePrinter( hPrn );
+ }
+ SalDriverData const * pSetupDriverData = reinterpret_cast<SalDriverData const *>(pSetupData->GetDriverData());
+ if ( (pSetupData->GetSystem() == JOBSETUP_SYSTEM_WINDOWS) &&
+ (pPrinter->maDriverName == pSetupData->GetDriver()) &&
+ (pSetupData->GetDriverDataLen() > sizeof( SalDriverData )) &&
+ static_cast<tools::Long>(pSetupData->GetDriverDataLen() - pSetupDriverData->mnDriverOffset) == nSysJobSize &&
+ pSetupDriverData->mnSysSignature == SAL_DRIVERDATA_SYSSIGN )
+ {
+ if( pDevModeW &&
+ (dmSpecVersion == pDevModeW->dmSpecVersion) &&
+ (dmDriverVersion == pDevModeW->dmDriverVersion) )
+ return true;
+ }
+ if ( bDelete )
+ {
+ pSetupData->SetDriverData( nullptr, 0 );
+ }
+ }
+
+ return false;
+}
+
+static bool ImplUpdateSalJobSetup( WinSalInfoPrinter const * pPrinter, ImplJobSetup* pSetupData,
+ bool bIn, weld::Window* pVisibleDlgParent )
+{
+ HANDLE hPrn;
+ LPWSTR pPrinterNameW = const_cast<LPWSTR>(o3tl::toW(pPrinter->maDeviceName.getStr()));
+ if ( !OpenPrinterW( pPrinterNameW, &hPrn, nullptr ) )
+ return false;
+ // #131642# hPrn==HGDI_ERROR even though OpenPrinter() succeeded!
+ if( hPrn == HGDI_ERROR )
+ return false;
+
+ LONG nRet;
+ HWND hWnd = nullptr;
+ DWORD nMode = DM_OUT_BUFFER;
+ std::unique_ptr<sal_uInt8[]> pDriverData;
+ SalDriverData* pOutBuffer = nullptr;
+ BYTE const * pInBuffer = nullptr;
+
+ LONG nSysJobSize = DocumentPropertiesW( hWnd, hPrn,
+ pPrinterNameW,
+ nullptr, nullptr, 0 );
+ if ( nSysJobSize < 0 )
+ {
+ ClosePrinter( hPrn );
+ return false;
+ }
+
+ // make Outputbuffer
+ const std::size_t nDriverDataLen = sizeof(SalDriverData) + nSysJobSize-1;
+ pDriverData = std::make_unique<sal_uInt8[]>( nDriverDataLen );
+ memset(pDriverData.get(), 0, nDriverDataLen);
+ pOutBuffer = reinterpret_cast<SalDriverData*>(pDriverData.get());
+ pOutBuffer->mnSysSignature = SAL_DRIVERDATA_SYSSIGN;
+ // calculate driver data offset including structure padding
+ pOutBuffer->mnDriverOffset = sal::static_int_cast<sal_uInt16>(
+ reinterpret_cast<char*>(pOutBuffer->maDriverData) -
+ reinterpret_cast<char*>(pOutBuffer) );
+
+ // check if we have a suitable input buffer
+ if ( bIn && ImplTestSalJobSetup( pPrinter, pSetupData, false ) )
+ {
+ pInBuffer = pSetupData->GetDriverData() + reinterpret_cast<SalDriverData const *>(pSetupData->GetDriverData())->mnDriverOffset;
+ nMode |= DM_IN_BUFFER;
+ }
+
+ // check if the dialog should be shown
+ if ( pVisibleDlgParent )
+ {
+ hWnd = pVisibleDlgParent->get_system_data().hWnd;
+ nMode |= DM_IN_PROMPT;
+ }
+
+ // Release mutex, in the other case we don't get paints and so on
+ sal_uInt32 nMutexCount = 0;
+ WinSalInstance* pInst = GetSalData()->mpInstance;
+ if ( pInst && pVisibleDlgParent )
+ nMutexCount = pInst->ReleaseYieldMutexAll();
+
+ BYTE* pOutDevMode = reinterpret_cast<BYTE*>(pOutBuffer) + pOutBuffer->mnDriverOffset;
+ nRet = DocumentPropertiesW( hWnd, hPrn,
+ pPrinterNameW,
+ reinterpret_cast<LPDEVMODEW>(pOutDevMode), reinterpret_cast<LPDEVMODEW>(const_cast<BYTE *>(pInBuffer)), nMode );
+ if ( pInst && pVisibleDlgParent )
+ pInst->AcquireYieldMutex( nMutexCount );
+ ClosePrinter( hPrn );
+
+ if( (nRet < 0) || (pVisibleDlgParent && (nRet == IDCANCEL)) )
+ return false;
+
+ // fill up string buffers with 0 so they do not influence a JobSetup's memcmp
+ if( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmSize >= 64 )
+ {
+ sal_Int32 nLen = rtl_ustr_getLength( o3tl::toU(reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmDeviceName) );
+ if ( sal::static_int_cast<size_t>(nLen) < SAL_N_ELEMENTS( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmDeviceName ) )
+ memset( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmDeviceName+nLen, 0, sizeof( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmDeviceName )-(nLen*sizeof(sal_Unicode)) );
+ }
+ if( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmSize >= 166 )
+ {
+ sal_Int32 nLen = rtl_ustr_getLength( o3tl::toU(reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmFormName) );
+ if ( sal::static_int_cast<size_t>(nLen) < SAL_N_ELEMENTS( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmFormName ) )
+ memset( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmFormName+nLen, 0, sizeof( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmFormName )-(nLen*sizeof(sal_Unicode)) );
+ }
+
+ // update data
+ pSetupData->SetDriverData(std::move(pDriverData), nDriverDataLen);
+ pSetupData->SetSystem( JOBSETUP_SYSTEM_WINDOWS );
+
+ return true;
+}
+
+static void ImplDevModeToJobSetup( WinSalInfoPrinter const * pPrinter, ImplJobSetup* pSetupData, JobSetFlags nFlags )
+{
+ if ( !pSetupData || !pSetupData->GetDriverData() )
+ return;
+
+ DEVMODEW const * pDevModeW = SAL_DEVMODE_W(pSetupData);
+ if( pDevModeW == nullptr )
+ return;
+
+ // Orientation
+ if ( nFlags & JobSetFlags::ORIENTATION )
+ {
+ if ( pDevModeW->dmOrientation == DMORIENT_PORTRAIT )
+ pSetupData->SetOrientation( Orientation::Portrait );
+ else if ( pDevModeW->dmOrientation == DMORIENT_LANDSCAPE )
+ pSetupData->SetOrientation( Orientation::Landscape );
+ }
+
+ // PaperBin
+ if ( nFlags & JobSetFlags::PAPERBIN )
+ {
+ const DWORD nCount = ImplDeviceCaps( pPrinter, DC_BINS, nullptr, pSetupData );
+
+ if ( nCount && (nCount != GDI_ERROR) )
+ {
+ WORD* pBins = static_cast<WORD*>(rtl_allocateZeroMemory( nCount*sizeof(WORD) ));
+ ImplDeviceCaps( pPrinter, DC_BINS, reinterpret_cast<BYTE*>(pBins), pSetupData );
+ pSetupData->SetPaperBin( 0 );
+
+ // search the right bin and assign index to mnPaperBin
+ for( DWORD i = 0; i < nCount; ++i )
+ {
+ if( pDevModeW->dmDefaultSource == pBins[ i ] )
+ {
+ pSetupData->SetPaperBin( static_cast<sal_uInt16>(i) );
+ break;
+ }
+ }
+
+ std::free( pBins );
+ }
+ }
+
+ // PaperSize
+ if ( nFlags & JobSetFlags::PAPERSIZE )
+ {
+ if( (pDevModeW->dmFields & (DM_PAPERWIDTH|DM_PAPERLENGTH)) == (DM_PAPERWIDTH|DM_PAPERLENGTH) )
+ {
+ pSetupData->SetPaperWidth( pDevModeW->dmPaperWidth*10 );
+ pSetupData->SetPaperHeight( pDevModeW->dmPaperLength*10 );
+ }
+ else
+ {
+ const DWORD nPaperCount = ImplDeviceCaps( pPrinter, DC_PAPERS, nullptr, pSetupData );
+ WORD* pPapers = nullptr;
+ const DWORD nPaperSizeCount = ImplDeviceCaps( pPrinter, DC_PAPERSIZE, nullptr, pSetupData );
+ POINT* pPaperSizes = nullptr;
+ if ( nPaperCount && (nPaperCount != GDI_ERROR) )
+ {
+ pPapers = static_cast<WORD*>(rtl_allocateZeroMemory(nPaperCount*sizeof(WORD)));
+ ImplDeviceCaps( pPrinter, DC_PAPERS, reinterpret_cast<BYTE*>(pPapers), pSetupData );
+ }
+ if ( nPaperSizeCount && (nPaperSizeCount != GDI_ERROR) )
+ {
+ pPaperSizes = static_cast<POINT*>(rtl_allocateZeroMemory(nPaperSizeCount*sizeof(POINT)));
+ ImplDeviceCaps( pPrinter, DC_PAPERSIZE, reinterpret_cast<BYTE*>(pPaperSizes), pSetupData );
+ }
+ if( nPaperSizeCount == nPaperCount && pPaperSizes && pPapers )
+ {
+ for( DWORD i = 0; i < nPaperCount; ++i )
+ {
+ if( pPapers[ i ] == pDevModeW->dmPaperSize )
+ {
+ pSetupData->SetPaperWidth( pPaperSizes[ i ].x*10 );
+ pSetupData->SetPaperHeight( pPaperSizes[ i ].y*10 );
+ break;
+ }
+ }
+ }
+ if( pPapers )
+ std::free( pPapers );
+ if( pPaperSizes )
+ std::free( pPaperSizes );
+ }
+ switch( pDevModeW->dmPaperSize )
+ {
+ case DMPAPER_LETTER:
+ pSetupData->SetPaperFormat( PAPER_LETTER );
+ break;
+ case DMPAPER_TABLOID:
+ pSetupData->SetPaperFormat( PAPER_TABLOID );
+ break;
+ case DMPAPER_LEDGER:
+ pSetupData->SetPaperFormat( PAPER_LEDGER );
+ break;
+ case DMPAPER_LEGAL:
+ pSetupData->SetPaperFormat( PAPER_LEGAL );
+ break;
+ case DMPAPER_STATEMENT:
+ pSetupData->SetPaperFormat( PAPER_STATEMENT );
+ break;
+ case DMPAPER_EXECUTIVE:
+ pSetupData->SetPaperFormat( PAPER_EXECUTIVE );
+ break;
+ case DMPAPER_A3:
+ pSetupData->SetPaperFormat( PAPER_A3 );
+ break;
+ case DMPAPER_A4:
+ pSetupData->SetPaperFormat( PAPER_A4 );
+ break;
+ case DMPAPER_A5:
+ pSetupData->SetPaperFormat( PAPER_A5 );
+ break;
+ //See http://wiki.openoffice.org/wiki/DefaultPaperSize
+ //i.e.
+ //http://msdn.microsoft.com/en-us/library/dd319099(VS.85).aspx
+ //DMPAPER_B4 12 B4 (JIS) 257 x 364 mm
+ //http://partners.adobe.com/public/developer/en/ps/5003.PPD_Spec_v4.3.pdf
+ //also says that the MS DMPAPER_B4 is JIS, which makes most sense. And
+ //matches our Excel filter's belief about the matching XlPaperSize
+ //enumeration.
+
+ //http://msdn.microsoft.com/en-us/library/ms776398(VS.85).aspx said
+ ////"DMPAPER_B4 12 B4 (JIS) 250 x 354"
+ //which is bogus as it's either JIS 257 x 364 or ISO 250 x 353
+ //(cmc)
+ case DMPAPER_B4:
+ pSetupData->SetPaperFormat( PAPER_B4_JIS );
+ break;
+ case DMPAPER_B5:
+ pSetupData->SetPaperFormat( PAPER_B5_JIS );
+ break;
+ case DMPAPER_QUARTO:
+ pSetupData->SetPaperFormat( PAPER_QUARTO );
+ break;
+ case DMPAPER_10X14:
+ pSetupData->SetPaperFormat( PAPER_10x14 );
+ break;
+ case DMPAPER_NOTE:
+ pSetupData->SetPaperFormat( PAPER_LETTER );
+ break;
+ case DMPAPER_ENV_9:
+ pSetupData->SetPaperFormat( PAPER_ENV_9 );
+ break;
+ case DMPAPER_ENV_10:
+ pSetupData->SetPaperFormat( PAPER_ENV_10 );
+ break;
+ case DMPAPER_ENV_11:
+ pSetupData->SetPaperFormat( PAPER_ENV_11 );
+ break;
+ case DMPAPER_ENV_12:
+ pSetupData->SetPaperFormat( PAPER_ENV_12 );
+ break;
+ case DMPAPER_ENV_14:
+ pSetupData->SetPaperFormat( PAPER_ENV_14 );
+ break;
+ case DMPAPER_CSHEET:
+ pSetupData->SetPaperFormat( PAPER_C );
+ break;
+ case DMPAPER_DSHEET:
+ pSetupData->SetPaperFormat( PAPER_D );
+ break;
+ case DMPAPER_ESHEET:
+ pSetupData->SetPaperFormat( PAPER_E );
+ break;
+ case DMPAPER_ENV_DL:
+ pSetupData->SetPaperFormat( PAPER_ENV_DL );
+ break;
+ case DMPAPER_ENV_C5:
+ pSetupData->SetPaperFormat( PAPER_ENV_C5 );
+ break;
+ case DMPAPER_ENV_C3:
+ pSetupData->SetPaperFormat( PAPER_ENV_C3 );
+ break;
+ case DMPAPER_ENV_C4:
+ pSetupData->SetPaperFormat( PAPER_ENV_C4 );
+ break;
+ case DMPAPER_ENV_C6:
+ pSetupData->SetPaperFormat( PAPER_ENV_C6 );
+ break;
+ case DMPAPER_ENV_C65:
+ pSetupData->SetPaperFormat( PAPER_ENV_C65 );
+ break;
+ case DMPAPER_ENV_ITALY:
+ pSetupData->SetPaperFormat( PAPER_ENV_ITALY );
+ break;
+ case DMPAPER_ENV_MONARCH:
+ pSetupData->SetPaperFormat( PAPER_ENV_MONARCH );
+ break;
+ case DMPAPER_ENV_PERSONAL:
+ pSetupData->SetPaperFormat( PAPER_ENV_PERSONAL );
+ break;
+ case DMPAPER_FANFOLD_US:
+ pSetupData->SetPaperFormat( PAPER_FANFOLD_US );
+ break;
+ case DMPAPER_FANFOLD_STD_GERMAN:
+ pSetupData->SetPaperFormat( PAPER_FANFOLD_DE );
+ break;
+ case DMPAPER_FANFOLD_LGL_GERMAN:
+ pSetupData->SetPaperFormat( PAPER_FANFOLD_LEGAL_DE );
+ break;
+ case DMPAPER_ISO_B4:
+ pSetupData->SetPaperFormat( PAPER_B4_ISO );
+ break;
+ case DMPAPER_JAPANESE_POSTCARD:
+ pSetupData->SetPaperFormat( PAPER_POSTCARD_JP );
+ break;
+ case DMPAPER_9X11:
+ pSetupData->SetPaperFormat( PAPER_9x11 );
+ break;
+ case DMPAPER_10X11:
+ pSetupData->SetPaperFormat( PAPER_10x11 );
+ break;
+ case DMPAPER_15X11:
+ pSetupData->SetPaperFormat( PAPER_15x11 );
+ break;
+ case DMPAPER_ENV_INVITE:
+ pSetupData->SetPaperFormat( PAPER_ENV_INVITE );
+ break;
+ case DMPAPER_A_PLUS:
+ pSetupData->SetPaperFormat( PAPER_A_PLUS );
+ break;
+ case DMPAPER_B_PLUS:
+ pSetupData->SetPaperFormat( PAPER_B_PLUS );
+ break;
+ case DMPAPER_LETTER_PLUS:
+ pSetupData->SetPaperFormat( PAPER_LETTER_PLUS );
+ break;
+ case DMPAPER_A4_PLUS:
+ pSetupData->SetPaperFormat( PAPER_A4_PLUS );
+ break;
+ case DMPAPER_A2:
+ pSetupData->SetPaperFormat( PAPER_A2 );
+ break;
+ case DMPAPER_DBL_JAPANESE_POSTCARD:
+ pSetupData->SetPaperFormat( PAPER_DOUBLEPOSTCARD_JP );
+ break;
+ case DMPAPER_A6:
+ pSetupData->SetPaperFormat( PAPER_A6 );
+ break;
+ case DMPAPER_B6_JIS:
+ pSetupData->SetPaperFormat( PAPER_B6_JIS );
+ break;
+ case DMPAPER_12X11:
+ pSetupData->SetPaperFormat( PAPER_12x11 );
+ break;
+ default:
+ pSetupData->SetPaperFormat( PAPER_USER );
+ break;
+ }
+ }
+
+ if( nFlags & JobSetFlags::DUPLEXMODE )
+ {
+ DuplexMode eDuplex = DuplexMode::Unknown;
+ if( pDevModeW->dmFields & DM_DUPLEX )
+ {
+ if( pDevModeW->dmDuplex == DMDUP_SIMPLEX )
+ eDuplex = DuplexMode::Off;
+ else if( pDevModeW->dmDuplex == DMDUP_VERTICAL )
+ eDuplex = DuplexMode::LongEdge;
+ else if( pDevModeW->dmDuplex == DMDUP_HORIZONTAL )
+ eDuplex = DuplexMode::ShortEdge;
+ }
+ pSetupData->SetDuplexMode( eDuplex );
+ }
+}
+
+static void ImplJobSetupToDevMode( WinSalInfoPrinter const * pPrinter, const ImplJobSetup* pSetupData, JobSetFlags nFlags )
+{
+ if ( !pSetupData || !pSetupData->GetDriverData() )
+ return;
+
+ DEVMODEW* pDevModeW = const_cast<DEVMODEW *>(SAL_DEVMODE_W(pSetupData));
+ if( pDevModeW == nullptr )
+ return;
+
+ // Orientation
+ if ( nFlags & JobSetFlags::ORIENTATION )
+ {
+ pDevModeW->dmFields |= DM_ORIENTATION;
+ if ( pSetupData->GetOrientation() == Orientation::Portrait )
+ pDevModeW->dmOrientation = DMORIENT_PORTRAIT;
+ else
+ pDevModeW->dmOrientation = DMORIENT_LANDSCAPE;
+ }
+
+ // PaperBin
+ if ( nFlags & JobSetFlags::PAPERBIN )
+ {
+ const DWORD nCount = ImplDeviceCaps( pPrinter, DC_BINS, nullptr, pSetupData );
+
+ if ( nCount && (nCount != GDI_ERROR) )
+ {
+ WORD* pBins = static_cast<WORD*>(rtl_allocateZeroMemory(nCount*sizeof(WORD)));
+ ImplDeviceCaps( pPrinter, DC_BINS, reinterpret_cast<BYTE*>(pBins), pSetupData );
+ pDevModeW->dmFields |= DM_DEFAULTSOURCE;
+ pDevModeW->dmDefaultSource = pBins[ pSetupData->GetPaperBin() ];
+ std::free( pBins );
+ }
+ }
+
+ // PaperSize
+ if ( nFlags & JobSetFlags::PAPERSIZE )
+ {
+ pDevModeW->dmFields |= DM_PAPERSIZE;
+ pDevModeW->dmPaperWidth = 0;
+ pDevModeW->dmPaperLength = 0;
+
+ switch( pSetupData->GetPaperFormat() )
+ {
+ case PAPER_A2:
+ pDevModeW->dmPaperSize = DMPAPER_A2;
+ break;
+ case PAPER_A3:
+ pDevModeW->dmPaperSize = DMPAPER_A3;
+ break;
+ case PAPER_A4:
+ pDevModeW->dmPaperSize = DMPAPER_A4;
+ break;
+ case PAPER_A5:
+ pDevModeW->dmPaperSize = DMPAPER_A5;
+ break;
+ case PAPER_B4_ISO:
+ pDevModeW->dmPaperSize = DMPAPER_ISO_B4;
+ break;
+ case PAPER_LETTER:
+ pDevModeW->dmPaperSize = DMPAPER_LETTER;
+ break;
+ case PAPER_LEGAL:
+ pDevModeW->dmPaperSize = DMPAPER_LEGAL;
+ break;
+ case PAPER_TABLOID:
+ pDevModeW->dmPaperSize = DMPAPER_TABLOID;
+ break;
+
+ // http://msdn.microsoft.com/en-us/library/ms776398(VS.85).aspx
+ // DMPAPER_ENV_B6 is documented as:
+ // "DMPAPER_ENV_B6 35 Envelope B6 176 x 125 mm"
+ // which is the wrong way around, it is surely 125 x 176, i.e.
+ // compare DMPAPER_ENV_B4 and DMPAPER_ENV_B4 as
+ // DMPAPER_ENV_B4 33 Envelope B4 250 x 353 mm
+ // DMPAPER_ENV_B5 34 Envelope B5 176 x 250 mm
+
+ case PAPER_ENV_C4:
+ pDevModeW->dmPaperSize = DMPAPER_ENV_C4;
+ break;
+ case PAPER_ENV_C5:
+ pDevModeW->dmPaperSize = DMPAPER_ENV_C5;
+ break;
+ case PAPER_ENV_C6:
+ pDevModeW->dmPaperSize = DMPAPER_ENV_C6;
+ break;
+ case PAPER_ENV_C65:
+ pDevModeW->dmPaperSize = DMPAPER_ENV_C65;
+ break;
+ case PAPER_ENV_DL:
+ pDevModeW->dmPaperSize = DMPAPER_ENV_DL;
+ break;
+ case PAPER_C:
+ pDevModeW->dmPaperSize = DMPAPER_CSHEET;
+ break;
+ case PAPER_D:
+ pDevModeW->dmPaperSize = DMPAPER_DSHEET;
+ break;
+ case PAPER_E:
+ pDevModeW->dmPaperSize = DMPAPER_ESHEET;
+ break;
+ case PAPER_EXECUTIVE:
+ pDevModeW->dmPaperSize = DMPAPER_EXECUTIVE;
+ break;
+ case PAPER_FANFOLD_LEGAL_DE:
+ pDevModeW->dmPaperSize = DMPAPER_FANFOLD_LGL_GERMAN;
+ break;
+ case PAPER_ENV_MONARCH:
+ pDevModeW->dmPaperSize = DMPAPER_ENV_MONARCH;
+ break;
+ case PAPER_ENV_PERSONAL:
+ pDevModeW->dmPaperSize = DMPAPER_ENV_PERSONAL;
+ break;
+ case PAPER_ENV_9:
+ pDevModeW->dmPaperSize = DMPAPER_ENV_9;
+ break;
+ case PAPER_ENV_10:
+ pDevModeW->dmPaperSize = DMPAPER_ENV_10;
+ break;
+ case PAPER_ENV_11:
+ pDevModeW->dmPaperSize = DMPAPER_ENV_11;
+ break;
+ case PAPER_ENV_12:
+ pDevModeW->dmPaperSize = DMPAPER_ENV_12;
+ break;
+ //See the comments on DMPAPER_B4 above
+ case PAPER_B4_JIS:
+ pDevModeW->dmPaperSize = DMPAPER_B4;
+ break;
+ case PAPER_B5_JIS:
+ pDevModeW->dmPaperSize = DMPAPER_B5;
+ break;
+ case PAPER_B6_JIS:
+ pDevModeW->dmPaperSize = DMPAPER_B6_JIS;
+ break;
+ case PAPER_LEDGER:
+ pDevModeW->dmPaperSize = DMPAPER_LEDGER;
+ break;
+ case PAPER_STATEMENT:
+ pDevModeW->dmPaperSize = DMPAPER_STATEMENT;
+ break;
+ case PAPER_10x14:
+ pDevModeW->dmPaperSize = DMPAPER_10X14;
+ break;
+ case PAPER_ENV_14:
+ pDevModeW->dmPaperSize = DMPAPER_ENV_14;
+ break;
+ case PAPER_ENV_C3:
+ pDevModeW->dmPaperSize = DMPAPER_ENV_C3;
+ break;
+ case PAPER_ENV_ITALY:
+ pDevModeW->dmPaperSize = DMPAPER_ENV_ITALY;
+ break;
+ case PAPER_FANFOLD_US:
+ pDevModeW->dmPaperSize = DMPAPER_FANFOLD_US;
+ break;
+ case PAPER_FANFOLD_DE:
+ pDevModeW->dmPaperSize = DMPAPER_FANFOLD_STD_GERMAN;
+ break;
+ case PAPER_POSTCARD_JP:
+ pDevModeW->dmPaperSize = DMPAPER_JAPANESE_POSTCARD;
+ break;
+ case PAPER_9x11:
+ pDevModeW->dmPaperSize = DMPAPER_9X11;
+ break;
+ case PAPER_10x11:
+ pDevModeW->dmPaperSize = DMPAPER_10X11;
+ break;
+ case PAPER_15x11:
+ pDevModeW->dmPaperSize = DMPAPER_15X11;
+ break;
+ case PAPER_ENV_INVITE:
+ pDevModeW->dmPaperSize = DMPAPER_ENV_INVITE;
+ break;
+ case PAPER_A_PLUS:
+ pDevModeW->dmPaperSize = DMPAPER_A_PLUS;
+ break;
+ case PAPER_B_PLUS:
+ pDevModeW->dmPaperSize = DMPAPER_B_PLUS;
+ break;
+ case PAPER_LETTER_PLUS:
+ pDevModeW->dmPaperSize = DMPAPER_LETTER_PLUS;
+ break;
+ case PAPER_A4_PLUS:
+ pDevModeW->dmPaperSize = DMPAPER_A4_PLUS;
+ break;
+ case PAPER_DOUBLEPOSTCARD_JP:
+ pDevModeW->dmPaperSize = DMPAPER_DBL_JAPANESE_POSTCARD;
+ break;
+ case PAPER_A6:
+ pDevModeW->dmPaperSize = DMPAPER_A6;
+ break;
+ case PAPER_12x11:
+ pDevModeW->dmPaperSize = DMPAPER_12X11;
+ break;
+ default:
+ {
+ short nPaper = 0;
+ const DWORD nPaperCount = ImplDeviceCaps( pPrinter, DC_PAPERS, nullptr, pSetupData );
+ WORD* pPapers = nullptr;
+ const DWORD nPaperSizeCount = ImplDeviceCaps( pPrinter, DC_PAPERSIZE, nullptr, pSetupData );
+ POINT* pPaperSizes = nullptr;
+ DWORD nLandscapeAngle = ImplDeviceCaps( pPrinter, DC_ORIENTATION, nullptr, pSetupData );
+ if ( nPaperCount && (nPaperCount != GDI_ERROR) )
+ {
+ pPapers = static_cast<WORD*>(rtl_allocateZeroMemory(nPaperCount*sizeof(WORD)));
+ ImplDeviceCaps( pPrinter, DC_PAPERS, reinterpret_cast<BYTE*>(pPapers), pSetupData );
+ }
+ if ( nPaperSizeCount && (nPaperSizeCount != GDI_ERROR) )
+ {
+ pPaperSizes = static_cast<POINT*>(rtl_allocateZeroMemory(nPaperSizeCount*sizeof(POINT)));
+ ImplDeviceCaps( pPrinter, DC_PAPERSIZE, reinterpret_cast<BYTE*>(pPaperSizes), pSetupData );
+ }
+ if ( (nPaperSizeCount == nPaperCount) && pPapers && pPaperSizes )
+ {
+ PaperInfo aInfo(pSetupData->GetPaperWidth(), pSetupData->GetPaperHeight());
+ // compare paper formats and select a good match
+ for ( DWORD i = 0; i < nPaperCount; ++i )
+ {
+ if ( aInfo.sloppyEqual(PaperInfo(pPaperSizes[i].x*10, pPaperSizes[i].y*10)))
+ {
+ nPaper = pPapers[i];
+ break;
+ }
+ }
+
+ // 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 ( !nPaper && nLandscapeAngle != 0 )
+ {
+ PaperInfo aRotatedInfo(pSetupData->GetPaperHeight(), pSetupData->GetPaperWidth());
+ for ( DWORD i = 0; i < nPaperCount; ++i )
+ {
+ if ( aRotatedInfo.sloppyEqual(PaperInfo(pPaperSizes[i].x*10, pPaperSizes[i].y*10)) )
+ {
+ nPaper = pPapers[i];
+ break;
+ }
+ }
+ }
+
+ if ( nPaper )
+ pDevModeW->dmPaperSize = nPaper;
+ }
+
+ if ( !nPaper )
+ {
+ pDevModeW->dmFields |= DM_PAPERLENGTH | DM_PAPERWIDTH;
+ pDevModeW->dmPaperSize = DMPAPER_USER;
+ pDevModeW->dmPaperWidth = static_cast<short>(pSetupData->GetPaperWidth()/10);
+ pDevModeW->dmPaperLength = static_cast<short>(pSetupData->GetPaperHeight()/10);
+ }
+
+ if ( pPapers )
+ std::free(pPapers);
+ if ( pPaperSizes )
+ std::free(pPaperSizes);
+
+ break;
+ }
+ }
+ }
+ if( nFlags & JobSetFlags::DUPLEXMODE )
+ {
+ switch( pSetupData->GetDuplexMode() )
+ {
+ case DuplexMode::Off:
+ pDevModeW->dmFields |= DM_DUPLEX;
+ pDevModeW->dmDuplex = DMDUP_SIMPLEX;
+ break;
+ case DuplexMode::ShortEdge:
+ pDevModeW->dmFields |= DM_DUPLEX;
+ pDevModeW->dmDuplex = DMDUP_HORIZONTAL;
+ break;
+ case DuplexMode::LongEdge:
+ pDevModeW->dmFields |= DM_DUPLEX;
+ pDevModeW->dmDuplex = DMDUP_VERTICAL;
+ break;
+ case DuplexMode::Unknown:
+ break;
+ }
+ }
+}
+
+static HDC ImplCreateICW_WithCatch( LPWSTR pDriver,
+ LPCWSTR pDevice,
+ DEVMODEW const * pDevMode )
+{
+ HDC hDC = nullptr;
+ CATCH_DRIVER_EX_BEGIN;
+ hDC = CreateICW( pDriver, pDevice, nullptr, pDevMode );
+ CATCH_DRIVER_EX_END_2( "exception in CreateICW" );
+ return hDC;
+}
+
+static HDC ImplCreateSalPrnIC( WinSalInfoPrinter const * pPrinter, const ImplJobSetup* pSetupData )
+{
+ HDC hDC = nullptr;
+ DEVMODEW const * pDevMode;
+ if ( pSetupData && pSetupData->GetDriverData() )
+ pDevMode = SAL_DEVMODE_W( pSetupData );
+ else
+ pDevMode = nullptr;
+ // #95347 some buggy drivers (eg, OKI) write to those buffers in CreateIC, although declared const - so provide some space
+ // pl: does this hold true for Unicode functions ?
+ if( pPrinter->maDriverName.getLength() > 2048 || pPrinter->maDeviceName.getLength() > 2048 )
+ return nullptr;
+ sal_Unicode pDriverName[ 4096 ];
+ sal_Unicode pDeviceName[ 4096 ];
+ memcpy( pDriverName, pPrinter->maDriverName.getStr(), pPrinter->maDriverName.getLength()*sizeof(sal_Unicode));
+ memset( pDriverName+pPrinter->maDriverName.getLength(), 0, 32 );
+ memcpy( pDeviceName, pPrinter->maDeviceName.getStr(), pPrinter->maDeviceName.getLength()*sizeof(sal_Unicode));
+ memset( pDeviceName+pPrinter->maDeviceName.getLength(), 0, 32 );
+ hDC = ImplCreateICW_WithCatch( o3tl::toW(pDriverName),
+ o3tl::toW(pDeviceName),
+ pDevMode );
+ return hDC;
+}
+
+static WinSalGraphics* ImplCreateSalPrnGraphics( HDC hDC )
+{
+ WinSalGraphics* pGraphics = new WinSalGraphics(WinSalGraphics::PRINTER, false, nullptr, /* CHECKME */ nullptr);
+ pGraphics->SetLayout( SalLayoutFlags::NONE );
+ pGraphics->setHDC(hDC);
+ return pGraphics;
+}
+
+static bool ImplUpdateSalPrnIC( WinSalInfoPrinter* pPrinter, const ImplJobSetup* pSetupData )
+{
+ HDC hNewDC = ImplCreateSalPrnIC( pPrinter, pSetupData );
+ if ( !hNewDC )
+ return false;
+
+ pPrinter->setHDC(hNewDC);
+ return true;
+}
+
+
+SalInfoPrinter* WinSalInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pSetupData )
+{
+ WinSalInfoPrinter* pPrinter = new WinSalInfoPrinter;
+ if( ! pQueueInfo->moPortName )
+ GetPrinterQueueState( pQueueInfo );
+ pPrinter->maDriverName = pQueueInfo->maDriver;
+ pPrinter->maDeviceName = pQueueInfo->maPrinterName;
+ pPrinter->maPortName = pQueueInfo->moPortName ? *pQueueInfo->moPortName : OUString();
+
+ // check if the provided setup data match the actual printer
+ ImplTestSalJobSetup( pPrinter, pSetupData, true );
+
+ HDC hDC = ImplCreateSalPrnIC( pPrinter, pSetupData );
+ if ( !hDC )
+ {
+ delete pPrinter;
+ return nullptr;
+ }
+
+ pPrinter->setHDC(hDC);
+ if ( !pSetupData->GetDriverData() )
+ ImplUpdateSalJobSetup( pPrinter, pSetupData, false, nullptr );
+ ImplDevModeToJobSetup( pPrinter, pSetupData, JobSetFlags::ALL );
+ pSetupData->SetSystem( JOBSETUP_SYSTEM_WINDOWS );
+
+ return pPrinter;
+}
+
+void WinSalInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter )
+{
+ delete pPrinter;
+}
+
+
+WinSalInfoPrinter::WinSalInfoPrinter() :
+ m_hDC(nullptr),
+ m_pGraphics(nullptr),
+ m_bGraphics(false)
+{
+ m_bPapersInit = false;
+}
+
+WinSalInfoPrinter::~WinSalInfoPrinter()
+{
+ setHDC(nullptr);
+}
+
+void WinSalInfoPrinter::setHDC(HDC hNewDC)
+{
+ assert(!m_bGraphics);
+
+ if (m_hDC)
+ {
+ assert(!m_pGraphics || m_hDC == m_pGraphics->getHDC());
+ delete m_pGraphics;
+ m_pGraphics = nullptr;
+ DeleteDC(m_hDC);
+ }
+
+ m_hDC = hNewDC;
+}
+
+void WinSalInfoPrinter::InitPaperFormats( const ImplJobSetup* pSetupData )
+{
+ m_aPaperFormats.clear();
+
+ DWORD nCount = ImplDeviceCaps( this, DC_PAPERSIZE, nullptr, pSetupData );
+ if( nCount == GDI_ERROR )
+ nCount = 0;
+
+ if( nCount )
+ {
+ POINT* pPaperSizes = static_cast<POINT*>(rtl_allocateZeroMemory(nCount*sizeof(POINT)));
+ ImplDeviceCaps( this, DC_PAPERSIZE, reinterpret_cast<BYTE*>(pPaperSizes), pSetupData );
+
+ sal_Unicode* pNamesBuffer = static_cast<sal_Unicode*>(std::malloc(nCount*64*sizeof(sal_Unicode)));
+ ImplDeviceCaps( this, DC_PAPERNAMES, reinterpret_cast<BYTE*>(pNamesBuffer), pSetupData );
+
+ SAL_INFO("vcl.print", "DC_PAPERSIZE sizes (mm) from printer: " << DC_PAPERSIZE_array_to_string(pPaperSizes, nCount));
+
+ for( DWORD i = 0; i < nCount; ++i )
+ {
+ PaperInfo aInfo(pPaperSizes[i].x * 10, pPaperSizes[i].y * 10);
+ m_aPaperFormats.push_back( aInfo );
+ }
+ std::free( pNamesBuffer );
+ std::free( pPaperSizes );
+ }
+
+ m_bPapersInit = true;
+}
+
+int WinSalInfoPrinter::GetLandscapeAngle( const ImplJobSetup* pSetupData )
+{
+ const DWORD nRet = ImplDeviceCaps( this, DC_ORIENTATION, nullptr, pSetupData );
+
+ if( nRet != GDI_ERROR )
+ return static_cast<int>(nRet) * 10;
+ return 900; // guess
+}
+
+SalGraphics* WinSalInfoPrinter::AcquireGraphics()
+{
+ assert(m_hDC);
+ if (m_bGraphics)
+ return nullptr;
+
+ if (!m_pGraphics)
+ m_pGraphics = ImplCreateSalPrnGraphics(m_hDC);
+ if (m_pGraphics)
+ m_bGraphics = true;
+
+ return m_pGraphics;
+}
+
+void WinSalInfoPrinter::ReleaseGraphics( SalGraphics* )
+{
+ m_bGraphics = false;
+}
+
+bool WinSalInfoPrinter::Setup(weld::Window* pFrame, ImplJobSetup* pSetupData)
+{
+ if ( ImplUpdateSalJobSetup(this, pSetupData, true, pFrame))
+ {
+ ImplDevModeToJobSetup( this, pSetupData, JobSetFlags::ALL );
+ return ImplUpdateSalPrnIC( this, pSetupData );
+ }
+
+ return false;
+}
+
+bool WinSalInfoPrinter::SetPrinterData( ImplJobSetup* pSetupData )
+{
+ if ( !ImplTestSalJobSetup( this, pSetupData, false ) )
+ return false;
+ return ImplUpdateSalPrnIC( this, pSetupData );
+}
+
+bool WinSalInfoPrinter::SetData( JobSetFlags nFlags, ImplJobSetup* pSetupData )
+{
+ ImplJobSetupToDevMode( this, pSetupData, nFlags );
+ if ( ImplUpdateSalJobSetup( this, pSetupData, true, nullptr ) )
+ {
+ ImplDevModeToJobSetup( this, pSetupData, nFlags );
+ return ImplUpdateSalPrnIC( this, pSetupData );
+ }
+
+ return false;
+}
+
+sal_uInt16 WinSalInfoPrinter::GetPaperBinCount( const ImplJobSetup* pSetupData )
+{
+ DWORD nRet = ImplDeviceCaps( this, DC_BINS, nullptr, pSetupData );
+ if ( nRet && (nRet != GDI_ERROR) )
+ return nRet;
+ else
+ return 0;
+}
+
+OUString WinSalInfoPrinter::GetPaperBinName( const ImplJobSetup* pSetupData, sal_uInt16 nPaperBin )
+{
+ OUString aPaperBinName;
+
+ DWORD nBins = ImplDeviceCaps( this, DC_BINNAMES, nullptr, pSetupData );
+ if ( (nPaperBin < nBins) && (nBins != GDI_ERROR) )
+ {
+ auto pBuffer = std::make_unique<sal_Unicode[]>(nBins*24);
+ DWORD nRet = ImplDeviceCaps( this, DC_BINNAMES, reinterpret_cast<BYTE*>(pBuffer.get()), pSetupData );
+ if ( nRet && (nRet != GDI_ERROR) )
+ aPaperBinName = OUString( pBuffer.get() + (nPaperBin*24) );
+ }
+
+ return aPaperBinName;
+}
+
+sal_uInt32 WinSalInfoPrinter::GetCapabilities( const ImplJobSetup* pSetupData, PrinterCapType nType )
+{
+ DWORD nRet;
+
+ switch ( nType )
+ {
+ case PrinterCapType::SupportDialog:
+ return TRUE;
+ case PrinterCapType::Copies:
+ nRet = ImplDeviceCaps( this, DC_COPIES, nullptr, pSetupData );
+ if ( nRet && (nRet != GDI_ERROR) )
+ return nRet;
+ return 0;
+ case PrinterCapType::CollateCopies:
+ nRet = ImplDeviceCaps( this, DC_COLLATE, nullptr, pSetupData );
+ if ( nRet && (nRet != GDI_ERROR) )
+ {
+ nRet = ImplDeviceCaps( this, DC_COPIES, nullptr, pSetupData );
+ if ( nRet && (nRet != GDI_ERROR) )
+ return nRet;
+ }
+ return 0;
+
+ case PrinterCapType::SetOrientation:
+ nRet = ImplDeviceCaps( this, DC_ORIENTATION, nullptr, pSetupData );
+ if ( nRet && (nRet != GDI_ERROR) )
+ return TRUE;
+ return FALSE;
+
+ case PrinterCapType::SetPaperSize:
+ case PrinterCapType::SetPaper:
+ nRet = ImplDeviceCaps( this, DC_PAPERS, nullptr, pSetupData );
+ if ( nRet && (nRet != GDI_ERROR) )
+ return TRUE;
+ return FALSE;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+void WinSalInfoPrinter::GetPageInfo( const ImplJobSetup*,
+ tools::Long& rOutWidth, tools::Long& rOutHeight,
+ Point& rPageOffset,
+ Size& rPaperSize )
+{
+ HDC hDC = m_hDC;
+
+ rOutWidth = GetDeviceCaps( hDC, HORZRES );
+ rOutHeight = GetDeviceCaps( hDC, VERTRES );
+
+ rPageOffset.setX( GetDeviceCaps( hDC, PHYSICALOFFSETX ) );
+ rPageOffset.setY( GetDeviceCaps( hDC, PHYSICALOFFSETY ) );
+ rPaperSize.setWidth( GetDeviceCaps( hDC, PHYSICALWIDTH ) );
+ rPaperSize.setHeight( GetDeviceCaps( hDC, PHYSICALHEIGHT ) );
+}
+
+
+std::unique_ptr<SalPrinter> WinSalInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
+{
+ WinSalPrinter* pPrinter = new WinSalPrinter;
+ pPrinter->mpInfoPrinter = static_cast<WinSalInfoPrinter*>(pInfoPrinter);
+ return std::unique_ptr<SalPrinter>(pPrinter);
+}
+
+static BOOL CALLBACK SalPrintAbortProc( HDC hPrnDC, int /* nError */ )
+{
+ SalData* pSalData = GetSalData();
+ WinSalPrinter* pPrinter;
+ int i = 0;
+ bool bWhile = true;
+
+ // Ensure we handle the mutex which will be released in WinSalInstance::DoYield
+ SolarMutexGuard aSolarMutexGuard;
+ do
+ {
+ // process messages
+ bWhile = Application::Reschedule( true );
+ if (i > 15)
+ bWhile = false;
+ else
+ ++i;
+
+ pPrinter = pSalData->mpFirstPrinter;
+ while ( pPrinter )
+ {
+ if( pPrinter->mhDC == hPrnDC )
+ break;
+
+ pPrinter = pPrinter->mpNextPrinter;
+ }
+
+ if ( !pPrinter || pPrinter->mbAbort )
+ return FALSE;
+ }
+ while ( bWhile );
+
+ return TRUE;
+}
+
+static DEVMODEW const * ImplSalSetCopies( DEVMODEW const * pDevMode, sal_uInt32 nCopies, bool bCollate )
+{
+ if ( pDevMode && (nCopies > 1) )
+ {
+ if ( nCopies > 32765 )
+ nCopies = 32765;
+ sal_uLong nDevSize = pDevMode->dmSize+pDevMode->dmDriverExtra;
+ LPDEVMODEW pNewDevMode = static_cast<LPDEVMODEW>(std::malloc( nDevSize ));
+ assert(pNewDevMode); // Don't handle OOM conditions
+ memcpy( pNewDevMode, pDevMode, nDevSize );
+ pNewDevMode->dmFields |= DM_COPIES;
+ pNewDevMode->dmCopies = static_cast<short>(static_cast<sal_uInt16>(nCopies));
+ pNewDevMode->dmFields |= DM_COLLATE;
+ if ( bCollate )
+ pNewDevMode->dmCollate = DMCOLLATE_TRUE;
+ else
+ pNewDevMode->dmCollate = DMCOLLATE_FALSE;
+ return pNewDevMode;
+ }
+ else
+ {
+ return pDevMode;
+ }
+}
+
+
+WinSalPrinter::WinSalPrinter() :
+ mpInfoPrinter( nullptr ),
+ mpNextPrinter( nullptr ),
+ mhDC( nullptr ),
+ mnError( SalPrinterError::NONE ),
+ mnCopies( 0 ),
+ mbCollate( false ),
+ mbAbort( false ),
+ mbValid( true )
+{
+ SalData* pSalData = GetSalData();
+ // insert printer in printerlist
+ mpNextPrinter = pSalData->mpFirstPrinter;
+ pSalData->mpFirstPrinter = this;
+}
+
+WinSalPrinter::~WinSalPrinter()
+{
+ SalData* pSalData = GetSalData();
+
+ // release DC if there is one still around because of AbortJob
+ HDC hDC = mhDC;
+ if ( hDC )
+ {
+ // explicitly reset(), so the mxGraphics's borrowed HDC defaults are
+ // restored and WinSalGraphics's destructor won't work on a deleted HDC.
+ mxGraphics.reset();
+ DeleteDC( hDC );
+ }
+
+ // remove printer from printerlist
+ if ( this == pSalData->mpFirstPrinter )
+ pSalData->mpFirstPrinter = mpNextPrinter;
+ else
+ {
+ WinSalPrinter* pTempPrinter = pSalData->mpFirstPrinter;
+
+ while( pTempPrinter->mpNextPrinter != this )
+ pTempPrinter = pTempPrinter->mpNextPrinter;
+
+ pTempPrinter->mpNextPrinter = mpNextPrinter;
+ }
+}
+
+void WinSalPrinter::markInvalid()
+{
+ mbValid = false;
+}
+
+// need wrappers for StarTocW/A to use structured exception handling
+// since SEH does not mix with standard exception handling's cleanup
+static int lcl_StartDocW1( HDC hDC, DOCINFOW const * pInfo, WinSalPrinter* pPrt )
+{
+ int nRet = 0;
+ CATCH_DRIVER_EX_BEGIN;
+ nRet = ::StartDocW( hDC, pInfo );
+ CATCH_DRIVER_EX_END( "exception in StartDocW", pPrt );
+ return nRet;
+}
+
+static int lcl_StartDocW( HDC hDC, DOCINFOW const * pInfo, WinSalPrinter* pPrt )
+{
+ //tdf#127547 - Freeze/crash in Microsoft Print to PDF dialog, if we try to paste while
+ // executing the StartDocW method, Windows will call back into us on a separate thread,
+ // where we will attempt to take the SolarMutex.
+ SolarMutexReleaser aReleaser;
+
+ return lcl_StartDocW1(hDC, pInfo, pPrt);
+}
+
+bool WinSalPrinter::StartJob( const OUString* pFileName,
+ const OUString& rJobName,
+ const OUString&,
+ sal_uInt32 nCopies,
+ bool bCollate,
+ bool /*bDirect*/,
+ ImplJobSetup* pSetupData )
+{
+ mnError = SalPrinterError::NONE;
+ mbAbort = false;
+ mnCopies = nCopies;
+ mbCollate = bCollate;
+
+ DEVMODEW const * pOrgDevModeW = nullptr;
+ DEVMODEW const * pDevModeW = nullptr;
+ HDC hDC = nullptr;
+ if ( pSetupData && pSetupData->GetDriverData() )
+ {
+ pOrgDevModeW = SAL_DEVMODE_W( pSetupData );
+ pDevModeW = ImplSalSetCopies( pOrgDevModeW, nCopies, bCollate );
+ }
+
+ // #95347 some buggy drivers (eg, OKI) write to those buffers in CreateDC, although declared const - so provide some space
+ sal_Unicode aDrvBuf[4096];
+ sal_Unicode aDevBuf[4096];
+ memcpy( aDrvBuf, mpInfoPrinter->maDriverName.getStr(), (mpInfoPrinter->maDriverName.getLength()+1)*sizeof(sal_Unicode));
+ memcpy( aDevBuf, mpInfoPrinter->maDeviceName.getStr(), (mpInfoPrinter->maDeviceName.getLength()+1)*sizeof(sal_Unicode));
+ hDC = CreateDCW( o3tl::toW(aDrvBuf),
+ o3tl::toW(aDevBuf),
+ nullptr,
+ pDevModeW );
+
+ if ( pDevModeW != pOrgDevModeW )
+ std::free( const_cast<DEVMODEW *>(pDevModeW) );
+
+ if ( !hDC )
+ {
+ mnError = SalPrinterError::General;
+ return false;
+ }
+
+ // make sure mhDC is set before the printer driver may call our abortproc
+ mhDC = hDC;
+ if ( SetAbortProc( hDC, SalPrintAbortProc ) <= 0 )
+ {
+ mnError = SalPrinterError::General;
+ return false;
+ }
+
+ mnError = SalPrinterError::NONE;
+ mbAbort = false;
+
+ // As the Telecom Balloon Fax driver tends to send messages repeatedly
+ // we try to process first all, and then insert a dummy message
+ for (int i = 0; Application::Reschedule( true ) && i <= 15; ++i);
+ bool const ret = PostMessageW(GetSalData()->mpInstance->mhComWnd, SAL_MSG_DUMMY, 0, 0);
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+
+ // bring up a file chooser if printing to file port but no file name given
+ OUString aOutFileName;
+ if( mpInfoPrinter->maPortName.equalsIgnoreAsciiCase( "FILE:" ) && (!pFileName || pFileName->isEmpty()) )
+ {
+
+ uno::Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
+ uno::Reference< XFilePicker3 > xFilePicker = FilePicker::createWithMode(xContext, TemplateDescription::FILESAVE_SIMPLE);
+
+ if( xFilePicker->execute() == ExecutableDialogResults::OK )
+ {
+ Sequence< OUString > aPathSeq( xFilePicker->getSelectedFiles() );
+ INetURLObject aObj( aPathSeq[0] );
+ aOutFileName = aObj.PathToFileName();
+ }
+ else
+ {
+ mnError = SalPrinterError::Abort;
+ return false;
+ }
+ }
+
+ DOCINFOW aInfo = {};
+ aInfo.cbSize = sizeof( aInfo );
+ aInfo.lpszDocName = o3tl::toW(rJobName.getStr());
+ if ( pFileName || aOutFileName.getLength() )
+ {
+ if ( (pFileName && !pFileName->isEmpty()) || aOutFileName.getLength() )
+ {
+ aInfo.lpszOutput = o3tl::toW((pFileName && !pFileName->isEmpty()) ? pFileName->getStr() : aOutFileName.getStr());
+ }
+ else
+ aInfo.lpszOutput = L"FILE:";
+ }
+ else
+ aInfo.lpszOutput = nullptr;
+
+ // start Job, in the main thread
+ int nRet = vcl::solarthread::syncExecute([hDC, this, &aInfo]() -> int { return lcl_StartDocW(hDC, &aInfo, this); });
+
+ if ( nRet <= 0 )
+ {
+ DWORD nError = GetLastError();
+ if ( (nRet == SP_USERABORT) || (nRet == SP_APPABORT) || (nError == ERROR_PRINT_CANCELLED) || (nError == ERROR_CANCELLED) )
+ mnError = SalPrinterError::Abort;
+ else
+ mnError = SalPrinterError::General;
+ return false;
+ }
+
+ return true;
+}
+
+void WinSalPrinter::DoEndDoc(HDC hDC)
+{
+ CATCH_DRIVER_EX_BEGIN;
+ if( ::EndDoc( hDC ) <= 0 )
+ GetLastError();
+ CATCH_DRIVER_EX_END( "exception in EndDoc", this );
+}
+
+bool WinSalPrinter::EndJob()
+{
+ HDC hDC = mhDC;
+ if (isValid())
+ {
+ mxGraphics.reset();
+
+ // #i54419# Windows fax printer brings up a dialog in EndDoc
+ // which text previously copied in soffice process can be
+ // pasted to -> deadlock due to mutex not released.
+ // it should be safe to release the yield mutex over the EndDoc
+ // call, however the real solution is supposed to be the threading
+ // framework yet to come.
+ {
+ SolarMutexReleaser aReleaser;
+ DoEndDoc( hDC );
+ }
+ DeleteDC( hDC );
+ mhDC = nullptr;
+ }
+
+ return true;
+}
+
+SalGraphics* WinSalPrinter::StartPage( ImplJobSetup* pSetupData, bool bNewJobData )
+{
+ if (!isValid())
+ return nullptr;
+
+ HDC hDC = mhDC;
+ if ( pSetupData && pSetupData->GetDriverData() && bNewJobData )
+ {
+ DEVMODEW const * pOrgDevModeW;
+ DEVMODEW const * pDevModeW;
+ pOrgDevModeW = SAL_DEVMODE_W( pSetupData );
+ pDevModeW = ImplSalSetCopies( pOrgDevModeW, mnCopies, mbCollate );
+ ResetDCW( hDC, pDevModeW );
+ if ( pDevModeW != pOrgDevModeW )
+ std::free( const_cast<DEVMODEW *>(pDevModeW) );
+ }
+ volatile int nRet = 0;
+ CATCH_DRIVER_EX_BEGIN;
+ nRet = ::StartPage( hDC );
+ CATCH_DRIVER_EX_END( "exception in StartPage", this );
+
+ if ( nRet <= 0 )
+ {
+ GetLastError();
+ mnError = SalPrinterError::General;
+ return nullptr;
+ }
+
+ // Hack to work around old PostScript printer drivers optimizing away empty pages
+ // TODO: move into ImplCreateSalPrnGraphics()?
+ HPEN hTempPen = SelectPen( hDC, GetStockPen( NULL_PEN ) );
+ HBRUSH hTempBrush = SelectBrush( hDC, GetStockBrush( NULL_BRUSH ) );
+ Rectangle( hDC, -8000, -8000, -7999, -7999 );
+ SelectPen( hDC, hTempPen );
+ SelectBrush( hDC, hTempBrush );
+
+ mxGraphics.reset(ImplCreateSalPrnGraphics( hDC ));
+ return mxGraphics.get();
+}
+
+void WinSalPrinter::EndPage()
+{
+ mxGraphics.reset();
+
+ if (!isValid())
+ return;
+
+ HDC hDC = mhDC;
+ volatile int nRet = 0;
+ CATCH_DRIVER_EX_BEGIN;
+ nRet = ::EndPage( hDC );
+ CATCH_DRIVER_EX_END( "exception in EndPage", this );
+
+ if ( nRet <= 0 )
+ {
+ GetLastError();
+ mnError = SalPrinterError::General;
+ }
+}
+
+SalPrinterError WinSalPrinter::GetErrorCode()
+{
+ return mnError;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/gdi/salvd.cxx b/vcl/win/gdi/salvd.cxx
new file mode 100644
index 0000000000..7b3e7e11fc
--- /dev/null
+++ b/vcl/win/gdi/salvd.cxx
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svsys.h>
+
+#include <comphelper/windowserrorstring.hxx>
+
+#include <vcl/sysdata.hxx>
+
+#include <win/wincomp.hxx>
+#include <win/saldata.hxx>
+#include <win/salinst.h>
+#include <win/salgdi.h>
+#include <win/salvd.h>
+#include <sal/log.hxx>
+#include <o3tl/temporary.hxx>
+
+HBITMAP WinSalVirtualDevice::ImplCreateVirDevBitmap(HDC hDC, tools::Long nDX, tools::Long nDY, sal_uInt16 nBitCount, void **ppData)
+{
+ HBITMAP hBitmap;
+
+ if ( nBitCount == 1 )
+ {
+ hBitmap = CreateBitmap( static_cast<int>(nDX), static_cast<int>(nDY), 1, 1, nullptr );
+ SAL_WARN_IF( !hBitmap, "vcl", "CreateBitmap failed: " << WindowsErrorString( GetLastError() ) );
+ ppData = nullptr;
+ }
+ else
+ {
+ if (nBitCount == 0)
+ nBitCount = static_cast<WORD>(GetDeviceCaps(hDC, BITSPIXEL));
+
+ // #146839# Don't use CreateCompatibleBitmap() - there seem to
+ // be built-in limits for those HBITMAPs, at least this fails
+ // rather often on large displays/multi-monitor setups.
+ BITMAPINFO aBitmapInfo;
+ aBitmapInfo.bmiHeader.biSize = sizeof( BITMAPINFOHEADER );
+ aBitmapInfo.bmiHeader.biWidth = nDX;
+ aBitmapInfo.bmiHeader.biHeight = nDY;
+ aBitmapInfo.bmiHeader.biPlanes = 1;
+ aBitmapInfo.bmiHeader.biBitCount = nBitCount;
+ aBitmapInfo.bmiHeader.biCompression = BI_RGB;
+ aBitmapInfo.bmiHeader.biSizeImage = 0;
+ aBitmapInfo.bmiHeader.biXPelsPerMeter = 0;
+ aBitmapInfo.bmiHeader.biYPelsPerMeter = 0;
+ aBitmapInfo.bmiHeader.biClrUsed = 0;
+ aBitmapInfo.bmiHeader.biClrImportant = 0;
+
+ hBitmap = CreateDIBSection( hDC, &aBitmapInfo,
+ DIB_RGB_COLORS, ppData, nullptr,
+ 0 );
+ SAL_WARN_IF( !hBitmap, "vcl", "CreateDIBSection failed: " << WindowsErrorString( GetLastError() ) );
+ }
+
+ return hBitmap;
+}
+
+std::unique_ptr<SalVirtualDevice> WinSalInstance::CreateVirtualDevice( SalGraphics& rSGraphics,
+ tools::Long &nDX, tools::Long &nDY,
+ DeviceFormat /*eFormat*/,
+ const SystemGraphicsData* pData )
+{
+ WinSalGraphics& rGraphics = static_cast<WinSalGraphics&>(rSGraphics);
+ HDC hDC = nullptr;
+
+ if( pData )
+ {
+ hDC = (pData->hDC) ? pData->hDC : GetDC(pData->hWnd);
+ if (hDC)
+ {
+ nDX = GetDeviceCaps( hDC, HORZRES );
+ nDY = GetDeviceCaps( hDC, VERTRES );
+ }
+ else
+ {
+ nDX = 0;
+ nDY = 0;
+ }
+ }
+ else
+ {
+ hDC = CreateCompatibleDC( rGraphics.getHDC() );
+ SAL_WARN_IF( !hDC, "vcl", "CreateCompatibleDC failed: " << WindowsErrorString( GetLastError() ) );
+ }
+
+ if (!hDC)
+ return nullptr;
+
+ sal_uInt16 nBitCount = 0;
+ HBITMAP hBmp = nullptr;
+ if (!pData)
+ {
+ // #124826# continue even if hBmp could not be created
+ // if we would return a failure in this case, the process
+ // would terminate which is not required
+ hBmp = WinSalVirtualDevice::ImplCreateVirDevBitmap(rGraphics.getHDC(),
+ nDX, nDY, nBitCount,
+ &o3tl::temporary<void*>(nullptr));
+ }
+
+ const bool bForeignDC = pData != nullptr && pData->hDC != nullptr;
+ const SalData* pSalData = GetSalData();
+
+ WinSalVirtualDevice* pVDev = new WinSalVirtualDevice(hDC, hBmp, nBitCount,
+ bForeignDC, nDX, nDY);
+
+ WinSalGraphics* pVirGraphics = new WinSalGraphics(WinSalGraphics::VIRTUAL_DEVICE,
+ rGraphics.isScreen(), nullptr, pVDev);
+
+ // by default no! mirroring for VirtualDevices, can be enabled with EnableRTL()
+ pVirGraphics->SetLayout( SalLayoutFlags::NONE );
+ pVirGraphics->setHDC(hDC);
+
+ if ( pSalData->mhDitherPal && pVirGraphics->isScreen() )
+ {
+ pVirGraphics->setPalette(pSalData->mhDitherPal);
+ RealizePalette( hDC );
+ }
+
+ pVDev->setGraphics(pVirGraphics);
+
+ return std::unique_ptr<SalVirtualDevice>(pVDev);
+}
+
+WinSalVirtualDevice::WinSalVirtualDevice(HDC hDC, HBITMAP hBMP, sal_uInt16 nBitCount, bool bForeignDC, tools::Long nWidth, tools::Long nHeight)
+ : mhLocalDC(hDC), // HDC or 0 for Cache Device
+ mhBmp(hBMP), // Memory Bitmap
+ mnBitCount(nBitCount), // BitCount (0 or 1)
+ mbGraphics(false), // is Graphics used
+ mbForeignDC(bForeignDC), // uses a foreign DC instead of a bitmap
+ mnWidth(nWidth),
+ mnHeight(nHeight)
+{
+ // Default Bitmap
+ if (hBMP)
+ mhDefBmp = SelectBitmap(hDC, hBMP);
+ else
+ mhDefBmp = nullptr;
+
+ // insert VirDev into list of virtual devices
+ SalData* pSalData = GetSalData();
+ mpNext = pSalData->mpFirstVD;
+ pSalData->mpFirstVD = this;
+}
+
+WinSalVirtualDevice::~WinSalVirtualDevice()
+{
+ // remove VirDev from list of virtual devices
+ SalData* pSalData = GetSalData();
+ WinSalVirtualDevice** ppVirDev = &pSalData->mpFirstVD;
+ for(; (*ppVirDev != this) && *ppVirDev; ppVirDev = &(*ppVirDev)->mpNext );
+ if( *ppVirDev )
+ *ppVirDev = mpNext;
+
+ HDC hDC = mpGraphics->getHDC();
+ // restore the mpGraphics' original HDC values, so the HDC can be deleted in the !mbForeignDC case
+ mpGraphics->setHDC(nullptr);
+
+ if( mhDefBmp )
+ SelectBitmap(hDC, mhDefBmp);
+ if( !mbForeignDC )
+ DeleteDC(hDC);
+}
+
+SalGraphics* WinSalVirtualDevice::AcquireGraphics()
+{
+ if ( mbGraphics )
+ return nullptr;
+
+ if ( mpGraphics )
+ mbGraphics = true;
+
+ return mpGraphics.get();
+}
+
+void WinSalVirtualDevice::ReleaseGraphics( SalGraphics* )
+{
+ mbGraphics = false;
+}
+
+bool WinSalVirtualDevice::SetSize( tools::Long nDX, tools::Long nDY )
+{
+ if( mbForeignDC || !mhBmp )
+ return true; // ???
+
+ HBITMAP hNewBmp = ImplCreateVirDevBitmap(getHDC(), nDX, nDY, mnBitCount,
+ &o3tl::temporary<void*>(nullptr));
+ if (!hNewBmp)
+ {
+ mnWidth = 0;
+ mnHeight = 0;
+ return false;
+ }
+
+ mnWidth = nDX;
+ mnHeight = nDY;
+
+ SelectBitmap(getHDC(), hNewBmp);
+ mhBmp.reset(hNewBmp);
+
+ if (mpGraphics)
+ mpGraphics->GetImpl()->Init();
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/gdi/winlayout.cxx b/vcl/win/gdi/winlayout.cxx
new file mode 100644
index 0000000000..eb5c740580
--- /dev/null
+++ b/vcl/win/gdi/winlayout.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 <sal/config.h>
+#include <config_features.h>
+
+#include <memory>
+
+#include <o3tl/safeint.hxx>
+#include <osl/module.h>
+#include <osl/file.h>
+#include <sal/log.hxx>
+
+#include <comphelper/windowserrorstring.hxx>
+#include <comphelper/scopeguard.hxx>
+
+#include <win/salgdi.h>
+#include <win/saldata.hxx>
+#include <win/wingdiimpl.hxx>
+#include <ImplOutDevData.hxx>
+
+#include <win/DWriteTextRenderer.hxx>
+#include <win/scoped_gdi.hxx>
+
+#include <sallayout.hxx>
+
+#include <cstdio>
+#include <cstdlib>
+
+#include <rtl/character.hxx>
+
+#include <algorithm>
+
+#include <shlwapi.h>
+#include <winver.h>
+
+TextOutRenderer& TextOutRenderer::get(bool bUseDWrite, bool bRenderingModeNatural)
+{
+ SalData* const pSalData = GetSalData();
+
+ if (!pSalData)
+ { // don't call this after DeInitVCL()
+ fprintf(stderr, "TextOutRenderer fatal error: no SalData");
+ abort();
+ }
+
+ if (bUseDWrite)
+ {
+ if (!pSalData->m_pD2DWriteTextOutRenderer
+ || static_cast<D2DWriteTextOutRenderer*>(pSalData->m_pD2DWriteTextOutRenderer.get())
+ ->GetRenderingModeNatural()
+ != bRenderingModeNatural)
+ {
+ pSalData->m_pD2DWriteTextOutRenderer.reset(
+ new D2DWriteTextOutRenderer(bRenderingModeNatural));
+ }
+ return *pSalData->m_pD2DWriteTextOutRenderer;
+ }
+ if (!pSalData->m_pExTextOutRenderer)
+ {
+ pSalData->m_pExTextOutRenderer.reset(new ExTextOutRenderer);
+ }
+ return *pSalData->m_pExTextOutRenderer;
+}
+
+bool ExTextOutRenderer::operator()(GenericSalLayout const& rLayout, SalGraphics& /*rGraphics*/,
+ HDC hDC, bool /*bRenderingModeNatural*/)
+{
+ int nStart = 0;
+ basegfx::B2DPoint aPos;
+ const GlyphItem* pGlyph;
+ const WinFontInstance* pWinFont = static_cast<const WinFontInstance*>(&rLayout.GetFont());
+ UINT nTextAlign = GetTextAlign(hDC);
+ UINT nCurTextAlign = nTextAlign;
+ sal_Int32 nGlyphOffset = -pWinFont->GetTmDescent();
+
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ wchar_t glyphWStr = pGlyph->glyphId();
+ UINT32 nNewTextAlign = nCurTextAlign;
+ sal_Int32 nYOffset = 0;
+
+ if (pWinFont->IsCJKVerticalFont() && pGlyph->IsVertical())
+ {
+ nNewTextAlign = VTA_CENTER | TA_BOTTOM;
+ nYOffset = nGlyphOffset;
+ }
+ else
+ nNewTextAlign = nTextAlign;
+
+ if (nCurTextAlign != nNewTextAlign)
+ SetTextAlign(hDC, nNewTextAlign);
+
+ ExtTextOutW(hDC, aPos.getX(), aPos.getY() + nYOffset, ETO_GLYPH_INDEX, nullptr, &glyphWStr,
+ 1, nullptr);
+
+ nCurTextAlign = nNewTextAlign;
+ }
+
+ if (nCurTextAlign != nTextAlign)
+ SetTextAlign(hDC, nTextAlign);
+
+ return true;
+}
+
+std::unique_ptr<GenericSalLayout> WinSalGraphics::GetTextLayout(int nFallbackLevel)
+{
+ assert(mpWinFontEntry[nFallbackLevel]);
+ if (!mpWinFontEntry[nFallbackLevel])
+ return nullptr;
+
+ assert(mpWinFontEntry[nFallbackLevel]->GetFontFace());
+
+ mpWinFontEntry[nFallbackLevel]->SetGraphics(this);
+ return std::make_unique<GenericSalLayout>(*mpWinFontEntry[nFallbackLevel]);
+}
+
+WinFontInstance::WinFontInstance(const WinFontFace& rPFF, const vcl::font::FontSelectPattern& rFSP)
+ : LogicalFontInstance(rPFF, rFSP)
+ , m_pGraphics(nullptr)
+ , m_hFont(nullptr)
+ , m_bIsCJKVerticalFont(false)
+ , m_nTmDescent(0)
+{
+}
+
+WinFontInstance::~WinFontInstance()
+{
+ if (m_hFont)
+ ::DeleteFont(m_hFont);
+}
+
+bool WinFontInstance::hasHScale() const
+{
+ const vcl::font::FontSelectPattern& rPattern = GetFontSelectPattern();
+ int nHeight(rPattern.mnHeight);
+ int nWidth(rPattern.mnWidth ? rPattern.mnWidth * GetAverageWidthFactor() : nHeight);
+ return nWidth != nHeight;
+}
+
+float WinFontInstance::getHScale() const
+{
+ const vcl::font::FontSelectPattern& rPattern = GetFontSelectPattern();
+ int nHeight(rPattern.mnHeight);
+ if (!nHeight)
+ return 1.0;
+ float nWidth(rPattern.mnWidth ? rPattern.mnWidth * GetAverageWidthFactor() : nHeight);
+ return nWidth / nHeight;
+}
+
+void WinFontInstance::ImplInitHbFont(hb_font_t* /*pHbFont*/)
+{
+ assert(m_pGraphics);
+ // Calculate the AverageWidthFactor, see LogicalFontInstance::GetScale().
+ if (GetFontSelectPattern().mnWidth)
+ {
+ double nUPEM = GetFontFace()->UnitsPerEm();
+
+ LOGFONTW aLogFont;
+ GetObjectW(m_hFont, sizeof(LOGFONTW), &aLogFont);
+
+ // Set the height (font size) to EM to minimize rounding errors.
+ aLogFont.lfHeight = -nUPEM;
+ // Set width to the default to get the original value in the metrics.
+ aLogFont.lfWidth = 0;
+
+ TEXTMETRICW aFontMetric;
+ {
+ // Get the font metrics.
+ HDC hDC = m_pGraphics->getHDC();
+ ScopedSelectedHFONT hFont(hDC, CreateFontIndirectW(&aLogFont));
+ GetTextMetricsW(hDC, &aFontMetric);
+ }
+
+ SetAverageWidthFactor(nUPEM / aFontMetric.tmAveCharWidth);
+ }
+}
+
+void WinFontInstance::SetGraphics(WinSalGraphics* pGraphics)
+{
+ m_pGraphics = pGraphics;
+ if (m_hFont)
+ return;
+ HFONT hOrigFont;
+ HDC hDC = m_pGraphics->getHDC();
+ std::tie(m_hFont, m_bIsCJKVerticalFont, m_nTmDescent)
+ = m_pGraphics->ImplDoSetFont(hDC, GetFontSelectPattern(), GetFontFace(), hOrigFont);
+ SelectObject(hDC, hOrigFont);
+}
+
+void WinSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout, HDC hDC, bool bUseDWrite,
+ bool bRenderingModeNatural)
+{
+ TextOutRenderer& render = TextOutRenderer::get(bUseDWrite, bRenderingModeNatural);
+ render(rLayout, *this, hDC, bRenderingModeNatural);
+}
+
+void WinSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+ if (!mbPrinter && mWinSalGraphicsImplBase->DrawTextLayout(rLayout))
+ return; // handled by mWinSalGraphicsImplBase
+
+ HDC hDC = getHDC();
+ const WinFontInstance* pWinFont = static_cast<const WinFontInstance*>(&rLayout.GetFont());
+ const HFONT hLayoutFont = pWinFont->GetHFONT();
+
+ const HFONT hOrigFont = ::SelectFont(hDC, hLayoutFont);
+
+ // DWrite text renderer performs vertical writing better except printing.
+ const bool bVerticalScreenText
+ = !mbPrinter && rLayout.GetFont().GetFontSelectPattern().mbVertical;
+ const bool bRenderingModeNatural = rLayout.GetSubpixelPositioning();
+ const bool bUseDWrite = bVerticalScreenText || bRenderingModeNatural;
+ DrawTextLayout(rLayout, hDC, bUseDWrite, bRenderingModeNatural);
+
+ ::SelectFont(hDC, hOrigFont);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/src/50.bmp b/vcl/win/src/50.bmp
new file mode 100644
index 0000000000..b9d56fcd14
--- /dev/null
+++ b/vcl/win/src/50.bmp
Binary files differ
diff --git a/vcl/win/src/50.png b/vcl/win/src/50.png
new file mode 100644
index 0000000000..8517d965f0
--- /dev/null
+++ b/vcl/win/src/50.png
Binary files differ
diff --git a/vcl/win/src/ase.cur b/vcl/win/src/ase.cur
new file mode 100644
index 0000000000..7634a7d34a
--- /dev/null
+++ b/vcl/win/src/ase.cur
Binary files differ
diff --git a/vcl/win/src/asn.cur b/vcl/win/src/asn.cur
new file mode 100644
index 0000000000..e444e42bf3
--- /dev/null
+++ b/vcl/win/src/asn.cur
Binary files differ
diff --git a/vcl/win/src/asne.cur b/vcl/win/src/asne.cur
new file mode 100644
index 0000000000..e92cc65e7e
--- /dev/null
+++ b/vcl/win/src/asne.cur
Binary files differ
diff --git a/vcl/win/src/asns.cur b/vcl/win/src/asns.cur
new file mode 100644
index 0000000000..04d0b09c35
--- /dev/null
+++ b/vcl/win/src/asns.cur
Binary files differ
diff --git a/vcl/win/src/asnswe.cur b/vcl/win/src/asnswe.cur
new file mode 100644
index 0000000000..a0e25b16de
--- /dev/null
+++ b/vcl/win/src/asnswe.cur
Binary files differ
diff --git a/vcl/win/src/asnw.cur b/vcl/win/src/asnw.cur
new file mode 100644
index 0000000000..20322bc97b
--- /dev/null
+++ b/vcl/win/src/asnw.cur
Binary files differ
diff --git a/vcl/win/src/ass.cur b/vcl/win/src/ass.cur
new file mode 100644
index 0000000000..7166636a1a
--- /dev/null
+++ b/vcl/win/src/ass.cur
Binary files differ
diff --git a/vcl/win/src/asse.cur b/vcl/win/src/asse.cur
new file mode 100644
index 0000000000..8cb71234b0
--- /dev/null
+++ b/vcl/win/src/asse.cur
Binary files differ
diff --git a/vcl/win/src/assw.cur b/vcl/win/src/assw.cur
new file mode 100644
index 0000000000..46ee06d168
--- /dev/null
+++ b/vcl/win/src/assw.cur
Binary files differ
diff --git a/vcl/win/src/asw.cur b/vcl/win/src/asw.cur
new file mode 100644
index 0000000000..0ccac50f45
--- /dev/null
+++ b/vcl/win/src/asw.cur
Binary files differ
diff --git a/vcl/win/src/aswe.cur b/vcl/win/src/aswe.cur
new file mode 100644
index 0000000000..c238b7e10a
--- /dev/null
+++ b/vcl/win/src/aswe.cur
Binary files differ
diff --git a/vcl/win/src/chain.cur b/vcl/win/src/chain.cur
new file mode 100644
index 0000000000..02abb7ab71
--- /dev/null
+++ b/vcl/win/src/chain.cur
Binary files differ
diff --git a/vcl/win/src/chainnot.cur b/vcl/win/src/chainnot.cur
new file mode 100644
index 0000000000..938ece03f3
--- /dev/null
+++ b/vcl/win/src/chainnot.cur
Binary files differ
diff --git a/vcl/win/src/chart.cur b/vcl/win/src/chart.cur
new file mode 100644
index 0000000000..25fe85b760
--- /dev/null
+++ b/vcl/win/src/chart.cur
Binary files differ
diff --git a/vcl/win/src/copydata.cur b/vcl/win/src/copydata.cur
new file mode 100644
index 0000000000..d3c4bc93af
--- /dev/null
+++ b/vcl/win/src/copydata.cur
Binary files differ
diff --git a/vcl/win/src/copydlnk.cur b/vcl/win/src/copydlnk.cur
new file mode 100644
index 0000000000..495fd5e177
--- /dev/null
+++ b/vcl/win/src/copydlnk.cur
Binary files differ
diff --git a/vcl/win/src/copyf.cur b/vcl/win/src/copyf.cur
new file mode 100644
index 0000000000..450c09443a
--- /dev/null
+++ b/vcl/win/src/copyf.cur
Binary files differ
diff --git a/vcl/win/src/copyf2.cur b/vcl/win/src/copyf2.cur
new file mode 100644
index 0000000000..ac8de5da6b
--- /dev/null
+++ b/vcl/win/src/copyf2.cur
Binary files differ
diff --git a/vcl/win/src/copyflnk.cur b/vcl/win/src/copyflnk.cur
new file mode 100644
index 0000000000..e67f0539fa
--- /dev/null
+++ b/vcl/win/src/copyflnk.cur
Binary files differ
diff --git a/vcl/win/src/crook.cur b/vcl/win/src/crook.cur
new file mode 100644
index 0000000000..c40cf591e2
--- /dev/null
+++ b/vcl/win/src/crook.cur
Binary files differ
diff --git a/vcl/win/src/crop.cur b/vcl/win/src/crop.cur
new file mode 100644
index 0000000000..327fb06976
--- /dev/null
+++ b/vcl/win/src/crop.cur
Binary files differ
diff --git a/vcl/win/src/darc.cur b/vcl/win/src/darc.cur
new file mode 100644
index 0000000000..38504fa23c
--- /dev/null
+++ b/vcl/win/src/darc.cur
Binary files differ
diff --git a/vcl/win/src/dbezier.cur b/vcl/win/src/dbezier.cur
new file mode 100644
index 0000000000..f630b837dd
--- /dev/null
+++ b/vcl/win/src/dbezier.cur
Binary files differ
diff --git a/vcl/win/src/dcapt.cur b/vcl/win/src/dcapt.cur
new file mode 100644
index 0000000000..10dd5ba0d6
--- /dev/null
+++ b/vcl/win/src/dcapt.cur
Binary files differ
diff --git a/vcl/win/src/dcirccut.cur b/vcl/win/src/dcirccut.cur
new file mode 100644
index 0000000000..b19d3f8257
--- /dev/null
+++ b/vcl/win/src/dcirccut.cur
Binary files differ
diff --git a/vcl/win/src/dconnect.cur b/vcl/win/src/dconnect.cur
new file mode 100644
index 0000000000..5318d8f22d
--- /dev/null
+++ b/vcl/win/src/dconnect.cur
Binary files differ
diff --git a/vcl/win/src/dellipse.cur b/vcl/win/src/dellipse.cur
new file mode 100644
index 0000000000..c489a64033
--- /dev/null
+++ b/vcl/win/src/dellipse.cur
Binary files differ
diff --git a/vcl/win/src/detectiv.cur b/vcl/win/src/detectiv.cur
new file mode 100644
index 0000000000..30e5685b64
--- /dev/null
+++ b/vcl/win/src/detectiv.cur
Binary files differ
diff --git a/vcl/win/src/dfree.cur b/vcl/win/src/dfree.cur
new file mode 100644
index 0000000000..3ff56d0076
--- /dev/null
+++ b/vcl/win/src/dfree.cur
Binary files differ
diff --git a/vcl/win/src/dline.cur b/vcl/win/src/dline.cur
new file mode 100644
index 0000000000..623c33ac23
--- /dev/null
+++ b/vcl/win/src/dline.cur
Binary files differ
diff --git a/vcl/win/src/dpie.cur b/vcl/win/src/dpie.cur
new file mode 100644
index 0000000000..3b911cd01e
--- /dev/null
+++ b/vcl/win/src/dpie.cur
Binary files differ
diff --git a/vcl/win/src/dpolygon.cur b/vcl/win/src/dpolygon.cur
new file mode 100644
index 0000000000..9467f1e286
--- /dev/null
+++ b/vcl/win/src/dpolygon.cur
Binary files differ
diff --git a/vcl/win/src/drect.cur b/vcl/win/src/drect.cur
new file mode 100644
index 0000000000..60a5242c20
--- /dev/null
+++ b/vcl/win/src/drect.cur
Binary files differ
diff --git a/vcl/win/src/dtext.cur b/vcl/win/src/dtext.cur
new file mode 100644
index 0000000000..01e7d31eae
--- /dev/null
+++ b/vcl/win/src/dtext.cur
Binary files differ
diff --git a/vcl/win/src/fatcross.cur b/vcl/win/src/fatcross.cur
new file mode 100644
index 0000000000..9f92d04029
--- /dev/null
+++ b/vcl/win/src/fatcross.cur
Binary files differ
diff --git a/vcl/win/src/fill.cur b/vcl/win/src/fill.cur
new file mode 100644
index 0000000000..78f5fad87a
--- /dev/null
+++ b/vcl/win/src/fill.cur
Binary files differ
diff --git a/vcl/win/src/hshear.cur b/vcl/win/src/hshear.cur
new file mode 100644
index 0000000000..5cf2211458
--- /dev/null
+++ b/vcl/win/src/hshear.cur
Binary files differ
diff --git a/vcl/win/src/linkdata.cur b/vcl/win/src/linkdata.cur
new file mode 100644
index 0000000000..e47c1dea2c
--- /dev/null
+++ b/vcl/win/src/linkdata.cur
Binary files differ
diff --git a/vcl/win/src/linkf.cur b/vcl/win/src/linkf.cur
new file mode 100644
index 0000000000..6cc498a026
--- /dev/null
+++ b/vcl/win/src/linkf.cur
Binary files differ
diff --git a/vcl/win/src/magnify.cur b/vcl/win/src/magnify.cur
new file mode 100644
index 0000000000..1e32b92351
--- /dev/null
+++ b/vcl/win/src/magnify.cur
Binary files differ
diff --git a/vcl/win/src/mirror.cur b/vcl/win/src/mirror.cur
new file mode 100644
index 0000000000..e05eb836eb
--- /dev/null
+++ b/vcl/win/src/mirror.cur
Binary files differ
diff --git a/vcl/win/src/movebw.cur b/vcl/win/src/movebw.cur
new file mode 100644
index 0000000000..d079eb9fe2
--- /dev/null
+++ b/vcl/win/src/movebw.cur
Binary files differ
diff --git a/vcl/win/src/movedata.cur b/vcl/win/src/movedata.cur
new file mode 100644
index 0000000000..4d67cbe471
--- /dev/null
+++ b/vcl/win/src/movedata.cur
Binary files differ
diff --git a/vcl/win/src/movedlnk.cur b/vcl/win/src/movedlnk.cur
new file mode 100644
index 0000000000..1bb7b03064
--- /dev/null
+++ b/vcl/win/src/movedlnk.cur
Binary files differ
diff --git a/vcl/win/src/movef.cur b/vcl/win/src/movef.cur
new file mode 100644
index 0000000000..6abee2381d
--- /dev/null
+++ b/vcl/win/src/movef.cur
Binary files differ
diff --git a/vcl/win/src/movef2.cur b/vcl/win/src/movef2.cur
new file mode 100644
index 0000000000..d044981a3f
--- /dev/null
+++ b/vcl/win/src/movef2.cur
Binary files differ
diff --git a/vcl/win/src/moveflnk.cur b/vcl/win/src/moveflnk.cur
new file mode 100644
index 0000000000..630fa1bc3e
--- /dev/null
+++ b/vcl/win/src/moveflnk.cur
Binary files differ
diff --git a/vcl/win/src/movept.cur b/vcl/win/src/movept.cur
new file mode 100644
index 0000000000..81d3af5a05
--- /dev/null
+++ b/vcl/win/src/movept.cur
Binary files differ
diff --git a/vcl/win/src/nullptr.cur b/vcl/win/src/nullptr.cur
new file mode 100644
index 0000000000..28dbb2a903
--- /dev/null
+++ b/vcl/win/src/nullptr.cur
Binary files differ
diff --git a/vcl/win/src/pivotcol.cur b/vcl/win/src/pivotcol.cur
new file mode 100644
index 0000000000..061b3ba926
--- /dev/null
+++ b/vcl/win/src/pivotcol.cur
Binary files differ
diff --git a/vcl/win/src/pivotdel.cur b/vcl/win/src/pivotdel.cur
new file mode 100644
index 0000000000..4497dacd99
--- /dev/null
+++ b/vcl/win/src/pivotdel.cur
Binary files differ
diff --git a/vcl/win/src/pivotfld.cur b/vcl/win/src/pivotfld.cur
new file mode 100644
index 0000000000..efbbead893
--- /dev/null
+++ b/vcl/win/src/pivotfld.cur
Binary files differ
diff --git a/vcl/win/src/pivotrow.cur b/vcl/win/src/pivotrow.cur
new file mode 100644
index 0000000000..649444e9e1
--- /dev/null
+++ b/vcl/win/src/pivotrow.cur
Binary files differ
diff --git a/vcl/win/src/rotate.cur b/vcl/win/src/rotate.cur
new file mode 100644
index 0000000000..43c2a54a10
--- /dev/null
+++ b/vcl/win/src/rotate.cur
Binary files differ
diff --git a/vcl/win/src/salsrc.rc b/vcl/win/src/salsrc.rc
new file mode 100644
index 0000000000..b23ac149a7
--- /dev/null
+++ b/vcl/win/src/salsrc.rc
@@ -0,0 +1,90 @@
+/* -*- Mode: Fundamental; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <win/salids.hrc>
+
+SAL_RESID_POINTER_NULL CURSOR nullptr.cur
+SAL_RESID_POINTER_MAGNIFY CURSOR magnify.cur
+SAL_RESID_POINTER_FILL CURSOR fill.cur
+SAL_RESID_POINTER_ROTATE CURSOR rotate.cur
+SAL_RESID_POINTER_HSHEAR CURSOR hshear.cur
+SAL_RESID_POINTER_VSHEAR CURSOR vshear.cur
+SAL_RESID_POINTER_MIRROR CURSOR mirror.cur
+SAL_RESID_POINTER_CROOK CURSOR crook.cur
+SAL_RESID_POINTER_CROP CURSOR crop.cur
+SAL_RESID_POINTER_MOVEPOINT CURSOR movept.cur
+SAL_RESID_POINTER_MOVEBEZIERWEIGHT CURSOR movebw.cur
+SAL_RESID_POINTER_MOVEDATA CURSOR movedata.cur
+SAL_RESID_POINTER_COPYDATA CURSOR copydata.cur
+SAL_RESID_POINTER_LINKDATA CURSOR linkdata.cur
+SAL_RESID_POINTER_MOVEDATALINK CURSOR movedlnk.cur
+SAL_RESID_POINTER_COPYDATALINK CURSOR copydlnk.cur
+SAL_RESID_POINTER_MOVEFILE CURSOR movef.cur
+SAL_RESID_POINTER_COPYFILE CURSOR copyf.cur
+SAL_RESID_POINTER_LINKFILE CURSOR linkf.cur
+SAL_RESID_POINTER_MOVEFILELINK CURSOR moveflnk.cur
+SAL_RESID_POINTER_COPYFILELINK CURSOR copyflnk.cur
+SAL_RESID_POINTER_MOVEFILES CURSOR movef2.cur
+SAL_RESID_POINTER_COPYFILES CURSOR copyf2.cur
+SAL_RESID_POINTER_DRAW_LINE CURSOR dline.cur
+SAL_RESID_POINTER_DRAW_RECT CURSOR drect.cur
+SAL_RESID_POINTER_DRAW_POLYGON CURSOR dpolygon.cur
+SAL_RESID_POINTER_DRAW_BEZIER CURSOR dbezier.cur
+SAL_RESID_POINTER_DRAW_ARC CURSOR darc.cur
+SAL_RESID_POINTER_DRAW_PIE CURSOR dpie.cur
+SAL_RESID_POINTER_DRAW_CIRCLECUT CURSOR dcirccut.cur
+SAL_RESID_POINTER_DRAW_ELLIPSE CURSOR dellipse.cur
+SAL_RESID_POINTER_DRAW_FREEHAND CURSOR dfree.cur
+SAL_RESID_POINTER_DRAW_CONNECT CURSOR dconnect.cur
+SAL_RESID_POINTER_DRAW_TEXT CURSOR dtext.cur
+SAL_RESID_POINTER_DRAW_CAPTION CURSOR dcapt.cur
+SAL_RESID_POINTER_CHART CURSOR chart.cur
+SAL_RESID_POINTER_DETECTIVE CURSOR detectiv.cur
+SAL_RESID_POINTER_PIVOT_COL CURSOR pivotcol.cur
+SAL_RESID_POINTER_PIVOT_ROW CURSOR pivotrow.cur
+SAL_RESID_POINTER_PIVOT_FIELD CURSOR pivotfld.cur
+SAL_RESID_POINTER_PIVOT_DELETE CURSOR pivotdel.cur
+SAL_RESID_POINTER_CHAIN CURSOR chain.cur
+SAL_RESID_POINTER_CHAIN_NOTALLOWED CURSOR chainnot.cur
+SAL_RESID_POINTER_AUTOSCROLL_N CURSOR asn.cur
+SAL_RESID_POINTER_AUTOSCROLL_S CURSOR ass.cur
+SAL_RESID_POINTER_AUTOSCROLL_W CURSOR asw.cur
+SAL_RESID_POINTER_AUTOSCROLL_E CURSOR ase.cur
+SAL_RESID_POINTER_AUTOSCROLL_NW CURSOR asnw.cur
+SAL_RESID_POINTER_AUTOSCROLL_NE CURSOR asne.cur
+SAL_RESID_POINTER_AUTOSCROLL_SW CURSOR assw.cur
+SAL_RESID_POINTER_AUTOSCROLL_SE CURSOR asse.cur
+SAL_RESID_POINTER_AUTOSCROLL_NS CURSOR asns.cur
+SAL_RESID_POINTER_AUTOSCROLL_WE CURSOR aswe.cur
+SAL_RESID_POINTER_AUTOSCROLL_NSWE CURSOR asnswe.cur
+SAL_RESID_POINTER_TEXT_VERTICAL CURSOR vtext.cur
+SAL_RESID_POINTER_TAB_SELECT_S CURSOR tblsels.cur
+SAL_RESID_POINTER_TAB_SELECT_E CURSOR tblsele.cur
+SAL_RESID_POINTER_TAB_SELECT_SE CURSOR tblselse.cur
+SAL_RESID_POINTER_TAB_SELECT_W CURSOR tblselw.cur
+SAL_RESID_POINTER_TAB_SELECT_SW CURSOR tblselsw.cur
+SAL_RESID_POINTER_HIDEWHITESPACE CURSOR wshide.cur
+SAL_RESID_POINTER_SHOWWHITESPACE CURSOR wsshow.cur
+SAL_RESID_POINTER_FATCROSS CURSOR fatcross.cur
+
+SAL_RESID_BITMAP_50 BITMAP "50.bmp"
+
+SAL_RESID_ICON_DEFAULT ICON sd.ico
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/src/sd.ico b/vcl/win/src/sd.ico
new file mode 100644
index 0000000000..b2a0a07a67
--- /dev/null
+++ b/vcl/win/src/sd.ico
Binary files differ
diff --git a/vcl/win/src/tblsele.cur b/vcl/win/src/tblsele.cur
new file mode 100644
index 0000000000..3683e20df1
--- /dev/null
+++ b/vcl/win/src/tblsele.cur
Binary files differ
diff --git a/vcl/win/src/tblsels.cur b/vcl/win/src/tblsels.cur
new file mode 100644
index 0000000000..007182d734
--- /dev/null
+++ b/vcl/win/src/tblsels.cur
Binary files differ
diff --git a/vcl/win/src/tblselse.cur b/vcl/win/src/tblselse.cur
new file mode 100644
index 0000000000..986f013950
--- /dev/null
+++ b/vcl/win/src/tblselse.cur
Binary files differ
diff --git a/vcl/win/src/tblselsw.cur b/vcl/win/src/tblselsw.cur
new file mode 100644
index 0000000000..adabba1a2a
--- /dev/null
+++ b/vcl/win/src/tblselsw.cur
Binary files differ
diff --git a/vcl/win/src/tblselw.cur b/vcl/win/src/tblselw.cur
new file mode 100644
index 0000000000..a95eb85af4
--- /dev/null
+++ b/vcl/win/src/tblselw.cur
Binary files differ
diff --git a/vcl/win/src/vshear.cur b/vcl/win/src/vshear.cur
new file mode 100644
index 0000000000..a4bbf7e8eb
--- /dev/null
+++ b/vcl/win/src/vshear.cur
Binary files differ
diff --git a/vcl/win/src/vtext.cur b/vcl/win/src/vtext.cur
new file mode 100644
index 0000000000..776177901e
--- /dev/null
+++ b/vcl/win/src/vtext.cur
Binary files differ
diff --git a/vcl/win/src/wshide.cur b/vcl/win/src/wshide.cur
new file mode 100644
index 0000000000..bfa8fdfdba
--- /dev/null
+++ b/vcl/win/src/wshide.cur
Binary files differ
diff --git a/vcl/win/src/wsshow.cur b/vcl/win/src/wsshow.cur
new file mode 100644
index 0000000000..e0c2106034
--- /dev/null
+++ b/vcl/win/src/wsshow.cur
Binary files differ
diff --git a/vcl/win/window/keynames.cxx b/vcl/win/window/keynames.cxx
new file mode 100644
index 0000000000..e30f7284ca
--- /dev/null
+++ b/vcl/win/window/keynames.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 <string.h>
+#include <o3tl/string_view.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/macros.h>
+
+#include <win/salframe.h>
+
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+
+// Use unique ;) names to avoid clashes with the KEY_* (especially
+// KEY_SHIFT) from <vcl/vclenum.hxx>
+
+#define PAPUGA_KEY_ESC 0x10000
+#define PAPUGA_KEY_BACK 0xE0000
+#define PAPUGA_KEY_ENTER 0x1C0000
+#define PAPUGA_KEY_SPACEBAR 0x390000
+#define PAPUGA_KEY_HOME 0x1470000
+#define PAPUGA_KEY_UP 0x1480000
+#define PAPUGA_KEY_PAGEUP 0x1490000
+#define PAPUGA_KEY_LEFT 0x14B0000
+#define PAPUGA_KEY_RIGHT 0x14D0000
+#define PAPUGA_KEY_END 0x14F0000
+#define PAPUGA_KEY_DOWN 0x1500000
+#define PAPUGA_KEY_PAGEDOWN 0x1510000
+#define PAPUGA_KEY_INSERT 0x1520000
+#define PAPUGA_KEY_DELETE 0x1530000
+#define PAPUGA_KEY_CONTROL 0x21D0000
+#define PAPUGA_KEY_SHIFT 0x22A0000
+#define PAPUGA_KEY_ALT 0x2380000
+
+namespace vcl_sal {
+
+ namespace {
+
+ struct KeysNameReplacement
+ {
+ LONG aSymbol;
+ const char* pName;
+ };
+
+ struct KeyboardReplacements
+ {
+ const char* pLangName;
+ const KeysNameReplacement* pReplacements;
+ int nReplacements;
+ };
+
+ }
+
+ // CAUTION CAUTION CAUTION
+ // Every string value in the replacements tables must be in UTF-8
+ // but with the UTF-8 bytes encoded, not as such! Be careful!
+
+ const struct KeysNameReplacement aImplReplacements_Asturian[] =
+ {
+ { PAPUGA_KEY_BACK, "Retrocesu" },
+ { PAPUGA_KEY_ENTER, "Intro" },
+ { PAPUGA_KEY_SPACEBAR, "Espaciu" },
+ { PAPUGA_KEY_HOME, "Aniciu" },
+ { PAPUGA_KEY_UP, "Arriba" },
+ { PAPUGA_KEY_PAGEUP, "Re P\xc3\xa1" "x" },
+ { PAPUGA_KEY_LEFT, "Izquierda" },
+ { PAPUGA_KEY_RIGHT, "Drecha" },
+ { PAPUGA_KEY_END, "Fin" },
+ { PAPUGA_KEY_DOWN, "Abaxo" },
+ { PAPUGA_KEY_PAGEDOWN, "Av P\xc3\xa1" "x" },
+ { PAPUGA_KEY_INSERT, "Ins" },
+ { PAPUGA_KEY_DELETE, "Supr" },
+ { PAPUGA_KEY_SHIFT, "May\xc3\xba" "s" },
+ };
+
+ const struct KeysNameReplacement aImplReplacements_Catalan[] =
+ {
+ { PAPUGA_KEY_BACK, "Retroc\xc3\xa9" "s" },
+ { PAPUGA_KEY_ENTER, "Retorn" },
+ { PAPUGA_KEY_SPACEBAR, "Espai" },
+ { PAPUGA_KEY_HOME, "Inici" },
+ { PAPUGA_KEY_UP, "Amunt" },
+ { PAPUGA_KEY_PAGEUP, "Re P\xc3\xa0" "g" },
+ { PAPUGA_KEY_LEFT, "Esquerra" },
+ { PAPUGA_KEY_RIGHT, "Dreta" },
+ { PAPUGA_KEY_END, "Fi" },
+ { PAPUGA_KEY_DOWN, "Avall" },
+ { PAPUGA_KEY_PAGEDOWN, "Av P\xc3\xa0" "g" },
+ { PAPUGA_KEY_INSERT, "Ins" },
+ { PAPUGA_KEY_DELETE, "Supr" },
+ { PAPUGA_KEY_SHIFT, "Maj" },
+ };
+
+ const struct KeysNameReplacement aImplReplacements_Estonian[] =
+ {
+ { PAPUGA_KEY_RIGHT, "Nool paremale" },
+ { PAPUGA_KEY_LEFT, "Nool vasakule" },
+ { PAPUGA_KEY_UP, "Nool \xc3\xbc" "les" },
+ { PAPUGA_KEY_DOWN, "Nool alla" },
+ { PAPUGA_KEY_BACK, "Tagasil\xc3\xbc" "ke" },
+ { PAPUGA_KEY_ENTER, "Enter" },
+ { PAPUGA_KEY_SPACEBAR, "T\xc3\xbc" "hik" },
+ };
+
+ const struct KeysNameReplacement aImplReplacements_Lithuanian[] =
+ {
+ { PAPUGA_KEY_ESC, "Gr" },
+ { PAPUGA_KEY_BACK, "Naikinti" },
+ { PAPUGA_KEY_ENTER, "\xc4\xae" "vesti" },
+ { PAPUGA_KEY_SPACEBAR, "Tarpas" },
+ { PAPUGA_KEY_HOME, "Prad" },
+ { PAPUGA_KEY_UP, "Auk\xc5\xa1" "tyn" },
+ { PAPUGA_KEY_PAGEUP, "Psl\xe2\x86\x91" },
+ { PAPUGA_KEY_LEFT, "Kair\xc4\x97" "n" },
+ { PAPUGA_KEY_RIGHT, "De\xc5\xa1" "in\xc4\x97" "n" },
+ { PAPUGA_KEY_END, "Pab" },
+ { PAPUGA_KEY_DOWN, "\xc5\xbd" "emyn" },
+ { PAPUGA_KEY_PAGEDOWN, "Psl\xe2\x86\x93" },
+ { PAPUGA_KEY_INSERT, "\xc4\xae" "terpti" },
+ { PAPUGA_KEY_DELETE, "\xc5\xa0" "al" },
+ { PAPUGA_KEY_CONTROL, "Vald" },
+ { PAPUGA_KEY_SHIFT, "Lyg2" },
+ { PAPUGA_KEY_ALT, "Alt" },
+ };
+
+ const struct KeysNameReplacement aImplReplacements_Slovenian[] =
+ {
+ { PAPUGA_KEY_ESC, "Ube\xc5\xbe" "nica" },
+ { PAPUGA_KEY_BACK, "Vra\xc4\x8d" "alka" },
+ { PAPUGA_KEY_ENTER, "Vna\xc5\xa1" "alka" },
+ { PAPUGA_KEY_SPACEBAR, "Preslednica" },
+ { PAPUGA_KEY_HOME, "Za\xc4\x8d" "etek" },
+ { PAPUGA_KEY_UP, "Navzgor" },
+ { PAPUGA_KEY_PAGEUP, "Prej\xc5\xa1" "nja stran" },
+ { PAPUGA_KEY_LEFT, "Levo" },
+ { PAPUGA_KEY_RIGHT, "Desno" },
+ { PAPUGA_KEY_END, "Konec" },
+ { PAPUGA_KEY_DOWN, "Navzdol" },
+ { PAPUGA_KEY_PAGEDOWN, "Naslednja stran" },
+ { PAPUGA_KEY_INSERT, "Vrivalka" },
+ { PAPUGA_KEY_DELETE, "Brisalka" },
+ { PAPUGA_KEY_CONTROL, "Krmilka" },
+ { PAPUGA_KEY_SHIFT, "Dvigalka" },
+ { PAPUGA_KEY_ALT, "Izmenjalka" },
+ };
+
+ const struct KeysNameReplacement aImplReplacements_Spanish[] =
+ {
+ { PAPUGA_KEY_BACK, "Retroceso" },
+ { PAPUGA_KEY_ENTER, "Intro" },
+ { PAPUGA_KEY_SPACEBAR, "Espacio" },
+ { PAPUGA_KEY_HOME, "Inicio" },
+ { PAPUGA_KEY_UP, "Arriba" },
+ { PAPUGA_KEY_PAGEUP, "Re P\xc3\xa1" "g" },
+ { PAPUGA_KEY_LEFT, "Izquierda" },
+ { PAPUGA_KEY_RIGHT, "Derecha" },
+ { PAPUGA_KEY_END, "Fin" },
+ { PAPUGA_KEY_DOWN, "Abajo" },
+ { PAPUGA_KEY_PAGEDOWN, "Av P\xc3\xa1" "g" },
+ { PAPUGA_KEY_INSERT, "Ins" },
+ { PAPUGA_KEY_DELETE, "Supr" },
+ { PAPUGA_KEY_SHIFT, "May\xc3\xba" "s" },
+ };
+
+ const struct KeysNameReplacement aImplReplacements_Hungarian[] =
+ {
+ { PAPUGA_KEY_RIGHT, "Jobbra" },
+ { PAPUGA_KEY_LEFT, "Balra" },
+ { PAPUGA_KEY_UP, "Fel" },
+ { PAPUGA_KEY_DOWN, "Le" },
+ { PAPUGA_KEY_ENTER, "Enter" },
+ { PAPUGA_KEY_SPACEBAR, "Sz\xc3\xb3" "k\xc3\xb6" "z" },
+ };
+
+ const struct KeyboardReplacements aKeyboards[] =
+ {
+ { "ast",aImplReplacements_Asturian, std::size(aImplReplacements_Asturian) },
+ { "ca", aImplReplacements_Catalan, std::size(aImplReplacements_Catalan) },
+ { "et", aImplReplacements_Estonian, std::size(aImplReplacements_Estonian) },
+ { "hu", aImplReplacements_Hungarian, std::size(aImplReplacements_Hungarian) },
+ { "lt", aImplReplacements_Lithuanian, std::size(aImplReplacements_Lithuanian) },
+ { "sl", aImplReplacements_Slovenian, std::size(aImplReplacements_Slovenian) },
+ { "es", aImplReplacements_Spanish, std::size(aImplReplacements_Spanish) },
+ };
+
+ // translate keycodes, used within the displayed menu shortcuts
+ OUString getKeysReplacementName( std::u16string_view pLang, LONG nSymbol )
+ {
+ for( unsigned int n = 0; n < SAL_N_ELEMENTS(aKeyboards); n++ )
+ {
+ if( o3tl::equalsAscii( pLang, aKeyboards[n].pLangName ) )
+ {
+ const struct KeysNameReplacement* pRepl = aKeyboards[n].pReplacements;
+ for( int m = aKeyboards[n].nReplacements ; m ; )
+ {
+ if( nSymbol == pRepl[--m].aSymbol )
+ return OUString( pRepl[m].pName, strlen(pRepl[m].pName), RTL_TEXTENCODING_UTF8 );
+ }
+ }
+ }
+
+ return OUString();
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/window/salframe.cxx b/vcl/win/window/salframe.cxx
new file mode 100644
index 0000000000..cf2c8c6f8b
--- /dev/null
+++ b/vcl/win/window/salframe.cxx
@@ -0,0 +1,6099 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/container/XIndexAccess.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/awt/Rectangle.hpp>
+
+#include <officecfg/Office/Common.hxx>
+
+#include <memory>
+#include <string.h>
+#include <limits.h>
+
+#include <svsys.h>
+
+#include <comphelper/windowserrorstring.hxx>
+
+#include <fstream>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/ini_parser.hpp>
+#include <osl/file.hxx>
+#include <osl/process.h>
+
+#include <rtl/character.hxx>
+#include <rtl/string.h>
+#include <rtl/ustring.h>
+#include <sal/log.hxx>
+
+#include <osl/module.h>
+#include <comphelper/scopeguard.hxx>
+#include <tools/debug.hxx>
+#include <o3tl/enumarray.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/keycodes.hxx>
+#include <vcl/window.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/ptrstyle.hxx>
+
+#include <win/wincomp.hxx>
+#include <win/salids.hrc>
+#include <win/saldata.hxx>
+#include <win/salinst.h>
+#include <win/salbmp.h>
+#include <win/salgdi.h>
+#include <win/salsys.h>
+#include <win/salframe.h>
+#include <win/salvd.h>
+#include <win/salmenu.h>
+#include <win/salobj.h>
+#include <win/saltimer.h>
+
+#include <helpwin.hxx>
+#include <window.h>
+#include <sallayout.hxx>
+
+#include <vector>
+
+#include <com/sun/star/uno/Exception.hpp>
+
+#include <oleacc.h>
+#include <com/sun/star/accessibility/XMSAAService.hpp>
+
+#include <time.h>
+
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <dwmapi.h>
+#include <shobjidl.h>
+#include <propkey.h>
+#include <propvarutil.h>
+#include <shellapi.h>
+#include <uxtheme.h>
+#include <Vssym32.h>
+
+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;
+
+#ifndef IDC_PEN
+# define IDC_PEN MAKEINTRESOURCE(32631)
+#endif
+
+const unsigned int WM_USER_SYSTEM_WINDOW_ACTIVATED = RegisterWindowMessageW(L"SYSTEM_WINDOW_ACTIVATED");
+
+bool WinSalFrame::mbInReparent = false;
+
+// Macros for support of WM_UNICHAR & Keyman 6.0
+#define Uni_SupplementaryPlanesStart 0x10000
+
+static void UpdateFrameGeometry(WinSalFrame* pFrame);
+static void SetMaximizedFrameGeometry( HWND hWnd, WinSalFrame* pFrame, RECT* pParentRect = nullptr );
+
+static void SetGeometrySize(vcl::WindowPosSize& rWinPosSize, const Size& rSize)
+{
+ rWinPosSize.setWidth(rSize.Width() < 0 ? 0 : rSize.Width());
+ rWinPosSize.setHeight(rSize.Height() < 0 ? 0 : rSize.Height());
+}
+
+// If called with UpdateFrameGeometry, it must be called after it, as UpdateFrameGeometry
+// updates the geometry depending on the old state!
+void WinSalFrame::UpdateFrameState()
+{
+ // don't overwrite restore state in fullscreen mode
+ if (isFullScreen())
+ return;
+
+ const bool bVisible = (GetWindowStyle(mhWnd) & WS_VISIBLE);
+ if (IsIconic(mhWnd))
+ {
+ m_eState &= ~vcl::WindowState(vcl::WindowState::Normal | vcl::WindowState::Maximized);
+ m_eState |= vcl::WindowState::Minimized;
+ if (bVisible)
+ mnShowState = SW_SHOWMINIMIZED;
+ }
+ else if (IsZoomed(mhWnd))
+ {
+ m_eState &= ~vcl::WindowState(vcl::WindowState::Minimized | vcl::WindowState::Normal);
+ m_eState |= vcl::WindowState::Maximized;
+ if (bVisible)
+ mnShowState = SW_SHOWMAXIMIZED;
+ mbRestoreMaximize = true;
+ }
+ else
+ {
+ m_eState &= ~vcl::WindowState(vcl::WindowState::Minimized | vcl::WindowState::Maximized);
+ m_eState |= vcl::WindowState::Normal;
+ if (bVisible)
+ mnShowState = SW_SHOWNORMAL;
+ mbRestoreMaximize = false;
+ }
+}
+
+// if pParentRect is set, the workarea of the monitor that contains pParentRect is returned
+void ImplSalGetWorkArea( HWND hWnd, RECT *pRect, const RECT *pParentRect )
+{
+ if (Application::IsHeadlessModeEnabled()) {
+ pRect->left = 0;
+ pRect->top = 0;
+ pRect->right = VIRTUAL_DESKTOP_WIDTH;
+ pRect->bottom = VIRTUAL_DESKTOP_HEIGHT;
+ return;
+ }
+ // check if we or our parent is fullscreen, then the taskbar should be ignored
+ bool bIgnoreTaskbar = false;
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if( pFrame )
+ {
+ vcl::Window *pWin = pFrame->GetWindow();
+ while( pWin )
+ {
+ WorkWindow *pWorkWin = (pWin->GetType() == WindowType::WORKWINDOW) ? static_cast<WorkWindow *>(pWin) : nullptr;
+ if( pWorkWin && pWorkWin->ImplGetWindowImpl()->mbReallyVisible && pWorkWin->IsFullScreenMode() )
+ {
+ bIgnoreTaskbar = true;
+ break;
+ }
+ else
+ pWin = pWin->ImplGetWindowImpl()->mpParent;
+ }
+ }
+
+ // calculates the work area taking multiple monitors into account
+ static int nMonitors = GetSystemMetrics( SM_CMONITORS );
+ if( nMonitors == 1 )
+ {
+ if( bIgnoreTaskbar )
+ {
+ pRect->left = pRect->top = 0;
+ pRect->right = GetSystemMetrics( SM_CXSCREEN );
+ pRect->bottom = GetSystemMetrics( SM_CYSCREEN );
+ }
+ else
+ SystemParametersInfoW( SPI_GETWORKAREA, 0, pRect, 0 );
+ }
+ else
+ {
+ if( pParentRect != nullptr )
+ {
+ // return the size of the monitor where pParentRect lives
+ HMONITOR hMonitor;
+ MONITORINFO mi;
+
+ // get the nearest monitor to the passed rect.
+ hMonitor = MonitorFromRect(pParentRect, MONITOR_DEFAULTTONEAREST);
+
+ // get the work area or entire monitor rect.
+ mi.cbSize = sizeof(mi);
+ GetMonitorInfo(hMonitor, &mi);
+ if( !bIgnoreTaskbar )
+ *pRect = mi.rcWork;
+ else
+ *pRect = mi.rcMonitor;
+ }
+ else
+ {
+ // return the union of all monitors
+ pRect->left = GetSystemMetrics( SM_XVIRTUALSCREEN );
+ pRect->top = GetSystemMetrics( SM_YVIRTUALSCREEN );
+ pRect->right = pRect->left + GetSystemMetrics( SM_CXVIRTUALSCREEN );
+ pRect->bottom = pRect->top + GetSystemMetrics( SM_CYVIRTUALSCREEN );
+
+ // virtualscreen does not take taskbar into account, so use the corresponding
+ // diffs between screen and workarea from the default screen
+ // however, this is still not perfect: the taskbar might not be on the primary screen
+ if( !bIgnoreTaskbar )
+ {
+ RECT wRect, scrRect;
+ SystemParametersInfoW( SPI_GETWORKAREA, 0, &wRect, 0 );
+ scrRect.left = 0;
+ scrRect.top = 0;
+ scrRect.right = GetSystemMetrics( SM_CXSCREEN );
+ scrRect.bottom = GetSystemMetrics( SM_CYSCREEN );
+
+ pRect->left += wRect.left;
+ pRect->top += wRect.top;
+ pRect->right -= scrRect.right - wRect.right;
+ pRect->bottom -= scrRect.bottom - wRect.bottom;
+ }
+ }
+ }
+}
+
+namespace {
+
+enum PreferredAppMode
+{
+ AllowDark = 1,
+ ForceDark = 2,
+ ForceLight = 3
+};
+
+}
+
+static void UpdateDarkMode(HWND hWnd)
+{
+ static bool bOSSupportsDarkMode = OSSupportsDarkMode();
+ if (!bOSSupportsDarkMode)
+ return;
+
+ HINSTANCE hUxthemeLib = LoadLibraryExW(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
+ if (!hUxthemeLib)
+ return;
+
+ typedef PreferredAppMode(WINAPI* SetPreferredAppMode_t)(PreferredAppMode);
+ auto SetPreferredAppMode = reinterpret_cast<SetPreferredAppMode_t>(GetProcAddress(hUxthemeLib, MAKEINTRESOURCEA(135)));
+ if (SetPreferredAppMode)
+ {
+ switch (MiscSettings::GetDarkMode())
+ {
+ case 0:
+ SetPreferredAppMode(AllowDark);
+ break;
+ case 1:
+ SetPreferredAppMode(ForceLight);
+ break;
+ case 2:
+ SetPreferredAppMode(ForceDark);
+ break;
+ }
+ }
+
+ BOOL bDarkMode = UseDarkMode();
+
+ typedef void(WINAPI* AllowDarkModeForWindow_t)(HWND, BOOL);
+ auto AllowDarkModeForWindow = reinterpret_cast<AllowDarkModeForWindow_t>(GetProcAddress(hUxthemeLib, MAKEINTRESOURCEA(133)));
+ if (AllowDarkModeForWindow)
+ AllowDarkModeForWindow(hWnd, bDarkMode);
+
+ FreeLibrary(hUxthemeLib);
+
+ if (!AllowDarkModeForWindow)
+ return;
+
+ DwmSetWindowAttribute(hWnd, 20, &bDarkMode, sizeof(bDarkMode));
+}
+
+SalFrame* ImplSalCreateFrame( WinSalInstance* pInst,
+ HWND hWndParent, SalFrameStyleFlags nSalFrameStyle )
+{
+ WinSalFrame* pFrame = new WinSalFrame;
+ HWND hWnd;
+ DWORD nSysStyle = 0;
+ DWORD nExSysStyle = 0;
+ bool bSubFrame = false;
+
+ static const char* pEnvSynchronize = getenv("SAL_SYNCHRONIZE");
+ if ( pEnvSynchronize ) // no buffering of drawing commands
+ GdiSetBatchLimit( 1 );
+
+ static const char* pEnvTransparentFloats = getenv("SAL_TRANSPARENT_FLOATS" );
+
+ // determine creation data
+ if ( nSalFrameStyle & (SalFrameStyleFlags::PLUG | SalFrameStyleFlags::SYSTEMCHILD) )
+ {
+ nSysStyle |= WS_CHILD;
+ if( nSalFrameStyle & SalFrameStyleFlags::SYSTEMCHILD )
+ nSysStyle |= WS_CLIPSIBLINGS;
+ }
+ else
+ {
+ // #i87402# commenting out WS_CLIPCHILDREN
+ // this breaks SalFrameStyleFlags::SYSTEMCHILD handling, which is not
+ // used currently. Probably SalFrameStyleFlags::SYSTEMCHILD should be
+ // removed again.
+
+ // nSysStyle |= WS_CLIPCHILDREN;
+ if ( hWndParent )
+ {
+ nSysStyle |= WS_POPUP;
+ bSubFrame = true;
+ pFrame->mbNoIcon = true;
+ }
+ else
+ {
+ // Only with WS_OVERLAPPED we get a useful default position/size
+ if ( (nSalFrameStyle & (SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::MOVEABLE)) ==
+ (SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::MOVEABLE) )
+ nSysStyle |= WS_OVERLAPPED;
+ else
+ {
+ nSysStyle |= WS_POPUP;
+ if ( !(nSalFrameStyle & SalFrameStyleFlags::MOVEABLE) )
+ nExSysStyle |= WS_EX_TOOLWINDOW; // avoid taskbar appearance, for eg splash screen
+ }
+ }
+
+ if ( nSalFrameStyle & SalFrameStyleFlags::MOVEABLE )
+ {
+ pFrame->mbCaption = true;
+ nSysStyle |= WS_SYSMENU | WS_CAPTION;
+ if ( !hWndParent )
+ nSysStyle |= WS_SYSMENU | WS_MINIMIZEBOX;
+ else
+ nExSysStyle |= WS_EX_DLGMODALFRAME;
+
+ if ( nSalFrameStyle & SalFrameStyleFlags::SIZEABLE )
+ {
+ pFrame->mbSizeBorder = true;
+ nSysStyle |= WS_THICKFRAME;
+ if ( !hWndParent )
+ nSysStyle |= WS_MAXIMIZEBOX;
+ }
+ else
+ pFrame->mbFixBorder = true;
+
+ if ( nSalFrameStyle & SalFrameStyleFlags::DEFAULT )
+ nExSysStyle |= WS_EX_APPWINDOW;
+ }
+ if( nSalFrameStyle & SalFrameStyleFlags::TOOLWINDOW
+ // #100656# toolwindows lead to bad alt-tab behaviour, if they have the focus
+ // you must press it twice to leave the application
+ // so toolwindows are only used for non sizeable windows
+ // which are typically small, so a small caption makes sense
+
+ // #103578# looked too bad - above changes reverted
+ /* && !(nSalFrameStyle & SalFrameStyleFlags::SIZEABLE) */ )
+ {
+ pFrame->mbNoIcon = true;
+ nExSysStyle |= WS_EX_TOOLWINDOW;
+ if ( pEnvTransparentFloats /*&& !(nSalFrameStyle & SalFrameStyleFlags::MOVEABLE) */)
+ nExSysStyle |= WS_EX_LAYERED;
+ }
+ }
+ if ( nSalFrameStyle & SalFrameStyleFlags::FLOAT )
+ {
+ nExSysStyle |= WS_EX_TOOLWINDOW;
+ pFrame->mbFloatWin = true;
+
+ if (pEnvTransparentFloats)
+ nExSysStyle |= WS_EX_LAYERED;
+
+ }
+ if (nSalFrameStyle & SalFrameStyleFlags::TOOLTIP)
+ nExSysStyle |= WS_EX_TOPMOST;
+
+ // init frame data
+ pFrame->mnStyle = nSalFrameStyle;
+
+ // determine show style
+ pFrame->mnShowState = SW_SHOWNORMAL;
+ if ( (nSysStyle & (WS_POPUP | WS_MAXIMIZEBOX | WS_THICKFRAME)) == (WS_MAXIMIZEBOX | WS_THICKFRAME) )
+ {
+ if ( GetSystemMetrics( SM_CXSCREEN ) <= 1024 )
+ pFrame->mnShowState = SW_SHOWMAXIMIZED;
+ else
+ {
+ if ( nSalFrameStyle & SalFrameStyleFlags::DEFAULT )
+ {
+ SalData* pSalData = GetSalData();
+ pFrame->mnShowState = pSalData->mnCmdShow;
+ if ( (pFrame->mnShowState != SW_SHOWMINIMIZED) &&
+ (pFrame->mnShowState != SW_MINIMIZE) &&
+ (pFrame->mnShowState != SW_SHOWMINNOACTIVE) )
+ {
+ if ( (pFrame->mnShowState == SW_SHOWMAXIMIZED) ||
+ (pFrame->mnShowState == SW_MAXIMIZE) )
+ pFrame->mbOverwriteState = false;
+ pFrame->mnShowState = SW_SHOWMAXIMIZED;
+ }
+ else
+ pFrame->mbOverwriteState = false;
+ }
+ else
+ {
+ // Document Windows are also maximized, if the current Document Window
+ // is also maximized
+ HWND hWnd2 = GetForegroundWindow();
+ if ( hWnd2 && IsMaximized( hWnd2 ) &&
+ (GetWindowInstance( hWnd2 ) == pInst->mhInst) &&
+ ((GetWindowStyle( hWnd2 ) & (WS_POPUP | WS_MAXIMIZEBOX | WS_THICKFRAME)) == (WS_MAXIMIZEBOX | WS_THICKFRAME)) )
+ pFrame->mnShowState = SW_SHOWMAXIMIZED;
+ }
+ }
+ }
+
+ // create frame
+ LPCWSTR pClassName;
+ if ( bSubFrame )
+ {
+ if ( nSalFrameStyle & (SalFrameStyleFlags::MOVEABLE|SalFrameStyleFlags::NOSHADOW) ) // check if shadow not wanted
+ pClassName = SAL_SUBFRAME_CLASSNAMEW;
+ else
+ pClassName = SAL_TMPSUBFRAME_CLASSNAMEW; // undecorated floaters will get shadow on XP
+ }
+ else
+ {
+ if ( nSalFrameStyle & SalFrameStyleFlags::MOVEABLE )
+ pClassName = SAL_FRAME_CLASSNAMEW;
+ else
+ pClassName = SAL_TMPSUBFRAME_CLASSNAMEW;
+ }
+ hWnd = CreateWindowExW( nExSysStyle, pClassName, L"", nSysStyle,
+ CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
+ hWndParent, nullptr, pInst->mhInst, pFrame );
+ SAL_WARN_IF(!hWnd, "vcl", "CreateWindowExW failed: " << WindowsErrorString(GetLastError()));
+
+#if OSL_DEBUG_LEVEL > 1
+ // set transparency value
+ if( GetWindowExStyle( hWnd ) & WS_EX_LAYERED )
+ SetLayeredWindowAttributes( hWnd, 0, 230, 0x00000002 /*LWA_ALPHA*/ );
+#endif
+ if ( !hWnd )
+ {
+ delete pFrame;
+ return nullptr;
+ }
+
+ // If we have a Window with a Caption Bar and without
+ // a MaximizeBox, we change the SystemMenu
+ if ( (nSysStyle & (WS_CAPTION | WS_MAXIMIZEBOX)) == (WS_CAPTION) )
+ {
+ HMENU hSysMenu = GetSystemMenu( hWnd, FALSE );
+ if ( hSysMenu )
+ {
+ if ( !(nSysStyle & (WS_MINIMIZEBOX | WS_MAXIMIZEBOX)) )
+ DeleteMenu( hSysMenu, SC_RESTORE, MF_BYCOMMAND );
+ else
+ EnableMenuItem( hSysMenu, SC_RESTORE, MF_BYCOMMAND | MF_GRAYED | MF_DISABLED );
+ if ( !(nSysStyle & WS_MINIMIZEBOX) )
+ DeleteMenu( hSysMenu, SC_MINIMIZE, MF_BYCOMMAND );
+ if ( !(nSysStyle & WS_MAXIMIZEBOX) )
+ DeleteMenu( hSysMenu, SC_MAXIMIZE, MF_BYCOMMAND );
+ if ( !(nSysStyle & WS_THICKFRAME) )
+ DeleteMenu( hSysMenu, SC_SIZE, MF_BYCOMMAND );
+ }
+ }
+ if ( (nSysStyle & WS_SYSMENU) && !(nSalFrameStyle & SalFrameStyleFlags::CLOSEABLE) )
+ {
+ HMENU hSysMenu = GetSystemMenu( hWnd, FALSE );
+ if ( hSysMenu )
+ EnableMenuItem( hSysMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED | MF_DISABLED );
+ }
+
+ // reset input context
+ pFrame->mhDefIMEContext = ImmAssociateContext( hWnd, nullptr );
+
+ // determine output size and state
+ RECT aRect;
+ GetClientRect( hWnd, &aRect );
+ pFrame->mbDefPos = true;
+
+ UpdateFrameGeometry(pFrame);
+ pFrame->UpdateFrameState();
+
+ if( pFrame->mnShowState == SW_SHOWMAXIMIZED )
+ {
+ // #96084 set a useful internal window size because
+ // the window will not be maximized (and the size updated) before show()
+
+ SetMaximizedFrameGeometry( hWnd, pFrame );
+ }
+
+ return pFrame;
+}
+
+// helper that only creates the HWND
+// to allow for easy reparenting of system windows, (i.e. destroy and create new)
+HWND ImplSalReCreateHWND( HWND hWndParent, HWND oldhWnd, bool bAsChild )
+{
+ HINSTANCE hInstance = GetSalData()->mhInst;
+ sal_uLong nSysStyle = GetWindowLongW( oldhWnd, GWL_STYLE );
+ sal_uLong nExSysStyle = GetWindowLongW( oldhWnd, GWL_EXSTYLE );
+
+ if( bAsChild )
+ {
+ nSysStyle = WS_CHILD;
+ nExSysStyle = 0;
+ }
+
+ LPCWSTR pClassName = SAL_SUBFRAME_CLASSNAMEW;
+ return CreateWindowExW( nExSysStyle, pClassName, L"", nSysStyle,
+ CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
+ hWndParent, nullptr, hInstance, GetWindowPtr( oldhWnd ) );
+}
+
+// translation table from System keycodes into StartView keycodes
+#define KEY_TAB_SIZE 168
+
+const sal_uInt16 aImplTranslateKeyTab[KEY_TAB_SIZE] =
+{
+ // StarView-Code System-Code Index
+ 0, // 0
+ 0, // VK_LBUTTON 1
+ 0, // VK_RBUTTON 2
+ 0, // VK_CANCEL 3
+ 0, // VK_MBUTTON 4
+ 0, // 5
+ 0, // 6
+ 0, // 7
+ KEY_BACKSPACE, // VK_BACK 8
+ KEY_TAB, // VK_TAB 9
+ 0, // 10
+ 0, // 11
+ 0, // VK_CLEAR 12
+ KEY_RETURN, // VK_RETURN 13
+ 0, // 14
+ 0, // 15
+ 0, // VK_SHIFT 16
+ 0, // VK_CONTROL 17
+ 0, // VK_MENU 18
+ 0, // VK_PAUSE 19
+ 0, // VK_CAPITAL 20
+ 0, // VK_HANGUL 21
+ 0, // 22
+ 0, // 23
+ 0, // 24
+ KEY_HANGUL_HANJA, // VK_HANJA 25
+ 0, // 26
+ KEY_ESCAPE, // VK_ESCAPE 27
+ 0, // 28
+ 0, // 29
+ 0, // 30
+ 0, // 31
+ KEY_SPACE, // VK_SPACE 32
+ KEY_PAGEUP, // VK_PRIOR 33
+ KEY_PAGEDOWN, // VK_NEXT 34
+ KEY_END, // VK_END 35
+ KEY_HOME, // VK_HOME 36
+ KEY_LEFT, // VK_LEFT 37
+ KEY_UP, // VK_UP 38
+ KEY_RIGHT, // VK_RIGHT 39
+ KEY_DOWN, // VK_DOWN 40
+ 0, // VK_SELECT 41
+ 0, // VK_PRINT 42
+ 0, // VK_EXECUTE 43
+ 0, // VK_SNAPSHOT 44
+ KEY_INSERT, // VK_INSERT 45
+ KEY_DELETE, // VK_DELETE 46
+ KEY_HELP, // VK_HELP 47
+ KEY_0, // 48
+ KEY_1, // 49
+ KEY_2, // 50
+ KEY_3, // 51
+ KEY_4, // 52
+ KEY_5, // 53
+ KEY_6, // 54
+ KEY_7, // 55
+ KEY_8, // 56
+ KEY_9, // 57
+ 0, // 58
+ 0, // 59
+ 0, // 60
+ 0, // 61
+ 0, // 62
+ 0, // 63
+ 0, // 64
+ KEY_A, // 65
+ KEY_B, // 66
+ KEY_C, // 67
+ KEY_D, // 68
+ KEY_E, // 69
+ KEY_F, // 70
+ KEY_G, // 71
+ KEY_H, // 72
+ KEY_I, // 73
+ KEY_J, // 74
+ KEY_K, // 75
+ KEY_L, // 76
+ KEY_M, // 77
+ KEY_N, // 78
+ KEY_O, // 79
+ KEY_P, // 80
+ KEY_Q, // 81
+ KEY_R, // 82
+ KEY_S, // 83
+ KEY_T, // 84
+ KEY_U, // 85
+ KEY_V, // 86
+ KEY_W, // 87
+ KEY_X, // 88
+ KEY_Y, // 89
+ KEY_Z, // 90
+ 0, // VK_LWIN 91
+ 0, // VK_RWIN 92
+ KEY_CONTEXTMENU, // VK_APPS 93
+ 0, // 94
+ 0, // 95
+ KEY_0, // VK_NUMPAD0 96
+ KEY_1, // VK_NUMPAD1 97
+ KEY_2, // VK_NUMPAD2 98
+ KEY_3, // VK_NUMPAD3 99
+ KEY_4, // VK_NUMPAD4 100
+ KEY_5, // VK_NUMPAD5 101
+ KEY_6, // VK_NUMPAD6 102
+ KEY_7, // VK_NUMPAD7 103
+ KEY_8, // VK_NUMPAD8 104
+ KEY_9, // VK_NUMPAD9 105
+ KEY_MULTIPLY, // VK_MULTIPLY 106
+ KEY_ADD, // VK_ADD 107
+ KEY_DECIMAL, // VK_SEPARATOR 108
+ KEY_SUBTRACT, // VK_SUBTRACT 109
+ KEY_DECIMAL, // VK_DECIMAL 110
+ KEY_DIVIDE, // VK_DIVIDE 111
+ KEY_F1, // VK_F1 112
+ KEY_F2, // VK_F2 113
+ KEY_F3, // VK_F3 114
+ KEY_F4, // VK_F4 115
+ KEY_F5, // VK_F5 116
+ KEY_F6, // VK_F6 117
+ KEY_F7, // VK_F7 118
+ KEY_F8, // VK_F8 119
+ KEY_F9, // VK_F9 120
+ KEY_F10, // VK_F10 121
+ KEY_F11, // VK_F11 122
+ KEY_F12, // VK_F12 123
+ KEY_F13, // VK_F13 124
+ KEY_F14, // VK_F14 125
+ KEY_F15, // VK_F15 126
+ KEY_F16, // VK_F16 127
+ KEY_F17, // VK_F17 128
+ KEY_F18, // VK_F18 129
+ KEY_F19, // VK_F19 130
+ KEY_F20, // VK_F20 131
+ KEY_F21, // VK_F21 132
+ KEY_F22, // VK_F22 133
+ KEY_F23, // VK_F23 134
+ KEY_F24, // VK_F24 135
+ 0, // 136
+ 0, // 137
+ 0, // 138
+ 0, // 139
+ 0, // 140
+ 0, // 141
+ 0, // 142
+ 0, // 143
+ 0, // NUMLOCK 144
+ 0, // SCROLLLOCK 145
+ 0, // 146
+ 0, // 147
+ 0, // 148
+ 0, // 149
+ 0, // 150
+ 0, // 151
+ 0, // 152
+ 0, // 153
+ 0, // 154
+ 0, // 155
+ 0, // 156
+ 0, // 157
+ 0, // 158
+ 0, // 159
+ 0, // 160
+ 0, // 161
+ 0, // 162
+ 0, // 163
+ 0, // 164
+ 0, // 165
+ KEY_XF86BACK, // VK_BROWSER_BACK 166
+ KEY_XF86FORWARD // VK_BROWSER_FORWARD 167
+};
+
+static UINT ImplSalGetWheelScrollLines()
+{
+ UINT nScrLines = 0;
+ HWND hWndMsWheel = FindWindowW( MSH_WHEELMODULE_CLASS, MSH_WHEELMODULE_TITLE );
+ if ( hWndMsWheel )
+ {
+ UINT nGetScrollLinesMsgId = RegisterWindowMessageW( MSH_SCROLL_LINES );
+ nScrLines = static_cast<UINT>(SendMessageW( hWndMsWheel, nGetScrollLinesMsgId, 0, 0 ));
+ }
+
+ if ( !nScrLines )
+ if( !SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &nScrLines, 0 ) )
+ nScrLines = 0 ;
+
+ if ( !nScrLines )
+ nScrLines = 3;
+
+ return nScrLines;
+}
+
+static UINT ImplSalGetWheelScrollChars()
+{
+ UINT nScrChars = 0;
+ if( !SystemParametersInfoW( SPI_GETWHEELSCROLLCHARS, 0, &nScrChars, 0 ) )
+ {
+ return 3;
+ }
+
+ // system settings successfully read
+ return nScrChars;
+}
+
+static void ImplSalAddBorder( const WinSalFrame* pFrame, int& width, int& height )
+{
+ // transform client size into window size
+ RECT aWinRect;
+ aWinRect.left = 0;
+ aWinRect.right = width-1;
+ aWinRect.top = 0;
+ aWinRect.bottom = height-1;
+ AdjustWindowRectEx( &aWinRect, GetWindowStyle( pFrame->mhWnd ),
+ FALSE, GetWindowExStyle( pFrame->mhWnd ) );
+ width = aWinRect.right - aWinRect.left + 1;
+ height = aWinRect.bottom - aWinRect.top + 1;
+}
+
+static void ImplSalCalcFullScreenSize( const WinSalFrame* pFrame,
+ int& rX, int& rY, int& rDX, int& rDY )
+{
+ // set window to screen size
+ int nFrameX;
+ int nFrameY;
+ int nCaptionY;
+ int nScreenX = 0;
+ int nScreenY = 0;
+ int nScreenDX = 0;
+ int nScreenDY = 0;
+
+ if ( pFrame->mbSizeBorder )
+ {
+ nFrameX = GetSystemMetrics( SM_CXSIZEFRAME );
+ nFrameY = GetSystemMetrics( SM_CYSIZEFRAME );
+ }
+ else if ( pFrame->mbFixBorder )
+ {
+ nFrameX = GetSystemMetrics( SM_CXFIXEDFRAME );
+ nFrameY = GetSystemMetrics( SM_CYFIXEDFRAME );
+ }
+ else if ( pFrame->mbBorder )
+ {
+ nFrameX = GetSystemMetrics( SM_CXBORDER );
+ nFrameY = GetSystemMetrics( SM_CYBORDER );
+ }
+ else
+ {
+ nFrameX = 0;
+ nFrameY = 0;
+ }
+ if ( pFrame->mbCaption )
+ nCaptionY = GetSystemMetrics( SM_CYCAPTION );
+ else
+ nCaptionY = 0;
+
+ try
+ {
+ AbsoluteScreenPixelRectangle aRect;
+ sal_Int32 nMonitors = Application::GetScreenCount();
+ if( (pFrame->mnDisplay >= 0) && (pFrame->mnDisplay < nMonitors) )
+ {
+ aRect = Application::GetScreenPosSizePixel(pFrame->mnDisplay);
+ }
+ else
+ {
+ for (sal_Int32 i = 0; i < nMonitors; i++)
+ aRect.Union(Application::GetScreenPosSizePixel(i));
+ }
+ nScreenX = aRect.Left();
+ nScreenY = aRect.Top();
+ nScreenDX = aRect.GetWidth();
+ nScreenDY = aRect.GetHeight();
+ }
+ catch( Exception& )
+ {
+ }
+
+ if( !nScreenDX || !nScreenDY )
+ {
+ nScreenDX = GetSystemMetrics( SM_CXSCREEN );
+ nScreenDY = GetSystemMetrics( SM_CYSCREEN );
+ }
+
+ rX = nScreenX -nFrameX;
+ rY = nScreenY -(nFrameY+nCaptionY);
+ rDX = nScreenDX+(nFrameX*2);
+ rDY = nScreenDY+(nFrameY*2)+nCaptionY;
+}
+
+static void ImplSalFrameFullScreenPos( WinSalFrame* pFrame, bool bAlways = false )
+{
+ if ( bAlways || !IsIconic( pFrame->mhWnd ) )
+ {
+ // set window to screen size
+ int nX;
+ int nY;
+ int nWidth;
+ int nHeight;
+ ImplSalCalcFullScreenSize( pFrame, nX, nY, nWidth, nHeight );
+ SetWindowPos( pFrame->mhWnd, nullptr,
+ nX, nY, nWidth, nHeight,
+ SWP_NOZORDER | SWP_NOACTIVATE );
+ }
+}
+
+namespace {
+
+void SetForegroundWindow_Impl(HWND hwnd)
+{
+ if (!Application::IsHeadlessModeEnabled())
+ SetForegroundWindow(hwnd);
+}
+
+}
+
+WinSalFrame::WinSalFrame()
+{
+ SalData* pSalData = GetSalData();
+
+ mhWnd = nullptr;
+ mhCursor = LoadCursor( nullptr, IDC_ARROW );
+ mhDefIMEContext = nullptr;
+ mpLocalGraphics = nullptr;
+ mpThreadGraphics = nullptr;
+ m_eState = vcl::WindowState::Normal;
+ mnShowState = SW_SHOWNORMAL;
+ mnMinWidth = 0;
+ mnMinHeight = 0;
+ mnMaxWidth = SHRT_MAX;
+ mnMaxHeight = SHRT_MAX;
+ mnInputLang = 0;
+ mnInputCodePage = 0;
+ mbGraphics = false;
+ mbCaption = false;
+ mbBorder = false;
+ mbFixBorder = false;
+ mbSizeBorder = false;
+ mbFullScreenCaption = false;
+ mbPresentation = false;
+ mbInShow = false;
+ mbRestoreMaximize = false;
+ mbInMoveMsg = false;
+ mbInSizeMsg = false;
+ mbFullScreenToolWin = false;
+ mbDefPos = true;
+ mbOverwriteState = true;
+ mbIME = false;
+ mbHandleIME = false;
+ mbSpezIME = false;
+ mbAtCursorIME = false;
+ mbCandidateMode = false;
+ mbFloatWin = false;
+ mbNoIcon = false;
+ mSelectedhMenu = nullptr;
+ mLastActivatedhMenu = nullptr;
+ mpClipRgnData = nullptr;
+ mbFirstClipRect = true;
+ mpNextClipRect = nullptr;
+ mnDisplay = 0;
+ mbPropertiesStored = false;
+
+ // get data, when making 1st frame
+ if ( !pSalData->mpFirstFrame )
+ {
+ if ( !aSalShlData.mnWheelScrollLines )
+ aSalShlData.mnWheelScrollLines = ImplSalGetWheelScrollLines();
+ if ( !aSalShlData.mnWheelScrollChars )
+ aSalShlData.mnWheelScrollChars = ImplSalGetWheelScrollChars();
+ }
+
+ // insert frame in framelist
+ mpNextFrame = pSalData->mpFirstFrame;
+ pSalData->mpFirstFrame = this;
+}
+
+void WinSalFrame::updateScreenNumber()
+{
+ if( mnDisplay == -1 ) // spans all monitors
+ return;
+ WinSalSystem* pSys = static_cast<WinSalSystem*>(ImplGetSalSystem());
+ if( pSys )
+ {
+ const std::vector<WinSalSystem::DisplayMonitor>& rMonitors =
+ pSys->getMonitors();
+ AbsoluteScreenPixelPoint aPoint(maGeometry.pos());
+ size_t nMon = rMonitors.size();
+ for( size_t i = 0; i < nMon; i++ )
+ {
+ if( rMonitors[i].m_aArea.Contains( aPoint ) )
+ {
+ mnDisplay = static_cast<sal_Int32>(i);
+ maGeometry.setScreen(static_cast<unsigned int>(i));
+ }
+ }
+ }
+}
+
+bool WinSalFrame::ReleaseFrameGraphicsDC( WinSalGraphics* pGraphics )
+{
+ assert( pGraphics );
+ SalData* pSalData = GetSalData();
+ HDC hDC = pGraphics->getHDC();
+ if ( !hDC )
+ return false;
+ pGraphics->setHDC(nullptr);
+ SendMessageW( pSalData->mpInstance->mhComWnd, SAL_MSG_RELEASEDC,
+ reinterpret_cast<WPARAM>(mhWnd), reinterpret_cast<LPARAM>(hDC) );
+ if ( pGraphics == mpThreadGraphics )
+ pSalData->mnCacheDCInUse--;
+ return true;
+}
+
+WinSalFrame::~WinSalFrame()
+{
+ SalData* pSalData = GetSalData();
+
+ if( mpClipRgnData )
+ delete [] reinterpret_cast<BYTE*>(mpClipRgnData);
+
+ // remove frame from framelist
+ WinSalFrame** ppFrame = &pSalData->mpFirstFrame;
+ for(; (*ppFrame != this) && *ppFrame; ppFrame = &(*ppFrame)->mpNextFrame );
+ if( *ppFrame )
+ *ppFrame = mpNextFrame;
+ mpNextFrame = nullptr;
+
+ // destroy the thread SalGraphics
+ if ( mpThreadGraphics )
+ {
+ ReleaseFrameGraphicsDC( mpThreadGraphics );
+ delete mpThreadGraphics;
+ mpThreadGraphics = nullptr;
+ }
+
+ // destroy the local SalGraphics
+ if ( mpLocalGraphics )
+ {
+ ReleaseFrameGraphicsDC( mpLocalGraphics );
+ delete mpLocalGraphics;
+ mpLocalGraphics = nullptr;
+ }
+
+ if ( mhWnd )
+ {
+ // reset mouse leave data
+ if ( pSalData->mhWantLeaveMsg == mhWnd )
+ {
+ pSalData->mhWantLeaveMsg = nullptr;
+ }
+
+ // remove windows properties
+ if ( mbPropertiesStored )
+ SetApplicationID( OUString() );
+
+ // destroy system frame
+ if ( !DestroyWindow( mhWnd ) )
+ SetWindowPtr( mhWnd, nullptr );
+
+ mhWnd = nullptr;
+ }
+}
+
+bool WinSalFrame::InitFrameGraphicsDC( WinSalGraphics *pGraphics, HDC hDC, HWND hWnd )
+{
+ SalData* pSalData = GetSalData();
+ assert( pGraphics );
+ pGraphics->setHWND( hWnd );
+
+ HDC hCurrentDC = pGraphics->getHDC();
+ assert( !hCurrentDC || (hCurrentDC == hDC) );
+ if ( hCurrentDC )
+ return true;
+ pGraphics->setHDC( hDC );
+
+ if ( !hDC )
+ return false;
+
+ if ( pSalData->mhDitherPal )
+ pGraphics->setPalette(pSalData->mhDitherPal);
+
+ if ( pGraphics == mpThreadGraphics )
+ pSalData->mnCacheDCInUse++;
+ return true;
+}
+
+SalGraphics* WinSalFrame::AcquireGraphics()
+{
+ if ( mbGraphics || !mhWnd )
+ return nullptr;
+
+ SalData* pSalData = GetSalData();
+ WinSalGraphics *pGraphics = nullptr;
+ HDC hDC = nullptr;
+
+ // Other threads get an own DC, because Windows modify in the
+ // other case our DC (changing clip region), when they send a
+ // WM_ERASEBACKGROUND message
+ if ( !pSalData->mpInstance->IsMainThread() )
+ {
+ // We use only three CacheDC's for all threads, because W9x is limited
+ // to max. 5 Cache DC's per thread
+ if ( pSalData->mnCacheDCInUse >= 3 )
+ return nullptr;
+
+ if ( !mpThreadGraphics )
+ mpThreadGraphics = new WinSalGraphics(WinSalGraphics::WINDOW, true, mhWnd, this);
+ pGraphics = mpThreadGraphics;
+ assert( !pGraphics->getHDC() );
+ hDC = reinterpret_cast<HDC>(static_cast<sal_IntPtr>(SendMessageW( pSalData->mpInstance->mhComWnd,
+ SAL_MSG_GETCACHEDDC, reinterpret_cast<WPARAM>(mhWnd), 0 )));
+ }
+ else
+ {
+ if ( !mpLocalGraphics )
+ mpLocalGraphics = new WinSalGraphics(WinSalGraphics::WINDOW, true, mhWnd, this);
+ pGraphics = mpLocalGraphics;
+ hDC = pGraphics->getHDC();
+ if ( !hDC )
+ hDC = GetDC( mhWnd );
+ }
+
+ mbGraphics = InitFrameGraphicsDC( pGraphics, hDC, mhWnd );
+ return mbGraphics ? pGraphics : nullptr;
+}
+
+void WinSalFrame::ReleaseGraphics( SalGraphics* pGraphics )
+{
+ if ( mpThreadGraphics == pGraphics )
+ ReleaseFrameGraphicsDC( mpThreadGraphics );
+ mbGraphics = false;
+}
+
+bool WinSalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
+{
+ bool const ret = PostMessageW(mhWnd, SAL_MSG_USEREVENT, 0, reinterpret_cast<LPARAM>(pData.get()));
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+ if (ret)
+ pData.release();
+ return ret;
+}
+
+void WinSalFrame::SetTitle( const OUString& rTitle )
+{
+ static_assert( sizeof( WCHAR ) == sizeof( sal_Unicode ), "must be the same size" );
+
+ SetWindowTextW( mhWnd, o3tl::toW(rTitle.getStr()) );
+}
+
+void WinSalFrame::SetIcon( sal_uInt16 nIcon )
+{
+ // If we have a window without an Icon (for example a dialog), ignore this call
+ if ( mbNoIcon )
+ return;
+
+ // 0 means default (class) icon
+ HICON hIcon = nullptr, hSmIcon = nullptr;
+ if ( !nIcon )
+ nIcon = 1;
+
+ ImplLoadSalIcon( nIcon, hIcon, hSmIcon );
+
+ SAL_WARN_IF( !hIcon , "vcl", "WinSalFrame::SetIcon(): Could not load large icon !" );
+ SAL_WARN_IF( !hSmIcon , "vcl", "WinSalFrame::SetIcon(): Could not load small icon !" );
+
+ SendMessageW( mhWnd, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIcon) );
+ SendMessageW( mhWnd, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(hSmIcon) );
+}
+
+void WinSalFrame::SetMenu( SalMenu* pSalMenu )
+{
+ WinSalMenu* pWMenu = static_cast<WinSalMenu*>(pSalMenu);
+ if( pSalMenu && pWMenu->mbMenuBar )
+ ::SetMenu( mhWnd, pWMenu->mhMenu );
+}
+
+static HWND ImplGetParentHwnd( HWND hWnd )
+{
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if( !pFrame || !pFrame->GetWindow())
+ return ::GetParent( hWnd );
+ vcl::Window *pRealParent = pFrame->GetWindow()->ImplGetWindowImpl()->mpRealParent;
+ if( pRealParent )
+ return static_cast<WinSalFrame*>(pRealParent->ImplGetWindowImpl()->mpFrame)->mhWnd;
+ else
+ return ::GetParent( hWnd );
+
+}
+
+SalFrame* WinSalFrame::GetParent() const
+{
+ return GetWindowPtr( ImplGetParentHwnd( mhWnd ) );
+}
+
+static void ImplSalShow( HWND hWnd, bool bVisible, bool bNoActivate )
+{
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( !pFrame )
+ return;
+
+ if ( bVisible )
+ {
+ pFrame->mbDefPos = false;
+ pFrame->mbOverwriteState = true;
+ pFrame->mbInShow = true;
+
+ // #i4715, save position
+ RECT aRectPreMatrox, aRectPostMatrox;
+ GetWindowRect( hWnd, &aRectPreMatrox );
+
+ vcl::DeletionListener aDogTag( pFrame );
+ if( bNoActivate )
+ ShowWindow( hWnd, SW_SHOWNOACTIVATE );
+ else
+ ShowWindow( hWnd, pFrame->mnShowState );
+ if( aDogTag.isDeleted() )
+ return;
+
+ if (pFrame->mbFloatWin && !(pFrame->mnStyle & SalFrameStyleFlags::NOSHADOW))
+ {
+ // erase the window immediately to improve XP shadow effect
+ // otherwise the shadow may appears long time before the rest of the window
+ // especially when accessibility is on
+ HDC dc = GetDC( hWnd );
+ RECT aRect;
+ GetClientRect( hWnd, &aRect );
+ FillRect( dc, &aRect, reinterpret_cast<HBRUSH>(COLOR_MENU+1) ); // choose the menucolor, because its mostly noticeable for menus
+ ReleaseDC( hWnd, dc );
+ }
+
+ // #i4715, matrox centerpopup might have changed our position
+ // reposition popups without caption (menus, dropdowns, tooltips)
+ GetWindowRect( hWnd, &aRectPostMatrox );
+ if( (GetWindowStyle( hWnd ) & WS_POPUP) &&
+ !pFrame->mbCaption &&
+ (aRectPreMatrox.left != aRectPostMatrox.left || aRectPreMatrox.top != aRectPostMatrox.top) )
+ SetWindowPos( hWnd, nullptr, aRectPreMatrox.left, aRectPreMatrox.top, 0, 0, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE );
+
+ if( aDogTag.isDeleted() )
+ return;
+ vcl::Window *pClientWin = pFrame->GetWindow()->ImplGetClientWindow();
+ if ( pFrame->mbFloatWin || ( pClientWin && (pClientWin->GetStyle() & WB_SYSTEMFLOATWIN) ) )
+ pFrame->mnShowState = SW_SHOWNOACTIVATE;
+ else
+ pFrame->mnShowState = SW_SHOW;
+ // hide toolbar for W98
+ if ( pFrame->mbPresentation )
+ {
+ HWND hWndParent = ::GetParent( hWnd );
+ if ( hWndParent )
+ SetForegroundWindow_Impl( hWndParent );
+ SetForegroundWindow_Impl( hWnd );
+ }
+
+ pFrame->mbInShow = false;
+ pFrame->updateScreenNumber();
+
+ // Direct Paint only, if we get the SolarMutex
+ if ( ImplSalYieldMutexTryToAcquire() )
+ {
+ UpdateWindow( hWnd );
+ ImplSalYieldMutexRelease();
+ }
+ }
+ else
+ {
+ ShowWindow( hWnd, SW_HIDE );
+ }
+}
+
+void WinSalFrame::SetExtendedFrameStyle( SalExtStyle )
+{
+}
+
+void WinSalFrame::Show( bool bVisible, bool bNoActivate )
+{
+ // Post this Message to the window, because this only works
+ // in the thread of the window, which has create this window.
+ // We post this message to avoid deadlocks
+ if ( GetSalData()->mnAppThreadId != GetCurrentThreadId() )
+ {
+ bool const ret = PostMessageW(mhWnd, SAL_MSG_SHOW, WPARAM(bVisible), LPARAM(bNoActivate));
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+ }
+ else
+ ImplSalShow( mhWnd, bVisible, bNoActivate );
+}
+
+void WinSalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight )
+{
+ mnMinWidth = nWidth;
+ mnMinHeight = nHeight;
+}
+
+void WinSalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight )
+{
+ mnMaxWidth = nWidth;
+ mnMaxHeight = nHeight;
+}
+
+void WinSalFrame::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ sal_uInt16 nFlags )
+{
+ bool bVisible = (GetWindowStyle( mhWnd ) & WS_VISIBLE) != 0;
+ if ( !bVisible )
+ {
+ vcl::Window *pClientWin = GetWindow()->ImplGetClientWindow();
+ if ( mbFloatWin || ( pClientWin && (pClientWin->GetStyle() & WB_SYSTEMFLOATWIN) ) )
+ mnShowState = SW_SHOWNOACTIVATE;
+ else
+ mnShowState = SW_SHOWNORMAL;
+ }
+ else
+ {
+ if ( IsIconic( mhWnd ) || IsZoomed( mhWnd ) )
+ ShowWindow( mhWnd, SW_RESTORE );
+ }
+
+ SalEvent nEvent = SalEvent::NONE;
+ UINT nPosSize = 0;
+ RECT aClientRect, aWindowRect;
+ GetClientRect( mhWnd, &aClientRect ); // x,y always 0,0, but width and height without border
+ GetWindowRect( mhWnd, &aWindowRect ); // x,y in screen coordinates, width and height with border
+
+ if ( !(nFlags & (SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y)) )
+ nPosSize |= SWP_NOMOVE;
+ else
+ {
+ //SAL_WARN_IF( !nX || !nY, "vcl", " Windowposition of (0,0) requested!" );
+ nEvent = SalEvent::Move;
+ }
+ if ( !(nFlags & (SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT)) )
+ nPosSize |= SWP_NOSIZE;
+ else
+ nEvent = (nEvent == SalEvent::Move) ? SalEvent::MoveResize : SalEvent::Resize;
+
+ if ( !(nFlags & SAL_FRAME_POSSIZE_X) )
+ nX = aWindowRect.left;
+ if ( !(nFlags & SAL_FRAME_POSSIZE_Y) )
+ nY = aWindowRect.top;
+ if ( !(nFlags & SAL_FRAME_POSSIZE_WIDTH) )
+ nWidth = aClientRect.right-aClientRect.left;
+ if ( !(nFlags & SAL_FRAME_POSSIZE_HEIGHT) )
+ nHeight = aClientRect.bottom-aClientRect.top;
+
+ // Calculate window size including the border
+ RECT aWinRect;
+ aWinRect.left = 0;
+ aWinRect.right = static_cast<int>(nWidth)-1;
+ aWinRect.top = 0;
+ aWinRect.bottom = static_cast<int>(nHeight)-1;
+ AdjustWindowRectEx( &aWinRect, GetWindowStyle( mhWnd ),
+ FALSE, GetWindowExStyle( mhWnd ) );
+ nWidth = aWinRect.right - aWinRect.left + 1;
+ nHeight = aWinRect.bottom - aWinRect.top + 1;
+
+ HWND hWndParent = ImplGetParentHwnd(mhWnd);
+ // For dialogs (WS_POPUP && WS_DLGFRAME), we need to find the "real" parent,
+ // in case multiple dialogs are stacked on each other
+ // (we don't want to position the second dialog relative to the first one, but relative to the main window)
+ if ( (GetWindowStyle( mhWnd ) & WS_POPUP) && (GetWindowStyle( mhWnd ) & WS_DLGFRAME) ) // mhWnd is a dialog
+ {
+ while ( hWndParent && (GetWindowStyle( hWndParent ) & WS_POPUP) && (GetWindowStyle( hWndParent ) & WS_DLGFRAME) )
+ {
+ hWndParent = ::ImplGetParentHwnd( hWndParent );
+ }
+ }
+
+ if ( !(nPosSize & SWP_NOMOVE) && hWndParent )
+ {
+ RECT aParentRect;
+ GetClientRect( hWndParent, &aParentRect );
+ if( AllSettings::GetLayoutRTL() )
+ nX = (aParentRect.right - aParentRect.left) - nWidth-1 - nX;
+
+ //#110386#, do not transform coordinates for system child windows
+ if( !(GetWindowStyle( mhWnd ) & WS_CHILD) )
+ {
+ POINT aPt;
+ aPt.x = nX;
+ aPt.y = nY;
+
+ WinSalFrame* pParentFrame = GetWindowPtr( hWndParent );
+ if ( pParentFrame && pParentFrame->mnShowState == SW_SHOWMAXIMIZED )
+ {
+ // #i42485#: parent will be shown maximized in which case
+ // a ClientToScreen uses the wrong coordinates (i.e. those from the restore pos)
+ // so use the (already updated) frame geometry for the transformation
+ aPt.x += pParentFrame->maGeometry.x();
+ aPt.y += pParentFrame->maGeometry.y();
+ }
+ else
+ ClientToScreen( hWndParent, &aPt );
+
+ nX = aPt.x;
+ nY = aPt.y;
+
+ // the position is set
+ mbDefPos = false;
+ }
+ }
+
+ // #i3338# to be conformant to UNIX we must position the client window, ie without the decoration
+ // #i43250# if the position was read from the system (GetWindowRect(), see above), it must not be modified
+ if ( nFlags & SAL_FRAME_POSSIZE_X )
+ nX += aWinRect.left;
+ if ( nFlags & SAL_FRAME_POSSIZE_Y )
+ nY += aWinRect.top;
+
+ int nScreenX;
+ int nScreenY;
+ int nScreenWidth;
+ int nScreenHeight;
+
+ RECT aRect;
+ ImplSalGetWorkArea( mhWnd, &aRect, nullptr );
+ nScreenX = aRect.left;
+ nScreenY = aRect.top;
+ nScreenWidth = aRect.right-aRect.left;
+ nScreenHeight = aRect.bottom-aRect.top;
+
+ if ( mbDefPos && (nPosSize & SWP_NOMOVE)) // we got no positioning request, so choose default position
+ {
+ // center window
+
+ HWND hWndParent2 = ::GetParent( mhWnd );
+ // Search for TopLevel Frame
+ while ( hWndParent2 && (GetWindowStyle( hWndParent2 ) & WS_CHILD) )
+ hWndParent2 = ::GetParent( hWndParent2 );
+ // if the Window has a Parent, then center the window to
+ // the parent, in the other case to the screen
+ if ( hWndParent2 && !IsIconic( hWndParent2 ) &&
+ (GetWindowStyle( hWndParent2 ) & WS_VISIBLE) )
+ {
+ RECT aParentRect;
+ GetWindowRect( hWndParent2, &aParentRect );
+ int nParentWidth = aParentRect.right-aParentRect.left;
+ int nParentHeight = aParentRect.bottom-aParentRect.top;
+
+ // We don't center, when Parent is smaller than our window
+ if ( (nParentWidth-GetSystemMetrics( SM_CXFIXEDFRAME ) <= nWidth) &&
+ (nParentHeight-GetSystemMetrics( SM_CYFIXEDFRAME ) <= nHeight) )
+ {
+ int nOff = GetSystemMetrics( SM_CYSIZEFRAME ) + GetSystemMetrics( SM_CYCAPTION );
+ nX = aParentRect.left+nOff;
+ nY = aParentRect.top+nOff;
+ }
+ else
+ {
+ nX = (nParentWidth-nWidth)/2 + aParentRect.left;
+ nY = (nParentHeight-nHeight)/2 + aParentRect.top;
+ }
+ }
+ else
+ {
+ POINT pt;
+ GetCursorPos( &pt );
+ RECT aRect2;
+ aRect2.left = pt.x;
+ aRect2.top = pt.y;
+ aRect2.right = pt.x+2;
+ aRect2.bottom = pt.y+2;
+
+ // dualmonitor support:
+ // Get screensize of the monitor with the mouse pointer
+ ImplSalGetWorkArea( mhWnd, &aRect2, &aRect2 );
+
+ nX = ((aRect2.right-aRect2.left)-nWidth)/2 + aRect2.left;
+ nY = ((aRect2.bottom-aRect2.top)-nHeight)/2 + aRect2.top;
+ }
+
+ //if ( bVisible )
+ // mbDefPos = FALSE;
+
+ mbDefPos = false; // center only once
+ nPosSize &= ~SWP_NOMOVE; // activate positioning
+ nEvent = SalEvent::MoveResize;
+ }
+
+ // Adjust Window in the screen
+ bool bCheckOffScreen = true;
+
+ // but don't do this for floaters or ownerdraw windows that are currently moved interactively
+ if( (mnStyle & SalFrameStyleFlags::FLOAT) && !(mnStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) )
+ bCheckOffScreen = false;
+
+ if( mnStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
+ {
+ // may be the window is currently being moved (mouse is captured), then no check is required
+ if( mhWnd == ::GetCapture() )
+ bCheckOffScreen = false;
+ else
+ bCheckOffScreen = true;
+ }
+
+ if( bCheckOffScreen )
+ {
+ if ( nX+nWidth > nScreenX+nScreenWidth )
+ nX = (nScreenX+nScreenWidth) - nWidth;
+ if ( nY+nHeight > nScreenY+nScreenHeight )
+ nY = (nScreenY+nScreenHeight) - nHeight;
+ if ( nX < nScreenX )
+ nX = nScreenX;
+ if ( nY < nScreenY )
+ nY = nScreenY;
+ }
+
+ UINT nPosFlags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | nPosSize;
+ // bring floating windows always to top
+ if( !(mnStyle & SalFrameStyleFlags::FLOAT) )
+ nPosFlags |= SWP_NOZORDER; // do not change z-order
+
+ SetWindowPos( mhWnd, HWND_TOP, nX, nY, static_cast<int>(nWidth), static_cast<int>(nHeight), nPosFlags );
+
+ UpdateFrameGeometry(this);
+
+ // Notification -- really ???
+ if( nEvent != SalEvent::NONE )
+ CallCallback( nEvent, nullptr );
+}
+
+void WinSalFrame::ImplSetParentFrame( HWND hNewParentWnd, bool bAsChild )
+{
+ // save hwnd, will be overwritten in WM_CREATE during createwindow
+ HWND hWndOld = mhWnd;
+ HWND hWndOldParent = ::GetParent( hWndOld );
+ SalData* pSalData = GetSalData();
+
+ if( hNewParentWnd == hWndOldParent )
+ return;
+
+ ::std::vector< WinSalFrame* > children;
+ ::std::vector< WinSalObject* > systemChildren;
+
+ // search child windows
+ WinSalFrame *pFrame = pSalData->mpFirstFrame;
+ while( pFrame )
+ {
+ HWND hWndParent = ::GetParent( pFrame->mhWnd );
+ if( mhWnd == hWndParent )
+ children.push_back( pFrame );
+ pFrame = pFrame->mpNextFrame;
+ }
+
+ // search system child windows (plugins etc.)
+ WinSalObject *pObject = pSalData->mpFirstObject;
+ while( pObject )
+ {
+ HWND hWndParent = ::GetParent( pObject->mhWnd );
+ if( mhWnd == hWndParent )
+ systemChildren.push_back( pObject );
+ pObject = pObject->mpNextObject;
+ }
+
+ // to recreate the DCs, if they were destroyed
+ bool bHadLocalGraphics = false, bHadThreadGraphics = false;
+
+ HFONT hFont = nullptr;
+ HPEN hPen = nullptr;
+ HBRUSH hBrush = nullptr;
+
+ int oldCount = pSalData->mnCacheDCInUse;
+
+ // release the thread DC
+ if ( mpThreadGraphics )
+ {
+ // save current gdi objects before hdc is gone
+ HDC hDC = mpThreadGraphics->getHDC();
+ if ( hDC )
+ {
+ hFont = static_cast<HFONT>(GetCurrentObject( hDC, OBJ_FONT ));
+ hPen = static_cast<HPEN>(GetCurrentObject( hDC, OBJ_PEN ));
+ hBrush = static_cast<HBRUSH>(GetCurrentObject( hDC, OBJ_BRUSH ));
+ }
+
+ bHadThreadGraphics = ReleaseFrameGraphicsDC( mpThreadGraphics );
+ assert( (bHadThreadGraphics && hDC) || (!bHadThreadGraphics && !hDC) );
+ }
+
+ // release the local DC
+ if ( mpLocalGraphics )
+ bHadLocalGraphics = ReleaseFrameGraphicsDC( mpLocalGraphics );
+
+ // create a new hwnd with the same styles
+ HWND hWndParent = hNewParentWnd;
+ // forward to main thread
+ HWND hWnd = reinterpret_cast<HWND>(static_cast<sal_IntPtr>(SendMessageW( pSalData->mpInstance->mhComWnd,
+ bAsChild ? SAL_MSG_RECREATECHILDHWND : SAL_MSG_RECREATEHWND,
+ reinterpret_cast<WPARAM>(hWndParent), reinterpret_cast<LPARAM>(mhWnd) )));
+
+ // succeeded ?
+ SAL_WARN_IF( !IsWindow( hWnd ), "vcl", "WinSalFrame::SetParent not successful");
+
+ // re-create thread DC
+ if( bHadThreadGraphics )
+ {
+ HDC hDC = reinterpret_cast<HDC>(static_cast<sal_IntPtr>(
+ SendMessageW( pSalData->mpInstance->mhComWnd,
+ SAL_MSG_GETCACHEDDC, reinterpret_cast<WPARAM>(hWnd), 0 )));
+ InitFrameGraphicsDC( mpThreadGraphics, hDC, hWnd );
+ if ( hDC )
+ {
+ // re-select saved gdi objects
+ if( hFont )
+ SelectObject( hDC, hFont );
+ if( hPen )
+ SelectObject( hDC, hPen );
+ if( hBrush )
+ SelectObject( hDC, hBrush );
+
+ SAL_WARN_IF( oldCount != pSalData->mnCacheDCInUse, "vcl", "WinSalFrame::SetParent() hDC count corrupted");
+ }
+ }
+
+ // re-create local DC
+ if( bHadLocalGraphics )
+ InitFrameGraphicsDC( mpLocalGraphics, GetDC( hWnd ), hWnd );
+
+ // TODO: add SetParent() call for SalObjects
+ SAL_WARN_IF( !systemChildren.empty(), "vcl", "WinSalFrame::SetParent() parent of living system child window will be destroyed!");
+
+ // reparent children before old parent is destroyed
+ for (auto & child : children)
+ child->ImplSetParentFrame( hWnd, false );
+
+ children.clear();
+ systemChildren.clear();
+
+ // Now destroy original HWND in the thread where it was created.
+ SendMessageW( GetSalData()->mpInstance->mhComWnd,
+ SAL_MSG_DESTROYHWND, WPARAM(0), reinterpret_cast<LPARAM>(hWndOld));
+}
+
+void WinSalFrame::SetParent( SalFrame* pNewParent )
+{
+ WinSalFrame::mbInReparent = true;
+ ImplSetParentFrame( static_cast<WinSalFrame*>(pNewParent)->mhWnd, false );
+ WinSalFrame::mbInReparent = false;
+}
+
+void WinSalFrame::SetPluginParent( SystemParentData* pNewParent )
+{
+ if ( pNewParent->hWnd == nullptr )
+ {
+ pNewParent->hWnd = GetDesktopWindow();
+ }
+
+ WinSalFrame::mbInReparent = true;
+ ImplSetParentFrame( pNewParent->hWnd, true );
+ WinSalFrame::mbInReparent = false;
+}
+
+void WinSalFrame::GetWorkArea( AbsoluteScreenPixelRectangle &rRect )
+{
+ RECT aRect;
+
+ // pass cursor's position to ImplSalGetWorkArea to determine work area on
+ // multi monitor setups correctly.
+ POINT aPoint;
+ GetCursorPos(&aPoint);
+ RECT aRect2{ aPoint.x, aPoint.y, aPoint.x + 2, aPoint.y + 2 };
+
+ ImplSalGetWorkArea( mhWnd, &aRect, &aRect2 );
+ rRect.SetLeft( aRect.left );
+ rRect.SetRight( aRect.right-1 );
+ rRect.SetTop( aRect.top );
+ rRect.SetBottom( aRect.bottom-1 );
+}
+
+void WinSalFrame::GetClientSize( tools::Long& rWidth, tools::Long& rHeight )
+{
+ rWidth = maGeometry.width();
+ rHeight = maGeometry.height();
+}
+
+void WinSalFrame::SetWindowState(const vcl::WindowData* pState)
+{
+ // Check if the window fits into the screen, in case the screen
+ // resolution changed
+ int nX;
+ int nY;
+ int nWidth;
+ int nHeight;
+ int nScreenX;
+ int nScreenY;
+ int nScreenWidth;
+ int nScreenHeight;
+
+ RECT aRect;
+ ImplSalGetWorkArea( mhWnd, &aRect, nullptr );
+ // #102500# allow some overlap, the window could have been made a little larger than the physical screen
+ nScreenX = aRect.left-10;
+ nScreenY = aRect.top-10;
+ nScreenWidth = aRect.right-aRect.left+20;
+ nScreenHeight = aRect.bottom-aRect.top+20;
+
+ UINT nPosSize = 0;
+ RECT aWinRect;
+ GetWindowRect( mhWnd, &aWinRect );
+
+ // to be consistent with Unix, the frame state is without(!) decoration
+ // ->add the decoration
+ RECT aRect2 = aWinRect;
+ AdjustWindowRectEx( &aRect2, GetWindowStyle( mhWnd ),
+ FALSE, GetWindowExStyle( mhWnd ) );
+ tools::Long nTopDeco = abs( aWinRect.top - aRect2.top );
+ tools::Long nLeftDeco = abs( aWinRect.left - aRect2.left );
+ tools::Long nBottomDeco = abs( aWinRect.bottom - aRect2.bottom );
+ tools::Long nRightDeco = abs( aWinRect.right - aRect2.right );
+
+ // adjust window position/size to fit the screen
+ if ( !(pState->mask() & vcl::WindowDataMask::Pos) )
+ nPosSize |= SWP_NOMOVE;
+ if ( !(pState->mask() & vcl::WindowDataMask::Size) )
+ nPosSize |= SWP_NOSIZE;
+ if ( pState->mask() & vcl::WindowDataMask::X )
+ nX = static_cast<int>(pState->x()) - nLeftDeco;
+ else
+ nX = aWinRect.left;
+ if ( pState->mask() & vcl::WindowDataMask::Y )
+ nY = static_cast<int>(pState->y()) - nTopDeco;
+ else
+ nY = aWinRect.top;
+ if ( pState->mask() & vcl::WindowDataMask::Width )
+ nWidth = static_cast<int>(pState->width()) + nLeftDeco + nRightDeco;
+ else
+ nWidth = aWinRect.right-aWinRect.left;
+ if ( pState->mask() & vcl::WindowDataMask::Height )
+ nHeight = static_cast<int>(pState->height()) + nTopDeco + nBottomDeco;
+ else
+ nHeight = aWinRect.bottom-aWinRect.top;
+
+ // Adjust Window in the screen:
+ // if it does not fit into the screen do nothing, ie default pos/size will be used
+ // if there is an overlap with the screen border move the window while keeping its size
+
+ if( nWidth > nScreenWidth || nHeight > nScreenHeight )
+ nPosSize |= (SWP_NOMOVE | SWP_NOSIZE);
+
+ if ( nX+nWidth > nScreenX+nScreenWidth )
+ nX = (nScreenX+nScreenWidth) - nWidth;
+ if ( nY+nHeight > nScreenY+nScreenHeight )
+ nY = (nScreenY+nScreenHeight) - nHeight;
+ if ( nX < nScreenX )
+ nX = nScreenX;
+ if ( nY < nScreenY )
+ nY = nScreenY;
+
+ // set Restore-Position
+ WINDOWPLACEMENT aPlacement;
+ aPlacement.length = sizeof( aPlacement );
+ GetWindowPlacement( mhWnd, &aPlacement );
+
+ // set State
+ const bool bIsMinimized = IsIconic(mhWnd);
+ const bool bIsMaximized = IsZoomed(mhWnd);
+ const bool bVisible = (GetWindowStyle(mhWnd) & WS_VISIBLE);
+ bool bUpdateHiddenFramePos = false;
+ if ( !bVisible )
+ {
+ aPlacement.showCmd = SW_HIDE;
+
+ if (mbOverwriteState && (pState->mask() & vcl::WindowDataMask::State))
+ {
+ if (pState->state() & vcl::WindowState::Minimized)
+ mnShowState = SW_SHOWMINIMIZED;
+ else if (pState->state() & vcl::WindowState::Maximized)
+ {
+ mnShowState = SW_SHOWMAXIMIZED;
+ bUpdateHiddenFramePos = true;
+ }
+ else if (pState->state() & vcl::WindowState::Normal)
+ mnShowState = SW_SHOWNORMAL;
+ }
+ }
+ else
+ {
+ if ( pState->mask() & vcl::WindowDataMask::State )
+ {
+ if ( pState->state() & vcl::WindowState::Minimized )
+ {
+ if ( pState->state() & vcl::WindowState::Maximized )
+ aPlacement.flags |= WPF_RESTORETOMAXIMIZED;
+ aPlacement.showCmd = SW_SHOWMINIMIZED;
+ }
+ else if ( pState->state() & vcl::WindowState::Maximized )
+ aPlacement.showCmd = SW_SHOWMAXIMIZED;
+ else if ( pState->state() & vcl::WindowState::Normal )
+ aPlacement.showCmd = SW_RESTORE;
+ }
+ }
+
+ // if a window is neither minimized nor maximized or need not be
+ // positioned visibly (that is in visible state), do not use
+ // SetWindowPlacement since it calculates including the TaskBar
+ if (!bIsMinimized && !bIsMaximized && (!bVisible || (aPlacement.showCmd == SW_RESTORE)))
+ {
+ if( bUpdateHiddenFramePos )
+ {
+ RECT aStateRect;
+ aStateRect.left = nX;
+ aStateRect.top = nY;
+ aStateRect.right = nX+nWidth;
+ aStateRect.bottom = nY+nHeight;
+ // #96084 set a useful internal window size because
+ // the window will not be maximized (and the size updated) before show()
+ SetMaximizedFrameGeometry( mhWnd, this, &aStateRect );
+ SetWindowPos( mhWnd, nullptr,
+ maGeometry.x(), maGeometry.y(), maGeometry.width(), maGeometry.height(),
+ SWP_NOZORDER | SWP_NOACTIVATE | nPosSize );
+ }
+ else
+ SetWindowPos( mhWnd, nullptr,
+ nX, nY, nWidth, nHeight,
+ SWP_NOZORDER | SWP_NOACTIVATE | nPosSize );
+ }
+ else
+ {
+ if( !(nPosSize & (SWP_NOMOVE|SWP_NOSIZE)) )
+ {
+ aPlacement.rcNormalPosition.left = nX-nScreenX;
+ aPlacement.rcNormalPosition.top = nY-nScreenY;
+ aPlacement.rcNormalPosition.right = nX+nWidth-nScreenX;
+ aPlacement.rcNormalPosition.bottom = nY+nHeight-nScreenY;
+ }
+ SetWindowPlacement( mhWnd, &aPlacement );
+ }
+
+ if( !(nPosSize & SWP_NOMOVE) )
+ mbDefPos = false; // window was positioned
+}
+
+bool WinSalFrame::GetWindowState(vcl::WindowData* pState)
+{
+ pState->setPosSize(maGeometry.posSize());
+ pState->setState(m_eState);
+ pState->setMask(vcl::WindowDataMask::PosSizeState);
+ return true;
+}
+
+void WinSalFrame::SetScreenNumber( unsigned int nNewScreen )
+{
+ WinSalSystem* pSys = static_cast<WinSalSystem*>(ImplGetSalSystem());
+ if( pSys )
+ {
+ const std::vector<WinSalSystem::DisplayMonitor>& rMonitors =
+ pSys->getMonitors();
+ size_t nMon = rMonitors.size();
+ if( nNewScreen < nMon )
+ {
+ AbsoluteScreenPixelPoint aOldMonPos, aNewMonPos( rMonitors[nNewScreen].m_aArea.TopLeft() );
+ AbsoluteScreenPixelPoint aCurPos(maGeometry.pos());
+ for( size_t i = 0; i < nMon; i++ )
+ {
+ if( rMonitors[i].m_aArea.Contains( aCurPos ) )
+ {
+ aOldMonPos = rMonitors[i].m_aArea.TopLeft();
+ break;
+ }
+ }
+ mnDisplay = nNewScreen;
+ maGeometry.setScreen(nNewScreen);
+ SetPosSize( aNewMonPos.X() + (maGeometry.x() - aOldMonPos.X()),
+ aNewMonPos.Y() + (maGeometry.y() - aOldMonPos.Y()),
+ 0, 0,
+ SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y );
+ }
+ }
+}
+
+void WinSalFrame::SetApplicationID( const OUString &rApplicationID )
+{
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/dd378430(v=vs.85).aspx
+ // A window's properties must be removed before the window is closed.
+
+ IPropertyStore *pps;
+ HRESULT hr = SHGetPropertyStoreForWindow(mhWnd, IID_PPV_ARGS(&pps));
+ if (SUCCEEDED(hr))
+ {
+ PROPVARIANT pv;
+ if (!rApplicationID.isEmpty())
+ {
+ hr = InitPropVariantFromString(o3tl::toW(rApplicationID.getStr()), &pv);
+ mbPropertiesStored = true;
+ }
+ else
+ // if rApplicationID we remove the property from the window, if present
+ PropVariantInit(&pv);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pps->SetValue(PKEY_AppUserModel_ID, pv);
+ PropVariantClear(&pv);
+ }
+ pps->Release();
+ }
+}
+
+void WinSalFrame::ShowFullScreen(const bool bFullScreen, const sal_Int32 nDisplay)
+{
+ if ((isFullScreen() == bFullScreen) && (!bFullScreen || (mnDisplay == nDisplay)))
+ return;
+
+ mnDisplay = nDisplay;
+
+ if ( bFullScreen )
+ {
+ m_eState |= vcl::WindowState::FullScreen;
+ // to hide the Windows taskbar
+ DWORD nExStyle = GetWindowExStyle( mhWnd );
+ if ( nExStyle & WS_EX_TOOLWINDOW )
+ {
+ mbFullScreenToolWin = true;
+ nExStyle &= ~WS_EX_TOOLWINDOW;
+ SetWindowExStyle( mhWnd, nExStyle );
+ }
+ // save old position
+ GetWindowRect( mhWnd, &maFullScreenRect );
+
+ // save show state
+ mnFullScreenShowState = mnShowState;
+ if ( !(GetWindowStyle( mhWnd ) & WS_VISIBLE) )
+ mnShowState = SW_SHOW;
+
+ // Save caption state.
+ mbFullScreenCaption = mbCaption;
+ if (mbCaption)
+ {
+ DWORD nStyle = GetWindowStyle(mhWnd);
+ SetWindowStyle(mhWnd, nStyle & ~WS_CAPTION);
+ mbCaption = false;
+ }
+
+ // set window to screen size
+ ImplSalFrameFullScreenPos( this, true );
+ }
+ else
+ {
+ m_eState &= ~vcl::WindowState::FullScreen;
+ // when the ShowState has to be reset, hide the window first to
+ // reduce flicker
+ bool bVisible = (GetWindowStyle( mhWnd ) & WS_VISIBLE) != 0;
+ if ( bVisible && (mnShowState != mnFullScreenShowState) )
+ ShowWindow( mhWnd, SW_HIDE );
+
+ if ( mbFullScreenToolWin )
+ SetWindowExStyle( mhWnd, GetWindowExStyle( mhWnd ) | WS_EX_TOOLWINDOW );
+ mbFullScreenToolWin = false;
+
+ // Restore caption state.
+ if (mbFullScreenCaption)
+ {
+ DWORD nStyle = GetWindowStyle(mhWnd);
+ SetWindowStyle(mhWnd, nStyle | WS_CAPTION);
+ }
+ mbCaption = mbFullScreenCaption;
+
+ SetWindowPos( mhWnd, nullptr,
+ maFullScreenRect.left,
+ maFullScreenRect.top,
+ maFullScreenRect.right-maFullScreenRect.left,
+ maFullScreenRect.bottom-maFullScreenRect.top,
+ SWP_NOZORDER | SWP_NOACTIVATE );
+
+ // restore show state
+ if ( mnShowState != mnFullScreenShowState )
+ {
+ mnShowState = mnFullScreenShowState;
+ if ( bVisible )
+ {
+ mbInShow = true;
+ ShowWindow( mhWnd, mnShowState );
+ mbInShow = false;
+ UpdateWindow( mhWnd );
+ }
+ }
+ }
+}
+
+void WinSalFrame::StartPresentation( bool bStart )
+{
+ if ( mbPresentation == bStart )
+ return;
+
+ mbPresentation = bStart;
+
+ if ( bStart )
+ {
+ // turn off screen-saver / power saving when in Presentation mode
+ SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
+ }
+ else
+ {
+ // turn on screen-saver / power saving back
+ SetThreadExecutionState(ES_CONTINUOUS);
+ }
+}
+
+void WinSalFrame::SetAlwaysOnTop( bool bOnTop )
+{
+ HWND hWnd;
+ if ( bOnTop )
+ hWnd = HWND_TOPMOST;
+ else
+ hWnd = HWND_NOTOPMOST;
+ SetWindowPos( mhWnd, hWnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE );
+}
+
+static bool EnableAttachThreadInputHack()
+{
+ OUString aBootstrapUri;
+ if (osl_getProcessWorkingDir(&aBootstrapUri.pData) != osl_Process_E_None)
+ return false;
+ aBootstrapUri += "/bootstrap.ini";
+
+ OUString aSystemFileName;
+ if (osl::FileBase::getSystemPathFromFileURL(aBootstrapUri, aSystemFileName) != osl::FileBase::E_None)
+ return false;
+ if (aSystemFileName.getLength() > MAX_PATH)
+ return false;
+
+ // this uses the Boost ini parser, instead of tools::Config, as we already use it to read other
+ // values from bootstrap.ini in desktop/win32/source/loader.cxx, because that watchdog process
+ // can't access LO libs. This way the handling is consistent.
+ try
+ {
+ boost::property_tree::ptree pt;
+ std::ifstream aFile(o3tl::toW(aSystemFileName.getStr()));
+ boost::property_tree::ini_parser::read_ini(aFile, pt);
+ const bool bEnabled = pt.get("Win32.EnableAttachThreadInputHack", false);
+ SAL_WARN_IF(bEnabled, "vcl", "AttachThreadInput hack is enabled. Watch out for deadlocks!");
+ return bEnabled;
+ }
+ catch (...)
+ {
+ return false;
+ }
+}
+
+static void ImplSalToTop( HWND hWnd, SalFrameToTop nFlags )
+{
+ static const bool bEnableAttachThreadInputHack = EnableAttachThreadInputHack();
+
+ WinSalFrame* pToTopFrame = GetWindowPtr( hWnd );
+ if( pToTopFrame && (pToTopFrame->mnStyle & SalFrameStyleFlags::SYSTEMCHILD) )
+ BringWindowToTop( hWnd );
+
+ if ( nFlags & SalFrameToTop::ForegroundTask )
+ {
+ // LO used to always call AttachThreadInput here, which resulted in deadlocks
+ // in some installations for unknown reasons!
+ if (bEnableAttachThreadInputHack)
+ {
+ // This magic code is necessary to connect the input focus of the
+ // current window thread and the thread which owns the window that
+ // should be the new foreground window.
+ HWND hCurrWnd = GetForegroundWindow();
+ DWORD myThreadID = GetCurrentThreadId();
+ DWORD currThreadID = GetWindowThreadProcessId(hCurrWnd,nullptr);
+ AttachThreadInput(myThreadID, currThreadID, TRUE);
+ SetForegroundWindow_Impl(hWnd);
+ AttachThreadInput(myThreadID, currThreadID, FALSE);
+ }
+ else
+ SetForegroundWindow_Impl(hWnd);
+ }
+
+ if ( nFlags & SalFrameToTop::RestoreWhenMin )
+ {
+ HWND hIconicWnd = hWnd;
+ while ( hIconicWnd )
+ {
+ if ( IsIconic( hIconicWnd ) )
+ {
+ WinSalFrame* pFrame = GetWindowPtr( hIconicWnd );
+ if ( pFrame )
+ {
+ if ( GetWindowPtr( hWnd )->mbRestoreMaximize )
+ ShowWindow( hIconicWnd, SW_MAXIMIZE );
+ else
+ ShowWindow( hIconicWnd, SW_RESTORE );
+ }
+ else
+ ShowWindow( hIconicWnd, SW_RESTORE );
+ }
+
+ hIconicWnd = ::GetParent( hIconicWnd );
+ }
+ }
+
+ if ( !IsIconic( hWnd ) && IsWindowVisible( hWnd ) )
+ {
+ SetFocus( hWnd );
+
+ // Windows sometimes incorrectly reports to have the focus;
+ // thus make sure to really get the focus
+ if ( ::GetFocus() == hWnd )
+ SetForegroundWindow_Impl( hWnd );
+ }
+}
+
+void WinSalFrame::ToTop( SalFrameToTop nFlags )
+{
+ nFlags &= ~SalFrameToTop::GrabFocus; // this flag is not needed on win32
+ // Post this Message to the window, because this only works
+ // in the thread of the window, which has create this window.
+ // We post this message to avoid deadlocks
+ if ( GetSalData()->mnAppThreadId != GetCurrentThreadId() )
+ {
+ bool const ret = PostMessageW( mhWnd, SAL_MSG_TOTOP, static_cast<WPARAM>(nFlags), 0 );
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+ }
+ else
+ ImplSalToTop( mhWnd, nFlags );
+}
+
+void WinSalFrame::SetPointer( PointerStyle ePointerStyle )
+{
+ struct ImplPtrData
+ {
+ HCURSOR mhCursor;
+ LPCTSTR mnSysId;
+ UINT mnOwnId;
+ };
+
+ static o3tl::enumarray<PointerStyle, ImplPtrData> aImplPtrTab =
+ {
+ // [-loplugin:redundantfcast]:
+ ImplPtrData{ nullptr, IDC_ARROW, 0 }, // POINTER_ARROW
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_NULL }, // POINTER_NULL
+ ImplPtrData{ nullptr, IDC_WAIT, 0 }, // POINTER_WAIT
+ ImplPtrData{ nullptr, IDC_IBEAM, 0 }, // POINTER_TEXT
+ ImplPtrData{ nullptr, IDC_HELP, 0 }, // POINTER_HELP
+ ImplPtrData{ nullptr, IDC_CROSS, 0 }, // POINTER_CROSS
+ ImplPtrData{ nullptr, IDC_SIZEALL, 0 }, // POINTER_MOVE
+ ImplPtrData{ nullptr, IDC_SIZENS, 0 }, // POINTER_NSIZE
+ ImplPtrData{ nullptr, IDC_SIZENS, 0 }, // POINTER_SSIZE
+ ImplPtrData{ nullptr, IDC_SIZEWE, 0 }, // POINTER_WSIZE
+ ImplPtrData{ nullptr, IDC_SIZEWE, 0 }, // POINTER_ESIZE
+ ImplPtrData{ nullptr, IDC_SIZENWSE, 0 }, // POINTER_NWSIZE
+ ImplPtrData{ nullptr, IDC_SIZENESW, 0 }, // POINTER_NESIZE
+ ImplPtrData{ nullptr, IDC_SIZENESW, 0 }, // POINTER_SWSIZE
+ ImplPtrData{ nullptr, IDC_SIZENWSE, 0 }, // POINTER_SESIZE
+ ImplPtrData{ nullptr, IDC_SIZENS, 0 }, // POINTER_WINDOW_NSIZE
+ ImplPtrData{ nullptr, IDC_SIZENS, 0 }, // POINTER_WINDOW_SSIZE
+ ImplPtrData{ nullptr, IDC_SIZEWE, 0 }, // POINTER_WINDOW_WSIZE
+ ImplPtrData{ nullptr, IDC_SIZEWE, 0 }, // POINTER_WINDOW_ESIZE
+ ImplPtrData{ nullptr, IDC_SIZENWSE, 0 }, // POINTER_WINDOW_NWSIZE
+ ImplPtrData{ nullptr, IDC_SIZENESW, 0 }, // POINTER_WINDOW_NESIZE
+ ImplPtrData{ nullptr, IDC_SIZENESW, 0 }, // POINTER_WINDOW_SWSIZE
+ ImplPtrData{ nullptr, IDC_SIZENWSE, 0 }, // POINTER_WINDOW_SESIZE
+ ImplPtrData{ nullptr, IDC_SIZEWE, 0 }, // POINTER_HSPLIT
+ ImplPtrData{ nullptr, IDC_SIZENS, 0 }, // POINTER_VSPLIT
+ ImplPtrData{ nullptr, IDC_SIZEWE, 0 }, // POINTER_HSIZEBAR
+ ImplPtrData{ nullptr, IDC_SIZENS, 0 }, // POINTER_VSIZEBAR
+ ImplPtrData{ nullptr, IDC_HAND, 0 }, // POINTER_HAND
+ ImplPtrData{ nullptr, IDC_HAND, 0 }, // POINTER_REFHAND
+ ImplPtrData{ nullptr, IDC_PEN, 0 }, // POINTER_PEN
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MAGNIFY }, // POINTER_MAGNIFY
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_FILL }, // POINTER_FILL
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_ROTATE }, // POINTER_ROTATE
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_HSHEAR }, // POINTER_HSHEAR
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_VSHEAR }, // POINTER_VSHEAR
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MIRROR }, // POINTER_MIRROR
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_CROOK }, // POINTER_CROOK
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_CROP }, // POINTER_CROP
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MOVEPOINT }, // POINTER_MOVEPOINT
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MOVEBEZIERWEIGHT }, // POINTER_MOVEBEZIERWEIGHT
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MOVEDATA }, // POINTER_MOVEDATA
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_COPYDATA }, // POINTER_COPYDATA
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_LINKDATA }, // POINTER_LINKDATA
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MOVEDATALINK }, // POINTER_MOVEDATALINK
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_COPYDATALINK }, // POINTER_COPYDATALINK
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MOVEFILE }, // POINTER_MOVEFILE
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_COPYFILE }, // POINTER_COPYFILE
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_LINKFILE }, // POINTER_LINKFILE
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MOVEFILELINK }, // POINTER_MOVEFILELINK
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_COPYFILELINK }, // POINTER_COPYFILELINK
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MOVEFILES }, // POINTER_MOVEFILES
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_COPYFILES }, // POINTER_COPYFILES
+ ImplPtrData{ nullptr, IDC_NO, 0 }, // POINTER_NOTALLOWED
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_LINE }, // POINTER_DRAW_LINE
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_RECT }, // POINTER_DRAW_RECT
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_POLYGON }, // POINTER_DRAW_POLYGON
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_BEZIER }, // POINTER_DRAW_BEZIER
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_ARC }, // POINTER_DRAW_ARC
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_PIE }, // POINTER_DRAW_PIE
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_CIRCLECUT }, // POINTER_DRAW_CIRCLECUT
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_ELLIPSE }, // POINTER_DRAW_ELLIPSE
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_FREEHAND }, // POINTER_DRAW_FREEHAND
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_CONNECT }, // POINTER_DRAW_CONNECT
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_TEXT }, // POINTER_DRAW_TEXT
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_CAPTION }, // POINTER_DRAW_CAPTION
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_CHART }, // POINTER_CHART
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DETECTIVE }, // POINTER_DETECTIVE
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_PIVOT_COL }, // POINTER_PIVOT_COL
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_PIVOT_ROW }, // POINTER_PIVOT_ROW
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_PIVOT_FIELD }, // POINTER_PIVOT_FIELD
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_CHAIN }, // POINTER_CHAIN
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_CHAIN_NOTALLOWED }, // POINTER_CHAIN_NOTALLOWED
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_N }, // POINTER_AUTOSCROLL_N
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_S }, // POINTER_AUTOSCROLL_S
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_W }, // POINTER_AUTOSCROLL_W
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_E }, // POINTER_AUTOSCROLL_E
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_NW }, // POINTER_AUTOSCROLL_NW
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_NE }, // POINTER_AUTOSCROLL_NE
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_SW }, // POINTER_AUTOSCROLL_SW
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_SE }, // POINTER_AUTOSCROLL_SE
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_NS }, // POINTER_AUTOSCROLL_NS
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_WE }, // POINTER_AUTOSCROLL_WE
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_NSWE }, // POINTER_AUTOSCROLL_NSWE
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_TEXT_VERTICAL }, // POINTER_TEXT_VERTICAL
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_PIVOT_DELETE }, // POINTER_PIVOT_DELETE
+
+ // #i32329#
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_TAB_SELECT_S }, // POINTER_TAB_SELECT_S
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_TAB_SELECT_E }, // POINTER_TAB_SELECT_E
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_TAB_SELECT_SE }, // POINTER_TAB_SELECT_SE
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_TAB_SELECT_W }, // POINTER_TAB_SELECT_W
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_TAB_SELECT_SW }, // POINTER_TAB_SELECT_SW
+
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_HIDEWHITESPACE }, // POINTER_HIDEWHITESPACE
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_SHOWWHITESPACE }, // POINTER_UNHIDEWHITESPACE
+
+ ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_FATCROSS } // POINTER_FATCROSS
+ };
+
+ // Mousepointer loaded ?
+ if ( !aImplPtrTab[ePointerStyle].mhCursor )
+ {
+ if ( aImplPtrTab[ePointerStyle].mnOwnId )
+ aImplPtrTab[ePointerStyle].mhCursor = ImplLoadSalCursor( aImplPtrTab[ePointerStyle].mnOwnId );
+ else
+ aImplPtrTab[ePointerStyle].mhCursor = LoadCursor( nullptr, aImplPtrTab[ePointerStyle].mnSysId );
+ }
+
+ // change the mouse pointer if different
+ if ( mhCursor != aImplPtrTab[ePointerStyle].mhCursor )
+ {
+ mhCursor = aImplPtrTab[ePointerStyle].mhCursor;
+ SetCursor( mhCursor );
+ }
+}
+
+void WinSalFrame::CaptureMouse( bool bCapture )
+{
+ // Send this Message to the window, because CaptureMouse() only work
+ // in the thread of the window, which has create this window
+ int nMsg;
+ if ( bCapture )
+ nMsg = SAL_MSG_CAPTUREMOUSE;
+ else
+ nMsg = SAL_MSG_RELEASEMOUSE;
+ SendMessageW( mhWnd, nMsg, 0, 0 );
+}
+
+void WinSalFrame::SetPointerPos( tools::Long nX, tools::Long nY )
+{
+ POINT aPt;
+ aPt.x = static_cast<int>(nX);
+ aPt.y = static_cast<int>(nY);
+ ClientToScreen( mhWnd, &aPt );
+ SetCursorPos( aPt.x, aPt.y );
+}
+
+void WinSalFrame::Flush()
+{
+ if(mpLocalGraphics)
+ mpLocalGraphics->Flush();
+ if(mpThreadGraphics)
+ mpThreadGraphics->Flush();
+ GdiFlush();
+}
+
+static void ImplSalFrameSetInputContext( HWND hWnd, const SalInputContext* pContext )
+{
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ bool bIME(pContext->mnOptions & InputContextFlags::Text);
+ if ( bIME )
+ {
+ if ( !pFrame->mbIME )
+ {
+ pFrame->mbIME = true;
+
+ if ( pFrame->mhDefIMEContext )
+ {
+ ImmAssociateContext( pFrame->mhWnd, pFrame->mhDefIMEContext );
+ UINT nImeProps = ImmGetProperty( GetKeyboardLayout( 0 ), IGP_PROPERTY );
+ pFrame->mbSpezIME = (nImeProps & IME_PROP_SPECIAL_UI) != 0;
+ pFrame->mbAtCursorIME = (nImeProps & IME_PROP_AT_CARET) != 0;
+ pFrame->mbHandleIME = !pFrame->mbSpezIME;
+ }
+ }
+
+ // When the application can't handle IME messages, then the
+ // System should handle the IME handling
+ if ( !(pContext->mnOptions & InputContextFlags::ExtText) )
+ pFrame->mbHandleIME = false;
+
+ // Set the Font for IME Handling
+ if ( pContext->mpFont )
+ {
+ HIMC hIMC = ImmGetContext( pFrame->mhWnd );
+ if ( hIMC )
+ {
+ LOGFONTW aLogFont;
+ ImplGetLogFontFromFontSelect(pContext->mpFont->GetFontSelectPattern(),
+ nullptr, aLogFont);
+ ImmSetCompositionFontW( hIMC, &aLogFont );
+ ImmReleaseContext( pFrame->mhWnd, hIMC );
+ }
+ }
+ }
+ else
+ {
+ if ( pFrame->mbIME )
+ {
+ pFrame->mbIME = false;
+ pFrame->mbHandleIME = false;
+ ImmAssociateContext( pFrame->mhWnd, nullptr );
+ }
+ }
+}
+
+void WinSalFrame::SetInputContext( SalInputContext* pContext )
+{
+ // Must be called in the main thread!
+ SendMessageW( mhWnd, SAL_MSG_SETINPUTCONTEXT, 0, reinterpret_cast<LPARAM>(pContext) );
+}
+
+static void ImplSalFrameEndExtTextInput( HWND hWnd, EndExtTextInputFlags nFlags )
+{
+ HIMC hIMC = ImmGetContext( hWnd );
+ if ( hIMC )
+ {
+ DWORD nIndex;
+ if ( nFlags & EndExtTextInputFlags::Complete )
+ nIndex = CPS_COMPLETE;
+ else
+ nIndex = CPS_CANCEL;
+
+ ImmNotifyIME( hIMC, NI_COMPOSITIONSTR, nIndex, 0 );
+ ImmReleaseContext( hWnd, hIMC );
+ }
+}
+
+void WinSalFrame::EndExtTextInput( EndExtTextInputFlags nFlags )
+{
+ // Must be called in the main thread!
+ SendMessageW( mhWnd, SAL_MSG_ENDEXTTEXTINPUT, static_cast<WPARAM>(nFlags), 0 );
+}
+
+static void ImplGetKeyNameText( LONG lParam, sal_Unicode* pBuf,
+ UINT& rCount, UINT nMaxSize,
+ const char* pReplace )
+{
+ static_assert( sizeof( WCHAR ) == sizeof( sal_Unicode ), "must be the same size" );
+
+ static const int nMaxKeyLen = 350;
+ WCHAR aKeyBuf[ nMaxKeyLen ];
+ int nKeyLen = 0;
+ if ( lParam )
+ {
+ OUString aLang = Application::GetSettings().GetUILanguageTag().getLanguage();
+ OUString aRet;
+
+ aRet = ::vcl_sal::getKeysReplacementName( aLang, lParam );
+ if( aRet.isEmpty() )
+ {
+ nKeyLen = GetKeyNameTextW( lParam, aKeyBuf, nMaxKeyLen );
+ SAL_WARN_IF( nKeyLen > nMaxKeyLen, "vcl", "Invalid key name length!" );
+ if( nKeyLen > nMaxKeyLen )
+ nKeyLen = 0;
+ else if( nKeyLen > 0 )
+ {
+ // Capitalize just the first letter of key names
+ CharLowerBuffW( aKeyBuf, nKeyLen );
+
+ bool bUpper = true;
+ for( WCHAR *pW=aKeyBuf, *pE=pW+nKeyLen; pW < pE; ++pW )
+ {
+ if( bUpper )
+ CharUpperBuffW( pW, 1 );
+ bUpper = (*pW=='+') || (*pW=='-') || (*pW==' ') || (*pW=='.');
+ }
+ }
+ }
+ else
+ {
+ nKeyLen = aRet.getLength();
+ wcscpy( aKeyBuf, o3tl::toW( aRet.getStr() ));
+ }
+ }
+
+ if ( (nKeyLen > 0) || pReplace )
+ {
+ if( (rCount > 0) && (rCount < nMaxSize) )
+ {
+ pBuf[rCount] = '+';
+ rCount++;
+ }
+
+ if( nKeyLen > 0 )
+ {
+ WCHAR *pW = aKeyBuf, *pE = aKeyBuf + nKeyLen;
+ while ((pW < pE) && *pW && (rCount < nMaxSize))
+ pBuf[rCount++] = *pW++;
+ }
+ else // fall back to provided default name
+ {
+ while( *pReplace && (rCount < nMaxSize) )
+ {
+ pBuf[rCount] = *pReplace;
+ rCount++;
+ pReplace++;
+ }
+ }
+ }
+ else
+ rCount = 0;
+}
+
+OUString WinSalFrame::GetKeyName( sal_uInt16 nKeyCode )
+{
+ static const UINT nMaxKeyLen = 350;
+ sal_Unicode aKeyBuf[ nMaxKeyLen ];
+ UINT nKeyBufLen = 0;
+ UINT nSysCode = 0;
+
+ if ( nKeyCode & KEY_MOD1 )
+ {
+ nSysCode = MapVirtualKeyW( VK_CONTROL, 0 );
+ nSysCode = (nSysCode << 16) | ((sal_uLong(1)) << 25);
+ ImplGetKeyNameText( nSysCode, aKeyBuf, nKeyBufLen, nMaxKeyLen, "Ctrl" );
+ }
+
+ if ( nKeyCode & KEY_MOD2 )
+ {
+ nSysCode = MapVirtualKeyW( VK_MENU, 0 );
+ nSysCode = (nSysCode << 16) | ((sal_uLong(1)) << 25);
+ ImplGetKeyNameText( nSysCode, aKeyBuf, nKeyBufLen, nMaxKeyLen, "Alt" );
+ }
+
+ if ( nKeyCode & KEY_SHIFT )
+ {
+ nSysCode = MapVirtualKeyW( VK_SHIFT, 0 );
+ nSysCode = (nSysCode << 16) | ((sal_uLong(1)) << 25);
+ ImplGetKeyNameText( nSysCode, aKeyBuf, nKeyBufLen, nMaxKeyLen, "Shift" );
+ }
+
+ sal_uInt16 nCode = nKeyCode & 0x0FFF;
+ sal_uLong nSysCode2 = 0;
+ const char* pReplace = nullptr;
+ sal_Unicode cSVCode = 0;
+ char aFBuf[4];
+ nSysCode = 0;
+
+ if ( (nCode >= KEY_0) && (nCode <= KEY_9) )
+ cSVCode = '0' + (nCode - KEY_0);
+ else if ( (nCode >= KEY_A) && (nCode <= KEY_Z) )
+ cSVCode = 'A' + (nCode - KEY_A);
+ else if ( (nCode >= KEY_F1) && (nCode <= KEY_F26) )
+ {
+ nSysCode = VK_F1 + (nCode - KEY_F1);
+ aFBuf[0] = 'F';
+ if (nCode <= KEY_F9)
+ {
+ aFBuf[1] = sal::static_int_cast<char>('1' + (nCode - KEY_F1));
+ aFBuf[2] = 0;
+ }
+ else if (nCode <= KEY_F19)
+ {
+ aFBuf[1] = '1';
+ aFBuf[2] = sal::static_int_cast<char>('0' + (nCode - KEY_F10));
+ aFBuf[3] = 0;
+ }
+ else
+ {
+ aFBuf[1] = '2';
+ aFBuf[2] = sal::static_int_cast<char>('0' + (nCode - KEY_F20));
+ aFBuf[3] = 0;
+ }
+ pReplace = aFBuf;
+ }
+ else
+ {
+ switch ( nCode )
+ {
+ case KEY_DOWN:
+ nSysCode = VK_DOWN;
+ nSysCode2 = ((sal_uLong(1)) << 24);
+ pReplace = "Down";
+ break;
+ case KEY_UP:
+ nSysCode = VK_UP;
+ nSysCode2 = ((sal_uLong(1)) << 24);
+ pReplace = "Up";
+ break;
+ case KEY_LEFT:
+ nSysCode = VK_LEFT;
+ nSysCode2 = ((sal_uLong(1)) << 24);
+ pReplace = "Left";
+ break;
+ case KEY_RIGHT:
+ nSysCode = VK_RIGHT;
+ nSysCode2 = ((sal_uLong(1)) << 24);
+ pReplace = "Right";
+ break;
+ case KEY_HOME:
+ nSysCode = VK_HOME;
+ nSysCode2 = ((sal_uLong(1)) << 24);
+ pReplace = "Home";
+ break;
+ case KEY_END:
+ nSysCode = VK_END;
+ nSysCode2 = ((sal_uLong(1)) << 24);
+ pReplace = "End";
+ break;
+ case KEY_PAGEUP:
+ nSysCode = VK_PRIOR;
+ nSysCode2 = ((sal_uLong(1)) << 24);
+ pReplace = "Page Up";
+ break;
+ case KEY_PAGEDOWN:
+ nSysCode = VK_NEXT;
+ nSysCode2 = ((sal_uLong(1)) << 24);
+ pReplace = "Page Down";
+ break;
+ case KEY_RETURN:
+ nSysCode = VK_RETURN;
+ pReplace = "Enter";
+ break;
+ case KEY_ESCAPE:
+ nSysCode = VK_ESCAPE;
+ pReplace = "Escape";
+ break;
+ case KEY_TAB:
+ nSysCode = VK_TAB;
+ pReplace = "Tab";
+ break;
+ case KEY_BACKSPACE:
+ nSysCode = VK_BACK;
+ pReplace = "Backspace";
+ break;
+ case KEY_SPACE:
+ nSysCode = VK_SPACE;
+ pReplace = "Space";
+ break;
+ case KEY_INSERT:
+ nSysCode = VK_INSERT;
+ nSysCode2 = ((sal_uLong(1)) << 24);
+ pReplace = "Insert";
+ break;
+ case KEY_DELETE:
+ nSysCode = VK_DELETE;
+ nSysCode2 = ((sal_uLong(1)) << 24);
+ pReplace = "Delete";
+ break;
+
+ case KEY_ADD:
+ cSVCode = '+';
+ break;
+ case KEY_SUBTRACT:
+ cSVCode = '-';
+ break;
+ case KEY_MULTIPLY:
+ cSVCode = '*';
+ break;
+ case KEY_DIVIDE:
+ cSVCode = '/';
+ break;
+ case KEY_POINT:
+ cSVCode = '.';
+ break;
+ case KEY_COMMA:
+ cSVCode = ',';
+ break;
+ case KEY_LESS:
+ cSVCode = '<';
+ break;
+ case KEY_GREATER:
+ cSVCode = '>';
+ break;
+ case KEY_EQUAL:
+ cSVCode = '=';
+ break;
+ case KEY_NUMBERSIGN:
+ cSVCode = '#';
+ break;
+ case KEY_XF86FORWARD:
+ cSVCode = VK_BROWSER_FORWARD;
+ break;
+ case KEY_XF86BACK:
+ cSVCode = VK_BROWSER_BACK;
+ break;
+ case KEY_COLON:
+ cSVCode = ':';
+ break;
+ case KEY_SEMICOLON:
+ cSVCode = ';';
+ break;
+ case KEY_QUOTERIGHT:
+ cSVCode = '\'';
+ break;
+ case KEY_BRACKETLEFT:
+ cSVCode = '[';
+ break;
+ case KEY_BRACKETRIGHT:
+ cSVCode = ']';
+ break;
+ case KEY_QUOTELEFT:
+ cSVCode = '`';
+ break;
+ case KEY_RIGHTCURLYBRACKET:
+ cSVCode = '}';
+ break;
+ }
+ }
+
+ if ( nSysCode )
+ {
+ nSysCode = MapVirtualKeyW( nSysCode, 0 );
+ if ( nSysCode )
+ nSysCode = (nSysCode << 16) | nSysCode2;
+ ImplGetKeyNameText( nSysCode, aKeyBuf, nKeyBufLen, nMaxKeyLen, pReplace );
+ }
+ else
+ {
+ if ( cSVCode )
+ {
+ if ( nKeyBufLen > 0 )
+ aKeyBuf[ nKeyBufLen++ ] = '+';
+ if( nKeyBufLen < nMaxKeyLen )
+ aKeyBuf[ nKeyBufLen++ ] = cSVCode;
+ }
+ }
+
+ if( !nKeyBufLen )
+ return OUString();
+
+ return OUString( aKeyBuf, sal::static_int_cast< sal_uInt16 >(nKeyBufLen) );
+}
+
+static Color ImplWinColorToSal( COLORREF nColor )
+{
+ return Color( GetRValue( nColor ), GetGValue( nColor ), GetBValue( nColor ) );
+}
+
+static void ImplSalUpdateStyleFontW( HDC hDC, const LOGFONTW& rLogFont, vcl::Font& rFont )
+{
+ ImplSalLogFontToFontW( hDC, rLogFont, rFont );
+
+ // On Windows 9x, Windows NT we get sometimes very small sizes
+ // (for example for the small Caption height).
+ // So if it is MS Sans Serif, a none scalable font we use
+ // 8 Point as the minimum control height, in all other cases
+ // 6 Point is the smallest one
+ if ( rFont.GetFontHeight() < 8 )
+ {
+ if ( rtl_ustr_compareIgnoreAsciiCase( o3tl::toU(rLogFont.lfFaceName), o3tl::toU(L"MS Sans Serif") ) == 0 )
+ rFont.SetFontHeight( 8 );
+ else if ( rFont.GetFontHeight() < 6 )
+ rFont.SetFontHeight( 6 );
+ }
+}
+
+static tools::Long ImplW2I( const wchar_t* pStr )
+{
+ tools::Long n = 0;
+ int nSign = 1;
+
+ if ( *pStr == '-' )
+ {
+ nSign = -1;
+ pStr++;
+ }
+
+ while( (*pStr >= 48) && (*pStr <= 57) )
+ {
+ n *= 10;
+ n += ((*pStr) - 48);
+ pStr++;
+ }
+
+ n *= nSign;
+
+ return n;
+}
+
+void WinSalFrame::UpdateSettings( AllSettings& rSettings )
+{
+ MouseSettings aMouseSettings = rSettings.GetMouseSettings();
+ aMouseSettings.SetDoubleClickTime( GetDoubleClickTime() );
+ aMouseSettings.SetDoubleClickWidth( GetSystemMetrics( SM_CXDOUBLECLK ) );
+ aMouseSettings.SetDoubleClickHeight( GetSystemMetrics( SM_CYDOUBLECLK ) );
+ tools::Long nDragWidth = GetSystemMetrics( SM_CXDRAG );
+ tools::Long nDragHeight = GetSystemMetrics( SM_CYDRAG );
+ if ( nDragWidth )
+ aMouseSettings.SetStartDragWidth( nDragWidth );
+ if ( nDragHeight )
+ aMouseSettings.SetStartDragHeight( nDragHeight );
+ HKEY hRegKey;
+ if ( RegOpenKeyW( HKEY_CURRENT_USER,
+ L"Control Panel\\Desktop",
+ &hRegKey ) == ERROR_SUCCESS )
+ {
+ wchar_t aValueBuf[10];
+ DWORD nValueSize = sizeof( aValueBuf );
+ DWORD nType;
+ if ( RegQueryValueExW( hRegKey, L"MenuShowDelay", nullptr,
+ &nType, reinterpret_cast<LPBYTE>(aValueBuf), &nValueSize ) == ERROR_SUCCESS )
+ {
+ if ( nType == REG_SZ )
+ aMouseSettings.SetMenuDelay( static_cast<sal_uLong>(ImplW2I( aValueBuf )) );
+ }
+
+ RegCloseKey( hRegKey );
+ }
+
+ StyleSettings aStyleSettings = rSettings.GetStyleSettings();
+
+ // High contrast
+ HIGHCONTRAST hc;
+ hc.cbSize = sizeof( HIGHCONTRAST );
+ if( SystemParametersInfoW( SPI_GETHIGHCONTRAST, hc.cbSize, &hc, 0 )
+ && (hc.dwFlags & HCF_HIGHCONTRASTON) )
+ aStyleSettings.SetHighContrastMode( true );
+ else
+ aStyleSettings.SetHighContrastMode( false );
+
+ aStyleSettings.SetScrollBarSize( GetSystemMetrics( SM_CXVSCROLL ) );
+ aStyleSettings.SetSpinSize( GetSystemMetrics( SM_CXVSCROLL ) );
+ UINT blinkTime = GetCaretBlinkTime();
+ aStyleSettings.SetCursorBlinkTime(
+ blinkTime == 0 || blinkTime == INFINITE // 0 indicates error
+ ? STYLE_CURSOR_NOBLINKTIME : blinkTime );
+ aStyleSettings.SetFloatTitleHeight( GetSystemMetrics( SM_CYSMCAPTION ) );
+ aStyleSettings.SetTitleHeight( GetSystemMetrics( SM_CYCAPTION ) );
+ aStyleSettings.SetActiveBorderColor( ImplWinColorToSal( GetSysColor( COLOR_ACTIVEBORDER ) ) );
+ aStyleSettings.SetDeactiveBorderColor( ImplWinColorToSal( GetSysColor( COLOR_INACTIVEBORDER ) ) );
+ aStyleSettings.SetDeactiveColor( ImplWinColorToSal( GetSysColor( COLOR_GRADIENTINACTIVECAPTION ) ) );
+
+ Color aControlTextColor;
+ Color aMenuBarTextColor;
+ Color aMenuBarRolloverTextColor;
+ Color aHighlightTextColor = ImplWinColorToSal(GetSysColor(COLOR_HIGHLIGHTTEXT));
+
+ BOOL bFlatMenus = FALSE;
+ SystemParametersInfoW( SPI_GETFLATMENU, 0, &bFlatMenus, 0);
+ if( bFlatMenus )
+ {
+ aStyleSettings.SetUseFlatMenus( true );
+ // flat borders for our controls etc. as well in this mode (ie, no 3d borders)
+ // this is not active in the classic style appearance
+ aStyleSettings.SetUseFlatBorders( true );
+ }
+ else
+ {
+ aStyleSettings.SetUseFlatMenus( false );
+ aStyleSettings.SetUseFlatBorders( false );
+ }
+
+ if( bFlatMenus )
+ {
+ aStyleSettings.SetMenuHighlightColor( ImplWinColorToSal( GetSysColor( COLOR_MENUHILIGHT ) ) );
+ aStyleSettings.SetMenuBarRolloverColor( ImplWinColorToSal( GetSysColor( COLOR_MENUHILIGHT ) ) );
+ aStyleSettings.SetMenuBorderColor( ImplWinColorToSal( GetSysColor( COLOR_3DSHADOW ) ) );
+ }
+ else
+ {
+ aStyleSettings.SetMenuHighlightColor( ImplWinColorToSal( GetSysColor( COLOR_HIGHLIGHT ) ) );
+ aStyleSettings.SetMenuBarRolloverColor( ImplWinColorToSal( GetSysColor( COLOR_HIGHLIGHT ) ) );
+ aStyleSettings.SetMenuBorderColor( ImplWinColorToSal( GetSysColor( COLOR_3DLIGHT ) ) );
+ }
+
+ const bool bUseDarkMode(UseDarkMode());
+
+ OUString sThemeName(!bUseDarkMode ? u"colibre" : u"colibre_dark");
+ aStyleSettings.SetPreferredIconTheme(sThemeName, bUseDarkMode);
+
+ if (bUseDarkMode)
+ {
+ SetWindowTheme(mhWnd, L"Explorer", nullptr);
+
+ HTHEME hTheme = OpenThemeData(nullptr, L"ItemsView");
+ COLORREF color;
+ GetThemeColor(hTheme, 0, 0, TMT_FILLCOLOR, &color);
+ aStyleSettings.SetFaceColor( ImplWinColorToSal( color ) );
+ aStyleSettings.SetWindowColor( ImplWinColorToSal( color ) );
+
+ // tdf#156040 in the absence of a better idea, do like
+ // StyleSettings::Set3DColors does
+ Color aLightColor(ImplWinColorToSal(color));
+ aLightColor.DecreaseLuminance(64);
+ aStyleSettings.SetLightColor(aLightColor);
+
+ GetThemeColor(hTheme, 0, 0, TMT_TEXTCOLOR, &color);
+ aStyleSettings.SetWindowTextColor( ImplWinColorToSal( color ) );
+ aStyleSettings.SetToolTextColor( ImplWinColorToSal( color ) );
+ GetThemeColor(hTheme, 0, 0, TMT_SHADOWCOLOR, &color);
+ aStyleSettings.SetShadowColor( ImplWinColorToSal( color ) );
+ GetThemeColor(hTheme, 0, 0, TMT_DKSHADOW3D, &color);
+ aStyleSettings.SetDarkShadowColor( ImplWinColorToSal( color ) );
+ CloseThemeData(hTheme);
+
+ hTheme = OpenThemeData(mhWnd, L"Button");
+ GetThemeColor(hTheme, BP_PUSHBUTTON, PBS_NORMAL, TMT_TEXTCOLOR, &color);
+ aControlTextColor = ImplWinColorToSal(color);
+ GetThemeColor(hTheme, BP_CHECKBOX, CBS_CHECKEDNORMAL, TMT_TEXTCOLOR, &color);
+ aStyleSettings.SetRadioCheckTextColor( ImplWinColorToSal( color ) );
+ CloseThemeData(hTheme);
+
+ SetWindowTheme(mhWnd, nullptr, nullptr);
+
+ hTheme = OpenThemeData(mhWnd, L"Menu");
+ GetThemeColor(hTheme, MENU_POPUPITEM, MBI_NORMAL, TMT_TEXTCOLOR, &color);
+ aStyleSettings.SetMenuTextColor( ImplWinColorToSal( color ) );
+ aMenuBarTextColor = ImplWinColorToSal( color );
+ aMenuBarRolloverTextColor = ImplWinColorToSal( color );
+ CloseThemeData(hTheme);
+
+ aStyleSettings.SetActiveTabColor( aStyleSettings.GetWindowColor() );
+ hTheme = OpenThemeData(mhWnd, L"Toolbar");
+ GetThemeColor(hTheme, 0, 0, TMT_FILLCOLOR, &color);
+ aStyleSettings.SetInactiveTabColor( ImplWinColorToSal( color ) );
+ // see ImplDrawNativeControl for dark mode
+ aStyleSettings.SetMenuBarColor( aStyleSettings.GetWindowColor() );
+ CloseThemeData(hTheme);
+
+ hTheme = OpenThemeData(mhWnd, L"Textstyle");
+ if (hTheme)
+ {
+ GetThemeColor(hTheme, TEXT_HYPERLINKTEXT, TS_HYPERLINK_NORMAL, TMT_TEXTCOLOR, &color);
+ aStyleSettings.SetLinkColor(ImplWinColorToSal(color));
+ CloseThemeData(hTheme);
+ }
+
+ // tdf#148448 pick a warning color more likely to be readable as a
+ // background in a dark theme
+ aStyleSettings.SetWarningColor(Color(0xf5, 0x79, 0x00));
+ }
+ else
+ {
+ aStyleSettings.SetFaceColor( ImplWinColorToSal( GetSysColor( COLOR_3DFACE ) ) );
+ aStyleSettings.SetWindowColor( ImplWinColorToSal( GetSysColor( COLOR_WINDOW ) ) );
+ aStyleSettings.SetWindowTextColor( ImplWinColorToSal( GetSysColor( COLOR_WINDOWTEXT ) ) );
+ aStyleSettings.SetToolTextColor( ImplWinColorToSal( GetSysColor( COLOR_WINDOWTEXT ) ) );
+ aStyleSettings.SetLightColor( ImplWinColorToSal( GetSysColor( COLOR_3DHILIGHT ) ) );
+ aStyleSettings.SetShadowColor( ImplWinColorToSal( GetSysColor( COLOR_3DSHADOW ) ) );
+ aStyleSettings.SetDarkShadowColor( ImplWinColorToSal( GetSysColor( COLOR_3DDKSHADOW ) ) );
+ aControlTextColor = ImplWinColorToSal(GetSysColor(COLOR_BTNTEXT));
+ aStyleSettings.SetRadioCheckTextColor( ImplWinColorToSal( GetSysColor( COLOR_WINDOWTEXT ) ) );
+ aStyleSettings.SetMenuTextColor( ImplWinColorToSal( GetSysColor( COLOR_MENUTEXT ) ) );
+ aMenuBarTextColor = ImplWinColorToSal( GetSysColor( COLOR_MENUTEXT ) );
+ aMenuBarRolloverTextColor = aHighlightTextColor;
+ if( bFlatMenus )
+ aStyleSettings.SetMenuBarColor( ImplWinColorToSal( GetSysColor( COLOR_MENUBAR ) ) );
+ else
+ aStyleSettings.SetMenuBarColor( ImplWinColorToSal( GetSysColor( COLOR_MENU ) ) );
+ aStyleSettings.SetActiveTabColor( aStyleSettings.GetWindowColor() );
+ aStyleSettings.SetInactiveTabColor( aStyleSettings.GetFaceColor() );
+ }
+
+ if ( std::optional<Color> aColor = aStyleSettings.GetPersonaMenuBarTextColor() )
+ {
+ aMenuBarTextColor = *aColor;
+ if (!aStyleSettings.GetHighContrastMode())
+ aMenuBarRolloverTextColor = *aColor;
+ }
+
+ aStyleSettings.SetMenuBarTextColor( aMenuBarTextColor );
+ aStyleSettings.SetMenuBarRolloverTextColor( aMenuBarRolloverTextColor );
+
+ aStyleSettings.SetLightBorderColor( ImplWinColorToSal( GetSysColor( COLOR_3DLIGHT ) ) );
+ aStyleSettings.SetHelpColor( ImplWinColorToSal( GetSysColor( COLOR_INFOBK ) ) );
+ aStyleSettings.SetHelpTextColor( ImplWinColorToSal( GetSysColor( COLOR_INFOTEXT ) ) );
+
+ aStyleSettings.SetWorkspaceColor(aStyleSettings.GetFaceColor());
+ aStyleSettings.SetDialogColor(aStyleSettings.GetFaceColor());
+ aStyleSettings.SetDialogTextColor(aControlTextColor);
+
+ Color aHighlightButtonTextColor = aStyleSettings.GetHighContrastMode() ?
+ aHighlightTextColor : aControlTextColor;
+
+ if (aStyleSettings.GetHighContrastMode())
+ {
+ Color aLinkColor(ImplWinColorToSal(GetSysColor(COLOR_HOTLIGHT)));
+ aStyleSettings.SetLinkColor(aLinkColor);
+ aStyleSettings.SetVisitedLinkColor(aLinkColor);
+ }
+
+ aStyleSettings.SetDefaultButtonTextColor(aHighlightButtonTextColor);
+ aStyleSettings.SetButtonTextColor(aControlTextColor);
+ aStyleSettings.SetDefaultActionButtonTextColor(aHighlightButtonTextColor);
+ aStyleSettings.SetActionButtonTextColor(aControlTextColor);
+ aStyleSettings.SetFlatButtonTextColor(aControlTextColor);
+ aStyleSettings.SetDefaultButtonRolloverTextColor(aHighlightButtonTextColor);
+ aStyleSettings.SetButtonRolloverTextColor(aHighlightButtonTextColor);
+ aStyleSettings.SetDefaultActionButtonRolloverTextColor(aHighlightButtonTextColor);
+ aStyleSettings.SetActionButtonRolloverTextColor(aHighlightButtonTextColor);
+ aStyleSettings.SetFlatButtonRolloverTextColor(aHighlightButtonTextColor);
+ aStyleSettings.SetDefaultButtonPressedRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetButtonPressedRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetDefaultActionButtonPressedRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetActionButtonPressedRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetFlatButtonPressedRolloverTextColor(aControlTextColor);
+
+ aStyleSettings.SetTabTextColor(aControlTextColor);
+ aStyleSettings.SetTabRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetTabHighlightTextColor(aControlTextColor);
+
+ aStyleSettings.SetGroupTextColor( aStyleSettings.GetRadioCheckTextColor() );
+ aStyleSettings.SetLabelTextColor( aStyleSettings.GetRadioCheckTextColor() );
+ aStyleSettings.SetFieldColor( aStyleSettings.GetWindowColor() );
+ aStyleSettings.SetListBoxWindowBackgroundColor( aStyleSettings.GetWindowColor() );
+ aStyleSettings.SetFieldTextColor( aStyleSettings.GetWindowTextColor() );
+ aStyleSettings.SetFieldRolloverTextColor( aStyleSettings.GetFieldTextColor() );
+ aStyleSettings.SetListBoxWindowTextColor( aStyleSettings.GetFieldTextColor() );
+
+ aStyleSettings.SetAccentColor( ImplWinColorToSal( GetSysColor( COLOR_HIGHLIGHT ) ) );
+ // https://devblogs.microsoft.com/oldnewthing/20170405-00/?p=95905
+
+ aStyleSettings.SetHighlightColor( ImplWinColorToSal( GetSysColor( COLOR_HIGHLIGHT ) ) );
+ aStyleSettings.SetHighlightTextColor(aHighlightTextColor);
+ aStyleSettings.SetListBoxWindowHighlightColor( aStyleSettings.GetHighlightColor() );
+ aStyleSettings.SetListBoxWindowHighlightTextColor( aStyleSettings.GetHighlightTextColor() );
+ aStyleSettings.SetMenuHighlightTextColor( aStyleSettings.GetHighlightTextColor() );
+
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maNWFData.mnMenuFormatBorderX = 0;
+ pSVData->maNWFData.mnMenuFormatBorderY = 0;
+ pSVData->maNWFData.maMenuBarHighlightTextColor = COL_TRANSPARENT;
+ GetSalData()->mbThemeMenuSupport = false;
+ aStyleSettings.SetMenuColor( ImplWinColorToSal( GetSysColor( COLOR_MENU ) ) );
+ aStyleSettings.SetMenuBarHighlightTextColor(aStyleSettings.GetMenuHighlightTextColor());
+ aStyleSettings.SetActiveColor( ImplWinColorToSal( GetSysColor( COLOR_ACTIVECAPTION ) ) );
+ aStyleSettings.SetActiveTextColor( ImplWinColorToSal( GetSysColor( COLOR_CAPTIONTEXT ) ) );
+ aStyleSettings.SetDeactiveColor( ImplWinColorToSal( GetSysColor( COLOR_INACTIVECAPTION ) ) );
+ aStyleSettings.SetDeactiveTextColor( ImplWinColorToSal( GetSysColor( COLOR_INACTIVECAPTIONTEXT ) ) );
+
+ aStyleSettings.SetCheckedColorSpecialCase( );
+
+ // caret width
+ DWORD nCaretWidth = 2;
+ if( SystemParametersInfoW( SPI_GETCARETWIDTH, 0, &nCaretWidth, 0 ) )
+ aStyleSettings.SetCursorSize( nCaretWidth );
+
+ // Query Fonts
+ vcl::Font aMenuFont = aStyleSettings.GetMenuFont();
+ vcl::Font aTitleFont = aStyleSettings.GetTitleFont();
+ vcl::Font aFloatTitleFont = aStyleSettings.GetFloatTitleFont();
+ vcl::Font aHelpFont = aStyleSettings.GetHelpFont();
+ vcl::Font aAppFont = aStyleSettings.GetAppFont();
+ vcl::Font aIconFont = aStyleSettings.GetIconFont();
+ HDC hDC = GetDC( nullptr );
+ NONCLIENTMETRICSW aNonClientMetrics;
+ aNonClientMetrics.cbSize = sizeof( aNonClientMetrics );
+ if ( SystemParametersInfoW( SPI_GETNONCLIENTMETRICS, sizeof( aNonClientMetrics ), &aNonClientMetrics, 0 ) )
+ {
+ ImplSalUpdateStyleFontW( hDC, aNonClientMetrics.lfMenuFont, aMenuFont );
+ ImplSalUpdateStyleFontW( hDC, aNonClientMetrics.lfCaptionFont, aTitleFont );
+ ImplSalUpdateStyleFontW( hDC, aNonClientMetrics.lfSmCaptionFont, aFloatTitleFont );
+ ImplSalUpdateStyleFontW( hDC, aNonClientMetrics.lfStatusFont, aHelpFont );
+ ImplSalUpdateStyleFontW( hDC, aNonClientMetrics.lfMessageFont, aAppFont );
+
+ LOGFONTW aLogFont;
+ if ( SystemParametersInfoW( SPI_GETICONTITLELOGFONT, 0, &aLogFont, 0 ) )
+ ImplSalUpdateStyleFontW( hDC, aLogFont, aIconFont );
+ }
+
+ ReleaseDC( nullptr, hDC );
+
+ aStyleSettings.SetToolbarIconSize(ToolbarIconSize::Large);
+
+ aStyleSettings.BatchSetFonts( aAppFont, aAppFont );
+
+ aStyleSettings.SetMenuFont( aMenuFont );
+ aStyleSettings.SetTitleFont( aTitleFont );
+ aStyleSettings.SetFloatTitleFont( aFloatTitleFont );
+ aStyleSettings.SetHelpFont( aHelpFont );
+ aStyleSettings.SetIconFont( aIconFont );
+
+ if ( aAppFont.GetWeight() > WEIGHT_NORMAL )
+ aAppFont.SetWeight( WEIGHT_NORMAL );
+ aStyleSettings.SetToolFont( aAppFont );
+ aStyleSettings.SetTabFont( aAppFont );
+
+ BOOL bDragFull;
+ if ( SystemParametersInfoW( SPI_GETDRAGFULLWINDOWS, 0, &bDragFull, 0 ) )
+ {
+ DragFullOptions nDragFullOptions = aStyleSettings.GetDragFullOptions();
+ if ( bDragFull )
+ nDragFullOptions |= DragFullOptions::WindowMove | DragFullOptions::WindowSize | DragFullOptions::Docking | DragFullOptions::Split;
+ else
+ nDragFullOptions &= ~DragFullOptions(DragFullOptions::WindowMove | DragFullOptions::WindowSize | DragFullOptions::Docking | DragFullOptions::Split);
+ aStyleSettings.SetDragFullOptions( nDragFullOptions );
+ }
+
+ if ( RegOpenKeyW( HKEY_CURRENT_USER,
+ L"Control Panel\\International\\Calendars\\TwoDigitYearMax",
+ &hRegKey ) == ERROR_SUCCESS )
+ {
+ wchar_t aValueBuf[10];
+ DWORD nValue;
+ DWORD nValueSize = sizeof( aValueBuf );
+ DWORD nType;
+ if ( RegQueryValueExW( hRegKey, L"1", nullptr,
+ &nType, reinterpret_cast<LPBYTE>(aValueBuf), &nValueSize ) == ERROR_SUCCESS )
+ {
+ if ( nType == REG_SZ )
+ {
+ nValue = static_cast<sal_uLong>(ImplW2I( aValueBuf ));
+ if ( (nValue > 1000) && (nValue < 10000) )
+ {
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::DateFormat::TwoDigitYear::set(static_cast<sal_Int32>(nValue-99), batch);
+ batch->commit();
+ }
+ }
+ }
+
+ RegCloseKey( hRegKey );
+ }
+
+ rSettings.SetMouseSettings( aMouseSettings );
+ rSettings.SetStyleSettings( aStyleSettings );
+
+ // now apply the values from theming, if available
+ WinSalGraphics::updateSettingsNative( rSettings );
+}
+
+const SystemEnvData* WinSalFrame::GetSystemData() const
+{
+ return &maSysData;
+}
+
+void WinSalFrame::Beep()
+{
+ // a simple beep
+ MessageBeep( 0 );
+}
+
+SalFrame::SalPointerState WinSalFrame::GetPointerState()
+{
+ SalPointerState aState;
+ aState.mnState = 0;
+
+ if ( GetKeyState( VK_LBUTTON ) & 0x8000 )
+ aState.mnState |= MOUSE_LEFT;
+ if ( GetKeyState( VK_MBUTTON ) & 0x8000 )
+ aState.mnState |= MOUSE_MIDDLE;
+ if ( GetKeyState( VK_RBUTTON ) & 0x8000 )
+ aState.mnState |= MOUSE_RIGHT;
+ if ( GetKeyState( VK_SHIFT ) & 0x8000 )
+ aState.mnState |= KEY_SHIFT;
+ if ( GetKeyState( VK_CONTROL ) & 0x8000 )
+ aState.mnState |= KEY_MOD1;
+ if ( GetKeyState( VK_MENU ) & 0x8000 )
+ aState.mnState |= KEY_MOD2;
+
+ POINT pt;
+ GetCursorPos( &pt );
+
+ aState.maPos = Point(pt.x - maGeometry.x(), pt.y - maGeometry.y());
+ return aState;
+}
+
+KeyIndicatorState WinSalFrame::GetIndicatorState()
+{
+ KeyIndicatorState aState = KeyIndicatorState::NONE;
+ if (::GetKeyState(VK_CAPITAL))
+ aState |= KeyIndicatorState::CAPSLOCK;
+
+ if (::GetKeyState(VK_NUMLOCK))
+ aState |= KeyIndicatorState::NUMLOCK;
+
+ if (::GetKeyState(VK_SCROLL))
+ aState |= KeyIndicatorState::SCROLLLOCK;
+
+ return aState;
+}
+
+void WinSalFrame::SimulateKeyPress( sal_uInt16 nKeyCode )
+{
+ BYTE nVKey = 0;
+ switch (nKeyCode)
+ {
+ case KEY_CAPSLOCK:
+ nVKey = VK_CAPITAL;
+ break;
+ }
+
+ if (nVKey > 0 && nVKey < 255)
+ {
+ ::keybd_event(nVKey, 0x45, KEYEVENTF_EXTENDEDKEY, 0);
+ ::keybd_event(nVKey, 0x45, KEYEVENTF_EXTENDEDKEY|KEYEVENTF_KEYUP, 0);
+ }
+}
+
+void WinSalFrame::ResetClipRegion()
+{
+ SetWindowRgn( mhWnd, nullptr, TRUE );
+}
+
+void WinSalFrame::BeginSetClipRegion( sal_uInt32 nRects )
+{
+ if( mpClipRgnData )
+ delete [] reinterpret_cast<BYTE*>(mpClipRgnData);
+ sal_uLong nRectBufSize = sizeof(RECT)*nRects;
+ mpClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+nRectBufSize]);
+ mpClipRgnData->rdh.dwSize = sizeof( RGNDATAHEADER );
+ mpClipRgnData->rdh.iType = RDH_RECTANGLES;
+ mpClipRgnData->rdh.nCount = nRects;
+ mpClipRgnData->rdh.nRgnSize = nRectBufSize;
+ SetRectEmpty( &(mpClipRgnData->rdh.rcBound) );
+ mpNextClipRect = reinterpret_cast<RECT*>(&(mpClipRgnData->Buffer));
+ mbFirstClipRect = true;
+}
+
+void WinSalFrame::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ if( ! mpClipRgnData )
+ return;
+
+ RECT* pRect = mpNextClipRect;
+ RECT* pBoundRect = &(mpClipRgnData->rdh.rcBound);
+ tools::Long nRight = nX + nWidth;
+ tools::Long nBottom = nY + nHeight;
+
+ if ( mbFirstClipRect )
+ {
+ pBoundRect->left = nX;
+ pBoundRect->top = nY;
+ pBoundRect->right = nRight;
+ pBoundRect->bottom = nBottom;
+ mbFirstClipRect = false;
+ }
+ else
+ {
+ if ( nX < pBoundRect->left )
+ pBoundRect->left = static_cast<int>(nX);
+
+ if ( nY < pBoundRect->top )
+ pBoundRect->top = static_cast<int>(nY);
+
+ if ( nRight > pBoundRect->right )
+ pBoundRect->right = static_cast<int>(nRight);
+
+ if ( nBottom > pBoundRect->bottom )
+ pBoundRect->bottom = static_cast<int>(nBottom);
+ }
+
+ pRect->left = static_cast<int>(nX);
+ pRect->top = static_cast<int>(nY);
+ pRect->right = static_cast<int>(nRight);
+ pRect->bottom = static_cast<int>(nBottom);
+ if( (mpNextClipRect - reinterpret_cast<RECT*>(&mpClipRgnData->Buffer)) < static_cast<int>(mpClipRgnData->rdh.nCount) )
+ mpNextClipRect++;
+}
+
+void WinSalFrame::EndSetClipRegion()
+{
+ if( ! mpClipRgnData )
+ return;
+
+ HRGN hRegion;
+
+ // create region from accumulated rectangles
+ if ( mpClipRgnData->rdh.nCount == 1 )
+ {
+ RECT* pRect = &(mpClipRgnData->rdh.rcBound);
+ hRegion = CreateRectRgn( pRect->left, pRect->top,
+ pRect->right, pRect->bottom );
+ }
+ else
+ {
+ sal_uLong nSize = mpClipRgnData->rdh.nRgnSize+sizeof(RGNDATAHEADER);
+ hRegion = ExtCreateRegion( nullptr, nSize, mpClipRgnData );
+ }
+ delete [] reinterpret_cast<BYTE*>(mpClipRgnData);
+ mpClipRgnData = nullptr;
+
+ SAL_WARN_IF( !hRegion, "vcl", "WinSalFrame::EndSetClipRegion() - Can't create ClipRegion" );
+ if( hRegion )
+ {
+ RECT aWindowRect;
+ GetWindowRect( mhWnd, &aWindowRect );
+ POINT aPt;
+ aPt.x=0;
+ aPt.y=0;
+ ClientToScreen( mhWnd, &aPt );
+ OffsetRgn( hRegion, aPt.x - aWindowRect.left, aPt.y - aWindowRect.top );
+
+ if( SetWindowRgn( mhWnd, hRegion, TRUE ) == 0 )
+ DeleteObject( hRegion );
+ }
+}
+
+void WinSalFrame::UpdateDarkMode()
+{
+ ::UpdateDarkMode(mhWnd);
+}
+
+bool WinSalFrame::GetUseDarkMode() const
+{
+ return UseDarkMode();
+}
+
+bool WinSalFrame::GetUseReducedAnimation() const
+{
+ BOOL bEnableAnimation = FALSE;
+ SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &bEnableAnimation, 0);
+ return !bEnableAnimation;
+}
+
+static bool ImplHandleMouseMsg( HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam )
+{
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( !pFrame )
+ return false;
+
+ if( nMsg == WM_LBUTTONDOWN || nMsg == WM_MBUTTONDOWN || nMsg == WM_RBUTTONDOWN )
+ {
+ // #103168# post again if async focus has not arrived yet
+ // hopefully we will not receive the corresponding button up before this
+ // button down arrives again
+ vcl::Window *pWin = pFrame->GetWindow();
+ if( pWin && pWin->ImplGetWindowImpl()->mpFrameData->mnFocusId )
+ {
+ bool const ret = PostMessageW( hWnd, nMsg, wParam, lParam );
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+ return true;
+ }
+ }
+ SalMouseEvent aMouseEvt;
+ bool nRet;
+ SalEvent nEvent = SalEvent::NONE;
+ bool bCall = true;
+
+ aMouseEvt.mnX = static_cast<short>(LOWORD( lParam ));
+ aMouseEvt.mnY = static_cast<short>(HIWORD( lParam ));
+ aMouseEvt.mnCode = 0;
+ aMouseEvt.mnTime = GetMessageTime();
+
+ // Use GetKeyState(), as some Logitech mouse drivers do not check
+ // KeyState when simulating double-click with center mouse button
+
+ if ( GetKeyState( VK_LBUTTON ) & 0x8000 )
+ aMouseEvt.mnCode |= MOUSE_LEFT;
+ if ( GetKeyState( VK_MBUTTON ) & 0x8000 )
+ aMouseEvt.mnCode |= MOUSE_MIDDLE;
+ if ( GetKeyState( VK_RBUTTON ) & 0x8000 )
+ aMouseEvt.mnCode |= MOUSE_RIGHT;
+ if ( GetKeyState( VK_SHIFT ) & 0x8000 )
+ aMouseEvt.mnCode |= KEY_SHIFT;
+ if ( GetKeyState( VK_CONTROL ) & 0x8000 )
+ aMouseEvt.mnCode |= KEY_MOD1;
+ if ( GetKeyState( VK_MENU ) & 0x8000 )
+ aMouseEvt.mnCode |= KEY_MOD2;
+
+ switch ( nMsg )
+ {
+ case WM_MOUSEMOVE:
+ {
+ // As the mouse events are not collected correctly when
+ // pressing modifier keys (as interrupted by KeyEvents)
+ // we do this here ourselves
+ if ( aMouseEvt.mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2) )
+ {
+ MSG aTempMsg;
+ if ( PeekMessageW( &aTempMsg, hWnd, WM_MOUSEFIRST, WM_MOUSELAST, PM_NOREMOVE | PM_NOYIELD ) )
+ {
+ if ( (aTempMsg.message == WM_MOUSEMOVE) &&
+ (aTempMsg.wParam == wParam) )
+ return true;
+ }
+ }
+
+ SalData* pSalData = GetSalData();
+ // Test for MouseLeave
+ if ( pSalData->mhWantLeaveMsg && (pSalData->mhWantLeaveMsg != hWnd) )
+ SendMessageW( pSalData->mhWantLeaveMsg, SAL_MSG_MOUSELEAVE, 0, GetMessagePos() );
+
+ pSalData->mhWantLeaveMsg = hWnd;
+ aMouseEvt.mnButton = 0;
+ nEvent = SalEvent::MouseMove;
+ }
+ break;
+
+ case WM_NCMOUSEMOVE:
+ case SAL_MSG_MOUSELEAVE:
+ {
+ SalData* pSalData = GetSalData();
+ if ( pSalData->mhWantLeaveMsg == hWnd )
+ {
+ // Mouse-Coordinates are relative to the screen
+ POINT aPt;
+ aPt.x = static_cast<short>(LOWORD(lParam));
+ aPt.y = static_cast<short>(HIWORD(lParam));
+ ScreenToClient(hWnd, &aPt);
+ if (const auto& pHelpWin = ImplGetSVHelpData().mpHelpWin)
+ {
+ const tools::Rectangle& rHelpRect = pHelpWin->GetHelpArea();
+ if (rHelpRect.Contains(Point(aPt.x, aPt.y)))
+ {
+ // We have entered a tooltip (help window). Don't call the handler here; it
+ // would launch the sequence "Mouse leaves the Control->Control redraws->
+ // Help window gets destroyed->Mouse enters the Control->Control redraws",
+ // which takes CPU and may flicker. Just destroy the help window and pretend
+ // we are still over the original window.
+ ImplDestroyHelpWindow(true);
+ bCall = false;
+ break;
+ }
+ }
+ pSalData->mhWantLeaveMsg = nullptr;
+ aMouseEvt.mnX = aPt.x;
+ aMouseEvt.mnY = aPt.y;
+ aMouseEvt.mnButton = 0;
+ nEvent = SalEvent::MouseLeave;
+ }
+ else
+ bCall = false;
+ }
+ break;
+
+ case WM_LBUTTONDOWN:
+ aMouseEvt.mnButton = MOUSE_LEFT;
+ nEvent = SalEvent::MouseButtonDown;
+ break;
+
+ case WM_MBUTTONDOWN:
+ aMouseEvt.mnButton = MOUSE_MIDDLE;
+ nEvent = SalEvent::MouseButtonDown;
+ break;
+
+ case WM_RBUTTONDOWN:
+ aMouseEvt.mnButton = MOUSE_RIGHT;
+ nEvent = SalEvent::MouseButtonDown;
+ break;
+
+ case WM_LBUTTONUP:
+ aMouseEvt.mnButton = MOUSE_LEFT;
+ nEvent = SalEvent::MouseButtonUp;
+ break;
+
+ case WM_MBUTTONUP:
+ aMouseEvt.mnButton = MOUSE_MIDDLE;
+ nEvent = SalEvent::MouseButtonUp;
+ break;
+
+ case WM_RBUTTONUP:
+ aMouseEvt.mnButton = MOUSE_RIGHT;
+ nEvent = SalEvent::MouseButtonUp;
+ break;
+ }
+
+ // check if this window was destroyed - this might happen if we are the help window
+ // and sent a mouse leave message to the application which killed the help window, ie ourselves
+ if( !IsWindow( hWnd ) )
+ return false;
+
+ if ( bCall )
+ {
+ if ( nEvent == SalEvent::MouseButtonDown )
+ UpdateWindow( hWnd );
+
+ if( AllSettings::GetLayoutRTL() )
+ aMouseEvt.mnX = pFrame->maGeometry.width() - 1 - aMouseEvt.mnX;
+
+ nRet = pFrame->CallCallback( nEvent, &aMouseEvt );
+ if ( nMsg == WM_MOUSEMOVE )
+ SetCursor( pFrame->mhCursor );
+ }
+ else
+ nRet = false;
+
+ return nRet;
+}
+
+static bool ImplHandleMouseActivateMsg( HWND hWnd )
+{
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( !pFrame )
+ return false;
+
+ if ( pFrame->mbFloatWin )
+ return true;
+
+ return pFrame->CallCallback( SalEvent::MouseActivate, nullptr );
+}
+
+static bool ImplHandleWheelMsg( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam )
+{
+ DBG_ASSERT( nMsg == WM_MOUSEWHEEL ||
+ nMsg == WM_MOUSEHWHEEL,
+ "ImplHandleWheelMsg() called with no wheel mouse event" );
+
+ ImplSalYieldMutexAcquireWithWait();
+
+ bool nRet = false;
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( pFrame )
+ {
+ WORD nWinModCode = LOWORD( wParam );
+ POINT aWinPt;
+ aWinPt.x = static_cast<short>(LOWORD( lParam ));
+ aWinPt.y = static_cast<short>(HIWORD( lParam ));
+ ScreenToClient( hWnd, &aWinPt );
+
+ SalWheelMouseEvent aWheelEvt;
+ aWheelEvt.mnTime = GetMessageTime();
+ aWheelEvt.mnX = aWinPt.x;
+ aWheelEvt.mnY = aWinPt.y;
+ aWheelEvt.mnCode = 0;
+ aWheelEvt.mnDelta = static_cast<short>(HIWORD( wParam ));
+ aWheelEvt.mnNotchDelta = aWheelEvt.mnDelta/WHEEL_DELTA;
+ if( aWheelEvt.mnNotchDelta == 0 )
+ {
+ if( aWheelEvt.mnDelta > 0 )
+ aWheelEvt.mnNotchDelta = 1;
+ else if( aWheelEvt.mnDelta < 0 )
+ aWheelEvt.mnNotchDelta = -1;
+ }
+
+ if( nMsg == WM_MOUSEWHEEL )
+ {
+ if ( aSalShlData.mnWheelScrollLines == WHEEL_PAGESCROLL )
+ aWheelEvt.mnScrollLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL;
+ else
+ aWheelEvt.mnScrollLines = aSalShlData.mnWheelScrollLines;
+ aWheelEvt.mbHorz = false;
+ }
+ else
+ {
+ aWheelEvt.mnScrollLines = aSalShlData.mnWheelScrollChars;
+ aWheelEvt.mbHorz = true;
+
+ // fdo#36380 - seems horiz scrolling has swapped direction
+ aWheelEvt.mnDelta *= -1;
+ aWheelEvt.mnNotchDelta *= -1;
+ }
+
+ if ( nWinModCode & MK_SHIFT )
+ aWheelEvt.mnCode |= KEY_SHIFT;
+ if ( nWinModCode & MK_CONTROL )
+ aWheelEvt.mnCode |= KEY_MOD1;
+ if ( GetKeyState( VK_MENU ) & 0x8000 )
+ aWheelEvt.mnCode |= KEY_MOD2;
+
+ if( AllSettings::GetLayoutRTL() )
+ aWheelEvt.mnX = pFrame->maGeometry.width() - 1 - aWheelEvt.mnX;
+
+ nRet = pFrame->CallCallback( SalEvent::WheelMouse, &aWheelEvt );
+ }
+
+ ImplSalYieldMutexRelease();
+
+ return nRet;
+}
+
+static sal_uInt16 ImplSalGetKeyCode( WPARAM wParam )
+{
+ sal_uInt16 nKeyCode;
+
+ // convert KeyCode
+ if ( wParam < KEY_TAB_SIZE )
+ nKeyCode = aImplTranslateKeyTab[wParam];
+ else
+ {
+ SalData* pSalData = GetSalData();
+ std::map< UINT, sal_uInt16 >::const_iterator it = pSalData->maVKMap.find( static_cast<UINT>(wParam) );
+ if( it != pSalData->maVKMap.end() )
+ nKeyCode = it->second;
+ else
+ nKeyCode = 0;
+ }
+
+ return nKeyCode;
+}
+
+static void ImplUpdateInputLang( WinSalFrame* pFrame )
+{
+ UINT nLang = LOWORD( GetKeyboardLayout( 0 ) );
+ if ( nLang && nLang != pFrame->mnInputLang )
+ {
+ // keep input lang up-to-date
+ pFrame->mnInputLang = nLang;
+ }
+
+ // We are on Windows NT so we use Unicode FrameProcs and get
+ // Unicode charcodes directly from Windows no need to set up a
+ // code page
+ return;
+}
+
+static sal_Unicode ImplGetCharCode( WinSalFrame* pFrame, WPARAM nCharCode )
+{
+ ImplUpdateInputLang( pFrame );
+
+ // We are on Windows NT so we use Unicode FrameProcs and we
+ // get Unicode charcodes directly from Windows
+ return static_cast<sal_Unicode>(nCharCode);
+}
+
+LanguageType WinSalFrame::GetInputLanguage()
+{
+ if( !mnInputLang )
+ ImplUpdateInputLang( this );
+
+ if( !mnInputLang )
+ return LANGUAGE_DONTKNOW;
+ else
+ return LanguageType(mnInputLang);
+}
+
+bool WinSalFrame::MapUnicodeToKeyCode( sal_Unicode aUnicode, LanguageType aLangType, vcl::KeyCode& rKeyCode )
+{
+ bool bRet = false;
+ sal_IntPtr nLangType = static_cast<sal_uInt16>(aLangType);
+ // just use the passed language identifier, do not try to load additional keyboard support
+ HKL hkl = reinterpret_cast<HKL>(nLangType);
+
+ if( hkl )
+ {
+ SHORT scan = VkKeyScanExW( aUnicode, hkl );
+ if( LOWORD(scan) == 0xFFFF )
+ // keyboard not loaded or key cannot be mapped
+ bRet = false;
+ else
+ {
+ BYTE vkeycode = LOBYTE(scan);
+ BYTE shiftstate = HIBYTE(scan);
+
+ // Last argument is set to false, because there's no decision made
+ // yet which key should be assigned to MOD3 modifier on Windows.
+ // Windows key - user's can be confused, because it should display
+ // Windows menu (applies to both left/right key)
+ // Menu key - this key is used to display context menu
+ // AltGr key - probably it has no sense
+ rKeyCode = vcl::KeyCode( ImplSalGetKeyCode( vkeycode ),
+ (shiftstate & 0x01) != 0, // shift
+ (shiftstate & 0x02) != 0, // ctrl
+ (shiftstate & 0x04) != 0, // alt
+ false );
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+static void UnsetAltIfAltGr(SalKeyEvent& rKeyEvt, sal_uInt16 nModCode)
+{
+ if ((nModCode & (KEY_MOD1 | KEY_MOD2)) == (KEY_MOD1 | KEY_MOD2) &&
+ rKeyEvt.mnCharCode)
+ {
+ // this is actually AltGr and should not be handled as Alt
+ rKeyEvt.mnCode &= ~(KEY_MOD1 | KEY_MOD2);
+ }
+}
+
+// tdf#152404 Commit uncommitted text before dispatching key shortcuts. In
+// certain cases such as pressing Control-Alt-C in a Writer document while
+// there is uncommitted text will call WinSalFrame::EndExtTextInput() which
+// will dispatch a SalEvent::EndExtTextInput event. Writer's handler for that
+// event will delete the uncommitted text and then insert the committed text
+// but LibreOffice will crash when deleting the uncommitted text because
+// deletion of the text also removes and deletes the newly inserted comment.
+static void FlushIMBeforeShortCut(WinSalFrame* pFrame, SalEvent nEvent, sal_uInt16 nModCode)
+{
+ if (pFrame->mbCandidateMode && nEvent == SalEvent::KeyInput
+ && (nModCode & (KEY_MOD1 | KEY_MOD2)))
+ {
+ pFrame->EndExtTextInput(EndExtTextInputFlags::Complete);
+ }
+}
+
+static bool ImplHandleKeyMsg( HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam, LRESULT& rResult )
+{
+ static bool bIgnoreCharMsg = false;
+ static WPARAM nDeadChar = 0;
+ static WPARAM nLastVKChar = 0;
+ static sal_uInt16 nLastChar = 0;
+ static ModKeyFlags nLastModKeyCode = ModKeyFlags::NONE;
+ static bool bWaitForModKeyRelease = false;
+ sal_uInt16 nRepeat = LOWORD( lParam )-1;
+ sal_uInt16 nModCode = 0;
+
+ // this key might have been relayed by SysChild and thus
+ // may not be processed twice
+ GetSalData()->mnSalObjWantKeyEvt = 0;
+
+ if ( nMsg == WM_DEADCHAR )
+ {
+ nDeadChar = wParam;
+ return false;
+ }
+
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( !pFrame )
+ return false;
+
+ // reset the background mode for each text input,
+ // as some tools such as RichWin may have changed it
+ if ( pFrame->mpLocalGraphics &&
+ pFrame->mpLocalGraphics->getHDC() )
+ SetBkMode( pFrame->mpLocalGraphics->getHDC(), TRANSPARENT );
+
+ // determine modifiers
+ if ( GetKeyState( VK_SHIFT ) & 0x8000 )
+ nModCode |= KEY_SHIFT;
+ if ( GetKeyState( VK_CONTROL ) & 0x8000 )
+ nModCode |= KEY_MOD1;
+ if (GetKeyState(VK_MENU) & 0x8000)
+ nModCode |= KEY_MOD2;
+
+ if ( (nMsg == WM_CHAR) || (nMsg == WM_SYSCHAR) )
+ {
+ nDeadChar = 0;
+
+ if ( bIgnoreCharMsg )
+ {
+ bIgnoreCharMsg = false;
+ // #101635# if zero is returned here for WM_SYSCHAR (ALT+<key>) Windows will beep
+ // because this 'hotkey' was not processed -> better return 1
+ // except for Alt-SPACE which should always open the sysmenu (#104616#)
+
+ // also return zero if a system menubar is available that might process this hotkey
+ // this also applies to the OLE inplace embedding where we are a child window
+ if( (GetWindowStyle( hWnd ) & WS_CHILD) || GetMenu( hWnd ) || (wParam == 0x20) )
+ return false;
+ else
+ return true;
+ }
+
+ // ignore backspace as a single key, so that
+ // we do not get problems for combinations w/ a DeadKey
+ if ( wParam == 0x08 ) // BACKSPACE
+ return false;
+
+ // only "free flying" WM_CHAR messages arrive here, that are
+ // created by typing an ALT-NUMPAD combination
+ SalKeyEvent aKeyEvt;
+
+ if ( (wParam >= '0') && (wParam <= '9') )
+ aKeyEvt.mnCode = sal::static_int_cast<sal_uInt16>(KEYGROUP_NUM + wParam - '0');
+ else if ( (wParam >= 'A') && (wParam <= 'Z') )
+ aKeyEvt.mnCode = sal::static_int_cast<sal_uInt16>(KEYGROUP_ALPHA + wParam - 'A');
+ else if ( (wParam >= 'a') && (wParam <= 'z') )
+ aKeyEvt.mnCode = sal::static_int_cast<sal_uInt16>(KEYGROUP_ALPHA + wParam - 'a');
+ else if ( wParam == 0x0D ) // RETURN
+ aKeyEvt.mnCode = KEY_RETURN;
+ else if ( wParam == 0x1B ) // ESCAPE
+ aKeyEvt.mnCode = KEY_ESCAPE;
+ else if ( wParam == 0x09 ) // TAB
+ aKeyEvt.mnCode = KEY_TAB;
+ else if ( wParam == 0x20 ) // SPACE
+ aKeyEvt.mnCode = KEY_SPACE;
+ else
+ aKeyEvt.mnCode = 0;
+
+ aKeyEvt.mnCode |= nModCode;
+ aKeyEvt.mnCharCode = ImplGetCharCode( pFrame, wParam );
+ aKeyEvt.mnRepeat = nRepeat;
+
+ UnsetAltIfAltGr(aKeyEvt, nModCode);
+ FlushIMBeforeShortCut(pFrame, SalEvent::KeyInput, nModCode);
+
+ nLastChar = 0;
+ nLastVKChar = 0;
+
+ bool nRet = pFrame->CallCallback( SalEvent::KeyInput, &aKeyEvt );
+ pFrame->CallCallback( SalEvent::KeyUp, &aKeyEvt );
+ return nRet;
+ }
+ // #i11583#, MCD, 2003-01-13, Support for WM_UNICHAR & Keyman 6.0; addition begins
+ else if( nMsg == WM_UNICHAR )
+ {
+ // If Windows is asking if we accept WM_UNICHAR, return TRUE
+ if(wParam == UNICODE_NOCHAR)
+ {
+ rResult = TRUE; // ssa: this will actually return TRUE to windows
+ return true; // ...but this will only avoid calling the defwindowproc
+ }
+
+ if (!rtl::isUnicodeCodePoint(wParam))
+ return false;
+
+ SalKeyEvent aKeyEvt;
+ aKeyEvt.mnCode = nModCode; // Or should it be 0? - as this is always a character returned
+ aKeyEvt.mnRepeat = 0;
+
+ if( wParam >= Uni_SupplementaryPlanesStart )
+ {
+ // character is supplementary char in UTF-32 format - must be converted to UTF-16 supplementary pair
+ aKeyEvt.mnCharCode = rtl::getHighSurrogate(wParam);
+ nLastChar = 0;
+ nLastVKChar = 0;
+ pFrame->CallCallback(SalEvent::KeyInput, &aKeyEvt);
+ pFrame->CallCallback(SalEvent::KeyUp, &aKeyEvt);
+ wParam = rtl::getLowSurrogate(wParam);
+ }
+
+ aKeyEvt.mnCharCode = static_cast<sal_Unicode>(wParam);
+
+ nLastChar = 0;
+ nLastVKChar = 0;
+ bool nRet = pFrame->CallCallback( SalEvent::KeyInput, &aKeyEvt );
+ pFrame->CallCallback( SalEvent::KeyUp, &aKeyEvt );
+
+ return nRet;
+ }
+ // MCD, 2003-01-13, Support for WM_UNICHAR & Keyman 6.0; addition ends
+ else
+ {
+ // for shift, control and menu we issue a KeyModChange event
+ if ( (wParam == VK_SHIFT) || (wParam == VK_CONTROL) || (wParam == VK_MENU) )
+ {
+ SalKeyModEvent aModEvt;
+ aModEvt.mbDown = false; // auto-accelerator feature not supported here.
+ aModEvt.mnCode = nModCode;
+ aModEvt.mnModKeyCode = ModKeyFlags::NONE; // no command events will be sent if this member is 0
+
+ ModKeyFlags tmpCode = ModKeyFlags::NONE;
+ if( GetKeyState( VK_LSHIFT ) & 0x8000 )
+ tmpCode |= ModKeyFlags::LeftShift;
+ if( GetKeyState( VK_RSHIFT ) & 0x8000 )
+ tmpCode |= ModKeyFlags::RightShift;
+ if( GetKeyState( VK_LCONTROL ) & 0x8000 )
+ tmpCode |= ModKeyFlags::LeftMod1;
+ if( GetKeyState( VK_RCONTROL ) & 0x8000 )
+ tmpCode |= ModKeyFlags::RightMod1;
+ if( GetKeyState( VK_LMENU ) & 0x8000 )
+ tmpCode |= ModKeyFlags::LeftMod2;
+ if( GetKeyState( VK_RMENU ) & 0x8000 )
+ tmpCode |= ModKeyFlags::RightMod2;
+
+ if( tmpCode < nLastModKeyCode )
+ {
+ aModEvt.mnModKeyCode = nLastModKeyCode;
+ nLastModKeyCode = ModKeyFlags::NONE;
+ bWaitForModKeyRelease = true;
+ }
+ else
+ {
+ if( !bWaitForModKeyRelease )
+ nLastModKeyCode = tmpCode;
+ }
+
+ if( tmpCode == ModKeyFlags::NONE )
+ bWaitForModKeyRelease = false;
+
+ return pFrame->CallCallback( SalEvent::KeyModChange, &aModEvt );
+ }
+ else
+ {
+ SalKeyEvent aKeyEvt;
+ SalEvent nEvent;
+ MSG aCharMsg;
+ bool bCharPeek = false;
+ UINT nCharMsg = WM_CHAR;
+ bool bKeyUp = (nMsg == WM_KEYUP) || (nMsg == WM_SYSKEYUP);
+
+ nLastModKeyCode = ModKeyFlags::NONE; // make sure no modkey messages are sent if they belong to a hotkey (see above)
+ aKeyEvt.mnCharCode = 0;
+ aKeyEvt.mnCode = ImplSalGetKeyCode( wParam );
+ if ( !bKeyUp )
+ {
+ // check for charcode
+ // Get the related WM_CHAR message using PeekMessage, if available.
+ // The WM_CHAR message is always at the beginning of the
+ // message queue. Also it is made certain that there is always only
+ // one WM_CHAR message in the queue.
+ bCharPeek = PeekMessageW( &aCharMsg, hWnd,
+ WM_CHAR, WM_CHAR, PM_NOREMOVE | PM_NOYIELD );
+ if ( bCharPeek && (nDeadChar == aCharMsg.wParam) )
+ {
+ bCharPeek = false;
+ nDeadChar = 0;
+
+ if ( wParam == VK_BACK )
+ {
+ PeekMessageW( &aCharMsg, hWnd,
+ nCharMsg, nCharMsg, PM_REMOVE | PM_NOYIELD );
+ return false;
+ }
+ }
+ else
+ {
+ if ( !bCharPeek )
+ {
+ bCharPeek = PeekMessageW( &aCharMsg, hWnd,
+ WM_SYSCHAR, WM_SYSCHAR, PM_NOREMOVE | PM_NOYIELD );
+ nCharMsg = WM_SYSCHAR;
+ }
+ }
+ if ( bCharPeek )
+ aKeyEvt.mnCharCode = ImplGetCharCode( pFrame, aCharMsg.wParam );
+ else
+ aKeyEvt.mnCharCode = 0;
+
+ nLastChar = aKeyEvt.mnCharCode;
+ nLastVKChar = wParam;
+ }
+ else
+ {
+ if ( wParam == nLastVKChar )
+ {
+ aKeyEvt.mnCharCode = nLastChar;
+ nLastChar = 0;
+ nLastVKChar = 0;
+ }
+ }
+
+ if ( aKeyEvt.mnCode || aKeyEvt.mnCharCode )
+ {
+ if ( bKeyUp )
+ nEvent = SalEvent::KeyUp;
+ else
+ nEvent = SalEvent::KeyInput;
+
+ aKeyEvt.mnCode |= nModCode;
+ aKeyEvt.mnRepeat = nRepeat;
+
+ UnsetAltIfAltGr(aKeyEvt, nModCode);
+ FlushIMBeforeShortCut(pFrame, nEvent, nModCode);
+
+ bIgnoreCharMsg = bCharPeek;
+ bool nRet = pFrame->CallCallback( nEvent, &aKeyEvt );
+ // independent part only reacts on keyup but Windows does not send
+ // keyup for VK_HANJA
+ if( aKeyEvt.mnCode == KEY_HANGUL_HANJA )
+ nRet = pFrame->CallCallback( SalEvent::KeyUp, &aKeyEvt );
+
+ bIgnoreCharMsg = false;
+
+ // char-message, then remove or ignore
+ if ( bCharPeek )
+ {
+ nDeadChar = 0;
+ if ( nRet )
+ {
+ PeekMessageW( &aCharMsg, hWnd,
+ nCharMsg, nCharMsg, PM_REMOVE | PM_NOYIELD );
+ }
+ else
+ bIgnoreCharMsg = true;
+ }
+
+ return nRet;
+ }
+ else
+ return false;
+ }
+ }
+}
+
+bool ImplHandleSalObjKeyMsg( HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam )
+{
+ if ( (nMsg == WM_KEYDOWN) || (nMsg == WM_KEYUP) )
+ {
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( !pFrame )
+ return false;
+
+ sal_uInt16 nRepeat = LOWORD( lParam )-1;
+ sal_uInt16 nModCode = 0;
+
+ // determine modifiers
+ if ( GetKeyState( VK_SHIFT ) & 0x8000 )
+ nModCode |= KEY_SHIFT;
+ if ( GetKeyState( VK_CONTROL ) & 0x8000 )
+ nModCode |= KEY_MOD1;
+ if ( GetKeyState( VK_MENU ) & 0x8000 )
+ nModCode |= KEY_MOD2;
+
+ if ( (wParam != VK_SHIFT) && (wParam != VK_CONTROL) && (wParam != VK_MENU) )
+ {
+ SalKeyEvent aKeyEvt;
+ SalEvent nEvent;
+
+ // convert KeyCode
+ aKeyEvt.mnCode = ImplSalGetKeyCode( wParam );
+ aKeyEvt.mnCharCode = 0;
+
+ if ( aKeyEvt.mnCode )
+ {
+ if (nMsg == WM_KEYUP)
+ nEvent = SalEvent::KeyUp;
+ else
+ nEvent = SalEvent::KeyInput;
+
+ aKeyEvt.mnCode |= nModCode;
+ aKeyEvt.mnRepeat = nRepeat;
+ bool nRet = pFrame->CallCallback( nEvent, &aKeyEvt );
+ return nRet;
+ }
+ else
+ return false;
+ }
+ }
+
+ return false;
+}
+
+bool ImplHandleSalObjSysCharMsg( HWND hWnd, WPARAM wParam, LPARAM lParam )
+{
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( !pFrame )
+ return false;
+
+ sal_uInt16 nRepeat = LOWORD( lParam )-1;
+ sal_uInt16 nModCode = 0;
+ sal_uInt16 cKeyCode = static_cast<sal_uInt16>(wParam);
+
+ // determine modifiers
+ if ( GetKeyState( VK_SHIFT ) & 0x8000 )
+ nModCode |= KEY_SHIFT;
+ if ( GetKeyState( VK_CONTROL ) & 0x8000 )
+ nModCode |= KEY_MOD1;
+ nModCode |= KEY_MOD2;
+
+ // assemble KeyEvent
+ SalKeyEvent aKeyEvt;
+ if ( (cKeyCode >= 48) && (cKeyCode <= 57) )
+ aKeyEvt.mnCode = KEY_0+(cKeyCode-48);
+ else if ( (cKeyCode >= 65) && (cKeyCode <= 90) )
+ aKeyEvt.mnCode = KEY_A+(cKeyCode-65);
+ else if ( (cKeyCode >= 97) && (cKeyCode <= 122) )
+ aKeyEvt.mnCode = KEY_A+(cKeyCode-97);
+ else
+ aKeyEvt.mnCode = 0;
+ aKeyEvt.mnCode |= nModCode;
+ aKeyEvt.mnCharCode = ImplGetCharCode( pFrame, cKeyCode );
+ aKeyEvt.mnRepeat = nRepeat;
+ bool nRet = pFrame->CallCallback( SalEvent::KeyInput, &aKeyEvt );
+ pFrame->CallCallback( SalEvent::KeyUp, &aKeyEvt );
+ return nRet;
+}
+
+namespace {
+
+enum class DeferPolicy
+{
+ Blocked,
+ Allowed
+};
+
+}
+
+// Remember to release the solar mutex on success!
+static WinSalFrame* ProcessOrDeferMessage( HWND hWnd, INT nMsg, WPARAM pWParam = 0,
+ DeferPolicy eCanDefer = DeferPolicy::Allowed )
+{
+ bool bFailedCondition = false, bGotMutex = false;
+ WinSalFrame* pFrame = nullptr;
+
+ if ( DeferPolicy::Blocked == eCanDefer )
+ assert( (DeferPolicy::Blocked == eCanDefer) && (nMsg == 0) && (pWParam == 0) );
+ else
+ assert( (DeferPolicy::Allowed == eCanDefer) && (nMsg != 0) );
+
+ if ( DeferPolicy::Blocked == eCanDefer )
+ {
+ ImplSalYieldMutexAcquireWithWait();
+ bGotMutex = true;
+ }
+ else if ( !(bGotMutex = ImplSalYieldMutexTryToAcquire()) )
+ bFailedCondition = true;
+
+ if ( !bFailedCondition )
+ {
+ pFrame = GetWindowPtr( hWnd );
+ bFailedCondition = pFrame == nullptr;
+ }
+
+ if ( bFailedCondition )
+ {
+ if ( bGotMutex )
+ ImplSalYieldMutexRelease();
+ if ( DeferPolicy::Allowed == eCanDefer )
+ {
+ bool const ret = PostMessageW(hWnd, nMsg, pWParam, 0);
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+ }
+ }
+
+ return pFrame;
+}
+
+namespace {
+
+enum class PostedState
+{
+ IsPosted,
+ IsInitial
+};
+
+}
+
+static bool ImplHandlePostPaintMsg( HWND hWnd, RECT* pRect,
+ PostedState eProcessed = PostedState::IsPosted )
+{
+ RECT* pMsgRect;
+ if ( PostedState::IsInitial == eProcessed )
+ {
+ pMsgRect = new RECT;
+ CopyRect( pMsgRect, pRect );
+ }
+ else
+ pMsgRect = pRect;
+
+ WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, SAL_MSG_POSTPAINT,
+ reinterpret_cast<WPARAM>(pMsgRect) );
+ if ( pFrame )
+ {
+ SalPaintEvent aPEvt( pRect->left, pRect->top, pRect->right-pRect->left, pRect->bottom-pRect->top );
+ pFrame->CallCallback( SalEvent::Paint, &aPEvt );
+ ImplSalYieldMutexRelease();
+ if ( PostedState::IsPosted == eProcessed )
+ delete pRect;
+ }
+
+ return (pFrame != nullptr);
+}
+
+static bool ImplHandlePaintMsg( HWND hWnd )
+{
+ bool bPaintSuccessful = false;
+
+ // even without the Yield mutex, we can still change the clip region,
+ // because other threads don't use the Yield mutex
+ // --> see AcquireGraphics()
+
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( pFrame )
+ {
+ // clip region must be set, as we don't get a proper
+ // bounding rectangle otherwise
+ WinSalGraphics *pGraphics = pFrame->mpLocalGraphics;
+ bool bHasClipRegion = pGraphics &&
+ pGraphics->getHDC() && pGraphics->getRegion();
+ if ( bHasClipRegion )
+ SelectClipRgn( pGraphics->getHDC(), nullptr );
+
+ // according to Windows documentation one shall check first if
+ // there really is a paint-region
+ RECT aUpdateRect;
+ PAINTSTRUCT aPs;
+ bool bHasPaintRegion = GetUpdateRect( hWnd, nullptr, FALSE );
+ if ( bHasPaintRegion )
+ {
+ // call BeginPaint/EndPaint to query the paint rect and use
+ // this information in the (deferred) paint
+ BeginPaint( hWnd, &aPs );
+ CopyRect( &aUpdateRect, &aPs.rcPaint );
+ }
+
+ // reset clip region
+ if ( bHasClipRegion )
+ SelectClipRgn( pGraphics->getHDC(), pGraphics->getRegion() );
+
+ // try painting
+ if ( bHasPaintRegion )
+ {
+ bPaintSuccessful = ImplHandlePostPaintMsg(
+ hWnd, &aUpdateRect, PostedState::IsInitial );
+ EndPaint( hWnd, &aPs );
+ }
+ else // if there is nothing to paint, the paint is successful
+ bPaintSuccessful = true;
+ }
+
+ return bPaintSuccessful;
+}
+
+static void SetMaximizedFrameGeometry( HWND hWnd, WinSalFrame* pFrame, RECT* pParentRect )
+{
+ // calculate and set frame geometry of a maximized window - useful if the window is still hidden
+
+ // dualmonitor support:
+ // Get screensize of the monitor with the mouse pointer
+
+ RECT aRectMouse;
+ if( ! pParentRect )
+ {
+ POINT pt;
+ GetCursorPos( &pt );
+ aRectMouse.left = pt.x;
+ aRectMouse.top = pt.y;
+ aRectMouse.right = pt.x+2;
+ aRectMouse.bottom = pt.y+2;
+ pParentRect = &aRectMouse;
+ }
+
+ RECT aRect;
+ ImplSalGetWorkArea( hWnd, &aRect, pParentRect );
+
+ // a maximized window has no other borders than the caption
+ pFrame->maGeometry.setDecorations(0, pFrame->mbCaption ? GetSystemMetrics(SM_CYCAPTION) : 0, 0, 0);
+
+ aRect.top += pFrame->maGeometry.topDecoration();
+ pFrame->maGeometry.setPos({ aRect.left, aRect.top });
+ SetGeometrySize(pFrame->maGeometry, { aRect.right - aRect.left, aRect.bottom - aRect.top });
+}
+
+static void UpdateFrameGeometry(WinSalFrame* pFrame)
+{
+ if( !pFrame )
+ return;
+ const HWND hWnd = pFrame->mhWnd;
+
+ RECT aRect;
+ GetWindowRect( hWnd, &aRect );
+ pFrame->maGeometry.setPosSize({ 0, 0 }, { 0, 0 });
+ pFrame->maGeometry.setDecorations(0, 0, 0, 0);
+ pFrame->maGeometry.setScreen(0);
+
+ if ( IsIconic( hWnd ) )
+ return;
+
+ POINT aPt;
+ aPt.x=0;
+ aPt.y=0;
+ ClientToScreen(hWnd, &aPt);
+ int cx = aPt.x - aRect.left;
+
+ pFrame->maGeometry.setDecorations(cx, aPt.y - aRect.top, cx, 0);
+ pFrame->maGeometry.setPos({ aPt.x, aPt.y });
+
+ RECT aInnerRect;
+ GetClientRect( hWnd, &aInnerRect );
+ if( aInnerRect.right )
+ {
+ // improve right decoration
+ aPt.x=aInnerRect.right;
+ aPt.y=aInnerRect.top;
+ ClientToScreen(hWnd, &aPt);
+ pFrame->maGeometry.setRightDecoration(aRect.right - aPt.x);
+ }
+ if( aInnerRect.bottom ) // may be zero if window was not shown yet
+ pFrame->maGeometry.setBottomDecoration(aRect.bottom - aPt.y - aInnerRect.bottom);
+ else
+ // bottom border is typically the same as left/right
+ pFrame->maGeometry.setBottomDecoration(pFrame->maGeometry.leftDecoration());
+
+ int nWidth = aRect.right - aRect.left
+ - pFrame->maGeometry.rightDecoration() - pFrame->maGeometry.leftDecoration();
+ int nHeight = aRect.bottom - aRect.top
+ - pFrame->maGeometry.bottomDecoration() - pFrame->maGeometry.topDecoration();
+ SetGeometrySize(pFrame->maGeometry, { nWidth, nHeight });
+ pFrame->updateScreenNumber();
+}
+
+static void ImplCallClosePopupsHdl( HWND hWnd )
+{
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( pFrame )
+ {
+ pFrame->CallCallback( SalEvent::ClosePopups, nullptr );
+ }
+}
+
+static void ImplCallMoveHdl(HWND hWnd)
+{
+ WinSalFrame* pFrame = ProcessOrDeferMessage(hWnd, SAL_MSG_POSTMOVE);
+ if (!pFrame)
+ return;
+
+ pFrame->CallCallback(SalEvent::Move, nullptr);
+
+ ImplSalYieldMutexRelease();
+}
+
+static void ImplHandleMoveMsg(HWND hWnd, LPARAM lParam)
+{
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if (!pFrame)
+ return;
+
+ UpdateFrameGeometry(pFrame);
+
+#ifdef NDEBUG
+ (void) lParam;
+#endif
+ assert(IsIconic(hWnd) || (pFrame->maGeometry.x() == static_cast<sal_Int16>(LOWORD(lParam))));
+ assert(IsIconic(hWnd) || (pFrame->maGeometry.y() == static_cast<sal_Int16>(HIWORD(lParam))));
+
+ if (GetWindowStyle(hWnd) & WS_VISIBLE)
+ pFrame->mbDefPos = false;
+
+ // protect against recursion
+ if (!pFrame->mbInMoveMsg)
+ {
+ // adjust window again for FullScreenMode
+ pFrame->mbInMoveMsg = true;
+ if (pFrame->isFullScreen())
+ ImplSalFrameFullScreenPos(pFrame);
+ pFrame->mbInMoveMsg = false;
+ }
+
+ pFrame->UpdateFrameState();
+
+ ImplCallMoveHdl(hWnd);
+}
+
+static void ImplCallSizeHdl( HWND hWnd )
+{
+ WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, SAL_MSG_POSTCALLSIZE );
+ if (!pFrame)
+ return;
+
+ pFrame->CallCallback(SalEvent::Resize, nullptr);
+ // to avoid double Paints by VCL and SAL
+ if (IsWindowVisible(hWnd) && !pFrame->mbInShow)
+ UpdateWindow(hWnd);
+
+ ImplSalYieldMutexRelease();
+}
+
+static void ImplHandleSizeMsg(HWND hWnd, WPARAM wParam, LPARAM lParam)
+{
+ if ((wParam == SIZE_MAXSHOW) || (wParam == SIZE_MAXHIDE))
+ return;
+
+ WinSalFrame* pFrame = GetWindowPtr(hWnd);
+ if (!pFrame)
+ return;
+
+ UpdateFrameGeometry(pFrame);
+
+#ifdef NDEBUG
+ (void) lParam;
+#endif
+ assert(pFrame->maGeometry.width() == static_cast<sal_Int16>(LOWORD(lParam)));
+ assert(pFrame->maGeometry.height() == static_cast<sal_Int16>(HIWORD(lParam)));
+
+ pFrame->UpdateFrameState();
+
+ ImplCallSizeHdl(hWnd);
+
+ WinSalTimer* pTimer = static_cast<WinSalTimer*>(ImplGetSVData()->maSchedCtx.mpSalTimer);
+ if (pTimer)
+ pTimer->SetForceRealTimer(true);
+}
+
+static void ImplHandleFocusMsg( HWND hWnd )
+{
+ WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, SAL_MSG_POSTFOCUS );
+ if (!pFrame)
+ return;
+ const ::comphelper::ScopeGuard aScopeGuard([](){ ImplSalYieldMutexRelease(); });
+
+ if (WinSalFrame::mbInReparent)
+ return;
+
+ const bool bGotFocus = ::GetFocus() == hWnd;
+ if (bGotFocus)
+ {
+ if (IsWindowVisible(hWnd) && !pFrame->mbInShow)
+ UpdateWindow(hWnd);
+
+ // do we support IME?
+ if (pFrame->mbIME && pFrame->mhDefIMEContext)
+ {
+ UINT nImeProps = ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY);
+ pFrame->mbSpezIME = (nImeProps & IME_PROP_SPECIAL_UI) != 0;
+ pFrame->mbAtCursorIME = (nImeProps & IME_PROP_AT_CARET) != 0;
+ pFrame->mbHandleIME = !pFrame->mbSpezIME;
+ }
+ }
+
+ pFrame->CallCallback(bGotFocus ? SalEvent::GetFocus : SalEvent::LoseFocus, nullptr);
+}
+
+static void ImplHandleCloseMsg( HWND hWnd )
+{
+ WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, WM_CLOSE );
+ if ( pFrame )
+ {
+ pFrame->CallCallback( SalEvent::Close, nullptr );
+ ImplSalYieldMutexRelease();
+ }
+}
+
+static bool ImplHandleShutDownMsg( HWND hWnd )
+{
+ bool nRet = false;
+ WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, 0, 0, DeferPolicy::Blocked );
+ if ( pFrame )
+ {
+ nRet = pFrame->CallCallback( SalEvent::Shutdown, nullptr );
+ ImplSalYieldMutexRelease();
+ }
+ return nRet;
+}
+
+static void ImplHandleSettingsChangeMsg( HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam )
+{
+ SalEvent nSalEvent = SalEvent::SettingsChanged;
+
+ if ( nMsg == WM_DEVMODECHANGE )
+ nSalEvent = SalEvent::PrinterChanged;
+ else if ( nMsg == WM_DISPLAYCHANGE )
+ nSalEvent = SalEvent::DisplayChanged;
+ else if ( nMsg == WM_FONTCHANGE )
+ nSalEvent = SalEvent::FontChanged;
+ else if ( nMsg == WM_WININICHANGE )
+ {
+ if ( lParam )
+ {
+ if ( ImplSalWICompareAscii( reinterpret_cast<const wchar_t*>(lParam), "devices" ) == 0 )
+ nSalEvent = SalEvent::PrinterChanged;
+ }
+ }
+
+ if ( nMsg == WM_SETTINGCHANGE )
+ {
+ if ( wParam == SPI_SETWHEELSCROLLLINES )
+ aSalShlData.mnWheelScrollLines = ImplSalGetWheelScrollLines();
+ else if( wParam == SPI_SETWHEELSCROLLCHARS )
+ aSalShlData.mnWheelScrollChars = ImplSalGetWheelScrollChars();
+ UpdateDarkMode(hWnd);
+ GetSalData()->mbThemeChanged = true;
+ }
+
+ if ( WM_SYSCOLORCHANGE == nMsg && GetSalData()->mhDitherPal )
+ ImplUpdateSysColorEntries();
+
+ WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, 0, 0, DeferPolicy::Blocked );
+ if (!pFrame)
+ return;
+
+ if (((nMsg == WM_DISPLAYCHANGE) || (nMsg == WM_WININICHANGE)) && pFrame->isFullScreen())
+ ImplSalFrameFullScreenPos(pFrame);
+
+ pFrame->CallCallback(nSalEvent, nullptr);
+
+ ImplSalYieldMutexRelease();
+}
+
+static void ImplHandleUserEvent( HWND hWnd, LPARAM lParam )
+{
+ WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, 0, 0, DeferPolicy::Blocked );
+ if ( pFrame )
+ {
+ pFrame->CallCallback( SalEvent::UserEvent, reinterpret_cast<void*>(lParam) );
+ ImplSalYieldMutexRelease();
+ }
+}
+
+static void ImplHandleForcePalette( HWND hWnd )
+{
+ SalData* pSalData = GetSalData();
+ HPALETTE hPal = pSalData->mhDitherPal;
+ if (!hPal)
+ return;
+
+ WinSalFrame* pFrame = ProcessOrDeferMessage(hWnd, SAL_MSG_FORCEPALETTE);
+ if (!pFrame)
+ return;
+ const ::comphelper::ScopeGuard aScopeGuard([](){ ImplSalYieldMutexRelease(); });
+
+ WinSalGraphics* pGraphics = pFrame->mpLocalGraphics;
+ if (!pGraphics || !pGraphics->getHDC() || !pGraphics->getDefPal()
+ || (pGraphics->setPalette(hPal, FALSE) == GDI_ERROR))
+ return;
+
+ InvalidateRect(hWnd, nullptr, FALSE);
+ UpdateWindow(hWnd);
+ pFrame->CallCallback(SalEvent::DisplayChanged, nullptr);
+}
+
+static LRESULT ImplHandlePalette( bool bFrame, HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam, bool& rDef )
+{
+ SalData* pSalData = GetSalData();
+ HPALETTE hPal = pSalData->mhDitherPal;
+ if ( !hPal )
+ return 0;
+
+ rDef = false;
+ if ( pSalData->mbInPalChange )
+ return 0;
+
+ if ( (nMsg == WM_PALETTECHANGED) || (nMsg == SAL_MSG_POSTPALCHANGED) )
+ {
+ if ( reinterpret_cast<HWND>(wParam) == hWnd )
+ return 0;
+ }
+
+ bool bReleaseMutex = false;
+ if ( (nMsg == WM_QUERYNEWPALETTE) || (nMsg == WM_PALETTECHANGED) )
+ {
+ // as Windows can send these messages also, we have to use
+ // the Solar semaphore
+ if ( ImplSalYieldMutexTryToAcquire() )
+ bReleaseMutex = true;
+ else if ( nMsg == WM_QUERYNEWPALETTE )
+ {
+ bool const ret = PostMessageW(hWnd, SAL_MSG_POSTQUERYNEWPAL, wParam, lParam);
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+ }
+ else /* ( nMsg == WM_PALETTECHANGED ) */
+ {
+ bool const ret = PostMessageW(hWnd, SAL_MSG_POSTPALCHANGED, wParam, lParam);
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+ }
+ }
+
+ WinSalVirtualDevice*pTempVD;
+ WinSalFrame* pTempFrame;
+ WinSalGraphics* pGraphics;
+ HDC hDC;
+ HPALETTE hOldPal = nullptr;
+ UINT nCols = GDI_ERROR;
+ bool bUpdate;
+
+ pSalData->mbInPalChange = true;
+
+ // reset all palettes in VirDevs and Frames
+ pTempVD = pSalData->mpFirstVD;
+ while ( pTempVD )
+ {
+ pGraphics = pTempVD->getGraphics();
+ pGraphics->setPalette(nullptr);
+ pTempVD = pTempVD->getNext();
+ }
+ pTempFrame = pSalData->mpFirstFrame;
+ while ( pTempFrame )
+ {
+ pGraphics = pTempFrame->mpLocalGraphics;
+ pGraphics->setPalette(nullptr);
+ pTempFrame = pTempFrame->mpNextFrame;
+ }
+
+ // re-initialize palette
+ WinSalFrame* pFrame = nullptr;
+ if ( bFrame )
+ pFrame = GetWindowPtr( hWnd );
+
+ UnrealizeObject(hPal);
+ const bool bStdDC = pFrame && pFrame->mpLocalGraphics && pFrame->mpLocalGraphics->getHDC();
+ if (!bStdDC)
+ {
+ hDC = GetDC(hWnd);
+ hOldPal = SelectPalette(hDC, hPal, TRUE);
+ if (hOldPal)
+ nCols = RealizePalette(hDC);
+ }
+ else
+ {
+ hDC = pFrame->mpLocalGraphics->getHDC();
+ nCols = pFrame->mpLocalGraphics->setPalette(hPal);
+ }
+
+ bUpdate = nCols != 0 && nCols != GDI_ERROR;
+
+ if ( !bStdDC )
+ {
+ if (hOldPal)
+ SelectPalette(hDC, hOldPal, TRUE);
+ ReleaseDC( hWnd, hDC );
+ }
+
+ // reset all palettes in VirDevs and Frames
+ pTempVD = pSalData->mpFirstVD;
+ while ( pTempVD )
+ {
+ pGraphics = pTempVD->getGraphics();
+ if ( pGraphics->getDefPal() )
+ pGraphics->setPalette(hPal);
+ pTempVD = pTempVD->getNext();
+ }
+
+ pTempFrame = pSalData->mpFirstFrame;
+ while ( pTempFrame )
+ {
+ if ( pTempFrame != pFrame )
+ {
+ pGraphics = pTempFrame->mpLocalGraphics;
+ if (pGraphics && pGraphics->getDefPal())
+ {
+ UINT nRes = pGraphics->setPalette(hPal);
+ if (nRes != 0 && nRes != GDI_ERROR)
+ bUpdate = true;
+ }
+ }
+ pTempFrame = pTempFrame->mpNextFrame;
+ }
+
+ // if colors changed, update the window
+ if ( bUpdate )
+ {
+ pTempFrame = pSalData->mpFirstFrame;
+ while ( pTempFrame )
+ {
+ pGraphics = pTempFrame->mpLocalGraphics;
+ if (pGraphics && pGraphics->getDefPal())
+ {
+ InvalidateRect( pTempFrame->mhWnd, nullptr, FALSE );
+ UpdateWindow( pTempFrame->mhWnd );
+ pTempFrame->CallCallback( SalEvent::DisplayChanged, nullptr );
+ }
+ pTempFrame = pTempFrame->mpNextFrame;
+ }
+ }
+
+ pSalData->mbInPalChange = false;
+
+ if ( bReleaseMutex )
+ ImplSalYieldMutexRelease();
+
+ if ( nMsg == WM_PALETTECHANGED )
+ return 0;
+ else
+ return nCols;
+}
+
+static bool ImplHandleMinMax( HWND hWnd, LPARAM lParam )
+{
+ bool bRet = false;
+
+ if ( ImplSalYieldMutexTryToAcquire() )
+ {
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( pFrame )
+ {
+ MINMAXINFO* pMinMax = reinterpret_cast<MINMAXINFO*>(lParam);
+
+ if (pFrame->isFullScreen())
+ {
+ int nX;
+ int nY;
+ int nDX;
+ int nDY;
+ ImplSalCalcFullScreenSize( pFrame, nX, nY, nDX, nDY );
+
+ if ( pMinMax->ptMaxPosition.x > nX )
+ pMinMax->ptMaxPosition.x = nX;
+ if ( pMinMax->ptMaxPosition.y > nY )
+ pMinMax->ptMaxPosition.y = nY;
+
+ if ( pMinMax->ptMaxSize.x < nDX )
+ pMinMax->ptMaxSize.x = nDX;
+ if ( pMinMax->ptMaxSize.y < nDY )
+ pMinMax->ptMaxSize.y = nDY;
+ if ( pMinMax->ptMaxTrackSize.x < nDX )
+ pMinMax->ptMaxTrackSize.x = nDX;
+ if ( pMinMax->ptMaxTrackSize.y < nDY )
+ pMinMax->ptMaxTrackSize.y = nDY;
+
+ pMinMax->ptMinTrackSize.x = nDX;
+ pMinMax->ptMinTrackSize.y = nDY;
+
+ bRet = true;
+ }
+
+ if ( pFrame->mnMinWidth || pFrame->mnMinHeight )
+ {
+ int nWidth = pFrame->mnMinWidth;
+ int nHeight = pFrame->mnMinHeight;
+
+ ImplSalAddBorder( pFrame, nWidth, nHeight );
+
+ if ( pMinMax->ptMinTrackSize.x < nWidth )
+ pMinMax->ptMinTrackSize.x = nWidth;
+ if ( pMinMax->ptMinTrackSize.y < nHeight )
+ pMinMax->ptMinTrackSize.y = nHeight;
+ }
+
+ if ( pFrame->mnMaxWidth || pFrame->mnMaxHeight )
+ {
+ int nWidth = pFrame->mnMaxWidth;
+ int nHeight = pFrame->mnMaxHeight;
+
+ ImplSalAddBorder( pFrame, nWidth, nHeight );
+
+ if( nWidth > 0 && nHeight > 0 ) // protect against int overflow due to INT_MAX initialisation
+ {
+ if ( pMinMax->ptMaxTrackSize.x > nWidth )
+ pMinMax->ptMaxTrackSize.x = nWidth;
+ if ( pMinMax->ptMaxTrackSize.y > nHeight )
+ pMinMax->ptMaxTrackSize.y = nHeight;
+ }
+ }
+ }
+
+ ImplSalYieldMutexRelease();
+ }
+
+ return bRet;
+}
+
+// retrieves the SalMenuItem pointer from a hMenu
+// the pointer is stored in every item, so if no position
+// is specified we just use the first item (ie, pos=0)
+// if bByPosition is false then nPos denotes a menu id instead of a position
+static WinSalMenuItem* ImplGetSalMenuItem( HMENU hMenu, UINT nPos, bool bByPosition=true )
+{
+ MENUITEMINFOW mi = {};
+ mi.cbSize = sizeof( mi );
+ mi.fMask = MIIM_DATA;
+ if( !GetMenuItemInfoW( hMenu, nPos, bByPosition, &mi) )
+ SAL_WARN("vcl", "GetMenuItemInfoW failed: " << WindowsErrorString(GetLastError()));
+
+ return reinterpret_cast<WinSalMenuItem *>(mi.dwItemData);
+}
+
+// returns the index of the currently selected item if any or -1
+static int ImplGetSelectedIndex( HMENU hMenu )
+{
+ MENUITEMINFOW mi = {};
+ mi.cbSize = sizeof( mi );
+ mi.fMask = MIIM_STATE;
+ int n = GetMenuItemCount( hMenu );
+ if( n != -1 )
+ {
+ for(int i=0; i<n; i++ )
+ {
+ if( !GetMenuItemInfoW( hMenu, i, TRUE, &mi) )
+ SAL_WARN( "vcl", "GetMenuItemInfoW failed: " << WindowsErrorString( GetLastError() ) );
+ else
+ {
+ if( mi.fState & MFS_HILITE )
+ return i;
+ }
+ }
+ }
+ return -1;
+}
+
+static LRESULT ImplMenuChar( HWND, WPARAM wParam, LPARAM lParam )
+{
+ LRESULT nRet = MNC_IGNORE;
+ HMENU hMenu = reinterpret_cast<HMENU>(lParam);
+ OUString aMnemonic( "&" + OUStringChar(static_cast<sal_Unicode>(LOWORD(wParam))) );
+ aMnemonic = aMnemonic.toAsciiLowerCase(); // we only have ascii mnemonics
+
+ // search the mnemonic in the current menu
+ int nItemCount = GetMenuItemCount( hMenu );
+ int nFound = 0;
+ int idxFound = -1;
+ int idxSelected = ImplGetSelectedIndex( hMenu );
+ int idx = idxSelected != -1 ? idxSelected+1 : 0; // if duplicate mnemonics cycle through menu
+ for( int i=0; i< nItemCount; i++, idx++ )
+ {
+ WinSalMenuItem* pSalMenuItem = ImplGetSalMenuItem( hMenu, idx % nItemCount );
+ if( !pSalMenuItem )
+ continue;
+ OUString aStr = pSalMenuItem->mText;
+ aStr = aStr.toAsciiLowerCase();
+ if( aStr.indexOf( aMnemonic ) != -1 )
+ {
+ if( idxFound == -1 )
+ idxFound = idx % nItemCount;
+ if( nFound++ )
+ break; // duplicate found
+ }
+ }
+ if( nFound == 1 )
+ nRet = MAKELRESULT( idxFound, MNC_EXECUTE );
+ else
+ // duplicate mnemonics, just select the next occurrence
+ nRet = MAKELRESULT( idxFound, MNC_SELECT );
+
+ return nRet;
+}
+
+static LRESULT ImplMeasureItem( HWND hWnd, WPARAM wParam, LPARAM lParam )
+{
+ LRESULT nRet = 0;
+ if( !wParam )
+ {
+ // request was sent by a menu
+ nRet = 1;
+ MEASUREITEMSTRUCT *pMI = reinterpret_cast<LPMEASUREITEMSTRUCT>(lParam);
+ if( pMI->CtlType != ODT_MENU )
+ return 0;
+
+ WinSalMenuItem *pSalMenuItem = reinterpret_cast<WinSalMenuItem *>(pMI->itemData);
+ if( !pSalMenuItem )
+ return 0;
+
+ HDC hdc = GetDC( hWnd );
+ SIZE strSize;
+
+ NONCLIENTMETRICSW ncm = {};
+ ncm.cbSize = sizeof( ncm );
+ SystemParametersInfoW( SPI_GETNONCLIENTMETRICS, 0, &ncm, 0 );
+
+ // Assume every menu item can be default and printed bold
+ //ncm.lfMenuFont.lfWeight = FW_BOLD;
+
+ HFONT hfntOld = static_cast<HFONT>(SelectObject(hdc, CreateFontIndirectW( &ncm.lfMenuFont )));
+
+ // menu text and accelerator
+ OUString aStr(pSalMenuItem->mText);
+ if( pSalMenuItem->mAccelText.getLength() )
+ {
+ aStr += " " + pSalMenuItem->mAccelText;
+ }
+ GetTextExtentPoint32W( hdc, o3tl::toW(aStr.getStr()),
+ aStr.getLength(), &strSize );
+
+ // image
+ Size bmpSize( 16, 16 );
+ //if( pSalMenuItem->maBitmap )
+ // bmpSize = pSalMenuItem->maBitmap.GetSizePixel();
+
+ // checkmark
+ Size checkSize( GetSystemMetrics( SM_CXMENUCHECK ), GetSystemMetrics( SM_CYMENUCHECK ) );
+
+ pMI->itemWidth = checkSize.Width() + 3 + bmpSize.Width() + 3 + strSize.cx;
+ pMI->itemHeight = std::max( std::max( checkSize.Height(), bmpSize.Height() ), tools::Long(strSize.cy) );
+ pMI->itemHeight += 4;
+
+ DeleteObject( SelectObject(hdc, hfntOld) );
+ ReleaseDC( hWnd, hdc );
+ }
+
+ return nRet;
+}
+
+static LRESULT ImplDrawItem(HWND, WPARAM wParam, LPARAM lParam )
+{
+ LRESULT nRet = 0;
+ if( !wParam )
+ {
+ // request was sent by a menu
+ nRet = 1;
+ DRAWITEMSTRUCT *pDI = reinterpret_cast<LPDRAWITEMSTRUCT>(lParam);
+ if( pDI->CtlType != ODT_MENU )
+ return 0;
+
+ WinSalMenuItem *pSalMenuItem = reinterpret_cast<WinSalMenuItem *>(pDI->itemData);
+ if( !pSalMenuItem )
+ return 0;
+
+ COLORREF clrPrevText, clrPrevBkgnd;
+ HFONT hfntOld;
+ HBRUSH hbrOld;
+ bool fChecked = (pDI->itemState & ODS_CHECKED);
+ bool fSelected = (pDI->itemState & ODS_SELECTED);
+ bool fDisabled = (pDI->itemState & (ODS_DISABLED | ODS_GRAYED));
+
+ // Set the appropriate foreground and background colors.
+ RECT aRect = pDI->rcItem;
+
+ if ( fDisabled )
+ clrPrevText = SetTextColor( pDI->hDC, GetSysColor( COLOR_GRAYTEXT ) );
+ else
+ clrPrevText = SetTextColor( pDI->hDC, GetSysColor( fSelected ? COLOR_HIGHLIGHTTEXT : COLOR_MENUTEXT ) );
+
+ DWORD colBackground = GetSysColor( fSelected ? COLOR_HIGHLIGHT : COLOR_MENU );
+ clrPrevBkgnd = SetBkColor( pDI->hDC, colBackground );
+
+ hbrOld = static_cast<HBRUSH>(SelectObject( pDI->hDC, CreateSolidBrush( GetBkColor( pDI->hDC ) ) ));
+
+ // Fill background
+ if(!PatBlt( pDI->hDC, aRect.left, aRect.top, aRect.right-aRect.left, aRect.bottom-aRect.top, PATCOPY ))
+ SAL_WARN("vcl", "PatBlt failed: " << WindowsErrorString(GetLastError()));
+
+ int lineHeight = aRect.bottom-aRect.top;
+
+ int x = aRect.left;
+ int y = aRect.top;
+
+ int checkWidth = GetSystemMetrics( SM_CXMENUCHECK );
+ int checkHeight = GetSystemMetrics( SM_CYMENUCHECK );
+ if( fChecked )
+ {
+ RECT r;
+ r.left = 0;
+ r.top = 0;
+ r.right = checkWidth;
+ r.bottom = checkWidth;
+ HDC memDC = CreateCompatibleDC( pDI->hDC );
+ HBITMAP memBmp = CreateCompatibleBitmap( pDI->hDC, checkWidth, checkHeight );
+ HBITMAP hOldBmp = static_cast<HBITMAP>(SelectObject( memDC, memBmp ));
+ DrawFrameControl( memDC, &r, DFC_MENU, DFCS_MENUCHECK );
+ BitBlt( pDI->hDC, x, y+(lineHeight-checkHeight)/2, checkWidth, checkHeight, memDC, 0, 0, SRCAND );
+ DeleteObject( SelectObject( memDC, hOldBmp ) );
+ DeleteDC( memDC );
+ }
+ x += checkWidth+3;
+
+ //Size bmpSize = aBitmap.GetSizePixel();
+ Size bmpSize(16, 16);
+ if( !pSalMenuItem->maBitmap.IsEmpty() )
+ {
+ Bitmap aBitmap( pSalMenuItem->maBitmap );
+
+ // set transparent pixels to background color
+ if( fDisabled )
+ colBackground = RGB(255,255,255);
+ aBitmap.Replace( COL_LIGHTMAGENTA,
+ Color( GetRValue(colBackground),GetGValue(colBackground),GetBValue(colBackground) ));
+
+ WinSalBitmap* pSalBmp = static_cast<WinSalBitmap*>(aBitmap.ImplGetSalBitmap().get());
+ HGLOBAL hDrawDIB = pSalBmp->ImplGethDIB();
+
+ if( hDrawDIB )
+ {
+ PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( hDrawDIB ));
+ PBYTE pBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize +
+ WinSalBitmap::ImplGetDIBColorCount( hDrawDIB ) * sizeof( RGBQUAD );
+
+ HBITMAP hBmp = CreateDIBitmap( pDI->hDC, &pBI->bmiHeader, CBM_INIT, pBits, pBI, DIB_RGB_COLORS );
+ GlobalUnlock( hDrawDIB );
+
+ HBRUSH hbrIcon = CreateSolidBrush( GetSysColor( COLOR_GRAYTEXT ) );
+ DrawStateW( pDI->hDC, hbrIcon, nullptr, reinterpret_cast<LPARAM>(hBmp), WPARAM(0),
+ x, y+(lineHeight-bmpSize.Height())/2, bmpSize.Width(), bmpSize.Height(),
+ DST_BITMAP | (fDisabled ? (fSelected ? DSS_MONO : DSS_DISABLED) : DSS_NORMAL) );
+
+ DeleteObject( hbrIcon );
+ DeleteObject( hBmp );
+ }
+
+ }
+ x += bmpSize.Width() + 3;
+ aRect.left = x;
+
+ NONCLIENTMETRICSW ncm = {};
+ ncm.cbSize = sizeof( ncm );
+ SystemParametersInfoW( SPI_GETNONCLIENTMETRICS, 0, &ncm, 0 );
+
+ // Print default menu entry with bold font
+ //if ( pDI->itemState & ODS_DEFAULT )
+ // ncm.lfMenuFont.lfWeight = FW_BOLD;
+
+ hfntOld = static_cast<HFONT>(SelectObject(pDI->hDC, CreateFontIndirectW( &ncm.lfMenuFont )));
+
+ SIZE strSize;
+ OUString aStr( pSalMenuItem->mText );
+ GetTextExtentPoint32W( pDI->hDC, o3tl::toW(aStr.getStr()),
+ aStr.getLength(), &strSize );
+
+ if(!DrawStateW( pDI->hDC, nullptr, nullptr,
+ reinterpret_cast<LPARAM>(aStr.getStr()),
+ WPARAM(0), aRect.left, aRect.top + (lineHeight - strSize.cy)/2, 0, 0,
+ DST_PREFIXTEXT | (fDisabled && !fSelected ? DSS_DISABLED : DSS_NORMAL) ) )
+ SAL_WARN("vcl", "DrawStateW failed: " << WindowsErrorString(GetLastError()));
+
+ if( pSalMenuItem->mAccelText.getLength() )
+ {
+ SIZE strSizeA;
+ aStr = pSalMenuItem->mAccelText;
+ GetTextExtentPoint32W( pDI->hDC, o3tl::toW(aStr.getStr()),
+ aStr.getLength(), &strSizeA );
+ TEXTMETRICW tm;
+ GetTextMetricsW( pDI->hDC, &tm );
+
+ // position the accelerator string to the right but leave space for the
+ // (potential) submenu arrow (tm.tmMaxCharWidth)
+ if(!DrawStateW( pDI->hDC, nullptr, nullptr,
+ reinterpret_cast<LPARAM>(aStr.getStr()),
+ WPARAM(0), aRect.right-strSizeA.cx-tm.tmMaxCharWidth, aRect.top + (lineHeight - strSizeA.cy)/2, 0, 0,
+ DST_TEXT | (fDisabled && !fSelected ? DSS_DISABLED : DSS_NORMAL) ) )
+ SAL_WARN("vcl", "DrawStateW failed: " << WindowsErrorString(GetLastError()));
+ }
+
+ // Restore the original font and colors.
+ DeleteObject( SelectObject( pDI->hDC, hbrOld ) );
+ DeleteObject( SelectObject( pDI->hDC, hfntOld) );
+ SetTextColor(pDI->hDC, clrPrevText);
+ SetBkColor(pDI->hDC, clrPrevBkgnd);
+ }
+ return nRet;
+}
+
+static bool ImplHandleMenuActivate( HWND hWnd, WPARAM wParam, LPARAM )
+{
+ // Menu activation
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( !pFrame )
+ return false;
+
+ HMENU hMenu = reinterpret_cast<HMENU>(wParam);
+ // WORD nPos = LOWORD (lParam);
+ // bool bWindowMenu = (bool) HIWORD(lParam);
+
+ // Send activate and deactivate together, so we have not keep track of opened menus
+ // this will be enough to have the menus updated correctly
+ SalMenuEvent aMenuEvt;
+ WinSalMenuItem *pSalMenuItem = ImplGetSalMenuItem( hMenu, 0 );
+ if( pSalMenuItem )
+ aMenuEvt.mpMenu = pSalMenuItem->mpMenu;
+ else
+ aMenuEvt.mpMenu = nullptr;
+
+ bool nRet = pFrame->CallCallback( SalEvent::MenuActivate, &aMenuEvt );
+ if( nRet )
+ nRet = pFrame->CallCallback( SalEvent::MenuDeactivate, &aMenuEvt );
+ if( nRet )
+ pFrame->mLastActivatedhMenu = hMenu;
+
+ return nRet;
+}
+
+static bool ImplHandleMenuSelect( HWND hWnd, WPARAM wParam, LPARAM lParam )
+{
+ // Menu selection
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( !pFrame )
+ return false;
+
+ WORD nId = LOWORD(wParam); // menu item or submenu index
+ WORD nFlags = HIWORD(wParam);
+ HMENU hMenu = reinterpret_cast<HMENU>(lParam);
+
+ // check if we have to process the message
+ if( !GetSalData()->IsKnownMenuHandle( hMenu ) )
+ return false;
+
+ bool bByPosition = false;
+ if( nFlags & MF_POPUP )
+ bByPosition = true;
+
+ bool nRet = false;
+ if ( hMenu && !pFrame->mLastActivatedhMenu )
+ {
+ // we never activated a menu (ie, no WM_INITMENUPOPUP has occurred yet)
+ // which means this must be the menubar -> send activation/deactivation
+ SalMenuEvent aMenuEvt;
+ WinSalMenuItem *pSalMenuItem = ImplGetSalMenuItem( hMenu, nId, bByPosition );
+ if( pSalMenuItem )
+ aMenuEvt.mpMenu = pSalMenuItem->mpMenu;
+ else
+ aMenuEvt.mpMenu = nullptr;
+
+ nRet = pFrame->CallCallback( SalEvent::MenuActivate, &aMenuEvt );
+ if( nRet )
+ nRet = pFrame->CallCallback( SalEvent::MenuDeactivate, &aMenuEvt );
+ if( nRet )
+ pFrame->mLastActivatedhMenu = hMenu;
+ }
+
+ if( !hMenu && nFlags == 0xFFFF )
+ {
+ // all menus are closed, reset activation logic
+ pFrame->mLastActivatedhMenu = nullptr;
+ }
+
+ if( hMenu )
+ {
+ // hMenu must be saved, as it is not passed in WM_COMMAND which always occurs after a selection
+ // if a menu is closed due to a command selection then hMenu is NULL, but WM_COMMAND comes later
+ // so we must not overwrite it in this case
+ pFrame->mSelectedhMenu = hMenu;
+
+ // send highlight event
+ if( nFlags & MF_POPUP )
+ {
+ // submenu selected
+ // wParam now carries an index instead of an id -> retrieve id
+ MENUITEMINFOW mi = {};
+ mi.cbSize = sizeof( mi );
+ mi.fMask = MIIM_ID;
+ if( GetMenuItemInfoW( hMenu, LOWORD(wParam), TRUE, &mi) )
+ nId = sal::static_int_cast<WORD>(mi.wID);
+ }
+
+ SalMenuEvent aMenuEvt;
+ aMenuEvt.mnId = nId;
+ WinSalMenuItem *pSalMenuItem = ImplGetSalMenuItem( hMenu, nId, false );
+ if( pSalMenuItem )
+ aMenuEvt.mpMenu = pSalMenuItem->mpMenu;
+ else
+ aMenuEvt.mpMenu = nullptr;
+
+ nRet = pFrame->CallCallback( SalEvent::MenuHighlight, &aMenuEvt );
+ }
+
+ return nRet;
+}
+
+static bool ImplHandleCommand( HWND hWnd, WPARAM wParam, LPARAM )
+{
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( !pFrame )
+ return false;
+
+ bool nRet = false;
+ if( !HIWORD(wParam) )
+ {
+ // Menu command
+ WORD nId = LOWORD(wParam);
+ if( nId ) // zero for separators
+ {
+ SalMenuEvent aMenuEvt;
+ aMenuEvt.mnId = nId;
+ WinSalMenuItem *pSalMenuItem = ImplGetSalMenuItem( pFrame->mSelectedhMenu, nId, false );
+ if( pSalMenuItem )
+ aMenuEvt.mpMenu = pSalMenuItem->mpMenu;
+ else
+ aMenuEvt.mpMenu = nullptr;
+
+ nRet = pFrame->CallCallback( SalEvent::MenuCommand, &aMenuEvt );
+ }
+ }
+ return nRet;
+}
+
+static bool ImplHandleSysCommand( HWND hWnd, WPARAM wParam, LPARAM lParam )
+{
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( !pFrame )
+ return false;
+
+ WPARAM nCommand = wParam & 0xFFF0;
+
+ if (pFrame->isFullScreen())
+ {
+ bool bMaximize = IsZoomed( pFrame->mhWnd );
+ bool bMinimize = IsIconic( pFrame->mhWnd );
+ if ( (nCommand == SC_SIZE) ||
+ (!bMinimize && (nCommand == SC_MOVE)) ||
+ (!bMaximize && (nCommand == SC_MAXIMIZE)) ||
+ (bMaximize && (nCommand == SC_RESTORE)) )
+ {
+ return true;
+ }
+ }
+
+ if ( nCommand == SC_MOVE )
+ {
+ WinSalTimer* pTimer = static_cast<WinSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
+ if ( pTimer )
+ pTimer->SetForceRealTimer( true );
+ }
+
+ if ( nCommand == SC_KEYMENU )
+ {
+ // do not process SC_KEYMENU if we have a native menu
+ // Windows should handle this
+ if( GetMenu( hWnd ) )
+ return false;
+
+ // Process here KeyMenu events only for Alt to activate the MenuBar,
+ // or if a SysChild window is in focus, as Alt-key-combinations are
+ // only processed via this event
+ if ( !LOWORD( lParam ) )
+ {
+ // Only trigger if no other key is pressed.
+ // Contrary to Docu the CharCode is delivered with the x-coordinate
+ // that is pressed in addition.
+ // Also 32 for space, 99 for c, 100 for d, ...
+ // As this is not documented, we check the state of the space-bar
+ if ( GetKeyState( VK_SPACE ) & 0x8000 )
+ return false;
+
+ // to avoid activating the MenuBar for Alt+MouseKey
+ if ( (GetKeyState( VK_LBUTTON ) & 0x8000) ||
+ (GetKeyState( VK_RBUTTON ) & 0x8000) ||
+ (GetKeyState( VK_MBUTTON ) & 0x8000) ||
+ (GetKeyState( VK_SHIFT ) & 0x8000) )
+ return true;
+
+ SalKeyEvent aKeyEvt;
+ aKeyEvt.mnCode = KEY_MENU;
+ aKeyEvt.mnCharCode = 0;
+ aKeyEvt.mnRepeat = 0;
+ bool nRet = pFrame->CallCallback( SalEvent::KeyInput, &aKeyEvt );
+ pFrame->CallCallback( SalEvent::KeyUp, &aKeyEvt );
+ return nRet;
+ }
+ else
+ {
+ // check if a SysChild is in focus
+ HWND hFocusWnd = ::GetFocus();
+ if ( hFocusWnd && ImplFindSalObject( hFocusWnd ) )
+ {
+ char cKeyCode = static_cast<char>(static_cast<unsigned char>(LOWORD( lParam )));
+ // LowerCase
+ if ( (cKeyCode >= 65) && (cKeyCode <= 90) )
+ cKeyCode += 32;
+ // We only accept 0-9 and A-Z; all other keys have to be
+ // processed by the SalObj hook
+ if ( ((cKeyCode >= 48) && (cKeyCode <= 57)) ||
+ ((cKeyCode >= 97) && (cKeyCode <= 122)) )
+ {
+ sal_uInt16 nModCode = 0;
+ if ( GetKeyState( VK_SHIFT ) & 0x8000 )
+ nModCode |= KEY_SHIFT;
+ if ( GetKeyState( VK_CONTROL ) & 0x8000 )
+ nModCode |= KEY_MOD1;
+ nModCode |= KEY_MOD2;
+
+ SalKeyEvent aKeyEvt;
+ if ( (cKeyCode >= 48) && (cKeyCode <= 57) )
+ aKeyEvt.mnCode = KEY_0+(cKeyCode-48);
+ else
+ aKeyEvt.mnCode = KEY_A+(cKeyCode-97);
+ aKeyEvt.mnCode |= nModCode;
+ aKeyEvt.mnCharCode = cKeyCode;
+ aKeyEvt.mnRepeat = 0;
+ bool nRet = pFrame->CallCallback( SalEvent::KeyInput, &aKeyEvt );
+ pFrame->CallCallback( SalEvent::KeyUp, &aKeyEvt );
+ return nRet;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+static void ImplHandleInputLangChange( HWND hWnd, WPARAM, LPARAM lParam )
+{
+ ImplSalYieldMutexAcquireWithWait();
+
+ // check if we support IME
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+
+ if ( !pFrame )
+ return;
+
+ if ( pFrame->mbIME && pFrame->mhDefIMEContext )
+ {
+ HKL hKL = reinterpret_cast<HKL>(lParam);
+ UINT nImeProps = ImmGetProperty( hKL, IGP_PROPERTY );
+
+ pFrame->mbSpezIME = (nImeProps & IME_PROP_SPECIAL_UI) != 0;
+ pFrame->mbAtCursorIME = (nImeProps & IME_PROP_AT_CARET) != 0;
+ pFrame->mbHandleIME = !pFrame->mbSpezIME;
+ }
+
+ // trigger input language and codepage update
+ UINT nLang = pFrame->mnInputLang;
+ ImplUpdateInputLang( pFrame );
+
+ // notify change
+ if( nLang != pFrame->mnInputLang )
+ pFrame->CallCallback( SalEvent::InputLanguageChange, nullptr );
+
+ // reinit spec. keys
+ GetSalData()->initKeyCodeMap();
+
+ ImplSalYieldMutexRelease();
+}
+
+static void ImplUpdateIMECursorPos( WinSalFrame* pFrame, HIMC hIMC )
+{
+ COMPOSITIONFORM aForm = {};
+
+ // get cursor position and from it calculate default position
+ // for the composition window
+ SalExtTextInputPosEvent aPosEvt;
+ pFrame->CallCallback( SalEvent::ExtTextInputPos, &aPosEvt );
+ if ( (aPosEvt.mnX == -1) && (aPosEvt.mnY == -1) )
+ aForm.dwStyle |= CFS_DEFAULT;
+ else
+ {
+ aForm.dwStyle |= CFS_POINT;
+ aForm.ptCurrentPos.x = aPosEvt.mnX;
+ aForm.ptCurrentPos.y = aPosEvt.mnY;
+ }
+ ImmSetCompositionWindow( hIMC, &aForm );
+
+ // Because not all IME's use this values, we create
+ // a Windows caret to force the Position from the IME
+ if ( GetFocus() == pFrame->mhWnd )
+ {
+ CreateCaret( pFrame->mhWnd, nullptr,
+ aPosEvt.mnWidth, aPosEvt.mnHeight );
+ SetCaretPos( aPosEvt.mnX, aPosEvt.mnY );
+ }
+}
+
+static bool ImplHandleIMEStartComposition( HWND hWnd )
+{
+ bool bDef = true;
+
+ ImplSalYieldMutexAcquireWithWait();
+
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( pFrame )
+ {
+ HIMC hIMC = ImmGetContext( hWnd );
+ if ( hIMC )
+ {
+ ImplUpdateIMECursorPos( pFrame, hIMC );
+ ImmReleaseContext( hWnd, hIMC );
+ }
+
+ if ( pFrame->mbHandleIME )
+ {
+ if ( pFrame->mbAtCursorIME )
+ bDef = false;
+ }
+ }
+
+ ImplSalYieldMutexRelease();
+
+ return bDef;
+}
+
+static bool ImplHandleIMECompositionInput( WinSalFrame* pFrame,
+ HIMC hIMC, LPARAM lParam )
+{
+ bool bDef = true;
+
+ // Init Event
+ SalExtTextInputEvent aEvt;
+ aEvt.mpTextAttr = nullptr;
+ aEvt.mnCursorPos = 0;
+ aEvt.mnCursorFlags = 0;
+
+ // If we get a result string, then we handle this input
+ if ( lParam & GCS_RESULTSTR )
+ {
+ bDef = false;
+
+ LONG nTextLen = ImmGetCompositionStringW( hIMC, GCS_RESULTSTR, nullptr, 0 ) / sizeof( WCHAR );
+ if ( nTextLen >= 0 )
+ {
+ auto pTextBuf = std::make_unique<WCHAR[]>(nTextLen);
+ ImmGetCompositionStringW( hIMC, GCS_RESULTSTR, pTextBuf.get(), nTextLen*sizeof( WCHAR ) );
+ aEvt.maText = OUString( o3tl::toU(pTextBuf.get()), static_cast<sal_Int32>(nTextLen) );
+ }
+
+ aEvt.mnCursorPos = aEvt.maText.getLength();
+ pFrame->CallCallback( SalEvent::ExtTextInput, &aEvt );
+ pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+ ImplUpdateIMECursorPos( pFrame, hIMC );
+ }
+
+ // If the IME doesn't support OnSpot input, then there is nothing to do
+ if ( !pFrame->mbAtCursorIME )
+ return !bDef;
+
+ // If we get new Composition data, then we handle this new input
+ if ( (lParam & (GCS_COMPSTR | GCS_COMPATTR)) ||
+ ((lParam & GCS_CURSORPOS) && !(lParam & GCS_RESULTSTR)) )
+ {
+ bDef = false;
+
+ std::unique_ptr<ExtTextInputAttr[]> pSalAttrAry;
+ LONG nTextLen = ImmGetCompositionStringW( hIMC, GCS_COMPSTR, nullptr, 0 ) / sizeof( WCHAR );
+ if ( nTextLen > 0 )
+ {
+ {
+ auto pTextBuf = std::make_unique<WCHAR[]>(nTextLen);
+ ImmGetCompositionStringW( hIMC, GCS_COMPSTR, pTextBuf.get(), nTextLen*sizeof( WCHAR ) );
+ aEvt.maText = OUString( o3tl::toU(pTextBuf.get()), static_cast<sal_Int32>(nTextLen) );
+ }
+
+ std::unique_ptr<BYTE[]> pAttrBuf;
+ LONG nAttrLen = ImmGetCompositionStringW( hIMC, GCS_COMPATTR, nullptr, 0 );
+ if ( nAttrLen > 0 )
+ {
+ pAttrBuf.reset(new BYTE[nAttrLen]);
+ ImmGetCompositionStringW( hIMC, GCS_COMPATTR, pAttrBuf.get(), nAttrLen );
+ }
+
+ if ( pAttrBuf )
+ {
+ sal_Int32 nTextLen2 = aEvt.maText.getLength();
+ pSalAttrAry.reset(new ExtTextInputAttr[nTextLen2]);
+ memset( pSalAttrAry.get(), 0, nTextLen2*sizeof( sal_uInt16 ) );
+ for( sal_Int32 i = 0; (i < nTextLen2) && (i < nAttrLen); i++ )
+ {
+ BYTE nWinAttr = pAttrBuf.get()[i];
+ ExtTextInputAttr nSalAttr;
+ if ( nWinAttr == ATTR_TARGET_CONVERTED )
+ {
+ nSalAttr = ExtTextInputAttr::BoldUnderline;
+ aEvt.mnCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
+ }
+ else if ( nWinAttr == ATTR_CONVERTED )
+ nSalAttr = ExtTextInputAttr::DashDotUnderline;
+ else if ( nWinAttr == ATTR_TARGET_NOTCONVERTED )
+ nSalAttr = ExtTextInputAttr::Highlight;
+ else if ( nWinAttr == ATTR_INPUT_ERROR )
+ nSalAttr = ExtTextInputAttr::RedText | ExtTextInputAttr::DottedUnderline;
+ else /* ( nWinAttr == ATTR_INPUT ) */
+ nSalAttr = ExtTextInputAttr::DottedUnderline;
+ pSalAttrAry[i] = nSalAttr;
+ }
+
+ aEvt.mpTextAttr = pSalAttrAry.get();
+ }
+ }
+
+ // Only when we get new composition data, we must send this event
+ if ( (nTextLen > 0) || !(lParam & GCS_RESULTSTR) )
+ {
+ // End the mode, if the last character is deleted
+ if ( !nTextLen )
+ {
+ pFrame->CallCallback( SalEvent::ExtTextInput, &aEvt );
+ pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+ }
+ else
+ {
+ // Because Cursor-Position and DeltaStart never updated
+ // from the korean input engine, we must handle this here
+ if ( lParam & CS_INSERTCHAR )
+ {
+ aEvt.mnCursorPos = nTextLen;
+ if ( aEvt.mnCursorPos && (lParam & CS_NOMOVECARET) )
+ aEvt.mnCursorPos--;
+ }
+ else
+ aEvt.mnCursorPos = LOWORD( ImmGetCompositionStringW( hIMC, GCS_CURSORPOS, nullptr, 0 ) );
+
+ if ( pFrame->mbCandidateMode )
+ aEvt.mnCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
+ if ( lParam & CS_NOMOVECARET )
+ aEvt.mnCursorFlags |= EXTTEXTINPUT_CURSOR_OVERWRITE;
+
+ pFrame->CallCallback( SalEvent::ExtTextInput, &aEvt );
+ }
+ ImplUpdateIMECursorPos( pFrame, hIMC );
+ }
+ }
+
+ return !bDef;
+}
+
+static bool ImplHandleIMEComposition( HWND hWnd, LPARAM lParam )
+{
+ bool bDef = true;
+ ImplSalYieldMutexAcquireWithWait();
+
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( pFrame && (!lParam || (lParam & GCS_RESULTSTR)) )
+ {
+ // reset the background mode for each text input,
+ // as some tools such as RichWin may have changed it
+ if ( pFrame->mpLocalGraphics &&
+ pFrame->mpLocalGraphics->getHDC() )
+ SetBkMode( pFrame->mpLocalGraphics->getHDC(), TRANSPARENT );
+ }
+
+ if ( pFrame && pFrame->mbHandleIME )
+ {
+ if ( !lParam )
+ {
+ SalExtTextInputEvent aEvt;
+ aEvt.mpTextAttr = nullptr;
+ aEvt.mnCursorPos = 0;
+ aEvt.mnCursorFlags = 0;
+ pFrame->CallCallback( SalEvent::ExtTextInput, &aEvt );
+ pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+ }
+ else if ( lParam & (GCS_RESULTSTR | GCS_COMPSTR | GCS_COMPATTR | GCS_CURSORPOS) )
+ {
+ HIMC hIMC = ImmGetContext( hWnd );
+ if ( hIMC )
+ {
+ if ( ImplHandleIMECompositionInput( pFrame, hIMC, lParam ) )
+ bDef = false;
+
+ ImmReleaseContext( hWnd, hIMC );
+ }
+ }
+ }
+
+ ImplSalYieldMutexRelease();
+ return bDef;
+}
+
+static bool ImplHandleIMEEndComposition( HWND hWnd )
+{
+ bool bDef = true;
+
+ ImplSalYieldMutexAcquireWithWait();
+
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( pFrame && pFrame->mbHandleIME )
+ {
+ if ( pFrame->mbAtCursorIME )
+ {
+ pFrame->mbCandidateMode = false;
+ bDef = false;
+ }
+ }
+
+ ImplSalYieldMutexRelease();
+
+ return bDef;
+}
+
+static bool ImplHandleAppCommand( HWND hWnd, LPARAM lParam, LRESULT & nRet )
+{
+ MediaCommand nCommand;
+ switch( GET_APPCOMMAND_LPARAM(lParam) )
+ {
+ case APPCOMMAND_MEDIA_CHANNEL_DOWN: nCommand = MediaCommand::ChannelDown; break;
+ case APPCOMMAND_MEDIA_CHANNEL_UP: nCommand = MediaCommand::ChannelUp; break;
+ case APPCOMMAND_MEDIA_NEXTTRACK: nCommand = MediaCommand::NextTrack; break;
+ case APPCOMMAND_MEDIA_PAUSE: nCommand = MediaCommand::Pause; break;
+ case APPCOMMAND_MEDIA_PLAY: nCommand = MediaCommand::Play; break;
+ case APPCOMMAND_MEDIA_PLAY_PAUSE: nCommand = MediaCommand::PlayPause; break;
+ case APPCOMMAND_MEDIA_PREVIOUSTRACK: nCommand = MediaCommand::PreviousTrack; break;
+ case APPCOMMAND_MEDIA_RECORD: nCommand = MediaCommand::Record; break;
+ case APPCOMMAND_MEDIA_REWIND: nCommand = MediaCommand::Rewind; break;
+ case APPCOMMAND_MEDIA_STOP: nCommand = MediaCommand::Stop; break;
+ case APPCOMMAND_MIC_ON_OFF_TOGGLE: nCommand = MediaCommand::MicOnOffToggle; break;
+ case APPCOMMAND_MICROPHONE_VOLUME_DOWN: nCommand = MediaCommand::MicrophoneVolumeDown; break;
+ case APPCOMMAND_MICROPHONE_VOLUME_MUTE: nCommand = MediaCommand::MicrophoneVolumeMute; break;
+ case APPCOMMAND_MICROPHONE_VOLUME_UP: nCommand = MediaCommand::MicrophoneVolumeUp; break;
+ case APPCOMMAND_VOLUME_DOWN: nCommand = MediaCommand::VolumeDown; break;
+ case APPCOMMAND_VOLUME_MUTE: nCommand = MediaCommand::VolumeMute; break;
+ case APPCOMMAND_VOLUME_UP: nCommand = MediaCommand::VolumeUp; break;
+ default:
+ return false;
+ }
+
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ vcl::Window *pWindow = pFrame ? pFrame->GetWindow() : nullptr;
+
+ if( pWindow )
+ {
+ const Point aPoint;
+ CommandMediaData aMediaData(nCommand);
+ CommandEvent aCEvt( aPoint, CommandEventId::Media, false, &aMediaData );
+ NotifyEvent aNCmdEvt( NotifyEventType::COMMAND, pWindow, &aCEvt );
+
+ if ( !ImplCallPreNotify( aNCmdEvt ) )
+ {
+ pWindow->Command( aCEvt );
+ nRet = 1;
+ return !aMediaData.GetPassThroughToOS();
+ }
+ }
+
+ return false;
+}
+
+static void ImplHandleIMENotify( HWND hWnd, WPARAM wParam )
+{
+ if ( wParam == WPARAM(IMN_OPENCANDIDATE) )
+ {
+ ImplSalYieldMutexAcquireWithWait();
+
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( pFrame && pFrame->mbHandleIME &&
+ pFrame->mbAtCursorIME )
+ {
+ // we want to hide the cursor
+ pFrame->mbCandidateMode = true;
+ ImplHandleIMEComposition( hWnd, GCS_CURSORPOS );
+
+ HWND hWnd2 = pFrame->mhWnd;
+ HIMC hIMC = ImmGetContext( hWnd2 );
+ if ( hIMC )
+ {
+ LONG nBufLen = ImmGetCompositionStringW( hIMC, GCS_COMPSTR, nullptr, 0 );
+ if ( nBufLen >= 1 )
+ {
+ SalExtTextInputPosEvent aPosEvt;
+ pFrame->CallCallback( SalEvent::ExtTextInputPos, &aPosEvt );
+
+ // Vertical !!!
+ CANDIDATEFORM aForm;
+ aForm.dwIndex = 0;
+ aForm.dwStyle = CFS_EXCLUDE;
+ aForm.ptCurrentPos.x = aPosEvt.mnX;
+ aForm.ptCurrentPos.y = aPosEvt.mnY+1;
+ aForm.rcArea.left = aPosEvt.mnX;
+ aForm.rcArea.top = aPosEvt.mnY;
+ aForm.rcArea.right = aForm.rcArea.left+aPosEvt.mnExtWidth+1;
+ aForm.rcArea.bottom = aForm.rcArea.top+aPosEvt.mnHeight+1;
+ ImmSetCandidateWindow( hIMC, &aForm );
+ }
+
+ ImmReleaseContext( hWnd2, hIMC );
+ }
+ }
+
+ ImplSalYieldMutexRelease();
+ }
+ else if ( wParam == WPARAM(IMN_CLOSECANDIDATE) )
+ {
+ ImplSalYieldMutexAcquireWithWait();
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ if ( pFrame )
+ pFrame->mbCandidateMode = false;
+ ImplSalYieldMutexRelease();
+ }
+}
+
+static bool
+ImplHandleGetObject(HWND hWnd, LPARAM lParam, WPARAM wParam, LRESULT & nRet)
+{
+ uno::Reference<accessibility::XMSAAService> xMSAA;
+ if (ImplSalYieldMutexTryToAcquire())
+ {
+ if (!Application::GetSettings().GetMiscSettings().GetEnableATToolSupport())
+ {
+ // IA2 should be enabled automatically
+ AllSettings aSettings = Application::GetSettings();
+ MiscSettings aMisc = aSettings.GetMiscSettings();
+ aMisc.SetEnableATToolSupport(true);
+ // The above is enough, since aMisc changes the same shared ImplMiscData as used in global
+ // settings, so no need to call aSettings.SetMiscSettings and Application::SetSettings
+
+ if (!Application::GetSettings().GetMiscSettings().GetEnableATToolSupport())
+ return false; // locked down somehow ?
+ }
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // Make sure to launch Accessibility only the following criteria are satisfied
+ // to avoid RFT interrupts regular accessibility processing
+ if ( !pSVData->mxAccessBridge.is() )
+ {
+ if( !InitAccessBridge() )
+ return false;
+ }
+ xMSAA.set(pSVData->mxAccessBridge, uno::UNO_QUERY);
+ ImplSalYieldMutexRelease();
+ }
+ else
+ { // tdf#155794: access without locking: hopefully this should be fine
+ // as the bridge is typically inited in Desktop::Main() already and the
+ // WM_GETOBJECT is received only on the main thread and by the time in
+ // VCL shutdown when ImplSvData dies there should not be Windows any
+ // more that could receive messages.
+ xMSAA.set(ImplGetSVData()->mxAccessBridge, uno::UNO_QUERY);
+ }
+
+ if ( xMSAA.is() )
+ {
+ sal_Int32 lParam32 = static_cast<sal_Int32>(lParam);
+ sal_uInt32 wParam32 = static_cast<sal_uInt32>(wParam);
+
+ // mhOnSetTitleWnd not set to reasonable value anywhere...
+ if ( lParam32 == OBJID_CLIENT )
+ {
+ nRet = xMSAA->getAccObjectPtr(
+ reinterpret_cast<sal_Int64>(hWnd), lParam32, wParam32);
+ if (nRet != 0)
+ return true;
+ }
+ }
+ return false;
+}
+
+static LRESULT ImplHandleIMEReconvertString( HWND hWnd, LPARAM lParam )
+{
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ LPRECONVERTSTRING pReconvertString = reinterpret_cast<LPRECONVERTSTRING>(lParam);
+ LRESULT nRet = 0;
+ SalSurroundingTextRequestEvent aEvt;
+ aEvt.maText.clear();
+ aEvt.mnStart = aEvt.mnEnd = 0;
+
+ UINT nImeProps = ImmGetProperty( GetKeyboardLayout( 0 ), IGP_SETCOMPSTR );
+ if( (nImeProps & SCS_CAP_SETRECONVERTSTRING) == 0 )
+ {
+ // This IME does not support reconversion.
+ return 0;
+ }
+
+ if( !pReconvertString )
+ {
+ // The first call for reconversion.
+ pFrame->CallCallback( SalEvent::StartReconversion, nullptr );
+
+ // Retrieve the surrounding text from the focused control.
+ pFrame->CallCallback( SalEvent::SurroundingTextRequest, &aEvt );
+
+ if( aEvt.maText.isEmpty())
+ {
+ return 0;
+ }
+
+ nRet = sizeof(RECONVERTSTRING) + (aEvt.maText.getLength() + 1) * sizeof(WCHAR);
+ }
+ else
+ {
+ // The second call for reconversion.
+
+ // Retrieve the surrounding text from the focused control.
+ pFrame->CallCallback( SalEvent::SurroundingTextRequest, &aEvt );
+ nRet = sizeof(RECONVERTSTRING) + (aEvt.maText.getLength() + 1) * sizeof(WCHAR);
+
+ pReconvertString->dwStrOffset = sizeof(RECONVERTSTRING);
+ pReconvertString->dwStrLen = aEvt.maText.getLength();
+ pReconvertString->dwCompStrOffset = aEvt.mnStart * sizeof(WCHAR);
+ pReconvertString->dwCompStrLen = aEvt.mnEnd - aEvt.mnStart;
+ pReconvertString->dwTargetStrOffset = pReconvertString->dwCompStrOffset;
+ pReconvertString->dwTargetStrLen = pReconvertString->dwCompStrLen;
+
+ memcpy( pReconvertString + 1, aEvt.maText.getStr(), (aEvt.maText.getLength() + 1) * sizeof(WCHAR) );
+ }
+
+ // just return the required size of buffer to reconvert.
+ return nRet;
+}
+
+static LRESULT ImplHandleIMEConfirmReconvertString( HWND hWnd, LPARAM lParam )
+{
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ LPRECONVERTSTRING pReconvertString = reinterpret_cast<LPRECONVERTSTRING>(lParam);
+ SalSurroundingTextRequestEvent aEvt;
+ aEvt.maText.clear();
+ aEvt.mnStart = aEvt.mnEnd = 0;
+
+ pFrame->CallCallback( SalEvent::SurroundingTextRequest, &aEvt );
+
+ sal_uLong nTmpStart = pReconvertString->dwCompStrOffset / sizeof(WCHAR);
+ sal_uLong nTmpEnd = nTmpStart + pReconvertString->dwCompStrLen;
+
+ if( nTmpStart != aEvt.mnStart || nTmpEnd != aEvt.mnEnd )
+ {
+ SalSurroundingTextSelectionChangeEvent aSelEvt { nTmpStart, nTmpEnd };
+ pFrame->CallCallback( SalEvent::SurroundingTextSelectionChange, &aSelEvt );
+ }
+
+ return TRUE;
+}
+
+static LRESULT ImplHandleIMEQueryCharPosition( HWND hWnd, LPARAM lParam ) {
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ PIMECHARPOSITION pQueryCharPosition = reinterpret_cast<PIMECHARPOSITION>(lParam);
+ if ( pQueryCharPosition->dwSize < sizeof(IMECHARPOSITION) )
+ return FALSE;
+
+ SalQueryCharPositionEvent aEvt;
+ aEvt.mbValid = false;
+ aEvt.mnCharPos = pQueryCharPosition->dwCharPos;
+
+ pFrame->CallCallback( SalEvent::QueryCharPosition, &aEvt );
+
+ if ( !aEvt.mbValid )
+ return FALSE;
+
+ if ( aEvt.mbVertical )
+ {
+ // For vertical writing, the base line is left edge of the rectangle
+ // and the target position is top-right corner.
+ pQueryCharPosition->pt.x = aEvt.maCursorBound.getX() + aEvt.maCursorBound.GetWidth();
+ pQueryCharPosition->pt.y = aEvt.maCursorBound.getY();
+ pQueryCharPosition->cLineHeight = aEvt.maCursorBound.GetWidth();
+ }
+ else
+ {
+ // For horizontal writing, the base line is the bottom edge of the rectangle.
+ // and the target position is top-left corner.
+ pQueryCharPosition->pt.x = aEvt.maCursorBound.getX();
+ pQueryCharPosition->pt.y = aEvt.maCursorBound.getY();
+ pQueryCharPosition->cLineHeight = aEvt.maCursorBound.GetHeight();
+ }
+
+ // Currently not supported but many IMEs usually ignore them.
+ pQueryCharPosition->rcDocument.left = 0;
+ pQueryCharPosition->rcDocument.top = 0;
+ pQueryCharPosition->rcDocument.right = 0;
+ pQueryCharPosition->rcDocument.bottom = 0;
+
+ return TRUE;
+}
+
+void SalTestMouseLeave()
+{
+ SalData* pSalData = GetSalData();
+
+ if ( pSalData->mhWantLeaveMsg && !::GetCapture() )
+ {
+ POINT aPt;
+ GetCursorPos( &aPt );
+
+ // a one item cache, because this function is sometimes hot - if the cursor has not moved, then
+ // no need to call WindowFromPoint
+ static POINT cachedPoint;
+ if (cachedPoint.x == aPt.x && cachedPoint.y == aPt.y)
+ return;
+ cachedPoint = aPt;
+
+ if ( pSalData->mhWantLeaveMsg != WindowFromPoint( aPt ) )
+ SendMessageW( pSalData->mhWantLeaveMsg, SAL_MSG_MOUSELEAVE, 0, MAKELPARAM( aPt.x, aPt.y ) );
+ }
+}
+
+static bool ImplSalWheelMousePos( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam ,
+ LRESULT& rResult )
+{
+ POINT aPt;
+ POINT aScreenPt;
+ aScreenPt.x = static_cast<short>(LOWORD( lParam ));
+ aScreenPt.y = static_cast<short>(HIWORD( lParam ));
+ // find child window that is at this position
+ HWND hChildWnd;
+ HWND hWheelWnd = hWnd;
+ do
+ {
+ hChildWnd = hWheelWnd;
+ aPt = aScreenPt;
+ ScreenToClient( hChildWnd, &aPt );
+ hWheelWnd = ChildWindowFromPointEx( hChildWnd, aPt, CWP_SKIPINVISIBLE | CWP_SKIPTRANSPARENT );
+ }
+ while ( hWheelWnd && (hWheelWnd != hChildWnd) );
+ if ( hWheelWnd && (hWheelWnd != hWnd) &&
+ (hWheelWnd != ::GetFocus()) && IsWindowEnabled( hWheelWnd ) )
+ {
+ rResult = SendMessageW( hWheelWnd, nMsg, wParam, lParam );
+ return false;
+ }
+
+ return true;
+}
+
+static LRESULT CALLBACK SalFrameWndProc( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam, bool& rDef )
+{
+ LRESULT nRet = 0;
+ static bool bInWheelMsg = false;
+ static bool bInQueryEnd = false;
+
+ SAL_INFO("vcl.gdi.wndproc", "SalFrameWndProc(nMsg=" << nMsg << ", wParam=" << wParam << ", lParam=" << lParam << ")");
+
+ // By WM_CREATE we connect the frame with the window handle
+ if ( nMsg == WM_CREATE )
+ {
+ // Save Window-Instance in Windowhandle
+ // Can also be used for the A-Version, because the struct
+ // to access lpCreateParams is the same structure
+ CREATESTRUCTW* pStruct = reinterpret_cast<CREATESTRUCTW*>(lParam);
+ WinSalFrame* pFrame = static_cast<WinSalFrame*>(pStruct->lpCreateParams);
+ if ( pFrame != nullptr )
+ {
+ SetWindowPtr( hWnd, pFrame );
+
+ UpdateDarkMode(hWnd);
+
+ // Set HWND already here, as data might be used already
+ // when messages are being sent by CreateWindow()
+ pFrame->mhWnd = hWnd;
+ pFrame->maSysData.hWnd = hWnd;
+ }
+ return 0;
+ }
+
+ ImplSVData* pSVData = ImplGetSVData();
+ // #i72707# TODO: the mbDeInit check will not be needed
+ // once all windows that are not properly closed on exit got fixed
+ if( pSVData->mbDeInit )
+ return 0;
+
+ if ( WM_USER_SYSTEM_WINDOW_ACTIVATED == nMsg )
+ {
+ ImplHideSplash();
+ return 0;
+ }
+
+ switch( nMsg )
+ {
+ case WM_MOUSEMOVE:
+ case WM_LBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ case WM_LBUTTONUP:
+ case WM_MBUTTONUP:
+ case WM_RBUTTONUP:
+ case WM_NCMOUSEMOVE:
+ case SAL_MSG_MOUSELEAVE:
+ ImplSalYieldMutexAcquireWithWait();
+ rDef = !ImplHandleMouseMsg( hWnd, nMsg, wParam, lParam );
+ ImplSalYieldMutexRelease();
+ break;
+
+ case WM_NCLBUTTONDOWN:
+ case WM_NCMBUTTONDOWN:
+ case WM_NCRBUTTONDOWN:
+ ImplSalYieldMutexAcquireWithWait();
+ ImplCallClosePopupsHdl( hWnd ); // close popups...
+ ImplSalYieldMutexRelease();
+ break;
+
+ case WM_MOUSEACTIVATE:
+ if ( LOWORD( lParam ) == HTCLIENT )
+ {
+ ImplSalYieldMutexAcquireWithWait();
+ nRet = LRESULT(ImplHandleMouseActivateMsg( hWnd ));
+ ImplSalYieldMutexRelease();
+ if ( nRet )
+ {
+ nRet = MA_NOACTIVATE;
+ rDef = false;
+ }
+ }
+ break;
+
+ case WM_KEYDOWN:
+ case WM_KEYUP:
+ case WM_DEADCHAR:
+ case WM_CHAR:
+ case WM_UNICHAR: // MCD, 2003-01-13, Support for WM_UNICHAR & Keyman 6.0
+ case WM_SYSKEYDOWN:
+ case WM_SYSKEYUP:
+ case WM_SYSCHAR:
+ ImplSalYieldMutexAcquireWithWait();
+ rDef = !ImplHandleKeyMsg( hWnd, nMsg, wParam, lParam, nRet );
+ ImplSalYieldMutexRelease();
+ break;
+
+ case WM_MOUSEWHEEL:
+ case WM_MOUSEHWHEEL:
+ // protect against recursion, in case the message is returned
+ // by IE or the external window
+ if ( !bInWheelMsg )
+ {
+ bInWheelMsg = true;
+ rDef = !ImplHandleWheelMsg( hWnd, nMsg, wParam, lParam );
+ // If we did not process the message, re-check if here is a
+ // connected (?) window that we have to notify.
+ if ( rDef )
+ rDef = ImplSalWheelMousePos( hWnd, nMsg, wParam, lParam, nRet );
+ bInWheelMsg = false;
+ }
+ break;
+
+ case WM_COMMAND:
+ ImplSalYieldMutexAcquireWithWait();
+ rDef = !ImplHandleCommand( hWnd, wParam, lParam );
+ ImplSalYieldMutexRelease();
+ break;
+
+ case WM_INITMENUPOPUP:
+ ImplSalYieldMutexAcquireWithWait();
+ rDef = !ImplHandleMenuActivate( hWnd, wParam, lParam );
+ ImplSalYieldMutexRelease();
+ break;
+
+ case WM_MENUSELECT:
+ ImplSalYieldMutexAcquireWithWait();
+ rDef = !ImplHandleMenuSelect( hWnd, wParam, lParam );
+ ImplSalYieldMutexRelease();
+ break;
+
+ case WM_SYSCOMMAND:
+ ImplSalYieldMutexAcquireWithWait();
+ nRet = LRESULT(ImplHandleSysCommand( hWnd, wParam, lParam ));
+ ImplSalYieldMutexRelease();
+ if ( nRet )
+ rDef = false;
+ break;
+
+ case WM_MENUCHAR:
+ nRet = ImplMenuChar( hWnd, wParam, lParam );
+ if( nRet )
+ rDef = false;
+ break;
+
+ case WM_MEASUREITEM:
+ nRet = ImplMeasureItem(hWnd, wParam, lParam);
+ if( nRet )
+ rDef = false;
+ break;
+
+ case WM_DRAWITEM:
+ nRet = ImplDrawItem(hWnd, wParam, lParam);
+ if( nRet )
+ rDef = false;
+ break;
+
+ case WM_MOVE:
+ ImplHandleMoveMsg(hWnd, lParam);
+ rDef = false;
+ break;
+ case SAL_MSG_POSTMOVE:
+ ImplCallMoveHdl(hWnd);
+ rDef = false;
+ break;
+ case WM_SIZE:
+ ImplHandleSizeMsg(hWnd, wParam, lParam);
+ rDef = false;
+ break;
+ case SAL_MSG_POSTCALLSIZE:
+ ImplCallSizeHdl( hWnd );
+ rDef = false;
+ break;
+
+ case WM_GETMINMAXINFO:
+ if ( ImplHandleMinMax( hWnd, lParam ) )
+ rDef = false;
+ break;
+
+ case WM_ERASEBKGND:
+ nRet = 1;
+ rDef = false;
+ break;
+ case WM_PAINT:
+ ImplHandlePaintMsg( hWnd );
+ rDef = false;
+ break;
+ case SAL_MSG_POSTPAINT:
+ ImplHandlePostPaintMsg( hWnd, reinterpret_cast<RECT*>(wParam) );
+ rDef = false;
+ break;
+
+ case SAL_MSG_FORCEPALETTE:
+ ImplHandleForcePalette( hWnd );
+ rDef = false;
+ break;
+
+ case WM_QUERYNEWPALETTE:
+ case SAL_MSG_POSTQUERYNEWPAL:
+ nRet = ImplHandlePalette( true, hWnd, nMsg, wParam, lParam, rDef );
+ break;
+
+ case WM_ACTIVATE:
+ // Getting activated, we also want to set our palette.
+ // We do this in Activate, so that other external child windows
+ // can overwrite our palette. Thus our palette is set only once
+ // and not recursively, as at all other places it is set only as
+ // the background palette.
+ if ( LOWORD( wParam ) != WA_INACTIVE )
+ SendMessageW( hWnd, SAL_MSG_FORCEPALETTE, 0, 0 );
+ break;
+
+ case WM_ENABLE:
+ // #95133# a system dialog is opened/closed, using our app window as parent
+ {
+ WinSalFrame* pFrame = GetWindowPtr( hWnd );
+ vcl::Window *pWin = nullptr;
+ if( pFrame )
+ pWin = pFrame->GetWindow();
+
+ if( !wParam )
+ {
+ pSVData->maAppData.mnModalMode++;
+
+ ImplHideSplash();
+ if( pWin )
+ {
+ pWin->EnableInput( false, nullptr );
+ pWin->IncModalCount(); // #106303# support frame based modal count
+ }
+ }
+ else
+ {
+ ImplGetSVData()->maAppData.mnModalMode--;
+ if( pWin )
+ {
+ pWin->EnableInput( true, nullptr );
+ pWin->DecModalCount(); // #106303# support frame based modal count
+ }
+ }
+ }
+ break;
+
+ case WM_KILLFOCUS:
+ DestroyCaret();
+ [[fallthrough]];
+ case WM_SETFOCUS:
+ case SAL_MSG_POSTFOCUS:
+ ImplHandleFocusMsg( hWnd );
+ rDef = false;
+ break;
+
+ case WM_CLOSE:
+ ImplHandleCloseMsg( hWnd );
+ rDef = false;
+ break;
+
+ case WM_QUERYENDSESSION:
+ if( !bInQueryEnd )
+ {
+ // handle queryendsession only once
+ bInQueryEnd = true;
+ nRet = LRESULT(!ImplHandleShutDownMsg( hWnd ));
+ rDef = false;
+
+ // Issue #16314#: ImplHandleShutDownMsg causes a PostMessage in case of allowing shutdown.
+ // This posted message was never processed and cause Windows XP to hang after log off
+ // if there are multiple sessions and the current session wasn't the first one started.
+ // So if shutdown is allowed we assume that a post message was done and retrieve all
+ // messages in the message queue and dispatch them before we return control to the system.
+
+ if ( nRet )
+ {
+ SolarMutexGuard aGuard;
+ while ( Application::Reschedule( true ) );
+ }
+ }
+ else
+ {
+ ImplSalYieldMutexAcquireWithWait();
+ ImplSalYieldMutexRelease();
+ rDef = true;
+ }
+ break;
+
+ case WM_ENDSESSION:
+ if( !wParam )
+ bInQueryEnd = false; // no shutdown: allow query again
+ nRet = FALSE;
+ rDef = false;
+ break;
+
+ case WM_DISPLAYCHANGE:
+ case WM_SETTINGCHANGE:
+ case WM_DEVMODECHANGE:
+ case WM_FONTCHANGE:
+ case WM_SYSCOLORCHANGE:
+ case WM_TIMECHANGE:
+ ImplHandleSettingsChangeMsg( hWnd, nMsg, wParam, lParam );
+ break;
+
+ case WM_THEMECHANGED:
+ GetSalData()->mbThemeChanged = true;
+ break;
+
+ case SAL_MSG_USEREVENT:
+ ImplHandleUserEvent( hWnd, lParam );
+ rDef = false;
+ break;
+
+ case SAL_MSG_CAPTUREMOUSE:
+ SetCapture( hWnd );
+ rDef = false;
+ break;
+ case SAL_MSG_RELEASEMOUSE:
+ if ( ::GetCapture() == hWnd )
+ ReleaseCapture();
+ rDef = false;
+ break;
+ case SAL_MSG_TOTOP:
+ ImplSalToTop( hWnd, static_cast<SalFrameToTop>(wParam) );
+ rDef = false;
+ break;
+ case SAL_MSG_SHOW:
+ ImplSalShow( hWnd, static_cast<bool>(wParam), static_cast<bool>(lParam) );
+ rDef = false;
+ break;
+ case SAL_MSG_SETINPUTCONTEXT:
+ ImplSalFrameSetInputContext( hWnd, reinterpret_cast<const SalInputContext*>(lParam) );
+ rDef = false;
+ break;
+ case SAL_MSG_ENDEXTTEXTINPUT:
+ ImplSalFrameEndExtTextInput( hWnd, static_cast<EndExtTextInputFlags>(wParam) );
+ rDef = false;
+ break;
+
+ case WM_INPUTLANGCHANGE:
+ ImplHandleInputLangChange( hWnd, wParam, lParam );
+ break;
+
+ case WM_IME_CHAR:
+ // #103487#, some IMEs (eg, those that do not work onspot)
+ // may send WM_IME_CHAR instead of WM_IME_COMPOSITION
+ // we just handle it like a WM_CHAR message - seems to work fine
+ ImplSalYieldMutexAcquireWithWait();
+ rDef = !ImplHandleKeyMsg( hWnd, WM_CHAR, wParam, lParam, nRet );
+ ImplSalYieldMutexRelease();
+ break;
+
+ case WM_IME_STARTCOMPOSITION:
+ rDef = ImplHandleIMEStartComposition( hWnd );
+ break;
+
+ case WM_IME_COMPOSITION:
+ rDef = ImplHandleIMEComposition( hWnd, lParam );
+ break;
+
+ case WM_IME_ENDCOMPOSITION:
+ rDef = ImplHandleIMEEndComposition( hWnd );
+ break;
+
+ case WM_IME_NOTIFY:
+ ImplHandleIMENotify( hWnd, wParam );
+ break;
+
+ case WM_GETOBJECT:
+ // tdf#155794: this must complete without taking SolarMutex
+ if ( ImplHandleGetObject( hWnd, lParam, wParam, nRet ) )
+ {
+ rDef = false;
+ }
+ break;
+
+ case WM_APPCOMMAND:
+ if( ImplHandleAppCommand( hWnd, lParam, nRet ) )
+ {
+ rDef = false;
+ }
+ break;
+ case WM_IME_REQUEST:
+ if ( static_cast<sal_uIntPtr>(wParam) == IMR_RECONVERTSTRING )
+ {
+ nRet = ImplHandleIMEReconvertString( hWnd, lParam );
+ rDef = false;
+ }
+ else if( static_cast<sal_uIntPtr>(wParam) == IMR_CONFIRMRECONVERTSTRING )
+ {
+ nRet = ImplHandleIMEConfirmReconvertString( hWnd, lParam );
+ rDef = false;
+ }
+ else if ( static_cast<sal_uIntPtr>(wParam) == IMR_QUERYCHARPOSITION )
+ {
+ if ( ImplSalYieldMutexTryToAcquire() )
+ {
+ nRet = ImplHandleIMEQueryCharPosition( hWnd, lParam );
+ ImplSalYieldMutexRelease();
+ }
+ else
+ nRet = FALSE;
+ rDef = false;
+ }
+ break;
+ }
+
+ return nRet;
+}
+
+LRESULT CALLBACK SalFrameWndProcW( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam )
+{
+ bool bDef = true;
+ LRESULT nRet = 0;
+ __try
+ {
+ nRet = SalFrameWndProc( hWnd, nMsg, wParam, lParam, bDef );
+ }
+ __except(WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(GetExceptionCode(), GetExceptionInformation()))
+ {
+ }
+
+ if ( bDef )
+ nRet = DefWindowProcW( hWnd, nMsg, wParam, lParam );
+ return nRet;
+}
+
+bool ImplHandleGlobalMsg( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam, LRESULT& rlResult )
+{
+ // handle all messages concerning all frames so they get processed only once
+ // Must work for Unicode and none Unicode
+ bool bResult = false;
+ if ( (nMsg == WM_PALETTECHANGED) || (nMsg == SAL_MSG_POSTPALCHANGED) )
+ {
+ bResult = true;
+ rlResult = ImplHandlePalette( false, hWnd, nMsg, wParam, lParam, bResult );
+ }
+ else if( nMsg == WM_DISPLAYCHANGE )
+ {
+ WinSalSystem* pSys = static_cast<WinSalSystem*>(ImplGetSalSystem());
+ if( pSys )
+ pSys->clearMonitors();
+ bResult = (pSys != nullptr);
+ }
+ return bResult;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/window/salmenu.cxx b/vcl/win/window/salmenu.cxx
new file mode 100644
index 0000000000..91a15284ae
--- /dev/null
+++ b/vcl/win/window/salmenu.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 <svsys.h>
+
+#include <vcl/menu.hxx>
+#include <vcl/sysdata.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <o3tl/safeint.hxx>
+
+#include <win/wincomp.hxx>
+#include <win/saldata.hxx>
+#include <win/salinst.h>
+#include <win/salframe.h>
+#include <win/salmenu.h>
+
+#include <salgdi.hxx>
+
+static DWORD myerr=0;
+
+bool SalData::IsKnownMenuHandle( HMENU hMenu )
+{
+ if( mhMenuSet.find( hMenu ) == mhMenuSet.end() )
+ return false;
+ else
+ return true;
+}
+
+// WinSalInst factory methods
+
+std::unique_ptr<SalMenu> WinSalInstance::CreateMenu( bool bMenuBar, Menu* )
+{
+ WinSalMenu *pSalMenu = new WinSalMenu();
+
+ pSalMenu->mbMenuBar = bMenuBar;
+ pSalMenu->mhWnd = nullptr;
+ if( bMenuBar )
+ pSalMenu->mhMenu = ::CreateMenu();
+ else
+ pSalMenu->mhMenu = ::CreatePopupMenu();
+
+ if( pSalMenu->mhMenu )
+ GetSalData()->mhMenuSet.insert( pSalMenu->mhMenu );
+
+ return std::unique_ptr<SalMenu>(pSalMenu);
+}
+
+std::unique_ptr<SalMenuItem> WinSalInstance::CreateMenuItem( const SalItemParams & rItemData )
+{
+ WinSalMenuItem *pSalMenuItem = new WinSalMenuItem();
+ memset( &pSalMenuItem->mInfo, 0, sizeof( MENUITEMINFOW ) );
+ pSalMenuItem->mInfo.cbSize = sizeof( MENUITEMINFOW );
+
+ if( rItemData.eType == MenuItemType::SEPARATOR )
+ {
+ // separator
+ pSalMenuItem->mInfo.fMask = MIIM_TYPE;
+ pSalMenuItem->mInfo.fType = MFT_SEPARATOR;
+ }
+ else
+ {
+ // item
+ pSalMenuItem->mText = rItemData.aText;
+ pSalMenuItem->mpMenu = rItemData.pMenu;
+ pSalMenuItem->maBitmap= !!rItemData.aImage ? rItemData.aImage.GetBitmapEx().GetBitmap() : Bitmap();
+ pSalMenuItem->mnId = rItemData.nId;
+
+ // 'translate' mnemonics
+ pSalMenuItem->mText = pSalMenuItem->mText.replaceAll( "~", "&" );
+
+ pSalMenuItem->mInfo.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID | MIIM_DATA;
+ pSalMenuItem->mInfo.fType = MFT_STRING;
+ pSalMenuItem->mInfo.dwTypeData = o3tl::toW(const_cast<sal_Unicode *>(pSalMenuItem->mText.getStr()));
+ pSalMenuItem->mInfo.cch = pSalMenuItem->mText.getLength();
+
+ pSalMenuItem->mInfo.wID = rItemData.nId;
+ pSalMenuItem->mInfo.dwItemData = reinterpret_cast<ULONG_PTR>(pSalMenuItem); // user data
+ }
+
+ return std::unique_ptr<SalMenuItem>(pSalMenuItem);
+}
+
+/*
+ * WinSalMenu
+ */
+
+WinSalMenu::WinSalMenu()
+{
+ mhMenu = nullptr;
+ mbMenuBar = false;
+ mhWnd = nullptr;
+ mpParentMenu = nullptr;
+}
+
+WinSalMenu::~WinSalMenu()
+{
+ // only required if not associated to a window...
+ GetSalData()->mhMenuSet.erase( mhMenu );
+ ::DestroyMenu( mhMenu );
+}
+
+bool WinSalMenu::VisibleMenuBar()
+{
+ // The Win32 implementation never shows a native
+ // menubar. Thus, native menus are only visible
+ // when the menu is merged with an OLE container.
+ // The reason are missing tooltips, ownerdraw
+ // issues and accessibility which are better supported
+ // by VCL menus.
+ // Nevertheless, the native menus are always created
+ // and the application will properly react to all native
+ // menu messages.
+
+ return false;
+}
+
+void WinSalMenu::SetFrame( const SalFrame *pFrame )
+{
+ if( pFrame )
+ mhWnd = static_cast<const WinSalFrame*>(pFrame)->mhWnd;
+ else
+ mhWnd = nullptr;
+}
+
+void WinSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos )
+{
+ if( pSalMenuItem )
+ {
+ WinSalMenuItem* pWItem = static_cast<WinSalMenuItem*>(pSalMenuItem);
+ if( nPos == MENU_APPEND )
+ {
+ nPos = ::GetMenuItemCount( mhMenu );
+ if( nPos == static_cast<unsigned>( -1 ) )
+ return;
+ }
+
+ if(!::InsertMenuItemW( mhMenu, nPos, TRUE, &pWItem->mInfo ))
+ myerr = GetLastError();
+ else
+ pWItem->mpSalMenu = this;
+ }
+}
+
+void WinSalMenu::RemoveItem( unsigned nPos )
+{
+ int num = ::GetMenuItemCount( mhMenu );
+ if( num != -1 && nPos < o3tl::make_unsigned(num) )
+ {
+ WinSalMenuItem *pSalMenuItem = nullptr;
+
+ MENUITEMINFOW mi = {};
+ mi.cbSize = sizeof( mi );
+ mi.fMask = MIIM_DATA;
+ if( !GetMenuItemInfoW( mhMenu, nPos, TRUE, &mi) )
+ myerr = GetLastError();
+ else
+ pSalMenuItem = reinterpret_cast<WinSalMenuItem *>(mi.dwItemData);
+
+ if( !::RemoveMenu( mhMenu, nPos, MF_BYPOSITION ) )
+ myerr = GetLastError();
+ else
+ {
+ if( pSalMenuItem )
+ pSalMenuItem->mpSalMenu = nullptr;
+ }
+ }
+}
+
+static void ImplRemoveItemById( WinSalMenu *pSalMenu, unsigned nItemId )
+{
+ if( !pSalMenu )
+ return;
+
+ WinSalMenuItem *pSalMenuItem = nullptr;
+
+ MENUITEMINFOW mi = {};
+ mi.cbSize = sizeof( mi );
+ mi.fMask = MIIM_DATA;
+ if( !GetMenuItemInfoW( pSalMenu->mhMenu, nItemId, FALSE, &mi) )
+ myerr = GetLastError();
+ else
+ pSalMenuItem = reinterpret_cast<WinSalMenuItem *>(mi.dwItemData);
+
+ if( !::RemoveMenu( pSalMenu->mhMenu, nItemId, MF_BYCOMMAND ) )
+ myerr = GetLastError();
+ else
+ {
+ if( pSalMenuItem )
+ pSalMenuItem->mpSalMenu = nullptr;
+ }
+}
+
+void WinSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned nPos )
+{
+ if( pSalMenuItem )
+ {
+ WinSalMenuItem* pWMenuItem = static_cast<WinSalMenuItem*>(pSalMenuItem);
+ WinSalMenu* pWSubMenu = static_cast<WinSalMenu*>(pSubMenu);
+ if( pWMenuItem->mInfo.hSubMenu )
+ {
+ GetSalData()->mhMenuSet.erase( pWMenuItem->mInfo.hSubMenu );
+ ::DestroyMenu( pWMenuItem->mInfo.hSubMenu );
+ }
+
+ pWMenuItem->mInfo.fMask |= MIIM_SUBMENU;
+ if( !pSubMenu )
+ pWMenuItem->mInfo.hSubMenu = nullptr;
+ else
+ {
+ pWMenuItem->mInfo.hSubMenu = pWSubMenu->mhMenu;
+ pWSubMenu->mpParentMenu = this;
+ }
+
+ if(!::SetMenuItemInfoW( mhMenu, nPos, TRUE, &pWMenuItem->mInfo ) )
+ myerr = GetLastError();
+ }
+}
+
+void WinSalMenu::CheckItem( unsigned nPos, bool bCheck )
+{
+ ::CheckMenuItem(mhMenu, nPos, MF_BYPOSITION|(bCheck ? MF_CHECKED : MF_UNCHECKED));
+}
+
+void WinSalMenu::EnableItem( unsigned nPos, bool bEnable )
+{
+ ::EnableMenuItem(mhMenu, nPos, MF_BYPOSITION|(bEnable ? MF_ENABLED : (MF_DISABLED|MF_GRAYED)));
+}
+
+void WinSalMenu::SetItemImage( unsigned /*nPos*/, SalMenuItem* pSalMenuItem, const Image& rImage )
+{
+ if( pSalMenuItem )
+ {
+ WinSalMenuItem* pWItem = static_cast<WinSalMenuItem*>(pSalMenuItem);
+ if( !!rImage )
+ pWItem->maBitmap = rImage.GetBitmapEx().GetBitmap();
+ else
+ pWItem->maBitmap = Bitmap();
+ }
+}
+
+void WinSalMenu::SetItemText( unsigned nPos, SalMenuItem* pSalMenuItem, const OUString& rText )
+{
+ if( pSalMenuItem )
+ {
+ WinSalMenuItem* pWItem = static_cast<WinSalMenuItem*>(pSalMenuItem);
+ pWItem->mText = rText;
+ // 'translate' mnemonics
+ pWItem->mText = pWItem->mText.replaceAll( "~", "&" );
+ pWItem->mInfo.fMask = MIIM_TYPE | MIIM_DATA;
+ pWItem->mInfo.fType = MFT_STRING;
+
+ // combine text and accelerator text
+ OUString aStr( pWItem->mText );
+ if( pWItem->mAccelText.getLength() )
+ {
+ aStr += "\t" + pWItem->mAccelText;
+ }
+ pWItem->mInfo.dwTypeData = o3tl::toW(const_cast<sal_Unicode *>(aStr.getStr()));
+ pWItem->mInfo.cch = aStr.getLength();
+
+ if(!::SetMenuItemInfoW( mhMenu, nPos, TRUE, &pWItem->mInfo ))
+ myerr = GetLastError();
+ }
+}
+
+void WinSalMenu::SetAccelerator( unsigned nPos, SalMenuItem* pSalMenuItem, const vcl::KeyCode&, const OUString& rKeyName )
+{
+ if( pSalMenuItem )
+ {
+ WinSalMenuItem* pWItem = static_cast<WinSalMenuItem*>(pSalMenuItem);
+ pWItem->mAccelText = rKeyName;
+ pWItem->mInfo.fMask = MIIM_TYPE | MIIM_DATA;
+ pWItem->mInfo.fType = MFT_STRING;
+
+ // combine text and accelerator text
+ OUString aStr( pWItem->mText );
+ if( pWItem->mAccelText.getLength() )
+ {
+ aStr += "\t" + pWItem->mAccelText;
+ }
+ pWItem->mInfo.dwTypeData = o3tl::toW(const_cast<sal_Unicode *>(aStr.getStr()));
+ pWItem->mInfo.cch = aStr.getLength();
+
+ if(!::SetMenuItemInfoW( mhMenu, nPos, TRUE, &pWItem->mInfo ))
+ myerr = GetLastError();
+ }
+}
+
+void WinSalMenu::GetSystemMenuData( SystemMenuData* pData )
+{
+ if( pData )
+ pData->hMenu = mhMenu;
+}
+
+/*
+ * SalMenuItem
+ */
+
+WinSalMenuItem::WinSalMenuItem()
+{
+ memset( &mInfo, 0, sizeof( MENUITEMINFOW ) );
+ mpMenu = nullptr;
+ mnId = 0xFFFF;
+ mpSalMenu = nullptr;
+}
+
+WinSalMenuItem::~WinSalMenuItem()
+{
+ if( mpSalMenu )
+ ImplRemoveItemById( mpSalMenu, mnId );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/win/window/salobj.cxx b/vcl/win/window/salobj.cxx
new file mode 100644
index 0000000000..05ad16b0d4
--- /dev/null
+++ b/vcl/win/window/salobj.cxx
@@ -0,0 +1,679 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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 <svsys.h>
+
+#include <vcl/svapp.hxx>
+#include <sal/log.hxx>
+
+#include <win/wincomp.hxx>
+#include <win/saldata.hxx>
+#include <win/salinst.h>
+#include <win/salframe.h>
+#include <win/salobj.h>
+
+#include <comphelper/windowserrorstring.hxx>
+
+static bool ImplIsSysWindowOrChild( HWND hWndParent, HWND hWndChild )
+{
+ if ( hWndParent == hWndChild )
+ return true;
+
+ HWND hTempWnd = ::GetParent( hWndChild );
+ while ( hTempWnd )
+ {
+ // stop searching if not a child window
+ if ( !(GetWindowStyle( hTempWnd ) & WS_CHILD) )
+ return false;
+ if ( hTempWnd == hWndParent )
+ return true;
+ hTempWnd = ::GetParent( hTempWnd );
+ }
+
+ return false;
+}
+
+WinSalObject* ImplFindSalObject( HWND hWndChild )
+{
+ SalData* pSalData = GetSalData();
+ WinSalObject* pObject = pSalData->mpFirstObject;
+ while ( pObject )
+ {
+ if ( ImplIsSysWindowOrChild( pObject->mhWndChild, hWndChild ) )
+ return pObject;
+
+ pObject = pObject->mpNextObject;
+ }
+
+ return nullptr;
+}
+
+static WinSalFrame* ImplFindSalObjectFrame( HWND hWnd )
+{
+ WinSalFrame* pFrame = nullptr;
+ WinSalObject* pObject = ImplFindSalObject( hWnd );
+ if ( pObject )
+ {
+ // find matching frame
+ HWND hWnd2 = ::GetParent( pObject->mhWnd );
+ pFrame = GetSalData()->mpFirstFrame;
+ while ( pFrame )
+ {
+ if ( pFrame->mhWnd == hWnd2 )
+ break;
+
+ pFrame = pFrame->mpNextFrame;
+ }
+ }
+
+ return pFrame;
+}
+
+static LRESULT CALLBACK SalSysMsgProc( int nCode, WPARAM wParam, LPARAM lParam )
+{
+ // Used for Unicode and none Unicode
+ SalData* pSalData = GetSalData();
+
+ if ( (nCode >= 0) && lParam )
+ {
+ CWPSTRUCT* pData = reinterpret_cast<CWPSTRUCT*>(lParam);
+ if ( (pData->message != WM_KEYDOWN) &&
+ (pData->message != WM_KEYUP) )
+ pSalData->mnSalObjWantKeyEvt = 0;
+
+
+ // check if we need to process data for a SalObject-window
+ WinSalObject* pObject;
+ if ( pData->message == WM_SETFOCUS )
+ {
+ pObject = ImplFindSalObject( pData->hwnd );
+ if ( pObject )
+ {
+ pObject->mhLastFocusWnd = pData->hwnd;
+ if ( ImplSalYieldMutexTryToAcquire() )
+ {
+ pObject->CallCallback( SalObjEvent::GetFocus );
+ ImplSalYieldMutexRelease();
+ }
+ else
+ {
+ bool const ret = PostMessageW(pObject->mhWnd, SALOBJ_MSG_POSTFOCUS, 0, 0);
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+ }
+ }
+ }
+ else if ( pData->message == WM_KILLFOCUS )
+ {
+ pObject = ImplFindSalObject( pData->hwnd );
+ if ( pObject && !ImplFindSalObject( reinterpret_cast<HWND>(pData->wParam) ) )
+ {
+ // only call LoseFocus, if truly no child window gets the focus
+ if ( !pData->wParam || !ImplFindSalObject( reinterpret_cast<HWND>(pData->wParam) ) )
+ {
+ if ( ImplSalYieldMutexTryToAcquire() )
+ {
+ pObject->CallCallback( SalObjEvent::LoseFocus );
+ ImplSalYieldMutexRelease();
+ }
+ else
+ {
+ bool const ret = PostMessageW(pObject->mhWnd, SALOBJ_MSG_POSTFOCUS, 0, 0);
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+ }
+ }
+ else
+ pObject->mhLastFocusWnd = reinterpret_cast<HWND>(pData->wParam);
+ }
+ }
+ }
+
+ return CallNextHookEx( pSalData->mhSalObjMsgHook, nCode, wParam, lParam );
+}
+
+bool ImplSalPreDispatchMsg( const MSG* pMsg )
+{
+ // Used for Unicode and none Unicode
+ SalData* pSalData = GetSalData();
+ WinSalObject* pObject;
+
+ if ( (pMsg->message == WM_LBUTTONDOWN) ||
+ (pMsg->message == WM_RBUTTONDOWN) ||
+ (pMsg->message == WM_MBUTTONDOWN) )
+ {
+ ImplSalYieldMutexAcquireWithWait();
+ pObject = ImplFindSalObject( pMsg->hwnd );
+ if ( pObject && !pObject->IsMouseTransparent() )
+ {
+ bool const ret = PostMessageW(pObject->mhWnd, SALOBJ_MSG_TOTOP, 0, 0);
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+ }
+ ImplSalYieldMutexRelease();
+ }
+
+ if ( (pMsg->message == WM_KEYDOWN) ||
+ (pMsg->message == WM_KEYUP) )
+ {
+ // process KeyEvents even if the control does not process them itself
+ // SysKeys are processed as WM_SYSCOMMAND
+ // Char-Events are not processed, as they are not accelerator-relevant
+ bool bWantedKeyCode = false;
+ // A-Z, 0-9 only when combined with the Control-key
+ if ( ((pMsg->wParam >= 65) && (pMsg->wParam <= 90)) ||
+ ((pMsg->wParam >= 48) && (pMsg->wParam <= 57)) )
+ {
+ if ( GetKeyState( VK_CONTROL ) & 0x8000 )
+ bWantedKeyCode = true;
+ }
+ else if ( ((pMsg->wParam >= VK_F1) && (pMsg->wParam <= VK_F24)) ||
+ ((pMsg->wParam >= VK_SPACE) && (pMsg->wParam <= VK_HELP)) ||
+ (pMsg->wParam == VK_BACK) || (pMsg->wParam == VK_TAB) ||
+ (pMsg->wParam == VK_CLEAR) || (pMsg->wParam == VK_RETURN) ||
+ (pMsg->wParam == VK_ESCAPE) )
+ bWantedKeyCode = true;
+ if ( bWantedKeyCode )
+ {
+ ImplSalYieldMutexAcquireWithWait();
+ pObject = ImplFindSalObject( pMsg->hwnd );
+ if ( pObject )
+ pSalData->mnSalObjWantKeyEvt = pMsg->wParam;
+ ImplSalYieldMutexRelease();
+ }
+ }
+ // check WM_SYSCHAR, to activate menu with Alt key
+ else if ( pMsg->message == WM_SYSCHAR )
+ {
+ pSalData->mnSalObjWantKeyEvt = 0;
+
+ sal_uInt16 nKeyCode = LOWORD( pMsg->wParam );
+ // only 0-9 and A-Z
+ if ( ((nKeyCode >= 48) && (nKeyCode <= 57)) ||
+ ((nKeyCode >= 65) && (nKeyCode <= 90)) ||
+ ((nKeyCode >= 97) && (nKeyCode <= 122)) )
+ {
+ bool bRet = false;
+ ImplSalYieldMutexAcquireWithWait();
+ pObject = ImplFindSalObject( pMsg->hwnd );
+ if ( pObject )
+ {
+ if ( pMsg->hwnd == ::GetFocus() )
+ {
+ WinSalFrame* pFrame = ImplFindSalObjectFrame( pMsg->hwnd );
+ if ( pFrame )
+ {
+ if ( ImplHandleSalObjSysCharMsg( pFrame->mhWnd, pMsg->wParam, pMsg->lParam ) )
+ bRet = true;
+ }
+ }
+ }
+ ImplSalYieldMutexRelease();
+ if ( bRet )
+ return true;
+ }
+ }
+ else
+ pSalData->mnSalObjWantKeyEvt = 0;
+
+ return false;
+}
+
+void ImplSalPostDispatchMsg( const MSG* pMsg )
+{
+ // Used for Unicode and none Unicode
+ SalData *pSalData = GetSalData();
+
+ if ( (pMsg->message == WM_KEYDOWN) || (pMsg->message == WM_KEYUP) )
+ {
+ if ( pSalData->mnSalObjWantKeyEvt == pMsg->wParam )
+ {
+ pSalData->mnSalObjWantKeyEvt = 0;
+ if ( pMsg->hwnd == ::GetFocus() )
+ {
+ ImplSalYieldMutexAcquireWithWait();
+ WinSalFrame* pFrame = ImplFindSalObjectFrame( pMsg->hwnd );
+ if ( pFrame )
+ ImplHandleSalObjKeyMsg( pFrame->mhWnd, pMsg->message, pMsg->wParam, pMsg->lParam );
+ ImplSalYieldMutexRelease();
+ }
+ }
+ }
+
+ pSalData->mnSalObjWantKeyEvt = 0;
+}
+
+static LRESULT CALLBACK SalSysObjWndProcW(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch( nMsg )
+ {
+ case WM_ERASEBKGND:
+ return 1;
+ case WM_PAINT:
+ {
+ PAINTSTRUCT aPs;
+ BeginPaint( hWnd, &aPs );
+ EndPaint( hWnd, &aPs );
+ }
+ return 0;
+
+ case WM_PARENTNOTIFY:
+ if (UINT nNotifyMsg = LOWORD(wParam);
+ (nNotifyMsg == WM_LBUTTONDOWN) ||
+ (nNotifyMsg == WM_RBUTTONDOWN) ||
+ (nNotifyMsg == WM_MBUTTONDOWN) )
+ {
+ ImplSalYieldMutexAcquireWithWait();
+ WinSalObject* pSysObj = GetSalObjWindowPtr(hWnd);
+ if ( pSysObj && !pSysObj->IsMouseTransparent() )
+ pSysObj->CallCallback( SalObjEvent::ToTop );
+ ImplSalYieldMutexRelease();
+ }
+ break;
+
+ case WM_MOUSEACTIVATE:
+ ImplSalYieldMutexAcquireWithWait();
+ if (WinSalObject* pSysObj = GetSalObjWindowPtr(hWnd);
+ pSysObj && !pSysObj->IsMouseTransparent())
+ {
+ bool const ret = PostMessageW( hWnd, SALOBJ_MSG_TOTOP, 0, 0 );
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+ }
+ ImplSalYieldMutexRelease();
+ break;
+
+ case SALOBJ_MSG_TOTOP:
+ if ( ImplSalYieldMutexTryToAcquire() )
+ {
+ if (WinSalObject* pSysObj = GetSalObjWindowPtr(hWnd))
+ pSysObj->CallCallback(SalObjEvent::ToTop);
+ ImplSalYieldMutexRelease();
+ return 0;
+ }
+ else
+ {
+ bool const ret = PostMessageW( hWnd, SALOBJ_MSG_TOTOP, 0, 0 );
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+ }
+ break;
+
+ case SALOBJ_MSG_POSTFOCUS:
+ if ( ImplSalYieldMutexTryToAcquire() )
+ {
+ WinSalObject* pSysObj = GetSalObjWindowPtr(hWnd);
+ HWND hFocusWnd = ::GetFocus();
+ SalObjEvent nEvent;
+ if ( hFocusWnd && ImplIsSysWindowOrChild( hWnd, hFocusWnd ) )
+ nEvent = SalObjEvent::GetFocus;
+ else
+ nEvent = SalObjEvent::LoseFocus;
+ pSysObj->CallCallback( nEvent );
+ ImplSalYieldMutexRelease();
+ }
+ else
+ {
+ bool const ret = PostMessageW(hWnd, SALOBJ_MSG_POSTFOCUS, 0, 0);
+ SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!");
+ }
+ return 0;
+
+ case WM_SIZE:
+ if (HWND hWndChild = GetWindow(hWnd, GW_CHILD))
+ {
+ SetWindowPos( hWndChild,
+ nullptr, 0, 0, static_cast<int>(LOWORD( lParam )), static_cast<int>(HIWORD( lParam )),
+ SWP_NOZORDER | SWP_NOACTIVATE );
+ }
+ return 0;
+
+ case WM_CREATE:
+ {
+ // Save the window instance at the window handle.
+ CREATESTRUCTW* pStruct = reinterpret_cast<CREATESTRUCTW*>(lParam);
+ WinSalObject* pSysObj = static_cast<WinSalObject*>(pStruct->lpCreateParams);
+ SetSalObjWindowPtr( hWnd, pSysObj );
+ // set HWND already here,
+ // as instance data might be used during CreateWindow() events
+ pSysObj->mhWnd = hWnd;
+ return 0;
+ }
+ }
+
+ return DefWindowProcW(hWnd, nMsg, wParam, lParam);
+}
+
+static LRESULT CALLBACK SalSysObjChildWndProcW(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch( nMsg )
+ {
+ // clear background for plugins
+ case WM_ERASEBKGND:
+ if (WinSalObject* pSysObj = GetSalObjWindowPtr(GetParent(hWnd));
+ pSysObj && !pSysObj->IsEraseBackgroundEnabled())
+ {
+ // do not erase background
+ return 1;
+ }
+ break;
+
+ case WM_PAINT:
+ {
+ PAINTSTRUCT aPs;
+ BeginPaint( hWnd, &aPs );
+ EndPaint( hWnd, &aPs );
+ }
+ return 0;
+
+ case WM_MOUSEMOVE:
+ case WM_LBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ case WM_LBUTTONUP:
+ case WM_MBUTTONUP:
+ case WM_RBUTTONUP:
+ if (WinSalObject* pSysObj = GetSalObjWindowPtr(GetParent(hWnd));
+ pSysObj && pSysObj->IsMouseTransparent())
+ {
+ // forward mouse events to parent frame
+ HWND hWndParent = GetParent(pSysObj->mhWnd);
+
+ // transform coordinates
+ POINT pt;
+ pt.x = static_cast<tools::Long>(LOWORD(lParam));
+ pt.y = static_cast<tools::Long>(HIWORD(lParam));
+ MapWindowPoints(hWnd, hWndParent, &pt, 1);
+ lParam = MAKELPARAM(static_cast<WORD>(pt.x), static_cast<WORD>(pt.y));
+
+ return SendMessageW(hWndParent, nMsg, wParam, lParam);
+ }
+ break;
+ }
+
+ return DefWindowProcW(hWnd, nMsg, wParam, lParam);
+}
+
+SalObject* ImplSalCreateObject( WinSalInstance* pInst, WinSalFrame* pParent )
+{
+ SalData* pSalData = GetSalData();
+
+ // install hook, if it is the first SalObject
+ if ( !pSalData->mpFirstObject )
+ {
+ pSalData->mhSalObjMsgHook = SetWindowsHookExW( WH_CALLWNDPROC,
+ SalSysMsgProc,
+ pSalData->mhInst,
+ pSalData->mnAppThreadId );
+ }
+
+ if ( !pSalData->mbObjClassInit )
+ {
+ WNDCLASSEXW aWndClassEx;
+ aWndClassEx.cbSize = sizeof( aWndClassEx );
+ aWndClassEx.style = 0;
+ aWndClassEx.lpfnWndProc = SalSysObjWndProcW;
+ aWndClassEx.cbClsExtra = 0;
+ aWndClassEx.cbWndExtra = SAL_OBJECT_WNDEXTRA;
+ aWndClassEx.hInstance = pSalData->mhInst;
+ aWndClassEx.hIcon = nullptr;
+ aWndClassEx.hIconSm = nullptr;
+ aWndClassEx.hCursor = LoadCursor( nullptr, IDC_ARROW );
+ aWndClassEx.hbrBackground = nullptr;
+ aWndClassEx.lpszMenuName = nullptr;
+ aWndClassEx.lpszClassName = SAL_OBJECT_CLASSNAMEW;
+ if ( RegisterClassExW( &aWndClassEx ) )
+ {
+ // Clean background first because of plugins.
+ aWndClassEx.cbWndExtra = 0;
+ aWndClassEx.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1);
+ aWndClassEx.lpfnWndProc = SalSysObjChildWndProcW;
+ aWndClassEx.lpszClassName = SAL_OBJECT_CHILDCLASSNAMEW;
+ if ( RegisterClassExW( &aWndClassEx ) )
+ pSalData->mbObjClassInit = true;
+ }
+ }
+
+ if ( pSalData->mbObjClassInit )
+ {
+ WinSalObject* pObject = new WinSalObject;
+
+ // #135235# Clip siblings of this
+ // SystemChildWindow. Otherwise, DXCanvas (using a hidden
+ // SystemChildWindow) clobbers applets/plugins during
+ // animations .
+ HWND hWnd = CreateWindowExW( 0, SAL_OBJECT_CLASSNAMEW, L"",
+ WS_CHILD | WS_CLIPSIBLINGS, 0, 0, 0, 0,
+ pParent->mhWnd, nullptr,
+ pInst->mhInst, pObject );
+
+ HWND hWndChild = nullptr;
+ if ( hWnd )
+ {
+ // #135235# Explicitly stack SystemChildWindows in
+ // the order they're created - since there's no notion
+ // of zorder.
+ SetWindowPos(hWnd,HWND_TOP,0,0,0,0,
+ SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOREDRAW|SWP_NOSIZE);
+ hWndChild = CreateWindowExW( 0, SAL_OBJECT_CHILDCLASSNAMEW, L"",
+ WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE,
+ 0, 0, 0, 0,
+ hWnd, nullptr,
+ pInst->mhInst, nullptr );
+ }
+
+ if ( !hWndChild )
+ {
+ SAL_WARN("vcl", "CreateWindowExW failed: " << WindowsErrorString(GetLastError()));
+
+ delete pObject;
+ return nullptr;
+ }
+
+ if ( hWnd )
+ {
+ pObject->mhWnd = hWnd;
+ pObject->mhWndChild = hWndChild;
+ pObject->maSysData.hWnd = hWndChild;
+ return pObject;
+ }
+ }
+
+ return nullptr;
+}
+
+WinSalObject::WinSalObject()
+{
+ SalData* pSalData = GetSalData();
+
+ mhWnd = nullptr;
+ mhWndChild = nullptr;
+ mhLastFocusWnd = nullptr;
+ mpStdClipRgnData = nullptr;
+
+ // Insert object in objectlist
+ mpNextObject = pSalData->mpFirstObject;
+ pSalData->mpFirstObject = this;
+}
+
+WinSalObject::~WinSalObject()
+{
+ SalData* pSalData = GetSalData();
+
+ // remove frame from framelist
+ if ( this == pSalData->mpFirstObject )
+ {
+ pSalData->mpFirstObject = mpNextObject;
+
+ // remove hook, if it is the last SalObject
+ if ( !pSalData->mpFirstObject )
+ UnhookWindowsHookEx( pSalData->mhSalObjMsgHook );
+ }
+ else
+ {
+ WinSalObject* pTempObject = pSalData->mpFirstObject;
+ while ( pTempObject->mpNextObject != this )
+ pTempObject = pTempObject->mpNextObject;
+
+ pTempObject->mpNextObject = mpNextObject;
+ }
+
+ // destroy cache data
+ delete [] reinterpret_cast<BYTE*>(mpStdClipRgnData);
+
+ HWND hWndParent = ::GetParent( mhWnd );
+
+ if ( mhWndChild )
+ DestroyWindow( mhWndChild );
+ if ( mhWnd )
+ DestroyWindow( mhWnd );
+
+ // reset palette, if no external child window is left,
+ // as they might have overwritten our palette
+ if ( hWndParent &&
+ ::GetActiveWindow() == hWndParent &&
+ !GetWindow( hWndParent, GW_CHILD ) )
+ SendMessageW( hWndParent, SAL_MSG_FORCEPALETTE, 0, 0 );
+}
+
+void WinSalObject::ResetClipRegion()
+{
+ SetWindowRgn( mhWnd, nullptr, TRUE );
+}
+
+void WinSalObject::BeginSetClipRegion( sal_uInt32 nRectCount )
+{
+ sal_uLong nRectBufSize = sizeof(RECT)*nRectCount;
+ if ( nRectCount < SAL_CLIPRECT_COUNT )
+ {
+ if ( !mpStdClipRgnData )
+ mpStdClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+(SAL_CLIPRECT_COUNT*sizeof(RECT))]);
+ mpClipRgnData = mpStdClipRgnData;
+ }
+ else
+ mpClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+nRectBufSize]);
+ mpClipRgnData->rdh.dwSize = sizeof( RGNDATAHEADER );
+ mpClipRgnData->rdh.iType = RDH_RECTANGLES;
+ mpClipRgnData->rdh.nCount = nRectCount;
+ mpClipRgnData->rdh.nRgnSize = nRectBufSize;
+ SetRectEmpty( &(mpClipRgnData->rdh.rcBound) );
+ mpNextClipRect = reinterpret_cast<RECT*>(&(mpClipRgnData->Buffer));
+ mbFirstClipRect = true;
+}
+
+void WinSalObject::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ RECT* pRect = mpNextClipRect;
+ RECT* pBoundRect = &(mpClipRgnData->rdh.rcBound);
+ tools::Long nRight = nX + nWidth;
+ tools::Long nBottom = nY + nHeight;
+
+ if ( mbFirstClipRect )
+ {
+ pBoundRect->left = nX;
+ pBoundRect->top = nY;
+ pBoundRect->right = nRight;
+ pBoundRect->bottom = nBottom;
+ mbFirstClipRect = false;
+ }
+ else
+ {
+ if ( nX < pBoundRect->left )
+ pBoundRect->left = static_cast<int>(nX);
+
+ if ( nY < pBoundRect->top )
+ pBoundRect->top = static_cast<int>(nY);
+
+ if ( nRight > pBoundRect->right )
+ pBoundRect->right = static_cast<int>(nRight);
+
+ if ( nBottom > pBoundRect->bottom )
+ pBoundRect->bottom = static_cast<int>(nBottom);
+ }
+
+ pRect->left = static_cast<int>(nX);
+ pRect->top = static_cast<int>(nY);
+ pRect->right = static_cast<int>(nRight);
+ pRect->bottom = static_cast<int>(nBottom);
+ mpNextClipRect++;
+}
+
+void WinSalObject::EndSetClipRegion()
+{
+ HRGN hRegion;
+
+ // create a ClipRegion from the vcl::Region data
+ if ( mpClipRgnData->rdh.nCount == 1 )
+ {
+ RECT* pRect = &(mpClipRgnData->rdh.rcBound);
+ hRegion = CreateRectRgn( pRect->left, pRect->top,
+ pRect->right, pRect->bottom );
+ }
+ else
+ {
+ sal_uLong nSize = mpClipRgnData->rdh.nRgnSize+sizeof(RGNDATAHEADER);
+ hRegion = ExtCreateRegion( nullptr, nSize, mpClipRgnData );
+ if ( mpClipRgnData != mpStdClipRgnData )
+ delete [] reinterpret_cast<BYTE*>(mpClipRgnData);
+ }
+
+ SAL_WARN_IF( !hRegion, "vcl", "SalObject::EndSetClipRegion() - Can't create ClipRegion" );
+ SetWindowRgn( mhWnd, hRegion, TRUE );
+}
+
+void WinSalObject::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ sal_uLong nStyle = 0;
+ bool bVisible = (GetWindowStyle( mhWnd ) & WS_VISIBLE) != 0;
+ if ( bVisible )
+ {
+ ShowWindow( mhWnd, SW_HIDE );
+ nStyle |= SWP_SHOWWINDOW;
+ }
+ SetWindowPos( mhWnd, nullptr,
+ static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nWidth), static_cast<int>(nHeight),
+ SWP_NOZORDER | SWP_NOACTIVATE | nStyle );
+}
+
+void WinSalObject::Show( bool bVisible )
+{
+ if ( bVisible )
+ ShowWindow( mhWnd, SW_SHOWNORMAL );
+ else
+ ShowWindow( mhWnd, SW_HIDE );
+}
+
+void WinSalObject::Enable( bool bEnable )
+{
+ EnableWindow( mhWnd, bEnable );
+}
+
+void WinSalObject::GrabFocus()
+{
+ if ( mhLastFocusWnd &&
+ IsWindow( mhLastFocusWnd ) &&
+ ImplIsSysWindowOrChild( mhWndChild, mhLastFocusWnd ) )
+ ::SetFocus( mhLastFocusWnd );
+ else
+ ::SetFocus( mhWndChild );
+}
+
+const SystemEnvData* WinSalObject::GetSystemData() const
+{
+ return &maSysData;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/602fuzzer.cxx b/vcl/workben/602fuzzer.cxx
new file mode 100644
index 0000000000..6fb96a4c77
--- /dev/null
+++ b/vcl/workben/602fuzzer.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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" bool TestImport602(SvStream &rStream);
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImport602(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/602fuzzer.options b/vcl/workben/602fuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/602fuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/bmpfuzzer.cxx b/vcl/workben/bmpfuzzer.cxx
new file mode 100644
index 0000000000..8e22aabafc
--- /dev/null
+++ b/vcl/workben/bmpfuzzer.cxx
@@ -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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/dibtools.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Bitmap aTarget;
+ (void)ReadDIB(aTarget, aStream, true);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/bmpfuzzer.options b/vcl/workben/bmpfuzzer.options
new file mode 100644
index 0000000000..8ef6457cac
--- /dev/null
+++ b/vcl/workben/bmpfuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+dict = bmp.dict
diff --git a/vcl/workben/cgmfuzzer.cxx b/vcl/workben/cgmfuzzer.cxx
new file mode 100644
index 0000000000..812fd0dbe3
--- /dev/null
+++ b/vcl/workben/cgmfuzzer.cxx
@@ -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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * i18npool_component_getFactory( const char* , void* , void* );
+
+void * com_sun_star_i18n_LocaleDataImpl_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_get_implementation( void *, void * );
+void * com_sun_star_comp_framework_Desktop_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_get_implementation( void *, void * );
+void * com_sun_star_i18n_NativeNumberSupplier_get_implementation( void *, void * );
+void * com_sun_star_i18n_NumberFormatCodeMapper_get_implementation( void *, void * );
+void * com_sun_star_i18n_Transliteration_get_implementation( void *, void * );
+void * com_sun_star_drawing_EnhancedCustomShapeEngine_get_implementation( void *, void * );
+void * com_sun_star_drawing_SvxShapeCollection_get_implementation( void *, void * );
+void * SfxDocumentMetaData_get_implementation( void *, void * );
+void * com_sun_star_animations_AnimateColor_get_implementation( void *, void * );
+void * com_sun_star_animations_AnimateMotion_get_implementation( void *, void * );
+void * com_sun_star_animations_AnimateSet_get_implementation( void *, void * );
+void * com_sun_star_animations_AnimateTransform_get_implementation( void *, void * );
+void * com_sun_star_animations_Animate_get_implementation( void *, void * );
+void * com_sun_star_animations_Audio_get_implementation( void *, void * );
+void * com_sun_star_animations_Command_get_implementation( void *, void * );
+void * com_sun_star_animations_IterateContainer_get_implementation( void *, void * );
+void * com_sun_star_animations_ParallelTimeContainer_get_implementation( void *, void * );
+void * com_sun_star_animations_SequenceTimeContainer_get_implementation( void *, void * );
+void * com_sun_star_animations_TransitionFilter_get_implementation( void *, void * );
+void * com_sun_star_comp_comphelper_OPropertyBag( void *, void * );
+void * com_sun_star_comp_uui_UUIInteractionHandler_get_implementation( void *, void * );
+void * emfio_emfreader_XEmfParser_get_implementation( void *, void * );
+void * unoxml_rdfRepository_get_implementation( void *, void * );
+void * unoxml_CURI_get_implementation( void *, void * );
+void * unoxml_CLiteral_get_implementation( void *, void * );
+void * unoxml_CBlankNode_get_implementation( void *, void * );
+void * unoxml_CXPathAPI_get_implementation( void *, void * );
+void * unoxml_CSAXDocumentBuilder_get_implementation( void *, void * );
+void * unoxml_CDocumentBuilder_get_implementation( void *, void * );
+void * linguistic_ConvDicList_get_implementation( void *, void * );
+void * linguistic_DicList_get_implementation( void *, void * );
+void * linguistic_LinguProps_get_implementation( void *, void * );
+void * linguistic_LngSvcMgr_get_implementation( void *, void * );
+void * linguistic_GrammarCheckingIterator_get_implementation( void *, void * );
+void * sd_DrawingDocument_get_implementation( void *, void * );
+void * com_sun_star_comp_Draw_DrawingModule_get_implementation( void *, void * );
+void * sd_PresentationDocument_get_implementation( void *, void * );
+void * com_sun_star_comp_Draw_PresenterHelper_get_implementation( void *, void * );
+void * com_sun_star_comp_Draw_PresenterPreviewCache_get_implementation( void *, void * );
+void * com_sun_star_comp_Draw_SlideRenderer_get_implementation( void *, void * );
+void * com_sun_star_comp_sd_InsertSlideController_get_implementation( void *, void * );
+void * com_sun_star_comp_sd_SlideLayoutController_get_implementation( void *, void * );
+void * com_sun_star_comp_sd_DisplayModeController_get_implementation( void *, void * );
+void * ucb_UcbCommandEnvironment_get_implementation( void *, void * );
+void * ucb_UcbContentProviderProxyFactory_get_implementation( void *, void * );
+void * ucb_UcbPropertiesManager_get_implementation( void *, void * );
+void * ucb_UcbStore_get_implementation( void *, void * );
+void * ucb_UniversalContentBroker_get_implementation( void *, void * );
+void * ucb_OFileAccess_get_implementation( void *, void * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libi18npoollo.a", i18npool_component_getFactory },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { "com_sun_star_i18n_LocaleDataImpl_get_implementation", com_sun_star_i18n_LocaleDataImpl_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_Unicode_get_implementation", com_sun_star_i18n_BreakIterator_Unicode_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_get_implementation", com_sun_star_i18n_BreakIterator_get_implementation },
+ { "com_sun_star_comp_framework_Desktop_get_implementation", com_sun_star_comp_framework_Desktop_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_Unicode_get_implementation", com_sun_star_i18n_CharacterClassification_Unicode_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_get_implementation", com_sun_star_i18n_CharacterClassification_get_implementation },
+ { "com_sun_star_i18n_NativeNumberSupplier_get_implementation", com_sun_star_i18n_NativeNumberSupplier_get_implementation },
+ { "com_sun_star_i18n_NumberFormatCodeMapper_get_implementation", com_sun_star_i18n_NumberFormatCodeMapper_get_implementation },
+ { "com_sun_star_i18n_Transliteration_get_implementation", com_sun_star_i18n_Transliteration_get_implementation },
+ { "com_sun_star_drawing_EnhancedCustomShapeEngine_get_implementation", com_sun_star_drawing_EnhancedCustomShapeEngine_get_implementation },
+ { "com_sun_star_drawing_SvxShapeCollection_get_implementation", com_sun_star_drawing_SvxShapeCollection_get_implementation },
+ { "SfxDocumentMetaData_get_implementation", SfxDocumentMetaData_get_implementation },
+ { "com_sun_star_animations_AnimateColor_get_implementation", com_sun_star_animations_AnimateColor_get_implementation },
+ { "com_sun_star_animations_AnimateMotion_get_implementation", com_sun_star_animations_AnimateMotion_get_implementation },
+ { "com_sun_star_animations_AnimateSet_get_implementation", com_sun_star_animations_AnimateSet_get_implementation },
+ { "com_sun_star_animations_AnimateTransform_get_implementation", com_sun_star_animations_AnimateTransform_get_implementation },
+ { "com_sun_star_animations_Animate_get_implementation", com_sun_star_animations_Animate_get_implementation },
+ { "com_sun_star_animations_Audio_get_implementation", com_sun_star_animations_Audio_get_implementation },
+ { "com_sun_star_animations_Command_get_implementation", com_sun_star_animations_Command_get_implementation },
+ { "com_sun_star_animations_IterateContainer_get_implementation", com_sun_star_animations_IterateContainer_get_implementation },
+ { "com_sun_star_animations_ParallelTimeContainer_get_implementation", com_sun_star_animations_ParallelTimeContainer_get_implementation },
+ { "com_sun_star_animations_SequenceTimeContainer_get_implementation", com_sun_star_animations_SequenceTimeContainer_get_implementation },
+ { "com_sun_star_animations_TransitionFilter_get_implementation", com_sun_star_animations_TransitionFilter_get_implementation },
+ { "com_sun_star_comp_comphelper_OPropertyBag", com_sun_star_comp_comphelper_OPropertyBag },
+ { "com_sun_star_comp_uui_UUIInteractionHandler_get_implementation", com_sun_star_comp_uui_UUIInteractionHandler_get_implementation },
+ { "emfio_emfreader_XEmfParser_get_implementation", emfio_emfreader_XEmfParser_get_implementation},
+ { "unoxml_rdfRepository_get_implementation", unoxml_rdfRepository_get_implementation },
+ { "unoxml_CURI_get_implementation", unoxml_CURI_get_implementation },
+ { "unoxml_CLiteral_get_implementation", unoxml_CLiteral_get_implementation },
+ { "unoxml_CBlankNode_get_implementation", unoxml_CBlankNode_get_implementation },
+ { "unoxml_CXPathAPI_get_implementation", unoxml_CXPathAPI_get_implementation },
+ { "unoxml_CSAXDocumentBuilder_get_implementation", unoxml_CSAXDocumentBuilder_get_implementation },
+ { "unoxml_CDocumentBuilder_get_implementation", unoxml_CDocumentBuilder_get_implementation },
+ { "linguistic_ConvDicList_get_implementation", linguistic_ConvDicList_get_implementation },
+ { "linguistic_DicList_get_implementation", linguistic_DicList_get_implementation },
+ { "linguistic_LinguProps_get_implementation", linguistic_LinguProps_get_implementation },
+ { "linguistic_LngSvcMgr_get_implementation", linguistic_LngSvcMgr_get_implementation },
+ { "linguistic_GrammarCheckingIterator_get_implementation", linguistic_GrammarCheckingIterator_get_implementation },
+ { "sd_DrawingDocument_get_implementation", sd_DrawingDocument_get_implementation },
+ { "com_sun_star_comp_Draw_DrawingModule_get_implementation", com_sun_star_comp_Draw_DrawingModule_get_implementation },
+ { "sd_PresentationDocument_get_implementation", sd_PresentationDocument_get_implementation },
+ { "com_sun_star_comp_Draw_PresenterHelper_get_implementation", com_sun_star_comp_Draw_PresenterHelper_get_implementation },
+ { "com_sun_star_comp_Draw_PresenterPreviewCache_get_implementation", com_sun_star_comp_Draw_PresenterPreviewCache_get_implementation },
+ { "com_sun_star_comp_Draw_SlideRenderer_get_implementation", com_sun_star_comp_Draw_SlideRenderer_get_implementation },
+ { "com_sun_star_comp_sd_InsertSlideController_get_implementation", com_sun_star_comp_sd_InsertSlideController_get_implementation },
+ { "com_sun_star_comp_sd_SlideLayoutController_get_implementation", com_sun_star_comp_sd_SlideLayoutController_get_implementation },
+ { "com_sun_star_comp_sd_DisplayModeController_get_implementation", com_sun_star_comp_sd_DisplayModeController_get_implementation },
+ { "ucb_UcbCommandEnvironment_get_implementation", ucb_UcbCommandEnvironment_get_implementation, },
+ { "ucb_UcbContentProviderProxyFactory_get_implementation", ucb_UcbContentProviderProxyFactory_get_implementation },
+ { "ucb_UcbPropertiesManager_get_implementation", ucb_UcbPropertiesManager_get_implementation },
+ { "ucb_UcbStore_get_implementation", ucb_UcbStore_get_implementation },
+ { "ucb_UniversalContentBroker_get_implementation", ucb_UniversalContentBroker_get_implementation },
+ { "ucb_OFileAccess_get_implementation", ucb_OFileAccess_get_implementation },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" void* SdCreateDialogFactory()
+{
+ return nullptr;
+}
+
+extern "C" bool TestImportCGM(SvStream &rStream);
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportCGM(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/cgmfuzzer.options b/vcl/workben/cgmfuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/cgmfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/cnttype/makefile.mk b/vcl/workben/cnttype/makefile.mk
new file mode 100644
index 0000000000..ad1e635eb7
--- /dev/null
+++ b/vcl/workben/cnttype/makefile.mk
@@ -0,0 +1,44 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+PRJ=..$/..$/..
+
+PRJNAME=dtrans
+TARGET=testcnttype
+LIBTARGET=NO
+TARGETTYPE=CUI
+
+# --- Settings -----------------------------------------------------
+
+.INCLUDE : settings.mk
+
+CFLAGS+=-GX
+
+# --- Files --------------------------------------------------------
+
+OBJFILES= $(OBJ)$/testcnttype.obj
+APP1TARGET= $(TARGET)
+APP1OBJS= $(OBJ)$/testcnttype.obj
+
+APP1STDLIBS= $(SALLIB) \
+ $(CPPULIB) \
+ $(CPPUHELPERLIB) \
+
+# --- Targets ------------------------------------------------------
+.INCLUDE : target.mk
+
diff --git a/vcl/workben/cnttype/testcnttype.cxx b/vcl/workben/cnttype/testcnttype.cxx
new file mode 100644
index 0000000000..a148348ff6
--- /dev/null
+++ b/vcl/workben/cnttype/testcnttype.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 <cppuhelper/servicefactory.hxx>
+#include <com/sun/star/lang/XTypeProvider.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/container/NoSuchElementException.hpp>
+#include <com/sun/star/datatransfer/XMimeContentType.hpp>
+#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <osl/diagnose.h>
+
+#include <stdio.h>
+
+#include <vector>
+
+// my defines
+
+#define TEST_CLIPBOARD
+#define RDB_SYSPATH "d:\\projects\\src621\\dtrans\\wntmsci7\\bin\\applicat.rdb"
+
+// namespaces
+
+using namespace ::std;
+using namespace ::cppu;
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::container;
+
+void ShutdownServiceMgr( Reference< XMultiServiceFactory >& SrvMgr )
+{
+ // Cast factory to XComponent
+ Reference< XComponent > xComponent( SrvMgr, UNO_QUERY );
+
+ if ( !xComponent.is() )
+ OSL_FAIL("Error shutting down");
+
+ // Dispose and clear factory
+ xComponent->dispose();
+ SrvMgr.clear();
+}
+
+sal_Bool readCntTypesFromFileIntoVector( char* fname, vector< string >& vecData )
+{
+ FILE* fstream;
+
+ fstream = fopen( fname, "r+" );
+ if ( !fstream )
+ return sal_False;
+
+ // set pointer to file start
+ fseek( fstream, 0, SEEK_SET );
+
+ char line[1024];
+ while ( fscanf( fstream, "%1023[^\n]s", line ) != EOF )
+ {
+ vecData.push_back( line );
+ fgetc( fstream );
+ }
+
+ fclose( fstream );
+
+ return sal_True;
+}
+
+sal_Bool processCntTypesAndWriteResultIntoFile( char* fname, vector< string >& vecData, Reference< XMimeContentTypeFactory > cnttypeFactory )
+{
+ FILE* fstream;
+
+ fstream = fopen( fname, "w" );
+ if ( !fstream )
+ return sal_False;
+
+ // set pointer to file start
+ fseek( fstream, 0, SEEK_SET );
+
+ for ( const auto& rData : vecData )
+ {
+ try
+ {
+ fprintf( fstream, "Read: %s\n", rData.c_str( ) );
+
+ Reference< XMimeContentType > xMCntTyp = cnttypeFactory->createMimeContentType( OUString::createFromAscii( rData.c_str( ) ) );
+
+ fwprintf( fstream, OUString("Type: %s\n"), xMCntTyp->getMediaType( ).getStr( ) );
+ fwprintf( fstream, OUString("Subtype: %s\n"), xMCntTyp->getMediaSubtype( ).getStr( ) );
+
+ Sequence< OUString > seqParam = xMCntTyp->getParameters( );
+ sal_Int32 nParams = seqParam.getLength( );
+
+ for ( sal_Int32 i = 0; i < nParams; i++ )
+ {
+ fwprintf( fstream, OUString("PName: %s\n"), seqParam[i].getStr( ) );
+ fwprintf( fstream, OUString("PValue: %s\n"), xMCntTyp->getParameterValue( seqParam[i] ).getStr( ) );
+ }
+ }
+ catch( IllegalArgumentException& ex )
+ {
+ fwprintf( fstream, OUString("Read incorrect content type!\n\n") );
+ }
+ catch( NoSuchElementException& )
+ {
+ fwprintf( fstream, OUString("Value of parameter not available\n") );
+ }
+ catch( ... )
+ {
+ fwprintf( fstream, OUString("Unknown error!\n\n") );
+ }
+
+ fwprintf( fstream, OUString("\n#############################################\n\n") );
+ }
+
+ fclose( fstream );
+
+ return sal_True;
+}
+
+// main
+
+int SAL_CALL main( int nArgc, char* argv[] )
+{
+ if ( nArgc != 3 )
+ printf( "Start with: testcnttype input-file output-file\n" );
+
+ // get the global service-manager
+
+ Reference< XMultiServiceFactory > g_xFactory( createRegistryServiceFactory( RDB_SYSPATH ) );
+
+ // Print a message if an error occurred.
+ if ( !g_xFactory.is( ) )
+ {
+ OSL_FAIL("Can't create RegistryServiceFactory");
+ return(-1);
+ }
+
+ vector< string > vecCntTypes;
+
+ // open input-file and read the data
+ if ( !readCntTypesFromFileIntoVector( argv[1], vecCntTypes ) )
+ {
+ printf( "Can't open input file" );
+ ShutdownServiceMgr( g_xFactory );
+ }
+
+ Reference< XMimeContentTypeFactory >
+ xMCntTypeFactory( g_xFactory->createInstance("com.sun.star.datatransfer.MimeContentTypeFactory"), UNO_QUERY );
+
+ if ( !xMCntTypeFactory.is( ) )
+ {
+ OSL_FAIL( "Error creating MimeContentTypeFactory Service" );
+ return(-1);
+ }
+
+ if ( !processCntTypesAndWriteResultIntoFile( argv[2], vecCntTypes, xMCntTypeFactory ) )
+ {
+ printf( "Can't open output file" );
+ ShutdownServiceMgr( g_xFactory );
+ }
+
+ // shutdown the service manager
+
+ ShutdownServiceMgr( g_xFactory );
+
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/commonfuzzer.hxx b/vcl/workben/commonfuzzer.hxx
new file mode 100644
index 0000000000..c9afd65552
--- /dev/null
+++ b/vcl/workben/commonfuzzer.hxx
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/main.h>
+#include <tools/extendapplicationenvironment.hxx>
+
+#include <cppuhelper/bootstrap.hxx>
+#include <comphelper/processfactory.hxx>
+
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <unotools/configmgr.hxx>
+#include <rtl/bootstrap.hxx>
+#include <rtl/strbuf.hxx>
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <vcl/graph.hxx>
+#include <vcl/print.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/wmf.hxx>
+#include <unistd.h>
+#include <stdlib.h>
+#include "headless/svpgdi.hxx"
+#include "unx/fontmanager.hxx"
+#include "unx/glyphcache.hxx"
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace cppu;
+
+namespace
+{
+ OUString getExecutableDir()
+ {
+ OUString uri;
+ if (osl_getExecutableFile(&uri.pData) != osl_Process_E_None) {
+ abort();
+ }
+ sal_Int32 lastDirSeparatorPos = uri.lastIndexOf('/');
+ if (lastDirSeparatorPos >= 0) {
+ uri = uri.copy(0, lastDirSeparatorPos + 1);
+ }
+ return uri;
+ }
+
+ OUString getExecutableName()
+ {
+ OUString uri;
+ if (osl_getExecutableFile(&uri.pData) != osl_Process_E_None) {
+ abort();
+ }
+ return uri.copy(uri.lastIndexOf('/') + 1);
+ }
+
+ void setFontConfigConf(const OUString &execdir)
+ {
+ osl::File aFontConfig("file:///tmp/wmffuzzerfonts.conf");
+ if (aFontConfig.open(osl_File_OpenFlag_Create | osl_File_OpenFlag_Write) == osl::File::E_None)
+ {
+ OUString sExecDir;
+ osl::FileBase::getSystemPathFromFileURL(execdir, sExecDir);
+
+ OStringBuffer aBuffer("<?xml version=\"1.0\"?>\n<fontconfig><dir>");
+ aBuffer.append(OUStringToOString(sExecDir, osl_getThreadTextEncoding()))
+ .append(OUStringToOString(getExecutableName(), osl_getThreadTextEncoding())).append(".fonts");
+ aBuffer.append("</dir><cachedir>/tmp/cache/fontconfig</cachedir></fontconfig>");
+ OString aConf = aBuffer.makeStringAndClear();
+ sal_uInt64 aBytesWritten;
+ aFontConfig.write(aConf.getStr(), aConf.getLength(), aBytesWritten);
+ assert(aBytesWritten == aConf.getLength());
+ }
+ setenv("FONTCONFIG_FILE", "/tmp/wmffuzzerfonts.conf", 0);
+ }
+}
+
+extern "C"
+{
+ __attribute__((weak)) void __lsan_disable();
+ __attribute__((weak)) void __lsan_enable();
+}
+
+void CommonInitialize(int *argc, char ***argv)
+{
+ setenv("SAL_USE_VCLPLUGIN", "svp", 1);
+ setenv("JPEGMEM", "768M", 1);
+ setenv("JSIMD_FORCENONE", "1", 1); // https://github.com/libjpeg-turbo/libjpeg-turbo/issues/253
+ setenv("SC_MAX_MATRIX_ELEMENTS", "60000000", 1);
+ setenv("SC_NO_THREADED_CALCULATION", "1", 1);
+ setenv("SAL_DISABLE_PRINTERLIST", "1", 1);
+ setenv("SAL_DISABLE_DEFAULTPRINTER", "1", 1);
+ setenv("SAL_NO_FONT_LOOKUP", "1", 1);
+ setenv("SAX_DISABLE_THREADS", "1", 1);
+
+ //allow bubbling of max input len to fuzzer targets
+ int nMaxLen = 0;
+ for (int i = 0; i < *argc; ++i)
+ {
+ if (strncmp((*argv)[i], "-max_len=", 9) == 0)
+ nMaxLen = atoi((*argv)[i] + 9);
+ }
+ setenv("FUZZ_MAX_INPUT_LEN", "1", nMaxLen);
+
+ osl_setCommandArgs(*argc, *argv);
+
+ OUString sExecDir = getExecutableDir();
+ rtl::Bootstrap::set("BRAND_BASE_DIR", sExecDir);
+ setFontConfigConf(sExecDir);
+
+ tools::extendApplicationEnvironment();
+
+ Reference< XComponentContext > xContext =
+ defaultBootstrap_InitialComponentContext(sExecDir + getExecutableName() + ".unorc");
+ Reference< XMultiServiceFactory > xServiceManager( xContext->getServiceManager(), UNO_QUERY );
+ if( !xServiceManager.is() )
+ Application::Abort( "Failed to bootstrap" );
+ comphelper::setProcessServiceFactory( xServiceManager );
+ utl::ConfigManager::EnableFuzzing();
+ Application::EnableHeadlessMode(false);
+ InitVCL();
+
+ //we don't have a de-init, so inside this leak disabled region...
+ //get the font info
+ psp::PrintFontManager::get();
+ //get the printer info
+ Printer::GetPrinterQueues();
+
+ //https://github.com/google/oss-fuzz/issues/1449
+ //https://github.com/google/oss-fuzz/issues/5441
+ //release the solarmutex so a fork can acquire it which should
+ //allow these fuzzers to work without AFL_DRIVER_DONT_DEFER set
+ //removing the confusion of #5441 and the need for AFL_DRIVER_DONT_DEFER
+ //in .options files
+ Application::ReleaseSolarMutex();
+}
+
+void TypicalFuzzerInitialize(int *argc, char ***argv)
+{
+ if (__lsan_disable)
+ __lsan_disable();
+
+ CommonInitialize(argc, argv);
+
+ if (__lsan_enable)
+ __lsan_enable();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/dbffuzzer.cxx b/vcl/workben/dbffuzzer.cxx
new file mode 100644
index 0000000000..089e1f397a
--- /dev/null
+++ b/vcl/workben/dbffuzzer.cxx
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/FilterConfigItem.hxx>
+#include <com/sun/star/ucb/XContentProvider.hpp>
+#include <com/sun/star/ucb/XUniversalContentBroker.hpp>
+#include "commonfuzzer.hxx"
+
+extern "C" void* ScCreateDialogFactory() { return nullptr; }
+
+extern "C" bool TestImportDBF(SvStream& rStream);
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
+{
+ if (__lsan_disable)
+ __lsan_disable();
+
+ CommonInitialize(argc, argv);
+
+ // initialise unconfigured UCB:
+ css::uno::Reference<css::ucb::XUniversalContentBroker> xUcb(
+ comphelper::getProcessServiceFactory()->createInstance(
+ "com.sun.star.ucb.UniversalContentBroker"),
+ css::uno::UNO_QUERY_THROW);
+ css::uno::Sequence<css::uno::Any> aArgs{ css::uno::Any(OUString("NoConfig")) };
+ css::uno::Reference<css::ucb::XContentProvider> xFileProvider(
+ comphelper::getProcessServiceFactory()->createInstanceWithArguments(
+ "com.sun.star.ucb.FileContentProvider", aArgs),
+ css::uno::UNO_QUERY_THROW);
+ xUcb->registerContentProvider(xFileProvider, "file", true);
+
+ if (__lsan_enable)
+ __lsan_enable();
+
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportDBF(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/dbffuzzer.options b/vcl/workben/dbffuzzer.options
new file mode 100644
index 0000000000..f41b545a43
--- /dev/null
+++ b/vcl/workben/dbffuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 32768
diff --git a/vcl/workben/diffuzzer.cxx b/vcl/workben/diffuzzer.cxx
new file mode 100644
index 0000000000..60d1190fc2
--- /dev/null
+++ b/vcl/workben/diffuzzer.cxx
@@ -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/.
+ */
+
+#include <tools/stream.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void* i18npool_component_getFactory(const char*, void*, void*);
+
+void* com_sun_star_i18n_LocaleDataImpl_get_implementation(void*, void*);
+void* com_sun_star_i18n_BreakIterator_Unicode_get_implementation(void*, void*);
+void* com_sun_star_i18n_BreakIterator_get_implementation(void*, void*);
+void* com_sun_star_comp_framework_Desktop_get_implementation(void*, void*);
+void* com_sun_star_i18n_CharacterClassification_Unicode_get_implementation(void*, void*);
+void* com_sun_star_i18n_CharacterClassification_get_implementation(void*, void*);
+void* com_sun_star_i18n_Collator_get_implementation(void*, void*);
+void* com_sun_star_i18n_NativeNumberSupplier_get_implementation(void*, void*);
+void* com_sun_star_i18n_NumberFormatCodeMapper_get_implementation(void*, void*);
+void* com_sun_star_i18n_Transliteration_get_implementation(void*, void*);
+void* i18npool_CalendarImpl_get_implementation(void*, void*);
+}
+
+const lib_to_factory_mapping* lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[]
+ = { { "libi18npoollo.a", i18npool_component_getFactory }, { 0, 0 } };
+
+ return map;
+}
+
+const lib_to_constructor_mapping* lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[]
+ = { { "com_sun_star_i18n_LocaleDataImpl_get_implementation",
+ com_sun_star_i18n_LocaleDataImpl_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_Unicode_get_implementation",
+ com_sun_star_i18n_BreakIterator_Unicode_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_get_implementation",
+ com_sun_star_i18n_BreakIterator_get_implementation },
+ { "com_sun_star_comp_framework_Desktop_get_implementation",
+ com_sun_star_comp_framework_Desktop_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_Unicode_get_implementation",
+ com_sun_star_i18n_CharacterClassification_Unicode_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_get_implementation",
+ com_sun_star_i18n_CharacterClassification_get_implementation },
+ { "com_sun_star_i18n_Collator_get_implementation",
+ com_sun_star_i18n_Collator_get_implementation },
+ { "com_sun_star_i18n_NativeNumberSupplier_get_implementation",
+ com_sun_star_i18n_NativeNumberSupplier_get_implementation },
+ { "com_sun_star_i18n_NumberFormatCodeMapper_get_implementation",
+ com_sun_star_i18n_NumberFormatCodeMapper_get_implementation },
+ { "com_sun_star_i18n_Transliteration_get_implementation",
+ com_sun_star_i18n_Transliteration_get_implementation },
+ { "i18npool_CalendarImpl_get_implementation",
+ i18npool_CalendarImpl_get_implementation },
+ { 0, 0 } };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*) { return nullptr; }
+
+extern "C" void* ScCreateDialogFactory() { return nullptr; }
+
+extern "C" bool TestImportDIF(SvStream& rStream);
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportDIF(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/diffuzzer.options b/vcl/workben/diffuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/diffuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/docxfuzzer.cxx b/vcl/workben/docxfuzzer.cxx
new file mode 100644
index 0000000000..73fc198bd8
--- /dev/null
+++ b/vcl/workben/docxfuzzer.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 <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+extern "C" void* SwCreateDialogFactory()
+{
+ return nullptr;
+}
+
+extern "C" bool TestImportDOCX(SvStream &rStream);
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportDOCX(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/docxfuzzer.options b/vcl/workben/docxfuzzer.options
new file mode 100644
index 0000000000..e8c2b812b0
--- /dev/null
+++ b/vcl/workben/docxfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 98304
diff --git a/vcl/workben/dtrans/makefile.mk b/vcl/workben/dtrans/makefile.mk
new file mode 100644
index 0000000000..588d4685a3
--- /dev/null
+++ b/vcl/workben/dtrans/makefile.mk
@@ -0,0 +1,44 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+PRJ=..$/..
+
+PRJNAME= dtrans
+TARGET= test_dtrans
+
+LIBTARGET=NO
+TARGETTYPE=CUI
+ENABLE_EXCEPTIONS=TRUE
+
+# --- Settings -----------------------------------------------------
+.INCLUDE : settings.mk
+
+# --- Files --------------------------------------------------------
+
+APP1TARGET= $(TARGET)
+APP1OBJS= \
+ $(OBJ)$/test_dtrans.obj
+
+APP1STDLIBS= \
+ $(SALLIB) \
+ $(CPPULIB) \
+ $(CPPUHELPERLIB)
+
+# --- Targets ------------------------------------------------------
+.INCLUDE : target.mk
+
diff --git a/vcl/workben/dtrans/test_dtrans.cxx b/vcl/workben/dtrans/test_dtrans.cxx
new file mode 100644
index 0000000000..f531f12fd1
--- /dev/null
+++ b/vcl/workben/dtrans/test_dtrans.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 <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardManager.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardOwner.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+
+#include <cppuhelper/servicefactory.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <rtl/ustring.hxx>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+// my defines
+
+#ifdef UNX
+#define PATH_SEPARATOR '/'
+#else
+#define PATH_SEPARATOR '\\'
+#endif
+
+#define ENSURE( a, b ) if( !a ) { fprintf( stderr, b "\n" ); exit( -1 ); }
+#define TEST( a, b ) fprintf( stderr, "Testing " a ); fprintf( stderr, b ? "passed\n" : "FAILED\n" )
+#define PERFORM( a, b ) fprintf( stderr, "Performing " a); b; fprintf( stderr, "done\n" )
+#define TRACE( a ) fprintf( stderr, a )
+
+// namespaces
+
+using namespace ::std;
+using namespace ::cppu;
+using namespace ::com::sun::star::container;
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::datatransfer::clipboard;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::io;
+using namespace ::com::sun::star::lang;
+
+// globals
+
+const char * app = NULL;
+
+// ClipboardOwner
+
+class ClipboardOwner : public WeakImplHelper< XClipboardOwner >
+{
+ Reference< XClipboard > m_xClipboard;
+ Reference< XTransferable > m_xTransferable;
+
+ sal_uInt32 m_nReceivedLostOwnerships;
+
+public:
+ ClipboardOwner();
+
+ // XClipboardOwner
+
+ virtual void SAL_CALL lostOwnership( const Reference< XClipboard >& xClipboard, const Reference< XTransferable >& xTrans ) throw(RuntimeException);
+
+ sal_uInt32 receivedLostOwnerships() { return m_nReceivedLostOwnerships; };
+ Reference< XClipboard > lostOwnershipClipboardValue() { return m_xClipboard; }
+ Reference< XTransferable > lostOwnershipTransferableValue() { return m_xTransferable; };
+};
+
+// ctor
+
+ClipboardOwner::ClipboardOwner():
+ m_nReceivedLostOwnerships( 0 )
+{
+}
+
+// lostOwnership
+
+void SAL_CALL ClipboardOwner::lostOwnership( const Reference< XClipboard >& xClipboard, const Reference< XTransferable >& xTrans )
+ throw(RuntimeException)
+{
+ m_nReceivedLostOwnerships++;
+ m_xClipboard = xClipboard;
+ m_xTransferable = xTrans;
+}
+
+// ClipboardListener
+
+class ClipboardListener : public WeakImplHelper< XClipboardListener >
+{
+ Reference< XClipboard > m_xClipboard;
+ Reference< XTransferable > m_xTransferable;
+
+ sal_uInt32 m_nReceivedChangedContentsEvents;
+
+public:
+ ClipboardListener();
+
+ // XClipboardOwner
+
+ virtual void SAL_CALL changedContents( const ClipboardEvent& event ) throw(RuntimeException);
+
+ // XEventListener
+
+ virtual void SAL_CALL disposing( const EventObject& event ) throw(RuntimeException);
+
+ sal_uInt32 receivedChangedContentsEvents() { return m_nReceivedChangedContentsEvents; };
+ Reference< XClipboard > changedContentsEventClipboardValue() { return m_xClipboard; }
+ Reference< XTransferable > changedContentsEventTransferableValue() { return m_xTransferable; };
+};
+
+// ctor
+
+ClipboardListener::ClipboardListener():
+ m_nReceivedChangedContentsEvents( 0 )
+{
+}
+
+// changedContents
+
+void SAL_CALL ClipboardListener::changedContents( const ClipboardEvent& event )
+ throw(RuntimeException)
+{
+ m_nReceivedChangedContentsEvents++;
+ m_xClipboard.set(event.Source, UNO_QUERY);
+ m_xTransferable = event.Contents;
+}
+
+// disposing
+
+void SAL_CALL ClipboardListener::disposing( const EventObject& event )
+ throw(RuntimeException)
+{
+}
+
+// StringTransferable
+
+class StringTransferable : public WeakImplHelper< XClipboardOwner, XTransferable >
+{
+public:
+ StringTransferable( );
+
+ // XTransferable
+
+ virtual Any SAL_CALL getTransferData( const DataFlavor& aFlavor ) throw(UnsupportedFlavorException, IOException, RuntimeException);
+ virtual Sequence< DataFlavor > SAL_CALL getTransferDataFlavors( ) throw(RuntimeException);
+ virtual sal_Bool SAL_CALL isDataFlavorSupported( const DataFlavor& aFlavor ) throw(RuntimeException);
+
+ // XClipboardOwner
+
+ virtual void SAL_CALL lostOwnership( const Reference< XClipboard >& xClipboard, const Reference< XTransferable >& xTrans ) throw(RuntimeException);
+
+ sal_Bool receivedLostOwnership() { return m_receivedLostOwnership; };
+ void clearReceivedLostOwnership() { m_receivedLostOwnership = sal_False; };
+
+private:
+ Sequence< DataFlavor > m_seqDFlv;
+ OUString m_Data;
+ sal_Bool m_receivedLostOwnership;
+};
+
+// ctor
+
+StringTransferable::StringTransferable( ) :
+ m_seqDFlv( 1 ),
+ m_receivedLostOwnership( sal_False ),
+ m_Data( OUString("clipboard test content") )
+{
+ DataFlavor df;
+
+ /*
+ df.MimeType = L"text/plain; charset=unicode";
+ df.DataType = cppu::UnoType<OUString>::get();
+
+ m_seqDFlv[0] = df;
+ */
+
+ //df.MimeType = L"text/plain; charset=windows1252";
+ df.MimeType = "text/html";
+ df.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ m_seqDFlv[0] = df;
+}
+
+// getTransferData
+
+Any SAL_CALL StringTransferable::getTransferData( const DataFlavor& aFlavor )
+ throw(UnsupportedFlavorException, IOException, RuntimeException)
+{
+ Any anyData;
+
+ /*if ( aFlavor == m_seqDFlv[0] )
+ {
+ anyData = makeAny( m_Data );
+ } */
+
+ return anyData;
+}
+
+// getTransferDataFlavors
+
+Sequence< DataFlavor > SAL_CALL StringTransferable::getTransferDataFlavors( )
+ throw(RuntimeException)
+{
+ return m_seqDFlv;
+}
+
+// isDataFlavorSupported
+
+sal_Bool SAL_CALL StringTransferable::isDataFlavorSupported( const DataFlavor& aFlavor )
+ throw(RuntimeException)
+{
+ sal_Int32 nLength = m_seqDFlv.getLength( );
+ sal_Bool bRet = sal_False;
+
+// for ( sal_Int32 i = 0; i < nLength; ++i )
+// {
+// if ( m_seqDFlv[i] == aFlavor )
+// {
+// bRet = sal_True;
+// break;
+// }
+// }
+
+ return bRet;
+}
+
+// lostOwnership
+
+void SAL_CALL StringTransferable::lostOwnership( const Reference< XClipboard >& xClipboard, const Reference< XTransferable >& xTrans )
+ throw(RuntimeException)
+{
+ m_receivedLostOwnership = sal_True;
+}
+
+// main
+
+int SAL_CALL main( int argc, const char* argv[] )
+{
+ OUString aRegistry;
+
+ // check command line parameters
+
+ if ( NULL == ( app = strrchr( argv[0], PATH_SEPARATOR ) ) )
+ app = argv[0];
+ else
+ app++;
+
+ for( int n = 1; n < argc; n++ )
+ {
+ if( strncmp( argv[n], "-r", 2 ) == 0 )
+ {
+ if( strlen( argv[n] ) > 2 )
+ aRegistry = OUString::createFromAscii( argv[n] + 2 );
+ else if ( n + 1 < argc )
+ aRegistry = OUString::createFromAscii( argv[++n] );
+ }
+ }
+
+ if( aRegistry.isEmpty( ) )
+ fprintf( stderr, "Usage: %s -r full-path-to-applicat.rdb\n", app );
+
+ // create service manager
+
+ Reference< XMultiServiceFactory > xServiceManager;
+
+ try
+ {
+ xServiceManager = createRegistryServiceFactory( aRegistry, sal_True );
+ ENSURE( xServiceManager.is(), "*** ERROR *** service manager could not be created." );
+
+ // create an instance of GenericClipboard service
+
+ Sequence< Any > arguments(1);
+ arguments[0] = makeAny( OUString("generic") );
+
+ Reference< XClipboard > xClipboard( xServiceManager->createInstanceWithArguments(
+ "com.sun.star.datatransfer.clipboard.GenericClipboard",
+ arguments ), UNO_QUERY );
+
+ ENSURE( xClipboard.is(), "*** ERROR *** generic clipboard service could not be created." );
+
+ Reference< XClipboardNotifier > xClipboardNotifier( xClipboard, UNO_QUERY );
+ Reference< XClipboardListener > xClipboardListener = new ClipboardListener();
+ ClipboardListener * pListener = (ClipboardListener *) xClipboardListener.get();
+
+ if( xClipboardNotifier.is() )
+ xClipboardNotifier->addClipboardListener( xClipboardListener );
+
+ // run various tests on clipboard implementation
+
+ TRACE( "\n*** testing generic clipboard service ***\n" );
+
+ Reference< XTransferable > xContents = new StringTransferable();
+ Reference< XClipboardOwner > xOwner = new ClipboardOwner();
+ ClipboardOwner *pOwner = (ClipboardOwner *) xOwner.get();
+
+ TEST( "initial contents (none): ", xClipboard->getContents().is() == sal_False );
+
+ PERFORM( "update on contents with clipboard owner: ", xClipboard->setContents( xContents, xOwner ) );
+ TEST( "current clipboard contents: ", xContents == xClipboard->getContents() );
+
+ if( xClipboardNotifier.is() )
+ {
+ TEST( "if received changedContents notifications: ", pListener->receivedChangedContentsEvents() > 0 );
+ TEST( "if received exactly 1 changedContents notification: ", pListener->receivedChangedContentsEvents() == 1 );
+ TEST( "if received changedContents notification for correct clipboard: ", pListener->changedContentsEventClipboardValue() == xClipboard );
+ TEST( "if received changedContents notification for correct clipboard: ", pListener->changedContentsEventTransferableValue() == xContents );
+ }
+
+ PERFORM( "update on contents without data (clear): ", xClipboard->setContents( Reference< XTransferable >(), Reference< XClipboardOwner >() ) );
+ TEST( "if received lostOwnership message(s): ", pOwner->receivedLostOwnerships() > 0 );
+ TEST( "if received exactly 1 lostOwnership message: ", pOwner->receivedLostOwnerships() == 1 );
+ TEST( "if received lostOwnership message for the correct clipboard: ", pOwner->lostOwnershipClipboardValue() == xClipboard );
+ TEST( "if received lostOwnership message for the correct transferable: ", pOwner->lostOwnershipTransferableValue() == xContents );
+ TEST( "current clipboard contents (none): ", xClipboard->getContents().is() == sal_False );
+
+ if( xClipboardNotifier.is() )
+ {
+ TEST( "if received changedContents notifications: ", pListener->receivedChangedContentsEvents() > 1 );
+ TEST( "if received exactly 1 changedContents notification: ", pListener->receivedChangedContentsEvents() == 2 );
+ TEST( "if received changedContents notification for correct clipboard: ", pListener->changedContentsEventClipboardValue() == xClipboard );
+ TEST( "if received changedContents notification for correct transferable: ", ! pListener->changedContentsEventTransferableValue().is() );
+ }
+
+ PERFORM( "update on contents without clipboard owner: ", xClipboard->setContents( xContents, Reference< XClipboardOwner >() ) );
+ TEST( "that no further lostOwnership messages were received: ", pOwner->receivedLostOwnerships() == 1 );
+ TEST( "current clipboard contents: ", xContents == xClipboard->getContents() );
+
+ if( xClipboardNotifier.is() )
+ {
+ TEST( "if received changedContents notifications: ", pListener->receivedChangedContentsEvents() > 2 );
+ TEST( "if received exactly 1 changedContents notification: ", pListener->receivedChangedContentsEvents() == 3 );
+ TEST( "if received changedContents notification for correct clipboard: ", pListener->changedContentsEventClipboardValue() == xClipboard );
+ TEST( "if received changedContents notification for correct transferable: ", pListener->changedContentsEventTransferableValue() == xContents );
+ }
+
+ PERFORM( "update on contents without data (clear): ", xClipboard->setContents( Reference< XTransferable >(), Reference< XClipboardOwner >() ) );
+ TEST( "that no further lostOwnership messages were received: ", pOwner->receivedLostOwnerships() == 1 );
+ TEST( "current clipboard contents (none): ", xClipboard->getContents().is() == sal_False );
+
+ if( xClipboardNotifier.is() )
+ {
+ TEST( "if received changedContents notifications: ", pListener->receivedChangedContentsEvents() > 3 );
+ TEST( "if received exactly 1 changedContents notification: ", pListener->receivedChangedContentsEvents() == 4 );
+ TEST( "if received changedContents notification for correct clipboard: ", pListener->changedContentsEventClipboardValue() == xClipboard );
+ TEST( "if received changedContents notification for correct transferable: ", ! pListener->changedContentsEventTransferableValue().is() );
+ }
+
+ // create an instance of ClipboardManager service
+
+ Reference< XClipboardManager > xClipboardManager( xServiceManager->createInstance(
+ "com.sun.star.datatransfer.clipboard.ClipboardManager" ), UNO_QUERY );
+
+ ENSURE( xClipboardManager.is(), "*** ERROR *** clipboard manager service could not be created." );
+
+ // run various tests on clipboard manager implementation
+
+ TRACE( "\n*** testing clipboard manager service ***\n" );
+
+ TEST( "initial number of clipboards (0): ", xClipboardManager->listClipboardNames().getLength() == 0 );
+ PERFORM( "insertion of generic clipboard: ", xClipboardManager->addClipboard( xClipboard ) );
+ TEST( "number of inserted clipboards (1): ", xClipboardManager->listClipboardNames().getLength() == 1 );
+ TEST( "name of inserted clipboard (generic): ", xClipboardManager->listClipboardNames()[0] == "generic" );
+ TEST( "inserted clipboard instance: ", xClipboardManager->getClipboard( OUString("generic") ) == xClipboard );
+ PERFORM( "removal of generic clipboard: ", xClipboardManager->removeClipboard( OUString("generic") ) );
+ TEST( "number of inserted clipboards (0): ", xClipboardManager->listClipboardNames().getLength() == 0 );
+ TRACE( "Testing inserted clipboard instance (none): " );
+ try
+ {
+ xClipboardManager->getClipboard( OUString("generic") );
+ TRACE( "FAILED\n" );
+ }
+ catch (const NoSuchElementException&)
+ {
+ TRACE( "passed\n" );
+ }
+ }
+
+ catch (const Exception&)
+ {
+ ENSURE( sal_False, "*** ERROR *** exception caught." );
+ }
+
+ // shutdown the service manager
+
+ // query XComponent interface
+ Reference< XComponent > xComponent( xServiceManager, UNO_QUERY );
+
+ ENSURE( xComponent.is(), "*** ERROR *** service manager does not support XComponent." );
+
+ // Dispose and clear factory
+ xComponent->dispose();
+ xServiceManager.clear();
+
+ fprintf( stderr, "Done.\n" );
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/dxffuzzer.cxx b/vcl/workben/dxffuzzer.cxx
new file mode 100644
index 0000000000..89d6e19a69
--- /dev/null
+++ b/vcl/workben/dxffuzzer.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 <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+#include <filter/DxfReader.hxx>
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * com_sun_star_i18n_LocaleDataImpl_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_get_implementation( void *, void * );
+void * com_sun_star_i18n_NativeNumberSupplier_get_implementation( void *, void * );
+void * com_sun_star_i18n_NumberFormatCodeMapper_get_implementation( void *, void * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { "com_sun_star_i18n_LocaleDataImpl_get_implementation", com_sun_star_i18n_LocaleDataImpl_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_Unicode_get_implementation", com_sun_star_i18n_BreakIterator_Unicode_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_get_implementation", com_sun_star_i18n_BreakIterator_get_implementation },
+ { "com_sun_star_i18n_NativeNumberSupplier_get_implementation", com_sun_star_i18n_NativeNumberSupplier_get_implementation },
+ { "com_sun_star_i18n_NumberFormatCodeMapper_get_implementation", com_sun_star_i18n_NumberFormatCodeMapper_get_implementation },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportDxfGraphic(aStream, aGraphic);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/dxffuzzer.options b/vcl/workben/dxffuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/dxffuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/epsfuzzer.cxx b/vcl/workben/epsfuzzer.cxx
new file mode 100644
index 0000000000..66e9e380ef
--- /dev/null
+++ b/vcl/workben/epsfuzzer.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 <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+#include <filter/EpsReader.hxx>
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * com_sun_star_i18n_LocaleDataImpl_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_get_implementation( void *, void * );
+void * com_sun_star_i18n_NativeNumberSupplier_get_implementation( void *, void * );
+void * com_sun_star_i18n_NumberFormatCodeMapper_get_implementation( void *, void * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { "com_sun_star_i18n_LocaleDataImpl_get_implementation", com_sun_star_i18n_LocaleDataImpl_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_Unicode_get_implementation", com_sun_star_i18n_BreakIterator_Unicode_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_get_implementation", com_sun_star_i18n_BreakIterator_get_implementation },
+ { "com_sun_star_i18n_NativeNumberSupplier_get_implementation", com_sun_star_i18n_NativeNumberSupplier_get_implementation },
+ { "com_sun_star_i18n_NumberFormatCodeMapper_get_implementation", com_sun_star_i18n_NumberFormatCodeMapper_get_implementation },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportEpsGraphic(aStream, aGraphic);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/epsfuzzer.options b/vcl/workben/epsfuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/epsfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/fftester.cxx b/vcl/workben/fftester.cxx
new file mode 100644
index 0000000000..44fb5338f8
--- /dev/null
+++ b/vcl/workben/fftester.cxx
@@ -0,0 +1,575 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+ /* e.g.
+ make
+ cp workdir/LinkTarget/Executable/fftester instdir/program
+ LD_LIBRARY_PATH=`pwd`/instdir/program instdir/program/fftester <foo> png
+ or on macOS
+ make
+ cp workdir/LinkTarget/Executable/fftester instdir/LibreOfficeDev.app/Contents/MacOS/
+ DYLD_LIBRARY_PATH=`pwd`/instdir/LibreOfficeDev.app/Contents/Frameworks instdir/LibreOfficeDev.app/Contents/MacOS/fftester <foo> png
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <sal/main.h>
+#include <tools/extendapplicationenvironment.hxx>
+
+#include <cppuhelper/bootstrap.hxx>
+#include <comphelper/processfactory.hxx>
+
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/ucb/XContentProvider.hpp>
+#include <com/sun/star/ucb/XUniversalContentBroker.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <unotools/configmgr.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/event.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+#include <vcl/filter/SvmReader.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/wmf.hxx>
+#include <vcl/wrkwin.hxx>
+#include <fltcall.hxx>
+#include <filter/TiffReader.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/PsdReader.hxx>
+#include <filter/PcdReader.hxx>
+#include <filter/PbmReader.hxx>
+#include <filter/DxfReader.hxx>
+#include <filter/WebpReader.hxx>
+#include <filter/XpmReader.hxx>
+#include <osl/file.hxx>
+#include <osl/module.hxx>
+#include <rtl/bootstrap.hxx>
+#include <tools/stream.hxx>
+#include <vcl/gdimtf.hxx>
+#include <fontsubset.hxx>
+
+#include "../source/filter/igif/gifread.hxx"
+#include "../source/filter/ixbm/xbmread.hxx"
+#include "../source/filter/jpeg/jpeg.hxx"
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace cppu;
+
+typedef bool (*FFilterCall)(SvStream &rStream);
+
+#ifndef DISABLE_DYNLOADING
+namespace {
+
+FFilterCall load(std::u16string_view library, char const * function) {
+ OUString path = OUString::Concat("$LO_LIB_DIR/") + library;
+ rtl::Bootstrap::expandMacros(path); //TODO: check for failure
+ osl::Module aLibrary(path, SAL_LOADMODULE_LAZY);
+ auto const fn = reinterpret_cast<FFilterCall>(aLibrary.getFunctionSymbol(function));
+ aLibrary.release();
+ return fn;
+}
+
+}
+#endif
+
+SAL_IMPLEMENT_MAIN_WITH_ARGS(argc, argv)
+{
+ int ret = -1;
+ try
+ {
+ if (argc < 3)
+ {
+ fprintf(stderr, "Usage: fftester <filename> <wmf|jpg>\n");
+ return -1;
+ }
+
+ setenv("SAL_USE_VCLPLUGIN", "svp", 1);
+ setenv("JPEGMEM", "768M", 1);
+ setenv("SC_MAX_MATRIX_ELEMENTS", "60000000", 1);
+ setenv("SC_NO_THREADED_CALCULATION", "1", 1);
+ setenv("SAL_DISABLE_PRINTERLIST", "1", 1);
+ setenv("SAL_DISABLE_DEFAULTPRINTER", "1", 1);
+ setenv("SAL_NO_FONT_LOOKUP", "1", 1);
+
+ OUString in(argv[1], strlen(argv[1]), RTL_TEXTENCODING_UTF8);
+ OUString out;
+ osl::File::getFileURLFromSystemPath(in, out);
+
+ tools::extendApplicationEnvironment();
+
+ Reference< XComponentContext > xContext = defaultBootstrap_InitialComponentContext();
+ Reference< XMultiServiceFactory > xServiceManager( xContext->getServiceManager(), UNO_QUERY );
+ if( !xServiceManager.is() )
+ Application::Abort( "Failed to bootstrap" );
+ comphelper::setProcessServiceFactory( xServiceManager );
+ utl::ConfigManager::EnableFuzzing();
+
+ // initialise unconfigured UCB:
+ css::uno::Reference<css::ucb::XUniversalContentBroker> xUcb(comphelper::getProcessServiceFactory()->
+ createInstance("com.sun.star.ucb.UniversalContentBroker"), css::uno::UNO_QUERY_THROW);
+ css::uno::Sequence<css::uno::Any> aArgs{ css::uno::Any(OUString("NoConfig")) };
+ css::uno::Reference<css::ucb::XContentProvider> xFileProvider(comphelper::getProcessServiceFactory()->
+ createInstanceWithArguments("com.sun.star.ucb.FileContentProvider", aArgs), css::uno::UNO_QUERY_THROW);
+ xUcb->registerContentProvider(xFileProvider, "file", true);
+
+ Application::EnableHeadlessMode(false);
+ InitVCL();
+
+ if (strcmp(argv[2], "wmf") == 0 || strcmp(argv[2], "emf") == 0)
+ {
+ GDIMetaFile aGDIMetaFile;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ReadWindowMetafile(aFileStream, aGDIMetaFile));
+ }
+ else if (strcmp(argv[2], "jpg") == 0)
+ {
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ImportJPEG(aFileStream, aGraphic, GraphicFilterImportFlags::NONE, nullptr));
+ }
+ else if (strcmp(argv[2], "gif") == 0)
+ {
+ SvFileStream aFileStream(out, StreamMode::READ);
+ Graphic aGraphic;
+ ret = static_cast<int>(ImportGIF(aFileStream, aGraphic));
+ }
+ else if (strcmp(argv[2], "xbm") == 0)
+ {
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ImportXBM(aFileStream, aGraphic));
+ }
+ else if (strcmp(argv[2], "xpm") == 0)
+ {
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ImportXPM(aFileStream, aGraphic));
+ }
+ else if (strcmp(argv[2], "png") == 0)
+ {
+ SvFileStream aFileStream(out, StreamMode::READ);
+ vcl::PngImageReader aReader(aFileStream);
+ ret = static_cast<int>(!aReader.read().IsEmpty());
+ }
+ else if (strcmp(argv[2], "bmp") == 0)
+ {
+ Bitmap aTarget;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ReadDIB(aTarget, aFileStream, true));
+ }
+ else if (strcmp(argv[2], "pcd") == 0)
+ {
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ImportPcdGraphic(aFileStream, aGraphic, nullptr));
+ }
+ else if (strcmp(argv[2], "dxf") == 0)
+ {
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ImportDxfGraphic(aFileStream, aGraphic));
+ }
+ else if (strcmp(argv[2], "met") == 0)
+ {
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ImportMetGraphic(aFileStream, aGraphic));
+ }
+ else if ((strcmp(argv[2], "pbm") == 0) || strcmp(argv[2], "ppm") == 0)
+ {
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ImportPbmGraphic(aFileStream, aGraphic));
+ }
+ else if (strcmp(argv[2], "psd") == 0)
+ {
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ImportPsdGraphic(aFileStream, aGraphic));
+ }
+ else if (strcmp(argv[2], "eps") == 0)
+ {
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ImportEpsGraphic(aFileStream, aGraphic));
+ }
+ else if (strcmp(argv[2], "pct") == 0)
+ {
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ImportPictGraphic(aFileStream, aGraphic));
+ }
+ else if (strcmp(argv[2], "pcx") == 0)
+ {
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ImportPcxGraphic(aFileStream, aGraphic));
+ }
+ else if (strcmp(argv[2], "ras") == 0)
+ {
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ImportRasGraphic(aFileStream, aGraphic));
+ }
+ else if (strcmp(argv[2], "tga") == 0)
+ {
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ImportTgaGraphic(aFileStream, aGraphic));
+ }
+ else if (strcmp(argv[2], "tif") == 0)
+ {
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ImportTiffGraphicImport(aFileStream, aGraphic));
+ }
+ else if (strcmp(argv[2], "webp") == 0)
+ {
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ImportWebpGraphic(aFileStream, aGraphic));
+ }
+ else if (strcmp(argv[2], "sft") == 0)
+ {
+ SvFileStream aFileStream(out, StreamMode::READ);
+ std::vector<sal_uInt8> aData(aFileStream.remainingSize());
+ aFileStream.ReadBytes(aData.data(), aData.size());
+ ret = TestFontSubset(aData.data(), aData.size());
+ }
+#ifndef DISABLE_DYNLOADING
+ else if ((strcmp(argv[2], "doc") == 0) || (strcmp(argv[2], "ww8") == 0))
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libmswordlo.so", "TestImportWW8");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "ww6") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libmswordlo.so", "TestImportWW6");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "ww2") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libmswordlo.so", "TestImportWW2");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "rtf") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libmswordlo.so", "TestImportRTF");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "html") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libswlo.so", "TestImportHTML");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "fodt") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libswlo.so", "TestImportFODT");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "fodt2pdf") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libswlo.so", "TestPDFExportFODT");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "docx") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libswlo.so", "TestImportDOCX");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "fods") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libsclo.so", "TestImportFODS");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "xlsx") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libsclo.so", "TestImportXLSX");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "fodp") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libsdlo.so", "TestImportFODP");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "pptx") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libsdlo.so", "TestImportPPTX");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "xls") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libscfiltlo.so", "TestImportXLS");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "wks") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libscfiltlo.so", "TestImportWKS");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "hwp") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libhwplo.so", "TestImportHWP");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "602") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libt602filterlo.so", "TestImport602");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "lwp") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"liblwpftlo.so", "TestImportLWP");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "ppt") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libsdlo.so", "TestImportPPT");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "cgm") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libsdlo.so", "TestImportCGM");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "qpw") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libscfiltlo.so", "TestImportQPW");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "dbf") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libsclo.so", "TestImportDBF");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "dif") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libscfiltlo.so", "TestImportDIF");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "sc-rtf") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libscfiltlo.so", "TestImportCalcRTF");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "slk") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libsclo.so", "TestImportSLK");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "ole") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libsotlo.so", "TestImportOLE2");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "mml") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libsmlo.so", "TestImportMML");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "mtp") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libsmlo.so", "TestImportMathType");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "svm") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libvcllo.so", "TestImportSVM");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "zip") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libpackage2.so", "TestImportZip");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+ else if (strcmp(argv[2], "svg") == 0)
+ {
+ static FFilterCall pfnImport(nullptr);
+ if (!pfnImport)
+ {
+ pfnImport = load(u"libsvgiolo.so", "TestImportSVG");
+ }
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>((*pfnImport)(aFileStream));
+ }
+#endif
+ }
+ catch (...)
+ {
+ abort();
+ }
+
+ return ret;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/fodpfuzzer.cxx b/vcl/workben/fodpfuzzer.cxx
new file mode 100644
index 0000000000..b51efac1c6
--- /dev/null
+++ b/vcl/workben/fodpfuzzer.cxx
@@ -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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+extern "C" bool TestImportFODP(SvStream &rStream);
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" void* SdCreateDialogFactory()
+{
+ return nullptr;
+}
+
+extern "C" void* com_sun_star_comp_Draw_VisioImportFilter_get_implementation()
+{
+ return nullptr;
+}
+
+extern "C" void* sdext_PDFIHybridAdaptor_get_implementation()
+{
+ return nullptr;
+}
+
+extern "C" void* sdext_PDFIRawAdaptor_Writer_get_implementation()
+{
+ return nullptr;
+}
+
+extern "C" void* sdext_PDFIRawAdaptor_Draw_get_implementation()
+{
+ return nullptr;
+}
+
+extern "C" void* sdext_PDFIRawAdaptor_Impress_get_implementation()
+{
+ return nullptr;
+}
+
+extern "C" void* sdext_PDFDetector_get_implementation()
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportFODP(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/fodpfuzzer.options b/vcl/workben/fodpfuzzer.options
new file mode 100644
index 0000000000..1d9660180c
--- /dev/null
+++ b/vcl/workben/fodpfuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+dict = odf.dict
diff --git a/vcl/workben/fodsfuzzer.cxx b/vcl/workben/fodsfuzzer.cxx
new file mode 100644
index 0000000000..ed9efd6e39
--- /dev/null
+++ b/vcl/workben/fodsfuzzer.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 <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+extern "C" void* ScCreateDialogFactory()
+{
+ return nullptr;
+}
+
+extern "C" bool TestImportFODS(SvStream &rStream);
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportFODS(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/fodsfuzzer.options b/vcl/workben/fodsfuzzer.options
new file mode 100644
index 0000000000..1d9660180c
--- /dev/null
+++ b/vcl/workben/fodsfuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+dict = odf.dict
diff --git a/vcl/workben/fodt2pdffuzzer.cxx b/vcl/workben/fodt2pdffuzzer.cxx
new file mode 100644
index 0000000000..0b6323a61a
--- /dev/null
+++ b/vcl/workben/fodt2pdffuzzer.cxx
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/FilterConfigItem.hxx>
+#include <com/sun/star/awt/XToolkit.hpp>
+#include <com/sun/star/ucb/XContentProvider.hpp>
+#include <com/sun/star/ucb/XUniversalContentBroker.hpp>
+#include <libxml/parser.h>
+#include "commonfuzzer.hxx"
+
+extern "C" void* SwCreateDialogFactory() { return nullptr; }
+
+extern "C" bool TestPDFExportFODT(SvStream& rStream);
+
+static void silent_error_func(void*, const char* /*format*/, ...) {}
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
+{
+ if (__lsan_disable)
+ __lsan_disable();
+
+ CommonInitialize(argc, argv);
+
+ // initialise unconfigured UCB:
+ css::uno::Reference<css::ucb::XUniversalContentBroker> xUcb(
+ comphelper::getProcessServiceFactory()->createInstance(
+ "com.sun.star.ucb.UniversalContentBroker"),
+ css::uno::UNO_QUERY_THROW);
+ css::uno::Sequence<css::uno::Any> aArgs{ css::uno::Any(OUString("NoConfig")) };
+ css::uno::Reference<css::ucb::XContentProvider> xFileProvider(
+ comphelper::getProcessServiceFactory()->createInstanceWithArguments(
+ "com.sun.star.ucb.FileContentProvider", aArgs),
+ css::uno::UNO_QUERY_THROW);
+ xUcb->registerContentProvider(xFileProvider, "file", true);
+
+ // create and hold a reference to XToolkit here to avoid the lsan warning about its leak
+ // due to getting created in the unusual case of no vcl main loop
+ static css::uno::Reference<css::awt::XToolkit> xTk(
+ comphelper::getProcessServiceFactory()->createInstance("com.sun.star.awt.Toolkit"),
+ css::uno::UNO_QUERY_THROW);
+
+ if (__lsan_enable)
+ __lsan_enable();
+
+ xmlInitParser();
+ xmlSetGenericErrorFunc(nullptr, silent_error_func);
+
+ return 0;
+}
+
+extern "C" size_t LLVMFuzzerMutate(uint8_t* Data, size_t Size, size_t MaxSize);
+
+extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* Data, size_t Size, size_t MaxSize,
+ unsigned int /*Seed*/)
+{
+ size_t Ret = LLVMFuzzerMutate(Data, Size, MaxSize);
+
+ // an effort to only generate valid xml, in this fuzzer we only really care
+ // about the deeper levels of turning valid input into writer layout and
+ // pdf export
+
+ xmlParserCtxtPtr ctxt = xmlNewParserCtxt();
+ xmlDocPtr Doc = xmlCtxtReadMemory(ctxt, reinterpret_cast<const char*>(Data), Ret, nullptr,
+ nullptr, XML_PARSE_NONET);
+ if (Doc == nullptr)
+ Ret = 0;
+ else
+ xmlFreeDoc(Doc);
+ xmlFreeParserCtxt(ctxt);
+ xmlResetLastError();
+ return Ret;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ bool bFODTLoaded = TestPDFExportFODT(aStream);
+ // if the fodt didn't load then reject so that input will not be added to the corpus
+ // we're not interested in input that doesn't go on to exercise the pdf export
+ return bFODTLoaded ? 0 : -1;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/fodt2pdffuzzer.options b/vcl/workben/fodt2pdffuzzer.options
new file mode 100644
index 0000000000..b75fa1aff3
--- /dev/null
+++ b/vcl/workben/fodt2pdffuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 32768
+dict = odf.dict
diff --git a/vcl/workben/fodtfuzzer.cxx b/vcl/workben/fodtfuzzer.cxx
new file mode 100644
index 0000000000..71c37aa7c4
--- /dev/null
+++ b/vcl/workben/fodtfuzzer.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 <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+extern "C" void* SwCreateDialogFactory()
+{
+ return nullptr;
+}
+
+extern "C" bool TestImportFODT(SvStream &rStream);
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportFODT(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/fodtfuzzer.options b/vcl/workben/fodtfuzzer.options
new file mode 100644
index 0000000000..1d9660180c
--- /dev/null
+++ b/vcl/workben/fodtfuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+dict = odf.dict
diff --git a/vcl/workben/giffuzzer.cxx b/vcl/workben/giffuzzer.cxx
new file mode 100644
index 0000000000..7a2b9a9f05
--- /dev/null
+++ b/vcl/workben/giffuzzer.cxx
@@ -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/.
+ */
+
+#include <tools/stream.hxx>
+#include <../source/filter/igif/gifread.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportGIF(aStream, aGraphic);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/giffuzzer.options b/vcl/workben/giffuzzer.options
new file mode 100644
index 0000000000..57d53b24ed
--- /dev/null
+++ b/vcl/workben/giffuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+dict = gif.dict
diff --git a/vcl/workben/htmlfuzzer.cxx b/vcl/workben/htmlfuzzer.cxx
new file mode 100644
index 0000000000..b2e8944208
--- /dev/null
+++ b/vcl/workben/htmlfuzzer.cxx
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+extern "C" void* SwCreateDialogFactory() { return nullptr; }
+
+extern "C" bool TestImportHTML(SvStream& rStream);
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportHTML(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/htmlfuzzer.options b/vcl/workben/htmlfuzzer.options
new file mode 100644
index 0000000000..32f759a096
--- /dev/null
+++ b/vcl/workben/htmlfuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 20480
+dict = html.dict
diff --git a/vcl/workben/hwpfuzzer.cxx b/vcl/workben/hwpfuzzer.cxx
new file mode 100644
index 0000000000..430b1bdfc8
--- /dev/null
+++ b/vcl/workben/hwpfuzzer.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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" bool TestImportHWP(SvStream &rStream);
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportHWP(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/hwpfuzzer.options b/vcl/workben/hwpfuzzer.options
new file mode 100644
index 0000000000..bf169a74ba
--- /dev/null
+++ b/vcl/workben/hwpfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 49152
diff --git a/vcl/workben/icontest.cxx b/vcl/workben/icontest.cxx
new file mode 100644
index 0000000000..86cf4da23e
--- /dev/null
+++ b/vcl/workben/icontest.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 is a quick hack to test some stuff. Work in progress. Don't touch
+ * and don't bother inspecting too closely.
+ *
+ * =======================================================================
+ */
+
+#include <iostream>
+
+#include <math.h>
+
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/lang/XMultiComponentFactory.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/ucb/UniversalContentBroker.hpp>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/bootstrap.hxx>
+#include <osl/file.hxx>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/toolkit/fixed.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/image.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/vclmain.hxx>
+#include <vcl/wrkwin.hxx>
+
+using namespace com::sun::star;
+
+namespace {
+ const int WIDTH = 1000, HEIGHT = 800;
+
+ double getTimeNow()
+ {
+ TimeValue aValue;
+ osl_getSystemTime(&aValue);
+ return static_cast<double>(aValue.Seconds) +
+ static_cast<double>(aValue.Nanosec) / (1000*1000*1000);
+ }
+
+class MyWorkWindow : public WorkWindow
+{
+ double mnStartTime;
+ int mnPaintCount;
+
+public:
+ Graphic maGraphic;
+ BitmapEx *mpBitmap;
+ VclPtr<FixedBitmap> mpFixedBitmap;
+
+ MyWorkWindow( vcl::Window* pParent, WinBits nWinStyle );
+ virtual ~MyWorkWindow() override { disposeOnce(); }
+ virtual void dispose() override { mpFixedBitmap.clear(); WorkWindow::dispose(); }
+ void LoadGraphic( const OUString& sImageFile );
+
+ virtual void Paint( vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& rRect ) override;
+ virtual void Resize() override;
+};
+
+}
+
+MyWorkWindow::MyWorkWindow( vcl::Window* pParent, WinBits nWinStyle )
+ : WorkWindow(pParent, nWinStyle)
+ , mpBitmap(nullptr)
+ , mpFixedBitmap(nullptr)
+{
+ mnPaintCount = 0;
+ mnStartTime = getTimeNow();
+ EnableInput();
+}
+
+void MyWorkWindow::LoadGraphic( const OUString& sImageFile )
+{
+ SvFileStream aFileStream( sImageFile, StreamMode::READ );
+ GraphicFilter aGraphicFilter(false);
+ if (aGraphicFilter.ImportGraphic(maGraphic, sImageFile, aFileStream) != ERRCODE_NONE)
+ {
+ SAL_WARN("vcl.icontest", "Could not import image '" << sImageFile << "'");
+ return;
+ }
+}
+
+void MyWorkWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ std::cout << "==> Paint! " << mnPaintCount++ << " (vcl) " << GetSizePixel() << " " << getTimeNow() - mnStartTime << std::endl;
+
+ Size aGraphicSize( maGraphic.GetSizePixel() );
+ float aspect = static_cast<float>(aGraphicSize.Width()) / aGraphicSize.Height();
+ Size aSize;
+ if( aspect >= (float(WIDTH)) / HEIGHT )
+ aSize = Size( WIDTH, HEIGHT/aspect );
+ else
+ aSize = Size( WIDTH * aspect, HEIGHT );
+ aSize.setWidth( aSize.Width() * (1 + (0.1*sin(mnPaintCount/60.))) );
+ aSize.setHeight( aSize.Height() * (1 + (0.1*sin(mnPaintCount/50.))) );
+
+ BitmapEx aEmpty;
+ mpFixedBitmap->SetBitmap( aEmpty );
+ GraphicConversionParameters aConv( aSize );
+ mpBitmap = new BitmapEx(maGraphic.GetBitmapEx( aConv ));
+ mpFixedBitmap->SetBitmap( *mpBitmap );
+ mpFixedBitmap->SetSizePixel( aSize );
+
+ WorkWindow::Paint(rRenderContext, rRect);
+
+ if (mnPaintCount == 100)
+ Application::Quit();
+
+ Invalidate( InvalidateFlags::Children );
+}
+
+void MyWorkWindow::Resize()
+{
+ SAL_INFO("vcl.icontest", "Resize " << GetSizePixel());
+}
+
+namespace {
+
+class IconTestApp : public Application
+{
+public:
+ virtual void Init() override;
+ virtual int Main() override;
+
+ IconTestApp() : nRet(EXIT_SUCCESS) {};
+
+private:
+ int nRet;
+
+ void DoItWithVcl(const OUString& sImageFile);
+};
+
+}
+
+void IconTestApp::Init()
+{
+ nRet = EXIT_SUCCESS;
+
+ uno::Reference<uno::XComponentContext> xContext =
+ cppu::defaultBootstrap_InitialComponentContext();
+ uno::Reference<lang::XMultiComponentFactory> xFactory =
+ xContext->getServiceManager();
+ uno::Reference<lang::XMultiServiceFactory> xSFactory(xFactory, uno::UNO_QUERY_THROW);
+ comphelper::setProcessServiceFactory(xSFactory);
+
+ // Create UCB (for backwards compatibility, in case some code still uses
+ // plain createInstance w/o args directly to obtain an instance):
+ ::ucb::UniversalContentBroker::create(
+ comphelper::getProcessComponentContext() );
+}
+
+int IconTestApp::Main()
+{
+ if (GetCommandLineParamCount() != 1)
+ {
+ fprintf(stderr, "Usage: imagetest <image>\n");
+ return EXIT_FAILURE;
+ }
+ OUString sImageFile( GetCommandLineParam( 0 ) );
+ DoItWithVcl( sImageFile );
+
+ return nRet;
+}
+
+void IconTestApp::DoItWithVcl( const OUString& sImageFile)
+{
+ try
+ {
+ VclPtrInstance<MyWorkWindow> pWindow( nullptr, WB_APP | WB_STDWORK | WB_SIZEABLE | WB_CLOSEABLE | WB_CLIPCHILDREN );
+
+ pWindow->SetText("VCL Image Test");
+
+ pWindow->LoadGraphic( sImageFile );
+ pWindow->mpFixedBitmap = VclPtr<FixedBitmap>::Create( pWindow );
+ pWindow->mpFixedBitmap->SetPosPixel( Point( 0, 0 ) );
+ pWindow->mpFixedBitmap->Show();
+
+ pWindow->Hide();
+ pWindow->Show();
+
+ Execute();
+ }
+ catch (const uno::Exception &e)
+ {
+ fprintf(stderr, "fatal error: %s\n", OUStringToOString(e.Message, osl_getThreadTextEncoding()).getStr());
+ nRet = EXIT_FAILURE;
+ }
+ catch (const std::exception &e)
+ {
+ fprintf(stderr, "fatal error: %s\n", e.what());
+ nRet = EXIT_FAILURE;
+ }
+}
+
+void vclmain::createApplication()
+{
+ static IconTestApp aApp;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/jpgfuzzer.cxx b/vcl/workben/jpgfuzzer.cxx
new file mode 100644
index 0000000000..5850deec58
--- /dev/null
+++ b/vcl/workben/jpgfuzzer.cxx
@@ -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/.
+ */
+
+#include <tools/stream.hxx>
+#include <../source/filter/jpeg/jpeg.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportJPEG(aStream, aGraphic, GraphicFilterImportFlags::NONE, nullptr);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/jpgfuzzer.options b/vcl/workben/jpgfuzzer.options
new file mode 100644
index 0000000000..dfcaf9cd93
--- /dev/null
+++ b/vcl/workben/jpgfuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+dict = jpeg.dict
diff --git a/vcl/workben/listfonts.cxx b/vcl/workben/listfonts.cxx
new file mode 100644
index 0000000000..49380c03bc
--- /dev/null
+++ b/vcl/workben/listfonts.cxx
@@ -0,0 +1,544 @@
+/* -*- 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 <osl/file.hxx>
+#include <osl/process.h>
+#include <rtl/textenc.h>
+#include <sal/main.h>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/bootstrap.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/degree.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+
+#include <vcl/font/Feature.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/vclmain.hxx>
+#include <vcl/wrkwin.hxx>
+
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <string_view>
+
+namespace
+{
+OUString GetOctetTextEncodingName(sal_uInt16 encoding)
+{
+ switch (encoding)
+ {
+ case RTL_TEXTENCODING_APPLE_ARABIC:
+ return "Arabic (Apple Macintosh)";
+
+ case RTL_TEXTENCODING_IBM_864:
+ return "Arabic (DOS/OS2-864)";
+
+ case RTL_TEXTENCODING_ISO_8859_6:
+ return "Arabic (ISO-8859-6)";
+
+ case RTL_TEXTENCODING_MS_1256:
+ return "Arabic (Windows-1256)";
+
+ case RTL_TEXTENCODING_IBM_775:
+ return "Baltic (DOS/OS2-775)";
+
+ case RTL_TEXTENCODING_ISO_8859_4:
+ return "Baltic (ISO-8859-4)";
+
+ case RTL_TEXTENCODING_MS_1257:
+ return "Baltic (Windows-1257)";
+
+ case RTL_TEXTENCODING_APPLE_CENTEURO:
+ return "Central European (Apple Macintosh)";
+
+ case RTL_TEXTENCODING_APPLE_CROATIAN:
+ return "Central European (Apple Macintosh/Croatian)";
+
+ case RTL_TEXTENCODING_APPLE_ROMANIAN:
+ return "Central European (Apple Macintosh/Romanian)";
+
+ case RTL_TEXTENCODING_IBM_852:
+ return "Central European (DOS/OS2-852)";
+
+ case RTL_TEXTENCODING_ISO_8859_2:
+ return "Central European (ISO-8859-2)";
+
+ case RTL_TEXTENCODING_ISO_8859_10:
+ return "Central European (ISO-8859-10)";
+
+ case RTL_TEXTENCODING_ISO_8859_13:
+ return "Central European (ISO-8859-13)";
+
+ case RTL_TEXTENCODING_MS_1250:
+ return "Central European (Windows-1250/WinLatin 2)";
+
+ case RTL_TEXTENCODING_APPLE_CHINSIMP:
+ return "Chinese Simplified (Apple Macintosh)";
+
+ case RTL_TEXTENCODING_EUC_CN:
+ return "Chinese Simplified (EUC-CN)";
+
+ case RTL_TEXTENCODING_GB_2312:
+ return "Chinese Simplified (GB-2312)";
+
+ case RTL_TEXTENCODING_GBK:
+ return "Chinese Simplified (GBK/GB-2312-80)";
+
+ case RTL_TEXTENCODING_ISO_2022_CN:
+ return "Chinese Simplified (ISO-2022-CN)";
+
+ case RTL_TEXTENCODING_MS_936:
+ return "Chinese Simplified (Windows-936)";
+
+ case RTL_TEXTENCODING_GB_18030:
+ return "Chinese Simplified (GB-18030)";
+
+ case RTL_TEXTENCODING_APPLE_CHINTRAD:
+ return "Chinese Traditional (Apple Macintosh)";
+
+ case RTL_TEXTENCODING_BIG5:
+ return "Chinese Traditional (BIG5)";
+
+ case RTL_TEXTENCODING_EUC_TW:
+ return "Chinese Traditional (EUC-TW)";
+
+ case RTL_TEXTENCODING_GBT_12345:
+ return "Chinese Traditional (GBT-12345)";
+
+ case RTL_TEXTENCODING_MS_950:
+ return "Chinese Traditional (Windows-950)";
+
+ case RTL_TEXTENCODING_BIG5_HKSCS:
+ return "Chinese Traditional (BIG5-HKSCS)";
+
+ case RTL_TEXTENCODING_APPLE_CYRILLIC:
+ return "Cyrillic (Apple Macintosh)";
+
+ case RTL_TEXTENCODING_APPLE_UKRAINIAN:
+ return "Cyrillic (Apple Macintosh/Ukrainian)";
+
+ case RTL_TEXTENCODING_IBM_855:
+ return "Cyrillic (DOS/OS2-855)";
+
+ case RTL_TEXTENCODING_IBM_866:
+ return "Cyrillic (DOS/OS2-866/Russian)";
+
+ case RTL_TEXTENCODING_ISO_8859_5:
+ return "Cyrillic (ISO-8859-5)";
+
+ case RTL_TEXTENCODING_KOI8_R:
+ return "Cyrillic (KOI8-R)";
+
+ case RTL_TEXTENCODING_KOI8_U:
+ return "Cyrillic (KOI8-U)";
+
+ case RTL_TEXTENCODING_MS_1251:
+ return "Cyrillic (Windows-1251)";
+
+ case RTL_TEXTENCODING_APPLE_GREEK:
+ return "Greek (Apple Macintosh)";
+
+ case RTL_TEXTENCODING_IBM_737:
+ return "Greek (DOS/OS2-737)";
+
+ case RTL_TEXTENCODING_IBM_869:
+ return "Greek (DOS/OS2-869/Modern)";
+
+ case RTL_TEXTENCODING_ISO_8859_7:
+ return "Greek (ISO-8859-7)";
+
+ case RTL_TEXTENCODING_MS_1253:
+ return "Greek (Windows-1253)";
+
+ case RTL_TEXTENCODING_APPLE_HEBREW:
+ return "Hebrew (Apple Macintosh)";
+
+ case RTL_TEXTENCODING_IBM_862:
+ return "Hebrew (DOS/OS2-862)";
+
+ case RTL_TEXTENCODING_ISO_8859_8:
+ return "Hebrew (ISO-8859-8)";
+
+ case RTL_TEXTENCODING_MS_1255:
+ return "Hebrew (Windows-1255)";
+
+ case RTL_TEXTENCODING_APPLE_KOREAN:
+ return "Korean (Apple Macintosh)";
+
+ case RTL_TEXTENCODING_EUC_KR:
+ return "Korean (EUC-KR)";
+
+ case RTL_TEXTENCODING_ISO_2022_KR:
+ return "Korean (ISO-2022-KR)";
+
+ case RTL_TEXTENCODING_MS_949:
+ return "Korean (Windows-Wansung-949)";
+
+ case RTL_TEXTENCODING_MS_1361:
+ return "Korean (Windows-Johab-1361)";
+
+ case RTL_TEXTENCODING_ISO_8859_3:
+ return "Latin 3 (ISO-8859-3)";
+
+ case RTL_TEXTENCODING_ISCII_DEVANAGARI:
+ return "Indian (ISCII Devanagari)";
+
+ case RTL_TEXTENCODING_APPLE_JAPANESE:
+ return "Japanese (Apple Macintosh)";
+
+ case RTL_TEXTENCODING_EUC_JP:
+ return "Japanese (EUC-JP)";
+
+ case RTL_TEXTENCODING_ISO_2022_JP:
+ return "Japanese (ISO-2022-JP)";
+
+ case RTL_TEXTENCODING_SHIFT_JIS:
+ return "Japanese (Shift-JIS)";
+
+ case RTL_TEXTENCODING_MS_932:
+ return "Japanese (Windows-932)";
+
+ case RTL_TEXTENCODING_SYMBOL:
+ return "Symbol";
+
+ case RTL_TEXTENCODING_APPLE_THAI:
+ return "Thai (Apple Macintosh)";
+
+ case RTL_TEXTENCODING_MS_874:
+ return "Thai (Dos/Windows-874)";
+
+ case RTL_TEXTENCODING_TIS_620:
+ return "Thai (TIS 620)";
+
+ case RTL_TEXTENCODING_APPLE_TURKISH:
+ return "Turkish (Apple Macintosh)";
+
+ case RTL_TEXTENCODING_IBM_857:
+ return "Turkish (DOS/OS2-857)";
+
+ case RTL_TEXTENCODING_ISO_8859_9:
+ return "Turkish (ISO-8859-9)";
+
+ case RTL_TEXTENCODING_MS_1254:
+ return "Turkish (Windows-1254)";
+
+ case RTL_TEXTENCODING_UTF7:
+ return "Unicode (UTF-7)";
+
+ case RTL_TEXTENCODING_UTF8:
+ return "Unicode (UTF-8)";
+
+ case RTL_TEXTENCODING_JAVA_UTF8:
+ return "Unicode (Java's modified UTF-8)";
+
+ case RTL_TEXTENCODING_MS_1258:
+ return "Vietnamese (Windows-1258)";
+
+ case RTL_TEXTENCODING_APPLE_ROMAN:
+ return "Western (Apple Macintosh)";
+
+ case RTL_TEXTENCODING_APPLE_ICELAND:
+ return "Western (Apple Macintosh/Icelandic)";
+
+ case RTL_TEXTENCODING_ASCII_US:
+ return "Western (ASCII/US)";
+
+ case RTL_TEXTENCODING_IBM_437:
+ return "Western (DOS/OS2-437/US)";
+
+ case RTL_TEXTENCODING_IBM_850:
+ return "Western (DOS/OS2-850/International)";
+
+ case RTL_TEXTENCODING_IBM_860:
+ return "Western (DOS/OS2-860/Portuguese)";
+
+ case RTL_TEXTENCODING_IBM_861:
+ return "Western (DOS/OS2-861/Icelandic)";
+
+ case RTL_TEXTENCODING_IBM_863:
+ return "Western (DOS/OS2-863/Canadian-French)";
+
+ case RTL_TEXTENCODING_IBM_865:
+ return "Western (DOS/OS2-865/Nordic)";
+
+ case RTL_TEXTENCODING_ISO_8859_1:
+ return "Western (ISO-8859-1)";
+
+ case RTL_TEXTENCODING_ISO_8859_14:
+ return "Western (ISO-8859-14)";
+
+ case RTL_TEXTENCODING_ISO_8859_15:
+ return "Western (ISO-8859-15/EURO)";
+
+ case RTL_TEXTENCODING_MS_1252:
+ return "Western (Window-1252/WinLatin 1)";
+
+ case RTL_TEXTENCODING_UCS4:
+ return "UCS4";
+
+ case RTL_TEXTENCODING_UCS2:
+ return "UCS2 (aka Unicode)";
+
+ default:
+ {
+ OUString sUnknown = "Unknown (0x" + OUString::number(encoding, 16) + ")";
+ return sUnknown;
+ }
+ }
+}
+
+class ListFontsWin : public WorkWindow
+{
+public:
+ explicit ListFontsWin()
+ : WorkWindow(nullptr, WB_HIDE)
+ {
+ }
+};
+
+class ListFonts : public Application
+{
+public:
+ virtual int Main() override;
+
+private:
+ static void showHelp()
+ {
+ std::cerr << "Usage: listfonts --help | FILE | -v FILE\n";
+ std::cerr << "Lists the current fonts installed on the system.\n";
+ std::cerr << "To show the font features of each font, use -v before FILE.\n";
+ std::cerr << "If outputting to stdout, use -- for FILE.\n";
+ std::exit(0);
+ }
+
+ void Init() override;
+ void DeInit() override;
+
+ css::uno::Reference<css::lang::XMultiServiceFactory> xServiceManager;
+ bool mbStdOut = false;
+ bool mbShowFeatures = false;
+ OUString maFilename;
+};
+
+int ListFonts::Main()
+{
+ try
+ {
+ VclPtrInstance<ListFontsWin> pWin;
+ OutputDevice* pOutDev = pWin->GetOutDev();
+
+ std::streambuf* coutbuf = nullptr;
+ std::fstream out;
+
+ if (!mbStdOut)
+ {
+ std::u16string_view filenamev = maFilename;
+ std::string filename(filenamev.begin(), filenamev.end());
+
+ out.open(filename, std::ios::out | std::ios::trunc);
+
+ coutbuf = std::cout.rdbuf();
+ std::cout.rdbuf(out.rdbuf());
+ }
+
+ std::vector<int> aIndices;
+ for (int i = 0; i < pOutDev->GetFontFaceCollectionCount(); i++)
+ aIndices.push_back(i);
+
+ std::sort(aIndices.begin(), aIndices.end(), [&](int a, int b) {
+ return pOutDev->GetFontMetricFromCollection(a).GetHashValueIgnoreColor()
+ > pOutDev->GetFontMetricFromCollection(b).GetHashValueIgnoreColor();
+ });
+
+ for (const auto& i : aIndices)
+ {
+ // note: to get the correct font metrics, you actually have to get the font metric from the
+ // system, and *then* you must set it as the current font of OutputDevice... then you need
+ // to get the font metric (which corrects a variety of things like the orientation, line
+ // height, slant, etc. - including converting from logical coords to device coords)
+
+ FontMetric aSystemFont = pOutDev->GetFontMetricFromCollection(i);
+ pOutDev->SetFont(aSystemFont);
+
+ FontMetric aFont = pOutDev->GetFontMetric();
+
+ std::cout << aFont.GetFamilyName() << "\n\tFamily type: " << aFont.GetFamilyType()
+ << "\n\tStyle name: " << aFont.GetStyleName()
+ << "\n\tWeight: " << aFont.GetWeight() << "\n\tItalic: " << aFont.GetItalic()
+ << "\n\tPitch: " << aFont.GetPitch()
+ << "\n\tWidth type: " << aFont.GetWidthType()
+ << "\n\tAlignment: " << aFont.GetAlignment()
+ << "\n\tCharset: " << GetOctetTextEncodingName(aFont.GetCharSet())
+ << "\n\tAscent: " << aFont.GetAscent()
+ << "\n\tDescent: " << aFont.GetDescent()
+ << "\n\tInternal leading: " << aFont.GetInternalLeading()
+ << "\n\tExternal leading: " << aFont.GetExternalLeading()
+ << "\n\tLine height: " << aFont.GetLineHeight()
+ << "\n\tSlant: " << aFont.GetSlant()
+ << "\n\tBullet offset: " << aFont.GetBulletOffset()
+ << "\n\tFullstop centered? " << (aFont.IsFullstopCentered() ? "yes" : "no")
+ << "\n\tOrientation: " << toDegrees(aFont.GetOrientation())
+ << " degrees\n\tQuality: " << aFont.GetQuality() << "\n";
+
+ if (mbShowFeatures)
+ {
+ std::vector<vcl::font::Feature> features;
+ pOutDev->GetFontFeatures(features);
+
+ for (auto const& feature : features)
+ {
+ std::ios init(nullptr);
+ init.copyfmt(std::cout);
+
+ std::cout << "\t"
+ << (feature.m_eType == vcl::font::FeatureType::OpenType ? "OpenType"
+ : "Graphite")
+ << " Feature:\n\t\t"
+ << vcl::font::featureCodeAsString(feature.m_nCode) << "\n";
+
+ std::cout << "\t\tDescription: " << feature.m_aDefinition.getDescription()
+ << "\n";
+ std::cout << "\t\tType: "
+ << (feature.m_aDefinition.getType()
+ == vcl::font::FeatureParameterType::BOOL
+ ? "BOOL"
+ : "ENUM")
+ << "\n";
+
+ std::cout.copyfmt(init);
+
+ if (feature.m_aDefinition.getType() == vcl::font::FeatureParameterType::ENUM)
+ {
+ for (auto const& param : feature.m_aDefinition.getEnumParameters())
+ {
+ std::cout << "\t\t\t" << param.getDescription() << ": "
+ << param.getCode() << "\n";
+ }
+ }
+
+ std::cout << "\t\tDefault: 0x" << std::hex << feature.m_aDefinition.getDefault()
+ << "\n";
+
+ std::cout.copyfmt(init);
+ }
+ }
+ }
+
+ std::cout << std::flush;
+
+ if (!mbStdOut)
+ {
+ std::cout.rdbuf(coutbuf);
+ out.close();
+ }
+
+ std::exit(0);
+ }
+ catch (const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("vcl.app", "Fatal");
+ return 1;
+ }
+ catch (const std::exception& e)
+ {
+ SAL_WARN("vcl.app", "Fatal: " << e.what());
+ return 1;
+ }
+ return 0;
+}
+}
+
+void ListFonts::Init()
+{
+ const sal_uInt16 nCmdParams = GetCommandLineParamCount();
+ OUString aArg;
+
+ if (nCmdParams == 0)
+ {
+ showHelp();
+ std::exit(1);
+ }
+ else
+ {
+ aArg = GetCommandLineParam(0);
+
+ if (aArg == "--help" || aArg == "-h")
+ {
+ showHelp();
+ std::exit(0);
+ }
+ else if (nCmdParams == 2 && (aArg == "--verbose" || aArg == "-v"))
+ {
+ aArg = GetCommandLineParam(1);
+ mbShowFeatures = true;
+
+ if (aArg == "--")
+ mbStdOut = true;
+ }
+ else if (nCmdParams == 1)
+ {
+ if (aArg == "--")
+ mbStdOut = true;
+ }
+ else
+ {
+ std::cerr << "invalid arguments\n";
+ showHelp();
+ std::exit(1);
+ }
+ }
+
+ if (!mbStdOut)
+ {
+ maFilename = aArg;
+
+ osl::File aFile(maFilename);
+
+ if (!aFile.open(osl_File_OpenFlag_Create))
+ throw css::uno::RuntimeException("Can not create file: " + aArg);
+
+ aFile.close();
+ }
+
+ auto xContext = cppu::defaultBootstrap_InitialComponentContext();
+ xServiceManager.set(xContext->getServiceManager(), css::uno::UNO_QUERY);
+
+ if (!xServiceManager.is())
+ Application::Abort("Bootstrap failure - no service manager");
+
+ comphelper::setProcessServiceFactory(xServiceManager);
+
+ LanguageTag::setConfiguredSystemLanguage(MsLangId::getSystemLanguage());
+}
+
+void ListFonts::DeInit()
+{
+ auto xContext = css::uno::Reference<css::lang::XComponent>(
+ comphelper::getProcessComponentContext(), css::uno::UNO_QUERY_THROW);
+ xContext->dispose();
+ ::comphelper::setProcessServiceFactory(nullptr);
+}
+
+SAL_IMPLEMENT_MAIN()
+{
+ ListFonts aApp;
+ InitVCL();
+ int ret = aApp.Main();
+ DeInitVCL();
+
+ return ret;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/workben/listglyphs.cxx b/vcl/workben/listglyphs.cxx
new file mode 100644
index 0000000000..def2ff8181
--- /dev/null
+++ b/vcl/workben/listglyphs.cxx
@@ -0,0 +1,231 @@
+/* -*- 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 <osl/file.hxx>
+#include <osl/process.h>
+#include <rtl/textenc.h>
+#include <sal/main.h>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/bootstrap.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/degree.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+
+#include <vcl/font/Feature.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/vclmain.hxx>
+#include <vcl/wrkwin.hxx>
+
+#include <font/LogicalFontInstance.hxx>
+#include <font/PhysicalFontFace.hxx>
+
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <string_view>
+
+namespace
+{
+class ListGlyphsWin : public WorkWindow
+{
+public:
+ explicit ListGlyphsWin()
+ : WorkWindow(nullptr, WB_HIDE)
+ {
+ }
+};
+
+class ListGlyphs : public Application
+{
+public:
+ virtual int Main() override;
+
+private:
+ static void showHelp()
+ {
+ std::cerr << "Usage: listglyphs --help | <fontname> FILE\n";
+ std::cerr << "Lists the current glyphs in a font installed on the system.\n";
+ std::cerr << "If outputting to stdout, use -- for FILE.\n";
+ std::exit(0);
+ }
+
+ void Init() override;
+ void DeInit() override;
+
+ css::uno::Reference<css::lang::XMultiServiceFactory> xServiceManager;
+ bool mbStdOut = false;
+ OUString maFilename;
+ OUString maFontname;
+};
+
+int ListGlyphs::Main()
+{
+ try
+ {
+ VclPtrInstance<ListGlyphsWin> pWin;
+ OutputDevice* pOutDev = pWin->GetOutDev();
+
+ std::streambuf* coutbuf = nullptr;
+ std::fstream out;
+
+ if (!mbStdOut)
+ {
+ std::u16string_view filenamev = maFilename;
+ std::string filename(filenamev.begin(), filenamev.end());
+
+ out.open(filename, std::ios::out | std::ios::trunc);
+
+ coutbuf = std::cout.rdbuf();
+ std::cout.rdbuf(out.rdbuf());
+ }
+
+ bool bFontExists = false;
+
+ for (sal_Int32 nFont = 0; nFont < pOutDev->GetFontFaceCollectionCount(); nFont++)
+ {
+ FontMetric aSystemFont = pOutDev->GetFontMetricFromCollection(nFont);
+ if (aSystemFont.GetFamilyName() == maFontname)
+ {
+ bFontExists = true;
+ break;
+ }
+ }
+
+ if (!bFontExists)
+ {
+ std::cerr << maFontname << " does not exist\n";
+ std::exit(1);
+ }
+
+ pOutDev->SetFont(vcl::Font(maFontname, Size(0, 11)));
+
+ LogicalFontInstance const* pFontInstance = pOutDev->GetFontInstance();
+ vcl::font::PhysicalFontFace const* pFontFace = pFontInstance->GetFontFace();
+ FontCharMapRef pCharMap = pFontFace->GetFontCharMap();
+
+ sal_UCS4 nLastChar = pCharMap->GetLastChar();
+ for (sal_UCS4 nChar = pCharMap->GetFirstChar(); nChar < nLastChar;
+ nChar = pCharMap->GetNextChar(nChar))
+ {
+ auto nGlyphIndex = pFontInstance->GetGlyphIndex(nChar);
+ tools::Rectangle aGlyphBounds;
+ pFontInstance->GetGlyphBoundRect(nGlyphIndex, aGlyphBounds, false);
+ std::cout << "Codepoint: " << pFontFace->GetGlyphName(nGlyphIndex)
+ << "; glyph bounds: " << aGlyphBounds << "\n";
+ }
+
+ std::cout << std::flush;
+
+ if (!mbStdOut)
+ {
+ std::cout.rdbuf(coutbuf);
+ out.close();
+ }
+
+ std::exit(0);
+ }
+ catch (const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("vcl.app", "Fatal");
+ return 1;
+ }
+ catch (const std::exception& e)
+ {
+ SAL_WARN("vcl.app", "Fatal: " << e.what());
+ return 1;
+ }
+ return 0;
+}
+
+void ListGlyphs::Init()
+{
+ const sal_uInt16 nCmdParams = GetCommandLineParamCount();
+ OUString aArg;
+
+ if (nCmdParams == 0)
+ {
+ showHelp();
+ std::exit(1);
+ }
+ else
+ {
+ aArg = GetCommandLineParam(0);
+
+ if (aArg == "--help" || aArg == "-h")
+ {
+ showHelp();
+ std::exit(0);
+ }
+
+ if (nCmdParams == 2)
+ {
+ maFontname = GetCommandLineParam(0);
+
+ aArg = GetCommandLineParam(1);
+
+ if (aArg == "--")
+ mbStdOut = true;
+
+ maFilename = aArg;
+ }
+ else
+ {
+ std::cerr << "invalid arguments\n";
+ showHelp();
+ std::exit(1);
+ }
+ }
+
+ if (!mbStdOut)
+ {
+ osl::File aFile(maFilename);
+
+ if (!aFile.open(osl_File_OpenFlag_Create))
+ throw css::uno::RuntimeException("Can not create file: " + aArg);
+
+ aFile.close();
+ }
+
+ auto xContext = cppu::defaultBootstrap_InitialComponentContext();
+ xServiceManager.set(xContext->getServiceManager(), css::uno::UNO_QUERY);
+
+ if (!xServiceManager.is())
+ Application::Abort("Bootstrap failure - no service manager");
+
+ comphelper::setProcessServiceFactory(xServiceManager);
+
+ LanguageTag::setConfiguredSystemLanguage(MsLangId::getSystemLanguage());
+}
+
+void ListGlyphs::DeInit()
+{
+ auto xContext = css::uno::Reference<css::lang::XComponent>(
+ comphelper::getProcessComponentContext(), css::uno::UNO_QUERY_THROW);
+ xContext->dispose();
+ ::comphelper::setProcessServiceFactory(nullptr);
+}
+}
+
+SAL_IMPLEMENT_MAIN()
+{
+ ListGlyphs aApp;
+ InitVCL();
+ int ret = aApp.Main();
+ DeInitVCL();
+
+ return ret;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/workben/lwpfuzzer.cxx b/vcl/workben/lwpfuzzer.cxx
new file mode 100644
index 0000000000..fc07045420
--- /dev/null
+++ b/vcl/workben/lwpfuzzer.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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" bool TestImportLWP(SvStream &rStream);
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportLWP(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/lwpfuzzer.options b/vcl/workben/lwpfuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/lwpfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/metfuzzer.cxx b/vcl/workben/metfuzzer.cxx
new file mode 100644
index 0000000000..9ca6fd35c9
--- /dev/null
+++ b/vcl/workben/metfuzzer.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 <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+#include <filter/MetReader.hxx>
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * com_sun_star_i18n_LocaleDataImpl_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_get_implementation( void *, void * );
+void * com_sun_star_i18n_NativeNumberSupplier_get_implementation( void *, void * );
+void * com_sun_star_i18n_NumberFormatCodeMapper_get_implementation( void *, void * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { "com_sun_star_i18n_LocaleDataImpl_get_implementation", com_sun_star_i18n_LocaleDataImpl_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_Unicode_get_implementation", com_sun_star_i18n_BreakIterator_Unicode_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_get_implementation", com_sun_star_i18n_BreakIterator_get_implementation },
+ { "com_sun_star_i18n_NativeNumberSupplier_get_implementation", com_sun_star_i18n_NativeNumberSupplier_get_implementation },
+ { "com_sun_star_i18n_NumberFormatCodeMapper_get_implementation", com_sun_star_i18n_NumberFormatCodeMapper_get_implementation },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportMetGraphic(aStream, aGraphic);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/metfuzzer.options b/vcl/workben/metfuzzer.options
new file mode 100644
index 0000000000..bf169a74ba
--- /dev/null
+++ b/vcl/workben/metfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 49152
diff --git a/vcl/workben/minvcl.cxx b/vcl/workben/minvcl.cxx
new file mode 100644
index 0000000000..cd144e7e40
--- /dev/null
+++ b/vcl/workben/minvcl.cxx
@@ -0,0 +1,76 @@
+/* -*- 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 <sal/config.h>
+
+#include <framework/desktop.hxx>
+#include <cppuhelper/bootstrap.hxx>
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/wrkwin.hxx>
+#include <sal/main.h>
+
+#include <iostream>
+
+namespace
+{
+class TheApplication : public Application
+{
+public:
+ virtual int Main();
+
+private:
+ VclPtr<vcl::Window> mpWin;
+};
+}
+
+int TheApplication::Main()
+{
+ mpWin = VclPtr<WorkWindow>::Create(nullptr, WB_APP | WB_STDWORK);
+ mpWin->SetText(u"Minimum VCL application with a window"_ustr);
+ mpWin->Show();
+ Execute();
+ mpWin.disposeAndClear();
+ return 0;
+}
+
+SAL_IMPLEMENT_MAIN()
+{
+ try
+ {
+ TheApplication aApp;
+
+ auto xContext = cppu::defaultBootstrap_InitialComponentContext();
+ css::uno::Reference<css::lang::XMultiServiceFactory> xServiceManager(
+ xContext->getServiceManager(), css::uno::UNO_QUERY);
+ comphelper::setProcessServiceFactory(xServiceManager);
+ LanguageTag::setConfiguredSystemLanguage(MsLangId::getSystemLanguage());
+ InitVCL();
+
+ aApp.Main();
+
+ framework::getDesktop(::comphelper::getProcessComponentContext())->terminate();
+ DeInitVCL();
+ comphelper::setProcessServiceFactory(nullptr);
+ }
+ catch (...)
+ {
+ std::cout << "Exception has occurred\n";
+ return 1;
+ }
+
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/workben/mmlfuzzer.cxx b/vcl/workben/mmlfuzzer.cxx
new file mode 100644
index 0000000000..8811c2743a
--- /dev/null
+++ b/vcl/workben/mmlfuzzer.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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+extern "C" bool TestImportMML(SvStream& rStream);
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportMML(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/mmlfuzzer.options b/vcl/workben/mmlfuzzer.options
new file mode 100644
index 0000000000..aa700cce92
--- /dev/null
+++ b/vcl/workben/mmlfuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+dict = mathml.dict
diff --git a/vcl/workben/mtfdemo.cxx b/vcl/workben/mtfdemo.cxx
new file mode 100644
index 0000000000..8cc9363a04
--- /dev/null
+++ b/vcl/workben/mtfdemo.cxx
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <cppuhelper/bootstrap.hxx>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/registry/XSimpleRegistry.hpp>
+#include <com/sun/star/ucb/UniversalContentBroker.hpp>
+#include <com/sun/star/uno/RuntimeException.hpp>
+
+#include <vcl/vclmain.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/wmf.hxx>
+
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/virdev.hxx>
+#include <sal/log.hxx>
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <framework/desktop.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+
+#include <iostream>
+
+using namespace css;
+
+namespace {
+
+class DemoMtfWin : public WorkWindow
+{
+ OUString maFileName;
+
+public:
+ explicit DemoMtfWin(const OUString& rFileName)
+ : WorkWindow(nullptr, WB_APP | WB_STDWORK)
+ {
+ maFileName = rFileName;
+ }
+
+ virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override;
+};
+
+}
+
+void DemoMtfWin::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ GDIMetaFile aMtf;
+ SvFileStream aFileStream(maFileName, StreamMode::READ);
+
+ if (aFileStream.IsOpen())
+ {
+ ReadWindowMetafile(aFileStream, aMtf);
+ }
+ else
+ {
+ Application::Abort("Can't read metafile " + aFileStream.GetFileName());
+ }
+
+ aMtf.Play(*GetOutDev(), aMtf.GetActionSize());
+ aMtf.Stop();
+ aFileStream.Close();
+
+ WorkWindow::Paint(rRenderContext, rRect);
+}
+
+namespace {
+
+class DemoMtfApp : public Application
+{
+ VclPtr<DemoMtfWin> mpWin;
+ OUString maFileName;
+
+ static void showHelp()
+ {
+ std::cerr << "Usage: mtfdemo --help | FILE | -d FILE" << std::endl;
+ std::cerr << "A VCL test app that displays Windows metafiles or dumps metaactions." << std::endl;
+ std::cerr << "If you want to dump as metadump.xml, use -d before FILE." << std::endl;
+ std::exit(0);
+ }
+
+public:
+
+ DemoMtfApp()
+ : mpWin(nullptr)
+ {
+ }
+
+ virtual int Main() override
+ {
+ try
+ {
+ mpWin = VclPtr<DemoMtfWin>::Create(maFileName);
+ mpWin->SetText("Display metafile");
+
+ mpWin->Show();
+
+ Application::Execute();
+ mpWin.disposeAndClear();
+ }
+ catch (const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("vcl.app", "Fatal");
+ return 1;
+ }
+ catch (const std::exception& e)
+ {
+ SAL_WARN("vcl.app", "Fatal: " << e.what());
+ return 1;
+ }
+ return 0;
+ }
+
+private:
+ uno::Reference<lang::XMultiServiceFactory> xMSF;
+ void Init() override
+ {
+ LanguageTag::setConfiguredSystemLanguage(MsLangId::getSystemLanguage());
+
+ try
+ {
+ const sal_uInt16 nCmdParams = GetCommandLineParamCount();
+ OUString aArg, aFilename;
+ bool bDumpXML = false;
+
+ if (nCmdParams == 0)
+ {
+ showHelp();
+ std::exit(1);
+ }
+ else
+ {
+ aArg = GetCommandLineParam(0);
+
+ if (aArg == "--help" || aArg == "-h")
+ {
+ showHelp();
+ std::exit(0);
+ }
+ else if (nCmdParams > 1 && (aArg == "--dump" || aArg == "-d"))
+ {
+ aFilename = GetCommandLineParam(1);
+ bDumpXML = true;
+ }
+ else
+ aFilename = aArg;
+ }
+
+ OUString sWorkingDir, sFileUrl;
+ osl_getProcessWorkingDir(&sWorkingDir.pData);
+ osl::FileBase::RC rc = osl::FileBase::getFileURLFromSystemPath(aFilename, sFileUrl);
+ if (rc == osl::FileBase::E_None)
+ {
+ rc = osl::FileBase::getAbsoluteFileURL(sWorkingDir, sFileUrl, maFileName);
+ if (rc != osl::FileBase::E_None)
+ {
+ throw css::uno::RuntimeException("Can not make absolute: " + aFilename);
+ }
+ }
+ else
+ {
+ throw css::uno::RuntimeException("Can not get file url from system path: " + aFilename);
+ }
+
+ uno::Reference<uno::XComponentContext> xComponentContext
+ = ::cppu::defaultBootstrap_InitialComponentContext();
+ xMSF.set(xComponentContext->getServiceManager(), uno::UNO_QUERY);
+ if(!xMSF.is())
+ Application::Abort("Bootstrap failure - no service manager");
+
+ ::comphelper::setProcessServiceFactory(xMSF);
+
+ if(bDumpXML)
+ {
+ GDIMetaFile aMtf;
+ SvFileStream aFileStream(maFileName, StreamMode::READ);
+
+ if (aFileStream.IsOpen())
+ {
+ ReadWindowMetafile(aFileStream, aMtf);
+ }
+ else
+ {
+ throw css::uno::RuntimeException("Can't read metafile " + aFileStream.GetFileName());
+ }
+
+ OUString sAbsoluteDumpUrl, sDumpUrl;
+ rc = osl::FileBase::getFileURLFromSystemPath("metadump.xml", sDumpUrl);
+ if (rc == osl::FileBase::E_None)
+ {
+ rc = osl::FileBase::getAbsoluteFileURL(sWorkingDir, sDumpUrl, sAbsoluteDumpUrl);
+ if (rc != osl::FileBase::E_None)
+ {
+ throw css::uno::RuntimeException("Can not make absolute: metadump.xml");
+ }
+ }
+ else
+ {
+ throw css::uno::RuntimeException("Can not get file url from system path: metadump.xml");
+ }
+
+ aMtf.dumpAsXml(rtl::OUStringToOString(sAbsoluteDumpUrl, RTL_TEXTENCODING_UTF8).getStr());
+ std::cout << "Dumped metaactions as metadump.xml" << std::endl;
+ framework::getDesktop(::comphelper::getProcessComponentContext())->terminate();
+ framework::getDesktop(::comphelper::getProcessComponentContext())->disposing();
+ std::exit(0);
+ }
+
+ }
+ catch (const uno::Exception &e)
+ {
+ Application::Abort("Bootstrap exception " + e.Message);
+ }
+ }
+
+ void DeInit() override
+ {
+ framework::getDesktop(::comphelper::getProcessComponentContext())->terminate();
+ framework::getDesktop(::comphelper::getProcessComponentContext())->disposing();
+
+ ::comphelper::setProcessServiceFactory(nullptr);
+ }
+
+};
+
+}
+
+void vclmain::createApplication()
+{
+ static DemoMtfApp aApp;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/mtpfuzzer.cxx b/vcl/workben/mtpfuzzer.cxx
new file mode 100644
index 0000000000..8ed271ced9
--- /dev/null
+++ b/vcl/workben/mtpfuzzer.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 <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping* lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = { { 0, 0 } };
+
+ return map;
+}
+
+const lib_to_constructor_mapping* lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = { { 0, 0 } };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*) { return nullptr; }
+
+extern "C" bool TestImportMathType(SvStream& rStream);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportMathType(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/mtpfuzzer.options b/vcl/workben/mtpfuzzer.options
new file mode 100644
index 0000000000..9e9bf3455e
--- /dev/null
+++ b/vcl/workben/mtpfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 16384
diff --git a/vcl/workben/olefuzzer.cxx b/vcl/workben/olefuzzer.cxx
new file mode 100644
index 0000000000..4d67ab57e7
--- /dev/null
+++ b/vcl/workben/olefuzzer.cxx
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * com_sun_star_comp_uui_UUIInteractionHandler_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_get_implementation( void *, void * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { "com_sun_star_comp_uui_UUIInteractionHandler_get_implementation", com_sun_star_comp_uui_UUIInteractionHandler_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_Unicode_get_implementation", com_sun_star_i18n_CharacterClassification_Unicode_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_get_implementation", com_sun_star_i18n_CharacterClassification_get_implementation },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" bool TestImportOLE2(SvStream &rStream);
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ TestImportOLE2(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/olefuzzer.options b/vcl/workben/olefuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/olefuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/pasteboard.mm b/vcl/workben/pasteboard.mm
new file mode 100644
index 0000000000..4123f8eae0
--- /dev/null
+++ b/vcl/workben/pasteboard.mm
@@ -0,0 +1,133 @@
+// -*- Mode: ObjC; 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/.
+ */
+
+// List the contents of the macOS pasteboard
+
+// Build with: clang++ -g -Wall -o pasteboard pasteboard.mm -framework AppKit -framework UniformTypeIdentifiers
+
+#import <unistd.h>
+
+#import <iostream>
+#import <AppKit/AppKit.h>
+#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
+
+static void usage()
+{
+ std::cout << "Usage: pasteboard\n"
+ " List the types on the pasteboard and in each pasteboard item.\n"
+ " pasteboard -a\n"
+ " Output the data for all types to stdout. Note: output will\n"
+ " in many cases be binary. The different types are separated by a textual header.\n"
+ " pasteboard -t type\n"
+ " Output the data for the type in question to stdout. Note: output will\n"
+ " in many cases be binary.\n";
+}
+
+int main(int argc, char** argv)
+{
+ NSString* requestedType = @"";
+
+ int ch;
+
+ while ((ch = getopt(argc, argv, "at:")) != -1)
+ {
+ switch (ch)
+ {
+ case 'a':
+ requestedType = @"*";
+ break;
+ case 't':
+ requestedType = [NSString stringWithUTF8String:optarg];
+ break;
+ case '?':
+ usage();
+ return 0;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0)
+ {
+ usage();
+ return 1;
+ }
+
+ NSPasteboard* pb = [NSPasteboard generalPasteboard];
+
+ if ([requestedType isEqualToString:@"*"])
+ {
+ NSArray<NSPasteboardType>* types = [pb types];
+ for (unsigned i = 0; i < [types count]; i++)
+ {
+ NSData* data = [pb dataForType:types[i]];
+ std::cout << i << ": " << [types[i] UTF8String] << ": " << std::to_string([data length]) << " bytes:\n";
+ if (data != nil)
+ {
+ std::cout.write((const char*)[data bytes], [data length]);
+ std::cout << "\n";
+ }
+ }
+ return 0;
+ }
+
+ if ([requestedType length] > 0)
+ {
+ NSData* data = [pb dataForType:requestedType];
+
+ if (data == nil)
+ std::cerr << "No data for " << [requestedType UTF8String] << std::endl;
+ else
+ std::cout.write((const char*)[data bytes], [data length]);
+
+ return 0;
+ }
+
+ {
+ NSArray<NSPasteboardType>* types = [pb types];
+ std::cout << "Types directly on pasteboard:\n";
+ for (unsigned i = 0; i < [types count]; i++)
+ {
+ std::cout << " " << i << ": " << [types[i] UTF8String] << "\n";
+ }
+ }
+
+ NSArray<NSPasteboardItem*>* items = [pb pasteboardItems];
+ std::cout << "New-style items on pasteboard:\n";
+
+ for (unsigned i = 0; i < [items count]; i++)
+ {
+ std::cout << " Item " << i << ", types:\n";
+ NSArray<NSPasteboardType>* types = [items[i] types];
+ for (unsigned j = 0; j < [types count]; j++)
+ {
+ std::cout << " " << j << ": " << [types[j] UTF8String] << std::flush;
+
+ if ([types[j] isEqualToString:[UTTypePlainText identifier]] ||
+ [types[j] isEqualToString:[UTTypeUTF8PlainText identifier]] ||
+ [types[j] isEqualToString:[UTTypeText identifier]] ||
+ [types[j] isEqualToString:[UTTypeHTML identifier]] ||
+ [types[j] isEqualToString:[UTTypeRTF identifier]] ||
+ [types[j] isEqualToString:[UTTypeUTF16ExternalPlainText identifier]])
+ {
+ NSString* string = [items[i] stringForType:NSPasteboardTypeString];
+ if ([string length] > 500)
+ string = [[string substringToIndex:501] stringByAppendingString:@"..."];
+ std::cout << ": '" << [string UTF8String] << "'";
+ }
+ std::cout << "\n";
+ }
+ }
+
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/workben/pcdfuzzer.cxx b/vcl/workben/pcdfuzzer.cxx
new file mode 100644
index 0000000000..86df131da1
--- /dev/null
+++ b/vcl/workben/pcdfuzzer.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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+#include <filter/PcdReader.hxx>
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportPcdGraphic(aStream, aGraphic, nullptr);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/pcdfuzzer.options b/vcl/workben/pcdfuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/pcdfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/pctfuzzer.cxx b/vcl/workben/pctfuzzer.cxx
new file mode 100644
index 0000000000..db1ad0c90c
--- /dev/null
+++ b/vcl/workben/pctfuzzer.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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+#include <filter/PictReader.hxx>
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportPictGraphic(aStream, aGraphic);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/pctfuzzer.options b/vcl/workben/pctfuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/pctfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/pcxfuzzer.cxx b/vcl/workben/pcxfuzzer.cxx
new file mode 100644
index 0000000000..2785cf4c4e
--- /dev/null
+++ b/vcl/workben/pcxfuzzer.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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+#include <filter/PcxReader.hxx>
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportPcxGraphic(aStream, aGraphic);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/pcxfuzzer.options b/vcl/workben/pcxfuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/pcxfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/pngfuzzer.cxx b/vcl/workben/pngfuzzer.cxx
new file mode 100644
index 0000000000..e5e3c9e8a2
--- /dev/null
+++ b/vcl/workben/pngfuzzer.cxx
@@ -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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ vcl::PngImageReader aReader(aStream);
+ (void)aReader.read();
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/pngfuzzer.options b/vcl/workben/pngfuzzer.options
new file mode 100644
index 0000000000..d62b773415
--- /dev/null
+++ b/vcl/workben/pngfuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+dict = png.dict
diff --git a/vcl/workben/ppmfuzzer.cxx b/vcl/workben/ppmfuzzer.cxx
new file mode 100644
index 0000000000..9595fa3aa7
--- /dev/null
+++ b/vcl/workben/ppmfuzzer.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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+#include <filter/PbmReader.hxx>
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportPbmGraphic(aStream, aGraphic);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/ppmfuzzer.options b/vcl/workben/ppmfuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/ppmfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/pptfuzzer.cxx b/vcl/workben/pptfuzzer.cxx
new file mode 100644
index 0000000000..f36cd08d3a
--- /dev/null
+++ b/vcl/workben/pptfuzzer.cxx
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+#include <svx/svdobj.hxx>
+
+extern "C" {
+void * i18npool_component_getFactory( const char* , void* , void* );
+
+void * com_sun_star_i18n_LocaleDataImpl_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_get_implementation( void *, void * );
+void * com_sun_star_comp_framework_Desktop_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_get_implementation( void *, void * );
+void * com_sun_star_i18n_NativeNumberSupplier_get_implementation( void *, void * );
+void * com_sun_star_i18n_NumberFormatCodeMapper_get_implementation( void *, void * );
+void * com_sun_star_i18n_Transliteration_get_implementation( void *, void * );
+void * com_sun_star_drawing_EnhancedCustomShapeEngine_get_implementation( void *, void * );
+void * com_sun_star_drawing_SvxShapeCollection_get_implementation( void *, void * );
+void * SfxDocumentMetaData_get_implementation( void *, void * );
+void * com_sun_star_animations_AnimateColor_get_implementation( void *, void * );
+void * com_sun_star_animations_AnimateMotion_get_implementation( void *, void * );
+void * com_sun_star_animations_AnimateSet_get_implementation( void *, void * );
+void * com_sun_star_animations_AnimateTransform_get_implementation( void *, void * );
+void * com_sun_star_animations_Animate_get_implementation( void *, void * );
+void * com_sun_star_animations_Audio_get_implementation( void *, void * );
+void * com_sun_star_animations_Command_get_implementation( void *, void * );
+void * com_sun_star_animations_IterateContainer_get_implementation( void *, void * );
+void * com_sun_star_animations_ParallelTimeContainer_get_implementation( void *, void * );
+void * com_sun_star_animations_SequenceTimeContainer_get_implementation( void *, void * );
+void * com_sun_star_animations_TransitionFilter_get_implementation( void *, void * );
+void * com_sun_star_comp_comphelper_OPropertyBag( void *, void * );
+void * com_sun_star_comp_uui_UUIInteractionHandler_get_implementation( void *, void * );
+void * emfio_emfreader_XEmfParser_get_implementation( void *, void * );
+void * i18npool_CalendarImpl_get_implementation( void *, void * );
+void * i18npool_Calendar_gregorian_get_implementation( void *, void * );
+void * unoxml_CXPathAPI_get_implementation( void *, void * );
+void * unoxml_CDocumentBuilder_get_implementation( void *, void * );
+void * sd_PresentationDocument_get_implementation( void *, void * );
+void * ucb_UcbCommandEnvironment_get_implementation( void *, void * );
+void * ucb_UcbContentProviderProxyFactory_get_implementation( void *, void * );
+void * ucb_UcbPropertiesManager_get_implementation( void *, void * );
+void * ucb_UcbStore_get_implementation( void *, void * );
+void * ucb_UniversalContentBroker_get_implementation( void *, void * );
+void * ucb_OFileAccess_get_implementation( void *, void * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libi18npoollo.a", i18npool_component_getFactory },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { "com_sun_star_i18n_LocaleDataImpl_get_implementation", com_sun_star_i18n_LocaleDataImpl_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_Unicode_get_implementation", com_sun_star_i18n_BreakIterator_Unicode_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_get_implementation", com_sun_star_i18n_BreakIterator_get_implementation },
+ { "com_sun_star_comp_framework_Desktop_get_implementation", com_sun_star_comp_framework_Desktop_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_Unicode_get_implementation", com_sun_star_i18n_CharacterClassification_Unicode_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_get_implementation", com_sun_star_i18n_CharacterClassification_get_implementation },
+ { "com_sun_star_i18n_NativeNumberSupplier_get_implementation", com_sun_star_i18n_NativeNumberSupplier_get_implementation },
+ { "com_sun_star_i18n_NumberFormatCodeMapper_get_implementation", com_sun_star_i18n_NumberFormatCodeMapper_get_implementation },
+ { "com_sun_star_i18n_Transliteration_get_implementation", com_sun_star_i18n_Transliteration_get_implementation },
+ { "com_sun_star_drawing_EnhancedCustomShapeEngine_get_implementation", com_sun_star_drawing_EnhancedCustomShapeEngine_get_implementation },
+ { "com_sun_star_drawing_SvxShapeCollection_get_implementation", com_sun_star_drawing_SvxShapeCollection_get_implementation },
+ { "SfxDocumentMetaData_get_implementation", SfxDocumentMetaData_get_implementation },
+ { "com_sun_star_animations_AnimateColor_get_implementation", com_sun_star_animations_AnimateColor_get_implementation },
+ { "com_sun_star_animations_AnimateMotion_get_implementation", com_sun_star_animations_AnimateMotion_get_implementation },
+ { "com_sun_star_animations_AnimateSet_get_implementation", com_sun_star_animations_AnimateSet_get_implementation },
+ { "com_sun_star_animations_AnimateTransform_get_implementation", com_sun_star_animations_AnimateTransform_get_implementation },
+ { "com_sun_star_animations_Animate_get_implementation", com_sun_star_animations_Animate_get_implementation },
+ { "com_sun_star_animations_Audio_get_implementation", com_sun_star_animations_Audio_get_implementation },
+ { "com_sun_star_animations_Command_get_implementation", com_sun_star_animations_Command_get_implementation },
+ { "com_sun_star_animations_IterateContainer_get_implementation", com_sun_star_animations_IterateContainer_get_implementation },
+ { "com_sun_star_animations_ParallelTimeContainer_get_implementation", com_sun_star_animations_ParallelTimeContainer_get_implementation },
+ { "com_sun_star_animations_SequenceTimeContainer_get_implementation", com_sun_star_animations_SequenceTimeContainer_get_implementation },
+ { "com_sun_star_animations_TransitionFilter_get_implementation", com_sun_star_animations_TransitionFilter_get_implementation },
+ { "com_sun_star_comp_comphelper_OPropertyBag", com_sun_star_comp_comphelper_OPropertyBag },
+ { "com_sun_star_comp_uui_UUIInteractionHandler_get_implementation", com_sun_star_comp_uui_UUIInteractionHandler_get_implementation },
+ { "emfio_emfreader_XEmfParser_get_implementation", emfio_emfreader_XEmfParser_get_implementation},
+ { "i18npool_CalendarImpl_get_implementation", i18npool_CalendarImpl_get_implementation},
+ { "i18npool_Calendar_gregorian_get_implementation", i18npool_Calendar_gregorian_get_implementation},
+ { "unoxml_CXPathAPI_get_implementation", unoxml_CXPathAPI_get_implementation },
+ { "unoxml_CDocumentBuilder_get_implementation", unoxml_CDocumentBuilder_get_implementation },
+ { "sd_PresentationDocument_get_implementation", sd_PresentationDocument_get_implementation },
+ { "ucb_UcbCommandEnvironment_get_implementation", ucb_UcbCommandEnvironment_get_implementation, },
+ { "ucb_UcbContentProviderProxyFactory_get_implementation", ucb_UcbContentProviderProxyFactory_get_implementation },
+ { "ucb_UcbPropertiesManager_get_implementation", ucb_UcbPropertiesManager_get_implementation },
+ { "ucb_UcbStore_get_implementation", ucb_UcbStore_get_implementation },
+ { "ucb_UniversalContentBroker_get_implementation", ucb_UniversalContentBroker_get_implementation },
+ { "ucb_OFileAccess_get_implementation", ucb_OFileAccess_get_implementation },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" void* SdCreateDialogFactory()
+{
+ return nullptr;
+}
+
+extern "C" bool TestImportPPT(SvStream &rStream);
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ if (__lsan_disable)
+ __lsan_disable();
+
+ CommonInitialize(argc, argv);
+ SdrObject::GetGlobalDrawObjectItemPool();
+
+ comphelper::getProcessServiceFactory()->createInstance("com.sun.star.comp.Draw.PresentationDocument");
+
+ if (__lsan_enable)
+ __lsan_enable();
+
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportPPT(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/pptfuzzer.options b/vcl/workben/pptfuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/pptfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/pptxfuzzer.cxx b/vcl/workben/pptxfuzzer.cxx
new file mode 100644
index 0000000000..33c6952ec8
--- /dev/null
+++ b/vcl/workben/pptxfuzzer.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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+extern "C" bool TestImportPPTX(SvStream& rStream);
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" void* SdCreateDialogFactory() { return nullptr; }
+
+extern "C" void* com_sun_star_comp_Draw_VisioImportFilter_get_implementation() { return nullptr; }
+
+extern "C" void* sdext_PDFIHybridAdaptor_get_implementation() { return nullptr; }
+
+extern "C" void* sdext_PDFIRawAdaptor_Writer_get_implementation() { return nullptr; }
+
+extern "C" void* sdext_PDFIRawAdaptor_Draw_get_implementation() { return nullptr; }
+
+extern "C" void* sdext_PDFIRawAdaptor_Impress_get_implementation() { return nullptr; }
+
+extern "C" void* sdext_PDFDetector_get_implementation() { return nullptr; }
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportPPTX(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/pptxfuzzer.options b/vcl/workben/pptxfuzzer.options
new file mode 100644
index 0000000000..e8c2b812b0
--- /dev/null
+++ b/vcl/workben/pptxfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 98304
diff --git a/vcl/workben/psdfuzzer.cxx b/vcl/workben/psdfuzzer.cxx
new file mode 100644
index 0000000000..652fd1cd33
--- /dev/null
+++ b/vcl/workben/psdfuzzer.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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+#include <filter/PsdReader.hxx>
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportPsdGraphic(aStream, aGraphic);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/psdfuzzer.options b/vcl/workben/psdfuzzer.options
new file mode 100644
index 0000000000..9af125e7a1
--- /dev/null
+++ b/vcl/workben/psdfuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+dict = psd.dict
diff --git a/vcl/workben/qpwfuzzer.cxx b/vcl/workben/qpwfuzzer.cxx
new file mode 100644
index 0000000000..0246f840df
--- /dev/null
+++ b/vcl/workben/qpwfuzzer.cxx
@@ -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/.
+ */
+
+#include <tools/stream.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * i18npool_component_getFactory( const char* , void* , void* );
+
+void * com_sun_star_i18n_LocaleDataImpl_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_get_implementation( void *, void * );
+void * com_sun_star_comp_framework_Desktop_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_get_implementation( void *, void * );
+void * com_sun_star_i18n_Collator_get_implementation( void *, void * );
+void * com_sun_star_i18n_NativeNumberSupplier_get_implementation( void *, void * );
+void * com_sun_star_i18n_NumberFormatCodeMapper_get_implementation( void *, void * );
+void * com_sun_star_i18n_Transliteration_get_implementation( void *, void * );
+void * emfio_emfreader_XEmfParser_get_implementation( void *, void * );
+void * i18npool_CalendarImpl_get_implementation( void *, void * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libi18npoollo.a", i18npool_component_getFactory },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { "com_sun_star_i18n_LocaleDataImpl_get_implementation", com_sun_star_i18n_LocaleDataImpl_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_Unicode_get_implementation", com_sun_star_i18n_BreakIterator_Unicode_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_get_implementation", com_sun_star_i18n_BreakIterator_get_implementation },
+ { "com_sun_star_comp_framework_Desktop_get_implementation", com_sun_star_comp_framework_Desktop_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_Unicode_get_implementation", com_sun_star_i18n_CharacterClassification_Unicode_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_get_implementation", com_sun_star_i18n_CharacterClassification_get_implementation },
+ { "com_sun_star_i18n_Collator_get_implementation", com_sun_star_i18n_Collator_get_implementation },
+ { "com_sun_star_i18n_NativeNumberSupplier_get_implementation", com_sun_star_i18n_NativeNumberSupplier_get_implementation },
+ { "com_sun_star_i18n_NumberFormatCodeMapper_get_implementation", com_sun_star_i18n_NumberFormatCodeMapper_get_implementation },
+ { "com_sun_star_i18n_Transliteration_get_implementation", com_sun_star_i18n_Transliteration_get_implementation },
+ { "emfio_emfreader_XEmfParser_get_implementation", emfio_emfreader_XEmfParser_get_implementation},
+ { "i18npool_CalendarImpl_get_implementation", i18npool_CalendarImpl_get_implementation},
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" void* ScCreateDialogFactory()
+{
+ return nullptr;
+}
+
+extern "C" bool TestImportQPW(SvStream &rStream);
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportQPW(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/qpwfuzzer.options b/vcl/workben/qpwfuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/qpwfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/rasfuzzer.cxx b/vcl/workben/rasfuzzer.cxx
new file mode 100644
index 0000000000..84678eace5
--- /dev/null
+++ b/vcl/workben/rasfuzzer.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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+#include <filter/RasReader.hxx>
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportRasGraphic(aStream, aGraphic);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/rasfuzzer.options b/vcl/workben/rasfuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/rasfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/rtffuzzer.cxx b/vcl/workben/rtffuzzer.cxx
new file mode 100644
index 0000000000..cd68a84eda
--- /dev/null
+++ b/vcl/workben/rtffuzzer.cxx
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * i18npool_component_getFactory( const char* , void* , void* );
+
+void * com_sun_star_i18n_LocaleDataImpl_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_get_implementation( void *, void * );
+void * com_sun_star_comp_framework_Desktop_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_get_implementation( void *, void * );
+void * com_sun_star_i18n_NativeNumberSupplier_get_implementation( void *, void * );
+void * com_sun_star_i18n_NumberFormatCodeMapper_get_implementation( void *, void * );
+void * com_sun_star_i18n_Transliteration_get_implementation( void *, void * );
+void * com_sun_star_comp_Writer_RtfFilter_get_implementation( void *, void * );
+void * emfio_emfreader_XEmfParser_get_implementation( void *, void * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libi18npoollo.a", i18npool_component_getFactory },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { "com_sun_star_i18n_LocaleDataImpl_get_implementation", com_sun_star_i18n_LocaleDataImpl_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_Unicode_get_implementation", com_sun_star_i18n_BreakIterator_Unicode_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_get_implementation", com_sun_star_i18n_BreakIterator_get_implementation },
+ { "com_sun_star_comp_framework_Desktop_get_implementation", com_sun_star_comp_framework_Desktop_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_Unicode_get_implementation", com_sun_star_i18n_CharacterClassification_Unicode_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_get_implementation", com_sun_star_i18n_CharacterClassification_get_implementation },
+ { "com_sun_star_i18n_NativeNumberSupplier_get_implementation", com_sun_star_i18n_NativeNumberSupplier_get_implementation },
+ { "com_sun_star_i18n_NumberFormatCodeMapper_get_implementation", com_sun_star_i18n_NumberFormatCodeMapper_get_implementation },
+ { "com_sun_star_i18n_Transliteration_get_implementation", com_sun_star_i18n_Transliteration_get_implementation },
+ { "com_sun_star_comp_Writer_RtfFilter_get_implementation", com_sun_star_comp_Writer_RtfFilter_get_implementation },
+ { "emfio_emfreader_XEmfParser_get_implementation", emfio_emfreader_XEmfParser_get_implementation},
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" void* SwCreateDialogFactory()
+{
+ return nullptr;
+}
+
+extern "C" bool TestImportRTF(SvStream &rStream);
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportRTF(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/rtffuzzer.options b/vcl/workben/rtffuzzer.options
new file mode 100644
index 0000000000..adb4b57135
--- /dev/null
+++ b/vcl/workben/rtffuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+dict = rtf.dict
diff --git a/vcl/workben/scrtffuzzer.cxx b/vcl/workben/scrtffuzzer.cxx
new file mode 100644
index 0000000000..56c0cddd7a
--- /dev/null
+++ b/vcl/workben/scrtffuzzer.cxx
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void* i18npool_component_getFactory(const char*, void*, void*);
+
+void* com_sun_star_i18n_LocaleDataImpl_get_implementation(void*, void*);
+void* com_sun_star_i18n_BreakIterator_Unicode_get_implementation(void*, void*);
+void* com_sun_star_i18n_BreakIterator_get_implementation(void*, void*);
+void* com_sun_star_comp_framework_Desktop_get_implementation(void*, void*);
+void* com_sun_star_i18n_CharacterClassification_Unicode_get_implementation(void*, void*);
+void* com_sun_star_i18n_CharacterClassification_get_implementation(void*, void*);
+void* com_sun_star_i18n_NativeNumberSupplier_get_implementation(void*, void*);
+void* com_sun_star_i18n_NumberFormatCodeMapper_get_implementation(void*, void*);
+void* com_sun_star_i18n_Transliteration_get_implementation(void*, void*);
+void* i18npool_CalendarImpl_get_implementation(void*, void*);
+}
+
+const lib_to_factory_mapping* lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[]
+ = { { "libi18npoollo.a", i18npool_component_getFactory }, { 0, 0 } };
+
+ return map;
+}
+
+const lib_to_constructor_mapping* lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[]
+ = { { "com_sun_star_i18n_LocaleDataImpl_get_implementation",
+ com_sun_star_i18n_LocaleDataImpl_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_Unicode_get_implementation",
+ com_sun_star_i18n_BreakIterator_Unicode_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_get_implementation",
+ com_sun_star_i18n_BreakIterator_get_implementation },
+ { "com_sun_star_comp_framework_Desktop_get_implementation",
+ com_sun_star_comp_framework_Desktop_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_Unicode_get_implementation",
+ com_sun_star_i18n_CharacterClassification_Unicode_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_get_implementation",
+ com_sun_star_i18n_CharacterClassification_get_implementation },
+ { "com_sun_star_i18n_NativeNumberSupplier_get_implementation",
+ com_sun_star_i18n_NativeNumberSupplier_get_implementation },
+ { "com_sun_star_i18n_NumberFormatCodeMapper_get_implementation",
+ com_sun_star_i18n_NumberFormatCodeMapper_get_implementation },
+ { "com_sun_star_i18n_Transliteration_get_implementation",
+ com_sun_star_i18n_Transliteration_get_implementation },
+ { "i18npool_CalendarImpl_get_implementation",
+ i18npool_CalendarImpl_get_implementation },
+ { 0, 0 } };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*) { return nullptr; }
+
+extern "C" void* ScCreateDialogFactory() { return nullptr; }
+
+extern "C" bool TestImportCalcRTF(SvStream& rStream);
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportCalcRTF(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/scrtffuzzer.options b/vcl/workben/scrtffuzzer.options
new file mode 100644
index 0000000000..f41b545a43
--- /dev/null
+++ b/vcl/workben/scrtffuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 32768
diff --git a/vcl/workben/sftfuzzer.cxx b/vcl/workben/sftfuzzer.cxx
new file mode 100644
index 0000000000..f0a4a9a8ca
--- /dev/null
+++ b/vcl/workben/sftfuzzer.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/.
+ */
+
+#include <fontsubset.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping* lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = { { 0, 0 } };
+
+ return map;
+}
+
+const lib_to_constructor_mapping* lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = { { 0, 0 } };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*) { return nullptr; }
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ return TestFontSubset(data, size);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/sftfuzzer.options b/vcl/workben/sftfuzzer.options
new file mode 100644
index 0000000000..98f36ac231
--- /dev/null
+++ b/vcl/workben/sftfuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+dict = otf.dict
diff --git a/vcl/workben/slkfuzzer.cxx b/vcl/workben/slkfuzzer.cxx
new file mode 100644
index 0000000000..ddf0487874
--- /dev/null
+++ b/vcl/workben/slkfuzzer.cxx
@@ -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/.
+ */
+
+#include <tools/stream.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * i18npool_component_getFactory( const char* , void* , void* );
+
+void * com_sun_star_i18n_LocaleDataImpl_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_get_implementation( void *, void * );
+void * com_sun_star_comp_framework_Desktop_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_get_implementation( void *, void * );
+void * com_sun_star_i18n_Collator_get_implementation( void *, void * );
+void * com_sun_star_i18n_NativeNumberSupplier_get_implementation( void *, void * );
+void * com_sun_star_i18n_NumberFormatCodeMapper_get_implementation( void *, void * );
+void * com_sun_star_i18n_Transliteration_get_implementation( void *, void * );
+void * emfio_emfreader_XEmfParser_get_implementation( void *, void * );
+void * i18npool_CalendarImpl_get_implementation( void *, void * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libi18npoollo.a", i18npool_component_getFactory },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { "com_sun_star_i18n_LocaleDataImpl_get_implementation", com_sun_star_i18n_LocaleDataImpl_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_Unicode_get_implementation", com_sun_star_i18n_BreakIterator_Unicode_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_get_implementation", com_sun_star_i18n_BreakIterator_get_implementation },
+ { "com_sun_star_comp_framework_Desktop_get_implementation", com_sun_star_comp_framework_Desktop_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_Unicode_get_implementation", com_sun_star_i18n_CharacterClassification_Unicode_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_get_implementation", com_sun_star_i18n_CharacterClassification_get_implementation },
+ { "com_sun_star_i18n_Collator_get_implementation", com_sun_star_i18n_Collator_get_implementation },
+ { "com_sun_star_i18n_NativeNumberSupplier_get_implementation", com_sun_star_i18n_NativeNumberSupplier_get_implementation },
+ { "com_sun_star_i18n_NumberFormatCodeMapper_get_implementation", com_sun_star_i18n_NumberFormatCodeMapper_get_implementation },
+ { "com_sun_star_i18n_Transliteration_get_implementation", com_sun_star_i18n_Transliteration_get_implementation },
+ { "emfio_emfreader_XEmfParser_get_implementation", emfio_emfreader_XEmfParser_get_implementation},
+ { "i18npool_CalendarImpl_get_implementation", i18npool_CalendarImpl_get_implementation},
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" void* ScCreateDialogFactory()
+{
+ return nullptr;
+}
+
+extern "C" bool TestImportSLK(SvStream &rStream);
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportSLK(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/slkfuzzer.options b/vcl/workben/slkfuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/slkfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/svdem.cxx b/vcl/workben/svdem.cxx
new file mode 100644
index 0000000000..5c7cbb12ad
--- /dev/null
+++ b/vcl/workben/svdem.cxx
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/main.h>
+#include <sal/log.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/extendapplicationenvironment.hxx>
+#include <cppuhelper/bootstrap.hxx>
+#include <comphelper/processfactory.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <framework/desktop.hxx>
+
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/wrkwin.hxx>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace cppu;
+
+// Forward declaration
+static void Main();
+
+SAL_IMPLEMENT_MAIN()
+{
+ try
+ {
+ tools::extendApplicationEnvironment();
+
+ Reference< XComponentContext > xContext = defaultBootstrap_InitialComponentContext();
+ Reference< XMultiServiceFactory > xServiceManager( xContext->getServiceManager(), UNO_QUERY );
+
+ if( !xServiceManager.is() )
+ Application::Abort( "Failed to bootstrap" );
+
+ comphelper::setProcessServiceFactory( xServiceManager );
+
+ LanguageTag::setConfiguredSystemLanguage(MsLangId::getSystemLanguage());
+
+ InitVCL();
+ ::Main();
+
+ framework::getDesktop(::comphelper::getProcessComponentContext())->terminate();
+
+ DeInitVCL();
+ }
+ catch (const Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("vcl.app", "Fatal");
+ return 1;
+ }
+ catch (const std::exception &e)
+ {
+ fprintf(stderr, "fatal error: %s\n", e.what());
+ return 1;
+ }
+
+ return 0;
+}
+
+namespace {
+
+class MyWin : public WorkWindow
+{
+public:
+ MyWin( vcl::Window* pParent, WinBits nWinStyle );
+};
+
+}
+
+void Main()
+{
+ ScopedVclPtrInstance< MyWin > aMainWin( nullptr, WB_APP | WB_STDWORK );
+ aMainWin->SetText("VCL - Workbench");
+ aMainWin->Show();
+
+ Application::Execute();
+}
+
+MyWin::MyWin( vcl::Window* pParent, WinBits nWinStyle ) :
+ WorkWindow( pParent, nWinStyle )
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/svgfuzzer.cxx b/vcl/workben/svgfuzzer.cxx
new file mode 100644
index 0000000000..593afc21b9
--- /dev/null
+++ b/vcl/workben/svgfuzzer.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 <tools/stream.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <com/sun/star/io/WrongFormatException.hpp>
+#include <osl/detail/component-mapping.h>
+
+extern "C" bool TestImportSVG(SvStream& rStream);
+
+const lib_to_factory_mapping* lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = { { 0, 0 } };
+
+ return map;
+}
+
+const lib_to_constructor_mapping* lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = { { 0, 0 } };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*) { return nullptr; }
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportSVG(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/svgfuzzer.options b/vcl/workben/svgfuzzer.options
new file mode 100644
index 0000000000..95a7cc501d
--- /dev/null
+++ b/vcl/workben/svgfuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 32000
+dict = svg.dict
diff --git a/vcl/workben/svmfuzzer.cxx b/vcl/workben/svmfuzzer.cxx
new file mode 100644
index 0000000000..53757e3199
--- /dev/null
+++ b/vcl/workben/svmfuzzer.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 <tools/stream.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" bool TestImportSVM(SvStream &rStream);
+
+extern "C" {
+void * com_sun_star_i18n_LocaleDataImpl_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_get_implementation( void *, void * );
+void * com_sun_star_i18n_NativeNumberSupplier_get_implementation( void *, void * );
+void * com_sun_star_i18n_NumberFormatCodeMapper_get_implementation( void *, void * );
+void * com_sun_star_comp_rendering_CanvasFactory_get_implementation( void *, void * );
+void * com_sun_star_comp_rendering_Canvas_VCL_get_implementation( void *, void * );
+void * linguistic_ConvDicList_get_implementation( void *, void * );
+void * linguistic_DicList_get_implementation( void *, void * );
+void * linguistic_LinguProps_get_implementation( void *, void * );
+void * linguistic_LngSvcMgr_get_implementation( void *, void * );
+void * linguistic_GrammarCheckingIterator_get_implementation( void *, void * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { "com_sun_star_i18n_LocaleDataImpl_get_implementation", com_sun_star_i18n_LocaleDataImpl_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_Unicode_get_implementation", com_sun_star_i18n_BreakIterator_Unicode_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_get_implementation", com_sun_star_i18n_BreakIterator_get_implementation },
+ { "com_sun_star_i18n_NativeNumberSupplier_get_implementation", com_sun_star_i18n_NativeNumberSupplier_get_implementation },
+ { "com_sun_star_i18n_NumberFormatCodeMapper_get_implementation", com_sun_star_i18n_NumberFormatCodeMapper_get_implementation },
+ { "com_sun_star_comp_rendering_CanvasFactory_get_implementation", com_sun_star_comp_rendering_CanvasFactory_get_implementation },
+ { "com_sun_star_comp_rendering_Canvas_VCL_get_implementation", com_sun_star_comp_rendering_Canvas_VCL_get_implementation },
+ { "linguistic_ConvDicList_get_implementation", linguistic_ConvDicList_get_implementation },
+ { "linguistic_DicList_get_implementation", linguistic_DicList_get_implementation },
+ { "linguistic_LinguProps_get_implementation", linguistic_LinguProps_get_implementation },
+ { "linguistic_LngSvcMgr_get_implementation", linguistic_LngSvcMgr_get_implementation },
+ { "linguistic_GrammarCheckingIterator_get_implementation", linguistic_GrammarCheckingIterator_get_implementation },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportSVM(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/svmfuzzer.options b/vcl/workben/svmfuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/svmfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/svpclient.cxx b/vcl/workben/svpclient.cxx
new file mode 100644
index 0000000000..18d1f1aaad
--- /dev/null
+++ b/vcl/workben/svpclient.cxx
@@ -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 .
+ */
+
+#include <sal/main.h>
+#include <sal/log.hxx>
+
+#include <cppuhelper/bootstrap.hxx>
+#include <comphelper/processfactory.hxx>
+
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/toolkit/fixed.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/graph.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/extendapplicationenvironment.hxx>
+#include <tools/stream.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <math.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace cppu;
+
+// Forward declaration
+static void Main();
+
+SAL_IMPLEMENT_MAIN()
+{
+ try
+ {
+ tools::extendApplicationEnvironment();
+
+ // create the global service-manager
+ Reference< XComponentContext > xContext = defaultBootstrap_InitialComponentContext();
+ Reference< XMultiServiceFactory > xServiceManager( xContext->getServiceManager(), UNO_QUERY );
+
+ if( !xServiceManager.is() )
+ Application::Abort( "Failed to bootstrap" );
+
+ comphelper::setProcessServiceFactory( xServiceManager );
+
+ InitVCL();
+ ::Main();
+ DeInitVCL();
+ }
+ catch (const Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("vcl", "Fatal");
+ return 1;
+ }
+ catch (const std::exception& e)
+ {
+ SAL_WARN("vcl", "Fatal: " << e.what());
+ return 1;
+ }
+
+ return 0;
+}
+
+namespace {
+
+class MyWin : public WorkWindow
+{
+ VclPtr<PushButton> m_aListButton;
+ VclPtr<ListBox> m_aSvpBitmaps;
+ VclPtr<FixedImage> m_aImage;
+ VclPtr<PushButton> m_aQuitButton;
+public:
+ MyWin( vcl::Window* pParent, WinBits nWinStyle );
+
+ virtual bool Close() override;
+ virtual ~MyWin() override { disposeOnce(); }
+ virtual void dispose() override;
+
+ void parseList( std::string_view rList );
+ static OString processCommand( const OString& rCommand );
+
+ DECL_LINK( ListHdl, Button*, void );
+ DECL_LINK( SelectHdl, ListBox&, void );
+ DECL_STATIC_LINK( MyWin, QuitHdl, Button*, void );
+};
+
+}
+
+void Main()
+{
+ ScopedVclPtrInstance< MyWin > aMainWin( nullptr, WB_STDWORK );
+ aMainWin->SetText( "SvpClient" );
+ aMainWin->Show();
+
+ Application::Execute();
+}
+
+MyWin::MyWin( vcl::Window* pParent, WinBits nWinStyle ) :
+ WorkWindow( pParent, nWinStyle ),
+ m_aListButton(VclPtr<PushButton>::Create(this, 0)),
+ m_aSvpBitmaps(VclPtr<ListBox>::Create(this, WB_BORDER)),
+ m_aImage(VclPtr<FixedImage>::Create(this, WB_BORDER)),
+ m_aQuitButton(VclPtr<PushButton>::Create(this, 0))
+{
+ m_aListButton->SetPosSizePixel( Point( 10, 10 ), Size( 120, 25 ) );
+ m_aListButton->SetText( "List Elements" );
+ m_aListButton->SetClickHdl( LINK( this, MyWin, ListHdl ) );
+ m_aListButton->Show();
+
+ m_aSvpBitmaps->SetPosSizePixel( Point( 10, 40 ), Size( 150, 150 ) );
+ m_aSvpBitmaps->SetSelectHdl( LINK( this, MyWin, SelectHdl ) );
+ m_aSvpBitmaps->Show();
+
+ m_aImage->SetPosSizePixel( Point( 170, 10 ), Size( 400, 400 ) );
+ m_aImage->Show();
+
+ m_aQuitButton->SetPosSizePixel( Point( 10, 300 ), Size( 120,25 ) );
+ m_aQuitButton->SetText( "Quit SVP server" );
+ m_aQuitButton->SetClickHdl( LINK( this, MyWin, QuitHdl ) );
+ m_aQuitButton->Show();
+}
+
+bool MyWin::Close()
+{
+ bool bRet = WorkWindow::Close();
+ if( bRet )
+ Application::Quit();
+ return bRet;
+}
+
+void MyWin::dispose()
+{
+ m_aListButton.disposeAndClear();
+ m_aSvpBitmaps.disposeAndClear();
+ m_aImage.disposeAndClear();
+ m_aQuitButton.disposeAndClear();
+ WorkWindow::dispose();
+}
+
+void MyWin::parseList( std::string_view rList )
+{
+ sal_Int32 nTokenPos = 0;
+ OUString aElementType;
+ m_aSvpBitmaps->Clear();
+ while( nTokenPos >= 0 )
+ {
+ std::string_view aLine = o3tl::getToken(rList, 0, '\n', nTokenPos );
+ if( aLine.empty() || aLine[0] == '#' )
+ continue;
+
+ if( o3tl::starts_with(aLine, "ElementType: " ) )
+ aElementType = OStringToOUString( aLine.substr( 13 ), RTL_TEXTENCODING_ASCII_US );
+ else
+ {
+ OUString aNewElement =
+ aElementType + ": " +
+ OStringToOUString( aLine, RTL_TEXTENCODING_ASCII_US );
+ m_aSvpBitmaps->InsertEntry( aNewElement );
+ }
+ }
+}
+
+OString MyWin::processCommand( const OString& rCommand )
+{
+ static const char* pEnv = getenv("SVP_LISTENER_PORT");
+ OStringBuffer aAnswer;
+ int nPort = (pEnv && *pEnv) ? atoi(pEnv) : 8000;
+ int nSocket = socket( PF_INET, SOCK_STREAM, 0 );
+ if( nSocket >= 0)
+ {
+ struct sockaddr_in addr;
+ memset(&addr, 0, sizeof(struct sockaddr_in));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(nPort);
+ addr.sin_addr.s_addr = INADDR_ANY;
+ if( connect( nSocket, reinterpret_cast<sockaddr*>(&addr), sizeof(addr) ) )
+ {
+ perror( "SvpElementContainer: connect() failed" );
+ }
+ else
+ {
+ ssize_t nBytes = 0;
+ ssize_t fd = write( nSocket, rCommand.getStr(), rCommand.getLength() );
+
+ if (fd == 0)
+ SAL_WARN("vcl", "Connection closed on other end");
+ else if (fd < 0)
+ SAL_WARN("vcl", "Error writing to socket: " << strerror( errno ));
+
+ fd = write( nSocket, "\n", 1 );
+
+ if (fd == 0)
+ SAL_WARN("vcl", "Connection closed on other end");
+ else if (fd < 0)
+ SAL_WARN("vcl", "Error writing to socket: " << strerror( errno ));
+
+
+ char buf[256];
+ do
+ {
+ nBytes = read( nSocket, buf, sizeof(buf) );
+ aAnswer.append( buf, nBytes );
+ } while( nBytes == sizeof( buf ) );
+ }
+ close(nSocket);
+ }
+ else
+ perror( "SvpElementContainer: socket() failed\n" );
+ return aAnswer.makeStringAndClear();
+}
+
+IMPL_LINK_NOARG( MyWin, ListHdl, Button*, void)
+{
+ parseList( processCommand( "list"_ostr ) );
+}
+
+IMPL_STATIC_LINK_NOARG( MyWin, QuitHdl, Button*, void)
+{
+ processCommand( "quit"_ostr );
+}
+
+IMPL_LINK_NOARG( MyWin, SelectHdl, ListBox&, void)
+{
+ OUString aEntry = m_aSvpBitmaps->GetSelectedEntry();
+ sal_Int32 nPos = aEntry.indexOf( ": " );
+ if( nPos == -1 )
+ return;
+
+ OString aCommand =
+ "get " +
+ OUStringToOString( aEntry.subView( nPos+2 ), RTL_TEXTENCODING_ASCII_US );
+ OString aAnswer( processCommand( aCommand ) );
+ SvMemoryStream aStream( aAnswer.getLength() );
+ aStream.WriteBytes( aAnswer.getStr(), aAnswer.getLength() );
+ aStream.Seek( STREAM_SEEK_TO_BEGIN );
+
+ Graphic aGraphicResult;
+ GraphicFilter &rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.ImportGraphic( aGraphicResult, u"import", aStream );
+
+ BitmapEx aBitmap = aGraphicResult.GetBitmapEx();
+
+ SAL_INFO("vcl", "got bitmap of size " << aBitmap.GetSizePixel().Width() << "x" << aBitmap.GetSizePixel().Height());
+ Size aFixedSize( aBitmap.GetSizePixel() );
+ aFixedSize.AdjustWidth(10 );
+ aFixedSize.AdjustHeight(10 );
+ m_aImage->SetSizePixel( aFixedSize );
+ m_aImage->SetImage( Image( aBitmap ) );
+
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/svptest.cxx b/vcl/workben/svptest.cxx
new file mode 100644
index 0000000000..1ecc03835f
--- /dev/null
+++ b/vcl/workben/svptest.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 <sal/config.h>
+
+#include <sal/main.h>
+#include <sal/log.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/extendapplicationenvironment.hxx>
+
+#include <cppuhelper/bootstrap.hxx>
+#include <comphelper/processfactory.hxx>
+
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/vclptr.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <rtl/ustrbuf.hxx>
+
+#include <math.h>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace cppu;
+
+// Forward declaration
+static void Main();
+
+SAL_IMPLEMENT_MAIN()
+{
+ try
+ {
+ tools::extendApplicationEnvironment();
+
+ Reference< XComponentContext > xContext = defaultBootstrap_InitialComponentContext();
+ Reference< XMultiServiceFactory > xServiceManager( xContext->getServiceManager(), UNO_QUERY );
+
+ if( !xServiceManager.is() )
+ Application::Abort( "Failed to bootstrap" );
+
+ comphelper::setProcessServiceFactory( xServiceManager );
+
+ InitVCL();
+ ::Main();
+ DeInitVCL();
+ }
+ catch (const Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("vcl.app", "Fatal");
+ return 1;
+ }
+ catch (const std::exception &e)
+ {
+ fprintf(stderr, "fatal error: %s\n", e.what());
+ return 1;
+ }
+
+ return 0;
+}
+
+namespace {
+
+class MyWin : public WorkWindow
+{
+ Bitmap m_aBitmap;
+public:
+ MyWin( vcl::Window* pParent, WinBits nWinStyle );
+
+ virtual void Paint( vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& rRect ) override;
+};
+
+}
+
+void Main()
+{
+ ScopedVclPtrInstance< MyWin > aMainWin( nullptr, WB_APP | WB_STDWORK );
+ aMainWin->SetText( "VCL - Workbench" );
+ aMainWin->Show();
+
+ Application::Execute();
+}
+
+MyWin::MyWin( vcl::Window* pParent, WinBits nWinStyle ) :
+ WorkWindow( pParent, nWinStyle ),
+ m_aBitmap(Size(256, 256), vcl::PixelFormat::N32_BPP)
+{
+ // prepare an alpha mask
+ BitmapScopedWriteAccess pAcc(m_aBitmap);
+ for( int nX = 0; nX < 256; nX++ )
+ {
+ for( int nY = 0; nY < 256; nY++ )
+ {
+ double fRed = 255.0-1.5*std::hypot(nX, nY);
+ if( fRed < 0.0 )
+ fRed = 0.0;
+ double fGreen = 255.0-1.5*std::hypot(255-nX, nY);
+ if( fGreen < 0.0 )
+ fGreen = 0.0;
+ double fBlue = 255.0-1.5*std::hypot(128-nX, 255-nY);
+ if( fBlue < 0.0 )
+ fBlue = 0.0;
+ pAcc->SetPixel( nY, nX, BitmapColor( sal_uInt8(fRed), sal_uInt8(fGreen), sal_uInt8(fBlue) ) );
+ }
+ }
+}
+
+static Point project( const Point& rPoint )
+{
+ const double angle_x = M_PI / 6.0;
+ const double angle_z = M_PI / 6.0;
+
+ // transform planar coordinates to 3d
+ double x = rPoint.X();
+ double y = rPoint.Y();
+
+ // rotate around X axis
+ double x1 = x;
+ double y1 = y * cos( angle_x );
+ double z1 = y * sin( angle_x );
+
+ // rotate around Z axis
+ double x2 = x1 * cos( angle_z ) + y1 * sin( angle_z );
+ //double y2 = y1 * cos( angle_z ) - x1 * sin( angle_z );
+ double z2 = z1;
+
+ return Point( static_cast<sal_Int32>(x2), static_cast<sal_Int32>(z2) );
+}
+
+static Color approachColor( const Color& rFrom, const Color& rTo )
+{
+ Color aColor;
+ sal_uInt8 nDiff;
+ // approach red
+ if( rFrom.GetRed() < rTo.GetRed() )
+ {
+ nDiff = rTo.GetRed() - rFrom.GetRed();
+ aColor.SetRed( rFrom.GetRed() + std::min<sal_uInt8>( nDiff, 10 ) );
+ }
+ else if( rFrom.GetRed() > rTo.GetRed() )
+ {
+ nDiff = rFrom.GetRed() - rTo.GetRed();
+ aColor.SetRed( rFrom.GetRed() - std::min<sal_uInt8>( nDiff, 10 ) );
+ }
+ else
+ aColor.SetRed( rFrom.GetRed() );
+
+ // approach Green
+ if( rFrom.GetGreen() < rTo.GetGreen() )
+ {
+ nDiff = rTo.GetGreen() - rFrom.GetGreen();
+ aColor.SetGreen( rFrom.GetGreen() + std::min<sal_uInt8>( nDiff, 10 ) );
+ }
+ else if( rFrom.GetGreen() > rTo.GetGreen() )
+ {
+ nDiff = rFrom.GetGreen() - rTo.GetGreen();
+ aColor.SetGreen( rFrom.GetGreen() - std::min<sal_uInt8>( nDiff, 10 ) );
+ }
+ else
+ aColor.SetGreen( rFrom.GetGreen() );
+
+ // approach blue
+ if( rFrom.GetBlue() < rTo.GetBlue() )
+ {
+ nDiff = rTo.GetBlue() - rFrom.GetBlue();
+ aColor.SetBlue( rFrom.GetBlue() + std::min<sal_uInt8>( nDiff, 10 ) );
+ }
+ else if( rFrom.GetBlue() > rTo.GetBlue() )
+ {
+ nDiff = rFrom.GetBlue() - rTo.GetBlue();
+ aColor.SetBlue( rFrom.GetBlue() - std::min<sal_uInt8>( nDiff, 10 ) );
+ }
+ else
+ aColor.SetBlue( rFrom.GetBlue() );
+
+ return aColor;
+}
+
+#define DELTA 5.0
+void MyWin::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ WorkWindow::Paint(rRenderContext, rRect);
+
+ rRenderContext.Push();
+ MapMode aMapMode(MapUnit::Map100thMM);
+
+ rRenderContext.SetMapMode(aMapMode);
+
+ Size aPaperSize = rRenderContext.GetOutputSize();
+ Point aCenter(aPaperSize.Width() / 2 - 300,
+ (aPaperSize.Height() - 8400) / 2 + 8400);
+ Point aP1(aPaperSize.Width() / 48, 0), aP2(aPaperSize.Width() / 40, 0);
+ Point aPoint;
+
+ rRenderContext.DrawRect(tools::Rectangle(Point(0, 0), aPaperSize));
+ rRenderContext.DrawRect(tools::Rectangle(Point(100, 100),
+ Size(aPaperSize.Width() - 200,
+ aPaperSize.Height() - 200)));
+ rRenderContext.DrawRect(tools::Rectangle(Point(200, 200),
+ Size(aPaperSize.Width() - 400,
+ aPaperSize.Height() - 400)));
+ rRenderContext.DrawRect(tools::Rectangle(Point(300, 300),
+ Size(aPaperSize.Width() - 600,
+ aPaperSize.Height() - 600)));
+
+ const int nFontCount = rRenderContext.GetFontFaceCollectionCount();
+ const int nFontSamples = (nFontCount < 15) ? nFontCount : 15;
+ for (int i = 0; i < nFontSamples; ++i)
+ {
+
+ FontMetric aFont(rRenderContext.GetFontMetricFromCollection((i * nFontCount) / nFontSamples));
+ aFont.SetFontHeight(400 + (i % 7) * 100);
+ aFont.SetOrientation(Degree10(i * (3600 / nFontSamples)));
+ rRenderContext.SetFont(aFont);
+
+ sal_uInt8 nRed = (i << 6) & 0xC0;
+ sal_uInt8 nGreen = (i << 4) & 0xC0;
+ sal_uInt8 nBlue = (i << 2) & 0xC0;
+ rRenderContext.SetTextColor(Color(nRed, nGreen, nBlue));
+
+ rRenderContext.DrawText(tools::Rectangle(Point((aPaperSize.Width() - 4000) / 2, 2000),
+ Size(aPaperSize.Width() - 2100, aPaperSize.Height() - 4000)),
+ "SVP test program",
+ DrawTextFlags::MultiLine);
+ }
+
+ rRenderContext.SetFillColor();
+ rRenderContext.DrawRect(tools::Rectangle(Point(aPaperSize.Width() - 4000, 1000),
+ Size(3000, 3000)));
+ rRenderContext.DrawBitmap(Point(aPaperSize.Width() - 4000, 1000),
+ Size( 3000,3000 ),
+ m_aBitmap);
+
+ Color const aWhite(0xff, 0xff, 0xff);
+ Color const aBlack(0, 0, 0);
+ Color const aLightRed(0xff, 0, 0);
+ Color const aDarkRed(0x40, 0, 0);
+ Color const aLightBlue(0, 0, 0xff);
+ Color const aDarkBlue(0,0,0x40);
+ Color const aLightGreen(0, 0xff, 0);
+ Color const aDarkGreen(0, 0x40, 0);
+
+ Gradient aGradient(css::awt::GradientStyle_LINEAR, aBlack, aWhite);
+ aGradient.SetAngle(900_deg10);
+ rRenderContext.DrawGradient(tools::Rectangle(Point(1000, 4500),
+ Size(aPaperSize.Width() - 2000, 500)),
+ aGradient);
+ aGradient.SetStartColor(aDarkRed);
+ aGradient.SetEndColor(aLightBlue);
+ rRenderContext.DrawGradient(tools::Rectangle(Point(1000, 5300),
+ Size(aPaperSize.Width() - 2000, 500)),
+ aGradient);
+ aGradient.SetStartColor(aDarkBlue);
+ aGradient.SetEndColor(aLightGreen);
+ rRenderContext.DrawGradient(tools::Rectangle(Point(1000, 6100),
+ Size(aPaperSize.Width() - 2000, 500)),
+ aGradient);
+ aGradient.SetStartColor(aDarkGreen);
+ aGradient.SetEndColor(aLightRed);
+ rRenderContext.DrawGradient(tools::Rectangle(Point(1000, 6900),
+ Size(aPaperSize.Width() - 2000, 500)),
+ aGradient);
+
+ LineInfo aLineInfo(LineStyle::Solid, 200);
+ const double sind = sin(basegfx::deg2rad(DELTA));
+ const double cosd = cos(basegfx::deg2rad(DELTA));
+ const double factor = 1 + (DELTA / 1000.0);
+ int n = 0;
+ Color aLineColor(0, 0, 0);
+ Color aApproachColor(0, 0, 200);
+
+ while (aP2.X() < aCenter.X() && n++ < 680)
+ {
+ aLineInfo.SetWidth(n / 3);
+ aLineColor = approachColor(aLineColor, aApproachColor);
+ rRenderContext.SetLineColor(aLineColor);
+
+ // switch approach color
+ if (aApproachColor.IsRGBEqual(aLineColor))
+ {
+ if (aApproachColor.GetRed())
+ aApproachColor = Color(0, 0, 200);
+ else if (aApproachColor.GetGreen())
+ aApproachColor = Color(200, 0, 0);
+ else
+ aApproachColor = Color(0, 200, 0);
+ }
+
+ rRenderContext.DrawLine(project(aP1) + aCenter,
+ project(aP2) + aCenter,
+ aLineInfo);
+ aPoint.setX( static_cast<int>((static_cast<double>(aP1.X())*cosd - static_cast<double>(aP1.Y())*sind)*factor) );
+ aPoint.setY( static_cast<int>((static_cast<double>(aP1.Y())*cosd + static_cast<double>(aP1.X())*sind)*factor) );
+ aP1 = aPoint;
+ aPoint.setX( static_cast<int>((static_cast<double>(aP2.X())*cosd - static_cast<double>(aP2.Y())*sind)*factor) );
+ aPoint.setY( static_cast<int>((static_cast<double>(aP2.Y())*cosd + static_cast<double>(aP2.X())*sind)*factor) );
+ aP2 = aPoint;
+ }
+ rRenderContext.Pop();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/tgafuzzer.cxx b/vcl/workben/tgafuzzer.cxx
new file mode 100644
index 0000000000..32b7fcb3d5
--- /dev/null
+++ b/vcl/workben/tgafuzzer.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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+#include <filter/TgaReader.hxx>
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportTgaGraphic(aStream, aGraphic);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/tgafuzzer.options b/vcl/workben/tgafuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/tgafuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/tiffuzzer.cxx b/vcl/workben/tiffuzzer.cxx
new file mode 100644
index 0000000000..14dcf42ecf
--- /dev/null
+++ b/vcl/workben/tiffuzzer.cxx
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/FilterConfigItem.hxx>
+#include <filter/TiffReader.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ try
+ {
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportTiffGraphicImport(aStream, aGraphic);
+ }
+ catch (...)
+ {
+ }
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/tiffuzzer.options b/vcl/workben/tiffuzzer.options
new file mode 100644
index 0000000000..f0ed890b6f
--- /dev/null
+++ b/vcl/workben/tiffuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+dict = tiff.dict
diff --git a/vcl/workben/vcldemo.cxx b/vcl/workben/vcldemo.cxx
new file mode 100644
index 0000000000..6824b60393
--- /dev/null
+++ b/vcl/workben/vcldemo.cxx
@@ -0,0 +1,2270 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <thread>
+
+#include <config_features.h>
+
+#include <math.h>
+#include <rtl/math.hxx>
+#include <sal/log.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/random.hxx>
+#include <cppuhelper/bootstrap.hxx>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/registry/XSimpleRegistry.hpp>
+#include <com/sun/star/ucb/UniversalContentBroker.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <o3tl/safeint.hxx>
+#include <osl/time.h>
+#include <vcl/gradient.hxx>
+#include <vcl/vclmain.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <salhelper/thread.hxx>
+
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/stream.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/toolkit/combobox.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/help.hxx>
+#include <vcl/kernarray.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/ImageTree.hxx>
+#include <vcl/BitmapEmbossGreyFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+#include <framework/desktop.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+
+#define FIXME_SELF_INTERSECTING_WORKING 0
+#define FIXME_BOUNCE_BUTTON 0
+#define THUMB_REPEAT_FACTOR 10
+
+using namespace com::sun::star;
+
+namespace {
+ double getTimeNow()
+ {
+ TimeValue aValue;
+ osl_getSystemTime(&aValue);
+ return static_cast<double>(aValue.Seconds) * 1000 +
+ static_cast<double>(aValue.Nanosec) / (1000*1000);
+ }
+
+}
+
+namespace {
+
+enum RenderStyle {
+ RENDER_THUMB, // small view <n> to a page
+ RENDER_EXPANDED, // expanded view of this renderer
+};
+
+class DemoRenderer
+{
+ Bitmap maIntroBW;
+ BitmapEx maIntro;
+
+ int mnSegmentsX;
+ int mnSegmentsY;
+
+ struct RenderContext {
+ RenderStyle meStyle;
+ bool mbVDev;
+ DemoRenderer *mpDemoRenderer;
+ Size maSize;
+ };
+ struct RegionRenderer {
+ public:
+ RegionRenderer() :
+ sumTime(0),
+ countTime(0)
+ { }
+ virtual ~RegionRenderer() {}
+ virtual OUString getName() = 0;
+ virtual sal_uInt16 getAccelerator() = 0;
+ virtual void RenderRegion(OutputDevice &rDev, tools::Rectangle r,
+ const RenderContext &rCtx) = 0;
+ // repeating count for profiling (to exceed the poor time resolution on Windows)
+ virtual sal_uInt16 getTestRepeatCount() = 0;
+#define RENDER_DETAILS(name,key,repeat) \
+ virtual OUString getName() override \
+ { return SAL_STRINGIFY(name); } \
+ virtual sal_uInt16 getAccelerator() override \
+ { return key; } \
+ virtual sal_uInt16 getTestRepeatCount() override \
+ { return repeat; }
+
+ double sumTime;
+ int countTime;
+ };
+
+ std::vector< RegionRenderer * > maRenderers;
+ sal_Int32 mnSelectedRenderer;
+ sal_Int32 iterCount;
+
+ void InitRenderers();
+
+public:
+ DemoRenderer() : mnSegmentsX(0)
+ , mnSegmentsY(0)
+ , mnSelectedRenderer(-1)
+ , iterCount(0)
+#if FIXME_BOUNCE_BUTTON
+ , mpButton(NULL)
+ , mpButtonWin(NULL)
+ , mnBounceX(1)
+ , mnBounceY(1)
+#endif
+ {
+ if (!Application::LoadBrandBitmap(u"intro", maIntro))
+ Application::Abort("Failed to load intro image");
+
+ maIntroBW = maIntro.GetBitmap();
+
+ BitmapEx aTmpBmpEx(maIntroBW);
+ BitmapFilter::Filter(aTmpBmpEx, BitmapEmbossGreyFilter(0_deg100, 0_deg100));
+ maIntroBW = aTmpBmpEx.GetBitmap();
+
+ InitRenderers();
+ mnSegmentsY = rtl::math::round(std::sqrt(maRenderers.size()), 0,
+ rtl_math_RoundingMode_Down);
+ mnSegmentsX = (maRenderers.size() + mnSegmentsY - 1)/mnSegmentsY;
+ }
+
+ OUString getRendererList();
+ double getAndResetBenchmark(RenderStyle style);
+ void selectRenderer(std::u16string_view rName);
+ int selectNextRenderer();
+ void setIterCount(sal_Int32 iterCount);
+ sal_Int32 getIterCount() const;
+ void addTime(int i, double t);
+
+ Size maSize;
+ void SetSizePixel(const Size &rSize) { maSize = rSize; }
+ const Size& GetSizePixel() const { return maSize; }
+
+
+// more of a 'Window' concept - push upwards ?
+#if FIXME_BOUNCE_BUTTON
+ // Bouncing windows on click ...
+ PushButton *mpButton;
+ FloatingWindow *mpButtonWin;
+ AutoTimer maBounce;
+ int mnBounceX, mnBounceY;
+ DECL_LINK(BounceTimerCb, Timer*, void);
+#endif
+
+ bool MouseButtonDown(const MouseEvent& rMEvt);
+ void KeyInput(const KeyEvent& rKEvt);
+
+ static std::vector<tools::Rectangle> partition(const tools::Rectangle &rRect, int nX, int nY)
+ {
+ std::vector<tools::Rectangle> aRegions = partition(rRect.GetSize(), nX, nY);
+ for (auto & region : aRegions)
+ region.Move(rRect.Left(), rRect.Top());
+
+ return aRegions;
+ }
+
+ static std::vector<tools::Rectangle> partition(const RenderContext &rCtx, int nX, int nY)
+ {
+ return partition(rCtx.maSize, nX, nY);
+ }
+
+ static std::vector<tools::Rectangle> partition(Size aSize, int nX, int nY)
+ {
+ tools::Rectangle r;
+ std::vector<tools::Rectangle> aRegions;
+
+ // Make small cleared area for these guys
+ tools::Long nBorderSize = std::min(aSize.Height() / 32, aSize.Width() / 32);
+ tools::Long nBoxWidth = (aSize.Width() - nBorderSize*(nX+1)) / nX;
+ tools::Long nBoxHeight = (aSize.Height() - nBorderSize*(nY+1)) / nY;
+ for (int y = 0; y < nY; y++)
+ {
+ for (int x = 0; x < nX; x++)
+ {
+ r.SetPos(Point(nBorderSize + (nBorderSize + nBoxWidth) * x,
+ nBorderSize + (nBorderSize + nBoxHeight) * y));
+ r.SetSize(Size(nBoxWidth, nBoxHeight));
+ aRegions.push_back(r);
+ }
+ }
+
+ return aRegions;
+ }
+
+ static void clearRects(OutputDevice &rDev, std::vector<tools::Rectangle> &rRects)
+ {
+ for (size_t i = 0; i < rRects.size(); i++)
+ {
+ // knock up a nice little border
+ rDev.SetLineColor(COL_GRAY);
+ rDev.SetFillColor(COL_LIGHTGRAY);
+ if (i % 2)
+ {
+ int nBorderSize = rRects[i].GetWidth() / 5;
+ rDev.DrawRect(rRects[i], nBorderSize, nBorderSize);
+ }
+ else
+ rDev.DrawRect(rRects[i]);
+ }
+ }
+
+ static void drawBackground(OutputDevice &rDev, const tools::Rectangle& r)
+ {
+ rDev.Erase();
+ Gradient aGradient;
+ aGradient.SetStartColor(COL_BLUE);
+ aGradient.SetEndColor(COL_GREEN);
+ aGradient.SetStyle(css::awt::GradientStyle_LINEAR);
+ rDev.DrawGradient(r, aGradient);
+ }
+
+ struct DrawLines : public RegionRenderer
+ {
+ RENDER_DETAILS(lines,KEY_L,100)
+ virtual void RenderRegion(OutputDevice &rDev, tools::Rectangle r,
+ const RenderContext &rCtx) override
+ {
+ if (rCtx.meStyle == RENDER_EXPANDED)
+ {
+ AntialiasingFlags nOldAA = rDev.GetAntialiasing();
+ rDev.SetAntialiasing(AntialiasingFlags::Enable);
+
+ std::vector<tools::Rectangle> aRegions(DemoRenderer::partition(rCtx, 4, 4));
+ DemoRenderer::clearRects(rDev, aRegions);
+
+#if 0 // FIXME: get this through to the backend ...
+ double nTransparency[] = {
+ 1.0, 1.0, 1.0, 1.0,
+ 0.8, 0.8, 0.8, 0.8,
+ 0.5, 0.5, 0.5, 0.5,
+ 0.1, 0.1, 0.1, 0.1
+ };
+#endif
+ drawing::LineCap const eLineCaps[] = {
+ drawing::LineCap_BUTT, drawing::LineCap_ROUND, drawing::LineCap_SQUARE, drawing::LineCap_BUTT,
+ drawing::LineCap_BUTT, drawing::LineCap_ROUND, drawing::LineCap_SQUARE, drawing::LineCap_BUTT,
+ drawing::LineCap_BUTT, drawing::LineCap_ROUND, drawing::LineCap_SQUARE, drawing::LineCap_BUTT,
+ drawing::LineCap_BUTT, drawing::LineCap_ROUND, drawing::LineCap_SQUARE, drawing::LineCap_BUTT
+ };
+ basegfx::B2DLineJoin const eJoins[] = {
+ basegfx::B2DLineJoin::NONE, basegfx::B2DLineJoin::Bevel, basegfx::B2DLineJoin::Miter, basegfx::B2DLineJoin::Round,
+ basegfx::B2DLineJoin::NONE, basegfx::B2DLineJoin::Bevel, basegfx::B2DLineJoin::Miter, basegfx::B2DLineJoin::Round,
+ basegfx::B2DLineJoin::NONE, basegfx::B2DLineJoin::Bevel, basegfx::B2DLineJoin::Miter, basegfx::B2DLineJoin::Round,
+ basegfx::B2DLineJoin::NONE, basegfx::B2DLineJoin::Bevel, basegfx::B2DLineJoin::Miter, basegfx::B2DLineJoin::Round
+ };
+ double const aLineWidths[] = {
+ 10.0, 15.0, 20.0, 10.0,
+ 10.0, 15.0, 20.0, 10.0,
+ 10.0, 15.0, 20.0, 10.0,
+ 0.1, 1.0, 10.0, 50.0
+ };
+ for (size_t i = 0; i < aRegions.size(); i++)
+ {
+ // Half of them not-anti-aliased ..
+ if (i >= aRegions.size()/2)
+ rDev.SetAntialiasing(nOldAA);
+
+ static const struct {
+ double nX, nY;
+ } aPoints[] = {
+ { 0.2, 0.2 }, { 0.8, 0.3 }, { 0.7, 0.8 }
+ };
+ rDev.SetLineColor(COL_BLACK);
+ basegfx::B2DPolygon aPoly;
+ tools::Rectangle aSub(aRegions[i]);
+ for (const auto& rPoint : aPoints)
+ {
+ aPoly.append(basegfx::B2DPoint(aSub.Left() + aSub.GetWidth() * rPoint.nX,
+ aSub.Top() + aSub.GetHeight() * rPoint.nY));
+ }
+ rDev.DrawPolyLine(aPoly, aLineWidths[i], eJoins[i], eLineCaps[i]);
+ }
+ }
+ else
+ {
+ rDev.SetFillColor(COL_LIGHTRED);
+ rDev.SetLineColor(COL_BLACK);
+ rDev.DrawRect(r);
+
+ for(tools::Long i=0; i<r.GetHeight(); i+=15)
+ rDev.DrawLine(Point(r.Left(), r.Top()+i), Point(r.Right(), r.Bottom()-i));
+ for(tools::Long i=0; i<r.GetWidth(); i+=15)
+ rDev.DrawLine(Point(r.Left()+i, r.Bottom()), Point(r.Right()-i, r.Top()));
+
+ // Should draw a white-line across the middle
+ Color aLastPixel(COL_WHITE);
+ Point aCenter((r.Left() + r.Right())/2 - 4,
+ (r.Top() + r.Bottom())/2 - 4);
+ for(int i=0; i<8; i++)
+ {
+ rDev.DrawPixel(aCenter, aLastPixel);
+ aLastPixel = rDev.GetPixel(aCenter);
+ aCenter.Move(1,1);
+ }
+ }
+ }
+ };
+
+ struct DrawText : public RegionRenderer
+ {
+ RENDER_DETAILS(text,KEY_T,1)
+
+ virtual void RenderRegion(OutputDevice &rDev, tools::Rectangle r,
+ const RenderContext &rCtx) override
+ {
+ if (rCtx.meStyle == RENDER_EXPANDED)
+ {
+ std::vector<tools::Rectangle> aToplevelRegions(
+ DemoRenderer::partition(rCtx, 1, 3));
+ std::vector<tools::Rectangle> aSubRegions(
+ DemoRenderer::partition(aToplevelRegions[0], 4, 2));
+ tools::Rectangle aBottom(aToplevelRegions[1].TopLeft(),
+ aToplevelRegions[2].BottomRight());
+ DemoRenderer::clearRects(rDev,aSubRegions);
+ static struct {
+ bool mbClip;
+ bool mbArabicText;
+ bool mbRotate;
+ } const aRenderData[] = {
+ { false, false, false },
+ { false, true, false },
+ { false, true, true },
+ { false, false, true },
+ { true, false, true },
+ { true, true, true },
+ { true, true, false },
+ { true, false, false },
+ };
+
+ size_t i = 0;
+ for (int y = 0; y < 2; y++)
+ {
+ for (int x = 0; x < 4; x++)
+ {
+ assert(i < std::size(aRenderData));
+ drawText(rDev, aSubRegions[i], aRenderData[i].mbClip,
+ aRenderData[i].mbArabicText, aRenderData[i].mbRotate);
+ i++;
+ }
+ }
+
+ drawComplex(rDev, aBottom);
+ }
+ else
+ {
+ drawText(rDev, r, false, false, false);
+ }
+ }
+
+ static void drawText (OutputDevice &rDev, tools::Rectangle r, bool bClip, bool bArabicText, bool bRotate)
+ {
+ rDev.SetClipRegion( vcl::Region(r) );
+
+ const unsigned char pTextUTF8[] = {
+ 0xd9, 0x88, 0xd8, 0xa7, 0xd8, 0xad, 0xd9, 0x90,
+ 0xd8, 0xaf, 0xd9, 0x92, 0x20, 0xd8, 0xa5, 0xd8,
+ 0xab, 0xd9, 0x8d, 0xd9, 0x86, 0xd9, 0x8a, 0xd9,
+ 0x86, 0x20, 0xd8, 0xab, 0xd9, 0x84, 0xd8, 0xa7,
+ 0xd8, 0xab, 0xd8, 0xa9, 0xd9, 0x8c, 0x00
+ };
+ OUString aArabicText( reinterpret_cast<char const *>(pTextUTF8),
+ SAL_N_ELEMENTS( pTextUTF8 ) - 1,
+ RTL_TEXTENCODING_UTF8 );
+
+ OUString aText;
+
+ // To have more text displayed one after the other (overlapping, and in different colours), then
+ // change this value
+ const int nPrintNumCopies=1;
+
+ if (bArabicText)
+ aText = aArabicText;
+ else
+ aText = "Click any rect to zoom!!!!";
+
+ std::vector<OUString> aFontNames;
+
+ static Color const nCols[] = {
+ COL_BLACK, COL_BLUE, COL_GREEN, COL_CYAN, COL_RED, COL_MAGENTA,
+ COL_BROWN, COL_GRAY, COL_LIGHTGRAY, COL_LIGHTBLUE, COL_LIGHTGREEN,
+ COL_LIGHTCYAN, COL_LIGHTRED, COL_LIGHTMAGENTA, COL_YELLOW, COL_WHITE
+ };
+
+ // a few fonts to start with
+ const char *pNames[] = {
+ "Times", "Liberation Sans", "Arial", "Linux Biolinum G", "Linux Libertine Display G"
+ };
+
+ for (size_t i = 0; i < SAL_N_ELEMENTS(pNames); i++)
+ aFontNames.push_back(OUString::createFromAscii(pNames[i]));
+
+ if (bClip && !bRotate)
+ {
+ // only show the first quarter of the text
+ tools::Rectangle aRect( r.TopLeft(), Size( r.GetWidth()/2, r.GetHeight()/2 ) );
+ rDev.SetClipRegion( vcl::Region( aRect ) );
+ }
+
+ for (int i = 1; i < nPrintNumCopies+1; i++)
+ {
+ int nFontHeight=0, nFontIndex=0, nFontColorIndex=0;
+
+ if (nPrintNumCopies == 1)
+ {
+ float const nFontMagnitude = 0.25f;
+ // random font size to avoid buffering
+ nFontHeight = 1 + nFontMagnitude * (0.9 + comphelper::rng::uniform_real_distribution(0.0, std::nextafter(0.1, DBL_MAX))) * (r.Bottom() - r.Top());
+ nFontIndex=0;
+ nFontColorIndex=0;
+ }
+ else
+ {
+ // random font size to avoid buffering
+ nFontHeight = 1 + i * (0.9 + comphelper::rng::uniform_real_distribution(0.0, std::nextafter(0.1, DBL_MAX))) * (r.Top() - r.Bottom()) / nPrintNumCopies;
+ nFontIndex = (i % aFontNames.size());
+ nFontColorIndex=(i % aFontNames.size());
+ }
+
+ rDev.SetTextColor(nCols[nFontColorIndex]);
+ vcl::Font aFont( aFontNames[nFontIndex], Size(0, nFontHeight ));
+
+ if (bRotate)
+ {
+ tools::Rectangle aFontRect = r;
+
+ int nHeight = r.GetHeight();
+
+ // move the text to the bottom of the bounding rect before rotating
+ aFontRect.AdjustTop(nHeight/2 );
+ aFontRect.AdjustBottom(nHeight );
+
+ aFont.SetOrientation(450_deg10); // 45 degrees
+
+ rDev.SetFont(aFont);
+ rDev.DrawText(aFontRect, aText);
+
+ if (bClip)
+ {
+ tools::Rectangle aClipRect( Point( r.Left(), r.Top() + ( r.GetHeight()/2 ) ) , Size( r.GetWidth()/2, r.GetHeight()/2 ) );
+ rDev.SetClipRegion( vcl::Region( aClipRect ) );
+ }
+ else
+ rDev.SetClipRegion( vcl::Region(r) );
+ }
+ else
+ {
+ rDev.SetFont(aFont);
+ rDev.DrawText(r, aText);
+ }
+ }
+
+ rDev.SetClipRegion();
+ }
+
+ static void drawComplex (OutputDevice &rDev, tools::Rectangle r)
+ {
+ const unsigned char pInvalid[] = { 0xfe, 0x1f, 0 };
+ const unsigned char pDiacritic1[] = { 0x61, 0xcc, 0x8a, 0xcc, 0x8c, 0 };
+ const unsigned char pDiacritic2[] = { 0x61, 0xcc, 0x88, 0xcc, 0x86, 0 };
+ const unsigned char pDiacritic3[] = { 0x61, 0xcc, 0x8b, 0xcc, 0x87, 0 };
+ const unsigned char pJustification[] = {
+ 0x64, 0x20, 0xc3, 0xa1, 0xc3, 0xa9, 0x77, 0xc4, 0x8d,
+ 0xc5, 0xa1, 0xc3, 0xbd, 0xc5, 0x99, 0x20, 0xc4, 0x9b, 0
+ };
+ const unsigned char pEmojis[] = {
+ 0xf0, 0x9f, 0x8d, 0x80, 0xf0, 0x9f, 0x91, 0x98,
+ 0xf0, 0x9f, 0x92, 0x8a, 0xf0, 0x9f, 0x92, 0x99,
+ 0xf0, 0x9f, 0x92, 0xa4, 0xf0, 0x9f, 0x94, 0x90, 0
+ };
+ const unsigned char pThreeBowlG[] = {
+ 0xe2, 0x9a, 0x82, 0xe2, 0x99, 0xa8, 0xc4, 0x9e, 0
+ };
+ const unsigned char pWavesAndDomino[] = {
+ 0xe2, 0x99, 0x92, 0xf0, 0x9f, 0x81, 0xa0,
+ 0xf0, 0x9f, 0x82, 0x93, 0
+ };
+ const unsigned char pSpadesAndBits[] = {
+ 0xf0, 0x9f, 0x82, 0xa1, 0xc2, 0xa2, 0xc2, 0xa2, 0
+ };
+
+ static struct {
+ const char *mpFont;
+ const char *mpString;
+ } const aRuns[] = {
+#define SET(font,string) { font, reinterpret_cast<const char *>(string) }
+ {"sans", "a"}, // logical font - no 'sans' font.
+ {"opensymbol", "#$%"}, // font fallback - $ is missing.
+ SET("sans", pInvalid), // unicode invalid character
+ // tdf#96266 - stacking diacritics
+ SET("carlito", pDiacritic1),
+ SET("carlito", pDiacritic2),
+ SET("carlito", pDiacritic3),
+ SET("liberation sans", pDiacritic1),
+ SET("liberation sans", pDiacritic2),
+ SET("liberation sans", pDiacritic3),
+ SET("liberation sans", pDiacritic3),
+
+ // tdf#95222 - justification issue
+ // - FIXME: replicate justification
+ SET("gentium basic", pJustification),
+
+ // tdf#97319 - Unicode beyond BMP; SMP & Plane 2
+ SET("symbola", pEmojis),
+ SET("symbola", pThreeBowlG),
+ SET("symbola", pWavesAndDomino),
+ SET("symbola", pSpadesAndBits),
+ };
+
+ // Nice clean white background
+ rDev.DrawWallpaper(r, Wallpaper(COL_WHITE));
+ rDev.SetClipRegion(vcl::Region(r));
+
+ Point aPos(r.Left(), r.Top()+20);
+
+ tools::Long nMaxTextHeight = 0;
+ for (size_t i = 0; i < std::size(aRuns); ++i)
+ {
+ // Legend
+ vcl::Font aIndexFont("sans", Size(0,20));
+ aIndexFont.SetColor( COL_BLACK);
+ tools::Rectangle aTextRect;
+ rDev.SetFont(aIndexFont);
+ OUString aText = OUString::number(i) + ".";
+ rDev.DrawText(aPos, aText);
+ if (rDev.GetTextBoundRect(aTextRect, aText))
+ aPos.Move(aTextRect.GetWidth() + 8, 0);
+
+ // Text
+ FontWeight aWeights[] = { WEIGHT_NORMAL,
+ WEIGHT_BOLD,
+ WEIGHT_NORMAL };
+ FontItalic const aItalics[] = { ITALIC_NONE,
+ ITALIC_NONE,
+ ITALIC_NORMAL };
+ vcl::Font aFont(OUString::createFromAscii(
+ aRuns[i].mpFont),
+ Size(0,42));
+ aFont.SetColor(COL_BLACK);
+ for (size_t j = 0; j < std::size(aWeights); ++j)
+ {
+ aFont.SetItalic(aItalics[j]);
+ aFont.SetWeight(aWeights[j]);
+ rDev.SetFont(aFont);
+
+ OUString aString(aRuns[i].mpString,
+ strlen(aRuns[i].mpString),
+ RTL_TEXTENCODING_UTF8);
+ tools::Long nNewX = drawStringBox(rDev, aPos, aString,
+ nMaxTextHeight);
+
+ aPos.setX( nNewX );
+
+ if (aPos.X() >= r.Right())
+ {
+ aPos = Point(r.Left(), aPos.Y() + nMaxTextHeight + 15);
+ nMaxTextHeight = 0;
+ if(j>0)
+ j--; // re-render the last point.
+ }
+ if (aPos.Y() > r.Bottom())
+ break;
+ }
+ if (aPos.Y() > r.Bottom())
+ break;
+ }
+
+ rDev.SetClipRegion();
+ }
+ // render text, bbox, DX arrays etc.
+ static tools::Long drawStringBox(OutputDevice &rDev, Point aPos,
+ const OUString &aText,
+ tools::Long &nMaxTextHeight)
+ {
+ rDev.Push();
+ {
+ tools::Rectangle aTextRect;
+
+ rDev.DrawText(aPos,aText);
+
+ if (rDev.GetTextBoundRect(aTextRect, aText))
+ {
+ aTextRect.Move(aPos.X(), aPos.Y());
+ rDev.SetFillColor();
+ rDev.SetLineColor(COL_BLACK);
+ rDev.DrawRect(aTextRect);
+ if (aTextRect.GetHeight() > nMaxTextHeight)
+ nMaxTextHeight = aTextRect.GetHeight();
+ // This should intersect with the text
+ tools::Rectangle aInnerRect(
+ aTextRect.Left()+1, aTextRect.Top()+1,
+ aTextRect.Right()-1, aTextRect.Bottom()-1);
+ rDev.SetLineColor(COL_WHITE);
+ rDev.SetRasterOp(RasterOp::Xor);
+ rDev.DrawRect(aInnerRect);
+ rDev.SetRasterOp(RasterOp::OverPaint);
+ }
+
+ // DX array rendering
+ KernArray aItems;
+ rDev.GetTextArray(aText, &aItems);
+ for (tools::Long j = 0; j < aText.getLength(); ++j)
+ {
+ Point aTop = aTextRect.TopLeft();
+ Point aBottom = aTop;
+ aTop.Move(aItems[j], 0);
+ aBottom.Move(aItems[j], aTextRect.GetHeight());
+ rDev.SetLineColor(COL_RED);
+ rDev.SetRasterOp(RasterOp::Xor);
+ rDev.DrawLine(aTop,aBottom);
+ rDev.SetRasterOp(RasterOp::OverPaint);
+ }
+
+ aPos.Move(aTextRect.GetWidth() + 16, 0);
+ }
+ rDev.Pop();
+ return aPos.X();
+ }
+ };
+
+ struct DrawCheckered : public RegionRenderer
+ {
+ RENDER_DETAILS(checks,KEY_C,20)
+ virtual void RenderRegion(OutputDevice &rDev, tools::Rectangle r,
+ const RenderContext &rCtx) override
+ {
+ if (rCtx.meStyle == RENDER_EXPANDED)
+ {
+ std::vector<tools::Rectangle> aRegions(DemoRenderer::partition(rCtx, 2, 2));
+ for (size_t i = 0; i < aRegions.size(); i++)
+ {
+ vcl::Region aRegion;
+ tools::Rectangle aSub(aRegions[i]);
+ tools::Rectangle aSmaller(aSub);
+ aSmaller.Move(10,10);
+ aSmaller.setWidth(aSmaller.getOpenWidth()-20);
+ aSmaller.setHeight(aSmaller.getOpenHeight()-24);
+ switch (i) {
+ case 0:
+ aRegion = vcl::Region(aSub);
+ break;
+ case 1:
+ aRegion = vcl::Region(aSmaller);
+ aRegion.XOr(aSub);
+ break;
+ case 2:
+ {
+ tools::Polygon aPoly(aSub);
+ aPoly.Rotate(aSub.Center(), 450_deg10);
+ aPoly.Clip(aSmaller);
+ aRegion = vcl::Region(aPoly);
+ break;
+ }
+ case 3:
+ {
+ tools::PolyPolygon aPolyPoly;
+ sal_Int32 nTW = aSub.GetWidth()/6;
+ sal_Int32 nTH = aSub.GetHeight()/6;
+ tools::Rectangle aTiny(Point(4, 4), Size(nTW*2, nTH*2));
+ aPolyPoly.Insert( tools::Polygon(aTiny));
+ aTiny.Move(nTW*3, nTH*3);
+ aPolyPoly.Insert( tools::Polygon(aTiny));
+ aTiny.Move(nTW, nTH);
+ aPolyPoly.Insert( tools::Polygon(aTiny));
+
+ aRegion = vcl::Region(aPolyPoly);
+ break;
+ }
+ } // switch
+ rDev.SetClipRegion(aRegion);
+ rDev.DrawCheckered(aSub.TopLeft(), aSub.GetSize());
+ rDev.SetClipRegion();
+ }
+ }
+ else
+ {
+ rDev.DrawCheckered(r.TopLeft(), r.GetSize());
+ }
+ }
+ };
+
+ struct DrawPoly : public RegionRenderer
+ {
+ RENDER_DETAILS(poly,KEY_P,20)
+ DrawCheckered maCheckered;
+ virtual void RenderRegion(OutputDevice &rDev, tools::Rectangle r,
+ const RenderContext &rCtx) override
+ {
+ maCheckered.RenderRegion(rDev, r, rCtx);
+
+ tools::Long nDx = r.GetWidth()/20;
+ tools::Long nDy = r.GetHeight()/20;
+ tools::Rectangle aShrunk(r);
+ aShrunk.Move(nDx, nDy);
+ aShrunk.SetSize(Size(r.GetWidth()-nDx*2,
+ r.GetHeight()-nDy*2));
+ tools::Polygon aPoly(aShrunk);
+ tools::PolyPolygon aPPoly(aPoly);
+ rDev.SetLineColor(COL_RED);
+ rDev.SetFillColor(COL_RED);
+ // This hits the optional 'drawPolyPolygon' code-path
+ rDev.DrawTransparent(aPPoly, 64);
+ }
+ };
+
+ struct DrawEllipse : public RegionRenderer
+ {
+ RENDER_DETAILS(ellipse,KEY_E,500)
+ static void doInvert(OutputDevice &rDev, const tools::Rectangle &r,
+ InvertFlags nFlags)
+ {
+ rDev.Invert(r, nFlags);
+ if (r.GetWidth() > 10 && r.GetHeight() > 10)
+ {
+ tools::Rectangle aSmall(r.Center()-Point(4,4), Size(8,8));
+ rDev.Invert(aSmall,nFlags);
+ }
+ }
+ virtual void RenderRegion(OutputDevice &rDev, tools::Rectangle r,
+ const RenderContext &rCtx) override
+ {
+ rDev.SetLineColor(COL_RED);
+ rDev.SetFillColor(COL_GREEN);
+ rDev.DrawEllipse(r);
+
+ if (rCtx.meStyle == RENDER_EXPANDED)
+ {
+ auto aRegions = partition(rCtx, 2, 2);
+ doInvert(rDev, aRegions[0], InvertFlags::NONE);
+ rDev.DrawText(aRegions[0], "InvertFlags::NONE");
+ doInvert(rDev, aRegions[1], InvertFlags::N50);
+ rDev.DrawText(aRegions[1], "InvertFlags::N50");
+ doInvert(rDev, aRegions[3], InvertFlags::TrackFrame);
+ rDev.DrawText(aRegions[3], "InvertFlags::TrackFrame");
+ }
+ }
+ };
+
+ struct DrawGradient : public RegionRenderer
+ {
+ RENDER_DETAILS(gradient,KEY_G,50)
+ virtual void RenderRegion(OutputDevice &rDev, tools::Rectangle r,
+ const RenderContext &rCtx) override
+ {
+ if (rCtx.meStyle == RENDER_EXPANDED)
+ {
+ std::vector<tools::Rectangle> aRegions(DemoRenderer::partition(rCtx,5, 4));
+ static Color const nStartCols[] = {
+ COL_RED, COL_RED, COL_RED, COL_GREEN, COL_GREEN,
+ COL_BLUE, COL_BLUE, COL_BLUE, COL_CYAN, COL_CYAN,
+ COL_BLACK, COL_LIGHTGRAY, COL_WHITE, COL_BLUE, COL_CYAN,
+ COL_WHITE, COL_WHITE, COL_WHITE, COL_BLACK, COL_BLACK
+ };
+ static Color const nEndCols[] = {
+ COL_WHITE, COL_WHITE, COL_WHITE, COL_BLACK, COL_BLACK,
+ COL_RED, COL_RED, COL_RED, COL_GREEN, COL_GREEN,
+ COL_GRAY, COL_GRAY, COL_LIGHTGRAY, COL_LIGHTBLUE, COL_LIGHTCYAN,
+ COL_BLUE, COL_BLUE, COL_BLUE, COL_CYAN, COL_CYAN
+ };
+ css::awt::GradientStyle eStyles[] = {
+ css::awt::GradientStyle_LINEAR, css::awt::GradientStyle_AXIAL, css::awt::GradientStyle_RADIAL, css::awt::GradientStyle_ELLIPTICAL, css::awt::GradientStyle_SQUARE,
+ css::awt::GradientStyle_RECT, css::awt::GradientStyle::GradientStyle_MAKE_FIXED_SIZE, css::awt::GradientStyle_LINEAR, css::awt::GradientStyle_RADIAL, css::awt::GradientStyle_LINEAR,
+ css::awt::GradientStyle_LINEAR, css::awt::GradientStyle_AXIAL, css::awt::GradientStyle_RADIAL, css::awt::GradientStyle_ELLIPTICAL, css::awt::GradientStyle_SQUARE,
+ css::awt::GradientStyle_RECT, css::awt::GradientStyle::GradientStyle_MAKE_FIXED_SIZE, css::awt::GradientStyle_LINEAR, css::awt::GradientStyle_RADIAL, css::awt::GradientStyle_LINEAR
+ };
+ sal_uInt16 nAngles[] = {
+ 0, 0, 0, 0, 0,
+ 15, 30, 45, 60, 75,
+ 90, 120, 135, 160, 180,
+ 0, 0, 0, 0, 0
+ };
+ sal_uInt16 nBorders[] = {
+ 0, 0, 0, 0, 0,
+ 1, 10, 100, 10, 1,
+ 0, 0, 0, 0, 0,
+ 1, 10, 20, 10, 1,
+ 0, 0, 0, 0, 0
+ };
+ DemoRenderer::clearRects(rDev, aRegions);
+ assert(aRegions.size() <= SAL_N_ELEMENTS(nStartCols));
+ assert(aRegions.size() <= SAL_N_ELEMENTS(nEndCols));
+ assert(aRegions.size() <= SAL_N_ELEMENTS(eStyles));
+ assert(aRegions.size() <= SAL_N_ELEMENTS(nAngles));
+ assert(aRegions.size() <= SAL_N_ELEMENTS(nBorders));
+ for (size_t i = 0; i < aRegions.size(); i++)
+ {
+ tools::Rectangle aSub = aRegions[i];
+ Gradient aGradient;
+ aGradient.SetStartColor(nStartCols[i]);
+ aGradient.SetEndColor(nEndCols[i]);
+ aGradient.SetStyle(eStyles[i]);
+ aGradient.SetAngle(Degree10(nAngles[i]));
+ aGradient.SetBorder(nBorders[i]);
+ rDev.DrawGradient(aSub, aGradient);
+ }
+ }
+ else
+ {
+ Gradient aGradient;
+ aGradient.SetStartColor(COL_YELLOW);
+ aGradient.SetEndColor(COL_RED);
+ aGradient.SetStyle(css::awt::GradientStyle_RECT);
+ aGradient.SetBorder(r.GetSize().Width()/20);
+ rDev.DrawGradient(r, aGradient);
+ }
+ }
+ };
+
+ struct DrawBitmap : public RegionRenderer
+ {
+ RENDER_DETAILS(bitmap,KEY_B,10)
+
+ // Simulate Page Borders rendering - which ultimately should
+ // be done with a shader / gradient
+ static void SimulateBorderStretch(OutputDevice &rDev, const tools::Rectangle& r)
+ {
+ BitmapEx aPageShadowMask("sw/res/page-shadow-mask.png");
+
+ BitmapEx aRight(aPageShadowMask);
+ sal_Int32 nSlice = (aPageShadowMask.GetSizePixel().Width() - 3) / 4;
+ // a width x 1 slice
+ aRight.Crop(tools::Rectangle(Point((nSlice * 3) + 3, (nSlice * 2) + 1),
+ Size(nSlice, 1)));
+ AlphaMask aAlphaMask(aRight.GetBitmap());
+ Bitmap aBlockColor(aAlphaMask.GetSizePixel(), vcl::PixelFormat::N24_BPP);
+ aBlockColor.Erase(COL_RED);
+ BitmapEx aShadowStretch(aBlockColor, aAlphaMask);
+
+ Point aRenderPt(r.TopLeft());
+
+ tools::Long aSizes[] = { 200, 100, 200, 100, 50, 5, 2 };
+
+ // and yes - we really do this in the page border rendering code ...
+ for (const auto& rSize : aSizes)
+ {
+ aShadowStretch.Scale(Size(aShadowStretch.GetSizePixel().Width(), rSize),
+ BmpScaleFlag::Fast);
+
+ rDev.DrawBitmapEx(aRenderPt, aShadowStretch);
+ aRenderPt.Move(aShadowStretch.GetSizePixel().Width() + 4, 0);
+ }
+
+ AlphaMask aWholeMask(aPageShadowMask.GetBitmap());
+ aBlockColor = Bitmap(aPageShadowMask.GetSizePixel(), vcl::PixelFormat::N24_BPP);
+ aBlockColor.Erase(COL_GREEN);
+ BitmapEx aWhole(aBlockColor, aWholeMask);
+
+ aRenderPt = r.Center();
+ aRenderPt.Move(nSlice+1, 0);
+
+ // An offset background for alpha rendering
+ rDev.SetFillColor(COL_BLUE);
+ tools::Rectangle aSurround(r.Center(), aPageShadowMask.GetSizePixel());
+ rDev.DrawRect(aSurround);
+ rDev.DrawBitmapEx(aRenderPt, aWhole);
+ }
+
+ virtual void RenderRegion(OutputDevice &rDev, tools::Rectangle r,
+ const RenderContext &rCtx) override
+ {
+ Bitmap aBitmap(rCtx.mpDemoRenderer->maIntroBW);
+ aBitmap.Scale(r.GetSize(), BmpScaleFlag::BestQuality);
+ rDev.DrawBitmap(r.TopLeft(), aBitmap);
+
+ SimulateBorderStretch(rDev, r);
+ }
+ };
+
+ struct DrawBitmapEx : public RegionRenderer
+ {
+ RENDER_DETAILS(bitmapex,KEY_X,2)
+ DrawCheckered maCheckered;
+ virtual void RenderRegion(OutputDevice &rDev, tools::Rectangle r,
+ const RenderContext &rCtx) override
+ {
+ maCheckered.RenderRegion(rDev, r, rCtx);
+
+ BitmapEx aBitmap(rCtx.mpDemoRenderer->maIntro);
+ aBitmap.Scale(r.GetSize(), BmpScaleFlag::BestQuality);
+ AlphaMask aSemiTransp(aBitmap.GetSizePixel());
+ aSemiTransp.Erase(64);
+ rDev.DrawBitmapEx(r.TopLeft(), BitmapEx(aBitmap.GetBitmap(),
+ aSemiTransp));
+ }
+ };
+
+ struct DrawPolyPolygons : public RegionRenderer
+ {
+ RENDER_DETAILS(polypoly,KEY_N,100)
+ virtual void RenderRegion(OutputDevice &rDev, tools::Rectangle r,
+ const RenderContext &) override
+ {
+ static struct {
+ double nX, nY;
+ } const aPoints[] = { { 0.1, 0.1 }, { 0.9, 0.9 },
+#if FIXME_SELF_INTERSECTING_WORKING
+ { 0.9, 0.1 }, { 0.1, 0.9 },
+ { 0.1, 0.1 }
+#else
+ { 0.1, 0.9 }, { 0.5, 0.5 },
+ { 0.9, 0.1 }, { 0.1, 0.1 }
+#endif
+ };
+
+ tools::PolyPolygon aPolyPoly;
+ // Render 4x polygons & aggregate into another PolyPolygon
+ for (int x = 0; x < 2; x++)
+ {
+ for (int y = 0; y < 2; y++)
+ {
+ tools::Rectangle aSubRect(r);
+ aSubRect.Move(x * r.GetWidth()/3, y * r.GetHeight()/3);
+ aSubRect.SetSize(Size(r.GetWidth()/2, r.GetHeight()/4));
+ tools::Polygon aPoly(std::size(aPoints));
+ for (size_t v = 0; v < std::size(aPoints); v++)
+ {
+ aPoly.SetPoint(Point(aSubRect.Left() +
+ aSubRect.GetWidth() * aPoints[v].nX,
+ aSubRect.Top() +
+ aSubRect.GetHeight() * aPoints[v].nY),
+ v);
+ }
+ rDev.SetLineColor(COL_YELLOW);
+ rDev.SetFillColor(COL_BLACK);
+ rDev.DrawPolygon(aPoly);
+
+ // now move and add to the polypolygon
+ aPoly.Move(0, r.GetHeight()/2);
+ aPolyPoly.Insert(aPoly);
+ }
+ }
+ rDev.SetLineColor(COL_LIGHTRED);
+ rDev.SetFillColor(COL_GREEN);
+ rDev.DrawTransparent(aPolyPoly, 50);
+ }
+ };
+
+ struct DrawClipped : public RegionRenderer
+ {
+ RENDER_DETAILS(clip,KEY_D,10)
+ virtual void RenderRegion(OutputDevice &rDev, tools::Rectangle r,
+ const RenderContext &) override
+ {
+ std::vector<tools::Rectangle> aRegions(DemoRenderer::partition(r, 2, 2));
+ const int nLimits[] = { 4, -100 };
+ for (int i = 0; i < 2; ++i)
+ {
+ sal_uInt16 nHue = 0;
+ rDev.Push(vcl::PushFlags::CLIPREGION);
+ tools::Rectangle aOuter = aRegions[i];
+ tools::Rectangle aInner = aOuter;
+ while (aInner.GetWidth() > nLimits[i] && aInner.GetHeight() > nLimits[i])
+ {
+ aInner.expand(-1);
+ rDev.SetClipRegion(vcl::Region(aInner));
+ rDev.SetFillColor(Color::HSBtoRGB(nHue, 75, 100));
+ nHue = (nHue + 97) % 360;
+ rDev.DrawRect(aOuter);
+ }
+ rDev.Pop();
+ }
+
+ {
+ sal_uInt16 nHue = 0;
+ tools::Rectangle aOuter = aRegions[2];
+ std::vector<tools::Rectangle> aPieces(DemoRenderer::partition(aOuter, 2, 2));
+ for (int j = 0; j < std::min(aOuter.GetWidth(), aOuter.GetHeight())/5; ++j)
+ {
+ rDev.Push(vcl::PushFlags::CLIPREGION);
+
+ vcl::Region aClipRegion;
+ for (int i = 0; i < 4; ++i)
+ {
+ aPieces[i].expand(-1);
+ aPieces[i].Move(2 - i/2, 2 - i/2);
+ aClipRegion.Union(aPieces[i]);
+ }
+ assert (aClipRegion.getRegionBand());
+ rDev.SetClipRegion(aClipRegion);
+ rDev.SetFillColor(Color::HSBtoRGB(nHue, 75, 75));
+ nHue = (nHue + 97) % 360;
+ rDev.DrawRect(aOuter);
+
+ rDev.Pop();
+ }
+ }
+
+ {
+ sal_uInt16 nHue = 0;
+ tools::Rectangle aOuter = aRegions[3];
+ std::vector<tools::Rectangle> aPieces(DemoRenderer::partition(aOuter, 2, 2));
+ bool bDone = false;
+ while (!bDone)
+ {
+ rDev.Push(vcl::PushFlags::CLIPREGION);
+
+ for (int i = 0; i < 4; ++i)
+ {
+ vcl::Region aClipRegion;
+ tools::Polygon aPoly;
+ switch (i) {
+ case 3:
+ case 0: // 45degree rectangle.
+ aPoly = tools::Polygon(aPieces[i]);
+ aPoly.Rotate(aPieces[i].Center(), 450_deg10);
+ break;
+ case 1: // arc
+ aPoly = tools::Polygon(aPieces[i],
+ aPieces[i].TopLeft(),
+ aPieces[i].BottomRight());
+ break;
+ case 2:
+ aPoly = tools::Polygon(aPieces[i],
+ aPieces[i].GetWidth()/5,
+ aPieces[i].GetHeight()/5);
+ aPoly.Rotate(aPieces[i].Center(), 450_deg10);
+ break;
+ }
+ aClipRegion = vcl::Region(aPoly);
+ aPieces[i].expand(-1);
+ aPieces[i].Move(2 - i/2, 2 - i/2);
+
+ bDone = aPieces[i].GetWidth() < 4 ||
+ aPieces[i].GetHeight() < 4;
+
+ if (!bDone)
+ {
+ assert (!aClipRegion.getRegionBand());
+
+ rDev.SetClipRegion(aClipRegion);
+ rDev.SetFillColor(Color::HSBtoRGB(nHue, 50, 75));
+ nHue = (nHue + 97) % 360;
+ rDev.DrawRect(aOuter);
+ }
+ }
+
+ rDev.Pop();
+ }
+ }
+ }
+ };
+
+ struct DrawToVirtualDevice : public RegionRenderer
+ {
+ RENDER_DETAILS(vdev,KEY_V,1)
+ enum RenderType {
+ RENDER_AS_BITMAP,
+ RENDER_AS_OUTDEV,
+ RENDER_AS_BITMAPEX,
+ RENDER_AS_ALPHA_OUTDEV
+ };
+
+ static void SizeAndRender(OutputDevice &rDev, const tools::Rectangle& r, RenderType eType,
+ const RenderContext &rCtx)
+ {
+ ScopedVclPtr<VirtualDevice> pNested;
+
+ if (static_cast<int>(eType) < RENDER_AS_BITMAPEX)
+ pNested = VclPtr<VirtualDevice>::Create(rDev).get();
+ else
+ pNested = VclPtr<VirtualDevice>::Create(rDev,DeviceFormat::WITH_ALPHA).get();
+
+ pNested->SetOutputSizePixel(r.GetSize());
+ tools::Rectangle aWhole(Point(0,0), r.GetSize());
+
+ // mini me
+ rCtx.mpDemoRenderer->drawToDevice(*pNested, r.GetSize(), true);
+
+ if (eType == RENDER_AS_BITMAP)
+ {
+ Bitmap aBitmap(pNested->GetBitmap(Point(0,0),aWhole.GetSize()));
+ rDev.DrawBitmap(r.TopLeft(), aBitmap);
+ }
+ else if (eType == RENDER_AS_BITMAPEX)
+ {
+ BitmapEx aBitmapEx(pNested->GetBitmapEx(Point(0,0),aWhole.GetSize()));
+ rDev.DrawBitmapEx(r.TopLeft(), aBitmapEx);
+ }
+ else if (eType == RENDER_AS_OUTDEV ||
+ eType == RENDER_AS_ALPHA_OUTDEV)
+ {
+ rDev.DrawOutDev(r.TopLeft(), r.GetSize(),
+ aWhole.TopLeft(), aWhole.GetSize(),
+ *pNested);
+ }
+ }
+ virtual void RenderRegion(OutputDevice &rDev, tools::Rectangle r,
+ const RenderContext &rCtx) override
+ {
+ // avoid infinite recursion
+ if (rCtx.mbVDev)
+ return;
+
+ if (rCtx.meStyle == RENDER_EXPANDED)
+ {
+ std::vector<tools::Rectangle> aRegions(DemoRenderer::partition(rCtx,2, 2));
+ DemoRenderer::clearRects(rDev, aRegions);
+
+ RenderType const eRenderTypes[] { RENDER_AS_BITMAP, RENDER_AS_OUTDEV,
+ RENDER_AS_BITMAPEX, RENDER_AS_ALPHA_OUTDEV };
+ for (size_t i = 0; i < aRegions.size(); i++)
+ SizeAndRender(rDev, aRegions[i], eRenderTypes[i], rCtx);
+ }
+ else
+ SizeAndRender(rDev, r, RENDER_AS_BITMAP, rCtx);
+ }
+ };
+
+ struct DrawXOR : public RegionRenderer
+ {
+ RENDER_DETAILS(xor,KEY_X,1)
+
+ virtual void RenderRegion(OutputDevice &rDev, tools::Rectangle r,
+ const RenderContext &rCtx) override
+ {
+ // avoid infinite recursion
+ if (rCtx.mbVDev)
+ return;
+
+ rDev.Push();
+
+ AntialiasingFlags nFlags = rDev.GetAntialiasing();
+ rDev.SetAntialiasing(nFlags & ~AntialiasingFlags::Enable);
+ rDev.SetRasterOp( RasterOp::Xor );
+
+ rCtx.mpDemoRenderer->drawThumbs(rDev, r, true);
+
+ rDev.Pop();
+ }
+ };
+
+ struct DrawIcons : public RegionRenderer
+ {
+ RENDER_DETAILS(icons,KEY_I,1)
+
+ std::vector<OUString> maIconNames;
+ std::vector<BitmapEx> maIcons;
+ bool bHasLoadedAll;
+ DrawIcons() : bHasLoadedAll(false)
+ {
+ // a few icons to start with
+ const char *pNames[] = {
+ "cmd/lc_openurl.png",
+ "cmd/lc_newdoc.png",
+ "cmd/lc_choosemacro.png",
+ "cmd/lc_save.png",
+ "cmd/lc_saveas.png",
+ "cmd/lc_importdialog.png",
+ "cmd/lc_sendmail.png",
+ "cmd/lc_editdoc.png",
+ "cmd/lc_print.png",
+ "cmd/lc_combobox.png",
+ "cmd/lc_insertformcombo.png",
+ "cmd/lc_printpreview.png",
+ "cmd/lc_cut.png",
+ "cmd/lc_copy.png",
+ "cmd/lc_paste.png",
+ "cmd/sc_autopilotmenu.png",
+ "cmd/lc_formatpaintbrush.png",
+ "cmd/lc_undo.png",
+ "cmd/lc_redo.png",
+ "cmd/lc_marks.png",
+ "cmd/lc_fieldnames.png",
+ "cmd/lc_hyperlinkdialog.png",
+ "cmd/lc_basicshapes.rectangle.png",
+ "cmd/lc_basicshapes.round-rectangle.png"
+ };
+ for (size_t i = 0; i < SAL_N_ELEMENTS(pNames); i++)
+ {
+ maIconNames.push_back(OUString::createFromAscii(pNames[i]));
+ maIcons.emplace_back(maIconNames[i]);
+ }
+ }
+
+ void LoadAllImages()
+ {
+ if (bHasLoadedAll)
+ return;
+ bHasLoadedAll = true;
+
+ css::uno::Reference<css::container::XNameAccess> xRef(ImageTree::get().getNameAccess());
+ const css::uno::Sequence< OUString > aAllIcons = xRef->getElementNames();
+
+ for (const auto& rIcon : aAllIcons)
+ {
+ if (rIcon.endsWithIgnoreAsciiCase("svg"))
+ continue; // too slow to load.
+ maIconNames.push_back(rIcon);
+ maIcons.emplace_back(rIcon);
+ }
+ }
+
+ void doDrawIcons(OutputDevice &rDev, tools::Rectangle r, bool bExpanded)
+ {
+ tools::Long nMaxH = 0;
+ Point p(r.LeftCenter());
+ size_t nToRender = maIcons.size();
+
+ if (!bExpanded && maIcons.size() > 64)
+ nToRender = 64;
+ for (size_t i = 0; i < nToRender; i++)
+ {
+ Size aSize(maIcons[i].GetSizePixel());
+// sAL_DEBUG("Draw icon '" << maIconNames[i] << "'");
+
+ if (!(i % 4))
+ rDev.DrawBitmapEx(p, maIcons[i]);
+ else
+ {
+ basegfx::B2DHomMatrix aTransform;
+ aTransform.scale(aSize.Width(), aSize.Height());
+ switch (i % 4)
+ {
+ case 2:
+ aTransform.shearX(static_cast<double>((i >> 2) % 8) / 8);
+ aTransform.shearY(static_cast<double>((i >> 4) % 8) / 8);
+ break;
+ case 3:
+ aTransform.translate(-aSize.Width()/2, -aSize.Height()/2);
+ aTransform.rotate(i);
+ if (i & 0x100)
+ {
+ aTransform.shearX(static_cast<double>((i >> 2) % 8) / 8);
+ aTransform.shearY(static_cast<double>((i >> 4) % 8) / 8);
+ }
+ aTransform.translate(aSize.Width()/2, aSize.Height()/2);
+ break;
+ default:
+ aTransform.translate(-aSize.Width()/2, -aSize.Height()/2);
+ aTransform.rotate(2 * 2 * M_PI * i / nToRender);
+ aTransform.translate(aSize.Width()/2, aSize.Height()/2);
+ break;
+ }
+ aTransform.translate(p.X(), p.Y());
+ rDev.DrawTransformedBitmapEx(aTransform, maIcons[i]);
+ }
+
+ // next position
+ p.Move(aSize.Width(), 0);
+ if (aSize.Height() > nMaxH)
+ nMaxH = aSize.Height();
+ if (p.X() >= r.Right()) // wrap to next line
+ {
+ p = Point(r.Left(), p.Y() + nMaxH);
+ nMaxH = 0;
+ }
+ if (p.Y() >= r.Bottom()) // re-start at middle
+ p = r.LeftCenter();
+ }
+ }
+
+ static BitmapEx AlphaRecovery(OutputDevice &rDev, Point aPt, BitmapEx const &aSrc)
+ {
+ // Compositing onto 2x colors beyond our control
+ ScopedVclPtrInstance< VirtualDevice > aWhite;
+ ScopedVclPtrInstance< VirtualDevice > aBlack;
+ aWhite->SetOutputSizePixel(aSrc.GetSizePixel());
+ aWhite->SetBackground(Wallpaper(COL_WHITE));
+ aWhite->Erase();
+ aBlack->SetOutputSizePixel(aSrc.GetSizePixel());
+ aBlack->SetBackground(Wallpaper(COL_BLACK));
+ aBlack->Erase();
+ aWhite->DrawBitmapEx(Point(), aSrc);
+ aBlack->DrawBitmapEx(Point(), aSrc);
+
+ // Now recover that alpha...
+ Bitmap aWhiteBmp = aWhite->GetBitmap(Point(),aSrc.GetSizePixel());
+ Bitmap aBlackBmp = aBlack->GetBitmap(Point(),aSrc.GetSizePixel());
+ AlphaMask aMask(aSrc.GetSizePixel());
+ Bitmap aRecovered(aSrc.GetSizePixel(), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pMaskAcc(aMask);
+ BitmapScopedWriteAccess pRecAcc(aRecovered);
+ BitmapScopedReadAccess pAccW(aWhiteBmp); // a * pix + (1-a)
+ BitmapScopedReadAccess pAccB(aBlackBmp); // a * pix + 0
+ int nSizeX = aSrc.GetSizePixel().Width();
+ int nSizeY = aSrc.GetSizePixel().Height();
+ for (int y = 0; y < nSizeY; y++)
+ {
+ Scanline pScanlineMask = pMaskAcc->GetScanline( y );
+ Scanline pScanlineRec = pRecAcc->GetScanline( y );
+ Scanline pScanlineW = pAccW->GetScanline( y );
+ Scanline pScanlineB = pAccB->GetScanline( y );
+ for (int x = 0; x < nSizeX; x++)
+ {
+ BitmapColor aColW = pAccW->GetPixelFromData(pScanlineW,x);
+ BitmapColor aColB = pAccB->GetPixelFromData(pScanlineB,x);
+ tools::Long nAR = static_cast<tools::Long>(aColW.GetRed() - aColB.GetRed()); // (1-a)
+ tools::Long nAG = static_cast<tools::Long>(aColW.GetGreen() - aColB.GetGreen()); // (1-a)
+ tools::Long nAB = static_cast<tools::Long>(aColW.GetBlue() - aColB.GetBlue()); // (1-a)
+
+#define CLAMP(a,b,c) (((a)<=(b))?(b):(((a)>=(c))?(c):(a)))
+
+ // we get the most precision from the largest delta
+ tools::Long nInverseAlpha = std::max(nAR, std::max(nAG, nAB)); // (1-a)
+ nInverseAlpha = CLAMP(nInverseAlpha, 0, 255);
+ tools::Long nAlpha = 255 - nInverseAlpha;
+
+ pMaskAcc->SetPixelOnData(pScanlineMask,x,BitmapColor(static_cast<sal_Int8>(CLAMP(nInverseAlpha,0,255))));
+ // now recover the pixels
+ tools::Long nR = (aColW.GetRed() + aColB.GetRed() - nInverseAlpha) * 128;
+ tools::Long nG = (aColW.GetGreen() + aColB.GetGreen() - nInverseAlpha) * 128;
+ tools::Long nB = (aColW.GetBlue() + aColB.GetBlue() - nInverseAlpha) * 128;
+ if (nAlpha == 0)
+ { // doesn't matter what's behind transparency
+ nR = nG = nB = 0;
+ }
+ else
+ {
+ nR /= nAlpha; nG /= nAlpha; nB /= nAlpha;
+ }
+ pRecAcc->SetPixelOnData(pScanlineRec,x,BitmapColor(
+ static_cast<sal_uInt8>(CLAMP(nR,0,255)),
+ static_cast<sal_uInt8>(CLAMP(nG,0,255)),
+ static_cast<sal_uInt8>(CLAMP(nB,0,255))));
+#undef CLAMP
+ }
+ }
+ }
+ rDev.DrawBitmap(aPt, aWhiteBmp);
+ aPt.Move(aSrc.GetSizePixel().Width(), 0);
+ rDev.DrawBitmap(aPt, aBlackBmp);
+ aPt.Move(aSrc.GetSizePixel().Width(), 0);
+ rDev.DrawBitmap(aPt, aRecovered);
+ aPt.Move(aSrc.GetSizePixel().Width(), 0);
+ rDev.DrawBitmap(aPt, aMask.GetBitmap());
+ aPt.Move(aSrc.GetSizePixel().Width(), 0);
+
+ return BitmapEx(aRecovered, aMask);
+ }
+
+ virtual void RenderRegion(OutputDevice &rDev, tools::Rectangle r,
+ const RenderContext &rCtx) override
+ {
+ if (rCtx.meStyle == RENDER_EXPANDED)
+ {
+ LoadAllImages();
+
+ Point aLocation(0,maIcons[0].GetSizePixel().Height() + 8);
+ for (size_t i = 0; i < maIcons.size(); i++)
+ {
+ BitmapEx aSrc = maIcons[i];
+
+ // original above
+ Point aAbove(aLocation);
+ aAbove.Move(0,-aSrc.GetSizePixel().Height() - 4);
+ rDev.DrawBitmapEx(aAbove, aSrc);
+ aAbove.Move(aSrc.GetSizePixel().Width(),0);
+ aAbove.Move(aSrc.GetSizePixel().Width(),0);
+ rDev.DrawBitmap(aAbove, aSrc.GetBitmap());
+ aAbove.Move(aSrc.GetSizePixel().Width(),0);
+ rDev.DrawBitmap(aAbove, aSrc.GetAlphaMask().GetBitmap());
+
+ // intermediates middle
+ BitmapEx aResult = AlphaRecovery(rDev, aLocation, aSrc);
+
+ // result below
+ Point aBelow(aLocation);
+ aBelow.Move(0,aResult.GetSizePixel().Height());
+ rDev.DrawBitmapEx(aBelow, aResult);
+
+ // mini convert test.
+ aBelow.Move(aResult.GetSizePixel().Width()+4,0);
+ rDev.DrawBitmapEx(aBelow, aResult);
+
+ Bitmap aGrey = aSrc.GetBitmap();
+ aGrey.Convert(BmpConversion::N8BitGreys);
+ rDev.DrawBitmap(aBelow, aGrey);
+
+ aBelow.Move(aGrey.GetSizePixel().Width(),0);
+ BitmapEx aGreyMask(aSrc);
+ rDev.DrawBitmapEx(aBelow, aGreyMask);
+
+ aLocation.Move(aSrc.GetSizePixel().Width()*6,0);
+ if (aLocation.X() > r.Right())
+ aLocation = Point(0,aLocation.Y()+aSrc.GetSizePixel().Height()*3+4);
+ }
+
+ // now go crazy with random foo
+ doDrawIcons(rDev, r, true);
+ }
+ else
+ {
+ doDrawIcons(rDev, r, false);
+ }
+ }
+ };
+
+ struct FetchDrawBitmap : public RegionRenderer
+ {
+ RENDER_DETAILS(fetchdraw,KEY_F,50)
+ virtual void RenderRegion(OutputDevice &rDev, tools::Rectangle r,
+ const RenderContext &) override
+ {
+ Bitmap aBitmap(rDev.GetBitmap(Point(0,0),rDev.GetOutputSizePixel()));
+ aBitmap.Scale(r.GetSize(), BmpScaleFlag::BestQuality);
+ rDev.DrawBitmap(r.TopLeft(), aBitmap);
+ }
+ };
+
+ void drawThumbs(vcl::RenderContext& rDev, tools::Rectangle aRect, bool bVDev)
+ {
+ RenderContext aCtx;
+ aCtx.meStyle = RENDER_THUMB;
+ aCtx.mbVDev = bVDev;
+ aCtx.mpDemoRenderer = this;
+ aCtx.maSize = aRect.GetSize();
+ std::vector<tools::Rectangle> aRegions(partition(aRect, mnSegmentsX, mnSegmentsY));
+ DemoRenderer::clearRects(rDev, aRegions);
+ for (size_t i = 0; i < maRenderers.size(); i++)
+ {
+ RegionRenderer * r = maRenderers[i];
+
+ rDev.SetClipRegion( vcl::Region( aRegions[i] ) );
+
+ // profiling?
+ if (getIterCount() > 0)
+ {
+ if (!bVDev)
+ {
+ double nStartTime = getTimeNow();
+ for (int j = 0; j < r->getTestRepeatCount() * THUMB_REPEAT_FACTOR; j++)
+ r->RenderRegion(rDev, aRegions[i], aCtx);
+ addTime(i, (getTimeNow() - nStartTime) / THUMB_REPEAT_FACTOR);
+ } else
+ for (int j = 0; j < r->getTestRepeatCount(); j++)
+ r->RenderRegion(rDev, aRegions[i], aCtx);
+ }
+ else
+ r->RenderRegion(rDev, aRegions[i], aCtx);
+
+ rDev.SetClipRegion();
+ }
+ }
+
+ void drawToDevice(vcl::RenderContext& rDev, Size aSize, bool bVDev)
+ {
+ RenderContext aCtx;
+ aCtx.mbVDev = bVDev;
+ aCtx.mpDemoRenderer = this;
+ aCtx.maSize = aSize;
+ tools::Rectangle aWholeWin(Point(0,0), rDev.GetOutputSizePixel());
+
+ drawBackground(rDev, aWholeWin);
+
+ if (!bVDev /* want everything in the vdev */ &&
+ mnSelectedRenderer >= 0 &&
+ o3tl::make_unsigned(mnSelectedRenderer) < maRenderers.size())
+ {
+ aCtx.meStyle = RENDER_EXPANDED;
+ RegionRenderer * r = maRenderers[mnSelectedRenderer];
+ // profiling?
+ if (getIterCount() > 0)
+ {
+ double nStartTime = getTimeNow();
+ for (int i = 0; i < r->getTestRepeatCount(); i++)
+ r->RenderRegion(rDev, aWholeWin, aCtx);
+ addTime(mnSelectedRenderer, getTimeNow() - nStartTime);
+ } else
+ r->RenderRegion(rDev, aWholeWin, aCtx);
+ }
+ else
+ drawThumbs(rDev, aWholeWin, bVDev);
+ }
+ std::vector<VclPtr<vcl::Window> > maInvalidates;
+ void addInvalidate(vcl::Window *pWindow) { maInvalidates.emplace_back(pWindow); };
+ void removeInvalidate(vcl::Window *pWindow)
+ {
+ auto aIt = std::find(maInvalidates.begin(), maInvalidates.end(), pWindow);
+ if (aIt != maInvalidates.end())
+ maInvalidates.erase(aIt);
+ }
+ void Invalidate()
+ {
+ for (auto const& invalidate : maInvalidates)
+ invalidate->Invalidate();
+ }
+};
+
+}
+
+#if FIXME_BOUNCE_BUTTON
+IMPL_LINK_NOARG(DemoRenderer,BounceTimerCb,Timer*,void)
+{
+ mpButton->Check(mnBounceX>0);
+ mpButton->SetPressed(mnBounceY>0);
+
+ Point aCur = mpButtonWin->GetPosPixel();
+ static const int nMovePix = 10;
+ aCur.Move(mnBounceX * nMovePix, mnBounceX * nMovePix);
+ Size aWinSize = GetSizePixel();
+ if (aCur.X() <= 0 || aCur.X() >= aWinSize.Width())
+ mnBounceX *= -1;
+ if (aCur.Y() <= 0 || aCur.Y() >= aWinSize.Height())
+ mnBounceX *= -1;
+ mpButtonWin->SetPosPixel(aCur);
+
+ // All smoke and mirrors to test sub-region invalidation underneath
+ Rectangle aRect(aCur, mpButtonWin->GetSizePixel());
+ Invalidate(aRect);
+}
+#endif
+
+void DemoRenderer::KeyInput(const KeyEvent &rKEvt)
+{
+ sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
+
+ // click to zoom out
+ if (mnSelectedRenderer >= 0)
+ {
+ if (nCode == KEY_ESCAPE || nCode == KEY_BACKSPACE)
+ {
+ mnSelectedRenderer = -1;
+ Invalidate();
+ return;
+ }
+ }
+ else
+ {
+ for (size_t i = 0; i < maRenderers.size(); i++)
+ {
+ if (nCode == maRenderers[i]->getAccelerator())
+ {
+ mnSelectedRenderer = i;
+ Invalidate();
+ return;
+ }
+ }
+ }
+}
+
+bool DemoRenderer::MouseButtonDown(const MouseEvent& rMEvt)
+{
+ // click to zoom out
+ if (mnSelectedRenderer >= 0)
+ {
+ mnSelectedRenderer = -1;
+ Invalidate();
+ return true;
+ }
+
+ // click on a region to zoom into it
+ std::vector<tools::Rectangle> aRegions(partition(GetSizePixel(), mnSegmentsX, mnSegmentsY));
+ for (size_t i = 0; i < aRegions.size(); i++)
+ {
+ if (aRegions[i].Contains(rMEvt.GetPosPixel()))
+ {
+ mnSelectedRenderer = i;
+ Invalidate();
+ return true;
+ }
+ }
+
+#if FIXME_BOUNCE_BUTTON
+ // otherwise bounce floating windows
+ if (!mpButton)
+ {
+ mpButtonWin = VclPtr<FloatingWindow>::Create(this);
+ mpButton = VclPtr<PushButton>::Create(mpButtonWin);
+ mpButton->SetSymbol(SymbolType::HELP);
+ mpButton->SetText("PushButton demo");
+ mpButton->SetPosSizePixel(Point(0,0), mpButton->GetOptimalSize());
+ mpButton->Show();
+ mpButtonWin->SetPosSizePixel(Point(0,0), mpButton->GetOptimalSize());
+ mpButtonWin->Show();
+ mnBounceX = 1; mnBounceX = 1;
+ maBounce.SetInvokeHandler(LINK(this,DemoRenderer,BounceTimerCb));
+ maBounce.SetTimeout(55);
+ maBounce.Start();
+ }
+ else
+ {
+ maBounce.Stop();
+ delete mpButtonWin;
+ mpButtonWin = NULL;
+ mpButton = NULL;
+ }
+#endif
+ return false;
+}
+
+void DemoRenderer::InitRenderers()
+{
+ maRenderers.push_back(new DrawLines);
+ maRenderers.push_back(new DrawText);
+ maRenderers.push_back(new DrawPoly);
+ maRenderers.push_back(new DrawEllipse);
+ maRenderers.push_back(new DrawCheckered);
+ maRenderers.push_back(new DrawBitmapEx);
+ maRenderers.push_back(new DrawBitmap);
+ maRenderers.push_back(new DrawGradient);
+ maRenderers.push_back(new DrawPolyPolygons);
+ maRenderers.push_back(new DrawClipped);
+ maRenderers.push_back(new DrawToVirtualDevice);
+ maRenderers.push_back(new DrawXOR);
+ maRenderers.push_back(new DrawIcons());
+ maRenderers.push_back(new FetchDrawBitmap);
+}
+
+OUString DemoRenderer::getRendererList()
+{
+ OUStringBuffer aBuf;
+ for (size_t i = 0; i < maRenderers.size(); i++)
+ {
+ aBuf.append(maRenderers[i]->getName());
+ aBuf.append(' ');
+ }
+ return aBuf.makeStringAndClear();
+}
+
+double DemoRenderer::getAndResetBenchmark(const RenderStyle style)
+{
+ double geomean = 1.0;
+ fprintf(stderr, "Rendering: %s, Times (ms):\n", style == RENDER_THUMB ? "THUMB": "EXPANDED");
+ for (size_t i = 0; i < maRenderers.size(); i++)
+ {
+ double avgtime = maRenderers[i]->sumTime / maRenderers[i]->countTime;
+ geomean *= avgtime;
+ fprintf(stderr, "%s: %f (iteration: %d*%d*%d)\n",
+ OUStringToOString(maRenderers[i]->getName(),
+ RTL_TEXTENCODING_UTF8).getStr(), avgtime,
+ maRenderers[i]->countTime, maRenderers[i]->getTestRepeatCount(),
+ (style == RENDER_THUMB) ? THUMB_REPEAT_FACTOR : 1);
+ maRenderers[i]->sumTime = 0;
+ maRenderers[i]->countTime = 0;
+ }
+ geomean = pow(geomean, 1.0/maRenderers.size());
+ fprintf(stderr, "GEOMEAN_%s: %f\n", style == RENDER_THUMB ? "THUMB": "EXPANDED", geomean);
+ return geomean;
+}
+
+void DemoRenderer::setIterCount(sal_Int32 i)
+{
+ iterCount = i;
+}
+
+sal_Int32 DemoRenderer::getIterCount() const
+{
+ return iterCount;
+}
+
+void DemoRenderer::addTime(int i, double t)
+{
+ maRenderers[i]->sumTime += t / maRenderers[i]->getTestRepeatCount();
+ maRenderers[i]->countTime++;
+}
+
+void DemoRenderer::selectRenderer(std::u16string_view rName )
+{
+ for (size_t i = 0; i < maRenderers.size(); i++)
+ {
+ if (maRenderers[i]->getName() == rName)
+ {
+ mnSelectedRenderer = i;
+ Invalidate();
+ return;
+ }
+ }
+}
+
+int DemoRenderer::selectNextRenderer()
+{
+ mnSelectedRenderer++;
+ if (mnSelectedRenderer == static_cast<signed>(maRenderers.size()))
+ mnSelectedRenderer = -1;
+ Invalidate();
+ return mnSelectedRenderer;
+}
+
+namespace {
+
+class DemoWin : public WorkWindow
+{
+ DemoRenderer &mrRenderer;
+ bool underTesting;
+ bool testThreads;
+
+ class RenderThread final : public salhelper::Thread {
+ DemoWin &mrWin;
+ sal_uInt32 const mnDelaySecs = 0;
+ public:
+ RenderThread(DemoWin &rWin, sal_uInt32 nDelaySecs)
+ : Thread("vcldemo render thread")
+ , mrWin(rWin)
+ , mnDelaySecs(nDelaySecs)
+ {
+ launch();
+ }
+ virtual ~RenderThread() override
+ {
+ join();
+ }
+ virtual void execute() override
+ {
+ std::this_thread::sleep_for(std::chrono::seconds(mnDelaySecs));
+
+ SolarMutexGuard aGuard;
+ fprintf (stderr, "render from a different thread\n");
+ mrWin.Invalidate();
+ }
+ };
+ rtl::Reference<RenderThread> mxThread;
+
+public:
+ DemoWin(DemoRenderer &rRenderer, bool bThreads) :
+ WorkWindow(nullptr, WB_APP | WB_STDWORK),
+ mrRenderer(rRenderer),
+ testThreads(bThreads)
+ {
+ mrRenderer.addInvalidate(this);
+ underTesting = false;
+ }
+ virtual ~DemoWin() override
+ {
+ disposeOnce();
+ }
+ virtual void dispose() override
+ {
+ mxThread.clear();
+ mrRenderer.removeInvalidate(this);
+ WorkWindow::dispose();
+ }
+ virtual void MouseButtonDown(const MouseEvent& rMEvt) override
+ {
+ mrRenderer.SetSizePixel(GetSizePixel());
+ if (mrRenderer.MouseButtonDown(rMEvt))
+ return;
+
+ if (testThreads)
+ { // render this window asynchronously in a new thread
+ sal_uInt32 nDelaySecs = 0;
+ if (rMEvt.GetButtons() & MOUSE_RIGHT)
+ nDelaySecs = 5;
+ mxThread = new RenderThread(*this, nDelaySecs);
+ }
+ else
+ { // spawn another window
+ VclPtrInstance<DemoWin> pNewWin(mrRenderer, testThreads);
+ pNewWin->SetText("Another interactive VCL demo window");
+ pNewWin->Show();
+ }
+ }
+ virtual void KeyInput(const KeyEvent& rKEvt) override
+ {
+ mrRenderer.SetSizePixel(GetSizePixel());
+ mrRenderer.KeyInput(rKEvt);
+ }
+ virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override
+ {
+ mrRenderer.SetSizePixel(GetSizePixel());
+ fprintf(stderr, "DemoWin::Paint(%" SAL_PRIdINT64 ",%" SAL_PRIdINT64 ",%" SAL_PRIdINT64 ",%" SAL_PRIdINT64 ")\n", sal_Int64(rRect.Left()), sal_Int64(rRect.Top()), sal_Int64(rRect.getOpenWidth()), sal_Int64(rRect.getOpenHeight()));
+ if (mrRenderer.getIterCount() == 0)
+ mrRenderer.drawToDevice(rRenderContext, GetSizePixel(), false);
+ else
+ TestAndQuit(rRenderContext);
+ }
+
+ void TestAndQuit(vcl::RenderContext& rRenderContext)
+ {
+ if (underTesting)
+ return;
+ underTesting = true;
+ for (sal_Int32 i = 0; i < mrRenderer.getIterCount(); i++)
+ {
+ while (mrRenderer.selectNextRenderer() > -1)
+ {
+ mrRenderer.drawToDevice(rRenderContext, GetSizePixel(), false);
+ }
+ }
+
+ double expandedGEOMEAN = mrRenderer.getAndResetBenchmark(RENDER_EXPANDED);
+
+ for (sal_Int32 i = 0; i < mrRenderer.getIterCount(); i++)
+ mrRenderer.drawToDevice(rRenderContext, GetSizePixel(), false);
+
+ double thumbGEOMEAN = mrRenderer.getAndResetBenchmark(RENDER_THUMB);
+
+ fprintf(stderr, "GEOMEAN_TOTAL: %f\n", pow(thumbGEOMEAN * expandedGEOMEAN, 0.5));
+ Application::Quit();
+ }
+};
+
+struct PointerData {
+ PointerStyle eStyle;
+ const char * name;
+};
+
+}
+
+const PointerData gvPointerData [] = {
+ { PointerStyle::Null, "Null" },
+ { PointerStyle::Magnify, "Magnify" },
+ { PointerStyle::Fill, "Fill" },
+ { PointerStyle::MoveData, "MoveData" },
+ { PointerStyle::CopyData, "CopyData" },
+ { PointerStyle::MoveFile, "MoveFile" },
+ { PointerStyle::CopyFile, "CopyFile" },
+ { PointerStyle::MoveFiles, "MoveFiles" },
+ { PointerStyle::CopyFiles, "CopyFiles" },
+ { PointerStyle::NotAllowed, "NotAllowed" },
+ { PointerStyle::Rotate, "Rotate" },
+ { PointerStyle::HShear, "HShear" },
+ { PointerStyle::VShear, "VShear" },
+ { PointerStyle::DrawLine, "DrawLine" },
+ { PointerStyle::DrawRect, "DrawRect" },
+ { PointerStyle::DrawPolygon, "DrawPolygon" },
+ { PointerStyle::DrawBezier, "DrawBezier" },
+ { PointerStyle::DrawArc, "DrawArc" },
+ { PointerStyle::DrawPie, "DrawPie" },
+ { PointerStyle::DrawCircleCut, "DrawCircleCut" },
+ { PointerStyle::DrawEllipse, "DrawEllipse" },
+ { PointerStyle::DrawConnect, "DrawConnect" },
+ { PointerStyle::DrawText, "DrawText" },
+ { PointerStyle::Mirror, "Mirror" },
+ { PointerStyle::Crook, "Crook" },
+ { PointerStyle::Crop, "Crop" },
+ { PointerStyle::MovePoint, "MovePoint" },
+ { PointerStyle::MoveBezierWeight, "MoveBezierWeight" },
+ { PointerStyle::DrawFreehand, "DrawFreehand" },
+ { PointerStyle::DrawCaption, "DrawCaption" },
+ { PointerStyle::LinkData, "LinkData" },
+ { PointerStyle::MoveDataLink, "MoveDataLink" },
+ { PointerStyle::CopyDataLink, "CopyDataLink" },
+ { PointerStyle::LinkFile, "LinkFile" },
+ { PointerStyle::MoveFileLink, "MoveFileLink" },
+ { PointerStyle::CopyFileLink, "CopyFileLink" },
+ { PointerStyle::Chart, "Chart" },
+ { PointerStyle::Detective, "Detective" },
+ { PointerStyle::PivotCol, "PivotCol" },
+ { PointerStyle::PivotRow, "PivotRow" },
+ { PointerStyle::PivotField, "PivotField" },
+ { PointerStyle::PivotDelete, "PivotDelete" },
+ { PointerStyle::Chain, "Chain" },
+ { PointerStyle::ChainNotAllowed, "ChainNotAllowed" },
+ { PointerStyle::AutoScrollN, "AutoScrollN" },
+ { PointerStyle::AutoScrollS, "AutoScrollS" },
+ { PointerStyle::AutoScrollW, "AutoScrollW" },
+ { PointerStyle::AutoScrollE, "AutoScrollE" },
+ { PointerStyle::AutoScrollNW, "AutoScrollNW" },
+ { PointerStyle::AutoScrollNE, "AutoScrollNE" },
+ { PointerStyle::AutoScrollSW, "AutoScrollSW" },
+ { PointerStyle::AutoScrollSE, "AutoScrollSE" },
+ { PointerStyle::AutoScrollNS, "AutoScrollNS" },
+ { PointerStyle::AutoScrollWE, "AutoScrollWE" },
+ { PointerStyle::AutoScrollNSWE, "AutoScrollNSWE" },
+ { PointerStyle::TextVertical, "TextVertical" },
+ { PointerStyle::TabSelectS, "TabSelectS" },
+ { PointerStyle::TabSelectE, "TabSelectE" },
+ { PointerStyle::TabSelectSE, "TabSelectSE" },
+ { PointerStyle::TabSelectW, "TabSelectW" },
+ { PointerStyle::TabSelectSW, "TabSelectSW" },
+ { PointerStyle::HideWhitespace, "HideWhitespace" },
+ { PointerStyle::ShowWhitespace, "ShowWhitespace" },
+ { PointerStyle::FatCross, "FatCross" },
+};
+
+namespace {
+
+class DemoWidgets : public WorkWindow
+{
+ VclPtr<MenuBar> mpBar;
+ VclPtr<VclBox> mpBox;
+ VclPtr<ToolBox> mpToolbox;
+ VclPtr<PushButton> mpButton;
+ std::vector<VclPtr<VclHBox>> mvCursorBoxes;
+ std::vector<VclPtr<PushButton>> mvCursorButtons;
+
+ DECL_LINK(CursorButtonClick, Button*, void);
+
+public:
+ DemoWidgets() :
+ WorkWindow(nullptr, WB_APP | WB_STDWORK),
+ mpBox(VclPtrInstance<VclVBox>(this, false, 3)),
+ mpToolbox(VclPtrInstance<ToolBox>(mpBox.get())),
+ mpButton(VclPtrInstance<PushButton>(mpBox.get()))
+ {
+ SetText("VCL widget demo");
+
+ Wallpaper aWallpaper(BitmapEx("sfx2/res/128x128_writer_doc-p.png"));
+ aWallpaper.SetStyle(WallpaperStyle::BottomRight);
+ aWallpaper.SetColor(COL_RED);
+
+ mpBox->SetBackground(aWallpaper);
+ mpBox->Show();
+
+ Help::EnableBalloonHelp();
+ mpToolbox->SetHelpText("Help text");
+ mpToolbox->InsertItem(ToolBoxItemId(0), "Toolbar item", OUString());
+ mpToolbox->SetQuickHelpText(ToolBoxItemId(0), "This is a tooltip popup");
+ mpToolbox->InsertSeparator();
+ mpToolbox->Show();
+
+ mpButton->SetText("Click me; go on");
+ mpButton->Show();
+
+ int i = 0;
+ VclHBox* pCurrentCursorHBox = nullptr;
+ constexpr int numButtonsPerRow = 9;
+ for (auto & rData : gvPointerData)
+ {
+ if (i % numButtonsPerRow == 0)
+ {
+ mvCursorBoxes.push_back(VclPtrInstance<VclHBox>(mpBox.get(), true, numButtonsPerRow));
+ pCurrentCursorHBox = mvCursorBoxes.back().get();
+ pCurrentCursorHBox->Show();
+ }
+ mvCursorButtons.emplace_back(VclPtrInstance<PushButton>(pCurrentCursorHBox));
+ PushButton& rButton = *mvCursorButtons.back();
+ rButton.SetText(OUString::createFromAscii(rData.name));
+ rButton.SetClickHdl(LINK(this,DemoWidgets,CursorButtonClick));
+ rButton.Show();
+ ++i;
+ }
+
+ mpBar = VclPtr<MenuBar>::Create();
+ mpBar->InsertItem(0,"File");
+ VclPtrInstance<PopupMenu> pPopup;
+ pPopup->InsertItem(0,"Item");
+ mpBar->SetPopupMenu(0, pPopup);
+ SetMenuBar(mpBar);
+
+ Show();
+ }
+ virtual ~DemoWidgets() override { disposeOnce(); }
+ virtual void dispose() override
+ {
+ for (auto & p : mvCursorButtons)
+ p.disposeAndClear();
+ mvCursorButtons.clear();
+ for (auto & p : mvCursorBoxes)
+ p.disposeAndClear();
+ mvCursorBoxes.clear();
+ mpToolbox.disposeAndClear();
+ mpButton.disposeAndClear();
+ mpBox.disposeAndClear();
+ mpBar.disposeAndClear();
+ WorkWindow::dispose();
+ }
+ virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) override
+ {
+ tools::Rectangle aWholeSize(Point(0, 0),GetOutputSizePixel());
+ vcl::Region aClip(aWholeSize);
+ tools::Rectangle aExclude(tools::Rectangle(Point(50,50),Size(100,100)));
+ aClip.Exclude(aExclude);
+
+ Wallpaper aWallpaper(COL_GREEN);
+
+ rRenderContext.Push(vcl::PushFlags::CLIPREGION);
+ rRenderContext.IntersectClipRegion(aClip);
+ rRenderContext.DrawWallpaper(aWholeSize, aWallpaper);
+ rRenderContext.Pop();
+
+ ScopedVclPtrInstance< VirtualDevice > pDev(*GetOutDev());
+ pDev->EnableRTL(IsRTLEnabled());
+ pDev->SetOutputSizePixel(aExclude.GetSize());
+
+ tools::Rectangle aSubRect(aWholeSize);
+ aSubRect.Move(-aExclude.Left(), -aExclude.Top());
+ pDev->DrawWallpaper(aSubRect, aWallpaper );
+
+ rRenderContext.DrawOutDev(aExclude.TopLeft(), aExclude.GetSize(),
+ Point( 0, 0 ), aExclude.GetSize(), *pDev );
+ }
+};
+
+}
+
+IMPL_LINK(DemoWidgets, CursorButtonClick, Button*, pButton, void)
+{
+ for (size_t i=0; i<SAL_N_ELEMENTS(gvPointerData); ++i)
+ {
+ if (mvCursorButtons[i].get() == pButton)
+ {
+ mpBox->SetPointer( gvPointerData[i].eStyle );
+ return;
+ }
+ }
+ assert(false);
+}
+
+namespace {
+
+class DemoPopup : public FloatingWindow
+{
+ public:
+ DemoPopup() : FloatingWindow( nullptr, WB_SYSTEMWINDOW|WB_TOOLTIPWIN)
+ {
+ SetType( WindowType::HELPTEXTWINDOW );
+
+ SetOutputSizePixel( Size( 300, 30 ) );
+ SetBackground(Wallpaper(COL_YELLOW));
+
+ Show( true, ShowFlags::NoActivate );
+ PaintImmediately();
+ }
+
+ virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) override
+ {
+ Size aSize = GetOutputSizePixel();
+ tools::Rectangle aTextRect(Point(6, 6), aSize);
+
+ SetTextColor(COL_BLACK);
+ SetTextAlign(ALIGN_TOP);
+ rRenderContext.DrawText(aTextRect, "This is a standalone help text test",
+ DrawTextFlags::MultiLine|DrawTextFlags::WordBreak|
+ DrawTextFlags::Left|DrawTextFlags::Top);
+
+ rRenderContext.SetLineColor(COL_BLACK);
+ rRenderContext.SetFillColor();
+ rRenderContext.DrawRect( tools::Rectangle( Point(), aSize ) );
+ aSize.AdjustWidth( -2 );
+ aSize.AdjustHeight( -2 );
+ Color aColor( rRenderContext.GetLineColor() );
+ rRenderContext.SetLineColor( COL_GRAY );
+ rRenderContext.DrawRect( tools::Rectangle( Point( 1, 1 ), aSize ) );
+ rRenderContext.SetLineColor( aColor );
+ }
+
+ virtual void MouseButtonDown( const MouseEvent & ) override
+ {
+ Application::Quit();
+ }
+};
+
+}
+
+namespace {
+ void renderFonts()
+ {
+ ScopedVclPtrInstance<VirtualDevice> xDevice;
+ Size aSize(1024, 1024);
+ xDevice->SetOutputSizePixel(aSize);
+
+#if 0
+ for (auto & aFontName : aFontNames)
+ {
+ vcl::Font aFont(aFontName, Size(0,96));
+
+ aFont.Set(COL_BLACK);
+ xDevice->SetFont(aFont);
+ xDevice->Erase();
+
+ FontMetric aMetric = xDevice->GetFontMetric(aFont);
+
+ FontCharMapRef xMap;
+ if (xDevice->GetFontCharMap(xMap))
+ {
+ ... iterate through glyphs ...
+ }
+
+
+ bool GetGlyphBoundRects( const Point& rOrigin, const OUString& rStr, int nIndex,
+ int nLen, int nBase, MetricVector& rVector );
+
+include/vcl/outdev.hxx:typedef std::vector< Rectangle > MetricVector;
+include/vcl/outdev.hxx: MetricVector* pVector = nullptr, OUString* pDisplayText = nullptr );
+include/vcl/outdev.hxx: MetricVector* pVector = nullptr, OUString* pDisplayText = nullptr,
+include/vcl/outdev.hxx: MetricVector* pVector, OUString* pDisplayText, vcl::ITextLayout& _rLayout );
+include/vcl/outdev.hxx: DrawTextFlags nStyle = DrawTextFlags::Mnemonic, MetricVector* pVector = nullp
+
+ bool GetTextBoundRect( Rectangle& rRect,
+ const OUString& rStr, sal_Int32 nBase = 0, sal_Int32 nIndex = 0, sal_Int32 nLen = -1,
+ sal_uLong nLayoutWidth = 0, const long* pDXArray = nullptr ) const;
+
+
+ void DrawText( const Point& rStartPt, const OUString& rStr,
+ sal_Int32 nIndex = 0, sal_Int32 nLen = -1,
+ MetricVector* pVector = nullptr, OUString* pDisplayText = nullptr );
+
+ void DrawText( const Rectangle& rRect,
+ const OUString& rStr, DrawTextFlags nStyle = DrawTextFlags::NONE,
+ MetricVector* pVector = nullptr, OUString* pDisplayText = nullptr,
+ vcl::ITextLayout* _pTextLayout = nullptr );
+
+ Rectangle GetTextRect( const Rectangle& rRect,
+ const OUString& rStr, DrawTextFlags nStyle = DrawTextFlags::WordBreak,
+ TextRectInfo* pInfo = nullptr,
+ const vcl::ITextLayout* _pTextLayout = nullptr ) const;
+
+ }
+#endif
+
+ }
+};
+
+namespace {
+
+class DemoApp : public Application
+{
+ static int showHelp(DemoRenderer &rRenderer)
+ {
+ fprintf(stderr,"vcldemo - a VCL test app\n");
+ fprintf(stderr," --help - print this text\n");
+ fprintf(stderr," --show <renderer> - start with a given renderer, options are:\n");
+ OUString aRenderers(rRenderer.getRendererList());
+ fprintf(stderr," %s\n",
+ OUStringToOString(aRenderers, RTL_TEXTENCODING_UTF8).getStr());
+ fprintf(stderr," --test <iterCount> - create benchmark data\n");
+ fprintf(stderr," --widgets - launch the widget test.\n");
+ fprintf(stderr," --popup - launch the popup test.\n");
+ fprintf(stderr," --threads - render from multiple threads.\n");
+ fprintf(stderr," --font <fontname> - run the font render test.\n");
+ fprintf(stderr, "\n");
+ return 0;
+ }
+
+public:
+ DemoApp() {}
+
+ virtual int Main() override
+ {
+ try
+ {
+ bool bWidgets = false;
+ bool bThreads = false;
+ bool bPopup = false;
+ DemoRenderer aRenderer;
+ std::vector<OUString> aFontNames;
+
+ for (sal_uInt16 i = 0; i < GetCommandLineParamCount(); ++i)
+ {
+ bool bLast = i == GetCommandLineParamCount() - 1;
+ OUString aArg = GetCommandLineParam(i);
+ if (aArg == "--help" || aArg == "-h")
+ return showHelp(aRenderer);
+ if (aArg == "--show")
+ {
+ if (bLast)
+ return showHelp(aRenderer);
+ else
+ aRenderer.selectRenderer(GetCommandLineParam(++i));
+ }
+ else if (aArg == "--test")
+ {
+ if (bLast)
+ return showHelp(aRenderer);
+ else
+ aRenderer.setIterCount(GetCommandLineParam(++i).toInt32());
+ }
+ else if (aArg == "--widgets")
+ bWidgets = true;
+ else if (aArg == "--popup")
+ bPopup = true;
+ else if (aArg == "--threads")
+ bThreads = true;
+ else if (aArg == "--font" && !bLast)
+ aFontNames.push_back(GetCommandLineParam(++i));
+ else if (aArg.startsWith("--"))
+ {
+ fprintf(stderr,"Unknown argument '%s'\n",
+ OUStringToOString(aArg, RTL_TEXTENCODING_UTF8).getStr());
+ return showHelp(aRenderer);
+ }
+ }
+
+ ScopedVclPtrInstance<DemoWin> aMainWin(aRenderer, bThreads);
+ VclPtr<DemoWidgets> xWidgets;
+ VclPtr<DemoPopup> xPopup;
+
+ aMainWin->SetText("Interactive VCL demo #1");
+ if (bWidgets)
+ xWidgets = VclPtr< DemoWidgets >::Create ();
+ else if (bPopup)
+ xPopup = VclPtrInstance< DemoPopup> ();
+ else if (!aFontNames.empty())
+ renderFonts();
+ else
+ aMainWin->Show();
+
+ Application::Execute();
+
+ xWidgets.disposeAndClear();
+ xPopup.disposeAndClear();
+ }
+ catch (const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("vcl.app", "Fatal");
+ return 1;
+ }
+ catch (const std::exception& e)
+ {
+ SAL_WARN("vcl.app", "Fatal: " << e.what());
+ return 1;
+ }
+ return 0;
+ }
+
+protected:
+ void Init() override
+ {
+ LanguageTag::setConfiguredSystemLanguage(MsLangId::getSystemLanguage());
+
+ try
+ {
+ uno::Reference<uno::XComponentContext> xComponentContext
+ = ::cppu::defaultBootstrap_InitialComponentContext();
+ uno::Reference<lang::XMultiServiceFactory> xMSF;
+ xMSF.set(xComponentContext->getServiceManager(), uno::UNO_QUERY);
+ if(!xMSF.is())
+ Application::Abort("Bootstrap failure - no service manager");
+
+ ::comphelper::setProcessServiceFactory(xMSF);
+ }
+ catch (const uno::Exception &e)
+ {
+ Application::Abort("Bootstrap exception " + e.Message);
+ }
+ }
+ void DeInit() override
+ {
+ framework::getDesktop(::comphelper::getProcessComponentContext())->terminate();
+ framework::getDesktop(::comphelper::getProcessComponentContext())->disposing();
+
+ uno::Reference< lang::XComponent >(
+ comphelper::getProcessComponentContext(),
+ uno::UNO_QUERY_THROW)-> dispose();
+ ::comphelper::setProcessServiceFactory(nullptr);
+ }
+};
+
+}
+
+void vclmain::createApplication()
+{
+#ifdef _WIN32
+ _putenv_s("LIBO_VCL_DEMO", "1");
+#else
+ setenv("LIBO_VCL_DEMO", "1", 0);
+#endif
+ static DemoApp aApp;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/webpfuzzer.cxx b/vcl/workben/webpfuzzer.cxx
new file mode 100644
index 0000000000..b7ae8f7414
--- /dev/null
+++ b/vcl/workben/webpfuzzer.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 <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+#include <filter/WebpReader.hxx>
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping* lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = { { 0, 0 } };
+
+ return map;
+}
+
+const lib_to_constructor_mapping* lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = { { 0, 0 } };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*) { return nullptr; }
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportWebpGraphic(aStream, aGraphic);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/webpfuzzer.options b/vcl/workben/webpfuzzer.options
new file mode 100644
index 0000000000..6ca996b350
--- /dev/null
+++ b/vcl/workben/webpfuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+dict = webp.dict
diff --git a/vcl/workben/win/dnd/atlwindow.cxx b/vcl/workben/win/dnd/atlwindow.cxx
new file mode 100644
index 0000000000..61781e18f5
--- /dev/null
+++ b/vcl/workben/win/dnd/atlwindow.cxx
@@ -0,0 +1,238 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * 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/uno/Reference.h>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+
+#include <cppuhelper/servicefactory.hxx>
+#include <rtl/string.h>
+
+#include "atlwindow.hxx"
+#include "targetlistener.hxx"
+#include "sourcelistener.hxx"
+#include <map>
+
+#include <winbase.h>
+using namespace com::sun::star::lang;
+using namespace com::sun::star::datatransfer::dnd;
+using namespace com::sun::star::datatransfer::dnd::DNDConstants;
+using namespace cppu;
+
+LRESULT APIENTRY EditSubclassProc( HWND hwnd, UINT uMsg,WPARAM wParam, LPARAM lParam) ;
+
+extern Reference< XMultiServiceFactory > MultiServiceFactory;
+DWORD WINAPI MTAFunc(LPVOID pParams);
+
+char* szSTAWin= "XDragSource::executeDrag is called from the same "
+ "OLE STA thread that created the window.";
+char* szMTAWin= "XDragSource::executeDrag is called from an MTA thread "
+ "that did not create the window.";
+
+WNDPROC wpOrigEditProc;
+
+map<HWND, HWND> mapEditToMainWnd;
+
+LRESULT AWindow::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+ Reference<XComponent> xcompSource( m_xDragSource, UNO_QUERY);
+
+ PostQuitMessage(0);
+
+ m_xDropTarget=0;
+ m_xDragSource=0;
+
+ // Remove the subclass from the edit control.
+ ::SetWindowLong(m_hwndEdit, GWL_WNDPROC,
+ (LONG) wpOrigEditProc);
+
+ return 0;
+}
+
+LRESULT AWindow::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+ // Prepare the EDIT control
+ m_hwndEdit = CreateWindowA(
+ "EDIT", // predefined class
+ NULL, // no window title
+ WS_CHILD | WS_VISIBLE | WS_VSCROLL |
+ ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL,
+ 0, 0, 0, 0, // set size in WM_SIZE message
+ m_hWnd, // parent window
+ (HMENU) NULL, // edit control ID
+ (HINSTANCE) GetWindowLong( GWL_HINSTANCE),
+ NULL);
+
+ // the map is used in the window procedure for the edit window to associate the
+ // it to the right main window ( AWindow)
+ mapEditToMainWnd[m_hwndEdit]= m_hWnd;
+ // Superclass the edit window, because we want to process mouse messages
+ wpOrigEditProc = (WNDPROC) ::SetWindowLongA(m_hwndEdit,
+ GWL_WNDPROC, (LONG) EditSubclassProc);
+
+ // Add text to the window.
+ if( m_isMTA)
+ ::SendMessageA(m_hwndEdit, WM_SETTEXT, 0, (LPARAM) szMTAWin);
+ else
+ ::SendMessageA(m_hwndEdit, WM_SETTEXT, 0, (LPARAM) szSTAWin);
+
+ // create the DragSource
+ Reference< XInterface> xint= MultiServiceFactory->createInstance("com.sun.star.datatransfer.dnd.OleDragSource");
+ m_xDragSource.set( xint, UNO_QUERY );
+ Reference<XInitialization> xInit( xint, UNO_QUERY);
+
+ Any ar[2];
+ ar[1]<<= (sal_uInt32)m_hWnd;
+ xInit->initialize( Sequence<Any>( ar, 2) );
+
+ //create the DropTarget
+ Reference< XInterface> xintTarget= MultiServiceFactory->createInstance("com.sun.star.datatransfer.dnd.OleDropTarget");
+ m_xDropTarget.set( xintTarget, UNO_QUERY );
+ Reference<XInitialization> xInitTarget( xintTarget, UNO_QUERY);
+
+ Any any;
+ any <<= (sal_uInt32)m_hWnd;
+ xInitTarget->initialize( Sequence<Any>( &any, 1) );
+
+ m_xDropTarget->addDropTargetListener( static_cast<XDropTargetListener*>
+ ( new DropTargetListener( m_hwndEdit)) );
+// // make this window a drop target
+ m_xDropTarget->setActive(sal_True);
+
+ return 0;
+}
+
+// When the mouse is dragged for a second than a drag is initiated
+LRESULT AWindow::OnMouseAction(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+ if( uMsg== WM_LBUTTONDOWN)
+ {
+ SetTimer( 1, 1000);
+ }
+
+ else if( uMsg == WM_LBUTTONUP)
+ {
+ KillTimer( 1);
+ }
+
+ return 0;
+}
+
+LRESULT AWindow::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+ USES_CONVERSION;
+ KillTimer( 1);
+ if(m_xDragSource.is())
+ {
+
+ //Get the Text out of the Edit window
+ int length= (int)::SendMessageA( m_hwndEdit, WM_GETTEXTLENGTH, 0, 0);
+ char * pBuffer= new char[length + 1];
+ ZeroMemory( pBuffer, length + 1);
+ ::SendMessageA( m_hwndEdit, WM_GETTEXT, length, (LPARAM) pBuffer);
+
+ IDataObject* pData= NULL;
+ HRESULT hr= CreateDataCache( NULL, CLSID_NULL, __uuidof(IDataObject),(void**) &pData);
+ if( pData)
+ {
+ FORMATETC format={ CF_TEXT, NULL, DVASPECT_CONTENT, -1, };
+
+ HGLOBAL mem= GlobalAlloc(GHND, length + 1 );
+ void* pMem= GlobalLock( mem);
+ memcpy( pMem, pBuffer, length+1);
+ GlobalUnlock( mem);
+
+ STGMEDIUM medium;
+ medium.tymed= TYMED_HGLOBAL;
+ medium.hGlobal= mem;
+ medium.pUnkForRelease= NULL;
+
+ pData->SetData( &format, &medium, TRUE); // releases HGLOBAL eventually
+
+ Reference<XTransferable> xTrans= CDOTransferable::create(
+ MultiServiceFactory, pData);
+
+ // call XDragSource::executeDrag from an MTA
+ if( m_isMTA )
+ {
+ DWORD mtaThreadId;
+ ThreadData data;
+ data.source= m_xDragSource;
+ data.transferable= xTrans;
+
+ data.evtThreadReady= CreateEvent( NULL, FALSE, FALSE, NULL);
+
+ CloseHandle(CreateThread(NULL, 0, MTAFunc, &data, 0, &mtaThreadId));
+ // We must wait until the thread copied the ThreadData structure
+ WaitForSingleObject( data.evtThreadReady, INFINITE);
+ CloseHandle( data.evtThreadReady);
+
+ }
+ else
+ {
+ m_xDragSource->startDrag( DragGestureEvent(),
+ ACTION_LINK|ACTION_MOVE|ACTION_COPY,
+ 0,
+ 0,
+ xTrans,
+ Reference<XDragSourceListener>( static_cast<XDragSourceListener*>(new DragSourceListener() ) ) );
+ }
+ }
+
+ delete[] pBuffer;
+ }
+
+ return 0;
+}
+
+LRESULT AWindow::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+ // Make the edit control the size of the window's
+ // client area.
+ ::MoveWindow(m_hwndEdit,
+ 0, 0, // starting x- and y-coordinates
+ LOWORD(lParam), // width of client area
+ HIWORD(lParam), // height of client area
+ TRUE); // repaint window
+
+ return 0;
+}
+LRESULT AWindow::OnFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+ ::SetFocus(m_hwndEdit);
+ return 0;
+}
+
+// Subclass procedure for EDIT window
+LRESULT APIENTRY EditSubclassProc( HWND hwnd, UINT uMsg,WPARAM wParam, LPARAM lParam)
+{
+
+ if( uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST)
+ {
+ HWND hAWindow= mapEditToMainWnd[hwnd];
+ ::SendMessage( hAWindow, uMsg, wParam, lParam);
+
+ }
+ return CallWindowProc( wpOrigEditProc, hwnd, uMsg,
+ wParam, lParam);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/win/dnd/atlwindow.hxx b/vcl/workben/win/dnd/atlwindow.hxx
new file mode 100644
index 0000000000..047acf6769
--- /dev/null
+++ b/vcl/workben/win/dnd/atlwindow.hxx
@@ -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 .
+ */
+#pragma once
+#include <atlbase.h>
+extern CComModule _Module;
+#include <atlcom.h>
+#include <atlctl.h>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragSource.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/uno/Reference.h>
+#include "../../source/inc/DtObjFactory.hxx"
+
+using namespace com::sun::star::uno;
+using namespace com::sun::star::datatransfer::dnd;
+using namespace com::sun::star::datatransfer;
+
+struct ThreadData
+{
+ Reference<XDragSource> source;
+ Reference<XTransferable> transferable;
+ HANDLE evtThreadReady;
+};
+
+class AWindow: public CWindowImpl<AWindow, CWindow,
+ CWinTraits<WS_CAPTION |WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0> >
+{
+ TCHAR m_strName[80];
+ Reference<XDropTarget> m_xDropTarget;
+ Reference<XDragSource> m_xDragSource;
+ BOOL m_isMTA;
+
+ HWND m_hwndEdit;
+
+public:
+ explicit AWindow(LPCTSTR strName)
+ {
+ RECT rcPos= {0,0,200,200};
+ Create(0, rcPos, strName);
+ }
+ AWindow(LPCTSTR strName, RECT pos, BOOL mta=FALSE): m_isMTA( mta)
+ {
+ Create(0, pos, strName);
+ }
+
+ ~AWindow()
+ {
+ if(m_hWnd)
+ DestroyWindow();
+ }
+
+ BEGIN_MSG_MAP(AWindow)
+ MESSAGE_HANDLER( WM_CLOSE, OnClose)
+ MESSAGE_HANDLER( WM_CREATE, OnCreate)
+ MESSAGE_RANGE_HANDLER( WM_MOUSEFIRST, WM_MOUSELAST, OnMouseAction)
+ MESSAGE_HANDLER( WM_TIMER, OnTimer)
+ MESSAGE_HANDLER( WM_SIZE, OnSize)
+ MESSAGE_HANDLER( WM_SETFOCUS, OnFocus)
+
+ END_MSG_MAP()
+
+ LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+ LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+ LRESULT OnMouseAction(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+ LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+ LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+ LRESULT OnFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/win/dnd/dndTest.cxx b/vcl/workben/win/dnd/dndTest.cxx
new file mode 100644
index 0000000000..2f2a7bccfc
--- /dev/null
+++ b/vcl/workben/win/dnd/dndTest.cxx
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#if defined _MSC_VER
+#pragma warning(push,1)
+#endif
+#include <windows.h>
+#include <comdef.h>
+#include <atlbase.h>
+CComModule _Module;
+#include <atlcom.h>
+#include <atlimpl.cpp>
+#if defined _MSC_VER
+#pragma warning(pop)
+#endif
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <rtl/process.h>
+#include <cppuhelper/servicefactory.hxx>
+#include "sourcelistener.hxx"
+
+#include "atlwindow.hxx"
+BEGIN_OBJECT_MAP(ObjectMap)
+END_OBJECT_MAP()
+
+using namespace com::sun::star::lang;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::datatransfer::dnd;
+using namespace com::sun::star::datatransfer::dnd::DNDConstants;
+
+HRESULT doTest();
+DWORD WINAPI MTAFunc( void* threadData);
+
+Reference< XMultiServiceFactory > MultiServiceFactory;
+
+int main( int argc, char *argv[ ], char *envp[ ] )
+{
+ HRESULT hr;
+ if( FAILED( hr=CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)))
+ {
+ printf("CoInitializeEx failed \n");
+ return -1;
+ }
+
+ _Module.Init( ObjectMap, GetModuleHandleA( NULL));
+
+ if( FAILED(hr=doTest()))
+ {
+ _com_error err( hr);
+ }
+
+ _Module.Term();
+ CoUninitialize();
+ return 0;
+}
+
+HRESULT doTest()
+{
+
+ MultiServiceFactory= createRegistryServiceFactory( OUString(L"types.rdb"), OUString( L"services.rdb") , sal_True);
+
+ // create the MTA thread that is used to realize MTA calls to the services
+ // We create the thread and wait until the thread has created its message queue
+ HANDLE evt= CreateEventA(NULL, FALSE, FALSE, NULL);
+ DWORD threadIdMTA=0;
+ HANDLE hMTAThread= CreateThread( NULL, 0, MTAFunc, &evt, 0, &threadIdMTA);
+ WaitForSingleObject( evt, INFINITE);
+ CloseHandle(evt);
+
+ HRESULT hr= S_OK;
+ RECT pos1={0,0,300,200};
+ AWindow win("DnD starting in Ole STA", threadIdMTA, pos1);
+
+ RECT pos2={ 0, 205, 300, 405};
+ AWindow win2("DnD starting in MTA", threadIdMTA, pos2, true);
+
+ // win3 and win4 call initialize from an MTA but they are created in an STA
+ RECT pos3={300,0,600,200};
+ AWindow win3("DnD starting in OLE STA", threadIdMTA, pos3, false, true);
+
+ RECT pos4={ 300, 205, 600, 405};
+ AWindow win24("DnD starting in Ole MTA", threadIdMTA, pos4, true, true);
+
+ MSG msg;
+ while( GetMessageA(&msg, (HWND)NULL, 0, 0) )
+ {
+ TranslateMessage( &msg);
+ DispatchMessageA( &msg);
+ }
+
+ // Shut down the MTA thread
+ PostThreadMessageA( threadIdMTA, WM_QUIT, 0, 0);
+ WaitForSingleObject(hMTAThread, INFINITE);
+ CloseHandle(hMTAThread);
+
+ return S_OK;
+}
+
+extern Reference<XMultiServiceFactory> MultiServiceFactory;
+DWORD WINAPI MTAFunc( void* threadData)
+{
+ HRESULT hr= CoInitializeEx( NULL, COINIT_MULTITHREADED);
+ ATLASSERT( FAILED(hr) );
+ MSG msg;
+ // force the creation of a message queue
+ PeekMessageA(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
+ SetEvent( *(HANDLE*)threadData );
+
+ RECT pos={0, 406, 300, 605};
+ AWindow win("DnD, full MTA", GetCurrentThreadId(), pos, false, true);
+
+ while( GetMessageA(&msg, (HWND)NULL, 0, 0) )
+ {
+ switch( msg.message)
+ {
+ case WM_SOURCE_INIT:
+ {
+ InitializationData* pData= (InitializationData*)msg.wParam;
+ Any any;
+ any <<= (sal_uInt32) pData->hWnd;
+ pData->xInit->initialize( Sequence<Any>( &any, 1));
+
+ CoTaskMemFree( pData);
+ break;
+ }
+ case WM_SOURCE_STARTDRAG:
+ {
+ // wParam contains necessary data
+ StartDragData* pData= (StartDragData*)msg.wParam;
+ Sequence<DataFlavor> seq= pData->transferable->getTransferDataFlavors();
+ // have a look what flavours are supported
+ for( int i=0; i<seq.(); i++)
+ {
+ DataFlavor d= seq[i];
+ }
+ pData->source->startDrag( DragGestureEvent(),
+ ACTION_LINK|ACTION_MOVE|ACTION_COPY,
+ 0,
+ 0,
+ pData->transferable,
+ Reference<XDragSourceListener>( static_cast<XDragSourceListener*>
+ ( new DragSourceListener())));
+ CoTaskMemFree( pData);
+ break;
+ }
+
+ } // end switch
+
+ TranslateMessage( &msg);
+ DispatchMessageA( &msg);
+ }
+
+ CoUninitialize();
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/win/dnd/makefile.mk b/vcl/workben/win/dnd/makefile.mk
new file mode 100644
index 0000000000..e0d438418e
--- /dev/null
+++ b/vcl/workben/win/dnd/makefile.mk
@@ -0,0 +1,70 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+PRJ=..$/..$/..$/
+
+PRJNAME=dtrans
+TARGET=dndTest
+TARGETTYPE=CUI
+LIBTARGET=NO
+
+ENABLE_EXCEPTIONS=TRUE
+
+# --- Settings ---
+
+.INCLUDE : settings.mk
+
+# --- Files ---
+
+CFLAGS+= -D_WIN32_DCOM
+
+INCPRE+= -I$(ATL_INCLUDE)
+
+OBJFILES= $(OBJ)$/dndTest.obj \
+ $(OBJ)$/atlwindow.obj \
+ $(OBJ)$/targetlistener.obj \
+ $(OBJ)$/sourcelistener.obj \
+ $(OBJ)$/dataobject.obj
+
+APP1NOSAL=TRUE
+
+APP1TARGET= $(TARGET)
+APP1OBJS=$(OBJFILES)
+
+APP1STDLIBS= \
+ $(SALLIB) \
+ $(CPPUHELPERLIB) \
+ $(CPPULIB) \
+ $(UWINAPILIB) \
+ $(USER32LIB) \
+ $(OLE32LIB) \
+ comsupp.lib \
+ $(OLEAUT32LIB) \
+ $(GDI32LIB) \
+ $(UUIDLIB)
+
+APP1LIBS= \
+ $(SLB)$/dtobjfact.lib \
+ $(SLB)$/dtutils.lib
+
+APP1DEF= $(MISC)\$(APP1TARGET).def
+
+# --- Targets ---
+
+.INCLUDE : target.mk
+
diff --git a/vcl/workben/win/dnd/sourcelistener.cxx b/vcl/workben/win/dnd/sourcelistener.cxx
new file mode 100644
index 0000000000..aa3366e79a
--- /dev/null
+++ b/vcl/workben/win/dnd/sourcelistener.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 "sourcelistener.hxx"
+
+DragSourceListener::DragSourceListener()
+{
+}
+DragSourceListener::~DragSourceListener()
+{
+}
+
+void SAL_CALL DragSourceListener::disposing( const EventObject& Source )
+ throw(RuntimeException)
+{
+}
+
+void SAL_CALL DragSourceListener::dragDropEnd( const DragSourceDropEvent& dsde )
+ throw(RuntimeException)
+{
+}
+
+void SAL_CALL DragSourceListener::dragEnter( const DragSourceDragEvent& dsde )
+ throw(RuntimeException)
+{
+}
+
+void SAL_CALL DragSourceListener::dragExit( const DragSourceEvent& dse )
+ throw(RuntimeException)
+{
+}
+
+void SAL_CALL DragSourceListener::dragOver( const DragSourceDragEvent& dsde )
+ throw(RuntimeException)
+{
+}
+
+void SAL_CALL DragSourceListener::dropActionChanged( const DragSourceDragEvent& dsde )
+ throw(RuntimeException)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/win/dnd/sourcelistener.hxx b/vcl/workben/win/dnd/sourcelistener.hxx
new file mode 100644
index 0000000000..0fc0513990
--- /dev/null
+++ b/vcl/workben/win/dnd/sourcelistener.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 <cppuhelper/implbase.hxx>
+#include <com/sun/star/datatransfer/dnd/XDragSourceListener.hpp>
+#include <com/sun/star/datatransfer/dnd/DragSourceDropEvent.hpp>
+#include <com/sun/star/datatransfer/dnd/DragSourceDragEvent.hpp>
+
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::datatransfer::dnd;
+using namespace ::cppu;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+
+class DragSourceListener: public WeakImplHelper<XDragSourceListener>
+{
+ // this is a window where dropped data are shown as text (only text)
+public:
+ DragSourceListener( );
+ ~DragSourceListener();
+
+ virtual void SAL_CALL disposing( const EventObject& Source )
+ throw(RuntimeException);
+
+ virtual void SAL_CALL dragDropEnd( const DragSourceDropEvent& dsde )
+ throw(RuntimeException);
+ virtual void SAL_CALL dragEnter( const DragSourceDragEvent& dsde )
+ throw(RuntimeException);
+ virtual void SAL_CALL dragExit( const DragSourceEvent& dse )
+ throw(RuntimeException);
+ virtual void SAL_CALL dragOver( const DragSourceDragEvent& dsde )
+ throw(RuntimeException);
+ virtual void SAL_CALL dropActionChanged( const DragSourceDragEvent& dsde )
+ throw(RuntimeException);
+
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/win/dnd/targetlistener.cxx b/vcl/workben/win/dnd/targetlistener.cxx
new file mode 100644
index 0000000000..0a93d39456
--- /dev/null
+++ b/vcl/workben/win/dnd/targetlistener.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 "targetlistener.hxx"
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/datatransfer/DataFlavor.hpp>
+
+using namespace com::sun::star::datatransfer::dnd::DNDConstants;
+using namespace com::sun::star::datatransfer;
+
+DropTargetListener::DropTargetListener(HWND hEdit):m_hEdit( hEdit)
+{
+}
+DropTargetListener::~DropTargetListener()
+{
+}
+
+void SAL_CALL DropTargetListener::disposing( const EventObject& Source )
+ throw(RuntimeException)
+{
+
+}
+
+void SAL_CALL DropTargetListener::drop( const DropTargetDropEvent& e )
+ throw(RuntimeException)
+{
+ e.Context->rejectDrop();
+
+ DataFlavor flavor( OUString(OUString("text/plain;charset=windows-1252")),
+ OUString(L"Text plain"), cppu::UnoType<Sequence<sal_Int8>>::get() );
+
+ Any anyData= e.Transferable->getTransferData( flavor);
+ Sequence<sal_Int8> seq= *( Sequence<sal_Int8>*)anyData.getValue();
+ SendMessage( m_hEdit, WM_SETTEXT, 0, (LPARAM) seq.getConstArray() );
+}
+
+void SAL_CALL DropTargetListener::dragEnter( const DropTargetDragEnterEvent& dtde )
+ throw(RuntimeException)
+{
+ //If one drags something that is not moveable
+ if( !(dtde.SourceActions & dtde.DropAction) )
+ dtde.Context->acceptDrag( ACTION_COPY);
+}
+
+void SAL_CALL DropTargetListener::dragExit( const DropTargetEvent& dte )
+ throw(RuntimeException)
+{
+}
+
+void SAL_CALL DropTargetListener::dragOver( const DropTargetDragEvent& dtde )
+ throw(RuntimeException)
+{
+ if( !(dtde.SourceActions & dtde.DropAction) )
+ dtde.Context->acceptDrag( ACTION_COPY);
+}
+
+void SAL_CALL DropTargetListener::dropActionChanged( const DropTargetDragEvent& dtde )
+ throw(RuntimeException)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/win/dnd/targetlistener.hxx b/vcl/workben/win/dnd/targetlistener.hxx
new file mode 100644
index 0000000000..f42fac33ae
--- /dev/null
+++ b/vcl/workben/win/dnd/targetlistener.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 .
+ */
+
+#pragma once
+
+#if defined _MSC_VER
+#pragma warning(push,1)
+#endif
+#include <windows.h>
+#if defined _MSC_VER
+#pragma warning(pop)
+#endif
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/datatransfer/dnd/XDropTargetListener.hpp>
+#include <com/sun/star/datatransfer/dnd/DropTargetDropEvent.hpp>
+#include <com/sun/star/datatransfer/dnd/DropTargetDragEvent.hpp>
+#include <com/sun/star/datatransfer/dnd/DropTargetDragEnterEvent.hpp>
+
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::datatransfer::dnd;
+using namespace ::cppu;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+
+class DropTargetListener: public WeakImplHelper<XDropTargetListener>
+{
+ // this is a window where dropped data are shown as text (only text)
+ HWND m_hEdit;
+public:
+ explicit DropTargetListener(HWND hEdit);
+ ~DropTargetListener();
+
+ virtual void SAL_CALL disposing( const EventObject& Source )
+ throw(RuntimeException);
+
+ virtual void SAL_CALL drop( const DropTargetDropEvent& dtde )
+ throw(RuntimeException);
+ virtual void SAL_CALL dragEnter( const DropTargetDragEnterEvent& dtde )
+ throw(RuntimeException);
+ virtual void SAL_CALL dragExit( const DropTargetEvent& dte )
+ throw(RuntimeException);
+ virtual void SAL_CALL dragOver( const DropTargetDragEvent& dtde )
+ throw(RuntimeException);
+ virtual void SAL_CALL dropActionChanged( const DropTargetDragEvent& dtde )
+ throw(RuntimeException);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/win/dnd/transferable.cxx b/vcl/workben/win/dnd/transferable.cxx
new file mode 100644
index 0000000000..924c031763
--- /dev/null
+++ b/vcl/workben/win/dnd/transferable.cxx
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "transferable.hxx"
+
+// ctor
+
+CTransferable::CTransferable( wchar_t* dataString ) :
+ m_seqDFlv( 1 ),
+ m_Data( dataString )
+{
+ DataFlavor df;
+
+ /*
+ df.MimeType = L"text/plain; charset=unicode";
+ df.DataType = cppu::UnoType<OUString>::get();
+
+ m_seqDFlv[0] = df;
+ */
+
+ //df.MimeType = L"text/plain; charset=windows1252";
+ df.MimeType = L"text/plain";
+ df.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ m_seqDFlv[0] = df;
+}
+
+// getTransferData
+
+Any SAL_CALL CTransferable::getTransferData( const DataFlavor& aFlavor )
+ throw(UnsupportedFlavorException, IOException, RuntimeException)
+{
+ Any anyData;
+
+ /*if ( aFlavor == m_seqDFlv[0] )
+ {
+ anyData = makeAny( m_Data );
+ }
+ else*/ if ( aFlavor == m_seqDFlv[0] )
+ {
+ OString aStr( m_Data.getStr( ), m_Data.getLength( ), 1252 );
+ Sequence< sal_Int8 > sOfChars( aStr.getLength( ) );
+ sal_Int32 lenStr = aStr.getLength( );
+
+ for ( sal_Int32 i = 0; i < lenStr; ++i )
+ sOfChars[i] = aStr[i];
+
+ anyData = makeAny( sOfChars );
+ }
+
+ return anyData;
+}
+
+// getTransferDataFlavors
+
+Sequence< DataFlavor > SAL_CALL CTransferable::getTransferDataFlavors( )
+ throw(RuntimeException)
+{
+ return m_seqDFlv;
+}
+
+// isDataFlavorSupported
+
+sal_Bool SAL_CALL CTransferable::isDataFlavorSupported( const DataFlavor& aFlavor )
+ throw(RuntimeException)
+{
+ sal_Int32 nLength = m_seqDFlv.getLength( );
+ sal_Bool bRet = sal_False;
+
+ for ( sal_Int32 i = 0; i < nLength; ++i )
+ {
+ if ( m_seqDFlv[i] == aFlavor )
+ {
+ bRet = sal_True;
+ break;
+ }
+ }
+
+ return bRet;
+}
+
+// lostOwnership
+
+void SAL_CALL CTransferable::lostOwnership( const Reference< XClipboard >& xClipboard, const Reference< XTransferable >& xTrans )
+ throw(RuntimeException)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/win/dnd/transferable.hxx b/vcl/workben/win/dnd/transferable.hxx
new file mode 100644
index 0000000000..c1606cab86
--- /dev/null
+++ b/vcl/workben/win/dnd/transferable.hxx
@@ -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 .
+ */
+#pragma once
+
+#include <cppuhelper/servicefactory.hxx>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardOwner.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <osl/diagnose.h>
+
+#include <stdio.h>
+#if defined _MSC_VER
+#pragma warning(push,1)
+#endif
+#include <windows.h>
+#include <objbase.h>
+#if defined _MSC_VER
+#pragma warning(pop)
+#endif
+
+#include <memory>
+
+#include <process.h>
+
+#include "../../source/win32/ImplHelper.hxx"
+
+// my defines
+
+#define TEST_CLIPBOARD
+#define RDB_SYSPATH "d:\\projects\\src616\\dtrans\\wntmsci7\\bin\\applicat.rdb"
+#define WINCLIPBOARD_SERVICE_NAME L"com.sun.star.datatransfer.clipboard.SystemClipboard"
+#define WRITE_CB
+#define EVT_MANUAL_RESET TRUE
+#define EVT_INIT_NONSIGNALED FALSE
+#define EVT_NONAME ""
+
+// namespaces
+
+using namespace ::cppu;
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::datatransfer::clipboard;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::io;
+using namespace ::com::sun::star::lang;
+
+class CTransferable : public WeakImplHelper< XClipboardOwner, XTransferable >
+{
+public:
+ CTransferable( ){};
+ explicit CTransferable( wchar_t* dataString);
+
+ // XTransferable
+
+ virtual Any SAL_CALL getTransferData( const DataFlavor& aFlavor ) throw(UnsupportedFlavorException, IOException, RuntimeException);
+ virtual Sequence< DataFlavor > SAL_CALL getTransferDataFlavors( ) throw(RuntimeException);
+ virtual sal_Bool SAL_CALL isDataFlavorSupported( const DataFlavor& aFlavor ) throw(RuntimeException);
+
+ // XClipboardOwner
+
+ virtual void SAL_CALL lostOwnership( const Reference< XClipboard >& xClipboard, const Reference< XTransferable >& xTrans ) throw(RuntimeException);
+
+private:
+ Sequence< DataFlavor > m_seqDFlv;
+ OUString m_Data;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/win/dtrans/XTDo.cxx b/vcl/workben/win/dtrans/XTDo.cxx
new file mode 100644
index 0000000000..f8da707d92
--- /dev/null
+++ b/vcl/workben/win/dtrans/XTDo.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 <osl/diagnose.h>
+
+#include "../DTransHelper.hxx"
+
+#include "XTDo.hxx"
+
+#if defined _MSC_VER
+#pragma warning(push,1)
+#endif
+#include <windows.h>
+#include <ole2.h>
+#if defined _MSC_VER
+#pragma warning(pop)
+#endif
+#include <memory>
+
+using namespace ::std;
+
+// OTWrapperDataObject
+
+/*
+ in the constructor we enumerate all formats offered by the transferable
+ and convert the formats into formatetc structures
+ if the transferable supports text in different charsets we use either
+ the charset equal to the charset of the current thread or an arbitrary
+ charset supported by the transferable and the system
+ if the transferable supports only unicodetext we offer in addition to
+ this text in the charset of the current thread
+ in order to allow the consumer of the clipboard to query for the charset
+ of the text in the clipboard we offer a CF_LOCALE
+*/
+CXTDataObject::CXTDataObject( ) :
+ m_nRefCnt( 0 )
+{
+
+}
+
+// IUnknown->QueryInterface
+
+STDMETHODIMP CXTDataObject::QueryInterface( REFIID iid, LPVOID* ppvObject )
+{
+ OSL_ASSERT( NULL != ppvObject );
+
+ if ( NULL == ppvObject )
+ return E_INVALIDARG;
+
+ HRESULT hr = E_NOINTERFACE;
+
+ *ppvObject = NULL;
+
+ if ( ( __uuidof( IUnknown ) == iid ) || ( __uuidof( IDataObject ) == iid ) )
+ {
+ *ppvObject = static_cast< IUnknown* >( this );
+ ( (LPUNKNOWN)*ppvObject )->AddRef( );
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+// IUnknown->AddRef
+
+STDMETHODIMP_(ULONG) CXTDataObject::AddRef( )
+{
+ return static_cast< ULONG >( InterlockedIncrement( &m_nRefCnt ) );
+}
+
+// IUnknown->Release
+
+STDMETHODIMP_(ULONG) CXTDataObject::Release( )
+{
+ // we need a helper variable because it's
+ // not allowed to access a member variable
+ // after an object is destroyed
+ ULONG nRefCnt = static_cast< ULONG >( InterlockedDecrement( &m_nRefCnt ) );
+
+ if ( 0 == nRefCnt )
+ {
+ delete this;
+ }
+
+ return nRefCnt;
+}
+
+/*------------------------------------------------------------------------
+
+ IDataObject->GetData
+ we deliver data only into global memory
+
+ algo:
+ 1. convert the given formatect struct into a valid dataflavor
+ 2. if the transferable directly supports the requested format
+ 2.1. if text data requested add a trailing '\0' in order to prevent
+ problems (windows needs '\0' terminated strings
+ 2.2. we expect unicode data as Sequence< sal_Unicode > and all other
+ text and raw data as Sequence< sal_Int8 >
+
+------------------------------------------------------------------------*/
+
+STDMETHODIMP CXTDataObject::GetData( LPFORMATETC pFormatetc, LPSTGMEDIUM pmedium )
+{
+ if ( ( NULL == pFormatetc ) || ( NULL == pmedium ) )
+ return E_INVALIDARG;
+
+ HRESULT hr = E_FAIL;
+
+ if ( CF_TEXT == pFormatetc->cfFormat )
+ {
+ CHGlobalHelper hGlobHlp( TRUE );
+
+ char pBuff[] = "Test OleClipboard";
+ hGlobHlp.Write( pBuff, sizeof( pBuff ), NULL );
+
+ pmedium->tymed = TYMED_HGLOBAL;
+ pmedium->hGlobal = hGlobHlp.GetHGlobal( );
+ pmedium->pUnkForRelease = NULL;
+
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+// IDataObject->EnumFormatEtc
+
+STDMETHODIMP CXTDataObject::EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC** ppenumFormatetc )
+{
+ if ( ( NULL == ppenumFormatetc ) || ( DATADIR_SET == dwDirection ) )
+ return E_INVALIDARG;
+
+ *ppenumFormatetc = NULL;
+
+ HRESULT hr = E_FAIL;
+
+ if ( DATADIR_GET == dwDirection )
+ {
+ *ppenumFormatetc = new CEnumFormatEtc( this );
+ static_cast< LPUNKNOWN >( *ppenumFormatetc )->AddRef( );
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+// IDataObject->QueryGetData
+
+STDMETHODIMP CXTDataObject::QueryGetData( LPFORMATETC pFormatetc )
+{
+ return E_NOTIMPL;
+}
+
+// IDataObject->GetDataHere
+
+STDMETHODIMP CXTDataObject::GetDataHere( LPFORMATETC, LPSTGMEDIUM )
+{
+ return E_NOTIMPL;
+}
+
+// IDataObject->GetCanonicalFormatEtc
+
+STDMETHODIMP CXTDataObject::GetCanonicalFormatEtc( LPFORMATETC, LPFORMATETC )
+{
+ return E_NOTIMPL;
+}
+
+// IDataObject->SetData
+
+STDMETHODIMP CXTDataObject::SetData( LPFORMATETC, LPSTGMEDIUM, BOOL )
+{
+ return E_NOTIMPL;
+}
+
+// IDataObject->DAdvise
+
+STDMETHODIMP CXTDataObject::DAdvise( LPFORMATETC, DWORD, LPADVISESINK, DWORD * )
+{
+ return E_NOTIMPL;
+}
+
+// IDataObject->DUnadvise
+
+STDMETHODIMP CXTDataObject::DUnadvise( DWORD )
+{
+ return E_NOTIMPL;
+}
+
+// IDataObject->EnumDAdvise
+
+STDMETHODIMP CXTDataObject::EnumDAdvise( LPENUMSTATDATA * )
+{
+ return E_NOTIMPL;
+}
+
+CXTDataObject::operator IDataObject*( )
+{
+ return static_cast< IDataObject* >( this );
+}
+
+CEnumFormatEtc::CEnumFormatEtc( LPUNKNOWN pUnkDataObj ) :
+ m_nRefCnt( 0 ),
+ m_pUnkDataObj( pUnkDataObj ),
+ m_nCurrPos( 0 )
+{
+}
+
+// IUnknown->QueryInterface
+
+STDMETHODIMP CEnumFormatEtc::QueryInterface( REFIID iid, LPVOID* ppvObject )
+{
+ if ( NULL == ppvObject )
+ return E_INVALIDARG;
+
+ HRESULT hr = E_NOINTERFACE;
+
+ *ppvObject = NULL;
+
+ if ( ( __uuidof( IUnknown ) == iid ) || ( __uuidof( IEnumFORMATETC ) == iid ) )
+ {
+ *ppvObject = static_cast< IUnknown* >( this );
+ static_cast< LPUNKNOWN >( *ppvObject )->AddRef( );
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+// IUnknown->AddRef
+
+STDMETHODIMP_(ULONG) CEnumFormatEtc::AddRef( )
+{
+ // keep the dataobject alive
+ m_pUnkDataObj->AddRef( );
+ return InterlockedIncrement( &m_nRefCnt );
+}
+
+// IUnknown->Release
+
+STDMETHODIMP_(ULONG) CEnumFormatEtc::Release( )
+{
+ // release the outer dataobject
+ m_pUnkDataObj->Release( );
+
+ // we need a helper variable because it's
+ // not allowed to access a member variable
+ // after an object is destroyed
+ ULONG nRefCnt = InterlockedDecrement( &m_nRefCnt );
+ if ( 0 == nRefCnt )
+ delete this;
+
+ return nRefCnt;
+}
+
+// IEnumFORMATETC->Next
+
+STDMETHODIMP CEnumFormatEtc::Next( ULONG celt, LPFORMATETC rgelt, ULONG* pceltFetched )
+{
+ if ( ( 0 != celt ) && ( NULL == rgelt ) )
+ return E_INVALIDARG;
+
+ ULONG ulFetched = 0;
+ ULONG ulToFetch = celt;
+ HRESULT hr = S_FALSE;
+
+ while( m_nCurrPos < 1 )
+ {
+ rgelt->cfFormat = CF_TEXT;
+ rgelt->ptd = NULL;
+ rgelt->dwAspect = DVASPECT_CONTENT;
+ rgelt->lindex = -1;
+ rgelt->tymed = TYMED_HGLOBAL;
+
+ ++m_nCurrPos;
+ ++rgelt;
+ --ulToFetch;
+ ++ulFetched;
+ }
+
+ if ( ulFetched == celt )
+ hr = S_OK;
+
+ if ( NULL != pceltFetched )
+ {
+ *pceltFetched = ulFetched;
+ }
+
+ return hr;
+}
+
+// IEnumFORMATETC->Skip
+
+STDMETHODIMP CEnumFormatEtc::Skip( ULONG celt )
+{
+ HRESULT hr = S_FALSE;
+
+ /*
+ if ( ( m_nCurrPos + celt ) < m_nClipFormats )
+ {
+ m_nCurrPos += celt;
+ hr = S_OK;
+ }
+ */
+
+ return hr;
+}
+
+// IEnumFORMATETC->Reset
+
+STDMETHODIMP CEnumFormatEtc::Reset( )
+{
+ m_nCurrPos = 0;
+ return S_OK;
+}
+
+// IEnumFORMATETC->Clone
+
+STDMETHODIMP CEnumFormatEtc::Clone( IEnumFORMATETC** ppenum )
+{
+ OSL_ASSERT( NULL != ppenum );
+
+ if ( NULL == ppenum )
+ return E_INVALIDARG;
+
+ HRESULT hr = E_FAIL;
+
+ *ppenum = NULL;
+
+ CEnumFormatEtc* pCEnumFEtc = new CEnumFormatEtc( m_pUnkDataObj );
+ if ( NULL != pCEnumFEtc )
+ {
+ pCEnumFEtc->m_nCurrPos = m_nCurrPos;
+ *ppenum = static_cast< IEnumFORMATETC* >( pCEnumFEtc );
+ static_cast< LPUNKNOWN >( *ppenum )->AddRef( );
+ hr = NOERROR;
+ }
+
+ return hr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/win/dtrans/XTDo.hxx b/vcl/workben/win/dtrans/XTDo.hxx
new file mode 100644
index 0000000000..095987a49d
--- /dev/null
+++ b/vcl/workben/win/dtrans/XTDo.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#if defined _MSC_VER
+#pragma warning(push,1)
+#endif
+#include <windows.h>
+#include <ole2.h>
+#include <objidl.h>
+#if defined _MSC_VER
+#pragma warning(pop)
+#endif
+
+#include <vector>
+
+class EnumFormatEtc;
+
+/*--------------------------------------------------------------------------
+ - the function principle of the windows clipboard:
+ a data provider offers all formats he can deliver on the clipboard
+ a clipboard client ask for the available formats on the clipboard
+ and decides if there is a format he can use
+ if there is one, he requests the data in this format
+
+ - This class inherits from IDataObject and so can be placed on the
+ OleClipboard. The class wraps a transferable object which is the
+ original DataSource
+ - DataFlavors offered by this transferable will be translated into
+ appropriate clipboard formats
+ - if the transferable contains text data always text and unicodetext
+ will be offered or vice versa
+ - text data will be automatically converted between text and unicode text
+ - although the transferable may support text in different charsets
+ (codepages) only text in one codepage can be offered by the clipboard
+
+----------------------------------------------------------------------------*/
+
+class CXTDataObject : public IDataObject
+{
+public:
+ CXTDataObject( );
+
+ // ole interface implementation
+
+ //IUnknown interface methods
+ STDMETHODIMP QueryInterface(REFIID iid, LPVOID* ppvObject);
+ STDMETHODIMP_( ULONG ) AddRef( );
+ STDMETHODIMP_( ULONG ) Release( );
+
+ // IDataObject interface methods
+ STDMETHODIMP GetData( LPFORMATETC pFormatetc, LPSTGMEDIUM pmedium );
+ STDMETHODIMP GetDataHere( LPFORMATETC pFormatetc, LPSTGMEDIUM pmedium );
+ STDMETHODIMP QueryGetData( LPFORMATETC pFormatetc );
+ STDMETHODIMP GetCanonicalFormatEtc( LPFORMATETC pFormatectIn, LPFORMATETC pFormatetcOut );
+ STDMETHODIMP SetData( LPFORMATETC pFormatetc, LPSTGMEDIUM pmedium, BOOL fRelease );
+ STDMETHODIMP EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC** ppenumFormatetc );
+ STDMETHODIMP DAdvise( LPFORMATETC pFormatetc, DWORD advf, LPADVISESINK pAdvSink, DWORD* pdwConnection );
+ STDMETHODIMP DUnadvise( DWORD dwConnection );
+ STDMETHODIMP EnumDAdvise( LPENUMSTATDATA* ppenumAdvise );
+
+ operator IDataObject*( );
+
+private:
+
+private:
+ LONG m_nRefCnt;
+};
+
+class CEnumFormatEtc : public IEnumFORMATETC
+{
+public:
+ explicit CEnumFormatEtc( LPUNKNOWN pUnkDataObj );
+
+ // IUnknown
+ STDMETHODIMP QueryInterface( REFIID iid, LPVOID* ppvObject );
+ STDMETHODIMP_( ULONG ) AddRef( );
+ STDMETHODIMP_( ULONG ) Release( );
+
+ //IEnumFORMATETC
+ STDMETHODIMP Next( ULONG celt, LPFORMATETC rgelt, ULONG* pceltFetched );
+ STDMETHODIMP Skip( ULONG celt );
+ STDMETHODIMP Reset( );
+ STDMETHODIMP Clone( IEnumFORMATETC** ppenum );
+
+private:
+ LONG m_nRefCnt;
+ LPUNKNOWN m_pUnkDataObj;
+ ULONG m_nCurrPos;
+};
+
+typedef CEnumFormatEtc *PCEnumFormatEtc;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/win/dtrans/makefile.mk b/vcl/workben/win/dtrans/makefile.mk
new file mode 100644
index 0000000000..3c82289f8e
--- /dev/null
+++ b/vcl/workben/win/dtrans/makefile.mk
@@ -0,0 +1,81 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+PRJ=..$/..$/..
+
+PRJNAME= dtrans
+TARGET= testwincb
+TARGET1= testmshl
+LIBTARGET= NO
+TARGETTYPE= CUI
+USE_BOUNDCHK=
+TESTCB=TRUE
+
+.IF "$(USE_BOUNDCHK)"=="TR"
+bndchk=tr
+stoponerror=tr
+.ENDIF
+
+# --- Settings -----------------------------------------------------
+
+.INCLUDE : settings.mk
+
+.IF "$(TESTCB)"=="TRUE"
+
+CFLAGS+=-D_WIN32_DCOM -EHsc -Ob0
+
+# --- Files --------------------------------------------------------
+
+OBJFILES= $(OBJ)$/test_wincb.obj
+APP1TARGET= $(TARGET)
+APP1OBJS= $(OBJ)$/test_wincb.obj
+
+APP1STDLIBS= $(SALLIB) \
+ $(CPPULIB) \
+ $(CPPUHELPERLIB) \
+ $(USER32LIB) \
+ $(OLE32LIB)\
+ $(COMDLG32LIB)
+
+APP1LIBS= $(SLB)$/dtutils.lib
+
+APP1NOSAL= TRUE
+
+.ENDIF
+
+.IF "$(TESTCB)"==""
+
+CFLAGS+=/D_WIN32_DCOM /EHsc /Ob0
+
+OBJFILES= $(OBJ)$/testmarshal.obj
+APP1TARGET= $(TARGET1)
+APP1OBJS= $(OBJ)$/testmarshal.obj
+
+APP1STDLIBS= $(SALLIB)\
+ $(USER32LIB)\
+ $(OLE32LIB)\
+ comsupp.lib\
+ $(OLEAUT32LIB)
+
+APP1LIBS=
+APP1NOSAL= TRUE
+
+.ENDIF
+
+# --- Targets ------------------------------------------------------
+.INCLUDE : target.mk
diff --git a/vcl/workben/win/dtrans/test_wincb.cxx b/vcl/workben/win/dtrans/test_wincb.cxx
new file mode 100644
index 0000000000..96839e22a4
--- /dev/null
+++ b/vcl/workben/win/dtrans/test_wincb.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 "../misc/ImplHelper.hxx"
+
+#include <cppuhelper/servicefactory.hxx>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardOwner.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
+#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <osl/diagnose.h>
+
+#include <stdio.h>
+#if defined _MSC_VER
+#pragma warning(push,1)
+#endif
+#include <windows.h>
+#include <objbase.h>
+#if defined _MSC_VER
+#pragma warning(pop)
+#endif
+
+#include <memory>
+
+#include <process.h>
+
+// my defines
+
+#define TEST_CLIPBOARD
+#define RDB_SYSPATH "d:\\projects\\src623\\dtrans\\wntmsci7\\bin\\applicat.rdb"
+#define WINCLIPBOARD_SERVICE_NAME L"com.sun.star.datatransfer.clipboard.SystemClipboard"
+#define WRITE_CB
+#define EVT_MANUAL_RESET TRUE
+#define EVT_INIT_NONSIGNALED FALSE
+#define EVT_NONAME ""
+
+// namespaces
+
+using namespace ::std;
+using namespace ::cppu;
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::datatransfer::clipboard;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::io;
+using namespace ::com::sun::star::lang;
+
+// globales
+
+Reference< XTransferable > rXTransfRead;
+HANDLE g_hEvtThreadWakeup;
+
+class CClipboardListener : public WeakImplHelper < XClipboardListener >
+{
+public:
+ ~CClipboardListener( );
+
+ // XClipboardListener
+
+ virtual void SAL_CALL disposing( const EventObject& Source ) throw(RuntimeException);
+ virtual void SAL_CALL changedContents( const ClipboardEvent& event ) throw( RuntimeException );
+};
+
+CClipboardListener::~CClipboardListener( )
+{
+}
+
+void SAL_CALL CClipboardListener::disposing( const EventObject& Source ) throw(RuntimeException)
+{
+
+}
+
+void SAL_CALL CClipboardListener::changedContents( const ClipboardEvent& event ) throw( RuntimeException )
+{
+ //MessageBox( NULL, TEXT("Clipboard content changed"), TEXT("Info"), MB_OK | MB_ICONINFORMATION );
+}
+
+class CTransferable : public WeakImplHelper< XClipboardOwner, XTransferable >
+{
+public:
+ CTransferable( );
+
+ // XTransferable
+
+ virtual Any SAL_CALL getTransferData( const DataFlavor& aFlavor )
+ throw(UnsupportedFlavorException, IOException, RuntimeException);
+
+ virtual Sequence< DataFlavor > SAL_CALL getTransferDataFlavors( ) throw(RuntimeException);
+
+ virtual sal_Bool SAL_CALL isDataFlavorSupported( const DataFlavor& aFlavor ) throw(RuntimeException);
+
+ // XClipboardOwner
+
+ virtual void SAL_CALL lostOwnership( const Reference< XClipboard >& xClipboard, const Reference< XTransferable >& xTrans )
+ throw(RuntimeException);
+
+private:
+ Sequence< DataFlavor > m_FlavorList;
+ OUString m_Data;
+};
+
+// ctor
+
+CTransferable::CTransferable( ) :
+ m_FlavorList( 1 ),
+ m_Data( OUString("I bought a new bike!") )
+{
+ DataFlavor df;
+
+ //df.MimeType = L"text/plain;charset=utf-16";
+ //df.DataType = cppu::UnoType<OUString>::get();
+
+ df.MimeType = L"text/plain;charset=Windows1252";
+ df.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ m_FlavorList[0] = df;
+}
+
+// getTransferData
+
+Any SAL_CALL CTransferable::getTransferData( const DataFlavor& aFlavor )
+ throw(UnsupportedFlavorException, IOException, RuntimeException)
+{
+ Any anyData;
+
+ /*
+ if ( aFlavor.MimeType == m_FlavorList[0].MimeType )
+ anyData = makeAny( m_Data );
+ */
+ if ( aFlavor.MimeType.equalsIgnoreCase( m_FlavorList[0].MimeType ) )
+ {
+ OString text(
+ m_Data.getStr( ),
+ m_Data.getLength( ),
+ RTL_TEXTENCODING_ASCII_US );
+
+ Sequence< sal_Int8 > textStream( text.getLength( ) + 1 );
+
+ memcpy( textStream.getArray( ), text.getStr( ), textStream.getLength( ) );
+
+ anyData = makeAny( textStream );
+ }
+ else
+ throw UnsupportedFlavorException( );
+
+ return anyData;
+}
+
+// getTransferDataFlavors
+
+Sequence< DataFlavor > SAL_CALL CTransferable::getTransferDataFlavors( )
+ throw(RuntimeException)
+{
+ return m_FlavorList;
+}
+
+// isDataFlavorSupported
+
+sal_Bool SAL_CALL CTransferable::isDataFlavorSupported( const DataFlavor& aFlavor )
+ throw(RuntimeException)
+{
+ sal_Int32 nLength = m_FlavorList.getLength( );
+
+ for ( sal_Int32 i = 0; i < nLength; ++i )
+ if ( m_FlavorList[i].MimeType == aFlavor.MimeType )
+ return sal_True;
+
+ return sal_False;
+}
+
+// lostOwnership
+
+void SAL_CALL CTransferable::lostOwnership(
+ const Reference< XClipboard >& xClipboard, const Reference< XTransferable >& xTrans )
+ throw(RuntimeException)
+{
+ //MessageBox( NULL, TEXT("No longer clipboard owner"), TEXT("Info"), MB_OK | MB_ICONINFORMATION );
+}
+
+// main
+
+int SAL_CALL main( int nArgc, char* Argv[] )
+{
+ // create a multi-threaded apartment; we can test only
+ // with a multithreaded apartment because for a single
+ // threaded apartment we need a message loop to deliver
+ // messages to our XTDataObject
+ //HRESULT hr = CoInitializeEx( NULL, COINIT_MULTITHREADED );
+ (void)CoInitializeEx( NULL, COINIT_APARTMENTTHREADED );
+
+ char buff[6];
+
+ LCID lcid = MAKELCID( MAKELANGID( LANG_GERMAN, SUBLANG_GERMAN ), SORT_DEFAULT );
+
+ BOOL bValid = IsValidLocale( lcid, LCID_SUPPORTED );
+ GetLocaleInfoA( lcid, LOCALE_IDEFAULTANSICODEPAGE, buff, sizeof( buff ) );
+
+ // get the global service-manager
+
+ Reference< XMultiServiceFactory > g_xFactory( createRegistryServiceFactory( RDB_SYSPATH ) );
+
+ // Print a message if an error occurred.
+ if ( !g_xFactory.is( ) )
+ {
+ OSL_FAIL("Can't create RegistryServiceFactory");
+ return(-1);
+ }
+
+ // try to get an Interface to a XFilePicker Service
+
+ Reference< XTransferable > rXTransf( static_cast< XTransferable* >( new CTransferable ) );
+
+ Reference< XClipboard >xClipboard( g_xFactory->createInstance( WINCLIPBOARD_SERVICE_NAME ), UNO_QUERY );
+ if ( !xClipboard.is( ) )
+ {
+ OSL_FAIL( "Error creating Clipboard Service" );
+ return(-1);
+ }
+
+ Reference< XClipboardNotifier > xClipNotifier( xClipboard, UNO_QUERY );
+ Reference< XClipboardListener > rXClipListener( static_cast< XClipboardListener* >( new CClipboardListener() ) );
+ xClipNotifier->addClipboardListener( rXClipListener );
+
+ MessageBox( NULL, TEXT("Go"), TEXT("INFO"), MB_OK|MB_ICONINFORMATION);
+
+ // set new clipboard content
+ xClipboard->setContents( rXTransf, Reference< XClipboardOwner >( rXTransf, UNO_QUERY ) );
+
+ /*
+ MessageBox( NULL, TEXT("Clear content"), TEXT("INFO"), MB_OK|MB_ICONINFORMATION);
+
+ Reference< XClipboardOwner > rXClipOwner;
+ Reference< XTransferable > rXEmptyTransf;
+ xClipboard->setContents( rXEmptyTransf, rXClipOwner );
+ */
+
+ MessageBox( NULL, TEXT("Stop"), TEXT("INFO"), MB_OK|MB_ICONINFORMATION);
+
+ // flush the clipboard content
+ Reference< XFlushableClipboard > rXFlushableClip( xClipboard, UNO_QUERY );
+ rXFlushableClip->flushClipboard( );
+ rXFlushableClip.clear();
+
+ xClipNotifier->removeClipboardListener( rXClipListener );
+ rXClipListener.clear();
+ xClipNotifier.clear();
+
+ // shutdown the service manager
+
+ // Cast factory to XComponent
+ Reference< XComponent > xComponent( g_xFactory, UNO_QUERY );
+
+ if ( !xComponent.is() )
+ OSL_FAIL("Error shutting down");
+
+ // Dispose and clear factory
+ xComponent->dispose();
+ xComponent.clear();
+
+ g_xFactory.clear();
+
+ CoUninitialize( );
+
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/win/dtrans/testmarshal.cxx b/vcl/workben/win/dtrans/testmarshal.cxx
new file mode 100644
index 0000000000..8ef53c0f4d
--- /dev/null
+++ b/vcl/workben/win/dtrans/testmarshal.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 <rtl/ustring.hxx>
+#include <sal/types.h>
+
+#include <stdio.h>
+#if defined _MSC_VER
+#pragma warning(push,1)
+#endif
+#include <windows.h>
+#include <objbase.h>
+#if defined _MSC_VER
+#pragma warning(pop)
+#endif
+
+#include <memory>
+
+#include <process.h>
+#include "XTDo.hxx"
+
+// my defines
+
+#define WRITE_CB
+#define EVT_MANUAL_RESET TRUE
+#define EVT_INIT_NONSIGNALED FALSE
+#define EVT_NONAME ""
+#define WAIT_MSGLOOP
+#define RAW_MARSHALING
+
+// namespaces
+
+using namespace ::std;
+
+// globales
+
+HANDLE g_hEvtThreadWakeup;
+
+#ifdef RAW_MARSHALING
+ HGLOBAL g_hGlob;
+#else
+ IStream* g_pStm;
+#endif
+
+// a thread in another apartment to test apartment transparency
+
+DWORD WINAPI ThreadProc(_In_ LPVOID pParam)
+{
+ // setup another apartment
+ HRESULT hr = OleInitialize( NULL );
+
+ WaitForSingleObject( g_hEvtThreadWakeup, INFINITE );
+
+ IDataObject* pIDo = NULL;
+
+#ifdef RAW_MARSHALING
+
+ IStream* pStm = NULL;
+ hr = CreateStreamOnHGlobal( g_hGlob, FALSE, &pStm );
+ if ( SUCCEEDED( hr ) )
+ {
+ hr = CoUnmarshalInterface(
+ pStm,
+ __uuidof( IDataObject ),
+ (void**)&pIDo );
+
+ hr = pStm->Release( );
+ }
+
+#else
+
+ hr = CoGetInterfaceAndReleaseStream(
+ g_pStm,
+ __uuidof( IDataObject ),
+ (void**)&pIDo
+ );
+
+#endif
+
+ IEnumFORMATETC* pIEEtc;
+ hr = pIDo->EnumFormatEtc( DATADIR_GET, &pIEEtc );
+
+ hr = OleIsCurrentClipboard( pIDo );
+
+ hr = OleFlushClipboard( );
+
+ OleUninitialize( );
+
+ return 0;
+}
+
+// main
+
+int SAL_CALL main( int nArgc, char* Argv[] )
+{
+ HRESULT hr = OleInitialize( NULL );
+
+ g_hEvtThreadWakeup = CreateEvent( 0,
+ EVT_MANUAL_RESET,
+ EVT_INIT_NONSIGNALED,
+ EVT_NONAME );
+
+ DWORD uThreadId;
+
+ // create a thread in another apartment
+ HANDLE hThread = CreateThread( NULL, 0, ThreadProc, NULL, 0, &uThreadId );
+
+ IDataObject* pIDo = new CXTDataObject( );
+
+ hr = OleSetClipboard( pIDo );
+ hr = E_FAIL;
+
+ hr = OleIsCurrentClipboard( pIDo );
+
+ //hr = OleGetClipboard( &pIDo );
+ if ( SUCCEEDED( hr ) )
+ {
+#ifdef RAW_MARSHALING
+
+ IStream* pStm = NULL;
+
+ hr = CreateStreamOnHGlobal( 0, FALSE, &pStm );
+ if ( SUCCEEDED( hr ) )
+ {
+ hr = CoMarshalInterface(
+ pStm,
+ __uuidof( IDataObject ),
+ pIDo,
+ MSHCTX_INPROC,
+ 0,
+ MSHLFLAGS_NORMAL );
+ if ( SUCCEEDED( hr ) )
+ hr = GetHGlobalFromStream( pStm, &g_hGlob );
+
+ hr = pStm->Release( );
+ }
+
+#else
+
+ hr = CoMarshalInterThreadInterfaceInStream(
+ __uuidof( IDataObject ),
+ pIDo,
+ &g_pStm );
+
+#endif
+
+ if ( SUCCEEDED( hr ) )
+ {
+ // wakeup the thread and waiting util it ends
+ SetEvent( g_hEvtThreadWakeup );
+
+#ifdef WAIT_MSGLOOP
+
+ BOOL bContinue = TRUE;
+
+ while( bContinue )
+ {
+ DWORD dwResult = WaitForMultipleObjects(
+ 1,
+ &hThread,
+ TRUE,
+ 0 );
+
+ if ( WAIT_OBJECT_0 == dwResult )
+ {
+ bContinue = FALSE;
+ }
+ else
+ {
+ MSG msg;
+ while( PeekMessage(
+ &msg,
+ NULL,
+ 0,
+ 0,
+ PM_REMOVE ) )
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ }
+ } // while
+
+#endif
+
+ } // if
+ } // if
+
+ OleFlushClipboard( );
+
+ OleUninitialize( );
+
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/wksfuzzer.cxx b/vcl/workben/wksfuzzer.cxx
new file mode 100644
index 0000000000..a12fe2f208
--- /dev/null
+++ b/vcl/workben/wksfuzzer.cxx
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/FilterConfigItem.hxx>
+#include <com/sun/star/ucb/XContentProvider.hpp>
+#include <com/sun/star/ucb/XUniversalContentBroker.hpp>
+#include "commonfuzzer.hxx"
+
+extern "C" void* ScCreateDialogFactory() { return nullptr; }
+
+extern "C" bool TestImportWKS(SvStream& rStream);
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
+{
+ if (__lsan_disable)
+ __lsan_disable();
+
+ CommonInitialize(argc, argv);
+
+ // initialise unconfigured UCB:
+ css::uno::Reference<css::ucb::XUniversalContentBroker> xUcb(
+ comphelper::getProcessServiceFactory()->createInstance(
+ "com.sun.star.ucb.UniversalContentBroker"),
+ css::uno::UNO_QUERY_THROW);
+ css::uno::Sequence<css::uno::Any> aArgs{ css::uno::Any(OUString("NoConfig")) };
+ css::uno::Reference<css::ucb::XContentProvider> xFileProvider(
+ comphelper::getProcessServiceFactory()->createInstanceWithArguments(
+ "com.sun.star.ucb.FileContentProvider", aArgs),
+ css::uno::UNO_QUERY_THROW);
+ xUcb->registerContentProvider(xFileProvider, "file", true);
+
+ if (__lsan_enable)
+ __lsan_enable();
+
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportWKS(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/wksfuzzer.options b/vcl/workben/wksfuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/wksfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/wmffuzzer.cxx b/vcl/workben/wmffuzzer.cxx
new file mode 100644
index 0000000000..371798fc94
--- /dev/null
+++ b/vcl/workben/wmffuzzer.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/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/wmf.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * com_sun_star_i18n_LocaleDataImpl_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_get_implementation( void *, void * );
+void * emfio_emfreader_XEmfParser_get_implementation( void *, void * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { "com_sun_star_i18n_LocaleDataImpl_get_implementation", com_sun_star_i18n_LocaleDataImpl_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_Unicode_get_implementation", com_sun_star_i18n_BreakIterator_Unicode_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_get_implementation", com_sun_star_i18n_BreakIterator_get_implementation },
+ { "emfio_emfreader_XEmfParser_get_implementation", emfio_emfreader_XEmfParser_get_implementation},
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ GDIMetaFile aGDIMetaFile;
+ (void)ReadWindowMetafile(aStream, aGDIMetaFile);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/wmffuzzer.options b/vcl/workben/wmffuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/wmffuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/ww2fuzzer.cxx b/vcl/workben/ww2fuzzer.cxx
new file mode 100644
index 0000000000..5ffe0445c3
--- /dev/null
+++ b/vcl/workben/ww2fuzzer.cxx
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * i18npool_component_getFactory( const char* , void* , void* );
+
+void * com_sun_star_comp_framework_Desktop_get_implementation( void *, void * );
+void * com_sun_star_i18n_LocaleDataImpl_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_get_implementation( void *, void * );
+void * SfxDocumentMetaData_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_get_implementation( void *, void * );
+void * com_sun_star_i18n_NativeNumberSupplier_get_implementation( void *, void * );
+void * com_sun_star_i18n_NumberFormatCodeMapper_get_implementation( void *, void * );
+void * com_sun_star_comp_graphic_GraphicProvider_get_implementation( void *, void * );
+void * IndexedPropertyValuesContainer_get_implementation( void *, void * );
+void * com_sun_star_comp_uui_UUIInteractionHandler_get_implementation( void *, void * );
+void * com_sun_star_i18n_Transliteration_get_implementation( void *, void * );
+void * com_sun_star_text_DefaultNumberingProvider_get_implementation( void *, void * );
+void * com_sun_star_comp_uri_UriReferenceFactory_get_implementation( void *, void * );
+void * emfio_emfreader_XEmfParser_get_implementation( void *, void * );
+void * i18npool_CalendarImpl_get_implementation(void*, void*);
+void * i18npool_Calendar_gregorian_get_implementation( void *, void * );
+void * unoxml_rdfRepository_get_implementation( void *, void * );
+void * unoxml_CURI_get_implementation( void *, void * );
+void * unoxml_CLiteral_get_implementation( void *, void * );
+void * unoxml_CBlankNode_get_implementation( void *, void * );
+void * unoxml_CXPathAPI_get_implementation( void *, void * );
+void * unoxml_CSAXDocumentBuilder_get_implementation( void *, void * );
+void * unoxml_CDocumentBuilder_get_implementation( void *, void * );
+void * ucb_UcbCommandEnvironment_get_implementation( void *, void * );
+void * ucb_UcbContentProviderProxyFactory_get_implementation( void *, void * );
+void * ucb_UcbPropertiesManager_get_implementation( void *, void * );
+void * ucb_UcbStore_get_implementation( void *, void * );
+void * ucb_UniversalContentBroker_get_implementation( void *, void * );
+void * ucb_OFileAccess_get_implementation( void *, void * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libi18npoollo.a", i18npool_component_getFactory },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { "com_sun_star_comp_framework_Desktop_get_implementation", com_sun_star_comp_framework_Desktop_get_implementation },
+ { "com_sun_star_i18n_LocaleDataImpl_get_implementation", com_sun_star_i18n_LocaleDataImpl_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_Unicode_get_implementation", com_sun_star_i18n_BreakIterator_Unicode_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_get_implementation", com_sun_star_i18n_BreakIterator_get_implementation },
+ { "SfxDocumentMetaData_get_implementation", SfxDocumentMetaData_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_Unicode_get_implementation", com_sun_star_i18n_CharacterClassification_Unicode_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_get_implementation", com_sun_star_i18n_CharacterClassification_get_implementation },
+ { "com_sun_star_i18n_NativeNumberSupplier_get_implementation", com_sun_star_i18n_NativeNumberSupplier_get_implementation },
+ { "com_sun_star_i18n_NumberFormatCodeMapper_get_implementation", com_sun_star_i18n_NumberFormatCodeMapper_get_implementation },
+ { "com_sun_star_comp_graphic_GraphicProvider_get_implementation", com_sun_star_comp_graphic_GraphicProvider_get_implementation },
+ { "IndexedPropertyValuesContainer_get_implementation", IndexedPropertyValuesContainer_get_implementation },
+ { "com_sun_star_comp_uui_UUIInteractionHandler_get_implementation", com_sun_star_comp_uui_UUIInteractionHandler_get_implementation },
+ { "com_sun_star_i18n_Transliteration_get_implementation", com_sun_star_i18n_Transliteration_get_implementation },
+ { "com_sun_star_text_DefaultNumberingProvider_get_implementation", com_sun_star_text_DefaultNumberingProvider_get_implementation },
+ { "com_sun_star_comp_uri_UriReferenceFactory_get_implementation", com_sun_star_comp_uri_UriReferenceFactory_get_implementation},
+ { "emfio_emfreader_XEmfParser_get_implementation", emfio_emfreader_XEmfParser_get_implementation},
+ { "i18npool_CalendarImpl_get_implementation", i18npool_CalendarImpl_get_implementation},
+ { "i18npool_Calendar_gregorian_get_implementation", i18npool_Calendar_gregorian_get_implementation},
+ { "unoxml_rdfRepository_get_implementation", unoxml_rdfRepository_get_implementation },
+ { "unoxml_CURI_get_implementation", unoxml_CURI_get_implementation },
+ { "unoxml_CLiteral_get_implementation", unoxml_CLiteral_get_implementation },
+ { "unoxml_CBlankNode_get_implementation", unoxml_CBlankNode_get_implementation },
+ { "unoxml_CXPathAPI_get_implementation", unoxml_CXPathAPI_get_implementation },
+ { "unoxml_CSAXDocumentBuilder_get_implementation", unoxml_CSAXDocumentBuilder_get_implementation },
+ { "unoxml_CDocumentBuilder_get_implementation", unoxml_CDocumentBuilder_get_implementation },
+ { "ucb_UcbCommandEnvironment_get_implementation", ucb_UcbCommandEnvironment_get_implementation, },
+ { "ucb_UcbContentProviderProxyFactory_get_implementation", ucb_UcbContentProviderProxyFactory_get_implementation },
+ { "ucb_UcbPropertiesManager_get_implementation", ucb_UcbPropertiesManager_get_implementation },
+ { "ucb_UcbStore_get_implementation", ucb_UcbStore_get_implementation },
+ { "ucb_UniversalContentBroker_get_implementation", ucb_UniversalContentBroker_get_implementation },
+ { "ucb_OFileAccess_get_implementation", ucb_OFileAccess_get_implementation },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" void* SwCreateDialogFactory()
+{
+ return nullptr;
+}
+
+extern "C" bool TestImportWW2(SvStream &rStream);
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportWW2(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/ww2fuzzer.options b/vcl/workben/ww2fuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/ww2fuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/ww6fuzzer.cxx b/vcl/workben/ww6fuzzer.cxx
new file mode 100644
index 0000000000..a17be9105a
--- /dev/null
+++ b/vcl/workben/ww6fuzzer.cxx
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * i18npool_component_getFactory( const char* , void* , void* );
+
+void * com_sun_star_comp_framework_Desktop_get_implementation( void *, void * );
+void * com_sun_star_i18n_LocaleDataImpl_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_get_implementation( void *, void * );
+void * SfxDocumentMetaData_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_get_implementation( void *, void * );
+void * com_sun_star_i18n_NativeNumberSupplier_get_implementation( void *, void * );
+void * com_sun_star_i18n_NumberFormatCodeMapper_get_implementation( void *, void * );
+void * com_sun_star_comp_graphic_GraphicProvider_get_implementation( void *, void * );
+void * IndexedPropertyValuesContainer_get_implementation( void *, void * );
+void * com_sun_star_comp_uui_UUIInteractionHandler_get_implementation( void *, void * );
+void * com_sun_star_comp_comphelper_OPropertyBag( void *, void * );
+void * com_sun_star_i18n_Transliteration_get_implementation( void *, void * );
+void * com_sun_star_text_DefaultNumberingProvider_get_implementation( void *, void * );
+void * com_sun_star_comp_uri_UriReferenceFactory_get_implementation( void *, void * );
+void * emfio_emfreader_XEmfParser_get_implementation( void *, void * );
+void * i18npool_CalendarImpl_get_implementation(void*, void*);
+void * i18npool_Calendar_gregorian_get_implementation( void *, void * );
+void * unoxml_rdfRepository_get_implementation( void *, void * );
+void * unoxml_CURI_get_implementation( void *, void * );
+void * unoxml_CLiteral_get_implementation( void *, void * );
+void * unoxml_CBlankNode_get_implementation( void *, void * );
+void * unoxml_CXPathAPI_get_implementation( void *, void * );
+void * unoxml_CSAXDocumentBuilder_get_implementation( void *, void * );
+void * unoxml_CDocumentBuilder_get_implementation( void *, void * );
+void * ucb_UcbCommandEnvironment_get_implementation( void *, void * );
+void * ucb_UcbContentProviderProxyFactory_get_implementation( void *, void * );
+void * ucb_UcbPropertiesManager_get_implementation( void *, void * );
+void * ucb_UcbStore_get_implementation( void *, void * );
+void * ucb_UniversalContentBroker_get_implementation( void *, void * );
+void * ucb_OFileAccess_get_implementation( void *, void * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libi18npoollo.a", i18npool_component_getFactory },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { "com_sun_star_comp_framework_Desktop_get_implementation", com_sun_star_comp_framework_Desktop_get_implementation },
+ { "com_sun_star_i18n_LocaleDataImpl_get_implementation", com_sun_star_i18n_LocaleDataImpl_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_Unicode_get_implementation", com_sun_star_i18n_BreakIterator_Unicode_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_get_implementation", com_sun_star_i18n_BreakIterator_get_implementation },
+ { "SfxDocumentMetaData_get_implementation", SfxDocumentMetaData_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_Unicode_get_implementation", com_sun_star_i18n_CharacterClassification_Unicode_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_get_implementation", com_sun_star_i18n_CharacterClassification_get_implementation },
+ { "com_sun_star_i18n_NativeNumberSupplier_get_implementation", com_sun_star_i18n_NativeNumberSupplier_get_implementation },
+ { "com_sun_star_i18n_NumberFormatCodeMapper_get_implementation", com_sun_star_i18n_NumberFormatCodeMapper_get_implementation },
+ { "com_sun_star_comp_graphic_GraphicProvider_get_implementation", com_sun_star_comp_graphic_GraphicProvider_get_implementation },
+ { "IndexedPropertyValuesContainer_get_implementation", IndexedPropertyValuesContainer_get_implementation },
+ { "com_sun_star_comp_uui_UUIInteractionHandler_get_implementation", com_sun_star_comp_uui_UUIInteractionHandler_get_implementation },
+ { "com_sun_star_comp_comphelper_OPropertyBag", com_sun_star_comp_comphelper_OPropertyBag },
+ { "com_sun_star_i18n_Transliteration_get_implementation", com_sun_star_i18n_Transliteration_get_implementation },
+ { "com_sun_star_text_DefaultNumberingProvider_get_implementation", com_sun_star_text_DefaultNumberingProvider_get_implementation },
+ { "com_sun_star_comp_uri_UriReferenceFactory_get_implementation", com_sun_star_comp_uri_UriReferenceFactory_get_implementation},
+ { "emfio_emfreader_XEmfParser_get_implementation", emfio_emfreader_XEmfParser_get_implementation},
+ { "i18npool_CalendarImpl_get_implementation", i18npool_CalendarImpl_get_implementation },
+ { "i18npool_Calendar_gregorian_get_implementation", i18npool_Calendar_gregorian_get_implementation},
+ { "unoxml_rdfRepository_get_implementation", unoxml_rdfRepository_get_implementation },
+ { "unoxml_CURI_get_implementation", unoxml_CURI_get_implementation },
+ { "unoxml_CLiteral_get_implementation", unoxml_CLiteral_get_implementation },
+ { "unoxml_CBlankNode_get_implementation", unoxml_CBlankNode_get_implementation },
+ { "unoxml_CXPathAPI_get_implementation", unoxml_CXPathAPI_get_implementation },
+ { "unoxml_CSAXDocumentBuilder_get_implementation", unoxml_CSAXDocumentBuilder_get_implementation },
+ { "unoxml_CDocumentBuilder_get_implementation", unoxml_CDocumentBuilder_get_implementation },
+ { "ucb_UcbCommandEnvironment_get_implementation", ucb_UcbCommandEnvironment_get_implementation, },
+ { "ucb_UcbContentProviderProxyFactory_get_implementation", ucb_UcbContentProviderProxyFactory_get_implementation },
+ { "ucb_UcbPropertiesManager_get_implementation", ucb_UcbPropertiesManager_get_implementation },
+ { "ucb_UcbStore_get_implementation", ucb_UcbStore_get_implementation },
+ { "ucb_UniversalContentBroker_get_implementation", ucb_UniversalContentBroker_get_implementation },
+ { "ucb_OFileAccess_get_implementation", ucb_OFileAccess_get_implementation },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" void* SwCreateDialogFactory()
+{
+ return nullptr;
+}
+
+extern "C" bool TestImportWW6(SvStream &rStream);
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportWW6(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/ww6fuzzer.options b/vcl/workben/ww6fuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/ww6fuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/ww8fuzzer.cxx b/vcl/workben/ww8fuzzer.cxx
new file mode 100644
index 0000000000..113e798610
--- /dev/null
+++ b/vcl/workben/ww8fuzzer.cxx
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+extern "C" {
+void * i18npool_component_getFactory( const char* , void* , void* );
+
+void * com_sun_star_comp_framework_Desktop_get_implementation( void *, void * );
+void * com_sun_star_i18n_LocaleDataImpl_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_BreakIterator_get_implementation( void *, void * );
+void * SfxDocumentMetaData_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_Unicode_get_implementation( void *, void * );
+void * com_sun_star_i18n_CharacterClassification_get_implementation( void *, void * );
+void * com_sun_star_i18n_NativeNumberSupplier_get_implementation( void *, void * );
+void * com_sun_star_i18n_NumberFormatCodeMapper_get_implementation( void *, void * );
+void * com_sun_star_comp_graphic_GraphicProvider_get_implementation( void *, void * );
+void * IndexedPropertyValuesContainer_get_implementation( void *, void * );
+void * com_sun_star_comp_uui_UUIInteractionHandler_get_implementation( void *, void * );
+void * com_sun_star_comp_comphelper_OPropertyBag( void *, void * );
+void * com_sun_star_i18n_Transliteration_get_implementation( void *, void * );
+void * com_sun_star_text_DefaultNumberingProvider_get_implementation( void *, void * );
+void * com_sun_star_comp_uri_UriReferenceFactory_get_implementation( void *, void * );
+void * emfio_emfreader_XEmfParser_get_implementation( void *, void * );
+void * i18npool_CalendarImpl_get_implementation(void*, void*);
+void * i18npool_Calendar_gregorian_get_implementation( void *, void * );
+void * unoxml_rdfRepository_get_implementation( void *, void * );
+void * unoxml_CURI_get_implementation( void *, void * );
+void * unoxml_CLiteral_get_implementation( void *, void * );
+void * unoxml_CBlankNode_get_implementation( void *, void * );
+void * unoxml_CXPathAPI_get_implementation( void *, void * );
+void * unoxml_CSAXDocumentBuilder_get_implementation( void *, void * );
+void * unoxml_CDocumentBuilder_get_implementation( void *, void * );
+void * ucb_UcbCommandEnvironment_get_implementation( void *, void * );
+void * ucb_UcbContentProviderProxyFactory_get_implementation( void *, void * );
+void * ucb_UcbPropertiesManager_get_implementation( void *, void * );
+void * ucb_UcbStore_get_implementation( void *, void * );
+void * ucb_UniversalContentBroker_get_implementation( void *, void * );
+void * ucb_OFileAccess_get_implementation( void *, void * );
+}
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { "libi18npoollo.a", i18npool_component_getFactory },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { "com_sun_star_comp_framework_Desktop_get_implementation", com_sun_star_comp_framework_Desktop_get_implementation },
+ { "com_sun_star_i18n_LocaleDataImpl_get_implementation", com_sun_star_i18n_LocaleDataImpl_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_Unicode_get_implementation", com_sun_star_i18n_BreakIterator_Unicode_get_implementation },
+ { "com_sun_star_i18n_BreakIterator_get_implementation", com_sun_star_i18n_BreakIterator_get_implementation },
+ { "SfxDocumentMetaData_get_implementation", SfxDocumentMetaData_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_Unicode_get_implementation", com_sun_star_i18n_CharacterClassification_Unicode_get_implementation },
+ { "com_sun_star_i18n_CharacterClassification_get_implementation", com_sun_star_i18n_CharacterClassification_get_implementation },
+ { "com_sun_star_i18n_NativeNumberSupplier_get_implementation", com_sun_star_i18n_NativeNumberSupplier_get_implementation },
+ { "com_sun_star_i18n_NumberFormatCodeMapper_get_implementation", com_sun_star_i18n_NumberFormatCodeMapper_get_implementation },
+ { "com_sun_star_comp_graphic_GraphicProvider_get_implementation", com_sun_star_comp_graphic_GraphicProvider_get_implementation },
+ { "IndexedPropertyValuesContainer_get_implementation", IndexedPropertyValuesContainer_get_implementation },
+ { "com_sun_star_comp_uui_UUIInteractionHandler_get_implementation", com_sun_star_comp_uui_UUIInteractionHandler_get_implementation },
+ { "com_sun_star_comp_comphelper_OPropertyBag", com_sun_star_comp_comphelper_OPropertyBag },
+ { "com_sun_star_i18n_Transliteration_get_implementation", com_sun_star_i18n_Transliteration_get_implementation },
+ { "com_sun_star_text_DefaultNumberingProvider_get_implementation", com_sun_star_text_DefaultNumberingProvider_get_implementation },
+ { "com_sun_star_comp_uri_UriReferenceFactory_get_implementation", com_sun_star_comp_uri_UriReferenceFactory_get_implementation},
+ { "emfio_emfreader_XEmfParser_get_implementation", emfio_emfreader_XEmfParser_get_implementation},
+ { "i18npool_CalendarImpl_get_implementation", i18npool_CalendarImpl_get_implementation},
+ { "i18npool_Calendar_gregorian_get_implementation", i18npool_Calendar_gregorian_get_implementation},
+ { "unoxml_rdfRepository_get_implementation", unoxml_rdfRepository_get_implementation },
+ { "unoxml_CURI_get_implementation", unoxml_CURI_get_implementation },
+ { "unoxml_CLiteral_get_implementation", unoxml_CLiteral_get_implementation },
+ { "unoxml_CBlankNode_get_implementation", unoxml_CBlankNode_get_implementation },
+ { "unoxml_CXPathAPI_get_implementation", unoxml_CXPathAPI_get_implementation },
+ { "unoxml_CSAXDocumentBuilder_get_implementation", unoxml_CSAXDocumentBuilder_get_implementation },
+ { "unoxml_CDocumentBuilder_get_implementation", unoxml_CDocumentBuilder_get_implementation },
+ { "ucb_UcbCommandEnvironment_get_implementation", ucb_UcbCommandEnvironment_get_implementation, },
+ { "ucb_UcbContentProviderProxyFactory_get_implementation", ucb_UcbContentProviderProxyFactory_get_implementation },
+ { "ucb_UcbPropertiesManager_get_implementation", ucb_UcbPropertiesManager_get_implementation },
+ { "ucb_UcbStore_get_implementation", ucb_UcbStore_get_implementation },
+ { "ucb_UniversalContentBroker_get_implementation", ucb_UniversalContentBroker_get_implementation },
+ { "ucb_OFileAccess_get_implementation", ucb_OFileAccess_get_implementation },
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" void* SwCreateDialogFactory()
+{
+ return nullptr;
+}
+
+extern "C" bool TestImportWW8(SvStream &rStream);
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportWW8(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/ww8fuzzer.options b/vcl/workben/ww8fuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/ww8fuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/xbmfuzzer.cxx b/vcl/workben/xbmfuzzer.cxx
new file mode 100644
index 0000000000..bb261f60a9
--- /dev/null
+++ b/vcl/workben/xbmfuzzer.cxx
@@ -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/.
+ */
+
+#include <tools/stream.hxx>
+#include <../source/filter/ixbm/xbmread.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportXBM(aStream, aGraphic);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/xbmfuzzer.options b/vcl/workben/xbmfuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/xbmfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/xlsfuzzer.cxx b/vcl/workben/xlsfuzzer.cxx
new file mode 100644
index 0000000000..4dd38ea228
--- /dev/null
+++ b/vcl/workben/xlsfuzzer.cxx
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/FilterConfigItem.hxx>
+#include <com/sun/star/ucb/XContentProvider.hpp>
+#include <com/sun/star/ucb/XUniversalContentBroker.hpp>
+#include "commonfuzzer.hxx"
+
+extern "C" void* ScCreateDialogFactory() { return nullptr; }
+
+extern "C" bool TestImportXLS(SvStream& rStream);
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
+{
+ if (__lsan_disable)
+ __lsan_disable();
+
+ CommonInitialize(argc, argv);
+
+ // initialise unconfigured UCB:
+ css::uno::Reference<css::ucb::XUniversalContentBroker> xUcb(
+ comphelper::getProcessServiceFactory()->createInstance(
+ "com.sun.star.ucb.UniversalContentBroker"),
+ css::uno::UNO_QUERY_THROW);
+ css::uno::Sequence<css::uno::Any> aArgs{ css::uno::Any(OUString("NoConfig")) };
+ css::uno::Reference<css::ucb::XContentProvider> xFileProvider(
+ comphelper::getProcessServiceFactory()->createInstanceWithArguments(
+ "com.sun.star.ucb.FileContentProvider", aArgs),
+ css::uno::UNO_QUERY_THROW);
+ xUcb->registerContentProvider(xFileProvider, "file", true);
+
+ if (__lsan_enable)
+ __lsan_enable();
+
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportXLS(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/xlsfuzzer.options b/vcl/workben/xlsfuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/xlsfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/xlsxfuzzer.cxx b/vcl/workben/xlsxfuzzer.cxx
new file mode 100644
index 0000000000..a325bd69fa
--- /dev/null
+++ b/vcl/workben/xlsxfuzzer.cxx
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+
+extern "C" void* ScCreateDialogFactory() { return nullptr; }
+
+extern "C" bool TestImportXLSX(SvStream& rStream);
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportXLSX(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/xlsxfuzzer.options b/vcl/workben/xlsxfuzzer.options
new file mode 100644
index 0000000000..e8c2b812b0
--- /dev/null
+++ b/vcl/workben/xlsxfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 98304
diff --git a/vcl/workben/xpmfuzzer.cxx b/vcl/workben/xpmfuzzer.cxx
new file mode 100644
index 0000000000..bd9413fb52
--- /dev/null
+++ b/vcl/workben/xpmfuzzer.cxx
@@ -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/.
+ */
+
+#include <tools/stream.hxx>
+#include <filter/XpmReader.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*)
+{
+ return nullptr;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportXPM(aStream, aGraphic);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/xpmfuzzer.options b/vcl/workben/xpmfuzzer.options
new file mode 100644
index 0000000000..678d526b1e
--- /dev/null
+++ b/vcl/workben/xpmfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/vcl/workben/zipfuzzer.cxx b/vcl/workben/zipfuzzer.cxx
new file mode 100644
index 0000000000..9474c2df2c
--- /dev/null
+++ b/vcl/workben/zipfuzzer.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 <tools/stream.hxx>
+#include "commonfuzzer.hxx"
+
+#include <config_features.h>
+#include <com/sun/star/io/WrongFormatException.hpp>
+#include <osl/detail/component-mapping.h>
+
+extern "C" bool TestImportZip(SvStream& rStream);
+
+const lib_to_factory_mapping* lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = { { 0, 0 } };
+
+ return map;
+}
+
+const lib_to_constructor_mapping* lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = { { 0, 0 } };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*) { return nullptr; }
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ (void)TestImportZip(aStream);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/zipfuzzer.options b/vcl/workben/zipfuzzer.options
new file mode 100644
index 0000000000..13fd47f9cb
--- /dev/null
+++ b/vcl/workben/zipfuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 32000
+dict = zip.dict